@moonquake2004/dsh-doctor 0.4.29 → 0.4.31
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/dsh-doctor.mjs +56 -1
- package/package.json +1 -1
package/dsh-doctor.mjs
CHANGED
|
@@ -310,6 +310,36 @@ function checkEnv() {
|
|
|
310
310
|
supported ? undefined : `升级 node 到 ${rangeSrc.range}(${rangeSrc.from},见 #2259)`);
|
|
311
311
|
}
|
|
312
312
|
|
|
313
|
+
// E13:CLI 是否真的会执行(#6341 的静默失效签名)
|
|
314
|
+
// `lib/bin.js` 末尾是 `if (import.meta.main) await runCli();`,而 `import.meta.main` 只在
|
|
315
|
+
// Node **≥22.18.0 / ≥24.2.0** 存在。落在窗口外的 Node 上,**任何** dsh 命令都零输出、退出码 0。
|
|
316
|
+
// 判据(#6341 的关键观察):连 `--version` 都静默——缺原生模块、端口占用、patch 问题都会打印东西,
|
|
317
|
+
// 只有"入口根本没执行"才会连版本号都不输出。故这里直接问一次版本号。
|
|
318
|
+
{
|
|
319
|
+
const whichCmd = process.platform === 'win32' ? 'where' : 'which';
|
|
320
|
+
const found = (() => { const r = spawnSync(whichCmd, ['dsh'], { encoding: 'utf8' }); return r.status === 0 ? String(r.stdout).split(/\r?\n/)[0].trim() : null; })();
|
|
321
|
+
if (!found) {
|
|
322
|
+
reportSkip('env', 'E13', 'PATH 中未找到 dsh,跳过 CLI 可执行性探测');
|
|
323
|
+
} else {
|
|
324
|
+
const r = spawnSync(process.platform === 'win32' ? 'dsh.cmd' : 'dsh', ['--version'], { encoding: 'utf8', timeout: 15000, shell: process.platform === 'win32' });
|
|
325
|
+
const out = String(r.stdout || '').trim();
|
|
326
|
+
const ver = /\d+\.\d+\.\d+[-\w.]*/.exec(out);
|
|
327
|
+
if (r.status === 0 && out === '') {
|
|
328
|
+
report('env', 'E13', false,
|
|
329
|
+
`dsh 在 PATH 中(${found})但 \`dsh --version\` **零输出且退出码 0** —— 这是 #6341 的静默失效签名:`
|
|
330
|
+
+ `CLI 入口被 \`if (import.meta.main) await runCli();\` 门控,而该 API 仅在 Node **≥22.18.0 / ≥24.2.0** 存在;`
|
|
331
|
+
+ `(当前 Node ${process.version};若它落在窗口外,这就是原因——本机 Node 24.20 上该 API 存在、命令正常)`,
|
|
332
|
+
'换用 Node ≥22.18.0 或 ≥24.2.0(注意:仓库声明范围 >=24.0.0 **包含** 24.0/24.1 这两个不可用版本)');
|
|
333
|
+
} else if (r.status !== 0) {
|
|
334
|
+
report('env', 'E13', false,
|
|
335
|
+
`dsh 在 PATH 中但 \`--version\` 以退出码 ${r.status} 失败:${String((r.stderr || '') + out).slice(0, 120)}`,
|
|
336
|
+
'按上面的报错定位(依赖缺失 / 入口异常)');
|
|
337
|
+
} else {
|
|
338
|
+
report('env', 'E13', true, `CLI 可执行(dsh --version → ${ver ? ver[0] : out.slice(0, 24)})`);
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
313
343
|
// E12:运行时 zstd 稳定性(#6651 的运行时线索)
|
|
314
344
|
// 锚点已核实:dsh-session-persistence-jsonl:15 直接 `import { zstdCompress, zstdDecompress,
|
|
315
345
|
// zstdDecompressSync } from "node:zlib"`(用于 :1263 / :1287 / :1368)——会话日志的读写**依赖 Node 内置 zstd**。
|
|
@@ -476,7 +506,32 @@ function checkPort3080() {
|
|
|
476
506
|
srv.on('error', finish((e) => {
|
|
477
507
|
if (e.code === 'EADDRINUSE') {
|
|
478
508
|
const info = portOccupierInfo(port);
|
|
479
|
-
|
|
509
|
+
// 端口被占用 **不等于** 服务健康(#6693):一个在 apply() 里抛错的插件会让 cordis 中断装载链、
|
|
510
|
+
// 进程退出、systemd 重启 —— 表现为"端口绑着、systemd active、页面永远打不开"。故这里真发一次 HTTP:
|
|
511
|
+
// 应答 → 正常;无应答 → 如实报"绑着但不服务",这正是崩溃重启循环的样子。
|
|
512
|
+
const httpAlive = (() => {
|
|
513
|
+
for (const bin of ['curl', 'wget']) {
|
|
514
|
+
try {
|
|
515
|
+
const args = bin === 'curl'
|
|
516
|
+
? ['-s', '-o', '/dev/null', '-w', '%{http_code}', '--max-time', '5', `http://127.0.0.1:${port}/`]
|
|
517
|
+
: ['-q', '-O', '-T', '2', `http://127.0.0.1:${port}/`];
|
|
518
|
+
const rr = spawnSync(bin, args, { encoding: 'utf8', timeout: 8000 });
|
|
519
|
+
const code = String(rr.stdout || '').trim();
|
|
520
|
+
if (bin === 'wget') return rr.status === 0;
|
|
521
|
+
return /^\d{3}$/.test(code) && code !== '000';
|
|
522
|
+
} catch { /* 换下一个工具 */ }
|
|
523
|
+
}
|
|
524
|
+
return null; // 无工具可探测 → 不据此下结论
|
|
525
|
+
})();
|
|
526
|
+
if (info && info.dsh && httpAlive === false) {
|
|
527
|
+
report('env', 'E10-port-3080', false,
|
|
528
|
+
`端口 ${port} 被 dsh 进程占用(PID ${info.pid})但 **HTTP 无应答** —— 绑定成功 ≠ 服务健康:`
|
|
529
|
+
+ `这与"插件在 apply() 期间抛错 → 装载链中断 → 进程退出 → systemd 重启"的崩溃重启循环一致(#6693)`,
|
|
530
|
+
`先在会话外跑 npx @moonquake2004/dsh-doctor --boot-check 找失败的 entry;确认后 --quarantine <包名> 让 dsh 先起来`);
|
|
531
|
+
} else if (info && info.dsh) {
|
|
532
|
+
report('env', 'E10-port-3080', true,
|
|
533
|
+
`端口 ${port} 被 dsh web 实例占用(PID ${info.pid})${httpAlive === true ? '且 HTTP 有应答' : ''}——宿主自身或另一实例,正常`, undefined);
|
|
534
|
+
}
|
|
480
535
|
else if (info) report('env', 'E10-port-3080', false, `端口 ${port} 被其他程序占用(PID ${info.pid}: ${info.cmd}),dsh web 启动会 address in use(#1719)`, `关掉占用进程,或让 dsh web 用别的端口`);
|
|
481
536
|
else report('env', 'E10-port-3080', true, `⚠ 端口 ${port} 被占用但无法识别占用者`, undefined);
|
|
482
537
|
} else {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@moonquake2004/dsh-doctor",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.31",
|
|
4
4
|
"description": "Offline diagnostic for DeepSeek Harness — 28 built-in + 5 catalog checks across env/profile/session (Layer A checks-as-data), self-update (Layer B), and a semi-automatic LLM observer (Layer C, --observe); Doctor panel in web UI settings.",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"files": [
|