@agentclientprotocol/claude-agent-acp 0.99.2-preview.9 → 0.100.1-preview.1

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.
@@ -0,0 +1,212 @@
1
+ import type { AcpSessionNotification } from "./acp-subagents.js";
2
+ import type { NativeSubagent } from "./native-subagents.js";
3
+ import { type AcpToolCallRenderer } from "./tool-calls/renderer.js";
4
+ import { BackgroundOutputTail } from "./background-output.js";
5
+ /** An item of a `background` list. */
6
+ export type BackgroundItem = {
7
+ type: "terminal";
8
+ terminalId: string;
9
+ } | {
10
+ type: "tool_call";
11
+ toolCallId: string;
12
+ } | {
13
+ type: "subagent";
14
+ sessionId: string;
15
+ } | {
16
+ type: "_claude/task";
17
+ taskId: string;
18
+ description: string;
19
+ };
20
+ /** The display terminal of a command that went to the background (see `Session.backgroundTerminals`). */
21
+ export interface BackgroundTerminal {
22
+ /** The Bash tool call, whose id names the terminal. */
23
+ toolCallId: string;
24
+ /** The subagent tool call that ran the command, if any. */
25
+ parentToolUseId?: string;
26
+ /** The file that Claude Code writes the command's output to, from the Bash result. */
27
+ outputFilePath?: string;
28
+ /** Reads that file into the terminal while the command runs. */
29
+ output?: BackgroundOutputTail;
30
+ }
31
+ /** A tool call whose task runs on in the background (see `Session.backgroundToolCalls`). */
32
+ export interface BackgroundToolCall {
33
+ toolCallId: string;
34
+ /** The tool use of the call, which Claude Code no longer caches after its result. */
35
+ toolUse: {
36
+ name: string;
37
+ input: unknown;
38
+ };
39
+ /** The subagent tool call that made the call, if any. */
40
+ parentToolUseId?: string;
41
+ paused?: boolean;
42
+ }
43
+ /** How a background task ended, as Claude Code reports it. Undefined when it did not say. */
44
+ export type BackgroundTaskEnd = "completed" | "failed" | "stopped" | undefined;
45
+ /** The live background task set of `background_tasks_changed`, by task id. */
46
+ export type BackgroundLevel = Map<string, {
47
+ ambient: boolean;
48
+ taskType: string;
49
+ description: string;
50
+ parentTaskId?: string;
51
+ }>;
52
+ /** The work state of a session, as its `state_update` names it. */
53
+ export type WorkStateName = "idle" | "running" | "requires_action" | "unknown";
54
+ /** A new list of the session `sessionId`, with what its repeated state needs. */
55
+ export interface BackgroundReport {
56
+ sessionId: string;
57
+ items: BackgroundItem[];
58
+ /** The session's work state now, or undefined when it has none yet. */
59
+ state: WorkStateName | undefined;
60
+ /** The parent of a subagent session, which mirrors the child's state. */
61
+ parentSessionId?: string;
62
+ }
63
+ /** Where the agent reports the lists (see the agent's `background` option). */
64
+ export interface BackgroundSink {
65
+ /** The list of a session changed, or is reported for the first time. */
66
+ changed(report: BackgroundReport): void;
67
+ /** The session ended: its list is no longer reported. */
68
+ forget(sessionId: string): void;
69
+ }
70
+ /** The state of a session that its lists are built from. */
71
+ export interface BackgroundWorkSession {
72
+ reportsBackground?: boolean;
73
+ backgroundLevel?: BackgroundLevel;
74
+ backgroundTerminals?: Map<string, BackgroundTerminal>;
75
+ backgroundToolCalls?: Map<string, BackgroundToolCall>;
76
+ nativeSubagentsByTaskId?: Map<string, NativeSubagent>;
77
+ liveBackgroundTasks?: Map<string, {
78
+ parentToolUseId?: string;
79
+ isSubagent: boolean;
80
+ }>;
81
+ /** The tool uses whose result has not come yet, by id. */
82
+ toolUseCache?: Record<string, unknown>;
83
+ query: {
84
+ stopTask(taskId: string): Promise<void>;
85
+ };
86
+ }
87
+ /**
88
+ * Registers each tool call of a tool result message whose task goes on in
89
+ * the background: a `Monitor` or `Workflow` call, whose structured result
90
+ * names its task, or a call that a live task names as its tool use, such as
91
+ * a backgrounded MCP call. The task must be in the level: one that ended
92
+ * already, or that Claude Code marks `ambient`, leaves its call as it is.
93
+ * Returns the ids of the calls, whose result then reports `in_progress` (see
94
+ * `withRunningToolCall`).
95
+ */
96
+ export declare function registerBackgroundToolCalls(session: BackgroundWorkSession, content: unknown, toolUseResult: unknown, toolUses: Record<string, {
97
+ name: string;
98
+ input: unknown;
99
+ }>, parentToolUseId: string | null): Set<string>;
100
+ /** The level of a `background_tasks_changed` payload. */
101
+ export declare function backgroundLevel(tasks: {
102
+ task_id: string;
103
+ task_type: string;
104
+ description: string;
105
+ parent_task_id?: string;
106
+ ambient?: boolean;
107
+ }[]): BackgroundLevel;
108
+ /**
109
+ * The `background` list of the root session `rootSessionId` and of each of
110
+ * its announced subagent sessions, by session id, deeper sessions first and
111
+ * the root last. Each list is empty while
112
+ * the level has not been reported: Claude Code sends none before the first
113
+ * background task starts.
114
+ */
115
+ export declare function backgroundLists(session: BackgroundWorkSession, rootSessionId: string): Map<string, BackgroundItem[]>;
116
+ /**
117
+ * The tasks of the terminals and tool calls that `level` does not hold:
118
+ * Claude Code no longer runs them, so they end before a list drops them, as
119
+ * v2 requires. A terminal or tool call is registered at its tool result,
120
+ * after its task joined the level, so a later level without the task means
121
+ * that it ended. Claude Code usually sends a task's end before that level,
122
+ * which ends them first, but the SDK does not promise that order.
123
+ */
124
+ export declare function droppedTasks(session: BackgroundWorkSession, level: BackgroundLevel): string[];
125
+ /**
126
+ * Starts reading the output of the background command `taskId` into its
127
+ * terminal, when the session reports its background work and the Bash result
128
+ * named the command's output file. `send` sends an update of the terminal's
129
+ * tool call, routed as the call's other updates. Called once the Bash
130
+ * result's own updates are sent: the first output replaces the placeholder
131
+ * that they gave the terminal.
132
+ */
133
+ export declare function followBackgroundTerminalOutput(session: BackgroundWorkSession, sessionId: string, taskId: string, send: (notification: AcpSessionNotification) => Promise<void>): void;
134
+ /**
135
+ * Reads the rest of the output of the background command `taskId` into its
136
+ * terminal: the command ended, and its output goes before its exit, as v2
137
+ * asks.
138
+ */
139
+ export declare function finishBackgroundTerminalOutput(session: BackgroundWorkSession, taskId: string): Promise<void>;
140
+ /**
141
+ * Removes the terminal of the background command `taskId` of the session
142
+ * `sessionId`, and returns the update that exits it, or undefined when the
143
+ * command has no terminal. Claude Code reports no exit code for it, and
144
+ * `completed` does not mean 0: Claude Code accepts some non-zero codes as a
145
+ * success, such as grep's 1.
146
+ */
147
+ export declare function takeBackgroundTerminalExit(session: BackgroundWorkSession, sessionId: string, taskId: string): AcpSessionNotification | undefined;
148
+ /**
149
+ * Removes the background tool call of the task `taskId` of the session
150
+ * `sessionId`, and returns the update that gives it its final status, or
151
+ * undefined when the task has no such call. A task that Claude Code stopped
152
+ * is `cancelled`. One that ended without saying how, such as one that the
153
+ * level dropped, is `completed`: it no longer runs, and reported no failure.
154
+ */
155
+ export declare function takeBackgroundToolCallEnd(session: BackgroundWorkSession, sessionId: string, taskId: string, end: BackgroundTaskEnd): AcpSessionNotification | undefined;
156
+ /**
157
+ * The update that reports the background tool call of the task `taskId` as
158
+ * `paused`, or as `in_progress` again, when `paused` changes it. Claude Code
159
+ * pauses a workflow this way.
160
+ */
161
+ export declare function backgroundToolCallPause(session: BackgroundWorkSession, sessionId: string, taskId: string, paused: boolean): AcpSessionNotification | undefined;
162
+ /**
163
+ * The current state of each background tool call in `toolCallIds` of the
164
+ * root session `sessionId`: the call as `renderer` shows its tool use, with
165
+ * its status now (see `ClaudeAcpAgent.publishBackgroundToolCalls`).
166
+ */
167
+ export declare function backgroundToolCallStates(session: BackgroundWorkSession & {
168
+ cwd: string;
169
+ }, sessionId: string, toolCallIds: string[], renderer: AcpToolCallRenderer): AcpSessionNotification[];
170
+ /**
171
+ * Ends the background work of a session whose query closed: Claude Code's
172
+ * background tasks end with its process. Returns the updates that exit the
173
+ * root session's terminals and cancel its tool calls, and empties the level,
174
+ * so the next lists are empty. A subagent's command or tool call is left as
175
+ * it was: it is on the child's stream, which the closed query's routing
176
+ * owned.
177
+ */
178
+ export declare function endBackgroundWork(session: BackgroundWorkSession, sessionId: string): AcpSessionNotification[];
179
+ /**
180
+ * Stops the task of a terminal or tool call that the session `sessionId`
181
+ * lists (v2 `terminal/cancel` and `tool_call/cancel`). `sessions` are the
182
+ * root sessions, by id: a subagent session's list is built with its root's.
183
+ * The terminal exits, or the tool call gets its final status, when Claude
184
+ * Code reports the task stopped.
185
+ */
186
+ export declare function cancelBackgroundWork(sessions: Record<string, BackgroundWorkSession>, sessionId: string, target: {
187
+ terminalId: string;
188
+ } | {
189
+ toolCallId: string;
190
+ }): Promise<void>;
191
+ /**
192
+ * Publishes the lists of each session that reports them, each time one
193
+ * changes. The sink sends them to the client synchronously, so a list follows
194
+ * the updates sent before it.
195
+ */
196
+ export declare class BackgroundWorkReporter {
197
+ private readonly sink?;
198
+ /** The list last published for each session, as JSON, and the root session it is built with. */
199
+ private readonly published;
200
+ constructor(sink?: BackgroundSink | undefined);
201
+ /** Whether the lists are reported at all. */
202
+ get enabled(): boolean;
203
+ /**
204
+ * Publishes each list of the root session `rootSessionId` that changed. The
205
+ * first call for a session publishes its list, also when it is empty.
206
+ * `rootState` is the root session's state now.
207
+ */
208
+ sync(rootSessionId: string, session: BackgroundWorkSession, rootState: WorkStateName): void;
209
+ /** The root session `rootSessionId` ended, and its subagent sessions with it. */
210
+ forget(rootSessionId: string): void;
211
+ }
212
+ //# sourceMappingURL=background-work.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"background-work.d.ts","sourceRoot":"","sources":["../src/background-work.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,428 @@
1
+ /**
2
+ * The background work of a session: the work that Claude Code keeps running
3
+ * independently of the session's foreground work, which ACP v2 lists on every
4
+ * `state_update` of the session as `background` (the v2 prompt lifecycle,
5
+ * "Background work").
6
+ *
7
+ * The lists are built from Claude Code's live background task set
8
+ * (`background_tasks_changed`), without the tasks it marks `ambient`, which
9
+ * are housekeeping that it started on its own. The root session and each
10
+ * announced subagent session (the subagents RFD form) has its own list. Each
11
+ * task is listed once, in the list of the session that runs it:
12
+ *
13
+ * - A command that went to the background: its display terminal, from the
14
+ * Bash tool result until its task ends (`Session.backgroundTerminals`).
15
+ * - A detached subagent: its child session, in its parent's list, while it
16
+ * works, and while it is idle with background work of its own.
17
+ * - A task that a tool call started, such as a `Monitor`, a `Workflow` or a
18
+ * backgrounded MCP call: that tool call, kept `in_progress` (or `paused`)
19
+ * from its tool result until its task ends (`Session.backgroundToolCalls`).
20
+ * - Any other task, once its `task_started` came and the tool call that
21
+ * started it, if any, returned: a `_claude/task` item with the task's id
22
+ * and description. For example, a task whose tool call ended before the
23
+ * task joined the level.
24
+ *
25
+ * Only a session whose interrupt spares background tasks reports its lists
26
+ * (`Session.reportsBackground`): `session/cancel` must leave listed work
27
+ * running.
28
+ */
29
+ import { RequestError } from "@agentclientprotocol/sdk/unstable";
30
+ import { V2_CANCELLED, V2_PAUSED } from "./tool-calls/renderer.js";
31
+ import { BackgroundOutputTail } from "./background-output.js";
32
+ /** The tools whose structured result names the task that the call started. */
33
+ const TASK_TOOLS = new Set(["Monitor", "Workflow"]);
34
+ /**
35
+ * Registers each tool call of a tool result message whose task goes on in
36
+ * the background: a `Monitor` or `Workflow` call, whose structured result
37
+ * names its task, or a call that a live task names as its tool use, such as
38
+ * a backgrounded MCP call. The task must be in the level: one that ended
39
+ * already, or that Claude Code marks `ambient`, leaves its call as it is.
40
+ * Returns the ids of the calls, whose result then reports `in_progress` (see
41
+ * `withRunningToolCall`).
42
+ */
43
+ export function registerBackgroundToolCalls(session, content, toolUseResult, toolUses, parentToolUseId) {
44
+ const registered = new Set();
45
+ if (!session.reportsBackground || !Array.isArray(content))
46
+ return registered;
47
+ const results = content.filter((block) => typeof block === "object" &&
48
+ block !== null &&
49
+ block.type === "tool_result" &&
50
+ typeof block.tool_use_id === "string" &&
51
+ block.is_error !== true);
52
+ const structuredTaskId = results.length === 1 &&
53
+ typeof toolUseResult === "object" &&
54
+ toolUseResult !== null &&
55
+ typeof toolUseResult.taskId === "string"
56
+ ? toolUseResult.taskId
57
+ : undefined;
58
+ for (const { tool_use_id: toolCallId } of results) {
59
+ const toolUse = toolUses[toolCallId];
60
+ if (!toolUse)
61
+ continue;
62
+ const taskId = (TASK_TOOLS.has(toolUse.name) ? structuredTaskId : undefined) ??
63
+ [...(session.liveBackgroundTasks ?? [])].find(([, task]) => task.parentToolUseId === toolCallId && !task.isSubagent)?.[0];
64
+ const task = taskId === undefined ? undefined : session.backgroundLevel?.get(taskId);
65
+ if (!task ||
66
+ task.ambient ||
67
+ task.taskType === "local_bash" ||
68
+ task.taskType === "local_agent") {
69
+ continue;
70
+ }
71
+ (session.backgroundToolCalls ??= new Map()).set(taskId, {
72
+ toolCallId,
73
+ toolUse,
74
+ ...(parentToolUseId ? { parentToolUseId } : {}),
75
+ });
76
+ registered.add(toolCallId);
77
+ }
78
+ return registered;
79
+ }
80
+ /** The level of a `background_tasks_changed` payload. */
81
+ export function backgroundLevel(tasks) {
82
+ return new Map(tasks.map((task) => [
83
+ task.task_id,
84
+ {
85
+ ambient: task.ambient === true,
86
+ taskType: task.task_type,
87
+ description: task.description,
88
+ ...(task.parent_task_id !== undefined ? { parentTaskId: task.parent_task_id } : {}),
89
+ },
90
+ ]));
91
+ }
92
+ /**
93
+ * The `background` list of the root session `rootSessionId` and of each of
94
+ * its announced subagent sessions, by session id, deeper sessions first and
95
+ * the root last. Each list is empty while
96
+ * the level has not been reported: Claude Code sends none before the first
97
+ * background task starts.
98
+ */
99
+ export function backgroundLists(session, rootSessionId) {
100
+ const children = [...(session.nativeSubagentsByTaskId ?? [])].filter(([, child]) => child.announced);
101
+ const childByTask = new Map(children);
102
+ const childByToolUse = new Map(children.flatMap(([, child]) => child.parentToolUseId ? [[child.parentToolUseId, child]] : []));
103
+ const lists = new Map([
104
+ [rootSessionId, []],
105
+ ...children.map(([, child]) => [child.sessionId, []]),
106
+ ]);
107
+ const level = session.backgroundLevel ?? new Map();
108
+ for (const [taskId, task] of level) {
109
+ if (task.ambient)
110
+ continue;
111
+ // A subagent's command or tool call runs on the child's stream.
112
+ const ownerOf = (parentToolUseId) => parentToolUseId ? childByToolUse.get(parentToolUseId)?.sessionId : rootSessionId;
113
+ const terminal = session.backgroundTerminals?.get(taskId);
114
+ if (terminal) {
115
+ const owner = ownerOf(terminal.parentToolUseId);
116
+ if (owner)
117
+ lists.get(owner).push({ type: "terminal", terminalId: terminal.toolCallId });
118
+ continue;
119
+ }
120
+ const toolCall = session.backgroundToolCalls?.get(taskId);
121
+ if (toolCall) {
122
+ const owner = ownerOf(toolCall.parentToolUseId);
123
+ if (owner)
124
+ lists.get(owner).push({ type: "tool_call", toolCallId: toolCall.toolCallId });
125
+ continue;
126
+ }
127
+ // A command without a terminal yet is one that foreground work waits on.
128
+ // A subagent is listed below, as its session.
129
+ if (task.taskType === "local_bash" || task.taskType === "local_agent")
130
+ continue;
131
+ // A task joins the level before its `task_started`, and before the tool
132
+ // call that starts it returns. Until both, the task is unknown or part of
133
+ // that tool call, which the foreground work waits on. Claude Code drops a
134
+ // tool use from its cache at the tool's result.
135
+ const live = session.liveBackgroundTasks?.get(taskId);
136
+ if (!live || (live.parentToolUseId && session.toolUseCache?.[live.parentToolUseId])) {
137
+ continue;
138
+ }
139
+ const parent = task.parentTaskId ? childByTask.get(task.parentTaskId) : undefined;
140
+ lists
141
+ .get(parent?.sessionId ?? rootSessionId)
142
+ .push({ type: "_claude/task", taskId, description: task.description });
143
+ }
144
+ // A child is listed in its parent's list while it works in the background,
145
+ // and while it is idle with background work of its own. Deeper children
146
+ // first, so a child's list is whole before its parent's reads it.
147
+ const sessions = new Map(children.map(([, child]) => [child.sessionId, child]));
148
+ const depth = (child) => {
149
+ const parent = sessions.get(child.parentSessionId);
150
+ return parent ? depth(parent) + 1 : 0;
151
+ };
152
+ for (const [taskId, child] of children.sort(([, a], [, b]) => depth(b) - depth(a))) {
153
+ const task = level.get(taskId);
154
+ const working = child.workState !== undefined &&
155
+ child.workState !== "idle" &&
156
+ task !== undefined &&
157
+ !task.ambient;
158
+ if (working || lists.get(child.sessionId).length > 0) {
159
+ lists.get(child.parentSessionId)?.push({ type: "subagent", sessionId: child.sessionId });
160
+ }
161
+ }
162
+ // In the same order, the root last: a child's own list goes out before its
163
+ // parent's list drops it.
164
+ return new Map([
165
+ ...children.map(([, child]) => [
166
+ child.sessionId,
167
+ lists.get(child.sessionId),
168
+ ]),
169
+ [rootSessionId, lists.get(rootSessionId)],
170
+ ]);
171
+ }
172
+ /**
173
+ * The tasks of the terminals and tool calls that `level` does not hold:
174
+ * Claude Code no longer runs them, so they end before a list drops them, as
175
+ * v2 requires. A terminal or tool call is registered at its tool result,
176
+ * after its task joined the level, so a later level without the task means
177
+ * that it ended. Claude Code usually sends a task's end before that level,
178
+ * which ends them first, but the SDK does not promise that order.
179
+ */
180
+ export function droppedTasks(session, level) {
181
+ return [
182
+ ...(session.backgroundTerminals?.keys() ?? []),
183
+ ...(session.backgroundToolCalls?.keys() ?? []),
184
+ ].filter((taskId) => !level.has(taskId));
185
+ }
186
+ /**
187
+ * Starts reading the output of the background command `taskId` into its
188
+ * terminal, when the session reports its background work and the Bash result
189
+ * named the command's output file. `send` sends an update of the terminal's
190
+ * tool call, routed as the call's other updates. Called once the Bash
191
+ * result's own updates are sent: the first output replaces the placeholder
192
+ * that they gave the terminal.
193
+ */
194
+ export function followBackgroundTerminalOutput(session, sessionId, taskId, send) {
195
+ const terminal = session.backgroundTerminals?.get(taskId);
196
+ if (!session.reportsBackground || !terminal?.outputFilePath || terminal.output)
197
+ return;
198
+ terminal.output = new BackgroundOutputTail(terminal.outputFilePath, (output) => send(terminalOutputUpdate(sessionId, terminal, output)));
199
+ }
200
+ /** The update that gives the terminal of `terminal` the text `output`. */
201
+ function terminalOutputUpdate(sessionId, terminal, output) {
202
+ const data = { terminal_id: terminal.toolCallId, data: output.text };
203
+ return {
204
+ sessionId,
205
+ update: {
206
+ sessionUpdate: "tool_call_update",
207
+ toolCallId: terminal.toolCallId,
208
+ _meta: {
209
+ ...(terminal.parentToolUseId
210
+ ? { claudeCode: { parentToolUseId: terminal.parentToolUseId } }
211
+ : {}),
212
+ ...(output.replaces ? { terminal_output: data } : { terminal_output_delta: data }),
213
+ },
214
+ },
215
+ };
216
+ }
217
+ /**
218
+ * Reads the rest of the output of the background command `taskId` into its
219
+ * terminal: the command ended, and its output goes before its exit, as v2
220
+ * asks.
221
+ */
222
+ export async function finishBackgroundTerminalOutput(session, taskId) {
223
+ await session.backgroundTerminals?.get(taskId)?.output?.finish();
224
+ }
225
+ /**
226
+ * Removes the terminal of the background command `taskId` of the session
227
+ * `sessionId`, and returns the update that exits it, or undefined when the
228
+ * command has no terminal. Claude Code reports no exit code for it, and
229
+ * `completed` does not mean 0: Claude Code accepts some non-zero codes as a
230
+ * success, such as grep's 1.
231
+ */
232
+ export function takeBackgroundTerminalExit(session, sessionId, taskId) {
233
+ const terminal = session.backgroundTerminals?.get(taskId);
234
+ if (!terminal)
235
+ return undefined;
236
+ terminal.output?.stop();
237
+ session.backgroundTerminals.delete(taskId);
238
+ return {
239
+ sessionId,
240
+ update: {
241
+ sessionUpdate: "tool_call_update",
242
+ toolCallId: terminal.toolCallId,
243
+ _meta: {
244
+ ...(terminal.parentToolUseId
245
+ ? { claudeCode: { parentToolUseId: terminal.parentToolUseId } }
246
+ : {}),
247
+ terminal_exit: { terminal_id: terminal.toolCallId, exit_code: null, signal: null },
248
+ },
249
+ },
250
+ };
251
+ }
252
+ /**
253
+ * Removes the background tool call of the task `taskId` of the session
254
+ * `sessionId`, and returns the update that gives it its final status, or
255
+ * undefined when the task has no such call. A task that Claude Code stopped
256
+ * is `cancelled`. One that ended without saying how, such as one that the
257
+ * level dropped, is `completed`: it no longer runs, and reported no failure.
258
+ */
259
+ export function takeBackgroundToolCallEnd(session, sessionId, taskId, end) {
260
+ const toolCall = session.backgroundToolCalls?.get(taskId);
261
+ if (!toolCall)
262
+ return undefined;
263
+ session.backgroundToolCalls.delete(taskId);
264
+ return toolCallStatusUpdate(sessionId, toolCall, end === "stopped" ? V2_CANCELLED : end === "failed" ? "failed" : "completed");
265
+ }
266
+ /**
267
+ * The update that reports the background tool call of the task `taskId` as
268
+ * `paused`, or as `in_progress` again, when `paused` changes it. Claude Code
269
+ * pauses a workflow this way.
270
+ */
271
+ export function backgroundToolCallPause(session, sessionId, taskId, paused) {
272
+ const toolCall = session.backgroundToolCalls?.get(taskId);
273
+ if (!toolCall || (toolCall.paused ?? false) === paused)
274
+ return undefined;
275
+ toolCall.paused = paused;
276
+ return toolCallStatusUpdate(sessionId, toolCall, paused ? V2_PAUSED : "in_progress");
277
+ }
278
+ /**
279
+ * The current state of each background tool call in `toolCallIds` of the
280
+ * root session `sessionId`: the call as `renderer` shows its tool use, with
281
+ * its status now (see `ClaudeAcpAgent.publishBackgroundToolCalls`).
282
+ */
283
+ export function backgroundToolCallStates(session, sessionId, toolCallIds, renderer) {
284
+ return [...(session.backgroundToolCalls?.values() ?? [])]
285
+ .filter((toolCall) => toolCallIds.includes(toolCall.toolCallId))
286
+ .map((toolCall) => {
287
+ const update = renderer.toolCall({ id: toolCall.toolCallId, ...toolCall.toolUse }, { cwd: session.cwd });
288
+ const status = toolCall.paused ? V2_PAUSED : "in_progress";
289
+ return { sessionId, update: { ...update, status } };
290
+ });
291
+ }
292
+ function toolCallStatusUpdate(sessionId, toolCall, status) {
293
+ return {
294
+ sessionId,
295
+ update: {
296
+ sessionUpdate: "tool_call_update",
297
+ toolCallId: toolCall.toolCallId,
298
+ status,
299
+ ...(toolCall.parentToolUseId
300
+ ? { _meta: { claudeCode: { parentToolUseId: toolCall.parentToolUseId } } }
301
+ : {}),
302
+ },
303
+ };
304
+ }
305
+ /**
306
+ * Ends the background work of a session whose query closed: Claude Code's
307
+ * background tasks end with its process. Returns the updates that exit the
308
+ * root session's terminals and cancel its tool calls, and empties the level,
309
+ * so the next lists are empty. A subagent's command or tool call is left as
310
+ * it was: it is on the child's stream, which the closed query's routing
311
+ * owned.
312
+ */
313
+ export function endBackgroundWork(session, sessionId) {
314
+ if (!session.reportsBackground)
315
+ return [];
316
+ // No command writes output any more.
317
+ for (const terminal of session.backgroundTerminals?.values() ?? [])
318
+ terminal.output?.stop();
319
+ const exits = [...(session.backgroundTerminals ?? [])].flatMap(([taskId, terminal]) => {
320
+ const exit = terminal.parentToolUseId
321
+ ? undefined
322
+ : takeBackgroundTerminalExit(session, sessionId, taskId);
323
+ return exit ? [exit] : [];
324
+ });
325
+ const ends = [...(session.backgroundToolCalls ?? [])].flatMap(([taskId, toolCall]) => {
326
+ const end = toolCall.parentToolUseId
327
+ ? undefined
328
+ : takeBackgroundToolCallEnd(session, sessionId, taskId, "stopped");
329
+ return end ? [end] : [];
330
+ });
331
+ session.backgroundLevel = undefined;
332
+ return [...exits, ...ends];
333
+ }
334
+ /**
335
+ * Stops the task of a terminal or tool call that the session `sessionId`
336
+ * lists (v2 `terminal/cancel` and `tool_call/cancel`). `sessions` are the
337
+ * root sessions, by id: a subagent session's list is built with its root's.
338
+ * The terminal exits, or the tool call gets its final status, when Claude
339
+ * Code reports the task stopped.
340
+ */
341
+ export async function cancelBackgroundWork(sessions, sessionId, target) {
342
+ const notCancellable = () => "terminalId" in target
343
+ ? RequestError.invalidParams({ sessionId, terminalId: target.terminalId }, `Terminal ${target.terminalId} is not a command running in the background, so it cannot be cancelled.`)
344
+ : RequestError.invalidParams({ sessionId, toolCallId: target.toolCallId }, `Tool call ${target.toolCallId} is not running in the background, so it cannot be cancelled.`);
345
+ const isTarget = (item) => "terminalId" in target
346
+ ? item.type === "terminal" && item.terminalId === target.terminalId
347
+ : item.type === "tool_call" && item.toolCallId === target.toolCallId;
348
+ const listed = (root, session) => session.reportsBackground === true &&
349
+ (backgroundLists(session, root).get(sessionId)?.some(isTarget) ?? false);
350
+ const owner = Object.entries(sessions).find(([root, session]) => listed(root, session));
351
+ if (!owner)
352
+ throw notCancellable();
353
+ const [root, session] = owner;
354
+ const id = "terminalId" in target ? target.terminalId : target.toolCallId;
355
+ const taskId = [
356
+ ...(("terminalId" in target ? session.backgroundTerminals : session.backgroundToolCalls) ?? []),
357
+ ].find(([, entry]) => entry.toolCallId === id)[0];
358
+ try {
359
+ await session.query.stopTask(taskId);
360
+ }
361
+ catch (error) {
362
+ // The task ended meanwhile: it has ended on the client too, or is about to.
363
+ if (!listed(root, session))
364
+ throw notCancellable();
365
+ throw error;
366
+ }
367
+ }
368
+ /**
369
+ * Publishes the lists of each session that reports them, each time one
370
+ * changes. The sink sends them to the client synchronously, so a list follows
371
+ * the updates sent before it.
372
+ */
373
+ export class BackgroundWorkReporter {
374
+ sink;
375
+ /** The list last published for each session, as JSON, and the root session it is built with. */
376
+ published = new Map();
377
+ constructor(sink) {
378
+ this.sink = sink;
379
+ }
380
+ /** Whether the lists are reported at all. */
381
+ get enabled() {
382
+ return this.sink !== undefined;
383
+ }
384
+ /**
385
+ * Publishes each list of the root session `rootSessionId` that changed. The
386
+ * first call for a session publishes its list, also when it is empty.
387
+ * `rootState` is the root session's state now.
388
+ */
389
+ sync(rootSessionId, session, rootState) {
390
+ if (!this.sink || !session.reportsBackground)
391
+ return;
392
+ const children = new Map([...(session.nativeSubagentsByTaskId?.values() ?? [])].map((child) => [
393
+ child.sessionId,
394
+ child,
395
+ ]));
396
+ for (const [sessionId, items] of backgroundLists(session, rootSessionId)) {
397
+ const json = JSON.stringify(items);
398
+ if (this.published.get(sessionId)?.json === json)
399
+ continue;
400
+ this.published.set(sessionId, { json, root: rootSessionId });
401
+ const child = children.get(sessionId);
402
+ this.sink.changed({
403
+ sessionId,
404
+ items,
405
+ state: child ? workStateName(child.workState) : rootState,
406
+ ...(child ? { parentSessionId: child.parentSessionId } : {}),
407
+ });
408
+ }
409
+ }
410
+ /** The root session `rootSessionId` ended, and its subagent sessions with it. */
411
+ forget(rootSessionId) {
412
+ for (const [sessionId, { root }] of this.published) {
413
+ if (root !== rootSessionId)
414
+ continue;
415
+ this.published.delete(sessionId);
416
+ this.sink?.forget(sessionId);
417
+ }
418
+ }
419
+ }
420
+ /** A child's work state, when it is one that v2 names. */
421
+ function workStateName(state) {
422
+ return state === "idle" ||
423
+ state === "running" ||
424
+ state === "requires_action" ||
425
+ state === "unknown"
426
+ ? state
427
+ : undefined;
428
+ }
@@ -218,6 +218,13 @@ export declare class NativeSubagentRuntime {
218
218
  * `sessionId` of this live session, or undefined when no such child runs here.
219
219
  */
220
220
  liveWorkState(sessionId: string): WorkState["state"] | undefined;
221
+ /**
222
+ * The RFD form: the `subagent_update` that tells a client that holds nothing
223
+ * of the announced child `sessionId` of this live session what it is now:
224
+ * its labels, its directory, its `cancel` capability, and its work state.
225
+ * Undefined when no such child runs here.
226
+ */
227
+ currentUpdate(sessionId: string): AcpSessionNotification | undefined;
221
228
  /**
222
229
  * Reports the model of a child response, an exact model id, as the entry of
223
230
  * the root session's `model` option that matches it, else as the id (see