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,336 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Redacted rollouts in the shape Codex 0.166 writes for multi-agent sessions:
|
|
3
|
+
* a parent that spawns a child (`spawn_agent` → `SubAgentActivity` started →
|
|
4
|
+
* `{"task_name": …}` output → MESSAGE / FINAL_ANSWER agent messages →
|
|
5
|
+
* `completed`), a failed spawn, `followup_task` / `send_message` /
|
|
6
|
+
* `interrupt_agent` interactions, and a forked child whose rollout embeds the
|
|
7
|
+
* parent's history (and session_meta) before its
|
|
8
|
+
* `subagent_history_start_ordinal`, which itself spawns a grandchild.
|
|
9
|
+
* Payload text is placeholder; ids are synthetic.
|
|
10
|
+
*
|
|
11
|
+
* Test-only helpers.
|
|
12
|
+
*
|
|
13
|
+
* @module testing/codexSubagentFixtures
|
|
14
|
+
*/
|
|
15
|
+
export const PARENT_ID = '01a18f87-0000-7000-8000-000000000001';
|
|
16
|
+
export const CHILD_ID = '01a18f9d-0000-7000-8000-000000000002';
|
|
17
|
+
export const GRANDCHILD_ID = '01a18fa0-0000-7000-8000-000000000003';
|
|
18
|
+
export const CALL_SPAWN = 'call_spawn_builder';
|
|
19
|
+
export const CALL_SPAWN_FAILED = 'call_spawn_failed';
|
|
20
|
+
export const CALL_FOLLOWUP = 'call_followup_builder';
|
|
21
|
+
export const CALL_SEND = 'call_send_builder';
|
|
22
|
+
export const CALL_INTERRUPT = 'call_interrupt_builder';
|
|
23
|
+
export const CALL_SPAWN_GRANDCHILD = 'call_spawn_deep_dive';
|
|
24
|
+
export const CHILD_TURN_ID = '01a18f9d-0000-7000-8000-0000000000t1';
|
|
25
|
+
export const CHILD_PATH = '/root/review_builder';
|
|
26
|
+
export const GRANDCHILD_PATH = '/root/review_builder/deep_dive';
|
|
27
|
+
/** `2026-10-31T01:<minute>:<second>.000Z`. */
|
|
28
|
+
export function at(minute, second = 0) {
|
|
29
|
+
return `2026-10-31T01:${String(minute).padStart(2, '0')}:${String(second).padStart(2, '0')}.000Z`;
|
|
30
|
+
}
|
|
31
|
+
function numbered(rows) {
|
|
32
|
+
return rows.map((row, ordinal) => ({ timestamp: row.timestamp, ordinal, ...row }));
|
|
33
|
+
}
|
|
34
|
+
function tokenCount(timestamp, total, last) {
|
|
35
|
+
return {
|
|
36
|
+
timestamp,
|
|
37
|
+
type: 'event_msg',
|
|
38
|
+
payload: {
|
|
39
|
+
type: 'token_count',
|
|
40
|
+
info: {
|
|
41
|
+
total_token_usage: { input_tokens: total, output_tokens: 0, total_tokens: total },
|
|
42
|
+
last_token_usage: { input_tokens: last, output_tokens: 0, total_tokens: last },
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
function functionCall(timestamp, callId, name, args, namespace = 'collaboration') {
|
|
48
|
+
return {
|
|
49
|
+
timestamp,
|
|
50
|
+
type: 'response_item',
|
|
51
|
+
payload: {
|
|
52
|
+
type: 'function_call',
|
|
53
|
+
status: 'completed',
|
|
54
|
+
id: `fc_${callId}`,
|
|
55
|
+
name,
|
|
56
|
+
...(namespace ? { namespace } : {}),
|
|
57
|
+
arguments: JSON.stringify(args),
|
|
58
|
+
call_id: callId,
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
function output(timestamp, callId, text) {
|
|
63
|
+
return {
|
|
64
|
+
timestamp,
|
|
65
|
+
type: 'response_item',
|
|
66
|
+
payload: { type: 'function_call_output', call_id: callId, output: text },
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
function activity(timestamp, threadId, item) {
|
|
70
|
+
return {
|
|
71
|
+
timestamp,
|
|
72
|
+
type: 'event_msg',
|
|
73
|
+
payload: {
|
|
74
|
+
type: 'item_completed',
|
|
75
|
+
thread_id: threadId,
|
|
76
|
+
turn_id: `${threadId}-turn`,
|
|
77
|
+
item: { type: 'SubAgentActivity', model: null, reasoning_effort: null, ...item },
|
|
78
|
+
},
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
function agentMessage(timestamp, id, author, recipient, messageType, payload) {
|
|
82
|
+
return {
|
|
83
|
+
timestamp,
|
|
84
|
+
type: 'response_item',
|
|
85
|
+
payload: {
|
|
86
|
+
type: 'agent_message',
|
|
87
|
+
id,
|
|
88
|
+
author,
|
|
89
|
+
recipient,
|
|
90
|
+
content: [
|
|
91
|
+
{
|
|
92
|
+
type: 'input_text',
|
|
93
|
+
text: `Message Type: ${messageType}\nTask name: ${recipient}\nSender: ${author}\nPayload:\n${payload}`,
|
|
94
|
+
},
|
|
95
|
+
...(payload ? [] : [{ type: 'encrypted_content', encrypted_content: 'gAAAA-redacted' }]),
|
|
96
|
+
],
|
|
97
|
+
},
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
function exec(timestamp, callId) {
|
|
101
|
+
return functionCall(timestamp, callId, 'exec_command', { cmd: 'rg TODO' }, undefined);
|
|
102
|
+
}
|
|
103
|
+
/** The parent session's rollout (ordinals 0…). */
|
|
104
|
+
export function parentRollout(options) {
|
|
105
|
+
const rows = [
|
|
106
|
+
{
|
|
107
|
+
timestamp: at(15),
|
|
108
|
+
type: 'session_meta',
|
|
109
|
+
payload: {
|
|
110
|
+
id: PARENT_ID,
|
|
111
|
+
session_id: PARENT_ID,
|
|
112
|
+
timestamp: at(15),
|
|
113
|
+
cwd: options.cwd,
|
|
114
|
+
originator: 'codex-tui',
|
|
115
|
+
cli_version: '0.166.0',
|
|
116
|
+
source: 'vscode',
|
|
117
|
+
thread_source: 'user',
|
|
118
|
+
history_mode: 'paginated',
|
|
119
|
+
base_instructions: { text: 'Parent base instructions.' },
|
|
120
|
+
},
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
timestamp: at(15, 1),
|
|
124
|
+
type: 'turn_context',
|
|
125
|
+
payload: { model: 'gpt-test', cwd: options.cwd },
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
timestamp: at(15, 2),
|
|
129
|
+
type: 'response_item',
|
|
130
|
+
payload: {
|
|
131
|
+
type: 'message',
|
|
132
|
+
role: 'user',
|
|
133
|
+
content: [{ type: 'input_text', text: 'Review the builder.' }],
|
|
134
|
+
},
|
|
135
|
+
},
|
|
136
|
+
tokenCount(at(15, 3), 1000, 1000),
|
|
137
|
+
functionCall(at(40, 33), CALL_SPAWN, 'spawn_agent', {
|
|
138
|
+
task_name: 'review_builder',
|
|
139
|
+
message: 'gAAAA-redacted',
|
|
140
|
+
}),
|
|
141
|
+
options.withoutActivity
|
|
142
|
+
? null
|
|
143
|
+
: activity(at(40, 33), PARENT_ID, {
|
|
144
|
+
id: CALL_SPAWN,
|
|
145
|
+
kind: 'started',
|
|
146
|
+
model: 'gpt-test',
|
|
147
|
+
reasoning_effort: 'high',
|
|
148
|
+
agent_thread_id: CHILD_ID,
|
|
149
|
+
agent_path: CHILD_PATH,
|
|
150
|
+
}),
|
|
151
|
+
output(at(40, 34), CALL_SPAWN, JSON.stringify({ task_name: CHILD_PATH })),
|
|
152
|
+
functionCall(at(40, 35), CALL_SPAWN_FAILED, 'spawn_agent', {
|
|
153
|
+
task_name: 'review_extra',
|
|
154
|
+
message: 'gAAAA-redacted',
|
|
155
|
+
}),
|
|
156
|
+
output(at(40, 35), CALL_SPAWN_FAILED, 'collab spawn failed: agent thread limit reached'),
|
|
157
|
+
{
|
|
158
|
+
timestamp: at(41, 8),
|
|
159
|
+
type: 'inter_agent_communication_metadata',
|
|
160
|
+
payload: { trigger_turn: false },
|
|
161
|
+
},
|
|
162
|
+
agentMessage(at(41, 8), 'amsg_progress', CHILD_PATH, '/root', 'MESSAGE', ''),
|
|
163
|
+
options.withoutActivity || options.withoutCompleted
|
|
164
|
+
? null
|
|
165
|
+
: activity(at(42, 21), PARENT_ID, {
|
|
166
|
+
id: `subagent-completed-${CHILD_TURN_ID}`,
|
|
167
|
+
kind: 'completed',
|
|
168
|
+
agent_thread_id: CHILD_ID,
|
|
169
|
+
agent_path: CHILD_PATH,
|
|
170
|
+
}),
|
|
171
|
+
agentMessage(at(42, 25), 'amsg_final', CHILD_PATH, '/root', 'FINAL_ANSWER', 'Found two issues.'),
|
|
172
|
+
functionCall(at(45, 37), CALL_FOLLOWUP, 'followup_task', {
|
|
173
|
+
target: 'review_builder',
|
|
174
|
+
message: 'gAAAA-redacted',
|
|
175
|
+
}),
|
|
176
|
+
options.withoutActivity
|
|
177
|
+
? null
|
|
178
|
+
: activity(at(45, 37), PARENT_ID, {
|
|
179
|
+
id: CALL_FOLLOWUP,
|
|
180
|
+
kind: 'interacted',
|
|
181
|
+
agent_thread_id: CHILD_ID,
|
|
182
|
+
agent_path: CHILD_PATH,
|
|
183
|
+
}),
|
|
184
|
+
output(at(45, 38), CALL_FOLLOWUP, ''),
|
|
185
|
+
functionCall(at(45, 40), CALL_SEND, 'send_message', {
|
|
186
|
+
target: 'review_builder',
|
|
187
|
+
message: 'gAAAA-redacted',
|
|
188
|
+
}),
|
|
189
|
+
options.withoutActivity
|
|
190
|
+
? null
|
|
191
|
+
: activity(at(45, 40), PARENT_ID, {
|
|
192
|
+
id: CALL_SEND,
|
|
193
|
+
kind: 'interacted',
|
|
194
|
+
agent_thread_id: CHILD_ID,
|
|
195
|
+
agent_path: CHILD_PATH,
|
|
196
|
+
}),
|
|
197
|
+
functionCall(at(46, 0), CALL_INTERRUPT, 'interrupt_agent', { target: 'review_builder' }),
|
|
198
|
+
options.withoutActivity
|
|
199
|
+
? null
|
|
200
|
+
: activity(at(46, 0), PARENT_ID, {
|
|
201
|
+
id: CALL_INTERRUPT,
|
|
202
|
+
kind: 'interrupted',
|
|
203
|
+
agent_thread_id: CHILD_ID,
|
|
204
|
+
agent_path: CHILD_PATH,
|
|
205
|
+
}),
|
|
206
|
+
tokenCount(at(46, 5), 2000, 1000),
|
|
207
|
+
];
|
|
208
|
+
return numbered(rows.filter((row) => row !== null));
|
|
209
|
+
}
|
|
210
|
+
/** Records the child inherits from the parent (re-stamped at fork time, as Codex does). */
|
|
211
|
+
const INHERITED_RECORDS = 5;
|
|
212
|
+
/** The forked child's rollout: its meta, the parent's history, then its own records. */
|
|
213
|
+
export function childRollout(options) {
|
|
214
|
+
const start = 1 + INHERITED_RECORDS;
|
|
215
|
+
return numbered([
|
|
216
|
+
{
|
|
217
|
+
timestamp: at(40, 33),
|
|
218
|
+
type: 'session_meta',
|
|
219
|
+
payload: {
|
|
220
|
+
id: CHILD_ID,
|
|
221
|
+
session_id: PARENT_ID,
|
|
222
|
+
forked_from_id: PARENT_ID,
|
|
223
|
+
parent_thread_id: PARENT_ID,
|
|
224
|
+
timestamp: at(40, 33),
|
|
225
|
+
cwd: options.cwd,
|
|
226
|
+
originator: 'codex-tui',
|
|
227
|
+
cli_version: '0.166.0',
|
|
228
|
+
source: {
|
|
229
|
+
subagent: {
|
|
230
|
+
thread_spawn: {
|
|
231
|
+
parent_thread_id: PARENT_ID,
|
|
232
|
+
depth: 1,
|
|
233
|
+
agent_path: CHILD_PATH,
|
|
234
|
+
agent_nickname: 'Banach',
|
|
235
|
+
agent_role: null,
|
|
236
|
+
},
|
|
237
|
+
},
|
|
238
|
+
},
|
|
239
|
+
thread_source: 'subagent',
|
|
240
|
+
agent_nickname: 'Banach',
|
|
241
|
+
agent_path: CHILD_PATH,
|
|
242
|
+
history_mode: 'paginated',
|
|
243
|
+
subagent_history_start_ordinal: start,
|
|
244
|
+
multi_agent_version: 'v2',
|
|
245
|
+
},
|
|
246
|
+
},
|
|
247
|
+
// The parent's history, embedded by the fork.
|
|
248
|
+
{
|
|
249
|
+
timestamp: at(40, 33),
|
|
250
|
+
type: 'session_meta',
|
|
251
|
+
payload: {
|
|
252
|
+
id: PARENT_ID,
|
|
253
|
+
timestamp: at(15),
|
|
254
|
+
cwd: '/somewhere/else',
|
|
255
|
+
source: 'vscode',
|
|
256
|
+
base_instructions: { text: 'Parent base instructions.' },
|
|
257
|
+
},
|
|
258
|
+
},
|
|
259
|
+
{ timestamp: at(40, 33), type: 'turn_context', payload: { model: 'gpt-parent' } },
|
|
260
|
+
exec(at(40, 33), 'call_parent_exec'),
|
|
261
|
+
tokenCount(at(40, 33), 5000, 5000),
|
|
262
|
+
{
|
|
263
|
+
timestamp: at(40, 33),
|
|
264
|
+
type: 'response_item',
|
|
265
|
+
payload: {
|
|
266
|
+
type: 'message',
|
|
267
|
+
role: 'developer',
|
|
268
|
+
content: [{ type: 'input_text', text: 'You are now a newly spawned child agent.' }],
|
|
269
|
+
},
|
|
270
|
+
},
|
|
271
|
+
// The child's own records.
|
|
272
|
+
{
|
|
273
|
+
timestamp: at(40, 34),
|
|
274
|
+
type: 'event_msg',
|
|
275
|
+
payload: { type: 'task_started', turn_id: CHILD_TURN_ID },
|
|
276
|
+
},
|
|
277
|
+
{ timestamp: at(40, 34), type: 'turn_context', payload: { model: 'gpt-test' } },
|
|
278
|
+
exec(at(40, 35), 'call_child_exec'),
|
|
279
|
+
output(at(40, 36), 'call_child_exec', 'Process exited with code 0'),
|
|
280
|
+
tokenCount(at(40, 37), 300, 300),
|
|
281
|
+
functionCall(at(40, 38), CALL_SPAWN_GRANDCHILD, 'spawn_agent', {
|
|
282
|
+
task_name: 'deep_dive',
|
|
283
|
+
message: 'gAAAA-redacted',
|
|
284
|
+
}),
|
|
285
|
+
activity(at(40, 38), CHILD_ID, {
|
|
286
|
+
id: CALL_SPAWN_GRANDCHILD,
|
|
287
|
+
kind: 'started',
|
|
288
|
+
agent_thread_id: GRANDCHILD_ID,
|
|
289
|
+
agent_path: GRANDCHILD_PATH,
|
|
290
|
+
}),
|
|
291
|
+
output(at(40, 39), CALL_SPAWN_GRANDCHILD, JSON.stringify({ task_name: GRANDCHILD_PATH })),
|
|
292
|
+
tokenCount(at(42, 20), 500, 200),
|
|
293
|
+
]);
|
|
294
|
+
}
|
|
295
|
+
/** The grandchild's rollout (depth 2): a fork of the child. */
|
|
296
|
+
export function grandchildRollout(options) {
|
|
297
|
+
return numbered([
|
|
298
|
+
{
|
|
299
|
+
timestamp: at(40, 38),
|
|
300
|
+
type: 'session_meta',
|
|
301
|
+
payload: {
|
|
302
|
+
id: GRANDCHILD_ID,
|
|
303
|
+
session_id: PARENT_ID,
|
|
304
|
+
forked_from_id: CHILD_ID,
|
|
305
|
+
parent_thread_id: CHILD_ID,
|
|
306
|
+
timestamp: at(40, 38),
|
|
307
|
+
cwd: options.cwd,
|
|
308
|
+
cli_version: '0.166.0',
|
|
309
|
+
source: {
|
|
310
|
+
subagent: {
|
|
311
|
+
thread_spawn: {
|
|
312
|
+
parent_thread_id: CHILD_ID,
|
|
313
|
+
depth: 2,
|
|
314
|
+
agent_path: GRANDCHILD_PATH,
|
|
315
|
+
agent_nickname: 'Noether',
|
|
316
|
+
agent_role: 'explorer',
|
|
317
|
+
},
|
|
318
|
+
},
|
|
319
|
+
},
|
|
320
|
+
thread_source: 'subagent',
|
|
321
|
+
agent_nickname: 'Noether',
|
|
322
|
+
agent_path: GRANDCHILD_PATH,
|
|
323
|
+
agent_role: 'explorer',
|
|
324
|
+
subagent_history_start_ordinal: 3,
|
|
325
|
+
},
|
|
326
|
+
},
|
|
327
|
+
{ timestamp: at(40, 38), type: 'session_meta', payload: { id: CHILD_ID, cwd: options.cwd } },
|
|
328
|
+
exec(at(40, 38), 'call_child_exec'),
|
|
329
|
+
exec(at(40, 40), 'call_grandchild_exec'),
|
|
330
|
+
tokenCount(at(40, 41), 50, 50),
|
|
331
|
+
]);
|
|
332
|
+
}
|
|
333
|
+
/** Rows as JSONL text. */
|
|
334
|
+
export function toJsonl(rows) {
|
|
335
|
+
return rows.map((row) => JSON.stringify(row)).join('\n') + '\n';
|
|
336
|
+
}
|
|
@@ -21,6 +21,8 @@
|
|
|
21
21
|
import * as fs from 'fs';
|
|
22
22
|
import * as path from 'path';
|
|
23
23
|
import * as readline from 'readline';
|
|
24
|
+
import { Readable } from 'stream';
|
|
25
|
+
import { isCompressedRolloutPath, readRolloutBufferAsync, readRolloutHeadAsync, } from './providers/compressedRollouts';
|
|
24
26
|
import { listCandidates, readPromptOrdinalMarks, resolveRoots, } from './promptHistory';
|
|
25
27
|
import { codexRolloutOrigin } from './parsers/codexRolloutOrigin';
|
|
26
28
|
import { realpathOrNull } from './workspaceRoots';
|
|
@@ -42,7 +44,9 @@ export async function findToolCallOrigins(options) {
|
|
|
42
44
|
return found;
|
|
43
45
|
const providers = options.providers ?? ['claude-code', 'codex'];
|
|
44
46
|
const sinceMs = options.since ? options.since.getTime() : null;
|
|
45
|
-
const candidates = await listCandidates(providers, listingRoots, sinceMs
|
|
47
|
+
const candidates = await listCandidates(providers, listingRoots, sinceMs, {
|
|
48
|
+
includeCompressedRollouts: options.includeCompressedRollouts,
|
|
49
|
+
});
|
|
46
50
|
const byProviderSession = new Map();
|
|
47
51
|
for (const candidate of candidates) {
|
|
48
52
|
byProviderSession.set(`${candidate.provider}:${candidate.sessionId}`, candidate);
|
|
@@ -152,21 +156,15 @@ function claudeSubagentTranscripts(sessionFile, sessionId) {
|
|
|
152
156
|
}));
|
|
153
157
|
}
|
|
154
158
|
async function readFirstRecord(filePath) {
|
|
155
|
-
let handle;
|
|
156
159
|
try {
|
|
157
|
-
|
|
158
|
-
const
|
|
159
|
-
const { bytesRead } = await handle.read(buffer, 0, buffer.length, 0);
|
|
160
|
-
const text = buffer.subarray(0, bytesRead).toString('utf8');
|
|
160
|
+
// A compressed rollout's head is decompressed.
|
|
161
|
+
const text = (await readRolloutHeadAsync(filePath, 256 * 1024)).toString('utf8');
|
|
161
162
|
const newline = text.indexOf('\n');
|
|
162
163
|
return JSON.parse(newline === -1 ? text : text.slice(0, newline));
|
|
163
164
|
}
|
|
164
165
|
catch {
|
|
165
166
|
return undefined;
|
|
166
167
|
}
|
|
167
|
-
finally {
|
|
168
|
-
await handle?.close();
|
|
169
|
-
}
|
|
170
168
|
}
|
|
171
169
|
/** Fail-closed scope check on a recorded working directory. */
|
|
172
170
|
function inScope(cwd, roots, realpaths) {
|
|
@@ -187,7 +185,16 @@ async function scanForIds(filePath, provider, wanted, roots, realpaths, signal)
|
|
|
187
185
|
const hits = [];
|
|
188
186
|
let stream;
|
|
189
187
|
try {
|
|
190
|
-
|
|
188
|
+
if (isCompressedRolloutPath(filePath)) {
|
|
189
|
+
// Compressed rollouts are complete; search their decompressed content.
|
|
190
|
+
const content = await readRolloutBufferAsync(filePath);
|
|
191
|
+
if (!content)
|
|
192
|
+
return hits;
|
|
193
|
+
stream = Readable.from([content.toString('utf8')]);
|
|
194
|
+
}
|
|
195
|
+
else {
|
|
196
|
+
stream = fs.createReadStream(filePath, { encoding: 'utf8' });
|
|
197
|
+
}
|
|
191
198
|
}
|
|
192
199
|
catch {
|
|
193
200
|
return hits;
|
package/dist/esm/toolCategory.js
CHANGED
|
@@ -16,7 +16,11 @@ export const TOOL_CATEGORIES = [
|
|
|
16
16
|
'mcp',
|
|
17
17
|
'other',
|
|
18
18
|
];
|
|
19
|
-
/**
|
|
19
|
+
/**
|
|
20
|
+
* Lower-cased tool names of every provider, by category. Codex's bare `wait`
|
|
21
|
+
* is a code-mode cell wait, not a subagent wait (that is `wait_agent`), so it
|
|
22
|
+
* is `other`.
|
|
23
|
+
*/
|
|
20
24
|
const CATEGORY_BY_NAME = new Map([
|
|
21
25
|
[
|
|
22
26
|
'shell',
|
|
@@ -50,7 +54,7 @@ const CATEGORY_BY_NAME = new Map([
|
|
|
50
54
|
'send_message',
|
|
51
55
|
'followup_task',
|
|
52
56
|
'list_agents',
|
|
53
|
-
'
|
|
57
|
+
'interrupt_agent',
|
|
54
58
|
],
|
|
55
59
|
],
|
|
56
60
|
[
|
package/dist/esm/transcript.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { calculateNormalizedUsageCost, extractNormalizedUsage, } from './usageNormalization';
|
|
2
|
-
import { isTaskNotificationSystemEvent } from './subagentSpawn';
|
|
2
|
+
import { isTaskNotificationSystemEvent, isTaskStatusSystemEvent } from './subagentSpawn';
|
|
3
|
+
import { isSubagentActivitySystemEvent } from './subagentActivity';
|
|
3
4
|
const EMPTY_TOTALS = {
|
|
4
5
|
uncachedInputTokens: 0,
|
|
5
6
|
cacheReadTokens: 0,
|
|
@@ -27,7 +28,10 @@ export function projectSessionTranscript(events, options = {}) {
|
|
|
27
28
|
let gitBranch;
|
|
28
29
|
events.forEach((event, eventIndex) => {
|
|
29
30
|
// A reader's synthetic notice that a background task finished, not a message.
|
|
30
|
-
if (isTaskNotificationSystemEvent(event))
|
|
31
|
+
if (isTaskNotificationSystemEvent(event) || isTaskStatusSystemEvent(event))
|
|
32
|
+
return;
|
|
33
|
+
// Likewise a subagent lifecycle record (opt-in Codex parser event).
|
|
34
|
+
if (isSubagentActivitySystemEvent(event))
|
|
31
35
|
return;
|
|
32
36
|
if (nonEmpty(event.cwd))
|
|
33
37
|
cwd = event.cwd;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { formatToolSummary, truncate } from '../formatters/toolSummary';
|
|
2
|
-
import {
|
|
2
|
+
import { SUBAGENT_SPAWN_TOOL_NAMES } from '../subagentSpawn';
|
|
3
3
|
const DEFAULT_NARRATION_CHARS = 4000;
|
|
4
4
|
const DEFAULT_REASONING_CHARS = 8000;
|
|
5
5
|
const DEFAULT_PROCESS_STEPS = 60;
|
|
@@ -22,9 +22,12 @@ export function reasoningSummary(text) {
|
|
|
22
22
|
}
|
|
23
23
|
return { title: null, body: trimmed };
|
|
24
24
|
}
|
|
25
|
-
/**
|
|
25
|
+
/**
|
|
26
|
+
* Whether a tool call spawns a subagent (`Agent`, or its earlier name `Task`).
|
|
27
|
+
* Codex `spawn_agent` is not included here (its turns project as before).
|
|
28
|
+
*/
|
|
26
29
|
export function isAssistantTurnSubagentTool(toolName) {
|
|
27
|
-
return
|
|
30
|
+
return typeof toolName === 'string' && SUBAGENT_SPAWN_TOOL_NAMES.includes(toolName);
|
|
28
31
|
}
|
|
29
32
|
export function segmentAssistantTurn(events, options = {}) {
|
|
30
33
|
const maxNarrationChars = options.maxNarrationChars ?? DEFAULT_NARRATION_CHARS;
|
|
@@ -15,7 +15,10 @@
|
|
|
15
15
|
* tagged with the subagent, and their sizes and mtimes join the fingerprint.
|
|
16
16
|
* (Codex spawned subagents and forks are top-level rollouts, already listed
|
|
17
17
|
* as sessions of their own; their events are tagged with the parent read
|
|
18
|
-
* from the rollout's first line
|
|
18
|
+
* from the rollout's first line; the parent history a forked child embeds
|
|
19
|
+
* is not counted (it carries no token usage). Compressed rollouts
|
|
20
|
+
* (`….jsonl.zst`) are read when a Codex provider was created with
|
|
21
|
+
* `includeCompressedRollouts`.)
|
|
19
22
|
*
|
|
20
23
|
* Each event also carries the ordinal of the human prompt it answered, from
|
|
21
24
|
* the same scanner as `collectPromptHistory()`, so usage joins prompt history
|
|
@@ -8,7 +8,8 @@
|
|
|
8
8
|
* FollowEvent is a lossy summary for timeline display and CLI output.
|
|
9
9
|
*/
|
|
10
10
|
import { formatToolSummary } from '../formatters/toolSummary';
|
|
11
|
-
import { isTaskNotificationSystemEvent, taskNotificationFromEvent } from '../subagentSpawn';
|
|
11
|
+
import { agentsStoppedNoticeFromEvent, describeAgentsStopped, describeTaskStatus, isAgentsKilledSystemEvent, isTaskNotificationSystemEvent, taskNotificationFromEvent, taskStatusFromEvent, } from '../subagentSpawn';
|
|
12
|
+
import { describeSubagentActivity, subagentActivityFromEvent } from '../subagentActivity';
|
|
12
13
|
/**
|
|
13
14
|
* Converts a single SessionEvent to one or more FollowEvents.
|
|
14
15
|
*
|
|
@@ -163,6 +164,35 @@ export function toFollowEvents(event, providerId) {
|
|
|
163
164
|
});
|
|
164
165
|
break;
|
|
165
166
|
}
|
|
167
|
+
// The user stopping background agents, or a background agent's final status.
|
|
168
|
+
const stopped = agentsStoppedNoticeFromEvent(event);
|
|
169
|
+
const taskStatus = stopped ? null : taskStatusFromEvent(event);
|
|
170
|
+
if (stopped || taskStatus || isAgentsKilledSystemEvent(event)) {
|
|
171
|
+
events.push({
|
|
172
|
+
providerId,
|
|
173
|
+
type: 'system',
|
|
174
|
+
timestamp: ts,
|
|
175
|
+
summary: stopped
|
|
176
|
+
? describeAgentsStopped(stopped)
|
|
177
|
+
: taskStatus
|
|
178
|
+
? describeTaskStatus(taskStatus)
|
|
179
|
+
: describeAgentsStopped(null),
|
|
180
|
+
raw: event,
|
|
181
|
+
});
|
|
182
|
+
break;
|
|
183
|
+
}
|
|
184
|
+
// A subagent lifecycle record (opt-in Codex parser event): a content-free summary.
|
|
185
|
+
const activity = subagentActivityFromEvent(event);
|
|
186
|
+
if (activity) {
|
|
187
|
+
events.push({
|
|
188
|
+
providerId,
|
|
189
|
+
type: 'system',
|
|
190
|
+
timestamp: ts,
|
|
191
|
+
summary: describeSubagentActivity(activity),
|
|
192
|
+
raw: event,
|
|
193
|
+
});
|
|
194
|
+
break;
|
|
195
|
+
}
|
|
166
196
|
const text = extractTextContent(event.message?.content);
|
|
167
197
|
const label = event.message?.sourceLabel || event.message?.role || 'system';
|
|
168
198
|
const summary = text ||
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import * as fs from 'fs';
|
|
6
6
|
import { fileHeadOf, HEAD_BYTES, headMatches } from '../fileHead';
|
|
7
|
+
import { FileContinuity, probeFileContinuity } from '../fileContinuity';
|
|
7
8
|
import { JsonlParser } from '../parsers/jsonl';
|
|
8
9
|
const DEFAULT_DEBOUNCE_MS = 100;
|
|
9
10
|
const DEFAULT_CATCHUP_INTERVAL_MS = 30_000;
|
|
@@ -128,10 +129,8 @@ class JsonlTailReader {
|
|
|
128
129
|
/** Inside a line over `maxLineBytes`: drop bytes until its newline. */
|
|
129
130
|
skippingLine = false;
|
|
130
131
|
currentLine = { offset: 0, length: 0 };
|
|
131
|
-
/** Identity and prefix of the file the offsets refer to, once read. */
|
|
132
|
-
|
|
133
|
-
head = null;
|
|
134
|
-
lastMtimeMs = -1;
|
|
132
|
+
/** Identity, mtime, and prefix of the file the offsets refer to, once read. */
|
|
133
|
+
continuity = new FileContinuity();
|
|
135
134
|
maxReadBytes;
|
|
136
135
|
maxLineBytes;
|
|
137
136
|
rewritePolicy;
|
|
@@ -339,21 +338,10 @@ class JsonlTailReader {
|
|
|
339
338
|
}
|
|
340
339
|
/** Whether the continuity check needs bytes from the file. */
|
|
341
340
|
probeNeeded(stat) {
|
|
342
|
-
return (this.
|
|
343
|
-
stat.dev !== this.identity.dev ||
|
|
344
|
-
stat.ino !== this.identity.ino ||
|
|
345
|
-
stat.size < this.readOffset ||
|
|
346
|
-
stat.mtimeMs !== this.lastMtimeMs ||
|
|
347
|
-
this.head === null ||
|
|
348
|
-
this.head.bytes < Math.min(HEAD_BYTES, stat.size));
|
|
341
|
+
return this.continuity.needsProbe(stat, this.readOffset);
|
|
349
342
|
}
|
|
350
343
|
probeSync(fd, stat) {
|
|
351
|
-
return
|
|
352
|
-
prefix: readPrefix(fd, stat),
|
|
353
|
-
before: this.committedOffset > 0 && this.committedOffset <= stat.size
|
|
354
|
-
? readAt(fd, this.committedOffset - 1, 1)
|
|
355
|
-
: null,
|
|
356
|
-
};
|
|
344
|
+
return probeFileContinuity(fd, stat, this.committedOffset);
|
|
357
345
|
}
|
|
358
346
|
async probeAsync(handle, stat) {
|
|
359
347
|
const committedOffset = this.committedOffset;
|
|
@@ -370,31 +358,16 @@ class JsonlTailReader {
|
|
|
370
358
|
* newline. `null` when it can continue.
|
|
371
359
|
*/
|
|
372
360
|
resetReason(stat, probe) {
|
|
373
|
-
|
|
374
|
-
return 'replaced';
|
|
375
|
-
}
|
|
376
|
-
if (stat.size < this.readOffset)
|
|
377
|
-
return 'truncated';
|
|
378
|
-
if (this.identity && stat.mtimeMs !== this.lastMtimeMs && probe) {
|
|
379
|
-
if (this.head && !headMatches(this.head, probe.prefix))
|
|
380
|
-
return 'rewritten';
|
|
381
|
-
if (probe.before && probe.before[0] !== 0x0a)
|
|
382
|
-
return 'rewritten';
|
|
383
|
-
}
|
|
384
|
-
return null;
|
|
361
|
+
return this.continuity.reasonFor(stat, this.readOffset, probe);
|
|
385
362
|
}
|
|
386
363
|
/** Records the identity and prefix of the file the offsets now refer to. */
|
|
387
364
|
adoptFile(stat, probe) {
|
|
388
|
-
this.
|
|
389
|
-
this.lastMtimeMs = stat.mtimeMs;
|
|
390
|
-
if (probe && (!this.head || this.head.bytes < Math.min(HEAD_BYTES, stat.size))) {
|
|
391
|
-
this.head = fileHeadOf(probe.prefix);
|
|
392
|
-
}
|
|
365
|
+
this.continuity.record(stat, probe);
|
|
393
366
|
}
|
|
394
367
|
reset(reason, offset) {
|
|
395
368
|
const previousOffset = this.committedOffset;
|
|
396
369
|
this.position(offset);
|
|
397
|
-
this.
|
|
370
|
+
this.continuity.discardHead();
|
|
398
371
|
if (reason === 'replaced' && this.active) {
|
|
399
372
|
// fs.watch follows the old inode; watch the file now at the path.
|
|
400
373
|
this.fsWatcher?.close();
|
|
@@ -469,7 +442,7 @@ class JsonlTailReader {
|
|
|
469
442
|
this.position(Math.max(0, offset));
|
|
470
443
|
}
|
|
471
444
|
checkpoint() {
|
|
472
|
-
return checkpointOf(this.committedOffset, this.
|
|
445
|
+
return checkpointOf(this.committedOffset, this.continuity.knownIdentity, this.continuity.knownHead);
|
|
473
446
|
}
|
|
474
447
|
resumeFrom(checkpoint) {
|
|
475
448
|
this.generation += 1;
|
|
@@ -487,16 +460,12 @@ class JsonlTailReader {
|
|
|
487
460
|
? lastCompleteLineOffset(fd, stat.size)
|
|
488
461
|
: 0;
|
|
489
462
|
this.position(offset);
|
|
490
|
-
this.
|
|
491
|
-
this.lastMtimeMs = stat.mtimeMs;
|
|
492
|
-
this.head = fileHeadOf(prefix);
|
|
463
|
+
this.continuity.adoptPrefix(stat, prefix);
|
|
493
464
|
return matches;
|
|
494
465
|
}
|
|
495
466
|
catch (error) {
|
|
496
467
|
this.position(0);
|
|
497
|
-
this.
|
|
498
|
-
this.head = null;
|
|
499
|
-
this.lastMtimeMs = -1;
|
|
468
|
+
this.continuity.forget();
|
|
500
469
|
this.options.onError?.(toError(error));
|
|
501
470
|
return false;
|
|
502
471
|
}
|
|
@@ -6,7 +6,7 @@ import { createJsonlTail } from './jsonlTail';
|
|
|
6
6
|
import { codexTokenUsageFrom, normalizeCodexToolName, resolveCodexCallUsage, } from '../parsers/codexParser';
|
|
7
7
|
import { formatToolSummary } from '../formatters/toolSummary';
|
|
8
8
|
import { ClaudeUsageDeduper, normalizeClaudeUsage } from '../usage/claudeUsageDedupe';
|
|
9
|
-
import { parseTaskNotification, taskNotificationPromptFromAttachment } from '../subagentSpawn';
|
|
9
|
+
import { describeAgentsStopped, describeTaskStatus, isAgentsKilledSystemEvent, isFinalTaskStatus, parseAgentsStoppedNotice, parseTaskNotification, taskNotificationPromptFromAttachment, taskStatusFromAttachment, } from '../subagentSpawn';
|
|
10
10
|
// ── Normalizers ──
|
|
11
11
|
/**
|
|
12
12
|
* Split lines of one Claude response repeat its usage; run the raw usage
|
|
@@ -136,15 +136,25 @@ function normalizeClaudeCodeEvent(raw, deduper, includeTaskNotifications) {
|
|
|
136
136
|
}
|
|
137
137
|
else if (includeTaskNotifications) {
|
|
138
138
|
// A task notification that landed mid-turn is only an attachment line.
|
|
139
|
-
const
|
|
139
|
+
const prompt = taskNotificationPromptFromAttachment(raw);
|
|
140
|
+
const notification = parseTaskNotification(prompt);
|
|
141
|
+
const stopped = notification ? null : parseAgentsStoppedNotice(prompt);
|
|
142
|
+
const taskStatus = taskStatusFromAttachment(raw);
|
|
143
|
+
let summary = null;
|
|
140
144
|
if (notification) {
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
145
|
+
summary = notification.summary ?? `Background task ${notification.status ?? 'finished'}`;
|
|
146
|
+
}
|
|
147
|
+
else if (stopped) {
|
|
148
|
+
summary = describeAgentsStopped(stopped);
|
|
149
|
+
}
|
|
150
|
+
else if (taskStatus && isFinalTaskStatus(taskStatus.status)) {
|
|
151
|
+
summary = describeTaskStatus(taskStatus);
|
|
152
|
+
}
|
|
153
|
+
else if (isAgentsKilledSystemEvent(raw)) {
|
|
154
|
+
summary = describeAgentsStopped(null);
|
|
155
|
+
}
|
|
156
|
+
if (summary !== null) {
|
|
157
|
+
events.push({ providerId: 'claude-code', type: 'system', timestamp: ts, summary, raw });
|
|
148
158
|
}
|
|
149
159
|
}
|
|
150
160
|
}
|