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/index.js CHANGED
@@ -76,10 +76,41 @@ function isSummaryMessage(msg) {
76
76
  function isQueueOperationMessage(msg) {
77
77
  return msg.type === "queue-operation";
78
78
  }
79
+ function isAttachmentMessage(msg) {
80
+ return msg.type === "attachment";
81
+ }
82
+ function isCompactSummaryMessage(msg) {
83
+ return msg.type === "user" && msg.isCompactSummary === true;
84
+ }
79
85
  function isCompactBoundary(msg) {
80
86
  return msg.type === "system" && msg.subtype === "compact_boundary";
81
87
  }
82
88
 
89
+ // src/lib/agentEnvelope.ts
90
+ var ENVELOPE_RE = /<(agent-message|teammate-message)([^>]*)>([\s\S]*?)<\/\1>/g;
91
+ var SENDER_PATTERNS = [
92
+ /\bteammate_id=(?:"([^"]*)"|'([^']*)')/,
93
+ /\bfrom=(?:"([^"]*)"|'([^']*)')/
94
+ ];
95
+ function readSender(attrs) {
96
+ for (const pattern of SENDER_PATTERNS) {
97
+ const m = attrs.match(pattern);
98
+ const value = m?.[1] ?? m?.[2];
99
+ if (value) return value;
100
+ }
101
+ return null;
102
+ }
103
+ function parseAgentEnvelope(text) {
104
+ let sender = null;
105
+ let matched = false;
106
+ const unwrapped = text.replace(ENVELOPE_RE, (_full, _tag, attrs, inner) => {
107
+ matched = true;
108
+ if (sender === null) sender = readSender(attrs);
109
+ return inner.trim();
110
+ });
111
+ return { sender, matched, body: matched ? unwrapped.trim() : text };
112
+ }
113
+
83
114
  // src/lib/turnBuilder.ts
84
115
  function extractTextFromContent(content) {
85
116
  if (typeof content === "string") return content;
@@ -96,7 +127,58 @@ function extractToolResultText(content) {
96
127
  function isVisibleQueuedPrompt(content) {
97
128
  if (!content?.trim()) return false;
98
129
  const trimmed = content.trimStart();
99
- return !trimmed.startsWith("<task-notification>") && !trimmed.startsWith("<local-command-");
130
+ if (!trimmed.startsWith("<")) return true;
131
+ return !trimmed.includes("<task-notification>") && !trimmed.startsWith("<system-reminder>") && !trimmed.startsWith("<local-command-");
132
+ }
133
+ var TASK_NOTIFICATION_BLOCK_RE = /<task-notification>[\s\S]*?<\/task-notification>/g;
134
+ var ANY_TAG_RE = /<[^>]*>/g;
135
+ function isTaskNotificationRecord(msg) {
136
+ if (msg.origin?.kind === "task-notification") return true;
137
+ const text = extractTextFromContent(msg.message.content);
138
+ if (!text.includes("<task-notification>")) return false;
139
+ return text.replace(TASK_NOTIFICATION_BLOCK_RE, "").replace(ANY_TAG_RE, "").trim() === "";
140
+ }
141
+ function queuedCommandPrompt(msg) {
142
+ if (!isAttachmentMessage(msg)) return null;
143
+ const attachment = msg.attachment;
144
+ if (!attachment || attachment.type !== "queued_command") return null;
145
+ if (attachment.commandMode !== "prompt") return null;
146
+ const prompt = attachment.prompt;
147
+ if (prompt == null) return null;
148
+ const raw = typeof prompt === "string" ? prompt : extractTextFromContent(prompt);
149
+ if (!isVisibleQueuedPrompt(raw)) return null;
150
+ const origin = attachment.origin;
151
+ if (origin?.kind === "peer") {
152
+ const sender = origin.name ?? origin.from ?? null;
153
+ if (sender) {
154
+ return {
155
+ raw,
156
+ sender,
157
+ body: origin.body ?? parseAgentEnvelope(raw).body
158
+ };
159
+ }
160
+ }
161
+ if (origin?.kind === "human") {
162
+ return { raw, sender: null, body: raw };
163
+ }
164
+ const parsed = parseAgentEnvelope(raw);
165
+ return { raw, sender: parsed.sender, body: parsed.body };
166
+ }
167
+ var ATTRIBUTION_FIELDS = [
168
+ ["attributionAgent", "agent"],
169
+ ["attributionSkill", "skill"],
170
+ ["attributionPlugin", "plugin"],
171
+ ["attributionMcpServer", "mcpServer"],
172
+ ["attributionMcpTool", "mcpTool"]
173
+ ];
174
+ function mergeAttribution(current, msg) {
175
+ let merged = current;
176
+ for (const [field, key] of ATTRIBUTION_FIELDS) {
177
+ const value = msg[field];
178
+ if (typeof value !== "string" || !value) continue;
179
+ merged = { ...merged, [key]: value };
180
+ }
181
+ return merged;
100
182
  }
101
183
  function mergeTokenUsage(existing, incoming) {
102
184
  if (!existing) {
@@ -110,35 +192,30 @@ function mergeTokenUsage(existing, incoming) {
110
192
  speed: incoming.speed ?? existing.speed
111
193
  };
112
194
  }
113
- function buildCompactionSummary(turns, title) {
114
- if (turns.length === 0) return title;
115
- const toolCounts = {};
116
- for (const turn of turns) {
117
- for (const tc of turn.toolCalls) {
118
- toolCounts[tc.name] = (toolCounts[tc.name] || 0) + 1;
119
- }
120
- }
121
- const prompts = [];
122
- for (const turn of turns) {
123
- if (!turn.userMessage) continue;
124
- const text = extractTextFromContent(
125
- typeof turn.userMessage === "string" ? turn.userMessage : turn.userMessage
126
- );
127
- const firstLine = text.split("\n")[0].trim();
128
- if (firstLine.length > 0) {
129
- prompts.push(firstLine.length > 120 ? firstLine.slice(0, 117) + "..." : firstLine);
130
- }
131
- }
132
- const topTools = Object.entries(toolCounts).sort((a, b) => b[1] - a[1]).slice(0, 5).map(([name, count]) => `${name} x${count}`).join(", ");
133
- const parts = [`**${title}**`, `${turns.length} turns compacted`];
134
- if (topTools) parts.push(`Tools: ${topTools}`);
135
- if (prompts.length > 0) {
136
- parts.push("Prompts:");
137
- const shown = prompts.slice(0, 6);
138
- for (const p of shown) parts.push(`- ${p}`);
139
- if (prompts.length > 6) parts.push(`- ...and ${prompts.length - 6} more`);
140
- }
141
- return parts.join("\n");
195
+ var RESUME_PREAMBLE_RE = /^This session is being continued from a previous conversation[\s\S]*?\n\nSummary:\s*\n/;
196
+ 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]*$/;
197
+ function extractCompactionSummary(content) {
198
+ const text = extractTextFromContent(content).trim();
199
+ return text.replace(RESUME_PREAMBLE_RE, "").replace(RESUME_INSTRUCTIONS_RE, "").trim();
200
+ }
201
+ function normalizeCompactionMeta(meta) {
202
+ if (!meta) return void 0;
203
+ return { trigger: meta.trigger, preTokens: meta.preTokens, postTokens: meta.postTokens };
204
+ }
205
+ function createTurn(msg, userMessage) {
206
+ return {
207
+ id: msg.uuid ?? crypto.randomUUID(),
208
+ userMessage,
209
+ contentBlocks: [],
210
+ thinking: [],
211
+ assistantText: [],
212
+ toolCalls: [],
213
+ subAgentActivity: [],
214
+ timestamp: msg.timestamp ?? "",
215
+ durationMs: null,
216
+ tokenUsage: null,
217
+ model: null
218
+ };
142
219
  }
143
220
  function groupPlanModeBlocks(blocks) {
144
221
  const result = [];
@@ -203,7 +280,7 @@ function groupPlanModeBlocks(blocks) {
203
280
  embedded.push(...next.toolCalls);
204
281
  j++;
205
282
  }
206
- } else if (next.kind === "hook_event" || next.kind === "text" || next.kind === "queued_prompt") {
283
+ } else if (next.kind === "hook_event" || next.kind === "text" || next.kind === "queued_prompt" || next.kind === "agent_message") {
207
284
  passthroughBlocks.push(next);
208
285
  j++;
209
286
  } else {
@@ -224,10 +301,79 @@ function groupPlanModeBlocks(blocks) {
224
301
  }
225
302
  return result;
226
303
  }
304
+ function clearAgentMessageReplies(turn) {
305
+ if (!turn.contentBlocks.some((b) => b.kind === "agent_message")) return turn;
306
+ return {
307
+ ...turn,
308
+ contentBlocks: turn.contentBlocks.map((block) => {
309
+ if (block.kind !== "agent_message") return block;
310
+ const { reply: _paired, ...unpaired } = block;
311
+ return unpaired;
312
+ })
313
+ };
314
+ }
315
+ function pairAgentMessageReplies(turns) {
316
+ const repaired = turns.map(clearAgentMessageReplies);
317
+ const unanswered = /* @__PURE__ */ new Map();
318
+ for (const turn of repaired) {
319
+ for (const block of turn.contentBlocks) {
320
+ if (block.kind === "agent_message") {
321
+ const waiting = unanswered.get(block.sender);
322
+ if (waiting) waiting.push(block);
323
+ else unanswered.set(block.sender, [block]);
324
+ continue;
325
+ }
326
+ if (block.kind !== "tool_calls") continue;
327
+ for (const call of block.toolCalls) {
328
+ if (call.name !== "SendMessage") continue;
329
+ const to = call.input.to;
330
+ if (typeof to !== "string" || !to) continue;
331
+ const target = unanswered.get(to)?.shift();
332
+ if (!target) continue;
333
+ const summary = call.input.summary;
334
+ target.reply = {
335
+ summary: typeof summary === "string" ? summary : "",
336
+ timestamp: call.timestamp || block.timestamp || ""
337
+ };
338
+ }
339
+ }
340
+ }
341
+ return repaired;
342
+ }
343
+ function findTurnStartIndices(messages) {
344
+ const starts = [];
345
+ let hasOpenTurn = false;
346
+ for (let i = 0; i < messages.length; i++) {
347
+ const msg = messages[i];
348
+ if (isSummaryMessage(msg) || isCompactBoundary(msg)) {
349
+ hasOpenTurn = false;
350
+ continue;
351
+ }
352
+ if (isUserMessage(msg) && !msg.isMeta) {
353
+ const content = msg.message.content;
354
+ const continuesOpenTurn = hasOpenTurn && Array.isArray(content) && content.some((b) => b.type === "tool_result");
355
+ if (continuesOpenTurn) continue;
356
+ starts.push(i);
357
+ hasOpenTurn = true;
358
+ continue;
359
+ }
360
+ if (isAssistantMessage(msg) && !hasOpenTurn) {
361
+ starts.push(i);
362
+ hasOpenTurn = true;
363
+ }
364
+ }
365
+ return starts;
366
+ }
227
367
  function buildTurns(messages) {
228
368
  const turns = [];
229
369
  let current = null;
230
370
  let pendingCompaction = null;
371
+ function attachPendingCompaction(turn) {
372
+ if (!pendingCompaction) return;
373
+ turn.compactionSummary = pendingCompaction.summary;
374
+ turn.compactionMeta = pendingCompaction.meta;
375
+ pendingCompaction = null;
376
+ }
231
377
  let pendingRecap = null;
232
378
  const pendingToolUses = /* @__PURE__ */ new Map();
233
379
  const seenMessageIds = /* @__PURE__ */ new Set();
@@ -235,6 +381,41 @@ function buildTurns(messages) {
235
381
  const agentBlockMap = /* @__PURE__ */ new Map();
236
382
  const backgroundAgentParentIds = /* @__PURE__ */ new Set();
237
383
  const taskMetaMap = /* @__PURE__ */ new Map();
384
+ const pendingQueuedPrompts = [];
385
+ const enqueueSourcedPrompts = /* @__PURE__ */ new Map();
386
+ function noteEnqueueSourced(turn, content) {
387
+ let counts = enqueueSourcedPrompts.get(turn);
388
+ if (!counts) {
389
+ counts = /* @__PURE__ */ new Map();
390
+ enqueueSourcedPrompts.set(turn, counts);
391
+ }
392
+ counts.set(content, (counts.get(content) ?? 0) + 1);
393
+ }
394
+ function consumeEnqueueSourced(turn, content) {
395
+ const counts = enqueueSourcedPrompts.get(turn);
396
+ const remaining = counts?.get(content);
397
+ if (!counts || !remaining) return false;
398
+ if (remaining === 1) counts.delete(content);
399
+ else counts.set(content, remaining - 1);
400
+ return true;
401
+ }
402
+ function flushPendingQueuedPrompts() {
403
+ for (const prompt of pendingQueuedPrompts) {
404
+ prompt.turn.contentBlocks.push(
405
+ prompt.sender ? {
406
+ kind: "agent_message",
407
+ sender: prompt.sender,
408
+ body: prompt.body,
409
+ timestamp: prompt.timestamp
410
+ } : {
411
+ kind: "queued_prompt",
412
+ content: prompt.content,
413
+ timestamp: prompt.timestamp
414
+ }
415
+ );
416
+ }
417
+ pendingQueuedPrompts.length = 0;
418
+ }
238
419
  function flushSubAgentMessages(parentId) {
239
420
  if (!current) return;
240
421
  const agentMsgs = subAgentMap.get(parentId);
@@ -282,18 +463,20 @@ function buildTurns(messages) {
282
463
  for (const msg of messages) {
283
464
  if (isSummaryMessage(msg)) {
284
465
  finalizeTurn2();
285
- pendingCompaction = buildCompactionSummary(
286
- turns,
287
- msg.summary ?? "Conversation compacted"
288
- );
466
+ pendingCompaction = { summary: msg.summary };
289
467
  continue;
290
468
  }
291
469
  if (isCompactBoundary(msg)) {
292
470
  finalizeTurn2();
293
- pendingCompaction = buildCompactionSummary(
294
- turns,
295
- msg.content ?? "Conversation compacted"
296
- );
471
+ pendingCompaction = { meta: normalizeCompactionMeta(msg.compactMetadata) };
472
+ continue;
473
+ }
474
+ if (isCompactSummaryMessage(msg)) {
475
+ finalizeTurn2();
476
+ current = createTurn(msg, null);
477
+ current.compactionSummary = extractCompactionSummary(msg.message.content);
478
+ current.compactionMeta = pendingCompaction?.meta;
479
+ pendingCompaction = null;
297
480
  continue;
298
481
  }
299
482
  if (isSystemMessage(msg) && msg.subtype === "away_summary" && msg.content) {
@@ -302,14 +485,44 @@ function buildTurns(messages) {
302
485
  }
303
486
  if (isQueueOperationMessage(msg)) {
304
487
  if (current && msg.operation === "enqueue" && isVisibleQueuedPrompt(msg.content)) {
305
- current.contentBlocks.push({
306
- kind: "queued_prompt",
488
+ const parsed = parseAgentEnvelope(msg.content);
489
+ pendingQueuedPrompts.push({
490
+ turn: current,
307
491
  content: msg.content,
308
- timestamp: msg.timestamp
492
+ timestamp: msg.timestamp,
493
+ sender: parsed.sender,
494
+ body: parsed.body
495
+ });
496
+ noteEnqueueSourced(current, msg.content);
497
+ }
498
+ continue;
499
+ }
500
+ if (isAttachmentMessage(msg)) {
501
+ const queued = queuedCommandPrompt(msg);
502
+ if (current && queued !== null && !consumeEnqueueSourced(current, queued.raw)) {
503
+ pendingQueuedPrompts.push({
504
+ turn: current,
505
+ content: queued.raw,
506
+ timestamp: msg.attachment?.timestamp ?? msg.timestamp,
507
+ sender: queued.sender,
508
+ body: queued.body
309
509
  });
310
510
  }
311
511
  continue;
312
512
  }
513
+ if (pendingQueuedPrompts.length > 0) {
514
+ const userContent = isUserMessage(msg) && !msg.isMeta ? msg.message.content : null;
515
+ if (typeof userContent === "string") {
516
+ const duplicateIndex = pendingQueuedPrompts.findIndex(
517
+ (prompt) => prompt.content === userContent
518
+ );
519
+ if (duplicateIndex >= 0) {
520
+ const [dropped] = pendingQueuedPrompts.splice(duplicateIndex, 1);
521
+ consumeEnqueueSourced(dropped.turn, dropped.content);
522
+ }
523
+ }
524
+ flushPendingQueuedPrompts();
525
+ }
313
526
  if (isUserMessage(msg) && !msg.isMeta) {
314
527
  const content = msg.message.content;
315
528
  if (typeof content !== "string" && Array.isArray(content)) {
@@ -372,24 +585,24 @@ function buildTurns(messages) {
372
585
  continue;
373
586
  }
374
587
  }
375
- finalizeTurn2();
376
- current = {
377
- id: msg.uuid ?? crypto.randomUUID(),
378
- userMessage: msg.message.content,
379
- contentBlocks: [],
380
- thinking: [],
381
- assistantText: [],
382
- toolCalls: [],
383
- subAgentActivity: [],
384
- timestamp: msg.timestamp ?? "",
385
- durationMs: null,
386
- tokenUsage: null,
387
- model: null
388
- };
389
- if (pendingCompaction) {
390
- current.compactionSummary = pendingCompaction;
391
- pendingCompaction = null;
588
+ if (isTaskNotificationRecord(msg)) {
589
+ const notification = {
590
+ kind: "task_notification",
591
+ content: extractTextFromContent(content),
592
+ timestamp: msg.timestamp
593
+ };
594
+ if (current) {
595
+ current.contentBlocks.push(notification);
596
+ continue;
597
+ }
598
+ current = createTurn(msg, null);
599
+ current.contentBlocks.push(notification);
600
+ attachPendingCompaction(current);
601
+ continue;
392
602
  }
603
+ finalizeTurn2();
604
+ current = createTurn(msg, msg.message.content);
605
+ attachPendingCompaction(current);
393
606
  if (pendingRecap) {
394
607
  current.contentBlocks.push({ kind: "recap", content: pendingRecap.content, timestamp: pendingRecap.timestamp });
395
608
  pendingRecap = null;
@@ -415,21 +628,12 @@ function buildTurns(messages) {
415
628
  };
416
629
  var flushToolCalls = flushToolCalls2, flushThinking = flushThinking2;
417
630
  if (!current) {
418
- current = {
419
- id: msg.uuid ?? crypto.randomUUID(),
420
- userMessage: null,
421
- contentBlocks: [],
422
- thinking: [],
423
- assistantText: [],
424
- toolCalls: [],
425
- subAgentActivity: [],
426
- timestamp: msg.timestamp ?? "",
427
- durationMs: null,
428
- tokenUsage: null,
429
- model: null
430
- };
631
+ current = createTurn(msg, null);
632
+ attachPendingCompaction(current);
431
633
  }
432
634
  current.model = msg.message.model;
635
+ if (msg.effort) current.effort = msg.effort;
636
+ current.attribution = mergeAttribution(current.attribution, msg);
433
637
  const msgId = msg.message.id;
434
638
  if (!seenMessageIds.has(msgId)) {
435
639
  seenMessageIds.add(msgId);
@@ -619,145 +823,15 @@ function buildTurns(messages) {
619
823
  continue;
620
824
  }
621
825
  if (isSystemMessage(msg) && msg.subtype === "turn_duration" && current) {
622
- current.durationMs = msg.durationMs ?? null;
826
+ if (msg.durationMs !== void 0 && msg.durationMs !== null) {
827
+ current.durationMs = (current.durationMs ?? 0) + msg.durationMs;
828
+ }
623
829
  continue;
624
830
  }
625
831
  }
832
+ flushPendingQueuedPrompts();
626
833
  finalizeTurn2();
627
- return turns;
628
- }
629
-
630
- // src/lib/pricingTiers.ts
631
- var TIER_HAIKU_35 = { input: 0.8, output: 4, cacheWrite: 1, cacheRead: 0.08, webSearch: 0.01 };
632
- var TIER_HAIKU_45 = { input: 1, output: 5, cacheWrite: 1.25, cacheRead: 0.1, webSearch: 0.01 };
633
- var TIER_SONNET = { input: 3, output: 15, cacheWrite: 3.75, cacheRead: 0.3, webSearch: 0.01 };
634
- var TIER_OPUS_MID = { input: 5, output: 25, cacheWrite: 6.25, cacheRead: 0.5, webSearch: 0.01 };
635
- var TIER_FRONTIER = { input: 10, output: 50, cacheWrite: 12.5, cacheRead: 1, webSearch: 0.01 };
636
- var TIER_OPUS_LEGACY = { input: 15, output: 75, cacheWrite: 18.75, cacheRead: 1.5, webSearch: 0.01 };
637
- var TIER_GPT_56_SOL = { input: 5, output: 30, cacheWrite: 6.25, cacheRead: 0.5, webSearch: 0.01 };
638
- var TIER_GPT_56_TERRA = { input: 2.5, output: 15, cacheWrite: 3.125, cacheRead: 0.25, webSearch: 0.01 };
639
- var TIER_GPT_56_LUNA = { input: 1, output: 6, cacheWrite: 1.25, cacheRead: 0.1, webSearch: 0.01 };
640
- var TIER_GPT_55 = { input: 5, output: 30, cacheWrite: 5, cacheRead: 0.5, webSearch: 0.01 };
641
- var TIER_GPT_54 = { input: 2.5, output: 15, cacheWrite: 2.5, cacheRead: 0.25, webSearch: 0.01 };
642
- var TIER_GPT_54_MINI = { input: 0.75, output: 4.5, cacheWrite: 0.75, cacheRead: 0.075, webSearch: 0.01 };
643
- var TIER_FAST = { input: 30, output: 150, cacheWrite: 37.5, cacheRead: 3, webSearch: 0.01 };
644
- var MODEL_TIERS = [
645
- // OpenAI GPT models. Keep variants before their shared family prefixes.
646
- { match: "gpt-5.6-sol", tier: TIER_GPT_56_SOL },
647
- { match: "gpt-5.6-terra", tier: TIER_GPT_56_TERRA },
648
- { match: "gpt-5.6-luna", tier: TIER_GPT_56_LUNA },
649
- { match: "gpt-5.6", tier: TIER_GPT_56_SOL },
650
- { match: "gpt-5.4-mini", tier: TIER_GPT_54_MINI },
651
- { match: "gpt-5.5", tier: TIER_GPT_55 },
652
- { match: "gpt-5.4", tier: TIER_GPT_54 },
653
- // Frontier (Fable 5 / Mythos 5 / Opus 4.8)
654
- { match: "fable-5", tier: TIER_FRONTIER },
655
- { match: "mythos-5", tier: TIER_FRONTIER },
656
- { match: "opus-4-8", tier: TIER_FRONTIER },
657
- // Haiku
658
- { match: "haiku-4-5", tier: TIER_HAIKU_45 },
659
- { match: "haiku-4-0", tier: TIER_HAIKU_35 },
660
- { match: "3-5-haiku", tier: TIER_HAIKU_35 },
661
- // Sonnet (3.5 through 4.6 — all one tier in current CC)
662
- { match: "sonnet-4-7", tier: TIER_SONNET },
663
- { match: "sonnet-4-6", tier: TIER_SONNET },
664
- { match: "sonnet-4-5", tier: TIER_SONNET },
665
- { match: "sonnet-4-0", tier: TIER_SONNET },
666
- { match: "3-7-sonnet", tier: TIER_SONNET },
667
- { match: "3-5-sonnet", tier: TIER_SONNET },
668
- // Opus 4.5–4.7
669
- { match: "opus-4-7", tier: TIER_OPUS_MID },
670
- { match: "opus-4-6", tier: TIER_OPUS_MID },
671
- { match: "opus-4-5", tier: TIER_OPUS_MID },
672
- // Opus legacy (4.0, 4.1)
673
- { match: "opus-4-1", tier: TIER_OPUS_LEGACY },
674
- { match: "opus-4-0", tier: TIER_OPUS_LEGACY }
675
- ];
676
- var FAST_MODE_MODELS = ["opus-4-6", "opus-4-7"];
677
- var DEFAULT_TIER = TIER_OPUS_MID;
678
- var FAMILY_FALLBACKS = [
679
- { match: "fable", tier: TIER_FRONTIER },
680
- { match: "mythos", tier: TIER_FRONTIER },
681
- { match: "haiku", tier: TIER_HAIKU_45 },
682
- { match: "sonnet", tier: TIER_SONNET },
683
- { match: "opus", tier: TIER_OPUS_MID }
684
- ];
685
- function resolveTier(model, speed) {
686
- if (speed === "fast" && FAST_MODE_MODELS.some((m) => model.includes(m))) {
687
- return TIER_FAST;
688
- }
689
- for (const entry of MODEL_TIERS) {
690
- if (model.includes(entry.match)) return entry.tier;
691
- }
692
- for (const entry of FAMILY_FALLBACKS) {
693
- if (model.includes(entry.match)) return entry.tier;
694
- }
695
- return DEFAULT_TIER;
696
- }
697
- function hasKnownPricing(model) {
698
- if (!model) return true;
699
- const normalized = model.toLowerCase();
700
- if (!normalized.startsWith("gpt-")) return true;
701
- return MODEL_TIERS.some((entry) => normalized.includes(entry.match));
702
- }
703
-
704
- // src/lib/token-costs.ts
705
- var CHARS_PER_TOKEN = 4;
706
- function calculateCost(c) {
707
- if (!hasKnownPricing(c.model)) return Number.NaN;
708
- const p = resolveTier(c.model ?? "", c.speed);
709
- return c.inputTokens / 1e6 * p.input + c.outputTokens / 1e6 * p.output + c.cacheWriteTokens / 1e6 * p.cacheWrite + c.cacheReadTokens / 1e6 * p.cacheRead + (c.webSearchRequests ?? 0) * p.webSearch;
710
- }
711
- function charsToTokens(chars) {
712
- return Math.ceil(chars / CHARS_PER_TOKEN);
713
- }
714
- function totalLength(strings) {
715
- let n = 0;
716
- for (const s of strings) n += s.length;
717
- return n;
718
- }
719
- function totalToolInputLength(toolCalls) {
720
- let n = 0;
721
- for (const tc of toolCalls) n += JSON.stringify(tc.input).length;
722
- return n;
723
- }
724
- function estimateThinkingTokens(turn) {
725
- return charsToTokens(totalLength(turn.thinking.map((b) => b.thinking)));
726
- }
727
- function estimateVisibleOutputTokens(turn) {
728
- return charsToTokens(totalLength(turn.assistantText) + totalToolInputLength(turn.toolCalls));
729
- }
730
- function estimateTotalOutputTokens(turn) {
731
- const estimated = estimateThinkingTokens(turn) + estimateVisibleOutputTokens(turn);
732
- return Math.max(estimated, turn.tokenUsage?.output_tokens ?? 0);
733
- }
734
- function estimateSubAgentOutput(sa) {
735
- const chars = totalLength(sa.thinking) + totalLength(sa.text) + totalToolInputLength(sa.toolCalls);
736
- return Math.max(charsToTokens(chars), sa.tokenUsage?.output_tokens ?? 0);
737
- }
738
- function calculateTurnCostEstimated(turn) {
739
- if (!turn.tokenUsage) return 0;
740
- const u = turn.tokenUsage;
741
- return calculateCost({
742
- model: turn.model,
743
- inputTokens: u.input_tokens,
744
- outputTokens: estimateTotalOutputTokens(turn),
745
- cacheWriteTokens: u.cache_creation_input_tokens ?? 0,
746
- cacheReadTokens: u.cache_read_input_tokens ?? 0,
747
- speed: u.speed
748
- });
749
- }
750
- function calculateSubAgentCostEstimated(sa) {
751
- if (!sa.tokenUsage) return 0;
752
- const u = sa.tokenUsage;
753
- return calculateCost({
754
- model: sa.model,
755
- inputTokens: u.input_tokens,
756
- outputTokens: estimateSubAgentOutput(sa),
757
- cacheWriteTokens: u.cache_creation_input_tokens ?? 0,
758
- cacheReadTokens: u.cache_read_input_tokens ?? 0,
759
- speed: u.speed
760
- });
834
+ return pairAgentMessageReplies(turns);
761
835
  }
762
836
 
763
837
  // src/lib/sessionStats.ts
@@ -769,12 +843,11 @@ function countToolCalls(toolCalls, counts) {
769
843
  }
770
844
  return errors;
771
845
  }
772
- function addUsageToStats(stats, usage, estimatedOutput, cost) {
846
+ function addUsageToStats(stats, usage) {
773
847
  stats.totalInputTokens += usage.input_tokens;
774
- stats.totalOutputTokens += estimatedOutput;
848
+ stats.totalOutputTokens += usage.output_tokens;
775
849
  stats.totalCacheCreationTokens += usage.cache_creation_input_tokens ?? 0;
776
850
  stats.totalCacheReadTokens += usage.cache_read_input_tokens ?? 0;
777
- stats.totalCostUSD += cost;
778
851
  }
779
852
  function createEmptySessionStats(turnCount) {
780
853
  return {
@@ -782,7 +855,6 @@ function createEmptySessionStats(turnCount) {
782
855
  totalOutputTokens: 0,
783
856
  totalCacheCreationTokens: 0,
784
857
  totalCacheReadTokens: 0,
785
- totalCostUSD: 0,
786
858
  toolCallCounts: {},
787
859
  errorCount: 0,
788
860
  totalDurationMs: 0,
@@ -790,34 +862,20 @@ function createEmptySessionStats(turnCount) {
790
862
  };
791
863
  }
792
864
  function computeStats(turns) {
793
- const stats = {
794
- totalInputTokens: 0,
795
- totalOutputTokens: 0,
796
- totalCacheCreationTokens: 0,
797
- totalCacheReadTokens: 0,
798
- totalCostUSD: 0,
799
- toolCallCounts: {},
800
- errorCount: 0,
801
- totalDurationMs: 0,
802
- turnCount: turns.length
803
- };
865
+ const stats = createEmptySessionStats(turns.length);
804
866
  for (const turn of turns) {
805
- if (turn.tokenUsage) {
806
- addUsageToStats(stats, turn.tokenUsage, estimateTotalOutputTokens(turn), calculateTurnCostEstimated(turn));
807
- }
867
+ if (turn.tokenUsage) addUsageToStats(stats, turn.tokenUsage);
808
868
  if (turn.durationMs) stats.totalDurationMs += turn.durationMs;
809
869
  stats.errorCount += countToolCalls(turn.toolCalls, stats.toolCallCounts);
810
870
  for (const sa of turn.subAgentActivity) {
811
871
  stats.errorCount += countToolCalls(sa.toolCalls, stats.toolCallCounts);
812
- if (sa.tokenUsage) {
813
- addUsageToStats(stats, sa.tokenUsage, estimateSubAgentOutput(sa), calculateSubAgentCostEstimated(sa));
814
- }
872
+ if (sa.tokenUsage) addUsageToStats(stats, sa.tokenUsage);
815
873
  }
816
874
  }
817
875
  return stats;
818
876
  }
819
877
 
820
- // src/lib/codex-patches.ts
878
+ // src/lib/codex-exec.ts
821
879
  var JS_SIMPLE_ESCAPES = {
822
880
  "0": "\0",
823
881
  b: "\b",
@@ -883,6 +941,8 @@ function readJsStringLiteral(source, startIndex) {
883
941
  }
884
942
  return null;
885
943
  }
944
+
945
+ // src/lib/codex-patches.ts
886
946
  function isApplyPatchText(value) {
887
947
  return value.includes("*** Begin Patch") && value.includes("*** End Patch");
888
948
  }
@@ -916,7 +976,9 @@ function extractApplyPatchInputs(execSource) {
916
976
  }
917
977
  function resolveCodexPatchPath(filePath, cwd) {
918
978
  if (!cwd || filePath.startsWith("/") || /^[A-Za-z]:[\\/]/.test(filePath)) return filePath;
919
- return `${cwd.replace(/[\\/]+$/, "")}/${filePath.replace(/^\.\//, "")}`;
979
+ const base = cwd.replace(/[\\/]+$/, "");
980
+ const relative = filePath.replace(/^\.[\\/]/, "");
981
+ return /^[A-Za-z]:[\\/]/.test(cwd) ? `${base}\\${relative.replace(/\//g, "\\")}` : `${base}/${relative}`;
920
982
  }
921
983
  function parseApplyPatch(patchText, callId, timestamp, cwd = "") {
922
984
  const toolCalls = [];
@@ -1043,6 +1105,24 @@ function findFailedNestedPatchCallIds(output, wrapperIsError, calls) {
1043
1105
  function isObject(value) {
1044
1106
  return typeof value === "object" && value !== null;
1045
1107
  }
1108
+ var COLLABORATION_TOOL_NAMES = /* @__PURE__ */ new Map([
1109
+ ["spawn_agent", "spawn_agent"],
1110
+ ["spawnAgent", "spawn_agent"],
1111
+ ["wait_agent", "wait_agent"],
1112
+ ["waitAgent", "wait_agent"],
1113
+ ["send_message", "send_message"],
1114
+ ["sendMessage", "send_message"],
1115
+ ["followup_task", "followup_task"],
1116
+ ["followupTask", "followup_task"],
1117
+ ["list_agents", "list_agents"],
1118
+ ["listAgents", "list_agents"],
1119
+ ["interrupt_agent", "interrupt_agent"],
1120
+ ["interruptAgent", "interrupt_agent"]
1121
+ ]);
1122
+ function normalizeFunctionName(rawName) {
1123
+ const leaf = rawName.split(/(?:__|[.:/])+/).filter(Boolean).at(-1) ?? rawName;
1124
+ return COLLABORATION_TOOL_NAMES.get(leaf) ?? rawName;
1125
+ }
1046
1126
  function inferToolError(output) {
1047
1127
  if (!output) return false;
1048
1128
  const exitMatch = output.match(/Process exited with code (\d+)/);
@@ -1168,28 +1248,6 @@ function buildCodexUserContent(message, images, audio = [], localImages = [], lo
1168
1248
  ...text ? [{ type: "text", text }] : []
1169
1249
  ];
1170
1250
  }
1171
- function normalizeFunctionName(rawName) {
1172
- const leaf = rawName.split(/(?:__|[.:/])+/).filter(Boolean).at(-1) ?? rawName;
1173
- const aliases = {
1174
- spawnAgent: "spawn_agent",
1175
- waitAgent: "wait_agent",
1176
- sendMessage: "send_message",
1177
- followupTask: "followup_task",
1178
- listAgents: "list_agents",
1179
- interruptAgent: "interrupt_agent"
1180
- };
1181
- const canonicalCollaborationNames = /* @__PURE__ */ new Set([
1182
- "spawn_agent",
1183
- "wait_agent",
1184
- "send_message",
1185
- "followup_task",
1186
- "list_agents",
1187
- "interrupt_agent"
1188
- ]);
1189
- if (aliases[leaf]) return aliases[leaf];
1190
- if (canonicalCollaborationNames.has(leaf)) return leaf;
1191
- return rawName;
1192
- }
1193
1251
  function extractInterAgentMessage(payload) {
1194
1252
  const rawText = extractMessageText(payload, "input_text");
1195
1253
  if (!rawText) return { messageType: null, text: "" };
@@ -1203,7 +1261,7 @@ function extractInterAgentMessage(payload) {
1203
1261
  return { messageType, text };
1204
1262
  }
1205
1263
  function agentNameFromPath(agentPath) {
1206
- const segments = agentPath.split("/").filter(Boolean);
1264
+ const segments = agentPath.split(/[\\/]/).filter(Boolean);
1207
1265
  return segments.at(-1) ?? null;
1208
1266
  }
1209
1267
  function readableAgentPrompt(value) {
@@ -1296,9 +1354,35 @@ function parseToolInput(argumentsText) {
1296
1354
  return { raw: argumentsText };
1297
1355
  }
1298
1356
  }
1299
- function createTurn(turnId, timestamp, model) {
1357
+ function safeStringify(value) {
1358
+ if (typeof value === "string") return value;
1359
+ try {
1360
+ return JSON.stringify(value);
1361
+ } catch {
1362
+ return String(value ?? "");
1363
+ }
1364
+ }
1365
+ function findString(...values) {
1366
+ return values.find((value) => typeof value === "string");
1367
+ }
1368
+ function isCodeModeChild(callId) {
1369
+ return callId.startsWith("exec-");
1370
+ }
1371
+ function parseMcpEventResult(value) {
1372
+ if (!isObject2(value)) return { text: safeStringify(value), isError: false };
1373
+ if ("Err" in value) return { text: safeStringify(value.Err), isError: true };
1374
+ const ok = isObject2(value.Ok) ? value.Ok : null;
1375
+ if (!ok) return { text: safeStringify(value), isError: false };
1376
+ const parsedContent = parseCustomToolOutput(ok.content);
1377
+ const text = parsedContent.text || (ok.structuredContent !== void 0 ? safeStringify(ok.structuredContent) : safeStringify(ok));
1378
+ return {
1379
+ text,
1380
+ isError: ok.isError === true || parsedContent.isError
1381
+ };
1382
+ }
1383
+ function createTurn2(turnId, timestamp, model) {
1300
1384
  return {
1301
- id: turnId || randomTurnId("codex-turn"),
1385
+ id: turnId ? `${turnId}@${timestamp}` : randomTurnId("codex-turn"),
1302
1386
  userMessage: null,
1303
1387
  contentBlocks: [],
1304
1388
  thinking: [],
@@ -1320,6 +1404,17 @@ function extractPromptFromRecord(record) {
1320
1404
  }
1321
1405
  return "";
1322
1406
  }
1407
+ function recordOpensTurn(record, payload) {
1408
+ if (record.type === "session_meta") return false;
1409
+ if (record.type === "event_msg" && payload?.type === "task_started") return false;
1410
+ if (record.type === "response_item" && payload?.type === "message") {
1411
+ if (payload.role === "developer" || payload.role === "system") return false;
1412
+ if (payload.role === "user") {
1413
+ return extractPromptFromRecord(record) !== "" || extractResponseMessageImages(payload).length > 0 || extractResponseMessageAudio(payload).length > 0;
1414
+ }
1415
+ }
1416
+ return true;
1417
+ }
1323
1418
  function extractMetadataFromRecords(records) {
1324
1419
  let sessionId = "";
1325
1420
  let version = "";
@@ -1486,14 +1581,35 @@ function parseCodexSession(jsonlText, options) {
1486
1581
  let currentModel = metadata.model || null;
1487
1582
  let lastTurnTimestamp = "";
1488
1583
  let pendingCompaction = null;
1584
+ let sawTurnStart = false;
1489
1585
  function createActiveTurn(turnId, timestamp, model) {
1490
- const turn = createTurn(turnId, timestamp, model);
1586
+ const turn = createTurn2(turnId, timestamp, model);
1587
+ if (!sawTurnStart) turn.isFragment = true;
1491
1588
  if (pendingCompaction) {
1492
1589
  turn.compactionSummary = pendingCompaction;
1493
1590
  pendingCompaction = null;
1494
1591
  }
1495
1592
  return turn;
1496
1593
  }
1594
+ function upsertWebSearchCall(targetTurn, id, input, status, timestamp) {
1595
+ const existing = webSearchCalls.get(id);
1596
+ if (existing) {
1597
+ existing.input = { ...existing.input, ...input };
1598
+ existing.result = status;
1599
+ existing.isError = status === "failed";
1600
+ return;
1601
+ }
1602
+ const toolCall = {
1603
+ id,
1604
+ name: "WebSearch",
1605
+ input,
1606
+ result: status,
1607
+ isError: status === "failed",
1608
+ timestamp
1609
+ };
1610
+ webSearchCalls.set(id, toolCall);
1611
+ appendToolCall(targetTurn, toolCall, timestamp);
1612
+ }
1497
1613
  for (const record of records) {
1498
1614
  const payload = isObject2(record.payload) ? record.payload : void 0;
1499
1615
  const timestamp = record.timestamp ?? "";
@@ -1509,12 +1625,14 @@ function parseCodexSession(jsonlText, options) {
1509
1625
  currentTurnId = typeof payload?.turn_id === "string" ? payload.turn_id : null;
1510
1626
  currentModel = typeof payload?.model === "string" ? payload.model : currentModel;
1511
1627
  lastTurnTimestamp = timestamp;
1628
+ sawTurnStart = true;
1512
1629
  continue;
1513
1630
  }
1514
1631
  if (record.type === "event_msg" && payload?.type === "user_message" && typeof payload.message === "string") {
1515
1632
  if (current && (current.assistantText.length > 0 || current.toolCalls.length > 0 || current.thinking.length > 0)) {
1516
1633
  current = finalizeTurn(turns, current, lastTurnTimestamp);
1517
1634
  }
1635
+ sawTurnStart = true;
1518
1636
  current ??= createActiveTurn(currentTurnId, timestamp, currentModel);
1519
1637
  const localImages = (Array.isArray(payload.local_images) ? payload.local_images : []).filter(
1520
1638
  (p) => typeof p === "string" && p.length > 0
@@ -1529,10 +1647,12 @@ function parseCodexSession(jsonlText, options) {
1529
1647
  localImages,
1530
1648
  localAudio
1531
1649
  );
1650
+ current.isFragment = false;
1532
1651
  current.timestamp = current.timestamp || timestamp;
1533
1652
  lastTurnTimestamp = timestamp;
1534
1653
  continue;
1535
1654
  }
1655
+ if (!current && !recordOpensTurn(record, payload)) continue;
1536
1656
  current ??= createActiveTurn(currentTurnId, timestamp, currentModel);
1537
1657
  if (!current.model && currentModel) current.model = currentModel;
1538
1658
  if (!current.timestamp) current.timestamp = timestamp;
@@ -1549,22 +1669,29 @@ function parseCodexSession(jsonlText, options) {
1549
1669
  const input = {};
1550
1670
  if (typeof payload.query === "string" && payload.query) input.query = payload.query;
1551
1671
  if (isObject2(payload.action)) input.action = payload.action;
1552
- const existing = webSearchCalls.get(payload.call_id);
1672
+ upsertWebSearchCall(current, payload.call_id, input, "completed", timestamp);
1673
+ continue;
1674
+ }
1675
+ if (record.type === "event_msg" && payload?.type === "mcp_tool_call_end" && typeof payload.call_id === "string" && isObject2(payload.invocation)) {
1676
+ const invocation = payload.invocation;
1677
+ const server = typeof invocation.server === "string" ? invocation.server : "mcp";
1678
+ const tool = typeof invocation.tool === "string" ? invocation.tool : "tool";
1679
+ const input = isObject2(invocation.arguments) ? invocation.arguments : {};
1680
+ const result = parseMcpEventResult(payload.result);
1681
+ const existing = pendingToolCalls.get(payload.call_id);
1553
1682
  if (existing) {
1554
- existing.input = { ...existing.input, ...input };
1555
- existing.result = "completed";
1556
- existing.isError = false;
1557
- } else {
1558
- const toolCall = {
1683
+ existing.result = result.text;
1684
+ existing.isError = result.isError;
1685
+ pendingToolCalls.delete(payload.call_id);
1686
+ } else if (!isCodeModeChild(payload.call_id)) {
1687
+ appendToolCall(current, {
1559
1688
  id: payload.call_id,
1560
- name: "WebSearch",
1689
+ name: `mcp__${server}__${tool}`,
1561
1690
  input,
1562
- result: "completed",
1563
- isError: false,
1691
+ result: result.text,
1692
+ isError: result.isError,
1564
1693
  timestamp
1565
- };
1566
- webSearchCalls.set(toolCall.id, toolCall);
1567
- appendToolCall(current, toolCall, timestamp);
1694
+ }, timestamp);
1568
1695
  }
1569
1696
  continue;
1570
1697
  }
@@ -1618,6 +1745,8 @@ function parseCodexSession(jsonlText, options) {
1618
1745
  const audio = extractResponseMessageAudio(payload);
1619
1746
  if (text || images.length > 0 || audio.length > 0) {
1620
1747
  current.userMessage = buildCodexUserContent(text, images, audio);
1748
+ current.isFragment = false;
1749
+ sawTurnStart = true;
1621
1750
  }
1622
1751
  continue;
1623
1752
  }
@@ -1625,25 +1754,38 @@ function parseCodexSession(jsonlText, options) {
1625
1754
  const input = {};
1626
1755
  if (isObject2(payload.action)) input.action = payload.action;
1627
1756
  const status = typeof payload.status === "string" ? payload.status : "completed";
1628
- const existing = webSearchCalls.get(payload.id);
1629
- if (existing) {
1630
- existing.input = { ...existing.input, ...input };
1631
- existing.result = status;
1632
- existing.isError = status === "failed";
1633
- } else {
1757
+ upsertWebSearchCall(current, payload.id, input, status, timestamp);
1758
+ continue;
1759
+ }
1760
+ if (payload.type === "tool_search_call") {
1761
+ const callId = findString(payload.call_id, payload.id) ?? "";
1762
+ if (callId) {
1634
1763
  const toolCall = {
1635
- id: payload.id,
1636
- name: "WebSearch",
1637
- input,
1638
- result: status,
1639
- isError: status === "failed",
1764
+ id: callId,
1765
+ name: "ToolSearch",
1766
+ input: isObject2(payload.arguments) ? payload.arguments : {},
1767
+ result: null,
1768
+ isError: false,
1640
1769
  timestamp
1641
1770
  };
1642
- webSearchCalls.set(toolCall.id, toolCall);
1771
+ pendingToolCalls.set(callId, toolCall);
1643
1772
  appendToolCall(current, toolCall, timestamp);
1644
1773
  }
1645
1774
  continue;
1646
1775
  }
1776
+ if (payload.type === "tool_search_output" && typeof payload.call_id === "string") {
1777
+ const toolCall = pendingToolCalls.get(payload.call_id);
1778
+ if (toolCall) {
1779
+ const tools = Array.isArray(payload.tools) ? payload.tools : [];
1780
+ const names = tools.filter(isObject2).map((tool) => findString(tool.name) ?? "").filter(Boolean);
1781
+ const count = names.length > 0 ? names.length : tools.length;
1782
+ const found = `Found ${count} tool${count === 1 ? "" : "s"}`;
1783
+ toolCall.result = names.length > 0 ? `${found}: ${names.join(", ")}` : found;
1784
+ toolCall.isError = payload.status === "failed";
1785
+ pendingToolCalls.delete(payload.call_id);
1786
+ }
1787
+ continue;
1788
+ }
1647
1789
  if (payload.type === "agent_message" && typeof payload.author === "string") {
1648
1790
  const author = payload.author;
1649
1791
  const recipient = typeof payload.recipient === "string" ? payload.recipient : null;
@@ -1917,61 +2059,29 @@ ${newJsonlText}` : newJsonlText);
1917
2059
  if (newMessages.length === 0) return existing;
1918
2060
  const existingRawMessages = existing.rawMessages;
1919
2061
  const allRawMessages = [...existingRawMessages, ...newMessages];
1920
- let lastTurnStartIdx = existingRawMessages.length;
1921
- let turnsToKeep = existing.turns.length > 0 ? existing.turns.length - 1 : 0;
1922
- if (existing.turns.length > 0) {
1923
- let userMsgCount = 0;
1924
- for (let i = existingRawMessages.length - 1; i >= 0; i--) {
1925
- const msg = existingRawMessages[i];
1926
- if (msg.type === "user" && !msg.isMeta) {
1927
- const content = msg.message?.content;
1928
- if (Array.isArray(content) && content.some((b) => b.type === "tool_result")) {
1929
- continue;
1930
- }
1931
- userMsgCount++;
1932
- if (userMsgCount === 1) {
1933
- lastTurnStartIdx = i;
1934
- break;
1935
- }
1936
- }
2062
+ const turnStarts = findTurnStartIndices(existingRawMessages);
2063
+ const historyTurnCount = Math.max(0, existing.turns.length - turnStarts.length);
2064
+ let rebuildFromTurn = Math.max(0, turnStarts.length - 1);
2065
+ const progressParentIds = /* @__PURE__ */ new Set();
2066
+ for (const msg of newMessages) {
2067
+ if (msg.type === "progress") {
2068
+ const parentId = msg.parentToolUseID;
2069
+ if (parentId) progressParentIds.add(parentId);
1937
2070
  }
1938
- const progressParentIds = /* @__PURE__ */ new Set();
1939
- for (const msg of newMessages) {
1940
- if (msg.type === "progress") {
1941
- const parentId = msg.parentToolUseID;
1942
- if (parentId) progressParentIds.add(parentId);
1943
- }
1944
- }
1945
- if (progressParentIds.size > 0) {
1946
- for (let t = existing.turns.length - 2; t >= 0; t--) {
1947
- const turn = existing.turns[t];
1948
- const ownsProgressParent = turn.toolCalls.some((tc) => progressParentIds.has(tc.id));
1949
- if (ownsProgressParent) {
1950
- turnsToKeep = t;
1951
- let found = 0;
1952
- for (let i = 0; i < existingRawMessages.length; i++) {
1953
- const msg = existingRawMessages[i];
1954
- if (msg.type === "user" && !msg.isMeta) {
1955
- const content = msg.message?.content;
1956
- if (Array.isArray(content) && content.some((b) => b.type === "tool_result")) {
1957
- continue;
1958
- }
1959
- if (found === t) {
1960
- lastTurnStartIdx = i;
1961
- break;
1962
- }
1963
- found++;
1964
- }
1965
- }
1966
- break;
1967
- }
2071
+ }
2072
+ if (progressParentIds.size > 0) {
2073
+ for (let t = rebuildFromTurn - 1; t >= 0; t--) {
2074
+ const turn = existing.turns[historyTurnCount + t];
2075
+ if (turn?.toolCalls.some((tc) => progressParentIds.has(tc.id))) {
2076
+ rebuildFromTurn = t;
2077
+ break;
1968
2078
  }
1969
2079
  }
1970
2080
  }
1971
- const keptTurns = existing.turns.slice(0, turnsToKeep);
1972
- const tailMessages = allRawMessages.slice(lastTurnStartIdx);
1973
- const tailTurns = buildTurns(tailMessages);
1974
- const allTurns = [...keptTurns, ...tailTurns];
2081
+ const rebuildFrom = turnStarts[rebuildFromTurn] ?? existingRawMessages.length;
2082
+ const keptTurns = existing.turns.slice(0, historyTurnCount + rebuildFromTurn);
2083
+ const tailTurns = buildTurns(allRawMessages.slice(rebuildFrom));
2084
+ const allTurns = pairAgentMessageReplies([...keptTurns, ...tailTurns]);
1975
2085
  const stats = computeStats(allTurns);
1976
2086
  return {
1977
2087
  sessionId: existing.sessionId,
@@ -2633,7 +2743,11 @@ async function matchSubagentToMember(leadSessionId, subagentFileName, members) {
2633
2743
  }
2634
2744
  return null;
2635
2745
  }
2636
- var HOME_PREFIX = (0, import_node_os2.homedir)().replace(/\//g, "-").replace(/^-/, "").toLowerCase();
2746
+ function encodeClaudeDirName(cwd) {
2747
+ const normalized = cwd.replace(/[\\/]+$/, "") || cwd;
2748
+ return normalized.replace(/[^a-zA-Z0-9]/g, "-");
2749
+ }
2750
+ var HOME_PREFIX = encodeClaudeDirName((0, import_node_os2.homedir)()).replace(/^-/, "").toLowerCase();
2637
2751
 
2638
2752
  // src/commands/search.ts
2639
2753
  async function searchSessions(query, opts, searchIndex) {
@@ -2985,6 +3099,7 @@ var import_promises3 = require("node:fs/promises");
2985
3099
  var import_node_path5 = require("node:path");
2986
3100
  var RESULT_TRUNCATE_LIMIT = 1e4;
2987
3101
  var L1_RESPONSE_LIMIT = 15e4;
3102
+ var COMPACTION_SUMMARY_PREVIEW = 400;
2988
3103
  function extractUserMessageText(userMessage) {
2989
3104
  if (userMessage === null) return null;
2990
3105
  if (typeof userMessage === "string") return userMessage;
@@ -3076,9 +3191,13 @@ function mapTurnToSummary(turn, turnIndex) {
3076
3191
  subAgents,
3077
3192
  hasThinking: turn.thinking.length > 0,
3078
3193
  isError: turn.toolCalls.some((tc) => tc.isError),
3079
- compactionSummary: turn.compactionSummary ?? null
3194
+ compactionSummary: truncateCompactionSummary(turn.compactionSummary)
3080
3195
  };
3081
3196
  }
3197
+ function truncateCompactionSummary(summary) {
3198
+ if (!summary) return null;
3199
+ return summary.length > COMPACTION_SUMMARY_PREVIEW ? summary.slice(0, COMPACTION_SUMMARY_PREVIEW) + "... [truncated, use L2 for full text]" : summary;
3200
+ }
3082
3201
  function mapTurnToDetail(session, turnIndex) {
3083
3202
  const turn = session.turns[turnIndex];
3084
3203
  const contentBlocks = turn.contentBlocks.map((block) => mapContentBlock(block));
@@ -3086,6 +3205,7 @@ function mapTurnToDetail(session, turnIndex) {
3086
3205
  sessionId: session.sessionId,
3087
3206
  turnIndex,
3088
3207
  userMessage: extractUserMessageText(turn.userMessage),
3208
+ compactionSummary: turn.compactionSummary ?? null,
3089
3209
  contentBlocks,
3090
3210
  tokenUsage: turn.tokenUsage ? { input: turn.tokenUsage.input_tokens, output: turn.tokenUsage.output_tokens } : null,
3091
3211
  model: turn.model,
@@ -3119,6 +3239,14 @@ function mapContentBlock(block) {
3119
3239
  };
3120
3240
  case "queued_prompt":
3121
3241
  return { kind: "queued_prompt", content: block.content, timestamp: block.timestamp ?? null };
3242
+ case "agent_message":
3243
+ return {
3244
+ kind: "agent_message",
3245
+ sender: block.sender,
3246
+ body: block.body,
3247
+ reply: block.reply ?? null,
3248
+ timestamp: block.timestamp ?? null
3249
+ };
3122
3250
  case "hook_event":
3123
3251
  return { kind: "hook_event", events: block.events, timestamp: block.timestamp ?? null };
3124
3252
  case "plan_mode":
@@ -3132,6 +3260,12 @@ function mapContentBlock(block) {
3132
3260
  };
3133
3261
  case "recap":
3134
3262
  return { kind: "recap", content: block.content, timestamp: block.timestamp ?? null };
3263
+ case "task_notification":
3264
+ return { kind: "task_notification", content: block.content, timestamp: block.timestamp ?? null };
3265
+ default: {
3266
+ const exhaustive = block;
3267
+ return exhaustive;
3268
+ }
3135
3269
  }
3136
3270
  }
3137
3271
  async function findSubagentFile(parentJsonlPath, agentId) {
@@ -3270,17 +3404,99 @@ var import_node_path6 = require("node:path");
3270
3404
  var import_promises4 = require("node:fs/promises");
3271
3405
 
3272
3406
  // src/lib/sessionStatus.ts
3407
+ var TASK_NOTIFICATION_MARKER = "<task-notification>";
3408
+ var TASK_ID_RE = /<task-id>([^<]+)<\/task-id>/g;
3409
+ var TOOL_USE_ID_RE = /<tool-use-id>([^<]+)<\/tool-use-id>/g;
3410
+ function isObject3(value) {
3411
+ return typeof value === "object" && value !== null;
3412
+ }
3413
+ function userMessageTexts(msg) {
3414
+ const message = isObject3(msg.message) ? msg.message : null;
3415
+ const content = message?.content;
3416
+ if (typeof content === "string") return [content];
3417
+ if (!Array.isArray(content)) return [];
3418
+ return content.filter((block) => isObject3(block) && typeof block.text === "string").map((block) => block.text);
3419
+ }
3420
+ function collectClaudeBackgroundWork(rawMessages) {
3421
+ const launches = [];
3422
+ const resolved = /* @__PURE__ */ new Set();
3423
+ const queuedIds = /* @__PURE__ */ new Set();
3424
+ const deliveredIds = /* @__PURE__ */ new Set();
3425
+ function noteNotifications(text, sink) {
3426
+ if (!text.includes(TASK_NOTIFICATION_MARKER)) return;
3427
+ for (const match of text.matchAll(TASK_ID_RE)) {
3428
+ resolved.add(match[1]);
3429
+ sink?.add(match[1]);
3430
+ }
3431
+ for (const match of text.matchAll(TOOL_USE_ID_RE)) {
3432
+ resolved.add(match[1]);
3433
+ sink?.add(match[1]);
3434
+ }
3435
+ }
3436
+ for (const msg of rawMessages) {
3437
+ if (msg.type === "user") {
3438
+ const toolUseResult = isObject3(msg.toolUseResult) ? msg.toolUseResult : null;
3439
+ if (toolUseResult?.status === "async_launched") {
3440
+ const message = isObject3(msg.message) ? msg.message : null;
3441
+ const blocks = Array.isArray(message?.content) ? message.content : [];
3442
+ const toolUseIds = blocks.filter((block) => isObject3(block) && block.type === "tool_result").map((block) => block.tool_use_id);
3443
+ const ids = [toolUseResult.agentId, toolUseResult.taskId, ...toolUseIds].filter((id) => typeof id === "string" && id.length > 0);
3444
+ const description = [toolUseResult.description, toolUseResult.workflowName, toolUseResult.summary].find((value) => typeof value === "string" && value.length > 0) ?? "";
3445
+ if (ids.length > 0) launches.push({ ids, description });
3446
+ }
3447
+ for (const text of userMessageTexts(msg)) noteNotifications(text, deliveredIds);
3448
+ continue;
3449
+ }
3450
+ if (msg.type === "assistant") {
3451
+ const message = isObject3(msg.message) ? msg.message : null;
3452
+ const blocks = Array.isArray(message?.content) ? message.content : [];
3453
+ for (const block of blocks) {
3454
+ if (!isObject3(block) || block.type !== "tool_use" || block.name !== "TaskStop") continue;
3455
+ const input = isObject3(block.input) ? block.input : null;
3456
+ const taskId = input?.task_id ?? input?.shell_id;
3457
+ if (typeof taskId === "string") resolved.add(taskId);
3458
+ }
3459
+ continue;
3460
+ }
3461
+ if (msg.type === "attachment") {
3462
+ const attachment = isObject3(msg.attachment) ? msg.attachment : null;
3463
+ if (typeof attachment?.prompt === "string") noteNotifications(attachment.prompt, deliveredIds);
3464
+ continue;
3465
+ }
3466
+ if (msg.type === "queue-operation" && typeof msg.content === "string") {
3467
+ const op = msg.operation;
3468
+ noteNotifications(msg.content, op === "enqueue" ? queuedIds : deliveredIds);
3469
+ }
3470
+ }
3471
+ return {
3472
+ pendingDescriptions: launches.filter((launch) => !launch.ids.some((id) => resolved.has(id))).map((launch) => launch.description),
3473
+ undeliveredNotifications: [...queuedIds].filter((id) => !deliveredIds.has(id)).length
3474
+ };
3475
+ }
3476
+ function awaitingAgentsResult(pendingDescriptions, pendingQueue) {
3477
+ return {
3478
+ status: "awaiting_agents",
3479
+ pendingQueue,
3480
+ pendingAgents: pendingDescriptions.length,
3481
+ pendingAgentDescriptions: pendingDescriptions.filter((description) => description.length > 0)
3482
+ };
3483
+ }
3273
3484
  function deriveSessionStatus(rawMessages) {
3274
3485
  const firstType = rawMessages[0]?.type;
3275
3486
  if (firstType === "session_meta" || firstType === "turn_context" || firstType === "event_msg" || firstType === "response_item") {
3276
3487
  return deriveCodexSessionStatus(rawMessages);
3277
3488
  }
3278
3489
  let pendingEnqueues = 0;
3490
+ let backgroundWork = null;
3491
+ let deliveringNotification = false;
3279
3492
  function result(status, toolName) {
3280
3493
  const info = { status, pendingQueue: Math.max(0, pendingEnqueues) };
3281
3494
  if (toolName) info.toolName = toolName;
3282
3495
  return info;
3283
3496
  }
3497
+ function getBackgroundWork() {
3498
+ return backgroundWork ??= collectClaudeBackgroundWork(rawMessages);
3499
+ }
3284
3500
  for (let i = rawMessages.length - 1; i >= 0; i--) {
3285
3501
  const msg = rawMessages[i];
3286
3502
  if (msg.type === "queue-operation") continue;
@@ -3297,15 +3513,26 @@ function deriveSessionStatus(rawMessages) {
3297
3513
  for (let i = rawMessages.length - 1; i >= 0; i--) {
3298
3514
  const msg = rawMessages[i];
3299
3515
  if (msg.type === "queue-operation") {
3516
+ if (typeof msg.content === "string" && msg.content.includes(TASK_NOTIFICATION_MARKER)) continue;
3300
3517
  const op = msg.operation;
3301
3518
  if (op === "enqueue") pendingEnqueues++;
3302
3519
  else if (op === "dequeue" || op === "remove") pendingEnqueues--;
3303
3520
  continue;
3304
3521
  }
3522
+ if (msg.type === "attachment") {
3523
+ const attachment = isObject3(msg.attachment) ? msg.attachment : null;
3524
+ if (attachment?.commandMode === "task-notification") deliveringNotification = true;
3525
+ continue;
3526
+ }
3305
3527
  if (msg.type === "assistant") {
3306
3528
  const message = msg.message;
3307
3529
  const stopReason = message?.stop_reason;
3308
3530
  if (stopReason === "end_turn") {
3531
+ const work = getBackgroundWork();
3532
+ if (deliveringNotification || work.undeliveredNotifications > 0) return result("processing");
3533
+ if (work.pendingDescriptions.length > 0) {
3534
+ return awaitingAgentsResult(work.pendingDescriptions, Math.max(0, pendingEnqueues));
3535
+ }
3309
3536
  let hasActivity = false;
3310
3537
  for (let j = i - 1; j >= 0; j--) {
3311
3538
  const m = rawMessages[j];
@@ -3340,14 +3567,124 @@ function deriveSessionStatus(rawMessages) {
3340
3567
  }
3341
3568
  return { status: "idle" };
3342
3569
  }
3570
+ var TERMINAL_INTER_AGENT_TYPES = /* @__PURE__ */ new Set(["FINAL_ANSWER", "ERROR", "FAILED", "INTERRUPTED"]);
3571
+ function codexPayloadText(payload) {
3572
+ const content = payload.content;
3573
+ if (!Array.isArray(content)) return "";
3574
+ return content.filter((block) => isObject3(block) && block.type === "input_text" && typeof block.text === "string").map((block) => block.text).join("\n");
3575
+ }
3576
+ function parseJsonObject(value) {
3577
+ if (typeof value !== "string") return null;
3578
+ try {
3579
+ const parsed = JSON.parse(value);
3580
+ return isObject3(parsed) ? parsed : null;
3581
+ } catch {
3582
+ return null;
3583
+ }
3584
+ }
3585
+ function collectCodexPendingAgents(rawMessages) {
3586
+ const alive = /* @__PURE__ */ new Map();
3587
+ const done = /* @__PURE__ */ new Set();
3588
+ const agentIdByPath = /* @__PURE__ */ new Map();
3589
+ const spawnCalls = /* @__PURE__ */ new Map();
3590
+ const waitCalls = /* @__PURE__ */ new Set();
3591
+ const interruptCalls = /* @__PURE__ */ new Map();
3592
+ function markAlive(agentId, description) {
3593
+ if (!done.has(agentId)) alive.set(agentId, alive.get(agentId) || description);
3594
+ }
3595
+ for (const msg of rawMessages) {
3596
+ const payload = isObject3(msg.payload) ? msg.payload : null;
3597
+ if (!payload) continue;
3598
+ if (msg.type === "event_msg" && payload.type === "sub_agent_activity" && typeof payload.agent_thread_id === "string" && typeof payload.agent_path === "string") {
3599
+ const agentId = payload.agent_thread_id;
3600
+ const kind = typeof payload.kind === "string" ? payload.kind : "started";
3601
+ const eventId = typeof payload.event_id === "string" ? payload.event_id : "";
3602
+ const existingId = agentIdByPath.get(payload.agent_path);
3603
+ const known = alive.has(agentId) || done.has(agentId) || existingId !== void 0 || spawnCalls.has(eventId);
3604
+ if (kind !== "started" && !known) continue;
3605
+ if (existingId && existingId !== agentId) {
3606
+ if (alive.has(existingId)) {
3607
+ markAlive(agentId, alive.get(existingId) ?? "");
3608
+ alive.delete(existingId);
3609
+ }
3610
+ if (done.has(existingId)) done.add(agentId);
3611
+ }
3612
+ agentIdByPath.set(payload.agent_path, agentId);
3613
+ if (kind === "interrupted") {
3614
+ done.add(agentId);
3615
+ alive.delete(agentId);
3616
+ } else {
3617
+ markAlive(agentId, spawnCalls.get(eventId) ?? "");
3618
+ }
3619
+ continue;
3620
+ }
3621
+ if (msg.type !== "response_item") continue;
3622
+ if (payload.type === "function_call" && typeof payload.call_id === "string") {
3623
+ const name = normalizeFunctionName(typeof payload.name === "string" ? payload.name : "");
3624
+ if (name === "spawn_agent") {
3625
+ const input = parseJsonObject(payload.arguments);
3626
+ spawnCalls.set(payload.call_id, typeof input?.task_name === "string" ? input.task_name : "");
3627
+ } else if (name === "wait_agent") {
3628
+ waitCalls.add(payload.call_id);
3629
+ } else if (name === "interrupt_agent") {
3630
+ const input = parseJsonObject(payload.arguments);
3631
+ const targets = [input?.agent_id, ...Array.isArray(input?.agent_ids) ? input.agent_ids : []].filter((id) => typeof id === "string");
3632
+ interruptCalls.set(payload.call_id, targets);
3633
+ }
3634
+ continue;
3635
+ }
3636
+ if (payload.type === "function_call_output" && typeof payload.call_id === "string") {
3637
+ const callId = payload.call_id;
3638
+ if (spawnCalls.has(callId)) {
3639
+ const output = parseJsonObject(payload.output);
3640
+ const agentId = typeof output?.agent_id === "string" ? output.agent_id : "";
3641
+ if (agentId) {
3642
+ markAlive(agentId, spawnCalls.get(callId) ?? "");
3643
+ const agentPath = typeof output?.task_name === "string" ? output.task_name : "";
3644
+ if (agentPath && !agentIdByPath.has(agentPath)) agentIdByPath.set(agentPath, agentId);
3645
+ }
3646
+ } else if (waitCalls.has(callId)) {
3647
+ const output = parseJsonObject(payload.output);
3648
+ const statusMap = isObject3(output?.status) ? output.status : {};
3649
+ for (const [agentId, status] of Object.entries(statusMap)) {
3650
+ const statusValue = isObject3(status) ? status : {};
3651
+ const lifecycle = typeof statusValue.status === "string" ? statusValue.status : "";
3652
+ const isTerminal = typeof statusValue.completed === "string" || typeof statusValue.failed === "string" || typeof statusValue.error === "string" || typeof statusValue.interrupted === "string" || lifecycle === "completed" || lifecycle === "failed" || lifecycle === "interrupted";
3653
+ if (isTerminal) {
3654
+ done.add(agentId);
3655
+ alive.delete(agentId);
3656
+ }
3657
+ }
3658
+ } else if (interruptCalls.has(callId)) {
3659
+ for (const agentId of interruptCalls.get(callId) ?? []) {
3660
+ done.add(agentId);
3661
+ alive.delete(agentId);
3662
+ }
3663
+ }
3664
+ continue;
3665
+ }
3666
+ if (payload.type === "agent_message" && typeof payload.author === "string") {
3667
+ const text = codexPayloadText(payload);
3668
+ const messageType = text.match(/^Message Type:\s*([^\r\n]+)/)?.[1]?.trim().toUpperCase() ?? "";
3669
+ if (!TERMINAL_INTER_AGENT_TYPES.has(messageType)) continue;
3670
+ const agentId = agentIdByPath.get(payload.author) ?? payload.author;
3671
+ done.add(agentId);
3672
+ alive.delete(agentId);
3673
+ }
3674
+ }
3675
+ return [...alive.entries()].filter(([agentId]) => !done.has(agentId)).map(([, description]) => description);
3676
+ }
3343
3677
  function deriveCodexSessionStatus(rawMessages) {
3344
3678
  for (let i = rawMessages.length - 1; i >= 0; i--) {
3345
3679
  const msg = rawMessages[i];
3346
3680
  if (msg.type === "event_msg") {
3347
3681
  const payload = msg.payload;
3348
3682
  switch (payload?.type) {
3349
- case "task_complete":
3683
+ case "task_complete": {
3684
+ const pending = collectCodexPendingAgents(rawMessages);
3685
+ if (pending.length > 0) return awaitingAgentsResult(pending, 0);
3350
3686
  return { status: "completed" };
3687
+ }
3351
3688
  case "task_started":
3352
3689
  return { status: "processing" };
3353
3690
  case "agent_message":
@@ -3360,7 +3697,7 @@ function deriveCodexSessionStatus(rawMessages) {
3360
3697
  const payload = msg.payload;
3361
3698
  if (!payload) continue;
3362
3699
  if (payload.type === "function_call") {
3363
- return { status: "tool_use", toolName: payload.name };
3700
+ return { status: "tool_use", toolName: payload.name ? normalizeFunctionName(payload.name) : payload.name };
3364
3701
  }
3365
3702
  if (payload.type === "message") {
3366
3703
  if (payload.role === "assistant") return { status: "thinking" };
@@ -3659,8 +3996,7 @@ async function listSessions(opts = {}) {
3659
3996
  return results;
3660
3997
  }
3661
3998
  async function currentSession(cwd) {
3662
- const projectDirName = cwd.replace(/[/.]/g, "-");
3663
- const projectDir = (0, import_node_path6.join)(dirs.PROJECTS_DIR, projectDirName);
3999
+ const projectDir = (0, import_node_path6.join)(dirs.PROJECTS_DIR, encodeClaudeDirName(cwd));
3664
4000
  let files;
3665
4001
  try {
3666
4002
  files = await (0, import_promises5.readdir)(projectDir);