@luchenglong/cli-bridge 0.2.7 → 0.2.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/CHANGELOG.md +97 -0
  2. package/PROVIDER-CONTRACT.md +53 -4
  3. package/README.md +12 -11
  4. package/docs/HANDOVER.md +9 -0
  5. package/docs/audits/2026-10-18-preset-0.2.1-contract-fix.md +160 -0
  6. package/docs/audits/README.md +15 -0
  7. package/dynamic/agy.host.js +1 -1
  8. package/dynamic/claude.host.js +1801 -0
  9. package/dynamic/claude.host.template.mjs +108 -0
  10. package/dynamic/codebuddy.host.js +1 -1
  11. package/dynamic/codex.host.js +1955 -0
  12. package/dynamic/codex.host.template.mjs +137 -0
  13. package/dynamic/mimo.host.js +1 -1
  14. package/dynamic/opencode.host.js +1 -1
  15. package/home-plugin/cli-bridge-indicator/lib/client.js +600 -606
  16. package/home-plugin/cli-bridge-indicator/lib/preset-declarations.mjs +87 -9
  17. package/mcp/cli-bridge-mcp.mjs +6 -4
  18. package/mcp/providers/claude.mjs +231 -0
  19. package/mcp/providers/codex.mjs +239 -0
  20. package/package.json +10 -6
  21. package/preset/agy-first/agent.cordis.yml +0 -5
  22. package/preset/agy-first/plugin-list.mjs +4 -1
  23. package/preset/claude-first/agent.cordis.yml +136 -0
  24. package/preset/claude-first/cli-bridge.mjs +131 -0
  25. package/preset/claude-first/plugin-list.mjs +28 -0
  26. package/preset/claude-first/preset.yml +3 -0
  27. package/preset/codebuddy-first/agent.cordis.yml +0 -5
  28. package/preset/codex-first/agent.cordis.yml +136 -0
  29. package/preset/codex-first/cli-bridge.mjs +160 -0
  30. package/preset/codex-first/plugin-list.mjs +28 -0
  31. package/preset/codex-first/preset.yml +3 -0
  32. package/preset/mimo-first/agent.cordis.yml +1 -7
  33. package/preset/mimo-first/plugin-list.mjs +18 -12
  34. package/preset/mimo-member/plugin-list.mjs +49 -0
  35. package/preset/opencode-first/agent.cordis.yml +0 -5
  36. package/preset/opencode-first/plugin-list.mjs +4 -2
  37. package/preset/standard-base.mjs +18 -6
  38. package/providers/claude-exe.mjs +112 -0
  39. package/providers/claude-models.mjs +334 -0
  40. package/providers/claude-policy.mjs +37 -0
  41. package/providers/claude.mjs +508 -0
  42. package/providers/codex-cli.mjs +471 -0
  43. package/providers/codex-models.mjs +107 -0
  44. package/providers/codex.mjs +544 -0
  45. package/providers/registry.mjs +25 -1
  46. package/scripts/build.mjs +1 -2
  47. package/scripts/verify.mjs +8 -2
package/CHANGELOG.md CHANGED
@@ -9,6 +9,103 @@ GitHub Release(`scripts/verify.mjs` 在 CI 与 prepack 双重锁一致)。
9
9
  > - CodeBuddy 线:[docs/history/codebuddy/CHANGELOG.md](docs/history/codebuddy/CHANGELOG.md)(终版 1.6.2)
10
10
  > - MiMo 线:[docs/history/mimo/CHANGELOG.md](docs/history/mimo/CHANGELOG.md)(终版 1.2.9)
11
11
 
12
+ ## [0.2.9] - 2026-10-20
13
+
14
+ ### Added
15
+ - **两家新 provider:`claude`(Claude Code)与 `codex`(OpenAI Codex CLI)**,四形态全覆盖
16
+ (描述符 / 持久 Agent Preset / dynamic 沙箱宿主 / MCP 工具面),`PROVIDER_IDS` 由 4 家扩到 6 家:
17
+ - `claude`:工具 `claude_run` / `claude_continue` / `claude_status` + `claude_models`;
18
+ preset `claude-first`。走 `-p --output-format stream-json --verbose`,从 `system`/`assistant`/
19
+ `result` 事件折叠实时步骤;sessionId 取 `result.session_id`,续聊走 `--resume`。
20
+ - `codex`:工具 `codex_run` / `codex_continue` / `codex_status` + `codex_models`;
21
+ preset `codex-first`。走 `exec --json`,从 `thread.started`/`turn.started`/`item.completed`/
22
+ `turn.completed` 折叠;sessionId 取 `thread_id`,续聊走 `exec resume <id>`。
23
+ - 两者默认权限都按「非交互无 TTY」处理(claude `--dangerously-skip-permissions`;codex
24
+ `-c approval_policy` + `sandbox_mode`),并把可选项暴露成设置项
25
+ (claude `maxBudgetUsd`;codex `sandboxMode` / `approvalPolicy`)。
26
+ - **模型清单一律实时、绝不冒充**(延续 0.2.6/0.2.7 对 codebuddy 的整改纪律):
27
+ - `claude_models` 唯一权威来源 = 无效模型触发的服务端 400 报文
28
+ (「The supported API model names are …」),实测本机为 `deepseek-flash, deepseek-v4-pro`;
29
+ `~/.claude/settings.json` 里配置的 `ANTHROPIC_DEFAULT_*_MODEL` 只作诊断线索,不进可用清单。
30
+ - `codex_models` 默认只走 live(`GET {base_url}/models` + Bearer,实测 OpenRouter 返回 469 项;
31
+ 过滤后可对话的 80 项);本地 `model_catalog_json` / `models.json` 仅在**显式**
32
+ `source=catalog|builtin` 时返回并逐项标注,绝不冒充实时。任何失败(含 200 但 0 个模型、
33
+ 畸形载荷、401)一律 `ok:false` + 可操作错误。
34
+
35
+ ### Changed
36
+ - **`PRESET_DIRS`(provider id → `preset/<dir>/`)统一为单一来源**:原先在
37
+ `scripts/build.mjs`、`home-plugin/.../preset-declarations.mjs`、`scripts/verify.mjs`
38
+ 各硬编码一份,新增 provider 漏改任一处就会出现「产物静默不生成」或误导性的
39
+ 「plugin-list missing」。现统一从 `providers/registry.mjs` 导入,加第 N 家 provider 三处零改动。
40
+ - **keystone 测试去掉同类硬编码**(否则新预设静默不被覆盖):
41
+ `tests/keystone/scheduling-gate.test.mjs` 的 apply 映射改为按 `PRESET_DIRS` 动态 import;
42
+ `tests/keystone/preset-config-contract.test.mjs` 与 `persona-prompt-vars.test.mjs`
43
+ 的目录清单改为从 registry 派生 —— 现在自动覆盖全部 7 个 preset(含 `claude-first` /
44
+ `codex-first` / 成员预设 `mimo-member`)。
45
+ - `package.json` description 更新为六家;`.snapshot-backup*/` 加入 `.gitignore` 并移出 git 索引。
46
+
47
+ ### Fixed
48
+ - `home-plugin/cli-bridge-indicator/lib/client.js` 由 606 行压到 600 行(红线内)。压缩只用
49
+ 无损手段(合并重复的头部叙述、删一处多余空行);**保留红线 4**:`__ModuleLoader__.load({ id })`
50
+ 逐字等于包名,`tests/keystone/client-ui-surface.test.mjs` 的「声明必须存在」守卫未被削弱
51
+ (曾尝试把两张查表合并成一条声明,会破坏该守卫的正则,已回退)。
52
+
53
+ ### 说明(实测更正,防回归)
54
+ codex 团队用真实 CLI 更正了三处易错事实,已在描述符里加防线并有单测钉死:
55
+ 1. `codex exec` **不接受** `-a/--ask-for-approval`(那是根命令的旗)→ 审批只能
56
+ `-c approval_policy="…"`;
57
+ 2. `codex exec resume` **没有** `-C/-s/-o/--add-dir` → cwd 交 spawn、沙箱走 `-c sandbox_mode="…"`;
58
+ 3. `item.completed` 里 `item.type==="error"` 是**告警**(续聊会带一条「原模型与当前模型不同」),
59
+ 不是致命错;成功判据 = 退出码 0 + 收到 `turn.completed` + 无顶层错误。
60
+
61
+ ## [0.2.8] - 2026-10-18
62
+
63
+ > 用户使用 agy 优先模式时整轮失败:
64
+ > `unknown prompt variable "{{cwd}}" in section "deployment:persona-suffix"; registered variables: provider, model`。
65
+ > 根因是**预设从旧版上游模板转录**,继承了上游在实装 `0.2.1-alpha.5` 已删除的三类写法。
66
+ > 完整方案与逐项取证:[docs/audits/2026-10-18-preset-0.2.1-contract-fix.md](docs/audits/2026-10-18-preset-0.2.1-contract-fix.md)。
67
+
68
+ ### Fixed
69
+
70
+ - **预设整体不可用:persona `suffix` 里的 `{{cwd}}` 不是注册变量**。`dsh-system-prompt` 的
71
+ `interpolate()` 对 `{{name}}` 严格解析(须匹配 `/^[a-z][a-z0-9_]*$/` 且**已注册**),
72
+ 未注册引用会在**任何模型请求之前**抛错 ⇒ 该预设完全不可用。当前循环只注册 `provider` 与
73
+ `model`(`dsh-agent-loop`),工作目录已改由 working-directory 服务以「不插值的用户上下文」
74
+ 提供 —— 官方预设的 persona 因此**只有 `prefix`**。已在 `standard-base.mjs` 与
75
+ agy/mimo/opencode 三个单源里删除 `suffix`,并重新生成四个 `agent.cordis.yml`。
76
+ - **`cordis-mimo` 整个预设挂载失败:`tool-subagent.config.persona`**。`dsh-tool-subagent`
77
+ 0.2.1 起在 `apply()` 里对 `persona`/`toolFilter` **显式 throw**
78
+ ("…no longer supported; configure persona and tool plugins in an Agent Preset and select it
79
+ with agentPreset (spawn only)")。原委派人格已**迁移**为成员预设(见 Added),能力未丢失。
80
+ - **死配置 `backgroundMode`**(四行委派行都有)。该字段已从 DSH 全部包移除,且实装 schema
81
+ **会放行并保留**它(`Config['~standard'].validate` 无 issues;schemastery 没有 strict 开关),
82
+ 只是**无人读取** ⇒ 静默**忽略**而非丢弃:表面声明 `continuable`/`one-shot` 却毫无效果,
83
+ 且**运行时不报错**。已删除。
84
+
85
+ ### Added
86
+
87
+ - **成员预设 `cordis-mimo-member`**(`preset/mimo-member/plugin-list.mjs`):人格 = 旧委派指令
88
+ verbatim;工具面 = mimo 桥接行(子 agent 必须能调 `mimo_run` 才能"派活")+ 只读文件面
89
+ (旧 `toolFilter` 白名单的新表达:`write`/`edit` 直接不存在)。`cordis-mimo` 的 **spawn**
90
+ 行以 `agentPreset` 引用它;**fork 行不设**(`agentPreset` 仅 in-process spawn 支持,
91
+ fork/外部后端会 reject)。成员预设**先于**主预设注册(委派行 `validate()` 会经
92
+ `presets.readDocument(id)` 校验引用存在)。
93
+ - **防回归闸门 `tests/keystone/persona-prompt-vars.test.mjs`**:persona 的 `{{var}}` 必须在注册
94
+ 白名单内且不设 `suffix`;`tool-subagent` 行不得带已删字段;委派行的 `agentPreset` 必须等于
95
+ 成员预设 `meta.id` 且只出现在 `spawn` 行。内存单源 + 磁盘生成物双层。三道闸门均已实测
96
+ **按正确原因变红**后再保留。
97
+
98
+ ### Changed
99
+
100
+ - `tests/keystone/scheduling-gate.test.mjs` 的注册顺序期望改为从成员表派生
101
+ (`[...memberPresetIds(ids), ...provider presets]`),不硬编码。
102
+
103
+ ### 未跟进(能力面差异,非缺陷)
104
+
105
+ - 相对实装 `dsh-web-app@0.2.1-alpha.5` 的官方 standard 预设(35 行),本包基线(对齐官方
106
+ `0.1.7-rc.1`,33 行)缺 `time-context`、`tool-schedule`、`tool-plugin-market`。属能力面差异,
107
+ 是否跟进待定。
108
+
12
109
  ## [0.2.7] - 2026-10-18
13
110
 
14
111
  > 0.2.6 发布后立即发现两件事必须纠正,故 0.2.7 紧随其后。
@@ -8,10 +8,18 @@
8
8
 
9
9
  ## 0. 命名与硬约束(红线)
10
10
 
11
- - provider id(全小写,用于事件通道/设置分区/文件名/工具名):`agy` | `codebuddy` | `mimo`。
12
- - preset id(保持旧包不变,旧会话头引用):`cordis-agy` | `codebuddy-first` | `cordis-mimo`。
13
- - 模型工具名(保持旧包不变):`agy_run/agy_continue/agy_status(/agy_quota)`、
14
- `codebuddy_run/codebuddy_continue/codebuddy_status`、`mimo_run/mimo_continue/mimo_status(/mimo_settings)`。
11
+ - provider id(全小写,用于事件通道/设置分区/文件名/工具名):
12
+ `agy` | `codebuddy` | `mimo` | `opencode` | `claude` | `codex`。
13
+ **唯一 id 轴 = `providers/registry.mjs` 的 `PROVIDER_IDS`**;preset 目录映射的
14
+ **唯一来源**是同文件的 `PRESET_DIRS`(`build.mjs` / `preset-declarations.mjs` /
15
+ `verify.mjs` 都从它导入,**禁止**再各写一份——漏改会静默不产物或报误导性错误)。
16
+ - preset id:`cordis-agy` | `codebuddy-first` | `cordis-mimo`(三家旧包,**保持旧值不变**,
17
+ 旧会话头引用);`opencode-first` | `claude-first` | `codex-first`(本包新增,直接用
18
+ `<id>-first`)。注意 preset **目录名**与 presetId 不一定相同(agy/mimo 是旧包历史遗留)。
19
+ - 工具名(旧包语义逐字保持):`agy_run/agy_continue/agy_status(/agy_quota)`、
20
+ `codebuddy_run/codebuddy_continue/codebuddy_status(/codebuddy_models)`、
21
+ `mimo_run/mimo_continue/mimo_status(/mimo_settings)`、`opencode_run/…(/opencode_models)`、
22
+ `claude_run/…(/claude_models)`、`codex_run/…(/codex_models)`。
15
23
  - 客户端契约(红线 4):浏览器半注册 id **必须**等于包名 `@luchenglong/cli-bridge`;
16
24
  该文件是 keystone(`home-plugin/cli-bridge-indicator/lib/client.js`),provider 作者**不碰**。
17
25
  - 动态沙箱形态禁止 `import/require`;派生文件一律由 `node scripts/build.mjs` 生成,
@@ -233,3 +241,44 @@ export function check(env) → true | throw // --che
233
241
  node: 模块,dynamic 沙箱形态若拿不到 fetch 则 preflight 报 `DESKTOP_UNREACHABLE`
234
242
  并提示改用 mimocode 后端(保持旧包语义)。
235
243
 
244
+ ## 10. claude / codex 的实测事实(0.2.9 接入,改动前必读)
245
+
246
+ 两家都是**在真机上跑出来的**,下面每条都有单测或 `check()` 防线钉住,别凭直觉"顺手修正"。
247
+
248
+ ### claude(Claude Code,实测 2.0.76)
249
+ - 非交互:`claude -p <prompt> --output-format text|json|stream-json [--verbose]`。
250
+ `stream-json` 必须配 `--verbose`,否则只吐结果不吐事件。
251
+ - 事件:`system`(含 `session_id` / `model` / `tools` / `permissionMode` / `apiKeySource`)→
252
+ `assistant`(`message`)→ `result`(`subtype` / `is_error` / `result` / `session_id` /
253
+ `total_cost_usd` / `usage` / `modelUsage`)。
254
+ - **成功判定陷阱**:`is_error=true` 时退出码可能是 0 也可能是 1,且 `subtype` 仍可能是
255
+ `"success"`(无效模型实测就是这个形状)。判定必须 `is_error === false && exitCode === 0`。
256
+ - 续聊:`--resume <session_id>` / `-c|--continue` / `--fork-session`。
257
+ - 模型清单:**唯一权威来源 = 无效模型触发的服务端 400 报文**
258
+ `"The supported API model names are X, Y, ..."`。`~/.claude/settings.json` 的 env 块里
259
+ `ANTHROPIC_DEFAULT_*_MODEL` / `ANTHROPIC_MODEL` 是**用户配置的别名映射,不是服务端可用
260
+ 全集**(实测两者不一致),只能作诊断线索。
261
+ - 权限:`-p` 无 TTY,任何需要交互批准的动作都会挂到超时,故 auto 模式映射为
262
+ `--dangerously-skip-permissions`(或 plan 态 `--permission-mode plan`)。
263
+
264
+ ### codex(OpenAI Codex CLI,实测 0.98.0)
265
+ - 非交互:`codex exec [opts] <prompt>`;`--json` 出事件流,不加就是纯文本。
266
+ - 事件:`thread.started`(`thread_id`)→ `turn.started` → `item.completed`(`item`)→
267
+ `turn.completed`(`usage`)。
268
+ - **`codex exec` 不接受 `-a/--ask-for-approval`**(那是根命令 `codex` 的旗,clap 会报
269
+ unexpected argument)→ 审批只能 `-c approval_policy="…"`。
270
+ - **`codex exec resume` 不支持 `-C/-s/-o/--add-dir`** → cwd 交给 spawn 的 `cwd`,
271
+ 沙箱走 `-c sandbox_mode="…"`。
272
+ - **`item.completed` 里 `item.type==="error"` 是告警,不是致命错**(`resume --last` 会带一条
273
+ 「此会话原模型与当前模型不同」)。致命只有顶层 `error` / `turn.failed`;
274
+ 成功判据 = 退出码 0 + 收到 `turn.completed` + 无顶层错误。
275
+ - 非 git 目录必须 `--skip-git-repo-check`。
276
+ - 退出码:0 成功;1 运行错误(stdout 仍是合法事件流);2 参数错误。
277
+ - 配置/凭据:`~/.codex/config.toml`(`model` / `model_provider` /
278
+ `[model_providers.<name>]` 的 `base_url` + `experimental_bearer_token` /
279
+ `model_catalog_json`)、`~/.codex/auth.json`(`OPENAI_API_KEY` / `OPENAI_BASE_URL`);
280
+ `CODEX_HOME` 可覆盖目录。**模型清单默认只走 live**(`GET {base_url}/models`),
281
+ 本地 `model_catalog_json` / `models.json` 仅在显式 `source=catalog|builtin` 时返回。
282
+ - npm 全局安装得到的是 `.cmd` shim:无 shell 时 spawn 不了,走 shell 会把 prompt 二次解释。
283
+ 因此命中 `.cmd/.bat` 时**明确报错并指引指向真实 exe**,不静默套 shell、不静默降级。
284
+
package/README.md CHANGED
@@ -4,13 +4,14 @@
4
4
 
5
5
  > 一个用于 **DeepSeek Harness (DSH)** 的 Cordis 插件包:把编码/构建/调试/排查等实际工作
6
6
  > **优先派发给本机 CLI agent** —— Google **Antigravity(`agy`)**、腾讯 **CodeBuddy Code**、
7
- > 小米 **MiMo(MiMoCode CLI / MiMo Desktop)** —— DSH 全程掌控这些 CLI(非交互、无提示),
8
- > 并在 **限流 / 网络不通** 时弹窗让用户选择是否回退到 **DSH 本地 API 配置**;会话标题栏提供
9
- > **一盏聚合实时状态灯**(三家分区),设置面板统一三家偏好与凭据。
7
+ > 小米 **MiMo(MiMoCode)**、**OpenCode**、**Claude Code**、**OpenAI Codex CLI** —— DSH 全程
8
+ > 掌控这些 CLI(非交互、无提示),并在 **限流 / 网络不通** 时弹窗让用户选择是否回退到
9
+ > **DSH 本地 API 配置**;会话标题栏提供 **一盏聚合实时状态灯**(六家分区),设置面板统一
10
+ > 各家的偏好与凭据。
10
11
  >
11
12
  > 本包整合并取代三个旧包:`agy-first-bridge`(1.7.1 终版)、`codebuddy-first-bridge`(1.6.2 终版)、
12
13
  > `mimo-first-bridge`(1.2.9 终版)。旧包已 deprecated,功能全部并入此处;迁移见
13
- > [docs/MIGRATION.md](docs/MIGRATION.md)。
14
+ > [docs/MIGRATION.md](docs/MIGRATION.md)。`opencode` / `claude` / `codex` 为本包新增。
14
15
 
15
16
  ![聚合状态灯的几种状态](assets/indicator-states.svg)
16
17
 
@@ -25,7 +26,7 @@
25
26
  **工具名与入参语义与旧包逐字一致**,历史会话提示词零改动继续可用。
26
27
  2. **一段 CLI 优先策略提示**(每家各自 POLICY 段),让模型在所有模式下优先把实际工作
27
28
  派发给对应 CLI,原生工具只用于只读查询与最终验证。
28
- 3. **一盏聚合状态灯(家级插件,随 DSH 启动加载)**:标题栏 pill 汇总三家状态
29
+ 3. **一盏聚合状态灯(家级插件,随 DSH 启动加载)**:标题栏 pill 汇总各家状态
29
30
  (绿=就绪/成功、黄=运行中、红=失败、灰=不可用、琥珀=本地回退、preset 高亮=本会话运行在桥接预设下);
30
31
  悬停看当前步骤,点击展开按 provider、按项目(工作目录)的活动面板 ——
31
32
  每项目实时步骤折叠(当前工具 + 参数摘要 + 轨迹)、runs/tokens 累计、最近完成运行。
@@ -42,12 +43,12 @@
42
43
 
43
44
  | 形态 | 位置 | 能力 | 状态灯 |
44
45
  | --- | --- | --- | --- |
45
- | **持久 Agent Preset**(推荐) | [`preset/<dir>/`](preset/) | 工具 + POLICY + 回退弹窗 + 三家预设(`cordis-agy` / `codebuddy-first` / `cordis-mimo`) | ✅(家级灯自动点亮) |
46
+ | **持久 Agent Preset**(推荐) | [`preset/<dir>/`](preset/) | 工具 + POLICY + 回退弹窗 + 各家预设(`cordis-agy` / `codebuddy-first` / `cordis-mimo` / `opencode-first` / `claude-first` / `codex-first`) | ✅(家级灯自动点亮) |
46
47
  | **家级插件(指示灯+设置+声明注册)** | [`home-plugin/cli-bridge-indicator/`](home-plugin/cli-bridge-indicator/) | HTTP 路由 + 浏览器 pill/面板/设置 + **preset 声明制注册** | 本体 |
47
48
  | **动态宿主(沙箱插件)** | [`dynamic/<id>.host.js`](dynamic/)(由模板生成) | 无 preset 时在任意组合里挂载工具 | ✅(经收集器汇入) |
48
- | **MCP stdio server** | [`mcp/cli-bridge-mcp.mjs`](mcp/)(bin `cli-bridge-mcp`) | 任何 MCP 客户端(Claude Code/Cursor/DSH 外部)直连本机三家 CLI | ✅(快照文件桥接,见下) |
49
+ | **MCP stdio server** | [`mcp/cli-bridge-mcp.mjs`](mcp/)(bin `cli-bridge-mcp`) | 任何 MCP 客户端(Claude Code/Cursor/DSH 外部)直连本机各 CLI | ✅(快照文件桥接,见下) |
49
50
 
50
- MCP 接入(一个 server 暴露全部三家工具;只想要一家用兼容入口或 `--provider=`):
51
+ MCP 接入(一个 server 暴露全部各家工具;只想要一家用兼容入口或 `--provider=`):
51
52
 
52
53
  ```json
53
54
  {
@@ -110,7 +111,7 @@ home-plugin/cli-bridge-indicator/
110
111
  preset/standard-base.mjs + preset/<dir>/plugin-list.mjs ── scripts/build.mjs ──> agent.cordis.yml / preset.yml / dynamic/*.host.js
111
112
  ```
112
113
 
113
- - 开发/移植第三家 CLI?契约见 [PROVIDER-CONTRACT.md](PROVIDER-CONTRACT.md),一次实现一个描述符即可接入全部形态。
114
+ - 再接入一家 CLI(第 N 家)?契约见 [PROVIDER-CONTRACT.md](PROVIDER-CONTRACT.md),一次实现一个描述符即可接入全部形态。
114
115
  - 为什么 preset id 和工具名不变?旧会话头/提示词引用它们(红线清单在契约 §0)。
115
116
  - 生成物为什么提交进仓库?fresh clone 直接可装可用;`node scripts/build.mjs --check` 在 CI/prepack 防漂移。
116
117
 
@@ -122,7 +123,7 @@ npm install -g @luchenglong/cli-bridge # MCP 用:cli-bridge-mcp 已在
122
123
 
123
124
  DSH 接线(home-plugin bundle + presets + 指示灯一次装齐)见 **[docs/INSTALL.md](docs/INSTALL.md)**。
124
125
  要点:包带 `dsh.bundle.patch`(bundle 面自注册),DSH profile 加入该 bundle 后
125
- 三盏能力 + 三家 preset + 设置面板 + 状态灯一次到位;本仓库脚本 `npm run deploy:local`
126
+ 各家能力 + 各家 preset + 设置面板 + 状态灯一次到位;本仓库脚本 `npm run deploy:local`
126
127
  只做到「暂存目录」,不改运行时接线(需先停 DSH)。
127
128
 
128
129
  ## 开发
@@ -145,7 +146,7 @@ npm run check # 三闸门全量
145
146
  - [docs/RELEASE-SOP.md](docs/RELEASE-SOP.md) — 发版流程与红线检查
146
147
  - [docs/FALLBACK-AND-INDICATOR.md](docs/FALLBACK-AND-INDICATOR.md) — 回退弹窗与状态灯语义详解
147
148
  - [PROVIDER-CONTRACT.md](PROVIDER-CONTRACT.md) — provider 接入契约(冻结)
148
- - [CHANGELOG.md](CHANGELOG.md) + [docs/history/](docs/history/) — 统一历史与三家 lineage 全量历史
149
+ - [CHANGELOG.md](CHANGELOG.md) + [docs/history/](docs/history/) — 统一历史与三条旧 lineage 的全量历史
149
150
 
150
151
  ## 许可
151
152
 
package/docs/HANDOVER.md CHANGED
@@ -317,6 +317,15 @@ npm whoami # 应输出 luchenglong
317
317
  已收敛到 `mcp/version.mjs` 单一来源(红线 §5.8)。
318
318
  - **测试里硬编码 id 列表**:4 个 keystone 测试钉死三家 id,新增 provider 即红 → 违反单一 id 轴。
319
319
  已全部改为从 `PROVIDER_IDS` 派生(红线 §5.4)。
320
+ - **预设契约随 DSH 版本漂移**(2026-10-18):预设从**旧版上游模板**转录,继承了上游已删除的写法 →
321
+ ① persona `suffix` 里的 `{{cwd}}` 不再是注册变量(DSH 只注册 `provider`/`model`),
322
+ 组装期直接 throw,**整个预设不可用**;② `dsh-tool-subagent` 0.2.1 起 `persona`/`toolFilter`
323
+ 在 `apply()` 里显式 throw、`backgroundMode` 被静默**忽略**(注意:不是"被 schema 丢弃" ——
324
+ 实测实装 schema 会**放行并保留**该键,只是无人读取;所以运行时不报错,只能靠静态闸门拦)。
325
+ 已修 + 立三层闸门(`tests/keystone/persona-prompt-vars.test.mjs`)。
326
+ **完整方案与取证:`docs/audits/2026-10-18-preset-0.2.1-contract-fix.md`**(索引 `docs/audits/README.md`)。
327
+ 教训:上游预设模板是**会过期**的参考物,升级 DSH 后必须重新对照已实装包的
328
+ `presets/*.patch.yml` 与 `lib/index.js` 的 schema,不能沿用旧转录。
320
329
 
321
330
  ## 13. OpenCode provider(0.2.3 新增)
322
331
 
@@ -0,0 +1,160 @@
1
+ # 审计:DSH 0.2.1 预设契约漂移修复(2026-10-18)
2
+
3
+ > 起因:用户使用 **agy 优先模式**时整轮失败 ——
4
+ > `unknown prompt variable "{{cwd}}" in section "deployment:persona-suffix"; registered variables: provider, model`
5
+ >
6
+ > 修复范围:本仓(`@luchenglong/cli-bridge`)+ 姊妹仓 `agy-first-bridge`。
7
+ > 本文是**修复方案 + 结果审计**存档(用户规则 #29),并与 `docs/HANDOVER.md` §12 速查表互相索引。
8
+
9
+ ---
10
+
11
+ ## 1. 根因
12
+
13
+ DSH 的提示词组装器 `interpolate()`(`dsh-system-prompt/lib/index.js:158-182`)对 `{{name}}` 做**严格解析**:
14
+
15
+ | 判据 | 源码 | 违反后果 |
16
+ |---|---|---|
17
+ | 完整 `{{…}}` 组 | `GROUP_AT = /^\{\{([^{}]*)\}\}/`(:65) | 有 `}}` 却不成组 → `malformed prompt variable reference` |
18
+ | 名字形态 | `VARIABLE_NAME = /^[a-z][a-z0-9_]*$/`(:63) | `{{cwd }}`(含空格)也算 malformed |
19
+ | **必须已注册** | `if (!Object.hasOwn(variables, name)) throw`(:172-174) | `unknown prompt variable` |
20
+
21
+ 关键:它在**任何模型请求之前**、组装系统提示词时抛错 ⇒ 整个回合失败,且该预设**完全不可用**(不是降级、不是警告)。
22
+
23
+ **当前只注册两个变量**(已全量取证):
24
+
25
+ - `dsh-agent-loop/lib/index.js:1591-1592` —— `provider` / `model`,全仓无第二个注册点。
26
+ - 官方 `dsh-system-prompt/README.zh.md:72` —— 工作目录改由 working-directory 服务以
27
+ **不执行插值的用户上下文**提供;`:78` 明确要求从 preset 的 `suffix` 字段移除该句。
28
+ - 实装 `dsh-web-app@0.2.1-alpha.5` 的 `presets/{standard,ptc,cordis,minimal}.patch.yml`
29
+ 的 persona **只有 `prefix`**,没有 `suffix`。
30
+
31
+ **为什么现在才炸(漂移性质)**:上游预设在 `0.1.5-rc.2` ~ `0.1.7-rc.1` 期间**确实带**这句
32
+ (`agy-first-bridge/.probe/wa-*` 逐版留证 16 处),到 `0.2.1-alpha.5` 才移除。
33
+ 本仓预设从**旧版上游模板转录**而来 ⇒ 继承了被上游删除的 suffix。
34
+ 属"上游改版、下游转录物陈旧"型漂移,**不是**当初写错。
35
+
36
+ ---
37
+
38
+ ## 2. 第二类缺陷(同一主题,独立成因)
39
+
40
+ 排查"还有没有同类问题"时又查出两类 **dsh-tool-subagent 契约漂移**:
41
+
42
+ | 字段 | 0.2.1 行为 | 证据 | 影响 |
43
+ |---|---|---|---|
44
+ | `persona` | **显式 throw** | `dsh-tool-subagent/lib/index.js:355`:`for (const field of ["persona","toolFilter"]) if (Object.hasOwn(config, field)) throw new Error("tool-subagent: … is no longer supported; configure persona and tool plugins in an Agent Preset and select it with agentPreset (spawn only)")` | **mimo 预设整个挂载失败** |
45
+ | `toolFilter` | 同上 | 同上 | 同上 |
46
+ | `backgroundMode` | **静默忽略(非丢弃)** | 该字段已从 DSH 全部包消失(实测全仓 0 命中);且实装 schema **会放行**它 —— 见下方更正 | 声明 `continuable`/`one-shot` 却毫无效果,**运行时不报错** |
47
+
48
+ > **⚠ 机制更正(2026-10-18 独立验证查出)**:初稿写"`Config` 不是 strict ⇒ 被静默丢弃"是**错的**。
49
+ > 用实装包双路径实测:`Config({provider, backgroundMode})` 与 cordis 真正走的
50
+ > `Config['~standard'].validate(...)`(`cordis/lib/index.js:958`)**都无 issues 且把该键原样保留**;
51
+ > `Config.strict` 为 `undefined` —— schemastery **根本没有 strict 开关**。
52
+ > 正确表述:未知/已删键**通过校验并被保留**,随后**无人读取** ⇒ 静默**忽略**。
53
+ > 这条更正很关键:正因为校验放行,**运行时永远抓不到这类死字段**,静态闸门是唯一防线;
54
+ > 而"schema 会丢"的错误表述会让人误以为 schema 层已兜住。
55
+ > 附带影响:键被保留 ⇒ 任何对 config 的反射/对比/序列化都可能把它当**幽灵设置**暴露出来。
56
+
57
+ 当前 `Config` 接受:`provider` / `toolName` / `modelSelectionSettings` / `agentOptions` /
58
+ `agentPreset` / `allowedAgentPresets` / `maxDepth`。
59
+
60
+ ---
61
+
62
+ ## 3. 修复方案
63
+
64
+ ### 3.1 变量契约(`{{cwd}}`)
65
+
66
+ 删除所有 preset 的 persona `suffix`,保留 `prefix` 的 `{{model}}`。与官方预设形态一致。
67
+
68
+ | 文件 | 性质 |
69
+ |---|---|
70
+ | `preset/standard-base.mjs` | **源头**(共享基座;codebuddy 经它继承) |
71
+ | `preset/agy-first/plugin-list.mjs`、`preset/mimo-first/plugin-list.mjs`、`preset/opencode-first/plugin-list.mjs` | **源头**(各自 persona 覆盖) |
72
+ | `preset/{agy,codebuddy,mimo,opencode}-first/agent.cordis.yml` | **生成物** —— 由 `node scripts/build.mjs` 从上述源头重新生成 |
73
+
74
+ > ⚠️ 只改生成物无效:`npm test` 里的 `build --check` 会判漂移失败。
75
+
76
+ ### 3.2 死字段(`backgroundMode`)
77
+
78
+ 从 `preset/standard-base.mjs` 的委派组四行中删除。
79
+
80
+ ### 3.3 mimo 委派人设 → 成员预设(迁移,不删能力)
81
+
82
+ 0.2.1 的契约是"人格与工具面放进独立 Agent Preset,由委派行经 `agentPreset` 选择"。
83
+ 新增成员预设 + 让 spawn 行引用它,**保留**"子 agent 优先把活派给 mimo_run"这一能力。
84
+
85
+ | 新增/改动 | 内容 |
86
+ |---|---|
87
+ | **新增** `preset/mimo-member/plugin-list.mjs` | 成员预设单源:人格(旧委派指令 verbatim)+ mimo 桥接工具面(子 agent 必须能调 `mimo_run`)+ 只读文件面(旧 `toolFilter` 白名单的新表达:`write/edit: false`) |
88
+ | `preset/mimo-first/plugin-list.mjs` | 删 `persona`/`toolFilter`;**仅 spawn 行**加 `agentPreset: cordis-mimo-member`(`fork` 与外部后端会 reject 该字段) |
89
+ | `home-plugin/…/lib/preset-declarations.mjs` | 新增 `declareMemberPresets()` 并在主预设**之前**注册;导出 `memberPresetIds()` 供测试派生 |
90
+
91
+ **可行性取证(关键,避免了错误的实现路径)**:
92
+
93
+ ```js
94
+ // dsh-agent-preset-registry/lib/index.js
95
+ async register(definition) { … this.definitions.set(definition.id, record) } // :470-479 写
96
+ readDocument(agentPreset) { const record = this.definitions.get(agentPreset) … } // :613 读**同一个 Map**
97
+ ```
98
+
99
+ 而委派行 `validate()` 正是用 `presets.readDocument(id)` 校验引用
100
+ (`dsh-tool-subagent/lib/index.js:228-232`,未注册即 `agent-preset/not-found`)。
101
+ ⇒ **动态 `register()` 与 bundle 行声明互通**,无需改用 bundle 行,
102
+ 因而不破坏本包"旧 DSH 上不因行 import 失败而整包拒挂"的既有设计(见 `cordis.patch.yml` 头注)。
103
+
104
+ 顺序要求:成员预设**先于**主预设注册(kexi-crypto v1.9.21 同款结论)。
105
+
106
+ ---
107
+
108
+ ## 4. 防回归闸门
109
+
110
+ 新增 `tests/keystone/persona-prompt-vars.test.mjs`(15 项),三层同时覆盖:
111
+
112
+ 1. **内存单源层** —— 对 5 个 preset 目录(4 provider + 1 成员)的 `buildPlugins()` 输出断言:
113
+ - persona 的 `prefix`/`suffix` 里每个 `{{var}}` ∈ `['provider','model']`;
114
+ - `suffix` 必须缺席;
115
+ - `tool-subagent` 行不含 `persona`/`toolFilter`/`backgroundMode`。
116
+ 2. **磁盘生成物层** —— 对 4 个 `agent.cordis.yml` 全文同样断言(防只手改生成物)。
117
+ 字段用**键正则** `(^|\n)[ \t]*<field>[ \t]*:` 匹配,而非子串 —— 否则
118
+ `name: '@deepseek-ai/dsh-persona'` 与 `- id: persona` 会误报。
119
+ 3. **跨文件一致性层** —— 委派行引用的 `agentPreset` 必须等于成员预设的 `meta.id`,
120
+ 且只允许出现在 `provider: spawn` 行上(fork/外部后端会 reject)。
121
+
122
+ `tests/keystone/scheduling-gate.test.mjs` 与 `tests/mimo/plugin-list.test.mjs` 同步改造:
123
+ 注册顺序期望改为 `[...memberPresetIds(ids), ...provider presets]`(从单一源派生,不硬编码)。
124
+
125
+ ---
126
+
127
+ ## 5. 验证结果(本机实测)
128
+
129
+ | 命令 | 结果 |
130
+ |---|---|
131
+ | `node scripts/build.mjs --check` | `[build] up to date`,exit 0 |
132
+ | `node --test` | **373 tests / 372 pass / 0 fail / 1 skipped**,exit 0(20.5s) |
133
+ | `node scripts/verify.mjs` | `verify: OK`(含导出目标、四 provider 描述符、凭据扫描 201 文件) |
134
+ | `node mcp/cli-bridge-mcp.mjs --check` | `"errors": []`,exit 0 |
135
+ | 静态复核 | mimo 生成物含 `agentPreset: cordis-mimo-member`、**无** `persona:` 字段、无 `backgroundMode`、4 个 yml 均无 `{{cwd}}` |
136
+
137
+ **闸门"确实会红"取证**(临时注入缺陷 → 跑闸门 → 还原,三次均按**正确原因**变红):
138
+
139
+ | 注入的缺陷 | 闸门报错 |
140
+ |---|---|
141
+ | 删掉 spawn 行的 `agentPreset` | `本 provider 应至少有一行经 agentPreset 选择成员预设` |
142
+ | 把 `persona` 写回 spawn 行 | `tool-subagent 行 'tool-subagent' 仍带已删除字段 persona` |
143
+ | 成员预设 `id` 改成 `cordis-mimo-member-TYPO` | `tool-subagent 行 'tool-subagent' 的 agentPreset 与成员预设 id 不一致` |
144
+
145
+ 另:`{{cwd}}` 的闸门在修复过程中亦已实测会红(写回后 `agent.cordis.yml 引用了未注册变量 {{cwd}}`)。
146
+ 全部场景还原后复跑均恢复绿。
147
+
148
+ ---
149
+
150
+ ## 6. 未做 / 遗留
151
+
152
+ - **未发版**:未 bump `package.json` 版本、未加 `CHANGELOG.md` 条目、未 `npm publish`、未 commit。
153
+ - **姊妹仓**:`agy-first-bridge` 的同类修复由并行同伴完成并独立复跑验证,同样未发版。
154
+ - **历史留证未动**:`docs/history/**/CHANGELOG.md` 与 `agy-first-bridge/.probe/**` 里的 `{{cwd}}`
155
+ 字面量是"上游旧版确有该 suffix"的原始证据,**故意保留**。
156
+ - **面未覆盖**:本次只治理 preset 内的人格模板;宿主层 `personaSuffix`、
157
+ 家级/profile `cordis.patch.yml` 若也有未注册引用,需另行排查(本闸门不覆盖)。
158
+ - **成员预设工具面偏窄**(刻意):只给只读文件面 + 桥接行。若希望子 agent 能自己跑构建做验证,
159
+ 需在 `preset/mimo-member/plugin-list.mjs` 里补平台门控 shell —— 属产品取舍,未擅自加。
160
+ - **`.probe/` 留证已过期**(只到 `0.1.7-rc.1`,实装已 `0.2.1-alpha.5`):若要复算上游轨迹需重取该版本 tarball。
@@ -0,0 +1,15 @@
1
+ # docs/audits — 修复方案与结果审计存档
2
+
3
+ 本目录按"一事一档"存放**修复方案 + 验证结果 + 取证**,供后来者追溯"当时为什么这么改、
4
+ 验证到什么程度、还有什么没做"。索引如下(新档请追加一行)。
5
+
6
+ | 日期 | 主题 | 档 |
7
+ |---|---|---|
8
+ | 2026-10-18 | DSH 0.2.1 预设契约漂移:`{{cwd}}` 未注册变量、`tool-subagent` 已删字段、mimo 委派人设迁移为成员预设 | [2026-10-18-preset-0.2.1-contract-fix.md](./2026-10-18-preset-0.2.1-contract-fix.md) |
9
+
10
+ 相关索引:
11
+
12
+ - 事故速查表:`docs/HANDOVER.md` §12
13
+ - 排障手册(症状 → 原因 → 处置):`docs/HANDOVER.md` §10
14
+ - 不可动的契约(红线):`docs/HANDOVER.md` §5
15
+ - 发布流程:`docs/RELEASE-SOP.md`
@@ -1112,7 +1112,7 @@ function isBridgeEnabled(selfId, ids, io) {
1112
1112
  }
1113
1113
 
1114
1114
 
1115
- const SCHED_ALL_IDS = ["agy","codebuddy","mimo","opencode"]
1115
+ const SCHED_ALL_IDS = ["agy","codebuddy","mimo","opencode","claude","codex"]
1116
1116
 
1117
1117
 
1118
1118
  return {