@zhengjunyao/dsh-restart 0.1.0 → 0.1.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 +35 -0
- package/README.md +7 -1
- package/README.zh.md +16 -3
- package/helper/restart-helper.mjs +141 -4
- package/lib/index.js +6 -0
- package/lib/types/config.d.ts +12 -0
- package/lib/types/restart.d.ts +2 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,41 @@
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
## [0.1.1] - 2026-09-12
|
|
8
|
+
|
|
9
|
+
就绪判定从「端口应答」改为「端口应答 + 进程存活 + 启动输出无 boot 致命行」。
|
|
10
|
+
|
|
11
|
+
### 修复 (Fixed)
|
|
12
|
+
|
|
13
|
+
- **就绪判定不再只看端口**:`dsh web` 先绑端口、后加载插件树,所以「端口应答」不等于
|
|
14
|
+
「宿主起来了」——2026-09-12 本机真实踩到:一次重启被报成 `ready after 4.0s`,新宿主其实
|
|
15
|
+
几秒后死于 `plugin tree failed to load … timed out waiting for the writer lock`,页面跳过去
|
|
16
|
+
才发现是死的。现在判定就绪需同时满足三条:端口应答、进程存活、启动输出里没有 boot 致命行
|
|
17
|
+
(`plugin tree failed to load`)。
|
|
18
|
+
- **就绪后继续守一段**:新增 `bootWatchMs`(默认 30s)——报了就绪之后助手继续观察新宿主,
|
|
19
|
+
把它「起来又死」判为启动失败,并撤回 `readyAt`(状态文件不再保留一个已经不存在的就绪声明);
|
|
20
|
+
凭证写锁超时这类慢失败(宿主自己的锁等待是 30s)现在是可见的失败,而不是静默的成功。
|
|
21
|
+
- **失败检测保持窄**:致命行只认 boot 阶段必死的输出,不用宽泛的报错正则——健康宿主启动时
|
|
22
|
+
本来就会打印 `session/list failed` 之类的报错形状噪音,不能因此把好启动判成失败。
|
|
23
|
+
- **observe 模式(launchd 托管)同规**:托管路径原先也是「端口一应答就报就绪」。现在同样要
|
|
24
|
+
先守住 `readyConfirmMs`,并跟随托管方的日志判断有没有 boot 致命行(宿主进程不在助手手里,
|
|
25
|
+
日志就是存活性信号);这里刻意不做长观察——把端口守住是托管方的职责,反过来给托管方
|
|
26
|
+
自己的重启潮记账只会产生假失败。
|
|
27
|
+
|
|
28
|
+
### 新增 (Added)
|
|
29
|
+
|
|
30
|
+
- 新配置项 `readyConfirmMs`(默认 `4000`)与 `bootWatchMs`(默认 `30000`),均为 0 时关闭对应窗口。
|
|
31
|
+
- `tests/helper.mjs` 新增三个用例:宿主应答端口后才在插件上崩掉、应答端口后静默退出、
|
|
32
|
+
健康宿主打印错误形状噪音(回归保护)。
|
|
33
|
+
- `tests/launchd.mjs` 新增一个用例:observe 模式下托管宿主的日志出现 boot 致命行时不得报就绪。
|
|
34
|
+
|
|
35
|
+
### 兼容性 (Compatibility)
|
|
36
|
+
|
|
37
|
+
- DSH:`>=0.1.5-rc.1`
|
|
38
|
+
- Node:`^22.19.0 || >=24.0.0`
|
|
39
|
+
- DSH peer:^0.1.0-rc.6 || ^0.1.1-rc.1 || ^0.1.2-alpha.1 || ^0.1.5-rc.1
|
|
40
|
+
- 无破坏性变更:新增配置项都有默认值,未改动路由、工具名与既有配置语义。
|
|
41
|
+
|
|
7
42
|
## [0.1.0] - 2026-09-12
|
|
8
43
|
|
|
9
44
|
首个版本。
|
package/README.md
CHANGED
|
@@ -37,6 +37,12 @@ left. `dsh-restart` handles all three:
|
|
|
37
37
|
- Failure handling: error lines are detected (Error / EADDRINUSE /
|
|
38
38
|
MODULE_NOT_FOUND / stack frames), the boot log is shown in the overlay, and
|
|
39
39
|
`maxAttempts` (default 2) automatic retries run before giving up.
|
|
40
|
+
- Readiness is not "the port answers": `dsh web` binds its port before the
|
|
41
|
+
plugin tree loads, so the helper only reports ready once the port answers,
|
|
42
|
+
the process is still alive, and the boot output carries no fatal line — held
|
|
43
|
+
over `readyConfirmMs` (4s) first, then watched for `bootWatchMs` (30s) so a
|
|
44
|
+
host that reports ready and dies seconds later is reclassified as a failed
|
|
45
|
+
restart instead of a silent success.
|
|
40
46
|
- Agent tools: `dsh_restart_status` (read-only) and `dsh_restart`, which demands
|
|
41
47
|
`confirm: true` because the local standing rule is that DSH is never restarted
|
|
42
48
|
without explicit user consent.
|
|
@@ -91,7 +97,7 @@ panel / dsh_restart
|
|
|
91
97
|
## Tests
|
|
92
98
|
|
|
93
99
|
```sh
|
|
94
|
-
pnpm test #
|
|
100
|
+
pnpm test # 168 assertions across five suites
|
|
95
101
|
```
|
|
96
102
|
|
|
97
103
|
`smoke` (config/history/log detection/host identity), `helper` (the real helper
|
package/README.zh.md
CHANGED
|
@@ -40,6 +40,14 @@
|
|
|
40
40
|
报错行、退出码,并可一键「重试启动」。
|
|
41
41
|
- **失败自动重试**:`maxAttempts`(默认 2)次内自动重试;都失败就停在那里等人工处理,
|
|
42
42
|
不会把端口/日志丢掉。
|
|
43
|
+
- **就绪不等于端口通了**:`dsh web` 会先绑端口、后加载插件树,所以「端口应答」远早于
|
|
44
|
+
「宿主真的起来了」。助手判定就绪要同时满足三条——端口应答、进程存活、启动输出里没有
|
|
45
|
+
boot 致命行——并先守住 `readyConfirmMs`(默认 4s);此后还会继续观察
|
|
46
|
+
`bootWatchMs`(默认 30s),把「已经报了就绪、几秒后却自己死掉」的启动(例如凭证写锁
|
|
47
|
+
等 30 秒才超时)重新判为失败,而不是留下一个假的成功。
|
|
48
|
+
**launchd 托管的 observe 模式同规**:先守 `readyConfirmMs`,并跟随托管方日志判断有没有
|
|
49
|
+
boot 致命行(宿主进程不在助手手里,日志就是存活性信号);托管路径刻意不做长观察——
|
|
50
|
+
守住端口是托管方的职责。
|
|
43
51
|
- **Agent 工具**:`dsh_restart_status`(只读:宿主 pid/端口/版本/启动时长/启动命令、
|
|
44
52
|
助手阶段与失败原因、最近重启记录、上次启动日志里的疑似报错行)、
|
|
45
53
|
`dsh_restart`(真正重启;**必须已获得用户同意**并传 `confirm: true`,
|
|
@@ -85,6 +93,8 @@ dsh plugin --profile web add github:zhengjy01/dsh-restart
|
|
|
85
93
|
| `killGraceMs` | `6000` | 端口迟迟不释放时,助手 SIGKILL 旧进程前的宽限 |
|
|
86
94
|
| `portFreeTimeoutMs` | `25000` | 等旧进程释放端口的上限 |
|
|
87
95
|
| `lingerMs` | `4000` | 就绪后助手退出前保留控制台的时间 |
|
|
96
|
+
| `readyConfirmMs` | `4000` | 端口应答后、判定「已就绪」前必须守住的稳定时长(0 = 不守) |
|
|
97
|
+
| `bootWatchMs` | `30000` | 已报就绪后继续观察新宿主、把「起来又死」改判为失败的时间窗(0 = 不守) |
|
|
88
98
|
| `logLines` | `200` | 面板/接口返回的日志行数 |
|
|
89
99
|
| `autoReload` | `true` | 新宿主应答后自动刷新页面 |
|
|
90
100
|
| `showOverlay` | `true` | 重启时显示全屏遮罩 |
|
|
@@ -136,18 +146,21 @@ dsh plugin --profile web add github:zhengjy01/dsh-restart
|
|
|
136
146
|
## 测试
|
|
137
147
|
|
|
138
148
|
```sh
|
|
139
|
-
pnpm test #
|
|
149
|
+
pnpm test # 168 项断言,五个套件
|
|
140
150
|
```
|
|
141
151
|
|
|
142
152
|
- `tests/smoke.mjs` — 配置读写与钳制、历史、日志尾部与报错识别、启动签名、宿主信息;
|
|
143
153
|
- `tests/helper.mjs` — **真的**跑助手:崩溃路径(捕获退出码、stderr 报错行、控制台页面、
|
|
144
|
-
|
|
154
|
+
手动重试)、成功路径(等端口 → 拉起 → 就绪计时),以及就绪判定的三种边界:
|
|
155
|
+
宿主应答端口后才在插件上崩掉(不得报就绪,且要撤回已报的就绪状态、写入失败报告)、
|
|
156
|
+
应答端口后静默退出(没有任何报错行也要靠存活性判失败)、健康宿主打印错误形状的噪音
|
|
157
|
+
(不得误判为失败);
|
|
145
158
|
- `tests/routes.mjs` — 合成的 req/res 打全部路由,含 loopback/跨站/方法守卫;
|
|
146
159
|
- `tests/handoff.mjs` — 端到端:假宿主进程 → POST 重启 → 旧进程真的退出 →
|
|
147
160
|
助手用相同命令拉起第二代 → 端口重新应答(新 pid)、`restarted: true`、历史落盘;
|
|
148
161
|
- `tests/launchd.mjs` — launchd 识别(**按 pid 匹配 `launchctl list`**,因为 Node 会把
|
|
149
162
|
`XPC_SERVICE_NAME` 改写成 `0`)+ observe 模式(助手执行 kickCommand、跟随托管方日志、
|
|
150
|
-
**绝不自己 spawn
|
|
163
|
+
**绝不自己 spawn**),以及托管宿主日志出现 boot 致命行时不得报就绪。
|
|
151
164
|
|
|
152
165
|
## 边界
|
|
153
166
|
|
|
@@ -87,6 +87,22 @@ const PORT_FREE_TIMEOUT_MS = Number(spec.portFreeTimeoutMs) || 25_000
|
|
|
87
87
|
const MAX_ATTEMPTS = Math.max(1, Number(spec.maxAttempts) || 2)
|
|
88
88
|
const RING_LINES = Math.max(200, Number(spec.ringLines) || 600)
|
|
89
89
|
const LINGER_MS = Math.max(0, Number(spec.lingerMs) || 4_000)
|
|
90
|
+
/**
|
|
91
|
+
* How long a boot that answers the port must hold before readiness is
|
|
92
|
+
* reported. The port answering is not the same event as the host being up:
|
|
93
|
+
* `dsh web` binds it before the plugin tree loads, so this window is what
|
|
94
|
+
* separates "listening" from "survived the first seconds of its boot".
|
|
95
|
+
*/
|
|
96
|
+
const READY_CONFIRM_MS = Math.max(0, Number(spec.readyConfirmMs) || 4_000)
|
|
97
|
+
/**
|
|
98
|
+
* How long a host that already reported ready is still watched before the
|
|
99
|
+
* helper lets go of it. Boot failures that wait on something slow surface long
|
|
100
|
+
* after the port answers — a credentials writer-lock timeout is 30s — so
|
|
101
|
+
* without this window a restart can be reported ready seconds before the new
|
|
102
|
+
* host dies, which is exactly the silent-success case this helper exists to
|
|
103
|
+
* prevent.
|
|
104
|
+
*/
|
|
105
|
+
const BOOT_WATCH_MS = Math.max(0, Number(spec.bootWatchMs) || 30_000)
|
|
90
106
|
/**
|
|
91
107
|
* `spawn` (default): this helper relaunches the host itself.
|
|
92
108
|
* `observe`: something else owns the relaunch (a launchd job, a supervisor);
|
|
@@ -139,11 +155,24 @@ const ERROR_HINT = new RegExp(
|
|
|
139
155
|
].join('|'),
|
|
140
156
|
)
|
|
141
157
|
|
|
158
|
+
/**
|
|
159
|
+
* Boot-stage output no live host keeps running after.
|
|
160
|
+
*
|
|
161
|
+
* Deliberately narrow, and deliberately not {@link ERROR_HINT}: a healthy
|
|
162
|
+
* `dsh web` prints error-shaped lines all through its boot (`session/list
|
|
163
|
+
* failed`, a plugin warning, a retrying network call), and treating those as
|
|
164
|
+
* fatal would turn healthy restarts into failed ones. Only lines that mean the
|
|
165
|
+
* host is on its way out belong here.
|
|
166
|
+
*/
|
|
167
|
+
const FATAL_BOOT_HINT = /plugin tree failed to load/
|
|
168
|
+
|
|
142
169
|
/** Append one line to the log file and the in-memory ring. */
|
|
143
170
|
function record(line, stream = 'out') {
|
|
144
171
|
const text = String(line).replace(ANSI, '')
|
|
145
172
|
const at = Date.now()
|
|
146
|
-
|
|
173
|
+
// The attempt number rides along so a fatal line from a boot we already gave
|
|
174
|
+
// up on cannot condemn the attempt that is running now.
|
|
175
|
+
ring.push({ t: at, s: stream, text, attempt })
|
|
147
176
|
if (ring.length > RING_LINES) ring.splice(0, ring.length - RING_LINES)
|
|
148
177
|
// Only the host's own output can explain a failed boot; our 'sys' lines are
|
|
149
178
|
// narration and would otherwise trip the detector on their own wording.
|
|
@@ -168,6 +197,22 @@ function makeLineSplitter(stream) {
|
|
|
168
197
|
}
|
|
169
198
|
}
|
|
170
199
|
|
|
200
|
+
/**
|
|
201
|
+
* The newest boot-fatal line the host printed, or null.
|
|
202
|
+
*
|
|
203
|
+
* Read from the ring rather than from `errors`: that list carries everything
|
|
204
|
+
* that looks like an error to a human, including lines a healthy host prints
|
|
205
|
+
* on the way up. Our own narration is skipped — it says "failed" a lot.
|
|
206
|
+
*/
|
|
207
|
+
function fatalBootLine() {
|
|
208
|
+
for (let index = ring.length - 1; index >= 0; index -= 1) {
|
|
209
|
+
const line = ring[index]
|
|
210
|
+
if (line.attempt !== attempt) continue
|
|
211
|
+
if (line.s !== 'sys' && FATAL_BOOT_HINT.test(line.text)) return line.text
|
|
212
|
+
}
|
|
213
|
+
return null
|
|
214
|
+
}
|
|
215
|
+
|
|
171
216
|
/** Fallback port actually bound (null until the console is listening). */
|
|
172
217
|
let activeFallbackPort = null
|
|
173
218
|
|
|
@@ -324,6 +369,61 @@ async function waitPortReady(port, host, timeoutMs, child) {
|
|
|
324
369
|
}
|
|
325
370
|
}
|
|
326
371
|
|
|
372
|
+
/**
|
|
373
|
+
* Watch a boot that already answers the port.
|
|
374
|
+
*
|
|
375
|
+
* Three things end the watch early, and each of them means the new host is not
|
|
376
|
+
* the host the caller was promised: the process is gone, its output printed a
|
|
377
|
+
* line nothing survives, or the port stopped answering.
|
|
378
|
+
*
|
|
379
|
+
* @param child - the launched host, or null when we do not own it.
|
|
380
|
+
* @param windowMs - how long the boot has to hold.
|
|
381
|
+
* @returns the failure to report, or null when the host survived the window.
|
|
382
|
+
*/
|
|
383
|
+
async function watchBoot(child, windowMs) {
|
|
384
|
+
const deadline = Date.now() + windowMs
|
|
385
|
+
for (;;) {
|
|
386
|
+
const fatal = fatalBootLine()
|
|
387
|
+
if (fatal !== null) return { kind: 'boot', message: `启动输出报错:${fatal}` }
|
|
388
|
+
if (child !== null && child.exitCode !== null) {
|
|
389
|
+
const tail = errors.slice(-3).map((entry) => entry.text).join(' | ')
|
|
390
|
+
return {
|
|
391
|
+
kind: 'exit',
|
|
392
|
+
message: `新进程退出(code=${child.exitCode})${tail === '' ? '' : ':' + tail}`,
|
|
393
|
+
exitCode: child.exitCode,
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
if (!(await portOpen(PORT, HOST))) {
|
|
397
|
+
return { kind: 'port', message: `新进程不再占用 ${URL_BASE}` }
|
|
398
|
+
}
|
|
399
|
+
if (Date.now() >= deadline) return null
|
|
400
|
+
await sleep(300)
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
/**
|
|
405
|
+
* Decide whether a boot that answers the port is really up.
|
|
406
|
+
*
|
|
407
|
+
* Two windows, because a `dsh` boot fails at two speeds. The settle window
|
|
408
|
+
* catches the fast ones (a plugin that cannot be imported, a module that is not
|
|
409
|
+
* there) before readiness is ever claimed. The watch that follows catches the
|
|
410
|
+
* late ones — a boot blocked on a lock or a slow plugin load looks healthy for
|
|
411
|
+
* tens of seconds first — and clears `readyAt` again, so the status file never
|
|
412
|
+
* keeps claiming a host that has since died.
|
|
413
|
+
*
|
|
414
|
+
* @returns null when the boot is confirmed, or the failure to report.
|
|
415
|
+
*/
|
|
416
|
+
async function confirmBoot(child) {
|
|
417
|
+
const early = await watchBoot(child, READY_CONFIRM_MS)
|
|
418
|
+
if (early !== null) return early
|
|
419
|
+
readyAt = Date.now()
|
|
420
|
+
setPhase('ready', `ready after ${((readyAt - startedAt) / 1000).toFixed(1)}s`)
|
|
421
|
+
const late = await watchBoot(child, BOOT_WATCH_MS)
|
|
422
|
+
if (late === null) return null
|
|
423
|
+
readyAt = null
|
|
424
|
+
return late
|
|
425
|
+
}
|
|
426
|
+
|
|
327
427
|
// --------------------------------------------------------------- child spawn
|
|
328
428
|
|
|
329
429
|
/** Launch the new host, wiring both output streams into the log. */
|
|
@@ -667,6 +767,17 @@ async function observeBoot() {
|
|
|
667
767
|
for (;;) {
|
|
668
768
|
follow()
|
|
669
769
|
if (await portOpen(PORT, HOST)) {
|
|
770
|
+
// The owner's new host answers — but an answering port is not a host
|
|
771
|
+
// either here: the same "listening, then dead on a plugin" boot happens
|
|
772
|
+
// under launchd. Observe mode has no child handle, so the owner's log is
|
|
773
|
+
// the liveness signal.
|
|
774
|
+
const settled = await observeSettle(follow)
|
|
775
|
+
if (settled !== null) {
|
|
776
|
+
failure = settled
|
|
777
|
+
record(`[dsh-restart] attempt ${attempt} failed: ${failure.message}`, 'sys')
|
|
778
|
+
persist()
|
|
779
|
+
return 'failed'
|
|
780
|
+
}
|
|
670
781
|
readyAt = Date.now()
|
|
671
782
|
setPhase('ready', `ready after ${((readyAt - startedAt) / 1000).toFixed(1)}s`)
|
|
672
783
|
return 'ready'
|
|
@@ -684,6 +795,29 @@ async function observeBoot() {
|
|
|
684
795
|
}
|
|
685
796
|
}
|
|
686
797
|
|
|
798
|
+
/**
|
|
799
|
+
* Hold an owner-managed boot through the settle window.
|
|
800
|
+
*
|
|
801
|
+
* No long watch here, deliberately: keeping the port up is the owner's job, and
|
|
802
|
+
* a supervisor that restarts a flapping host would otherwise leave one failure
|
|
803
|
+
* in the log for this helper to wrongly pin on the restart it was asked to
|
|
804
|
+
* watch.
|
|
805
|
+
*
|
|
806
|
+
* @param follow - the owner-log tailer, pumped each pass so late lines land.
|
|
807
|
+
* @returns the failure to report, or null when the boot held.
|
|
808
|
+
*/
|
|
809
|
+
async function observeSettle(follow) {
|
|
810
|
+
const deadline = Date.now() + READY_CONFIRM_MS
|
|
811
|
+
for (;;) {
|
|
812
|
+
follow()
|
|
813
|
+
const fatal = fatalBootLine()
|
|
814
|
+
if (fatal !== null) return { kind: 'boot', message: `启动输出报错:${fatal}` }
|
|
815
|
+
if (!(await portOpen(PORT, HOST))) return { kind: 'port', message: `新进程不再占用 ${URL_BASE}` }
|
|
816
|
+
if (Date.now() >= deadline) return null
|
|
817
|
+
await sleep(300)
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
|
|
687
821
|
/** One launch attempt; resolves 'ready' | 'failed'. */
|
|
688
822
|
async function attemptBoot() {
|
|
689
823
|
if (MODE === 'observe') return observeBoot()
|
|
@@ -695,9 +829,12 @@ async function attemptBoot() {
|
|
|
695
829
|
}
|
|
696
830
|
setPhase('waiting-ready', `waiting for ${URL_BASE} to answer (up to ${Math.round(BOOT_TIMEOUT_MS / 1000)}s)`)
|
|
697
831
|
if (await waitPortReady(PORT, HOST, BOOT_TIMEOUT_MS, child)) {
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
832
|
+
const late = await confirmBoot(child)
|
|
833
|
+
if (late === null) return 'ready'
|
|
834
|
+
failure = late
|
|
835
|
+
record(`[dsh-restart] attempt ${attempt} failed: ${failure.message}`, 'sys')
|
|
836
|
+
persist()
|
|
837
|
+
return 'failed'
|
|
701
838
|
}
|
|
702
839
|
const code = child.exitCode
|
|
703
840
|
const tail = errors.slice(-3).map((entry) => entry.text).join(' | ')
|
package/lib/index.js
CHANGED
|
@@ -31,6 +31,8 @@ const DEFAULT_CONFIG = {
|
|
|
31
31
|
killGraceMs: 6e3,
|
|
32
32
|
portFreeTimeoutMs: 25e3,
|
|
33
33
|
lingerMs: 4e3,
|
|
34
|
+
readyConfirmMs: 4e3,
|
|
35
|
+
bootWatchMs: 3e4,
|
|
34
36
|
logLines: 200,
|
|
35
37
|
autoReload: true,
|
|
36
38
|
showOverlay: true,
|
|
@@ -102,6 +104,8 @@ function normalizeConfig(patch, base = DEFAULT_CONFIG) {
|
|
|
102
104
|
killGraceMs: intIn(source.killGraceMs ?? base.killGraceMs, base.killGraceMs, 0, 12e4),
|
|
103
105
|
portFreeTimeoutMs: intIn(source.portFreeTimeoutMs ?? base.portFreeTimeoutMs, base.portFreeTimeoutMs, 0, 3e5),
|
|
104
106
|
lingerMs: intIn(source.lingerMs ?? base.lingerMs, base.lingerMs, 0, 6e5),
|
|
107
|
+
readyConfirmMs: intIn(source.readyConfirmMs ?? base.readyConfirmMs, base.readyConfirmMs, 0, 12e4),
|
|
108
|
+
bootWatchMs: intIn(source.bootWatchMs ?? base.bootWatchMs, base.bootWatchMs, 0, 6e5),
|
|
105
109
|
logLines: intIn(source.logLines ?? base.logLines, base.logLines, 20, 2e3),
|
|
106
110
|
autoReload: typeof source.autoReload === "boolean" ? source.autoReload : base.autoReload,
|
|
107
111
|
showOverlay: typeof source.showOverlay === "boolean" ? source.showOverlay : base.showOverlay,
|
|
@@ -572,6 +576,8 @@ async function buildSpec(options) {
|
|
|
572
576
|
killGraceMs: options.config.killGraceMs,
|
|
573
577
|
portFreeTimeoutMs: options.config.portFreeTimeoutMs,
|
|
574
578
|
lingerMs: options.config.lingerMs,
|
|
579
|
+
readyConfirmMs: options.config.readyConfirmMs,
|
|
580
|
+
bootWatchMs: options.config.bootWatchMs,
|
|
575
581
|
ringLines: options.config.logLines,
|
|
576
582
|
dshVersion: await readDshVersion(),
|
|
577
583
|
profile: readProfile(),
|
package/lib/types/config.d.ts
CHANGED
|
@@ -36,6 +36,18 @@ export interface RestartConfig {
|
|
|
36
36
|
portFreeTimeoutMs: number;
|
|
37
37
|
/** How long the helper keeps its console up after success before exiting. */
|
|
38
38
|
lingerMs: number;
|
|
39
|
+
/**
|
|
40
|
+
* How long a boot that answers the port must hold before the helper reports
|
|
41
|
+
* it ready. `dsh web` binds its port before the plugin tree loads, so this is
|
|
42
|
+
* what separates "listening" from "survived the start of its boot".
|
|
43
|
+
*/
|
|
44
|
+
readyConfirmMs: number;
|
|
45
|
+
/**
|
|
46
|
+
* How long the helper keeps watching a host that already reported ready.
|
|
47
|
+
* Boot failures that wait on something slow (a credentials writer-lock
|
|
48
|
+
* timeout is 30s) surface well after the port answers.
|
|
49
|
+
*/
|
|
50
|
+
bootWatchMs: number;
|
|
39
51
|
/** Lines of boot log kept for display. */
|
|
40
52
|
logLines: number;
|
|
41
53
|
/** Auto-reload the page once the new host answers. */
|
package/lib/types/restart.d.ts
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zhengjunyao/dsh-restart",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "One-click restart for DeepSeek Harness: a web button (plus a dsh_restart agent tool) hands the relaunch to a detached helper that waits for the port to free, relaunches the exact same command, streams the new host's output, auto-reconnects the page — and shows the boot errors in a recovery console when the new host dies",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|