diff --git a/README.md b/README.md index 92a82fa..d82b5d1 100644 --- a/README.md +++ b/README.md @@ -87,6 +87,12 @@ automatically. Point `ZCODE_BIN` at the bundled `zcode.cjs`: | `ZCODE_BASE_URL` | _(from config)_ | Override the provider base URL | | `ZCODE_ACP_DEBUG` | _(unset)_ | Set to `1` to enable verbose diagnostic logs (event flow, probe loops, status updates). Default is quiet — only warnings (backend pipe errors, command/permission failures, lock timeouts) are emitted. Enable this when diagnosing bridge issues; the logs appear in `Zed.log` prefixed with `[zcode-acp]`. | +## ACP Registry + +This server is compatible with the [ACP Registry](https://agentclientprotocol.com/get-started/registry). It advertises a single `agent`-type auth method at `initialize` time — the GLM API key is read from `~/.zcode/v2/config.json` by the ZCode backend, so **no editor-side credentials are required**. + +The registry submission assets live under [`registry/zcode-acp-server/`](registry/zcode-acp-server/) (`agent.json` + `icon.svg`). Once the package is published to npm, copy that directory into a fork of [`agentclientprotocol/registry`](https://github.com/agentclientprotocol/registry) and open a PR — the CI validates the `agent.json` schema, icon, and that `initialize` returns a non-empty `authMethods`. + ## Develop ```bash diff --git a/README.zh-CN.md b/README.zh-CN.md index 125b78d..5de0097 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -86,6 +86,12 @@ ZCode CLI 内置于桌面应用中,默认不会加到 `PATH`。用 `ZCODE_BIN` | `ZCODE_BASE_URL` | _(来自 config) | 覆盖 provider 的 base URL | | `ZCODE_ACP_DEBUG` | _(未设置) | 设为 `1` 可开启详细诊断日志(事件流、探测循环、状态更新)。默认安静——只输出警告类日志(后端管道错误、命令/权限失败、锁等待超时)。诊断桥接问题时开启;日志出现在 `Zed.log` 中,前缀为 `[zcode-acp]`。 | +## ACP Registry + +本服务端兼容 [ACP Registry](https://agentclientprotocol.com/get-started/registry)。它在 `initialize` 时声明一个 `agent` 类型的认证方法——GLM API key 由 ZCode 后端从 `~/.zcode/v2/config.json` 读取,**编辑器侧无需配置任何凭据**。 + +Registry 提交资产位于 [`registry/zcode-acp-server/`](registry/zcode-acp-server/)(`agent.json` + `icon.svg`)。包发布到 npm 后,将该目录复制到 [`agentclientprotocol/registry`](https://github.com/agentclientprotocol/registry) 的 fork 中并提 PR——CI 会校验 `agent.json` schema、图标,以及 `initialize` 返回的 `authMethods` 非空。 + ## 开发 ```bash diff --git a/registry/zcode-acp-server/agent.json b/registry/zcode-acp-server/agent.json new file mode 100644 index 0000000..b09078c --- /dev/null +++ b/registry/zcode-acp-server/agent.json @@ -0,0 +1,16 @@ +{ + "id": "zcode-acp-server", + "name": "ZCode", + "version": "0.1.0", + "description": "Standalone ACP server bridging the headless ZCode app-server (GLM-5.2) to editors like Zed and JetBrains. Supports streaming, tool calls, session fork/resume, mode switching, and reads GLM credentials locally — no editor-side API key required.", + "repository": "https://github.com/william0wang/zcode-acp", + "website": "https://github.com/william0wang/zcode-acp", + "authors": ["William Wang"], + "license": "Apache-2.0", + "icon": "icon.svg", + "distribution": { + "npx": { + "package": "zcode-acp-server@0.1.0" + } + } +} diff --git a/registry/zcode-acp-server/icon.svg b/registry/zcode-acp-server/icon.svg new file mode 100644 index 0000000..1a012da --- /dev/null +++ b/registry/zcode-acp-server/icon.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/server.ts b/src/server.ts index fa50f28..2c0ad99 100644 --- a/src/server.ts +++ b/src/server.ts @@ -114,9 +114,19 @@ export class ZcodeAcpServer { promptCapabilities: { image: false, audio: false, embeddedContext: false }, mcpCapabilities: { http: false, sse: false }, sessionCapabilities: { list: {}, resume: {}, fork: {} }, - auth: {}, }, - authMethods: [], + // The GLM API key is read from ~/.zcode/v2/config.json by the ZCode + // backend subprocess; the editor never needs to supply credentials. + // Declared as AuthMethodAgent (no `type` field → defaults to "agent"), + // which the ACP registry CI accepts as "agent self-handles auth". + authMethods: [ + { + id: "zcode-credentials", + name: "ZCode built-in credentials", + description: + "Reads the GLM API key from ~/.zcode/v2/config.json managed by the ZCode desktop app. No editor-side credentials required.", + }, + ], }; } } diff --git a/tests/server.test.ts b/tests/server.test.ts new file mode 100644 index 0000000..6cbfa02 --- /dev/null +++ b/tests/server.test.ts @@ -0,0 +1,80 @@ +/** + * server.ts tests — verify the `initialize` response shape, with focus on the + * ACP registry's authentication validation. + * + * The registry CI (verify_agents.py --auth-check) spawns the server with an + * isolated HOME (no credentials) and asserts that `initialize` returns a + * non-empty `authMethods` array where at least one method has type "agent" or + * "terminal" (type omitted defaults to "agent"). An empty array is rejected. + * https://github.com/agentclientprotocol/registry/blob/main/.github/workflows/client.py + */ + +import type * as acp from "@agentclientprotocol/sdk"; +import { describe, expect, it } from "vitest"; + +import { ZcodeAcpServer } from "../src/server.js"; + +/** Minimal initialize params matching the ACP handshake shape. */ +function initParams(): acp.InitializeRequest { + return { + protocolVersion: 1, + clientInfo: { name: "test-client", version: "0.0.0" }, + clientCapabilities: {}, + } as acp.InitializeRequest; +} + +describe("ZcodeAcpServer.initialize", () => { + it("returns protocol version 1 and agent info", async () => { + const server = new ZcodeAcpServer(); + const resp = await server.initialize(initParams()); + expect(resp.protocolVersion).toBe(1); + expect(resp.agentInfo.name).toBe("zcode-acp-server"); + }); + + it("advertises loadSession + list/resume/fork session capabilities", async () => { + const server = new ZcodeAcpServer(); + const resp = await server.initialize(initParams()); + expect(resp.agentCapabilities?.loadSession).toBe(true); + expect(resp.agentCapabilities?.sessionCapabilities?.list).toBeDefined(); + expect(resp.agentCapabilities?.sessionCapabilities?.resume).toBeDefined(); + expect(resp.agentCapabilities?.sessionCapabilities?.fork).toBeDefined(); + }); + + // Registry CI gate: rejects `authMethods: []` with "No authMethods in response". + it("returns a non-empty authMethods array", async () => { + const server = new ZcodeAcpServer(); + const resp = await server.initialize(initParams()); + expect(resp.authMethods).toBeDefined(); + expect(resp.authMethods!.length).toBeGreaterThan(0); + }); + + // Registry CI gate: ≥1 method must be recognisable as agent/terminal type. + // AuthMethodAgent has no `type` field — omitted defaults to "agent". + it("advertises at least one agent-type auth method (registry requirement)", async () => { + const server = new ZcodeAcpServer(); + const resp = await server.initialize(initParams()); + const methods = resp.authMethods!.map((m) => { + const obj = m as { type?: string }; + return obj.type ?? "agent"; // omitted type defaults to "agent" + }); + expect(methods).toContain("agent"); + }); + + it("the auth method has the required id + name fields", async () => { + const server = new ZcodeAcpServer(); + const resp = await server.initialize(initParams()); + for (const m of resp.authMethods!) { + const method = m as { id?: string; name?: string }; + expect(method.id).toBeTruthy(); + expect(method.name).toBeTruthy(); + } + }); + + // The registry CI runs initialize with an empty HOME and no config.json — + // initialize must NOT eagerly spawn the backend or read credentials. + it("does not spawn the backend during initialize", async () => { + const server = new ZcodeAcpServer(); + await server.initialize(initParams()); + expect(server.backend).toBeNull(); + }); +});