sidekick-shared 0.29.1 → 0.30.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/CHANGELOG.md +26 -0
- package/README.md +171 -60
- package/dist/aggregation/EventAggregator.d.ts +79 -4
- package/dist/aggregation/EventAggregator.js +391 -37
- package/dist/aggregation/types.d.ts +27 -3
- package/dist/browser.d.ts +7 -3
- package/dist/browser.js +23 -3
- package/dist/context/sessionContext.js +5 -1
- package/dist/esm/aggregation/EventAggregator.js +392 -38
- package/dist/esm/browser.js +3 -1
- package/dist/esm/context/sessionContext.js +6 -2
- package/dist/esm/fileContinuity.js +149 -0
- package/dist/esm/index.js +5 -2
- package/dist/esm/node.js +2 -0
- package/dist/esm/parsers/codexParser.js +192 -3
- package/dist/esm/parsers/subagentEndState.js +61 -0
- package/dist/esm/parsers/subagentScanner.js +4 -0
- package/dist/esm/parsers/subagentTraceParser.js +6 -0
- package/dist/esm/promptHistory.js +22 -11
- package/dist/esm/providers/claudeCode.js +50 -27
- package/dist/esm/providers/codex.js +153 -163
- package/dist/esm/providers/codexCompressedReader.js +184 -0
- package/dist/esm/providers/codexDatabase.js +47 -2
- package/dist/esm/providers/codexRollouts.js +20 -2
- package/dist/esm/providers/codexSubagents.js +456 -0
- package/dist/esm/providers/compressedRollouts.js +330 -0
- package/dist/esm/providers/factory.js +4 -1
- package/dist/esm/providers/rolloutWalker.js +32 -4
- package/dist/esm/schemas/sessionEvent.js +32 -4
- package/dist/esm/sessionMonitor.js +7 -1
- package/dist/esm/sessionPreviews.js +9 -0
- package/dist/esm/sessionPromptHistory.js +5 -3
- package/dist/esm/subagentActivity.js +125 -0
- package/dist/esm/subagentDisplayState.js +81 -0
- package/dist/esm/subagentSpawn.js +206 -6
- package/dist/esm/testing/codexSubagentFixtures.js +336 -0
- package/dist/esm/toolCallOrigins.js +17 -10
- package/dist/esm/toolCategory.js +6 -2
- package/dist/esm/transcript.js +6 -2
- package/dist/esm/turns/assistantTurn.js +6 -3
- package/dist/esm/usage/usageEvents.js +4 -1
- package/dist/esm/watchers/eventBridge.js +31 -1
- package/dist/esm/watchers/jsonlTail.js +11 -42
- package/dist/esm/watchers/jsonlWatcher.js +19 -9
- package/dist/esm/watchers/providerReaderWatcher.js +5 -4
- package/dist/fileContinuity.d.ts +95 -0
- package/dist/fileContinuity.js +187 -0
- package/dist/index.d.ts +14 -7
- package/dist/index.js +39 -10
- package/dist/node.d.ts +4 -0
- package/dist/node.js +16 -2
- package/dist/parsers/codexParser.d.ts +44 -0
- package/dist/parsers/codexParser.js +192 -3
- package/dist/parsers/subagentEndState.d.ts +20 -0
- package/dist/parsers/subagentEndState.js +65 -0
- package/dist/parsers/subagentScanner.js +4 -0
- package/dist/parsers/subagentTraceParser.d.ts +8 -0
- package/dist/parsers/subagentTraceParser.js +6 -0
- package/dist/promptHistory.d.ts +13 -3
- package/dist/promptHistory.js +22 -11
- package/dist/providers/claudeCode.js +50 -27
- package/dist/providers/codex.d.ts +35 -5
- package/dist/providers/codex.js +150 -160
- package/dist/providers/codexCompressedReader.d.ts +58 -0
- package/dist/providers/codexCompressedReader.js +221 -0
- package/dist/providers/codexDatabase.d.ts +25 -0
- package/dist/providers/codexDatabase.js +48 -2
- package/dist/providers/codexRollouts.d.ts +16 -2
- package/dist/providers/codexRollouts.js +20 -2
- package/dist/providers/codexSubagents.d.ts +66 -0
- package/dist/providers/codexSubagents.js +495 -0
- package/dist/providers/compressedRollouts.d.ts +114 -0
- package/dist/providers/compressedRollouts.js +380 -0
- package/dist/providers/factory.d.ts +5 -0
- package/dist/providers/factory.js +4 -1
- package/dist/providers/rolloutWalker.d.ts +15 -2
- package/dist/providers/rolloutWalker.js +33 -4
- package/dist/providers/types.d.ts +22 -1
- package/dist/schemas/sessionEvent.d.ts +26 -2
- package/dist/schemas/sessionEvent.js +31 -3
- package/dist/sessionMonitor.d.ts +6 -0
- package/dist/sessionMonitor.js +7 -1
- package/dist/sessionPreviews.js +9 -0
- package/dist/sessionPromptHistory.d.ts +5 -0
- package/dist/sessionPromptHistory.js +5 -36
- package/dist/subagentActivity.d.ts +91 -0
- package/dist/subagentActivity.js +132 -0
- package/dist/subagentDisplayState.d.ts +81 -0
- package/dist/subagentDisplayState.js +86 -0
- package/dist/subagentSpawn.d.ts +100 -5
- package/dist/subagentSpawn.js +218 -7
- package/dist/testing/codexSubagentFixtures.d.ts +48 -0
- package/dist/testing/codexSubagentFixtures.js +344 -0
- package/dist/toolCallOrigins.d.ts +5 -0
- package/dist/toolCallOrigins.js +17 -10
- package/dist/toolCategory.js +6 -2
- package/dist/transcript.js +5 -1
- package/dist/turns/assistantTurn.d.ts +4 -1
- package/dist/turns/assistantTurn.js +5 -2
- package/dist/types/codex.d.ts +71 -2
- package/dist/types/sessionEvent.d.ts +38 -3
- package/dist/usage/usageEvents.d.ts +4 -1
- package/dist/usage/usageEvents.js +4 -1
- package/dist/watchers/eventBridge.js +30 -0
- package/dist/watchers/jsonlTail.js +11 -42
- package/dist/watchers/jsonlWatcher.d.ts +5 -1
- package/dist/watchers/jsonlWatcher.js +18 -8
- package/dist/watchers/providerReaderWatcher.js +5 -4
- package/package.json +1 -1
|
@@ -0,0 +1,495 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Codex subagent discovery for `CodexProvider.scanSubagents()`.
|
|
4
|
+
*
|
|
5
|
+
* Every spawned thread below a session (children, their children, …), each
|
|
6
|
+
* with its rollout, the parent's spawn call that started it, its nickname and
|
|
7
|
+
* agent path, and stats from the child's own records. A forked child's
|
|
8
|
+
* rollout embeds the parent's session_meta and history before
|
|
9
|
+
* `session_meta.subagent_history_start_ordinal`; that prefix is the parent's
|
|
10
|
+
* and is not counted (the parser skips it, and so do the timestamps here).
|
|
11
|
+
*
|
|
12
|
+
* The spawn link comes from the parent's rollout: the `item_completed` →
|
|
13
|
+
* `SubAgentActivity` record with `kind: 'started'` names the spawn call id
|
|
14
|
+
* and the child thread. When a parent has no such record (older Codex), the
|
|
15
|
+
* spawn call whose output named the child's agent path
|
|
16
|
+
* (`{"task_name":"/root/x"}`) is used. Parent scans are cached and read
|
|
17
|
+
* incrementally as the parent rollout grows.
|
|
18
|
+
*
|
|
19
|
+
* Node-only: reads files.
|
|
20
|
+
*
|
|
21
|
+
* @module providers/codexSubagents
|
|
22
|
+
*/
|
|
23
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
24
|
+
if (k2 === undefined) k2 = k;
|
|
25
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
26
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
27
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
28
|
+
}
|
|
29
|
+
Object.defineProperty(o, k2, desc);
|
|
30
|
+
}) : (function(o, m, k, k2) {
|
|
31
|
+
if (k2 === undefined) k2 = k;
|
|
32
|
+
o[k2] = m[k];
|
|
33
|
+
}));
|
|
34
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
35
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
36
|
+
}) : function(o, v) {
|
|
37
|
+
o["default"] = v;
|
|
38
|
+
});
|
|
39
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
40
|
+
var ownKeys = function(o) {
|
|
41
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
42
|
+
var ar = [];
|
|
43
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
44
|
+
return ar;
|
|
45
|
+
};
|
|
46
|
+
return ownKeys(o);
|
|
47
|
+
};
|
|
48
|
+
return function (mod) {
|
|
49
|
+
if (mod && mod.__esModule) return mod;
|
|
50
|
+
var result = {};
|
|
51
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
52
|
+
__setModuleDefault(result, mod);
|
|
53
|
+
return result;
|
|
54
|
+
};
|
|
55
|
+
})();
|
|
56
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
57
|
+
exports.clearCodexSpawnLinkCache = clearCodexSpawnLinkCache;
|
|
58
|
+
exports.readCodexSpawnLinks = readCodexSpawnLinks;
|
|
59
|
+
exports.buildCodexSubagentStats = buildCodexSubagentStats;
|
|
60
|
+
exports.discoverCodexSubagents = discoverCodexSubagents;
|
|
61
|
+
const fs = __importStar(require("fs"));
|
|
62
|
+
const path = __importStar(require("path"));
|
|
63
|
+
const codexParser_1 = require("../parsers/codexParser");
|
|
64
|
+
const subagentStatsCache_1 = require("../parsers/subagentStatsCache");
|
|
65
|
+
const usageNormalization_1 = require("../usageNormalization");
|
|
66
|
+
const subagentUsage_1 = require("../usage/subagentUsage");
|
|
67
|
+
const codexRolloutOrigin_1 = require("../parsers/codexRolloutOrigin");
|
|
68
|
+
const codexRollouts_1 = require("./codexRollouts");
|
|
69
|
+
const compressedRollouts_1 = require("./compressedRollouts");
|
|
70
|
+
const rolloutWalker_1 = require("./rolloutWalker");
|
|
71
|
+
const SPAWN_LINK_CACHE_LIMIT = 256;
|
|
72
|
+
const PENDING_SPAWN_LIMIT = 1024;
|
|
73
|
+
const spawnLinkCache = new Map();
|
|
74
|
+
/** @internal Test hook. */
|
|
75
|
+
function clearCodexSpawnLinkCache() {
|
|
76
|
+
spawnLinkCache.clear();
|
|
77
|
+
}
|
|
78
|
+
function freshEntry(size, mtimeMs) {
|
|
79
|
+
return {
|
|
80
|
+
size,
|
|
81
|
+
mtimeMs,
|
|
82
|
+
offset: 0,
|
|
83
|
+
historyStart: undefined,
|
|
84
|
+
skipBelow: null,
|
|
85
|
+
pendingSpawns: new Set(),
|
|
86
|
+
links: { started: new Map(), spawnedPaths: new Map() },
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
function historyStartOf(meta) {
|
|
90
|
+
const value = meta?.subagent_history_start_ordinal;
|
|
91
|
+
return typeof value === 'number' && Number.isInteger(value) && value > 0 ? value : null;
|
|
92
|
+
}
|
|
93
|
+
function record(value) {
|
|
94
|
+
return value && typeof value === 'object' && !Array.isArray(value)
|
|
95
|
+
? value
|
|
96
|
+
: undefined;
|
|
97
|
+
}
|
|
98
|
+
function nonEmpty(value) {
|
|
99
|
+
return typeof value === 'string' && value.length > 0 ? value : undefined;
|
|
100
|
+
}
|
|
101
|
+
/** Applies one rollout line to a spawn-link scan; most lines are rejected by substring. */
|
|
102
|
+
function scanLine(entry, line) {
|
|
103
|
+
const isMeta = line.includes('"session_meta"');
|
|
104
|
+
const isActivity = line.includes('"SubAgentActivity"');
|
|
105
|
+
const isSpawnCall = line.includes('"spawn_agent"') && line.includes('"function_call"');
|
|
106
|
+
const isOutput = entry.pendingSpawns.size > 0 && line.includes('"function_call_output"');
|
|
107
|
+
if (!isMeta && !isActivity && !isSpawnCall && !isOutput)
|
|
108
|
+
return;
|
|
109
|
+
let parsed;
|
|
110
|
+
try {
|
|
111
|
+
parsed = JSON.parse(line);
|
|
112
|
+
}
|
|
113
|
+
catch {
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
const payload = record(parsed.payload);
|
|
117
|
+
if (!payload)
|
|
118
|
+
return;
|
|
119
|
+
if (parsed.type === 'session_meta') {
|
|
120
|
+
// As the parser: the own meta comes first; an embedded ancestor meta
|
|
121
|
+
// below the history start makes the records below it the parent's.
|
|
122
|
+
if (entry.historyStart === undefined) {
|
|
123
|
+
entry.historyStart = historyStartOf(payload);
|
|
124
|
+
}
|
|
125
|
+
else if (entry.skipBelow === null &&
|
|
126
|
+
typeof entry.historyStart === 'number' &&
|
|
127
|
+
typeof parsed.ordinal === 'number' &&
|
|
128
|
+
parsed.ordinal < entry.historyStart) {
|
|
129
|
+
entry.skipBelow = entry.historyStart;
|
|
130
|
+
}
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
const skipBelow = entry.skipBelow;
|
|
134
|
+
if (skipBelow !== null && typeof parsed.ordinal === 'number' && parsed.ordinal < skipBelow) {
|
|
135
|
+
return; // the parent's history, embedded in a forked child
|
|
136
|
+
}
|
|
137
|
+
if (parsed.type === 'event_msg' && payload.type === 'item_completed') {
|
|
138
|
+
const item = record(payload.item);
|
|
139
|
+
if (item?.type !== 'SubAgentActivity' || item.kind !== 'started')
|
|
140
|
+
return;
|
|
141
|
+
const threadId = nonEmpty(item.agent_thread_id);
|
|
142
|
+
const callId = nonEmpty(item.id);
|
|
143
|
+
if (!threadId || !callId)
|
|
144
|
+
return;
|
|
145
|
+
const agentPath = nonEmpty(item.agent_path);
|
|
146
|
+
entry.links.started.set(threadId, { callId, ...(agentPath ? { agentPath } : {}) });
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
if (parsed.type !== 'response_item')
|
|
150
|
+
return;
|
|
151
|
+
const callId = nonEmpty(payload.call_id);
|
|
152
|
+
if (!callId)
|
|
153
|
+
return;
|
|
154
|
+
if (payload.type === 'function_call' && payload.name === 'spawn_agent') {
|
|
155
|
+
entry.pendingSpawns.add(callId);
|
|
156
|
+
while (entry.pendingSpawns.size > PENDING_SPAWN_LIMIT) {
|
|
157
|
+
entry.pendingSpawns.delete(entry.pendingSpawns.values().next().value);
|
|
158
|
+
}
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
if (payload.type === 'function_call_output' && entry.pendingSpawns.has(callId)) {
|
|
162
|
+
entry.pendingSpawns.delete(callId);
|
|
163
|
+
let output;
|
|
164
|
+
try {
|
|
165
|
+
output = JSON.parse(String(payload.output));
|
|
166
|
+
}
|
|
167
|
+
catch {
|
|
168
|
+
return; // e.g. "collab spawn failed: …"
|
|
169
|
+
}
|
|
170
|
+
const taskName = nonEmpty(record(output)?.task_name);
|
|
171
|
+
if (!taskName)
|
|
172
|
+
return;
|
|
173
|
+
const calls = entry.links.spawnedPaths.get(taskName) ?? [];
|
|
174
|
+
calls.push(callId);
|
|
175
|
+
entry.links.spawnedPaths.set(taskName, calls);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
function remember(filePath, entry) {
|
|
179
|
+
spawnLinkCache.delete(filePath);
|
|
180
|
+
spawnLinkCache.set(filePath, entry);
|
|
181
|
+
while (spawnLinkCache.size > SPAWN_LINK_CACHE_LIMIT) {
|
|
182
|
+
spawnLinkCache.delete(spawnLinkCache.keys().next().value);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* The spawn calls a rollout records (see {@link CodexSpawnLinks}). Cached per
|
|
187
|
+
* path; a growing `.jsonl` is read only past what was read before, a
|
|
188
|
+
* compressed one whole. `null` when the rollout cannot be read.
|
|
189
|
+
*/
|
|
190
|
+
function readCodexSpawnLinks(filePath, options = {}) {
|
|
191
|
+
let stat;
|
|
192
|
+
try {
|
|
193
|
+
stat = fs.statSync(filePath);
|
|
194
|
+
}
|
|
195
|
+
catch {
|
|
196
|
+
spawnLinkCache.delete(filePath);
|
|
197
|
+
return null;
|
|
198
|
+
}
|
|
199
|
+
let entry = spawnLinkCache.get(filePath);
|
|
200
|
+
if (entry && entry.size === stat.size && entry.mtimeMs === stat.mtimeMs) {
|
|
201
|
+
remember(filePath, entry);
|
|
202
|
+
return entry.links;
|
|
203
|
+
}
|
|
204
|
+
if ((0, compressedRollouts_1.isCompressedRolloutPath)(filePath)) {
|
|
205
|
+
const content = (0, compressedRollouts_1.readRolloutBuffer)(filePath, options);
|
|
206
|
+
if (!content)
|
|
207
|
+
return null;
|
|
208
|
+
entry = freshEntry(stat.size, stat.mtimeMs);
|
|
209
|
+
for (const line of content.toString('utf8').split('\n'))
|
|
210
|
+
if (line)
|
|
211
|
+
scanLine(entry, line);
|
|
212
|
+
remember(filePath, entry);
|
|
213
|
+
return entry.links;
|
|
214
|
+
}
|
|
215
|
+
// A rollout that shrank was replaced: scan it again from the start.
|
|
216
|
+
if (!entry || stat.size < entry.offset || stat.size < entry.size) {
|
|
217
|
+
entry = freshEntry(stat.size, stat.mtimeMs);
|
|
218
|
+
}
|
|
219
|
+
let fd;
|
|
220
|
+
try {
|
|
221
|
+
fd = fs.openSync(filePath, 'r');
|
|
222
|
+
}
|
|
223
|
+
catch {
|
|
224
|
+
return null;
|
|
225
|
+
}
|
|
226
|
+
try {
|
|
227
|
+
const length = stat.size - entry.offset;
|
|
228
|
+
if (length > 0) {
|
|
229
|
+
const buffer = Buffer.alloc(length);
|
|
230
|
+
const bytesRead = fs.readSync(fd, buffer, 0, length, entry.offset);
|
|
231
|
+
const bytes = buffer.subarray(0, bytesRead);
|
|
232
|
+
const lastNewline = bytes.lastIndexOf(0x0a);
|
|
233
|
+
if (lastNewline >= 0) {
|
|
234
|
+
for (const line of bytes.toString('utf8', 0, lastNewline).split('\n')) {
|
|
235
|
+
if (line)
|
|
236
|
+
scanLine(entry, line);
|
|
237
|
+
}
|
|
238
|
+
entry.offset += lastNewline + 1;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
catch {
|
|
243
|
+
return null;
|
|
244
|
+
}
|
|
245
|
+
finally {
|
|
246
|
+
fs.closeSync(fd);
|
|
247
|
+
}
|
|
248
|
+
entry.size = stat.size;
|
|
249
|
+
entry.mtimeMs = stat.mtimeMs;
|
|
250
|
+
remember(filePath, entry);
|
|
251
|
+
return entry.links;
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* Stats of one subagent rollout from its own records: tool calls, tokens,
|
|
255
|
+
* and first/last timestamps after the embedded parent history (the child's
|
|
256
|
+
* own `session_meta` counts). Reads `.jsonl.zst` when the runtime can.
|
|
257
|
+
* `null` when the rollout is missing or cannot be read.
|
|
258
|
+
*/
|
|
259
|
+
function buildCodexSubagentStats(agentId, rolloutPath, description, options = {}) {
|
|
260
|
+
if (!rolloutPath || !fs.existsSync(rolloutPath))
|
|
261
|
+
return null;
|
|
262
|
+
const content = (0, compressedRollouts_1.readRolloutText)(rolloutPath, options);
|
|
263
|
+
if (content === null)
|
|
264
|
+
return null;
|
|
265
|
+
const stats = {
|
|
266
|
+
agentId,
|
|
267
|
+
description: description || undefined,
|
|
268
|
+
toolCalls: [],
|
|
269
|
+
inputTokens: 0,
|
|
270
|
+
outputTokens: 0,
|
|
271
|
+
};
|
|
272
|
+
const parser = new codexParser_1.CodexRolloutParser();
|
|
273
|
+
let sawMeta = false;
|
|
274
|
+
// The child's last own turn boundary: `task_complete` means it ended cleanly.
|
|
275
|
+
let lastTurnEvent;
|
|
276
|
+
for (const line of content.split('\n')) {
|
|
277
|
+
const trimmed = line.trim();
|
|
278
|
+
if (!trimmed)
|
|
279
|
+
continue;
|
|
280
|
+
let parsed;
|
|
281
|
+
try {
|
|
282
|
+
parsed = JSON.parse(trimmed);
|
|
283
|
+
}
|
|
284
|
+
catch {
|
|
285
|
+
continue;
|
|
286
|
+
}
|
|
287
|
+
let events;
|
|
288
|
+
try {
|
|
289
|
+
events = parser.convertLine(parsed);
|
|
290
|
+
}
|
|
291
|
+
catch {
|
|
292
|
+
continue; // Skip lines the parser rejects.
|
|
293
|
+
}
|
|
294
|
+
// Timestamps of the child's own records only, as the parser decides them.
|
|
295
|
+
let own;
|
|
296
|
+
if (parsed.type === 'session_meta') {
|
|
297
|
+
own = !sawMeta;
|
|
298
|
+
sawMeta = true;
|
|
299
|
+
}
|
|
300
|
+
else {
|
|
301
|
+
const end = parser.getInheritedHistoryEnd();
|
|
302
|
+
own = !(end !== null && typeof parsed.ordinal === 'number' && parsed.ordinal < end);
|
|
303
|
+
}
|
|
304
|
+
if (own && parsed.type === 'event_msg') {
|
|
305
|
+
const payloadType = parsed.payload?.type;
|
|
306
|
+
if (payloadType === 'task_started' ||
|
|
307
|
+
payloadType === 'task_complete' ||
|
|
308
|
+
payloadType === 'turn_aborted') {
|
|
309
|
+
lastTurnEvent = payloadType;
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
if (own && parsed.timestamp) {
|
|
313
|
+
const ts = new Date(parsed.timestamp);
|
|
314
|
+
if (!Number.isNaN(ts.getTime())) {
|
|
315
|
+
if (!stats.startTime || ts < stats.startTime)
|
|
316
|
+
stats.startTime = ts;
|
|
317
|
+
if (!stats.endTime || ts > stats.endTime)
|
|
318
|
+
stats.endTime = ts;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
for (const event of events) {
|
|
322
|
+
if (event.type === 'assistant' && Array.isArray(event.message.content)) {
|
|
323
|
+
for (const block of event.message.content) {
|
|
324
|
+
if (block.type === 'tool_use' && typeof block.name === 'string') {
|
|
325
|
+
stats.toolCalls.push({
|
|
326
|
+
name: block.name,
|
|
327
|
+
input: block.input || {},
|
|
328
|
+
timestamp: new Date(event.timestamp),
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
// Duplicate token_count events are already dropped by the parser.
|
|
334
|
+
const usage = (0, usageNormalization_1.extractNormalizedUsage)(event);
|
|
335
|
+
if (usage)
|
|
336
|
+
(0, subagentUsage_1.addUsageToSubagent)(stats, usage);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
if (stats.startTime && stats.endTime) {
|
|
340
|
+
stats.durationMs = stats.endTime.getTime() - stats.startTime.getTime();
|
|
341
|
+
}
|
|
342
|
+
if (lastTurnEvent)
|
|
343
|
+
stats.endedCleanly = lastTurnEvent === 'task_complete';
|
|
344
|
+
return stats;
|
|
345
|
+
}
|
|
346
|
+
function spawnSource(source) {
|
|
347
|
+
let value = source;
|
|
348
|
+
if (typeof value === 'string') {
|
|
349
|
+
if (!value.startsWith('{'))
|
|
350
|
+
return undefined;
|
|
351
|
+
try {
|
|
352
|
+
value = JSON.parse(value);
|
|
353
|
+
}
|
|
354
|
+
catch {
|
|
355
|
+
return undefined;
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
return record(record(record(value)?.subagent)?.thread_spawn);
|
|
359
|
+
}
|
|
360
|
+
function childFromDbThread(thread, parentId, depth) {
|
|
361
|
+
const spawn = spawnSource(thread.source);
|
|
362
|
+
return {
|
|
363
|
+
id: thread.id,
|
|
364
|
+
parentId,
|
|
365
|
+
depth,
|
|
366
|
+
rolloutPath: thread.rollout_path,
|
|
367
|
+
description: thread.title || thread.agent_nickname,
|
|
368
|
+
agentType: thread.agent_role,
|
|
369
|
+
agentNickname: thread.agent_nickname ?? nonEmpty(spawn?.agent_nickname),
|
|
370
|
+
agentPath: thread.agent_path ?? nonEmpty(spawn?.agent_path),
|
|
371
|
+
};
|
|
372
|
+
}
|
|
373
|
+
/** Children below `sessionId` from the state database; empty when it knows none. */
|
|
374
|
+
function childrenFromDb(db, sessionId) {
|
|
375
|
+
const children = [];
|
|
376
|
+
const seen = new Set();
|
|
377
|
+
for (const spawned of db.getSpawnedDescendantThreads(sessionId)) {
|
|
378
|
+
seen.add(spawned.thread.id);
|
|
379
|
+
children.push(childFromDbThread(spawned.thread, spawned.parentId, spawned.depth));
|
|
380
|
+
}
|
|
381
|
+
// Older schemas record forks in `forked_from_id` (direct children only).
|
|
382
|
+
for (const thread of db.getThreadsByForkedFromId(sessionId)) {
|
|
383
|
+
if (seen.has(thread.id))
|
|
384
|
+
continue;
|
|
385
|
+
seen.add(thread.id);
|
|
386
|
+
children.push(childFromDbThread(thread, sessionId, 1));
|
|
387
|
+
}
|
|
388
|
+
return children;
|
|
389
|
+
}
|
|
390
|
+
/** Children below `sessionId` among recent rollouts, by their session_meta. */
|
|
391
|
+
function childrenFromFiles(files, sessionId) {
|
|
392
|
+
const candidates = files
|
|
393
|
+
.map((file) => ({ file, meta: (0, codexRollouts_1.readSessionMeta)(file.path) }))
|
|
394
|
+
.filter((entry) => !!entry.meta);
|
|
395
|
+
const depthOf = new Map([[sessionId, 0]]);
|
|
396
|
+
const children = [];
|
|
397
|
+
let added = true;
|
|
398
|
+
while (added) {
|
|
399
|
+
added = false;
|
|
400
|
+
for (const { file, meta } of candidates) {
|
|
401
|
+
const id = (0, rolloutWalker_1.extractRolloutSessionId)(path.basename(file.path));
|
|
402
|
+
if (depthOf.has(id))
|
|
403
|
+
continue;
|
|
404
|
+
const parentId = meta.forked_from_id ?? (0, codexRolloutOrigin_1.codexSpawnParentFromSource)(meta.source);
|
|
405
|
+
const parentDepth = parentId !== undefined ? depthOf.get(parentId) : undefined;
|
|
406
|
+
if (parentId === undefined || parentDepth === undefined)
|
|
407
|
+
continue;
|
|
408
|
+
depthOf.set(id, parentDepth + 1);
|
|
409
|
+
added = true;
|
|
410
|
+
const spawn = spawnSource(meta.source);
|
|
411
|
+
// `source` is a plain string for top-level sessions but an object
|
|
412
|
+
// (`{ subagent: { thread_spawn: … } }`) for spawned children.
|
|
413
|
+
const description = meta.agent_nickname || (typeof meta.source === 'string' ? meta.source : undefined);
|
|
414
|
+
children.push({
|
|
415
|
+
id,
|
|
416
|
+
parentId,
|
|
417
|
+
depth: parentDepth + 1,
|
|
418
|
+
rolloutPath: file.path,
|
|
419
|
+
description,
|
|
420
|
+
agentType: meta.agent_role ?? nonEmpty(spawn?.agent_role),
|
|
421
|
+
agentNickname: meta.agent_nickname ?? nonEmpty(spawn?.agent_nickname),
|
|
422
|
+
agentPath: meta.agent_path ?? nonEmpty(spawn?.agent_path),
|
|
423
|
+
});
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
return children;
|
|
427
|
+
}
|
|
428
|
+
/**
|
|
429
|
+
* Every subagent below a session with links and own-record stats. Children
|
|
430
|
+
* whose rollout is gone (or compressed, without `includeCompressed`) are
|
|
431
|
+
* left out, as before.
|
|
432
|
+
*/
|
|
433
|
+
function discoverCodexSubagents(sessionId, options) {
|
|
434
|
+
const readOptions = { onDiagnostic: options.onDiagnostic };
|
|
435
|
+
let children = options.db ? childrenFromDb(options.db, sessionId) : [];
|
|
436
|
+
if (children.length === 0)
|
|
437
|
+
children = childrenFromFiles(options.fallbackFiles(), sessionId);
|
|
438
|
+
if (children.length === 0)
|
|
439
|
+
return [];
|
|
440
|
+
const pathOf = new Map();
|
|
441
|
+
for (const child of children) {
|
|
442
|
+
pathOf.set(child.id, (0, compressedRollouts_1.resolveRolloutPath)(child.rolloutPath, { includeCompressed: options.includeCompressed }));
|
|
443
|
+
}
|
|
444
|
+
const linksOf = new Map();
|
|
445
|
+
const claimed = new Map();
|
|
446
|
+
const links = (parentId) => {
|
|
447
|
+
if (!linksOf.has(parentId)) {
|
|
448
|
+
const parentPath = parentId === sessionId ? options.sessionRolloutPath() : (pathOf.get(parentId) ?? null);
|
|
449
|
+
linksOf.set(parentId, parentPath ? readCodexSpawnLinks(parentPath, readOptions) : null);
|
|
450
|
+
}
|
|
451
|
+
return linksOf.get(parentId) ?? null;
|
|
452
|
+
};
|
|
453
|
+
// Started links first, so the agent-path fallback never takes a claimed call.
|
|
454
|
+
const linked = new Map();
|
|
455
|
+
for (const child of children) {
|
|
456
|
+
const callId = links(child.parentId)?.started.get(child.id)?.callId;
|
|
457
|
+
if (!callId)
|
|
458
|
+
continue;
|
|
459
|
+
linked.set(child.id, callId);
|
|
460
|
+
const set = claimed.get(child.parentId) ?? new Set();
|
|
461
|
+
set.add(callId);
|
|
462
|
+
claimed.set(child.parentId, set);
|
|
463
|
+
}
|
|
464
|
+
const results = [];
|
|
465
|
+
for (const child of children) {
|
|
466
|
+
const filePath = pathOf.get(child.id);
|
|
467
|
+
if (!filePath)
|
|
468
|
+
continue;
|
|
469
|
+
const built = (0, subagentStatsCache_1.cachedSubagentStats)(filePath, () => buildCodexSubagentStats(child.id, filePath, child.description, readOptions));
|
|
470
|
+
if (!built)
|
|
471
|
+
continue;
|
|
472
|
+
let spawnToolUseId = linked.get(child.id);
|
|
473
|
+
const parentLinks = links(child.parentId);
|
|
474
|
+
const agentPath = child.agentPath ?? parentLinks?.started.get(child.id)?.agentPath;
|
|
475
|
+
if (!spawnToolUseId && agentPath && parentLinks) {
|
|
476
|
+
const used = claimed.get(child.parentId) ?? new Set();
|
|
477
|
+
spawnToolUseId = parentLinks.spawnedPaths.get(agentPath)?.find((id) => !used.has(id));
|
|
478
|
+
if (spawnToolUseId) {
|
|
479
|
+
used.add(spawnToolUseId);
|
|
480
|
+
claimed.set(child.parentId, used);
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
results.push({
|
|
484
|
+
...built,
|
|
485
|
+
...(child.agentType && !built.agentType ? { agentType: child.agentType } : {}),
|
|
486
|
+
filePath,
|
|
487
|
+
...(spawnToolUseId ? { spawnToolUseId } : {}),
|
|
488
|
+
...(child.agentNickname ? { agentNickname: child.agentNickname } : {}),
|
|
489
|
+
...(agentPath ? { agentPath } : {}),
|
|
490
|
+
parentAgentId: child.parentId,
|
|
491
|
+
depth: child.depth,
|
|
492
|
+
});
|
|
493
|
+
}
|
|
494
|
+
return results;
|
|
495
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reads Codex rollouts that Codex compressed.
|
|
3
|
+
*
|
|
4
|
+
* Codex compresses older rollouts in place to `rollout-<ts>-<id>.jsonl.zst`
|
|
5
|
+
* (one Zstandard frame), while its state database keeps the `.jsonl` path.
|
|
6
|
+
* Node ships `zlib.zstdDecompressSync` from 22.15 / 23.8; on older runtimes
|
|
7
|
+
* (Node 20, older VS Code Electron builds) compressed rollouts cannot be read
|
|
8
|
+
* and are skipped, reported once as a `read_failed` diagnostic.
|
|
9
|
+
*
|
|
10
|
+
* Every reader here takes either form: a `.jsonl` path reads as before, a
|
|
11
|
+
* `.jsonl.zst` path is decompressed. Compressed rollouts are complete (Codex
|
|
12
|
+
* only compresses rollouts it no longer writes), so they are read whole and
|
|
13
|
+
* never tailed.
|
|
14
|
+
*
|
|
15
|
+
* Node-only: reads files.
|
|
16
|
+
*
|
|
17
|
+
* @module providers/compressedRollouts
|
|
18
|
+
*/
|
|
19
|
+
import type { SessionProviderDiagnostic } from './types';
|
|
20
|
+
/** File-name suffix of a compressed rollout. */
|
|
21
|
+
export declare const COMPRESSED_ROLLOUT_SUFFIX = ".jsonl.zst";
|
|
22
|
+
type ZstdDecompressSync = (input: Buffer) => Buffer;
|
|
23
|
+
/** Test hooks; not part of the public entry points. */
|
|
24
|
+
export declare const __compressedRolloutTesting: {
|
|
25
|
+
/** Replace the runtime's `zstdDecompressSync` (`null` = unavailable, `undefined` = restore). */
|
|
26
|
+
setDecompress(fn: ZstdDecompressSync | null | undefined): void;
|
|
27
|
+
};
|
|
28
|
+
/** Whether a path (or file name) names a compressed rollout (`*.jsonl.zst`). */
|
|
29
|
+
export declare function isCompressedRolloutPath(filePath: string): boolean;
|
|
30
|
+
/** Whether this runtime can decompress Zstandard (`zlib.zstdDecompressSync`). */
|
|
31
|
+
export declare function isZstdDecompressionAvailable(): boolean;
|
|
32
|
+
export interface ReadRolloutOptions {
|
|
33
|
+
/**
|
|
34
|
+
* Receives a content-safe `read_failed` diagnostic when a compressed
|
|
35
|
+
* rollout cannot be read (no Zstandard support, or a corrupt frame).
|
|
36
|
+
*/
|
|
37
|
+
onDiagnostic?: (diagnostic: SessionProviderDiagnostic) => void;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Whether compressed rollouts can be read here; when they cannot, reports
|
|
41
|
+
* the runtime limit through `onDiagnostic` (once per process). Callers that
|
|
42
|
+
* list rollouts use it to leave `.jsonl.zst` files out instead of listing
|
|
43
|
+
* sessions they cannot read.
|
|
44
|
+
*/
|
|
45
|
+
export declare function canReadCompressedRollouts(options?: ReadRolloutOptions): boolean;
|
|
46
|
+
/** @internal Test hook: report the runtime limit again. */
|
|
47
|
+
export declare function resetCompressedRolloutDiagnostics(): void;
|
|
48
|
+
/**
|
|
49
|
+
* The whole content of a rollout: a `.jsonl` file as stored, a `.jsonl.zst`
|
|
50
|
+
* file decompressed. `null` when the file cannot be read or decompressed
|
|
51
|
+
* (the latter reported through `onDiagnostic`).
|
|
52
|
+
*/
|
|
53
|
+
export declare function readRolloutBuffer(filePath: string, options?: ReadRolloutOptions): Buffer | null;
|
|
54
|
+
/** {@link readRolloutBuffer} decoded as UTF-8. */
|
|
55
|
+
export declare function readRolloutText(filePath: string, options?: ReadRolloutOptions): string | null;
|
|
56
|
+
/** Async twin of {@link readRolloutBuffer}; decompression itself is synchronous. */
|
|
57
|
+
export declare function readRolloutBufferAsync(filePath: string, options?: ReadRolloutOptions): Promise<Buffer | null>;
|
|
58
|
+
/**
|
|
59
|
+
* Up to `maxBytes` of a rollout's content from its start (decompressed for
|
|
60
|
+
* a `.jsonl.zst`). An empty buffer when the file cannot be read.
|
|
61
|
+
*/
|
|
62
|
+
export declare function readRolloutHead(filePath: string, maxBytes: number, options?: ReadRolloutOptions): Buffer;
|
|
63
|
+
/** Async twin of {@link readRolloutHead}. */
|
|
64
|
+
export declare function readRolloutHeadAsync(filePath: string, maxBytes: number, options?: ReadRolloutOptions): Promise<Buffer>;
|
|
65
|
+
/**
|
|
66
|
+
* The first `maxLines` complete lines of a rollout (decompressed for a
|
|
67
|
+
* `.jsonl.zst`) within `maxBytes` of content; a trailing line without a
|
|
68
|
+
* newline counts when the whole content fit.
|
|
69
|
+
*/
|
|
70
|
+
export declare function readRolloutFirstLines(filePath: string, maxLines: number, maxBytes?: number, options?: ReadRolloutOptions): string[];
|
|
71
|
+
/**
|
|
72
|
+
* The content size a Zstandard frame header declares (`Frame_Content_Size`),
|
|
73
|
+
* or `null` when the header omits it or is not a Zstandard frame. Skippable
|
|
74
|
+
* frames before it are not followed.
|
|
75
|
+
*/
|
|
76
|
+
export declare function zstdFrameContentSize(header: Buffer): number | null;
|
|
77
|
+
/** Size and mtime of a rollout's content (a compressed rollout: decompressed size). */
|
|
78
|
+
export interface RolloutContentStat {
|
|
79
|
+
size: number;
|
|
80
|
+
mtimeMs: number;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Stat of a rollout's content: the file's own for a `.jsonl`; for a
|
|
84
|
+
* `.jsonl.zst`, the decompressed size (from the frame header, else by
|
|
85
|
+
* decompressing) with the compressed file's mtime. Rejects like `fs.stat`
|
|
86
|
+
* when the file is missing; a compressed rollout that cannot be read
|
|
87
|
+
* rejects too.
|
|
88
|
+
*/
|
|
89
|
+
export declare function statRolloutContent(filePath: string, options?: ReadRolloutOptions): Promise<RolloutContentStat>;
|
|
90
|
+
/** The read and close calls of a file handle that the rollout scanners use. */
|
|
91
|
+
export interface RolloutReadHandle {
|
|
92
|
+
read(buffer: Buffer, offset: number, length: number, position: number): Promise<{
|
|
93
|
+
bytesRead: number;
|
|
94
|
+
}>;
|
|
95
|
+
close(): Promise<void>;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Opens a rollout for positional reads over its content: a file handle for
|
|
99
|
+
* a `.jsonl`, the decompressed content for a `.jsonl.zst` (with `size`, the
|
|
100
|
+
* content length). Rejects when the file cannot be opened or decompressed.
|
|
101
|
+
*/
|
|
102
|
+
export declare function openRolloutReadHandle(filePath: string, options?: ReadRolloutOptions): Promise<{
|
|
103
|
+
handle: RolloutReadHandle;
|
|
104
|
+
size: number | null;
|
|
105
|
+
}>;
|
|
106
|
+
/**
|
|
107
|
+
* The path of a rollout as stored now: `filePath` when it exists, else its
|
|
108
|
+
* compressed sibling (`<filePath>.zst`) when `includeCompressed` is set and
|
|
109
|
+
* that exists. `null` when neither does.
|
|
110
|
+
*/
|
|
111
|
+
export declare function resolveRolloutPath(filePath: string, options?: {
|
|
112
|
+
includeCompressed?: boolean;
|
|
113
|
+
}): string | null;
|
|
114
|
+
export {};
|