(files)
| 100 | * scores into the self-query eval fixture's own corpus and moves its numbers. |
| 101 | */ |
| 102 | export function parseSession(files) { |
| 103 | const events = []; |
| 104 | for (const f of files) { |
| 105 | for (const line of readFileSync(f, 'utf8').split('\n')) { |
| 106 | if (!line) continue; |
| 107 | try { events.push(JSON.parse(line)); } catch { /* partial line */ } |
| 108 | } |
| 109 | } |
| 110 | |
| 111 | const toolCalls = []; // display sequence |
| 112 | const nameById = new Map(); // tool_use_id -> tool name |
| 113 | const cliById = new Set(); // tool_use_ids that tried to run the codegraph CLI |
| 114 | const counts = {}; // tool name -> calls |
| 115 | // Attempts vs successes: run-all.sh's hook DENIES CLI invocations, and a |
| 116 | // denied attempt puts no codegraph output in the window. Only a call that |
| 117 | // actually returned content contaminates the arm. |
| 118 | let initTools = null, result = null, raced = false, cliCalls = 0, cliContaminated = 0; |
| 119 | const results = []; // one `result` event per session segment (multi-turn) |
| 120 | let compactions = 0; |
| 121 | // Raw codegraph_explore response text, in call order. Feeds the envelope view |
| 122 | // (see formatEnvelope) — kept here rather than re-parsed from the log later so |
| 123 | // a multi-segment session's responses stay in one ordered list. |
| 124 | const exploreTexts = []; |
| 125 | |
| 126 | // A timeline of everything appended to the context, in order. `req` entries |
| 127 | // are assistant requests (carrying that request's ctx); `add` entries are |
| 128 | // characters appended (assistant output blocks, tool results, user text). |
| 129 | const timeline = []; |
| 130 | const seenMsgIds = new Set(); |
| 131 | |
| 132 | for (const ev of events) { |
| 133 | if (ev.type === 'system' && ev.subtype === 'init') { |
| 134 | initTools = (ev.tools || []).filter((t) => /codegraph/.test(t)); |
| 135 | } |
| 136 | if (ev.type === 'system' && (ev.subtype === 'compact_boundary' || ev.subtype === 'compaction')) { |
| 137 | compactions++; |
| 138 | timeline.push({ kind: 'compact' }); |
| 139 | } |
| 140 | if (ev.type === 'assistant' && ev.message) { |
| 141 | const id = ev.message.id; |
| 142 | // One event per content block, same id + same usage: count usage once, |
| 143 | // but take the content blocks from every event that carries the id. |
| 144 | if (id && !seenMsgIds.has(id)) { |
| 145 | seenMsgIds.add(id); |
| 146 | const u = ev.message.usage || {}; |
| 147 | const ctx = (u.input_tokens || 0) + (u.cache_read_input_tokens || 0) + (u.cache_creation_input_tokens || 0); |
| 148 | timeline.push({ kind: 'req', ctx, out: u.output_tokens || 0 }); |
| 149 | } |
| 150 | for (const b of ev.message.content || []) { |
| 151 | timeline.push({ kind: 'add', family: null, chars: assistantBlockChars(b) }); |
| 152 | if (b.type === 'tool_use') { |
| 153 | nameById.set(b.id, b.name); |
| 154 | counts[b.name] = (counts[b.name] || 0) + 1; |
| 155 | let detail = ''; |
| 156 | if (b.name === 'Task') detail = ` [subagent_type=${b.input?.subagent_type ?? '?'}] ${(b.input?.description ?? '').slice(0, 40)}`; |
| 157 | else if (/codegraph/.test(b.name)) detail = ` ${JSON.stringify(b.input?.query ?? b.input?.task ?? b.input?.symbol ?? '').slice(0, 60)}`; |
| 158 | else if (b.name === 'Bash') { |
| 159 | detail = ` ${(b.input?.command ?? '').slice(0, 50)}`; |
no test coverage detected