@agentclientprotocol/claude-agent-acp 0.89.2-preview.2 → 0.89.2-preview.20

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.
Files changed (52) hide show
  1. package/README.md +51 -8
  2. package/dist/acp-agent.d.ts +131 -23
  3. package/dist/acp-agent.d.ts.map +1 -1
  4. package/dist/acp-agent.js +879 -239
  5. package/dist/acp-subagents.d.ts +16 -6
  6. package/dist/acp-subagents.d.ts.map +1 -1
  7. package/dist/acp-subagents.js +8 -2
  8. package/dist/async-tasks.d.ts +4 -1
  9. package/dist/async-tasks.d.ts.map +1 -1
  10. package/dist/auth-status.d.ts +2 -2
  11. package/dist/auth-status.d.ts.map +1 -1
  12. package/dist/auth-status.js +18 -4
  13. package/dist/exit-plan.d.ts +17 -0
  14. package/dist/exit-plan.d.ts.map +1 -1
  15. package/dist/exit-plan.js +34 -0
  16. package/dist/native-subagents.d.ts +190 -11
  17. package/dist/native-subagents.d.ts.map +1 -1
  18. package/dist/native-subagents.js +419 -40
  19. package/dist/settings.d.ts +6 -0
  20. package/dist/settings.d.ts.map +1 -1
  21. package/dist/settings.js +13 -0
  22. package/dist/subagent-history.d.ts +27 -2
  23. package/dist/subagent-history.d.ts.map +1 -1
  24. package/dist/subagent-history.js +38 -10
  25. package/dist/subagent-replay.d.ts +108 -0
  26. package/dist/subagent-replay.d.ts.map +1 -0
  27. package/dist/subagent-replay.js +261 -0
  28. package/dist/tool-calls/background.d.ts +9 -0
  29. package/dist/tool-calls/background.d.ts.map +1 -1
  30. package/dist/tool-calls/background.js +25 -0
  31. package/dist/tool-calls/renderer.d.ts +32 -7
  32. package/dist/tool-calls/renderer.d.ts.map +1 -1
  33. package/dist/tool-calls/renderer.js +54 -21
  34. package/dist/turn-events.d.ts +39 -17
  35. package/dist/turn-events.d.ts.map +1 -1
  36. package/dist/v2/agent.d.ts.map +1 -1
  37. package/dist/v2/agent.js +52 -14
  38. package/dist/v2/initialize.d.ts.map +1 -1
  39. package/dist/v2/initialize.js +10 -1
  40. package/dist/v2/prompt.d.ts +29 -18
  41. package/dist/v2/prompt.d.ts.map +1 -1
  42. package/dist/v2/prompt.js +54 -43
  43. package/dist/v2/session-update.d.ts +11 -0
  44. package/dist/v2/session-update.d.ts.map +1 -1
  45. package/dist/v2/session-update.js +67 -0
  46. package/dist/v2/session.d.ts +3 -12
  47. package/dist/v2/session.d.ts.map +1 -1
  48. package/dist/v2/session.js +5 -18
  49. package/dist/v2/tool-call-meta.d.ts +44 -0
  50. package/dist/v2/tool-call-meta.d.ts.map +1 -0
  51. package/dist/v2/tool-call-meta.js +72 -0
  52. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"settings.d.ts","sourceRoot":"","sources":["../src/settings.ts"],"names":[],"mappings":"AAIA,OAAO,EAGL,KAAK,QAAQ,EACd,MAAM,gCAAgC,CAAC;AA0BxC,MAAM,WAAW,sBAAsB;IACrC,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;IACtB,MAAM,CAAC,EAAE;QAAE,GAAG,EAAE,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,IAAI,CAAC;QAAC,KAAK,EAAE,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,IAAI,CAAA;KAAE,CAAC;CAC7E;AAED;;;;;;;;GAQG;AACH,qBAAa,eAAe;IAC1B,OAAO,CAAC,GAAG,CAAS;IACpB,OAAO,CAAC,SAAS,CAAgB;IACjC,OAAO,CAAC,mBAAmB,CAAgB;IAC3C,OAAO,CAAC,QAAQ,CAAsB;IACtC,OAAO,CAAC,QAAQ,CAAC,CAAa;IAC9B,OAAO,CAAC,MAAM,CAAqE;IACnF,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,aAAa,CAA8C;IACnE,OAAO,CAAC,WAAW,CAA8B;gBAErC,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,sBAAsB;IAMzD;;OAEG;IACG,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAmBjC;;;OAGG;IACH,OAAO,CAAC,eAAe;IASvB;;;OAGG;YACW,eAAe;IAe7B;;OAEG;IACH,OAAO,CAAC,aAAa;IAyBrB;;OAEG;IACH,OAAO,CAAC,oBAAoB;IAqB5B;;OAEG;IACH,WAAW,IAAI,QAAQ;IAIvB;;;OAGG;IACH,sBAAsB,IAAI,MAAM,EAAE;IAIlC;;OAEG;IACH,MAAM,IAAI,MAAM;IAIhB;;OAEG;IACG,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAUxC;;OAEG;IACH,OAAO,IAAI,IAAI;CAehB"}
1
+ {"version":3,"file":"settings.d.ts","sourceRoot":"","sources":["../src/settings.ts"],"names":[],"mappings":"AAIA,OAAO,EAGL,KAAK,QAAQ,EACd,MAAM,gCAAgC,CAAC;AA0BxC,MAAM,WAAW,sBAAsB;IACrC,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;IACtB,MAAM,CAAC,EAAE;QAAE,GAAG,EAAE,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,IAAI,CAAC;QAAC,KAAK,EAAE,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,IAAI,CAAA;KAAE,CAAC;CAC7E;AAED;;;;;;;;GAQG;AACH,qBAAa,eAAe;IAC1B,OAAO,CAAC,GAAG,CAAS;IACpB,OAAO,CAAC,SAAS,CAAgB;IACjC,OAAO,CAAC,mBAAmB,CAAgB;IAC3C,OAAO,CAAC,mBAAmB,CAAC,CAAS;IACrC,OAAO,CAAC,QAAQ,CAAsB;IACtC,OAAO,CAAC,QAAQ,CAAC,CAAa;IAC9B,OAAO,CAAC,MAAM,CAAqE;IACnF,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,aAAa,CAA8C;IACnE,OAAO,CAAC,WAAW,CAA8B;gBAErC,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,sBAAsB;IAMzD;;OAEG;IACG,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAmBjC;;;OAGG;IACH,OAAO,CAAC,eAAe;IASvB;;;OAGG;YACW,eAAe;IAoB7B;;OAEG;IACH,OAAO,CAAC,aAAa;IAyBrB;;OAEG;IACH,OAAO,CAAC,oBAAoB;IAqB5B;;OAEG;IACH,WAAW,IAAI,QAAQ;IAIvB;;;OAGG;IACH,eAAe,CAAC,gBAAgB,CAAC,EAAE,QAAQ,GAAG,MAAM,GAAG,SAAS;IAMhE;;;OAGG;IACH,sBAAsB,IAAI,MAAM,EAAE;IAIlC;;OAEG;IACH,MAAM,IAAI,MAAM;IAIhB;;OAEG;IACG,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAUxC;;OAEG;IACH,OAAO,IAAI,IAAI;CAehB"}
package/dist/settings.js CHANGED
@@ -38,6 +38,7 @@ export class SettingsManager {
38
38
  cwd;
39
39
  effective = {};
40
40
  managedDeniedModels = [];
41
+ managedApiKeyHelper;
41
42
  watchers = [];
42
43
  onChange;
43
44
  logger;
@@ -94,11 +95,16 @@ export class SettingsManager {
94
95
  this.managedDeniedModels = resolved.sources
95
96
  .filter((entry) => entry.source === "managed")
96
97
  .flatMap((entry) => entry.settings.deniedModels ?? []);
98
+ this.managedApiKeyHelper =
99
+ resolved.provenance.apiKeyHelper?.source === "managed"
100
+ ? resolved.effective.apiKeyHelper
101
+ : undefined;
97
102
  }
98
103
  catch (error) {
99
104
  this.logger.error("Failed to resolve settings:", error);
100
105
  this.effective = {};
101
106
  this.managedDeniedModels = [];
107
+ this.managedApiKeyHelper = undefined;
102
108
  }
103
109
  }
104
110
  /**
@@ -155,6 +161,13 @@ export class SettingsManager {
155
161
  getSettings() {
156
162
  return this.effective;
157
163
  }
164
+ /**
165
+ * The `apiKeyHelper` the CLI runs: managed settings outrank the `--settings`
166
+ * override, which outranks the user/project/local files.
167
+ */
168
+ getApiKeyHelper(settingsOverride) {
169
+ return (this.managedApiKeyHelper ?? settingsOverride?.apiKeyHelper ?? this.effective.apiKeyHelper);
170
+ }
158
171
  /**
159
172
  * `deniedModels` entries from the managed tier, the only tier the CLI
160
173
  * honors them from.
@@ -1,7 +1,23 @@
1
+ import { type SessionMessage } from "@anthropic-ai/claude-agent-sdk";
2
+ /** What the metadata file of a subagent says about it. */
3
+ export interface SubagentRecord {
4
+ /** The SDK agent id, the name of its transcript. */
5
+ agentId: string;
6
+ /** The `name` that the Agent call gave it. */
7
+ name?: string;
8
+ /** Its role, the `subagent_type` of the Agent call. */
9
+ agentType?: string;
10
+ /** The short `description` of the Agent call. */
11
+ description?: string;
12
+ /** Whether the Agent call ran it in the background. */
13
+ background: boolean;
14
+ /** Whether the user stopped it. */
15
+ stoppedByUser: boolean;
16
+ }
1
17
  /** Where the subagent transcripts of a session are. */
2
18
  export interface SubagentHistory {
3
- /** The subagent id of each Agent or Task tool use that launched a subagent. */
4
- ids: Map<string, string>;
19
+ /** The subagent of each Agent or Task tool use that launched one. */
20
+ agents: Map<string, SubagentRecord>;
5
21
  /**
6
22
  * The project directory of the session, as the SDK takes it in `dir`. With
7
23
  * it, the SDK reads a subagent transcript without a search of every project.
@@ -17,4 +33,13 @@ export interface SubagentHistory {
17
33
  * The ids are empty for a session without a local transcript.
18
34
  */
19
35
  export declare function subagentHistory(sessionId: string): Promise<SubagentHistory>;
36
+ /**
37
+ * The transcript of the subagent `agentId` of a session, or an empty list
38
+ * when it cannot be read. With the project directory, the SDK skips its
39
+ * search of every project; a session opened from another directory falls
40
+ * back to that search.
41
+ */
42
+ export declare function readSubagentMessages(sessionId: string, agentId: string, history: SubagentHistory, logger: {
43
+ error(...args: unknown[]): void;
44
+ }): Promise<SessionMessage[]>;
20
45
  //# sourceMappingURL=subagent-history.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"subagent-history.d.ts","sourceRoot":"","sources":["../src/subagent-history.ts"],"names":[],"mappings":"AAKA,uDAAuD;AACvD,MAAM,WAAW,eAAe;IAC9B,+EAA+E;IAC/E,GAAG,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACzB;;;OAGG;IACH,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED;;;;;;;GAOG;AACH,wBAAsB,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC,CA6BjF"}
1
+ {"version":3,"file":"subagent-history.d.ts","sourceRoot":"","sources":["../src/subagent-history.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,cAAc,EACpB,MAAM,gCAAgC,CAAC;AAKxC,0DAA0D;AAC1D,MAAM,WAAW,cAAc;IAC7B,oDAAoD;IACpD,OAAO,EAAE,MAAM,CAAC;IAChB,8CAA8C;IAC9C,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,uDAAuD;IACvD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,iDAAiD;IACjD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,uDAAuD;IACvD,UAAU,EAAE,OAAO,CAAC;IACpB,mCAAmC;IACnC,aAAa,EAAE,OAAO,CAAC;CACxB;AAED,uDAAuD;AACvD,MAAM,WAAW,eAAe;IAC9B,qEAAqE;IACrE,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;IACpC;;;OAGG;IACH,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED;;;;;;;GAOG;AACH,wBAAsB,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC,CAwCjF;AAED;;;;;GAKG;AACH,wBAAsB,oBAAoB,CACxC,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,eAAe,EACxB,MAAM,EAAE;IAAE,KAAK,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI,CAAA;CAAE,GAC1C,OAAO,CAAC,cAAc,EAAE,CAAC,CAW3B"}
@@ -1,4 +1,4 @@
1
- import { getSessionInfo } from "@anthropic-ai/claude-agent-sdk";
1
+ import { getSessionInfo, getSubagentMessages, } from "@anthropic-ai/claude-agent-sdk";
2
2
  import { readdir, readFile } from "node:fs/promises";
3
3
  import path from "node:path";
4
4
  import { findTranscript } from "./resumed-session.js";
@@ -11,17 +11,17 @@ import { findTranscript } from "./resumed-session.js";
11
11
  * The ids are empty for a session without a local transcript.
12
12
  */
13
13
  export async function subagentHistory(sessionId) {
14
- const ids = new Map();
14
+ const agents = new Map();
15
15
  const transcript = await findTranscript(sessionId);
16
16
  if (!transcript)
17
- return { ids };
17
+ return { agents };
18
18
  const directory = path.join(path.dirname(transcript), sessionId, "subagents");
19
19
  let names;
20
20
  try {
21
21
  names = await readdir(directory);
22
22
  }
23
23
  catch {
24
- return { ids };
24
+ return { agents };
25
25
  }
26
26
  await Promise.all(names.map(async (name) => {
27
27
  const match = /^agent-(.+)\.meta\.json$/.exec(name);
@@ -29,18 +29,46 @@ export async function subagentHistory(sessionId) {
29
29
  return;
30
30
  try {
31
31
  const meta = JSON.parse(await readFile(path.join(directory, name), "utf8"));
32
- const toolUseId = meta?.toolUseId;
33
- if (typeof toolUseId === "string")
34
- ids.set(toolUseId, match[1]);
32
+ if (typeof meta?.toolUseId !== "string")
33
+ return;
34
+ const text = (value) => (typeof value === "string" ? value : undefined);
35
+ agents.set(meta.toolUseId, {
36
+ agentId: match[1],
37
+ name: text(meta.name),
38
+ agentType: text(meta.agentType),
39
+ description: text(meta.description),
40
+ background: meta.requestShape === "background",
41
+ stoppedByUser: meta.stoppedByUser === true,
42
+ });
35
43
  }
36
44
  catch {
37
45
  // A damaged meta file leaves its subagent without history.
38
46
  }
39
47
  }));
40
- if (ids.size === 0)
41
- return { ids };
48
+ if (agents.size === 0)
49
+ return { agents };
42
50
  // The folder name encodes the project directory, and the encoding loses
43
51
  // characters. The session info reads it back from the transcript.
44
52
  const dir = (await getSessionInfo(sessionId).catch(() => undefined))?.cwd;
45
- return dir ? { ids, dir } : { ids };
53
+ return dir ? { agents, dir } : { agents };
54
+ }
55
+ /**
56
+ * The transcript of the subagent `agentId` of a session, or an empty list
57
+ * when it cannot be read. With the project directory, the SDK skips its
58
+ * search of every project; a session opened from another directory falls
59
+ * back to that search.
60
+ */
61
+ export async function readSubagentMessages(sessionId, agentId, history, logger) {
62
+ try {
63
+ if (history.dir) {
64
+ const messages = await getSubagentMessages(sessionId, agentId, { dir: history.dir });
65
+ if (messages.length > 0)
66
+ return messages;
67
+ }
68
+ return await getSubagentMessages(sessionId, agentId);
69
+ }
70
+ catch (error) {
71
+ logger.error(`Failed to read the history of subagent ${agentId}:`, error);
72
+ return [];
73
+ }
46
74
  }
@@ -0,0 +1,108 @@
1
+ /**
2
+ * The replay of subagents to a client on the subagents RFD form.
3
+ *
4
+ * Claude Code keeps each subagent's history in its own transcript, and the
5
+ * subagent's metadata file names the Agent or Task call that launched it (see
6
+ * `subagentHistory`). The replay announces each child right after that call,
7
+ * under the session id it had live, the SDK agent id, and sends the child's
8
+ * history to the child session. Every child's end goes out before the replay
9
+ * ends, since no subagent from before the reload still runs.
10
+ *
11
+ * A launch without metadata has no known id, so it is not announced: the RFD
12
+ * forbids inventing one. Its Agent call stays an ordinary tool call of the
13
+ * parent, and a notice says that subagent history is missing.
14
+ */
15
+ import type { SessionMessage } from "@anthropic-ai/claude-agent-sdk";
16
+ import type { AcpSessionNotification, SubagentState } from "./acp-subagents.js";
17
+ import type { SubagentHistory, SubagentRecord } from "./subagent-history.js";
18
+ type Publish = (notification: AcpSessionNotification) => Promise<void>;
19
+ /** A child that the replay shows: its launch has metadata. */
20
+ export type ReplayChild = {
21
+ /** The Agent or Task tool use that launched it. */
22
+ launchId: string;
23
+ /** Its session id: the SDK agent id, as live. */
24
+ sessionId: string;
25
+ record: SubagentRecord;
26
+ /** The session that made the launch. */
27
+ parentSessionId: string;
28
+ /** The input of the launch call. */
29
+ input: Record<string, unknown>;
30
+ /** How the launch's tool result ended a foreground child's run. */
31
+ launchEnd?: SubagentState;
32
+ /** The state of the child's last persisted task notification. */
33
+ notified?: SubagentState;
34
+ announced: boolean;
35
+ promptSent: boolean;
36
+ };
37
+ export declare class RfdSubagentReplay {
38
+ private readonly history;
39
+ private readonly publish;
40
+ /** The children by the id of the tool use that launched each. */
41
+ private readonly children;
42
+ private readonly byAgentId;
43
+ private readonly sendMessages;
44
+ /** Launches without metadata, which the replay leaves out. */
45
+ private readonly omitted;
46
+ /** Children whose transcript could not be read: only their association is replayed. */
47
+ private withoutHistory;
48
+ constructor(history: SubagentHistory, publish: Publish);
49
+ /**
50
+ * Registers the launches, the launch results, and the SendMessage calls in
51
+ * `messages`, the transcript of the session `ownerSessionId`.
52
+ */
53
+ scan(messages: readonly SessionMessage[], ownerSessionId: string): void;
54
+ /** The child that the tool use `toolUseId` launched, when the replay shows it. */
55
+ childOfLaunch(toolUseId: string): ReplayChild | undefined;
56
+ /** Announces `child` on its parent, as live: running, with the parent's labels. */
57
+ announce(child: ReplayChild): Promise<void>;
58
+ /**
59
+ * The rest of a child's user message to replay, or `null` when nothing is
60
+ * left, after its text went out as the child's incoming `session_message`.
61
+ *
62
+ * A child session has no user, so the text of its user messages is never a
63
+ * user message. The first text is the prompt of the launch. A message that
64
+ * a SendMessage call delivered (`origin.kind: "coordinator"`) is another
65
+ * incoming message. Any other text is harness text, such as "[Request
66
+ * interrupted by user for tool use]", which the live child session does not
67
+ * show. Tool results stay.
68
+ */
69
+ childContent(child: ReplayChild, message: SessionMessage, content: unknown): Promise<unknown>;
70
+ /**
71
+ * Ends the replay of `child`'s history. `messages` is its transcript: when
72
+ * it is empty, the replay has only the child's association, and the prompt
73
+ * comes from the launch call.
74
+ */
75
+ childReplayed(child: ReplayChild, messages: readonly SessionMessage[]): Promise<void>;
76
+ /**
77
+ * Takes a persisted task notification. Returns whether it was a child's:
78
+ * its task id is the agent id.
79
+ */
80
+ taskNotification(taskId: string, status: string): boolean;
81
+ /**
82
+ * Sends the end of every announced child, and a notice when subagent history
83
+ * is missing. The end is the state of the child's last task notification,
84
+ * else `cancelled` for a child the user stopped, else the result of its
85
+ * foreground launch, else `idle` with no stop reason.
86
+ */
87
+ finish(supportsNotices: boolean, rootSessionId: string): Promise<void>;
88
+ private registerLaunch;
89
+ /** The prompt of the launch, with the launch's id, as live. */
90
+ private sendPrompt;
91
+ /**
92
+ * A message that a SendMessage call delivered. Claude Code frames it for the
93
+ * child's model ("The coordinator sent a message while you were working:",
94
+ * then the message, then an instruction), so the call it came from is the
95
+ * first undelivered SendMessage to the child whose message the delivered
96
+ * text contains. With that call, the message has the call's id and text and
97
+ * its sender, as live; without it, its transcript id and the delivered text.
98
+ */
99
+ private sendDelivery;
100
+ private sendMessage;
101
+ }
102
+ /**
103
+ * The end of a foreground launch from its tool result: an error result that
104
+ * names a stop is a cancellation, any other error a failure.
105
+ */
106
+ export declare function replaySubagentTerminalState(block: Record<string, unknown>): "completed" | "failed" | "cancelled";
107
+ export {};
108
+ //# sourceMappingURL=subagent-replay.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"subagent-replay.d.ts","sourceRoot":"","sources":["../src/subagent-replay.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gCAAgC,CAAC;AACrE,OAAO,KAAK,EAAE,sBAAsB,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAShF,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAE7E,KAAK,OAAO,GAAG,CAAC,YAAY,EAAE,sBAAsB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;AAEvE,8DAA8D;AAC9D,MAAM,MAAM,WAAW,GAAG;IACxB,mDAAmD;IACnD,QAAQ,EAAE,MAAM,CAAC;IACjB,iDAAiD;IACjD,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,cAAc,CAAC;IACvB,wCAAwC;IACxC,eAAe,EAAE,MAAM,CAAC;IACxB,oCAAoC;IACpC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC/B,mEAAmE;IACnE,SAAS,CAAC,EAAE,aAAa,CAAC;IAC1B,iEAAiE;IACjE,QAAQ,CAAC,EAAE,aAAa,CAAC;IACzB,SAAS,EAAE,OAAO,CAAC;IACnB,UAAU,EAAE,OAAO,CAAC;CACrB,CAAC;AAUF,qBAAa,iBAAiB;IAW1B,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,OAAO;IAX1B,iEAAiE;IACjE,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAkC;IAC3D,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAkC;IAC5D,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAyB;IACtD,8DAA8D;IAC9D,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAqB;IAC7C,uFAAuF;IACvF,OAAO,CAAC,cAAc,CAAK;gBAGR,OAAO,EAAE,eAAe,EACxB,OAAO,EAAE,OAAO;IAGnC;;;OAGG;IACH,IAAI,CAAC,QAAQ,EAAE,SAAS,cAAc,EAAE,EAAE,cAAc,EAAE,MAAM,GAAG,IAAI;IA6BvE,kFAAkF;IAClF,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,WAAW,GAAG,SAAS;IAIzD,mFAAmF;IAC7E,QAAQ,CAAC,KAAK,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;IAoBjD;;;;;;;;;;OAUG;IACG,YAAY,CAChB,KAAK,EAAE,WAAW,EAClB,OAAO,EAAE,cAAc,EACvB,OAAO,EAAE,OAAO,GACf,OAAO,CAAC,OAAO,CAAC;IAanB;;;;OAIG;IACG,aAAa,CAAC,KAAK,EAAE,WAAW,EAAE,QAAQ,EAAE,SAAS,cAAc,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAO3F;;;OAGG;IACH,gBAAgB,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO;IAOzD;;;;;OAKG;IACG,MAAM,CAAC,eAAe,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IA0B5E,OAAO,CAAC,cAAc;IAwBtB,+DAA+D;YACjD,UAAU;IAKxB;;;;;;;OAOG;YACW,YAAY;YAoBZ,WAAW;CAiB1B;AAED;;;GAGG;AACH,wBAAgB,2BAA2B,CACzC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC7B,WAAW,GAAG,QAAQ,GAAG,WAAW,CAMtC"}
@@ -0,0 +1,261 @@
1
+ import { isNativeSubagentControlTool, nativeSubagentState, rfdEndState, rfdLabels, } from "./native-subagents.js";
2
+ import { noticeUpdate } from "./session-notices.js";
3
+ export class RfdSubagentReplay {
4
+ history;
5
+ publish;
6
+ /** The children by the id of the tool use that launched each. */
7
+ children = new Map();
8
+ byAgentId = new Map();
9
+ sendMessages = [];
10
+ /** Launches without metadata, which the replay leaves out. */
11
+ omitted = new Set();
12
+ /** Children whose transcript could not be read: only their association is replayed. */
13
+ withoutHistory = 0;
14
+ constructor(history, publish) {
15
+ this.history = history;
16
+ this.publish = publish;
17
+ }
18
+ /**
19
+ * Registers the launches, the launch results, and the SendMessage calls in
20
+ * `messages`, the transcript of the session `ownerSessionId`.
21
+ */
22
+ scan(messages, ownerSessionId) {
23
+ for (const message of messages) {
24
+ for (const block of contentBlocks(message)) {
25
+ if (block.type === "tool_use" && typeof block.id === "string") {
26
+ if (isNativeSubagentControlTool(block.name)) {
27
+ this.registerLaunch(block.id, inputOf(block), ownerSessionId);
28
+ }
29
+ else if (block.name === "SendMessage") {
30
+ const input = inputOf(block);
31
+ if (typeof input.to === "string" && typeof input.message === "string") {
32
+ this.sendMessages.push({
33
+ toolUseId: block.id,
34
+ to: input.to,
35
+ message: input.message,
36
+ senderSessionId: ownerSessionId,
37
+ delivered: false,
38
+ });
39
+ }
40
+ }
41
+ }
42
+ else if (block.type === "tool_result" && typeof block.tool_use_id === "string") {
43
+ const child = this.children.get(block.tool_use_id);
44
+ // A background launch answers at once; its result is not the end.
45
+ if (child && !child.record.background) {
46
+ child.launchEnd = replaySubagentTerminalState(block);
47
+ }
48
+ }
49
+ }
50
+ }
51
+ }
52
+ /** The child that the tool use `toolUseId` launched, when the replay shows it. */
53
+ childOfLaunch(toolUseId) {
54
+ return this.children.get(toolUseId);
55
+ }
56
+ /** Announces `child` on its parent, as live: running, with the parent's labels. */
57
+ async announce(child) {
58
+ if (child.announced)
59
+ return;
60
+ const labels = rfdLabels(child.record.name ?? child.input.name, child.record.agentType ?? child.input.subagent_type, child.record.description ?? child.input.description);
61
+ await this.publish({
62
+ sessionId: child.parentSessionId,
63
+ update: {
64
+ sessionUpdate: "subagent_update",
65
+ sessionId: child.sessionId,
66
+ ...(labels.title ? { title: labels.title } : {}),
67
+ ...(labels.description ? { description: labels.description } : {}),
68
+ state: { state: "running" },
69
+ },
70
+ });
71
+ child.announced = true;
72
+ }
73
+ /**
74
+ * The rest of a child's user message to replay, or `null` when nothing is
75
+ * left, after its text went out as the child's incoming `session_message`.
76
+ *
77
+ * A child session has no user, so the text of its user messages is never a
78
+ * user message. The first text is the prompt of the launch. A message that
79
+ * a SendMessage call delivered (`origin.kind: "coordinator"`) is another
80
+ * incoming message. Any other text is harness text, such as "[Request
81
+ * interrupted by user for tool use]", which the live child session does not
82
+ * show. Tool results stay.
83
+ */
84
+ async childContent(child, message, content) {
85
+ const text = textOf(content);
86
+ if (text.trim()) {
87
+ if (isDelivery(message))
88
+ await this.sendDelivery(child, message, text);
89
+ else if (!child.promptSent)
90
+ await this.sendPrompt(child, text);
91
+ }
92
+ if (!Array.isArray(content))
93
+ return null;
94
+ const rest = content.filter((block) => !(typeof block === "object" && block !== null && block.type === "text"));
95
+ return rest.length > 0 ? rest : null;
96
+ }
97
+ /**
98
+ * Ends the replay of `child`'s history. `messages` is its transcript: when
99
+ * it is empty, the replay has only the child's association, and the prompt
100
+ * comes from the launch call.
101
+ */
102
+ async childReplayed(child, messages) {
103
+ if (messages.length === 0)
104
+ this.withoutHistory++;
105
+ if (!child.promptSent && typeof child.input.prompt === "string") {
106
+ await this.sendPrompt(child, child.input.prompt);
107
+ }
108
+ }
109
+ /**
110
+ * Takes a persisted task notification. Returns whether it was a child's:
111
+ * its task id is the agent id.
112
+ */
113
+ taskNotification(taskId, status) {
114
+ const child = this.byAgentId.get(taskId);
115
+ if (!child)
116
+ return false;
117
+ child.notified = nativeSubagentState(status) ?? child.notified;
118
+ return true;
119
+ }
120
+ /**
121
+ * Sends the end of every announced child, and a notice when subagent history
122
+ * is missing. The end is the state of the child's last task notification,
123
+ * else `cancelled` for a child the user stopped, else the result of its
124
+ * foreground launch, else `idle` with no stop reason.
125
+ */
126
+ async finish(supportsNotices, rootSessionId) {
127
+ for (const child of [...this.children.values()].reverse()) {
128
+ if (!child.announced)
129
+ continue;
130
+ const end = child.notified ?? (child.record.stoppedByUser ? "cancelled" : child.launchEnd);
131
+ const state = end ? rfdEndState(end, undefined) : { state: "idle" };
132
+ await this.publish({
133
+ sessionId: child.parentSessionId,
134
+ update: { sessionUpdate: "subagent_update", sessionId: child.sessionId, state },
135
+ });
136
+ }
137
+ const missing = this.omitted.size + this.withoutHistory;
138
+ if (missing > 0 && supportsNotices) {
139
+ await this.publish({
140
+ sessionId: rootSessionId,
141
+ update: noticeUpdate({
142
+ severity: "info",
143
+ title: "Some subagent history is not available",
144
+ description: missing === 1
145
+ ? "The history of 1 subagent of this session could not be restored."
146
+ : `The history of ${missing} subagents of this session could not be restored.`,
147
+ }),
148
+ });
149
+ }
150
+ }
151
+ registerLaunch(toolUseId, input, parentSessionId) {
152
+ if (this.children.has(toolUseId))
153
+ return;
154
+ const record = this.history.agents.get(toolUseId);
155
+ if (!record) {
156
+ this.omitted.add(toolUseId);
157
+ return;
158
+ }
159
+ const child = {
160
+ launchId: toolUseId,
161
+ sessionId: record.agentId,
162
+ record,
163
+ parentSessionId,
164
+ input,
165
+ announced: false,
166
+ promptSent: false,
167
+ };
168
+ this.children.set(toolUseId, child);
169
+ this.byAgentId.set(record.agentId, child);
170
+ }
171
+ /** The prompt of the launch, with the launch's id, as live. */
172
+ async sendPrompt(child, text) {
173
+ child.promptSent = true;
174
+ await this.sendMessage(child, child.launchId, text, child.parentSessionId);
175
+ }
176
+ /**
177
+ * A message that a SendMessage call delivered. Claude Code frames it for the
178
+ * child's model ("The coordinator sent a message while you were working:",
179
+ * then the message, then an instruction), so the call it came from is the
180
+ * first undelivered SendMessage to the child whose message the delivered
181
+ * text contains. With that call, the message has the call's id and text and
182
+ * its sender, as live; without it, its transcript id and the delivered text.
183
+ */
184
+ async sendDelivery(child, message, text) {
185
+ const names = [child.sessionId, child.record.name, child.input.name];
186
+ const call = this.sendMessages.find((candidate) => !candidate.delivered &&
187
+ names.includes(candidate.to) &&
188
+ text.includes(candidate.message.trim()));
189
+ if (call) {
190
+ call.delivered = true;
191
+ await this.sendMessage(child, call.toolUseId, call.message, call.senderSessionId);
192
+ }
193
+ else {
194
+ await this.sendMessage(child, message.uuid, text, undefined);
195
+ }
196
+ }
197
+ async sendMessage(child, messageId, text, sender) {
198
+ await this.publish({
199
+ sessionId: child.sessionId,
200
+ update: {
201
+ sessionUpdate: "session_message",
202
+ messageId,
203
+ ...(sender ? { senderSessionId: sender } : {}),
204
+ recipientSessionId: child.sessionId,
205
+ content: [{ type: "text", text }],
206
+ },
207
+ });
208
+ }
209
+ }
210
+ /**
211
+ * The end of a foreground launch from its tool result: an error result that
212
+ * names a stop is a cancellation, any other error a failure.
213
+ */
214
+ export function replaySubagentTerminalState(block) {
215
+ if (block.is_error !== true)
216
+ return "completed";
217
+ const text = replayContentText(block.content).toLowerCase();
218
+ return /\b(?:cancelled|canceled|interrupted|stopped|killed)\b/.test(text)
219
+ ? "cancelled"
220
+ : "failed";
221
+ }
222
+ function replayContentText(value, seen = new Set()) {
223
+ if (typeof value === "string")
224
+ return value;
225
+ if (typeof value !== "object" || value === null || seen.has(value))
226
+ return "";
227
+ seen.add(value);
228
+ if (Array.isArray(value))
229
+ return value.map((item) => replayContentText(item, seen)).join(" ");
230
+ const record = value;
231
+ return [record.text, record.content, record.message]
232
+ .map((item) => replayContentText(item, seen))
233
+ .filter(Boolean)
234
+ .join(" ");
235
+ }
236
+ function contentBlocks(message) {
237
+ const content = message.message?.content;
238
+ return Array.isArray(content)
239
+ ? content.filter((block) => typeof block === "object" && block !== null)
240
+ : [];
241
+ }
242
+ function inputOf(block) {
243
+ return typeof block.input === "object" && block.input !== null
244
+ ? block.input
245
+ : {};
246
+ }
247
+ /** The text of user message content: a string, or its text blocks. */
248
+ function textOf(content) {
249
+ if (typeof content === "string")
250
+ return content;
251
+ if (!Array.isArray(content))
252
+ return "";
253
+ return content
254
+ .flatMap((block) => typeof block === "object" && block !== null && block.type === "text" ? [block.text] : [])
255
+ .filter((text) => typeof text === "string")
256
+ .join("\n");
257
+ }
258
+ /** A message that a SendMessage call delivered to the child. */
259
+ function isDelivery(message) {
260
+ return message.origin?.kind === "coordinator";
261
+ }
@@ -20,4 +20,13 @@ import type { SessionNotification } from "@agentclientprotocol/sdk";
20
20
  * promise a card state it has no way to ever resolve.
21
21
  */
22
22
  export declare function backgroundedBashToolCall(notification: SessionNotification, backgroundedToolCallIds: ReadonlySet<string>, asyncTasksSupported: boolean): SessionNotification;
23
+ /**
24
+ * Keeps the display terminal of a command that went to the background running:
25
+ * drops `terminal_exit` from the tool result update of `toolCallId`.
26
+ *
27
+ * The Bash call returns as soon as the command detaches, so that exit would
28
+ * mark the terminal as ended while the command still runs. The consumer sends
29
+ * the exit when the command's task ends (see `Session.backgroundTerminals`).
30
+ */
31
+ export declare function withRunningTerminal(notification: SessionNotification, toolCallId: string | undefined): SessionNotification;
23
32
  //# sourceMappingURL=background.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"background.d.ts","sourceRoot":"","sources":["../../src/tool-calls/background.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAGpE;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,wBAAwB,CACtC,YAAY,EAAE,mBAAmB,EACjC,uBAAuB,EAAE,WAAW,CAAC,MAAM,CAAC,EAC5C,mBAAmB,EAAE,OAAO,GAC3B,mBAAmB,CAgBrB"}
1
+ {"version":3,"file":"background.d.ts","sourceRoot":"","sources":["../../src/tool-calls/background.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAGpE;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,wBAAwB,CACtC,YAAY,EAAE,mBAAmB,EACjC,uBAAuB,EAAE,WAAW,CAAC,MAAM,CAAC,EAC5C,mBAAmB,EAAE,OAAO,GAC3B,mBAAmB,CAgBrB;AAED;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CACjC,YAAY,EAAE,mBAAmB,EACjC,UAAU,EAAE,MAAM,GAAG,SAAS,GAC7B,mBAAmB,CAiBrB"}
@@ -34,3 +34,28 @@ export function backgroundedBashToolCall(notification, backgroundedToolCallIds,
34
34
  },
35
35
  };
36
36
  }
37
+ /**
38
+ * Keeps the display terminal of a command that went to the background running:
39
+ * drops `terminal_exit` from the tool result update of `toolCallId`.
40
+ *
41
+ * The Bash call returns as soon as the command detaches, so that exit would
42
+ * mark the terminal as ended while the command still runs. The consumer sends
43
+ * the exit when the command's task ends (see `Session.backgroundTerminals`).
44
+ */
45
+ export function withRunningTerminal(notification, toolCallId) {
46
+ const update = notification.update;
47
+ if (toolCallId === undefined ||
48
+ update.sessionUpdate !== "tool_call_update" ||
49
+ update.toolCallId !== toolCallId ||
50
+ !update._meta ||
51
+ !("terminal_exit" in update._meta)) {
52
+ return notification;
53
+ }
54
+ const meta = { ...update._meta };
55
+ delete meta.terminal_exit;
56
+ const next = { ...update, _meta: meta };
57
+ // An empty `_meta` would clear what a v2 client stores for the tool call.
58
+ if (Object.keys(meta).length === 0)
59
+ delete next._meta;
60
+ return { ...notification, update: next };
61
+ }