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

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 (42) hide show
  1. package/README.md +8 -0
  2. package/dist/acp-agent.d.ts +60 -18
  3. package/dist/acp-agent.d.ts.map +1 -1
  4. package/dist/acp-agent.js +325 -100
  5. package/dist/diff.d.ts +1 -1
  6. package/dist/diff.js +5 -5
  7. package/dist/elicitation.d.ts +1 -2
  8. package/dist/elicitation.d.ts.map +1 -1
  9. package/dist/elicitation.js +34 -16
  10. package/dist/fork-session.d.ts.map +1 -1
  11. package/dist/fork-session.js +23 -0
  12. package/dist/native-subagents.d.ts +49 -3
  13. package/dist/native-subagents.d.ts.map +1 -1
  14. package/dist/native-subagents.js +118 -4
  15. package/dist/session-index/service.d.ts +3 -1
  16. package/dist/session-index/service.d.ts.map +1 -1
  17. package/dist/session-index/service.js +5 -10
  18. package/dist/startup-failure.d.ts +16 -0
  19. package/dist/startup-failure.d.ts.map +1 -0
  20. package/dist/startup-failure.js +38 -0
  21. package/dist/subagent-history.d.ts +8 -1
  22. package/dist/subagent-history.d.ts.map +1 -1
  23. package/dist/subagent-history.js +40 -1
  24. package/dist/subagent-replay.d.ts +19 -3
  25. package/dist/subagent-replay.d.ts.map +1 -1
  26. package/dist/subagent-replay.js +46 -4
  27. package/dist/tool-calls/reporters/interaction.d.ts.map +1 -1
  28. package/dist/tool-calls/reporters/interaction.js +8 -3
  29. package/dist/v2/agent.d.ts.map +1 -1
  30. package/dist/v2/agent.js +42 -17
  31. package/dist/v2/initialize.d.ts +16 -1
  32. package/dist/v2/initialize.d.ts.map +1 -1
  33. package/dist/v2/initialize.js +26 -3
  34. package/dist/v2/permission.d.ts +5 -2
  35. package/dist/v2/permission.d.ts.map +1 -1
  36. package/dist/v2/permission.js +5 -2
  37. package/dist/v2/prompt.d.ts +2 -1
  38. package/dist/v2/prompt.d.ts.map +1 -1
  39. package/dist/v2/prompt.js +5 -1
  40. package/dist/v2/session.d.ts.map +1 -1
  41. package/dist/v2/session.js +21 -9
  42. package/package.json +6 -6
@@ -0,0 +1,38 @@
1
+ import { RequestError } from "@agentclientprotocol/sdk/unstable";
2
+ /** Makes the CLI write an `error_during_execution` result with a
3
+ * `startup_failure_reason` before it exits on a known startup failure. Without
4
+ * it, some failures (e.g. `proxy_invalid`) end with stderr and an exit code
5
+ * alone. The SDK then rejects `initializationResult()` with an error that
6
+ * carries the reason as `startupFailureReason`. */
7
+ export const STARTUP_FAILURE_RESULTS_ENV = { CLAUDE_CODE_STARTUP_FAILURE_RESULTS: "1" };
8
+ /** The SDK's prefix on the message of an error built from an error result. */
9
+ const ERROR_RESULT_PREFIX = "Claude Code returned an error result: ";
10
+ /** Reasons that signing in again, or with another account, can fix. */
11
+ const AUTH_REQUIRED_REASONS = new Set([
12
+ "gateway_signin_required",
13
+ "org_pin_mismatch",
14
+ "org_config_refused",
15
+ ]);
16
+ /** The ACP error for a CLI that refused to start, or `undefined` when `error`
17
+ * carries no `startupFailureReason` (older CLIs, failures without a known
18
+ * cause). The message is the CLI's own, which says what to fix, e.g.
19
+ * `Invalid proxy URL in HTTPS_PROXY: … Fix or unset HTTPS_PROXY and restart
20
+ * Claude Code.`; `data.startupFailureReason` carries the reason. */
21
+ export function startupFailureError(error) {
22
+ if (!(error instanceof Error))
23
+ return undefined;
24
+ const reason = error.startupFailureReason;
25
+ if (typeof reason !== "string" || reason === "")
26
+ return undefined;
27
+ const message = error.message.startsWith(ERROR_RESULT_PREFIX)
28
+ ? error.message.slice(ERROR_RESULT_PREFIX.length)
29
+ : error.message;
30
+ const data = { startupFailureReason: reason };
31
+ if (AUTH_REQUIRED_REASONS.has(reason)) {
32
+ return RequestError.authRequired(data, message);
33
+ }
34
+ if (reason === "cwd_unavailable") {
35
+ return RequestError.invalidParams(data, message);
36
+ }
37
+ return RequestError.internalError(data, message);
38
+ }
@@ -14,6 +14,11 @@ export interface SubagentRecord {
14
14
  background: boolean;
15
15
  /** Whether the user stopped it. */
16
16
  stoppedByUser: boolean;
17
+ /**
18
+ * Its working directory, when Claude Code placed it outside its parent's:
19
+ * in a worktree of its own or of an ancestor, or in a `cwd` of its own.
20
+ */
21
+ cwd?: string;
17
22
  }
18
23
  /** Where the subagent transcripts of a session are. */
19
24
  export interface SubagentHistory {
@@ -31,7 +36,9 @@ export interface SubagentHistory {
31
36
  * Claude keeps the history of a subagent in its own transcript,
32
37
  * `<session>/subagents/agent-<id>.jsonl`, next to the session transcript.
33
38
  * The `agent-<id>.meta.json` file beside it names the launching tool use.
34
- * The ids are empty for a session without a local transcript.
39
+ * For a child that Claude Code placed elsewhere, it has `worktreePath`,
40
+ * `inheritedWorktreePath` or `cwd`; then the child's working directory is the
41
+ * `cwd` of its transcript's records. The ids are empty for a session without a local transcript.
35
42
  */
36
43
  export declare function subagentHistory(sessionId: string): Promise<SubagentHistory>;
37
44
  /**
@@ -1 +1 @@
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,OAAO,EAA+B,KAAK,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAG7F,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;AAOD;;;GAGG;AACH,wBAAsB,kBAAkB,CACtC,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAE7B;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,eAAe,CACnC,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,MAAM,EACf,MAAM,EAAE;IAAE,KAAK,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI,CAAA;CAAE,GAC1C,OAAO,CAAC,kBAAkB,EAAE,CAAC,CAmD/B;AAED,gFAAgF;AAChF,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,EAAE,CAW5D;AA0DD;;;;;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;AAyBD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,eAAe,CAAC,QAAQ,EAAE,cAAc,EAAE,GAAG,cAAc,EAAE,CAkB5E"}
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,OAAO,EAA+B,KAAK,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAG7F,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;IACvB;;;OAGG;IACH,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;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;;;;;;;;;GASG;AACH,wBAAsB,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC,CA+CjF;AAgCD;;;GAGG;AACH,wBAAsB,kBAAkB,CACtC,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAE7B;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,eAAe,CACnC,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,MAAM,EACf,MAAM,EAAE;IAAE,KAAK,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI,CAAA;CAAE,GAC1C,OAAO,CAAC,kBAAkB,EAAE,CAAC,CAoD/B;AAED,gFAAgF;AAChF,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,EAAE,CAW5D;AA0DD;;;;;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;AAyBD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,eAAe,CAAC,QAAQ,EAAE,cAAc,EAAE,GAAG,cAAc,EAAE,CAkB5E"}
@@ -11,7 +11,9 @@ import { findTranscript } from "./resumed-session.js";
11
11
  * Claude keeps the history of a subagent in its own transcript,
12
12
  * `<session>/subagents/agent-<id>.jsonl`, next to the session transcript.
13
13
  * The `agent-<id>.meta.json` file beside it names the launching tool use.
14
- * The ids are empty for a session without a local transcript.
14
+ * For a child that Claude Code placed elsewhere, it has `worktreePath`,
15
+ * `inheritedWorktreePath` or `cwd`; then the child's working directory is the
16
+ * `cwd` of its transcript's records. The ids are empty for a session without a local transcript.
15
17
  */
16
18
  export async function subagentHistory(sessionId) {
17
19
  const agents = new Map();
@@ -35,6 +37,12 @@ export async function subagentHistory(sessionId) {
35
37
  if (typeof meta?.toolUseId !== "string")
36
38
  return;
37
39
  const text = (value) => (typeof value === "string" ? value : undefined);
40
+ const placement = [meta.cwd, meta.worktreePath, meta.inheritedWorktreePath]
41
+ .map(text)
42
+ .find((value) => value && path.isAbsolute(value));
43
+ const cwd = placement
44
+ ? ((await recordedCwd(path.join(directory, `agent-${match[1]}.jsonl`))) ?? placement)
45
+ : undefined;
38
46
  agents.set(meta.toolUseId, {
39
47
  agentId: match[1],
40
48
  name: text(meta.name),
@@ -42,6 +50,7 @@ export async function subagentHistory(sessionId) {
42
50
  description: text(meta.description),
43
51
  background: meta.requestShape === "background",
44
52
  stoppedByUser: meta.stoppedByUser === true,
53
+ ...(cwd ? { cwd } : {}),
45
54
  });
46
55
  }
47
56
  catch {
@@ -55,6 +64,35 @@ export async function subagentHistory(sessionId) {
55
64
  const dir = (await getSessionInfo(sessionId).catch(() => undefined))?.cwd;
56
65
  return dir ? { agents, dir } : { agents };
57
66
  }
67
+ /**
68
+ * The `cwd` of the first record of the transcript `file` that has one, or
69
+ * undefined when the file cannot be read. Claude Code writes the working
70
+ * directory of the session or subagent on each record.
71
+ */
72
+ async function recordedCwd(file) {
73
+ const stream = createReadStream(file, { encoding: "utf8" });
74
+ try {
75
+ for await (const line of readline.createInterface({ input: stream, crlfDelay: Infinity })) {
76
+ if (!line.includes('"cwd"'))
77
+ continue;
78
+ try {
79
+ const cwd = JSON.parse(line)?.cwd;
80
+ if (typeof cwd === "string" && path.isAbsolute(cwd))
81
+ return cwd;
82
+ }
83
+ catch {
84
+ // A damaged record; a later one can have the `cwd`.
85
+ }
86
+ }
87
+ }
88
+ catch {
89
+ // An unreadable transcript; the caller falls back to the metadata's path.
90
+ }
91
+ finally {
92
+ stream.destroy();
93
+ }
94
+ return undefined;
95
+ }
58
96
  /** The folder of the subagent transcripts, next to the session transcript `transcript`. */
59
97
  function subagentsDirectory(transcript, sessionId) {
60
98
  return path.join(path.dirname(transcript), sessionId, "subagents");
@@ -121,6 +159,7 @@ export async function subagentLineage(sessionId, agentId, logger) {
121
159
  agentId: current,
122
160
  launchId: launch.launchId,
123
161
  ...(launcher !== null ? { parentAgentId: launcher } : {}),
162
+ ...(launch.record.cwd ? { cwd: launch.record.cwd } : {}),
124
163
  ...(launch.record.name ? { name: launch.record.name } : {}),
125
164
  ...(launch.record.agentType ? { agentType: launch.record.agentType } : {}),
126
165
  ...(launch.record.description ? { description: launch.record.description } : {}),
@@ -54,6 +54,8 @@ export type ReplayChild = {
54
54
  /** The state of the child's last persisted task notification. */
55
55
  notified?: SubagentState;
56
56
  announced: boolean;
57
+ /** The `cwd` that the replay reported for it; undefined while it inherits its parent's. */
58
+ cwd?: string;
57
59
  promptSent: boolean;
58
60
  /** Whether it handed a report to its parent with a SubagentHandback call. */
59
61
  handedBack?: boolean;
@@ -72,6 +74,8 @@ export declare class RfdSubagentReplay {
72
74
  * {@link currentStates}, after the load response.
73
75
  */
74
76
  private readonly mapsDuringReplay;
77
+ /** The working directory of the root session, which a child inherits unless it reports its own. */
78
+ private readonly rootCwd?;
75
79
  /** The children by the id of the tool use that launched each. */
76
80
  private readonly children;
77
81
  private readonly byAgentId;
@@ -88,7 +92,9 @@ export declare class RfdSubagentReplay {
88
92
  * A load lacks the list until the CLI started, so it maps only in
89
93
  * {@link currentStates}, after the load response.
90
94
  */
91
- mapsDuringReplay?: boolean);
95
+ mapsDuringReplay?: boolean,
96
+ /** The working directory of the root session, which a child inherits unless it reports its own. */
97
+ rootCwd?: string | undefined);
92
98
  /**
93
99
  * Registers the launches, the launch results, and the SendMessage calls in
94
100
  * `messages`, the transcript of the session `ownerSessionId`.
@@ -98,10 +104,16 @@ export declare class RfdSubagentReplay {
98
104
  childOfLaunch(toolUseId: string): ReplayChild | undefined;
99
105
  /**
100
106
  * Announces `child` on its parent, as live: running, with the parent's
101
- * labels. Then it sends `firstModel`, the model of the child's first
102
- * response, when the transcript has one.
107
+ * labels, and with its recorded `cwd` when that differs from its parent's.
108
+ * Then it sends `firstModel`, the model of the child's first response, when
109
+ * the transcript has one.
103
110
  */
104
111
  announce(child: ReplayChild, firstModel?: string): Promise<void>;
112
+ /**
113
+ * The working directory of the session `sessionId` as the client holds it:
114
+ * a child's reported `cwd`, else its parent's, up to the root's.
115
+ */
116
+ private effectiveCwd;
105
117
  /**
106
118
  * Takes the model of a replayed response of the child `owner`. A child
107
119
  * whose model changes gets its `model` option again.
@@ -156,6 +168,10 @@ export declare class RfdSubagentReplay {
156
168
  * when a live session is loaded again. Such a child reports that state and
157
169
  * can be cancelled, as live. Any other child cannot be running, because the
158
170
  * process that ran it is gone, so its replayed end is its current state.
171
+ *
172
+ * A replayed `cwd` whose directory is gone, such as a worktree that Claude
173
+ * Code removed when the child finished, is cleared with `cwd: null`, as the
174
+ * RFD requires after a reconnection.
159
175
  */
160
176
  currentStates(live: (sessionId: string) => WorkState["state"] | undefined, liveConfiguration?: (sessionId: string) => SessionConfigOption[] | undefined): AcpSessionNotification[];
161
177
  private registerLaunch;
@@ -1 +1 @@
1
- {"version":3,"file":"subagent-replay.d.ts","sourceRoot":"","sources":["../src/subagent-replay.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"subagent-replay.d.ts","sourceRoot":"","sources":["../src/subagent-replay.ts"],"names":[],"mappings":"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"}
@@ -1,10 +1,12 @@
1
- import { childModelOption, HANDBACK_TOOL, handbackMessage, isNativeSubagentControlTool, launchReply, linkedSessionMessages, nativeSubagentState, responseModelEntry, rfdEndState, rfdLabels, } from "./native-subagents.js";
1
+ import { statSync } from "node:fs";
2
+ import { childModelOption, HANDBACK_TOOL, handbackMessage, isNativeSubagentControlTool, launchReply, linkedSessionMessages, nativeSubagentState, responseModelEntry, rfdEndState, rfdLabels, sameDirectory, } from "./native-subagents.js";
2
3
  import { noticeUpdate } from "./session-notices.js";
3
4
  export class RfdSubagentReplay {
4
5
  history;
5
6
  publish;
6
7
  models;
7
8
  mapsDuringReplay;
9
+ rootCwd;
8
10
  /** The children by the id of the tool use that launched each. */
9
11
  children = new Map();
10
12
  byAgentId = new Map();
@@ -21,11 +23,14 @@ export class RfdSubagentReplay {
21
23
  * A load lacks the list until the CLI started, so it maps only in
22
24
  * {@link currentStates}, after the load response.
23
25
  */
24
- mapsDuringReplay = true) {
26
+ mapsDuringReplay = true,
27
+ /** The working directory of the root session, which a child inherits unless it reports its own. */
28
+ rootCwd) {
25
29
  this.history = history;
26
30
  this.publish = publish;
27
31
  this.models = models;
28
32
  this.mapsDuringReplay = mapsDuringReplay;
33
+ this.rootCwd = rootCwd;
29
34
  }
30
35
  /**
31
36
  * Registers the launches, the launch results, and the SendMessage calls in
@@ -76,13 +81,19 @@ export class RfdSubagentReplay {
76
81
  }
77
82
  /**
78
83
  * Announces `child` on its parent, as live: running, with the parent's
79
- * labels. Then it sends `firstModel`, the model of the child's first
80
- * response, when the transcript has one.
84
+ * labels, and with its recorded `cwd` when that differs from its parent's.
85
+ * Then it sends `firstModel`, the model of the child's first response, when
86
+ * the transcript has one.
81
87
  */
82
88
  async announce(child, firstModel) {
83
89
  if (child.announced)
84
90
  return;
85
91
  const labels = rfdLabels(child.record.name ?? child.input.name, child.record.agentType ?? child.input.subagent_type, child.record.description ?? child.input.description);
92
+ const inherited = this.effectiveCwd(child.parentSessionId);
93
+ const cwd = child.record.cwd &&
94
+ (inherited === undefined || !(await sameDirectory(child.record.cwd, inherited)))
95
+ ? child.record.cwd
96
+ : undefined;
86
97
  await this.publish({
87
98
  sessionId: child.parentSessionId,
88
99
  update: {
@@ -90,12 +101,29 @@ export class RfdSubagentReplay {
90
101
  sessionId: child.sessionId,
91
102
  ...(labels.title ? { title: labels.title } : {}),
92
103
  ...(labels.description ? { description: labels.description } : {}),
104
+ ...(cwd ? { cwd } : {}),
93
105
  state: { state: "running" },
94
106
  },
95
107
  });
108
+ child.cwd = cwd;
96
109
  child.announced = true;
97
110
  await this.responseModel(child, firstModel);
98
111
  }
112
+ /**
113
+ * The working directory of the session `sessionId` as the client holds it:
114
+ * a child's reported `cwd`, else its parent's, up to the root's.
115
+ */
116
+ effectiveCwd(sessionId) {
117
+ const seen = new Set();
118
+ let child = this.byAgentId.get(sessionId);
119
+ while (child && !seen.has(child.sessionId)) {
120
+ if (child.cwd)
121
+ return child.cwd;
122
+ seen.add(child.sessionId);
123
+ child = this.byAgentId.get(child.parentSessionId);
124
+ }
125
+ return this.rootCwd;
126
+ }
99
127
  /**
100
128
  * Takes the model of a replayed response of the child `owner`. A child
101
129
  * whose model changes gets its `model` option again.
@@ -258,6 +286,10 @@ export class RfdSubagentReplay {
258
286
  * when a live session is loaded again. Such a child reports that state and
259
287
  * can be cancelled, as live. Any other child cannot be running, because the
260
288
  * process that ran it is gone, so its replayed end is its current state.
289
+ *
290
+ * A replayed `cwd` whose directory is gone, such as a worktree that Claude
291
+ * Code removed when the child finished, is cleared with `cwd: null`, as the
292
+ * RFD requires after a reconnection.
261
293
  */
262
294
  currentStates(live, liveConfiguration) {
263
295
  return [...this.children.values()]
@@ -278,6 +310,7 @@ export class RfdSubagentReplay {
278
310
  sessionUpdate: "subagent_update",
279
311
  sessionId: child.sessionId,
280
312
  ...(liveState !== undefined ? { capabilities: { cancel: {} } } : {}),
313
+ ...(child.cwd && !isDirectory(child.cwd) ? { cwd: null } : {}),
281
314
  state,
282
315
  },
283
316
  },
@@ -427,6 +460,15 @@ function textOf(content) {
427
460
  .filter((text) => typeof text === "string")
428
461
  .join("\n");
429
462
  }
463
+ /** Whether `dir` is an existing directory. */
464
+ function isDirectory(dir) {
465
+ try {
466
+ return statSync(dir).isDirectory();
467
+ }
468
+ catch {
469
+ return false;
470
+ }
471
+ }
430
472
  /** A message that a SendMessage call delivered to the child. */
431
473
  function isDelivery(message) {
432
474
  return message.origin?.kind === "coordinator";
@@ -1 +1 @@
1
- {"version":3,"file":"interaction.d.ts","sourceRoot":"","sources":["../../../src/tool-calls/reporters/interaction.ts"],"names":[],"mappings":"AAoBA,OAAO,KAAK,EACV,YAAY,EACZ,iBAAiB,EACjB,eAAe,EACf,cAAc,EACd,YAAY,EACb,MAAM,aAAa,CAAC;AAErB;;;;;;;GAOG;AACH,qBAAa,oBAAqB,YAAW,YAAY;IACvD,OAAO,CAAC,KAAK,EAAE,OAAO,EAAE,EAAE,YAAY,EAAE,EAAE,cAAc,GAAG,YAAY;IAYvE,iEAAiE;IACjE,UAAU,CAAC,OAAO,EAAE,iBAAiB,GAAG,eAAe;IASvD;;;OAGG;IACH,WAAW,CAAC,OAAO,EAAE,iBAAiB,GAAG,eAAe,GAAG,SAAS;CASrE;AAED,2GAA2G;AAC3G,qBAAa,qBAAsB,YAAW,YAAY;IACxD,OAAO,IAAI,YAAY;CAGxB;AAqBD,oEAAoE;AACpE,qBAAa,uBAAwB,YAAW,YAAY;IAC1D;;;OAGG;IACH,OAAO,CAAC,KAAK,EAAE,OAAO,EAAE,EAAE,YAAY,EAAE,EAAE,cAAc,GAAG,YAAY;CAgBxE;AAED,wEAAwE;AACxE,qBAAa,aAAc,YAAW,YAAY;IAChD,OAAO,CAAC,KAAK,EAAE,OAAO,GAAG,YAAY;IAKrC,UAAU,IAAI,eAAe;CAG9B;AAED;;;GAGG;AACH,qBAAa,gBAAiB,YAAW,YAAY;IACvC,OAAO,CAAC,QAAQ,CAAC,QAAQ;gBAAR,QAAQ,EAAE,MAAM;IAE7C,OAAO,CAAC,KAAK,EAAE,OAAO,GAAG,YAAY;CAyBtC;AAED,kEAAkE;AAClE,qBAAa,sBAAuB,YAAW,YAAY;IACzD,OAAO,CAAC,KAAK,EAAE,OAAO,GAAG,YAAY;CAmBtC;AAED,8EAA8E;AAC9E,qBAAa,eAAgB,YAAW,YAAY;IACtC,OAAO,CAAC,QAAQ,CAAC,QAAQ;gBAAR,QAAQ,EAAE,MAAM;IAE7C,OAAO,CAAC,KAAK,EAAE,OAAO,GAAG,YAAY;CAgBtC;AAED;;;;;GAKG;AACH,qBAAa,oBAAqB,SAAQ,eAAe;IACvD,WAAW,CAAC,EAAE,MAAM,EAAE,YAAY,EAAE,EAAE,iBAAiB,GAAG,eAAe,GAAG,SAAS;CAGtF;AA4HD;;;;;GAKG;AACH,MAAM,MAAM,cAAc,GAAG,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;AA0C9D;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,gBAAgB,CAC9B,SAAS,EAAE,MAAM,EACjB,GAAG,CAAC,EAAE,MAAM,EACZ,KAAK,CAAC,EAAE,cAAc,GACrB,MAAM,GAAG,SAAS,CA8EpB"}
1
+ {"version":3,"file":"interaction.d.ts","sourceRoot":"","sources":["../../../src/tool-calls/reporters/interaction.ts"],"names":[],"mappings":"AAoBA,OAAO,KAAK,EACV,YAAY,EACZ,iBAAiB,EACjB,eAAe,EACf,cAAc,EACd,YAAY,EACb,MAAM,aAAa,CAAC;AAErB;;;;;;;GAOG;AACH,qBAAa,oBAAqB,YAAW,YAAY;IACvD,OAAO,CAAC,KAAK,EAAE,OAAO,EAAE,EAAE,YAAY,EAAE,EAAE,cAAc,GAAG,YAAY;IAYvE,iEAAiE;IACjE,UAAU,CAAC,OAAO,EAAE,iBAAiB,GAAG,eAAe;IASvD;;;OAGG;IACH,WAAW,CAAC,OAAO,EAAE,iBAAiB,GAAG,eAAe,GAAG,SAAS;CASrE;AAED,2GAA2G;AAC3G,qBAAa,qBAAsB,YAAW,YAAY;IACxD,OAAO,IAAI,YAAY;CAGxB;AAqBD,oEAAoE;AACpE,qBAAa,uBAAwB,YAAW,YAAY;IAC1D;;;OAGG;IACH,OAAO,CAAC,KAAK,EAAE,OAAO,EAAE,EAAE,YAAY,EAAE,EAAE,cAAc,GAAG,YAAY;CAgBxE;AAED,wEAAwE;AACxE,qBAAa,aAAc,YAAW,YAAY;IAChD,OAAO,CAAC,KAAK,EAAE,OAAO,GAAG,YAAY;IAKrC,UAAU,IAAI,eAAe;CAG9B;AAED;;;GAGG;AACH,qBAAa,gBAAiB,YAAW,YAAY;IACvC,OAAO,CAAC,QAAQ,CAAC,QAAQ;gBAAR,QAAQ,EAAE,MAAM;IAE7C,OAAO,CAAC,KAAK,EAAE,OAAO,GAAG,YAAY;CAyBtC;AAED,kEAAkE;AAClE,qBAAa,sBAAuB,YAAW,YAAY;IACzD,OAAO,CAAC,KAAK,EAAE,OAAO,GAAG,YAAY;CAmBtC;AAED,8EAA8E;AAC9E,qBAAa,eAAgB,YAAW,YAAY;IACtC,OAAO,CAAC,QAAQ,CAAC,QAAQ;gBAAR,QAAQ,EAAE,MAAM;IAE7C,OAAO,CAAC,KAAK,EAAE,OAAO,GAAG,YAAY;CAgBtC;AAED;;;;;GAKG;AACH,qBAAa,oBAAqB,SAAQ,eAAe;IACvD,WAAW,CAAC,EAAE,MAAM,EAAE,YAAY,EAAE,EAAE,iBAAiB,GAAG,eAAe,GAAG,SAAS;CAGtF;AAiID;;;;;GAKG;AACH,MAAM,MAAM,cAAc,GAAG,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;AA0C9D;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,gBAAgB,CAC9B,SAAS,EAAE,MAAM,EACjB,GAAG,CAAC,EAAE,MAAM,EACZ,KAAK,CAAC,EAAE,cAAc,GACrB,MAAM,GAAG,SAAS,CA8EpB"}
@@ -256,14 +256,15 @@ function frontmatterScalar(raw) {
256
256
  return value;
257
257
  }
258
258
  /**
259
- * The `name:` that the frontmatter of [skillMdPath] declares, or `undefined` when it declares none.
259
+ * The `name:` that the frontmatter of [skillMdPath] declares, `undefined` when it declares none,
260
+ * or `null` when the file could not be read.
260
261
  *
261
262
  * Only the frontmatter block is parsed, so a `name:` further down the body is not a name.
262
263
  */
263
264
  function declaredSkillName(skillMdPath) {
264
265
  const head = frontmatterHead(skillMdPath);
265
266
  if (head === undefined) {
266
- return undefined;
267
+ return null;
267
268
  }
268
269
  const lines = head.split(/\r?\n/);
269
270
  if (lines[0]?.trim() !== "---") {
@@ -298,6 +299,10 @@ function cachedDeclaredName(skillMdPath, stats) {
298
299
  return cached.declaredName;
299
300
  }
300
301
  const declaredName = declaredSkillName(skillMdPath);
302
+ // A transient read error can clear without changing the stat signature, so the next pass must retry it.
303
+ if (declaredName === null) {
304
+ return null;
305
+ }
301
306
  if (scannedSkills.size >= MAX_CACHED_SKILLS) {
302
307
  scannedSkills.clear();
303
308
  }
@@ -333,7 +338,7 @@ function skillsByDeclaredName(containerDir, scans) {
333
338
  continue;
334
339
  }
335
340
  const name = cachedDeclaredName(file, stats);
336
- if (name !== undefined && !byName.has(name)) {
341
+ if (typeof name === "string" && !byName.has(name)) {
337
342
  byName.set(name, file);
338
343
  }
339
344
  }
@@ -1 +1 @@
1
- {"version":3,"file":"agent.d.ts","sourceRoot":"","sources":["../../src/v2/agent.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAE,MAAM,mDAAmD,CAAC;AACxE,OAAO,EAAE,cAAc,EAAkB,KAAK,MAAM,EAAE,MAAM,iBAAiB,CAAC;AAqB9E;;;;;;;;GAQG;AACH,wBAAgB,UAAU,CACxB,MAAM,EAAE,MAAM,GAAG,SAAS,EAC1B,OAAO,EAAE,CAAC,KAAK,EAAE,cAAc,KAAK,IAAI,GACvC,EAAE,CAAC,QAAQ,CAkGb"}
1
+ {"version":3,"file":"agent.d.ts","sourceRoot":"","sources":["../../src/v2/agent.ts"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EAAE,MAAM,mDAAmD,CAAC;AACxE,OAAO,EAAE,cAAc,EAAkB,KAAK,MAAM,EAAE,MAAM,iBAAiB,CAAC;AAqB9E;;;;;;;;GAQG;AACH,wBAAgB,UAAU,CACxB,MAAM,EAAE,MAAM,GAAG,SAAS,EAC1B,OAAO,EAAE,CAAC,KAAK,EAAE,cAAc,KAAK,IAAI,GACvC,EAAE,CAAC,QAAQ,CA4Gb"}
package/dist/v2/agent.js CHANGED
@@ -39,11 +39,11 @@ export function v2AgentApp(logger, onAgent) {
39
39
  .onRequest(v2.methods.agent.providers.set, ({ params }) => agent.unstable_setProvider(params))
40
40
  .onRequest(v2.methods.agent.providers.disable, ({ params }) => agent.unstable_disableProvider(params))
41
41
  .onRequest(v2.methods.agent.session.new, async ({ params }) => {
42
- const response = await agent.newSession(v1NewSessionRequest(params));
43
- return {
44
- ...v2NewSessionResponse(response),
45
- ...(await setupCommands(agent, response.sessionId, logger)),
46
- };
42
+ const request = v1NewSessionRequest(params);
43
+ const response = await agent.newSession(request);
44
+ const commands = await setupCommands(agent, response.sessionId, logger);
45
+ afterSetupResponse(agent, response.sessionId, request.mcpServers, commands, logger);
46
+ return { ...v2NewSessionResponse(response), ...commands };
47
47
  })
48
48
  .onRequest(v2.methods.agent.session.list, ({ params }) => agent.listSessions(params))
49
49
  .onRequest(v2.methods.agent.session.resume, async ({ params }) => {
@@ -65,16 +65,19 @@ export function v2AgentApp(logger, onAgent) {
65
65
  const commands = await setupCommands(agent, params.sessionId, logger);
66
66
  // The response ends a replay. The subagents RFD treats the replayed
67
67
  // subagent states before it as history, so their current states follow it.
68
- setTimeout(() => void agent.publishCurrentSubagentStates(params.sessionId), 0);
68
+ afterSetupResponse(agent, params.sessionId, restore.request.mcpServers ?? [], commands, logger, () => void agent.publishCurrentSubagentStates(params.sessionId));
69
69
  return { ...v2ResumeSessionResponse(response), ...commands };
70
70
  })
71
71
  .onRequest(v2.methods.agent.session.fork, ({ params }) => agent.forking(params.sessionId, async () => {
72
72
  // The agent starts the forked session, so the client can prompt it.
73
- const response = await agent.unstable_forkSession(v1ForkSessionRequest(params));
73
+ const request = v1ForkSessionRequest(params);
74
+ const response = await agent.unstable_forkSession(request);
75
+ const commands = await setupCommands(agent, response.sessionId, logger);
76
+ afterSetupResponse(agent, response.sessionId, request.mcpServers ?? [], commands, logger);
74
77
  return {
75
78
  sessionId: response.sessionId,
76
79
  ...v2ResumeSessionResponse(response),
77
- ...(await setupCommands(agent, response.sessionId, logger)),
80
+ ...commands,
78
81
  };
79
82
  }))
80
83
  .onRequest(v2.methods.agent.session.close, async ({ params }) => {
@@ -111,12 +114,13 @@ function stateSnapshot(state) {
111
114
  return { sessionUpdate: "state_update", state: "requires_action" };
112
115
  }
113
116
  /**
114
- * The `availableCommands` of a setup response, so a client can show the
115
- * session's commands without waiting for an update. The agent sends a v1
116
- * client the same list as an `available_commands_update` after the response,
117
- * and a v2 client none (`ClaudeAcpAgent.afterSetupResponse`). Later changes
118
- * still arrive as updates. An empty list is left out, as the draft asks. The
119
- * commands are optional, so a failure to read them does not fail the setup.
117
+ * The `availableCommands` of a setup response, which v2 requires when the
118
+ * session has commands. The agent sends a v1 client the same list as an
119
+ * `available_commands_update` after the response, and a v2 client none
120
+ * (`ClaudeAcpAgent.afterSetupResponse`). Later changes still arrive as
121
+ * updates. An empty list is left out, as the draft asks. A failure to read
122
+ * the commands does not fail the setup: the result is `undefined`, and
123
+ * {@link afterSetupResponse} sends the commands in an update instead.
120
124
  */
121
125
  async function setupCommands(agent, sessionId, logger) {
122
126
  try {
@@ -125,9 +129,30 @@ async function setupCommands(agent, sessionId, logger) {
125
129
  }
126
130
  catch (error) {
127
131
  (logger ?? console).error(`Session ${sessionId}: failed to read the session's commands`, error);
128
- return {};
132
+ return undefined;
129
133
  }
130
134
  }
135
+ /**
136
+ * The work that follows a v2 setup response, run once the response is sent
137
+ * (`ClaudeAcpAgent.afterSetupResponse` does it on v1):
138
+ *
139
+ * - When `commands` is `undefined` (reading them for the response failed),
140
+ * an `available_commands_update` with the commands, read again.
141
+ * - `then`, such as sending the current states of replayed subagents.
142
+ * - OAuth for the MCP servers that need it. Its URL elicitation must not
143
+ * reach the client before the response, which v2 asks against.
144
+ */
145
+ function afterSetupResponse(agent, sessionId, mcpServers, commands, logger, then) {
146
+ setTimeout(() => {
147
+ if (!commands) {
148
+ agent.sendAvailableCommandsUpdate(sessionId).catch((error) => {
149
+ (logger ?? console).error(`Session ${sessionId}: failed to send the session's commands`, error);
150
+ });
151
+ }
152
+ then?.();
153
+ agent.authenticateMcpServers(sessionId, mcpServers);
154
+ }, 0);
155
+ }
131
156
  /**
132
157
  * The {@link AcpClient} of an ACP v2 connection: it sends the v1 messages of
133
158
  * the agent as v2 messages.
@@ -291,7 +316,7 @@ class V2ClientConnection {
291
316
  async requestPermission(params, signal) {
292
317
  // The subject is an update of the tool call, with its stored `_meta`.
293
318
  const toolCall = this.toolCallMeta.subject(params.sessionId, params.toolCall);
294
- const response = await this.ctx.request(v2.methods.client.session.requestPermission, v2PermissionRequest({ ...params, toolCall }), { cancellationSignal: signal });
319
+ const response = await this.ctx.request(v2.methods.client.session.requestPermission, v2PermissionRequest({ ...params, toolCall }), { cancellationSignal: signal, stopWaitingOnCancel: true });
295
320
  return v1PermissionResponse(response);
296
321
  }
297
322
  // Elicitation is the same in v1 and v2.
@@ -300,7 +325,7 @@ class V2ClientConnection {
300
325
  // The v1 types accept a property schema with any tag; the v2 types accept
301
326
  // one only in a received value. As on v1, an MCP server's schema is
302
327
  // relayed here unchanged, so this cast does not check it.
303
- params, { cancellationSignal: signal });
328
+ params, { cancellationSignal: signal, stopWaitingOnCancel: true });
304
329
  }
305
330
  completeElicitation(params) {
306
331
  return this.ctx.notify(v2.methods.client.elicitation.complete, params);
@@ -15,7 +15,22 @@ export declare function v1InitializeRequest(request: v2.InitializeRequest): Init
15
15
  * The v2 `initialize` response for the v1 response of the agent.
16
16
  *
17
17
  * It does not advertise what the v2 surface does not serve yet: the
18
- * extensions of {@link UNSERVED_EXTENSION_KEYS}.
18
+ * extensions of {@link UNSERVED_EXTENSION_KEYS}, and the AIR extensions of
19
+ * {@link withoutUnservedAirExtensions}.
19
20
  */
20
21
  export declare function v2InitializeResponse(response: InitializeResponse): v2.InitializeResponse;
22
+ /**
23
+ * The session capabilities of the v2 surface, which serves each through a v1
24
+ * method of the agent.
25
+ *
26
+ * - The session baseline: `session/new`, `list`, `resume` (with replay),
27
+ * `close`, `prompt`, `cancel`, and `update`.
28
+ * - `session/delete`, `session/fork` (which returns a session the client can
29
+ * prompt at once), and `additionalDirectories`.
30
+ * - The prompt content and MCP transports that the agent's v1 `initialize`
31
+ * lists: images, embedded context, and HTTP servers. Stdio servers are in the
32
+ * v1 baseline; v2 has no SSE transport.
33
+ * - `systemPrompt`, in `append` and `override` mode.
34
+ */
35
+ export declare const V2_SESSION_CAPABILITIES: v2.SessionCapabilities;
21
36
  //# sourceMappingURL=initialize.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"initialize.d.ts","sourceRoot":"","sources":["../../src/v2/initialize.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,EAGL,KAAK,iBAAiB,EACtB,KAAK,kBAAkB,EACxB,MAAM,mCAAmC,CAAC;AAC3C,OAAO,KAAK,EAAE,MAAM,mDAAmD,CAAC;AAGxE,0DAA0D;AAC1D,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,EAAE,CAAC,iBAAiB,GAAG,iBAAiB,CAmCpF;AASD;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,kBAAkB,GAAG,EAAE,CAAC,kBAAkB,CAuBxF"}
1
+ {"version":3,"file":"initialize.d.ts","sourceRoot":"","sources":["../../src/v2/initialize.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,EAGL,KAAK,iBAAiB,EACtB,KAAK,kBAAkB,EACxB,MAAM,mCAAmC,CAAC;AAC3C,OAAO,KAAK,EAAE,MAAM,mDAAmD,CAAC;AAIxE,0DAA0D;AAC1D,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,EAAE,CAAC,iBAAiB,GAAG,iBAAiB,CAmCpF;AASD;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,kBAAkB,GAAG,EAAE,CAAC,kBAAkB,CA2BxF;AA0BD;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,uBAAuB,EAAE,EAAE,CAAC,mBAOxC,CAAC"}
@@ -9,6 +9,7 @@
9
9
  */
10
10
  import { PROTOCOL_VERSION as V1_PROTOCOL_VERSION, } from "@agentclientprotocol/sdk/unstable";
11
11
  import * as v2 from "@agentclientprotocol/sdk/experimental/v2/unstable";
12
+ import { AIR_ASYNC_TASKS_CAPABILITY, AIR_GOAL_KEY } from "../air-extension.js";
12
13
  import { SYSTEM_PROMPT_CAPABILITY } from "../client-system-prompt.js";
13
14
  /** The v1 `initialize` request that a v2 client means. */
14
15
  export function v1InitializeRequest(request) {
@@ -57,14 +58,15 @@ const UNSERVED_EXTENSION_KEYS = ["steering"];
57
58
  * The v2 `initialize` response for the v1 response of the agent.
58
59
  *
59
60
  * It does not advertise what the v2 surface does not serve yet: the
60
- * extensions of {@link UNSERVED_EXTENSION_KEYS}.
61
+ * extensions of {@link UNSERVED_EXTENSION_KEYS}, and the AIR extensions of
62
+ * {@link withoutUnservedAirExtensions}.
61
63
  */
62
64
  export function v2InitializeResponse(response) {
63
65
  if (!response.agentInfo) {
64
66
  throw new Error("ACP v2 requires agentInfo, and the agent reported none");
65
67
  }
66
68
  const meta = response.agentCapabilities?._meta;
67
- const topMeta = Object.fromEntries(Object.entries(response._meta ?? {}).filter(([key]) => !UNSERVED_EXTENSION_KEYS.includes(key)));
69
+ const topMeta = withoutUnservedAirExtensions(Object.fromEntries(Object.entries(response._meta ?? {}).filter(([key]) => !UNSERVED_EXTENSION_KEYS.includes(key))));
68
70
  return {
69
71
  protocolVersion: v2.PROTOCOL_VERSION,
70
72
  info: response.agentInfo,
@@ -81,6 +83,27 @@ export function v2InitializeResponse(response) {
81
83
  ...(Object.keys(topMeta).length > 0 ? { _meta: topMeta } : {}),
82
84
  };
83
85
  }
86
+ /**
87
+ * `meta` without the AIR extensions whose methods the v2 surface does not
88
+ * register: the `asyncTasks` capability (`_session/async_task/stop`) and the
89
+ * `goal` entry (`_session/goal`) of `_meta.jetbrains.air`.
90
+ */
91
+ function withoutUnservedAirExtensions(meta) {
92
+ const jetbrains = asRecord(meta.jetbrains);
93
+ const air = asRecord(jetbrains.air);
94
+ if (Object.keys(air).length === 0)
95
+ return meta;
96
+ const served = Object.fromEntries(Object.entries(air).filter(([key]) => key !== AIR_GOAL_KEY));
97
+ if (Array.isArray(served.capabilities)) {
98
+ served.capabilities = served.capabilities.filter((capability) => capability !== AIR_ASYNC_TASKS_CAPABILITY);
99
+ }
100
+ return { ...meta, jetbrains: { ...jetbrains, air: served } };
101
+ }
102
+ function asRecord(value) {
103
+ return value && typeof value === "object" && !Array.isArray(value)
104
+ ? value
105
+ : {};
106
+ }
84
107
  /**
85
108
  * The session capabilities of the v2 surface, which serves each through a v1
86
109
  * method of the agent.
@@ -94,7 +117,7 @@ export function v2InitializeResponse(response) {
94
117
  * v1 baseline; v2 has no SSE transport.
95
118
  * - `systemPrompt`, in `append` and `override` mode.
96
119
  */
97
- const V2_SESSION_CAPABILITIES = {
120
+ export const V2_SESSION_CAPABILITIES = {
98
121
  prompt: { image: {}, embeddedContext: {} },
99
122
  mcp: { stdio: {}, http: {} },
100
123
  delete: {},
@@ -18,8 +18,11 @@ export declare function v2PermissionRequest(request: AcpPermissionRequest): v2.R
18
18
  * The v1 form of the answer to a permission request.
19
19
  *
20
20
  * v2 adds outcomes that v1 does not know, which the draft says are no
21
- * approval. They become `cancelled`, which the agent reads as an aborted tool
22
- * use, as it does a cancelled prompt.
21
+ * approval. They become `cancelled`. In v2, `cancelled` also means that the
22
+ * user dismissed the prompt or the client declined it by policy. So the agent
23
+ * reads a `cancelled` outcome on v2 as the user refusing the tool, unless a
24
+ * `session/cancel` is stopping the session's work: then the tool use is
25
+ * aborted, as on v1.
23
26
  */
24
27
  export declare function v1PermissionResponse(response: v2.RequestPermissionResponse): RequestPermissionResponse;
25
28
  //# sourceMappingURL=permission.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"permission.d.ts","sourceRoot":"","sources":["../../src/v2/permission.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,mCAAmC,CAAC;AACnF,OAAO,KAAK,KAAK,EAAE,MAAM,mDAAmD,CAAC;AAC7E,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,gCAAgC,CAAC;AAG3E;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,oBAAoB,GAAG,EAAE,CAAC,wBAAwB,CAY9F;AAED;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAClC,QAAQ,EAAE,EAAE,CAAC,yBAAyB,GACrC,yBAAyB,CAS3B"}
1
+ {"version":3,"file":"permission.d.ts","sourceRoot":"","sources":["../../src/v2/permission.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,mCAAmC,CAAC;AACnF,OAAO,KAAK,KAAK,EAAE,MAAM,mDAAmD,CAAC;AAC7E,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,gCAAgC,CAAC;AAG3E;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,oBAAoB,GAAG,EAAE,CAAC,wBAAwB,CAY9F;AAED;;;;;;;;;GASG;AACH,wBAAgB,oBAAoB,CAClC,QAAQ,EAAE,EAAE,CAAC,yBAAyB,GACrC,yBAAyB,CAS3B"}
@@ -24,8 +24,11 @@ export function v2PermissionRequest(request) {
24
24
  * The v1 form of the answer to a permission request.
25
25
  *
26
26
  * v2 adds outcomes that v1 does not know, which the draft says are no
27
- * approval. They become `cancelled`, which the agent reads as an aborted tool
28
- * use, as it does a cancelled prompt.
27
+ * approval. They become `cancelled`. In v2, `cancelled` also means that the
28
+ * user dismissed the prompt or the client declined it by policy. So the agent
29
+ * reads a `cancelled` outcome on v2 as the user refusing the tool, unless a
30
+ * `session/cancel` is stopping the session's work: then the tool use is
31
+ * aborted, as on v1.
29
32
  */
30
33
  export function v1PermissionResponse(response) {
31
34
  const outcome = response.outcome;
@@ -51,7 +51,8 @@ export declare function v2Foreground(send: (update: v2.SessionUpdate) => void):
51
51
  * so such a block fails the request with invalid params (-32602) rather than
52
52
  * being dropped from the prompt. The same goes for an embedded blob that
53
53
  * Claude cannot read, e.g. an `application/zip` resource: `promptToClaude`
54
- * would drop it.
54
+ * would drop it. An empty `prompt` fails the same way: v2 requires at least
55
+ * one content block.
55
56
  */
56
57
  export declare function v1PromptRequest({ prompt, ...request }: v2.PromptRequest): PromptRequest;
57
58
  export declare function v1ContentBlock(block: v2.ContentBlock): ContentBlock;