@luchenglong/cli-bridge 0.2.8 → 0.2.10

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 (50) hide show
  1. package/CHANGELOG.md +91 -0
  2. package/PROVIDER-CONTRACT.md +87 -5
  3. package/README.md +12 -11
  4. package/docs/HANDOVER.md +109 -15
  5. package/dynamic/agy.host.js +1 -1
  6. package/dynamic/claude.host.js +1858 -0
  7. package/dynamic/claude.host.template.mjs +108 -0
  8. package/dynamic/codebuddy.host.js +1427 -1379
  9. package/dynamic/codex.host.js +1930 -0
  10. package/dynamic/codex.host.template.mjs +137 -0
  11. package/dynamic/mimo.host.js +721 -515
  12. package/dynamic/mimo.host.template.mjs +2 -0
  13. package/dynamic/opencode.host.js +741 -585
  14. package/dynamic/opencode.host.template.mjs +2 -0
  15. package/home-plugin/cli-bridge-indicator/lib/client.js +74 -90
  16. package/home-plugin/cli-bridge-indicator/lib/index.mjs +48 -0
  17. package/home-plugin/cli-bridge-indicator/lib/preset-declarations.mjs +2 -3
  18. package/mcp/cli-bridge-mcp.mjs +4 -2
  19. package/mcp/providers/claude.mjs +231 -0
  20. package/mcp/providers/codebuddy.mjs +427 -427
  21. package/mcp/providers/codex.mjs +239 -0
  22. package/package.json +6 -2
  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/codex-first/agent.cordis.yml +136 -0
  28. package/preset/codex-first/cli-bridge.mjs +160 -0
  29. package/preset/codex-first/plugin-list.mjs +28 -0
  30. package/preset/codex-first/preset.yml +3 -0
  31. package/providers/claude-exe.mjs +112 -0
  32. package/providers/claude-models.mjs +338 -0
  33. package/providers/claude-policy.mjs +37 -0
  34. package/providers/claude.mjs +561 -0
  35. package/providers/codebuddy-catalog.mjs +367 -599
  36. package/providers/codebuddy-consts.mjs +72 -72
  37. package/providers/codebuddy-models-source.mjs +254 -0
  38. package/providers/codebuddy-models-tool.mjs +106 -106
  39. package/providers/codebuddy-schemas.mjs +46 -46
  40. package/providers/codebuddy.mjs +600 -578
  41. package/providers/codex-cli.mjs +405 -0
  42. package/providers/codex-models.mjs +107 -0
  43. package/providers/codex.mjs +585 -0
  44. package/providers/mimo-pure.mjs +270 -111
  45. package/providers/mimo.mjs +453 -517
  46. package/providers/opencode-exe.mjs +190 -63
  47. package/providers/opencode.mjs +553 -587
  48. package/providers/registry.mjs +25 -1
  49. package/scripts/build.mjs +261 -262
  50. package/scripts/verify.mjs +8 -2
package/CHANGELOG.md CHANGED
@@ -9,6 +9,97 @@ 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.10] - 2026-10-20
13
+
14
+ > ⚠️ **0.2.9 的 dynamic 沙箱形态在 opencode / mimo 两家是坏的**,本版修复。若你在用这两家的
15
+ > dynamic 形态,请升级。
16
+
17
+ ### Fixed
18
+ - **`dynamic/opencode.host.js` 与 `dynamic/mimo.host.js` 运行即 `ReferenceError`**(0.2.9 已发布)。
19
+ 根因:这两家的代码被拆进片文件(`opencode-exe.mjs` / `mimo-pure.mjs`)后,它们的 dynamic
20
+ **模板里漏了片文件占位标记**,build 只在模板声明占位时才内联片文件 —— 于是生成物里
21
+ `candidateExePaths` / `parseMimoJson` 只有引用、没有定义。实测证据:修前 `apply()` 直接抛
22
+ `ReferenceError: candidateExePaths is not defined`;修后两家 `apply()` 正常。
23
+ 已补占位并重建,生成物内符号定义数 0 → 1。
24
+ - **新增永久防线**:`tests/{opencode,mimo}/dynamic-template.test.mjs` 断言生成物里片文件符号
25
+ **"定义数 = 1"**(而不是"出现过")—— 纯求值检查抓不到这类缺陷,因为未定义标识符只在函数
26
+ 被调用时才炸。
27
+ - **EOL 与 `.gitattributes` 不一致**:`providers/codebuddy*.mjs` 等工作区曾被写成 CRLF
28
+ (PowerShell 默认行为),而仓库要求 `eol=lf`,导致生成物按 CRLF 源拼出、别的机器 clone 后
29
+ `build --check` 必然 drift(`dynamic/codebuddy.host.js` 甚至被 git 当二进制)。
30
+ 已规范化全部源码为 LF 并重建;`git ls-files --eol` 复核全部 `w/lf`。
31
+
32
+ ### Added
33
+ - **模型下拉改为「手动拉取」(契约 §3.2)** —— 用户反馈 codebuddy 各产品面都自动拿不到可用
34
+ 清单,希望加刷新按钮、每次手动拉取:
35
+ - `settings.meta()` **不再自动探测**(面板同步回调,探测会拖慢首屏;且探不到时下拉空着,
36
+ 用户分不清"没模型"与"没拉到")。现在只读缓存,字段声明 `refresh: { label }` 即渲染按钮。
37
+ - 新增可选契约 `settings.refreshModels(settings, env) → { ok, ids, labels?, source, error? }`,
38
+ 已由 5 家实现(codebuddy / opencode / mimo / claude / codex);agy 无模型字段故不适用。
39
+ - 新端点 `POST /cli-bridge/settings/models`(body `{provider}`):**业务失败恒返回 HTTP 200**
40
+ + `{ok:false, error}`,不用 4xx/5xx —— 否则前端容易把"没拉到"当网络故障静默吞掉。
41
+ - `client.js` 渲染「拉取可用模型」按钮 + 进行中态 + 结果/错误文本;拿到 options 只更新展示态,
42
+ 用户选中仍走原有 POST 保存。
43
+ - 各家的 `source` 如实标注(`account-live` / `live` / `opencode-models` / `mimo-models`)。
44
+
45
+ ### 说明(各团队实测发现并修掉的真缺陷)
46
+ - claude:异步探测在 spawn 失败时 `error` 与 `close` 各推进一次候选游标,会**跳过后面能跑的
47
+ 候选**(用户填错 binPath 就再也走不到有效 exe);已加 per-attempt 守卫 + 回归钉子。
48
+ - codex:live `GET /models` 无超时,端点卡住会让面板一直转圈;已加 AbortController 上界(20s)。
49
+ - codebuddy/claude:`meta()` 与 `refreshModels` 的"产品面/缓存键"口径必须同源,否则会出现
50
+ 「刷新成功但下拉仍空」。已统一取值来源并各加用例。
51
+ - codebuddy:探测进程的环境曾硬编码 `process.env`,不尊重调用者传入的 env,导致测试无法隔离
52
+ (会回落到本机真实 CLI 并"假装成功")。现传入 env 可覆盖。
53
+
54
+ ## [0.2.9] - 2026-10-20
55
+
56
+ ### Added
57
+ - **两家新 provider:`claude`(Claude Code)与 `codex`(OpenAI Codex CLI)**,四形态全覆盖
58
+ (描述符 / 持久 Agent Preset / dynamic 沙箱宿主 / MCP 工具面),`PROVIDER_IDS` 由 4 家扩到 6 家:
59
+ - `claude`:工具 `claude_run` / `claude_continue` / `claude_status` + `claude_models`;
60
+ preset `claude-first`。走 `-p --output-format stream-json --verbose`,从 `system`/`assistant`/
61
+ `result` 事件折叠实时步骤;sessionId 取 `result.session_id`,续聊走 `--resume`。
62
+ - `codex`:工具 `codex_run` / `codex_continue` / `codex_status` + `codex_models`;
63
+ preset `codex-first`。走 `exec --json`,从 `thread.started`/`turn.started`/`item.completed`/
64
+ `turn.completed` 折叠;sessionId 取 `thread_id`,续聊走 `exec resume <id>`。
65
+ - 两者默认权限都按「非交互无 TTY」处理(claude `--dangerously-skip-permissions`;codex
66
+ `-c approval_policy` + `sandbox_mode`),并把可选项暴露成设置项
67
+ (claude `maxBudgetUsd`;codex `sandboxMode` / `approvalPolicy`)。
68
+ - **模型清单一律实时、绝不冒充**(延续 0.2.6/0.2.7 对 codebuddy 的整改纪律):
69
+ - `claude_models` 唯一权威来源 = 无效模型触发的服务端 400 报文
70
+ (「The supported API model names are …」),实测本机为 `deepseek-flash, deepseek-v4-pro`;
71
+ `~/.claude/settings.json` 里配置的 `ANTHROPIC_DEFAULT_*_MODEL` 只作诊断线索,不进可用清单。
72
+ - `codex_models` 默认只走 live(`GET {base_url}/models` + Bearer,实测 OpenRouter 返回 469 项;
73
+ 过滤后可对话的 80 项);本地 `model_catalog_json` / `models.json` 仅在**显式**
74
+ `source=catalog|builtin` 时返回并逐项标注,绝不冒充实时。任何失败(含 200 但 0 个模型、
75
+ 畸形载荷、401)一律 `ok:false` + 可操作错误。
76
+
77
+ ### Changed
78
+ - **`PRESET_DIRS`(provider id → `preset/<dir>/`)统一为单一来源**:原先在
79
+ `scripts/build.mjs`、`home-plugin/.../preset-declarations.mjs`、`scripts/verify.mjs`
80
+ 各硬编码一份,新增 provider 漏改任一处就会出现「产物静默不生成」或误导性的
81
+ 「plugin-list missing」。现统一从 `providers/registry.mjs` 导入,加第 N 家 provider 三处零改动。
82
+ - **keystone 测试去掉同类硬编码**(否则新预设静默不被覆盖):
83
+ `tests/keystone/scheduling-gate.test.mjs` 的 apply 映射改为按 `PRESET_DIRS` 动态 import;
84
+ `tests/keystone/preset-config-contract.test.mjs` 与 `persona-prompt-vars.test.mjs`
85
+ 的目录清单改为从 registry 派生 —— 现在自动覆盖全部 7 个 preset(含 `claude-first` /
86
+ `codex-first` / 成员预设 `mimo-member`)。
87
+ - `package.json` description 更新为六家;`.snapshot-backup*/` 加入 `.gitignore` 并移出 git 索引。
88
+
89
+ ### Fixed
90
+ - `home-plugin/cli-bridge-indicator/lib/client.js` 由 606 行压到 600 行(红线内)。压缩只用
91
+ 无损手段(合并重复的头部叙述、删一处多余空行);**保留红线 4**:`__ModuleLoader__.load({ id })`
92
+ 逐字等于包名,`tests/keystone/client-ui-surface.test.mjs` 的「声明必须存在」守卫未被削弱
93
+ (曾尝试把两张查表合并成一条声明,会破坏该守卫的正则,已回退)。
94
+
95
+ ### 说明(实测更正,防回归)
96
+ codex 团队用真实 CLI 更正了三处易错事实,已在描述符里加防线并有单测钉死:
97
+ 1. `codex exec` **不接受** `-a/--ask-for-approval`(那是根命令的旗)→ 审批只能
98
+ `-c approval_policy="…"`;
99
+ 2. `codex exec resume` **没有** `-C/-s/-o/--add-dir` → cwd 交 spawn、沙箱走 `-c sandbox_mode="…"`;
100
+ 3. `item.completed` 里 `item.type==="error"` 是**告警**(续聊会带一条「原模型与当前模型不同」),
101
+ 不是致命错;成功判据 = 退出码 0 + 收到 `turn.completed` + 无顶层错误。
102
+
12
103
  ## [0.2.8] - 2026-10-18
13
104
 
14
105
  > 用户使用 agy 优先模式时整轮失败:
@@ -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` 生成,
@@ -126,13 +134,46 @@ meta(env) → {
126
134
  allowCustom?: boolean, // select 允许自定义输入(defaultModel 就是它)
127
135
  placeholder?: string,
128
136
  note?: string, // 字段下方说明
129
- secret?: boolean // 凭据字段:必须同时列入 secretKeys
137
+ secret?: boolean, // 凭据字段:必须同时列入 secretKeys
138
+ refresh?: { // 可选:给 select 加「手动拉取」按钮(见 §3.2)
139
+ label?: string // 按钮文案,缺省「拉取可用项」
140
+ }
130
141
  } ]
131
142
  }
132
143
  ```
133
144
 
134
145
  diagnose(settings, env) 返回一行文本(卡片底部「诊断」区);无诊断给 ''。
135
146
 
147
+ ### 3.2 手动拉取(`refreshModels`)—— 面板不自动探测
148
+
149
+ **背景**:模型可用列表只能问服务端/端点,而"问"要起子进程、可能联网、可能因为某个
150
+ 产品面没登录而失败。早期做法是在 `meta()` 里同步探针(`spawnSync` + 缓存),带来两个问题:
151
+ ① 面板首屏可能被拖慢;② 探不到时下拉空着,用户无法区分"没模型"与"没拉到"。
152
+
153
+ **现在**:`meta()` **只读缓存、绝不主动探测**;探测改由用户点按钮显式触发。
154
+ provider 若希望自己的某个 select 字段支持手动拉取,则:
155
+
156
+ 1. 在 `meta()` 的该字段上加 `refresh: { label: '拉取可用模型' }`;
157
+ 2. 实现 `settings.refreshModels(settings, env)`(**可选**,async):
158
+
159
+ ```
160
+ refreshModels(settings, env) → Promise<{
161
+ ok: boolean,
162
+ ids?: string[], // 可用项 id(keystone 转成 options:[{value,label}])
163
+ labels?: object, // 可选:id → 展示文案
164
+ source?: string, // 数据来源标注,面板会显示(如 'account-live' / 'live')
165
+ error?: string // ok:false 时必填且要可行动(告诉用户下一步怎么做)
166
+ }>
167
+ ```
168
+
169
+ **铁律**:探测失败必须 `ok:false` + 可操作错误,**绝不**返回 `{ok:true, ids:[]}`
170
+ 或把静态表冒充成权威清单(这是本项目反复踩过的坑,0.2.6~0.2.9 都在修它)。
171
+
172
+ keystone 提供端点 `POST /cli-bridge/settings/models`(body `{ provider: '<id>' }`),
173
+ 固定返回 HTTP 200 + `{ ok, provider, options, source, error }` —— 用 `ok` 表达业务失败,
174
+ 不用 HTTP 状态码,以免前端把"没拉到"当成网络错误而静默。client.js 渲染按钮、处理 loading
175
+ 与错误展示,并把拿到的 options 合并进当前字段(不落盘,纯展示态;用户选中后照常走 POST 保存)。
176
+
136
177
  ### 3.1 调度区(顶层 `_scheduling`,非 provider 分区 —— 新增 provider 零改动接入)
137
178
 
138
179
  `{DSH_HOME}/cli-bridge-settings.json` 顶层另有 `_scheduling` 区(下划线前缀 = 非 provider
@@ -233,3 +274,44 @@ export function check(env) → true | throw // --che
233
274
  node: 模块,dynamic 沙箱形态若拿不到 fetch 则 preflight 报 `DESKTOP_UNREACHABLE`
234
275
  并提示改用 mimocode 后端(保持旧包语义)。
235
276
 
277
+ ## 10. claude / codex 的实测事实(0.2.9 接入,改动前必读)
278
+
279
+ 两家都是**在真机上跑出来的**,下面每条都有单测或 `check()` 防线钉住,别凭直觉"顺手修正"。
280
+
281
+ ### claude(Claude Code,实测 2.0.76)
282
+ - 非交互:`claude -p <prompt> --output-format text|json|stream-json [--verbose]`。
283
+ `stream-json` 必须配 `--verbose`,否则只吐结果不吐事件。
284
+ - 事件:`system`(含 `session_id` / `model` / `tools` / `permissionMode` / `apiKeySource`)→
285
+ `assistant`(`message`)→ `result`(`subtype` / `is_error` / `result` / `session_id` /
286
+ `total_cost_usd` / `usage` / `modelUsage`)。
287
+ - **成功判定陷阱**:`is_error=true` 时退出码可能是 0 也可能是 1,且 `subtype` 仍可能是
288
+ `"success"`(无效模型实测就是这个形状)。判定必须 `is_error === false && exitCode === 0`。
289
+ - 续聊:`--resume <session_id>` / `-c|--continue` / `--fork-session`。
290
+ - 模型清单:**唯一权威来源 = 无效模型触发的服务端 400 报文**
291
+ `"The supported API model names are X, Y, ..."`。`~/.claude/settings.json` 的 env 块里
292
+ `ANTHROPIC_DEFAULT_*_MODEL` / `ANTHROPIC_MODEL` 是**用户配置的别名映射,不是服务端可用
293
+ 全集**(实测两者不一致),只能作诊断线索。
294
+ - 权限:`-p` 无 TTY,任何需要交互批准的动作都会挂到超时,故 auto 模式映射为
295
+ `--dangerously-skip-permissions`(或 plan 态 `--permission-mode plan`)。
296
+
297
+ ### codex(OpenAI Codex CLI,实测 0.98.0)
298
+ - 非交互:`codex exec [opts] <prompt>`;`--json` 出事件流,不加就是纯文本。
299
+ - 事件:`thread.started`(`thread_id`)→ `turn.started` → `item.completed`(`item`)→
300
+ `turn.completed`(`usage`)。
301
+ - **`codex exec` 不接受 `-a/--ask-for-approval`**(那是根命令 `codex` 的旗,clap 会报
302
+ unexpected argument)→ 审批只能 `-c approval_policy="…"`。
303
+ - **`codex exec resume` 不支持 `-C/-s/-o/--add-dir`** → cwd 交给 spawn 的 `cwd`,
304
+ 沙箱走 `-c sandbox_mode="…"`。
305
+ - **`item.completed` 里 `item.type==="error"` 是告警,不是致命错**(`resume --last` 会带一条
306
+ 「此会话原模型与当前模型不同」)。致命只有顶层 `error` / `turn.failed`;
307
+ 成功判据 = 退出码 0 + 收到 `turn.completed` + 无顶层错误。
308
+ - 非 git 目录必须 `--skip-git-repo-check`。
309
+ - 退出码:0 成功;1 运行错误(stdout 仍是合法事件流);2 参数错误。
310
+ - 配置/凭据:`~/.codex/config.toml`(`model` / `model_provider` /
311
+ `[model_providers.<name>]` 的 `base_url` + `experimental_bearer_token` /
312
+ `model_catalog_json`)、`~/.codex/auth.json`(`OPENAI_API_KEY` / `OPENAI_BASE_URL`);
313
+ `CODEX_HOME` 可覆盖目录。**模型清单默认只走 live**(`GET {base_url}/models`),
314
+ 本地 `model_catalog_json` / `models.json` 仅在显式 `source=catalog|builtin` 时返回。
315
+ - npm 全局安装得到的是 `.cmd` shim:无 shell 时 spawn 不了,走 shell 会把 prompt 二次解释。
316
+ 因此命中 `.cmd/.bat` 时**明确报错并指引指向真实 exe**,不静默套 shell、不静默降级。
317
+
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
@@ -2,30 +2,32 @@
2
2
 
3
3
  > 面向接手这个仓库/这台机器的下一个人(或下一个 Agent)。
4
4
  > 目标:**不读完所有历史也能安全改动、发布、排障**。历史事故与红线集中在 §5 与 §10。
5
- > 最近一次更新:2026-10-16(0.2.4:**第四个 provider OpenCode** 接入 + 状态灯淡出修复 +
6
- > 版本单一来源;运行时改为 **npm registry 标准安装**,不再依赖本地开发目录)。
5
+ > 最近一次更新:2026-10-20(**0.2.10:模型下拉改为「手动拉取」+ 修掉 0.2.9 里
6
+ > opencode/mimo 的 dynamic 形态 ReferenceError(模板漏片文件占位)+ 源码 EOL 规范化为 LF**)。
7
+ > 此前的 claude/codex 接入见 §14,OpenCode 见 §13。
7
8
 
8
9
  ---
9
10
 
10
11
  ## 1. 这是什么
11
12
 
12
- `@luchenglong/cli-bridge` 把**本机四个编码 CLI**(agy / CodeBuddy / MiMo / **OpenCode**)以同一套
13
- provider 描述符接入 DSH,并有四种落地形态(preset / 动态宿主 / 家级宿主插件+指示灯 / MCP stdio server)。
13
+ `@luchenglong/cli-bridge` 把**本机六个编码 CLI**(agy / CodeBuddy / MiMo / OpenCode /
14
+ **Claude Code** / **OpenAI Codex**)以同一套 provider 描述符接入 DSH,并有四种落地形态
15
+ (preset / 动态宿主 / 家级宿主插件+指示灯 / MCP stdio server)。
14
16
  它由三个旧包(`agy-first-bridge` / `codebuddy-first-bridge` / `mimo-first-bridge`)合并而来,
15
- 旧包的工具名、预设 id、策略文案**逐字保留**(兼容承诺,见 §5)。OpenCode 是统一包首个**新增**
16
- provider(无旧包可迁,无兼容包袱),见 §13。
17
+ 旧包的工具名、预设 id、策略文案**逐字保留**(兼容承诺,见 §5)。`opencode` / `claude` / `codex`
18
+ 是统一包**新增**的 provider(无旧包可迁,无兼容包袱),见 §13 与 §14。
17
19
 
18
20
  ## 2. 当前状态快照
19
21
 
20
22
  | 项 | 值 |
21
23
  | --- | --- |
22
- | 版本 | **0.2.4**(三处版本锁:`package.json` / `mcp/cli-bridge-mcp.mjs` 的 `SERVER_VERSION` / `CHANGELOG.md` 顶部;`verify.mjs` 机器比对) |
23
- | npm | `@luchenglong/cli-bridge@0.2.4`(`latest`;shasum `08ca3325376e96abc935435784106fd1ee158ba4`,68 files / 376.0 kB) |
24
- | GitHub | `new-256/cli-bridge`,`main` @ `acf6f09`;tag `v0.2.1` / `v0.2.2` / `v0.2.3` / `v0.2.4` |
24
+ | 版本 | **0.2.10**(三处版本锁:`package.json` / `mcp/cli-bridge-mcp.mjs` 的 `SERVER_VERSION` / `CHANGELOG.md` 顶部;`verify.mjs` 机器比对) |
25
+ | npm | `@luchenglong/cli-bridge@0.2.10`(`latest`;shasum 以 `npm view` 为准,100+ files) |
26
+ | GitHub | `new-256/cli-bridge`,`main` 见 `git log`;tag `v0.2.1` … `v0.2.10` |
25
27
  | 旧包 | 三个都已在 npm 标 **deprecated**(提示迁移到统一包):`agy-first-bridge@1.7.1`、`codebuddy-first-bridge@1.6.2`、`mimo-first-bridge@1.2.9` |
26
28
  | 旧仓 | `new-256/agy-first-bridge`、`new-256/mimo-first-bridge`:README 顶部已加迁移横幅且 **archived(只读)**;`codebuddy-first-bridge` 在 GitHub 上不存在 |
27
- | 测试 | **327 pass / 0 fail**(33 个 `*.test.mjs`),四闸门 `npm run check` 全绿 |
28
- | 本机运行时 | DSH Desktop;`dsh-home/profiles/web` 以 **npm registry 标准依赖** `@luchenglong/cli-bridge: "0.2.4"` + pnpm 快照部署(2026-10-15 起不再用 `file:`) |
29
+ | 测试 | **600 tests / 599 pass / 0 fail / 1 skip**(既有跳过),四闸门 `npm run check` 全绿 |
30
+ | 本机运行时 | DSH Desktop;`dsh-home/profiles/web` 以 **npm registry 标准依赖** + pnpm 快照部署(不用 `file:`)。⚠ 已发布 ≠ 运行时已切,切版本要用户自己做 |
29
31
 
30
32
  **已知遗留 / 未处理项**(均有意为之,非故障):
31
33
  - 家级补丁 `dsh-home\cordis.patch.yml` 里四条 MCP 行**仍是注释状态** —— 与旧版本交接文档
@@ -375,11 +377,103 @@ npm whoami # 应输出 luchenglong
375
377
 
376
378
  ---
377
379
 
380
+ ## 14. Claude Code / OpenAI Codex provider(0.2.9 新增)
381
+
382
+ 两家都是**在真机上探测**后实现的,不要凭直觉改 argv —— 有一条错就会被 CLI 直接拒(`unexpected argument`)。
383
+ 完整实测事实见 `PROVIDER-CONTRACT.md` §10,这里只记要点与坑。
384
+
385
+ ### 文件与命名
386
+ - `providers/claude.mjs`(508) + `claude-exe.mjs`(112) / `claude-models.mjs`(334) / `claude-policy.mjs`(37)
387
+ - `providers/codex.mjs`(544) + `codex-cli.mjs`(471) / `codex-models.mjs`(107)
388
+ - preset `preset/{claude,codex}-first/`;dynamic `dynamic/{claude,codex}.host.template.mjs`;MCP `mcp/providers/{claude,codex}.mjs`
389
+ - 工具:`claude_run/_continue/_status/_models`、`codex_run/_continue/_status/_models`
390
+
391
+ ### 关键差异(与 agy/codebuddy/mimo/opencode 比)
392
+ | 维度 | claude | codex |
393
+ | --- | --- | --- |
394
+ | 非交互 | `-p --output-format stream-json --verbose` | `exec --json` |
395
+ | sessionId | `result.session_id` | `thread.started.thread_id` |
396
+ | 续聊 | `--resume <id>` / `-c` | `exec resume <id>` / `--last` |
397
+ | 成功判定 | **`is_error===false && exit===0`**(subtype 不可靠!) | exit 0 + `turn.completed` + 无顶层 error |
398
+ | 模型清单 | 无效模型触发 400 报文 | `GET {base_url}/models`(live) |
399
+
400
+ ### 三个必须知道的坑
401
+ 1. **claude 的成功判定不能看 subtype**:无效模型时 `is_error=true` 但 `subtype` 仍是 `"success"`、
402
+ 退出码可能是 0 也可能是 1。只看 subtype 或只看 exit code 都会把失败当成功。
403
+ 2. **`codex exec` 不吃根命令的旗**:`-a/--ask-for-approval` 只属于 `codex`,`codex exec` 会报
404
+ unexpected argument;审批走 `-c approval_policy="…"`。同理 `exec resume` 没有 `-C/-s/-o/--add-dir`。
405
+ 3. **`item.completed` 的 `item.type==="error"` 是告警不是致命错**(`resume --last` 会带一条
406
+ 「此模型与原会话不同」)。把它当致命错会让正常续聊全判失败。
407
+
408
+ ### 配置/凭据(本机实际布局,都不是官方直连)
409
+ - claude 走了 **DeepSeek 的 Anthropic 兼容端点**:`~/.claude/settings.json` 的 env 块里
410
+ `ANTHROPIC_BASE_URL=https://api.deepseek.com/anthropic` + `ANTHROPIC_AUTH_TOKEN` +
411
+ `ANTHROPIC_DEFAULT_*_MODEL=deepseek-v4-pro[1M]`。所以实测可用模型是 `deepseek-flash,
412
+ deepseek-v4-pro`,**不是** Claude 官方模型名。
413
+ - codex 走了 **OpenRouter**:`~/.codex/config.toml` 里 `model_provider="custom"`、
414
+ `base_url=https://openrouter.ai/api/v1`、`experimental_bearer_token`。实时 `/models` 返回 469 项。
415
+ - 因此两家的"可用模型"完全取决于用户配的端点,**不能硬编码模型名**;只能实时问端点。
416
+
417
+ ### 手动验收脚本(会真联网/真花钱,故不进 `node --test`)
418
+ ```
419
+ node tests/e2e/lead-live-smoke.mjs # 走描述符的 buildArgv→真 CLI→parseJson→buildResult 全链路
420
+ node tests/codex/helpers/live-cli-smoke.mjs
421
+ node tests/claude/helpers/verify-real-cli.mjs
422
+ ```
423
+
424
+ ---
425
+
426
+ ## 15. 模型下拉的「手动拉取」与两条新坑(0.2.10)
427
+
428
+ ### 15.1 机制(契约 §3.2)
429
+ 面板**不再自动探测**模型清单。原因:探测要起子进程/联网,面板是同步回调 —— 拖慢首屏;
430
+ 且探不到时下拉空着,用户分不清"这家没模型"与"没拉到"。
431
+
432
+ 现在:`meta()` 只读缓存 → 字段声明 `refresh: { label: '拉取可用模型' }` 即渲染按钮 →
433
+ 点击 `POST /cli-bridge/settings/models {provider}` → keystone 调该 provider 的
434
+ `settings.refreshModels(settings, env)` → 返回 `{ok, ids, labels?, source, error?}` →
435
+ 前端把 `ids` 变成下拉选项(只更新展示态,选中后照常 POST 保存)。
436
+
437
+ **铁律**:失败必须 `ok:false` + 可操作错误,端点恒返回 **HTTP 200**(业务失败不用 4xx,
438
+ 否则前端会当网络错误静默吞掉)。`source` 会显示给用户(`account-live` / `live` / …)。
439
+
440
+ 已实现:codebuddy / opencode / mimo / claude / codex(agy 无模型字段)。
441
+ 各家的探测来源不同,别混:codebuddy=无效模型触发 400 报文;claude=同机制(Anthropic 兼容端点);
442
+ codex=`GET {base_url}/models`;opencode=`opencode models`;mimo=`mimo models`。
443
+
444
+ ### 15.2 坑一:dynamic 模板的片文件占位(**0.2.9 真实事故**)
445
+ 把 `providers/<id>.mjs` 拆成片文件(`<id>-*.mjs`)后,**必须确认 `dynamic/<id>.host.template.mjs`
446
+ 里有片文件占位行**,否则 build 不会内联那些片 —— 生成物里符号只有引用没有定义,运行时
447
+ `ReferenceError`(且**纯语法检查/new Function 求值都抓不到**,因为未定义标识符只在函数被
448
+ 调用时才炸)。0.2.9 就是这样让 opencode/mimo 的 dynamic 形态坏掉的。
449
+
450
+ 现在有永久防线:`tests/{opencode,mimo,claude,codex}/dynamic-template.test.mjs` 断言生成物里
451
+ 片文件符号的**定义数 = 1**(不是"出现过")。加新片文件时请照抄这个断言。
452
+
453
+ 排查手法:`node tests/e2e/sandbox-smoke.mjs [id]` —— 求值 + 逐片符号定义数一把过。
454
+
455
+ ### 15.3 坑二:EOL 必须 LF(否则别的机器 build --check 必然 drift)
456
+ `.gitattributes` 要求 `eol=lf`,但 **PowerShell 的 `Set-Content` / `[IO.File]::WriteAllLines`
457
+ 在 Windows 默认写 CRLF**。一旦源文件是 CRLF,build 按它拼出的生成物也是 CRLF,于是:
458
+ - 本机 `build --check` 可能通过(源和产物一起是 CRLF);
459
+ - 但别人 clone(git 按 eol=lf 检出)+ 跑 `build --check` → **必然 drift**;
460
+ - 严重时 git 会把生成物当二进制(`git ls-files --eol` 显示 `i/-text w/-text`)。
461
+
462
+ 用法:`node tests/e2e/normalize-eol.mjs`(干跑)/ `--apply`(规范化)。
463
+ 发版前建议:规范化 EOL → `node scripts/build.mjs` → `build --check` 必须 `up to date` →
464
+ `git ls-files --eol providers/ dynamic/` 全部 `w/lf`。
465
+
466
+ ---
467
+
378
468
  ### 交接最小检查单
379
469
 
380
- - [ ] `npm run check` 全绿(327 测试)
470
+ - [ ] `npm run check` 全绿(600 测试)
381
471
  - [ ] `node mcp/cli-bridge-mcp.mjs --check` 显示 `scheduling.source` 且版本与 package.json 一致
382
- - [ ] `GET $env:DSH_WEB_URL/cli-bridge/status` 返回**四家** + `scheduling`
383
- - [ ] 设置面板有 `总设置 | AGY | CB | MIMO | OPENCODE` 五个标签;OpenCode 卡能选模型并保存落盘
472
+ - [ ] `GET $env:DSH_WEB_URL/cli-bridge/status` 返回**六家** + `scheduling`
473
+ - [ ] 设置面板有 `总设置 | AGY | CB | MIMO | OPENCODE | CLAUDE | CODEX` 七类卡片;
474
+ 有「默认模型」的那几张,点「拉取可用模型」能拿到清单(拿不到会显示具体原因)
384
475
  - [ ] 起一个 opencode 任务:灯亮 → 完成后 **60 秒内灭**(§6.1)
385
- - [ ] 知道红线在 §5、生效时机在 §7.2、排障在 §10、OpenCode 在 §13
476
+ - [ ] 知道红线在 §5、生效时机在 §7.2、排障在 §10、OpenCode 在 §13、claude/codex 在 §14、
477
+ 手动拉取与两条新踩的坑在 §15
478
+ - [ ] **`PRESET_DIRS` 只在 `providers/registry.mjs` 一处** —— 加 provider 别再在
479
+ `build.mjs` / `preset-declarations.mjs` / `verify.mjs` 里各写一份(0.2.9 收敛的坑)
@@ -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 {