@agentclientprotocol/claude-agent-acp 0.85.2-preview.19 → 0.85.2-preview.3

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 (62) hide show
  1. package/README.md +0 -1
  2. package/dist/acp-agent.d.ts +18 -81
  3. package/dist/acp-agent.d.ts.map +1 -1
  4. package/dist/acp-agent.js +170 -435
  5. package/dist/air-extension.d.ts +0 -2
  6. package/dist/air-extension.d.ts.map +1 -1
  7. package/dist/air-extension.js +0 -2
  8. package/dist/diff.d.ts +8 -45
  9. package/dist/diff.d.ts.map +1 -1
  10. package/dist/diff.js +30 -111
  11. package/dist/permissions/presentation.d.ts +2 -17
  12. package/dist/permissions/presentation.d.ts.map +1 -1
  13. package/dist/permissions/presentation.js +0 -4
  14. package/dist/resumed-session.d.ts +6 -16
  15. package/dist/resumed-session.d.ts.map +1 -1
  16. package/dist/resumed-session.js +25 -91
  17. package/dist/session-mode.d.ts +0 -14
  18. package/dist/session-mode.d.ts.map +1 -1
  19. package/dist/session-mode.js +0 -22
  20. package/dist/tool-calls/client-capabilities.d.ts +2 -20
  21. package/dist/tool-calls/client-capabilities.d.ts.map +1 -1
  22. package/dist/tool-calls/client-capabilities.js +4 -14
  23. package/dist/tool-calls/renderer.d.ts.map +1 -1
  24. package/dist/tool-calls/renderer.js +2 -3
  25. package/dist/tool-calls/reporters/file-edit.d.ts +1 -1
  26. package/dist/tool-calls/reporters/file-edit.d.ts.map +1 -1
  27. package/dist/tool-calls/reporters/file-edit.js +14 -20
  28. package/dist/turn-events.d.ts +5 -11
  29. package/dist/turn-events.d.ts.map +1 -1
  30. package/dist/usage-markdown.d.ts +0 -2
  31. package/dist/usage-markdown.d.ts.map +1 -1
  32. package/dist/usage-markdown.js +1 -2
  33. package/dist/utils.d.ts +0 -12
  34. package/dist/utils.d.ts.map +1 -1
  35. package/dist/utils.js +0 -28
  36. package/dist/v2/agent.d.ts.map +1 -1
  37. package/dist/v2/agent.js +17 -121
  38. package/dist/v2/initialize.d.ts +5 -2
  39. package/dist/v2/initialize.d.ts.map +1 -1
  40. package/dist/v2/initialize.js +6 -23
  41. package/dist/v2/session-update.d.ts +0 -6
  42. package/dist/v2/session-update.d.ts.map +1 -1
  43. package/dist/v2/session-update.js +0 -27
  44. package/dist/v2/session.d.ts +4 -15
  45. package/dist/v2/session.d.ts.map +1 -1
  46. package/dist/v2/session.js +6 -20
  47. package/package.json +2 -2
  48. package/dist/mcp-command.d.ts +0 -51
  49. package/dist/mcp-command.d.ts.map +0 -1
  50. package/dist/mcp-command.js +0 -321
  51. package/dist/v2/permission.d.ts +0 -25
  52. package/dist/v2/permission.d.ts.map +0 -1
  53. package/dist/v2/permission.js +0 -38
  54. package/dist/v2/prompt.d.ts +0 -47
  55. package/dist/v2/prompt.d.ts.map +0 -1
  56. package/dist/v2/prompt.js +0 -153
  57. package/dist/v2/terminal.d.ts +0 -42
  58. package/dist/v2/terminal.d.ts.map +0 -1
  59. package/dist/v2/terminal.js +0 -95
  60. package/dist/v2/tool-call.d.ts +0 -38
  61. package/dist/v2/tool-call.d.ts.map +0 -1
  62. package/dist/v2/tool-call.js +0 -49
@@ -38,8 +38,11 @@ export function v1InitializeRequest(request) {
38
38
  /**
39
39
  * The v2 `initialize` response for the v1 response of the agent.
40
40
  *
41
- * It does not advertise the unstable `providers` methods, which the v2
42
- * surface does not serve yet.
41
+ * It advertises no `session` capability yet. A v2 agent that advertises
42
+ * `session` must serve the whole session baseline (`session/new`, `list`,
43
+ * `resume`, `close`, `prompt`, `cancel`, and `update`), and the v2 surface of
44
+ * the adapter does not serve it yet. Nor does it advertise the unstable
45
+ * `providers` methods, which the v2 surface does not serve yet either.
43
46
  */
44
47
  export function v2InitializeResponse(response) {
45
48
  if (!response.agentInfo) {
@@ -51,31 +54,11 @@ export function v2InitializeResponse(response) {
51
54
  info: response.agentInfo,
52
55
  // v1 `auth.logout` has no v2 counterpart: a v2 agent that advertises auth
53
56
  // methods must serve `auth/logout`.
54
- capabilities: {
55
- session: V2_SESSION_CAPABILITIES,
56
- ...(meta != null ? { _meta: meta } : {}),
57
- },
57
+ capabilities: meta != null ? { _meta: meta } : {},
58
58
  authMethods: (response.authMethods ?? []).map(v2AuthMethod),
59
59
  ...(response._meta != null ? { _meta: response._meta } : {}),
60
60
  };
61
61
  }
62
- /**
63
- * The session capabilities of the v2 surface, which serves each through a v1
64
- * method of the agent.
65
- *
66
- * - The session baseline: `session/new`, `list`, `resume` (with replay),
67
- * `close`, `prompt`, `cancel`, and `update`.
68
- * - `session/delete` and `additionalDirectories`. Not `session/fork` yet.
69
- * - The prompt content and MCP transports that the agent's v1 `initialize`
70
- * lists: images, embedded context, and HTTP servers. Stdio servers are in the
71
- * v1 baseline; v2 has no SSE transport.
72
- */
73
- const V2_SESSION_CAPABILITIES = {
74
- prompt: { image: {}, embeddedContext: {} },
75
- mcp: { stdio: {}, http: {} },
76
- delete: {},
77
- additionalDirectories: {},
78
- };
79
62
  /** v2 names the id of a method `methodId`, requires `type`, and lists `env` as name and value pairs. */
80
63
  function v2AuthMethod(method) {
81
64
  if ("type" in method && method.type === "terminal") {
@@ -4,12 +4,6 @@
4
4
  */
5
5
  import * as v2 from "@agentclientprotocol/sdk/experimental/v2";
6
6
  import type { AcpSessionNotification } from "../acp-subagents.js";
7
- /**
8
- * The id of the plan of a session. v1 has one plan per session, which every
9
- * plan update replaces whole, from TodoWrite and from the Task tools alike.
10
- * v2 tracks plans by id, so that plan gets one.
11
- */
12
- export declare const SESSION_PLAN_ID = "plan";
13
7
  /**
14
8
  * The v2 form of a v1 session update, or `undefined` when v2 has no
15
9
  * counterpart and the update tells a v2 client nothing new.
@@ -1 +1 @@
1
- {"version":3,"file":"session-update.d.ts","sourceRoot":"","sources":["../../src/v2/session-update.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,KAAK,EAAE,MAAM,0CAA0C,CAAC;AAC/D,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,qBAAqB,CAAC;AAIlE;;;;GAIG;AACH,eAAO,MAAM,eAAe,SAAS,CAAC;AAEtC;;;;;;GAMG;AACH,wBAAgB,eAAe,CAC7B,MAAM,EAAE,sBAAsB,CAAC,QAAQ,CAAC,GACvC,EAAE,CAAC,aAAa,GAAG,SAAS,CAwC9B"}
1
+ {"version":3,"file":"session-update.d.ts","sourceRoot":"","sources":["../../src/v2/session-update.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,KAAK,EAAE,MAAM,0CAA0C,CAAC;AAC/D,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,qBAAqB,CAAC;AAGlE;;;;;;GAMG;AACH,wBAAgB,eAAe,CAC7B,MAAM,EAAE,sBAAsB,CAAC,QAAQ,CAAC,GACvC,EAAE,CAAC,aAAa,GAAG,SAAS,CAoB9B"}
@@ -1,11 +1,4 @@
1
1
  import { v2AvailableCommands, v2ConfigOptions } from "./session.js";
2
- import { v2ToolCallUpdate } from "./tool-call.js";
3
- /**
4
- * The id of the plan of a session. v1 has one plan per session, which every
5
- * plan update replaces whole, from TodoWrite and from the Task tools alike.
6
- * v2 tracks plans by id, so that plan gets one.
7
- */
8
- export const SESSION_PLAN_ID = "plan";
9
2
  /**
10
3
  * The v2 form of a v1 session update, or `undefined` when v2 has no
11
4
  * counterpart and the update tells a v2 client nothing new.
@@ -23,26 +16,6 @@ export function v2SessionUpdate(update) {
23
16
  case "usage_update":
24
17
  case "notice":
25
18
  return update;
26
- case "agent_message_chunk":
27
- case "agent_thought_chunk":
28
- case "user_message_chunk": {
29
- // v2 requires the id of the message a chunk belongs to; v1 allows none.
30
- const { messageId, ...chunk } = update;
31
- if (!messageId) {
32
- throw new Error(`An ACP v2 ${update.sessionUpdate} needs a messageId`);
33
- }
34
- return { ...chunk, messageId };
35
- }
36
- case "tool_call":
37
- case "tool_call_update":
38
- return v2ToolCallUpdate(update);
39
- case "plan":
40
- // A v2 plan of items is replaced whole as well.
41
- return {
42
- sessionUpdate: "plan_update",
43
- plan: { type: "items", planId: SESSION_PLAN_ID, entries: update.entries },
44
- ...(update._meta != null ? { _meta: update._meta } : {}),
45
- };
46
19
  case "current_mode_update":
47
20
  // v2 has no modes: the mode is the `mode` config option. Every mode
48
21
  // change also reaches the client as a `config_option_update` or in the
@@ -3,25 +3,14 @@
3
3
  * `ClaudeAcpAgent`: v2 requests become the v1 requests that the agent serves,
4
4
  * and its v1 responses become v2 responses.
5
5
  */
6
- import { type AvailableCommand, type LoadSessionRequest, type NewSessionRequest, type NewSessionResponse, type ResumeSessionRequest, type ResumeSessionResponse, type SessionConfigOption, type SetSessionConfigOptionRequest } from "@agentclientprotocol/sdk";
6
+ import { type AvailableCommand, type NewSessionRequest, type NewSessionResponse, type ResumeSessionRequest, type ResumeSessionResponse, type SessionConfigOption, type SetSessionConfigOptionRequest } from "@agentclientprotocol/sdk";
7
7
  import * as v2 from "@agentclientprotocol/sdk/experimental/v2";
8
8
  export declare function v1NewSessionRequest(params: v2.NewSessionRequest): NewSessionRequest;
9
9
  /**
10
- * The v1 request that a v2 `session/resume` means.
11
- *
12
- * v2 restores a session with `session/resume` and replays its history only
13
- * when `replayFrom` asks for it. v1 has two methods: `session/resume`, which
14
- * never replays, and `session/load`, which replays the whole history before it
15
- * responds. `replayFrom: { type: "start" }` is the second; a cursor that
16
- * starts elsewhere is rejected.
10
+ * `replayFrom` is rejected until the v2 surface can translate the replayed
11
+ * history, which needs the message and tool call updates.
17
12
  */
18
- export declare function v1RestoreSessionRequest(params: v2.ResumeSessionRequest): {
19
- method: "resume";
20
- request: ResumeSessionRequest;
21
- } | {
22
- method: "load";
23
- request: LoadSessionRequest;
24
- };
13
+ export declare function v1ResumeSessionRequest(params: v2.ResumeSessionRequest): ResumeSessionRequest;
25
14
  /** v2 drops `modes`: the session mode is the `mode` config option, which the agent always lists. */
26
15
  export declare function v2NewSessionResponse(response: NewSessionResponse): v2.NewSessionResponse;
27
16
  export declare function v2ResumeSessionResponse(response: ResumeSessionResponse): v2.ResumeSessionResponse;
@@ -1 +1 @@
1
- {"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../../src/v2/session.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EAEL,KAAK,gBAAgB,EACrB,KAAK,kBAAkB,EAEvB,KAAK,iBAAiB,EACtB,KAAK,kBAAkB,EACvB,KAAK,oBAAoB,EACzB,KAAK,qBAAqB,EAC1B,KAAK,mBAAmB,EAGxB,KAAK,6BAA6B,EACnC,MAAM,0BAA0B,CAAC;AAClC,OAAO,KAAK,EAAE,MAAM,0CAA0C,CAAC;AAE/D,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,EAAE,CAAC,iBAAiB,GAAG,iBAAiB,CAInF;AAED;;;;;;;;GAQG;AACH,wBAAgB,uBAAuB,CACrC,MAAM,EAAE,EAAE,CAAC,oBAAoB,GAE7B;IAAE,MAAM,EAAE,QAAQ,CAAC;IAAC,OAAO,EAAE,oBAAoB,CAAA;CAAE,GACnD;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,kBAAkB,CAAA;CAAE,CAkBlD;AAED,oGAAoG;AACpG,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,kBAAkB,GAAG,EAAE,CAAC,kBAAkB,CAExF;AAED,wBAAgB,uBAAuB,CAAC,QAAQ,EAAE,qBAAqB,GAAG,EAAE,CAAC,qBAAqB,CAKjG;AAED,uEAAuE;AACvE,wBAAgB,+BAA+B,CAC7C,MAAM,EAAE,EAAE,CAAC,6BAA6B,GACvC,6BAA6B,CAgB/B;AAED,sFAAsF;AACtF,wBAAgB,eAAe,CAAC,OAAO,EAAE,mBAAmB,EAAE,GAAG,EAAE,CAAC,mBAAmB,EAAE,CAQxF;AAWD,oEAAoE;AACpE,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,gBAAgB,EAAE,GAAG,EAAE,CAAC,gBAAgB,EAAE,CAKvF"}
1
+ {"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../../src/v2/session.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EAEL,KAAK,gBAAgB,EAErB,KAAK,iBAAiB,EACtB,KAAK,kBAAkB,EACvB,KAAK,oBAAoB,EACzB,KAAK,qBAAqB,EAC1B,KAAK,mBAAmB,EAGxB,KAAK,6BAA6B,EACnC,MAAM,0BAA0B,CAAC;AAClC,OAAO,KAAK,EAAE,MAAM,0CAA0C,CAAC;AAE/D,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,EAAE,CAAC,iBAAiB,GAAG,iBAAiB,CAInF;AAED;;;GAGG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,EAAE,CAAC,oBAAoB,GAAG,oBAAoB,CAS5F;AAED,oGAAoG;AACpG,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,kBAAkB,GAAG,EAAE,CAAC,kBAAkB,CAExF;AAED,wBAAgB,uBAAuB,CAAC,QAAQ,EAAE,qBAAqB,GAAG,EAAE,CAAC,qBAAqB,CAKjG;AAED,uEAAuE;AACvE,wBAAgB,+BAA+B,CAC7C,MAAM,EAAE,EAAE,CAAC,6BAA6B,GACvC,6BAA6B,CAgB/B;AAED,sFAAsF;AACtF,wBAAgB,eAAe,CAAC,OAAO,EAAE,mBAAmB,EAAE,GAAG,EAAE,CAAC,mBAAmB,EAAE,CAQxF;AAWD,oEAAoE;AACpE,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,gBAAgB,EAAE,GAAG,EAAE,CAAC,gBAAgB,EAAE,CAKvF"}
@@ -11,29 +11,15 @@ export function v1NewSessionRequest(params) {
11
11
  return { ...request, mcpServers: (mcpServers ?? []).map(v1McpServer) };
12
12
  }
13
13
  /**
14
- * The v1 request that a v2 `session/resume` means.
15
- *
16
- * v2 restores a session with `session/resume` and replays its history only
17
- * when `replayFrom` asks for it. v1 has two methods: `session/resume`, which
18
- * never replays, and `session/load`, which replays the whole history before it
19
- * responds. `replayFrom: { type: "start" }` is the second; a cursor that
20
- * starts elsewhere is rejected.
14
+ * `replayFrom` is rejected until the v2 surface can translate the replayed
15
+ * history, which needs the message and tool call updates.
21
16
  */
22
- export function v1RestoreSessionRequest(params) {
17
+ export function v1ResumeSessionRequest(params) {
23
18
  const { replayFrom, mcpServers, ...request } = params;
24
- if (replayFrom == null) {
25
- return {
26
- method: "resume",
27
- request: { ...request, ...(mcpServers ? { mcpServers: mcpServers.map(v1McpServer) } : {}) },
28
- };
19
+ if (replayFrom != null) {
20
+ throw RequestError.invalidParams({ replayFrom }, "The ACP v2 surface does not replay session history yet");
29
21
  }
30
- if (replayFrom.type !== "start") {
31
- throw RequestError.invalidParams({ replayFrom }, `Replaying from a ${replayFrom.type} cursor is not supported`);
32
- }
33
- return {
34
- method: "load",
35
- request: { ...request, mcpServers: (mcpServers ?? []).map(v1McpServer) },
36
- };
22
+ return { ...request, ...(mcpServers ? { mcpServers: mcpServers.map(v1McpServer) } : {}) };
37
23
  }
38
24
  /** v2 drops `modes`: the session mode is the `mode` config option, which the agent always lists. */
39
25
  export function v2NewSessionResponse(response) {
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "0.85.2-preview.19",
6
+ "version": "0.85.2-preview.3",
7
7
  "description": "An ACP-compatible coding agent powered by the Claude Agent SDK (TypeScript)",
8
8
  "main": "dist/lib.js",
9
9
  "types": "dist/lib.d.ts",
@@ -66,7 +66,7 @@
66
66
  },
67
67
  "dependencies": {
68
68
  "@agentclientprotocol/sdk": "1.7.0",
69
- "@anthropic-ai/claude-agent-sdk": "0.3.287",
69
+ "@anthropic-ai/claude-agent-sdk": "0.3.286",
70
70
  "diff": "9.0.0",
71
71
  "zod": "4.6.5"
72
72
  },
@@ -1,51 +0,0 @@
1
- import type { AvailableCommand } from "@agentclientprotocol/sdk";
2
- import type { McpServerStatus, Query } from "@anthropic-ai/claude-agent-sdk";
3
- /** The `/mcp` entry of `available_commands_update`. The adapter shows the
4
- * servers as a list, and runs a reconnect, an enable, or a disable through
5
- * the SDK control API. */
6
- export declare const MCP_AVAILABLE_COMMAND: AvailableCommand;
7
- /** A `/mcp` prompt that gets a replacement for its Claude Code text. `all` is
8
- * true for `all` and for a reconnect without a server name, as in Claude
9
- * Code. `server` is undefined for an enable or a disable without a name. */
10
- export type McpCommand = {
11
- action: "status";
12
- } | {
13
- action: "reconnect" | "enable" | "disable";
14
- all: boolean;
15
- server?: string;
16
- };
17
- /** Parse a prompt that is exactly `/mcp`, or `/mcp reconnect`, `/mcp enable`,
18
- * or `/mcp disable` with an optional server name or `all`. Any other `/mcp`
19
- * argument gives null, and the prompt gets no replacement. */
20
- export declare function parseMcpCommand(text: string): McpCommand | null;
21
- /** Read the MCP server status for the list after a `/mcp` command. Resolves
22
- * to null when the read fails, takes too long, or `signal` aborts. Then the
23
- * turn keeps the text of Claude Code. */
24
- export declare function readMcpServerStatus(query: Pick<Query, "mcpServerStatus">, signal: AbortSignal, logError: (message: string) => void): Promise<McpServerStatus[] | null>;
25
- /** Make an MCP error short and readable for one list item. The result has
26
- * no leading wrapper segments, at most two sentences, and at most
27
- * `MAX_ERROR_LENGTH` code points. A wrapper before an error code stays, so
28
- * an error code is never alone. The result is empty for an error with only
29
- * whitespace. */
30
- export declare function cleanMcpError(error: string): string;
31
- /** Markdown for `/mcp`: a summary line, one group of servers for each
32
- * status, and the disabled servers on one line. A list reads better than a
33
- * table in a narrow chat. */
34
- export declare function formatMcpStatus(statuses: McpServerStatus[]): string;
35
- /** The SDK query methods that `/mcp` uses. */
36
- export type McpControlQuery = Pick<Query, "mcpServerStatus" | "reconnectMcpServer" | "toggleMcpServer">;
37
- /** The result of {@link runMcpCommand}. `markdown` is null when the turn
38
- * keeps the text of Claude Code. `reconnected` holds the servers that the
39
- * adapter tried to reconnect. */
40
- export type McpCommandOutcome = {
41
- markdown: string | null;
42
- reconnected: string[];
43
- };
44
- /** Run a `/mcp` command after Claude Code sent `originalOutput`, its text for
45
- * the command. Claude Code refuses a reconnect, an enable, and a disable in
46
- * SDK mode, so the adapter runs them through the SDK control API. The result
47
- * of each server goes above the server list. An error of one server does
48
- * not stop the others. When the first status read fails or `signal`
49
- * aborts, the turn keeps the text of Claude Code. */
50
- export declare function runMcpCommand(query: McpControlQuery, command: McpCommand, originalOutput: string, signal: AbortSignal, logError: (message: string) => void): Promise<McpCommandOutcome>;
51
- //# sourceMappingURL=mcp-command.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"mcp-command.d.ts","sourceRoot":"","sources":["../src/mcp-command.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AACjE,OAAO,KAAK,EAAE,eAAe,EAAE,KAAK,EAAE,MAAM,gCAAgC,CAAC;AAI7E;;2BAE2B;AAC3B,eAAO,MAAM,qBAAqB,EAAE,gBAInC,CAAC;AAQF;;6EAE6E;AAC7E,MAAM,MAAM,UAAU,GAClB;IAAE,MAAM,EAAE,QAAQ,CAAA;CAAE,GACpB;IAAE,MAAM,EAAE,WAAW,GAAG,QAAQ,GAAG,SAAS,CAAC;IAAC,GAAG,EAAE,OAAO,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAElF;;+DAE+D;AAC/D,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,UAAU,GAAG,IAAI,CAW/D;AAED;;0CAE0C;AAC1C,wBAAsB,mBAAmB,CACvC,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,iBAAiB,CAAC,EACrC,MAAM,EAAE,WAAW,EACnB,QAAQ,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,GAClC,OAAO,CAAC,eAAe,EAAE,GAAG,IAAI,CAAC,CAkBnC;AAuED;;;;kBAIkB;AAClB,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAiBnD;AA2DD;;8BAE8B;AAC9B,wBAAgB,eAAe,CAAC,QAAQ,EAAE,eAAe,EAAE,GAAG,MAAM,CA6BnE;AAED,8CAA8C;AAC9C,MAAM,MAAM,eAAe,GAAG,IAAI,CAChC,KAAK,EACL,iBAAiB,GAAG,oBAAoB,GAAG,iBAAiB,CAC7D,CAAC;AAEF;;kCAEkC;AAClC,MAAM,MAAM,iBAAiB,GAAG;IAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,WAAW,EAAE,MAAM,EAAE,CAAA;CAAE,CAAC;AA4DnF;;;;;sDAKsD;AACtD,wBAAsB,aAAa,CACjC,KAAK,EAAE,eAAe,EACtB,OAAO,EAAE,UAAU,EACnB,cAAc,EAAE,MAAM,EACtB,MAAM,EAAE,WAAW,EACnB,QAAQ,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,GAClC,OAAO,CAAC,iBAAiB,CAAC,CAoD5B"}
@@ -1,321 +0,0 @@
1
- import { escapeMarkdown } from "./usage-markdown.js";
2
- import { raceTimeoutAndAbort } from "./utils.js";
3
- /** The `/mcp` entry of `available_commands_update`. The adapter shows the
4
- * servers as a list, and runs a reconnect, an enable, or a disable through
5
- * the SDK control API. */
6
- export const MCP_AVAILABLE_COMMAND = {
7
- name: "mcp",
8
- description: "Show the MCP servers and their status, or reconnect, enable, or disable a server",
9
- input: { hint: "[reconnect|enable|disable [<server>|all]]" },
10
- };
11
- /** The time that the status read after a `/mcp` command can take. */
12
- const MCP_STATUS_TIMEOUT_MS = 5_000;
13
- /** The time that one reconnect, enable, or disable can take. */
14
- const MCP_ACTION_TIMEOUT_MS = 30_000;
15
- /** Parse a prompt that is exactly `/mcp`, or `/mcp reconnect`, `/mcp enable`,
16
- * or `/mcp disable` with an optional server name or `all`. Any other `/mcp`
17
- * argument gives null, and the prompt gets no replacement. */
18
- export function parseMcpCommand(text) {
19
- const words = text.trim().split(/\s+/);
20
- if (words[0] !== "/mcp")
21
- return null;
22
- if (words.length === 1)
23
- return { action: "status" };
24
- const action = words[1]?.toLowerCase();
25
- if (action !== "reconnect" && action !== "enable" && action !== "disable")
26
- return null;
27
- const server = words.slice(2).join(" ");
28
- if (server === "all" || (server === "" && action === "reconnect")) {
29
- return { action, all: true };
30
- }
31
- return server === "" ? { action, all: false } : { action, all: false, server };
32
- }
33
- /** Read the MCP server status for the list after a `/mcp` command. Resolves
34
- * to null when the read fails, takes too long, or `signal` aborts. Then the
35
- * turn keeps the text of Claude Code. */
36
- export async function readMcpServerStatus(query, signal, logError) {
37
- if (signal.aborted)
38
- return null;
39
- try {
40
- const outcome = await raceTimeoutAndAbort(query.mcpServerStatus(), MCP_STATUS_TIMEOUT_MS, signal);
41
- if (outcome.type === "timeout") {
42
- logError("The MCP server status read timed out; keeping the Claude Code text of /mcp");
43
- }
44
- return outcome.type === "done" ? outcome.value : null;
45
- }
46
- catch (error) {
47
- if (!signal.aborted) {
48
- logError(`The MCP server status read failed; keeping the Claude Code text of /mcp: ${error}`);
49
- }
50
- return null;
51
- }
52
- }
53
- /** The known statuses in the order of the groups and of the summary counts.
54
- * A disabled server is not a group. It is on one line at the end. */
55
- const GROUP_ORDER = ["failed", "needs-auth", "pending", "connected"];
56
- const GROUP_TITLES = {
57
- failed: "Failed",
58
- "needs-auth": "Needs authentication",
59
- pending: "Connecting",
60
- connected: "Connected",
61
- };
62
- const SUMMARY_LABELS = {
63
- connected: "connected",
64
- failed: "failed",
65
- "needs-auth": "need authentication",
66
- pending: "connecting",
67
- disabled: "disabled",
68
- };
69
- const KNOWN_STATUSES = [...GROUP_ORDER, "disabled"];
70
- const MAX_ERROR_LENGTH = 160;
71
- /** The longest segment that can be a wrapper. A longer segment is content. */
72
- const MAX_WRAPPER_LENGTH = 60;
73
- /** True for a server that a `/mcp reconnect` without a name must retry. */
74
- function needsReconnect(status) {
75
- return (status.status === "failed" || status.status === "pending" || status.status === "needs-auth");
76
- }
77
- /** The text as a Markdown code span. The fence is longer than the longest
78
- * backtick run in the text, so the span shows the text unchanged. The user
79
- * can copy a server name from it into `/mcp reconnect`. */
80
- function codeSpan(text) {
81
- const longestRun = Math.max(0, ...(text.match(/`+/g) ?? []).map((run) => run.length));
82
- const fence = "`".repeat(longestRun + 1);
83
- // CommonMark strips one space from each end of a span, so a pad keeps the text unchanged.
84
- const pad = text.startsWith("`") || text.endsWith("`") || /^ .*[^ ].* $/.test(text) ? " " : "";
85
- return `${fence}${pad}${text}${pad}${fence}`;
86
- }
87
- function isErrorCode(segment) {
88
- return /^-?\d+$/.test(segment);
89
- }
90
- /** True for a segment that only wraps the real error, such as
91
- * `MCP startup failed`, `McpError`, `MCP error -32001`, an error code, or a
92
- * Rust or TypeScript type path. */
93
- function isWrapperSegment(segment) {
94
- return (isErrorCode(segment) ||
95
- segment.includes("::") ||
96
- (segment.length <= MAX_WRAPPER_LENGTH &&
97
- !/[.!?]/.test(segment) &&
98
- /(failed|error|exception)(\s+-?\d+)?$/i.test(segment)));
99
- }
100
- /** Cut the text to `MAX_ERROR_LENGTH` code points, so a surrogate pair stays whole. */
101
- function truncate(text) {
102
- const chars = Array.from(text);
103
- if (chars.length <= MAX_ERROR_LENGTH)
104
- return text;
105
- const cut = chars.slice(0, MAX_ERROR_LENGTH - 1).join("");
106
- return `${cut.trimEnd()}…`;
107
- }
108
- /** Make an MCP error short and readable for one list item. The result has
109
- * no leading wrapper segments, at most two sentences, and at most
110
- * `MAX_ERROR_LENGTH` code points. A wrapper before an error code stays, so
111
- * an error code is never alone. The result is empty for an error with only
112
- * whitespace. */
113
- export function cleanMcpError(error) {
114
- const segments = error.replace(/\s+/g, " ").trim().split(": ");
115
- let start = 0;
116
- while (start < segments.length - 1 &&
117
- isWrapperSegment(segments[start]) &&
118
- !segments.slice(start + 1).every(isErrorCode)) {
119
- start++;
120
- }
121
- const content = segments.slice(start).join(": ");
122
- return truncate(content
123
- .split(/(?<=[.!?]) /)
124
- .slice(0, 2)
125
- .join(" "));
126
- }
127
- function toolCount(status) {
128
- if (!status.tools)
129
- return undefined;
130
- return status.tools.length === 1 ? "1 tool" : `${status.tools.length} tools`;
131
- }
132
- /** The server name as inline code. Claude Code keys the servers by name, so
133
- * two servers have the same name only from different scopes. Then the
134
- * scope tells them apart. */
135
- function serverName(status, duplicates) {
136
- const origin = status.scope ?? status.source;
137
- return duplicates.has(status.name) && origin
138
- ? `${codeSpan(status.name)} (${escapeMarkdown(origin)})`
139
- : codeSpan(status.name);
140
- }
141
- function serverItem(status, duplicates) {
142
- const error = status.error ? cleanMcpError(status.error) : "";
143
- const details = [error === "" ? undefined : escapeMarkdown(error), toolCount(status)]
144
- .filter((detail) => detail !== undefined)
145
- .join("; ");
146
- const name = serverName(status, duplicates);
147
- return details === "" ? `- ${name}` : `- ${name}: ${details}`;
148
- }
149
- function summaryLine(statuses) {
150
- // The counts follow the group order. An unknown status also counts, so the counts add up to the total.
151
- const counts = [...GROUP_ORDER, ...unknownStatuses(statuses), "disabled"]
152
- .map((state) => ({
153
- state,
154
- count: statuses.filter((status) => status.status === state).length,
155
- }))
156
- .filter(({ count }) => count > 0);
157
- const parts = counts.map(({ state, count }) => count === 1 && state === "needs-auth"
158
- ? "1 needs authentication"
159
- : `${count} ${SUMMARY_LABELS[state] ?? escapeMarkdown(state)}`);
160
- return `**MCP servers:** ${statuses.length} (${parts.join(", ")})`;
161
- }
162
- /** The statuses that this adapter does not know, in the SDK order. The SDK
163
- * can report a new status before the adapter knows it. */
164
- function unknownStatuses(statuses) {
165
- return [
166
- ...new Set(statuses
167
- .map((status) => status.status)
168
- .filter((state) => !KNOWN_STATUSES.includes(state))),
169
- ];
170
- }
171
- /** The group title of a status that the adapter does not know. It starts with a capital letter, as a known title does. */
172
- function unknownTitle(state) {
173
- return escapeMarkdown(state.charAt(0).toUpperCase() + state.slice(1));
174
- }
175
- /** Markdown for `/mcp`: a summary line, one group of servers for each
176
- * status, and the disabled servers on one line. A list reads better than a
177
- * table in a narrow chat. */
178
- export function formatMcpStatus(statuses) {
179
- if (statuses.length === 0)
180
- return "No MCP servers are configured.";
181
- const blocks = [];
182
- const names = statuses.map((status) => status.name);
183
- const duplicates = new Set(names.filter((name, index) => names.indexOf(name) !== index));
184
- blocks.push(summaryLine(statuses));
185
- const groups = [
186
- ...GROUP_ORDER.map((state) => [state, GROUP_TITLES[state]]),
187
- ...unknownStatuses(statuses).map((state) => [state, unknownTitle(state)]),
188
- ];
189
- for (const [state, title] of groups) {
190
- const members = statuses.filter((status) => status.status === state);
191
- if (members.length === 0)
192
- continue;
193
- blocks.push([`**${title}**`, ...members.map((status) => serverItem(status, duplicates))].join("\n"));
194
- }
195
- const disabled = statuses.filter((status) => status.status === "disabled");
196
- if (disabled.length > 0) {
197
- blocks.push(`**Disabled:** ${disabled.map((status) => serverName(status, duplicates)).join(", ")}`);
198
- }
199
- if (statuses.some(needsReconnect)) {
200
- blocks.push("Run `/mcp reconnect <server>` to reconnect one server, or `/mcp reconnect` to reconnect every server that is not connected and not disabled.");
201
- }
202
- return blocks.join("\n\n");
203
- }
204
- /** Wait for one control call. The wait stops when `signal` aborts or the
205
- * call takes more than {@link MCP_ACTION_TIMEOUT_MS}. */
206
- async function awaitCall(call, signal) {
207
- if (signal.aborted)
208
- return { type: "aborted" };
209
- try {
210
- const outcome = await raceTimeoutAndAbort(call(), MCP_ACTION_TIMEOUT_MS, signal);
211
- if (outcome.type === "timeout")
212
- return { type: "failed", error: "The request timed out." };
213
- return outcome.type === "done" ? { type: "done" } : outcome;
214
- }
215
- catch (error) {
216
- return signal.aborted
217
- ? { type: "aborted" }
218
- : { type: "failed", error: error instanceof Error ? error.message : String(error) };
219
- }
220
- }
221
- const ACTION_DONE = { reconnect: "Reconnected", enable: "Enabled", disable: "Disabled" };
222
- /** The Claude Code sentence that refuses an action in SDK mode. */
223
- const UNAVAILABLE_SENTENCE = /aren't available in this session/i;
224
- /** A Claude Code sentence that points to a terminal, which an ACP client
225
- * does not have. */
226
- function isTerminalSentence(sentence) {
227
- return /in the terminal/i.test(sentence) || sentence.includes("`/mcp reconnect all` here");
228
- }
229
- function sentences(text) {
230
- return text
231
- .trim()
232
- .split(/(?<=[.!?])\s+/)
233
- .filter((sentence) => sentence !== "");
234
- }
235
- /** The servers that the action changes. A named server is always a target,
236
- * so the control API reports its own error. */
237
- function actionTargets(command, statuses) {
238
- if (!command.all)
239
- return command.server === undefined ? [] : [command.server];
240
- const names = statuses
241
- .filter((status) => {
242
- if (command.action === "reconnect")
243
- return needsReconnect(status);
244
- if (command.action === "enable")
245
- return status.status === "disabled";
246
- return status.status !== "disabled";
247
- })
248
- .map((status) => status.name);
249
- return [...new Set(names)];
250
- }
251
- function nothingToDoLine(action) {
252
- if (action === "enable")
253
- return "Every MCP server is already enabled.";
254
- if (action === "disable")
255
- return "Every MCP server is already disabled.";
256
- return "Every MCP server is connected or disabled. There is nothing to reconnect.";
257
- }
258
- /** Run a `/mcp` command after Claude Code sent `originalOutput`, its text for
259
- * the command. Claude Code refuses a reconnect, an enable, and a disable in
260
- * SDK mode, so the adapter runs them through the SDK control API. The result
261
- * of each server goes above the server list. An error of one server does
262
- * not stop the others. When the first status read fails or `signal`
263
- * aborts, the turn keeps the text of Claude Code. */
264
- export async function runMcpCommand(query, command, originalOutput, signal, logError) {
265
- const reconnected = [];
266
- const keep = { markdown: null, reconnected };
267
- if (command.action === "status") {
268
- // The list replaces the Claude Code summary line.
269
- const statuses = await readMcpServerStatus(query, signal, logError);
270
- return { markdown: statuses ? formatMcpStatus(statuses) : null, reconnected };
271
- }
272
- const before = await readMcpServerStatus(query, signal, logError);
273
- if (!before)
274
- return keep;
275
- const cliSentences = sentences(originalOutput).filter((sentence) => !isTerminalSentence(sentence));
276
- const cliNamesUnknown = cliSentences.some((sentence) => /no MCP server named/i.test(sentence));
277
- const results = [];
278
- if (!command.all && command.server === undefined) {
279
- results.push(`Name a server or \`all\`, for example \`/mcp ${command.action} <server>\`.`);
280
- }
281
- const known = new Set(before.map((status) => status.name));
282
- const targets = actionTargets(command, before);
283
- if (command.all && targets.length === 0)
284
- results.push(nothingToDoLine(command.action));
285
- for (const server of targets) {
286
- if (!known.has(server)) {
287
- // Claude Code checks the name first. Its sentence then stays.
288
- if (!cliNamesUnknown)
289
- results.push(`There's no MCP server named ${codeSpan(server)}.`);
290
- continue;
291
- }
292
- if (command.action === "reconnect")
293
- reconnected.push(server);
294
- const call = command.action === "reconnect"
295
- ? () => query.reconnectMcpServer(server)
296
- : () => query.toggleMcpServer(server, command.action === "enable");
297
- const result = await awaitCall(call, signal);
298
- if (result.type === "aborted")
299
- return keep;
300
- if (result.type === "done") {
301
- results.push(`- ${ACTION_DONE[command.action]} ${codeSpan(server)}`);
302
- }
303
- else {
304
- const error = cleanMcpError(result.error);
305
- const failure = `- Couldn't ${command.action} ${codeSpan(server)}`;
306
- results.push(error === "" ? failure : `${failure}: ${escapeMarkdown(error)}`);
307
- }
308
- }
309
- const blocks = [];
310
- const kept = cliSentences.filter((sentence) => !UNAVAILABLE_SENTENCE.test(sentence));
311
- if (kept.length > 0)
312
- blocks.push(kept.join(" "));
313
- if (results.length > 0)
314
- blocks.push(results.join("\n"));
315
- const after = await readMcpServerStatus(query, signal, logError);
316
- if (signal.aborted)
317
- return keep;
318
- if (after)
319
- blocks.push(formatMcpStatus(after));
320
- return { markdown: blocks.join("\n\n"), reconnected };
321
- }
@@ -1,25 +0,0 @@
1
- /**
2
- * The v2 form of the permission requests that `ClaudeAcpAgent` sends, and the
3
- * v1 form of their answers.
4
- */
5
- import type { RequestPermissionResponse } from "@agentclientprotocol/sdk";
6
- import type * as v2 from "@agentclientprotocol/sdk/experimental/v2";
7
- import type { AcpPermissionRequest } from "../permissions/presentation.js";
8
- /**
9
- * The v2 form of a permission request of the agent.
10
- *
11
- * v1 asks about a tool call, whose title the prompt shows. v2 gives the prompt
12
- * its own `title` and `description`, which the agent sets for a v2 client
13
- * (`AcpPermissionRequest`), and asks about a `subject`: here always the tool
14
- * call, as the same patch that a `tool_call_update` carries.
15
- */
16
- export declare function v2PermissionRequest(request: AcpPermissionRequest): v2.RequestPermissionRequest;
17
- /**
18
- * The v1 form of the answer to a permission request.
19
- *
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.
23
- */
24
- export declare function v1PermissionResponse(response: v2.RequestPermissionResponse): RequestPermissionResponse;
25
- //# sourceMappingURL=permission.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"permission.d.ts","sourceRoot":"","sources":["../../src/v2/permission.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,0BAA0B,CAAC;AAC1E,OAAO,KAAK,KAAK,EAAE,MAAM,0CAA0C,CAAC;AACpE,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,38 +0,0 @@
1
- import { v2ToolCallPatch } from "./tool-call.js";
2
- /**
3
- * The v2 form of a permission request of the agent.
4
- *
5
- * v1 asks about a tool call, whose title the prompt shows. v2 gives the prompt
6
- * its own `title` and `description`, which the agent sets for a v2 client
7
- * (`AcpPermissionRequest`), and asks about a `subject`: here always the tool
8
- * call, as the same patch that a `tool_call_update` carries.
9
- */
10
- export function v2PermissionRequest(request) {
11
- if (request.title === undefined) {
12
- throw new Error("An ACP v2 permission request needs a title");
13
- }
14
- return {
15
- sessionId: request.sessionId,
16
- title: request.title,
17
- ...(request.description ? { description: request.description } : {}),
18
- subject: { type: "tool_call", toolCall: v2ToolCallPatch(request.toolCall) },
19
- options: request.options,
20
- ...(request._meta != null ? { _meta: request._meta } : {}),
21
- };
22
- }
23
- /**
24
- * The v1 form of the answer to a permission request.
25
- *
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.
29
- */
30
- export function v1PermissionResponse(response) {
31
- const outcome = response.outcome;
32
- return {
33
- outcome: outcome.outcome === "selected"
34
- ? outcome
35
- : { outcome: "cancelled" },
36
- ...(response._meta != null ? { _meta: response._meta } : {}),
37
- };
38
- }