cogpit-memory 0.2.1 → 0.3.0
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 +34 -25
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +3261 -1726
- package/dist/commands/search.d.ts +1 -1
- package/dist/commands/sessions.d.ts +2 -1
- package/dist/index.d.ts +0 -1
- package/dist/index.js +2762 -1300
- package/dist/lib/agent-descriptors.d.ts +387 -0
- package/dist/lib/agents.d.ts +112 -0
- package/dist/lib/claude.d.ts +49 -0
- package/dist/lib/codex-exec.d.ts +12 -0
- package/dist/lib/codex-tool-normalization.d.ts +25 -4
- package/dist/lib/codex.d.ts +29 -1
- package/dist/lib/copilot.d.ts +51 -0
- package/dist/lib/dirs.d.ts +9 -0
- package/dist/lib/helpers.d.ts +0 -20
- package/dist/lib/interactiveState.d.ts +9 -0
- package/dist/lib/metadata.d.ts +16 -14
- package/dist/lib/parser.d.ts +6 -4
- package/dist/lib/search-index.d.ts +7 -67
- package/dist/lib/sessionStats.d.ts +2 -1
- package/dist/lib/sessionStatus.d.ts +11 -32
- package/dist/lib/stores.d.ts +47 -0
- package/dist/lib/turnBuilder.d.ts +22 -0
- package/dist/lib/turnContent.d.ts +2 -0
- package/dist/lib/types.d.ts +41 -3
- package/package.json +3 -2
- package/skill/SKILL.md +58 -48
- package/dist/lib/token-costs.d.ts +0 -27
package/dist/index.js
CHANGED
|
@@ -54,8 +54,526 @@ var import_better_sqlite3 = __toESM(require("better-sqlite3"));
|
|
|
54
54
|
var Database = import_better_sqlite3.default;
|
|
55
55
|
|
|
56
56
|
// src/lib/search-index.ts
|
|
57
|
-
var
|
|
58
|
-
|
|
57
|
+
var import_node_fs2 = require("node:fs");
|
|
58
|
+
|
|
59
|
+
// src/lib/types.ts
|
|
60
|
+
var AGENT_KINDS = ["codex", "copilot", "claude"];
|
|
61
|
+
|
|
62
|
+
// src/lib/agent-descriptors.ts
|
|
63
|
+
var SESSION_UUID = "[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}";
|
|
64
|
+
var SESSION_UUID_RE = new RegExp(`^${SESSION_UUID}$`, "i");
|
|
65
|
+
function encodeBase64DirName(prefix, cwd) {
|
|
66
|
+
const bytes = new TextEncoder().encode(cwd);
|
|
67
|
+
let binary = "";
|
|
68
|
+
for (const byte of bytes) binary += String.fromCharCode(byte);
|
|
69
|
+
const encoded = btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
70
|
+
return `${prefix}${encoded}`;
|
|
71
|
+
}
|
|
72
|
+
function decodeBase64DirName(prefix, dirName) {
|
|
73
|
+
if (!dirName.startsWith(prefix)) return null;
|
|
74
|
+
try {
|
|
75
|
+
const encoded = dirName.slice(prefix.length).replace(/-/g, "+").replace(/_/g, "/");
|
|
76
|
+
const binary = atob(encoded);
|
|
77
|
+
const bytes = Uint8Array.from(binary, (character) => character.charCodeAt(0));
|
|
78
|
+
return new TextDecoder().decode(bytes);
|
|
79
|
+
} catch {
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
function base64DirNameCodec(prefix) {
|
|
84
|
+
return {
|
|
85
|
+
lossy: false,
|
|
86
|
+
owns: (dirName) => typeof dirName === "string" && dirName.startsWith(prefix),
|
|
87
|
+
encode: (cwd) => encodeBase64DirName(prefix, cwd),
|
|
88
|
+
decode: (dirName) => decodeBase64DirName(prefix, dirName)
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
function sessionIdFromJsonlName(fileName) {
|
|
92
|
+
const match = new RegExp(`(${SESSION_UUID})\\.jsonl$`, "i").exec(fileName);
|
|
93
|
+
return match ? match[1] : null;
|
|
94
|
+
}
|
|
95
|
+
var jsonlPathUrlCodec = {
|
|
96
|
+
urlId: (fileName) => fileName.replace(/\.jsonl$/, ""),
|
|
97
|
+
fileNameFromUrlId: (urlId) => urlId.endsWith(".jsonl") ? urlId : `${urlId}.jsonl`
|
|
98
|
+
};
|
|
99
|
+
function shellQuote(value) {
|
|
100
|
+
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
101
|
+
}
|
|
102
|
+
function flag(name, value) {
|
|
103
|
+
return value ? [name, value] : [];
|
|
104
|
+
}
|
|
105
|
+
function commandCapture(command, pattern) {
|
|
106
|
+
return pattern.exec(command)?.[1] ?? null;
|
|
107
|
+
}
|
|
108
|
+
var ARGV_SESSION_ID = "[0-9a-f-]{36}";
|
|
109
|
+
var CODEX_DIR_PREFIX = "codex__";
|
|
110
|
+
var COPILOT_DIR_PREFIX = "copilot__";
|
|
111
|
+
var CLAUDE_PERMISSION_MODES = /* @__PURE__ */ new Set(["default", "plan", "acceptEdits", "dontAsk", "auto"]);
|
|
112
|
+
var claude = {
|
|
113
|
+
kind: "claude",
|
|
114
|
+
displayName: "Claude Code",
|
|
115
|
+
binName: "claude",
|
|
116
|
+
dirName: {
|
|
117
|
+
lossy: true,
|
|
118
|
+
// Claude has no prefix: it owns every dirName no other agent claims. The
|
|
119
|
+
// registry checks the prefixed agents first, so this is only ever reached
|
|
120
|
+
// as the terminal arm.
|
|
121
|
+
owns: (dirName) => typeof dirName === "string" && !dirName.startsWith(CODEX_DIR_PREFIX) && !dirName.startsWith(COPILOT_DIR_PREFIX),
|
|
122
|
+
/**
|
|
123
|
+
* Claude Code's own convention: every character outside `[A-Za-z0-9]`
|
|
124
|
+
* becomes `-`, so `/Users/x/proj` and `C:\Users\x\proj` both collapse into a
|
|
125
|
+
* flat name under `~/.claude/projects`.
|
|
126
|
+
*/
|
|
127
|
+
encode: (cwd) => (cwd.replace(/[\\/]+$/, "") || cwd).replace(/[^a-zA-Z0-9]/g, "-"),
|
|
128
|
+
/**
|
|
129
|
+
* Best-effort inverse. The encoding is lossy — a literal `-` is
|
|
130
|
+
* indistinguishable from a separator — so a caller that can read the
|
|
131
|
+
* session's recorded `cwd` should always prefer that.
|
|
132
|
+
*/
|
|
133
|
+
decode: (dirName) => {
|
|
134
|
+
const windowsDrive = /^([A-Za-z])--(.*)$/.exec(dirName);
|
|
135
|
+
if (windowsDrive) return `${windowsDrive[1]}:\\${windowsDrive[2].replace(/-/g, "\\")}`;
|
|
136
|
+
return "/" + dirName.replace(/^-/, "").replace(/-/g, "/");
|
|
137
|
+
}
|
|
138
|
+
},
|
|
139
|
+
sessionFile: {
|
|
140
|
+
name: (sessionId) => `${sessionId}.jsonl`,
|
|
141
|
+
sessionId: sessionIdFromJsonlName,
|
|
142
|
+
...jsonlPathUrlCodec
|
|
143
|
+
},
|
|
144
|
+
metadataFromHead: true,
|
|
145
|
+
resume: {
|
|
146
|
+
command: (sessionId) => `claude --resume ${sessionId}`,
|
|
147
|
+
args: (sessionId) => ["--resume", sessionId]
|
|
148
|
+
},
|
|
149
|
+
launchArgs: {
|
|
150
|
+
permissions: (permissions) => {
|
|
151
|
+
if (!permissions?.mode) return ["--permission-mode", "default"];
|
|
152
|
+
if (permissions.mode === "bypassPermissions") return ["--dangerously-skip-permissions"];
|
|
153
|
+
return [
|
|
154
|
+
...CLAUDE_PERMISSION_MODES.has(permissions.mode) ? ["--permission-mode", permissions.mode] : [],
|
|
155
|
+
...(permissions.allowedTools ?? []).flatMap((tool) => ["--allowedTools", tool]),
|
|
156
|
+
...(permissions.disallowedTools ?? []).flatMap((tool) => ["--disallowedTools", tool])
|
|
157
|
+
];
|
|
158
|
+
},
|
|
159
|
+
model: (model) => flag("--model", model),
|
|
160
|
+
effort: (effort) => flag("--effort", effort),
|
|
161
|
+
// Claude's faster tier is a setting on the live Agent SDK query, not a flag.
|
|
162
|
+
fastTier: () => []
|
|
163
|
+
},
|
|
164
|
+
serviceTier: null,
|
|
165
|
+
cli: {
|
|
166
|
+
packageName: "@anthropic-ai/claude-code",
|
|
167
|
+
homebrew: { name: "claude-code", cask: false },
|
|
168
|
+
wingetId: null,
|
|
169
|
+
versionArgs: ["--version"],
|
|
170
|
+
selfUpdate: {
|
|
171
|
+
args: ["update"],
|
|
172
|
+
matches: (path) => path.endsWith("/.local/bin/claude") || path.endsWith("/.local/bin/claude.exe") || path.includes("/.local/share/claude/")
|
|
173
|
+
},
|
|
174
|
+
bundledBySdk: true,
|
|
175
|
+
homeDirName: ".claude",
|
|
176
|
+
homeEnvVar: null,
|
|
177
|
+
installMarker: "projects",
|
|
178
|
+
homeIsDiscoverable: false,
|
|
179
|
+
process: {
|
|
180
|
+
by: "command",
|
|
181
|
+
sessionIdFromCommand: (command) => commandCapture(command, new RegExp(`--resume(?:=|\\s+)(${ARGV_SESSION_ID})`)) ?? commandCapture(command, new RegExp(`--session-id(?:=|\\s+)(${ARGV_SESSION_ID})`))
|
|
182
|
+
}
|
|
183
|
+
},
|
|
184
|
+
config: {
|
|
185
|
+
rootDirName: ".claude",
|
|
186
|
+
instructions: [
|
|
187
|
+
{ in: "scope", path: "CLAUDE.md", scopes: ["global", "project"] },
|
|
188
|
+
{ in: "root", path: "CLAUDE.md", label: ".claude/CLAUDE.md", scopes: ["project"] }
|
|
189
|
+
],
|
|
190
|
+
settings: [
|
|
191
|
+
{ in: "root", path: "settings.json", scopes: ["global"] },
|
|
192
|
+
{ in: "root", path: "settings.local.json", scopes: ["project"] }
|
|
193
|
+
],
|
|
194
|
+
skillsDir: "skills",
|
|
195
|
+
agentsDir: "agents",
|
|
196
|
+
commandsDir: "commands",
|
|
197
|
+
themesDir: "themes",
|
|
198
|
+
pluginsDir: "plugins",
|
|
199
|
+
pluginManifestDir: ".claude-plugin"
|
|
200
|
+
},
|
|
201
|
+
contextWindow: {
|
|
202
|
+
// Anything not listed is treated as current-generation, so a model released
|
|
203
|
+
// after this table was written reports the larger window rather than a
|
|
204
|
+
// stale small one. Limits are LiteLLM `max_input_tokens`, the same table the
|
|
205
|
+
// cost code prices against. Deliberately not family-based: sonnet-4-5 is
|
|
206
|
+
// 200k while sonnet-4-6 is 1M.
|
|
207
|
+
defaultLimit: 1e6,
|
|
208
|
+
limits: [
|
|
209
|
+
{ match: "claude-haiku-4-5", limit: 2e5 },
|
|
210
|
+
{ match: "claude-haiku-4-1", limit: 2e5 },
|
|
211
|
+
{ match: "claude-sonnet-4-5", limit: 2e5 },
|
|
212
|
+
{ match: "claude-opus-4-5", limit: 2e5 },
|
|
213
|
+
{ match: "claude-opus-4-1", limit: 2e5 }
|
|
214
|
+
],
|
|
215
|
+
compactBuffer: 33e3,
|
|
216
|
+
extendedContext: { marker: "[1m]", limit: 1e6 }
|
|
217
|
+
},
|
|
218
|
+
capabilities: {
|
|
219
|
+
worktrees: true,
|
|
220
|
+
mcp: true,
|
|
221
|
+
slashCommands: true,
|
|
222
|
+
workflows: true,
|
|
223
|
+
agentTeams: true,
|
|
224
|
+
subagentTranscripts: true,
|
|
225
|
+
nestedSubagents: false,
|
|
226
|
+
sharing: true,
|
|
227
|
+
goals: "transcript",
|
|
228
|
+
undo: true,
|
|
229
|
+
redo: true,
|
|
230
|
+
nativeRewind: false,
|
|
231
|
+
fileCheckpoints: true,
|
|
232
|
+
reasoningEffort: true,
|
|
233
|
+
fastTier: true,
|
|
234
|
+
imageInput: true,
|
|
235
|
+
midTurnSteering: false,
|
|
236
|
+
stopSession: true,
|
|
237
|
+
ultracode: true,
|
|
238
|
+
contextWindow: true,
|
|
239
|
+
externalProcesses: false,
|
|
240
|
+
accountSwitching: true,
|
|
241
|
+
modelFallbackNotices: true,
|
|
242
|
+
autoPermissionMode: "per-model",
|
|
243
|
+
settingsApply: "live",
|
|
244
|
+
turnLiveness: "transcript",
|
|
245
|
+
tokenStreaming: true,
|
|
246
|
+
nativeFork: false,
|
|
247
|
+
namedSessions: true
|
|
248
|
+
}
|
|
249
|
+
};
|
|
250
|
+
var CODEX_SERVICE_TIER = {
|
|
251
|
+
appServerValue: "priority",
|
|
252
|
+
cliConfigValue: "fast"
|
|
253
|
+
};
|
|
254
|
+
var codex = {
|
|
255
|
+
kind: "codex",
|
|
256
|
+
displayName: "Codex",
|
|
257
|
+
binName: "codex",
|
|
258
|
+
dirName: base64DirNameCodec(CODEX_DIR_PREFIX),
|
|
259
|
+
sessionFile: {
|
|
260
|
+
/**
|
|
261
|
+
* Codex nests rollouts by local date:
|
|
262
|
+
* `YYYY/MM/DD/rollout-YYYY-MM-DDTHH-MM-SS-<sessionId>.jsonl`.
|
|
263
|
+
*/
|
|
264
|
+
name: (sessionId, at = /* @__PURE__ */ new Date()) => {
|
|
265
|
+
const year = String(at.getFullYear());
|
|
266
|
+
const month = String(at.getMonth() + 1).padStart(2, "0");
|
|
267
|
+
const day = String(at.getDate()).padStart(2, "0");
|
|
268
|
+
const hour = String(at.getHours()).padStart(2, "0");
|
|
269
|
+
const minute = String(at.getMinutes()).padStart(2, "0");
|
|
270
|
+
const second = String(at.getSeconds()).padStart(2, "0");
|
|
271
|
+
return `${year}/${month}/${day}/rollout-${year}-${month}-${day}T${hour}-${minute}-${second}-${sessionId}.jsonl`;
|
|
272
|
+
},
|
|
273
|
+
sessionId: sessionIdFromJsonlName,
|
|
274
|
+
// The date nesting is unrecoverable from the id, so the URL keeps the path.
|
|
275
|
+
...jsonlPathUrlCodec
|
|
276
|
+
},
|
|
277
|
+
// A rollout's header carries no turn count and no last user message; both
|
|
278
|
+
// only settle at the end of the file.
|
|
279
|
+
metadataFromHead: false,
|
|
280
|
+
resume: {
|
|
281
|
+
command: (sessionId, cwd) => cwd ? `codex -C ${shellQuote(cwd)} resume ${sessionId}` : `codex resume ${sessionId}`,
|
|
282
|
+
args: (sessionId) => ["resume", sessionId]
|
|
283
|
+
},
|
|
284
|
+
launchArgs: {
|
|
285
|
+
permissions: (permissions) => {
|
|
286
|
+
const mode = permissions?.mode || "default";
|
|
287
|
+
if (mode === "bypassPermissions") return ["--dangerously-bypass-approvals-and-sandbox"];
|
|
288
|
+
return [
|
|
289
|
+
"--sandbox",
|
|
290
|
+
mode === "plan" ? "read-only" : "workspace-write",
|
|
291
|
+
"-c",
|
|
292
|
+
'approval_policy="never"'
|
|
293
|
+
];
|
|
294
|
+
},
|
|
295
|
+
model: (model) => flag("-m", model),
|
|
296
|
+
effort: (effort) => effort ? ["-c", `model_reasoning_effort=${JSON.stringify(effort)}`] : [],
|
|
297
|
+
fastTier: (enabled) => enabled ? [
|
|
298
|
+
"-c",
|
|
299
|
+
`service_tier=${JSON.stringify(CODEX_SERVICE_TIER.cliConfigValue)}`,
|
|
300
|
+
"--enable",
|
|
301
|
+
"fast_mode"
|
|
302
|
+
] : []
|
|
303
|
+
},
|
|
304
|
+
serviceTier: CODEX_SERVICE_TIER,
|
|
305
|
+
cli: {
|
|
306
|
+
packageName: "@openai/codex",
|
|
307
|
+
homebrew: { name: "codex", cask: false },
|
|
308
|
+
wingetId: null,
|
|
309
|
+
versionArgs: ["--version"],
|
|
310
|
+
// Codex ships no self-updater; a hand-placed binary has to be replaced by
|
|
311
|
+
// whatever put it there.
|
|
312
|
+
selfUpdate: null,
|
|
313
|
+
bundledBySdk: false,
|
|
314
|
+
homeDirName: ".codex",
|
|
315
|
+
homeEnvVar: "CODEX_HOME",
|
|
316
|
+
installMarker: "",
|
|
317
|
+
homeIsDiscoverable: true,
|
|
318
|
+
process: {
|
|
319
|
+
by: "command",
|
|
320
|
+
sessionIdFromCommand: (command) => commandCapture(
|
|
321
|
+
command,
|
|
322
|
+
new RegExp(`codex(?:\\s+\\S+)*\\s+exec\\s+resume\\s+(${ARGV_SESSION_ID})`)
|
|
323
|
+
)
|
|
324
|
+
}
|
|
325
|
+
},
|
|
326
|
+
config: {
|
|
327
|
+
rootDirName: ".codex",
|
|
328
|
+
instructions: [{ in: "scope", path: "AGENTS.md", scopes: ["global", "project"] }],
|
|
329
|
+
settings: [{ in: "root", path: "config.toml", scopes: ["global", "project"] }],
|
|
330
|
+
skillsDir: "skills",
|
|
331
|
+
agentsDir: "agents",
|
|
332
|
+
commandsDir: "prompts",
|
|
333
|
+
themesDir: null,
|
|
334
|
+
pluginsDir: null,
|
|
335
|
+
pluginManifestDir: null
|
|
336
|
+
},
|
|
337
|
+
contextWindow: {
|
|
338
|
+
// The GPT-5 family window. A rollout that reports `model_context_window`
|
|
339
|
+
// overrides this; it is only the fallback for a model Cogpit has not seen.
|
|
340
|
+
defaultLimit: 272e3,
|
|
341
|
+
limits: [],
|
|
342
|
+
// Codex decides its own compaction point and never publishes a reserve.
|
|
343
|
+
compactBuffer: 0,
|
|
344
|
+
extendedContext: null
|
|
345
|
+
},
|
|
346
|
+
capabilities: {
|
|
347
|
+
worktrees: false,
|
|
348
|
+
mcp: false,
|
|
349
|
+
slashCommands: false,
|
|
350
|
+
workflows: false,
|
|
351
|
+
agentTeams: false,
|
|
352
|
+
subagentTranscripts: true,
|
|
353
|
+
nestedSubagents: true,
|
|
354
|
+
sharing: false,
|
|
355
|
+
goals: "thread-api",
|
|
356
|
+
undo: true,
|
|
357
|
+
redo: true,
|
|
358
|
+
nativeRewind: false,
|
|
359
|
+
fileCheckpoints: false,
|
|
360
|
+
reasoningEffort: true,
|
|
361
|
+
fastTier: true,
|
|
362
|
+
imageInput: true,
|
|
363
|
+
midTurnSteering: true,
|
|
364
|
+
stopSession: false,
|
|
365
|
+
ultracode: false,
|
|
366
|
+
contextWindow: false,
|
|
367
|
+
externalProcesses: false,
|
|
368
|
+
accountSwitching: false,
|
|
369
|
+
modelFallbackNotices: false,
|
|
370
|
+
autoPermissionMode: "never",
|
|
371
|
+
settingsApply: "next-turn",
|
|
372
|
+
turnLiveness: "runtime",
|
|
373
|
+
tokenStreaming: true,
|
|
374
|
+
nativeFork: false,
|
|
375
|
+
// Codex derives its own thread name.
|
|
376
|
+
namedSessions: false
|
|
377
|
+
}
|
|
378
|
+
};
|
|
379
|
+
var copilot = {
|
|
380
|
+
kind: "copilot",
|
|
381
|
+
displayName: "GitHub Copilot CLI",
|
|
382
|
+
binName: "copilot",
|
|
383
|
+
dirName: base64DirNameCodec(COPILOT_DIR_PREFIX),
|
|
384
|
+
sessionFile: {
|
|
385
|
+
/** Copilot keeps one directory per session: `<uuid>/events.jsonl`. */
|
|
386
|
+
name: (sessionId) => `${sessionId}/events.jsonl`,
|
|
387
|
+
sessionId: (fileName) => {
|
|
388
|
+
const match = new RegExp(`^(${SESSION_UUID})/events\\.jsonl$`, "i").exec(fileName);
|
|
389
|
+
return match ? match[1] : null;
|
|
390
|
+
},
|
|
391
|
+
// `<id>/events.jsonl` rebuilds from the id, so URLs stay short.
|
|
392
|
+
urlId: (fileName) => {
|
|
393
|
+
const match = new RegExp(`^(${SESSION_UUID})/events\\.jsonl$`, "i").exec(fileName);
|
|
394
|
+
return match ? match[1] : fileName.replace(/\.jsonl$/, "");
|
|
395
|
+
},
|
|
396
|
+
fileNameFromUrlId: (urlId) => urlId.endsWith(".jsonl") ? urlId : `${urlId}/events.jsonl`
|
|
397
|
+
},
|
|
398
|
+
metadataFromHead: true,
|
|
399
|
+
resume: {
|
|
400
|
+
command: (sessionId, cwd) => cwd ? `copilot -C ${shellQuote(cwd)} --resume ${sessionId}` : `copilot --resume ${sessionId}`,
|
|
401
|
+
args: (sessionId) => [`--resume=${sessionId}`]
|
|
402
|
+
},
|
|
403
|
+
// Cogpit launches Copilot as a stdio RPC server and configures each turn
|
|
404
|
+
// afterwards, over `session.create` and `session.setPermissionMode`. These are
|
|
405
|
+
// the flags its CLI accepts for the same settings when it is driven straight
|
|
406
|
+
// from a shell instead.
|
|
407
|
+
launchArgs: {
|
|
408
|
+
permissions: (permissions) => permissions?.mode === "bypassPermissions" || permissions?.mode === "auto" ? ["--allow-all"] : [],
|
|
409
|
+
model: (model) => flag("--model", model),
|
|
410
|
+
effort: (effort) => flag("--reasoning-effort", effort),
|
|
411
|
+
fastTier: () => []
|
|
412
|
+
},
|
|
413
|
+
serviceTier: null,
|
|
414
|
+
cli: {
|
|
415
|
+
packageName: "@github/copilot",
|
|
416
|
+
homebrew: { name: "copilot-cli", cask: true },
|
|
417
|
+
wingetId: "GitHub.Copilot",
|
|
418
|
+
versionArgs: ["--version"],
|
|
419
|
+
selfUpdate: {
|
|
420
|
+
args: ["update"],
|
|
421
|
+
// `/usr/local/bin/copilot` is also an npm prefix, so this is only ever
|
|
422
|
+
// reached after the package-manager checks have declined — which they can
|
|
423
|
+
// only do because the caller passes the realpath in as well.
|
|
424
|
+
matches: (path) => path.endsWith("/.local/bin/copilot") || path === "/usr/local/bin/copilot"
|
|
425
|
+
},
|
|
426
|
+
bundledBySdk: false,
|
|
427
|
+
homeDirName: ".copilot",
|
|
428
|
+
homeEnvVar: "COPILOT_HOME",
|
|
429
|
+
installMarker: "session-state",
|
|
430
|
+
homeIsDiscoverable: true,
|
|
431
|
+
// `copilot` is too common a word to match loosely: editor extensions and
|
|
432
|
+
// unrelated tools name it in their argv.
|
|
433
|
+
process: {
|
|
434
|
+
by: "executable",
|
|
435
|
+
sessionIdFromCommand: (command) => commandCapture(command, new RegExp(`--resume(?:=|\\s+)(${ARGV_SESSION_ID})`))
|
|
436
|
+
}
|
|
437
|
+
},
|
|
438
|
+
config: {
|
|
439
|
+
rootDirName: ".copilot",
|
|
440
|
+
// Copilot reads the same cross-CLI instruction file Codex does, so the two
|
|
441
|
+
// entries merge into one row carrying both badges.
|
|
442
|
+
instructions: [{ in: "scope", path: "AGENTS.md", scopes: ["global", "project"] }],
|
|
443
|
+
settings: [{ in: "root", path: "config.json", scopes: ["global"] }],
|
|
444
|
+
skillsDir: "skills",
|
|
445
|
+
agentsDir: null,
|
|
446
|
+
commandsDir: null,
|
|
447
|
+
themesDir: null,
|
|
448
|
+
pluginsDir: null,
|
|
449
|
+
pluginManifestDir: null
|
|
450
|
+
},
|
|
451
|
+
contextWindow: {
|
|
452
|
+
// Copilot brokers models from several vendors, so this is the smallest
|
|
453
|
+
// window any of them offers rather than a per-model table Cogpit cannot
|
|
454
|
+
// keep current.
|
|
455
|
+
defaultLimit: 2e5,
|
|
456
|
+
limits: [],
|
|
457
|
+
compactBuffer: 0,
|
|
458
|
+
extendedContext: null
|
|
459
|
+
},
|
|
460
|
+
capabilities: {
|
|
461
|
+
worktrees: false,
|
|
462
|
+
mcp: false,
|
|
463
|
+
slashCommands: false,
|
|
464
|
+
workflows: false,
|
|
465
|
+
agentTeams: false,
|
|
466
|
+
subagentTranscripts: false,
|
|
467
|
+
nestedSubagents: false,
|
|
468
|
+
sharing: false,
|
|
469
|
+
goals: false,
|
|
470
|
+
undo: true,
|
|
471
|
+
// Copilot persists no undo state of its own, so there is nothing to redo
|
|
472
|
+
// from and no branch history to show.
|
|
473
|
+
redo: false,
|
|
474
|
+
nativeRewind: true,
|
|
475
|
+
fileCheckpoints: false,
|
|
476
|
+
// Effort is only offered for models whose catalog entry advertises it.
|
|
477
|
+
reasoningEffort: false,
|
|
478
|
+
fastTier: false,
|
|
479
|
+
imageInput: false,
|
|
480
|
+
midTurnSteering: true,
|
|
481
|
+
stopSession: true,
|
|
482
|
+
ultracode: false,
|
|
483
|
+
contextWindow: false,
|
|
484
|
+
externalProcesses: true,
|
|
485
|
+
accountSwitching: false,
|
|
486
|
+
modelFallbackNotices: false,
|
|
487
|
+
autoPermissionMode: "always",
|
|
488
|
+
settingsApply: "next-turn",
|
|
489
|
+
turnLiveness: "runtime",
|
|
490
|
+
tokenStreaming: false,
|
|
491
|
+
nativeFork: true,
|
|
492
|
+
namedSessions: true
|
|
493
|
+
}
|
|
494
|
+
};
|
|
495
|
+
var DESCRIPTORS = Object.freeze({
|
|
496
|
+
claude,
|
|
497
|
+
codex,
|
|
498
|
+
copilot
|
|
499
|
+
});
|
|
500
|
+
function descriptorFor(kind) {
|
|
501
|
+
return DESCRIPTORS[kind];
|
|
502
|
+
}
|
|
503
|
+
function descriptorForDirName(dirName) {
|
|
504
|
+
for (const kind of AGENT_KINDS) {
|
|
505
|
+
if (kind === "claude") continue;
|
|
506
|
+
if (DESCRIPTORS[kind].dirName.owns(dirName)) return DESCRIPTORS[kind];
|
|
507
|
+
}
|
|
508
|
+
return claude;
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
// src/lib/codex-exec.ts
|
|
512
|
+
var JS_SIMPLE_ESCAPES = {
|
|
513
|
+
"0": "\0",
|
|
514
|
+
b: "\b",
|
|
515
|
+
f: "\f",
|
|
516
|
+
n: "\n",
|
|
517
|
+
r: "\r",
|
|
518
|
+
t: " ",
|
|
519
|
+
v: "\v"
|
|
520
|
+
};
|
|
521
|
+
function readJsStringLiteral(source, startIndex) {
|
|
522
|
+
const quote = source[startIndex];
|
|
523
|
+
if (quote !== '"' && quote !== "'" && quote !== "`") return null;
|
|
524
|
+
let value = "";
|
|
525
|
+
let index = startIndex + 1;
|
|
526
|
+
while (index < source.length) {
|
|
527
|
+
const char = source[index];
|
|
528
|
+
if (char === quote) return { endIndex: index + 1, value };
|
|
529
|
+
if (char !== "\\") {
|
|
530
|
+
value += char;
|
|
531
|
+
index++;
|
|
532
|
+
continue;
|
|
533
|
+
}
|
|
534
|
+
index++;
|
|
535
|
+
if (index >= source.length) return null;
|
|
536
|
+
const escaped = source[index];
|
|
537
|
+
if (escaped in JS_SIMPLE_ESCAPES) {
|
|
538
|
+
value += JS_SIMPLE_ESCAPES[escaped];
|
|
539
|
+
index++;
|
|
540
|
+
continue;
|
|
541
|
+
}
|
|
542
|
+
if (escaped === "\n") {
|
|
543
|
+
index++;
|
|
544
|
+
continue;
|
|
545
|
+
}
|
|
546
|
+
if (escaped === "\r") {
|
|
547
|
+
index += source[index + 1] === "\n" ? 2 : 1;
|
|
548
|
+
continue;
|
|
549
|
+
}
|
|
550
|
+
if (escaped === "x" && /^[0-9a-fA-F]{2}$/.test(source.slice(index + 1, index + 3))) {
|
|
551
|
+
value += String.fromCharCode(Number.parseInt(source.slice(index + 1, index + 3), 16));
|
|
552
|
+
index += 3;
|
|
553
|
+
continue;
|
|
554
|
+
}
|
|
555
|
+
if (escaped === "u") {
|
|
556
|
+
const braced = source.slice(index + 1).match(/^\{([0-9a-fA-F]+)\}/);
|
|
557
|
+
if (braced) {
|
|
558
|
+
const codePoint = Number.parseInt(braced[1], 16);
|
|
559
|
+
if (Number.isFinite(codePoint) && codePoint <= 1114111) {
|
|
560
|
+
value += String.fromCodePoint(codePoint);
|
|
561
|
+
index += braced[0].length + 1;
|
|
562
|
+
continue;
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
const hex = source.slice(index + 1, index + 5);
|
|
566
|
+
if (/^[0-9a-fA-F]{4}$/.test(hex)) {
|
|
567
|
+
value += String.fromCharCode(Number.parseInt(hex, 16));
|
|
568
|
+
index += 5;
|
|
569
|
+
continue;
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
value += escaped;
|
|
573
|
+
index++;
|
|
574
|
+
}
|
|
575
|
+
return null;
|
|
576
|
+
}
|
|
59
577
|
|
|
60
578
|
// src/lib/messageTypeGuards.ts
|
|
61
579
|
function isUserMessage(msg) {
|
|
@@ -86,6 +604,59 @@ function isCompactBoundary(msg) {
|
|
|
86
604
|
return msg.type === "system" && msg.subtype === "compact_boundary";
|
|
87
605
|
}
|
|
88
606
|
|
|
607
|
+
// src/lib/sessionStats.ts
|
|
608
|
+
function countToolCalls(toolCalls, counts) {
|
|
609
|
+
let errors = 0;
|
|
610
|
+
for (const tc of toolCalls) {
|
|
611
|
+
counts[tc.name] = (counts[tc.name] ?? 0) + 1;
|
|
612
|
+
if (tc.isError) errors++;
|
|
613
|
+
}
|
|
614
|
+
return errors;
|
|
615
|
+
}
|
|
616
|
+
function addUsageToStats(stats, usage) {
|
|
617
|
+
stats.totalInputTokens += usage.input_tokens;
|
|
618
|
+
stats.totalOutputTokens += usage.output_tokens;
|
|
619
|
+
stats.totalCacheCreationTokens += usage.cache_creation_input_tokens ?? 0;
|
|
620
|
+
stats.totalCacheReadTokens += usage.cache_read_input_tokens ?? 0;
|
|
621
|
+
}
|
|
622
|
+
function createEmptySessionStats(turnCount) {
|
|
623
|
+
return {
|
|
624
|
+
totalInputTokens: 0,
|
|
625
|
+
totalOutputTokens: 0,
|
|
626
|
+
totalCacheCreationTokens: 0,
|
|
627
|
+
totalCacheReadTokens: 0,
|
|
628
|
+
toolCallCounts: {},
|
|
629
|
+
errorCount: 0,
|
|
630
|
+
totalDurationMs: 0,
|
|
631
|
+
turnCount
|
|
632
|
+
};
|
|
633
|
+
}
|
|
634
|
+
function computeStats(turns) {
|
|
635
|
+
const stats = createEmptySessionStats(turns.length);
|
|
636
|
+
for (const turn of turns) {
|
|
637
|
+
if (turn.tokenUsage) addUsageToStats(stats, turn.tokenUsage);
|
|
638
|
+
if (turn.durationMs) stats.totalDurationMs += turn.durationMs;
|
|
639
|
+
stats.errorCount += countToolCalls(turn.toolCalls, stats.toolCallCounts);
|
|
640
|
+
for (const sa of turn.subAgentActivity) {
|
|
641
|
+
stats.errorCount += countToolCalls(sa.toolCalls, stats.toolCallCounts);
|
|
642
|
+
if (sa.tokenUsage) addUsageToStats(stats, sa.tokenUsage);
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
return stats;
|
|
646
|
+
}
|
|
647
|
+
function mergeTokenUsage(existing, incoming) {
|
|
648
|
+
if (!existing) return { ...incoming };
|
|
649
|
+
const thinking = (existing.output_tokens_details?.thinking_tokens ?? 0) + (incoming.output_tokens_details?.thinking_tokens ?? 0);
|
|
650
|
+
return {
|
|
651
|
+
input_tokens: existing.input_tokens + incoming.input_tokens,
|
|
652
|
+
output_tokens: existing.output_tokens + incoming.output_tokens,
|
|
653
|
+
cache_creation_input_tokens: (existing.cache_creation_input_tokens ?? 0) + (incoming.cache_creation_input_tokens ?? 0),
|
|
654
|
+
cache_read_input_tokens: (existing.cache_read_input_tokens ?? 0) + (incoming.cache_read_input_tokens ?? 0),
|
|
655
|
+
speed: incoming.speed ?? existing.speed,
|
|
656
|
+
...existing.output_tokens_details || incoming.output_tokens_details ? { output_tokens_details: { thinking_tokens: thinking } } : {}
|
|
657
|
+
};
|
|
658
|
+
}
|
|
659
|
+
|
|
89
660
|
// src/lib/agentEnvelope.ts
|
|
90
661
|
var ENVELOPE_RE = /<(agent-message|teammate-message)([^>]*)>([\s\S]*?)<\/\1>/g;
|
|
91
662
|
var SENDER_PATTERNS = [
|
|
@@ -180,18 +751,6 @@ function mergeAttribution(current, msg) {
|
|
|
180
751
|
}
|
|
181
752
|
return merged;
|
|
182
753
|
}
|
|
183
|
-
function mergeTokenUsage(existing, incoming) {
|
|
184
|
-
if (!existing) {
|
|
185
|
-
return { ...incoming };
|
|
186
|
-
}
|
|
187
|
-
return {
|
|
188
|
-
input_tokens: existing.input_tokens + incoming.input_tokens,
|
|
189
|
-
output_tokens: existing.output_tokens + incoming.output_tokens,
|
|
190
|
-
cache_creation_input_tokens: (existing.cache_creation_input_tokens ?? 0) + (incoming.cache_creation_input_tokens ?? 0),
|
|
191
|
-
cache_read_input_tokens: (existing.cache_read_input_tokens ?? 0) + (incoming.cache_read_input_tokens ?? 0),
|
|
192
|
-
speed: incoming.speed ?? existing.speed
|
|
193
|
-
};
|
|
194
|
-
}
|
|
195
754
|
var RESUME_PREAMBLE_RE = /^This session is being continued from a previous conversation[\s\S]*?\n\nSummary:\s*\n/;
|
|
196
755
|
var RESUME_INSTRUCTIONS_RE = /\n+(?:If you need specific details from before compaction|(?:Please c|C)ontinue the conversation from where it left off)[\s\S]*$/;
|
|
197
756
|
function extractCompactionSummary(content) {
|
|
@@ -364,8 +923,11 @@ function findTurnStartIndices(messages) {
|
|
|
364
923
|
}
|
|
365
924
|
return starts;
|
|
366
925
|
}
|
|
367
|
-
function
|
|
926
|
+
function buildTurnsWithStarts(messages) {
|
|
368
927
|
const turns = [];
|
|
928
|
+
const turnStartIndices = [];
|
|
929
|
+
let pendingTurnStart = null;
|
|
930
|
+
let recordIndex = 0;
|
|
369
931
|
let current = null;
|
|
370
932
|
let pendingCompaction = null;
|
|
371
933
|
function attachPendingCompaction(turn) {
|
|
@@ -457,10 +1019,13 @@ function buildTurns(messages) {
|
|
|
457
1019
|
}
|
|
458
1020
|
current.contentBlocks = groupPlanModeBlocks(current.contentBlocks);
|
|
459
1021
|
turns.push(current);
|
|
1022
|
+
if (pendingTurnStart !== null) turnStartIndices.push(pendingTurnStart);
|
|
1023
|
+
pendingTurnStart = null;
|
|
460
1024
|
current = null;
|
|
461
1025
|
agentBlockMap.clear();
|
|
462
1026
|
}
|
|
463
|
-
for (
|
|
1027
|
+
for (recordIndex = 0; recordIndex < messages.length; recordIndex++) {
|
|
1028
|
+
const msg = messages[recordIndex];
|
|
464
1029
|
if (isSummaryMessage(msg)) {
|
|
465
1030
|
finalizeTurn2();
|
|
466
1031
|
pendingCompaction = { summary: msg.summary };
|
|
@@ -473,6 +1038,7 @@ function buildTurns(messages) {
|
|
|
473
1038
|
}
|
|
474
1039
|
if (isCompactSummaryMessage(msg)) {
|
|
475
1040
|
finalizeTurn2();
|
|
1041
|
+
pendingTurnStart = recordIndex;
|
|
476
1042
|
current = createTurn(msg, null);
|
|
477
1043
|
current.compactionSummary = extractCompactionSummary(msg.message.content);
|
|
478
1044
|
current.compactionMeta = pendingCompaction?.meta;
|
|
@@ -595,12 +1161,14 @@ function buildTurns(messages) {
|
|
|
595
1161
|
current.contentBlocks.push(notification);
|
|
596
1162
|
continue;
|
|
597
1163
|
}
|
|
1164
|
+
pendingTurnStart = recordIndex;
|
|
598
1165
|
current = createTurn(msg, null);
|
|
599
1166
|
current.contentBlocks.push(notification);
|
|
600
1167
|
attachPendingCompaction(current);
|
|
601
1168
|
continue;
|
|
602
1169
|
}
|
|
603
1170
|
finalizeTurn2();
|
|
1171
|
+
pendingTurnStart = recordIndex;
|
|
604
1172
|
current = createTurn(msg, msg.message.content);
|
|
605
1173
|
attachPendingCompaction(current);
|
|
606
1174
|
if (pendingRecap) {
|
|
@@ -628,6 +1196,7 @@ function buildTurns(messages) {
|
|
|
628
1196
|
};
|
|
629
1197
|
var flushToolCalls = flushToolCalls2, flushThinking = flushThinking2;
|
|
630
1198
|
if (!current) {
|
|
1199
|
+
pendingTurnStart = recordIndex;
|
|
631
1200
|
current = createTurn(msg, null);
|
|
632
1201
|
attachPendingCompaction(current);
|
|
633
1202
|
}
|
|
@@ -831,115 +1400,290 @@ function buildTurns(messages) {
|
|
|
831
1400
|
}
|
|
832
1401
|
flushPendingQueuedPrompts();
|
|
833
1402
|
finalizeTurn2();
|
|
834
|
-
return pairAgentMessageReplies(turns);
|
|
1403
|
+
return { turns: pairAgentMessageReplies(turns), turnStartIndices };
|
|
1404
|
+
}
|
|
1405
|
+
function buildTurns(messages) {
|
|
1406
|
+
return buildTurnsWithStarts(messages).turns;
|
|
835
1407
|
}
|
|
836
1408
|
|
|
837
|
-
// src/lib/
|
|
838
|
-
function
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
1409
|
+
// src/lib/claude.ts
|
|
1410
|
+
function isClaudeSessionText() {
|
|
1411
|
+
return true;
|
|
1412
|
+
}
|
|
1413
|
+
function parseRecords(lines) {
|
|
1414
|
+
const messages = [];
|
|
1415
|
+
for (const line of lines) {
|
|
1416
|
+
const trimmed = line.trim();
|
|
1417
|
+
if (!trimmed) continue;
|
|
1418
|
+
try {
|
|
1419
|
+
messages.push(JSON.parse(trimmed));
|
|
1420
|
+
} catch {
|
|
1421
|
+
}
|
|
843
1422
|
}
|
|
844
|
-
return
|
|
1423
|
+
return messages;
|
|
845
1424
|
}
|
|
846
|
-
function
|
|
847
|
-
|
|
848
|
-
stats.totalOutputTokens += usage.output_tokens;
|
|
849
|
-
stats.totalCacheCreationTokens += usage.cache_creation_input_tokens ?? 0;
|
|
850
|
-
stats.totalCacheReadTokens += usage.cache_read_input_tokens ?? 0;
|
|
1425
|
+
function parseLines(jsonlText) {
|
|
1426
|
+
return parseRecords(jsonlText.split("\n"));
|
|
851
1427
|
}
|
|
852
|
-
function
|
|
1428
|
+
function extractSessionMetadata(messages) {
|
|
1429
|
+
const meta = { sessionId: "", version: "", gitBranch: "", cwd: "", slug: "", name: "", model: "", branchedFrom: void 0 };
|
|
1430
|
+
for (const msg of messages) {
|
|
1431
|
+
if (msg.sessionId && !meta.sessionId) meta.sessionId = msg.sessionId;
|
|
1432
|
+
if (msg.version && !meta.version) meta.version = msg.version;
|
|
1433
|
+
if (msg.gitBranch && !meta.gitBranch) meta.gitBranch = msg.gitBranch;
|
|
1434
|
+
if (msg.cwd && !meta.cwd) meta.cwd = msg.cwd;
|
|
1435
|
+
if (msg.slug && !meta.slug) meta.slug = msg.slug;
|
|
1436
|
+
if (msg.name && !meta.name) meta.name = msg.name;
|
|
1437
|
+
if (msg.branchedFrom && !meta.branchedFrom) {
|
|
1438
|
+
meta.branchedFrom = msg.branchedFrom;
|
|
1439
|
+
}
|
|
1440
|
+
if (isAssistantMessage(msg) && msg.message.model && !meta.model) {
|
|
1441
|
+
meta.model = msg.message.model;
|
|
1442
|
+
}
|
|
1443
|
+
if (meta.sessionId && meta.version && meta.gitBranch && meta.cwd && meta.model) break;
|
|
1444
|
+
}
|
|
1445
|
+
return meta;
|
|
1446
|
+
}
|
|
1447
|
+
function extractClaudeMetadataFromLines(lines) {
|
|
1448
|
+
return extractSessionMetadata(parseRecords(lines));
|
|
1449
|
+
}
|
|
1450
|
+
function parseClaudeSession(jsonlText, options) {
|
|
1451
|
+
const rawMessages = parseLines(jsonlText);
|
|
1452
|
+
const metadata = extractSessionMetadata(rawMessages);
|
|
1453
|
+
const turns = buildTurns(rawMessages);
|
|
1454
|
+
const stats = options?.skipStats ? createEmptySessionStats(turns.length) : computeStats(turns);
|
|
853
1455
|
return {
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
errorCount: 0,
|
|
860
|
-
totalDurationMs: 0,
|
|
861
|
-
turnCount
|
|
1456
|
+
...metadata,
|
|
1457
|
+
turns,
|
|
1458
|
+
stats,
|
|
1459
|
+
rawMessages: options?.skipStats ? [] : rawMessages,
|
|
1460
|
+
agentKind: "claude"
|
|
862
1461
|
};
|
|
863
1462
|
}
|
|
864
|
-
function
|
|
865
|
-
const
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
1463
|
+
function appendClaudeSession(existing, newJsonlText) {
|
|
1464
|
+
const newMessages = parseLines(newJsonlText);
|
|
1465
|
+
if (newMessages.length === 0) return existing;
|
|
1466
|
+
const existingRawMessages = existing.rawMessages;
|
|
1467
|
+
const allRawMessages = [...existingRawMessages, ...newMessages];
|
|
1468
|
+
const turnStarts = findTurnStartIndices(existingRawMessages);
|
|
1469
|
+
const historyTurnCount = Math.max(0, existing.turns.length - turnStarts.length);
|
|
1470
|
+
let rebuildFromTurn = Math.max(0, turnStarts.length - 1);
|
|
1471
|
+
const progressParentIds = /* @__PURE__ */ new Set();
|
|
1472
|
+
for (const msg of newMessages) {
|
|
1473
|
+
if (msg.type === "progress") {
|
|
1474
|
+
const parentId = msg.parentToolUseID;
|
|
1475
|
+
if (parentId) progressParentIds.add(parentId);
|
|
873
1476
|
}
|
|
874
1477
|
}
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
1478
|
+
if (progressParentIds.size > 0) {
|
|
1479
|
+
for (let t = rebuildFromTurn - 1; t >= 0; t--) {
|
|
1480
|
+
const turn = existing.turns[historyTurnCount + t];
|
|
1481
|
+
if (turn?.toolCalls.some((tc) => progressParentIds.has(tc.id))) {
|
|
1482
|
+
rebuildFromTurn = t;
|
|
1483
|
+
break;
|
|
1484
|
+
}
|
|
1485
|
+
}
|
|
1486
|
+
}
|
|
1487
|
+
const rebuildFrom = turnStarts[rebuildFromTurn] ?? existingRawMessages.length;
|
|
1488
|
+
const keptTurns = existing.turns.slice(0, historyTurnCount + rebuildFromTurn);
|
|
1489
|
+
const tailTurns = buildTurns(allRawMessages.slice(rebuildFrom));
|
|
1490
|
+
const allTurns = pairAgentMessageReplies([...keptTurns, ...tailTurns]);
|
|
1491
|
+
const stats = computeStats(allTurns);
|
|
1492
|
+
return {
|
|
1493
|
+
sessionId: existing.sessionId,
|
|
1494
|
+
version: existing.version,
|
|
1495
|
+
gitBranch: existing.gitBranch,
|
|
1496
|
+
cwd: existing.cwd,
|
|
1497
|
+
slug: existing.slug,
|
|
1498
|
+
name: existing.name,
|
|
1499
|
+
model: existing.model || (allTurns.length > 0 ? allTurns[allTurns.length - 1].model || "" : ""),
|
|
1500
|
+
turns: allTurns,
|
|
1501
|
+
stats,
|
|
1502
|
+
rawMessages: allRawMessages,
|
|
1503
|
+
branchedFrom: existing.branchedFrom,
|
|
1504
|
+
agentKind: existing.agentKind
|
|
1505
|
+
};
|
|
1506
|
+
}
|
|
1507
|
+
var TASK_NOTIFICATION_MARKER = "<task-notification>";
|
|
1508
|
+
var TASK_ID_RE = /<task-id>([^<]+)<\/task-id>/g;
|
|
1509
|
+
var TOOL_USE_ID_RE = /<tool-use-id>([^<]+)<\/tool-use-id>/g;
|
|
1510
|
+
function isObject(value) {
|
|
1511
|
+
return typeof value === "object" && value !== null;
|
|
1512
|
+
}
|
|
1513
|
+
function userMessageTexts(msg) {
|
|
1514
|
+
const message = isObject(msg.message) ? msg.message : null;
|
|
1515
|
+
const content = message?.content;
|
|
1516
|
+
if (typeof content === "string") return [content];
|
|
1517
|
+
if (!Array.isArray(content)) return [];
|
|
1518
|
+
return content.filter((block) => isObject(block) && typeof block.text === "string").map((block) => block.text);
|
|
1519
|
+
}
|
|
1520
|
+
function collectClaudeBackgroundWork(rawMessages) {
|
|
1521
|
+
const launches = [];
|
|
1522
|
+
const resolved = /* @__PURE__ */ new Set();
|
|
1523
|
+
const queuedIds = /* @__PURE__ */ new Set();
|
|
1524
|
+
const deliveredIds = /* @__PURE__ */ new Set();
|
|
1525
|
+
function noteNotifications(text, sink) {
|
|
1526
|
+
if (!text.includes(TASK_NOTIFICATION_MARKER)) return;
|
|
1527
|
+
for (const match of text.matchAll(TASK_ID_RE)) {
|
|
1528
|
+
resolved.add(match[1]);
|
|
1529
|
+
sink?.add(match[1]);
|
|
1530
|
+
}
|
|
1531
|
+
for (const match of text.matchAll(TOOL_USE_ID_RE)) {
|
|
1532
|
+
resolved.add(match[1]);
|
|
1533
|
+
sink?.add(match[1]);
|
|
1534
|
+
}
|
|
1535
|
+
}
|
|
1536
|
+
for (const msg of rawMessages) {
|
|
1537
|
+
if (msg.type === "user") {
|
|
1538
|
+
const toolUseResult = isObject(msg.toolUseResult) ? msg.toolUseResult : null;
|
|
1539
|
+
if (toolUseResult?.status === "async_launched") {
|
|
1540
|
+
const message = isObject(msg.message) ? msg.message : null;
|
|
1541
|
+
const blocks = Array.isArray(message?.content) ? message.content : [];
|
|
1542
|
+
const toolUseIds = blocks.filter((block) => isObject(block) && block.type === "tool_result").map((block) => block.tool_use_id);
|
|
1543
|
+
const ids = [toolUseResult.agentId, toolUseResult.taskId, ...toolUseIds].filter((id) => typeof id === "string" && id.length > 0);
|
|
1544
|
+
const description = [toolUseResult.description, toolUseResult.workflowName, toolUseResult.summary].find((value) => typeof value === "string" && value.length > 0) ?? "";
|
|
1545
|
+
if (ids.length > 0) launches.push({ ids, description });
|
|
1546
|
+
}
|
|
1547
|
+
for (const text of userMessageTexts(msg)) noteNotifications(text, deliveredIds);
|
|
899
1548
|
continue;
|
|
900
1549
|
}
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
1550
|
+
if (msg.type === "assistant") {
|
|
1551
|
+
const message = isObject(msg.message) ? msg.message : null;
|
|
1552
|
+
const blocks = Array.isArray(message?.content) ? message.content : [];
|
|
1553
|
+
for (const block of blocks) {
|
|
1554
|
+
if (!isObject(block) || block.type !== "tool_use" || block.name !== "TaskStop") continue;
|
|
1555
|
+
const input = isObject(block.input) ? block.input : null;
|
|
1556
|
+
const taskId = input?.task_id ?? input?.shell_id;
|
|
1557
|
+
if (typeof taskId === "string") resolved.add(taskId);
|
|
1558
|
+
}
|
|
907
1559
|
continue;
|
|
908
1560
|
}
|
|
909
|
-
if (
|
|
910
|
-
|
|
1561
|
+
if (msg.type === "attachment") {
|
|
1562
|
+
const attachment = isObject(msg.attachment) ? msg.attachment : null;
|
|
1563
|
+
if (typeof attachment?.prompt === "string") noteNotifications(attachment.prompt, deliveredIds);
|
|
911
1564
|
continue;
|
|
912
1565
|
}
|
|
913
|
-
if (
|
|
914
|
-
|
|
1566
|
+
if (msg.type === "queue-operation" && typeof msg.content === "string") {
|
|
1567
|
+
const op = msg.operation;
|
|
1568
|
+
noteNotifications(msg.content, op === "enqueue" ? queuedIds : deliveredIds);
|
|
1569
|
+
}
|
|
1570
|
+
}
|
|
1571
|
+
return {
|
|
1572
|
+
pendingDescriptions: launches.filter((launch) => !launch.ids.some((id) => resolved.has(id))).map((launch) => launch.description),
|
|
1573
|
+
undeliveredNotifications: [...queuedIds].filter((id) => !deliveredIds.has(id)).length
|
|
1574
|
+
};
|
|
1575
|
+
}
|
|
1576
|
+
function awaitingAgentsResult(pendingDescriptions, pendingQueue) {
|
|
1577
|
+
return {
|
|
1578
|
+
status: "awaiting_agents",
|
|
1579
|
+
pendingQueue,
|
|
1580
|
+
pendingAgents: pendingDescriptions.length,
|
|
1581
|
+
pendingAgentDescriptions: pendingDescriptions.filter((description) => description.length > 0)
|
|
1582
|
+
};
|
|
1583
|
+
}
|
|
1584
|
+
function deriveClaudeSessionStatus(rawMessages) {
|
|
1585
|
+
let pendingEnqueues = 0;
|
|
1586
|
+
let backgroundWork = null;
|
|
1587
|
+
let deliveringNotification = false;
|
|
1588
|
+
function result(status, toolName) {
|
|
1589
|
+
const info = { status, pendingQueue: Math.max(0, pendingEnqueues) };
|
|
1590
|
+
if (toolName) info.toolName = toolName;
|
|
1591
|
+
return info;
|
|
1592
|
+
}
|
|
1593
|
+
function getBackgroundWork() {
|
|
1594
|
+
return backgroundWork ??= collectClaudeBackgroundWork(rawMessages);
|
|
1595
|
+
}
|
|
1596
|
+
for (let i = rawMessages.length - 1; i >= 0; i--) {
|
|
1597
|
+
const msg = rawMessages[i];
|
|
1598
|
+
if (msg.type === "queue-operation") continue;
|
|
1599
|
+
if (msg.type === "progress") {
|
|
1600
|
+
const data = msg.data;
|
|
1601
|
+
if (data?.type === "hook_progress") {
|
|
1602
|
+
const decision = data.decision ?? data.hookSpecificOutput?.permissionDecision;
|
|
1603
|
+
if (decision === "defer") return result("deferred");
|
|
1604
|
+
}
|
|
1605
|
+
break;
|
|
1606
|
+
}
|
|
1607
|
+
break;
|
|
1608
|
+
}
|
|
1609
|
+
for (let i = rawMessages.length - 1; i >= 0; i--) {
|
|
1610
|
+
const msg = rawMessages[i];
|
|
1611
|
+
if (msg.type === "queue-operation") {
|
|
1612
|
+
if (typeof msg.content === "string" && msg.content.includes(TASK_NOTIFICATION_MARKER)) continue;
|
|
1613
|
+
const op = msg.operation;
|
|
1614
|
+
if (op === "enqueue") pendingEnqueues++;
|
|
1615
|
+
else if (op === "dequeue" || op === "remove") pendingEnqueues--;
|
|
915
1616
|
continue;
|
|
916
1617
|
}
|
|
917
|
-
if (
|
|
918
|
-
|
|
919
|
-
|
|
1618
|
+
if (msg.type === "attachment") {
|
|
1619
|
+
const attachment = isObject(msg.attachment) ? msg.attachment : null;
|
|
1620
|
+
if (attachment?.commandMode === "task-notification") deliveringNotification = true;
|
|
920
1621
|
continue;
|
|
921
1622
|
}
|
|
922
|
-
if (
|
|
923
|
-
const
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
1623
|
+
if (msg.type === "assistant") {
|
|
1624
|
+
const message = msg.message;
|
|
1625
|
+
const stopReason = message?.stop_reason;
|
|
1626
|
+
if (stopReason === "end_turn") {
|
|
1627
|
+
const work = getBackgroundWork();
|
|
1628
|
+
if (deliveringNotification || work.undeliveredNotifications > 0) return result("processing");
|
|
1629
|
+
if (work.pendingDescriptions.length > 0) {
|
|
1630
|
+
return awaitingAgentsResult(work.pendingDescriptions, Math.max(0, pendingEnqueues));
|
|
930
1631
|
}
|
|
1632
|
+
let hasActivity = false;
|
|
1633
|
+
for (let j = i - 1; j >= 0; j--) {
|
|
1634
|
+
const m = rawMessages[j];
|
|
1635
|
+
if (m.type === "user" && !m.isMeta) {
|
|
1636
|
+
hasActivity = true;
|
|
1637
|
+
break;
|
|
1638
|
+
}
|
|
1639
|
+
}
|
|
1640
|
+
return result(hasActivity ? "completed" : "idle");
|
|
931
1641
|
}
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
continue;
|
|
1642
|
+
if (stopReason === "tool_use") {
|
|
1643
|
+
const content = message?.content;
|
|
1644
|
+
const toolUseBlock = content?.findLast?.((b) => b.type === "tool_use");
|
|
1645
|
+
return result("tool_use", toolUseBlock?.name);
|
|
937
1646
|
}
|
|
1647
|
+
return result("thinking");
|
|
938
1648
|
}
|
|
939
|
-
|
|
940
|
-
|
|
1649
|
+
if (msg.type === "user") {
|
|
1650
|
+
const isMeta = msg.isMeta;
|
|
1651
|
+
if (isMeta) continue;
|
|
1652
|
+
return result("processing");
|
|
1653
|
+
}
|
|
1654
|
+
if (msg.type === "system" && msg.subtype === "terminal_reason") {
|
|
1655
|
+
const reason = msg.reason;
|
|
1656
|
+
if (reason) {
|
|
1657
|
+
return { status: "completed", terminalReason: reason, pendingQueue: Math.max(0, pendingEnqueues) };
|
|
1658
|
+
}
|
|
1659
|
+
continue;
|
|
1660
|
+
}
|
|
1661
|
+
if (msg.type === "summary") continue;
|
|
1662
|
+
if (msg.type === "system" && msg.subtype === "compact_boundary") continue;
|
|
941
1663
|
}
|
|
942
|
-
return
|
|
1664
|
+
return { status: "idle" };
|
|
1665
|
+
}
|
|
1666
|
+
function brandClaudeBranch(firstRecord, sessionId, turnIndex) {
|
|
1667
|
+
const originalId = typeof firstRecord.sessionId === "string" ? firstRecord.sessionId : "";
|
|
1668
|
+
return {
|
|
1669
|
+
record: { ...firstRecord, sessionId, branchedFrom: { sessionId: originalId, turnIndex } },
|
|
1670
|
+
originalId
|
|
1671
|
+
};
|
|
1672
|
+
}
|
|
1673
|
+
function claudeTurnBoundaries(records) {
|
|
1674
|
+
return buildTurnsWithStarts(records).turnStartIndices;
|
|
1675
|
+
}
|
|
1676
|
+
|
|
1677
|
+
// src/lib/turnContent.ts
|
|
1678
|
+
function appendAssistantText(turn, text, timestamp) {
|
|
1679
|
+
if (!text) return;
|
|
1680
|
+
turn.assistantText.push(text);
|
|
1681
|
+
const last = turn.contentBlocks[turn.contentBlocks.length - 1];
|
|
1682
|
+
if (last?.kind === "text") {
|
|
1683
|
+
last.text.push(text);
|
|
1684
|
+
return;
|
|
1685
|
+
}
|
|
1686
|
+
turn.contentBlocks.push({ kind: "text", text: [text], timestamp });
|
|
943
1687
|
}
|
|
944
1688
|
|
|
945
1689
|
// src/lib/codex-patches.ts
|
|
@@ -977,8 +1721,8 @@ function extractApplyPatchInputs(execSource) {
|
|
|
977
1721
|
function resolveCodexPatchPath(filePath, cwd) {
|
|
978
1722
|
if (!cwd || filePath.startsWith("/") || /^[A-Za-z]:[\\/]/.test(filePath)) return filePath;
|
|
979
1723
|
const base = cwd.replace(/[\\/]+$/, "");
|
|
980
|
-
const
|
|
981
|
-
return /^[A-Za-z]:[\\/]/.test(cwd) ? `${base}\\${
|
|
1724
|
+
const relative2 = filePath.replace(/^\.[\\/]/, "");
|
|
1725
|
+
return /^[A-Za-z]:[\\/]/.test(cwd) ? `${base}\\${relative2.replace(/\//g, "\\")}` : `${base}/${relative2}`;
|
|
982
1726
|
}
|
|
983
1727
|
function parseApplyPatch(patchText, callId, timestamp, cwd = "") {
|
|
984
1728
|
const toolCalls = [];
|
|
@@ -1102,7 +1846,7 @@ function findFailedNestedPatchCallIds(output, wrapperIsError, calls) {
|
|
|
1102
1846
|
}
|
|
1103
1847
|
|
|
1104
1848
|
// src/lib/codex-tool-normalization.ts
|
|
1105
|
-
function
|
|
1849
|
+
function isObject2(value) {
|
|
1106
1850
|
return typeof value === "object" && value !== null;
|
|
1107
1851
|
}
|
|
1108
1852
|
var COLLABORATION_TOOL_NAMES = /* @__PURE__ */ new Map([
|
|
@@ -1119,43 +1863,135 @@ var COLLABORATION_TOOL_NAMES = /* @__PURE__ */ new Map([
|
|
|
1119
1863
|
["interrupt_agent", "interrupt_agent"],
|
|
1120
1864
|
["interruptAgent", "interrupt_agent"]
|
|
1121
1865
|
]);
|
|
1866
|
+
var NATIVE_TOOL_NAMES = /* @__PURE__ */ new Set([
|
|
1867
|
+
"exec",
|
|
1868
|
+
"exec_command",
|
|
1869
|
+
"write_stdin",
|
|
1870
|
+
"apply_patch",
|
|
1871
|
+
"update_plan",
|
|
1872
|
+
"view_image",
|
|
1873
|
+
"request_user_input",
|
|
1874
|
+
"request_user_input_async",
|
|
1875
|
+
"tool_search",
|
|
1876
|
+
"list_mcp_resources",
|
|
1877
|
+
"list_mcp_resource_templates",
|
|
1878
|
+
"read_mcp_resource",
|
|
1879
|
+
"request_plugin_install",
|
|
1880
|
+
"create_goal",
|
|
1881
|
+
"get_goal",
|
|
1882
|
+
"update_goal",
|
|
1883
|
+
"wait"
|
|
1884
|
+
]);
|
|
1122
1885
|
function normalizeFunctionName(rawName) {
|
|
1886
|
+
if (rawName.startsWith("mcp__")) return rawName;
|
|
1123
1887
|
const leaf = rawName.split(/(?:__|[.:/])+/).filter(Boolean).at(-1) ?? rawName;
|
|
1124
|
-
return COLLABORATION_TOOL_NAMES.get(leaf) ?? rawName;
|
|
1888
|
+
return COLLABORATION_TOOL_NAMES.get(leaf) ?? (NATIVE_TOOL_NAMES.has(leaf) ? leaf : rawName);
|
|
1889
|
+
}
|
|
1890
|
+
function hasFailedExit(output) {
|
|
1891
|
+
const exitMatch = output?.match(/Process exited with code (\d+)/);
|
|
1892
|
+
return exitMatch ? exitMatch[1] !== "0" : false;
|
|
1893
|
+
}
|
|
1894
|
+
function isCodexQuestionTool(name) {
|
|
1895
|
+
return name === "request_user_input" || name === "request_user_input_async";
|
|
1125
1896
|
}
|
|
1126
|
-
function
|
|
1127
|
-
|
|
1128
|
-
const
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1897
|
+
function normalizeQuestions(input) {
|
|
1898
|
+
const source = Array.isArray(input.questions) ? input.questions : [input];
|
|
1899
|
+
const questions = source.filter((question) => isObject2(question)).map((question) => {
|
|
1900
|
+
const text = typeof question.question === "string" ? question.question : typeof question.title === "string" ? question.title : "";
|
|
1901
|
+
const options = Array.isArray(question.options) ? question.options : [];
|
|
1902
|
+
return {
|
|
1903
|
+
question: text,
|
|
1904
|
+
...typeof question.header === "string" && question.header ? { header: question.header } : {},
|
|
1905
|
+
options: options.map((option) => {
|
|
1906
|
+
if (typeof option === "string") return { label: option };
|
|
1907
|
+
if (!isObject2(option) || typeof option.label !== "string") return null;
|
|
1908
|
+
return {
|
|
1909
|
+
label: option.label,
|
|
1910
|
+
...typeof option.description === "string" && option.description ? { description: option.description } : {}
|
|
1911
|
+
};
|
|
1912
|
+
}).filter((option) => option !== null)
|
|
1913
|
+
};
|
|
1914
|
+
}).filter((question) => question.question);
|
|
1915
|
+
return questions.length > 0 ? { ...input, questions } : input;
|
|
1132
1916
|
}
|
|
1133
1917
|
function normalizePlanToTodos(input) {
|
|
1134
1918
|
const plan = Array.isArray(input.plan) ? input.plan : [];
|
|
1135
|
-
const todos = plan.filter((item) =>
|
|
1919
|
+
const todos = plan.filter((item) => isObject2(item)).map((item) => ({
|
|
1136
1920
|
content: typeof item.step === "string" ? item.step : "",
|
|
1137
1921
|
status: typeof item.status === "string" ? item.status : "pending",
|
|
1138
1922
|
activeForm: typeof item.step === "string" ? item.step : ""
|
|
1139
1923
|
}));
|
|
1140
1924
|
return { todos };
|
|
1141
1925
|
}
|
|
1142
|
-
function
|
|
1143
|
-
if (
|
|
1144
|
-
|
|
1145
|
-
|
|
1926
|
+
function outputImage(block) {
|
|
1927
|
+
if (block.type !== "image" && block.type !== "input_image" && block.type !== "image_url") return null;
|
|
1928
|
+
const source = isObject2(block.source) ? block.source : block;
|
|
1929
|
+
const mediaType = source.media_type ?? source.mimeType;
|
|
1930
|
+
if (typeof mediaType === "string" && mediaType.startsWith("image/") && typeof source.data === "string" && source.data) {
|
|
1931
|
+
return { type: "image", source: { type: "base64", media_type: mediaType, data: source.data } };
|
|
1932
|
+
}
|
|
1933
|
+
const url = isObject2(block.image_url) ? block.image_url.url : block.image_url;
|
|
1934
|
+
const match = typeof url === "string" ? /^data:(image\/[^;,]+);base64,(.+)$/is.exec(url) : null;
|
|
1935
|
+
return match ? { type: "image", source: { type: "base64", media_type: match[1].toLowerCase(), data: match[2] } } : null;
|
|
1936
|
+
}
|
|
1937
|
+
function outputContent(output) {
|
|
1938
|
+
if (typeof output === "string") return { parts: [output], text: output, images: [] };
|
|
1939
|
+
if (!Array.isArray(output)) {
|
|
1940
|
+
const text = output == null ? "" : JSON.stringify(output);
|
|
1941
|
+
return { parts: [text], text, images: [] };
|
|
1942
|
+
}
|
|
1943
|
+
const parts = [];
|
|
1944
|
+
const images = [];
|
|
1945
|
+
for (const block of output) {
|
|
1946
|
+
if (!isObject2(block)) continue;
|
|
1947
|
+
if (typeof block.text === "string") parts.push(block.text);
|
|
1948
|
+
const image = outputImage(block);
|
|
1949
|
+
if (image) images.push(image);
|
|
1950
|
+
}
|
|
1951
|
+
return { parts, text: parts.join(""), images };
|
|
1952
|
+
}
|
|
1953
|
+
var SCRIPT_STATUS = /^Script (?:completed|failed|running)\b/;
|
|
1954
|
+
function collectExitCodes(chunk, codes) {
|
|
1955
|
+
if (!isObject2(chunk)) return;
|
|
1956
|
+
if (chunk.status === "rejected") codes.push(1);
|
|
1957
|
+
else if (chunk.status === "fulfilled") collectExitCodes(chunk.value, codes);
|
|
1958
|
+
else if (typeof chunk.exit_code === "number") codes.push(chunk.exit_code);
|
|
1959
|
+
}
|
|
1960
|
+
function scriptFailed(parts) {
|
|
1961
|
+
const status = parts[0] ?? "";
|
|
1962
|
+
if (!SCRIPT_STATUS.test(status)) return void 0;
|
|
1963
|
+
if (status.startsWith("Script failed")) return true;
|
|
1964
|
+
const codes = [];
|
|
1965
|
+
for (const part of parts.slice(1)) {
|
|
1966
|
+
let chunk;
|
|
1967
|
+
try {
|
|
1968
|
+
chunk = JSON.parse(part);
|
|
1969
|
+
} catch {
|
|
1970
|
+
continue;
|
|
1971
|
+
}
|
|
1972
|
+
for (const entry of Array.isArray(chunk) ? chunk : [chunk]) collectExitCodes(entry, codes);
|
|
1973
|
+
}
|
|
1974
|
+
return codes.length > 0 ? codes.some((code) => code !== 0) : void 0;
|
|
1146
1975
|
}
|
|
1147
1976
|
function parseCustomToolOutput(output) {
|
|
1148
|
-
const
|
|
1149
|
-
|
|
1977
|
+
const original = outputContent(output);
|
|
1978
|
+
let envelope = isObject2(output) && !Array.isArray(output) ? output : null;
|
|
1150
1979
|
try {
|
|
1151
|
-
const parsed = JSON.parse(
|
|
1152
|
-
|
|
1153
|
-
const meta = isObject(parsed.metadata) ? parsed.metadata : null;
|
|
1154
|
-
const isError = meta ? meta.exit_code !== 0 && meta.exit_code !== void 0 : inferToolError(text);
|
|
1155
|
-
return { text, isError };
|
|
1980
|
+
const parsed = envelope ?? JSON.parse(original.text);
|
|
1981
|
+
if (isObject2(parsed) && !Array.isArray(parsed)) envelope = parsed;
|
|
1156
1982
|
} catch {
|
|
1157
|
-
return { text: rawOutput, isError: inferToolError(rawOutput) };
|
|
1158
1983
|
}
|
|
1984
|
+
const content = envelope && ("output" in envelope || "content" in envelope) ? outputContent(envelope.output ?? envelope.content) : original;
|
|
1985
|
+
const images = content === original ? original.images : [...original.images, ...content.images];
|
|
1986
|
+
const metadata = isObject2(envelope?.metadata) ? envelope.metadata : null;
|
|
1987
|
+
const exitCode = metadata?.exit_code ?? envelope?.exit_code;
|
|
1988
|
+
const explicitError = typeof envelope?.isError === "boolean" ? envelope.isError : typeof envelope?.is_error === "boolean" ? envelope.is_error : void 0;
|
|
1989
|
+
const isError = explicitError === true || (typeof exitCode === "number" ? exitCode !== 0 : explicitError ?? scriptFailed(original.parts) ?? hasFailedExit(content.text));
|
|
1990
|
+
return {
|
|
1991
|
+
text: content.text,
|
|
1992
|
+
isError,
|
|
1993
|
+
...images.length > 0 ? { images } : {}
|
|
1994
|
+
};
|
|
1159
1995
|
}
|
|
1160
1996
|
|
|
1161
1997
|
// src/lib/codex.ts
|
|
@@ -1177,7 +2013,7 @@ function safeParseLine(line) {
|
|
|
1177
2013
|
return null;
|
|
1178
2014
|
}
|
|
1179
2015
|
}
|
|
1180
|
-
function
|
|
2016
|
+
function isObject3(value) {
|
|
1181
2017
|
return typeof value === "object" && value !== null;
|
|
1182
2018
|
}
|
|
1183
2019
|
function isCodexRecord(record) {
|
|
@@ -1187,7 +2023,7 @@ function isCodexRecord(record) {
|
|
|
1187
2023
|
function extractMessageText(payload, blockType) {
|
|
1188
2024
|
const content = payload?.content;
|
|
1189
2025
|
if (!Array.isArray(content)) return "";
|
|
1190
|
-
return content.filter((block) =>
|
|
2026
|
+
return content.filter((block) => isObject3(block) && block.type === blockType && typeof block.text === "string").map((block) => block.text).join("\n").trim();
|
|
1191
2027
|
}
|
|
1192
2028
|
var CODEX_IMAGE_DATA_URL = /^data:(image\/(?:png|jpeg|gif|webp));base64,(.+)$/is;
|
|
1193
2029
|
var CODEX_AUDIO_DATA_URL = /^data:(audio\/(?:wav|x-wav|mpeg|mp4|webm|ogg));base64,(.+)$/is;
|
|
@@ -1227,11 +2063,11 @@ function extractEventMessageAudio(payload) {
|
|
|
1227
2063
|
}
|
|
1228
2064
|
function extractResponseMessageImages(payload) {
|
|
1229
2065
|
if (!Array.isArray(payload.content)) return [];
|
|
1230
|
-
return payload.content.filter((block) =>
|
|
2066
|
+
return payload.content.filter((block) => isObject3(block) && block.type === "input_image").map((block) => parseImageDataUrl(block.image_url)).filter((image) => image !== null);
|
|
1231
2067
|
}
|
|
1232
2068
|
function extractResponseMessageAudio(payload) {
|
|
1233
2069
|
if (!Array.isArray(payload.content)) return [];
|
|
1234
|
-
return payload.content.filter((block) =>
|
|
2070
|
+
return payload.content.filter((block) => isObject3(block) && block.type === "input_audio").map((block) => parseAudioDataUrl(block.audio_url)).filter((audio) => audio !== null);
|
|
1235
2071
|
}
|
|
1236
2072
|
function buildCodexUserContent(message, images, audio = [], localImages = [], localAudio = []) {
|
|
1237
2073
|
const attachmentSuffix = [
|
|
@@ -1276,17 +2112,8 @@ function normalizePromptText(text) {
|
|
|
1276
2112
|
if (SKIP_PROMPT_PREFIXES.some((prefix) => trimmed.startsWith(prefix))) return "";
|
|
1277
2113
|
return trimmed;
|
|
1278
2114
|
}
|
|
1279
|
-
function mergeTokenUsage2(existing, incoming) {
|
|
1280
|
-
if (!existing) return { ...incoming };
|
|
1281
|
-
return {
|
|
1282
|
-
input_tokens: existing.input_tokens + incoming.input_tokens,
|
|
1283
|
-
output_tokens: existing.output_tokens + incoming.output_tokens,
|
|
1284
|
-
cache_creation_input_tokens: (existing.cache_creation_input_tokens ?? 0) + (incoming.cache_creation_input_tokens ?? 0),
|
|
1285
|
-
cache_read_input_tokens: (existing.cache_read_input_tokens ?? 0) + (incoming.cache_read_input_tokens ?? 0)
|
|
1286
|
-
};
|
|
1287
|
-
}
|
|
1288
2115
|
function parseTokenUsage(value) {
|
|
1289
|
-
if (!
|
|
2116
|
+
if (!isObject3(value)) return null;
|
|
1290
2117
|
const reportedInputTokens = typeof value.input_tokens === "number" ? value.input_tokens : 0;
|
|
1291
2118
|
const outputTokens = typeof value.output_tokens === "number" ? value.output_tokens : 0;
|
|
1292
2119
|
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;
|
|
@@ -1301,16 +2128,6 @@ function parseTokenUsage(value) {
|
|
|
1301
2128
|
cache_read_input_tokens: cacheRead
|
|
1302
2129
|
};
|
|
1303
2130
|
}
|
|
1304
|
-
function appendAssistantText(turn, text, timestamp) {
|
|
1305
|
-
if (!text) return;
|
|
1306
|
-
turn.assistantText.push(text);
|
|
1307
|
-
const last = turn.contentBlocks[turn.contentBlocks.length - 1];
|
|
1308
|
-
if (last && last.kind === "text") {
|
|
1309
|
-
last.text.push(text);
|
|
1310
|
-
return;
|
|
1311
|
-
}
|
|
1312
|
-
turn.contentBlocks.push({ kind: "text", text: [text], timestamp });
|
|
1313
|
-
}
|
|
1314
2131
|
function appendThinking(turn, text, timestamp) {
|
|
1315
2132
|
if (!text) return;
|
|
1316
2133
|
const block = { type: "thinking", thinking: text, signature: "" };
|
|
@@ -1349,7 +2166,7 @@ function parseToolInput(argumentsText) {
|
|
|
1349
2166
|
if (typeof argumentsText !== "string" || !argumentsText.trim()) return {};
|
|
1350
2167
|
try {
|
|
1351
2168
|
const parsed = JSON.parse(argumentsText);
|
|
1352
|
-
return
|
|
2169
|
+
return isObject3(parsed) ? parsed : { value: parsed };
|
|
1353
2170
|
} catch {
|
|
1354
2171
|
return { raw: argumentsText };
|
|
1355
2172
|
}
|
|
@@ -1369,15 +2186,16 @@ function isCodeModeChild(callId) {
|
|
|
1369
2186
|
return callId.startsWith("exec-");
|
|
1370
2187
|
}
|
|
1371
2188
|
function parseMcpEventResult(value) {
|
|
1372
|
-
if (!
|
|
2189
|
+
if (!isObject3(value)) return { text: safeStringify(value), isError: false };
|
|
1373
2190
|
if ("Err" in value) return { text: safeStringify(value.Err), isError: true };
|
|
1374
|
-
const ok =
|
|
2191
|
+
const ok = isObject3(value.Ok) ? value.Ok : null;
|
|
1375
2192
|
if (!ok) return { text: safeStringify(value), isError: false };
|
|
1376
|
-
const parsedContent = parseCustomToolOutput(ok.content);
|
|
1377
|
-
const text = parsedContent.text || (ok.structuredContent !== void 0 ? safeStringify(ok.structuredContent) : safeStringify(ok));
|
|
2193
|
+
const parsedContent = parseCustomToolOutput({ ...ok, content: ok.content });
|
|
2194
|
+
const text = parsedContent.text || (parsedContent.images?.length ? "" : ok.structuredContent !== void 0 ? safeStringify(ok.structuredContent) : safeStringify(ok));
|
|
1378
2195
|
return {
|
|
2196
|
+
...parsedContent,
|
|
1379
2197
|
text,
|
|
1380
|
-
isError:
|
|
2198
|
+
isError: parsedContent.isError
|
|
1381
2199
|
};
|
|
1382
2200
|
}
|
|
1383
2201
|
function createTurn2(turnId, timestamp, model) {
|
|
@@ -1396,10 +2214,10 @@ function createTurn2(turnId, timestamp, model) {
|
|
|
1396
2214
|
};
|
|
1397
2215
|
}
|
|
1398
2216
|
function extractPromptFromRecord(record) {
|
|
1399
|
-
if (record.type === "event_msg" &&
|
|
2217
|
+
if (record.type === "event_msg" && isObject3(record.payload) && record.payload.type === "user_message" && typeof record.payload.message === "string") {
|
|
1400
2218
|
return normalizePromptText(record.payload.message);
|
|
1401
2219
|
}
|
|
1402
|
-
if (record.type === "response_item" &&
|
|
2220
|
+
if (record.type === "response_item" && isObject3(record.payload) && record.payload.type === "message" && record.payload.role === "user") {
|
|
1403
2221
|
return normalizePromptText(extractMessageText(record.payload, "input_text"));
|
|
1404
2222
|
}
|
|
1405
2223
|
return "";
|
|
@@ -1432,12 +2250,12 @@ function extractMetadataFromRecords(records) {
|
|
|
1432
2250
|
let agentPath = "/root";
|
|
1433
2251
|
let previousPrompt = "";
|
|
1434
2252
|
for (const record of records) {
|
|
1435
|
-
if (!
|
|
2253
|
+
if (!isObject3(record.payload)) continue;
|
|
1436
2254
|
if (record.type === "session_meta") {
|
|
1437
2255
|
sessionId ||= typeof record.payload.id === "string" ? record.payload.id : "";
|
|
1438
2256
|
version ||= typeof record.payload.cli_version === "string" ? record.payload.cli_version : "";
|
|
1439
2257
|
cwd ||= typeof record.payload.cwd === "string" ? record.payload.cwd : "";
|
|
1440
|
-
if (!branchedFrom &&
|
|
2258
|
+
if (!branchedFrom && isObject3(record.payload.branchedFrom)) {
|
|
1441
2259
|
const sourceId = typeof record.payload.branchedFrom.sessionId === "string" ? record.payload.branchedFrom.sessionId : "";
|
|
1442
2260
|
if (sourceId) {
|
|
1443
2261
|
branchedFrom = {
|
|
@@ -1446,10 +2264,10 @@ function extractMetadataFromRecords(records) {
|
|
|
1446
2264
|
};
|
|
1447
2265
|
}
|
|
1448
2266
|
}
|
|
1449
|
-
const source =
|
|
1450
|
-
if (source &&
|
|
2267
|
+
const source = isObject3(record.payload.source) ? record.payload.source : null;
|
|
2268
|
+
if (source && isObject3(source.subagent)) {
|
|
1451
2269
|
isSubagent = true;
|
|
1452
|
-
const threadSpawn =
|
|
2270
|
+
const threadSpawn = isObject3(source.subagent.thread_spawn) ? source.subagent.thread_spawn : null;
|
|
1453
2271
|
if (threadSpawn && typeof threadSpawn.agent_path === "string") {
|
|
1454
2272
|
agentPath = threadSpawn.agent_path;
|
|
1455
2273
|
}
|
|
@@ -1457,7 +2275,7 @@ function extractMetadataFromRecords(records) {
|
|
|
1457
2275
|
if (typeof record.payload.forked_from_id === "string" && record.payload.forked_from_id) {
|
|
1458
2276
|
parentSessionId = record.payload.forked_from_id;
|
|
1459
2277
|
}
|
|
1460
|
-
const git =
|
|
2278
|
+
const git = isObject3(record.payload.git) ? record.payload.git : null;
|
|
1461
2279
|
gitBranch ||= git && typeof git.branch === "string" ? git.branch : "";
|
|
1462
2280
|
}
|
|
1463
2281
|
if (record.type === "turn_context") {
|
|
@@ -1502,13 +2320,17 @@ function isCodexSessionText(jsonlText) {
|
|
|
1502
2320
|
}
|
|
1503
2321
|
return false;
|
|
1504
2322
|
}
|
|
2323
|
+
function isCodexRawRecords(records) {
|
|
2324
|
+
return isCodexRecord(records[0] ?? null);
|
|
2325
|
+
}
|
|
1505
2326
|
function extractCodexMetadataFromLines(lines) {
|
|
1506
2327
|
const records = lines.map(safeParseLine).filter(isCodexRecord);
|
|
1507
2328
|
return extractMetadataFromRecords(records);
|
|
1508
2329
|
}
|
|
1509
|
-
function
|
|
1510
|
-
const
|
|
1511
|
-
|
|
2330
|
+
function walkCodexRecords(records, options) {
|
|
2331
|
+
const metadata = extractMetadataFromRecords(
|
|
2332
|
+
records.filter(isCodexRecord)
|
|
2333
|
+
);
|
|
1512
2334
|
const turns = [];
|
|
1513
2335
|
const pendingToolCalls = /* @__PURE__ */ new Map();
|
|
1514
2336
|
const webSearchCalls = /* @__PURE__ */ new Map();
|
|
@@ -1518,7 +2340,7 @@ function parseCodexSession(jsonlText, options) {
|
|
|
1518
2340
|
const agentIdByPath = /* @__PURE__ */ new Map();
|
|
1519
2341
|
const agentIdBySpawnCall = /* @__PURE__ */ new Map();
|
|
1520
2342
|
const agentMessages = /* @__PURE__ */ new Map();
|
|
1521
|
-
function upsertAgentMessage(agentId, targetTurn,
|
|
2343
|
+
function upsertAgentMessage(agentId, targetTurn, eventTimestamp2, update = {}) {
|
|
1522
2344
|
const info = agentRegistry.get(agentId);
|
|
1523
2345
|
let entry = agentMessages.get(agentId);
|
|
1524
2346
|
if (!entry) {
|
|
@@ -1532,7 +2354,7 @@ function parseCodexSession(jsonlText, options) {
|
|
|
1532
2354
|
toolCalls: [],
|
|
1533
2355
|
thinking: [],
|
|
1534
2356
|
text: [],
|
|
1535
|
-
timestamp: info?.timestamp ??
|
|
2357
|
+
timestamp: info?.timestamp ?? eventTimestamp2,
|
|
1536
2358
|
tokenUsage: null,
|
|
1537
2359
|
model: info?.model ?? null,
|
|
1538
2360
|
isBackground: false,
|
|
@@ -1544,7 +2366,7 @@ function parseCodexSession(jsonlText, options) {
|
|
|
1544
2366
|
if (lastBlock?.kind === "sub_agent") {
|
|
1545
2367
|
lastBlock.messages.push(agentMessage2);
|
|
1546
2368
|
} else {
|
|
1547
|
-
targetTurn.contentBlocks.push({ kind: "sub_agent", messages: [agentMessage2], timestamp:
|
|
2369
|
+
targetTurn.contentBlocks.push({ kind: "sub_agent", messages: [agentMessage2], timestamp: eventTimestamp2 });
|
|
1548
2370
|
}
|
|
1549
2371
|
entry = { message: agentMessage2, turn: targetTurn };
|
|
1550
2372
|
agentMessages.set(agentId, entry);
|
|
@@ -1566,9 +2388,9 @@ function parseCodexSession(jsonlText, options) {
|
|
|
1566
2388
|
if (!(update.status === "running" && agentMessage.status && terminalStatuses.has(agentMessage.status))) {
|
|
1567
2389
|
agentMessage.status = update.status;
|
|
1568
2390
|
}
|
|
1569
|
-
if (terminalStatuses.has(update.status) && agentMessage.timestamp &&
|
|
2391
|
+
if (terminalStatuses.has(update.status) && agentMessage.timestamp && eventTimestamp2) {
|
|
1570
2392
|
const startedAt = new Date(agentMessage.timestamp).getTime();
|
|
1571
|
-
const endedAt = new Date(
|
|
2393
|
+
const endedAt = new Date(eventTimestamp2).getTime();
|
|
1572
2394
|
if (Number.isFinite(startedAt) && Number.isFinite(endedAt) && endedAt >= startedAt) {
|
|
1573
2395
|
agentMessage.durationMs = endedAt - startedAt;
|
|
1574
2396
|
}
|
|
@@ -1582,8 +2404,23 @@ function parseCodexSession(jsonlText, options) {
|
|
|
1582
2404
|
let lastTurnTimestamp = "";
|
|
1583
2405
|
let pendingCompaction = null;
|
|
1584
2406
|
let sawTurnStart = false;
|
|
2407
|
+
const turnStartIndices = [];
|
|
2408
|
+
let recordIndex = 0;
|
|
2409
|
+
let pendingContextIndex = null;
|
|
2410
|
+
let pendingTurnStart = null;
|
|
2411
|
+
function commitTurn(turn) {
|
|
2412
|
+
const before = turns.length;
|
|
2413
|
+
const result = finalizeTurn(turns, turn, lastTurnTimestamp);
|
|
2414
|
+
if (turns.length > before && pendingTurnStart !== null) {
|
|
2415
|
+
turnStartIndices.push(pendingTurnStart);
|
|
2416
|
+
}
|
|
2417
|
+
pendingTurnStart = null;
|
|
2418
|
+
return result;
|
|
2419
|
+
}
|
|
1585
2420
|
function createActiveTurn(turnId, timestamp, model) {
|
|
1586
2421
|
const turn = createTurn2(turnId, timestamp, model);
|
|
2422
|
+
pendingTurnStart = pendingContextIndex ?? recordIndex;
|
|
2423
|
+
pendingContextIndex = null;
|
|
1587
2424
|
if (!sawTurnStart) turn.isFragment = true;
|
|
1588
2425
|
if (pendingCompaction) {
|
|
1589
2426
|
turn.compactionSummary = pendingCompaction;
|
|
@@ -1610,18 +2447,22 @@ function parseCodexSession(jsonlText, options) {
|
|
|
1610
2447
|
webSearchCalls.set(id, toolCall);
|
|
1611
2448
|
appendToolCall(targetTurn, toolCall, timestamp);
|
|
1612
2449
|
}
|
|
1613
|
-
for (
|
|
1614
|
-
const
|
|
2450
|
+
for (let index = 0; index < records.length; index++) {
|
|
2451
|
+
const record = records[index];
|
|
2452
|
+
if (!isCodexRecord(record)) continue;
|
|
2453
|
+
recordIndex = index;
|
|
2454
|
+
const payload = isObject3(record.payload) ? record.payload : void 0;
|
|
1615
2455
|
const timestamp = record.timestamp ?? "";
|
|
1616
2456
|
if (record.type === "compacted") {
|
|
1617
|
-
current =
|
|
2457
|
+
current = commitTurn(current);
|
|
1618
2458
|
pendingCompaction = "Conversation compacted";
|
|
1619
2459
|
lastTurnTimestamp = timestamp;
|
|
1620
2460
|
continue;
|
|
1621
2461
|
}
|
|
1622
2462
|
if (record.type === "world_state") continue;
|
|
1623
2463
|
if (record.type === "turn_context") {
|
|
1624
|
-
current =
|
|
2464
|
+
current = commitTurn(current);
|
|
2465
|
+
pendingContextIndex = index;
|
|
1625
2466
|
currentTurnId = typeof payload?.turn_id === "string" ? payload.turn_id : null;
|
|
1626
2467
|
currentModel = typeof payload?.model === "string" ? payload.model : currentModel;
|
|
1627
2468
|
lastTurnTimestamp = timestamp;
|
|
@@ -1630,7 +2471,7 @@ function parseCodexSession(jsonlText, options) {
|
|
|
1630
2471
|
}
|
|
1631
2472
|
if (record.type === "event_msg" && payload?.type === "user_message" && typeof payload.message === "string") {
|
|
1632
2473
|
if (current && (current.assistantText.length > 0 || current.toolCalls.length > 0 || current.thinking.length > 0)) {
|
|
1633
|
-
current =
|
|
2474
|
+
current = commitTurn(current);
|
|
1634
2475
|
}
|
|
1635
2476
|
sawTurnStart = true;
|
|
1636
2477
|
current ??= createActiveTurn(currentTurnId, timestamp, currentModel);
|
|
@@ -1658,30 +2499,31 @@ function parseCodexSession(jsonlText, options) {
|
|
|
1658
2499
|
if (!current.timestamp) current.timestamp = timestamp;
|
|
1659
2500
|
if (timestamp) lastTurnTimestamp = timestamp;
|
|
1660
2501
|
if (record.type === "event_msg" && payload?.type === "token_count") {
|
|
1661
|
-
const info =
|
|
2502
|
+
const info = isObject3(payload.info) ? payload.info : null;
|
|
1662
2503
|
const lastUsage = info ? parseTokenUsage(info.last_token_usage) : null;
|
|
1663
2504
|
if (lastUsage) {
|
|
1664
|
-
current.tokenUsage =
|
|
2505
|
+
current.tokenUsage = mergeTokenUsage(current.tokenUsage, lastUsage);
|
|
1665
2506
|
}
|
|
1666
2507
|
continue;
|
|
1667
2508
|
}
|
|
1668
2509
|
if (record.type === "event_msg" && payload?.type === "web_search_end" && typeof payload.call_id === "string") {
|
|
1669
2510
|
const input = {};
|
|
1670
2511
|
if (typeof payload.query === "string" && payload.query) input.query = payload.query;
|
|
1671
|
-
if (
|
|
2512
|
+
if (isObject3(payload.action)) input.action = payload.action;
|
|
1672
2513
|
upsertWebSearchCall(current, payload.call_id, input, "completed", timestamp);
|
|
1673
2514
|
continue;
|
|
1674
2515
|
}
|
|
1675
|
-
if (record.type === "event_msg" && payload?.type === "mcp_tool_call_end" && typeof payload.call_id === "string" &&
|
|
2516
|
+
if (record.type === "event_msg" && payload?.type === "mcp_tool_call_end" && typeof payload.call_id === "string" && isObject3(payload.invocation)) {
|
|
1676
2517
|
const invocation = payload.invocation;
|
|
1677
2518
|
const server = typeof invocation.server === "string" ? invocation.server : "mcp";
|
|
1678
2519
|
const tool = typeof invocation.tool === "string" ? invocation.tool : "tool";
|
|
1679
|
-
const input =
|
|
2520
|
+
const input = isObject3(invocation.arguments) ? invocation.arguments : {};
|
|
1680
2521
|
const result = parseMcpEventResult(payload.result);
|
|
1681
2522
|
const existing = pendingToolCalls.get(payload.call_id);
|
|
1682
2523
|
if (existing) {
|
|
1683
2524
|
existing.result = result.text;
|
|
1684
2525
|
existing.isError = result.isError;
|
|
2526
|
+
if (result.images) existing.resultImages = result.images;
|
|
1685
2527
|
pendingToolCalls.delete(payload.call_id);
|
|
1686
2528
|
} else if (!isCodeModeChild(payload.call_id)) {
|
|
1687
2529
|
appendToolCall(current, {
|
|
@@ -1690,6 +2532,7 @@ function parseCodexSession(jsonlText, options) {
|
|
|
1690
2532
|
input,
|
|
1691
2533
|
result: result.text,
|
|
1692
2534
|
isError: result.isError,
|
|
2535
|
+
...result.images ? { resultImages: result.images } : {},
|
|
1693
2536
|
timestamp
|
|
1694
2537
|
}, timestamp);
|
|
1695
2538
|
}
|
|
@@ -1697,9 +2540,9 @@ function parseCodexSession(jsonlText, options) {
|
|
|
1697
2540
|
}
|
|
1698
2541
|
if (record.type === "event_msg" && payload?.type === "sub_agent_activity" && typeof payload.agent_thread_id === "string" && typeof payload.agent_path === "string") {
|
|
1699
2542
|
const agentId = payload.agent_thread_id;
|
|
1700
|
-
const
|
|
2543
|
+
const eventId2 = typeof payload.event_id === "string" ? payload.event_id : "";
|
|
1701
2544
|
const kind = typeof payload.kind === "string" ? payload.kind : "started";
|
|
1702
|
-
const spawnInfo =
|
|
2545
|
+
const spawnInfo = eventId2 ? spawnAgentCalls.get(eventId2) : void 0;
|
|
1703
2546
|
const existingId = agentIdByPath.get(payload.agent_path);
|
|
1704
2547
|
const existing = agentRegistry.get(agentId) ?? (existingId ? agentRegistry.get(existingId) : void 0);
|
|
1705
2548
|
if (kind !== "started" && !existing && !spawnInfo) continue;
|
|
@@ -1720,11 +2563,11 @@ function parseCodexSession(jsonlText, options) {
|
|
|
1720
2563
|
model: existing?.model ?? spawnInfo?.model ?? null,
|
|
1721
2564
|
agentType: existing?.agentType ?? spawnInfo?.agentType ?? null,
|
|
1722
2565
|
timestamp: existing?.timestamp ?? (timestamp || spawnInfo?.timestamp || ""),
|
|
1723
|
-
parentToolCallId: existing?.parentToolCallId ??
|
|
2566
|
+
parentToolCallId: existing?.parentToolCallId ?? eventId2
|
|
1724
2567
|
};
|
|
1725
2568
|
agentRegistry.set(agentId, info);
|
|
1726
2569
|
agentIdByPath.set(payload.agent_path, agentId);
|
|
1727
|
-
if (
|
|
2570
|
+
if (eventId2 && (kind === "started" || spawnInfo)) agentIdBySpawnCall.set(eventId2, agentId);
|
|
1728
2571
|
const status = kind === "interrupted" ? "interrupted" : "running";
|
|
1729
2572
|
upsertAgentMessage(agentId, current, timestamp, { status });
|
|
1730
2573
|
continue;
|
|
@@ -1752,7 +2595,7 @@ function parseCodexSession(jsonlText, options) {
|
|
|
1752
2595
|
}
|
|
1753
2596
|
if (payload.type === "web_search_call" && typeof payload.id === "string") {
|
|
1754
2597
|
const input = {};
|
|
1755
|
-
if (
|
|
2598
|
+
if (isObject3(payload.action)) input.action = payload.action;
|
|
1756
2599
|
const status = typeof payload.status === "string" ? payload.status : "completed";
|
|
1757
2600
|
upsertWebSearchCall(current, payload.id, input, status, timestamp);
|
|
1758
2601
|
continue;
|
|
@@ -1763,7 +2606,7 @@ function parseCodexSession(jsonlText, options) {
|
|
|
1763
2606
|
const toolCall = {
|
|
1764
2607
|
id: callId,
|
|
1765
2608
|
name: "ToolSearch",
|
|
1766
|
-
input:
|
|
2609
|
+
input: isObject3(payload.arguments) ? payload.arguments : {},
|
|
1767
2610
|
result: null,
|
|
1768
2611
|
isError: false,
|
|
1769
2612
|
timestamp
|
|
@@ -1777,7 +2620,7 @@ function parseCodexSession(jsonlText, options) {
|
|
|
1777
2620
|
const toolCall = pendingToolCalls.get(payload.call_id);
|
|
1778
2621
|
if (toolCall) {
|
|
1779
2622
|
const tools = Array.isArray(payload.tools) ? payload.tools : [];
|
|
1780
|
-
const names = tools.filter(
|
|
2623
|
+
const names = tools.filter(isObject3).map((tool) => findString(tool.name) ?? "").filter(Boolean);
|
|
1781
2624
|
const count = names.length > 0 ? names.length : tools.length;
|
|
1782
2625
|
const found = `Found ${count} tool${count === 1 ? "" : "s"}`;
|
|
1783
2626
|
toolCall.result = names.length > 0 ? `${found}: ${names.join(", ")}` : found;
|
|
@@ -1830,6 +2673,9 @@ function parseCodexSession(jsonlText, options) {
|
|
|
1830
2673
|
} else if (functionName === "update_plan") {
|
|
1831
2674
|
name = "TodoWrite";
|
|
1832
2675
|
input = normalizePlanToTodos(parsedInput);
|
|
2676
|
+
} else if (isCodexQuestionTool(functionName)) {
|
|
2677
|
+
name = "AskUserQuestion";
|
|
2678
|
+
input = normalizeQuestions(parsedInput);
|
|
1833
2679
|
}
|
|
1834
2680
|
if (functionName === "spawn_agent") {
|
|
1835
2681
|
spawnAgentCalls.set(payload.call_id, {
|
|
@@ -1847,7 +2693,8 @@ function parseCodexSession(jsonlText, options) {
|
|
|
1847
2693
|
input,
|
|
1848
2694
|
result: null,
|
|
1849
2695
|
isError: false,
|
|
1850
|
-
timestamp
|
|
2696
|
+
timestamp,
|
|
2697
|
+
...functionName === "request_user_input_async" ? { asyncQuestion: true } : {}
|
|
1851
2698
|
};
|
|
1852
2699
|
pendingToolCalls.set(toolCall.id, toolCall);
|
|
1853
2700
|
appendToolCall(current, toolCall, timestamp);
|
|
@@ -1856,9 +2703,11 @@ function parseCodexSession(jsonlText, options) {
|
|
|
1856
2703
|
if (payload.type === "function_call_output" && typeof payload.call_id === "string") {
|
|
1857
2704
|
const toolCall = pendingToolCalls.get(payload.call_id);
|
|
1858
2705
|
if (!toolCall) continue;
|
|
1859
|
-
const
|
|
2706
|
+
const parsedOutput = parseCustomToolOutput(payload.output);
|
|
2707
|
+
const output = parsedOutput.text;
|
|
1860
2708
|
toolCall.result = output;
|
|
1861
|
-
toolCall.isError =
|
|
2709
|
+
toolCall.isError = parsedOutput.isError;
|
|
2710
|
+
if (parsedOutput.images) toolCall.resultImages = parsedOutput.images;
|
|
1862
2711
|
pendingToolCalls.delete(payload.call_id);
|
|
1863
2712
|
if (toolCall.name === "spawn_agent" && output) {
|
|
1864
2713
|
try {
|
|
@@ -1890,9 +2739,9 @@ function parseCodexSession(jsonlText, options) {
|
|
|
1890
2739
|
if (toolCall.name === "wait_agent" && output && current) {
|
|
1891
2740
|
try {
|
|
1892
2741
|
const result = JSON.parse(output);
|
|
1893
|
-
const statusMap =
|
|
2742
|
+
const statusMap = isObject3(result.status) ? result.status : {};
|
|
1894
2743
|
for (const [agentId, status] of Object.entries(statusMap)) {
|
|
1895
|
-
const statusValue =
|
|
2744
|
+
const statusValue = isObject3(status) ? status : {};
|
|
1896
2745
|
const completedText = typeof statusValue.completed === "string" ? statusValue.completed : "";
|
|
1897
2746
|
const failedText = typeof statusValue.failed === "string" ? statusValue.failed : typeof statusValue.error === "string" ? statusValue.error : "";
|
|
1898
2747
|
const interruptedText = typeof statusValue.interrupted === "string" ? statusValue.interrupted : "";
|
|
@@ -1920,7 +2769,7 @@ function parseCodexSession(jsonlText, options) {
|
|
|
1920
2769
|
continue;
|
|
1921
2770
|
}
|
|
1922
2771
|
if (payload.type === "custom_tool_call" && typeof payload.call_id === "string") {
|
|
1923
|
-
const name = typeof payload.name === "string" ? payload.name : "tool";
|
|
2772
|
+
const name = normalizeFunctionName(typeof payload.name === "string" ? payload.name : "tool");
|
|
1924
2773
|
const rawInput = typeof payload.input === "string" ? payload.input : "";
|
|
1925
2774
|
const callId = payload.call_id;
|
|
1926
2775
|
const perFileCalls = rawInput ? parseCodexToolPatches(name, rawInput, callId, timestamp, metadata.cwd) : [];
|
|
@@ -1950,7 +2799,7 @@ function parseCodexSession(jsonlText, options) {
|
|
|
1950
2799
|
}
|
|
1951
2800
|
if (payload.type === "custom_tool_call_output" && typeof payload.call_id === "string") {
|
|
1952
2801
|
const callId = payload.call_id;
|
|
1953
|
-
const { text, isError } = parseCustomToolOutput(payload.output);
|
|
2802
|
+
const { text, isError, images } = parseCustomToolOutput(payload.output);
|
|
1954
2803
|
const patchCalls = patchCallIds.get(callId);
|
|
1955
2804
|
if (patchCalls) {
|
|
1956
2805
|
const failedIds = patchCalls.direct && isError ? new Set(patchCalls.calls.map((call) => call.id)) : findFailedNestedPatchCallIds(text, isError, patchCalls.calls);
|
|
@@ -1968,13 +2817,15 @@ function parseCodexSession(jsonlText, options) {
|
|
|
1968
2817
|
if (toolCall) {
|
|
1969
2818
|
toolCall.result = text;
|
|
1970
2819
|
toolCall.isError = isError;
|
|
2820
|
+
if (images) toolCall.resultImages = images;
|
|
1971
2821
|
pendingToolCalls.delete(callId);
|
|
1972
2822
|
}
|
|
1973
2823
|
continue;
|
|
1974
2824
|
}
|
|
1975
2825
|
}
|
|
1976
|
-
|
|
2826
|
+
commitTurn(current);
|
|
1977
2827
|
return {
|
|
2828
|
+
turnStartIndices,
|
|
1978
2829
|
sessionId: metadata.sessionId,
|
|
1979
2830
|
version: metadata.version,
|
|
1980
2831
|
gitBranch: metadata.gitBranch,
|
|
@@ -1989,115 +2840,1120 @@ function parseCodexSession(jsonlText, options) {
|
|
|
1989
2840
|
agentKind: "codex"
|
|
1990
2841
|
};
|
|
1991
2842
|
}
|
|
2843
|
+
function parseCodexSession(jsonlText, options) {
|
|
2844
|
+
const records = jsonlText.split("\n").map((line) => line.trim()).filter(Boolean).map(safeParseLine).filter(isCodexRecord);
|
|
2845
|
+
const { turnStartIndices: _starts, ...session } = walkCodexRecords(
|
|
2846
|
+
records,
|
|
2847
|
+
options
|
|
2848
|
+
);
|
|
2849
|
+
return session;
|
|
2850
|
+
}
|
|
2851
|
+
function appendCodexSession(existing, newJsonlText) {
|
|
2852
|
+
const prefix = existing.rawMessages.map((record) => JSON.stringify(record)).join("\n");
|
|
2853
|
+
return parseCodexSession(prefix ? `${prefix}
|
|
2854
|
+
${newJsonlText}` : newJsonlText);
|
|
2855
|
+
}
|
|
2856
|
+
var TERMINAL_INTER_AGENT_TYPES = /* @__PURE__ */ new Set(["FINAL_ANSWER", "ERROR", "FAILED", "INTERRUPTED"]);
|
|
2857
|
+
function codexPayloadText(payload) {
|
|
2858
|
+
const content = payload.content;
|
|
2859
|
+
if (!Array.isArray(content)) return "";
|
|
2860
|
+
return content.filter((block) => isObject3(block) && block.type === "input_text" && typeof block.text === "string").map((block) => block.text).join("\n");
|
|
2861
|
+
}
|
|
2862
|
+
function parseJsonObject(value) {
|
|
2863
|
+
if (typeof value !== "string") return null;
|
|
2864
|
+
try {
|
|
2865
|
+
const parsed = JSON.parse(value);
|
|
2866
|
+
return isObject3(parsed) ? parsed : null;
|
|
2867
|
+
} catch {
|
|
2868
|
+
return null;
|
|
2869
|
+
}
|
|
2870
|
+
}
|
|
2871
|
+
function collectCodexPendingAgents(rawMessages) {
|
|
2872
|
+
const alive = /* @__PURE__ */ new Map();
|
|
2873
|
+
const done = /* @__PURE__ */ new Set();
|
|
2874
|
+
const agentIdByPath = /* @__PURE__ */ new Map();
|
|
2875
|
+
const spawnCalls = /* @__PURE__ */ new Map();
|
|
2876
|
+
const waitCalls = /* @__PURE__ */ new Set();
|
|
2877
|
+
const interruptCalls = /* @__PURE__ */ new Map();
|
|
2878
|
+
function markAlive(agentId, description) {
|
|
2879
|
+
if (!done.has(agentId)) alive.set(agentId, alive.get(agentId) || description);
|
|
2880
|
+
}
|
|
2881
|
+
for (const msg of rawMessages) {
|
|
2882
|
+
const payload = isObject3(msg.payload) ? msg.payload : null;
|
|
2883
|
+
if (!payload) continue;
|
|
2884
|
+
if (msg.type === "event_msg" && payload.type === "sub_agent_activity" && typeof payload.agent_thread_id === "string" && typeof payload.agent_path === "string") {
|
|
2885
|
+
const agentId = payload.agent_thread_id;
|
|
2886
|
+
const kind = typeof payload.kind === "string" ? payload.kind : "started";
|
|
2887
|
+
const eventId2 = typeof payload.event_id === "string" ? payload.event_id : "";
|
|
2888
|
+
const existingId = agentIdByPath.get(payload.agent_path);
|
|
2889
|
+
const known = alive.has(agentId) || done.has(agentId) || existingId !== void 0 || spawnCalls.has(eventId2);
|
|
2890
|
+
if (kind !== "started" && !known) continue;
|
|
2891
|
+
if (existingId && existingId !== agentId) {
|
|
2892
|
+
if (alive.has(existingId)) {
|
|
2893
|
+
markAlive(agentId, alive.get(existingId) ?? "");
|
|
2894
|
+
alive.delete(existingId);
|
|
2895
|
+
}
|
|
2896
|
+
if (done.has(existingId)) done.add(agentId);
|
|
2897
|
+
}
|
|
2898
|
+
agentIdByPath.set(payload.agent_path, agentId);
|
|
2899
|
+
if (kind === "interrupted") {
|
|
2900
|
+
done.add(agentId);
|
|
2901
|
+
alive.delete(agentId);
|
|
2902
|
+
} else {
|
|
2903
|
+
markAlive(agentId, spawnCalls.get(eventId2) ?? "");
|
|
2904
|
+
}
|
|
2905
|
+
continue;
|
|
2906
|
+
}
|
|
2907
|
+
if (msg.type !== "response_item") continue;
|
|
2908
|
+
if (payload.type === "function_call" && typeof payload.call_id === "string") {
|
|
2909
|
+
const name = normalizeFunctionName(typeof payload.name === "string" ? payload.name : "");
|
|
2910
|
+
if (name === "spawn_agent") {
|
|
2911
|
+
const input = parseJsonObject(payload.arguments);
|
|
2912
|
+
spawnCalls.set(payload.call_id, typeof input?.task_name === "string" ? input.task_name : "");
|
|
2913
|
+
} else if (name === "wait_agent") {
|
|
2914
|
+
waitCalls.add(payload.call_id);
|
|
2915
|
+
} else if (name === "interrupt_agent") {
|
|
2916
|
+
const input = parseJsonObject(payload.arguments);
|
|
2917
|
+
const targets = [input?.agent_id, ...Array.isArray(input?.agent_ids) ? input.agent_ids : []].filter((id) => typeof id === "string");
|
|
2918
|
+
interruptCalls.set(payload.call_id, targets);
|
|
2919
|
+
}
|
|
2920
|
+
continue;
|
|
2921
|
+
}
|
|
2922
|
+
if (payload.type === "function_call_output" && typeof payload.call_id === "string") {
|
|
2923
|
+
const callId = payload.call_id;
|
|
2924
|
+
if (spawnCalls.has(callId)) {
|
|
2925
|
+
const output = parseJsonObject(payload.output);
|
|
2926
|
+
const agentId = typeof output?.agent_id === "string" ? output.agent_id : "";
|
|
2927
|
+
if (agentId) {
|
|
2928
|
+
markAlive(agentId, spawnCalls.get(callId) ?? "");
|
|
2929
|
+
const agentPath = typeof output?.task_name === "string" ? output.task_name : "";
|
|
2930
|
+
if (agentPath && !agentIdByPath.has(agentPath)) agentIdByPath.set(agentPath, agentId);
|
|
2931
|
+
}
|
|
2932
|
+
} else if (waitCalls.has(callId)) {
|
|
2933
|
+
const output = parseJsonObject(payload.output);
|
|
2934
|
+
const statusMap = isObject3(output?.status) ? output.status : {};
|
|
2935
|
+
for (const [agentId, status] of Object.entries(statusMap)) {
|
|
2936
|
+
const statusValue = isObject3(status) ? status : {};
|
|
2937
|
+
const lifecycle = typeof statusValue.status === "string" ? statusValue.status : "";
|
|
2938
|
+
const isTerminal = typeof statusValue.completed === "string" || typeof statusValue.failed === "string" || typeof statusValue.error === "string" || typeof statusValue.interrupted === "string" || lifecycle === "completed" || lifecycle === "failed" || lifecycle === "interrupted";
|
|
2939
|
+
if (isTerminal) {
|
|
2940
|
+
done.add(agentId);
|
|
2941
|
+
alive.delete(agentId);
|
|
2942
|
+
}
|
|
2943
|
+
}
|
|
2944
|
+
} else if (interruptCalls.has(callId)) {
|
|
2945
|
+
for (const agentId of interruptCalls.get(callId) ?? []) {
|
|
2946
|
+
done.add(agentId);
|
|
2947
|
+
alive.delete(agentId);
|
|
2948
|
+
}
|
|
2949
|
+
}
|
|
2950
|
+
continue;
|
|
2951
|
+
}
|
|
2952
|
+
if (payload.type === "agent_message" && typeof payload.author === "string") {
|
|
2953
|
+
const text = codexPayloadText(payload);
|
|
2954
|
+
const messageType = text.match(/^Message Type:\s*([^\r\n]+)/)?.[1]?.trim().toUpperCase() ?? "";
|
|
2955
|
+
if (!TERMINAL_INTER_AGENT_TYPES.has(messageType)) continue;
|
|
2956
|
+
const agentId = agentIdByPath.get(payload.author) ?? payload.author;
|
|
2957
|
+
done.add(agentId);
|
|
2958
|
+
alive.delete(agentId);
|
|
2959
|
+
}
|
|
2960
|
+
}
|
|
2961
|
+
return [...alive.entries()].filter(([agentId]) => !done.has(agentId)).map(([, description]) => description);
|
|
2962
|
+
}
|
|
2963
|
+
function deriveCodexSessionStatus(rawMessages) {
|
|
2964
|
+
for (let i = rawMessages.length - 1; i >= 0; i--) {
|
|
2965
|
+
const msg = rawMessages[i];
|
|
2966
|
+
if (msg.type === "event_msg") {
|
|
2967
|
+
const payload = msg.payload;
|
|
2968
|
+
switch (payload?.type) {
|
|
2969
|
+
case "task_complete": {
|
|
2970
|
+
const pending = collectCodexPendingAgents(rawMessages);
|
|
2971
|
+
if (pending.length > 0) {
|
|
2972
|
+
return {
|
|
2973
|
+
status: "awaiting_agents",
|
|
2974
|
+
pendingQueue: 0,
|
|
2975
|
+
pendingAgents: pending.length,
|
|
2976
|
+
pendingAgentDescriptions: pending.filter((description) => description.length > 0)
|
|
2977
|
+
};
|
|
2978
|
+
}
|
|
2979
|
+
return { status: "completed" };
|
|
2980
|
+
}
|
|
2981
|
+
case "task_started":
|
|
2982
|
+
return { status: "processing" };
|
|
2983
|
+
case "agent_message":
|
|
2984
|
+
return { status: "thinking" };
|
|
2985
|
+
case "token_count":
|
|
2986
|
+
continue;
|
|
2987
|
+
}
|
|
2988
|
+
}
|
|
2989
|
+
if (msg.type === "response_item") {
|
|
2990
|
+
const payload = msg.payload;
|
|
2991
|
+
if (!payload) continue;
|
|
2992
|
+
if (payload.type === "function_call") {
|
|
2993
|
+
return { status: "tool_use", toolName: payload.name ? normalizeFunctionName(payload.name) : payload.name };
|
|
2994
|
+
}
|
|
2995
|
+
if (payload.type === "message") {
|
|
2996
|
+
if (payload.role === "assistant") return { status: "thinking" };
|
|
2997
|
+
if (payload.role === "user") return { status: "processing" };
|
|
2998
|
+
}
|
|
2999
|
+
}
|
|
3000
|
+
}
|
|
3001
|
+
return { status: "idle" };
|
|
3002
|
+
}
|
|
3003
|
+
function brandCodexBranch(firstRecord, sessionId, turnIndex) {
|
|
3004
|
+
const payload = isObject3(firstRecord.payload) ? { ...firstRecord.payload } : {};
|
|
3005
|
+
const originalId = typeof payload.id === "string" ? payload.id : "";
|
|
3006
|
+
payload.id = sessionId;
|
|
3007
|
+
payload.branchedFrom = { sessionId: originalId, turnIndex };
|
|
3008
|
+
return { record: { ...firstRecord, payload }, originalId };
|
|
3009
|
+
}
|
|
3010
|
+
function codexTurnBoundaries(records) {
|
|
3011
|
+
return walkCodexRecords(records, { skipStats: true }).turnStartIndices;
|
|
3012
|
+
}
|
|
1992
3013
|
|
|
1993
|
-
// src/lib/
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
return
|
|
3014
|
+
// src/lib/copilot.ts
|
|
3015
|
+
var COPILOT_EVENT_PREFIX = /^(?:assistant|auto_mode|auto_mode_switch|background_tasks|canvas|command|custom|elicitation|exit_plan_mode|extensions|external_tool|factory|hook|managed_settings|mcp|model|pending_messages|permission|sampling|session|skill|subagent|system|tool|ui|user_input)\./;
|
|
3016
|
+
function isObject4(value) {
|
|
3017
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1997
3018
|
}
|
|
1998
|
-
function
|
|
1999
|
-
|
|
3019
|
+
function isRootEvent(event) {
|
|
3020
|
+
return typeof event.agentId !== "string" || event.agentId.length === 0;
|
|
3021
|
+
}
|
|
3022
|
+
function isCopilotEvent(value) {
|
|
3023
|
+
if (!isObject4(value) || typeof value.type !== "string" || !isObject4(value.data)) return false;
|
|
3024
|
+
return value.type === "abort" || value.type === "binary_asset" || value.type === "user.message" || COPILOT_EVENT_PREFIX.test(value.type);
|
|
3025
|
+
}
|
|
3026
|
+
function safeParseLine2(line) {
|
|
3027
|
+
try {
|
|
3028
|
+
const value = JSON.parse(line);
|
|
3029
|
+
return isCopilotEvent(value) ? value : null;
|
|
3030
|
+
} catch {
|
|
3031
|
+
return null;
|
|
3032
|
+
}
|
|
3033
|
+
}
|
|
3034
|
+
function parseEvents(jsonlText) {
|
|
3035
|
+
return jsonlText.split("\n").map((line) => line.trim()).filter(Boolean).map(safeParseLine2).filter((event) => event !== null);
|
|
3036
|
+
}
|
|
3037
|
+
function readString(...values) {
|
|
3038
|
+
return values.find((value) => typeof value === "string") ?? "";
|
|
3039
|
+
}
|
|
3040
|
+
function readNumber(value) {
|
|
3041
|
+
return typeof value === "number" && Number.isFinite(value) ? value : 0;
|
|
3042
|
+
}
|
|
3043
|
+
function eventTimestamp(event) {
|
|
3044
|
+
return typeof event.timestamp === "string" ? event.timestamp : "";
|
|
3045
|
+
}
|
|
3046
|
+
function eventId(event, prefix) {
|
|
3047
|
+
if (typeof event.id === "string" && event.id) return event.id;
|
|
3048
|
+
return globalThis.crypto?.randomUUID?.() ?? `${prefix}-${Math.random().toString(36).slice(2, 10)}`;
|
|
3049
|
+
}
|
|
3050
|
+
function turnEventId(event) {
|
|
3051
|
+
const durableEventId = eventId(event, "copilot-turn");
|
|
3052
|
+
const sourceTurnId = readString(event.data.turnId);
|
|
3053
|
+
return sourceTurnId ? `${sourceTurnId}@${durableEventId}` : durableEventId;
|
|
3054
|
+
}
|
|
3055
|
+
function applyContext(context, current) {
|
|
3056
|
+
if (!isObject4(context)) return;
|
|
3057
|
+
const cwd = readString(context.cwd);
|
|
3058
|
+
const branch = readString(context.branch);
|
|
3059
|
+
if (cwd) current.cwd = cwd;
|
|
3060
|
+
if (branch) current.gitBranch = branch;
|
|
3061
|
+
}
|
|
3062
|
+
function extractMetadata(events) {
|
|
3063
|
+
const metadata = {
|
|
3064
|
+
sessionId: "",
|
|
3065
|
+
version: "",
|
|
3066
|
+
gitBranch: "",
|
|
3067
|
+
cwd: "",
|
|
3068
|
+
model: "",
|
|
3069
|
+
slug: "",
|
|
3070
|
+
name: "",
|
|
3071
|
+
firstUserMessage: "",
|
|
3072
|
+
lastUserMessage: "",
|
|
3073
|
+
timestamp: "",
|
|
3074
|
+
lastTimestamp: "",
|
|
3075
|
+
turnCount: 0,
|
|
3076
|
+
isSubagent: false,
|
|
3077
|
+
parentSessionId: null
|
|
3078
|
+
};
|
|
3079
|
+
for (const event of events) {
|
|
3080
|
+
if (!isRootEvent(event)) continue;
|
|
3081
|
+
const { data } = event;
|
|
3082
|
+
const timestamp = eventTimestamp(event);
|
|
3083
|
+
if (timestamp) metadata.lastTimestamp = timestamp;
|
|
3084
|
+
if (event.type === "session.start") {
|
|
3085
|
+
metadata.sessionId ||= readString(data.sessionId);
|
|
3086
|
+
metadata.version ||= readString(data.copilotVersion) || (typeof data.version === "number" ? String(data.version) : readString(data.version));
|
|
3087
|
+
applyContext(data.context, metadata);
|
|
3088
|
+
metadata.model = readString(data.selectedModel) || metadata.model;
|
|
3089
|
+
continue;
|
|
3090
|
+
}
|
|
3091
|
+
if (event.type === "session.resume") {
|
|
3092
|
+
applyContext(data.context, metadata);
|
|
3093
|
+
metadata.model = readString(data.selectedModel) || metadata.model;
|
|
3094
|
+
continue;
|
|
3095
|
+
}
|
|
3096
|
+
if (event.type === "session.context_changed") {
|
|
3097
|
+
applyContext(data, metadata);
|
|
3098
|
+
continue;
|
|
3099
|
+
}
|
|
3100
|
+
if (event.type === "session.auto_mode_resolved") {
|
|
3101
|
+
metadata.model = readString(data.chosenModel) || metadata.model;
|
|
3102
|
+
continue;
|
|
3103
|
+
}
|
|
3104
|
+
if (event.type === "session.model_change") {
|
|
3105
|
+
metadata.model = readString(data.newModel) || metadata.model;
|
|
3106
|
+
continue;
|
|
3107
|
+
}
|
|
3108
|
+
if (event.type === "session.title_changed") {
|
|
3109
|
+
metadata.name = readString(data.title) || metadata.name;
|
|
3110
|
+
continue;
|
|
3111
|
+
}
|
|
3112
|
+
if (event.type === "session.shutdown") {
|
|
3113
|
+
metadata.model = readString(data.currentModel) || metadata.model;
|
|
3114
|
+
continue;
|
|
3115
|
+
}
|
|
3116
|
+
if (event.type === "assistant.turn_start" || event.type === "assistant.turn_end" || event.type === "assistant.message" || event.type === "assistant.usage" || event.type === "tool.execution_start" || event.type === "tool.execution_complete") {
|
|
3117
|
+
metadata.model = readString(data.model) || metadata.model;
|
|
3118
|
+
}
|
|
3119
|
+
if (event.type !== "user.message") continue;
|
|
3120
|
+
metadata.turnCount++;
|
|
3121
|
+
metadata.timestamp ||= timestamp;
|
|
3122
|
+
const content = readString(data.content).trim();
|
|
3123
|
+
if (!content) continue;
|
|
3124
|
+
metadata.firstUserMessage ||= content;
|
|
3125
|
+
metadata.lastUserMessage = content;
|
|
3126
|
+
}
|
|
3127
|
+
metadata.lastTimestamp ||= metadata.timestamp;
|
|
3128
|
+
return metadata;
|
|
3129
|
+
}
|
|
3130
|
+
function isCopilotSessionText(jsonlText) {
|
|
2000
3131
|
for (const line of jsonlText.split("\n")) {
|
|
2001
3132
|
const trimmed = line.trim();
|
|
2002
3133
|
if (!trimmed) continue;
|
|
2003
3134
|
try {
|
|
2004
|
-
|
|
3135
|
+
const value = JSON.parse(trimmed);
|
|
3136
|
+
if (isCopilotEvent(value)) return true;
|
|
2005
3137
|
} catch {
|
|
2006
3138
|
}
|
|
2007
3139
|
}
|
|
2008
|
-
return
|
|
3140
|
+
return false;
|
|
2009
3141
|
}
|
|
2010
|
-
function
|
|
2011
|
-
|
|
2012
|
-
return firstType === "session_meta" || firstType === "turn_context" || firstType === "event_msg" || firstType === "response_item" || firstType === "compacted" || firstType === "world_state" || firstType === "inter_agent_communication_metadata";
|
|
3142
|
+
function isCopilotRawRecords(records) {
|
|
3143
|
+
return isCopilotEventType(records[0]?.type);
|
|
2013
3144
|
}
|
|
2014
|
-
function
|
|
2015
|
-
|
|
3145
|
+
function extractCopilotMetadataFromLines(lines) {
|
|
3146
|
+
const events = lines.map((line) => safeParseLine2(line.trim())).filter(
|
|
3147
|
+
(event) => event !== null
|
|
3148
|
+
);
|
|
3149
|
+
return extractMetadata(events);
|
|
2016
3150
|
}
|
|
2017
|
-
function
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
if (msg.slug && !meta.slug) meta.slug = msg.slug;
|
|
2025
|
-
if (msg.name && !meta.name) meta.name = msg.name;
|
|
2026
|
-
if (msg.branchedFrom && !meta.branchedFrom) {
|
|
2027
|
-
meta.branchedFrom = msg.branchedFrom;
|
|
2028
|
-
}
|
|
2029
|
-
if (isAssistantMessage(msg) && msg.message.model && !meta.model) {
|
|
2030
|
-
meta.model = msg.message.model;
|
|
2031
|
-
}
|
|
2032
|
-
if (meta.sessionId && meta.version && meta.gitBranch && meta.cwd && meta.model) break;
|
|
3151
|
+
function safeStringify2(value) {
|
|
3152
|
+
if (typeof value === "string") return value;
|
|
3153
|
+
if (value === void 0) return "";
|
|
3154
|
+
try {
|
|
3155
|
+
return JSON.stringify(value);
|
|
3156
|
+
} catch {
|
|
3157
|
+
return String(value);
|
|
2033
3158
|
}
|
|
2034
|
-
return meta;
|
|
2035
3159
|
}
|
|
2036
|
-
function
|
|
2037
|
-
if (
|
|
2038
|
-
|
|
3160
|
+
function textFromValue(value) {
|
|
3161
|
+
if (typeof value === "string") return value;
|
|
3162
|
+
if (!Array.isArray(value)) return "";
|
|
3163
|
+
return value.flatMap((part) => {
|
|
3164
|
+
const text = typeof part === "string" ? part : isObject4(part) ? readString(part.text, part.content) : "";
|
|
3165
|
+
return text ? [text] : [];
|
|
3166
|
+
}).join("\n");
|
|
3167
|
+
}
|
|
3168
|
+
function collectBinaryAssets(events) {
|
|
3169
|
+
const assets = /* @__PURE__ */ new Map();
|
|
3170
|
+
for (const event of events) {
|
|
3171
|
+
if (event.type !== "session.binary_asset" && event.type !== "binary_asset") continue;
|
|
3172
|
+
const assetId = readString(event.data.assetId);
|
|
3173
|
+
const data = readString(event.data.data);
|
|
3174
|
+
const mimeType = readString(event.data.mimeType);
|
|
3175
|
+
if (assetId && data && mimeType) assets.set(assetId, { data, mimeType });
|
|
2039
3176
|
}
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
3177
|
+
return assets;
|
|
3178
|
+
}
|
|
3179
|
+
function resolveImage(value, assets) {
|
|
3180
|
+
if (!isObject4(value)) return null;
|
|
3181
|
+
const asset = assets.get(readString(value.assetId));
|
|
3182
|
+
const mimeType = readString(value.mimeType, asset?.mimeType).toLowerCase();
|
|
3183
|
+
const data = readString(value.data, asset?.data);
|
|
3184
|
+
if (!mimeType.startsWith("image/") || !data) return null;
|
|
2044
3185
|
return {
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
stats,
|
|
2048
|
-
rawMessages: options?.skipStats ? [] : rawMessages,
|
|
2049
|
-
agentKind: "claude"
|
|
3186
|
+
type: "image",
|
|
3187
|
+
source: { type: "base64", media_type: mimeType, data }
|
|
2050
3188
|
};
|
|
2051
3189
|
}
|
|
2052
|
-
function
|
|
2053
|
-
if (
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
const historyTurnCount = Math.max(0, existing.turns.length - turnStarts.length);
|
|
2064
|
-
let rebuildFromTurn = Math.max(0, turnStarts.length - 1);
|
|
2065
|
-
const progressParentIds = /* @__PURE__ */ new Set();
|
|
2066
|
-
for (const msg of newMessages) {
|
|
2067
|
-
if (msg.type === "progress") {
|
|
2068
|
-
const parentId = msg.parentToolUseID;
|
|
2069
|
-
if (parentId) progressParentIds.add(parentId);
|
|
2070
|
-
}
|
|
3190
|
+
function extractImages(values, assets) {
|
|
3191
|
+
if (!Array.isArray(values)) return [];
|
|
3192
|
+
const images = [];
|
|
3193
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3194
|
+
for (const value of values) {
|
|
3195
|
+
const image = resolveImage(value, assets);
|
|
3196
|
+
if (!image) continue;
|
|
3197
|
+
const key = `${image.source.media_type}:${image.source.data}`;
|
|
3198
|
+
if (seen.has(key)) continue;
|
|
3199
|
+
seen.add(key);
|
|
3200
|
+
images.push(image);
|
|
2071
3201
|
}
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
3202
|
+
return images;
|
|
3203
|
+
}
|
|
3204
|
+
function buildUserContent(data, assets) {
|
|
3205
|
+
const text = readString(data.content);
|
|
3206
|
+
const images = extractImages(data.attachments, assets);
|
|
3207
|
+
if (images.length === 0) return text;
|
|
3208
|
+
return [...images, ...text ? [{ type: "text", text }] : []];
|
|
3209
|
+
}
|
|
3210
|
+
function extractToolResultImages(data, assets) {
|
|
3211
|
+
if (!isObject4(data.result)) return [];
|
|
3212
|
+
return extractImages([
|
|
3213
|
+
...Array.isArray(data.result.contents) ? data.result.contents : [],
|
|
3214
|
+
...Array.isArray(data.result.binaryResultsForLlm) ? data.result.binaryResultsForLlm : []
|
|
3215
|
+
], assets);
|
|
3216
|
+
}
|
|
3217
|
+
function parseUsage(data, inputIncludesCache = false) {
|
|
3218
|
+
const cacheCreationInputTokens = readNumber(data.cacheWriteTokens);
|
|
3219
|
+
const cacheReadInputTokens = readNumber(data.cacheReadTokens);
|
|
3220
|
+
const inputTokens = readNumber(data.inputTokens);
|
|
3221
|
+
const usage = {
|
|
3222
|
+
input_tokens: inputIncludesCache ? Math.max(0, inputTokens - cacheCreationInputTokens - cacheReadInputTokens) : inputTokens,
|
|
3223
|
+
output_tokens: readNumber(data.outputTokens),
|
|
3224
|
+
cache_creation_input_tokens: cacheCreationInputTokens,
|
|
3225
|
+
cache_read_input_tokens: cacheReadInputTokens
|
|
3226
|
+
};
|
|
3227
|
+
const thinkingTokens = readNumber(data.reasoningTokens);
|
|
3228
|
+
if (thinkingTokens) usage.output_tokens_details = { thinking_tokens: thinkingTokens };
|
|
3229
|
+
if (usage.input_tokens === 0 && usage.output_tokens === 0 && usage.cache_creation_input_tokens === 0 && usage.cache_read_input_tokens === 0 && thinkingTokens === 0) return null;
|
|
3230
|
+
return usage;
|
|
3231
|
+
}
|
|
3232
|
+
function parseModelMetrics(data) {
|
|
3233
|
+
if (!isObject4(data.modelMetrics)) return null;
|
|
3234
|
+
let total = null;
|
|
3235
|
+
for (const value of Object.values(data.modelMetrics)) {
|
|
3236
|
+
if (!isObject4(value) || !isObject4(value.usage)) continue;
|
|
3237
|
+
const usage = parseUsage(value.usage, true);
|
|
3238
|
+
if (usage) total = mergeTokenUsage(total, usage);
|
|
2080
3239
|
}
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
3240
|
+
return total;
|
|
3241
|
+
}
|
|
3242
|
+
function normalizeChoice(value) {
|
|
3243
|
+
if (typeof value === "string") return { label: value };
|
|
3244
|
+
if (!isObject4(value)) return null;
|
|
3245
|
+
const label = readString(value.label, value.title, value.value, value.name);
|
|
3246
|
+
if (!label) return null;
|
|
3247
|
+
const description = readString(value.description);
|
|
3248
|
+
return description ? { label, description } : { label };
|
|
3249
|
+
}
|
|
3250
|
+
function normalizeChoices(value) {
|
|
3251
|
+
if (!Array.isArray(value)) return [];
|
|
3252
|
+
return value.map(normalizeChoice).filter((choice) => choice !== null);
|
|
3253
|
+
}
|
|
3254
|
+
function normalizeQuestion(value) {
|
|
3255
|
+
if (!isObject4(value)) return null;
|
|
3256
|
+
const question = readString(value.question, value.prompt, value.text, value.message);
|
|
3257
|
+
if (!question) return null;
|
|
3258
|
+
const header = readString(value.header, value.title);
|
|
3259
|
+
const multiSelect = typeof value.multiSelect === "boolean" ? value.multiSelect : typeof value.multi_select === "boolean" ? value.multi_select : void 0;
|
|
2086
3260
|
return {
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
3261
|
+
question,
|
|
3262
|
+
...header ? { header } : {},
|
|
3263
|
+
options: normalizeChoices(value.options ?? value.choices),
|
|
3264
|
+
...multiSelect === void 0 ? {} : { multiSelect }
|
|
3265
|
+
};
|
|
3266
|
+
}
|
|
3267
|
+
function normalizeAskUserInput(input) {
|
|
3268
|
+
const source = Array.isArray(input.questions) ? input.questions : [input];
|
|
3269
|
+
const questions = source.map(normalizeQuestion).filter((question) => question !== null);
|
|
3270
|
+
return questions.length > 0 ? { ...input, questions } : input;
|
|
3271
|
+
}
|
|
3272
|
+
function normalizeToolName(name) {
|
|
3273
|
+
switch (name.toLowerCase().replace(/-/g, "_")) {
|
|
3274
|
+
case "ask_user":
|
|
3275
|
+
return "AskUserQuestion";
|
|
3276
|
+
case "bash":
|
|
3277
|
+
case "powershell":
|
|
3278
|
+
case "shell":
|
|
3279
|
+
return "Bash";
|
|
3280
|
+
case "create":
|
|
3281
|
+
return "Write";
|
|
3282
|
+
case "edit":
|
|
3283
|
+
return "Edit";
|
|
3284
|
+
default:
|
|
3285
|
+
return name;
|
|
3286
|
+
}
|
|
3287
|
+
}
|
|
3288
|
+
function normalizeToolInput(name, value) {
|
|
3289
|
+
const input = isObject4(value) ? value : value === void 0 ? {} : { value };
|
|
3290
|
+
if (name === "AskUserQuestion") return normalizeAskUserInput(input);
|
|
3291
|
+
if (name === "Edit") {
|
|
3292
|
+
return {
|
|
3293
|
+
...input,
|
|
3294
|
+
file_path: readString(input.file_path, input.path),
|
|
3295
|
+
old_string: readString(input.old_string, input.old_str),
|
|
3296
|
+
new_string: readString(input.new_string, input.new_str)
|
|
3297
|
+
};
|
|
3298
|
+
}
|
|
3299
|
+
if (name === "Write") {
|
|
3300
|
+
return {
|
|
3301
|
+
...input,
|
|
3302
|
+
file_path: readString(input.file_path, input.path),
|
|
3303
|
+
content: readString(input.content, input.file_text)
|
|
3304
|
+
};
|
|
3305
|
+
}
|
|
3306
|
+
if (name === "Bash" && typeof input.command !== "string" && typeof input.cmd === "string") {
|
|
3307
|
+
return { ...input, command: input.cmd };
|
|
3308
|
+
}
|
|
3309
|
+
return input;
|
|
3310
|
+
}
|
|
3311
|
+
function extractToolResult(data) {
|
|
3312
|
+
const error = data.error;
|
|
3313
|
+
if (typeof error === "string") return error;
|
|
3314
|
+
if (isObject4(error)) {
|
|
3315
|
+
const message = readString(error.message, error.code);
|
|
3316
|
+
if (message) return message;
|
|
3317
|
+
}
|
|
3318
|
+
const result = data.result;
|
|
3319
|
+
if (typeof result === "string") return result;
|
|
3320
|
+
if (!isObject4(result)) return result === void 0 ? "" : safeStringify2(result);
|
|
3321
|
+
if (typeof result.detailedContent === "string") return result.detailedContent;
|
|
3322
|
+
if (typeof result.content === "string") return result.content;
|
|
3323
|
+
if (Array.isArray(result.contents)) {
|
|
3324
|
+
const content = result.contents.flatMap((part) => {
|
|
3325
|
+
const text = typeof part === "string" ? part : isObject4(part) ? readString(part.text, part.content, part.output) : "";
|
|
3326
|
+
return text ? [text] : [];
|
|
3327
|
+
}).join("\n");
|
|
3328
|
+
if (content) return content;
|
|
3329
|
+
}
|
|
3330
|
+
if (result.structuredContent !== void 0) return safeStringify2(result.structuredContent);
|
|
3331
|
+
return safeStringify2(result);
|
|
3332
|
+
}
|
|
3333
|
+
function appendThinking2(turn, text, timestamp, signature) {
|
|
3334
|
+
if (!text || turn.thinking.some((block2) => block2.thinking === text)) return;
|
|
3335
|
+
const block = { type: "thinking", thinking: text, signature };
|
|
3336
|
+
turn.thinking.push(block);
|
|
3337
|
+
const last = turn.contentBlocks[turn.contentBlocks.length - 1];
|
|
3338
|
+
if (last?.kind === "thinking") {
|
|
3339
|
+
last.blocks.push(block);
|
|
3340
|
+
return;
|
|
3341
|
+
}
|
|
3342
|
+
turn.contentBlocks.push({ kind: "thinking", blocks: [block], timestamp });
|
|
3343
|
+
}
|
|
3344
|
+
function appendToolCall2(turn, call, timestamp) {
|
|
3345
|
+
turn.toolCalls.push(call);
|
|
3346
|
+
const last = turn.contentBlocks[turn.contentBlocks.length - 1];
|
|
3347
|
+
if (last?.kind === "tool_calls") {
|
|
3348
|
+
last.toolCalls.push(call);
|
|
3349
|
+
return;
|
|
3350
|
+
}
|
|
3351
|
+
turn.contentBlocks.push({ kind: "tool_calls", toolCalls: [call], timestamp });
|
|
3352
|
+
}
|
|
3353
|
+
function newToolCall(id, name, input, timestamp) {
|
|
3354
|
+
return { id, name, input, result: null, isError: false, timestamp };
|
|
3355
|
+
}
|
|
3356
|
+
function toolCallsFromRequests(value, timestamp, isPending) {
|
|
3357
|
+
if (!Array.isArray(value)) return [];
|
|
3358
|
+
const calls = [];
|
|
3359
|
+
for (const request of value) {
|
|
3360
|
+
if (!isObject4(request)) continue;
|
|
3361
|
+
const id = readString(request.toolCallId);
|
|
3362
|
+
const rawName = readString(request.name);
|
|
3363
|
+
if (!id || !rawName || isPending(id)) continue;
|
|
3364
|
+
const name = normalizeToolName(rawName);
|
|
3365
|
+
calls.push(newToolCall(id, name, normalizeToolInput(name, request.arguments), timestamp));
|
|
3366
|
+
}
|
|
3367
|
+
return calls;
|
|
3368
|
+
}
|
|
3369
|
+
function updateToolCallStart(call, name, input, timestamp) {
|
|
3370
|
+
call.name = name;
|
|
3371
|
+
call.input = normalizeToolInput(name, input);
|
|
3372
|
+
call.timestamp = timestamp || call.timestamp;
|
|
3373
|
+
}
|
|
3374
|
+
function updateToolCallResult(call, data, binaryAssets) {
|
|
3375
|
+
call.result = extractToolResult(data);
|
|
3376
|
+
const resultImages = extractToolResultImages(data, binaryAssets);
|
|
3377
|
+
if (resultImages.length > 0) call.resultImages = resultImages;
|
|
3378
|
+
call.isError = data.success === false || data.error !== void 0 && data.error !== null;
|
|
3379
|
+
}
|
|
3380
|
+
function createTurn3(id, timestamp, userMessage, model, effort, isFragment) {
|
|
3381
|
+
return {
|
|
3382
|
+
id,
|
|
3383
|
+
userMessage,
|
|
3384
|
+
contentBlocks: [],
|
|
3385
|
+
thinking: [],
|
|
3386
|
+
assistantText: [],
|
|
3387
|
+
toolCalls: [],
|
|
3388
|
+
subAgentActivity: [],
|
|
3389
|
+
timestamp,
|
|
3390
|
+
durationMs: null,
|
|
3391
|
+
tokenUsage: null,
|
|
3392
|
+
model,
|
|
3393
|
+
...effort ? { effort } : {},
|
|
3394
|
+
...isFragment ? { isFragment: true } : {}
|
|
3395
|
+
};
|
|
3396
|
+
}
|
|
3397
|
+
function hasTurnContent(turn) {
|
|
3398
|
+
return turn.userMessage !== null || turn.assistantText.length > 0 || turn.thinking.length > 0 || turn.toolCalls.length > 0 || turn.subAgentActivity.length > 0;
|
|
3399
|
+
}
|
|
3400
|
+
function parseCopilotSession(jsonlText, options) {
|
|
3401
|
+
const events = parseEvents(jsonlText);
|
|
3402
|
+
const metadata = extractMetadata(events);
|
|
3403
|
+
const binaryAssets = collectBinaryAssets(events);
|
|
3404
|
+
const turns = [];
|
|
3405
|
+
const pendingToolCalls = /* @__PURE__ */ new Map();
|
|
3406
|
+
const agentMessages = /* @__PURE__ */ new Map();
|
|
3407
|
+
const pendingAgentToolCalls = /* @__PURE__ */ new Map();
|
|
3408
|
+
const endTimestamps = /* @__PURE__ */ new WeakMap();
|
|
3409
|
+
const apiDurationMs = /* @__PURE__ */ new WeakMap();
|
|
3410
|
+
let current = null;
|
|
3411
|
+
let currentModel = null;
|
|
3412
|
+
let currentEffort;
|
|
3413
|
+
let sawAssistantUsage = false;
|
|
3414
|
+
let sawAssistantDuration = false;
|
|
3415
|
+
let shutdownUsage = null;
|
|
3416
|
+
function noteTimestamp(turn, timestamp) {
|
|
3417
|
+
if (timestamp) endTimestamps.set(turn, timestamp);
|
|
3418
|
+
}
|
|
3419
|
+
function ensureTurn(event) {
|
|
3420
|
+
if (!current) {
|
|
3421
|
+
current = createTurn3(
|
|
3422
|
+
turnEventId(event),
|
|
3423
|
+
eventTimestamp(event),
|
|
3424
|
+
null,
|
|
3425
|
+
currentModel,
|
|
3426
|
+
currentEffort,
|
|
3427
|
+
true
|
|
3428
|
+
);
|
|
3429
|
+
}
|
|
3430
|
+
return current;
|
|
3431
|
+
}
|
|
3432
|
+
function finalizeCurrent() {
|
|
3433
|
+
if (!current) return;
|
|
3434
|
+
if (hasTurnContent(current)) turns.push(current);
|
|
3435
|
+
current = null;
|
|
3436
|
+
}
|
|
3437
|
+
function ensureAgent(event) {
|
|
3438
|
+
const agentId = typeof event.agentId === "string" ? event.agentId : "";
|
|
3439
|
+
if (!agentId) return null;
|
|
3440
|
+
const existing = agentMessages.get(agentId);
|
|
3441
|
+
if (existing) return existing.message;
|
|
3442
|
+
const data = event.data;
|
|
3443
|
+
const parentToolUseId = readString(data.toolCallId);
|
|
3444
|
+
const targetTurn = parentToolUseId ? pendingToolCalls.get(parentToolUseId)?.turn ?? current : current;
|
|
3445
|
+
if (!targetTurn) return null;
|
|
3446
|
+
const executionMode = readString(data.executionMode);
|
|
3447
|
+
const message = {
|
|
3448
|
+
agentId,
|
|
3449
|
+
...parentToolUseId ? { parentToolUseId } : {},
|
|
3450
|
+
agentName: readString(data.agentDisplayName, data.agentName) || null,
|
|
3451
|
+
subagentType: readString(data.agentType, data.agentName) || null,
|
|
3452
|
+
type: "assistant",
|
|
3453
|
+
content: null,
|
|
3454
|
+
toolCalls: [],
|
|
3455
|
+
thinking: [],
|
|
3456
|
+
text: [],
|
|
3457
|
+
timestamp: eventTimestamp(event),
|
|
3458
|
+
tokenUsage: null,
|
|
3459
|
+
model: readString(data.model) || null,
|
|
3460
|
+
isBackground: executionMode === "background",
|
|
3461
|
+
...readString(data.description) ? { prompt: readString(data.description) } : {},
|
|
3462
|
+
status: "running"
|
|
3463
|
+
};
|
|
3464
|
+
targetTurn.subAgentActivity.push(message);
|
|
3465
|
+
targetTurn.contentBlocks.push({
|
|
3466
|
+
kind: message.isBackground ? "background_agent" : "sub_agent",
|
|
3467
|
+
messages: [message],
|
|
3468
|
+
timestamp: message.timestamp
|
|
3469
|
+
});
|
|
3470
|
+
agentMessages.set(agentId, { message, turn: targetTurn });
|
|
3471
|
+
return message;
|
|
3472
|
+
}
|
|
3473
|
+
function applyAgentEvent(event) {
|
|
3474
|
+
if (isRootEvent(event)) return false;
|
|
3475
|
+
const message = ensureAgent(event);
|
|
3476
|
+
if (!message) return true;
|
|
3477
|
+
const { data } = event;
|
|
3478
|
+
const timestamp = eventTimestamp(event);
|
|
3479
|
+
if (event.type === "subagent.started" || event.type === "subagent.configured") {
|
|
3480
|
+
message.parentToolUseId ||= readString(data.toolCallId) || void 0;
|
|
3481
|
+
message.agentName = readString(data.agentDisplayName, data.agentName) || message.agentName;
|
|
3482
|
+
message.subagentType = readString(data.agentType, data.agentName) || message.subagentType;
|
|
3483
|
+
message.model = readString(data.model) || message.model;
|
|
3484
|
+
message.prompt ||= readString(data.description) || void 0;
|
|
3485
|
+
message.isBackground ||= readString(data.executionMode) === "background";
|
|
3486
|
+
message.status = "running";
|
|
3487
|
+
return true;
|
|
3488
|
+
}
|
|
3489
|
+
if (event.type === "subagent.completed" || event.type === "subagent.failed") {
|
|
3490
|
+
message.agentName = readString(data.agentDisplayName, data.agentName) || message.agentName;
|
|
3491
|
+
message.subagentType = readString(data.agentType, data.agentName) || message.subagentType;
|
|
3492
|
+
message.model = readString(data.model) || message.model;
|
|
3493
|
+
message.durationMs = readNumber(data.durationMs) || message.durationMs;
|
|
3494
|
+
message.toolUseCount = readNumber(data.totalToolCalls) || message.toolUseCount;
|
|
3495
|
+
message.status = event.type === "subagent.completed" ? "completed" : "failed";
|
|
3496
|
+
const failure = readString(data.error, data.message);
|
|
3497
|
+
if (failure && !message.text.includes(failure)) message.text.push(failure);
|
|
3498
|
+
return true;
|
|
3499
|
+
}
|
|
3500
|
+
if (event.type === "user.message") {
|
|
3501
|
+
message.prompt ||= readString(data.content) || void 0;
|
|
3502
|
+
return true;
|
|
3503
|
+
}
|
|
3504
|
+
if (event.type === "assistant.reasoning") {
|
|
3505
|
+
const reasoning = readString(data.content);
|
|
3506
|
+
if (reasoning && !message.thinking.includes(reasoning)) message.thinking.push(reasoning);
|
|
3507
|
+
return true;
|
|
3508
|
+
}
|
|
3509
|
+
if (event.type === "assistant.message") {
|
|
3510
|
+
message.model = readString(data.model) || message.model;
|
|
3511
|
+
const reasoning = readString(data.reasoningText);
|
|
3512
|
+
if (reasoning && !message.thinking.includes(reasoning)) message.thinking.push(reasoning);
|
|
3513
|
+
const text = textFromValue(data.content);
|
|
3514
|
+
if (text && !message.text.includes(text)) message.text.push(text);
|
|
3515
|
+
for (const call of toolCallsFromRequests(
|
|
3516
|
+
data.toolRequests,
|
|
3517
|
+
timestamp,
|
|
3518
|
+
(id) => pendingAgentToolCalls.has(id)
|
|
3519
|
+
)) {
|
|
3520
|
+
message.toolCalls.push(call);
|
|
3521
|
+
pendingAgentToolCalls.set(call.id, { call, message });
|
|
3522
|
+
}
|
|
3523
|
+
return true;
|
|
3524
|
+
}
|
|
3525
|
+
if (event.type === "assistant.usage") {
|
|
3526
|
+
message.model = readString(data.model) || message.model;
|
|
3527
|
+
const usage = parseUsage(data);
|
|
3528
|
+
if (usage) message.tokenUsage = mergeTokenUsage(message.tokenUsage, usage);
|
|
3529
|
+
return true;
|
|
3530
|
+
}
|
|
3531
|
+
if (event.type === "tool.execution_start") {
|
|
3532
|
+
const callId = readString(data.toolCallId) || eventId(event, "copilot-agent-tool");
|
|
3533
|
+
const rawName = readString(data.toolName) || "Tool";
|
|
3534
|
+
const name = normalizeToolName(rawName);
|
|
3535
|
+
const existing = pendingAgentToolCalls.get(callId);
|
|
3536
|
+
if (existing) {
|
|
3537
|
+
updateToolCallStart(existing.call, name, data.arguments, timestamp);
|
|
3538
|
+
} else {
|
|
3539
|
+
const call = newToolCall(
|
|
3540
|
+
callId,
|
|
3541
|
+
name,
|
|
3542
|
+
normalizeToolInput(name, data.arguments),
|
|
3543
|
+
timestamp
|
|
3544
|
+
);
|
|
3545
|
+
message.toolCalls.push(call);
|
|
3546
|
+
pendingAgentToolCalls.set(callId, { call, message });
|
|
3547
|
+
}
|
|
3548
|
+
return true;
|
|
3549
|
+
}
|
|
3550
|
+
if (event.type === "tool.execution_complete") {
|
|
3551
|
+
const callId = readString(data.toolCallId) || eventId(event, "copilot-agent-tool");
|
|
3552
|
+
let pending = pendingAgentToolCalls.get(callId);
|
|
3553
|
+
if (!pending) {
|
|
3554
|
+
const rawName = readString(data.toolName) || "Tool";
|
|
3555
|
+
const name = normalizeToolName(rawName);
|
|
3556
|
+
const call = newToolCall(callId, name, {}, timestamp);
|
|
3557
|
+
message.toolCalls.push(call);
|
|
3558
|
+
pending = { call, message };
|
|
3559
|
+
}
|
|
3560
|
+
updateToolCallResult(pending.call, data, binaryAssets);
|
|
3561
|
+
pendingAgentToolCalls.delete(callId);
|
|
3562
|
+
return true;
|
|
3563
|
+
}
|
|
3564
|
+
return true;
|
|
3565
|
+
}
|
|
3566
|
+
for (const event of events) {
|
|
3567
|
+
if (applyAgentEvent(event)) continue;
|
|
3568
|
+
const { data } = event;
|
|
3569
|
+
const timestamp = eventTimestamp(event);
|
|
3570
|
+
if (event.type === "session.start" || event.type === "session.resume") {
|
|
3571
|
+
currentModel = readString(data.selectedModel) || currentModel;
|
|
3572
|
+
currentEffort = readString(data.reasoningEffort) || currentEffort;
|
|
3573
|
+
continue;
|
|
3574
|
+
}
|
|
3575
|
+
if (event.type === "session.auto_mode_resolved") {
|
|
3576
|
+
currentModel = readString(data.chosenModel) || currentModel;
|
|
3577
|
+
if (current) current.model = currentModel;
|
|
3578
|
+
continue;
|
|
3579
|
+
}
|
|
3580
|
+
if (event.type === "session.model_change") {
|
|
3581
|
+
currentModel = readString(data.newModel) || currentModel;
|
|
3582
|
+
const effort = data.reasoningEffort;
|
|
3583
|
+
if (typeof effort === "string") currentEffort = effort;
|
|
3584
|
+
if (current) {
|
|
3585
|
+
current.model = currentModel;
|
|
3586
|
+
if (currentEffort) current.effort = currentEffort;
|
|
3587
|
+
}
|
|
3588
|
+
continue;
|
|
3589
|
+
}
|
|
3590
|
+
if (event.type === "session.shutdown") {
|
|
3591
|
+
currentModel = readString(data.currentModel) || currentModel;
|
|
3592
|
+
if (current && !current.model) current.model = currentModel;
|
|
3593
|
+
shutdownUsage = parseModelMetrics(data) ?? shutdownUsage;
|
|
3594
|
+
const duration = readNumber(data.totalApiDurationMs);
|
|
3595
|
+
if (current && !sawAssistantDuration && duration) apiDurationMs.set(current, duration);
|
|
3596
|
+
continue;
|
|
3597
|
+
}
|
|
3598
|
+
if (event.type === "user.message") {
|
|
3599
|
+
finalizeCurrent();
|
|
3600
|
+
current = createTurn3(
|
|
3601
|
+
turnEventId(event),
|
|
3602
|
+
timestamp,
|
|
3603
|
+
buildUserContent(data, binaryAssets),
|
|
3604
|
+
currentModel,
|
|
3605
|
+
currentEffort,
|
|
3606
|
+
false
|
|
3607
|
+
);
|
|
3608
|
+
noteTimestamp(current, timestamp);
|
|
3609
|
+
continue;
|
|
3610
|
+
}
|
|
3611
|
+
if (event.type === "assistant.turn_start") {
|
|
3612
|
+
currentModel = readString(data.model) || currentModel;
|
|
3613
|
+
const turn = ensureTurn(event);
|
|
3614
|
+
turn.model = currentModel;
|
|
3615
|
+
if (currentEffort) turn.effort = currentEffort;
|
|
3616
|
+
noteTimestamp(turn, timestamp);
|
|
3617
|
+
continue;
|
|
3618
|
+
}
|
|
3619
|
+
if (event.type === "assistant.reasoning") {
|
|
3620
|
+
const turn = ensureTurn(event);
|
|
3621
|
+
appendThinking2(
|
|
3622
|
+
turn,
|
|
3623
|
+
readString(data.content),
|
|
3624
|
+
timestamp,
|
|
3625
|
+
readString(data.reasoningId) || eventId(event, "copilot-thinking")
|
|
3626
|
+
);
|
|
3627
|
+
noteTimestamp(turn, timestamp);
|
|
3628
|
+
continue;
|
|
3629
|
+
}
|
|
3630
|
+
if (event.type === "assistant.message") {
|
|
3631
|
+
currentModel = readString(data.model) || currentModel;
|
|
3632
|
+
const turn = ensureTurn(event);
|
|
3633
|
+
turn.model = currentModel;
|
|
3634
|
+
if (currentEffort) turn.effort = currentEffort;
|
|
3635
|
+
appendThinking2(
|
|
3636
|
+
turn,
|
|
3637
|
+
readString(data.reasoningText),
|
|
3638
|
+
timestamp,
|
|
3639
|
+
eventId(event, "copilot-thinking")
|
|
3640
|
+
);
|
|
3641
|
+
appendAssistantText(turn, textFromValue(data.content), timestamp);
|
|
3642
|
+
for (const call of toolCallsFromRequests(
|
|
3643
|
+
data.toolRequests,
|
|
3644
|
+
timestamp,
|
|
3645
|
+
(id) => pendingToolCalls.has(id)
|
|
3646
|
+
)) {
|
|
3647
|
+
appendToolCall2(turn, call, timestamp);
|
|
3648
|
+
pendingToolCalls.set(call.id, { call, turn });
|
|
3649
|
+
}
|
|
3650
|
+
noteTimestamp(turn, timestamp);
|
|
3651
|
+
continue;
|
|
3652
|
+
}
|
|
3653
|
+
if (event.type === "assistant.usage") {
|
|
3654
|
+
currentModel = readString(data.model) || currentModel;
|
|
3655
|
+
currentEffort = readString(data.reasoningEffort) || currentEffort;
|
|
3656
|
+
const usage = parseUsage(data);
|
|
3657
|
+
const duration = readNumber(data.duration);
|
|
3658
|
+
if (usage) sawAssistantUsage = true;
|
|
3659
|
+
if (duration) sawAssistantDuration = true;
|
|
3660
|
+
const turn = ensureTurn(event);
|
|
3661
|
+
turn.model = currentModel;
|
|
3662
|
+
if (currentEffort) turn.effort = currentEffort;
|
|
3663
|
+
if (usage) turn.tokenUsage = mergeTokenUsage(turn.tokenUsage, usage);
|
|
3664
|
+
if (duration) apiDurationMs.set(turn, (apiDurationMs.get(turn) ?? 0) + duration);
|
|
3665
|
+
noteTimestamp(turn, timestamp);
|
|
3666
|
+
continue;
|
|
3667
|
+
}
|
|
3668
|
+
if (event.type === "tool.execution_start") {
|
|
3669
|
+
currentModel = readString(data.model) || currentModel;
|
|
3670
|
+
const turn = ensureTurn(event);
|
|
3671
|
+
turn.model = currentModel;
|
|
3672
|
+
const callId = readString(data.toolCallId) || eventId(event, "copilot-tool");
|
|
3673
|
+
const rawName = readString(data.toolName) || "Tool";
|
|
3674
|
+
const name = normalizeToolName(rawName);
|
|
3675
|
+
const existing = pendingToolCalls.get(callId);
|
|
3676
|
+
if (existing) {
|
|
3677
|
+
updateToolCallStart(existing.call, name, data.arguments, timestamp);
|
|
3678
|
+
} else {
|
|
3679
|
+
const call = newToolCall(
|
|
3680
|
+
callId,
|
|
3681
|
+
name,
|
|
3682
|
+
normalizeToolInput(name, data.arguments),
|
|
3683
|
+
timestamp
|
|
3684
|
+
);
|
|
3685
|
+
appendToolCall2(turn, call, timestamp);
|
|
3686
|
+
pendingToolCalls.set(callId, { call, turn });
|
|
3687
|
+
}
|
|
3688
|
+
noteTimestamp(turn, timestamp);
|
|
3689
|
+
continue;
|
|
3690
|
+
}
|
|
3691
|
+
if (event.type === "tool.execution_complete") {
|
|
3692
|
+
currentModel = readString(data.model) || currentModel;
|
|
3693
|
+
const callId = readString(data.toolCallId) || eventId(event, "copilot-tool");
|
|
3694
|
+
let pending = pendingToolCalls.get(callId);
|
|
3695
|
+
if (!pending) {
|
|
3696
|
+
const turn = ensureTurn(event);
|
|
3697
|
+
const rawName = readString(data.toolName) || "Tool";
|
|
3698
|
+
const name = normalizeToolName(rawName);
|
|
3699
|
+
const call = newToolCall(callId, name, {}, timestamp);
|
|
3700
|
+
appendToolCall2(turn, call, timestamp);
|
|
3701
|
+
pending = { call, turn };
|
|
3702
|
+
}
|
|
3703
|
+
updateToolCallResult(pending.call, data, binaryAssets);
|
|
3704
|
+
pendingToolCalls.delete(callId);
|
|
3705
|
+
noteTimestamp(pending.turn, timestamp);
|
|
3706
|
+
continue;
|
|
3707
|
+
}
|
|
3708
|
+
if (event.type === "session.error") {
|
|
3709
|
+
const turn = ensureTurn(event);
|
|
3710
|
+
const errorType = readString(data.errorType) || "unknown";
|
|
3711
|
+
const input = { errorType };
|
|
3712
|
+
if (typeof data.statusCode === "number") input.statusCode = data.statusCode;
|
|
3713
|
+
if (typeof data.errorCode === "string") input.errorCode = data.errorCode;
|
|
3714
|
+
appendToolCall2(turn, {
|
|
3715
|
+
id: eventId(event, "copilot-error"),
|
|
3716
|
+
name: "Error",
|
|
3717
|
+
input,
|
|
3718
|
+
result: readString(data.message) || "Copilot session error",
|
|
3719
|
+
isError: true,
|
|
3720
|
+
timestamp
|
|
3721
|
+
}, timestamp);
|
|
3722
|
+
noteTimestamp(turn, timestamp);
|
|
3723
|
+
continue;
|
|
3724
|
+
}
|
|
3725
|
+
if (event.type === "assistant.turn_end" || event.type === "assistant.idle" || event.type === "session.idle") {
|
|
3726
|
+
if (current) noteTimestamp(current, timestamp);
|
|
3727
|
+
}
|
|
3728
|
+
}
|
|
3729
|
+
finalizeCurrent();
|
|
3730
|
+
if (!sawAssistantUsage && shutdownUsage && turns.length === 1) {
|
|
3731
|
+
turns[0].tokenUsage = mergeTokenUsage(turns[0].tokenUsage, shutdownUsage);
|
|
3732
|
+
}
|
|
3733
|
+
if (metadata.model) {
|
|
3734
|
+
for (const turn of turns) turn.model ||= metadata.model;
|
|
3735
|
+
}
|
|
3736
|
+
for (const turn of turns) {
|
|
3737
|
+
const endTimestamp = endTimestamps.get(turn);
|
|
3738
|
+
const start = Date.parse(turn.timestamp);
|
|
3739
|
+
const end = endTimestamp ? Date.parse(endTimestamp) : Number.NaN;
|
|
3740
|
+
if (Number.isFinite(start) && Number.isFinite(end) && end >= start) {
|
|
3741
|
+
turn.durationMs = end - start;
|
|
3742
|
+
} else {
|
|
3743
|
+
const duration = apiDurationMs.get(turn) ?? 0;
|
|
3744
|
+
if (duration) turn.durationMs = duration;
|
|
3745
|
+
}
|
|
3746
|
+
}
|
|
3747
|
+
const stats = options?.skipStats ? createEmptySessionStats(turns.length) : computeStats(turns);
|
|
3748
|
+
if (!options?.skipStats && !sawAssistantUsage && shutdownUsage && turns.length > 1) {
|
|
3749
|
+
stats.totalInputTokens = shutdownUsage.input_tokens;
|
|
3750
|
+
stats.totalOutputTokens = shutdownUsage.output_tokens;
|
|
3751
|
+
stats.totalCacheCreationTokens = shutdownUsage.cache_creation_input_tokens ?? 0;
|
|
3752
|
+
stats.totalCacheReadTokens = shutdownUsage.cache_read_input_tokens ?? 0;
|
|
3753
|
+
}
|
|
3754
|
+
return {
|
|
3755
|
+
sessionId: metadata.sessionId,
|
|
3756
|
+
version: metadata.version,
|
|
3757
|
+
gitBranch: metadata.gitBranch,
|
|
3758
|
+
cwd: metadata.cwd,
|
|
3759
|
+
slug: metadata.slug,
|
|
3760
|
+
name: metadata.name,
|
|
3761
|
+
model: metadata.model,
|
|
3762
|
+
turns,
|
|
2095
3763
|
stats,
|
|
2096
|
-
rawMessages:
|
|
2097
|
-
|
|
2098
|
-
agentKind: existing.agentKind
|
|
3764
|
+
rawMessages: options?.skipStats ? [] : events,
|
|
3765
|
+
agentKind: "copilot"
|
|
2099
3766
|
};
|
|
2100
3767
|
}
|
|
3768
|
+
function appendCopilotSession(existing, newJsonlText) {
|
|
3769
|
+
const prefix = existing.rawMessages.map((record) => JSON.stringify(record)).join("\n");
|
|
3770
|
+
return parseCopilotSession(prefix ? `${prefix}
|
|
3771
|
+
${newJsonlText}` : newJsonlText);
|
|
3772
|
+
}
|
|
3773
|
+
function isCopilotEventType(type) {
|
|
3774
|
+
return typeof type === "string" && (type === "abort" || type === "binary_asset" || type === "user.message" || type.startsWith("assistant.") || type.startsWith("permission.") || type.startsWith("session.") || type.startsWith("subagent.") || type.startsWith("tool.") || type.startsWith("user_input."));
|
|
3775
|
+
}
|
|
3776
|
+
function deriveCopilotSessionStatus(rawMessages) {
|
|
3777
|
+
const pendingAgents = /* @__PURE__ */ new Map();
|
|
3778
|
+
for (const event of rawMessages) {
|
|
3779
|
+
const agentId = typeof event.agentId === "string" ? event.agentId : "";
|
|
3780
|
+
if (!agentId && (event.type === "abort" || event.type === "session.shutdown" || event.type === "session.error")) {
|
|
3781
|
+
pendingAgents.clear();
|
|
3782
|
+
continue;
|
|
3783
|
+
}
|
|
3784
|
+
if (!agentId || !event.type.startsWith("subagent.")) continue;
|
|
3785
|
+
const data = isObject4(event.data) ? event.data : {};
|
|
3786
|
+
if (event.type === "subagent.completed" || event.type === "subagent.failed") {
|
|
3787
|
+
pendingAgents.delete(agentId);
|
|
3788
|
+
continue;
|
|
3789
|
+
}
|
|
3790
|
+
if (event.type === "subagent.started" || event.type === "subagent.configured") {
|
|
3791
|
+
const description = [data.agentDisplayName, data.description, data.agentName].find((value) => typeof value === "string") ?? "";
|
|
3792
|
+
pendingAgents.set(agentId, description || pendingAgents.get(agentId) || "");
|
|
3793
|
+
}
|
|
3794
|
+
}
|
|
3795
|
+
if (pendingAgents.size > 0) {
|
|
3796
|
+
const descriptions = [...pendingAgents.values()];
|
|
3797
|
+
return {
|
|
3798
|
+
status: "awaiting_agents",
|
|
3799
|
+
pendingQueue: 0,
|
|
3800
|
+
pendingAgents: descriptions.length,
|
|
3801
|
+
pendingAgentDescriptions: descriptions.filter((description) => description.length > 0)
|
|
3802
|
+
};
|
|
3803
|
+
}
|
|
3804
|
+
let sawUserActivity = false;
|
|
3805
|
+
const completedPermissionRequests = /* @__PURE__ */ new Set();
|
|
3806
|
+
for (let i = rawMessages.length - 1; i >= 0; i--) {
|
|
3807
|
+
const event = rawMessages[i];
|
|
3808
|
+
if (typeof event.agentId === "string" && event.agentId) continue;
|
|
3809
|
+
const data = isObject4(event.data) ? event.data : {};
|
|
3810
|
+
switch (event.type) {
|
|
3811
|
+
case "abort":
|
|
3812
|
+
case "session.shutdown":
|
|
3813
|
+
return { status: "completed" };
|
|
3814
|
+
case "assistant.turn_end": {
|
|
3815
|
+
for (let j = i - 1; j >= 0; j--) {
|
|
3816
|
+
const previous = rawMessages[j];
|
|
3817
|
+
if (typeof previous.agentId === "string" && previous.agentId) continue;
|
|
3818
|
+
if (previous.type === "user.message") break;
|
|
3819
|
+
if (previous.type === "abort") return { status: "completed" };
|
|
3820
|
+
if (previous.type !== "assistant.message") continue;
|
|
3821
|
+
const previousData = isObject4(previous.data) ? previous.data : {};
|
|
3822
|
+
return Array.isArray(previousData.toolRequests) && previousData.toolRequests.length > 0 ? { status: "processing" } : { status: "completed" };
|
|
3823
|
+
}
|
|
3824
|
+
return { status: "processing" };
|
|
3825
|
+
}
|
|
3826
|
+
case "session.error":
|
|
3827
|
+
return {
|
|
3828
|
+
status: "completed",
|
|
3829
|
+
terminalReason: typeof data.message === "string" ? data.message : "Copilot session error"
|
|
3830
|
+
};
|
|
3831
|
+
case "assistant.idle":
|
|
3832
|
+
case "session.idle":
|
|
3833
|
+
return { status: sawUserActivity ? "completed" : "idle" };
|
|
3834
|
+
case "permission.completed": {
|
|
3835
|
+
const requestId = typeof data.requestId === "string" ? data.requestId : "";
|
|
3836
|
+
if (requestId) completedPermissionRequests.add(requestId);
|
|
3837
|
+
continue;
|
|
3838
|
+
}
|
|
3839
|
+
case "permission.requested": {
|
|
3840
|
+
const requestId = typeof data.requestId === "string" ? data.requestId : "";
|
|
3841
|
+
if (requestId && completedPermissionRequests.has(requestId)) continue;
|
|
3842
|
+
return { status: "deferred" };
|
|
3843
|
+
}
|
|
3844
|
+
case "user_input.requested":
|
|
3845
|
+
return { status: "tool_use", toolName: "AskUserQuestion" };
|
|
3846
|
+
case "tool.execution_start":
|
|
3847
|
+
if (completedPermissionRequests.size > 0) return { status: "thinking" };
|
|
3848
|
+
return {
|
|
3849
|
+
status: "tool_use",
|
|
3850
|
+
toolName: typeof data.toolName === "string" ? normalizeFunctionName(data.toolName) : typeof data.name === "string" ? normalizeFunctionName(data.name) : void 0
|
|
3851
|
+
};
|
|
3852
|
+
case "tool.execution_complete":
|
|
3853
|
+
return { status: "thinking" };
|
|
3854
|
+
case "assistant.turn_start":
|
|
3855
|
+
return { status: "processing" };
|
|
3856
|
+
case "assistant.message":
|
|
3857
|
+
case "assistant.message_delta":
|
|
3858
|
+
case "assistant.reasoning":
|
|
3859
|
+
case "assistant.reasoning_delta":
|
|
3860
|
+
return { status: "thinking" };
|
|
3861
|
+
case "user.message":
|
|
3862
|
+
sawUserActivity = true;
|
|
3863
|
+
return { status: "processing" };
|
|
3864
|
+
case "session.start":
|
|
3865
|
+
case "session.resume":
|
|
3866
|
+
return { status: sawUserActivity ? "processing" : "idle" };
|
|
3867
|
+
}
|
|
3868
|
+
}
|
|
3869
|
+
return { status: "idle" };
|
|
3870
|
+
}
|
|
3871
|
+
function brandCopilotBranch() {
|
|
3872
|
+
throw new Error("Copilot sessions branch through the CLI's fork API, not by copying the transcript");
|
|
3873
|
+
}
|
|
3874
|
+
function copilotTurnBoundaries(records) {
|
|
3875
|
+
const boundaries = [];
|
|
3876
|
+
for (let index = 0; index < records.length; index++) {
|
|
3877
|
+
const record = records[index];
|
|
3878
|
+
if (record.type !== "user.message" || typeof record.agentId === "string") continue;
|
|
3879
|
+
const data = record.data;
|
|
3880
|
+
if (!data || typeof data !== "object" || Array.isArray(data)) continue;
|
|
3881
|
+
if (typeof record.id !== "string" || !record.id) continue;
|
|
3882
|
+
boundaries.push(index);
|
|
3883
|
+
}
|
|
3884
|
+
return boundaries;
|
|
3885
|
+
}
|
|
3886
|
+
|
|
3887
|
+
// src/lib/agents.ts
|
|
3888
|
+
var TERMINAL_AGENT_KIND = AGENT_KINDS[AGENT_KINDS.length - 1];
|
|
3889
|
+
var codexFormat = {
|
|
3890
|
+
kind: "codex",
|
|
3891
|
+
descriptor: descriptorFor("codex"),
|
|
3892
|
+
detectsText: isCodexSessionText,
|
|
3893
|
+
detectsRecords: isCodexRawRecords,
|
|
3894
|
+
parse: parseCodexSession,
|
|
3895
|
+
append: appendCodexSession,
|
|
3896
|
+
metadataFromLines: extractCodexMetadataFromLines,
|
|
3897
|
+
status: deriveCodexSessionStatus,
|
|
3898
|
+
brandBranch: brandCodexBranch,
|
|
3899
|
+
turnBoundaries: codexTurnBoundaries
|
|
3900
|
+
};
|
|
3901
|
+
var copilotFormat = {
|
|
3902
|
+
kind: "copilot",
|
|
3903
|
+
descriptor: descriptorFor("copilot"),
|
|
3904
|
+
detectsText: isCopilotSessionText,
|
|
3905
|
+
detectsRecords: isCopilotRawRecords,
|
|
3906
|
+
parse: parseCopilotSession,
|
|
3907
|
+
append: appendCopilotSession,
|
|
3908
|
+
metadataFromLines: extractCopilotMetadataFromLines,
|
|
3909
|
+
status: deriveCopilotSessionStatus,
|
|
3910
|
+
brandBranch: brandCopilotBranch,
|
|
3911
|
+
turnBoundaries: copilotTurnBoundaries
|
|
3912
|
+
};
|
|
3913
|
+
var claudeFormat = {
|
|
3914
|
+
kind: "claude",
|
|
3915
|
+
descriptor: descriptorFor("claude"),
|
|
3916
|
+
// The terminal arm answers `true` for both, so order alone decides.
|
|
3917
|
+
detectsText: isClaudeSessionText,
|
|
3918
|
+
detectsRecords: isClaudeSessionText,
|
|
3919
|
+
parse: parseClaudeSession,
|
|
3920
|
+
append: appendClaudeSession,
|
|
3921
|
+
metadataFromLines: extractClaudeMetadataFromLines,
|
|
3922
|
+
status: deriveClaudeSessionStatus,
|
|
3923
|
+
brandBranch: brandClaudeBranch,
|
|
3924
|
+
turnBoundaries: claudeTurnBoundaries
|
|
3925
|
+
};
|
|
3926
|
+
var FORMATS = Object.freeze({
|
|
3927
|
+
claude: claudeFormat,
|
|
3928
|
+
codex: codexFormat,
|
|
3929
|
+
copilot: copilotFormat
|
|
3930
|
+
});
|
|
3931
|
+
function firstMatch(matches) {
|
|
3932
|
+
for (const kind of AGENT_KINDS) {
|
|
3933
|
+
if (matches(FORMATS[kind])) return FORMATS[kind];
|
|
3934
|
+
}
|
|
3935
|
+
return FORMATS[TERMINAL_AGENT_KIND];
|
|
3936
|
+
}
|
|
3937
|
+
function formatForText(jsonlText) {
|
|
3938
|
+
return firstMatch((format) => format.detectsText(jsonlText));
|
|
3939
|
+
}
|
|
3940
|
+
function formatForRecords(records) {
|
|
3941
|
+
return firstMatch((format) => format.detectsRecords(records));
|
|
3942
|
+
}
|
|
3943
|
+
|
|
3944
|
+
// src/lib/parser.ts
|
|
3945
|
+
function extractTextFromContent2(content) {
|
|
3946
|
+
if (typeof content === "string") return content;
|
|
3947
|
+
return content.filter((b) => b.type === "text").map((b) => b.text).join("\n");
|
|
3948
|
+
}
|
|
3949
|
+
function parseSession(jsonlText, options) {
|
|
3950
|
+
return formatForText(jsonlText).parse(jsonlText, options);
|
|
3951
|
+
}
|
|
3952
|
+
function parseSessionAppend(existing, newJsonlText) {
|
|
3953
|
+
const byRecords = formatForRecords(existing.rawMessages);
|
|
3954
|
+
const format = byRecords.kind === TERMINAL_AGENT_KIND ? formatForText(newJsonlText) : byRecords;
|
|
3955
|
+
return format.append(existing, newJsonlText);
|
|
3956
|
+
}
|
|
2101
3957
|
function getUserMessageText(content) {
|
|
2102
3958
|
if (content === null) return "";
|
|
2103
3959
|
if (typeof content === "string") return content;
|
|
@@ -2108,20 +3964,272 @@ function getUserMessageImages(content) {
|
|
|
2108
3964
|
return content.filter((b) => b.type === "image");
|
|
2109
3965
|
}
|
|
2110
3966
|
|
|
3967
|
+
// src/lib/stores.ts
|
|
3968
|
+
var import_node_fs = require("node:fs");
|
|
3969
|
+
var import_node_path2 = require("node:path");
|
|
3970
|
+
|
|
3971
|
+
// src/lib/dirs.ts
|
|
3972
|
+
var import_node_path = require("node:path");
|
|
3973
|
+
var import_node_os = require("node:os");
|
|
3974
|
+
function agentHomeDir(kind, env = process.env, homeDirectory = (0, import_node_os.homedir)()) {
|
|
3975
|
+
const { homeEnvVar, homeDirName } = descriptorFor(kind).cli;
|
|
3976
|
+
const override = homeEnvVar ? env[homeEnvVar] : void 0;
|
|
3977
|
+
return override || (0, import_node_path.join)(homeDirectory, homeDirName);
|
|
3978
|
+
}
|
|
3979
|
+
var dirs = {
|
|
3980
|
+
PROJECTS_DIR: (0, import_node_path.join)(agentHomeDir("claude"), "projects"),
|
|
3981
|
+
TEAMS_DIR: (0, import_node_path.join)(agentHomeDir("claude"), "teams"),
|
|
3982
|
+
TASKS_DIR: (0, import_node_path.join)(agentHomeDir("claude"), "tasks"),
|
|
3983
|
+
CODEX_SESSIONS_DIR: (0, import_node_path.join)(agentHomeDir("codex"), "sessions"),
|
|
3984
|
+
COPILOT_SESSIONS_DIR: (0, import_node_path.join)(agentHomeDir("copilot"), "session-state")
|
|
3985
|
+
};
|
|
3986
|
+
var DEFAULT_DB_PATH = (0, import_node_path.join)(agentHomeDir("claude"), "cogpit-memory", "search-index.db");
|
|
3987
|
+
|
|
3988
|
+
// src/lib/stores.ts
|
|
3989
|
+
var NON_PROJECT_DIRS = /* @__PURE__ */ new Set(["memory"]);
|
|
3990
|
+
var MAX_WALK_DEPTH = 4;
|
|
3991
|
+
var SUBAGENTS_DIR = "subagents";
|
|
3992
|
+
function mtimeOf(filePath) {
|
|
3993
|
+
try {
|
|
3994
|
+
return (0, import_node_fs.statSync)(filePath).mtimeMs;
|
|
3995
|
+
} catch {
|
|
3996
|
+
return null;
|
|
3997
|
+
}
|
|
3998
|
+
}
|
|
3999
|
+
function readDirSafe(dir) {
|
|
4000
|
+
try {
|
|
4001
|
+
return (0, import_node_fs.readdirSync)(dir, { withFileTypes: true });
|
|
4002
|
+
} catch {
|
|
4003
|
+
return [];
|
|
4004
|
+
}
|
|
4005
|
+
}
|
|
4006
|
+
function isWithin(root, target) {
|
|
4007
|
+
const rel = (0, import_node_path2.relative)(root, target);
|
|
4008
|
+
return rel !== "" && !rel.startsWith("..") && !(0, import_node_path2.isAbsolute)(rel);
|
|
4009
|
+
}
|
|
4010
|
+
function canonicalWithinRoot(root, filePath) {
|
|
4011
|
+
try {
|
|
4012
|
+
const realRoot = (0, import_node_fs.realpathSync)(root);
|
|
4013
|
+
const real = (0, import_node_fs.realpathSync)(filePath);
|
|
4014
|
+
return isWithin(realRoot, real) ? real : null;
|
|
4015
|
+
} catch {
|
|
4016
|
+
return null;
|
|
4017
|
+
}
|
|
4018
|
+
}
|
|
4019
|
+
function segmentsUnder(root, filePath) {
|
|
4020
|
+
return (0, import_node_path2.relative)(root, filePath).split(import_node_path2.sep).filter(Boolean);
|
|
4021
|
+
}
|
|
4022
|
+
function claudeIdentify(filePath, root) {
|
|
4023
|
+
const segments = segmentsUnder(root, filePath);
|
|
4024
|
+
const subagentAt = segments.lastIndexOf(SUBAGENTS_DIR);
|
|
4025
|
+
if (subagentAt > 0) {
|
|
4026
|
+
const parentSessionId = segments[subagentAt - 1];
|
|
4027
|
+
return { sessionId: parentSessionId, isSubagent: true, parentSessionId };
|
|
4028
|
+
}
|
|
4029
|
+
const fileName = segments[segments.length - 1] ?? "";
|
|
4030
|
+
return {
|
|
4031
|
+
sessionId: descriptorFor("claude").sessionFile.sessionId(fileName) ?? fileName.replace(/\.jsonl$/, ""),
|
|
4032
|
+
isSubagent: false,
|
|
4033
|
+
parentSessionId: null
|
|
4034
|
+
};
|
|
4035
|
+
}
|
|
4036
|
+
function subagentDirOf(parentPath) {
|
|
4037
|
+
return (0, import_node_path2.join)(parentPath.replace(/\.jsonl$/, ""), SUBAGENTS_DIR);
|
|
4038
|
+
}
|
|
4039
|
+
function walkClaudeSubagents(parentPath, parentSessionId, cutoffMs, depth, out) {
|
|
4040
|
+
if (depth >= MAX_WALK_DEPTH) return;
|
|
4041
|
+
const dir = subagentDirOf(parentPath);
|
|
4042
|
+
for (const entry of readDirSafe(dir)) {
|
|
4043
|
+
if (!entry.name.startsWith("agent-") || !entry.name.endsWith(".jsonl")) continue;
|
|
4044
|
+
const filePath = (0, import_node_path2.join)(dir, entry.name);
|
|
4045
|
+
const mtimeMs = mtimeOf(filePath);
|
|
4046
|
+
if (mtimeMs !== null && mtimeMs >= cutoffMs) {
|
|
4047
|
+
out.push({
|
|
4048
|
+
path: filePath,
|
|
4049
|
+
mtimeMs,
|
|
4050
|
+
sessionId: parentSessionId,
|
|
4051
|
+
isSubagent: true,
|
|
4052
|
+
parentSessionId
|
|
4053
|
+
});
|
|
4054
|
+
}
|
|
4055
|
+
walkClaudeSubagents(filePath, parentSessionId, cutoffMs, depth + 1, out);
|
|
4056
|
+
}
|
|
4057
|
+
}
|
|
4058
|
+
function walkClaude(root, cutoffMs) {
|
|
4059
|
+
const found = [];
|
|
4060
|
+
for (const project of readDirSafe(root)) {
|
|
4061
|
+
if (!project.isDirectory() || NON_PROJECT_DIRS.has(project.name)) continue;
|
|
4062
|
+
const projectDir = (0, import_node_path2.join)(root, project.name);
|
|
4063
|
+
for (const entry of readDirSafe(projectDir)) {
|
|
4064
|
+
if (!entry.isFile() || !entry.name.endsWith(".jsonl")) continue;
|
|
4065
|
+
const filePath = (0, import_node_path2.join)(projectDir, entry.name);
|
|
4066
|
+
const mtimeMs = mtimeOf(filePath);
|
|
4067
|
+
const { sessionId } = claudeIdentify(filePath, root);
|
|
4068
|
+
if (mtimeMs !== null && mtimeMs >= cutoffMs) {
|
|
4069
|
+
found.push({ path: filePath, mtimeMs, sessionId, isSubagent: false, parentSessionId: null });
|
|
4070
|
+
}
|
|
4071
|
+
walkClaudeSubagents(filePath, sessionId, cutoffMs, 0, found);
|
|
4072
|
+
}
|
|
4073
|
+
}
|
|
4074
|
+
return found;
|
|
4075
|
+
}
|
|
4076
|
+
function walkCodex(root, cutoffMs, dir = root, depth = 0) {
|
|
4077
|
+
if (depth > MAX_WALK_DEPTH) return [];
|
|
4078
|
+
const found = [];
|
|
4079
|
+
for (const entry of readDirSafe(dir)) {
|
|
4080
|
+
const filePath = (0, import_node_path2.join)(dir, entry.name);
|
|
4081
|
+
if (entry.isDirectory()) {
|
|
4082
|
+
found.push(...walkCodex(root, cutoffMs, filePath, depth + 1));
|
|
4083
|
+
continue;
|
|
4084
|
+
}
|
|
4085
|
+
if (!entry.name.endsWith(".jsonl")) continue;
|
|
4086
|
+
const mtimeMs = mtimeOf(filePath);
|
|
4087
|
+
if (mtimeMs === null || mtimeMs < cutoffMs) continue;
|
|
4088
|
+
found.push({
|
|
4089
|
+
path: filePath,
|
|
4090
|
+
mtimeMs,
|
|
4091
|
+
sessionId: descriptorFor("codex").sessionFile.sessionId(entry.name) ?? "",
|
|
4092
|
+
isSubagent: false,
|
|
4093
|
+
parentSessionId: null
|
|
4094
|
+
});
|
|
4095
|
+
}
|
|
4096
|
+
return found;
|
|
4097
|
+
}
|
|
4098
|
+
function copilotTranscript(root, sessionId) {
|
|
4099
|
+
return (0, import_node_path2.join)(root, descriptorFor("copilot").sessionFile.name(sessionId));
|
|
4100
|
+
}
|
|
4101
|
+
function walkCopilot(root, cutoffMs) {
|
|
4102
|
+
const found = [];
|
|
4103
|
+
for (const entry of readDirSafe(root)) {
|
|
4104
|
+
if (!entry.isDirectory()) continue;
|
|
4105
|
+
const filePath = copilotTranscript(root, entry.name);
|
|
4106
|
+
const mtimeMs = mtimeOf(filePath);
|
|
4107
|
+
if (mtimeMs === null || mtimeMs < cutoffMs) continue;
|
|
4108
|
+
found.push({
|
|
4109
|
+
path: filePath,
|
|
4110
|
+
mtimeMs,
|
|
4111
|
+
sessionId: entry.name,
|
|
4112
|
+
isSubagent: false,
|
|
4113
|
+
parentSessionId: null
|
|
4114
|
+
});
|
|
4115
|
+
}
|
|
4116
|
+
return found;
|
|
4117
|
+
}
|
|
4118
|
+
var WALKERS = {
|
|
4119
|
+
claude: {
|
|
4120
|
+
root: () => dirs.PROJECTS_DIR,
|
|
4121
|
+
list: walkClaude,
|
|
4122
|
+
identify: claudeIdentify,
|
|
4123
|
+
find(root, sessionId) {
|
|
4124
|
+
const fileName = descriptorFor("claude").sessionFile.name(sessionId);
|
|
4125
|
+
for (const project of readDirSafe(root)) {
|
|
4126
|
+
if (!project.isDirectory() || NON_PROJECT_DIRS.has(project.name)) continue;
|
|
4127
|
+
const candidate = (0, import_node_path2.join)(root, project.name, fileName);
|
|
4128
|
+
if (mtimeOf(candidate) !== null) return candidate;
|
|
4129
|
+
}
|
|
4130
|
+
return null;
|
|
4131
|
+
}
|
|
4132
|
+
},
|
|
4133
|
+
codex: {
|
|
4134
|
+
root: () => dirs.CODEX_SESSIONS_DIR,
|
|
4135
|
+
list: (root, cutoffMs) => walkCodex(root, cutoffMs),
|
|
4136
|
+
identify: (filePath) => ({
|
|
4137
|
+
sessionId: descriptorFor("codex").sessionFile.sessionId(filePath) ?? "",
|
|
4138
|
+
isSubagent: false,
|
|
4139
|
+
parentSessionId: null
|
|
4140
|
+
}),
|
|
4141
|
+
// A rollout's date nesting cannot be rebuilt from the id, so the tree is
|
|
4142
|
+
// walked; every rollout carries the id in its file name.
|
|
4143
|
+
find(root, sessionId) {
|
|
4144
|
+
const walk = (dir, depth) => {
|
|
4145
|
+
if (depth > MAX_WALK_DEPTH) return null;
|
|
4146
|
+
for (const entry of readDirSafe(dir)) {
|
|
4147
|
+
const filePath = (0, import_node_path2.join)(dir, entry.name);
|
|
4148
|
+
if (entry.isDirectory()) {
|
|
4149
|
+
const match = walk(filePath, depth + 1);
|
|
4150
|
+
if (match) return match;
|
|
4151
|
+
continue;
|
|
4152
|
+
}
|
|
4153
|
+
if (entry.name.endsWith(`${sessionId}.jsonl`)) return filePath;
|
|
4154
|
+
}
|
|
4155
|
+
return null;
|
|
4156
|
+
};
|
|
4157
|
+
return walk(root, 0);
|
|
4158
|
+
}
|
|
4159
|
+
},
|
|
4160
|
+
copilot: {
|
|
4161
|
+
root: () => dirs.COPILOT_SESSIONS_DIR,
|
|
4162
|
+
list: walkCopilot,
|
|
4163
|
+
identify: (filePath, root) => ({
|
|
4164
|
+
sessionId: segmentsUnder(root, filePath)[0] ?? "",
|
|
4165
|
+
isSubagent: false,
|
|
4166
|
+
parentSessionId: null
|
|
4167
|
+
}),
|
|
4168
|
+
find(root, sessionId) {
|
|
4169
|
+
const candidate = copilotTranscript(root, sessionId);
|
|
4170
|
+
return mtimeOf(candidate) === null ? null : candidate;
|
|
4171
|
+
}
|
|
4172
|
+
}
|
|
4173
|
+
};
|
|
4174
|
+
function makeStore(kind) {
|
|
4175
|
+
const walker = WALKERS[kind];
|
|
4176
|
+
return {
|
|
4177
|
+
kind,
|
|
4178
|
+
descriptor: descriptorFor(kind),
|
|
4179
|
+
root: walker.root,
|
|
4180
|
+
ownsPath: (filePath) => isWithin((0, import_node_path2.resolve)(walker.root()), (0, import_node_path2.resolve)(filePath)),
|
|
4181
|
+
identify: (filePath, root = walker.root()) => walker.identify(filePath, root),
|
|
4182
|
+
list: (cutoffMs, root = walker.root()) => walker.list(root, cutoffMs),
|
|
4183
|
+
find: (sessionId) => walker.find(walker.root(), sessionId)
|
|
4184
|
+
};
|
|
4185
|
+
}
|
|
4186
|
+
var STORES = {
|
|
4187
|
+
claude: makeStore("claude"),
|
|
4188
|
+
codex: makeStore("codex"),
|
|
4189
|
+
copilot: makeStore("copilot")
|
|
4190
|
+
};
|
|
4191
|
+
function storeFor(kind) {
|
|
4192
|
+
return STORES[kind];
|
|
4193
|
+
}
|
|
4194
|
+
function defaultStore() {
|
|
4195
|
+
return storeFor(descriptorForDirName(null).kind);
|
|
4196
|
+
}
|
|
4197
|
+
function allStores() {
|
|
4198
|
+
return AGENT_KINDS.map((kind) => STORES[kind]);
|
|
4199
|
+
}
|
|
4200
|
+
function storeForPath(filePath) {
|
|
4201
|
+
return allStores().find((store) => store.ownsPath(filePath)) ?? null;
|
|
4202
|
+
}
|
|
4203
|
+
function listAllSessionFiles(cutoffMs) {
|
|
4204
|
+
return allStores().flatMap((store) => store.list(cutoffMs)).sort((a, b) => b.mtimeMs - a.mtimeMs);
|
|
4205
|
+
}
|
|
4206
|
+
function isSinglePathSegment(value) {
|
|
4207
|
+
return value.length > 0 && !value.includes("/") && !value.includes("\\") && !value.includes("\0") && value !== "." && value !== "..";
|
|
4208
|
+
}
|
|
4209
|
+
async function findSessionFile(sessionId) {
|
|
4210
|
+
if (!isSinglePathSegment(sessionId)) return null;
|
|
4211
|
+
for (const store of allStores()) {
|
|
4212
|
+
const filePath = store.find(sessionId);
|
|
4213
|
+
const canonical = filePath && canonicalWithinRoot(store.root(), filePath);
|
|
4214
|
+
if (canonical) return canonical;
|
|
4215
|
+
}
|
|
4216
|
+
return null;
|
|
4217
|
+
}
|
|
4218
|
+
|
|
2111
4219
|
// src/lib/search-index.ts
|
|
2112
4220
|
var MAX_CONTENT_LEN = 4096;
|
|
2113
4221
|
function truncContent(text) {
|
|
2114
4222
|
return text.length > MAX_CONTENT_LEN ? text.slice(0, MAX_CONTENT_LEN) : text;
|
|
2115
4223
|
}
|
|
4224
|
+
function identifyTranscript(filePath) {
|
|
4225
|
+
const store = storeForPath(filePath) ?? defaultStore();
|
|
4226
|
+
return store.identify(filePath, store.root());
|
|
4227
|
+
}
|
|
2116
4228
|
var SearchIndex = class {
|
|
2117
4229
|
db;
|
|
2118
4230
|
dbPath;
|
|
2119
|
-
projectsDir = null;
|
|
2120
|
-
_watcherRunning = false;
|
|
2121
4231
|
_lastFullBuild = null;
|
|
2122
4232
|
_lastUpdate = null;
|
|
2123
|
-
watcher = null;
|
|
2124
|
-
debounceTimers = /* @__PURE__ */ new Map();
|
|
2125
4233
|
constructor(dbPath) {
|
|
2126
4234
|
this.dbPath = dbPath;
|
|
2127
4235
|
this.db = new Database(dbPath);
|
|
@@ -2157,7 +4265,7 @@ var SearchIndex = class {
|
|
|
2157
4265
|
const { count: totalRows } = this.db.prepare("SELECT COUNT(*) as count FROM search_content").get();
|
|
2158
4266
|
let dbSizeBytes = 0;
|
|
2159
4267
|
try {
|
|
2160
|
-
dbSizeBytes = (0,
|
|
4268
|
+
dbSizeBytes = (0, import_node_fs2.statSync)(this.dbPath).size;
|
|
2161
4269
|
} catch {
|
|
2162
4270
|
}
|
|
2163
4271
|
return {
|
|
@@ -2168,7 +4276,6 @@ var SearchIndex = class {
|
|
|
2168
4276
|
indexedSessions,
|
|
2169
4277
|
indexedSubagents,
|
|
2170
4278
|
totalRows,
|
|
2171
|
-
watcherRunning: this._watcherRunning,
|
|
2172
4279
|
lastFullBuild: this._lastFullBuild,
|
|
2173
4280
|
lastUpdate: this._lastUpdate
|
|
2174
4281
|
};
|
|
@@ -2229,13 +4336,11 @@ var SearchIndex = class {
|
|
|
2229
4336
|
* All inserts run in a single transaction for performance.
|
|
2230
4337
|
*/
|
|
2231
4338
|
indexFile(filePath, sessionId, mtimeMs, opts) {
|
|
2232
|
-
|
|
2233
|
-
sessionId = (0, import_node_path.basename)(filePath, ".jsonl");
|
|
2234
|
-
}
|
|
4339
|
+
const resolvedSessionId = sessionId ?? identifyTranscript(filePath).sessionId;
|
|
2235
4340
|
if (mtimeMs == null) {
|
|
2236
|
-
mtimeMs = (0,
|
|
4341
|
+
mtimeMs = (0, import_node_fs2.statSync)(filePath).mtimeMs;
|
|
2237
4342
|
}
|
|
2238
|
-
const content = (0,
|
|
4343
|
+
const content = (0, import_node_fs2.readFileSync)(filePath, "utf-8");
|
|
2239
4344
|
const session = parseSession(content, { skipStats: true });
|
|
2240
4345
|
const isSubagent = opts?.isSubagent ? 1 : 0;
|
|
2241
4346
|
const parentSessionId = opts?.parentSessionId ?? null;
|
|
@@ -2254,12 +4359,34 @@ var SearchIndex = class {
|
|
|
2254
4359
|
const txn = this.db.transaction(() => {
|
|
2255
4360
|
deleteContent.run(filePath);
|
|
2256
4361
|
deleteFile.run(filePath);
|
|
2257
|
-
this.insertSessionContent(insert,
|
|
2258
|
-
insertFile.run(filePath, mtimeMs,
|
|
4362
|
+
this.insertSessionContent(insert, resolvedSessionId, filePath, session);
|
|
4363
|
+
insertFile.run(filePath, mtimeMs, resolvedSessionId, isSubagent, parentSessionId);
|
|
2259
4364
|
});
|
|
2260
4365
|
txn();
|
|
2261
4366
|
this._lastUpdate = (/* @__PURE__ */ new Date()).toISOString();
|
|
2262
4367
|
}
|
|
4368
|
+
pruneMissingFiles() {
|
|
4369
|
+
const files = this.db.prepare("SELECT file_path FROM indexed_files").all();
|
|
4370
|
+
const missing = files.filter(({ file_path }) => {
|
|
4371
|
+
try {
|
|
4372
|
+
(0, import_node_fs2.statSync)(file_path);
|
|
4373
|
+
return false;
|
|
4374
|
+
} catch (error) {
|
|
4375
|
+
const code = error.code;
|
|
4376
|
+
return code === "ENOENT" || code === "ENOTDIR";
|
|
4377
|
+
}
|
|
4378
|
+
});
|
|
4379
|
+
if (missing.length === 0) return 0;
|
|
4380
|
+
this.db.transaction(() => {
|
|
4381
|
+
this.db.exec("CREATE TEMP TABLE missing_transcripts (file_path TEXT PRIMARY KEY)");
|
|
4382
|
+
const insert = this.db.prepare("INSERT INTO missing_transcripts VALUES (?)");
|
|
4383
|
+
for (const { file_path } of missing) insert.run(file_path);
|
|
4384
|
+
this.db.exec("DELETE FROM search_content WHERE source_file IN (SELECT file_path FROM missing_transcripts)");
|
|
4385
|
+
this.db.exec("DELETE FROM indexed_files WHERE file_path IN (SELECT file_path FROM missing_transcripts)");
|
|
4386
|
+
this.db.exec("DROP TABLE missing_transcripts");
|
|
4387
|
+
})();
|
|
4388
|
+
return missing.length;
|
|
4389
|
+
}
|
|
2263
4390
|
/**
|
|
2264
4391
|
* Query the FTS5 index and return structured search results.
|
|
2265
4392
|
*
|
|
@@ -2272,6 +4399,7 @@ var SearchIndex = class {
|
|
|
2272
4399
|
* - `limit` defaults to 200 and is clamped to a max of 200.
|
|
2273
4400
|
*/
|
|
2274
4401
|
search(query, opts) {
|
|
4402
|
+
this.pruneMissingFiles();
|
|
2275
4403
|
const limit = Math.min(Math.max(1, opts?.limit ?? 200), 200);
|
|
2276
4404
|
const sessionId = opts?.sessionId;
|
|
2277
4405
|
const maxAgeMs = opts?.maxAgeMs;
|
|
@@ -2294,6 +4422,10 @@ var SearchIndex = class {
|
|
|
2294
4422
|
conditions.push("sc.session_id = ?");
|
|
2295
4423
|
params.push(sessionId);
|
|
2296
4424
|
}
|
|
4425
|
+
if (opts?.excludeSessionId) {
|
|
4426
|
+
conditions.push("sc.session_id != ?");
|
|
4427
|
+
params.push(opts.excludeSessionId);
|
|
4428
|
+
}
|
|
2297
4429
|
sql += " WHERE " + conditions.join(" AND ");
|
|
2298
4430
|
sql += " ORDER BY sc.rowid DESC";
|
|
2299
4431
|
sql += " LIMIT ?";
|
|
@@ -2335,27 +4467,26 @@ var SearchIndex = class {
|
|
|
2335
4467
|
conditions.push("sc.session_id = ?");
|
|
2336
4468
|
params.push(opts.sessionId);
|
|
2337
4469
|
}
|
|
4470
|
+
if (opts?.excludeSessionId) {
|
|
4471
|
+
conditions.push("sc.session_id != ?");
|
|
4472
|
+
params.push(opts.excludeSessionId);
|
|
4473
|
+
}
|
|
2338
4474
|
sql += " WHERE " + conditions.join(" AND ");
|
|
2339
4475
|
const row = this.db.prepare(sql).get(...params);
|
|
2340
4476
|
return { totalHits: row.total, sessionsSearched: row.sessions };
|
|
2341
4477
|
}
|
|
2342
4478
|
/**
|
|
2343
|
-
* Clear all indexed data and re-index every
|
|
2344
|
-
* Structure: projectsDir/{projectName}/{sessionId}.jsonl
|
|
2345
|
-
* Subagents: projectsDir/{projectName}/{sessionId}/subagents/agent-{id}.jsonl
|
|
4479
|
+
* Clear all indexed data and re-index every transcript, from every agent.
|
|
2346
4480
|
*
|
|
2347
4481
|
* Optimized: discovers all files first, then processes them in a single
|
|
2348
4482
|
* SQLite transaction with pre-prepared statements. This avoids the overhead
|
|
2349
4483
|
* of 3000+ individual transactions (each forcing a disk sync).
|
|
2350
|
-
*
|
|
2351
|
-
* Stores `projectsDir` as a class field so `rebuild()` can reuse it.
|
|
2352
4484
|
*/
|
|
2353
|
-
buildFull(
|
|
2354
|
-
this.projectsDir = projectsDir;
|
|
4485
|
+
buildFull() {
|
|
2355
4486
|
this.db.close();
|
|
2356
4487
|
for (const suffix of ["", "-wal", "-shm"]) {
|
|
2357
4488
|
try {
|
|
2358
|
-
(0,
|
|
4489
|
+
(0, import_node_fs2.unlinkSync)(this.dbPath + suffix);
|
|
2359
4490
|
} catch {
|
|
2360
4491
|
}
|
|
2361
4492
|
}
|
|
@@ -2367,8 +4498,8 @@ var SearchIndex = class {
|
|
|
2367
4498
|
this.db.exec("PRAGMA mmap_size = 268435456");
|
|
2368
4499
|
this.initSchema();
|
|
2369
4500
|
const files = [];
|
|
2370
|
-
this.discoverFiles(
|
|
2371
|
-
files.push(
|
|
4501
|
+
this.discoverFiles((file) => {
|
|
4502
|
+
files.push(file);
|
|
2372
4503
|
});
|
|
2373
4504
|
const insert = this.db.prepare(
|
|
2374
4505
|
"INSERT INTO search_content (session_id, source_file, location, content) VALUES (?, ?, ?, ?)"
|
|
@@ -2379,7 +4510,7 @@ var SearchIndex = class {
|
|
|
2379
4510
|
const txn = this.db.transaction(() => {
|
|
2380
4511
|
for (const file of files) {
|
|
2381
4512
|
try {
|
|
2382
|
-
const content = (0,
|
|
4513
|
+
const content = (0, import_node_fs2.readFileSync)(file.path, "utf-8");
|
|
2383
4514
|
const session = parseSession(content, { skipStats: true });
|
|
2384
4515
|
this.insertSessionContent(insert, file.sessionId, file.path, session);
|
|
2385
4516
|
insertFile.run(file.path, file.mtimeMs, file.sessionId, file.isSubagent ? 1 : 0, file.parentSessionId);
|
|
@@ -2392,39 +4523,6 @@ var SearchIndex = class {
|
|
|
2392
4523
|
this._lastFullBuild = (/* @__PURE__ */ new Date()).toISOString();
|
|
2393
4524
|
this._lastUpdate = (/* @__PURE__ */ new Date()).toISOString();
|
|
2394
4525
|
}
|
|
2395
|
-
/**
|
|
2396
|
-
* Incrementally re-index only files whose mtime has changed since last index.
|
|
2397
|
-
* New files (not in indexed_files) are always indexed.
|
|
2398
|
-
*
|
|
2399
|
-
* WARNING: This walks ALL files under projectsDir and stats each one.
|
|
2400
|
-
* On large session stores (3000+ files) this can take minutes.
|
|
2401
|
-
* Prefer `updateRecent()` for CLI search paths.
|
|
2402
|
-
*/
|
|
2403
|
-
updateStale(projectsDir) {
|
|
2404
|
-
this.projectsDir = projectsDir;
|
|
2405
|
-
const getIndexed = this.db.prepare(
|
|
2406
|
-
"SELECT mtime_ms FROM indexed_files WHERE file_path = ?"
|
|
2407
|
-
);
|
|
2408
|
-
const filesToIndex = [];
|
|
2409
|
-
this.discoverFiles(projectsDir, (filePath, sessionId, mtimeMs, isSubagent, parentSessionId) => {
|
|
2410
|
-
const existing = getIndexed.get(filePath);
|
|
2411
|
-
if (!existing || existing.mtime_ms < mtimeMs) {
|
|
2412
|
-
filesToIndex.push({ path: filePath, sessionId, mtimeMs, isSubagent, parentSessionId });
|
|
2413
|
-
}
|
|
2414
|
-
});
|
|
2415
|
-
for (const file of filesToIndex) {
|
|
2416
|
-
try {
|
|
2417
|
-
this.indexFile(file.path, file.sessionId, file.mtimeMs, {
|
|
2418
|
-
isSubagent: file.isSubagent,
|
|
2419
|
-
parentSessionId: file.parentSessionId
|
|
2420
|
-
});
|
|
2421
|
-
} catch {
|
|
2422
|
-
}
|
|
2423
|
-
}
|
|
2424
|
-
if (filesToIndex.length > 0) {
|
|
2425
|
-
this._lastUpdate = (/* @__PURE__ */ new Date()).toISOString();
|
|
2426
|
-
}
|
|
2427
|
-
}
|
|
2428
4526
|
/**
|
|
2429
4527
|
* Lightweight incremental update for CLI search paths.
|
|
2430
4528
|
*
|
|
@@ -2433,8 +4531,7 @@ var SearchIndex = class {
|
|
|
2433
4531
|
* Caps re-indexing to `maxFiles` to prevent blocking on large backlogs
|
|
2434
4532
|
* (run `index rebuild` for a full catch-up).
|
|
2435
4533
|
*/
|
|
2436
|
-
updateRecent(
|
|
2437
|
-
this.projectsDir = projectsDir;
|
|
4534
|
+
updateRecent(maxFiles = 50) {
|
|
2438
4535
|
const row = this.db.prepare(
|
|
2439
4536
|
"SELECT MAX(mtime_ms) as max_mtime FROM indexed_files"
|
|
2440
4537
|
).get();
|
|
@@ -2443,12 +4540,12 @@ var SearchIndex = class {
|
|
|
2443
4540
|
"SELECT mtime_ms FROM indexed_files WHERE file_path = ?"
|
|
2444
4541
|
);
|
|
2445
4542
|
const filesToIndex = [];
|
|
2446
|
-
this.discoverFiles(
|
|
2447
|
-
if (mtimeMs <= highWater) {
|
|
2448
|
-
const existing = getIndexed.get(
|
|
2449
|
-
if (existing && existing.mtime_ms >= mtimeMs) return;
|
|
4543
|
+
this.discoverFiles((file) => {
|
|
4544
|
+
if (file.mtimeMs <= highWater) {
|
|
4545
|
+
const existing = getIndexed.get(file.path);
|
|
4546
|
+
if (existing && existing.mtime_ms >= file.mtimeMs) return;
|
|
2450
4547
|
}
|
|
2451
|
-
filesToIndex.push(
|
|
4548
|
+
filesToIndex.push(file);
|
|
2452
4549
|
});
|
|
2453
4550
|
filesToIndex.sort((a, b) => b.mtimeMs - a.mtimeMs);
|
|
2454
4551
|
const batch = filesToIndex.slice(0, maxFiles);
|
|
@@ -2465,179 +4562,19 @@ var SearchIndex = class {
|
|
|
2465
4562
|
this._lastUpdate = (/* @__PURE__ */ new Date()).toISOString();
|
|
2466
4563
|
}
|
|
2467
4564
|
}
|
|
2468
|
-
/**
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
*/
|
|
2472
|
-
rebuild() {
|
|
2473
|
-
if (!this.projectsDir) return;
|
|
2474
|
-
this.buildFull(this.projectsDir);
|
|
2475
|
-
}
|
|
2476
|
-
/**
|
|
2477
|
-
* Walk the projects directory tree and invoke `callback` for every JSONL file.
|
|
2478
|
-
*
|
|
2479
|
-
* Directory structure:
|
|
2480
|
-
* projectsDir/
|
|
2481
|
-
* {projectName}/
|
|
2482
|
-
* {sessionId}.jsonl <- session file
|
|
2483
|
-
* {sessionId}/subagents/
|
|
2484
|
-
* agent-{agentId}.jsonl <- subagent file (recursive)
|
|
2485
|
-
*
|
|
2486
|
-
* Skips the "memory" directory.
|
|
2487
|
-
*/
|
|
2488
|
-
discoverFiles(projectsDir, callback) {
|
|
2489
|
-
let entries;
|
|
2490
|
-
try {
|
|
2491
|
-
entries = (0, import_node_fs.readdirSync)(projectsDir, { withFileTypes: true });
|
|
2492
|
-
} catch {
|
|
2493
|
-
return;
|
|
2494
|
-
}
|
|
2495
|
-
for (const entry of entries) {
|
|
2496
|
-
if (entry.name === "memory" || !entry.isDirectory()) continue;
|
|
2497
|
-
const projectDir = (0, import_node_path.join)(projectsDir, entry.name);
|
|
2498
|
-
let files;
|
|
2499
|
-
try {
|
|
2500
|
-
files = (0, import_node_fs.readdirSync)(projectDir, { withFileTypes: true });
|
|
2501
|
-
} catch {
|
|
2502
|
-
continue;
|
|
2503
|
-
}
|
|
2504
|
-
for (const file of files) {
|
|
2505
|
-
if (!file.name.endsWith(".jsonl") || !file.isFile()) continue;
|
|
2506
|
-
const filePath = (0, import_node_path.join)(projectDir, file.name);
|
|
2507
|
-
const sessionId = (0, import_node_path.basename)(file.name, ".jsonl");
|
|
2508
|
-
try {
|
|
2509
|
-
const s = (0, import_node_fs.statSync)(filePath);
|
|
2510
|
-
callback(filePath, sessionId, s.mtimeMs, false, null);
|
|
2511
|
-
} catch {
|
|
2512
|
-
continue;
|
|
2513
|
-
}
|
|
2514
|
-
this.discoverSubagents(filePath, sessionId, callback, 0, 4);
|
|
2515
|
-
}
|
|
2516
|
-
}
|
|
2517
|
-
}
|
|
2518
|
-
/**
|
|
2519
|
-
* Recursively discover subagent JSONL files under the subagents directory
|
|
2520
|
-
* that corresponds to `parentPath`.
|
|
2521
|
-
*
|
|
2522
|
-
* For a parent at `/path/to/session-1.jsonl`, looks for subagents at
|
|
2523
|
-
* `/path/to/session-1/subagents/agent-*.jsonl`.
|
|
2524
|
-
*
|
|
2525
|
-
* Recurses up to `maxDepth` levels (default 4).
|
|
2526
|
-
*/
|
|
2527
|
-
discoverSubagents(parentPath, parentSessionId, callback, depth, maxDepth) {
|
|
2528
|
-
if (depth >= maxDepth) return;
|
|
2529
|
-
const subDir = parentPath.replace(/\.jsonl$/, "") + "/subagents";
|
|
2530
|
-
let files;
|
|
2531
|
-
try {
|
|
2532
|
-
files = (0, import_node_fs.readdirSync)(subDir);
|
|
2533
|
-
} catch {
|
|
2534
|
-
return;
|
|
2535
|
-
}
|
|
2536
|
-
for (const file of files) {
|
|
2537
|
-
if (!file.startsWith("agent-") || !file.endsWith(".jsonl")) continue;
|
|
2538
|
-
const filePath = (0, import_node_path.join)(subDir, file);
|
|
2539
|
-
try {
|
|
2540
|
-
const s = (0, import_node_fs.statSync)(filePath);
|
|
2541
|
-
callback(filePath, parentSessionId, s.mtimeMs, true, parentSessionId);
|
|
2542
|
-
} catch {
|
|
2543
|
-
continue;
|
|
2544
|
-
}
|
|
2545
|
-
this.discoverSubagents(filePath, parentSessionId, callback, depth + 1, maxDepth);
|
|
2546
|
-
}
|
|
2547
|
-
}
|
|
2548
|
-
/**
|
|
2549
|
-
* Start watching `projectsDir` for JSONL file changes.
|
|
2550
|
-
* Runs `updateStale()` immediately for an initial sync, then sets up
|
|
2551
|
-
* `fs.watch` with `{ recursive: true }` (macOS-compatible) to detect
|
|
2552
|
-
* subsequent file changes and trigger debounced re-indexing.
|
|
2553
|
-
*/
|
|
2554
|
-
startWatching(projectsDir) {
|
|
2555
|
-
this.projectsDir = projectsDir;
|
|
2556
|
-
this.updateStale(projectsDir);
|
|
2557
|
-
try {
|
|
2558
|
-
this.watcher = (0, import_node_fs.watch)(projectsDir, { recursive: true }, (_event, filename) => {
|
|
2559
|
-
if (!filename || !filename.endsWith(".jsonl")) return;
|
|
2560
|
-
this.debouncedReindex((0, import_node_path.join)(projectsDir, filename));
|
|
2561
|
-
});
|
|
2562
|
-
this._watcherRunning = true;
|
|
2563
|
-
} catch (err) {
|
|
2564
|
-
console.warn("[search-index] fs.watch failed (recursive may not be supported):", err);
|
|
2565
|
-
this._watcherRunning = false;
|
|
2566
|
-
}
|
|
2567
|
-
}
|
|
2568
|
-
/**
|
|
2569
|
-
* Stop the file watcher and clear any pending debounce timers.
|
|
2570
|
-
* Safe to call even when not watching (no-op).
|
|
2571
|
-
*/
|
|
2572
|
-
stopWatching() {
|
|
2573
|
-
if (this.watcher) {
|
|
2574
|
-
this.watcher.close();
|
|
2575
|
-
this.watcher = null;
|
|
2576
|
-
}
|
|
2577
|
-
for (const timer of this.debounceTimers.values()) {
|
|
2578
|
-
clearTimeout(timer);
|
|
2579
|
-
}
|
|
2580
|
-
this.debounceTimers.clear();
|
|
2581
|
-
this._watcherRunning = false;
|
|
2582
|
-
}
|
|
2583
|
-
/**
|
|
2584
|
-
* Private helper: debounce re-indexing of a single file.
|
|
2585
|
-
* Waits 2 seconds after the last change event for a given file path
|
|
2586
|
-
* before actually calling `indexFile()`. This coalesces rapid writes
|
|
2587
|
-
* (e.g. streaming JSONL appends) into a single index operation.
|
|
2588
|
-
*/
|
|
2589
|
-
debouncedReindex(filePath) {
|
|
2590
|
-
const existing = this.debounceTimers.get(filePath);
|
|
2591
|
-
if (existing) clearTimeout(existing);
|
|
2592
|
-
this.debounceTimers.set(
|
|
2593
|
-
filePath,
|
|
2594
|
-
setTimeout(() => {
|
|
2595
|
-
this.debounceTimers.delete(filePath);
|
|
2596
|
-
try {
|
|
2597
|
-
const s = (0, import_node_fs.statSync)(filePath);
|
|
2598
|
-
const parts = filePath.split("/");
|
|
2599
|
-
const fileName = parts[parts.length - 1];
|
|
2600
|
-
const isSubagent = parts.includes("subagents");
|
|
2601
|
-
let sessionId;
|
|
2602
|
-
let parentSessionId = null;
|
|
2603
|
-
if (isSubagent) {
|
|
2604
|
-
const subagentsIdx = parts.lastIndexOf("subagents");
|
|
2605
|
-
const parentDir = parts[subagentsIdx - 1];
|
|
2606
|
-
sessionId = parentDir;
|
|
2607
|
-
parentSessionId = parentDir;
|
|
2608
|
-
} else {
|
|
2609
|
-
sessionId = (0, import_node_path.basename)(fileName, ".jsonl");
|
|
2610
|
-
}
|
|
2611
|
-
this.indexFile(filePath, sessionId, s.mtimeMs, {
|
|
2612
|
-
isSubagent,
|
|
2613
|
-
parentSessionId
|
|
2614
|
-
});
|
|
2615
|
-
} catch {
|
|
2616
|
-
}
|
|
2617
|
-
}, 2e3)
|
|
2618
|
-
);
|
|
4565
|
+
/** Every transcript from every agent, tagged with the session it belongs to. */
|
|
4566
|
+
discoverFiles(callback) {
|
|
4567
|
+
for (const store of allStores()) for (const file of store.list(0)) callback(file);
|
|
2619
4568
|
}
|
|
2620
4569
|
close() {
|
|
2621
|
-
this.stopWatching();
|
|
2622
4570
|
this.db.close();
|
|
2623
4571
|
}
|
|
2624
4572
|
};
|
|
2625
4573
|
|
|
2626
|
-
// src/lib/dirs.ts
|
|
2627
|
-
var import_node_path2 = require("node:path");
|
|
2628
|
-
var import_node_os = require("node:os");
|
|
2629
|
-
var dirs = {
|
|
2630
|
-
PROJECTS_DIR: (0, import_node_path2.join)((0, import_node_os.homedir)(), ".claude", "projects"),
|
|
2631
|
-
TEAMS_DIR: (0, import_node_path2.join)((0, import_node_os.homedir)(), ".claude", "teams"),
|
|
2632
|
-
TASKS_DIR: (0, import_node_path2.join)((0, import_node_os.homedir)(), ".claude", "tasks"),
|
|
2633
|
-
CODEX_SESSIONS_DIR: (0, import_node_path2.join)((0, import_node_os.homedir)(), ".codex", "sessions")
|
|
2634
|
-
};
|
|
2635
|
-
var DEFAULT_DB_PATH = (0, import_node_path2.join)((0, import_node_os.homedir)(), ".claude", "cogpit-memory", "search-index.db");
|
|
2636
|
-
|
|
2637
4574
|
// src/commands/search.ts
|
|
2638
|
-
var
|
|
2639
|
-
var
|
|
2640
|
-
var
|
|
4575
|
+
var import_node_fs3 = require("node:fs");
|
|
4576
|
+
var import_promises = require("node:fs/promises");
|
|
4577
|
+
var import_node_path3 = require("node:path");
|
|
2641
4578
|
|
|
2642
4579
|
// src/lib/response.ts
|
|
2643
4580
|
function parseMaxAge(raw) {
|
|
@@ -2657,98 +4594,6 @@ function parseMaxAge(raw) {
|
|
|
2657
4594
|
}
|
|
2658
4595
|
}
|
|
2659
4596
|
|
|
2660
|
-
// src/lib/helpers.ts
|
|
2661
|
-
var import_promises = require("node:fs/promises");
|
|
2662
|
-
var import_node_path3 = require("node:path");
|
|
2663
|
-
var import_node_os2 = require("node:os");
|
|
2664
|
-
async function findJsonlPath(sessionId) {
|
|
2665
|
-
const targetFile = `${sessionId}.jsonl`;
|
|
2666
|
-
try {
|
|
2667
|
-
const entries = await (0, import_promises.readdir)(dirs.PROJECTS_DIR, { withFileTypes: true });
|
|
2668
|
-
for (const entry of entries) {
|
|
2669
|
-
if (!entry.isDirectory() || entry.name === "memory") continue;
|
|
2670
|
-
const projectDir = (0, import_node_path3.join)(dirs.PROJECTS_DIR, entry.name);
|
|
2671
|
-
try {
|
|
2672
|
-
const files = await (0, import_promises.readdir)(projectDir);
|
|
2673
|
-
if (files.includes(targetFile)) {
|
|
2674
|
-
return (0, import_node_path3.join)(projectDir, targetFile);
|
|
2675
|
-
}
|
|
2676
|
-
} catch {
|
|
2677
|
-
continue;
|
|
2678
|
-
}
|
|
2679
|
-
}
|
|
2680
|
-
} catch {
|
|
2681
|
-
}
|
|
2682
|
-
const codexRoot = (0, import_node_path3.join)((0, import_node_os2.homedir)(), ".codex", "sessions");
|
|
2683
|
-
const walk = async (dir, depth) => {
|
|
2684
|
-
if (depth > 4) return null;
|
|
2685
|
-
let entries;
|
|
2686
|
-
try {
|
|
2687
|
-
entries = await (0, import_promises.readdir)(dir, { withFileTypes: true });
|
|
2688
|
-
} catch {
|
|
2689
|
-
return null;
|
|
2690
|
-
}
|
|
2691
|
-
for (const entry of entries) {
|
|
2692
|
-
const filePath = (0, import_node_path3.join)(dir, entry.name);
|
|
2693
|
-
if (entry.isDirectory()) {
|
|
2694
|
-
const match = await walk(filePath, depth + 1);
|
|
2695
|
-
if (match) return match;
|
|
2696
|
-
continue;
|
|
2697
|
-
}
|
|
2698
|
-
if (!entry.name.endsWith(".jsonl")) continue;
|
|
2699
|
-
if (entry.name.endsWith(`${sessionId}.jsonl`)) return filePath;
|
|
2700
|
-
}
|
|
2701
|
-
return null;
|
|
2702
|
-
};
|
|
2703
|
-
const codexMatch = await walk(codexRoot, 0);
|
|
2704
|
-
if (codexMatch) return codexMatch;
|
|
2705
|
-
return null;
|
|
2706
|
-
}
|
|
2707
|
-
async function matchSubagentToMember(leadSessionId, subagentFileName, members) {
|
|
2708
|
-
const entries = await (0, import_promises.readdir)(dirs.PROJECTS_DIR, { withFileTypes: true });
|
|
2709
|
-
for (const entry of entries) {
|
|
2710
|
-
if (!entry.isDirectory() || entry.name === "memory") continue;
|
|
2711
|
-
const filePath = (0, import_node_path3.join)(
|
|
2712
|
-
dirs.PROJECTS_DIR,
|
|
2713
|
-
entry.name,
|
|
2714
|
-
leadSessionId,
|
|
2715
|
-
"subagents",
|
|
2716
|
-
subagentFileName
|
|
2717
|
-
);
|
|
2718
|
-
try {
|
|
2719
|
-
const fh = await (0, import_promises.open)(filePath, "r");
|
|
2720
|
-
try {
|
|
2721
|
-
const buf = Buffer.alloc(16384);
|
|
2722
|
-
const { bytesRead } = await fh.read(buf, 0, 16384, 0);
|
|
2723
|
-
const firstLine = buf.subarray(0, bytesRead).toString("utf-8").split("\n")[0] || "";
|
|
2724
|
-
for (const member of members) {
|
|
2725
|
-
if (member.agentType === "team-lead") continue;
|
|
2726
|
-
const prompt = member.prompt || "";
|
|
2727
|
-
const snippet = prompt.slice(0, 120);
|
|
2728
|
-
const terms = [
|
|
2729
|
-
member.name,
|
|
2730
|
-
member.name.replace(/-/g, " "),
|
|
2731
|
-
...snippet ? [snippet, snippet.replace(/"/g, '\\"')] : []
|
|
2732
|
-
];
|
|
2733
|
-
if (terms.some((t) => firstLine.includes(t))) {
|
|
2734
|
-
return member.name;
|
|
2735
|
-
}
|
|
2736
|
-
}
|
|
2737
|
-
} finally {
|
|
2738
|
-
await fh.close();
|
|
2739
|
-
}
|
|
2740
|
-
} catch {
|
|
2741
|
-
continue;
|
|
2742
|
-
}
|
|
2743
|
-
}
|
|
2744
|
-
return null;
|
|
2745
|
-
}
|
|
2746
|
-
function encodeClaudeDirName(cwd) {
|
|
2747
|
-
const normalized = cwd.replace(/[\\/]+$/, "") || cwd;
|
|
2748
|
-
return normalized.replace(/[^a-zA-Z0-9]/g, "-");
|
|
2749
|
-
}
|
|
2750
|
-
var HOME_PREFIX = encodeClaudeDirName((0, import_node_os2.homedir)()).replace(/^-/, "").toLowerCase();
|
|
2751
|
-
|
|
2752
4597
|
// src/commands/search.ts
|
|
2753
4598
|
async function searchSessions(query, opts, searchIndex) {
|
|
2754
4599
|
if (!query || query.length < 2) {
|
|
@@ -2757,21 +4602,20 @@ async function searchSessions(query, opts, searchIndex) {
|
|
|
2757
4602
|
const limit = Math.min(Math.max(1, opts.limit ?? 20), 200);
|
|
2758
4603
|
const caseSensitive = opts.caseSensitive ?? false;
|
|
2759
4604
|
const maxAgeMs = parseMaxAge(opts.maxAge ?? "5d");
|
|
2760
|
-
const depth = Math.min(Math.max(1, opts.depth ?? 4), 4);
|
|
2761
4605
|
let index = searchIndex ?? null;
|
|
2762
4606
|
let ownedIndex = false;
|
|
2763
4607
|
if (!index && searchIndex === void 0) {
|
|
2764
4608
|
try {
|
|
2765
|
-
const dbExists = (0,
|
|
4609
|
+
const dbExists = (0, import_node_fs3.existsSync)(DEFAULT_DB_PATH);
|
|
2766
4610
|
if (!dbExists) {
|
|
2767
|
-
(0,
|
|
4611
|
+
(0, import_node_fs3.mkdirSync)((0, import_node_path3.dirname)(DEFAULT_DB_PATH), { recursive: true });
|
|
2768
4612
|
}
|
|
2769
4613
|
index = new SearchIndex(DEFAULT_DB_PATH);
|
|
2770
4614
|
ownedIndex = true;
|
|
2771
4615
|
if (!dbExists) {
|
|
2772
|
-
index.buildFull(
|
|
4616
|
+
index.buildFull();
|
|
2773
4617
|
} else {
|
|
2774
|
-
index.updateRecent(
|
|
4618
|
+
index.updateRecent();
|
|
2775
4619
|
}
|
|
2776
4620
|
} catch {
|
|
2777
4621
|
}
|
|
@@ -2781,9 +4625,14 @@ async function searchSessions(query, opts, searchIndex) {
|
|
|
2781
4625
|
const hits = index.search(query, {
|
|
2782
4626
|
limit,
|
|
2783
4627
|
sessionId: opts.sessionId,
|
|
4628
|
+
excludeSessionId: opts.excludeSessionId,
|
|
2784
4629
|
maxAgeMs,
|
|
2785
4630
|
caseSensitive
|
|
2786
4631
|
});
|
|
4632
|
+
if (opts.sessionId && hits.length === 0) {
|
|
4633
|
+
if (ownedIndex) index.close();
|
|
4634
|
+
return rawScanSearch(query, opts.sessionId, maxAgeMs, limit, caseSensitive, opts.excludeSessionId);
|
|
4635
|
+
}
|
|
2787
4636
|
const grouped = /* @__PURE__ */ new Map();
|
|
2788
4637
|
for (const hit of hits) {
|
|
2789
4638
|
let entry = grouped.get(hit.sessionId);
|
|
@@ -2807,6 +4656,7 @@ async function searchSessions(query, opts, searchIndex) {
|
|
|
2807
4656
|
if (hits.length >= limit) {
|
|
2808
4657
|
const counts = index.countMatches(query, {
|
|
2809
4658
|
sessionId: opts.sessionId,
|
|
4659
|
+
excludeSessionId: opts.excludeSessionId,
|
|
2810
4660
|
maxAgeMs
|
|
2811
4661
|
});
|
|
2812
4662
|
totalHits = counts.totalHits;
|
|
@@ -2824,7 +4674,7 @@ async function searchSessions(query, opts, searchIndex) {
|
|
|
2824
4674
|
if (ownedIndex) index.close();
|
|
2825
4675
|
}
|
|
2826
4676
|
}
|
|
2827
|
-
return rawScanSearch(query, opts.sessionId ?? null, maxAgeMs, limit, caseSensitive,
|
|
4677
|
+
return rawScanSearch(query, opts.sessionId ?? null, maxAgeMs, limit, caseSensitive, opts.excludeSessionId);
|
|
2828
4678
|
}
|
|
2829
4679
|
var CWD_READ_BYTES = 4096;
|
|
2830
4680
|
var cwdCache = /* @__PURE__ */ new Map();
|
|
@@ -2832,30 +4682,16 @@ async function cwdFromFilePath(filePath) {
|
|
|
2832
4682
|
const cached = cwdCache.get(filePath);
|
|
2833
4683
|
if (cached !== void 0) return cached;
|
|
2834
4684
|
try {
|
|
2835
|
-
const fh = await (0,
|
|
4685
|
+
const fh = await (0, import_promises.open)(filePath, "r");
|
|
2836
4686
|
try {
|
|
2837
4687
|
const buf = Buffer.alloc(CWD_READ_BYTES);
|
|
2838
4688
|
const { bytesRead } = await fh.read(buf, 0, CWD_READ_BYTES, 0);
|
|
2839
4689
|
const head = buf.subarray(0, bytesRead).toString("utf-8");
|
|
2840
|
-
const lines = head.split("\n", 10);
|
|
2841
|
-
|
|
2842
|
-
|
|
2843
|
-
|
|
2844
|
-
|
|
2845
|
-
if (obj.cwd) {
|
|
2846
|
-
cwdCache.set(filePath, obj.cwd);
|
|
2847
|
-
return obj.cwd;
|
|
2848
|
-
}
|
|
2849
|
-
if (obj.type === "session_meta" && obj.payload?.cwd) {
|
|
2850
|
-
cwdCache.set(filePath, obj.payload.cwd);
|
|
2851
|
-
return obj.payload.cwd;
|
|
2852
|
-
}
|
|
2853
|
-
if (obj.type === "turn_context" && obj.payload?.cwd) {
|
|
2854
|
-
cwdCache.set(filePath, obj.payload.cwd);
|
|
2855
|
-
return obj.payload.cwd;
|
|
2856
|
-
}
|
|
2857
|
-
} catch {
|
|
2858
|
-
}
|
|
4690
|
+
const lines = head.split("\n", 10).filter(Boolean);
|
|
4691
|
+
const cwd = formatForText(lines.join("\n")).metadataFromLines(lines).cwd;
|
|
4692
|
+
if (cwd) {
|
|
4693
|
+
cwdCache.set(filePath, cwd);
|
|
4694
|
+
return cwd;
|
|
2859
4695
|
}
|
|
2860
4696
|
} finally {
|
|
2861
4697
|
await fh.close();
|
|
@@ -2866,6 +4702,7 @@ async function cwdFromFilePath(filePath) {
|
|
|
2866
4702
|
return "";
|
|
2867
4703
|
}
|
|
2868
4704
|
var SNIPPET_WINDOW = 150;
|
|
4705
|
+
var MAX_SUBAGENT_DEPTH = 4;
|
|
2869
4706
|
function generateSnippet(text, matchIdx, queryLen) {
|
|
2870
4707
|
if (matchIdx === -1) return text.slice(0, SNIPPET_WINDOW);
|
|
2871
4708
|
const halfWindow = Math.floor((SNIPPET_WINDOW - queryLen) / 2);
|
|
@@ -2901,49 +4738,17 @@ function searchField(text, location, query, caseSensitive, extras) {
|
|
|
2901
4738
|
};
|
|
2902
4739
|
}
|
|
2903
4740
|
async function discoverSingleSession(sessionId) {
|
|
2904
|
-
const jsonlPath = await
|
|
4741
|
+
const jsonlPath = await findSessionFile(sessionId);
|
|
2905
4742
|
if (!jsonlPath) return [];
|
|
2906
|
-
const s = await (0,
|
|
2907
|
-
return [{ path: jsonlPath, mtimeMs: s.mtimeMs }];
|
|
4743
|
+
const s = await (0, import_promises.stat)(jsonlPath);
|
|
4744
|
+
return [{ path: jsonlPath, mtimeMs: s.mtimeMs, sessionId }];
|
|
2908
4745
|
}
|
|
2909
|
-
|
|
2910
|
-
|
|
2911
|
-
let entries;
|
|
2912
|
-
try {
|
|
2913
|
-
entries = await (0, import_promises2.readdir)(dirs.PROJECTS_DIR, { withFileTypes: true });
|
|
2914
|
-
} catch {
|
|
2915
|
-
return [];
|
|
2916
|
-
}
|
|
2917
|
-
const projectDirs = entries.filter((e) => e.isDirectory() && e.name !== "memory").map((e) => (0, import_node_path4.join)(dirs.PROJECTS_DIR, e.name));
|
|
2918
|
-
const nested = await Promise.all(
|
|
2919
|
-
projectDirs.map(async (projectDir) => {
|
|
2920
|
-
try {
|
|
2921
|
-
const files = await (0, import_promises2.readdir)(projectDir);
|
|
2922
|
-
const jsonlFiles = files.filter((f) => f.endsWith(".jsonl"));
|
|
2923
|
-
const statResults = await Promise.all(
|
|
2924
|
-
jsonlFiles.map(async (f) => {
|
|
2925
|
-
const filePath = (0, import_node_path4.join)(projectDir, f);
|
|
2926
|
-
try {
|
|
2927
|
-
const s = await (0, import_promises2.stat)(filePath);
|
|
2928
|
-
return s.mtimeMs >= cutoff ? { path: filePath, mtimeMs: s.mtimeMs } : null;
|
|
2929
|
-
} catch {
|
|
2930
|
-
return null;
|
|
2931
|
-
}
|
|
2932
|
-
})
|
|
2933
|
-
);
|
|
2934
|
-
return statResults.filter((r) => r !== null);
|
|
2935
|
-
} catch {
|
|
2936
|
-
return [];
|
|
2937
|
-
}
|
|
2938
|
-
})
|
|
2939
|
-
);
|
|
2940
|
-
const results = nested.flat();
|
|
2941
|
-
results.sort((a, b) => b.mtimeMs - a.mtimeMs);
|
|
2942
|
-
return results;
|
|
4746
|
+
function discoverAllSessions(maxAgeMs) {
|
|
4747
|
+
return listAllSessionFiles(Date.now() - maxAgeMs).filter((file) => !file.isSubagent).map((file) => ({ path: file.path, mtimeMs: file.mtimeMs, sessionId: file.sessionId }));
|
|
2943
4748
|
}
|
|
2944
4749
|
async function rawTextMatch(filePath, query, caseSensitive) {
|
|
2945
4750
|
try {
|
|
2946
|
-
const content = await (0,
|
|
4751
|
+
const content = await (0, import_promises.readFile)(filePath, "utf-8");
|
|
2947
4752
|
const haystack = caseSensitive ? content : content.toLowerCase();
|
|
2948
4753
|
const needle = caseSensitive ? query : query.toLowerCase();
|
|
2949
4754
|
if (haystack.includes(needle)) return content;
|
|
@@ -3018,13 +4823,13 @@ async function walkSubagentFiles(parentJsonlPath, query, caseSensitive, currentD
|
|
|
3018
4823
|
const subDir = parentJsonlPath.replace(/\.jsonl$/, "") + "/subagents";
|
|
3019
4824
|
let files;
|
|
3020
4825
|
try {
|
|
3021
|
-
files = await (0,
|
|
4826
|
+
files = await (0, import_promises.readdir)(subDir);
|
|
3022
4827
|
} catch {
|
|
3023
4828
|
return [];
|
|
3024
4829
|
}
|
|
3025
4830
|
const agentFiles = files.filter((f) => f.startsWith("agent-") && f.endsWith(".jsonl")).map((f) => ({
|
|
3026
4831
|
agentId: f.slice("agent-".length, -".jsonl".length),
|
|
3027
|
-
filePath: (0,
|
|
4832
|
+
filePath: (0, import_node_path3.join)(subDir, f)
|
|
3028
4833
|
}));
|
|
3029
4834
|
const matched = await Promise.all(
|
|
3030
4835
|
agentFiles.map(async ({ agentId, filePath }) => ({
|
|
@@ -3043,26 +4848,27 @@ async function walkSubagentFiles(parentJsonlPath, query, caseSensitive, currentD
|
|
|
3043
4848
|
);
|
|
3044
4849
|
return hitArrays.flat();
|
|
3045
4850
|
}
|
|
3046
|
-
async function rawScanSearch(query, sessionId, maxAgeMs, limit, caseSensitive,
|
|
4851
|
+
async function rawScanSearch(query, sessionId, maxAgeMs, limit, caseSensitive, excludeSessionId) {
|
|
3047
4852
|
try {
|
|
3048
|
-
const files = sessionId ? await discoverSingleSession(sessionId) :
|
|
4853
|
+
const files = sessionId ? await discoverSingleSession(sessionId) : discoverAllSessions(maxAgeMs);
|
|
3049
4854
|
let totalHits = 0;
|
|
3050
4855
|
let returnedHits = 0;
|
|
3051
4856
|
const results = [];
|
|
3052
4857
|
let sessionsSearched = 0;
|
|
3053
4858
|
for (const file of files) {
|
|
4859
|
+
if (file.sessionId === excludeSessionId) continue;
|
|
3054
4860
|
if (returnedHits >= limit) break;
|
|
3055
4861
|
const rawContent = await rawTextMatch(file.path, query, caseSensitive);
|
|
3056
4862
|
sessionsSearched++;
|
|
3057
4863
|
if (!rawContent) continue;
|
|
3058
4864
|
const session = parseSession(rawContent);
|
|
3059
4865
|
const sessionHits = walkSession(session, query, caseSensitive);
|
|
3060
|
-
const subagentHits = await walkSubagentFiles(file.path, query, caseSensitive, 0,
|
|
4866
|
+
const subagentHits = await walkSubagentFiles(file.path, query, caseSensitive, 0, MAX_SUBAGENT_DEPTH);
|
|
3061
4867
|
const allHits = [...sessionHits, ...subagentHits];
|
|
3062
4868
|
if (allHits.length === 0) continue;
|
|
3063
4869
|
totalHits += allHits.length;
|
|
3064
4870
|
const sessionResult = {
|
|
3065
|
-
sessionId: session.sessionId || (0,
|
|
4871
|
+
sessionId: session.sessionId || (0, import_node_path3.basename)(file.path, ".jsonl"),
|
|
3066
4872
|
cwd: session.cwd || "",
|
|
3067
4873
|
hits: []
|
|
3068
4874
|
};
|
|
@@ -3097,6 +4903,51 @@ async function rawScanSearch(query, sessionId, maxAgeMs, limit, caseSensitive, d
|
|
|
3097
4903
|
// src/commands/context.ts
|
|
3098
4904
|
var import_promises3 = require("node:fs/promises");
|
|
3099
4905
|
var import_node_path5 = require("node:path");
|
|
4906
|
+
|
|
4907
|
+
// src/lib/helpers.ts
|
|
4908
|
+
var import_promises2 = require("node:fs/promises");
|
|
4909
|
+
var import_node_path4 = require("node:path");
|
|
4910
|
+
async function matchSubagentToMember(leadSessionId, subagentFileName, members) {
|
|
4911
|
+
const entries = await (0, import_promises2.readdir)(dirs.PROJECTS_DIR, { withFileTypes: true });
|
|
4912
|
+
for (const entry of entries) {
|
|
4913
|
+
if (!entry.isDirectory() || entry.name === "memory") continue;
|
|
4914
|
+
const filePath = (0, import_node_path4.join)(
|
|
4915
|
+
dirs.PROJECTS_DIR,
|
|
4916
|
+
entry.name,
|
|
4917
|
+
leadSessionId,
|
|
4918
|
+
"subagents",
|
|
4919
|
+
subagentFileName
|
|
4920
|
+
);
|
|
4921
|
+
try {
|
|
4922
|
+
const fh = await (0, import_promises2.open)(filePath, "r");
|
|
4923
|
+
try {
|
|
4924
|
+
const buf = Buffer.alloc(16384);
|
|
4925
|
+
const { bytesRead } = await fh.read(buf, 0, 16384, 0);
|
|
4926
|
+
const firstLine = buf.subarray(0, bytesRead).toString("utf-8").split("\n")[0] || "";
|
|
4927
|
+
for (const member of members) {
|
|
4928
|
+
if (member.agentType === "team-lead") continue;
|
|
4929
|
+
const prompt = member.prompt || "";
|
|
4930
|
+
const snippet = prompt.slice(0, 120);
|
|
4931
|
+
const terms = [
|
|
4932
|
+
member.name,
|
|
4933
|
+
member.name.replace(/-/g, " "),
|
|
4934
|
+
...snippet ? [snippet, snippet.replace(/"/g, '\\"')] : []
|
|
4935
|
+
];
|
|
4936
|
+
if (terms.some((t) => firstLine.includes(t))) {
|
|
4937
|
+
return member.name;
|
|
4938
|
+
}
|
|
4939
|
+
}
|
|
4940
|
+
} finally {
|
|
4941
|
+
await fh.close();
|
|
4942
|
+
}
|
|
4943
|
+
} catch {
|
|
4944
|
+
continue;
|
|
4945
|
+
}
|
|
4946
|
+
}
|
|
4947
|
+
return null;
|
|
4948
|
+
}
|
|
4949
|
+
|
|
4950
|
+
// src/commands/context.ts
|
|
3100
4951
|
var RESULT_TRUNCATE_LIMIT = 1e4;
|
|
3101
4952
|
var L1_RESPONSE_LIMIT = 15e4;
|
|
3102
4953
|
var COMPACTION_SUMMARY_PREVIEW = 400;
|
|
@@ -3268,562 +5119,236 @@ function mapContentBlock(block) {
|
|
|
3268
5119
|
}
|
|
3269
5120
|
}
|
|
3270
5121
|
}
|
|
3271
|
-
async function findSubagentFile(parentJsonlPath, agentId) {
|
|
3272
|
-
const subagentsDir = parentJsonlPath.replace(/\.jsonl$/, "") + "/subagents";
|
|
3273
|
-
try {
|
|
3274
|
-
const files = await (0, import_promises3.readdir)(subagentsDir);
|
|
3275
|
-
for (const f of files) {
|
|
3276
|
-
if (!f.startsWith("agent-") || !f.endsWith(".jsonl")) continue;
|
|
3277
|
-
const fileAgentId = f.replace("agent-", "").replace(".jsonl", "");
|
|
3278
|
-
if (fileAgentId === agentId) {
|
|
3279
|
-
return { filePath: (0, import_node_path5.join)(subagentsDir, f), fileName: f };
|
|
3280
|
-
}
|
|
3281
|
-
}
|
|
3282
|
-
} catch {
|
|
3283
|
-
}
|
|
3284
|
-
return null;
|
|
3285
|
-
}
|
|
3286
|
-
async function findTeamContext(sessionId, subagentFileName) {
|
|
3287
|
-
try {
|
|
3288
|
-
const teamEntries = await (0, import_promises3.readdir)(dirs.TEAMS_DIR, { withFileTypes: true });
|
|
3289
|
-
for (const entry of teamEntries) {
|
|
3290
|
-
if (!entry.isDirectory()) continue;
|
|
3291
|
-
const configPath = (0, import_node_path5.join)(dirs.TEAMS_DIR, entry.name, "config.json");
|
|
3292
|
-
try {
|
|
3293
|
-
const raw = await (0, import_promises3.readFile)(configPath, "utf-8");
|
|
3294
|
-
const config = JSON.parse(raw);
|
|
3295
|
-
if (config.leadSessionId !== sessionId) continue;
|
|
3296
|
-
const members = config.members ?? [];
|
|
3297
|
-
const memberName = await matchSubagentToMember(sessionId, subagentFileName, members);
|
|
3298
|
-
if (!memberName) continue;
|
|
3299
|
-
let currentTask = null;
|
|
3300
|
-
try {
|
|
3301
|
-
const taskDir = (0, import_node_path5.join)(dirs.TASKS_DIR, entry.name);
|
|
3302
|
-
const taskFiles = await (0, import_promises3.readdir)(taskDir);
|
|
3303
|
-
for (const tf of taskFiles) {
|
|
3304
|
-
if (!tf.endsWith(".json")) continue;
|
|
3305
|
-
const taskRaw = await (0, import_promises3.readFile)((0, import_node_path5.join)(taskDir, tf), "utf-8");
|
|
3306
|
-
const task = JSON.parse(taskRaw);
|
|
3307
|
-
if (task.owner === memberName && task.status === "in_progress") {
|
|
3308
|
-
currentTask = { id: task.id, subject: task.subject, status: task.status };
|
|
3309
|
-
break;
|
|
3310
|
-
}
|
|
3311
|
-
}
|
|
3312
|
-
} catch {
|
|
3313
|
-
}
|
|
3314
|
-
return { teamName: entry.name, role: memberName, currentTask };
|
|
3315
|
-
} catch {
|
|
3316
|
-
}
|
|
3317
|
-
}
|
|
3318
|
-
} catch {
|
|
3319
|
-
}
|
|
3320
|
-
return null;
|
|
3321
|
-
}
|
|
3322
|
-
function findParentToolCallId(session, agentId) {
|
|
3323
|
-
for (const turn of session.turns) {
|
|
3324
|
-
for (const msg of turn.subAgentActivity) {
|
|
3325
|
-
if (msg.agentId === agentId) {
|
|
3326
|
-
for (const tc of turn.toolCalls) {
|
|
3327
|
-
if (tc.name === "Task" || tc.name === "Agent") {
|
|
3328
|
-
return tc.id;
|
|
3329
|
-
}
|
|
3330
|
-
}
|
|
3331
|
-
}
|
|
3332
|
-
}
|
|
3333
|
-
}
|
|
3334
|
-
return null;
|
|
3335
|
-
}
|
|
3336
|
-
function findAgentMetadata(session, agentId) {
|
|
3337
|
-
for (const turn of session.turns) {
|
|
3338
|
-
for (const msg of turn.subAgentActivity) {
|
|
3339
|
-
if (msg.agentId === agentId) {
|
|
3340
|
-
return {
|
|
3341
|
-
name: msg.agentName,
|
|
3342
|
-
type: msg.subagentType,
|
|
3343
|
-
isBackground: msg.isBackground
|
|
3344
|
-
};
|
|
3345
|
-
}
|
|
3346
|
-
}
|
|
3347
|
-
}
|
|
3348
|
-
return { name: null, type: null, isBackground: false };
|
|
3349
|
-
}
|
|
3350
|
-
async function getSessionOverview(sessionId) {
|
|
3351
|
-
const jsonlPath = await findJsonlPath(sessionId);
|
|
3352
|
-
if (!jsonlPath) return { error: "Session not found" };
|
|
3353
|
-
const content = await (0, import_promises3.readFile)(jsonlPath, "utf-8");
|
|
3354
|
-
const session = parseSession(content);
|
|
3355
|
-
return mapSessionToOverview(session);
|
|
3356
|
-
}
|
|
3357
|
-
async function getTurnDetail(sessionId, turnIndex) {
|
|
3358
|
-
const jsonlPath = await findJsonlPath(sessionId);
|
|
3359
|
-
if (!jsonlPath) return { error: "Session not found" };
|
|
3360
|
-
const content = await (0, import_promises3.readFile)(jsonlPath, "utf-8");
|
|
3361
|
-
const session = parseSession(content);
|
|
3362
|
-
if (turnIndex < 0 || turnIndex >= session.turns.length) return { error: "Turn not found" };
|
|
3363
|
-
return mapTurnToDetail(session, turnIndex);
|
|
3364
|
-
}
|
|
3365
|
-
async function getAgentOverview(sessionId, agentId) {
|
|
3366
|
-
const jsonlPath = await findJsonlPath(sessionId);
|
|
3367
|
-
if (!jsonlPath) return { error: "Session not found" };
|
|
3368
|
-
const content = await (0, import_promises3.readFile)(jsonlPath, "utf-8");
|
|
3369
|
-
const session = parseSession(content);
|
|
3370
|
-
const subagentFile = await findSubagentFile(jsonlPath, agentId);
|
|
3371
|
-
if (!subagentFile) return { error: "Agent not found" };
|
|
3372
|
-
const subagentContent = await (0, import_promises3.readFile)(subagentFile.filePath, "utf-8");
|
|
3373
|
-
const subagentSession = parseSession(subagentContent);
|
|
3374
|
-
const metadata = findAgentMetadata(session, agentId);
|
|
3375
|
-
const parentToolCallId = findParentToolCallId(session, agentId);
|
|
3376
|
-
const teamContext = await findTeamContext(sessionId, subagentFile.fileName);
|
|
3377
|
-
return {
|
|
3378
|
-
sessionId,
|
|
3379
|
-
agentId,
|
|
3380
|
-
name: metadata.name,
|
|
3381
|
-
type: metadata.type,
|
|
3382
|
-
parentToolCallId,
|
|
3383
|
-
isBackground: metadata.isBackground,
|
|
3384
|
-
teamContext,
|
|
3385
|
-
overview: mapSessionToOverview(subagentSession)
|
|
3386
|
-
};
|
|
3387
|
-
}
|
|
3388
|
-
async function getAgentTurnDetail(sessionId, agentId, turnIndex) {
|
|
3389
|
-
const jsonlPath = await findJsonlPath(sessionId);
|
|
3390
|
-
if (!jsonlPath) return { error: "Session not found" };
|
|
3391
|
-
const subagentFile = await findSubagentFile(jsonlPath, agentId);
|
|
3392
|
-
if (!subagentFile) return { error: "Agent not found" };
|
|
3393
|
-
const subagentContent = await (0, import_promises3.readFile)(subagentFile.filePath, "utf-8");
|
|
3394
|
-
const subagentSession = parseSession(subagentContent);
|
|
3395
|
-
if (turnIndex < 0 || turnIndex >= subagentSession.turns.length) return { error: "Turn not found" };
|
|
3396
|
-
return mapTurnToDetail(subagentSession, turnIndex);
|
|
3397
|
-
}
|
|
3398
|
-
|
|
3399
|
-
// src/commands/sessions.ts
|
|
3400
|
-
var import_promises5 = require("node:fs/promises");
|
|
3401
|
-
var import_node_path6 = require("node:path");
|
|
3402
|
-
|
|
3403
|
-
// src/lib/metadata.ts
|
|
3404
|
-
var import_promises4 = require("node:fs/promises");
|
|
3405
|
-
|
|
3406
|
-
// src/lib/sessionStatus.ts
|
|
3407
|
-
var TASK_NOTIFICATION_MARKER = "<task-notification>";
|
|
3408
|
-
var TASK_ID_RE = /<task-id>([^<]+)<\/task-id>/g;
|
|
3409
|
-
var TOOL_USE_ID_RE = /<tool-use-id>([^<]+)<\/tool-use-id>/g;
|
|
3410
|
-
function isObject3(value) {
|
|
3411
|
-
return typeof value === "object" && value !== null;
|
|
3412
|
-
}
|
|
3413
|
-
function userMessageTexts(msg) {
|
|
3414
|
-
const message = isObject3(msg.message) ? msg.message : null;
|
|
3415
|
-
const content = message?.content;
|
|
3416
|
-
if (typeof content === "string") return [content];
|
|
3417
|
-
if (!Array.isArray(content)) return [];
|
|
3418
|
-
return content.filter((block) => isObject3(block) && typeof block.text === "string").map((block) => block.text);
|
|
3419
|
-
}
|
|
3420
|
-
function collectClaudeBackgroundWork(rawMessages) {
|
|
3421
|
-
const launches = [];
|
|
3422
|
-
const resolved = /* @__PURE__ */ new Set();
|
|
3423
|
-
const queuedIds = /* @__PURE__ */ new Set();
|
|
3424
|
-
const deliveredIds = /* @__PURE__ */ new Set();
|
|
3425
|
-
function noteNotifications(text, sink) {
|
|
3426
|
-
if (!text.includes(TASK_NOTIFICATION_MARKER)) return;
|
|
3427
|
-
for (const match of text.matchAll(TASK_ID_RE)) {
|
|
3428
|
-
resolved.add(match[1]);
|
|
3429
|
-
sink?.add(match[1]);
|
|
3430
|
-
}
|
|
3431
|
-
for (const match of text.matchAll(TOOL_USE_ID_RE)) {
|
|
3432
|
-
resolved.add(match[1]);
|
|
3433
|
-
sink?.add(match[1]);
|
|
3434
|
-
}
|
|
3435
|
-
}
|
|
3436
|
-
for (const msg of rawMessages) {
|
|
3437
|
-
if (msg.type === "user") {
|
|
3438
|
-
const toolUseResult = isObject3(msg.toolUseResult) ? msg.toolUseResult : null;
|
|
3439
|
-
if (toolUseResult?.status === "async_launched") {
|
|
3440
|
-
const message = isObject3(msg.message) ? msg.message : null;
|
|
3441
|
-
const blocks = Array.isArray(message?.content) ? message.content : [];
|
|
3442
|
-
const toolUseIds = blocks.filter((block) => isObject3(block) && block.type === "tool_result").map((block) => block.tool_use_id);
|
|
3443
|
-
const ids = [toolUseResult.agentId, toolUseResult.taskId, ...toolUseIds].filter((id) => typeof id === "string" && id.length > 0);
|
|
3444
|
-
const description = [toolUseResult.description, toolUseResult.workflowName, toolUseResult.summary].find((value) => typeof value === "string" && value.length > 0) ?? "";
|
|
3445
|
-
if (ids.length > 0) launches.push({ ids, description });
|
|
3446
|
-
}
|
|
3447
|
-
for (const text of userMessageTexts(msg)) noteNotifications(text, deliveredIds);
|
|
3448
|
-
continue;
|
|
3449
|
-
}
|
|
3450
|
-
if (msg.type === "assistant") {
|
|
3451
|
-
const message = isObject3(msg.message) ? msg.message : null;
|
|
3452
|
-
const blocks = Array.isArray(message?.content) ? message.content : [];
|
|
3453
|
-
for (const block of blocks) {
|
|
3454
|
-
if (!isObject3(block) || block.type !== "tool_use" || block.name !== "TaskStop") continue;
|
|
3455
|
-
const input = isObject3(block.input) ? block.input : null;
|
|
3456
|
-
const taskId = input?.task_id ?? input?.shell_id;
|
|
3457
|
-
if (typeof taskId === "string") resolved.add(taskId);
|
|
3458
|
-
}
|
|
3459
|
-
continue;
|
|
3460
|
-
}
|
|
3461
|
-
if (msg.type === "attachment") {
|
|
3462
|
-
const attachment = isObject3(msg.attachment) ? msg.attachment : null;
|
|
3463
|
-
if (typeof attachment?.prompt === "string") noteNotifications(attachment.prompt, deliveredIds);
|
|
3464
|
-
continue;
|
|
3465
|
-
}
|
|
3466
|
-
if (msg.type === "queue-operation" && typeof msg.content === "string") {
|
|
3467
|
-
const op = msg.operation;
|
|
3468
|
-
noteNotifications(msg.content, op === "enqueue" ? queuedIds : deliveredIds);
|
|
3469
|
-
}
|
|
3470
|
-
}
|
|
3471
|
-
return {
|
|
3472
|
-
pendingDescriptions: launches.filter((launch) => !launch.ids.some((id) => resolved.has(id))).map((launch) => launch.description),
|
|
3473
|
-
undeliveredNotifications: [...queuedIds].filter((id) => !deliveredIds.has(id)).length
|
|
3474
|
-
};
|
|
3475
|
-
}
|
|
3476
|
-
function awaitingAgentsResult(pendingDescriptions, pendingQueue) {
|
|
3477
|
-
return {
|
|
3478
|
-
status: "awaiting_agents",
|
|
3479
|
-
pendingQueue,
|
|
3480
|
-
pendingAgents: pendingDescriptions.length,
|
|
3481
|
-
pendingAgentDescriptions: pendingDescriptions.filter((description) => description.length > 0)
|
|
3482
|
-
};
|
|
3483
|
-
}
|
|
3484
|
-
function deriveSessionStatus(rawMessages) {
|
|
3485
|
-
const firstType = rawMessages[0]?.type;
|
|
3486
|
-
if (firstType === "session_meta" || firstType === "turn_context" || firstType === "event_msg" || firstType === "response_item") {
|
|
3487
|
-
return deriveCodexSessionStatus(rawMessages);
|
|
3488
|
-
}
|
|
3489
|
-
let pendingEnqueues = 0;
|
|
3490
|
-
let backgroundWork = null;
|
|
3491
|
-
let deliveringNotification = false;
|
|
3492
|
-
function result(status, toolName) {
|
|
3493
|
-
const info = { status, pendingQueue: Math.max(0, pendingEnqueues) };
|
|
3494
|
-
if (toolName) info.toolName = toolName;
|
|
3495
|
-
return info;
|
|
3496
|
-
}
|
|
3497
|
-
function getBackgroundWork() {
|
|
3498
|
-
return backgroundWork ??= collectClaudeBackgroundWork(rawMessages);
|
|
3499
|
-
}
|
|
3500
|
-
for (let i = rawMessages.length - 1; i >= 0; i--) {
|
|
3501
|
-
const msg = rawMessages[i];
|
|
3502
|
-
if (msg.type === "queue-operation") continue;
|
|
3503
|
-
if (msg.type === "progress") {
|
|
3504
|
-
const data = msg.data;
|
|
3505
|
-
if (data?.type === "hook_progress") {
|
|
3506
|
-
const decision = data.decision ?? data.hookSpecificOutput?.permissionDecision;
|
|
3507
|
-
if (decision === "defer") return result("deferred");
|
|
3508
|
-
}
|
|
3509
|
-
break;
|
|
3510
|
-
}
|
|
3511
|
-
break;
|
|
3512
|
-
}
|
|
3513
|
-
for (let i = rawMessages.length - 1; i >= 0; i--) {
|
|
3514
|
-
const msg = rawMessages[i];
|
|
3515
|
-
if (msg.type === "queue-operation") {
|
|
3516
|
-
if (typeof msg.content === "string" && msg.content.includes(TASK_NOTIFICATION_MARKER)) continue;
|
|
3517
|
-
const op = msg.operation;
|
|
3518
|
-
if (op === "enqueue") pendingEnqueues++;
|
|
3519
|
-
else if (op === "dequeue" || op === "remove") pendingEnqueues--;
|
|
3520
|
-
continue;
|
|
3521
|
-
}
|
|
3522
|
-
if (msg.type === "attachment") {
|
|
3523
|
-
const attachment = isObject3(msg.attachment) ? msg.attachment : null;
|
|
3524
|
-
if (attachment?.commandMode === "task-notification") deliveringNotification = true;
|
|
3525
|
-
continue;
|
|
3526
|
-
}
|
|
3527
|
-
if (msg.type === "assistant") {
|
|
3528
|
-
const message = msg.message;
|
|
3529
|
-
const stopReason = message?.stop_reason;
|
|
3530
|
-
if (stopReason === "end_turn") {
|
|
3531
|
-
const work = getBackgroundWork();
|
|
3532
|
-
if (deliveringNotification || work.undeliveredNotifications > 0) return result("processing");
|
|
3533
|
-
if (work.pendingDescriptions.length > 0) {
|
|
3534
|
-
return awaitingAgentsResult(work.pendingDescriptions, Math.max(0, pendingEnqueues));
|
|
3535
|
-
}
|
|
3536
|
-
let hasActivity = false;
|
|
3537
|
-
for (let j = i - 1; j >= 0; j--) {
|
|
3538
|
-
const m = rawMessages[j];
|
|
3539
|
-
if (m.type === "user" && !m.isMeta) {
|
|
3540
|
-
hasActivity = true;
|
|
3541
|
-
break;
|
|
3542
|
-
}
|
|
3543
|
-
}
|
|
3544
|
-
return result(hasActivity ? "completed" : "idle");
|
|
3545
|
-
}
|
|
3546
|
-
if (stopReason === "tool_use") {
|
|
3547
|
-
const content = message?.content;
|
|
3548
|
-
const toolUseBlock = content?.findLast?.((b) => b.type === "tool_use");
|
|
3549
|
-
return result("tool_use", toolUseBlock?.name);
|
|
3550
|
-
}
|
|
3551
|
-
return result("thinking");
|
|
3552
|
-
}
|
|
3553
|
-
if (msg.type === "user") {
|
|
3554
|
-
const isMeta = msg.isMeta;
|
|
3555
|
-
if (isMeta) continue;
|
|
3556
|
-
return result("processing");
|
|
3557
|
-
}
|
|
3558
|
-
if (msg.type === "system" && msg.subtype === "terminal_reason") {
|
|
3559
|
-
const reason = msg.reason;
|
|
3560
|
-
if (reason) {
|
|
3561
|
-
return { status: "completed", terminalReason: reason, pendingQueue: Math.max(0, pendingEnqueues) };
|
|
3562
|
-
}
|
|
3563
|
-
continue;
|
|
3564
|
-
}
|
|
3565
|
-
if (msg.type === "summary") continue;
|
|
3566
|
-
if (msg.type === "system" && msg.subtype === "compact_boundary") continue;
|
|
3567
|
-
}
|
|
3568
|
-
return { status: "idle" };
|
|
3569
|
-
}
|
|
3570
|
-
var TERMINAL_INTER_AGENT_TYPES = /* @__PURE__ */ new Set(["FINAL_ANSWER", "ERROR", "FAILED", "INTERRUPTED"]);
|
|
3571
|
-
function codexPayloadText(payload) {
|
|
3572
|
-
const content = payload.content;
|
|
3573
|
-
if (!Array.isArray(content)) return "";
|
|
3574
|
-
return content.filter((block) => isObject3(block) && block.type === "input_text" && typeof block.text === "string").map((block) => block.text).join("\n");
|
|
3575
|
-
}
|
|
3576
|
-
function parseJsonObject(value) {
|
|
3577
|
-
if (typeof value !== "string") return null;
|
|
3578
|
-
try {
|
|
3579
|
-
const parsed = JSON.parse(value);
|
|
3580
|
-
return isObject3(parsed) ? parsed : null;
|
|
3581
|
-
} catch {
|
|
3582
|
-
return null;
|
|
3583
|
-
}
|
|
3584
|
-
}
|
|
3585
|
-
function collectCodexPendingAgents(rawMessages) {
|
|
3586
|
-
const alive = /* @__PURE__ */ new Map();
|
|
3587
|
-
const done = /* @__PURE__ */ new Set();
|
|
3588
|
-
const agentIdByPath = /* @__PURE__ */ new Map();
|
|
3589
|
-
const spawnCalls = /* @__PURE__ */ new Map();
|
|
3590
|
-
const waitCalls = /* @__PURE__ */ new Set();
|
|
3591
|
-
const interruptCalls = /* @__PURE__ */ new Map();
|
|
3592
|
-
function markAlive(agentId, description) {
|
|
3593
|
-
if (!done.has(agentId)) alive.set(agentId, alive.get(agentId) || description);
|
|
3594
|
-
}
|
|
3595
|
-
for (const msg of rawMessages) {
|
|
3596
|
-
const payload = isObject3(msg.payload) ? msg.payload : null;
|
|
3597
|
-
if (!payload) continue;
|
|
3598
|
-
if (msg.type === "event_msg" && payload.type === "sub_agent_activity" && typeof payload.agent_thread_id === "string" && typeof payload.agent_path === "string") {
|
|
3599
|
-
const agentId = payload.agent_thread_id;
|
|
3600
|
-
const kind = typeof payload.kind === "string" ? payload.kind : "started";
|
|
3601
|
-
const eventId = typeof payload.event_id === "string" ? payload.event_id : "";
|
|
3602
|
-
const existingId = agentIdByPath.get(payload.agent_path);
|
|
3603
|
-
const known = alive.has(agentId) || done.has(agentId) || existingId !== void 0 || spawnCalls.has(eventId);
|
|
3604
|
-
if (kind !== "started" && !known) continue;
|
|
3605
|
-
if (existingId && existingId !== agentId) {
|
|
3606
|
-
if (alive.has(existingId)) {
|
|
3607
|
-
markAlive(agentId, alive.get(existingId) ?? "");
|
|
3608
|
-
alive.delete(existingId);
|
|
3609
|
-
}
|
|
3610
|
-
if (done.has(existingId)) done.add(agentId);
|
|
3611
|
-
}
|
|
3612
|
-
agentIdByPath.set(payload.agent_path, agentId);
|
|
3613
|
-
if (kind === "interrupted") {
|
|
3614
|
-
done.add(agentId);
|
|
3615
|
-
alive.delete(agentId);
|
|
3616
|
-
} else {
|
|
3617
|
-
markAlive(agentId, spawnCalls.get(eventId) ?? "");
|
|
3618
|
-
}
|
|
3619
|
-
continue;
|
|
3620
|
-
}
|
|
3621
|
-
if (msg.type !== "response_item") continue;
|
|
3622
|
-
if (payload.type === "function_call" && typeof payload.call_id === "string") {
|
|
3623
|
-
const name = normalizeFunctionName(typeof payload.name === "string" ? payload.name : "");
|
|
3624
|
-
if (name === "spawn_agent") {
|
|
3625
|
-
const input = parseJsonObject(payload.arguments);
|
|
3626
|
-
spawnCalls.set(payload.call_id, typeof input?.task_name === "string" ? input.task_name : "");
|
|
3627
|
-
} else if (name === "wait_agent") {
|
|
3628
|
-
waitCalls.add(payload.call_id);
|
|
3629
|
-
} else if (name === "interrupt_agent") {
|
|
3630
|
-
const input = parseJsonObject(payload.arguments);
|
|
3631
|
-
const targets = [input?.agent_id, ...Array.isArray(input?.agent_ids) ? input.agent_ids : []].filter((id) => typeof id === "string");
|
|
3632
|
-
interruptCalls.set(payload.call_id, targets);
|
|
3633
|
-
}
|
|
3634
|
-
continue;
|
|
3635
|
-
}
|
|
3636
|
-
if (payload.type === "function_call_output" && typeof payload.call_id === "string") {
|
|
3637
|
-
const callId = payload.call_id;
|
|
3638
|
-
if (spawnCalls.has(callId)) {
|
|
3639
|
-
const output = parseJsonObject(payload.output);
|
|
3640
|
-
const agentId = typeof output?.agent_id === "string" ? output.agent_id : "";
|
|
3641
|
-
if (agentId) {
|
|
3642
|
-
markAlive(agentId, spawnCalls.get(callId) ?? "");
|
|
3643
|
-
const agentPath = typeof output?.task_name === "string" ? output.task_name : "";
|
|
3644
|
-
if (agentPath && !agentIdByPath.has(agentPath)) agentIdByPath.set(agentPath, agentId);
|
|
3645
|
-
}
|
|
3646
|
-
} else if (waitCalls.has(callId)) {
|
|
3647
|
-
const output = parseJsonObject(payload.output);
|
|
3648
|
-
const statusMap = isObject3(output?.status) ? output.status : {};
|
|
3649
|
-
for (const [agentId, status] of Object.entries(statusMap)) {
|
|
3650
|
-
const statusValue = isObject3(status) ? status : {};
|
|
3651
|
-
const lifecycle = typeof statusValue.status === "string" ? statusValue.status : "";
|
|
3652
|
-
const isTerminal = typeof statusValue.completed === "string" || typeof statusValue.failed === "string" || typeof statusValue.error === "string" || typeof statusValue.interrupted === "string" || lifecycle === "completed" || lifecycle === "failed" || lifecycle === "interrupted";
|
|
3653
|
-
if (isTerminal) {
|
|
3654
|
-
done.add(agentId);
|
|
3655
|
-
alive.delete(agentId);
|
|
5122
|
+
async function findSubagentFile(parentJsonlPath, agentId) {
|
|
5123
|
+
const subagentsDir = parentJsonlPath.replace(/\.jsonl$/, "") + "/subagents";
|
|
5124
|
+
try {
|
|
5125
|
+
const files = await (0, import_promises3.readdir)(subagentsDir);
|
|
5126
|
+
for (const f of files) {
|
|
5127
|
+
if (!f.startsWith("agent-") || !f.endsWith(".jsonl")) continue;
|
|
5128
|
+
const fileAgentId = f.replace("agent-", "").replace(".jsonl", "");
|
|
5129
|
+
if (fileAgentId === agentId) {
|
|
5130
|
+
return { filePath: (0, import_node_path5.join)(subagentsDir, f), fileName: f };
|
|
5131
|
+
}
|
|
5132
|
+
}
|
|
5133
|
+
} catch {
|
|
5134
|
+
}
|
|
5135
|
+
return null;
|
|
5136
|
+
}
|
|
5137
|
+
async function findTeamContext(sessionId, subagentFileName) {
|
|
5138
|
+
try {
|
|
5139
|
+
const teamEntries = await (0, import_promises3.readdir)(dirs.TEAMS_DIR, { withFileTypes: true });
|
|
5140
|
+
for (const entry of teamEntries) {
|
|
5141
|
+
if (!entry.isDirectory()) continue;
|
|
5142
|
+
const configPath = (0, import_node_path5.join)(dirs.TEAMS_DIR, entry.name, "config.json");
|
|
5143
|
+
try {
|
|
5144
|
+
const raw = await (0, import_promises3.readFile)(configPath, "utf-8");
|
|
5145
|
+
const config = JSON.parse(raw);
|
|
5146
|
+
if (config.leadSessionId !== sessionId) continue;
|
|
5147
|
+
const members = config.members ?? [];
|
|
5148
|
+
const memberName = await matchSubagentToMember(sessionId, subagentFileName, members);
|
|
5149
|
+
if (!memberName) continue;
|
|
5150
|
+
let currentTask = null;
|
|
5151
|
+
try {
|
|
5152
|
+
const taskDir = (0, import_node_path5.join)(dirs.TASKS_DIR, entry.name);
|
|
5153
|
+
const taskFiles = await (0, import_promises3.readdir)(taskDir);
|
|
5154
|
+
for (const tf of taskFiles) {
|
|
5155
|
+
if (!tf.endsWith(".json")) continue;
|
|
5156
|
+
const taskRaw = await (0, import_promises3.readFile)((0, import_node_path5.join)(taskDir, tf), "utf-8");
|
|
5157
|
+
const task = JSON.parse(taskRaw);
|
|
5158
|
+
if (task.owner === memberName && task.status === "in_progress") {
|
|
5159
|
+
currentTask = { id: task.id, subject: task.subject, status: task.status };
|
|
5160
|
+
break;
|
|
5161
|
+
}
|
|
3656
5162
|
}
|
|
5163
|
+
} catch {
|
|
3657
5164
|
}
|
|
3658
|
-
|
|
3659
|
-
|
|
3660
|
-
done.add(agentId);
|
|
3661
|
-
alive.delete(agentId);
|
|
3662
|
-
}
|
|
5165
|
+
return { teamName: entry.name, role: memberName, currentTask };
|
|
5166
|
+
} catch {
|
|
3663
5167
|
}
|
|
3664
|
-
continue;
|
|
3665
|
-
}
|
|
3666
|
-
if (payload.type === "agent_message" && typeof payload.author === "string") {
|
|
3667
|
-
const text = codexPayloadText(payload);
|
|
3668
|
-
const messageType = text.match(/^Message Type:\s*([^\r\n]+)/)?.[1]?.trim().toUpperCase() ?? "";
|
|
3669
|
-
if (!TERMINAL_INTER_AGENT_TYPES.has(messageType)) continue;
|
|
3670
|
-
const agentId = agentIdByPath.get(payload.author) ?? payload.author;
|
|
3671
|
-
done.add(agentId);
|
|
3672
|
-
alive.delete(agentId);
|
|
3673
5168
|
}
|
|
5169
|
+
} catch {
|
|
3674
5170
|
}
|
|
3675
|
-
return
|
|
5171
|
+
return null;
|
|
3676
5172
|
}
|
|
3677
|
-
function
|
|
3678
|
-
for (
|
|
3679
|
-
const msg
|
|
3680
|
-
|
|
3681
|
-
|
|
3682
|
-
|
|
3683
|
-
|
|
3684
|
-
|
|
3685
|
-
if (pending.length > 0) return awaitingAgentsResult(pending, 0);
|
|
3686
|
-
return { status: "completed" };
|
|
5173
|
+
function findParentToolCallId(session, agentId) {
|
|
5174
|
+
for (const turn of session.turns) {
|
|
5175
|
+
for (const msg of turn.subAgentActivity) {
|
|
5176
|
+
if (msg.agentId === agentId) {
|
|
5177
|
+
for (const tc of turn.toolCalls) {
|
|
5178
|
+
if (tc.name === "Task" || tc.name === "Agent") {
|
|
5179
|
+
return tc.id;
|
|
5180
|
+
}
|
|
3687
5181
|
}
|
|
3688
|
-
case "task_started":
|
|
3689
|
-
return { status: "processing" };
|
|
3690
|
-
case "agent_message":
|
|
3691
|
-
return { status: "thinking" };
|
|
3692
|
-
case "token_count":
|
|
3693
|
-
continue;
|
|
3694
5182
|
}
|
|
3695
5183
|
}
|
|
3696
|
-
|
|
3697
|
-
|
|
3698
|
-
|
|
3699
|
-
|
|
3700
|
-
|
|
3701
|
-
|
|
3702
|
-
if (
|
|
3703
|
-
|
|
3704
|
-
|
|
5184
|
+
}
|
|
5185
|
+
return null;
|
|
5186
|
+
}
|
|
5187
|
+
function findAgentMetadata(session, agentId) {
|
|
5188
|
+
for (const turn of session.turns) {
|
|
5189
|
+
for (const msg of turn.subAgentActivity) {
|
|
5190
|
+
if (msg.agentId === agentId) {
|
|
5191
|
+
return {
|
|
5192
|
+
name: msg.agentName,
|
|
5193
|
+
type: msg.subagentType,
|
|
5194
|
+
isBackground: msg.isBackground
|
|
5195
|
+
};
|
|
3705
5196
|
}
|
|
3706
5197
|
}
|
|
3707
5198
|
}
|
|
3708
|
-
return {
|
|
5199
|
+
return { name: null, type: null, isBackground: false };
|
|
5200
|
+
}
|
|
5201
|
+
async function getSessionOverview(sessionId) {
|
|
5202
|
+
const jsonlPath = await findSessionFile(sessionId);
|
|
5203
|
+
if (!jsonlPath) return { error: "Session not found" };
|
|
5204
|
+
const content = await (0, import_promises3.readFile)(jsonlPath, "utf-8");
|
|
5205
|
+
const session = parseSession(content);
|
|
5206
|
+
return mapSessionToOverview(session);
|
|
5207
|
+
}
|
|
5208
|
+
async function getTurnDetail(sessionId, turnIndex) {
|
|
5209
|
+
const jsonlPath = await findSessionFile(sessionId);
|
|
5210
|
+
if (!jsonlPath) return { error: "Session not found" };
|
|
5211
|
+
const content = await (0, import_promises3.readFile)(jsonlPath, "utf-8");
|
|
5212
|
+
const session = parseSession(content);
|
|
5213
|
+
if (turnIndex < 0 || turnIndex >= session.turns.length) return { error: "Turn not found" };
|
|
5214
|
+
return mapTurnToDetail(session, turnIndex);
|
|
5215
|
+
}
|
|
5216
|
+
async function getAgentOverview(sessionId, agentId) {
|
|
5217
|
+
const jsonlPath = await findSessionFile(sessionId);
|
|
5218
|
+
if (!jsonlPath) return { error: "Session not found" };
|
|
5219
|
+
const content = await (0, import_promises3.readFile)(jsonlPath, "utf-8");
|
|
5220
|
+
const session = parseSession(content);
|
|
5221
|
+
const subagentFile = await findSubagentFile(jsonlPath, agentId);
|
|
5222
|
+
if (!subagentFile) return { error: "Agent not found" };
|
|
5223
|
+
const subagentContent = await (0, import_promises3.readFile)(subagentFile.filePath, "utf-8");
|
|
5224
|
+
const subagentSession = parseSession(subagentContent);
|
|
5225
|
+
const metadata = findAgentMetadata(session, agentId);
|
|
5226
|
+
const parentToolCallId = findParentToolCallId(session, agentId);
|
|
5227
|
+
const teamContext = await findTeamContext(sessionId, subagentFile.fileName);
|
|
5228
|
+
return {
|
|
5229
|
+
sessionId,
|
|
5230
|
+
agentId,
|
|
5231
|
+
name: metadata.name,
|
|
5232
|
+
type: metadata.type,
|
|
5233
|
+
parentToolCallId,
|
|
5234
|
+
isBackground: metadata.isBackground,
|
|
5235
|
+
teamContext,
|
|
5236
|
+
overview: mapSessionToOverview(subagentSession)
|
|
5237
|
+
};
|
|
5238
|
+
}
|
|
5239
|
+
async function getAgentTurnDetail(sessionId, agentId, turnIndex) {
|
|
5240
|
+
const jsonlPath = await findSessionFile(sessionId);
|
|
5241
|
+
if (!jsonlPath) return { error: "Session not found" };
|
|
5242
|
+
const subagentFile = await findSubagentFile(jsonlPath, agentId);
|
|
5243
|
+
if (!subagentFile) return { error: "Agent not found" };
|
|
5244
|
+
const subagentContent = await (0, import_promises3.readFile)(subagentFile.filePath, "utf-8");
|
|
5245
|
+
const subagentSession = parseSession(subagentContent);
|
|
5246
|
+
if (turnIndex < 0 || turnIndex >= subagentSession.turns.length) return { error: "Turn not found" };
|
|
5247
|
+
return mapTurnToDetail(subagentSession, turnIndex);
|
|
3709
5248
|
}
|
|
3710
5249
|
|
|
5250
|
+
// src/commands/sessions.ts
|
|
5251
|
+
var import_node_path6 = require("node:path");
|
|
5252
|
+
|
|
3711
5253
|
// src/lib/metadata.ts
|
|
3712
|
-
|
|
3713
|
-
|
|
3714
|
-
|
|
3715
|
-
|
|
3716
|
-
|
|
3717
|
-
|
|
3718
|
-
|
|
3719
|
-
|
|
3720
|
-
|
|
3721
|
-
|
|
3722
|
-
|
|
3723
|
-
|
|
3724
|
-
|
|
3725
|
-
|
|
3726
|
-
|
|
3727
|
-
|
|
3728
|
-
|
|
3729
|
-
|
|
3730
|
-
|
|
3731
|
-
|
|
3732
|
-
|
|
3733
|
-
|
|
5254
|
+
var import_promises4 = require("node:fs/promises");
|
|
5255
|
+
|
|
5256
|
+
// src/lib/sessionStatus.ts
|
|
5257
|
+
function deriveSessionStatus(rawMessages) {
|
|
5258
|
+
return formatForRecords(rawMessages).status(rawMessages);
|
|
5259
|
+
}
|
|
5260
|
+
|
|
5261
|
+
// src/lib/metadata.ts
|
|
5262
|
+
var FULL_READ_LIMIT = 65536;
|
|
5263
|
+
var HEAD_READ_BYTES = 32768;
|
|
5264
|
+
function readHeader(format, lines) {
|
|
5265
|
+
return format.metadataFromLines(lines);
|
|
5266
|
+
}
|
|
5267
|
+
function cleanUserText(text) {
|
|
5268
|
+
const cleaned = text.replace(/<[^>]+>[\s\S]*?<\/[^>]+>/g, "").trim();
|
|
5269
|
+
return cleaned.length > 5 ? cleaned.slice(0, 120) : "";
|
|
5270
|
+
}
|
|
5271
|
+
function scanClaudeMessages(lines) {
|
|
5272
|
+
const meta = {
|
|
5273
|
+
firstUserMessage: "",
|
|
5274
|
+
lastUserMessage: "",
|
|
5275
|
+
timestamp: "",
|
|
5276
|
+
turnCount: 0
|
|
5277
|
+
};
|
|
5278
|
+
for (const line of lines) {
|
|
5279
|
+
let record;
|
|
3734
5280
|
try {
|
|
3735
|
-
|
|
5281
|
+
record = JSON.parse(line);
|
|
3736
5282
|
} catch {
|
|
3737
|
-
|
|
5283
|
+
continue;
|
|
3738
5284
|
}
|
|
3739
|
-
|
|
3740
|
-
|
|
3741
|
-
|
|
3742
|
-
|
|
3743
|
-
|
|
3744
|
-
|
|
5285
|
+
if (record.type !== "user" || record.isMeta) continue;
|
|
5286
|
+
if (!meta.timestamp) meta.timestamp = record.timestamp || "";
|
|
5287
|
+
const content = record.message?.content;
|
|
5288
|
+
let extracted = "";
|
|
5289
|
+
if (typeof content === "string") {
|
|
5290
|
+
extracted = cleanUserText(content);
|
|
5291
|
+
} else if (Array.isArray(content)) {
|
|
5292
|
+
for (const block of content) {
|
|
5293
|
+
if (block.type !== "text" || typeof block.text !== "string") continue;
|
|
5294
|
+
extracted = cleanUserText(block.text);
|
|
5295
|
+
if (extracted) break;
|
|
5296
|
+
}
|
|
3745
5297
|
}
|
|
3746
|
-
|
|
5298
|
+
if (extracted) {
|
|
5299
|
+
if (!meta.firstUserMessage) meta.firstUserMessage = extracted;
|
|
5300
|
+
meta.lastUserMessage = extracted;
|
|
5301
|
+
}
|
|
5302
|
+
meta.turnCount += 1;
|
|
5303
|
+
}
|
|
5304
|
+
return meta;
|
|
5305
|
+
}
|
|
5306
|
+
async function readLines(filePath, size) {
|
|
5307
|
+
if (size <= FULL_READ_LIMIT) {
|
|
5308
|
+
const content = await (0, import_promises4.readFile)(filePath, "utf-8");
|
|
5309
|
+
return { lines: content.split("\n").filter(Boolean), isPartialRead: false };
|
|
5310
|
+
}
|
|
5311
|
+
const handle = await (0, import_promises4.open)(filePath, "r");
|
|
5312
|
+
try {
|
|
5313
|
+
const buffer = Buffer.alloc(HEAD_READ_BYTES);
|
|
5314
|
+
const { bytesRead } = await handle.read(buffer, 0, HEAD_READ_BYTES, 0);
|
|
5315
|
+
const text = buffer.subarray(0, bytesRead).toString("utf-8");
|
|
5316
|
+
const lastNewline = text.lastIndexOf("\n");
|
|
3747
5317
|
return {
|
|
3748
|
-
|
|
3749
|
-
|
|
3750
|
-
gitBranch: meta.gitBranch,
|
|
3751
|
-
model: meta.model,
|
|
3752
|
-
slug: meta.slug,
|
|
3753
|
-
cwd: meta.cwd,
|
|
3754
|
-
firstUserMessage: meta.firstUserMessage,
|
|
3755
|
-
lastUserMessage: meta.lastUserMessage,
|
|
3756
|
-
timestamp: meta.timestamp,
|
|
3757
|
-
turnCount: meta.turnCount,
|
|
3758
|
-
lineCount: lines.length,
|
|
3759
|
-
branchedFrom: meta.branchedFrom
|
|
5318
|
+
lines: (lastNewline > 0 ? text.slice(0, lastNewline) : text).split("\n").filter(Boolean),
|
|
5319
|
+
isPartialRead: true
|
|
3760
5320
|
};
|
|
5321
|
+
} finally {
|
|
5322
|
+
await handle.close();
|
|
3761
5323
|
}
|
|
3762
|
-
|
|
3763
|
-
|
|
3764
|
-
|
|
3765
|
-
let
|
|
3766
|
-
|
|
3767
|
-
|
|
3768
|
-
|
|
3769
|
-
|
|
3770
|
-
let timestamp = "";
|
|
3771
|
-
let turnCount = 0;
|
|
3772
|
-
let branchedFrom;
|
|
3773
|
-
for (const line of lines) {
|
|
3774
|
-
try {
|
|
3775
|
-
const obj = JSON.parse(line);
|
|
3776
|
-
if (obj.sessionId && !sessionId) sessionId = obj.sessionId;
|
|
3777
|
-
if (obj.version && !version) version = obj.version;
|
|
3778
|
-
if (obj.gitBranch && !gitBranch) gitBranch = obj.gitBranch;
|
|
3779
|
-
if (obj.slug && !slug) slug = obj.slug;
|
|
3780
|
-
if (obj.cwd && !cwd) cwd = obj.cwd;
|
|
3781
|
-
if (obj.branchedFrom && !branchedFrom) branchedFrom = obj.branchedFrom;
|
|
3782
|
-
if (obj.type === "assistant" && obj.message?.model && !model) {
|
|
3783
|
-
model = obj.message.model;
|
|
3784
|
-
}
|
|
3785
|
-
if (obj.type === "user" && !obj.isMeta && !timestamp) {
|
|
3786
|
-
timestamp = obj.timestamp || "";
|
|
3787
|
-
}
|
|
3788
|
-
if (obj.type === "user" && !obj.isMeta) {
|
|
3789
|
-
const c = obj.message?.content;
|
|
3790
|
-
let extracted = "";
|
|
3791
|
-
if (typeof c === "string") {
|
|
3792
|
-
const cleaned = c.replace(/<[^>]+>[\s\S]*?<\/[^>]+>/g, "").trim();
|
|
3793
|
-
if (cleaned && cleaned.length > 5) extracted = cleaned.slice(0, 120);
|
|
3794
|
-
} else if (Array.isArray(c)) {
|
|
3795
|
-
for (const block of c) {
|
|
3796
|
-
if (block.type === "text") {
|
|
3797
|
-
const cleaned = block.text.replace(/<[^>]+>[\s\S]*?<\/[^>]+>/g, "").trim();
|
|
3798
|
-
if (cleaned && cleaned.length > 5) {
|
|
3799
|
-
extracted = cleaned.slice(0, 120);
|
|
3800
|
-
break;
|
|
3801
|
-
}
|
|
3802
|
-
}
|
|
3803
|
-
}
|
|
3804
|
-
}
|
|
3805
|
-
if (extracted) {
|
|
3806
|
-
if (!firstUserMessage) firstUserMessage = extracted;
|
|
3807
|
-
lastUserMessage = extracted;
|
|
3808
|
-
}
|
|
3809
|
-
turnCount++;
|
|
3810
|
-
}
|
|
3811
|
-
} catch {
|
|
3812
|
-
}
|
|
5324
|
+
}
|
|
5325
|
+
async function getSessionMeta(filePath) {
|
|
5326
|
+
const fileStat = await (0, import_promises4.stat)(filePath);
|
|
5327
|
+
let { lines, isPartialRead } = await readLines(filePath, fileStat.size);
|
|
5328
|
+
const format = formatForText(lines.join("\n"));
|
|
5329
|
+
if (isPartialRead && !format.descriptor.metadataFromHead) {
|
|
5330
|
+
lines = (await (0, import_promises4.readFile)(filePath, "utf-8")).split("\n").filter(Boolean);
|
|
5331
|
+
isPartialRead = false;
|
|
3813
5332
|
}
|
|
5333
|
+
const header = readHeader(format, lines);
|
|
5334
|
+
const conversation = header.turnCount === void 0 ? scanClaudeMessages(lines) : {
|
|
5335
|
+
firstUserMessage: header.firstUserMessage ?? "",
|
|
5336
|
+
lastUserMessage: header.lastUserMessage ?? "",
|
|
5337
|
+
timestamp: header.timestamp ?? "",
|
|
5338
|
+
turnCount: header.turnCount
|
|
5339
|
+
};
|
|
3814
5340
|
return {
|
|
3815
|
-
sessionId,
|
|
3816
|
-
version,
|
|
3817
|
-
gitBranch,
|
|
3818
|
-
model,
|
|
3819
|
-
slug,
|
|
3820
|
-
cwd,
|
|
3821
|
-
|
|
3822
|
-
|
|
3823
|
-
|
|
3824
|
-
|
|
3825
|
-
|
|
3826
|
-
branchedFrom
|
|
5341
|
+
sessionId: header.sessionId,
|
|
5342
|
+
version: header.version,
|
|
5343
|
+
gitBranch: header.gitBranch,
|
|
5344
|
+
model: header.model,
|
|
5345
|
+
slug: header.slug,
|
|
5346
|
+
cwd: header.cwd,
|
|
5347
|
+
...conversation,
|
|
5348
|
+
// A head read saw a known number of lines in a known number of bytes;
|
|
5349
|
+
// scaling that by the file size is the only estimate available.
|
|
5350
|
+
lineCount: isPartialRead ? Math.round(fileStat.size / (HEAD_READ_BYTES / lines.length)) : lines.length,
|
|
5351
|
+
branchedFrom: header.branchedFrom
|
|
3827
5352
|
};
|
|
3828
5353
|
}
|
|
3829
5354
|
async function getSessionStatus(filePath) {
|
|
@@ -3837,6 +5362,7 @@ async function getSessionStatus(filePath) {
|
|
|
3837
5362
|
const meaningful = [];
|
|
3838
5363
|
let cursor = fileStat.size;
|
|
3839
5364
|
let leftover = "";
|
|
5365
|
+
let copilotTurnEnded = false;
|
|
3840
5366
|
for (let chunk = 0; chunk < MAX_CHUNKS && cursor > 0; chunk++) {
|
|
3841
5367
|
const readSize = Math.min(CHUNK, cursor);
|
|
3842
5368
|
cursor -= readSize;
|
|
@@ -3855,6 +5381,10 @@ async function getSessionStatus(filePath) {
|
|
|
3855
5381
|
} catch {
|
|
3856
5382
|
continue;
|
|
3857
5383
|
}
|
|
5384
|
+
if (obj.type.startsWith("subagent.") && typeof obj.agentId === "string" && obj.agentId.length > 0) {
|
|
5385
|
+
meaningful.unshift(obj);
|
|
5386
|
+
continue;
|
|
5387
|
+
}
|
|
3858
5388
|
if (obj.type === "event_msg") {
|
|
3859
5389
|
const payload = obj.payload;
|
|
3860
5390
|
switch (payload?.type) {
|
|
@@ -3879,6 +5409,20 @@ async function getSessionStatus(filePath) {
|
|
|
3879
5409
|
if (role === "user") return { status: "processing" };
|
|
3880
5410
|
}
|
|
3881
5411
|
}
|
|
5412
|
+
if (obj.type === "abort" || obj.type === "user.message" || obj.type.startsWith("assistant.") || obj.type.startsWith("permission.") || obj.type.startsWith("session.") || obj.type.startsWith("tool.") || obj.type.startsWith("user_input.")) {
|
|
5413
|
+
if (typeof obj.agentId === "string" && obj.agentId) continue;
|
|
5414
|
+
meaningful.unshift(obj);
|
|
5415
|
+
if (obj.type === "assistant.turn_end") {
|
|
5416
|
+
copilotTurnEnded = true;
|
|
5417
|
+
continue;
|
|
5418
|
+
}
|
|
5419
|
+
if (copilotTurnEnded && obj.type !== "assistant.message" && obj.type !== "user.message") {
|
|
5420
|
+
continue;
|
|
5421
|
+
}
|
|
5422
|
+
const status = deriveSessionStatus(meaningful);
|
|
5423
|
+
if (status.status !== "idle") return status;
|
|
5424
|
+
continue;
|
|
5425
|
+
}
|
|
3882
5426
|
if (obj.type === "assistant" || obj.type === "user" || obj.type === "queue-operation") {
|
|
3883
5427
|
meaningful.unshift(obj);
|
|
3884
5428
|
const isEndTurn = obj.type === "assistant" && obj.message?.stop_reason === "end_turn";
|
|
@@ -3911,75 +5455,15 @@ function toSessionSummary(file, meta, status) {
|
|
|
3911
5455
|
turnCount: meta.turnCount,
|
|
3912
5456
|
status,
|
|
3913
5457
|
mtime: file.mtimeMs,
|
|
3914
|
-
|
|
3915
|
-
|
|
3916
|
-
}
|
|
3917
|
-
async function listCodexSessionFiles(cutoff) {
|
|
3918
|
-
const walk = async (dir, depth) => {
|
|
3919
|
-
if (depth > 4) return [];
|
|
3920
|
-
let entries;
|
|
3921
|
-
try {
|
|
3922
|
-
entries = await (0, import_promises5.readdir)(dir, { withFileTypes: true });
|
|
3923
|
-
} catch {
|
|
3924
|
-
return [];
|
|
3925
|
-
}
|
|
3926
|
-
const results = [];
|
|
3927
|
-
for (const entry of entries) {
|
|
3928
|
-
const filePath = (0, import_node_path6.join)(dir, entry.name);
|
|
3929
|
-
if (entry.isDirectory()) {
|
|
3930
|
-
results.push(...await walk(filePath, depth + 1));
|
|
3931
|
-
continue;
|
|
3932
|
-
}
|
|
3933
|
-
if (!entry.name.endsWith(".jsonl")) continue;
|
|
3934
|
-
try {
|
|
3935
|
-
const s = await (0, import_promises5.stat)(filePath);
|
|
3936
|
-
if (s.mtimeMs >= cutoff) results.push({ path: filePath, mtimeMs: s.mtimeMs });
|
|
3937
|
-
} catch {
|
|
3938
|
-
continue;
|
|
3939
|
-
}
|
|
3940
|
-
}
|
|
3941
|
-
return results;
|
|
5458
|
+
// Which root the transcript came out of, which is known before it is read.
|
|
5459
|
+
source: storeForPath(file.path)?.kind ?? descriptorFor("claude").kind
|
|
3942
5460
|
};
|
|
3943
|
-
return walk(dirs.CODEX_SESSIONS_DIR, 0);
|
|
3944
5461
|
}
|
|
3945
5462
|
async function listSessions(opts = {}) {
|
|
3946
5463
|
const limit = Math.min(Math.max(1, opts.limit ?? 20), 100);
|
|
3947
5464
|
const maxAgeMs = parseMaxAge(opts.maxAge ?? "7d");
|
|
3948
5465
|
const cutoff = Date.now() - maxAgeMs;
|
|
3949
|
-
|
|
3950
|
-
try {
|
|
3951
|
-
entries = await (0, import_promises5.readdir)(dirs.PROJECTS_DIR, { withFileTypes: true });
|
|
3952
|
-
} catch {
|
|
3953
|
-
return [];
|
|
3954
|
-
}
|
|
3955
|
-
const projectDirs = entries.filter((e) => e.isDirectory() && e.name !== "memory").map((e) => (0, import_node_path6.join)(dirs.PROJECTS_DIR, e.name));
|
|
3956
|
-
const [nested, codexFiles] = await Promise.all([
|
|
3957
|
-
Promise.all(
|
|
3958
|
-
projectDirs.map(async (projectDir) => {
|
|
3959
|
-
try {
|
|
3960
|
-
const files = await (0, import_promises5.readdir)(projectDir);
|
|
3961
|
-
const jsonlFiles = files.filter((f) => f.endsWith(".jsonl"));
|
|
3962
|
-
const statResults = await Promise.all(
|
|
3963
|
-
jsonlFiles.map(async (f) => {
|
|
3964
|
-
const filePath = (0, import_node_path6.join)(projectDir, f);
|
|
3965
|
-
try {
|
|
3966
|
-
const s = await (0, import_promises5.stat)(filePath);
|
|
3967
|
-
return s.mtimeMs >= cutoff ? { path: filePath, mtimeMs: s.mtimeMs } : null;
|
|
3968
|
-
} catch {
|
|
3969
|
-
return null;
|
|
3970
|
-
}
|
|
3971
|
-
})
|
|
3972
|
-
);
|
|
3973
|
-
return statResults.filter((r) => r !== null);
|
|
3974
|
-
} catch {
|
|
3975
|
-
return [];
|
|
3976
|
-
}
|
|
3977
|
-
})
|
|
3978
|
-
),
|
|
3979
|
-
listCodexSessionFiles(cutoff)
|
|
3980
|
-
]);
|
|
3981
|
-
const allFiles = [...nested.flat(), ...codexFiles];
|
|
3982
|
-
allFiles.sort((a, b) => b.mtimeMs - a.mtimeMs);
|
|
5466
|
+
const allFiles = listAllSessionFiles(cutoff).filter((file) => !file.isSubagent);
|
|
3983
5467
|
const results = [];
|
|
3984
5468
|
for (const file of allFiles) {
|
|
3985
5469
|
if (results.length >= limit) break;
|
|
@@ -3996,41 +5480,19 @@ async function listSessions(opts = {}) {
|
|
|
3996
5480
|
return results;
|
|
3997
5481
|
}
|
|
3998
5482
|
async function currentSession(cwd) {
|
|
3999
|
-
const
|
|
4000
|
-
|
|
4001
|
-
|
|
4002
|
-
|
|
4003
|
-
|
|
4004
|
-
|
|
4005
|
-
}
|
|
4006
|
-
const jsonlFiles = files.filter((f) => f.endsWith(".jsonl"));
|
|
4007
|
-
const [statResults, codexCandidates] = await Promise.all([
|
|
4008
|
-
Promise.all(
|
|
4009
|
-
jsonlFiles.map(async (f) => {
|
|
4010
|
-
const filePath = (0, import_node_path6.join)(projectDir, f);
|
|
4011
|
-
try {
|
|
4012
|
-
const s = await (0, import_promises5.stat)(filePath);
|
|
4013
|
-
return { path: filePath, mtimeMs: s.mtimeMs };
|
|
4014
|
-
} catch {
|
|
4015
|
-
return null;
|
|
4016
|
-
}
|
|
4017
|
-
})
|
|
4018
|
-
),
|
|
4019
|
-
listCodexSessionFiles(0)
|
|
4020
|
-
]);
|
|
4021
|
-
const codexMatches = [];
|
|
4022
|
-
for (const file of codexCandidates) {
|
|
5483
|
+
const claude2 = storeFor("claude");
|
|
5484
|
+
const projectDir = (0, import_node_path6.join)(claude2.root(), claude2.descriptor.dirName.encode(cwd));
|
|
5485
|
+
const direct = claude2.list(0, claude2.root()).filter((file) => !file.isSubagent && file.path.startsWith(projectDir + import_node_path6.sep));
|
|
5486
|
+
const searched = [];
|
|
5487
|
+
for (const file of listAllSessionFiles(0)) {
|
|
5488
|
+
if (file.isSubagent || storeForPath(file.path)?.kind === "claude") continue;
|
|
4023
5489
|
try {
|
|
4024
|
-
|
|
4025
|
-
if (meta2.cwd === cwd) codexMatches.push(file);
|
|
5490
|
+
if ((await getSessionMeta(file.path)).cwd === cwd) searched.push(file);
|
|
4026
5491
|
} catch {
|
|
4027
5492
|
continue;
|
|
4028
5493
|
}
|
|
4029
5494
|
}
|
|
4030
|
-
const valid = [
|
|
4031
|
-
...statResults.filter((r) => r !== null),
|
|
4032
|
-
...codexMatches
|
|
4033
|
-
];
|
|
5495
|
+
const valid = [...direct, ...searched];
|
|
4034
5496
|
if (valid.length === 0) return null;
|
|
4035
5497
|
valid.sort((a, b) => b.mtimeMs - a.mtimeMs);
|
|
4036
5498
|
const latest = valid[0];
|
|
@@ -4043,11 +5505,11 @@ async function currentSession(cwd) {
|
|
|
4043
5505
|
}
|
|
4044
5506
|
|
|
4045
5507
|
// src/commands/index-cmd.ts
|
|
4046
|
-
var
|
|
5508
|
+
var import_node_fs4 = require("node:fs");
|
|
4047
5509
|
var import_node_path7 = require("node:path");
|
|
4048
5510
|
async function indexStats(dbPath) {
|
|
4049
5511
|
const path = dbPath ?? DEFAULT_DB_PATH;
|
|
4050
|
-
if (!(0,
|
|
5512
|
+
if (!(0, import_node_fs4.existsSync)(path)) {
|
|
4051
5513
|
return { error: `Database not found at ${path}. Run 'cogpit-memory index rebuild' to create it.` };
|
|
4052
5514
|
}
|
|
4053
5515
|
const index = new SearchIndex(path);
|
|
@@ -4057,9 +5519,9 @@ async function indexStats(dbPath) {
|
|
|
4057
5519
|
}
|
|
4058
5520
|
async function indexRebuild(dbPath) {
|
|
4059
5521
|
const path = dbPath ?? DEFAULT_DB_PATH;
|
|
4060
|
-
(0,
|
|
5522
|
+
(0, import_node_fs4.mkdirSync)((0, import_node_path7.dirname)(path), { recursive: true });
|
|
4061
5523
|
const index = new SearchIndex(path);
|
|
4062
|
-
index.buildFull(
|
|
5524
|
+
index.buildFull();
|
|
4063
5525
|
const stats = index.getStats();
|
|
4064
5526
|
index.close();
|
|
4065
5527
|
return { status: "rebuilt", stats };
|