cogpit-memory 0.1.9 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/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,45 @@ 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
+ }
70
+ function isAttachmentMessage(msg) {
71
+ return msg.type === "attachment";
72
+ }
73
+ function isCompactSummaryMessage(msg) {
74
+ return msg.type === "user" && msg.isCompactSummary === true;
75
+ }
67
76
  function isCompactBoundary(msg) {
68
77
  return msg.type === "system" && msg.subtype === "compact_boundary";
69
78
  }
79
+
80
+ // src/lib/agentEnvelope.ts
81
+ var ENVELOPE_RE = /<(agent-message|teammate-message)([^>]*)>([\s\S]*?)<\/\1>/g;
82
+ var SENDER_PATTERNS = [
83
+ /\bteammate_id=(?:"([^"]*)"|'([^']*)')/,
84
+ /\bfrom=(?:"([^"]*)"|'([^']*)')/
85
+ ];
86
+ function readSender(attrs) {
87
+ for (const pattern of SENDER_PATTERNS) {
88
+ const m = attrs.match(pattern);
89
+ const value = m?.[1] ?? m?.[2];
90
+ if (value) return value;
91
+ }
92
+ return null;
93
+ }
94
+ function parseAgentEnvelope(text) {
95
+ let sender = null;
96
+ let matched = false;
97
+ const unwrapped = text.replace(ENVELOPE_RE, (_full, _tag, attrs, inner) => {
98
+ matched = true;
99
+ if (sender === null) sender = readSender(attrs);
100
+ return inner.trim();
101
+ });
102
+ return { sender, matched, body: matched ? unwrapped.trim() : text };
103
+ }
104
+
105
+ // src/lib/turnBuilder.ts
70
106
  function extractTextFromContent(content) {
71
107
  if (typeof content === "string") return content;
72
108
  return content.filter((b) => b.type === "text").map((b) => b.text).join("\n");
@@ -79,6 +115,62 @@ function extractToolResultText(content) {
79
115
  return "";
80
116
  }).filter(Boolean).join("\n");
81
117
  }
118
+ function isVisibleQueuedPrompt(content) {
119
+ if (!content?.trim()) return false;
120
+ const trimmed = content.trimStart();
121
+ if (!trimmed.startsWith("<")) return true;
122
+ return !trimmed.includes("<task-notification>") && !trimmed.startsWith("<system-reminder>") && !trimmed.startsWith("<local-command-");
123
+ }
124
+ var TASK_NOTIFICATION_BLOCK_RE = /<task-notification>[\s\S]*?<\/task-notification>/g;
125
+ var ANY_TAG_RE = /<[^>]*>/g;
126
+ function isTaskNotificationRecord(msg) {
127
+ if (msg.origin?.kind === "task-notification") return true;
128
+ const text = extractTextFromContent(msg.message.content);
129
+ if (!text.includes("<task-notification>")) return false;
130
+ return text.replace(TASK_NOTIFICATION_BLOCK_RE, "").replace(ANY_TAG_RE, "").trim() === "";
131
+ }
132
+ function queuedCommandPrompt(msg) {
133
+ if (!isAttachmentMessage(msg)) return null;
134
+ const attachment = msg.attachment;
135
+ if (!attachment || attachment.type !== "queued_command") return null;
136
+ if (attachment.commandMode !== "prompt") return null;
137
+ const prompt = attachment.prompt;
138
+ if (prompt == null) return null;
139
+ const raw = typeof prompt === "string" ? prompt : extractTextFromContent(prompt);
140
+ if (!isVisibleQueuedPrompt(raw)) return null;
141
+ const origin = attachment.origin;
142
+ if (origin?.kind === "peer") {
143
+ const sender = origin.name ?? origin.from ?? null;
144
+ if (sender) {
145
+ return {
146
+ raw,
147
+ sender,
148
+ body: origin.body ?? parseAgentEnvelope(raw).body
149
+ };
150
+ }
151
+ }
152
+ if (origin?.kind === "human") {
153
+ return { raw, sender: null, body: raw };
154
+ }
155
+ const parsed = parseAgentEnvelope(raw);
156
+ return { raw, sender: parsed.sender, body: parsed.body };
157
+ }
158
+ var ATTRIBUTION_FIELDS = [
159
+ ["attributionAgent", "agent"],
160
+ ["attributionSkill", "skill"],
161
+ ["attributionPlugin", "plugin"],
162
+ ["attributionMcpServer", "mcpServer"],
163
+ ["attributionMcpTool", "mcpTool"]
164
+ ];
165
+ function mergeAttribution(current, msg) {
166
+ let merged = current;
167
+ for (const [field, key] of ATTRIBUTION_FIELDS) {
168
+ const value = msg[field];
169
+ if (typeof value !== "string" || !value) continue;
170
+ merged = { ...merged, [key]: value };
171
+ }
172
+ return merged;
173
+ }
82
174
  function mergeTokenUsage(existing, incoming) {
83
175
  if (!existing) {
84
176
  return { ...incoming };
@@ -87,49 +179,210 @@ function mergeTokenUsage(existing, incoming) {
87
179
  input_tokens: existing.input_tokens + incoming.input_tokens,
88
180
  output_tokens: existing.output_tokens + incoming.output_tokens,
89
181
  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)
182
+ cache_read_input_tokens: (existing.cache_read_input_tokens ?? 0) + (incoming.cache_read_input_tokens ?? 0),
183
+ speed: incoming.speed ?? existing.speed
91
184
  };
92
185
  }
93
- function buildCompactionSummary(turns, title) {
94
- if (turns.length === 0) return title;
95
- const toolCounts = {};
96
- for (const turn of turns) {
97
- for (const tc of turn.toolCalls) {
98
- toolCounts[tc.name] = (toolCounts[tc.name] || 0) + 1;
186
+ var RESUME_PREAMBLE_RE = /^This session is being continued from a previous conversation[\s\S]*?\n\nSummary:\s*\n/;
187
+ var RESUME_INSTRUCTIONS_RE = /\n+(?:If you need specific details from before compaction|(?:Please c|C)ontinue the conversation from where it left off)[\s\S]*$/;
188
+ function extractCompactionSummary(content) {
189
+ const text = extractTextFromContent(content).trim();
190
+ return text.replace(RESUME_PREAMBLE_RE, "").replace(RESUME_INSTRUCTIONS_RE, "").trim();
191
+ }
192
+ function normalizeCompactionMeta(meta) {
193
+ if (!meta) return void 0;
194
+ return { trigger: meta.trigger, preTokens: meta.preTokens, postTokens: meta.postTokens };
195
+ }
196
+ function createTurn(msg, userMessage) {
197
+ return {
198
+ id: msg.uuid ?? crypto.randomUUID(),
199
+ userMessage,
200
+ contentBlocks: [],
201
+ thinking: [],
202
+ assistantText: [],
203
+ toolCalls: [],
204
+ subAgentActivity: [],
205
+ timestamp: msg.timestamp ?? "",
206
+ durationMs: null,
207
+ tokenUsage: null,
208
+ model: null
209
+ };
210
+ }
211
+ function groupPlanModeBlocks(blocks) {
212
+ const result = [];
213
+ let i = 0;
214
+ while (i < blocks.length) {
215
+ const block = blocks[i];
216
+ if (block.kind === "tool_calls") {
217
+ const enterIdx = block.toolCalls.findIndex((tc) => tc.name === "EnterPlanMode");
218
+ if (enterIdx !== -1) {
219
+ const enterCall = block.toolCalls[enterIdx];
220
+ const plan = String(enterCall.input.plan ?? "");
221
+ const timestamp = block.timestamp;
222
+ const before = block.toolCalls.slice(0, enterIdx);
223
+ if (before.length > 0) {
224
+ result.push({ kind: "tool_calls", toolCalls: before, timestamp: block.timestamp });
225
+ }
226
+ const embedded = [];
227
+ const afterEnterInSameBlock = block.toolCalls.slice(enterIdx + 1);
228
+ const exitInSameBlock = afterEnterInSameBlock.findIndex((tc) => tc.name === "ExitPlanMode");
229
+ if (exitInSameBlock !== -1) {
230
+ embedded.push(...afterEnterInSameBlock.slice(0, exitInSameBlock));
231
+ const exitCall = afterEnterInSameBlock[exitInSameBlock];
232
+ const planFilePath = exitCall.input.path ? String(exitCall.input.path) : void 0;
233
+ let status = "pending";
234
+ if (exitCall.result !== null) {
235
+ status = exitCall.isError ? "rejected" : "approved";
236
+ }
237
+ result.push({ kind: "plan_mode", plan, planFilePath, status, toolCalls: embedded, timestamp });
238
+ const after = afterEnterInSameBlock.slice(exitInSameBlock + 1);
239
+ if (after.length > 0) {
240
+ result.push({ kind: "tool_calls", toolCalls: after, timestamp: block.timestamp });
241
+ }
242
+ i++;
243
+ continue;
244
+ } else {
245
+ embedded.push(...afterEnterInSameBlock);
246
+ let exitCall;
247
+ let planFilePath;
248
+ let status = "pending";
249
+ let j = i + 1;
250
+ const passthroughBlocks = [];
251
+ while (j < blocks.length) {
252
+ const next = blocks[j];
253
+ if (next.kind === "tool_calls") {
254
+ const exitIdx = next.toolCalls.findIndex((tc) => tc.name === "ExitPlanMode");
255
+ if (exitIdx !== -1) {
256
+ embedded.push(...next.toolCalls.slice(0, exitIdx));
257
+ exitCall = next.toolCalls[exitIdx];
258
+ planFilePath = exitCall.input.path ? String(exitCall.input.path) : void 0;
259
+ if (exitCall.result !== null) {
260
+ status = exitCall.isError ? "rejected" : "approved";
261
+ }
262
+ const tail = next.toolCalls.slice(exitIdx + 1);
263
+ result.push({ kind: "plan_mode", plan, planFilePath, status, toolCalls: embedded, timestamp });
264
+ result.push(...passthroughBlocks);
265
+ if (tail.length > 0) {
266
+ result.push({ kind: "tool_calls", toolCalls: tail, timestamp: next.timestamp });
267
+ }
268
+ i = j + 1;
269
+ break;
270
+ } else {
271
+ embedded.push(...next.toolCalls);
272
+ j++;
273
+ }
274
+ } else if (next.kind === "hook_event" || next.kind === "text" || next.kind === "queued_prompt" || next.kind === "agent_message") {
275
+ passthroughBlocks.push(next);
276
+ j++;
277
+ } else {
278
+ break;
279
+ }
280
+ }
281
+ if (!exitCall) {
282
+ result.push({ kind: "plan_mode", plan, planFilePath: void 0, status: "pending", toolCalls: embedded, timestamp });
283
+ result.push(...passthroughBlocks);
284
+ i = j;
285
+ }
286
+ continue;
287
+ }
288
+ }
99
289
  }
290
+ result.push(block);
291
+ i++;
100
292
  }
101
- const prompts = [];
102
- for (const turn of turns) {
103
- if (!turn.userMessage) continue;
104
- const text = extractTextFromContent(
105
- typeof turn.userMessage === "string" ? turn.userMessage : turn.userMessage
106
- );
107
- const firstLine = text.split("\n")[0].trim();
108
- if (firstLine.length > 0) {
109
- prompts.push(firstLine.length > 120 ? firstLine.slice(0, 117) + "..." : firstLine);
293
+ return result;
294
+ }
295
+ function clearAgentMessageReplies(turn) {
296
+ if (!turn.contentBlocks.some((b) => b.kind === "agent_message")) return turn;
297
+ return {
298
+ ...turn,
299
+ contentBlocks: turn.contentBlocks.map((block) => {
300
+ if (block.kind !== "agent_message") return block;
301
+ const { reply: _paired, ...unpaired } = block;
302
+ return unpaired;
303
+ })
304
+ };
305
+ }
306
+ function pairAgentMessageReplies(turns) {
307
+ const repaired = turns.map(clearAgentMessageReplies);
308
+ const unanswered = /* @__PURE__ */ new Map();
309
+ for (const turn of repaired) {
310
+ for (const block of turn.contentBlocks) {
311
+ if (block.kind === "agent_message") {
312
+ const waiting = unanswered.get(block.sender);
313
+ if (waiting) waiting.push(block);
314
+ else unanswered.set(block.sender, [block]);
315
+ continue;
316
+ }
317
+ if (block.kind !== "tool_calls") continue;
318
+ for (const call of block.toolCalls) {
319
+ if (call.name !== "SendMessage") continue;
320
+ const to = call.input.to;
321
+ if (typeof to !== "string" || !to) continue;
322
+ const target = unanswered.get(to)?.shift();
323
+ if (!target) continue;
324
+ const summary = call.input.summary;
325
+ target.reply = {
326
+ summary: typeof summary === "string" ? summary : "",
327
+ timestamp: call.timestamp || block.timestamp || ""
328
+ };
329
+ }
110
330
  }
111
331
  }
112
- const topTools = Object.entries(toolCounts).sort((a, b) => b[1] - a[1]).slice(0, 5).map(([name, count]) => `${name} x${count}`).join(", ");
113
- const parts = [`**${title}**`, `${turns.length} turns compacted`];
114
- if (topTools) parts.push(`Tools: ${topTools}`);
115
- if (prompts.length > 0) {
116
- parts.push("Prompts:");
117
- const shown = prompts.slice(0, 6);
118
- for (const p of shown) parts.push(`- ${p}`);
119
- if (prompts.length > 6) parts.push(`- ...and ${prompts.length - 6} more`);
120
- }
121
- return parts.join("\n");
332
+ return repaired;
122
333
  }
123
334
  function buildTurns(messages) {
124
335
  const turns = [];
125
336
  let current = null;
126
337
  let pendingCompaction = null;
338
+ function attachPendingCompaction(turn) {
339
+ if (!pendingCompaction) return;
340
+ turn.compactionSummary = pendingCompaction.summary;
341
+ turn.compactionMeta = pendingCompaction.meta;
342
+ pendingCompaction = null;
343
+ }
344
+ let pendingRecap = null;
127
345
  const pendingToolUses = /* @__PURE__ */ new Map();
128
346
  const seenMessageIds = /* @__PURE__ */ new Set();
129
347
  const subAgentMap = /* @__PURE__ */ new Map();
130
348
  const agentBlockMap = /* @__PURE__ */ new Map();
131
349
  const backgroundAgentParentIds = /* @__PURE__ */ new Set();
132
350
  const taskMetaMap = /* @__PURE__ */ new Map();
351
+ const pendingQueuedPrompts = [];
352
+ const enqueueSourcedPrompts = /* @__PURE__ */ new Map();
353
+ function noteEnqueueSourced(turn, content) {
354
+ let counts = enqueueSourcedPrompts.get(turn);
355
+ if (!counts) {
356
+ counts = /* @__PURE__ */ new Map();
357
+ enqueueSourcedPrompts.set(turn, counts);
358
+ }
359
+ counts.set(content, (counts.get(content) ?? 0) + 1);
360
+ }
361
+ function consumeEnqueueSourced(turn, content) {
362
+ const counts = enqueueSourcedPrompts.get(turn);
363
+ const remaining = counts?.get(content);
364
+ if (!counts || !remaining) return false;
365
+ if (remaining === 1) counts.delete(content);
366
+ else counts.set(content, remaining - 1);
367
+ return true;
368
+ }
369
+ function flushPendingQueuedPrompts() {
370
+ for (const prompt of pendingQueuedPrompts) {
371
+ prompt.turn.contentBlocks.push(
372
+ prompt.sender ? {
373
+ kind: "agent_message",
374
+ sender: prompt.sender,
375
+ body: prompt.body,
376
+ timestamp: prompt.timestamp
377
+ } : {
378
+ kind: "queued_prompt",
379
+ content: prompt.content,
380
+ timestamp: prompt.timestamp
381
+ }
382
+ );
383
+ }
384
+ pendingQueuedPrompts.length = 0;
385
+ }
133
386
  function flushSubAgentMessages(parentId) {
134
387
  if (!current) return;
135
388
  const agentMsgs = subAgentMap.get(parentId);
@@ -154,6 +407,22 @@ function buildTurns(messages) {
154
407
  for (const [parentId] of subAgentMap) {
155
408
  flushSubAgentMessages(parentId);
156
409
  }
410
+ for (const block of current.contentBlocks) {
411
+ if (block.kind !== "hook_event") continue;
412
+ for (const ev of block.events) {
413
+ const isPostToolUse = ev.eventName === "PostToolUse" || ev.eventName.startsWith("PostToolUse:");
414
+ if (!isPostToolUse || !ev.toolUseId) continue;
415
+ const toolCall = current.toolCalls.find((tc) => tc.id === ev.toolUseId);
416
+ if (!toolCall) continue;
417
+ if (ev.updatedToolOutput) {
418
+ toolCall.outputReplacedByHook = true;
419
+ }
420
+ if (ev.durationMs !== void 0) {
421
+ toolCall.hookDurationMs = (toolCall.hookDurationMs ?? 0) + ev.durationMs;
422
+ }
423
+ }
424
+ }
425
+ current.contentBlocks = groupPlanModeBlocks(current.contentBlocks);
157
426
  turns.push(current);
158
427
  current = null;
159
428
  agentBlockMap.clear();
@@ -161,20 +430,66 @@ function buildTurns(messages) {
161
430
  for (const msg of messages) {
162
431
  if (isSummaryMessage(msg)) {
163
432
  finalizeTurn2();
164
- pendingCompaction = buildCompactionSummary(
165
- turns,
166
- msg.summary ?? "Conversation compacted"
167
- );
433
+ pendingCompaction = { summary: msg.summary };
168
434
  continue;
169
435
  }
170
436
  if (isCompactBoundary(msg)) {
171
437
  finalizeTurn2();
172
- pendingCompaction = buildCompactionSummary(
173
- turns,
174
- msg.content ?? "Conversation compacted"
175
- );
438
+ pendingCompaction = { meta: normalizeCompactionMeta(msg.compactMetadata) };
439
+ continue;
440
+ }
441
+ if (isCompactSummaryMessage(msg)) {
442
+ finalizeTurn2();
443
+ current = createTurn(msg, null);
444
+ current.compactionSummary = extractCompactionSummary(msg.message.content);
445
+ current.compactionMeta = pendingCompaction?.meta;
446
+ pendingCompaction = null;
447
+ continue;
448
+ }
449
+ if (isSystemMessage(msg) && msg.subtype === "away_summary" && msg.content) {
450
+ pendingRecap = { content: msg.content, timestamp: msg.timestamp };
451
+ continue;
452
+ }
453
+ if (isQueueOperationMessage(msg)) {
454
+ if (current && msg.operation === "enqueue" && isVisibleQueuedPrompt(msg.content)) {
455
+ const parsed = parseAgentEnvelope(msg.content);
456
+ pendingQueuedPrompts.push({
457
+ turn: current,
458
+ content: msg.content,
459
+ timestamp: msg.timestamp,
460
+ sender: parsed.sender,
461
+ body: parsed.body
462
+ });
463
+ noteEnqueueSourced(current, msg.content);
464
+ }
465
+ continue;
466
+ }
467
+ if (isAttachmentMessage(msg)) {
468
+ const queued = queuedCommandPrompt(msg);
469
+ if (current && queued !== null && !consumeEnqueueSourced(current, queued.raw)) {
470
+ pendingQueuedPrompts.push({
471
+ turn: current,
472
+ content: queued.raw,
473
+ timestamp: msg.attachment?.timestamp ?? msg.timestamp,
474
+ sender: queued.sender,
475
+ body: queued.body
476
+ });
477
+ }
176
478
  continue;
177
479
  }
480
+ if (pendingQueuedPrompts.length > 0) {
481
+ const userContent = isUserMessage(msg) && !msg.isMeta ? msg.message.content : null;
482
+ if (typeof userContent === "string") {
483
+ const duplicateIndex = pendingQueuedPrompts.findIndex(
484
+ (prompt) => prompt.content === userContent
485
+ );
486
+ if (duplicateIndex >= 0) {
487
+ const [dropped] = pendingQueuedPrompts.splice(duplicateIndex, 1);
488
+ consumeEnqueueSourced(dropped.turn, dropped.content);
489
+ }
490
+ }
491
+ flushPendingQueuedPrompts();
492
+ }
178
493
  if (isUserMessage(msg) && !msg.isMeta) {
179
494
  const content = msg.message.content;
180
495
  if (typeof content !== "string" && Array.isArray(content)) {
@@ -185,7 +500,7 @@ function buildTurns(messages) {
185
500
  const pending = pendingToolUses.get(block.tool_use_id);
186
501
  if (pending) {
187
502
  pending.turn.toolCalls[pending.index].result = extractToolResultText(block.content);
188
- pending.turn.toolCalls[pending.index].isError = block.is_error;
503
+ pending.turn.toolCalls[pending.index].isError = block.is_error === true;
189
504
  pendingToolUses.delete(block.tool_use_id);
190
505
  }
191
506
  }
@@ -203,6 +518,7 @@ function buildTurns(messages) {
203
518
  }
204
519
  const agentMsg = {
205
520
  agentId: toolUseResult.agentId,
521
+ parentToolUseId: toolUseId || void 0,
206
522
  agentName: taskMeta?.name ?? null,
207
523
  subagentType: taskMeta?.subagentType ?? null,
208
524
  type: "assistant",
@@ -236,23 +552,27 @@ function buildTurns(messages) {
236
552
  continue;
237
553
  }
238
554
  }
555
+ if (isTaskNotificationRecord(msg)) {
556
+ const notification = {
557
+ kind: "task_notification",
558
+ content: extractTextFromContent(content),
559
+ timestamp: msg.timestamp
560
+ };
561
+ if (current) {
562
+ current.contentBlocks.push(notification);
563
+ continue;
564
+ }
565
+ current = createTurn(msg, null);
566
+ current.contentBlocks.push(notification);
567
+ attachPendingCompaction(current);
568
+ continue;
569
+ }
239
570
  finalizeTurn2();
240
- current = {
241
- id: msg.uuid ?? crypto.randomUUID(),
242
- userMessage: msg.message.content,
243
- contentBlocks: [],
244
- thinking: [],
245
- assistantText: [],
246
- toolCalls: [],
247
- subAgentActivity: [],
248
- timestamp: msg.timestamp ?? "",
249
- durationMs: null,
250
- tokenUsage: null,
251
- model: null
252
- };
253
- if (pendingCompaction) {
254
- current.compactionSummary = pendingCompaction;
255
- pendingCompaction = null;
571
+ current = createTurn(msg, msg.message.content);
572
+ attachPendingCompaction(current);
573
+ if (pendingRecap) {
574
+ current.contentBlocks.push({ kind: "recap", content: pendingRecap.content, timestamp: pendingRecap.timestamp });
575
+ pendingRecap = null;
256
576
  }
257
577
  continue;
258
578
  }
@@ -275,21 +595,12 @@ function buildTurns(messages) {
275
595
  };
276
596
  var flushToolCalls = flushToolCalls2, flushThinking = flushThinking2;
277
597
  if (!current) {
278
- current = {
279
- id: msg.uuid ?? crypto.randomUUID(),
280
- userMessage: null,
281
- contentBlocks: [],
282
- thinking: [],
283
- assistantText: [],
284
- toolCalls: [],
285
- subAgentActivity: [],
286
- timestamp: msg.timestamp ?? "",
287
- durationMs: null,
288
- tokenUsage: null,
289
- model: null
290
- };
598
+ current = createTurn(msg, null);
599
+ attachPendingCompaction(current);
291
600
  }
292
601
  current.model = msg.message.model;
602
+ if (msg.effort) current.effort = msg.effort;
603
+ current.attribution = mergeAttribution(current.attribution, msg);
293
604
  const msgId = msg.message.id;
294
605
  if (!seenMessageIds.has(msgId)) {
295
606
  seenMessageIds.add(msgId);
@@ -303,6 +614,7 @@ function buildTurns(messages) {
303
614
  if (block.type === "thinking") {
304
615
  flushToolCalls2();
305
616
  const tb = block;
617
+ if (!tb.thinking) continue;
306
618
  current.thinking.push(tb);
307
619
  msgThinking.push(tb);
308
620
  } else if (block.type === "text") {
@@ -382,6 +694,7 @@ function buildTurns(messages) {
382
694
  const taskMeta = taskMetaMap.get(parentId);
383
695
  const agentMsg = {
384
696
  agentId: data.agentId,
697
+ parentToolUseId: parentId || void 0,
385
698
  agentName: taskMeta?.name ?? null,
386
699
  subagentType: taskMeta?.subagentType ?? null,
387
700
  type: data.message.type,
@@ -427,7 +740,7 @@ function buildTurns(messages) {
427
740
  );
428
741
  if (match) {
429
742
  match.result = extractToolResultText(block.content);
430
- match.isError = block.is_error;
743
+ match.isError = block.is_error === true;
431
744
  }
432
745
  }
433
746
  }
@@ -446,118 +759,46 @@ function buildTurns(messages) {
446
759
  }
447
760
  continue;
448
761
  }
762
+ if (isProgressMessage(msg) && msg.data.type === "hook_progress") {
763
+ const data = msg.data;
764
+ const eventName = String(data.hook_event_name ?? data.hookEvent ?? "unknown");
765
+ const hookSpecific = data.hookSpecificOutput;
766
+ const ev = {
767
+ eventName,
768
+ source: data.source !== void 0 ? String(data.source) : void 0,
769
+ toolName: data.tool_name !== void 0 ? String(data.tool_name) : void 0,
770
+ toolUseId: data.tool_use_id !== void 0 ? String(data.tool_use_id) : void 0,
771
+ command: data.command !== void 0 ? String(data.command) : void 0,
772
+ output: data.output !== void 0 ? String(data.output) : void 0,
773
+ stderr: data.stderr !== void 0 ? String(data.stderr) : void 0,
774
+ exitCode: data.exit_code !== void 0 ? Number(data.exit_code) : void 0,
775
+ decision: data.decision !== void 0 ? String(data.decision) : void 0,
776
+ durationMs: data.duration_ms !== void 0 ? Number(data.duration_ms) : void 0,
777
+ updatedToolOutput: hookSpecific?.updatedToolOutput !== void 0 ? String(hookSpecific.updatedToolOutput) : void 0,
778
+ sessionTitle: hookSpecific?.sessionTitle !== void 0 ? String(hookSpecific.sessionTitle) : void 0,
779
+ worktreePath: hookSpecific?.worktreePath !== void 0 ? String(hookSpecific.worktreePath) : void 0,
780
+ timestamp: msg.timestamp ?? ""
781
+ };
782
+ if (current) {
783
+ const last = current.contentBlocks[current.contentBlocks.length - 1];
784
+ if (last && last.kind === "hook_event") {
785
+ last.events.push(ev);
786
+ } else {
787
+ current.contentBlocks.push({ kind: "hook_event", events: [ev], timestamp: msg.timestamp });
788
+ }
789
+ }
790
+ continue;
791
+ }
449
792
  if (isSystemMessage(msg) && msg.subtype === "turn_duration" && current) {
450
- current.durationMs = msg.durationMs ?? null;
793
+ if (msg.durationMs !== void 0 && msg.durationMs !== null) {
794
+ current.durationMs = (current.durationMs ?? 0) + msg.durationMs;
795
+ }
451
796
  continue;
452
797
  }
453
798
  }
799
+ flushPendingQueuedPrompts();
454
800
  finalizeTurn2();
455
- return turns;
456
- }
457
-
458
- // src/lib/pricingTiers.ts
459
- var EXTENDED_CONTEXT_THRESHOLD = 2e5;
460
- var TIER_HAIKU_35 = { input: 0.8, output: 4, cacheWrite: 1, cacheRead: 0.08, webSearch: 0.01 };
461
- 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 };
464
- 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 };
467
- var MODEL_TIERS = [
468
- // Haiku
469
- { match: "haiku-4-5", tier: TIER_HAIKU_45 },
470
- { match: "haiku-4-0", tier: TIER_HAIKU_35 },
471
- { 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 },
482
- // Opus legacy (4.0, 4.1)
483
- { match: "opus-4-1", tier: TIER_OPUS_LEGACY },
484
- { match: "opus-4-0", tier: TIER_OPUS_LEGACY }
485
- ];
486
- var DEFAULT_TIER = TIER_SONNET_LATEST;
487
- var DEFAULT_EXTENDED_TIER = TIER_EXTENDED;
488
- var FAMILY_FALLBACKS = [
489
- { match: "haiku", tier: TIER_HAIKU_45 },
490
- { match: "sonnet", tier: TIER_SONNET_LATEST },
491
- { match: "opus", tier: TIER_SONNET_LATEST }
492
- ];
493
- function resolveTier(model, totalInputTokens) {
494
- const isExtended = (totalInputTokens ?? 0) > EXTENDED_CONTEXT_THRESHOLD;
495
- for (const entry of MODEL_TIERS) {
496
- if (model.includes(entry.match)) {
497
- return isExtended && entry.extendedTier ? entry.extendedTier : entry.tier;
498
- }
499
- }
500
- for (const entry of FAMILY_FALLBACKS) {
501
- if (model.includes(entry.match)) return entry.tier;
502
- }
503
- return isExtended ? DEFAULT_EXTENDED_TIER : DEFAULT_TIER;
504
- }
505
-
506
- // src/lib/token-costs.ts
507
- var CHARS_PER_TOKEN = 4;
508
- function calculateCost(c) {
509
- const totalInput = c.inputTokens + c.cacheWriteTokens + c.cacheReadTokens;
510
- const p = resolveTier(c.model ?? "", totalInput);
511
- 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
- }
513
- function charsToTokens(chars) {
514
- return Math.ceil(chars / CHARS_PER_TOKEN);
515
- }
516
- function totalLength(strings) {
517
- let n = 0;
518
- for (const s of strings) n += s.length;
519
- return n;
520
- }
521
- function totalToolInputLength(toolCalls) {
522
- let n = 0;
523
- for (const tc of toolCalls) n += JSON.stringify(tc.input).length;
524
- return n;
525
- }
526
- function estimateThinkingTokens(turn) {
527
- return charsToTokens(totalLength(turn.thinking.map((b) => b.thinking)));
528
- }
529
- function estimateVisibleOutputTokens(turn) {
530
- return charsToTokens(totalLength(turn.assistantText) + totalToolInputLength(turn.toolCalls));
531
- }
532
- function estimateTotalOutputTokens(turn) {
533
- const estimated = estimateThinkingTokens(turn) + estimateVisibleOutputTokens(turn);
534
- return Math.max(estimated, turn.tokenUsage?.output_tokens ?? 0);
535
- }
536
- function estimateSubAgentOutput(sa) {
537
- const chars = totalLength(sa.thinking) + totalLength(sa.text) + totalToolInputLength(sa.toolCalls);
538
- return Math.max(charsToTokens(chars), sa.tokenUsage?.output_tokens ?? 0);
539
- }
540
- function calculateTurnCostEstimated(turn) {
541
- if (!turn.tokenUsage) return 0;
542
- const u = turn.tokenUsage;
543
- return calculateCost({
544
- model: turn.model,
545
- inputTokens: u.input_tokens,
546
- outputTokens: estimateTotalOutputTokens(turn),
547
- cacheWriteTokens: u.cache_creation_input_tokens ?? 0,
548
- cacheReadTokens: u.cache_read_input_tokens ?? 0
549
- });
550
- }
551
- function calculateSubAgentCostEstimated(sa) {
552
- if (!sa.tokenUsage) return 0;
553
- const u = sa.tokenUsage;
554
- return calculateCost({
555
- model: sa.model,
556
- inputTokens: u.input_tokens,
557
- outputTokens: estimateSubAgentOutput(sa),
558
- cacheWriteTokens: u.cache_creation_input_tokens ?? 0,
559
- cacheReadTokens: u.cache_read_input_tokens ?? 0
560
- });
801
+ return pairAgentMessageReplies(turns);
561
802
  }
562
803
 
563
804
  // src/lib/sessionStats.ts
@@ -569,43 +810,325 @@ function countToolCalls(toolCalls, counts) {
569
810
  }
570
811
  return errors;
571
812
  }
572
- function addUsageToStats(stats, usage, estimatedOutput, cost) {
813
+ function addUsageToStats(stats, usage) {
573
814
  stats.totalInputTokens += usage.input_tokens;
574
- stats.totalOutputTokens += estimatedOutput;
815
+ stats.totalOutputTokens += usage.output_tokens;
575
816
  stats.totalCacheCreationTokens += usage.cache_creation_input_tokens ?? 0;
576
817
  stats.totalCacheReadTokens += usage.cache_read_input_tokens ?? 0;
577
- stats.totalCostUSD += cost;
578
818
  }
579
- function computeStats(turns) {
580
- const stats = {
819
+ function createEmptySessionStats(turnCount) {
820
+ return {
581
821
  totalInputTokens: 0,
582
822
  totalOutputTokens: 0,
583
823
  totalCacheCreationTokens: 0,
584
824
  totalCacheReadTokens: 0,
585
- totalCostUSD: 0,
586
825
  toolCallCounts: {},
587
826
  errorCount: 0,
588
827
  totalDurationMs: 0,
589
- turnCount: turns.length
828
+ turnCount
590
829
  };
830
+ }
831
+ function computeStats(turns) {
832
+ const stats = createEmptySessionStats(turns.length);
591
833
  for (const turn of turns) {
592
- if (turn.tokenUsage) {
593
- addUsageToStats(stats, turn.tokenUsage, estimateTotalOutputTokens(turn), calculateTurnCostEstimated(turn));
594
- }
834
+ if (turn.tokenUsage) addUsageToStats(stats, turn.tokenUsage);
595
835
  if (turn.durationMs) stats.totalDurationMs += turn.durationMs;
596
836
  stats.errorCount += countToolCalls(turn.toolCalls, stats.toolCallCounts);
597
837
  for (const sa of turn.subAgentActivity) {
598
838
  stats.errorCount += countToolCalls(sa.toolCalls, stats.toolCallCounts);
599
- if (sa.tokenUsage) {
600
- addUsageToStats(stats, sa.tokenUsage, estimateSubAgentOutput(sa), calculateSubAgentCostEstimated(sa));
601
- }
839
+ if (sa.tokenUsage) addUsageToStats(stats, sa.tokenUsage);
602
840
  }
603
841
  }
604
842
  return stats;
605
843
  }
606
844
 
845
+ // src/lib/codex-exec.ts
846
+ var JS_SIMPLE_ESCAPES = {
847
+ "0": "\0",
848
+ b: "\b",
849
+ f: "\f",
850
+ n: "\n",
851
+ r: "\r",
852
+ t: " ",
853
+ v: "\v"
854
+ };
855
+ function readJsStringLiteral(source, startIndex) {
856
+ const quote = source[startIndex];
857
+ if (quote !== '"' && quote !== "'" && quote !== "`") return null;
858
+ let value = "";
859
+ let index = startIndex + 1;
860
+ while (index < source.length) {
861
+ const char = source[index];
862
+ if (char === quote) return { endIndex: index + 1, value };
863
+ if (char !== "\\") {
864
+ value += char;
865
+ index++;
866
+ continue;
867
+ }
868
+ index++;
869
+ if (index >= source.length) return null;
870
+ const escaped = source[index];
871
+ if (escaped in JS_SIMPLE_ESCAPES) {
872
+ value += JS_SIMPLE_ESCAPES[escaped];
873
+ index++;
874
+ continue;
875
+ }
876
+ if (escaped === "\n") {
877
+ index++;
878
+ continue;
879
+ }
880
+ if (escaped === "\r") {
881
+ index += source[index + 1] === "\n" ? 2 : 1;
882
+ continue;
883
+ }
884
+ if (escaped === "x" && /^[0-9a-fA-F]{2}$/.test(source.slice(index + 1, index + 3))) {
885
+ value += String.fromCharCode(Number.parseInt(source.slice(index + 1, index + 3), 16));
886
+ index += 3;
887
+ continue;
888
+ }
889
+ if (escaped === "u") {
890
+ const braced = source.slice(index + 1).match(/^\{([0-9a-fA-F]+)\}/);
891
+ if (braced) {
892
+ const codePoint = Number.parseInt(braced[1], 16);
893
+ if (Number.isFinite(codePoint) && codePoint <= 1114111) {
894
+ value += String.fromCodePoint(codePoint);
895
+ index += braced[0].length + 1;
896
+ continue;
897
+ }
898
+ }
899
+ const hex = source.slice(index + 1, index + 5);
900
+ if (/^[0-9a-fA-F]{4}$/.test(hex)) {
901
+ value += String.fromCharCode(Number.parseInt(hex, 16));
902
+ index += 5;
903
+ continue;
904
+ }
905
+ }
906
+ value += escaped;
907
+ index++;
908
+ }
909
+ return null;
910
+ }
911
+
912
+ // src/lib/codex-patches.ts
913
+ function isApplyPatchText(value) {
914
+ return value.includes("*** Begin Patch") && value.includes("*** End Patch");
915
+ }
916
+ function extractApplyPatchInputs(execSource) {
917
+ if (!execSource.includes("apply_patch") || !execSource.includes("*** Begin Patch")) return [];
918
+ const assignedPatches = /* @__PURE__ */ new Map();
919
+ for (let index = 0; index < execSource.length; index++) {
920
+ const literal = readJsStringLiteral(execSource, index);
921
+ if (!literal) continue;
922
+ if (isApplyPatchText(literal.value)) {
923
+ const assignment = execSource.slice(0, index).match(/(?:^|[;\n])\s*(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*=\s*$/);
924
+ if (assignment) assignedPatches.set(assignment[1], literal.value);
925
+ }
926
+ index = literal.endIndex - 1;
927
+ }
928
+ const patches = [];
929
+ const seen = /* @__PURE__ */ new Set();
930
+ const applyCall = /\btools\s*\.\s*apply_patch\s*\(/g;
931
+ let match;
932
+ while ((match = applyCall.exec(execSource)) !== null) {
933
+ let argumentIndex = match.index + match[0].length;
934
+ while (/\s/.test(execSource[argumentIndex] ?? "")) argumentIndex++;
935
+ const literal = readJsStringLiteral(execSource, argumentIndex);
936
+ const identifier = literal ? null : execSource.slice(argumentIndex).match(/^([A-Za-z_$][\w$]*)/);
937
+ const patch = literal?.value ?? (identifier ? assignedPatches.get(identifier[1]) : void 0);
938
+ if (!patch || !isApplyPatchText(patch) || seen.has(patch)) continue;
939
+ seen.add(patch);
940
+ patches.push(patch);
941
+ }
942
+ return patches;
943
+ }
944
+ function resolveCodexPatchPath(filePath, cwd) {
945
+ if (!cwd || filePath.startsWith("/") || /^[A-Za-z]:[\\/]/.test(filePath)) return filePath;
946
+ const base = cwd.replace(/[\\/]+$/, "");
947
+ const relative = filePath.replace(/^\.[\\/]/, "");
948
+ return /^[A-Za-z]:[\\/]/.test(cwd) ? `${base}\\${relative.replace(/\//g, "\\")}` : `${base}/${relative}`;
949
+ }
950
+ function parseApplyPatch(patchText, callId, timestamp, cwd = "") {
951
+ const toolCalls = [];
952
+ const filePattern = /^\*\*\*\s+(Update File|Add File|Delete File):\s*(.+)$/gm;
953
+ const sections = [];
954
+ let match;
955
+ while ((match = filePattern.exec(patchText)) !== null) {
956
+ sections.push({
957
+ action: match[1],
958
+ filePath: match[2].trim(),
959
+ headerIdx: match.index,
960
+ startIdx: match.index + match[0].length
961
+ });
962
+ }
963
+ for (let i = 0; i < sections.length; i++) {
964
+ const section = sections[i];
965
+ const endIdx = i + 1 < sections.length ? sections[i + 1].headerIdx : patchText.length;
966
+ const body = patchText.slice(section.startIdx, endIdx);
967
+ const movedTo = body.match(/^\*\*\*\s+Move to:\s*(.+)$/m)?.[1]?.trim();
968
+ const sourcePath = resolveCodexPatchPath(section.filePath, cwd);
969
+ const filePath = resolveCodexPatchPath(movedTo || section.filePath, cwd);
970
+ const hunkBodies = body.split(/^@@.*$/m).filter((s) => s.trim());
971
+ const oldLines = [];
972
+ const newLines = [];
973
+ for (const hunk of hunkBodies) {
974
+ for (const line of hunk.split("\n")) {
975
+ if (line.startsWith("-")) {
976
+ oldLines.push(line.slice(1));
977
+ } else if (line.startsWith("+")) {
978
+ newLines.push(line.slice(1));
979
+ } else if (line.startsWith(" ")) {
980
+ oldLines.push(line.slice(1));
981
+ newLines.push(line.slice(1));
982
+ }
983
+ }
984
+ }
985
+ const fileId = sections.length > 1 ? `${callId}:file-${i}` : callId;
986
+ if (section.action === "Add File") {
987
+ toolCalls.push({
988
+ id: fileId,
989
+ name: "Write",
990
+ input: { file_path: filePath, content: newLines.join("\n") },
991
+ result: null,
992
+ isError: false,
993
+ timestamp
994
+ });
995
+ } else if (section.action === "Delete File") {
996
+ toolCalls.push({
997
+ id: fileId,
998
+ name: "Edit",
999
+ input: { file_path: filePath, old_string: oldLines.join("\n"), new_string: "" },
1000
+ result: null,
1001
+ isError: false,
1002
+ timestamp
1003
+ });
1004
+ } else {
1005
+ toolCalls.push({
1006
+ id: fileId,
1007
+ name: "Edit",
1008
+ input: {
1009
+ file_path: filePath,
1010
+ ...movedTo ? { old_path: sourcePath } : {},
1011
+ old_string: oldLines.join("\n"),
1012
+ new_string: newLines.join("\n")
1013
+ },
1014
+ result: null,
1015
+ isError: false,
1016
+ timestamp
1017
+ });
1018
+ }
1019
+ }
1020
+ return toolCalls;
1021
+ }
1022
+ function parseCodexToolPatches(toolName, rawInput, callId, timestamp, cwd = "") {
1023
+ if (toolName === "apply_patch") {
1024
+ return parseApplyPatch(rawInput, callId, timestamp, cwd);
1025
+ }
1026
+ return extractApplyPatchInputs(rawInput).flatMap(
1027
+ (patch, patchIndex) => parseApplyPatch(patch, `${callId}:patch-${patchIndex}`, timestamp, cwd)
1028
+ );
1029
+ }
1030
+ function findFailedNestedPatchCallIds(output, wrapperIsError, calls) {
1031
+ if (!wrapperIsError || !/(?:apply[_ ]patch|invalid patch|invalid context|failed to (?:apply|find)|could not apply)/i.test(output)) {
1032
+ return /* @__PURE__ */ new Set();
1033
+ }
1034
+ const groups = /* @__PURE__ */ new Map();
1035
+ for (const call of calls) {
1036
+ const patchIndex = call.id.match(/:patch-(\d+)(?::file-\d+)?$/)?.[1];
1037
+ if (patchIndex === void 0) continue;
1038
+ const index = Number(patchIndex);
1039
+ const group = groups.get(index) ?? [];
1040
+ group.push(call);
1041
+ groups.set(index, group);
1042
+ }
1043
+ if (groups.size === 0) return new Set(calls.map((call) => call.id));
1044
+ const normalizedOutput = output.toLowerCase().replaceAll("\\", "/");
1045
+ const mentionedGroups = /* @__PURE__ */ new Set();
1046
+ for (const [index, group] of groups) {
1047
+ if (group.some((call) => {
1048
+ const filePath = String(call.input.file_path ?? "").toLowerCase().replaceAll("\\", "/");
1049
+ if (!filePath) return false;
1050
+ const segments = filePath.split("/").filter(Boolean);
1051
+ const suffix = segments.slice(-2).join("/");
1052
+ return normalizedOutput.includes(filePath) || suffix.includes("/") && normalizedOutput.includes(suffix);
1053
+ })) {
1054
+ mentionedGroups.add(index);
1055
+ }
1056
+ }
1057
+ for (const match of normalizedOutput.matchAll(/\bpatch\s*(?:#|-)?\s*(\d+)\b/g)) {
1058
+ const index = Number(match[1]);
1059
+ if (groups.has(index)) mentionedGroups.add(index);
1060
+ }
1061
+ if (mentionedGroups.size === 0) mentionedGroups.add(Math.max(...groups.keys()));
1062
+ const firstFailedIndex = Math.min(...mentionedGroups);
1063
+ for (const index of groups.keys()) {
1064
+ if (index >= firstFailedIndex) mentionedGroups.add(index);
1065
+ }
1066
+ return new Set(
1067
+ [...mentionedGroups].flatMap((index) => groups.get(index)?.map((call) => call.id) ?? [])
1068
+ );
1069
+ }
1070
+
1071
+ // src/lib/codex-tool-normalization.ts
1072
+ function isObject(value) {
1073
+ return typeof value === "object" && value !== null;
1074
+ }
1075
+ var COLLABORATION_TOOL_NAMES = /* @__PURE__ */ new Map([
1076
+ ["spawn_agent", "spawn_agent"],
1077
+ ["spawnAgent", "spawn_agent"],
1078
+ ["wait_agent", "wait_agent"],
1079
+ ["waitAgent", "wait_agent"],
1080
+ ["send_message", "send_message"],
1081
+ ["sendMessage", "send_message"],
1082
+ ["followup_task", "followup_task"],
1083
+ ["followupTask", "followup_task"],
1084
+ ["list_agents", "list_agents"],
1085
+ ["listAgents", "list_agents"],
1086
+ ["interrupt_agent", "interrupt_agent"],
1087
+ ["interruptAgent", "interrupt_agent"]
1088
+ ]);
1089
+ function normalizeFunctionName(rawName) {
1090
+ const leaf = rawName.split(/(?:__|[.:/])+/).filter(Boolean).at(-1) ?? rawName;
1091
+ return COLLABORATION_TOOL_NAMES.get(leaf) ?? rawName;
1092
+ }
1093
+ function inferToolError(output) {
1094
+ if (!output) return false;
1095
+ const exitMatch = output.match(/Process exited with code (\d+)/);
1096
+ if (exitMatch) return exitMatch[1] !== "0";
1097
+ const withoutSuccessSummaries = output.replace(/\b0\s+(?:fail(?:ed|ures?)?|errors?)\b/gi, "").replace(/\bno\s+(?:failures?|errors?)\b/gi, "");
1098
+ return /\b(error|failed|exception)\b/i.test(withoutSuccessSummaries);
1099
+ }
1100
+ function normalizePlanToTodos(input) {
1101
+ const plan = Array.isArray(input.plan) ? input.plan : [];
1102
+ const todos = plan.filter((item) => isObject(item)).map((item) => ({
1103
+ content: typeof item.step === "string" ? item.step : "",
1104
+ status: typeof item.status === "string" ? item.status : "pending",
1105
+ activeForm: typeof item.step === "string" ? item.step : ""
1106
+ }));
1107
+ return { todos };
1108
+ }
1109
+ function extractCustomToolOutputText(output) {
1110
+ if (typeof output === "string") return output;
1111
+ if (!Array.isArray(output)) return "";
1112
+ return output.filter((block) => isObject(block) && typeof block.text === "string").map((block) => block.text).join("");
1113
+ }
1114
+ function parseCustomToolOutput(output) {
1115
+ const rawOutput = extractCustomToolOutputText(output);
1116
+ if (!rawOutput) return { text: "", isError: false };
1117
+ try {
1118
+ const parsed = JSON.parse(rawOutput);
1119
+ const text = typeof parsed.output === "string" ? parsed.output : rawOutput;
1120
+ const meta = isObject(parsed.metadata) ? parsed.metadata : null;
1121
+ const isError = meta ? meta.exit_code !== 0 && meta.exit_code !== void 0 : inferToolError(text);
1122
+ return { text, isError };
1123
+ } catch {
1124
+ return { text: rawOutput, isError: inferToolError(rawOutput) };
1125
+ }
1126
+ }
1127
+
607
1128
  // src/lib/codex.ts
608
1129
  var SKIP_PROMPT_PREFIXES = [
1130
+ "# AGENTS.md instructions for ",
1131
+ "<recommended_plugins>",
609
1132
  "<environment_context>",
610
1133
  "<permissions instructions>",
611
1134
  "<collaboration_mode>",
@@ -621,17 +1144,98 @@ function safeParseLine(line) {
621
1144
  return null;
622
1145
  }
623
1146
  }
624
- function isObject(value) {
1147
+ function isObject2(value) {
625
1148
  return typeof value === "object" && value !== null;
626
1149
  }
627
1150
  function isCodexRecord(record) {
628
1151
  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";
1152
+ 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
1153
  }
631
1154
  function extractMessageText(payload, blockType) {
632
1155
  const content = payload?.content;
633
1156
  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();
1157
+ return content.filter((block) => isObject2(block) && block.type === blockType && typeof block.text === "string").map((block) => block.text).join("\n").trim();
1158
+ }
1159
+ var CODEX_IMAGE_DATA_URL = /^data:(image\/(?:png|jpeg|gif|webp));base64,(.+)$/is;
1160
+ var CODEX_AUDIO_DATA_URL = /^data:(audio\/(?:wav|x-wav|mpeg|mp4|webm|ogg));base64,(.+)$/is;
1161
+ function parseImageDataUrl(value) {
1162
+ if (typeof value !== "string") return null;
1163
+ const match = CODEX_IMAGE_DATA_URL.exec(value);
1164
+ if (!match?.[1] || !match[2]) return null;
1165
+ return {
1166
+ type: "image",
1167
+ source: {
1168
+ type: "base64",
1169
+ media_type: match[1].toLowerCase(),
1170
+ data: match[2]
1171
+ }
1172
+ };
1173
+ }
1174
+ function parseAudioDataUrl(value) {
1175
+ if (typeof value !== "string") return null;
1176
+ const match = CODEX_AUDIO_DATA_URL.exec(value);
1177
+ if (!match?.[1] || !match[2]) return null;
1178
+ return {
1179
+ type: "audio",
1180
+ source: {
1181
+ type: "base64",
1182
+ media_type: match[1].toLowerCase(),
1183
+ data: match[2]
1184
+ }
1185
+ };
1186
+ }
1187
+ function extractEventMessageImages(payload) {
1188
+ if (!Array.isArray(payload.images)) return [];
1189
+ return payload.images.map(parseImageDataUrl).filter((image) => image !== null);
1190
+ }
1191
+ function extractEventMessageAudio(payload) {
1192
+ if (!Array.isArray(payload.audio)) return [];
1193
+ return payload.audio.map(parseAudioDataUrl).filter((audio) => audio !== null);
1194
+ }
1195
+ function extractResponseMessageImages(payload) {
1196
+ if (!Array.isArray(payload.content)) return [];
1197
+ return payload.content.filter((block) => isObject2(block) && block.type === "input_image").map((block) => parseImageDataUrl(block.image_url)).filter((image) => image !== null);
1198
+ }
1199
+ function extractResponseMessageAudio(payload) {
1200
+ if (!Array.isArray(payload.content)) return [];
1201
+ return payload.content.filter((block) => isObject2(block) && block.type === "input_audio").map((block) => parseAudioDataUrl(block.audio_url)).filter((audio) => audio !== null);
1202
+ }
1203
+ function buildCodexUserContent(message, images, audio = [], localImages = [], localAudio = []) {
1204
+ const attachmentSuffix = [
1205
+ ...localImages.map((path) => `
1206
+ ![image](<${path}>)`),
1207
+ ...localAudio.map((path) => `
1208
+ [audio attachment](<${path}>)`)
1209
+ ].join("");
1210
+ const text = message + attachmentSuffix;
1211
+ if (images.length === 0 && audio.length === 0) return text;
1212
+ return [
1213
+ ...images,
1214
+ ...audio,
1215
+ ...text ? [{ type: "text", text }] : []
1216
+ ];
1217
+ }
1218
+ function extractInterAgentMessage(payload) {
1219
+ const rawText = extractMessageText(payload, "input_text");
1220
+ if (!rawText) return { messageType: null, text: "" };
1221
+ const headerEnd = rawText.match(/\r?\nPayload:\s*(?:\r?\n|$)/);
1222
+ if (!rawText.startsWith("Message Type:") || !headerEnd || headerEnd.index === void 0) {
1223
+ return { messageType: null, text: rawText.trim() };
1224
+ }
1225
+ const header = rawText.slice(0, headerEnd.index);
1226
+ const messageType = header.match(/^Message Type:\s*([^\r\n]+)/)?.[1]?.trim().toUpperCase() ?? null;
1227
+ const text = rawText.slice(headerEnd.index + headerEnd[0].length).trim();
1228
+ return { messageType, text };
1229
+ }
1230
+ function agentNameFromPath(agentPath) {
1231
+ const segments = agentPath.split(/[\\/]/).filter(Boolean);
1232
+ return segments.at(-1) ?? null;
1233
+ }
1234
+ function readableAgentPrompt(value) {
1235
+ if (typeof value !== "string") return "";
1236
+ const prompt = value.trim();
1237
+ if (/^gAAAAA[A-Za-z0-9_-]+$/.test(prompt)) return "";
1238
+ return prompt;
635
1239
  }
636
1240
  function normalizePromptText(text) {
637
1241
  const trimmed = text.trim();
@@ -649,11 +1253,13 @@ function mergeTokenUsage2(existing, incoming) {
649
1253
  };
650
1254
  }
651
1255
  function parseTokenUsage(value) {
652
- if (!isObject(value)) return null;
653
- const inputTokens = typeof value.input_tokens === "number" ? value.input_tokens : 0;
1256
+ if (!isObject2(value)) return null;
1257
+ const reportedInputTokens = typeof value.input_tokens === "number" ? value.input_tokens : 0;
654
1258
  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;
1259
+ 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;
1260
+ const nativeCachedInput = typeof value.cached_input_tokens === "number" ? Math.max(0, value.cached_input_tokens) : null;
1261
+ const cacheRead = nativeCachedInput ?? (typeof value.cache_read_input_tokens === "number" ? value.cache_read_input_tokens : 0);
1262
+ const inputTokens = nativeCachedInput === null ? reportedInputTokens : Math.max(0, reportedInputTokens - nativeCachedInput);
657
1263
  if (inputTokens === 0 && outputTokens === 0 && cacheCreation === 0 && cacheRead === 0) return null;
658
1264
  return {
659
1265
  input_tokens: inputTokens,
@@ -694,7 +1300,7 @@ function appendToolCall(turn, toolCall, timestamp) {
694
1300
  }
695
1301
  function finalizeTurn(turns, current, lastTimestamp) {
696
1302
  if (!current) return null;
697
- const hasContent = current.userMessage !== null || current.assistantText.length > 0 || current.toolCalls.length > 0 || current.thinking.length > 0;
1303
+ const hasContent = current.userMessage !== null || current.assistantText.length > 0 || current.toolCalls.length > 0 || current.thinking.length > 0 || current.subAgentActivity.length > 0;
698
1304
  if (!hasContent) return null;
699
1305
  if (current.timestamp && lastTimestamp) {
700
1306
  const start = new Date(current.timestamp).getTime();
@@ -710,20 +1316,40 @@ function parseToolInput(argumentsText) {
710
1316
  if (typeof argumentsText !== "string" || !argumentsText.trim()) return {};
711
1317
  try {
712
1318
  const parsed = JSON.parse(argumentsText);
713
- return isObject(parsed) ? parsed : { value: parsed };
1319
+ return isObject2(parsed) ? parsed : { value: parsed };
714
1320
  } catch {
715
1321
  return { raw: argumentsText };
716
1322
  }
717
1323
  }
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);
1324
+ function safeStringify(value) {
1325
+ if (typeof value === "string") return value;
1326
+ try {
1327
+ return JSON.stringify(value);
1328
+ } catch {
1329
+ return String(value ?? "");
1330
+ }
1331
+ }
1332
+ function findString(...values) {
1333
+ return values.find((value) => typeof value === "string");
1334
+ }
1335
+ function isCodeModeChild(callId) {
1336
+ return callId.startsWith("exec-");
1337
+ }
1338
+ function parseMcpEventResult(value) {
1339
+ if (!isObject2(value)) return { text: safeStringify(value), isError: false };
1340
+ if ("Err" in value) return { text: safeStringify(value.Err), isError: true };
1341
+ const ok = isObject2(value.Ok) ? value.Ok : null;
1342
+ if (!ok) return { text: safeStringify(value), isError: false };
1343
+ const parsedContent = parseCustomToolOutput(ok.content);
1344
+ const text = parsedContent.text || (ok.structuredContent !== void 0 ? safeStringify(ok.structuredContent) : safeStringify(ok));
1345
+ return {
1346
+ text,
1347
+ isError: ok.isError === true || parsedContent.isError
1348
+ };
723
1349
  }
724
- function createTurn(turnId, timestamp, model) {
1350
+ function createTurn2(turnId, timestamp, model) {
725
1351
  return {
726
- id: turnId || randomTurnId("codex-turn"),
1352
+ id: turnId ? `${turnId}@${timestamp}` : randomTurnId("codex-turn"),
727
1353
  userMessage: null,
728
1354
  contentBlocks: [],
729
1355
  thinking: [],
@@ -737,14 +1363,25 @@ function createTurn(turnId, timestamp, model) {
737
1363
  };
738
1364
  }
739
1365
  function extractPromptFromRecord(record) {
740
- if (record.type === "event_msg" && isObject(record.payload) && record.payload.type === "user_message" && typeof record.payload.message === "string") {
1366
+ if (record.type === "event_msg" && isObject2(record.payload) && record.payload.type === "user_message" && typeof record.payload.message === "string") {
741
1367
  return normalizePromptText(record.payload.message);
742
1368
  }
743
- if (record.type === "response_item" && isObject(record.payload) && record.payload.type === "message" && record.payload.role === "user") {
1369
+ if (record.type === "response_item" && isObject2(record.payload) && record.payload.type === "message" && record.payload.role === "user") {
744
1370
  return normalizePromptText(extractMessageText(record.payload, "input_text"));
745
1371
  }
746
1372
  return "";
747
1373
  }
1374
+ function recordOpensTurn(record, payload) {
1375
+ if (record.type === "session_meta") return false;
1376
+ if (record.type === "event_msg" && payload?.type === "task_started") return false;
1377
+ if (record.type === "response_item" && payload?.type === "message") {
1378
+ if (payload.role === "developer" || payload.role === "system") return false;
1379
+ if (payload.role === "user") {
1380
+ return extractPromptFromRecord(record) !== "" || extractResponseMessageImages(payload).length > 0 || extractResponseMessageAudio(payload).length > 0;
1381
+ }
1382
+ }
1383
+ return true;
1384
+ }
748
1385
  function extractMetadataFromRecords(records) {
749
1386
  let sessionId = "";
750
1387
  let version = "";
@@ -757,14 +1394,17 @@ function extractMetadataFromRecords(records) {
757
1394
  let timestamp = "";
758
1395
  let lastTimestamp = "";
759
1396
  let turnCount = 0;
1397
+ let isSubagent = false;
1398
+ let parentSessionId = null;
1399
+ let agentPath = "/root";
760
1400
  let previousPrompt = "";
761
1401
  for (const record of records) {
762
- if (!isObject(record.payload)) continue;
1402
+ if (!isObject2(record.payload)) continue;
763
1403
  if (record.type === "session_meta") {
764
1404
  sessionId ||= typeof record.payload.id === "string" ? record.payload.id : "";
765
1405
  version ||= typeof record.payload.cli_version === "string" ? record.payload.cli_version : "";
766
1406
  cwd ||= typeof record.payload.cwd === "string" ? record.payload.cwd : "";
767
- if (!branchedFrom && isObject(record.payload.branchedFrom)) {
1407
+ if (!branchedFrom && isObject2(record.payload.branchedFrom)) {
768
1408
  const sourceId = typeof record.payload.branchedFrom.sessionId === "string" ? record.payload.branchedFrom.sessionId : "";
769
1409
  if (sourceId) {
770
1410
  branchedFrom = {
@@ -773,7 +1413,18 @@ function extractMetadataFromRecords(records) {
773
1413
  };
774
1414
  }
775
1415
  }
776
- const git = isObject(record.payload.git) ? record.payload.git : null;
1416
+ const source = isObject2(record.payload.source) ? record.payload.source : null;
1417
+ if (source && isObject2(source.subagent)) {
1418
+ isSubagent = true;
1419
+ const threadSpawn = isObject2(source.subagent.thread_spawn) ? source.subagent.thread_spawn : null;
1420
+ if (threadSpawn && typeof threadSpawn.agent_path === "string") {
1421
+ agentPath = threadSpawn.agent_path;
1422
+ }
1423
+ }
1424
+ if (typeof record.payload.forked_from_id === "string" && record.payload.forked_from_id) {
1425
+ parentSessionId = record.payload.forked_from_id;
1426
+ }
1427
+ const git = isObject2(record.payload.git) ? record.payload.git : null;
777
1428
  gitBranch ||= git && typeof git.branch === "string" ? git.branch : "";
778
1429
  }
779
1430
  if (record.type === "turn_context") {
@@ -798,12 +1449,16 @@ function extractMetadataFromRecords(records) {
798
1449
  cwd,
799
1450
  model,
800
1451
  slug: "",
1452
+ name: "",
801
1453
  branchedFrom,
802
1454
  firstUserMessage,
803
1455
  lastUserMessage,
804
1456
  timestamp,
805
1457
  lastTimestamp,
806
- turnCount
1458
+ turnCount,
1459
+ isSubagent,
1460
+ parentSessionId,
1461
+ agentPath
807
1462
  };
808
1463
  }
809
1464
  function isCodexSessionText(jsonlText) {
@@ -818,47 +1473,229 @@ function extractCodexMetadataFromLines(lines) {
818
1473
  const records = lines.map(safeParseLine).filter(isCodexRecord);
819
1474
  return extractMetadataFromRecords(records);
820
1475
  }
821
- function parseCodexSession(jsonlText) {
1476
+ function parseCodexSession(jsonlText, options) {
822
1477
  const records = jsonlText.split("\n").map((line) => line.trim()).filter(Boolean).map(safeParseLine).filter(isCodexRecord);
823
1478
  const metadata = extractMetadataFromRecords(records);
824
1479
  const turns = [];
825
1480
  const pendingToolCalls = /* @__PURE__ */ new Map();
1481
+ const webSearchCalls = /* @__PURE__ */ new Map();
1482
+ const patchCallIds = /* @__PURE__ */ new Map();
1483
+ const spawnAgentCalls = /* @__PURE__ */ new Map();
1484
+ const agentRegistry = /* @__PURE__ */ new Map();
1485
+ const agentIdByPath = /* @__PURE__ */ new Map();
1486
+ const agentIdBySpawnCall = /* @__PURE__ */ new Map();
1487
+ const agentMessages = /* @__PURE__ */ new Map();
1488
+ function upsertAgentMessage(agentId, targetTurn, eventTimestamp, update = {}) {
1489
+ const info = agentRegistry.get(agentId);
1490
+ let entry = agentMessages.get(agentId);
1491
+ if (!entry) {
1492
+ const agentMessage2 = {
1493
+ agentId,
1494
+ parentToolUseId: info?.parentToolCallId || void 0,
1495
+ agentName: info?.nickname ?? (info?.agentPath ? agentNameFromPath(info.agentPath) : null),
1496
+ subagentType: info?.agentType ?? null,
1497
+ type: "assistant",
1498
+ content: null,
1499
+ toolCalls: [],
1500
+ thinking: [],
1501
+ text: [],
1502
+ timestamp: info?.timestamp ?? eventTimestamp,
1503
+ tokenUsage: null,
1504
+ model: info?.model ?? null,
1505
+ isBackground: false,
1506
+ prompt: info?.message || void 0,
1507
+ status: update.status ?? "running"
1508
+ };
1509
+ targetTurn.subAgentActivity.push(agentMessage2);
1510
+ const lastBlock = targetTurn.contentBlocks[targetTurn.contentBlocks.length - 1];
1511
+ if (lastBlock?.kind === "sub_agent") {
1512
+ lastBlock.messages.push(agentMessage2);
1513
+ } else {
1514
+ targetTurn.contentBlocks.push({ kind: "sub_agent", messages: [agentMessage2], timestamp: eventTimestamp });
1515
+ }
1516
+ entry = { message: agentMessage2, turn: targetTurn };
1517
+ agentMessages.set(agentId, entry);
1518
+ }
1519
+ const agentMessage = entry.message;
1520
+ if (info) {
1521
+ agentMessage.parentToolUseId = info.parentToolCallId || agentMessage.parentToolUseId;
1522
+ agentMessage.agentName = info.nickname ?? (info.agentPath ? agentNameFromPath(info.agentPath) : agentMessage.agentName);
1523
+ agentMessage.subagentType = info.agentType;
1524
+ agentMessage.model = info.model;
1525
+ agentMessage.prompt = info.message || agentMessage.prompt;
1526
+ }
1527
+ if (update.text && !agentMessage.text.includes(update.text)) {
1528
+ agentMessage.text.push(update.text);
1529
+ agentMessage.content = agentMessage.text.join("\n\n");
1530
+ }
1531
+ if (update.status) {
1532
+ const terminalStatuses = /* @__PURE__ */ new Set(["completed", "failed", "interrupted"]);
1533
+ if (!(update.status === "running" && agentMessage.status && terminalStatuses.has(agentMessage.status))) {
1534
+ agentMessage.status = update.status;
1535
+ }
1536
+ if (terminalStatuses.has(update.status) && agentMessage.timestamp && eventTimestamp) {
1537
+ const startedAt = new Date(agentMessage.timestamp).getTime();
1538
+ const endedAt = new Date(eventTimestamp).getTime();
1539
+ if (Number.isFinite(startedAt) && Number.isFinite(endedAt) && endedAt >= startedAt) {
1540
+ agentMessage.durationMs = endedAt - startedAt;
1541
+ }
1542
+ }
1543
+ }
1544
+ return agentMessage;
1545
+ }
826
1546
  let current = null;
827
1547
  let currentTurnId = null;
828
1548
  let currentModel = metadata.model || null;
829
1549
  let lastTurnTimestamp = "";
1550
+ let pendingCompaction = null;
1551
+ let sawTurnStart = false;
1552
+ function createActiveTurn(turnId, timestamp, model) {
1553
+ const turn = createTurn2(turnId, timestamp, model);
1554
+ if (!sawTurnStart) turn.isFragment = true;
1555
+ if (pendingCompaction) {
1556
+ turn.compactionSummary = pendingCompaction;
1557
+ pendingCompaction = null;
1558
+ }
1559
+ return turn;
1560
+ }
1561
+ function upsertWebSearchCall(targetTurn, id, input, status, timestamp) {
1562
+ const existing = webSearchCalls.get(id);
1563
+ if (existing) {
1564
+ existing.input = { ...existing.input, ...input };
1565
+ existing.result = status;
1566
+ existing.isError = status === "failed";
1567
+ return;
1568
+ }
1569
+ const toolCall = {
1570
+ id,
1571
+ name: "WebSearch",
1572
+ input,
1573
+ result: status,
1574
+ isError: status === "failed",
1575
+ timestamp
1576
+ };
1577
+ webSearchCalls.set(id, toolCall);
1578
+ appendToolCall(targetTurn, toolCall, timestamp);
1579
+ }
830
1580
  for (const record of records) {
831
- const payload = isObject(record.payload) ? record.payload : void 0;
1581
+ const payload = isObject2(record.payload) ? record.payload : void 0;
832
1582
  const timestamp = record.timestamp ?? "";
1583
+ if (record.type === "compacted") {
1584
+ current = finalizeTurn(turns, current, lastTurnTimestamp);
1585
+ pendingCompaction = "Conversation compacted";
1586
+ lastTurnTimestamp = timestamp;
1587
+ continue;
1588
+ }
1589
+ if (record.type === "world_state") continue;
833
1590
  if (record.type === "turn_context") {
834
1591
  current = finalizeTurn(turns, current, lastTurnTimestamp);
835
1592
  currentTurnId = typeof payload?.turn_id === "string" ? payload.turn_id : null;
836
1593
  currentModel = typeof payload?.model === "string" ? payload.model : currentModel;
837
1594
  lastTurnTimestamp = timestamp;
1595
+ sawTurnStart = true;
838
1596
  continue;
839
1597
  }
840
1598
  if (record.type === "event_msg" && payload?.type === "user_message" && typeof payload.message === "string") {
841
1599
  if (current && (current.assistantText.length > 0 || current.toolCalls.length > 0 || current.thinking.length > 0)) {
842
1600
  current = finalizeTurn(turns, current, lastTurnTimestamp);
843
1601
  }
844
- current ??= createTurn(currentTurnId, timestamp, currentModel);
845
- current.userMessage = payload.message;
1602
+ sawTurnStart = true;
1603
+ current ??= createActiveTurn(currentTurnId, timestamp, currentModel);
1604
+ const localImages = (Array.isArray(payload.local_images) ? payload.local_images : []).filter(
1605
+ (p) => typeof p === "string" && p.length > 0
1606
+ );
1607
+ const localAudio = (Array.isArray(payload.local_audio) ? payload.local_audio : []).filter(
1608
+ (p) => typeof p === "string" && p.length > 0
1609
+ );
1610
+ current.userMessage = buildCodexUserContent(
1611
+ payload.message,
1612
+ extractEventMessageImages(payload),
1613
+ extractEventMessageAudio(payload),
1614
+ localImages,
1615
+ localAudio
1616
+ );
1617
+ current.isFragment = false;
846
1618
  current.timestamp = current.timestamp || timestamp;
847
1619
  lastTurnTimestamp = timestamp;
848
1620
  continue;
849
1621
  }
850
- current ??= createTurn(currentTurnId, timestamp, currentModel);
1622
+ if (!current && !recordOpensTurn(record, payload)) continue;
1623
+ current ??= createActiveTurn(currentTurnId, timestamp, currentModel);
851
1624
  if (!current.model && currentModel) current.model = currentModel;
852
1625
  if (!current.timestamp) current.timestamp = timestamp;
853
1626
  if (timestamp) lastTurnTimestamp = timestamp;
854
1627
  if (record.type === "event_msg" && payload?.type === "token_count") {
855
- const info = isObject(payload.info) ? payload.info : null;
1628
+ const info = isObject2(payload.info) ? payload.info : null;
856
1629
  const lastUsage = info ? parseTokenUsage(info.last_token_usage) : null;
857
1630
  if (lastUsage) {
858
1631
  current.tokenUsage = mergeTokenUsage2(current.tokenUsage, lastUsage);
859
1632
  }
860
1633
  continue;
861
1634
  }
1635
+ if (record.type === "event_msg" && payload?.type === "web_search_end" && typeof payload.call_id === "string") {
1636
+ const input = {};
1637
+ if (typeof payload.query === "string" && payload.query) input.query = payload.query;
1638
+ if (isObject2(payload.action)) input.action = payload.action;
1639
+ upsertWebSearchCall(current, payload.call_id, input, "completed", timestamp);
1640
+ continue;
1641
+ }
1642
+ if (record.type === "event_msg" && payload?.type === "mcp_tool_call_end" && typeof payload.call_id === "string" && isObject2(payload.invocation)) {
1643
+ const invocation = payload.invocation;
1644
+ const server = typeof invocation.server === "string" ? invocation.server : "mcp";
1645
+ const tool = typeof invocation.tool === "string" ? invocation.tool : "tool";
1646
+ const input = isObject2(invocation.arguments) ? invocation.arguments : {};
1647
+ const result = parseMcpEventResult(payload.result);
1648
+ const existing = pendingToolCalls.get(payload.call_id);
1649
+ if (existing) {
1650
+ existing.result = result.text;
1651
+ existing.isError = result.isError;
1652
+ pendingToolCalls.delete(payload.call_id);
1653
+ } else if (!isCodeModeChild(payload.call_id)) {
1654
+ appendToolCall(current, {
1655
+ id: payload.call_id,
1656
+ name: `mcp__${server}__${tool}`,
1657
+ input,
1658
+ result: result.text,
1659
+ isError: result.isError,
1660
+ timestamp
1661
+ }, timestamp);
1662
+ }
1663
+ continue;
1664
+ }
1665
+ if (record.type === "event_msg" && payload?.type === "sub_agent_activity" && typeof payload.agent_thread_id === "string" && typeof payload.agent_path === "string") {
1666
+ const agentId = payload.agent_thread_id;
1667
+ const eventId = typeof payload.event_id === "string" ? payload.event_id : "";
1668
+ const kind = typeof payload.kind === "string" ? payload.kind : "started";
1669
+ const spawnInfo = eventId ? spawnAgentCalls.get(eventId) : void 0;
1670
+ const existingId = agentIdByPath.get(payload.agent_path);
1671
+ const existing = agentRegistry.get(agentId) ?? (existingId ? agentRegistry.get(existingId) : void 0);
1672
+ if (kind !== "started" && !existing && !spawnInfo) continue;
1673
+ if (existingId && existingId !== agentId && !agentMessages.has(agentId)) {
1674
+ const provisionalEntry = agentMessages.get(existingId);
1675
+ if (provisionalEntry) {
1676
+ provisionalEntry.message.agentId = agentId;
1677
+ agentMessages.delete(existingId);
1678
+ agentMessages.set(agentId, provisionalEntry);
1679
+ }
1680
+ agentRegistry.delete(existingId);
1681
+ }
1682
+ const info = {
1683
+ agentId,
1684
+ nickname: existing?.nickname ?? agentNameFromPath(payload.agent_path) ?? spawnInfo?.taskName ?? null,
1685
+ agentPath: payload.agent_path,
1686
+ message: existing?.message ?? spawnInfo?.message ?? "",
1687
+ model: existing?.model ?? spawnInfo?.model ?? null,
1688
+ agentType: existing?.agentType ?? spawnInfo?.agentType ?? null,
1689
+ timestamp: existing?.timestamp ?? (timestamp || spawnInfo?.timestamp || ""),
1690
+ parentToolCallId: existing?.parentToolCallId ?? eventId
1691
+ };
1692
+ agentRegistry.set(agentId, info);
1693
+ agentIdByPath.set(payload.agent_path, agentId);
1694
+ if (eventId && (kind === "started" || spawnInfo)) agentIdBySpawnCall.set(eventId, agentId);
1695
+ const status = kind === "interrupted" ? "interrupted" : "running";
1696
+ upsertAgentMessage(agentId, current, timestamp, { status });
1697
+ continue;
1698
+ }
862
1699
  if (record.type !== "response_item" || !payload) continue;
863
1700
  if (payload.type === "reasoning") {
864
1701
  const summary = Array.isArray(payload.summary) ? payload.summary.filter((item) => typeof item === "string").join("\n") : "";
@@ -871,14 +1708,110 @@ function parseCodexSession(jsonlText) {
871
1708
  }
872
1709
  if (payload.type === "message" && payload.role === "user" && current.userMessage === null) {
873
1710
  const text = normalizePromptText(extractMessageText(payload, "input_text"));
874
- if (text) current.userMessage = text;
1711
+ const images = extractResponseMessageImages(payload);
1712
+ const audio = extractResponseMessageAudio(payload);
1713
+ if (text || images.length > 0 || audio.length > 0) {
1714
+ current.userMessage = buildCodexUserContent(text, images, audio);
1715
+ current.isFragment = false;
1716
+ sawTurnStart = true;
1717
+ }
1718
+ continue;
1719
+ }
1720
+ if (payload.type === "web_search_call" && typeof payload.id === "string") {
1721
+ const input = {};
1722
+ if (isObject2(payload.action)) input.action = payload.action;
1723
+ const status = typeof payload.status === "string" ? payload.status : "completed";
1724
+ upsertWebSearchCall(current, payload.id, input, status, timestamp);
1725
+ continue;
1726
+ }
1727
+ if (payload.type === "tool_search_call") {
1728
+ const callId = findString(payload.call_id, payload.id) ?? "";
1729
+ if (callId) {
1730
+ const toolCall = {
1731
+ id: callId,
1732
+ name: "ToolSearch",
1733
+ input: isObject2(payload.arguments) ? payload.arguments : {},
1734
+ result: null,
1735
+ isError: false,
1736
+ timestamp
1737
+ };
1738
+ pendingToolCalls.set(callId, toolCall);
1739
+ appendToolCall(current, toolCall, timestamp);
1740
+ }
1741
+ continue;
1742
+ }
1743
+ if (payload.type === "tool_search_output" && typeof payload.call_id === "string") {
1744
+ const toolCall = pendingToolCalls.get(payload.call_id);
1745
+ if (toolCall) {
1746
+ const tools = Array.isArray(payload.tools) ? payload.tools : [];
1747
+ const names = tools.filter(isObject2).map((tool) => findString(tool.name) ?? "").filter(Boolean);
1748
+ const count = names.length > 0 ? names.length : tools.length;
1749
+ const found = `Found ${count} tool${count === 1 ? "" : "s"}`;
1750
+ toolCall.result = names.length > 0 ? `${found}: ${names.join(", ")}` : found;
1751
+ toolCall.isError = payload.status === "failed";
1752
+ pendingToolCalls.delete(payload.call_id);
1753
+ }
1754
+ continue;
1755
+ }
1756
+ if (payload.type === "agent_message" && typeof payload.author === "string") {
1757
+ const author = payload.author;
1758
+ const recipient = typeof payload.recipient === "string" ? payload.recipient : null;
1759
+ const interAgentMessage = extractInterAgentMessage(payload);
1760
+ if (interAgentMessage.messageType === "NEW_TASK") continue;
1761
+ const knownAgentId = agentIdByPath.get(author);
1762
+ const isPotentialChild = author.startsWith(`${metadata.agentPath}/`) && (recipient === null || recipient === metadata.agentPath);
1763
+ if (!knownAgentId && !isPotentialChild) continue;
1764
+ const agentId = knownAgentId ?? author;
1765
+ if (!knownAgentId) {
1766
+ agentRegistry.set(agentId, {
1767
+ agentId,
1768
+ nickname: agentNameFromPath(author),
1769
+ agentPath: author,
1770
+ message: "",
1771
+ model: null,
1772
+ agentType: null,
1773
+ timestamp,
1774
+ parentToolCallId: ""
1775
+ });
1776
+ agentIdByPath.set(author, agentId);
1777
+ }
1778
+ let status;
1779
+ if (interAgentMessage.messageType === "FINAL_ANSWER") status = "completed";
1780
+ else if (interAgentMessage.messageType === "ERROR" || interAgentMessage.messageType === "FAILED") status = "failed";
1781
+ else if (interAgentMessage.messageType === "INTERRUPTED") status = "interrupted";
1782
+ else if (interAgentMessage.messageType === "MESSAGE") status = "running";
1783
+ upsertAgentMessage(agentId, current, timestamp, {
1784
+ status,
1785
+ text: interAgentMessage.text || void 0
1786
+ });
875
1787
  continue;
876
1788
  }
877
1789
  if (payload.type === "function_call" && typeof payload.call_id === "string") {
1790
+ const rawName = typeof payload.name === "string" ? payload.name : "tool";
1791
+ const functionName = normalizeFunctionName(rawName);
1792
+ const parsedInput = parseToolInput(payload.arguments);
1793
+ let name = functionName;
1794
+ let input = parsedInput;
1795
+ if (functionName === "exec_command") {
1796
+ name = "Bash";
1797
+ } else if (functionName === "update_plan") {
1798
+ name = "TodoWrite";
1799
+ input = normalizePlanToTodos(parsedInput);
1800
+ }
1801
+ if (functionName === "spawn_agent") {
1802
+ spawnAgentCalls.set(payload.call_id, {
1803
+ callId: payload.call_id,
1804
+ message: readableAgentPrompt(parsedInput.message),
1805
+ taskName: typeof parsedInput.task_name === "string" ? parsedInput.task_name : null,
1806
+ model: typeof parsedInput.model === "string" ? parsedInput.model : null,
1807
+ agentType: typeof parsedInput.agent_type === "string" ? parsedInput.agent_type : null,
1808
+ timestamp
1809
+ });
1810
+ }
878
1811
  const toolCall = {
879
1812
  id: payload.call_id,
880
- name: typeof payload.name === "string" ? payload.name : "tool",
881
- input: parseToolInput(payload.arguments),
1813
+ name,
1814
+ input,
882
1815
  result: null,
883
1816
  isError: false,
884
1817
  timestamp
@@ -894,6 +1827,116 @@ function parseCodexSession(jsonlText) {
894
1827
  toolCall.result = output;
895
1828
  toolCall.isError = inferToolError(output);
896
1829
  pendingToolCalls.delete(payload.call_id);
1830
+ if (toolCall.name === "spawn_agent" && output) {
1831
+ try {
1832
+ const result = JSON.parse(output);
1833
+ const agentId = typeof result.agent_id === "string" ? result.agent_id : agentIdBySpawnCall.get(toolCall.id) ?? "";
1834
+ const agentPath = typeof result.task_name === "string" ? result.task_name : null;
1835
+ const nickname = typeof result.nickname === "string" ? result.nickname : agentPath ? agentNameFromPath(agentPath) : null;
1836
+ if (agentId) {
1837
+ const spawnInfo = spawnAgentCalls.get(toolCall.id);
1838
+ const existing = agentRegistry.get(agentId);
1839
+ const info = {
1840
+ agentId,
1841
+ nickname: nickname ?? existing?.nickname ?? spawnInfo?.taskName ?? null,
1842
+ agentPath: agentPath ?? existing?.agentPath ?? null,
1843
+ message: existing?.message ?? spawnInfo?.message ?? "",
1844
+ model: existing?.model ?? spawnInfo?.model ?? null,
1845
+ agentType: existing?.agentType ?? spawnInfo?.agentType ?? null,
1846
+ timestamp: existing?.timestamp ?? spawnInfo?.timestamp ?? timestamp,
1847
+ parentToolCallId: existing?.parentToolCallId ?? toolCall.id
1848
+ };
1849
+ agentRegistry.set(agentId, info);
1850
+ agentIdBySpawnCall.set(toolCall.id, agentId);
1851
+ if (info.agentPath) agentIdByPath.set(info.agentPath, agentId);
1852
+ upsertAgentMessage(agentId, current, timestamp, { status: "running" });
1853
+ }
1854
+ } catch {
1855
+ }
1856
+ }
1857
+ if (toolCall.name === "wait_agent" && output && current) {
1858
+ try {
1859
+ const result = JSON.parse(output);
1860
+ const statusMap = isObject2(result.status) ? result.status : {};
1861
+ for (const [agentId, status] of Object.entries(statusMap)) {
1862
+ const statusValue = isObject2(status) ? status : {};
1863
+ const completedText = typeof statusValue.completed === "string" ? statusValue.completed : "";
1864
+ const failedText = typeof statusValue.failed === "string" ? statusValue.failed : typeof statusValue.error === "string" ? statusValue.error : "";
1865
+ const interruptedText = typeof statusValue.interrupted === "string" ? statusValue.interrupted : "";
1866
+ const lifecycle = completedText ? "completed" : failedText ? "failed" : interruptedText ? "interrupted" : typeof statusValue.status === "string" ? statusValue.status : "running";
1867
+ if (!agentRegistry.has(agentId)) {
1868
+ agentRegistry.set(agentId, {
1869
+ agentId,
1870
+ nickname: null,
1871
+ agentPath: null,
1872
+ message: "",
1873
+ model: null,
1874
+ agentType: null,
1875
+ timestamp,
1876
+ parentToolCallId: ""
1877
+ });
1878
+ }
1879
+ upsertAgentMessage(agentId, current, timestamp, {
1880
+ status: lifecycle,
1881
+ text: completedText || failedText || interruptedText || void 0
1882
+ });
1883
+ }
1884
+ } catch {
1885
+ }
1886
+ }
1887
+ continue;
1888
+ }
1889
+ if (payload.type === "custom_tool_call" && typeof payload.call_id === "string") {
1890
+ const name = typeof payload.name === "string" ? payload.name : "tool";
1891
+ const rawInput = typeof payload.input === "string" ? payload.input : "";
1892
+ const callId = payload.call_id;
1893
+ const perFileCalls = rawInput ? parseCodexToolPatches(name, rawInput, callId, timestamp, metadata.cwd) : [];
1894
+ if (name !== "apply_patch") {
1895
+ const toolCall = {
1896
+ id: callId,
1897
+ name: name === "exec_command" ? "Bash" : name,
1898
+ input: parseToolInput(rawInput),
1899
+ result: null,
1900
+ isError: false,
1901
+ timestamp
1902
+ };
1903
+ pendingToolCalls.set(toolCall.id, toolCall);
1904
+ appendToolCall(current, toolCall, timestamp);
1905
+ }
1906
+ for (const tc of perFileCalls) {
1907
+ pendingToolCalls.set(tc.id, tc);
1908
+ appendToolCall(current, tc, timestamp);
1909
+ }
1910
+ if (perFileCalls.length > 0) {
1911
+ patchCallIds.set(callId, {
1912
+ calls: perFileCalls,
1913
+ direct: name === "apply_patch"
1914
+ });
1915
+ }
1916
+ continue;
1917
+ }
1918
+ if (payload.type === "custom_tool_call_output" && typeof payload.call_id === "string") {
1919
+ const callId = payload.call_id;
1920
+ const { text, isError } = parseCustomToolOutput(payload.output);
1921
+ const patchCalls = patchCallIds.get(callId);
1922
+ if (patchCalls) {
1923
+ const failedIds = patchCalls.direct && isError ? new Set(patchCalls.calls.map((call) => call.id)) : findFailedNestedPatchCallIds(text, isError, patchCalls.calls);
1924
+ for (const patchCall of patchCalls.calls) {
1925
+ const tc = pendingToolCalls.get(patchCall.id);
1926
+ if (tc) {
1927
+ tc.result = text;
1928
+ tc.isError = failedIds.has(tc.id);
1929
+ pendingToolCalls.delete(tc.id);
1930
+ }
1931
+ }
1932
+ patchCallIds.delete(callId);
1933
+ }
1934
+ const toolCall = pendingToolCalls.get(callId);
1935
+ if (toolCall) {
1936
+ toolCall.result = text;
1937
+ toolCall.isError = isError;
1938
+ pendingToolCalls.delete(callId);
1939
+ }
897
1940
  continue;
898
1941
  }
899
1942
  }
@@ -904,19 +1947,17 @@ function parseCodexSession(jsonlText) {
904
1947
  gitBranch: metadata.gitBranch,
905
1948
  cwd: metadata.cwd,
906
1949
  slug: metadata.slug,
1950
+ name: "",
907
1951
  model: metadata.model,
908
1952
  turns,
909
- stats: computeStats(turns),
910
- rawMessages: records,
1953
+ stats: options?.skipStats ? createEmptySessionStats(turns.length) : computeStats(turns),
1954
+ rawMessages: options?.skipStats ? [] : records,
911
1955
  branchedFrom: metadata.branchedFrom,
912
1956
  agentKind: "codex"
913
1957
  };
914
1958
  }
915
1959
 
916
1960
  // src/lib/parser.ts
917
- function isAssistantMessage2(msg) {
918
- return msg.type === "assistant";
919
- }
920
1961
  function extractTextFromContent2(content) {
921
1962
  if (typeof content === "string") return content;
922
1963
  return content.filter((b) => b.type === "text").map((b) => b.text).join("\n");
@@ -934,49 +1975,37 @@ function parseLines(jsonlText) {
934
1975
  return messages;
935
1976
  }
936
1977
  function extractSessionMetadata(messages) {
937
- const meta = { sessionId: "", version: "", gitBranch: "", cwd: "", slug: "", model: "", branchedFrom: void 0 };
1978
+ const meta = { sessionId: "", version: "", gitBranch: "", cwd: "", slug: "", name: "", model: "", branchedFrom: void 0 };
938
1979
  for (const msg of messages) {
939
1980
  if (msg.sessionId && !meta.sessionId) meta.sessionId = msg.sessionId;
940
1981
  if (msg.version && !meta.version) meta.version = msg.version;
941
1982
  if (msg.gitBranch && !meta.gitBranch) meta.gitBranch = msg.gitBranch;
942
1983
  if (msg.cwd && !meta.cwd) meta.cwd = msg.cwd;
943
1984
  if (msg.slug && !meta.slug) meta.slug = msg.slug;
1985
+ if (msg.name && !meta.name) meta.name = msg.name;
944
1986
  if (msg.branchedFrom && !meta.branchedFrom) {
945
1987
  meta.branchedFrom = msg.branchedFrom;
946
1988
  }
947
- if (isAssistantMessage2(msg) && msg.message.model && !meta.model) {
1989
+ if (isAssistantMessage(msg) && msg.message.model && !meta.model) {
948
1990
  meta.model = msg.message.model;
949
1991
  }
950
1992
  if (meta.sessionId && meta.version && meta.gitBranch && meta.cwd && meta.model) break;
951
1993
  }
952
1994
  return meta;
953
1995
  }
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) {
1996
+ function parseSession(jsonlText, options) {
968
1997
  if (isCodexSessionText(jsonlText)) {
969
- return parseCodexSession(jsonlText);
1998
+ return parseCodexSession(jsonlText, options);
970
1999
  }
971
2000
  const rawMessages = parseLines(jsonlText);
972
2001
  const metadata = extractSessionMetadata(rawMessages);
973
2002
  const turns = buildTurns(rawMessages);
974
- const stats = opts?.skipStats ? emptyStats(turns.length) : computeStats(turns);
2003
+ const stats = options?.skipStats ? createEmptySessionStats(turns.length) : computeStats(turns);
975
2004
  return {
976
2005
  ...metadata,
977
2006
  turns,
978
2007
  stats,
979
- rawMessages: opts?.skipStats ? [] : rawMessages,
2008
+ rawMessages: options?.skipStats ? [] : rawMessages,
980
2009
  agentKind: "claude"
981
2010
  };
982
2011
  }
@@ -1231,17 +2260,11 @@ var SearchIndex = class {
1231
2260
  buildFull(projectsDir) {
1232
2261
  this.projectsDir = projectsDir;
1233
2262
  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 {
2263
+ for (const suffix of ["", "-wal", "-shm"]) {
2264
+ try {
2265
+ (0, import_node_fs.unlinkSync)(this.dbPath + suffix);
2266
+ } catch {
2267
+ }
1245
2268
  }
1246
2269
  this.db = new Database(this.dbPath);
1247
2270
  this.db.exec("PRAGMA journal_mode = WAL");
@@ -1513,7 +2536,8 @@ var import_node_os = require("node:os");
1513
2536
  var dirs = {
1514
2537
  PROJECTS_DIR: (0, import_node_path2.join)((0, import_node_os.homedir)(), ".claude", "projects"),
1515
2538
  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")
2539
+ TASKS_DIR: (0, import_node_path2.join)((0, import_node_os.homedir)(), ".claude", "tasks"),
2540
+ CODEX_SESSIONS_DIR: (0, import_node_path2.join)((0, import_node_os.homedir)(), ".codex", "sessions")
1517
2541
  };
1518
2542
  var DEFAULT_DB_PATH = (0, import_node_path2.join)((0, import_node_os.homedir)(), ".claude", "cogpit-memory", "search-index.db");
1519
2543
 
@@ -1621,7 +2645,11 @@ async function matchSubagentToMember(leadSessionId, subagentFileName, members) {
1621
2645
  }
1622
2646
  return null;
1623
2647
  }
1624
- var HOME_PREFIX = (0, import_node_os2.homedir)().replace(/\//g, "-").replace(/^-/, "").toLowerCase();
2648
+ function encodeClaudeDirName(cwd) {
2649
+ const normalized = cwd.replace(/[\\/]+$/, "") || cwd;
2650
+ return normalized.replace(/[^a-zA-Z0-9]/g, "-");
2651
+ }
2652
+ var HOME_PREFIX = encodeClaudeDirName((0, import_node_os2.homedir)()).replace(/^-/, "").toLowerCase();
1625
2653
 
1626
2654
  // src/commands/search.ts
1627
2655
  async function searchSessions(query, opts, searchIndex) {
@@ -1973,6 +3001,7 @@ var import_promises3 = require("node:fs/promises");
1973
3001
  var import_node_path5 = require("node:path");
1974
3002
  var RESULT_TRUNCATE_LIMIT = 1e4;
1975
3003
  var L1_RESPONSE_LIMIT = 15e4;
3004
+ var COMPACTION_SUMMARY_PREVIEW = 400;
1976
3005
  function extractUserMessageText(userMessage) {
1977
3006
  if (userMessage === null) return null;
1978
3007
  if (typeof userMessage === "string") return userMessage;
@@ -1980,6 +3009,8 @@ function extractUserMessageText(userMessage) {
1980
3009
  for (const block of userMessage) {
1981
3010
  if (block.type === "text") parts.push(block.text);
1982
3011
  else if (block.type === "image") parts.push("[image attached]");
3012
+ else if (block.type === "document") parts.push("[document attached]");
3013
+ else if (block.type === "audio") parts.push("[audio attached]");
1983
3014
  }
1984
3015
  return parts.length > 0 ? parts.join("\n") : null;
1985
3016
  }
@@ -2062,9 +3093,13 @@ function mapTurnToSummary(turn, turnIndex) {
2062
3093
  subAgents,
2063
3094
  hasThinking: turn.thinking.length > 0,
2064
3095
  isError: turn.toolCalls.some((tc) => tc.isError),
2065
- compactionSummary: turn.compactionSummary ?? null
3096
+ compactionSummary: truncateCompactionSummary(turn.compactionSummary)
2066
3097
  };
2067
3098
  }
3099
+ function truncateCompactionSummary(summary) {
3100
+ if (!summary) return null;
3101
+ return summary.length > COMPACTION_SUMMARY_PREVIEW ? summary.slice(0, COMPACTION_SUMMARY_PREVIEW) + "... [truncated, use L2 for full text]" : summary;
3102
+ }
2068
3103
  function mapTurnToDetail(session, turnIndex) {
2069
3104
  const turn = session.turns[turnIndex];
2070
3105
  const contentBlocks = turn.contentBlocks.map((block) => mapContentBlock(block));
@@ -2072,12 +3107,17 @@ function mapTurnToDetail(session, turnIndex) {
2072
3107
  sessionId: session.sessionId,
2073
3108
  turnIndex,
2074
3109
  userMessage: extractUserMessageText(turn.userMessage),
3110
+ compactionSummary: turn.compactionSummary ?? null,
2075
3111
  contentBlocks,
2076
3112
  tokenUsage: turn.tokenUsage ? { input: turn.tokenUsage.input_tokens, output: turn.tokenUsage.output_tokens } : null,
2077
3113
  model: turn.model,
2078
3114
  durationMs: turn.durationMs
2079
3115
  };
2080
3116
  }
3117
+ function mapToolCall(tc) {
3118
+ const { result, resultTruncated } = truncateResult(tc.result);
3119
+ return { id: tc.id, name: tc.name, input: tc.input, result, resultTruncated, isError: tc.isError };
3120
+ }
2081
3121
  function mapContentBlock(block) {
2082
3122
  switch (block.kind) {
2083
3123
  case "thinking": {
@@ -2089,10 +3129,7 @@ function mapContentBlock(block) {
2089
3129
  case "tool_calls":
2090
3130
  return {
2091
3131
  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
- }),
3132
+ toolCalls: block.toolCalls.map(mapToolCall),
2096
3133
  timestamp: block.timestamp ?? null
2097
3134
  };
2098
3135
  case "sub_agent":
@@ -2102,6 +3139,35 @@ function mapContentBlock(block) {
2102
3139
  agents: block.messages.map(mapSubAgentDetail),
2103
3140
  timestamp: block.timestamp ?? null
2104
3141
  };
3142
+ case "queued_prompt":
3143
+ return { kind: "queued_prompt", content: block.content, timestamp: block.timestamp ?? null };
3144
+ case "agent_message":
3145
+ return {
3146
+ kind: "agent_message",
3147
+ sender: block.sender,
3148
+ body: block.body,
3149
+ reply: block.reply ?? null,
3150
+ timestamp: block.timestamp ?? null
3151
+ };
3152
+ case "hook_event":
3153
+ return { kind: "hook_event", events: block.events, timestamp: block.timestamp ?? null };
3154
+ case "plan_mode":
3155
+ return {
3156
+ kind: "plan_mode",
3157
+ plan: block.plan,
3158
+ planFilePath: block.planFilePath ?? null,
3159
+ status: block.status,
3160
+ toolCalls: block.toolCalls.map(mapToolCall),
3161
+ timestamp: block.timestamp ?? null
3162
+ };
3163
+ case "recap":
3164
+ return { kind: "recap", content: block.content, timestamp: block.timestamp ?? null };
3165
+ case "task_notification":
3166
+ return { kind: "task_notification", content: block.content, timestamp: block.timestamp ?? null };
3167
+ default: {
3168
+ const exhaustive = block;
3169
+ return exhaustive;
3170
+ }
2105
3171
  }
2106
3172
  }
2107
3173
  async function findSubagentFile(parentJsonlPath, agentId) {
@@ -2224,7 +3290,6 @@ async function getAgentOverview(sessionId, agentId) {
2224
3290
  async function getAgentTurnDetail(sessionId, agentId, turnIndex) {
2225
3291
  const jsonlPath = await findJsonlPath(sessionId);
2226
3292
  if (!jsonlPath) return { error: "Session not found" };
2227
- const content = await (0, import_promises3.readFile)(jsonlPath, "utf-8");
2228
3293
  const subagentFile = await findSubagentFile(jsonlPath, agentId);
2229
3294
  if (!subagentFile) return { error: "Agent not found" };
2230
3295
  const subagentContent = await (0, import_promises3.readFile)(subagentFile.filePath, "utf-8");
@@ -2236,35 +3301,140 @@ async function getAgentTurnDetail(sessionId, agentId, turnIndex) {
2236
3301
  // src/commands/sessions.ts
2237
3302
  var import_promises5 = require("node:fs/promises");
2238
3303
  var import_node_path6 = require("node:path");
2239
- var import_node_os3 = require("node:os");
2240
3304
 
2241
3305
  // src/lib/metadata.ts
2242
3306
  var import_promises4 = require("node:fs/promises");
2243
3307
 
2244
3308
  // src/lib/sessionStatus.ts
3309
+ var TASK_NOTIFICATION_MARKER = "<task-notification>";
3310
+ var TASK_ID_RE = /<task-id>([^<]+)<\/task-id>/g;
3311
+ var TOOL_USE_ID_RE = /<tool-use-id>([^<]+)<\/tool-use-id>/g;
3312
+ function isObject3(value) {
3313
+ return typeof value === "object" && value !== null;
3314
+ }
3315
+ function userMessageTexts(msg) {
3316
+ const message = isObject3(msg.message) ? msg.message : null;
3317
+ const content = message?.content;
3318
+ if (typeof content === "string") return [content];
3319
+ if (!Array.isArray(content)) return [];
3320
+ return content.filter((block) => isObject3(block) && typeof block.text === "string").map((block) => block.text);
3321
+ }
3322
+ function collectClaudeBackgroundWork(rawMessages) {
3323
+ const launches = [];
3324
+ const resolved = /* @__PURE__ */ new Set();
3325
+ const queuedIds = /* @__PURE__ */ new Set();
3326
+ const deliveredIds = /* @__PURE__ */ new Set();
3327
+ function noteNotifications(text, sink) {
3328
+ if (!text.includes(TASK_NOTIFICATION_MARKER)) return;
3329
+ for (const match of text.matchAll(TASK_ID_RE)) {
3330
+ resolved.add(match[1]);
3331
+ sink?.add(match[1]);
3332
+ }
3333
+ for (const match of text.matchAll(TOOL_USE_ID_RE)) {
3334
+ resolved.add(match[1]);
3335
+ sink?.add(match[1]);
3336
+ }
3337
+ }
3338
+ for (const msg of rawMessages) {
3339
+ if (msg.type === "user") {
3340
+ const toolUseResult = isObject3(msg.toolUseResult) ? msg.toolUseResult : null;
3341
+ if (toolUseResult?.status === "async_launched") {
3342
+ const message = isObject3(msg.message) ? msg.message : null;
3343
+ const blocks = Array.isArray(message?.content) ? message.content : [];
3344
+ const toolUseIds = blocks.filter((block) => isObject3(block) && block.type === "tool_result").map((block) => block.tool_use_id);
3345
+ const ids = [toolUseResult.agentId, toolUseResult.taskId, ...toolUseIds].filter((id) => typeof id === "string" && id.length > 0);
3346
+ const description = [toolUseResult.description, toolUseResult.workflowName, toolUseResult.summary].find((value) => typeof value === "string" && value.length > 0) ?? "";
3347
+ if (ids.length > 0) launches.push({ ids, description });
3348
+ }
3349
+ for (const text of userMessageTexts(msg)) noteNotifications(text, deliveredIds);
3350
+ continue;
3351
+ }
3352
+ if (msg.type === "assistant") {
3353
+ const message = isObject3(msg.message) ? msg.message : null;
3354
+ const blocks = Array.isArray(message?.content) ? message.content : [];
3355
+ for (const block of blocks) {
3356
+ if (!isObject3(block) || block.type !== "tool_use" || block.name !== "TaskStop") continue;
3357
+ const input = isObject3(block.input) ? block.input : null;
3358
+ const taskId = input?.task_id ?? input?.shell_id;
3359
+ if (typeof taskId === "string") resolved.add(taskId);
3360
+ }
3361
+ continue;
3362
+ }
3363
+ if (msg.type === "attachment") {
3364
+ const attachment = isObject3(msg.attachment) ? msg.attachment : null;
3365
+ if (typeof attachment?.prompt === "string") noteNotifications(attachment.prompt, deliveredIds);
3366
+ continue;
3367
+ }
3368
+ if (msg.type === "queue-operation" && typeof msg.content === "string") {
3369
+ const op = msg.operation;
3370
+ noteNotifications(msg.content, op === "enqueue" ? queuedIds : deliveredIds);
3371
+ }
3372
+ }
3373
+ return {
3374
+ pendingDescriptions: launches.filter((launch) => !launch.ids.some((id) => resolved.has(id))).map((launch) => launch.description),
3375
+ undeliveredNotifications: [...queuedIds].filter((id) => !deliveredIds.has(id)).length
3376
+ };
3377
+ }
3378
+ function awaitingAgentsResult(pendingDescriptions, pendingQueue) {
3379
+ return {
3380
+ status: "awaiting_agents",
3381
+ pendingQueue,
3382
+ pendingAgents: pendingDescriptions.length,
3383
+ pendingAgentDescriptions: pendingDescriptions.filter((description) => description.length > 0)
3384
+ };
3385
+ }
2245
3386
  function deriveSessionStatus(rawMessages) {
2246
3387
  const firstType = rawMessages[0]?.type;
2247
3388
  if (firstType === "session_meta" || firstType === "turn_context" || firstType === "event_msg" || firstType === "response_item") {
2248
3389
  return deriveCodexSessionStatus(rawMessages);
2249
3390
  }
2250
3391
  let pendingEnqueues = 0;
3392
+ let backgroundWork = null;
3393
+ let deliveringNotification = false;
2251
3394
  function result(status, toolName) {
2252
3395
  const info = { status, pendingQueue: Math.max(0, pendingEnqueues) };
2253
3396
  if (toolName) info.toolName = toolName;
2254
3397
  return info;
2255
3398
  }
3399
+ function getBackgroundWork() {
3400
+ return backgroundWork ??= collectClaudeBackgroundWork(rawMessages);
3401
+ }
3402
+ for (let i = rawMessages.length - 1; i >= 0; i--) {
3403
+ const msg = rawMessages[i];
3404
+ if (msg.type === "queue-operation") continue;
3405
+ if (msg.type === "progress") {
3406
+ const data = msg.data;
3407
+ if (data?.type === "hook_progress") {
3408
+ const decision = data.decision ?? data.hookSpecificOutput?.permissionDecision;
3409
+ if (decision === "defer") return result("deferred");
3410
+ }
3411
+ break;
3412
+ }
3413
+ break;
3414
+ }
2256
3415
  for (let i = rawMessages.length - 1; i >= 0; i--) {
2257
3416
  const msg = rawMessages[i];
2258
3417
  if (msg.type === "queue-operation") {
3418
+ if (typeof msg.content === "string" && msg.content.includes(TASK_NOTIFICATION_MARKER)) continue;
2259
3419
  const op = msg.operation;
2260
3420
  if (op === "enqueue") pendingEnqueues++;
2261
3421
  else if (op === "dequeue" || op === "remove") pendingEnqueues--;
2262
3422
  continue;
2263
3423
  }
3424
+ if (msg.type === "attachment") {
3425
+ const attachment = isObject3(msg.attachment) ? msg.attachment : null;
3426
+ if (attachment?.commandMode === "task-notification") deliveringNotification = true;
3427
+ continue;
3428
+ }
2264
3429
  if (msg.type === "assistant") {
2265
3430
  const message = msg.message;
2266
3431
  const stopReason = message?.stop_reason;
2267
3432
  if (stopReason === "end_turn") {
3433
+ const work = getBackgroundWork();
3434
+ if (deliveringNotification || work.undeliveredNotifications > 0) return result("processing");
3435
+ if (work.pendingDescriptions.length > 0) {
3436
+ return awaitingAgentsResult(work.pendingDescriptions, Math.max(0, pendingEnqueues));
3437
+ }
2268
3438
  let hasActivity = false;
2269
3439
  for (let j = i - 1; j >= 0; j--) {
2270
3440
  const m = rawMessages[j];
@@ -2287,19 +3457,136 @@ function deriveSessionStatus(rawMessages) {
2287
3457
  if (isMeta) continue;
2288
3458
  return result("processing");
2289
3459
  }
3460
+ if (msg.type === "system" && msg.subtype === "terminal_reason") {
3461
+ const reason = msg.reason;
3462
+ if (reason) {
3463
+ return { status: "completed", terminalReason: reason, pendingQueue: Math.max(0, pendingEnqueues) };
3464
+ }
3465
+ continue;
3466
+ }
2290
3467
  if (msg.type === "summary") continue;
2291
3468
  if (msg.type === "system" && msg.subtype === "compact_boundary") continue;
2292
3469
  }
2293
3470
  return { status: "idle" };
2294
3471
  }
3472
+ var TERMINAL_INTER_AGENT_TYPES = /* @__PURE__ */ new Set(["FINAL_ANSWER", "ERROR", "FAILED", "INTERRUPTED"]);
3473
+ function codexPayloadText(payload) {
3474
+ const content = payload.content;
3475
+ if (!Array.isArray(content)) return "";
3476
+ return content.filter((block) => isObject3(block) && block.type === "input_text" && typeof block.text === "string").map((block) => block.text).join("\n");
3477
+ }
3478
+ function parseJsonObject(value) {
3479
+ if (typeof value !== "string") return null;
3480
+ try {
3481
+ const parsed = JSON.parse(value);
3482
+ return isObject3(parsed) ? parsed : null;
3483
+ } catch {
3484
+ return null;
3485
+ }
3486
+ }
3487
+ function collectCodexPendingAgents(rawMessages) {
3488
+ const alive = /* @__PURE__ */ new Map();
3489
+ const done = /* @__PURE__ */ new Set();
3490
+ const agentIdByPath = /* @__PURE__ */ new Map();
3491
+ const spawnCalls = /* @__PURE__ */ new Map();
3492
+ const waitCalls = /* @__PURE__ */ new Set();
3493
+ const interruptCalls = /* @__PURE__ */ new Map();
3494
+ function markAlive(agentId, description) {
3495
+ if (!done.has(agentId)) alive.set(agentId, alive.get(agentId) || description);
3496
+ }
3497
+ for (const msg of rawMessages) {
3498
+ const payload = isObject3(msg.payload) ? msg.payload : null;
3499
+ if (!payload) continue;
3500
+ if (msg.type === "event_msg" && payload.type === "sub_agent_activity" && typeof payload.agent_thread_id === "string" && typeof payload.agent_path === "string") {
3501
+ const agentId = payload.agent_thread_id;
3502
+ const kind = typeof payload.kind === "string" ? payload.kind : "started";
3503
+ const eventId = typeof payload.event_id === "string" ? payload.event_id : "";
3504
+ const existingId = agentIdByPath.get(payload.agent_path);
3505
+ const known = alive.has(agentId) || done.has(agentId) || existingId !== void 0 || spawnCalls.has(eventId);
3506
+ if (kind !== "started" && !known) continue;
3507
+ if (existingId && existingId !== agentId) {
3508
+ if (alive.has(existingId)) {
3509
+ markAlive(agentId, alive.get(existingId) ?? "");
3510
+ alive.delete(existingId);
3511
+ }
3512
+ if (done.has(existingId)) done.add(agentId);
3513
+ }
3514
+ agentIdByPath.set(payload.agent_path, agentId);
3515
+ if (kind === "interrupted") {
3516
+ done.add(agentId);
3517
+ alive.delete(agentId);
3518
+ } else {
3519
+ markAlive(agentId, spawnCalls.get(eventId) ?? "");
3520
+ }
3521
+ continue;
3522
+ }
3523
+ if (msg.type !== "response_item") continue;
3524
+ if (payload.type === "function_call" && typeof payload.call_id === "string") {
3525
+ const name = normalizeFunctionName(typeof payload.name === "string" ? payload.name : "");
3526
+ if (name === "spawn_agent") {
3527
+ const input = parseJsonObject(payload.arguments);
3528
+ spawnCalls.set(payload.call_id, typeof input?.task_name === "string" ? input.task_name : "");
3529
+ } else if (name === "wait_agent") {
3530
+ waitCalls.add(payload.call_id);
3531
+ } else if (name === "interrupt_agent") {
3532
+ const input = parseJsonObject(payload.arguments);
3533
+ const targets = [input?.agent_id, ...Array.isArray(input?.agent_ids) ? input.agent_ids : []].filter((id) => typeof id === "string");
3534
+ interruptCalls.set(payload.call_id, targets);
3535
+ }
3536
+ continue;
3537
+ }
3538
+ if (payload.type === "function_call_output" && typeof payload.call_id === "string") {
3539
+ const callId = payload.call_id;
3540
+ if (spawnCalls.has(callId)) {
3541
+ const output = parseJsonObject(payload.output);
3542
+ const agentId = typeof output?.agent_id === "string" ? output.agent_id : "";
3543
+ if (agentId) {
3544
+ markAlive(agentId, spawnCalls.get(callId) ?? "");
3545
+ const agentPath = typeof output?.task_name === "string" ? output.task_name : "";
3546
+ if (agentPath && !agentIdByPath.has(agentPath)) agentIdByPath.set(agentPath, agentId);
3547
+ }
3548
+ } else if (waitCalls.has(callId)) {
3549
+ const output = parseJsonObject(payload.output);
3550
+ const statusMap = isObject3(output?.status) ? output.status : {};
3551
+ for (const [agentId, status] of Object.entries(statusMap)) {
3552
+ const statusValue = isObject3(status) ? status : {};
3553
+ const lifecycle = typeof statusValue.status === "string" ? statusValue.status : "";
3554
+ const isTerminal = typeof statusValue.completed === "string" || typeof statusValue.failed === "string" || typeof statusValue.error === "string" || typeof statusValue.interrupted === "string" || lifecycle === "completed" || lifecycle === "failed" || lifecycle === "interrupted";
3555
+ if (isTerminal) {
3556
+ done.add(agentId);
3557
+ alive.delete(agentId);
3558
+ }
3559
+ }
3560
+ } else if (interruptCalls.has(callId)) {
3561
+ for (const agentId of interruptCalls.get(callId) ?? []) {
3562
+ done.add(agentId);
3563
+ alive.delete(agentId);
3564
+ }
3565
+ }
3566
+ continue;
3567
+ }
3568
+ if (payload.type === "agent_message" && typeof payload.author === "string") {
3569
+ const text = codexPayloadText(payload);
3570
+ const messageType = text.match(/^Message Type:\s*([^\r\n]+)/)?.[1]?.trim().toUpperCase() ?? "";
3571
+ if (!TERMINAL_INTER_AGENT_TYPES.has(messageType)) continue;
3572
+ const agentId = agentIdByPath.get(payload.author) ?? payload.author;
3573
+ done.add(agentId);
3574
+ alive.delete(agentId);
3575
+ }
3576
+ }
3577
+ return [...alive.entries()].filter(([agentId]) => !done.has(agentId)).map(([, description]) => description);
3578
+ }
2295
3579
  function deriveCodexSessionStatus(rawMessages) {
2296
3580
  for (let i = rawMessages.length - 1; i >= 0; i--) {
2297
3581
  const msg = rawMessages[i];
2298
3582
  if (msg.type === "event_msg") {
2299
3583
  const payload = msg.payload;
2300
3584
  switch (payload?.type) {
2301
- case "task_complete":
3585
+ case "task_complete": {
3586
+ const pending = collectCodexPendingAgents(rawMessages);
3587
+ if (pending.length > 0) return awaitingAgentsResult(pending, 0);
2302
3588
  return { status: "completed" };
3589
+ }
2303
3590
  case "task_started":
2304
3591
  return { status: "processing" };
2305
3592
  case "agent_message":
@@ -2312,7 +3599,7 @@ function deriveCodexSessionStatus(rawMessages) {
2312
3599
  const payload = msg.payload;
2313
3600
  if (!payload) continue;
2314
3601
  if (payload.type === "function_call") {
2315
- return { status: "tool_use", toolName: payload.name };
3602
+ return { status: "tool_use", toolName: payload.name ? normalizeFunctionName(payload.name) : payload.name };
2316
3603
  }
2317
3604
  if (payload.type === "message") {
2318
3605
  if (payload.role === "assistant") return { status: "thinking" };
@@ -2512,7 +3799,23 @@ async function getSessionStatus(filePath) {
2512
3799
  }
2513
3800
 
2514
3801
  // src/commands/sessions.ts
2515
- var CODEX_SESSIONS_DIR = (0, import_node_path6.join)((0, import_node_os3.homedir)(), ".codex", "sessions");
3802
+ function toSessionSummary(file, meta, status) {
3803
+ return {
3804
+ sessionId: meta.sessionId,
3805
+ filePath: file.path,
3806
+ timestamp: meta.timestamp,
3807
+ model: meta.model,
3808
+ cwd: meta.cwd,
3809
+ gitBranch: meta.gitBranch,
3810
+ slug: meta.slug,
3811
+ firstMessage: meta.firstUserMessage,
3812
+ lastMessage: meta.lastUserMessage,
3813
+ turnCount: meta.turnCount,
3814
+ status,
3815
+ mtime: file.mtimeMs,
3816
+ source: file.path.startsWith(dirs.CODEX_SESSIONS_DIR + "/") ? "codex" : "claude"
3817
+ };
3818
+ }
2516
3819
  async function listCodexSessionFiles(cutoff) {
2517
3820
  const walk = async (dir, depth) => {
2518
3821
  if (depth > 4) return [];
@@ -2539,7 +3842,7 @@ async function listCodexSessionFiles(cutoff) {
2539
3842
  }
2540
3843
  return results;
2541
3844
  };
2542
- return walk(CODEX_SESSIONS_DIR, 0);
3845
+ return walk(dirs.CODEX_SESSIONS_DIR, 0);
2543
3846
  }
2544
3847
  async function listSessions(opts = {}) {
2545
3848
  const limit = Math.min(Math.max(1, opts.limit ?? 20), 100);
@@ -2552,29 +3855,31 @@ async function listSessions(opts = {}) {
2552
3855
  return [];
2553
3856
  }
2554
3857
  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);
3858
+ const [nested, codexFiles] = await Promise.all([
3859
+ Promise.all(
3860
+ projectDirs.map(async (projectDir) => {
3861
+ try {
3862
+ const files = await (0, import_promises5.readdir)(projectDir);
3863
+ const jsonlFiles = files.filter((f) => f.endsWith(".jsonl"));
3864
+ const statResults = await Promise.all(
3865
+ jsonlFiles.map(async (f) => {
3866
+ const filePath = (0, import_node_path6.join)(projectDir, f);
3867
+ try {
3868
+ const s = await (0, import_promises5.stat)(filePath);
3869
+ return s.mtimeMs >= cutoff ? { path: filePath, mtimeMs: s.mtimeMs } : null;
3870
+ } catch {
3871
+ return null;
3872
+ }
3873
+ })
3874
+ );
3875
+ return statResults.filter((r) => r !== null);
3876
+ } catch {
3877
+ return [];
3878
+ }
3879
+ })
3880
+ ),
3881
+ listCodexSessionFiles(cutoff)
3882
+ ]);
2578
3883
  const allFiles = [...nested.flat(), ...codexFiles];
2579
3884
  allFiles.sort((a, b) => b.mtimeMs - a.mtimeMs);
2580
3885
  const results = [];
@@ -2586,29 +3891,14 @@ async function listSessions(opts = {}) {
2586
3891
  getSessionStatus(file.path)
2587
3892
  ]);
2588
3893
  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
- });
3894
+ results.push(toSessionSummary(file, meta, statusInfo.status));
2604
3895
  } catch {
2605
3896
  }
2606
3897
  }
2607
3898
  return results;
2608
3899
  }
2609
3900
  async function currentSession(cwd) {
2610
- const projectDirName = cwd.replace(/[/.]/g, "-");
2611
- const projectDir = (0, import_node_path6.join)(dirs.PROJECTS_DIR, projectDirName);
3901
+ const projectDir = (0, import_node_path6.join)(dirs.PROJECTS_DIR, encodeClaudeDirName(cwd));
2612
3902
  let files;
2613
3903
  try {
2614
3904
  files = await (0, import_promises5.readdir)(projectDir);
@@ -2616,18 +3906,20 @@ async function currentSession(cwd) {
2616
3906
  files = [];
2617
3907
  }
2618
3908
  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);
3909
+ const [statResults, codexCandidates] = await Promise.all([
3910
+ Promise.all(
3911
+ jsonlFiles.map(async (f) => {
3912
+ const filePath = (0, import_node_path6.join)(projectDir, f);
3913
+ try {
3914
+ const s = await (0, import_promises5.stat)(filePath);
3915
+ return { path: filePath, mtimeMs: s.mtimeMs };
3916
+ } catch {
3917
+ return null;
3918
+ }
3919
+ })
3920
+ ),
3921
+ listCodexSessionFiles(0)
3922
+ ]);
2631
3923
  const codexMatches = [];
2632
3924
  for (const file of codexCandidates) {
2633
3925
  try {
@@ -2649,21 +3941,7 @@ async function currentSession(cwd) {
2649
3941
  getSessionMeta(latest.path),
2650
3942
  getSessionStatus(latest.path)
2651
3943
  ]);
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
- };
3944
+ return toSessionSummary(latest, meta, statusInfo.status);
2667
3945
  }
2668
3946
 
2669
3947
  // src/commands/index-cmd.ts
@@ -2820,27 +4098,29 @@ async function main() {
2820
4098
  const cmd = parseArgs(argv);
2821
4099
  let result;
2822
4100
  switch (cmd.command) {
2823
- case "search":
4101
+ case "search": {
2824
4102
  if (!cmd.args.query) {
2825
4103
  console.error(JSON.stringify({ error: "Usage: cogpit-memory search <query>" }));
2826
4104
  process.exit(1);
2827
4105
  }
2828
- result = await searchSessions(cmd.args.query, {
4106
+ const searchResult = await searchSessions(cmd.args.query, {
2829
4107
  sessionId: cmd.args.session,
2830
4108
  maxAge: cmd.args.maxAge,
2831
4109
  limit: cmd.args.limit,
2832
4110
  caseSensitive: cmd.args.caseSensitive
2833
4111
  });
2834
- if (result && !("error" in result) && (cmd.args.sessionLimit || cmd.args.hitsPerSession)) {
2835
- const sl = cmd.args.sessionLimit ?? result.results.length;
4112
+ if ("results" in searchResult && (cmd.args.sessionLimit || cmd.args.hitsPerSession)) {
4113
+ const sl = cmd.args.sessionLimit ?? searchResult.results.length;
2836
4114
  const hpp = cmd.args.hitsPerSession ?? Infinity;
2837
- result.results = result.results.slice(0, sl).map((sr) => ({
4115
+ searchResult.results = searchResult.results.slice(0, sl).map((sr) => ({
2838
4116
  ...sr,
2839
4117
  hits: sr.hits.slice(0, hpp)
2840
4118
  }));
2841
- result.returnedHits = result.results.reduce((n, sr) => n + sr.hits.length, 0);
4119
+ searchResult.returnedHits = searchResult.results.reduce((n, sr) => n + sr.hits.length, 0);
2842
4120
  }
4121
+ result = searchResult;
2843
4122
  break;
4123
+ }
2844
4124
  case "context":
2845
4125
  if (!cmd.args.sessionId) {
2846
4126
  console.error(JSON.stringify({ error: "Usage: cogpit-memory context <sessionId>" }));