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