@moonquake2004/dsh-doctor 0.2.6 → 0.2.7
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 +25 -0
- package/README.zh.md +25 -0
- package/dsh-doctor.mjs +39 -9
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -74,6 +74,31 @@ With `--envelope` (doctor-contract mode): `0` = all pass · `1` = any WARN · `2
|
|
|
74
74
|
|
|
75
75
|
|
|
76
76
|
|
|
77
|
+
## Symptom → check quick-start (dsh-diagnose alignment)
|
|
78
|
+
|
|
79
|
+
If you're coming from a symptom (rather than from the machine), these are the checks to run first. Coverage is honest: ✅ = direct offline coverage, ⚠️ = partial (we see the log/profile effects, not the runtime internals), ❌ = gap (runtime-only, no offline probe today).
|
|
80
|
+
|
|
81
|
+
| Symptom family | dsh-doctor checks | What they catch |
|
|
82
|
+
|---|---|---|
|
|
83
|
+
| session log corruption / can't resume | S1, S2, S6, S7, S8, S9, S10 | orphan tool calls, unclosed turns, seq gaps, end-seed replay, unknown event types, zstd single-frame, sourceEventSeqs drift |
|
|
84
|
+
| oversized / cold-start stall | S11 | estimated materialization heap, corrupt-session quarantine |
|
|
85
|
+
| boot failure (UI won't open) | P1–P10, E10 | dangling bundles, id collisions, patch syntax, host shadowing, adapter conflicts, client-service injects, port 3080 |
|
|
86
|
+
| tool registry gaps (tools missing) | P1, P2, P8, P10, P9 | unresolved/conflicting/duplicated tool registrations, client-only service injects |
|
|
87
|
+
| compaction / history unavailable | S10, S6, S8 | sourceEventSeqs not remapped after compaction |
|
|
88
|
+
| agent-loop lifecycle (session stuck "running") | S2, S1, S6 | unclosed turns, orphan tool calls, broken seq |
|
|
89
|
+
| llm retry storms | S6, S11, S2 | retry traffic effects on log integrity/size |
|
|
90
|
+
| token metering off | S11, S1, S2 | metering derives from the event stream |
|
|
91
|
+
| workflow script failures | P7, S6, S8, S1 | patch syntax (boot), workflow event integrity |
|
|
92
|
+
| approval policy pending | S2, S1 | open turns / orphan calls from pending or rejected approvals |
|
|
93
|
+
| credentials resolution | E2, E5, P4 | `.env` shape, storage JSON, `file:` links |
|
|
94
|
+
| web internals | E10, P10, E5 | port, client half, workspace storage |
|
|
95
|
+
| subagent depth | S11, S8 | session size, subagent event types |
|
|
96
|
+
| sandbox denials | E4 | node-pty binary (infra only) — ❌ runtime policy not offline-checkable |
|
|
97
|
+
| approval internals | S2 | ⚠️ runtime policy; only the turn-level effect |
|
|
98
|
+
| credentials internals | E2, E5 | ⚠️ file-level only |
|
|
99
|
+
|
|
100
|
+
The `dsh-doctor/v1` envelope (`--json --envelope`) is the machine-readable form of any of these runs, so a symptom tool can consume the verdict directly.
|
|
101
|
+
|
|
77
102
|
## Self-update check (v0.2.1, Layer B)
|
|
78
103
|
|
|
79
104
|
The tool also watches its own npm version: each run compares the installed version against `dist-tags.latest` (same 6h TTL cache + offline fallback as the catalog). When a newer release exists it prints a notice and reports `update: { current, latest, available }` in JSON — it never touches your install without being asked.
|
package/README.zh.md
CHANGED
|
@@ -67,6 +67,31 @@ node dsh-doctor.mjs --no-catalog # 不拉远程目录(只用内置副
|
|
|
67
67
|
- [zoahdev/dsh-plugin-doctor](https://github.com/zoahdev/dsh-plugin-doctor) —— 发布前插件 bundle 健康检查(manifest/patch/entry/files/build/pack+全新 profile 安装)+ 宿主遮蔽 `profile-shadow` 哨兵(作者/CI 侧)。与本工具的用户侧 profile/session/env 诊断互补;它的 `profile-shadow` 与我们的 P5 从两个方向标记同一个宿主遮蔽前置条件。
|
|
68
68
|
- [boyin111-1/dsh-doctor](https://github.com/boyin111-1/dsh-doctor) —— 同生态位离线诊断兄弟实现,用同一批坏 fixture 交叉验证。
|
|
69
69
|
|
|
70
|
+
## Symptom → check quick-start (dsh-diagnose alignment)
|
|
71
|
+
|
|
72
|
+
If you're coming from a symptom (rather than from the machine), these are the checks to run first. Coverage is honest: ✅ = direct offline coverage, ⚠️ = partial (we see the log/profile effects, not the runtime internals), ❌ = gap (runtime-only, no offline probe today).
|
|
73
|
+
|
|
74
|
+
| Symptom family | dsh-doctor checks | What they catch |
|
|
75
|
+
|---|---|---|
|
|
76
|
+
| session log corruption / can't resume | S1, S2, S6, S7, S8, S9, S10 | orphan tool calls, unclosed turns, seq gaps, end-seed replay, unknown event types, zstd single-frame, sourceEventSeqs drift |
|
|
77
|
+
| oversized / cold-start stall | S11 | estimated materialization heap, corrupt-session quarantine |
|
|
78
|
+
| boot failure (UI won't open) | P1–P10, E10 | dangling bundles, id collisions, patch syntax, host shadowing, adapter conflicts, client-service injects, port 3080 |
|
|
79
|
+
| tool registry gaps (tools missing) | P1, P2, P8, P10, P9 | unresolved/conflicting/duplicated tool registrations, client-only service injects |
|
|
80
|
+
| compaction / history unavailable | S10, S6, S8 | sourceEventSeqs not remapped after compaction |
|
|
81
|
+
| agent-loop lifecycle (session stuck "running") | S2, S1, S6 | unclosed turns, orphan tool calls, broken seq |
|
|
82
|
+
| llm retry storms | S6, S11, S2 | retry traffic effects on log integrity/size |
|
|
83
|
+
| token metering off | S11, S1, S2 | metering derives from the event stream |
|
|
84
|
+
| workflow script failures | P7, S6, S8, S1 | patch syntax (boot), workflow event integrity |
|
|
85
|
+
| approval policy pending | S2, S1 | open turns / orphan calls from pending or rejected approvals |
|
|
86
|
+
| credentials resolution | E2, E5, P4 | `.env` shape, storage JSON, `file:` links |
|
|
87
|
+
| web internals | E10, P10, E5 | port, client half, workspace storage |
|
|
88
|
+
| subagent depth | S11, S8 | session size, subagent event types |
|
|
89
|
+
| sandbox denials | E4 | node-pty binary (infra only) — ❌ runtime policy not offline-checkable |
|
|
90
|
+
| approval internals | S2 | ⚠️ runtime policy; only the turn-level effect |
|
|
91
|
+
| credentials internals | E2, E5 | ⚠️ file-level only |
|
|
92
|
+
|
|
93
|
+
The `dsh-doctor/v1` envelope (`--json --envelope`) is the machine-readable form of any of these runs, so a symptom tool can consume the verdict directly.
|
|
94
|
+
|
|
70
95
|
## 自更新检查(v0.2.1,层 B)
|
|
71
96
|
|
|
72
97
|
工具也会盯着自己的 npm 版本:每次运行对比已装版本与 `dist-tags.latest`(与目录同样的 6h TTL 缓存 + 离线回退)。有新版时打印提示、JSON 里报 `update: { current, latest, available }`——**未经你要求绝不改动你的安装**。
|
package/dsh-doctor.mjs
CHANGED
|
@@ -12,6 +12,8 @@
|
|
|
12
12
|
* P7 cordis.patch.yml 结构 lint(#1724:~ insert: 是 YAML null → parsePatchList 崩溃 → UI 打不开;tab 缩进/缺冒号同族)
|
|
13
13
|
* P8 adapter provider 注册冲突(#1904②:两 bundle 抢注同一 provider → boot 时 DUPLICATE_ADAPTER 崩溃)
|
|
14
14
|
* P9 ctx.settings 未声明 inject: ['settings'](#1904⑤:先于 settings 就绪激活 → namespace not registered)
|
|
15
|
+
* P10 inject 引用客户端专属服务(#1947:@deepseek-ai/dsh-client-* 服务端永不提供 → Fiber 永久 PENDING → web boot 失败)
|
|
16
|
+
* P11 已装 bundle 的 main 入口产物缺失(#1965:市场装未构建源码树 → ERR_MODULE_NOT_FOUND → boot 崩)
|
|
15
17
|
* [session]
|
|
16
18
|
* S1 孤儿 tool_call(#1363:assistant tool_calls 无对应 tool 结果 → INVALID_REQUEST)
|
|
17
19
|
* S2 未闭合 turn(#466/#1265:turn/start 无 turn/end → 会话永久"运行中")
|
|
@@ -455,6 +457,18 @@ function checkProfile(name) {
|
|
|
455
457
|
report('profile', 'P8', true, '无 adapter provider 注册冲突', undefined);
|
|
456
458
|
}
|
|
457
459
|
|
|
460
|
+
// 提取 bundle 构建产物里声明的全部 inject 依赖名(模块 inject + ctx.inject;bundle 可能混入内部模块的 inject)
|
|
461
|
+
const bundleInjectDecls = (all) => {
|
|
462
|
+
const declared = [];
|
|
463
|
+
for (const m of all.matchAll(/inject\s*=\s*\[([^\]]*)\]/gs)) {
|
|
464
|
+
declared.push(...[...m[1].matchAll(/['"]([^'"]+)['"]/g)].map((x) => x[1]));
|
|
465
|
+
}
|
|
466
|
+
for (const m of all.matchAll(/ctx\.inject\s*\(\s*\[([^\]]*)\]/g)) {
|
|
467
|
+
declared.push(...[...m[1].matchAll(/['"]([^'"]+)['"]/g)].map((x) => x[1]));
|
|
468
|
+
}
|
|
469
|
+
return declared.filter((v, i) => declared.indexOf(v) === i);
|
|
470
|
+
};
|
|
471
|
+
|
|
458
472
|
// P9:ctx.settings/ctx.get('settings') 未声明 settings 依赖(#1904⑤:先于 settings 就绪激活 → namespace not registered)
|
|
459
473
|
// 注意边界:sctx.settings 不算(sctx 是别的变量);ctx.inject(["settings"], cb) 运行时声明算满足
|
|
460
474
|
const injectIssues = [];
|
|
@@ -463,21 +477,36 @@ function checkProfile(name) {
|
|
|
463
477
|
const all = files.map(readJs).join('\n');
|
|
464
478
|
const usesSettings = /(?<![A-Za-z0-9_$])ctx\.(?:get\(\s*['"]settings['"]\s*\)|settings\b)/.test(all);
|
|
465
479
|
if (!usesSettings) continue;
|
|
466
|
-
const
|
|
467
|
-
// 收集全部 inject 声明(bundle 里可能混有内部模块的 inject;任一含 settings 即满足)
|
|
468
|
-
for (const m of all.matchAll(/inject\s*=\s*\[([^\]]*)\]/gs)) {
|
|
469
|
-
declared.push(...[...m[1].matchAll(/['"]([^'"]+)['"]/g)].map((x) => x[1]));
|
|
470
|
-
}
|
|
471
|
-
for (const m of all.matchAll(/ctx\.inject\s*\(\s*\[([^\]]*)\]/g)) {
|
|
472
|
-
declared.push(...[...m[1].matchAll(/['"]([^'"]+)['"]/g)].map((x) => x[1]));
|
|
473
|
-
}
|
|
474
|
-
const uniq = declared.filter((v, i) => declared.indexOf(v) === i);
|
|
480
|
+
const uniq = bundleInjectDecls(all);
|
|
475
481
|
if (!uniq.includes('settings')) {
|
|
476
482
|
injectIssues.push(`${b}(用 ctx.settings 但 settings 依赖未声明${uniq.length ? `,全部 inject: [${uniq.join(', ')}]` : ',未找到任何 inject 声明'})`);
|
|
477
483
|
}
|
|
478
484
|
}
|
|
479
485
|
if (injectIssues.length) report('profile', 'P9', false, `插件用 ctx.settings 但未声明 settings 依赖(#1904⑤:激活时 settings 可能未就绪 → namespace not registered): ${injectIssues.join('; ')}`, '在插件代码加 export const inject = ["settings"](或对可选服务做 undefined 处理)');
|
|
480
486
|
else report('profile', 'P9', true, 'bundle 的 ctx.settings 用法均声明了 settings 依赖(模块 inject 或 ctx.inject)', undefined);
|
|
487
|
+
|
|
488
|
+
// P10:inject 引用客户端专属服务(@deepseek-ai/dsh-client-*)→ 服务端永不提供 → Fiber 永久 PENDING → web boot 失败(#1947)
|
|
489
|
+
const clientInjectIssues = [];
|
|
490
|
+
for (const [b, d] of bundleDirs) {
|
|
491
|
+
const all = collectJsFiles(d).map(readJs).join('\n');
|
|
492
|
+
const clientDeps = bundleInjectDecls(all).filter((n) => /^(@deepseek-ai\/)?dsh-client-/.test(n));
|
|
493
|
+
if (clientDeps.length) clientInjectIssues.push(`${b}(inject 引用客户端专属服务: ${clientDeps.join(', ')})`);
|
|
494
|
+
}
|
|
495
|
+
if (clientInjectIssues.length) report('profile', 'P10', false, `插件 inject 引用客户端专属服务(服务端 cordis 树永不提供 → Fiber 永久 PENDING → web boot 失败,#1947): ${clientInjectIssues.join('; ')}`, '客户端服务不能作为服务端插件依赖:把相关功能移到插件 client 半(package.json 的 dsh.client.inject),或删除该 inject');
|
|
496
|
+
else report('profile', 'P10', true, '无客户端专属服务注入', undefined);
|
|
497
|
+
|
|
498
|
+
// P11:已装 bundle 的 main 入口产物缺失(#1965:市场把未构建源码树当插件装 → ERR_MODULE_NOT_FOUND → boot 崩)
|
|
499
|
+
const entryIssues = [];
|
|
500
|
+
for (const [b, d] of bundleDirs) {
|
|
501
|
+
let main;
|
|
502
|
+
try { main = JSON.parse(readFileSync(join(d, 'package.json'), 'utf8')).main; } catch { continue; }
|
|
503
|
+
if (typeof main !== 'string' || !main.endsWith('.js')) continue;
|
|
504
|
+
if (!existsSync(join(d, main))) {
|
|
505
|
+
entryIssues.push(`${b}(main=${main} 但产物缺失——未构建的源码树,或装错了仓库根而非 monorepo 子包)`);
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
if (entryIssues.length) report('profile', 'P11', false, `已装 bundle 的 main 入口缺失(#1965:市场装源码不跑构建 → ERR_MODULE_NOT_FOUND → dsh web boot 崩溃): ${entryIssues.join('; ')}`, '在插件目录跑构建(pnpm install && pnpm run build 产出 main 指向的文件),或改用打包好的 npm 包安装;monorepo 插件需装子包(dsh-market #18 同族)');
|
|
509
|
+
else report('profile', 'P11', true, '已装 bundle 的 main 入口产物均在', undefined);
|
|
481
510
|
}
|
|
482
511
|
|
|
483
512
|
/* ================= session ================= */
|
|
@@ -1019,6 +1048,7 @@ async function run() {
|
|
|
1019
1048
|
const exitCode = summary.fail > 0 ? 2 : summary.warn > 0 ? 1 : 0;
|
|
1020
1049
|
console.log(JSON.stringify({
|
|
1021
1050
|
schema: 'dsh-doctor/v1',
|
|
1051
|
+
tool: 'dsh-doctor',
|
|
1022
1052
|
generatedAt: new Date().toISOString(),
|
|
1023
1053
|
profile: profileArg,
|
|
1024
1054
|
exitCode,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@moonquake2004/dsh-doctor",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.7",
|
|
4
4
|
"description": "Offline diagnostic for DeepSeek Harness — 19 built-in checks + self-updating catalog (Layer A) + self-update check (Layer B); 'Doctor' panel in web UI settings.",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"files": [
|