dsh-plugin-upgrader 1.6.2 → 1.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,89 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.7.0 — 2026-11-03
4
+
5
+ - **Every version is judged against the DSH that is actually running, before it is
6
+ offered.** The host refuses an install whose declared DSH range does not admit this
7
+ runtime, and on the older replacement path it refuses *after* the bundle has been
8
+ unloaded — switching a real skin plugin to `0.1.5` (`>=0.1.7-rc.1 <0.1.8-0`) left the
9
+ package removed from the profile with a dangling patch id. Both directions are covered
10
+ now. Going *down* the history on a newer DSH marks the releases that no longer admit
11
+ this runtime and disables their switch button. Going *up* on an older DSH stops
12
+ offering `latest` as an upgrade when that release does not admit this runtime, and the
13
+ row says which range it wants instead.
14
+ - **The runtime version comes from the host's own entry point.** A profile's
15
+ `@deepseek-ai/dsh-app-boot` is a link into an npx cache and measured `0.1.2-rc.1`
16
+ against a runtime that reports `0.2.1-rc.5`; reading the profile would have marked
17
+ installable releases uninstallable. When the version cannot be read, nothing is marked
18
+ — the host stays the judge, and the page only loses a hint.
19
+ - **The rules are the gate's rules.** `engines.dsh` decides alone when present;
20
+ otherwise every `peerDependencies` range on `@deepseek-ai/dsh` and `@deepseek-ai/dsh-*`
21
+ must hold, with `workspace:^`/`~`/`*` meaning "this runtime", prereleases included.
22
+ `src/host/version.ts` implements the matching, checked against `node-semver` over 49
23
+ ranges × 41 versions (2035 pairs, all agreeing). Three inequalities were wrong before
24
+ that check — most visibly `~0.1.7-rc.1` admitting `0.1.6`.
25
+ - **A batch no longer disappears when the plugin replaces itself.** Upgrading another
26
+ plugin is what makes the host re-stage this plugin's own module in a `live` profile, so
27
+ the install a user was watching could evict the code tracking it: the replacement
28
+ instance started with an empty job map, answered `unknown-job` for the id the page was
29
+ polling, and the page reported "lost contact" over an install that finished fine. The
30
+ batch store now lives on `globalThis` under a stable symbol, so whichever module
31
+ instance answers knew the batch. Cleanup only clears the run that owns it.
32
+ - **A version the host would refuse is never installed, and a failed replacement puts the
33
+ old one back.** The page already marked those releases, but a client-side mark cannot
34
+ stop a request, and the host's gate fires after pnpm has rewritten the lockfile — which
35
+ is how a refused switch used to end with the package gone. The step now reads the
36
+ target's declared range *before* touching anything and answers
37
+ `incompatible-version` with the range it wanted, leaving the profile untouched. When a
38
+ failure slips past that (the gate is still the final word), the recovery distinguishes
39
+ the two damages instead of guessing: a package that is still on disk gets re-selected, a
40
+ package that is gone gets the previous version installed again. The outcome carries
41
+ `rolledBack` so the log can say what came back, and a rollback that is itself refused
42
+ says so rather than reporting a clean failure over a missing plugin.
43
+ - **The two ways a restart wait ends badly now say different things.** A pid that never
44
+ changes means the old process is still answering; a pid that *did* change but a page that
45
+ cannot reload onto the new host means another restart. Both used to print the first
46
+ sentence, which sent people looking for a process that was already gone.
47
+ - **One unanswered poll no longer reports a healthy upgrade as lost.** Polling a batch used
48
+ to stop at the first failed request, and the tab reads "polling stopped" as "the host
49
+ restarted" — so an install that went on to succeed could still finish with a "lost
50
+ contact" banner. The host answers from the same thread that runs the install, so a
51
+ single poll timing out or coming back 5xx during pnpm is ordinary, not fatal. `fetch`
52
+ rejecting (a keep-alive socket closed under a long install) leaked out of the loop
53
+ entirely for the same reason. Polls now retry up to four times in a row, and a good poll
54
+ resets the count, so it takes ~3 s of real silence to give up.
55
+
56
+ ## 1.6.3 — 2026-11-02
57
+
58
+ - **"Restart DSH" is now a thing the page can walk you through, instead of advice it
59
+ cannot follow up on.** A step the manager answers with `restart-required` used to leave a
60
+ banner saying a restart was needed and nothing else. The banner now grows a button that
61
+ opens a dialog naming the one action that actually finishes the restart for *this* host:
62
+ quit and reopen the desktop app, or paste back the command line a terminal start used
63
+ (read from the process itself, quoted so it is safe to paste). The dialog says plainly
64
+ that the page cannot restart the host — the desktop shell keeps relaunch in its main
65
+ process, and a terminal-started host has nobody to bring it back — rather than offering
66
+ a button that would reload the page and leave the change unapplied.
67
+ - **It watches the pid, so you never have to guess whether the restart took.** After you
68
+ confirm, the dialog polls a new `GET /api/plugin-upgrader/host` route once a second until
69
+ a *different* pid answers, then reloads through the boot-document guard (the same one
70
+ 1.3.0 added), so the reload lands on a document the new host can actually serve. Two
71
+ limits are deliberate: the pid is computed per request rather than cached, because a
72
+ cached value would make a real restart look like a hang, and the whole wait is capped at
73
+ two minutes before the dialog says the host has not come back.
74
+ - **The button is tied to the result, not to the colour of the banner.** `warn` also
75
+ carries "this page has no install entry" and "a reload was stopped", and only one of the
76
+ three is finished by restarting — so `restart-required` sets the flag and starting a new
77
+ batch clears it.
78
+ - **A raster icon ships with the package.** `assets/icon.svg` stays the npm `./icon`
79
+ export and the source of truth; `assets/icon-180.png` is a 180 px render of that same
80
+ artwork produced by `scripts/render-icon.py` (Pillow, 4× supersampling), for the
81
+ marketplaces that list plugins and only accept bitmap icons. Both files are in `files`,
82
+ and the pack check now fails if the PNG goes missing.
83
+ - **`screenshots.json` lists the source-manager screenshot too.** It had been left at the
84
+ two original images when 1.6.0 added the dialog, so every market that reads the list
85
+ showed three features and two pictures.
86
+
3
87
  ## 1.6.2 — 2026-11-02
4
88
 
5
89
  - **A DSH Store notice was re-checked against the current code, not taken at face value.**
package/README.en.md CHANGED
@@ -2,6 +2,10 @@
2
2
 
3
3
  [![npm](https://img.shields.io/npm/v/dsh-plugin-upgrader.svg)](https://www.npmjs.com/package/dsh-plugin-upgrader)
4
4
  [![license](https://img.shields.io/npm/l/dsh-plugin-upgrader.svg)](./LICENSE)
5
+ [![dsh.so install verified](https://cdn.dsh.so/badge/install/dsh-plugin-upgrader@0.2.1-rc.5.svg)](https://www.dsh.so/artifact/dsh-plugin-upgrader/)
6
+ [![dsh.so risk: low](https://cdn.dsh.so/badge/dsh-plugin-upgrader.svg)](https://www.dsh.so/artifact/dsh-plugin-upgrader/)
7
+ [![DSH Market listed](https://raw.githubusercontent.com/2BingLing/dsh-market/master/assets/readme/badge-listed-en.svg)](https://dsh.market/)
8
+ [![DeepSeek Harness Plugin](https://deepseekharnessplugins.com/api/badge/zuwenyin/dsh-plugin-upgrader)](https://deepseekharnessplugins.com/plugins/zuwenyin/dsh-plugin-upgrader)
5
9
 
6
10
  > One-click upgrades plus version history and rollback for the DeepSeek Harness
7
11
  > **Plugins** page.
@@ -25,7 +29,10 @@ English · [中文](./README.md)
25
29
  runs, and a cancel button that works.
26
30
  - **Multi-select upgrade** — tick any subset of plugins and upgrade only those.
27
31
  - **Version history / rollback** — expand any plugin to see every published version
28
- (with publish date and engine requirement) and switch to it in one click.
32
+ (with publish date and engine requirement) and switch to it in one click. Every
33
+ version is first checked against the **DSH you are actually running**: releases
34
+ this runtime cannot install are marked and their switch button is disabled (the
35
+ host's gate refuses them only after the bundle has been unloaded).
29
36
  - **GitHub links** — the `GH` button opens the plugin's repository; clicking a
30
37
  version number opens that version's GitHub release page.
31
38
  - **A lagging mirror is named** — versions published upstream but not yet mirrored
@@ -38,6 +45,12 @@ English · [中文](./README.md)
38
45
  built-in sources, the ones `pluginManager` is configured with, and the ones you added.
39
46
  Built-ins cannot be edited or deleted; your own can. Every row can be tested on its
40
47
  own, and so can the source currently in use.
48
+ - **It tells you how to restart when a restart is needed** — when a result comes back
49
+ `restart-required`, the banner grows a "Restart DSH" button. It explains why this
50
+ particular change has to wait and gives the one action that actually finishes the
51
+ restart for the way you started the host: quit and reopen the desktop app, or paste
52
+ back the command a terminal start used. After you confirm, it watches the host's pid
53
+ and reloads only once a different process answers, instead of leaving you guessing.
41
54
 
42
55
  ![Source manager](https://raw.githubusercontent.com/zuwenyin/dsh-plugin-upgrader/main/screenshots/sources.png)
43
56
 
@@ -113,6 +126,7 @@ host half:
113
126
  | `POST /api/plugin-upgrader/job` | queue a batch of upgrade / switch steps, return a job id immediately |
114
127
  | `GET /api/plugin-upgrader/job?id=…` | poll progress, current phase, elapsed time, per-plugin results and the live log |
115
128
  | `POST /api/plugin-upgrader/cancel` | abort the step in flight, whichever install is running |
129
+ | `GET /api/plugin-upgrader/host` | this process's pid and how it was started, for the "restart DSH" dialog |
116
130
 
117
131
  Every route is loopback-only: a batch installs packages, so a paired remote device
118
132
  or a cross-site page must never reach it.
@@ -252,7 +266,7 @@ so the real purpose is spelled out here.
252
266
  | `https://registry.npmjs.org/{package}` | only when the configured registry is not the official one: an extra upstream check that reports a lagging mirror | an unreadable upstream means "no gap", never "everything is missing" |
253
267
  | `https://api.github.com/repos/{owner}/{repo}/tags?per_page=100` | tag list, to decide whether a version can link to `releases/tag/v…` (30 minute cache, 8 second timeout) | falls back to the `releases` list; never guesses a tag that 404s |
254
268
  | `{the address being tested}/-/ping` and its root | *Test* in the Registry dialog probes both **concurrently**, first 2xx wins (5 s budget for the whole probe) | reports the most specific reason: status code first, then timeout, then the raw network error |
255
- | the local DSH web server | the plugin's own four `/api/plugin-upgrader/*` routes | — |
269
+ | the local DSH web server | the plugin's own five `/api/plugin-upgrader/*` routes | — |
256
270
 
257
271
  The default registry is whatever pnpm is configured with (usually `.npmrc`); the
258
272
  **Registry** button on the Versions tab switches to the official registry, mainland
@@ -302,6 +316,35 @@ acceptance. `dshOperations` (install / start / uninstall / rollback) follows the
302
316
  rule: left out rather than filled with `passed` before there is a per-release run behind
303
317
  it.
304
318
 
319
+ ### Whether each version matches the DSH you run
320
+
321
+ DSH checks a plugin's declared DSH requirement at install time and **refuses** the install
322
+ when it does not hold. That check happens after the version has been taken out on the
323
+ older replacement path, which is how "I clicked switch and the plugin disappeared" really
324
+ happened. The page therefore reaches the verdict first, under exactly the host's rules:
325
+
326
+ - when the manifest declares `engines.dsh`, that range alone decides and
327
+ `peerDependencies` are ignored;
328
+ - otherwise every `peerDependencies` range on `@deepseek-ai/dsh` and
329
+ `@deepseek-ai/dsh-*` must hold, with `workspace:^` / `~` / `*` meaning "this very
330
+ runtime";
331
+ - prereleases take part in matching (`includePrerelease`), as they do at the gate.
332
+
333
+ Both directions are covered: after DSH moves forward, releases in the history that no
334
+ longer admit this runtime are marked "not for this DSH" and their switch button is
335
+ disabled; while DSH stays behind, a `latest` that this runtime cannot install stops
336
+ counting as an upgrade, and the row says which range it wants instead.
337
+
338
+ The runtime version is read from **the host's own entry point**, not from the profile: a
339
+ profile's `@deepseek-ai/dsh-app-boot` is a link into an npx cache and measured
340
+ `0.1.2-rc.1` against a runtime that reports `0.2.1-rc.5`. When it cannot be read, nothing
341
+ is marked — a guessed version would call installable releases uninstallable.
342
+
343
+ The matcher is implemented in `src/host/version.ts` and was checked against `node-semver`
344
+ over 49 ranges × 41 versions (2035 pairs, all agreeing). The `semver` package is not
345
+ pulled in because one more runtime dependency is one more supply-chain review, and the
346
+ only semantics needed here are the `includePrerelease` ones.
347
+
305
348
  ## Project layout
306
349
 
307
350
  ```
package/README.md CHANGED
@@ -2,6 +2,10 @@
2
2
 
3
3
  [![npm](https://img.shields.io/npm/v/dsh-plugin-upgrader.svg)](https://www.npmjs.com/package/dsh-plugin-upgrader)
4
4
  [![license](https://img.shields.io/npm/l/dsh-plugin-upgrader.svg)](./LICENSE)
5
+ [![dsh.so install verified](https://cdn.dsh.so/badge/install/dsh-plugin-upgrader@0.2.1-rc.5.svg)](https://www.dsh.so/artifact/dsh-plugin-upgrader/)
6
+ [![dsh.so risk: low](https://cdn.dsh.so/badge/dsh-plugin-upgrader.svg)](https://www.dsh.so/artifact/dsh-plugin-upgrader/)
7
+ [![DSH Market 已收录](https://raw.githubusercontent.com/2BingLing/dsh-market/master/assets/readme/badge-listed-zh.svg)](https://dsh.market/)
8
+ [![DeepSeek Harness Plugin](https://deepseekharnessplugins.com/api/badge/zuwenyin/dsh-plugin-upgrader)](https://deepseekharnessplugins.com/plugins/zuwenyin/dsh-plugin-upgrader)
5
9
 
6
10
  > 给 DeepSeek Harness(DSH)的「插件」页补上**一键升级**和**版本历史 / 回滚**。
7
11
 
@@ -20,6 +24,8 @@ DSH 自带的插件页能装、能卸、能看已安装列表,但缺两件事
20
24
  每步都有已用时计时和不确定态进度条,卡住时可以一键取消。
21
25
  - **多选升级** —— 勾选任意几个插件,只升级选中的这些。
22
26
  - **版本历史 / 回滚** —— 展开任意插件即可看到已发布的全部版本(含发布时间与引擎要求),点一下切到那个版本。
27
+ 每个版本都会先跟你**当前跑的 DSH** 比对一次:装不了的版本会被标出来,切换按钮也是禁用的
28
+ (宿主的兼容性闸门是在 bundle 已经卸下之后才拒绝的,不能让它走到那一步)。
23
29
  - **一键跳 GitHub** —— 每行的 `GH` 按钮打开插件仓库主页;点版本号直接进该版本的 GitHub 发布页。
24
30
  - **镜像落后会标出来** —— 配置的镜像还没同步、但上游已经发布的版本会单独标一行,
25
31
  而不是假装已经是最新版。
@@ -28,6 +34,10 @@ DSH 自带的插件页能装、能卸、能看已安装列表,但缺两件事
28
34
  - **安装源管理** —— 「注册表」按钮打开一个对话框,里面列出自带源、`pluginManager` 配的源
29
35
  和用户自己加的源;自带源不能改也不能删,自己加的可以随时改/删。每一行都能单独
30
36
  「测试」能不能连上,当前在用的那个也能一键测。
37
+ - **要重启时告诉你怎么重启** —— 升级结果里带 `restart-required` 时,提示条上多出一个
38
+ 「重启 DSH」按钮,点开说明为什么这次改动要等,并按你的启动方式给出那一条能完成重启的
39
+ 操作:桌面版是「退出应用再打开」,命令行启动的是可复制的原命令。确认重启后它会盯着
40
+ 宿主的 pid,换了进程才刷新页面,而不是让你对着一个不确定的页面猜。
31
41
 
32
42
  ![安装源管理](https://raw.githubusercontent.com/zuwenyin/dsh-plugin-upgrader/main/screenshots/sources.png)
33
43
 
@@ -95,6 +105,7 @@ dsh plugin --profile web add "$PWD"
95
105
  | `POST /api/plugin-upgrader/job` | 提交一批升级 / 切换步骤,立即返回 job id |
96
106
  | `GET /api/plugin-upgrader/job?id=…` | 轮询进度、当前阶段、已用时、逐项结果与实时日志 |
97
107
  | `POST /api/plugin-upgrader/cancel` | 中断当前步骤(任何一次安装,包括退回老路径后的重试) |
108
+ | `GET /api/plugin-upgrader/host` | 本进程的 pid 与启动方式,供「重启 DSH」对话框说明怎么重启 |
98
109
 
99
110
  所有路由仅限本机回环访问:批量操作会安装软件包,配对设备或跨站页面一律拒绝。
100
111
 
@@ -213,7 +224,7 @@ atomic-write: timed out waiting for the writer lock at …/package.json.lock
213
224
  | `https://registry.npmjs.org/{包名}` | 只当配置的不是官方源时,额外查一次上游,报告「镜像落后」 | 读不到就当作没有缺口 |
214
225
  | `https://api.github.com/repos/{owner}/{repo}/tags?per_page=100` | 取 tag 列表,判断某个版本号能不能链到 `releases/tag/v…`(30 分钟缓存,8 秒超时) | 退到 `releases` 列表页,绝不猜一个 404 的 tag |
215
226
  | `{被测试的地址}/-/ping` 与根地址 | 「注册表」对话框里点「测试」时,**并发**探测这两条,谁先 2xx 用谁(整次探测 5 秒预算) | 报最具体的原因:状态码优先,其次超时,最后网络错误原文 |
216
- | 本机 DSH Web 服务 | `/api/plugin-upgrader/*` 自己的四个路由 | — |
227
+ | 本机 DSH Web 服务 | `/api/plugin-upgrader/*` 自己的五个路由 | — |
217
228
 
218
229
  默认源是 pnpm 自己配置的那个(通常是 `.npmrc` 里的),也可以在「版本」页的「注册表」按钮里
219
230
  换成官方源、大陆镜像或自己加的源。**没有任何遥测**,不向第三方发送使用数据。
@@ -253,6 +264,29 @@ atomic-write: timed out waiting for the writer lock at …/package.json.lock
253
264
  `dshOperations`(install / start / uninstall / rollback)同理,没有逐版本的实测记录就留空,
254
265
  不写 `passed`。
255
266
 
267
+ ### 每个版本对不对得上现在的 DSH
268
+
269
+ DSH 在装插件时会检查插件声明的 DSH 要求,不满足就**拒绝安装**。这个检查发生在版本已经被
270
+ 摘掉之后(老路径是先卸再装),所以「点了切换,结果插件没了」是真实发生过的事。因此页面自己
271
+ 先算一遍,规则和宿主完全一致:
272
+
273
+ - 插件声明了 `engines.dsh` 时,**只看它**,`peerDependencies` 被忽略;
274
+ - 否则,`peerDependencies` 里每一个 `@deepseek-ai/dsh`、`@deepseek-ai/dsh-*` 都要满足,
275
+ `workspace:^` / `~` / `*` 视为「就是当前这个运行时」;
276
+ - 预发布版本参与比较(`includePrerelease`),与宿主的闸门相同。
277
+
278
+ 两种方向都会标出来:DSH 升级之后想**降级**插件,历史里那些不再接受当前运行时的版本会
279
+ 被标成「不支持当前 DSH」,切换按钮禁用;DSH 还停在旧版本时想**升级**,如果 `latest`
280
+ 不接受当前运行时,它就不再算「可升级」,行上会写明它要的是哪个范围。
281
+
282
+ 运行时版本是从**宿主自己的入口**读的,不是从 profile 读的:profile 里的
283
+ `@deepseek-ai/dsh-app-boot` 是指向 npx 缓存的链接,实测是 `0.1.2-rc.1`,而宿主真正报的是
284
+ `0.2.1-rc.5`。读不到时**什么都不标**——猜一个版本会把本来能装的版本说成不能装。
285
+
286
+ 判定用的是 `src/host/version.ts` 里自己实现的区间匹配,已经在 49 个 range × 41 个版本共
287
+ 2035 组上跟 `node-semver` 逐一对过(全部一致)。不引入 `semver` 依赖,是因为这条链路上
288
+ 多一个运行时依赖就多一次供应链审阅,而这里需要的语义只有 `includePrerelease` 那一档。
289
+
256
290
  ## 项目结构
257
291
 
258
292
  ```
Binary file