cogpit-memory 0.1.9 → 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 +1114 -178
- 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 +1117 -180
- 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,55 +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
|
|
971
|
-
return {
|
|
972
|
-
totalInputTokens: 0,
|
|
973
|
-
totalOutputTokens: 0,
|
|
974
|
-
totalCacheCreationTokens: 0,
|
|
975
|
-
totalCacheReadTokens: 0,
|
|
976
|
-
totalCostUSD: 0,
|
|
977
|
-
toolCallCounts: {},
|
|
978
|
-
errorCount: 0,
|
|
979
|
-
totalDurationMs: 0,
|
|
980
|
-
turnCount
|
|
981
|
-
};
|
|
982
|
-
}
|
|
983
|
-
function parseSession(jsonlText, opts) {
|
|
1894
|
+
function parseSession(jsonlText, options) {
|
|
984
1895
|
if (isCodexSessionText(jsonlText)) {
|
|
985
|
-
return parseCodexSession(jsonlText);
|
|
1896
|
+
return parseCodexSession(jsonlText, options);
|
|
986
1897
|
}
|
|
987
1898
|
const rawMessages = parseLines(jsonlText);
|
|
988
1899
|
const metadata = extractSessionMetadata(rawMessages);
|
|
989
1900
|
const turns = buildTurns(rawMessages);
|
|
990
|
-
const stats =
|
|
1901
|
+
const stats = options?.skipStats ? createEmptySessionStats(turns.length) : computeStats(turns);
|
|
991
1902
|
return {
|
|
992
1903
|
...metadata,
|
|
993
1904
|
turns,
|
|
994
1905
|
stats,
|
|
995
|
-
rawMessages:
|
|
1906
|
+
rawMessages: options?.skipStats ? [] : rawMessages,
|
|
996
1907
|
agentKind: "claude"
|
|
997
1908
|
};
|
|
998
1909
|
}
|
|
@@ -1004,13 +1915,14 @@ ${newJsonlText}` : newJsonlText);
|
|
|
1004
1915
|
}
|
|
1005
1916
|
const newMessages = parseLines(newJsonlText);
|
|
1006
1917
|
if (newMessages.length === 0) return existing;
|
|
1007
|
-
const
|
|
1008
|
-
|
|
1918
|
+
const existingRawMessages = existing.rawMessages;
|
|
1919
|
+
const allRawMessages = [...existingRawMessages, ...newMessages];
|
|
1920
|
+
let lastTurnStartIdx = existingRawMessages.length;
|
|
1009
1921
|
let turnsToKeep = existing.turns.length > 0 ? existing.turns.length - 1 : 0;
|
|
1010
1922
|
if (existing.turns.length > 0) {
|
|
1011
1923
|
let userMsgCount = 0;
|
|
1012
|
-
for (let i =
|
|
1013
|
-
const msg =
|
|
1924
|
+
for (let i = existingRawMessages.length - 1; i >= 0; i--) {
|
|
1925
|
+
const msg = existingRawMessages[i];
|
|
1014
1926
|
if (msg.type === "user" && !msg.isMeta) {
|
|
1015
1927
|
const content = msg.message?.content;
|
|
1016
1928
|
if (Array.isArray(content) && content.some((b) => b.type === "tool_result")) {
|
|
@@ -1037,8 +1949,8 @@ ${newJsonlText}` : newJsonlText);
|
|
|
1037
1949
|
if (ownsProgressParent) {
|
|
1038
1950
|
turnsToKeep = t;
|
|
1039
1951
|
let found = 0;
|
|
1040
|
-
for (let i = 0; i <
|
|
1041
|
-
const msg =
|
|
1952
|
+
for (let i = 0; i < existingRawMessages.length; i++) {
|
|
1953
|
+
const msg = existingRawMessages[i];
|
|
1042
1954
|
if (msg.type === "user" && !msg.isMeta) {
|
|
1043
1955
|
const content = msg.message?.content;
|
|
1044
1956
|
if (Array.isArray(content) && content.some((b) => b.type === "tool_result")) {
|
|
@@ -1067,11 +1979,13 @@ ${newJsonlText}` : newJsonlText);
|
|
|
1067
1979
|
gitBranch: existing.gitBranch,
|
|
1068
1980
|
cwd: existing.cwd,
|
|
1069
1981
|
slug: existing.slug,
|
|
1982
|
+
name: existing.name,
|
|
1070
1983
|
model: existing.model || (allTurns.length > 0 ? allTurns[allTurns.length - 1].model || "" : ""),
|
|
1071
1984
|
turns: allTurns,
|
|
1072
1985
|
stats,
|
|
1073
1986
|
rawMessages: allRawMessages,
|
|
1074
|
-
branchedFrom: existing.branchedFrom
|
|
1987
|
+
branchedFrom: existing.branchedFrom,
|
|
1988
|
+
agentKind: existing.agentKind
|
|
1075
1989
|
};
|
|
1076
1990
|
}
|
|
1077
1991
|
function getUserMessageText(content) {
|
|
@@ -1329,17 +2243,11 @@ var SearchIndex = class {
|
|
|
1329
2243
|
buildFull(projectsDir) {
|
|
1330
2244
|
this.projectsDir = projectsDir;
|
|
1331
2245
|
this.db.close();
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
(0, import_node_fs.unlinkSync)(this.dbPath + "-wal");
|
|
1338
|
-
} catch {
|
|
1339
|
-
}
|
|
1340
|
-
try {
|
|
1341
|
-
(0, import_node_fs.unlinkSync)(this.dbPath + "-shm");
|
|
1342
|
-
} catch {
|
|
2246
|
+
for (const suffix of ["", "-wal", "-shm"]) {
|
|
2247
|
+
try {
|
|
2248
|
+
(0, import_node_fs.unlinkSync)(this.dbPath + suffix);
|
|
2249
|
+
} catch {
|
|
2250
|
+
}
|
|
1343
2251
|
}
|
|
1344
2252
|
this.db = new Database(this.dbPath);
|
|
1345
2253
|
this.db.exec("PRAGMA journal_mode = WAL");
|
|
@@ -1611,7 +2519,8 @@ var import_node_os = require("node:os");
|
|
|
1611
2519
|
var dirs = {
|
|
1612
2520
|
PROJECTS_DIR: (0, import_node_path2.join)((0, import_node_os.homedir)(), ".claude", "projects"),
|
|
1613
2521
|
TEAMS_DIR: (0, import_node_path2.join)((0, import_node_os.homedir)(), ".claude", "teams"),
|
|
1614
|
-
TASKS_DIR: (0, import_node_path2.join)((0, import_node_os.homedir)(), ".claude", "tasks")
|
|
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")
|
|
1615
2524
|
};
|
|
1616
2525
|
var DEFAULT_DB_PATH = (0, import_node_path2.join)((0, import_node_os.homedir)(), ".claude", "cogpit-memory", "search-index.db");
|
|
1617
2526
|
|
|
@@ -2083,6 +2992,8 @@ function extractUserMessageText(userMessage) {
|
|
|
2083
2992
|
for (const block of userMessage) {
|
|
2084
2993
|
if (block.type === "text") parts.push(block.text);
|
|
2085
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]");
|
|
2086
2997
|
}
|
|
2087
2998
|
return parts.length > 0 ? parts.join("\n") : null;
|
|
2088
2999
|
}
|
|
@@ -2181,6 +3092,10 @@ function mapTurnToDetail(session, turnIndex) {
|
|
|
2181
3092
|
durationMs: turn.durationMs
|
|
2182
3093
|
};
|
|
2183
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
|
+
}
|
|
2184
3099
|
function mapContentBlock(block) {
|
|
2185
3100
|
switch (block.kind) {
|
|
2186
3101
|
case "thinking": {
|
|
@@ -2192,10 +3107,7 @@ function mapContentBlock(block) {
|
|
|
2192
3107
|
case "tool_calls":
|
|
2193
3108
|
return {
|
|
2194
3109
|
kind: "tool_calls",
|
|
2195
|
-
toolCalls: block.toolCalls.map(
|
|
2196
|
-
const { result, resultTruncated } = truncateResult(tc.result);
|
|
2197
|
-
return { id: tc.id, name: tc.name, input: tc.input, result, resultTruncated, isError: tc.isError };
|
|
2198
|
-
}),
|
|
3110
|
+
toolCalls: block.toolCalls.map(mapToolCall),
|
|
2199
3111
|
timestamp: block.timestamp ?? null
|
|
2200
3112
|
};
|
|
2201
3113
|
case "sub_agent":
|
|
@@ -2205,6 +3117,21 @@ function mapContentBlock(block) {
|
|
|
2205
3117
|
agents: block.messages.map(mapSubAgentDetail),
|
|
2206
3118
|
timestamp: block.timestamp ?? null
|
|
2207
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 };
|
|
2208
3135
|
}
|
|
2209
3136
|
}
|
|
2210
3137
|
async function findSubagentFile(parentJsonlPath, agentId) {
|
|
@@ -2327,7 +3254,6 @@ async function getAgentOverview(sessionId, agentId) {
|
|
|
2327
3254
|
async function getAgentTurnDetail(sessionId, agentId, turnIndex) {
|
|
2328
3255
|
const jsonlPath = await findJsonlPath(sessionId);
|
|
2329
3256
|
if (!jsonlPath) return { error: "Session not found" };
|
|
2330
|
-
const content = await (0, import_promises3.readFile)(jsonlPath, "utf-8");
|
|
2331
3257
|
const subagentFile = await findSubagentFile(jsonlPath, agentId);
|
|
2332
3258
|
if (!subagentFile) return { error: "Agent not found" };
|
|
2333
3259
|
const subagentContent = await (0, import_promises3.readFile)(subagentFile.filePath, "utf-8");
|
|
@@ -2339,7 +3265,6 @@ async function getAgentTurnDetail(sessionId, agentId, turnIndex) {
|
|
|
2339
3265
|
// src/commands/sessions.ts
|
|
2340
3266
|
var import_promises5 = require("node:fs/promises");
|
|
2341
3267
|
var import_node_path6 = require("node:path");
|
|
2342
|
-
var import_node_os3 = require("node:os");
|
|
2343
3268
|
|
|
2344
3269
|
// src/lib/metadata.ts
|
|
2345
3270
|
var import_promises4 = require("node:fs/promises");
|
|
@@ -2356,6 +3281,19 @@ function deriveSessionStatus(rawMessages) {
|
|
|
2356
3281
|
if (toolName) info.toolName = toolName;
|
|
2357
3282
|
return info;
|
|
2358
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
|
+
}
|
|
2359
3297
|
for (let i = rawMessages.length - 1; i >= 0; i--) {
|
|
2360
3298
|
const msg = rawMessages[i];
|
|
2361
3299
|
if (msg.type === "queue-operation") {
|
|
@@ -2390,6 +3328,13 @@ function deriveSessionStatus(rawMessages) {
|
|
|
2390
3328
|
if (isMeta) continue;
|
|
2391
3329
|
return result("processing");
|
|
2392
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
|
+
}
|
|
2393
3338
|
if (msg.type === "summary") continue;
|
|
2394
3339
|
if (msg.type === "system" && msg.subtype === "compact_boundary") continue;
|
|
2395
3340
|
}
|
|
@@ -2615,7 +3560,23 @@ async function getSessionStatus(filePath) {
|
|
|
2615
3560
|
}
|
|
2616
3561
|
|
|
2617
3562
|
// src/commands/sessions.ts
|
|
2618
|
-
|
|
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
|
+
}
|
|
2619
3580
|
async function listCodexSessionFiles(cutoff) {
|
|
2620
3581
|
const walk = async (dir, depth) => {
|
|
2621
3582
|
if (depth > 4) return [];
|
|
@@ -2642,7 +3603,7 @@ async function listCodexSessionFiles(cutoff) {
|
|
|
2642
3603
|
}
|
|
2643
3604
|
return results;
|
|
2644
3605
|
};
|
|
2645
|
-
return walk(CODEX_SESSIONS_DIR, 0);
|
|
3606
|
+
return walk(dirs.CODEX_SESSIONS_DIR, 0);
|
|
2646
3607
|
}
|
|
2647
3608
|
async function listSessions(opts = {}) {
|
|
2648
3609
|
const limit = Math.min(Math.max(1, opts.limit ?? 20), 100);
|
|
@@ -2655,29 +3616,31 @@ async function listSessions(opts = {}) {
|
|
|
2655
3616
|
return [];
|
|
2656
3617
|
}
|
|
2657
3618
|
const projectDirs = entries.filter((e) => e.isDirectory() && e.name !== "memory").map((e) => (0, import_node_path6.join)(dirs.PROJECTS_DIR, e.name));
|
|
2658
|
-
const nested = await Promise.all(
|
|
2659
|
-
|
|
2660
|
-
|
|
2661
|
-
|
|
2662
|
-
|
|
2663
|
-
|
|
2664
|
-
|
|
2665
|
-
|
|
2666
|
-
|
|
2667
|
-
|
|
2668
|
-
|
|
2669
|
-
|
|
2670
|
-
|
|
2671
|
-
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
|
|
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
|
+
]);
|
|
2681
3644
|
const allFiles = [...nested.flat(), ...codexFiles];
|
|
2682
3645
|
allFiles.sort((a, b) => b.mtimeMs - a.mtimeMs);
|
|
2683
3646
|
const results = [];
|
|
@@ -2689,21 +3652,7 @@ async function listSessions(opts = {}) {
|
|
|
2689
3652
|
getSessionStatus(file.path)
|
|
2690
3653
|
]);
|
|
2691
3654
|
if (opts.cwd && meta.cwd !== opts.cwd) continue;
|
|
2692
|
-
results.push(
|
|
2693
|
-
sessionId: meta.sessionId,
|
|
2694
|
-
filePath: file.path,
|
|
2695
|
-
timestamp: meta.timestamp,
|
|
2696
|
-
model: meta.model,
|
|
2697
|
-
cwd: meta.cwd,
|
|
2698
|
-
gitBranch: meta.gitBranch,
|
|
2699
|
-
slug: meta.slug,
|
|
2700
|
-
firstMessage: meta.firstUserMessage,
|
|
2701
|
-
lastMessage: meta.lastUserMessage,
|
|
2702
|
-
turnCount: meta.turnCount,
|
|
2703
|
-
status: statusInfo.status,
|
|
2704
|
-
mtime: file.mtimeMs,
|
|
2705
|
-
source: file.path.startsWith(CODEX_SESSIONS_DIR + "/") ? "codex" : "claude"
|
|
2706
|
-
});
|
|
3655
|
+
results.push(toSessionSummary(file, meta, statusInfo.status));
|
|
2707
3656
|
} catch {
|
|
2708
3657
|
}
|
|
2709
3658
|
}
|
|
@@ -2719,18 +3668,20 @@ async function currentSession(cwd) {
|
|
|
2719
3668
|
files = [];
|
|
2720
3669
|
}
|
|
2721
3670
|
const jsonlFiles = files.filter((f) => f.endsWith(".jsonl"));
|
|
2722
|
-
const statResults = await Promise.all(
|
|
2723
|
-
|
|
2724
|
-
|
|
2725
|
-
|
|
2726
|
-
|
|
2727
|
-
|
|
2728
|
-
|
|
2729
|
-
|
|
2730
|
-
|
|
2731
|
-
|
|
2732
|
-
|
|
2733
|
-
|
|
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
|
+
]);
|
|
2734
3685
|
const codexMatches = [];
|
|
2735
3686
|
for (const file of codexCandidates) {
|
|
2736
3687
|
try {
|
|
@@ -2752,21 +3703,7 @@ async function currentSession(cwd) {
|
|
|
2752
3703
|
getSessionMeta(latest.path),
|
|
2753
3704
|
getSessionStatus(latest.path)
|
|
2754
3705
|
]);
|
|
2755
|
-
return
|
|
2756
|
-
sessionId: meta.sessionId,
|
|
2757
|
-
filePath: latest.path,
|
|
2758
|
-
timestamp: meta.timestamp,
|
|
2759
|
-
model: meta.model,
|
|
2760
|
-
cwd: meta.cwd,
|
|
2761
|
-
gitBranch: meta.gitBranch,
|
|
2762
|
-
slug: meta.slug,
|
|
2763
|
-
firstMessage: meta.firstUserMessage,
|
|
2764
|
-
lastMessage: meta.lastUserMessage,
|
|
2765
|
-
turnCount: meta.turnCount,
|
|
2766
|
-
status: statusInfo.status,
|
|
2767
|
-
mtime: latest.mtimeMs,
|
|
2768
|
-
source: latest.path.startsWith(CODEX_SESSIONS_DIR + "/") ? "codex" : "claude"
|
|
2769
|
-
};
|
|
3706
|
+
return toSessionSummary(latest, meta, statusInfo.status);
|
|
2770
3707
|
}
|
|
2771
3708
|
|
|
2772
3709
|
// src/commands/index-cmd.ts
|