dsh-plugin-offline-updater
v0.3.0
Published
DeepSeek Harness (DSH) plugin: check and update installed profile plugins from npm, with release-age guard and a safe-version fallback.
Maintainers
Readme
插件离线更新器(dsh-plugin-offline-updater)
DeepSeek Harness(DSH)的 Cordis 插件,用于在 Web GUI 中管理当前 profile 已安装用户插件的版本更新。
它会在侧栏增加一个「插件更新」入口,列出所有已安装的用户插件,逐个向 npm 源查询最新版本并与本地版本比较;有新版本时可直接更新,并遵守「新版本 24 小时发布年龄」限制。设置页最上方还提供「更新本插件」,用于更新插件自身。
功能
- 插件列表:读取当前 DSH profile 中已安装的用户插件(
availability === 'profile'),展示图标、名称、版本、启用状态(已启用/未启用)与描述,自动排除本插件自身;列表顺序与官方「已安装插件」页面一致(按去掉 scope 与dsh-前缀后的短名称排序)。读取 bundle 清单失败时回退到 profilepackage.json+node_modules/<pkg>/package.json。 - 检查更新:按插件调用 npm registry(含 scoped 包与私有源),比较本地版本与
dist-tags.latest,显示「已是最新版本 / 发现新版本 / 检查失败」。 - 24 小时新版本限制:按目标版本自身的发布时间判定,不足 24 小时(或发布时间未知)时弹窗确认;确认后该版本记入「已确认」,30 天内不再重复弹窗。
- 稳妥版本:存在「已满 24 小时、高于当前版本且不高于
latest、非预发布」的版本时,确认弹窗额外提供「更新到稳妥版本」,一键安装该版本。 - 更新前会话校验:只要还有会话未结束——智能体仍在生成/执行工具,或队列里还有待处理消息——更新请求会被直接拒绝(
409,返回未结束会话数量),不会查询 npm、不会改动 profile;等这些会话完成或停止后再更新。只是打开着但空闲的会话、以及后台 job 不拦更新。 - 两种更新模式:默认原位
pnpm add升级;可在设置中开启「更新前先卸载旧插件」,改为pnpm remove+pnpm add,与官方插件市场行为一致(安装失败会尽力回滚到原版本)。 - 更新本插件:设置页最上方提供「更新本插件」,复用同一套检查/更新逻辑与 24 小时门槛,但始终原位
pnpm add(不受「更新前先卸载旧插件」影响);成功后弹窗提示重启 DSH,可直接重启或稍后手动重启。更新后即使刷新页面,设置项仍保留「等待重启」状态和「重启 DSH」按钮。只适用于以 npm registry 方式安装的本插件,link/file 等安装时该项禁用并说明原因。 - 发布年龄白名单维护:以
--config.minimumReleaseAge=0放行的过新版本,安装成功后会写入 profilepnpm-workspace.yaml的minimumReleaseAgeExclude,避免之后所有插件(含官方市场)安装被ERR_PNPM_MINIMUM_RELEASE_AGE_VIOLATION拦下。 - 列表分页:客户端分页,默认每页 10 条;刷新按钮会重新拉取列表并对当前页插件静默检查一次,不会自动更新。点击刷新后当前页所有行会同时显示「检查中…」并置灰,全部检查完成后结果一起出现。单行检查、整页刷新检查或插件更新进行期间,分页按钮全部禁用,避免把进行中的操作翻到页外;操作结束后恢复。
- 并发检查数:设置里可选 4 / 6 / 8 / 10(默认 4,10 等于整页并发),决定右上角刷新时同时查询几个插件;每路查询都是一个独立的
pnpm view子进程,档位越高刷新越快、对 pnpm 与源端压力越大。 - 检查结果缓存:刷新(全局检查)的结果会缓存在页面内存里,每次刷新前先清空旧数据;在设置的「检查结果缓存时长」(默认 5 分钟,可选 5/10/30/60 分钟)内点单个插件的「检查更新」,直接复用该结果,不再重复查询 npm 源。更新成功后自动丢弃该行的缓存。
- 跟随 DSH 的 registry 语义:读
pluginManager.registries()(等价于 profile 的pnpm config get registry),私有/未知源只问它自己,绝不回退到公网镜像;查询与安装都通过 profile 自带的 pnpm 执行,自动继承.npmrc的认证、代理与 CA。 - 更新后一键重启:只要当前这次 DSH 进程里更新过插件,页面右上角刷新按钮左侧就会出现一个电源图标(悬停提示「重启 DSH」;没更新过时不显示)。确认后插件会结束并重新拉起 DSH,让新版本插件代码立即生效;重启会中断正在运行的任务与会话。目前只有 Windows 桌面版支持自动重启,其它环境会提示手动重启。
环境要求
- DSH 运行环境(
dsh web或 ElectronELECTRON_RUN_AS_NODE),Node.js ≥ 20(只需要 ESM 与 Node 内置模块;Host 端不使用全局fetch)。 - profile 内自带 pnpm(检查与更新都通过 profile 的 pnpm 执行;没有可用 pnpm 的环境无法检查或更新)。
- 仅适用于 registry(npm)来源的 profile 依赖;
link:、file:、portal:、workspace:、git:、https:、tarball 来源的插件不支持自动更新。 - 纯 ESM,无构建步骤、无 TypeScript、无打包器、无第三方运行时依赖。
安装
插件已发布到 npmjs(包名 dsh-plugin-offline-updater),两种常规装法:
- 在 DSH 的插件市场里搜索安装(市场按 npm 的
dist-tags.latest取版本); - 或直接用当前 profile 自带的 pnpm 装进 profile:
pnpm add dsh-plugin-offline-updater。
安装后侧栏会出现「插件更新」入口(可在「设置 → 插件 → 插件离线更新器」的详情页里关闭)。更新任意插件后需要重启 DSH 才会加载新版本代码:Windows 桌面版可以直接点页面右上角的「重启 DSH」按钮,其它环境请手动退出并重新打开;只改本插件自身的前端代码时刷新页面即可。
使用
- 打开侧栏「插件更新」,页面列出当前 profile 的已安装用户插件(每页 10 条)。
- 点击某行的「检查更新」,插件会查询 npm 源并给出结果;也可点右上角刷新按钮重新拉列表并对当前页做一次静默检查(只展示状态,不会自动更新)。点刷新后当前页所有插件行会同时变成置灰的「检查中…」,全部检查完成后结果一起刷新。检查或更新进行期间分页按钮会全部禁用,结束后恢复。刚刷新过、且仍在「检查结果缓存时长」窗口内时,单行「检查更新」会直接复用那次检查结果。
- 有新版本且发布时间已满 24 小时:直接更新。
- 新版本不足 24 小时:弹窗提示,可选择
- 「继续更新」:忽略该版本的 24 小时限制(记入已确认);
- 「更新到稳妥版本」:安装最近一个已满 24 小时的版本(无需确认,且不会记为已确认);
- 「取消」:不做任何操作。
弹窗可用
Esc或点击遮罩关闭。
- 更新完成后提示「已下载 vX.Y.Z,重启 DSH 后生效」,此时右上角出现「重启 DSH」电源图标:
- 点一下会先确认(提示运行中的任务会被中断),确认后自动关闭并重新打开 DSH,新版本插件代码随即生效;
- 不点也可以手动退出并重新打开 DSH;重启后按钮自动消失,直到下一次更新。
有会话还没结束时,更新会被拒绝并提示「还有 N 个会话尚未结束」,等它完成或停止后再点一次即可;只是打开着、没有在跑的会话不影响更新。
更新插件自身:在「设置 → 插件 → 插件离线更新器」的详情页最上方点「更新本插件」,流程与普通插件相同(检查 npm 源、24 小时门槛、稳妥版本),但始终原位安装;安装成功后立即弹窗提示重启 DSH,可选择「立即重启」或「稍后」。该项下方固定显示一行「当前版本 vX」,检查/更新时在同一行用 · 追加状态文案(如「检查中…」「已是最新版本」「已更新到 vX,重启 DSH 后生效」);文案过长时截断为省略号,鼠标悬停可看全文,这一行不会改变高度。
设置
设置入口位于「设置 → 插件 → 插件离线更新器」的 bundle 详情页,最上方是一个自身更新项,下面共三个开关和两个下拉选择:
| 设置 | 默认 | 说明 |
| --- | --- | --- |
| 更新本插件 | — | 检查 npm 源并原位更新插件自身;受 24 小时门槛约束,成功后提示重启。当前安装方式为 link/file 等非 registry 来源时按钮禁用。始终原位,不受「更新前先卸载旧插件」影响。 |
| 侧栏入口 | 开 | 是否在侧栏显示「插件更新」入口,切换时实时挂载/卸载页面。 |
| 忽略新版本 24 小时限制 | 关 | 开启后过新版本直接更新、不再弹窗,对所有插件全局生效。 |
| 更新前先卸载旧插件 | 关 | 关闭为原位升级;开启后先 pnpm remove 再 pnpm add,模拟官方插件市场。removable === false 的插件在此模式下拒绝更新。 |
| 并发检查数 | 4 | 右上角刷新时同时查询几个插件,下拉可选 4 / 6 / 8 / 10。每路查询是一个 pnpm view 子进程,档位越高越快、压力越大;10 等于整页(每页最多 10 条)并发。 |
| 检查结果缓存时长 | 5 分钟 | 右上角刷新后可复用全局检查结果的时间窗口,下拉可选 5 / 10 / 30 / 60 分钟。窗口内单行「检查更新」不再请求 npm 源。 |
设置与「已确认版本」持久化在 ~/.dsh/plugin-offline-updater/settings.json(DSH_HOME 存在时以其为准)。
HTTP API
Host 半注册以下同源精确路由,前缀固定为 /api/dsh-plugin-offline-updater:
| 方法 | 路径 | 说明 |
| --- | --- | --- |
| GET/HEAD | /settings | 返回 { ok, sidebarEntry, ignoreReleaseAge, uninstallBeforeUpdate, checkConcurrency, checkConcurrencyOptions, checkCacheMinutes, checkCacheOptions, selfVersion, selfUpdatable, restartPending, selfRestartPending, selfUpdatedVersion, restartSupported, restartMode, hostPid } |
| POST | /settings | 接受 { sidebarEntry?, ignoreReleaseAge?, uninstallBeforeUpdate?, checkConcurrency?, checkCacheMinutes? } 或 { key, value };checkConcurrency 只接受 checkConcurrencyOptions(4/6/8/10)里的档位,checkCacheMinutes 只接受 checkCacheOptions 中的档位,其余值 400 |
| GET/HEAD | /plugins | 返回 { ok, plugins },每项含 name、version、title、description、可选 icon(data:image/...)、removable、enabled(该组合包在 profile 中是否启用) |
| POST | /check | body { name },查询 npm 最新版本并计算是否需要确认 |
| POST | /update | body { name, version?, force? },执行更新 |
| POST | /restart | 结束并重新拉起当前 DSH(仅 Windows 桌面版;没有等待重启的更新时返回 409) |
/settings 里的 restartPending 表示「当前这个 DSH 进程里更新过插件、还没重启」,它按进程区分,重启后自动失效;restartSupported / restartMode 说明当前环境能否自动重启(desktop 或 cli / platform)。hostPid 是当前 Host 进程 pid:重启时页面只有观察到它变化(或旧 Host 先下线再恢复)才刷新,避免在进程被结束之前刷进旧 Host、因客户端模块 revision 不一致而启动失败。selfVersion 是当前进程实际加载的插件版本,selfUpdatable 表示当前 profile 中本插件是否为 npm registry 来源(link/file 等安装为 false,设置页的自身更新项会禁用)。selfRestartPending / selfUpdatedVersion 只针对本插件自身的更新:前者表示本进程更新过本插件且尚未重启,后者记录当时安装的版本;设置页刷新后据此恢复「已更新/等待重启」状态。
/check 返回 ok、name、currentVersion、latestVersion、publishedAt、ageHours、tooFresh、releaseAgeHours、updateAvailable、safeVersion、safePublishedAt、safeAgeHours、ignoreReleaseAge、acknowledged、needsConfirm、registry;拿不到 registry 数据时返回 502 { ok: false, error }。
/update 仅允许更新已安装且为 registry 来源的插件;本插件自身也可以通过它更新,但会强制走原位安装(忽略「更新前先卸载旧插件」),且要求自身是 npm registry 来源。version 必须为合法 semver 且能在源中找到。还有未结束的会话时返回 409 { ok: false, unfinishedSessions, error }(不会执行 pnpm);门槛不满足且未 force、未确认时返回 409 { needsConfirm: true, safeVersion, ... };已有更新在跑时也返回 409;未安装返回 404;pnpm 失败返回 502(错误经脱敏)。成功时返回 { ok, name, installedVersion, restartRequired: true, restartPending: true, updateMode: 'in-place' | 'reinstall', registry, application: 'restart-required' }。同一时间只允许一个更新正在执行。
所有接口(含 GET/HEAD 读取)都仅放行本机回环请求:缺失 Host/Origin 时视为非页面请求而放行(Desktop 代理可能剥离它们),存在时必须为回环且同源,sec-fetch-site: cross-site 一律拒绝,校验不通过返回 403。读取接口也校验,是为了防止 DNS rebinding 读到插件清单与设置。
目录结构
.
├─ package.json # 包清单 + dsh.bundle / dsh.client 声明
├─ cordis.patch.yml # 插入的 Loader 行(id: plugin-offline-updater)
├─ index.js # Host 半:HTTP API、插件列表、检查/更新编排
├─ client.js # Client 半:侧栏图标、插件列表页、设置卡、确认弹窗
├─ lib/
│ ├─ settings.js # 设置与「已确认版本」持久化
│ ├─ registry.js # registry 选择(DSH registryPlan 语义)+ pnpm view 结果解析 + semver 比较
│ ├─ install.js # profile 目录 / pnpm 运行时解析、pnpm view 查询、pnpm add / remove+add
│ ├─ release-age.js # minimumReleaseAgeExclude 白名单维护(guard 校验 + record 合并写盘)
│ └─ restart.js # 重启 DSH:生成批处理助手,经 WMI 在应用进程树之外执行
├─ locale/
│ ├─ en.json # 插件元信息(meta.title / meta.description)
│ └─ zh.json
├─ icon.svg # 插件图标(package.json 导出 ./icon)
├─ LICENSE # MIT
├─ README.md # 本文件
├─ .github/workflows/
│ └─ publish.yml # 推 v* tag 触发自动发布
├─ .gitattributes # workflow 的 *.yml 强制 LF 入库
└─ .gitignore # 不提交 node_modules / lockfile / 临时测试目录发布到 npm 的 tarball 只包含 package.json、README.md、LICENSE 加上 files 白名单(index.js、client.js、lib/*.js、cordis.patch.yml、locale/*.json、icon.svg),共 14 个文件;仓库里的 .github/、.gitattributes、.gitignore 和本地临时目录不会随包发布。
开发
本地开发时可在仓库根目录做语法检查(改完 Host 端文件必做):
node --check index.js
node --check client.js
Get-ChildItem lib/*.js | ForEach-Object { node --check $_.FullName }
node -e "JSON.parse(require('fs').readFileSync('package.json','utf8'))"把仓库目录以 link 方式安装进 profile 后即可调试:index.js 与 lib/ 的改动需要重启 DSH 才会加载,只改 client.js 时刷新页面即可。
已知限制
- 只处理 registry(npm)来源的 profile 依赖。
- 更新已安装插件后必须重启 DSH 才加载新版本代码。
- 更新请求在还有未结束会话时会被拒绝(智能体运行中或队列里有待处理消息);检查与安装之间没有锁住会话,极端情况下仍可能有会话在安装过程中开始运行。
- 自动重启目前只在 Windows 桌面版生效:DSH 没有对外的重启接口,插件只能结束当前进程再重新拉起,运行中的任务、终端与后台作业会被中断(会话本身会持久化)。
dsh web命令行部署与非 Windows 桌面版会提示手动重启。 - 本插件不会把自己列进插件列表;自身更新从设置页最上方的「更新本插件」发起,且要求本插件以 npm registry 方式安装(link/file 等安装时该按钮禁用)。
- 检查与更新完全依赖 profile 的 pnpm;无法执行 pnpm 的环境无法使用本插件。
