@lark-apaas/openclaw-scripts-diagnose-cli 0.1.14-alpha.1 → 0.1.14-alpha.2

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/dist/index.cjs +234 -169
  2. package/package.json +1 -1
package/dist/index.cjs CHANGED
@@ -52,7 +52,7 @@ node_assert = __toESM(node_assert);
52
52
  * it terse and parseable.
53
53
  */
54
54
  function getVersion() {
55
- return "0.1.14-alpha.1";
55
+ return "0.1.14-alpha.2";
56
56
  }
57
57
  //#endregion
58
58
  //#region src/rule-engine/base.ts
@@ -3513,6 +3513,161 @@ function extractScopedNameFromSpec$1(spec) {
3513
3513
  const at = spec.indexOf("@", 1);
3514
3514
  return at === -1 ? spec : spec.slice(0, at);
3515
3515
  }
3516
+ /**
3517
+ * Returns true if the installed feishu plugin is version-incompatible with
3518
+ * the current openclaw (or is a legacy plugin that must be replaced).
3519
+ * Used by the upgrade_lark_needed rule and the upgrade-lark pre-check gate.
3520
+ */
3521
+ function needsLarkUpgrade(ctx) {
3522
+ const cc = resolveCompatContext(ctx);
3523
+ if (!cc) return false;
3524
+ const { ocCur, recommendedOc, installed, isLegacy } = cc;
3525
+ if (isForkPlugin(installed)) return false;
3526
+ if (recommendedOc) return resolveUpgradeDirection(installed, ocCur, recommendedOc, isLegacy) === "lark";
3527
+ return isLegacy || !isVersionCompatible(installed, ocCur);
3528
+ }
3529
+ //#endregion
3530
+ //#region src/channels-probe.ts
3531
+ const CHANNEL_LINE_RE = /^-\s+Feishu\s+([^:]+):\s+(.+)$/;
3532
+ /**
3533
+ * Port of Python `_account_is_working` from the feishu-channel-success-rate skill.
3534
+ *
3535
+ * Strips colon-prefixed key:value bits (dm:, bot:, in:, out:, token:, allow:,
3536
+ * intents:, groups:, health:) and evaluates the canonical health formula.
3537
+ */
3538
+ function accountIsWorking(bits) {
3539
+ const bitTokens = /* @__PURE__ */ new Set();
3540
+ let hasError = false;
3541
+ let hasProbeFailed = false;
3542
+ for (const raw of bits) {
3543
+ const b = raw.trim();
3544
+ if (!b) continue;
3545
+ if (b.startsWith("error:")) {
3546
+ hasError = true;
3547
+ continue;
3548
+ }
3549
+ if (b === "probe failed") {
3550
+ hasProbeFailed = true;
3551
+ continue;
3552
+ }
3553
+ bitTokens.add(b.split(":")[0]);
3554
+ }
3555
+ if (!bitTokens.has("enabled") || !bitTokens.has("configured")) return false;
3556
+ if (bitTokens.has("works")) return true;
3557
+ if (bitTokens.has("running") && !hasError && !hasProbeFailed) return true;
3558
+ return false;
3559
+ }
3560
+ /**
3561
+ * Parse the raw stdout of `openclaw channels status --probe`.
3562
+ * Port of Python `extract_channels_probe` from the feishu-channel-success-rate skill.
3563
+ */
3564
+ function parseChannelsProbeOutput(text) {
3565
+ const gatewayReachable = text.includes("Gateway reachable");
3566
+ const accounts = [];
3567
+ let anyAccountWorking = false;
3568
+ for (const line of text.split("\n")) {
3569
+ const m = CHANNEL_LINE_RE.exec(line.trim());
3570
+ if (!m) continue;
3571
+ const [, acct, rest] = m;
3572
+ const bits = rest.split(",").map((b) => b.trim());
3573
+ const isWorking = accountIsWorking(bits);
3574
+ if (isWorking) anyAccountWorking = true;
3575
+ accounts.push({
3576
+ id: acct.trim(),
3577
+ bits,
3578
+ isWorking,
3579
+ raw: line.trim()
3580
+ });
3581
+ }
3582
+ return {
3583
+ gatewayReachable,
3584
+ accounts,
3585
+ anyAccountWorking
3586
+ };
3587
+ }
3588
+ /**
3589
+ * Run `openclaw channels status --probe` and return a structured result.
3590
+ *
3591
+ * The command may exit non-zero when some bot accounts fail their probe — that
3592
+ * is still useful output. We therefore try to parse stdout even when the
3593
+ * process exits with a non-zero code, falling back to an unavailable result
3594
+ * only when there is genuinely no output to parse.
3595
+ *
3596
+ * @param timeoutMs Maximum wait time. Default is 60 s because v2026.4.x
3597
+ * lacks a per-request HTTP timeout and can block indefinitely.
3598
+ */
3599
+ function runChannelsProbe(timeoutMs = 6e4) {
3600
+ let stdout = "";
3601
+ let execError;
3602
+ try {
3603
+ stdout = (0, node_child_process.execSync)("openclaw channels status --probe", {
3604
+ encoding: "utf-8",
3605
+ timeout: timeoutMs,
3606
+ stdio: [
3607
+ "ignore",
3608
+ "pipe",
3609
+ "pipe"
3610
+ ]
3611
+ });
3612
+ } catch (e) {
3613
+ const err = e;
3614
+ stdout = err.stdout ?? "";
3615
+ execError = err.message;
3616
+ const stderrRaw = err.stderr;
3617
+ const stderr = (typeof stderrRaw === "string" ? stderrRaw : stderrRaw?.toString("utf-8") ?? "").trim();
3618
+ if (stderr) console.error(`channels-probe: stderr from CLI: ${stderr}`);
3619
+ }
3620
+ if (stdout.trim()) return {
3621
+ available: true,
3622
+ ...parseChannelsProbeOutput(stdout)
3623
+ };
3624
+ return {
3625
+ available: false,
3626
+ gatewayReachable: false,
3627
+ accounts: [],
3628
+ anyAccountWorking: false,
3629
+ error: execError ?? "no output from openclaw channels status --probe"
3630
+ };
3631
+ }
3632
+ //#endregion
3633
+ //#region src/rules/upgrade-lark-needed.ts
3634
+ /**
3635
+ * Detects the condition that warrants running `upgrade-lark`:
3636
+ * - feishu plugin version incompatible with current openclaw, AND
3637
+ * - channels are not working.
3638
+ *
3639
+ * Both conditions must be true simultaneously. If version is compatible or
3640
+ * channels are working, the rule passes (no action needed).
3641
+ *
3642
+ * profile: experimental — runs only in full sweep mode, not in standard doctor.
3643
+ * level: silent — telemetry/sweep-only, does not trigger page-level repair UI.
3644
+ */
3645
+ let UpgradeLarkNeededRule = class UpgradeLarkNeededRule extends DiagnoseRule {
3646
+ validate(ctx) {
3647
+ if (!needsLarkUpgrade(ctx)) return { pass: true };
3648
+ let anyAccountWorking = false;
3649
+ try {
3650
+ anyAccountWorking = runChannelsProbe(3e4).anyAccountWorking;
3651
+ } catch {
3652
+ return { pass: true };
3653
+ }
3654
+ if (anyAccountWorking) return { pass: true };
3655
+ return {
3656
+ pass: false,
3657
+ action: "upgrade_lark",
3658
+ message: "飞书插件版本不兼容且 channels 不可用,建议执行 upgrade-lark 命令升级飞书插件"
3659
+ };
3660
+ }
3661
+ };
3662
+ UpgradeLarkNeededRule = __decorate([Rule({
3663
+ key: "upgrade_lark_needed",
3664
+ description: "检测飞书插件版本不兼容且 channels 不可用,判断是否需要执行 upgrade-lark 升级",
3665
+ dependsOn: ["feishu_plugin_version_compat_lark"],
3666
+ repairMode: "check-only",
3667
+ level: "silent",
3668
+ profile: "experimental",
3669
+ usesVars: ["recommendedOpenclawTag"]
3670
+ })], UpgradeLarkNeededRule);
3516
3671
  //#endregion
3517
3672
  //#region src/rules/cleanup-install-backup-dirs.ts
3518
3673
  const DIR_PREFIX = ".openclaw-install-";
@@ -6236,23 +6391,6 @@ function reportError(params) {
6236
6391
  } catch {}
6237
6392
  }
6238
6393
  //#endregion
6239
- //#region ../../openclaw-slardar/lib/report-log.js
6240
- function reportLog(params) {
6241
- try {
6242
- const extra = mergeLogExtra(buildTelemetryFields(params), {
6243
- event: params.event,
6244
- phase: params.phase,
6245
- status: params.status,
6246
- ...params.extra
6247
- });
6248
- Slardar.sendLog?.({
6249
- content: params.message,
6250
- level: params.level ?? "info",
6251
- extra
6252
- });
6253
- } catch {}
6254
- }
6255
- //#endregion
6256
6394
  //#region src/install-cli.ts
6257
6395
  const LARK_CLI_NAME = "lark-cli";
6258
6396
  const AGENT_SKILLS_NAME = "agent-skills";
@@ -10149,109 +10287,6 @@ function finalize(results, aborted) {
10149
10287
  };
10150
10288
  }
10151
10289
  //#endregion
10152
- //#region src/channels-probe.ts
10153
- const CHANNEL_LINE_RE = /^-\s+Feishu\s+([^:]+):\s+(.+)$/;
10154
- /**
10155
- * Port of Python `_account_is_working` from the feishu-channel-success-rate skill.
10156
- *
10157
- * Strips colon-prefixed key:value bits (dm:, bot:, in:, out:, token:, allow:,
10158
- * intents:, groups:, health:) and evaluates the canonical health formula.
10159
- */
10160
- function accountIsWorking(bits) {
10161
- const bitTokens = /* @__PURE__ */ new Set();
10162
- let hasError = false;
10163
- let hasProbeFailed = false;
10164
- for (const raw of bits) {
10165
- const b = raw.trim();
10166
- if (!b) continue;
10167
- if (b.startsWith("error:")) {
10168
- hasError = true;
10169
- continue;
10170
- }
10171
- if (b === "probe failed") {
10172
- hasProbeFailed = true;
10173
- continue;
10174
- }
10175
- bitTokens.add(b.split(":")[0]);
10176
- }
10177
- if (!bitTokens.has("enabled") || !bitTokens.has("configured")) return false;
10178
- if (bitTokens.has("works")) return true;
10179
- if (bitTokens.has("running") && !hasError && !hasProbeFailed) return true;
10180
- return false;
10181
- }
10182
- /**
10183
- * Parse the raw stdout of `openclaw channels status --probe`.
10184
- * Port of Python `extract_channels_probe` from the feishu-channel-success-rate skill.
10185
- */
10186
- function parseChannelsProbeOutput(text) {
10187
- const gatewayReachable = text.includes("Gateway reachable");
10188
- const accounts = [];
10189
- let anyAccountWorking = false;
10190
- for (const line of text.split("\n")) {
10191
- const m = CHANNEL_LINE_RE.exec(line.trim());
10192
- if (!m) continue;
10193
- const [, acct, rest] = m;
10194
- const bits = rest.split(",").map((b) => b.trim());
10195
- const isWorking = accountIsWorking(bits);
10196
- if (isWorking) anyAccountWorking = true;
10197
- accounts.push({
10198
- id: acct.trim(),
10199
- bits,
10200
- isWorking,
10201
- raw: line.trim()
10202
- });
10203
- }
10204
- return {
10205
- gatewayReachable,
10206
- accounts,
10207
- anyAccountWorking
10208
- };
10209
- }
10210
- /**
10211
- * Run `openclaw channels status --probe` and return a structured result.
10212
- *
10213
- * The command may exit non-zero when some bot accounts fail their probe — that
10214
- * is still useful output. We therefore try to parse stdout even when the
10215
- * process exits with a non-zero code, falling back to an unavailable result
10216
- * only when there is genuinely no output to parse.
10217
- *
10218
- * @param timeoutMs Maximum wait time. Default is 60 s because v2026.4.x
10219
- * lacks a per-request HTTP timeout and can block indefinitely.
10220
- */
10221
- function runChannelsProbe(timeoutMs = 6e4) {
10222
- let stdout = "";
10223
- let execError;
10224
- try {
10225
- stdout = (0, node_child_process.execSync)("openclaw channels status --probe", {
10226
- encoding: "utf-8",
10227
- timeout: timeoutMs,
10228
- stdio: [
10229
- "ignore",
10230
- "pipe",
10231
- "pipe"
10232
- ]
10233
- });
10234
- } catch (e) {
10235
- const err = e;
10236
- stdout = err.stdout ?? "";
10237
- execError = err.message;
10238
- const stderrRaw = err.stderr;
10239
- const stderr = (typeof stderrRaw === "string" ? stderrRaw : stderrRaw?.toString("utf-8") ?? "").trim();
10240
- if (stderr) console.error(`channels-probe: stderr from CLI: ${stderr}`);
10241
- }
10242
- if (stdout.trim()) return {
10243
- available: true,
10244
- ...parseChannelsProbeOutput(stdout)
10245
- };
10246
- return {
10247
- available: false,
10248
- gatewayReachable: false,
10249
- accounts: [],
10250
- anyAccountWorking: false,
10251
- error: execError ?? "no output from openclaw channels status --probe"
10252
- };
10253
- }
10254
- //#endregion
10255
10290
  //#region src/innerapi/reportCliRun.ts
10256
10291
  /**
10257
10292
  * CLI-side client for studio_server's `openclaw.report_cli_run` inner
@@ -10331,7 +10366,7 @@ async function reportCliRun(opts) {
10331
10366
  //#region src/help.ts
10332
10367
  const BIN = "mclaw-diagnose";
10333
10368
  function versionBanner() {
10334
- return `v0.1.14-alpha.1`;
10369
+ return `v0.1.14-alpha.2`;
10335
10370
  }
10336
10371
  const COMMANDS = [
10337
10372
  {
@@ -10912,18 +10947,16 @@ function reportDoctorRunToSlardar(opts) {
10912
10947
  }
10913
10948
  });
10914
10949
  }
10915
- /** Read the tail of a file, up to maxBytes. Returns empty string on any error. */
10916
- function readFileTail(filePath, maxBytes = 4e3) {
10950
+ function readLogFile(filePath) {
10917
10951
  try {
10918
- const content = node_fs.default.readFileSync(filePath, "utf-8");
10919
- if (content.length <= maxBytes) return content;
10920
- return `...(truncated — showing last ${maxBytes} chars)...\n` + content.slice(-maxBytes);
10952
+ return node_fs.default.readFileSync(filePath, "utf-8");
10921
10953
  } catch {
10922
10954
  return "";
10923
10955
  }
10924
10956
  }
10925
10957
  function reportUpgradeLarkToSlardar(opts) {
10926
- console.error(`[slardar] upgrade-lark reportTask scene=${opts.scene ?? ""} success=${opts.success} exitCode=${opts.exitCode ?? ""} rollbackOk=${opts.rollbackOk ?? ""}`);
10958
+ console.error(`[slardar] upgrade_lark_run scene=${opts.scene ?? ""} success=${opts.success} exitCode=${opts.exitCode ?? ""} rollbackOk=${opts.rollbackOk ?? ""}`);
10959
+ const logContent = readLogFile(opts.logFile);
10927
10960
  reportTask({
10928
10961
  eventName: "upgrade_lark_run",
10929
10962
  durationMs: opts.durationMs,
@@ -10932,22 +10965,9 @@ function reportUpgradeLarkToSlardar(opts) {
10932
10965
  scene: opts.scene ?? "",
10933
10966
  exit_code: String(opts.exitCode ?? ""),
10934
10967
  rollback_ok: opts.rollbackOk != null ? String(opts.rollbackOk) : "",
10935
- validation_error: (opts.validationError ?? "").slice(0, 200),
10936
- error_msg: (opts.error ?? "").slice(0, 200)
10937
- }
10938
- });
10939
- const logContent = readFileTail(opts.logFile);
10940
- console.error(`[slardar] upgrade_lark_detail logFile=${opts.logFile} contentLength=${logContent.length} sendLogAvailable=${typeof Slardar.sendLog}`);
10941
- reportLog({
10942
- event: "upgrade_lark_detail",
10943
- message: logContent || "(no log content)",
10944
- level: opts.success ? "info" : "error",
10945
- extra: {
10946
- log_file: opts.logFile,
10947
- scene: opts.scene ?? "",
10948
- success: String(opts.success),
10949
- exit_code: String(opts.exitCode ?? ""),
10950
- rollback_ok: opts.rollbackOk != null ? String(opts.rollbackOk) : ""
10968
+ validation_error: opts.validationError ?? "",
10969
+ error_msg: opts.error ?? "",
10970
+ log_content: logContent
10951
10971
  }
10952
10972
  });
10953
10973
  }
@@ -11117,10 +11137,61 @@ function runUpgradeLark(opts) {
11117
11137
  log(` configPath : ${configPath}`);
11118
11138
  log(`${"=".repeat(60)}`);
11119
11139
  log("");
11120
- log("── [1/8] 升级前状态快照 ──────────────────────────────────");
11121
- log(`before-state: botCount=${countFeishuBots(configPath)}`);
11140
+ log("── [Pre-check A] channels probe(升级前)────────────────");
11141
+ const beforeChannels = probeChannels("before", log, 3e4);
11122
11142
  log("");
11123
- log("── [2/8] 文件备份 ────────────────────────────────────────");
11143
+ log("── [Pre-check B] 版本兼容预检 ───────────────────────────");
11144
+ let versionNeedsUpgrade = false;
11145
+ try {
11146
+ const rawConfig = node_fs.default.readFileSync(configPath, "utf-8");
11147
+ versionNeedsUpgrade = needsLarkUpgrade({
11148
+ config: loadJSON5().parse(rawConfig),
11149
+ configPath,
11150
+ vars: {},
11151
+ providerDeps: {
11152
+ usesMiaodaProvider: false,
11153
+ usesMiaodaSecretProvider: false
11154
+ }
11155
+ });
11156
+ log(` version-compat pre-check: ${versionNeedsUpgrade ? "NEEDS_UPGRADE" : "ok"}`);
11157
+ } catch (e) {
11158
+ log(` version-compat pre-check error: ${e.message} — treating as needs-upgrade`);
11159
+ versionNeedsUpgrade = true;
11160
+ }
11161
+ log("");
11162
+ log("── [Gate] 升级前置条件检查 ───────────────────────────────");
11163
+ log(` version needs upgrade : ${versionNeedsUpgrade}`);
11164
+ log(` channels working before: ${beforeChannels.anyAccountWorking}`);
11165
+ if (!versionNeedsUpgrade) {
11166
+ const reason = "version already compatible — upgrade not needed";
11167
+ log(` SKIP: ${reason}`);
11168
+ log(`${"=".repeat(60)}`);
11169
+ log("upgrade-lark skipped (pre-check gate)");
11170
+ log(`${"=".repeat(60)}`);
11171
+ return {
11172
+ ok: true,
11173
+ skipped: true,
11174
+ skipReason: reason,
11175
+ logFile
11176
+ };
11177
+ }
11178
+ if (beforeChannels.anyAccountWorking) {
11179
+ const reason = "channels are working — upgrade not needed (version mismatch but system is functional)";
11180
+ log(` SKIP: ${reason}`);
11181
+ log(`${"=".repeat(60)}`);
11182
+ log("upgrade-lark skipped (pre-check gate)");
11183
+ log(`${"=".repeat(60)}`);
11184
+ return {
11185
+ ok: true,
11186
+ skipped: true,
11187
+ skipReason: reason,
11188
+ logFile
11189
+ };
11190
+ }
11191
+ log(" PROCEED: version incompatible AND channels not working → running upgrade");
11192
+ log("");
11193
+ log("── [1/6] 文件备份 ────────────────────────────────────────");
11194
+ log(`before-state: botCount=${countFeishuBots(configPath)}`);
11124
11195
  const backup = backupFiles(fsOpts);
11125
11196
  if (!backup.ok) {
11126
11197
  log(`ERROR: ${backup.error}`);
@@ -11133,10 +11204,7 @@ function runUpgradeLark(opts) {
11133
11204
  log("backup: ok");
11134
11205
  logVersionSnapshot("before-versions", snapshotVersions(cwd, log), log);
11135
11206
  log("");
11136
- log("── [3/8] channels probe(升级前)────────────────────────");
11137
- const beforeChannels = probeChannels("before", log, 3e4);
11138
- log("");
11139
- log("── [4/8] 清理本地 openclaw shim ─────────────────────────");
11207
+ log("── [2/6] 清理本地 openclaw shim ─────────────────────────");
11140
11208
  const localOpenclawBin = node_path.default.join(cwd, "node_modules", ".bin", "openclaw");
11141
11209
  if (node_fs.default.existsSync(localOpenclawBin)) try {
11142
11210
  node_fs.default.rmSync(localOpenclawBin);
@@ -11146,7 +11214,7 @@ function runUpgradeLark(opts) {
11146
11214
  }
11147
11215
  else log(` skipped: ${localOpenclawBin} (not found)`);
11148
11216
  log("");
11149
- log("── [5/8] npx install (@larksuite/openclaw-lark-tools update) ──");
11217
+ log("── [3/6] npx install (@larksuite/openclaw-lark-tools update) ──");
11150
11218
  const npxResult = (0, node_child_process.spawnSync)("npx", [
11151
11219
  "-y",
11152
11220
  "@larksuite/openclaw-lark-tools",
@@ -11189,7 +11257,7 @@ function runUpgradeLark(opts) {
11189
11257
  };
11190
11258
  };
11191
11259
  log("");
11192
- log("── [6/8] 插件安装检查 + 版本兼容校验 ───────────────────");
11260
+ log("── [4/6] 插件安装检查 + 版本兼容校验 ───────────────────");
11193
11261
  const larkExtDir = node_path.default.join(cwd, "extensions", "openclaw-lark");
11194
11262
  const larkVersion = readPkgVersion(node_path.default.join(larkExtDir, "package.json"));
11195
11263
  log(` extensions/openclaw-lark: ${node_fs.default.existsSync(larkExtDir) ? "exists" : "missing"}, version=${larkVersion ?? "n/a"}`);
@@ -11217,25 +11285,22 @@ function runUpgradeLark(opts) {
11217
11285
  log(" version compat: ok");
11218
11286
  logVersionSnapshot("after-versions", snapshotVersions(cwd, log), log);
11219
11287
  log("");
11220
- log("── [7/8] channels probe(升级后)────────────────────────");
11288
+ log("── [5/6] channels probe(升级后)────────────────────────");
11221
11289
  if (!probeChannels("after", log, 3e4).anyAccountWorking) {
11222
- if (!beforeChannels.anyAccountWorking) {
11223
- log(" channels: not working before or after install — pre-existing issue, skipping rollback");
11224
- return {
11225
- ok: false,
11226
- error: "channels probe: no working account (pre-existing issue, not caused by install)",
11227
- validationError: "channels probe: no working account (pre-existing)",
11228
- stdout: npxStdout,
11229
- stderr: npxStderr,
11230
- exitCode: npxExitCode,
11231
- logFile
11232
- };
11233
- }
11234
- return doRollback("channels probe: no working account after install (was working before)");
11290
+ log(" channels: not working before or after install — pre-existing issue, skipping rollback");
11291
+ return {
11292
+ ok: false,
11293
+ error: "channels probe: no working account (pre-existing issue, not caused by install)",
11294
+ validationError: "channels probe: no working account (pre-existing)",
11295
+ stdout: npxStdout,
11296
+ stderr: npxStderr,
11297
+ exitCode: npxExitCode,
11298
+ logFile
11299
+ };
11235
11300
  }
11236
- log(" channels: ok");
11301
+ log(" channels: ok (recovered after install)");
11237
11302
  log("");
11238
- log("── [8/8] doctor --fix ────────────────────────────────────");
11303
+ log("── [6/6] doctor --fix ────────────────────────────────────");
11239
11304
  const fixArgs = ["doctor", "--fix"];
11240
11305
  if (opts.scene) fixArgs.push(`--scene=${opts.scene}`);
11241
11306
  const fixResult = (0, node_child_process.spawnSync)(process.execPath, [cliScript, ...fixArgs], {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lark-apaas/openclaw-scripts-diagnose-cli",
3
- "version": "0.1.14-alpha.1",
3
+ "version": "0.1.14-alpha.2",
4
4
  "description": "CLI for OpenClaw config diagnose and repair with JSON5 support",
5
5
  "main": "dist/index.cjs",
6
6
  "bin": {