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/index.js
CHANGED
|
@@ -57,7 +57,7 @@ var Database = import_better_sqlite3.default;
|
|
|
57
57
|
var import_node_fs = require("node:fs");
|
|
58
58
|
var import_node_path = require("node:path");
|
|
59
59
|
|
|
60
|
-
// src/lib/
|
|
60
|
+
// src/lib/messageTypeGuards.ts
|
|
61
61
|
function isUserMessage(msg) {
|
|
62
62
|
return msg.type === "user";
|
|
63
63
|
}
|
|
@@ -73,9 +73,14 @@ function isSystemMessage(msg) {
|
|
|
73
73
|
function isSummaryMessage(msg) {
|
|
74
74
|
return msg.type === "summary";
|
|
75
75
|
}
|
|
76
|
+
function isQueueOperationMessage(msg) {
|
|
77
|
+
return msg.type === "queue-operation";
|
|
78
|
+
}
|
|
76
79
|
function isCompactBoundary(msg) {
|
|
77
80
|
return msg.type === "system" && msg.subtype === "compact_boundary";
|
|
78
81
|
}
|
|
82
|
+
|
|
83
|
+
// src/lib/turnBuilder.ts
|
|
79
84
|
function extractTextFromContent(content) {
|
|
80
85
|
if (typeof content === "string") return content;
|
|
81
86
|
return content.filter((b) => b.type === "text").map((b) => b.text).join("\n");
|
|
@@ -88,6 +93,11 @@ function extractToolResultText(content) {
|
|
|
88
93
|
return "";
|
|
89
94
|
}).filter(Boolean).join("\n");
|
|
90
95
|
}
|
|
96
|
+
function isVisibleQueuedPrompt(content) {
|
|
97
|
+
if (!content?.trim()) return false;
|
|
98
|
+
const trimmed = content.trimStart();
|
|
99
|
+
return !trimmed.startsWith("<task-notification>") && !trimmed.startsWith("<local-command-");
|
|
100
|
+
}
|
|
91
101
|
function mergeTokenUsage(existing, incoming) {
|
|
92
102
|
if (!existing) {
|
|
93
103
|
return { ...incoming };
|
|
@@ -96,7 +106,8 @@ function mergeTokenUsage(existing, incoming) {
|
|
|
96
106
|
input_tokens: existing.input_tokens + incoming.input_tokens,
|
|
97
107
|
output_tokens: existing.output_tokens + incoming.output_tokens,
|
|
98
108
|
cache_creation_input_tokens: (existing.cache_creation_input_tokens ?? 0) + (incoming.cache_creation_input_tokens ?? 0),
|
|
99
|
-
cache_read_input_tokens: (existing.cache_read_input_tokens ?? 0) + (incoming.cache_read_input_tokens ?? 0)
|
|
109
|
+
cache_read_input_tokens: (existing.cache_read_input_tokens ?? 0) + (incoming.cache_read_input_tokens ?? 0),
|
|
110
|
+
speed: incoming.speed ?? existing.speed
|
|
100
111
|
};
|
|
101
112
|
}
|
|
102
113
|
function buildCompactionSummary(turns, title) {
|
|
@@ -129,10 +140,95 @@ function buildCompactionSummary(turns, title) {
|
|
|
129
140
|
}
|
|
130
141
|
return parts.join("\n");
|
|
131
142
|
}
|
|
143
|
+
function groupPlanModeBlocks(blocks) {
|
|
144
|
+
const result = [];
|
|
145
|
+
let i = 0;
|
|
146
|
+
while (i < blocks.length) {
|
|
147
|
+
const block = blocks[i];
|
|
148
|
+
if (block.kind === "tool_calls") {
|
|
149
|
+
const enterIdx = block.toolCalls.findIndex((tc) => tc.name === "EnterPlanMode");
|
|
150
|
+
if (enterIdx !== -1) {
|
|
151
|
+
const enterCall = block.toolCalls[enterIdx];
|
|
152
|
+
const plan = String(enterCall.input.plan ?? "");
|
|
153
|
+
const timestamp = block.timestamp;
|
|
154
|
+
const before = block.toolCalls.slice(0, enterIdx);
|
|
155
|
+
if (before.length > 0) {
|
|
156
|
+
result.push({ kind: "tool_calls", toolCalls: before, timestamp: block.timestamp });
|
|
157
|
+
}
|
|
158
|
+
const embedded = [];
|
|
159
|
+
const afterEnterInSameBlock = block.toolCalls.slice(enterIdx + 1);
|
|
160
|
+
const exitInSameBlock = afterEnterInSameBlock.findIndex((tc) => tc.name === "ExitPlanMode");
|
|
161
|
+
if (exitInSameBlock !== -1) {
|
|
162
|
+
embedded.push(...afterEnterInSameBlock.slice(0, exitInSameBlock));
|
|
163
|
+
const exitCall = afterEnterInSameBlock[exitInSameBlock];
|
|
164
|
+
const planFilePath = exitCall.input.path ? String(exitCall.input.path) : void 0;
|
|
165
|
+
let status = "pending";
|
|
166
|
+
if (exitCall.result !== null) {
|
|
167
|
+
status = exitCall.isError ? "rejected" : "approved";
|
|
168
|
+
}
|
|
169
|
+
result.push({ kind: "plan_mode", plan, planFilePath, status, toolCalls: embedded, timestamp });
|
|
170
|
+
const after = afterEnterInSameBlock.slice(exitInSameBlock + 1);
|
|
171
|
+
if (after.length > 0) {
|
|
172
|
+
result.push({ kind: "tool_calls", toolCalls: after, timestamp: block.timestamp });
|
|
173
|
+
}
|
|
174
|
+
i++;
|
|
175
|
+
continue;
|
|
176
|
+
} else {
|
|
177
|
+
embedded.push(...afterEnterInSameBlock);
|
|
178
|
+
let exitCall;
|
|
179
|
+
let planFilePath;
|
|
180
|
+
let status = "pending";
|
|
181
|
+
let j = i + 1;
|
|
182
|
+
const passthroughBlocks = [];
|
|
183
|
+
while (j < blocks.length) {
|
|
184
|
+
const next = blocks[j];
|
|
185
|
+
if (next.kind === "tool_calls") {
|
|
186
|
+
const exitIdx = next.toolCalls.findIndex((tc) => tc.name === "ExitPlanMode");
|
|
187
|
+
if (exitIdx !== -1) {
|
|
188
|
+
embedded.push(...next.toolCalls.slice(0, exitIdx));
|
|
189
|
+
exitCall = next.toolCalls[exitIdx];
|
|
190
|
+
planFilePath = exitCall.input.path ? String(exitCall.input.path) : void 0;
|
|
191
|
+
if (exitCall.result !== null) {
|
|
192
|
+
status = exitCall.isError ? "rejected" : "approved";
|
|
193
|
+
}
|
|
194
|
+
const tail = next.toolCalls.slice(exitIdx + 1);
|
|
195
|
+
result.push({ kind: "plan_mode", plan, planFilePath, status, toolCalls: embedded, timestamp });
|
|
196
|
+
result.push(...passthroughBlocks);
|
|
197
|
+
if (tail.length > 0) {
|
|
198
|
+
result.push({ kind: "tool_calls", toolCalls: tail, timestamp: next.timestamp });
|
|
199
|
+
}
|
|
200
|
+
i = j + 1;
|
|
201
|
+
break;
|
|
202
|
+
} else {
|
|
203
|
+
embedded.push(...next.toolCalls);
|
|
204
|
+
j++;
|
|
205
|
+
}
|
|
206
|
+
} else if (next.kind === "hook_event" || next.kind === "text" || next.kind === "queued_prompt") {
|
|
207
|
+
passthroughBlocks.push(next);
|
|
208
|
+
j++;
|
|
209
|
+
} else {
|
|
210
|
+
break;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
if (!exitCall) {
|
|
214
|
+
result.push({ kind: "plan_mode", plan, planFilePath: void 0, status: "pending", toolCalls: embedded, timestamp });
|
|
215
|
+
result.push(...passthroughBlocks);
|
|
216
|
+
i = j;
|
|
217
|
+
}
|
|
218
|
+
continue;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
result.push(block);
|
|
223
|
+
i++;
|
|
224
|
+
}
|
|
225
|
+
return result;
|
|
226
|
+
}
|
|
132
227
|
function buildTurns(messages) {
|
|
133
228
|
const turns = [];
|
|
134
229
|
let current = null;
|
|
135
230
|
let pendingCompaction = null;
|
|
231
|
+
let pendingRecap = null;
|
|
136
232
|
const pendingToolUses = /* @__PURE__ */ new Map();
|
|
137
233
|
const seenMessageIds = /* @__PURE__ */ new Set();
|
|
138
234
|
const subAgentMap = /* @__PURE__ */ new Map();
|
|
@@ -163,6 +259,22 @@ function buildTurns(messages) {
|
|
|
163
259
|
for (const [parentId] of subAgentMap) {
|
|
164
260
|
flushSubAgentMessages(parentId);
|
|
165
261
|
}
|
|
262
|
+
for (const block of current.contentBlocks) {
|
|
263
|
+
if (block.kind !== "hook_event") continue;
|
|
264
|
+
for (const ev of block.events) {
|
|
265
|
+
const isPostToolUse = ev.eventName === "PostToolUse" || ev.eventName.startsWith("PostToolUse:");
|
|
266
|
+
if (!isPostToolUse || !ev.toolUseId) continue;
|
|
267
|
+
const toolCall = current.toolCalls.find((tc) => tc.id === ev.toolUseId);
|
|
268
|
+
if (!toolCall) continue;
|
|
269
|
+
if (ev.updatedToolOutput) {
|
|
270
|
+
toolCall.outputReplacedByHook = true;
|
|
271
|
+
}
|
|
272
|
+
if (ev.durationMs !== void 0) {
|
|
273
|
+
toolCall.hookDurationMs = (toolCall.hookDurationMs ?? 0) + ev.durationMs;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
current.contentBlocks = groupPlanModeBlocks(current.contentBlocks);
|
|
166
278
|
turns.push(current);
|
|
167
279
|
current = null;
|
|
168
280
|
agentBlockMap.clear();
|
|
@@ -184,6 +296,20 @@ function buildTurns(messages) {
|
|
|
184
296
|
);
|
|
185
297
|
continue;
|
|
186
298
|
}
|
|
299
|
+
if (isSystemMessage(msg) && msg.subtype === "away_summary" && msg.content) {
|
|
300
|
+
pendingRecap = { content: msg.content, timestamp: msg.timestamp };
|
|
301
|
+
continue;
|
|
302
|
+
}
|
|
303
|
+
if (isQueueOperationMessage(msg)) {
|
|
304
|
+
if (current && msg.operation === "enqueue" && isVisibleQueuedPrompt(msg.content)) {
|
|
305
|
+
current.contentBlocks.push({
|
|
306
|
+
kind: "queued_prompt",
|
|
307
|
+
content: msg.content,
|
|
308
|
+
timestamp: msg.timestamp
|
|
309
|
+
});
|
|
310
|
+
}
|
|
311
|
+
continue;
|
|
312
|
+
}
|
|
187
313
|
if (isUserMessage(msg) && !msg.isMeta) {
|
|
188
314
|
const content = msg.message.content;
|
|
189
315
|
if (typeof content !== "string" && Array.isArray(content)) {
|
|
@@ -194,7 +320,7 @@ function buildTurns(messages) {
|
|
|
194
320
|
const pending = pendingToolUses.get(block.tool_use_id);
|
|
195
321
|
if (pending) {
|
|
196
322
|
pending.turn.toolCalls[pending.index].result = extractToolResultText(block.content);
|
|
197
|
-
pending.turn.toolCalls[pending.index].isError = block.is_error;
|
|
323
|
+
pending.turn.toolCalls[pending.index].isError = block.is_error === true;
|
|
198
324
|
pendingToolUses.delete(block.tool_use_id);
|
|
199
325
|
}
|
|
200
326
|
}
|
|
@@ -212,6 +338,7 @@ function buildTurns(messages) {
|
|
|
212
338
|
}
|
|
213
339
|
const agentMsg = {
|
|
214
340
|
agentId: toolUseResult.agentId,
|
|
341
|
+
parentToolUseId: toolUseId || void 0,
|
|
215
342
|
agentName: taskMeta?.name ?? null,
|
|
216
343
|
subagentType: taskMeta?.subagentType ?? null,
|
|
217
344
|
type: "assistant",
|
|
@@ -263,6 +390,10 @@ function buildTurns(messages) {
|
|
|
263
390
|
current.compactionSummary = pendingCompaction;
|
|
264
391
|
pendingCompaction = null;
|
|
265
392
|
}
|
|
393
|
+
if (pendingRecap) {
|
|
394
|
+
current.contentBlocks.push({ kind: "recap", content: pendingRecap.content, timestamp: pendingRecap.timestamp });
|
|
395
|
+
pendingRecap = null;
|
|
396
|
+
}
|
|
266
397
|
continue;
|
|
267
398
|
}
|
|
268
399
|
if (isAssistantMessage(msg)) {
|
|
@@ -312,6 +443,7 @@ function buildTurns(messages) {
|
|
|
312
443
|
if (block.type === "thinking") {
|
|
313
444
|
flushToolCalls2();
|
|
314
445
|
const tb = block;
|
|
446
|
+
if (!tb.thinking) continue;
|
|
315
447
|
current.thinking.push(tb);
|
|
316
448
|
msgThinking.push(tb);
|
|
317
449
|
} else if (block.type === "text") {
|
|
@@ -391,6 +523,7 @@ function buildTurns(messages) {
|
|
|
391
523
|
const taskMeta = taskMetaMap.get(parentId);
|
|
392
524
|
const agentMsg = {
|
|
393
525
|
agentId: data.agentId,
|
|
526
|
+
parentToolUseId: parentId || void 0,
|
|
394
527
|
agentName: taskMeta?.name ?? null,
|
|
395
528
|
subagentType: taskMeta?.subagentType ?? null,
|
|
396
529
|
type: data.message.type,
|
|
@@ -436,7 +569,7 @@ function buildTurns(messages) {
|
|
|
436
569
|
);
|
|
437
570
|
if (match) {
|
|
438
571
|
match.result = extractToolResultText(block.content);
|
|
439
|
-
match.isError = block.is_error;
|
|
572
|
+
match.isError = block.is_error === true;
|
|
440
573
|
}
|
|
441
574
|
}
|
|
442
575
|
}
|
|
@@ -455,6 +588,36 @@ function buildTurns(messages) {
|
|
|
455
588
|
}
|
|
456
589
|
continue;
|
|
457
590
|
}
|
|
591
|
+
if (isProgressMessage(msg) && msg.data.type === "hook_progress") {
|
|
592
|
+
const data = msg.data;
|
|
593
|
+
const eventName = String(data.hook_event_name ?? data.hookEvent ?? "unknown");
|
|
594
|
+
const hookSpecific = data.hookSpecificOutput;
|
|
595
|
+
const ev = {
|
|
596
|
+
eventName,
|
|
597
|
+
source: data.source !== void 0 ? String(data.source) : void 0,
|
|
598
|
+
toolName: data.tool_name !== void 0 ? String(data.tool_name) : void 0,
|
|
599
|
+
toolUseId: data.tool_use_id !== void 0 ? String(data.tool_use_id) : void 0,
|
|
600
|
+
command: data.command !== void 0 ? String(data.command) : void 0,
|
|
601
|
+
output: data.output !== void 0 ? String(data.output) : void 0,
|
|
602
|
+
stderr: data.stderr !== void 0 ? String(data.stderr) : void 0,
|
|
603
|
+
exitCode: data.exit_code !== void 0 ? Number(data.exit_code) : void 0,
|
|
604
|
+
decision: data.decision !== void 0 ? String(data.decision) : void 0,
|
|
605
|
+
durationMs: data.duration_ms !== void 0 ? Number(data.duration_ms) : void 0,
|
|
606
|
+
updatedToolOutput: hookSpecific?.updatedToolOutput !== void 0 ? String(hookSpecific.updatedToolOutput) : void 0,
|
|
607
|
+
sessionTitle: hookSpecific?.sessionTitle !== void 0 ? String(hookSpecific.sessionTitle) : void 0,
|
|
608
|
+
worktreePath: hookSpecific?.worktreePath !== void 0 ? String(hookSpecific.worktreePath) : void 0,
|
|
609
|
+
timestamp: msg.timestamp ?? ""
|
|
610
|
+
};
|
|
611
|
+
if (current) {
|
|
612
|
+
const last = current.contentBlocks[current.contentBlocks.length - 1];
|
|
613
|
+
if (last && last.kind === "hook_event") {
|
|
614
|
+
last.events.push(ev);
|
|
615
|
+
} else {
|
|
616
|
+
current.contentBlocks.push({ kind: "hook_event", events: [ev], timestamp: msg.timestamp });
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
continue;
|
|
620
|
+
}
|
|
458
621
|
if (isSystemMessage(msg) && msg.subtype === "turn_duration" && current) {
|
|
459
622
|
current.durationMs = msg.durationMs ?? null;
|
|
460
623
|
continue;
|
|
@@ -465,58 +628,84 @@ function buildTurns(messages) {
|
|
|
465
628
|
}
|
|
466
629
|
|
|
467
630
|
// src/lib/pricingTiers.ts
|
|
468
|
-
var EXTENDED_CONTEXT_THRESHOLD = 2e5;
|
|
469
631
|
var TIER_HAIKU_35 = { input: 0.8, output: 4, cacheWrite: 1, cacheRead: 0.08, webSearch: 0.01 };
|
|
470
632
|
var TIER_HAIKU_45 = { input: 1, output: 5, cacheWrite: 1.25, cacheRead: 0.1, webSearch: 0.01 };
|
|
471
|
-
var
|
|
472
|
-
var
|
|
633
|
+
var TIER_SONNET = { input: 3, output: 15, cacheWrite: 3.75, cacheRead: 0.3, webSearch: 0.01 };
|
|
634
|
+
var TIER_OPUS_MID = { input: 5, output: 25, cacheWrite: 6.25, cacheRead: 0.5, webSearch: 0.01 };
|
|
635
|
+
var TIER_FRONTIER = { input: 10, output: 50, cacheWrite: 12.5, cacheRead: 1, webSearch: 0.01 };
|
|
473
636
|
var TIER_OPUS_LEGACY = { input: 15, output: 75, cacheWrite: 18.75, cacheRead: 1.5, webSearch: 0.01 };
|
|
474
|
-
var
|
|
475
|
-
var
|
|
637
|
+
var TIER_GPT_56_SOL = { input: 5, output: 30, cacheWrite: 6.25, cacheRead: 0.5, webSearch: 0.01 };
|
|
638
|
+
var TIER_GPT_56_TERRA = { input: 2.5, output: 15, cacheWrite: 3.125, cacheRead: 0.25, webSearch: 0.01 };
|
|
639
|
+
var TIER_GPT_56_LUNA = { input: 1, output: 6, cacheWrite: 1.25, cacheRead: 0.1, webSearch: 0.01 };
|
|
640
|
+
var TIER_GPT_55 = { input: 5, output: 30, cacheWrite: 5, cacheRead: 0.5, webSearch: 0.01 };
|
|
641
|
+
var TIER_GPT_54 = { input: 2.5, output: 15, cacheWrite: 2.5, cacheRead: 0.25, webSearch: 0.01 };
|
|
642
|
+
var TIER_GPT_54_MINI = { input: 0.75, output: 4.5, cacheWrite: 0.75, cacheRead: 0.075, webSearch: 0.01 };
|
|
643
|
+
var TIER_FAST = { input: 30, output: 150, cacheWrite: 37.5, cacheRead: 3, webSearch: 0.01 };
|
|
476
644
|
var MODEL_TIERS = [
|
|
645
|
+
// OpenAI GPT models. Keep variants before their shared family prefixes.
|
|
646
|
+
{ match: "gpt-5.6-sol", tier: TIER_GPT_56_SOL },
|
|
647
|
+
{ match: "gpt-5.6-terra", tier: TIER_GPT_56_TERRA },
|
|
648
|
+
{ match: "gpt-5.6-luna", tier: TIER_GPT_56_LUNA },
|
|
649
|
+
{ match: "gpt-5.6", tier: TIER_GPT_56_SOL },
|
|
650
|
+
{ match: "gpt-5.4-mini", tier: TIER_GPT_54_MINI },
|
|
651
|
+
{ match: "gpt-5.5", tier: TIER_GPT_55 },
|
|
652
|
+
{ match: "gpt-5.4", tier: TIER_GPT_54 },
|
|
653
|
+
// Frontier (Fable 5 / Mythos 5 / Opus 4.8)
|
|
654
|
+
{ match: "fable-5", tier: TIER_FRONTIER },
|
|
655
|
+
{ match: "mythos-5", tier: TIER_FRONTIER },
|
|
656
|
+
{ match: "opus-4-8", tier: TIER_FRONTIER },
|
|
477
657
|
// Haiku
|
|
478
658
|
{ match: "haiku-4-5", tier: TIER_HAIKU_45 },
|
|
479
659
|
{ match: "haiku-4-0", tier: TIER_HAIKU_35 },
|
|
480
660
|
{ match: "3-5-haiku", tier: TIER_HAIKU_35 },
|
|
481
|
-
// Sonnet
|
|
482
|
-
{ match: "sonnet-4-
|
|
483
|
-
{ match: "sonnet-4-
|
|
484
|
-
|
|
485
|
-
{ match: "sonnet-4-0", tier:
|
|
486
|
-
{ match: "3-7-sonnet", tier:
|
|
487
|
-
{ match: "3-5-sonnet", tier:
|
|
488
|
-
// Opus
|
|
489
|
-
{ match: "opus-4-
|
|
490
|
-
{ match: "opus-4-
|
|
661
|
+
// Sonnet (3.5 through 4.6 — all one tier in current CC)
|
|
662
|
+
{ match: "sonnet-4-7", tier: TIER_SONNET },
|
|
663
|
+
{ match: "sonnet-4-6", tier: TIER_SONNET },
|
|
664
|
+
{ match: "sonnet-4-5", tier: TIER_SONNET },
|
|
665
|
+
{ match: "sonnet-4-0", tier: TIER_SONNET },
|
|
666
|
+
{ match: "3-7-sonnet", tier: TIER_SONNET },
|
|
667
|
+
{ match: "3-5-sonnet", tier: TIER_SONNET },
|
|
668
|
+
// Opus 4.5–4.7
|
|
669
|
+
{ match: "opus-4-7", tier: TIER_OPUS_MID },
|
|
670
|
+
{ match: "opus-4-6", tier: TIER_OPUS_MID },
|
|
671
|
+
{ match: "opus-4-5", tier: TIER_OPUS_MID },
|
|
491
672
|
// Opus legacy (4.0, 4.1)
|
|
492
673
|
{ match: "opus-4-1", tier: TIER_OPUS_LEGACY },
|
|
493
674
|
{ match: "opus-4-0", tier: TIER_OPUS_LEGACY }
|
|
494
675
|
];
|
|
495
|
-
var
|
|
496
|
-
var
|
|
676
|
+
var FAST_MODE_MODELS = ["opus-4-6", "opus-4-7"];
|
|
677
|
+
var DEFAULT_TIER = TIER_OPUS_MID;
|
|
497
678
|
var FAMILY_FALLBACKS = [
|
|
679
|
+
{ match: "fable", tier: TIER_FRONTIER },
|
|
680
|
+
{ match: "mythos", tier: TIER_FRONTIER },
|
|
498
681
|
{ match: "haiku", tier: TIER_HAIKU_45 },
|
|
499
|
-
{ match: "sonnet", tier:
|
|
500
|
-
{ match: "opus", tier:
|
|
682
|
+
{ match: "sonnet", tier: TIER_SONNET },
|
|
683
|
+
{ match: "opus", tier: TIER_OPUS_MID }
|
|
501
684
|
];
|
|
502
|
-
function resolveTier(model,
|
|
503
|
-
|
|
685
|
+
function resolveTier(model, speed) {
|
|
686
|
+
if (speed === "fast" && FAST_MODE_MODELS.some((m) => model.includes(m))) {
|
|
687
|
+
return TIER_FAST;
|
|
688
|
+
}
|
|
504
689
|
for (const entry of MODEL_TIERS) {
|
|
505
|
-
if (model.includes(entry.match))
|
|
506
|
-
return isExtended && entry.extendedTier ? entry.extendedTier : entry.tier;
|
|
507
|
-
}
|
|
690
|
+
if (model.includes(entry.match)) return entry.tier;
|
|
508
691
|
}
|
|
509
692
|
for (const entry of FAMILY_FALLBACKS) {
|
|
510
693
|
if (model.includes(entry.match)) return entry.tier;
|
|
511
694
|
}
|
|
512
|
-
return
|
|
695
|
+
return DEFAULT_TIER;
|
|
696
|
+
}
|
|
697
|
+
function hasKnownPricing(model) {
|
|
698
|
+
if (!model) return true;
|
|
699
|
+
const normalized = model.toLowerCase();
|
|
700
|
+
if (!normalized.startsWith("gpt-")) return true;
|
|
701
|
+
return MODEL_TIERS.some((entry) => normalized.includes(entry.match));
|
|
513
702
|
}
|
|
514
703
|
|
|
515
704
|
// src/lib/token-costs.ts
|
|
516
705
|
var CHARS_PER_TOKEN = 4;
|
|
517
706
|
function calculateCost(c) {
|
|
518
|
-
|
|
519
|
-
const p = resolveTier(c.model ?? "",
|
|
707
|
+
if (!hasKnownPricing(c.model)) return Number.NaN;
|
|
708
|
+
const p = resolveTier(c.model ?? "", c.speed);
|
|
520
709
|
return c.inputTokens / 1e6 * p.input + c.outputTokens / 1e6 * p.output + c.cacheWriteTokens / 1e6 * p.cacheWrite + c.cacheReadTokens / 1e6 * p.cacheRead + (c.webSearchRequests ?? 0) * p.webSearch;
|
|
521
710
|
}
|
|
522
711
|
function charsToTokens(chars) {
|
|
@@ -554,7 +743,8 @@ function calculateTurnCostEstimated(turn) {
|
|
|
554
743
|
inputTokens: u.input_tokens,
|
|
555
744
|
outputTokens: estimateTotalOutputTokens(turn),
|
|
556
745
|
cacheWriteTokens: u.cache_creation_input_tokens ?? 0,
|
|
557
|
-
cacheReadTokens: u.cache_read_input_tokens ?? 0
|
|
746
|
+
cacheReadTokens: u.cache_read_input_tokens ?? 0,
|
|
747
|
+
speed: u.speed
|
|
558
748
|
});
|
|
559
749
|
}
|
|
560
750
|
function calculateSubAgentCostEstimated(sa) {
|
|
@@ -565,7 +755,8 @@ function calculateSubAgentCostEstimated(sa) {
|
|
|
565
755
|
inputTokens: u.input_tokens,
|
|
566
756
|
outputTokens: estimateSubAgentOutput(sa),
|
|
567
757
|
cacheWriteTokens: u.cache_creation_input_tokens ?? 0,
|
|
568
|
-
cacheReadTokens: u.cache_read_input_tokens ?? 0
|
|
758
|
+
cacheReadTokens: u.cache_read_input_tokens ?? 0,
|
|
759
|
+
speed: u.speed
|
|
569
760
|
});
|
|
570
761
|
}
|
|
571
762
|
|
|
@@ -585,6 +776,19 @@ function addUsageToStats(stats, usage, estimatedOutput, cost) {
|
|
|
585
776
|
stats.totalCacheReadTokens += usage.cache_read_input_tokens ?? 0;
|
|
586
777
|
stats.totalCostUSD += cost;
|
|
587
778
|
}
|
|
779
|
+
function createEmptySessionStats(turnCount) {
|
|
780
|
+
return {
|
|
781
|
+
totalInputTokens: 0,
|
|
782
|
+
totalOutputTokens: 0,
|
|
783
|
+
totalCacheCreationTokens: 0,
|
|
784
|
+
totalCacheReadTokens: 0,
|
|
785
|
+
totalCostUSD: 0,
|
|
786
|
+
toolCallCounts: {},
|
|
787
|
+
errorCount: 0,
|
|
788
|
+
totalDurationMs: 0,
|
|
789
|
+
turnCount
|
|
790
|
+
};
|
|
791
|
+
}
|
|
588
792
|
function computeStats(turns) {
|
|
589
793
|
const stats = {
|
|
590
794
|
totalInputTokens: 0,
|
|
@@ -613,8 +817,271 @@ function computeStats(turns) {
|
|
|
613
817
|
return stats;
|
|
614
818
|
}
|
|
615
819
|
|
|
820
|
+
// src/lib/codex-patches.ts
|
|
821
|
+
var JS_SIMPLE_ESCAPES = {
|
|
822
|
+
"0": "\0",
|
|
823
|
+
b: "\b",
|
|
824
|
+
f: "\f",
|
|
825
|
+
n: "\n",
|
|
826
|
+
r: "\r",
|
|
827
|
+
t: " ",
|
|
828
|
+
v: "\v"
|
|
829
|
+
};
|
|
830
|
+
function readJsStringLiteral(source, startIndex) {
|
|
831
|
+
const quote = source[startIndex];
|
|
832
|
+
if (quote !== '"' && quote !== "'" && quote !== "`") return null;
|
|
833
|
+
let value = "";
|
|
834
|
+
let index = startIndex + 1;
|
|
835
|
+
while (index < source.length) {
|
|
836
|
+
const char = source[index];
|
|
837
|
+
if (char === quote) return { endIndex: index + 1, value };
|
|
838
|
+
if (char !== "\\") {
|
|
839
|
+
value += char;
|
|
840
|
+
index++;
|
|
841
|
+
continue;
|
|
842
|
+
}
|
|
843
|
+
index++;
|
|
844
|
+
if (index >= source.length) return null;
|
|
845
|
+
const escaped = source[index];
|
|
846
|
+
if (escaped in JS_SIMPLE_ESCAPES) {
|
|
847
|
+
value += JS_SIMPLE_ESCAPES[escaped];
|
|
848
|
+
index++;
|
|
849
|
+
continue;
|
|
850
|
+
}
|
|
851
|
+
if (escaped === "\n") {
|
|
852
|
+
index++;
|
|
853
|
+
continue;
|
|
854
|
+
}
|
|
855
|
+
if (escaped === "\r") {
|
|
856
|
+
index += source[index + 1] === "\n" ? 2 : 1;
|
|
857
|
+
continue;
|
|
858
|
+
}
|
|
859
|
+
if (escaped === "x" && /^[0-9a-fA-F]{2}$/.test(source.slice(index + 1, index + 3))) {
|
|
860
|
+
value += String.fromCharCode(Number.parseInt(source.slice(index + 1, index + 3), 16));
|
|
861
|
+
index += 3;
|
|
862
|
+
continue;
|
|
863
|
+
}
|
|
864
|
+
if (escaped === "u") {
|
|
865
|
+
const braced = source.slice(index + 1).match(/^\{([0-9a-fA-F]+)\}/);
|
|
866
|
+
if (braced) {
|
|
867
|
+
const codePoint = Number.parseInt(braced[1], 16);
|
|
868
|
+
if (Number.isFinite(codePoint) && codePoint <= 1114111) {
|
|
869
|
+
value += String.fromCodePoint(codePoint);
|
|
870
|
+
index += braced[0].length + 1;
|
|
871
|
+
continue;
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
const hex = source.slice(index + 1, index + 5);
|
|
875
|
+
if (/^[0-9a-fA-F]{4}$/.test(hex)) {
|
|
876
|
+
value += String.fromCharCode(Number.parseInt(hex, 16));
|
|
877
|
+
index += 5;
|
|
878
|
+
continue;
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
value += escaped;
|
|
882
|
+
index++;
|
|
883
|
+
}
|
|
884
|
+
return null;
|
|
885
|
+
}
|
|
886
|
+
function isApplyPatchText(value) {
|
|
887
|
+
return value.includes("*** Begin Patch") && value.includes("*** End Patch");
|
|
888
|
+
}
|
|
889
|
+
function extractApplyPatchInputs(execSource) {
|
|
890
|
+
if (!execSource.includes("apply_patch") || !execSource.includes("*** Begin Patch")) return [];
|
|
891
|
+
const assignedPatches = /* @__PURE__ */ new Map();
|
|
892
|
+
for (let index = 0; index < execSource.length; index++) {
|
|
893
|
+
const literal = readJsStringLiteral(execSource, index);
|
|
894
|
+
if (!literal) continue;
|
|
895
|
+
if (isApplyPatchText(literal.value)) {
|
|
896
|
+
const assignment = execSource.slice(0, index).match(/(?:^|[;\n])\s*(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*=\s*$/);
|
|
897
|
+
if (assignment) assignedPatches.set(assignment[1], literal.value);
|
|
898
|
+
}
|
|
899
|
+
index = literal.endIndex - 1;
|
|
900
|
+
}
|
|
901
|
+
const patches = [];
|
|
902
|
+
const seen = /* @__PURE__ */ new Set();
|
|
903
|
+
const applyCall = /\btools\s*\.\s*apply_patch\s*\(/g;
|
|
904
|
+
let match;
|
|
905
|
+
while ((match = applyCall.exec(execSource)) !== null) {
|
|
906
|
+
let argumentIndex = match.index + match[0].length;
|
|
907
|
+
while (/\s/.test(execSource[argumentIndex] ?? "")) argumentIndex++;
|
|
908
|
+
const literal = readJsStringLiteral(execSource, argumentIndex);
|
|
909
|
+
const identifier = literal ? null : execSource.slice(argumentIndex).match(/^([A-Za-z_$][\w$]*)/);
|
|
910
|
+
const patch = literal?.value ?? (identifier ? assignedPatches.get(identifier[1]) : void 0);
|
|
911
|
+
if (!patch || !isApplyPatchText(patch) || seen.has(patch)) continue;
|
|
912
|
+
seen.add(patch);
|
|
913
|
+
patches.push(patch);
|
|
914
|
+
}
|
|
915
|
+
return patches;
|
|
916
|
+
}
|
|
917
|
+
function resolveCodexPatchPath(filePath, cwd) {
|
|
918
|
+
if (!cwd || filePath.startsWith("/") || /^[A-Za-z]:[\\/]/.test(filePath)) return filePath;
|
|
919
|
+
return `${cwd.replace(/[\\/]+$/, "")}/${filePath.replace(/^\.\//, "")}`;
|
|
920
|
+
}
|
|
921
|
+
function parseApplyPatch(patchText, callId, timestamp, cwd = "") {
|
|
922
|
+
const toolCalls = [];
|
|
923
|
+
const filePattern = /^\*\*\*\s+(Update File|Add File|Delete File):\s*(.+)$/gm;
|
|
924
|
+
const sections = [];
|
|
925
|
+
let match;
|
|
926
|
+
while ((match = filePattern.exec(patchText)) !== null) {
|
|
927
|
+
sections.push({
|
|
928
|
+
action: match[1],
|
|
929
|
+
filePath: match[2].trim(),
|
|
930
|
+
headerIdx: match.index,
|
|
931
|
+
startIdx: match.index + match[0].length
|
|
932
|
+
});
|
|
933
|
+
}
|
|
934
|
+
for (let i = 0; i < sections.length; i++) {
|
|
935
|
+
const section = sections[i];
|
|
936
|
+
const endIdx = i + 1 < sections.length ? sections[i + 1].headerIdx : patchText.length;
|
|
937
|
+
const body = patchText.slice(section.startIdx, endIdx);
|
|
938
|
+
const movedTo = body.match(/^\*\*\*\s+Move to:\s*(.+)$/m)?.[1]?.trim();
|
|
939
|
+
const sourcePath = resolveCodexPatchPath(section.filePath, cwd);
|
|
940
|
+
const filePath = resolveCodexPatchPath(movedTo || section.filePath, cwd);
|
|
941
|
+
const hunkBodies = body.split(/^@@.*$/m).filter((s) => s.trim());
|
|
942
|
+
const oldLines = [];
|
|
943
|
+
const newLines = [];
|
|
944
|
+
for (const hunk of hunkBodies) {
|
|
945
|
+
for (const line of hunk.split("\n")) {
|
|
946
|
+
if (line.startsWith("-")) {
|
|
947
|
+
oldLines.push(line.slice(1));
|
|
948
|
+
} else if (line.startsWith("+")) {
|
|
949
|
+
newLines.push(line.slice(1));
|
|
950
|
+
} else if (line.startsWith(" ")) {
|
|
951
|
+
oldLines.push(line.slice(1));
|
|
952
|
+
newLines.push(line.slice(1));
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
const fileId = sections.length > 1 ? `${callId}:file-${i}` : callId;
|
|
957
|
+
if (section.action === "Add File") {
|
|
958
|
+
toolCalls.push({
|
|
959
|
+
id: fileId,
|
|
960
|
+
name: "Write",
|
|
961
|
+
input: { file_path: filePath, content: newLines.join("\n") },
|
|
962
|
+
result: null,
|
|
963
|
+
isError: false,
|
|
964
|
+
timestamp
|
|
965
|
+
});
|
|
966
|
+
} else if (section.action === "Delete File") {
|
|
967
|
+
toolCalls.push({
|
|
968
|
+
id: fileId,
|
|
969
|
+
name: "Edit",
|
|
970
|
+
input: { file_path: filePath, old_string: oldLines.join("\n"), new_string: "" },
|
|
971
|
+
result: null,
|
|
972
|
+
isError: false,
|
|
973
|
+
timestamp
|
|
974
|
+
});
|
|
975
|
+
} else {
|
|
976
|
+
toolCalls.push({
|
|
977
|
+
id: fileId,
|
|
978
|
+
name: "Edit",
|
|
979
|
+
input: {
|
|
980
|
+
file_path: filePath,
|
|
981
|
+
...movedTo ? { old_path: sourcePath } : {},
|
|
982
|
+
old_string: oldLines.join("\n"),
|
|
983
|
+
new_string: newLines.join("\n")
|
|
984
|
+
},
|
|
985
|
+
result: null,
|
|
986
|
+
isError: false,
|
|
987
|
+
timestamp
|
|
988
|
+
});
|
|
989
|
+
}
|
|
990
|
+
}
|
|
991
|
+
return toolCalls;
|
|
992
|
+
}
|
|
993
|
+
function parseCodexToolPatches(toolName, rawInput, callId, timestamp, cwd = "") {
|
|
994
|
+
if (toolName === "apply_patch") {
|
|
995
|
+
return parseApplyPatch(rawInput, callId, timestamp, cwd);
|
|
996
|
+
}
|
|
997
|
+
return extractApplyPatchInputs(rawInput).flatMap(
|
|
998
|
+
(patch, patchIndex) => parseApplyPatch(patch, `${callId}:patch-${patchIndex}`, timestamp, cwd)
|
|
999
|
+
);
|
|
1000
|
+
}
|
|
1001
|
+
function findFailedNestedPatchCallIds(output, wrapperIsError, calls) {
|
|
1002
|
+
if (!wrapperIsError || !/(?:apply[_ ]patch|invalid patch|invalid context|failed to (?:apply|find)|could not apply)/i.test(output)) {
|
|
1003
|
+
return /* @__PURE__ */ new Set();
|
|
1004
|
+
}
|
|
1005
|
+
const groups = /* @__PURE__ */ new Map();
|
|
1006
|
+
for (const call of calls) {
|
|
1007
|
+
const patchIndex = call.id.match(/:patch-(\d+)(?::file-\d+)?$/)?.[1];
|
|
1008
|
+
if (patchIndex === void 0) continue;
|
|
1009
|
+
const index = Number(patchIndex);
|
|
1010
|
+
const group = groups.get(index) ?? [];
|
|
1011
|
+
group.push(call);
|
|
1012
|
+
groups.set(index, group);
|
|
1013
|
+
}
|
|
1014
|
+
if (groups.size === 0) return new Set(calls.map((call) => call.id));
|
|
1015
|
+
const normalizedOutput = output.toLowerCase().replaceAll("\\", "/");
|
|
1016
|
+
const mentionedGroups = /* @__PURE__ */ new Set();
|
|
1017
|
+
for (const [index, group] of groups) {
|
|
1018
|
+
if (group.some((call) => {
|
|
1019
|
+
const filePath = String(call.input.file_path ?? "").toLowerCase().replaceAll("\\", "/");
|
|
1020
|
+
if (!filePath) return false;
|
|
1021
|
+
const segments = filePath.split("/").filter(Boolean);
|
|
1022
|
+
const suffix = segments.slice(-2).join("/");
|
|
1023
|
+
return normalizedOutput.includes(filePath) || suffix.includes("/") && normalizedOutput.includes(suffix);
|
|
1024
|
+
})) {
|
|
1025
|
+
mentionedGroups.add(index);
|
|
1026
|
+
}
|
|
1027
|
+
}
|
|
1028
|
+
for (const match of normalizedOutput.matchAll(/\bpatch\s*(?:#|-)?\s*(\d+)\b/g)) {
|
|
1029
|
+
const index = Number(match[1]);
|
|
1030
|
+
if (groups.has(index)) mentionedGroups.add(index);
|
|
1031
|
+
}
|
|
1032
|
+
if (mentionedGroups.size === 0) mentionedGroups.add(Math.max(...groups.keys()));
|
|
1033
|
+
const firstFailedIndex = Math.min(...mentionedGroups);
|
|
1034
|
+
for (const index of groups.keys()) {
|
|
1035
|
+
if (index >= firstFailedIndex) mentionedGroups.add(index);
|
|
1036
|
+
}
|
|
1037
|
+
return new Set(
|
|
1038
|
+
[...mentionedGroups].flatMap((index) => groups.get(index)?.map((call) => call.id) ?? [])
|
|
1039
|
+
);
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
// src/lib/codex-tool-normalization.ts
|
|
1043
|
+
function isObject(value) {
|
|
1044
|
+
return typeof value === "object" && value !== null;
|
|
1045
|
+
}
|
|
1046
|
+
function inferToolError(output) {
|
|
1047
|
+
if (!output) return false;
|
|
1048
|
+
const exitMatch = output.match(/Process exited with code (\d+)/);
|
|
1049
|
+
if (exitMatch) return exitMatch[1] !== "0";
|
|
1050
|
+
const withoutSuccessSummaries = output.replace(/\b0\s+(?:fail(?:ed|ures?)?|errors?)\b/gi, "").replace(/\bno\s+(?:failures?|errors?)\b/gi, "");
|
|
1051
|
+
return /\b(error|failed|exception)\b/i.test(withoutSuccessSummaries);
|
|
1052
|
+
}
|
|
1053
|
+
function normalizePlanToTodos(input) {
|
|
1054
|
+
const plan = Array.isArray(input.plan) ? input.plan : [];
|
|
1055
|
+
const todos = plan.filter((item) => isObject(item)).map((item) => ({
|
|
1056
|
+
content: typeof item.step === "string" ? item.step : "",
|
|
1057
|
+
status: typeof item.status === "string" ? item.status : "pending",
|
|
1058
|
+
activeForm: typeof item.step === "string" ? item.step : ""
|
|
1059
|
+
}));
|
|
1060
|
+
return { todos };
|
|
1061
|
+
}
|
|
1062
|
+
function extractCustomToolOutputText(output) {
|
|
1063
|
+
if (typeof output === "string") return output;
|
|
1064
|
+
if (!Array.isArray(output)) return "";
|
|
1065
|
+
return output.filter((block) => isObject(block) && typeof block.text === "string").map((block) => block.text).join("");
|
|
1066
|
+
}
|
|
1067
|
+
function parseCustomToolOutput(output) {
|
|
1068
|
+
const rawOutput = extractCustomToolOutputText(output);
|
|
1069
|
+
if (!rawOutput) return { text: "", isError: false };
|
|
1070
|
+
try {
|
|
1071
|
+
const parsed = JSON.parse(rawOutput);
|
|
1072
|
+
const text = typeof parsed.output === "string" ? parsed.output : rawOutput;
|
|
1073
|
+
const meta = isObject(parsed.metadata) ? parsed.metadata : null;
|
|
1074
|
+
const isError = meta ? meta.exit_code !== 0 && meta.exit_code !== void 0 : inferToolError(text);
|
|
1075
|
+
return { text, isError };
|
|
1076
|
+
} catch {
|
|
1077
|
+
return { text: rawOutput, isError: inferToolError(rawOutput) };
|
|
1078
|
+
}
|
|
1079
|
+
}
|
|
1080
|
+
|
|
616
1081
|
// src/lib/codex.ts
|
|
617
1082
|
var SKIP_PROMPT_PREFIXES = [
|
|
1083
|
+
"# AGENTS.md instructions for ",
|
|
1084
|
+
"<recommended_plugins>",
|
|
618
1085
|
"<environment_context>",
|
|
619
1086
|
"<permissions instructions>",
|
|
620
1087
|
"<collaboration_mode>",
|
|
@@ -630,17 +1097,120 @@ function safeParseLine(line) {
|
|
|
630
1097
|
return null;
|
|
631
1098
|
}
|
|
632
1099
|
}
|
|
633
|
-
function
|
|
1100
|
+
function isObject2(value) {
|
|
634
1101
|
return typeof value === "object" && value !== null;
|
|
635
1102
|
}
|
|
636
1103
|
function isCodexRecord(record) {
|
|
637
1104
|
if (!record || typeof record.type !== "string") return false;
|
|
638
|
-
return record.type === "session_meta" || record.type === "turn_context" || record.type === "event_msg" || record.type === "response_item";
|
|
1105
|
+
return record.type === "session_meta" || record.type === "turn_context" || record.type === "event_msg" || record.type === "response_item" || record.type === "inter_agent_communication_metadata" || record.type === "compacted" || record.type === "world_state";
|
|
639
1106
|
}
|
|
640
1107
|
function extractMessageText(payload, blockType) {
|
|
641
1108
|
const content = payload?.content;
|
|
642
1109
|
if (!Array.isArray(content)) return "";
|
|
643
|
-
return content.filter((block) =>
|
|
1110
|
+
return content.filter((block) => isObject2(block) && block.type === blockType && typeof block.text === "string").map((block) => block.text).join("\n").trim();
|
|
1111
|
+
}
|
|
1112
|
+
var CODEX_IMAGE_DATA_URL = /^data:(image\/(?:png|jpeg|gif|webp));base64,(.+)$/is;
|
|
1113
|
+
var CODEX_AUDIO_DATA_URL = /^data:(audio\/(?:wav|x-wav|mpeg|mp4|webm|ogg));base64,(.+)$/is;
|
|
1114
|
+
function parseImageDataUrl(value) {
|
|
1115
|
+
if (typeof value !== "string") return null;
|
|
1116
|
+
const match = CODEX_IMAGE_DATA_URL.exec(value);
|
|
1117
|
+
if (!match?.[1] || !match[2]) return null;
|
|
1118
|
+
return {
|
|
1119
|
+
type: "image",
|
|
1120
|
+
source: {
|
|
1121
|
+
type: "base64",
|
|
1122
|
+
media_type: match[1].toLowerCase(),
|
|
1123
|
+
data: match[2]
|
|
1124
|
+
}
|
|
1125
|
+
};
|
|
1126
|
+
}
|
|
1127
|
+
function parseAudioDataUrl(value) {
|
|
1128
|
+
if (typeof value !== "string") return null;
|
|
1129
|
+
const match = CODEX_AUDIO_DATA_URL.exec(value);
|
|
1130
|
+
if (!match?.[1] || !match[2]) return null;
|
|
1131
|
+
return {
|
|
1132
|
+
type: "audio",
|
|
1133
|
+
source: {
|
|
1134
|
+
type: "base64",
|
|
1135
|
+
media_type: match[1].toLowerCase(),
|
|
1136
|
+
data: match[2]
|
|
1137
|
+
}
|
|
1138
|
+
};
|
|
1139
|
+
}
|
|
1140
|
+
function extractEventMessageImages(payload) {
|
|
1141
|
+
if (!Array.isArray(payload.images)) return [];
|
|
1142
|
+
return payload.images.map(parseImageDataUrl).filter((image) => image !== null);
|
|
1143
|
+
}
|
|
1144
|
+
function extractEventMessageAudio(payload) {
|
|
1145
|
+
if (!Array.isArray(payload.audio)) return [];
|
|
1146
|
+
return payload.audio.map(parseAudioDataUrl).filter((audio) => audio !== null);
|
|
1147
|
+
}
|
|
1148
|
+
function extractResponseMessageImages(payload) {
|
|
1149
|
+
if (!Array.isArray(payload.content)) return [];
|
|
1150
|
+
return payload.content.filter((block) => isObject2(block) && block.type === "input_image").map((block) => parseImageDataUrl(block.image_url)).filter((image) => image !== null);
|
|
1151
|
+
}
|
|
1152
|
+
function extractResponseMessageAudio(payload) {
|
|
1153
|
+
if (!Array.isArray(payload.content)) return [];
|
|
1154
|
+
return payload.content.filter((block) => isObject2(block) && block.type === "input_audio").map((block) => parseAudioDataUrl(block.audio_url)).filter((audio) => audio !== null);
|
|
1155
|
+
}
|
|
1156
|
+
function buildCodexUserContent(message, images, audio = [], localImages = [], localAudio = []) {
|
|
1157
|
+
const attachmentSuffix = [
|
|
1158
|
+
...localImages.map((path) => `
|
|
1159
|
+
`),
|
|
1160
|
+
...localAudio.map((path) => `
|
|
1161
|
+
[audio attachment](<${path}>)`)
|
|
1162
|
+
].join("");
|
|
1163
|
+
const text = message + attachmentSuffix;
|
|
1164
|
+
if (images.length === 0 && audio.length === 0) return text;
|
|
1165
|
+
return [
|
|
1166
|
+
...images,
|
|
1167
|
+
...audio,
|
|
1168
|
+
...text ? [{ type: "text", text }] : []
|
|
1169
|
+
];
|
|
1170
|
+
}
|
|
1171
|
+
function normalizeFunctionName(rawName) {
|
|
1172
|
+
const leaf = rawName.split(/(?:__|[.:/])+/).filter(Boolean).at(-1) ?? rawName;
|
|
1173
|
+
const aliases = {
|
|
1174
|
+
spawnAgent: "spawn_agent",
|
|
1175
|
+
waitAgent: "wait_agent",
|
|
1176
|
+
sendMessage: "send_message",
|
|
1177
|
+
followupTask: "followup_task",
|
|
1178
|
+
listAgents: "list_agents",
|
|
1179
|
+
interruptAgent: "interrupt_agent"
|
|
1180
|
+
};
|
|
1181
|
+
const canonicalCollaborationNames = /* @__PURE__ */ new Set([
|
|
1182
|
+
"spawn_agent",
|
|
1183
|
+
"wait_agent",
|
|
1184
|
+
"send_message",
|
|
1185
|
+
"followup_task",
|
|
1186
|
+
"list_agents",
|
|
1187
|
+
"interrupt_agent"
|
|
1188
|
+
]);
|
|
1189
|
+
if (aliases[leaf]) return aliases[leaf];
|
|
1190
|
+
if (canonicalCollaborationNames.has(leaf)) return leaf;
|
|
1191
|
+
return rawName;
|
|
1192
|
+
}
|
|
1193
|
+
function extractInterAgentMessage(payload) {
|
|
1194
|
+
const rawText = extractMessageText(payload, "input_text");
|
|
1195
|
+
if (!rawText) return { messageType: null, text: "" };
|
|
1196
|
+
const headerEnd = rawText.match(/\r?\nPayload:\s*(?:\r?\n|$)/);
|
|
1197
|
+
if (!rawText.startsWith("Message Type:") || !headerEnd || headerEnd.index === void 0) {
|
|
1198
|
+
return { messageType: null, text: rawText.trim() };
|
|
1199
|
+
}
|
|
1200
|
+
const header = rawText.slice(0, headerEnd.index);
|
|
1201
|
+
const messageType = header.match(/^Message Type:\s*([^\r\n]+)/)?.[1]?.trim().toUpperCase() ?? null;
|
|
1202
|
+
const text = rawText.slice(headerEnd.index + headerEnd[0].length).trim();
|
|
1203
|
+
return { messageType, text };
|
|
1204
|
+
}
|
|
1205
|
+
function agentNameFromPath(agentPath) {
|
|
1206
|
+
const segments = agentPath.split("/").filter(Boolean);
|
|
1207
|
+
return segments.at(-1) ?? null;
|
|
1208
|
+
}
|
|
1209
|
+
function readableAgentPrompt(value) {
|
|
1210
|
+
if (typeof value !== "string") return "";
|
|
1211
|
+
const prompt = value.trim();
|
|
1212
|
+
if (/^gAAAAA[A-Za-z0-9_-]+$/.test(prompt)) return "";
|
|
1213
|
+
return prompt;
|
|
644
1214
|
}
|
|
645
1215
|
function normalizePromptText(text) {
|
|
646
1216
|
const trimmed = text.trim();
|
|
@@ -658,11 +1228,13 @@ function mergeTokenUsage2(existing, incoming) {
|
|
|
658
1228
|
};
|
|
659
1229
|
}
|
|
660
1230
|
function parseTokenUsage(value) {
|
|
661
|
-
if (!
|
|
662
|
-
const
|
|
1231
|
+
if (!isObject2(value)) return null;
|
|
1232
|
+
const reportedInputTokens = typeof value.input_tokens === "number" ? value.input_tokens : 0;
|
|
663
1233
|
const outputTokens = typeof value.output_tokens === "number" ? value.output_tokens : 0;
|
|
664
|
-
const cacheCreation = typeof value.cache_creation_input_tokens === "number" ? value.cache_creation_input_tokens : 0;
|
|
665
|
-
const
|
|
1234
|
+
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;
|
|
1235
|
+
const nativeCachedInput = typeof value.cached_input_tokens === "number" ? Math.max(0, value.cached_input_tokens) : null;
|
|
1236
|
+
const cacheRead = nativeCachedInput ?? (typeof value.cache_read_input_tokens === "number" ? value.cache_read_input_tokens : 0);
|
|
1237
|
+
const inputTokens = nativeCachedInput === null ? reportedInputTokens : Math.max(0, reportedInputTokens - nativeCachedInput);
|
|
666
1238
|
if (inputTokens === 0 && outputTokens === 0 && cacheCreation === 0 && cacheRead === 0) return null;
|
|
667
1239
|
return {
|
|
668
1240
|
input_tokens: inputTokens,
|
|
@@ -703,7 +1275,7 @@ function appendToolCall(turn, toolCall, timestamp) {
|
|
|
703
1275
|
}
|
|
704
1276
|
function finalizeTurn(turns, current, lastTimestamp) {
|
|
705
1277
|
if (!current) return null;
|
|
706
|
-
const hasContent = current.userMessage !== null || current.assistantText.length > 0 || current.toolCalls.length > 0 || current.thinking.length > 0;
|
|
1278
|
+
const hasContent = current.userMessage !== null || current.assistantText.length > 0 || current.toolCalls.length > 0 || current.thinking.length > 0 || current.subAgentActivity.length > 0;
|
|
707
1279
|
if (!hasContent) return null;
|
|
708
1280
|
if (current.timestamp && lastTimestamp) {
|
|
709
1281
|
const start = new Date(current.timestamp).getTime();
|
|
@@ -719,17 +1291,11 @@ function parseToolInput(argumentsText) {
|
|
|
719
1291
|
if (typeof argumentsText !== "string" || !argumentsText.trim()) return {};
|
|
720
1292
|
try {
|
|
721
1293
|
const parsed = JSON.parse(argumentsText);
|
|
722
|
-
return
|
|
1294
|
+
return isObject2(parsed) ? parsed : { value: parsed };
|
|
723
1295
|
} catch {
|
|
724
1296
|
return { raw: argumentsText };
|
|
725
1297
|
}
|
|
726
1298
|
}
|
|
727
|
-
function inferToolError(output) {
|
|
728
|
-
if (!output) return false;
|
|
729
|
-
const exitMatch = output.match(/Process exited with code (\d+)/);
|
|
730
|
-
if (exitMatch) return exitMatch[1] !== "0";
|
|
731
|
-
return /\b(error|failed|exception)\b/i.test(output);
|
|
732
|
-
}
|
|
733
1299
|
function createTurn(turnId, timestamp, model) {
|
|
734
1300
|
return {
|
|
735
1301
|
id: turnId || randomTurnId("codex-turn"),
|
|
@@ -746,10 +1312,10 @@ function createTurn(turnId, timestamp, model) {
|
|
|
746
1312
|
};
|
|
747
1313
|
}
|
|
748
1314
|
function extractPromptFromRecord(record) {
|
|
749
|
-
if (record.type === "event_msg" &&
|
|
1315
|
+
if (record.type === "event_msg" && isObject2(record.payload) && record.payload.type === "user_message" && typeof record.payload.message === "string") {
|
|
750
1316
|
return normalizePromptText(record.payload.message);
|
|
751
1317
|
}
|
|
752
|
-
if (record.type === "response_item" &&
|
|
1318
|
+
if (record.type === "response_item" && isObject2(record.payload) && record.payload.type === "message" && record.payload.role === "user") {
|
|
753
1319
|
return normalizePromptText(extractMessageText(record.payload, "input_text"));
|
|
754
1320
|
}
|
|
755
1321
|
return "";
|
|
@@ -766,14 +1332,17 @@ function extractMetadataFromRecords(records) {
|
|
|
766
1332
|
let timestamp = "";
|
|
767
1333
|
let lastTimestamp = "";
|
|
768
1334
|
let turnCount = 0;
|
|
1335
|
+
let isSubagent = false;
|
|
1336
|
+
let parentSessionId = null;
|
|
1337
|
+
let agentPath = "/root";
|
|
769
1338
|
let previousPrompt = "";
|
|
770
1339
|
for (const record of records) {
|
|
771
|
-
if (!
|
|
1340
|
+
if (!isObject2(record.payload)) continue;
|
|
772
1341
|
if (record.type === "session_meta") {
|
|
773
1342
|
sessionId ||= typeof record.payload.id === "string" ? record.payload.id : "";
|
|
774
1343
|
version ||= typeof record.payload.cli_version === "string" ? record.payload.cli_version : "";
|
|
775
1344
|
cwd ||= typeof record.payload.cwd === "string" ? record.payload.cwd : "";
|
|
776
|
-
if (!branchedFrom &&
|
|
1345
|
+
if (!branchedFrom && isObject2(record.payload.branchedFrom)) {
|
|
777
1346
|
const sourceId = typeof record.payload.branchedFrom.sessionId === "string" ? record.payload.branchedFrom.sessionId : "";
|
|
778
1347
|
if (sourceId) {
|
|
779
1348
|
branchedFrom = {
|
|
@@ -782,7 +1351,18 @@ function extractMetadataFromRecords(records) {
|
|
|
782
1351
|
};
|
|
783
1352
|
}
|
|
784
1353
|
}
|
|
785
|
-
const
|
|
1354
|
+
const source = isObject2(record.payload.source) ? record.payload.source : null;
|
|
1355
|
+
if (source && isObject2(source.subagent)) {
|
|
1356
|
+
isSubagent = true;
|
|
1357
|
+
const threadSpawn = isObject2(source.subagent.thread_spawn) ? source.subagent.thread_spawn : null;
|
|
1358
|
+
if (threadSpawn && typeof threadSpawn.agent_path === "string") {
|
|
1359
|
+
agentPath = threadSpawn.agent_path;
|
|
1360
|
+
}
|
|
1361
|
+
}
|
|
1362
|
+
if (typeof record.payload.forked_from_id === "string" && record.payload.forked_from_id) {
|
|
1363
|
+
parentSessionId = record.payload.forked_from_id;
|
|
1364
|
+
}
|
|
1365
|
+
const git = isObject2(record.payload.git) ? record.payload.git : null;
|
|
786
1366
|
gitBranch ||= git && typeof git.branch === "string" ? git.branch : "";
|
|
787
1367
|
}
|
|
788
1368
|
if (record.type === "turn_context") {
|
|
@@ -807,12 +1387,16 @@ function extractMetadataFromRecords(records) {
|
|
|
807
1387
|
cwd,
|
|
808
1388
|
model,
|
|
809
1389
|
slug: "",
|
|
1390
|
+
name: "",
|
|
810
1391
|
branchedFrom,
|
|
811
1392
|
firstUserMessage,
|
|
812
1393
|
lastUserMessage,
|
|
813
1394
|
timestamp,
|
|
814
1395
|
lastTimestamp,
|
|
815
|
-
turnCount
|
|
1396
|
+
turnCount,
|
|
1397
|
+
isSubagent,
|
|
1398
|
+
parentSessionId,
|
|
1399
|
+
agentPath
|
|
816
1400
|
};
|
|
817
1401
|
}
|
|
818
1402
|
function isCodexSessionText(jsonlText) {
|
|
@@ -827,18 +1411,99 @@ function extractCodexMetadataFromLines(lines) {
|
|
|
827
1411
|
const records = lines.map(safeParseLine).filter(isCodexRecord);
|
|
828
1412
|
return extractMetadataFromRecords(records);
|
|
829
1413
|
}
|
|
830
|
-
function parseCodexSession(jsonlText) {
|
|
1414
|
+
function parseCodexSession(jsonlText, options) {
|
|
831
1415
|
const records = jsonlText.split("\n").map((line) => line.trim()).filter(Boolean).map(safeParseLine).filter(isCodexRecord);
|
|
832
1416
|
const metadata = extractMetadataFromRecords(records);
|
|
833
1417
|
const turns = [];
|
|
834
1418
|
const pendingToolCalls = /* @__PURE__ */ new Map();
|
|
1419
|
+
const webSearchCalls = /* @__PURE__ */ new Map();
|
|
1420
|
+
const patchCallIds = /* @__PURE__ */ new Map();
|
|
1421
|
+
const spawnAgentCalls = /* @__PURE__ */ new Map();
|
|
1422
|
+
const agentRegistry = /* @__PURE__ */ new Map();
|
|
1423
|
+
const agentIdByPath = /* @__PURE__ */ new Map();
|
|
1424
|
+
const agentIdBySpawnCall = /* @__PURE__ */ new Map();
|
|
1425
|
+
const agentMessages = /* @__PURE__ */ new Map();
|
|
1426
|
+
function upsertAgentMessage(agentId, targetTurn, eventTimestamp, update = {}) {
|
|
1427
|
+
const info = agentRegistry.get(agentId);
|
|
1428
|
+
let entry = agentMessages.get(agentId);
|
|
1429
|
+
if (!entry) {
|
|
1430
|
+
const agentMessage2 = {
|
|
1431
|
+
agentId,
|
|
1432
|
+
parentToolUseId: info?.parentToolCallId || void 0,
|
|
1433
|
+
agentName: info?.nickname ?? (info?.agentPath ? agentNameFromPath(info.agentPath) : null),
|
|
1434
|
+
subagentType: info?.agentType ?? null,
|
|
1435
|
+
type: "assistant",
|
|
1436
|
+
content: null,
|
|
1437
|
+
toolCalls: [],
|
|
1438
|
+
thinking: [],
|
|
1439
|
+
text: [],
|
|
1440
|
+
timestamp: info?.timestamp ?? eventTimestamp,
|
|
1441
|
+
tokenUsage: null,
|
|
1442
|
+
model: info?.model ?? null,
|
|
1443
|
+
isBackground: false,
|
|
1444
|
+
prompt: info?.message || void 0,
|
|
1445
|
+
status: update.status ?? "running"
|
|
1446
|
+
};
|
|
1447
|
+
targetTurn.subAgentActivity.push(agentMessage2);
|
|
1448
|
+
const lastBlock = targetTurn.contentBlocks[targetTurn.contentBlocks.length - 1];
|
|
1449
|
+
if (lastBlock?.kind === "sub_agent") {
|
|
1450
|
+
lastBlock.messages.push(agentMessage2);
|
|
1451
|
+
} else {
|
|
1452
|
+
targetTurn.contentBlocks.push({ kind: "sub_agent", messages: [agentMessage2], timestamp: eventTimestamp });
|
|
1453
|
+
}
|
|
1454
|
+
entry = { message: agentMessage2, turn: targetTurn };
|
|
1455
|
+
agentMessages.set(agentId, entry);
|
|
1456
|
+
}
|
|
1457
|
+
const agentMessage = entry.message;
|
|
1458
|
+
if (info) {
|
|
1459
|
+
agentMessage.parentToolUseId = info.parentToolCallId || agentMessage.parentToolUseId;
|
|
1460
|
+
agentMessage.agentName = info.nickname ?? (info.agentPath ? agentNameFromPath(info.agentPath) : agentMessage.agentName);
|
|
1461
|
+
agentMessage.subagentType = info.agentType;
|
|
1462
|
+
agentMessage.model = info.model;
|
|
1463
|
+
agentMessage.prompt = info.message || agentMessage.prompt;
|
|
1464
|
+
}
|
|
1465
|
+
if (update.text && !agentMessage.text.includes(update.text)) {
|
|
1466
|
+
agentMessage.text.push(update.text);
|
|
1467
|
+
agentMessage.content = agentMessage.text.join("\n\n");
|
|
1468
|
+
}
|
|
1469
|
+
if (update.status) {
|
|
1470
|
+
const terminalStatuses = /* @__PURE__ */ new Set(["completed", "failed", "interrupted"]);
|
|
1471
|
+
if (!(update.status === "running" && agentMessage.status && terminalStatuses.has(agentMessage.status))) {
|
|
1472
|
+
agentMessage.status = update.status;
|
|
1473
|
+
}
|
|
1474
|
+
if (terminalStatuses.has(update.status) && agentMessage.timestamp && eventTimestamp) {
|
|
1475
|
+
const startedAt = new Date(agentMessage.timestamp).getTime();
|
|
1476
|
+
const endedAt = new Date(eventTimestamp).getTime();
|
|
1477
|
+
if (Number.isFinite(startedAt) && Number.isFinite(endedAt) && endedAt >= startedAt) {
|
|
1478
|
+
agentMessage.durationMs = endedAt - startedAt;
|
|
1479
|
+
}
|
|
1480
|
+
}
|
|
1481
|
+
}
|
|
1482
|
+
return agentMessage;
|
|
1483
|
+
}
|
|
835
1484
|
let current = null;
|
|
836
1485
|
let currentTurnId = null;
|
|
837
1486
|
let currentModel = metadata.model || null;
|
|
838
1487
|
let lastTurnTimestamp = "";
|
|
1488
|
+
let pendingCompaction = null;
|
|
1489
|
+
function createActiveTurn(turnId, timestamp, model) {
|
|
1490
|
+
const turn = createTurn(turnId, timestamp, model);
|
|
1491
|
+
if (pendingCompaction) {
|
|
1492
|
+
turn.compactionSummary = pendingCompaction;
|
|
1493
|
+
pendingCompaction = null;
|
|
1494
|
+
}
|
|
1495
|
+
return turn;
|
|
1496
|
+
}
|
|
839
1497
|
for (const record of records) {
|
|
840
|
-
const payload =
|
|
1498
|
+
const payload = isObject2(record.payload) ? record.payload : void 0;
|
|
841
1499
|
const timestamp = record.timestamp ?? "";
|
|
1500
|
+
if (record.type === "compacted") {
|
|
1501
|
+
current = finalizeTurn(turns, current, lastTurnTimestamp);
|
|
1502
|
+
pendingCompaction = "Conversation compacted";
|
|
1503
|
+
lastTurnTimestamp = timestamp;
|
|
1504
|
+
continue;
|
|
1505
|
+
}
|
|
1506
|
+
if (record.type === "world_state") continue;
|
|
842
1507
|
if (record.type === "turn_context") {
|
|
843
1508
|
current = finalizeTurn(turns, current, lastTurnTimestamp);
|
|
844
1509
|
currentTurnId = typeof payload?.turn_id === "string" ? payload.turn_id : null;
|
|
@@ -850,24 +1515,93 @@ function parseCodexSession(jsonlText) {
|
|
|
850
1515
|
if (current && (current.assistantText.length > 0 || current.toolCalls.length > 0 || current.thinking.length > 0)) {
|
|
851
1516
|
current = finalizeTurn(turns, current, lastTurnTimestamp);
|
|
852
1517
|
}
|
|
853
|
-
current ??=
|
|
854
|
-
|
|
1518
|
+
current ??= createActiveTurn(currentTurnId, timestamp, currentModel);
|
|
1519
|
+
const localImages = (Array.isArray(payload.local_images) ? payload.local_images : []).filter(
|
|
1520
|
+
(p) => typeof p === "string" && p.length > 0
|
|
1521
|
+
);
|
|
1522
|
+
const localAudio = (Array.isArray(payload.local_audio) ? payload.local_audio : []).filter(
|
|
1523
|
+
(p) => typeof p === "string" && p.length > 0
|
|
1524
|
+
);
|
|
1525
|
+
current.userMessage = buildCodexUserContent(
|
|
1526
|
+
payload.message,
|
|
1527
|
+
extractEventMessageImages(payload),
|
|
1528
|
+
extractEventMessageAudio(payload),
|
|
1529
|
+
localImages,
|
|
1530
|
+
localAudio
|
|
1531
|
+
);
|
|
855
1532
|
current.timestamp = current.timestamp || timestamp;
|
|
856
1533
|
lastTurnTimestamp = timestamp;
|
|
857
1534
|
continue;
|
|
858
1535
|
}
|
|
859
|
-
current ??=
|
|
1536
|
+
current ??= createActiveTurn(currentTurnId, timestamp, currentModel);
|
|
860
1537
|
if (!current.model && currentModel) current.model = currentModel;
|
|
861
1538
|
if (!current.timestamp) current.timestamp = timestamp;
|
|
862
1539
|
if (timestamp) lastTurnTimestamp = timestamp;
|
|
863
1540
|
if (record.type === "event_msg" && payload?.type === "token_count") {
|
|
864
|
-
const info =
|
|
1541
|
+
const info = isObject2(payload.info) ? payload.info : null;
|
|
865
1542
|
const lastUsage = info ? parseTokenUsage(info.last_token_usage) : null;
|
|
866
1543
|
if (lastUsage) {
|
|
867
1544
|
current.tokenUsage = mergeTokenUsage2(current.tokenUsage, lastUsage);
|
|
868
1545
|
}
|
|
869
1546
|
continue;
|
|
870
1547
|
}
|
|
1548
|
+
if (record.type === "event_msg" && payload?.type === "web_search_end" && typeof payload.call_id === "string") {
|
|
1549
|
+
const input = {};
|
|
1550
|
+
if (typeof payload.query === "string" && payload.query) input.query = payload.query;
|
|
1551
|
+
if (isObject2(payload.action)) input.action = payload.action;
|
|
1552
|
+
const existing = webSearchCalls.get(payload.call_id);
|
|
1553
|
+
if (existing) {
|
|
1554
|
+
existing.input = { ...existing.input, ...input };
|
|
1555
|
+
existing.result = "completed";
|
|
1556
|
+
existing.isError = false;
|
|
1557
|
+
} else {
|
|
1558
|
+
const toolCall = {
|
|
1559
|
+
id: payload.call_id,
|
|
1560
|
+
name: "WebSearch",
|
|
1561
|
+
input,
|
|
1562
|
+
result: "completed",
|
|
1563
|
+
isError: false,
|
|
1564
|
+
timestamp
|
|
1565
|
+
};
|
|
1566
|
+
webSearchCalls.set(toolCall.id, toolCall);
|
|
1567
|
+
appendToolCall(current, toolCall, timestamp);
|
|
1568
|
+
}
|
|
1569
|
+
continue;
|
|
1570
|
+
}
|
|
1571
|
+
if (record.type === "event_msg" && payload?.type === "sub_agent_activity" && typeof payload.agent_thread_id === "string" && typeof payload.agent_path === "string") {
|
|
1572
|
+
const agentId = payload.agent_thread_id;
|
|
1573
|
+
const eventId = typeof payload.event_id === "string" ? payload.event_id : "";
|
|
1574
|
+
const kind = typeof payload.kind === "string" ? payload.kind : "started";
|
|
1575
|
+
const spawnInfo = eventId ? spawnAgentCalls.get(eventId) : void 0;
|
|
1576
|
+
const existingId = agentIdByPath.get(payload.agent_path);
|
|
1577
|
+
const existing = agentRegistry.get(agentId) ?? (existingId ? agentRegistry.get(existingId) : void 0);
|
|
1578
|
+
if (kind !== "started" && !existing && !spawnInfo) continue;
|
|
1579
|
+
if (existingId && existingId !== agentId && !agentMessages.has(agentId)) {
|
|
1580
|
+
const provisionalEntry = agentMessages.get(existingId);
|
|
1581
|
+
if (provisionalEntry) {
|
|
1582
|
+
provisionalEntry.message.agentId = agentId;
|
|
1583
|
+
agentMessages.delete(existingId);
|
|
1584
|
+
agentMessages.set(agentId, provisionalEntry);
|
|
1585
|
+
}
|
|
1586
|
+
agentRegistry.delete(existingId);
|
|
1587
|
+
}
|
|
1588
|
+
const info = {
|
|
1589
|
+
agentId,
|
|
1590
|
+
nickname: existing?.nickname ?? agentNameFromPath(payload.agent_path) ?? spawnInfo?.taskName ?? null,
|
|
1591
|
+
agentPath: payload.agent_path,
|
|
1592
|
+
message: existing?.message ?? spawnInfo?.message ?? "",
|
|
1593
|
+
model: existing?.model ?? spawnInfo?.model ?? null,
|
|
1594
|
+
agentType: existing?.agentType ?? spawnInfo?.agentType ?? null,
|
|
1595
|
+
timestamp: existing?.timestamp ?? (timestamp || spawnInfo?.timestamp || ""),
|
|
1596
|
+
parentToolCallId: existing?.parentToolCallId ?? eventId
|
|
1597
|
+
};
|
|
1598
|
+
agentRegistry.set(agentId, info);
|
|
1599
|
+
agentIdByPath.set(payload.agent_path, agentId);
|
|
1600
|
+
if (eventId && (kind === "started" || spawnInfo)) agentIdBySpawnCall.set(eventId, agentId);
|
|
1601
|
+
const status = kind === "interrupted" ? "interrupted" : "running";
|
|
1602
|
+
upsertAgentMessage(agentId, current, timestamp, { status });
|
|
1603
|
+
continue;
|
|
1604
|
+
}
|
|
871
1605
|
if (record.type !== "response_item" || !payload) continue;
|
|
872
1606
|
if (payload.type === "reasoning") {
|
|
873
1607
|
const summary = Array.isArray(payload.summary) ? payload.summary.filter((item) => typeof item === "string").join("\n") : "";
|
|
@@ -880,14 +1614,95 @@ function parseCodexSession(jsonlText) {
|
|
|
880
1614
|
}
|
|
881
1615
|
if (payload.type === "message" && payload.role === "user" && current.userMessage === null) {
|
|
882
1616
|
const text = normalizePromptText(extractMessageText(payload, "input_text"));
|
|
883
|
-
|
|
1617
|
+
const images = extractResponseMessageImages(payload);
|
|
1618
|
+
const audio = extractResponseMessageAudio(payload);
|
|
1619
|
+
if (text || images.length > 0 || audio.length > 0) {
|
|
1620
|
+
current.userMessage = buildCodexUserContent(text, images, audio);
|
|
1621
|
+
}
|
|
1622
|
+
continue;
|
|
1623
|
+
}
|
|
1624
|
+
if (payload.type === "web_search_call" && typeof payload.id === "string") {
|
|
1625
|
+
const input = {};
|
|
1626
|
+
if (isObject2(payload.action)) input.action = payload.action;
|
|
1627
|
+
const status = typeof payload.status === "string" ? payload.status : "completed";
|
|
1628
|
+
const existing = webSearchCalls.get(payload.id);
|
|
1629
|
+
if (existing) {
|
|
1630
|
+
existing.input = { ...existing.input, ...input };
|
|
1631
|
+
existing.result = status;
|
|
1632
|
+
existing.isError = status === "failed";
|
|
1633
|
+
} else {
|
|
1634
|
+
const toolCall = {
|
|
1635
|
+
id: payload.id,
|
|
1636
|
+
name: "WebSearch",
|
|
1637
|
+
input,
|
|
1638
|
+
result: status,
|
|
1639
|
+
isError: status === "failed",
|
|
1640
|
+
timestamp
|
|
1641
|
+
};
|
|
1642
|
+
webSearchCalls.set(toolCall.id, toolCall);
|
|
1643
|
+
appendToolCall(current, toolCall, timestamp);
|
|
1644
|
+
}
|
|
1645
|
+
continue;
|
|
1646
|
+
}
|
|
1647
|
+
if (payload.type === "agent_message" && typeof payload.author === "string") {
|
|
1648
|
+
const author = payload.author;
|
|
1649
|
+
const recipient = typeof payload.recipient === "string" ? payload.recipient : null;
|
|
1650
|
+
const interAgentMessage = extractInterAgentMessage(payload);
|
|
1651
|
+
if (interAgentMessage.messageType === "NEW_TASK") continue;
|
|
1652
|
+
const knownAgentId = agentIdByPath.get(author);
|
|
1653
|
+
const isPotentialChild = author.startsWith(`${metadata.agentPath}/`) && (recipient === null || recipient === metadata.agentPath);
|
|
1654
|
+
if (!knownAgentId && !isPotentialChild) continue;
|
|
1655
|
+
const agentId = knownAgentId ?? author;
|
|
1656
|
+
if (!knownAgentId) {
|
|
1657
|
+
agentRegistry.set(agentId, {
|
|
1658
|
+
agentId,
|
|
1659
|
+
nickname: agentNameFromPath(author),
|
|
1660
|
+
agentPath: author,
|
|
1661
|
+
message: "",
|
|
1662
|
+
model: null,
|
|
1663
|
+
agentType: null,
|
|
1664
|
+
timestamp,
|
|
1665
|
+
parentToolCallId: ""
|
|
1666
|
+
});
|
|
1667
|
+
agentIdByPath.set(author, agentId);
|
|
1668
|
+
}
|
|
1669
|
+
let status;
|
|
1670
|
+
if (interAgentMessage.messageType === "FINAL_ANSWER") status = "completed";
|
|
1671
|
+
else if (interAgentMessage.messageType === "ERROR" || interAgentMessage.messageType === "FAILED") status = "failed";
|
|
1672
|
+
else if (interAgentMessage.messageType === "INTERRUPTED") status = "interrupted";
|
|
1673
|
+
else if (interAgentMessage.messageType === "MESSAGE") status = "running";
|
|
1674
|
+
upsertAgentMessage(agentId, current, timestamp, {
|
|
1675
|
+
status,
|
|
1676
|
+
text: interAgentMessage.text || void 0
|
|
1677
|
+
});
|
|
884
1678
|
continue;
|
|
885
1679
|
}
|
|
886
1680
|
if (payload.type === "function_call" && typeof payload.call_id === "string") {
|
|
1681
|
+
const rawName = typeof payload.name === "string" ? payload.name : "tool";
|
|
1682
|
+
const functionName = normalizeFunctionName(rawName);
|
|
1683
|
+
const parsedInput = parseToolInput(payload.arguments);
|
|
1684
|
+
let name = functionName;
|
|
1685
|
+
let input = parsedInput;
|
|
1686
|
+
if (functionName === "exec_command") {
|
|
1687
|
+
name = "Bash";
|
|
1688
|
+
} else if (functionName === "update_plan") {
|
|
1689
|
+
name = "TodoWrite";
|
|
1690
|
+
input = normalizePlanToTodos(parsedInput);
|
|
1691
|
+
}
|
|
1692
|
+
if (functionName === "spawn_agent") {
|
|
1693
|
+
spawnAgentCalls.set(payload.call_id, {
|
|
1694
|
+
callId: payload.call_id,
|
|
1695
|
+
message: readableAgentPrompt(parsedInput.message),
|
|
1696
|
+
taskName: typeof parsedInput.task_name === "string" ? parsedInput.task_name : null,
|
|
1697
|
+
model: typeof parsedInput.model === "string" ? parsedInput.model : null,
|
|
1698
|
+
agentType: typeof parsedInput.agent_type === "string" ? parsedInput.agent_type : null,
|
|
1699
|
+
timestamp
|
|
1700
|
+
});
|
|
1701
|
+
}
|
|
887
1702
|
const toolCall = {
|
|
888
1703
|
id: payload.call_id,
|
|
889
|
-
name
|
|
890
|
-
input
|
|
1704
|
+
name,
|
|
1705
|
+
input,
|
|
891
1706
|
result: null,
|
|
892
1707
|
isError: false,
|
|
893
1708
|
timestamp
|
|
@@ -903,6 +1718,116 @@ function parseCodexSession(jsonlText) {
|
|
|
903
1718
|
toolCall.result = output;
|
|
904
1719
|
toolCall.isError = inferToolError(output);
|
|
905
1720
|
pendingToolCalls.delete(payload.call_id);
|
|
1721
|
+
if (toolCall.name === "spawn_agent" && output) {
|
|
1722
|
+
try {
|
|
1723
|
+
const result = JSON.parse(output);
|
|
1724
|
+
const agentId = typeof result.agent_id === "string" ? result.agent_id : agentIdBySpawnCall.get(toolCall.id) ?? "";
|
|
1725
|
+
const agentPath = typeof result.task_name === "string" ? result.task_name : null;
|
|
1726
|
+
const nickname = typeof result.nickname === "string" ? result.nickname : agentPath ? agentNameFromPath(agentPath) : null;
|
|
1727
|
+
if (agentId) {
|
|
1728
|
+
const spawnInfo = spawnAgentCalls.get(toolCall.id);
|
|
1729
|
+
const existing = agentRegistry.get(agentId);
|
|
1730
|
+
const info = {
|
|
1731
|
+
agentId,
|
|
1732
|
+
nickname: nickname ?? existing?.nickname ?? spawnInfo?.taskName ?? null,
|
|
1733
|
+
agentPath: agentPath ?? existing?.agentPath ?? null,
|
|
1734
|
+
message: existing?.message ?? spawnInfo?.message ?? "",
|
|
1735
|
+
model: existing?.model ?? spawnInfo?.model ?? null,
|
|
1736
|
+
agentType: existing?.agentType ?? spawnInfo?.agentType ?? null,
|
|
1737
|
+
timestamp: existing?.timestamp ?? spawnInfo?.timestamp ?? timestamp,
|
|
1738
|
+
parentToolCallId: existing?.parentToolCallId ?? toolCall.id
|
|
1739
|
+
};
|
|
1740
|
+
agentRegistry.set(agentId, info);
|
|
1741
|
+
agentIdBySpawnCall.set(toolCall.id, agentId);
|
|
1742
|
+
if (info.agentPath) agentIdByPath.set(info.agentPath, agentId);
|
|
1743
|
+
upsertAgentMessage(agentId, current, timestamp, { status: "running" });
|
|
1744
|
+
}
|
|
1745
|
+
} catch {
|
|
1746
|
+
}
|
|
1747
|
+
}
|
|
1748
|
+
if (toolCall.name === "wait_agent" && output && current) {
|
|
1749
|
+
try {
|
|
1750
|
+
const result = JSON.parse(output);
|
|
1751
|
+
const statusMap = isObject2(result.status) ? result.status : {};
|
|
1752
|
+
for (const [agentId, status] of Object.entries(statusMap)) {
|
|
1753
|
+
const statusValue = isObject2(status) ? status : {};
|
|
1754
|
+
const completedText = typeof statusValue.completed === "string" ? statusValue.completed : "";
|
|
1755
|
+
const failedText = typeof statusValue.failed === "string" ? statusValue.failed : typeof statusValue.error === "string" ? statusValue.error : "";
|
|
1756
|
+
const interruptedText = typeof statusValue.interrupted === "string" ? statusValue.interrupted : "";
|
|
1757
|
+
const lifecycle = completedText ? "completed" : failedText ? "failed" : interruptedText ? "interrupted" : typeof statusValue.status === "string" ? statusValue.status : "running";
|
|
1758
|
+
if (!agentRegistry.has(agentId)) {
|
|
1759
|
+
agentRegistry.set(agentId, {
|
|
1760
|
+
agentId,
|
|
1761
|
+
nickname: null,
|
|
1762
|
+
agentPath: null,
|
|
1763
|
+
message: "",
|
|
1764
|
+
model: null,
|
|
1765
|
+
agentType: null,
|
|
1766
|
+
timestamp,
|
|
1767
|
+
parentToolCallId: ""
|
|
1768
|
+
});
|
|
1769
|
+
}
|
|
1770
|
+
upsertAgentMessage(agentId, current, timestamp, {
|
|
1771
|
+
status: lifecycle,
|
|
1772
|
+
text: completedText || failedText || interruptedText || void 0
|
|
1773
|
+
});
|
|
1774
|
+
}
|
|
1775
|
+
} catch {
|
|
1776
|
+
}
|
|
1777
|
+
}
|
|
1778
|
+
continue;
|
|
1779
|
+
}
|
|
1780
|
+
if (payload.type === "custom_tool_call" && typeof payload.call_id === "string") {
|
|
1781
|
+
const name = typeof payload.name === "string" ? payload.name : "tool";
|
|
1782
|
+
const rawInput = typeof payload.input === "string" ? payload.input : "";
|
|
1783
|
+
const callId = payload.call_id;
|
|
1784
|
+
const perFileCalls = rawInput ? parseCodexToolPatches(name, rawInput, callId, timestamp, metadata.cwd) : [];
|
|
1785
|
+
if (name !== "apply_patch") {
|
|
1786
|
+
const toolCall = {
|
|
1787
|
+
id: callId,
|
|
1788
|
+
name: name === "exec_command" ? "Bash" : name,
|
|
1789
|
+
input: parseToolInput(rawInput),
|
|
1790
|
+
result: null,
|
|
1791
|
+
isError: false,
|
|
1792
|
+
timestamp
|
|
1793
|
+
};
|
|
1794
|
+
pendingToolCalls.set(toolCall.id, toolCall);
|
|
1795
|
+
appendToolCall(current, toolCall, timestamp);
|
|
1796
|
+
}
|
|
1797
|
+
for (const tc of perFileCalls) {
|
|
1798
|
+
pendingToolCalls.set(tc.id, tc);
|
|
1799
|
+
appendToolCall(current, tc, timestamp);
|
|
1800
|
+
}
|
|
1801
|
+
if (perFileCalls.length > 0) {
|
|
1802
|
+
patchCallIds.set(callId, {
|
|
1803
|
+
calls: perFileCalls,
|
|
1804
|
+
direct: name === "apply_patch"
|
|
1805
|
+
});
|
|
1806
|
+
}
|
|
1807
|
+
continue;
|
|
1808
|
+
}
|
|
1809
|
+
if (payload.type === "custom_tool_call_output" && typeof payload.call_id === "string") {
|
|
1810
|
+
const callId = payload.call_id;
|
|
1811
|
+
const { text, isError } = parseCustomToolOutput(payload.output);
|
|
1812
|
+
const patchCalls = patchCallIds.get(callId);
|
|
1813
|
+
if (patchCalls) {
|
|
1814
|
+
const failedIds = patchCalls.direct && isError ? new Set(patchCalls.calls.map((call) => call.id)) : findFailedNestedPatchCallIds(text, isError, patchCalls.calls);
|
|
1815
|
+
for (const patchCall of patchCalls.calls) {
|
|
1816
|
+
const tc = pendingToolCalls.get(patchCall.id);
|
|
1817
|
+
if (tc) {
|
|
1818
|
+
tc.result = text;
|
|
1819
|
+
tc.isError = failedIds.has(tc.id);
|
|
1820
|
+
pendingToolCalls.delete(tc.id);
|
|
1821
|
+
}
|
|
1822
|
+
}
|
|
1823
|
+
patchCallIds.delete(callId);
|
|
1824
|
+
}
|
|
1825
|
+
const toolCall = pendingToolCalls.get(callId);
|
|
1826
|
+
if (toolCall) {
|
|
1827
|
+
toolCall.result = text;
|
|
1828
|
+
toolCall.isError = isError;
|
|
1829
|
+
pendingToolCalls.delete(callId);
|
|
1830
|
+
}
|
|
906
1831
|
continue;
|
|
907
1832
|
}
|
|
908
1833
|
}
|
|
@@ -913,19 +1838,17 @@ function parseCodexSession(jsonlText) {
|
|
|
913
1838
|
gitBranch: metadata.gitBranch,
|
|
914
1839
|
cwd: metadata.cwd,
|
|
915
1840
|
slug: metadata.slug,
|
|
1841
|
+
name: "",
|
|
916
1842
|
model: metadata.model,
|
|
917
1843
|
turns,
|
|
918
|
-
stats: computeStats(turns),
|
|
919
|
-
rawMessages: records,
|
|
1844
|
+
stats: options?.skipStats ? createEmptySessionStats(turns.length) : computeStats(turns),
|
|
1845
|
+
rawMessages: options?.skipStats ? [] : records,
|
|
920
1846
|
branchedFrom: metadata.branchedFrom,
|
|
921
1847
|
agentKind: "codex"
|
|
922
1848
|
};
|
|
923
1849
|
}
|
|
924
1850
|
|
|
925
1851
|
// src/lib/parser.ts
|
|
926
|
-
function isAssistantMessage2(msg) {
|
|
927
|
-
return msg.type === "assistant";
|
|
928
|
-
}
|
|
929
1852
|
function extractTextFromContent2(content) {
|
|
930
1853
|
if (typeof content === "string") return content;
|
|
931
1854
|
return content.filter((b) => b.type === "text").map((b) => b.text).join("\n");
|
|
@@ -944,42 +1867,43 @@ function parseLines(jsonlText) {
|
|
|
944
1867
|
}
|
|
945
1868
|
function isCodexRawMessages(rawMessages) {
|
|
946
1869
|
const firstType = rawMessages[0]?.type;
|
|
947
|
-
return firstType === "session_meta" || firstType === "turn_context" || firstType === "event_msg" || firstType === "response_item";
|
|
1870
|
+
return firstType === "session_meta" || firstType === "turn_context" || firstType === "event_msg" || firstType === "response_item" || firstType === "compacted" || firstType === "world_state" || firstType === "inter_agent_communication_metadata";
|
|
948
1871
|
}
|
|
949
1872
|
function serializeRawMessages(rawMessages) {
|
|
950
1873
|
return rawMessages.map((msg) => JSON.stringify(msg)).join("\n");
|
|
951
1874
|
}
|
|
952
1875
|
function extractSessionMetadata(messages) {
|
|
953
|
-
const meta = { sessionId: "", version: "", gitBranch: "", cwd: "", slug: "", model: "", branchedFrom: void 0 };
|
|
1876
|
+
const meta = { sessionId: "", version: "", gitBranch: "", cwd: "", slug: "", name: "", model: "", branchedFrom: void 0 };
|
|
954
1877
|
for (const msg of messages) {
|
|
955
1878
|
if (msg.sessionId && !meta.sessionId) meta.sessionId = msg.sessionId;
|
|
956
1879
|
if (msg.version && !meta.version) meta.version = msg.version;
|
|
957
1880
|
if (msg.gitBranch && !meta.gitBranch) meta.gitBranch = msg.gitBranch;
|
|
958
1881
|
if (msg.cwd && !meta.cwd) meta.cwd = msg.cwd;
|
|
959
1882
|
if (msg.slug && !meta.slug) meta.slug = msg.slug;
|
|
1883
|
+
if (msg.name && !meta.name) meta.name = msg.name;
|
|
960
1884
|
if (msg.branchedFrom && !meta.branchedFrom) {
|
|
961
1885
|
meta.branchedFrom = msg.branchedFrom;
|
|
962
1886
|
}
|
|
963
|
-
if (
|
|
1887
|
+
if (isAssistantMessage(msg) && msg.message.model && !meta.model) {
|
|
964
1888
|
meta.model = msg.message.model;
|
|
965
1889
|
}
|
|
966
1890
|
if (meta.sessionId && meta.version && meta.gitBranch && meta.cwd && meta.model) break;
|
|
967
1891
|
}
|
|
968
1892
|
return meta;
|
|
969
1893
|
}
|
|
970
|
-
function parseSession(jsonlText,
|
|
1894
|
+
function parseSession(jsonlText, options) {
|
|
971
1895
|
if (isCodexSessionText(jsonlText)) {
|
|
972
|
-
return parseCodexSession(jsonlText);
|
|
1896
|
+
return parseCodexSession(jsonlText, options);
|
|
973
1897
|
}
|
|
974
1898
|
const rawMessages = parseLines(jsonlText);
|
|
975
1899
|
const metadata = extractSessionMetadata(rawMessages);
|
|
976
1900
|
const turns = buildTurns(rawMessages);
|
|
977
|
-
const stats =
|
|
1901
|
+
const stats = options?.skipStats ? createEmptySessionStats(turns.length) : computeStats(turns);
|
|
978
1902
|
return {
|
|
979
1903
|
...metadata,
|
|
980
1904
|
turns,
|
|
981
1905
|
stats,
|
|
982
|
-
rawMessages:
|
|
1906
|
+
rawMessages: options?.skipStats ? [] : rawMessages,
|
|
983
1907
|
agentKind: "claude"
|
|
984
1908
|
};
|
|
985
1909
|
}
|
|
@@ -991,13 +1915,14 @@ ${newJsonlText}` : newJsonlText);
|
|
|
991
1915
|
}
|
|
992
1916
|
const newMessages = parseLines(newJsonlText);
|
|
993
1917
|
if (newMessages.length === 0) return existing;
|
|
994
|
-
const
|
|
995
|
-
|
|
1918
|
+
const existingRawMessages = existing.rawMessages;
|
|
1919
|
+
const allRawMessages = [...existingRawMessages, ...newMessages];
|
|
1920
|
+
let lastTurnStartIdx = existingRawMessages.length;
|
|
996
1921
|
let turnsToKeep = existing.turns.length > 0 ? existing.turns.length - 1 : 0;
|
|
997
1922
|
if (existing.turns.length > 0) {
|
|
998
1923
|
let userMsgCount = 0;
|
|
999
|
-
for (let i =
|
|
1000
|
-
const msg =
|
|
1924
|
+
for (let i = existingRawMessages.length - 1; i >= 0; i--) {
|
|
1925
|
+
const msg = existingRawMessages[i];
|
|
1001
1926
|
if (msg.type === "user" && !msg.isMeta) {
|
|
1002
1927
|
const content = msg.message?.content;
|
|
1003
1928
|
if (Array.isArray(content) && content.some((b) => b.type === "tool_result")) {
|
|
@@ -1024,8 +1949,8 @@ ${newJsonlText}` : newJsonlText);
|
|
|
1024
1949
|
if (ownsProgressParent) {
|
|
1025
1950
|
turnsToKeep = t;
|
|
1026
1951
|
let found = 0;
|
|
1027
|
-
for (let i = 0; i <
|
|
1028
|
-
const msg =
|
|
1952
|
+
for (let i = 0; i < existingRawMessages.length; i++) {
|
|
1953
|
+
const msg = existingRawMessages[i];
|
|
1029
1954
|
if (msg.type === "user" && !msg.isMeta) {
|
|
1030
1955
|
const content = msg.message?.content;
|
|
1031
1956
|
if (Array.isArray(content) && content.some((b) => b.type === "tool_result")) {
|
|
@@ -1054,11 +1979,13 @@ ${newJsonlText}` : newJsonlText);
|
|
|
1054
1979
|
gitBranch: existing.gitBranch,
|
|
1055
1980
|
cwd: existing.cwd,
|
|
1056
1981
|
slug: existing.slug,
|
|
1982
|
+
name: existing.name,
|
|
1057
1983
|
model: existing.model || (allTurns.length > 0 ? allTurns[allTurns.length - 1].model || "" : ""),
|
|
1058
1984
|
turns: allTurns,
|
|
1059
1985
|
stats,
|
|
1060
1986
|
rawMessages: allRawMessages,
|
|
1061
|
-
branchedFrom: existing.branchedFrom
|
|
1987
|
+
branchedFrom: existing.branchedFrom,
|
|
1988
|
+
agentKind: existing.agentKind
|
|
1062
1989
|
};
|
|
1063
1990
|
}
|
|
1064
1991
|
function getUserMessageText(content) {
|
|
@@ -1136,6 +2063,18 @@ var SearchIndex = class {
|
|
|
1136
2063
|
lastUpdate: this._lastUpdate
|
|
1137
2064
|
};
|
|
1138
2065
|
}
|
|
2066
|
+
/** Index tool call inputs and results under the given location prefix. */
|
|
2067
|
+
insertToolCalls(insert, sessionId, filePath, prefix, toolCalls) {
|
|
2068
|
+
for (const tc of toolCalls) {
|
|
2069
|
+
const inputStr = JSON.stringify(tc.input);
|
|
2070
|
+
if (inputStr && inputStr !== "{}") {
|
|
2071
|
+
insert.run(sessionId, filePath, `${prefix}/toolCall/${tc.id}/input`, truncContent(inputStr));
|
|
2072
|
+
}
|
|
2073
|
+
if (tc.result) {
|
|
2074
|
+
insert.run(sessionId, filePath, `${prefix}/toolCall/${tc.id}/result`, truncContent(tc.result));
|
|
2075
|
+
}
|
|
2076
|
+
}
|
|
2077
|
+
}
|
|
1139
2078
|
/**
|
|
1140
2079
|
* Insert all searchable content from a parsed session into the FTS5 index.
|
|
1141
2080
|
* Shared by both `indexFile` (single-file) and `buildFull` (batch).
|
|
@@ -1156,15 +2095,7 @@ var SearchIndex = class {
|
|
|
1156
2095
|
if (thinkingText) {
|
|
1157
2096
|
insert.run(sessionId, filePath, `${prefix}/thinking`, truncContent(thinkingText));
|
|
1158
2097
|
}
|
|
1159
|
-
|
|
1160
|
-
const inputStr = JSON.stringify(tc.input);
|
|
1161
|
-
if (inputStr && inputStr !== "{}") {
|
|
1162
|
-
insert.run(sessionId, filePath, `${prefix}/toolCall/${tc.id}/input`, truncContent(inputStr));
|
|
1163
|
-
}
|
|
1164
|
-
if (tc.result) {
|
|
1165
|
-
insert.run(sessionId, filePath, `${prefix}/toolCall/${tc.id}/result`, truncContent(tc.result));
|
|
1166
|
-
}
|
|
1167
|
-
}
|
|
2098
|
+
this.insertToolCalls(insert, sessionId, filePath, prefix, turn.toolCalls);
|
|
1168
2099
|
for (const sa of turn.subAgentActivity) {
|
|
1169
2100
|
const saPrefix = `agent/${sa.agentId}`;
|
|
1170
2101
|
const saText = sa.text.join("\n\n").trim();
|
|
@@ -1175,15 +2106,7 @@ var SearchIndex = class {
|
|
|
1175
2106
|
if (saThinking) {
|
|
1176
2107
|
insert.run(sessionId, filePath, `${saPrefix}/thinking`, truncContent(saThinking));
|
|
1177
2108
|
}
|
|
1178
|
-
|
|
1179
|
-
const inputStr = JSON.stringify(tc.input);
|
|
1180
|
-
if (inputStr && inputStr !== "{}") {
|
|
1181
|
-
insert.run(sessionId, filePath, `${saPrefix}/toolCall/${tc.id}/input`, truncContent(inputStr));
|
|
1182
|
-
}
|
|
1183
|
-
if (tc.result) {
|
|
1184
|
-
insert.run(sessionId, filePath, `${saPrefix}/toolCall/${tc.id}/result`, truncContent(tc.result));
|
|
1185
|
-
}
|
|
1186
|
-
}
|
|
2109
|
+
this.insertToolCalls(insert, sessionId, filePath, saPrefix, sa.toolCalls);
|
|
1187
2110
|
}
|
|
1188
2111
|
if (turn.compactionSummary) {
|
|
1189
2112
|
insert.run(sessionId, filePath, `${prefix}/compactionSummary`, truncContent(turn.compactionSummary));
|
|
@@ -1230,7 +2153,7 @@ var SearchIndex = class {
|
|
|
1230
2153
|
/**
|
|
1231
2154
|
* Query the FTS5 index and return structured search results.
|
|
1232
2155
|
*
|
|
1233
|
-
* - FTS5
|
|
2156
|
+
* - FTS5 unicode61 tokenizer is case-insensitive by default.
|
|
1234
2157
|
* - When `caseSensitive` is true, a post-filter checks the original query
|
|
1235
2158
|
* against the snippet text (exact case match).
|
|
1236
2159
|
* - When `maxAgeMs` is provided, only files whose mtime in `indexed_files`
|
|
@@ -1320,17 +2243,11 @@ var SearchIndex = class {
|
|
|
1320
2243
|
buildFull(projectsDir) {
|
|
1321
2244
|
this.projectsDir = projectsDir;
|
|
1322
2245
|
this.db.close();
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
(0, import_node_fs.unlinkSync)(this.dbPath + "-wal");
|
|
1329
|
-
} catch {
|
|
1330
|
-
}
|
|
1331
|
-
try {
|
|
1332
|
-
(0, import_node_fs.unlinkSync)(this.dbPath + "-shm");
|
|
1333
|
-
} catch {
|
|
2246
|
+
for (const suffix of ["", "-wal", "-shm"]) {
|
|
2247
|
+
try {
|
|
2248
|
+
(0, import_node_fs.unlinkSync)(this.dbPath + suffix);
|
|
2249
|
+
} catch {
|
|
2250
|
+
}
|
|
1334
2251
|
}
|
|
1335
2252
|
this.db = new Database(this.dbPath);
|
|
1336
2253
|
this.db.exec("PRAGMA journal_mode = WAL");
|
|
@@ -1401,9 +2318,10 @@ var SearchIndex = class {
|
|
|
1401
2318
|
/**
|
|
1402
2319
|
* Lightweight incremental update for CLI search paths.
|
|
1403
2320
|
*
|
|
1404
|
-
*
|
|
1405
|
-
*
|
|
1406
|
-
*
|
|
2321
|
+
* Still walks and stats all files via `discoverFiles`, but skips DB lookups
|
|
2322
|
+
* for files with mtime <= the high-water mark that are already indexed.
|
|
2323
|
+
* Caps re-indexing to `maxFiles` to prevent blocking on large backlogs
|
|
2324
|
+
* (run `index rebuild` for a full catch-up).
|
|
1407
2325
|
*/
|
|
1408
2326
|
updateRecent(projectsDir, maxFiles = 50) {
|
|
1409
2327
|
this.projectsDir = projectsDir;
|
|
@@ -1601,7 +2519,8 @@ var import_node_os = require("node:os");
|
|
|
1601
2519
|
var dirs = {
|
|
1602
2520
|
PROJECTS_DIR: (0, import_node_path2.join)((0, import_node_os.homedir)(), ".claude", "projects"),
|
|
1603
2521
|
TEAMS_DIR: (0, import_node_path2.join)((0, import_node_os.homedir)(), ".claude", "teams"),
|
|
1604
|
-
TASKS_DIR: (0, import_node_path2.join)((0, import_node_os.homedir)(), ".claude", "tasks")
|
|
2522
|
+
TASKS_DIR: (0, import_node_path2.join)((0, import_node_os.homedir)(), ".claude", "tasks"),
|
|
2523
|
+
CODEX_SESSIONS_DIR: (0, import_node_path2.join)((0, import_node_os.homedir)(), ".codex", "sessions")
|
|
1605
2524
|
};
|
|
1606
2525
|
var DEFAULT_DB_PATH = (0, import_node_path2.join)((0, import_node_os.homedir)(), ".claude", "cogpit-memory", "search-index.db");
|
|
1607
2526
|
|
|
@@ -2073,6 +2992,8 @@ function extractUserMessageText(userMessage) {
|
|
|
2073
2992
|
for (const block of userMessage) {
|
|
2074
2993
|
if (block.type === "text") parts.push(block.text);
|
|
2075
2994
|
else if (block.type === "image") parts.push("[image attached]");
|
|
2995
|
+
else if (block.type === "document") parts.push("[document attached]");
|
|
2996
|
+
else if (block.type === "audio") parts.push("[audio attached]");
|
|
2076
2997
|
}
|
|
2077
2998
|
return parts.length > 0 ? parts.join("\n") : null;
|
|
2078
2999
|
}
|
|
@@ -2171,6 +3092,10 @@ function mapTurnToDetail(session, turnIndex) {
|
|
|
2171
3092
|
durationMs: turn.durationMs
|
|
2172
3093
|
};
|
|
2173
3094
|
}
|
|
3095
|
+
function mapToolCall(tc) {
|
|
3096
|
+
const { result, resultTruncated } = truncateResult(tc.result);
|
|
3097
|
+
return { id: tc.id, name: tc.name, input: tc.input, result, resultTruncated, isError: tc.isError };
|
|
3098
|
+
}
|
|
2174
3099
|
function mapContentBlock(block) {
|
|
2175
3100
|
switch (block.kind) {
|
|
2176
3101
|
case "thinking": {
|
|
@@ -2182,10 +3107,7 @@ function mapContentBlock(block) {
|
|
|
2182
3107
|
case "tool_calls":
|
|
2183
3108
|
return {
|
|
2184
3109
|
kind: "tool_calls",
|
|
2185
|
-
toolCalls: block.toolCalls.map(
|
|
2186
|
-
const { result, resultTruncated } = truncateResult(tc.result);
|
|
2187
|
-
return { id: tc.id, name: tc.name, input: tc.input, result, resultTruncated, isError: tc.isError };
|
|
2188
|
-
}),
|
|
3110
|
+
toolCalls: block.toolCalls.map(mapToolCall),
|
|
2189
3111
|
timestamp: block.timestamp ?? null
|
|
2190
3112
|
};
|
|
2191
3113
|
case "sub_agent":
|
|
@@ -2195,6 +3117,21 @@ function mapContentBlock(block) {
|
|
|
2195
3117
|
agents: block.messages.map(mapSubAgentDetail),
|
|
2196
3118
|
timestamp: block.timestamp ?? null
|
|
2197
3119
|
};
|
|
3120
|
+
case "queued_prompt":
|
|
3121
|
+
return { kind: "queued_prompt", content: block.content, timestamp: block.timestamp ?? null };
|
|
3122
|
+
case "hook_event":
|
|
3123
|
+
return { kind: "hook_event", events: block.events, timestamp: block.timestamp ?? null };
|
|
3124
|
+
case "plan_mode":
|
|
3125
|
+
return {
|
|
3126
|
+
kind: "plan_mode",
|
|
3127
|
+
plan: block.plan,
|
|
3128
|
+
planFilePath: block.planFilePath ?? null,
|
|
3129
|
+
status: block.status,
|
|
3130
|
+
toolCalls: block.toolCalls.map(mapToolCall),
|
|
3131
|
+
timestamp: block.timestamp ?? null
|
|
3132
|
+
};
|
|
3133
|
+
case "recap":
|
|
3134
|
+
return { kind: "recap", content: block.content, timestamp: block.timestamp ?? null };
|
|
2198
3135
|
}
|
|
2199
3136
|
}
|
|
2200
3137
|
async function findSubagentFile(parentJsonlPath, agentId) {
|
|
@@ -2317,7 +3254,6 @@ async function getAgentOverview(sessionId, agentId) {
|
|
|
2317
3254
|
async function getAgentTurnDetail(sessionId, agentId, turnIndex) {
|
|
2318
3255
|
const jsonlPath = await findJsonlPath(sessionId);
|
|
2319
3256
|
if (!jsonlPath) return { error: "Session not found" };
|
|
2320
|
-
const content = await (0, import_promises3.readFile)(jsonlPath, "utf-8");
|
|
2321
3257
|
const subagentFile = await findSubagentFile(jsonlPath, agentId);
|
|
2322
3258
|
if (!subagentFile) return { error: "Agent not found" };
|
|
2323
3259
|
const subagentContent = await (0, import_promises3.readFile)(subagentFile.filePath, "utf-8");
|
|
@@ -2329,7 +3265,6 @@ async function getAgentTurnDetail(sessionId, agentId, turnIndex) {
|
|
|
2329
3265
|
// src/commands/sessions.ts
|
|
2330
3266
|
var import_promises5 = require("node:fs/promises");
|
|
2331
3267
|
var import_node_path6 = require("node:path");
|
|
2332
|
-
var import_node_os3 = require("node:os");
|
|
2333
3268
|
|
|
2334
3269
|
// src/lib/metadata.ts
|
|
2335
3270
|
var import_promises4 = require("node:fs/promises");
|
|
@@ -2346,6 +3281,19 @@ function deriveSessionStatus(rawMessages) {
|
|
|
2346
3281
|
if (toolName) info.toolName = toolName;
|
|
2347
3282
|
return info;
|
|
2348
3283
|
}
|
|
3284
|
+
for (let i = rawMessages.length - 1; i >= 0; i--) {
|
|
3285
|
+
const msg = rawMessages[i];
|
|
3286
|
+
if (msg.type === "queue-operation") continue;
|
|
3287
|
+
if (msg.type === "progress") {
|
|
3288
|
+
const data = msg.data;
|
|
3289
|
+
if (data?.type === "hook_progress") {
|
|
3290
|
+
const decision = data.decision ?? data.hookSpecificOutput?.permissionDecision;
|
|
3291
|
+
if (decision === "defer") return result("deferred");
|
|
3292
|
+
}
|
|
3293
|
+
break;
|
|
3294
|
+
}
|
|
3295
|
+
break;
|
|
3296
|
+
}
|
|
2349
3297
|
for (let i = rawMessages.length - 1; i >= 0; i--) {
|
|
2350
3298
|
const msg = rawMessages[i];
|
|
2351
3299
|
if (msg.type === "queue-operation") {
|
|
@@ -2380,6 +3328,13 @@ function deriveSessionStatus(rawMessages) {
|
|
|
2380
3328
|
if (isMeta) continue;
|
|
2381
3329
|
return result("processing");
|
|
2382
3330
|
}
|
|
3331
|
+
if (msg.type === "system" && msg.subtype === "terminal_reason") {
|
|
3332
|
+
const reason = msg.reason;
|
|
3333
|
+
if (reason) {
|
|
3334
|
+
return { status: "completed", terminalReason: reason, pendingQueue: Math.max(0, pendingEnqueues) };
|
|
3335
|
+
}
|
|
3336
|
+
continue;
|
|
3337
|
+
}
|
|
2383
3338
|
if (msg.type === "summary") continue;
|
|
2384
3339
|
if (msg.type === "system" && msg.subtype === "compact_boundary") continue;
|
|
2385
3340
|
}
|
|
@@ -2605,7 +3560,23 @@ async function getSessionStatus(filePath) {
|
|
|
2605
3560
|
}
|
|
2606
3561
|
|
|
2607
3562
|
// src/commands/sessions.ts
|
|
2608
|
-
|
|
3563
|
+
function toSessionSummary(file, meta, status) {
|
|
3564
|
+
return {
|
|
3565
|
+
sessionId: meta.sessionId,
|
|
3566
|
+
filePath: file.path,
|
|
3567
|
+
timestamp: meta.timestamp,
|
|
3568
|
+
model: meta.model,
|
|
3569
|
+
cwd: meta.cwd,
|
|
3570
|
+
gitBranch: meta.gitBranch,
|
|
3571
|
+
slug: meta.slug,
|
|
3572
|
+
firstMessage: meta.firstUserMessage,
|
|
3573
|
+
lastMessage: meta.lastUserMessage,
|
|
3574
|
+
turnCount: meta.turnCount,
|
|
3575
|
+
status,
|
|
3576
|
+
mtime: file.mtimeMs,
|
|
3577
|
+
source: file.path.startsWith(dirs.CODEX_SESSIONS_DIR + "/") ? "codex" : "claude"
|
|
3578
|
+
};
|
|
3579
|
+
}
|
|
2609
3580
|
async function listCodexSessionFiles(cutoff) {
|
|
2610
3581
|
const walk = async (dir, depth) => {
|
|
2611
3582
|
if (depth > 4) return [];
|
|
@@ -2632,7 +3603,7 @@ async function listCodexSessionFiles(cutoff) {
|
|
|
2632
3603
|
}
|
|
2633
3604
|
return results;
|
|
2634
3605
|
};
|
|
2635
|
-
return walk(CODEX_SESSIONS_DIR, 0);
|
|
3606
|
+
return walk(dirs.CODEX_SESSIONS_DIR, 0);
|
|
2636
3607
|
}
|
|
2637
3608
|
async function listSessions(opts = {}) {
|
|
2638
3609
|
const limit = Math.min(Math.max(1, opts.limit ?? 20), 100);
|
|
@@ -2645,29 +3616,31 @@ async function listSessions(opts = {}) {
|
|
|
2645
3616
|
return [];
|
|
2646
3617
|
}
|
|
2647
3618
|
const projectDirs = entries.filter((e) => e.isDirectory() && e.name !== "memory").map((e) => (0, import_node_path6.join)(dirs.PROJECTS_DIR, e.name));
|
|
2648
|
-
const nested = await Promise.all(
|
|
2649
|
-
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
|
|
2656
|
-
|
|
2657
|
-
|
|
2658
|
-
|
|
2659
|
-
|
|
2660
|
-
|
|
2661
|
-
|
|
2662
|
-
|
|
2663
|
-
|
|
2664
|
-
|
|
2665
|
-
|
|
2666
|
-
|
|
2667
|
-
|
|
2668
|
-
|
|
2669
|
-
|
|
2670
|
-
|
|
3619
|
+
const [nested, codexFiles] = await Promise.all([
|
|
3620
|
+
Promise.all(
|
|
3621
|
+
projectDirs.map(async (projectDir) => {
|
|
3622
|
+
try {
|
|
3623
|
+
const files = await (0, import_promises5.readdir)(projectDir);
|
|
3624
|
+
const jsonlFiles = files.filter((f) => f.endsWith(".jsonl"));
|
|
3625
|
+
const statResults = await Promise.all(
|
|
3626
|
+
jsonlFiles.map(async (f) => {
|
|
3627
|
+
const filePath = (0, import_node_path6.join)(projectDir, f);
|
|
3628
|
+
try {
|
|
3629
|
+
const s = await (0, import_promises5.stat)(filePath);
|
|
3630
|
+
return s.mtimeMs >= cutoff ? { path: filePath, mtimeMs: s.mtimeMs } : null;
|
|
3631
|
+
} catch {
|
|
3632
|
+
return null;
|
|
3633
|
+
}
|
|
3634
|
+
})
|
|
3635
|
+
);
|
|
3636
|
+
return statResults.filter((r) => r !== null);
|
|
3637
|
+
} catch {
|
|
3638
|
+
return [];
|
|
3639
|
+
}
|
|
3640
|
+
})
|
|
3641
|
+
),
|
|
3642
|
+
listCodexSessionFiles(cutoff)
|
|
3643
|
+
]);
|
|
2671
3644
|
const allFiles = [...nested.flat(), ...codexFiles];
|
|
2672
3645
|
allFiles.sort((a, b) => b.mtimeMs - a.mtimeMs);
|
|
2673
3646
|
const results = [];
|
|
@@ -2679,21 +3652,7 @@ async function listSessions(opts = {}) {
|
|
|
2679
3652
|
getSessionStatus(file.path)
|
|
2680
3653
|
]);
|
|
2681
3654
|
if (opts.cwd && meta.cwd !== opts.cwd) continue;
|
|
2682
|
-
results.push(
|
|
2683
|
-
sessionId: meta.sessionId,
|
|
2684
|
-
filePath: file.path,
|
|
2685
|
-
timestamp: meta.timestamp,
|
|
2686
|
-
model: meta.model,
|
|
2687
|
-
cwd: meta.cwd,
|
|
2688
|
-
gitBranch: meta.gitBranch,
|
|
2689
|
-
slug: meta.slug,
|
|
2690
|
-
firstMessage: meta.firstUserMessage,
|
|
2691
|
-
lastMessage: meta.lastUserMessage,
|
|
2692
|
-
turnCount: meta.turnCount,
|
|
2693
|
-
status: statusInfo.status,
|
|
2694
|
-
mtime: file.mtimeMs,
|
|
2695
|
-
source: file.path.startsWith(CODEX_SESSIONS_DIR + "/") ? "codex" : "claude"
|
|
2696
|
-
});
|
|
3655
|
+
results.push(toSessionSummary(file, meta, statusInfo.status));
|
|
2697
3656
|
} catch {
|
|
2698
3657
|
}
|
|
2699
3658
|
}
|
|
@@ -2709,18 +3668,20 @@ async function currentSession(cwd) {
|
|
|
2709
3668
|
files = [];
|
|
2710
3669
|
}
|
|
2711
3670
|
const jsonlFiles = files.filter((f) => f.endsWith(".jsonl"));
|
|
2712
|
-
const statResults = await Promise.all(
|
|
2713
|
-
|
|
2714
|
-
|
|
2715
|
-
|
|
2716
|
-
|
|
2717
|
-
|
|
2718
|
-
|
|
2719
|
-
|
|
2720
|
-
|
|
2721
|
-
|
|
2722
|
-
|
|
2723
|
-
|
|
3671
|
+
const [statResults, codexCandidates] = await Promise.all([
|
|
3672
|
+
Promise.all(
|
|
3673
|
+
jsonlFiles.map(async (f) => {
|
|
3674
|
+
const filePath = (0, import_node_path6.join)(projectDir, f);
|
|
3675
|
+
try {
|
|
3676
|
+
const s = await (0, import_promises5.stat)(filePath);
|
|
3677
|
+
return { path: filePath, mtimeMs: s.mtimeMs };
|
|
3678
|
+
} catch {
|
|
3679
|
+
return null;
|
|
3680
|
+
}
|
|
3681
|
+
})
|
|
3682
|
+
),
|
|
3683
|
+
listCodexSessionFiles(0)
|
|
3684
|
+
]);
|
|
2724
3685
|
const codexMatches = [];
|
|
2725
3686
|
for (const file of codexCandidates) {
|
|
2726
3687
|
try {
|
|
@@ -2742,21 +3703,7 @@ async function currentSession(cwd) {
|
|
|
2742
3703
|
getSessionMeta(latest.path),
|
|
2743
3704
|
getSessionStatus(latest.path)
|
|
2744
3705
|
]);
|
|
2745
|
-
return
|
|
2746
|
-
sessionId: meta.sessionId,
|
|
2747
|
-
filePath: latest.path,
|
|
2748
|
-
timestamp: meta.timestamp,
|
|
2749
|
-
model: meta.model,
|
|
2750
|
-
cwd: meta.cwd,
|
|
2751
|
-
gitBranch: meta.gitBranch,
|
|
2752
|
-
slug: meta.slug,
|
|
2753
|
-
firstMessage: meta.firstUserMessage,
|
|
2754
|
-
lastMessage: meta.lastUserMessage,
|
|
2755
|
-
turnCount: meta.turnCount,
|
|
2756
|
-
status: statusInfo.status,
|
|
2757
|
-
mtime: latest.mtimeMs,
|
|
2758
|
-
source: latest.path.startsWith(CODEX_SESSIONS_DIR + "/") ? "codex" : "claude"
|
|
2759
|
-
};
|
|
3706
|
+
return toSessionSummary(latest, meta, statusInfo.status);
|
|
2760
3707
|
}
|
|
2761
3708
|
|
|
2762
3709
|
// src/commands/index-cmd.ts
|