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

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 +227 -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.3";
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,119 @@ 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, false);
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, true);
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
+ * @param isSingleAccount true for single-account layout (expects provider-ref),
3692
+ * false for multi-account (expects plaintext from larkApps).
3693
+ */
3694
+ checkBot(label, bot, larkApp, issues, isSingleAccount) {
3695
+ const creatorOpenID = larkApp.creatorOpenID;
3696
+ const allowFrom = Array.isArray(bot.allowFrom) ? bot.allowFrom : [];
3697
+ if (typeof creatorOpenID === "string" && creatorOpenID !== "") {
3698
+ if (!allowFrom.includes(creatorOpenID)) issues.push(`${label} allowFrom missing creatorOpenID ${creatorOpenID.length > 8 ? creatorOpenID.slice(0, 4) + "***" + creatorOpenID.slice(-4) : "***"}`);
3699
+ } else if (allowFrom.length === 0) issues.push(`${label} allowFrom is empty (creatorOpenID unavailable, cannot auto-fix)`);
3700
+ const secret = bot.appSecret;
3701
+ if (isSingleAccount) if (typeof secret === "object" && secret !== null && !Array.isArray(secret)) {
3702
+ if (!matchMap(secret, DEFAULT_FEISHU_APP_SECRET)) issues.push(`${label} appSecret is a provider-ref but not the canonical one`);
3703
+ } else issues.push(`${label} appSecret should be provider-ref, got ${typeof secret}`);
3704
+ else if (typeof secret === "string") {
3705
+ if (secret !== larkApp.appSecret) issues.push(`${label} appSecret plaintext mismatch`);
3706
+ } else issues.push(`${label} appSecret should be plaintext, got ${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, false);
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, true);
3727
+ }
3728
+ }
3729
+ /** Fix a single bot entry in-place.
3730
+ * @param isSingleAccount true for single-account layout (use provider-ref),
3731
+ * false for multi-account (use plaintext from larkApps).
3732
+ */
3733
+ fixBot(bot, larkApp, isSingleAccount) {
3734
+ const creatorOpenID = larkApp.creatorOpenID;
3735
+ if (typeof creatorOpenID === "string" && creatorOpenID !== "") {
3736
+ const allowFrom = Array.isArray(bot.allowFrom) ? [...bot.allowFrom] : [];
3737
+ if (!allowFrom.includes(creatorOpenID)) {
3738
+ allowFrom.push(creatorOpenID);
3739
+ bot.allowFrom = allowFrom;
3740
+ }
3741
+ }
3742
+ const secret = bot.appSecret;
3743
+ let needsFix = false;
3744
+ if (isSingleAccount) if (typeof secret === "object" && secret !== null && !Array.isArray(secret)) {
3745
+ if (!matchMap(secret, DEFAULT_FEISHU_APP_SECRET)) needsFix = true;
3746
+ } else needsFix = true;
3747
+ else if (typeof secret === "string") {
3748
+ if (secret !== larkApp.appSecret) needsFix = true;
3749
+ } else needsFix = true;
3750
+ if (needsFix) bot.appSecret = isSingleAccount ? { ...DEFAULT_FEISHU_APP_SECRET } : larkApp.appSecret;
3751
+ }
3752
+ };
3753
+ FeishuBotChannelConfigRule = __decorate([Rule({
3754
+ key: "feishu_bot_channel_config",
3755
+ description: "确保飞书配置中 bot 账号的 allowFrom 包含其创建者 openID 且 appSecret 值正确",
3756
+ dependsOn: [
3757
+ "config_syntax_check",
3758
+ "feishu_default_account",
3759
+ "feishu_bot_id"
3760
+ ],
3761
+ repairMode: "standard",
3762
+ usesVars: ["larkApps"],
3763
+ level: "critical"
3764
+ })], FeishuBotChannelConfigRule);
3765
+ //#endregion
3661
3766
  //#region src/check.ts
3662
3767
  /** Telemetry-aware entry: returns both the legacy CheckResult (for stdout)
3663
3768
  * AND a DoctorReport-shape payload (for `openclaw.report_cli_run`). The
@@ -4121,9 +4226,6 @@ const PROVIDER_FILE_PATH = "/home/gem/workspace/.force/openclaw/miaoda-provider-
4121
4226
  const SECRETS_FILE_PATH = "/home/gem/workspace/.force/openclaw/miaoda-openclaw-secrets.json";
4122
4227
  /** Absolute path to the openclaw config JSON. */
4123
4228
  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
4229
  //#endregion
4128
4230
  //#region src/run-log.ts
4129
4231
  let currentRunContext;
@@ -4260,9 +4362,10 @@ function makeLogger(logFile) {
4260
4362
  /**
4261
4363
  * Start an async reset task: spawn a detached child process and return the taskId.
4262
4364
  *
4263
- * The child process runs: node cli.js reset --worker --task-id=xxx --ctx=base64
4365
+ * The child process runs: node cli.js reset --worker --task-id=xxx
4366
+ * The worker fetches ctx from innerApi itself — no --ctx passthrough.
4264
4367
  */
4265
- function startAsyncReset(ctxBase64) {
4368
+ function startAsyncReset() {
4266
4369
  const taskId = (0, node_crypto.randomUUID)();
4267
4370
  const resultFile = resetResultFile(taskId);
4268
4371
  const log = makeLogger(resetLogFile(taskId));
@@ -4286,8 +4389,7 @@ function startAsyncReset(ctxBase64) {
4286
4389
  process.argv[1],
4287
4390
  "reset",
4288
4391
  "--worker",
4289
- `--task-id=${taskId}`,
4290
- `--ctx=${ctxBase64}`
4392
+ `--task-id=${taskId}`
4291
4393
  ], {
4292
4394
  detached: true,
4293
4395
  stdio: "ignore",
@@ -6236,23 +6338,6 @@ function reportError(params) {
6236
6338
  } catch {}
6237
6339
  }
6238
6340
  //#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
6341
  //#region src/install-cli.ts
6257
6342
  const LARK_CLI_NAME = "lark-cli";
6258
6343
  const AGENT_SKILLS_NAME = "agent-skills";
@@ -6818,6 +6903,60 @@ function mergeCoreBackupAndOrigins(configPath, vars, resetData, log) {
6818
6903
  log(`allowedOrigins: added ${added.length} (${JSON.stringify(added)}), total now ${mergedOrigins.length}`);
6819
6904
  }
6820
6905
  /**
6906
+ * Fix bot account allowFrom and appSecret using larkApps from innerApi.
6907
+ *
6908
+ * For each bot account (key starts with `bot-cli_`):
6909
+ * - allowFrom must contain the bot's own creatorOpenID from larkApps
6910
+ * - appSecret must be either the canonical provider-ref or match larkApps plaintext
6911
+ *
6912
+ * Runs after mergeCoreBackupAndOrigins so it operates on the final config state.
6913
+ */
6914
+ function fixBotChannelConfig(configPath, larkApps, log) {
6915
+ if (!larkApps || larkApps.length === 0) {
6916
+ log("no larkApps data, skip bot channel config fix");
6917
+ return;
6918
+ }
6919
+ const config = loadJSON5().parse(node_fs.default.readFileSync(configPath, "utf-8"));
6920
+ const accounts = asRecord(getNestedMap(config, "channels", "feishu")?.accounts);
6921
+ if (!accounts) {
6922
+ log("no feishu accounts in config, skip bot channel config fix");
6923
+ return;
6924
+ }
6925
+ let fixCount = 0;
6926
+ for (const [, account] of Object.entries(accounts)) {
6927
+ const bot = asRecord(account);
6928
+ if (!bot) continue;
6929
+ const appId = bot.appId;
6930
+ if (typeof appId !== "string" || !appId.startsWith("cli_")) continue;
6931
+ const larkApp = larkApps.find((e) => e.larkAppID === appId);
6932
+ if (!larkApp) continue;
6933
+ const creatorOpenID = larkApp.creatorOpenID;
6934
+ if (typeof creatorOpenID === "string" && creatorOpenID !== "") {
6935
+ const allowFrom = Array.isArray(bot.allowFrom) ? [...bot.allowFrom] : [];
6936
+ if (!allowFrom.includes(creatorOpenID)) {
6937
+ allowFrom.push(creatorOpenID);
6938
+ bot.allowFrom = allowFrom;
6939
+ fixCount++;
6940
+ }
6941
+ }
6942
+ const secret = bot.appSecret;
6943
+ let needsFix = false;
6944
+ if (typeof secret === "object" && secret !== null && !Array.isArray(secret)) {
6945
+ if (!matchMap(secret, DEFAULT_FEISHU_APP_SECRET)) needsFix = true;
6946
+ } else if (typeof secret === "string") {
6947
+ if (secret !== larkApp.appSecret) needsFix = true;
6948
+ } else needsFix = true;
6949
+ if (needsFix) {
6950
+ bot.appSecret = { ...DEFAULT_FEISHU_APP_SECRET };
6951
+ fixCount++;
6952
+ }
6953
+ }
6954
+ if (fixCount > 0) {
6955
+ node_fs.default.writeFileSync(configPath, JSON.stringify(config, null, 2), "utf-8");
6956
+ log(`fixed ${fixCount} bot channel config issue(s) (allowFrom/appSecret)`);
6957
+ } else log("bot channel config ok, no fixes needed");
6958
+ }
6959
+ /**
6821
6960
  * Step 7: Verify startup scripts landed in configDir/scripts/.
6822
6961
  *
6823
6962
  * Scripts are extracted directly to configDir/scripts/ during stageTemplate —
@@ -6962,6 +7101,7 @@ async function runReset(input, taskId, resultFile) {
6962
7101
  await step5InstallOpenclaw(openclawTag, ossFileMap, log);
6963
7102
  step(6);
6964
7103
  mergeCoreBackupAndOrigins(configPath, vars, resetData, log);
7104
+ fixBotChannelConfig(configPath, vars.larkApps, log);
6965
7105
  step(7);
6966
7106
  verifyStartupScripts(configDir, log);
6967
7107
  step(8);
@@ -7760,7 +7900,8 @@ function normalizeCtx(raw) {
7760
7900
  reset: {
7761
7901
  templateVars: r.reset.templateVars ?? {},
7762
7902
  coreBackup: r.reset.coreBackup
7763
- }
7903
+ },
7904
+ larkApps: Array.isArray(r.larkApps) ? r.larkApps : []
7764
7905
  };
7765
7906
  }
7766
7907
  const vars = r.vars ?? {};
@@ -7785,7 +7926,8 @@ function normalizeCtx(raw) {
7785
7926
  reset: {
7786
7927
  templateVars: resetData.templateVars ?? {},
7787
7928
  coreBackup: resetData.coreBackup
7788
- }
7929
+ },
7930
+ larkApps: Array.isArray(r.larkApps) ? r.larkApps : []
7789
7931
  };
7790
7932
  }
7791
7933
  function fillApp(src) {
@@ -7850,7 +7992,8 @@ function buildCheckInput(raw, configPathOverride) {
7850
7992
  providerFilePath: PROVIDER_FILE_PATH,
7851
7993
  secretsFilePath: SECRETS_FILE_PATH,
7852
7994
  templateVars: ctx.app.templateVars,
7853
- recommendedOpenclawTag: ctx.app.recommendedOpenclawTag
7995
+ recommendedOpenclawTag: ctx.app.recommendedOpenclawTag,
7996
+ larkApps: ctx.larkApps
7854
7997
  },
7855
7998
  templateVars: ctx.app.templateVars
7856
7999
  };
@@ -7882,7 +8025,8 @@ function buildRepairInput(raw, configPathOverride) {
7882
8025
  providerFilePath: PROVIDER_FILE_PATH,
7883
8026
  secretsFilePath: SECRETS_FILE_PATH,
7884
8027
  templateVars: ctx.app.templateVars,
7885
- recommendedOpenclawTag: ctx.app.recommendedOpenclawTag
8028
+ recommendedOpenclawTag: ctx.app.recommendedOpenclawTag,
8029
+ larkApps: ctx.larkApps
7886
8030
  },
7887
8031
  repairData: {
7888
8032
  secretsContent: ctx.secrets.secretsContent,
@@ -7918,7 +8062,8 @@ function buildResetInput(raw, configPathOverride) {
7918
8062
  providerFilePath: PROVIDER_FILE_PATH,
7919
8063
  secretsFilePath: SECRETS_FILE_PATH,
7920
8064
  templateVars: ctx.app.templateVars,
7921
- recommendedOpenclawTag: ctx.app.recommendedOpenclawTag
8065
+ recommendedOpenclawTag: ctx.app.recommendedOpenclawTag,
8066
+ larkApps: ctx.larkApps
7922
8067
  },
7923
8068
  resetData: {
7924
8069
  templateVars: ctx.reset.templateVars,
@@ -10149,109 +10294,6 @@ function finalize(results, aborted) {
10149
10294
  };
10150
10295
  }
10151
10296
  //#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
10297
  //#region src/innerapi/reportCliRun.ts
10256
10298
  /**
10257
10299
  * CLI-side client for studio_server's `openclaw.report_cli_run` inner
@@ -10331,7 +10373,7 @@ async function reportCliRun(opts) {
10331
10373
  //#region src/help.ts
10332
10374
  const BIN = "mclaw-diagnose";
10333
10375
  function versionBanner() {
10334
- return `v0.1.14-alpha.1`;
10376
+ return `v0.1.14-alpha.3`;
10335
10377
  }
10336
10378
  const COMMANDS = [
10337
10379
  {
@@ -10435,16 +10477,12 @@ EXIT CODES
10435
10477
  hidden: true,
10436
10478
  summary: "Run rule-engine check only",
10437
10479
  help: `USAGE
10438
- ${BIN} check [--ctx=<base64>]
10480
+ ${BIN} check
10439
10481
 
10440
10482
  DESCRIPTION
10441
10483
  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).
10484
+ returns { failedRules }. Ctx is fetched from innerapi automatically.
10485
+ End-users should prefer \`doctor\`.
10448
10486
  `
10449
10487
  },
10450
10488
  {
@@ -10452,16 +10490,11 @@ OPTIONS
10452
10490
  hidden: true,
10453
10491
  summary: "Apply standard-mode repairs",
10454
10492
  help: `USAGE
10455
- ${BIN} repair [--ctx=<base64>]
10493
+ ${BIN} repair
10456
10494
 
10457
10495
  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.
10496
+ Runs repair for the failing rules. Ctx is fetched from innerapi
10497
+ automatically. End-users should use \`doctor --fix\` instead.
10465
10498
  `
10466
10499
  },
10467
10500
  {
@@ -10469,14 +10502,15 @@ OPTIONS
10469
10502
  hidden: true,
10470
10503
  summary: "Re-initialize sandbox via the 9-step reset pipeline",
10471
10504
  help: `USAGE
10472
- ${BIN} reset --async [--ctx=<base64>]
10473
- ${BIN} reset --worker --task-id=<id> [--ctx=<base64>]
10505
+ ${BIN} reset --async
10506
+ ${BIN} reset --worker --task-id=<id>
10474
10507
 
10475
10508
  DESCRIPTION
10476
10509
  Two-phase pipeline driven asynchronously: the --async invocation spawns
10477
10510
  a detached worker and returns { taskId } immediately; the --worker
10478
10511
  invocation (spawned by --async) runs the actual 9 steps and writes
10479
10512
  progress to /tmp/openclaw-diagnose/reset-<taskId>.json.
10513
+ Ctx is fetched from innerapi automatically.
10480
10514
 
10481
10515
  Poll progress with \`${BIN} get_reset_task --task-id=<id>\`.
10482
10516
 
@@ -10484,7 +10518,6 @@ OPTIONS
10484
10518
  --async Start a detached worker and return taskId on stdout.
10485
10519
  --worker Internal — run the 9-step pipeline (launched by --async).
10486
10520
  --task-id=<id> Required with --worker; identifies the progress file.
10487
- --ctx=<base64> Opaque ctx JSON; fetched from innerapi when absent.
10488
10521
  `
10489
10522
  },
10490
10523
  {
@@ -10507,7 +10540,7 @@ OPTIONS
10507
10540
  hidden: true,
10508
10541
  summary: "Download + install the openclaw tarball",
10509
10542
  help: `USAGE
10510
- ${BIN} install-openclaw <tag> [--ctx=<base64> | --oss_file_map=<base64>]
10543
+ ${BIN} install-openclaw <tag> [--oss_file_map=<base64>]
10511
10544
 
10512
10545
  DESCRIPTION
10513
10546
  Downloads the openclaw@<tag> tgz via the signed OSS URL found in the
@@ -10519,9 +10552,9 @@ ARGUMENTS
10519
10552
  <tag> Openclaw version tag, e.g. 2026.4.11.
10520
10553
 
10521
10554
  OPTIONS
10522
- --ctx=<base64> Opaque ctx; ossFileMap is extracted from it.
10523
10555
  --oss_file_map=... Pre-built OSS URL map (base64 JSON); skips innerapi
10524
- entirely. Wins over --ctx when both provided.
10556
+ entirely. When absent, ossFileMap is fetched from
10557
+ innerapi automatically.
10525
10558
  `
10526
10559
  },
10527
10560
  {
@@ -10547,8 +10580,7 @@ OPTIONS
10547
10580
  --home_base=<dir> Override the /home/gem base (tests).
10548
10581
  --config_path=<p> Override the openclaw.json path (tests).
10549
10582
  --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).
10583
+ --oss_file_map=... Pre-built OSS URL map (base64 JSON); skips innerapi.
10552
10584
  `
10553
10585
  },
10554
10586
  {
@@ -10575,7 +10607,6 @@ OPTIONS
10575
10607
  --cli=<name> CLI package to install by short name or scoped
10576
10608
  packageName (repeatable, at least one required).
10577
10609
  --home_base=<dir> Override the /home/gem base (tests).
10578
- --ctx=<base64> Opaque ctx; ossFileMap is extracted from it.
10579
10610
  --oss_file_map=... Pre-built OSS URL map (base64 JSON); skips innerapi.
10580
10611
 
10581
10612
  EXAMPLES
@@ -10629,46 +10660,6 @@ OPTIONS
10629
10660
  EXIT CODES
10630
10661
  0 Success or skipped (prerequisites not met).
10631
10662
  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
10663
  `
10673
10664
  },
10674
10665
  {
@@ -10702,41 +10693,6 @@ EXAMPLES
10702
10693
  ${BIN} rules # all rules
10703
10694
  ${BIN} rules --rule=gateway # single rule
10704
10695
  ${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
10696
  `
10741
10697
  },
10742
10698
  {
@@ -10757,8 +10713,7 @@ OPTIONS
10757
10713
  --role=<role> Package role (e.g. template, config).
10758
10714
  --name=<name> Package name within the role.
10759
10715
  --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).
10716
+ --oss_file_map=... Pre-built OSS URL map (base64 JSON); skips innerapi.
10762
10717
  `
10763
10718
  }
10764
10719
  ];
@@ -10834,31 +10789,31 @@ function planVarsFields(opts = {}) {
10834
10789
  *
10835
10790
  * Per-command group needs:
10836
10791
  *
10837
- * doctor / check app (rule-driven)
10838
- * repair app + secrets (writes secretsContent / providerKeyContent)
10839
- * reset app + secrets + install + reset (the works)
10792
+ * doctor / check app + larkApps
10793
+ * repair app + secrets + larkApps
10794
+ * reset app + secrets + install + reset + larkApps
10840
10795
  * install-* install only
10841
10796
  *
10842
10797
  * Empty result (`{}`) means "no group needed" — the CLI can skip the
10843
10798
  * `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
10799
  */
10847
10800
  function planCtxPopulate(opts) {
10848
10801
  if (opts.command === "install") return { install: true };
10849
10802
  const populate = {};
10850
- const appFields = planVarsFields({
10803
+ if (planVarsFields({
10851
10804
  disabled: opts.disabled,
10852
10805
  onlyRules: opts.onlyRules,
10853
10806
  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") {
10807
+ }).length > 0) populate.app = true;
10808
+ if (opts.command === "repair") {
10809
+ populate.secrets = true;
10810
+ populate.larkApps = true;
10811
+ } else if (opts.command === "reset") {
10858
10812
  populate.secrets = true;
10859
10813
  populate.install = true;
10860
10814
  populate.reset = true;
10861
- }
10815
+ populate.larkApps = true;
10816
+ } else if (opts.command === "doctor" || opts.command === "check") populate.larkApps = true;
10862
10817
  return populate;
10863
10818
  }
10864
10819
  //#endregion
@@ -10912,378 +10867,11 @@ function reportDoctorRunToSlardar(opts) {
10912
10867
  }
10913
10868
  });
10914
10869
  }
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
10870
  //#endregion
11268
10871
  //#region src/index.ts
11269
10872
  const args = node_process.default.argv.slice(2);
11270
10873
  const mode = args.find((a) => !a.startsWith("-"));
11271
10874
  /**
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
10875
  * Pull the first non-flag positional after the mode name.
11288
10876
  * (The mode itself is args[0] in the filtered set, so we skip index 0.)
11289
10877
  */
@@ -11311,8 +10899,8 @@ function getMultiFlag(args, name) {
11311
10899
  * case but is no longer consulted.
11312
10900
  */
11313
10901
  async function reportRun(command, rc, _raw, invocation, durationMs, outcome, slardar = {
11314
- scene,
11315
- profile,
10902
+ scene: void 0,
10903
+ profile: "standard",
11316
10904
  fix: false
11317
10905
  }) {
11318
10906
  console.error(`${command}: telemetry calling report_cli_run`);
@@ -11376,7 +10964,7 @@ async function main() {
11376
10964
  console.error(`${mode}: begin argv=[${args.join(" ")}] version=${getVersion()} traceId=${traceId ?? "-"} caller=${caller ?? "-"} runIdGenerated=${rc.generated}`);
11377
10965
  switch (mode) {
11378
10966
  case "check": {
11379
- const raw = parseCtxFlag(args) ?? await fetchCtxViaInnerApi({
10967
+ const raw = await fetchCtxViaInnerApi({
11380
10968
  populate: planCtxPopulate({
11381
10969
  command: "check",
11382
10970
  profile
@@ -11401,7 +10989,7 @@ async function main() {
11401
10989
  break;
11402
10990
  }
11403
10991
  case "repair": {
11404
- const raw = parseCtxFlag(args) ?? await fetchCtxViaInnerApi({
10992
+ const raw = await fetchCtxViaInnerApi({
11405
10993
  populate: planCtxPopulate({
11406
10994
  command: "repair",
11407
10995
  profile
@@ -11472,27 +11060,15 @@ async function main() {
11472
11060
  break;
11473
11061
  }
11474
11062
  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")) {
11063
+ if (args.includes("--async")) console.log(JSON.stringify(startAsyncReset()));
11064
+ else if (args.includes("--worker")) {
11489
11065
  const taskId = args.find((a) => a.startsWith("--task-id="))?.slice(10);
11490
11066
  if (!taskId) {
11491
11067
  console.error("Error: --task-id=<id> is required for worker");
11492
11068
  node_process.default.exit(1);
11493
11069
  }
11494
11070
  const resultFile = resetResultFile(taskId);
11495
- const raw = parseCtxFlag(args) ?? await fetchCtxViaInnerApi({
11071
+ const raw = await fetchCtxViaInnerApi({
11496
11072
  populate: planCtxPopulate({ command: "reset" }),
11497
11073
  caller,
11498
11074
  traceId
@@ -11516,7 +11092,7 @@ async function main() {
11516
11092
  return;
11517
11093
  }
11518
11094
  } else {
11519
- console.error("Usage: reset --async [--ctx=<base64>] | reset --worker --task-id=<id> [--ctx=<base64>]");
11095
+ console.error("Usage: reset --async | reset --worker --task-id=<id>");
11520
11096
  node_process.default.exit(1);
11521
11097
  }
11522
11098
  break;
@@ -11532,14 +11108,14 @@ async function main() {
11532
11108
  case "install-openclaw": {
11533
11109
  const tag = getPositionalTag(args, "install-openclaw");
11534
11110
  if (!tag) {
11535
- console.error("Usage: install-openclaw <tag> [--ctx=<base64> | --oss_file_map=<base64>]");
11111
+ console.error("Usage: install-openclaw <tag> [--oss_file_map=<base64>]");
11536
11112
  node_process.default.exit(1);
11537
11113
  }
11538
11114
  const ossFileMapFlag = getFlag(args, "oss_file_map");
11539
11115
  let installOssFileMap;
11540
11116
  let rawForTelemetry;
11541
11117
  if (!ossFileMapFlag) {
11542
- rawForTelemetry = parseCtxFlag(args) ?? await fetchCtxViaInnerApi({
11118
+ rawForTelemetry = await fetchCtxViaInnerApi({
11543
11119
  populate: planCtxPopulate({ command: "install" }),
11544
11120
  caller,
11545
11121
  traceId
@@ -11574,7 +11150,7 @@ async function main() {
11574
11150
  case "install-extension": {
11575
11151
  const tag = getPositionalTag(args, "install-extension");
11576
11152
  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>]");
11153
+ console.error("Usage: install-extension <tag> (--all | --extension=<name>...) [--home_base=<dir>] [--config_path=<path>] [--skip-config-update] [--oss_file_map=<base64>]");
11578
11154
  node_process.default.exit(1);
11579
11155
  }
11580
11156
  const all = args.includes("--all");
@@ -11586,7 +11162,7 @@ async function main() {
11586
11162
  let installOssFileMap;
11587
11163
  let rawForTelemetry;
11588
11164
  if (!ossFileMapFlag) {
11589
- rawForTelemetry = parseCtxFlag(args) ?? await fetchCtxViaInnerApi({
11165
+ rawForTelemetry = await fetchCtxViaInnerApi({
11590
11166
  populate: planCtxPopulate({ command: "install" }),
11591
11167
  caller,
11592
11168
  traceId
@@ -11632,12 +11208,12 @@ async function main() {
11632
11208
  case "install-cli": {
11633
11209
  const tag = getPositionalTag(args, "install-cli");
11634
11210
  if (!tag) {
11635
- console.error("Usage: install-cli <tag> --cli=<name>... [--home_base=<dir>] [--ctx=<base64> | --oss_file_map=<base64>]");
11211
+ console.error("Usage: install-cli <tag> --cli=<name>... [--home_base=<dir>] [--oss_file_map=<base64>]");
11636
11212
  node_process.default.exit(1);
11637
11213
  }
11638
11214
  const names = getMultiFlag(args, "cli");
11639
11215
  if (names.length === 0) {
11640
- console.error("Usage: install-cli <tag> --cli=<name>... [--home_base=<dir>] [--ctx=<base64> | --oss_file_map=<base64>]");
11216
+ console.error("Usage: install-cli <tag> --cli=<name>... [--home_base=<dir>] [--oss_file_map=<base64>]");
11641
11217
  node_process.default.exit(1);
11642
11218
  }
11643
11219
  const homeBase = getFlag(args, "home_base");
@@ -11645,7 +11221,7 @@ async function main() {
11645
11221
  let installOssFileMap;
11646
11222
  let rawForTelemetry;
11647
11223
  if (!ossFileMapFlag) {
11648
- rawForTelemetry = parseCtxFlag(args) ?? await fetchCtxViaInnerApi({
11224
+ rawForTelemetry = await fetchCtxViaInnerApi({
11649
11225
  populate: planCtxPopulate({ command: "install" }),
11650
11226
  caller,
11651
11227
  traceId
@@ -11693,7 +11269,7 @@ async function main() {
11693
11269
  case "download-resource": {
11694
11270
  const tag = getPositionalTag(args, "download-resource");
11695
11271
  if (!tag) {
11696
- console.error("Usage: download-resource <tag> --role=<role> --name=<name> [--dir=<dir>] [--ctx=<base64> | --oss_file_map=<base64>]");
11272
+ console.error("Usage: download-resource <tag> --role=<role> --name=<name> [--dir=<dir>] [--oss_file_map=<base64>]");
11697
11273
  node_process.default.exit(1);
11698
11274
  }
11699
11275
  const role = getFlag(args, "role");
@@ -11707,7 +11283,7 @@ async function main() {
11707
11283
  let installOssFileMap;
11708
11284
  let rawForTelemetry;
11709
11285
  if (!ossFileMapFlag) {
11710
- rawForTelemetry = parseCtxFlag(args) ?? await fetchCtxViaInnerApi({
11286
+ rawForTelemetry = await fetchCtxViaInnerApi({
11711
11287
  populate: planCtxPopulate({ command: "install" }),
11712
11288
  caller,
11713
11289
  traceId
@@ -11781,50 +11357,6 @@ async function main() {
11781
11357
  if (!result.ok) node_process.default.exit(1);
11782
11358
  break;
11783
11359
  }
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
11360
  default:
11829
11361
  node_process.default.stderr.write(`Unknown command: ${mode}\n\n`);
11830
11362
  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.3",
4
4
  "description": "CLI for OpenClaw config diagnose and repair with JSON5 support",
5
5
  "main": "dist/index.cjs",
6
6
  "bin": {