@moonquake2004/dsh-doctor 0.4.27 → 0.4.29
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 +69 -0
- package/package.json +1 -1
package/dsh-doctor.mjs
CHANGED
|
@@ -1316,6 +1316,70 @@ function checkSession(targetPath) {
|
|
|
1316
1316
|
report('session', 'S9', true, '非 zstd 输入,跳过容器检查', undefined);
|
|
1317
1317
|
}
|
|
1318
1318
|
|
|
1319
|
+
// S14:投影安全性预检(#6686)——迁移**成功**、但打开(project)时抛错的会话
|
|
1320
|
+
// 报告者实测:v0→v3 迁移后部分事件**没有消息体**,而 stock projection 直接穿透读取,
|
|
1321
|
+
// 于是 `failed to project session: Cannot read properties of undefined (reading 'content')`,
|
|
1322
|
+
// 整个历史会话打不开。我们离线预判同一批形态,让用户在"点开之前"就知道哪些会话会炸:
|
|
1323
|
+
// ① 事件类型暗示有消息体,但 data.message 缺失/非对象(如 assistant/message、tool/result、
|
|
1324
|
+
// system/message —— 三个 stock projection 分别读 data.message.content、
|
|
1325
|
+
// data.message.source.callId、data.message.content[0].isError);
|
|
1326
|
+
// ② surfaceOp 为 replace 形态({op:'replace', start/end} 或 startSeq/endSeq),
|
|
1327
|
+
// 但引用的位置在本日志里解析不到。
|
|
1328
|
+
{
|
|
1329
|
+
const rawRows = [];
|
|
1330
|
+
for (const ln of String(text).split('\n')) {
|
|
1331
|
+
if (!ln.trim()) continue;
|
|
1332
|
+
try { rawRows.push(JSON.parse(ln)); } catch { /* 解析失败由 S11/SS4 报 */ }
|
|
1333
|
+
}
|
|
1334
|
+
// **判定对象必须是"打开时真正被投影的那条流"**:对需要迁移的日志(v0/v1/v2),
|
|
1335
|
+
// 缺消息体可能是在**迁移过程中**产生的,磁盘行上看不出来。所以这里优先走真实迁移链取"迁移后的视图"。
|
|
1336
|
+
// (2026-09 自查:初版只看磁盘行,会漏掉报告者 #6686 的实际病例。)
|
|
1337
|
+
let rows = rawRows;
|
|
1338
|
+
let view = '磁盘原始行';
|
|
1339
|
+
const hdr = rawRows[0];
|
|
1340
|
+
if (hdr && hdr.type === 'session' && hdr.version !== 3 && FORMAT_CATALOG) {
|
|
1341
|
+
try {
|
|
1342
|
+
const r = FORMAT_CATALOG.createRestore(hdr, { validation: 'current', recovery: 'strict' });
|
|
1343
|
+
const decoded = [];
|
|
1344
|
+
for (let i = 1; i < rawRows.length; i++) {
|
|
1345
|
+
const d = r.decodeRow(rawRows[i]);
|
|
1346
|
+
decoded.push((d && d.type) ? d : (d && d.event) ? d.event : rawRows[i]);
|
|
1347
|
+
}
|
|
1348
|
+
r.finish();
|
|
1349
|
+
rows = decoded;
|
|
1350
|
+
view = `迁移后视图(v${hdr.version} → v3,走真实迁移链)`;
|
|
1351
|
+
} catch { /* 迁移被拒由 S12 报;这里退回磁盘行并注明 */ view = '磁盘原始行(迁移链未走通,见 S12)'; }
|
|
1352
|
+
}
|
|
1353
|
+
const seqs = new Set(rows.map((r) => r.seq).filter((x) => typeof x === 'number'));
|
|
1354
|
+
const probs = [];
|
|
1355
|
+
for (let i = 0; i < rows.length; i++) {
|
|
1356
|
+
const r = rows[i];
|
|
1357
|
+
const bodyTypes = /^(assistant\/message|tool\/result|system\/message)$/;
|
|
1358
|
+
if (bodyTypes.test(String(r.type)) && !(r.data && typeof r.data.message === 'object' && r.data.message !== null)) {
|
|
1359
|
+
probs.push(`行 ${i + 1}(${r.type})缺 data.message——stock projection 会穿透读取并抛错`);
|
|
1360
|
+
}
|
|
1361
|
+
const so = r.surfaceOp;
|
|
1362
|
+
if (so && typeof so === 'object' && so.op === 'replace') {
|
|
1363
|
+
const hasStartEnd = Object.hasOwn(so, 'start') && Object.hasOwn(so, 'end');
|
|
1364
|
+
const hasSeqRange = Object.hasOwn(so, 'startSeq') && Object.hasOwn(so, 'endSeq');
|
|
1365
|
+
const okRef = hasSeqRange
|
|
1366
|
+
? (seqs.has(so.startSeq) || seqs.has(so.endSeq))
|
|
1367
|
+
: (hasStartEnd && (seqs.has(so.start) || seqs.has(so.end)));
|
|
1368
|
+
if (!okRef) probs.push(`行 ${i + 1}(${r.type})surfaceOp replace 的位置在本日志里解析不到`);
|
|
1369
|
+
}
|
|
1370
|
+
}
|
|
1371
|
+
if (probs.length) {
|
|
1372
|
+
report('session', 'S14', false,
|
|
1373
|
+
`检测到 ${probs.length} 处会让会话**打不开**的事件体/引用不完整(#6686:迁移成功但 project 抛错;判定视图:${view}):\n `
|
|
1374
|
+
+ probs.slice(0, 6).join('\n ')
|
|
1375
|
+
+ (probs.length > 6 ? `\n …另有 ${probs.length - 6} 处` : ''),
|
|
1376
|
+
'这类会话在 GUI 里点开会报 failed to project session;上游修法(可选链 + 中性兜底)见 #6686;'
|
|
1377
|
+
+ '在那之前可先把该会话目录移出 sessions/ 隔离,避免它影响会话列表');
|
|
1378
|
+
} else {
|
|
1379
|
+
report('session', 'S14', true, `投影安全性预检通过(${rows.length} 个事件:消息体完整、replace 引用可解析;判定视图:${view})`);
|
|
1380
|
+
}
|
|
1381
|
+
}
|
|
1382
|
+
|
|
1319
1383
|
// S13:会话头完整性(#6651)——首行必须是 {"type":"session",...}
|
|
1320
1384
|
// 实测:首行被写成事件时 dsh-doctor 的其余 S 检查**全部照常通过**(它们看的是事件流),
|
|
1321
1385
|
// S11 甚至会报"均健康";而 harness 侧会 `corrupt Zstandard session log` 直接拒绝启动 `dsh web`。
|
|
@@ -1682,6 +1746,11 @@ function scanAllSessions() {
|
|
|
1682
1746
|
for (let li = 0; li < lines.length; li++) {
|
|
1683
1747
|
const ln = lines[li]; if (!ln.trim()) continue;
|
|
1684
1748
|
let d; try { d = JSON.parse(ln); } catch { problems.push(`行 ${li + 1} 无法解析`); continue; }
|
|
1749
|
+
// #6686:事件类型暗示有消息体却没有 → 打开(project)时会抛错,整个会话打不开
|
|
1750
|
+
if (/^(assistant\/message|tool\/result|system\/message)$/.test(String(d.type))
|
|
1751
|
+
&& !(d.data && typeof d.data.message === 'object' && d.data.message !== null)) {
|
|
1752
|
+
problems.push(`行 ${li + 1}(${d.type})缺 data.message(#6686:project 会抛错,会话打不开)`);
|
|
1753
|
+
}
|
|
1685
1754
|
// #6651:首行必须是会话头——否则 harness 拒绝启动 dsh web,而其余检查看不出问题
|
|
1686
1755
|
if (!firstRowSeen) {
|
|
1687
1756
|
firstRowSeen = true;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@moonquake2004/dsh-doctor",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.29",
|
|
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": [
|