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

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 +225 -695
  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.11";
56
56
  }
57
57
  //#endregion
58
58
  //#region src/rule-engine/base.ts
@@ -3347,6 +3347,7 @@ function resolveUpgradeDirection(installed, ocCur, recommendedOc, isLegacy) {
3347
3347
  /** 提取公共前置上下文;任何前置条件不满足时返回 null(规则 pass)。 */
3348
3348
  function resolveCompatContext(ctx) {
3349
3349
  const recommendedOc = ctx.vars.recommendedOpenclawTag;
3350
+ if (!recommendedOc) return null;
3350
3351
  const ocCur = getOcVersion();
3351
3352
  if (!ocCur) return null;
3352
3353
  const installed = getInstalledPlugin(ctx);
@@ -3367,7 +3368,6 @@ let FeishuPluginOpenclawUpgradeRule = class FeishuPluginOpenclawUpgradeRule exte
3367
3368
  validate(ctx) {
3368
3369
  const cc = resolveCompatContext(ctx);
3369
3370
  if (!cc) return { pass: true };
3370
- if (!cc.recommendedOc) return { pass: true };
3371
3371
  const { ocCur, recommendedOc, installed, isLegacy } = cc;
3372
3372
  if (isForkPlugin(installed)) return validateForkPlugin(installed, ocCur, recommendedOc);
3373
3373
  if (resolveUpgradeDirection(installed, ocCur, recommendedOc, isLegacy) !== "openclaw") return { pass: true };
@@ -3397,14 +3397,6 @@ let FeishuPluginLarkUpgradeRule = class FeishuPluginLarkUpgradeRule extends Diag
3397
3397
  if (!cc) return { pass: true };
3398
3398
  const { ocCur, recommendedOc, installed, isLegacy } = cc;
3399
3399
  if (isForkPlugin(installed)) return { pass: true };
3400
- if (!recommendedOc) {
3401
- if (isLegacy || !isVersionCompatible(installed, ocCur)) return {
3402
- pass: false,
3403
- action: "upgrade_lark",
3404
- message: `${buildCompatPrefix(installed, ocCur, isLegacy)};建议升级飞书插件至兼容版本`
3405
- };
3406
- return { pass: true };
3407
- }
3408
3400
  if (resolveUpgradeDirection(installed, ocCur, recommendedOc, isLegacy) !== "lark") return { pass: true };
3409
3401
  return {
3410
3402
  pass: false,
@@ -3658,6 +3650,117 @@ LarkCliMissingForInstalledLarkPluginRule = __decorate([Rule({
3658
3650
  usesVars: ["recommendedOpenclawTag"]
3659
3651
  })], LarkCliMissingForInstalledLarkPluginRule);
3660
3652
  //#endregion
3653
+ //#region src/rules/feishu-bot-channel-config.ts
3654
+ /**
3655
+ * Ensures each bot account's channel config is correct:
3656
+ * 1. `allowFrom` contains its own `creatorOpenID` from larkApps
3657
+ * 2. `appSecret` is either the canonical provider-ref or matches larkApps plaintext
3658
+ *
3659
+ * Covers both multi-account (channels.feishu.accounts) and single-account
3660
+ * (channels.feishu.appId + allowFrom at top level) layouts.
3661
+ */
3662
+ let FeishuBotChannelConfigRule = class FeishuBotChannelConfigRule extends DiagnoseRule {
3663
+ validate(ctx) {
3664
+ const larkApps = ctx.vars.larkApps;
3665
+ if (!larkApps || larkApps.length === 0) return { pass: true };
3666
+ const feishu = asRecord(getNestedMap(ctx.config, "channels", "feishu"));
3667
+ if (!feishu) return { pass: true };
3668
+ const issues = [];
3669
+ const accounts = asRecord(feishu.accounts);
3670
+ if (accounts) for (const [accountId, account] of Object.entries(accounts)) {
3671
+ const bot = asRecord(account);
3672
+ if (!bot) continue;
3673
+ const appId = bot.appId;
3674
+ if (typeof appId !== "string" || !appId.startsWith("cli_")) continue;
3675
+ const larkApp = larkApps.find((e) => e.larkAppID === appId);
3676
+ if (!larkApp) continue;
3677
+ this.checkBot(accountId, bot, larkApp, issues);
3678
+ }
3679
+ const singleAppId = feishu.appId;
3680
+ if (typeof singleAppId === "string" && singleAppId.startsWith("cli_") && !accounts) {
3681
+ const larkApp = larkApps.find((e) => e.larkAppID === singleAppId);
3682
+ if (larkApp) this.checkBot("feishu", feishu, larkApp, issues);
3683
+ }
3684
+ if (issues.length === 0) return { pass: true };
3685
+ return {
3686
+ pass: false,
3687
+ message: issues.join("; ")
3688
+ };
3689
+ }
3690
+ /** Check a single bot entry (either an account object or the feishu channel itself).
3691
+ * appSecret is validated based on its current type:
3692
+ * - object → must match canonical provider-ref
3693
+ * - string → must match larkApps plaintext
3694
+ */
3695
+ checkBot(label, bot, larkApp, issues) {
3696
+ const creatorOpenID = larkApp.creatorOpenID;
3697
+ const allowFrom = Array.isArray(bot.allowFrom) ? bot.allowFrom : [];
3698
+ if (typeof creatorOpenID === "string" && creatorOpenID !== "") {
3699
+ if (!allowFrom.includes(creatorOpenID)) issues.push(`${label} allowFrom missing creatorOpenID ${creatorOpenID.length > 8 ? creatorOpenID.slice(0, 4) + "***" + creatorOpenID.slice(-4) : "***"}`);
3700
+ } else if (allowFrom.length === 0) issues.push(`${label} allowFrom is empty (creatorOpenID unavailable, cannot auto-fix)`);
3701
+ const secret = bot.appSecret;
3702
+ if (typeof secret === "object" && secret !== null && !Array.isArray(secret)) {
3703
+ if (!matchMap(secret, DEFAULT_FEISHU_APP_SECRET)) issues.push(`${label} appSecret is a provider-ref but not the canonical one`);
3704
+ } else if (typeof secret === "string") {
3705
+ if (secret !== larkApp.appSecret) issues.push(`${label} appSecret plaintext mismatch`);
3706
+ } else issues.push(`${label} appSecret has unexpected type ${typeof secret}`);
3707
+ }
3708
+ repair(ctx) {
3709
+ const larkApps = ctx.vars.larkApps;
3710
+ if (!larkApps || larkApps.length === 0) return;
3711
+ const feishu = asRecord(getNestedMap(ctx.config, "channels", "feishu"));
3712
+ if (!feishu) return;
3713
+ const accounts = asRecord(feishu.accounts);
3714
+ if (accounts) for (const [, account] of Object.entries(accounts)) {
3715
+ const bot = asRecord(account);
3716
+ if (!bot) continue;
3717
+ const appId = bot.appId;
3718
+ if (typeof appId !== "string" || !appId.startsWith("cli_")) continue;
3719
+ const larkApp = larkApps.find((e) => e.larkAppID === appId);
3720
+ if (!larkApp) continue;
3721
+ this.fixBot(bot, larkApp);
3722
+ }
3723
+ const singleAppId = feishu.appId;
3724
+ if (typeof singleAppId === "string" && singleAppId.startsWith("cli_") && !accounts) {
3725
+ const larkApp = larkApps.find((e) => e.larkAppID === singleAppId);
3726
+ if (larkApp) this.fixBot(feishu, larkApp);
3727
+ }
3728
+ }
3729
+ /** Fix a single bot entry in-place.
3730
+ * appSecret is repaired based on its current type:
3731
+ * - object → fix to canonical provider-ref
3732
+ * - string → fix to larkApps plaintext
3733
+ */
3734
+ fixBot(bot, larkApp) {
3735
+ const creatorOpenID = larkApp.creatorOpenID;
3736
+ if (typeof creatorOpenID === "string" && creatorOpenID !== "") {
3737
+ const allowFrom = Array.isArray(bot.allowFrom) ? [...bot.allowFrom] : [];
3738
+ if (!allowFrom.includes(creatorOpenID)) {
3739
+ allowFrom.push(creatorOpenID);
3740
+ bot.allowFrom = allowFrom;
3741
+ }
3742
+ }
3743
+ const secret = bot.appSecret;
3744
+ if (typeof secret === "object" && secret !== null && !Array.isArray(secret)) {
3745
+ if (!matchMap(secret, DEFAULT_FEISHU_APP_SECRET)) bot.appSecret = { ...DEFAULT_FEISHU_APP_SECRET };
3746
+ } else if (typeof secret === "string") {
3747
+ if (secret !== larkApp.appSecret) bot.appSecret = larkApp.appSecret;
3748
+ }
3749
+ }
3750
+ };
3751
+ FeishuBotChannelConfigRule = __decorate([Rule({
3752
+ key: "feishu_bot_channel_config",
3753
+ description: "确保飞书配置中 bot 账号的 allowFrom 包含其创建者 openID 且 appSecret 值正确",
3754
+ dependsOn: [
3755
+ "config_syntax_check",
3756
+ "feishu_default_account",
3757
+ "feishu_bot_id"
3758
+ ],
3759
+ repairMode: "standard",
3760
+ usesVars: ["larkApps"],
3761
+ level: "critical"
3762
+ })], FeishuBotChannelConfigRule);
3763
+ //#endregion
3661
3764
  //#region src/check.ts
3662
3765
  /** Telemetry-aware entry: returns both the legacy CheckResult (for stdout)
3663
3766
  * AND a DoctorReport-shape payload (for `openclaw.report_cli_run`). The
@@ -4121,9 +4224,6 @@ const PROVIDER_FILE_PATH = "/home/gem/workspace/.force/openclaw/miaoda-provider-
4121
4224
  const SECRETS_FILE_PATH = "/home/gem/workspace/.force/openclaw/miaoda-openclaw-secrets.json";
4122
4225
  /** Absolute path to the openclaw config JSON. */
4123
4226
  const CONFIG_PATH = `${WORKSPACE_DIR}/openclaw.json`;
4124
- function upgradeLarkLogFile(runId) {
4125
- return `${DIAGNOSE_DIR}/upgrade-lark-${(/* @__PURE__ */ new Date()).toISOString().slice(0, 19).replace(/:/g, "-")}-${runId.slice(0, 8)}.log`;
4126
- }
4127
4227
  //#endregion
4128
4228
  //#region src/run-log.ts
4129
4229
  let currentRunContext;
@@ -4260,9 +4360,10 @@ function makeLogger(logFile) {
4260
4360
  /**
4261
4361
  * Start an async reset task: spawn a detached child process and return the taskId.
4262
4362
  *
4263
- * The child process runs: node cli.js reset --worker --task-id=xxx --ctx=base64
4363
+ * The child process runs: node cli.js reset --worker --task-id=xxx
4364
+ * The worker fetches ctx from innerApi itself — no --ctx passthrough.
4264
4365
  */
4265
- function startAsyncReset(ctxBase64) {
4366
+ function startAsyncReset() {
4266
4367
  const taskId = (0, node_crypto.randomUUID)();
4267
4368
  const resultFile = resetResultFile(taskId);
4268
4369
  const log = makeLogger(resetLogFile(taskId));
@@ -4286,8 +4387,7 @@ function startAsyncReset(ctxBase64) {
4286
4387
  process.argv[1],
4287
4388
  "reset",
4288
4389
  "--worker",
4289
- `--task-id=${taskId}`,
4290
- `--ctx=${ctxBase64}`
4390
+ `--task-id=${taskId}`
4291
4391
  ], {
4292
4392
  detached: true,
4293
4393
  stdio: "ignore",
@@ -6236,23 +6336,6 @@ function reportError(params) {
6236
6336
  } catch {}
6237
6337
  }
6238
6338
  //#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
6339
  //#region src/install-cli.ts
6257
6340
  const LARK_CLI_NAME = "lark-cli";
6258
6341
  const AGENT_SKILLS_NAME = "agent-skills";
@@ -6818,6 +6901,60 @@ function mergeCoreBackupAndOrigins(configPath, vars, resetData, log) {
6818
6901
  log(`allowedOrigins: added ${added.length} (${JSON.stringify(added)}), total now ${mergedOrigins.length}`);
6819
6902
  }
6820
6903
  /**
6904
+ * Fix bot account allowFrom and appSecret using larkApps from innerApi.
6905
+ *
6906
+ * For each bot account (key starts with `bot-cli_`):
6907
+ * - allowFrom must contain the bot's own creatorOpenID from larkApps
6908
+ * - appSecret must be either the canonical provider-ref or match larkApps plaintext
6909
+ *
6910
+ * Runs after mergeCoreBackupAndOrigins so it operates on the final config state.
6911
+ */
6912
+ function fixBotChannelConfig(configPath, larkApps, log) {
6913
+ if (!larkApps || larkApps.length === 0) {
6914
+ log("no larkApps data, skip bot channel config fix");
6915
+ return;
6916
+ }
6917
+ const config = loadJSON5().parse(node_fs.default.readFileSync(configPath, "utf-8"));
6918
+ const accounts = asRecord(getNestedMap(config, "channels", "feishu")?.accounts);
6919
+ if (!accounts) {
6920
+ log("no feishu accounts in config, skip bot channel config fix");
6921
+ return;
6922
+ }
6923
+ let fixCount = 0;
6924
+ for (const [, account] of Object.entries(accounts)) {
6925
+ const bot = asRecord(account);
6926
+ if (!bot) continue;
6927
+ const appId = bot.appId;
6928
+ if (typeof appId !== "string" || !appId.startsWith("cli_")) continue;
6929
+ const larkApp = larkApps.find((e) => e.larkAppID === appId);
6930
+ if (!larkApp) continue;
6931
+ const creatorOpenID = larkApp.creatorOpenID;
6932
+ if (typeof creatorOpenID === "string" && creatorOpenID !== "") {
6933
+ const allowFrom = Array.isArray(bot.allowFrom) ? [...bot.allowFrom] : [];
6934
+ if (!allowFrom.includes(creatorOpenID)) {
6935
+ allowFrom.push(creatorOpenID);
6936
+ bot.allowFrom = allowFrom;
6937
+ fixCount++;
6938
+ }
6939
+ }
6940
+ const secret = bot.appSecret;
6941
+ let needsFix = false;
6942
+ if (typeof secret === "object" && secret !== null && !Array.isArray(secret)) {
6943
+ if (!matchMap(secret, DEFAULT_FEISHU_APP_SECRET)) needsFix = true;
6944
+ } else if (typeof secret === "string") {
6945
+ if (secret !== larkApp.appSecret) needsFix = true;
6946
+ } else needsFix = true;
6947
+ if (needsFix) {
6948
+ bot.appSecret = { ...DEFAULT_FEISHU_APP_SECRET };
6949
+ fixCount++;
6950
+ }
6951
+ }
6952
+ if (fixCount > 0) {
6953
+ node_fs.default.writeFileSync(configPath, JSON.stringify(config, null, 2), "utf-8");
6954
+ log(`fixed ${fixCount} bot channel config issue(s) (allowFrom/appSecret)`);
6955
+ } else log("bot channel config ok, no fixes needed");
6956
+ }
6957
+ /**
6821
6958
  * Step 7: Verify startup scripts landed in configDir/scripts/.
6822
6959
  *
6823
6960
  * Scripts are extracted directly to configDir/scripts/ during stageTemplate —
@@ -6962,6 +7099,7 @@ async function runReset(input, taskId, resultFile) {
6962
7099
  await step5InstallOpenclaw(openclawTag, ossFileMap, log);
6963
7100
  step(6);
6964
7101
  mergeCoreBackupAndOrigins(configPath, vars, resetData, log);
7102
+ fixBotChannelConfig(configPath, vars.larkApps, log);
6965
7103
  step(7);
6966
7104
  verifyStartupScripts(configDir, log);
6967
7105
  step(8);
@@ -7760,7 +7898,8 @@ function normalizeCtx(raw) {
7760
7898
  reset: {
7761
7899
  templateVars: r.reset.templateVars ?? {},
7762
7900
  coreBackup: r.reset.coreBackup
7763
- }
7901
+ },
7902
+ larkApps: Array.isArray(r.larkApps) ? r.larkApps : []
7764
7903
  };
7765
7904
  }
7766
7905
  const vars = r.vars ?? {};
@@ -7785,7 +7924,8 @@ function normalizeCtx(raw) {
7785
7924
  reset: {
7786
7925
  templateVars: resetData.templateVars ?? {},
7787
7926
  coreBackup: resetData.coreBackup
7788
- }
7927
+ },
7928
+ larkApps: Array.isArray(r.larkApps) ? r.larkApps : []
7789
7929
  };
7790
7930
  }
7791
7931
  function fillApp(src) {
@@ -7850,7 +7990,8 @@ function buildCheckInput(raw, configPathOverride) {
7850
7990
  providerFilePath: PROVIDER_FILE_PATH,
7851
7991
  secretsFilePath: SECRETS_FILE_PATH,
7852
7992
  templateVars: ctx.app.templateVars,
7853
- recommendedOpenclawTag: ctx.app.recommendedOpenclawTag
7993
+ recommendedOpenclawTag: ctx.app.recommendedOpenclawTag,
7994
+ larkApps: ctx.larkApps
7854
7995
  },
7855
7996
  templateVars: ctx.app.templateVars
7856
7997
  };
@@ -7882,7 +8023,8 @@ function buildRepairInput(raw, configPathOverride) {
7882
8023
  providerFilePath: PROVIDER_FILE_PATH,
7883
8024
  secretsFilePath: SECRETS_FILE_PATH,
7884
8025
  templateVars: ctx.app.templateVars,
7885
- recommendedOpenclawTag: ctx.app.recommendedOpenclawTag
8026
+ recommendedOpenclawTag: ctx.app.recommendedOpenclawTag,
8027
+ larkApps: ctx.larkApps
7886
8028
  },
7887
8029
  repairData: {
7888
8030
  secretsContent: ctx.secrets.secretsContent,
@@ -7918,7 +8060,8 @@ function buildResetInput(raw, configPathOverride) {
7918
8060
  providerFilePath: PROVIDER_FILE_PATH,
7919
8061
  secretsFilePath: SECRETS_FILE_PATH,
7920
8062
  templateVars: ctx.app.templateVars,
7921
- recommendedOpenclawTag: ctx.app.recommendedOpenclawTag
8063
+ recommendedOpenclawTag: ctx.app.recommendedOpenclawTag,
8064
+ larkApps: ctx.larkApps
7922
8065
  },
7923
8066
  resetData: {
7924
8067
  templateVars: ctx.reset.templateVars,
@@ -10149,109 +10292,6 @@ function finalize(results, aborted) {
10149
10292
  };
10150
10293
  }
10151
10294
  //#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
10295
  //#region src/innerapi/reportCliRun.ts
10256
10296
  /**
10257
10297
  * CLI-side client for studio_server's `openclaw.report_cli_run` inner
@@ -10331,7 +10371,7 @@ async function reportCliRun(opts) {
10331
10371
  //#region src/help.ts
10332
10372
  const BIN = "mclaw-diagnose";
10333
10373
  function versionBanner() {
10334
- return `v0.1.14-alpha.1`;
10374
+ return `v0.1.14-alpha.11`;
10335
10375
  }
10336
10376
  const COMMANDS = [
10337
10377
  {
@@ -10435,16 +10475,12 @@ EXIT CODES
10435
10475
  hidden: true,
10436
10476
  summary: "Run rule-engine check only",
10437
10477
  help: `USAGE
10438
- ${BIN} check [--ctx=<base64>]
10478
+ ${BIN} check
10439
10479
 
10440
10480
  DESCRIPTION
10441
10481
  Runs the rule engine against the sandbox's current openclaw config and
10442
- returns { failedRules }. Used by sandbox_console's push-style callers
10443
- that already own the ctx — end-users should prefer \`doctor\`.
10444
-
10445
- OPTIONS
10446
- --ctx=<base64> Opaque ctx JSON (base64). When absent, fetched from
10447
- innerapi (same path as doctor).
10482
+ returns { failedRules }. Ctx is fetched from innerapi automatically.
10483
+ End-users should prefer \`doctor\`.
10448
10484
  `
10449
10485
  },
10450
10486
  {
@@ -10452,16 +10488,11 @@ OPTIONS
10452
10488
  hidden: true,
10453
10489
  summary: "Apply standard-mode repairs",
10454
10490
  help: `USAGE
10455
- ${BIN} repair [--ctx=<base64>]
10491
+ ${BIN} repair
10456
10492
 
10457
10493
  DESCRIPTION
10458
- Runs repair for the failing rules listed inside the ctx's repairData.
10459
- Intended for sandbox_console's push path — end-users should use
10460
- \`doctor --fix\` instead.
10461
-
10462
- OPTIONS
10463
- --ctx=<base64> Opaque ctx JSON (base64). When absent, fetched from
10464
- innerapi.
10494
+ Runs repair for the failing rules. Ctx is fetched from innerapi
10495
+ automatically. End-users should use \`doctor --fix\` instead.
10465
10496
  `
10466
10497
  },
10467
10498
  {
@@ -10469,14 +10500,15 @@ OPTIONS
10469
10500
  hidden: true,
10470
10501
  summary: "Re-initialize sandbox via the 9-step reset pipeline",
10471
10502
  help: `USAGE
10472
- ${BIN} reset --async [--ctx=<base64>]
10473
- ${BIN} reset --worker --task-id=<id> [--ctx=<base64>]
10503
+ ${BIN} reset --async
10504
+ ${BIN} reset --worker --task-id=<id>
10474
10505
 
10475
10506
  DESCRIPTION
10476
10507
  Two-phase pipeline driven asynchronously: the --async invocation spawns
10477
10508
  a detached worker and returns { taskId } immediately; the --worker
10478
10509
  invocation (spawned by --async) runs the actual 9 steps and writes
10479
10510
  progress to /tmp/openclaw-diagnose/reset-<taskId>.json.
10511
+ Ctx is fetched from innerapi automatically.
10480
10512
 
10481
10513
  Poll progress with \`${BIN} get_reset_task --task-id=<id>\`.
10482
10514
 
@@ -10484,7 +10516,6 @@ OPTIONS
10484
10516
  --async Start a detached worker and return taskId on stdout.
10485
10517
  --worker Internal — run the 9-step pipeline (launched by --async).
10486
10518
  --task-id=<id> Required with --worker; identifies the progress file.
10487
- --ctx=<base64> Opaque ctx JSON; fetched from innerapi when absent.
10488
10519
  `
10489
10520
  },
10490
10521
  {
@@ -10507,7 +10538,7 @@ OPTIONS
10507
10538
  hidden: true,
10508
10539
  summary: "Download + install the openclaw tarball",
10509
10540
  help: `USAGE
10510
- ${BIN} install-openclaw <tag> [--ctx=<base64> | --oss_file_map=<base64>]
10541
+ ${BIN} install-openclaw <tag> [--oss_file_map=<base64>]
10511
10542
 
10512
10543
  DESCRIPTION
10513
10544
  Downloads the openclaw@<tag> tgz via the signed OSS URL found in the
@@ -10519,9 +10550,9 @@ ARGUMENTS
10519
10550
  <tag> Openclaw version tag, e.g. 2026.4.11.
10520
10551
 
10521
10552
  OPTIONS
10522
- --ctx=<base64> Opaque ctx; ossFileMap is extracted from it.
10523
10553
  --oss_file_map=... Pre-built OSS URL map (base64 JSON); skips innerapi
10524
- entirely. Wins over --ctx when both provided.
10554
+ entirely. When absent, ossFileMap is fetched from
10555
+ innerapi automatically.
10525
10556
  `
10526
10557
  },
10527
10558
  {
@@ -10547,8 +10578,7 @@ OPTIONS
10547
10578
  --home_base=<dir> Override the /home/gem base (tests).
10548
10579
  --config_path=<p> Override the openclaw.json path (tests).
10549
10580
  --skip-config-update Leave plugins.installs in openclaw.json untouched.
10550
- --ctx=<base64> Opaque ctx; see install-openclaw for semantics.
10551
- --oss_file_map=... Pre-built OSS URL map (base64 JSON).
10581
+ --oss_file_map=... Pre-built OSS URL map (base64 JSON); skips innerapi.
10552
10582
  `
10553
10583
  },
10554
10584
  {
@@ -10575,7 +10605,6 @@ OPTIONS
10575
10605
  --cli=<name> CLI package to install by short name or scoped
10576
10606
  packageName (repeatable, at least one required).
10577
10607
  --home_base=<dir> Override the /home/gem base (tests).
10578
- --ctx=<base64> Opaque ctx; ossFileMap is extracted from it.
10579
10608
  --oss_file_map=... Pre-built OSS URL map (base64 JSON); skips innerapi.
10580
10609
 
10581
10610
  EXAMPLES
@@ -10629,46 +10658,6 @@ OPTIONS
10629
10658
  EXIT CODES
10630
10659
  0 Success or skipped (prerequisites not met).
10631
10660
  1 Secret/path unresolvable, lark-cli failed, or config unreadable.
10632
- `
10633
- },
10634
- {
10635
- name: "upgrade-lark",
10636
- hidden: false,
10637
- summary: "Upgrade the Feishu/Lark plugin via @larksuite/openclaw-lark-tools",
10638
- help: `USAGE
10639
- ${BIN} upgrade-lark [--scene=<scene>] [--caller=<n>] [--trace-id=<id>]
10640
-
10641
- DESCRIPTION
10642
- Upgrades the Feishu/Lark plugin by running:
10643
- npx -y @larksuite/openclaw-lark-tools update --use-existing
10644
-
10645
- Before the upgrade, the following files are backed up:
10646
- - openclaw.json
10647
- - extensions/openclaw-lark/ (if present)
10648
- - extensions/feishu-openclaw-plugin/ (if present)
10649
- After the upgrade, the result is validated:
10650
- - feishu.accounts bot count must not decrease
10651
- - gateway config structure must remain valid (port/mode/bind/auth/trustedProxies)
10652
- If the upgrade command fails, or validation fails, the backed-up files are
10653
- restored to roll back the changes.
10654
-
10655
- Execution is logged to /tmp/openclaw-diagnose/upgrade-lark-<runId>.log.
10656
-
10657
- Output is a single JSON object on stdout:
10658
- { "ok": true, "stdout": "...", "stderr": "...", "logFile": "..." }
10659
- { "ok": false, "error": "...", "stderr": "...", "exitCode": 1,
10660
- "rollbackOk": true, "validationError": "...", "logFile": "..." }
10661
-
10662
- OPTIONS
10663
- --scene=<scene> Telemetry label forwarded to Slardar only.
10664
- Known values: PageUpgradeLark, etc. Custom strings accepted.
10665
- --caller=<name> Optional metadata passed to innerapi.
10666
- --trace-id=<id> Optional log-correlation id.
10667
-
10668
- EXIT CODES
10669
- 0 Success: upgrade ran and all validations passed.
10670
- 1 Failure: npx error, validation failed, or git commit failed.
10671
- File rollback was attempted (see rollbackOk in the JSON output).
10672
10661
  `
10673
10662
  },
10674
10663
  {
@@ -10702,41 +10691,6 @@ EXAMPLES
10702
10691
  ${BIN} rules # all rules
10703
10692
  ${BIN} rules --rule=gateway # single rule
10704
10693
  ${BIN} rules --rule=gateway --rule=feishu_channel # multiple rules
10705
- `
10706
- },
10707
- {
10708
- name: "channels-probe",
10709
- hidden: true,
10710
- summary: "Check feishu channel health via openclaw channels status --probe",
10711
- help: `USAGE
10712
- ${BIN} channels-probe [--timeout=<ms>]
10713
-
10714
- DESCRIPTION
10715
- Runs \`openclaw channels status --probe\` and returns a structured JSON
10716
- summary of whether the current environment's feishu channels are
10717
- configured and working correctly.
10718
-
10719
- Output:
10720
- {
10721
- "available": true,
10722
- "gatewayReachable": true,
10723
- "accounts": [
10724
- { "id": "default", "bits": ["enabled","configured","running","works"],
10725
- "isWorking": true, "raw": "- Feishu default: ..." }
10726
- ],
10727
- "anyAccountWorking": true
10728
- }
10729
-
10730
- An account is considered working when:
10731
- enabled ∧ configured ∧ ( works ∨ ( running ∧ no error: ∧ no probe failed ) )
10732
-
10733
- "available": false means the CLI invocation itself failed (openclaw not
10734
- found, gateway unreachable, or no parseable output returned).
10735
-
10736
- OPTIONS
10737
- --timeout=<ms> Max wait in milliseconds (default: 60000). The probe
10738
- can hang indefinitely on openclaw v2026.4.x due to a
10739
- missing per-request HTTP timeout — set this accordingly.
10740
10694
  `
10741
10695
  },
10742
10696
  {
@@ -10757,8 +10711,7 @@ OPTIONS
10757
10711
  --role=<role> Package role (e.g. template, config).
10758
10712
  --name=<name> Package name within the role.
10759
10713
  --dir=<dir> Target dir (defaults to dirname(pkg.installPath)).
10760
- --ctx=<base64> Opaque ctx; ossFileMap is extracted from it.
10761
- --oss_file_map=... Pre-built OSS URL map (base64 JSON).
10714
+ --oss_file_map=... Pre-built OSS URL map (base64 JSON); skips innerapi.
10762
10715
  `
10763
10716
  }
10764
10717
  ];
@@ -10834,31 +10787,31 @@ function planVarsFields(opts = {}) {
10834
10787
  *
10835
10788
  * Per-command group needs:
10836
10789
  *
10837
- * doctor / check app (rule-driven)
10838
- * repair app + secrets (writes secretsContent / providerKeyContent)
10839
- * reset app + secrets + install + reset (the works)
10790
+ * doctor / check app + larkApps
10791
+ * repair app + secrets + larkApps
10792
+ * reset app + secrets + install + reset + larkApps
10840
10793
  * install-* install only
10841
10794
  *
10842
10795
  * Empty result (`{}`) means "no group needed" — the CLI can skip the
10843
10796
  * `fetchCtxViaInnerApi` call entirely and run with a synthetic empty ctx.
10844
- * Happens e.g. when the user pinned `--rule=<key>` to a vars-free rule on
10845
- * `doctor`.
10846
10797
  */
10847
10798
  function planCtxPopulate(opts) {
10848
10799
  if (opts.command === "install") return { install: true };
10849
10800
  const populate = {};
10850
- const appFields = planVarsFields({
10801
+ if (planVarsFields({
10851
10802
  disabled: opts.disabled,
10852
10803
  onlyRules: opts.onlyRules,
10853
10804
  profile: opts.profile
10854
- });
10855
- if (appFields.length > 0) populate.app = appFields;
10856
- if (opts.command === "repair") populate.secrets = true;
10857
- else if (opts.command === "reset") {
10805
+ }).length > 0) populate.app = true;
10806
+ if (opts.command === "repair") {
10807
+ populate.secrets = true;
10808
+ populate.larkApps = true;
10809
+ } else if (opts.command === "reset") {
10858
10810
  populate.secrets = true;
10859
10811
  populate.install = true;
10860
10812
  populate.reset = true;
10861
- }
10813
+ populate.larkApps = true;
10814
+ } else if (opts.command === "doctor" || opts.command === "check") populate.larkApps = true;
10862
10815
  return populate;
10863
10816
  }
10864
10817
  //#endregion
@@ -10912,378 +10865,11 @@ function reportDoctorRunToSlardar(opts) {
10912
10865
  }
10913
10866
  });
10914
10867
  }
10915
- /** Read the tail of a file, up to maxBytes. Returns empty string on any error. */
10916
- function readFileTail(filePath, maxBytes = 4e3) {
10917
- 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);
10921
- } catch {
10922
- return "";
10923
- }
10924
- }
10925
- function reportUpgradeLarkToSlardar(opts) {
10926
- console.error(`[slardar] upgrade-lark reportTask scene=${opts.scene ?? ""} success=${opts.success} exitCode=${opts.exitCode ?? ""} rollbackOk=${opts.rollbackOk ?? ""}`);
10927
- reportTask({
10928
- eventName: "upgrade_lark_run",
10929
- durationMs: opts.durationMs,
10930
- status: opts.success ? "success" : "failed",
10931
- extraCategories: {
10932
- scene: opts.scene ?? "",
10933
- exit_code: String(opts.exitCode ?? ""),
10934
- 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) : ""
10951
- }
10952
- });
10953
- }
10954
- //#endregion
10955
- //#region src/upgrade-lark.ts
10956
- /** Plugin directories under extensions/ that are backed up before upgrade */
10957
- const FEISHU_PLUGIN_DIRS = ["openclaw-lark", "feishu-openclaw-plugin"];
10958
- /** Version compat rule keys checked in the doctor output after install */
10959
- const VERSION_COMPAT_RULE_KEYS = ["feishu_plugin_version_compat_lark", "feishu_plugin_version_compat_openclaw"];
10960
- function backupFiles(opts) {
10961
- const { workspaceDir, configPath, backupDir, log } = opts;
10962
- try {
10963
- node_fs.default.mkdirSync(backupDir, { recursive: true });
10964
- log(`backup dir: ${backupDir}`);
10965
- if (node_fs.default.existsSync(configPath)) {
10966
- const stat = node_fs.default.statSync(configPath);
10967
- node_fs.default.copyFileSync(configPath, node_path.default.join(backupDir, "openclaw.json"));
10968
- log(` backed up: openclaw.json (${stat.size} bytes)`);
10969
- } else log(` skipped: openclaw.json (not found)`);
10970
- const extSrc = node_path.default.join(workspaceDir, "extensions");
10971
- for (const pluginDir of FEISHU_PLUGIN_DIRS) {
10972
- const src = node_path.default.join(extSrc, pluginDir);
10973
- if (node_fs.default.existsSync(src)) {
10974
- const dst = node_path.default.join(backupDir, "extensions", pluginDir);
10975
- node_fs.default.cpSync(src, dst, { recursive: true });
10976
- const version = readPkgVersion(node_path.default.join(src, "package.json"));
10977
- log(` backed up: extensions/${pluginDir}${version ? ` (version: ${version})` : ""}`);
10978
- } else log(` skipped: extensions/${pluginDir} (not found)`);
10979
- }
10980
- return { ok: true };
10981
- } catch (e) {
10982
- return {
10983
- ok: false,
10984
- error: `backup failed: ${e.message}`
10985
- };
10986
- }
10987
- }
10988
- function restoreFiles(opts) {
10989
- const { workspaceDir, configPath, backupDir, log } = opts;
10990
- try {
10991
- const configBackup = node_path.default.join(backupDir, "openclaw.json");
10992
- if (node_fs.default.existsSync(configBackup)) {
10993
- node_fs.default.copyFileSync(configBackup, configPath);
10994
- log(` restored: openclaw.json`);
10995
- }
10996
- const extDst = node_path.default.join(workspaceDir, "extensions");
10997
- for (const pluginDir of FEISHU_PLUGIN_DIRS) {
10998
- const backupSrc = node_path.default.join(backupDir, "extensions", pluginDir);
10999
- if (node_fs.default.existsSync(backupSrc)) {
11000
- const dst = node_path.default.join(extDst, pluginDir);
11001
- if (node_fs.default.existsSync(dst)) node_fs.default.rmSync(dst, {
11002
- recursive: true,
11003
- force: true
11004
- });
11005
- node_fs.default.cpSync(backupSrc, dst, { recursive: true });
11006
- log(` restored: extensions/${pluginDir}`);
11007
- }
11008
- }
11009
- return true;
11010
- } catch (e) {
11011
- log(` restore error: ${e.message}`);
11012
- return false;
11013
- }
11014
- }
11015
- function readPkgVersion(pkgPath) {
11016
- try {
11017
- const pkg = JSON.parse(node_fs.default.readFileSync(pkgPath, "utf-8"));
11018
- return typeof pkg.version === "string" ? pkg.version : null;
11019
- } catch {
11020
- return null;
11021
- }
11022
- }
11023
- function snapshotVersions(cwd, log) {
11024
- const ocResult = (0, node_child_process.spawnSync)("openclaw", ["--version"], {
11025
- cwd,
11026
- encoding: "utf-8",
11027
- stdio: [
11028
- "ignore",
11029
- "pipe",
11030
- "pipe"
11031
- ],
11032
- timeout: 5e3
11033
- });
11034
- const ocRaw = (ocResult.stdout ?? "").trim() || (ocResult.stderr ?? "").trim();
11035
- const extDir = node_path.default.join(cwd, "extensions");
11036
- const larkPkg = node_path.default.join(extDir, "openclaw-lark", "package.json");
11037
- const feishuPkg = node_path.default.join(extDir, "feishu-openclaw-plugin", "package.json");
11038
- log(` version-check paths: ${larkPkg} [${node_fs.default.existsSync(larkPkg) ? "exists" : "missing"}]`);
11039
- log(` version-check paths: ${feishuPkg} [${node_fs.default.existsSync(feishuPkg) ? "exists" : "missing"}]`);
11040
- return {
11041
- openclaw: ocRaw || null,
11042
- openclawLark: readPkgVersion(larkPkg),
11043
- feishuOpenclawPlugin: readPkgVersion(feishuPkg)
11044
- };
11045
- }
11046
- function logVersionSnapshot(label, v, log) {
11047
- log(`${label}: openclaw=${v.openclaw ?? "n/a"} openclaw-lark=${v.openclawLark ?? "n/a"} feishu-openclaw-plugin=${v.feishuOpenclawPlugin ?? "n/a"}`);
11048
- }
11049
- function countFeishuBots(configPath) {
11050
- try {
11051
- const raw = node_fs.default.readFileSync(configPath, "utf-8");
11052
- const config = loadJSON5().parse(raw);
11053
- const accounts = getNestedMap(config, "channels", "feishu", "accounts");
11054
- if (accounts) return Object.keys(accounts).length;
11055
- const feishu = getNestedMap(config, "channels", "feishu");
11056
- return typeof feishu?.appId === "string" && feishu.appId ? 1 : 0;
11057
- } catch {
11058
- return 0;
11059
- }
11060
- }
11061
- /**
11062
- * Parse doctor stdout (first JSON line) and return an error string if any
11063
- * version compat rule failed. Returns null on parse failure so a broken doctor
11064
- * output does not block the install.
11065
- */
11066
- function checkVersionCompatFromDoctorOutput(stdout, log) {
11067
- const firstLine = stdout.split("\n")[0]?.trim();
11068
- if (!firstLine) {
11069
- log(" doctor(compat): empty output, skipping version compat check");
11070
- return null;
11071
- }
11072
- try {
11073
- const report = JSON.parse(firstLine);
11074
- for (const outcome of report.results) if (VERSION_COMPAT_RULE_KEYS.includes(outcome.rule)) {
11075
- if (outcome.status === "failed" || outcome.status === "still-broken" || outcome.status === "error") return `version compat rule ${outcome.rule} ${outcome.status}: ${outcome.message ?? "(no message)"}`;
11076
- }
11077
- return null;
11078
- } catch (e) {
11079
- log(` doctor(compat): failed to parse output — ${e.message}`);
11080
- return null;
11081
- }
11082
- }
11083
- /** Run channels probe, log results, and return the result. Never throws. */
11084
- function probeChannels(label, log, timeoutMs) {
11085
- try {
11086
- const r = runChannelsProbe(timeoutMs);
11087
- log(` ${label} available=${r.available} anyAccountWorking=${r.anyAccountWorking}`);
11088
- if (r.error) log(` ${label} error: ${r.error}`);
11089
- if (r.gatewayReachable != null) log(` ${label} gatewayReachable: ${r.gatewayReachable}`);
11090
- for (const acct of r.accounts ?? []) log(` ${label} account ${acct.id}: isWorking=${acct.isWorking} bits=[${acct.bits.join(",")}]`);
11091
- return r;
11092
- } catch (e) {
11093
- log(` ${label} channels probe threw: ${e.message}`);
11094
- return {
11095
- available: false,
11096
- accounts: [],
11097
- anyAccountWorking: false
11098
- };
11099
- }
11100
- }
11101
- function runUpgradeLark(opts) {
11102
- const cwd = opts.cwd ?? "/home/gem/workspace/agent";
11103
- const configPath = opts.configPath ?? CONFIG_PATH;
11104
- const logFile = upgradeLarkLogFile(opts.runId);
11105
- const log = makeLogger(logFile);
11106
- const fsOpts = {
11107
- workspaceDir: cwd,
11108
- configPath,
11109
- backupDir: node_path.default.join(opts.backupBaseDir ?? "/tmp/openclaw-diagnose", `upgrade-lark-backup-${opts.runId}`),
11110
- log
11111
- };
11112
- const cliScript = opts.cliScript ?? process.argv[1];
11113
- const statusCheckDelayMs = opts.statusCheckDelayMs ?? 5e3;
11114
- log(`${"=".repeat(60)}`);
11115
- log(`upgrade-lark started runId=${opts.runId}`);
11116
- log(` cwd : ${cwd}`);
11117
- log(` configPath : ${configPath}`);
11118
- log(`${"=".repeat(60)}`);
11119
- log("");
11120
- log("── [1/8] 升级前状态快照 ──────────────────────────────────");
11121
- log(`before-state: botCount=${countFeishuBots(configPath)}`);
11122
- log("");
11123
- log("── [2/8] 文件备份 ────────────────────────────────────────");
11124
- const backup = backupFiles(fsOpts);
11125
- if (!backup.ok) {
11126
- log(`ERROR: ${backup.error}`);
11127
- return {
11128
- ok: false,
11129
- error: backup.error,
11130
- logFile
11131
- };
11132
- }
11133
- log("backup: ok");
11134
- logVersionSnapshot("before-versions", snapshotVersions(cwd, log), log);
11135
- log("");
11136
- log("── [3/8] channels probe(升级前)────────────────────────");
11137
- const beforeChannels = probeChannels("before", log, 3e4);
11138
- log("");
11139
- log("── [4/8] 清理本地 openclaw shim ─────────────────────────");
11140
- const localOpenclawBin = node_path.default.join(cwd, "node_modules", ".bin", "openclaw");
11141
- if (node_fs.default.existsSync(localOpenclawBin)) try {
11142
- node_fs.default.rmSync(localOpenclawBin);
11143
- log(` removed: ${localOpenclawBin}`);
11144
- } catch (e) {
11145
- log(` WARN: failed to remove ${localOpenclawBin}: ${e.message}`);
11146
- }
11147
- else log(` skipped: ${localOpenclawBin} (not found)`);
11148
- log("");
11149
- log("── [5/8] npx install (@larksuite/openclaw-lark-tools update) ──");
11150
- const npxResult = (0, node_child_process.spawnSync)("npx", [
11151
- "-y",
11152
- "@larksuite/openclaw-lark-tools",
11153
- "update"
11154
- ], {
11155
- cwd,
11156
- encoding: "utf-8",
11157
- stdio: [
11158
- "ignore",
11159
- "pipe",
11160
- "pipe"
11161
- ],
11162
- timeout: 12e4
11163
- });
11164
- const npxStdout = npxResult.stdout?.trim() ?? "";
11165
- const npxStderr = npxResult.stderr?.trim() ?? "";
11166
- const npxExitCode = npxResult.status ?? 1;
11167
- if (npxStdout) log(`npx stdout:\n${npxStdout}`);
11168
- if (npxStderr) log(`npx stderr:\n${npxStderr}`);
11169
- log(`npx exit: ${npxExitCode}${npxResult.error ? ` error: ${npxResult.error.message}` : ""}`);
11170
- if (statusCheckDelayMs > 0) {
11171
- log("");
11172
- log(`── 等待 ${statusCheckDelayMs / 1e3}s(让 openclaw 服务完成重启) ─────────────`);
11173
- Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, statusCheckDelayMs);
11174
- log("wait done");
11175
- }
11176
- const doRollback = (reason) => {
11177
- log(`ERROR: ${reason}`);
11178
- const rollbackOk = restoreFiles(fsOpts);
11179
- log(`rollback: ${rollbackOk ? "ok" : "FAILED"}`);
11180
- return {
11181
- ok: false,
11182
- error: reason,
11183
- validationError: reason,
11184
- stdout: npxStdout,
11185
- stderr: npxStderr,
11186
- exitCode: npxExitCode,
11187
- rollbackOk,
11188
- logFile
11189
- };
11190
- };
11191
- log("");
11192
- log("── [6/8] 插件安装检查 + 版本兼容校验 ───────────────────");
11193
- const larkExtDir = node_path.default.join(cwd, "extensions", "openclaw-lark");
11194
- const larkVersion = readPkgVersion(node_path.default.join(larkExtDir, "package.json"));
11195
- log(` extensions/openclaw-lark: ${node_fs.default.existsSync(larkExtDir) ? "exists" : "missing"}, version=${larkVersion ?? "n/a"}`);
11196
- if (!node_fs.default.existsSync(larkExtDir)) return doRollback("extensions/openclaw-lark not found after install");
11197
- if (!larkVersion) return doRollback("extensions/openclaw-lark/package.json has no valid version after install");
11198
- log(" running doctor version compat check...");
11199
- const compatArgs = ["doctor"];
11200
- if (opts.scene) compatArgs.push(`--scene=${opts.scene}`);
11201
- const compatResult = (0, node_child_process.spawnSync)(process.execPath, [cliScript, ...compatArgs], {
11202
- cwd,
11203
- encoding: "utf-8",
11204
- stdio: [
11205
- "ignore",
11206
- "pipe",
11207
- "pipe"
11208
- ],
11209
- timeout: 6e4,
11210
- env: process.env
11211
- });
11212
- if (compatResult.stdout?.trim()) log(`doctor(compat) stdout:\n${compatResult.stdout.trim()}`);
11213
- if (compatResult.stderr?.trim()) log(`doctor(compat) stderr:\n${compatResult.stderr.trim()}`);
11214
- log(`doctor(compat) exit: ${compatResult.status ?? "null"}${compatResult.error ? ` error: ${compatResult.error.message}` : ""}`);
11215
- const compatError = checkVersionCompatFromDoctorOutput(compatResult.stdout?.trim() ?? "", log);
11216
- if (compatError) return doRollback(compatError);
11217
- log(" version compat: ok");
11218
- logVersionSnapshot("after-versions", snapshotVersions(cwd, log), log);
11219
- log("");
11220
- log("── [7/8] channels probe(升级后)────────────────────────");
11221
- 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)");
11235
- }
11236
- log(" channels: ok");
11237
- log("");
11238
- log("── [8/8] doctor --fix ────────────────────────────────────");
11239
- const fixArgs = ["doctor", "--fix"];
11240
- if (opts.scene) fixArgs.push(`--scene=${opts.scene}`);
11241
- const fixResult = (0, node_child_process.spawnSync)(process.execPath, [cliScript, ...fixArgs], {
11242
- cwd,
11243
- encoding: "utf-8",
11244
- stdio: [
11245
- "ignore",
11246
- "pipe",
11247
- "pipe"
11248
- ],
11249
- timeout: 6e4,
11250
- env: process.env
11251
- });
11252
- if (fixResult.stdout?.trim()) log(`doctor(fix) stdout:\n${fixResult.stdout.trim()}`);
11253
- if (fixResult.stderr?.trim()) log(`doctor(fix) stderr:\n${fixResult.stderr.trim()}`);
11254
- log(`doctor(fix) exit: ${fixResult.status ?? "null"}${fixResult.error ? ` error: ${fixResult.error.message}` : ""}`);
11255
- log("");
11256
- log(`${"=".repeat(60)}`);
11257
- log("upgrade-lark completed successfully");
11258
- log(`${"=".repeat(60)}`);
11259
- return {
11260
- ok: true,
11261
- stdout: npxStdout,
11262
- stderr: npxStderr,
11263
- exitCode: npxExitCode,
11264
- logFile
11265
- };
11266
- }
11267
10868
  //#endregion
11268
10869
  //#region src/index.ts
11269
10870
  const args = node_process.default.argv.slice(2);
11270
10871
  const mode = args.find((a) => !a.startsWith("-"));
11271
10872
  /**
11272
- * Decode `--ctx=<base64>` into an opaque JSON object. Returns undefined when
11273
- * the flag isn't present — the caller decides whether to fall back to the
11274
- * innerapi or to error out.
11275
- *
11276
- * The object's shape is not enforced here; downstream code consumes it via
11277
- * either `normalizeCtx()` (new path) or direct field access for the legacy
11278
- * check/repair/reset contract still used by sandbox_console push.
11279
- */
11280
- function parseCtxFlag(args) {
11281
- const ctxArg = args.find((a) => a.startsWith("--ctx="));
11282
- if (!ctxArg) return void 0;
11283
- const b64 = ctxArg.slice(6);
11284
- return JSON.parse(Buffer.from(b64, "base64").toString("utf-8"));
11285
- }
11286
- /**
11287
10873
  * Pull the first non-flag positional after the mode name.
11288
10874
  * (The mode itself is args[0] in the filtered set, so we skip index 0.)
11289
10875
  */
@@ -11311,8 +10897,8 @@ function getMultiFlag(args, name) {
11311
10897
  * case but is no longer consulted.
11312
10898
  */
11313
10899
  async function reportRun(command, rc, _raw, invocation, durationMs, outcome, slardar = {
11314
- scene,
11315
- profile,
10900
+ scene: void 0,
10901
+ profile: "standard",
11316
10902
  fix: false
11317
10903
  }) {
11318
10904
  console.error(`${command}: telemetry calling report_cli_run`);
@@ -11376,7 +10962,7 @@ async function main() {
11376
10962
  console.error(`${mode}: begin argv=[${args.join(" ")}] version=${getVersion()} traceId=${traceId ?? "-"} caller=${caller ?? "-"} runIdGenerated=${rc.generated}`);
11377
10963
  switch (mode) {
11378
10964
  case "check": {
11379
- const raw = parseCtxFlag(args) ?? await fetchCtxViaInnerApi({
10965
+ const raw = await fetchCtxViaInnerApi({
11380
10966
  populate: planCtxPopulate({
11381
10967
  command: "check",
11382
10968
  profile
@@ -11401,7 +10987,7 @@ async function main() {
11401
10987
  break;
11402
10988
  }
11403
10989
  case "repair": {
11404
- const raw = parseCtxFlag(args) ?? await fetchCtxViaInnerApi({
10990
+ const raw = await fetchCtxViaInnerApi({
11405
10991
  populate: planCtxPopulate({
11406
10992
  command: "repair",
11407
10993
  profile
@@ -11472,27 +11058,15 @@ async function main() {
11472
11058
  break;
11473
11059
  }
11474
11060
  case "reset":
11475
- if (args.includes("--async")) {
11476
- const ctxArg = args.find((a) => a.startsWith("--ctx="));
11477
- let ctxBase64;
11478
- if (ctxArg) ctxBase64 = ctxArg.slice(6);
11479
- else {
11480
- const fetched = await fetchCtxViaInnerApi({
11481
- populate: planCtxPopulate({ command: "reset" }),
11482
- caller,
11483
- traceId
11484
- });
11485
- ctxBase64 = Buffer.from(JSON.stringify(fetched), "utf-8").toString("base64");
11486
- }
11487
- console.log(JSON.stringify(startAsyncReset(ctxBase64)));
11488
- } else if (args.includes("--worker")) {
11061
+ if (args.includes("--async")) console.log(JSON.stringify(startAsyncReset()));
11062
+ else if (args.includes("--worker")) {
11489
11063
  const taskId = args.find((a) => a.startsWith("--task-id="))?.slice(10);
11490
11064
  if (!taskId) {
11491
11065
  console.error("Error: --task-id=<id> is required for worker");
11492
11066
  node_process.default.exit(1);
11493
11067
  }
11494
11068
  const resultFile = resetResultFile(taskId);
11495
- const raw = parseCtxFlag(args) ?? await fetchCtxViaInnerApi({
11069
+ const raw = await fetchCtxViaInnerApi({
11496
11070
  populate: planCtxPopulate({ command: "reset" }),
11497
11071
  caller,
11498
11072
  traceId
@@ -11516,7 +11090,7 @@ async function main() {
11516
11090
  return;
11517
11091
  }
11518
11092
  } else {
11519
- console.error("Usage: reset --async [--ctx=<base64>] | reset --worker --task-id=<id> [--ctx=<base64>]");
11093
+ console.error("Usage: reset --async | reset --worker --task-id=<id>");
11520
11094
  node_process.default.exit(1);
11521
11095
  }
11522
11096
  break;
@@ -11532,14 +11106,14 @@ async function main() {
11532
11106
  case "install-openclaw": {
11533
11107
  const tag = getPositionalTag(args, "install-openclaw");
11534
11108
  if (!tag) {
11535
- console.error("Usage: install-openclaw <tag> [--ctx=<base64> | --oss_file_map=<base64>]");
11109
+ console.error("Usage: install-openclaw <tag> [--oss_file_map=<base64>]");
11536
11110
  node_process.default.exit(1);
11537
11111
  }
11538
11112
  const ossFileMapFlag = getFlag(args, "oss_file_map");
11539
11113
  let installOssFileMap;
11540
11114
  let rawForTelemetry;
11541
11115
  if (!ossFileMapFlag) {
11542
- rawForTelemetry = parseCtxFlag(args) ?? await fetchCtxViaInnerApi({
11116
+ rawForTelemetry = await fetchCtxViaInnerApi({
11543
11117
  populate: planCtxPopulate({ command: "install" }),
11544
11118
  caller,
11545
11119
  traceId
@@ -11574,7 +11148,7 @@ async function main() {
11574
11148
  case "install-extension": {
11575
11149
  const tag = getPositionalTag(args, "install-extension");
11576
11150
  if (!tag) {
11577
- console.error("Usage: install-extension <tag> (--all | --extension=<name>...) [--home_base=<dir>] [--config_path=<path>] [--skip-config-update] [--ctx=<base64> | --oss_file_map=<base64>]");
11151
+ console.error("Usage: install-extension <tag> (--all | --extension=<name>...) [--home_base=<dir>] [--config_path=<path>] [--skip-config-update] [--oss_file_map=<base64>]");
11578
11152
  node_process.default.exit(1);
11579
11153
  }
11580
11154
  const all = args.includes("--all");
@@ -11586,7 +11160,7 @@ async function main() {
11586
11160
  let installOssFileMap;
11587
11161
  let rawForTelemetry;
11588
11162
  if (!ossFileMapFlag) {
11589
- rawForTelemetry = parseCtxFlag(args) ?? await fetchCtxViaInnerApi({
11163
+ rawForTelemetry = await fetchCtxViaInnerApi({
11590
11164
  populate: planCtxPopulate({ command: "install" }),
11591
11165
  caller,
11592
11166
  traceId
@@ -11632,12 +11206,12 @@ async function main() {
11632
11206
  case "install-cli": {
11633
11207
  const tag = getPositionalTag(args, "install-cli");
11634
11208
  if (!tag) {
11635
- console.error("Usage: install-cli <tag> --cli=<name>... [--home_base=<dir>] [--ctx=<base64> | --oss_file_map=<base64>]");
11209
+ console.error("Usage: install-cli <tag> --cli=<name>... [--home_base=<dir>] [--oss_file_map=<base64>]");
11636
11210
  node_process.default.exit(1);
11637
11211
  }
11638
11212
  const names = getMultiFlag(args, "cli");
11639
11213
  if (names.length === 0) {
11640
- console.error("Usage: install-cli <tag> --cli=<name>... [--home_base=<dir>] [--ctx=<base64> | --oss_file_map=<base64>]");
11214
+ console.error("Usage: install-cli <tag> --cli=<name>... [--home_base=<dir>] [--oss_file_map=<base64>]");
11641
11215
  node_process.default.exit(1);
11642
11216
  }
11643
11217
  const homeBase = getFlag(args, "home_base");
@@ -11645,7 +11219,7 @@ async function main() {
11645
11219
  let installOssFileMap;
11646
11220
  let rawForTelemetry;
11647
11221
  if (!ossFileMapFlag) {
11648
- rawForTelemetry = parseCtxFlag(args) ?? await fetchCtxViaInnerApi({
11222
+ rawForTelemetry = await fetchCtxViaInnerApi({
11649
11223
  populate: planCtxPopulate({ command: "install" }),
11650
11224
  caller,
11651
11225
  traceId
@@ -11693,7 +11267,7 @@ async function main() {
11693
11267
  case "download-resource": {
11694
11268
  const tag = getPositionalTag(args, "download-resource");
11695
11269
  if (!tag) {
11696
- console.error("Usage: download-resource <tag> --role=<role> --name=<name> [--dir=<dir>] [--ctx=<base64> | --oss_file_map=<base64>]");
11270
+ console.error("Usage: download-resource <tag> --role=<role> --name=<name> [--dir=<dir>] [--oss_file_map=<base64>]");
11697
11271
  node_process.default.exit(1);
11698
11272
  }
11699
11273
  const role = getFlag(args, "role");
@@ -11707,7 +11281,7 @@ async function main() {
11707
11281
  let installOssFileMap;
11708
11282
  let rawForTelemetry;
11709
11283
  if (!ossFileMapFlag) {
11710
- rawForTelemetry = parseCtxFlag(args) ?? await fetchCtxViaInnerApi({
11284
+ rawForTelemetry = await fetchCtxViaInnerApi({
11711
11285
  populate: planCtxPopulate({ command: "install" }),
11712
11286
  caller,
11713
11287
  traceId
@@ -11781,50 +11355,6 @@ async function main() {
11781
11355
  if (!result.ok) node_process.default.exit(1);
11782
11356
  break;
11783
11357
  }
11784
- case "upgrade-lark": {
11785
- const result = runUpgradeLark({
11786
- runId: rc.runId,
11787
- scene
11788
- });
11789
- const upgradeDurationMs = Date.now() - t0;
11790
- console.log(JSON.stringify(result));
11791
- reportUpgradeLarkToSlardar({
11792
- scene,
11793
- durationMs: upgradeDurationMs,
11794
- success: result.ok,
11795
- logFile: result.logFile,
11796
- exitCode: result.exitCode,
11797
- rollbackOk: result.rollbackOk,
11798
- validationError: result.validationError,
11799
- error: result.error
11800
- });
11801
- try {
11802
- await reportCliRun({
11803
- command: "upgrade-lark",
11804
- runId: rc.runId,
11805
- version: getVersion(),
11806
- invocation: args.join(" "),
11807
- durationMs: upgradeDurationMs,
11808
- caller: rc.caller,
11809
- traceId: rc.traceId,
11810
- success: result.ok,
11811
- result,
11812
- error: result.ok ? void 0 : { message: result.error ?? "upgrade-lark failed" }
11813
- });
11814
- } catch (e) {
11815
- console.error(`[telemetry] reportCliRun failed: ${e.message}`);
11816
- }
11817
- if (!result.ok) {
11818
- node_process.default.exitCode = 1;
11819
- return;
11820
- }
11821
- break;
11822
- }
11823
- case "channels-probe": {
11824
- const result = runChannelsProbe(getFlag(args, "timeout") ? Number(getFlag(args, "timeout")) : void 0);
11825
- console.log(JSON.stringify(result));
11826
- break;
11827
- }
11828
11358
  default:
11829
11359
  node_process.default.stderr.write(`Unknown command: ${mode}\n\n`);
11830
11360
  node_process.default.stderr.write(formatTopLevelHelp(helpFlags.expert));
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.11",
4
4
  "description": "CLI for OpenClaw config diagnose and repair with JSON5 support",
5
5
  "main": "dist/index.cjs",
6
6
  "bin": {