cogpit-memory 0.1.9 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -57,7 +57,7 @@ var Database = import_better_sqlite3.default;
57
57
  var import_node_fs = require("node:fs");
58
58
  var import_node_path = require("node:path");
59
59
 
60
- // src/lib/turnBuilder.ts
60
+ // src/lib/messageTypeGuards.ts
61
61
  function isUserMessage(msg) {
62
62
  return msg.type === "user";
63
63
  }
@@ -73,9 +73,45 @@ function isSystemMessage(msg) {
73
73
  function isSummaryMessage(msg) {
74
74
  return msg.type === "summary";
75
75
  }
76
+ function isQueueOperationMessage(msg) {
77
+ return msg.type === "queue-operation";
78
+ }
79
+ function isAttachmentMessage(msg) {
80
+ return msg.type === "attachment";
81
+ }
82
+ function isCompactSummaryMessage(msg) {
83
+ return msg.type === "user" && msg.isCompactSummary === true;
84
+ }
76
85
  function isCompactBoundary(msg) {
77
86
  return msg.type === "system" && msg.subtype === "compact_boundary";
78
87
  }
88
+
89
+ // src/lib/agentEnvelope.ts
90
+ var ENVELOPE_RE = /<(agent-message|teammate-message)([^>]*)>([\s\S]*?)<\/\1>/g;
91
+ var SENDER_PATTERNS = [
92
+ /\bteammate_id=(?:"([^"]*)"|'([^']*)')/,
93
+ /\bfrom=(?:"([^"]*)"|'([^']*)')/
94
+ ];
95
+ function readSender(attrs) {
96
+ for (const pattern of SENDER_PATTERNS) {
97
+ const m = attrs.match(pattern);
98
+ const value = m?.[1] ?? m?.[2];
99
+ if (value) return value;
100
+ }
101
+ return null;
102
+ }
103
+ function parseAgentEnvelope(text) {
104
+ let sender = null;
105
+ let matched = false;
106
+ const unwrapped = text.replace(ENVELOPE_RE, (_full, _tag, attrs, inner) => {
107
+ matched = true;
108
+ if (sender === null) sender = readSender(attrs);
109
+ return inner.trim();
110
+ });
111
+ return { sender, matched, body: matched ? unwrapped.trim() : text };
112
+ }
113
+
114
+ // src/lib/turnBuilder.ts
79
115
  function extractTextFromContent(content) {
80
116
  if (typeof content === "string") return content;
81
117
  return content.filter((b) => b.type === "text").map((b) => b.text).join("\n");
@@ -88,6 +124,62 @@ function extractToolResultText(content) {
88
124
  return "";
89
125
  }).filter(Boolean).join("\n");
90
126
  }
127
+ function isVisibleQueuedPrompt(content) {
128
+ if (!content?.trim()) return false;
129
+ const trimmed = content.trimStart();
130
+ if (!trimmed.startsWith("<")) return true;
131
+ return !trimmed.includes("<task-notification>") && !trimmed.startsWith("<system-reminder>") && !trimmed.startsWith("<local-command-");
132
+ }
133
+ var TASK_NOTIFICATION_BLOCK_RE = /<task-notification>[\s\S]*?<\/task-notification>/g;
134
+ var ANY_TAG_RE = /<[^>]*>/g;
135
+ function isTaskNotificationRecord(msg) {
136
+ if (msg.origin?.kind === "task-notification") return true;
137
+ const text = extractTextFromContent(msg.message.content);
138
+ if (!text.includes("<task-notification>")) return false;
139
+ return text.replace(TASK_NOTIFICATION_BLOCK_RE, "").replace(ANY_TAG_RE, "").trim() === "";
140
+ }
141
+ function queuedCommandPrompt(msg) {
142
+ if (!isAttachmentMessage(msg)) return null;
143
+ const attachment = msg.attachment;
144
+ if (!attachment || attachment.type !== "queued_command") return null;
145
+ if (attachment.commandMode !== "prompt") return null;
146
+ const prompt = attachment.prompt;
147
+ if (prompt == null) return null;
148
+ const raw = typeof prompt === "string" ? prompt : extractTextFromContent(prompt);
149
+ if (!isVisibleQueuedPrompt(raw)) return null;
150
+ const origin = attachment.origin;
151
+ if (origin?.kind === "peer") {
152
+ const sender = origin.name ?? origin.from ?? null;
153
+ if (sender) {
154
+ return {
155
+ raw,
156
+ sender,
157
+ body: origin.body ?? parseAgentEnvelope(raw).body
158
+ };
159
+ }
160
+ }
161
+ if (origin?.kind === "human") {
162
+ return { raw, sender: null, body: raw };
163
+ }
164
+ const parsed = parseAgentEnvelope(raw);
165
+ return { raw, sender: parsed.sender, body: parsed.body };
166
+ }
167
+ var ATTRIBUTION_FIELDS = [
168
+ ["attributionAgent", "agent"],
169
+ ["attributionSkill", "skill"],
170
+ ["attributionPlugin", "plugin"],
171
+ ["attributionMcpServer", "mcpServer"],
172
+ ["attributionMcpTool", "mcpTool"]
173
+ ];
174
+ function mergeAttribution(current, msg) {
175
+ let merged = current;
176
+ for (const [field, key] of ATTRIBUTION_FIELDS) {
177
+ const value = msg[field];
178
+ if (typeof value !== "string" || !value) continue;
179
+ merged = { ...merged, [key]: value };
180
+ }
181
+ return merged;
182
+ }
91
183
  function mergeTokenUsage(existing, incoming) {
92
184
  if (!existing) {
93
185
  return { ...incoming };
@@ -96,49 +188,234 @@ function mergeTokenUsage(existing, incoming) {
96
188
  input_tokens: existing.input_tokens + incoming.input_tokens,
97
189
  output_tokens: existing.output_tokens + incoming.output_tokens,
98
190
  cache_creation_input_tokens: (existing.cache_creation_input_tokens ?? 0) + (incoming.cache_creation_input_tokens ?? 0),
99
- cache_read_input_tokens: (existing.cache_read_input_tokens ?? 0) + (incoming.cache_read_input_tokens ?? 0)
191
+ cache_read_input_tokens: (existing.cache_read_input_tokens ?? 0) + (incoming.cache_read_input_tokens ?? 0),
192
+ speed: incoming.speed ?? existing.speed
100
193
  };
101
194
  }
102
- function buildCompactionSummary(turns, title) {
103
- if (turns.length === 0) return title;
104
- const toolCounts = {};
105
- for (const turn of turns) {
106
- for (const tc of turn.toolCalls) {
107
- toolCounts[tc.name] = (toolCounts[tc.name] || 0) + 1;
195
+ var RESUME_PREAMBLE_RE = /^This session is being continued from a previous conversation[\s\S]*?\n\nSummary:\s*\n/;
196
+ var RESUME_INSTRUCTIONS_RE = /\n+(?:If you need specific details from before compaction|(?:Please c|C)ontinue the conversation from where it left off)[\s\S]*$/;
197
+ function extractCompactionSummary(content) {
198
+ const text = extractTextFromContent(content).trim();
199
+ return text.replace(RESUME_PREAMBLE_RE, "").replace(RESUME_INSTRUCTIONS_RE, "").trim();
200
+ }
201
+ function normalizeCompactionMeta(meta) {
202
+ if (!meta) return void 0;
203
+ return { trigger: meta.trigger, preTokens: meta.preTokens, postTokens: meta.postTokens };
204
+ }
205
+ function createTurn(msg, userMessage) {
206
+ return {
207
+ id: msg.uuid ?? crypto.randomUUID(),
208
+ userMessage,
209
+ contentBlocks: [],
210
+ thinking: [],
211
+ assistantText: [],
212
+ toolCalls: [],
213
+ subAgentActivity: [],
214
+ timestamp: msg.timestamp ?? "",
215
+ durationMs: null,
216
+ tokenUsage: null,
217
+ model: null
218
+ };
219
+ }
220
+ function groupPlanModeBlocks(blocks) {
221
+ const result = [];
222
+ let i = 0;
223
+ while (i < blocks.length) {
224
+ const block = blocks[i];
225
+ if (block.kind === "tool_calls") {
226
+ const enterIdx = block.toolCalls.findIndex((tc) => tc.name === "EnterPlanMode");
227
+ if (enterIdx !== -1) {
228
+ const enterCall = block.toolCalls[enterIdx];
229
+ const plan = String(enterCall.input.plan ?? "");
230
+ const timestamp = block.timestamp;
231
+ const before = block.toolCalls.slice(0, enterIdx);
232
+ if (before.length > 0) {
233
+ result.push({ kind: "tool_calls", toolCalls: before, timestamp: block.timestamp });
234
+ }
235
+ const embedded = [];
236
+ const afterEnterInSameBlock = block.toolCalls.slice(enterIdx + 1);
237
+ const exitInSameBlock = afterEnterInSameBlock.findIndex((tc) => tc.name === "ExitPlanMode");
238
+ if (exitInSameBlock !== -1) {
239
+ embedded.push(...afterEnterInSameBlock.slice(0, exitInSameBlock));
240
+ const exitCall = afterEnterInSameBlock[exitInSameBlock];
241
+ const planFilePath = exitCall.input.path ? String(exitCall.input.path) : void 0;
242
+ let status = "pending";
243
+ if (exitCall.result !== null) {
244
+ status = exitCall.isError ? "rejected" : "approved";
245
+ }
246
+ result.push({ kind: "plan_mode", plan, planFilePath, status, toolCalls: embedded, timestamp });
247
+ const after = afterEnterInSameBlock.slice(exitInSameBlock + 1);
248
+ if (after.length > 0) {
249
+ result.push({ kind: "tool_calls", toolCalls: after, timestamp: block.timestamp });
250
+ }
251
+ i++;
252
+ continue;
253
+ } else {
254
+ embedded.push(...afterEnterInSameBlock);
255
+ let exitCall;
256
+ let planFilePath;
257
+ let status = "pending";
258
+ let j = i + 1;
259
+ const passthroughBlocks = [];
260
+ while (j < blocks.length) {
261
+ const next = blocks[j];
262
+ if (next.kind === "tool_calls") {
263
+ const exitIdx = next.toolCalls.findIndex((tc) => tc.name === "ExitPlanMode");
264
+ if (exitIdx !== -1) {
265
+ embedded.push(...next.toolCalls.slice(0, exitIdx));
266
+ exitCall = next.toolCalls[exitIdx];
267
+ planFilePath = exitCall.input.path ? String(exitCall.input.path) : void 0;
268
+ if (exitCall.result !== null) {
269
+ status = exitCall.isError ? "rejected" : "approved";
270
+ }
271
+ const tail = next.toolCalls.slice(exitIdx + 1);
272
+ result.push({ kind: "plan_mode", plan, planFilePath, status, toolCalls: embedded, timestamp });
273
+ result.push(...passthroughBlocks);
274
+ if (tail.length > 0) {
275
+ result.push({ kind: "tool_calls", toolCalls: tail, timestamp: next.timestamp });
276
+ }
277
+ i = j + 1;
278
+ break;
279
+ } else {
280
+ embedded.push(...next.toolCalls);
281
+ j++;
282
+ }
283
+ } else if (next.kind === "hook_event" || next.kind === "text" || next.kind === "queued_prompt" || next.kind === "agent_message") {
284
+ passthroughBlocks.push(next);
285
+ j++;
286
+ } else {
287
+ break;
288
+ }
289
+ }
290
+ if (!exitCall) {
291
+ result.push({ kind: "plan_mode", plan, planFilePath: void 0, status: "pending", toolCalls: embedded, timestamp });
292
+ result.push(...passthroughBlocks);
293
+ i = j;
294
+ }
295
+ continue;
296
+ }
297
+ }
108
298
  }
299
+ result.push(block);
300
+ i++;
109
301
  }
110
- const prompts = [];
111
- for (const turn of turns) {
112
- if (!turn.userMessage) continue;
113
- const text = extractTextFromContent(
114
- typeof turn.userMessage === "string" ? turn.userMessage : turn.userMessage
115
- );
116
- const firstLine = text.split("\n")[0].trim();
117
- if (firstLine.length > 0) {
118
- prompts.push(firstLine.length > 120 ? firstLine.slice(0, 117) + "..." : firstLine);
302
+ return result;
303
+ }
304
+ function clearAgentMessageReplies(turn) {
305
+ if (!turn.contentBlocks.some((b) => b.kind === "agent_message")) return turn;
306
+ return {
307
+ ...turn,
308
+ contentBlocks: turn.contentBlocks.map((block) => {
309
+ if (block.kind !== "agent_message") return block;
310
+ const { reply: _paired, ...unpaired } = block;
311
+ return unpaired;
312
+ })
313
+ };
314
+ }
315
+ function pairAgentMessageReplies(turns) {
316
+ const repaired = turns.map(clearAgentMessageReplies);
317
+ const unanswered = /* @__PURE__ */ new Map();
318
+ for (const turn of repaired) {
319
+ for (const block of turn.contentBlocks) {
320
+ if (block.kind === "agent_message") {
321
+ const waiting = unanswered.get(block.sender);
322
+ if (waiting) waiting.push(block);
323
+ else unanswered.set(block.sender, [block]);
324
+ continue;
325
+ }
326
+ if (block.kind !== "tool_calls") continue;
327
+ for (const call of block.toolCalls) {
328
+ if (call.name !== "SendMessage") continue;
329
+ const to = call.input.to;
330
+ if (typeof to !== "string" || !to) continue;
331
+ const target = unanswered.get(to)?.shift();
332
+ if (!target) continue;
333
+ const summary = call.input.summary;
334
+ target.reply = {
335
+ summary: typeof summary === "string" ? summary : "",
336
+ timestamp: call.timestamp || block.timestamp || ""
337
+ };
338
+ }
119
339
  }
120
340
  }
121
- const topTools = Object.entries(toolCounts).sort((a, b) => b[1] - a[1]).slice(0, 5).map(([name, count]) => `${name} x${count}`).join(", ");
122
- const parts = [`**${title}**`, `${turns.length} turns compacted`];
123
- if (topTools) parts.push(`Tools: ${topTools}`);
124
- if (prompts.length > 0) {
125
- parts.push("Prompts:");
126
- const shown = prompts.slice(0, 6);
127
- for (const p of shown) parts.push(`- ${p}`);
128
- if (prompts.length > 6) parts.push(`- ...and ${prompts.length - 6} more`);
341
+ return repaired;
342
+ }
343
+ function findTurnStartIndices(messages) {
344
+ const starts = [];
345
+ let hasOpenTurn = false;
346
+ for (let i = 0; i < messages.length; i++) {
347
+ const msg = messages[i];
348
+ if (isSummaryMessage(msg) || isCompactBoundary(msg)) {
349
+ hasOpenTurn = false;
350
+ continue;
351
+ }
352
+ if (isUserMessage(msg) && !msg.isMeta) {
353
+ const content = msg.message.content;
354
+ const continuesOpenTurn = hasOpenTurn && Array.isArray(content) && content.some((b) => b.type === "tool_result");
355
+ if (continuesOpenTurn) continue;
356
+ starts.push(i);
357
+ hasOpenTurn = true;
358
+ continue;
359
+ }
360
+ if (isAssistantMessage(msg) && !hasOpenTurn) {
361
+ starts.push(i);
362
+ hasOpenTurn = true;
363
+ }
129
364
  }
130
- return parts.join("\n");
365
+ return starts;
131
366
  }
132
367
  function buildTurns(messages) {
133
368
  const turns = [];
134
369
  let current = null;
135
370
  let pendingCompaction = null;
371
+ function attachPendingCompaction(turn) {
372
+ if (!pendingCompaction) return;
373
+ turn.compactionSummary = pendingCompaction.summary;
374
+ turn.compactionMeta = pendingCompaction.meta;
375
+ pendingCompaction = null;
376
+ }
377
+ let pendingRecap = null;
136
378
  const pendingToolUses = /* @__PURE__ */ new Map();
137
379
  const seenMessageIds = /* @__PURE__ */ new Set();
138
380
  const subAgentMap = /* @__PURE__ */ new Map();
139
381
  const agentBlockMap = /* @__PURE__ */ new Map();
140
382
  const backgroundAgentParentIds = /* @__PURE__ */ new Set();
141
383
  const taskMetaMap = /* @__PURE__ */ new Map();
384
+ const pendingQueuedPrompts = [];
385
+ const enqueueSourcedPrompts = /* @__PURE__ */ new Map();
386
+ function noteEnqueueSourced(turn, content) {
387
+ let counts = enqueueSourcedPrompts.get(turn);
388
+ if (!counts) {
389
+ counts = /* @__PURE__ */ new Map();
390
+ enqueueSourcedPrompts.set(turn, counts);
391
+ }
392
+ counts.set(content, (counts.get(content) ?? 0) + 1);
393
+ }
394
+ function consumeEnqueueSourced(turn, content) {
395
+ const counts = enqueueSourcedPrompts.get(turn);
396
+ const remaining = counts?.get(content);
397
+ if (!counts || !remaining) return false;
398
+ if (remaining === 1) counts.delete(content);
399
+ else counts.set(content, remaining - 1);
400
+ return true;
401
+ }
402
+ function flushPendingQueuedPrompts() {
403
+ for (const prompt of pendingQueuedPrompts) {
404
+ prompt.turn.contentBlocks.push(
405
+ prompt.sender ? {
406
+ kind: "agent_message",
407
+ sender: prompt.sender,
408
+ body: prompt.body,
409
+ timestamp: prompt.timestamp
410
+ } : {
411
+ kind: "queued_prompt",
412
+ content: prompt.content,
413
+ timestamp: prompt.timestamp
414
+ }
415
+ );
416
+ }
417
+ pendingQueuedPrompts.length = 0;
418
+ }
142
419
  function flushSubAgentMessages(parentId) {
143
420
  if (!current) return;
144
421
  const agentMsgs = subAgentMap.get(parentId);
@@ -163,6 +440,22 @@ function buildTurns(messages) {
163
440
  for (const [parentId] of subAgentMap) {
164
441
  flushSubAgentMessages(parentId);
165
442
  }
443
+ for (const block of current.contentBlocks) {
444
+ if (block.kind !== "hook_event") continue;
445
+ for (const ev of block.events) {
446
+ const isPostToolUse = ev.eventName === "PostToolUse" || ev.eventName.startsWith("PostToolUse:");
447
+ if (!isPostToolUse || !ev.toolUseId) continue;
448
+ const toolCall = current.toolCalls.find((tc) => tc.id === ev.toolUseId);
449
+ if (!toolCall) continue;
450
+ if (ev.updatedToolOutput) {
451
+ toolCall.outputReplacedByHook = true;
452
+ }
453
+ if (ev.durationMs !== void 0) {
454
+ toolCall.hookDurationMs = (toolCall.hookDurationMs ?? 0) + ev.durationMs;
455
+ }
456
+ }
457
+ }
458
+ current.contentBlocks = groupPlanModeBlocks(current.contentBlocks);
166
459
  turns.push(current);
167
460
  current = null;
168
461
  agentBlockMap.clear();
@@ -170,20 +463,66 @@ function buildTurns(messages) {
170
463
  for (const msg of messages) {
171
464
  if (isSummaryMessage(msg)) {
172
465
  finalizeTurn2();
173
- pendingCompaction = buildCompactionSummary(
174
- turns,
175
- msg.summary ?? "Conversation compacted"
176
- );
466
+ pendingCompaction = { summary: msg.summary };
177
467
  continue;
178
468
  }
179
469
  if (isCompactBoundary(msg)) {
180
470
  finalizeTurn2();
181
- pendingCompaction = buildCompactionSummary(
182
- turns,
183
- msg.content ?? "Conversation compacted"
184
- );
471
+ pendingCompaction = { meta: normalizeCompactionMeta(msg.compactMetadata) };
472
+ continue;
473
+ }
474
+ if (isCompactSummaryMessage(msg)) {
475
+ finalizeTurn2();
476
+ current = createTurn(msg, null);
477
+ current.compactionSummary = extractCompactionSummary(msg.message.content);
478
+ current.compactionMeta = pendingCompaction?.meta;
479
+ pendingCompaction = null;
480
+ continue;
481
+ }
482
+ if (isSystemMessage(msg) && msg.subtype === "away_summary" && msg.content) {
483
+ pendingRecap = { content: msg.content, timestamp: msg.timestamp };
484
+ continue;
485
+ }
486
+ if (isQueueOperationMessage(msg)) {
487
+ if (current && msg.operation === "enqueue" && isVisibleQueuedPrompt(msg.content)) {
488
+ const parsed = parseAgentEnvelope(msg.content);
489
+ pendingQueuedPrompts.push({
490
+ turn: current,
491
+ content: msg.content,
492
+ timestamp: msg.timestamp,
493
+ sender: parsed.sender,
494
+ body: parsed.body
495
+ });
496
+ noteEnqueueSourced(current, msg.content);
497
+ }
498
+ continue;
499
+ }
500
+ if (isAttachmentMessage(msg)) {
501
+ const queued = queuedCommandPrompt(msg);
502
+ if (current && queued !== null && !consumeEnqueueSourced(current, queued.raw)) {
503
+ pendingQueuedPrompts.push({
504
+ turn: current,
505
+ content: queued.raw,
506
+ timestamp: msg.attachment?.timestamp ?? msg.timestamp,
507
+ sender: queued.sender,
508
+ body: queued.body
509
+ });
510
+ }
185
511
  continue;
186
512
  }
513
+ if (pendingQueuedPrompts.length > 0) {
514
+ const userContent = isUserMessage(msg) && !msg.isMeta ? msg.message.content : null;
515
+ if (typeof userContent === "string") {
516
+ const duplicateIndex = pendingQueuedPrompts.findIndex(
517
+ (prompt) => prompt.content === userContent
518
+ );
519
+ if (duplicateIndex >= 0) {
520
+ const [dropped] = pendingQueuedPrompts.splice(duplicateIndex, 1);
521
+ consumeEnqueueSourced(dropped.turn, dropped.content);
522
+ }
523
+ }
524
+ flushPendingQueuedPrompts();
525
+ }
187
526
  if (isUserMessage(msg) && !msg.isMeta) {
188
527
  const content = msg.message.content;
189
528
  if (typeof content !== "string" && Array.isArray(content)) {
@@ -194,7 +533,7 @@ function buildTurns(messages) {
194
533
  const pending = pendingToolUses.get(block.tool_use_id);
195
534
  if (pending) {
196
535
  pending.turn.toolCalls[pending.index].result = extractToolResultText(block.content);
197
- pending.turn.toolCalls[pending.index].isError = block.is_error;
536
+ pending.turn.toolCalls[pending.index].isError = block.is_error === true;
198
537
  pendingToolUses.delete(block.tool_use_id);
199
538
  }
200
539
  }
@@ -212,6 +551,7 @@ function buildTurns(messages) {
212
551
  }
213
552
  const agentMsg = {
214
553
  agentId: toolUseResult.agentId,
554
+ parentToolUseId: toolUseId || void 0,
215
555
  agentName: taskMeta?.name ?? null,
216
556
  subagentType: taskMeta?.subagentType ?? null,
217
557
  type: "assistant",
@@ -245,23 +585,27 @@ function buildTurns(messages) {
245
585
  continue;
246
586
  }
247
587
  }
588
+ if (isTaskNotificationRecord(msg)) {
589
+ const notification = {
590
+ kind: "task_notification",
591
+ content: extractTextFromContent(content),
592
+ timestamp: msg.timestamp
593
+ };
594
+ if (current) {
595
+ current.contentBlocks.push(notification);
596
+ continue;
597
+ }
598
+ current = createTurn(msg, null);
599
+ current.contentBlocks.push(notification);
600
+ attachPendingCompaction(current);
601
+ continue;
602
+ }
248
603
  finalizeTurn2();
249
- current = {
250
- id: msg.uuid ?? crypto.randomUUID(),
251
- userMessage: msg.message.content,
252
- contentBlocks: [],
253
- thinking: [],
254
- assistantText: [],
255
- toolCalls: [],
256
- subAgentActivity: [],
257
- timestamp: msg.timestamp ?? "",
258
- durationMs: null,
259
- tokenUsage: null,
260
- model: null
261
- };
262
- if (pendingCompaction) {
263
- current.compactionSummary = pendingCompaction;
264
- pendingCompaction = null;
604
+ current = createTurn(msg, msg.message.content);
605
+ attachPendingCompaction(current);
606
+ if (pendingRecap) {
607
+ current.contentBlocks.push({ kind: "recap", content: pendingRecap.content, timestamp: pendingRecap.timestamp });
608
+ pendingRecap = null;
265
609
  }
266
610
  continue;
267
611
  }
@@ -284,21 +628,12 @@ function buildTurns(messages) {
284
628
  };
285
629
  var flushToolCalls = flushToolCalls2, flushThinking = flushThinking2;
286
630
  if (!current) {
287
- current = {
288
- id: msg.uuid ?? crypto.randomUUID(),
289
- userMessage: null,
290
- contentBlocks: [],
291
- thinking: [],
292
- assistantText: [],
293
- toolCalls: [],
294
- subAgentActivity: [],
295
- timestamp: msg.timestamp ?? "",
296
- durationMs: null,
297
- tokenUsage: null,
298
- model: null
299
- };
631
+ current = createTurn(msg, null);
632
+ attachPendingCompaction(current);
300
633
  }
301
634
  current.model = msg.message.model;
635
+ if (msg.effort) current.effort = msg.effort;
636
+ current.attribution = mergeAttribution(current.attribution, msg);
302
637
  const msgId = msg.message.id;
303
638
  if (!seenMessageIds.has(msgId)) {
304
639
  seenMessageIds.add(msgId);
@@ -312,6 +647,7 @@ function buildTurns(messages) {
312
647
  if (block.type === "thinking") {
313
648
  flushToolCalls2();
314
649
  const tb = block;
650
+ if (!tb.thinking) continue;
315
651
  current.thinking.push(tb);
316
652
  msgThinking.push(tb);
317
653
  } else if (block.type === "text") {
@@ -391,6 +727,7 @@ function buildTurns(messages) {
391
727
  const taskMeta = taskMetaMap.get(parentId);
392
728
  const agentMsg = {
393
729
  agentId: data.agentId,
730
+ parentToolUseId: parentId || void 0,
394
731
  agentName: taskMeta?.name ?? null,
395
732
  subagentType: taskMeta?.subagentType ?? null,
396
733
  type: data.message.type,
@@ -436,7 +773,7 @@ function buildTurns(messages) {
436
773
  );
437
774
  if (match) {
438
775
  match.result = extractToolResultText(block.content);
439
- match.isError = block.is_error;
776
+ match.isError = block.is_error === true;
440
777
  }
441
778
  }
442
779
  }
@@ -455,118 +792,46 @@ function buildTurns(messages) {
455
792
  }
456
793
  continue;
457
794
  }
795
+ if (isProgressMessage(msg) && msg.data.type === "hook_progress") {
796
+ const data = msg.data;
797
+ const eventName = String(data.hook_event_name ?? data.hookEvent ?? "unknown");
798
+ const hookSpecific = data.hookSpecificOutput;
799
+ const ev = {
800
+ eventName,
801
+ source: data.source !== void 0 ? String(data.source) : void 0,
802
+ toolName: data.tool_name !== void 0 ? String(data.tool_name) : void 0,
803
+ toolUseId: data.tool_use_id !== void 0 ? String(data.tool_use_id) : void 0,
804
+ command: data.command !== void 0 ? String(data.command) : void 0,
805
+ output: data.output !== void 0 ? String(data.output) : void 0,
806
+ stderr: data.stderr !== void 0 ? String(data.stderr) : void 0,
807
+ exitCode: data.exit_code !== void 0 ? Number(data.exit_code) : void 0,
808
+ decision: data.decision !== void 0 ? String(data.decision) : void 0,
809
+ durationMs: data.duration_ms !== void 0 ? Number(data.duration_ms) : void 0,
810
+ updatedToolOutput: hookSpecific?.updatedToolOutput !== void 0 ? String(hookSpecific.updatedToolOutput) : void 0,
811
+ sessionTitle: hookSpecific?.sessionTitle !== void 0 ? String(hookSpecific.sessionTitle) : void 0,
812
+ worktreePath: hookSpecific?.worktreePath !== void 0 ? String(hookSpecific.worktreePath) : void 0,
813
+ timestamp: msg.timestamp ?? ""
814
+ };
815
+ if (current) {
816
+ const last = current.contentBlocks[current.contentBlocks.length - 1];
817
+ if (last && last.kind === "hook_event") {
818
+ last.events.push(ev);
819
+ } else {
820
+ current.contentBlocks.push({ kind: "hook_event", events: [ev], timestamp: msg.timestamp });
821
+ }
822
+ }
823
+ continue;
824
+ }
458
825
  if (isSystemMessage(msg) && msg.subtype === "turn_duration" && current) {
459
- current.durationMs = msg.durationMs ?? null;
826
+ if (msg.durationMs !== void 0 && msg.durationMs !== null) {
827
+ current.durationMs = (current.durationMs ?? 0) + msg.durationMs;
828
+ }
460
829
  continue;
461
830
  }
462
831
  }
832
+ flushPendingQueuedPrompts();
463
833
  finalizeTurn2();
464
- return turns;
465
- }
466
-
467
- // src/lib/pricingTiers.ts
468
- var EXTENDED_CONTEXT_THRESHOLD = 2e5;
469
- var TIER_HAIKU_35 = { input: 0.8, output: 4, cacheWrite: 1, cacheRead: 0.08, webSearch: 0.01 };
470
- var TIER_HAIKU_45 = { input: 1, output: 5, cacheWrite: 1.25, cacheRead: 0.1, webSearch: 0.01 };
471
- var TIER_SONNET_LEGACY = { input: 3, output: 15, cacheWrite: 3.75, cacheRead: 0.3, webSearch: 0.01 };
472
- var TIER_SONNET_LATEST = { input: 5, output: 25, cacheWrite: 6.25, cacheRead: 0.5, webSearch: 0.01 };
473
- var TIER_OPUS_LEGACY = { input: 15, output: 75, cacheWrite: 18.75, cacheRead: 1.5, webSearch: 0.01 };
474
- var TIER_SONNET_LEGACY_EXT = { input: 6, output: 22.5, cacheWrite: 7.5, cacheRead: 0.6, webSearch: 0.01 };
475
- var TIER_EXTENDED = { input: 10, output: 37.5, cacheWrite: 12.5, cacheRead: 1, webSearch: 0.01 };
476
- var MODEL_TIERS = [
477
- // Haiku
478
- { match: "haiku-4-5", tier: TIER_HAIKU_45 },
479
- { match: "haiku-4-0", tier: TIER_HAIKU_35 },
480
- { match: "3-5-haiku", tier: TIER_HAIKU_35 },
481
- // Sonnet latest (4.5+)
482
- { match: "sonnet-4-6", tier: TIER_SONNET_LATEST, extendedTier: TIER_EXTENDED },
483
- { match: "sonnet-4-5", tier: TIER_SONNET_LATEST, extendedTier: TIER_EXTENDED },
484
- // Sonnet legacy (3.5, 3.7, 4.0)
485
- { match: "sonnet-4-0", tier: TIER_SONNET_LEGACY, extendedTier: TIER_SONNET_LEGACY_EXT },
486
- { match: "3-7-sonnet", tier: TIER_SONNET_LEGACY, extendedTier: TIER_SONNET_LEGACY_EXT },
487
- { match: "3-5-sonnet", tier: TIER_SONNET_LEGACY, extendedTier: TIER_SONNET_LEGACY_EXT },
488
- // Opus latest (4.5+) — same tier as sonnet latest
489
- { match: "opus-4-6", tier: TIER_SONNET_LATEST, extendedTier: TIER_EXTENDED },
490
- { match: "opus-4-5", tier: TIER_SONNET_LATEST, extendedTier: TIER_EXTENDED },
491
- // Opus legacy (4.0, 4.1)
492
- { match: "opus-4-1", tier: TIER_OPUS_LEGACY },
493
- { match: "opus-4-0", tier: TIER_OPUS_LEGACY }
494
- ];
495
- var DEFAULT_TIER = TIER_SONNET_LATEST;
496
- var DEFAULT_EXTENDED_TIER = TIER_EXTENDED;
497
- var FAMILY_FALLBACKS = [
498
- { match: "haiku", tier: TIER_HAIKU_45 },
499
- { match: "sonnet", tier: TIER_SONNET_LATEST },
500
- { match: "opus", tier: TIER_SONNET_LATEST }
501
- ];
502
- function resolveTier(model, totalInputTokens) {
503
- const isExtended = (totalInputTokens ?? 0) > EXTENDED_CONTEXT_THRESHOLD;
504
- for (const entry of MODEL_TIERS) {
505
- if (model.includes(entry.match)) {
506
- return isExtended && entry.extendedTier ? entry.extendedTier : entry.tier;
507
- }
508
- }
509
- for (const entry of FAMILY_FALLBACKS) {
510
- if (model.includes(entry.match)) return entry.tier;
511
- }
512
- return isExtended ? DEFAULT_EXTENDED_TIER : DEFAULT_TIER;
513
- }
514
-
515
- // src/lib/token-costs.ts
516
- var CHARS_PER_TOKEN = 4;
517
- function calculateCost(c) {
518
- const totalInput = c.inputTokens + c.cacheWriteTokens + c.cacheReadTokens;
519
- const p = resolveTier(c.model ?? "", totalInput);
520
- return c.inputTokens / 1e6 * p.input + c.outputTokens / 1e6 * p.output + c.cacheWriteTokens / 1e6 * p.cacheWrite + c.cacheReadTokens / 1e6 * p.cacheRead + (c.webSearchRequests ?? 0) * p.webSearch;
521
- }
522
- function charsToTokens(chars) {
523
- return Math.ceil(chars / CHARS_PER_TOKEN);
524
- }
525
- function totalLength(strings) {
526
- let n = 0;
527
- for (const s of strings) n += s.length;
528
- return n;
529
- }
530
- function totalToolInputLength(toolCalls) {
531
- let n = 0;
532
- for (const tc of toolCalls) n += JSON.stringify(tc.input).length;
533
- return n;
534
- }
535
- function estimateThinkingTokens(turn) {
536
- return charsToTokens(totalLength(turn.thinking.map((b) => b.thinking)));
537
- }
538
- function estimateVisibleOutputTokens(turn) {
539
- return charsToTokens(totalLength(turn.assistantText) + totalToolInputLength(turn.toolCalls));
540
- }
541
- function estimateTotalOutputTokens(turn) {
542
- const estimated = estimateThinkingTokens(turn) + estimateVisibleOutputTokens(turn);
543
- return Math.max(estimated, turn.tokenUsage?.output_tokens ?? 0);
544
- }
545
- function estimateSubAgentOutput(sa) {
546
- const chars = totalLength(sa.thinking) + totalLength(sa.text) + totalToolInputLength(sa.toolCalls);
547
- return Math.max(charsToTokens(chars), sa.tokenUsage?.output_tokens ?? 0);
548
- }
549
- function calculateTurnCostEstimated(turn) {
550
- if (!turn.tokenUsage) return 0;
551
- const u = turn.tokenUsage;
552
- return calculateCost({
553
- model: turn.model,
554
- inputTokens: u.input_tokens,
555
- outputTokens: estimateTotalOutputTokens(turn),
556
- cacheWriteTokens: u.cache_creation_input_tokens ?? 0,
557
- cacheReadTokens: u.cache_read_input_tokens ?? 0
558
- });
559
- }
560
- function calculateSubAgentCostEstimated(sa) {
561
- if (!sa.tokenUsage) return 0;
562
- const u = sa.tokenUsage;
563
- return calculateCost({
564
- model: sa.model,
565
- inputTokens: u.input_tokens,
566
- outputTokens: estimateSubAgentOutput(sa),
567
- cacheWriteTokens: u.cache_creation_input_tokens ?? 0,
568
- cacheReadTokens: u.cache_read_input_tokens ?? 0
569
- });
834
+ return pairAgentMessageReplies(turns);
570
835
  }
571
836
 
572
837
  // src/lib/sessionStats.ts
@@ -578,43 +843,325 @@ function countToolCalls(toolCalls, counts) {
578
843
  }
579
844
  return errors;
580
845
  }
581
- function addUsageToStats(stats, usage, estimatedOutput, cost) {
846
+ function addUsageToStats(stats, usage) {
582
847
  stats.totalInputTokens += usage.input_tokens;
583
- stats.totalOutputTokens += estimatedOutput;
848
+ stats.totalOutputTokens += usage.output_tokens;
584
849
  stats.totalCacheCreationTokens += usage.cache_creation_input_tokens ?? 0;
585
850
  stats.totalCacheReadTokens += usage.cache_read_input_tokens ?? 0;
586
- stats.totalCostUSD += cost;
587
851
  }
588
- function computeStats(turns) {
589
- const stats = {
852
+ function createEmptySessionStats(turnCount) {
853
+ return {
590
854
  totalInputTokens: 0,
591
855
  totalOutputTokens: 0,
592
856
  totalCacheCreationTokens: 0,
593
857
  totalCacheReadTokens: 0,
594
- totalCostUSD: 0,
595
858
  toolCallCounts: {},
596
859
  errorCount: 0,
597
860
  totalDurationMs: 0,
598
- turnCount: turns.length
861
+ turnCount
599
862
  };
863
+ }
864
+ function computeStats(turns) {
865
+ const stats = createEmptySessionStats(turns.length);
600
866
  for (const turn of turns) {
601
- if (turn.tokenUsage) {
602
- addUsageToStats(stats, turn.tokenUsage, estimateTotalOutputTokens(turn), calculateTurnCostEstimated(turn));
603
- }
867
+ if (turn.tokenUsage) addUsageToStats(stats, turn.tokenUsage);
604
868
  if (turn.durationMs) stats.totalDurationMs += turn.durationMs;
605
869
  stats.errorCount += countToolCalls(turn.toolCalls, stats.toolCallCounts);
606
870
  for (const sa of turn.subAgentActivity) {
607
871
  stats.errorCount += countToolCalls(sa.toolCalls, stats.toolCallCounts);
608
- if (sa.tokenUsage) {
609
- addUsageToStats(stats, sa.tokenUsage, estimateSubAgentOutput(sa), calculateSubAgentCostEstimated(sa));
610
- }
872
+ if (sa.tokenUsage) addUsageToStats(stats, sa.tokenUsage);
611
873
  }
612
874
  }
613
875
  return stats;
614
876
  }
615
877
 
878
+ // src/lib/codex-exec.ts
879
+ var JS_SIMPLE_ESCAPES = {
880
+ "0": "\0",
881
+ b: "\b",
882
+ f: "\f",
883
+ n: "\n",
884
+ r: "\r",
885
+ t: " ",
886
+ v: "\v"
887
+ };
888
+ function readJsStringLiteral(source, startIndex) {
889
+ const quote = source[startIndex];
890
+ if (quote !== '"' && quote !== "'" && quote !== "`") return null;
891
+ let value = "";
892
+ let index = startIndex + 1;
893
+ while (index < source.length) {
894
+ const char = source[index];
895
+ if (char === quote) return { endIndex: index + 1, value };
896
+ if (char !== "\\") {
897
+ value += char;
898
+ index++;
899
+ continue;
900
+ }
901
+ index++;
902
+ if (index >= source.length) return null;
903
+ const escaped = source[index];
904
+ if (escaped in JS_SIMPLE_ESCAPES) {
905
+ value += JS_SIMPLE_ESCAPES[escaped];
906
+ index++;
907
+ continue;
908
+ }
909
+ if (escaped === "\n") {
910
+ index++;
911
+ continue;
912
+ }
913
+ if (escaped === "\r") {
914
+ index += source[index + 1] === "\n" ? 2 : 1;
915
+ continue;
916
+ }
917
+ if (escaped === "x" && /^[0-9a-fA-F]{2}$/.test(source.slice(index + 1, index + 3))) {
918
+ value += String.fromCharCode(Number.parseInt(source.slice(index + 1, index + 3), 16));
919
+ index += 3;
920
+ continue;
921
+ }
922
+ if (escaped === "u") {
923
+ const braced = source.slice(index + 1).match(/^\{([0-9a-fA-F]+)\}/);
924
+ if (braced) {
925
+ const codePoint = Number.parseInt(braced[1], 16);
926
+ if (Number.isFinite(codePoint) && codePoint <= 1114111) {
927
+ value += String.fromCodePoint(codePoint);
928
+ index += braced[0].length + 1;
929
+ continue;
930
+ }
931
+ }
932
+ const hex = source.slice(index + 1, index + 5);
933
+ if (/^[0-9a-fA-F]{4}$/.test(hex)) {
934
+ value += String.fromCharCode(Number.parseInt(hex, 16));
935
+ index += 5;
936
+ continue;
937
+ }
938
+ }
939
+ value += escaped;
940
+ index++;
941
+ }
942
+ return null;
943
+ }
944
+
945
+ // src/lib/codex-patches.ts
946
+ function isApplyPatchText(value) {
947
+ return value.includes("*** Begin Patch") && value.includes("*** End Patch");
948
+ }
949
+ function extractApplyPatchInputs(execSource) {
950
+ if (!execSource.includes("apply_patch") || !execSource.includes("*** Begin Patch")) return [];
951
+ const assignedPatches = /* @__PURE__ */ new Map();
952
+ for (let index = 0; index < execSource.length; index++) {
953
+ const literal = readJsStringLiteral(execSource, index);
954
+ if (!literal) continue;
955
+ if (isApplyPatchText(literal.value)) {
956
+ const assignment = execSource.slice(0, index).match(/(?:^|[;\n])\s*(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*=\s*$/);
957
+ if (assignment) assignedPatches.set(assignment[1], literal.value);
958
+ }
959
+ index = literal.endIndex - 1;
960
+ }
961
+ const patches = [];
962
+ const seen = /* @__PURE__ */ new Set();
963
+ const applyCall = /\btools\s*\.\s*apply_patch\s*\(/g;
964
+ let match;
965
+ while ((match = applyCall.exec(execSource)) !== null) {
966
+ let argumentIndex = match.index + match[0].length;
967
+ while (/\s/.test(execSource[argumentIndex] ?? "")) argumentIndex++;
968
+ const literal = readJsStringLiteral(execSource, argumentIndex);
969
+ const identifier = literal ? null : execSource.slice(argumentIndex).match(/^([A-Za-z_$][\w$]*)/);
970
+ const patch = literal?.value ?? (identifier ? assignedPatches.get(identifier[1]) : void 0);
971
+ if (!patch || !isApplyPatchText(patch) || seen.has(patch)) continue;
972
+ seen.add(patch);
973
+ patches.push(patch);
974
+ }
975
+ return patches;
976
+ }
977
+ function resolveCodexPatchPath(filePath, cwd) {
978
+ if (!cwd || filePath.startsWith("/") || /^[A-Za-z]:[\\/]/.test(filePath)) return filePath;
979
+ const base = cwd.replace(/[\\/]+$/, "");
980
+ const relative = filePath.replace(/^\.[\\/]/, "");
981
+ return /^[A-Za-z]:[\\/]/.test(cwd) ? `${base}\\${relative.replace(/\//g, "\\")}` : `${base}/${relative}`;
982
+ }
983
+ function parseApplyPatch(patchText, callId, timestamp, cwd = "") {
984
+ const toolCalls = [];
985
+ const filePattern = /^\*\*\*\s+(Update File|Add File|Delete File):\s*(.+)$/gm;
986
+ const sections = [];
987
+ let match;
988
+ while ((match = filePattern.exec(patchText)) !== null) {
989
+ sections.push({
990
+ action: match[1],
991
+ filePath: match[2].trim(),
992
+ headerIdx: match.index,
993
+ startIdx: match.index + match[0].length
994
+ });
995
+ }
996
+ for (let i = 0; i < sections.length; i++) {
997
+ const section = sections[i];
998
+ const endIdx = i + 1 < sections.length ? sections[i + 1].headerIdx : patchText.length;
999
+ const body = patchText.slice(section.startIdx, endIdx);
1000
+ const movedTo = body.match(/^\*\*\*\s+Move to:\s*(.+)$/m)?.[1]?.trim();
1001
+ const sourcePath = resolveCodexPatchPath(section.filePath, cwd);
1002
+ const filePath = resolveCodexPatchPath(movedTo || section.filePath, cwd);
1003
+ const hunkBodies = body.split(/^@@.*$/m).filter((s) => s.trim());
1004
+ const oldLines = [];
1005
+ const newLines = [];
1006
+ for (const hunk of hunkBodies) {
1007
+ for (const line of hunk.split("\n")) {
1008
+ if (line.startsWith("-")) {
1009
+ oldLines.push(line.slice(1));
1010
+ } else if (line.startsWith("+")) {
1011
+ newLines.push(line.slice(1));
1012
+ } else if (line.startsWith(" ")) {
1013
+ oldLines.push(line.slice(1));
1014
+ newLines.push(line.slice(1));
1015
+ }
1016
+ }
1017
+ }
1018
+ const fileId = sections.length > 1 ? `${callId}:file-${i}` : callId;
1019
+ if (section.action === "Add File") {
1020
+ toolCalls.push({
1021
+ id: fileId,
1022
+ name: "Write",
1023
+ input: { file_path: filePath, content: newLines.join("\n") },
1024
+ result: null,
1025
+ isError: false,
1026
+ timestamp
1027
+ });
1028
+ } else if (section.action === "Delete File") {
1029
+ toolCalls.push({
1030
+ id: fileId,
1031
+ name: "Edit",
1032
+ input: { file_path: filePath, old_string: oldLines.join("\n"), new_string: "" },
1033
+ result: null,
1034
+ isError: false,
1035
+ timestamp
1036
+ });
1037
+ } else {
1038
+ toolCalls.push({
1039
+ id: fileId,
1040
+ name: "Edit",
1041
+ input: {
1042
+ file_path: filePath,
1043
+ ...movedTo ? { old_path: sourcePath } : {},
1044
+ old_string: oldLines.join("\n"),
1045
+ new_string: newLines.join("\n")
1046
+ },
1047
+ result: null,
1048
+ isError: false,
1049
+ timestamp
1050
+ });
1051
+ }
1052
+ }
1053
+ return toolCalls;
1054
+ }
1055
+ function parseCodexToolPatches(toolName, rawInput, callId, timestamp, cwd = "") {
1056
+ if (toolName === "apply_patch") {
1057
+ return parseApplyPatch(rawInput, callId, timestamp, cwd);
1058
+ }
1059
+ return extractApplyPatchInputs(rawInput).flatMap(
1060
+ (patch, patchIndex) => parseApplyPatch(patch, `${callId}:patch-${patchIndex}`, timestamp, cwd)
1061
+ );
1062
+ }
1063
+ function findFailedNestedPatchCallIds(output, wrapperIsError, calls) {
1064
+ if (!wrapperIsError || !/(?:apply[_ ]patch|invalid patch|invalid context|failed to (?:apply|find)|could not apply)/i.test(output)) {
1065
+ return /* @__PURE__ */ new Set();
1066
+ }
1067
+ const groups = /* @__PURE__ */ new Map();
1068
+ for (const call of calls) {
1069
+ const patchIndex = call.id.match(/:patch-(\d+)(?::file-\d+)?$/)?.[1];
1070
+ if (patchIndex === void 0) continue;
1071
+ const index = Number(patchIndex);
1072
+ const group = groups.get(index) ?? [];
1073
+ group.push(call);
1074
+ groups.set(index, group);
1075
+ }
1076
+ if (groups.size === 0) return new Set(calls.map((call) => call.id));
1077
+ const normalizedOutput = output.toLowerCase().replaceAll("\\", "/");
1078
+ const mentionedGroups = /* @__PURE__ */ new Set();
1079
+ for (const [index, group] of groups) {
1080
+ if (group.some((call) => {
1081
+ const filePath = String(call.input.file_path ?? "").toLowerCase().replaceAll("\\", "/");
1082
+ if (!filePath) return false;
1083
+ const segments = filePath.split("/").filter(Boolean);
1084
+ const suffix = segments.slice(-2).join("/");
1085
+ return normalizedOutput.includes(filePath) || suffix.includes("/") && normalizedOutput.includes(suffix);
1086
+ })) {
1087
+ mentionedGroups.add(index);
1088
+ }
1089
+ }
1090
+ for (const match of normalizedOutput.matchAll(/\bpatch\s*(?:#|-)?\s*(\d+)\b/g)) {
1091
+ const index = Number(match[1]);
1092
+ if (groups.has(index)) mentionedGroups.add(index);
1093
+ }
1094
+ if (mentionedGroups.size === 0) mentionedGroups.add(Math.max(...groups.keys()));
1095
+ const firstFailedIndex = Math.min(...mentionedGroups);
1096
+ for (const index of groups.keys()) {
1097
+ if (index >= firstFailedIndex) mentionedGroups.add(index);
1098
+ }
1099
+ return new Set(
1100
+ [...mentionedGroups].flatMap((index) => groups.get(index)?.map((call) => call.id) ?? [])
1101
+ );
1102
+ }
1103
+
1104
+ // src/lib/codex-tool-normalization.ts
1105
+ function isObject(value) {
1106
+ return typeof value === "object" && value !== null;
1107
+ }
1108
+ var COLLABORATION_TOOL_NAMES = /* @__PURE__ */ new Map([
1109
+ ["spawn_agent", "spawn_agent"],
1110
+ ["spawnAgent", "spawn_agent"],
1111
+ ["wait_agent", "wait_agent"],
1112
+ ["waitAgent", "wait_agent"],
1113
+ ["send_message", "send_message"],
1114
+ ["sendMessage", "send_message"],
1115
+ ["followup_task", "followup_task"],
1116
+ ["followupTask", "followup_task"],
1117
+ ["list_agents", "list_agents"],
1118
+ ["listAgents", "list_agents"],
1119
+ ["interrupt_agent", "interrupt_agent"],
1120
+ ["interruptAgent", "interrupt_agent"]
1121
+ ]);
1122
+ function normalizeFunctionName(rawName) {
1123
+ const leaf = rawName.split(/(?:__|[.:/])+/).filter(Boolean).at(-1) ?? rawName;
1124
+ return COLLABORATION_TOOL_NAMES.get(leaf) ?? rawName;
1125
+ }
1126
+ function inferToolError(output) {
1127
+ if (!output) return false;
1128
+ const exitMatch = output.match(/Process exited with code (\d+)/);
1129
+ if (exitMatch) return exitMatch[1] !== "0";
1130
+ const withoutSuccessSummaries = output.replace(/\b0\s+(?:fail(?:ed|ures?)?|errors?)\b/gi, "").replace(/\bno\s+(?:failures?|errors?)\b/gi, "");
1131
+ return /\b(error|failed|exception)\b/i.test(withoutSuccessSummaries);
1132
+ }
1133
+ function normalizePlanToTodos(input) {
1134
+ const plan = Array.isArray(input.plan) ? input.plan : [];
1135
+ const todos = plan.filter((item) => isObject(item)).map((item) => ({
1136
+ content: typeof item.step === "string" ? item.step : "",
1137
+ status: typeof item.status === "string" ? item.status : "pending",
1138
+ activeForm: typeof item.step === "string" ? item.step : ""
1139
+ }));
1140
+ return { todos };
1141
+ }
1142
+ function extractCustomToolOutputText(output) {
1143
+ if (typeof output === "string") return output;
1144
+ if (!Array.isArray(output)) return "";
1145
+ return output.filter((block) => isObject(block) && typeof block.text === "string").map((block) => block.text).join("");
1146
+ }
1147
+ function parseCustomToolOutput(output) {
1148
+ const rawOutput = extractCustomToolOutputText(output);
1149
+ if (!rawOutput) return { text: "", isError: false };
1150
+ try {
1151
+ const parsed = JSON.parse(rawOutput);
1152
+ const text = typeof parsed.output === "string" ? parsed.output : rawOutput;
1153
+ const meta = isObject(parsed.metadata) ? parsed.metadata : null;
1154
+ const isError = meta ? meta.exit_code !== 0 && meta.exit_code !== void 0 : inferToolError(text);
1155
+ return { text, isError };
1156
+ } catch {
1157
+ return { text: rawOutput, isError: inferToolError(rawOutput) };
1158
+ }
1159
+ }
1160
+
616
1161
  // src/lib/codex.ts
617
1162
  var SKIP_PROMPT_PREFIXES = [
1163
+ "# AGENTS.md instructions for ",
1164
+ "<recommended_plugins>",
618
1165
  "<environment_context>",
619
1166
  "<permissions instructions>",
620
1167
  "<collaboration_mode>",
@@ -630,17 +1177,98 @@ function safeParseLine(line) {
630
1177
  return null;
631
1178
  }
632
1179
  }
633
- function isObject(value) {
1180
+ function isObject2(value) {
634
1181
  return typeof value === "object" && value !== null;
635
1182
  }
636
1183
  function isCodexRecord(record) {
637
1184
  if (!record || typeof record.type !== "string") return false;
638
- return record.type === "session_meta" || record.type === "turn_context" || record.type === "event_msg" || record.type === "response_item";
1185
+ return record.type === "session_meta" || record.type === "turn_context" || record.type === "event_msg" || record.type === "response_item" || record.type === "inter_agent_communication_metadata" || record.type === "compacted" || record.type === "world_state";
639
1186
  }
640
1187
  function extractMessageText(payload, blockType) {
641
1188
  const content = payload?.content;
642
1189
  if (!Array.isArray(content)) return "";
643
- return content.filter((block) => isObject(block) && block.type === blockType && typeof block.text === "string").map((block) => block.text).join("\n").trim();
1190
+ return content.filter((block) => isObject2(block) && block.type === blockType && typeof block.text === "string").map((block) => block.text).join("\n").trim();
1191
+ }
1192
+ var CODEX_IMAGE_DATA_URL = /^data:(image\/(?:png|jpeg|gif|webp));base64,(.+)$/is;
1193
+ var CODEX_AUDIO_DATA_URL = /^data:(audio\/(?:wav|x-wav|mpeg|mp4|webm|ogg));base64,(.+)$/is;
1194
+ function parseImageDataUrl(value) {
1195
+ if (typeof value !== "string") return null;
1196
+ const match = CODEX_IMAGE_DATA_URL.exec(value);
1197
+ if (!match?.[1] || !match[2]) return null;
1198
+ return {
1199
+ type: "image",
1200
+ source: {
1201
+ type: "base64",
1202
+ media_type: match[1].toLowerCase(),
1203
+ data: match[2]
1204
+ }
1205
+ };
1206
+ }
1207
+ function parseAudioDataUrl(value) {
1208
+ if (typeof value !== "string") return null;
1209
+ const match = CODEX_AUDIO_DATA_URL.exec(value);
1210
+ if (!match?.[1] || !match[2]) return null;
1211
+ return {
1212
+ type: "audio",
1213
+ source: {
1214
+ type: "base64",
1215
+ media_type: match[1].toLowerCase(),
1216
+ data: match[2]
1217
+ }
1218
+ };
1219
+ }
1220
+ function extractEventMessageImages(payload) {
1221
+ if (!Array.isArray(payload.images)) return [];
1222
+ return payload.images.map(parseImageDataUrl).filter((image) => image !== null);
1223
+ }
1224
+ function extractEventMessageAudio(payload) {
1225
+ if (!Array.isArray(payload.audio)) return [];
1226
+ return payload.audio.map(parseAudioDataUrl).filter((audio) => audio !== null);
1227
+ }
1228
+ function extractResponseMessageImages(payload) {
1229
+ if (!Array.isArray(payload.content)) return [];
1230
+ return payload.content.filter((block) => isObject2(block) && block.type === "input_image").map((block) => parseImageDataUrl(block.image_url)).filter((image) => image !== null);
1231
+ }
1232
+ function extractResponseMessageAudio(payload) {
1233
+ if (!Array.isArray(payload.content)) return [];
1234
+ return payload.content.filter((block) => isObject2(block) && block.type === "input_audio").map((block) => parseAudioDataUrl(block.audio_url)).filter((audio) => audio !== null);
1235
+ }
1236
+ function buildCodexUserContent(message, images, audio = [], localImages = [], localAudio = []) {
1237
+ const attachmentSuffix = [
1238
+ ...localImages.map((path) => `
1239
+ ![image](<${path}>)`),
1240
+ ...localAudio.map((path) => `
1241
+ [audio attachment](<${path}>)`)
1242
+ ].join("");
1243
+ const text = message + attachmentSuffix;
1244
+ if (images.length === 0 && audio.length === 0) return text;
1245
+ return [
1246
+ ...images,
1247
+ ...audio,
1248
+ ...text ? [{ type: "text", text }] : []
1249
+ ];
1250
+ }
1251
+ function extractInterAgentMessage(payload) {
1252
+ const rawText = extractMessageText(payload, "input_text");
1253
+ if (!rawText) return { messageType: null, text: "" };
1254
+ const headerEnd = rawText.match(/\r?\nPayload:\s*(?:\r?\n|$)/);
1255
+ if (!rawText.startsWith("Message Type:") || !headerEnd || headerEnd.index === void 0) {
1256
+ return { messageType: null, text: rawText.trim() };
1257
+ }
1258
+ const header = rawText.slice(0, headerEnd.index);
1259
+ const messageType = header.match(/^Message Type:\s*([^\r\n]+)/)?.[1]?.trim().toUpperCase() ?? null;
1260
+ const text = rawText.slice(headerEnd.index + headerEnd[0].length).trim();
1261
+ return { messageType, text };
1262
+ }
1263
+ function agentNameFromPath(agentPath) {
1264
+ const segments = agentPath.split(/[\\/]/).filter(Boolean);
1265
+ return segments.at(-1) ?? null;
1266
+ }
1267
+ function readableAgentPrompt(value) {
1268
+ if (typeof value !== "string") return "";
1269
+ const prompt = value.trim();
1270
+ if (/^gAAAAA[A-Za-z0-9_-]+$/.test(prompt)) return "";
1271
+ return prompt;
644
1272
  }
645
1273
  function normalizePromptText(text) {
646
1274
  const trimmed = text.trim();
@@ -658,11 +1286,13 @@ function mergeTokenUsage2(existing, incoming) {
658
1286
  };
659
1287
  }
660
1288
  function parseTokenUsage(value) {
661
- if (!isObject(value)) return null;
662
- const inputTokens = typeof value.input_tokens === "number" ? value.input_tokens : 0;
1289
+ if (!isObject2(value)) return null;
1290
+ const reportedInputTokens = typeof value.input_tokens === "number" ? value.input_tokens : 0;
663
1291
  const outputTokens = typeof value.output_tokens === "number" ? value.output_tokens : 0;
664
- const cacheCreation = typeof value.cache_creation_input_tokens === "number" ? value.cache_creation_input_tokens : 0;
665
- const cacheRead = typeof value.cache_read_input_tokens === "number" ? value.cache_read_input_tokens : 0;
1292
+ const cacheCreation = typeof value.cache_write_input_tokens === "number" ? value.cache_write_input_tokens : typeof value.cache_creation_input_tokens === "number" ? value.cache_creation_input_tokens : 0;
1293
+ const nativeCachedInput = typeof value.cached_input_tokens === "number" ? Math.max(0, value.cached_input_tokens) : null;
1294
+ const cacheRead = nativeCachedInput ?? (typeof value.cache_read_input_tokens === "number" ? value.cache_read_input_tokens : 0);
1295
+ const inputTokens = nativeCachedInput === null ? reportedInputTokens : Math.max(0, reportedInputTokens - nativeCachedInput);
666
1296
  if (inputTokens === 0 && outputTokens === 0 && cacheCreation === 0 && cacheRead === 0) return null;
667
1297
  return {
668
1298
  input_tokens: inputTokens,
@@ -703,7 +1333,7 @@ function appendToolCall(turn, toolCall, timestamp) {
703
1333
  }
704
1334
  function finalizeTurn(turns, current, lastTimestamp) {
705
1335
  if (!current) return null;
706
- const hasContent = current.userMessage !== null || current.assistantText.length > 0 || current.toolCalls.length > 0 || current.thinking.length > 0;
1336
+ const hasContent = current.userMessage !== null || current.assistantText.length > 0 || current.toolCalls.length > 0 || current.thinking.length > 0 || current.subAgentActivity.length > 0;
707
1337
  if (!hasContent) return null;
708
1338
  if (current.timestamp && lastTimestamp) {
709
1339
  const start = new Date(current.timestamp).getTime();
@@ -719,20 +1349,40 @@ function parseToolInput(argumentsText) {
719
1349
  if (typeof argumentsText !== "string" || !argumentsText.trim()) return {};
720
1350
  try {
721
1351
  const parsed = JSON.parse(argumentsText);
722
- return isObject(parsed) ? parsed : { value: parsed };
1352
+ return isObject2(parsed) ? parsed : { value: parsed };
723
1353
  } catch {
724
1354
  return { raw: argumentsText };
725
1355
  }
726
1356
  }
727
- function inferToolError(output) {
728
- if (!output) return false;
729
- const exitMatch = output.match(/Process exited with code (\d+)/);
730
- if (exitMatch) return exitMatch[1] !== "0";
731
- return /\b(error|failed|exception)\b/i.test(output);
1357
+ function safeStringify(value) {
1358
+ if (typeof value === "string") return value;
1359
+ try {
1360
+ return JSON.stringify(value);
1361
+ } catch {
1362
+ return String(value ?? "");
1363
+ }
1364
+ }
1365
+ function findString(...values) {
1366
+ return values.find((value) => typeof value === "string");
1367
+ }
1368
+ function isCodeModeChild(callId) {
1369
+ return callId.startsWith("exec-");
1370
+ }
1371
+ function parseMcpEventResult(value) {
1372
+ if (!isObject2(value)) return { text: safeStringify(value), isError: false };
1373
+ if ("Err" in value) return { text: safeStringify(value.Err), isError: true };
1374
+ const ok = isObject2(value.Ok) ? value.Ok : null;
1375
+ if (!ok) return { text: safeStringify(value), isError: false };
1376
+ const parsedContent = parseCustomToolOutput(ok.content);
1377
+ const text = parsedContent.text || (ok.structuredContent !== void 0 ? safeStringify(ok.structuredContent) : safeStringify(ok));
1378
+ return {
1379
+ text,
1380
+ isError: ok.isError === true || parsedContent.isError
1381
+ };
732
1382
  }
733
- function createTurn(turnId, timestamp, model) {
1383
+ function createTurn2(turnId, timestamp, model) {
734
1384
  return {
735
- id: turnId || randomTurnId("codex-turn"),
1385
+ id: turnId ? `${turnId}@${timestamp}` : randomTurnId("codex-turn"),
736
1386
  userMessage: null,
737
1387
  contentBlocks: [],
738
1388
  thinking: [],
@@ -746,14 +1396,25 @@ function createTurn(turnId, timestamp, model) {
746
1396
  };
747
1397
  }
748
1398
  function extractPromptFromRecord(record) {
749
- if (record.type === "event_msg" && isObject(record.payload) && record.payload.type === "user_message" && typeof record.payload.message === "string") {
1399
+ if (record.type === "event_msg" && isObject2(record.payload) && record.payload.type === "user_message" && typeof record.payload.message === "string") {
750
1400
  return normalizePromptText(record.payload.message);
751
1401
  }
752
- if (record.type === "response_item" && isObject(record.payload) && record.payload.type === "message" && record.payload.role === "user") {
1402
+ if (record.type === "response_item" && isObject2(record.payload) && record.payload.type === "message" && record.payload.role === "user") {
753
1403
  return normalizePromptText(extractMessageText(record.payload, "input_text"));
754
1404
  }
755
1405
  return "";
756
1406
  }
1407
+ function recordOpensTurn(record, payload) {
1408
+ if (record.type === "session_meta") return false;
1409
+ if (record.type === "event_msg" && payload?.type === "task_started") return false;
1410
+ if (record.type === "response_item" && payload?.type === "message") {
1411
+ if (payload.role === "developer" || payload.role === "system") return false;
1412
+ if (payload.role === "user") {
1413
+ return extractPromptFromRecord(record) !== "" || extractResponseMessageImages(payload).length > 0 || extractResponseMessageAudio(payload).length > 0;
1414
+ }
1415
+ }
1416
+ return true;
1417
+ }
757
1418
  function extractMetadataFromRecords(records) {
758
1419
  let sessionId = "";
759
1420
  let version = "";
@@ -766,14 +1427,17 @@ function extractMetadataFromRecords(records) {
766
1427
  let timestamp = "";
767
1428
  let lastTimestamp = "";
768
1429
  let turnCount = 0;
1430
+ let isSubagent = false;
1431
+ let parentSessionId = null;
1432
+ let agentPath = "/root";
769
1433
  let previousPrompt = "";
770
1434
  for (const record of records) {
771
- if (!isObject(record.payload)) continue;
1435
+ if (!isObject2(record.payload)) continue;
772
1436
  if (record.type === "session_meta") {
773
1437
  sessionId ||= typeof record.payload.id === "string" ? record.payload.id : "";
774
1438
  version ||= typeof record.payload.cli_version === "string" ? record.payload.cli_version : "";
775
1439
  cwd ||= typeof record.payload.cwd === "string" ? record.payload.cwd : "";
776
- if (!branchedFrom && isObject(record.payload.branchedFrom)) {
1440
+ if (!branchedFrom && isObject2(record.payload.branchedFrom)) {
777
1441
  const sourceId = typeof record.payload.branchedFrom.sessionId === "string" ? record.payload.branchedFrom.sessionId : "";
778
1442
  if (sourceId) {
779
1443
  branchedFrom = {
@@ -782,7 +1446,18 @@ function extractMetadataFromRecords(records) {
782
1446
  };
783
1447
  }
784
1448
  }
785
- const git = isObject(record.payload.git) ? record.payload.git : null;
1449
+ const source = isObject2(record.payload.source) ? record.payload.source : null;
1450
+ if (source && isObject2(source.subagent)) {
1451
+ isSubagent = true;
1452
+ const threadSpawn = isObject2(source.subagent.thread_spawn) ? source.subagent.thread_spawn : null;
1453
+ if (threadSpawn && typeof threadSpawn.agent_path === "string") {
1454
+ agentPath = threadSpawn.agent_path;
1455
+ }
1456
+ }
1457
+ if (typeof record.payload.forked_from_id === "string" && record.payload.forked_from_id) {
1458
+ parentSessionId = record.payload.forked_from_id;
1459
+ }
1460
+ const git = isObject2(record.payload.git) ? record.payload.git : null;
786
1461
  gitBranch ||= git && typeof git.branch === "string" ? git.branch : "";
787
1462
  }
788
1463
  if (record.type === "turn_context") {
@@ -807,12 +1482,16 @@ function extractMetadataFromRecords(records) {
807
1482
  cwd,
808
1483
  model,
809
1484
  slug: "",
1485
+ name: "",
810
1486
  branchedFrom,
811
1487
  firstUserMessage,
812
1488
  lastUserMessage,
813
1489
  timestamp,
814
1490
  lastTimestamp,
815
- turnCount
1491
+ turnCount,
1492
+ isSubagent,
1493
+ parentSessionId,
1494
+ agentPath
816
1495
  };
817
1496
  }
818
1497
  function isCodexSessionText(jsonlText) {
@@ -827,47 +1506,229 @@ function extractCodexMetadataFromLines(lines) {
827
1506
  const records = lines.map(safeParseLine).filter(isCodexRecord);
828
1507
  return extractMetadataFromRecords(records);
829
1508
  }
830
- function parseCodexSession(jsonlText) {
1509
+ function parseCodexSession(jsonlText, options) {
831
1510
  const records = jsonlText.split("\n").map((line) => line.trim()).filter(Boolean).map(safeParseLine).filter(isCodexRecord);
832
1511
  const metadata = extractMetadataFromRecords(records);
833
1512
  const turns = [];
834
1513
  const pendingToolCalls = /* @__PURE__ */ new Map();
1514
+ const webSearchCalls = /* @__PURE__ */ new Map();
1515
+ const patchCallIds = /* @__PURE__ */ new Map();
1516
+ const spawnAgentCalls = /* @__PURE__ */ new Map();
1517
+ const agentRegistry = /* @__PURE__ */ new Map();
1518
+ const agentIdByPath = /* @__PURE__ */ new Map();
1519
+ const agentIdBySpawnCall = /* @__PURE__ */ new Map();
1520
+ const agentMessages = /* @__PURE__ */ new Map();
1521
+ function upsertAgentMessage(agentId, targetTurn, eventTimestamp, update = {}) {
1522
+ const info = agentRegistry.get(agentId);
1523
+ let entry = agentMessages.get(agentId);
1524
+ if (!entry) {
1525
+ const agentMessage2 = {
1526
+ agentId,
1527
+ parentToolUseId: info?.parentToolCallId || void 0,
1528
+ agentName: info?.nickname ?? (info?.agentPath ? agentNameFromPath(info.agentPath) : null),
1529
+ subagentType: info?.agentType ?? null,
1530
+ type: "assistant",
1531
+ content: null,
1532
+ toolCalls: [],
1533
+ thinking: [],
1534
+ text: [],
1535
+ timestamp: info?.timestamp ?? eventTimestamp,
1536
+ tokenUsage: null,
1537
+ model: info?.model ?? null,
1538
+ isBackground: false,
1539
+ prompt: info?.message || void 0,
1540
+ status: update.status ?? "running"
1541
+ };
1542
+ targetTurn.subAgentActivity.push(agentMessage2);
1543
+ const lastBlock = targetTurn.contentBlocks[targetTurn.contentBlocks.length - 1];
1544
+ if (lastBlock?.kind === "sub_agent") {
1545
+ lastBlock.messages.push(agentMessage2);
1546
+ } else {
1547
+ targetTurn.contentBlocks.push({ kind: "sub_agent", messages: [agentMessage2], timestamp: eventTimestamp });
1548
+ }
1549
+ entry = { message: agentMessage2, turn: targetTurn };
1550
+ agentMessages.set(agentId, entry);
1551
+ }
1552
+ const agentMessage = entry.message;
1553
+ if (info) {
1554
+ agentMessage.parentToolUseId = info.parentToolCallId || agentMessage.parentToolUseId;
1555
+ agentMessage.agentName = info.nickname ?? (info.agentPath ? agentNameFromPath(info.agentPath) : agentMessage.agentName);
1556
+ agentMessage.subagentType = info.agentType;
1557
+ agentMessage.model = info.model;
1558
+ agentMessage.prompt = info.message || agentMessage.prompt;
1559
+ }
1560
+ if (update.text && !agentMessage.text.includes(update.text)) {
1561
+ agentMessage.text.push(update.text);
1562
+ agentMessage.content = agentMessage.text.join("\n\n");
1563
+ }
1564
+ if (update.status) {
1565
+ const terminalStatuses = /* @__PURE__ */ new Set(["completed", "failed", "interrupted"]);
1566
+ if (!(update.status === "running" && agentMessage.status && terminalStatuses.has(agentMessage.status))) {
1567
+ agentMessage.status = update.status;
1568
+ }
1569
+ if (terminalStatuses.has(update.status) && agentMessage.timestamp && eventTimestamp) {
1570
+ const startedAt = new Date(agentMessage.timestamp).getTime();
1571
+ const endedAt = new Date(eventTimestamp).getTime();
1572
+ if (Number.isFinite(startedAt) && Number.isFinite(endedAt) && endedAt >= startedAt) {
1573
+ agentMessage.durationMs = endedAt - startedAt;
1574
+ }
1575
+ }
1576
+ }
1577
+ return agentMessage;
1578
+ }
835
1579
  let current = null;
836
1580
  let currentTurnId = null;
837
1581
  let currentModel = metadata.model || null;
838
1582
  let lastTurnTimestamp = "";
1583
+ let pendingCompaction = null;
1584
+ let sawTurnStart = false;
1585
+ function createActiveTurn(turnId, timestamp, model) {
1586
+ const turn = createTurn2(turnId, timestamp, model);
1587
+ if (!sawTurnStart) turn.isFragment = true;
1588
+ if (pendingCompaction) {
1589
+ turn.compactionSummary = pendingCompaction;
1590
+ pendingCompaction = null;
1591
+ }
1592
+ return turn;
1593
+ }
1594
+ function upsertWebSearchCall(targetTurn, id, input, status, timestamp) {
1595
+ const existing = webSearchCalls.get(id);
1596
+ if (existing) {
1597
+ existing.input = { ...existing.input, ...input };
1598
+ existing.result = status;
1599
+ existing.isError = status === "failed";
1600
+ return;
1601
+ }
1602
+ const toolCall = {
1603
+ id,
1604
+ name: "WebSearch",
1605
+ input,
1606
+ result: status,
1607
+ isError: status === "failed",
1608
+ timestamp
1609
+ };
1610
+ webSearchCalls.set(id, toolCall);
1611
+ appendToolCall(targetTurn, toolCall, timestamp);
1612
+ }
839
1613
  for (const record of records) {
840
- const payload = isObject(record.payload) ? record.payload : void 0;
1614
+ const payload = isObject2(record.payload) ? record.payload : void 0;
841
1615
  const timestamp = record.timestamp ?? "";
1616
+ if (record.type === "compacted") {
1617
+ current = finalizeTurn(turns, current, lastTurnTimestamp);
1618
+ pendingCompaction = "Conversation compacted";
1619
+ lastTurnTimestamp = timestamp;
1620
+ continue;
1621
+ }
1622
+ if (record.type === "world_state") continue;
842
1623
  if (record.type === "turn_context") {
843
1624
  current = finalizeTurn(turns, current, lastTurnTimestamp);
844
1625
  currentTurnId = typeof payload?.turn_id === "string" ? payload.turn_id : null;
845
1626
  currentModel = typeof payload?.model === "string" ? payload.model : currentModel;
846
1627
  lastTurnTimestamp = timestamp;
1628
+ sawTurnStart = true;
847
1629
  continue;
848
1630
  }
849
1631
  if (record.type === "event_msg" && payload?.type === "user_message" && typeof payload.message === "string") {
850
1632
  if (current && (current.assistantText.length > 0 || current.toolCalls.length > 0 || current.thinking.length > 0)) {
851
1633
  current = finalizeTurn(turns, current, lastTurnTimestamp);
852
1634
  }
853
- current ??= createTurn(currentTurnId, timestamp, currentModel);
854
- current.userMessage = payload.message;
1635
+ sawTurnStart = true;
1636
+ current ??= createActiveTurn(currentTurnId, timestamp, currentModel);
1637
+ const localImages = (Array.isArray(payload.local_images) ? payload.local_images : []).filter(
1638
+ (p) => typeof p === "string" && p.length > 0
1639
+ );
1640
+ const localAudio = (Array.isArray(payload.local_audio) ? payload.local_audio : []).filter(
1641
+ (p) => typeof p === "string" && p.length > 0
1642
+ );
1643
+ current.userMessage = buildCodexUserContent(
1644
+ payload.message,
1645
+ extractEventMessageImages(payload),
1646
+ extractEventMessageAudio(payload),
1647
+ localImages,
1648
+ localAudio
1649
+ );
1650
+ current.isFragment = false;
855
1651
  current.timestamp = current.timestamp || timestamp;
856
1652
  lastTurnTimestamp = timestamp;
857
1653
  continue;
858
1654
  }
859
- current ??= createTurn(currentTurnId, timestamp, currentModel);
1655
+ if (!current && !recordOpensTurn(record, payload)) continue;
1656
+ current ??= createActiveTurn(currentTurnId, timestamp, currentModel);
860
1657
  if (!current.model && currentModel) current.model = currentModel;
861
1658
  if (!current.timestamp) current.timestamp = timestamp;
862
1659
  if (timestamp) lastTurnTimestamp = timestamp;
863
1660
  if (record.type === "event_msg" && payload?.type === "token_count") {
864
- const info = isObject(payload.info) ? payload.info : null;
1661
+ const info = isObject2(payload.info) ? payload.info : null;
865
1662
  const lastUsage = info ? parseTokenUsage(info.last_token_usage) : null;
866
1663
  if (lastUsage) {
867
1664
  current.tokenUsage = mergeTokenUsage2(current.tokenUsage, lastUsage);
868
1665
  }
869
1666
  continue;
870
1667
  }
1668
+ if (record.type === "event_msg" && payload?.type === "web_search_end" && typeof payload.call_id === "string") {
1669
+ const input = {};
1670
+ if (typeof payload.query === "string" && payload.query) input.query = payload.query;
1671
+ if (isObject2(payload.action)) input.action = payload.action;
1672
+ upsertWebSearchCall(current, payload.call_id, input, "completed", timestamp);
1673
+ continue;
1674
+ }
1675
+ if (record.type === "event_msg" && payload?.type === "mcp_tool_call_end" && typeof payload.call_id === "string" && isObject2(payload.invocation)) {
1676
+ const invocation = payload.invocation;
1677
+ const server = typeof invocation.server === "string" ? invocation.server : "mcp";
1678
+ const tool = typeof invocation.tool === "string" ? invocation.tool : "tool";
1679
+ const input = isObject2(invocation.arguments) ? invocation.arguments : {};
1680
+ const result = parseMcpEventResult(payload.result);
1681
+ const existing = pendingToolCalls.get(payload.call_id);
1682
+ if (existing) {
1683
+ existing.result = result.text;
1684
+ existing.isError = result.isError;
1685
+ pendingToolCalls.delete(payload.call_id);
1686
+ } else if (!isCodeModeChild(payload.call_id)) {
1687
+ appendToolCall(current, {
1688
+ id: payload.call_id,
1689
+ name: `mcp__${server}__${tool}`,
1690
+ input,
1691
+ result: result.text,
1692
+ isError: result.isError,
1693
+ timestamp
1694
+ }, timestamp);
1695
+ }
1696
+ continue;
1697
+ }
1698
+ if (record.type === "event_msg" && payload?.type === "sub_agent_activity" && typeof payload.agent_thread_id === "string" && typeof payload.agent_path === "string") {
1699
+ const agentId = payload.agent_thread_id;
1700
+ const eventId = typeof payload.event_id === "string" ? payload.event_id : "";
1701
+ const kind = typeof payload.kind === "string" ? payload.kind : "started";
1702
+ const spawnInfo = eventId ? spawnAgentCalls.get(eventId) : void 0;
1703
+ const existingId = agentIdByPath.get(payload.agent_path);
1704
+ const existing = agentRegistry.get(agentId) ?? (existingId ? agentRegistry.get(existingId) : void 0);
1705
+ if (kind !== "started" && !existing && !spawnInfo) continue;
1706
+ if (existingId && existingId !== agentId && !agentMessages.has(agentId)) {
1707
+ const provisionalEntry = agentMessages.get(existingId);
1708
+ if (provisionalEntry) {
1709
+ provisionalEntry.message.agentId = agentId;
1710
+ agentMessages.delete(existingId);
1711
+ agentMessages.set(agentId, provisionalEntry);
1712
+ }
1713
+ agentRegistry.delete(existingId);
1714
+ }
1715
+ const info = {
1716
+ agentId,
1717
+ nickname: existing?.nickname ?? agentNameFromPath(payload.agent_path) ?? spawnInfo?.taskName ?? null,
1718
+ agentPath: payload.agent_path,
1719
+ message: existing?.message ?? spawnInfo?.message ?? "",
1720
+ model: existing?.model ?? spawnInfo?.model ?? null,
1721
+ agentType: existing?.agentType ?? spawnInfo?.agentType ?? null,
1722
+ timestamp: existing?.timestamp ?? (timestamp || spawnInfo?.timestamp || ""),
1723
+ parentToolCallId: existing?.parentToolCallId ?? eventId
1724
+ };
1725
+ agentRegistry.set(agentId, info);
1726
+ agentIdByPath.set(payload.agent_path, agentId);
1727
+ if (eventId && (kind === "started" || spawnInfo)) agentIdBySpawnCall.set(eventId, agentId);
1728
+ const status = kind === "interrupted" ? "interrupted" : "running";
1729
+ upsertAgentMessage(agentId, current, timestamp, { status });
1730
+ continue;
1731
+ }
871
1732
  if (record.type !== "response_item" || !payload) continue;
872
1733
  if (payload.type === "reasoning") {
873
1734
  const summary = Array.isArray(payload.summary) ? payload.summary.filter((item) => typeof item === "string").join("\n") : "";
@@ -880,14 +1741,110 @@ function parseCodexSession(jsonlText) {
880
1741
  }
881
1742
  if (payload.type === "message" && payload.role === "user" && current.userMessage === null) {
882
1743
  const text = normalizePromptText(extractMessageText(payload, "input_text"));
883
- if (text) current.userMessage = text;
1744
+ const images = extractResponseMessageImages(payload);
1745
+ const audio = extractResponseMessageAudio(payload);
1746
+ if (text || images.length > 0 || audio.length > 0) {
1747
+ current.userMessage = buildCodexUserContent(text, images, audio);
1748
+ current.isFragment = false;
1749
+ sawTurnStart = true;
1750
+ }
1751
+ continue;
1752
+ }
1753
+ if (payload.type === "web_search_call" && typeof payload.id === "string") {
1754
+ const input = {};
1755
+ if (isObject2(payload.action)) input.action = payload.action;
1756
+ const status = typeof payload.status === "string" ? payload.status : "completed";
1757
+ upsertWebSearchCall(current, payload.id, input, status, timestamp);
1758
+ continue;
1759
+ }
1760
+ if (payload.type === "tool_search_call") {
1761
+ const callId = findString(payload.call_id, payload.id) ?? "";
1762
+ if (callId) {
1763
+ const toolCall = {
1764
+ id: callId,
1765
+ name: "ToolSearch",
1766
+ input: isObject2(payload.arguments) ? payload.arguments : {},
1767
+ result: null,
1768
+ isError: false,
1769
+ timestamp
1770
+ };
1771
+ pendingToolCalls.set(callId, toolCall);
1772
+ appendToolCall(current, toolCall, timestamp);
1773
+ }
1774
+ continue;
1775
+ }
1776
+ if (payload.type === "tool_search_output" && typeof payload.call_id === "string") {
1777
+ const toolCall = pendingToolCalls.get(payload.call_id);
1778
+ if (toolCall) {
1779
+ const tools = Array.isArray(payload.tools) ? payload.tools : [];
1780
+ const names = tools.filter(isObject2).map((tool) => findString(tool.name) ?? "").filter(Boolean);
1781
+ const count = names.length > 0 ? names.length : tools.length;
1782
+ const found = `Found ${count} tool${count === 1 ? "" : "s"}`;
1783
+ toolCall.result = names.length > 0 ? `${found}: ${names.join(", ")}` : found;
1784
+ toolCall.isError = payload.status === "failed";
1785
+ pendingToolCalls.delete(payload.call_id);
1786
+ }
1787
+ continue;
1788
+ }
1789
+ if (payload.type === "agent_message" && typeof payload.author === "string") {
1790
+ const author = payload.author;
1791
+ const recipient = typeof payload.recipient === "string" ? payload.recipient : null;
1792
+ const interAgentMessage = extractInterAgentMessage(payload);
1793
+ if (interAgentMessage.messageType === "NEW_TASK") continue;
1794
+ const knownAgentId = agentIdByPath.get(author);
1795
+ const isPotentialChild = author.startsWith(`${metadata.agentPath}/`) && (recipient === null || recipient === metadata.agentPath);
1796
+ if (!knownAgentId && !isPotentialChild) continue;
1797
+ const agentId = knownAgentId ?? author;
1798
+ if (!knownAgentId) {
1799
+ agentRegistry.set(agentId, {
1800
+ agentId,
1801
+ nickname: agentNameFromPath(author),
1802
+ agentPath: author,
1803
+ message: "",
1804
+ model: null,
1805
+ agentType: null,
1806
+ timestamp,
1807
+ parentToolCallId: ""
1808
+ });
1809
+ agentIdByPath.set(author, agentId);
1810
+ }
1811
+ let status;
1812
+ if (interAgentMessage.messageType === "FINAL_ANSWER") status = "completed";
1813
+ else if (interAgentMessage.messageType === "ERROR" || interAgentMessage.messageType === "FAILED") status = "failed";
1814
+ else if (interAgentMessage.messageType === "INTERRUPTED") status = "interrupted";
1815
+ else if (interAgentMessage.messageType === "MESSAGE") status = "running";
1816
+ upsertAgentMessage(agentId, current, timestamp, {
1817
+ status,
1818
+ text: interAgentMessage.text || void 0
1819
+ });
884
1820
  continue;
885
1821
  }
886
1822
  if (payload.type === "function_call" && typeof payload.call_id === "string") {
1823
+ const rawName = typeof payload.name === "string" ? payload.name : "tool";
1824
+ const functionName = normalizeFunctionName(rawName);
1825
+ const parsedInput = parseToolInput(payload.arguments);
1826
+ let name = functionName;
1827
+ let input = parsedInput;
1828
+ if (functionName === "exec_command") {
1829
+ name = "Bash";
1830
+ } else if (functionName === "update_plan") {
1831
+ name = "TodoWrite";
1832
+ input = normalizePlanToTodos(parsedInput);
1833
+ }
1834
+ if (functionName === "spawn_agent") {
1835
+ spawnAgentCalls.set(payload.call_id, {
1836
+ callId: payload.call_id,
1837
+ message: readableAgentPrompt(parsedInput.message),
1838
+ taskName: typeof parsedInput.task_name === "string" ? parsedInput.task_name : null,
1839
+ model: typeof parsedInput.model === "string" ? parsedInput.model : null,
1840
+ agentType: typeof parsedInput.agent_type === "string" ? parsedInput.agent_type : null,
1841
+ timestamp
1842
+ });
1843
+ }
887
1844
  const toolCall = {
888
1845
  id: payload.call_id,
889
- name: typeof payload.name === "string" ? payload.name : "tool",
890
- input: parseToolInput(payload.arguments),
1846
+ name,
1847
+ input,
891
1848
  result: null,
892
1849
  isError: false,
893
1850
  timestamp
@@ -903,6 +1860,116 @@ function parseCodexSession(jsonlText) {
903
1860
  toolCall.result = output;
904
1861
  toolCall.isError = inferToolError(output);
905
1862
  pendingToolCalls.delete(payload.call_id);
1863
+ if (toolCall.name === "spawn_agent" && output) {
1864
+ try {
1865
+ const result = JSON.parse(output);
1866
+ const agentId = typeof result.agent_id === "string" ? result.agent_id : agentIdBySpawnCall.get(toolCall.id) ?? "";
1867
+ const agentPath = typeof result.task_name === "string" ? result.task_name : null;
1868
+ const nickname = typeof result.nickname === "string" ? result.nickname : agentPath ? agentNameFromPath(agentPath) : null;
1869
+ if (agentId) {
1870
+ const spawnInfo = spawnAgentCalls.get(toolCall.id);
1871
+ const existing = agentRegistry.get(agentId);
1872
+ const info = {
1873
+ agentId,
1874
+ nickname: nickname ?? existing?.nickname ?? spawnInfo?.taskName ?? null,
1875
+ agentPath: agentPath ?? existing?.agentPath ?? null,
1876
+ message: existing?.message ?? spawnInfo?.message ?? "",
1877
+ model: existing?.model ?? spawnInfo?.model ?? null,
1878
+ agentType: existing?.agentType ?? spawnInfo?.agentType ?? null,
1879
+ timestamp: existing?.timestamp ?? spawnInfo?.timestamp ?? timestamp,
1880
+ parentToolCallId: existing?.parentToolCallId ?? toolCall.id
1881
+ };
1882
+ agentRegistry.set(agentId, info);
1883
+ agentIdBySpawnCall.set(toolCall.id, agentId);
1884
+ if (info.agentPath) agentIdByPath.set(info.agentPath, agentId);
1885
+ upsertAgentMessage(agentId, current, timestamp, { status: "running" });
1886
+ }
1887
+ } catch {
1888
+ }
1889
+ }
1890
+ if (toolCall.name === "wait_agent" && output && current) {
1891
+ try {
1892
+ const result = JSON.parse(output);
1893
+ const statusMap = isObject2(result.status) ? result.status : {};
1894
+ for (const [agentId, status] of Object.entries(statusMap)) {
1895
+ const statusValue = isObject2(status) ? status : {};
1896
+ const completedText = typeof statusValue.completed === "string" ? statusValue.completed : "";
1897
+ const failedText = typeof statusValue.failed === "string" ? statusValue.failed : typeof statusValue.error === "string" ? statusValue.error : "";
1898
+ const interruptedText = typeof statusValue.interrupted === "string" ? statusValue.interrupted : "";
1899
+ const lifecycle = completedText ? "completed" : failedText ? "failed" : interruptedText ? "interrupted" : typeof statusValue.status === "string" ? statusValue.status : "running";
1900
+ if (!agentRegistry.has(agentId)) {
1901
+ agentRegistry.set(agentId, {
1902
+ agentId,
1903
+ nickname: null,
1904
+ agentPath: null,
1905
+ message: "",
1906
+ model: null,
1907
+ agentType: null,
1908
+ timestamp,
1909
+ parentToolCallId: ""
1910
+ });
1911
+ }
1912
+ upsertAgentMessage(agentId, current, timestamp, {
1913
+ status: lifecycle,
1914
+ text: completedText || failedText || interruptedText || void 0
1915
+ });
1916
+ }
1917
+ } catch {
1918
+ }
1919
+ }
1920
+ continue;
1921
+ }
1922
+ if (payload.type === "custom_tool_call" && typeof payload.call_id === "string") {
1923
+ const name = typeof payload.name === "string" ? payload.name : "tool";
1924
+ const rawInput = typeof payload.input === "string" ? payload.input : "";
1925
+ const callId = payload.call_id;
1926
+ const perFileCalls = rawInput ? parseCodexToolPatches(name, rawInput, callId, timestamp, metadata.cwd) : [];
1927
+ if (name !== "apply_patch") {
1928
+ const toolCall = {
1929
+ id: callId,
1930
+ name: name === "exec_command" ? "Bash" : name,
1931
+ input: parseToolInput(rawInput),
1932
+ result: null,
1933
+ isError: false,
1934
+ timestamp
1935
+ };
1936
+ pendingToolCalls.set(toolCall.id, toolCall);
1937
+ appendToolCall(current, toolCall, timestamp);
1938
+ }
1939
+ for (const tc of perFileCalls) {
1940
+ pendingToolCalls.set(tc.id, tc);
1941
+ appendToolCall(current, tc, timestamp);
1942
+ }
1943
+ if (perFileCalls.length > 0) {
1944
+ patchCallIds.set(callId, {
1945
+ calls: perFileCalls,
1946
+ direct: name === "apply_patch"
1947
+ });
1948
+ }
1949
+ continue;
1950
+ }
1951
+ if (payload.type === "custom_tool_call_output" && typeof payload.call_id === "string") {
1952
+ const callId = payload.call_id;
1953
+ const { text, isError } = parseCustomToolOutput(payload.output);
1954
+ const patchCalls = patchCallIds.get(callId);
1955
+ if (patchCalls) {
1956
+ const failedIds = patchCalls.direct && isError ? new Set(patchCalls.calls.map((call) => call.id)) : findFailedNestedPatchCallIds(text, isError, patchCalls.calls);
1957
+ for (const patchCall of patchCalls.calls) {
1958
+ const tc = pendingToolCalls.get(patchCall.id);
1959
+ if (tc) {
1960
+ tc.result = text;
1961
+ tc.isError = failedIds.has(tc.id);
1962
+ pendingToolCalls.delete(tc.id);
1963
+ }
1964
+ }
1965
+ patchCallIds.delete(callId);
1966
+ }
1967
+ const toolCall = pendingToolCalls.get(callId);
1968
+ if (toolCall) {
1969
+ toolCall.result = text;
1970
+ toolCall.isError = isError;
1971
+ pendingToolCalls.delete(callId);
1972
+ }
906
1973
  continue;
907
1974
  }
908
1975
  }
@@ -913,19 +1980,17 @@ function parseCodexSession(jsonlText) {
913
1980
  gitBranch: metadata.gitBranch,
914
1981
  cwd: metadata.cwd,
915
1982
  slug: metadata.slug,
1983
+ name: "",
916
1984
  model: metadata.model,
917
1985
  turns,
918
- stats: computeStats(turns),
919
- rawMessages: records,
1986
+ stats: options?.skipStats ? createEmptySessionStats(turns.length) : computeStats(turns),
1987
+ rawMessages: options?.skipStats ? [] : records,
920
1988
  branchedFrom: metadata.branchedFrom,
921
1989
  agentKind: "codex"
922
1990
  };
923
1991
  }
924
1992
 
925
1993
  // src/lib/parser.ts
926
- function isAssistantMessage2(msg) {
927
- return msg.type === "assistant";
928
- }
929
1994
  function extractTextFromContent2(content) {
930
1995
  if (typeof content === "string") return content;
931
1996
  return content.filter((b) => b.type === "text").map((b) => b.text).join("\n");
@@ -944,55 +2009,43 @@ function parseLines(jsonlText) {
944
2009
  }
945
2010
  function isCodexRawMessages(rawMessages) {
946
2011
  const firstType = rawMessages[0]?.type;
947
- return firstType === "session_meta" || firstType === "turn_context" || firstType === "event_msg" || firstType === "response_item";
2012
+ return firstType === "session_meta" || firstType === "turn_context" || firstType === "event_msg" || firstType === "response_item" || firstType === "compacted" || firstType === "world_state" || firstType === "inter_agent_communication_metadata";
948
2013
  }
949
2014
  function serializeRawMessages(rawMessages) {
950
2015
  return rawMessages.map((msg) => JSON.stringify(msg)).join("\n");
951
2016
  }
952
2017
  function extractSessionMetadata(messages) {
953
- const meta = { sessionId: "", version: "", gitBranch: "", cwd: "", slug: "", model: "", branchedFrom: void 0 };
2018
+ const meta = { sessionId: "", version: "", gitBranch: "", cwd: "", slug: "", name: "", model: "", branchedFrom: void 0 };
954
2019
  for (const msg of messages) {
955
2020
  if (msg.sessionId && !meta.sessionId) meta.sessionId = msg.sessionId;
956
2021
  if (msg.version && !meta.version) meta.version = msg.version;
957
2022
  if (msg.gitBranch && !meta.gitBranch) meta.gitBranch = msg.gitBranch;
958
2023
  if (msg.cwd && !meta.cwd) meta.cwd = msg.cwd;
959
2024
  if (msg.slug && !meta.slug) meta.slug = msg.slug;
2025
+ if (msg.name && !meta.name) meta.name = msg.name;
960
2026
  if (msg.branchedFrom && !meta.branchedFrom) {
961
2027
  meta.branchedFrom = msg.branchedFrom;
962
2028
  }
963
- if (isAssistantMessage2(msg) && msg.message.model && !meta.model) {
2029
+ if (isAssistantMessage(msg) && msg.message.model && !meta.model) {
964
2030
  meta.model = msg.message.model;
965
2031
  }
966
2032
  if (meta.sessionId && meta.version && meta.gitBranch && meta.cwd && meta.model) break;
967
2033
  }
968
2034
  return meta;
969
2035
  }
970
- function emptyStats(turnCount) {
971
- return {
972
- totalInputTokens: 0,
973
- totalOutputTokens: 0,
974
- totalCacheCreationTokens: 0,
975
- totalCacheReadTokens: 0,
976
- totalCostUSD: 0,
977
- toolCallCounts: {},
978
- errorCount: 0,
979
- totalDurationMs: 0,
980
- turnCount
981
- };
982
- }
983
- function parseSession(jsonlText, opts) {
2036
+ function parseSession(jsonlText, options) {
984
2037
  if (isCodexSessionText(jsonlText)) {
985
- return parseCodexSession(jsonlText);
2038
+ return parseCodexSession(jsonlText, options);
986
2039
  }
987
2040
  const rawMessages = parseLines(jsonlText);
988
2041
  const metadata = extractSessionMetadata(rawMessages);
989
2042
  const turns = buildTurns(rawMessages);
990
- const stats = opts?.skipStats ? emptyStats(turns.length) : computeStats(turns);
2043
+ const stats = options?.skipStats ? createEmptySessionStats(turns.length) : computeStats(turns);
991
2044
  return {
992
2045
  ...metadata,
993
2046
  turns,
994
2047
  stats,
995
- rawMessages: opts?.skipStats ? [] : rawMessages,
2048
+ rawMessages: options?.skipStats ? [] : rawMessages,
996
2049
  agentKind: "claude"
997
2050
  };
998
2051
  }
@@ -1004,62 +2057,31 @@ ${newJsonlText}` : newJsonlText);
1004
2057
  }
1005
2058
  const newMessages = parseLines(newJsonlText);
1006
2059
  if (newMessages.length === 0) return existing;
1007
- const allRawMessages = [...existing.rawMessages, ...newMessages];
1008
- let lastTurnStartIdx = existing.rawMessages.length;
1009
- let turnsToKeep = existing.turns.length > 0 ? existing.turns.length - 1 : 0;
1010
- if (existing.turns.length > 0) {
1011
- let userMsgCount = 0;
1012
- for (let i = existing.rawMessages.length - 1; i >= 0; i--) {
1013
- const msg = existing.rawMessages[i];
1014
- if (msg.type === "user" && !msg.isMeta) {
1015
- const content = msg.message?.content;
1016
- if (Array.isArray(content) && content.some((b) => b.type === "tool_result")) {
1017
- continue;
1018
- }
1019
- userMsgCount++;
1020
- if (userMsgCount === 1) {
1021
- lastTurnStartIdx = i;
1022
- break;
1023
- }
1024
- }
1025
- }
1026
- const progressParentIds = /* @__PURE__ */ new Set();
1027
- for (const msg of newMessages) {
1028
- if (msg.type === "progress") {
1029
- const parentId = msg.parentToolUseID;
1030
- if (parentId) progressParentIds.add(parentId);
1031
- }
1032
- }
1033
- if (progressParentIds.size > 0) {
1034
- for (let t = existing.turns.length - 2; t >= 0; t--) {
1035
- const turn = existing.turns[t];
1036
- const ownsProgressParent = turn.toolCalls.some((tc) => progressParentIds.has(tc.id));
1037
- if (ownsProgressParent) {
1038
- turnsToKeep = t;
1039
- let found = 0;
1040
- for (let i = 0; i < existing.rawMessages.length; i++) {
1041
- const msg = existing.rawMessages[i];
1042
- if (msg.type === "user" && !msg.isMeta) {
1043
- const content = msg.message?.content;
1044
- if (Array.isArray(content) && content.some((b) => b.type === "tool_result")) {
1045
- continue;
1046
- }
1047
- if (found === t) {
1048
- lastTurnStartIdx = i;
1049
- break;
1050
- }
1051
- found++;
1052
- }
1053
- }
1054
- break;
1055
- }
2060
+ const existingRawMessages = existing.rawMessages;
2061
+ const allRawMessages = [...existingRawMessages, ...newMessages];
2062
+ const turnStarts = findTurnStartIndices(existingRawMessages);
2063
+ const historyTurnCount = Math.max(0, existing.turns.length - turnStarts.length);
2064
+ let rebuildFromTurn = Math.max(0, turnStarts.length - 1);
2065
+ const progressParentIds = /* @__PURE__ */ new Set();
2066
+ for (const msg of newMessages) {
2067
+ if (msg.type === "progress") {
2068
+ const parentId = msg.parentToolUseID;
2069
+ if (parentId) progressParentIds.add(parentId);
2070
+ }
2071
+ }
2072
+ if (progressParentIds.size > 0) {
2073
+ for (let t = rebuildFromTurn - 1; t >= 0; t--) {
2074
+ const turn = existing.turns[historyTurnCount + t];
2075
+ if (turn?.toolCalls.some((tc) => progressParentIds.has(tc.id))) {
2076
+ rebuildFromTurn = t;
2077
+ break;
1056
2078
  }
1057
2079
  }
1058
2080
  }
1059
- const keptTurns = existing.turns.slice(0, turnsToKeep);
1060
- const tailMessages = allRawMessages.slice(lastTurnStartIdx);
1061
- const tailTurns = buildTurns(tailMessages);
1062
- const allTurns = [...keptTurns, ...tailTurns];
2081
+ const rebuildFrom = turnStarts[rebuildFromTurn] ?? existingRawMessages.length;
2082
+ const keptTurns = existing.turns.slice(0, historyTurnCount + rebuildFromTurn);
2083
+ const tailTurns = buildTurns(allRawMessages.slice(rebuildFrom));
2084
+ const allTurns = pairAgentMessageReplies([...keptTurns, ...tailTurns]);
1063
2085
  const stats = computeStats(allTurns);
1064
2086
  return {
1065
2087
  sessionId: existing.sessionId,
@@ -1067,11 +2089,13 @@ ${newJsonlText}` : newJsonlText);
1067
2089
  gitBranch: existing.gitBranch,
1068
2090
  cwd: existing.cwd,
1069
2091
  slug: existing.slug,
2092
+ name: existing.name,
1070
2093
  model: existing.model || (allTurns.length > 0 ? allTurns[allTurns.length - 1].model || "" : ""),
1071
2094
  turns: allTurns,
1072
2095
  stats,
1073
2096
  rawMessages: allRawMessages,
1074
- branchedFrom: existing.branchedFrom
2097
+ branchedFrom: existing.branchedFrom,
2098
+ agentKind: existing.agentKind
1075
2099
  };
1076
2100
  }
1077
2101
  function getUserMessageText(content) {
@@ -1329,17 +2353,11 @@ var SearchIndex = class {
1329
2353
  buildFull(projectsDir) {
1330
2354
  this.projectsDir = projectsDir;
1331
2355
  this.db.close();
1332
- try {
1333
- (0, import_node_fs.unlinkSync)(this.dbPath);
1334
- } catch {
1335
- }
1336
- try {
1337
- (0, import_node_fs.unlinkSync)(this.dbPath + "-wal");
1338
- } catch {
1339
- }
1340
- try {
1341
- (0, import_node_fs.unlinkSync)(this.dbPath + "-shm");
1342
- } catch {
2356
+ for (const suffix of ["", "-wal", "-shm"]) {
2357
+ try {
2358
+ (0, import_node_fs.unlinkSync)(this.dbPath + suffix);
2359
+ } catch {
2360
+ }
1343
2361
  }
1344
2362
  this.db = new Database(this.dbPath);
1345
2363
  this.db.exec("PRAGMA journal_mode = WAL");
@@ -1611,7 +2629,8 @@ var import_node_os = require("node:os");
1611
2629
  var dirs = {
1612
2630
  PROJECTS_DIR: (0, import_node_path2.join)((0, import_node_os.homedir)(), ".claude", "projects"),
1613
2631
  TEAMS_DIR: (0, import_node_path2.join)((0, import_node_os.homedir)(), ".claude", "teams"),
1614
- TASKS_DIR: (0, import_node_path2.join)((0, import_node_os.homedir)(), ".claude", "tasks")
2632
+ TASKS_DIR: (0, import_node_path2.join)((0, import_node_os.homedir)(), ".claude", "tasks"),
2633
+ CODEX_SESSIONS_DIR: (0, import_node_path2.join)((0, import_node_os.homedir)(), ".codex", "sessions")
1615
2634
  };
1616
2635
  var DEFAULT_DB_PATH = (0, import_node_path2.join)((0, import_node_os.homedir)(), ".claude", "cogpit-memory", "search-index.db");
1617
2636
 
@@ -1724,7 +2743,11 @@ async function matchSubagentToMember(leadSessionId, subagentFileName, members) {
1724
2743
  }
1725
2744
  return null;
1726
2745
  }
1727
- var HOME_PREFIX = (0, import_node_os2.homedir)().replace(/\//g, "-").replace(/^-/, "").toLowerCase();
2746
+ function encodeClaudeDirName(cwd) {
2747
+ const normalized = cwd.replace(/[\\/]+$/, "") || cwd;
2748
+ return normalized.replace(/[^a-zA-Z0-9]/g, "-");
2749
+ }
2750
+ var HOME_PREFIX = encodeClaudeDirName((0, import_node_os2.homedir)()).replace(/^-/, "").toLowerCase();
1728
2751
 
1729
2752
  // src/commands/search.ts
1730
2753
  async function searchSessions(query, opts, searchIndex) {
@@ -2076,6 +3099,7 @@ var import_promises3 = require("node:fs/promises");
2076
3099
  var import_node_path5 = require("node:path");
2077
3100
  var RESULT_TRUNCATE_LIMIT = 1e4;
2078
3101
  var L1_RESPONSE_LIMIT = 15e4;
3102
+ var COMPACTION_SUMMARY_PREVIEW = 400;
2079
3103
  function extractUserMessageText(userMessage) {
2080
3104
  if (userMessage === null) return null;
2081
3105
  if (typeof userMessage === "string") return userMessage;
@@ -2083,6 +3107,8 @@ function extractUserMessageText(userMessage) {
2083
3107
  for (const block of userMessage) {
2084
3108
  if (block.type === "text") parts.push(block.text);
2085
3109
  else if (block.type === "image") parts.push("[image attached]");
3110
+ else if (block.type === "document") parts.push("[document attached]");
3111
+ else if (block.type === "audio") parts.push("[audio attached]");
2086
3112
  }
2087
3113
  return parts.length > 0 ? parts.join("\n") : null;
2088
3114
  }
@@ -2165,9 +3191,13 @@ function mapTurnToSummary(turn, turnIndex) {
2165
3191
  subAgents,
2166
3192
  hasThinking: turn.thinking.length > 0,
2167
3193
  isError: turn.toolCalls.some((tc) => tc.isError),
2168
- compactionSummary: turn.compactionSummary ?? null
3194
+ compactionSummary: truncateCompactionSummary(turn.compactionSummary)
2169
3195
  };
2170
3196
  }
3197
+ function truncateCompactionSummary(summary) {
3198
+ if (!summary) return null;
3199
+ return summary.length > COMPACTION_SUMMARY_PREVIEW ? summary.slice(0, COMPACTION_SUMMARY_PREVIEW) + "... [truncated, use L2 for full text]" : summary;
3200
+ }
2171
3201
  function mapTurnToDetail(session, turnIndex) {
2172
3202
  const turn = session.turns[turnIndex];
2173
3203
  const contentBlocks = turn.contentBlocks.map((block) => mapContentBlock(block));
@@ -2175,12 +3205,17 @@ function mapTurnToDetail(session, turnIndex) {
2175
3205
  sessionId: session.sessionId,
2176
3206
  turnIndex,
2177
3207
  userMessage: extractUserMessageText(turn.userMessage),
3208
+ compactionSummary: turn.compactionSummary ?? null,
2178
3209
  contentBlocks,
2179
3210
  tokenUsage: turn.tokenUsage ? { input: turn.tokenUsage.input_tokens, output: turn.tokenUsage.output_tokens } : null,
2180
3211
  model: turn.model,
2181
3212
  durationMs: turn.durationMs
2182
3213
  };
2183
3214
  }
3215
+ function mapToolCall(tc) {
3216
+ const { result, resultTruncated } = truncateResult(tc.result);
3217
+ return { id: tc.id, name: tc.name, input: tc.input, result, resultTruncated, isError: tc.isError };
3218
+ }
2184
3219
  function mapContentBlock(block) {
2185
3220
  switch (block.kind) {
2186
3221
  case "thinking": {
@@ -2192,10 +3227,7 @@ function mapContentBlock(block) {
2192
3227
  case "tool_calls":
2193
3228
  return {
2194
3229
  kind: "tool_calls",
2195
- toolCalls: block.toolCalls.map((tc) => {
2196
- const { result, resultTruncated } = truncateResult(tc.result);
2197
- return { id: tc.id, name: tc.name, input: tc.input, result, resultTruncated, isError: tc.isError };
2198
- }),
3230
+ toolCalls: block.toolCalls.map(mapToolCall),
2199
3231
  timestamp: block.timestamp ?? null
2200
3232
  };
2201
3233
  case "sub_agent":
@@ -2205,6 +3237,35 @@ function mapContentBlock(block) {
2205
3237
  agents: block.messages.map(mapSubAgentDetail),
2206
3238
  timestamp: block.timestamp ?? null
2207
3239
  };
3240
+ case "queued_prompt":
3241
+ return { kind: "queued_prompt", content: block.content, timestamp: block.timestamp ?? null };
3242
+ case "agent_message":
3243
+ return {
3244
+ kind: "agent_message",
3245
+ sender: block.sender,
3246
+ body: block.body,
3247
+ reply: block.reply ?? null,
3248
+ timestamp: block.timestamp ?? null
3249
+ };
3250
+ case "hook_event":
3251
+ return { kind: "hook_event", events: block.events, timestamp: block.timestamp ?? null };
3252
+ case "plan_mode":
3253
+ return {
3254
+ kind: "plan_mode",
3255
+ plan: block.plan,
3256
+ planFilePath: block.planFilePath ?? null,
3257
+ status: block.status,
3258
+ toolCalls: block.toolCalls.map(mapToolCall),
3259
+ timestamp: block.timestamp ?? null
3260
+ };
3261
+ case "recap":
3262
+ return { kind: "recap", content: block.content, timestamp: block.timestamp ?? null };
3263
+ case "task_notification":
3264
+ return { kind: "task_notification", content: block.content, timestamp: block.timestamp ?? null };
3265
+ default: {
3266
+ const exhaustive = block;
3267
+ return exhaustive;
3268
+ }
2208
3269
  }
2209
3270
  }
2210
3271
  async function findSubagentFile(parentJsonlPath, agentId) {
@@ -2327,7 +3388,6 @@ async function getAgentOverview(sessionId, agentId) {
2327
3388
  async function getAgentTurnDetail(sessionId, agentId, turnIndex) {
2328
3389
  const jsonlPath = await findJsonlPath(sessionId);
2329
3390
  if (!jsonlPath) return { error: "Session not found" };
2330
- const content = await (0, import_promises3.readFile)(jsonlPath, "utf-8");
2331
3391
  const subagentFile = await findSubagentFile(jsonlPath, agentId);
2332
3392
  if (!subagentFile) return { error: "Agent not found" };
2333
3393
  const subagentContent = await (0, import_promises3.readFile)(subagentFile.filePath, "utf-8");
@@ -2339,35 +3399,140 @@ async function getAgentTurnDetail(sessionId, agentId, turnIndex) {
2339
3399
  // src/commands/sessions.ts
2340
3400
  var import_promises5 = require("node:fs/promises");
2341
3401
  var import_node_path6 = require("node:path");
2342
- var import_node_os3 = require("node:os");
2343
3402
 
2344
3403
  // src/lib/metadata.ts
2345
3404
  var import_promises4 = require("node:fs/promises");
2346
3405
 
2347
3406
  // src/lib/sessionStatus.ts
3407
+ var TASK_NOTIFICATION_MARKER = "<task-notification>";
3408
+ var TASK_ID_RE = /<task-id>([^<]+)<\/task-id>/g;
3409
+ var TOOL_USE_ID_RE = /<tool-use-id>([^<]+)<\/tool-use-id>/g;
3410
+ function isObject3(value) {
3411
+ return typeof value === "object" && value !== null;
3412
+ }
3413
+ function userMessageTexts(msg) {
3414
+ const message = isObject3(msg.message) ? msg.message : null;
3415
+ const content = message?.content;
3416
+ if (typeof content === "string") return [content];
3417
+ if (!Array.isArray(content)) return [];
3418
+ return content.filter((block) => isObject3(block) && typeof block.text === "string").map((block) => block.text);
3419
+ }
3420
+ function collectClaudeBackgroundWork(rawMessages) {
3421
+ const launches = [];
3422
+ const resolved = /* @__PURE__ */ new Set();
3423
+ const queuedIds = /* @__PURE__ */ new Set();
3424
+ const deliveredIds = /* @__PURE__ */ new Set();
3425
+ function noteNotifications(text, sink) {
3426
+ if (!text.includes(TASK_NOTIFICATION_MARKER)) return;
3427
+ for (const match of text.matchAll(TASK_ID_RE)) {
3428
+ resolved.add(match[1]);
3429
+ sink?.add(match[1]);
3430
+ }
3431
+ for (const match of text.matchAll(TOOL_USE_ID_RE)) {
3432
+ resolved.add(match[1]);
3433
+ sink?.add(match[1]);
3434
+ }
3435
+ }
3436
+ for (const msg of rawMessages) {
3437
+ if (msg.type === "user") {
3438
+ const toolUseResult = isObject3(msg.toolUseResult) ? msg.toolUseResult : null;
3439
+ if (toolUseResult?.status === "async_launched") {
3440
+ const message = isObject3(msg.message) ? msg.message : null;
3441
+ const blocks = Array.isArray(message?.content) ? message.content : [];
3442
+ const toolUseIds = blocks.filter((block) => isObject3(block) && block.type === "tool_result").map((block) => block.tool_use_id);
3443
+ const ids = [toolUseResult.agentId, toolUseResult.taskId, ...toolUseIds].filter((id) => typeof id === "string" && id.length > 0);
3444
+ const description = [toolUseResult.description, toolUseResult.workflowName, toolUseResult.summary].find((value) => typeof value === "string" && value.length > 0) ?? "";
3445
+ if (ids.length > 0) launches.push({ ids, description });
3446
+ }
3447
+ for (const text of userMessageTexts(msg)) noteNotifications(text, deliveredIds);
3448
+ continue;
3449
+ }
3450
+ if (msg.type === "assistant") {
3451
+ const message = isObject3(msg.message) ? msg.message : null;
3452
+ const blocks = Array.isArray(message?.content) ? message.content : [];
3453
+ for (const block of blocks) {
3454
+ if (!isObject3(block) || block.type !== "tool_use" || block.name !== "TaskStop") continue;
3455
+ const input = isObject3(block.input) ? block.input : null;
3456
+ const taskId = input?.task_id ?? input?.shell_id;
3457
+ if (typeof taskId === "string") resolved.add(taskId);
3458
+ }
3459
+ continue;
3460
+ }
3461
+ if (msg.type === "attachment") {
3462
+ const attachment = isObject3(msg.attachment) ? msg.attachment : null;
3463
+ if (typeof attachment?.prompt === "string") noteNotifications(attachment.prompt, deliveredIds);
3464
+ continue;
3465
+ }
3466
+ if (msg.type === "queue-operation" && typeof msg.content === "string") {
3467
+ const op = msg.operation;
3468
+ noteNotifications(msg.content, op === "enqueue" ? queuedIds : deliveredIds);
3469
+ }
3470
+ }
3471
+ return {
3472
+ pendingDescriptions: launches.filter((launch) => !launch.ids.some((id) => resolved.has(id))).map((launch) => launch.description),
3473
+ undeliveredNotifications: [...queuedIds].filter((id) => !deliveredIds.has(id)).length
3474
+ };
3475
+ }
3476
+ function awaitingAgentsResult(pendingDescriptions, pendingQueue) {
3477
+ return {
3478
+ status: "awaiting_agents",
3479
+ pendingQueue,
3480
+ pendingAgents: pendingDescriptions.length,
3481
+ pendingAgentDescriptions: pendingDescriptions.filter((description) => description.length > 0)
3482
+ };
3483
+ }
2348
3484
  function deriveSessionStatus(rawMessages) {
2349
3485
  const firstType = rawMessages[0]?.type;
2350
3486
  if (firstType === "session_meta" || firstType === "turn_context" || firstType === "event_msg" || firstType === "response_item") {
2351
3487
  return deriveCodexSessionStatus(rawMessages);
2352
3488
  }
2353
3489
  let pendingEnqueues = 0;
3490
+ let backgroundWork = null;
3491
+ let deliveringNotification = false;
2354
3492
  function result(status, toolName) {
2355
3493
  const info = { status, pendingQueue: Math.max(0, pendingEnqueues) };
2356
3494
  if (toolName) info.toolName = toolName;
2357
3495
  return info;
2358
3496
  }
3497
+ function getBackgroundWork() {
3498
+ return backgroundWork ??= collectClaudeBackgroundWork(rawMessages);
3499
+ }
3500
+ for (let i = rawMessages.length - 1; i >= 0; i--) {
3501
+ const msg = rawMessages[i];
3502
+ if (msg.type === "queue-operation") continue;
3503
+ if (msg.type === "progress") {
3504
+ const data = msg.data;
3505
+ if (data?.type === "hook_progress") {
3506
+ const decision = data.decision ?? data.hookSpecificOutput?.permissionDecision;
3507
+ if (decision === "defer") return result("deferred");
3508
+ }
3509
+ break;
3510
+ }
3511
+ break;
3512
+ }
2359
3513
  for (let i = rawMessages.length - 1; i >= 0; i--) {
2360
3514
  const msg = rawMessages[i];
2361
3515
  if (msg.type === "queue-operation") {
3516
+ if (typeof msg.content === "string" && msg.content.includes(TASK_NOTIFICATION_MARKER)) continue;
2362
3517
  const op = msg.operation;
2363
3518
  if (op === "enqueue") pendingEnqueues++;
2364
3519
  else if (op === "dequeue" || op === "remove") pendingEnqueues--;
2365
3520
  continue;
2366
3521
  }
3522
+ if (msg.type === "attachment") {
3523
+ const attachment = isObject3(msg.attachment) ? msg.attachment : null;
3524
+ if (attachment?.commandMode === "task-notification") deliveringNotification = true;
3525
+ continue;
3526
+ }
2367
3527
  if (msg.type === "assistant") {
2368
3528
  const message = msg.message;
2369
3529
  const stopReason = message?.stop_reason;
2370
3530
  if (stopReason === "end_turn") {
3531
+ const work = getBackgroundWork();
3532
+ if (deliveringNotification || work.undeliveredNotifications > 0) return result("processing");
3533
+ if (work.pendingDescriptions.length > 0) {
3534
+ return awaitingAgentsResult(work.pendingDescriptions, Math.max(0, pendingEnqueues));
3535
+ }
2371
3536
  let hasActivity = false;
2372
3537
  for (let j = i - 1; j >= 0; j--) {
2373
3538
  const m = rawMessages[j];
@@ -2390,19 +3555,136 @@ function deriveSessionStatus(rawMessages) {
2390
3555
  if (isMeta) continue;
2391
3556
  return result("processing");
2392
3557
  }
3558
+ if (msg.type === "system" && msg.subtype === "terminal_reason") {
3559
+ const reason = msg.reason;
3560
+ if (reason) {
3561
+ return { status: "completed", terminalReason: reason, pendingQueue: Math.max(0, pendingEnqueues) };
3562
+ }
3563
+ continue;
3564
+ }
2393
3565
  if (msg.type === "summary") continue;
2394
3566
  if (msg.type === "system" && msg.subtype === "compact_boundary") continue;
2395
3567
  }
2396
3568
  return { status: "idle" };
2397
3569
  }
3570
+ var TERMINAL_INTER_AGENT_TYPES = /* @__PURE__ */ new Set(["FINAL_ANSWER", "ERROR", "FAILED", "INTERRUPTED"]);
3571
+ function codexPayloadText(payload) {
3572
+ const content = payload.content;
3573
+ if (!Array.isArray(content)) return "";
3574
+ return content.filter((block) => isObject3(block) && block.type === "input_text" && typeof block.text === "string").map((block) => block.text).join("\n");
3575
+ }
3576
+ function parseJsonObject(value) {
3577
+ if (typeof value !== "string") return null;
3578
+ try {
3579
+ const parsed = JSON.parse(value);
3580
+ return isObject3(parsed) ? parsed : null;
3581
+ } catch {
3582
+ return null;
3583
+ }
3584
+ }
3585
+ function collectCodexPendingAgents(rawMessages) {
3586
+ const alive = /* @__PURE__ */ new Map();
3587
+ const done = /* @__PURE__ */ new Set();
3588
+ const agentIdByPath = /* @__PURE__ */ new Map();
3589
+ const spawnCalls = /* @__PURE__ */ new Map();
3590
+ const waitCalls = /* @__PURE__ */ new Set();
3591
+ const interruptCalls = /* @__PURE__ */ new Map();
3592
+ function markAlive(agentId, description) {
3593
+ if (!done.has(agentId)) alive.set(agentId, alive.get(agentId) || description);
3594
+ }
3595
+ for (const msg of rawMessages) {
3596
+ const payload = isObject3(msg.payload) ? msg.payload : null;
3597
+ if (!payload) continue;
3598
+ if (msg.type === "event_msg" && payload.type === "sub_agent_activity" && typeof payload.agent_thread_id === "string" && typeof payload.agent_path === "string") {
3599
+ const agentId = payload.agent_thread_id;
3600
+ const kind = typeof payload.kind === "string" ? payload.kind : "started";
3601
+ const eventId = typeof payload.event_id === "string" ? payload.event_id : "";
3602
+ const existingId = agentIdByPath.get(payload.agent_path);
3603
+ const known = alive.has(agentId) || done.has(agentId) || existingId !== void 0 || spawnCalls.has(eventId);
3604
+ if (kind !== "started" && !known) continue;
3605
+ if (existingId && existingId !== agentId) {
3606
+ if (alive.has(existingId)) {
3607
+ markAlive(agentId, alive.get(existingId) ?? "");
3608
+ alive.delete(existingId);
3609
+ }
3610
+ if (done.has(existingId)) done.add(agentId);
3611
+ }
3612
+ agentIdByPath.set(payload.agent_path, agentId);
3613
+ if (kind === "interrupted") {
3614
+ done.add(agentId);
3615
+ alive.delete(agentId);
3616
+ } else {
3617
+ markAlive(agentId, spawnCalls.get(eventId) ?? "");
3618
+ }
3619
+ continue;
3620
+ }
3621
+ if (msg.type !== "response_item") continue;
3622
+ if (payload.type === "function_call" && typeof payload.call_id === "string") {
3623
+ const name = normalizeFunctionName(typeof payload.name === "string" ? payload.name : "");
3624
+ if (name === "spawn_agent") {
3625
+ const input = parseJsonObject(payload.arguments);
3626
+ spawnCalls.set(payload.call_id, typeof input?.task_name === "string" ? input.task_name : "");
3627
+ } else if (name === "wait_agent") {
3628
+ waitCalls.add(payload.call_id);
3629
+ } else if (name === "interrupt_agent") {
3630
+ const input = parseJsonObject(payload.arguments);
3631
+ const targets = [input?.agent_id, ...Array.isArray(input?.agent_ids) ? input.agent_ids : []].filter((id) => typeof id === "string");
3632
+ interruptCalls.set(payload.call_id, targets);
3633
+ }
3634
+ continue;
3635
+ }
3636
+ if (payload.type === "function_call_output" && typeof payload.call_id === "string") {
3637
+ const callId = payload.call_id;
3638
+ if (spawnCalls.has(callId)) {
3639
+ const output = parseJsonObject(payload.output);
3640
+ const agentId = typeof output?.agent_id === "string" ? output.agent_id : "";
3641
+ if (agentId) {
3642
+ markAlive(agentId, spawnCalls.get(callId) ?? "");
3643
+ const agentPath = typeof output?.task_name === "string" ? output.task_name : "";
3644
+ if (agentPath && !agentIdByPath.has(agentPath)) agentIdByPath.set(agentPath, agentId);
3645
+ }
3646
+ } else if (waitCalls.has(callId)) {
3647
+ const output = parseJsonObject(payload.output);
3648
+ const statusMap = isObject3(output?.status) ? output.status : {};
3649
+ for (const [agentId, status] of Object.entries(statusMap)) {
3650
+ const statusValue = isObject3(status) ? status : {};
3651
+ const lifecycle = typeof statusValue.status === "string" ? statusValue.status : "";
3652
+ const isTerminal = typeof statusValue.completed === "string" || typeof statusValue.failed === "string" || typeof statusValue.error === "string" || typeof statusValue.interrupted === "string" || lifecycle === "completed" || lifecycle === "failed" || lifecycle === "interrupted";
3653
+ if (isTerminal) {
3654
+ done.add(agentId);
3655
+ alive.delete(agentId);
3656
+ }
3657
+ }
3658
+ } else if (interruptCalls.has(callId)) {
3659
+ for (const agentId of interruptCalls.get(callId) ?? []) {
3660
+ done.add(agentId);
3661
+ alive.delete(agentId);
3662
+ }
3663
+ }
3664
+ continue;
3665
+ }
3666
+ if (payload.type === "agent_message" && typeof payload.author === "string") {
3667
+ const text = codexPayloadText(payload);
3668
+ const messageType = text.match(/^Message Type:\s*([^\r\n]+)/)?.[1]?.trim().toUpperCase() ?? "";
3669
+ if (!TERMINAL_INTER_AGENT_TYPES.has(messageType)) continue;
3670
+ const agentId = agentIdByPath.get(payload.author) ?? payload.author;
3671
+ done.add(agentId);
3672
+ alive.delete(agentId);
3673
+ }
3674
+ }
3675
+ return [...alive.entries()].filter(([agentId]) => !done.has(agentId)).map(([, description]) => description);
3676
+ }
2398
3677
  function deriveCodexSessionStatus(rawMessages) {
2399
3678
  for (let i = rawMessages.length - 1; i >= 0; i--) {
2400
3679
  const msg = rawMessages[i];
2401
3680
  if (msg.type === "event_msg") {
2402
3681
  const payload = msg.payload;
2403
3682
  switch (payload?.type) {
2404
- case "task_complete":
3683
+ case "task_complete": {
3684
+ const pending = collectCodexPendingAgents(rawMessages);
3685
+ if (pending.length > 0) return awaitingAgentsResult(pending, 0);
2405
3686
  return { status: "completed" };
3687
+ }
2406
3688
  case "task_started":
2407
3689
  return { status: "processing" };
2408
3690
  case "agent_message":
@@ -2415,7 +3697,7 @@ function deriveCodexSessionStatus(rawMessages) {
2415
3697
  const payload = msg.payload;
2416
3698
  if (!payload) continue;
2417
3699
  if (payload.type === "function_call") {
2418
- return { status: "tool_use", toolName: payload.name };
3700
+ return { status: "tool_use", toolName: payload.name ? normalizeFunctionName(payload.name) : payload.name };
2419
3701
  }
2420
3702
  if (payload.type === "message") {
2421
3703
  if (payload.role === "assistant") return { status: "thinking" };
@@ -2615,7 +3897,23 @@ async function getSessionStatus(filePath) {
2615
3897
  }
2616
3898
 
2617
3899
  // src/commands/sessions.ts
2618
- var CODEX_SESSIONS_DIR = (0, import_node_path6.join)((0, import_node_os3.homedir)(), ".codex", "sessions");
3900
+ function toSessionSummary(file, meta, status) {
3901
+ return {
3902
+ sessionId: meta.sessionId,
3903
+ filePath: file.path,
3904
+ timestamp: meta.timestamp,
3905
+ model: meta.model,
3906
+ cwd: meta.cwd,
3907
+ gitBranch: meta.gitBranch,
3908
+ slug: meta.slug,
3909
+ firstMessage: meta.firstUserMessage,
3910
+ lastMessage: meta.lastUserMessage,
3911
+ turnCount: meta.turnCount,
3912
+ status,
3913
+ mtime: file.mtimeMs,
3914
+ source: file.path.startsWith(dirs.CODEX_SESSIONS_DIR + "/") ? "codex" : "claude"
3915
+ };
3916
+ }
2619
3917
  async function listCodexSessionFiles(cutoff) {
2620
3918
  const walk = async (dir, depth) => {
2621
3919
  if (depth > 4) return [];
@@ -2642,7 +3940,7 @@ async function listCodexSessionFiles(cutoff) {
2642
3940
  }
2643
3941
  return results;
2644
3942
  };
2645
- return walk(CODEX_SESSIONS_DIR, 0);
3943
+ return walk(dirs.CODEX_SESSIONS_DIR, 0);
2646
3944
  }
2647
3945
  async function listSessions(opts = {}) {
2648
3946
  const limit = Math.min(Math.max(1, opts.limit ?? 20), 100);
@@ -2655,29 +3953,31 @@ async function listSessions(opts = {}) {
2655
3953
  return [];
2656
3954
  }
2657
3955
  const projectDirs = entries.filter((e) => e.isDirectory() && e.name !== "memory").map((e) => (0, import_node_path6.join)(dirs.PROJECTS_DIR, e.name));
2658
- const nested = await Promise.all(
2659
- projectDirs.map(async (projectDir) => {
2660
- try {
2661
- const files = await (0, import_promises5.readdir)(projectDir);
2662
- const jsonlFiles = files.filter((f) => f.endsWith(".jsonl"));
2663
- const statResults = await Promise.all(
2664
- jsonlFiles.map(async (f) => {
2665
- const filePath = (0, import_node_path6.join)(projectDir, f);
2666
- try {
2667
- const s = await (0, import_promises5.stat)(filePath);
2668
- return s.mtimeMs >= cutoff ? { path: filePath, mtimeMs: s.mtimeMs } : null;
2669
- } catch {
2670
- return null;
2671
- }
2672
- })
2673
- );
2674
- return statResults.filter((r) => r !== null);
2675
- } catch {
2676
- return [];
2677
- }
2678
- })
2679
- );
2680
- const codexFiles = await listCodexSessionFiles(cutoff);
3956
+ const [nested, codexFiles] = await Promise.all([
3957
+ Promise.all(
3958
+ projectDirs.map(async (projectDir) => {
3959
+ try {
3960
+ const files = await (0, import_promises5.readdir)(projectDir);
3961
+ const jsonlFiles = files.filter((f) => f.endsWith(".jsonl"));
3962
+ const statResults = await Promise.all(
3963
+ jsonlFiles.map(async (f) => {
3964
+ const filePath = (0, import_node_path6.join)(projectDir, f);
3965
+ try {
3966
+ const s = await (0, import_promises5.stat)(filePath);
3967
+ return s.mtimeMs >= cutoff ? { path: filePath, mtimeMs: s.mtimeMs } : null;
3968
+ } catch {
3969
+ return null;
3970
+ }
3971
+ })
3972
+ );
3973
+ return statResults.filter((r) => r !== null);
3974
+ } catch {
3975
+ return [];
3976
+ }
3977
+ })
3978
+ ),
3979
+ listCodexSessionFiles(cutoff)
3980
+ ]);
2681
3981
  const allFiles = [...nested.flat(), ...codexFiles];
2682
3982
  allFiles.sort((a, b) => b.mtimeMs - a.mtimeMs);
2683
3983
  const results = [];
@@ -2689,29 +3989,14 @@ async function listSessions(opts = {}) {
2689
3989
  getSessionStatus(file.path)
2690
3990
  ]);
2691
3991
  if (opts.cwd && meta.cwd !== opts.cwd) continue;
2692
- results.push({
2693
- sessionId: meta.sessionId,
2694
- filePath: file.path,
2695
- timestamp: meta.timestamp,
2696
- model: meta.model,
2697
- cwd: meta.cwd,
2698
- gitBranch: meta.gitBranch,
2699
- slug: meta.slug,
2700
- firstMessage: meta.firstUserMessage,
2701
- lastMessage: meta.lastUserMessage,
2702
- turnCount: meta.turnCount,
2703
- status: statusInfo.status,
2704
- mtime: file.mtimeMs,
2705
- source: file.path.startsWith(CODEX_SESSIONS_DIR + "/") ? "codex" : "claude"
2706
- });
3992
+ results.push(toSessionSummary(file, meta, statusInfo.status));
2707
3993
  } catch {
2708
3994
  }
2709
3995
  }
2710
3996
  return results;
2711
3997
  }
2712
3998
  async function currentSession(cwd) {
2713
- const projectDirName = cwd.replace(/[/.]/g, "-");
2714
- const projectDir = (0, import_node_path6.join)(dirs.PROJECTS_DIR, projectDirName);
3999
+ const projectDir = (0, import_node_path6.join)(dirs.PROJECTS_DIR, encodeClaudeDirName(cwd));
2715
4000
  let files;
2716
4001
  try {
2717
4002
  files = await (0, import_promises5.readdir)(projectDir);
@@ -2719,18 +4004,20 @@ async function currentSession(cwd) {
2719
4004
  files = [];
2720
4005
  }
2721
4006
  const jsonlFiles = files.filter((f) => f.endsWith(".jsonl"));
2722
- const statResults = await Promise.all(
2723
- jsonlFiles.map(async (f) => {
2724
- const filePath = (0, import_node_path6.join)(projectDir, f);
2725
- try {
2726
- const s = await (0, import_promises5.stat)(filePath);
2727
- return { path: filePath, mtimeMs: s.mtimeMs };
2728
- } catch {
2729
- return null;
2730
- }
2731
- })
2732
- );
2733
- const codexCandidates = await listCodexSessionFiles(0);
4007
+ const [statResults, codexCandidates] = await Promise.all([
4008
+ Promise.all(
4009
+ jsonlFiles.map(async (f) => {
4010
+ const filePath = (0, import_node_path6.join)(projectDir, f);
4011
+ try {
4012
+ const s = await (0, import_promises5.stat)(filePath);
4013
+ return { path: filePath, mtimeMs: s.mtimeMs };
4014
+ } catch {
4015
+ return null;
4016
+ }
4017
+ })
4018
+ ),
4019
+ listCodexSessionFiles(0)
4020
+ ]);
2734
4021
  const codexMatches = [];
2735
4022
  for (const file of codexCandidates) {
2736
4023
  try {
@@ -2752,21 +4039,7 @@ async function currentSession(cwd) {
2752
4039
  getSessionMeta(latest.path),
2753
4040
  getSessionStatus(latest.path)
2754
4041
  ]);
2755
- return {
2756
- sessionId: meta.sessionId,
2757
- filePath: latest.path,
2758
- timestamp: meta.timestamp,
2759
- model: meta.model,
2760
- cwd: meta.cwd,
2761
- gitBranch: meta.gitBranch,
2762
- slug: meta.slug,
2763
- firstMessage: meta.firstUserMessage,
2764
- lastMessage: meta.lastUserMessage,
2765
- turnCount: meta.turnCount,
2766
- status: statusInfo.status,
2767
- mtime: latest.mtimeMs,
2768
- source: latest.path.startsWith(CODEX_SESSIONS_DIR + "/") ? "codex" : "claude"
2769
- };
4042
+ return toSessionSummary(latest, meta, statusInfo.status);
2770
4043
  }
2771
4044
 
2772
4045
  // src/commands/index-cmd.ts