# openharness-reader 适配 DSH 0.1.1-rc.2 调研报告 > 调研日期:2026-08-23 · 目标版本:`@deepseek-ai/dsh@0.1.1-rc.2` · 基准版本:`0.1.0-rc.7`(本插件 v0.4.3 当前适配线) > 约束:本文档为纯调研产物,**未修改任何代码**。 --- ## 一、结论(TL;DR) **插件的全部核心依赖面在 0.1.1-rc.2 中保持兼容,没有发现任何破坏性变更(breaking change)。** 适配工作量很小,属于"版本声明刷新 + 回归验证"级别: | 级别 | 事项 | 性质 | |------|------|------| | 必做 | 放宽 `package.json` 里 5 个 `@deepseek-ai/dsh-*` 的 peerDependencies 范围,使其覆盖 `0.1.1-rc.2` | 元数据修改,不改逻辑 | | 建议 | devDependencies 同步升到 `0.1.1-rc.2`,重跑 `npm run typecheck && npm test` 对着新类型校验一遍 | 验证动作 | | 建议 | 在真实 0.1.1-rc.2 环境做一轮手工回归(见 §五 清单) | 验证动作 | | 可选 | README「版本要求」段落补一句 0.1.1 兼容说明 | 文档 | **不需要**改 host 半的 fs RPC、不需要改设置注册、不需要改客户端挂载方式、不需要改打包格式。 --- ## 二、前提澄清:本机现状与版本线 调研时确认了一个重要事实——**本机正在运行的 DSH 其实还没升级**: - `~/Library/Application Support/app.openharness.work/config.json` 中 `dshVersionLocked: "0.1.0-rc.7"`、`dshUpdateMode: "manual"`; - `dsh-version.json` 记录运行版本 `0.1.0-rc.7`; - 系统提示指向的 npx checkout(`npm-cache/_npx/2ede61d9d…`)整套包均为 rc.7。 因此本次调研的 0.1.1-rc.2 证据来自 **npm registry 上发布的真实工件**:把插件依赖到的 14 个包按 `rc.7` 与 `0.1.1-rc.2` 双版本下载解包、逐文件 diff。版本线如下(npmmirror,dist-tags `latest = next = 0.1.1-rc.2`): ``` … → 0.1.0-rc.6 → 0.1.0-rc.7 → 0.1.0-rc.8 → 0.1.1-rc.1 → 0.1.1-rc.2 ``` > 注:中间还有一个 `0.1.0-rc.8`;如想小步走也可先验 rc.8,但本报告按你指定的 0.1.1-rc.2 给结论。 --- ## 三、依赖面逐项对比(核心证据) ### 3.1 宿主半(host half) | 插件用到的接口 | 来源包 | rc.7 → 0.1.1-rc.2 差异 | 判定 | |---|---|---|---| | `FileSystem` 缝隙全套:`resolve / stat / readText / listDir / writeText / editText / lstat / processPath`、`displayPath`、写意图 `{kind:'replaceIfVersion'\|'createIfAbsent'}` | `dsh-fs` | **lib 目录零差异**(仅 README/package.json 变) | ✅ 无需改 | | 策略事件 `fs/write-intent`、`fs/edit-intent`、`fs/observed`(`ctx.waterfall` / `ctx.emit`) | cordis + dsh-fs 语义 | 语义载体未变 | ✅ 无需改 | | `FsErrorCode / FsTarget / FsVersion / FileSystem` 类型 | `dsh-fs` | 同上,零差异 | ✅ 无需改 | | `RpcResult`(从 `dsh-host-apiproxy/api` 导入) | `dsh-host-apiproxy` | `api/index.d.ts` 未变,`export type { … RpcResult } from './rpc.ts'` 原样保留(第 59 行核实) | ✅ 无需改 | | `ConnectionRpcHandler`、`connection.rpc.handle(channel, handler, { authority: 'loopback' })` | `dsh-client-connection` | **根 `types/rpc.d.ts` 零差异**(handler 签名、authority 选项原样) | ✅ 无需改 | | `installSettingsSection(ctx, settingsNamespace(NS), SCHEMA, defaults, { setSource, onChange })` | `dsh-settings` | **lib 目录零差异** | ✅ 无需改 | | `ctx.get('agents')` → `session.cwd` 解析 | 运行时约定 | 未发现变化 | ✅ 无需改 | ### 3.2 客户端半(client half) | 插件用到的接口 | 来源包 | 差异 | 判定 | |---|---|---|---| | `window.__ModuleLoader__.load({ id, factory })` 客户端模块格式 | dsh 客户端模块系统 | 新版 `dsh-client-web/lib/index.js` 机制原样 | ✅ 无需改 | | `window.__DSH_BOOT__` 启动注入 | `dsh-web-app` / `dsh-client-web` | 两版 lib 中均存在该符号 | ✅ 无需改 | | 浏览器模块表外部化 `react / react-dom / dsh-client-runtime / …`(build.mjs CLIENT_EXTERNALS) | `dsh-client-web` | 新版第 114 行仍有 `"react": React` 模块注册 | ✅ 无需改 | | `SessionListState`(type-only 导入自 `dsh-client-runtime/client`) | `dsh-client-runtime` | `sessions/service.d.ts` 两版相同;`client/index.d.ts` 只**新增**导出(`abbreviateHomePath`、`sessionRecallLabels`),无删改 | ✅ 无需改 | | `ctx.connection.rpc.call(channel, endpoint, payload)` | `dsh-client-connection` | caller 签名不变;新增可选 `createWebConnectionRpc(doFetch?)` 传输覆盖参数(纯增量) | ✅ 无需改 | | `settings.plugin.item` keyed 槽位卡片 | `dsh-client-ui-settings-plugins` | 该槽位归属此包,**两版均存在**且用法一致 | ✅ 无需改 | | `ctx.get('settingsScope').bind({ namespace })` → `{ getSnapshot, subscribe, set, unset }` | `dsh-client-ui-settings` | 服务仍由该包提供;内部重构为"共享 describe 镜像派生"(新增 schema.d.ts / settings-mirror.d.ts),**公开 `bind(spec)` 契约不变**;快照三态 `loading/ready/unavailable` 语义保留 | ✅ 无需改(行为等价重构) | | `slots.inject('settings.plugin.item', cb)` / `slots.register({name,key}, render)` | `dsh-client-ui-slots` | 类型文件仅注释变化("web-react" 字样改为 "ui-renderer") | ✅ 无需改 | | `--dsw-alias-*` 主题 CSS token | web frontend | 新版 frontend bundle 与新增 `base.css` 中均存在 | ✅ 无需改 | | `dsh.bundle.patch`(cordis.patch.yml insert 行)+ `dsh.client.inject` 注入声明 | package.json `dsh` 字段契约 | `bundle.patch` 消费点在新版 web-app boot 中原样保留 | ✅ 无需改 | ### 3.3 底座版本 | 依赖 | rc.7 | 0.1.1-rc.2 | 影响 | |---|---|---|---| | `@deepseek-ai/cordis` | ^4.0.1 | ^4.0.1(元包未变) | ✅ 插件 peer `cordis ^4.0.1` 继续有效 | | `@deepseek-ai/schemastery` | 3.18.x | 未变 | ✅ | | react | 应用自带 | 应用浏览器模块表仍提供 `"react"` | ✅ 插件外部化策略继续成立 | --- ## 四、需要做的调整(实施清单) ### 4.1 【必做】peerDependencies 范围放宽 现状(package.json): ```json "@deepseek-ai/dsh-client-connection": "^0.1.0-rc.6", "@deepseek-ai/dsh-client-runtime": "^0.1.0-rc.6", "@deepseek-ai/dsh-client-ui-settings":"^0.1.0-rc.7", "@deepseek-ai/dsh-settings": "^0.1.0-rc.6" ``` 问题:semver 里 `^0.1.0-rc.6` 等价于 `>=0.1.0-rc.6 <0.2.0`,但预发布版本只匹配**同一 major.minor.patch 三元组**上的比较器——`0.1.1-rc.2` 的三元组是 `0.1.1`,不等于比较器上的 `0.1.0`,所以**不匹配**。后果:在 0.1.1-rc.2 宿主上安装本插件会产生 peer 不满足警告(pnpm/npm 视严格程度可能 ERESOLVE)。 推荐改法(显式枚举支持线,最稳妥): ```json "@deepseek-ai/dsh-client-connection": "^0.1.0-rc.6 || ^0.1.1-rc.2", "@deepseek-ai/dsh-client-runtime": "^0.1.0-rc.6 || ^0.1.1-rc.2", "@deepseek-ai/dsh-client-ui-settings":"^0.1.0-rc.6 || ^0.1.1-rc.2", "@deepseek-ai/dsh-settings": "^0.1.0-rc.6 || ^0.1.1-rc.2", "@deepseek-ai/schemastery": "^3.18.1", // 不变 "@deepseek-ai/cordis": "^4.0.1" // 不变 ``` 备选:直接 `"*"` 或 `">=0.1.0-rc.6"`(后者同样受预发布匹配规则限制,覆盖不了 `0.1.x` 后续三元组;`*` 最省心但失去版本护栏)。若后续 DSH 发 `0.1.2-rc.x`,需要再追加一条 `|| ^0.1.2-rc.x`。 ### 4.2 【建议】devDependencies 升级 + 全量校验 ```bash # 把 @deepseek-ai/dsh-client-ui-settings / dsh-fs / dsh-settings 的 dev 版本升到 0.1.1-rc.2 npm run typecheck # 对着 0.1.1-rc.2 的 .d.ts 重新过一遍 npm test # tests/client-smoke.mjs + tests/host-rpc.mjs npm run build # 重出 bundle,确认外部化解析正常 ``` 预期全部通过(依据 §三 的零破坏证据);若有报错即为需要跟进的真实差异点。 ### 4.3 【建议】真机回归清单(升级 DSH 后过一遍) 1. **设置卡片**:设置 → 插件 → 插件配置 里出现 openharness-reader 卡片;改 `maxReadBytes` 保存 → 刷新后仍在(scope 写路径 + revision 栅栏); 2. **fs RPC 六端点**:浏览 / stat / 读 / 写 / 编辑 / 重命名各一次; 3. **冲突对话框**:编辑器开着文件时用 agent 工具改同一文件,再保存应弹冲突(`replaceIfVersion` CAS 链路); 4. **只读保护**:打开 >5MB 文件与二进制文件确认只读降级; 5. **挂载与布局**:body portal 面板出现、共享宽度推挤、折叠竖条、窄屏抽屉; 6. **i18n**:zh / en 两语言文案正常。 ### 4.4 【文档】README「版本要求」补一句 现有表述「设置卡片需 dsh >=0.1.0-rc.6」可补充:「已验证兼容 0.1.1-rc.2」。等真正动手改时一起做。 --- ## 五、明确**不需要**做的事(防过度适配) - ❌ 不需要改 host 半 fs RPC / 策略事件派发(`dsh-fs` lib 零差异); - ❌ 不需要改 `installSettingsSection` 注册方式(`dsh-settings` lib 零差异); - ❌ 不需要改 RPC 通道注册与 authority 声明(connection 根类型零差异); - ❌ 不需要改客户端挂载(`__ModuleLoader__` / `__DSH_BOOT__` / body portal 契约全在); - ❌ 不需要跟进以下 0.1.1 生态变化(对本插件无影响,仅记录在案): - 大量 runtime 包把 dependencies 迁移为 peerDependencies; - `dsh-client-ui-settings` 不再把 react 列为 peer(内部渲染重构所致;插件自绘卡片壳,duck-type 不 import 官方组件,不受影响); - 新增包 `@deepseek-ai/dsh-client-ui-renderer`(slots 渲染器从 web-react 拆出;插件未依赖 web-react); - connection 新增 `ClientTransportHooks` / `RpcFetch`(worker preview 场景的可选传输钩子,默认路径不变); - `/api` `maxRequestBodyBytes` 默认值文档化为 300 MiB(仅注释)。 --- ## 六、关于"每 10 分钟检查、中断自动续跑"的 loop **能实现,且本次已经实际生效了一次。** 采用的是本会话的持久化目标机制(goal): - 已创建 goal:*「调研 0.1.1-rc.2 适配并产出本报告;未完成而停滞/中止即自动续跑」*; - 本次会话中途确实被中断过一次,goal 自动转为 disarmed;你发"怎么停了/继续"后我执行 resume 重新武装,从断点(下载对比尚未开始处)续跑到了现在——这就是它兜底的样子; - 报告一旦交付,goal 即标记完成,loop 自然结束,不会空转。 诚实说明边界: 1. goal 的自动续跑轮次由 harness 驱动,**不是严格的 cron**——没有"精确每 10 分钟"的墙钟定时器原语;后台 job 可以 sleep 600 秒模拟节拍,但它无法唤醒一个已经结束的会话,所以对"会话死了"的场景只有 goal 这层持久化是有效的; 2. 若要真正的系统级定时器(crontab / 宿主内建调度),必须改宿主配置或加代码,违反你"不做任何代码修改"的约束,故未采用; 3. goal 是**同会话**持久化:换一个全新会话不会自动接续(届时把本报告路径发给新会话即可无缝继续)。 --- ## 七、附:证据与复现 - 双版本解包目录(临时区,重启后可能被清理):`$TMPDIR/tmp.RxilcYSJcF/dsh-diff/{rc7,new}//` - 覆盖包:`dsh`、`dsh-web-app`、`dsh-client-web`、`dsh-client-modules`、`dsh-client-runtime`、`dsh-client-connection`、`dsh-client-ui-settings`、`dsh-client-ui-settings-plugins`、`dsh-client-ui-slots`、`dsh-web-frontend`、`dsh-settings`、`dsh-fs`、`dsh-host-apiproxy`、`dsh-client-ui-primitives`(registry 存在性核对) - 复现核心命令: ```bash curl -sS https://registry.npmmirror.com/@deepseek-ai%2Fdsh | jq '.dist-tags, .versions | keys' # 逐包下载双版本 tarball 并 diff -rq rc7/ new/ ``` - 本机升级 DSH 的入口:App 内更新,或手动解除 `config.json` 的 `dshVersionLocked` 后 `npx @deepseek-ai/dsh@0.1.1-rc.2 web`(升级后记得按 §4.3 回归)。