cogpit-memory 0.1.9 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +26 -5
- package/dist/cli.d.ts +30 -0
- package/dist/cli.js +1618 -338
- package/dist/commands/context.d.ts +20 -0
- package/dist/commands/index-cmd.d.ts +15 -0
- package/dist/commands/install-skill.d.ts +4 -0
- package/dist/commands/search.d.ts +39 -0
- package/dist/commands/sessions.d.ts +38 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.js +1659 -386
- package/dist/lib/agentEnvelope.d.ts +24 -0
- package/dist/lib/codex-exec.d.ts +27 -0
- package/dist/lib/codex-patches.d.ts +9 -0
- package/dist/lib/codex-tool-normalization.d.ts +14 -0
- package/dist/lib/codex.d.ts +30 -0
- package/dist/lib/dirs.d.ts +8 -0
- package/dist/lib/helpers.d.ts +25 -0
- package/dist/lib/interactiveState.d.ts +40 -0
- package/dist/lib/messageTypeGuards.d.ts +26 -0
- package/dist/lib/metadata.d.ts +31 -0
- package/dist/lib/parser.d.ts +14 -0
- package/dist/lib/response.d.ts +2 -0
- package/dist/lib/search-index.d.ts +154 -0
- package/dist/lib/sessionStats.d.ts +10 -0
- package/dist/lib/sessionStatus.d.ts +42 -0
- package/dist/lib/token-costs.d.ts +27 -0
- package/dist/lib/turnBuilder.d.ts +38 -0
- package/dist/lib/types.d.ts +553 -0
- package/package.json +32 -8
- package/skill/SKILL.md +4 -4
package/dist/index.js
CHANGED
|
@@ -57,7 +57,7 @@ var Database = import_better_sqlite3.default;
|
|
|
57
57
|
var import_node_fs = require("node:fs");
|
|
58
58
|
var import_node_path = require("node:path");
|
|
59
59
|
|
|
60
|
-
// src/lib/
|
|
60
|
+
// src/lib/messageTypeGuards.ts
|
|
61
61
|
function isUserMessage(msg) {
|
|
62
62
|
return msg.type === "user";
|
|
63
63
|
}
|
|
@@ -73,9 +73,45 @@ function isSystemMessage(msg) {
|
|
|
73
73
|
function isSummaryMessage(msg) {
|
|
74
74
|
return msg.type === "summary";
|
|
75
75
|
}
|
|
76
|
+
function isQueueOperationMessage(msg) {
|
|
77
|
+
return msg.type === "queue-operation";
|
|
78
|
+
}
|
|
79
|
+
function isAttachmentMessage(msg) {
|
|
80
|
+
return msg.type === "attachment";
|
|
81
|
+
}
|
|
82
|
+
function isCompactSummaryMessage(msg) {
|
|
83
|
+
return msg.type === "user" && msg.isCompactSummary === true;
|
|
84
|
+
}
|
|
76
85
|
function isCompactBoundary(msg) {
|
|
77
86
|
return msg.type === "system" && msg.subtype === "compact_boundary";
|
|
78
87
|
}
|
|
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
|
+
|
|
114
|
+
// src/lib/turnBuilder.ts
|
|
79
115
|
function extractTextFromContent(content) {
|
|
80
116
|
if (typeof content === "string") return content;
|
|
81
117
|
return content.filter((b) => b.type === "text").map((b) => b.text).join("\n");
|
|
@@ -88,6 +124,62 @@ function extractToolResultText(content) {
|
|
|
88
124
|
return "";
|
|
89
125
|
}).filter(Boolean).join("\n");
|
|
90
126
|
}
|
|
127
|
+
function isVisibleQueuedPrompt(content) {
|
|
128
|
+
if (!content?.trim()) return false;
|
|
129
|
+
const trimmed = content.trimStart();
|
|
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;
|
|
182
|
+
}
|
|
91
183
|
function mergeTokenUsage(existing, incoming) {
|
|
92
184
|
if (!existing) {
|
|
93
185
|
return { ...incoming };
|
|
@@ -96,49 +188,234 @@ function mergeTokenUsage(existing, incoming) {
|
|
|
96
188
|
input_tokens: existing.input_tokens + incoming.input_tokens,
|
|
97
189
|
output_tokens: existing.output_tokens + incoming.output_tokens,
|
|
98
190
|
cache_creation_input_tokens: (existing.cache_creation_input_tokens ?? 0) + (incoming.cache_creation_input_tokens ?? 0),
|
|
99
|
-
cache_read_input_tokens: (existing.cache_read_input_tokens ?? 0) + (incoming.cache_read_input_tokens ?? 0)
|
|
191
|
+
cache_read_input_tokens: (existing.cache_read_input_tokens ?? 0) + (incoming.cache_read_input_tokens ?? 0),
|
|
192
|
+
speed: incoming.speed ?? existing.speed
|
|
100
193
|
};
|
|
101
194
|
}
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
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
|
+
};
|
|
219
|
+
}
|
|
220
|
+
function groupPlanModeBlocks(blocks) {
|
|
221
|
+
const result = [];
|
|
222
|
+
let i = 0;
|
|
223
|
+
while (i < blocks.length) {
|
|
224
|
+
const block = blocks[i];
|
|
225
|
+
if (block.kind === "tool_calls") {
|
|
226
|
+
const enterIdx = block.toolCalls.findIndex((tc) => tc.name === "EnterPlanMode");
|
|
227
|
+
if (enterIdx !== -1) {
|
|
228
|
+
const enterCall = block.toolCalls[enterIdx];
|
|
229
|
+
const plan = String(enterCall.input.plan ?? "");
|
|
230
|
+
const timestamp = block.timestamp;
|
|
231
|
+
const before = block.toolCalls.slice(0, enterIdx);
|
|
232
|
+
if (before.length > 0) {
|
|
233
|
+
result.push({ kind: "tool_calls", toolCalls: before, timestamp: block.timestamp });
|
|
234
|
+
}
|
|
235
|
+
const embedded = [];
|
|
236
|
+
const afterEnterInSameBlock = block.toolCalls.slice(enterIdx + 1);
|
|
237
|
+
const exitInSameBlock = afterEnterInSameBlock.findIndex((tc) => tc.name === "ExitPlanMode");
|
|
238
|
+
if (exitInSameBlock !== -1) {
|
|
239
|
+
embedded.push(...afterEnterInSameBlock.slice(0, exitInSameBlock));
|
|
240
|
+
const exitCall = afterEnterInSameBlock[exitInSameBlock];
|
|
241
|
+
const planFilePath = exitCall.input.path ? String(exitCall.input.path) : void 0;
|
|
242
|
+
let status = "pending";
|
|
243
|
+
if (exitCall.result !== null) {
|
|
244
|
+
status = exitCall.isError ? "rejected" : "approved";
|
|
245
|
+
}
|
|
246
|
+
result.push({ kind: "plan_mode", plan, planFilePath, status, toolCalls: embedded, timestamp });
|
|
247
|
+
const after = afterEnterInSameBlock.slice(exitInSameBlock + 1);
|
|
248
|
+
if (after.length > 0) {
|
|
249
|
+
result.push({ kind: "tool_calls", toolCalls: after, timestamp: block.timestamp });
|
|
250
|
+
}
|
|
251
|
+
i++;
|
|
252
|
+
continue;
|
|
253
|
+
} else {
|
|
254
|
+
embedded.push(...afterEnterInSameBlock);
|
|
255
|
+
let exitCall;
|
|
256
|
+
let planFilePath;
|
|
257
|
+
let status = "pending";
|
|
258
|
+
let j = i + 1;
|
|
259
|
+
const passthroughBlocks = [];
|
|
260
|
+
while (j < blocks.length) {
|
|
261
|
+
const next = blocks[j];
|
|
262
|
+
if (next.kind === "tool_calls") {
|
|
263
|
+
const exitIdx = next.toolCalls.findIndex((tc) => tc.name === "ExitPlanMode");
|
|
264
|
+
if (exitIdx !== -1) {
|
|
265
|
+
embedded.push(...next.toolCalls.slice(0, exitIdx));
|
|
266
|
+
exitCall = next.toolCalls[exitIdx];
|
|
267
|
+
planFilePath = exitCall.input.path ? String(exitCall.input.path) : void 0;
|
|
268
|
+
if (exitCall.result !== null) {
|
|
269
|
+
status = exitCall.isError ? "rejected" : "approved";
|
|
270
|
+
}
|
|
271
|
+
const tail = next.toolCalls.slice(exitIdx + 1);
|
|
272
|
+
result.push({ kind: "plan_mode", plan, planFilePath, status, toolCalls: embedded, timestamp });
|
|
273
|
+
result.push(...passthroughBlocks);
|
|
274
|
+
if (tail.length > 0) {
|
|
275
|
+
result.push({ kind: "tool_calls", toolCalls: tail, timestamp: next.timestamp });
|
|
276
|
+
}
|
|
277
|
+
i = j + 1;
|
|
278
|
+
break;
|
|
279
|
+
} else {
|
|
280
|
+
embedded.push(...next.toolCalls);
|
|
281
|
+
j++;
|
|
282
|
+
}
|
|
283
|
+
} else if (next.kind === "hook_event" || next.kind === "text" || next.kind === "queued_prompt" || next.kind === "agent_message") {
|
|
284
|
+
passthroughBlocks.push(next);
|
|
285
|
+
j++;
|
|
286
|
+
} else {
|
|
287
|
+
break;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
if (!exitCall) {
|
|
291
|
+
result.push({ kind: "plan_mode", plan, planFilePath: void 0, status: "pending", toolCalls: embedded, timestamp });
|
|
292
|
+
result.push(...passthroughBlocks);
|
|
293
|
+
i = j;
|
|
294
|
+
}
|
|
295
|
+
continue;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
108
298
|
}
|
|
299
|
+
result.push(block);
|
|
300
|
+
i++;
|
|
109
301
|
}
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
302
|
+
return result;
|
|
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
|
+
}
|
|
119
339
|
}
|
|
120
340
|
}
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
if (
|
|
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
|
+
}
|
|
129
364
|
}
|
|
130
|
-
return
|
|
365
|
+
return starts;
|
|
131
366
|
}
|
|
132
367
|
function buildTurns(messages) {
|
|
133
368
|
const turns = [];
|
|
134
369
|
let current = null;
|
|
135
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
|
+
}
|
|
377
|
+
let pendingRecap = null;
|
|
136
378
|
const pendingToolUses = /* @__PURE__ */ new Map();
|
|
137
379
|
const seenMessageIds = /* @__PURE__ */ new Set();
|
|
138
380
|
const subAgentMap = /* @__PURE__ */ new Map();
|
|
139
381
|
const agentBlockMap = /* @__PURE__ */ new Map();
|
|
140
382
|
const backgroundAgentParentIds = /* @__PURE__ */ new Set();
|
|
141
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
|
+
}
|
|
142
419
|
function flushSubAgentMessages(parentId) {
|
|
143
420
|
if (!current) return;
|
|
144
421
|
const agentMsgs = subAgentMap.get(parentId);
|
|
@@ -163,6 +440,22 @@ function buildTurns(messages) {
|
|
|
163
440
|
for (const [parentId] of subAgentMap) {
|
|
164
441
|
flushSubAgentMessages(parentId);
|
|
165
442
|
}
|
|
443
|
+
for (const block of current.contentBlocks) {
|
|
444
|
+
if (block.kind !== "hook_event") continue;
|
|
445
|
+
for (const ev of block.events) {
|
|
446
|
+
const isPostToolUse = ev.eventName === "PostToolUse" || ev.eventName.startsWith("PostToolUse:");
|
|
447
|
+
if (!isPostToolUse || !ev.toolUseId) continue;
|
|
448
|
+
const toolCall = current.toolCalls.find((tc) => tc.id === ev.toolUseId);
|
|
449
|
+
if (!toolCall) continue;
|
|
450
|
+
if (ev.updatedToolOutput) {
|
|
451
|
+
toolCall.outputReplacedByHook = true;
|
|
452
|
+
}
|
|
453
|
+
if (ev.durationMs !== void 0) {
|
|
454
|
+
toolCall.hookDurationMs = (toolCall.hookDurationMs ?? 0) + ev.durationMs;
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
current.contentBlocks = groupPlanModeBlocks(current.contentBlocks);
|
|
166
459
|
turns.push(current);
|
|
167
460
|
current = null;
|
|
168
461
|
agentBlockMap.clear();
|
|
@@ -170,20 +463,66 @@ function buildTurns(messages) {
|
|
|
170
463
|
for (const msg of messages) {
|
|
171
464
|
if (isSummaryMessage(msg)) {
|
|
172
465
|
finalizeTurn2();
|
|
173
|
-
pendingCompaction =
|
|
174
|
-
turns,
|
|
175
|
-
msg.summary ?? "Conversation compacted"
|
|
176
|
-
);
|
|
466
|
+
pendingCompaction = { summary: msg.summary };
|
|
177
467
|
continue;
|
|
178
468
|
}
|
|
179
469
|
if (isCompactBoundary(msg)) {
|
|
180
470
|
finalizeTurn2();
|
|
181
|
-
pendingCompaction =
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
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;
|
|
480
|
+
continue;
|
|
481
|
+
}
|
|
482
|
+
if (isSystemMessage(msg) && msg.subtype === "away_summary" && msg.content) {
|
|
483
|
+
pendingRecap = { content: msg.content, timestamp: msg.timestamp };
|
|
484
|
+
continue;
|
|
485
|
+
}
|
|
486
|
+
if (isQueueOperationMessage(msg)) {
|
|
487
|
+
if (current && msg.operation === "enqueue" && isVisibleQueuedPrompt(msg.content)) {
|
|
488
|
+
const parsed = parseAgentEnvelope(msg.content);
|
|
489
|
+
pendingQueuedPrompts.push({
|
|
490
|
+
turn: current,
|
|
491
|
+
content: msg.content,
|
|
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
|
|
509
|
+
});
|
|
510
|
+
}
|
|
185
511
|
continue;
|
|
186
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
|
+
}
|
|
187
526
|
if (isUserMessage(msg) && !msg.isMeta) {
|
|
188
527
|
const content = msg.message.content;
|
|
189
528
|
if (typeof content !== "string" && Array.isArray(content)) {
|
|
@@ -194,7 +533,7 @@ function buildTurns(messages) {
|
|
|
194
533
|
const pending = pendingToolUses.get(block.tool_use_id);
|
|
195
534
|
if (pending) {
|
|
196
535
|
pending.turn.toolCalls[pending.index].result = extractToolResultText(block.content);
|
|
197
|
-
pending.turn.toolCalls[pending.index].isError = block.is_error;
|
|
536
|
+
pending.turn.toolCalls[pending.index].isError = block.is_error === true;
|
|
198
537
|
pendingToolUses.delete(block.tool_use_id);
|
|
199
538
|
}
|
|
200
539
|
}
|
|
@@ -212,6 +551,7 @@ function buildTurns(messages) {
|
|
|
212
551
|
}
|
|
213
552
|
const agentMsg = {
|
|
214
553
|
agentId: toolUseResult.agentId,
|
|
554
|
+
parentToolUseId: toolUseId || void 0,
|
|
215
555
|
agentName: taskMeta?.name ?? null,
|
|
216
556
|
subagentType: taskMeta?.subagentType ?? null,
|
|
217
557
|
type: "assistant",
|
|
@@ -245,23 +585,27 @@ function buildTurns(messages) {
|
|
|
245
585
|
continue;
|
|
246
586
|
}
|
|
247
587
|
}
|
|
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;
|
|
602
|
+
}
|
|
248
603
|
finalizeTurn2();
|
|
249
|
-
current =
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
contentBlocks:
|
|
253
|
-
|
|
254
|
-
assistantText: [],
|
|
255
|
-
toolCalls: [],
|
|
256
|
-
subAgentActivity: [],
|
|
257
|
-
timestamp: msg.timestamp ?? "",
|
|
258
|
-
durationMs: null,
|
|
259
|
-
tokenUsage: null,
|
|
260
|
-
model: null
|
|
261
|
-
};
|
|
262
|
-
if (pendingCompaction) {
|
|
263
|
-
current.compactionSummary = pendingCompaction;
|
|
264
|
-
pendingCompaction = null;
|
|
604
|
+
current = createTurn(msg, msg.message.content);
|
|
605
|
+
attachPendingCompaction(current);
|
|
606
|
+
if (pendingRecap) {
|
|
607
|
+
current.contentBlocks.push({ kind: "recap", content: pendingRecap.content, timestamp: pendingRecap.timestamp });
|
|
608
|
+
pendingRecap = null;
|
|
265
609
|
}
|
|
266
610
|
continue;
|
|
267
611
|
}
|
|
@@ -284,21 +628,12 @@ function buildTurns(messages) {
|
|
|
284
628
|
};
|
|
285
629
|
var flushToolCalls = flushToolCalls2, flushThinking = flushThinking2;
|
|
286
630
|
if (!current) {
|
|
287
|
-
current =
|
|
288
|
-
|
|
289
|
-
userMessage: null,
|
|
290
|
-
contentBlocks: [],
|
|
291
|
-
thinking: [],
|
|
292
|
-
assistantText: [],
|
|
293
|
-
toolCalls: [],
|
|
294
|
-
subAgentActivity: [],
|
|
295
|
-
timestamp: msg.timestamp ?? "",
|
|
296
|
-
durationMs: null,
|
|
297
|
-
tokenUsage: null,
|
|
298
|
-
model: null
|
|
299
|
-
};
|
|
631
|
+
current = createTurn(msg, null);
|
|
632
|
+
attachPendingCompaction(current);
|
|
300
633
|
}
|
|
301
634
|
current.model = msg.message.model;
|
|
635
|
+
if (msg.effort) current.effort = msg.effort;
|
|
636
|
+
current.attribution = mergeAttribution(current.attribution, msg);
|
|
302
637
|
const msgId = msg.message.id;
|
|
303
638
|
if (!seenMessageIds.has(msgId)) {
|
|
304
639
|
seenMessageIds.add(msgId);
|
|
@@ -312,6 +647,7 @@ function buildTurns(messages) {
|
|
|
312
647
|
if (block.type === "thinking") {
|
|
313
648
|
flushToolCalls2();
|
|
314
649
|
const tb = block;
|
|
650
|
+
if (!tb.thinking) continue;
|
|
315
651
|
current.thinking.push(tb);
|
|
316
652
|
msgThinking.push(tb);
|
|
317
653
|
} else if (block.type === "text") {
|
|
@@ -391,6 +727,7 @@ function buildTurns(messages) {
|
|
|
391
727
|
const taskMeta = taskMetaMap.get(parentId);
|
|
392
728
|
const agentMsg = {
|
|
393
729
|
agentId: data.agentId,
|
|
730
|
+
parentToolUseId: parentId || void 0,
|
|
394
731
|
agentName: taskMeta?.name ?? null,
|
|
395
732
|
subagentType: taskMeta?.subagentType ?? null,
|
|
396
733
|
type: data.message.type,
|
|
@@ -436,7 +773,7 @@ function buildTurns(messages) {
|
|
|
436
773
|
);
|
|
437
774
|
if (match) {
|
|
438
775
|
match.result = extractToolResultText(block.content);
|
|
439
|
-
match.isError = block.is_error;
|
|
776
|
+
match.isError = block.is_error === true;
|
|
440
777
|
}
|
|
441
778
|
}
|
|
442
779
|
}
|
|
@@ -455,118 +792,46 @@ function buildTurns(messages) {
|
|
|
455
792
|
}
|
|
456
793
|
continue;
|
|
457
794
|
}
|
|
795
|
+
if (isProgressMessage(msg) && msg.data.type === "hook_progress") {
|
|
796
|
+
const data = msg.data;
|
|
797
|
+
const eventName = String(data.hook_event_name ?? data.hookEvent ?? "unknown");
|
|
798
|
+
const hookSpecific = data.hookSpecificOutput;
|
|
799
|
+
const ev = {
|
|
800
|
+
eventName,
|
|
801
|
+
source: data.source !== void 0 ? String(data.source) : void 0,
|
|
802
|
+
toolName: data.tool_name !== void 0 ? String(data.tool_name) : void 0,
|
|
803
|
+
toolUseId: data.tool_use_id !== void 0 ? String(data.tool_use_id) : void 0,
|
|
804
|
+
command: data.command !== void 0 ? String(data.command) : void 0,
|
|
805
|
+
output: data.output !== void 0 ? String(data.output) : void 0,
|
|
806
|
+
stderr: data.stderr !== void 0 ? String(data.stderr) : void 0,
|
|
807
|
+
exitCode: data.exit_code !== void 0 ? Number(data.exit_code) : void 0,
|
|
808
|
+
decision: data.decision !== void 0 ? String(data.decision) : void 0,
|
|
809
|
+
durationMs: data.duration_ms !== void 0 ? Number(data.duration_ms) : void 0,
|
|
810
|
+
updatedToolOutput: hookSpecific?.updatedToolOutput !== void 0 ? String(hookSpecific.updatedToolOutput) : void 0,
|
|
811
|
+
sessionTitle: hookSpecific?.sessionTitle !== void 0 ? String(hookSpecific.sessionTitle) : void 0,
|
|
812
|
+
worktreePath: hookSpecific?.worktreePath !== void 0 ? String(hookSpecific.worktreePath) : void 0,
|
|
813
|
+
timestamp: msg.timestamp ?? ""
|
|
814
|
+
};
|
|
815
|
+
if (current) {
|
|
816
|
+
const last = current.contentBlocks[current.contentBlocks.length - 1];
|
|
817
|
+
if (last && last.kind === "hook_event") {
|
|
818
|
+
last.events.push(ev);
|
|
819
|
+
} else {
|
|
820
|
+
current.contentBlocks.push({ kind: "hook_event", events: [ev], timestamp: msg.timestamp });
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
continue;
|
|
824
|
+
}
|
|
458
825
|
if (isSystemMessage(msg) && msg.subtype === "turn_duration" && current) {
|
|
459
|
-
|
|
826
|
+
if (msg.durationMs !== void 0 && msg.durationMs !== null) {
|
|
827
|
+
current.durationMs = (current.durationMs ?? 0) + msg.durationMs;
|
|
828
|
+
}
|
|
460
829
|
continue;
|
|
461
830
|
}
|
|
462
831
|
}
|
|
832
|
+
flushPendingQueuedPrompts();
|
|
463
833
|
finalizeTurn2();
|
|
464
|
-
return turns;
|
|
465
|
-
}
|
|
466
|
-
|
|
467
|
-
// src/lib/pricingTiers.ts
|
|
468
|
-
var EXTENDED_CONTEXT_THRESHOLD = 2e5;
|
|
469
|
-
var TIER_HAIKU_35 = { input: 0.8, output: 4, cacheWrite: 1, cacheRead: 0.08, webSearch: 0.01 };
|
|
470
|
-
var TIER_HAIKU_45 = { input: 1, output: 5, cacheWrite: 1.25, cacheRead: 0.1, webSearch: 0.01 };
|
|
471
|
-
var TIER_SONNET_LEGACY = { input: 3, output: 15, cacheWrite: 3.75, cacheRead: 0.3, webSearch: 0.01 };
|
|
472
|
-
var TIER_SONNET_LATEST = { input: 5, output: 25, cacheWrite: 6.25, cacheRead: 0.5, webSearch: 0.01 };
|
|
473
|
-
var TIER_OPUS_LEGACY = { input: 15, output: 75, cacheWrite: 18.75, cacheRead: 1.5, webSearch: 0.01 };
|
|
474
|
-
var TIER_SONNET_LEGACY_EXT = { input: 6, output: 22.5, cacheWrite: 7.5, cacheRead: 0.6, webSearch: 0.01 };
|
|
475
|
-
var TIER_EXTENDED = { input: 10, output: 37.5, cacheWrite: 12.5, cacheRead: 1, webSearch: 0.01 };
|
|
476
|
-
var MODEL_TIERS = [
|
|
477
|
-
// Haiku
|
|
478
|
-
{ match: "haiku-4-5", tier: TIER_HAIKU_45 },
|
|
479
|
-
{ match: "haiku-4-0", tier: TIER_HAIKU_35 },
|
|
480
|
-
{ match: "3-5-haiku", tier: TIER_HAIKU_35 },
|
|
481
|
-
// Sonnet latest (4.5+)
|
|
482
|
-
{ match: "sonnet-4-6", tier: TIER_SONNET_LATEST, extendedTier: TIER_EXTENDED },
|
|
483
|
-
{ match: "sonnet-4-5", tier: TIER_SONNET_LATEST, extendedTier: TIER_EXTENDED },
|
|
484
|
-
// Sonnet legacy (3.5, 3.7, 4.0)
|
|
485
|
-
{ match: "sonnet-4-0", tier: TIER_SONNET_LEGACY, extendedTier: TIER_SONNET_LEGACY_EXT },
|
|
486
|
-
{ match: "3-7-sonnet", tier: TIER_SONNET_LEGACY, extendedTier: TIER_SONNET_LEGACY_EXT },
|
|
487
|
-
{ match: "3-5-sonnet", tier: TIER_SONNET_LEGACY, extendedTier: TIER_SONNET_LEGACY_EXT },
|
|
488
|
-
// Opus latest (4.5+) — same tier as sonnet latest
|
|
489
|
-
{ match: "opus-4-6", tier: TIER_SONNET_LATEST, extendedTier: TIER_EXTENDED },
|
|
490
|
-
{ match: "opus-4-5", tier: TIER_SONNET_LATEST, extendedTier: TIER_EXTENDED },
|
|
491
|
-
// Opus legacy (4.0, 4.1)
|
|
492
|
-
{ match: "opus-4-1", tier: TIER_OPUS_LEGACY },
|
|
493
|
-
{ match: "opus-4-0", tier: TIER_OPUS_LEGACY }
|
|
494
|
-
];
|
|
495
|
-
var DEFAULT_TIER = TIER_SONNET_LATEST;
|
|
496
|
-
var DEFAULT_EXTENDED_TIER = TIER_EXTENDED;
|
|
497
|
-
var FAMILY_FALLBACKS = [
|
|
498
|
-
{ match: "haiku", tier: TIER_HAIKU_45 },
|
|
499
|
-
{ match: "sonnet", tier: TIER_SONNET_LATEST },
|
|
500
|
-
{ match: "opus", tier: TIER_SONNET_LATEST }
|
|
501
|
-
];
|
|
502
|
-
function resolveTier(model, totalInputTokens) {
|
|
503
|
-
const isExtended = (totalInputTokens ?? 0) > EXTENDED_CONTEXT_THRESHOLD;
|
|
504
|
-
for (const entry of MODEL_TIERS) {
|
|
505
|
-
if (model.includes(entry.match)) {
|
|
506
|
-
return isExtended && entry.extendedTier ? entry.extendedTier : entry.tier;
|
|
507
|
-
}
|
|
508
|
-
}
|
|
509
|
-
for (const entry of FAMILY_FALLBACKS) {
|
|
510
|
-
if (model.includes(entry.match)) return entry.tier;
|
|
511
|
-
}
|
|
512
|
-
return isExtended ? DEFAULT_EXTENDED_TIER : DEFAULT_TIER;
|
|
513
|
-
}
|
|
514
|
-
|
|
515
|
-
// src/lib/token-costs.ts
|
|
516
|
-
var CHARS_PER_TOKEN = 4;
|
|
517
|
-
function calculateCost(c) {
|
|
518
|
-
const totalInput = c.inputTokens + c.cacheWriteTokens + c.cacheReadTokens;
|
|
519
|
-
const p = resolveTier(c.model ?? "", totalInput);
|
|
520
|
-
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;
|
|
521
|
-
}
|
|
522
|
-
function charsToTokens(chars) {
|
|
523
|
-
return Math.ceil(chars / CHARS_PER_TOKEN);
|
|
524
|
-
}
|
|
525
|
-
function totalLength(strings) {
|
|
526
|
-
let n = 0;
|
|
527
|
-
for (const s of strings) n += s.length;
|
|
528
|
-
return n;
|
|
529
|
-
}
|
|
530
|
-
function totalToolInputLength(toolCalls) {
|
|
531
|
-
let n = 0;
|
|
532
|
-
for (const tc of toolCalls) n += JSON.stringify(tc.input).length;
|
|
533
|
-
return n;
|
|
534
|
-
}
|
|
535
|
-
function estimateThinkingTokens(turn) {
|
|
536
|
-
return charsToTokens(totalLength(turn.thinking.map((b) => b.thinking)));
|
|
537
|
-
}
|
|
538
|
-
function estimateVisibleOutputTokens(turn) {
|
|
539
|
-
return charsToTokens(totalLength(turn.assistantText) + totalToolInputLength(turn.toolCalls));
|
|
540
|
-
}
|
|
541
|
-
function estimateTotalOutputTokens(turn) {
|
|
542
|
-
const estimated = estimateThinkingTokens(turn) + estimateVisibleOutputTokens(turn);
|
|
543
|
-
return Math.max(estimated, turn.tokenUsage?.output_tokens ?? 0);
|
|
544
|
-
}
|
|
545
|
-
function estimateSubAgentOutput(sa) {
|
|
546
|
-
const chars = totalLength(sa.thinking) + totalLength(sa.text) + totalToolInputLength(sa.toolCalls);
|
|
547
|
-
return Math.max(charsToTokens(chars), sa.tokenUsage?.output_tokens ?? 0);
|
|
548
|
-
}
|
|
549
|
-
function calculateTurnCostEstimated(turn) {
|
|
550
|
-
if (!turn.tokenUsage) return 0;
|
|
551
|
-
const u = turn.tokenUsage;
|
|
552
|
-
return calculateCost({
|
|
553
|
-
model: turn.model,
|
|
554
|
-
inputTokens: u.input_tokens,
|
|
555
|
-
outputTokens: estimateTotalOutputTokens(turn),
|
|
556
|
-
cacheWriteTokens: u.cache_creation_input_tokens ?? 0,
|
|
557
|
-
cacheReadTokens: u.cache_read_input_tokens ?? 0
|
|
558
|
-
});
|
|
559
|
-
}
|
|
560
|
-
function calculateSubAgentCostEstimated(sa) {
|
|
561
|
-
if (!sa.tokenUsage) return 0;
|
|
562
|
-
const u = sa.tokenUsage;
|
|
563
|
-
return calculateCost({
|
|
564
|
-
model: sa.model,
|
|
565
|
-
inputTokens: u.input_tokens,
|
|
566
|
-
outputTokens: estimateSubAgentOutput(sa),
|
|
567
|
-
cacheWriteTokens: u.cache_creation_input_tokens ?? 0,
|
|
568
|
-
cacheReadTokens: u.cache_read_input_tokens ?? 0
|
|
569
|
-
});
|
|
834
|
+
return pairAgentMessageReplies(turns);
|
|
570
835
|
}
|
|
571
836
|
|
|
572
837
|
// src/lib/sessionStats.ts
|
|
@@ -578,43 +843,325 @@ function countToolCalls(toolCalls, counts) {
|
|
|
578
843
|
}
|
|
579
844
|
return errors;
|
|
580
845
|
}
|
|
581
|
-
function addUsageToStats(stats, usage
|
|
846
|
+
function addUsageToStats(stats, usage) {
|
|
582
847
|
stats.totalInputTokens += usage.input_tokens;
|
|
583
|
-
stats.totalOutputTokens +=
|
|
848
|
+
stats.totalOutputTokens += usage.output_tokens;
|
|
584
849
|
stats.totalCacheCreationTokens += usage.cache_creation_input_tokens ?? 0;
|
|
585
850
|
stats.totalCacheReadTokens += usage.cache_read_input_tokens ?? 0;
|
|
586
|
-
stats.totalCostUSD += cost;
|
|
587
851
|
}
|
|
588
|
-
function
|
|
589
|
-
|
|
852
|
+
function createEmptySessionStats(turnCount) {
|
|
853
|
+
return {
|
|
590
854
|
totalInputTokens: 0,
|
|
591
855
|
totalOutputTokens: 0,
|
|
592
856
|
totalCacheCreationTokens: 0,
|
|
593
857
|
totalCacheReadTokens: 0,
|
|
594
|
-
totalCostUSD: 0,
|
|
595
858
|
toolCallCounts: {},
|
|
596
859
|
errorCount: 0,
|
|
597
860
|
totalDurationMs: 0,
|
|
598
|
-
turnCount
|
|
861
|
+
turnCount
|
|
599
862
|
};
|
|
863
|
+
}
|
|
864
|
+
function computeStats(turns) {
|
|
865
|
+
const stats = createEmptySessionStats(turns.length);
|
|
600
866
|
for (const turn of turns) {
|
|
601
|
-
if (turn.tokenUsage)
|
|
602
|
-
addUsageToStats(stats, turn.tokenUsage, estimateTotalOutputTokens(turn), calculateTurnCostEstimated(turn));
|
|
603
|
-
}
|
|
867
|
+
if (turn.tokenUsage) addUsageToStats(stats, turn.tokenUsage);
|
|
604
868
|
if (turn.durationMs) stats.totalDurationMs += turn.durationMs;
|
|
605
869
|
stats.errorCount += countToolCalls(turn.toolCalls, stats.toolCallCounts);
|
|
606
870
|
for (const sa of turn.subAgentActivity) {
|
|
607
871
|
stats.errorCount += countToolCalls(sa.toolCalls, stats.toolCallCounts);
|
|
608
|
-
if (sa.tokenUsage)
|
|
609
|
-
addUsageToStats(stats, sa.tokenUsage, estimateSubAgentOutput(sa), calculateSubAgentCostEstimated(sa));
|
|
610
|
-
}
|
|
872
|
+
if (sa.tokenUsage) addUsageToStats(stats, sa.tokenUsage);
|
|
611
873
|
}
|
|
612
874
|
}
|
|
613
875
|
return stats;
|
|
614
876
|
}
|
|
615
877
|
|
|
878
|
+
// src/lib/codex-exec.ts
|
|
879
|
+
var JS_SIMPLE_ESCAPES = {
|
|
880
|
+
"0": "\0",
|
|
881
|
+
b: "\b",
|
|
882
|
+
f: "\f",
|
|
883
|
+
n: "\n",
|
|
884
|
+
r: "\r",
|
|
885
|
+
t: " ",
|
|
886
|
+
v: "\v"
|
|
887
|
+
};
|
|
888
|
+
function readJsStringLiteral(source, startIndex) {
|
|
889
|
+
const quote = source[startIndex];
|
|
890
|
+
if (quote !== '"' && quote !== "'" && quote !== "`") return null;
|
|
891
|
+
let value = "";
|
|
892
|
+
let index = startIndex + 1;
|
|
893
|
+
while (index < source.length) {
|
|
894
|
+
const char = source[index];
|
|
895
|
+
if (char === quote) return { endIndex: index + 1, value };
|
|
896
|
+
if (char !== "\\") {
|
|
897
|
+
value += char;
|
|
898
|
+
index++;
|
|
899
|
+
continue;
|
|
900
|
+
}
|
|
901
|
+
index++;
|
|
902
|
+
if (index >= source.length) return null;
|
|
903
|
+
const escaped = source[index];
|
|
904
|
+
if (escaped in JS_SIMPLE_ESCAPES) {
|
|
905
|
+
value += JS_SIMPLE_ESCAPES[escaped];
|
|
906
|
+
index++;
|
|
907
|
+
continue;
|
|
908
|
+
}
|
|
909
|
+
if (escaped === "\n") {
|
|
910
|
+
index++;
|
|
911
|
+
continue;
|
|
912
|
+
}
|
|
913
|
+
if (escaped === "\r") {
|
|
914
|
+
index += source[index + 1] === "\n" ? 2 : 1;
|
|
915
|
+
continue;
|
|
916
|
+
}
|
|
917
|
+
if (escaped === "x" && /^[0-9a-fA-F]{2}$/.test(source.slice(index + 1, index + 3))) {
|
|
918
|
+
value += String.fromCharCode(Number.parseInt(source.slice(index + 1, index + 3), 16));
|
|
919
|
+
index += 3;
|
|
920
|
+
continue;
|
|
921
|
+
}
|
|
922
|
+
if (escaped === "u") {
|
|
923
|
+
const braced = source.slice(index + 1).match(/^\{([0-9a-fA-F]+)\}/);
|
|
924
|
+
if (braced) {
|
|
925
|
+
const codePoint = Number.parseInt(braced[1], 16);
|
|
926
|
+
if (Number.isFinite(codePoint) && codePoint <= 1114111) {
|
|
927
|
+
value += String.fromCodePoint(codePoint);
|
|
928
|
+
index += braced[0].length + 1;
|
|
929
|
+
continue;
|
|
930
|
+
}
|
|
931
|
+
}
|
|
932
|
+
const hex = source.slice(index + 1, index + 5);
|
|
933
|
+
if (/^[0-9a-fA-F]{4}$/.test(hex)) {
|
|
934
|
+
value += String.fromCharCode(Number.parseInt(hex, 16));
|
|
935
|
+
index += 5;
|
|
936
|
+
continue;
|
|
937
|
+
}
|
|
938
|
+
}
|
|
939
|
+
value += escaped;
|
|
940
|
+
index++;
|
|
941
|
+
}
|
|
942
|
+
return null;
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
// src/lib/codex-patches.ts
|
|
946
|
+
function isApplyPatchText(value) {
|
|
947
|
+
return value.includes("*** Begin Patch") && value.includes("*** End Patch");
|
|
948
|
+
}
|
|
949
|
+
function extractApplyPatchInputs(execSource) {
|
|
950
|
+
if (!execSource.includes("apply_patch") || !execSource.includes("*** Begin Patch")) return [];
|
|
951
|
+
const assignedPatches = /* @__PURE__ */ new Map();
|
|
952
|
+
for (let index = 0; index < execSource.length; index++) {
|
|
953
|
+
const literal = readJsStringLiteral(execSource, index);
|
|
954
|
+
if (!literal) continue;
|
|
955
|
+
if (isApplyPatchText(literal.value)) {
|
|
956
|
+
const assignment = execSource.slice(0, index).match(/(?:^|[;\n])\s*(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*=\s*$/);
|
|
957
|
+
if (assignment) assignedPatches.set(assignment[1], literal.value);
|
|
958
|
+
}
|
|
959
|
+
index = literal.endIndex - 1;
|
|
960
|
+
}
|
|
961
|
+
const patches = [];
|
|
962
|
+
const seen = /* @__PURE__ */ new Set();
|
|
963
|
+
const applyCall = /\btools\s*\.\s*apply_patch\s*\(/g;
|
|
964
|
+
let match;
|
|
965
|
+
while ((match = applyCall.exec(execSource)) !== null) {
|
|
966
|
+
let argumentIndex = match.index + match[0].length;
|
|
967
|
+
while (/\s/.test(execSource[argumentIndex] ?? "")) argumentIndex++;
|
|
968
|
+
const literal = readJsStringLiteral(execSource, argumentIndex);
|
|
969
|
+
const identifier = literal ? null : execSource.slice(argumentIndex).match(/^([A-Za-z_$][\w$]*)/);
|
|
970
|
+
const patch = literal?.value ?? (identifier ? assignedPatches.get(identifier[1]) : void 0);
|
|
971
|
+
if (!patch || !isApplyPatchText(patch) || seen.has(patch)) continue;
|
|
972
|
+
seen.add(patch);
|
|
973
|
+
patches.push(patch);
|
|
974
|
+
}
|
|
975
|
+
return patches;
|
|
976
|
+
}
|
|
977
|
+
function resolveCodexPatchPath(filePath, cwd) {
|
|
978
|
+
if (!cwd || filePath.startsWith("/") || /^[A-Za-z]:[\\/]/.test(filePath)) return filePath;
|
|
979
|
+
const base = cwd.replace(/[\\/]+$/, "");
|
|
980
|
+
const relative = filePath.replace(/^\.[\\/]/, "");
|
|
981
|
+
return /^[A-Za-z]:[\\/]/.test(cwd) ? `${base}\\${relative.replace(/\//g, "\\")}` : `${base}/${relative}`;
|
|
982
|
+
}
|
|
983
|
+
function parseApplyPatch(patchText, callId, timestamp, cwd = "") {
|
|
984
|
+
const toolCalls = [];
|
|
985
|
+
const filePattern = /^\*\*\*\s+(Update File|Add File|Delete File):\s*(.+)$/gm;
|
|
986
|
+
const sections = [];
|
|
987
|
+
let match;
|
|
988
|
+
while ((match = filePattern.exec(patchText)) !== null) {
|
|
989
|
+
sections.push({
|
|
990
|
+
action: match[1],
|
|
991
|
+
filePath: match[2].trim(),
|
|
992
|
+
headerIdx: match.index,
|
|
993
|
+
startIdx: match.index + match[0].length
|
|
994
|
+
});
|
|
995
|
+
}
|
|
996
|
+
for (let i = 0; i < sections.length; i++) {
|
|
997
|
+
const section = sections[i];
|
|
998
|
+
const endIdx = i + 1 < sections.length ? sections[i + 1].headerIdx : patchText.length;
|
|
999
|
+
const body = patchText.slice(section.startIdx, endIdx);
|
|
1000
|
+
const movedTo = body.match(/^\*\*\*\s+Move to:\s*(.+)$/m)?.[1]?.trim();
|
|
1001
|
+
const sourcePath = resolveCodexPatchPath(section.filePath, cwd);
|
|
1002
|
+
const filePath = resolveCodexPatchPath(movedTo || section.filePath, cwd);
|
|
1003
|
+
const hunkBodies = body.split(/^@@.*$/m).filter((s) => s.trim());
|
|
1004
|
+
const oldLines = [];
|
|
1005
|
+
const newLines = [];
|
|
1006
|
+
for (const hunk of hunkBodies) {
|
|
1007
|
+
for (const line of hunk.split("\n")) {
|
|
1008
|
+
if (line.startsWith("-")) {
|
|
1009
|
+
oldLines.push(line.slice(1));
|
|
1010
|
+
} else if (line.startsWith("+")) {
|
|
1011
|
+
newLines.push(line.slice(1));
|
|
1012
|
+
} else if (line.startsWith(" ")) {
|
|
1013
|
+
oldLines.push(line.slice(1));
|
|
1014
|
+
newLines.push(line.slice(1));
|
|
1015
|
+
}
|
|
1016
|
+
}
|
|
1017
|
+
}
|
|
1018
|
+
const fileId = sections.length > 1 ? `${callId}:file-${i}` : callId;
|
|
1019
|
+
if (section.action === "Add File") {
|
|
1020
|
+
toolCalls.push({
|
|
1021
|
+
id: fileId,
|
|
1022
|
+
name: "Write",
|
|
1023
|
+
input: { file_path: filePath, content: newLines.join("\n") },
|
|
1024
|
+
result: null,
|
|
1025
|
+
isError: false,
|
|
1026
|
+
timestamp
|
|
1027
|
+
});
|
|
1028
|
+
} else if (section.action === "Delete File") {
|
|
1029
|
+
toolCalls.push({
|
|
1030
|
+
id: fileId,
|
|
1031
|
+
name: "Edit",
|
|
1032
|
+
input: { file_path: filePath, old_string: oldLines.join("\n"), new_string: "" },
|
|
1033
|
+
result: null,
|
|
1034
|
+
isError: false,
|
|
1035
|
+
timestamp
|
|
1036
|
+
});
|
|
1037
|
+
} else {
|
|
1038
|
+
toolCalls.push({
|
|
1039
|
+
id: fileId,
|
|
1040
|
+
name: "Edit",
|
|
1041
|
+
input: {
|
|
1042
|
+
file_path: filePath,
|
|
1043
|
+
...movedTo ? { old_path: sourcePath } : {},
|
|
1044
|
+
old_string: oldLines.join("\n"),
|
|
1045
|
+
new_string: newLines.join("\n")
|
|
1046
|
+
},
|
|
1047
|
+
result: null,
|
|
1048
|
+
isError: false,
|
|
1049
|
+
timestamp
|
|
1050
|
+
});
|
|
1051
|
+
}
|
|
1052
|
+
}
|
|
1053
|
+
return toolCalls;
|
|
1054
|
+
}
|
|
1055
|
+
function parseCodexToolPatches(toolName, rawInput, callId, timestamp, cwd = "") {
|
|
1056
|
+
if (toolName === "apply_patch") {
|
|
1057
|
+
return parseApplyPatch(rawInput, callId, timestamp, cwd);
|
|
1058
|
+
}
|
|
1059
|
+
return extractApplyPatchInputs(rawInput).flatMap(
|
|
1060
|
+
(patch, patchIndex) => parseApplyPatch(patch, `${callId}:patch-${patchIndex}`, timestamp, cwd)
|
|
1061
|
+
);
|
|
1062
|
+
}
|
|
1063
|
+
function findFailedNestedPatchCallIds(output, wrapperIsError, calls) {
|
|
1064
|
+
if (!wrapperIsError || !/(?:apply[_ ]patch|invalid patch|invalid context|failed to (?:apply|find)|could not apply)/i.test(output)) {
|
|
1065
|
+
return /* @__PURE__ */ new Set();
|
|
1066
|
+
}
|
|
1067
|
+
const groups = /* @__PURE__ */ new Map();
|
|
1068
|
+
for (const call of calls) {
|
|
1069
|
+
const patchIndex = call.id.match(/:patch-(\d+)(?::file-\d+)?$/)?.[1];
|
|
1070
|
+
if (patchIndex === void 0) continue;
|
|
1071
|
+
const index = Number(patchIndex);
|
|
1072
|
+
const group = groups.get(index) ?? [];
|
|
1073
|
+
group.push(call);
|
|
1074
|
+
groups.set(index, group);
|
|
1075
|
+
}
|
|
1076
|
+
if (groups.size === 0) return new Set(calls.map((call) => call.id));
|
|
1077
|
+
const normalizedOutput = output.toLowerCase().replaceAll("\\", "/");
|
|
1078
|
+
const mentionedGroups = /* @__PURE__ */ new Set();
|
|
1079
|
+
for (const [index, group] of groups) {
|
|
1080
|
+
if (group.some((call) => {
|
|
1081
|
+
const filePath = String(call.input.file_path ?? "").toLowerCase().replaceAll("\\", "/");
|
|
1082
|
+
if (!filePath) return false;
|
|
1083
|
+
const segments = filePath.split("/").filter(Boolean);
|
|
1084
|
+
const suffix = segments.slice(-2).join("/");
|
|
1085
|
+
return normalizedOutput.includes(filePath) || suffix.includes("/") && normalizedOutput.includes(suffix);
|
|
1086
|
+
})) {
|
|
1087
|
+
mentionedGroups.add(index);
|
|
1088
|
+
}
|
|
1089
|
+
}
|
|
1090
|
+
for (const match of normalizedOutput.matchAll(/\bpatch\s*(?:#|-)?\s*(\d+)\b/g)) {
|
|
1091
|
+
const index = Number(match[1]);
|
|
1092
|
+
if (groups.has(index)) mentionedGroups.add(index);
|
|
1093
|
+
}
|
|
1094
|
+
if (mentionedGroups.size === 0) mentionedGroups.add(Math.max(...groups.keys()));
|
|
1095
|
+
const firstFailedIndex = Math.min(...mentionedGroups);
|
|
1096
|
+
for (const index of groups.keys()) {
|
|
1097
|
+
if (index >= firstFailedIndex) mentionedGroups.add(index);
|
|
1098
|
+
}
|
|
1099
|
+
return new Set(
|
|
1100
|
+
[...mentionedGroups].flatMap((index) => groups.get(index)?.map((call) => call.id) ?? [])
|
|
1101
|
+
);
|
|
1102
|
+
}
|
|
1103
|
+
|
|
1104
|
+
// src/lib/codex-tool-normalization.ts
|
|
1105
|
+
function isObject(value) {
|
|
1106
|
+
return typeof value === "object" && value !== null;
|
|
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
|
+
}
|
|
1126
|
+
function inferToolError(output) {
|
|
1127
|
+
if (!output) return false;
|
|
1128
|
+
const exitMatch = output.match(/Process exited with code (\d+)/);
|
|
1129
|
+
if (exitMatch) return exitMatch[1] !== "0";
|
|
1130
|
+
const withoutSuccessSummaries = output.replace(/\b0\s+(?:fail(?:ed|ures?)?|errors?)\b/gi, "").replace(/\bno\s+(?:failures?|errors?)\b/gi, "");
|
|
1131
|
+
return /\b(error|failed|exception)\b/i.test(withoutSuccessSummaries);
|
|
1132
|
+
}
|
|
1133
|
+
function normalizePlanToTodos(input) {
|
|
1134
|
+
const plan = Array.isArray(input.plan) ? input.plan : [];
|
|
1135
|
+
const todos = plan.filter((item) => isObject(item)).map((item) => ({
|
|
1136
|
+
content: typeof item.step === "string" ? item.step : "",
|
|
1137
|
+
status: typeof item.status === "string" ? item.status : "pending",
|
|
1138
|
+
activeForm: typeof item.step === "string" ? item.step : ""
|
|
1139
|
+
}));
|
|
1140
|
+
return { todos };
|
|
1141
|
+
}
|
|
1142
|
+
function extractCustomToolOutputText(output) {
|
|
1143
|
+
if (typeof output === "string") return output;
|
|
1144
|
+
if (!Array.isArray(output)) return "";
|
|
1145
|
+
return output.filter((block) => isObject(block) && typeof block.text === "string").map((block) => block.text).join("");
|
|
1146
|
+
}
|
|
1147
|
+
function parseCustomToolOutput(output) {
|
|
1148
|
+
const rawOutput = extractCustomToolOutputText(output);
|
|
1149
|
+
if (!rawOutput) return { text: "", isError: false };
|
|
1150
|
+
try {
|
|
1151
|
+
const parsed = JSON.parse(rawOutput);
|
|
1152
|
+
const text = typeof parsed.output === "string" ? parsed.output : rawOutput;
|
|
1153
|
+
const meta = isObject(parsed.metadata) ? parsed.metadata : null;
|
|
1154
|
+
const isError = meta ? meta.exit_code !== 0 && meta.exit_code !== void 0 : inferToolError(text);
|
|
1155
|
+
return { text, isError };
|
|
1156
|
+
} catch {
|
|
1157
|
+
return { text: rawOutput, isError: inferToolError(rawOutput) };
|
|
1158
|
+
}
|
|
1159
|
+
}
|
|
1160
|
+
|
|
616
1161
|
// src/lib/codex.ts
|
|
617
1162
|
var SKIP_PROMPT_PREFIXES = [
|
|
1163
|
+
"# AGENTS.md instructions for ",
|
|
1164
|
+
"<recommended_plugins>",
|
|
618
1165
|
"<environment_context>",
|
|
619
1166
|
"<permissions instructions>",
|
|
620
1167
|
"<collaboration_mode>",
|
|
@@ -630,17 +1177,98 @@ function safeParseLine(line) {
|
|
|
630
1177
|
return null;
|
|
631
1178
|
}
|
|
632
1179
|
}
|
|
633
|
-
function
|
|
1180
|
+
function isObject2(value) {
|
|
634
1181
|
return typeof value === "object" && value !== null;
|
|
635
1182
|
}
|
|
636
1183
|
function isCodexRecord(record) {
|
|
637
1184
|
if (!record || typeof record.type !== "string") return false;
|
|
638
|
-
return record.type === "session_meta" || record.type === "turn_context" || record.type === "event_msg" || record.type === "response_item";
|
|
1185
|
+
return record.type === "session_meta" || record.type === "turn_context" || record.type === "event_msg" || record.type === "response_item" || record.type === "inter_agent_communication_metadata" || record.type === "compacted" || record.type === "world_state";
|
|
639
1186
|
}
|
|
640
1187
|
function extractMessageText(payload, blockType) {
|
|
641
1188
|
const content = payload?.content;
|
|
642
1189
|
if (!Array.isArray(content)) return "";
|
|
643
|
-
return content.filter((block) =>
|
|
1190
|
+
return content.filter((block) => isObject2(block) && block.type === blockType && typeof block.text === "string").map((block) => block.text).join("\n").trim();
|
|
1191
|
+
}
|
|
1192
|
+
var CODEX_IMAGE_DATA_URL = /^data:(image\/(?:png|jpeg|gif|webp));base64,(.+)$/is;
|
|
1193
|
+
var CODEX_AUDIO_DATA_URL = /^data:(audio\/(?:wav|x-wav|mpeg|mp4|webm|ogg));base64,(.+)$/is;
|
|
1194
|
+
function parseImageDataUrl(value) {
|
|
1195
|
+
if (typeof value !== "string") return null;
|
|
1196
|
+
const match = CODEX_IMAGE_DATA_URL.exec(value);
|
|
1197
|
+
if (!match?.[1] || !match[2]) return null;
|
|
1198
|
+
return {
|
|
1199
|
+
type: "image",
|
|
1200
|
+
source: {
|
|
1201
|
+
type: "base64",
|
|
1202
|
+
media_type: match[1].toLowerCase(),
|
|
1203
|
+
data: match[2]
|
|
1204
|
+
}
|
|
1205
|
+
};
|
|
1206
|
+
}
|
|
1207
|
+
function parseAudioDataUrl(value) {
|
|
1208
|
+
if (typeof value !== "string") return null;
|
|
1209
|
+
const match = CODEX_AUDIO_DATA_URL.exec(value);
|
|
1210
|
+
if (!match?.[1] || !match[2]) return null;
|
|
1211
|
+
return {
|
|
1212
|
+
type: "audio",
|
|
1213
|
+
source: {
|
|
1214
|
+
type: "base64",
|
|
1215
|
+
media_type: match[1].toLowerCase(),
|
|
1216
|
+
data: match[2]
|
|
1217
|
+
}
|
|
1218
|
+
};
|
|
1219
|
+
}
|
|
1220
|
+
function extractEventMessageImages(payload) {
|
|
1221
|
+
if (!Array.isArray(payload.images)) return [];
|
|
1222
|
+
return payload.images.map(parseImageDataUrl).filter((image) => image !== null);
|
|
1223
|
+
}
|
|
1224
|
+
function extractEventMessageAudio(payload) {
|
|
1225
|
+
if (!Array.isArray(payload.audio)) return [];
|
|
1226
|
+
return payload.audio.map(parseAudioDataUrl).filter((audio) => audio !== null);
|
|
1227
|
+
}
|
|
1228
|
+
function extractResponseMessageImages(payload) {
|
|
1229
|
+
if (!Array.isArray(payload.content)) return [];
|
|
1230
|
+
return payload.content.filter((block) => isObject2(block) && block.type === "input_image").map((block) => parseImageDataUrl(block.image_url)).filter((image) => image !== null);
|
|
1231
|
+
}
|
|
1232
|
+
function extractResponseMessageAudio(payload) {
|
|
1233
|
+
if (!Array.isArray(payload.content)) return [];
|
|
1234
|
+
return payload.content.filter((block) => isObject2(block) && block.type === "input_audio").map((block) => parseAudioDataUrl(block.audio_url)).filter((audio) => audio !== null);
|
|
1235
|
+
}
|
|
1236
|
+
function buildCodexUserContent(message, images, audio = [], localImages = [], localAudio = []) {
|
|
1237
|
+
const attachmentSuffix = [
|
|
1238
|
+
...localImages.map((path) => `
|
|
1239
|
+
`),
|
|
1240
|
+
...localAudio.map((path) => `
|
|
1241
|
+
[audio attachment](<${path}>)`)
|
|
1242
|
+
].join("");
|
|
1243
|
+
const text = message + attachmentSuffix;
|
|
1244
|
+
if (images.length === 0 && audio.length === 0) return text;
|
|
1245
|
+
return [
|
|
1246
|
+
...images,
|
|
1247
|
+
...audio,
|
|
1248
|
+
...text ? [{ type: "text", text }] : []
|
|
1249
|
+
];
|
|
1250
|
+
}
|
|
1251
|
+
function extractInterAgentMessage(payload) {
|
|
1252
|
+
const rawText = extractMessageText(payload, "input_text");
|
|
1253
|
+
if (!rawText) return { messageType: null, text: "" };
|
|
1254
|
+
const headerEnd = rawText.match(/\r?\nPayload:\s*(?:\r?\n|$)/);
|
|
1255
|
+
if (!rawText.startsWith("Message Type:") || !headerEnd || headerEnd.index === void 0) {
|
|
1256
|
+
return { messageType: null, text: rawText.trim() };
|
|
1257
|
+
}
|
|
1258
|
+
const header = rawText.slice(0, headerEnd.index);
|
|
1259
|
+
const messageType = header.match(/^Message Type:\s*([^\r\n]+)/)?.[1]?.trim().toUpperCase() ?? null;
|
|
1260
|
+
const text = rawText.slice(headerEnd.index + headerEnd[0].length).trim();
|
|
1261
|
+
return { messageType, text };
|
|
1262
|
+
}
|
|
1263
|
+
function agentNameFromPath(agentPath) {
|
|
1264
|
+
const segments = agentPath.split(/[\\/]/).filter(Boolean);
|
|
1265
|
+
return segments.at(-1) ?? null;
|
|
1266
|
+
}
|
|
1267
|
+
function readableAgentPrompt(value) {
|
|
1268
|
+
if (typeof value !== "string") return "";
|
|
1269
|
+
const prompt = value.trim();
|
|
1270
|
+
if (/^gAAAAA[A-Za-z0-9_-]+$/.test(prompt)) return "";
|
|
1271
|
+
return prompt;
|
|
644
1272
|
}
|
|
645
1273
|
function normalizePromptText(text) {
|
|
646
1274
|
const trimmed = text.trim();
|
|
@@ -658,11 +1286,13 @@ function mergeTokenUsage2(existing, incoming) {
|
|
|
658
1286
|
};
|
|
659
1287
|
}
|
|
660
1288
|
function parseTokenUsage(value) {
|
|
661
|
-
if (!
|
|
662
|
-
const
|
|
1289
|
+
if (!isObject2(value)) return null;
|
|
1290
|
+
const reportedInputTokens = typeof value.input_tokens === "number" ? value.input_tokens : 0;
|
|
663
1291
|
const outputTokens = typeof value.output_tokens === "number" ? value.output_tokens : 0;
|
|
664
|
-
const cacheCreation = typeof value.cache_creation_input_tokens === "number" ? value.cache_creation_input_tokens : 0;
|
|
665
|
-
const
|
|
1292
|
+
const cacheCreation = typeof value.cache_write_input_tokens === "number" ? value.cache_write_input_tokens : typeof value.cache_creation_input_tokens === "number" ? value.cache_creation_input_tokens : 0;
|
|
1293
|
+
const nativeCachedInput = typeof value.cached_input_tokens === "number" ? Math.max(0, value.cached_input_tokens) : null;
|
|
1294
|
+
const cacheRead = nativeCachedInput ?? (typeof value.cache_read_input_tokens === "number" ? value.cache_read_input_tokens : 0);
|
|
1295
|
+
const inputTokens = nativeCachedInput === null ? reportedInputTokens : Math.max(0, reportedInputTokens - nativeCachedInput);
|
|
666
1296
|
if (inputTokens === 0 && outputTokens === 0 && cacheCreation === 0 && cacheRead === 0) return null;
|
|
667
1297
|
return {
|
|
668
1298
|
input_tokens: inputTokens,
|
|
@@ -703,7 +1333,7 @@ function appendToolCall(turn, toolCall, timestamp) {
|
|
|
703
1333
|
}
|
|
704
1334
|
function finalizeTurn(turns, current, lastTimestamp) {
|
|
705
1335
|
if (!current) return null;
|
|
706
|
-
const hasContent = current.userMessage !== null || current.assistantText.length > 0 || current.toolCalls.length > 0 || current.thinking.length > 0;
|
|
1336
|
+
const hasContent = current.userMessage !== null || current.assistantText.length > 0 || current.toolCalls.length > 0 || current.thinking.length > 0 || current.subAgentActivity.length > 0;
|
|
707
1337
|
if (!hasContent) return null;
|
|
708
1338
|
if (current.timestamp && lastTimestamp) {
|
|
709
1339
|
const start = new Date(current.timestamp).getTime();
|
|
@@ -719,20 +1349,40 @@ function parseToolInput(argumentsText) {
|
|
|
719
1349
|
if (typeof argumentsText !== "string" || !argumentsText.trim()) return {};
|
|
720
1350
|
try {
|
|
721
1351
|
const parsed = JSON.parse(argumentsText);
|
|
722
|
-
return
|
|
1352
|
+
return isObject2(parsed) ? parsed : { value: parsed };
|
|
723
1353
|
} catch {
|
|
724
1354
|
return { raw: argumentsText };
|
|
725
1355
|
}
|
|
726
1356
|
}
|
|
727
|
-
function
|
|
728
|
-
if (
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
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
|
+
};
|
|
732
1382
|
}
|
|
733
|
-
function
|
|
1383
|
+
function createTurn2(turnId, timestamp, model) {
|
|
734
1384
|
return {
|
|
735
|
-
id: turnId
|
|
1385
|
+
id: turnId ? `${turnId}@${timestamp}` : randomTurnId("codex-turn"),
|
|
736
1386
|
userMessage: null,
|
|
737
1387
|
contentBlocks: [],
|
|
738
1388
|
thinking: [],
|
|
@@ -746,14 +1396,25 @@ function createTurn(turnId, timestamp, model) {
|
|
|
746
1396
|
};
|
|
747
1397
|
}
|
|
748
1398
|
function extractPromptFromRecord(record) {
|
|
749
|
-
if (record.type === "event_msg" &&
|
|
1399
|
+
if (record.type === "event_msg" && isObject2(record.payload) && record.payload.type === "user_message" && typeof record.payload.message === "string") {
|
|
750
1400
|
return normalizePromptText(record.payload.message);
|
|
751
1401
|
}
|
|
752
|
-
if (record.type === "response_item" &&
|
|
1402
|
+
if (record.type === "response_item" && isObject2(record.payload) && record.payload.type === "message" && record.payload.role === "user") {
|
|
753
1403
|
return normalizePromptText(extractMessageText(record.payload, "input_text"));
|
|
754
1404
|
}
|
|
755
1405
|
return "";
|
|
756
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
|
+
}
|
|
757
1418
|
function extractMetadataFromRecords(records) {
|
|
758
1419
|
let sessionId = "";
|
|
759
1420
|
let version = "";
|
|
@@ -766,14 +1427,17 @@ function extractMetadataFromRecords(records) {
|
|
|
766
1427
|
let timestamp = "";
|
|
767
1428
|
let lastTimestamp = "";
|
|
768
1429
|
let turnCount = 0;
|
|
1430
|
+
let isSubagent = false;
|
|
1431
|
+
let parentSessionId = null;
|
|
1432
|
+
let agentPath = "/root";
|
|
769
1433
|
let previousPrompt = "";
|
|
770
1434
|
for (const record of records) {
|
|
771
|
-
if (!
|
|
1435
|
+
if (!isObject2(record.payload)) continue;
|
|
772
1436
|
if (record.type === "session_meta") {
|
|
773
1437
|
sessionId ||= typeof record.payload.id === "string" ? record.payload.id : "";
|
|
774
1438
|
version ||= typeof record.payload.cli_version === "string" ? record.payload.cli_version : "";
|
|
775
1439
|
cwd ||= typeof record.payload.cwd === "string" ? record.payload.cwd : "";
|
|
776
|
-
if (!branchedFrom &&
|
|
1440
|
+
if (!branchedFrom && isObject2(record.payload.branchedFrom)) {
|
|
777
1441
|
const sourceId = typeof record.payload.branchedFrom.sessionId === "string" ? record.payload.branchedFrom.sessionId : "";
|
|
778
1442
|
if (sourceId) {
|
|
779
1443
|
branchedFrom = {
|
|
@@ -782,7 +1446,18 @@ function extractMetadataFromRecords(records) {
|
|
|
782
1446
|
};
|
|
783
1447
|
}
|
|
784
1448
|
}
|
|
785
|
-
const
|
|
1449
|
+
const source = isObject2(record.payload.source) ? record.payload.source : null;
|
|
1450
|
+
if (source && isObject2(source.subagent)) {
|
|
1451
|
+
isSubagent = true;
|
|
1452
|
+
const threadSpawn = isObject2(source.subagent.thread_spawn) ? source.subagent.thread_spawn : null;
|
|
1453
|
+
if (threadSpawn && typeof threadSpawn.agent_path === "string") {
|
|
1454
|
+
agentPath = threadSpawn.agent_path;
|
|
1455
|
+
}
|
|
1456
|
+
}
|
|
1457
|
+
if (typeof record.payload.forked_from_id === "string" && record.payload.forked_from_id) {
|
|
1458
|
+
parentSessionId = record.payload.forked_from_id;
|
|
1459
|
+
}
|
|
1460
|
+
const git = isObject2(record.payload.git) ? record.payload.git : null;
|
|
786
1461
|
gitBranch ||= git && typeof git.branch === "string" ? git.branch : "";
|
|
787
1462
|
}
|
|
788
1463
|
if (record.type === "turn_context") {
|
|
@@ -807,12 +1482,16 @@ function extractMetadataFromRecords(records) {
|
|
|
807
1482
|
cwd,
|
|
808
1483
|
model,
|
|
809
1484
|
slug: "",
|
|
1485
|
+
name: "",
|
|
810
1486
|
branchedFrom,
|
|
811
1487
|
firstUserMessage,
|
|
812
1488
|
lastUserMessage,
|
|
813
1489
|
timestamp,
|
|
814
1490
|
lastTimestamp,
|
|
815
|
-
turnCount
|
|
1491
|
+
turnCount,
|
|
1492
|
+
isSubagent,
|
|
1493
|
+
parentSessionId,
|
|
1494
|
+
agentPath
|
|
816
1495
|
};
|
|
817
1496
|
}
|
|
818
1497
|
function isCodexSessionText(jsonlText) {
|
|
@@ -827,47 +1506,229 @@ function extractCodexMetadataFromLines(lines) {
|
|
|
827
1506
|
const records = lines.map(safeParseLine).filter(isCodexRecord);
|
|
828
1507
|
return extractMetadataFromRecords(records);
|
|
829
1508
|
}
|
|
830
|
-
function parseCodexSession(jsonlText) {
|
|
1509
|
+
function parseCodexSession(jsonlText, options) {
|
|
831
1510
|
const records = jsonlText.split("\n").map((line) => line.trim()).filter(Boolean).map(safeParseLine).filter(isCodexRecord);
|
|
832
1511
|
const metadata = extractMetadataFromRecords(records);
|
|
833
1512
|
const turns = [];
|
|
834
1513
|
const pendingToolCalls = /* @__PURE__ */ new Map();
|
|
1514
|
+
const webSearchCalls = /* @__PURE__ */ new Map();
|
|
1515
|
+
const patchCallIds = /* @__PURE__ */ new Map();
|
|
1516
|
+
const spawnAgentCalls = /* @__PURE__ */ new Map();
|
|
1517
|
+
const agentRegistry = /* @__PURE__ */ new Map();
|
|
1518
|
+
const agentIdByPath = /* @__PURE__ */ new Map();
|
|
1519
|
+
const agentIdBySpawnCall = /* @__PURE__ */ new Map();
|
|
1520
|
+
const agentMessages = /* @__PURE__ */ new Map();
|
|
1521
|
+
function upsertAgentMessage(agentId, targetTurn, eventTimestamp, update = {}) {
|
|
1522
|
+
const info = agentRegistry.get(agentId);
|
|
1523
|
+
let entry = agentMessages.get(agentId);
|
|
1524
|
+
if (!entry) {
|
|
1525
|
+
const agentMessage2 = {
|
|
1526
|
+
agentId,
|
|
1527
|
+
parentToolUseId: info?.parentToolCallId || void 0,
|
|
1528
|
+
agentName: info?.nickname ?? (info?.agentPath ? agentNameFromPath(info.agentPath) : null),
|
|
1529
|
+
subagentType: info?.agentType ?? null,
|
|
1530
|
+
type: "assistant",
|
|
1531
|
+
content: null,
|
|
1532
|
+
toolCalls: [],
|
|
1533
|
+
thinking: [],
|
|
1534
|
+
text: [],
|
|
1535
|
+
timestamp: info?.timestamp ?? eventTimestamp,
|
|
1536
|
+
tokenUsage: null,
|
|
1537
|
+
model: info?.model ?? null,
|
|
1538
|
+
isBackground: false,
|
|
1539
|
+
prompt: info?.message || void 0,
|
|
1540
|
+
status: update.status ?? "running"
|
|
1541
|
+
};
|
|
1542
|
+
targetTurn.subAgentActivity.push(agentMessage2);
|
|
1543
|
+
const lastBlock = targetTurn.contentBlocks[targetTurn.contentBlocks.length - 1];
|
|
1544
|
+
if (lastBlock?.kind === "sub_agent") {
|
|
1545
|
+
lastBlock.messages.push(agentMessage2);
|
|
1546
|
+
} else {
|
|
1547
|
+
targetTurn.contentBlocks.push({ kind: "sub_agent", messages: [agentMessage2], timestamp: eventTimestamp });
|
|
1548
|
+
}
|
|
1549
|
+
entry = { message: agentMessage2, turn: targetTurn };
|
|
1550
|
+
agentMessages.set(agentId, entry);
|
|
1551
|
+
}
|
|
1552
|
+
const agentMessage = entry.message;
|
|
1553
|
+
if (info) {
|
|
1554
|
+
agentMessage.parentToolUseId = info.parentToolCallId || agentMessage.parentToolUseId;
|
|
1555
|
+
agentMessage.agentName = info.nickname ?? (info.agentPath ? agentNameFromPath(info.agentPath) : agentMessage.agentName);
|
|
1556
|
+
agentMessage.subagentType = info.agentType;
|
|
1557
|
+
agentMessage.model = info.model;
|
|
1558
|
+
agentMessage.prompt = info.message || agentMessage.prompt;
|
|
1559
|
+
}
|
|
1560
|
+
if (update.text && !agentMessage.text.includes(update.text)) {
|
|
1561
|
+
agentMessage.text.push(update.text);
|
|
1562
|
+
agentMessage.content = agentMessage.text.join("\n\n");
|
|
1563
|
+
}
|
|
1564
|
+
if (update.status) {
|
|
1565
|
+
const terminalStatuses = /* @__PURE__ */ new Set(["completed", "failed", "interrupted"]);
|
|
1566
|
+
if (!(update.status === "running" && agentMessage.status && terminalStatuses.has(agentMessage.status))) {
|
|
1567
|
+
agentMessage.status = update.status;
|
|
1568
|
+
}
|
|
1569
|
+
if (terminalStatuses.has(update.status) && agentMessage.timestamp && eventTimestamp) {
|
|
1570
|
+
const startedAt = new Date(agentMessage.timestamp).getTime();
|
|
1571
|
+
const endedAt = new Date(eventTimestamp).getTime();
|
|
1572
|
+
if (Number.isFinite(startedAt) && Number.isFinite(endedAt) && endedAt >= startedAt) {
|
|
1573
|
+
agentMessage.durationMs = endedAt - startedAt;
|
|
1574
|
+
}
|
|
1575
|
+
}
|
|
1576
|
+
}
|
|
1577
|
+
return agentMessage;
|
|
1578
|
+
}
|
|
835
1579
|
let current = null;
|
|
836
1580
|
let currentTurnId = null;
|
|
837
1581
|
let currentModel = metadata.model || null;
|
|
838
1582
|
let lastTurnTimestamp = "";
|
|
1583
|
+
let pendingCompaction = null;
|
|
1584
|
+
let sawTurnStart = false;
|
|
1585
|
+
function createActiveTurn(turnId, timestamp, model) {
|
|
1586
|
+
const turn = createTurn2(turnId, timestamp, model);
|
|
1587
|
+
if (!sawTurnStart) turn.isFragment = true;
|
|
1588
|
+
if (pendingCompaction) {
|
|
1589
|
+
turn.compactionSummary = pendingCompaction;
|
|
1590
|
+
pendingCompaction = null;
|
|
1591
|
+
}
|
|
1592
|
+
return turn;
|
|
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
|
+
}
|
|
839
1613
|
for (const record of records) {
|
|
840
|
-
const payload =
|
|
1614
|
+
const payload = isObject2(record.payload) ? record.payload : void 0;
|
|
841
1615
|
const timestamp = record.timestamp ?? "";
|
|
1616
|
+
if (record.type === "compacted") {
|
|
1617
|
+
current = finalizeTurn(turns, current, lastTurnTimestamp);
|
|
1618
|
+
pendingCompaction = "Conversation compacted";
|
|
1619
|
+
lastTurnTimestamp = timestamp;
|
|
1620
|
+
continue;
|
|
1621
|
+
}
|
|
1622
|
+
if (record.type === "world_state") continue;
|
|
842
1623
|
if (record.type === "turn_context") {
|
|
843
1624
|
current = finalizeTurn(turns, current, lastTurnTimestamp);
|
|
844
1625
|
currentTurnId = typeof payload?.turn_id === "string" ? payload.turn_id : null;
|
|
845
1626
|
currentModel = typeof payload?.model === "string" ? payload.model : currentModel;
|
|
846
1627
|
lastTurnTimestamp = timestamp;
|
|
1628
|
+
sawTurnStart = true;
|
|
847
1629
|
continue;
|
|
848
1630
|
}
|
|
849
1631
|
if (record.type === "event_msg" && payload?.type === "user_message" && typeof payload.message === "string") {
|
|
850
1632
|
if (current && (current.assistantText.length > 0 || current.toolCalls.length > 0 || current.thinking.length > 0)) {
|
|
851
1633
|
current = finalizeTurn(turns, current, lastTurnTimestamp);
|
|
852
1634
|
}
|
|
853
|
-
|
|
854
|
-
current
|
|
1635
|
+
sawTurnStart = true;
|
|
1636
|
+
current ??= createActiveTurn(currentTurnId, timestamp, currentModel);
|
|
1637
|
+
const localImages = (Array.isArray(payload.local_images) ? payload.local_images : []).filter(
|
|
1638
|
+
(p) => typeof p === "string" && p.length > 0
|
|
1639
|
+
);
|
|
1640
|
+
const localAudio = (Array.isArray(payload.local_audio) ? payload.local_audio : []).filter(
|
|
1641
|
+
(p) => typeof p === "string" && p.length > 0
|
|
1642
|
+
);
|
|
1643
|
+
current.userMessage = buildCodexUserContent(
|
|
1644
|
+
payload.message,
|
|
1645
|
+
extractEventMessageImages(payload),
|
|
1646
|
+
extractEventMessageAudio(payload),
|
|
1647
|
+
localImages,
|
|
1648
|
+
localAudio
|
|
1649
|
+
);
|
|
1650
|
+
current.isFragment = false;
|
|
855
1651
|
current.timestamp = current.timestamp || timestamp;
|
|
856
1652
|
lastTurnTimestamp = timestamp;
|
|
857
1653
|
continue;
|
|
858
1654
|
}
|
|
859
|
-
current
|
|
1655
|
+
if (!current && !recordOpensTurn(record, payload)) continue;
|
|
1656
|
+
current ??= createActiveTurn(currentTurnId, timestamp, currentModel);
|
|
860
1657
|
if (!current.model && currentModel) current.model = currentModel;
|
|
861
1658
|
if (!current.timestamp) current.timestamp = timestamp;
|
|
862
1659
|
if (timestamp) lastTurnTimestamp = timestamp;
|
|
863
1660
|
if (record.type === "event_msg" && payload?.type === "token_count") {
|
|
864
|
-
const info =
|
|
1661
|
+
const info = isObject2(payload.info) ? payload.info : null;
|
|
865
1662
|
const lastUsage = info ? parseTokenUsage(info.last_token_usage) : null;
|
|
866
1663
|
if (lastUsage) {
|
|
867
1664
|
current.tokenUsage = mergeTokenUsage2(current.tokenUsage, lastUsage);
|
|
868
1665
|
}
|
|
869
1666
|
continue;
|
|
870
1667
|
}
|
|
1668
|
+
if (record.type === "event_msg" && payload?.type === "web_search_end" && typeof payload.call_id === "string") {
|
|
1669
|
+
const input = {};
|
|
1670
|
+
if (typeof payload.query === "string" && payload.query) input.query = payload.query;
|
|
1671
|
+
if (isObject2(payload.action)) input.action = payload.action;
|
|
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);
|
|
1682
|
+
if (existing) {
|
|
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, {
|
|
1688
|
+
id: payload.call_id,
|
|
1689
|
+
name: `mcp__${server}__${tool}`,
|
|
1690
|
+
input,
|
|
1691
|
+
result: result.text,
|
|
1692
|
+
isError: result.isError,
|
|
1693
|
+
timestamp
|
|
1694
|
+
}, timestamp);
|
|
1695
|
+
}
|
|
1696
|
+
continue;
|
|
1697
|
+
}
|
|
1698
|
+
if (record.type === "event_msg" && payload?.type === "sub_agent_activity" && typeof payload.agent_thread_id === "string" && typeof payload.agent_path === "string") {
|
|
1699
|
+
const agentId = payload.agent_thread_id;
|
|
1700
|
+
const eventId = typeof payload.event_id === "string" ? payload.event_id : "";
|
|
1701
|
+
const kind = typeof payload.kind === "string" ? payload.kind : "started";
|
|
1702
|
+
const spawnInfo = eventId ? spawnAgentCalls.get(eventId) : void 0;
|
|
1703
|
+
const existingId = agentIdByPath.get(payload.agent_path);
|
|
1704
|
+
const existing = agentRegistry.get(agentId) ?? (existingId ? agentRegistry.get(existingId) : void 0);
|
|
1705
|
+
if (kind !== "started" && !existing && !spawnInfo) continue;
|
|
1706
|
+
if (existingId && existingId !== agentId && !agentMessages.has(agentId)) {
|
|
1707
|
+
const provisionalEntry = agentMessages.get(existingId);
|
|
1708
|
+
if (provisionalEntry) {
|
|
1709
|
+
provisionalEntry.message.agentId = agentId;
|
|
1710
|
+
agentMessages.delete(existingId);
|
|
1711
|
+
agentMessages.set(agentId, provisionalEntry);
|
|
1712
|
+
}
|
|
1713
|
+
agentRegistry.delete(existingId);
|
|
1714
|
+
}
|
|
1715
|
+
const info = {
|
|
1716
|
+
agentId,
|
|
1717
|
+
nickname: existing?.nickname ?? agentNameFromPath(payload.agent_path) ?? spawnInfo?.taskName ?? null,
|
|
1718
|
+
agentPath: payload.agent_path,
|
|
1719
|
+
message: existing?.message ?? spawnInfo?.message ?? "",
|
|
1720
|
+
model: existing?.model ?? spawnInfo?.model ?? null,
|
|
1721
|
+
agentType: existing?.agentType ?? spawnInfo?.agentType ?? null,
|
|
1722
|
+
timestamp: existing?.timestamp ?? (timestamp || spawnInfo?.timestamp || ""),
|
|
1723
|
+
parentToolCallId: existing?.parentToolCallId ?? eventId
|
|
1724
|
+
};
|
|
1725
|
+
agentRegistry.set(agentId, info);
|
|
1726
|
+
agentIdByPath.set(payload.agent_path, agentId);
|
|
1727
|
+
if (eventId && (kind === "started" || spawnInfo)) agentIdBySpawnCall.set(eventId, agentId);
|
|
1728
|
+
const status = kind === "interrupted" ? "interrupted" : "running";
|
|
1729
|
+
upsertAgentMessage(agentId, current, timestamp, { status });
|
|
1730
|
+
continue;
|
|
1731
|
+
}
|
|
871
1732
|
if (record.type !== "response_item" || !payload) continue;
|
|
872
1733
|
if (payload.type === "reasoning") {
|
|
873
1734
|
const summary = Array.isArray(payload.summary) ? payload.summary.filter((item) => typeof item === "string").join("\n") : "";
|
|
@@ -880,14 +1741,110 @@ function parseCodexSession(jsonlText) {
|
|
|
880
1741
|
}
|
|
881
1742
|
if (payload.type === "message" && payload.role === "user" && current.userMessage === null) {
|
|
882
1743
|
const text = normalizePromptText(extractMessageText(payload, "input_text"));
|
|
883
|
-
|
|
1744
|
+
const images = extractResponseMessageImages(payload);
|
|
1745
|
+
const audio = extractResponseMessageAudio(payload);
|
|
1746
|
+
if (text || images.length > 0 || audio.length > 0) {
|
|
1747
|
+
current.userMessage = buildCodexUserContent(text, images, audio);
|
|
1748
|
+
current.isFragment = false;
|
|
1749
|
+
sawTurnStart = true;
|
|
1750
|
+
}
|
|
1751
|
+
continue;
|
|
1752
|
+
}
|
|
1753
|
+
if (payload.type === "web_search_call" && typeof payload.id === "string") {
|
|
1754
|
+
const input = {};
|
|
1755
|
+
if (isObject2(payload.action)) input.action = payload.action;
|
|
1756
|
+
const status = typeof payload.status === "string" ? payload.status : "completed";
|
|
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) {
|
|
1763
|
+
const toolCall = {
|
|
1764
|
+
id: callId,
|
|
1765
|
+
name: "ToolSearch",
|
|
1766
|
+
input: isObject2(payload.arguments) ? payload.arguments : {},
|
|
1767
|
+
result: null,
|
|
1768
|
+
isError: false,
|
|
1769
|
+
timestamp
|
|
1770
|
+
};
|
|
1771
|
+
pendingToolCalls.set(callId, toolCall);
|
|
1772
|
+
appendToolCall(current, toolCall, timestamp);
|
|
1773
|
+
}
|
|
1774
|
+
continue;
|
|
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
|
+
}
|
|
1789
|
+
if (payload.type === "agent_message" && typeof payload.author === "string") {
|
|
1790
|
+
const author = payload.author;
|
|
1791
|
+
const recipient = typeof payload.recipient === "string" ? payload.recipient : null;
|
|
1792
|
+
const interAgentMessage = extractInterAgentMessage(payload);
|
|
1793
|
+
if (interAgentMessage.messageType === "NEW_TASK") continue;
|
|
1794
|
+
const knownAgentId = agentIdByPath.get(author);
|
|
1795
|
+
const isPotentialChild = author.startsWith(`${metadata.agentPath}/`) && (recipient === null || recipient === metadata.agentPath);
|
|
1796
|
+
if (!knownAgentId && !isPotentialChild) continue;
|
|
1797
|
+
const agentId = knownAgentId ?? author;
|
|
1798
|
+
if (!knownAgentId) {
|
|
1799
|
+
agentRegistry.set(agentId, {
|
|
1800
|
+
agentId,
|
|
1801
|
+
nickname: agentNameFromPath(author),
|
|
1802
|
+
agentPath: author,
|
|
1803
|
+
message: "",
|
|
1804
|
+
model: null,
|
|
1805
|
+
agentType: null,
|
|
1806
|
+
timestamp,
|
|
1807
|
+
parentToolCallId: ""
|
|
1808
|
+
});
|
|
1809
|
+
agentIdByPath.set(author, agentId);
|
|
1810
|
+
}
|
|
1811
|
+
let status;
|
|
1812
|
+
if (interAgentMessage.messageType === "FINAL_ANSWER") status = "completed";
|
|
1813
|
+
else if (interAgentMessage.messageType === "ERROR" || interAgentMessage.messageType === "FAILED") status = "failed";
|
|
1814
|
+
else if (interAgentMessage.messageType === "INTERRUPTED") status = "interrupted";
|
|
1815
|
+
else if (interAgentMessage.messageType === "MESSAGE") status = "running";
|
|
1816
|
+
upsertAgentMessage(agentId, current, timestamp, {
|
|
1817
|
+
status,
|
|
1818
|
+
text: interAgentMessage.text || void 0
|
|
1819
|
+
});
|
|
884
1820
|
continue;
|
|
885
1821
|
}
|
|
886
1822
|
if (payload.type === "function_call" && typeof payload.call_id === "string") {
|
|
1823
|
+
const rawName = typeof payload.name === "string" ? payload.name : "tool";
|
|
1824
|
+
const functionName = normalizeFunctionName(rawName);
|
|
1825
|
+
const parsedInput = parseToolInput(payload.arguments);
|
|
1826
|
+
let name = functionName;
|
|
1827
|
+
let input = parsedInput;
|
|
1828
|
+
if (functionName === "exec_command") {
|
|
1829
|
+
name = "Bash";
|
|
1830
|
+
} else if (functionName === "update_plan") {
|
|
1831
|
+
name = "TodoWrite";
|
|
1832
|
+
input = normalizePlanToTodos(parsedInput);
|
|
1833
|
+
}
|
|
1834
|
+
if (functionName === "spawn_agent") {
|
|
1835
|
+
spawnAgentCalls.set(payload.call_id, {
|
|
1836
|
+
callId: payload.call_id,
|
|
1837
|
+
message: readableAgentPrompt(parsedInput.message),
|
|
1838
|
+
taskName: typeof parsedInput.task_name === "string" ? parsedInput.task_name : null,
|
|
1839
|
+
model: typeof parsedInput.model === "string" ? parsedInput.model : null,
|
|
1840
|
+
agentType: typeof parsedInput.agent_type === "string" ? parsedInput.agent_type : null,
|
|
1841
|
+
timestamp
|
|
1842
|
+
});
|
|
1843
|
+
}
|
|
887
1844
|
const toolCall = {
|
|
888
1845
|
id: payload.call_id,
|
|
889
|
-
name
|
|
890
|
-
input
|
|
1846
|
+
name,
|
|
1847
|
+
input,
|
|
891
1848
|
result: null,
|
|
892
1849
|
isError: false,
|
|
893
1850
|
timestamp
|
|
@@ -903,6 +1860,116 @@ function parseCodexSession(jsonlText) {
|
|
|
903
1860
|
toolCall.result = output;
|
|
904
1861
|
toolCall.isError = inferToolError(output);
|
|
905
1862
|
pendingToolCalls.delete(payload.call_id);
|
|
1863
|
+
if (toolCall.name === "spawn_agent" && output) {
|
|
1864
|
+
try {
|
|
1865
|
+
const result = JSON.parse(output);
|
|
1866
|
+
const agentId = typeof result.agent_id === "string" ? result.agent_id : agentIdBySpawnCall.get(toolCall.id) ?? "";
|
|
1867
|
+
const agentPath = typeof result.task_name === "string" ? result.task_name : null;
|
|
1868
|
+
const nickname = typeof result.nickname === "string" ? result.nickname : agentPath ? agentNameFromPath(agentPath) : null;
|
|
1869
|
+
if (agentId) {
|
|
1870
|
+
const spawnInfo = spawnAgentCalls.get(toolCall.id);
|
|
1871
|
+
const existing = agentRegistry.get(agentId);
|
|
1872
|
+
const info = {
|
|
1873
|
+
agentId,
|
|
1874
|
+
nickname: nickname ?? existing?.nickname ?? spawnInfo?.taskName ?? null,
|
|
1875
|
+
agentPath: agentPath ?? existing?.agentPath ?? null,
|
|
1876
|
+
message: existing?.message ?? spawnInfo?.message ?? "",
|
|
1877
|
+
model: existing?.model ?? spawnInfo?.model ?? null,
|
|
1878
|
+
agentType: existing?.agentType ?? spawnInfo?.agentType ?? null,
|
|
1879
|
+
timestamp: existing?.timestamp ?? spawnInfo?.timestamp ?? timestamp,
|
|
1880
|
+
parentToolCallId: existing?.parentToolCallId ?? toolCall.id
|
|
1881
|
+
};
|
|
1882
|
+
agentRegistry.set(agentId, info);
|
|
1883
|
+
agentIdBySpawnCall.set(toolCall.id, agentId);
|
|
1884
|
+
if (info.agentPath) agentIdByPath.set(info.agentPath, agentId);
|
|
1885
|
+
upsertAgentMessage(agentId, current, timestamp, { status: "running" });
|
|
1886
|
+
}
|
|
1887
|
+
} catch {
|
|
1888
|
+
}
|
|
1889
|
+
}
|
|
1890
|
+
if (toolCall.name === "wait_agent" && output && current) {
|
|
1891
|
+
try {
|
|
1892
|
+
const result = JSON.parse(output);
|
|
1893
|
+
const statusMap = isObject2(result.status) ? result.status : {};
|
|
1894
|
+
for (const [agentId, status] of Object.entries(statusMap)) {
|
|
1895
|
+
const statusValue = isObject2(status) ? status : {};
|
|
1896
|
+
const completedText = typeof statusValue.completed === "string" ? statusValue.completed : "";
|
|
1897
|
+
const failedText = typeof statusValue.failed === "string" ? statusValue.failed : typeof statusValue.error === "string" ? statusValue.error : "";
|
|
1898
|
+
const interruptedText = typeof statusValue.interrupted === "string" ? statusValue.interrupted : "";
|
|
1899
|
+
const lifecycle = completedText ? "completed" : failedText ? "failed" : interruptedText ? "interrupted" : typeof statusValue.status === "string" ? statusValue.status : "running";
|
|
1900
|
+
if (!agentRegistry.has(agentId)) {
|
|
1901
|
+
agentRegistry.set(agentId, {
|
|
1902
|
+
agentId,
|
|
1903
|
+
nickname: null,
|
|
1904
|
+
agentPath: null,
|
|
1905
|
+
message: "",
|
|
1906
|
+
model: null,
|
|
1907
|
+
agentType: null,
|
|
1908
|
+
timestamp,
|
|
1909
|
+
parentToolCallId: ""
|
|
1910
|
+
});
|
|
1911
|
+
}
|
|
1912
|
+
upsertAgentMessage(agentId, current, timestamp, {
|
|
1913
|
+
status: lifecycle,
|
|
1914
|
+
text: completedText || failedText || interruptedText || void 0
|
|
1915
|
+
});
|
|
1916
|
+
}
|
|
1917
|
+
} catch {
|
|
1918
|
+
}
|
|
1919
|
+
}
|
|
1920
|
+
continue;
|
|
1921
|
+
}
|
|
1922
|
+
if (payload.type === "custom_tool_call" && typeof payload.call_id === "string") {
|
|
1923
|
+
const name = typeof payload.name === "string" ? payload.name : "tool";
|
|
1924
|
+
const rawInput = typeof payload.input === "string" ? payload.input : "";
|
|
1925
|
+
const callId = payload.call_id;
|
|
1926
|
+
const perFileCalls = rawInput ? parseCodexToolPatches(name, rawInput, callId, timestamp, metadata.cwd) : [];
|
|
1927
|
+
if (name !== "apply_patch") {
|
|
1928
|
+
const toolCall = {
|
|
1929
|
+
id: callId,
|
|
1930
|
+
name: name === "exec_command" ? "Bash" : name,
|
|
1931
|
+
input: parseToolInput(rawInput),
|
|
1932
|
+
result: null,
|
|
1933
|
+
isError: false,
|
|
1934
|
+
timestamp
|
|
1935
|
+
};
|
|
1936
|
+
pendingToolCalls.set(toolCall.id, toolCall);
|
|
1937
|
+
appendToolCall(current, toolCall, timestamp);
|
|
1938
|
+
}
|
|
1939
|
+
for (const tc of perFileCalls) {
|
|
1940
|
+
pendingToolCalls.set(tc.id, tc);
|
|
1941
|
+
appendToolCall(current, tc, timestamp);
|
|
1942
|
+
}
|
|
1943
|
+
if (perFileCalls.length > 0) {
|
|
1944
|
+
patchCallIds.set(callId, {
|
|
1945
|
+
calls: perFileCalls,
|
|
1946
|
+
direct: name === "apply_patch"
|
|
1947
|
+
});
|
|
1948
|
+
}
|
|
1949
|
+
continue;
|
|
1950
|
+
}
|
|
1951
|
+
if (payload.type === "custom_tool_call_output" && typeof payload.call_id === "string") {
|
|
1952
|
+
const callId = payload.call_id;
|
|
1953
|
+
const { text, isError } = parseCustomToolOutput(payload.output);
|
|
1954
|
+
const patchCalls = patchCallIds.get(callId);
|
|
1955
|
+
if (patchCalls) {
|
|
1956
|
+
const failedIds = patchCalls.direct && isError ? new Set(patchCalls.calls.map((call) => call.id)) : findFailedNestedPatchCallIds(text, isError, patchCalls.calls);
|
|
1957
|
+
for (const patchCall of patchCalls.calls) {
|
|
1958
|
+
const tc = pendingToolCalls.get(patchCall.id);
|
|
1959
|
+
if (tc) {
|
|
1960
|
+
tc.result = text;
|
|
1961
|
+
tc.isError = failedIds.has(tc.id);
|
|
1962
|
+
pendingToolCalls.delete(tc.id);
|
|
1963
|
+
}
|
|
1964
|
+
}
|
|
1965
|
+
patchCallIds.delete(callId);
|
|
1966
|
+
}
|
|
1967
|
+
const toolCall = pendingToolCalls.get(callId);
|
|
1968
|
+
if (toolCall) {
|
|
1969
|
+
toolCall.result = text;
|
|
1970
|
+
toolCall.isError = isError;
|
|
1971
|
+
pendingToolCalls.delete(callId);
|
|
1972
|
+
}
|
|
906
1973
|
continue;
|
|
907
1974
|
}
|
|
908
1975
|
}
|
|
@@ -913,19 +1980,17 @@ function parseCodexSession(jsonlText) {
|
|
|
913
1980
|
gitBranch: metadata.gitBranch,
|
|
914
1981
|
cwd: metadata.cwd,
|
|
915
1982
|
slug: metadata.slug,
|
|
1983
|
+
name: "",
|
|
916
1984
|
model: metadata.model,
|
|
917
1985
|
turns,
|
|
918
|
-
stats: computeStats(turns),
|
|
919
|
-
rawMessages: records,
|
|
1986
|
+
stats: options?.skipStats ? createEmptySessionStats(turns.length) : computeStats(turns),
|
|
1987
|
+
rawMessages: options?.skipStats ? [] : records,
|
|
920
1988
|
branchedFrom: metadata.branchedFrom,
|
|
921
1989
|
agentKind: "codex"
|
|
922
1990
|
};
|
|
923
1991
|
}
|
|
924
1992
|
|
|
925
1993
|
// src/lib/parser.ts
|
|
926
|
-
function isAssistantMessage2(msg) {
|
|
927
|
-
return msg.type === "assistant";
|
|
928
|
-
}
|
|
929
1994
|
function extractTextFromContent2(content) {
|
|
930
1995
|
if (typeof content === "string") return content;
|
|
931
1996
|
return content.filter((b) => b.type === "text").map((b) => b.text).join("\n");
|
|
@@ -944,55 +2009,43 @@ function parseLines(jsonlText) {
|
|
|
944
2009
|
}
|
|
945
2010
|
function isCodexRawMessages(rawMessages) {
|
|
946
2011
|
const firstType = rawMessages[0]?.type;
|
|
947
|
-
return firstType === "session_meta" || firstType === "turn_context" || firstType === "event_msg" || firstType === "response_item";
|
|
2012
|
+
return firstType === "session_meta" || firstType === "turn_context" || firstType === "event_msg" || firstType === "response_item" || firstType === "compacted" || firstType === "world_state" || firstType === "inter_agent_communication_metadata";
|
|
948
2013
|
}
|
|
949
2014
|
function serializeRawMessages(rawMessages) {
|
|
950
2015
|
return rawMessages.map((msg) => JSON.stringify(msg)).join("\n");
|
|
951
2016
|
}
|
|
952
2017
|
function extractSessionMetadata(messages) {
|
|
953
|
-
const meta = { sessionId: "", version: "", gitBranch: "", cwd: "", slug: "", model: "", branchedFrom: void 0 };
|
|
2018
|
+
const meta = { sessionId: "", version: "", gitBranch: "", cwd: "", slug: "", name: "", model: "", branchedFrom: void 0 };
|
|
954
2019
|
for (const msg of messages) {
|
|
955
2020
|
if (msg.sessionId && !meta.sessionId) meta.sessionId = msg.sessionId;
|
|
956
2021
|
if (msg.version && !meta.version) meta.version = msg.version;
|
|
957
2022
|
if (msg.gitBranch && !meta.gitBranch) meta.gitBranch = msg.gitBranch;
|
|
958
2023
|
if (msg.cwd && !meta.cwd) meta.cwd = msg.cwd;
|
|
959
2024
|
if (msg.slug && !meta.slug) meta.slug = msg.slug;
|
|
2025
|
+
if (msg.name && !meta.name) meta.name = msg.name;
|
|
960
2026
|
if (msg.branchedFrom && !meta.branchedFrom) {
|
|
961
2027
|
meta.branchedFrom = msg.branchedFrom;
|
|
962
2028
|
}
|
|
963
|
-
if (
|
|
2029
|
+
if (isAssistantMessage(msg) && msg.message.model && !meta.model) {
|
|
964
2030
|
meta.model = msg.message.model;
|
|
965
2031
|
}
|
|
966
2032
|
if (meta.sessionId && meta.version && meta.gitBranch && meta.cwd && meta.model) break;
|
|
967
2033
|
}
|
|
968
2034
|
return meta;
|
|
969
2035
|
}
|
|
970
|
-
function
|
|
971
|
-
return {
|
|
972
|
-
totalInputTokens: 0,
|
|
973
|
-
totalOutputTokens: 0,
|
|
974
|
-
totalCacheCreationTokens: 0,
|
|
975
|
-
totalCacheReadTokens: 0,
|
|
976
|
-
totalCostUSD: 0,
|
|
977
|
-
toolCallCounts: {},
|
|
978
|
-
errorCount: 0,
|
|
979
|
-
totalDurationMs: 0,
|
|
980
|
-
turnCount
|
|
981
|
-
};
|
|
982
|
-
}
|
|
983
|
-
function parseSession(jsonlText, opts) {
|
|
2036
|
+
function parseSession(jsonlText, options) {
|
|
984
2037
|
if (isCodexSessionText(jsonlText)) {
|
|
985
|
-
return parseCodexSession(jsonlText);
|
|
2038
|
+
return parseCodexSession(jsonlText, options);
|
|
986
2039
|
}
|
|
987
2040
|
const rawMessages = parseLines(jsonlText);
|
|
988
2041
|
const metadata = extractSessionMetadata(rawMessages);
|
|
989
2042
|
const turns = buildTurns(rawMessages);
|
|
990
|
-
const stats =
|
|
2043
|
+
const stats = options?.skipStats ? createEmptySessionStats(turns.length) : computeStats(turns);
|
|
991
2044
|
return {
|
|
992
2045
|
...metadata,
|
|
993
2046
|
turns,
|
|
994
2047
|
stats,
|
|
995
|
-
rawMessages:
|
|
2048
|
+
rawMessages: options?.skipStats ? [] : rawMessages,
|
|
996
2049
|
agentKind: "claude"
|
|
997
2050
|
};
|
|
998
2051
|
}
|
|
@@ -1004,62 +2057,31 @@ ${newJsonlText}` : newJsonlText);
|
|
|
1004
2057
|
}
|
|
1005
2058
|
const newMessages = parseLines(newJsonlText);
|
|
1006
2059
|
if (newMessages.length === 0) return existing;
|
|
1007
|
-
const
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
}
|
|
1026
|
-
const progressParentIds = /* @__PURE__ */ new Set();
|
|
1027
|
-
for (const msg of newMessages) {
|
|
1028
|
-
if (msg.type === "progress") {
|
|
1029
|
-
const parentId = msg.parentToolUseID;
|
|
1030
|
-
if (parentId) progressParentIds.add(parentId);
|
|
1031
|
-
}
|
|
1032
|
-
}
|
|
1033
|
-
if (progressParentIds.size > 0) {
|
|
1034
|
-
for (let t = existing.turns.length - 2; t >= 0; t--) {
|
|
1035
|
-
const turn = existing.turns[t];
|
|
1036
|
-
const ownsProgressParent = turn.toolCalls.some((tc) => progressParentIds.has(tc.id));
|
|
1037
|
-
if (ownsProgressParent) {
|
|
1038
|
-
turnsToKeep = t;
|
|
1039
|
-
let found = 0;
|
|
1040
|
-
for (let i = 0; i < existing.rawMessages.length; i++) {
|
|
1041
|
-
const msg = existing.rawMessages[i];
|
|
1042
|
-
if (msg.type === "user" && !msg.isMeta) {
|
|
1043
|
-
const content = msg.message?.content;
|
|
1044
|
-
if (Array.isArray(content) && content.some((b) => b.type === "tool_result")) {
|
|
1045
|
-
continue;
|
|
1046
|
-
}
|
|
1047
|
-
if (found === t) {
|
|
1048
|
-
lastTurnStartIdx = i;
|
|
1049
|
-
break;
|
|
1050
|
-
}
|
|
1051
|
-
found++;
|
|
1052
|
-
}
|
|
1053
|
-
}
|
|
1054
|
-
break;
|
|
1055
|
-
}
|
|
2060
|
+
const existingRawMessages = existing.rawMessages;
|
|
2061
|
+
const allRawMessages = [...existingRawMessages, ...newMessages];
|
|
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);
|
|
2070
|
+
}
|
|
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;
|
|
1056
2078
|
}
|
|
1057
2079
|
}
|
|
1058
2080
|
}
|
|
1059
|
-
const
|
|
1060
|
-
const
|
|
1061
|
-
const tailTurns = buildTurns(
|
|
1062
|
-
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]);
|
|
1063
2085
|
const stats = computeStats(allTurns);
|
|
1064
2086
|
return {
|
|
1065
2087
|
sessionId: existing.sessionId,
|
|
@@ -1067,11 +2089,13 @@ ${newJsonlText}` : newJsonlText);
|
|
|
1067
2089
|
gitBranch: existing.gitBranch,
|
|
1068
2090
|
cwd: existing.cwd,
|
|
1069
2091
|
slug: existing.slug,
|
|
2092
|
+
name: existing.name,
|
|
1070
2093
|
model: existing.model || (allTurns.length > 0 ? allTurns[allTurns.length - 1].model || "" : ""),
|
|
1071
2094
|
turns: allTurns,
|
|
1072
2095
|
stats,
|
|
1073
2096
|
rawMessages: allRawMessages,
|
|
1074
|
-
branchedFrom: existing.branchedFrom
|
|
2097
|
+
branchedFrom: existing.branchedFrom,
|
|
2098
|
+
agentKind: existing.agentKind
|
|
1075
2099
|
};
|
|
1076
2100
|
}
|
|
1077
2101
|
function getUserMessageText(content) {
|
|
@@ -1329,17 +2353,11 @@ var SearchIndex = class {
|
|
|
1329
2353
|
buildFull(projectsDir) {
|
|
1330
2354
|
this.projectsDir = projectsDir;
|
|
1331
2355
|
this.db.close();
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
(0, import_node_fs.unlinkSync)(this.dbPath + "-wal");
|
|
1338
|
-
} catch {
|
|
1339
|
-
}
|
|
1340
|
-
try {
|
|
1341
|
-
(0, import_node_fs.unlinkSync)(this.dbPath + "-shm");
|
|
1342
|
-
} catch {
|
|
2356
|
+
for (const suffix of ["", "-wal", "-shm"]) {
|
|
2357
|
+
try {
|
|
2358
|
+
(0, import_node_fs.unlinkSync)(this.dbPath + suffix);
|
|
2359
|
+
} catch {
|
|
2360
|
+
}
|
|
1343
2361
|
}
|
|
1344
2362
|
this.db = new Database(this.dbPath);
|
|
1345
2363
|
this.db.exec("PRAGMA journal_mode = WAL");
|
|
@@ -1611,7 +2629,8 @@ var import_node_os = require("node:os");
|
|
|
1611
2629
|
var dirs = {
|
|
1612
2630
|
PROJECTS_DIR: (0, import_node_path2.join)((0, import_node_os.homedir)(), ".claude", "projects"),
|
|
1613
2631
|
TEAMS_DIR: (0, import_node_path2.join)((0, import_node_os.homedir)(), ".claude", "teams"),
|
|
1614
|
-
TASKS_DIR: (0, import_node_path2.join)((0, import_node_os.homedir)(), ".claude", "tasks")
|
|
2632
|
+
TASKS_DIR: (0, import_node_path2.join)((0, import_node_os.homedir)(), ".claude", "tasks"),
|
|
2633
|
+
CODEX_SESSIONS_DIR: (0, import_node_path2.join)((0, import_node_os.homedir)(), ".codex", "sessions")
|
|
1615
2634
|
};
|
|
1616
2635
|
var DEFAULT_DB_PATH = (0, import_node_path2.join)((0, import_node_os.homedir)(), ".claude", "cogpit-memory", "search-index.db");
|
|
1617
2636
|
|
|
@@ -1724,7 +2743,11 @@ async function matchSubagentToMember(leadSessionId, subagentFileName, members) {
|
|
|
1724
2743
|
}
|
|
1725
2744
|
return null;
|
|
1726
2745
|
}
|
|
1727
|
-
|
|
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();
|
|
1728
2751
|
|
|
1729
2752
|
// src/commands/search.ts
|
|
1730
2753
|
async function searchSessions(query, opts, searchIndex) {
|
|
@@ -2076,6 +3099,7 @@ var import_promises3 = require("node:fs/promises");
|
|
|
2076
3099
|
var import_node_path5 = require("node:path");
|
|
2077
3100
|
var RESULT_TRUNCATE_LIMIT = 1e4;
|
|
2078
3101
|
var L1_RESPONSE_LIMIT = 15e4;
|
|
3102
|
+
var COMPACTION_SUMMARY_PREVIEW = 400;
|
|
2079
3103
|
function extractUserMessageText(userMessage) {
|
|
2080
3104
|
if (userMessage === null) return null;
|
|
2081
3105
|
if (typeof userMessage === "string") return userMessage;
|
|
@@ -2083,6 +3107,8 @@ function extractUserMessageText(userMessage) {
|
|
|
2083
3107
|
for (const block of userMessage) {
|
|
2084
3108
|
if (block.type === "text") parts.push(block.text);
|
|
2085
3109
|
else if (block.type === "image") parts.push("[image attached]");
|
|
3110
|
+
else if (block.type === "document") parts.push("[document attached]");
|
|
3111
|
+
else if (block.type === "audio") parts.push("[audio attached]");
|
|
2086
3112
|
}
|
|
2087
3113
|
return parts.length > 0 ? parts.join("\n") : null;
|
|
2088
3114
|
}
|
|
@@ -2165,9 +3191,13 @@ function mapTurnToSummary(turn, turnIndex) {
|
|
|
2165
3191
|
subAgents,
|
|
2166
3192
|
hasThinking: turn.thinking.length > 0,
|
|
2167
3193
|
isError: turn.toolCalls.some((tc) => tc.isError),
|
|
2168
|
-
compactionSummary: turn.compactionSummary
|
|
3194
|
+
compactionSummary: truncateCompactionSummary(turn.compactionSummary)
|
|
2169
3195
|
};
|
|
2170
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
|
+
}
|
|
2171
3201
|
function mapTurnToDetail(session, turnIndex) {
|
|
2172
3202
|
const turn = session.turns[turnIndex];
|
|
2173
3203
|
const contentBlocks = turn.contentBlocks.map((block) => mapContentBlock(block));
|
|
@@ -2175,12 +3205,17 @@ function mapTurnToDetail(session, turnIndex) {
|
|
|
2175
3205
|
sessionId: session.sessionId,
|
|
2176
3206
|
turnIndex,
|
|
2177
3207
|
userMessage: extractUserMessageText(turn.userMessage),
|
|
3208
|
+
compactionSummary: turn.compactionSummary ?? null,
|
|
2178
3209
|
contentBlocks,
|
|
2179
3210
|
tokenUsage: turn.tokenUsage ? { input: turn.tokenUsage.input_tokens, output: turn.tokenUsage.output_tokens } : null,
|
|
2180
3211
|
model: turn.model,
|
|
2181
3212
|
durationMs: turn.durationMs
|
|
2182
3213
|
};
|
|
2183
3214
|
}
|
|
3215
|
+
function mapToolCall(tc) {
|
|
3216
|
+
const { result, resultTruncated } = truncateResult(tc.result);
|
|
3217
|
+
return { id: tc.id, name: tc.name, input: tc.input, result, resultTruncated, isError: tc.isError };
|
|
3218
|
+
}
|
|
2184
3219
|
function mapContentBlock(block) {
|
|
2185
3220
|
switch (block.kind) {
|
|
2186
3221
|
case "thinking": {
|
|
@@ -2192,10 +3227,7 @@ function mapContentBlock(block) {
|
|
|
2192
3227
|
case "tool_calls":
|
|
2193
3228
|
return {
|
|
2194
3229
|
kind: "tool_calls",
|
|
2195
|
-
toolCalls: block.toolCalls.map(
|
|
2196
|
-
const { result, resultTruncated } = truncateResult(tc.result);
|
|
2197
|
-
return { id: tc.id, name: tc.name, input: tc.input, result, resultTruncated, isError: tc.isError };
|
|
2198
|
-
}),
|
|
3230
|
+
toolCalls: block.toolCalls.map(mapToolCall),
|
|
2199
3231
|
timestamp: block.timestamp ?? null
|
|
2200
3232
|
};
|
|
2201
3233
|
case "sub_agent":
|
|
@@ -2205,6 +3237,35 @@ function mapContentBlock(block) {
|
|
|
2205
3237
|
agents: block.messages.map(mapSubAgentDetail),
|
|
2206
3238
|
timestamp: block.timestamp ?? null
|
|
2207
3239
|
};
|
|
3240
|
+
case "queued_prompt":
|
|
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
|
+
};
|
|
3250
|
+
case "hook_event":
|
|
3251
|
+
return { kind: "hook_event", events: block.events, timestamp: block.timestamp ?? null };
|
|
3252
|
+
case "plan_mode":
|
|
3253
|
+
return {
|
|
3254
|
+
kind: "plan_mode",
|
|
3255
|
+
plan: block.plan,
|
|
3256
|
+
planFilePath: block.planFilePath ?? null,
|
|
3257
|
+
status: block.status,
|
|
3258
|
+
toolCalls: block.toolCalls.map(mapToolCall),
|
|
3259
|
+
timestamp: block.timestamp ?? null
|
|
3260
|
+
};
|
|
3261
|
+
case "recap":
|
|
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
|
+
}
|
|
2208
3269
|
}
|
|
2209
3270
|
}
|
|
2210
3271
|
async function findSubagentFile(parentJsonlPath, agentId) {
|
|
@@ -2327,7 +3388,6 @@ async function getAgentOverview(sessionId, agentId) {
|
|
|
2327
3388
|
async function getAgentTurnDetail(sessionId, agentId, turnIndex) {
|
|
2328
3389
|
const jsonlPath = await findJsonlPath(sessionId);
|
|
2329
3390
|
if (!jsonlPath) return { error: "Session not found" };
|
|
2330
|
-
const content = await (0, import_promises3.readFile)(jsonlPath, "utf-8");
|
|
2331
3391
|
const subagentFile = await findSubagentFile(jsonlPath, agentId);
|
|
2332
3392
|
if (!subagentFile) return { error: "Agent not found" };
|
|
2333
3393
|
const subagentContent = await (0, import_promises3.readFile)(subagentFile.filePath, "utf-8");
|
|
@@ -2339,35 +3399,140 @@ async function getAgentTurnDetail(sessionId, agentId, turnIndex) {
|
|
|
2339
3399
|
// src/commands/sessions.ts
|
|
2340
3400
|
var import_promises5 = require("node:fs/promises");
|
|
2341
3401
|
var import_node_path6 = require("node:path");
|
|
2342
|
-
var import_node_os3 = require("node:os");
|
|
2343
3402
|
|
|
2344
3403
|
// src/lib/metadata.ts
|
|
2345
3404
|
var import_promises4 = require("node:fs/promises");
|
|
2346
3405
|
|
|
2347
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
|
+
}
|
|
2348
3484
|
function deriveSessionStatus(rawMessages) {
|
|
2349
3485
|
const firstType = rawMessages[0]?.type;
|
|
2350
3486
|
if (firstType === "session_meta" || firstType === "turn_context" || firstType === "event_msg" || firstType === "response_item") {
|
|
2351
3487
|
return deriveCodexSessionStatus(rawMessages);
|
|
2352
3488
|
}
|
|
2353
3489
|
let pendingEnqueues = 0;
|
|
3490
|
+
let backgroundWork = null;
|
|
3491
|
+
let deliveringNotification = false;
|
|
2354
3492
|
function result(status, toolName) {
|
|
2355
3493
|
const info = { status, pendingQueue: Math.max(0, pendingEnqueues) };
|
|
2356
3494
|
if (toolName) info.toolName = toolName;
|
|
2357
3495
|
return info;
|
|
2358
3496
|
}
|
|
3497
|
+
function getBackgroundWork() {
|
|
3498
|
+
return backgroundWork ??= collectClaudeBackgroundWork(rawMessages);
|
|
3499
|
+
}
|
|
3500
|
+
for (let i = rawMessages.length - 1; i >= 0; i--) {
|
|
3501
|
+
const msg = rawMessages[i];
|
|
3502
|
+
if (msg.type === "queue-operation") continue;
|
|
3503
|
+
if (msg.type === "progress") {
|
|
3504
|
+
const data = msg.data;
|
|
3505
|
+
if (data?.type === "hook_progress") {
|
|
3506
|
+
const decision = data.decision ?? data.hookSpecificOutput?.permissionDecision;
|
|
3507
|
+
if (decision === "defer") return result("deferred");
|
|
3508
|
+
}
|
|
3509
|
+
break;
|
|
3510
|
+
}
|
|
3511
|
+
break;
|
|
3512
|
+
}
|
|
2359
3513
|
for (let i = rawMessages.length - 1; i >= 0; i--) {
|
|
2360
3514
|
const msg = rawMessages[i];
|
|
2361
3515
|
if (msg.type === "queue-operation") {
|
|
3516
|
+
if (typeof msg.content === "string" && msg.content.includes(TASK_NOTIFICATION_MARKER)) continue;
|
|
2362
3517
|
const op = msg.operation;
|
|
2363
3518
|
if (op === "enqueue") pendingEnqueues++;
|
|
2364
3519
|
else if (op === "dequeue" || op === "remove") pendingEnqueues--;
|
|
2365
3520
|
continue;
|
|
2366
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
|
+
}
|
|
2367
3527
|
if (msg.type === "assistant") {
|
|
2368
3528
|
const message = msg.message;
|
|
2369
3529
|
const stopReason = message?.stop_reason;
|
|
2370
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
|
+
}
|
|
2371
3536
|
let hasActivity = false;
|
|
2372
3537
|
for (let j = i - 1; j >= 0; j--) {
|
|
2373
3538
|
const m = rawMessages[j];
|
|
@@ -2390,19 +3555,136 @@ function deriveSessionStatus(rawMessages) {
|
|
|
2390
3555
|
if (isMeta) continue;
|
|
2391
3556
|
return result("processing");
|
|
2392
3557
|
}
|
|
3558
|
+
if (msg.type === "system" && msg.subtype === "terminal_reason") {
|
|
3559
|
+
const reason = msg.reason;
|
|
3560
|
+
if (reason) {
|
|
3561
|
+
return { status: "completed", terminalReason: reason, pendingQueue: Math.max(0, pendingEnqueues) };
|
|
3562
|
+
}
|
|
3563
|
+
continue;
|
|
3564
|
+
}
|
|
2393
3565
|
if (msg.type === "summary") continue;
|
|
2394
3566
|
if (msg.type === "system" && msg.subtype === "compact_boundary") continue;
|
|
2395
3567
|
}
|
|
2396
3568
|
return { status: "idle" };
|
|
2397
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
|
+
}
|
|
2398
3677
|
function deriveCodexSessionStatus(rawMessages) {
|
|
2399
3678
|
for (let i = rawMessages.length - 1; i >= 0; i--) {
|
|
2400
3679
|
const msg = rawMessages[i];
|
|
2401
3680
|
if (msg.type === "event_msg") {
|
|
2402
3681
|
const payload = msg.payload;
|
|
2403
3682
|
switch (payload?.type) {
|
|
2404
|
-
case "task_complete":
|
|
3683
|
+
case "task_complete": {
|
|
3684
|
+
const pending = collectCodexPendingAgents(rawMessages);
|
|
3685
|
+
if (pending.length > 0) return awaitingAgentsResult(pending, 0);
|
|
2405
3686
|
return { status: "completed" };
|
|
3687
|
+
}
|
|
2406
3688
|
case "task_started":
|
|
2407
3689
|
return { status: "processing" };
|
|
2408
3690
|
case "agent_message":
|
|
@@ -2415,7 +3697,7 @@ function deriveCodexSessionStatus(rawMessages) {
|
|
|
2415
3697
|
const payload = msg.payload;
|
|
2416
3698
|
if (!payload) continue;
|
|
2417
3699
|
if (payload.type === "function_call") {
|
|
2418
|
-
return { status: "tool_use", toolName: payload.name };
|
|
3700
|
+
return { status: "tool_use", toolName: payload.name ? normalizeFunctionName(payload.name) : payload.name };
|
|
2419
3701
|
}
|
|
2420
3702
|
if (payload.type === "message") {
|
|
2421
3703
|
if (payload.role === "assistant") return { status: "thinking" };
|
|
@@ -2615,7 +3897,23 @@ async function getSessionStatus(filePath) {
|
|
|
2615
3897
|
}
|
|
2616
3898
|
|
|
2617
3899
|
// src/commands/sessions.ts
|
|
2618
|
-
|
|
3900
|
+
function toSessionSummary(file, meta, status) {
|
|
3901
|
+
return {
|
|
3902
|
+
sessionId: meta.sessionId,
|
|
3903
|
+
filePath: file.path,
|
|
3904
|
+
timestamp: meta.timestamp,
|
|
3905
|
+
model: meta.model,
|
|
3906
|
+
cwd: meta.cwd,
|
|
3907
|
+
gitBranch: meta.gitBranch,
|
|
3908
|
+
slug: meta.slug,
|
|
3909
|
+
firstMessage: meta.firstUserMessage,
|
|
3910
|
+
lastMessage: meta.lastUserMessage,
|
|
3911
|
+
turnCount: meta.turnCount,
|
|
3912
|
+
status,
|
|
3913
|
+
mtime: file.mtimeMs,
|
|
3914
|
+
source: file.path.startsWith(dirs.CODEX_SESSIONS_DIR + "/") ? "codex" : "claude"
|
|
3915
|
+
};
|
|
3916
|
+
}
|
|
2619
3917
|
async function listCodexSessionFiles(cutoff) {
|
|
2620
3918
|
const walk = async (dir, depth) => {
|
|
2621
3919
|
if (depth > 4) return [];
|
|
@@ -2642,7 +3940,7 @@ async function listCodexSessionFiles(cutoff) {
|
|
|
2642
3940
|
}
|
|
2643
3941
|
return results;
|
|
2644
3942
|
};
|
|
2645
|
-
return walk(CODEX_SESSIONS_DIR, 0);
|
|
3943
|
+
return walk(dirs.CODEX_SESSIONS_DIR, 0);
|
|
2646
3944
|
}
|
|
2647
3945
|
async function listSessions(opts = {}) {
|
|
2648
3946
|
const limit = Math.min(Math.max(1, opts.limit ?? 20), 100);
|
|
@@ -2655,29 +3953,31 @@ async function listSessions(opts = {}) {
|
|
|
2655
3953
|
return [];
|
|
2656
3954
|
}
|
|
2657
3955
|
const projectDirs = entries.filter((e) => e.isDirectory() && e.name !== "memory").map((e) => (0, import_node_path6.join)(dirs.PROJECTS_DIR, e.name));
|
|
2658
|
-
const nested = await Promise.all(
|
|
2659
|
-
|
|
2660
|
-
|
|
2661
|
-
|
|
2662
|
-
|
|
2663
|
-
|
|
2664
|
-
|
|
2665
|
-
|
|
2666
|
-
|
|
2667
|
-
|
|
2668
|
-
|
|
2669
|
-
|
|
2670
|
-
|
|
2671
|
-
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
|
|
3956
|
+
const [nested, codexFiles] = await Promise.all([
|
|
3957
|
+
Promise.all(
|
|
3958
|
+
projectDirs.map(async (projectDir) => {
|
|
3959
|
+
try {
|
|
3960
|
+
const files = await (0, import_promises5.readdir)(projectDir);
|
|
3961
|
+
const jsonlFiles = files.filter((f) => f.endsWith(".jsonl"));
|
|
3962
|
+
const statResults = await Promise.all(
|
|
3963
|
+
jsonlFiles.map(async (f) => {
|
|
3964
|
+
const filePath = (0, import_node_path6.join)(projectDir, f);
|
|
3965
|
+
try {
|
|
3966
|
+
const s = await (0, import_promises5.stat)(filePath);
|
|
3967
|
+
return s.mtimeMs >= cutoff ? { path: filePath, mtimeMs: s.mtimeMs } : null;
|
|
3968
|
+
} catch {
|
|
3969
|
+
return null;
|
|
3970
|
+
}
|
|
3971
|
+
})
|
|
3972
|
+
);
|
|
3973
|
+
return statResults.filter((r) => r !== null);
|
|
3974
|
+
} catch {
|
|
3975
|
+
return [];
|
|
3976
|
+
}
|
|
3977
|
+
})
|
|
3978
|
+
),
|
|
3979
|
+
listCodexSessionFiles(cutoff)
|
|
3980
|
+
]);
|
|
2681
3981
|
const allFiles = [...nested.flat(), ...codexFiles];
|
|
2682
3982
|
allFiles.sort((a, b) => b.mtimeMs - a.mtimeMs);
|
|
2683
3983
|
const results = [];
|
|
@@ -2689,29 +3989,14 @@ async function listSessions(opts = {}) {
|
|
|
2689
3989
|
getSessionStatus(file.path)
|
|
2690
3990
|
]);
|
|
2691
3991
|
if (opts.cwd && meta.cwd !== opts.cwd) continue;
|
|
2692
|
-
results.push(
|
|
2693
|
-
sessionId: meta.sessionId,
|
|
2694
|
-
filePath: file.path,
|
|
2695
|
-
timestamp: meta.timestamp,
|
|
2696
|
-
model: meta.model,
|
|
2697
|
-
cwd: meta.cwd,
|
|
2698
|
-
gitBranch: meta.gitBranch,
|
|
2699
|
-
slug: meta.slug,
|
|
2700
|
-
firstMessage: meta.firstUserMessage,
|
|
2701
|
-
lastMessage: meta.lastUserMessage,
|
|
2702
|
-
turnCount: meta.turnCount,
|
|
2703
|
-
status: statusInfo.status,
|
|
2704
|
-
mtime: file.mtimeMs,
|
|
2705
|
-
source: file.path.startsWith(CODEX_SESSIONS_DIR + "/") ? "codex" : "claude"
|
|
2706
|
-
});
|
|
3992
|
+
results.push(toSessionSummary(file, meta, statusInfo.status));
|
|
2707
3993
|
} catch {
|
|
2708
3994
|
}
|
|
2709
3995
|
}
|
|
2710
3996
|
return results;
|
|
2711
3997
|
}
|
|
2712
3998
|
async function currentSession(cwd) {
|
|
2713
|
-
const
|
|
2714
|
-
const projectDir = (0, import_node_path6.join)(dirs.PROJECTS_DIR, projectDirName);
|
|
3999
|
+
const projectDir = (0, import_node_path6.join)(dirs.PROJECTS_DIR, encodeClaudeDirName(cwd));
|
|
2715
4000
|
let files;
|
|
2716
4001
|
try {
|
|
2717
4002
|
files = await (0, import_promises5.readdir)(projectDir);
|
|
@@ -2719,18 +4004,20 @@ async function currentSession(cwd) {
|
|
|
2719
4004
|
files = [];
|
|
2720
4005
|
}
|
|
2721
4006
|
const jsonlFiles = files.filter((f) => f.endsWith(".jsonl"));
|
|
2722
|
-
const statResults = await Promise.all(
|
|
2723
|
-
|
|
2724
|
-
|
|
2725
|
-
|
|
2726
|
-
|
|
2727
|
-
|
|
2728
|
-
|
|
2729
|
-
|
|
2730
|
-
|
|
2731
|
-
|
|
2732
|
-
|
|
2733
|
-
|
|
4007
|
+
const [statResults, codexCandidates] = await Promise.all([
|
|
4008
|
+
Promise.all(
|
|
4009
|
+
jsonlFiles.map(async (f) => {
|
|
4010
|
+
const filePath = (0, import_node_path6.join)(projectDir, f);
|
|
4011
|
+
try {
|
|
4012
|
+
const s = await (0, import_promises5.stat)(filePath);
|
|
4013
|
+
return { path: filePath, mtimeMs: s.mtimeMs };
|
|
4014
|
+
} catch {
|
|
4015
|
+
return null;
|
|
4016
|
+
}
|
|
4017
|
+
})
|
|
4018
|
+
),
|
|
4019
|
+
listCodexSessionFiles(0)
|
|
4020
|
+
]);
|
|
2734
4021
|
const codexMatches = [];
|
|
2735
4022
|
for (const file of codexCandidates) {
|
|
2736
4023
|
try {
|
|
@@ -2752,21 +4039,7 @@ async function currentSession(cwd) {
|
|
|
2752
4039
|
getSessionMeta(latest.path),
|
|
2753
4040
|
getSessionStatus(latest.path)
|
|
2754
4041
|
]);
|
|
2755
|
-
return
|
|
2756
|
-
sessionId: meta.sessionId,
|
|
2757
|
-
filePath: latest.path,
|
|
2758
|
-
timestamp: meta.timestamp,
|
|
2759
|
-
model: meta.model,
|
|
2760
|
-
cwd: meta.cwd,
|
|
2761
|
-
gitBranch: meta.gitBranch,
|
|
2762
|
-
slug: meta.slug,
|
|
2763
|
-
firstMessage: meta.firstUserMessage,
|
|
2764
|
-
lastMessage: meta.lastUserMessage,
|
|
2765
|
-
turnCount: meta.turnCount,
|
|
2766
|
-
status: statusInfo.status,
|
|
2767
|
-
mtime: latest.mtimeMs,
|
|
2768
|
-
source: latest.path.startsWith(CODEX_SESSIONS_DIR + "/") ? "codex" : "claude"
|
|
2769
|
-
};
|
|
4042
|
+
return toSessionSummary(latest, meta, statusInfo.status);
|
|
2770
4043
|
}
|
|
2771
4044
|
|
|
2772
4045
|
// src/commands/index-cmd.ts
|