@lark-apaas/openclaw-scripts-diagnose-cli 0.1.1-alpha.24 → 0.1.1-alpha.26
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/dist/index.cjs +176 -15
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -365,6 +365,74 @@ ConfigSyntaxRule = __decorate([Rule({
|
|
|
365
365
|
repairMode: "ai"
|
|
366
366
|
})], ConfigSyntaxRule);
|
|
367
367
|
//#endregion
|
|
368
|
+
//#region src/rules/template-vars-unreplaced.ts
|
|
369
|
+
/**
|
|
370
|
+
* Placeholder format used by miaoda-openclaw-template and Go-side templateVars,
|
|
371
|
+
* e.g. `$$__FEISHU_APP_ID__`. Double underscores on both sides act as a natural
|
|
372
|
+
* boundary so split-join replacement can't accidentally overlap between keys.
|
|
373
|
+
*/
|
|
374
|
+
const PLACEHOLDER_RE = /\$\$__[A-Z0-9_]+__/g;
|
|
375
|
+
let TemplateVarsUnreplacedRule = class TemplateVarsUnreplacedRule extends DiagnoseRule {
|
|
376
|
+
validate(ctx) {
|
|
377
|
+
const found = /* @__PURE__ */ new Set();
|
|
378
|
+
collectPlaceholders(ctx.config, found);
|
|
379
|
+
if (found.size === 0) return { pass: true };
|
|
380
|
+
return {
|
|
381
|
+
pass: false,
|
|
382
|
+
message: "存在未替换的模板占位符: " + [...found].sort().join(", ")
|
|
383
|
+
};
|
|
384
|
+
}
|
|
385
|
+
repair(ctx) {
|
|
386
|
+
const map = ctx.templateVars;
|
|
387
|
+
if (!map || Object.keys(map).length === 0) return;
|
|
388
|
+
replaceInPlace(ctx.config, Object.entries(map));
|
|
389
|
+
}
|
|
390
|
+
};
|
|
391
|
+
TemplateVarsUnreplacedRule = __decorate([Rule({
|
|
392
|
+
key: "template_vars_unreplaced",
|
|
393
|
+
dependsOn: ["config_syntax_check"],
|
|
394
|
+
repairMode: "standard"
|
|
395
|
+
})], TemplateVarsUnreplacedRule);
|
|
396
|
+
function collectPlaceholders(value, found) {
|
|
397
|
+
if (typeof value === "string") {
|
|
398
|
+
const matches = value.match(PLACEHOLDER_RE);
|
|
399
|
+
if (matches) for (const m of matches) found.add(m);
|
|
400
|
+
return;
|
|
401
|
+
}
|
|
402
|
+
if (Array.isArray(value)) {
|
|
403
|
+
for (const v of value) collectPlaceholders(v, found);
|
|
404
|
+
return;
|
|
405
|
+
}
|
|
406
|
+
if (value && typeof value === "object") for (const v of Object.values(value)) collectPlaceholders(v, found);
|
|
407
|
+
}
|
|
408
|
+
function replaceInPlace(value, entries) {
|
|
409
|
+
if (Array.isArray(value)) {
|
|
410
|
+
for (let i = 0; i < value.length; i++) {
|
|
411
|
+
const el = value[i];
|
|
412
|
+
if (typeof el === "string") value[i] = applyVars(el, entries);
|
|
413
|
+
else replaceInPlace(el, entries);
|
|
414
|
+
}
|
|
415
|
+
return;
|
|
416
|
+
}
|
|
417
|
+
if (value && typeof value === "object") {
|
|
418
|
+
const obj = value;
|
|
419
|
+
for (const key of Object.keys(obj)) {
|
|
420
|
+
const v = obj[key];
|
|
421
|
+
if (typeof v === "string") obj[key] = applyVars(v, entries);
|
|
422
|
+
else replaceInPlace(v, entries);
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
/** Split-join replacement — matches the algorithm in reset.ts:120 and avoids regex-escaping `$$`. */
|
|
427
|
+
function applyVars(str, entries) {
|
|
428
|
+
let out = str;
|
|
429
|
+
for (const [placeholder, value] of entries) {
|
|
430
|
+
if (!value) continue;
|
|
431
|
+
if (out.includes(placeholder)) out = out.split(placeholder).join(value);
|
|
432
|
+
}
|
|
433
|
+
return out;
|
|
434
|
+
}
|
|
435
|
+
//#endregion
|
|
368
436
|
//#region src/rules/model-provider.ts
|
|
369
437
|
var _ModelProviderRule;
|
|
370
438
|
let ModelProviderRule = class ModelProviderRule extends DiagnoseRule {
|
|
@@ -894,7 +962,7 @@ function findLeftoverDirs(extensionsDir) {
|
|
|
894
962
|
}
|
|
895
963
|
let CleanupInstallBackupDirsRule = class CleanupInstallBackupDirsRule extends DiagnoseRule {
|
|
896
964
|
validate(ctx) {
|
|
897
|
-
const configPath = ctx
|
|
965
|
+
const { configPath } = ctx;
|
|
898
966
|
if (!configPath) return { pass: true };
|
|
899
967
|
const dirs = findLeftoverDirs(resolveExtensionsDir(configPath));
|
|
900
968
|
if (dirs.length === 0) return { pass: true };
|
|
@@ -904,7 +972,7 @@ let CleanupInstallBackupDirsRule = class CleanupInstallBackupDirsRule extends Di
|
|
|
904
972
|
};
|
|
905
973
|
}
|
|
906
974
|
repair(ctx) {
|
|
907
|
-
const configPath = ctx
|
|
975
|
+
const { configPath } = ctx;
|
|
908
976
|
if (!configPath) return;
|
|
909
977
|
const dirs = findLeftoverDirs(resolveExtensionsDir(configPath));
|
|
910
978
|
const failures = [];
|
|
@@ -1254,6 +1322,54 @@ const SECRETS_FILE_PATH = "/home/gem/workspace/.force/openclaw/miaoda-openclaw-s
|
|
|
1254
1322
|
const CONFIG_PATH = `${WORKSPACE_DIR}/openclaw.json`;
|
|
1255
1323
|
//#endregion
|
|
1256
1324
|
//#region src/logger.ts
|
|
1325
|
+
/**
|
|
1326
|
+
* Shared CLI log file. Every log line the CLI emits — whether through
|
|
1327
|
+
* `console.error` (rules, helpers, errors) or through the per-task
|
|
1328
|
+
* `makeLogger` (reset worker) — is tee'd here so operators have a single
|
|
1329
|
+
* file to tail when diagnosing a sandbox.
|
|
1330
|
+
*
|
|
1331
|
+
* `/tmp` is ephemeral on sandbox restart; we rely on that for rotation
|
|
1332
|
+
* (no size-based rotation implemented).
|
|
1333
|
+
*/
|
|
1334
|
+
const CLI_LOG_FILE = "/tmp/openclaw-diagnose/cli.log";
|
|
1335
|
+
/** Append one line to the shared cli.log. Swallows any fs error —
|
|
1336
|
+
* logging must never break the business flow. */
|
|
1337
|
+
function appendCliLog(line) {
|
|
1338
|
+
try {
|
|
1339
|
+
const dir = node_path.default.dirname(CLI_LOG_FILE);
|
|
1340
|
+
if (!node_fs.default.existsSync(dir)) node_fs.default.mkdirSync(dir, { recursive: true });
|
|
1341
|
+
node_fs.default.appendFileSync(CLI_LOG_FILE, line);
|
|
1342
|
+
} catch {}
|
|
1343
|
+
}
|
|
1344
|
+
let stderrMirrorInstalled = false;
|
|
1345
|
+
/**
|
|
1346
|
+
* Install a process-wide `console.error` interceptor that mirrors each
|
|
1347
|
+
* line to BOTH the original stderr AND cli.log. Call once at CLI entry
|
|
1348
|
+
* before any subcommand dispatch; idempotent.
|
|
1349
|
+
*
|
|
1350
|
+
* Why console.error and not console.log: the CLI's stdout carries the
|
|
1351
|
+
* structured JSON result protocol consumed by sandbox_console and other
|
|
1352
|
+
* callers — any log line on stdout would corrupt JSON parsing. Rules,
|
|
1353
|
+
* helpers, and error paths therefore must route debug output through
|
|
1354
|
+
* console.error (stderr).
|
|
1355
|
+
*/
|
|
1356
|
+
function installStderrMirror() {
|
|
1357
|
+
if (stderrMirrorInstalled) return;
|
|
1358
|
+
stderrMirrorInstalled = true;
|
|
1359
|
+
const original = console.error.bind(console);
|
|
1360
|
+
console.error = (...args) => {
|
|
1361
|
+
original(...args);
|
|
1362
|
+
const body = args.map((a) => typeof a === "string" ? a : safeStringify(a)).join(" ");
|
|
1363
|
+
appendCliLog(`[${(/* @__PURE__ */ new Date()).toISOString()}] ${body}\n`);
|
|
1364
|
+
};
|
|
1365
|
+
}
|
|
1366
|
+
function safeStringify(v) {
|
|
1367
|
+
try {
|
|
1368
|
+
return JSON.stringify(v);
|
|
1369
|
+
} catch {
|
|
1370
|
+
return String(v);
|
|
1371
|
+
}
|
|
1372
|
+
}
|
|
1257
1373
|
function makeLogger(logFile) {
|
|
1258
1374
|
try {
|
|
1259
1375
|
const dir = node_path.default.dirname(logFile);
|
|
@@ -1264,9 +1380,45 @@ function makeLogger(logFile) {
|
|
|
1264
1380
|
try {
|
|
1265
1381
|
node_fs.default.appendFileSync(logFile, line);
|
|
1266
1382
|
} catch {}
|
|
1383
|
+
appendCliLog(line);
|
|
1267
1384
|
};
|
|
1268
1385
|
}
|
|
1269
1386
|
//#endregion
|
|
1387
|
+
//#region src/fs-utils.ts
|
|
1388
|
+
/**
|
|
1389
|
+
* Rename src → dst, falling back to `mv` (which handles cross-device copy)
|
|
1390
|
+
* when the kernel returns EXDEV.
|
|
1391
|
+
*
|
|
1392
|
+
* Sandbox filesystems can put sibling paths on different "devices" from
|
|
1393
|
+
* rename(2)'s point of view: bind mounts, overlayfs copy-up, and
|
|
1394
|
+
* mount-point children inside a single directory all trip EXDEV. Seen in
|
|
1395
|
+
* production when reset's atomic swap did
|
|
1396
|
+
* /home/gem/.npm-global/lib/node_modules/openclaw → openclaw.bak
|
|
1397
|
+
* and the openclaw subdir was a bind-mounted volume.
|
|
1398
|
+
*
|
|
1399
|
+
* Behavior:
|
|
1400
|
+
* - Happy path hits rename(2) — atomic, single syscall, microseconds.
|
|
1401
|
+
* - EXDEV path shells out to `mv`, which does rename() then copy+unlink
|
|
1402
|
+
* on failure. Non-atomic but correct; callers already have rollback
|
|
1403
|
+
* logic (install-openclaw restores from .bak) so loss of atomicity
|
|
1404
|
+
* only matters if the process dies mid-copy, and that's survivable.
|
|
1405
|
+
* - Any other error (ENOENT, EACCES, EBUSY...) rethrows as-is so callers
|
|
1406
|
+
* see the real problem instead of a misleading `mv` fallback failure.
|
|
1407
|
+
*/
|
|
1408
|
+
function moveSafe(src, dst) {
|
|
1409
|
+
try {
|
|
1410
|
+
node_fs.default.renameSync(src, dst);
|
|
1411
|
+
} catch (e) {
|
|
1412
|
+
if (e?.code !== "EXDEV") throw e;
|
|
1413
|
+
(0, node_child_process.execSync)(`mv ${shellQuote(src)} ${shellQuote(dst)}`, { stdio: "ignore" });
|
|
1414
|
+
}
|
|
1415
|
+
}
|
|
1416
|
+
/** POSIX single-quote shell escape. Paths with embedded quotes are rare but
|
|
1417
|
+
* the token-file path conventions in sandboxes don't guarantee cleanliness. */
|
|
1418
|
+
function shellQuote(s) {
|
|
1419
|
+
return `'${s.replace(/'/g, `'\\''`)}'`;
|
|
1420
|
+
}
|
|
1421
|
+
//#endregion
|
|
1270
1422
|
//#region src/reset-async.ts
|
|
1271
1423
|
/**
|
|
1272
1424
|
* Start an async reset task: spawn a detached child process and return the taskId.
|
|
@@ -1289,7 +1441,7 @@ function startAsyncReset(ctxBase64) {
|
|
|
1289
1441
|
const dir = node_path.default.dirname(resultFile);
|
|
1290
1442
|
if (!node_fs.default.existsSync(dir)) node_fs.default.mkdirSync(dir, { recursive: true });
|
|
1291
1443
|
node_fs.default.writeFileSync(tmpPath, JSON.stringify(initial), "utf-8");
|
|
1292
|
-
|
|
1444
|
+
moveSafe(tmpPath, resultFile);
|
|
1293
1445
|
const child = (0, node_child_process.spawn)(process.execPath, [
|
|
1294
1446
|
process.argv[1],
|
|
1295
1447
|
"reset",
|
|
@@ -1313,7 +1465,7 @@ function startAsyncReset(ctxBase64) {
|
|
|
1313
1465
|
};
|
|
1314
1466
|
const errTmpPath = resultFile + ".tmp";
|
|
1315
1467
|
node_fs.default.writeFileSync(errTmpPath, JSON.stringify(failResult));
|
|
1316
|
-
|
|
1468
|
+
moveSafe(errTmpPath, resultFile);
|
|
1317
1469
|
});
|
|
1318
1470
|
child.unref();
|
|
1319
1471
|
log(`spawned worker pid=${child.pid}`);
|
|
@@ -1321,10 +1473,16 @@ function startAsyncReset(ctxBase64) {
|
|
|
1321
1473
|
}
|
|
1322
1474
|
//#endregion
|
|
1323
1475
|
//#region src/oss/fetchManifest.ts
|
|
1476
|
+
const MANIFEST_PREFIX = "builtin/manifests/openclaw/recommended/";
|
|
1477
|
+
const MANIFEST_SUFFIX = ".json";
|
|
1324
1478
|
async function fetchManifest(ossFileMap, tag) {
|
|
1325
|
-
const key =
|
|
1479
|
+
const key = `${MANIFEST_PREFIX}${tag}${MANIFEST_SUFFIX}`;
|
|
1326
1480
|
const url = ossFileMap[key];
|
|
1327
|
-
if (!url)
|
|
1481
|
+
if (!url) {
|
|
1482
|
+
const available = Object.keys(ossFileMap).filter((k) => k.startsWith(MANIFEST_PREFIX) && k.endsWith(MANIFEST_SUFFIX)).map((k) => k.slice(39, -5));
|
|
1483
|
+
const availStr = available.length ? available.join(", ") : "(none)";
|
|
1484
|
+
throw new Error(`manifest signed URL missing for tag "${tag}" (key ${key}). Available tags in ossFileMap: ${availStr}. Either pass an available tag or update the studio_server TCC openclaw_upgrade_config supported_versions.`);
|
|
1485
|
+
}
|
|
1328
1486
|
const res = await fetch(url);
|
|
1329
1487
|
if (!res.ok) throw new Error(`fetch manifest failed: HTTP ${res.status} ${res.statusText}`);
|
|
1330
1488
|
return await res.json();
|
|
@@ -1362,7 +1520,7 @@ async function downloadWithCache(pkg, ossFileMap, opts = {}) {
|
|
|
1362
1520
|
console.error(`⚠ [downloadWithCache] INTEGRITY BYPASS for ${pkg.ossKey}: expected ${expected.slice(0, 12)}… got ${actual.slice(0, 12)}… — ${sourceLabel}. DO NOT use this flag in production.`);
|
|
1363
1521
|
} else throw new Error(`integrity mismatch for ${pkg.ossKey}: expected ${expected} got ${actual}`);
|
|
1364
1522
|
}
|
|
1365
|
-
|
|
1523
|
+
moveSafe(tmpFile, destFile);
|
|
1366
1524
|
return destFile;
|
|
1367
1525
|
} catch (e) {
|
|
1368
1526
|
try {
|
|
@@ -1385,7 +1543,7 @@ async function installOpenclaw(openclawTag, ossFileMap, opts = {}) {
|
|
|
1385
1543
|
force: true
|
|
1386
1544
|
});
|
|
1387
1545
|
const hadExisting = node_fs.default.existsSync(targetDir);
|
|
1388
|
-
if (hadExisting)
|
|
1546
|
+
if (hadExisting) moveSafe(targetDir, bakDir);
|
|
1389
1547
|
try {
|
|
1390
1548
|
node_fs.default.mkdirSync(targetDir, { recursive: true });
|
|
1391
1549
|
(0, node_child_process.execSync)(`tar -xzf '${tarball}' -C '${targetDir}' --strip-components=1`, { stdio: "ignore" });
|
|
@@ -1397,7 +1555,7 @@ async function installOpenclaw(openclawTag, ossFileMap, opts = {}) {
|
|
|
1397
1555
|
force: true
|
|
1398
1556
|
});
|
|
1399
1557
|
} catch {}
|
|
1400
|
-
if (hadExisting && node_fs.default.existsSync(bakDir))
|
|
1558
|
+
if (hadExisting && node_fs.default.existsSync(bakDir)) moveSafe(bakDir, targetDir);
|
|
1401
1559
|
throw e;
|
|
1402
1560
|
}
|
|
1403
1561
|
if (node_fs.default.existsSync(bakDir)) node_fs.default.rmSync(bakDir, {
|
|
@@ -1472,7 +1630,7 @@ function updatePluginInstalls(configPath, installedPkgs) {
|
|
|
1472
1630
|
} else skipped++;
|
|
1473
1631
|
const tmpPath = configPath + ".installs-tmp";
|
|
1474
1632
|
node_fs.default.writeFileSync(tmpPath, JSON.stringify(config, null, 2), "utf-8");
|
|
1475
|
-
|
|
1633
|
+
moveSafe(tmpPath, configPath);
|
|
1476
1634
|
console.error(`[install-extension] plugins.installs updated: ${updated} entry(ies) in ${configPath}` + (skipped > 0 ? ` (${skipped} package(s) without installMetadata skipped)` : ""));
|
|
1477
1635
|
}
|
|
1478
1636
|
function installOne(pkg, tarball, homeBase) {
|
|
@@ -1498,8 +1656,8 @@ function installOne(pkg, tarball, homeBase) {
|
|
|
1498
1656
|
throw e;
|
|
1499
1657
|
}
|
|
1500
1658
|
const hadOld = node_fs.default.existsSync(destDir);
|
|
1501
|
-
if (hadOld)
|
|
1502
|
-
|
|
1659
|
+
if (hadOld) moveSafe(destDir, oldDir);
|
|
1660
|
+
moveSafe(stagingDir, destDir);
|
|
1503
1661
|
if (hadOld && node_fs.default.existsSync(oldDir)) node_fs.default.rmSync(oldDir, {
|
|
1504
1662
|
recursive: true,
|
|
1505
1663
|
force: true
|
|
@@ -1566,7 +1724,7 @@ function writeResultFile(resultFile, result) {
|
|
|
1566
1724
|
if (!node_fs.default.existsSync(dir)) node_fs.default.mkdirSync(dir, { recursive: true });
|
|
1567
1725
|
const tmpPath = resultFile + ".tmp";
|
|
1568
1726
|
node_fs.default.writeFileSync(tmpPath, JSON.stringify(result), "utf-8");
|
|
1569
|
-
|
|
1727
|
+
moveSafe(tmpPath, resultFile);
|
|
1570
1728
|
}
|
|
1571
1729
|
function updateProgress(resultFile, step, startedAt) {
|
|
1572
1730
|
writeResultFile(resultFile, {
|
|
@@ -1878,7 +2036,8 @@ async function runReset(input, taskId, resultFile) {
|
|
|
1878
2036
|
process.exit(1);
|
|
1879
2037
|
}
|
|
1880
2038
|
let openclawTag;
|
|
1881
|
-
|
|
2039
|
+
if (resetData.openclawTag) openclawTag = resetData.openclawTag;
|
|
2040
|
+
else try {
|
|
1882
2041
|
openclawTag = getOpenclawTagFromOssFileMap(ossFileMap);
|
|
1883
2042
|
} catch (e) {
|
|
1884
2043
|
const err = e.message;
|
|
@@ -2272,7 +2431,8 @@ function buildResetInput(raw, configPathOverride) {
|
|
|
2272
2431
|
secretsContent: ctx.secrets.secretsContent,
|
|
2273
2432
|
providerKeyContent: ctx.secrets.providerKeyContent,
|
|
2274
2433
|
coreBackup: ctx.reset.coreBackup,
|
|
2275
|
-
ossFileMap: ctx.install.ossFileMap
|
|
2434
|
+
ossFileMap: ctx.install.ossFileMap,
|
|
2435
|
+
openclawTag: ctx.install.openclawTag
|
|
2276
2436
|
}
|
|
2277
2437
|
};
|
|
2278
2438
|
}
|
|
@@ -2332,6 +2492,7 @@ function getMultiFlag(args, name) {
|
|
|
2332
2492
|
return args.filter((a) => a.startsWith(prefix)).map((a) => a.slice(prefix.length));
|
|
2333
2493
|
}
|
|
2334
2494
|
async function main() {
|
|
2495
|
+
installStderrMirror();
|
|
2335
2496
|
switch (mode) {
|
|
2336
2497
|
case "check":
|
|
2337
2498
|
case "repair": {
|
package/package.json
CHANGED