dsh-autorestart 0.7.0 → 0.8.1
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 +47 -0
- package/README.md +137 -6
- package/client.js +387 -0
- package/cordis.patch.yml +7 -0
- package/docs/README.zh.md +83 -5
- package/docs/platform-matrix.md +92 -0
- package/docs/rescue-drill.md +606 -0
- package/index.js +598 -3
- package/lib/diagnostics.js +8 -0
- package/lib/rescue-paths.js +253 -0
- package/lib/rescue.js +596 -0
- package/lib/scheduler-capability.js +300 -0
- package/lib/watchdog-arbiter.js +117 -0
- package/lib/watchdog-schedule.js +310 -0
- package/lib/watchdog-tier.js +182 -0
- package/lib/watchdog.js +479 -0
- package/locale/en.json +28 -1
- package/locale/zh.json +28 -1
- package/package.json +6 -1
- package/scripts/README.md +65 -0
- package/scripts/drill-rescue.ps1 +508 -0
- package/scripts/drill-rescue.sh +427 -0
- package/scripts/drill-watchdog.mjs +341 -0
- package/scripts/emergency-disable.sh +314 -0
- package/scripts/emergency-toggle.ps1 +136 -65
- package/scripts/rescue-strings.zh.json +168 -0
- package/scripts/restore-profile.cmd +44 -0
- package/scripts/restore-profile.ps1 +608 -0
- package/scripts/restore-profile.sh +593 -0
- package/scripts/run-tests.ps1 +1 -1
- package/scripts/watchdog.mjs +203 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.8.1 — 2026-10-29
|
|
4
|
+
|
|
5
|
+
**修复打包遗漏**:0.8.0 的 npm 包按 `package.json` 的 `files` 白名单打包,而那份白名单只收了 `scripts/*.ps1` 与 `scripts/*.cmd`——**三个 POSIX 自救脚本(`restore-profile.sh` / `emergency-disable.sh` / `drill-rescue.sh`)、`scripts/README.md`、`scripts/rescue-strings.zh.json` 全都没进包**。Linux / WSL 用户装完 0.8.0 后,文档让他去双击/运行 `scripts/restore-profile.sh`,那个文件却不存在。
|
|
6
|
+
|
|
7
|
+
现在把白名单补齐(`.sh`、`.mjs`、`scripts/README.md`、`scripts/rescue-strings.zh.json`、`CHANGELOG.md`),并用 `npm pack --dry-run` 逐项确认:**54 → 61 个文件**,三个 `.sh` 都在。单元测试 740 项仍全绿。
|
|
8
|
+
|
|
9
|
+
0.8.0 的内容与行为没有其它变化;只是那个包缺文件。
|
|
10
|
+
|
|
11
|
+
## 0.8.0 — 2026-10-29
|
|
12
|
+
|
|
13
|
+
这一版把"出事之后怎么办"做成真正的闭环:**起不来就自动还原、再拉起、留下报告**,并且桌面端与各种 Web 托管端(Windows / Linux / WSL)都覆盖。
|
|
14
|
+
|
|
15
|
+
### 新增
|
|
16
|
+
|
|
17
|
+
- **看门狗(watchdog v2)**:重启前登记一个一次性监护者;新实例按约定报到就算过关,否则**先复查一次**(防误判),确认真的没起来才**自动还原重启前的快照 → 再自动拉起一次 → 把结果写进报告**。最多还原一次、拉起一次,绝不死循环;回滚前先探活,绝不双开。
|
|
18
|
+
- **三档调度,跨环境都可用**:Windows 一次性计划任务 → Linux/WSL 的 systemd-run → 自建后台进程(setsid nohup)。都没有时**明确告知"没有自动回滚"**,绝不假装有保护。
|
|
19
|
+
- **同一时刻只有一个监护者**:v2 可用时不启动旧的 v1;v2 不可用时才回落 v1;报告里记 arbiter 字段,出事能一眼看出当时是谁在监护。
|
|
20
|
+
- **POSIX 版自救包**:restore-profile.sh / emergency-disable.sh / drill-rescue.sh(与 Windows 的 .cmd/.ps1 同一套规则:先把坏的那份挪走留证、还原后回读校验、路径闸门、不自动重启)。
|
|
21
|
+
- **设置页「出事了怎么办」**:按平台显示对应的救命脚本绝对路径,可复制、可打开文件夹;Windows 还能一键在桌面放一个快捷方式(只有你点才创建)。
|
|
22
|
+
- **装上就告诉你有没有兜底**:第一次启用时真的建一个空计划任务验证能力(建 → 回读校验 → 删 → 再确认删掉),不能用就直接说清"会丢失:重启后起不来时不会自动恢复"。
|
|
23
|
+
- **平台能力矩阵**:docs/platform-matrix.md 列出 13 条能力 × 5 个平台,逐格标清能用、有前提(并写明用户会看到什么)与做不到。
|
|
24
|
+
|
|
25
|
+
### 修复
|
|
26
|
+
|
|
27
|
+
- **同一次重启出现两个看门狗会互相废掉**:旧的 PowerShell 看门狗只还原不拉起,若它先抢到锁,新的看门狗会放弃 → 结果是"配置回滚了却没有任何东西在运行"。已改成仲裁者,同一时刻只允许一个。
|
|
28
|
+
- **集成测试会往真实机器注册计划任务**(留下空壳任务)。测试改为注入,不再碰系统。
|
|
29
|
+
- 计划任务的触发时间曾写成 UTC,导致**任务会晚 8 小时才触发**;改为本地时间。
|
|
30
|
+
- 看门狗里一个忘记 await 的探活判断让 Promise 恒为真,看门狗每次都"放弃";已修并加回归用例。
|
|
31
|
+
|
|
32
|
+
### 文档
|
|
33
|
+
|
|
34
|
+
- 讲清**关机倒数会关掉哪台机器**:Windows 桌面 / Windows web → 关这台 Windows;**WSL 里的 web → 关掉整台 Windows**(不是关 WSL);容器里没有可靠的关机方式(界面会这么说)。
|
|
35
|
+
- 自救包补齐"Windows 用哪个、Linux/WSL 用哪个",并写明 restore-profile 与 emergency-disable 的分工与选择口诀。
|
|
36
|
+
- 记录写 POSIX 脚本踩过的三个坑(dash 数单引号里的括号、双引号里的 sed 转义被吃、嵌套 JSON 平铺 grep 找不到)。
|
|
37
|
+
|
|
38
|
+
### 验证
|
|
39
|
+
|
|
40
|
+
- 单元测试 **740 项全绿**(node --test plugins/dsh-autorestart/test/*.test.mjs)。
|
|
41
|
+
- 看门狗演练(三档都真触发)+ Windows 自救演练 + **WSL 里的 POSIX 自救演练** 全部 exit 0。
|
|
42
|
+
- 全部脚本字节级纯 ASCII(rescue-strings.zh.json 无 BOM),机器上无残留计划任务。
|
|
43
|
+
- 未真关电:POSIX 的关机链路只验证到"计划层"(用户决定不为一条常识证据断开会话),矩阵里如实标注。
|
|
44
|
+
|
|
45
|
+
## 0.7.0 — 2026-10-29
|
|
46
|
+
|
|
47
|
+
这一版的主题是**认得出自己在什么环境里、并且永远不双开**,外加一个用户真能点到的问题反馈入口。
|
package/README.md
CHANGED
|
@@ -13,6 +13,13 @@ Chinese version: [README.zh.md](docs/README.zh.md)
|
|
|
13
13
|
|
|
14
14
|
`dsh-autorestart` turns a DeepSeek Harness restart into a resumable event instead of a dead end.
|
|
15
15
|
|
|
16
|
+
**New in 0.7.0** — it now also knows *how it was started*: the desktop client, somebody else's
|
|
17
|
+
supervisor (a shell script, systemd, a container, pm2, a Windows scheduled task), or a bare process
|
|
18
|
+
nobody manages. It never starts a second DSH next to a running one, and when it cannot tell it does
|
|
19
|
+
nothing instead of guessing a command. The settings page shows what is really inside the
|
|
20
|
+
pre-restart backups (count, local time, size, path), and carries a **Report a problem** entry that
|
|
21
|
+
copies the diagnostics and opens a pre-filled GitHub issue.
|
|
22
|
+
|
|
16
23
|
An agent can request a graceful restart. The plugin waits until every conversation is idle and the
|
|
17
24
|
host has been quiet for the settle window, then it restarts the host. Once the host is back, the
|
|
18
25
|
interrupted sessions resume automatically: a session that owns a durable goal gets that goal
|
|
@@ -98,8 +105,35 @@ writes that sentence and the matching command into the settings page.
|
|
|
98
105
|
|
|
99
106
|
### If DSH does not come up
|
|
100
107
|
|
|
101
|
-
|
|
102
|
-
|
|
108
|
+
**First work out which situation you are in**: if the whole environment is broken (a changed
|
|
109
|
+
configuration, another plugin, a bad upgrade) → go back to the last good state; if **only this
|
|
110
|
+
plugin** is suspect → take this plugin out instead.
|
|
111
|
+
|
|
112
|
+
**And use the scripts of YOUR platform** — a Linux machine has no `.cmd` file, and a Windows
|
|
113
|
+
machine has no `.sh`:
|
|
114
|
+
|
|
115
|
+
| Your machine | back to the last good state | take this plugin out |
|
|
116
|
+
|---|---|---|
|
|
117
|
+
| **Windows** (the desktop app, `dsh web` on Windows) | double-click `scripts/restore-profile.cmd` | double-click `scripts/emergency-disable.cmd` |
|
|
118
|
+
| **Linux / WSL / macOS** (`dsh web`) | `sh scripts/restore-profile.sh` | `sh scripts/emergency-disable.sh` |
|
|
119
|
+
|
|
120
|
+
Two scripts per platform, and they do different jobs:
|
|
121
|
+
|
|
122
|
+
- **`restore-profile.cmd` / `restore-profile.sh` — back to the last good state.** It puts the
|
|
123
|
+
newest snapshot that carries a manifest back over the profile, and leaves the broken one behind
|
|
124
|
+
as `profile.broken-<timestamp>` (moved aside, not deleted). It does not restart DSH for you.
|
|
125
|
+
- **`emergency-disable.cmd` / `emergency-disable.sh` — take this plugin out and change nothing
|
|
126
|
+
else.** It removes only `dsh-autorestart` from the profile's bundle list and keeps a backup of
|
|
127
|
+
the file.
|
|
128
|
+
|
|
129
|
+
**Which one:** "back to the last good state" → `restore-profile`; "I only suspect this plugin" →
|
|
130
|
+
`emergency-disable`. The boundaries (the profile directory has to exist, a snapshot with a
|
|
131
|
+
manifest is required, DSH is never restarted for you, the `broken-` folder stays) are written down
|
|
132
|
+
in [`scripts/README.md`](scripts/README.md).
|
|
133
|
+
|
|
134
|
+
**What each platform can do** (and what it cannot) is spelled out in
|
|
135
|
+
[`docs/platform-matrix.md`](docs/platform-matrix.md) — the settings page shows the paths of the
|
|
136
|
+
scripts that really exist on the machine you are on.
|
|
103
137
|
|
|
104
138
|
### What has really been tested
|
|
105
139
|
|
|
@@ -136,9 +170,8 @@ The settings page has a "**Report a problem**" group at the bottom:
|
|
|
136
170
|
- When the browser refuses the clipboard (an insecure context, or no permission), the plugin shows
|
|
137
171
|
the diagnostics in a **selectable** box and says so — a report never dies because the browser said
|
|
138
172
|
no.
|
|
139
|
-
- No GitHub account? The same group
|
|
140
|
-
|
|
141
|
-
paste).
|
|
173
|
+
- No GitHub account? The same group shows the mailbox (252170229@qq.com) as plain text, plus a
|
|
174
|
+
"Copy the diagnostics" button.
|
|
142
175
|
|
|
143
176
|
## What it occupies in DSH
|
|
144
177
|
|
|
@@ -165,7 +198,7 @@ what it adds before you install it.
|
|
|
165
198
|
|
|
166
199
|
## Install
|
|
167
200
|
|
|
168
|
-
Current version: **0.
|
|
201
|
+
Current version: **0.8.1**.
|
|
169
202
|
|
|
170
203
|
From npm — the package is published under the name `dsh-autorestart`:
|
|
171
204
|
|
|
@@ -485,6 +518,75 @@ own health route. A slow start inside `rollbackTimeoutMs` is never treated as a
|
|
|
485
518
|
healthy host is never touched. The restore itself is bounded as well: every copy step gives up after
|
|
486
519
|
`rollbackCopyTimeoutMs` (default `120000`) and says so in the report.
|
|
487
520
|
|
|
521
|
+
### When the machine refuses a scheduled task (the automatic rollback is gone)
|
|
522
|
+
|
|
523
|
+
The safety net above hangs on a **one-off scheduled task**: the helper that brings the host back
|
|
524
|
+
after a restart is a task that fires a minute later. On a machine where such a task cannot be
|
|
525
|
+
created (a corporate policy, a locked-down account, a machine without the rights) the net is
|
|
526
|
+
silently missing.
|
|
527
|
+
|
|
528
|
+
So the plugin **checks the machine once** — the first time it runs there — and tells the truth in
|
|
529
|
+
the settings page:
|
|
530
|
+
|
|
531
|
+
- `Automatic rollback: available` — the net is there.
|
|
532
|
+
- `Automatic rollback: **not available**` — and exactly what is lost: *DSH will not recover by
|
|
533
|
+
itself when it does not come up after a restart*. The line points at the restore script in
|
|
534
|
+
"If DSH will not start", which is the way out.
|
|
535
|
+
|
|
536
|
+
**What this deliberately does NOT do:**
|
|
537
|
+
|
|
538
|
+
- **No fallback chain.** There is no "use the startup folder instead", no "use a systemd timer
|
|
539
|
+
instead", no second mechanism that is tried when the first one is refused.
|
|
540
|
+
- **No bypassing the policy.** The plugin does not look for a way around a machine that says no.
|
|
541
|
+
If the machine refuses, the answer is "not available" — and only that is reported.
|
|
542
|
+
|
|
543
|
+
A refused check is **never** a failed installation: the plugin works exactly as before, only the
|
|
544
|
+
safety net is missing. The check itself is harmless by construction — it registers a task that
|
|
545
|
+
fires in two minutes and does nothing (`cmd /c exit 0`), reads it back, deletes it, and reads it
|
|
546
|
+
back once more to prove that it is gone. It runs **once**, not on every visit of the settings page,
|
|
547
|
+
and it never leaves a task behind (not even when a step of it fails).
|
|
548
|
+
|
|
549
|
+
### The watchdog: the three ways it can be triggered
|
|
550
|
+
|
|
551
|
+
The rollback above needs somebody to *wait*: the host is restarted, and a
|
|
552
|
+
one-off thing has to notice that the new host never came up. That thing is the
|
|
553
|
+
**watchdog** (`scripts/watchdog.mjs`), and how it is triggered is the part
|
|
554
|
+
that differs between the machines — so the plugin picks the best way the
|
|
555
|
+
machine has and says which one it picked:
|
|
556
|
+
|
|
557
|
+
| Tier | Mechanism | Where |
|
|
558
|
+
|---|---|---|
|
|
559
|
+
| `scheduled-task` | a one-off Windows scheduled task (read back after it was registered, deleted completely afterwards) | the desktop app and `dsh web` on Windows |
|
|
560
|
+
| `systemd-run` | a systemd user timer (`systemd-run --user --on-active=…`), reset when it is over | `dsh web` on Linux, WSL with systemd |
|
|
561
|
+
| `detached-process` | a background process of our own (`setsid nohup sh -c 'sleep …; …'`), its pid in the state directory | containers, WSL without systemd |
|
|
562
|
+
| `none` | nothing on this machine | then there is **no automatic rollback**, and the settings page says so |
|
|
563
|
+
|
|
564
|
+
**One function decides it** (`lib/watchdog-tier.js`), and the settings page, the
|
|
565
|
+
diagnostics and the watchdog itself all ask that one function — so the warning
|
|
566
|
+
("this machine has no automatic rollback") can never drift away from what the
|
|
567
|
+
plugin actually does.
|
|
568
|
+
|
|
569
|
+
The flow is the same for every tier (`lib/watchdog.js`): wait for the new host
|
|
570
|
+
to write the ready marker → ask again (a slow host is not a dead host) → is an
|
|
571
|
+
instance alive? (then there is nothing to restore) → take the shared lock
|
|
572
|
+
(`rollback.lock`, so exactly one of us restores) → restore the snapshot → start
|
|
573
|
+
the host again → wait for the ready marker once more. At most **one** restore
|
|
574
|
+
and **one** start; then the watchdog stops and writes what happened into
|
|
575
|
+
`<stateDir>/last-watchdog-report.json` (which tier it used, and why).
|
|
576
|
+
|
|
577
|
+
**Only one watchdog at a time.** There is an older watcher in this plugin as
|
|
578
|
+
well (it restores the snapshot but does **not** start the host again). Both take
|
|
579
|
+
the same lock, so running them together is worse than running one: the one that
|
|
580
|
+
loses the lock gives up — and if the new one lost, the profile would be restored
|
|
581
|
+
and **nothing would be running**. So the plugin decides once, in one place
|
|
582
|
+
(`lib/watchdog-arbiter.js`): the new watchdog takes over whenever it can be
|
|
583
|
+
armed at all, and the old one only runs when the new one cannot (no tier on this
|
|
584
|
+
machine, the watchdog is switched off, or the registration failed). The report
|
|
585
|
+
names the arbiter (`arbiter: "v2"` or `"v1"`), so it is visible at a glance who
|
|
586
|
+
was watching.
|
|
587
|
+
|
|
588
|
+
`watchdogEnabled: false` switches it off entirely.
|
|
589
|
+
|
|
488
590
|
### The access link after a restart
|
|
489
591
|
|
|
490
592
|
The boot token of `dsh web` is created **per process**, so the link the browser holds answers `401`
|
|
@@ -711,6 +813,35 @@ be cancelled — the card has a button, and the moon button itself becomes a tog
|
|
|
711
813
|
is armed. If a conversation starts working again during the countdown, the countdown goes back to
|
|
712
814
|
"waiting" and nothing is switched off.
|
|
713
815
|
|
|
816
|
+
### Which machine gets switched off?
|
|
817
|
+
|
|
818
|
+
The plugin works out where it runs and switches off **that machine** — and the label in the
|
|
819
|
+
settings says which one, so there is no guessing:
|
|
820
|
+
|
|
821
|
+
| Where DSH runs | What the shutdown switches off | The label you see |
|
|
822
|
+
|---|---|---|
|
|
823
|
+
| The Windows desktop app | that Windows computer | "Switch off this Windows computer" |
|
|
824
|
+
| `dsh web` on Windows | that Windows computer | "Switch off this Windows computer" |
|
|
825
|
+
| `dsh web` on a Linux host | that Linux host | "Shut down this Linux host" |
|
|
826
|
+
| **`dsh web` inside WSL** | **the whole Windows machine** (not the little WSL box!) | "The DSH inside WSL → switches off the whole Windows" |
|
|
827
|
+
| A container | nothing at all (there is no command) | "This machine has no reliable way to switch off" |
|
|
828
|
+
|
|
829
|
+
> ⚠️ **A shutdown countdown inside WSL means your whole machine goes down** — not just that
|
|
830
|
+
> web service. WSL is a small Linux environment *inside* Windows; shutting that environment
|
|
831
|
+
> down would only stop the distribution. To switch off the **host**, the plugin goes through
|
|
832
|
+
> the mounted drive: `shutdown.exe` (`/mnt/c/Windows/System32/shutdown.exe /s /f /t 0`), with
|
|
833
|
+
> `powershell.exe -Command "Stop-Computer -Force"` and `cmd.exe /c shutdown.exe /s /f /t 0`
|
|
834
|
+
> as the fallbacks. In other words: **the host goes off.**
|
|
835
|
+
|
|
836
|
+
**How far this is verified.** The *plan* is verified for every platform
|
|
837
|
+
(`test/sleep-plan-posix.test.mjs`: inside WSL the plan must take the "switch off the whole
|
|
838
|
+
Windows" branch, a plain Linux plan must not contain a single Windows command, and a machine
|
|
839
|
+
without a way to switch off has an empty command and **executes nothing**). The real power-off
|
|
840
|
+
is verified on **Windows**; on POSIX it is deliberately **not** switched off for real — a
|
|
841
|
+
common-sense fact is not worth ending every session on the machine for.
|
|
842
|
+
|
|
843
|
+
The full picture per platform is in [`docs/platform-matrix.md`](docs/platform-matrix.md).
|
|
844
|
+
|
|
714
845
|
### Shutdown and restart: who goes first
|
|
715
846
|
|
|
716
847
|
**The short version.** Pressing the moon button means "switch the machine off when the work is
|