dsh-acp-enhanced 0.9.1 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README-zh.md CHANGED
@@ -103,8 +103,8 @@ ACP 线上。
103
103
 
104
104
  ## 快速开始
105
105
 
106
- **需要 `dsh ≥ 0.1.5-rc.2`**(`npm install -g @deepseek-ai/dsh@0.1.5-rc.2`,或下方 peer 范围内的
107
- 任意版本):本桥在每条受支持线(0.1.5-rc.2 直到 0.1.7)上只消费同一套已声明表面,不在运行期
106
+ **需要 `dsh ≥ 0.1.6-alpha.1`**(`npm install -g @deepseek-ai/dsh@0.2.0-rc.2`,或下方 peer 范围内的
107
+ 任意版本):本桥在每条受支持线(0.1.6-alpha.1 直到 0.2.0)上只消费同一套已声明表面,不在运行期
108
108
  探测更老的代际。
109
109
 
110
110
  本包遵循 dsh 官方插件规范(声明了 `dsh.bundle`),安装与官方组合包一致:**一条命令**
@@ -155,6 +155,20 @@ Zed 会用极简 PATH 拉起 agent,因此用随附启动器 `scripts/dsh-acp-z
155
155
  > (`DEEPSEEK_API_KEY`)由 dsh 凭据服务解析即可;启动脚本还会兜底继承正在运行的
156
156
  > `dsh web` 进程的 key。
157
157
 
158
+ 排查「卡住的轮次」(是模型请求卡住,还是工具卡住):
159
+
160
+ ```jsonc
161
+ "env": {
162
+ // ...上面的其它变量...
163
+ "ACP_LOG": "/Users/you/.dsh/dsh-acp-enhanced.trace.jsonl" // 追加写 JSONL 事件 trace
164
+ }
165
+ ```
166
+
167
+ 每行是一个会话事件,带墙钟 `time`(ms epoch);看上去「挂住」的轮次事后可归因:
168
+ **模型请求卡住**表现为 `step/start` 与首个 `assistant/chunk` 之间出现长间隔,**工具执行卡住**
169
+ 则表现为 `tool/call` 与 `tool/result` 之间长间隔(result 行带 `elapsedMs`)。
170
+ `prompt/settled` 行覆盖整条用户消息往返(stopReason + 耗时)。
171
+
158
172
  可选:固定面板默认项(都可随时在面板里改):
159
173
 
160
174
  ```jsonc
@@ -252,7 +266,7 @@ dsh --profile acp-enhanced --dump-config # 查看组合后的完整
252
266
  包名,id 可在 `--dump-config` 输出里查)定位:
253
267
 
254
268
  ```yaml
255
- - id: mnemon
269
+ - id: example-row
256
270
  disabled: true
257
271
  ```
258
272
 
@@ -280,7 +294,7 @@ profile 是一个**单一故障域**:`cordis-plugin-loader` 会等待每个条
280
294
 
281
295
  - **只新增模型侧工具/命令的插件** → 把行写进某个 preset composition。**0.1.7 线**上是
282
296
  profile 用户层里的一条 `@deepseek-ai/dsh-agent-preset` 行(见「自建 preset」);
283
- 0.1.5/0.1.6 上是 `$DSH_HOME/.agent-presets/<id>/` 目录(组合写 `agent.cordis.yml`,
297
+ 0.1.6 线上是 `$DSH_HOME/.agent-presets/<id>/` 目录(组合写 `agent.cordis.yml`,
284
298
  选择器里的名称写 `preset.yml`)。两种方式下 ACP 的 `agent_preset` 下拉都会列出它;
285
299
  组合加载失败的 preset 只会被标记为 broken 并从列表里剔除,不会拖垮进程。
286
300
  - **需要配置宿主服务的插件**(例如要覆写宿主 `web` 行 `searchProvider` 的搜索 provider)
@@ -296,21 +310,31 @@ dsh --profile acp-enhanced --dump-config # 每一行来自哪一层
296
310
 
297
311
  ## 兼容性
298
312
 
299
- 同一个桥只对应**一套已声明的 harness 表面**:**dsh ≥ 0.1.5-rc.2**(peer 范围
300
- `^0.1.5-rc.2 || ^0.1.6-alpha.1 || ^0.1.7-alpha.1`)。该范围内的**三条线**每次 CI 都会做真实启动
301
- 验证——握手、profile settle 与真实 `session/new`——另有跨代链接检查。桥只消费 harness
313
+ 同一个桥只对应**一套已声明的 harness 表面**:**dsh ≥ 0.1.6-alpha.1**(peer 范围
314
+ `^0.1.6-alpha.1 || ^0.1.7-alpha.1 || ^0.2.0-rc.2`)。该范围内的**三条线**每次 CI
315
+ 都会做真实启动验证——握手、profile settle 与真实 `session/new`——另有跨代链接检查,以及
316
+ `scripts/support-claim-test.mjs` 这个把本节声明与 npm 自己的 semver 对齐的守卫。桥只消费 harness
302
317
  **已声明**的表面:`docs/capability-seams.md` 里的服务、`docs/event-producer-consumer.md` 里的
303
318
  事件、以及已发布包的导出。`scripts/api-surface-check.mjs` 会对其他一切报错(CI 的阻塞步骤)。
304
319
  这里没有特性嗅探,也没有代际矩阵:0.1.7 只需要两处适配——一处**成员探测**
305
320
  (`presets.resolveMountable` 在 0.1.6 及以前是私有成员、0.1.7 直接删除,改用 `resolve()` 加该行
306
321
  自身的 `broken` 判定),以及 `cordis.patch.yml` 里一行**代际门控行**(agent-preset roster 被上游
307
- 重新打包,门控读取正在启动的安装自身 manifest 的版本,见下)。缺少任一者时会回退到旧形状,
308
- 两条路径都不会让启动失败。
322
+ 重新打包,门控读取正在启动的安装自身 manifest 的版本,见下);0.2.0 **一处都不用**(桥导入的每个
323
+ 包只变了版本号,新增的那个导出是纯增量)。缺少任一者时会回退到旧形状,两条路径都不会让启动失败。
324
+
325
+ 唯一会静默出问题的是**宿主自己的 peer 门禁**:从 0.1.7 起,`@deepseek-ai/dsh-app-boot` 会把每个
326
+ bundle 声明的 `@deepseek-ai/dsh*` peer 与正在运行的 CLI 版本逐一核对,不匹配时直接丢掉该 bundle
327
+ 的整层 patch,只在 stderr 留一行(`dsh: skipping profile bundle "…"`)。所以范围少写一条线不是
328
+ 告警,而是桥没有了——上面那个范围既是声明的,也是逐线真实启动过的。**安装**路径更严:从 0.1.7 起
329
+ 插件管理器还会解析 bundle 各 patch 文件里的**每一行 `name`**,对解析到的包跑同一个门控,只要有一个
330
+ 不匹配就拒绝整个安装(`incompatible-version`)——哪怕那一行在该代际已被门控设为 `disabled`。所以本桥
331
+ 只为真正链接的包声明 peer,≤ 0.1.6 的名册行则交给 CLI 自己的安装去解析。
309
332
 
310
333
  ### 支持策略
311
334
 
312
335
  | 桥版本 | 支持的 dsh 线 | 变化 |
313
336
  |---|---|---|
337
+ | **0.10.0** | `^0.1.6-alpha.1 \|\| ^0.1.7-alpha.1 \|\| ^0.2.0-rc.2` | 支持 0.2.0,同时**下限上移到 0.1.6-alpha.1**(放弃 0.1.5-rc.2:它的启动冒烟在 GitHub runner 上从未通过,未经验证的线不能留在声明里):`lib/` 一行都不用改,但声明的范围比宿主的 peer 门禁少了一条线——在 0.2.0 CLI 上,门禁已经丢掉整层 patch,而桥自己的 helper 还认为它受支持。≤ 0.1.6 的名册包也不再声明为 peer:安装路径会检查**每一行 patch 的 `name`** 解析出的包,所以一个被代际门控关掉的行也会让 0.2.0 上的 `dsh plugin add` 直接失败。现在 helper 与 npm 的上界语义一致(`<0.2.0-0`),并新增一个守卫,让范围、CI 矩阵与链接检查代际三者始终一致 |
314
338
  | **0.9.1** | `^0.1.5-rc.2 \|\| ^0.1.6-alpha.1 \|\| ^0.1.7-alpha.1` | 支持 0.1.7:agent-preset roster 上游换包,桥同时下发两种形状(代际门控行),并把四个 preset 声明内联进来 |
315
339
  | **0.9.0** | `^0.1.5-rc.2 \|\| ^0.1.6-alpha.1` | 只消费已声明表面;下限 0.1.5-rc.2;移除 `session/delete` |
316
340
  | 0.8.x | `^0.1.0-rc.6 … ^0.1.6-alpha.1`(未发布) | 0.1.3+ 实时 seam;0.1.5 持久化 handle API |
@@ -320,9 +344,11 @@ dsh --profile acp-enhanced --dump-config # 每一行来自哪一层
320
344
 
321
345
  - **新的 dsh API 线对应一次新的桥发布,而不是把运行期探测写得更宽。** 0.7.x 正是靠探测吞下
322
346
  0.1.1 → 0.1.5,也正是它悄悄腐烂的原因。0.1.7 属于重新打包而非新的 API 线,所以 0.9.1 用
323
- 一个 patch 版本吸收它——所需的探测只有一次成员检查,而不是一张代际矩阵。
324
- - **下限只随桥的 minor 移动,且绝不静默**:CLI 低于范围时启动器会在启动前告警,doctor 会以
325
- `RESULT FAIL — CLI too old` 停下。
347
+ 一个 patch 版本吸收它——所需的探测只有一次成员检查,而不是一张代际矩阵。0.2.0 连这一步都不
348
+ 需要:所有被消费的包只变了版本号,所以这次发布只是一次声明变更,外加保证声明的守卫。
349
+ - **下限只随桥的 minor 移动,且绝不静默**:CLI 落在范围之外时启动器会在启动前告警,doctor 会以
350
+ `RESULT FAIL — CLI too old`(低于下限)或 `RESULT FAIL — CLI line not supported by this bridge`
351
+ (越过上限)停下。
326
352
  - **放弃某条线的方式是发布一个明确这么说的桥**;旧线留在 `feat/dsh-0.1.3-plus-support` 分支上,
327
353
  供无法迁移的用户使用。
328
354
  - **在下一条线发布之前就盯住它**:定时 `canary` workflow 会安装 `alpha` dist-tag 并跑表面守卫、
@@ -347,13 +373,13 @@ standard/ptc/minimal/cordis 组合打进去、并作为只读 `system` root 前
347
373
 
348
374
  | 表面 | ≤ 0.1.6 | ≥ 0.1.7 | 桥的做法 |
349
375
  |---|---|---|---|
350
- | roster 行 | `@deepseek-ai/dsh-agent-presets` + `config.default` | `@deepseek-ai/dsh-agent-preset-registry` + `config.default` | 两行都下发,各自由代际门控 `disabled`,因此恰好只有一行激活(两者提供同一个服务名,第二次 `provide` 会抛错) |
376
+ | roster 行 | `@deepseek-ai/dsh-agent-presets` + `config.default` | `@deepseek-ai/dsh-agent-preset-registry` + `config.default` | 两行都下发,各自由代际门控 `disabled`,因此恰好只有一行激活(两者提供同一个服务名,第二次 `provide` 会抛错)。两行都从运行中 CLI 自己的安装闭包里解析——它们都不是本包的 `peerDependency`,0.10.0 正是为此移除了旧名册那条(见下) |
351
377
  | 随包 preset | 打在 roster 包内(`system` root) | 每个 preset 一条 `@deepseek-ai/dsh-agent-preset` 声明,谁需要谁下发(`@deepseek-ai/dsh-web-app` 以 `presets/*.patch.yml` 层下发) | 四条声明按 web-app 组合包原样(MIT,0.1.7-rc.2)内联进 `cordis.patch.yml`,并补回旧 roster 每个 preset 的 `preset.yml` 里的展示元数据——0.1.7 对内置 id 不再发布 `name` |
352
378
  | 可挂载解析 | 私有 `presets.resolveMountable(id)` | `presets.resolve(id)` 会**故意**返回损坏行;各挂载路径在解析之后才拒绝 | 成员探测:有 `resolveMountable` 就用它,否则 `resolve()` 加该行自身的 `broken` 理由 |
353
379
 
354
380
  门控读的是**正在启动的这套安装自身的身份**:打开 `profileContext.installAnchor`(即运行中 CLI
355
381
  自己的 `package.json`),用它的 `version` 决定形状(registry roster 从 0.1.x 线的 0.1.7 开始)。
356
- 0.1.5 上根本没有 `profileContext`,这本身就已经是「≤ 0.1.6」的答案;任何读不到、解析不了、
382
+ 更老的线上根本没有 `profileContext`(`≤ 0.1.6`),这本身就是答案;任何读不到、解析不了、
357
383
  归类不了的情况都保留旧行。`!!js` 表达式以 `with (ctx)` 在 loader context 加全局上求值,因此版本
358
384
  只能「读」而不能「问」——作用域里没有任何东西暴露它。
359
385
 
@@ -418,15 +444,80 @@ loader 要挂载的数据:
418
444
 
419
445
  两者出错时都是静默的:
420
446
 
421
- - **`dsh-free-search` 必须 ≥ 0.4.39**。更早的版本 import `SettingsProvider`,而 0.1.7 的
422
- `dsh-settings` 把它换成了 `SettingsForms`;该条目 import 失败、loader 继续跑,`web_search`
447
+ - **每条第三方 bundle 都要有为该线发布的版本**。旧版本可能 import `SettingsProvider`,而 0.1.7 的
448
+ `dsh-settings` 把它换成了 `SettingsForms`;该条目 import 失败、loader 继续跑,那个能力
423
449
  就这么消失了。`scripts/acp-doctor.mjs` 现在会把这种启动报告成降级(`RESULT DEGRADED` 并列出
424
450
  条目名、退出码 1),而不是 `RESULT READY`。
425
451
  - **`$DSH_HOME/.agent-presets/` 里的自建 preset 不再被发现**——见上文「自建 preset」。
426
452
 
453
+ ### 0.10.0 的变更(增量:支持 dsh 0.2.0)
454
+
455
+ `dsh` 0.2.0(RC 线,尚无 stable)是一次 **客户端/UI 版本**。本桥 import 的一切除版本号外都没变:
456
+ 它链接的 harness 包与 0.1.7-rc.2 的差异只在 `package.json`(逐包审计覆盖十个包),`@deepseek-ai/dsh-session`
457
+ 多了一个导出(`ToolCallRecovery`,上游修「工具调度失败后会话无法继续」),两份生成目录新增
458
+ `ctx.otel` 与 `ctx.productAnalytics`,**没有任何删除**——0 个服务、0 个事件消失。
459
+
460
+ 真正变了的是宿主的 **peer 门控**,这次发版就是为它:
461
+
462
+ | | ≤ 0.1.7 | 对停在 0.1.7 的桥,≥ 0.2.0 会怎样 |
463
+ |---|---|---|
464
+ | `dsh plugin add` 安装该 bundle | 正常安装 | 硬失败 **`incompatible-version`**,并提示可申请精确版本豁免 |
465
+ | 启动已带该 bundle 的 profile | bundle 正常加载 | CLI **整层丢掉该 bundle 的 patch 层**,只打一行 stderr:`dsh: skipping profile bundle "dsh-acp-enhanced": … peerDependencies {…}` |
466
+ | launcher 自己的告警 | — | *什么也没有*:旧辅助函数的上界没有带 semver 的 `-0`,于是它把 `0.2.0-rc.2` 报成「支持」,而宿主早已丢弃桥 |
467
+
468
+ 修了五处,都不在 `lib/`:
469
+
470
+ - 其余每个 `@deepseek-ai/dsh*` peer 都加上 `|| ^0.2.0-rc.2`;门控是拿*运行版本*逐个比对声明的
471
+ 范围,所以只要有一条没放宽,整个 bundle 就没了;
472
+ - `@deepseek-ai/dsh-agent-presets` 从 `peerDependencies` 中**移除**。它是 ≤ 0.1.6 各线的名册,而
473
+ 0.1.6 之后再无发布,把它声明成 peer 会让 pnpm 在**每个** 0.2.0 profile 里装上它——而*安装*路径
474
+ 检查的不只是清单:`bundleComponentManifests` 会把 bundle 各 patch 文件里**每一行的 `name`** 解析
475
+ 出来,对解析到的包跑同一个门控,于是那个根本没被 import 的 0.1.6 包会让整个安装以
476
+ `incompatible-version` 失败——即使范围已经放宽也一样。被拒的是**要装进去的东西**(新装的包,以及它
477
+ 自己的行与 peer 解析到的包);profile 里只是**已经有**一个不兼容的第三方成员时,只会收到警告、照常
478
+ 留在安装里,但在启动时被拒(升级它、移除它,或授予精确版本豁免——实测见迁移记录
479
+ `docs/plans/2026-09-30-dsh-0.2.0-support.md` §5 row 23)。行本身保留(在后续各代由代际门控设为
480
+ `disabled`),它仍然从运行中 CLI 自己的安装闭包里解析——这正是自 0.9.1 起兄弟行
481
+ `@deepseek-ai/dsh-agent-preset-registry` 一直用的机制。各线实测:旧名册包能从 0.1.6 的
482
+ 安装里解析到,从 0.1.7 与 0.2.0 解析不到,所以 0.1.6 上日志仍是*旧名册*的
483
+ `preset "…" not found`,0.1.7/0.2.0 上则是注册表的 `Unknown agent preset`;
484
+ - `scripts/lib/dsh-version.mjs` 现在把 caret 上界展开成 `<x.y.0-0`,与
485
+ `semver.satisfies(…, { includePrerelease: true })` 一致,因此上界版本自身的预发布版
486
+ (`0.2.0-rc.2` 对 `^0.1.7-alpha.1`)被正确判为*范围外*;doctor 的失败信息也区分「CLI 太旧」与
487
+ 「该 CLI 线未经验证」;
488
+ - `scripts/support-claim-test.mjs`(新增,CI 阻塞项)断言声明的范围、CI 启动矩阵、`compat-check`
489
+ 各代与辅助函数完全一致,辅助函数在每条线边界上与 npm 自己的 semver 一致,并且**每个声明的 peer
490
+ 都同时是 devDependency**——也就是 `lib/` 真正链接的那份闭包。最后这条保证了「只被某代 patch 行
491
+ 需要」的包不会再次混进 `peerDependencies`。
492
+ - **doctor 与启动器**会把它报出来。`acp-doctor.mjs` 只在 boot 确实因此挂掉时归为 `LAYER manifest-gate`
493
+ (崩溃签名优先于它;若 boot 仍开了线程则报 `DEGRADED <n> inactive items`——被跳过是少了项能力,
494
+ 不等于 profile 一定死),其 `FIX` 区分*本桥*与*第三方*;启动器把同一行 stderr 翻译成
495
+ `dsh-acp-zed: MANIFEST-GATE …`,且不占用它唯一那条致命提示的名额。
496
+ 这一步很关键:它是唯一一类不是堆栈的失败——启动会带着缺层继续跑,日志里不会有别的东西提醒你。
497
+
498
+ 两条门控路径都有覆盖:安装路径由四个矩阵格各自执行 `dsh plugin add`,而
499
+ `scripts/acp-smoke-keyless.mjs` 断言启动 stderr 里**没有** `skipping profile bundle` 行。
500
+
501
+ `cordis.patch.yml` 的代际门控不需要新边界:`minor > 1` 已把 0.2.0 归为注册表名册线,四条内联的
502
+ preset 声明与 `@deepseek-ai/dsh-web-app@0.2.0-rc.2` 的 `config` 子树相同(声明行本身另带本桥的
503
+ `disabled` 代际门控与展示元数据)。
504
+
505
+ **第三方 bundle 只能自己放宽门控。** 声明只含 0.1.x 的第三方 bundle 会在 0.2.0 profile 上被跳过,
506
+ 直到它的发布者自己放宽范围——本包修不了这个。被跳过的代价是**那条 bundle 的能力**,不是 profile。实测:用 0.1.6 CLI 建出的一个 profile
507
+ (桥 + 这样一条 bundle,在 0.1.7-rc.2 上是 `RESULT READY`),改由 0.2.0-rc.2 启动时,一个
508
+ 直接 spawn CLI 的独立 ACP 探针(不经 doctor/启动器)在 ~0.5s 内应答了 `initialize`、进程存活、开出了
509
+ `session/new` 线程,`session/prompt` 一路走到模型调用——用户层为空、以及用户层带一行配置被跳过 bundle 的
510
+ 条目(正是「别的行依赖它」那种形状)两种情况下都是如此。`scripts/acp-doctor.mjs`
511
+ 报的就是这个:`BOOT OK` + `DEGRADED <n> inactive items`。只有当 boot 确实因此挂掉时它才打
512
+ `LAYER manifest-gate`,而这对本桥意味着**自跳过**(连握手都没有),其 `FIX` 会区分两种情况,不会在被跳过
513
+ 的是别人的 bundle 时叫你去升级本桥。上游还有一条逃生口:当某个 bundle 在新线上没有发布时,可以用精确版本
514
+ 豁免 `dsh plugin allow-version <pkg@ver> --dsh-version <exact> --accept-risk --profile <p>`——实测能撤掉
515
+ 跳过、boot 照常稳定;但授予它就等于接下警告里写的崩溃/数据损坏风险,而那条 bundle 的代码在新线上是否真的
516
+ 能用,恰恰是没有验证的部分。
517
+
427
518
  ### 从已发布的 ≤ 0.7.0 升级
428
519
 
429
- npm 上的 `latest` 是 **0.7.0**,属于 0.1.3 之前的 API 线,因此桥和 CLI **必须一起动**——只升一半,
520
+ 0.1.3 之前的 API 线止于 **0.7.0**,它的桥和 CLI **必须一起动**——只升一半,
430
521
  两种顺序都会坏:
431
522
 
432
523
  | 顺序 | 结果 |
@@ -437,8 +528,8 @@ npm 上的 `latest` 是 **0.7.0**,属于 0.1.3 之前的 API 线,因此桥
437
528
 
438
529
  升级清单:
439
530
 
440
- 1. `npm install -g @deepseek-ai/dsh@0.1.5-rc.2`(或上面 peer 范围内的任意版本)。
441
- 2. `dsh plugin --profile acp-enhanced add dsh-acp-enhanced@0.9.1`。升级桥是显式动作:profile 里的依赖
531
+ 1. `npm install -g @deepseek-ai/dsh@0.2.0-rc.2`(或上面 peer 范围内的任意版本)。
532
+ 2. `dsh plugin --profile acp-enhanced add dsh-acp-enhanced@0.10.0`。升级桥是显式动作:profile 里的依赖
442
533
  是对 0.x 的 caret,所以 `dsh plugin update` **不会**自行把你带到新的 minor。
443
534
  3. 以前是从检出目录启动、或设过 `DSH_PATH`?旧启动器会自行切到 `~/.dsh-acp`,现在不会了。请在 Zed 的
444
535
  `agent_servers.env` 里设 `DSH_HOME=<那个 home>`,或在默认 home 里重建 profile。启动器若在那里
@@ -446,8 +537,8 @@ npm 上的 `latest` 是 **0.7.0**,属于 0.1.3 之前的 API 线,因此桥
446
537
  4. 以前照旧 README 在 profile 用户层里塞过 `subagent-model-selection-settings`?把它删掉:现在由桥的
447
538
  patch 提供该行,重复 id 会让启动中止。`scripts/init-acp-home.sh` 会自动清理;启动器会告警,doctor
448
539
  会点名该 id。
449
- 5. 确认 profile 里的第三方 bundle 支持你要升到的那条线(`dsh-free-search` 在 0.1.5 上 ≥ 0.4.24
450
- 已验证;在 0.1.7 上需要 ≥ 0.4.39,见「升级到 0.1.7 前要检查的两件事」)——profile 是单一故障域。
540
+ 5. 确认 profile 里每条第三方 bundle 都有为你要升到的那条线发布的版本(见「升级到 0.1.7 前要检查的
541
+ 两件事」)——profile 是单一故障域。
451
542
  6. 重启 Zed(或新开一个 agent 线程);先用 `node <pkg>/scripts/acp-doctor.mjs` 验证整条链路(它现在连
452
543
  开线程都会实测)。
453
544
 
@@ -456,7 +547,7 @@ npm 上的 `latest` 是 **0.7.0**,属于 0.1.3 之前的 API 线,因此桥
456
547
  `$DSH_HOME/profiles/node_modules` 是同 home 下所有 profile 共享的**同一个**依赖闭包,
457
548
  dsh 每次启动都会把它 heal 成最后启动的那个 CLI。因此:
458
549
 
459
- > 这是 **0.1.5 线**的行为。到 0.1.6-alpha.2 及之后,这个共享闭包已完全不存在(harness 从 CLI
550
+ > 这是**旧线**的行为。到 0.1.6-alpha.2 及之后,这个共享闭包已完全不存在(harness 从 CLI
460
551
  > 自身的安装位置解析;profile 的 `node_modules` 只放外部插件),所以启动器的漂移检查是「按线」
461
552
  > 的,路径消失时会静默跳过。
462
553
 
@@ -472,7 +563,7 @@ dsh 每次启动都会把它 heal 成最后启动的那个 CLI。因此:
472
563
  当前解析结果随时可查:
473
564
 
474
565
  ```sh
475
- node scripts/compat-check.mjs # 仅限仓库检出:分别安装 0.1.5-rc.2、0.1.6-alpha.2、0.1.7-rc.2 三套,逐一导入本桥
566
+ node scripts/compat-check.mjs # 仅限仓库检出:分别安装 0.1.6-alpha.2、0.1.7-rc.2、0.2.0-rc.2 三套,逐一导入本桥
476
567
  node <pkg>/scripts/acp-doctor.mjs # CLI 与闭包版本、bundle 列表,并真实启动一次(随包发布)
477
568
  ```
478
569
 
@@ -514,41 +605,47 @@ node <pkg>/scripts/acp-doctor.mjs --profile <name> --home <dsh-home> --timeout 6
514
605
  ```
515
606
 
516
607
  它会打印 CLI 与版本、home、profile、每个 bundle 及其版本、支持的 peer 范围与共享闭包版本,
517
- 然后把启动失败归入三层之一:
608
+ 然后把启动失败归入四层之一:
518
609
 
519
610
  | 层 | `dsh` stderr 里的特征 | 含义 | 修法 |
520
611
  |---|---|---|---|
612
+ | **manifest-gate** | `skipping profile bundle "<名称>": … peerDependencies {…}` | CLI 的 peer 门禁不认可某个 bundle 声明的 `@deepseek-ai/dsh*` peer,在 loader 看到它之前就丢掉了整层 patch。名字是 `dsh-acp-enhanced` 时丢的是本桥,连 ACP server 都没有;是第三方 bundle 时,那条 bundle 的行不会挂载——少的是它的能力,boot 照常继续,doctor 报 `DEGRADED` | doctor 的 `FIX` 会点名被跳过的那个 bundle——把 CLI 升/固定到*它*声明的范围内、升级该 bundle 本身,或把它从 profile 里拿掉。被跳过的若是第三方 bundle,升级本桥没有用 |
521
613
  | **link-time** | `does not provide an export named …`、`SyntaxError: The requested module …` | 启动的 CLI 闭包无法满足本桥的某个 import | 见 doctor 的 `LAYER link-time`:对齐代次——重启该 home 下其他 dsh 进程(共享闭包会愈合到最后启动的那个 CLI),或用 `DSH_PATH=<匹配的 dsh>` 锁定本启动器 |
522
614
  | **mount-time** | `failed to apply loader entry …`、`… requires … in the Host scope`、`duplicate loader entry id: …` | loader 拒绝了某一个条目并向上抛出,整棵插件树因此中止 | doctor 会打印 `SUBJECT <条目> (<模块>)`——补装缺失模块、在用户层禁用该行(`- id: <条目>` + `disabled: true`),或把 `dsh.profile.bundles` 收敛为 `@deepseek-ai/dsh-base` + `dsh-acp-enhanced`;若为重复 id,请从用户层删除该行(它归 bundle patch 所有) |
523
615
  | **run-time** | 握手成功后出现 `… is not a function` | 桥调用到了该 CLI 代次不提供的 harness 服务方法 | `npm install -g @deepseek-ai/dsh@<支持范围内的版本>`(见[兼容性](#兼容性)) |
524
616
 
525
- 启动器在 Zed 启动过程中会把同样三类特征翻译到 **stderr**(stdout 是 ACP 协议线),
526
- 所以 agent 日志里已经带有失败层与修法。
617
+ 启动器在 Zed 启动过程中会把同样四类特征翻译到 **stderr**(stdout 是 ACP 协议线——而
618
+ manifest-gate 那行是唯一不是堆栈的一类:boot 会继续跑、只是没有那条 bundle,所以翻译出来的
619
+ 这行就是日志里全部的证据),所以 agent 日志里已经带有失败层与修法。
527
620
 
528
621
  | 症状 | 定位 | 处理 |
529
622
  |---|---|---|
530
623
  | Zed 卡死无输出、线程始终不应答 | `node <pkg>/scripts/acp-doctor.mjs` | 会打印 `BOOT FAILED` 与 `LAYER`/`SUBJECT`/`FIX`,照 `FIX` 做即可。先应答 `initialize` 再立刻退出的 profile 也会被如实报出 |
531
624
  | `exec: dsh: not found`(status 127) | `which dsh` | 用随附 `dsh-acp-zed.sh` 启动器(自定位 node/dsh),或安装 CLI |
532
625
  | `no API key for provider route "xxx"` | `ls -l $DSH_HOME/.credentials.yaml` | 写入 `~/.dsh/.credentials.yaml`,或在 agent_servers 里设 `env.DEEPSEEK_API_KEY` |
533
- | `SyntaxError: … 'PresetMountError'` | agent 日志里的桥版本 | 你在 0.1.5 宿主上跑 0.9.0 之前的桥副本——升级本包 |
626
+ | `SyntaxError: … 'PresetMountError'` | agent 日志里的桥版本 | 你在 ≤ 0.1.6 宿主上跑 0.9.0 之前的桥副本——升级本包 |
534
627
  | `modelSelectionSettings requires … in the Host scope` | `dsh --profile acp-enhanced --dump-config \| grep subagent-model-selection` | `standard` preset 需要的宿主行缺失——该行由 bridge 的 bundle patch 提供,请重装/升级 bridge(`dsh plugin --profile acp-enhanced add dsh-acp-enhanced`),并检查用户层没有把它 `disabled: true` |
535
628
  | `duplicate loader entry id: <行>` | doctor 会打印 `LAYER mount-time` 与该 id | 两层都插了同一行。请从 profile 用户层(`$DSH_HOME/profiles/acp-enhanced/cordis.patch.yml`)删掉它——这类宿主行归 bundle patch 所有;`scripts/init-acp-home.sh` 会自动清掉遗留的 `subagent-model-selection-settings` 副本 |
536
629
  | 宿主升级后旧线程变空白 | `ls $DSH_HOME/sessions` | 会话存放在 `$DSH_HOME/sessions/<slug>/`;把旧 home 的历史拷进来(`scripts/init-acp-home.sh --copy-sessions`)即可继续 |
537
- | 旧线程打不开:`Internal error … dsh-session-format-v0-to-v1 refuses this format v0 Session: … source summary requires notice form … (raw log: <路径>)` | 报错里的 `raw log` 路径 | 这是 dsh CLI 内置 frozen v0→v1 迁移在拒绝旧第三方 bundle 写进会话的数据——所有 dsh 客户端都会遇到,并非只有经本桥的 ACP,且迁移绝不改写原文件。已知案例:`dsh-mnemon` ≤0.5.6(注入带 `form`+`summary` 的消息)与 `dsh-message-edit` 的 `message-edit/version` 事件。`dsh-mnemon` ≥0.5.7 自带 `bin/repair-legacy-session.mjs`(输出修复副本,原件保留);升级写入方 bundle 后新会话不再携带该写法 |
630
+ | 旧线程打不开:`Internal error … dsh-session-format-v0-to-v1 refuses this format v0 Session: … source summary requires notice form … (raw log: <路径>)` | 报错里的 `raw log` 路径 | 这是 dsh CLI 内置 frozen v0→v1 迁移在拒绝旧第三方 bundle 写进会话的数据——所有 dsh 客户端都会遇到,并非只有经本桥的 ACP,且迁移绝不改写原文件。当时的写入方会注入带 `form`+`summary` 的消息或 `message-edit/version` 事件;写入方 bundle 的后续版本通常自带修复脚本(输出修复副本,原件保留)。升级写入方 bundle,新会话就不再携带该写法 |
538
631
  | 无法切换模型 | `ACP_DEBUG=1 dsh --profile acp-enhanced`,然后尝试切换 | 携带的 `reasoning_effort` 在目标模型上不受支持:本桥按模型记住上次使用的强度(随 profile 持久化),会回退到该模型默认值而不是让切换失败。另检查路由是否真实——幽灵 provider 会被过滤,只广播 `config.provider` 的模型 |
539
632
  | 上下文用量不显示 | 线程里执行 `/status` | 选到了不可路由的"幽灵 provider";确认 profile 的 provider 指向真实路由 |
540
633
  | 轮次以 usage 结束但**面板没有回复文本**(空白) | `ACP_DEBUG=1`,看是否有 `agent/assistant-stream frame=chunk` | 0.9.0 起唯一的实时 seam 是 `agent/assistant-stream` 帧,某个 step 完全没有上线文本时由已提交的 `assistant/message` 兜底。有帧却无文本 = 客户端渲染问题;完全没有帧 = 正在走兜底路径(桥太旧就升级) |
541
634
  | 升级 dsh 后报 `Unknown agent preset: <id>` | `ls $DSH_HOME/.agent-presets` 与 profile 的 `cordis.patch.yml` | 该 preset 已不在名册里——0.1.7 起不再扫描 `$DSH_HOME/.agent-presets`。把它改成一条 `@deepseek-ai/dsh-agent-preset` 声明行(见「自建 preset」);0.9.1 下*空*会话会先用名册默认值打开(stderr 有说明),但恢复已跑过它的线程在补上行之前仍然失败 |
542
- | profile 以前有的能力静默消失(例如 `web_search`) | `node <pkg>/scripts/acp-doctor.mjs`——降级启动会打印 `DEGRADED <n> loader entries never activated` 并列出条目名 | 某个 entry 导入失败不会拖垮 profile,它只是不存在。给这条 dsh 线升级该 bundle——`dsh-free-search` 在 0.1.7 上需要 ≥ 0.4.39,因为 0.4.24 import 的 `SettingsProvider` 已被 `dsh-settings` 删除——或直接移除它 |
635
+ | profile 以前有的能力静默消失 | `node <pkg>/scripts/acp-doctor.mjs`——降级启动会打印 `DEGRADED <n> inactive items` 并列出条目名 | 某个 entry 导入失败不会拖垮 profile,它只是不存在。该 bundle 是为另一条线构建的:它 import 了本线已删除的导出(0.1.7 用 `SettingsForms` 替换了 `dsh-settings` 里的 `SettingsProvider`)。给这条 dsh 线升级该 bundle,或直接移除它 |
636
+ | 升级 dsh 后出现 `dsh: skipping profile bundle "<名称>": … peerDependencies {…}`——整个 bundle 没了 | 那行 stderr、agent 的 stderr,或 `acp-doctor.mjs`(boot 因此挂掉时报 `LAYER manifest-gate`——也就是自跳过——仍能用则报 `DEGRADED`;两种情况都意味着那条 bundle 的能力没了) | CLI 的 peer 门禁:从 0.1.7 起 `dsh-app-boot` 会把每个 bundle 声明的 `@deepseek-ai/dsh*` peer 与运行版本核对,不匹配就丢掉它的整层 patch(安装路径更会直接以 `incompatible-version` 拒绝)。只有该 bundle 的发布者能放宽范围——升级它(`dsh plugin --profile acp-enhanced add dsh-acp-enhanced@0.10.0`),或把 CLI 固定到该 bundle 声明的线上。本桥声明了上面三条线;被跳过的是第三方 bundle 时,是*它*的范围止步于旧线,升级本桥没有用 |
543
637
  | 会话侧边栏空白或迟迟不出现(dsh 0.1.7 上的 ≤ 0.9.0 桥) | `ls $DSH_HOME/sessions \| wc -l`,以及 agent stderr 里的 `timeout: session/list` | 修复前的列表会解码每一个已存日志(见「0.1.7 对『会话库很大』意味着什么」);几百个会话就会超过 30s 线路超时。升级桥到 ≥ 0.9.1——现在秒级返回,其余标题以 `session_info_update` 陆续送达 |
544
638
  | 改了插件却不生效 | profile `cordis.patch.yml` 的 mtime | 改动只在**下一个**进程生效:新开 agent 线程(或重启 Zed) |
639
+ | Stop 之后线程直接死掉:`Internal error: prompt was not queued: Cannot read properties of null (reading 'kind')`,同一线程里改配置项也报 `{"details":"Cannot read properties of null (reading 'kind')"}` | `ACP_DEBUG=1`——该线程最后一条 `turn/end` 的 `reason=null`,再次加载它也一样失败 | 桥 ≤ 0.9.1 把 `new Error(...)` 当作 harness 的取消原因,而 `AgentCancelCause` 是封闭联合(`{kind:'user'\|'parent'\|'disposed'\|'hook'}`)。harness 自己的轮次收尾因此撞上 `assertNever` 抛错,落盘 `turn/end {reason: null}`;按 `reason.kind` 折叠它的会话 projection 会直接抛错,于是该会话之后每次 projection 读取(prompt、改配置、`session/load`)都失败。该线程本身无法恢复——新开一个;升级桥即可消除根因(下游用 `reason?.kind` 只是不再抛错,坏事件仍留在日志里) |
545
640
  | 需要详细诊断 | — | `ACP_DEBUG=1`(stderr 生命周期 trace)与 `ACP_LOG=/tmp/acp.jsonl`(逐事件 JSONL,带耗时) |
546
641
 
547
642
  ## 开发
548
643
 
549
644
  ```sh
550
645
  pnpm install # 安装开发依赖(仓库锁定 CLI 与测试脚本)
551
- node scripts/compat-check.mjs # 支持线上的链接检查(0.1.5-rc.2 / 0.1.6-alpha.2 / 0.1.7-rc.2 临时安装)
646
+ node scripts/compat-check.mjs # 支持线上的链接检查(0.1.6-alpha.2 / 0.1.7-rc.2 / 0.2.0-rc.2 三套临时安装)
647
+ node scripts/support-claim-test.mjs # 声明范围、CI 矩阵与 helper 和 npm semver 一致(CI 阻塞步骤)
648
+ node scripts/boot-classify-test.mjs # 仍可用的 boot 上被跳过只是 DEGRADED 而非失败;崩溃签名优先于跳过行(CI 阻塞步骤)
552
649
  node scripts/api-surface-check.mjs # 公开表面守卫:不得使用未声明的 harness API(CI 阻塞步骤)
553
650
  node scripts/pack-check.mjs # 包完整性:入口文件、权限位、所引用文件是否都随包发布(CI 阻塞步骤)
554
651
  node scripts/acp-client.mjs # 端到端冒烟(需要 API key)
@@ -562,6 +659,7 @@ node scripts/stored-titles-test.mjs # session/list 标题读取器:读取量
562
659
  node scripts/session-facts-test.mjs # 会话日志折叠:runningPreset/isBlank,live 与 stored 共享同一契约(无网络)
563
660
  node scripts/context-window-test.mjs # request/context 折叠:恢复后的环保留分母(无网络)
564
661
  node scripts/tool-result-test.mjs # tool/result 的 id 与正文提取,覆盖各支持代的形状(无网络)
662
+ node scripts/cancel-cause-test.mjs # 取消原因契约:每处 agent.cancel() 都传 AgentCancelCause 联合成员(无网络)
565
663
  node scripts/replay-order-test.mjs # 重放/回退的分块顺序:思考块先于它产出的回复(无网络)
566
664
  node scripts/acp-image-e2e.mjs # 图片能力端到端(vision 模型段需 API key)
567
665
  node scripts/acp-message-fallback-test.mjs # 实时 seam + assistant/message 回退:seam 确实触发且回复恰好到达一次
@@ -570,7 +668,7 @@ node scripts/acp-doctor.mjs # 真实启动一次 profile,指出失
570
668
  scripts/init-acp-home.sh # 可选:引导**独立** home(启动器不会自行切过去)
571
669
  ```
572
670
 
573
- harness 包的 devDependency 用该线的 prerelease range(当前 `^0.1.7-alpha.1`,会解析到
671
+ harness 包的 devDependency 用该线的 prerelease range(当前 `^0.2.0-rc.2`,会解析到
574
672
  锁定的 `@deepseek-ai/dsh` CLI 自己声明的精确闭包),让仓库依赖树与全新 CLI 安装解析出
575
673
  同一个连贯家族——在此用精确 patch
576
674
  锁定、与 CLI 的 range 闭包混存会得到分裂闭包(同名包两个版本),profile 启动时报