cogpit-memory 0.1.8 → 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/cli.js CHANGED
@@ -48,7 +48,7 @@ var Database = import_better_sqlite3.default;
48
48
  var import_node_fs = require("node:fs");
49
49
  var import_node_path = require("node:path");
50
50
 
51
- // src/lib/turnBuilder.ts
51
+ // src/lib/messageTypeGuards.ts
52
52
  function isUserMessage(msg) {
53
53
  return msg.type === "user";
54
54
  }
@@ -64,9 +64,14 @@ function isSystemMessage(msg) {
64
64
  function isSummaryMessage(msg) {
65
65
  return msg.type === "summary";
66
66
  }
67
+ function isQueueOperationMessage(msg) {
68
+ return msg.type === "queue-operation";
69
+ }
67
70
  function isCompactBoundary(msg) {
68
71
  return msg.type === "system" && msg.subtype === "compact_boundary";
69
72
  }
73
+
74
+ // src/lib/turnBuilder.ts
70
75
  function extractTextFromContent(content) {
71
76
  if (typeof content === "string") return content;
72
77
  return content.filter((b) => b.type === "text").map((b) => b.text).join("\n");
@@ -79,6 +84,11 @@ function extractToolResultText(content) {
79
84
  return "";
80
85
  }).filter(Boolean).join("\n");
81
86
  }
87
+ function isVisibleQueuedPrompt(content) {
88
+ if (!content?.trim()) return false;
89
+ const trimmed = content.trimStart();
90
+ return !trimmed.startsWith("<task-notification>") && !trimmed.startsWith("<local-command-");
91
+ }
82
92
  function mergeTokenUsage(existing, incoming) {
83
93
  if (!existing) {
84
94
  return { ...incoming };
@@ -87,7 +97,8 @@ function mergeTokenUsage(existing, incoming) {
87
97
  input_tokens: existing.input_tokens + incoming.input_tokens,
88
98
  output_tokens: existing.output_tokens + incoming.output_tokens,
89
99
  cache_creation_input_tokens: (existing.cache_creation_input_tokens ?? 0) + (incoming.cache_creation_input_tokens ?? 0),
90
- cache_read_input_tokens: (existing.cache_read_input_tokens ?? 0) + (incoming.cache_read_input_tokens ?? 0)
100
+ cache_read_input_tokens: (existing.cache_read_input_tokens ?? 0) + (incoming.cache_read_input_tokens ?? 0),
101
+ speed: incoming.speed ?? existing.speed
91
102
  };
92
103
  }
93
104
  function buildCompactionSummary(turns, title) {
@@ -120,10 +131,95 @@ function buildCompactionSummary(turns, title) {
120
131
  }
121
132
  return parts.join("\n");
122
133
  }
134
+ function groupPlanModeBlocks(blocks) {
135
+ const result = [];
136
+ let i = 0;
137
+ while (i < blocks.length) {
138
+ const block = blocks[i];
139
+ if (block.kind === "tool_calls") {
140
+ const enterIdx = block.toolCalls.findIndex((tc) => tc.name === "EnterPlanMode");
141
+ if (enterIdx !== -1) {
142
+ const enterCall = block.toolCalls[enterIdx];
143
+ const plan = String(enterCall.input.plan ?? "");
144
+ const timestamp = block.timestamp;
145
+ const before = block.toolCalls.slice(0, enterIdx);
146
+ if (before.length > 0) {
147
+ result.push({ kind: "tool_calls", toolCalls: before, timestamp: block.timestamp });
148
+ }
149
+ const embedded = [];
150
+ const afterEnterInSameBlock = block.toolCalls.slice(enterIdx + 1);
151
+ const exitInSameBlock = afterEnterInSameBlock.findIndex((tc) => tc.name === "ExitPlanMode");
152
+ if (exitInSameBlock !== -1) {
153
+ embedded.push(...afterEnterInSameBlock.slice(0, exitInSameBlock));
154
+ const exitCall = afterEnterInSameBlock[exitInSameBlock];
155
+ const planFilePath = exitCall.input.path ? String(exitCall.input.path) : void 0;
156
+ let status = "pending";
157
+ if (exitCall.result !== null) {
158
+ status = exitCall.isError ? "rejected" : "approved";
159
+ }
160
+ result.push({ kind: "plan_mode", plan, planFilePath, status, toolCalls: embedded, timestamp });
161
+ const after = afterEnterInSameBlock.slice(exitInSameBlock + 1);
162
+ if (after.length > 0) {
163
+ result.push({ kind: "tool_calls", toolCalls: after, timestamp: block.timestamp });
164
+ }
165
+ i++;
166
+ continue;
167
+ } else {
168
+ embedded.push(...afterEnterInSameBlock);
169
+ let exitCall;
170
+ let planFilePath;
171
+ let status = "pending";
172
+ let j = i + 1;
173
+ const passthroughBlocks = [];
174
+ while (j < blocks.length) {
175
+ const next = blocks[j];
176
+ if (next.kind === "tool_calls") {
177
+ const exitIdx = next.toolCalls.findIndex((tc) => tc.name === "ExitPlanMode");
178
+ if (exitIdx !== -1) {
179
+ embedded.push(...next.toolCalls.slice(0, exitIdx));
180
+ exitCall = next.toolCalls[exitIdx];
181
+ planFilePath = exitCall.input.path ? String(exitCall.input.path) : void 0;
182
+ if (exitCall.result !== null) {
183
+ status = exitCall.isError ? "rejected" : "approved";
184
+ }
185
+ const tail = next.toolCalls.slice(exitIdx + 1);
186
+ result.push({ kind: "plan_mode", plan, planFilePath, status, toolCalls: embedded, timestamp });
187
+ result.push(...passthroughBlocks);
188
+ if (tail.length > 0) {
189
+ result.push({ kind: "tool_calls", toolCalls: tail, timestamp: next.timestamp });
190
+ }
191
+ i = j + 1;
192
+ break;
193
+ } else {
194
+ embedded.push(...next.toolCalls);
195
+ j++;
196
+ }
197
+ } else if (next.kind === "hook_event" || next.kind === "text" || next.kind === "queued_prompt") {
198
+ passthroughBlocks.push(next);
199
+ j++;
200
+ } else {
201
+ break;
202
+ }
203
+ }
204
+ if (!exitCall) {
205
+ result.push({ kind: "plan_mode", plan, planFilePath: void 0, status: "pending", toolCalls: embedded, timestamp });
206
+ result.push(...passthroughBlocks);
207
+ i = j;
208
+ }
209
+ continue;
210
+ }
211
+ }
212
+ }
213
+ result.push(block);
214
+ i++;
215
+ }
216
+ return result;
217
+ }
123
218
  function buildTurns(messages) {
124
219
  const turns = [];
125
220
  let current = null;
126
221
  let pendingCompaction = null;
222
+ let pendingRecap = null;
127
223
  const pendingToolUses = /* @__PURE__ */ new Map();
128
224
  const seenMessageIds = /* @__PURE__ */ new Set();
129
225
  const subAgentMap = /* @__PURE__ */ new Map();
@@ -154,6 +250,22 @@ function buildTurns(messages) {
154
250
  for (const [parentId] of subAgentMap) {
155
251
  flushSubAgentMessages(parentId);
156
252
  }
253
+ for (const block of current.contentBlocks) {
254
+ if (block.kind !== "hook_event") continue;
255
+ for (const ev of block.events) {
256
+ const isPostToolUse = ev.eventName === "PostToolUse" || ev.eventName.startsWith("PostToolUse:");
257
+ if (!isPostToolUse || !ev.toolUseId) continue;
258
+ const toolCall = current.toolCalls.find((tc) => tc.id === ev.toolUseId);
259
+ if (!toolCall) continue;
260
+ if (ev.updatedToolOutput) {
261
+ toolCall.outputReplacedByHook = true;
262
+ }
263
+ if (ev.durationMs !== void 0) {
264
+ toolCall.hookDurationMs = (toolCall.hookDurationMs ?? 0) + ev.durationMs;
265
+ }
266
+ }
267
+ }
268
+ current.contentBlocks = groupPlanModeBlocks(current.contentBlocks);
157
269
  turns.push(current);
158
270
  current = null;
159
271
  agentBlockMap.clear();
@@ -175,6 +287,20 @@ function buildTurns(messages) {
175
287
  );
176
288
  continue;
177
289
  }
290
+ if (isSystemMessage(msg) && msg.subtype === "away_summary" && msg.content) {
291
+ pendingRecap = { content: msg.content, timestamp: msg.timestamp };
292
+ continue;
293
+ }
294
+ if (isQueueOperationMessage(msg)) {
295
+ if (current && msg.operation === "enqueue" && isVisibleQueuedPrompt(msg.content)) {
296
+ current.contentBlocks.push({
297
+ kind: "queued_prompt",
298
+ content: msg.content,
299
+ timestamp: msg.timestamp
300
+ });
301
+ }
302
+ continue;
303
+ }
178
304
  if (isUserMessage(msg) && !msg.isMeta) {
179
305
  const content = msg.message.content;
180
306
  if (typeof content !== "string" && Array.isArray(content)) {
@@ -185,7 +311,7 @@ function buildTurns(messages) {
185
311
  const pending = pendingToolUses.get(block.tool_use_id);
186
312
  if (pending) {
187
313
  pending.turn.toolCalls[pending.index].result = extractToolResultText(block.content);
188
- pending.turn.toolCalls[pending.index].isError = block.is_error;
314
+ pending.turn.toolCalls[pending.index].isError = block.is_error === true;
189
315
  pendingToolUses.delete(block.tool_use_id);
190
316
  }
191
317
  }
@@ -203,6 +329,7 @@ function buildTurns(messages) {
203
329
  }
204
330
  const agentMsg = {
205
331
  agentId: toolUseResult.agentId,
332
+ parentToolUseId: toolUseId || void 0,
206
333
  agentName: taskMeta?.name ?? null,
207
334
  subagentType: taskMeta?.subagentType ?? null,
208
335
  type: "assistant",
@@ -254,6 +381,10 @@ function buildTurns(messages) {
254
381
  current.compactionSummary = pendingCompaction;
255
382
  pendingCompaction = null;
256
383
  }
384
+ if (pendingRecap) {
385
+ current.contentBlocks.push({ kind: "recap", content: pendingRecap.content, timestamp: pendingRecap.timestamp });
386
+ pendingRecap = null;
387
+ }
257
388
  continue;
258
389
  }
259
390
  if (isAssistantMessage(msg)) {
@@ -303,6 +434,7 @@ function buildTurns(messages) {
303
434
  if (block.type === "thinking") {
304
435
  flushToolCalls2();
305
436
  const tb = block;
437
+ if (!tb.thinking) continue;
306
438
  current.thinking.push(tb);
307
439
  msgThinking.push(tb);
308
440
  } else if (block.type === "text") {
@@ -382,6 +514,7 @@ function buildTurns(messages) {
382
514
  const taskMeta = taskMetaMap.get(parentId);
383
515
  const agentMsg = {
384
516
  agentId: data.agentId,
517
+ parentToolUseId: parentId || void 0,
385
518
  agentName: taskMeta?.name ?? null,
386
519
  subagentType: taskMeta?.subagentType ?? null,
387
520
  type: data.message.type,
@@ -427,7 +560,7 @@ function buildTurns(messages) {
427
560
  );
428
561
  if (match) {
429
562
  match.result = extractToolResultText(block.content);
430
- match.isError = block.is_error;
563
+ match.isError = block.is_error === true;
431
564
  }
432
565
  }
433
566
  }
@@ -446,6 +579,36 @@ function buildTurns(messages) {
446
579
  }
447
580
  continue;
448
581
  }
582
+ if (isProgressMessage(msg) && msg.data.type === "hook_progress") {
583
+ const data = msg.data;
584
+ const eventName = String(data.hook_event_name ?? data.hookEvent ?? "unknown");
585
+ const hookSpecific = data.hookSpecificOutput;
586
+ const ev = {
587
+ eventName,
588
+ source: data.source !== void 0 ? String(data.source) : void 0,
589
+ toolName: data.tool_name !== void 0 ? String(data.tool_name) : void 0,
590
+ toolUseId: data.tool_use_id !== void 0 ? String(data.tool_use_id) : void 0,
591
+ command: data.command !== void 0 ? String(data.command) : void 0,
592
+ output: data.output !== void 0 ? String(data.output) : void 0,
593
+ stderr: data.stderr !== void 0 ? String(data.stderr) : void 0,
594
+ exitCode: data.exit_code !== void 0 ? Number(data.exit_code) : void 0,
595
+ decision: data.decision !== void 0 ? String(data.decision) : void 0,
596
+ durationMs: data.duration_ms !== void 0 ? Number(data.duration_ms) : void 0,
597
+ updatedToolOutput: hookSpecific?.updatedToolOutput !== void 0 ? String(hookSpecific.updatedToolOutput) : void 0,
598
+ sessionTitle: hookSpecific?.sessionTitle !== void 0 ? String(hookSpecific.sessionTitle) : void 0,
599
+ worktreePath: hookSpecific?.worktreePath !== void 0 ? String(hookSpecific.worktreePath) : void 0,
600
+ timestamp: msg.timestamp ?? ""
601
+ };
602
+ if (current) {
603
+ const last = current.contentBlocks[current.contentBlocks.length - 1];
604
+ if (last && last.kind === "hook_event") {
605
+ last.events.push(ev);
606
+ } else {
607
+ current.contentBlocks.push({ kind: "hook_event", events: [ev], timestamp: msg.timestamp });
608
+ }
609
+ }
610
+ continue;
611
+ }
449
612
  if (isSystemMessage(msg) && msg.subtype === "turn_duration" && current) {
450
613
  current.durationMs = msg.durationMs ?? null;
451
614
  continue;
@@ -456,58 +619,84 @@ function buildTurns(messages) {
456
619
  }
457
620
 
458
621
  // src/lib/pricingTiers.ts
459
- var EXTENDED_CONTEXT_THRESHOLD = 2e5;
460
622
  var TIER_HAIKU_35 = { input: 0.8, output: 4, cacheWrite: 1, cacheRead: 0.08, webSearch: 0.01 };
461
623
  var TIER_HAIKU_45 = { input: 1, output: 5, cacheWrite: 1.25, cacheRead: 0.1, webSearch: 0.01 };
462
- var TIER_SONNET_LEGACY = { input: 3, output: 15, cacheWrite: 3.75, cacheRead: 0.3, webSearch: 0.01 };
463
- var TIER_SONNET_LATEST = { input: 5, output: 25, cacheWrite: 6.25, cacheRead: 0.5, webSearch: 0.01 };
624
+ var TIER_SONNET = { input: 3, output: 15, cacheWrite: 3.75, cacheRead: 0.3, webSearch: 0.01 };
625
+ var TIER_OPUS_MID = { input: 5, output: 25, cacheWrite: 6.25, cacheRead: 0.5, webSearch: 0.01 };
626
+ var TIER_FRONTIER = { input: 10, output: 50, cacheWrite: 12.5, cacheRead: 1, webSearch: 0.01 };
464
627
  var TIER_OPUS_LEGACY = { input: 15, output: 75, cacheWrite: 18.75, cacheRead: 1.5, webSearch: 0.01 };
465
- var TIER_SONNET_LEGACY_EXT = { input: 6, output: 22.5, cacheWrite: 7.5, cacheRead: 0.6, webSearch: 0.01 };
466
- var TIER_EXTENDED = { input: 10, output: 37.5, cacheWrite: 12.5, cacheRead: 1, webSearch: 0.01 };
628
+ var TIER_GPT_56_SOL = { input: 5, output: 30, cacheWrite: 6.25, cacheRead: 0.5, webSearch: 0.01 };
629
+ var TIER_GPT_56_TERRA = { input: 2.5, output: 15, cacheWrite: 3.125, cacheRead: 0.25, webSearch: 0.01 };
630
+ var TIER_GPT_56_LUNA = { input: 1, output: 6, cacheWrite: 1.25, cacheRead: 0.1, webSearch: 0.01 };
631
+ var TIER_GPT_55 = { input: 5, output: 30, cacheWrite: 5, cacheRead: 0.5, webSearch: 0.01 };
632
+ var TIER_GPT_54 = { input: 2.5, output: 15, cacheWrite: 2.5, cacheRead: 0.25, webSearch: 0.01 };
633
+ var TIER_GPT_54_MINI = { input: 0.75, output: 4.5, cacheWrite: 0.75, cacheRead: 0.075, webSearch: 0.01 };
634
+ var TIER_FAST = { input: 30, output: 150, cacheWrite: 37.5, cacheRead: 3, webSearch: 0.01 };
467
635
  var MODEL_TIERS = [
636
+ // OpenAI GPT models. Keep variants before their shared family prefixes.
637
+ { match: "gpt-5.6-sol", tier: TIER_GPT_56_SOL },
638
+ { match: "gpt-5.6-terra", tier: TIER_GPT_56_TERRA },
639
+ { match: "gpt-5.6-luna", tier: TIER_GPT_56_LUNA },
640
+ { match: "gpt-5.6", tier: TIER_GPT_56_SOL },
641
+ { match: "gpt-5.4-mini", tier: TIER_GPT_54_MINI },
642
+ { match: "gpt-5.5", tier: TIER_GPT_55 },
643
+ { match: "gpt-5.4", tier: TIER_GPT_54 },
644
+ // Frontier (Fable 5 / Mythos 5 / Opus 4.8)
645
+ { match: "fable-5", tier: TIER_FRONTIER },
646
+ { match: "mythos-5", tier: TIER_FRONTIER },
647
+ { match: "opus-4-8", tier: TIER_FRONTIER },
468
648
  // Haiku
469
649
  { match: "haiku-4-5", tier: TIER_HAIKU_45 },
470
650
  { match: "haiku-4-0", tier: TIER_HAIKU_35 },
471
651
  { match: "3-5-haiku", tier: TIER_HAIKU_35 },
472
- // Sonnet latest (4.5+)
473
- { match: "sonnet-4-6", tier: TIER_SONNET_LATEST, extendedTier: TIER_EXTENDED },
474
- { match: "sonnet-4-5", tier: TIER_SONNET_LATEST, extendedTier: TIER_EXTENDED },
475
- // Sonnet legacy (3.5, 3.7, 4.0)
476
- { match: "sonnet-4-0", tier: TIER_SONNET_LEGACY, extendedTier: TIER_SONNET_LEGACY_EXT },
477
- { match: "3-7-sonnet", tier: TIER_SONNET_LEGACY, extendedTier: TIER_SONNET_LEGACY_EXT },
478
- { match: "3-5-sonnet", tier: TIER_SONNET_LEGACY, extendedTier: TIER_SONNET_LEGACY_EXT },
479
- // Opus latest (4.5+) — same tier as sonnet latest
480
- { match: "opus-4-6", tier: TIER_SONNET_LATEST, extendedTier: TIER_EXTENDED },
481
- { match: "opus-4-5", tier: TIER_SONNET_LATEST, extendedTier: TIER_EXTENDED },
652
+ // Sonnet (3.5 through 4.6 — all one tier in current CC)
653
+ { match: "sonnet-4-7", tier: TIER_SONNET },
654
+ { match: "sonnet-4-6", tier: TIER_SONNET },
655
+ { match: "sonnet-4-5", tier: TIER_SONNET },
656
+ { match: "sonnet-4-0", tier: TIER_SONNET },
657
+ { match: "3-7-sonnet", tier: TIER_SONNET },
658
+ { match: "3-5-sonnet", tier: TIER_SONNET },
659
+ // Opus 4.5–4.7
660
+ { match: "opus-4-7", tier: TIER_OPUS_MID },
661
+ { match: "opus-4-6", tier: TIER_OPUS_MID },
662
+ { match: "opus-4-5", tier: TIER_OPUS_MID },
482
663
  // Opus legacy (4.0, 4.1)
483
664
  { match: "opus-4-1", tier: TIER_OPUS_LEGACY },
484
665
  { match: "opus-4-0", tier: TIER_OPUS_LEGACY }
485
666
  ];
486
- var DEFAULT_TIER = TIER_SONNET_LATEST;
487
- var DEFAULT_EXTENDED_TIER = TIER_EXTENDED;
667
+ var FAST_MODE_MODELS = ["opus-4-6", "opus-4-7"];
668
+ var DEFAULT_TIER = TIER_OPUS_MID;
488
669
  var FAMILY_FALLBACKS = [
670
+ { match: "fable", tier: TIER_FRONTIER },
671
+ { match: "mythos", tier: TIER_FRONTIER },
489
672
  { match: "haiku", tier: TIER_HAIKU_45 },
490
- { match: "sonnet", tier: TIER_SONNET_LATEST },
491
- { match: "opus", tier: TIER_SONNET_LATEST }
673
+ { match: "sonnet", tier: TIER_SONNET },
674
+ { match: "opus", tier: TIER_OPUS_MID }
492
675
  ];
493
- function resolveTier(model, totalInputTokens) {
494
- const isExtended = (totalInputTokens ?? 0) > EXTENDED_CONTEXT_THRESHOLD;
676
+ function resolveTier(model, speed) {
677
+ if (speed === "fast" && FAST_MODE_MODELS.some((m) => model.includes(m))) {
678
+ return TIER_FAST;
679
+ }
495
680
  for (const entry of MODEL_TIERS) {
496
- if (model.includes(entry.match)) {
497
- return isExtended && entry.extendedTier ? entry.extendedTier : entry.tier;
498
- }
681
+ if (model.includes(entry.match)) return entry.tier;
499
682
  }
500
683
  for (const entry of FAMILY_FALLBACKS) {
501
684
  if (model.includes(entry.match)) return entry.tier;
502
685
  }
503
- return isExtended ? DEFAULT_EXTENDED_TIER : DEFAULT_TIER;
686
+ return DEFAULT_TIER;
687
+ }
688
+ function hasKnownPricing(model) {
689
+ if (!model) return true;
690
+ const normalized = model.toLowerCase();
691
+ if (!normalized.startsWith("gpt-")) return true;
692
+ return MODEL_TIERS.some((entry) => normalized.includes(entry.match));
504
693
  }
505
694
 
506
695
  // src/lib/token-costs.ts
507
696
  var CHARS_PER_TOKEN = 4;
508
697
  function calculateCost(c) {
509
- const totalInput = c.inputTokens + c.cacheWriteTokens + c.cacheReadTokens;
510
- const p = resolveTier(c.model ?? "", totalInput);
698
+ if (!hasKnownPricing(c.model)) return Number.NaN;
699
+ const p = resolveTier(c.model ?? "", c.speed);
511
700
  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;
512
701
  }
513
702
  function charsToTokens(chars) {
@@ -545,7 +734,8 @@ function calculateTurnCostEstimated(turn) {
545
734
  inputTokens: u.input_tokens,
546
735
  outputTokens: estimateTotalOutputTokens(turn),
547
736
  cacheWriteTokens: u.cache_creation_input_tokens ?? 0,
548
- cacheReadTokens: u.cache_read_input_tokens ?? 0
737
+ cacheReadTokens: u.cache_read_input_tokens ?? 0,
738
+ speed: u.speed
549
739
  });
550
740
  }
551
741
  function calculateSubAgentCostEstimated(sa) {
@@ -556,7 +746,8 @@ function calculateSubAgentCostEstimated(sa) {
556
746
  inputTokens: u.input_tokens,
557
747
  outputTokens: estimateSubAgentOutput(sa),
558
748
  cacheWriteTokens: u.cache_creation_input_tokens ?? 0,
559
- cacheReadTokens: u.cache_read_input_tokens ?? 0
749
+ cacheReadTokens: u.cache_read_input_tokens ?? 0,
750
+ speed: u.speed
560
751
  });
561
752
  }
562
753
 
@@ -576,6 +767,19 @@ function addUsageToStats(stats, usage, estimatedOutput, cost) {
576
767
  stats.totalCacheReadTokens += usage.cache_read_input_tokens ?? 0;
577
768
  stats.totalCostUSD += cost;
578
769
  }
770
+ function createEmptySessionStats(turnCount) {
771
+ return {
772
+ totalInputTokens: 0,
773
+ totalOutputTokens: 0,
774
+ totalCacheCreationTokens: 0,
775
+ totalCacheReadTokens: 0,
776
+ totalCostUSD: 0,
777
+ toolCallCounts: {},
778
+ errorCount: 0,
779
+ totalDurationMs: 0,
780
+ turnCount
781
+ };
782
+ }
579
783
  function computeStats(turns) {
580
784
  const stats = {
581
785
  totalInputTokens: 0,
@@ -604,8 +808,271 @@ function computeStats(turns) {
604
808
  return stats;
605
809
  }
606
810
 
811
+ // src/lib/codex-patches.ts
812
+ var JS_SIMPLE_ESCAPES = {
813
+ "0": "\0",
814
+ b: "\b",
815
+ f: "\f",
816
+ n: "\n",
817
+ r: "\r",
818
+ t: " ",
819
+ v: "\v"
820
+ };
821
+ function readJsStringLiteral(source, startIndex) {
822
+ const quote = source[startIndex];
823
+ if (quote !== '"' && quote !== "'" && quote !== "`") return null;
824
+ let value = "";
825
+ let index = startIndex + 1;
826
+ while (index < source.length) {
827
+ const char = source[index];
828
+ if (char === quote) return { endIndex: index + 1, value };
829
+ if (char !== "\\") {
830
+ value += char;
831
+ index++;
832
+ continue;
833
+ }
834
+ index++;
835
+ if (index >= source.length) return null;
836
+ const escaped = source[index];
837
+ if (escaped in JS_SIMPLE_ESCAPES) {
838
+ value += JS_SIMPLE_ESCAPES[escaped];
839
+ index++;
840
+ continue;
841
+ }
842
+ if (escaped === "\n") {
843
+ index++;
844
+ continue;
845
+ }
846
+ if (escaped === "\r") {
847
+ index += source[index + 1] === "\n" ? 2 : 1;
848
+ continue;
849
+ }
850
+ if (escaped === "x" && /^[0-9a-fA-F]{2}$/.test(source.slice(index + 1, index + 3))) {
851
+ value += String.fromCharCode(Number.parseInt(source.slice(index + 1, index + 3), 16));
852
+ index += 3;
853
+ continue;
854
+ }
855
+ if (escaped === "u") {
856
+ const braced = source.slice(index + 1).match(/^\{([0-9a-fA-F]+)\}/);
857
+ if (braced) {
858
+ const codePoint = Number.parseInt(braced[1], 16);
859
+ if (Number.isFinite(codePoint) && codePoint <= 1114111) {
860
+ value += String.fromCodePoint(codePoint);
861
+ index += braced[0].length + 1;
862
+ continue;
863
+ }
864
+ }
865
+ const hex = source.slice(index + 1, index + 5);
866
+ if (/^[0-9a-fA-F]{4}$/.test(hex)) {
867
+ value += String.fromCharCode(Number.parseInt(hex, 16));
868
+ index += 5;
869
+ continue;
870
+ }
871
+ }
872
+ value += escaped;
873
+ index++;
874
+ }
875
+ return null;
876
+ }
877
+ function isApplyPatchText(value) {
878
+ return value.includes("*** Begin Patch") && value.includes("*** End Patch");
879
+ }
880
+ function extractApplyPatchInputs(execSource) {
881
+ if (!execSource.includes("apply_patch") || !execSource.includes("*** Begin Patch")) return [];
882
+ const assignedPatches = /* @__PURE__ */ new Map();
883
+ for (let index = 0; index < execSource.length; index++) {
884
+ const literal = readJsStringLiteral(execSource, index);
885
+ if (!literal) continue;
886
+ if (isApplyPatchText(literal.value)) {
887
+ const assignment = execSource.slice(0, index).match(/(?:^|[;\n])\s*(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*=\s*$/);
888
+ if (assignment) assignedPatches.set(assignment[1], literal.value);
889
+ }
890
+ index = literal.endIndex - 1;
891
+ }
892
+ const patches = [];
893
+ const seen = /* @__PURE__ */ new Set();
894
+ const applyCall = /\btools\s*\.\s*apply_patch\s*\(/g;
895
+ let match;
896
+ while ((match = applyCall.exec(execSource)) !== null) {
897
+ let argumentIndex = match.index + match[0].length;
898
+ while (/\s/.test(execSource[argumentIndex] ?? "")) argumentIndex++;
899
+ const literal = readJsStringLiteral(execSource, argumentIndex);
900
+ const identifier = literal ? null : execSource.slice(argumentIndex).match(/^([A-Za-z_$][\w$]*)/);
901
+ const patch = literal?.value ?? (identifier ? assignedPatches.get(identifier[1]) : void 0);
902
+ if (!patch || !isApplyPatchText(patch) || seen.has(patch)) continue;
903
+ seen.add(patch);
904
+ patches.push(patch);
905
+ }
906
+ return patches;
907
+ }
908
+ function resolveCodexPatchPath(filePath, cwd) {
909
+ if (!cwd || filePath.startsWith("/") || /^[A-Za-z]:[\\/]/.test(filePath)) return filePath;
910
+ return `${cwd.replace(/[\\/]+$/, "")}/${filePath.replace(/^\.\//, "")}`;
911
+ }
912
+ function parseApplyPatch(patchText, callId, timestamp, cwd = "") {
913
+ const toolCalls = [];
914
+ const filePattern = /^\*\*\*\s+(Update File|Add File|Delete File):\s*(.+)$/gm;
915
+ const sections = [];
916
+ let match;
917
+ while ((match = filePattern.exec(patchText)) !== null) {
918
+ sections.push({
919
+ action: match[1],
920
+ filePath: match[2].trim(),
921
+ headerIdx: match.index,
922
+ startIdx: match.index + match[0].length
923
+ });
924
+ }
925
+ for (let i = 0; i < sections.length; i++) {
926
+ const section = sections[i];
927
+ const endIdx = i + 1 < sections.length ? sections[i + 1].headerIdx : patchText.length;
928
+ const body = patchText.slice(section.startIdx, endIdx);
929
+ const movedTo = body.match(/^\*\*\*\s+Move to:\s*(.+)$/m)?.[1]?.trim();
930
+ const sourcePath = resolveCodexPatchPath(section.filePath, cwd);
931
+ const filePath = resolveCodexPatchPath(movedTo || section.filePath, cwd);
932
+ const hunkBodies = body.split(/^@@.*$/m).filter((s) => s.trim());
933
+ const oldLines = [];
934
+ const newLines = [];
935
+ for (const hunk of hunkBodies) {
936
+ for (const line of hunk.split("\n")) {
937
+ if (line.startsWith("-")) {
938
+ oldLines.push(line.slice(1));
939
+ } else if (line.startsWith("+")) {
940
+ newLines.push(line.slice(1));
941
+ } else if (line.startsWith(" ")) {
942
+ oldLines.push(line.slice(1));
943
+ newLines.push(line.slice(1));
944
+ }
945
+ }
946
+ }
947
+ const fileId = sections.length > 1 ? `${callId}:file-${i}` : callId;
948
+ if (section.action === "Add File") {
949
+ toolCalls.push({
950
+ id: fileId,
951
+ name: "Write",
952
+ input: { file_path: filePath, content: newLines.join("\n") },
953
+ result: null,
954
+ isError: false,
955
+ timestamp
956
+ });
957
+ } else if (section.action === "Delete File") {
958
+ toolCalls.push({
959
+ id: fileId,
960
+ name: "Edit",
961
+ input: { file_path: filePath, old_string: oldLines.join("\n"), new_string: "" },
962
+ result: null,
963
+ isError: false,
964
+ timestamp
965
+ });
966
+ } else {
967
+ toolCalls.push({
968
+ id: fileId,
969
+ name: "Edit",
970
+ input: {
971
+ file_path: filePath,
972
+ ...movedTo ? { old_path: sourcePath } : {},
973
+ old_string: oldLines.join("\n"),
974
+ new_string: newLines.join("\n")
975
+ },
976
+ result: null,
977
+ isError: false,
978
+ timestamp
979
+ });
980
+ }
981
+ }
982
+ return toolCalls;
983
+ }
984
+ function parseCodexToolPatches(toolName, rawInput, callId, timestamp, cwd = "") {
985
+ if (toolName === "apply_patch") {
986
+ return parseApplyPatch(rawInput, callId, timestamp, cwd);
987
+ }
988
+ return extractApplyPatchInputs(rawInput).flatMap(
989
+ (patch, patchIndex) => parseApplyPatch(patch, `${callId}:patch-${patchIndex}`, timestamp, cwd)
990
+ );
991
+ }
992
+ function findFailedNestedPatchCallIds(output, wrapperIsError, calls) {
993
+ if (!wrapperIsError || !/(?:apply[_ ]patch|invalid patch|invalid context|failed to (?:apply|find)|could not apply)/i.test(output)) {
994
+ return /* @__PURE__ */ new Set();
995
+ }
996
+ const groups = /* @__PURE__ */ new Map();
997
+ for (const call of calls) {
998
+ const patchIndex = call.id.match(/:patch-(\d+)(?::file-\d+)?$/)?.[1];
999
+ if (patchIndex === void 0) continue;
1000
+ const index = Number(patchIndex);
1001
+ const group = groups.get(index) ?? [];
1002
+ group.push(call);
1003
+ groups.set(index, group);
1004
+ }
1005
+ if (groups.size === 0) return new Set(calls.map((call) => call.id));
1006
+ const normalizedOutput = output.toLowerCase().replaceAll("\\", "/");
1007
+ const mentionedGroups = /* @__PURE__ */ new Set();
1008
+ for (const [index, group] of groups) {
1009
+ if (group.some((call) => {
1010
+ const filePath = String(call.input.file_path ?? "").toLowerCase().replaceAll("\\", "/");
1011
+ if (!filePath) return false;
1012
+ const segments = filePath.split("/").filter(Boolean);
1013
+ const suffix = segments.slice(-2).join("/");
1014
+ return normalizedOutput.includes(filePath) || suffix.includes("/") && normalizedOutput.includes(suffix);
1015
+ })) {
1016
+ mentionedGroups.add(index);
1017
+ }
1018
+ }
1019
+ for (const match of normalizedOutput.matchAll(/\bpatch\s*(?:#|-)?\s*(\d+)\b/g)) {
1020
+ const index = Number(match[1]);
1021
+ if (groups.has(index)) mentionedGroups.add(index);
1022
+ }
1023
+ if (mentionedGroups.size === 0) mentionedGroups.add(Math.max(...groups.keys()));
1024
+ const firstFailedIndex = Math.min(...mentionedGroups);
1025
+ for (const index of groups.keys()) {
1026
+ if (index >= firstFailedIndex) mentionedGroups.add(index);
1027
+ }
1028
+ return new Set(
1029
+ [...mentionedGroups].flatMap((index) => groups.get(index)?.map((call) => call.id) ?? [])
1030
+ );
1031
+ }
1032
+
1033
+ // src/lib/codex-tool-normalization.ts
1034
+ function isObject(value) {
1035
+ return typeof value === "object" && value !== null;
1036
+ }
1037
+ function inferToolError(output) {
1038
+ if (!output) return false;
1039
+ const exitMatch = output.match(/Process exited with code (\d+)/);
1040
+ if (exitMatch) return exitMatch[1] !== "0";
1041
+ const withoutSuccessSummaries = output.replace(/\b0\s+(?:fail(?:ed|ures?)?|errors?)\b/gi, "").replace(/\bno\s+(?:failures?|errors?)\b/gi, "");
1042
+ return /\b(error|failed|exception)\b/i.test(withoutSuccessSummaries);
1043
+ }
1044
+ function normalizePlanToTodos(input) {
1045
+ const plan = Array.isArray(input.plan) ? input.plan : [];
1046
+ const todos = plan.filter((item) => isObject(item)).map((item) => ({
1047
+ content: typeof item.step === "string" ? item.step : "",
1048
+ status: typeof item.status === "string" ? item.status : "pending",
1049
+ activeForm: typeof item.step === "string" ? item.step : ""
1050
+ }));
1051
+ return { todos };
1052
+ }
1053
+ function extractCustomToolOutputText(output) {
1054
+ if (typeof output === "string") return output;
1055
+ if (!Array.isArray(output)) return "";
1056
+ return output.filter((block) => isObject(block) && typeof block.text === "string").map((block) => block.text).join("");
1057
+ }
1058
+ function parseCustomToolOutput(output) {
1059
+ const rawOutput = extractCustomToolOutputText(output);
1060
+ if (!rawOutput) return { text: "", isError: false };
1061
+ try {
1062
+ const parsed = JSON.parse(rawOutput);
1063
+ const text = typeof parsed.output === "string" ? parsed.output : rawOutput;
1064
+ const meta = isObject(parsed.metadata) ? parsed.metadata : null;
1065
+ const isError = meta ? meta.exit_code !== 0 && meta.exit_code !== void 0 : inferToolError(text);
1066
+ return { text, isError };
1067
+ } catch {
1068
+ return { text: rawOutput, isError: inferToolError(rawOutput) };
1069
+ }
1070
+ }
1071
+
607
1072
  // src/lib/codex.ts
608
1073
  var SKIP_PROMPT_PREFIXES = [
1074
+ "# AGENTS.md instructions for ",
1075
+ "<recommended_plugins>",
609
1076
  "<environment_context>",
610
1077
  "<permissions instructions>",
611
1078
  "<collaboration_mode>",
@@ -621,17 +1088,120 @@ function safeParseLine(line) {
621
1088
  return null;
622
1089
  }
623
1090
  }
624
- function isObject(value) {
1091
+ function isObject2(value) {
625
1092
  return typeof value === "object" && value !== null;
626
1093
  }
627
1094
  function isCodexRecord(record) {
628
1095
  if (!record || typeof record.type !== "string") return false;
629
- return record.type === "session_meta" || record.type === "turn_context" || record.type === "event_msg" || record.type === "response_item";
1096
+ 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";
630
1097
  }
631
1098
  function extractMessageText(payload, blockType) {
632
1099
  const content = payload?.content;
633
1100
  if (!Array.isArray(content)) return "";
634
- return content.filter((block) => isObject(block) && block.type === blockType && typeof block.text === "string").map((block) => block.text).join("\n").trim();
1101
+ return content.filter((block) => isObject2(block) && block.type === blockType && typeof block.text === "string").map((block) => block.text).join("\n").trim();
1102
+ }
1103
+ var CODEX_IMAGE_DATA_URL = /^data:(image\/(?:png|jpeg|gif|webp));base64,(.+)$/is;
1104
+ var CODEX_AUDIO_DATA_URL = /^data:(audio\/(?:wav|x-wav|mpeg|mp4|webm|ogg));base64,(.+)$/is;
1105
+ function parseImageDataUrl(value) {
1106
+ if (typeof value !== "string") return null;
1107
+ const match = CODEX_IMAGE_DATA_URL.exec(value);
1108
+ if (!match?.[1] || !match[2]) return null;
1109
+ return {
1110
+ type: "image",
1111
+ source: {
1112
+ type: "base64",
1113
+ media_type: match[1].toLowerCase(),
1114
+ data: match[2]
1115
+ }
1116
+ };
1117
+ }
1118
+ function parseAudioDataUrl(value) {
1119
+ if (typeof value !== "string") return null;
1120
+ const match = CODEX_AUDIO_DATA_URL.exec(value);
1121
+ if (!match?.[1] || !match[2]) return null;
1122
+ return {
1123
+ type: "audio",
1124
+ source: {
1125
+ type: "base64",
1126
+ media_type: match[1].toLowerCase(),
1127
+ data: match[2]
1128
+ }
1129
+ };
1130
+ }
1131
+ function extractEventMessageImages(payload) {
1132
+ if (!Array.isArray(payload.images)) return [];
1133
+ return payload.images.map(parseImageDataUrl).filter((image) => image !== null);
1134
+ }
1135
+ function extractEventMessageAudio(payload) {
1136
+ if (!Array.isArray(payload.audio)) return [];
1137
+ return payload.audio.map(parseAudioDataUrl).filter((audio) => audio !== null);
1138
+ }
1139
+ function extractResponseMessageImages(payload) {
1140
+ if (!Array.isArray(payload.content)) return [];
1141
+ return payload.content.filter((block) => isObject2(block) && block.type === "input_image").map((block) => parseImageDataUrl(block.image_url)).filter((image) => image !== null);
1142
+ }
1143
+ function extractResponseMessageAudio(payload) {
1144
+ if (!Array.isArray(payload.content)) return [];
1145
+ return payload.content.filter((block) => isObject2(block) && block.type === "input_audio").map((block) => parseAudioDataUrl(block.audio_url)).filter((audio) => audio !== null);
1146
+ }
1147
+ function buildCodexUserContent(message, images, audio = [], localImages = [], localAudio = []) {
1148
+ const attachmentSuffix = [
1149
+ ...localImages.map((path) => `
1150
+ ![image](<${path}>)`),
1151
+ ...localAudio.map((path) => `
1152
+ [audio attachment](<${path}>)`)
1153
+ ].join("");
1154
+ const text = message + attachmentSuffix;
1155
+ if (images.length === 0 && audio.length === 0) return text;
1156
+ return [
1157
+ ...images,
1158
+ ...audio,
1159
+ ...text ? [{ type: "text", text }] : []
1160
+ ];
1161
+ }
1162
+ function normalizeFunctionName(rawName) {
1163
+ const leaf = rawName.split(/(?:__|[.:/])+/).filter(Boolean).at(-1) ?? rawName;
1164
+ const aliases = {
1165
+ spawnAgent: "spawn_agent",
1166
+ waitAgent: "wait_agent",
1167
+ sendMessage: "send_message",
1168
+ followupTask: "followup_task",
1169
+ listAgents: "list_agents",
1170
+ interruptAgent: "interrupt_agent"
1171
+ };
1172
+ const canonicalCollaborationNames = /* @__PURE__ */ new Set([
1173
+ "spawn_agent",
1174
+ "wait_agent",
1175
+ "send_message",
1176
+ "followup_task",
1177
+ "list_agents",
1178
+ "interrupt_agent"
1179
+ ]);
1180
+ if (aliases[leaf]) return aliases[leaf];
1181
+ if (canonicalCollaborationNames.has(leaf)) return leaf;
1182
+ return rawName;
1183
+ }
1184
+ function extractInterAgentMessage(payload) {
1185
+ const rawText = extractMessageText(payload, "input_text");
1186
+ if (!rawText) return { messageType: null, text: "" };
1187
+ const headerEnd = rawText.match(/\r?\nPayload:\s*(?:\r?\n|$)/);
1188
+ if (!rawText.startsWith("Message Type:") || !headerEnd || headerEnd.index === void 0) {
1189
+ return { messageType: null, text: rawText.trim() };
1190
+ }
1191
+ const header = rawText.slice(0, headerEnd.index);
1192
+ const messageType = header.match(/^Message Type:\s*([^\r\n]+)/)?.[1]?.trim().toUpperCase() ?? null;
1193
+ const text = rawText.slice(headerEnd.index + headerEnd[0].length).trim();
1194
+ return { messageType, text };
1195
+ }
1196
+ function agentNameFromPath(agentPath) {
1197
+ const segments = agentPath.split("/").filter(Boolean);
1198
+ return segments.at(-1) ?? null;
1199
+ }
1200
+ function readableAgentPrompt(value) {
1201
+ if (typeof value !== "string") return "";
1202
+ const prompt = value.trim();
1203
+ if (/^gAAAAA[A-Za-z0-9_-]+$/.test(prompt)) return "";
1204
+ return prompt;
635
1205
  }
636
1206
  function normalizePromptText(text) {
637
1207
  const trimmed = text.trim();
@@ -649,11 +1219,13 @@ function mergeTokenUsage2(existing, incoming) {
649
1219
  };
650
1220
  }
651
1221
  function parseTokenUsage(value) {
652
- if (!isObject(value)) return null;
653
- const inputTokens = typeof value.input_tokens === "number" ? value.input_tokens : 0;
1222
+ if (!isObject2(value)) return null;
1223
+ const reportedInputTokens = typeof value.input_tokens === "number" ? value.input_tokens : 0;
654
1224
  const outputTokens = typeof value.output_tokens === "number" ? value.output_tokens : 0;
655
- const cacheCreation = typeof value.cache_creation_input_tokens === "number" ? value.cache_creation_input_tokens : 0;
656
- const cacheRead = typeof value.cache_read_input_tokens === "number" ? value.cache_read_input_tokens : 0;
1225
+ 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;
1226
+ const nativeCachedInput = typeof value.cached_input_tokens === "number" ? Math.max(0, value.cached_input_tokens) : null;
1227
+ const cacheRead = nativeCachedInput ?? (typeof value.cache_read_input_tokens === "number" ? value.cache_read_input_tokens : 0);
1228
+ const inputTokens = nativeCachedInput === null ? reportedInputTokens : Math.max(0, reportedInputTokens - nativeCachedInput);
657
1229
  if (inputTokens === 0 && outputTokens === 0 && cacheCreation === 0 && cacheRead === 0) return null;
658
1230
  return {
659
1231
  input_tokens: inputTokens,
@@ -694,7 +1266,7 @@ function appendToolCall(turn, toolCall, timestamp) {
694
1266
  }
695
1267
  function finalizeTurn(turns, current, lastTimestamp) {
696
1268
  if (!current) return null;
697
- const hasContent = current.userMessage !== null || current.assistantText.length > 0 || current.toolCalls.length > 0 || current.thinking.length > 0;
1269
+ const hasContent = current.userMessage !== null || current.assistantText.length > 0 || current.toolCalls.length > 0 || current.thinking.length > 0 || current.subAgentActivity.length > 0;
698
1270
  if (!hasContent) return null;
699
1271
  if (current.timestamp && lastTimestamp) {
700
1272
  const start = new Date(current.timestamp).getTime();
@@ -710,17 +1282,11 @@ function parseToolInput(argumentsText) {
710
1282
  if (typeof argumentsText !== "string" || !argumentsText.trim()) return {};
711
1283
  try {
712
1284
  const parsed = JSON.parse(argumentsText);
713
- return isObject(parsed) ? parsed : { value: parsed };
1285
+ return isObject2(parsed) ? parsed : { value: parsed };
714
1286
  } catch {
715
1287
  return { raw: argumentsText };
716
1288
  }
717
1289
  }
718
- function inferToolError(output) {
719
- if (!output) return false;
720
- const exitMatch = output.match(/Process exited with code (\d+)/);
721
- if (exitMatch) return exitMatch[1] !== "0";
722
- return /\b(error|failed|exception)\b/i.test(output);
723
- }
724
1290
  function createTurn(turnId, timestamp, model) {
725
1291
  return {
726
1292
  id: turnId || randomTurnId("codex-turn"),
@@ -737,10 +1303,10 @@ function createTurn(turnId, timestamp, model) {
737
1303
  };
738
1304
  }
739
1305
  function extractPromptFromRecord(record) {
740
- if (record.type === "event_msg" && isObject(record.payload) && record.payload.type === "user_message" && typeof record.payload.message === "string") {
1306
+ if (record.type === "event_msg" && isObject2(record.payload) && record.payload.type === "user_message" && typeof record.payload.message === "string") {
741
1307
  return normalizePromptText(record.payload.message);
742
1308
  }
743
- if (record.type === "response_item" && isObject(record.payload) && record.payload.type === "message" && record.payload.role === "user") {
1309
+ if (record.type === "response_item" && isObject2(record.payload) && record.payload.type === "message" && record.payload.role === "user") {
744
1310
  return normalizePromptText(extractMessageText(record.payload, "input_text"));
745
1311
  }
746
1312
  return "";
@@ -757,14 +1323,17 @@ function extractMetadataFromRecords(records) {
757
1323
  let timestamp = "";
758
1324
  let lastTimestamp = "";
759
1325
  let turnCount = 0;
1326
+ let isSubagent = false;
1327
+ let parentSessionId = null;
1328
+ let agentPath = "/root";
760
1329
  let previousPrompt = "";
761
1330
  for (const record of records) {
762
- if (!isObject(record.payload)) continue;
1331
+ if (!isObject2(record.payload)) continue;
763
1332
  if (record.type === "session_meta") {
764
1333
  sessionId ||= typeof record.payload.id === "string" ? record.payload.id : "";
765
1334
  version ||= typeof record.payload.cli_version === "string" ? record.payload.cli_version : "";
766
1335
  cwd ||= typeof record.payload.cwd === "string" ? record.payload.cwd : "";
767
- if (!branchedFrom && isObject(record.payload.branchedFrom)) {
1336
+ if (!branchedFrom && isObject2(record.payload.branchedFrom)) {
768
1337
  const sourceId = typeof record.payload.branchedFrom.sessionId === "string" ? record.payload.branchedFrom.sessionId : "";
769
1338
  if (sourceId) {
770
1339
  branchedFrom = {
@@ -773,7 +1342,18 @@ function extractMetadataFromRecords(records) {
773
1342
  };
774
1343
  }
775
1344
  }
776
- const git = isObject(record.payload.git) ? record.payload.git : null;
1345
+ const source = isObject2(record.payload.source) ? record.payload.source : null;
1346
+ if (source && isObject2(source.subagent)) {
1347
+ isSubagent = true;
1348
+ const threadSpawn = isObject2(source.subagent.thread_spawn) ? source.subagent.thread_spawn : null;
1349
+ if (threadSpawn && typeof threadSpawn.agent_path === "string") {
1350
+ agentPath = threadSpawn.agent_path;
1351
+ }
1352
+ }
1353
+ if (typeof record.payload.forked_from_id === "string" && record.payload.forked_from_id) {
1354
+ parentSessionId = record.payload.forked_from_id;
1355
+ }
1356
+ const git = isObject2(record.payload.git) ? record.payload.git : null;
777
1357
  gitBranch ||= git && typeof git.branch === "string" ? git.branch : "";
778
1358
  }
779
1359
  if (record.type === "turn_context") {
@@ -798,12 +1378,16 @@ function extractMetadataFromRecords(records) {
798
1378
  cwd,
799
1379
  model,
800
1380
  slug: "",
1381
+ name: "",
801
1382
  branchedFrom,
802
1383
  firstUserMessage,
803
1384
  lastUserMessage,
804
1385
  timestamp,
805
1386
  lastTimestamp,
806
- turnCount
1387
+ turnCount,
1388
+ isSubagent,
1389
+ parentSessionId,
1390
+ agentPath
807
1391
  };
808
1392
  }
809
1393
  function isCodexSessionText(jsonlText) {
@@ -818,18 +1402,99 @@ function extractCodexMetadataFromLines(lines) {
818
1402
  const records = lines.map(safeParseLine).filter(isCodexRecord);
819
1403
  return extractMetadataFromRecords(records);
820
1404
  }
821
- function parseCodexSession(jsonlText) {
1405
+ function parseCodexSession(jsonlText, options) {
822
1406
  const records = jsonlText.split("\n").map((line) => line.trim()).filter(Boolean).map(safeParseLine).filter(isCodexRecord);
823
1407
  const metadata = extractMetadataFromRecords(records);
824
1408
  const turns = [];
825
1409
  const pendingToolCalls = /* @__PURE__ */ new Map();
1410
+ const webSearchCalls = /* @__PURE__ */ new Map();
1411
+ const patchCallIds = /* @__PURE__ */ new Map();
1412
+ const spawnAgentCalls = /* @__PURE__ */ new Map();
1413
+ const agentRegistry = /* @__PURE__ */ new Map();
1414
+ const agentIdByPath = /* @__PURE__ */ new Map();
1415
+ const agentIdBySpawnCall = /* @__PURE__ */ new Map();
1416
+ const agentMessages = /* @__PURE__ */ new Map();
1417
+ function upsertAgentMessage(agentId, targetTurn, eventTimestamp, update = {}) {
1418
+ const info = agentRegistry.get(agentId);
1419
+ let entry = agentMessages.get(agentId);
1420
+ if (!entry) {
1421
+ const agentMessage2 = {
1422
+ agentId,
1423
+ parentToolUseId: info?.parentToolCallId || void 0,
1424
+ agentName: info?.nickname ?? (info?.agentPath ? agentNameFromPath(info.agentPath) : null),
1425
+ subagentType: info?.agentType ?? null,
1426
+ type: "assistant",
1427
+ content: null,
1428
+ toolCalls: [],
1429
+ thinking: [],
1430
+ text: [],
1431
+ timestamp: info?.timestamp ?? eventTimestamp,
1432
+ tokenUsage: null,
1433
+ model: info?.model ?? null,
1434
+ isBackground: false,
1435
+ prompt: info?.message || void 0,
1436
+ status: update.status ?? "running"
1437
+ };
1438
+ targetTurn.subAgentActivity.push(agentMessage2);
1439
+ const lastBlock = targetTurn.contentBlocks[targetTurn.contentBlocks.length - 1];
1440
+ if (lastBlock?.kind === "sub_agent") {
1441
+ lastBlock.messages.push(agentMessage2);
1442
+ } else {
1443
+ targetTurn.contentBlocks.push({ kind: "sub_agent", messages: [agentMessage2], timestamp: eventTimestamp });
1444
+ }
1445
+ entry = { message: agentMessage2, turn: targetTurn };
1446
+ agentMessages.set(agentId, entry);
1447
+ }
1448
+ const agentMessage = entry.message;
1449
+ if (info) {
1450
+ agentMessage.parentToolUseId = info.parentToolCallId || agentMessage.parentToolUseId;
1451
+ agentMessage.agentName = info.nickname ?? (info.agentPath ? agentNameFromPath(info.agentPath) : agentMessage.agentName);
1452
+ agentMessage.subagentType = info.agentType;
1453
+ agentMessage.model = info.model;
1454
+ agentMessage.prompt = info.message || agentMessage.prompt;
1455
+ }
1456
+ if (update.text && !agentMessage.text.includes(update.text)) {
1457
+ agentMessage.text.push(update.text);
1458
+ agentMessage.content = agentMessage.text.join("\n\n");
1459
+ }
1460
+ if (update.status) {
1461
+ const terminalStatuses = /* @__PURE__ */ new Set(["completed", "failed", "interrupted"]);
1462
+ if (!(update.status === "running" && agentMessage.status && terminalStatuses.has(agentMessage.status))) {
1463
+ agentMessage.status = update.status;
1464
+ }
1465
+ if (terminalStatuses.has(update.status) && agentMessage.timestamp && eventTimestamp) {
1466
+ const startedAt = new Date(agentMessage.timestamp).getTime();
1467
+ const endedAt = new Date(eventTimestamp).getTime();
1468
+ if (Number.isFinite(startedAt) && Number.isFinite(endedAt) && endedAt >= startedAt) {
1469
+ agentMessage.durationMs = endedAt - startedAt;
1470
+ }
1471
+ }
1472
+ }
1473
+ return agentMessage;
1474
+ }
826
1475
  let current = null;
827
1476
  let currentTurnId = null;
828
1477
  let currentModel = metadata.model || null;
829
1478
  let lastTurnTimestamp = "";
1479
+ let pendingCompaction = null;
1480
+ function createActiveTurn(turnId, timestamp, model) {
1481
+ const turn = createTurn(turnId, timestamp, model);
1482
+ if (pendingCompaction) {
1483
+ turn.compactionSummary = pendingCompaction;
1484
+ pendingCompaction = null;
1485
+ }
1486
+ return turn;
1487
+ }
830
1488
  for (const record of records) {
831
- const payload = isObject(record.payload) ? record.payload : void 0;
1489
+ const payload = isObject2(record.payload) ? record.payload : void 0;
832
1490
  const timestamp = record.timestamp ?? "";
1491
+ if (record.type === "compacted") {
1492
+ current = finalizeTurn(turns, current, lastTurnTimestamp);
1493
+ pendingCompaction = "Conversation compacted";
1494
+ lastTurnTimestamp = timestamp;
1495
+ continue;
1496
+ }
1497
+ if (record.type === "world_state") continue;
833
1498
  if (record.type === "turn_context") {
834
1499
  current = finalizeTurn(turns, current, lastTurnTimestamp);
835
1500
  currentTurnId = typeof payload?.turn_id === "string" ? payload.turn_id : null;
@@ -841,24 +1506,93 @@ function parseCodexSession(jsonlText) {
841
1506
  if (current && (current.assistantText.length > 0 || current.toolCalls.length > 0 || current.thinking.length > 0)) {
842
1507
  current = finalizeTurn(turns, current, lastTurnTimestamp);
843
1508
  }
844
- current ??= createTurn(currentTurnId, timestamp, currentModel);
845
- current.userMessage = payload.message;
1509
+ current ??= createActiveTurn(currentTurnId, timestamp, currentModel);
1510
+ const localImages = (Array.isArray(payload.local_images) ? payload.local_images : []).filter(
1511
+ (p) => typeof p === "string" && p.length > 0
1512
+ );
1513
+ const localAudio = (Array.isArray(payload.local_audio) ? payload.local_audio : []).filter(
1514
+ (p) => typeof p === "string" && p.length > 0
1515
+ );
1516
+ current.userMessage = buildCodexUserContent(
1517
+ payload.message,
1518
+ extractEventMessageImages(payload),
1519
+ extractEventMessageAudio(payload),
1520
+ localImages,
1521
+ localAudio
1522
+ );
846
1523
  current.timestamp = current.timestamp || timestamp;
847
1524
  lastTurnTimestamp = timestamp;
848
1525
  continue;
849
1526
  }
850
- current ??= createTurn(currentTurnId, timestamp, currentModel);
1527
+ current ??= createActiveTurn(currentTurnId, timestamp, currentModel);
851
1528
  if (!current.model && currentModel) current.model = currentModel;
852
1529
  if (!current.timestamp) current.timestamp = timestamp;
853
1530
  if (timestamp) lastTurnTimestamp = timestamp;
854
1531
  if (record.type === "event_msg" && payload?.type === "token_count") {
855
- const info = isObject(payload.info) ? payload.info : null;
1532
+ const info = isObject2(payload.info) ? payload.info : null;
856
1533
  const lastUsage = info ? parseTokenUsage(info.last_token_usage) : null;
857
1534
  if (lastUsage) {
858
1535
  current.tokenUsage = mergeTokenUsage2(current.tokenUsage, lastUsage);
859
1536
  }
860
1537
  continue;
861
1538
  }
1539
+ if (record.type === "event_msg" && payload?.type === "web_search_end" && typeof payload.call_id === "string") {
1540
+ const input = {};
1541
+ if (typeof payload.query === "string" && payload.query) input.query = payload.query;
1542
+ if (isObject2(payload.action)) input.action = payload.action;
1543
+ const existing = webSearchCalls.get(payload.call_id);
1544
+ if (existing) {
1545
+ existing.input = { ...existing.input, ...input };
1546
+ existing.result = "completed";
1547
+ existing.isError = false;
1548
+ } else {
1549
+ const toolCall = {
1550
+ id: payload.call_id,
1551
+ name: "WebSearch",
1552
+ input,
1553
+ result: "completed",
1554
+ isError: false,
1555
+ timestamp
1556
+ };
1557
+ webSearchCalls.set(toolCall.id, toolCall);
1558
+ appendToolCall(current, toolCall, timestamp);
1559
+ }
1560
+ continue;
1561
+ }
1562
+ if (record.type === "event_msg" && payload?.type === "sub_agent_activity" && typeof payload.agent_thread_id === "string" && typeof payload.agent_path === "string") {
1563
+ const agentId = payload.agent_thread_id;
1564
+ const eventId = typeof payload.event_id === "string" ? payload.event_id : "";
1565
+ const kind = typeof payload.kind === "string" ? payload.kind : "started";
1566
+ const spawnInfo = eventId ? spawnAgentCalls.get(eventId) : void 0;
1567
+ const existingId = agentIdByPath.get(payload.agent_path);
1568
+ const existing = agentRegistry.get(agentId) ?? (existingId ? agentRegistry.get(existingId) : void 0);
1569
+ if (kind !== "started" && !existing && !spawnInfo) continue;
1570
+ if (existingId && existingId !== agentId && !agentMessages.has(agentId)) {
1571
+ const provisionalEntry = agentMessages.get(existingId);
1572
+ if (provisionalEntry) {
1573
+ provisionalEntry.message.agentId = agentId;
1574
+ agentMessages.delete(existingId);
1575
+ agentMessages.set(agentId, provisionalEntry);
1576
+ }
1577
+ agentRegistry.delete(existingId);
1578
+ }
1579
+ const info = {
1580
+ agentId,
1581
+ nickname: existing?.nickname ?? agentNameFromPath(payload.agent_path) ?? spawnInfo?.taskName ?? null,
1582
+ agentPath: payload.agent_path,
1583
+ message: existing?.message ?? spawnInfo?.message ?? "",
1584
+ model: existing?.model ?? spawnInfo?.model ?? null,
1585
+ agentType: existing?.agentType ?? spawnInfo?.agentType ?? null,
1586
+ timestamp: existing?.timestamp ?? (timestamp || spawnInfo?.timestamp || ""),
1587
+ parentToolCallId: existing?.parentToolCallId ?? eventId
1588
+ };
1589
+ agentRegistry.set(agentId, info);
1590
+ agentIdByPath.set(payload.agent_path, agentId);
1591
+ if (eventId && (kind === "started" || spawnInfo)) agentIdBySpawnCall.set(eventId, agentId);
1592
+ const status = kind === "interrupted" ? "interrupted" : "running";
1593
+ upsertAgentMessage(agentId, current, timestamp, { status });
1594
+ continue;
1595
+ }
862
1596
  if (record.type !== "response_item" || !payload) continue;
863
1597
  if (payload.type === "reasoning") {
864
1598
  const summary = Array.isArray(payload.summary) ? payload.summary.filter((item) => typeof item === "string").join("\n") : "";
@@ -871,14 +1605,95 @@ function parseCodexSession(jsonlText) {
871
1605
  }
872
1606
  if (payload.type === "message" && payload.role === "user" && current.userMessage === null) {
873
1607
  const text = normalizePromptText(extractMessageText(payload, "input_text"));
874
- if (text) current.userMessage = text;
1608
+ const images = extractResponseMessageImages(payload);
1609
+ const audio = extractResponseMessageAudio(payload);
1610
+ if (text || images.length > 0 || audio.length > 0) {
1611
+ current.userMessage = buildCodexUserContent(text, images, audio);
1612
+ }
1613
+ continue;
1614
+ }
1615
+ if (payload.type === "web_search_call" && typeof payload.id === "string") {
1616
+ const input = {};
1617
+ if (isObject2(payload.action)) input.action = payload.action;
1618
+ const status = typeof payload.status === "string" ? payload.status : "completed";
1619
+ const existing = webSearchCalls.get(payload.id);
1620
+ if (existing) {
1621
+ existing.input = { ...existing.input, ...input };
1622
+ existing.result = status;
1623
+ existing.isError = status === "failed";
1624
+ } else {
1625
+ const toolCall = {
1626
+ id: payload.id,
1627
+ name: "WebSearch",
1628
+ input,
1629
+ result: status,
1630
+ isError: status === "failed",
1631
+ timestamp
1632
+ };
1633
+ webSearchCalls.set(toolCall.id, toolCall);
1634
+ appendToolCall(current, toolCall, timestamp);
1635
+ }
1636
+ continue;
1637
+ }
1638
+ if (payload.type === "agent_message" && typeof payload.author === "string") {
1639
+ const author = payload.author;
1640
+ const recipient = typeof payload.recipient === "string" ? payload.recipient : null;
1641
+ const interAgentMessage = extractInterAgentMessage(payload);
1642
+ if (interAgentMessage.messageType === "NEW_TASK") continue;
1643
+ const knownAgentId = agentIdByPath.get(author);
1644
+ const isPotentialChild = author.startsWith(`${metadata.agentPath}/`) && (recipient === null || recipient === metadata.agentPath);
1645
+ if (!knownAgentId && !isPotentialChild) continue;
1646
+ const agentId = knownAgentId ?? author;
1647
+ if (!knownAgentId) {
1648
+ agentRegistry.set(agentId, {
1649
+ agentId,
1650
+ nickname: agentNameFromPath(author),
1651
+ agentPath: author,
1652
+ message: "",
1653
+ model: null,
1654
+ agentType: null,
1655
+ timestamp,
1656
+ parentToolCallId: ""
1657
+ });
1658
+ agentIdByPath.set(author, agentId);
1659
+ }
1660
+ let status;
1661
+ if (interAgentMessage.messageType === "FINAL_ANSWER") status = "completed";
1662
+ else if (interAgentMessage.messageType === "ERROR" || interAgentMessage.messageType === "FAILED") status = "failed";
1663
+ else if (interAgentMessage.messageType === "INTERRUPTED") status = "interrupted";
1664
+ else if (interAgentMessage.messageType === "MESSAGE") status = "running";
1665
+ upsertAgentMessage(agentId, current, timestamp, {
1666
+ status,
1667
+ text: interAgentMessage.text || void 0
1668
+ });
875
1669
  continue;
876
1670
  }
877
1671
  if (payload.type === "function_call" && typeof payload.call_id === "string") {
1672
+ const rawName = typeof payload.name === "string" ? payload.name : "tool";
1673
+ const functionName = normalizeFunctionName(rawName);
1674
+ const parsedInput = parseToolInput(payload.arguments);
1675
+ let name = functionName;
1676
+ let input = parsedInput;
1677
+ if (functionName === "exec_command") {
1678
+ name = "Bash";
1679
+ } else if (functionName === "update_plan") {
1680
+ name = "TodoWrite";
1681
+ input = normalizePlanToTodos(parsedInput);
1682
+ }
1683
+ if (functionName === "spawn_agent") {
1684
+ spawnAgentCalls.set(payload.call_id, {
1685
+ callId: payload.call_id,
1686
+ message: readableAgentPrompt(parsedInput.message),
1687
+ taskName: typeof parsedInput.task_name === "string" ? parsedInput.task_name : null,
1688
+ model: typeof parsedInput.model === "string" ? parsedInput.model : null,
1689
+ agentType: typeof parsedInput.agent_type === "string" ? parsedInput.agent_type : null,
1690
+ timestamp
1691
+ });
1692
+ }
878
1693
  const toolCall = {
879
1694
  id: payload.call_id,
880
- name: typeof payload.name === "string" ? payload.name : "tool",
881
- input: parseToolInput(payload.arguments),
1695
+ name,
1696
+ input,
882
1697
  result: null,
883
1698
  isError: false,
884
1699
  timestamp
@@ -894,6 +1709,116 @@ function parseCodexSession(jsonlText) {
894
1709
  toolCall.result = output;
895
1710
  toolCall.isError = inferToolError(output);
896
1711
  pendingToolCalls.delete(payload.call_id);
1712
+ if (toolCall.name === "spawn_agent" && output) {
1713
+ try {
1714
+ const result = JSON.parse(output);
1715
+ const agentId = typeof result.agent_id === "string" ? result.agent_id : agentIdBySpawnCall.get(toolCall.id) ?? "";
1716
+ const agentPath = typeof result.task_name === "string" ? result.task_name : null;
1717
+ const nickname = typeof result.nickname === "string" ? result.nickname : agentPath ? agentNameFromPath(agentPath) : null;
1718
+ if (agentId) {
1719
+ const spawnInfo = spawnAgentCalls.get(toolCall.id);
1720
+ const existing = agentRegistry.get(agentId);
1721
+ const info = {
1722
+ agentId,
1723
+ nickname: nickname ?? existing?.nickname ?? spawnInfo?.taskName ?? null,
1724
+ agentPath: agentPath ?? existing?.agentPath ?? null,
1725
+ message: existing?.message ?? spawnInfo?.message ?? "",
1726
+ model: existing?.model ?? spawnInfo?.model ?? null,
1727
+ agentType: existing?.agentType ?? spawnInfo?.agentType ?? null,
1728
+ timestamp: existing?.timestamp ?? spawnInfo?.timestamp ?? timestamp,
1729
+ parentToolCallId: existing?.parentToolCallId ?? toolCall.id
1730
+ };
1731
+ agentRegistry.set(agentId, info);
1732
+ agentIdBySpawnCall.set(toolCall.id, agentId);
1733
+ if (info.agentPath) agentIdByPath.set(info.agentPath, agentId);
1734
+ upsertAgentMessage(agentId, current, timestamp, { status: "running" });
1735
+ }
1736
+ } catch {
1737
+ }
1738
+ }
1739
+ if (toolCall.name === "wait_agent" && output && current) {
1740
+ try {
1741
+ const result = JSON.parse(output);
1742
+ const statusMap = isObject2(result.status) ? result.status : {};
1743
+ for (const [agentId, status] of Object.entries(statusMap)) {
1744
+ const statusValue = isObject2(status) ? status : {};
1745
+ const completedText = typeof statusValue.completed === "string" ? statusValue.completed : "";
1746
+ const failedText = typeof statusValue.failed === "string" ? statusValue.failed : typeof statusValue.error === "string" ? statusValue.error : "";
1747
+ const interruptedText = typeof statusValue.interrupted === "string" ? statusValue.interrupted : "";
1748
+ const lifecycle = completedText ? "completed" : failedText ? "failed" : interruptedText ? "interrupted" : typeof statusValue.status === "string" ? statusValue.status : "running";
1749
+ if (!agentRegistry.has(agentId)) {
1750
+ agentRegistry.set(agentId, {
1751
+ agentId,
1752
+ nickname: null,
1753
+ agentPath: null,
1754
+ message: "",
1755
+ model: null,
1756
+ agentType: null,
1757
+ timestamp,
1758
+ parentToolCallId: ""
1759
+ });
1760
+ }
1761
+ upsertAgentMessage(agentId, current, timestamp, {
1762
+ status: lifecycle,
1763
+ text: completedText || failedText || interruptedText || void 0
1764
+ });
1765
+ }
1766
+ } catch {
1767
+ }
1768
+ }
1769
+ continue;
1770
+ }
1771
+ if (payload.type === "custom_tool_call" && typeof payload.call_id === "string") {
1772
+ const name = typeof payload.name === "string" ? payload.name : "tool";
1773
+ const rawInput = typeof payload.input === "string" ? payload.input : "";
1774
+ const callId = payload.call_id;
1775
+ const perFileCalls = rawInput ? parseCodexToolPatches(name, rawInput, callId, timestamp, metadata.cwd) : [];
1776
+ if (name !== "apply_patch") {
1777
+ const toolCall = {
1778
+ id: callId,
1779
+ name: name === "exec_command" ? "Bash" : name,
1780
+ input: parseToolInput(rawInput),
1781
+ result: null,
1782
+ isError: false,
1783
+ timestamp
1784
+ };
1785
+ pendingToolCalls.set(toolCall.id, toolCall);
1786
+ appendToolCall(current, toolCall, timestamp);
1787
+ }
1788
+ for (const tc of perFileCalls) {
1789
+ pendingToolCalls.set(tc.id, tc);
1790
+ appendToolCall(current, tc, timestamp);
1791
+ }
1792
+ if (perFileCalls.length > 0) {
1793
+ patchCallIds.set(callId, {
1794
+ calls: perFileCalls,
1795
+ direct: name === "apply_patch"
1796
+ });
1797
+ }
1798
+ continue;
1799
+ }
1800
+ if (payload.type === "custom_tool_call_output" && typeof payload.call_id === "string") {
1801
+ const callId = payload.call_id;
1802
+ const { text, isError } = parseCustomToolOutput(payload.output);
1803
+ const patchCalls = patchCallIds.get(callId);
1804
+ if (patchCalls) {
1805
+ const failedIds = patchCalls.direct && isError ? new Set(patchCalls.calls.map((call) => call.id)) : findFailedNestedPatchCallIds(text, isError, patchCalls.calls);
1806
+ for (const patchCall of patchCalls.calls) {
1807
+ const tc = pendingToolCalls.get(patchCall.id);
1808
+ if (tc) {
1809
+ tc.result = text;
1810
+ tc.isError = failedIds.has(tc.id);
1811
+ pendingToolCalls.delete(tc.id);
1812
+ }
1813
+ }
1814
+ patchCallIds.delete(callId);
1815
+ }
1816
+ const toolCall = pendingToolCalls.get(callId);
1817
+ if (toolCall) {
1818
+ toolCall.result = text;
1819
+ toolCall.isError = isError;
1820
+ pendingToolCalls.delete(callId);
1821
+ }
897
1822
  continue;
898
1823
  }
899
1824
  }
@@ -904,19 +1829,17 @@ function parseCodexSession(jsonlText) {
904
1829
  gitBranch: metadata.gitBranch,
905
1830
  cwd: metadata.cwd,
906
1831
  slug: metadata.slug,
1832
+ name: "",
907
1833
  model: metadata.model,
908
1834
  turns,
909
- stats: computeStats(turns),
910
- rawMessages: records,
1835
+ stats: options?.skipStats ? createEmptySessionStats(turns.length) : computeStats(turns),
1836
+ rawMessages: options?.skipStats ? [] : records,
911
1837
  branchedFrom: metadata.branchedFrom,
912
1838
  agentKind: "codex"
913
1839
  };
914
1840
  }
915
1841
 
916
1842
  // src/lib/parser.ts
917
- function isAssistantMessage2(msg) {
918
- return msg.type === "assistant";
919
- }
920
1843
  function extractTextFromContent2(content) {
921
1844
  if (typeof content === "string") return content;
922
1845
  return content.filter((b) => b.type === "text").map((b) => b.text).join("\n");
@@ -934,36 +1857,37 @@ function parseLines(jsonlText) {
934
1857
  return messages;
935
1858
  }
936
1859
  function extractSessionMetadata(messages) {
937
- const meta = { sessionId: "", version: "", gitBranch: "", cwd: "", slug: "", model: "", branchedFrom: void 0 };
1860
+ const meta = { sessionId: "", version: "", gitBranch: "", cwd: "", slug: "", name: "", model: "", branchedFrom: void 0 };
938
1861
  for (const msg of messages) {
939
1862
  if (msg.sessionId && !meta.sessionId) meta.sessionId = msg.sessionId;
940
1863
  if (msg.version && !meta.version) meta.version = msg.version;
941
1864
  if (msg.gitBranch && !meta.gitBranch) meta.gitBranch = msg.gitBranch;
942
1865
  if (msg.cwd && !meta.cwd) meta.cwd = msg.cwd;
943
1866
  if (msg.slug && !meta.slug) meta.slug = msg.slug;
1867
+ if (msg.name && !meta.name) meta.name = msg.name;
944
1868
  if (msg.branchedFrom && !meta.branchedFrom) {
945
1869
  meta.branchedFrom = msg.branchedFrom;
946
1870
  }
947
- if (isAssistantMessage2(msg) && msg.message.model && !meta.model) {
1871
+ if (isAssistantMessage(msg) && msg.message.model && !meta.model) {
948
1872
  meta.model = msg.message.model;
949
1873
  }
950
1874
  if (meta.sessionId && meta.version && meta.gitBranch && meta.cwd && meta.model) break;
951
1875
  }
952
1876
  return meta;
953
1877
  }
954
- function parseSession(jsonlText, opts) {
1878
+ function parseSession(jsonlText, options) {
955
1879
  if (isCodexSessionText(jsonlText)) {
956
- return parseCodexSession(jsonlText);
1880
+ return parseCodexSession(jsonlText, options);
957
1881
  }
958
1882
  const rawMessages = parseLines(jsonlText);
959
1883
  const metadata = extractSessionMetadata(rawMessages);
960
1884
  const turns = buildTurns(rawMessages);
961
- const stats = opts?.skipStats ? { totalInputTokens: 0, totalOutputTokens: 0, totalCacheCreationTokens: 0, totalCacheReadTokens: 0, totalCostUSD: 0, toolCallCounts: {}, errorCount: 0, totalDurationMs: 0, turnCount: turns.length } : computeStats(turns);
1885
+ const stats = options?.skipStats ? createEmptySessionStats(turns.length) : computeStats(turns);
962
1886
  return {
963
1887
  ...metadata,
964
1888
  turns,
965
1889
  stats,
966
- rawMessages: opts?.skipStats ? [] : rawMessages,
1890
+ rawMessages: options?.skipStats ? [] : rawMessages,
967
1891
  agentKind: "claude"
968
1892
  };
969
1893
  }
@@ -1038,6 +1962,18 @@ var SearchIndex = class {
1038
1962
  lastUpdate: this._lastUpdate
1039
1963
  };
1040
1964
  }
1965
+ /** Index tool call inputs and results under the given location prefix. */
1966
+ insertToolCalls(insert, sessionId, filePath, prefix, toolCalls) {
1967
+ for (const tc of toolCalls) {
1968
+ const inputStr = JSON.stringify(tc.input);
1969
+ if (inputStr && inputStr !== "{}") {
1970
+ insert.run(sessionId, filePath, `${prefix}/toolCall/${tc.id}/input`, truncContent(inputStr));
1971
+ }
1972
+ if (tc.result) {
1973
+ insert.run(sessionId, filePath, `${prefix}/toolCall/${tc.id}/result`, truncContent(tc.result));
1974
+ }
1975
+ }
1976
+ }
1041
1977
  /**
1042
1978
  * Insert all searchable content from a parsed session into the FTS5 index.
1043
1979
  * Shared by both `indexFile` (single-file) and `buildFull` (batch).
@@ -1058,15 +1994,7 @@ var SearchIndex = class {
1058
1994
  if (thinkingText) {
1059
1995
  insert.run(sessionId, filePath, `${prefix}/thinking`, truncContent(thinkingText));
1060
1996
  }
1061
- for (const tc of turn.toolCalls) {
1062
- const inputStr = JSON.stringify(tc.input);
1063
- if (inputStr && inputStr !== "{}") {
1064
- insert.run(sessionId, filePath, `${prefix}/toolCall/${tc.id}/input`, truncContent(inputStr));
1065
- }
1066
- if (tc.result) {
1067
- insert.run(sessionId, filePath, `${prefix}/toolCall/${tc.id}/result`, truncContent(tc.result));
1068
- }
1069
- }
1997
+ this.insertToolCalls(insert, sessionId, filePath, prefix, turn.toolCalls);
1070
1998
  for (const sa of turn.subAgentActivity) {
1071
1999
  const saPrefix = `agent/${sa.agentId}`;
1072
2000
  const saText = sa.text.join("\n\n").trim();
@@ -1077,15 +2005,7 @@ var SearchIndex = class {
1077
2005
  if (saThinking) {
1078
2006
  insert.run(sessionId, filePath, `${saPrefix}/thinking`, truncContent(saThinking));
1079
2007
  }
1080
- for (const tc of sa.toolCalls) {
1081
- const inputStr = JSON.stringify(tc.input);
1082
- if (inputStr && inputStr !== "{}") {
1083
- insert.run(sessionId, filePath, `${saPrefix}/toolCall/${tc.id}/input`, truncContent(inputStr));
1084
- }
1085
- if (tc.result) {
1086
- insert.run(sessionId, filePath, `${saPrefix}/toolCall/${tc.id}/result`, truncContent(tc.result));
1087
- }
1088
- }
2008
+ this.insertToolCalls(insert, sessionId, filePath, saPrefix, sa.toolCalls);
1089
2009
  }
1090
2010
  if (turn.compactionSummary) {
1091
2011
  insert.run(sessionId, filePath, `${prefix}/compactionSummary`, truncContent(turn.compactionSummary));
@@ -1132,7 +2052,7 @@ var SearchIndex = class {
1132
2052
  /**
1133
2053
  * Query the FTS5 index and return structured search results.
1134
2054
  *
1135
- * - FTS5 trigram tokenizer is case-insensitive by default.
2055
+ * - FTS5 unicode61 tokenizer is case-insensitive by default.
1136
2056
  * - When `caseSensitive` is true, a post-filter checks the original query
1137
2057
  * against the snippet text (exact case match).
1138
2058
  * - When `maxAgeMs` is provided, only files whose mtime in `indexed_files`
@@ -1222,17 +2142,11 @@ var SearchIndex = class {
1222
2142
  buildFull(projectsDir) {
1223
2143
  this.projectsDir = projectsDir;
1224
2144
  this.db.close();
1225
- try {
1226
- (0, import_node_fs.unlinkSync)(this.dbPath);
1227
- } catch {
1228
- }
1229
- try {
1230
- (0, import_node_fs.unlinkSync)(this.dbPath + "-wal");
1231
- } catch {
1232
- }
1233
- try {
1234
- (0, import_node_fs.unlinkSync)(this.dbPath + "-shm");
1235
- } catch {
2145
+ for (const suffix of ["", "-wal", "-shm"]) {
2146
+ try {
2147
+ (0, import_node_fs.unlinkSync)(this.dbPath + suffix);
2148
+ } catch {
2149
+ }
1236
2150
  }
1237
2151
  this.db = new Database(this.dbPath);
1238
2152
  this.db.exec("PRAGMA journal_mode = WAL");
@@ -1303,9 +2217,10 @@ var SearchIndex = class {
1303
2217
  /**
1304
2218
  * Lightweight incremental update for CLI search paths.
1305
2219
  *
1306
- * Instead of stat-ing every file, only discovers files modified after the
1307
- * most recent mtime in the index. Caps re-indexing to `maxFiles` to prevent
1308
- * blocking on large backlogs (run `index rebuild` for a full catch-up).
2220
+ * Still walks and stats all files via `discoverFiles`, but skips DB lookups
2221
+ * for files with mtime <= the high-water mark that are already indexed.
2222
+ * Caps re-indexing to `maxFiles` to prevent blocking on large backlogs
2223
+ * (run `index rebuild` for a full catch-up).
1309
2224
  */
1310
2225
  updateRecent(projectsDir, maxFiles = 50) {
1311
2226
  this.projectsDir = projectsDir;
@@ -1503,7 +2418,8 @@ var import_node_os = require("node:os");
1503
2418
  var dirs = {
1504
2419
  PROJECTS_DIR: (0, import_node_path2.join)((0, import_node_os.homedir)(), ".claude", "projects"),
1505
2420
  TEAMS_DIR: (0, import_node_path2.join)((0, import_node_os.homedir)(), ".claude", "teams"),
1506
- TASKS_DIR: (0, import_node_path2.join)((0, import_node_os.homedir)(), ".claude", "tasks")
2421
+ TASKS_DIR: (0, import_node_path2.join)((0, import_node_os.homedir)(), ".claude", "tasks"),
2422
+ CODEX_SESSIONS_DIR: (0, import_node_path2.join)((0, import_node_os.homedir)(), ".codex", "sessions")
1507
2423
  };
1508
2424
  var DEFAULT_DB_PATH = (0, import_node_path2.join)((0, import_node_os.homedir)(), ".claude", "cogpit-memory", "search-index.db");
1509
2425
 
@@ -1970,6 +2886,8 @@ function extractUserMessageText(userMessage) {
1970
2886
  for (const block of userMessage) {
1971
2887
  if (block.type === "text") parts.push(block.text);
1972
2888
  else if (block.type === "image") parts.push("[image attached]");
2889
+ else if (block.type === "document") parts.push("[document attached]");
2890
+ else if (block.type === "audio") parts.push("[audio attached]");
1973
2891
  }
1974
2892
  return parts.length > 0 ? parts.join("\n") : null;
1975
2893
  }
@@ -2068,6 +2986,10 @@ function mapTurnToDetail(session, turnIndex) {
2068
2986
  durationMs: turn.durationMs
2069
2987
  };
2070
2988
  }
2989
+ function mapToolCall(tc) {
2990
+ const { result, resultTruncated } = truncateResult(tc.result);
2991
+ return { id: tc.id, name: tc.name, input: tc.input, result, resultTruncated, isError: tc.isError };
2992
+ }
2071
2993
  function mapContentBlock(block) {
2072
2994
  switch (block.kind) {
2073
2995
  case "thinking": {
@@ -2079,10 +3001,7 @@ function mapContentBlock(block) {
2079
3001
  case "tool_calls":
2080
3002
  return {
2081
3003
  kind: "tool_calls",
2082
- toolCalls: block.toolCalls.map((tc) => {
2083
- const { result, resultTruncated } = truncateResult(tc.result);
2084
- return { id: tc.id, name: tc.name, input: tc.input, result, resultTruncated, isError: tc.isError };
2085
- }),
3004
+ toolCalls: block.toolCalls.map(mapToolCall),
2086
3005
  timestamp: block.timestamp ?? null
2087
3006
  };
2088
3007
  case "sub_agent":
@@ -2092,6 +3011,21 @@ function mapContentBlock(block) {
2092
3011
  agents: block.messages.map(mapSubAgentDetail),
2093
3012
  timestamp: block.timestamp ?? null
2094
3013
  };
3014
+ case "queued_prompt":
3015
+ return { kind: "queued_prompt", content: block.content, timestamp: block.timestamp ?? null };
3016
+ case "hook_event":
3017
+ return { kind: "hook_event", events: block.events, timestamp: block.timestamp ?? null };
3018
+ case "plan_mode":
3019
+ return {
3020
+ kind: "plan_mode",
3021
+ plan: block.plan,
3022
+ planFilePath: block.planFilePath ?? null,
3023
+ status: block.status,
3024
+ toolCalls: block.toolCalls.map(mapToolCall),
3025
+ timestamp: block.timestamp ?? null
3026
+ };
3027
+ case "recap":
3028
+ return { kind: "recap", content: block.content, timestamp: block.timestamp ?? null };
2095
3029
  }
2096
3030
  }
2097
3031
  async function findSubagentFile(parentJsonlPath, agentId) {
@@ -2214,7 +3148,6 @@ async function getAgentOverview(sessionId, agentId) {
2214
3148
  async function getAgentTurnDetail(sessionId, agentId, turnIndex) {
2215
3149
  const jsonlPath = await findJsonlPath(sessionId);
2216
3150
  if (!jsonlPath) return { error: "Session not found" };
2217
- const content = await (0, import_promises3.readFile)(jsonlPath, "utf-8");
2218
3151
  const subagentFile = await findSubagentFile(jsonlPath, agentId);
2219
3152
  if (!subagentFile) return { error: "Agent not found" };
2220
3153
  const subagentContent = await (0, import_promises3.readFile)(subagentFile.filePath, "utf-8");
@@ -2226,7 +3159,6 @@ async function getAgentTurnDetail(sessionId, agentId, turnIndex) {
2226
3159
  // src/commands/sessions.ts
2227
3160
  var import_promises5 = require("node:fs/promises");
2228
3161
  var import_node_path6 = require("node:path");
2229
- var import_node_os3 = require("node:os");
2230
3162
 
2231
3163
  // src/lib/metadata.ts
2232
3164
  var import_promises4 = require("node:fs/promises");
@@ -2243,6 +3175,19 @@ function deriveSessionStatus(rawMessages) {
2243
3175
  if (toolName) info.toolName = toolName;
2244
3176
  return info;
2245
3177
  }
3178
+ for (let i = rawMessages.length - 1; i >= 0; i--) {
3179
+ const msg = rawMessages[i];
3180
+ if (msg.type === "queue-operation") continue;
3181
+ if (msg.type === "progress") {
3182
+ const data = msg.data;
3183
+ if (data?.type === "hook_progress") {
3184
+ const decision = data.decision ?? data.hookSpecificOutput?.permissionDecision;
3185
+ if (decision === "defer") return result("deferred");
3186
+ }
3187
+ break;
3188
+ }
3189
+ break;
3190
+ }
2246
3191
  for (let i = rawMessages.length - 1; i >= 0; i--) {
2247
3192
  const msg = rawMessages[i];
2248
3193
  if (msg.type === "queue-operation") {
@@ -2277,6 +3222,13 @@ function deriveSessionStatus(rawMessages) {
2277
3222
  if (isMeta) continue;
2278
3223
  return result("processing");
2279
3224
  }
3225
+ if (msg.type === "system" && msg.subtype === "terminal_reason") {
3226
+ const reason = msg.reason;
3227
+ if (reason) {
3228
+ return { status: "completed", terminalReason: reason, pendingQueue: Math.max(0, pendingEnqueues) };
3229
+ }
3230
+ continue;
3231
+ }
2280
3232
  if (msg.type === "summary") continue;
2281
3233
  if (msg.type === "system" && msg.subtype === "compact_boundary") continue;
2282
3234
  }
@@ -2502,7 +3454,23 @@ async function getSessionStatus(filePath) {
2502
3454
  }
2503
3455
 
2504
3456
  // src/commands/sessions.ts
2505
- var CODEX_SESSIONS_DIR = (0, import_node_path6.join)((0, import_node_os3.homedir)(), ".codex", "sessions");
3457
+ function toSessionSummary(file, meta, status) {
3458
+ return {
3459
+ sessionId: meta.sessionId,
3460
+ filePath: file.path,
3461
+ timestamp: meta.timestamp,
3462
+ model: meta.model,
3463
+ cwd: meta.cwd,
3464
+ gitBranch: meta.gitBranch,
3465
+ slug: meta.slug,
3466
+ firstMessage: meta.firstUserMessage,
3467
+ lastMessage: meta.lastUserMessage,
3468
+ turnCount: meta.turnCount,
3469
+ status,
3470
+ mtime: file.mtimeMs,
3471
+ source: file.path.startsWith(dirs.CODEX_SESSIONS_DIR + "/") ? "codex" : "claude"
3472
+ };
3473
+ }
2506
3474
  async function listCodexSessionFiles(cutoff) {
2507
3475
  const walk = async (dir, depth) => {
2508
3476
  if (depth > 4) return [];
@@ -2529,7 +3497,7 @@ async function listCodexSessionFiles(cutoff) {
2529
3497
  }
2530
3498
  return results;
2531
3499
  };
2532
- return walk(CODEX_SESSIONS_DIR, 0);
3500
+ return walk(dirs.CODEX_SESSIONS_DIR, 0);
2533
3501
  }
2534
3502
  async function listSessions(opts = {}) {
2535
3503
  const limit = Math.min(Math.max(1, opts.limit ?? 20), 100);
@@ -2542,29 +3510,31 @@ async function listSessions(opts = {}) {
2542
3510
  return [];
2543
3511
  }
2544
3512
  const projectDirs = entries.filter((e) => e.isDirectory() && e.name !== "memory").map((e) => (0, import_node_path6.join)(dirs.PROJECTS_DIR, e.name));
2545
- const nested = await Promise.all(
2546
- projectDirs.map(async (projectDir) => {
2547
- try {
2548
- const files = await (0, import_promises5.readdir)(projectDir);
2549
- const jsonlFiles = files.filter((f) => f.endsWith(".jsonl"));
2550
- const statResults = await Promise.all(
2551
- jsonlFiles.map(async (f) => {
2552
- const filePath = (0, import_node_path6.join)(projectDir, f);
2553
- try {
2554
- const s = await (0, import_promises5.stat)(filePath);
2555
- return s.mtimeMs >= cutoff ? { path: filePath, mtimeMs: s.mtimeMs } : null;
2556
- } catch {
2557
- return null;
2558
- }
2559
- })
2560
- );
2561
- return statResults.filter((r) => r !== null);
2562
- } catch {
2563
- return [];
2564
- }
2565
- })
2566
- );
2567
- const codexFiles = await listCodexSessionFiles(cutoff);
3513
+ const [nested, codexFiles] = await Promise.all([
3514
+ Promise.all(
3515
+ projectDirs.map(async (projectDir) => {
3516
+ try {
3517
+ const files = await (0, import_promises5.readdir)(projectDir);
3518
+ const jsonlFiles = files.filter((f) => f.endsWith(".jsonl"));
3519
+ const statResults = await Promise.all(
3520
+ jsonlFiles.map(async (f) => {
3521
+ const filePath = (0, import_node_path6.join)(projectDir, f);
3522
+ try {
3523
+ const s = await (0, import_promises5.stat)(filePath);
3524
+ return s.mtimeMs >= cutoff ? { path: filePath, mtimeMs: s.mtimeMs } : null;
3525
+ } catch {
3526
+ return null;
3527
+ }
3528
+ })
3529
+ );
3530
+ return statResults.filter((r) => r !== null);
3531
+ } catch {
3532
+ return [];
3533
+ }
3534
+ })
3535
+ ),
3536
+ listCodexSessionFiles(cutoff)
3537
+ ]);
2568
3538
  const allFiles = [...nested.flat(), ...codexFiles];
2569
3539
  allFiles.sort((a, b) => b.mtimeMs - a.mtimeMs);
2570
3540
  const results = [];
@@ -2576,21 +3546,7 @@ async function listSessions(opts = {}) {
2576
3546
  getSessionStatus(file.path)
2577
3547
  ]);
2578
3548
  if (opts.cwd && meta.cwd !== opts.cwd) continue;
2579
- results.push({
2580
- sessionId: meta.sessionId,
2581
- filePath: file.path,
2582
- timestamp: meta.timestamp,
2583
- model: meta.model,
2584
- cwd: meta.cwd,
2585
- gitBranch: meta.gitBranch,
2586
- slug: meta.slug,
2587
- firstMessage: meta.firstUserMessage,
2588
- lastMessage: meta.lastUserMessage,
2589
- turnCount: meta.turnCount,
2590
- status: statusInfo.status,
2591
- mtime: file.mtimeMs,
2592
- source: file.path.startsWith(CODEX_SESSIONS_DIR + "/") ? "codex" : "claude"
2593
- });
3549
+ results.push(toSessionSummary(file, meta, statusInfo.status));
2594
3550
  } catch {
2595
3551
  }
2596
3552
  }
@@ -2606,18 +3562,20 @@ async function currentSession(cwd) {
2606
3562
  files = [];
2607
3563
  }
2608
3564
  const jsonlFiles = files.filter((f) => f.endsWith(".jsonl"));
2609
- const statResults = await Promise.all(
2610
- jsonlFiles.map(async (f) => {
2611
- const filePath = (0, import_node_path6.join)(projectDir, f);
2612
- try {
2613
- const s = await (0, import_promises5.stat)(filePath);
2614
- return { path: filePath, mtimeMs: s.mtimeMs };
2615
- } catch {
2616
- return null;
2617
- }
2618
- })
2619
- );
2620
- const codexCandidates = await listCodexSessionFiles(0);
3565
+ const [statResults, codexCandidates] = await Promise.all([
3566
+ Promise.all(
3567
+ jsonlFiles.map(async (f) => {
3568
+ const filePath = (0, import_node_path6.join)(projectDir, f);
3569
+ try {
3570
+ const s = await (0, import_promises5.stat)(filePath);
3571
+ return { path: filePath, mtimeMs: s.mtimeMs };
3572
+ } catch {
3573
+ return null;
3574
+ }
3575
+ })
3576
+ ),
3577
+ listCodexSessionFiles(0)
3578
+ ]);
2621
3579
  const codexMatches = [];
2622
3580
  for (const file of codexCandidates) {
2623
3581
  try {
@@ -2639,21 +3597,7 @@ async function currentSession(cwd) {
2639
3597
  getSessionMeta(latest.path),
2640
3598
  getSessionStatus(latest.path)
2641
3599
  ]);
2642
- return {
2643
- sessionId: meta.sessionId,
2644
- filePath: latest.path,
2645
- timestamp: meta.timestamp,
2646
- model: meta.model,
2647
- cwd: meta.cwd,
2648
- gitBranch: meta.gitBranch,
2649
- slug: meta.slug,
2650
- firstMessage: meta.firstUserMessage,
2651
- lastMessage: meta.lastUserMessage,
2652
- turnCount: meta.turnCount,
2653
- status: statusInfo.status,
2654
- mtime: latest.mtimeMs,
2655
- source: latest.path.startsWith(CODEX_SESSIONS_DIR + "/") ? "codex" : "claude"
2656
- };
3600
+ return toSessionSummary(latest, meta, statusInfo.status);
2657
3601
  }
2658
3602
 
2659
3603
  // src/commands/index-cmd.ts
@@ -2810,27 +3754,29 @@ async function main() {
2810
3754
  const cmd = parseArgs(argv);
2811
3755
  let result;
2812
3756
  switch (cmd.command) {
2813
- case "search":
3757
+ case "search": {
2814
3758
  if (!cmd.args.query) {
2815
3759
  console.error(JSON.stringify({ error: "Usage: cogpit-memory search <query>" }));
2816
3760
  process.exit(1);
2817
3761
  }
2818
- result = await searchSessions(cmd.args.query, {
3762
+ const searchResult = await searchSessions(cmd.args.query, {
2819
3763
  sessionId: cmd.args.session,
2820
3764
  maxAge: cmd.args.maxAge,
2821
3765
  limit: cmd.args.limit,
2822
3766
  caseSensitive: cmd.args.caseSensitive
2823
3767
  });
2824
- if (result && !("error" in result) && (cmd.args.sessionLimit || cmd.args.hitsPerSession)) {
2825
- const sl = cmd.args.sessionLimit ?? result.results.length;
3768
+ if ("results" in searchResult && (cmd.args.sessionLimit || cmd.args.hitsPerSession)) {
3769
+ const sl = cmd.args.sessionLimit ?? searchResult.results.length;
2826
3770
  const hpp = cmd.args.hitsPerSession ?? Infinity;
2827
- result.results = result.results.slice(0, sl).map((sr) => ({
3771
+ searchResult.results = searchResult.results.slice(0, sl).map((sr) => ({
2828
3772
  ...sr,
2829
3773
  hits: sr.hits.slice(0, hpp)
2830
3774
  }));
2831
- result.returnedHits = result.results.reduce((n, sr) => n + sr.hits.length, 0);
3775
+ searchResult.returnedHits = searchResult.results.reduce((n, sr) => n + sr.hits.length, 0);
2832
3776
  }
3777
+ result = searchResult;
2833
3778
  break;
3779
+ }
2834
3780
  case "context":
2835
3781
  if (!cmd.args.sessionId) {
2836
3782
  console.error(JSON.stringify({ error: "Usage: cogpit-memory context <sessionId>" }));