cogpit-memory 0.1.10 → 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
@@ -67,10 +67,41 @@ function isSummaryMessage(msg) {
67
67
  function isQueueOperationMessage(msg) {
68
68
  return msg.type === "queue-operation";
69
69
  }
70
+ function isAttachmentMessage(msg) {
71
+ return msg.type === "attachment";
72
+ }
73
+ function isCompactSummaryMessage(msg) {
74
+ return msg.type === "user" && msg.isCompactSummary === true;
75
+ }
70
76
  function isCompactBoundary(msg) {
71
77
  return msg.type === "system" && msg.subtype === "compact_boundary";
72
78
  }
73
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
+
74
105
  // src/lib/turnBuilder.ts
75
106
  function extractTextFromContent(content) {
76
107
  if (typeof content === "string") return content;
@@ -87,7 +118,58 @@ function extractToolResultText(content) {
87
118
  function isVisibleQueuedPrompt(content) {
88
119
  if (!content?.trim()) return false;
89
120
  const trimmed = content.trimStart();
90
- return !trimmed.startsWith("<task-notification>") && !trimmed.startsWith("<local-command-");
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;
91
173
  }
92
174
  function mergeTokenUsage(existing, incoming) {
93
175
  if (!existing) {
@@ -101,35 +183,30 @@ function mergeTokenUsage(existing, incoming) {
101
183
  speed: incoming.speed ?? existing.speed
102
184
  };
103
185
  }
104
- function buildCompactionSummary(turns, title) {
105
- if (turns.length === 0) return title;
106
- const toolCounts = {};
107
- for (const turn of turns) {
108
- for (const tc of turn.toolCalls) {
109
- toolCounts[tc.name] = (toolCounts[tc.name] || 0) + 1;
110
- }
111
- }
112
- const prompts = [];
113
- for (const turn of turns) {
114
- if (!turn.userMessage) continue;
115
- const text = extractTextFromContent(
116
- typeof turn.userMessage === "string" ? turn.userMessage : turn.userMessage
117
- );
118
- const firstLine = text.split("\n")[0].trim();
119
- if (firstLine.length > 0) {
120
- prompts.push(firstLine.length > 120 ? firstLine.slice(0, 117) + "..." : firstLine);
121
- }
122
- }
123
- const topTools = Object.entries(toolCounts).sort((a, b) => b[1] - a[1]).slice(0, 5).map(([name, count]) => `${name} x${count}`).join(", ");
124
- const parts = [`**${title}**`, `${turns.length} turns compacted`];
125
- if (topTools) parts.push(`Tools: ${topTools}`);
126
- if (prompts.length > 0) {
127
- parts.push("Prompts:");
128
- const shown = prompts.slice(0, 6);
129
- for (const p of shown) parts.push(`- ${p}`);
130
- if (prompts.length > 6) parts.push(`- ...and ${prompts.length - 6} more`);
131
- }
132
- return parts.join("\n");
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
+ };
133
210
  }
134
211
  function groupPlanModeBlocks(blocks) {
135
212
  const result = [];
@@ -194,7 +271,7 @@ function groupPlanModeBlocks(blocks) {
194
271
  embedded.push(...next.toolCalls);
195
272
  j++;
196
273
  }
197
- } else if (next.kind === "hook_event" || next.kind === "text" || next.kind === "queued_prompt") {
274
+ } else if (next.kind === "hook_event" || next.kind === "text" || next.kind === "queued_prompt" || next.kind === "agent_message") {
198
275
  passthroughBlocks.push(next);
199
276
  j++;
200
277
  } else {
@@ -215,10 +292,55 @@ function groupPlanModeBlocks(blocks) {
215
292
  }
216
293
  return result;
217
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
+ }
330
+ }
331
+ }
332
+ return repaired;
333
+ }
218
334
  function buildTurns(messages) {
219
335
  const turns = [];
220
336
  let current = null;
221
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
+ }
222
344
  let pendingRecap = null;
223
345
  const pendingToolUses = /* @__PURE__ */ new Map();
224
346
  const seenMessageIds = /* @__PURE__ */ new Set();
@@ -226,6 +348,41 @@ function buildTurns(messages) {
226
348
  const agentBlockMap = /* @__PURE__ */ new Map();
227
349
  const backgroundAgentParentIds = /* @__PURE__ */ new Set();
228
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
+ }
229
386
  function flushSubAgentMessages(parentId) {
230
387
  if (!current) return;
231
388
  const agentMsgs = subAgentMap.get(parentId);
@@ -273,18 +430,20 @@ function buildTurns(messages) {
273
430
  for (const msg of messages) {
274
431
  if (isSummaryMessage(msg)) {
275
432
  finalizeTurn2();
276
- pendingCompaction = buildCompactionSummary(
277
- turns,
278
- msg.summary ?? "Conversation compacted"
279
- );
433
+ pendingCompaction = { summary: msg.summary };
280
434
  continue;
281
435
  }
282
436
  if (isCompactBoundary(msg)) {
283
437
  finalizeTurn2();
284
- pendingCompaction = buildCompactionSummary(
285
- turns,
286
- msg.content ?? "Conversation compacted"
287
- );
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;
288
447
  continue;
289
448
  }
290
449
  if (isSystemMessage(msg) && msg.subtype === "away_summary" && msg.content) {
@@ -293,14 +452,44 @@ function buildTurns(messages) {
293
452
  }
294
453
  if (isQueueOperationMessage(msg)) {
295
454
  if (current && msg.operation === "enqueue" && isVisibleQueuedPrompt(msg.content)) {
296
- current.contentBlocks.push({
297
- kind: "queued_prompt",
455
+ const parsed = parseAgentEnvelope(msg.content);
456
+ pendingQueuedPrompts.push({
457
+ turn: current,
298
458
  content: msg.content,
299
- timestamp: msg.timestamp
459
+ timestamp: msg.timestamp,
460
+ sender: parsed.sender,
461
+ body: parsed.body
300
462
  });
463
+ noteEnqueueSourced(current, msg.content);
301
464
  }
302
465
  continue;
303
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
+ }
478
+ continue;
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
+ }
304
493
  if (isUserMessage(msg) && !msg.isMeta) {
305
494
  const content = msg.message.content;
306
495
  if (typeof content !== "string" && Array.isArray(content)) {
@@ -363,24 +552,24 @@ function buildTurns(messages) {
363
552
  continue;
364
553
  }
365
554
  }
366
- finalizeTurn2();
367
- current = {
368
- id: msg.uuid ?? crypto.randomUUID(),
369
- userMessage: msg.message.content,
370
- contentBlocks: [],
371
- thinking: [],
372
- assistantText: [],
373
- toolCalls: [],
374
- subAgentActivity: [],
375
- timestamp: msg.timestamp ?? "",
376
- durationMs: null,
377
- tokenUsage: null,
378
- model: null
379
- };
380
- if (pendingCompaction) {
381
- current.compactionSummary = pendingCompaction;
382
- pendingCompaction = null;
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;
383
569
  }
570
+ finalizeTurn2();
571
+ current = createTurn(msg, msg.message.content);
572
+ attachPendingCompaction(current);
384
573
  if (pendingRecap) {
385
574
  current.contentBlocks.push({ kind: "recap", content: pendingRecap.content, timestamp: pendingRecap.timestamp });
386
575
  pendingRecap = null;
@@ -406,21 +595,12 @@ function buildTurns(messages) {
406
595
  };
407
596
  var flushToolCalls = flushToolCalls2, flushThinking = flushThinking2;
408
597
  if (!current) {
409
- current = {
410
- id: msg.uuid ?? crypto.randomUUID(),
411
- userMessage: null,
412
- contentBlocks: [],
413
- thinking: [],
414
- assistantText: [],
415
- toolCalls: [],
416
- subAgentActivity: [],
417
- timestamp: msg.timestamp ?? "",
418
- durationMs: null,
419
- tokenUsage: null,
420
- model: null
421
- };
598
+ current = createTurn(msg, null);
599
+ attachPendingCompaction(current);
422
600
  }
423
601
  current.model = msg.message.model;
602
+ if (msg.effort) current.effort = msg.effort;
603
+ current.attribution = mergeAttribution(current.attribution, msg);
424
604
  const msgId = msg.message.id;
425
605
  if (!seenMessageIds.has(msgId)) {
426
606
  seenMessageIds.add(msgId);
@@ -610,145 +790,15 @@ function buildTurns(messages) {
610
790
  continue;
611
791
  }
612
792
  if (isSystemMessage(msg) && msg.subtype === "turn_duration" && current) {
613
- current.durationMs = msg.durationMs ?? null;
793
+ if (msg.durationMs !== void 0 && msg.durationMs !== null) {
794
+ current.durationMs = (current.durationMs ?? 0) + msg.durationMs;
795
+ }
614
796
  continue;
615
797
  }
616
798
  }
799
+ flushPendingQueuedPrompts();
617
800
  finalizeTurn2();
618
- return turns;
619
- }
620
-
621
- // src/lib/pricingTiers.ts
622
- var TIER_HAIKU_35 = { input: 0.8, output: 4, cacheWrite: 1, cacheRead: 0.08, webSearch: 0.01 };
623
- var TIER_HAIKU_45 = { input: 1, output: 5, cacheWrite: 1.25, cacheRead: 0.1, webSearch: 0.01 };
624
- var TIER_SONNET = { input: 3, output: 15, cacheWrite: 3.75, cacheRead: 0.3, webSearch: 0.01 };
625
- var TIER_OPUS_MID = { input: 5, output: 25, cacheWrite: 6.25, cacheRead: 0.5, webSearch: 0.01 };
626
- var TIER_FRONTIER = { input: 10, output: 50, cacheWrite: 12.5, cacheRead: 1, webSearch: 0.01 };
627
- var TIER_OPUS_LEGACY = { input: 15, output: 75, cacheWrite: 18.75, cacheRead: 1.5, webSearch: 0.01 };
628
- var TIER_GPT_56_SOL = { input: 5, output: 30, cacheWrite: 6.25, cacheRead: 0.5, webSearch: 0.01 };
629
- var TIER_GPT_56_TERRA = { input: 2.5, output: 15, cacheWrite: 3.125, cacheRead: 0.25, webSearch: 0.01 };
630
- var TIER_GPT_56_LUNA = { input: 1, output: 6, cacheWrite: 1.25, cacheRead: 0.1, webSearch: 0.01 };
631
- var TIER_GPT_55 = { input: 5, output: 30, cacheWrite: 5, cacheRead: 0.5, webSearch: 0.01 };
632
- var TIER_GPT_54 = { input: 2.5, output: 15, cacheWrite: 2.5, cacheRead: 0.25, webSearch: 0.01 };
633
- var TIER_GPT_54_MINI = { input: 0.75, output: 4.5, cacheWrite: 0.75, cacheRead: 0.075, webSearch: 0.01 };
634
- var TIER_FAST = { input: 30, output: 150, cacheWrite: 37.5, cacheRead: 3, webSearch: 0.01 };
635
- var MODEL_TIERS = [
636
- // OpenAI GPT models. Keep variants before their shared family prefixes.
637
- { match: "gpt-5.6-sol", tier: TIER_GPT_56_SOL },
638
- { match: "gpt-5.6-terra", tier: TIER_GPT_56_TERRA },
639
- { match: "gpt-5.6-luna", tier: TIER_GPT_56_LUNA },
640
- { match: "gpt-5.6", tier: TIER_GPT_56_SOL },
641
- { match: "gpt-5.4-mini", tier: TIER_GPT_54_MINI },
642
- { match: "gpt-5.5", tier: TIER_GPT_55 },
643
- { match: "gpt-5.4", tier: TIER_GPT_54 },
644
- // Frontier (Fable 5 / Mythos 5 / Opus 4.8)
645
- { match: "fable-5", tier: TIER_FRONTIER },
646
- { match: "mythos-5", tier: TIER_FRONTIER },
647
- { match: "opus-4-8", tier: TIER_FRONTIER },
648
- // Haiku
649
- { match: "haiku-4-5", tier: TIER_HAIKU_45 },
650
- { match: "haiku-4-0", tier: TIER_HAIKU_35 },
651
- { match: "3-5-haiku", tier: TIER_HAIKU_35 },
652
- // Sonnet (3.5 through 4.6 — all one tier in current CC)
653
- { match: "sonnet-4-7", tier: TIER_SONNET },
654
- { match: "sonnet-4-6", tier: TIER_SONNET },
655
- { match: "sonnet-4-5", tier: TIER_SONNET },
656
- { match: "sonnet-4-0", tier: TIER_SONNET },
657
- { match: "3-7-sonnet", tier: TIER_SONNET },
658
- { match: "3-5-sonnet", tier: TIER_SONNET },
659
- // Opus 4.5–4.7
660
- { match: "opus-4-7", tier: TIER_OPUS_MID },
661
- { match: "opus-4-6", tier: TIER_OPUS_MID },
662
- { match: "opus-4-5", tier: TIER_OPUS_MID },
663
- // Opus legacy (4.0, 4.1)
664
- { match: "opus-4-1", tier: TIER_OPUS_LEGACY },
665
- { match: "opus-4-0", tier: TIER_OPUS_LEGACY }
666
- ];
667
- var FAST_MODE_MODELS = ["opus-4-6", "opus-4-7"];
668
- var DEFAULT_TIER = TIER_OPUS_MID;
669
- var FAMILY_FALLBACKS = [
670
- { match: "fable", tier: TIER_FRONTIER },
671
- { match: "mythos", tier: TIER_FRONTIER },
672
- { match: "haiku", tier: TIER_HAIKU_45 },
673
- { match: "sonnet", tier: TIER_SONNET },
674
- { match: "opus", tier: TIER_OPUS_MID }
675
- ];
676
- function resolveTier(model, speed) {
677
- if (speed === "fast" && FAST_MODE_MODELS.some((m) => model.includes(m))) {
678
- return TIER_FAST;
679
- }
680
- for (const entry of MODEL_TIERS) {
681
- if (model.includes(entry.match)) return entry.tier;
682
- }
683
- for (const entry of FAMILY_FALLBACKS) {
684
- if (model.includes(entry.match)) return entry.tier;
685
- }
686
- return DEFAULT_TIER;
687
- }
688
- function hasKnownPricing(model) {
689
- if (!model) return true;
690
- const normalized = model.toLowerCase();
691
- if (!normalized.startsWith("gpt-")) return true;
692
- return MODEL_TIERS.some((entry) => normalized.includes(entry.match));
693
- }
694
-
695
- // src/lib/token-costs.ts
696
- var CHARS_PER_TOKEN = 4;
697
- function calculateCost(c) {
698
- if (!hasKnownPricing(c.model)) return Number.NaN;
699
- const p = resolveTier(c.model ?? "", c.speed);
700
- return c.inputTokens / 1e6 * p.input + c.outputTokens / 1e6 * p.output + c.cacheWriteTokens / 1e6 * p.cacheWrite + c.cacheReadTokens / 1e6 * p.cacheRead + (c.webSearchRequests ?? 0) * p.webSearch;
701
- }
702
- function charsToTokens(chars) {
703
- return Math.ceil(chars / CHARS_PER_TOKEN);
704
- }
705
- function totalLength(strings) {
706
- let n = 0;
707
- for (const s of strings) n += s.length;
708
- return n;
709
- }
710
- function totalToolInputLength(toolCalls) {
711
- let n = 0;
712
- for (const tc of toolCalls) n += JSON.stringify(tc.input).length;
713
- return n;
714
- }
715
- function estimateThinkingTokens(turn) {
716
- return charsToTokens(totalLength(turn.thinking.map((b) => b.thinking)));
717
- }
718
- function estimateVisibleOutputTokens(turn) {
719
- return charsToTokens(totalLength(turn.assistantText) + totalToolInputLength(turn.toolCalls));
720
- }
721
- function estimateTotalOutputTokens(turn) {
722
- const estimated = estimateThinkingTokens(turn) + estimateVisibleOutputTokens(turn);
723
- return Math.max(estimated, turn.tokenUsage?.output_tokens ?? 0);
724
- }
725
- function estimateSubAgentOutput(sa) {
726
- const chars = totalLength(sa.thinking) + totalLength(sa.text) + totalToolInputLength(sa.toolCalls);
727
- return Math.max(charsToTokens(chars), sa.tokenUsage?.output_tokens ?? 0);
728
- }
729
- function calculateTurnCostEstimated(turn) {
730
- if (!turn.tokenUsage) return 0;
731
- const u = turn.tokenUsage;
732
- return calculateCost({
733
- model: turn.model,
734
- inputTokens: u.input_tokens,
735
- outputTokens: estimateTotalOutputTokens(turn),
736
- cacheWriteTokens: u.cache_creation_input_tokens ?? 0,
737
- cacheReadTokens: u.cache_read_input_tokens ?? 0,
738
- speed: u.speed
739
- });
740
- }
741
- function calculateSubAgentCostEstimated(sa) {
742
- if (!sa.tokenUsage) return 0;
743
- const u = sa.tokenUsage;
744
- return calculateCost({
745
- model: sa.model,
746
- inputTokens: u.input_tokens,
747
- outputTokens: estimateSubAgentOutput(sa),
748
- cacheWriteTokens: u.cache_creation_input_tokens ?? 0,
749
- cacheReadTokens: u.cache_read_input_tokens ?? 0,
750
- speed: u.speed
751
- });
801
+ return pairAgentMessageReplies(turns);
752
802
  }
753
803
 
754
804
  // src/lib/sessionStats.ts
@@ -760,12 +810,11 @@ function countToolCalls(toolCalls, counts) {
760
810
  }
761
811
  return errors;
762
812
  }
763
- function addUsageToStats(stats, usage, estimatedOutput, cost) {
813
+ function addUsageToStats(stats, usage) {
764
814
  stats.totalInputTokens += usage.input_tokens;
765
- stats.totalOutputTokens += estimatedOutput;
815
+ stats.totalOutputTokens += usage.output_tokens;
766
816
  stats.totalCacheCreationTokens += usage.cache_creation_input_tokens ?? 0;
767
817
  stats.totalCacheReadTokens += usage.cache_read_input_tokens ?? 0;
768
- stats.totalCostUSD += cost;
769
818
  }
770
819
  function createEmptySessionStats(turnCount) {
771
820
  return {
@@ -773,7 +822,6 @@ function createEmptySessionStats(turnCount) {
773
822
  totalOutputTokens: 0,
774
823
  totalCacheCreationTokens: 0,
775
824
  totalCacheReadTokens: 0,
776
- totalCostUSD: 0,
777
825
  toolCallCounts: {},
778
826
  errorCount: 0,
779
827
  totalDurationMs: 0,
@@ -781,34 +829,20 @@ function createEmptySessionStats(turnCount) {
781
829
  };
782
830
  }
783
831
  function computeStats(turns) {
784
- const stats = {
785
- totalInputTokens: 0,
786
- totalOutputTokens: 0,
787
- totalCacheCreationTokens: 0,
788
- totalCacheReadTokens: 0,
789
- totalCostUSD: 0,
790
- toolCallCounts: {},
791
- errorCount: 0,
792
- totalDurationMs: 0,
793
- turnCount: turns.length
794
- };
832
+ const stats = createEmptySessionStats(turns.length);
795
833
  for (const turn of turns) {
796
- if (turn.tokenUsage) {
797
- addUsageToStats(stats, turn.tokenUsage, estimateTotalOutputTokens(turn), calculateTurnCostEstimated(turn));
798
- }
834
+ if (turn.tokenUsage) addUsageToStats(stats, turn.tokenUsage);
799
835
  if (turn.durationMs) stats.totalDurationMs += turn.durationMs;
800
836
  stats.errorCount += countToolCalls(turn.toolCalls, stats.toolCallCounts);
801
837
  for (const sa of turn.subAgentActivity) {
802
838
  stats.errorCount += countToolCalls(sa.toolCalls, stats.toolCallCounts);
803
- if (sa.tokenUsage) {
804
- addUsageToStats(stats, sa.tokenUsage, estimateSubAgentOutput(sa), calculateSubAgentCostEstimated(sa));
805
- }
839
+ if (sa.tokenUsage) addUsageToStats(stats, sa.tokenUsage);
806
840
  }
807
841
  }
808
842
  return stats;
809
843
  }
810
844
 
811
- // src/lib/codex-patches.ts
845
+ // src/lib/codex-exec.ts
812
846
  var JS_SIMPLE_ESCAPES = {
813
847
  "0": "\0",
814
848
  b: "\b",
@@ -874,6 +908,8 @@ function readJsStringLiteral(source, startIndex) {
874
908
  }
875
909
  return null;
876
910
  }
911
+
912
+ // src/lib/codex-patches.ts
877
913
  function isApplyPatchText(value) {
878
914
  return value.includes("*** Begin Patch") && value.includes("*** End Patch");
879
915
  }
@@ -907,7 +943,9 @@ function extractApplyPatchInputs(execSource) {
907
943
  }
908
944
  function resolveCodexPatchPath(filePath, cwd) {
909
945
  if (!cwd || filePath.startsWith("/") || /^[A-Za-z]:[\\/]/.test(filePath)) return filePath;
910
- return `${cwd.replace(/[\\/]+$/, "")}/${filePath.replace(/^\.\//, "")}`;
946
+ const base = cwd.replace(/[\\/]+$/, "");
947
+ const relative = filePath.replace(/^\.[\\/]/, "");
948
+ return /^[A-Za-z]:[\\/]/.test(cwd) ? `${base}\\${relative.replace(/\//g, "\\")}` : `${base}/${relative}`;
911
949
  }
912
950
  function parseApplyPatch(patchText, callId, timestamp, cwd = "") {
913
951
  const toolCalls = [];
@@ -1034,6 +1072,24 @@ function findFailedNestedPatchCallIds(output, wrapperIsError, calls) {
1034
1072
  function isObject(value) {
1035
1073
  return typeof value === "object" && value !== null;
1036
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
+ }
1037
1093
  function inferToolError(output) {
1038
1094
  if (!output) return false;
1039
1095
  const exitMatch = output.match(/Process exited with code (\d+)/);
@@ -1159,28 +1215,6 @@ function buildCodexUserContent(message, images, audio = [], localImages = [], lo
1159
1215
  ...text ? [{ type: "text", text }] : []
1160
1216
  ];
1161
1217
  }
1162
- function normalizeFunctionName(rawName) {
1163
- const leaf = rawName.split(/(?:__|[.:/])+/).filter(Boolean).at(-1) ?? rawName;
1164
- const aliases = {
1165
- spawnAgent: "spawn_agent",
1166
- waitAgent: "wait_agent",
1167
- sendMessage: "send_message",
1168
- followupTask: "followup_task",
1169
- listAgents: "list_agents",
1170
- interruptAgent: "interrupt_agent"
1171
- };
1172
- const canonicalCollaborationNames = /* @__PURE__ */ new Set([
1173
- "spawn_agent",
1174
- "wait_agent",
1175
- "send_message",
1176
- "followup_task",
1177
- "list_agents",
1178
- "interrupt_agent"
1179
- ]);
1180
- if (aliases[leaf]) return aliases[leaf];
1181
- if (canonicalCollaborationNames.has(leaf)) return leaf;
1182
- return rawName;
1183
- }
1184
1218
  function extractInterAgentMessage(payload) {
1185
1219
  const rawText = extractMessageText(payload, "input_text");
1186
1220
  if (!rawText) return { messageType: null, text: "" };
@@ -1194,7 +1228,7 @@ function extractInterAgentMessage(payload) {
1194
1228
  return { messageType, text };
1195
1229
  }
1196
1230
  function agentNameFromPath(agentPath) {
1197
- const segments = agentPath.split("/").filter(Boolean);
1231
+ const segments = agentPath.split(/[\\/]/).filter(Boolean);
1198
1232
  return segments.at(-1) ?? null;
1199
1233
  }
1200
1234
  function readableAgentPrompt(value) {
@@ -1287,9 +1321,35 @@ function parseToolInput(argumentsText) {
1287
1321
  return { raw: argumentsText };
1288
1322
  }
1289
1323
  }
1290
- function createTurn(turnId, timestamp, model) {
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
+ };
1349
+ }
1350
+ function createTurn2(turnId, timestamp, model) {
1291
1351
  return {
1292
- id: turnId || randomTurnId("codex-turn"),
1352
+ id: turnId ? `${turnId}@${timestamp}` : randomTurnId("codex-turn"),
1293
1353
  userMessage: null,
1294
1354
  contentBlocks: [],
1295
1355
  thinking: [],
@@ -1311,6 +1371,17 @@ function extractPromptFromRecord(record) {
1311
1371
  }
1312
1372
  return "";
1313
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
+ }
1314
1385
  function extractMetadataFromRecords(records) {
1315
1386
  let sessionId = "";
1316
1387
  let version = "";
@@ -1477,14 +1548,35 @@ function parseCodexSession(jsonlText, options) {
1477
1548
  let currentModel = metadata.model || null;
1478
1549
  let lastTurnTimestamp = "";
1479
1550
  let pendingCompaction = null;
1551
+ let sawTurnStart = false;
1480
1552
  function createActiveTurn(turnId, timestamp, model) {
1481
- const turn = createTurn(turnId, timestamp, model);
1553
+ const turn = createTurn2(turnId, timestamp, model);
1554
+ if (!sawTurnStart) turn.isFragment = true;
1482
1555
  if (pendingCompaction) {
1483
1556
  turn.compactionSummary = pendingCompaction;
1484
1557
  pendingCompaction = null;
1485
1558
  }
1486
1559
  return turn;
1487
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
+ }
1488
1580
  for (const record of records) {
1489
1581
  const payload = isObject2(record.payload) ? record.payload : void 0;
1490
1582
  const timestamp = record.timestamp ?? "";
@@ -1500,12 +1592,14 @@ function parseCodexSession(jsonlText, options) {
1500
1592
  currentTurnId = typeof payload?.turn_id === "string" ? payload.turn_id : null;
1501
1593
  currentModel = typeof payload?.model === "string" ? payload.model : currentModel;
1502
1594
  lastTurnTimestamp = timestamp;
1595
+ sawTurnStart = true;
1503
1596
  continue;
1504
1597
  }
1505
1598
  if (record.type === "event_msg" && payload?.type === "user_message" && typeof payload.message === "string") {
1506
1599
  if (current && (current.assistantText.length > 0 || current.toolCalls.length > 0 || current.thinking.length > 0)) {
1507
1600
  current = finalizeTurn(turns, current, lastTurnTimestamp);
1508
1601
  }
1602
+ sawTurnStart = true;
1509
1603
  current ??= createActiveTurn(currentTurnId, timestamp, currentModel);
1510
1604
  const localImages = (Array.isArray(payload.local_images) ? payload.local_images : []).filter(
1511
1605
  (p) => typeof p === "string" && p.length > 0
@@ -1520,10 +1614,12 @@ function parseCodexSession(jsonlText, options) {
1520
1614
  localImages,
1521
1615
  localAudio
1522
1616
  );
1617
+ current.isFragment = false;
1523
1618
  current.timestamp = current.timestamp || timestamp;
1524
1619
  lastTurnTimestamp = timestamp;
1525
1620
  continue;
1526
1621
  }
1622
+ if (!current && !recordOpensTurn(record, payload)) continue;
1527
1623
  current ??= createActiveTurn(currentTurnId, timestamp, currentModel);
1528
1624
  if (!current.model && currentModel) current.model = currentModel;
1529
1625
  if (!current.timestamp) current.timestamp = timestamp;
@@ -1540,22 +1636,29 @@ function parseCodexSession(jsonlText, options) {
1540
1636
  const input = {};
1541
1637
  if (typeof payload.query === "string" && payload.query) input.query = payload.query;
1542
1638
  if (isObject2(payload.action)) input.action = payload.action;
1543
- const existing = webSearchCalls.get(payload.call_id);
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);
1544
1649
  if (existing) {
1545
- existing.input = { ...existing.input, ...input };
1546
- existing.result = "completed";
1547
- existing.isError = false;
1548
- } else {
1549
- const toolCall = {
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, {
1550
1655
  id: payload.call_id,
1551
- name: "WebSearch",
1656
+ name: `mcp__${server}__${tool}`,
1552
1657
  input,
1553
- result: "completed",
1554
- isError: false,
1658
+ result: result.text,
1659
+ isError: result.isError,
1555
1660
  timestamp
1556
- };
1557
- webSearchCalls.set(toolCall.id, toolCall);
1558
- appendToolCall(current, toolCall, timestamp);
1661
+ }, timestamp);
1559
1662
  }
1560
1663
  continue;
1561
1664
  }
@@ -1609,6 +1712,8 @@ function parseCodexSession(jsonlText, options) {
1609
1712
  const audio = extractResponseMessageAudio(payload);
1610
1713
  if (text || images.length > 0 || audio.length > 0) {
1611
1714
  current.userMessage = buildCodexUserContent(text, images, audio);
1715
+ current.isFragment = false;
1716
+ sawTurnStart = true;
1612
1717
  }
1613
1718
  continue;
1614
1719
  }
@@ -1616,25 +1721,38 @@ function parseCodexSession(jsonlText, options) {
1616
1721
  const input = {};
1617
1722
  if (isObject2(payload.action)) input.action = payload.action;
1618
1723
  const status = typeof payload.status === "string" ? payload.status : "completed";
1619
- const existing = webSearchCalls.get(payload.id);
1620
- if (existing) {
1621
- existing.input = { ...existing.input, ...input };
1622
- existing.result = status;
1623
- existing.isError = status === "failed";
1624
- } else {
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) {
1625
1730
  const toolCall = {
1626
- id: payload.id,
1627
- name: "WebSearch",
1628
- input,
1629
- result: status,
1630
- isError: status === "failed",
1731
+ id: callId,
1732
+ name: "ToolSearch",
1733
+ input: isObject2(payload.arguments) ? payload.arguments : {},
1734
+ result: null,
1735
+ isError: false,
1631
1736
  timestamp
1632
1737
  };
1633
- webSearchCalls.set(toolCall.id, toolCall);
1738
+ pendingToolCalls.set(callId, toolCall);
1634
1739
  appendToolCall(current, toolCall, timestamp);
1635
1740
  }
1636
1741
  continue;
1637
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
+ }
1638
1756
  if (payload.type === "agent_message" && typeof payload.author === "string") {
1639
1757
  const author = payload.author;
1640
1758
  const recipient = typeof payload.recipient === "string" ? payload.recipient : null;
@@ -2527,7 +2645,11 @@ async function matchSubagentToMember(leadSessionId, subagentFileName, members) {
2527
2645
  }
2528
2646
  return null;
2529
2647
  }
2530
- 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();
2531
2653
 
2532
2654
  // src/commands/search.ts
2533
2655
  async function searchSessions(query, opts, searchIndex) {
@@ -2879,6 +3001,7 @@ var import_promises3 = require("node:fs/promises");
2879
3001
  var import_node_path5 = require("node:path");
2880
3002
  var RESULT_TRUNCATE_LIMIT = 1e4;
2881
3003
  var L1_RESPONSE_LIMIT = 15e4;
3004
+ var COMPACTION_SUMMARY_PREVIEW = 400;
2882
3005
  function extractUserMessageText(userMessage) {
2883
3006
  if (userMessage === null) return null;
2884
3007
  if (typeof userMessage === "string") return userMessage;
@@ -2970,9 +3093,13 @@ function mapTurnToSummary(turn, turnIndex) {
2970
3093
  subAgents,
2971
3094
  hasThinking: turn.thinking.length > 0,
2972
3095
  isError: turn.toolCalls.some((tc) => tc.isError),
2973
- compactionSummary: turn.compactionSummary ?? null
3096
+ compactionSummary: truncateCompactionSummary(turn.compactionSummary)
2974
3097
  };
2975
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
+ }
2976
3103
  function mapTurnToDetail(session, turnIndex) {
2977
3104
  const turn = session.turns[turnIndex];
2978
3105
  const contentBlocks = turn.contentBlocks.map((block) => mapContentBlock(block));
@@ -2980,6 +3107,7 @@ function mapTurnToDetail(session, turnIndex) {
2980
3107
  sessionId: session.sessionId,
2981
3108
  turnIndex,
2982
3109
  userMessage: extractUserMessageText(turn.userMessage),
3110
+ compactionSummary: turn.compactionSummary ?? null,
2983
3111
  contentBlocks,
2984
3112
  tokenUsage: turn.tokenUsage ? { input: turn.tokenUsage.input_tokens, output: turn.tokenUsage.output_tokens } : null,
2985
3113
  model: turn.model,
@@ -3013,6 +3141,14 @@ function mapContentBlock(block) {
3013
3141
  };
3014
3142
  case "queued_prompt":
3015
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
+ };
3016
3152
  case "hook_event":
3017
3153
  return { kind: "hook_event", events: block.events, timestamp: block.timestamp ?? null };
3018
3154
  case "plan_mode":
@@ -3026,6 +3162,12 @@ function mapContentBlock(block) {
3026
3162
  };
3027
3163
  case "recap":
3028
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
+ }
3029
3171
  }
3030
3172
  }
3031
3173
  async function findSubagentFile(parentJsonlPath, agentId) {
@@ -3164,17 +3306,99 @@ var import_node_path6 = require("node:path");
3164
3306
  var import_promises4 = require("node:fs/promises");
3165
3307
 
3166
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
+ }
3167
3386
  function deriveSessionStatus(rawMessages) {
3168
3387
  const firstType = rawMessages[0]?.type;
3169
3388
  if (firstType === "session_meta" || firstType === "turn_context" || firstType === "event_msg" || firstType === "response_item") {
3170
3389
  return deriveCodexSessionStatus(rawMessages);
3171
3390
  }
3172
3391
  let pendingEnqueues = 0;
3392
+ let backgroundWork = null;
3393
+ let deliveringNotification = false;
3173
3394
  function result(status, toolName) {
3174
3395
  const info = { status, pendingQueue: Math.max(0, pendingEnqueues) };
3175
3396
  if (toolName) info.toolName = toolName;
3176
3397
  return info;
3177
3398
  }
3399
+ function getBackgroundWork() {
3400
+ return backgroundWork ??= collectClaudeBackgroundWork(rawMessages);
3401
+ }
3178
3402
  for (let i = rawMessages.length - 1; i >= 0; i--) {
3179
3403
  const msg = rawMessages[i];
3180
3404
  if (msg.type === "queue-operation") continue;
@@ -3191,15 +3415,26 @@ function deriveSessionStatus(rawMessages) {
3191
3415
  for (let i = rawMessages.length - 1; i >= 0; i--) {
3192
3416
  const msg = rawMessages[i];
3193
3417
  if (msg.type === "queue-operation") {
3418
+ if (typeof msg.content === "string" && msg.content.includes(TASK_NOTIFICATION_MARKER)) continue;
3194
3419
  const op = msg.operation;
3195
3420
  if (op === "enqueue") pendingEnqueues++;
3196
3421
  else if (op === "dequeue" || op === "remove") pendingEnqueues--;
3197
3422
  continue;
3198
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
+ }
3199
3429
  if (msg.type === "assistant") {
3200
3430
  const message = msg.message;
3201
3431
  const stopReason = message?.stop_reason;
3202
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
+ }
3203
3438
  let hasActivity = false;
3204
3439
  for (let j = i - 1; j >= 0; j--) {
3205
3440
  const m = rawMessages[j];
@@ -3234,14 +3469,124 @@ function deriveSessionStatus(rawMessages) {
3234
3469
  }
3235
3470
  return { status: "idle" };
3236
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
+ }
3237
3579
  function deriveCodexSessionStatus(rawMessages) {
3238
3580
  for (let i = rawMessages.length - 1; i >= 0; i--) {
3239
3581
  const msg = rawMessages[i];
3240
3582
  if (msg.type === "event_msg") {
3241
3583
  const payload = msg.payload;
3242
3584
  switch (payload?.type) {
3243
- case "task_complete":
3585
+ case "task_complete": {
3586
+ const pending = collectCodexPendingAgents(rawMessages);
3587
+ if (pending.length > 0) return awaitingAgentsResult(pending, 0);
3244
3588
  return { status: "completed" };
3589
+ }
3245
3590
  case "task_started":
3246
3591
  return { status: "processing" };
3247
3592
  case "agent_message":
@@ -3254,7 +3599,7 @@ function deriveCodexSessionStatus(rawMessages) {
3254
3599
  const payload = msg.payload;
3255
3600
  if (!payload) continue;
3256
3601
  if (payload.type === "function_call") {
3257
- return { status: "tool_use", toolName: payload.name };
3602
+ return { status: "tool_use", toolName: payload.name ? normalizeFunctionName(payload.name) : payload.name };
3258
3603
  }
3259
3604
  if (payload.type === "message") {
3260
3605
  if (payload.role === "assistant") return { status: "thinking" };
@@ -3553,8 +3898,7 @@ async function listSessions(opts = {}) {
3553
3898
  return results;
3554
3899
  }
3555
3900
  async function currentSession(cwd) {
3556
- const projectDirName = cwd.replace(/[/.]/g, "-");
3557
- const projectDir = (0, import_node_path6.join)(dirs.PROJECTS_DIR, projectDirName);
3901
+ const projectDir = (0, import_node_path6.join)(dirs.PROJECTS_DIR, encodeClaudeDirName(cwd));
3558
3902
  let files;
3559
3903
  try {
3560
3904
  files = await (0, import_promises5.readdir)(projectDir);