@armadra/agent 0.6.7 → 0.6.8
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.
- package/CHANGELOG.md +9 -0
- package/CHANGELOG.zh-CN.md +8 -0
- package/dist/bundle/ama.cjs +59 -8
- package/dist/drivers/acp/client.d.ts +23 -4
- package/dist/drivers/acp/client.js +69 -5
- package/dist/drivers/acp/testing/fake-agent-main.d.ts +1 -1
- package/dist/drivers/acp/testing/fake-agent-main.js +3 -2
- package/dist/drivers/acp/testing/fake-agent.d.ts +9 -0
- package/dist/drivers/acp/testing/fake-agent.js +91 -5
- package/dist/drivers/acp/types.d.ts +85 -2
- package/dist/drivers/acp/types.js +4 -2
- package/docs/acp.md +8 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,15 @@ English · [简体中文](CHANGELOG.zh-CN.md)
|
|
|
5
5
|
> This file is in English starting with 0.6.0. Release notes for 0.1 through 0.5.1 are in Chinese in
|
|
6
6
|
> [CHANGELOG.zh-CN.md](CHANGELOG.zh-CN.md). New entries go into both files.
|
|
7
7
|
|
|
8
|
+
## 0.6.8 (2026-10-04)
|
|
9
|
+
|
|
10
|
+
- **ACP client: elicitation and session config options**: `AcpClient` takes an optional `onElicitation(params, signal)`;
|
|
11
|
+
when given, `initialize` declares `clientCapabilities.elicitation` and `elicitation/create` goes to it (answers normalized
|
|
12
|
+
to `accept` / `decline` / `cancel`; pending ones resolve `cancel` on `cancel(sessionId)` or connection close). New
|
|
13
|
+
`setConfigOption(sessionId, configId, value)` and `configOptions` on session-open results. `AcpClient.features` gains
|
|
14
|
+
`elicitation` and `configOptions`. Without a handler the wire is unchanged. The fake ACP agent adds `[elicit]`, `[model]`
|
|
15
|
+
and `[env NAME]` markers and `--config-options`. Docs: docs/acp.md.
|
|
16
|
+
|
|
8
17
|
## 0.6.7 (2026-10-03)
|
|
9
18
|
|
|
10
19
|
- **A host runner with id `ama` is used**: when the host registers a runner for `ama` via `HostApi.runners.provide`
|
package/CHANGELOG.zh-CN.md
CHANGED
|
@@ -4,6 +4,14 @@
|
|
|
4
4
|
|
|
5
5
|
> 从 0.6.0 起 [CHANGELOG.md](CHANGELOG.md) 为英文,本文件保留中文记录(0.1–0.5.1 的完整历史在此)。新条目两份都要加。
|
|
6
6
|
|
|
7
|
+
## 0.6.8(2026-10-04)
|
|
8
|
+
|
|
9
|
+
- **ACP 客户端:elicitation 与会话配置项**:`AcpClient` 可选构造参数 `onElicitation(params, signal)`,给了才在 `initialize`
|
|
10
|
+
声明 `clientCapabilities.elicitation` 并把 `elicitation/create` 交给它(答复收成 `accept` / `decline` / `cancel`;
|
|
11
|
+
`cancel(sessionId)` 或连接关闭时挂起的回 `cancel`)。新增 `setConfigOption(sessionId, configId, value)`,开会话答复带
|
|
12
|
+
`configOptions`。`AcpClient.features` 多 `elicitation` 与 `configOptions`。不给处理器时线路不变。假 ACP Agent 加
|
|
13
|
+
`[elicit]`、`[model]`、`[env NAME]` 标记与 `--config-options`。文档:docs/acp.md。
|
|
14
|
+
|
|
7
15
|
## 0.6.7(2026-10-03)
|
|
8
16
|
|
|
9
17
|
- **宿主注入的 `ama` runner 生效**:宿主经 `HostApi.runners.provide` 注入 id 为 `ama` 的 runner(如 Armadra 画布上另一个
|
package/dist/bundle/ama.cjs
CHANGED
|
@@ -41,7 +41,7 @@ var AMA_VERSION;
|
|
|
41
41
|
var init_version = __esm({
|
|
42
42
|
"src/version.ts"() {
|
|
43
43
|
"use strict";
|
|
44
|
-
AMA_VERSION = true ? "0.6.
|
|
44
|
+
AMA_VERSION = true ? "0.6.8" : readPackageVersion();
|
|
45
45
|
}
|
|
46
46
|
});
|
|
47
47
|
|
|
@@ -30592,8 +30592,10 @@ var init_types9 = __esm({
|
|
|
30592
30592
|
sessionPrompt: "session/prompt",
|
|
30593
30593
|
sessionCancel: "session/cancel",
|
|
30594
30594
|
sessionSetMode: "session/set_mode",
|
|
30595
|
+
sessionSetConfigOption: "session/set_config_option",
|
|
30595
30596
|
sessionUpdate: "session/update",
|
|
30596
|
-
requestPermission: "session/request_permission"
|
|
30597
|
+
requestPermission: "session/request_permission",
|
|
30598
|
+
elicitationCreate: "elicitation/create"
|
|
30597
30599
|
};
|
|
30598
30600
|
RPC_ERRORS = {
|
|
30599
30601
|
parseError: -32700,
|
|
@@ -30790,18 +30792,25 @@ var init_jsonrpc = __esm({
|
|
|
30790
30792
|
function mcpServersOf(options) {
|
|
30791
30793
|
return [...options?.mcpServers ?? []];
|
|
30792
30794
|
}
|
|
30795
|
+
function elicitationAnswer(answer) {
|
|
30796
|
+
const action = answer?.action;
|
|
30797
|
+
if (action === void 0 || !ELICITATION_ACTIONS.has(action)) return { action: "cancel" };
|
|
30798
|
+
if (action !== "accept" || answer?.content === void 0) return { action };
|
|
30799
|
+
return { action, content: answer.content };
|
|
30800
|
+
}
|
|
30793
30801
|
function unattendedOutcome(params) {
|
|
30794
30802
|
const reject2 = params.options.find((o) => o.kind === "reject_once");
|
|
30795
30803
|
return {
|
|
30796
30804
|
outcome: reject2 !== void 0 ? { outcome: "selected", optionId: reject2.optionId } : { outcome: "cancelled" }
|
|
30797
30805
|
};
|
|
30798
30806
|
}
|
|
30799
|
-
var AcpClient;
|
|
30807
|
+
var ELICITATION_ACTIONS, AcpClient;
|
|
30800
30808
|
var init_client = __esm({
|
|
30801
30809
|
"src/drivers/acp/client.ts"() {
|
|
30802
30810
|
"use strict";
|
|
30803
30811
|
init_jsonrpc();
|
|
30804
30812
|
init_types9();
|
|
30813
|
+
ELICITATION_ACTIONS = /* @__PURE__ */ new Set(["accept", "decline", "cancel"]);
|
|
30805
30814
|
AcpClient = class {
|
|
30806
30815
|
constructor(options) {
|
|
30807
30816
|
this.options = options;
|
|
@@ -30815,6 +30824,7 @@ var init_client = __esm({
|
|
|
30815
30824
|
},
|
|
30816
30825
|
onClose: () => {
|
|
30817
30826
|
for (const set of this.pendingPermissions.values()) for (const c of set) c.abort();
|
|
30827
|
+
for (const set of this.pendingElicitations.values()) for (const c of set) c.abort();
|
|
30818
30828
|
options.onClose?.();
|
|
30819
30829
|
},
|
|
30820
30830
|
...options.onProtocolError !== void 0 ? { onProtocolError: options.onProtocolError } : {}
|
|
@@ -30822,13 +30832,17 @@ var init_client = __esm({
|
|
|
30822
30832
|
}
|
|
30823
30833
|
options;
|
|
30824
30834
|
/**
|
|
30825
|
-
*
|
|
30826
|
-
* - `mcpServers`:开会话时可经 {@link AcpSessionOptions} 传 MCP
|
|
30835
|
+
* 本版客户端支持的可选能力,供宿主做特性检测(旧版没有这个字段,旧一点的只有 `mcpServers`):
|
|
30836
|
+
* - `mcpServers`:开会话时可经 {@link AcpSessionOptions} 传 MCP 服务器;
|
|
30837
|
+
* - `elicitation`:构造参数 `onElicitation` 接 `elicitation/create`;
|
|
30838
|
+
* - `configOptions`:`setConfigOption` 与开会话答的 `configOptions`。
|
|
30827
30839
|
*/
|
|
30828
|
-
static features = { mcpServers: true };
|
|
30840
|
+
static features = { mcpServers: true, elicitation: true, configOptions: true };
|
|
30829
30841
|
peer;
|
|
30830
30842
|
/** sessionId → 挂起权限请求的取消器。 */
|
|
30831
30843
|
pendingPermissions = /* @__PURE__ */ new Map();
|
|
30844
|
+
/** sessionId(没给就是空串)→ 挂起 elicitation 的取消器。 */
|
|
30845
|
+
pendingElicitations = /* @__PURE__ */ new Map();
|
|
30832
30846
|
initResult;
|
|
30833
30847
|
get closed() {
|
|
30834
30848
|
return this.peer.closed;
|
|
@@ -30847,7 +30861,11 @@ var init_client = __esm({
|
|
|
30847
30861
|
ACP_METHODS.initialize,
|
|
30848
30862
|
{
|
|
30849
30863
|
protocolVersion: ACP_PROTOCOL_VERSION,
|
|
30850
|
-
clientCapabilities: {
|
|
30864
|
+
clientCapabilities: {
|
|
30865
|
+
fs: { readTextFile: false, writeTextFile: false },
|
|
30866
|
+
terminal: false,
|
|
30867
|
+
...this.options.onElicitation !== void 0 ? { elicitation: {} } : {}
|
|
30868
|
+
},
|
|
30851
30869
|
...this.options.clientInfo !== void 0 ? { clientInfo: this.options.clientInfo } : {}
|
|
30852
30870
|
},
|
|
30853
30871
|
signal
|
|
@@ -30913,15 +30931,22 @@ var init_client = __esm({
|
|
|
30913
30931
|
setMode(sessionId, modeId) {
|
|
30914
30932
|
return this.peer.request(ACP_METHODS.sessionSetMode, { sessionId, modeId });
|
|
30915
30933
|
}
|
|
30916
|
-
/**
|
|
30934
|
+
/** 改一个会话配置项(如模型);答复是全部配置项的新状态。 */
|
|
30935
|
+
setConfigOption(sessionId, configId, value) {
|
|
30936
|
+
return this.peer.request(ACP_METHODS.sessionSetConfigOption, { sessionId, configId, value });
|
|
30937
|
+
}
|
|
30938
|
+
/** 协议级取消:发通知,并让挂起的权限请求回 cancelled、挂起的 elicitation 回 cancel。 */
|
|
30917
30939
|
async cancel(sessionId) {
|
|
30918
30940
|
for (const controller of this.pendingPermissions.get(sessionId) ?? []) controller.abort();
|
|
30941
|
+
for (const controller of this.pendingElicitations.get(sessionId) ?? []) controller.abort();
|
|
30919
30942
|
await this.peer.notify(ACP_METHODS.sessionCancel, { sessionId });
|
|
30920
30943
|
}
|
|
30921
30944
|
close() {
|
|
30922
30945
|
this.peer.close();
|
|
30923
30946
|
}
|
|
30924
30947
|
async onRequest(method, params, connection) {
|
|
30948
|
+
if (method === ACP_METHODS.elicitationCreate && this.options.onElicitation !== void 0)
|
|
30949
|
+
return this.onElicitation(params, connection);
|
|
30925
30950
|
if (method !== ACP_METHODS.requestPermission)
|
|
30926
30951
|
throw new RpcError(RPC_ERRORS.methodNotFound, `client does not support ${method}`);
|
|
30927
30952
|
const request = params;
|
|
@@ -30953,6 +30978,32 @@ var init_client = __esm({
|
|
|
30953
30978
|
set.delete(controller);
|
|
30954
30979
|
}
|
|
30955
30980
|
}
|
|
30981
|
+
async onElicitation(params, connection) {
|
|
30982
|
+
const request = params;
|
|
30983
|
+
if (request === null || typeof request !== "object" || typeof request.message !== "string")
|
|
30984
|
+
throw new RpcError(RPC_ERRORS.invalidParams, "invalid elicitation/create params");
|
|
30985
|
+
const handler = this.options.onElicitation;
|
|
30986
|
+
const key = typeof request.sessionId === "string" ? request.sessionId : "";
|
|
30987
|
+
const controller = new AbortController();
|
|
30988
|
+
const set = this.pendingElicitations.get(key) ?? /* @__PURE__ */ new Set();
|
|
30989
|
+
set.add(controller);
|
|
30990
|
+
this.pendingElicitations.set(key, set);
|
|
30991
|
+
const signal = AbortSignal.any([controller.signal, connection]);
|
|
30992
|
+
try {
|
|
30993
|
+
const answer = await Promise.race([
|
|
30994
|
+
handler(request, signal),
|
|
30995
|
+
new Promise((resolve39) => {
|
|
30996
|
+
const cancelled = () => resolve39({ action: "cancel" });
|
|
30997
|
+
if (signal.aborted) cancelled();
|
|
30998
|
+
else signal.addEventListener("abort", cancelled, { once: true });
|
|
30999
|
+
})
|
|
31000
|
+
]);
|
|
31001
|
+
if (signal.aborted) return { action: "cancel" };
|
|
31002
|
+
return elicitationAnswer(answer);
|
|
31003
|
+
} finally {
|
|
31004
|
+
set.delete(controller);
|
|
31005
|
+
}
|
|
31006
|
+
}
|
|
30956
31007
|
};
|
|
30957
31008
|
}
|
|
30958
31009
|
});
|
|
@@ -10,12 +10,22 @@
|
|
|
10
10
|
* (规范要求:客户端取消回合后必须以 cancelled 回答挂起的请求)。
|
|
11
11
|
* - 开会话(new / resume / load)的 `mcpServers` 缺省为空数组;宿主可经 {@link AcpSessionOptions}
|
|
12
12
|
* 传入,原样转发。
|
|
13
|
+
* - `elicitation/create` 交给 `onElicitation`;给了它 `initialize` 才声明 `clientCapabilities.elicitation`,
|
|
14
|
+
* 没给时线路与旧版相同(不声明,Agent 发来的请求回 method not found)。`cancel(sessionId)`、连接关闭时
|
|
15
|
+
* 挂起的 elicitation 一律回 `{ action: "cancel" }`;ama 从不替人填表。
|
|
16
|
+
* - `setConfigOption(sessionId, configId, value)` 发 `session/set_config_option`;开会话答的 `configOptions`
|
|
17
|
+
* 原样交回。
|
|
13
18
|
*/
|
|
14
|
-
import { type AcpAgentCapabilities, type AcpContentBlock, type AcpImplementationInfo, type AcpInitializeResult, type AcpListSessionsResult, type AcpLoadSessionResult, type AcpNewSessionResult, type AcpPromptResult, type AcpRequestPermissionParams, type AcpRequestPermissionResult, type AcpSessionNotification, type AcpSessionOptions } from "./types.js";
|
|
19
|
+
import { type AcpAgentCapabilities, type AcpContentBlock, type AcpElicitationParams, type AcpElicitationResult, type AcpImplementationInfo, type AcpInitializeResult, type AcpListSessionsResult, type AcpLoadSessionResult, type AcpNewSessionResult, type AcpPromptResult, type AcpRequestPermissionParams, type AcpRequestPermissionResult, type AcpSessionNotification, type AcpSessionOptions, type AcpSetConfigOptionResult } from "./types.js";
|
|
15
20
|
export interface AcpClientHandlers {
|
|
16
21
|
onUpdate?(notification: AcpSessionNotification): void;
|
|
17
22
|
/** `signal`:该会话被 cancel、连接关闭时 abort(此时应尽快回 cancelled)。 */
|
|
18
23
|
onPermission?(params: AcpRequestPermissionParams, signal: AbortSignal): Promise<AcpRequestPermissionResult>;
|
|
24
|
+
/**
|
|
25
|
+
* `elicitation/create`:Agent 向人要结构化输入。给了它才声明 `clientCapabilities.elicitation`。
|
|
26
|
+
* `signal`:该会话被 cancel、连接关闭时 abort(此时回 `{ action: "cancel" }`,不必再答)。
|
|
27
|
+
*/
|
|
28
|
+
onElicitation?(params: AcpElicitationParams, signal: AbortSignal): Promise<AcpElicitationResult>;
|
|
19
29
|
onProtocolError?(line: string, reason: string): void;
|
|
20
30
|
onClose?(): void;
|
|
21
31
|
}
|
|
@@ -29,15 +39,21 @@ export declare function unattendedOutcome(params: AcpRequestPermissionParams): A
|
|
|
29
39
|
export declare class AcpClient {
|
|
30
40
|
private readonly options;
|
|
31
41
|
/**
|
|
32
|
-
*
|
|
33
|
-
* - `mcpServers`:开会话时可经 {@link AcpSessionOptions} 传 MCP
|
|
42
|
+
* 本版客户端支持的可选能力,供宿主做特性检测(旧版没有这个字段,旧一点的只有 `mcpServers`):
|
|
43
|
+
* - `mcpServers`:开会话时可经 {@link AcpSessionOptions} 传 MCP 服务器;
|
|
44
|
+
* - `elicitation`:构造参数 `onElicitation` 接 `elicitation/create`;
|
|
45
|
+
* - `configOptions`:`setConfigOption` 与开会话答的 `configOptions`。
|
|
34
46
|
*/
|
|
35
47
|
static readonly features: {
|
|
36
48
|
readonly mcpServers: true;
|
|
49
|
+
readonly elicitation: true;
|
|
50
|
+
readonly configOptions: true;
|
|
37
51
|
};
|
|
38
52
|
private readonly peer;
|
|
39
53
|
/** sessionId → 挂起权限请求的取消器。 */
|
|
40
54
|
private readonly pendingPermissions;
|
|
55
|
+
/** sessionId(没给就是空串)→ 挂起 elicitation 的取消器。 */
|
|
56
|
+
private readonly pendingElicitations;
|
|
41
57
|
private initResult;
|
|
42
58
|
constructor(options: AcpClientOptions);
|
|
43
59
|
get closed(): Promise<void>;
|
|
@@ -59,8 +75,11 @@ export declare class AcpClient {
|
|
|
59
75
|
closeSession(sessionId: string): Promise<unknown>;
|
|
60
76
|
prompt(sessionId: string, prompt: AcpContentBlock[], signal?: AbortSignal): Promise<AcpPromptResult>;
|
|
61
77
|
setMode(sessionId: string, modeId: string): Promise<unknown>;
|
|
62
|
-
/**
|
|
78
|
+
/** 改一个会话配置项(如模型);答复是全部配置项的新状态。 */
|
|
79
|
+
setConfigOption(sessionId: string, configId: string, value: string): Promise<AcpSetConfigOptionResult>;
|
|
80
|
+
/** 协议级取消:发通知,并让挂起的权限请求回 cancelled、挂起的 elicitation 回 cancel。 */
|
|
63
81
|
cancel(sessionId: string): Promise<void>;
|
|
64
82
|
close(): void;
|
|
65
83
|
private onRequest;
|
|
84
|
+
private onElicitation;
|
|
66
85
|
}
|
|
@@ -10,6 +10,11 @@
|
|
|
10
10
|
* (规范要求:客户端取消回合后必须以 cancelled 回答挂起的请求)。
|
|
11
11
|
* - 开会话(new / resume / load)的 `mcpServers` 缺省为空数组;宿主可经 {@link AcpSessionOptions}
|
|
12
12
|
* 传入,原样转发。
|
|
13
|
+
* - `elicitation/create` 交给 `onElicitation`;给了它 `initialize` 才声明 `clientCapabilities.elicitation`,
|
|
14
|
+
* 没给时线路与旧版相同(不声明,Agent 发来的请求回 method not found)。`cancel(sessionId)`、连接关闭时
|
|
15
|
+
* 挂起的 elicitation 一律回 `{ action: "cancel" }`;ama 从不替人填表。
|
|
16
|
+
* - `setConfigOption(sessionId, configId, value)` 发 `session/set_config_option`;开会话答的 `configOptions`
|
|
17
|
+
* 原样交回。
|
|
13
18
|
*/
|
|
14
19
|
import { JsonRpcPeer, RpcError } from "../jsonrpc.js";
|
|
15
20
|
import { ACP_METHODS, ACP_PROTOCOL_VERSION, RPC_ERRORS, } from "./types.js";
|
|
@@ -17,6 +22,16 @@ import { ACP_METHODS, ACP_PROTOCOL_VERSION, RPC_ERRORS, } from "./types.js";
|
|
|
17
22
|
function mcpServersOf(options) {
|
|
18
23
|
return [...(options?.mcpServers ?? [])];
|
|
19
24
|
}
|
|
25
|
+
const ELICITATION_ACTIONS = new Set(["accept", "decline", "cancel"]);
|
|
26
|
+
/** 处理器的答复收成规范形状:动作不认识当 cancel,`content` 只随 accept。 */
|
|
27
|
+
function elicitationAnswer(answer) {
|
|
28
|
+
const action = answer?.action;
|
|
29
|
+
if (action === undefined || !ELICITATION_ACTIONS.has(action))
|
|
30
|
+
return { action: "cancel" };
|
|
31
|
+
if (action !== "accept" || answer?.content === undefined)
|
|
32
|
+
return { action };
|
|
33
|
+
return { action, content: answer.content };
|
|
34
|
+
}
|
|
20
35
|
/** 无人值守的回答:首个 reject_once,没有就 cancelled。 */
|
|
21
36
|
export function unattendedOutcome(params) {
|
|
22
37
|
const reject = params.options.find((o) => o.kind === "reject_once");
|
|
@@ -29,13 +44,17 @@ export function unattendedOutcome(params) {
|
|
|
29
44
|
export class AcpClient {
|
|
30
45
|
options;
|
|
31
46
|
/**
|
|
32
|
-
*
|
|
33
|
-
* - `mcpServers`:开会话时可经 {@link AcpSessionOptions} 传 MCP
|
|
47
|
+
* 本版客户端支持的可选能力,供宿主做特性检测(旧版没有这个字段,旧一点的只有 `mcpServers`):
|
|
48
|
+
* - `mcpServers`:开会话时可经 {@link AcpSessionOptions} 传 MCP 服务器;
|
|
49
|
+
* - `elicitation`:构造参数 `onElicitation` 接 `elicitation/create`;
|
|
50
|
+
* - `configOptions`:`setConfigOption` 与开会话答的 `configOptions`。
|
|
34
51
|
*/
|
|
35
|
-
static features = { mcpServers: true };
|
|
52
|
+
static features = { mcpServers: true, elicitation: true, configOptions: true };
|
|
36
53
|
peer;
|
|
37
54
|
/** sessionId → 挂起权限请求的取消器。 */
|
|
38
55
|
pendingPermissions = new Map();
|
|
56
|
+
/** sessionId(没给就是空串)→ 挂起 elicitation 的取消器。 */
|
|
57
|
+
pendingElicitations = new Map();
|
|
39
58
|
initResult;
|
|
40
59
|
constructor(options) {
|
|
41
60
|
this.options = options;
|
|
@@ -51,6 +70,9 @@ export class AcpClient {
|
|
|
51
70
|
for (const set of this.pendingPermissions.values())
|
|
52
71
|
for (const c of set)
|
|
53
72
|
c.abort();
|
|
73
|
+
for (const set of this.pendingElicitations.values())
|
|
74
|
+
for (const c of set)
|
|
75
|
+
c.abort();
|
|
54
76
|
options.onClose?.();
|
|
55
77
|
},
|
|
56
78
|
...(options.onProtocolError !== undefined
|
|
@@ -73,7 +95,11 @@ export class AcpClient {
|
|
|
73
95
|
async initialize(signal) {
|
|
74
96
|
const result = await this.peer.request(ACP_METHODS.initialize, {
|
|
75
97
|
protocolVersion: ACP_PROTOCOL_VERSION,
|
|
76
|
-
clientCapabilities: {
|
|
98
|
+
clientCapabilities: {
|
|
99
|
+
fs: { readTextFile: false, writeTextFile: false },
|
|
100
|
+
terminal: false,
|
|
101
|
+
...(this.options.onElicitation !== undefined ? { elicitation: {} } : {}),
|
|
102
|
+
},
|
|
77
103
|
...(this.options.clientInfo !== undefined ? { clientInfo: this.options.clientInfo } : {}),
|
|
78
104
|
}, signal);
|
|
79
105
|
if (result.protocolVersion !== ACP_PROTOCOL_VERSION)
|
|
@@ -122,16 +148,24 @@ export class AcpClient {
|
|
|
122
148
|
setMode(sessionId, modeId) {
|
|
123
149
|
return this.peer.request(ACP_METHODS.sessionSetMode, { sessionId, modeId });
|
|
124
150
|
}
|
|
125
|
-
/**
|
|
151
|
+
/** 改一个会话配置项(如模型);答复是全部配置项的新状态。 */
|
|
152
|
+
setConfigOption(sessionId, configId, value) {
|
|
153
|
+
return this.peer.request(ACP_METHODS.sessionSetConfigOption, { sessionId, configId, value });
|
|
154
|
+
}
|
|
155
|
+
/** 协议级取消:发通知,并让挂起的权限请求回 cancelled、挂起的 elicitation 回 cancel。 */
|
|
126
156
|
async cancel(sessionId) {
|
|
127
157
|
for (const controller of this.pendingPermissions.get(sessionId) ?? [])
|
|
128
158
|
controller.abort();
|
|
159
|
+
for (const controller of this.pendingElicitations.get(sessionId) ?? [])
|
|
160
|
+
controller.abort();
|
|
129
161
|
await this.peer.notify(ACP_METHODS.sessionCancel, { sessionId });
|
|
130
162
|
}
|
|
131
163
|
close() {
|
|
132
164
|
this.peer.close();
|
|
133
165
|
}
|
|
134
166
|
async onRequest(method, params, connection) {
|
|
167
|
+
if (method === ACP_METHODS.elicitationCreate && this.options.onElicitation !== undefined)
|
|
168
|
+
return this.onElicitation(params, connection);
|
|
135
169
|
if (method !== ACP_METHODS.requestPermission)
|
|
136
170
|
throw new RpcError(RPC_ERRORS.methodNotFound, `client does not support ${method}`);
|
|
137
171
|
const request = params;
|
|
@@ -168,4 +202,34 @@ export class AcpClient {
|
|
|
168
202
|
set.delete(controller);
|
|
169
203
|
}
|
|
170
204
|
}
|
|
205
|
+
async onElicitation(params, connection) {
|
|
206
|
+
const request = params;
|
|
207
|
+
if (request === null || typeof request !== "object" || typeof request.message !== "string")
|
|
208
|
+
throw new RpcError(RPC_ERRORS.invalidParams, "invalid elicitation/create params");
|
|
209
|
+
const handler = this.options.onElicitation;
|
|
210
|
+
const key = typeof request.sessionId === "string" ? request.sessionId : "";
|
|
211
|
+
const controller = new AbortController();
|
|
212
|
+
const set = this.pendingElicitations.get(key) ?? new Set();
|
|
213
|
+
set.add(controller);
|
|
214
|
+
this.pendingElicitations.set(key, set);
|
|
215
|
+
const signal = AbortSignal.any([controller.signal, connection]);
|
|
216
|
+
try {
|
|
217
|
+
const answer = await Promise.race([
|
|
218
|
+
handler(request, signal),
|
|
219
|
+
new Promise((resolve) => {
|
|
220
|
+
const cancelled = () => resolve({ action: "cancel" });
|
|
221
|
+
if (signal.aborted)
|
|
222
|
+
cancelled();
|
|
223
|
+
else
|
|
224
|
+
signal.addEventListener("abort", cancelled, { once: true });
|
|
225
|
+
}),
|
|
226
|
+
]);
|
|
227
|
+
if (signal.aborted)
|
|
228
|
+
return { action: "cancel" };
|
|
229
|
+
return elicitationAnswer(answer);
|
|
230
|
+
}
|
|
231
|
+
finally {
|
|
232
|
+
set.delete(controller);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
171
235
|
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* 假 ACP Agent 的进程入口:`node dist/drivers/acp/testing/fake-agent-main.js [--minimal]`。[W5-E]
|
|
2
|
+
* 假 ACP Agent 的进程入口:`node dist/drivers/acp/testing/fake-agent-main.js [--minimal] [--config-options]`。[W5-E]
|
|
3
3
|
* 行为见 fake-agent.ts;stdin 结束即退出。
|
|
4
4
|
*/
|
|
5
5
|
import { runFakeAcpAgent } from "./fake-agent.js";
|
|
6
6
|
const minimal = process.argv.includes("--minimal");
|
|
7
|
-
|
|
7
|
+
const configOptions = process.argv.includes("--config-options");
|
|
8
|
+
void runFakeAcpAgent(process.stdin, process.stdout, { minimal, configOptions }).then(() => process.exit(0));
|
|
@@ -13,6 +13,13 @@
|
|
|
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
|
+
*
|
|
21
|
+
* `configOptions: true`(可执行入口 `--config-options`):开会话答一个 `model` 配置项(`small`,分组
|
|
22
|
+
* `big` 里有 `large`),`session/set_config_option` 可改;缺省不答,线路与之前相同。
|
|
16
23
|
*
|
|
17
24
|
* 会话:`session/new` 发 `fake-<n>`;`session/resume` / `session/load` 接受任何 `fake-` 开头的 id
|
|
18
25
|
* (load 先回放一条用户消息与一条回复);`session/list` 列本进程建过的会话。
|
|
@@ -22,6 +29,8 @@ export interface FakeAcpAgentOptions {
|
|
|
22
29
|
name?: string;
|
|
23
30
|
/** 不声明 resume / list / close / loadSession(测降级路径)。 */
|
|
24
31
|
minimal?: boolean;
|
|
32
|
+
/** 开会话答 `configOptions`(一个 `model` 选择项)并接 `session/set_config_option`。 */
|
|
33
|
+
configOptions?: boolean;
|
|
25
34
|
}
|
|
26
35
|
/** 在给定流上跑假 Agent;输入流结束时 resolve。 */
|
|
27
36
|
export declare function runFakeAcpAgent(input: NodeJS.ReadableStream, output: NodeJS.WritableStream, options?: FakeAcpAgentOptions): Promise<void>;
|
|
@@ -13,16 +13,38 @@
|
|
|
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
|
+
*
|
|
21
|
+
* `configOptions: true`(可执行入口 `--config-options`):开会话答一个 `model` 配置项(`small`,分组
|
|
22
|
+
* `big` 里有 `large`),`session/set_config_option` 可改;缺省不答,线路与之前相同。
|
|
16
23
|
*
|
|
17
24
|
* 会话:`session/new` 发 `fake-<n>`;`session/resume` / `session/load` 接受任何 `fake-` 开头的 id
|
|
18
25
|
* (load 先回放一条用户消息与一条回复);`session/list` 列本进程建过的会话。
|
|
19
26
|
*/
|
|
27
|
+
import { createHash } from "node:crypto";
|
|
20
28
|
import { ACP_METHODS, ACP_PROTOCOL_VERSION, RPC_ERRORS } from "../types.js";
|
|
21
29
|
import { JsonRpcPeer, RpcError } from "../../jsonrpc.js";
|
|
22
30
|
const MODES = [
|
|
23
31
|
{ id: "default", name: "Default" },
|
|
24
32
|
{ id: "plan", name: "Plan" },
|
|
25
33
|
];
|
|
34
|
+
const MODELS = [
|
|
35
|
+
{ value: "small", name: "Small" },
|
|
36
|
+
{ group: "big", name: "Big", options: [{ value: "large", name: "Large", description: "slow" }] },
|
|
37
|
+
];
|
|
38
|
+
const MODEL_VALUES = ["small", "large"];
|
|
39
|
+
/** 一个表单:必填的颜色(枚举)与可选的数量。 */
|
|
40
|
+
const ELICIT_SCHEMA = {
|
|
41
|
+
type: "object",
|
|
42
|
+
properties: {
|
|
43
|
+
color: { type: "string", enum: ["red", "blue"] },
|
|
44
|
+
count: { type: "integer", minimum: 1 },
|
|
45
|
+
},
|
|
46
|
+
required: ["color"],
|
|
47
|
+
};
|
|
26
48
|
function textOf(blocks) {
|
|
27
49
|
return blocks.map((b) => (b.type === "text" ? b.text : `[${b.type}]`)).join("");
|
|
28
50
|
}
|
|
@@ -37,6 +59,20 @@ function waitAbort(signal) {
|
|
|
37
59
|
export function runFakeAcpAgent(input, output, options = {}) {
|
|
38
60
|
const sessions = new Map();
|
|
39
61
|
let counter = 0;
|
|
62
|
+
/** 客户端在 initialize 里声明了 elicitation。 */
|
|
63
|
+
let canElicit = false;
|
|
64
|
+
const configOf = (s) => [
|
|
65
|
+
{
|
|
66
|
+
id: "model",
|
|
67
|
+
name: "Model",
|
|
68
|
+
category: "model",
|
|
69
|
+
type: "select",
|
|
70
|
+
currentValue: s.model,
|
|
71
|
+
options: MODELS,
|
|
72
|
+
},
|
|
73
|
+
];
|
|
74
|
+
/** 开会话答复里的 `configOptions`(关着时不加这个键)。 */
|
|
75
|
+
const config = (s) => options.configOptions === true ? { configOptions: configOf(s) } : {};
|
|
40
76
|
const update = (sessionId, value) => peer.notify(ACP_METHODS.sessionUpdate, { sessionId, update: value });
|
|
41
77
|
const session = (id) => {
|
|
42
78
|
const found = typeof id === "string" ? sessions.get(id) : undefined;
|
|
@@ -45,7 +81,7 @@ export function runFakeAcpAgent(input, output, options = {}) {
|
|
|
45
81
|
return found;
|
|
46
82
|
};
|
|
47
83
|
const open = (id, cwd) => {
|
|
48
|
-
const created = { id, cwd, mode: "default", turns: 0 };
|
|
84
|
+
const created = { id, cwd, mode: "default", model: "small", turns: 0 };
|
|
49
85
|
sessions.set(id, created);
|
|
50
86
|
return created;
|
|
51
87
|
};
|
|
@@ -75,6 +111,37 @@ export function runFakeAcpAgent(input, output, options = {}) {
|
|
|
75
111
|
});
|
|
76
112
|
if (text.includes("[refuse]"))
|
|
77
113
|
return done("refusal");
|
|
114
|
+
const say = (line) => update(s.id, { sessionUpdate: "agent_message_chunk", content: { type: "text", text: line } });
|
|
115
|
+
if (text.includes("[elicit]")) {
|
|
116
|
+
if (!canElicit) {
|
|
117
|
+
await say("elicit: unsupported");
|
|
118
|
+
return done("end_turn");
|
|
119
|
+
}
|
|
120
|
+
let answer;
|
|
121
|
+
try {
|
|
122
|
+
answer = await peer.request(ACP_METHODS.elicitationCreate, {
|
|
123
|
+
sessionId: s.id,
|
|
124
|
+
message: "Pick a color",
|
|
125
|
+
requestedSchema: ELICIT_SCHEMA,
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
catch {
|
|
129
|
+
return done("cancelled");
|
|
130
|
+
}
|
|
131
|
+
await say(`elicit: ${answer.action} ${JSON.stringify(answer.content ?? null)}`);
|
|
132
|
+
return done(answer.action === "cancel" ? "cancelled" : "end_turn");
|
|
133
|
+
}
|
|
134
|
+
if (text.includes("[model]")) {
|
|
135
|
+
await say(`model ${options.configOptions === true ? s.model : "none"}`);
|
|
136
|
+
return done("end_turn");
|
|
137
|
+
}
|
|
138
|
+
const env = /\[env ([A-Z0-9_]+)\]/.exec(text);
|
|
139
|
+
if (env !== null) {
|
|
140
|
+
const value = process.env[env[1]];
|
|
141
|
+
const digest = value === undefined ? "absent" : createHash("sha256").update(value).digest("hex");
|
|
142
|
+
await say(`env ${env[1]} ${digest}`);
|
|
143
|
+
return done("end_turn");
|
|
144
|
+
}
|
|
78
145
|
if (text.includes("[slow]")) {
|
|
79
146
|
await waitAbort(turn.signal);
|
|
80
147
|
return done("cancelled");
|
|
@@ -141,7 +208,9 @@ export function runFakeAcpAgent(input, output, options = {}) {
|
|
|
141
208
|
async onRequest(method, raw) {
|
|
142
209
|
const params = (raw ?? {});
|
|
143
210
|
switch (method) {
|
|
144
|
-
case ACP_METHODS.initialize:
|
|
211
|
+
case ACP_METHODS.initialize: {
|
|
212
|
+
const capabilities = params["clientCapabilities"];
|
|
213
|
+
canElicit = capabilities?.["elicitation"] != null;
|
|
145
214
|
return {
|
|
146
215
|
protocolVersion: ACP_PROTOCOL_VERSION,
|
|
147
216
|
agentCapabilities: options.minimal
|
|
@@ -154,12 +223,17 @@ export function runFakeAcpAgent(input, output, options = {}) {
|
|
|
154
223
|
authMethods: [],
|
|
155
224
|
agentInfo: { name: options.name ?? "fake-acp-agent", version: "1.0.0" },
|
|
156
225
|
};
|
|
226
|
+
}
|
|
157
227
|
case ACP_METHODS.sessionNew: {
|
|
158
228
|
counter += 1;
|
|
159
229
|
const s = open(`fake-${counter}`, String(params["cwd"] ?? ""));
|
|
160
230
|
if (options.minimal)
|
|
161
|
-
return { sessionId: s.id };
|
|
162
|
-
return {
|
|
231
|
+
return { sessionId: s.id, ...config(s) };
|
|
232
|
+
return {
|
|
233
|
+
sessionId: s.id,
|
|
234
|
+
modes: { currentModeId: s.mode, availableModes: MODES },
|
|
235
|
+
...config(s),
|
|
236
|
+
};
|
|
163
237
|
}
|
|
164
238
|
case ACP_METHODS.sessionResume:
|
|
165
239
|
case ACP_METHODS.sessionLoad: {
|
|
@@ -177,7 +251,7 @@ export function runFakeAcpAgent(input, output, options = {}) {
|
|
|
177
251
|
content: { type: "text", text: "echo: earlier" },
|
|
178
252
|
});
|
|
179
253
|
}
|
|
180
|
-
return { modes: { currentModeId: s.mode, availableModes: MODES } };
|
|
254
|
+
return { modes: { currentModeId: s.mode, availableModes: MODES }, ...config(s) };
|
|
181
255
|
}
|
|
182
256
|
case ACP_METHODS.sessionList:
|
|
183
257
|
return {
|
|
@@ -193,6 +267,18 @@ export function runFakeAcpAgent(input, output, options = {}) {
|
|
|
193
267
|
await update(s.id, { sessionUpdate: "current_mode_update", currentModeId: s.mode });
|
|
194
268
|
return {};
|
|
195
269
|
}
|
|
270
|
+
case ACP_METHODS.sessionSetConfigOption: {
|
|
271
|
+
if (options.configOptions !== true)
|
|
272
|
+
throw new RpcError(RPC_ERRORS.methodNotFound, `method not found: ${method}`);
|
|
273
|
+
const s = session(params["sessionId"]);
|
|
274
|
+
if (params["configId"] !== "model")
|
|
275
|
+
throw new RpcError(RPC_ERRORS.invalidParams, `unknown config: ${String(params["configId"])}`);
|
|
276
|
+
const value = String(params["value"]);
|
|
277
|
+
if (!MODEL_VALUES.includes(value))
|
|
278
|
+
throw new RpcError(RPC_ERRORS.invalidParams, `unknown model: ${value}`);
|
|
279
|
+
s.model = value;
|
|
280
|
+
return { configOptions: configOf(s) };
|
|
281
|
+
}
|
|
196
282
|
case ACP_METHODS.sessionPrompt:
|
|
197
283
|
return prompt(raw);
|
|
198
284
|
default:
|
|
@@ -2,8 +2,8 @@
|
|
|
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),
|
|
5
|
+
* `initialize`、`session/new|load|resume|list|close`、`session/prompt|cancel|set_mode|set_config_option`、
|
|
6
|
+
* `session/update`(含 `usage_update`)、`session/request_permission`、`elicitation/create`。字段名与规范一致(camelCase),
|
|
7
7
|
* 未知字段一律保留不报错;`_meta` 不解释。
|
|
8
8
|
*
|
|
9
9
|
* 不声明 `fs` / `terminal` 客户端能力(与 Armadra Q5 一致):外部 Agent 自己读写、自己跑命令,
|
|
@@ -22,8 +22,10 @@ export declare const ACP_METHODS: {
|
|
|
22
22
|
readonly sessionPrompt: "session/prompt";
|
|
23
23
|
readonly sessionCancel: "session/cancel";
|
|
24
24
|
readonly sessionSetMode: "session/set_mode";
|
|
25
|
+
readonly sessionSetConfigOption: "session/set_config_option";
|
|
25
26
|
readonly sessionUpdate: "session/update";
|
|
26
27
|
readonly requestPermission: "session/request_permission";
|
|
28
|
+
readonly elicitationCreate: "elicitation/create";
|
|
27
29
|
};
|
|
28
30
|
/** JSON-RPC 错误码(规范沿用 JSON-RPC 2.0;`-32000` 为 ACP 的 auth_required)。 */
|
|
29
31
|
export declare const RPC_ERRORS: {
|
|
@@ -76,6 +78,8 @@ export interface AcpClientCapabilities {
|
|
|
76
78
|
writeTextFile?: boolean;
|
|
77
79
|
};
|
|
78
80
|
terminal?: boolean;
|
|
81
|
+
/** 能接 `elicitation/create`(存在即支持)。 */
|
|
82
|
+
elicitation?: Record<string, unknown>;
|
|
79
83
|
}
|
|
80
84
|
export interface AcpInitializeParams {
|
|
81
85
|
protocolVersion: number;
|
|
@@ -149,6 +153,7 @@ export interface AcpNewSessionParams {
|
|
|
149
153
|
export interface AcpNewSessionResult {
|
|
150
154
|
sessionId: string;
|
|
151
155
|
modes?: AcpSessionModeState | null;
|
|
156
|
+
configOptions?: AcpSessionConfigOption[] | null;
|
|
152
157
|
}
|
|
153
158
|
export interface AcpLoadSessionParams {
|
|
154
159
|
sessionId: string;
|
|
@@ -157,6 +162,84 @@ export interface AcpLoadSessionParams {
|
|
|
157
162
|
}
|
|
158
163
|
export interface AcpLoadSessionResult {
|
|
159
164
|
modes?: AcpSessionModeState | null;
|
|
165
|
+
configOptions?: AcpSessionConfigOption[] | null;
|
|
166
|
+
}
|
|
167
|
+
/** 配置项里一个可选值。 */
|
|
168
|
+
export interface AcpConfigSelectOption {
|
|
169
|
+
value: string;
|
|
170
|
+
name: string;
|
|
171
|
+
description?: string | null;
|
|
172
|
+
}
|
|
173
|
+
/** 规范允许把可选值分组。 */
|
|
174
|
+
export interface AcpConfigSelectGroup {
|
|
175
|
+
group: string;
|
|
176
|
+
name: string;
|
|
177
|
+
options: AcpConfigSelectOption[];
|
|
178
|
+
}
|
|
179
|
+
/** `session/new|load|resume` 答的 `configOptions[]` 的一项(目前规范只有 `select`)。 */
|
|
180
|
+
export interface AcpSessionConfigOption {
|
|
181
|
+
id: string;
|
|
182
|
+
name: string;
|
|
183
|
+
description?: string | null;
|
|
184
|
+
/** `mode` / `model` / `thought_level`,或 Agent 自己的。 */
|
|
185
|
+
category?: string | null;
|
|
186
|
+
type: "select" | (string & {});
|
|
187
|
+
currentValue: string;
|
|
188
|
+
options: (AcpConfigSelectOption | AcpConfigSelectGroup)[];
|
|
189
|
+
}
|
|
190
|
+
export interface AcpSetConfigOptionParams {
|
|
191
|
+
sessionId: string;
|
|
192
|
+
configId: string;
|
|
193
|
+
value: string;
|
|
194
|
+
}
|
|
195
|
+
/** 答复带全部配置项的新状态(改一项可能连带别的)。 */
|
|
196
|
+
export interface AcpSetConfigOptionResult {
|
|
197
|
+
configOptions?: AcpSessionConfigOption[] | null;
|
|
198
|
+
}
|
|
199
|
+
/** 表单里的一个字段:扁平的原始类型,规范只允许这几种。 */
|
|
200
|
+
export type AcpElicitationField = {
|
|
201
|
+
type: "string";
|
|
202
|
+
title?: string;
|
|
203
|
+
description?: string;
|
|
204
|
+
enum?: string[];
|
|
205
|
+
enumNames?: string[];
|
|
206
|
+
format?: string;
|
|
207
|
+
minLength?: number;
|
|
208
|
+
maxLength?: number;
|
|
209
|
+
default?: string;
|
|
210
|
+
} | {
|
|
211
|
+
type: "number" | "integer";
|
|
212
|
+
title?: string;
|
|
213
|
+
description?: string;
|
|
214
|
+
minimum?: number;
|
|
215
|
+
maximum?: number;
|
|
216
|
+
default?: number;
|
|
217
|
+
} | {
|
|
218
|
+
type: "boolean";
|
|
219
|
+
title?: string;
|
|
220
|
+
description?: string;
|
|
221
|
+
default?: boolean;
|
|
222
|
+
};
|
|
223
|
+
export interface AcpElicitationSchema {
|
|
224
|
+
type: "object";
|
|
225
|
+
properties: Record<string, AcpElicitationField>;
|
|
226
|
+
required?: string[];
|
|
227
|
+
}
|
|
228
|
+
/** Agent 发来的 `elicitation/create` 参数;未知字段保留。 */
|
|
229
|
+
export interface AcpElicitationParams {
|
|
230
|
+
sessionId?: string;
|
|
231
|
+
message: string;
|
|
232
|
+
/** `form`(缺省)或 `url`。 */
|
|
233
|
+
mode?: "form" | "url" | (string & {});
|
|
234
|
+
requestedSchema?: AcpElicitationSchema;
|
|
235
|
+
url?: string;
|
|
236
|
+
[key: string]: unknown;
|
|
237
|
+
}
|
|
238
|
+
export type AcpElicitationAction = "accept" | "decline" | "cancel";
|
|
239
|
+
export interface AcpElicitationResult {
|
|
240
|
+
action: AcpElicitationAction;
|
|
241
|
+
/** 只随 `accept`。 */
|
|
242
|
+
content?: Record<string, string | number | boolean>;
|
|
160
243
|
}
|
|
161
244
|
export type AcpResumeSessionParams = AcpLoadSessionParams;
|
|
162
245
|
export type AcpResumeSessionResult = AcpLoadSessionResult;
|
|
@@ -2,8 +2,8 @@
|
|
|
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),
|
|
5
|
+
* `initialize`、`session/new|load|resume|list|close`、`session/prompt|cancel|set_mode|set_config_option`、
|
|
6
|
+
* `session/update`(含 `usage_update`)、`session/request_permission`、`elicitation/create`。字段名与规范一致(camelCase),
|
|
7
7
|
* 未知字段一律保留不报错;`_meta` 不解释。
|
|
8
8
|
*
|
|
9
9
|
* 不声明 `fs` / `terminal` 客户端能力(与 Armadra Q5 一致):外部 Agent 自己读写、自己跑命令,
|
|
@@ -21,8 +21,10 @@ export const ACP_METHODS = {
|
|
|
21
21
|
sessionPrompt: "session/prompt",
|
|
22
22
|
sessionCancel: "session/cancel",
|
|
23
23
|
sessionSetMode: "session/set_mode",
|
|
24
|
+
sessionSetConfigOption: "session/set_config_option",
|
|
24
25
|
sessionUpdate: "session/update",
|
|
25
26
|
requestPermission: "session/request_permission",
|
|
27
|
+
elicitationCreate: "elicitation/create",
|
|
26
28
|
};
|
|
27
29
|
/** JSON-RPC 错误码(规范沿用 JSON-RPC 2.0;`-32000` 为 ACP 的 auth_required)。 */
|
|
28
30
|
export const RPC_ERRORS = {
|
package/docs/acp.md
CHANGED
|
@@ -67,12 +67,19 @@ stdin 关闭后等已开始的运行结束再退出(0);SIGINT / SIGTERM
|
|
|
67
67
|
- 开会话(`newSession` / `resumeSession` / `loadSession`)的 `mcpServers` 缺省为空数组;宿主可传第三个参数
|
|
68
68
|
`{ mcpServers }`(如 stdio 的 `{ name, command, args, env: [{ name, value }] }`),原样转发给 Agent。
|
|
69
69
|
`AcpClient.features.mcpServers === true` 表示支持(旧版没有 `features`)。ama 自己作客户端时仍不传。
|
|
70
|
+
- `elicitation/create`(Agent 向人要结构化输入):构造参数给了 `onElicitation(params, signal)` 才在 `initialize` 声明
|
|
71
|
+
`clientCapabilities.elicitation` 并接这个请求;没给时不声明、请求回 method not found(与旧版相同)。答复收成
|
|
72
|
+
`{ action: "accept" | "decline" | "cancel", content? }`(`content` 只随 accept,不认识的动作当 cancel);`cancel(sessionId)`
|
|
73
|
+
与连接关闭时挂起的一律回 `{ action: "cancel" }`。ama 自己作客户端时不给处理器,从不替人填表。
|
|
74
|
+
- 会话配置项:开会话(new / load / resume)答的 `configOptions` 原样交回;`setConfigOption(sessionId, configId, value)` 发
|
|
75
|
+
`session/set_config_option`,答复是全部配置项的新状态。
|
|
76
|
+
- `AcpClient.features`:`{ mcpServers, elicitation, configOptions }`,宿主据此做特性检测。
|
|
70
77
|
|
|
71
78
|
`AcpDriver` 在客户端之上实现驱动契约(`AgentDriver`):续接优先 `session/resume`,其次 `session/load`(回放的历史丢弃),都不支持就新开并提示;按 ama 模式 `session/set_mode`,只读模式找不到对应模式 id 时拒绝启动。
|
|
72
79
|
|
|
73
80
|
## 测试替身
|
|
74
81
|
|
|
75
|
-
`runFakeAcpAgent(input, output)` 是进程内的假 ACP Agent,`fakeAcpAgentPath()` 是它的可执行入口(`node <path> [--minimal]`)。行为由提示里的标记决定:`[permission]`(请求权限,四个选项)、`[slow]`(等到 cancel)、`[plan]`、`[think]`、`[refuse]`,其余回 `echo: <文本>`。`--minimal` 不声明 resume / load / list / close
|
|
82
|
+
`runFakeAcpAgent(input, output)` 是进程内的假 ACP Agent,`fakeAcpAgentPath()` 是它的可执行入口(`node <path> [--minimal]`)。行为由提示里的标记决定:`[permission]`(请求权限,四个选项)、`[slow]`(等到 cancel)、`[plan]`、`[think]`、`[refuse]`,其余回 `echo: <文本>`。`--minimal` 不声明 resume / load / list / close,也不给模式,用来测降级路径。另有 `[elicit]`(发 `elicitation/create`,客户端没声明能力时回 `elicit: unsupported`)、`[model]`、`[env NAME]`(只回值的 sha256)三个标记;`--config-options`(进程内 `{ configOptions: true }`)让开会话答一个 `model` 配置项并接 `session/set_config_option`。
|
|
76
83
|
|
|
77
84
|
黄金记录在 `test/fixtures/acp/`:`driver-{allow,reject,cancel}.jsonl`(ama 驱动假 Agent 的三条路径)与 `mode-prompt.jsonl`(`ama --mode acp` 一轮往返)。`UPDATE_GOLDEN=1` 重写。
|
|
78
85
|
|