跳到正文
dsh-market 浏览插件 GitHub EN

zhengjy01/dsh-updater

跟踪 DSH 官方版本并一键更新:读取 npm dist-tags 与 GitHub 更新说明,列出当前版本到目标版本之间每一个版本的更新点,给出分级风险清单(预发布、破坏性变更措辞、本机多份安装与 PATH 不一致、插件 dsh.engines.dsh 兼容性、备份与回滚可用性),再把安装交给分离助手——用 rename 原子备份当前安装、执行 npm、校验落地结果、用原命令重启,新版本起不来就自动回滚。

Star 数 ★ 0 分类 开发与运行时 收录于 2026-09-15 npm @zhengjunyao/dsh-updater

安装

在 DeepSeek Harness 里通过 dsh-market 安装

dsh plugin --profile web add dshmarket

或使用命令行

dsh plugin --profile web add @zhengjunyao/dsh-updater

装任何插件都等于在你的机器上跑第三方代码,权限和你本人一样大——能读你的文件、用你的凭据、访问网络。请先审阅源码,并尽量锁定 commit(github:owner/repo#sha)。

README

English | 中文

跟踪 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 正文里的 <h3> / # 标题分组渲染条目; 正文是双语的(中文段 + --- + 英文段),解析优先取中文段,缺失时回退英文段。
  • 拿不到对应 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 <registry> --no-audit --no-fund @deepseek-ai/dsh@<版本> (--prefix 永远显式传入,不用 npm config get prefix);npm 输出实时流入日志;
  5. 校验落地结果:读 package.json 版本必须等于目标版本、入口脚本 <root>/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 前缀(<prefix>/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 <spec>(detached, stdio ignore)
        ├─ 回 202(helperPid / logFile / consoleUrl / backupDir / exitInMs)
        └─ 回复上线后才 SIGTERM 自己(~700ms;15s 后硬退出兜底)
        ▼
  助手(零依赖纯 Node ESM,宿主死后继续活)
        ├─ 起恢复控制台(3098,占用则 +1…+9)
        ├─ 等 127.0.0.1:<port> 释放(超时则 SIGKILL 旧 pid)
        ├─ mv 当前安装 → 同级 .dsh-updater-backup-<版本>-<时间>(rename,原子)
        ├─ npm install -g --prefix <前缀> --registry <registry> --no-audit --no-fund @deepseek-ai/dsh@<版本>
        ├─ 校验:package.json 版本 == 目标;lib/bin.js 存在;node_modules/@deepseek-ai 非空
        ├─ 用完全相同的 file/argv/cwd/env 拉起新宿主(stdout+stderr → logs/<时间>-<pid>.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/<时间>-<pid>.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 共用分离式助手模式(同一套「宿主先回包、助手接管、控制台兜底」结构)。 本版起在 peerDependencies 中显式声明兼容 DSH 0.2.0-rc.2(官方 DSH 包的版本范围已含 ^0.2.0-rc.2),在 0.2.0-rc.2 上不会再出现兼容告警;功能与行为无变化。

License

MIT — 见 LICENSE。

仓库:https://github.com/zhengjy01/dsh-updater

内容来自项目 README(GitHub)↗

评论

评论存放在 GitHub Discussions。用 GitHub 账号登录后可发表评论或点表情。