cogpit-memory 0.1.8 → 0.1.10
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/README.md +26 -5
- package/dist/cli.d.ts +30 -0
- package/dist/cli.js +1133 -187
- 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 +41 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.js +1136 -189
- package/dist/lib/codex-patches.d.ts +9 -0
- package/dist/lib/codex-tool-normalization.d.ts +8 -0
- package/dist/lib/codex.d.ts +30 -0
- package/dist/lib/dirs.d.ts +8 -0
- package/dist/lib/helpers.d.ts +11 -0
- package/dist/lib/interactiveState.d.ts +35 -0
- package/dist/lib/messageTypeGuards.d.ts +17 -0
- package/dist/lib/metadata.d.ts +31 -0
- package/dist/lib/parser.d.ts +14 -0
- package/dist/lib/pricingTiers.d.ts +30 -0
- package/dist/lib/response.d.ts +2 -0
- package/dist/lib/search-index.d.ts +154 -0
- package/dist/lib/sessionStats.d.ts +6 -0
- package/dist/lib/sessionStatus.d.ts +36 -0
- package/dist/lib/token-costs.d.ts +48 -0
- package/dist/lib/turnBuilder.d.ts +5 -0
- package/dist/lib/types.d.ts +401 -0
- package/package.json +13 -7
- 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,14 @@ 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
|
+
}
|
|
67
70
|
function isCompactBoundary(msg) {
|
|
68
71
|
return msg.type === "system" && msg.subtype === "compact_boundary";
|
|
69
72
|
}
|
|
73
|
+
|
|
74
|
+
// src/lib/turnBuilder.ts
|
|
70
75
|
function extractTextFromContent(content) {
|
|
71
76
|
if (typeof content === "string") return content;
|
|
72
77
|
return content.filter((b) => b.type === "text").map((b) => b.text).join("\n");
|
|
@@ -79,6 +84,11 @@ function extractToolResultText(content) {
|
|
|
79
84
|
return "";
|
|
80
85
|
}).filter(Boolean).join("\n");
|
|
81
86
|
}
|
|
87
|
+
function isVisibleQueuedPrompt(content) {
|
|
88
|
+
if (!content?.trim()) return false;
|
|
89
|
+
const trimmed = content.trimStart();
|
|
90
|
+
return !trimmed.startsWith("<task-notification>") && !trimmed.startsWith("<local-command-");
|
|
91
|
+
}
|
|
82
92
|
function mergeTokenUsage(existing, incoming) {
|
|
83
93
|
if (!existing) {
|
|
84
94
|
return { ...incoming };
|
|
@@ -87,7 +97,8 @@ function mergeTokenUsage(existing, incoming) {
|
|
|
87
97
|
input_tokens: existing.input_tokens + incoming.input_tokens,
|
|
88
98
|
output_tokens: existing.output_tokens + incoming.output_tokens,
|
|
89
99
|
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)
|
|
100
|
+
cache_read_input_tokens: (existing.cache_read_input_tokens ?? 0) + (incoming.cache_read_input_tokens ?? 0),
|
|
101
|
+
speed: incoming.speed ?? existing.speed
|
|
91
102
|
};
|
|
92
103
|
}
|
|
93
104
|
function buildCompactionSummary(turns, title) {
|
|
@@ -120,10 +131,95 @@ function buildCompactionSummary(turns, title) {
|
|
|
120
131
|
}
|
|
121
132
|
return parts.join("\n");
|
|
122
133
|
}
|
|
134
|
+
function groupPlanModeBlocks(blocks) {
|
|
135
|
+
const result = [];
|
|
136
|
+
let i = 0;
|
|
137
|
+
while (i < blocks.length) {
|
|
138
|
+
const block = blocks[i];
|
|
139
|
+
if (block.kind === "tool_calls") {
|
|
140
|
+
const enterIdx = block.toolCalls.findIndex((tc) => tc.name === "EnterPlanMode");
|
|
141
|
+
if (enterIdx !== -1) {
|
|
142
|
+
const enterCall = block.toolCalls[enterIdx];
|
|
143
|
+
const plan = String(enterCall.input.plan ?? "");
|
|
144
|
+
const timestamp = block.timestamp;
|
|
145
|
+
const before = block.toolCalls.slice(0, enterIdx);
|
|
146
|
+
if (before.length > 0) {
|
|
147
|
+
result.push({ kind: "tool_calls", toolCalls: before, timestamp: block.timestamp });
|
|
148
|
+
}
|
|
149
|
+
const embedded = [];
|
|
150
|
+
const afterEnterInSameBlock = block.toolCalls.slice(enterIdx + 1);
|
|
151
|
+
const exitInSameBlock = afterEnterInSameBlock.findIndex((tc) => tc.name === "ExitPlanMode");
|
|
152
|
+
if (exitInSameBlock !== -1) {
|
|
153
|
+
embedded.push(...afterEnterInSameBlock.slice(0, exitInSameBlock));
|
|
154
|
+
const exitCall = afterEnterInSameBlock[exitInSameBlock];
|
|
155
|
+
const planFilePath = exitCall.input.path ? String(exitCall.input.path) : void 0;
|
|
156
|
+
let status = "pending";
|
|
157
|
+
if (exitCall.result !== null) {
|
|
158
|
+
status = exitCall.isError ? "rejected" : "approved";
|
|
159
|
+
}
|
|
160
|
+
result.push({ kind: "plan_mode", plan, planFilePath, status, toolCalls: embedded, timestamp });
|
|
161
|
+
const after = afterEnterInSameBlock.slice(exitInSameBlock + 1);
|
|
162
|
+
if (after.length > 0) {
|
|
163
|
+
result.push({ kind: "tool_calls", toolCalls: after, timestamp: block.timestamp });
|
|
164
|
+
}
|
|
165
|
+
i++;
|
|
166
|
+
continue;
|
|
167
|
+
} else {
|
|
168
|
+
embedded.push(...afterEnterInSameBlock);
|
|
169
|
+
let exitCall;
|
|
170
|
+
let planFilePath;
|
|
171
|
+
let status = "pending";
|
|
172
|
+
let j = i + 1;
|
|
173
|
+
const passthroughBlocks = [];
|
|
174
|
+
while (j < blocks.length) {
|
|
175
|
+
const next = blocks[j];
|
|
176
|
+
if (next.kind === "tool_calls") {
|
|
177
|
+
const exitIdx = next.toolCalls.findIndex((tc) => tc.name === "ExitPlanMode");
|
|
178
|
+
if (exitIdx !== -1) {
|
|
179
|
+
embedded.push(...next.toolCalls.slice(0, exitIdx));
|
|
180
|
+
exitCall = next.toolCalls[exitIdx];
|
|
181
|
+
planFilePath = exitCall.input.path ? String(exitCall.input.path) : void 0;
|
|
182
|
+
if (exitCall.result !== null) {
|
|
183
|
+
status = exitCall.isError ? "rejected" : "approved";
|
|
184
|
+
}
|
|
185
|
+
const tail = next.toolCalls.slice(exitIdx + 1);
|
|
186
|
+
result.push({ kind: "plan_mode", plan, planFilePath, status, toolCalls: embedded, timestamp });
|
|
187
|
+
result.push(...passthroughBlocks);
|
|
188
|
+
if (tail.length > 0) {
|
|
189
|
+
result.push({ kind: "tool_calls", toolCalls: tail, timestamp: next.timestamp });
|
|
190
|
+
}
|
|
191
|
+
i = j + 1;
|
|
192
|
+
break;
|
|
193
|
+
} else {
|
|
194
|
+
embedded.push(...next.toolCalls);
|
|
195
|
+
j++;
|
|
196
|
+
}
|
|
197
|
+
} else if (next.kind === "hook_event" || next.kind === "text" || next.kind === "queued_prompt") {
|
|
198
|
+
passthroughBlocks.push(next);
|
|
199
|
+
j++;
|
|
200
|
+
} else {
|
|
201
|
+
break;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
if (!exitCall) {
|
|
205
|
+
result.push({ kind: "plan_mode", plan, planFilePath: void 0, status: "pending", toolCalls: embedded, timestamp });
|
|
206
|
+
result.push(...passthroughBlocks);
|
|
207
|
+
i = j;
|
|
208
|
+
}
|
|
209
|
+
continue;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
result.push(block);
|
|
214
|
+
i++;
|
|
215
|
+
}
|
|
216
|
+
return result;
|
|
217
|
+
}
|
|
123
218
|
function buildTurns(messages) {
|
|
124
219
|
const turns = [];
|
|
125
220
|
let current = null;
|
|
126
221
|
let pendingCompaction = null;
|
|
222
|
+
let pendingRecap = null;
|
|
127
223
|
const pendingToolUses = /* @__PURE__ */ new Map();
|
|
128
224
|
const seenMessageIds = /* @__PURE__ */ new Set();
|
|
129
225
|
const subAgentMap = /* @__PURE__ */ new Map();
|
|
@@ -154,6 +250,22 @@ function buildTurns(messages) {
|
|
|
154
250
|
for (const [parentId] of subAgentMap) {
|
|
155
251
|
flushSubAgentMessages(parentId);
|
|
156
252
|
}
|
|
253
|
+
for (const block of current.contentBlocks) {
|
|
254
|
+
if (block.kind !== "hook_event") continue;
|
|
255
|
+
for (const ev of block.events) {
|
|
256
|
+
const isPostToolUse = ev.eventName === "PostToolUse" || ev.eventName.startsWith("PostToolUse:");
|
|
257
|
+
if (!isPostToolUse || !ev.toolUseId) continue;
|
|
258
|
+
const toolCall = current.toolCalls.find((tc) => tc.id === ev.toolUseId);
|
|
259
|
+
if (!toolCall) continue;
|
|
260
|
+
if (ev.updatedToolOutput) {
|
|
261
|
+
toolCall.outputReplacedByHook = true;
|
|
262
|
+
}
|
|
263
|
+
if (ev.durationMs !== void 0) {
|
|
264
|
+
toolCall.hookDurationMs = (toolCall.hookDurationMs ?? 0) + ev.durationMs;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
current.contentBlocks = groupPlanModeBlocks(current.contentBlocks);
|
|
157
269
|
turns.push(current);
|
|
158
270
|
current = null;
|
|
159
271
|
agentBlockMap.clear();
|
|
@@ -175,6 +287,20 @@ function buildTurns(messages) {
|
|
|
175
287
|
);
|
|
176
288
|
continue;
|
|
177
289
|
}
|
|
290
|
+
if (isSystemMessage(msg) && msg.subtype === "away_summary" && msg.content) {
|
|
291
|
+
pendingRecap = { content: msg.content, timestamp: msg.timestamp };
|
|
292
|
+
continue;
|
|
293
|
+
}
|
|
294
|
+
if (isQueueOperationMessage(msg)) {
|
|
295
|
+
if (current && msg.operation === "enqueue" && isVisibleQueuedPrompt(msg.content)) {
|
|
296
|
+
current.contentBlocks.push({
|
|
297
|
+
kind: "queued_prompt",
|
|
298
|
+
content: msg.content,
|
|
299
|
+
timestamp: msg.timestamp
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
continue;
|
|
303
|
+
}
|
|
178
304
|
if (isUserMessage(msg) && !msg.isMeta) {
|
|
179
305
|
const content = msg.message.content;
|
|
180
306
|
if (typeof content !== "string" && Array.isArray(content)) {
|
|
@@ -185,7 +311,7 @@ function buildTurns(messages) {
|
|
|
185
311
|
const pending = pendingToolUses.get(block.tool_use_id);
|
|
186
312
|
if (pending) {
|
|
187
313
|
pending.turn.toolCalls[pending.index].result = extractToolResultText(block.content);
|
|
188
|
-
pending.turn.toolCalls[pending.index].isError = block.is_error;
|
|
314
|
+
pending.turn.toolCalls[pending.index].isError = block.is_error === true;
|
|
189
315
|
pendingToolUses.delete(block.tool_use_id);
|
|
190
316
|
}
|
|
191
317
|
}
|
|
@@ -203,6 +329,7 @@ function buildTurns(messages) {
|
|
|
203
329
|
}
|
|
204
330
|
const agentMsg = {
|
|
205
331
|
agentId: toolUseResult.agentId,
|
|
332
|
+
parentToolUseId: toolUseId || void 0,
|
|
206
333
|
agentName: taskMeta?.name ?? null,
|
|
207
334
|
subagentType: taskMeta?.subagentType ?? null,
|
|
208
335
|
type: "assistant",
|
|
@@ -254,6 +381,10 @@ function buildTurns(messages) {
|
|
|
254
381
|
current.compactionSummary = pendingCompaction;
|
|
255
382
|
pendingCompaction = null;
|
|
256
383
|
}
|
|
384
|
+
if (pendingRecap) {
|
|
385
|
+
current.contentBlocks.push({ kind: "recap", content: pendingRecap.content, timestamp: pendingRecap.timestamp });
|
|
386
|
+
pendingRecap = null;
|
|
387
|
+
}
|
|
257
388
|
continue;
|
|
258
389
|
}
|
|
259
390
|
if (isAssistantMessage(msg)) {
|
|
@@ -303,6 +434,7 @@ function buildTurns(messages) {
|
|
|
303
434
|
if (block.type === "thinking") {
|
|
304
435
|
flushToolCalls2();
|
|
305
436
|
const tb = block;
|
|
437
|
+
if (!tb.thinking) continue;
|
|
306
438
|
current.thinking.push(tb);
|
|
307
439
|
msgThinking.push(tb);
|
|
308
440
|
} else if (block.type === "text") {
|
|
@@ -382,6 +514,7 @@ function buildTurns(messages) {
|
|
|
382
514
|
const taskMeta = taskMetaMap.get(parentId);
|
|
383
515
|
const agentMsg = {
|
|
384
516
|
agentId: data.agentId,
|
|
517
|
+
parentToolUseId: parentId || void 0,
|
|
385
518
|
agentName: taskMeta?.name ?? null,
|
|
386
519
|
subagentType: taskMeta?.subagentType ?? null,
|
|
387
520
|
type: data.message.type,
|
|
@@ -427,7 +560,7 @@ function buildTurns(messages) {
|
|
|
427
560
|
);
|
|
428
561
|
if (match) {
|
|
429
562
|
match.result = extractToolResultText(block.content);
|
|
430
|
-
match.isError = block.is_error;
|
|
563
|
+
match.isError = block.is_error === true;
|
|
431
564
|
}
|
|
432
565
|
}
|
|
433
566
|
}
|
|
@@ -446,6 +579,36 @@ function buildTurns(messages) {
|
|
|
446
579
|
}
|
|
447
580
|
continue;
|
|
448
581
|
}
|
|
582
|
+
if (isProgressMessage(msg) && msg.data.type === "hook_progress") {
|
|
583
|
+
const data = msg.data;
|
|
584
|
+
const eventName = String(data.hook_event_name ?? data.hookEvent ?? "unknown");
|
|
585
|
+
const hookSpecific = data.hookSpecificOutput;
|
|
586
|
+
const ev = {
|
|
587
|
+
eventName,
|
|
588
|
+
source: data.source !== void 0 ? String(data.source) : void 0,
|
|
589
|
+
toolName: data.tool_name !== void 0 ? String(data.tool_name) : void 0,
|
|
590
|
+
toolUseId: data.tool_use_id !== void 0 ? String(data.tool_use_id) : void 0,
|
|
591
|
+
command: data.command !== void 0 ? String(data.command) : void 0,
|
|
592
|
+
output: data.output !== void 0 ? String(data.output) : void 0,
|
|
593
|
+
stderr: data.stderr !== void 0 ? String(data.stderr) : void 0,
|
|
594
|
+
exitCode: data.exit_code !== void 0 ? Number(data.exit_code) : void 0,
|
|
595
|
+
decision: data.decision !== void 0 ? String(data.decision) : void 0,
|
|
596
|
+
durationMs: data.duration_ms !== void 0 ? Number(data.duration_ms) : void 0,
|
|
597
|
+
updatedToolOutput: hookSpecific?.updatedToolOutput !== void 0 ? String(hookSpecific.updatedToolOutput) : void 0,
|
|
598
|
+
sessionTitle: hookSpecific?.sessionTitle !== void 0 ? String(hookSpecific.sessionTitle) : void 0,
|
|
599
|
+
worktreePath: hookSpecific?.worktreePath !== void 0 ? String(hookSpecific.worktreePath) : void 0,
|
|
600
|
+
timestamp: msg.timestamp ?? ""
|
|
601
|
+
};
|
|
602
|
+
if (current) {
|
|
603
|
+
const last = current.contentBlocks[current.contentBlocks.length - 1];
|
|
604
|
+
if (last && last.kind === "hook_event") {
|
|
605
|
+
last.events.push(ev);
|
|
606
|
+
} else {
|
|
607
|
+
current.contentBlocks.push({ kind: "hook_event", events: [ev], timestamp: msg.timestamp });
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
continue;
|
|
611
|
+
}
|
|
449
612
|
if (isSystemMessage(msg) && msg.subtype === "turn_duration" && current) {
|
|
450
613
|
current.durationMs = msg.durationMs ?? null;
|
|
451
614
|
continue;
|
|
@@ -456,58 +619,84 @@ function buildTurns(messages) {
|
|
|
456
619
|
}
|
|
457
620
|
|
|
458
621
|
// src/lib/pricingTiers.ts
|
|
459
|
-
var EXTENDED_CONTEXT_THRESHOLD = 2e5;
|
|
460
622
|
var TIER_HAIKU_35 = { input: 0.8, output: 4, cacheWrite: 1, cacheRead: 0.08, webSearch: 0.01 };
|
|
461
623
|
var TIER_HAIKU_45 = { input: 1, output: 5, cacheWrite: 1.25, cacheRead: 0.1, webSearch: 0.01 };
|
|
462
|
-
var
|
|
463
|
-
var
|
|
624
|
+
var TIER_SONNET = { input: 3, output: 15, cacheWrite: 3.75, cacheRead: 0.3, webSearch: 0.01 };
|
|
625
|
+
var TIER_OPUS_MID = { input: 5, output: 25, cacheWrite: 6.25, cacheRead: 0.5, webSearch: 0.01 };
|
|
626
|
+
var TIER_FRONTIER = { input: 10, output: 50, cacheWrite: 12.5, cacheRead: 1, webSearch: 0.01 };
|
|
464
627
|
var TIER_OPUS_LEGACY = { input: 15, output: 75, cacheWrite: 18.75, cacheRead: 1.5, webSearch: 0.01 };
|
|
465
|
-
var
|
|
466
|
-
var
|
|
628
|
+
var TIER_GPT_56_SOL = { input: 5, output: 30, cacheWrite: 6.25, cacheRead: 0.5, webSearch: 0.01 };
|
|
629
|
+
var TIER_GPT_56_TERRA = { input: 2.5, output: 15, cacheWrite: 3.125, cacheRead: 0.25, webSearch: 0.01 };
|
|
630
|
+
var TIER_GPT_56_LUNA = { input: 1, output: 6, cacheWrite: 1.25, cacheRead: 0.1, webSearch: 0.01 };
|
|
631
|
+
var TIER_GPT_55 = { input: 5, output: 30, cacheWrite: 5, cacheRead: 0.5, webSearch: 0.01 };
|
|
632
|
+
var TIER_GPT_54 = { input: 2.5, output: 15, cacheWrite: 2.5, cacheRead: 0.25, webSearch: 0.01 };
|
|
633
|
+
var TIER_GPT_54_MINI = { input: 0.75, output: 4.5, cacheWrite: 0.75, cacheRead: 0.075, webSearch: 0.01 };
|
|
634
|
+
var TIER_FAST = { input: 30, output: 150, cacheWrite: 37.5, cacheRead: 3, webSearch: 0.01 };
|
|
467
635
|
var MODEL_TIERS = [
|
|
636
|
+
// OpenAI GPT models. Keep variants before their shared family prefixes.
|
|
637
|
+
{ match: "gpt-5.6-sol", tier: TIER_GPT_56_SOL },
|
|
638
|
+
{ match: "gpt-5.6-terra", tier: TIER_GPT_56_TERRA },
|
|
639
|
+
{ match: "gpt-5.6-luna", tier: TIER_GPT_56_LUNA },
|
|
640
|
+
{ match: "gpt-5.6", tier: TIER_GPT_56_SOL },
|
|
641
|
+
{ match: "gpt-5.4-mini", tier: TIER_GPT_54_MINI },
|
|
642
|
+
{ match: "gpt-5.5", tier: TIER_GPT_55 },
|
|
643
|
+
{ match: "gpt-5.4", tier: TIER_GPT_54 },
|
|
644
|
+
// Frontier (Fable 5 / Mythos 5 / Opus 4.8)
|
|
645
|
+
{ match: "fable-5", tier: TIER_FRONTIER },
|
|
646
|
+
{ match: "mythos-5", tier: TIER_FRONTIER },
|
|
647
|
+
{ match: "opus-4-8", tier: TIER_FRONTIER },
|
|
468
648
|
// Haiku
|
|
469
649
|
{ match: "haiku-4-5", tier: TIER_HAIKU_45 },
|
|
470
650
|
{ match: "haiku-4-0", tier: TIER_HAIKU_35 },
|
|
471
651
|
{ match: "3-5-haiku", tier: TIER_HAIKU_35 },
|
|
472
|
-
// Sonnet
|
|
473
|
-
{ match: "sonnet-4-
|
|
474
|
-
{ match: "sonnet-4-
|
|
475
|
-
|
|
476
|
-
{ match: "sonnet-4-0", tier:
|
|
477
|
-
{ match: "3-7-sonnet", tier:
|
|
478
|
-
{ match: "3-5-sonnet", tier:
|
|
479
|
-
// Opus
|
|
480
|
-
{ match: "opus-4-
|
|
481
|
-
{ match: "opus-4-
|
|
652
|
+
// Sonnet (3.5 through 4.6 — all one tier in current CC)
|
|
653
|
+
{ match: "sonnet-4-7", tier: TIER_SONNET },
|
|
654
|
+
{ match: "sonnet-4-6", tier: TIER_SONNET },
|
|
655
|
+
{ match: "sonnet-4-5", tier: TIER_SONNET },
|
|
656
|
+
{ match: "sonnet-4-0", tier: TIER_SONNET },
|
|
657
|
+
{ match: "3-7-sonnet", tier: TIER_SONNET },
|
|
658
|
+
{ match: "3-5-sonnet", tier: TIER_SONNET },
|
|
659
|
+
// Opus 4.5–4.7
|
|
660
|
+
{ match: "opus-4-7", tier: TIER_OPUS_MID },
|
|
661
|
+
{ match: "opus-4-6", tier: TIER_OPUS_MID },
|
|
662
|
+
{ match: "opus-4-5", tier: TIER_OPUS_MID },
|
|
482
663
|
// Opus legacy (4.0, 4.1)
|
|
483
664
|
{ match: "opus-4-1", tier: TIER_OPUS_LEGACY },
|
|
484
665
|
{ match: "opus-4-0", tier: TIER_OPUS_LEGACY }
|
|
485
666
|
];
|
|
486
|
-
var
|
|
487
|
-
var
|
|
667
|
+
var FAST_MODE_MODELS = ["opus-4-6", "opus-4-7"];
|
|
668
|
+
var DEFAULT_TIER = TIER_OPUS_MID;
|
|
488
669
|
var FAMILY_FALLBACKS = [
|
|
670
|
+
{ match: "fable", tier: TIER_FRONTIER },
|
|
671
|
+
{ match: "mythos", tier: TIER_FRONTIER },
|
|
489
672
|
{ match: "haiku", tier: TIER_HAIKU_45 },
|
|
490
|
-
{ match: "sonnet", tier:
|
|
491
|
-
{ match: "opus", tier:
|
|
673
|
+
{ match: "sonnet", tier: TIER_SONNET },
|
|
674
|
+
{ match: "opus", tier: TIER_OPUS_MID }
|
|
492
675
|
];
|
|
493
|
-
function resolveTier(model,
|
|
494
|
-
|
|
676
|
+
function resolveTier(model, speed) {
|
|
677
|
+
if (speed === "fast" && FAST_MODE_MODELS.some((m) => model.includes(m))) {
|
|
678
|
+
return TIER_FAST;
|
|
679
|
+
}
|
|
495
680
|
for (const entry of MODEL_TIERS) {
|
|
496
|
-
if (model.includes(entry.match))
|
|
497
|
-
return isExtended && entry.extendedTier ? entry.extendedTier : entry.tier;
|
|
498
|
-
}
|
|
681
|
+
if (model.includes(entry.match)) return entry.tier;
|
|
499
682
|
}
|
|
500
683
|
for (const entry of FAMILY_FALLBACKS) {
|
|
501
684
|
if (model.includes(entry.match)) return entry.tier;
|
|
502
685
|
}
|
|
503
|
-
return
|
|
686
|
+
return DEFAULT_TIER;
|
|
687
|
+
}
|
|
688
|
+
function hasKnownPricing(model) {
|
|
689
|
+
if (!model) return true;
|
|
690
|
+
const normalized = model.toLowerCase();
|
|
691
|
+
if (!normalized.startsWith("gpt-")) return true;
|
|
692
|
+
return MODEL_TIERS.some((entry) => normalized.includes(entry.match));
|
|
504
693
|
}
|
|
505
694
|
|
|
506
695
|
// src/lib/token-costs.ts
|
|
507
696
|
var CHARS_PER_TOKEN = 4;
|
|
508
697
|
function calculateCost(c) {
|
|
509
|
-
|
|
510
|
-
const p = resolveTier(c.model ?? "",
|
|
698
|
+
if (!hasKnownPricing(c.model)) return Number.NaN;
|
|
699
|
+
const p = resolveTier(c.model ?? "", c.speed);
|
|
511
700
|
return c.inputTokens / 1e6 * p.input + c.outputTokens / 1e6 * p.output + c.cacheWriteTokens / 1e6 * p.cacheWrite + c.cacheReadTokens / 1e6 * p.cacheRead + (c.webSearchRequests ?? 0) * p.webSearch;
|
|
512
701
|
}
|
|
513
702
|
function charsToTokens(chars) {
|
|
@@ -545,7 +734,8 @@ function calculateTurnCostEstimated(turn) {
|
|
|
545
734
|
inputTokens: u.input_tokens,
|
|
546
735
|
outputTokens: estimateTotalOutputTokens(turn),
|
|
547
736
|
cacheWriteTokens: u.cache_creation_input_tokens ?? 0,
|
|
548
|
-
cacheReadTokens: u.cache_read_input_tokens ?? 0
|
|
737
|
+
cacheReadTokens: u.cache_read_input_tokens ?? 0,
|
|
738
|
+
speed: u.speed
|
|
549
739
|
});
|
|
550
740
|
}
|
|
551
741
|
function calculateSubAgentCostEstimated(sa) {
|
|
@@ -556,7 +746,8 @@ function calculateSubAgentCostEstimated(sa) {
|
|
|
556
746
|
inputTokens: u.input_tokens,
|
|
557
747
|
outputTokens: estimateSubAgentOutput(sa),
|
|
558
748
|
cacheWriteTokens: u.cache_creation_input_tokens ?? 0,
|
|
559
|
-
cacheReadTokens: u.cache_read_input_tokens ?? 0
|
|
749
|
+
cacheReadTokens: u.cache_read_input_tokens ?? 0,
|
|
750
|
+
speed: u.speed
|
|
560
751
|
});
|
|
561
752
|
}
|
|
562
753
|
|
|
@@ -576,6 +767,19 @@ function addUsageToStats(stats, usage, estimatedOutput, cost) {
|
|
|
576
767
|
stats.totalCacheReadTokens += usage.cache_read_input_tokens ?? 0;
|
|
577
768
|
stats.totalCostUSD += cost;
|
|
578
769
|
}
|
|
770
|
+
function createEmptySessionStats(turnCount) {
|
|
771
|
+
return {
|
|
772
|
+
totalInputTokens: 0,
|
|
773
|
+
totalOutputTokens: 0,
|
|
774
|
+
totalCacheCreationTokens: 0,
|
|
775
|
+
totalCacheReadTokens: 0,
|
|
776
|
+
totalCostUSD: 0,
|
|
777
|
+
toolCallCounts: {},
|
|
778
|
+
errorCount: 0,
|
|
779
|
+
totalDurationMs: 0,
|
|
780
|
+
turnCount
|
|
781
|
+
};
|
|
782
|
+
}
|
|
579
783
|
function computeStats(turns) {
|
|
580
784
|
const stats = {
|
|
581
785
|
totalInputTokens: 0,
|
|
@@ -604,8 +808,271 @@ function computeStats(turns) {
|
|
|
604
808
|
return stats;
|
|
605
809
|
}
|
|
606
810
|
|
|
811
|
+
// src/lib/codex-patches.ts
|
|
812
|
+
var JS_SIMPLE_ESCAPES = {
|
|
813
|
+
"0": "\0",
|
|
814
|
+
b: "\b",
|
|
815
|
+
f: "\f",
|
|
816
|
+
n: "\n",
|
|
817
|
+
r: "\r",
|
|
818
|
+
t: " ",
|
|
819
|
+
v: "\v"
|
|
820
|
+
};
|
|
821
|
+
function readJsStringLiteral(source, startIndex) {
|
|
822
|
+
const quote = source[startIndex];
|
|
823
|
+
if (quote !== '"' && quote !== "'" && quote !== "`") return null;
|
|
824
|
+
let value = "";
|
|
825
|
+
let index = startIndex + 1;
|
|
826
|
+
while (index < source.length) {
|
|
827
|
+
const char = source[index];
|
|
828
|
+
if (char === quote) return { endIndex: index + 1, value };
|
|
829
|
+
if (char !== "\\") {
|
|
830
|
+
value += char;
|
|
831
|
+
index++;
|
|
832
|
+
continue;
|
|
833
|
+
}
|
|
834
|
+
index++;
|
|
835
|
+
if (index >= source.length) return null;
|
|
836
|
+
const escaped = source[index];
|
|
837
|
+
if (escaped in JS_SIMPLE_ESCAPES) {
|
|
838
|
+
value += JS_SIMPLE_ESCAPES[escaped];
|
|
839
|
+
index++;
|
|
840
|
+
continue;
|
|
841
|
+
}
|
|
842
|
+
if (escaped === "\n") {
|
|
843
|
+
index++;
|
|
844
|
+
continue;
|
|
845
|
+
}
|
|
846
|
+
if (escaped === "\r") {
|
|
847
|
+
index += source[index + 1] === "\n" ? 2 : 1;
|
|
848
|
+
continue;
|
|
849
|
+
}
|
|
850
|
+
if (escaped === "x" && /^[0-9a-fA-F]{2}$/.test(source.slice(index + 1, index + 3))) {
|
|
851
|
+
value += String.fromCharCode(Number.parseInt(source.slice(index + 1, index + 3), 16));
|
|
852
|
+
index += 3;
|
|
853
|
+
continue;
|
|
854
|
+
}
|
|
855
|
+
if (escaped === "u") {
|
|
856
|
+
const braced = source.slice(index + 1).match(/^\{([0-9a-fA-F]+)\}/);
|
|
857
|
+
if (braced) {
|
|
858
|
+
const codePoint = Number.parseInt(braced[1], 16);
|
|
859
|
+
if (Number.isFinite(codePoint) && codePoint <= 1114111) {
|
|
860
|
+
value += String.fromCodePoint(codePoint);
|
|
861
|
+
index += braced[0].length + 1;
|
|
862
|
+
continue;
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
const hex = source.slice(index + 1, index + 5);
|
|
866
|
+
if (/^[0-9a-fA-F]{4}$/.test(hex)) {
|
|
867
|
+
value += String.fromCharCode(Number.parseInt(hex, 16));
|
|
868
|
+
index += 5;
|
|
869
|
+
continue;
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
value += escaped;
|
|
873
|
+
index++;
|
|
874
|
+
}
|
|
875
|
+
return null;
|
|
876
|
+
}
|
|
877
|
+
function isApplyPatchText(value) {
|
|
878
|
+
return value.includes("*** Begin Patch") && value.includes("*** End Patch");
|
|
879
|
+
}
|
|
880
|
+
function extractApplyPatchInputs(execSource) {
|
|
881
|
+
if (!execSource.includes("apply_patch") || !execSource.includes("*** Begin Patch")) return [];
|
|
882
|
+
const assignedPatches = /* @__PURE__ */ new Map();
|
|
883
|
+
for (let index = 0; index < execSource.length; index++) {
|
|
884
|
+
const literal = readJsStringLiteral(execSource, index);
|
|
885
|
+
if (!literal) continue;
|
|
886
|
+
if (isApplyPatchText(literal.value)) {
|
|
887
|
+
const assignment = execSource.slice(0, index).match(/(?:^|[;\n])\s*(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*=\s*$/);
|
|
888
|
+
if (assignment) assignedPatches.set(assignment[1], literal.value);
|
|
889
|
+
}
|
|
890
|
+
index = literal.endIndex - 1;
|
|
891
|
+
}
|
|
892
|
+
const patches = [];
|
|
893
|
+
const seen = /* @__PURE__ */ new Set();
|
|
894
|
+
const applyCall = /\btools\s*\.\s*apply_patch\s*\(/g;
|
|
895
|
+
let match;
|
|
896
|
+
while ((match = applyCall.exec(execSource)) !== null) {
|
|
897
|
+
let argumentIndex = match.index + match[0].length;
|
|
898
|
+
while (/\s/.test(execSource[argumentIndex] ?? "")) argumentIndex++;
|
|
899
|
+
const literal = readJsStringLiteral(execSource, argumentIndex);
|
|
900
|
+
const identifier = literal ? null : execSource.slice(argumentIndex).match(/^([A-Za-z_$][\w$]*)/);
|
|
901
|
+
const patch = literal?.value ?? (identifier ? assignedPatches.get(identifier[1]) : void 0);
|
|
902
|
+
if (!patch || !isApplyPatchText(patch) || seen.has(patch)) continue;
|
|
903
|
+
seen.add(patch);
|
|
904
|
+
patches.push(patch);
|
|
905
|
+
}
|
|
906
|
+
return patches;
|
|
907
|
+
}
|
|
908
|
+
function resolveCodexPatchPath(filePath, cwd) {
|
|
909
|
+
if (!cwd || filePath.startsWith("/") || /^[A-Za-z]:[\\/]/.test(filePath)) return filePath;
|
|
910
|
+
return `${cwd.replace(/[\\/]+$/, "")}/${filePath.replace(/^\.\//, "")}`;
|
|
911
|
+
}
|
|
912
|
+
function parseApplyPatch(patchText, callId, timestamp, cwd = "") {
|
|
913
|
+
const toolCalls = [];
|
|
914
|
+
const filePattern = /^\*\*\*\s+(Update File|Add File|Delete File):\s*(.+)$/gm;
|
|
915
|
+
const sections = [];
|
|
916
|
+
let match;
|
|
917
|
+
while ((match = filePattern.exec(patchText)) !== null) {
|
|
918
|
+
sections.push({
|
|
919
|
+
action: match[1],
|
|
920
|
+
filePath: match[2].trim(),
|
|
921
|
+
headerIdx: match.index,
|
|
922
|
+
startIdx: match.index + match[0].length
|
|
923
|
+
});
|
|
924
|
+
}
|
|
925
|
+
for (let i = 0; i < sections.length; i++) {
|
|
926
|
+
const section = sections[i];
|
|
927
|
+
const endIdx = i + 1 < sections.length ? sections[i + 1].headerIdx : patchText.length;
|
|
928
|
+
const body = patchText.slice(section.startIdx, endIdx);
|
|
929
|
+
const movedTo = body.match(/^\*\*\*\s+Move to:\s*(.+)$/m)?.[1]?.trim();
|
|
930
|
+
const sourcePath = resolveCodexPatchPath(section.filePath, cwd);
|
|
931
|
+
const filePath = resolveCodexPatchPath(movedTo || section.filePath, cwd);
|
|
932
|
+
const hunkBodies = body.split(/^@@.*$/m).filter((s) => s.trim());
|
|
933
|
+
const oldLines = [];
|
|
934
|
+
const newLines = [];
|
|
935
|
+
for (const hunk of hunkBodies) {
|
|
936
|
+
for (const line of hunk.split("\n")) {
|
|
937
|
+
if (line.startsWith("-")) {
|
|
938
|
+
oldLines.push(line.slice(1));
|
|
939
|
+
} else if (line.startsWith("+")) {
|
|
940
|
+
newLines.push(line.slice(1));
|
|
941
|
+
} else if (line.startsWith(" ")) {
|
|
942
|
+
oldLines.push(line.slice(1));
|
|
943
|
+
newLines.push(line.slice(1));
|
|
944
|
+
}
|
|
945
|
+
}
|
|
946
|
+
}
|
|
947
|
+
const fileId = sections.length > 1 ? `${callId}:file-${i}` : callId;
|
|
948
|
+
if (section.action === "Add File") {
|
|
949
|
+
toolCalls.push({
|
|
950
|
+
id: fileId,
|
|
951
|
+
name: "Write",
|
|
952
|
+
input: { file_path: filePath, content: newLines.join("\n") },
|
|
953
|
+
result: null,
|
|
954
|
+
isError: false,
|
|
955
|
+
timestamp
|
|
956
|
+
});
|
|
957
|
+
} else if (section.action === "Delete File") {
|
|
958
|
+
toolCalls.push({
|
|
959
|
+
id: fileId,
|
|
960
|
+
name: "Edit",
|
|
961
|
+
input: { file_path: filePath, old_string: oldLines.join("\n"), new_string: "" },
|
|
962
|
+
result: null,
|
|
963
|
+
isError: false,
|
|
964
|
+
timestamp
|
|
965
|
+
});
|
|
966
|
+
} else {
|
|
967
|
+
toolCalls.push({
|
|
968
|
+
id: fileId,
|
|
969
|
+
name: "Edit",
|
|
970
|
+
input: {
|
|
971
|
+
file_path: filePath,
|
|
972
|
+
...movedTo ? { old_path: sourcePath } : {},
|
|
973
|
+
old_string: oldLines.join("\n"),
|
|
974
|
+
new_string: newLines.join("\n")
|
|
975
|
+
},
|
|
976
|
+
result: null,
|
|
977
|
+
isError: false,
|
|
978
|
+
timestamp
|
|
979
|
+
});
|
|
980
|
+
}
|
|
981
|
+
}
|
|
982
|
+
return toolCalls;
|
|
983
|
+
}
|
|
984
|
+
function parseCodexToolPatches(toolName, rawInput, callId, timestamp, cwd = "") {
|
|
985
|
+
if (toolName === "apply_patch") {
|
|
986
|
+
return parseApplyPatch(rawInput, callId, timestamp, cwd);
|
|
987
|
+
}
|
|
988
|
+
return extractApplyPatchInputs(rawInput).flatMap(
|
|
989
|
+
(patch, patchIndex) => parseApplyPatch(patch, `${callId}:patch-${patchIndex}`, timestamp, cwd)
|
|
990
|
+
);
|
|
991
|
+
}
|
|
992
|
+
function findFailedNestedPatchCallIds(output, wrapperIsError, calls) {
|
|
993
|
+
if (!wrapperIsError || !/(?:apply[_ ]patch|invalid patch|invalid context|failed to (?:apply|find)|could not apply)/i.test(output)) {
|
|
994
|
+
return /* @__PURE__ */ new Set();
|
|
995
|
+
}
|
|
996
|
+
const groups = /* @__PURE__ */ new Map();
|
|
997
|
+
for (const call of calls) {
|
|
998
|
+
const patchIndex = call.id.match(/:patch-(\d+)(?::file-\d+)?$/)?.[1];
|
|
999
|
+
if (patchIndex === void 0) continue;
|
|
1000
|
+
const index = Number(patchIndex);
|
|
1001
|
+
const group = groups.get(index) ?? [];
|
|
1002
|
+
group.push(call);
|
|
1003
|
+
groups.set(index, group);
|
|
1004
|
+
}
|
|
1005
|
+
if (groups.size === 0) return new Set(calls.map((call) => call.id));
|
|
1006
|
+
const normalizedOutput = output.toLowerCase().replaceAll("\\", "/");
|
|
1007
|
+
const mentionedGroups = /* @__PURE__ */ new Set();
|
|
1008
|
+
for (const [index, group] of groups) {
|
|
1009
|
+
if (group.some((call) => {
|
|
1010
|
+
const filePath = String(call.input.file_path ?? "").toLowerCase().replaceAll("\\", "/");
|
|
1011
|
+
if (!filePath) return false;
|
|
1012
|
+
const segments = filePath.split("/").filter(Boolean);
|
|
1013
|
+
const suffix = segments.slice(-2).join("/");
|
|
1014
|
+
return normalizedOutput.includes(filePath) || suffix.includes("/") && normalizedOutput.includes(suffix);
|
|
1015
|
+
})) {
|
|
1016
|
+
mentionedGroups.add(index);
|
|
1017
|
+
}
|
|
1018
|
+
}
|
|
1019
|
+
for (const match of normalizedOutput.matchAll(/\bpatch\s*(?:#|-)?\s*(\d+)\b/g)) {
|
|
1020
|
+
const index = Number(match[1]);
|
|
1021
|
+
if (groups.has(index)) mentionedGroups.add(index);
|
|
1022
|
+
}
|
|
1023
|
+
if (mentionedGroups.size === 0) mentionedGroups.add(Math.max(...groups.keys()));
|
|
1024
|
+
const firstFailedIndex = Math.min(...mentionedGroups);
|
|
1025
|
+
for (const index of groups.keys()) {
|
|
1026
|
+
if (index >= firstFailedIndex) mentionedGroups.add(index);
|
|
1027
|
+
}
|
|
1028
|
+
return new Set(
|
|
1029
|
+
[...mentionedGroups].flatMap((index) => groups.get(index)?.map((call) => call.id) ?? [])
|
|
1030
|
+
);
|
|
1031
|
+
}
|
|
1032
|
+
|
|
1033
|
+
// src/lib/codex-tool-normalization.ts
|
|
1034
|
+
function isObject(value) {
|
|
1035
|
+
return typeof value === "object" && value !== null;
|
|
1036
|
+
}
|
|
1037
|
+
function inferToolError(output) {
|
|
1038
|
+
if (!output) return false;
|
|
1039
|
+
const exitMatch = output.match(/Process exited with code (\d+)/);
|
|
1040
|
+
if (exitMatch) return exitMatch[1] !== "0";
|
|
1041
|
+
const withoutSuccessSummaries = output.replace(/\b0\s+(?:fail(?:ed|ures?)?|errors?)\b/gi, "").replace(/\bno\s+(?:failures?|errors?)\b/gi, "");
|
|
1042
|
+
return /\b(error|failed|exception)\b/i.test(withoutSuccessSummaries);
|
|
1043
|
+
}
|
|
1044
|
+
function normalizePlanToTodos(input) {
|
|
1045
|
+
const plan = Array.isArray(input.plan) ? input.plan : [];
|
|
1046
|
+
const todos = plan.filter((item) => isObject(item)).map((item) => ({
|
|
1047
|
+
content: typeof item.step === "string" ? item.step : "",
|
|
1048
|
+
status: typeof item.status === "string" ? item.status : "pending",
|
|
1049
|
+
activeForm: typeof item.step === "string" ? item.step : ""
|
|
1050
|
+
}));
|
|
1051
|
+
return { todos };
|
|
1052
|
+
}
|
|
1053
|
+
function extractCustomToolOutputText(output) {
|
|
1054
|
+
if (typeof output === "string") return output;
|
|
1055
|
+
if (!Array.isArray(output)) return "";
|
|
1056
|
+
return output.filter((block) => isObject(block) && typeof block.text === "string").map((block) => block.text).join("");
|
|
1057
|
+
}
|
|
1058
|
+
function parseCustomToolOutput(output) {
|
|
1059
|
+
const rawOutput = extractCustomToolOutputText(output);
|
|
1060
|
+
if (!rawOutput) return { text: "", isError: false };
|
|
1061
|
+
try {
|
|
1062
|
+
const parsed = JSON.parse(rawOutput);
|
|
1063
|
+
const text = typeof parsed.output === "string" ? parsed.output : rawOutput;
|
|
1064
|
+
const meta = isObject(parsed.metadata) ? parsed.metadata : null;
|
|
1065
|
+
const isError = meta ? meta.exit_code !== 0 && meta.exit_code !== void 0 : inferToolError(text);
|
|
1066
|
+
return { text, isError };
|
|
1067
|
+
} catch {
|
|
1068
|
+
return { text: rawOutput, isError: inferToolError(rawOutput) };
|
|
1069
|
+
}
|
|
1070
|
+
}
|
|
1071
|
+
|
|
607
1072
|
// src/lib/codex.ts
|
|
608
1073
|
var SKIP_PROMPT_PREFIXES = [
|
|
1074
|
+
"# AGENTS.md instructions for ",
|
|
1075
|
+
"<recommended_plugins>",
|
|
609
1076
|
"<environment_context>",
|
|
610
1077
|
"<permissions instructions>",
|
|
611
1078
|
"<collaboration_mode>",
|
|
@@ -621,17 +1088,120 @@ function safeParseLine(line) {
|
|
|
621
1088
|
return null;
|
|
622
1089
|
}
|
|
623
1090
|
}
|
|
624
|
-
function
|
|
1091
|
+
function isObject2(value) {
|
|
625
1092
|
return typeof value === "object" && value !== null;
|
|
626
1093
|
}
|
|
627
1094
|
function isCodexRecord(record) {
|
|
628
1095
|
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";
|
|
1096
|
+
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
1097
|
}
|
|
631
1098
|
function extractMessageText(payload, blockType) {
|
|
632
1099
|
const content = payload?.content;
|
|
633
1100
|
if (!Array.isArray(content)) return "";
|
|
634
|
-
return content.filter((block) =>
|
|
1101
|
+
return content.filter((block) => isObject2(block) && block.type === blockType && typeof block.text === "string").map((block) => block.text).join("\n").trim();
|
|
1102
|
+
}
|
|
1103
|
+
var CODEX_IMAGE_DATA_URL = /^data:(image\/(?:png|jpeg|gif|webp));base64,(.+)$/is;
|
|
1104
|
+
var CODEX_AUDIO_DATA_URL = /^data:(audio\/(?:wav|x-wav|mpeg|mp4|webm|ogg));base64,(.+)$/is;
|
|
1105
|
+
function parseImageDataUrl(value) {
|
|
1106
|
+
if (typeof value !== "string") return null;
|
|
1107
|
+
const match = CODEX_IMAGE_DATA_URL.exec(value);
|
|
1108
|
+
if (!match?.[1] || !match[2]) return null;
|
|
1109
|
+
return {
|
|
1110
|
+
type: "image",
|
|
1111
|
+
source: {
|
|
1112
|
+
type: "base64",
|
|
1113
|
+
media_type: match[1].toLowerCase(),
|
|
1114
|
+
data: match[2]
|
|
1115
|
+
}
|
|
1116
|
+
};
|
|
1117
|
+
}
|
|
1118
|
+
function parseAudioDataUrl(value) {
|
|
1119
|
+
if (typeof value !== "string") return null;
|
|
1120
|
+
const match = CODEX_AUDIO_DATA_URL.exec(value);
|
|
1121
|
+
if (!match?.[1] || !match[2]) return null;
|
|
1122
|
+
return {
|
|
1123
|
+
type: "audio",
|
|
1124
|
+
source: {
|
|
1125
|
+
type: "base64",
|
|
1126
|
+
media_type: match[1].toLowerCase(),
|
|
1127
|
+
data: match[2]
|
|
1128
|
+
}
|
|
1129
|
+
};
|
|
1130
|
+
}
|
|
1131
|
+
function extractEventMessageImages(payload) {
|
|
1132
|
+
if (!Array.isArray(payload.images)) return [];
|
|
1133
|
+
return payload.images.map(parseImageDataUrl).filter((image) => image !== null);
|
|
1134
|
+
}
|
|
1135
|
+
function extractEventMessageAudio(payload) {
|
|
1136
|
+
if (!Array.isArray(payload.audio)) return [];
|
|
1137
|
+
return payload.audio.map(parseAudioDataUrl).filter((audio) => audio !== null);
|
|
1138
|
+
}
|
|
1139
|
+
function extractResponseMessageImages(payload) {
|
|
1140
|
+
if (!Array.isArray(payload.content)) return [];
|
|
1141
|
+
return payload.content.filter((block) => isObject2(block) && block.type === "input_image").map((block) => parseImageDataUrl(block.image_url)).filter((image) => image !== null);
|
|
1142
|
+
}
|
|
1143
|
+
function extractResponseMessageAudio(payload) {
|
|
1144
|
+
if (!Array.isArray(payload.content)) return [];
|
|
1145
|
+
return payload.content.filter((block) => isObject2(block) && block.type === "input_audio").map((block) => parseAudioDataUrl(block.audio_url)).filter((audio) => audio !== null);
|
|
1146
|
+
}
|
|
1147
|
+
function buildCodexUserContent(message, images, audio = [], localImages = [], localAudio = []) {
|
|
1148
|
+
const attachmentSuffix = [
|
|
1149
|
+
...localImages.map((path) => `
|
|
1150
|
+
`),
|
|
1151
|
+
...localAudio.map((path) => `
|
|
1152
|
+
[audio attachment](<${path}>)`)
|
|
1153
|
+
].join("");
|
|
1154
|
+
const text = message + attachmentSuffix;
|
|
1155
|
+
if (images.length === 0 && audio.length === 0) return text;
|
|
1156
|
+
return [
|
|
1157
|
+
...images,
|
|
1158
|
+
...audio,
|
|
1159
|
+
...text ? [{ type: "text", text }] : []
|
|
1160
|
+
];
|
|
1161
|
+
}
|
|
1162
|
+
function normalizeFunctionName(rawName) {
|
|
1163
|
+
const leaf = rawName.split(/(?:__|[.:/])+/).filter(Boolean).at(-1) ?? rawName;
|
|
1164
|
+
const aliases = {
|
|
1165
|
+
spawnAgent: "spawn_agent",
|
|
1166
|
+
waitAgent: "wait_agent",
|
|
1167
|
+
sendMessage: "send_message",
|
|
1168
|
+
followupTask: "followup_task",
|
|
1169
|
+
listAgents: "list_agents",
|
|
1170
|
+
interruptAgent: "interrupt_agent"
|
|
1171
|
+
};
|
|
1172
|
+
const canonicalCollaborationNames = /* @__PURE__ */ new Set([
|
|
1173
|
+
"spawn_agent",
|
|
1174
|
+
"wait_agent",
|
|
1175
|
+
"send_message",
|
|
1176
|
+
"followup_task",
|
|
1177
|
+
"list_agents",
|
|
1178
|
+
"interrupt_agent"
|
|
1179
|
+
]);
|
|
1180
|
+
if (aliases[leaf]) return aliases[leaf];
|
|
1181
|
+
if (canonicalCollaborationNames.has(leaf)) return leaf;
|
|
1182
|
+
return rawName;
|
|
1183
|
+
}
|
|
1184
|
+
function extractInterAgentMessage(payload) {
|
|
1185
|
+
const rawText = extractMessageText(payload, "input_text");
|
|
1186
|
+
if (!rawText) return { messageType: null, text: "" };
|
|
1187
|
+
const headerEnd = rawText.match(/\r?\nPayload:\s*(?:\r?\n|$)/);
|
|
1188
|
+
if (!rawText.startsWith("Message Type:") || !headerEnd || headerEnd.index === void 0) {
|
|
1189
|
+
return { messageType: null, text: rawText.trim() };
|
|
1190
|
+
}
|
|
1191
|
+
const header = rawText.slice(0, headerEnd.index);
|
|
1192
|
+
const messageType = header.match(/^Message Type:\s*([^\r\n]+)/)?.[1]?.trim().toUpperCase() ?? null;
|
|
1193
|
+
const text = rawText.slice(headerEnd.index + headerEnd[0].length).trim();
|
|
1194
|
+
return { messageType, text };
|
|
1195
|
+
}
|
|
1196
|
+
function agentNameFromPath(agentPath) {
|
|
1197
|
+
const segments = agentPath.split("/").filter(Boolean);
|
|
1198
|
+
return segments.at(-1) ?? null;
|
|
1199
|
+
}
|
|
1200
|
+
function readableAgentPrompt(value) {
|
|
1201
|
+
if (typeof value !== "string") return "";
|
|
1202
|
+
const prompt = value.trim();
|
|
1203
|
+
if (/^gAAAAA[A-Za-z0-9_-]+$/.test(prompt)) return "";
|
|
1204
|
+
return prompt;
|
|
635
1205
|
}
|
|
636
1206
|
function normalizePromptText(text) {
|
|
637
1207
|
const trimmed = text.trim();
|
|
@@ -649,11 +1219,13 @@ function mergeTokenUsage2(existing, incoming) {
|
|
|
649
1219
|
};
|
|
650
1220
|
}
|
|
651
1221
|
function parseTokenUsage(value) {
|
|
652
|
-
if (!
|
|
653
|
-
const
|
|
1222
|
+
if (!isObject2(value)) return null;
|
|
1223
|
+
const reportedInputTokens = typeof value.input_tokens === "number" ? value.input_tokens : 0;
|
|
654
1224
|
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
|
|
1225
|
+
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;
|
|
1226
|
+
const nativeCachedInput = typeof value.cached_input_tokens === "number" ? Math.max(0, value.cached_input_tokens) : null;
|
|
1227
|
+
const cacheRead = nativeCachedInput ?? (typeof value.cache_read_input_tokens === "number" ? value.cache_read_input_tokens : 0);
|
|
1228
|
+
const inputTokens = nativeCachedInput === null ? reportedInputTokens : Math.max(0, reportedInputTokens - nativeCachedInput);
|
|
657
1229
|
if (inputTokens === 0 && outputTokens === 0 && cacheCreation === 0 && cacheRead === 0) return null;
|
|
658
1230
|
return {
|
|
659
1231
|
input_tokens: inputTokens,
|
|
@@ -694,7 +1266,7 @@ function appendToolCall(turn, toolCall, timestamp) {
|
|
|
694
1266
|
}
|
|
695
1267
|
function finalizeTurn(turns, current, lastTimestamp) {
|
|
696
1268
|
if (!current) return null;
|
|
697
|
-
const hasContent = current.userMessage !== null || current.assistantText.length > 0 || current.toolCalls.length > 0 || current.thinking.length > 0;
|
|
1269
|
+
const hasContent = current.userMessage !== null || current.assistantText.length > 0 || current.toolCalls.length > 0 || current.thinking.length > 0 || current.subAgentActivity.length > 0;
|
|
698
1270
|
if (!hasContent) return null;
|
|
699
1271
|
if (current.timestamp && lastTimestamp) {
|
|
700
1272
|
const start = new Date(current.timestamp).getTime();
|
|
@@ -710,17 +1282,11 @@ function parseToolInput(argumentsText) {
|
|
|
710
1282
|
if (typeof argumentsText !== "string" || !argumentsText.trim()) return {};
|
|
711
1283
|
try {
|
|
712
1284
|
const parsed = JSON.parse(argumentsText);
|
|
713
|
-
return
|
|
1285
|
+
return isObject2(parsed) ? parsed : { value: parsed };
|
|
714
1286
|
} catch {
|
|
715
1287
|
return { raw: argumentsText };
|
|
716
1288
|
}
|
|
717
1289
|
}
|
|
718
|
-
function inferToolError(output) {
|
|
719
|
-
if (!output) return false;
|
|
720
|
-
const exitMatch = output.match(/Process exited with code (\d+)/);
|
|
721
|
-
if (exitMatch) return exitMatch[1] !== "0";
|
|
722
|
-
return /\b(error|failed|exception)\b/i.test(output);
|
|
723
|
-
}
|
|
724
1290
|
function createTurn(turnId, timestamp, model) {
|
|
725
1291
|
return {
|
|
726
1292
|
id: turnId || randomTurnId("codex-turn"),
|
|
@@ -737,10 +1303,10 @@ function createTurn(turnId, timestamp, model) {
|
|
|
737
1303
|
};
|
|
738
1304
|
}
|
|
739
1305
|
function extractPromptFromRecord(record) {
|
|
740
|
-
if (record.type === "event_msg" &&
|
|
1306
|
+
if (record.type === "event_msg" && isObject2(record.payload) && record.payload.type === "user_message" && typeof record.payload.message === "string") {
|
|
741
1307
|
return normalizePromptText(record.payload.message);
|
|
742
1308
|
}
|
|
743
|
-
if (record.type === "response_item" &&
|
|
1309
|
+
if (record.type === "response_item" && isObject2(record.payload) && record.payload.type === "message" && record.payload.role === "user") {
|
|
744
1310
|
return normalizePromptText(extractMessageText(record.payload, "input_text"));
|
|
745
1311
|
}
|
|
746
1312
|
return "";
|
|
@@ -757,14 +1323,17 @@ function extractMetadataFromRecords(records) {
|
|
|
757
1323
|
let timestamp = "";
|
|
758
1324
|
let lastTimestamp = "";
|
|
759
1325
|
let turnCount = 0;
|
|
1326
|
+
let isSubagent = false;
|
|
1327
|
+
let parentSessionId = null;
|
|
1328
|
+
let agentPath = "/root";
|
|
760
1329
|
let previousPrompt = "";
|
|
761
1330
|
for (const record of records) {
|
|
762
|
-
if (!
|
|
1331
|
+
if (!isObject2(record.payload)) continue;
|
|
763
1332
|
if (record.type === "session_meta") {
|
|
764
1333
|
sessionId ||= typeof record.payload.id === "string" ? record.payload.id : "";
|
|
765
1334
|
version ||= typeof record.payload.cli_version === "string" ? record.payload.cli_version : "";
|
|
766
1335
|
cwd ||= typeof record.payload.cwd === "string" ? record.payload.cwd : "";
|
|
767
|
-
if (!branchedFrom &&
|
|
1336
|
+
if (!branchedFrom && isObject2(record.payload.branchedFrom)) {
|
|
768
1337
|
const sourceId = typeof record.payload.branchedFrom.sessionId === "string" ? record.payload.branchedFrom.sessionId : "";
|
|
769
1338
|
if (sourceId) {
|
|
770
1339
|
branchedFrom = {
|
|
@@ -773,7 +1342,18 @@ function extractMetadataFromRecords(records) {
|
|
|
773
1342
|
};
|
|
774
1343
|
}
|
|
775
1344
|
}
|
|
776
|
-
const
|
|
1345
|
+
const source = isObject2(record.payload.source) ? record.payload.source : null;
|
|
1346
|
+
if (source && isObject2(source.subagent)) {
|
|
1347
|
+
isSubagent = true;
|
|
1348
|
+
const threadSpawn = isObject2(source.subagent.thread_spawn) ? source.subagent.thread_spawn : null;
|
|
1349
|
+
if (threadSpawn && typeof threadSpawn.agent_path === "string") {
|
|
1350
|
+
agentPath = threadSpawn.agent_path;
|
|
1351
|
+
}
|
|
1352
|
+
}
|
|
1353
|
+
if (typeof record.payload.forked_from_id === "string" && record.payload.forked_from_id) {
|
|
1354
|
+
parentSessionId = record.payload.forked_from_id;
|
|
1355
|
+
}
|
|
1356
|
+
const git = isObject2(record.payload.git) ? record.payload.git : null;
|
|
777
1357
|
gitBranch ||= git && typeof git.branch === "string" ? git.branch : "";
|
|
778
1358
|
}
|
|
779
1359
|
if (record.type === "turn_context") {
|
|
@@ -798,12 +1378,16 @@ function extractMetadataFromRecords(records) {
|
|
|
798
1378
|
cwd,
|
|
799
1379
|
model,
|
|
800
1380
|
slug: "",
|
|
1381
|
+
name: "",
|
|
801
1382
|
branchedFrom,
|
|
802
1383
|
firstUserMessage,
|
|
803
1384
|
lastUserMessage,
|
|
804
1385
|
timestamp,
|
|
805
1386
|
lastTimestamp,
|
|
806
|
-
turnCount
|
|
1387
|
+
turnCount,
|
|
1388
|
+
isSubagent,
|
|
1389
|
+
parentSessionId,
|
|
1390
|
+
agentPath
|
|
807
1391
|
};
|
|
808
1392
|
}
|
|
809
1393
|
function isCodexSessionText(jsonlText) {
|
|
@@ -818,18 +1402,99 @@ function extractCodexMetadataFromLines(lines) {
|
|
|
818
1402
|
const records = lines.map(safeParseLine).filter(isCodexRecord);
|
|
819
1403
|
return extractMetadataFromRecords(records);
|
|
820
1404
|
}
|
|
821
|
-
function parseCodexSession(jsonlText) {
|
|
1405
|
+
function parseCodexSession(jsonlText, options) {
|
|
822
1406
|
const records = jsonlText.split("\n").map((line) => line.trim()).filter(Boolean).map(safeParseLine).filter(isCodexRecord);
|
|
823
1407
|
const metadata = extractMetadataFromRecords(records);
|
|
824
1408
|
const turns = [];
|
|
825
1409
|
const pendingToolCalls = /* @__PURE__ */ new Map();
|
|
1410
|
+
const webSearchCalls = /* @__PURE__ */ new Map();
|
|
1411
|
+
const patchCallIds = /* @__PURE__ */ new Map();
|
|
1412
|
+
const spawnAgentCalls = /* @__PURE__ */ new Map();
|
|
1413
|
+
const agentRegistry = /* @__PURE__ */ new Map();
|
|
1414
|
+
const agentIdByPath = /* @__PURE__ */ new Map();
|
|
1415
|
+
const agentIdBySpawnCall = /* @__PURE__ */ new Map();
|
|
1416
|
+
const agentMessages = /* @__PURE__ */ new Map();
|
|
1417
|
+
function upsertAgentMessage(agentId, targetTurn, eventTimestamp, update = {}) {
|
|
1418
|
+
const info = agentRegistry.get(agentId);
|
|
1419
|
+
let entry = agentMessages.get(agentId);
|
|
1420
|
+
if (!entry) {
|
|
1421
|
+
const agentMessage2 = {
|
|
1422
|
+
agentId,
|
|
1423
|
+
parentToolUseId: info?.parentToolCallId || void 0,
|
|
1424
|
+
agentName: info?.nickname ?? (info?.agentPath ? agentNameFromPath(info.agentPath) : null),
|
|
1425
|
+
subagentType: info?.agentType ?? null,
|
|
1426
|
+
type: "assistant",
|
|
1427
|
+
content: null,
|
|
1428
|
+
toolCalls: [],
|
|
1429
|
+
thinking: [],
|
|
1430
|
+
text: [],
|
|
1431
|
+
timestamp: info?.timestamp ?? eventTimestamp,
|
|
1432
|
+
tokenUsage: null,
|
|
1433
|
+
model: info?.model ?? null,
|
|
1434
|
+
isBackground: false,
|
|
1435
|
+
prompt: info?.message || void 0,
|
|
1436
|
+
status: update.status ?? "running"
|
|
1437
|
+
};
|
|
1438
|
+
targetTurn.subAgentActivity.push(agentMessage2);
|
|
1439
|
+
const lastBlock = targetTurn.contentBlocks[targetTurn.contentBlocks.length - 1];
|
|
1440
|
+
if (lastBlock?.kind === "sub_agent") {
|
|
1441
|
+
lastBlock.messages.push(agentMessage2);
|
|
1442
|
+
} else {
|
|
1443
|
+
targetTurn.contentBlocks.push({ kind: "sub_agent", messages: [agentMessage2], timestamp: eventTimestamp });
|
|
1444
|
+
}
|
|
1445
|
+
entry = { message: agentMessage2, turn: targetTurn };
|
|
1446
|
+
agentMessages.set(agentId, entry);
|
|
1447
|
+
}
|
|
1448
|
+
const agentMessage = entry.message;
|
|
1449
|
+
if (info) {
|
|
1450
|
+
agentMessage.parentToolUseId = info.parentToolCallId || agentMessage.parentToolUseId;
|
|
1451
|
+
agentMessage.agentName = info.nickname ?? (info.agentPath ? agentNameFromPath(info.agentPath) : agentMessage.agentName);
|
|
1452
|
+
agentMessage.subagentType = info.agentType;
|
|
1453
|
+
agentMessage.model = info.model;
|
|
1454
|
+
agentMessage.prompt = info.message || agentMessage.prompt;
|
|
1455
|
+
}
|
|
1456
|
+
if (update.text && !agentMessage.text.includes(update.text)) {
|
|
1457
|
+
agentMessage.text.push(update.text);
|
|
1458
|
+
agentMessage.content = agentMessage.text.join("\n\n");
|
|
1459
|
+
}
|
|
1460
|
+
if (update.status) {
|
|
1461
|
+
const terminalStatuses = /* @__PURE__ */ new Set(["completed", "failed", "interrupted"]);
|
|
1462
|
+
if (!(update.status === "running" && agentMessage.status && terminalStatuses.has(agentMessage.status))) {
|
|
1463
|
+
agentMessage.status = update.status;
|
|
1464
|
+
}
|
|
1465
|
+
if (terminalStatuses.has(update.status) && agentMessage.timestamp && eventTimestamp) {
|
|
1466
|
+
const startedAt = new Date(agentMessage.timestamp).getTime();
|
|
1467
|
+
const endedAt = new Date(eventTimestamp).getTime();
|
|
1468
|
+
if (Number.isFinite(startedAt) && Number.isFinite(endedAt) && endedAt >= startedAt) {
|
|
1469
|
+
agentMessage.durationMs = endedAt - startedAt;
|
|
1470
|
+
}
|
|
1471
|
+
}
|
|
1472
|
+
}
|
|
1473
|
+
return agentMessage;
|
|
1474
|
+
}
|
|
826
1475
|
let current = null;
|
|
827
1476
|
let currentTurnId = null;
|
|
828
1477
|
let currentModel = metadata.model || null;
|
|
829
1478
|
let lastTurnTimestamp = "";
|
|
1479
|
+
let pendingCompaction = null;
|
|
1480
|
+
function createActiveTurn(turnId, timestamp, model) {
|
|
1481
|
+
const turn = createTurn(turnId, timestamp, model);
|
|
1482
|
+
if (pendingCompaction) {
|
|
1483
|
+
turn.compactionSummary = pendingCompaction;
|
|
1484
|
+
pendingCompaction = null;
|
|
1485
|
+
}
|
|
1486
|
+
return turn;
|
|
1487
|
+
}
|
|
830
1488
|
for (const record of records) {
|
|
831
|
-
const payload =
|
|
1489
|
+
const payload = isObject2(record.payload) ? record.payload : void 0;
|
|
832
1490
|
const timestamp = record.timestamp ?? "";
|
|
1491
|
+
if (record.type === "compacted") {
|
|
1492
|
+
current = finalizeTurn(turns, current, lastTurnTimestamp);
|
|
1493
|
+
pendingCompaction = "Conversation compacted";
|
|
1494
|
+
lastTurnTimestamp = timestamp;
|
|
1495
|
+
continue;
|
|
1496
|
+
}
|
|
1497
|
+
if (record.type === "world_state") continue;
|
|
833
1498
|
if (record.type === "turn_context") {
|
|
834
1499
|
current = finalizeTurn(turns, current, lastTurnTimestamp);
|
|
835
1500
|
currentTurnId = typeof payload?.turn_id === "string" ? payload.turn_id : null;
|
|
@@ -841,24 +1506,93 @@ function parseCodexSession(jsonlText) {
|
|
|
841
1506
|
if (current && (current.assistantText.length > 0 || current.toolCalls.length > 0 || current.thinking.length > 0)) {
|
|
842
1507
|
current = finalizeTurn(turns, current, lastTurnTimestamp);
|
|
843
1508
|
}
|
|
844
|
-
current ??=
|
|
845
|
-
|
|
1509
|
+
current ??= createActiveTurn(currentTurnId, timestamp, currentModel);
|
|
1510
|
+
const localImages = (Array.isArray(payload.local_images) ? payload.local_images : []).filter(
|
|
1511
|
+
(p) => typeof p === "string" && p.length > 0
|
|
1512
|
+
);
|
|
1513
|
+
const localAudio = (Array.isArray(payload.local_audio) ? payload.local_audio : []).filter(
|
|
1514
|
+
(p) => typeof p === "string" && p.length > 0
|
|
1515
|
+
);
|
|
1516
|
+
current.userMessage = buildCodexUserContent(
|
|
1517
|
+
payload.message,
|
|
1518
|
+
extractEventMessageImages(payload),
|
|
1519
|
+
extractEventMessageAudio(payload),
|
|
1520
|
+
localImages,
|
|
1521
|
+
localAudio
|
|
1522
|
+
);
|
|
846
1523
|
current.timestamp = current.timestamp || timestamp;
|
|
847
1524
|
lastTurnTimestamp = timestamp;
|
|
848
1525
|
continue;
|
|
849
1526
|
}
|
|
850
|
-
current ??=
|
|
1527
|
+
current ??= createActiveTurn(currentTurnId, timestamp, currentModel);
|
|
851
1528
|
if (!current.model && currentModel) current.model = currentModel;
|
|
852
1529
|
if (!current.timestamp) current.timestamp = timestamp;
|
|
853
1530
|
if (timestamp) lastTurnTimestamp = timestamp;
|
|
854
1531
|
if (record.type === "event_msg" && payload?.type === "token_count") {
|
|
855
|
-
const info =
|
|
1532
|
+
const info = isObject2(payload.info) ? payload.info : null;
|
|
856
1533
|
const lastUsage = info ? parseTokenUsage(info.last_token_usage) : null;
|
|
857
1534
|
if (lastUsage) {
|
|
858
1535
|
current.tokenUsage = mergeTokenUsage2(current.tokenUsage, lastUsage);
|
|
859
1536
|
}
|
|
860
1537
|
continue;
|
|
861
1538
|
}
|
|
1539
|
+
if (record.type === "event_msg" && payload?.type === "web_search_end" && typeof payload.call_id === "string") {
|
|
1540
|
+
const input = {};
|
|
1541
|
+
if (typeof payload.query === "string" && payload.query) input.query = payload.query;
|
|
1542
|
+
if (isObject2(payload.action)) input.action = payload.action;
|
|
1543
|
+
const existing = webSearchCalls.get(payload.call_id);
|
|
1544
|
+
if (existing) {
|
|
1545
|
+
existing.input = { ...existing.input, ...input };
|
|
1546
|
+
existing.result = "completed";
|
|
1547
|
+
existing.isError = false;
|
|
1548
|
+
} else {
|
|
1549
|
+
const toolCall = {
|
|
1550
|
+
id: payload.call_id,
|
|
1551
|
+
name: "WebSearch",
|
|
1552
|
+
input,
|
|
1553
|
+
result: "completed",
|
|
1554
|
+
isError: false,
|
|
1555
|
+
timestamp
|
|
1556
|
+
};
|
|
1557
|
+
webSearchCalls.set(toolCall.id, toolCall);
|
|
1558
|
+
appendToolCall(current, toolCall, timestamp);
|
|
1559
|
+
}
|
|
1560
|
+
continue;
|
|
1561
|
+
}
|
|
1562
|
+
if (record.type === "event_msg" && payload?.type === "sub_agent_activity" && typeof payload.agent_thread_id === "string" && typeof payload.agent_path === "string") {
|
|
1563
|
+
const agentId = payload.agent_thread_id;
|
|
1564
|
+
const eventId = typeof payload.event_id === "string" ? payload.event_id : "";
|
|
1565
|
+
const kind = typeof payload.kind === "string" ? payload.kind : "started";
|
|
1566
|
+
const spawnInfo = eventId ? spawnAgentCalls.get(eventId) : void 0;
|
|
1567
|
+
const existingId = agentIdByPath.get(payload.agent_path);
|
|
1568
|
+
const existing = agentRegistry.get(agentId) ?? (existingId ? agentRegistry.get(existingId) : void 0);
|
|
1569
|
+
if (kind !== "started" && !existing && !spawnInfo) continue;
|
|
1570
|
+
if (existingId && existingId !== agentId && !agentMessages.has(agentId)) {
|
|
1571
|
+
const provisionalEntry = agentMessages.get(existingId);
|
|
1572
|
+
if (provisionalEntry) {
|
|
1573
|
+
provisionalEntry.message.agentId = agentId;
|
|
1574
|
+
agentMessages.delete(existingId);
|
|
1575
|
+
agentMessages.set(agentId, provisionalEntry);
|
|
1576
|
+
}
|
|
1577
|
+
agentRegistry.delete(existingId);
|
|
1578
|
+
}
|
|
1579
|
+
const info = {
|
|
1580
|
+
agentId,
|
|
1581
|
+
nickname: existing?.nickname ?? agentNameFromPath(payload.agent_path) ?? spawnInfo?.taskName ?? null,
|
|
1582
|
+
agentPath: payload.agent_path,
|
|
1583
|
+
message: existing?.message ?? spawnInfo?.message ?? "",
|
|
1584
|
+
model: existing?.model ?? spawnInfo?.model ?? null,
|
|
1585
|
+
agentType: existing?.agentType ?? spawnInfo?.agentType ?? null,
|
|
1586
|
+
timestamp: existing?.timestamp ?? (timestamp || spawnInfo?.timestamp || ""),
|
|
1587
|
+
parentToolCallId: existing?.parentToolCallId ?? eventId
|
|
1588
|
+
};
|
|
1589
|
+
agentRegistry.set(agentId, info);
|
|
1590
|
+
agentIdByPath.set(payload.agent_path, agentId);
|
|
1591
|
+
if (eventId && (kind === "started" || spawnInfo)) agentIdBySpawnCall.set(eventId, agentId);
|
|
1592
|
+
const status = kind === "interrupted" ? "interrupted" : "running";
|
|
1593
|
+
upsertAgentMessage(agentId, current, timestamp, { status });
|
|
1594
|
+
continue;
|
|
1595
|
+
}
|
|
862
1596
|
if (record.type !== "response_item" || !payload) continue;
|
|
863
1597
|
if (payload.type === "reasoning") {
|
|
864
1598
|
const summary = Array.isArray(payload.summary) ? payload.summary.filter((item) => typeof item === "string").join("\n") : "";
|
|
@@ -871,14 +1605,95 @@ function parseCodexSession(jsonlText) {
|
|
|
871
1605
|
}
|
|
872
1606
|
if (payload.type === "message" && payload.role === "user" && current.userMessage === null) {
|
|
873
1607
|
const text = normalizePromptText(extractMessageText(payload, "input_text"));
|
|
874
|
-
|
|
1608
|
+
const images = extractResponseMessageImages(payload);
|
|
1609
|
+
const audio = extractResponseMessageAudio(payload);
|
|
1610
|
+
if (text || images.length > 0 || audio.length > 0) {
|
|
1611
|
+
current.userMessage = buildCodexUserContent(text, images, audio);
|
|
1612
|
+
}
|
|
1613
|
+
continue;
|
|
1614
|
+
}
|
|
1615
|
+
if (payload.type === "web_search_call" && typeof payload.id === "string") {
|
|
1616
|
+
const input = {};
|
|
1617
|
+
if (isObject2(payload.action)) input.action = payload.action;
|
|
1618
|
+
const status = typeof payload.status === "string" ? payload.status : "completed";
|
|
1619
|
+
const existing = webSearchCalls.get(payload.id);
|
|
1620
|
+
if (existing) {
|
|
1621
|
+
existing.input = { ...existing.input, ...input };
|
|
1622
|
+
existing.result = status;
|
|
1623
|
+
existing.isError = status === "failed";
|
|
1624
|
+
} else {
|
|
1625
|
+
const toolCall = {
|
|
1626
|
+
id: payload.id,
|
|
1627
|
+
name: "WebSearch",
|
|
1628
|
+
input,
|
|
1629
|
+
result: status,
|
|
1630
|
+
isError: status === "failed",
|
|
1631
|
+
timestamp
|
|
1632
|
+
};
|
|
1633
|
+
webSearchCalls.set(toolCall.id, toolCall);
|
|
1634
|
+
appendToolCall(current, toolCall, timestamp);
|
|
1635
|
+
}
|
|
1636
|
+
continue;
|
|
1637
|
+
}
|
|
1638
|
+
if (payload.type === "agent_message" && typeof payload.author === "string") {
|
|
1639
|
+
const author = payload.author;
|
|
1640
|
+
const recipient = typeof payload.recipient === "string" ? payload.recipient : null;
|
|
1641
|
+
const interAgentMessage = extractInterAgentMessage(payload);
|
|
1642
|
+
if (interAgentMessage.messageType === "NEW_TASK") continue;
|
|
1643
|
+
const knownAgentId = agentIdByPath.get(author);
|
|
1644
|
+
const isPotentialChild = author.startsWith(`${metadata.agentPath}/`) && (recipient === null || recipient === metadata.agentPath);
|
|
1645
|
+
if (!knownAgentId && !isPotentialChild) continue;
|
|
1646
|
+
const agentId = knownAgentId ?? author;
|
|
1647
|
+
if (!knownAgentId) {
|
|
1648
|
+
agentRegistry.set(agentId, {
|
|
1649
|
+
agentId,
|
|
1650
|
+
nickname: agentNameFromPath(author),
|
|
1651
|
+
agentPath: author,
|
|
1652
|
+
message: "",
|
|
1653
|
+
model: null,
|
|
1654
|
+
agentType: null,
|
|
1655
|
+
timestamp,
|
|
1656
|
+
parentToolCallId: ""
|
|
1657
|
+
});
|
|
1658
|
+
agentIdByPath.set(author, agentId);
|
|
1659
|
+
}
|
|
1660
|
+
let status;
|
|
1661
|
+
if (interAgentMessage.messageType === "FINAL_ANSWER") status = "completed";
|
|
1662
|
+
else if (interAgentMessage.messageType === "ERROR" || interAgentMessage.messageType === "FAILED") status = "failed";
|
|
1663
|
+
else if (interAgentMessage.messageType === "INTERRUPTED") status = "interrupted";
|
|
1664
|
+
else if (interAgentMessage.messageType === "MESSAGE") status = "running";
|
|
1665
|
+
upsertAgentMessage(agentId, current, timestamp, {
|
|
1666
|
+
status,
|
|
1667
|
+
text: interAgentMessage.text || void 0
|
|
1668
|
+
});
|
|
875
1669
|
continue;
|
|
876
1670
|
}
|
|
877
1671
|
if (payload.type === "function_call" && typeof payload.call_id === "string") {
|
|
1672
|
+
const rawName = typeof payload.name === "string" ? payload.name : "tool";
|
|
1673
|
+
const functionName = normalizeFunctionName(rawName);
|
|
1674
|
+
const parsedInput = parseToolInput(payload.arguments);
|
|
1675
|
+
let name = functionName;
|
|
1676
|
+
let input = parsedInput;
|
|
1677
|
+
if (functionName === "exec_command") {
|
|
1678
|
+
name = "Bash";
|
|
1679
|
+
} else if (functionName === "update_plan") {
|
|
1680
|
+
name = "TodoWrite";
|
|
1681
|
+
input = normalizePlanToTodos(parsedInput);
|
|
1682
|
+
}
|
|
1683
|
+
if (functionName === "spawn_agent") {
|
|
1684
|
+
spawnAgentCalls.set(payload.call_id, {
|
|
1685
|
+
callId: payload.call_id,
|
|
1686
|
+
message: readableAgentPrompt(parsedInput.message),
|
|
1687
|
+
taskName: typeof parsedInput.task_name === "string" ? parsedInput.task_name : null,
|
|
1688
|
+
model: typeof parsedInput.model === "string" ? parsedInput.model : null,
|
|
1689
|
+
agentType: typeof parsedInput.agent_type === "string" ? parsedInput.agent_type : null,
|
|
1690
|
+
timestamp
|
|
1691
|
+
});
|
|
1692
|
+
}
|
|
878
1693
|
const toolCall = {
|
|
879
1694
|
id: payload.call_id,
|
|
880
|
-
name
|
|
881
|
-
input
|
|
1695
|
+
name,
|
|
1696
|
+
input,
|
|
882
1697
|
result: null,
|
|
883
1698
|
isError: false,
|
|
884
1699
|
timestamp
|
|
@@ -894,6 +1709,116 @@ function parseCodexSession(jsonlText) {
|
|
|
894
1709
|
toolCall.result = output;
|
|
895
1710
|
toolCall.isError = inferToolError(output);
|
|
896
1711
|
pendingToolCalls.delete(payload.call_id);
|
|
1712
|
+
if (toolCall.name === "spawn_agent" && output) {
|
|
1713
|
+
try {
|
|
1714
|
+
const result = JSON.parse(output);
|
|
1715
|
+
const agentId = typeof result.agent_id === "string" ? result.agent_id : agentIdBySpawnCall.get(toolCall.id) ?? "";
|
|
1716
|
+
const agentPath = typeof result.task_name === "string" ? result.task_name : null;
|
|
1717
|
+
const nickname = typeof result.nickname === "string" ? result.nickname : agentPath ? agentNameFromPath(agentPath) : null;
|
|
1718
|
+
if (agentId) {
|
|
1719
|
+
const spawnInfo = spawnAgentCalls.get(toolCall.id);
|
|
1720
|
+
const existing = agentRegistry.get(agentId);
|
|
1721
|
+
const info = {
|
|
1722
|
+
agentId,
|
|
1723
|
+
nickname: nickname ?? existing?.nickname ?? spawnInfo?.taskName ?? null,
|
|
1724
|
+
agentPath: agentPath ?? existing?.agentPath ?? null,
|
|
1725
|
+
message: existing?.message ?? spawnInfo?.message ?? "",
|
|
1726
|
+
model: existing?.model ?? spawnInfo?.model ?? null,
|
|
1727
|
+
agentType: existing?.agentType ?? spawnInfo?.agentType ?? null,
|
|
1728
|
+
timestamp: existing?.timestamp ?? spawnInfo?.timestamp ?? timestamp,
|
|
1729
|
+
parentToolCallId: existing?.parentToolCallId ?? toolCall.id
|
|
1730
|
+
};
|
|
1731
|
+
agentRegistry.set(agentId, info);
|
|
1732
|
+
agentIdBySpawnCall.set(toolCall.id, agentId);
|
|
1733
|
+
if (info.agentPath) agentIdByPath.set(info.agentPath, agentId);
|
|
1734
|
+
upsertAgentMessage(agentId, current, timestamp, { status: "running" });
|
|
1735
|
+
}
|
|
1736
|
+
} catch {
|
|
1737
|
+
}
|
|
1738
|
+
}
|
|
1739
|
+
if (toolCall.name === "wait_agent" && output && current) {
|
|
1740
|
+
try {
|
|
1741
|
+
const result = JSON.parse(output);
|
|
1742
|
+
const statusMap = isObject2(result.status) ? result.status : {};
|
|
1743
|
+
for (const [agentId, status] of Object.entries(statusMap)) {
|
|
1744
|
+
const statusValue = isObject2(status) ? status : {};
|
|
1745
|
+
const completedText = typeof statusValue.completed === "string" ? statusValue.completed : "";
|
|
1746
|
+
const failedText = typeof statusValue.failed === "string" ? statusValue.failed : typeof statusValue.error === "string" ? statusValue.error : "";
|
|
1747
|
+
const interruptedText = typeof statusValue.interrupted === "string" ? statusValue.interrupted : "";
|
|
1748
|
+
const lifecycle = completedText ? "completed" : failedText ? "failed" : interruptedText ? "interrupted" : typeof statusValue.status === "string" ? statusValue.status : "running";
|
|
1749
|
+
if (!agentRegistry.has(agentId)) {
|
|
1750
|
+
agentRegistry.set(agentId, {
|
|
1751
|
+
agentId,
|
|
1752
|
+
nickname: null,
|
|
1753
|
+
agentPath: null,
|
|
1754
|
+
message: "",
|
|
1755
|
+
model: null,
|
|
1756
|
+
agentType: null,
|
|
1757
|
+
timestamp,
|
|
1758
|
+
parentToolCallId: ""
|
|
1759
|
+
});
|
|
1760
|
+
}
|
|
1761
|
+
upsertAgentMessage(agentId, current, timestamp, {
|
|
1762
|
+
status: lifecycle,
|
|
1763
|
+
text: completedText || failedText || interruptedText || void 0
|
|
1764
|
+
});
|
|
1765
|
+
}
|
|
1766
|
+
} catch {
|
|
1767
|
+
}
|
|
1768
|
+
}
|
|
1769
|
+
continue;
|
|
1770
|
+
}
|
|
1771
|
+
if (payload.type === "custom_tool_call" && typeof payload.call_id === "string") {
|
|
1772
|
+
const name = typeof payload.name === "string" ? payload.name : "tool";
|
|
1773
|
+
const rawInput = typeof payload.input === "string" ? payload.input : "";
|
|
1774
|
+
const callId = payload.call_id;
|
|
1775
|
+
const perFileCalls = rawInput ? parseCodexToolPatches(name, rawInput, callId, timestamp, metadata.cwd) : [];
|
|
1776
|
+
if (name !== "apply_patch") {
|
|
1777
|
+
const toolCall = {
|
|
1778
|
+
id: callId,
|
|
1779
|
+
name: name === "exec_command" ? "Bash" : name,
|
|
1780
|
+
input: parseToolInput(rawInput),
|
|
1781
|
+
result: null,
|
|
1782
|
+
isError: false,
|
|
1783
|
+
timestamp
|
|
1784
|
+
};
|
|
1785
|
+
pendingToolCalls.set(toolCall.id, toolCall);
|
|
1786
|
+
appendToolCall(current, toolCall, timestamp);
|
|
1787
|
+
}
|
|
1788
|
+
for (const tc of perFileCalls) {
|
|
1789
|
+
pendingToolCalls.set(tc.id, tc);
|
|
1790
|
+
appendToolCall(current, tc, timestamp);
|
|
1791
|
+
}
|
|
1792
|
+
if (perFileCalls.length > 0) {
|
|
1793
|
+
patchCallIds.set(callId, {
|
|
1794
|
+
calls: perFileCalls,
|
|
1795
|
+
direct: name === "apply_patch"
|
|
1796
|
+
});
|
|
1797
|
+
}
|
|
1798
|
+
continue;
|
|
1799
|
+
}
|
|
1800
|
+
if (payload.type === "custom_tool_call_output" && typeof payload.call_id === "string") {
|
|
1801
|
+
const callId = payload.call_id;
|
|
1802
|
+
const { text, isError } = parseCustomToolOutput(payload.output);
|
|
1803
|
+
const patchCalls = patchCallIds.get(callId);
|
|
1804
|
+
if (patchCalls) {
|
|
1805
|
+
const failedIds = patchCalls.direct && isError ? new Set(patchCalls.calls.map((call) => call.id)) : findFailedNestedPatchCallIds(text, isError, patchCalls.calls);
|
|
1806
|
+
for (const patchCall of patchCalls.calls) {
|
|
1807
|
+
const tc = pendingToolCalls.get(patchCall.id);
|
|
1808
|
+
if (tc) {
|
|
1809
|
+
tc.result = text;
|
|
1810
|
+
tc.isError = failedIds.has(tc.id);
|
|
1811
|
+
pendingToolCalls.delete(tc.id);
|
|
1812
|
+
}
|
|
1813
|
+
}
|
|
1814
|
+
patchCallIds.delete(callId);
|
|
1815
|
+
}
|
|
1816
|
+
const toolCall = pendingToolCalls.get(callId);
|
|
1817
|
+
if (toolCall) {
|
|
1818
|
+
toolCall.result = text;
|
|
1819
|
+
toolCall.isError = isError;
|
|
1820
|
+
pendingToolCalls.delete(callId);
|
|
1821
|
+
}
|
|
897
1822
|
continue;
|
|
898
1823
|
}
|
|
899
1824
|
}
|
|
@@ -904,19 +1829,17 @@ function parseCodexSession(jsonlText) {
|
|
|
904
1829
|
gitBranch: metadata.gitBranch,
|
|
905
1830
|
cwd: metadata.cwd,
|
|
906
1831
|
slug: metadata.slug,
|
|
1832
|
+
name: "",
|
|
907
1833
|
model: metadata.model,
|
|
908
1834
|
turns,
|
|
909
|
-
stats: computeStats(turns),
|
|
910
|
-
rawMessages: records,
|
|
1835
|
+
stats: options?.skipStats ? createEmptySessionStats(turns.length) : computeStats(turns),
|
|
1836
|
+
rawMessages: options?.skipStats ? [] : records,
|
|
911
1837
|
branchedFrom: metadata.branchedFrom,
|
|
912
1838
|
agentKind: "codex"
|
|
913
1839
|
};
|
|
914
1840
|
}
|
|
915
1841
|
|
|
916
1842
|
// src/lib/parser.ts
|
|
917
|
-
function isAssistantMessage2(msg) {
|
|
918
|
-
return msg.type === "assistant";
|
|
919
|
-
}
|
|
920
1843
|
function extractTextFromContent2(content) {
|
|
921
1844
|
if (typeof content === "string") return content;
|
|
922
1845
|
return content.filter((b) => b.type === "text").map((b) => b.text).join("\n");
|
|
@@ -934,36 +1857,37 @@ function parseLines(jsonlText) {
|
|
|
934
1857
|
return messages;
|
|
935
1858
|
}
|
|
936
1859
|
function extractSessionMetadata(messages) {
|
|
937
|
-
const meta = { sessionId: "", version: "", gitBranch: "", cwd: "", slug: "", model: "", branchedFrom: void 0 };
|
|
1860
|
+
const meta = { sessionId: "", version: "", gitBranch: "", cwd: "", slug: "", name: "", model: "", branchedFrom: void 0 };
|
|
938
1861
|
for (const msg of messages) {
|
|
939
1862
|
if (msg.sessionId && !meta.sessionId) meta.sessionId = msg.sessionId;
|
|
940
1863
|
if (msg.version && !meta.version) meta.version = msg.version;
|
|
941
1864
|
if (msg.gitBranch && !meta.gitBranch) meta.gitBranch = msg.gitBranch;
|
|
942
1865
|
if (msg.cwd && !meta.cwd) meta.cwd = msg.cwd;
|
|
943
1866
|
if (msg.slug && !meta.slug) meta.slug = msg.slug;
|
|
1867
|
+
if (msg.name && !meta.name) meta.name = msg.name;
|
|
944
1868
|
if (msg.branchedFrom && !meta.branchedFrom) {
|
|
945
1869
|
meta.branchedFrom = msg.branchedFrom;
|
|
946
1870
|
}
|
|
947
|
-
if (
|
|
1871
|
+
if (isAssistantMessage(msg) && msg.message.model && !meta.model) {
|
|
948
1872
|
meta.model = msg.message.model;
|
|
949
1873
|
}
|
|
950
1874
|
if (meta.sessionId && meta.version && meta.gitBranch && meta.cwd && meta.model) break;
|
|
951
1875
|
}
|
|
952
1876
|
return meta;
|
|
953
1877
|
}
|
|
954
|
-
function parseSession(jsonlText,
|
|
1878
|
+
function parseSession(jsonlText, options) {
|
|
955
1879
|
if (isCodexSessionText(jsonlText)) {
|
|
956
|
-
return parseCodexSession(jsonlText);
|
|
1880
|
+
return parseCodexSession(jsonlText, options);
|
|
957
1881
|
}
|
|
958
1882
|
const rawMessages = parseLines(jsonlText);
|
|
959
1883
|
const metadata = extractSessionMetadata(rawMessages);
|
|
960
1884
|
const turns = buildTurns(rawMessages);
|
|
961
|
-
const stats =
|
|
1885
|
+
const stats = options?.skipStats ? createEmptySessionStats(turns.length) : computeStats(turns);
|
|
962
1886
|
return {
|
|
963
1887
|
...metadata,
|
|
964
1888
|
turns,
|
|
965
1889
|
stats,
|
|
966
|
-
rawMessages:
|
|
1890
|
+
rawMessages: options?.skipStats ? [] : rawMessages,
|
|
967
1891
|
agentKind: "claude"
|
|
968
1892
|
};
|
|
969
1893
|
}
|
|
@@ -1038,6 +1962,18 @@ var SearchIndex = class {
|
|
|
1038
1962
|
lastUpdate: this._lastUpdate
|
|
1039
1963
|
};
|
|
1040
1964
|
}
|
|
1965
|
+
/** Index tool call inputs and results under the given location prefix. */
|
|
1966
|
+
insertToolCalls(insert, sessionId, filePath, prefix, toolCalls) {
|
|
1967
|
+
for (const tc of toolCalls) {
|
|
1968
|
+
const inputStr = JSON.stringify(tc.input);
|
|
1969
|
+
if (inputStr && inputStr !== "{}") {
|
|
1970
|
+
insert.run(sessionId, filePath, `${prefix}/toolCall/${tc.id}/input`, truncContent(inputStr));
|
|
1971
|
+
}
|
|
1972
|
+
if (tc.result) {
|
|
1973
|
+
insert.run(sessionId, filePath, `${prefix}/toolCall/${tc.id}/result`, truncContent(tc.result));
|
|
1974
|
+
}
|
|
1975
|
+
}
|
|
1976
|
+
}
|
|
1041
1977
|
/**
|
|
1042
1978
|
* Insert all searchable content from a parsed session into the FTS5 index.
|
|
1043
1979
|
* Shared by both `indexFile` (single-file) and `buildFull` (batch).
|
|
@@ -1058,15 +1994,7 @@ var SearchIndex = class {
|
|
|
1058
1994
|
if (thinkingText) {
|
|
1059
1995
|
insert.run(sessionId, filePath, `${prefix}/thinking`, truncContent(thinkingText));
|
|
1060
1996
|
}
|
|
1061
|
-
|
|
1062
|
-
const inputStr = JSON.stringify(tc.input);
|
|
1063
|
-
if (inputStr && inputStr !== "{}") {
|
|
1064
|
-
insert.run(sessionId, filePath, `${prefix}/toolCall/${tc.id}/input`, truncContent(inputStr));
|
|
1065
|
-
}
|
|
1066
|
-
if (tc.result) {
|
|
1067
|
-
insert.run(sessionId, filePath, `${prefix}/toolCall/${tc.id}/result`, truncContent(tc.result));
|
|
1068
|
-
}
|
|
1069
|
-
}
|
|
1997
|
+
this.insertToolCalls(insert, sessionId, filePath, prefix, turn.toolCalls);
|
|
1070
1998
|
for (const sa of turn.subAgentActivity) {
|
|
1071
1999
|
const saPrefix = `agent/${sa.agentId}`;
|
|
1072
2000
|
const saText = sa.text.join("\n\n").trim();
|
|
@@ -1077,15 +2005,7 @@ var SearchIndex = class {
|
|
|
1077
2005
|
if (saThinking) {
|
|
1078
2006
|
insert.run(sessionId, filePath, `${saPrefix}/thinking`, truncContent(saThinking));
|
|
1079
2007
|
}
|
|
1080
|
-
|
|
1081
|
-
const inputStr = JSON.stringify(tc.input);
|
|
1082
|
-
if (inputStr && inputStr !== "{}") {
|
|
1083
|
-
insert.run(sessionId, filePath, `${saPrefix}/toolCall/${tc.id}/input`, truncContent(inputStr));
|
|
1084
|
-
}
|
|
1085
|
-
if (tc.result) {
|
|
1086
|
-
insert.run(sessionId, filePath, `${saPrefix}/toolCall/${tc.id}/result`, truncContent(tc.result));
|
|
1087
|
-
}
|
|
1088
|
-
}
|
|
2008
|
+
this.insertToolCalls(insert, sessionId, filePath, saPrefix, sa.toolCalls);
|
|
1089
2009
|
}
|
|
1090
2010
|
if (turn.compactionSummary) {
|
|
1091
2011
|
insert.run(sessionId, filePath, `${prefix}/compactionSummary`, truncContent(turn.compactionSummary));
|
|
@@ -1132,7 +2052,7 @@ var SearchIndex = class {
|
|
|
1132
2052
|
/**
|
|
1133
2053
|
* Query the FTS5 index and return structured search results.
|
|
1134
2054
|
*
|
|
1135
|
-
* - FTS5
|
|
2055
|
+
* - FTS5 unicode61 tokenizer is case-insensitive by default.
|
|
1136
2056
|
* - When `caseSensitive` is true, a post-filter checks the original query
|
|
1137
2057
|
* against the snippet text (exact case match).
|
|
1138
2058
|
* - When `maxAgeMs` is provided, only files whose mtime in `indexed_files`
|
|
@@ -1222,17 +2142,11 @@ var SearchIndex = class {
|
|
|
1222
2142
|
buildFull(projectsDir) {
|
|
1223
2143
|
this.projectsDir = projectsDir;
|
|
1224
2144
|
this.db.close();
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
(0, import_node_fs.unlinkSync)(this.dbPath + "-wal");
|
|
1231
|
-
} catch {
|
|
1232
|
-
}
|
|
1233
|
-
try {
|
|
1234
|
-
(0, import_node_fs.unlinkSync)(this.dbPath + "-shm");
|
|
1235
|
-
} catch {
|
|
2145
|
+
for (const suffix of ["", "-wal", "-shm"]) {
|
|
2146
|
+
try {
|
|
2147
|
+
(0, import_node_fs.unlinkSync)(this.dbPath + suffix);
|
|
2148
|
+
} catch {
|
|
2149
|
+
}
|
|
1236
2150
|
}
|
|
1237
2151
|
this.db = new Database(this.dbPath);
|
|
1238
2152
|
this.db.exec("PRAGMA journal_mode = WAL");
|
|
@@ -1303,9 +2217,10 @@ var SearchIndex = class {
|
|
|
1303
2217
|
/**
|
|
1304
2218
|
* Lightweight incremental update for CLI search paths.
|
|
1305
2219
|
*
|
|
1306
|
-
*
|
|
1307
|
-
*
|
|
1308
|
-
*
|
|
2220
|
+
* Still walks and stats all files via `discoverFiles`, but skips DB lookups
|
|
2221
|
+
* for files with mtime <= the high-water mark that are already indexed.
|
|
2222
|
+
* Caps re-indexing to `maxFiles` to prevent blocking on large backlogs
|
|
2223
|
+
* (run `index rebuild` for a full catch-up).
|
|
1309
2224
|
*/
|
|
1310
2225
|
updateRecent(projectsDir, maxFiles = 50) {
|
|
1311
2226
|
this.projectsDir = projectsDir;
|
|
@@ -1503,7 +2418,8 @@ var import_node_os = require("node:os");
|
|
|
1503
2418
|
var dirs = {
|
|
1504
2419
|
PROJECTS_DIR: (0, import_node_path2.join)((0, import_node_os.homedir)(), ".claude", "projects"),
|
|
1505
2420
|
TEAMS_DIR: (0, import_node_path2.join)((0, import_node_os.homedir)(), ".claude", "teams"),
|
|
1506
|
-
TASKS_DIR: (0, import_node_path2.join)((0, import_node_os.homedir)(), ".claude", "tasks")
|
|
2421
|
+
TASKS_DIR: (0, import_node_path2.join)((0, import_node_os.homedir)(), ".claude", "tasks"),
|
|
2422
|
+
CODEX_SESSIONS_DIR: (0, import_node_path2.join)((0, import_node_os.homedir)(), ".codex", "sessions")
|
|
1507
2423
|
};
|
|
1508
2424
|
var DEFAULT_DB_PATH = (0, import_node_path2.join)((0, import_node_os.homedir)(), ".claude", "cogpit-memory", "search-index.db");
|
|
1509
2425
|
|
|
@@ -1970,6 +2886,8 @@ function extractUserMessageText(userMessage) {
|
|
|
1970
2886
|
for (const block of userMessage) {
|
|
1971
2887
|
if (block.type === "text") parts.push(block.text);
|
|
1972
2888
|
else if (block.type === "image") parts.push("[image attached]");
|
|
2889
|
+
else if (block.type === "document") parts.push("[document attached]");
|
|
2890
|
+
else if (block.type === "audio") parts.push("[audio attached]");
|
|
1973
2891
|
}
|
|
1974
2892
|
return parts.length > 0 ? parts.join("\n") : null;
|
|
1975
2893
|
}
|
|
@@ -2068,6 +2986,10 @@ function mapTurnToDetail(session, turnIndex) {
|
|
|
2068
2986
|
durationMs: turn.durationMs
|
|
2069
2987
|
};
|
|
2070
2988
|
}
|
|
2989
|
+
function mapToolCall(tc) {
|
|
2990
|
+
const { result, resultTruncated } = truncateResult(tc.result);
|
|
2991
|
+
return { id: tc.id, name: tc.name, input: tc.input, result, resultTruncated, isError: tc.isError };
|
|
2992
|
+
}
|
|
2071
2993
|
function mapContentBlock(block) {
|
|
2072
2994
|
switch (block.kind) {
|
|
2073
2995
|
case "thinking": {
|
|
@@ -2079,10 +3001,7 @@ function mapContentBlock(block) {
|
|
|
2079
3001
|
case "tool_calls":
|
|
2080
3002
|
return {
|
|
2081
3003
|
kind: "tool_calls",
|
|
2082
|
-
toolCalls: block.toolCalls.map(
|
|
2083
|
-
const { result, resultTruncated } = truncateResult(tc.result);
|
|
2084
|
-
return { id: tc.id, name: tc.name, input: tc.input, result, resultTruncated, isError: tc.isError };
|
|
2085
|
-
}),
|
|
3004
|
+
toolCalls: block.toolCalls.map(mapToolCall),
|
|
2086
3005
|
timestamp: block.timestamp ?? null
|
|
2087
3006
|
};
|
|
2088
3007
|
case "sub_agent":
|
|
@@ -2092,6 +3011,21 @@ function mapContentBlock(block) {
|
|
|
2092
3011
|
agents: block.messages.map(mapSubAgentDetail),
|
|
2093
3012
|
timestamp: block.timestamp ?? null
|
|
2094
3013
|
};
|
|
3014
|
+
case "queued_prompt":
|
|
3015
|
+
return { kind: "queued_prompt", content: block.content, timestamp: block.timestamp ?? null };
|
|
3016
|
+
case "hook_event":
|
|
3017
|
+
return { kind: "hook_event", events: block.events, timestamp: block.timestamp ?? null };
|
|
3018
|
+
case "plan_mode":
|
|
3019
|
+
return {
|
|
3020
|
+
kind: "plan_mode",
|
|
3021
|
+
plan: block.plan,
|
|
3022
|
+
planFilePath: block.planFilePath ?? null,
|
|
3023
|
+
status: block.status,
|
|
3024
|
+
toolCalls: block.toolCalls.map(mapToolCall),
|
|
3025
|
+
timestamp: block.timestamp ?? null
|
|
3026
|
+
};
|
|
3027
|
+
case "recap":
|
|
3028
|
+
return { kind: "recap", content: block.content, timestamp: block.timestamp ?? null };
|
|
2095
3029
|
}
|
|
2096
3030
|
}
|
|
2097
3031
|
async function findSubagentFile(parentJsonlPath, agentId) {
|
|
@@ -2214,7 +3148,6 @@ async function getAgentOverview(sessionId, agentId) {
|
|
|
2214
3148
|
async function getAgentTurnDetail(sessionId, agentId, turnIndex) {
|
|
2215
3149
|
const jsonlPath = await findJsonlPath(sessionId);
|
|
2216
3150
|
if (!jsonlPath) return { error: "Session not found" };
|
|
2217
|
-
const content = await (0, import_promises3.readFile)(jsonlPath, "utf-8");
|
|
2218
3151
|
const subagentFile = await findSubagentFile(jsonlPath, agentId);
|
|
2219
3152
|
if (!subagentFile) return { error: "Agent not found" };
|
|
2220
3153
|
const subagentContent = await (0, import_promises3.readFile)(subagentFile.filePath, "utf-8");
|
|
@@ -2226,7 +3159,6 @@ async function getAgentTurnDetail(sessionId, agentId, turnIndex) {
|
|
|
2226
3159
|
// src/commands/sessions.ts
|
|
2227
3160
|
var import_promises5 = require("node:fs/promises");
|
|
2228
3161
|
var import_node_path6 = require("node:path");
|
|
2229
|
-
var import_node_os3 = require("node:os");
|
|
2230
3162
|
|
|
2231
3163
|
// src/lib/metadata.ts
|
|
2232
3164
|
var import_promises4 = require("node:fs/promises");
|
|
@@ -2243,6 +3175,19 @@ function deriveSessionStatus(rawMessages) {
|
|
|
2243
3175
|
if (toolName) info.toolName = toolName;
|
|
2244
3176
|
return info;
|
|
2245
3177
|
}
|
|
3178
|
+
for (let i = rawMessages.length - 1; i >= 0; i--) {
|
|
3179
|
+
const msg = rawMessages[i];
|
|
3180
|
+
if (msg.type === "queue-operation") continue;
|
|
3181
|
+
if (msg.type === "progress") {
|
|
3182
|
+
const data = msg.data;
|
|
3183
|
+
if (data?.type === "hook_progress") {
|
|
3184
|
+
const decision = data.decision ?? data.hookSpecificOutput?.permissionDecision;
|
|
3185
|
+
if (decision === "defer") return result("deferred");
|
|
3186
|
+
}
|
|
3187
|
+
break;
|
|
3188
|
+
}
|
|
3189
|
+
break;
|
|
3190
|
+
}
|
|
2246
3191
|
for (let i = rawMessages.length - 1; i >= 0; i--) {
|
|
2247
3192
|
const msg = rawMessages[i];
|
|
2248
3193
|
if (msg.type === "queue-operation") {
|
|
@@ -2277,6 +3222,13 @@ function deriveSessionStatus(rawMessages) {
|
|
|
2277
3222
|
if (isMeta) continue;
|
|
2278
3223
|
return result("processing");
|
|
2279
3224
|
}
|
|
3225
|
+
if (msg.type === "system" && msg.subtype === "terminal_reason") {
|
|
3226
|
+
const reason = msg.reason;
|
|
3227
|
+
if (reason) {
|
|
3228
|
+
return { status: "completed", terminalReason: reason, pendingQueue: Math.max(0, pendingEnqueues) };
|
|
3229
|
+
}
|
|
3230
|
+
continue;
|
|
3231
|
+
}
|
|
2280
3232
|
if (msg.type === "summary") continue;
|
|
2281
3233
|
if (msg.type === "system" && msg.subtype === "compact_boundary") continue;
|
|
2282
3234
|
}
|
|
@@ -2502,7 +3454,23 @@ async function getSessionStatus(filePath) {
|
|
|
2502
3454
|
}
|
|
2503
3455
|
|
|
2504
3456
|
// src/commands/sessions.ts
|
|
2505
|
-
|
|
3457
|
+
function toSessionSummary(file, meta, status) {
|
|
3458
|
+
return {
|
|
3459
|
+
sessionId: meta.sessionId,
|
|
3460
|
+
filePath: file.path,
|
|
3461
|
+
timestamp: meta.timestamp,
|
|
3462
|
+
model: meta.model,
|
|
3463
|
+
cwd: meta.cwd,
|
|
3464
|
+
gitBranch: meta.gitBranch,
|
|
3465
|
+
slug: meta.slug,
|
|
3466
|
+
firstMessage: meta.firstUserMessage,
|
|
3467
|
+
lastMessage: meta.lastUserMessage,
|
|
3468
|
+
turnCount: meta.turnCount,
|
|
3469
|
+
status,
|
|
3470
|
+
mtime: file.mtimeMs,
|
|
3471
|
+
source: file.path.startsWith(dirs.CODEX_SESSIONS_DIR + "/") ? "codex" : "claude"
|
|
3472
|
+
};
|
|
3473
|
+
}
|
|
2506
3474
|
async function listCodexSessionFiles(cutoff) {
|
|
2507
3475
|
const walk = async (dir, depth) => {
|
|
2508
3476
|
if (depth > 4) return [];
|
|
@@ -2529,7 +3497,7 @@ async function listCodexSessionFiles(cutoff) {
|
|
|
2529
3497
|
}
|
|
2530
3498
|
return results;
|
|
2531
3499
|
};
|
|
2532
|
-
return walk(CODEX_SESSIONS_DIR, 0);
|
|
3500
|
+
return walk(dirs.CODEX_SESSIONS_DIR, 0);
|
|
2533
3501
|
}
|
|
2534
3502
|
async function listSessions(opts = {}) {
|
|
2535
3503
|
const limit = Math.min(Math.max(1, opts.limit ?? 20), 100);
|
|
@@ -2542,29 +3510,31 @@ async function listSessions(opts = {}) {
|
|
|
2542
3510
|
return [];
|
|
2543
3511
|
}
|
|
2544
3512
|
const projectDirs = entries.filter((e) => e.isDirectory() && e.name !== "memory").map((e) => (0, import_node_path6.join)(dirs.PROJECTS_DIR, e.name));
|
|
2545
|
-
const nested = await Promise.all(
|
|
2546
|
-
|
|
2547
|
-
|
|
2548
|
-
|
|
2549
|
-
|
|
2550
|
-
|
|
2551
|
-
|
|
2552
|
-
|
|
2553
|
-
|
|
2554
|
-
|
|
2555
|
-
|
|
2556
|
-
|
|
2557
|
-
|
|
2558
|
-
|
|
2559
|
-
|
|
2560
|
-
|
|
2561
|
-
|
|
2562
|
-
|
|
2563
|
-
|
|
2564
|
-
|
|
2565
|
-
|
|
2566
|
-
|
|
2567
|
-
|
|
3513
|
+
const [nested, codexFiles] = await Promise.all([
|
|
3514
|
+
Promise.all(
|
|
3515
|
+
projectDirs.map(async (projectDir) => {
|
|
3516
|
+
try {
|
|
3517
|
+
const files = await (0, import_promises5.readdir)(projectDir);
|
|
3518
|
+
const jsonlFiles = files.filter((f) => f.endsWith(".jsonl"));
|
|
3519
|
+
const statResults = await Promise.all(
|
|
3520
|
+
jsonlFiles.map(async (f) => {
|
|
3521
|
+
const filePath = (0, import_node_path6.join)(projectDir, f);
|
|
3522
|
+
try {
|
|
3523
|
+
const s = await (0, import_promises5.stat)(filePath);
|
|
3524
|
+
return s.mtimeMs >= cutoff ? { path: filePath, mtimeMs: s.mtimeMs } : null;
|
|
3525
|
+
} catch {
|
|
3526
|
+
return null;
|
|
3527
|
+
}
|
|
3528
|
+
})
|
|
3529
|
+
);
|
|
3530
|
+
return statResults.filter((r) => r !== null);
|
|
3531
|
+
} catch {
|
|
3532
|
+
return [];
|
|
3533
|
+
}
|
|
3534
|
+
})
|
|
3535
|
+
),
|
|
3536
|
+
listCodexSessionFiles(cutoff)
|
|
3537
|
+
]);
|
|
2568
3538
|
const allFiles = [...nested.flat(), ...codexFiles];
|
|
2569
3539
|
allFiles.sort((a, b) => b.mtimeMs - a.mtimeMs);
|
|
2570
3540
|
const results = [];
|
|
@@ -2576,21 +3546,7 @@ async function listSessions(opts = {}) {
|
|
|
2576
3546
|
getSessionStatus(file.path)
|
|
2577
3547
|
]);
|
|
2578
3548
|
if (opts.cwd && meta.cwd !== opts.cwd) continue;
|
|
2579
|
-
results.push(
|
|
2580
|
-
sessionId: meta.sessionId,
|
|
2581
|
-
filePath: file.path,
|
|
2582
|
-
timestamp: meta.timestamp,
|
|
2583
|
-
model: meta.model,
|
|
2584
|
-
cwd: meta.cwd,
|
|
2585
|
-
gitBranch: meta.gitBranch,
|
|
2586
|
-
slug: meta.slug,
|
|
2587
|
-
firstMessage: meta.firstUserMessage,
|
|
2588
|
-
lastMessage: meta.lastUserMessage,
|
|
2589
|
-
turnCount: meta.turnCount,
|
|
2590
|
-
status: statusInfo.status,
|
|
2591
|
-
mtime: file.mtimeMs,
|
|
2592
|
-
source: file.path.startsWith(CODEX_SESSIONS_DIR + "/") ? "codex" : "claude"
|
|
2593
|
-
});
|
|
3549
|
+
results.push(toSessionSummary(file, meta, statusInfo.status));
|
|
2594
3550
|
} catch {
|
|
2595
3551
|
}
|
|
2596
3552
|
}
|
|
@@ -2606,18 +3562,20 @@ async function currentSession(cwd) {
|
|
|
2606
3562
|
files = [];
|
|
2607
3563
|
}
|
|
2608
3564
|
const jsonlFiles = files.filter((f) => f.endsWith(".jsonl"));
|
|
2609
|
-
const statResults = await Promise.all(
|
|
2610
|
-
|
|
2611
|
-
|
|
2612
|
-
|
|
2613
|
-
|
|
2614
|
-
|
|
2615
|
-
|
|
2616
|
-
|
|
2617
|
-
|
|
2618
|
-
|
|
2619
|
-
|
|
2620
|
-
|
|
3565
|
+
const [statResults, codexCandidates] = await Promise.all([
|
|
3566
|
+
Promise.all(
|
|
3567
|
+
jsonlFiles.map(async (f) => {
|
|
3568
|
+
const filePath = (0, import_node_path6.join)(projectDir, f);
|
|
3569
|
+
try {
|
|
3570
|
+
const s = await (0, import_promises5.stat)(filePath);
|
|
3571
|
+
return { path: filePath, mtimeMs: s.mtimeMs };
|
|
3572
|
+
} catch {
|
|
3573
|
+
return null;
|
|
3574
|
+
}
|
|
3575
|
+
})
|
|
3576
|
+
),
|
|
3577
|
+
listCodexSessionFiles(0)
|
|
3578
|
+
]);
|
|
2621
3579
|
const codexMatches = [];
|
|
2622
3580
|
for (const file of codexCandidates) {
|
|
2623
3581
|
try {
|
|
@@ -2639,21 +3597,7 @@ async function currentSession(cwd) {
|
|
|
2639
3597
|
getSessionMeta(latest.path),
|
|
2640
3598
|
getSessionStatus(latest.path)
|
|
2641
3599
|
]);
|
|
2642
|
-
return
|
|
2643
|
-
sessionId: meta.sessionId,
|
|
2644
|
-
filePath: latest.path,
|
|
2645
|
-
timestamp: meta.timestamp,
|
|
2646
|
-
model: meta.model,
|
|
2647
|
-
cwd: meta.cwd,
|
|
2648
|
-
gitBranch: meta.gitBranch,
|
|
2649
|
-
slug: meta.slug,
|
|
2650
|
-
firstMessage: meta.firstUserMessage,
|
|
2651
|
-
lastMessage: meta.lastUserMessage,
|
|
2652
|
-
turnCount: meta.turnCount,
|
|
2653
|
-
status: statusInfo.status,
|
|
2654
|
-
mtime: latest.mtimeMs,
|
|
2655
|
-
source: latest.path.startsWith(CODEX_SESSIONS_DIR + "/") ? "codex" : "claude"
|
|
2656
|
-
};
|
|
3600
|
+
return toSessionSummary(latest, meta, statusInfo.status);
|
|
2657
3601
|
}
|
|
2658
3602
|
|
|
2659
3603
|
// src/commands/index-cmd.ts
|
|
@@ -2810,27 +3754,29 @@ async function main() {
|
|
|
2810
3754
|
const cmd = parseArgs(argv);
|
|
2811
3755
|
let result;
|
|
2812
3756
|
switch (cmd.command) {
|
|
2813
|
-
case "search":
|
|
3757
|
+
case "search": {
|
|
2814
3758
|
if (!cmd.args.query) {
|
|
2815
3759
|
console.error(JSON.stringify({ error: "Usage: cogpit-memory search <query>" }));
|
|
2816
3760
|
process.exit(1);
|
|
2817
3761
|
}
|
|
2818
|
-
|
|
3762
|
+
const searchResult = await searchSessions(cmd.args.query, {
|
|
2819
3763
|
sessionId: cmd.args.session,
|
|
2820
3764
|
maxAge: cmd.args.maxAge,
|
|
2821
3765
|
limit: cmd.args.limit,
|
|
2822
3766
|
caseSensitive: cmd.args.caseSensitive
|
|
2823
3767
|
});
|
|
2824
|
-
if (
|
|
2825
|
-
const sl = cmd.args.sessionLimit ??
|
|
3768
|
+
if ("results" in searchResult && (cmd.args.sessionLimit || cmd.args.hitsPerSession)) {
|
|
3769
|
+
const sl = cmd.args.sessionLimit ?? searchResult.results.length;
|
|
2826
3770
|
const hpp = cmd.args.hitsPerSession ?? Infinity;
|
|
2827
|
-
|
|
3771
|
+
searchResult.results = searchResult.results.slice(0, sl).map((sr) => ({
|
|
2828
3772
|
...sr,
|
|
2829
3773
|
hits: sr.hits.slice(0, hpp)
|
|
2830
3774
|
}));
|
|
2831
|
-
|
|
3775
|
+
searchResult.returnedHits = searchResult.results.reduce((n, sr) => n + sr.hits.length, 0);
|
|
2832
3776
|
}
|
|
3777
|
+
result = searchResult;
|
|
2833
3778
|
break;
|
|
3779
|
+
}
|
|
2834
3780
|
case "context":
|
|
2835
3781
|
if (!cmd.args.sessionId) {
|
|
2836
3782
|
console.error(JSON.stringify({ error: "Usage: cogpit-memory context <sessionId>" }));
|