dsh-ui-update
给 DeepSeek Harness 的 Web 界面加一个更新入口:设置 → 通用 → 「DSH 更新」。
一行设置项,显示当前版本、目标通道上的最新版本,并在有更新时提供一键更新按钮
(后台执行 npm install -g @deepseek-ai/dsh@<tag>,完成后提示重启 DSH)。
发布通道
默认盯 next 通道(DSH 的日常发布走 next,latest 常常停在更早的稳定 rc 上)。
界面上的版本信息会带一个「通道 next」标记,明确告诉你比的是哪条线。
改通道不用动代码,编辑 cordis.patch.yml 里的 config.channel 再重启 DSH 即可:
- insert:
- id: dsh-ui-update
name: 'dsh-ui-update'
config:
channel: next # latest / next / alpha,或任意已存在的 dist-tag
channel 会被 TAG_PATTERN 校验,非法值一律忽略并退回 next。
POST /api/dsh-update/apply 仍然接受显式 {"tag": "..."}(已知 dist-tag 或精确版本),
所以想临时装某个具体版本不需要改配置。
界面行为
| 状态 | 表现 |
|---|
| 尚未检查 | 显示「当前版本 / 最新版本 = 未知」与检查按钮 |
| 已是最新 | 绿色「已是最新」,没有更新按钮 |
| 有新版本 | 「有新版本可用」+ 版本号 + 通道名,出现「立即更新」按钮 |
| 更新中 | 按钮变为「更新中…」,下方实时显示 npm 输出(每 2s 轮询) |
| 更新成功 | 「重启 DSH 后生效」,并显示已装版本与校验结论 |
| 校验未通过 | 红色「安装完成,但启动校验未通过…」+ 具体缺失/残留说明 |
| 更新失败 | 「更新失败」+ exit code + 日志尾部 |
| 查询失败 | 显示 registry 错误(例如离线),不误报「已是最新」 |
安装
# 从 GitHub 安装
dsh plugin --profile web add github:DaXiangzi666/dsh-ui-update
# 或装本地目录;开发时用 link: 更顺手,改完重启 DSH 就生效
dsh plugin --profile web add link:E:\projects\harness\dsh-ui-update
dsh plugin 会在 %USERPROFILE%\.dsh\profiles\web 下调用 pnpm,并把本插件自动登记进
dsh.profile.bundles。装完重启 DSH(关掉再执行 dsh web)——宿主半边的 HTTP 路由
只在进程启动时注册。
若 pnpm 不可用,可以手工装:把 dsh-ui-update 目录放进
%USERPROFILE%\.dsh\profiles\web\node_modules\,再把 dsh-ui-update 追加到
%USERPROFILE%\.dsh\profiles\web\package.json 的 dsh.profile.bundles 数组里。
卸载
dsh plugin --profile web remove dsh-ui-update
HTTP 接口
全部仅允许回环来源(对端地址为 127.0.0.0/8 或 ::1,且 Host/Origin 也必须
是字面量回环主机名,防 DNS rebinding),写操作还要求 content-type: application/json。
| 方法 | 路径 | 说明 |
|---|
GET | /api/dsh-update/state | 状态快照(当前版本、通道、dist-tags、更新任务) |
POST | /api/dsh-update/check | 强制刷新 registry 上的 dist-tags |
POST | /api/dsh-update/apply | body {"tag":"next"},启动后台全局安装;省略 tag 则用当前通道 |
state 里的 latest 指的是当前通道上的版本(tags[channel]),channel 是通道名,
hasUpdate 就是拿它与 current 比较的结果。
tag 必须匹配 ^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$ 且必须是已知 dist-tag 或精确版本,
其余一律 400 —— 该值不会未经校验地进入 shell。
版本检测顺序
宿主半边按下列顺序探测「当前正在运行的 DSH」是哪个安装:
process.argv[1]/../package.json(实际入口旁边);
%APPDATA%\npm\node_modules\@deepseek-ai\dsh\package.json(Windows 全局安装位);
createRequire(import.meta.url).resolve("@deepseek-ai/dsh/package.json")。
三个候选都会随状态一起返回(variants),前端采用第一个能解析出版本号的。
更新安全
更新的是 DSH 自己,所以安装进程必须能独立活过「关掉 DSH 窗口」这一步,否则会把
跑到一半的 npm 一起杀掉,留下半套安装。宿主半边做了两件事:
- 脱离父进程:
spawn(..., { detached: true }) + unref()。关掉 DSH 窗口不再牵连
还在跑的 npm,让它自然跑完收尾。
- 装完自动校验 + 修复:npm 退出后核对「已装版本」和「
dsh 启动脚本是否以正式名
存在」。发现 .dsh-* 临时名启动脚本就当场改回 dsh / dsh.cmd / dsh.ps1;同时
报告残留的 npm staging 目录(node_modules\@deepseek-ai\.dsh-<随机>,可安全删除)。
校验结果随 job 返回:verified(是否通过)、installedVersion、repaired(自动修复
清单)、verifyDetail(一句话结论,含残留提示)。只有 verified: true 才显示绿色
「重启 DSH 后生效」。
已知事故(2026-09-26)
一次 0.1.5-rc.3 → 0.1.7-rc.2 的更新把机器留在「启动不了」的状态:包本体其实装好了
(node ...\bin.js --version 正常),但 %APPDATA%\npm\ 下 dsh 的三个启动脚本停在
临时名(.dsh-7e65g4Me / .dsh.cmd-qBi72Y8H / .dsh.ps1-WSSHMVlv),dsh 从 PATH
消失,dsh-start.bat 的 where dsh 失败 → 报「dsh not found」。
根因:当时的安装进程是 DSH 的子进程,用户按提示重启(关窗)时把没跑完的 npm
一起杀掉了。手动修复 = 把三个临时名改回正式名(脚本内容与名称无关,改完即用),再删掉
残留目录 node_modules\@deepseek-ai\.dsh-3azxHlzE(267 MB)。上面的自动校验与修复就是
把这套动作自动化了。
开发
不依赖 DSH、不依赖 react 的离线冒烟测试:
node dev\smoke-host.mjs # semver 比较、回环守卫、三条路由、shim 修复规划、真实 state 快照
node dev\smoke-client.mjs # bundle 包装、locale 注册、插槽注册形状、含校验结论的渲染分支
dev\smoke-client.mjs 用自带的极简 react / react/jsx-runtime 桩驱动组件,
所以离线也能跑(profile 里的 react 是 junction,不保证可解析)。
两个脚本都按脚本自身位置定位 lib/,所以在任何工作目录下运行都可以。
许可
MIT © DaXiangzi666