@moonquake2004/dsh-doctor 0.4.8 → 0.4.10

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 +36 -3
  2. package/package.json +1 -1
package/dsh-doctor.mjs CHANGED
@@ -274,6 +274,14 @@ function checkEnv() {
274
274
  for (const d of readdirSync(pnpmStore)) if (d.startsWith('node-pty@')) ptyDirs.push(join(pnpmStore, d, 'node_modules', 'node-pty'));
275
275
  }
276
276
  const plat = `${process.platform}-${process.arch}`;
277
+ // 无对象可查时 skip 而非 fail(2026-09 修正):E4 的判据是"**已安装的** dsh 里 node-pty 是否完整",
278
+ // 但此前在**没有任何 node_modules 的合成 HOME**(测试 fixture、全新环境)里也会报"未找到 node-pty" ——
279
+ // 那不是"缺失",而是"这里根本没有安装树可查"。据此报 fail 会让 fixture 全红、也会让 CI 在
280
+ // 干净容器里得到无意义的 exit 2。与项目一贯纪律一致:不适用 ⇒ skip 且带 reason。
281
+ // "有安装树可查" = PATH 里找到了 dsh 的 node_modules,**或**本 HOME 的 profile 里有 node_modules。
282
+ // 二者皆无(合成 HOME、干净容器)→ 这里没有"已安装的 dsh"可言,报 fail 是无意义的。
283
+ const fromPath = ptyDirs.length > 0 && existsSync(ptyDirs[0]);
284
+ const hasInstall = fromPath || existsSync(profileNM);
277
285
  const ptyFound = ptyDirs.filter((d) => existsSync(d));
278
286
  let ptyBinary = null;
279
287
  for (const d of ptyFound) {
@@ -282,7 +290,8 @@ function checkEnv() {
282
290
  }
283
291
  if (ptyBinary) break;
284
292
  }
285
- if (ptyFound.length === 0) report('env', 'E4', false, '未找到 node-pty(dsh web 终端依赖它,#1219)', '重新安装 @deepseek-ai/dsh,确保 node-pty 装全');
293
+ if (!hasInstall) reportSkip('env', 'E4', '未发现 DSH 安装树(无 profiles/node_modules 可查),跳过 node-pty 完整性检测');
294
+ else if (ptyFound.length === 0) report('env', 'E4', false, '未找到 node-pty(dsh web 终端依赖它,#1219)', '重新安装 @deepseek-ai/dsh,确保 node-pty 装全');
286
295
  else if (ptyBinary) report('env', 'E4', true, `node-pty 原生模块在位(${plat})`, undefined);
287
296
  else report('env', 'E4', false, `node-pty 存在但缺 ${plat} 原生二进制(#1219: dsh web 启动失败)`, '重装 node-pty(npm rebuild node-pty)或从源码构建');
288
297
 
@@ -1917,10 +1926,34 @@ async function run() {
1917
1926
  // 获取最新会话文件(供 SR*/SS* 检查使用)
1918
1927
  // 世代感知定位(与 S11 同一规则):三层 sessions/<user>/<session>/session*.jsonl*,
1919
1928
  // 目录内取最高世代(v0/v3 并存时取 v3),跨目录按 mtime 取最新,忽略 session.lock
1929
+ // 运行时检查(SR*/SS*)扫"最新会话",而最新会话通常**就是当前正在写入的会话** ——
1930
+ // 那会把操作者自己的操作报成安全发现(2026-09 实测:SR1 报的 critical 全是本次会话里
1931
+ // 自己写的 `curl|bash` 测试串,SR2 的 `doas` 来自自己引用的规则文本)。
1932
+ // 故默认跳过"仍在写入"的会话:取 mtime 早于活跃窗口的最新会话;
1933
+ // 窗口可用 DSH_DOCTOR_LIVE_WINDOW_MS 调整(默认 120s,设 0 关闭该保护)。
1934
+ const LIVE_WINDOW_MS = (() => {
1935
+ const v = Number(process.env.DSH_DOCTOR_LIVE_WINDOW_MS);
1936
+ return Number.isFinite(v) && v >= 0 ? v : 120000;
1937
+ })();
1920
1938
  const findLatestSession = () => {
1921
- if (sessionArg) return sessionArg;
1922
- try { return latestSessionLog(); } catch { return null; }
1939
+ if (sessionArg) return sessionArg; // 显式指定则始终尊重
1940
+ try {
1941
+ const root = join(HOME, 'sessions');
1942
+ const all = listSessionLogs(root); // 已按 mtime 降序
1943
+ if (!all.length) return null;
1944
+ if (LIVE_WINDOW_MS === 0) return all[0].f;
1945
+ const cutoff = Date.now() - LIVE_WINDOW_MS;
1946
+ const settled = all.find((x) => (x.m ?? 0) < cutoff);
1947
+ return (settled ?? all[0]).f;
1948
+ } catch { return null; }
1923
1949
  };
1950
+ const sessionIsLive = (() => {
1951
+ if (sessionArg || LIVE_WINDOW_MS === 0) return false;
1952
+ try {
1953
+ const all = listSessionLogs(join(HOME, 'sessions'));
1954
+ return all.length > 0 && (all[0].m ?? 0) >= Date.now() - LIVE_WINDOW_MS;
1955
+ } catch { return false; }
1956
+ })();
1924
1957
 
1925
1958
  const { results: secResults, exitCode: secExit, summary: secSummary } = await registry.runAll(
1926
1959
  (check) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@moonquake2004/dsh-doctor",
3
- "version": "0.4.8",
3
+ "version": "0.4.10",
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": [