@armadra/agent 0.6.7 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. package/CHANGELOG.md +70 -0
  2. package/CHANGELOG.zh-CN.md +51 -0
  3. package/README.md +4 -3
  4. package/README.zh-CN.md +1 -1
  5. package/dist/acp.d.ts +2 -2
  6. package/dist/acp.js +2 -2
  7. package/dist/ai/apis/shared.d.ts +7 -1
  8. package/dist/ai/apis/shared.js +10 -0
  9. package/dist/ai/fake/fake-provider.js +5 -1
  10. package/dist/ai/fake/fake-script.d.ts +5 -2
  11. package/dist/ai/fake/fake-script.js +6 -2
  12. package/dist/ai/types.d.ts +2 -1
  13. package/dist/bundle/ama.cjs +46181 -44829
  14. package/dist/cli/args.d.ts +6 -0
  15. package/dist/cli/args.js +46 -0
  16. package/dist/cli/bootstrap.js +13 -2
  17. package/dist/cli/compose-events.d.ts +10 -0
  18. package/dist/cli/compose-events.js +97 -0
  19. package/dist/cli/compose-session.d.ts +25 -4
  20. package/dist/cli/compose-session.js +130 -103
  21. package/dist/cli/compose.js +6 -1
  22. package/dist/cli/runtime.d.ts +3 -1
  23. package/dist/cli/subcommands/auth.d.ts +13 -1
  24. package/dist/cli/subcommands/auth.js +28 -1
  25. package/dist/drivers/acp/client.d.ts +28 -4
  26. package/dist/drivers/acp/client.js +78 -5
  27. package/dist/drivers/acp/driver.d.ts +15 -4
  28. package/dist/drivers/acp/driver.js +110 -33
  29. package/dist/drivers/acp/testing/fake-agent-main.d.ts +1 -1
  30. package/dist/drivers/acp/testing/fake-agent-main.js +10 -2
  31. package/dist/drivers/acp/testing/fake-agent.d.ts +24 -0
  32. package/dist/drivers/acp/testing/fake-agent.js +175 -6
  33. package/dist/drivers/acp/types.d.ts +156 -9
  34. package/dist/drivers/acp/types.js +13 -3
  35. package/dist/drivers/jsonrpc.d.ts +13 -3
  36. package/dist/drivers/jsonrpc.js +40 -7
  37. package/dist/drivers/turn.d.ts +15 -2
  38. package/dist/drivers/turn.js +33 -4
  39. package/dist/i18n/catalog.d.ts +59 -16
  40. package/dist/i18n/catalog.js +4 -1
  41. package/dist/i18n/messages/acp.d.ts +125 -0
  42. package/dist/i18n/messages/acp.js +126 -0
  43. package/dist/i18n/messages/auth.d.ts +2 -0
  44. package/dist/i18n/messages/auth.js +4 -2
  45. package/dist/i18n/messages/cli-args.d.ts +2 -0
  46. package/dist/i18n/messages/cli-args.js +2 -0
  47. package/dist/i18n/messages/cli.d.ts +4 -0
  48. package/dist/i18n/messages/cli.js +2 -0
  49. package/dist/i18n/messages/print.d.ts +3 -33
  50. package/dist/i18n/messages/print.js +3 -33
  51. package/dist/modes/acp/acp-auth-gate.d.ts +40 -0
  52. package/dist/modes/acp/acp-auth-gate.js +201 -0
  53. package/dist/modes/acp/acp-config.d.ts +43 -0
  54. package/dist/modes/acp/acp-config.js +151 -0
  55. package/dist/modes/acp/acp-connection.d.ts +29 -0
  56. package/dist/modes/acp/acp-connection.js +37 -0
  57. package/dist/modes/acp/acp-events.d.ts +59 -23
  58. package/dist/modes/acp/acp-events.js +154 -88
  59. package/dist/modes/acp/acp-mode.d.ts +13 -2
  60. package/dist/modes/acp/acp-mode.js +23 -8
  61. package/dist/modes/acp/acp-server.d.ts +59 -32
  62. package/dist/modes/acp/acp-server.js +322 -128
  63. package/dist/modes/acp/acp-sessions.d.ts +80 -0
  64. package/dist/modes/acp/acp-sessions.js +157 -0
  65. package/dist/modes/acp/acp-tool-text.d.ts +23 -0
  66. package/dist/modes/acp/acp-tool-text.js +83 -0
  67. package/dist/modes/print/json-event.d.ts +2 -1
  68. package/dist/modes/print/json-event.js +6 -1
  69. package/dist/tools/edit.d.ts +6 -2
  70. package/dist/tools/edit.js +19 -2
  71. package/dist/tools/types.d.ts +11 -0
  72. package/dist/tools/types.js +2 -0
  73. package/dist/tools/write.d.ts +1 -0
  74. package/dist/tools/write.js +21 -3
  75. package/docs/acp.md +151 -30
  76. package/docs/agents.md +2 -0
  77. package/docs/codemode.md +1 -1
  78. package/docs/en/acp.md +192 -0
  79. package/package.json +1 -1
@@ -13,16 +13,54 @@
13
13
  * | `[plan]` | 先发 `plan`(两条)再回文本 |
14
14
  * | `[think]` | 先发 `agent_thought_chunk` |
15
15
  * | `[refuse]` | `refusal` |
16
+ * | `[elicit]` | `elicitation/create`(选颜色的表单)→ 回 `elicit: <action> <content JSON>`;客户端 |
17
+ * | | 没声明 `elicitation` 能力时回 `elicit: unsupported`;`cancel` 时本回合 `cancelled` |
18
+ * | `[model]` | 回 `model <当前模型>`(`configOptions` 关着时是 `model none`) |
19
+ * | `[env NAME]` | 回 `env NAME <值的 sha256 | absent>`(测环境变量到没到,不回显值) |
20
+ * | `[cancel-request]` | 同 `[permission]` 发权限请求,挂起 `cancelRequestMs`(缺省 2 s)后自己发 |
21
+ * | | `$/cancel_request` 撤回;`tool_call_update` failed,回 `permission withdrawn`、`end_turn` |
22
+ *
23
+ * `configOptions: true`(可执行入口 `--config-options`):开会话答一个 `model` 配置项(分组 `fast` 里有
24
+ * `small`、`big` 里有 `large`,当前 `small`),`session/set_config_option` 可改;缺省不答,线路与之前相同。
25
+ * `configOnly: true`(`--config-only`):开会话不答 `modes`,改在 `configOptions` 里给 category `mode` 的
26
+ * 选择项(id `mode`),经 `session/set_config_option` 切换;可与 `configOptions` 同开(两项都给)。
27
+ * `authRequired: true`(`--auth-required`):`initialize` 给一条 terminal 型认证方法,开会话
28
+ * (new / load / resume)一律回 -32000。
16
29
  *
17
30
  * 会话:`session/new` 发 `fake-<n>`;`session/resume` / `session/load` 接受任何 `fake-` 开头的 id
18
31
  * (load 先回放一条用户消息与一条回复);`session/list` 列本进程建过的会话。
19
32
  */
33
+ import { createHash } from "node:crypto";
20
34
  import { ACP_METHODS, ACP_PROTOCOL_VERSION, RPC_ERRORS } from "../types.js";
21
35
  import { JsonRpcPeer, RpcError } from "../../jsonrpc.js";
36
+ /** `authRequired` 时 `initialize` 给的认证方法。 */
37
+ export const FAKE_AUTH_METHOD = {
38
+ type: "terminal",
39
+ id: "login",
40
+ name: "Log in",
41
+ description: "Run the fake agent's login command",
42
+ args: ["--login"],
43
+ };
22
44
  const MODES = [
23
45
  { id: "default", name: "Default" },
24
46
  { id: "plan", name: "Plan" },
25
47
  ];
48
+ /** 规范要求选项全部平铺或全部分组(不能混排),所以 `small` 也放进一组。 */
49
+ const MODELS = [
50
+ { group: "fast", name: "Fast", options: [{ value: "small", name: "Small" }] },
51
+ { group: "big", name: "Big", options: [{ value: "large", name: "Large", description: "slow" }] },
52
+ ];
53
+ const MODEL_VALUES = ["small", "large"];
54
+ const MODE_VALUES = MODES.map((m) => m.id);
55
+ /** 一个表单:必填的颜色(枚举)与可选的数量。 */
56
+ const ELICIT_SCHEMA = {
57
+ type: "object",
58
+ properties: {
59
+ color: { type: "string", enum: ["red", "blue"] },
60
+ count: { type: "integer", minimum: 1 },
61
+ },
62
+ required: ["color"],
63
+ };
26
64
  function textOf(blocks) {
27
65
  return blocks.map((b) => (b.type === "text" ? b.text : `[${b.type}]`)).join("");
28
66
  }
@@ -37,6 +75,43 @@ function waitAbort(signal) {
37
75
  export function runFakeAcpAgent(input, output, options = {}) {
38
76
  const sessions = new Map();
39
77
  let counter = 0;
78
+ /** 客户端在 initialize 里声明了 elicitation。 */
79
+ let canElicit = false;
80
+ const configOf = (s) => [
81
+ ...(options.configOnly === true
82
+ ? [
83
+ {
84
+ id: "mode",
85
+ name: "Mode",
86
+ category: "mode",
87
+ type: "select",
88
+ currentValue: s.mode,
89
+ options: MODES.map((m) => ({ value: m.id, name: m.name })),
90
+ },
91
+ ]
92
+ : []),
93
+ ...(options.configOptions === true
94
+ ? [
95
+ {
96
+ id: "model",
97
+ name: "Model",
98
+ category: "model",
99
+ type: "select",
100
+ currentValue: s.model,
101
+ options: MODELS,
102
+ },
103
+ ]
104
+ : []),
105
+ ];
106
+ const hasConfig = options.configOptions === true || options.configOnly === true;
107
+ /** 开会话答复里的 `configOptions`(关着时不加这个键)。 */
108
+ const config = (s) => hasConfig ? { configOptions: configOf(s) } : {};
109
+ /** 开会话答复里的 `modes`(`configOnly` 时不给)。 */
110
+ const modes = (s) => options.configOnly === true ? {} : { modes: { currentModeId: s.mode, availableModes: MODES } };
111
+ const requireAuth = () => {
112
+ if (options.authRequired === true)
113
+ throw new RpcError(RPC_ERRORS.authRequired, "authentication required");
114
+ };
40
115
  const update = (sessionId, value) => peer.notify(ACP_METHODS.sessionUpdate, { sessionId, update: value });
41
116
  const session = (id) => {
42
117
  const found = typeof id === "string" ? sessions.get(id) : undefined;
@@ -45,7 +120,7 @@ export function runFakeAcpAgent(input, output, options = {}) {
45
120
  return found;
46
121
  };
47
122
  const open = (id, cwd) => {
48
- const created = { id, cwd, mode: "default", turns: 0 };
123
+ const created = { id, cwd, mode: "default", model: "small", turns: 0 };
49
124
  sessions.set(id, created);
50
125
  return created;
51
126
  };
@@ -75,10 +150,77 @@ export function runFakeAcpAgent(input, output, options = {}) {
75
150
  });
76
151
  if (text.includes("[refuse]"))
77
152
  return done("refusal");
153
+ const say = (line) => update(s.id, { sessionUpdate: "agent_message_chunk", content: { type: "text", text: line } });
154
+ if (text.includes("[elicit]")) {
155
+ if (!canElicit) {
156
+ await say("elicit: unsupported");
157
+ return done("end_turn");
158
+ }
159
+ let answer;
160
+ try {
161
+ answer = await peer.request(ACP_METHODS.elicitationCreate, {
162
+ sessionId: s.id,
163
+ message: "Pick a color",
164
+ requestedSchema: ELICIT_SCHEMA,
165
+ });
166
+ }
167
+ catch {
168
+ return done("cancelled");
169
+ }
170
+ await say(`elicit: ${answer.action} ${JSON.stringify(answer.content ?? null)}`);
171
+ return done(answer.action === "cancel" ? "cancelled" : "end_turn");
172
+ }
173
+ if (text.includes("[model]")) {
174
+ await say(`model ${options.configOptions === true ? s.model : "none"}`);
175
+ return done("end_turn");
176
+ }
177
+ const env = /\[env ([A-Z0-9_]+)\]/.exec(text);
178
+ if (env !== null) {
179
+ const value = process.env[env[1]];
180
+ const digest = value === undefined ? "absent" : createHash("sha256").update(value).digest("hex");
181
+ await say(`env ${env[1]} ${digest}`);
182
+ return done("end_turn");
183
+ }
78
184
  if (text.includes("[slow]")) {
79
185
  await waitAbort(turn.signal);
80
186
  return done("cancelled");
81
187
  }
188
+ if (text.includes("[cancel-request]")) {
189
+ const toolCallId = `call-${s.turns}`;
190
+ await update(s.id, {
191
+ sessionUpdate: "tool_call",
192
+ toolCallId,
193
+ title: "Write note.txt",
194
+ kind: "edit",
195
+ status: "pending",
196
+ });
197
+ const withdraw = new AbortController();
198
+ const timer = setTimeout(() => withdraw.abort(), options.cancelRequestMs ?? 2000);
199
+ const asked = peer
200
+ .request(ACP_METHODS.requestPermission, {
201
+ sessionId: s.id,
202
+ toolCall: { toolCallId, title: "Write note.txt", kind: "edit", status: "pending" },
203
+ options: [
204
+ { optionId: "allow", name: "Allow", kind: "allow_once" },
205
+ { optionId: "reject", name: "Reject", kind: "reject_once" },
206
+ ],
207
+ }, withdraw.signal)
208
+ .then((answer) => answer, () => undefined);
209
+ const answer = await Promise.race([asked, waitAbort(turn.signal).then(() => undefined)]);
210
+ clearTimeout(timer);
211
+ if (turn.signal.aborted) {
212
+ withdraw.abort();
213
+ return done("cancelled");
214
+ }
215
+ if (answer === undefined) {
216
+ await update(s.id, { sessionUpdate: "tool_call_update", toolCallId, status: "failed" });
217
+ await say("permission withdrawn");
218
+ return done("end_turn");
219
+ }
220
+ await update(s.id, { sessionUpdate: "tool_call_update", toolCallId, status: "completed" });
221
+ await say(`answered ${answer.outcome.outcome}`);
222
+ return done("end_turn");
223
+ }
82
224
  if (text.includes("[permission]")) {
83
225
  const toolCallId = `call-${s.turns}`;
84
226
  await update(s.id, {
@@ -138,10 +280,14 @@ export function runFakeAcpAgent(input, output, options = {}) {
138
280
  const peer = new JsonRpcPeer({
139
281
  input,
140
282
  output,
283
+ // 只有 [cancel-request] 会 abort 出站请求;入站撤回目前不影响假 Agent 的处理器
284
+ cancelRequests: true,
141
285
  async onRequest(method, raw) {
142
286
  const params = (raw ?? {});
143
287
  switch (method) {
144
- case ACP_METHODS.initialize:
288
+ case ACP_METHODS.initialize: {
289
+ const capabilities = params["clientCapabilities"];
290
+ canElicit = capabilities?.["elicitation"] != null;
145
291
  return {
146
292
  protocolVersion: ACP_PROTOCOL_VERSION,
147
293
  agentCapabilities: options.minimal
@@ -151,18 +297,21 @@ export function runFakeAcpAgent(input, output, options = {}) {
151
297
  promptCapabilities: { image: false, embeddedContext: false },
152
298
  sessionCapabilities: { list: {}, resume: {}, close: {} },
153
299
  },
154
- authMethods: [],
300
+ authMethods: options.authRequired === true ? [FAKE_AUTH_METHOD] : [],
155
301
  agentInfo: { name: options.name ?? "fake-acp-agent", version: "1.0.0" },
156
302
  };
303
+ }
157
304
  case ACP_METHODS.sessionNew: {
305
+ requireAuth();
158
306
  counter += 1;
159
307
  const s = open(`fake-${counter}`, String(params["cwd"] ?? ""));
160
308
  if (options.minimal)
161
- return { sessionId: s.id };
162
- return { sessionId: s.id, modes: { currentModeId: s.mode, availableModes: MODES } };
309
+ return { sessionId: s.id, ...config(s) };
310
+ return { sessionId: s.id, ...modes(s), ...config(s) };
163
311
  }
164
312
  case ACP_METHODS.sessionResume:
165
313
  case ACP_METHODS.sessionLoad: {
314
+ requireAuth();
166
315
  const id = String(params["sessionId"] ?? "");
167
316
  if (options.minimal || !id.startsWith("fake-"))
168
317
  throw new RpcError(RPC_ERRORS.resourceNotFound, `unknown session: ${id}`);
@@ -177,7 +326,7 @@ export function runFakeAcpAgent(input, output, options = {}) {
177
326
  content: { type: "text", text: "echo: earlier" },
178
327
  });
179
328
  }
180
- return { modes: { currentModeId: s.mode, availableModes: MODES } };
329
+ return { ...modes(s), ...config(s) };
181
330
  }
182
331
  case ACP_METHODS.sessionList:
183
332
  return {
@@ -193,6 +342,26 @@ export function runFakeAcpAgent(input, output, options = {}) {
193
342
  await update(s.id, { sessionUpdate: "current_mode_update", currentModeId: s.mode });
194
343
  return {};
195
344
  }
345
+ case ACP_METHODS.sessionSetConfigOption: {
346
+ if (!hasConfig)
347
+ throw new RpcError(RPC_ERRORS.methodNotFound, `method not found: ${method}`);
348
+ const s = session(params["sessionId"]);
349
+ const value = String(params["value"]);
350
+ if (params["configId"] === "model" && options.configOptions === true) {
351
+ if (!MODEL_VALUES.includes(value))
352
+ throw new RpcError(RPC_ERRORS.invalidParams, `unknown model: ${value}`);
353
+ s.model = value;
354
+ }
355
+ else if (params["configId"] === "mode" && options.configOnly === true) {
356
+ if (!MODE_VALUES.includes(value))
357
+ throw new RpcError(RPC_ERRORS.invalidParams, `unknown mode: ${value}`);
358
+ s.mode = value;
359
+ }
360
+ else {
361
+ throw new RpcError(RPC_ERRORS.invalidParams, `unknown config: ${String(params["configId"])}`);
362
+ }
363
+ return { configOptions: configOf(s) };
364
+ }
196
365
  case ACP_METHODS.sessionPrompt:
197
366
  return prompt(raw);
198
367
  default:
@@ -2,9 +2,9 @@
2
2
  * ACP(Agent Client Protocol)v1 的子集类型(docs/wave5-plan.md §5.1,D14)。[W5-E]
3
3
  *
4
4
  * 手写、零依赖;只收 ama 作为客户端(驱动外部 Agent)与服务端(`ama --mode acp`)两侧用到的部分:
5
- * `initialize`、`session/new|load|resume|list|close`、`session/prompt|cancel|set_mode`、
6
- * `session/update`(含 `usage_update`)、`session/request_permission`。字段名与规范一致(camelCase),
7
- * 未知字段一律保留不报错;`_meta` 不解释。
5
+ * `initialize`、`authenticate`、`session/new|load|resume|list|close`、`session/prompt|cancel|set_mode|set_config_option`、
6
+ * `session/update`(含 `usage_update`)、`session/request_permission`、`elicitation/create`、`$/cancel_request`。
7
+ * 字段名与规范一致(camelCase),未知字段一律保留不报错;`_meta` 不解释(ama 自己发的只用 {@link ACP_META_KEY})。
8
8
  *
9
9
  * 不声明 `fs` / `terminal` 客户端能力(与 Armadra Q5 一致):外部 Agent 自己读写、自己跑命令,
10
10
  * 用它自己的权限策略;它要问人的才经 `session/request_permission` 回到 ama。
@@ -14,6 +14,8 @@ export declare const ACP_PROTOCOL_VERSION: 1;
14
14
  /** 方法名(客户端 → Agent 的请求 / 通知,Agent → 客户端的请求 / 通知)。 */
15
15
  export declare const ACP_METHODS: {
16
16
  readonly initialize: "initialize";
17
+ /** ama 不实现(只给 terminal 型认证方法,规范要求这类方法不经 authenticate)。 */
18
+ readonly authenticate: "authenticate";
17
19
  readonly sessionNew: "session/new";
18
20
  readonly sessionLoad: "session/load";
19
21
  readonly sessionResume: "session/resume";
@@ -22,8 +24,12 @@ export declare const ACP_METHODS: {
22
24
  readonly sessionPrompt: "session/prompt";
23
25
  readonly sessionCancel: "session/cancel";
24
26
  readonly sessionSetMode: "session/set_mode";
27
+ readonly sessionSetConfigOption: "session/set_config_option";
25
28
  readonly sessionUpdate: "session/update";
26
29
  readonly requestPermission: "session/request_permission";
30
+ readonly elicitationCreate: "elicitation/create";
31
+ /** 协议级取消(双向通知,`requestId` 指对端发来的、尚未答复的请求)。 */
32
+ readonly cancelRequest: "$/cancel_request";
27
33
  };
28
34
  /** JSON-RPC 错误码(规范沿用 JSON-RPC 2.0;`-32000` 为 ACP 的 auth_required)。 */
29
35
  export declare const RPC_ERRORS: {
@@ -34,6 +40,8 @@ export declare const RPC_ERRORS: {
34
40
  readonly internalError: -32603;
35
41
  readonly authRequired: -32000;
36
42
  readonly resourceNotFound: -32002;
43
+ /** 请求被 `$/cancel_request` 撤回。 */
44
+ readonly requestCancelled: -32800;
37
45
  };
38
46
  export interface AcpTextContent {
39
47
  type: "text";
@@ -76,6 +84,18 @@ export interface AcpClientCapabilities {
76
84
  writeTextFile?: boolean;
77
85
  };
78
86
  terminal?: boolean;
87
+ /** 能接 `elicitation/create`(存在即支持)。 */
88
+ elicitation?: Record<string, unknown>;
89
+ /** 会话相关的客户端能力;`configOptions.boolean` 存在即能显示 boolean 型配置项。 */
90
+ session?: {
91
+ configOptions?: {
92
+ boolean?: Record<string, unknown> | null;
93
+ } | null;
94
+ } | null;
95
+ /** `terminal: true`:客户端能替 Agent 起终端跑 terminal 型认证方法。 */
96
+ auth?: {
97
+ terminal?: boolean;
98
+ };
79
99
  }
80
100
  export interface AcpInitializeParams {
81
101
  protocolVersion: number;
@@ -99,11 +119,30 @@ export interface AcpAgentCapabilities {
99
119
  resume?: Record<string, unknown> | null;
100
120
  close?: Record<string, unknown> | null;
101
121
  };
122
+ /** Agent 侧认证能力(`logout` 存在即支持 `logout`)。 */
123
+ auth?: {
124
+ logout?: Record<string, unknown> | null;
125
+ };
102
126
  }
103
- export interface AcpAuthMethod {
127
+ /**
128
+ * 认证方法:缺省 / `agent` 型经 `authenticate` 完成;`terminal` 型由客户端起终端跑
129
+ * `<agent 命令> <args…>`(带 `env`),规范要求这类方法不经 `authenticate`。
130
+ */
131
+ export type AcpAuthMethod = {
132
+ type?: "agent";
104
133
  id: string;
105
134
  name: string;
106
135
  description?: string | null;
136
+ } | {
137
+ type: "terminal";
138
+ id: string;
139
+ name: string;
140
+ description?: string | null;
141
+ args?: string[];
142
+ env?: Record<string, string>;
143
+ };
144
+ export interface AcpAuthenticateParams {
145
+ methodId: string;
107
146
  }
108
147
  export interface AcpInitializeResult {
109
148
  protocolVersion: number;
@@ -149,6 +188,7 @@ export interface AcpNewSessionParams {
149
188
  export interface AcpNewSessionResult {
150
189
  sessionId: string;
151
190
  modes?: AcpSessionModeState | null;
191
+ configOptions?: AcpSessionConfigOption[] | null;
152
192
  }
153
193
  export interface AcpLoadSessionParams {
154
194
  sessionId: string;
@@ -157,6 +197,87 @@ export interface AcpLoadSessionParams {
157
197
  }
158
198
  export interface AcpLoadSessionResult {
159
199
  modes?: AcpSessionModeState | null;
200
+ configOptions?: AcpSessionConfigOption[] | null;
201
+ }
202
+ /** 配置项里一个可选值。 */
203
+ export interface AcpConfigSelectOption {
204
+ value: string;
205
+ name: string;
206
+ description?: string | null;
207
+ }
208
+ /** 规范允许把可选值分组。 */
209
+ export interface AcpConfigSelectGroup {
210
+ group: string;
211
+ name: string;
212
+ options: AcpConfigSelectOption[];
213
+ }
214
+ /** 配置项的语义分类:只是给客户端排版的提示,不参与正确性。 */
215
+ export type AcpConfigCategory = "mode" | "model" | "model_config" | "thought_level" | (string & {});
216
+ /** `session/new|load|resume` 答的 `configOptions[]` 的一项(ama 只发 `select`)。 */
217
+ export interface AcpSessionConfigOption {
218
+ id: string;
219
+ name: string;
220
+ description?: string | null;
221
+ /** `mode` / `model` / `thought_level`,或 Agent 自己的。 */
222
+ category?: AcpConfigCategory | null;
223
+ type: "select" | (string & {});
224
+ currentValue: string;
225
+ /** 全部平铺或全部分组,规范不允许混排。 */
226
+ options: AcpConfigSelectOption[] | AcpConfigSelectGroup[];
227
+ }
228
+ export interface AcpSetConfigOptionParams {
229
+ sessionId: string;
230
+ configId: string;
231
+ value: string;
232
+ }
233
+ /** 答复带全部配置项的新状态(改一项可能连带别的)。 */
234
+ export interface AcpSetConfigOptionResult {
235
+ configOptions?: AcpSessionConfigOption[] | null;
236
+ }
237
+ /** 表单里的一个字段:扁平的原始类型,规范只允许这几种。 */
238
+ export type AcpElicitationField = {
239
+ type: "string";
240
+ title?: string;
241
+ description?: string;
242
+ enum?: string[];
243
+ enumNames?: string[];
244
+ format?: string;
245
+ minLength?: number;
246
+ maxLength?: number;
247
+ default?: string;
248
+ } | {
249
+ type: "number" | "integer";
250
+ title?: string;
251
+ description?: string;
252
+ minimum?: number;
253
+ maximum?: number;
254
+ default?: number;
255
+ } | {
256
+ type: "boolean";
257
+ title?: string;
258
+ description?: string;
259
+ default?: boolean;
260
+ };
261
+ export interface AcpElicitationSchema {
262
+ type: "object";
263
+ properties: Record<string, AcpElicitationField>;
264
+ required?: string[];
265
+ }
266
+ /** Agent 发来的 `elicitation/create` 参数;未知字段保留。 */
267
+ export interface AcpElicitationParams {
268
+ sessionId?: string;
269
+ message: string;
270
+ /** `form`(缺省)或 `url`。 */
271
+ mode?: "form" | "url" | (string & {});
272
+ requestedSchema?: AcpElicitationSchema;
273
+ url?: string;
274
+ [key: string]: unknown;
275
+ }
276
+ export type AcpElicitationAction = "accept" | "decline" | "cancel";
277
+ export interface AcpElicitationResult {
278
+ action: AcpElicitationAction;
279
+ /** 只随 `accept`。 */
280
+ content?: Record<string, string | number | boolean>;
160
281
  }
161
282
  export type AcpResumeSessionParams = AcpLoadSessionParams;
162
283
  export type AcpResumeSessionResult = AcpLoadSessionResult;
@@ -186,7 +307,10 @@ export interface AcpPromptParams {
186
307
  sessionId: string;
187
308
  prompt: AcpContentBlock[];
188
309
  }
189
- /** 本回合用量(可选字段,按 2026-06 稳定的 usage 提案)。 */
310
+ /**
311
+ * 本回合用量。UNSTABLE:1.24.1 仍只在 schema.unstable.json 里(稳定 schema 的 PromptResponse
312
+ * 不列它,但允许附加字段,故照发);客户端不应依赖。
313
+ */
190
314
  export interface AcpPromptUsage {
191
315
  totalTokens?: number;
192
316
  inputTokens?: number;
@@ -202,6 +326,17 @@ export interface AcpPromptResult {
202
326
  export interface AcpCancelParams {
203
327
  sessionId: string;
204
328
  }
329
+ /** `$/cancel_request` 的参数:被撤回的请求 id。 */
330
+ export interface AcpCancelRequestParams {
331
+ requestId: string | number | null;
332
+ }
333
+ /** ama 自己的 `_meta` 命名空间(客户端不得假设其含义)。 */
334
+ export declare const ACP_META_KEY: "ama";
335
+ /** `_meta.ama` 的内容。 */
336
+ export interface AcpAmaMeta {
337
+ /** codemode 内层调用所属的外层工具调用。 */
338
+ parentToolCallId?: string;
339
+ }
205
340
  export type { AcpToolKind };
206
341
  export type AcpToolCallStatus = "pending" | "in_progress" | "completed" | "failed";
207
342
  export interface AcpToolCallLocation {
@@ -229,6 +364,10 @@ export interface AcpToolCall {
229
364
  locations?: AcpToolCallLocation[];
230
365
  rawInput?: unknown;
231
366
  rawOutput?: unknown;
367
+ /** 工具名(规范新增,供客户端按工具分组 / 记忆授权)。 */
368
+ name?: string | null;
369
+ /** 扩展位;ama 只用 `{ [ACP_META_KEY]: AcpAmaMeta }`。 */
370
+ _meta?: Record<string, unknown> | null;
232
371
  }
233
372
  /** `tool_call_update` 与权限请求里的 toolCall:除 id 外都可缺。 */
234
373
  export type AcpToolCallUpdate = Partial<AcpToolCall> & {
@@ -270,15 +409,23 @@ export type AcpSessionUpdate = {
270
409
  } | null;
271
410
  } | {
272
411
  sessionUpdate: "available_commands_update";
273
- availableCommands: {
274
- name: string;
275
- description: string;
276
- }[];
412
+ availableCommands: AcpAvailableCommand[];
413
+ } | {
414
+ sessionUpdate: "config_option_update";
415
+ configOptions: AcpSessionConfigOption[];
277
416
  } | {
278
417
  sessionUpdate: "session_info_update";
279
418
  title?: string | null;
280
419
  updatedAt?: string | null;
281
420
  };
421
+ /** 客户端可列给人选的斜杠命令(`/<name>`);`input.hint` 是参数提示。 */
422
+ export interface AcpAvailableCommand {
423
+ name: string;
424
+ description: string;
425
+ input?: {
426
+ hint: string;
427
+ } | null;
428
+ }
282
429
  export interface AcpSessionNotification {
283
430
  sessionId: string;
284
431
  update: AcpSessionUpdate;
@@ -2,9 +2,9 @@
2
2
  * ACP(Agent Client Protocol)v1 的子集类型(docs/wave5-plan.md §5.1,D14)。[W5-E]
3
3
  *
4
4
  * 手写、零依赖;只收 ama 作为客户端(驱动外部 Agent)与服务端(`ama --mode acp`)两侧用到的部分:
5
- * `initialize`、`session/new|load|resume|list|close`、`session/prompt|cancel|set_mode`、
6
- * `session/update`(含 `usage_update`)、`session/request_permission`。字段名与规范一致(camelCase),
7
- * 未知字段一律保留不报错;`_meta` 不解释。
5
+ * `initialize`、`authenticate`、`session/new|load|resume|list|close`、`session/prompt|cancel|set_mode|set_config_option`、
6
+ * `session/update`(含 `usage_update`)、`session/request_permission`、`elicitation/create`、`$/cancel_request`。
7
+ * 字段名与规范一致(camelCase),未知字段一律保留不报错;`_meta` 不解释(ama 自己发的只用 {@link ACP_META_KEY})。
8
8
  *
9
9
  * 不声明 `fs` / `terminal` 客户端能力(与 Armadra Q5 一致):外部 Agent 自己读写、自己跑命令,
10
10
  * 用它自己的权限策略;它要问人的才经 `session/request_permission` 回到 ama。
@@ -13,6 +13,8 @@ export const ACP_PROTOCOL_VERSION = 1;
13
13
  /** 方法名(客户端 → Agent 的请求 / 通知,Agent → 客户端的请求 / 通知)。 */
14
14
  export const ACP_METHODS = {
15
15
  initialize: "initialize",
16
+ /** ama 不实现(只给 terminal 型认证方法,规范要求这类方法不经 authenticate)。 */
17
+ authenticate: "authenticate",
16
18
  sessionNew: "session/new",
17
19
  sessionLoad: "session/load",
18
20
  sessionResume: "session/resume",
@@ -21,8 +23,12 @@ export const ACP_METHODS = {
21
23
  sessionPrompt: "session/prompt",
22
24
  sessionCancel: "session/cancel",
23
25
  sessionSetMode: "session/set_mode",
26
+ sessionSetConfigOption: "session/set_config_option",
24
27
  sessionUpdate: "session/update",
25
28
  requestPermission: "session/request_permission",
29
+ elicitationCreate: "elicitation/create",
30
+ /** 协议级取消(双向通知,`requestId` 指对端发来的、尚未答复的请求)。 */
31
+ cancelRequest: "$/cancel_request",
26
32
  };
27
33
  /** JSON-RPC 错误码(规范沿用 JSON-RPC 2.0;`-32000` 为 ACP 的 auth_required)。 */
28
34
  export const RPC_ERRORS = {
@@ -33,4 +39,8 @@ export const RPC_ERRORS = {
33
39
  internalError: -32603,
34
40
  authRequired: -32000,
35
41
  resourceNotFound: -32002,
42
+ /** 请求被 `$/cancel_request` 撤回。 */
43
+ requestCancelled: -32800,
36
44
  };
45
+ /** ama 自己的 `_meta` 命名空间(客户端不得假设其含义)。 */
46
+ export const ACP_META_KEY = "ama";
@@ -5,7 +5,10 @@
5
5
  * - 分帧复用 `modes/rpc/jsonl.ts`(只按 `\n` 切行、64 KiB 分片写、背压等 drain),写入串行;
6
6
  * - 双向:本端可发请求 / 通知,也处理对端的请求(`onRequest` 抛 {@link RpcError} 即回错误)与通知;
7
7
  * - `jsonrpcField: false` 时不写 `"jsonrpc":"2.0"`(Codex app-server 的线上形状),读取两种都认;
8
- * - 连接关闭(输入流结束)时所有挂起的请求以 `connection_closed` 失败。
8
+ * - 连接关闭(输入流结束)时所有挂起的请求以 `connection_closed` 失败;
9
+ * - `cancelRequests: true`(ACP 两侧)时支持协议级取消 `$/cancel_request`:本端请求的 `signal` abort
10
+ * 会通知对端;对端撤回本端正在处理的请求时 abort 该请求的 `ctx.signal`,处理器此后抛错一律回 -32800。
11
+ * 缺省关闭(Codex app-server 不认),线上形状与之前逐字节相同。
9
12
  */
10
13
  export type RpcId = string | number;
11
14
  export declare class RpcError extends Error {
@@ -15,7 +18,7 @@ export declare class RpcError extends Error {
15
18
  }
16
19
  export interface IncomingRequestContext {
17
20
  readonly id: RpcId;
18
- /** 连接关闭时 abort。 */
21
+ /** 连接关闭时 abort;`cancelRequests` 开着时对端发 `$/cancel_request` 撤回本请求也 abort。 */
19
22
  readonly signal: AbortSignal;
20
23
  }
21
24
  export interface JsonRpcPeerOptions {
@@ -29,10 +32,14 @@ export interface JsonRpcPeerOptions {
29
32
  onClose?(): void;
30
33
  /** 无法解析的行(不影响连接)。 */
31
34
  onProtocolError?(line: string, reason: string): void;
35
+ /** 协议级取消(ACP `$/cancel_request`):缺省 false(Codex app-server 不认)。 */
36
+ cancelRequests?: boolean;
32
37
  }
33
38
  export declare class JsonRpcPeer {
34
39
  private readonly options;
35
40
  private readonly pending;
41
+ /** 正在处理的对端请求(只在 `cancelRequests` 时记录),供 `$/cancel_request` 撤回。 */
42
+ private readonly inflight;
36
43
  private readonly reader;
37
44
  private readonly lifetime;
38
45
  private nextId;
@@ -43,7 +50,10 @@ export declare class JsonRpcPeer {
43
50
  readonly closed: Promise<void>;
44
51
  constructor(options: JsonRpcPeerOptions);
45
52
  get isOpen(): boolean;
46
- /** 发请求;`signal` abort 时本端不再等(不通知对端,协议级取消由调用方另发)。 */
53
+ /**
54
+ * 发请求;`signal` abort 时本端不再等。`cancelRequests` 开着时同时给对端发
55
+ * `$/cancel_request { requestId }`(已发出的请求才发),否则不通知对端。
56
+ */
47
57
  request<T = unknown>(method: string, params?: unknown, signal?: AbortSignal): Promise<T>;
48
58
  /** 输入流结束后仍可写(对端可能只关了自己的写端,还在读);写失败静默。 */
49
59
  notify(method: string, params?: unknown): Promise<void>;