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,86 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* How to show a subagent: its lifecycle's outcome once something settled
|
|
4
|
+
* it, otherwise `running`, unless its transcript or its parent says it has
|
|
5
|
+
* stopped in all but name.
|
|
6
|
+
*
|
|
7
|
+
* Some background agents never get a final notification (they stopped after
|
|
8
|
+
* an interim "may resume" notice, the user stopped them from the UI, or a
|
|
9
|
+
* restart killed them). Their lifecycles stay `running`. This rule reads
|
|
10
|
+
* them as:
|
|
11
|
+
*
|
|
12
|
+
* - `finished-unreported`: the transcript ended cleanly (last assistant turn
|
|
13
|
+
* `end_turn` / `stop_sequence`, no tool call unanswered) and has been quiet
|
|
14
|
+
* for {@link SUBAGENT_UNREPORTED_QUIET_MS}.
|
|
15
|
+
* - `ended`: neither the parent session nor the transcript has been active for
|
|
16
|
+
* {@link SUBAGENT_PARENT_IDLE_ENDED_MS}.
|
|
17
|
+
*
|
|
18
|
+
* Neither is stored: the state is recomputed from the latest data on every
|
|
19
|
+
* call, so an agent that resumes (its transcript grows, or a later
|
|
20
|
+
* notification reopens its lifecycle) reads as `running` again. Silence alone
|
|
21
|
+
* is never enough for `finished-unreported`: live agents can be quiet for
|
|
22
|
+
* hours.
|
|
23
|
+
*
|
|
24
|
+
* Pure: no filesystem access. Browser-safe.
|
|
25
|
+
*
|
|
26
|
+
* @module subagentDisplayState
|
|
27
|
+
*/
|
|
28
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
29
|
+
exports.SUBAGENT_PARENT_IDLE_ENDED_MS = exports.SUBAGENT_UNREPORTED_QUIET_MS = void 0;
|
|
30
|
+
exports.resolveSubagentDisplayState = resolveSubagentDisplayState;
|
|
31
|
+
exports.subagentDisplayLabel = subagentDisplayLabel;
|
|
32
|
+
/** Quiet time after a clean end before a running agent reads as finished without a report (2 min). */
|
|
33
|
+
exports.SUBAGENT_UNREPORTED_QUIET_MS = 2 * 60_000;
|
|
34
|
+
/** Parent and transcript idle time after which a running agent reads as ended (6 h). */
|
|
35
|
+
exports.SUBAGENT_PARENT_IDLE_ENDED_MS = 6 * 60 * 60_000;
|
|
36
|
+
function toMs(value) {
|
|
37
|
+
if (value === null || value === undefined)
|
|
38
|
+
return undefined;
|
|
39
|
+
const ms = value instanceof Date ? value.getTime() : new Date(value).getTime();
|
|
40
|
+
return Number.isFinite(ms) ? ms : undefined;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Resolves how to show a subagent (see the module docs). Settled lifecycles
|
|
44
|
+
* map to their outcome (`completed` when none is recorded). A running one is
|
|
45
|
+
* `stopped` / `completed` when its meta flags say so, `finished-unreported`
|
|
46
|
+
* after a clean end and {@link SUBAGENT_UNREPORTED_QUIET_MS} of quiet,
|
|
47
|
+
* `ended` when the parent and transcript have both been idle for longer than
|
|
48
|
+
* {@link SUBAGENT_PARENT_IDLE_ENDED_MS}, and `running` otherwise.
|
|
49
|
+
*/
|
|
50
|
+
function resolveSubagentDisplayState(lifecycle, trace, options) {
|
|
51
|
+
if (lifecycle.status !== 'running') {
|
|
52
|
+
return { state: lifecycle.outcome ?? 'completed', reason: 'settled' };
|
|
53
|
+
}
|
|
54
|
+
if (trace?.stoppedByUser)
|
|
55
|
+
return { state: 'stopped', reason: 'meta' };
|
|
56
|
+
if (trace?.finished)
|
|
57
|
+
return { state: 'completed', reason: 'meta' };
|
|
58
|
+
const now = toMs(options.now) ?? Date.now();
|
|
59
|
+
const lastActivity = toMs(trace?.lastActivityAt);
|
|
60
|
+
if (trace?.endedCleanly &&
|
|
61
|
+
lastActivity !== undefined &&
|
|
62
|
+
now - lastActivity >= exports.SUBAGENT_UNREPORTED_QUIET_MS) {
|
|
63
|
+
return { state: 'finished-unreported', reason: 'clean-end' };
|
|
64
|
+
}
|
|
65
|
+
const parentLast = toMs(options.parentLastActivity);
|
|
66
|
+
if (parentLast !== undefined) {
|
|
67
|
+
const latest = Math.max(parentLast, lastActivity ?? -Infinity);
|
|
68
|
+
if (now - latest > exports.SUBAGENT_PARENT_IDLE_ENDED_MS) {
|
|
69
|
+
return { state: 'ended', reason: 'parent-idle' };
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return { state: 'running' };
|
|
73
|
+
}
|
|
74
|
+
const LABELS = {
|
|
75
|
+
running: 'running',
|
|
76
|
+
'finished-unreported': 'finished (no report)',
|
|
77
|
+
ended: 'ended',
|
|
78
|
+
completed: 'completed',
|
|
79
|
+
failed: 'failed',
|
|
80
|
+
stopped: 'stopped',
|
|
81
|
+
killed: 'killed',
|
|
82
|
+
};
|
|
83
|
+
/** A short label: `running`, `finished (no report)`, `ended`, `completed`, `failed`, `stopped`, `killed`. */
|
|
84
|
+
function subagentDisplayLabel(state) {
|
|
85
|
+
return LABELS[state] ?? state;
|
|
86
|
+
}
|
package/dist/subagentSpawn.d.ts
CHANGED
|
@@ -1,11 +1,18 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Recognizes
|
|
2
|
+
* Recognizes subagent spawns, background-task notifications, and the other
|
|
3
|
+
* records that settle a background agent.
|
|
3
4
|
*
|
|
4
5
|
* Claude Code's subagent tool is `Agent`; `Task` is its earlier name (and the
|
|
5
6
|
* name OpenCode's `task` tool is normalized to). A background agent's spawning
|
|
6
7
|
* tool call gets its result right away ("Async agent launched successfully.")
|
|
7
8
|
* and its completion arrives later as a `<task-notification>` block, delivered
|
|
8
|
-
* either as a user line or as a mid-turn `queued_command` attachment.
|
|
9
|
+
* either as a user line or as a mid-turn `queued_command` attachment. When no
|
|
10
|
+
* notification comes, a `task_status` attachment, an `agents_killed` system
|
|
11
|
+
* line, or a plain-text "background agents were stopped by the user" notice
|
|
12
|
+
* can still say how it ended.
|
|
13
|
+
*
|
|
14
|
+
* Codex spawns with `spawn_agent` (parsed as `Spawn_agent`); its result
|
|
15
|
+
* (`{"task_name": "/root/x"}`) is always a background launch.
|
|
9
16
|
*
|
|
10
17
|
* Pure: no filesystem access, so it is safe for browser bundles.
|
|
11
18
|
*
|
|
@@ -13,10 +20,35 @@
|
|
|
13
20
|
*/
|
|
14
21
|
import type { SubagentOutcome } from './aggregation/types';
|
|
15
22
|
import type { SessionEvent } from './types/sessionEvent';
|
|
16
|
-
/**
|
|
23
|
+
/**
|
|
24
|
+
* Claude Code / OpenCode tool names that spawn a subagent: `Agent` (current)
|
|
25
|
+
* and `Task` (earlier name). Codex's `spawn_agent` is not in this list;
|
|
26
|
+
* {@link isSubagentSpawnTool} recognizes it as well.
|
|
27
|
+
*/
|
|
17
28
|
export declare const SUBAGENT_SPAWN_TOOL_NAMES: readonly string[];
|
|
18
|
-
/**
|
|
19
|
-
|
|
29
|
+
/**
|
|
30
|
+
* Whether a tool call is Codex's `spawn_agent`: by name (`Spawn_agent` as
|
|
31
|
+
* parsed, or the raw `spawn_agent`), or by the raw name the Codex parser keeps
|
|
32
|
+
* in the input (`_sidekickRawToolName`).
|
|
33
|
+
*/
|
|
34
|
+
export declare function isCodexSubagentSpawnTool(name: string | null | undefined, input?: unknown): boolean;
|
|
35
|
+
/**
|
|
36
|
+
* Whether a tool call spawns a subagent: one of
|
|
37
|
+
* {@link SUBAGENT_SPAWN_TOOL_NAMES} (`Agent`, `Task`), or Codex's
|
|
38
|
+
* `spawn_agent` (see {@link isCodexSubagentSpawnTool}; pass the call's input
|
|
39
|
+
* to also match on its raw name).
|
|
40
|
+
*/
|
|
41
|
+
export declare function isSubagentSpawnTool(name: string | null | undefined, input?: unknown): boolean;
|
|
42
|
+
/** A Codex `spawn_agent` result: the child was started in the background. */
|
|
43
|
+
export interface CodexSpawnLaunch {
|
|
44
|
+
/** The child's agent path (`task_name` in the result), e.g. `/root/review_builder`. */
|
|
45
|
+
agentPath?: string;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Recognizes a Codex `spawn_agent` result (`{"task_name": "/root/x"}`, as a
|
|
49
|
+
* JSON string or object). `null` for anything else.
|
|
50
|
+
*/
|
|
51
|
+
export declare function parseCodexSpawnLaunch(result: unknown): CodexSpawnLaunch | null;
|
|
20
52
|
/** A spawning tool result that launched the agent in the background. */
|
|
21
53
|
export interface AsyncAgentLaunch {
|
|
22
54
|
/** The agent id Claude Code reported (`agentId: <id>`), when present. */
|
|
@@ -78,3 +110,66 @@ export declare function isTaskNotificationSystemEvent(event: SessionEvent): bool
|
|
|
78
110
|
export declare function taskNotificationFromEvent(event: SessionEvent): TaskNotification | null;
|
|
79
111
|
/** Maps a notification `<status>` to an outcome; unknown or missing statuses read as `completed`. */
|
|
80
112
|
export declare function taskNotificationOutcome(status: string | undefined): SubagentOutcome;
|
|
113
|
+
/** A plain-text notice that the user stopped background agents. */
|
|
114
|
+
export interface AgentsStoppedNotice {
|
|
115
|
+
/** The stopped agents' descriptions, as the notice quotes them. */
|
|
116
|
+
descriptions: string[];
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Parses Claude Code's plain-text notice that the user stopped background
|
|
120
|
+
* agents: `2 background agents were stopped by the user: "A", "B".` (a
|
|
121
|
+
* `queued_command` attachment's prompt) or `Background agent "A" was stopped
|
|
122
|
+
* by the user.` (a queue-operation line). `null` for any other text.
|
|
123
|
+
*/
|
|
124
|
+
export declare function parseAgentsStoppedNotice(text: unknown): AgentsStoppedNotice | null;
|
|
125
|
+
/**
|
|
126
|
+
* The stop notice a task-notification event carries (a `queued_command`
|
|
127
|
+
* prompt that is plain text, not a `<task-notification>` block). `null`
|
|
128
|
+
* otherwise.
|
|
129
|
+
*/
|
|
130
|
+
export declare function agentsStoppedNoticeFromEvent(event: SessionEvent): AgentsStoppedNotice | null;
|
|
131
|
+
/** Claude Code's `agents_killed` system line: the user killed every running background agent. */
|
|
132
|
+
export declare const AGENTS_KILLED_SUBTYPE = "agents_killed";
|
|
133
|
+
/**
|
|
134
|
+
* Whether an event, or a raw Claude Code JSONL line, is an `agents_killed`
|
|
135
|
+
* system line (`{ type: 'system', subtype: 'agents_killed' }`).
|
|
136
|
+
*/
|
|
137
|
+
export declare function isAgentsKilledSystemEvent(event: unknown): boolean;
|
|
138
|
+
/** A background agent's status from a Claude Code `task_status` attachment. */
|
|
139
|
+
export interface SubagentTaskStatus {
|
|
140
|
+
/** The background agent's id. */
|
|
141
|
+
taskId: string;
|
|
142
|
+
/** `local_agent` for background agents. */
|
|
143
|
+
taskType: string;
|
|
144
|
+
/** As written: `running`, `completed`, `failed`, `killed`, `stopped`, … */
|
|
145
|
+
status: string;
|
|
146
|
+
description?: string;
|
|
147
|
+
}
|
|
148
|
+
/** `origin.kind` of the opt-in `system` event made from a `task_status` attachment. */
|
|
149
|
+
export declare const TASK_STATUS_ORIGIN_KIND = "task-status";
|
|
150
|
+
/** `type` of the content block that carries the status. */
|
|
151
|
+
export declare const TASK_STATUS_BLOCK_TYPE = "task_status";
|
|
152
|
+
/**
|
|
153
|
+
* The background agent's status a raw Claude Code `task_status` attachment
|
|
154
|
+
* line reports (`{ type: 'attachment', attachment: { type: 'task_status',
|
|
155
|
+
* taskId, taskType: 'local_agent', status, description } }`). `null` for any
|
|
156
|
+
* other value, including background shell tasks (`local_bash`).
|
|
157
|
+
*/
|
|
158
|
+
export declare function taskStatusFromAttachment(raw: unknown): SubagentTaskStatus | null;
|
|
159
|
+
/** Whether a task status is final (anything but `running` / `pending`). */
|
|
160
|
+
export declare function isFinalTaskStatus(status: string | undefined): boolean;
|
|
161
|
+
/**
|
|
162
|
+
* The synthetic `system` event `extractSessionEvents(raw, {
|
|
163
|
+
* includeTaskNotifications: true })` makes from a background agent's
|
|
164
|
+
* `task_status` attachment. It is bookkeeping, not a message or a prompt.
|
|
165
|
+
*/
|
|
166
|
+
export declare function isTaskStatusSystemEvent(event: SessionEvent): boolean;
|
|
167
|
+
/** The status an opt-in task-status `system` event carries; `null` for any other event. */
|
|
168
|
+
export declare function taskStatusFromEvent(event: SessionEvent): SubagentTaskStatus | null;
|
|
169
|
+
/**
|
|
170
|
+
* A one-line description of a stop: `Background agents stopped by the user (2)`,
|
|
171
|
+
* or, for an `agents_killed` line (`null`), `Background agents stopped by the user`.
|
|
172
|
+
*/
|
|
173
|
+
export declare function describeAgentsStopped(notice: AgentsStoppedNotice | null): string;
|
|
174
|
+
/** A one-line description of a task status, e.g. `Background agent "Explore auth" completed`. */
|
|
175
|
+
export declare function describeTaskStatus(status: SubagentTaskStatus): string;
|
package/dist/subagentSpawn.js
CHANGED
|
@@ -1,20 +1,29 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/**
|
|
3
|
-
* Recognizes
|
|
3
|
+
* Recognizes subagent spawns, background-task notifications, and the other
|
|
4
|
+
* records that settle a background agent.
|
|
4
5
|
*
|
|
5
6
|
* Claude Code's subagent tool is `Agent`; `Task` is its earlier name (and the
|
|
6
7
|
* name OpenCode's `task` tool is normalized to). A background agent's spawning
|
|
7
8
|
* tool call gets its result right away ("Async agent launched successfully.")
|
|
8
9
|
* and its completion arrives later as a `<task-notification>` block, delivered
|
|
9
|
-
* either as a user line or as a mid-turn `queued_command` attachment.
|
|
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.
|
|
10
17
|
*
|
|
11
18
|
* Pure: no filesystem access, so it is safe for browser bundles.
|
|
12
19
|
*
|
|
13
20
|
* @module subagentSpawn
|
|
14
21
|
*/
|
|
15
22
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
16
|
-
exports.SUBAGENT_SPAWN_TOOL_NAMES = void 0;
|
|
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;
|
|
17
25
|
exports.isSubagentSpawnTool = isSubagentSpawnTool;
|
|
26
|
+
exports.parseCodexSpawnLaunch = parseCodexSpawnLaunch;
|
|
18
27
|
exports.contentText = contentText;
|
|
19
28
|
exports.parseAsyncAgentLaunch = parseAsyncAgentLaunch;
|
|
20
29
|
exports.parseTaskNotification = parseTaskNotification;
|
|
@@ -22,12 +31,75 @@ exports.taskNotificationPromptFromAttachment = taskNotificationPromptFromAttachm
|
|
|
22
31
|
exports.isTaskNotificationSystemEvent = isTaskNotificationSystemEvent;
|
|
23
32
|
exports.taskNotificationFromEvent = taskNotificationFromEvent;
|
|
24
33
|
exports.taskNotificationOutcome = taskNotificationOutcome;
|
|
25
|
-
|
|
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
|
+
*/
|
|
26
48
|
exports.SUBAGENT_SPAWN_TOOL_NAMES = Object.freeze(['Agent', 'Task']);
|
|
27
49
|
const SPAWN_TOOLS = new Set(exports.SUBAGENT_SPAWN_TOOL_NAMES);
|
|
28
|
-
/**
|
|
29
|
-
|
|
30
|
-
|
|
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 } : {};
|
|
31
103
|
}
|
|
32
104
|
const ASYNC_LAUNCH_PREFIX = /^\s*Async agent launched successfully\b/;
|
|
33
105
|
const AGENT_ID_LINE = /(?:^|\n)[ \t]*agentId:[ \t]*([A-Za-z0-9][\w.@-]*)/;
|
|
@@ -172,3 +244,142 @@ function taskNotificationOutcome(status) {
|
|
|
172
244
|
return 'completed';
|
|
173
245
|
}
|
|
174
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;
|