dsh-autorestart 0.3.6 → 0.6.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/README.md +254 -11
- package/client.js +2500 -147
- package/cordis.patch.yml +24 -0
- package/docs/README.zh.md +160 -15
- package/index.js +1336 -80
- package/lib/checkpoint.js +116 -0
- package/lib/sleep.js +1012 -0
- package/locale/en.json +108 -1
- package/locale/zh.json +108 -1
- package/package.json +1 -1
package/cordis.patch.yml
CHANGED
|
@@ -25,6 +25,14 @@
|
|
|
25
25
|
relaunchDelaySeconds: 2
|
|
26
26
|
# quiet window (ms) after the last conversation activity before a restart may fire
|
|
27
27
|
settleMs: 2000
|
|
28
|
+
# Grace window (ms): the extra wait after the LAST conversation finished,
|
|
29
|
+
# before the host is really restarted or switched off. This plugin can
|
|
30
|
+
# only see the DSH agents — other plugins (a memory plugin that writes
|
|
31
|
+
# its notes a few seconds later, a summary, an MCP callback) queue
|
|
32
|
+
# their work with a timer of their own, and that work is invisible
|
|
33
|
+
# here. A DROPDOWN in the settings page: 0 / 3 / 5 / 10 / 20 / 30
|
|
34
|
+
# seconds. `0` means "no extra wait" (the behaviour of 0.4.0).
|
|
35
|
+
restartGraceMs: 10000
|
|
28
36
|
# dsh web: reload the page when the host boot token changes
|
|
29
37
|
autoReloadPage: true
|
|
30
38
|
# hard-block shell commands that would kill/relaunch DSH by hand
|
|
@@ -45,3 +53,19 @@
|
|
|
45
53
|
# How long the detached watcher waits for the relaunched host (ms) before
|
|
46
54
|
# it restores the snapshot and launches the host again.
|
|
47
55
|
rollbackTimeoutMs: 90000
|
|
56
|
+
# 收工关机:Show the graceful-shutdown button at the foot of the left
|
|
57
|
+
# sidebar, next to the Settings row (root scope: it is there even when
|
|
58
|
+
# no conversation is open).
|
|
59
|
+
# The button arms the countdown; the machine is switched off for real
|
|
60
|
+
# (a WSL host shuts Windows down, not the WSL distribution).
|
|
61
|
+
showSleepButton: true
|
|
62
|
+
# How many seconds the countdown lasts after every conversation is done.
|
|
63
|
+
# A DROPDOWN in the settings page: 0 / 10 / 20 / 30 / 60 seconds.
|
|
64
|
+
# `0` means no countdown at all — the machine is switched off as soon
|
|
65
|
+
# as every conversation is idle (the busy conversations still block
|
|
66
|
+
# it). The plugin counts down itself, so the operating system is asked
|
|
67
|
+
# to shut down immediately afterwards.
|
|
68
|
+
sleepCountdownSeconds: 20
|
|
69
|
+
# A custom shutdown command. Empty = the host is detected automatically
|
|
70
|
+
# (Windows / WSL / Linux / macOS); a value replaces the detected command.
|
|
71
|
+
sleepCommand: ''
|
package/docs/README.zh.md
CHANGED
|
@@ -18,8 +18,9 @@
|
|
|
18
18
|
- 多个会话同时要求重启时合并成一次,且每个请求者都会被唤醒;
|
|
19
19
|
- 带 goal 的会话不会把重启卡死:重启前自动**解除(disarm)**它的 goal(当前那一轮照常跑完,什么都不丢),重启之后自动重新上链;
|
|
20
20
|
- 重启前有两道检查:宿主侧查一次,独立 helper 在杀进程前再查一次;
|
|
21
|
-
- 界面上能看到状态:输入框工具行左侧的"待重启 ·
|
|
21
|
+
- 界面上能看到状态:输入框工具行左侧的"待重启 · 等待 N 个任务结束"小胶囊、左侧栏会话标题旁的圆点;快重启时圆点呼吸,点胶囊看详情;
|
|
22
22
|
- 可配置:静默窗口(默认 2 秒)、杀进程前等待(默认 2 秒)、忙碌时是否允许强制重启等。
|
|
23
|
+
- 同一套"等到大家都安静"的想法还做成了**收工关机**:左侧栏最底部(设置按钮旁边)一个月亮按钮 → 等所有会话收工 → 屏幕上倒数(一张浮动卡片:大字 + 鲸鱼娘)→ **关掉整台电脑**(见「收工关机」)。
|
|
23
24
|
|
|
24
25
|
场景对照:
|
|
25
26
|
|
|
@@ -55,8 +56,8 @@ session resume on boot, durable-goal re-arm, and an agent-callable idle-aware gr
|
|
|
55
56
|
| 类型 | 具体占用 |
|
|
56
57
|
| --- | --- |
|
|
57
58
|
| Agent 工具(6 个) | `continuum_restart`、`continuum_status`、`continuum_cancel_restart`、`continuum_park_goal`、`continuum_recover_now`、`continuum_handoff` |
|
|
58
|
-
| HTTP 路由(
|
|
59
|
-
| 前端槽位(
|
|
59
|
+
| HTTP 路由(7 条) | `<healthPath>`(默认 `/__dsh_autorestart`)下的 `GET /health`(另有 `?state=1`、`?gate=1`、`?probeIndex=1`)、`POST /park`、`POST /recover`、`POST /handoff`、`GET /sleep`、`POST /sleep`(收工关机——桌面端和 web 端走的是同一条路由)、`GET /announcements`、`POST /announcements`(一次性新功能公告);只接受本机回环或已认证的浏览器 |
|
|
60
|
+
| 前端槽位(6 个) | `conversation.input.left`(待重启小胶囊,id `autorestart-restart-pill`)、`sidebar.session.row.leading`(侧栏状态圆点,id `autorestart-restart-dot`)、`conversation.input.dock`(一次性安装提问,id `autorestart-backup-choice`;一次性新功能公告,id `autorestart-feature-announcement`)、`sidebar.footer.action`(收工关机的月亮按钮,id `autorestart-sleep-button`)、`shell.overlay`(倒数浮动卡片)、`settings.section`(设置页里的「自动重启与关机」分区,id `dsh-autorestart`) |
|
|
60
61
|
| 系统提示词 | 一段「重启政策」说明,告诉 agent 怎么请求重启、插件会做什么 |
|
|
61
62
|
| 宿主事件 | `agent/status`(跟踪哪些会话在忙)、`tools/pre-execute`(拦下"手动杀/重启 DSH"的 shell 命令)、启动时的恢复流程 |
|
|
62
63
|
| profile 行 | 一条 bundle 行:`dsh-autorestart` |
|
|
@@ -83,8 +84,24 @@ session resume on boot, durable-goal re-arm, and an agent-callable idle-aware gr
|
|
|
83
84
|
- **只问一次**:只有 `localStorage['dsh-autorestart:backup-choice-v1']` 缺失时才渲染;点任一项都会写下这个键,之后不再出现。
|
|
84
85
|
- **关掉也算作答**:按 `×` 或 `Esc` 与点选项写的是同一个「已作答」标记,这个浏览器此后**不会再被问**;关掉等于保留默认(配置 + 已安装插件)。
|
|
85
86
|
- **不打扰**:面板是 `role="status"` 的普通块,放在输入框下方的 dock 里;可聚焦、键盘可达、`Esc` 关闭。
|
|
86
|
-
- **答案存在哪**:宿主用官方 `configEditor` Service 写进 profile
|
|
87
|
-
- **想改主意**:设置 →
|
|
87
|
+
- **答案存在哪**:宿主用官方 `configEditor` Service 写进 profile 配置(就是设置分区里那个 `backupIncludePlugins` 键,**只有一份真相**)。万一这个服务不可用,才退化到写 `<DSH_HOME>/storages/dsh-autorestart/install-choice.json` 并在日志里 `warn`。**托管 profile 如果自己没有声明 `dsh-autorestart` 行**,就没有行可写,答案同样落在这个文件里而不是 profile 里 —— 声明那一行(见「平台与部署」)才能让 profile 配置保持唯一真相。
|
|
88
|
+
- **想改主意**:设置 → 自动重启与关机 → `backupIncludePlugins`。`continuum_status` 里随时能看到当前生效值(`backupIncludePlugins: true | false`)。
|
|
89
|
+
|
|
90
|
+
### 升级也会被告知一次:从旧版升级会看到一次新功能公告
|
|
91
|
+
|
|
92
|
+
**它补的漏洞**:上面那两个安装提问只在**首次安装**时出现一次。已经装好的老用户升级之后,**新功能对他们等于不存在** —— 新做的「收工关机」月亮按钮,老用户根本不会知道。所以从 **0.5.0** 起,每加一个新功能,都会在同一个位置(输入框下方的 dock)弹**一次**公告卡片:
|
|
93
|
+
|
|
94
|
+
> **新功能:收工关机**
|
|
95
|
+
> 左侧栏最底部多了一个 🌙「收工关机」按钮。
|
|
96
|
+
> 点一下 → 所有会话收工后倒数 **N 秒**关机。
|
|
97
|
+
> 想改行为,去「设置 → 自动重启与关机」。
|
|
98
|
+
> **知道了**
|
|
99
|
+
|
|
100
|
+
- **只有升级才会看到**:宿主把「这个功能的版本」和「已经展示过的版本」比一比(记在检查点文件的 `seenFeatureVersions` 里)。**新装**的用户不会看到这张卡 —— 安装提问已经把功能讲清楚了,答完就等于"已看过"。
|
|
101
|
+
- **每个版本只弹一次**:点「知道了」→ 宿主记下版本 → 卡片立刻消失 → **以后再也不出现**(重启宿主不会,升级到下一个版本也不会 —— 除非那个新版本又改了同一个功能,那时才再公告一次)。
|
|
102
|
+
- **没点不会静默吞掉**:卡片一直挂在那里等你回答;不回答,下次启动还会再出现一次。
|
|
103
|
+
- **秒数是读出来的,不是写死的**:卡片里的秒数就是当前生效的 `sleepCountdownSeconds`;月亮按钮被关掉时,卡片还会补一句「按钮目前关闭,可在设置里打开。」
|
|
104
|
+
- **状态存在哪**:`<DSH_HOME>/storages/dsh-autorestart/state.json`(和插件的检查点同一个文件)。宿主用 `GET /announcements` 报(`{ ok, pending: [{ feature, version }] }`),用 `POST /announcements`(body `{ feature }`)记。
|
|
88
105
|
|
|
89
106
|
## 工作方式
|
|
90
107
|
|
|
@@ -117,6 +134,8 @@ agent/status(running) ──► 写 checkpoint(<DSH_HOME>\storages\dsh-autores
|
|
|
117
134
|
|
|
118
135
|
## 安装
|
|
119
136
|
|
|
137
|
+
当前版本:**0.6.0**。
|
|
138
|
+
|
|
120
139
|
```powershell
|
|
121
140
|
# 在 dsh 里(或对 Agent 说):把工作区里的这个包安装为 profile bundle
|
|
122
141
|
# plugin_manager install_bundle target = <本目录绝对路径>
|
|
@@ -150,10 +169,30 @@ dsh plugin --profile desktop add C:\path\to\dsh-autorestart
|
|
|
150
169
|
| `restartCommand` | `''` | 自定义重启命令,支持 `{pid}` `{ppid}` `{app}` 占位符 |
|
|
151
170
|
| `relaunchDelaySeconds` | `2` | 脱离的 helper 在杀掉应用**之前**等几秒(`ping -n 3`,约 2 秒) |
|
|
152
171
|
| `settleMs` | `2000` | 静默窗口(ms):最后一次会话活动之后,宿主要安静这么久,重启才允许开始 |
|
|
172
|
+
| `restartGraceMs` | `10000` | 宽限期(ms):最后一个会话收工后,再等这么久才真正重启/关机——**下拉框:0 / 3 / 5 / 10 / 20 / 30 秒**,默认 10;`0` = 不额外等 |
|
|
153
173
|
| `autoReloadPage` | `true` | `dsh web`:host 重启(bootId 变化)后让页面自动 reload |
|
|
154
174
|
| `blockManualRestart` | `true` | 硬拦截 agent 用 shell 命令手动杀掉/重启 DSH,并引导到 `continuum_restart` |
|
|
155
175
|
| `healthPath` | `/__dsh_autorestart/health` | 健康检查路由(返回 bootId/pid/phase/自检标志) |
|
|
156
176
|
| `continuationText` | `''` | 覆盖默认续接提示文本 |
|
|
177
|
+
| `backupOnRestart` | `true` | 每次重启前给 profile 拍快照;重启后起不来时能自动回退 |
|
|
178
|
+
| `backupIncludePlugins` | `true` | 快照里是否带上已安装插件目录(本机约 117 MB、多约 3 秒);关掉就只备配置文件 |
|
|
179
|
+
| `maxBackups` | `2` | 保留几份快照;写新的时自动删旧的 |
|
|
180
|
+
| `rollbackTimeoutMs` | `90000` | 看护脚本等新宿主多久(毫秒),超时就回退快照并再拉起一次 |
|
|
181
|
+
| `showSleepButton` | `true` | 收工关机的月亮按钮是否显示在左侧栏最底部、设置按钮旁边 |
|
|
182
|
+
| `sleepCountdownSeconds` | `20` | 收工关机在真正关机前倒数几秒——**下拉框:0 / 10 / 20 / 30 / 60 秒**,默认 20;`0` 表示**不倒数、立即关机**(仍会先等所有会话收工) |
|
|
183
|
+
| `sleepCommand` | `''` | 收工关机的自定义关机命令;留空 = 自动识别宿主(Windows 用 `cmd.exe /c …`,其它平台用 `/bin/sh -c …`) |
|
|
184
|
+
|
|
185
|
+
### 为什么收工后还要多等几秒
|
|
186
|
+
|
|
187
|
+
**我们只看得到 DSH 的会话。** 这个插件知道的是:某个会话还在不在跑、有没有排队的 inbox 消息、最后一个是什么时候闲下来的。它**看不到别的插件用定时器排队的活** —— 比如记忆插件在会话结束后过几秒才把这次对话写进记忆、比如一次总结、一次 MCP 回调、一次日志落盘。对我们来说这些活是不存在的,所以「所有 agent 都闲了」并不等于「现在真的没有事情在发生」。
|
|
188
|
+
|
|
189
|
+
**宽限期就是为这件事留的。** `restartGraceMs`(默认 **10 秒**)的意思是:**最后一个会话收工后,再额外等这么久,才真正重启 / 关机**。这是一层普通的、可配置的保险,**不针对任何具体插件**。
|
|
190
|
+
|
|
191
|
+
- **两条路都守它**:重启闸门等的是 `max(settleMs, restartGraceMs)` 的安静;收工关机也在**开始倒数之前**先等满这个宽限期(所以宽限期不会把已经在走的倒计时顶回去一轮)。
|
|
192
|
+
- **等待期间又有活,重新计时**:宽限期永远跟着「最后一次活动」走 —— 会话又开始干活,就从那一刻重新等。
|
|
193
|
+
- **`0` 就是「和以前一样」**:`restartGraceMs: 0` 时行为与 0.4.0 完全一致。
|
|
194
|
+
- **在哪改**:设置 → 自动重启与关机 → 「收工后再等几秒(给记忆/总结类插件留时间)」。
|
|
195
|
+
- **怎么排障**:状态接口都带着它 —— `GET /health?state=1`、`GET /health?gate=1`、`continuum_status`、`GET /sleep` 都会报 `graceMs`(配了多宽限期)和 `quietForMs`(已经安静了多久)。
|
|
157
196
|
|
|
158
197
|
## Agent 工具与"它怎么知道该用插件"
|
|
159
198
|
|
|
@@ -190,6 +229,106 @@ dsh plugin --profile desktop add C:\path\to\dsh-autorestart
|
|
|
190
229
|
所以**不需要改 AGENTS.md**——这是 Host 插件钩子(工具注册 + system prompt section + tools.guard),对 profile 内所有 agent 生效;
|
|
191
230
|
你当然也可以额外在 AGENTS.md 里写一条团队规范,但它只是重复。
|
|
192
231
|
|
|
232
|
+
## 收工关机(Graceful shutdown)
|
|
233
|
+
|
|
234
|
+
**它干什么。** 左侧栏最底部一个月亮按钮:点一下 → 等**所有**会话都收工 → 屏幕上倒数(一个巨大的数字 + 一只蓝色小鲸鱼,每秒换一句中文)→ **关掉整台电脑**。倒数期间随时可以取消:卡片上有按钮,月亮按钮本身在关机期间也变成了"再点一下 = 取消"。倒数期间要是有会话又开始干活,倒计时会退回"等待",**绝不关机**。
|
|
235
|
+
|
|
236
|
+
**按钮在哪。** 月亮按钮在**左侧栏最底部、设置按钮旁边**(`sidebar.footer.action` 槽位)。这个槽位是 **root 作用域**的,所以**没有会话、切换任何页面时它也都在**。侧栏收成窄轨(折叠态)时按钮只显示月亮图标(`title` 和 `aria-label` 仍然说明它是干什么的),不会把窄轨撑变形。
|
|
237
|
+
|
|
238
|
+
**倒计时不遮界面。** 倒数是角落里的一张**浮动卡片**,不是全屏遮罩:它不压暗页面、不锁滚动、也不吃掉任何点击——卡片外面照常能读正文、能滚动、能点侧栏切会话,卡片自己**只有那个「算了,别关」按钮能点**。切换会话或切换页面也**不会打断或重置**倒数:秒数来自宿主的轮询,不是来自当前打开的那个视图。
|
|
239
|
+
|
|
240
|
+
**倒数多长由设置分区决定。** `sleepCountdownSeconds` 是一个**下拉框**:0 / 10 / 20 / 30 / 60 秒,默认 20 秒。`0` = **不倒数、立即关机**:所有会话一收工、闸门一开就直接关机(忙着的会话照样挡住它),屏幕上**不会出现倒计时卡片**。配置里还留着下拉框之外的老数值(15、45…)时,会自动吸附到最近的一档(15→20、45→60),不会把插件弄坏。这个下拉框就在**设置 → 自动重启与关机**分区里。
|
|
241
|
+
|
|
242
|
+
**点一下就够了。** 月亮按钮按下就武装:宿主等所有忙会话收工,在角落里倒数 **20 秒**(浮动卡片,不遮挡界面),然后关掉整台机器。**再点一下**(或点卡片上的「算了,别关」)就取消。除此之外什么都不用用户做:没有确认框、不用点第二次。
|
|
243
|
+
|
|
244
|
+
### 关机和重启谁先谁后
|
|
245
|
+
|
|
246
|
+
**一句话版本。** 点月亮按钮的意思是「**等活干完了再关电脑**」,不是「现在就算还有别的在跑也关掉」。所以:
|
|
247
|
+
|
|
248
|
+
1. **有待重启时,关机先让路。** 队列里还有一次重启没执行时,**不会关机** —— 卡片上写的是「等这次重启完成后再关机」。先重启,会话跟着回来继续干活,等它们都收工了,机器才关。
|
|
249
|
+
2. **重启之后活还在继续。** 跟着重启回来的会话是在干活的:只要还有会话在忙,关机就一直等;**所有**会话都空了才关。
|
|
250
|
+
3. **关机的意图跨重启保留。** 你只按了**一次**按钮。重启不会把这个请求吃掉:它被写进检查点文件,新进程启动后接着等那些活 —— **你不需要再点一次**。
|
|
251
|
+
4. **随时可以反悔。** 等待卡片上有「不关了」按钮(再点一次月亮按钮也行):一按,这个意图就彻底没了。
|
|
252
|
+
|
|
253
|
+
**意图存在哪。** 检查点文件(`<DSH_HOME>/storages/dsh-autorestart/state.json`)里的 `sleep: { armed, armedAt, countdownSeconds }`。机器关掉、关机命令没跑起来、或者你点了「不关了」,它都会被清掉。**记录坏掉就直接丢弃** —— 宁可让你再点一次,也不能因为半截文件让机器永远自己关自己。
|
|
254
|
+
|
|
255
|
+
**排障用。** `GET /sleep` 会报 `armedAt`(什么时候点的关机),旁边还有 `phase`、`blockers`、`graceMs`。
|
|
256
|
+
|
|
257
|
+
### 故障排查:点了没关机
|
|
258
|
+
|
|
259
|
+
**下面是真实宿主返回的东西**(文案是中文,字段名不是):
|
|
260
|
+
|
|
261
|
+
```
|
|
262
|
+
POST /__dsh_autorestart/sleep {"action":"arm"} # 有 3 个会话在干活
|
|
263
|
+
→ phase=waiting armed=true remainingMs=20000(满值=没开始倒数)
|
|
264
|
+
message: 等 3 个任务收工,收工后开始倒数。
|
|
265
|
+
|
|
266
|
+
# 制造一个待执行的重启之后:
|
|
267
|
+
GET /__dsh_autorestart/sleep
|
|
268
|
+
→ phase=waiting armed=true remainingMs=20000
|
|
269
|
+
message: 等这次重启完成后再关机……
|
|
270
|
+
blockers: [{"sessionId":"restart-pending","kind":"restart-pending",
|
|
271
|
+
"label":"待执行的重启","name":"待执行的重启"}]
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
**`remainingMs` 是满值 = 倒数还没开始**,也就是机器在等什么东西。下面是可能的原因,以及该怎么办:
|
|
275
|
+
|
|
276
|
+
1. **还有任务在跑。** `blockers[]` 列的就是它们,看 `kind`:`session` 是会话,`subagent` 是某个会话派出去的子智能体。**子智能体也算在干活,这是故意的** —— 否则重启会把它掐断。**怎么办:** 等它干完,或者把那个会话停掉;列表会自己变短。
|
|
277
|
+
2. **有待执行的重启。** `kind` 是 `restart-pending`(`label`/`name` 都是 `待执行的重启`)。这是正常排队,不是故障:先重启,会话跟着回来,完事之后倒数自动接着走。**怎么办:** 不用管,等重启完即可(也可以用 `continuum_cancel_restart` 把这次重启取消掉)。
|
|
278
|
+
3. **刚收工,还在宽限期里**(默认 10 秒,给记忆/总结类插件留落盘时间)。**你会看到:** `graceMs` 是配的窗口,`quietForMs` 是宿主已经安静了多久。**怎么办:** 等着;等待期间又有会话开始干活,计时会重新开始(这就是这个窗口的意义)。不想要这段等待就在设置里把它调成 `0`。
|
|
279
|
+
4. **倒数期间又有会话开始干活。** 倒数退回「等待」,`remainingMs` 变回满值。**你会看到:** 消息里说又有谁开始干活了,`blockers[]` 列着是谁。**怎么办:** 等他们干完。
|
|
280
|
+
5. **想看「到底等谁」。** `continuum_status` 的 `blockedBy[]` 和 `GET /sleep` 的 `blockers[]` 是**同一份数据**(前者给 agent 看,后者给脚本看)。每条都有 `kind`(它是什么)、`label`(会话标题)、`name`(完整称呼,比如「写代码的会话的子智能体」)。标题解析不到时 `label` 是 `null`、`name` 是空字符串 —— 这时候等待那句话就只说「等 3 个任务收工」,不带名字列表。
|
|
281
|
+
6. **彻底不想关了。** 等待卡片上的取消按钮(或者再点一次月亮按钮,或者 `POST /sleep {"action":"cancel"}`)会把这个意图彻底清掉:`armed` 变回 `false`,`armedAt` 变回 `null`。
|
|
282
|
+
|
|
283
|
+
**桌面端和 web 端行为完全一致。** 没有票据、没有额外请求头、没有隐藏模式:`POST /__dsh_autorestart/sleep` 只带一个动作,两端走的是同一条路由。脚本(或者 agent)同样可以驱动它:
|
|
284
|
+
|
|
285
|
+
```bash
|
|
286
|
+
# 武装(倒数之后关机)
|
|
287
|
+
curl -s -X POST http://127.0.0.1:<端口>/__dsh_autorestart/sleep \
|
|
288
|
+
-H 'content-type: application/json' -d '{"action":"arm"}'
|
|
289
|
+
# 取消
|
|
290
|
+
curl -s -X POST http://127.0.0.1:<端口>/__dsh_autorestart/sleep \
|
|
291
|
+
-H 'content-type: application/json' -d '{"action":"cancel"}'
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
**关的是真宿主机。** 不是关子系统,是关**整台机器**:
|
|
295
|
+
|
|
296
|
+
| 宿主 | 执行的命令 |
|
|
297
|
+
| --- | --- |
|
|
298
|
+
| Windows | `shutdown.exe /s /f /t 0` |
|
|
299
|
+
| **WSL**(DSH 跑在发行版里) | `/mnt/c/Windows/System32/shutdown.exe /s /f /t 0` —— 借挂载的 `C:` 盘调 Windows 的程序,所以**关掉的是整台 Windows**;**绝不只关 WSL 子系统** |
|
|
300
|
+
| 真 Linux 主机 | `systemctl poweroff`(兜底 `shutdown -h now`) |
|
|
301
|
+
| macOS | `osascript -e 'tell application "System Events" to shut down'` |
|
|
302
|
+
| 其它平台 | **一条命令都不执行**:按钮不显示,路由报 `phase: "unsupported"` |
|
|
303
|
+
|
|
304
|
+
WSL 的判定依据:`WSL_DISTRO_NAME`、`WSL_INTEROP`、`/proc/version` 里出现 microsoft,或挂载的 Windows 程序存在。倒数是插件自己做的,所以操作系统被要求**立即**关机(`/t 0`),不会再倒数第二遍。
|
|
305
|
+
|
|
306
|
+
**三个配置项。**
|
|
307
|
+
|
|
308
|
+
| 键 | 默认 | 说明 |
|
|
309
|
+
| --- | --- | --- |
|
|
310
|
+
| `showSleepButton` | `true` | 左侧栏最底部(设置按钮旁边)的月亮按钮是否显示 |
|
|
311
|
+
| `sleepCountdownSeconds` | `20` | 真正关机前倒数几秒——**下拉框:0 / 10 / 20 / 30 / 60 秒**,默认 20;`0` = 不倒数、立即关机(仍会先等会话收工) |
|
|
312
|
+
| `sleepCommand` | `''` | 自定义关机命令;留空 = 自动识别宿主,填了就用这条命令代替(Windows 走 `cmd.exe /c …`,其它平台走 `/bin/sh -c …`) |
|
|
313
|
+
|
|
314
|
+
**设置分区(设置 → 自动重启与关机)里只有三样东西。** 这个分区是设置页侧栏里的**独立一栏**(排在「Agent预设」下面),点进去直接就是这三个控件——不用先去插件详情页,也不用再展开任何一层。
|
|
315
|
+
|
|
316
|
+
| 控件 | 配置键 | 说明 |
|
|
317
|
+
| --- | --- | --- |
|
|
318
|
+
| 倒计时秒数(下拉) | `sleepCountdownSeconds` | 0 / 10 / 20 / 30 / 60 秒;`0` = 不倒数、立即关机 |
|
|
319
|
+
| 重启前备份(开关) | `backupOnRestart` | 关掉就完全不做重启前快照 |
|
|
320
|
+
| 备份包含已安装插件(开关) | `backupIncludePlugins` | 关掉后只备份 DSH 自身状态,不含 profile 里的插件目录;**「重启前备份」关掉时它置灰** |
|
|
321
|
+
|
|
322
|
+
改完立刻写进 profile 配置(走插件自己的 choice 路由,用官方 `configEditor` Service),并且**从宿主回读**,所以分区里永远不会出现宿主没接受的值。**非法值一律 400、一个字节都不写。**
|
|
323
|
+
|
|
324
|
+
**首次安装会问一次「按钮要不要留着」。** 答完写进 profile 配置的 `showSleepButton` 键(用官方 `configEditor` Service,和备份范围那次问询同一条路),所以设置分区和问询看到的永远是同一个值。
|
|
325
|
+
|
|
326
|
+
**想空跑、不真关机。** 给宿主进程的环境变量设 `DSH_AUTORESTART_SLEEP_DRYRUN=1`:插件只报告"本来会执行什么命令",**一条都不执行**。
|
|
327
|
+
|
|
328
|
+
**给代码看的状态字段。** `GET <healthPath 前缀>/sleep`(默认 `/__dsh_autorestart/sleep`)会回答阶段、倒数和"要关的是哪台机器"。其中几个字段是给**程序**看的、不是给人看的:`host.kind` 是机器类别(`windows` / `wsl` / `linux` / `macos` / `unsupported`;如果平台没认出来、但你配了 `sleepCommand`,则是 `custom`);`host.suspect` 为 `true` 表示这台机器**只凭挂着 Windows 盘**才被认成 WSL(是佐证,不是铁证);`lastRun.dryRun` 表示上一次是**试运行**,什么都没真关;`backupOnRestart` / `backupIncludePlugins` 报告重启前备份的当前范围。
|
|
329
|
+
|
|
330
|
+
**关机命令起不来时的退路。** 宿主会等命令的**真实结果**(进程有没有真的起来)。命令起不来(找不到程序、权限不够)时,关机**不会假装成功**:状态回到 `idle`,并给出原因("关机命令没跑起来(…),电脑还开着,可以再点一次月亮按钮。"),电脑保持开机。界面上那个「正在关机」的卡片也**不会把界面锁死**:最多停留 8 秒就会给出一个"关掉这个提示"的按钮。
|
|
331
|
+
|
|
193
332
|
## 需要 goal 吗?
|
|
194
333
|
|
|
195
334
|
不需要。
|
|
@@ -316,7 +455,7 @@ dsh plugin --profile desktop add C:\path\to\dsh-autorestart
|
|
|
316
455
|
```powershell
|
|
317
456
|
curl http://127.0.0.1:<当前 GUI 端口>/__dsh_autorestart/health
|
|
318
457
|
# {"ok":true,"plugin":"dsh-autorestart","bootId":"…","pid":17884,"phase":"running",
|
|
319
|
-
# "pageHeartbeat":true,"systemPrompt":true,"configSchema":true,"indexTaps":
|
|
458
|
+
# "pageHeartbeat":true,"systemPrompt":true,"configSchema":true,"indexTaps":1,…}
|
|
320
459
|
|
|
321
460
|
# 自检:把页面心跳注入逻辑作用到一段探针 HTML 上(不需要鉴权页面)
|
|
322
461
|
curl "http://127.0.0.1:<端口>/__dsh_autorestart/health?probeIndex=1"
|
|
@@ -351,12 +490,12 @@ curl -X POST http://127.0.0.1:<端口>/__dsh_autorestart/recover \
|
|
|
351
490
|
`<dsh 安装目录>/app.asar/dsh/node_modules/@deepseek-ai/schemastery/lib/index.mjs` 加载。健康路由的
|
|
352
491
|
`schemastery` 字段只报告粗粒度来源 `bare` / `asar-fallback` / `unresolved`;schema 缺失时降级为"配置照常可用、
|
|
353
492
|
只是没有可视化 schema",并在日志里给出警告,**不会**因此让插件激活失败。)
|
|
354
|
-
- 测试:**
|
|
355
|
-
`continuum_recover_now` / `/recover`
|
|
493
|
+
- 测试:**250 个插件用例全绿**(`plugins/dsh-autorestart/test/*.test.mjs`,其中 goal 解除 / 自述式恢复 /
|
|
494
|
+
`continuum_recover_now` / `/recover` 路由 / 收工关机(宿主侧状态机 + 前端纯函数)是新增的用例)。运行:
|
|
356
495
|
`<bundled node> --test "plugins/dsh-autorestart/test/*.test.mjs"`
|
|
357
496
|
(Node 24 把位置参数当 glob,务必加引号)。
|
|
358
497
|
注意:仓库外的 `verification/adversarial.test.mjs` 里仍是"只注册一条/两条路由"的旧断言(插件现在注册
|
|
359
|
-
健康 + park + recover
|
|
498
|
+
健康 + park + recover + handoff + choice + sleep 六条),该文件不在本插件改动范围内。
|
|
360
499
|
- 独立验证员复验:`verification/verify-report.md`。
|
|
361
500
|
- WMI 脱离进程树已实测(`Invoke-CimMethod Win32_Process Create` 的子进程 parent 是 `WmiPrvSE.exe`)。
|
|
362
501
|
- 健康路由在"开机时激活"的情况下会短暂晚于 webserver 就绪,插件会重试注册(≤30 次 × 2s)。
|
|
@@ -372,9 +511,10 @@ curl -X POST http://127.0.0.1:<端口>/__dsh_autorestart/recover \
|
|
|
372
511
|
|
|
373
512
|
```
|
|
374
513
|
index.js 插件入口:apply / 事件跟踪 / 工具 / 健康路由 / 启动恢复
|
|
375
|
-
lib/checkpoint.js 原子 JSON 检查点,容错读取,boot/restart/session/解除记录(含旧 parked
|
|
514
|
+
lib/checkpoint.js 原子 JSON 检查点,容错读取,boot/restart/session/解除记录(含旧 parked 记录)与"已看过的新功能公告"变更函数
|
|
376
515
|
lib/lifecycle.js 纯决策:bootPlan / restartDecision / selectGoalDisarms / continuationPrompt / describeState
|
|
377
516
|
lib/restart.js Windows 脱离重启(WMI 主、schtasks 兜底、dryRun、自定义命令)
|
|
517
|
+
lib/sleep.js 收工关机:识别真宿主机(含 WSL)、关机命令、等待→倒数→关机的状态机
|
|
378
518
|
locale/{en,zh}.json Plugin Manager 显示用标题/描述
|
|
379
519
|
icon.svg 插件图标
|
|
380
520
|
LICENSE MIT 许可(发布到插件市场需要)
|
|
@@ -431,8 +571,9 @@ or by requesting the health path with ?gate=1.
|
|
|
431
571
|
|
|
432
572
|
- 胶囊渲染在**输入框工具行左侧**(`conversation.input.left` 槽位),是一个高度约 22px 的小胶囊,**不额外占一行**(不再是输入框上方的一整行)。
|
|
433
573
|
- 只有当健康路由 `/__dsh_autorestart/health?state=1` 返回 `pendingRestart: true` **且**本会话 id 出现在 `waiters` 列表里时才会出现;别的会话等待重启时你看不到它。
|
|
434
|
-
-
|
|
435
|
-
- 悬停提示(原生 `title
|
|
574
|
+
- 文案有多种状态:还有别的**任务**占着时显示「待重启 · 等待 N 个任务结束」;都空闲时显示「待重启 · 空闲后自动重启」;被某个**自动续跑的 goal** 占着时显示「待重启 · 被某个自动续跑的 goal 占着」(见下面的升级 3)。
|
|
575
|
+
- 悬停提示(原生 `title`):「等待其他任务结束(计数含"排队但还没开始"的任务)后自动重启;重启后会自动继续本会话进度」。
|
|
576
|
+
- **每个占用者都说清自己是什么**:普通会话用它自己的标题;**子智能体**用"创建它的那个会话"的标题来称呼(显示成「子智能体(那个会话)」)。**原始的 `session-xxxx` 永远不会推给界面** —— 取不到标题时用一个通用说法。
|
|
436
577
|
- 颜色走**品牌色(蓝紫)** `var(--dsw-alias-brand-primary, #5b8def)`,**不再用告警黄(warning)色**:胶囊里的小圆点和侧边栏圆点都是品牌色,胶囊边框保持中性(`--dsw-alias-border-l1`),文字保持中性。
|
|
437
578
|
- 左侧边栏会话行的**行首 16px 状态格**里也会出现一个同款 8px 圆点(`sidebar.session.row.leading` 槽位,id `autorestart-restart-dot`,order 20):纯图标,完整说明放在悬停 `title` 里,aria-label 为「待重启」/「Pending restart」。
|
|
438
579
|
- 那个 16px 状态格会被**优先级更高的行状态**(ask / 未读 / 运行中)替换,所以圆点只在该行**空闲**时出现。
|
|
@@ -484,8 +625,12 @@ or by requesting the health path with ?gate=1.
|
|
|
484
625
|
}
|
|
485
626
|
```
|
|
486
627
|
|
|
487
|
-
- `blockedBy[]` 的每一项在原有字段(`sessionId`、`reason
|
|
488
|
-
-
|
|
628
|
+
- `blockedBy[]` 的每一项在原有字段(`sessionId`、`reason`)之外,还带两个字段:
|
|
629
|
+
- `kind`:`"session"` 或 `"subagent"` —— 这个占用者是普通会话还是**子智能体**(子智能体用 `agent.origin === 'subagent'` 判定;分叉会话的 `origin` 为空,仍然是普通会话)。
|
|
630
|
+
- `label`:**它该被怎么称呼** —— 普通会话用自己的标题;子智能体用「创建它的那个会话」的标题(所以界面显示成「子智能体(那个会话)」)。取不到就是 `null`,界面改用通用说法。
|
|
631
|
+
- 另外还有 `name`(完整的称呼,如「写代码的会话的子智能体」)、`parentTitle` / `parentSessionId`(子智能体的父会话)、`title`(`label` 的旧名字,保留给老客户端)。
|
|
632
|
+
- 这些字段是**尽力而为**:会话控制器缺失、抛错或没有标题都**不会**让这条路由失败,只是 `label` 为 `null`;**原始的 `session-xxxx` 永远不会被当成名字推给界面**。
|
|
633
|
+
- 标题查询结果会缓存约 **5 秒**(客户端每 2 秒轮询一次,不会把会话列表打成风暴)。
|
|
489
634
|
- 标题查询结果会缓存约 **5 秒**(客户端每 2 秒轮询一次,不会把会话列表打成风暴)。
|
|
490
635
|
- `blockedBy[]` 里的 `reason` 可能是 **goal 相关**的:`goal-active`(持有一个会自动续跑的已武装 goal)、`goal-exhausted`(goal 的轮次额度用完,无法继续)等;此时原本的占用原因保留在 `busyReason` 里。
|
|
491
636
|
- `parkUrl` 是停泊路由的绝对路径(由配置的 `healthPath` 前缀推导),客户端优先用它,而不是自己猜路径。
|
|
@@ -517,7 +662,7 @@ or by requesting the health path with ?gate=1.
|
|
|
517
662
|
|
|
518
663
|
同一个 profile 行也是首次安装那次提问的落点:**托管 profile 如果自己没有 `dsh-autorestart` 行**,
|
|
519
664
|
`configEditor` 就没有行可写,答案会退到 `<DSH_HOME>/storages/dsh-autorestart/install-choice.json`
|
|
520
|
-
而不是 profile 配置;声明这一行才能让 profile
|
|
665
|
+
而不是 profile 配置;声明这一行才能让 profile(以及设置分区)保持唯一真相。
|
|
521
666
|
|
|
522
667
|
- **浏览器侧的「待重启」小胶囊**依赖 `GET <healthPath>?state=1` 这条状态路由。这条路由现在接受
|
|
523
668
|
**loopback 调用者**,*或* **宿主 connection 服务判定为「已认证浏览器」的请求**
|