dsh-autorestart 0.6.1 → 0.6.2
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 +129 -30
- package/client.js +805 -133
- package/cordis.patch.yml +18 -8
- package/docs/README.zh.md +87 -19
- package/index.js +381 -59
- package/lib/activity.js +124 -0
- package/lib/lifecycle.js +17 -4
- package/lib/queued.js +146 -0
- package/locale/en.json +10 -6
- package/locale/zh.json +10 -6
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -76,7 +76,7 @@ what it adds before you install it.
|
|
|
76
76
|
|
|
77
77
|
## Install
|
|
78
78
|
|
|
79
|
-
Current version: **0.6.
|
|
79
|
+
Current version: **0.6.2**.
|
|
80
80
|
|
|
81
81
|
From npm — the package is published under the name `dsh-autorestart`:
|
|
82
82
|
|
|
@@ -144,7 +144,9 @@ show up in the Plugin Manager and in the config editor.
|
|
|
144
144
|
| `restartCommand` | `''` | Custom restart command. Supports the `{pid}`, `{ppid}` and `{app}` placeholders. |
|
|
145
145
|
| `relaunchDelaySeconds` | `2` | How long the detached helper waits before it kills the app (`ping -n 3`, roughly 2 seconds). |
|
|
146
146
|
| `settleMs` | `2000` | Quiet window in milliseconds: the host must stay silent this long after the last conversation activity before a restart may start. |
|
|
147
|
-
| `restartGraceMs` | `
|
|
147
|
+
| `restartGraceMs` | `0` | The **restart reserve** in milliseconds: how long the host waits after the last conversation finished before it is really RESTARTED — a dropdown: `0` / `5` / `10` / `20` / `30` / `60` seconds. `0` means "no extra wait". A restart is a daily operation, so the default is none. The settings page calls it *Wait before the restart*. |
|
|
148
|
+
| `sleepCountdownSeconds` | `20` | How many seconds the graceful shutdown counts down before the machine goes down — a dropdown: `0` / `10` / `20` / `30` / `60` seconds. `0` means "switch off right away" (still waiting for the conversations to be idle). **This visible countdown IS the wait before the machine goes off** — the shutdown has no second, invisible reserve. The settings page calls it *Shutdown countdown seconds*. |
|
|
149
|
+
| `queuedGraceMs` | `60000` | Upper bound in milliseconds for a **queued** (unclaimed) inbox message: how long it may hold the restart back before it is treated as stuck. `0` means "no upper bound" (the behaviour before 0.6.2). A **running** conversation always blocks, under every setting. |
|
|
148
150
|
| `autoReloadPage` | `true` | `dsh web`: reload the page automatically when the host boot token changes. |
|
|
149
151
|
| `blockManualRestart` | `true` | Hard-block an agent that tries to kill or restart DSH with a shell command, and point it at `continuum_restart` instead. |
|
|
150
152
|
| `healthPath` | `/__dsh_autorestart/health` | Health check route. Returns the boot id, the pid, the phase and the self-check flags. |
|
|
@@ -154,7 +156,6 @@ show up in the Plugin Manager and in the config editor.
|
|
|
154
156
|
| `maxBackups` | `2` | How many snapshots are kept; older ones are deleted when a new one is written. |
|
|
155
157
|
| `rollbackTimeoutMs` | `90000` | How long the detached watcher waits for the relaunched host before it restores the snapshot and launches the host again. |
|
|
156
158
|
| `showSleepButton` | `true` | Whether the moon button of the graceful shutdown is shown at the foot of the left sidebar, next to Settings. |
|
|
157
|
-
| `sleepCountdownSeconds` | `20` | How many seconds the graceful shutdown counts down before the machine goes down — a dropdown: `0` / `10` / `20` / `30` / `60` seconds. `0` means "switch off right away" (still waiting for the conversations to be idle). |
|
|
158
159
|
| `sleepCommand` | `''` | A custom shutdown command for the graceful shutdown. Empty = the host is detected automatically; a value replaces the detected command (`cmd.exe /c …` on Windows, `/bin/sh -c …` everywhere else). |
|
|
159
160
|
|
|
160
161
|
The two waits are independent of each other, and both default to two seconds:
|
|
@@ -174,21 +175,110 @@ work other plugins queue with a timer of their own — a memory plugin that writ
|
|
|
174
175
|
seconds after the conversation ended, a summary, an MCP callback, a log flush. That work is
|
|
175
176
|
invisible here, so "every agent is idle" does not mean "nothing is happening any more".
|
|
176
177
|
|
|
177
|
-
**
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
178
|
+
**One reserve window, for the restart.** A restart has no countdown of its own: the host restarts
|
|
179
|
+
as soon as the conversations are done. `restartGraceMs` is the extra wait that leaves room for the
|
|
180
|
+
delayed work of the other plugins — and it is the **only** such window:
|
|
181
|
+
|
|
182
|
+
| Window | Governs | Default | Offered |
|
|
183
|
+
| --- | --- | --- | --- |
|
|
184
|
+
| `restartGraceMs` | the **restart** | `0` (no extra wait) | `0` / `5` / `10` / `20` / `30` / `60` seconds |
|
|
185
|
+
| `sleepCountdownSeconds` | the **shutdown** (visible!) | `20` (twenty seconds) | `0` / `10` / `20` / `30` / `60` seconds |
|
|
186
|
+
|
|
187
|
+
- **The restart gate waits `max(settleMs, restartGraceMs)`** of quiet. With the default (`0`) the
|
|
188
|
+
gate waits for the settle window alone — a restart happens as soon as the conversations are done.
|
|
189
|
+
- **The graceful shutdown has no invisible reserve.** Its **visible countdown** is the wait before
|
|
190
|
+
the machine goes off: the seconds are on the screen, the user can watch them and cancel them.
|
|
191
|
+
A second, hidden window next to it would only say the same thing twice — so there is none. The
|
|
192
|
+
shutdown starts counting down as soon as every conversation is idle.
|
|
185
193
|
- **New activity restarts the timer.** If a conversation starts working again while the host is
|
|
186
|
-
waiting, the
|
|
187
|
-
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
`
|
|
191
|
-
(
|
|
194
|
+
waiting, the reserve starts over — the wait always follows the last activity.
|
|
195
|
+
- **Where to change them.** Settings → Auto-restart & shut down, in two titled groups:
|
|
196
|
+
*Restart settings* (the wait before the restart, the two backup switches) and *Shutdown settings*
|
|
197
|
+
(the countdown of the shutdown). The status interfaces report the restart reserve:
|
|
198
|
+
`GET /health?state=1`, `GET /health?gate=1` and `continuum_status` carry `graceMs` (the restart
|
|
199
|
+
reserve) and `effectiveSettleMs` (the window the gate really uses); `GET /sleep` carries
|
|
200
|
+
`restartGraceMs` and `countdownSeconds` (the countdown that is really running). All of them carry
|
|
201
|
+
`quietForMs` (how long the host has actually been quiet).
|
|
202
|
+
|
|
203
|
+
### Why a queued message cannot block the restart for ever
|
|
204
|
+
|
|
205
|
+
**The deadlock.** A conversation whose inbox holds a message nobody claimed yet counts as "busy"
|
|
206
|
+
for the restart gate. That is right while the message is about to be picked up — the agent claims
|
|
207
|
+
it within a second or two — but it is a deadlock when nobody ever claims it. The real case was a
|
|
208
|
+
fresh, empty conversation in the UI with one unclaimed message in its inbox: the gate stayed
|
|
209
|
+
closed, the hard deadline only logged a warning, and the restart never happened.
|
|
210
|
+
|
|
211
|
+
**The upper bound.** `queuedGraceMs` (default **60000** ms) is how long an unclaimed message may
|
|
212
|
+
hold the restart back:
|
|
213
|
+
|
|
214
|
+
- **A message claimed inside the window still blocks.** The normal case is unchanged: the agent
|
|
215
|
+
picks the message up and the restart waits for it.
|
|
216
|
+
- **A message that is still unclaimed after the window is stuck.** It stops blocking the restart,
|
|
217
|
+
and the log names the session that holds the queue (`the queued message of session <id> has been
|
|
218
|
+
waiting 61s and nobody claimed it; it no longer blocks the restart`). The restart goes on.
|
|
219
|
+
- **A running conversation always blocks — under every setting.** The bound only ever judges
|
|
220
|
+
*queued* work; a live turn is never relaxed, and no configuration can make the plugin kill one.
|
|
221
|
+
- **The timer follows the work.** A session that starts working drops its queue timer, and a new
|
|
222
|
+
queue counts from zero again.
|
|
223
|
+
- **`0` means "as before".** With `queuedGraceMs: 0` an unclaimed message blocks the restart for
|
|
224
|
+
ever, exactly like it did before this bound existed.
|
|
225
|
+
- **Where to look:** `GET /health?state=1`, `GET /health?gate=1` and `continuum_status` all report
|
|
226
|
+
`queuedGraceMs`.
|
|
227
|
+
|
|
228
|
+
### What you see while the restart waits a long time
|
|
229
|
+
|
|
230
|
+
When the hard deadline (`deferHardMs`) is over and a conversation is still running, the plugin does
|
|
231
|
+
not pretend that everything is fine — and it does not give up either:
|
|
232
|
+
|
|
233
|
+
- **The request is kept.** The pending restart stays pending, the requester stays checkpointed, and
|
|
234
|
+
the restart fires as soon as the gate is quiet again. Nothing is cancelled behind your back.
|
|
235
|
+
- **The wait becomes visible.** The log says it once a minute — "the restart has been waiting for
|
|
236
|
+
17m; `<session>` still holds it — cancel the restart or wait for that work to finish" — and
|
|
237
|
+
`GET /health?state=1` carries a `longWait` object (`since`, `reason`, `blockedBy`).
|
|
238
|
+
- **The bubble says it too.** The pending-restart pill shows one more line: "waiting for 17m:
|
|
239
|
+
`<session>` keeps holding it" (「已等 17 分:`<会话>`一直占着」).
|
|
240
|
+
|
|
241
|
+
A conversation is never killed to make the restart happen: `forceRestartWhenBusy` stays off by
|
|
242
|
+
default, and this plugin never turns it on.
|
|
243
|
+
|
|
244
|
+
### What the quiet window measures
|
|
245
|
+
|
|
246
|
+
The restart gate has two layers: **no busy conversation** and **a quiet window**
|
|
247
|
+
(`max(settleMs, restartGraceMs)`).
|
|
248
|
+
|
|
249
|
+
The quiet window answers one question: *how long ago did the last real work happen?* It is measured
|
|
250
|
+
from the LATER of two markers:
|
|
251
|
+
|
|
252
|
+
- **the last real activity signal** — a turn that started or ended (`agent/status`), a message that
|
|
253
|
+
was queued (`agent/inbox/inserted`), or a session event that says work happened (a message, a tool,
|
|
254
|
+
a turn);
|
|
255
|
+
- **the last moment a conversation was seen working** — a running turn or a queued message
|
|
256
|
+
(`busyAgents()`).
|
|
257
|
+
|
|
258
|
+
**A routine session event is not activity.** The host emits session events every two to three seconds
|
|
259
|
+
while the interface polls and synchronises (a title, a projection, a context request, a job that
|
|
260
|
+
settled). Counting those as activity reset the quiet window every couple of seconds, so a window
|
|
261
|
+
longer than the event interval could never fill: the restart never fired, however long the user
|
|
262
|
+
waited. That is why a grace window of ten seconds used to make the restart impossible.
|
|
263
|
+
|
|
264
|
+
The rule is one-sided: **an event whose type cannot be read is not activity.** When in doubt the
|
|
265
|
+
plugin restarts a little early rather than never.
|
|
266
|
+
|
|
267
|
+
**A running conversation always blocks.** The quiet window only decides *when* the restart may fire;
|
|
268
|
+
the busy check is the hard guarantee, and it is never relaxed by any setting.
|
|
269
|
+
|
|
270
|
+
**Where to look when the restart does not happen.** The status interfaces report the reason of the
|
|
271
|
+
gate next to the blockers (`GET /health?state=1` carries `reason`: `conversation-active` when a
|
|
272
|
+
conversation blocks, `settling` when only the quiet window is not full yet, `quiet` when it may go),
|
|
273
|
+
and the pending restart writes one diagnostic line into the log per minute:
|
|
274
|
+
|
|
275
|
+
```
|
|
276
|
+
restart gate: action=wait reason=agents-busy-within-soft-window busyCount=1 quietForMs=1432 effectiveSettleMs=10000
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
`action` is what the restart decision did (`wait`, `restart`, `long-wait`, …), `reason` why,
|
|
280
|
+
`busyCount` how many conversations block, `quietForMs` how long the host has been quiet, and
|
|
281
|
+
`effectiveSettleMs` the window it has to fill.
|
|
192
282
|
|
|
193
283
|
### Platform note
|
|
194
284
|
|
|
@@ -347,7 +437,8 @@ record that cannot be read is dropped — asking once more is much better than a
|
|
|
347
437
|
itself off for ever because of a half-written file.
|
|
348
438
|
|
|
349
439
|
**For the troubleshooting.** `GET /sleep` reports `armedAt` (when the shutdown was armed) next to
|
|
350
|
-
`phase`, `blockers` and
|
|
440
|
+
`phase`, `blockers`, `countdownSeconds` (the countdown that is really running) and
|
|
441
|
+
`restartGraceMs` (the restart reserve).
|
|
351
442
|
|
|
352
443
|
### Troubleshooting: the machine did not switch off
|
|
353
444
|
|
|
@@ -378,11 +469,13 @@ waiting for something. Here is what it can be, and what to do about it:
|
|
|
378
469
|
`待执行的重启`). That is normal queueing, not a fault: the restart runs first, the sessions
|
|
379
470
|
come back with it, and the countdown continues afterwards. **What to do:** nothing — wait for
|
|
380
471
|
the restart. (You can also cancel the restart with `continuum_cancel_restart`.)
|
|
381
|
-
3. **The work just ended and the
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
472
|
+
3. **The work just ended and the countdown has not started yet.** The shutdown has **no invisible
|
|
473
|
+
reserve**: as soon as every conversation is idle the countdown starts. **What you see:**
|
|
474
|
+
`phase` is `waiting` while `blockers[]` is empty and `remainingMs` is at its full value only
|
|
475
|
+
while the conversations are still busy — with an empty list the countdown is already running.
|
|
476
|
+
**What to do:** nothing; the countdown is on the screen and it can be cancelled there. (The
|
|
477
|
+
restart has its own window — `restartGraceMs`, default none — and the two never influence each
|
|
478
|
+
other.)
|
|
386
479
|
4. **Somebody started working during the countdown.** The countdown goes back to "waiting" and
|
|
387
480
|
`remainingMs` returns to its full value. **What you see:** the message says that somebody
|
|
388
481
|
started working again, and `blockers[]` lists who. **What to do:** let them finish.
|
|
@@ -455,13 +548,19 @@ more whether the button should stay. The answer is written into the profile conf
|
|
|
455
548
|
settings section and the prompt always show the same value.
|
|
456
549
|
|
|
457
550
|
**The settings section.** *Settings → Auto-restart & shut down* is a section of its own in the
|
|
458
|
-
settings sidebar (right below the Agent presets). It
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
- **
|
|
463
|
-
|
|
464
|
-
|
|
551
|
+
settings sidebar (right below the Agent presets). It is split into two titled groups — the restart
|
|
552
|
+
settings first, the shutdown settings after them:
|
|
553
|
+
|
|
554
|
+
- **Restart settings**
|
|
555
|
+
- **Wait before the restart** (`restartGraceMs`) — the dropdown `0` / `5` / `10` / `20` / `30` /
|
|
556
|
+
`60` seconds. `0` (the default) means "no extra wait": the host restarts as soon as the
|
|
557
|
+
conversations are done.
|
|
558
|
+
- **Backup before a restart** (`backupOnRestart`) — whether the pre-restart snapshot is taken at all.
|
|
559
|
+
- **Include the installed plugins in the backup** (`backupIncludePlugins`) — whether that snapshot
|
|
560
|
+
also contains the plugin directory of the profile. It is greyed out when the backup itself is off.
|
|
561
|
+
- **Shutdown settings**
|
|
562
|
+
- **Shutdown countdown seconds** (`sleepCountdownSeconds`) — the dropdown `0` / `10` / `20` / `30` /
|
|
563
|
+
`60`. `0` means "no countdown": the machine is switched off as soon as every conversation is idle.
|
|
465
564
|
|
|
466
565
|
A change is written into the profile configuration at once (through the plugin's own choice route)
|
|
467
566
|
and is read back from the host, so the section never shows a value the host did not take. A malformed
|