@moonquake2004/dsh-doctor 0.4.15 → 0.4.17
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 +54 -25
- package/package.json +1 -1
package/dsh-doctor.mjs
CHANGED
|
@@ -240,7 +240,9 @@ function resolveProfile(name) {
|
|
|
240
240
|
}
|
|
241
241
|
|
|
242
242
|
/* ================= env ================= */
|
|
243
|
-
/** v1 词汇表 r5(#1719)node 语义:pass = 满足 ^22.19.0 || >=24.0.0
|
|
243
|
+
/** v1 词汇表 r5(#1719/#2259)node 语义:pass = 满足 ^22.19.0 || >=24.0.0,其余 warn——无民间 fail 阈值。
|
|
244
|
+
* 注(2026-09 更正):该范围来自 v1 词汇表对齐,**不是**从某个 package.json 读来的;
|
|
245
|
+
* 已发布的 `@deepseek-ai/dsh@0.1.5-rc.1` **不含 engines 字段**,此前文案写"root package.json engines"属出处误引。 */
|
|
244
246
|
function nodeInSupportedRange(v) {
|
|
245
247
|
const m = /^v?(\d+)\.(\d+)\.(\d+)/.exec(String(v));
|
|
246
248
|
if (!m) return false;
|
|
@@ -264,8 +266,40 @@ function checkEnv() {
|
|
|
264
266
|
const version = String(nv.stdout).trim();
|
|
265
267
|
const supported = nodeInSupportedRange(version);
|
|
266
268
|
report('env', 'E3-node', supported,
|
|
267
|
-
supported ? `node ${version}(满足 ^22.19.0 || >=24.0.0
|
|
268
|
-
supported ? undefined : '升级 node 到 ^22.19.0 或 >=24.0.0(
|
|
269
|
+
supported ? `node ${version}(满足 v1 词汇表支持范围 ^22.19.0 || >=24.0.0;该版本未发布 engines 字段,#2259)` : `node ${version} 不在支持范围(^22.19.0 || >=24.0.0,v1 词汇表)——会话日志读取等能力受限`,
|
|
270
|
+
supported ? undefined : '升级 node 到 ^22.19.0 或 >=24.0.0(v1 词汇表范围,见 #2259)');
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// E12:运行时 zstd 稳定性(#6651 的运行时线索)
|
|
274
|
+
// 锚点已核实:dsh-session-persistence-jsonl:15 直接 `import { zstdCompress, zstdDecompress,
|
|
275
|
+
// zstdDecompressSync } from "node:zlib"`(用于 :1263 / :1287 / :1368)——会话日志的读写**依赖 Node 内置 zstd**。
|
|
276
|
+
// 该 API 在部分 Node 版本上仍是实验性的;社区报告 #6651(Linux + Node 22.22.3)出现首帧损坏让
|
|
277
|
+
// `dsh web` 无法启动,换到 Node 26 后正常,怀疑写入侧不稳。故这里如实报告运行时状态,不做因果断言。
|
|
278
|
+
if (!hasDshEnvironment(HOME)) {
|
|
279
|
+
reportSkip('env', 'E12', '未发现 DSH 环境,跳过运行时 zstd 稳定性检查');
|
|
280
|
+
} else {
|
|
281
|
+
const zstdFn = (() => { try { return createRequire(import.meta.url)('node:zlib').zstdDecompressSync; } catch { return null; } })();
|
|
282
|
+
if (typeof zstdFn !== 'function') {
|
|
283
|
+
report('env', 'E12', false,
|
|
284
|
+
`当前 Node(${process.version})没有 zlib.zstdDecompressSync——而会话持久化直接依赖它读写 .zstd 日志(#6651 同族风险)`,
|
|
285
|
+
'升级 Node 到内置 zstd 的版本(22.15+/23.8+),否则该运行时的 .zstd 会话日志无法读写');
|
|
286
|
+
} else {
|
|
287
|
+
// 用子进程观察 Node 是否对该 API 发出实验性警告(版本无关的经验判据,避免硬编码版本表)
|
|
288
|
+
let experimental = false; let probed = false;
|
|
289
|
+
try {
|
|
290
|
+
const r = spawnSync(process.execPath, ['-e', 'require("node:zlib").zstdDecompressSync(require("node:zlib").zstdCompressSync(Buffer.from("x")))'], { encoding: 'utf8', timeout: 10000 });
|
|
291
|
+
probed = true;
|
|
292
|
+
experimental = /ExperimentalWarning/.test(String(r.stderr || ''));
|
|
293
|
+
} catch { /* 探测失败 → 下面按"未探明"处理 */ }
|
|
294
|
+
if (!probed) reportSkip('env', 'E12', '运行时 zstd 实验性探测未能执行,跳过(不外推为通过)');
|
|
295
|
+
else if (experimental) {
|
|
296
|
+
report('env', 'E12', false,
|
|
297
|
+
`当前 Node(${process.version})的 zlib zstd 仍标记为**实验性**——会话持久化直接依赖它读写日志;社区报告 #6651 在同类运行时上出现首帧损坏并导致 dsh web 无法启动(换新版 Node 后正常,因果未定)`,
|
|
298
|
+
'升级到 zstd 已稳定的 Node(本工具在 24.x 上实测无实验性警告)后再观察;同时建议先用 dsh-doctor 扫一遍会话库有无 S13 类损坏');
|
|
299
|
+
} else {
|
|
300
|
+
report('env', 'E12', true, `运行时 zstd 非实验性(Node ${process.version})`, undefined);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
269
303
|
}
|
|
270
304
|
|
|
271
305
|
// E4:node-pty 原生模块完整性(#1219:pty.node 缺失 → dsh web 启动失败)
|
|
@@ -1162,35 +1196,30 @@ function checkSession(targetPath) {
|
|
|
1162
1196
|
`日志首行不是会话头(type=${header && header.type ? JSON.stringify(header.type) : '缺失/无法解析'})——#6651:此类损坏会让 \`dsh web\` **整体启动失败**(corrupt Zstandard session log: first frame is not exactly …),会话列表一并损坏`,
|
|
1163
1197
|
'优先从备份恢复该日志;无备份时把整个会话目录**移出** sessions/(隔离,勿删)后重启 dsh,再用本工具复查');
|
|
1164
1198
|
} else {
|
|
1165
|
-
// 更精确的判据:harness
|
|
1166
|
-
//
|
|
1167
|
-
//
|
|
1199
|
+
// 更精确的判据:harness 的读取路径是 **Node 的 `zlib.zstdDecompressSync`**,它**只解第一个帧**,
|
|
1200
|
+
// 然后要求该帧明文恰好是一行(dsh-session-persistence-jsonl:1891/2185 的
|
|
1201
|
+
// `plaintext.indexOf(10) !== plaintext.length - 1`)。所以最忠实的做法不是自己扫 magic 猜帧边界,
|
|
1202
|
+
// 而是**用同一个解压器**复现同一个条件(2026-09 与 #6651 的运行时线索一致后再校准)。
|
|
1168
1203
|
let frameNote = '';
|
|
1169
1204
|
let frameBad = false;
|
|
1170
1205
|
if (target.endsWith('.zstd')) {
|
|
1171
|
-
try {
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
const tmp = join(tmpdir(), `dsh-doctor-firstframe-${process.pid}.zst`);
|
|
1180
|
-
writeFileSync(tmp, raw.subarray(0, offs[1]));
|
|
1181
|
-
let first = '';
|
|
1182
|
-
try { first = execFileSync('zstd', ['-dc', tmp], { maxBuffer: 16 * 1024 * 1024 }).toString('utf8'); }
|
|
1183
|
-
finally { try { unlinkSync(tmp); } catch { /* 清理失败不致命 */ } }
|
|
1184
|
-
const frameLines = first.split('\n').filter((l) => l.trim());
|
|
1185
|
-
if (frameLines.length !== 1) {
|
|
1206
|
+
const zstdSync = (() => { try { return createRequire(import.meta.url)('node:zlib').zstdDecompressSync; } catch { return null; } })();
|
|
1207
|
+
if (typeof zstdSync !== 'function') {
|
|
1208
|
+
frameNote = '(本机 Node 无 zlib.zstdDecompressSync,未据此判定)';
|
|
1209
|
+
} else {
|
|
1210
|
+
try {
|
|
1211
|
+
const first = zstdSync(readFileSync(target)).toString('utf8');
|
|
1212
|
+
const exactlyOneLine = first.length > 0 && first.indexOf('\n') === first.length - 1;
|
|
1213
|
+
if (!exactlyOneLine) {
|
|
1186
1214
|
frameBad = true;
|
|
1187
|
-
|
|
1215
|
+
const n = first.split('\n').filter((l) => l.trim()).length;
|
|
1216
|
+
frameNote = `首帧明文含 ${n} 行(harness 要求恰好 1 行头)——帧边界错位/单帧容器都会触发 corrupt Zstandard session log`;
|
|
1188
1217
|
}
|
|
1189
|
-
}
|
|
1218
|
+
} catch (e) {
|
|
1190
1219
|
frameBad = true;
|
|
1191
|
-
frameNote =
|
|
1220
|
+
frameNote = `首帧无法解压(${String(e.code || e.message).slice(0, 40)})——与 harness 读取路径一致,启动会被拒绝`;
|
|
1192
1221
|
}
|
|
1193
|
-
}
|
|
1222
|
+
}
|
|
1194
1223
|
}
|
|
1195
1224
|
if (frameBad) {
|
|
1196
1225
|
report('session', 'S13', false,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@moonquake2004/dsh-doctor",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.17",
|
|
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": [
|