@moonquake2004/dsh-doctor 0.4.10 → 0.4.14
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 +47 -3
- package/package.json +1 -1
package/dsh-doctor.mjs
CHANGED
|
@@ -212,6 +212,15 @@ function readableSessionEventTypes() {
|
|
|
212
212
|
|
|
213
213
|
const READABLE = readableSessionEventTypes();
|
|
214
214
|
|
|
215
|
+
/**
|
|
216
|
+
* 是否存在值得诊断的 DSH 环境(真实用户机器有 sessions/ 或 settings.yaml;干净容器/仓库检出没有)。
|
|
217
|
+
* 以传入的 home 为准:探针应当判断"它被告知的那个环境",而不是进程全局的 HOME ——
|
|
218
|
+
* 这样单测可以显式构造两种环境,fixture 也不会被真实 HOME 的状态污染。
|
|
219
|
+
*/
|
|
220
|
+
function hasDshEnvironment(home = HOME) {
|
|
221
|
+
return existsSync(join(home, 'sessions')) || existsSync(join(home, 'settings.yaml'));
|
|
222
|
+
}
|
|
223
|
+
|
|
215
224
|
function report(section, id, ok, detail, fix, src) {
|
|
216
225
|
results.push({ section, id, ok, detail, fix, src: src ?? 'builtin' });
|
|
217
226
|
}
|
|
@@ -1140,6 +1149,23 @@ function checkSession(targetPath) {
|
|
|
1140
1149
|
} else {
|
|
1141
1150
|
report('session', 'S9', true, '非 zstd 输入,跳过容器检查', undefined);
|
|
1142
1151
|
}
|
|
1152
|
+
|
|
1153
|
+
// S13:会话头完整性(#6651)——首行必须是 {"type":"session",...}
|
|
1154
|
+
// 实测:首行被写成事件时 dsh-doctor 的其余 S 检查**全部照常通过**(它们看的是事件流),
|
|
1155
|
+
// S11 甚至会报"均健康";而 harness 侧会 `corrupt Zstandard session log` 直接拒绝启动 `dsh web`。
|
|
1156
|
+
{
|
|
1157
|
+
const firstLine = String(text).split('\n').find((l) => l.trim());
|
|
1158
|
+
let header = null;
|
|
1159
|
+
try { header = firstLine ? JSON.parse(firstLine) : null; } catch { header = null; }
|
|
1160
|
+
if (!header || header.type !== 'session') {
|
|
1161
|
+
report('session', 'S13', false,
|
|
1162
|
+
`日志首行不是会话头(type=${header && header.type ? JSON.stringify(header.type) : '缺失/无法解析'})——#6651:此类损坏会让 \`dsh web\` **整体启动失败**(corrupt Zstandard session log: first frame is not exactly …),会话列表一并损坏`,
|
|
1163
|
+
'优先从备份恢复该日志;无备份时把整个会话目录**移出** sessions/(隔离,勿删)后重启 dsh,再用本工具复查');
|
|
1164
|
+
} else {
|
|
1165
|
+
report('session', 'S13', true,
|
|
1166
|
+
`会话头完整(type=session, version=${header.version ?? '?'}, id=${String(header.id ?? '').slice(0, 12)})`);
|
|
1167
|
+
}
|
|
1168
|
+
}
|
|
1143
1169
|
const calls = new Map(); const results2 = new Set(); let maxSeq = -1;
|
|
1144
1170
|
const turnStarts = new Set(); const turnEnds = new Set();
|
|
1145
1171
|
const positions = []; const endSeedSeqs = [];
|
|
@@ -1453,11 +1479,17 @@ function scanAllSessions() {
|
|
|
1453
1479
|
totalDS += ds;
|
|
1454
1480
|
// 轻量损坏扫描:seq==index + end-seed 重放 + 未知类型
|
|
1455
1481
|
const problems = [];
|
|
1482
|
+
let firstRowSeen = false;
|
|
1456
1483
|
let evIndex = 0, lastSeed = -1, seedIdx = -1, posList = [];
|
|
1457
1484
|
const lines = text.split('\n');
|
|
1458
1485
|
for (let li = 0; li < lines.length; li++) {
|
|
1459
1486
|
const ln = lines[li]; if (!ln.trim()) continue;
|
|
1460
1487
|
let d; try { d = JSON.parse(ln); } catch { problems.push(`行 ${li + 1} 无法解析`); continue; }
|
|
1488
|
+
// #6651:首行必须是会话头——否则 harness 拒绝启动 dsh web,而其余检查看不出问题
|
|
1489
|
+
if (!firstRowSeen) {
|
|
1490
|
+
firstRowSeen = true;
|
|
1491
|
+
if (d.type !== 'session') problems.push(`首行不是会话头(#6651 启动阻断,实际 type=${d.type})`);
|
|
1492
|
+
}
|
|
1461
1493
|
if (!STORAGE_ROW_TYPES.has(d.type) && !READABLE.has(d.type) && d.ignorable !== true) problems.push(`未知类型 ${d.type}`);
|
|
1462
1494
|
if (d.type === 'session/end-seed' && typeof d.seq === 'number') { lastSeed = d.seq; seedIdx = posList.length; }
|
|
1463
1495
|
const t = d.type;
|
|
@@ -1647,8 +1679,14 @@ export function runCatalogCheck(check, ctx) {
|
|
|
1647
1679
|
switch (probe.type) {
|
|
1648
1680
|
case 'command-exists': {
|
|
1649
1681
|
const found = findCommand(probe.cmd);
|
|
1650
|
-
|
|
1651
|
-
|
|
1682
|
+
if (found) return { ok: true, detail: check.detailOk ?? `${probe.cmd} 在 PATH: ${found}` };
|
|
1683
|
+
// 无 DSH 环境可言时 skip(2026-09):在干净容器/仓库检出里跑本工具时,dsh 当然不在 PATH ——
|
|
1684
|
+
// 那不是"用户环境有问题",而是"这里没有被诊断的 DSH 环境"。据此报 error 会让 CI 与 fixture
|
|
1685
|
+
// 得到无意义的失败(CI 三次红都源于同类检查:E4 → E1-pnpm → E7)。
|
|
1686
|
+
if (!hasDshEnvironment(ctx?.home ?? HOME)) {
|
|
1687
|
+
return { skipped: true, detail: `未发现 DSH 环境(无 sessions/ 与 settings.yaml),跳过 ${probe.cmd} 的 PATH 检查` };
|
|
1688
|
+
}
|
|
1689
|
+
return { ok: false, detail: check.detailFail ?? `${probe.cmd} 不在 PATH` };
|
|
1652
1690
|
}
|
|
1653
1691
|
case 'path-exists':
|
|
1654
1692
|
case 'path-is-dir':
|
|
@@ -2021,7 +2059,13 @@ async function run() {
|
|
|
2021
2059
|
console.log(JSON.stringify(out, null, 2));
|
|
2022
2060
|
process.exit(exitCode);
|
|
2023
2061
|
} else if (jsonOut) {
|
|
2024
|
-
|
|
2062
|
+
// 每条检查带上 status(pass/warn/fail/skip,与 --envelope 同一词汇表)。
|
|
2063
|
+
// 2026-09:此前 --json 只给 ok 布尔,消费者(含我们自己的测试助手)无法区分
|
|
2064
|
+
// "warn 级失败"(如 CI 上无 pnpm → E1-pnpm warn,不翻退出码)与"error 级失败",
|
|
2065
|
+
// 于是把 warn 当成误报。状态本就不该由消费者自行推导。
|
|
2066
|
+
const statusOf = (r) => (r.skip ? 'skip' : (!r.ok ? (((r.section === 'security') ? r.severity !== 'critical' : catalogSeverity.get(r.id) === 'warn') ? 'warn' : 'fail') : 'pass'));
|
|
2067
|
+
const checksWithStatus = results.map((r) => ({ ...r, status: statusOf(r) }));
|
|
2068
|
+
console.log(JSON.stringify({ ok: bad.length === 0 && secExit === 0, checks: checksWithStatus, catalog: catalogMeta, update: updateInfo, ...(securityMeta.enabled ? { security: securityMeta } : {}) }, null, 2));
|
|
2025
2069
|
} else {
|
|
2026
2070
|
const sectionOrder = { env: 0, profile: 1, session: 2, catalog: 3 };
|
|
2027
2071
|
const ordered = [...results].sort((a, b) => (sectionOrder[a.section] ?? 9) - (sectionOrder[b.section] ?? 9));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@moonquake2004/dsh-doctor",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.14",
|
|
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": [
|