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/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,49 +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 emptyStats(turnCount) {
955
- return {
956
- totalInputTokens: 0,
957
- totalOutputTokens: 0,
958
- totalCacheCreationTokens: 0,
959
- totalCacheReadTokens: 0,
960
- totalCostUSD: 0,
961
- toolCallCounts: {},
962
- errorCount: 0,
963
- totalDurationMs: 0,
964
- turnCount
965
- };
966
- }
967
- function parseSession(jsonlText, opts) {
1878
+ function parseSession(jsonlText, options) {
968
1879
  if (isCodexSessionText(jsonlText)) {
969
- return parseCodexSession(jsonlText);
1880
+ return parseCodexSession(jsonlText, options);
970
1881
  }
971
1882
  const rawMessages = parseLines(jsonlText);
972
1883
  const metadata = extractSessionMetadata(rawMessages);
973
1884
  const turns = buildTurns(rawMessages);
974
- const stats = opts?.skipStats ? emptyStats(turns.length) : computeStats(turns);
1885
+ const stats = options?.skipStats ? createEmptySessionStats(turns.length) : computeStats(turns);
975
1886
  return {
976
1887
  ...metadata,
977
1888
  turns,
978
1889
  stats,
979
- rawMessages: opts?.skipStats ? [] : rawMessages,
1890
+ rawMessages: options?.skipStats ? [] : rawMessages,
980
1891
  agentKind: "claude"
981
1892
  };
982
1893
  }
@@ -1231,17 +2142,11 @@ var SearchIndex = class {
1231
2142
  buildFull(projectsDir) {
1232
2143
  this.projectsDir = projectsDir;
1233
2144
  this.db.close();
1234
- try {
1235
- (0, import_node_fs.unlinkSync)(this.dbPath);
1236
- } catch {
1237
- }
1238
- try {
1239
- (0, import_node_fs.unlinkSync)(this.dbPath + "-wal");
1240
- } catch {
1241
- }
1242
- try {
1243
- (0, import_node_fs.unlinkSync)(this.dbPath + "-shm");
1244
- } catch {
2145
+ for (const suffix of ["", "-wal", "-shm"]) {
2146
+ try {
2147
+ (0, import_node_fs.unlinkSync)(this.dbPath + suffix);
2148
+ } catch {
2149
+ }
1245
2150
  }
1246
2151
  this.db = new Database(this.dbPath);
1247
2152
  this.db.exec("PRAGMA journal_mode = WAL");
@@ -1513,7 +2418,8 @@ var import_node_os = require("node:os");
1513
2418
  var dirs = {
1514
2419
  PROJECTS_DIR: (0, import_node_path2.join)((0, import_node_os.homedir)(), ".claude", "projects"),
1515
2420
  TEAMS_DIR: (0, import_node_path2.join)((0, import_node_os.homedir)(), ".claude", "teams"),
1516
- 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")
1517
2423
  };
1518
2424
  var DEFAULT_DB_PATH = (0, import_node_path2.join)((0, import_node_os.homedir)(), ".claude", "cogpit-memory", "search-index.db");
1519
2425
 
@@ -1980,6 +2886,8 @@ function extractUserMessageText(userMessage) {
1980
2886
  for (const block of userMessage) {
1981
2887
  if (block.type === "text") parts.push(block.text);
1982
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]");
1983
2891
  }
1984
2892
  return parts.length > 0 ? parts.join("\n") : null;
1985
2893
  }
@@ -2078,6 +2986,10 @@ function mapTurnToDetail(session, turnIndex) {
2078
2986
  durationMs: turn.durationMs
2079
2987
  };
2080
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
+ }
2081
2993
  function mapContentBlock(block) {
2082
2994
  switch (block.kind) {
2083
2995
  case "thinking": {
@@ -2089,10 +3001,7 @@ function mapContentBlock(block) {
2089
3001
  case "tool_calls":
2090
3002
  return {
2091
3003
  kind: "tool_calls",
2092
- toolCalls: block.toolCalls.map((tc) => {
2093
- const { result, resultTruncated } = truncateResult(tc.result);
2094
- return { id: tc.id, name: tc.name, input: tc.input, result, resultTruncated, isError: tc.isError };
2095
- }),
3004
+ toolCalls: block.toolCalls.map(mapToolCall),
2096
3005
  timestamp: block.timestamp ?? null
2097
3006
  };
2098
3007
  case "sub_agent":
@@ -2102,6 +3011,21 @@ function mapContentBlock(block) {
2102
3011
  agents: block.messages.map(mapSubAgentDetail),
2103
3012
  timestamp: block.timestamp ?? null
2104
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 };
2105
3029
  }
2106
3030
  }
2107
3031
  async function findSubagentFile(parentJsonlPath, agentId) {
@@ -2224,7 +3148,6 @@ async function getAgentOverview(sessionId, agentId) {
2224
3148
  async function getAgentTurnDetail(sessionId, agentId, turnIndex) {
2225
3149
  const jsonlPath = await findJsonlPath(sessionId);
2226
3150
  if (!jsonlPath) return { error: "Session not found" };
2227
- const content = await (0, import_promises3.readFile)(jsonlPath, "utf-8");
2228
3151
  const subagentFile = await findSubagentFile(jsonlPath, agentId);
2229
3152
  if (!subagentFile) return { error: "Agent not found" };
2230
3153
  const subagentContent = await (0, import_promises3.readFile)(subagentFile.filePath, "utf-8");
@@ -2236,7 +3159,6 @@ async function getAgentTurnDetail(sessionId, agentId, turnIndex) {
2236
3159
  // src/commands/sessions.ts
2237
3160
  var import_promises5 = require("node:fs/promises");
2238
3161
  var import_node_path6 = require("node:path");
2239
- var import_node_os3 = require("node:os");
2240
3162
 
2241
3163
  // src/lib/metadata.ts
2242
3164
  var import_promises4 = require("node:fs/promises");
@@ -2253,6 +3175,19 @@ function deriveSessionStatus(rawMessages) {
2253
3175
  if (toolName) info.toolName = toolName;
2254
3176
  return info;
2255
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
+ }
2256
3191
  for (let i = rawMessages.length - 1; i >= 0; i--) {
2257
3192
  const msg = rawMessages[i];
2258
3193
  if (msg.type === "queue-operation") {
@@ -2287,6 +3222,13 @@ function deriveSessionStatus(rawMessages) {
2287
3222
  if (isMeta) continue;
2288
3223
  return result("processing");
2289
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
+ }
2290
3232
  if (msg.type === "summary") continue;
2291
3233
  if (msg.type === "system" && msg.subtype === "compact_boundary") continue;
2292
3234
  }
@@ -2512,7 +3454,23 @@ async function getSessionStatus(filePath) {
2512
3454
  }
2513
3455
 
2514
3456
  // src/commands/sessions.ts
2515
- 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
+ }
2516
3474
  async function listCodexSessionFiles(cutoff) {
2517
3475
  const walk = async (dir, depth) => {
2518
3476
  if (depth > 4) return [];
@@ -2539,7 +3497,7 @@ async function listCodexSessionFiles(cutoff) {
2539
3497
  }
2540
3498
  return results;
2541
3499
  };
2542
- return walk(CODEX_SESSIONS_DIR, 0);
3500
+ return walk(dirs.CODEX_SESSIONS_DIR, 0);
2543
3501
  }
2544
3502
  async function listSessions(opts = {}) {
2545
3503
  const limit = Math.min(Math.max(1, opts.limit ?? 20), 100);
@@ -2552,29 +3510,31 @@ async function listSessions(opts = {}) {
2552
3510
  return [];
2553
3511
  }
2554
3512
  const projectDirs = entries.filter((e) => e.isDirectory() && e.name !== "memory").map((e) => (0, import_node_path6.join)(dirs.PROJECTS_DIR, e.name));
2555
- const nested = await Promise.all(
2556
- projectDirs.map(async (projectDir) => {
2557
- try {
2558
- const files = await (0, import_promises5.readdir)(projectDir);
2559
- const jsonlFiles = files.filter((f) => f.endsWith(".jsonl"));
2560
- const statResults = await Promise.all(
2561
- jsonlFiles.map(async (f) => {
2562
- const filePath = (0, import_node_path6.join)(projectDir, f);
2563
- try {
2564
- const s = await (0, import_promises5.stat)(filePath);
2565
- return s.mtimeMs >= cutoff ? { path: filePath, mtimeMs: s.mtimeMs } : null;
2566
- } catch {
2567
- return null;
2568
- }
2569
- })
2570
- );
2571
- return statResults.filter((r) => r !== null);
2572
- } catch {
2573
- return [];
2574
- }
2575
- })
2576
- );
2577
- 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
+ ]);
2578
3538
  const allFiles = [...nested.flat(), ...codexFiles];
2579
3539
  allFiles.sort((a, b) => b.mtimeMs - a.mtimeMs);
2580
3540
  const results = [];
@@ -2586,21 +3546,7 @@ async function listSessions(opts = {}) {
2586
3546
  getSessionStatus(file.path)
2587
3547
  ]);
2588
3548
  if (opts.cwd && meta.cwd !== opts.cwd) continue;
2589
- results.push({
2590
- sessionId: meta.sessionId,
2591
- filePath: file.path,
2592
- timestamp: meta.timestamp,
2593
- model: meta.model,
2594
- cwd: meta.cwd,
2595
- gitBranch: meta.gitBranch,
2596
- slug: meta.slug,
2597
- firstMessage: meta.firstUserMessage,
2598
- lastMessage: meta.lastUserMessage,
2599
- turnCount: meta.turnCount,
2600
- status: statusInfo.status,
2601
- mtime: file.mtimeMs,
2602
- source: file.path.startsWith(CODEX_SESSIONS_DIR + "/") ? "codex" : "claude"
2603
- });
3549
+ results.push(toSessionSummary(file, meta, statusInfo.status));
2604
3550
  } catch {
2605
3551
  }
2606
3552
  }
@@ -2616,18 +3562,20 @@ async function currentSession(cwd) {
2616
3562
  files = [];
2617
3563
  }
2618
3564
  const jsonlFiles = files.filter((f) => f.endsWith(".jsonl"));
2619
- const statResults = await Promise.all(
2620
- jsonlFiles.map(async (f) => {
2621
- const filePath = (0, import_node_path6.join)(projectDir, f);
2622
- try {
2623
- const s = await (0, import_promises5.stat)(filePath);
2624
- return { path: filePath, mtimeMs: s.mtimeMs };
2625
- } catch {
2626
- return null;
2627
- }
2628
- })
2629
- );
2630
- 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
+ ]);
2631
3579
  const codexMatches = [];
2632
3580
  for (const file of codexCandidates) {
2633
3581
  try {
@@ -2649,21 +3597,7 @@ async function currentSession(cwd) {
2649
3597
  getSessionMeta(latest.path),
2650
3598
  getSessionStatus(latest.path)
2651
3599
  ]);
2652
- return {
2653
- sessionId: meta.sessionId,
2654
- filePath: latest.path,
2655
- timestamp: meta.timestamp,
2656
- model: meta.model,
2657
- cwd: meta.cwd,
2658
- gitBranch: meta.gitBranch,
2659
- slug: meta.slug,
2660
- firstMessage: meta.firstUserMessage,
2661
- lastMessage: meta.lastUserMessage,
2662
- turnCount: meta.turnCount,
2663
- status: statusInfo.status,
2664
- mtime: latest.mtimeMs,
2665
- source: latest.path.startsWith(CODEX_SESSIONS_DIR + "/") ? "codex" : "claude"
2666
- };
3600
+ return toSessionSummary(latest, meta, statusInfo.status);
2667
3601
  }
2668
3602
 
2669
3603
  // src/commands/index-cmd.ts
@@ -2820,27 +3754,29 @@ async function main() {
2820
3754
  const cmd = parseArgs(argv);
2821
3755
  let result;
2822
3756
  switch (cmd.command) {
2823
- case "search":
3757
+ case "search": {
2824
3758
  if (!cmd.args.query) {
2825
3759
  console.error(JSON.stringify({ error: "Usage: cogpit-memory search <query>" }));
2826
3760
  process.exit(1);
2827
3761
  }
2828
- result = await searchSessions(cmd.args.query, {
3762
+ const searchResult = await searchSessions(cmd.args.query, {
2829
3763
  sessionId: cmd.args.session,
2830
3764
  maxAge: cmd.args.maxAge,
2831
3765
  limit: cmd.args.limit,
2832
3766
  caseSensitive: cmd.args.caseSensitive
2833
3767
  });
2834
- if (result && !("error" in result) && (cmd.args.sessionLimit || cmd.args.hitsPerSession)) {
2835
- 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;
2836
3770
  const hpp = cmd.args.hitsPerSession ?? Infinity;
2837
- result.results = result.results.slice(0, sl).map((sr) => ({
3771
+ searchResult.results = searchResult.results.slice(0, sl).map((sr) => ({
2838
3772
  ...sr,
2839
3773
  hits: sr.hits.slice(0, hpp)
2840
3774
  }));
2841
- 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);
2842
3776
  }
3777
+ result = searchResult;
2843
3778
  break;
3779
+ }
2844
3780
  case "context":
2845
3781
  if (!cmd.args.sessionId) {
2846
3782
  console.error(JSON.stringify({ error: "Usage: cogpit-memory context <sessionId>" }));