@moonquake2004/dsh-doctor 0.4.27 → 0.4.28

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.
Files changed (2) hide show
  1. package/dsh-doctor.mjs +50 -0
  2. package/package.json +1 -1
package/dsh-doctor.mjs CHANGED
@@ -1316,6 +1316,51 @@ 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 rows = [];
1330
+ for (const ln of String(text).split('\n')) {
1331
+ if (!ln.trim()) continue;
1332
+ try { rows.push(JSON.parse(ln)); } catch { /* 解析失败由 S11/SS4 报 */ }
1333
+ }
1334
+ const seqs = new Set(rows.map((r) => r.seq).filter((x) => typeof x === 'number'));
1335
+ const probs = [];
1336
+ for (let i = 0; i < rows.length; i++) {
1337
+ const r = rows[i];
1338
+ const bodyTypes = /^(assistant\/message|tool\/result|system\/message)$/;
1339
+ if (bodyTypes.test(String(r.type)) && !(r.data && typeof r.data.message === 'object' && r.data.message !== null)) {
1340
+ probs.push(`行 ${i + 1}(${r.type})缺 data.message——stock projection 会穿透读取并抛错`);
1341
+ }
1342
+ const so = r.surfaceOp;
1343
+ if (so && typeof so === 'object' && so.op === 'replace') {
1344
+ const hasStartEnd = Object.hasOwn(so, 'start') && Object.hasOwn(so, 'end');
1345
+ const hasSeqRange = Object.hasOwn(so, 'startSeq') && Object.hasOwn(so, 'endSeq');
1346
+ const okRef = hasSeqRange
1347
+ ? (seqs.has(so.startSeq) || seqs.has(so.endSeq))
1348
+ : (hasStartEnd && (seqs.has(so.start) || seqs.has(so.end)));
1349
+ if (!okRef) probs.push(`行 ${i + 1}(${r.type})surfaceOp replace 的位置在本日志里解析不到`);
1350
+ }
1351
+ }
1352
+ if (probs.length) {
1353
+ report('session', 'S14', false,
1354
+ `检测到 ${probs.length} 处会让会话**打不开**的事件体/引用不完整(#6686:迁移成功但 project 抛错):\n `
1355
+ + probs.slice(0, 6).join('\n ')
1356
+ + (probs.length > 6 ? `\n …另有 ${probs.length - 6} 处` : ''),
1357
+ '这类会话在 GUI 里点开会报 failed to project session;上游修法(可选链 + 中性兜底)见 #6686;'
1358
+ + '在那之前可先把该会话目录移出 sessions/ 隔离,避免它影响会话列表');
1359
+ } else {
1360
+ report('session', 'S14', true, `投影安全性预检通过(${rows.length} 个事件:消息体完整、replace 引用可解析)`);
1361
+ }
1362
+ }
1363
+
1319
1364
  // S13:会话头完整性(#6651)——首行必须是 {"type":"session",...}
1320
1365
  // 实测:首行被写成事件时 dsh-doctor 的其余 S 检查**全部照常通过**(它们看的是事件流),
1321
1366
  // S11 甚至会报"均健康";而 harness 侧会 `corrupt Zstandard session log` 直接拒绝启动 `dsh web`。
@@ -1682,6 +1727,11 @@ function scanAllSessions() {
1682
1727
  for (let li = 0; li < lines.length; li++) {
1683
1728
  const ln = lines[li]; if (!ln.trim()) continue;
1684
1729
  let d; try { d = JSON.parse(ln); } catch { problems.push(`行 ${li + 1} 无法解析`); continue; }
1730
+ // #6686:事件类型暗示有消息体却没有 → 打开(project)时会抛错,整个会话打不开
1731
+ if (/^(assistant\/message|tool\/result|system\/message)$/.test(String(d.type))
1732
+ && !(d.data && typeof d.data.message === 'object' && d.data.message !== null)) {
1733
+ problems.push(`行 ${li + 1}(${d.type})缺 data.message(#6686:project 会抛错,会话打不开)`);
1734
+ }
1685
1735
  // #6651:首行必须是会话头——否则 harness 拒绝启动 dsh web,而其余检查看不出问题
1686
1736
  if (!firstRowSeen) {
1687
1737
  firstRowSeen = true;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@moonquake2004/dsh-doctor",
3
- "version": "0.4.27",
3
+ "version": "0.4.28",
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": [