cogpit-memory 0.1.9 → 0.1.10

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,14 @@ 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
+ }
76
79
  function isCompactBoundary(msg) {
77
80
  return msg.type === "system" && msg.subtype === "compact_boundary";
78
81
  }
82
+
83
+ // src/lib/turnBuilder.ts
79
84
  function extractTextFromContent(content) {
80
85
  if (typeof content === "string") return content;
81
86
  return content.filter((b) => b.type === "text").map((b) => b.text).join("\n");
@@ -88,6 +93,11 @@ function extractToolResultText(content) {
88
93
  return "";
89
94
  }).filter(Boolean).join("\n");
90
95
  }
96
+ function isVisibleQueuedPrompt(content) {
97
+ if (!content?.trim()) return false;
98
+ const trimmed = content.trimStart();
99
+ return !trimmed.startsWith("<task-notification>") && !trimmed.startsWith("<local-command-");
100
+ }
91
101
  function mergeTokenUsage(existing, incoming) {
92
102
  if (!existing) {
93
103
  return { ...incoming };
@@ -96,7 +106,8 @@ function mergeTokenUsage(existing, incoming) {
96
106
  input_tokens: existing.input_tokens + incoming.input_tokens,
97
107
  output_tokens: existing.output_tokens + incoming.output_tokens,
98
108
  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)
109
+ cache_read_input_tokens: (existing.cache_read_input_tokens ?? 0) + (incoming.cache_read_input_tokens ?? 0),
110
+ speed: incoming.speed ?? existing.speed
100
111
  };
101
112
  }
102
113
  function buildCompactionSummary(turns, title) {
@@ -129,10 +140,95 @@ function buildCompactionSummary(turns, title) {
129
140
  }
130
141
  return parts.join("\n");
131
142
  }
143
+ function groupPlanModeBlocks(blocks) {
144
+ const result = [];
145
+ let i = 0;
146
+ while (i < blocks.length) {
147
+ const block = blocks[i];
148
+ if (block.kind === "tool_calls") {
149
+ const enterIdx = block.toolCalls.findIndex((tc) => tc.name === "EnterPlanMode");
150
+ if (enterIdx !== -1) {
151
+ const enterCall = block.toolCalls[enterIdx];
152
+ const plan = String(enterCall.input.plan ?? "");
153
+ const timestamp = block.timestamp;
154
+ const before = block.toolCalls.slice(0, enterIdx);
155
+ if (before.length > 0) {
156
+ result.push({ kind: "tool_calls", toolCalls: before, timestamp: block.timestamp });
157
+ }
158
+ const embedded = [];
159
+ const afterEnterInSameBlock = block.toolCalls.slice(enterIdx + 1);
160
+ const exitInSameBlock = afterEnterInSameBlock.findIndex((tc) => tc.name === "ExitPlanMode");
161
+ if (exitInSameBlock !== -1) {
162
+ embedded.push(...afterEnterInSameBlock.slice(0, exitInSameBlock));
163
+ const exitCall = afterEnterInSameBlock[exitInSameBlock];
164
+ const planFilePath = exitCall.input.path ? String(exitCall.input.path) : void 0;
165
+ let status = "pending";
166
+ if (exitCall.result !== null) {
167
+ status = exitCall.isError ? "rejected" : "approved";
168
+ }
169
+ result.push({ kind: "plan_mode", plan, planFilePath, status, toolCalls: embedded, timestamp });
170
+ const after = afterEnterInSameBlock.slice(exitInSameBlock + 1);
171
+ if (after.length > 0) {
172
+ result.push({ kind: "tool_calls", toolCalls: after, timestamp: block.timestamp });
173
+ }
174
+ i++;
175
+ continue;
176
+ } else {
177
+ embedded.push(...afterEnterInSameBlock);
178
+ let exitCall;
179
+ let planFilePath;
180
+ let status = "pending";
181
+ let j = i + 1;
182
+ const passthroughBlocks = [];
183
+ while (j < blocks.length) {
184
+ const next = blocks[j];
185
+ if (next.kind === "tool_calls") {
186
+ const exitIdx = next.toolCalls.findIndex((tc) => tc.name === "ExitPlanMode");
187
+ if (exitIdx !== -1) {
188
+ embedded.push(...next.toolCalls.slice(0, exitIdx));
189
+ exitCall = next.toolCalls[exitIdx];
190
+ planFilePath = exitCall.input.path ? String(exitCall.input.path) : void 0;
191
+ if (exitCall.result !== null) {
192
+ status = exitCall.isError ? "rejected" : "approved";
193
+ }
194
+ const tail = next.toolCalls.slice(exitIdx + 1);
195
+ result.push({ kind: "plan_mode", plan, planFilePath, status, toolCalls: embedded, timestamp });
196
+ result.push(...passthroughBlocks);
197
+ if (tail.length > 0) {
198
+ result.push({ kind: "tool_calls", toolCalls: tail, timestamp: next.timestamp });
199
+ }
200
+ i = j + 1;
201
+ break;
202
+ } else {
203
+ embedded.push(...next.toolCalls);
204
+ j++;
205
+ }
206
+ } else if (next.kind === "hook_event" || next.kind === "text" || next.kind === "queued_prompt") {
207
+ passthroughBlocks.push(next);
208
+ j++;
209
+ } else {
210
+ break;
211
+ }
212
+ }
213
+ if (!exitCall) {
214
+ result.push({ kind: "plan_mode", plan, planFilePath: void 0, status: "pending", toolCalls: embedded, timestamp });
215
+ result.push(...passthroughBlocks);
216
+ i = j;
217
+ }
218
+ continue;
219
+ }
220
+ }
221
+ }
222
+ result.push(block);
223
+ i++;
224
+ }
225
+ return result;
226
+ }
132
227
  function buildTurns(messages) {
133
228
  const turns = [];
134
229
  let current = null;
135
230
  let pendingCompaction = null;
231
+ let pendingRecap = null;
136
232
  const pendingToolUses = /* @__PURE__ */ new Map();
137
233
  const seenMessageIds = /* @__PURE__ */ new Set();
138
234
  const subAgentMap = /* @__PURE__ */ new Map();
@@ -163,6 +259,22 @@ function buildTurns(messages) {
163
259
  for (const [parentId] of subAgentMap) {
164
260
  flushSubAgentMessages(parentId);
165
261
  }
262
+ for (const block of current.contentBlocks) {
263
+ if (block.kind !== "hook_event") continue;
264
+ for (const ev of block.events) {
265
+ const isPostToolUse = ev.eventName === "PostToolUse" || ev.eventName.startsWith("PostToolUse:");
266
+ if (!isPostToolUse || !ev.toolUseId) continue;
267
+ const toolCall = current.toolCalls.find((tc) => tc.id === ev.toolUseId);
268
+ if (!toolCall) continue;
269
+ if (ev.updatedToolOutput) {
270
+ toolCall.outputReplacedByHook = true;
271
+ }
272
+ if (ev.durationMs !== void 0) {
273
+ toolCall.hookDurationMs = (toolCall.hookDurationMs ?? 0) + ev.durationMs;
274
+ }
275
+ }
276
+ }
277
+ current.contentBlocks = groupPlanModeBlocks(current.contentBlocks);
166
278
  turns.push(current);
167
279
  current = null;
168
280
  agentBlockMap.clear();
@@ -184,6 +296,20 @@ function buildTurns(messages) {
184
296
  );
185
297
  continue;
186
298
  }
299
+ if (isSystemMessage(msg) && msg.subtype === "away_summary" && msg.content) {
300
+ pendingRecap = { content: msg.content, timestamp: msg.timestamp };
301
+ continue;
302
+ }
303
+ if (isQueueOperationMessage(msg)) {
304
+ if (current && msg.operation === "enqueue" && isVisibleQueuedPrompt(msg.content)) {
305
+ current.contentBlocks.push({
306
+ kind: "queued_prompt",
307
+ content: msg.content,
308
+ timestamp: msg.timestamp
309
+ });
310
+ }
311
+ continue;
312
+ }
187
313
  if (isUserMessage(msg) && !msg.isMeta) {
188
314
  const content = msg.message.content;
189
315
  if (typeof content !== "string" && Array.isArray(content)) {
@@ -194,7 +320,7 @@ function buildTurns(messages) {
194
320
  const pending = pendingToolUses.get(block.tool_use_id);
195
321
  if (pending) {
196
322
  pending.turn.toolCalls[pending.index].result = extractToolResultText(block.content);
197
- pending.turn.toolCalls[pending.index].isError = block.is_error;
323
+ pending.turn.toolCalls[pending.index].isError = block.is_error === true;
198
324
  pendingToolUses.delete(block.tool_use_id);
199
325
  }
200
326
  }
@@ -212,6 +338,7 @@ function buildTurns(messages) {
212
338
  }
213
339
  const agentMsg = {
214
340
  agentId: toolUseResult.agentId,
341
+ parentToolUseId: toolUseId || void 0,
215
342
  agentName: taskMeta?.name ?? null,
216
343
  subagentType: taskMeta?.subagentType ?? null,
217
344
  type: "assistant",
@@ -263,6 +390,10 @@ function buildTurns(messages) {
263
390
  current.compactionSummary = pendingCompaction;
264
391
  pendingCompaction = null;
265
392
  }
393
+ if (pendingRecap) {
394
+ current.contentBlocks.push({ kind: "recap", content: pendingRecap.content, timestamp: pendingRecap.timestamp });
395
+ pendingRecap = null;
396
+ }
266
397
  continue;
267
398
  }
268
399
  if (isAssistantMessage(msg)) {
@@ -312,6 +443,7 @@ function buildTurns(messages) {
312
443
  if (block.type === "thinking") {
313
444
  flushToolCalls2();
314
445
  const tb = block;
446
+ if (!tb.thinking) continue;
315
447
  current.thinking.push(tb);
316
448
  msgThinking.push(tb);
317
449
  } else if (block.type === "text") {
@@ -391,6 +523,7 @@ function buildTurns(messages) {
391
523
  const taskMeta = taskMetaMap.get(parentId);
392
524
  const agentMsg = {
393
525
  agentId: data.agentId,
526
+ parentToolUseId: parentId || void 0,
394
527
  agentName: taskMeta?.name ?? null,
395
528
  subagentType: taskMeta?.subagentType ?? null,
396
529
  type: data.message.type,
@@ -436,7 +569,7 @@ function buildTurns(messages) {
436
569
  );
437
570
  if (match) {
438
571
  match.result = extractToolResultText(block.content);
439
- match.isError = block.is_error;
572
+ match.isError = block.is_error === true;
440
573
  }
441
574
  }
442
575
  }
@@ -455,6 +588,36 @@ function buildTurns(messages) {
455
588
  }
456
589
  continue;
457
590
  }
591
+ if (isProgressMessage(msg) && msg.data.type === "hook_progress") {
592
+ const data = msg.data;
593
+ const eventName = String(data.hook_event_name ?? data.hookEvent ?? "unknown");
594
+ const hookSpecific = data.hookSpecificOutput;
595
+ const ev = {
596
+ eventName,
597
+ source: data.source !== void 0 ? String(data.source) : void 0,
598
+ toolName: data.tool_name !== void 0 ? String(data.tool_name) : void 0,
599
+ toolUseId: data.tool_use_id !== void 0 ? String(data.tool_use_id) : void 0,
600
+ command: data.command !== void 0 ? String(data.command) : void 0,
601
+ output: data.output !== void 0 ? String(data.output) : void 0,
602
+ stderr: data.stderr !== void 0 ? String(data.stderr) : void 0,
603
+ exitCode: data.exit_code !== void 0 ? Number(data.exit_code) : void 0,
604
+ decision: data.decision !== void 0 ? String(data.decision) : void 0,
605
+ durationMs: data.duration_ms !== void 0 ? Number(data.duration_ms) : void 0,
606
+ updatedToolOutput: hookSpecific?.updatedToolOutput !== void 0 ? String(hookSpecific.updatedToolOutput) : void 0,
607
+ sessionTitle: hookSpecific?.sessionTitle !== void 0 ? String(hookSpecific.sessionTitle) : void 0,
608
+ worktreePath: hookSpecific?.worktreePath !== void 0 ? String(hookSpecific.worktreePath) : void 0,
609
+ timestamp: msg.timestamp ?? ""
610
+ };
611
+ if (current) {
612
+ const last = current.contentBlocks[current.contentBlocks.length - 1];
613
+ if (last && last.kind === "hook_event") {
614
+ last.events.push(ev);
615
+ } else {
616
+ current.contentBlocks.push({ kind: "hook_event", events: [ev], timestamp: msg.timestamp });
617
+ }
618
+ }
619
+ continue;
620
+ }
458
621
  if (isSystemMessage(msg) && msg.subtype === "turn_duration" && current) {
459
622
  current.durationMs = msg.durationMs ?? null;
460
623
  continue;
@@ -465,58 +628,84 @@ function buildTurns(messages) {
465
628
  }
466
629
 
467
630
  // src/lib/pricingTiers.ts
468
- var EXTENDED_CONTEXT_THRESHOLD = 2e5;
469
631
  var TIER_HAIKU_35 = { input: 0.8, output: 4, cacheWrite: 1, cacheRead: 0.08, webSearch: 0.01 };
470
632
  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 };
633
+ var TIER_SONNET = { input: 3, output: 15, cacheWrite: 3.75, cacheRead: 0.3, webSearch: 0.01 };
634
+ var TIER_OPUS_MID = { input: 5, output: 25, cacheWrite: 6.25, cacheRead: 0.5, webSearch: 0.01 };
635
+ var TIER_FRONTIER = { input: 10, output: 50, cacheWrite: 12.5, cacheRead: 1, webSearch: 0.01 };
473
636
  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 };
637
+ var TIER_GPT_56_SOL = { input: 5, output: 30, cacheWrite: 6.25, cacheRead: 0.5, webSearch: 0.01 };
638
+ var TIER_GPT_56_TERRA = { input: 2.5, output: 15, cacheWrite: 3.125, cacheRead: 0.25, webSearch: 0.01 };
639
+ var TIER_GPT_56_LUNA = { input: 1, output: 6, cacheWrite: 1.25, cacheRead: 0.1, webSearch: 0.01 };
640
+ var TIER_GPT_55 = { input: 5, output: 30, cacheWrite: 5, cacheRead: 0.5, webSearch: 0.01 };
641
+ var TIER_GPT_54 = { input: 2.5, output: 15, cacheWrite: 2.5, cacheRead: 0.25, webSearch: 0.01 };
642
+ var TIER_GPT_54_MINI = { input: 0.75, output: 4.5, cacheWrite: 0.75, cacheRead: 0.075, webSearch: 0.01 };
643
+ var TIER_FAST = { input: 30, output: 150, cacheWrite: 37.5, cacheRead: 3, webSearch: 0.01 };
476
644
  var MODEL_TIERS = [
645
+ // OpenAI GPT models. Keep variants before their shared family prefixes.
646
+ { match: "gpt-5.6-sol", tier: TIER_GPT_56_SOL },
647
+ { match: "gpt-5.6-terra", tier: TIER_GPT_56_TERRA },
648
+ { match: "gpt-5.6-luna", tier: TIER_GPT_56_LUNA },
649
+ { match: "gpt-5.6", tier: TIER_GPT_56_SOL },
650
+ { match: "gpt-5.4-mini", tier: TIER_GPT_54_MINI },
651
+ { match: "gpt-5.5", tier: TIER_GPT_55 },
652
+ { match: "gpt-5.4", tier: TIER_GPT_54 },
653
+ // Frontier (Fable 5 / Mythos 5 / Opus 4.8)
654
+ { match: "fable-5", tier: TIER_FRONTIER },
655
+ { match: "mythos-5", tier: TIER_FRONTIER },
656
+ { match: "opus-4-8", tier: TIER_FRONTIER },
477
657
  // Haiku
478
658
  { match: "haiku-4-5", tier: TIER_HAIKU_45 },
479
659
  { match: "haiku-4-0", tier: TIER_HAIKU_35 },
480
660
  { 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 },
661
+ // Sonnet (3.5 through 4.6 — all one tier in current CC)
662
+ { match: "sonnet-4-7", tier: TIER_SONNET },
663
+ { match: "sonnet-4-6", tier: TIER_SONNET },
664
+ { match: "sonnet-4-5", tier: TIER_SONNET },
665
+ { match: "sonnet-4-0", tier: TIER_SONNET },
666
+ { match: "3-7-sonnet", tier: TIER_SONNET },
667
+ { match: "3-5-sonnet", tier: TIER_SONNET },
668
+ // Opus 4.5–4.7
669
+ { match: "opus-4-7", tier: TIER_OPUS_MID },
670
+ { match: "opus-4-6", tier: TIER_OPUS_MID },
671
+ { match: "opus-4-5", tier: TIER_OPUS_MID },
491
672
  // Opus legacy (4.0, 4.1)
492
673
  { match: "opus-4-1", tier: TIER_OPUS_LEGACY },
493
674
  { match: "opus-4-0", tier: TIER_OPUS_LEGACY }
494
675
  ];
495
- var DEFAULT_TIER = TIER_SONNET_LATEST;
496
- var DEFAULT_EXTENDED_TIER = TIER_EXTENDED;
676
+ var FAST_MODE_MODELS = ["opus-4-6", "opus-4-7"];
677
+ var DEFAULT_TIER = TIER_OPUS_MID;
497
678
  var FAMILY_FALLBACKS = [
679
+ { match: "fable", tier: TIER_FRONTIER },
680
+ { match: "mythos", tier: TIER_FRONTIER },
498
681
  { match: "haiku", tier: TIER_HAIKU_45 },
499
- { match: "sonnet", tier: TIER_SONNET_LATEST },
500
- { match: "opus", tier: TIER_SONNET_LATEST }
682
+ { match: "sonnet", tier: TIER_SONNET },
683
+ { match: "opus", tier: TIER_OPUS_MID }
501
684
  ];
502
- function resolveTier(model, totalInputTokens) {
503
- const isExtended = (totalInputTokens ?? 0) > EXTENDED_CONTEXT_THRESHOLD;
685
+ function resolveTier(model, speed) {
686
+ if (speed === "fast" && FAST_MODE_MODELS.some((m) => model.includes(m))) {
687
+ return TIER_FAST;
688
+ }
504
689
  for (const entry of MODEL_TIERS) {
505
- if (model.includes(entry.match)) {
506
- return isExtended && entry.extendedTier ? entry.extendedTier : entry.tier;
507
- }
690
+ if (model.includes(entry.match)) return entry.tier;
508
691
  }
509
692
  for (const entry of FAMILY_FALLBACKS) {
510
693
  if (model.includes(entry.match)) return entry.tier;
511
694
  }
512
- return isExtended ? DEFAULT_EXTENDED_TIER : DEFAULT_TIER;
695
+ return DEFAULT_TIER;
696
+ }
697
+ function hasKnownPricing(model) {
698
+ if (!model) return true;
699
+ const normalized = model.toLowerCase();
700
+ if (!normalized.startsWith("gpt-")) return true;
701
+ return MODEL_TIERS.some((entry) => normalized.includes(entry.match));
513
702
  }
514
703
 
515
704
  // src/lib/token-costs.ts
516
705
  var CHARS_PER_TOKEN = 4;
517
706
  function calculateCost(c) {
518
- const totalInput = c.inputTokens + c.cacheWriteTokens + c.cacheReadTokens;
519
- const p = resolveTier(c.model ?? "", totalInput);
707
+ if (!hasKnownPricing(c.model)) return Number.NaN;
708
+ const p = resolveTier(c.model ?? "", c.speed);
520
709
  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
710
  }
522
711
  function charsToTokens(chars) {
@@ -554,7 +743,8 @@ function calculateTurnCostEstimated(turn) {
554
743
  inputTokens: u.input_tokens,
555
744
  outputTokens: estimateTotalOutputTokens(turn),
556
745
  cacheWriteTokens: u.cache_creation_input_tokens ?? 0,
557
- cacheReadTokens: u.cache_read_input_tokens ?? 0
746
+ cacheReadTokens: u.cache_read_input_tokens ?? 0,
747
+ speed: u.speed
558
748
  });
559
749
  }
560
750
  function calculateSubAgentCostEstimated(sa) {
@@ -565,7 +755,8 @@ function calculateSubAgentCostEstimated(sa) {
565
755
  inputTokens: u.input_tokens,
566
756
  outputTokens: estimateSubAgentOutput(sa),
567
757
  cacheWriteTokens: u.cache_creation_input_tokens ?? 0,
568
- cacheReadTokens: u.cache_read_input_tokens ?? 0
758
+ cacheReadTokens: u.cache_read_input_tokens ?? 0,
759
+ speed: u.speed
569
760
  });
570
761
  }
571
762
 
@@ -585,6 +776,19 @@ function addUsageToStats(stats, usage, estimatedOutput, cost) {
585
776
  stats.totalCacheReadTokens += usage.cache_read_input_tokens ?? 0;
586
777
  stats.totalCostUSD += cost;
587
778
  }
779
+ function createEmptySessionStats(turnCount) {
780
+ return {
781
+ totalInputTokens: 0,
782
+ totalOutputTokens: 0,
783
+ totalCacheCreationTokens: 0,
784
+ totalCacheReadTokens: 0,
785
+ totalCostUSD: 0,
786
+ toolCallCounts: {},
787
+ errorCount: 0,
788
+ totalDurationMs: 0,
789
+ turnCount
790
+ };
791
+ }
588
792
  function computeStats(turns) {
589
793
  const stats = {
590
794
  totalInputTokens: 0,
@@ -613,8 +817,271 @@ function computeStats(turns) {
613
817
  return stats;
614
818
  }
615
819
 
820
+ // src/lib/codex-patches.ts
821
+ var JS_SIMPLE_ESCAPES = {
822
+ "0": "\0",
823
+ b: "\b",
824
+ f: "\f",
825
+ n: "\n",
826
+ r: "\r",
827
+ t: " ",
828
+ v: "\v"
829
+ };
830
+ function readJsStringLiteral(source, startIndex) {
831
+ const quote = source[startIndex];
832
+ if (quote !== '"' && quote !== "'" && quote !== "`") return null;
833
+ let value = "";
834
+ let index = startIndex + 1;
835
+ while (index < source.length) {
836
+ const char = source[index];
837
+ if (char === quote) return { endIndex: index + 1, value };
838
+ if (char !== "\\") {
839
+ value += char;
840
+ index++;
841
+ continue;
842
+ }
843
+ index++;
844
+ if (index >= source.length) return null;
845
+ const escaped = source[index];
846
+ if (escaped in JS_SIMPLE_ESCAPES) {
847
+ value += JS_SIMPLE_ESCAPES[escaped];
848
+ index++;
849
+ continue;
850
+ }
851
+ if (escaped === "\n") {
852
+ index++;
853
+ continue;
854
+ }
855
+ if (escaped === "\r") {
856
+ index += source[index + 1] === "\n" ? 2 : 1;
857
+ continue;
858
+ }
859
+ if (escaped === "x" && /^[0-9a-fA-F]{2}$/.test(source.slice(index + 1, index + 3))) {
860
+ value += String.fromCharCode(Number.parseInt(source.slice(index + 1, index + 3), 16));
861
+ index += 3;
862
+ continue;
863
+ }
864
+ if (escaped === "u") {
865
+ const braced = source.slice(index + 1).match(/^\{([0-9a-fA-F]+)\}/);
866
+ if (braced) {
867
+ const codePoint = Number.parseInt(braced[1], 16);
868
+ if (Number.isFinite(codePoint) && codePoint <= 1114111) {
869
+ value += String.fromCodePoint(codePoint);
870
+ index += braced[0].length + 1;
871
+ continue;
872
+ }
873
+ }
874
+ const hex = source.slice(index + 1, index + 5);
875
+ if (/^[0-9a-fA-F]{4}$/.test(hex)) {
876
+ value += String.fromCharCode(Number.parseInt(hex, 16));
877
+ index += 5;
878
+ continue;
879
+ }
880
+ }
881
+ value += escaped;
882
+ index++;
883
+ }
884
+ return null;
885
+ }
886
+ function isApplyPatchText(value) {
887
+ return value.includes("*** Begin Patch") && value.includes("*** End Patch");
888
+ }
889
+ function extractApplyPatchInputs(execSource) {
890
+ if (!execSource.includes("apply_patch") || !execSource.includes("*** Begin Patch")) return [];
891
+ const assignedPatches = /* @__PURE__ */ new Map();
892
+ for (let index = 0; index < execSource.length; index++) {
893
+ const literal = readJsStringLiteral(execSource, index);
894
+ if (!literal) continue;
895
+ if (isApplyPatchText(literal.value)) {
896
+ const assignment = execSource.slice(0, index).match(/(?:^|[;\n])\s*(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*=\s*$/);
897
+ if (assignment) assignedPatches.set(assignment[1], literal.value);
898
+ }
899
+ index = literal.endIndex - 1;
900
+ }
901
+ const patches = [];
902
+ const seen = /* @__PURE__ */ new Set();
903
+ const applyCall = /\btools\s*\.\s*apply_patch\s*\(/g;
904
+ let match;
905
+ while ((match = applyCall.exec(execSource)) !== null) {
906
+ let argumentIndex = match.index + match[0].length;
907
+ while (/\s/.test(execSource[argumentIndex] ?? "")) argumentIndex++;
908
+ const literal = readJsStringLiteral(execSource, argumentIndex);
909
+ const identifier = literal ? null : execSource.slice(argumentIndex).match(/^([A-Za-z_$][\w$]*)/);
910
+ const patch = literal?.value ?? (identifier ? assignedPatches.get(identifier[1]) : void 0);
911
+ if (!patch || !isApplyPatchText(patch) || seen.has(patch)) continue;
912
+ seen.add(patch);
913
+ patches.push(patch);
914
+ }
915
+ return patches;
916
+ }
917
+ function resolveCodexPatchPath(filePath, cwd) {
918
+ if (!cwd || filePath.startsWith("/") || /^[A-Za-z]:[\\/]/.test(filePath)) return filePath;
919
+ return `${cwd.replace(/[\\/]+$/, "")}/${filePath.replace(/^\.\//, "")}`;
920
+ }
921
+ function parseApplyPatch(patchText, callId, timestamp, cwd = "") {
922
+ const toolCalls = [];
923
+ const filePattern = /^\*\*\*\s+(Update File|Add File|Delete File):\s*(.+)$/gm;
924
+ const sections = [];
925
+ let match;
926
+ while ((match = filePattern.exec(patchText)) !== null) {
927
+ sections.push({
928
+ action: match[1],
929
+ filePath: match[2].trim(),
930
+ headerIdx: match.index,
931
+ startIdx: match.index + match[0].length
932
+ });
933
+ }
934
+ for (let i = 0; i < sections.length; i++) {
935
+ const section = sections[i];
936
+ const endIdx = i + 1 < sections.length ? sections[i + 1].headerIdx : patchText.length;
937
+ const body = patchText.slice(section.startIdx, endIdx);
938
+ const movedTo = body.match(/^\*\*\*\s+Move to:\s*(.+)$/m)?.[1]?.trim();
939
+ const sourcePath = resolveCodexPatchPath(section.filePath, cwd);
940
+ const filePath = resolveCodexPatchPath(movedTo || section.filePath, cwd);
941
+ const hunkBodies = body.split(/^@@.*$/m).filter((s) => s.trim());
942
+ const oldLines = [];
943
+ const newLines = [];
944
+ for (const hunk of hunkBodies) {
945
+ for (const line of hunk.split("\n")) {
946
+ if (line.startsWith("-")) {
947
+ oldLines.push(line.slice(1));
948
+ } else if (line.startsWith("+")) {
949
+ newLines.push(line.slice(1));
950
+ } else if (line.startsWith(" ")) {
951
+ oldLines.push(line.slice(1));
952
+ newLines.push(line.slice(1));
953
+ }
954
+ }
955
+ }
956
+ const fileId = sections.length > 1 ? `${callId}:file-${i}` : callId;
957
+ if (section.action === "Add File") {
958
+ toolCalls.push({
959
+ id: fileId,
960
+ name: "Write",
961
+ input: { file_path: filePath, content: newLines.join("\n") },
962
+ result: null,
963
+ isError: false,
964
+ timestamp
965
+ });
966
+ } else if (section.action === "Delete File") {
967
+ toolCalls.push({
968
+ id: fileId,
969
+ name: "Edit",
970
+ input: { file_path: filePath, old_string: oldLines.join("\n"), new_string: "" },
971
+ result: null,
972
+ isError: false,
973
+ timestamp
974
+ });
975
+ } else {
976
+ toolCalls.push({
977
+ id: fileId,
978
+ name: "Edit",
979
+ input: {
980
+ file_path: filePath,
981
+ ...movedTo ? { old_path: sourcePath } : {},
982
+ old_string: oldLines.join("\n"),
983
+ new_string: newLines.join("\n")
984
+ },
985
+ result: null,
986
+ isError: false,
987
+ timestamp
988
+ });
989
+ }
990
+ }
991
+ return toolCalls;
992
+ }
993
+ function parseCodexToolPatches(toolName, rawInput, callId, timestamp, cwd = "") {
994
+ if (toolName === "apply_patch") {
995
+ return parseApplyPatch(rawInput, callId, timestamp, cwd);
996
+ }
997
+ return extractApplyPatchInputs(rawInput).flatMap(
998
+ (patch, patchIndex) => parseApplyPatch(patch, `${callId}:patch-${patchIndex}`, timestamp, cwd)
999
+ );
1000
+ }
1001
+ function findFailedNestedPatchCallIds(output, wrapperIsError, calls) {
1002
+ if (!wrapperIsError || !/(?:apply[_ ]patch|invalid patch|invalid context|failed to (?:apply|find)|could not apply)/i.test(output)) {
1003
+ return /* @__PURE__ */ new Set();
1004
+ }
1005
+ const groups = /* @__PURE__ */ new Map();
1006
+ for (const call of calls) {
1007
+ const patchIndex = call.id.match(/:patch-(\d+)(?::file-\d+)?$/)?.[1];
1008
+ if (patchIndex === void 0) continue;
1009
+ const index = Number(patchIndex);
1010
+ const group = groups.get(index) ?? [];
1011
+ group.push(call);
1012
+ groups.set(index, group);
1013
+ }
1014
+ if (groups.size === 0) return new Set(calls.map((call) => call.id));
1015
+ const normalizedOutput = output.toLowerCase().replaceAll("\\", "/");
1016
+ const mentionedGroups = /* @__PURE__ */ new Set();
1017
+ for (const [index, group] of groups) {
1018
+ if (group.some((call) => {
1019
+ const filePath = String(call.input.file_path ?? "").toLowerCase().replaceAll("\\", "/");
1020
+ if (!filePath) return false;
1021
+ const segments = filePath.split("/").filter(Boolean);
1022
+ const suffix = segments.slice(-2).join("/");
1023
+ return normalizedOutput.includes(filePath) || suffix.includes("/") && normalizedOutput.includes(suffix);
1024
+ })) {
1025
+ mentionedGroups.add(index);
1026
+ }
1027
+ }
1028
+ for (const match of normalizedOutput.matchAll(/\bpatch\s*(?:#|-)?\s*(\d+)\b/g)) {
1029
+ const index = Number(match[1]);
1030
+ if (groups.has(index)) mentionedGroups.add(index);
1031
+ }
1032
+ if (mentionedGroups.size === 0) mentionedGroups.add(Math.max(...groups.keys()));
1033
+ const firstFailedIndex = Math.min(...mentionedGroups);
1034
+ for (const index of groups.keys()) {
1035
+ if (index >= firstFailedIndex) mentionedGroups.add(index);
1036
+ }
1037
+ return new Set(
1038
+ [...mentionedGroups].flatMap((index) => groups.get(index)?.map((call) => call.id) ?? [])
1039
+ );
1040
+ }
1041
+
1042
+ // src/lib/codex-tool-normalization.ts
1043
+ function isObject(value) {
1044
+ return typeof value === "object" && value !== null;
1045
+ }
1046
+ function inferToolError(output) {
1047
+ if (!output) return false;
1048
+ const exitMatch = output.match(/Process exited with code (\d+)/);
1049
+ if (exitMatch) return exitMatch[1] !== "0";
1050
+ const withoutSuccessSummaries = output.replace(/\b0\s+(?:fail(?:ed|ures?)?|errors?)\b/gi, "").replace(/\bno\s+(?:failures?|errors?)\b/gi, "");
1051
+ return /\b(error|failed|exception)\b/i.test(withoutSuccessSummaries);
1052
+ }
1053
+ function normalizePlanToTodos(input) {
1054
+ const plan = Array.isArray(input.plan) ? input.plan : [];
1055
+ const todos = plan.filter((item) => isObject(item)).map((item) => ({
1056
+ content: typeof item.step === "string" ? item.step : "",
1057
+ status: typeof item.status === "string" ? item.status : "pending",
1058
+ activeForm: typeof item.step === "string" ? item.step : ""
1059
+ }));
1060
+ return { todos };
1061
+ }
1062
+ function extractCustomToolOutputText(output) {
1063
+ if (typeof output === "string") return output;
1064
+ if (!Array.isArray(output)) return "";
1065
+ return output.filter((block) => isObject(block) && typeof block.text === "string").map((block) => block.text).join("");
1066
+ }
1067
+ function parseCustomToolOutput(output) {
1068
+ const rawOutput = extractCustomToolOutputText(output);
1069
+ if (!rawOutput) return { text: "", isError: false };
1070
+ try {
1071
+ const parsed = JSON.parse(rawOutput);
1072
+ const text = typeof parsed.output === "string" ? parsed.output : rawOutput;
1073
+ const meta = isObject(parsed.metadata) ? parsed.metadata : null;
1074
+ const isError = meta ? meta.exit_code !== 0 && meta.exit_code !== void 0 : inferToolError(text);
1075
+ return { text, isError };
1076
+ } catch {
1077
+ return { text: rawOutput, isError: inferToolError(rawOutput) };
1078
+ }
1079
+ }
1080
+
616
1081
  // src/lib/codex.ts
617
1082
  var SKIP_PROMPT_PREFIXES = [
1083
+ "# AGENTS.md instructions for ",
1084
+ "<recommended_plugins>",
618
1085
  "<environment_context>",
619
1086
  "<permissions instructions>",
620
1087
  "<collaboration_mode>",
@@ -630,17 +1097,120 @@ function safeParseLine(line) {
630
1097
  return null;
631
1098
  }
632
1099
  }
633
- function isObject(value) {
1100
+ function isObject2(value) {
634
1101
  return typeof value === "object" && value !== null;
635
1102
  }
636
1103
  function isCodexRecord(record) {
637
1104
  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";
1105
+ 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
1106
  }
640
1107
  function extractMessageText(payload, blockType) {
641
1108
  const content = payload?.content;
642
1109
  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();
1110
+ return content.filter((block) => isObject2(block) && block.type === blockType && typeof block.text === "string").map((block) => block.text).join("\n").trim();
1111
+ }
1112
+ var CODEX_IMAGE_DATA_URL = /^data:(image\/(?:png|jpeg|gif|webp));base64,(.+)$/is;
1113
+ var CODEX_AUDIO_DATA_URL = /^data:(audio\/(?:wav|x-wav|mpeg|mp4|webm|ogg));base64,(.+)$/is;
1114
+ function parseImageDataUrl(value) {
1115
+ if (typeof value !== "string") return null;
1116
+ const match = CODEX_IMAGE_DATA_URL.exec(value);
1117
+ if (!match?.[1] || !match[2]) return null;
1118
+ return {
1119
+ type: "image",
1120
+ source: {
1121
+ type: "base64",
1122
+ media_type: match[1].toLowerCase(),
1123
+ data: match[2]
1124
+ }
1125
+ };
1126
+ }
1127
+ function parseAudioDataUrl(value) {
1128
+ if (typeof value !== "string") return null;
1129
+ const match = CODEX_AUDIO_DATA_URL.exec(value);
1130
+ if (!match?.[1] || !match[2]) return null;
1131
+ return {
1132
+ type: "audio",
1133
+ source: {
1134
+ type: "base64",
1135
+ media_type: match[1].toLowerCase(),
1136
+ data: match[2]
1137
+ }
1138
+ };
1139
+ }
1140
+ function extractEventMessageImages(payload) {
1141
+ if (!Array.isArray(payload.images)) return [];
1142
+ return payload.images.map(parseImageDataUrl).filter((image) => image !== null);
1143
+ }
1144
+ function extractEventMessageAudio(payload) {
1145
+ if (!Array.isArray(payload.audio)) return [];
1146
+ return payload.audio.map(parseAudioDataUrl).filter((audio) => audio !== null);
1147
+ }
1148
+ function extractResponseMessageImages(payload) {
1149
+ if (!Array.isArray(payload.content)) return [];
1150
+ return payload.content.filter((block) => isObject2(block) && block.type === "input_image").map((block) => parseImageDataUrl(block.image_url)).filter((image) => image !== null);
1151
+ }
1152
+ function extractResponseMessageAudio(payload) {
1153
+ if (!Array.isArray(payload.content)) return [];
1154
+ return payload.content.filter((block) => isObject2(block) && block.type === "input_audio").map((block) => parseAudioDataUrl(block.audio_url)).filter((audio) => audio !== null);
1155
+ }
1156
+ function buildCodexUserContent(message, images, audio = [], localImages = [], localAudio = []) {
1157
+ const attachmentSuffix = [
1158
+ ...localImages.map((path) => `
1159
+ ![image](<${path}>)`),
1160
+ ...localAudio.map((path) => `
1161
+ [audio attachment](<${path}>)`)
1162
+ ].join("");
1163
+ const text = message + attachmentSuffix;
1164
+ if (images.length === 0 && audio.length === 0) return text;
1165
+ return [
1166
+ ...images,
1167
+ ...audio,
1168
+ ...text ? [{ type: "text", text }] : []
1169
+ ];
1170
+ }
1171
+ function normalizeFunctionName(rawName) {
1172
+ const leaf = rawName.split(/(?:__|[.:/])+/).filter(Boolean).at(-1) ?? rawName;
1173
+ const aliases = {
1174
+ spawnAgent: "spawn_agent",
1175
+ waitAgent: "wait_agent",
1176
+ sendMessage: "send_message",
1177
+ followupTask: "followup_task",
1178
+ listAgents: "list_agents",
1179
+ interruptAgent: "interrupt_agent"
1180
+ };
1181
+ const canonicalCollaborationNames = /* @__PURE__ */ new Set([
1182
+ "spawn_agent",
1183
+ "wait_agent",
1184
+ "send_message",
1185
+ "followup_task",
1186
+ "list_agents",
1187
+ "interrupt_agent"
1188
+ ]);
1189
+ if (aliases[leaf]) return aliases[leaf];
1190
+ if (canonicalCollaborationNames.has(leaf)) return leaf;
1191
+ return rawName;
1192
+ }
1193
+ function extractInterAgentMessage(payload) {
1194
+ const rawText = extractMessageText(payload, "input_text");
1195
+ if (!rawText) return { messageType: null, text: "" };
1196
+ const headerEnd = rawText.match(/\r?\nPayload:\s*(?:\r?\n|$)/);
1197
+ if (!rawText.startsWith("Message Type:") || !headerEnd || headerEnd.index === void 0) {
1198
+ return { messageType: null, text: rawText.trim() };
1199
+ }
1200
+ const header = rawText.slice(0, headerEnd.index);
1201
+ const messageType = header.match(/^Message Type:\s*([^\r\n]+)/)?.[1]?.trim().toUpperCase() ?? null;
1202
+ const text = rawText.slice(headerEnd.index + headerEnd[0].length).trim();
1203
+ return { messageType, text };
1204
+ }
1205
+ function agentNameFromPath(agentPath) {
1206
+ const segments = agentPath.split("/").filter(Boolean);
1207
+ return segments.at(-1) ?? null;
1208
+ }
1209
+ function readableAgentPrompt(value) {
1210
+ if (typeof value !== "string") return "";
1211
+ const prompt = value.trim();
1212
+ if (/^gAAAAA[A-Za-z0-9_-]+$/.test(prompt)) return "";
1213
+ return prompt;
644
1214
  }
645
1215
  function normalizePromptText(text) {
646
1216
  const trimmed = text.trim();
@@ -658,11 +1228,13 @@ function mergeTokenUsage2(existing, incoming) {
658
1228
  };
659
1229
  }
660
1230
  function parseTokenUsage(value) {
661
- if (!isObject(value)) return null;
662
- const inputTokens = typeof value.input_tokens === "number" ? value.input_tokens : 0;
1231
+ if (!isObject2(value)) return null;
1232
+ const reportedInputTokens = typeof value.input_tokens === "number" ? value.input_tokens : 0;
663
1233
  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;
1234
+ 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;
1235
+ const nativeCachedInput = typeof value.cached_input_tokens === "number" ? Math.max(0, value.cached_input_tokens) : null;
1236
+ const cacheRead = nativeCachedInput ?? (typeof value.cache_read_input_tokens === "number" ? value.cache_read_input_tokens : 0);
1237
+ const inputTokens = nativeCachedInput === null ? reportedInputTokens : Math.max(0, reportedInputTokens - nativeCachedInput);
666
1238
  if (inputTokens === 0 && outputTokens === 0 && cacheCreation === 0 && cacheRead === 0) return null;
667
1239
  return {
668
1240
  input_tokens: inputTokens,
@@ -703,7 +1275,7 @@ function appendToolCall(turn, toolCall, timestamp) {
703
1275
  }
704
1276
  function finalizeTurn(turns, current, lastTimestamp) {
705
1277
  if (!current) return null;
706
- const hasContent = current.userMessage !== null || current.assistantText.length > 0 || current.toolCalls.length > 0 || current.thinking.length > 0;
1278
+ const hasContent = current.userMessage !== null || current.assistantText.length > 0 || current.toolCalls.length > 0 || current.thinking.length > 0 || current.subAgentActivity.length > 0;
707
1279
  if (!hasContent) return null;
708
1280
  if (current.timestamp && lastTimestamp) {
709
1281
  const start = new Date(current.timestamp).getTime();
@@ -719,17 +1291,11 @@ function parseToolInput(argumentsText) {
719
1291
  if (typeof argumentsText !== "string" || !argumentsText.trim()) return {};
720
1292
  try {
721
1293
  const parsed = JSON.parse(argumentsText);
722
- return isObject(parsed) ? parsed : { value: parsed };
1294
+ return isObject2(parsed) ? parsed : { value: parsed };
723
1295
  } catch {
724
1296
  return { raw: argumentsText };
725
1297
  }
726
1298
  }
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);
732
- }
733
1299
  function createTurn(turnId, timestamp, model) {
734
1300
  return {
735
1301
  id: turnId || randomTurnId("codex-turn"),
@@ -746,10 +1312,10 @@ function createTurn(turnId, timestamp, model) {
746
1312
  };
747
1313
  }
748
1314
  function extractPromptFromRecord(record) {
749
- if (record.type === "event_msg" && isObject(record.payload) && record.payload.type === "user_message" && typeof record.payload.message === "string") {
1315
+ if (record.type === "event_msg" && isObject2(record.payload) && record.payload.type === "user_message" && typeof record.payload.message === "string") {
750
1316
  return normalizePromptText(record.payload.message);
751
1317
  }
752
- if (record.type === "response_item" && isObject(record.payload) && record.payload.type === "message" && record.payload.role === "user") {
1318
+ if (record.type === "response_item" && isObject2(record.payload) && record.payload.type === "message" && record.payload.role === "user") {
753
1319
  return normalizePromptText(extractMessageText(record.payload, "input_text"));
754
1320
  }
755
1321
  return "";
@@ -766,14 +1332,17 @@ function extractMetadataFromRecords(records) {
766
1332
  let timestamp = "";
767
1333
  let lastTimestamp = "";
768
1334
  let turnCount = 0;
1335
+ let isSubagent = false;
1336
+ let parentSessionId = null;
1337
+ let agentPath = "/root";
769
1338
  let previousPrompt = "";
770
1339
  for (const record of records) {
771
- if (!isObject(record.payload)) continue;
1340
+ if (!isObject2(record.payload)) continue;
772
1341
  if (record.type === "session_meta") {
773
1342
  sessionId ||= typeof record.payload.id === "string" ? record.payload.id : "";
774
1343
  version ||= typeof record.payload.cli_version === "string" ? record.payload.cli_version : "";
775
1344
  cwd ||= typeof record.payload.cwd === "string" ? record.payload.cwd : "";
776
- if (!branchedFrom && isObject(record.payload.branchedFrom)) {
1345
+ if (!branchedFrom && isObject2(record.payload.branchedFrom)) {
777
1346
  const sourceId = typeof record.payload.branchedFrom.sessionId === "string" ? record.payload.branchedFrom.sessionId : "";
778
1347
  if (sourceId) {
779
1348
  branchedFrom = {
@@ -782,7 +1351,18 @@ function extractMetadataFromRecords(records) {
782
1351
  };
783
1352
  }
784
1353
  }
785
- const git = isObject(record.payload.git) ? record.payload.git : null;
1354
+ const source = isObject2(record.payload.source) ? record.payload.source : null;
1355
+ if (source && isObject2(source.subagent)) {
1356
+ isSubagent = true;
1357
+ const threadSpawn = isObject2(source.subagent.thread_spawn) ? source.subagent.thread_spawn : null;
1358
+ if (threadSpawn && typeof threadSpawn.agent_path === "string") {
1359
+ agentPath = threadSpawn.agent_path;
1360
+ }
1361
+ }
1362
+ if (typeof record.payload.forked_from_id === "string" && record.payload.forked_from_id) {
1363
+ parentSessionId = record.payload.forked_from_id;
1364
+ }
1365
+ const git = isObject2(record.payload.git) ? record.payload.git : null;
786
1366
  gitBranch ||= git && typeof git.branch === "string" ? git.branch : "";
787
1367
  }
788
1368
  if (record.type === "turn_context") {
@@ -807,12 +1387,16 @@ function extractMetadataFromRecords(records) {
807
1387
  cwd,
808
1388
  model,
809
1389
  slug: "",
1390
+ name: "",
810
1391
  branchedFrom,
811
1392
  firstUserMessage,
812
1393
  lastUserMessage,
813
1394
  timestamp,
814
1395
  lastTimestamp,
815
- turnCount
1396
+ turnCount,
1397
+ isSubagent,
1398
+ parentSessionId,
1399
+ agentPath
816
1400
  };
817
1401
  }
818
1402
  function isCodexSessionText(jsonlText) {
@@ -827,18 +1411,99 @@ function extractCodexMetadataFromLines(lines) {
827
1411
  const records = lines.map(safeParseLine).filter(isCodexRecord);
828
1412
  return extractMetadataFromRecords(records);
829
1413
  }
830
- function parseCodexSession(jsonlText) {
1414
+ function parseCodexSession(jsonlText, options) {
831
1415
  const records = jsonlText.split("\n").map((line) => line.trim()).filter(Boolean).map(safeParseLine).filter(isCodexRecord);
832
1416
  const metadata = extractMetadataFromRecords(records);
833
1417
  const turns = [];
834
1418
  const pendingToolCalls = /* @__PURE__ */ new Map();
1419
+ const webSearchCalls = /* @__PURE__ */ new Map();
1420
+ const patchCallIds = /* @__PURE__ */ new Map();
1421
+ const spawnAgentCalls = /* @__PURE__ */ new Map();
1422
+ const agentRegistry = /* @__PURE__ */ new Map();
1423
+ const agentIdByPath = /* @__PURE__ */ new Map();
1424
+ const agentIdBySpawnCall = /* @__PURE__ */ new Map();
1425
+ const agentMessages = /* @__PURE__ */ new Map();
1426
+ function upsertAgentMessage(agentId, targetTurn, eventTimestamp, update = {}) {
1427
+ const info = agentRegistry.get(agentId);
1428
+ let entry = agentMessages.get(agentId);
1429
+ if (!entry) {
1430
+ const agentMessage2 = {
1431
+ agentId,
1432
+ parentToolUseId: info?.parentToolCallId || void 0,
1433
+ agentName: info?.nickname ?? (info?.agentPath ? agentNameFromPath(info.agentPath) : null),
1434
+ subagentType: info?.agentType ?? null,
1435
+ type: "assistant",
1436
+ content: null,
1437
+ toolCalls: [],
1438
+ thinking: [],
1439
+ text: [],
1440
+ timestamp: info?.timestamp ?? eventTimestamp,
1441
+ tokenUsage: null,
1442
+ model: info?.model ?? null,
1443
+ isBackground: false,
1444
+ prompt: info?.message || void 0,
1445
+ status: update.status ?? "running"
1446
+ };
1447
+ targetTurn.subAgentActivity.push(agentMessage2);
1448
+ const lastBlock = targetTurn.contentBlocks[targetTurn.contentBlocks.length - 1];
1449
+ if (lastBlock?.kind === "sub_agent") {
1450
+ lastBlock.messages.push(agentMessage2);
1451
+ } else {
1452
+ targetTurn.contentBlocks.push({ kind: "sub_agent", messages: [agentMessage2], timestamp: eventTimestamp });
1453
+ }
1454
+ entry = { message: agentMessage2, turn: targetTurn };
1455
+ agentMessages.set(agentId, entry);
1456
+ }
1457
+ const agentMessage = entry.message;
1458
+ if (info) {
1459
+ agentMessage.parentToolUseId = info.parentToolCallId || agentMessage.parentToolUseId;
1460
+ agentMessage.agentName = info.nickname ?? (info.agentPath ? agentNameFromPath(info.agentPath) : agentMessage.agentName);
1461
+ agentMessage.subagentType = info.agentType;
1462
+ agentMessage.model = info.model;
1463
+ agentMessage.prompt = info.message || agentMessage.prompt;
1464
+ }
1465
+ if (update.text && !agentMessage.text.includes(update.text)) {
1466
+ agentMessage.text.push(update.text);
1467
+ agentMessage.content = agentMessage.text.join("\n\n");
1468
+ }
1469
+ if (update.status) {
1470
+ const terminalStatuses = /* @__PURE__ */ new Set(["completed", "failed", "interrupted"]);
1471
+ if (!(update.status === "running" && agentMessage.status && terminalStatuses.has(agentMessage.status))) {
1472
+ agentMessage.status = update.status;
1473
+ }
1474
+ if (terminalStatuses.has(update.status) && agentMessage.timestamp && eventTimestamp) {
1475
+ const startedAt = new Date(agentMessage.timestamp).getTime();
1476
+ const endedAt = new Date(eventTimestamp).getTime();
1477
+ if (Number.isFinite(startedAt) && Number.isFinite(endedAt) && endedAt >= startedAt) {
1478
+ agentMessage.durationMs = endedAt - startedAt;
1479
+ }
1480
+ }
1481
+ }
1482
+ return agentMessage;
1483
+ }
835
1484
  let current = null;
836
1485
  let currentTurnId = null;
837
1486
  let currentModel = metadata.model || null;
838
1487
  let lastTurnTimestamp = "";
1488
+ let pendingCompaction = null;
1489
+ function createActiveTurn(turnId, timestamp, model) {
1490
+ const turn = createTurn(turnId, timestamp, model);
1491
+ if (pendingCompaction) {
1492
+ turn.compactionSummary = pendingCompaction;
1493
+ pendingCompaction = null;
1494
+ }
1495
+ return turn;
1496
+ }
839
1497
  for (const record of records) {
840
- const payload = isObject(record.payload) ? record.payload : void 0;
1498
+ const payload = isObject2(record.payload) ? record.payload : void 0;
841
1499
  const timestamp = record.timestamp ?? "";
1500
+ if (record.type === "compacted") {
1501
+ current = finalizeTurn(turns, current, lastTurnTimestamp);
1502
+ pendingCompaction = "Conversation compacted";
1503
+ lastTurnTimestamp = timestamp;
1504
+ continue;
1505
+ }
1506
+ if (record.type === "world_state") continue;
842
1507
  if (record.type === "turn_context") {
843
1508
  current = finalizeTurn(turns, current, lastTurnTimestamp);
844
1509
  currentTurnId = typeof payload?.turn_id === "string" ? payload.turn_id : null;
@@ -850,24 +1515,93 @@ function parseCodexSession(jsonlText) {
850
1515
  if (current && (current.assistantText.length > 0 || current.toolCalls.length > 0 || current.thinking.length > 0)) {
851
1516
  current = finalizeTurn(turns, current, lastTurnTimestamp);
852
1517
  }
853
- current ??= createTurn(currentTurnId, timestamp, currentModel);
854
- current.userMessage = payload.message;
1518
+ current ??= createActiveTurn(currentTurnId, timestamp, currentModel);
1519
+ const localImages = (Array.isArray(payload.local_images) ? payload.local_images : []).filter(
1520
+ (p) => typeof p === "string" && p.length > 0
1521
+ );
1522
+ const localAudio = (Array.isArray(payload.local_audio) ? payload.local_audio : []).filter(
1523
+ (p) => typeof p === "string" && p.length > 0
1524
+ );
1525
+ current.userMessage = buildCodexUserContent(
1526
+ payload.message,
1527
+ extractEventMessageImages(payload),
1528
+ extractEventMessageAudio(payload),
1529
+ localImages,
1530
+ localAudio
1531
+ );
855
1532
  current.timestamp = current.timestamp || timestamp;
856
1533
  lastTurnTimestamp = timestamp;
857
1534
  continue;
858
1535
  }
859
- current ??= createTurn(currentTurnId, timestamp, currentModel);
1536
+ current ??= createActiveTurn(currentTurnId, timestamp, currentModel);
860
1537
  if (!current.model && currentModel) current.model = currentModel;
861
1538
  if (!current.timestamp) current.timestamp = timestamp;
862
1539
  if (timestamp) lastTurnTimestamp = timestamp;
863
1540
  if (record.type === "event_msg" && payload?.type === "token_count") {
864
- const info = isObject(payload.info) ? payload.info : null;
1541
+ const info = isObject2(payload.info) ? payload.info : null;
865
1542
  const lastUsage = info ? parseTokenUsage(info.last_token_usage) : null;
866
1543
  if (lastUsage) {
867
1544
  current.tokenUsage = mergeTokenUsage2(current.tokenUsage, lastUsage);
868
1545
  }
869
1546
  continue;
870
1547
  }
1548
+ if (record.type === "event_msg" && payload?.type === "web_search_end" && typeof payload.call_id === "string") {
1549
+ const input = {};
1550
+ if (typeof payload.query === "string" && payload.query) input.query = payload.query;
1551
+ if (isObject2(payload.action)) input.action = payload.action;
1552
+ const existing = webSearchCalls.get(payload.call_id);
1553
+ if (existing) {
1554
+ existing.input = { ...existing.input, ...input };
1555
+ existing.result = "completed";
1556
+ existing.isError = false;
1557
+ } else {
1558
+ const toolCall = {
1559
+ id: payload.call_id,
1560
+ name: "WebSearch",
1561
+ input,
1562
+ result: "completed",
1563
+ isError: false,
1564
+ timestamp
1565
+ };
1566
+ webSearchCalls.set(toolCall.id, toolCall);
1567
+ appendToolCall(current, toolCall, timestamp);
1568
+ }
1569
+ continue;
1570
+ }
1571
+ if (record.type === "event_msg" && payload?.type === "sub_agent_activity" && typeof payload.agent_thread_id === "string" && typeof payload.agent_path === "string") {
1572
+ const agentId = payload.agent_thread_id;
1573
+ const eventId = typeof payload.event_id === "string" ? payload.event_id : "";
1574
+ const kind = typeof payload.kind === "string" ? payload.kind : "started";
1575
+ const spawnInfo = eventId ? spawnAgentCalls.get(eventId) : void 0;
1576
+ const existingId = agentIdByPath.get(payload.agent_path);
1577
+ const existing = agentRegistry.get(agentId) ?? (existingId ? agentRegistry.get(existingId) : void 0);
1578
+ if (kind !== "started" && !existing && !spawnInfo) continue;
1579
+ if (existingId && existingId !== agentId && !agentMessages.has(agentId)) {
1580
+ const provisionalEntry = agentMessages.get(existingId);
1581
+ if (provisionalEntry) {
1582
+ provisionalEntry.message.agentId = agentId;
1583
+ agentMessages.delete(existingId);
1584
+ agentMessages.set(agentId, provisionalEntry);
1585
+ }
1586
+ agentRegistry.delete(existingId);
1587
+ }
1588
+ const info = {
1589
+ agentId,
1590
+ nickname: existing?.nickname ?? agentNameFromPath(payload.agent_path) ?? spawnInfo?.taskName ?? null,
1591
+ agentPath: payload.agent_path,
1592
+ message: existing?.message ?? spawnInfo?.message ?? "",
1593
+ model: existing?.model ?? spawnInfo?.model ?? null,
1594
+ agentType: existing?.agentType ?? spawnInfo?.agentType ?? null,
1595
+ timestamp: existing?.timestamp ?? (timestamp || spawnInfo?.timestamp || ""),
1596
+ parentToolCallId: existing?.parentToolCallId ?? eventId
1597
+ };
1598
+ agentRegistry.set(agentId, info);
1599
+ agentIdByPath.set(payload.agent_path, agentId);
1600
+ if (eventId && (kind === "started" || spawnInfo)) agentIdBySpawnCall.set(eventId, agentId);
1601
+ const status = kind === "interrupted" ? "interrupted" : "running";
1602
+ upsertAgentMessage(agentId, current, timestamp, { status });
1603
+ continue;
1604
+ }
871
1605
  if (record.type !== "response_item" || !payload) continue;
872
1606
  if (payload.type === "reasoning") {
873
1607
  const summary = Array.isArray(payload.summary) ? payload.summary.filter((item) => typeof item === "string").join("\n") : "";
@@ -880,14 +1614,95 @@ function parseCodexSession(jsonlText) {
880
1614
  }
881
1615
  if (payload.type === "message" && payload.role === "user" && current.userMessage === null) {
882
1616
  const text = normalizePromptText(extractMessageText(payload, "input_text"));
883
- if (text) current.userMessage = text;
1617
+ const images = extractResponseMessageImages(payload);
1618
+ const audio = extractResponseMessageAudio(payload);
1619
+ if (text || images.length > 0 || audio.length > 0) {
1620
+ current.userMessage = buildCodexUserContent(text, images, audio);
1621
+ }
1622
+ continue;
1623
+ }
1624
+ if (payload.type === "web_search_call" && typeof payload.id === "string") {
1625
+ const input = {};
1626
+ if (isObject2(payload.action)) input.action = payload.action;
1627
+ const status = typeof payload.status === "string" ? payload.status : "completed";
1628
+ const existing = webSearchCalls.get(payload.id);
1629
+ if (existing) {
1630
+ existing.input = { ...existing.input, ...input };
1631
+ existing.result = status;
1632
+ existing.isError = status === "failed";
1633
+ } else {
1634
+ const toolCall = {
1635
+ id: payload.id,
1636
+ name: "WebSearch",
1637
+ input,
1638
+ result: status,
1639
+ isError: status === "failed",
1640
+ timestamp
1641
+ };
1642
+ webSearchCalls.set(toolCall.id, toolCall);
1643
+ appendToolCall(current, toolCall, timestamp);
1644
+ }
1645
+ continue;
1646
+ }
1647
+ if (payload.type === "agent_message" && typeof payload.author === "string") {
1648
+ const author = payload.author;
1649
+ const recipient = typeof payload.recipient === "string" ? payload.recipient : null;
1650
+ const interAgentMessage = extractInterAgentMessage(payload);
1651
+ if (interAgentMessage.messageType === "NEW_TASK") continue;
1652
+ const knownAgentId = agentIdByPath.get(author);
1653
+ const isPotentialChild = author.startsWith(`${metadata.agentPath}/`) && (recipient === null || recipient === metadata.agentPath);
1654
+ if (!knownAgentId && !isPotentialChild) continue;
1655
+ const agentId = knownAgentId ?? author;
1656
+ if (!knownAgentId) {
1657
+ agentRegistry.set(agentId, {
1658
+ agentId,
1659
+ nickname: agentNameFromPath(author),
1660
+ agentPath: author,
1661
+ message: "",
1662
+ model: null,
1663
+ agentType: null,
1664
+ timestamp,
1665
+ parentToolCallId: ""
1666
+ });
1667
+ agentIdByPath.set(author, agentId);
1668
+ }
1669
+ let status;
1670
+ if (interAgentMessage.messageType === "FINAL_ANSWER") status = "completed";
1671
+ else if (interAgentMessage.messageType === "ERROR" || interAgentMessage.messageType === "FAILED") status = "failed";
1672
+ else if (interAgentMessage.messageType === "INTERRUPTED") status = "interrupted";
1673
+ else if (interAgentMessage.messageType === "MESSAGE") status = "running";
1674
+ upsertAgentMessage(agentId, current, timestamp, {
1675
+ status,
1676
+ text: interAgentMessage.text || void 0
1677
+ });
884
1678
  continue;
885
1679
  }
886
1680
  if (payload.type === "function_call" && typeof payload.call_id === "string") {
1681
+ const rawName = typeof payload.name === "string" ? payload.name : "tool";
1682
+ const functionName = normalizeFunctionName(rawName);
1683
+ const parsedInput = parseToolInput(payload.arguments);
1684
+ let name = functionName;
1685
+ let input = parsedInput;
1686
+ if (functionName === "exec_command") {
1687
+ name = "Bash";
1688
+ } else if (functionName === "update_plan") {
1689
+ name = "TodoWrite";
1690
+ input = normalizePlanToTodos(parsedInput);
1691
+ }
1692
+ if (functionName === "spawn_agent") {
1693
+ spawnAgentCalls.set(payload.call_id, {
1694
+ callId: payload.call_id,
1695
+ message: readableAgentPrompt(parsedInput.message),
1696
+ taskName: typeof parsedInput.task_name === "string" ? parsedInput.task_name : null,
1697
+ model: typeof parsedInput.model === "string" ? parsedInput.model : null,
1698
+ agentType: typeof parsedInput.agent_type === "string" ? parsedInput.agent_type : null,
1699
+ timestamp
1700
+ });
1701
+ }
887
1702
  const toolCall = {
888
1703
  id: payload.call_id,
889
- name: typeof payload.name === "string" ? payload.name : "tool",
890
- input: parseToolInput(payload.arguments),
1704
+ name,
1705
+ input,
891
1706
  result: null,
892
1707
  isError: false,
893
1708
  timestamp
@@ -903,6 +1718,116 @@ function parseCodexSession(jsonlText) {
903
1718
  toolCall.result = output;
904
1719
  toolCall.isError = inferToolError(output);
905
1720
  pendingToolCalls.delete(payload.call_id);
1721
+ if (toolCall.name === "spawn_agent" && output) {
1722
+ try {
1723
+ const result = JSON.parse(output);
1724
+ const agentId = typeof result.agent_id === "string" ? result.agent_id : agentIdBySpawnCall.get(toolCall.id) ?? "";
1725
+ const agentPath = typeof result.task_name === "string" ? result.task_name : null;
1726
+ const nickname = typeof result.nickname === "string" ? result.nickname : agentPath ? agentNameFromPath(agentPath) : null;
1727
+ if (agentId) {
1728
+ const spawnInfo = spawnAgentCalls.get(toolCall.id);
1729
+ const existing = agentRegistry.get(agentId);
1730
+ const info = {
1731
+ agentId,
1732
+ nickname: nickname ?? existing?.nickname ?? spawnInfo?.taskName ?? null,
1733
+ agentPath: agentPath ?? existing?.agentPath ?? null,
1734
+ message: existing?.message ?? spawnInfo?.message ?? "",
1735
+ model: existing?.model ?? spawnInfo?.model ?? null,
1736
+ agentType: existing?.agentType ?? spawnInfo?.agentType ?? null,
1737
+ timestamp: existing?.timestamp ?? spawnInfo?.timestamp ?? timestamp,
1738
+ parentToolCallId: existing?.parentToolCallId ?? toolCall.id
1739
+ };
1740
+ agentRegistry.set(agentId, info);
1741
+ agentIdBySpawnCall.set(toolCall.id, agentId);
1742
+ if (info.agentPath) agentIdByPath.set(info.agentPath, agentId);
1743
+ upsertAgentMessage(agentId, current, timestamp, { status: "running" });
1744
+ }
1745
+ } catch {
1746
+ }
1747
+ }
1748
+ if (toolCall.name === "wait_agent" && output && current) {
1749
+ try {
1750
+ const result = JSON.parse(output);
1751
+ const statusMap = isObject2(result.status) ? result.status : {};
1752
+ for (const [agentId, status] of Object.entries(statusMap)) {
1753
+ const statusValue = isObject2(status) ? status : {};
1754
+ const completedText = typeof statusValue.completed === "string" ? statusValue.completed : "";
1755
+ const failedText = typeof statusValue.failed === "string" ? statusValue.failed : typeof statusValue.error === "string" ? statusValue.error : "";
1756
+ const interruptedText = typeof statusValue.interrupted === "string" ? statusValue.interrupted : "";
1757
+ const lifecycle = completedText ? "completed" : failedText ? "failed" : interruptedText ? "interrupted" : typeof statusValue.status === "string" ? statusValue.status : "running";
1758
+ if (!agentRegistry.has(agentId)) {
1759
+ agentRegistry.set(agentId, {
1760
+ agentId,
1761
+ nickname: null,
1762
+ agentPath: null,
1763
+ message: "",
1764
+ model: null,
1765
+ agentType: null,
1766
+ timestamp,
1767
+ parentToolCallId: ""
1768
+ });
1769
+ }
1770
+ upsertAgentMessage(agentId, current, timestamp, {
1771
+ status: lifecycle,
1772
+ text: completedText || failedText || interruptedText || void 0
1773
+ });
1774
+ }
1775
+ } catch {
1776
+ }
1777
+ }
1778
+ continue;
1779
+ }
1780
+ if (payload.type === "custom_tool_call" && typeof payload.call_id === "string") {
1781
+ const name = typeof payload.name === "string" ? payload.name : "tool";
1782
+ const rawInput = typeof payload.input === "string" ? payload.input : "";
1783
+ const callId = payload.call_id;
1784
+ const perFileCalls = rawInput ? parseCodexToolPatches(name, rawInput, callId, timestamp, metadata.cwd) : [];
1785
+ if (name !== "apply_patch") {
1786
+ const toolCall = {
1787
+ id: callId,
1788
+ name: name === "exec_command" ? "Bash" : name,
1789
+ input: parseToolInput(rawInput),
1790
+ result: null,
1791
+ isError: false,
1792
+ timestamp
1793
+ };
1794
+ pendingToolCalls.set(toolCall.id, toolCall);
1795
+ appendToolCall(current, toolCall, timestamp);
1796
+ }
1797
+ for (const tc of perFileCalls) {
1798
+ pendingToolCalls.set(tc.id, tc);
1799
+ appendToolCall(current, tc, timestamp);
1800
+ }
1801
+ if (perFileCalls.length > 0) {
1802
+ patchCallIds.set(callId, {
1803
+ calls: perFileCalls,
1804
+ direct: name === "apply_patch"
1805
+ });
1806
+ }
1807
+ continue;
1808
+ }
1809
+ if (payload.type === "custom_tool_call_output" && typeof payload.call_id === "string") {
1810
+ const callId = payload.call_id;
1811
+ const { text, isError } = parseCustomToolOutput(payload.output);
1812
+ const patchCalls = patchCallIds.get(callId);
1813
+ if (patchCalls) {
1814
+ const failedIds = patchCalls.direct && isError ? new Set(patchCalls.calls.map((call) => call.id)) : findFailedNestedPatchCallIds(text, isError, patchCalls.calls);
1815
+ for (const patchCall of patchCalls.calls) {
1816
+ const tc = pendingToolCalls.get(patchCall.id);
1817
+ if (tc) {
1818
+ tc.result = text;
1819
+ tc.isError = failedIds.has(tc.id);
1820
+ pendingToolCalls.delete(tc.id);
1821
+ }
1822
+ }
1823
+ patchCallIds.delete(callId);
1824
+ }
1825
+ const toolCall = pendingToolCalls.get(callId);
1826
+ if (toolCall) {
1827
+ toolCall.result = text;
1828
+ toolCall.isError = isError;
1829
+ pendingToolCalls.delete(callId);
1830
+ }
906
1831
  continue;
907
1832
  }
908
1833
  }
@@ -913,19 +1838,17 @@ function parseCodexSession(jsonlText) {
913
1838
  gitBranch: metadata.gitBranch,
914
1839
  cwd: metadata.cwd,
915
1840
  slug: metadata.slug,
1841
+ name: "",
916
1842
  model: metadata.model,
917
1843
  turns,
918
- stats: computeStats(turns),
919
- rawMessages: records,
1844
+ stats: options?.skipStats ? createEmptySessionStats(turns.length) : computeStats(turns),
1845
+ rawMessages: options?.skipStats ? [] : records,
920
1846
  branchedFrom: metadata.branchedFrom,
921
1847
  agentKind: "codex"
922
1848
  };
923
1849
  }
924
1850
 
925
1851
  // src/lib/parser.ts
926
- function isAssistantMessage2(msg) {
927
- return msg.type === "assistant";
928
- }
929
1852
  function extractTextFromContent2(content) {
930
1853
  if (typeof content === "string") return content;
931
1854
  return content.filter((b) => b.type === "text").map((b) => b.text).join("\n");
@@ -944,55 +1867,43 @@ function parseLines(jsonlText) {
944
1867
  }
945
1868
  function isCodexRawMessages(rawMessages) {
946
1869
  const firstType = rawMessages[0]?.type;
947
- return firstType === "session_meta" || firstType === "turn_context" || firstType === "event_msg" || firstType === "response_item";
1870
+ return firstType === "session_meta" || firstType === "turn_context" || firstType === "event_msg" || firstType === "response_item" || firstType === "compacted" || firstType === "world_state" || firstType === "inter_agent_communication_metadata";
948
1871
  }
949
1872
  function serializeRawMessages(rawMessages) {
950
1873
  return rawMessages.map((msg) => JSON.stringify(msg)).join("\n");
951
1874
  }
952
1875
  function extractSessionMetadata(messages) {
953
- const meta = { sessionId: "", version: "", gitBranch: "", cwd: "", slug: "", model: "", branchedFrom: void 0 };
1876
+ const meta = { sessionId: "", version: "", gitBranch: "", cwd: "", slug: "", name: "", model: "", branchedFrom: void 0 };
954
1877
  for (const msg of messages) {
955
1878
  if (msg.sessionId && !meta.sessionId) meta.sessionId = msg.sessionId;
956
1879
  if (msg.version && !meta.version) meta.version = msg.version;
957
1880
  if (msg.gitBranch && !meta.gitBranch) meta.gitBranch = msg.gitBranch;
958
1881
  if (msg.cwd && !meta.cwd) meta.cwd = msg.cwd;
959
1882
  if (msg.slug && !meta.slug) meta.slug = msg.slug;
1883
+ if (msg.name && !meta.name) meta.name = msg.name;
960
1884
  if (msg.branchedFrom && !meta.branchedFrom) {
961
1885
  meta.branchedFrom = msg.branchedFrom;
962
1886
  }
963
- if (isAssistantMessage2(msg) && msg.message.model && !meta.model) {
1887
+ if (isAssistantMessage(msg) && msg.message.model && !meta.model) {
964
1888
  meta.model = msg.message.model;
965
1889
  }
966
1890
  if (meta.sessionId && meta.version && meta.gitBranch && meta.cwd && meta.model) break;
967
1891
  }
968
1892
  return meta;
969
1893
  }
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) {
1894
+ function parseSession(jsonlText, options) {
984
1895
  if (isCodexSessionText(jsonlText)) {
985
- return parseCodexSession(jsonlText);
1896
+ return parseCodexSession(jsonlText, options);
986
1897
  }
987
1898
  const rawMessages = parseLines(jsonlText);
988
1899
  const metadata = extractSessionMetadata(rawMessages);
989
1900
  const turns = buildTurns(rawMessages);
990
- const stats = opts?.skipStats ? emptyStats(turns.length) : computeStats(turns);
1901
+ const stats = options?.skipStats ? createEmptySessionStats(turns.length) : computeStats(turns);
991
1902
  return {
992
1903
  ...metadata,
993
1904
  turns,
994
1905
  stats,
995
- rawMessages: opts?.skipStats ? [] : rawMessages,
1906
+ rawMessages: options?.skipStats ? [] : rawMessages,
996
1907
  agentKind: "claude"
997
1908
  };
998
1909
  }
@@ -1004,13 +1915,14 @@ ${newJsonlText}` : newJsonlText);
1004
1915
  }
1005
1916
  const newMessages = parseLines(newJsonlText);
1006
1917
  if (newMessages.length === 0) return existing;
1007
- const allRawMessages = [...existing.rawMessages, ...newMessages];
1008
- let lastTurnStartIdx = existing.rawMessages.length;
1918
+ const existingRawMessages = existing.rawMessages;
1919
+ const allRawMessages = [...existingRawMessages, ...newMessages];
1920
+ let lastTurnStartIdx = existingRawMessages.length;
1009
1921
  let turnsToKeep = existing.turns.length > 0 ? existing.turns.length - 1 : 0;
1010
1922
  if (existing.turns.length > 0) {
1011
1923
  let userMsgCount = 0;
1012
- for (let i = existing.rawMessages.length - 1; i >= 0; i--) {
1013
- const msg = existing.rawMessages[i];
1924
+ for (let i = existingRawMessages.length - 1; i >= 0; i--) {
1925
+ const msg = existingRawMessages[i];
1014
1926
  if (msg.type === "user" && !msg.isMeta) {
1015
1927
  const content = msg.message?.content;
1016
1928
  if (Array.isArray(content) && content.some((b) => b.type === "tool_result")) {
@@ -1037,8 +1949,8 @@ ${newJsonlText}` : newJsonlText);
1037
1949
  if (ownsProgressParent) {
1038
1950
  turnsToKeep = t;
1039
1951
  let found = 0;
1040
- for (let i = 0; i < existing.rawMessages.length; i++) {
1041
- const msg = existing.rawMessages[i];
1952
+ for (let i = 0; i < existingRawMessages.length; i++) {
1953
+ const msg = existingRawMessages[i];
1042
1954
  if (msg.type === "user" && !msg.isMeta) {
1043
1955
  const content = msg.message?.content;
1044
1956
  if (Array.isArray(content) && content.some((b) => b.type === "tool_result")) {
@@ -1067,11 +1979,13 @@ ${newJsonlText}` : newJsonlText);
1067
1979
  gitBranch: existing.gitBranch,
1068
1980
  cwd: existing.cwd,
1069
1981
  slug: existing.slug,
1982
+ name: existing.name,
1070
1983
  model: existing.model || (allTurns.length > 0 ? allTurns[allTurns.length - 1].model || "" : ""),
1071
1984
  turns: allTurns,
1072
1985
  stats,
1073
1986
  rawMessages: allRawMessages,
1074
- branchedFrom: existing.branchedFrom
1987
+ branchedFrom: existing.branchedFrom,
1988
+ agentKind: existing.agentKind
1075
1989
  };
1076
1990
  }
1077
1991
  function getUserMessageText(content) {
@@ -1329,17 +2243,11 @@ var SearchIndex = class {
1329
2243
  buildFull(projectsDir) {
1330
2244
  this.projectsDir = projectsDir;
1331
2245
  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 {
2246
+ for (const suffix of ["", "-wal", "-shm"]) {
2247
+ try {
2248
+ (0, import_node_fs.unlinkSync)(this.dbPath + suffix);
2249
+ } catch {
2250
+ }
1343
2251
  }
1344
2252
  this.db = new Database(this.dbPath);
1345
2253
  this.db.exec("PRAGMA journal_mode = WAL");
@@ -1611,7 +2519,8 @@ var import_node_os = require("node:os");
1611
2519
  var dirs = {
1612
2520
  PROJECTS_DIR: (0, import_node_path2.join)((0, import_node_os.homedir)(), ".claude", "projects"),
1613
2521
  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")
2522
+ TASKS_DIR: (0, import_node_path2.join)((0, import_node_os.homedir)(), ".claude", "tasks"),
2523
+ CODEX_SESSIONS_DIR: (0, import_node_path2.join)((0, import_node_os.homedir)(), ".codex", "sessions")
1615
2524
  };
1616
2525
  var DEFAULT_DB_PATH = (0, import_node_path2.join)((0, import_node_os.homedir)(), ".claude", "cogpit-memory", "search-index.db");
1617
2526
 
@@ -2083,6 +2992,8 @@ function extractUserMessageText(userMessage) {
2083
2992
  for (const block of userMessage) {
2084
2993
  if (block.type === "text") parts.push(block.text);
2085
2994
  else if (block.type === "image") parts.push("[image attached]");
2995
+ else if (block.type === "document") parts.push("[document attached]");
2996
+ else if (block.type === "audio") parts.push("[audio attached]");
2086
2997
  }
2087
2998
  return parts.length > 0 ? parts.join("\n") : null;
2088
2999
  }
@@ -2181,6 +3092,10 @@ function mapTurnToDetail(session, turnIndex) {
2181
3092
  durationMs: turn.durationMs
2182
3093
  };
2183
3094
  }
3095
+ function mapToolCall(tc) {
3096
+ const { result, resultTruncated } = truncateResult(tc.result);
3097
+ return { id: tc.id, name: tc.name, input: tc.input, result, resultTruncated, isError: tc.isError };
3098
+ }
2184
3099
  function mapContentBlock(block) {
2185
3100
  switch (block.kind) {
2186
3101
  case "thinking": {
@@ -2192,10 +3107,7 @@ function mapContentBlock(block) {
2192
3107
  case "tool_calls":
2193
3108
  return {
2194
3109
  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
- }),
3110
+ toolCalls: block.toolCalls.map(mapToolCall),
2199
3111
  timestamp: block.timestamp ?? null
2200
3112
  };
2201
3113
  case "sub_agent":
@@ -2205,6 +3117,21 @@ function mapContentBlock(block) {
2205
3117
  agents: block.messages.map(mapSubAgentDetail),
2206
3118
  timestamp: block.timestamp ?? null
2207
3119
  };
3120
+ case "queued_prompt":
3121
+ return { kind: "queued_prompt", content: block.content, timestamp: block.timestamp ?? null };
3122
+ case "hook_event":
3123
+ return { kind: "hook_event", events: block.events, timestamp: block.timestamp ?? null };
3124
+ case "plan_mode":
3125
+ return {
3126
+ kind: "plan_mode",
3127
+ plan: block.plan,
3128
+ planFilePath: block.planFilePath ?? null,
3129
+ status: block.status,
3130
+ toolCalls: block.toolCalls.map(mapToolCall),
3131
+ timestamp: block.timestamp ?? null
3132
+ };
3133
+ case "recap":
3134
+ return { kind: "recap", content: block.content, timestamp: block.timestamp ?? null };
2208
3135
  }
2209
3136
  }
2210
3137
  async function findSubagentFile(parentJsonlPath, agentId) {
@@ -2327,7 +3254,6 @@ async function getAgentOverview(sessionId, agentId) {
2327
3254
  async function getAgentTurnDetail(sessionId, agentId, turnIndex) {
2328
3255
  const jsonlPath = await findJsonlPath(sessionId);
2329
3256
  if (!jsonlPath) return { error: "Session not found" };
2330
- const content = await (0, import_promises3.readFile)(jsonlPath, "utf-8");
2331
3257
  const subagentFile = await findSubagentFile(jsonlPath, agentId);
2332
3258
  if (!subagentFile) return { error: "Agent not found" };
2333
3259
  const subagentContent = await (0, import_promises3.readFile)(subagentFile.filePath, "utf-8");
@@ -2339,7 +3265,6 @@ async function getAgentTurnDetail(sessionId, agentId, turnIndex) {
2339
3265
  // src/commands/sessions.ts
2340
3266
  var import_promises5 = require("node:fs/promises");
2341
3267
  var import_node_path6 = require("node:path");
2342
- var import_node_os3 = require("node:os");
2343
3268
 
2344
3269
  // src/lib/metadata.ts
2345
3270
  var import_promises4 = require("node:fs/promises");
@@ -2356,6 +3281,19 @@ function deriveSessionStatus(rawMessages) {
2356
3281
  if (toolName) info.toolName = toolName;
2357
3282
  return info;
2358
3283
  }
3284
+ for (let i = rawMessages.length - 1; i >= 0; i--) {
3285
+ const msg = rawMessages[i];
3286
+ if (msg.type === "queue-operation") continue;
3287
+ if (msg.type === "progress") {
3288
+ const data = msg.data;
3289
+ if (data?.type === "hook_progress") {
3290
+ const decision = data.decision ?? data.hookSpecificOutput?.permissionDecision;
3291
+ if (decision === "defer") return result("deferred");
3292
+ }
3293
+ break;
3294
+ }
3295
+ break;
3296
+ }
2359
3297
  for (let i = rawMessages.length - 1; i >= 0; i--) {
2360
3298
  const msg = rawMessages[i];
2361
3299
  if (msg.type === "queue-operation") {
@@ -2390,6 +3328,13 @@ function deriveSessionStatus(rawMessages) {
2390
3328
  if (isMeta) continue;
2391
3329
  return result("processing");
2392
3330
  }
3331
+ if (msg.type === "system" && msg.subtype === "terminal_reason") {
3332
+ const reason = msg.reason;
3333
+ if (reason) {
3334
+ return { status: "completed", terminalReason: reason, pendingQueue: Math.max(0, pendingEnqueues) };
3335
+ }
3336
+ continue;
3337
+ }
2393
3338
  if (msg.type === "summary") continue;
2394
3339
  if (msg.type === "system" && msg.subtype === "compact_boundary") continue;
2395
3340
  }
@@ -2615,7 +3560,23 @@ async function getSessionStatus(filePath) {
2615
3560
  }
2616
3561
 
2617
3562
  // src/commands/sessions.ts
2618
- var CODEX_SESSIONS_DIR = (0, import_node_path6.join)((0, import_node_os3.homedir)(), ".codex", "sessions");
3563
+ function toSessionSummary(file, meta, status) {
3564
+ return {
3565
+ sessionId: meta.sessionId,
3566
+ filePath: file.path,
3567
+ timestamp: meta.timestamp,
3568
+ model: meta.model,
3569
+ cwd: meta.cwd,
3570
+ gitBranch: meta.gitBranch,
3571
+ slug: meta.slug,
3572
+ firstMessage: meta.firstUserMessage,
3573
+ lastMessage: meta.lastUserMessage,
3574
+ turnCount: meta.turnCount,
3575
+ status,
3576
+ mtime: file.mtimeMs,
3577
+ source: file.path.startsWith(dirs.CODEX_SESSIONS_DIR + "/") ? "codex" : "claude"
3578
+ };
3579
+ }
2619
3580
  async function listCodexSessionFiles(cutoff) {
2620
3581
  const walk = async (dir, depth) => {
2621
3582
  if (depth > 4) return [];
@@ -2642,7 +3603,7 @@ async function listCodexSessionFiles(cutoff) {
2642
3603
  }
2643
3604
  return results;
2644
3605
  };
2645
- return walk(CODEX_SESSIONS_DIR, 0);
3606
+ return walk(dirs.CODEX_SESSIONS_DIR, 0);
2646
3607
  }
2647
3608
  async function listSessions(opts = {}) {
2648
3609
  const limit = Math.min(Math.max(1, opts.limit ?? 20), 100);
@@ -2655,29 +3616,31 @@ async function listSessions(opts = {}) {
2655
3616
  return [];
2656
3617
  }
2657
3618
  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);
3619
+ const [nested, codexFiles] = await Promise.all([
3620
+ Promise.all(
3621
+ projectDirs.map(async (projectDir) => {
3622
+ try {
3623
+ const files = await (0, import_promises5.readdir)(projectDir);
3624
+ const jsonlFiles = files.filter((f) => f.endsWith(".jsonl"));
3625
+ const statResults = await Promise.all(
3626
+ jsonlFiles.map(async (f) => {
3627
+ const filePath = (0, import_node_path6.join)(projectDir, f);
3628
+ try {
3629
+ const s = await (0, import_promises5.stat)(filePath);
3630
+ return s.mtimeMs >= cutoff ? { path: filePath, mtimeMs: s.mtimeMs } : null;
3631
+ } catch {
3632
+ return null;
3633
+ }
3634
+ })
3635
+ );
3636
+ return statResults.filter((r) => r !== null);
3637
+ } catch {
3638
+ return [];
3639
+ }
3640
+ })
3641
+ ),
3642
+ listCodexSessionFiles(cutoff)
3643
+ ]);
2681
3644
  const allFiles = [...nested.flat(), ...codexFiles];
2682
3645
  allFiles.sort((a, b) => b.mtimeMs - a.mtimeMs);
2683
3646
  const results = [];
@@ -2689,21 +3652,7 @@ async function listSessions(opts = {}) {
2689
3652
  getSessionStatus(file.path)
2690
3653
  ]);
2691
3654
  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
- });
3655
+ results.push(toSessionSummary(file, meta, statusInfo.status));
2707
3656
  } catch {
2708
3657
  }
2709
3658
  }
@@ -2719,18 +3668,20 @@ async function currentSession(cwd) {
2719
3668
  files = [];
2720
3669
  }
2721
3670
  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);
3671
+ const [statResults, codexCandidates] = await Promise.all([
3672
+ Promise.all(
3673
+ jsonlFiles.map(async (f) => {
3674
+ const filePath = (0, import_node_path6.join)(projectDir, f);
3675
+ try {
3676
+ const s = await (0, import_promises5.stat)(filePath);
3677
+ return { path: filePath, mtimeMs: s.mtimeMs };
3678
+ } catch {
3679
+ return null;
3680
+ }
3681
+ })
3682
+ ),
3683
+ listCodexSessionFiles(0)
3684
+ ]);
2734
3685
  const codexMatches = [];
2735
3686
  for (const file of codexCandidates) {
2736
3687
  try {
@@ -2752,21 +3703,7 @@ async function currentSession(cwd) {
2752
3703
  getSessionMeta(latest.path),
2753
3704
  getSessionStatus(latest.path)
2754
3705
  ]);
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
- };
3706
+ return toSessionSummary(latest, meta, statusInfo.status);
2770
3707
  }
2771
3708
 
2772
3709
  // src/commands/index-cmd.ts