dsh-web
zhu1090093659/dsh-webDeepSeek Harness(DSH)Web 插件聚合生态包 · 一切皆插件,创意工坊分发(dsh-market.com)
仅 Windows;升级路径需配置 DSH_AUTO_UPDATE_LAUNCH_CMD。在 Web 设置页检查、安装、回滚 DeepSeek Harness CLI 更新:新版本装上后先过配置树检查与健康重启,通过才切换启动指针,起不来就自动退回旧版本。
给 DeepSeek Harness(DSH)CLI 装一个能安全升级自己的插件:检查 @deepseek-ai/dsh 新版本、后台安装到独立目录、切换启动指针、健康检查失败自动回滚。网页端设置页里点按钮就行,安装目录(版本装在哪块盘)也能在同一个卡片里改。
English TL;DR — a DSH plugin that checks the npm dist-tag for
@deepseek-ai/dsh, installs the new version side by side (never in place), smoke-tests the composed config, repoints a launcher pointer, restarts the web host from a process outside the host's job object, and rolls back automatically if the new version does not come up healthy. It also moves the side-by-side install base to another drive (copy → verify → repoint → restart), rolling back both the pointer and the base if that fails. Windows only (PowerShell 5.1 + cmd). MIT.
图存在仓库里:docs/settings-card.png(45442 字节,901×928)。上面那张走 jsDelivr 镜像,GitHub 会把外链图包成 camo.githubusercontent.com 转发,所以即便本机 hosts 把 githubusercontent.com 指到 127.0.0.1(Steam++/Watt Toolkit 之类加速器),而加速器只转发了 github.com,内嵌图照样能显示。
维护者当前状态(2026-10-22):这台机器上的 web 实例故意停在
0.2.0-rc.2。0.2.1-rc.2本体能启动,但带dsh-vision-router的 profile 起不来(connection等webRuntime超时,见下文「实战记录」),已两次真实切换验证并自动回滚。等 vision-router 出兼容版本后再升;autoApply保持关闭,升级器会继续如实报告「有新版」。
DSH CLI 没有 update 子命令——dsh plugin 只是把参数转发给 pnpm,装插件用;升级主程序得自己 npm install。而在服务正在运行时原地覆盖安装,结果是:旧进程还握着旧模块,新版本起不来时你也回不去了。
这个插件的前提只有一条:永不原地升级正在运行的实例。
<installBase>\dsh-install-<version>,和当前实例完全隔离。node bin.js --profile web --dump-config 能组合出配置树(且退出码 0)才算装成功;装好才动启动指针。WmiPrvSE.exe,不在任何 job 树里),宿主死掉不会连坐。last-result.json(status: "rolled-back")。install-base.txt → 切指针 → 重启;失败时指针和目录一起回滚(否则指针会指向新目录里不存在的版本)。| 文件 | 作用 |
|---|---|
lib/index.js | 宿主插件:/status /check /apply /rollback /config /install-base /migrate 接口 + 后台定时检查 + 自动应用 |
lib/client.js | 设置页「载具更新」卡片(当前/最新版本、检查、安装、回滚、安装目录、自动更新开关) |
apply-update.ps1 | 真正干活的脚本:npm 安装 → 冒烟 → 切指针 → 停旧起新 → 健康检查 → 失败回滚 |
start-updater.ps1 | 交接器:用 WMI 把 apply-update.ps1 拉到一个不在宿主 job 树里的进程,然后自己退出(见「为什么重启要绕道 WMI」) |
cordis.patch.yml | 把插件本体插进 loader |
examples/start-dsh-web.cmd | 重启命令的模板(插件不猜你怎么启动服务,见下文) |
test/handler.test.mjs | 离线测试:临时目录里造一个假安装,直接驱动 HTTP handler,不碰真服务、不重启 |
# 从 GitHub 装(推荐;先 fork/clone 到本地也行,用 link: 指向本地目录)
dsh plugin --profile web add github:Wonderow666/dsh-auto-update
# 或从本地目录装
dsh plugin --profile web add link:C:\path\to\dsh-auto-update
然后把它加进 profile 的 bundles(%DSH_HOME%\profiles\web\package.json):
{
"dsh": { "profile": { "bundles": [ "...", "dsh-auto-update" ] } }
}
最后重启一次 web 宿主让插件加载(这一步重启会掐掉当前对话的传输,页面上稍等重连)。之后的升级就不用再手动重启了。
装之前先确认你的启动链就位(见下面「启动脚本契约」),否则插件会拒绝切换版本——这是刻意的:它宁可不动,也不把你留在一个起不来的版本上。
插件不去猜你的目录结构,三件事全部可配置:
| 变量 | 默认 | 说明 |
|---|---|---|
DSH_AUTO_UPDATE_LAUNCH_CMD | (空) | 切换版本后用来重启 web 宿主的脚本。没配置就不允许升级 |
DSH_AUTO_UPDATE_LAUNCHER_DIR | %DSH_HOME%\auto-update\launcher | 启动指针 current-root.txt 所在目录 |
DSH_AUTO_UPDATE_BOOT_LOG | (空) | 启动日志路径;配了才能把"起不来"的具体原因显示在卡片上 |
DSH_HOME | ~/.dsh | DSH 数据根;安装目录默认在它旁边 |
DSH_AUTO_UPDATE_INSTALL_BASE | (空) | 安装目录的兜底默认值,会被 install-base.txt 覆盖(见「安装目录」) |
setx DSH_AUTO_UPDATE_LAUNCH_CMD "C:\tools\start-dsh-web.cmd"
setx DSH_AUTO_UPDATE_LAUNCHER_DIR "C:\tools\dsh-launcher"
setx DSH_AUTO_UPDATE_BOOT_LOG "C:\tools\start-dsh-web.log"
cordis.patch.yml)- id: dsh-auto-update
name: dsh-auto-update
config:
enabled: true
channel: latest # latest | next | alpha
intervalHours: 6
autoApply: false # true = 定时检查到新版本就自动装
registry: "" # 空 = npm 默认源;可填镜像,例如 https://registry.npmmirror.com
运行时修改(设置页开关、接口调用)落到 %DSH_HOME%\auto-update\config.json,优先级高于 patch 里的默认值。
插件只负责切换指针,起进程这一步交给你自己的脚本。examples/start-dsh-web.cmd 是一份可直接改的模板,要点:
0.2.1-rc.2),脚本自己拼出安装目录。原因:current-root.txt 由 cmd.exe 按当前控制台代码页读取,写完整路径时任何代码页不一致都会读成乱码,然后静默判成"安装不存在"。只存版本号则任何代码页都安全。dsh-install-* 目录名倒序回退到最新的完整安装。web-run.out.log,并自行轮转)——否则"90 秒没起来"这种故障无从查起。install-base.txt 决定去哪个目录找 dsh-install-<版本>(见下一节)。读之前先执行 chcp 65001:这个文件是 UTF-8,按 OEM 代码页(936)直接解码会把中文目录名读成乱码,之后每个 if exist 判定都会静默失败。默认安装目录是 %DSH_HOME% 的上一级(这台机器上是 ...\w),每个版本装在 <安装目录>\dsh-install-<版本> 里。设置页的「安装目录」区块可以把它换到别的盘:
uiWorkspace.pickDirectory(),和新建会话时选工作目录用的是同一个选择器)。服务绑定在 127.0.0.1 时是 Windows 原生对话框,等于本机可视地挑目录;从手机或别处访问时对话框会出现在运行 dsh 的那台机器的屏幕上,这时手动输入或粘贴路径即可。将安装到 <安装目录>\dsh-install-<当前版本>;你自己填/选的路径里出现 Windows 禁止的字符(" * ? < > |)会立刻标红并禁用两个写入按钮(不用等提交后才报错)。中文目录名可以:E:\大鲸鱼老家 这种路径会按 UTF-8 写进 install-base.txt,启动器读之前先 chcp 65001。/apply 装的新版本会落在新目录。--dump-config 冒烟)→ 写 install-base.txt → 切指针 → 重启 → 健康检查 + 版本核对。任何一步失败,指针和 install-base.txt 会一起回滚——只回滚指针会让启动脚本去新目录里找一个根本不存在的版本。install-base.txt。契约(<指针目录>\install-base.txt,默认 %DSH_HOME%\auto-update\launcher\):
D:\dsh-installs
chcp 65001),否则 cmd 会按 936/其它 OEM 代码页解码,中文目录名变成乱码后静默指向空目录。仓库自带的 start-web.cmd 与 examples/start-dsh-web.cmd 都已经写好这一行。install-base.txt → 默认目录的顺序找,再退到该目录下最新的完整安装。限制:自定义目录必须是绝对路径、不含 " * ? < > |、不以空格或点结尾,磁盘剩余空间需 ≥ 安装体积的 1.2 倍(0.2.x 每个版本约 300–700 MB)。中文、空格都可以。
全部在 /dsh-auto-update/api 下。写操作必须带 x-dsh-auto-update: 1 头(挡跨站表单/图片式 CSRF;浏览器跨站发不出自定义头)。
| 方法 | 路径 | 说明 |
|---|---|---|
| GET | /status | 当前/最新版本、通道、可用更新、上次结果、挂起任务、canRestart |
| GET | /health | 存活探针 |
| POST | /check | 查 registry 的 latest/next/alpha,写 state.json |
| POST | /apply | 后台安装,body 可带 {"version":"x.y.z"},默认最新版 |
| POST | /rollback | 回到 previous-root.txt 记录的版本 |
| POST | /config | 改 autoApply / intervalHours / channel / registry,落 config.json |
| POST | /install-base | 设置安装目录,body {"base":"D:\\dsh-installs"};传 "" 恢复默认位置(只影响以后的新版本) |
| POST | /migrate | 把当前安装复制到新目录并重启过去,body {"base":"..."};失败自动回滚指针与目录 |
$base = "http://127.0.0.1:3080/dsh-auto-update/api"
curl.exe -s "$base/status"
curl.exe -s -X POST "$base/check" -H "x-dsh-auto-update: 1"
curl.exe -s -X POST "$base/apply" -H "x-dsh-auto-update: 1" -H "content-type: application/json" -d "{}"
curl.exe -s -X POST "$base/rollback" -H "x-dsh-auto-update: 1"
curl.exe -s -X POST "$base/install-base" -H "x-dsh-auto-update: 1" -H "content-type: application/json" -d '{"base":"D:\\dsh-installs"}'
curl.exe -s -X POST "$base/migrate" -H "x-dsh-auto-update: 1" -H "content-type: application/json" -d '{"base":"D:\\dsh-installs"}'
apply-update.ps1)apply.lock,超过 120 分钟的陈旧锁自动接管)npm install @deepseek-ai/dsh@<version> 到 <installBase>\dsh-install-<version>(可与已装版本复用)node bin.js --profile web --dump-config 冒烟:组合出配置树才算装成功previous-root.txt,把指针指向新版本--profile web 的 node/api/pair/status 最多 150 秒(20 秒后放宽为端口可连)last-result.json 记 rolled-back手动排障(不下载、不切指针、不重启):
$ps = 'C:\path\to\dsh-auto-update\apply-update.ps1'
# 纯计划
powershell -NoProfile -ExecutionPolicy Bypass -File $ps -Mode apply -Version 0.2.1-rc.2 -CurrentRoot C:\path\to\dsh-install-0.2.0-rc.2 -DryRun
# 只安装 + 冒烟,不激活
powershell -NoProfile -ExecutionPolicy Bypass -File $ps -Mode apply -Version 0.2.1-rc.2 -CurrentRoot C:\path\to\dsh-install-0.2.0-rc.2 -StageOnly
-DryRun 的早退发生在 npm install 之前——不存在"dry run 也真装了"的情况。
-Mode migrate)" * ? < > |、不是某个 dsh-install-* 本身、盘存在、剩余空间 ≥ 1.2× 安装体积("已经在那个目录"这个判断放在最前面,所以它给出的原因永远准确)robocopy <当前根> <新目录>\dsh-install-<版本> /E /R:1 /W:1 /MT:8(退出码 ≥ 8 视为失败)package.json 版本 == 当前版本,且 --dump-config 能组合出配置树install-base.txt(previous-root.txt / previous-base.txt,- 表示原本没有)install-base.txt → 切指针 → WMI 交接到 stage-2,由它停旧实例并用重启命令拉起install-base.txt 一起回滚,重启回原目录# 只打印计划,不复制、不改任何文件
powershell -NoProfile -ExecutionPolicy Bypass -File $ps -Mode migrate -Version 0.2.0-rc.2 -CurrentRoot C:\...\dsh-install-0.2.0-rc.2 -InstallBase D:\dsh-installs -DryRun
宿主只能被不在它 job 树里的进程重启。插件拉起的 apply-update.ps1 是 web 宿主的子进程,而 DSH 的 subprocess-local 会给工具树套 job object:宿主一死,job 关闭,整棵树一起没了。实践中表现出来的样子是——重启脚本日志停在 stopped pid ... 那行,后面重启的几行永远不执行(这类脚本我写过三个,三个都这样死掉)。
所以指针一切换就立刻 relinquish 控制权:Win32_Process.Create 让新进程的父进程变成 WmiPrvSE.exe,没有任何 job 覆盖它,stage-2 一定活到把新宿主拉起来。
失败恢复也不放在 catch 里:会抛异常的地方都在指针切换之前,那时旧实例还在正常服务,什么都不用做;切换之后的健康检查与回滚由 stage-2 独占。
上面说的是 stage-2,但插件第一次拉起 apply-update.ps1 用的一直是 node 的 detached: true。v0.2.3 之前的版本里,这条路径在这台机器上从来没有真正跑通过:/migrate 会返回一句 updater failed to start and printed nothing,或者干脆没有反应,updater.log 里一个字节都没有。
一次 flag 矩阵把它钉死了(同一段 powershell.exe -File <脚本> 命令,只改 spawn 选项,看子进程有没有写出标记文件):
| spawn 选项 | 子进程是否运行 |
|---|---|
detached: true(其余任意组合) | 否,零输出 |
detached: false | 是,退出码 0,输出正常 |
去掉 windowsHide / 去掉 -WindowStyle Hidden / stdio: 'ignore' | 仍然否 |
也就是说在这台机器上 DETACHED_PROCESS 拉起的 powershell.exe 根本不会执行;而普通子进程又能跑——但它待在宿主的 job 里,宿主一死就被带走。两个要求互相排斥,所以改成两步:
start-updater.ps1(能跑),它只做一件事:Win32_Process.Create 创建真正的 apply-update.ps1,Shell 里打印 started pid=<n>,然后退出;update.log 增长、apply.lock 出现、或 updater.log 里出现 started pid= 三者之一(宽限 20 秒,够两次冷启动 + WMI)。<installBase>\dsh-install-<version>,不会把 npm install 引向任意目录;改安装目录同样受限——只接受绝对路径(可含中文)、不含 " * ? < > |,且不能指向某个 dsh-install-* 目录本身^\d+\.\d+\.\d+(?:-[0-9A-Za-z.]+)?$,且必须比当前版本新(想降级请走 /rollback)apply / rollback 会在动指针前失败,不会留下起不来的 profileautoApply: false,自动更新必须用户在设置页显式打开%DSH_HOME%\auto-update\
| 文件 | 内容 |
|---|---|
config.json | 用户在网页上改过的配置 |
state.json | 最新 tag 表、上次检查时间、上次结果、进行中的任务 |
last-result.json | 脚本结论:pending / ok / rolled-back / failed / staged / dry-run |
update.log | 每次升级的完整日志(含 npm 输出) |
apply.lock | 单实例锁 |
launcher/current-root.txt | 启动指针(只存版本号;目录可由 DSH_AUTO_UPDATE_LAUNCHER_DIR 改变) |
launcher/install-base.txt | 安装目录(唯一一行 UTF-8 绝对路径,可含中文;文件不存在 = 默认目录) |
previous-base.txt | 迁移前的 install-base.txt 内容,供回滚恢复(- 表示原本没有这个文件) |
last-result.json,卡片上也会带 Cause:(取自启动日志的首条错误行)。手动 -Mode rollback -TargetRoot <旧根> 可立刻回退。npm install 要几分钟):把 registry 指到镜像,插件的查询和 npm install --registry 都会跟着走。host: '0.0.0.0':dsh-host-webserver 的较新版本不再允许通配地址绑定("binding every interface would expose remote code execution to the network")。如果你的 profile patch 里留着 lan-bind 的 host: '0.0.0.0',升级后会在 webserver 处启动失败并自动回滚。改成 127.0.0.1(远程访问交给隧道/中继)或某个具体网卡地址,再升级。0.2.0-rc.2 → 0.2.1-rc.2 在这台机器上做过两次真实切换,两次都是「新版启动失败 → 自动回滚到 0.2.0」,结论稳定且与升级器无关:
DSH_HOME + 真实 profile 配置逐插件二分:@deepseek-ai/dsh-base + dsh-web-app 能起;再加 dsh-vision-router 立刻起不来;移除它、其余六个插件全留着,能起。StartupError: dsh: startup failed: 1 required plugin did not activate,诊断表首行是 connection (required) webRuntime(即 webRuntime 一直没被提供,connection 卡在等待),日志里没有更具体的插件异常。dsh-vision-router 的 dsh.client.inject 声明了 @deepseek-ai/dsh-client-ui-primitives,而 0.2.1-rc.2 不再发布这个包(0.2.0 有、0.2.1 没有);它的 bundle patch 又给 modules / connection 两行追加了 webServer / webRuntime 依赖。3.0.0 与最新的 3.0.5 声明的 inject 完全一致,engines 虽写着支持 >=0.2.0-rc.2 <0.3.0-0,实测起不来。node test/handler.test.mjs # 全部(含一次 registry 查询)
$env:DSH_AUTO_UPDATE_OFFLINE=1; node test/handler.test.mjs # 跳过网络
测试在系统临时目录里造一个假安装(含 bin.js、package.json、指针文件)并把 DSH_HOME 指过去,所以在没装过 dsh 的机器上也能全绿,也不会碰你的真实 profile。
@deepseek-ai/dsh 主包,不动 profile 里的插件(插件各自更新)install-base.txt 前执行 chcp 65001(自带的两份脚本都已默认加上),自己另写的启动器漏了这一行就会静默找不到安装next / alpha 是预发布通道,别在生产开/api/status,再退回查端口属主进程的安装目录)。这条是踩出来的——曾经有一个老启动器残留进程占着端口,健康检查返回 200、卡片显示成功,而新版其实根本没起来。同时 Stop-WebHost 现在也会结束启动脚本包装进程(start-web.cmd),避免它把旧版本重新拉起来抢端口。MIT