dsh-autorestart 0.6.0 → 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 +150 -34
- package/client.js +805 -133
- package/cordis.patch.yml +18 -8
- package/docs/README.zh.md +93 -21
- package/index.js +456 -74
- package/lib/activity.js +124 -0
- package/lib/lifecycle.js +17 -4
- package/lib/queued.js +146 -0
- package/lib/semver.js +49 -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
|
|
|
@@ -284,9 +374,25 @@ once, in the same composer dock, as a small card:
|
|
|
284
374
|
already shown (`seenFeatureVersions` in the checkpoint file). A fresh installation never sees the
|
|
285
375
|
card: the install question already explains the features, and answering it marks the announcement
|
|
286
376
|
as seen.
|
|
287
|
-
- **
|
|
288
|
-
|
|
289
|
-
|
|
377
|
+
- **"Seen" means "not older", not "equal".** The card counts as answered as soon as the recorded
|
|
378
|
+
version is **not older** than the current feature version — a record of `0.6.0` counts as seen
|
|
379
|
+
while the feature itself stands at `0.5.0`. Why not equality? Because the record does not say
|
|
380
|
+
*which* release the user answered on (until 0.6.0 the card carried the version of the package),
|
|
381
|
+
so an equality check would tell a user who already dismissed the card that they never saw it. A
|
|
382
|
+
record that is not a version at all counts as seen as well: a broken record must not make the
|
|
383
|
+
card come back for ever.
|
|
384
|
+
- **Once per feature version, for ever.** "Got it" records the version, the card disappears at once,
|
|
385
|
+
and it never comes back — not with the next restart of the host, and not with the next release of
|
|
386
|
+
the plugin (a *newer version of the feature* is announced again, of course).
|
|
387
|
+
- **The announcement follows the FEATURE, not the release.** The version it carries is a constant in
|
|
388
|
+
the source (`SLEEP_FEATURE_VERSION` in `index.js`), **not** the version of the package: a release
|
|
389
|
+
never brings the card back. Until 0.6.0 the card carried the package version, and every release
|
|
390
|
+
(0.5.0 → 0.6.0) brought "graceful shutdown" back to the users who had already answered it.
|
|
391
|
+
- **How to announce it again on purpose.** Raise `SLEEP_FEATURE_VERSION` to a version **newer** than
|
|
392
|
+
the one that was recorded last: the card is then shown exactly once more. Do that **only** when
|
|
393
|
+
the feature itself changed so much that the user has to hear about it again (the shutdown became a
|
|
394
|
+
completely different thing, the button moved somewhere else, …). A release, a bug fix or a small
|
|
395
|
+
addition must **not** touch it — that is exactly the bug this constant exists to prevent.
|
|
290
396
|
- **An unanswered card is not swallowed.** The card stays until it is answered, and it comes back with
|
|
291
397
|
the next start of DSH.
|
|
292
398
|
- **The countdown is read, not written.** The seconds in the card are the ones that are really in
|
|
@@ -294,7 +400,8 @@ once, in the same composer dock, as a small card:
|
|
|
294
400
|
("The button is currently switched off; you can turn it on in the settings").
|
|
295
401
|
- **Where the state lives.** `<DSH_HOME>/storages/dsh-autorestart/state.json`, next to the checkpoint
|
|
296
402
|
of the plugin. The host serves it on `GET /announcements` (`{ ok, pending: [{ feature, version }] }`)
|
|
297
|
-
and records it on `POST /announcements` (`{ feature }`).
|
|
403
|
+
and records it on `POST /announcements` (`{ feature }`). The `version` in that answer is the feature
|
|
404
|
+
version — the same number that `SLEEP_FEATURE_VERSION` holds.
|
|
298
405
|
|
|
299
406
|
## Graceful shutdown (收工关机)
|
|
300
407
|
|
|
@@ -330,7 +437,8 @@ record that cannot be read is dropped — asking once more is much better than a
|
|
|
330
437
|
itself off for ever because of a half-written file.
|
|
331
438
|
|
|
332
439
|
**For the troubleshooting.** `GET /sleep` reports `armedAt` (when the shutdown was armed) next to
|
|
333
|
-
`phase`, `blockers` and
|
|
440
|
+
`phase`, `blockers`, `countdownSeconds` (the countdown that is really running) and
|
|
441
|
+
`restartGraceMs` (the restart reserve).
|
|
334
442
|
|
|
335
443
|
### Troubleshooting: the machine did not switch off
|
|
336
444
|
|
|
@@ -361,11 +469,13 @@ waiting for something. Here is what it can be, and what to do about it:
|
|
|
361
469
|
`待执行的重启`). That is normal queueing, not a fault: the restart runs first, the sessions
|
|
362
470
|
come back with it, and the countdown continues afterwards. **What to do:** nothing — wait for
|
|
363
471
|
the restart. (You can also cancel the restart with `continuum_cancel_restart`.)
|
|
364
|
-
3. **The work just ended and the
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
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.)
|
|
369
479
|
4. **Somebody started working during the countdown.** The countdown goes back to "waiting" and
|
|
370
480
|
`remainingMs` returns to its full value. **What you see:** the message says that somebody
|
|
371
481
|
started working again, and `blockers[]` lists who. **What to do:** let them finish.
|
|
@@ -438,13 +548,19 @@ more whether the button should stay. The answer is written into the profile conf
|
|
|
438
548
|
settings section and the prompt always show the same value.
|
|
439
549
|
|
|
440
550
|
**The settings section.** *Settings → Auto-restart & shut down* is a section of its own in the
|
|
441
|
-
settings sidebar (right below the Agent presets). It
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
- **
|
|
446
|
-
|
|
447
|
-
|
|
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.
|
|
448
564
|
|
|
449
565
|
A change is written into the profile configuration at once (through the plugin's own choice route)
|
|
450
566
|
and is read back from the host, so the section never shows a value the host did not take. A malformed
|