sidekick-shared 0.29.0 → 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 +48 -0
- package/README.md +198 -56
- package/dist/aggregation/EventAggregator.d.ts +96 -2
- package/dist/aggregation/EventAggregator.js +535 -42
- package/dist/aggregation/snapshot.d.ts +37 -8
- package/dist/aggregation/snapshot.js +31 -9
- package/dist/aggregation/types.d.ts +38 -1
- package/dist/browser.d.ts +9 -1
- package/dist/browser.js +29 -3
- package/dist/context/sessionContext.js +8 -0
- package/dist/esm/aggregation/EventAggregator.js +535 -42
- package/dist/esm/aggregation/snapshot.js +31 -9
- package/dist/esm/browser.js +3 -0
- package/dist/esm/context/sessionContext.js +8 -0
- package/dist/esm/fileContinuity.js +149 -0
- package/dist/esm/formatters/toolSummary.js +4 -1
- package/dist/esm/index.js +7 -2
- package/dist/esm/modelCatalog.js +5 -4
- package/dist/esm/modelContext.js +4 -3
- package/dist/esm/modelInfo.js +4 -3
- package/dist/esm/node.js +3 -1
- package/dist/esm/observedContextWindows.js +2 -1
- package/dist/esm/ownKey.js +26 -0
- package/dist/esm/parsers/codexParser.js +372 -7
- package/dist/esm/parsers/openCodeParser.js +2 -1
- package/dist/esm/parsers/subagentEndState.js +61 -0
- package/dist/esm/parsers/subagentScanner.js +37 -12
- package/dist/esm/parsers/subagentTraceParser.js +103 -37
- package/dist/esm/pricingCatalog.js +3 -2
- package/dist/esm/promptHistory.js +22 -11
- package/dist/esm/providers/claudeCode.js +60 -29
- package/dist/esm/providers/codex.js +235 -201
- 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 +63 -14
- 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 +363 -0
- 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 +8 -0
- package/dist/esm/turns/assistantTurn.js +6 -2
- package/dist/esm/usage/claudeUsageDedupe.js +68 -0
- package/dist/esm/usage/usageEvents.js +4 -1
- package/dist/esm/watchers/eventBridge.js +45 -0
- package/dist/esm/watchers/factory.js +5 -3
- package/dist/esm/watchers/jsonlTail.js +82 -48
- package/dist/esm/watchers/jsonlWatcher.js +61 -4
- package/dist/esm/watchers/providerReaderWatcher.js +18 -4
- package/dist/esm/zaiQuota.js +2 -1
- package/dist/fileContinuity.d.ts +95 -0
- package/dist/fileContinuity.js +187 -0
- package/dist/formatters/toolSummary.js +4 -1
- package/dist/index.d.ts +19 -7
- package/dist/index.js +48 -10
- package/dist/modelCatalog.js +5 -4
- package/dist/modelContext.js +4 -3
- package/dist/modelInfo.js +4 -3
- package/dist/node.d.ts +5 -1
- package/dist/node.js +18 -2
- package/dist/observedContextWindows.js +2 -1
- package/dist/ownKey.d.ts +16 -0
- package/dist/ownKey.js +30 -0
- package/dist/parsers/codexParser.d.ts +107 -0
- package/dist/parsers/codexParser.js +373 -7
- package/dist/parsers/openCodeParser.js +2 -1
- package/dist/parsers/subagentEndState.d.ts +20 -0
- package/dist/parsers/subagentEndState.js +65 -0
- package/dist/parsers/subagentScanner.d.ts +19 -6
- package/dist/parsers/subagentScanner.js +37 -12
- package/dist/parsers/subagentTraceParser.d.ts +33 -5
- package/dist/parsers/subagentTraceParser.js +103 -37
- package/dist/pricingCatalog.js +3 -2
- package/dist/promptHistory.d.ts +13 -3
- package/dist/promptHistory.js +22 -11
- package/dist/providers/claudeCode.js +59 -28
- package/dist/providers/codex.d.ts +35 -5
- package/dist/providers/codex.js +232 -198
- 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 +36 -1
- package/dist/schemas/index.d.ts +1 -0
- package/dist/schemas/sessionEvent.d.ts +51 -10
- package/dist/schemas/sessionEvent.js +63 -14
- 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 +175 -0
- package/dist/subagentSpawn.js +385 -0
- 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 +8 -0
- package/dist/turns/assistantTurn.d.ts +4 -0
- package/dist/turns/assistantTurn.js +6 -2
- package/dist/types/codex.d.ts +71 -2
- package/dist/types/sessionEvent.d.ts +38 -3
- package/dist/usage/claudeUsageDedupe.d.ts +31 -0
- package/dist/usage/claudeUsageDedupe.js +68 -0
- package/dist/usage/usageEvents.d.ts +4 -1
- package/dist/usage/usageEvents.js +4 -1
- package/dist/watchers/eventBridge.js +45 -0
- package/dist/watchers/factory.d.ts +6 -0
- package/dist/watchers/factory.js +5 -3
- package/dist/watchers/jsonlTail.d.ts +27 -4
- package/dist/watchers/jsonlTail.js +84 -48
- package/dist/watchers/jsonlWatcher.d.ts +22 -1
- package/dist/watchers/jsonlWatcher.js +60 -3
- package/dist/watchers/providerReaderWatcher.d.ts +7 -0
- package/dist/watchers/providerReaderWatcher.js +18 -4
- package/dist/watchers/types.d.ts +12 -0
- package/dist/zaiQuota.js +2 -1
- 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,4 +1,6 @@
|
|
|
1
1
|
import { calculateNormalizedUsageCost, extractNormalizedUsage, } from './usageNormalization';
|
|
2
|
+
import { isTaskNotificationSystemEvent, isTaskStatusSystemEvent } from './subagentSpawn';
|
|
3
|
+
import { isSubagentActivitySystemEvent } from './subagentActivity';
|
|
2
4
|
const EMPTY_TOTALS = {
|
|
3
5
|
uncachedInputTokens: 0,
|
|
4
6
|
cacheReadTokens: 0,
|
|
@@ -25,6 +27,12 @@ export function projectSessionTranscript(events, options = {}) {
|
|
|
25
27
|
let cwd;
|
|
26
28
|
let gitBranch;
|
|
27
29
|
events.forEach((event, eventIndex) => {
|
|
30
|
+
// A reader's synthetic notice that a background task finished, not a message.
|
|
31
|
+
if (isTaskNotificationSystemEvent(event) || isTaskStatusSystemEvent(event))
|
|
32
|
+
return;
|
|
33
|
+
// Likewise a subagent lifecycle record (opt-in Codex parser event).
|
|
34
|
+
if (isSubagentActivitySystemEvent(event))
|
|
35
|
+
return;
|
|
28
36
|
if (nonEmpty(event.cwd))
|
|
29
37
|
cwd = event.cwd;
|
|
30
38
|
if (nonEmpty(event.gitBranch))
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { formatToolSummary, truncate } from '../formatters/toolSummary';
|
|
2
|
+
import { SUBAGENT_SPAWN_TOOL_NAMES } from '../subagentSpawn';
|
|
2
3
|
const DEFAULT_NARRATION_CHARS = 4000;
|
|
3
4
|
const DEFAULT_REASONING_CHARS = 8000;
|
|
4
5
|
const DEFAULT_PROCESS_STEPS = 60;
|
|
5
6
|
const DEFAULT_REASONING_BLOCKS = 24;
|
|
6
7
|
const DEFAULT_TOOL_INPUT_CHARS = 200;
|
|
7
|
-
const SUBAGENT_TOOL_NAMES = new Set(['Task']);
|
|
8
8
|
const LABEL_MAX = 80;
|
|
9
9
|
const TITLE_RE = /^\s*\*\*([^*\n]+)\*\*[ \t]*(?:\n+([\s\S]*))?$/;
|
|
10
10
|
const TITLE_MAX = 100;
|
|
@@ -22,8 +22,12 @@ export function reasoningSummary(text) {
|
|
|
22
22
|
}
|
|
23
23
|
return { title: null, body: trimmed };
|
|
24
24
|
}
|
|
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
|
+
*/
|
|
25
29
|
export function isAssistantTurnSubagentTool(toolName) {
|
|
26
|
-
return toolName
|
|
30
|
+
return typeof toolName === 'string' && SUBAGENT_SPAWN_TOOL_NAMES.includes(toolName);
|
|
27
31
|
}
|
|
28
32
|
export function segmentAssistantTurn(events, options = {}) {
|
|
29
33
|
const maxNarrationChars = options.maxNarrationChars ?? DEFAULT_NARRATION_CHARS;
|
|
@@ -59,6 +59,25 @@ export function normalizeClaudeUsage(event) {
|
|
|
59
59
|
},
|
|
60
60
|
};
|
|
61
61
|
}
|
|
62
|
+
const BUCKET_KEYS = ['input', 'cacheRead', 'cacheWrite', 'output', 'reasoning'];
|
|
63
|
+
/** Entries {@link ClaudeUsageDeduper.exportState} keeps by default. */
|
|
64
|
+
const DEFAULT_EXPORT_LIMIT = 256;
|
|
65
|
+
function isCount(value) {
|
|
66
|
+
return typeof value === 'number' && Number.isFinite(value) && value >= 0;
|
|
67
|
+
}
|
|
68
|
+
function bucketsFrom(value) {
|
|
69
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
70
|
+
return null;
|
|
71
|
+
const record = value;
|
|
72
|
+
const buckets = {};
|
|
73
|
+
for (const key of BUCKET_KEYS) {
|
|
74
|
+
const count = record[key];
|
|
75
|
+
if (!isCount(count))
|
|
76
|
+
return null;
|
|
77
|
+
buckets[key] = count;
|
|
78
|
+
}
|
|
79
|
+
return buckets;
|
|
80
|
+
}
|
|
62
81
|
function bucketsOf(usage) {
|
|
63
82
|
return {
|
|
64
83
|
input: usage.uncachedInputTokens,
|
|
@@ -163,6 +182,55 @@ export class ClaudeUsageDeduper {
|
|
|
163
182
|
reset() {
|
|
164
183
|
this.seen.clear();
|
|
165
184
|
}
|
|
185
|
+
/**
|
|
186
|
+
* The most recent `limit` message ids and the tokens counted for each,
|
|
187
|
+
* oldest first. Split lines of one response are adjacent, so a reader
|
|
188
|
+
* resumed at the position this was taken at needs only the last few.
|
|
189
|
+
*/
|
|
190
|
+
exportState(limit = DEFAULT_EXPORT_LIMIT) {
|
|
191
|
+
const count = limit >= 0 ? Math.min(this.seen.size, Math.floor(limit)) : 0;
|
|
192
|
+
const entries = [];
|
|
193
|
+
if (count > 0) {
|
|
194
|
+
let skip = this.seen.size - count;
|
|
195
|
+
for (const [id, buckets] of this.seen) {
|
|
196
|
+
if (skip > 0) {
|
|
197
|
+
skip -= 1;
|
|
198
|
+
continue;
|
|
199
|
+
}
|
|
200
|
+
entries.push([id, { ...buckets }]);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
return { version: 1, entries };
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* Replaces this deduper's state with one from {@link exportState}. Returns
|
|
207
|
+
* false, changing nothing, when `state` is not a valid version-1 state
|
|
208
|
+
* (wrong shape, empty ids, or counts that are not finite non-negative
|
|
209
|
+
* numbers). Entries beyond this deduper's capacity drop oldest first.
|
|
210
|
+
*/
|
|
211
|
+
importState(state) {
|
|
212
|
+
if (!state || typeof state !== 'object')
|
|
213
|
+
return false;
|
|
214
|
+
const { version, entries } = state;
|
|
215
|
+
if (version !== 1 || !Array.isArray(entries))
|
|
216
|
+
return false;
|
|
217
|
+
const parsed = [];
|
|
218
|
+
for (const entry of entries) {
|
|
219
|
+
if (!Array.isArray(entry) || entry.length !== 2)
|
|
220
|
+
return false;
|
|
221
|
+
const [id, value] = entry;
|
|
222
|
+
if (typeof id !== 'string' || id.length === 0)
|
|
223
|
+
return false;
|
|
224
|
+
const buckets = bucketsFrom(value);
|
|
225
|
+
if (!buckets)
|
|
226
|
+
return false;
|
|
227
|
+
parsed.push([id, buckets]);
|
|
228
|
+
}
|
|
229
|
+
this.seen.clear();
|
|
230
|
+
for (const [id, buckets] of parsed)
|
|
231
|
+
this.remember(id, buckets);
|
|
232
|
+
return true;
|
|
233
|
+
}
|
|
166
234
|
remember(id, buckets) {
|
|
167
235
|
this.seen.delete(id);
|
|
168
236
|
this.seen.set(id, buckets);
|
|
@@ -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,6 +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 { agentsStoppedNoticeFromEvent, describeAgentsStopped, describeTaskStatus, isAgentsKilledSystemEvent, isTaskNotificationSystemEvent, taskNotificationFromEvent, taskStatusFromEvent, } from '../subagentSpawn';
|
|
12
|
+
import { describeSubagentActivity, subagentActivityFromEvent } from '../subagentActivity';
|
|
11
13
|
/**
|
|
12
14
|
* Converts a single SessionEvent to one or more FollowEvents.
|
|
13
15
|
*
|
|
@@ -148,6 +150,49 @@ export function toFollowEvents(event, providerId) {
|
|
|
148
150
|
break;
|
|
149
151
|
}
|
|
150
152
|
case 'system': {
|
|
153
|
+
// A background task's notice (opt-in reader event): its summary, not the raw block.
|
|
154
|
+
const notification = isTaskNotificationSystemEvent(event)
|
|
155
|
+
? taskNotificationFromEvent(event)
|
|
156
|
+
: null;
|
|
157
|
+
if (notification) {
|
|
158
|
+
events.push({
|
|
159
|
+
providerId,
|
|
160
|
+
type: 'system',
|
|
161
|
+
timestamp: ts,
|
|
162
|
+
summary: notification.summary ?? `Background task ${notification.status ?? 'finished'}`,
|
|
163
|
+
raw: event,
|
|
164
|
+
});
|
|
165
|
+
break;
|
|
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
|
+
}
|
|
151
196
|
const text = extractTextContent(event.message?.content);
|
|
152
197
|
const label = event.message?.sourceLabel || event.message?.role || 'system';
|
|
153
198
|
const summary = text ||
|
|
@@ -41,13 +41,15 @@ export function resolveSessionPath(provider, workspacePath, sessionId) {
|
|
|
41
41
|
export function createWatcher(options) {
|
|
42
42
|
const { provider, workspacePath, sessionId, callbacks } = options;
|
|
43
43
|
const sessionPath = resolveSessionPath(provider, workspacePath, sessionId);
|
|
44
|
-
const watcher = createWatcherForProvider(provider, sessionPath, callbacks
|
|
44
|
+
const watcher = createWatcherForProvider(provider, sessionPath, callbacks, {
|
|
45
|
+
includeTaskNotifications: options.includeTaskNotifications === true,
|
|
46
|
+
});
|
|
45
47
|
return { watcher, sessionPath };
|
|
46
48
|
}
|
|
47
|
-
function createWatcherForProvider(provider, sessionPath, callbacks) {
|
|
49
|
+
function createWatcherForProvider(provider, sessionPath, callbacks, jsonlOptions) {
|
|
48
50
|
switch (provider.id) {
|
|
49
51
|
case 'claude-code':
|
|
50
|
-
return new JsonlSessionWatcher(provider.id, sessionPath, callbacks);
|
|
52
|
+
return new JsonlSessionWatcher(provider.id, sessionPath, callbacks, jsonlOptions);
|
|
51
53
|
case 'codex':
|
|
52
54
|
return new ProviderReaderSessionWatcher(provider, sessionPath, callbacks);
|
|
53
55
|
case 'opencode': {
|