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,385 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Recognizes subagent spawns, background-task notifications, and the other
|
|
4
|
+
* records that settle a background agent.
|
|
5
|
+
*
|
|
6
|
+
* Claude Code's subagent tool is `Agent`; `Task` is its earlier name (and the
|
|
7
|
+
* name OpenCode's `task` tool is normalized to). A background agent's spawning
|
|
8
|
+
* tool call gets its result right away ("Async agent launched successfully.")
|
|
9
|
+
* and its completion arrives later as a `<task-notification>` block, delivered
|
|
10
|
+
* either as a user line or as a mid-turn `queued_command` attachment. When no
|
|
11
|
+
* notification comes, a `task_status` attachment, an `agents_killed` system
|
|
12
|
+
* line, or a plain-text "background agents were stopped by the user" notice
|
|
13
|
+
* can still say how it ended.
|
|
14
|
+
*
|
|
15
|
+
* Codex spawns with `spawn_agent` (parsed as `Spawn_agent`); its result
|
|
16
|
+
* (`{"task_name": "/root/x"}`) is always a background launch.
|
|
17
|
+
*
|
|
18
|
+
* Pure: no filesystem access, so it is safe for browser bundles.
|
|
19
|
+
*
|
|
20
|
+
* @module subagentSpawn
|
|
21
|
+
*/
|
|
22
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
23
|
+
exports.TASK_STATUS_BLOCK_TYPE = exports.TASK_STATUS_ORIGIN_KIND = exports.AGENTS_KILLED_SUBTYPE = exports.SUBAGENT_SPAWN_TOOL_NAMES = void 0;
|
|
24
|
+
exports.isCodexSubagentSpawnTool = isCodexSubagentSpawnTool;
|
|
25
|
+
exports.isSubagentSpawnTool = isSubagentSpawnTool;
|
|
26
|
+
exports.parseCodexSpawnLaunch = parseCodexSpawnLaunch;
|
|
27
|
+
exports.contentText = contentText;
|
|
28
|
+
exports.parseAsyncAgentLaunch = parseAsyncAgentLaunch;
|
|
29
|
+
exports.parseTaskNotification = parseTaskNotification;
|
|
30
|
+
exports.taskNotificationPromptFromAttachment = taskNotificationPromptFromAttachment;
|
|
31
|
+
exports.isTaskNotificationSystemEvent = isTaskNotificationSystemEvent;
|
|
32
|
+
exports.taskNotificationFromEvent = taskNotificationFromEvent;
|
|
33
|
+
exports.taskNotificationOutcome = taskNotificationOutcome;
|
|
34
|
+
exports.parseAgentsStoppedNotice = parseAgentsStoppedNotice;
|
|
35
|
+
exports.agentsStoppedNoticeFromEvent = agentsStoppedNoticeFromEvent;
|
|
36
|
+
exports.isAgentsKilledSystemEvent = isAgentsKilledSystemEvent;
|
|
37
|
+
exports.taskStatusFromAttachment = taskStatusFromAttachment;
|
|
38
|
+
exports.isFinalTaskStatus = isFinalTaskStatus;
|
|
39
|
+
exports.isTaskStatusSystemEvent = isTaskStatusSystemEvent;
|
|
40
|
+
exports.taskStatusFromEvent = taskStatusFromEvent;
|
|
41
|
+
exports.describeAgentsStopped = describeAgentsStopped;
|
|
42
|
+
exports.describeTaskStatus = describeTaskStatus;
|
|
43
|
+
/**
|
|
44
|
+
* Claude Code / OpenCode tool names that spawn a subagent: `Agent` (current)
|
|
45
|
+
* and `Task` (earlier name). Codex's `spawn_agent` is not in this list;
|
|
46
|
+
* {@link isSubagentSpawnTool} recognizes it as well.
|
|
47
|
+
*/
|
|
48
|
+
exports.SUBAGENT_SPAWN_TOOL_NAMES = Object.freeze(['Agent', 'Task']);
|
|
49
|
+
const SPAWN_TOOLS = new Set(exports.SUBAGENT_SPAWN_TOOL_NAMES);
|
|
50
|
+
/** Codex's raw spawn tool name, and the name the Codex parser normalizes it to. */
|
|
51
|
+
const CODEX_SPAWN_RAW = 'spawn_agent';
|
|
52
|
+
const CODEX_SPAWN_TOOLS = new Set([CODEX_SPAWN_RAW, 'Spawn_agent']);
|
|
53
|
+
/**
|
|
54
|
+
* Whether a tool call is Codex's `spawn_agent`: by name (`Spawn_agent` as
|
|
55
|
+
* parsed, or the raw `spawn_agent`), or by the raw name the Codex parser keeps
|
|
56
|
+
* in the input (`_sidekickRawToolName`).
|
|
57
|
+
*/
|
|
58
|
+
function isCodexSubagentSpawnTool(name, input) {
|
|
59
|
+
if (typeof name === 'string' && CODEX_SPAWN_TOOLS.has(name))
|
|
60
|
+
return true;
|
|
61
|
+
return (!!input &&
|
|
62
|
+
typeof input === 'object' &&
|
|
63
|
+
input._sidekickRawToolName === CODEX_SPAWN_RAW);
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Whether a tool call spawns a subagent: one of
|
|
67
|
+
* {@link SUBAGENT_SPAWN_TOOL_NAMES} (`Agent`, `Task`), or Codex's
|
|
68
|
+
* `spawn_agent` (see {@link isCodexSubagentSpawnTool}; pass the call's input
|
|
69
|
+
* to also match on its raw name).
|
|
70
|
+
*/
|
|
71
|
+
function isSubagentSpawnTool(name, input) {
|
|
72
|
+
if (typeof name === 'string' && SPAWN_TOOLS.has(name))
|
|
73
|
+
return true;
|
|
74
|
+
return isCodexSubagentSpawnTool(name, input);
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Recognizes a Codex `spawn_agent` result (`{"task_name": "/root/x"}`, as a
|
|
78
|
+
* JSON string or object). `null` for anything else.
|
|
79
|
+
*/
|
|
80
|
+
function parseCodexSpawnLaunch(result) {
|
|
81
|
+
let value = result;
|
|
82
|
+
if (typeof value !== 'string') {
|
|
83
|
+
const text = contentText(value);
|
|
84
|
+
value = text || value;
|
|
85
|
+
}
|
|
86
|
+
if (typeof value === 'string') {
|
|
87
|
+
const trimmed = value.trim();
|
|
88
|
+
if (!trimmed.startsWith('{'))
|
|
89
|
+
return null;
|
|
90
|
+
try {
|
|
91
|
+
value = JSON.parse(trimmed);
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
98
|
+
return null;
|
|
99
|
+
const taskName = value.task_name;
|
|
100
|
+
if (typeof taskName !== 'string')
|
|
101
|
+
return null;
|
|
102
|
+
return taskName ? { agentPath: taskName } : {};
|
|
103
|
+
}
|
|
104
|
+
const ASYNC_LAUNCH_PREFIX = /^\s*Async agent launched successfully\b/;
|
|
105
|
+
const AGENT_ID_LINE = /(?:^|\n)[ \t]*agentId:[ \t]*([A-Za-z0-9][\w.@-]*)/;
|
|
106
|
+
/**
|
|
107
|
+
* Plain text of tool-result or message content: a string as-is, or the text
|
|
108
|
+
* blocks of a content array joined by newlines.
|
|
109
|
+
*/
|
|
110
|
+
function contentText(content) {
|
|
111
|
+
if (typeof content === 'string')
|
|
112
|
+
return content;
|
|
113
|
+
if (!Array.isArray(content))
|
|
114
|
+
return '';
|
|
115
|
+
const parts = [];
|
|
116
|
+
for (const block of content) {
|
|
117
|
+
if (!block || typeof block !== 'object')
|
|
118
|
+
continue;
|
|
119
|
+
const typed = block;
|
|
120
|
+
if (typed.type === 'text' && typeof typed.text === 'string')
|
|
121
|
+
parts.push(typed.text);
|
|
122
|
+
}
|
|
123
|
+
return parts.join('\n');
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Recognizes the immediate result of a background spawn
|
|
127
|
+
* (`Async agent launched successfully.` … `agentId: <id> …`). Accepts the
|
|
128
|
+
* result text or the raw tool-result content. Returns `null` for anything
|
|
129
|
+
* else, including a finished foreground agent's result.
|
|
130
|
+
*/
|
|
131
|
+
function parseAsyncAgentLaunch(result) {
|
|
132
|
+
const text = contentText(result);
|
|
133
|
+
if (!ASYNC_LAUNCH_PREFIX.test(text))
|
|
134
|
+
return null;
|
|
135
|
+
const match = AGENT_ID_LINE.exec(text);
|
|
136
|
+
return match ? { agentId: match[1] } : {};
|
|
137
|
+
}
|
|
138
|
+
const NOTIFICATION_OPEN = '<task-notification>';
|
|
139
|
+
const NOTIFICATION_CLOSE = '</task-notification>';
|
|
140
|
+
const NOTIFICATION_PREFIX = /^\s*<task-notification>/;
|
|
141
|
+
const TAG_PATTERNS = {
|
|
142
|
+
taskId: /<task-id>\s*([\s\S]*?)\s*<\/task-id>/,
|
|
143
|
+
toolUseId: /<tool-use-id>\s*([\s\S]*?)\s*<\/tool-use-id>/,
|
|
144
|
+
status: /<status>\s*([\s\S]*?)\s*<\/status>/,
|
|
145
|
+
summary: /<summary>\s*([\s\S]*?)\s*<\/summary>/,
|
|
146
|
+
note: /<note>\s*([\s\S]*?)\s*<\/note>/,
|
|
147
|
+
};
|
|
148
|
+
const INTERIM_TEXT = /waiting on its own background work|background work of its own still running/i;
|
|
149
|
+
function tagValue(body, pattern) {
|
|
150
|
+
const value = pattern.exec(body)?.[1]?.trim();
|
|
151
|
+
return value ? value : undefined;
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Parses the first `<task-notification>` block in `text`. Whitespace and
|
|
155
|
+
* newlines between tags, missing fields, and unknown extra fields are
|
|
156
|
+
* tolerated; the block needs a `<task-id>`. Fields are read before the
|
|
157
|
+
* agent's own `<result>` output, which may quote anything.
|
|
158
|
+
*/
|
|
159
|
+
function parseTaskNotification(text) {
|
|
160
|
+
if (typeof text !== 'string')
|
|
161
|
+
return null;
|
|
162
|
+
const start = text.indexOf(NOTIFICATION_OPEN);
|
|
163
|
+
if (start < 0)
|
|
164
|
+
return null;
|
|
165
|
+
let body = text.slice(start + NOTIFICATION_OPEN.length);
|
|
166
|
+
const resultAt = body.indexOf('<result>');
|
|
167
|
+
let header = resultAt >= 0 ? body.slice(0, resultAt) : body;
|
|
168
|
+
if (resultAt < 0) {
|
|
169
|
+
const end = body.indexOf(NOTIFICATION_CLOSE);
|
|
170
|
+
if (end >= 0)
|
|
171
|
+
header = body = body.slice(0, end);
|
|
172
|
+
}
|
|
173
|
+
if (!TAG_PATTERNS.taskId.test(header))
|
|
174
|
+
header = body;
|
|
175
|
+
const taskId = tagValue(header, TAG_PATTERNS.taskId);
|
|
176
|
+
if (!taskId)
|
|
177
|
+
return null;
|
|
178
|
+
const toolUseId = tagValue(header, TAG_PATTERNS.toolUseId);
|
|
179
|
+
const status = tagValue(header, TAG_PATTERNS.status);
|
|
180
|
+
const summary = tagValue(header, TAG_PATTERNS.summary);
|
|
181
|
+
const note = tagValue(header, TAG_PATTERNS.note);
|
|
182
|
+
return {
|
|
183
|
+
taskId,
|
|
184
|
+
...(toolUseId ? { toolUseId } : {}),
|
|
185
|
+
...(status ? { status } : {}),
|
|
186
|
+
...(summary ? { summary } : {}),
|
|
187
|
+
interim: INTERIM_TEXT.test(summary ?? '') || INTERIM_TEXT.test(note ?? ''),
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* The `<task-notification>` prompt of a raw Claude Code attachment line
|
|
192
|
+
* (`{ type: 'attachment', attachment: { type: 'queued_command', commandMode:
|
|
193
|
+
* 'task-notification', prompt } }`), which is how a notification that lands
|
|
194
|
+
* mid-turn is recorded. `null` for any other value.
|
|
195
|
+
*/
|
|
196
|
+
function taskNotificationPromptFromAttachment(raw) {
|
|
197
|
+
if (!raw || typeof raw !== 'object')
|
|
198
|
+
return null;
|
|
199
|
+
const line = raw;
|
|
200
|
+
if (line.type !== 'attachment' || !line.attachment || typeof line.attachment !== 'object') {
|
|
201
|
+
return null;
|
|
202
|
+
}
|
|
203
|
+
const attachment = line.attachment;
|
|
204
|
+
return attachment.type === 'queued_command' &&
|
|
205
|
+
attachment.commandMode === 'task-notification' &&
|
|
206
|
+
typeof attachment.prompt === 'string'
|
|
207
|
+
? attachment.prompt
|
|
208
|
+
: null;
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* The synthetic `system` event `extractSessionEvents(raw, {
|
|
212
|
+
* includeTaskNotifications: true })` makes from a mid-turn task-notification
|
|
213
|
+
* attachment. It is bookkeeping, not a message or a prompt.
|
|
214
|
+
*/
|
|
215
|
+
function isTaskNotificationSystemEvent(event) {
|
|
216
|
+
return event.type === 'system' && event.origin?.kind === 'task-notification';
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* The task notification an event carries: a user line with
|
|
220
|
+
* `origin.kind === 'task-notification'` or whose text starts with
|
|
221
|
+
* `<task-notification>`, or the opt-in synthetic system event. `null` otherwise.
|
|
222
|
+
*/
|
|
223
|
+
function taskNotificationFromEvent(event) {
|
|
224
|
+
if (event.type !== 'user' && event.type !== 'system')
|
|
225
|
+
return null;
|
|
226
|
+
const flagged = event.origin?.kind === 'task-notification';
|
|
227
|
+
if (event.type === 'system' && !flagged)
|
|
228
|
+
return null;
|
|
229
|
+
const text = contentText(event.message?.content);
|
|
230
|
+
if (!flagged && !NOTIFICATION_PREFIX.test(text))
|
|
231
|
+
return null;
|
|
232
|
+
return parseTaskNotification(text);
|
|
233
|
+
}
|
|
234
|
+
/** Maps a notification `<status>` to an outcome; unknown or missing statuses read as `completed`. */
|
|
235
|
+
function taskNotificationOutcome(status) {
|
|
236
|
+
switch (status?.toLowerCase()) {
|
|
237
|
+
case 'failed':
|
|
238
|
+
return 'failed';
|
|
239
|
+
case 'killed':
|
|
240
|
+
return 'killed';
|
|
241
|
+
case 'stopped':
|
|
242
|
+
return 'stopped';
|
|
243
|
+
default:
|
|
244
|
+
return 'completed';
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
const STOPPED_PLURAL = /^\s*(\d+)\s+background agents? (?:were|was) stopped by the user:\s*([\s\S]*?)\s*$/i;
|
|
248
|
+
const STOPPED_SINGULAR = /^\s*Background agent "([\s\S]*)" was stopped by the user\.?\s*$/i;
|
|
249
|
+
/**
|
|
250
|
+
* Parses Claude Code's plain-text notice that the user stopped background
|
|
251
|
+
* agents: `2 background agents were stopped by the user: "A", "B".` (a
|
|
252
|
+
* `queued_command` attachment's prompt) or `Background agent "A" was stopped
|
|
253
|
+
* by the user.` (a queue-operation line). `null` for any other text.
|
|
254
|
+
*/
|
|
255
|
+
function parseAgentsStoppedNotice(text) {
|
|
256
|
+
if (typeof text !== 'string')
|
|
257
|
+
return null;
|
|
258
|
+
const singular = STOPPED_SINGULAR.exec(text);
|
|
259
|
+
if (singular)
|
|
260
|
+
return { descriptions: [singular[1].trim()].filter(Boolean) };
|
|
261
|
+
const plural = STOPPED_PLURAL.exec(text);
|
|
262
|
+
if (!plural)
|
|
263
|
+
return null;
|
|
264
|
+
let list = plural[2].trim();
|
|
265
|
+
if (list.endsWith('.'))
|
|
266
|
+
list = list.slice(0, -1).trimEnd();
|
|
267
|
+
if (!list.startsWith('"') || !list.endsWith('"') || list.length < 2)
|
|
268
|
+
return null;
|
|
269
|
+
const descriptions = list
|
|
270
|
+
.slice(1, -1)
|
|
271
|
+
.split(/",\s*"/)
|
|
272
|
+
.map((description) => description.trim())
|
|
273
|
+
.filter(Boolean);
|
|
274
|
+
return descriptions.length > 0 ? { descriptions } : null;
|
|
275
|
+
}
|
|
276
|
+
/**
|
|
277
|
+
* The stop notice a task-notification event carries (a `queued_command`
|
|
278
|
+
* prompt that is plain text, not a `<task-notification>` block). `null`
|
|
279
|
+
* otherwise.
|
|
280
|
+
*/
|
|
281
|
+
function agentsStoppedNoticeFromEvent(event) {
|
|
282
|
+
if (!isTaskNotificationSystemEvent(event))
|
|
283
|
+
return null;
|
|
284
|
+
return parseAgentsStoppedNotice(contentText(event.message?.content));
|
|
285
|
+
}
|
|
286
|
+
/** Claude Code's `agents_killed` system line: the user killed every running background agent. */
|
|
287
|
+
exports.AGENTS_KILLED_SUBTYPE = 'agents_killed';
|
|
288
|
+
/**
|
|
289
|
+
* Whether an event, or a raw Claude Code JSONL line, is an `agents_killed`
|
|
290
|
+
* system line (`{ type: 'system', subtype: 'agents_killed' }`).
|
|
291
|
+
*/
|
|
292
|
+
function isAgentsKilledSystemEvent(event) {
|
|
293
|
+
if (!event || typeof event !== 'object')
|
|
294
|
+
return false;
|
|
295
|
+
const line = event;
|
|
296
|
+
return line.type === 'system' && line.subtype === exports.AGENTS_KILLED_SUBTYPE;
|
|
297
|
+
}
|
|
298
|
+
/** `origin.kind` of the opt-in `system` event made from a `task_status` attachment. */
|
|
299
|
+
exports.TASK_STATUS_ORIGIN_KIND = 'task-status';
|
|
300
|
+
/** `type` of the content block that carries the status. */
|
|
301
|
+
exports.TASK_STATUS_BLOCK_TYPE = 'task_status';
|
|
302
|
+
const AGENT_TASK_TYPE = 'local_agent';
|
|
303
|
+
function nonEmpty(value) {
|
|
304
|
+
return typeof value === 'string' && value.trim() ? value.trim() : undefined;
|
|
305
|
+
}
|
|
306
|
+
/**
|
|
307
|
+
* The background agent's status a raw Claude Code `task_status` attachment
|
|
308
|
+
* line reports (`{ type: 'attachment', attachment: { type: 'task_status',
|
|
309
|
+
* taskId, taskType: 'local_agent', status, description } }`). `null` for any
|
|
310
|
+
* other value, including background shell tasks (`local_bash`).
|
|
311
|
+
*/
|
|
312
|
+
function taskStatusFromAttachment(raw) {
|
|
313
|
+
if (!raw || typeof raw !== 'object')
|
|
314
|
+
return null;
|
|
315
|
+
const line = raw;
|
|
316
|
+
if (line.type !== 'attachment' || !line.attachment || typeof line.attachment !== 'object') {
|
|
317
|
+
return null;
|
|
318
|
+
}
|
|
319
|
+
const attachment = line.attachment;
|
|
320
|
+
if (attachment.type !== 'task_status' || attachment.taskType !== AGENT_TASK_TYPE)
|
|
321
|
+
return null;
|
|
322
|
+
const taskId = nonEmpty(attachment.taskId);
|
|
323
|
+
const status = nonEmpty(attachment.status);
|
|
324
|
+
if (!taskId || !status)
|
|
325
|
+
return null;
|
|
326
|
+
const description = nonEmpty(attachment.description);
|
|
327
|
+
return { taskId, taskType: AGENT_TASK_TYPE, status, ...(description ? { description } : {}) };
|
|
328
|
+
}
|
|
329
|
+
/** Whether a task status is final (anything but `running` / `pending`). */
|
|
330
|
+
function isFinalTaskStatus(status) {
|
|
331
|
+
if (!status)
|
|
332
|
+
return false;
|
|
333
|
+
const lower = status.toLowerCase();
|
|
334
|
+
return lower !== 'running' && lower !== 'pending';
|
|
335
|
+
}
|
|
336
|
+
/**
|
|
337
|
+
* The synthetic `system` event `extractSessionEvents(raw, {
|
|
338
|
+
* includeTaskNotifications: true })` makes from a background agent's
|
|
339
|
+
* `task_status` attachment. It is bookkeeping, not a message or a prompt.
|
|
340
|
+
*/
|
|
341
|
+
function isTaskStatusSystemEvent(event) {
|
|
342
|
+
return event.type === 'system' && event.origin?.kind === exports.TASK_STATUS_ORIGIN_KIND;
|
|
343
|
+
}
|
|
344
|
+
/** The status an opt-in task-status `system` event carries; `null` for any other event. */
|
|
345
|
+
function taskStatusFromEvent(event) {
|
|
346
|
+
if (!isTaskStatusSystemEvent(event))
|
|
347
|
+
return null;
|
|
348
|
+
const content = event.message?.content;
|
|
349
|
+
if (!Array.isArray(content))
|
|
350
|
+
return null;
|
|
351
|
+
for (const block of content) {
|
|
352
|
+
if (!block || typeof block !== 'object')
|
|
353
|
+
continue;
|
|
354
|
+
const raw = block;
|
|
355
|
+
if (raw.type !== exports.TASK_STATUS_BLOCK_TYPE)
|
|
356
|
+
continue;
|
|
357
|
+
const taskId = nonEmpty(raw.taskId);
|
|
358
|
+
const status = nonEmpty(raw.status);
|
|
359
|
+
if (!taskId || !status)
|
|
360
|
+
return null;
|
|
361
|
+
const description = nonEmpty(raw.description);
|
|
362
|
+
return {
|
|
363
|
+
taskId,
|
|
364
|
+
taskType: nonEmpty(raw.taskType) ?? AGENT_TASK_TYPE,
|
|
365
|
+
status,
|
|
366
|
+
...(description ? { description } : {}),
|
|
367
|
+
};
|
|
368
|
+
}
|
|
369
|
+
return null;
|
|
370
|
+
}
|
|
371
|
+
/**
|
|
372
|
+
* A one-line description of a stop: `Background agents stopped by the user (2)`,
|
|
373
|
+
* or, for an `agents_killed` line (`null`), `Background agents stopped by the user`.
|
|
374
|
+
*/
|
|
375
|
+
function describeAgentsStopped(notice) {
|
|
376
|
+
const count = notice?.descriptions.length;
|
|
377
|
+
return count
|
|
378
|
+
? `Background agents stopped by the user (${count})`
|
|
379
|
+
: 'Background agents stopped by the user';
|
|
380
|
+
}
|
|
381
|
+
/** A one-line description of a task status, e.g. `Background agent "Explore auth" completed`. */
|
|
382
|
+
function describeTaskStatus(status) {
|
|
383
|
+
const name = status.description ? `Background agent "${status.description}"` : 'Background agent';
|
|
384
|
+
return `${name} ${status.status}`;
|
|
385
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
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 type CodexFixtureRow = Record<string, unknown>;
|
|
16
|
+
export declare const PARENT_ID = "01a18f87-0000-7000-8000-000000000001";
|
|
17
|
+
export declare const CHILD_ID = "01a18f9d-0000-7000-8000-000000000002";
|
|
18
|
+
export declare const GRANDCHILD_ID = "01a18fa0-0000-7000-8000-000000000003";
|
|
19
|
+
export declare const CALL_SPAWN = "call_spawn_builder";
|
|
20
|
+
export declare const CALL_SPAWN_FAILED = "call_spawn_failed";
|
|
21
|
+
export declare const CALL_FOLLOWUP = "call_followup_builder";
|
|
22
|
+
export declare const CALL_SEND = "call_send_builder";
|
|
23
|
+
export declare const CALL_INTERRUPT = "call_interrupt_builder";
|
|
24
|
+
export declare const CALL_SPAWN_GRANDCHILD = "call_spawn_deep_dive";
|
|
25
|
+
export declare const CHILD_TURN_ID = "01a18f9d-0000-7000-8000-0000000000t1";
|
|
26
|
+
export declare const CHILD_PATH = "/root/review_builder";
|
|
27
|
+
export declare const GRANDCHILD_PATH = "/root/review_builder/deep_dive";
|
|
28
|
+
/** `2026-10-31T01:<minute>:<second>.000Z`. */
|
|
29
|
+
export declare function at(minute: number, second?: number): string;
|
|
30
|
+
export interface ParentFixtureOptions {
|
|
31
|
+
cwd: string;
|
|
32
|
+
/** Leave out the `SubAgentActivity` records (as before Codex recorded them). */
|
|
33
|
+
withoutActivity?: boolean;
|
|
34
|
+
/** Leave out the `completed` record (Codex before 0.150). */
|
|
35
|
+
withoutCompleted?: boolean;
|
|
36
|
+
}
|
|
37
|
+
/** The parent session's rollout (ordinals 0…). */
|
|
38
|
+
export declare function parentRollout(options: ParentFixtureOptions): CodexFixtureRow[];
|
|
39
|
+
/** The forked child's rollout: its meta, the parent's history, then its own records. */
|
|
40
|
+
export declare function childRollout(options: {
|
|
41
|
+
cwd: string;
|
|
42
|
+
}): CodexFixtureRow[];
|
|
43
|
+
/** The grandchild's rollout (depth 2): a fork of the child. */
|
|
44
|
+
export declare function grandchildRollout(options: {
|
|
45
|
+
cwd: string;
|
|
46
|
+
}): CodexFixtureRow[];
|
|
47
|
+
/** Rows as JSONL text. */
|
|
48
|
+
export declare function toJsonl(rows: CodexFixtureRow[]): string;
|