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/LICENSE +21 -0
- package/dist/cli.js +630 -286
- package/dist/commands/sessions.d.ts +0 -3
- package/dist/index.js +673 -337
- package/dist/lib/agentEnvelope.d.ts +24 -0
- package/dist/lib/codex-exec.d.ts +27 -0
- package/dist/lib/codex-tool-normalization.d.ts +6 -0
- package/dist/lib/helpers.d.ts +14 -0
- package/dist/lib/interactiveState.d.ts +7 -2
- package/dist/lib/messageTypeGuards.d.ts +10 -1
- package/dist/lib/sessionStats.d.ts +4 -0
- package/dist/lib/sessionStatus.d.ts +12 -6
- package/dist/lib/token-costs.d.ts +15 -36
- package/dist/lib/turnBuilder.d.ts +33 -0
- package/dist/lib/types.d.ts +159 -7
- package/package.json +20 -2
- package/skill/SKILL.md +1 -1
- package/dist/lib/pricingTiers.d.ts +0 -30
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
|
-
|
|
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
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
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 =
|
|
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 =
|
|
294
|
-
|
|
295
|
-
|
|
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
|
-
|
|
306
|
-
|
|
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
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
846
|
+
function addUsageToStats(stats, usage) {
|
|
773
847
|
stats.totalInputTokens += usage.input_tokens;
|
|
774
|
-
stats.totalOutputTokens +=
|
|
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-
|
|
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
|
-
|
|
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(
|
|
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
|
|
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
|
|
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 =
|
|
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
|
-
|
|
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.
|
|
1555
|
-
existing.
|
|
1556
|
-
|
|
1557
|
-
} else {
|
|
1558
|
-
|
|
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:
|
|
1689
|
+
name: `mcp__${server}__${tool}`,
|
|
1561
1690
|
input,
|
|
1562
|
-
result:
|
|
1563
|
-
isError:
|
|
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
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
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:
|
|
1636
|
-
name: "
|
|
1637
|
-
input,
|
|
1638
|
-
result:
|
|
1639
|
-
isError:
|
|
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
|
-
|
|
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
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
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
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
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
|
|
1972
|
-
const
|
|
1973
|
-
const tailTurns = buildTurns(
|
|
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
|
-
|
|
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
|
|
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
|
|
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);
|