diff --git a/README.md b/README.md index d93c2ef..e3fa62e 100644 --- a/README.md +++ b/README.md @@ -134,7 +134,7 @@ zcode --prompt "继续" --resume sess_xxxx | tool_call / tool_call_update(工具调用展示)| ✅ 实时 | | usage_update(token 用量)| ✅ | | agent_message_chunk(文本输出)| ✅ **真流式**(0.14.8+)| -| agent_thought_chunk(思考过程)| ✅ 流式(GLM-5-Turbo)| +| agent_thought_chunk(思考过程)| ✅ 流式(GLM-5-Turbo;该模型已于 App 3.12.3 时代由服务端从 coding-plan provider 下线)| > `session/prompt` 的 `prompt` 参数除标准 ACP ContentBlock[] 外,bridge 还兼容纯字符串与 `{"content": "..."}` 键别名(内部统一归一,zcode review P3-5)。 | plan(任务清单)| ⚠️ 代码就位,数据驱动 | @@ -163,7 +163,7 @@ ACP bridge 额外暴露了 ZCode 新版协议方法,供编辑器/脚本调用 | `session/compact` | 压缩对话上下文 | 0.14.8 | `{sessionId}` | | `session/steer` ❌ | turn 进行中追加指令(**0.16 已移除**) | 0.14.8 | `{sessionId, content}` | | `session/setThoughtLevel` | ⭐ 设置思考强度(实测 GLM-5.2: max/high/nothink,按模型不同) | 0.15.0 | `{sessionId, thoughtLevel}` | -| `session/updateRuntimeModelConfig` | 运行时覆盖会话模型配置 | 0.15.0 | `{sessionId, runtimeModel, applyModelSelection?}`(0.16 起 `runtimeModel.revision` 必填) | +| `session/updateRuntimeModelConfig` ❌ | 运行时覆盖会话模型配置 | 0.15.0 | `{sessionId, runtimeModel, applyModelSelection?}`(0.16 起 `runtimeModel.revision` 必填) | | `session/cancelBackgroundTask` | 取消后台 Bash 任务 | 0.14.8 | `{sessionId, taskId}` | | `session/rewindCascade` ❌ | 级联回退(与 rewind 同 schema,**0.16 已移除**) | 0.15.0 | `{sessionId, target?, scope?, expectedRevision?}` | | `session/setModel` | 切换会话模型 | 0.14.8 | `{sessionId, modelId}` | @@ -171,7 +171,7 @@ ACP bridge 额外暴露了 ZCode 新版协议方法,供编辑器/脚本调用 > ❌ **0.16 已移除**:`session/steer`、`session/rewind`、`session/rewindCascade` 已从 app-server 删除。steer 语义并入 `session/send`(turn 进行中发送即 steer);rewind 无协议替代,仅剩 slash 命令 `/rewind` 与 `rewind.triggered` 事件。0.16.1 上调用这些方法会收到 `-32601`。 > -> ℹ️ **0.16 schema 变更**:`session/updateRuntimeModelConfig` 在 0.16.1 仍存活(实测),但 schema 新要求 `runtimeModel.revision`(string)必填。 +> ℹ️ **0.16 schema 变更**:`session/updateRuntimeModelConfig` 在 0.16.1 仍存活(实测),但 schema 新要求 `runtimeModel.revision`(string)必填。App 3.12.3 的同号 0.16.5 构建已删除该方法(2026-09-17 实测后端返 -32601),桥透传时降级为「已移除」文案。 **workspace 级**(按工作区 `{workspacePath, workspaceKey}` 定位,不依赖 sessionId): @@ -186,6 +186,8 @@ ACP bridge 额外暴露了 ZCode 新版协议方法,供编辑器/脚本调用 | `workspace/removeModelProvider` | 移除模型供应商 | `{workspace, providerId, expectedWorkspaceRevision?}` | | `workspace/updateProviderRegistry` | 批量更新供应商注册表 | `{workspace, registry, includeWorkspaceState?}` | +> ❌ **App 3.12.3 的 0.16.5 构建已删**:除 `workspace/generateText`(存活)外,上表 7 个 workspace/* 方法均已从 app-server 删除(2026-09-17 实测后端 -32601,且未搬家到 session/ 命名空间);`updateInteractionPreferences` 同批删除。上表语义适用于 0.15.0 – App 3.10.2 的 0.16.5 构建。桥透传调用会收到「当前 ZCode 版本已移除该能力」降级文案(见下方降级行为)。 + **prompt 级**(提示词增强,App 3.3.0 引入;❌ **0.16 已全部移除**,无替代): | 扩展方法 | 作用 | params | @@ -221,7 +223,7 @@ PR 自动审查闸门守护进程(第 4 组件,experimental):常驻轮 **canonical model id = `~/.zcode/v2/config.json` 里 `models` 的 key 原样**(如 `GLM-5.3`),**不加 provider 前缀**。`shared/credentials.py`、MCP server、ACP bridge、agent-help 四处统一用原始 id。实测(0.16.1 时代)`zai/GLM-5.2` 前缀形式也兼容,但非 canonical,本项目不使用。 -模型面现状(0.16.5 实测):当前 enabled provider(`builtin:zai-coding-plan`)的 models 为 `GLM-5.3` / `GLM-5.3-Flash` / `GLM-5-Turbo`。 +模型面现状(App 3.12.3 的 0.16.5 构建实测,2026-09-17):当前 enabled provider(`builtin:zai-coding-plan`)的 models 为 `GLM-5.3` / `GLM-5.3-Flash`(`GLM-5-Turbo` 已由服务端移除;3.10.2 时代为三者)。 ### 凭证注入:显式环境变量优先 @@ -306,6 +308,7 @@ ACP bridge 侧另有一个 env(不在上两表,仅 ACP 用):`ZCODE_ACP_D | ZCode CLI 版本 | 支持情况 | ACP bridge 流式 | 扩展方法 | |:--------------:|:--------:|:---------------:|:--------:| +| **0.16.5**(App 3.12.3,同版本号构建漂移) | ✅ 完整(核心面) | **真流式**(事件驱动) | ✅ session/*(workspace/* 删 7/8 仅 generateText 存活;updateRuntimeModelConfig/updateInteractionPreferences 已删,透传优雅降级) | | **0.16.5**(App 3.10.2) | ✅ 完整 | **真流式**(事件驱动) | ✅ session/* + workspace/*(与 0.16.1 同面;`automation/*` 未实现不受其删除影响) | | **0.16.1**(App 3.6.5) | ✅ 完整 | **真流式**(事件驱动) | ✅ session/* + workspace/*(`steer`/`rewind*`/`prompt/enhance*` 已于 0.16 移除;`updateRuntimeModelConfig` 存活但 `runtimeModel.revision` 必填) | | **0.15.x**(App 3.5.x) | ✅ 完整 | **真流式**(事件驱动) | ✅ 全部(协议面同 0.15.0 行;App 功能面:3.5.2 内置网页应用、PDF 预览,见规格书 changelog) | @@ -316,13 +319,13 @@ ACP bridge 侧另有一个 env(不在上两表,仅 ACP 用):`ZCODE_ACP_D | **0.14.5 ~ 0.14.7** | ✅ 兼容 | 伪流式(自动降级轮询) | ❌(旧版协议未实现) | | **< 0.14.5** | ⚠️ 未测 | — | — | -> 注:CLI 版本号相同不代表协议面相同——`prompt/enhance` 是 App 3.3.0 引入的协议方法(CLI 同为 0.15.0,仅 App 3.3.0+ 的 app-server 支持),又于 0.16 整体移除,仅 0.15.0 + App ≥ 3.3.0 的组合可用。0.16.1(App 3.6.5)协议面大改——真正断点是反向调用必须应答、事件模型调整、删除 steer/rewind/enhance(信封去 `jsonrpc`/方法 rename/`deliveryKind` 必填同为协议事实,但桥对内本就用这套调用面),详见 [docs/upgrade-0.16.1-spec.md](docs/upgrade-0.16.1-spec.md)(含勘误)。0.16.5 已于 2026-09-01 全链路复测(协议面兼容、桥无需代码改动),详见 [docs/recheck-0.16.5.md](docs/recheck-0.16.5.md)。 +> 注:CLI 版本号相同不代表协议面相同——`prompt/enhance` 是 App 3.3.0 引入的协议方法(CLI 同为 0.15.0,仅 App 3.3.0+ 的 app-server 支持),又于 0.16 整体移除,仅 0.15.0 + App ≥ 3.3.0 的组合可用。0.16.1(App 3.6.5)协议面大改——真正断点是反向调用必须应答、事件模型调整、删除 steer/rewind/enhance(信封去 `jsonrpc`/方法 rename/`deliveryKind` 必填同为协议事实,但桥对内本就用这套调用面),详见 [docs/upgrade-0.16.1-spec.md](docs/upgrade-0.16.1-spec.md)(含勘误)。0.16.5 已于 2026-09-01 全链路复测(协议面兼容、桥无需代码改动),详见 [docs/recheck-0.16.5.md](docs/recheck-0.16.5.md)。App 3.12.3 的内嵌 CLI `--version` 仍为 0.16.5 但**构建内容漂移**(同号删了 workspace/* 7/8 等,`--version` 不再是唯一兼容性判据),已于 2026-09-17 复测,详见 [docs/recheck-3.12.3.md](docs/recheck-3.12.3.md)。 **降级行为**: - 轮询降级**仅限 legacy(< 0.16)协议模式**:旧版下 `session/subscribe` 不可用时自动切换到轮询 `session/read`(伪流式)。**0.16+ 不再自动降级**——新协议模式下 subscribe 失败直接报错 `-32603`("0.16+ 必须走事件订阅;轮询降级仅限旧协议模式")。 - 轮询(legacy)路径的失败检测有固有局限:该路径收不到 `turn.failed` 事件(projection/messages 无失败标志),turn 失败只能靠「status=idle 但本轮无任何实质输出(text/tool/patch)」的启发式检测,可能误报(成功但无实质输出的 turn 被判失败)或漏报(失败前已吐出部分内容的 turn 被当成功);0.16+ 事件路径无此局限(`turn.failed` 终止帧已能正确判失败)。 - 扩展方法在旧版 ZCode 上会透传后端错误(`-32603 zcode failed: ...`),不影响标准 ACP 方法(new/prompt/cancel/list/resume)。例如在 App 3.2.x 上调用 `prompt/enhance`(3.3.0 新增)会得到 `-32603`,调用方应据此做版本判断。 -- 调用 0.16 已删除的方法(`session/steer`、`session/rewind*`、`prompt/enhance*` 等)时,后端返回 `-32601 Method not found`,bridge 会映射为明确错误文案("当前 ZCode 版本已移除该能力 (<方法名>); 该 ZCode 版本不支持此能力"),而非原始透传,调用方可据此做版本判断。 +- 调用 ZCode 已删除的方法(0.16 删的 `session/steer`、`session/rewind*`、`prompt/enhance*`,App 3.12.3 的同号 0.16.5 构建删的 workspace/* 7 个、`session/updateRuntimeModelConfig` 等)时,后端返回 `-32601 Method not found`,bridge 会映射为明确错误文案("当前 ZCode 版本已移除该能力 (<方法名>); 该 ZCode 版本不支持此能力",错误码保持 `-32601`),而非原始透传,调用方可据此做版本判断。判定已泛化为「透传/扩展方法后端 -32601 一律翻译」,不依赖硬编码方法清单(未来再删方法无需改桥);核心协议路径(create/send/stop/list/resume)的 -32601 属深度异常,保留原始错误透传。 ### MCP 规范兼容性说明 @@ -359,10 +362,10 @@ cp -r skills/zcode-bridge-guide ~/.zcode/skills/ 2. **工具调用 turn 不稳定**:ZCode app-server 的工具调用 turn 时长在 38s~100s+ 波动,有时不完成。 3. **流式输出**:ZCode CLI ≥ 0.14.8 支持事件推送(`session/subscribe`),ACP bridge 在此版本下实现**真流式**(逐段推送);旧版自动降级为伪流式(turn 完成后整段发)。 4. **diff 无内容**:ZCode 协议层不暴露 oldText/newText,只能列文件名。 -5. **GLM-5.2 无推理输出**:思考过程(agent_thought_chunk)在 GLM-5.2 下不触发,需 GLM-5-Turbo(GLM-5.2 为旧默认模型;GLM-5.3 行为未复测)。 +5. **GLM-5.2 无推理输出**:思考过程(agent_thought_chunk)在 GLM-5.2 下不触发,需 GLM-5-Turbo(GLM-5.2 为旧默认模型;GLM-5.3 行为未复测。GLM-5-Turbo 已于 App 3.12.3 时代由服务端从 coding-plan provider 下线,此条为历史观察)。 6. **TUI 不可用**:0.16.1 起 CLI 帮助虽列出 `tui` 命令(无参数即进入 TUI),但独立终端实测仍报错(`Cannot find package '@zcode/tui'`),仅 headless 模式可用。 7. **⚠️ ACP bridge 默认 `mode=yolo`(权限风险)**:为避免工具调用 turn 卡在权限确认,ACP bridge 的 `session/new` 强制以 `mode=yolo` 创建会话(见 `zcode-acp-bridge` 的 `_on_session_new`)。这意味着任意 prompt 都可能触发**无确认的文件修改和命令执行**。作为编辑器集成时请知悉此风险;现可用 `ZCODE_ACP_DEFAULT_MODE=build` 收紧默认值,且 bridge 启动日志(stderr)会对当前默认 mode 打显眼告警。更完整的方案是实现 ACP↔ZCode 的 permission 转发(本项目 P4b 未实现)。 -8. **⚠️ Provider 管理方法涉及 apiKey**:`workspace/upsertModelProvider`、`workspace/updateProviderRegistry` 的 `provider`/`registry` 参数会携带 `apiKey`(可能为 `{source:"inline", value:"sk-..."}` 明文)。ACP bridge 仅整体透传给 ZCode 后端、不读取也不在日志打印其明文;但调用方应自行确保传输通道(stdio)可信,并避免在日志中回显原始参数。 +8. **⚠️ Provider 管理方法涉及 apiKey**:`workspace/upsertModelProvider`、`workspace/updateProviderRegistry` 的 `provider`/`registry` 参数会携带 `apiKey`(可能为 `{source:"inline", value:"sk-..."}` 明文)。ACP bridge 仅整体透传给 ZCode 后端、不读取也不在日志打印其明文;但调用方应自行确保传输通道(stdio)可信,并避免在日志中回显原始参数。(这两个方法已于 App 3.12.3 的 0.16.5 构建删除,本条适用于 3.10.2 及更早构建。) 9. **⚠️ 事件模式 turn 超时契约(2026-08-08 起)**:`session/prompt` 在事件模式下若 turn 已启动但 120s 未收到完成信号,返回 **JSON-RPC 错误 `-32603`("事件流超时")**,而**不是**正常 `stopReason=max_turn_requests`——后者只保留给"turn 从未启动"的场景。ACP client 侧应按此区分「卡死」与「真的太长」(整体 review P1 + 复审 P1-B 的契约变更)。 ## 项目结构 diff --git a/docs/recheck-3.12.3.md b/docs/recheck-3.12.3.md new file mode 100644 index 0000000..96361a7 --- /dev/null +++ b/docs/recheck-3.12.3.md @@ -0,0 +1,87 @@ +# zcode-open-bridge 复测报告 — ZCode App 3.12.3 兼容性确认(CLI 版本号 0.16.5 未变,构建漂移) + +日期:2026-09-17 +调研人:主代理(Mac studio) +仓库: + +## 结论先行 + +- App 3.12.3 内嵌 CLI 的 `--version` 仍为 **0.16.5**(与 App 3.10.2 同号,见 [recheck-0.16.5.md](recheck-0.16.5.md)),但**构建内容漂移**:扩展透传面出现真实删除(workspace/* 8 删 7、`session/updateRuntimeModelConfig`、`updateInteractionPreferences`)。**`--version` 不能作为唯一兼容性判据**——同版本号、不同构建、不同协议面,这是本轮最重要的方法论教训,也是 issue #21 推动能力探测动态化的直接论据。 +- **核心链路零改动可用**:ACP bridge(initialize → session/new → session/prompt 流式全流程)、headless CLI、MCP server、review 体系全部实测通过,测试套件 `python3 -m pytest tests/ -q` 与 0.16.5 复测基线一致。 +- 变化集中在**扩展透传面**:被删方法桥不崩,后端 `-32601` 原样透传给 ACP client(本轮适配前);本轮仓库适配 = 文档更新 + `_passthrough_error` 泛化(透传方法的后端 `-32601` 统一翻译为「当前 ZCode 版本已移除该能力」文案,不再依赖硬编码方法清单,随本 PR)。 + +## 实证环境 + +| 平台 | App | CLI | CLI 路径 | 备注 | +| --- | --- | --- | --- | --- | +| macOS arm64(Mac studio) | 3.12.3(CFBundleShortVersionString) | 0.16.5(`zcode --version`,同号漂移构建) | `/Applications/ZCode.app/Contents/Resources/glm/zcode.cjs` | 官方构建经 `~/.local/bin/zcode-app` 符号链接可用;本轮探测全部 `ZCODE_BIN` 指向该官方构建 | +| (PATH 干扰项) | — | zcode-app-cli 3.11.2-25(社区,vendor zcode-runtime 0.16.5) | `~/.npm-global` 的 `zcode` | 本机 PATH 的 `zcode` 现为社区包装器,与官方构建区分;探测时须显式指 ZCODE_BIN | + +## ✅ 实测验证通过(与 0.16.5 基线一致) + +### 1. 测试套件 + +`python3 -m pytest tests/ -q` → **414 passed, 14 subtests passed**(与 2026-09-01 的 0.16.5 复测基线完全一致;本轮加入泛化降级用例后为 417 + 28 subtests)。 + +### 2. 凭证 + +- `~/.zcode/v2/config.json` 结构未变:`provider.*.options.baseURL/apiKey`,`models` 的 key 即 canonical id。 +- enabled provider 仍为 `builtin:zai-coding-plan`,baseURL `https://api.z.ai/api/anthropic`。 +- 模型面变化(服务端下发,见下 ⚠️):models 从 [GLM-5.3, GLM-5.3-Flash, GLM-5-Turbo] 变为 [GLM-5.3, GLM-5.3-Flash]。 + +### 3. headless CLI + +- `--prompt` / `--mode yolo` / `--disallowed-tools` / `--no-color` / `--json` 全部健在。 +- `--json` 顶层 keys = [eventCount, projection, response, sessionId, traceId, turnId, usage](与 0.16.5 基线一致);带凭证 env 注入后实测 `response="OK"`。 +- `--allowed-tools` 与 `--max-turns` 仍是帮助文案有、parseArgs 未接线(实测 `Unknown option '--max-turns'`)——agent-help 的「勿用」警告继续有效,适用范围扩为 0.16.1–0.16.5(含 3.10.2 与 3.12.3 两个构建)。 + +### 4. app-server 协议(活体探测,stdio 发无 jsonrpc 信封的 NDJSON) + +- **旧信封探测**:发含 `jsonrpc` 键的请求 → `-32600 invalid_union`(unrecognized_keys: `jsonrpc`),错误帧 id 仍为固定字符串 `"invalid-message"`——信封拒绝先于方法查找,与方法存亡无关,**桥的纪元探测不受本轮删除影响**。 +- **`session/create`**:result keys = [messages, projection, protocol, runtime, session, settings, slashCommands, todoGroups, todos];sessionId 位于 `result.session.sessionId`;`protocol = {name:"ZCode Protocol", version:1}`——与 0.16.5 完全一致。 +- **`session/subscribe`**(`deliveryKind:"desktop-continuous"`)result keys = [eventSeq, events, sessionId]。 +- **`session/send`** → `{accepted:true, stateRevision:1}`。 +- **`turn.completed`**(在 `session/event` 通知内)payload 含 response + usage(信封 keys: deliveryKind/eventId/payload/seq/sessionId/timestamp/traceId/turnId/type)。 +- **反向调用**仍是同样两个:`session/requestRuntimePreferences`(三布尔应答体仍被接受)、`interaction/requestOfficialMcpAuthHeaders`(桥回 `-32601` 安全降级,官方鉴权类 MCP 在桥内不可用,无断链)。 + +### 5. ACP 桥端到端 + +`ZCODE_BIN` 指向官方 3.12.3 构建:initialize → session/new → session/prompt 流式完成(`stopReason=end_turn`,agent_message_chunk 文本正常,sessionUpdate 种类含 agent_message_chunk/agent_thought_chunk/usage_update)。 + +### 6. 核心 session/* 方法面存活(空 params → -32602 即存活) + +`setModel`、`setMode`、`setThoughtLevel`、`fork`、`compact`、`goal`、`cancelBackgroundTask`、`list`、`resume`、`read`、`usage`、`messages`、`events`、`subagents`、`mcp/list` 全部存活;`session/list` 空参直接 SUCCESS(比 0.16.5 的 `-32602` 更宽松,无害)。 + +## ⚠️ 协议面变化(App 3.12.3 的 0.16.5 构建 vs App 3.10.2 的 0.16.5 构建) + +### 已删除(后端返 -32601 "Method not found",均实测两次确认) + +- `workspace/readState`、`workspace/setDefaultModel`、`workspace/setDefaultMode`、`workspace/setDefaultThoughtLevel`、`workspace/upsertModelProvider`、`workspace/removeModelProvider`、`workspace/updateProviderRegistry`——**workspace/* 面 8 个删 7 个,仅 `workspace/generateText` 存活**(空 params → -32602 参数校验,即方法仍在)。 +- `session/updateRuntimeModelConfig`。 +- `updateInteractionPreferences`(桥从未实现,无桥面影响)。 +- 已确认 `setDefault*` **没有搬家**到 session/ 命名空间:`session/setDefaultModel` 等同样 `-32601`。 + +### 新增通知 + +- `startup/storageState`:桥走通用通知路径安全丢弃,端到端实测无碍。同批观察到的通知种类:computer-use/operation-event、process/mcpTelemetry、session/event、state.updated、v4/telemetry/event——除 `startup/storageState` 外均与 0.16.5 相同。 + +### 模型面 + +- enabled provider 的 models 从 [GLM-5.3, GLM-5.3-Flash, GLM-5-Turbo] 变为 **[GLM-5.3, GLM-5.3-Flash]**(服务端下掉了 GLM-5-Turbo;README 模型面注记已同步)。 + +### 桥的透传降级实测(本轮适配的触发点) + +- workspace/readState 透传在 3.12.3 上返回 `-32601` 错误:桥不崩,错误原样透传给 ACP client(`-32603 "zcode readState failed: Method not found"` 形态)——与「已删方法应有明确文案」的既有降级(原 `_REMOVED_IN_016` 硬编码清单)不一致。 +- **本轮修复**:`zcode-acp-bridge` 的 `_removed_method_error` 泛化为 `_passthrough_error`——任何透传/扩展方法的后端 `-32601` 都翻译为「当前 ZCode 版本已移除该能力 (<方法名>)」文案(错误码保持 `-32601`),不再依赖硬编码清单(zcode review P3-3 预警的「未来版本再删方法时硬编码清单会误导」在本轮成真);非 `-32601` 错误码维持 `-32603 "zcode X failed"` 原文透传。核心协议路径(create/send/stop/list/resume)的 `-32601` 属深度异常,刻意保留原始错误,不套该文案。 + +## 方法论教训(写进 issue #21 的论据) + +**CLI 版本号相同 ≠ 协议面相同**。0.16.1 → 0.16.5 的复测可以靠 `--version` 门控,是因为当时「一个版本号对应一个构建」的经验假设成立;App 3.12.3 打破了这个假设——`--version` 仍是 0.16.5,协议面却删了 9 个方法、加了 1 个通知。兼容性判断必须落到**活体探测**(方法存在性、result keys、通知种类),这正是 issue #21 要求把 agent-help 的静态能力清单动态化的理由。本报告第 6 节的「空 params → -32602/-32601」探测法即最小可行的动态探测。 + +## 复测方法(可复现) + +- **环境**:`export ZCODE_BIN=~/.local/bin/zcode-app`(官方 3.12.3 构建的符号链接),确认 `--version` = 0.16.5 后再探测(注意 PATH 里的社区 `zcode` 包装器会干扰)。 +- **活体探测**:spawn `zcode app-server --stdio`,注入 `ZCODE_MODEL` / `ZCODE_BASE_URL` / `ANTHROPIC_API_KEY`,发无 `jsonrpc` 键的 NDJSON 帧,观察响应/通知。 +- **方法存在性**:空 params 发一次——`-32602`(参数校验)即存活,`-32601` 即已删;每个 -32601 结论复核第二次。 +- **headless**:`zcode --prompt "…" --mode yolo --no-color --json`。 +- **桥端到端**:`ZCODE_BIN` 指向官方构建后按 README 的 ACP bridge 用法跑 initialize → session/new → session/prompt。 diff --git a/packages/acp-bridge/zcode-acp-bridge b/packages/acp-bridge/zcode-acp-bridge index 88edf75..fd6f124 100755 --- a/packages/acp-bridge/zcode-acp-bridge +++ b/packages/acp-bridge/zcode-acp-bridge @@ -47,8 +47,12 @@ CLI 0.16.1 适配 (依据 docs/upgrade-0.16.1-spec.md, 全部经实测复核): session/create 与 session/send 时 server 会等应答才继续, 不应答永久卡住 (旧版桥无此代码路径, 即死于此 — 本适配的最高优先级单项)。 - session/create 返回 protocol {name, version} (0.16 无握手方法), 记录日志。 - - 0.16 已删方法 (steer/rewind*/prompt/enhance*) 后端返 -32601, - 不分协议模式, 统一映射为明确文案「当前 ZCode 版本已移除该能力」。 + - 透传/扩展方法的后端 -32601 (Method not found) 不分协议模式, 统一映射为 + 明确文案「当前 ZCode 版本已移除该能力」(steer/rewind*/prompt/enhance* 于 + 0.16 删除; App 3.12.3 的同号 0.16.5 构建又删 workspace/* 7/8 与 + updateRuntimeModelConfig — 版本号相同而构建漂移, 故不再依赖硬编码清单, + 见 _passthrough_error)。核心协议路径 (create/send/stop/list/resume) 的 + -32601 属深度异常, 保留原始错误透传, 不套该文案。 - state.updated 的 patch 合并为状态投影 (供事件流停滞检查); v4/telemetry/event 忽略; session.titleUpdated 转 stderr 日志。 @@ -60,14 +64,17 @@ CLI 0.16.1 适配 (依据 docs/upgrade-0.16.1-spec.md, 全部经实测复核): ✅ 工具调用展示 (tool.updated → tool_call/tool_call_update 实时) ✅ session/cancel → session/stop ✅ 扩展方法 (session 级, 0.14.8+): fork/rewind/goal/compact/steer - (rewind/steer 于 0.16 移除, 不分协议模式, 返回「已移除」文案) + (rewind/steer 于 0.16 移除, 后端 -32601 → 「已移除」文案, 下同) ✅ 扩展方法 (session 级, 0.15.0+): setThoughtLevel/updateRuntimeModelConfig/ cancelBackgroundTask/rewindCascade/setModel/setMode - (rewindCascade 于 0.16 移除, 同上) + (rewindCascade 于 0.16 移除; updateRuntimeModelConfig 于 App 3.12.3 的 + 同号 0.16.5 构建移除 — CLI 版本号未变而构建内容漂移, 同号不可靠) ✅ workspace/* 方法 (0.15.0+): readState/generateText/setDefault{Model,Mode,ThoughtLevel}/ upsertModelProvider/removeModelProvider/updateProviderRegistry + (App 3.12.3 的 0.16.5 构建删 7/8, 仅 generateText 存活, 见 + docs/recheck-3.12.3.md) ✅ prompt/* 方法 (App 3.3.0+): enhance (同步) / enhance/start+result+cancel (异步 job) - (于 0.16 移除, 同上) + (于 0.16 移除) 协议: ACP (JSON-RPC 2.0 over stdio) 对外; ZCode Protocol (无 jsonrpc 字段) 对内 依赖: 仅 Python3 标准库 @@ -1141,14 +1148,6 @@ class ACPBridge: def _next_id(self): return next(self._id_counter) - # 0.16 已删方法 (实测后端返 -32601 Method not found, 规格书 §2) - # 0.16 已删方法清单 — 与 README/SKILL 的「0.16 已移除」表格标注双处维护, - # 未来版本增删方法时两边同步 (zcode review P3-2) - _REMOVED_IN_016 = frozenset({ - "session/steer", "session/rewind", "session/rewindCascade", - "prompt/enhance", "prompt/enhance/start", "prompt/enhance/cancel", - }) - def _backend_v16(self): """后端是否为 0.16+ 新协议 (ZCodeBackend 启动探测缓存; 测试桩无该属性按旧协议)。""" return getattr(self.backend, "protocol_mode", "legacy") == "v16" @@ -1169,20 +1168,42 @@ class ACPBridge: except Exception: return False - def _removed_method_error(self, msg_id, zcode_method, resp): - """把已删方法的后端 -32601 映射为明确文案 (规格书 §7)。 - - 不分协议模式: ≤0.15 上这些方法存在, 根本不会返 -32601; 任何版本返了 - -32601 就说明该版本已移除它, 直接给出版本不支持文案。 - 文案不硬编码版本号 (zcode review P3-3): 「哪个版本移除」是历史事实, - 对调用方重要的是「当前版本不支持」; 硬编码 0.16 在未来版本再删方法时会误导。 + def _passthrough_error(self, msg_id, zcode_method, resp, redact=False): + """透传/扩展方法后端错误的统一出口 (原 _removed_method_error 的泛化, 规格书 §7)。 + + 判定只看错误码, 不再依赖硬编码方法清单 (原 _REMOVED_IN_016, zcode review + P3-3 预警过「硬编码清单在未来版本再删方法时会误导」— App 3.12.3 的同号 + 0.16.5 构建删掉 workspace/* 7/8 与 updateRuntimeModelConfig 后成真): + - 后端 -32601 (Method not found) → 该方法在当前连接的 ZCode 构建里已 + 不存在。≤0.15 时代方法存在根本不会返 -32601; 任何版本返了就说明该 + 构建已移除它, 翻译为「已移除」文案且错误码保持 -32601 (调用方据码 + 做版本判断)。文案不硬编码版本号: 「哪个版本移除」是历史事实, 对 + 调用方重要的是「当前版本不支持」。 + - 其他错误码 (含缺 code 的裸 error) → 维持 -32603 + "zcode <短名> failed: <后端原文>" 透传形态, 不一刀切 — -32602 参数 + 错、限流/内部错误等仍以后端原文为准。短名 = 去掉首段命名空间后余下 + 路径的 "/" 换空格 (session/fork→fork, prompt/enhance/start→ + "enhance start"), 与既有文案逐字一致。 + redact=True (provider/registry 族): 后端原文经 _redact_secret 脱敏, + 防 apiKey 随错误回显泄漏 (整体 review P2-3, 对 -32601 分支无需 — 文案 + 不含后端原文)。 + + 边界: 只用于扩展/透传 handler 的错误分支; 核心协议路径 (create/send/ + stop/list/resume 等) 的 -32601 属深度异常, 需要原始错误信息, 不走本 + helper (否则会被「已移除」文案掩盖真实故障)。 """ - if (zcode_method in self._REMOVED_IN_016 - and resp.get("error", {}).get("code") == -32601): + err = resp.get("error", {}) + if err.get("code") == -32601: return self._error(msg_id, -32601, f"当前 ZCode 版本已移除该能力 ({zcode_method}); " f"该 ZCode 版本不支持此能力") - return None + message = err.get("message", "") + if redact: + message = self._redact_secret(message) + # split 的 [-1] 在无 "/" 时本就返回原串, 无需三元分支 + short = zcode_method.split("/", 1)[-1].replace("/", " ") + return self._error(msg_id, -32603, + f"zcode {short} failed: {message}") def ensure_backend(self): if self.backend is None: @@ -1408,8 +1429,7 @@ class ACPBridge: zc_id = self._next_id() resp, _ = self.backend.request(zc_id, "session/fork", zc_params, timeout=15) if "error" in resp: - return self._error(msg_id, -32603, - f"zcode fork failed: {resp['error'].get('message', '')}") + return self._passthrough_error(msg_id, "session/fork", resp) result = resp.get("result", {}) new_sid = result.get("sessionId") if new_sid: @@ -1436,11 +1456,7 @@ class ACPBridge: zc_id = self._next_id() resp, _ = self.backend.request(zc_id, "session/rewind", zc_params, timeout=15) if "error" in resp: - mapped = self._removed_method_error(msg_id, "session/rewind", resp) - if mapped: - return mapped - return self._error(msg_id, -32603, - f"zcode rewind failed: {resp['error'].get('message', '')}") + return self._passthrough_error(msg_id, "session/rewind", resp) result = resp.get("result", {}) log("session/rewind → ok") return {"jsonrpc": "2.0", "id": msg_id, "result": result} @@ -1465,8 +1481,7 @@ class ACPBridge: zc_id = self._next_id() resp, _ = self.backend.request(zc_id, "session/goal", zc_params, timeout=15) if "error" in resp: - return self._error(msg_id, -32603, - f"zcode goal failed: {resp['error'].get('message', '')}") + return self._passthrough_error(msg_id, "session/goal", resp) result = resp.get("result", {}) # set/replace 启动内部 turn, 等待其 prompt lock 释放 (用 goal show 探测) # 实测 goal set 的内部 AI turn 耗时 10-45s+ 不等, 必须等锁释放 @@ -1496,8 +1511,7 @@ class ACPBridge: resp, _ = self.backend.request(zc_id, "session/compact", {"sessionId": zcode_sid}, timeout=30) if "error" in resp: - return self._error(msg_id, -32603, - f"zcode compact failed: {resp['error'].get('message', '')}") + return self._passthrough_error(msg_id, "session/compact", resp) result = resp.get("result", {}) log("session/compact → ok") return {"jsonrpc": "2.0", "id": msg_id, "result": result} @@ -1518,11 +1532,7 @@ class ACPBridge: resp, _ = self.backend.request(zc_id, "session/steer", {"sessionId": zcode_sid, "content": content}, timeout=15) if "error" in resp: - mapped = self._removed_method_error(msg_id, "session/steer", resp) - if mapped: - return mapped - return self._error(msg_id, -32603, - f"zcode steer failed: {resp['error'].get('message', '')}") + return self._passthrough_error(msg_id, "session/steer", resp) result = resp.get("result", {}) log(f"session/steer → kind={result.get('kind', '?')}") return {"jsonrpc": "2.0", "id": msg_id, "result": result} @@ -1603,8 +1613,7 @@ class ACPBridge: zc_id = self._next_id() resp, _ = self.backend.request(zc_id, "session/setThoughtLevel", zc_params, timeout=15) if "error" in resp: - return self._error(msg_id, -32603, - f"zcode setThoughtLevel failed: {resp['error'].get('message', '')}") + return self._passthrough_error(msg_id, "session/setThoughtLevel", resp) result = resp.get("result", {}) log("session/setThoughtLevel → ok") return {"jsonrpc": "2.0", "id": msg_id, "result": result} @@ -1631,8 +1640,7 @@ class ACPBridge: resp, _ = self.backend.request(zc_id, "session/updateRuntimeModelConfig", zc_params, timeout=15) if "error" in resp: - return self._error(msg_id, -32603, - f"zcode updateRuntimeModelConfig failed: {resp['error'].get('message', '')}") + return self._passthrough_error(msg_id, "session/updateRuntimeModelConfig", resp) result = resp.get("result", {}) log("session/updateRuntimeModelConfig → ok") return {"jsonrpc": "2.0", "id": msg_id, "result": result} @@ -1654,8 +1662,7 @@ class ACPBridge: resp, _ = self.backend.request(zc_id, "session/cancelBackgroundTask", zc_params, timeout=15) if "error" in resp: - return self._error(msg_id, -32603, - f"zcode cancelBackgroundTask failed: {resp['error'].get('message', '')}") + return self._passthrough_error(msg_id, "session/cancelBackgroundTask", resp) result = resp.get("result", {}) log(f"session/cancelBackgroundTask → cancelled={result.get('cancelled')}") return {"jsonrpc": "2.0", "id": msg_id, "result": result} @@ -1684,11 +1691,7 @@ class ACPBridge: zc_id = self._next_id() resp, _ = self.backend.request(zc_id, "session/rewindCascade", zc_params, timeout=15) if "error" in resp: - mapped = self._removed_method_error(msg_id, "session/rewindCascade", resp) - if mapped: - return mapped - return self._error(msg_id, -32603, - f"zcode rewindCascade failed: {resp['error'].get('message', '')}") + return self._passthrough_error(msg_id, "session/rewindCascade", resp) result = resp.get("result", {}) log("session/rewindCascade → ok") return {"jsonrpc": "2.0", "id": msg_id, "result": result} @@ -1709,8 +1712,7 @@ class ACPBridge: zc_id = self._next_id() resp, _ = self.backend.request(zc_id, "session/setModel", zc_params, timeout=15) if "error" in resp: - return self._error(msg_id, -32603, - f"zcode setModel failed: {resp['error'].get('message', '')}") + return self._passthrough_error(msg_id, "session/setModel", resp) result = resp.get("result", {}) log(f"session/setModel → {model_id}") return {"jsonrpc": "2.0", "id": msg_id, "result": result} @@ -1731,8 +1733,7 @@ class ACPBridge: zc_id = self._next_id() resp, _ = self.backend.request(zc_id, "session/setMode", zc_params, timeout=15) if "error" in resp: - return self._error(msg_id, -32603, - f"zcode setMode failed: {resp['error'].get('message', '')}") + return self._passthrough_error(msg_id, "session/setMode", resp) result = resp.get("result", {}) log(f"session/setMode → {mode}") return {"jsonrpc": "2.0", "id": msg_id, "result": result} @@ -1755,8 +1756,7 @@ class ACPBridge: zc_id = self._next_id() resp, _ = self.backend.request(zc_id, "workspace/readState", zc_params, timeout=15) if "error" in resp: - return self._error(msg_id, -32603, - f"zcode readState failed: {resp['error'].get('message', '')}") + return self._passthrough_error(msg_id, "workspace/readState", resp) result = resp.get("result", {}) log("workspace/readState → ok") return {"jsonrpc": "2.0", "id": msg_id, "result": result} @@ -1792,8 +1792,7 @@ class ACPBridge: zc_id = self._next_id() resp, _ = self.backend.request(zc_id, "workspace/generateText", zc_params, timeout=60) if "error" in resp: - return self._error(msg_id, -32603, - f"zcode generateText failed: {resp['error'].get('message', '')}") + return self._passthrough_error(msg_id, "workspace/generateText", resp) result = resp.get("result", {}) log(f"workspace/generateText → ok (text {len(result.get('text', ''))} chars)") return {"jsonrpc": "2.0", "id": msg_id, "result": result} @@ -1824,11 +1823,7 @@ class ACPBridge: zc_id = self._next_id() resp, _ = self.backend.request(zc_id, "prompt/enhance", zc_params, timeout=90) if "error" in resp: - mapped = self._removed_method_error(msg_id, "prompt/enhance", resp) - if mapped: - return mapped - return self._error(msg_id, -32603, - f"zcode enhance failed: {resp['error'].get('message', '')}") + return self._passthrough_error(msg_id, "prompt/enhance", resp) result = resp.get("result", {}) log(f"prompt/enhance → ok (enhanced {len(result.get('enhanced', ''))} chars)") return {"jsonrpc": "2.0", "id": msg_id, "result": result} @@ -1872,11 +1867,7 @@ class ACPBridge: if "error" in resp: if callable(_unreg): _unreg(request_id) - mapped = self._removed_method_error(msg_id, "prompt/enhance/start", resp) - if mapped: - return mapped - return self._error(msg_id, -32603, - f"zcode enhance start failed: {resp['error'].get('message', '')}") + return self._passthrough_error(msg_id, "prompt/enhance/start", resp) start_result = resp.get("result", {}) if not start_result.get("accepted"): # zcode 拒绝了 (理论上幂等会返回 accepted:true; 此处兜底) @@ -1966,11 +1957,7 @@ class ACPBridge: resp, _ = self.backend.request( zc_id, "prompt/enhance/cancel", {"requestId": request_id}, timeout=10) if "error" in resp: - mapped = self._removed_method_error(msg_id, "prompt/enhance/cancel", resp) - if mapped: - return mapped - return self._error(msg_id, -32603, - f"zcode enhance cancel failed: {resp['error'].get('message', '')}") + return self._passthrough_error(msg_id, "prompt/enhance/cancel", resp) result = resp.get("result", {}) log(f"prompt/enhance/cancel → cancelled={result.get('cancelled')}") return {"jsonrpc": "2.0", "id": msg_id, "result": result} @@ -1997,8 +1984,7 @@ class ACPBridge: zc_id = self._next_id() resp, _ = self.backend.request(zc_id, "workspace/setDefaultModel", zc_params, timeout=15) if "error" in resp: - return self._error(msg_id, -32603, - f"zcode setDefaultModel failed: {resp['error'].get('message', '')}") + return self._passthrough_error(msg_id, "workspace/setDefaultModel", resp) result = resp.get("result", {}) log("workspace/setDefaultModel → ok") return {"jsonrpc": "2.0", "id": msg_id, "result": result} @@ -2021,8 +2007,7 @@ class ACPBridge: zc_id = self._next_id() resp, _ = self.backend.request(zc_id, "workspace/setDefaultMode", zc_params, timeout=15) if "error" in resp: - return self._error(msg_id, -32603, - f"zcode setDefaultMode failed: {resp['error'].get('message', '')}") + return self._passthrough_error(msg_id, "workspace/setDefaultMode", resp) result = resp.get("result", {}) log(f"workspace/setDefaultMode → {mode}") return {"jsonrpc": "2.0", "id": msg_id, "result": result} @@ -2046,8 +2031,7 @@ class ACPBridge: resp, _ = self.backend.request(zc_id, "workspace/setDefaultThoughtLevel", zc_params, timeout=15) if "error" in resp: - return self._error(msg_id, -32603, - f"zcode setDefaultThoughtLevel failed: {resp['error'].get('message', '')}") + return self._passthrough_error(msg_id, "workspace/setDefaultThoughtLevel", resp) result = resp.get("result", {}) log("workspace/setDefaultThoughtLevel → ok") return {"jsonrpc": "2.0", "id": msg_id, "result": result} @@ -2077,9 +2061,8 @@ class ACPBridge: resp, _ = self.backend.request(zc_id, "workspace/upsertModelProvider", zc_params, timeout=15) if "error" in resp: - return self._error(msg_id, -32603, - f"zcode upsertModelProvider failed: " - f"{self._redact_secret(resp['error'].get('message', ''))}") + return self._passthrough_error(msg_id, "workspace/upsertModelProvider", + resp, redact=True) result = resp.get("result", {}) # 只打 providerId, 不打 provider 原文 (含 apiKey) log(f"workspace/upsertModelProvider → ok ({provider.get('providerId', '?')})") @@ -2105,8 +2088,7 @@ class ACPBridge: resp, _ = self.backend.request(zc_id, "workspace/removeModelProvider", zc_params, timeout=15) if "error" in resp: - return self._error(msg_id, -32603, - f"zcode removeModelProvider failed: {resp['error'].get('message', '')}") + return self._passthrough_error(msg_id, "workspace/removeModelProvider", resp) result = resp.get("result", {}) log(f"workspace/removeModelProvider → ok ({provider_id})") return {"jsonrpc": "2.0", "id": msg_id, "result": result} @@ -2133,9 +2115,8 @@ class ACPBridge: resp, _ = self.backend.request(zc_id, "workspace/updateProviderRegistry", zc_params, timeout=15) if "error" in resp: - return self._error(msg_id, -32603, - f"zcode updateProviderRegistry failed: " - f"{self._redact_secret(resp['error'].get('message', ''))}") + return self._passthrough_error(msg_id, "workspace/updateProviderRegistry", + resp, redact=True) result = resp.get("result", {}) provider_count = len(registry.get("providers", [])) log(f"workspace/updateProviderRegistry → {result.get('status', '?')} " diff --git a/packages/agent-help/zcode-agent-help b/packages/agent-help/zcode-agent-help index 538bd24..e41342e 100755 --- a/packages/agent-help/zcode-agent-help +++ b/packages/agent-help/zcode-agent-help @@ -162,10 +162,13 @@ def print_injected_env(config_path=None): # ============================================================ OVERVIEW = { "tool": "zcode", - "version_described": "0.16.5 (App 3.10.2)", + "version_described": "0.16.5", # 整体 review P2-6: version_described 是"静态清单基于该版本实测"的意思; # 实际安装版本以 environment.zcode_version (动态探测) 为准, 两者可能不一致。 - "tested_against": "0.16.5 (App 3.10.2)", + # App 3.12.3 起 --version 同为 0.16.5 但构建内容漂移 (workspace/* 7 个与 + # updateRuntimeModelConfig 被删, 见 key_methods 各 ❌ 注记), 故不再绑定具体 + # App 版本号 — 描述对象是 CLI 能力面 (0.16.5), 可用性以各条目注记为准。 + "tested_against": "0.16.5 (App 3.10.2 / 3.12.3 复测)", "what_it_is": ( "ZCode 是智谱(Z.AI)的 Agentic Coding CLI, 由 GLM 系列模型驱动。" "它能自主完成代码编写/审查/调试/分析等任务, 可调用工具(读写文件、" @@ -325,12 +328,15 @@ NATIVE_CLI = { # 自主性 {"category": "agency", "name": "autonomous-tools", "supported": True, "description": "自主调用工具: 读写文件、Bash、Grep、Web 搜索等", - "limitations": {"tool_allowlist": "帮助文案有 --allowed-tools, 0.16.1–0.16.5 实测均未接线 (Unknown option)", + "limitations": {"tool_allowlist": "帮助文案有 --allowed-tools, 0.16.1–0.16.5 实测均未接线 " + "(Unknown option)", "tool_denylist": "0.16.1+ 实测可用 (--disallowed-tools, 工具集级物理移除, yolo 也绕不过)", - "max_turns": "帮助文案有 --max-turns, 0.16.1–0.16.5 实测均未接线 (Unknown option)", + "max_turns": "帮助文案有 --max-turns, 0.16.1–0.16.5 实测均未接线 " + "(Unknown option)", "max_budget": "不支持"}}, {"category": "agency", "name": "tool-allowlist", "supported": False, - "description": "工具白名单 — 帮助文案有但 0.16.1–0.16.5 实测均未接线 (Unknown option), 勿用", + "description": "工具白名单 — 帮助文案有但 0.16.1–0.16.5 实测均未接线 " + "(Unknown option), 勿用", "arg": "--allowed-tools ", "since": "0.16.1 (仅文案)"}, {"category": "agency", "name": "tool-denylist", "supported": True, @@ -341,7 +347,8 @@ NATIVE_CLI = { "example": "zcode --prompt \"整理代码\" --disallowed-tools \"Bash(git *) Edit\"", "since": "0.16.1"}, {"category": "agency", "name": "max-turns", "supported": False, - "description": "最大模型轮次 — 帮助文案有但 0.16.1–0.16.5 实测均未接线 (Unknown option), 勿用", + "description": "最大模型轮次 — 帮助文案有但 0.16.1–0.16.5 实测均未接线 " + "(Unknown option), 勿用", "arg": "--max-turns ", "since": "0.16.1 (仅文案)"}, # 以下 5 项 0.16.5 help 实测在列, 引入版本未考, since 记 0.16.5 @@ -445,6 +452,10 @@ APP_SERVER = { "新增 server→client 反向调用 session/requestRuntimePreferences (client 必须应答, 否则卡住); " "session/subscribe 新增必填 deliveryKind。下列 key_methods 已更新到 0.16.1 实况: " "steer/rewind/rewindCascade/prompt/enhance* 均已移除 (调用返 -32601 Method not found)。" + "⚠ App 3.12.3 的内嵌 CLI --version 仍为 0.16.5 但构建内容漂移 (2026-09-17 实测, " + "详见 docs/recheck-3.12.3.md): workspace/* 8 删 7 (仅 generateText 存活) 与 " + "session/updateRuntimeModelConfig 被删 (返 -32601), 另新增 startup/storageState 通知 — " + "--version 不能作为唯一兼容性判据, 方法存疑时以活体探测为准。" ), "description": ( "长驻 stdio 服务, 支持多会话/多轮。ZCode 桌面 App 和 ACP 桥接器都用它。" @@ -501,7 +512,8 @@ APP_SERVER = { "note": "0.15.0+: 设置思考强度 (动态值, 按模型 reasoning 能力; 实测 GLM-5.2: max/high/nothink)"}, {"method": "session/updateRuntimeModelConfig", "params": "sessionId, runtimeModel, applyModelSelection?", "note": "0.15.0+: 运行时覆盖模型配置 (含 provider/providerOptions/thoughtLevel); " - "0.16.1 存活, 但 schema 新要求 runtimeModel.revision (string) 必填"}, + "0.16.1 存活, 但 schema 新要求 runtimeModel.revision (string) 必填; " + "❌ 已于 App 3.12.3 的 0.16.5 构建移除 (2026-09-17 实测返 -32601)"}, {"method": "session/rewindCascade", "params": "sessionId, target?, scope?, expectedRevision?", "note": "❌ 0.16 已移除 (调用返 -32601); 原为 0.15.0+ 级联回退 (与 rewind 同 schema)"}, {"method": "session/setMode", "params": "sessionId, mode", @@ -510,21 +522,29 @@ APP_SERVER = { "mode.auto.unimplemented 拒绝, 勿当'智能自动批准'用"}, # ----- 0.15.0 (App 3.2.0) 新增 workspace/* 方法 (按工作区定位, 非 session) ----- {"method": "workspace/readState", "params": "workspace:{workspacePath,workspaceKey}, runtimeModel?", - "note": "0.15.0+: 读工作区状态 (settings/modelCatalog/slashCommands)"}, + "note": "0.15.0+: 读工作区状态 (settings/modelCatalog/slashCommands); " + "❌ 已于 App 3.12.3 的 0.16.5 构建移除 (2026-09-17 实测返 -32601, 未搬家到 session/)"}, {"method": "workspace/generateText", "params": "workspace, modelRef, prompt, querySource, maxOutputTokens?, temperature?", - "note": "0.15.0+: 一次性文本生成 (不建会话, 无 messages/tools)"}, + "note": "0.15.0+: 一次性文本生成 (不建会话, 无 messages/tools); " + "App 3.12.3 的 0.16.5 构建实测仍存活 (workspace/* 8 删 7 中唯一幸存)"}, {"method": "workspace/setDefaultModel", "params": "workspace, model, runtimeModel?, expectedWorkspaceRevision?", - "note": "0.15.0+: 设工作区默认模型 (持久化, 乐观锁)"}, + "note": "0.15.0+: 设工作区默认模型 (持久化, 乐观锁); " + "❌ 已于 App 3.12.3 的 0.16.5 构建移除 (实测返 -32601)"}, {"method": "workspace/setDefaultMode", "params": "workspace, mode, expectedWorkspaceRevision?", - "note": "0.15.0+: 设工作区默认权限模式 (持久化)"}, + "note": "0.15.0+: 设工作区默认权限模式 (持久化); " + "❌ 已于 App 3.12.3 的 0.16.5 构建移除 (实测返 -32601)"}, {"method": "workspace/setDefaultThoughtLevel", "params": "workspace, thoughtLevel, expectedWorkspaceRevision?", - "note": "0.15.0+: 设工作区默认思考强度 (持久化)"}, + "note": "0.15.0+: 设工作区默认思考强度 (持久化); " + "❌ 已于 App 3.12.3 的 0.16.5 构建移除 (实测返 -32601)"}, {"method": "workspace/upsertModelProvider", "params": "workspace, provider, expectedWorkspaceRevision?", - "note": "0.15.0+: 新增/更新模型供应商 (provider 含 apiKey, 敏感)"}, + "note": "0.15.0+: 新增/更新模型供应商 (provider 含 apiKey, 敏感); " + "❌ 已于 App 3.12.3 的 0.16.5 构建移除 (实测返 -32601)"}, {"method": "workspace/removeModelProvider", "params": "workspace, providerId, expectedWorkspaceRevision?", - "note": "0.15.0+: 移除模型供应商"}, + "note": "0.15.0+: 移除模型供应商; " + "❌ 已于 App 3.12.3 的 0.16.5 构建移除 (实测返 -32601)"}, {"method": "workspace/updateProviderRegistry", "params": "workspace, registry, includeWorkspaceState?", - "note": "0.15.0+: 批量更新供应商注册表 (registry.providers 含 apiKey, 敏感)"}, + "note": "0.15.0+: 批量更新供应商注册表 (registry.providers 含 apiKey, 敏感); " + "❌ 已于 App 3.12.3 的 0.16.5 构建移除 (实测返 -32601)"}, # ----- App 3.3.0 新增 prompt/* 方法 (提示词增强); 0.16 已整族移除 ----- {"method": "prompt/enhance", "params": "workspace:{workspacePath,workspaceKey}, sessionId?, prompt, context?", "result": "{enhanced}", @@ -546,7 +566,9 @@ APP_SERVER = { "part_types": { "text": "Agent 文本回复", "tool": "工具调用 (含 callID/tool/state.{status,input,output})", - "reasoning": "推理过程 (仅 GLM-5-Turbo 产出, GLM-5.2 无)", + "reasoning": "推理过程 (仅 GLM-5-Turbo 产出, GLM-5.2 无; " + "GLM-5-Turbo 已于 App 3.12.3 时代由服务端从 coding-plan provider " + "下线, 此为历史观察)", "patch": "文件变更清单 (含 hash + files[], 但无 diff 内容)", "step-start": "步骤开始标记", "step-finish": "步骤结束 (含 cost/tokens)", @@ -610,7 +632,9 @@ ECOSYSTEM = { {"event": "tool_call/tool_call_update", "status": "✅ 实时 (事件驱动)"}, {"event": "usage_update", "status": "✅ 已验证"}, {"event": "agent_message_chunk", "status": "✅ 真流式 (model.streaming text_delta)"}, - {"event": "agent_thought_chunk", "status": "✅ 流式 (GLM-5-Turbo reasoning_delta)"}, + {"event": "agent_thought_chunk", + "status": "✅ 流式 (GLM-5-Turbo reasoning_delta; 该模型已于 App 3.12.3 " + "时代由服务端从 coding-plan provider 下线, 此为历史观察)"}, {"event": "plan", "status": "⚠️ 代码就位, 数据驱动"}, {"event": "diff (文件列表)", "status": "⚠️ 仅文件名无内容"}, ], diff --git a/skills/zcode-bridge-guide/SKILL.md b/skills/zcode-bridge-guide/SKILL.md index 30de2b1..e2fc5b5 100644 --- a/skills/zcode-bridge-guide/SKILL.md +++ b/skills/zcode-bridge-guide/SKILL.md @@ -8,7 +8,7 @@ user-invocable: true # 驱动 ZCode(三模式通用说明书) > 本 skill 是 [zcode-open-bridge](https://github.com/tizerluo/zcode-open-bridge) 项目的配套说明书。 -> 兼容 ZCode CLI **0.14.5 ~ 0.16.1**(App 3.6.5,实测含 3.2.1~3.6.5)。新版功能(事件驱动真流式、fork/goal/compact、workspace/*、setThoughtLevel 思考强度控制)在旧版上自动降级或返回 `-32603`;`session/steer`、`session/rewind*`、`prompt/enhance*` 已于 0.16 移除,调用返回 `-32601`。 +> 兼容 ZCode CLI **0.14.5 ~ 0.16.5**(实测含 App 3.2.1~3.12.3)。新版功能(事件驱动真流式、fork/goal/compact、workspace/*、setThoughtLevel 思考强度控制)在旧版上自动降级或返回 `-32603`;`session/steer`、`session/rewind*`、`prompt/enhance*` 已于 0.16 移除,调用返回 `-32601`。⚠️ App 3.12.3 的 0.16.5 构建(版本号未变、内容漂移)进一步删除了 workspace/* 面 7/8 方法与 `session/updateRuntimeModelConfig`(详见下文方法表注记与版本表)。 ## 前置条件 @@ -262,20 +262,20 @@ ACP bridge 暴露的 ZCode 新版协议方法,按定位维度分组。**sessio | `session/setModel` / `setMode` | 切换模型 / 权限模式 | 0.14.8 | `{sessionId, modelId}` / `{sessionId, mode}` | | `session/cancelBackgroundTask` | 取消后台 Bash 任务 | 0.14.8 | `{sessionId, taskId}` | | `session/rewindCascade` ❌ | 级联回退(同 rewind schema,**0.16 已移除**) | 0.15.0 | `{sessionId, target?, scope?, expectedRevision?}` | -| `session/updateRuntimeModelConfig` | 运行时覆盖模型配置 | 0.15.0 | `{sessionId, runtimeModel, applyModelSelection?}`(0.16 起 `runtimeModel.revision` 必填) | +| `session/updateRuntimeModelConfig` ❌ | 运行时覆盖模型配置 | 0.15.0 | `{sessionId, runtimeModel, applyModelSelection?}`(0.16 起 `runtimeModel.revision` 必填) | > ❌ **0.16 已移除**:`session/steer`、`session/rewind`、`session/rewindCascade` 已从 app-server 删除(steer 并入 `session/send`——turn 进行中发送即 steer;rewind 仅剩 slash 命令 `/rewind`),0.16.1 上调用会收到 `-32601`。 > -> ℹ️ **0.16 schema 变更**:`session/updateRuntimeModelConfig` 在 0.16.1 仍存活(实测),但 schema 新要求 `runtimeModel.revision`(string)必填。 +> ℹ️ **0.16 schema 变更**:`session/updateRuntimeModelConfig` 在 0.16.1 仍存活(实测),但 schema 新要求 `runtimeModel.revision`(string)必填。App 3.12.3 的同号 0.16.5 构建已删除该方法(2026-09-17 实测后端返 -32601),桥透传时降级为「已移除」文案。 -**workspace 级扩展方法**(0.15.0+): +**workspace 级扩展方法**(0.15.0+;❌ 除 `generateText` 外已于 **App 3.12.3 的 0.16.5 构建**移除,调用返回 `-32601`「已移除该能力」): | 方法 | 作用 | params | |------|------|--------| -| `workspace/readState` | 读工作区状态(模型目录/设置) | `{workspace, runtimeModel?}` | -| `workspace/generateText` | 一次性文本生成(不建会话) | `{workspace, modelRef, prompt, querySource, ...}` | -| `workspace/setDefaultModel` / `setDefaultMode` / `setDefaultThoughtLevel` | 设工作区默认值(持久化) | `{workspace, model\|mode\|thoughtLevel, expectedWorkspaceRevision?}` | -| `workspace/upsertModelProvider` / `removeModelProvider` / `updateProviderRegistry` | 管理模型供应商(含 apiKey,敏感) | `{workspace, provider\|providerId\|registry, ...}` | +| `workspace/readState` | 读工作区状态(模型目录/设置)❌ 3.12.3 已删 | `{workspace, runtimeModel?}` | +| `workspace/generateText` | 一次性文本生成(不建会话)—— 3.12.3 唯一幸存 | `{workspace, modelRef, prompt, querySource, ...}` | +| `workspace/setDefaultModel` / `setDefaultMode` / `setDefaultThoughtLevel` | 设工作区默认值(持久化)❌ 3.12.3 已删 | `{workspace, model\|mode\|thoughtLevel, expectedWorkspaceRevision?}` | +| `workspace/upsertModelProvider` / `removeModelProvider` / `updateProviderRegistry` | 管理模型供应商(含 apiKey,敏感)❌ 3.12.3 已删 | `{workspace, provider\|providerId\|registry, ...}` | **prompt 级扩展方法**(App 3.3.0 引入;❌ **0.16 已全部移除**,无替代): @@ -427,6 +427,8 @@ npm test # 全量,看实际数字 | ZCode CLI 版本 | 支持情况 | 差异 | |:--------------:|:--------:|------| +| **0.16.5** (App 3.12.3,同号构建漂移) | ✅ 完整(核心面) | session/* 核心面不变;workspace/* 删 7/8 仅 `generateText` 幸存,`session/updateRuntimeModelConfig`、`updateInteractionPreferences` 已删——桥统一翻译为 `-32601`「已移除该能力」(见 docs/recheck-3.12.3.md) | +| **0.16.5** (App 3.10.2) | ✅ 完整 | 与 0.16.1 同面 + `automation/*` 全族删除(桥未使用,无影响) | | **0.16.1** (App 3.6.5) | ✅ 完整 | ACP bridge 真流式;session/* + workspace/* 可用(steer/rewind*/prompt/enhance* 已于 0.16 移除;updateRuntimeModelConfig 存活但 `runtimeModel.revision` 必填) | | **0.15.x** (App 3.5.x) | ✅ 完整 | ACP bridge 真流式;扩展方法同 0.15.0 行(App 功能面:3.5.2 内置网页应用、PDF 预览,见规格书 changelog) | | **0.15.x** (App 3.4.x) | ✅ 完整 | ACP bridge 真流式;扩展方法同 0.15.0 行(App 功能面:3.4.2 定时任务 cron、Kimi K3,见规格书 changelog) | diff --git a/tests/test_app_server_methods.py b/tests/test_app_server_methods.py index ebce641..0ab4f40 100644 --- a/tests/test_app_server_methods.py +++ b/tests/test_app_server_methods.py @@ -25,7 +25,10 @@ M 存活方法回归 (规格书 §2 存活清单: setThoughtLevel/setModel/setMode/ cancelBackgroundTask/fork/goal/compact + workspace/*) D 已删方法降级: steer/rewind/rewindCascade → -32601「该版本不支持」文案 - (prompt/enhance* 的降级见 test_prompt_enhance.py) + (prompt/enhance* 的降级见 test_prompt_enhance.py); D4-D6: App 3.12.3 的 + 同号 0.16.5 构建删 workspace/* 7/8 与 updateRuntimeModelConfig 后, + 降级泛化为「透传方法后端 -32601 一律『已移除』文案, 非 -32601 不翻译」; + D7: 核心路径 (session/create) 的 -32601 属深度异常, 不套「已移除」文案 Z 未知方法仍 -32601 (bridge 自身文案, 与降级文案区分) 事实注记 (reviewer-1 0.16.1 真机抓帧, 对规格书 §4 信封描述的勘误): 事件判别 @@ -79,6 +82,17 @@ def _load_bridge_module(): # 规格书 §2: 0.16.1 已从 bundle 删除的方法 (steer/rewind 系; prompt/enhance* 见另一文件) DELETED_SESSION_METHODS = ("session/steer", "session/rewind", "session/rewindCascade") +# App 3.12.3 的 0.16.5 构建新删除的透传方法 (2026-09-17 实测, 两次确认): CLI +# --version 仍为 0.16.5 (与 App 3.10.2 同号) 但构建内容漂移 — workspace/* 8 删 7 +# (仅 generateText 存活), 另删 session/updateRuntimeModelConfig。这些方法不在 +# 桥的预知清单里, 用于验证 -32601 降级已泛化为「只看错误码」(D4)。 +REMOVED_IN_3123_METHODS = [ + "session/updateRuntimeModelConfig", + "workspace/readState", "workspace/setDefaultModel", "workspace/setDefaultMode", + "workspace/setDefaultThoughtLevel", "workspace/upsertModelProvider", + "workspace/removeModelProvider", "workspace/updateProviderRegistry", +] + # 规格书 §2 存活且实测仍在 bundle 的扩展方法 (回归锚; updateRuntimeModelConfig # 经 commander 0.16.1 实测确认存活 — schema 新要求 runtimeModel.revision 必填, # 已回归纳入, 透传用例见 M2) @@ -1259,6 +1273,104 @@ def test_d3_deleted_method_missing_params_still_32601(self): resp = self._call(bridge, "session/steer", {}) self._assert_error_code(resp, -32601) + # ---------- D4-D6: -32601 降级泛化 (App 3.12.3 的 0.16.5 构建漂移, 2026-09-17) ---------- + def test_d4_passthrough_removed_3123_methods(self): + """D4: 3.12.3 构建删除的透传方法 → -32601 + 「已移除」文案 (泛化降级) + + App 3.12.3 内嵌 CLI --version 仍为 0.16.5 (与 App 3.10.2 同号) 但构建 + 漂移: workspace/* 8 删 7 (仅 generateText 存活) + updateRuntimeModelConfig + 均实测返 -32601。这些方法不在桥原 _REMOVED_IN_016 硬编码清单里 — 泛化后 + 判定只看错误码, 未预知的删除同样得到「已移除」降级而非 -32603 原样透传 + (调用方据 -32601 做版本判断; 透传原始 Method not found 无区分度)。 + """ + for m in REMOVED_IN_3123_METHODS: + with self.subTest(method=m): + bridge, _ = self._new_bridge({m: {"response": { + "error": {"code": -32601, "message": "Method not found"}}}}) + resp = self._call(bridge, m, {"sessionId": "sess_x", + "workspacePath": "/p", + "runtimeModel": {"revision": "r1"}, + "model": {"modelId": "m"}, "mode": "yolo", + "thoughtLevel": "high", + "provider": {"models": [{"modelId": "m"}]}, + "providerId": "p", + "registry": {"providers": []}}) + self._assert_error_code(resp, -32601, + f"{m} 后端 -32601 应保持 -32601 返回") + self.assertIn("已移除", resp["error"]["message"], + f"{m} 的 -32601 应映射为「已移除」文案, " + f"实际: {resp['error']['message']}") + self.assertIn(m, resp["error"]["message"], + f"{m} 降级文案应含方法名便于定位, " + f"实际: {resp['error']['message']}") + + def test_d5_passthrough_other_error_codes_kept_failed(self): + """D5: 非 -32601 错误码不翻译 — 仍 -32603 "zcode <短名> failed: 原文" + + 泛化只针对 -32601 (方法不存在); 参数错/内部错/限流等必须保留后端原文, + 否则真实故障会被「已移除」文案掩盖。覆盖 -32603/-32000 与缺 code 的裸 + error (FakeBackend 里部分脚本就这么写), 且短名与既有文案逐字一致 + (session/fork→fork, workspace/readState→readState)。 + """ + cases = [ + ("workspace/readState", {"code": -32603, "message": "boom"}, + "zcode readState failed: boom"), + ("session/fork", {"code": -32000, "message": "rate limited"}, + "zcode fork failed: rate limited"), + ("session/setThoughtLevel", {"message": "model has no reasoning levels"}, + "zcode setThoughtLevel failed: model has no reasoning levels"), + ] + for method, err, want_prefix in cases: + with self.subTest(method=method, code=err.get("code")): + bridge, _ = self._new_bridge({method: {"response": {"error": err}}}) + resp = self._call(bridge, method, {"sessionId": "sess_x", + "workspacePath": "/p", + "thoughtLevel": "high"}) + self._assert_error_code(resp, -32603, + f"{method} 非 -32601 错误不得换码") + self.assertIn(want_prefix, resp["error"]["message"], + f"{method} 应保留 failed 原文形态, " + f"实际: {resp['error']['message']}") + self.assertNotIn("已移除", resp["error"]["message"], + f"{method} 非 -32601 不得套「已移除」文案") + + def test_d6_steer_rewind_wording_regression(self): + """D6: steer/rewind/rewindCascade 既有降级回归 — 收编进 _passthrough_error 后不回退 + + 原 _removed_method_error 依赖 _REMOVED_IN_016 硬编码清单; 泛化收编后 + 0.16 已删方法的行为必须不变: -32601 + 「已移除」文案 + 完整方法名 + (D1 只断言「不支持」, 这里补齐「已移除」与方法名两个要素)。 + """ + for m in DELETED_SESSION_METHODS: + with self.subTest(method=m): + bridge, _ = self._new_bridge({m: {"response": { + "error": {"code": -32601, "message": "Method not found"}}}}) + resp = self._call(bridge, m, {"sessionId": "sess_x", "content": "hi"}) + self._assert_error_code(resp, -32601) + self.assertIn("已移除", resp["error"]["message"], + f"{m} 应保持「已移除」文案, 实际: {resp['error']['message']}") + self.assertIn(m, resp["error"]["message"], + f"{m} 文案应含完整方法名, 实际: {resp['error']['message']}") + + def test_d7_core_path_32601_not_masked(self): + """D7: 核心路径 (session/create) 后端 -32601 不被「已移除」文案掩盖 + + 泛化的边界不变量: 只有透传/扩展方法走 _passthrough_error; 核心协议路径 + (create/send/stop/list/resume) 的 -32601 属深度异常 (session/create 不在 + 后端都意味着桥的协议纪元判定已失效), 必须保留原始错误信息 — 本用例 + 钉死 session/new 在该场景下仍返回 -32603 + "zcode create failed" 原文, + 不套「已移除」文案 (此前只有代码结构保证, 无测试拦截)。 + """ + bridge, _ = self._new_bridge({"session/create": {"response": { + "error": {"code": -32601, "message": "Method not found"}}}}) + resp = self._call(bridge, "session/new", {"cwd": "/p"}) + self._assert_error_code(resp, -32603, + "核心路径 -32601 应保持 -32603 透传, 不得换码") + self.assertIn("zcode create failed", resp["error"]["message"], + f"应保留 create failed 原文形态, 实际: {resp['error']['message']}") + self.assertNotIn("已移除", resp["error"]["message"], + "核心路径 -32601 属深度异常, 不得套「已移除」文案") + # ---------- Z: 未知方法 ---------- def test_z1_unknown_method_32601(self): """Z1: 真正未知的方法 → -32601 (bridge 自身文案, 与 D 系列降级文案区分)"""