← 返回列表
未验证
跟踪版本更新点并一键升级,失败自动回滚
尚未跑自动兼容性验证,可查看页面内的依赖与入口分析。 · 最近上游提交 2026/9/13 · 已提供中文文档
跟踪 DeepSeek Harness 自身的版本发布,一键更新:npm dist-tags + GitHub changelog,按风险分级,带原子重命名备份和自动回滚
综合分
29.7
GitHub 分
29.7
用户评分
—
★ Stars
0
周下载量
—
安装插件(需先安装 dsh CLI 引擎:npm install -g @deepseek-ai/dsh)
dsh plugin --profile web add zhengjy01/dsh-updater该插件未发布到 npm,走 GitHub 源安装(pnpm 若拦截 prepare 脚本,按其提示在 pnpm-workspace.yaml 的 allowBuilds 中放行后重跑)
数据截至 2026/9/16(元数据每日更新 · 实装验证按队列轮转,单条结论的验证时间见上方)
依赖的 DSH / Cordis 模块
@deepseek-ai/cordis@deepseek-ai/dsh-client-connection@deepseek-ai/dsh-client-ui-renderer@deepseek-ai/dsh-client-ui-settings@deepseek-ai/dsh-client-ui-slots@deepseek-ai/dsh-host-webserver@deepseek-ai/dsh-llm@deepseek-ai/dsh-system-prompt@deepseek-ai/dsh-tools用户评分
还没有人投票,来当第一个
订阅周报,不错过优质插件更新
每周一封 · 高评分插件 + 新用户活动
README
@zhengjunyao/dsh-updater
跟踪 DeepSeek Harness 自身的版本并一键更新:跟 npm dist-tags(latest / next / alpha)与
GitHub Release(tag dsh-v,双语正文),在 Web GUI 里显示当前版本到目标版本之间每一个版本的更新点
和一份分级风险清单,然后把安装交给一个分离助手——用 mv 把当前安装瞬时移开做备份 →
npm install --prefix → 校验落地版本 → 用完全相同的命令重启 →
启动失败自动回滚。
一句话定位
DSH 的更新频率高(0.1.x-rc.x 通道多),而 dsh 自身不带任何自更新机制。
- npm i -g @deepseek-ai/dsh 之后还得自己重启一次,而且重启命令必须和当初启动时一模一样
(端口、profile、cwd、node 路径都可能被改过)。
- 本机可能有多份 DSH 安装:process.argv[1] 反查出的运行安装、PATH 里最先命中的那份、
以及 npm config get prefix 指向的那份,三者完全可以是三个不同目录——一次天真的
npm i -g 会更新到最后那个,然后什么变化都不会发生。
- npm 发布频繁但只有 GitHub Release 写变更说明,且 npm 上有的版本不一定有对应 release;
反过来,只在 GitHub 发布的版本根本装不上。
本插件把「看版本 → 读更新点 → 评风险 → 装 → 校验 → 重启 → 失败回滚」串成一条链,并让
正在运行的安装目录成为唯一的更新目标。
四个能力
1. 跟踪最新版本
- 三个通道:npm dist-tag latest / next / alpha,由配置项 channel 决定跟随哪一个。
- 后台定时检查:checkIntervalMinutes(默认 180 分钟,0 关闭)+ checkOnBoot(启动后 8 秒跑一次)。
后台检查的目的不是打扰,而是让缓存里的 registry / releases 快照保持温热,
打开面板或问 Agent 时不用等两次网络往返。
- 缓存:cacheTtlMinutes(默认 30 分钟)内的结果直接复用,落地在 cache/registry.json
与 cache/releases.json;force 参数可绕过缓存。
- 上游故障不致命:registry 或 GitHub 拉取失败时,沿用上一次成功的结果并在
upstream.warnings 里标注「可能是旧的」——「连不上 GitHub」不会被显示成「没有新版本」。
- dist-tag 缺失或滞后时按通道阶梯回退:next / alpha 没打 tag 时,回退到
latest 之上最新的预发布版本,避免「明明有更新的预发布却报无更新」。
- 面板同时显示 newerElsewhere:当前通道之外是否存在更新的版本。
2. 显示更新点
- 版本之间的事实来自 npm(能不能装),变更说明来自 GitHub Release(改了什么)。
- 更新点覆盖 (当前版本, 目标版本] 区间内每一个已发布版本,不只是一个 diff;
用户跑 latest 常常一次跨过好几个发布。
- 每个版本按 GitHub Release 正文里的 / # 标题分组渲染条目;
正文是双语的(中文段 + --- + 英文段),解析优先取中文段,缺失时回退英文段。
- 拿不到对应 release 的版本明确标出「没有找到该版本的发布说明」(notesMissing),
不假装没有变化。
- 只在 GitHub 发布、npm 上不存在的版本会被排除出可选目标(installMissing)并说明原因。
- GitHub 未认证限流是 60 次/小时;配置 githubToken 可提高上限,面板会显示剩余额度与重置时间。
3. 一键更新
入口是设置页「DSH 更新」卡片里的按钮或侧边栏入口,也可以由 dsh_update 工具触发。
真正的安装由分离助手 helper/update-helper.mjs 完成,因为一个进程无法安全替换自己正在运行的代码:
1. 宿主写 pending-spec.json,spawn 分离助手,回 202 之后才退出自己;
2. 助手等旧宿主释放端口(超时后 SIGKILL 旧进程兜底);
3. mv(rename)把当前安装原子移开做备份——不是复制三万个文件;同目录 rename 是瞬时且原子的,
因此回滚不依赖剩余空间;
4. npm install -g --prefix --registry --no-audit --no-fund @deepseek-ai/dsh@
(--prefix 永远显式传入,不用 npm config get prefix);npm 输出实时流入日志;
5. 校验落地结果:读 package.json 版本必须等于目标版本、入口脚本 /lib/bin.js 必须存在、
node_modules/@deepseek-ai 下必须有包(npm 退出码为 0 也可能留下半装目录);
6. 用完全相同的调用重启(process.execArgv + argv[1:] + 原 cwd + 原可执行文件),
所以 dsh web --port 3080、自定义 profile、自定义路径都原样保留;
7. 启动失败则把备份 rename 回原位并拉起旧版本;
8. 期间恢复控制台始终可用(默认 http://127.0.0.1:3098,被占用时依次试 +1…+9)。
4. 更新风险提示
版本号本身不足以回答「该不该按这个按钮」。风险引擎把目标版本、本机安装拓扑、
已装插件契约与本次操作的可行性汇总成一份有序清单,分三级:
- high:不要盲目执行,默认拦截一键更新直到显式确认;
- warn:先读一遍;
- info:背景信息。
检查项(每一条都来自代码,不是猜测):
| 风险项 | 检查内容 | 级别 |
| --- | --- | --- |
| prerelease | 目标版本带预发布标识(rc / alpha / beta)。跟随 latest 通道却拿到预发布时升为 high | warn / high |
| breaking | 扫描更新点文本里的破坏性措辞(BREAKING、不兼容、破坏性、重大变更、不再支持、已移除、移除了、废弃、已弃用、必须升级、需要手动、要迁移、升级指南、deprecat、backwards-incompatib 等) | high(命中时);否则一条 info 说明「未发现」 |
| migration | 更新点里提到迁移 / 改名(migrat、迁移、改为、改名、重命名、rename) | warn |
| version-jump / version-gap | 跨版本幅度:跨 ≥2 个次版本或跨主版本记 warn(跨 ≥4 个次版本或跨主版本时);小跨度只记 info | warn / info |
| notes-missing | 区间内有版本在 npm 上存在却没有对应 GitHub release,变化内容未知 | warn |
| not-on-npm | 区间内有版本只在 GitHub 发布、npm 上没有,无法安装,已从可选目标排除 | info |
| install-unknown | 反查 process.argv[1] 得不到安装根,更新不知道要替换哪个目录 | high |
| multi-install | 本机存在多份 DSH 安装:说明本次只更新正在运行的那一份,并列出其它份;若 PATH 首选与运行中的不是同一个目录,明确指出「更新完重启后手动敲 dsh 可能又跑回旧的那一份」 | high |
| path-mismatch | 只有一份安装,但 PATH 首选仍与运行中的不一致 | warn |
| prefix-drift | npm config get prefix 与运行安装的前缀不一致(直接 npm i -g 会装到前者) | info |
| preflight: | 预检结果逐条并入清单(见下) | 按检查项 |
| plugin-incompatible | 已装插件在各自 package.json 的 dsh.engines.dsh 里声明的范围是否覆盖目标版本;不覆盖时提示「bundle 加载失败会让插件树整体起不来」 | high(有插件不兼容时);否则一条 info 汇总(含未声明范围的插件数) |
| backup | 更新前是否备份;关闭备份时明确写出「npm 会留下半装目录且无法自动恢复」 | info / warn |
| session-interrupt | 更新必然中断当前会话,并说明自动回滚是否开启 | info |
预检项(preflight,全部来自 src/installs.ts,无论通过与否都会逐条返回):
| 预检项 | 通过条件 | 不通过时级别 |
| --- | --- | --- |
| target | 从 npm registry 解析到了目标版本号 | high |
| install-root | 从 process.argv[1] 反查到了 @deepseek-ai/dsh 包根 | high |
| install-writable | 当前用户对安装根(及其前缀)有写权限,不需要 sudo | high |
| install-complete | 安装根下 node_modules/@deepseek-ai 有包(否则疑似损坏,更新会顺带修复,但回滚点也是这个损坏状态) | warn |
| npm | PATH 里能找到 npm | high |
| prefix | 能从安装目录推导出 npm 前缀(/lib/node_modules 布局) | warn |
| node | Node 主版本 ≥ 20(@deepseek-ai/dsh 自己不声明 engines,npm 不会替你拦低版本 Node,所以这里自设下限);解析不出主版本号时跳过 | high |
| tarball | 目标版本的 dist.tarball HEAD 返回成功(镜像/网络异常只记 warn,明确 404 才 high) | warn / high |
| disk | 剩余空间 ≥ 2 GB(一份新安装 + 一份等大的旧安装备份 + npm 的下载副本);读不到空间时跳过 | high |
分级与拦截:
- level 是所有风险项里最严重的一级;score 是 0–100 的加权分(high 45 分 / warn 12 分 / info 2 分,上限 100),供面板画表盘。
- vetted 为 false 表示清单里仍有 high 项——「还没被审过」。
- confirmOn 决定一键更新的拦截门槛:high(默认)只拦 high;warn 连 warn 也拦;
never 完全关闭拦截(仍然显示清单)。
- 被拦时:HTTP 返回 428(面板)或工具返回 requiresRiskConfirmation: true(Agent),
并附上最严重的前几项原因;确认后带 confirmRisk: true 重试。
- 检查在点击更新的那一刻重新跑一遍:用户确认过的那份报告,必须是即将执行的那次操作对应的报告。
安装
从 GitHub(仓库带 dsh-plugin topic)
dsh plugin --profile web add github:zhengjy01/dsh-updater
本地开发:link / file 两种写法
dsh plugin --profile web add link:/path/to/dsh-updater
dsh plugin --profile web add file:/path/to/dsh-updater
发布到 npm 之后
dsh plugin --profile web add @zhengjunyao/dsh-updater
装完需要重启一次 dsh web 才会加载宿主侧代码。
界面位置
- 设置页「DSH 更新」卡片(settings.section,id updater,order 339):当前版本与运行安装目录、
本机安装份数、各通道目标版本、更新点、风险清单、一键更新按钮、更新历史、上次失败报告、插件设置。
- 侧边栏入口:由 entry 控制——sidebar(默认,内联在侧边栏)/ ball(固定在角落的球)/
both(两者都有)/ off(不挂载入口)。
- 更新期间显示全屏遮罩(showOverlay,默认开),新宿主应答后自动刷新页面(autoReload,默认开)。
侧边栏入口的 tooltip / aria-label 与全屏遮罩的标题同样是「更新」文案,界面文字与上面描述一致。
界面截图
三张实机截图(本机 macOS,DSH 0.1.5-rc.1,窗口宽约 800px)。点击可看全尺寸。
① 默认 latest 通道:已是最新
注意画面里那块琥珀色的安装诊断:latest 通道下当前已是最新、没有任何更新可做,
但它照样显示——因为「本机有两份安装、PATH 首选与运行中的不是同一份」这件事与「有没有更新」无关,
用户随时都该看见。这是本机真实命中的情况,不是演示数据。
② 折叠区:更新记录 / 更新日志 / 失败报告 / 可用备份 / 插件设置
③ 切到 next 通道:出现可更新版本与逐条更新点
徽章从「已是最新」变成「可更新到 0.1.5-rc.2」,右上角目标版本随通道切换,
下方展开「更新点 0.1.5-rc.1 → 0.1.5-rc.2 · 跨 1 个版本」——每个版本一个可折叠区块,
带预发布徽章、发布日期与 GitHub release 链接,正文按 体验优化 等分组逐条列出。
HTTP 路由
全部注册在 /api/dsh-updater/,loopback-only(来源地址必须是 127.0.0.1 / ::1 /
::ffff:127.0.0.1,Host 必须是 127.0.0.1 / localhost / [::1],Sec-Fetch-Site: cross-site
被拒,带 Origin 时必须同源):
| 方法 | 路径 | 作用 | 关键状态码 |
| --- | --- | --- | --- |
| GET / HEAD | /api/dsh-updater/probe | 极小存活探针,页面重连时高频轮询 | 200;非 loopback 403;其它方法 405 |
| GET | /api/dsh-updater/status | 宿主 + 助手实时状态 + 配置 + 安装列表 + 备份 + 历史 + 失败报告 | 200;403 / 405 |
| GET | /api/dsh-updater/check | 解析通道、拉取更新点、给出风险清单(参数 channel / version / force) | 200 |
| POST | /api/dsh-updater/update | 交接更新:写 spec、spawn 助手,回 202 之后退出宿主 | 202 已安排;400 无目标版本 / 识别不出安装目录 / 无 npm / 找不到助手脚本;409 已经是最新或目标不比当前新;428 需要风险确认;500 交接失败 |
| GET | /api/dsh-updater/logs | 日志尾部 + 疑似报错行(参数 lines、which = latest / auto / spec / 绝对路径) | 200 |
| GET | /api/dsh-updater/history | 更新记录(参数 limit,上限为 historyLimit) | 200 |
| GET | /api/dsh-updater/failure | 上次失败报告;?format=md 返回 text/markdown | 200 |
| GET | /api/dsh-updater/installs | 本机每一份 DSH 安装 + 工具链事实 | 200 |
| POST | /api/dsh-updater/config | 打补丁改配置;{"reset": true} 恢复默认 | 200;400 请求体不是合法 JSON |
| GET | /api/dsh-updater/helper | 经宿主读取助手实时状态 | 200 |
| POST | /api/dsh-updater/helper/{retry\|rollback\|recheck} | 把命令转发给恢复控制台 | 200;404 未知命令;502 控制台不可达 |
/update 明确先回包再退出进程:回复上线之后才调用 commit(),浏览器永远拿得到确定的答复。
Agent 工具
| 工具 | 读写 | 参数 | 返回要点 |
| --- | --- | --- | --- |
| dsh_update_status | 只读 | channel、force | 运行版本与它实际加载的目录、本机安装份数、PATH 首选根、各通道目标版本、是否有更新、最近 6 条历史、可用备份、助手阶段与上次结果、失败报告正文、上游告警 |
| dsh_update_check | 只读 | channel、version、force | 逐版本更新点(含「没有找到该版本的发布说明」标记)、风险清单([级别] 标题|详情)、riskLevel / riskScore / vetted、不兼容插件、未通过的预检项 |
| dsh_update | 写:真正安装并重启 | confirm、confirmRisk、channel、version、reason、delayMs | 目标版本、助手 pid、备份目录、日志路径、恢复控制台地址、约多少毫秒后退出 |
dsh_update 的门槛:
- confirm: true 是必要条件——未经用户同意不得更新。本机铁律同样适用:不得私自重启或关闭 DSH,
而未提供 confirm 时工具只返回提示、不执行任何动作。
- 风险清单里有 high 项时还需要 confirmRisk: true(被拦时返回 requiresRiskConfirmation: true
与最严重的前几项)。
- 推荐流程:先 dsh_update_check,把更新点与风险讲给用户 → 得到明确同意 → 再调用 dsh_update。
- 更新必然中断当前回合并断开本会话连接;网页端会自动重连刷新。
配置
文件:DSH_HOME/dsh-updater/config.json(默认 ~/.dsh/dsh-updater/config.json,0600)。
首次加载时按插件行里的种子生成一次,之后文件为准(每次更新前重新读取)。
| 字段 | 类型 | 默认值 | 说明 |
| --- | --- | --- | --- |
| enabled | boolean | true | 总开关;为 false 时不挂载路由与工具 |
| announceToAgent | boolean | true | 是否在 Agent 系统提示里公告本插件(工具与行为) |
| entry | 'sidebar' \| 'ball' \| 'both' \| 'off' | 'sidebar' | 面板入口在 GUI 里的位置 |
| channel | 'latest' \| 'next' \| 'alpha' | 'latest' | 解析目标版本时跟随的 npm dist-tag |
| checkIntervalMinutes | number | 180 | 后台重新检查的间隔(分钟),0 关闭定时器 |
| checkOnBoot | boolean | true | 宿主启动后不久跑一次检查 |
| registry | string | 'https://registry.npmjs.org' | npm registry 基础地址 |
| apiBase | string | 'https://api.github.com' | GitHub API 基础地址 |
| githubToken | string | '' | 可选 GitHub token,提高未认证的 60 次/小时 release 限额 |
| cacheTtlMinutes | number | 30 | registry / releases 缓存的新鲜时长(分钟) |
| npmArgsExtra | string[] | [] | 追加到 npm install 命令后的额外参数(最多 20 项) |
| backupWhenUpdating | boolean | true | 替换前是否把当前安装完整备份(rename 移开) |
| backupCount | number | 3 | 保留几份更新前备份 |
| autoRollback | boolean | true | 新版本启动失败时是否自动恢复备份 |
| confirmOn | 'warn' \| 'high' \| 'never' | 'high' | 哪一级风险需要显式确认后才允许更新 |
| fallbackPort | number | 3098 | 分离恢复控制台的端口(被占用时依次试 +1…+9) |
| bootTimeoutMs | number | 180000 | 新宿主多久没应答算这次尝试失败 |
| maxAttempts | number | 2 | 单次更新请求的安装/启动尝试次数(1 = 不自动重试) |
| killGraceMs | number | 6000 | SIGTERM 旧宿主之后、助手 SIGKILL 之前的宽限 |
| portFreeTimeoutMs | number | 25000 | 等旧宿主释放端口的上限 |
| lingerMs | number | 4000 | 成功后助手保留控制台多久再退出 |
| readyConfirmMs | number | 4000 | 端口应答后、判定「已就绪」前必须守住的时长 |
| bootWatchMs | number | 30000 | 已报就绪后继续观察新宿主、把「起来又死」改判为失败的时间窗 |
| installTimeoutMs | number | 600000 | npm install 本身允许运行多久 |
| logLines | number | 400 | 面板与接口保留/显示的日志行数 |
| autoReload | boolean | true | 新宿主应答后自动刷新页面 |
| showOverlay | boolean | true | 等待期间显示全屏遮罩 |
| probeIntervalMs | number | 1200 | 重连探测间隔(毫秒) |
| historyLimit | number | 40 | history.json 里保留的更新记录条数 |
每个字段都会被钳制到合理区间(例如 maxAttempts 1–5、backupCount 0–20、
installTimeoutMs 30000–3600000),非法值静默回落到默认值,配置文件不会让插件抛错。
更新流程
面板按钮 / dsh_update 工具
│ POST /api/dsh-updater/update(先重新跑一遍检查与风险分级)
▼
宿主(旧进程)
├─ 写 pending-spec.json(0600)+ 追加一条 history 记录(outcome: pending)
├─ spawn 分离助手 helper/update-helper.mjs --spec (detached, stdio ignore)
├─ 回 202(helperPid / logFile / consoleUrl / backupDir / exitInMs)
└─ 回复上线后才 SIGTERM 自己(~700ms;15s 后硬退出兜底)
▼
助手(零依赖纯 Node ESM,宿主死后继续活)
├─ 起恢复控制台(3098,占用则 +1…+9)
├─ 等 127.0.0.1: 释放(超时则 SIGKILL 旧 pid)
├─ mv 当前安装 → 同级 .dsh-updater-backup--(rename,原子)
├─ npm install -g --prefix --registry --no-audit --no-fund @deepseek-ai/dsh@
├─ 校验:package.json 版本 == 目标;lib/bin.js 存在;node_modules/@deepseek-ai 非空
├─ 用完全相同的 file/argv/cwd/env 拉起新宿主(stdout+stderr → logs/-.log)
├─ 三重就绪判定:端口应答 + 进程存活 + 启动输出无 boot 致命行
│ 先守 readyConfirmMs(4s)再确认
├─ 就绪后继续观察 bootWatchMs(30s):期间掉线或出现致命行 → 重新判为失败
├─ 失败 → rename 恢复备份 → 拉起旧版本(attempt 预算重置,再试一轮)
├─ 仍失败 → 停在 failed,控制台留着等人工处理
└─ 成功 → 清理超出 backupCount 的旧备份 → 停留 lingerMs 后退出
▼
页面轮询 /api/dsh-updater/probe → 新宿主应答 → location.reload()
助手与宿主通过 status.json 交换状态。宿主无法读到自己死后写下的结果,
所以每次启动时会做一次 history 对账:只有当 status.json 里的目标版本与来源版本
和最新那条记录吻合时,才把 outcome / landedVersion 补回历史。
文件与目录
插件自身的东西都在 DSH_HOME/dsh-updater/ 下(可用 DSH_UPDATER_HOME 覆盖;
配置文件另可用 DSH_UPDATER_CONFIG 覆盖):
| 路径 | 内容 |
| --- | --- |
| config.json | 插件设置(0600) |
| history.json | 每次更新请求一条记录,最新在前(上限 historyLimit) |
| status.json | 由分离助手写入的实时更新状态 |
| pending-spec.json | 下一次助手运行的交接载荷(0600) |
| logs/-.log | 一次更新的 npm 与宿主输出 |
| cache/registry.json | 最近一次 npm registry 拉取(带 TTL) |
| cache/releases.json | 最近一次 GitHub releases 拉取(带 TTL) |
| last-failure.md | 失败时写成的一份可整段复制的报告(成功启动时会被删除) |
更新前备份不在这里,而是刻意放在被保护安装的同级目录:
/../.dsh-updater-backup--/
例:/opt/homebrew/lib/node_modules/@deepseek-ai/.dsh-updater-backup-0.1.1-20260913-205812/
同目录才能保证 rename 成功,取出与放回都是单次系统调用;该目录名被 npm 视为普通目录而不会清理它。
backupCount 之外的旧备份在更新成功后会被删除。
安全与风险边界
诚实地讲清楚:
- 更新会中断当前回合、断开浏览器连接并重启宿主。这是替换宿主进程的必然结果,不是可选项。
- 回滚恢复的是「更新前那一份安装」,也就是旧代码;它不保证与更新后新版本写下的数据或配置兼容。
「回滚成功」等于旧版本能起来,不等于一切回到更新之前。
- confirmOn: 'never' 会关掉风险拦截——风险清单照常显示,但一键更新不再被 high 项挡住。
这是给用户对自己机器做的选择,默认值是 high。
- 预发布版本(-rc. / -alpha. / -beta.)官方并未标为稳定版;接口与插件契约可能随时再变,
回滚也不保证插件兼容。
- 路由只在 loopback 上注册,且有同源与 Sec-Fetch-Site 守卫;恢复控制台则是开放 CORS
的本地 HTTP 服务(Access-Control-Allow-Origin: ),任何能在本机访问该端口的进程都能读到
日志与失败报告,也能 POST 重试/回滚。它只应存在于一次更新的存活期间。
- 备份与安装目录必须在同一个文件系统上:备份是 rename,跨文件系统无法完成,
此时 takeBackup() 会失败并中止安装(不会降级为慢速复制)。
- 安装目录不可写时预检直接给 high:分离助手无法输入密码,需要 sudo 的场景只能手动在终端执行。
- 助手不会注册任何 OS 级后台服务;它只活一次更新,成功后停留数秒即退出,
失败时留在原地等待处理(可随时 kill)。
故障排查
恢复控制台(默认 http://127.0.0.1:3098,被占用时端口号会 +1…+9;
DSH_HOME/dsh-updater/status.json 与 /api/dsh-updater/helper 里的 fallbackPort 是权威值):
| 控制台路由 | 内容 |
| --- | --- |
| GET / | 页面:阶段与已用时、当前安装步骤(完整的 npm 命令行)、npm 输出、启动日志、疑似报错行、失败原因、退出码 |
| GET /status | 助手的状态快照(JSON) |
| GET /report | 完整失败报告(markdown),供整段复制 |
| GET /log?lines=N | 原始日志尾部(纯文本) |
| POST /retry | 重试启动(重置尝试预算、清掉上次失败信息) |
| POST /rollback | 恢复备份并拉起旧版本 |
| POST /recheck | 不重新拉起,只重新探测主端口 |
页面上的三个按钮(重试启动 / 回滚到旧版本 / 重新检测)就是最后三个路由。
主宿主还活着时,也可以用 POST /api/dsh-updater/helper/{retry|rollback|recheck} 把命令转发过去。
其他材料:
- DSH_HOME/dsh-updater/last-failure.md — 失败报告,比原始日志更适合整段贴给别人看。
- DSH_HOME/dsh-updater/logs/ — 每次更新一个日志文件,npm 输出与宿主 stdout/stderr 都在里面。
- GET /api/dsh-updater/logs 或面板的日志区 — 尾部 + 疑似报错行(Error / EADDRINUSE /
MODULE_NOT_FOUND / npm ERR / Cannot find module / 真实调用栈帧等)。
- 更新后宿主直接起不来(新版本也没能拉起来)时,日志里的 lib/bin.js 与依赖检查结果就是第一步;
控制台报告会同时列出安装根、前缀、npm 命令与落地版本。
未验证:宿主自身的启动日志位置没有写进 README,因为插件把它作为「本次更新的日志」
记录在 logs/ 与恢复控制台里,而 DSH 自己写到哪儿取决于启动方式(终端、launchd 等),
本仓库代码没有读取那个文件。
可移植性验证
本仓库遵循「发布前必须过可移植性门禁」:插件是给别人用的,只在开发机的 link: + 自己的
profile 下能跑不算通过。
npm run build # tsc -p tsconfig.build.json && tsdown
npm run verify # node scripts/portability.mjs --health /api/dsh-updater/probe
npm run verify:quick # --skip-audit --stability 5
npm run verify:full # --stability 30
当前状态:npm run verify 连续两次 ✅ 通过(隔离 DSH_HOME → tarball 装进空 profile →
独立实例就绪 → 健康路由 200 → 客户端 bundle 进 __DSH_BOOT__.entries → 就绪后再守 15 秒稳定,
各 7–8 次探测全部命中同一 pid)。
scripts/portability.mjs 会在隔离的临时 DSH_HOME 里新建空 profile、
用 npm pack 出来的 tarball 安装(不走 link:)、起一个独立实例、打健康路由
/api/dsh-updater/probe、抓 index 确认客户端 bundle 进入了 __DSH_BOOT__.entries,
并在就绪后再守一段稳定性观察窗。必须隔离 DSH_HOME:同机第二个 DSH 实例会等待
~/.dsh/.credentials.yaml 的写锁直到超时(atomic-write: timed out waiting for the writer lock)
而启动失败,不隔离的话验证结果全是假的。
写动作(一键更新)有意不纳入脚本门禁:它会对本机真实的全局部署执行 npm install
并重启真实宿主,自动化门禁不能触发这种副作用。
写动作的正确性由 tests/helper.mjs 在 /tmp 里端到端验证:每个场景都自建一个假安装根
与一个假 npm(按真实 npm 的行为写入包、或失败、或写错内容),再对它们运行真的
helper/update-helper.mjs,检查发生了什么。覆盖 ok(装完校验通过并拉起新版本)、
wrong-version(npm 退出 0 但版本不对)、missing-entry(版本对但入口脚本缺失)、
empty-deps(版本对但依赖域为空)、npm-fails、boot-fails(装好了但起不来 → 回滚 + 拉起旧版本)、
boot-really-fails(都起不来 → 控制台留着等人工)、prune(成功后清理超出 backupCount 的备份)、
restart-only(install: false 时绝不调用 npm)。
npm test # smoke → versions → risk → helper → routes → handoff
全部测试都建立在临时目录与假 npm 之上,不会碰本机真实的 DSH 安装。当前工作树上
npm run build && npm test 的实测结果是 517 项断言全部通过(smoke 97 / versions 84 /
risk 64 / helper 65 / routes 110 / handoff 97)。
兼容性
- 要求:DeepSeek Harness >=0.1.5-rc.1(即 package.json 的 dsh.engines.dsh)。
- Node:^22.19.0 || >=24.0.0。
- peer:@deepseek-ai/dsh- 与 react / react-dom 见 package.json 的 peerDependencies。
- 平台:代码在 macOS 上开发。安装根枚举覆盖 Homebrew 前缀、/usr/local、~/.local
以及 Node 所在前缀;宿主由 launchd 托管时走 observe 模式——助手执行托管方的 kick 命令、
跟随托管方写入的 stdout/stderr 日志判断存活,并刻意不做长观察(守住端口是托管方的职责),
绝不自己再 spawn 一个宿主去抢端口。Linux / Windows 未实测。
- 与本作者的 dsh-restart 共用分离式助手模式(同一套「宿主先回包、助手接管、控制台兜底」结构)。
License
MIT — 见 LICENSE。
仓库:扫码进群