@lark-apaas/openclaw-scripts-diagnose-cli 0.1.14-alpha.2 → 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.
- package/dist/index.cjs +227 -760
- 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.
|
|
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,
|
|
@@ -3513,161 +3505,6 @@ function extractScopedNameFromSpec$1(spec) {
|
|
|
3513
3505
|
const at = spec.indexOf("@", 1);
|
|
3514
3506
|
return at === -1 ? spec : spec.slice(0, at);
|
|
3515
3507
|
}
|
|
3516
|
-
/**
|
|
3517
|
-
* Returns true if the installed feishu plugin is version-incompatible with
|
|
3518
|
-
* the current openclaw (or is a legacy plugin that must be replaced).
|
|
3519
|
-
* Used by the upgrade_lark_needed rule and the upgrade-lark pre-check gate.
|
|
3520
|
-
*/
|
|
3521
|
-
function needsLarkUpgrade(ctx) {
|
|
3522
|
-
const cc = resolveCompatContext(ctx);
|
|
3523
|
-
if (!cc) return false;
|
|
3524
|
-
const { ocCur, recommendedOc, installed, isLegacy } = cc;
|
|
3525
|
-
if (isForkPlugin(installed)) return false;
|
|
3526
|
-
if (recommendedOc) return resolveUpgradeDirection(installed, ocCur, recommendedOc, isLegacy) === "lark";
|
|
3527
|
-
return isLegacy || !isVersionCompatible(installed, ocCur);
|
|
3528
|
-
}
|
|
3529
|
-
//#endregion
|
|
3530
|
-
//#region src/channels-probe.ts
|
|
3531
|
-
const CHANNEL_LINE_RE = /^-\s+Feishu\s+([^:]+):\s+(.+)$/;
|
|
3532
|
-
/**
|
|
3533
|
-
* Port of Python `_account_is_working` from the feishu-channel-success-rate skill.
|
|
3534
|
-
*
|
|
3535
|
-
* Strips colon-prefixed key:value bits (dm:, bot:, in:, out:, token:, allow:,
|
|
3536
|
-
* intents:, groups:, health:) and evaluates the canonical health formula.
|
|
3537
|
-
*/
|
|
3538
|
-
function accountIsWorking(bits) {
|
|
3539
|
-
const bitTokens = /* @__PURE__ */ new Set();
|
|
3540
|
-
let hasError = false;
|
|
3541
|
-
let hasProbeFailed = false;
|
|
3542
|
-
for (const raw of bits) {
|
|
3543
|
-
const b = raw.trim();
|
|
3544
|
-
if (!b) continue;
|
|
3545
|
-
if (b.startsWith("error:")) {
|
|
3546
|
-
hasError = true;
|
|
3547
|
-
continue;
|
|
3548
|
-
}
|
|
3549
|
-
if (b === "probe failed") {
|
|
3550
|
-
hasProbeFailed = true;
|
|
3551
|
-
continue;
|
|
3552
|
-
}
|
|
3553
|
-
bitTokens.add(b.split(":")[0]);
|
|
3554
|
-
}
|
|
3555
|
-
if (!bitTokens.has("enabled") || !bitTokens.has("configured")) return false;
|
|
3556
|
-
if (bitTokens.has("works")) return true;
|
|
3557
|
-
if (bitTokens.has("running") && !hasError && !hasProbeFailed) return true;
|
|
3558
|
-
return false;
|
|
3559
|
-
}
|
|
3560
|
-
/**
|
|
3561
|
-
* Parse the raw stdout of `openclaw channels status --probe`.
|
|
3562
|
-
* Port of Python `extract_channels_probe` from the feishu-channel-success-rate skill.
|
|
3563
|
-
*/
|
|
3564
|
-
function parseChannelsProbeOutput(text) {
|
|
3565
|
-
const gatewayReachable = text.includes("Gateway reachable");
|
|
3566
|
-
const accounts = [];
|
|
3567
|
-
let anyAccountWorking = false;
|
|
3568
|
-
for (const line of text.split("\n")) {
|
|
3569
|
-
const m = CHANNEL_LINE_RE.exec(line.trim());
|
|
3570
|
-
if (!m) continue;
|
|
3571
|
-
const [, acct, rest] = m;
|
|
3572
|
-
const bits = rest.split(",").map((b) => b.trim());
|
|
3573
|
-
const isWorking = accountIsWorking(bits);
|
|
3574
|
-
if (isWorking) anyAccountWorking = true;
|
|
3575
|
-
accounts.push({
|
|
3576
|
-
id: acct.trim(),
|
|
3577
|
-
bits,
|
|
3578
|
-
isWorking,
|
|
3579
|
-
raw: line.trim()
|
|
3580
|
-
});
|
|
3581
|
-
}
|
|
3582
|
-
return {
|
|
3583
|
-
gatewayReachable,
|
|
3584
|
-
accounts,
|
|
3585
|
-
anyAccountWorking
|
|
3586
|
-
};
|
|
3587
|
-
}
|
|
3588
|
-
/**
|
|
3589
|
-
* Run `openclaw channels status --probe` and return a structured result.
|
|
3590
|
-
*
|
|
3591
|
-
* The command may exit non-zero when some bot accounts fail their probe — that
|
|
3592
|
-
* is still useful output. We therefore try to parse stdout even when the
|
|
3593
|
-
* process exits with a non-zero code, falling back to an unavailable result
|
|
3594
|
-
* only when there is genuinely no output to parse.
|
|
3595
|
-
*
|
|
3596
|
-
* @param timeoutMs Maximum wait time. Default is 60 s because v2026.4.x
|
|
3597
|
-
* lacks a per-request HTTP timeout and can block indefinitely.
|
|
3598
|
-
*/
|
|
3599
|
-
function runChannelsProbe(timeoutMs = 6e4) {
|
|
3600
|
-
let stdout = "";
|
|
3601
|
-
let execError;
|
|
3602
|
-
try {
|
|
3603
|
-
stdout = (0, node_child_process.execSync)("openclaw channels status --probe", {
|
|
3604
|
-
encoding: "utf-8",
|
|
3605
|
-
timeout: timeoutMs,
|
|
3606
|
-
stdio: [
|
|
3607
|
-
"ignore",
|
|
3608
|
-
"pipe",
|
|
3609
|
-
"pipe"
|
|
3610
|
-
]
|
|
3611
|
-
});
|
|
3612
|
-
} catch (e) {
|
|
3613
|
-
const err = e;
|
|
3614
|
-
stdout = err.stdout ?? "";
|
|
3615
|
-
execError = err.message;
|
|
3616
|
-
const stderrRaw = err.stderr;
|
|
3617
|
-
const stderr = (typeof stderrRaw === "string" ? stderrRaw : stderrRaw?.toString("utf-8") ?? "").trim();
|
|
3618
|
-
if (stderr) console.error(`channels-probe: stderr from CLI: ${stderr}`);
|
|
3619
|
-
}
|
|
3620
|
-
if (stdout.trim()) return {
|
|
3621
|
-
available: true,
|
|
3622
|
-
...parseChannelsProbeOutput(stdout)
|
|
3623
|
-
};
|
|
3624
|
-
return {
|
|
3625
|
-
available: false,
|
|
3626
|
-
gatewayReachable: false,
|
|
3627
|
-
accounts: [],
|
|
3628
|
-
anyAccountWorking: false,
|
|
3629
|
-
error: execError ?? "no output from openclaw channels status --probe"
|
|
3630
|
-
};
|
|
3631
|
-
}
|
|
3632
|
-
//#endregion
|
|
3633
|
-
//#region src/rules/upgrade-lark-needed.ts
|
|
3634
|
-
/**
|
|
3635
|
-
* Detects the condition that warrants running `upgrade-lark`:
|
|
3636
|
-
* - feishu plugin version incompatible with current openclaw, AND
|
|
3637
|
-
* - channels are not working.
|
|
3638
|
-
*
|
|
3639
|
-
* Both conditions must be true simultaneously. If version is compatible or
|
|
3640
|
-
* channels are working, the rule passes (no action needed).
|
|
3641
|
-
*
|
|
3642
|
-
* profile: experimental — runs only in full sweep mode, not in standard doctor.
|
|
3643
|
-
* level: silent — telemetry/sweep-only, does not trigger page-level repair UI.
|
|
3644
|
-
*/
|
|
3645
|
-
let UpgradeLarkNeededRule = class UpgradeLarkNeededRule extends DiagnoseRule {
|
|
3646
|
-
validate(ctx) {
|
|
3647
|
-
if (!needsLarkUpgrade(ctx)) return { pass: true };
|
|
3648
|
-
let anyAccountWorking = false;
|
|
3649
|
-
try {
|
|
3650
|
-
anyAccountWorking = runChannelsProbe(3e4).anyAccountWorking;
|
|
3651
|
-
} catch {
|
|
3652
|
-
return { pass: true };
|
|
3653
|
-
}
|
|
3654
|
-
if (anyAccountWorking) return { pass: true };
|
|
3655
|
-
return {
|
|
3656
|
-
pass: false,
|
|
3657
|
-
action: "upgrade_lark",
|
|
3658
|
-
message: "飞书插件版本不兼容且 channels 不可用,建议执行 upgrade-lark 命令升级飞书插件"
|
|
3659
|
-
};
|
|
3660
|
-
}
|
|
3661
|
-
};
|
|
3662
|
-
UpgradeLarkNeededRule = __decorate([Rule({
|
|
3663
|
-
key: "upgrade_lark_needed",
|
|
3664
|
-
description: "检测飞书插件版本不兼容且 channels 不可用,判断是否需要执行 upgrade-lark 升级",
|
|
3665
|
-
dependsOn: ["feishu_plugin_version_compat_lark"],
|
|
3666
|
-
repairMode: "check-only",
|
|
3667
|
-
level: "silent",
|
|
3668
|
-
profile: "experimental",
|
|
3669
|
-
usesVars: ["recommendedOpenclawTag"]
|
|
3670
|
-
})], UpgradeLarkNeededRule);
|
|
3671
3508
|
//#endregion
|
|
3672
3509
|
//#region src/rules/cleanup-install-backup-dirs.ts
|
|
3673
3510
|
const DIR_PREFIX = ".openclaw-install-";
|
|
@@ -3813,6 +3650,119 @@ LarkCliMissingForInstalledLarkPluginRule = __decorate([Rule({
|
|
|
3813
3650
|
usesVars: ["recommendedOpenclawTag"]
|
|
3814
3651
|
})], LarkCliMissingForInstalledLarkPluginRule);
|
|
3815
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
|
|
3816
3766
|
//#region src/check.ts
|
|
3817
3767
|
/** Telemetry-aware entry: returns both the legacy CheckResult (for stdout)
|
|
3818
3768
|
* AND a DoctorReport-shape payload (for `openclaw.report_cli_run`). The
|
|
@@ -4276,9 +4226,6 @@ const PROVIDER_FILE_PATH = "/home/gem/workspace/.force/openclaw/miaoda-provider-
|
|
|
4276
4226
|
const SECRETS_FILE_PATH = "/home/gem/workspace/.force/openclaw/miaoda-openclaw-secrets.json";
|
|
4277
4227
|
/** Absolute path to the openclaw config JSON. */
|
|
4278
4228
|
const CONFIG_PATH = `${WORKSPACE_DIR}/openclaw.json`;
|
|
4279
|
-
function upgradeLarkLogFile(runId) {
|
|
4280
|
-
return `${DIAGNOSE_DIR}/upgrade-lark-${(/* @__PURE__ */ new Date()).toISOString().slice(0, 19).replace(/:/g, "-")}-${runId.slice(0, 8)}.log`;
|
|
4281
|
-
}
|
|
4282
4229
|
//#endregion
|
|
4283
4230
|
//#region src/run-log.ts
|
|
4284
4231
|
let currentRunContext;
|
|
@@ -4415,9 +4362,10 @@ function makeLogger(logFile) {
|
|
|
4415
4362
|
/**
|
|
4416
4363
|
* Start an async reset task: spawn a detached child process and return the taskId.
|
|
4417
4364
|
*
|
|
4418
|
-
* The child process runs: node cli.js reset --worker --task-id=xxx
|
|
4365
|
+
* The child process runs: node cli.js reset --worker --task-id=xxx
|
|
4366
|
+
* The worker fetches ctx from innerApi itself — no --ctx passthrough.
|
|
4419
4367
|
*/
|
|
4420
|
-
function startAsyncReset(
|
|
4368
|
+
function startAsyncReset() {
|
|
4421
4369
|
const taskId = (0, node_crypto.randomUUID)();
|
|
4422
4370
|
const resultFile = resetResultFile(taskId);
|
|
4423
4371
|
const log = makeLogger(resetLogFile(taskId));
|
|
@@ -4441,8 +4389,7 @@ function startAsyncReset(ctxBase64) {
|
|
|
4441
4389
|
process.argv[1],
|
|
4442
4390
|
"reset",
|
|
4443
4391
|
"--worker",
|
|
4444
|
-
`--task-id=${taskId}
|
|
4445
|
-
`--ctx=${ctxBase64}`
|
|
4392
|
+
`--task-id=${taskId}`
|
|
4446
4393
|
], {
|
|
4447
4394
|
detached: true,
|
|
4448
4395
|
stdio: "ignore",
|
|
@@ -6956,6 +6903,60 @@ function mergeCoreBackupAndOrigins(configPath, vars, resetData, log) {
|
|
|
6956
6903
|
log(`allowedOrigins: added ${added.length} (${JSON.stringify(added)}), total now ${mergedOrigins.length}`);
|
|
6957
6904
|
}
|
|
6958
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
|
+
/**
|
|
6959
6960
|
* Step 7: Verify startup scripts landed in configDir/scripts/.
|
|
6960
6961
|
*
|
|
6961
6962
|
* Scripts are extracted directly to configDir/scripts/ during stageTemplate —
|
|
@@ -7100,6 +7101,7 @@ async function runReset(input, taskId, resultFile) {
|
|
|
7100
7101
|
await step5InstallOpenclaw(openclawTag, ossFileMap, log);
|
|
7101
7102
|
step(6);
|
|
7102
7103
|
mergeCoreBackupAndOrigins(configPath, vars, resetData, log);
|
|
7104
|
+
fixBotChannelConfig(configPath, vars.larkApps, log);
|
|
7103
7105
|
step(7);
|
|
7104
7106
|
verifyStartupScripts(configDir, log);
|
|
7105
7107
|
step(8);
|
|
@@ -7898,7 +7900,8 @@ function normalizeCtx(raw) {
|
|
|
7898
7900
|
reset: {
|
|
7899
7901
|
templateVars: r.reset.templateVars ?? {},
|
|
7900
7902
|
coreBackup: r.reset.coreBackup
|
|
7901
|
-
}
|
|
7903
|
+
},
|
|
7904
|
+
larkApps: Array.isArray(r.larkApps) ? r.larkApps : []
|
|
7902
7905
|
};
|
|
7903
7906
|
}
|
|
7904
7907
|
const vars = r.vars ?? {};
|
|
@@ -7923,7 +7926,8 @@ function normalizeCtx(raw) {
|
|
|
7923
7926
|
reset: {
|
|
7924
7927
|
templateVars: resetData.templateVars ?? {},
|
|
7925
7928
|
coreBackup: resetData.coreBackup
|
|
7926
|
-
}
|
|
7929
|
+
},
|
|
7930
|
+
larkApps: Array.isArray(r.larkApps) ? r.larkApps : []
|
|
7927
7931
|
};
|
|
7928
7932
|
}
|
|
7929
7933
|
function fillApp(src) {
|
|
@@ -7988,7 +7992,8 @@ function buildCheckInput(raw, configPathOverride) {
|
|
|
7988
7992
|
providerFilePath: PROVIDER_FILE_PATH,
|
|
7989
7993
|
secretsFilePath: SECRETS_FILE_PATH,
|
|
7990
7994
|
templateVars: ctx.app.templateVars,
|
|
7991
|
-
recommendedOpenclawTag: ctx.app.recommendedOpenclawTag
|
|
7995
|
+
recommendedOpenclawTag: ctx.app.recommendedOpenclawTag,
|
|
7996
|
+
larkApps: ctx.larkApps
|
|
7992
7997
|
},
|
|
7993
7998
|
templateVars: ctx.app.templateVars
|
|
7994
7999
|
};
|
|
@@ -8020,7 +8025,8 @@ function buildRepairInput(raw, configPathOverride) {
|
|
|
8020
8025
|
providerFilePath: PROVIDER_FILE_PATH,
|
|
8021
8026
|
secretsFilePath: SECRETS_FILE_PATH,
|
|
8022
8027
|
templateVars: ctx.app.templateVars,
|
|
8023
|
-
recommendedOpenclawTag: ctx.app.recommendedOpenclawTag
|
|
8028
|
+
recommendedOpenclawTag: ctx.app.recommendedOpenclawTag,
|
|
8029
|
+
larkApps: ctx.larkApps
|
|
8024
8030
|
},
|
|
8025
8031
|
repairData: {
|
|
8026
8032
|
secretsContent: ctx.secrets.secretsContent,
|
|
@@ -8056,7 +8062,8 @@ function buildResetInput(raw, configPathOverride) {
|
|
|
8056
8062
|
providerFilePath: PROVIDER_FILE_PATH,
|
|
8057
8063
|
secretsFilePath: SECRETS_FILE_PATH,
|
|
8058
8064
|
templateVars: ctx.app.templateVars,
|
|
8059
|
-
recommendedOpenclawTag: ctx.app.recommendedOpenclawTag
|
|
8065
|
+
recommendedOpenclawTag: ctx.app.recommendedOpenclawTag,
|
|
8066
|
+
larkApps: ctx.larkApps
|
|
8060
8067
|
},
|
|
8061
8068
|
resetData: {
|
|
8062
8069
|
templateVars: ctx.reset.templateVars,
|
|
@@ -10366,7 +10373,7 @@ async function reportCliRun(opts) {
|
|
|
10366
10373
|
//#region src/help.ts
|
|
10367
10374
|
const BIN = "mclaw-diagnose";
|
|
10368
10375
|
function versionBanner() {
|
|
10369
|
-
return `v0.1.14-alpha.
|
|
10376
|
+
return `v0.1.14-alpha.3`;
|
|
10370
10377
|
}
|
|
10371
10378
|
const COMMANDS = [
|
|
10372
10379
|
{
|
|
@@ -10470,16 +10477,12 @@ EXIT CODES
|
|
|
10470
10477
|
hidden: true,
|
|
10471
10478
|
summary: "Run rule-engine check only",
|
|
10472
10479
|
help: `USAGE
|
|
10473
|
-
${BIN} check
|
|
10480
|
+
${BIN} check
|
|
10474
10481
|
|
|
10475
10482
|
DESCRIPTION
|
|
10476
10483
|
Runs the rule engine against the sandbox's current openclaw config and
|
|
10477
|
-
returns { failedRules }.
|
|
10478
|
-
|
|
10479
|
-
|
|
10480
|
-
OPTIONS
|
|
10481
|
-
--ctx=<base64> Opaque ctx JSON (base64). When absent, fetched from
|
|
10482
|
-
innerapi (same path as doctor).
|
|
10484
|
+
returns { failedRules }. Ctx is fetched from innerapi automatically.
|
|
10485
|
+
End-users should prefer \`doctor\`.
|
|
10483
10486
|
`
|
|
10484
10487
|
},
|
|
10485
10488
|
{
|
|
@@ -10487,16 +10490,11 @@ OPTIONS
|
|
|
10487
10490
|
hidden: true,
|
|
10488
10491
|
summary: "Apply standard-mode repairs",
|
|
10489
10492
|
help: `USAGE
|
|
10490
|
-
${BIN} repair
|
|
10493
|
+
${BIN} repair
|
|
10491
10494
|
|
|
10492
10495
|
DESCRIPTION
|
|
10493
|
-
Runs repair for the failing rules
|
|
10494
|
-
|
|
10495
|
-
\`doctor --fix\` instead.
|
|
10496
|
-
|
|
10497
|
-
OPTIONS
|
|
10498
|
-
--ctx=<base64> Opaque ctx JSON (base64). When absent, fetched from
|
|
10499
|
-
innerapi.
|
|
10496
|
+
Runs repair for the failing rules. Ctx is fetched from innerapi
|
|
10497
|
+
automatically. End-users should use \`doctor --fix\` instead.
|
|
10500
10498
|
`
|
|
10501
10499
|
},
|
|
10502
10500
|
{
|
|
@@ -10504,14 +10502,15 @@ OPTIONS
|
|
|
10504
10502
|
hidden: true,
|
|
10505
10503
|
summary: "Re-initialize sandbox via the 9-step reset pipeline",
|
|
10506
10504
|
help: `USAGE
|
|
10507
|
-
${BIN} reset --async
|
|
10508
|
-
${BIN} reset --worker --task-id=<id>
|
|
10505
|
+
${BIN} reset --async
|
|
10506
|
+
${BIN} reset --worker --task-id=<id>
|
|
10509
10507
|
|
|
10510
10508
|
DESCRIPTION
|
|
10511
10509
|
Two-phase pipeline driven asynchronously: the --async invocation spawns
|
|
10512
10510
|
a detached worker and returns { taskId } immediately; the --worker
|
|
10513
10511
|
invocation (spawned by --async) runs the actual 9 steps and writes
|
|
10514
10512
|
progress to /tmp/openclaw-diagnose/reset-<taskId>.json.
|
|
10513
|
+
Ctx is fetched from innerapi automatically.
|
|
10515
10514
|
|
|
10516
10515
|
Poll progress with \`${BIN} get_reset_task --task-id=<id>\`.
|
|
10517
10516
|
|
|
@@ -10519,7 +10518,6 @@ OPTIONS
|
|
|
10519
10518
|
--async Start a detached worker and return taskId on stdout.
|
|
10520
10519
|
--worker Internal — run the 9-step pipeline (launched by --async).
|
|
10521
10520
|
--task-id=<id> Required with --worker; identifies the progress file.
|
|
10522
|
-
--ctx=<base64> Opaque ctx JSON; fetched from innerapi when absent.
|
|
10523
10521
|
`
|
|
10524
10522
|
},
|
|
10525
10523
|
{
|
|
@@ -10542,7 +10540,7 @@ OPTIONS
|
|
|
10542
10540
|
hidden: true,
|
|
10543
10541
|
summary: "Download + install the openclaw tarball",
|
|
10544
10542
|
help: `USAGE
|
|
10545
|
-
${BIN} install-openclaw <tag> [--
|
|
10543
|
+
${BIN} install-openclaw <tag> [--oss_file_map=<base64>]
|
|
10546
10544
|
|
|
10547
10545
|
DESCRIPTION
|
|
10548
10546
|
Downloads the openclaw@<tag> tgz via the signed OSS URL found in the
|
|
@@ -10554,9 +10552,9 @@ ARGUMENTS
|
|
|
10554
10552
|
<tag> Openclaw version tag, e.g. 2026.4.11.
|
|
10555
10553
|
|
|
10556
10554
|
OPTIONS
|
|
10557
|
-
--ctx=<base64> Opaque ctx; ossFileMap is extracted from it.
|
|
10558
10555
|
--oss_file_map=... Pre-built OSS URL map (base64 JSON); skips innerapi
|
|
10559
|
-
entirely.
|
|
10556
|
+
entirely. When absent, ossFileMap is fetched from
|
|
10557
|
+
innerapi automatically.
|
|
10560
10558
|
`
|
|
10561
10559
|
},
|
|
10562
10560
|
{
|
|
@@ -10582,8 +10580,7 @@ OPTIONS
|
|
|
10582
10580
|
--home_base=<dir> Override the /home/gem base (tests).
|
|
10583
10581
|
--config_path=<p> Override the openclaw.json path (tests).
|
|
10584
10582
|
--skip-config-update Leave plugins.installs in openclaw.json untouched.
|
|
10585
|
-
--
|
|
10586
|
-
--oss_file_map=... Pre-built OSS URL map (base64 JSON).
|
|
10583
|
+
--oss_file_map=... Pre-built OSS URL map (base64 JSON); skips innerapi.
|
|
10587
10584
|
`
|
|
10588
10585
|
},
|
|
10589
10586
|
{
|
|
@@ -10610,7 +10607,6 @@ OPTIONS
|
|
|
10610
10607
|
--cli=<name> CLI package to install by short name or scoped
|
|
10611
10608
|
packageName (repeatable, at least one required).
|
|
10612
10609
|
--home_base=<dir> Override the /home/gem base (tests).
|
|
10613
|
-
--ctx=<base64> Opaque ctx; ossFileMap is extracted from it.
|
|
10614
10610
|
--oss_file_map=... Pre-built OSS URL map (base64 JSON); skips innerapi.
|
|
10615
10611
|
|
|
10616
10612
|
EXAMPLES
|
|
@@ -10664,46 +10660,6 @@ OPTIONS
|
|
|
10664
10660
|
EXIT CODES
|
|
10665
10661
|
0 Success or skipped (prerequisites not met).
|
|
10666
10662
|
1 Secret/path unresolvable, lark-cli failed, or config unreadable.
|
|
10667
|
-
`
|
|
10668
|
-
},
|
|
10669
|
-
{
|
|
10670
|
-
name: "upgrade-lark",
|
|
10671
|
-
hidden: false,
|
|
10672
|
-
summary: "Upgrade the Feishu/Lark plugin via @larksuite/openclaw-lark-tools",
|
|
10673
|
-
help: `USAGE
|
|
10674
|
-
${BIN} upgrade-lark [--scene=<scene>] [--caller=<n>] [--trace-id=<id>]
|
|
10675
|
-
|
|
10676
|
-
DESCRIPTION
|
|
10677
|
-
Upgrades the Feishu/Lark plugin by running:
|
|
10678
|
-
npx -y @larksuite/openclaw-lark-tools update --use-existing
|
|
10679
|
-
|
|
10680
|
-
Before the upgrade, the following files are backed up:
|
|
10681
|
-
- openclaw.json
|
|
10682
|
-
- extensions/openclaw-lark/ (if present)
|
|
10683
|
-
- extensions/feishu-openclaw-plugin/ (if present)
|
|
10684
|
-
After the upgrade, the result is validated:
|
|
10685
|
-
- feishu.accounts bot count must not decrease
|
|
10686
|
-
- gateway config structure must remain valid (port/mode/bind/auth/trustedProxies)
|
|
10687
|
-
If the upgrade command fails, or validation fails, the backed-up files are
|
|
10688
|
-
restored to roll back the changes.
|
|
10689
|
-
|
|
10690
|
-
Execution is logged to /tmp/openclaw-diagnose/upgrade-lark-<runId>.log.
|
|
10691
|
-
|
|
10692
|
-
Output is a single JSON object on stdout:
|
|
10693
|
-
{ "ok": true, "stdout": "...", "stderr": "...", "logFile": "..." }
|
|
10694
|
-
{ "ok": false, "error": "...", "stderr": "...", "exitCode": 1,
|
|
10695
|
-
"rollbackOk": true, "validationError": "...", "logFile": "..." }
|
|
10696
|
-
|
|
10697
|
-
OPTIONS
|
|
10698
|
-
--scene=<scene> Telemetry label forwarded to Slardar only.
|
|
10699
|
-
Known values: PageUpgradeLark, etc. Custom strings accepted.
|
|
10700
|
-
--caller=<name> Optional metadata passed to innerapi.
|
|
10701
|
-
--trace-id=<id> Optional log-correlation id.
|
|
10702
|
-
|
|
10703
|
-
EXIT CODES
|
|
10704
|
-
0 Success: upgrade ran and all validations passed.
|
|
10705
|
-
1 Failure: npx error, validation failed, or git commit failed.
|
|
10706
|
-
File rollback was attempted (see rollbackOk in the JSON output).
|
|
10707
10663
|
`
|
|
10708
10664
|
},
|
|
10709
10665
|
{
|
|
@@ -10737,41 +10693,6 @@ EXAMPLES
|
|
|
10737
10693
|
${BIN} rules # all rules
|
|
10738
10694
|
${BIN} rules --rule=gateway # single rule
|
|
10739
10695
|
${BIN} rules --rule=gateway --rule=feishu_channel # multiple rules
|
|
10740
|
-
`
|
|
10741
|
-
},
|
|
10742
|
-
{
|
|
10743
|
-
name: "channels-probe",
|
|
10744
|
-
hidden: true,
|
|
10745
|
-
summary: "Check feishu channel health via openclaw channels status --probe",
|
|
10746
|
-
help: `USAGE
|
|
10747
|
-
${BIN} channels-probe [--timeout=<ms>]
|
|
10748
|
-
|
|
10749
|
-
DESCRIPTION
|
|
10750
|
-
Runs \`openclaw channels status --probe\` and returns a structured JSON
|
|
10751
|
-
summary of whether the current environment's feishu channels are
|
|
10752
|
-
configured and working correctly.
|
|
10753
|
-
|
|
10754
|
-
Output:
|
|
10755
|
-
{
|
|
10756
|
-
"available": true,
|
|
10757
|
-
"gatewayReachable": true,
|
|
10758
|
-
"accounts": [
|
|
10759
|
-
{ "id": "default", "bits": ["enabled","configured","running","works"],
|
|
10760
|
-
"isWorking": true, "raw": "- Feishu default: ..." }
|
|
10761
|
-
],
|
|
10762
|
-
"anyAccountWorking": true
|
|
10763
|
-
}
|
|
10764
|
-
|
|
10765
|
-
An account is considered working when:
|
|
10766
|
-
enabled ∧ configured ∧ ( works ∨ ( running ∧ no error: ∧ no probe failed ) )
|
|
10767
|
-
|
|
10768
|
-
"available": false means the CLI invocation itself failed (openclaw not
|
|
10769
|
-
found, gateway unreachable, or no parseable output returned).
|
|
10770
|
-
|
|
10771
|
-
OPTIONS
|
|
10772
|
-
--timeout=<ms> Max wait in milliseconds (default: 60000). The probe
|
|
10773
|
-
can hang indefinitely on openclaw v2026.4.x due to a
|
|
10774
|
-
missing per-request HTTP timeout — set this accordingly.
|
|
10775
10696
|
`
|
|
10776
10697
|
},
|
|
10777
10698
|
{
|
|
@@ -10792,8 +10713,7 @@ OPTIONS
|
|
|
10792
10713
|
--role=<role> Package role (e.g. template, config).
|
|
10793
10714
|
--name=<name> Package name within the role.
|
|
10794
10715
|
--dir=<dir> Target dir (defaults to dirname(pkg.installPath)).
|
|
10795
|
-
--
|
|
10796
|
-
--oss_file_map=... Pre-built OSS URL map (base64 JSON).
|
|
10716
|
+
--oss_file_map=... Pre-built OSS URL map (base64 JSON); skips innerapi.
|
|
10797
10717
|
`
|
|
10798
10718
|
}
|
|
10799
10719
|
];
|
|
@@ -10869,31 +10789,31 @@ function planVarsFields(opts = {}) {
|
|
|
10869
10789
|
*
|
|
10870
10790
|
* Per-command group needs:
|
|
10871
10791
|
*
|
|
10872
|
-
* doctor / check app
|
|
10873
|
-
* repair app + secrets
|
|
10874
|
-
* reset app + secrets + install + reset
|
|
10792
|
+
* doctor / check app + larkApps
|
|
10793
|
+
* repair app + secrets + larkApps
|
|
10794
|
+
* reset app + secrets + install + reset + larkApps
|
|
10875
10795
|
* install-* install only
|
|
10876
10796
|
*
|
|
10877
10797
|
* Empty result (`{}`) means "no group needed" — the CLI can skip the
|
|
10878
10798
|
* `fetchCtxViaInnerApi` call entirely and run with a synthetic empty ctx.
|
|
10879
|
-
* Happens e.g. when the user pinned `--rule=<key>` to a vars-free rule on
|
|
10880
|
-
* `doctor`.
|
|
10881
10799
|
*/
|
|
10882
10800
|
function planCtxPopulate(opts) {
|
|
10883
10801
|
if (opts.command === "install") return { install: true };
|
|
10884
10802
|
const populate = {};
|
|
10885
|
-
|
|
10803
|
+
if (planVarsFields({
|
|
10886
10804
|
disabled: opts.disabled,
|
|
10887
10805
|
onlyRules: opts.onlyRules,
|
|
10888
10806
|
profile: opts.profile
|
|
10889
|
-
});
|
|
10890
|
-
if (
|
|
10891
|
-
|
|
10892
|
-
|
|
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") {
|
|
10893
10812
|
populate.secrets = true;
|
|
10894
10813
|
populate.install = true;
|
|
10895
10814
|
populate.reset = true;
|
|
10896
|
-
|
|
10815
|
+
populate.larkApps = true;
|
|
10816
|
+
} else if (opts.command === "doctor" || opts.command === "check") populate.larkApps = true;
|
|
10897
10817
|
return populate;
|
|
10898
10818
|
}
|
|
10899
10819
|
//#endregion
|
|
@@ -10947,408 +10867,11 @@ function reportDoctorRunToSlardar(opts) {
|
|
|
10947
10867
|
}
|
|
10948
10868
|
});
|
|
10949
10869
|
}
|
|
10950
|
-
function readLogFile(filePath) {
|
|
10951
|
-
try {
|
|
10952
|
-
return node_fs.default.readFileSync(filePath, "utf-8");
|
|
10953
|
-
} catch {
|
|
10954
|
-
return "";
|
|
10955
|
-
}
|
|
10956
|
-
}
|
|
10957
|
-
function reportUpgradeLarkToSlardar(opts) {
|
|
10958
|
-
console.error(`[slardar] upgrade_lark_run scene=${opts.scene ?? ""} success=${opts.success} exitCode=${opts.exitCode ?? ""} rollbackOk=${opts.rollbackOk ?? ""}`);
|
|
10959
|
-
const logContent = readLogFile(opts.logFile);
|
|
10960
|
-
reportTask({
|
|
10961
|
-
eventName: "upgrade_lark_run",
|
|
10962
|
-
durationMs: opts.durationMs,
|
|
10963
|
-
status: opts.success ? "success" : "failed",
|
|
10964
|
-
extraCategories: {
|
|
10965
|
-
scene: opts.scene ?? "",
|
|
10966
|
-
exit_code: String(opts.exitCode ?? ""),
|
|
10967
|
-
rollback_ok: opts.rollbackOk != null ? String(opts.rollbackOk) : "",
|
|
10968
|
-
validation_error: opts.validationError ?? "",
|
|
10969
|
-
error_msg: opts.error ?? "",
|
|
10970
|
-
log_content: logContent
|
|
10971
|
-
}
|
|
10972
|
-
});
|
|
10973
|
-
}
|
|
10974
|
-
//#endregion
|
|
10975
|
-
//#region src/upgrade-lark.ts
|
|
10976
|
-
/** Plugin directories under extensions/ that are backed up before upgrade */
|
|
10977
|
-
const FEISHU_PLUGIN_DIRS = ["openclaw-lark", "feishu-openclaw-plugin"];
|
|
10978
|
-
/** Version compat rule keys checked in the doctor output after install */
|
|
10979
|
-
const VERSION_COMPAT_RULE_KEYS = ["feishu_plugin_version_compat_lark", "feishu_plugin_version_compat_openclaw"];
|
|
10980
|
-
function backupFiles(opts) {
|
|
10981
|
-
const { workspaceDir, configPath, backupDir, log } = opts;
|
|
10982
|
-
try {
|
|
10983
|
-
node_fs.default.mkdirSync(backupDir, { recursive: true });
|
|
10984
|
-
log(`backup dir: ${backupDir}`);
|
|
10985
|
-
if (node_fs.default.existsSync(configPath)) {
|
|
10986
|
-
const stat = node_fs.default.statSync(configPath);
|
|
10987
|
-
node_fs.default.copyFileSync(configPath, node_path.default.join(backupDir, "openclaw.json"));
|
|
10988
|
-
log(` backed up: openclaw.json (${stat.size} bytes)`);
|
|
10989
|
-
} else log(` skipped: openclaw.json (not found)`);
|
|
10990
|
-
const extSrc = node_path.default.join(workspaceDir, "extensions");
|
|
10991
|
-
for (const pluginDir of FEISHU_PLUGIN_DIRS) {
|
|
10992
|
-
const src = node_path.default.join(extSrc, pluginDir);
|
|
10993
|
-
if (node_fs.default.existsSync(src)) {
|
|
10994
|
-
const dst = node_path.default.join(backupDir, "extensions", pluginDir);
|
|
10995
|
-
node_fs.default.cpSync(src, dst, { recursive: true });
|
|
10996
|
-
const version = readPkgVersion(node_path.default.join(src, "package.json"));
|
|
10997
|
-
log(` backed up: extensions/${pluginDir}${version ? ` (version: ${version})` : ""}`);
|
|
10998
|
-
} else log(` skipped: extensions/${pluginDir} (not found)`);
|
|
10999
|
-
}
|
|
11000
|
-
return { ok: true };
|
|
11001
|
-
} catch (e) {
|
|
11002
|
-
return {
|
|
11003
|
-
ok: false,
|
|
11004
|
-
error: `backup failed: ${e.message}`
|
|
11005
|
-
};
|
|
11006
|
-
}
|
|
11007
|
-
}
|
|
11008
|
-
function restoreFiles(opts) {
|
|
11009
|
-
const { workspaceDir, configPath, backupDir, log } = opts;
|
|
11010
|
-
try {
|
|
11011
|
-
const configBackup = node_path.default.join(backupDir, "openclaw.json");
|
|
11012
|
-
if (node_fs.default.existsSync(configBackup)) {
|
|
11013
|
-
node_fs.default.copyFileSync(configBackup, configPath);
|
|
11014
|
-
log(` restored: openclaw.json`);
|
|
11015
|
-
}
|
|
11016
|
-
const extDst = node_path.default.join(workspaceDir, "extensions");
|
|
11017
|
-
for (const pluginDir of FEISHU_PLUGIN_DIRS) {
|
|
11018
|
-
const backupSrc = node_path.default.join(backupDir, "extensions", pluginDir);
|
|
11019
|
-
if (node_fs.default.existsSync(backupSrc)) {
|
|
11020
|
-
const dst = node_path.default.join(extDst, pluginDir);
|
|
11021
|
-
if (node_fs.default.existsSync(dst)) node_fs.default.rmSync(dst, {
|
|
11022
|
-
recursive: true,
|
|
11023
|
-
force: true
|
|
11024
|
-
});
|
|
11025
|
-
node_fs.default.cpSync(backupSrc, dst, { recursive: true });
|
|
11026
|
-
log(` restored: extensions/${pluginDir}`);
|
|
11027
|
-
}
|
|
11028
|
-
}
|
|
11029
|
-
return true;
|
|
11030
|
-
} catch (e) {
|
|
11031
|
-
log(` restore error: ${e.message}`);
|
|
11032
|
-
return false;
|
|
11033
|
-
}
|
|
11034
|
-
}
|
|
11035
|
-
function readPkgVersion(pkgPath) {
|
|
11036
|
-
try {
|
|
11037
|
-
const pkg = JSON.parse(node_fs.default.readFileSync(pkgPath, "utf-8"));
|
|
11038
|
-
return typeof pkg.version === "string" ? pkg.version : null;
|
|
11039
|
-
} catch {
|
|
11040
|
-
return null;
|
|
11041
|
-
}
|
|
11042
|
-
}
|
|
11043
|
-
function snapshotVersions(cwd, log) {
|
|
11044
|
-
const ocResult = (0, node_child_process.spawnSync)("openclaw", ["--version"], {
|
|
11045
|
-
cwd,
|
|
11046
|
-
encoding: "utf-8",
|
|
11047
|
-
stdio: [
|
|
11048
|
-
"ignore",
|
|
11049
|
-
"pipe",
|
|
11050
|
-
"pipe"
|
|
11051
|
-
],
|
|
11052
|
-
timeout: 5e3
|
|
11053
|
-
});
|
|
11054
|
-
const ocRaw = (ocResult.stdout ?? "").trim() || (ocResult.stderr ?? "").trim();
|
|
11055
|
-
const extDir = node_path.default.join(cwd, "extensions");
|
|
11056
|
-
const larkPkg = node_path.default.join(extDir, "openclaw-lark", "package.json");
|
|
11057
|
-
const feishuPkg = node_path.default.join(extDir, "feishu-openclaw-plugin", "package.json");
|
|
11058
|
-
log(` version-check paths: ${larkPkg} [${node_fs.default.existsSync(larkPkg) ? "exists" : "missing"}]`);
|
|
11059
|
-
log(` version-check paths: ${feishuPkg} [${node_fs.default.existsSync(feishuPkg) ? "exists" : "missing"}]`);
|
|
11060
|
-
return {
|
|
11061
|
-
openclaw: ocRaw || null,
|
|
11062
|
-
openclawLark: readPkgVersion(larkPkg),
|
|
11063
|
-
feishuOpenclawPlugin: readPkgVersion(feishuPkg)
|
|
11064
|
-
};
|
|
11065
|
-
}
|
|
11066
|
-
function logVersionSnapshot(label, v, log) {
|
|
11067
|
-
log(`${label}: openclaw=${v.openclaw ?? "n/a"} openclaw-lark=${v.openclawLark ?? "n/a"} feishu-openclaw-plugin=${v.feishuOpenclawPlugin ?? "n/a"}`);
|
|
11068
|
-
}
|
|
11069
|
-
function countFeishuBots(configPath) {
|
|
11070
|
-
try {
|
|
11071
|
-
const raw = node_fs.default.readFileSync(configPath, "utf-8");
|
|
11072
|
-
const config = loadJSON5().parse(raw);
|
|
11073
|
-
const accounts = getNestedMap(config, "channels", "feishu", "accounts");
|
|
11074
|
-
if (accounts) return Object.keys(accounts).length;
|
|
11075
|
-
const feishu = getNestedMap(config, "channels", "feishu");
|
|
11076
|
-
return typeof feishu?.appId === "string" && feishu.appId ? 1 : 0;
|
|
11077
|
-
} catch {
|
|
11078
|
-
return 0;
|
|
11079
|
-
}
|
|
11080
|
-
}
|
|
11081
|
-
/**
|
|
11082
|
-
* Parse doctor stdout (first JSON line) and return an error string if any
|
|
11083
|
-
* version compat rule failed. Returns null on parse failure so a broken doctor
|
|
11084
|
-
* output does not block the install.
|
|
11085
|
-
*/
|
|
11086
|
-
function checkVersionCompatFromDoctorOutput(stdout, log) {
|
|
11087
|
-
const firstLine = stdout.split("\n")[0]?.trim();
|
|
11088
|
-
if (!firstLine) {
|
|
11089
|
-
log(" doctor(compat): empty output, skipping version compat check");
|
|
11090
|
-
return null;
|
|
11091
|
-
}
|
|
11092
|
-
try {
|
|
11093
|
-
const report = JSON.parse(firstLine);
|
|
11094
|
-
for (const outcome of report.results) if (VERSION_COMPAT_RULE_KEYS.includes(outcome.rule)) {
|
|
11095
|
-
if (outcome.status === "failed" || outcome.status === "still-broken" || outcome.status === "error") return `version compat rule ${outcome.rule} ${outcome.status}: ${outcome.message ?? "(no message)"}`;
|
|
11096
|
-
}
|
|
11097
|
-
return null;
|
|
11098
|
-
} catch (e) {
|
|
11099
|
-
log(` doctor(compat): failed to parse output — ${e.message}`);
|
|
11100
|
-
return null;
|
|
11101
|
-
}
|
|
11102
|
-
}
|
|
11103
|
-
/** Run channels probe, log results, and return the result. Never throws. */
|
|
11104
|
-
function probeChannels(label, log, timeoutMs) {
|
|
11105
|
-
try {
|
|
11106
|
-
const r = runChannelsProbe(timeoutMs);
|
|
11107
|
-
log(` ${label} available=${r.available} anyAccountWorking=${r.anyAccountWorking}`);
|
|
11108
|
-
if (r.error) log(` ${label} error: ${r.error}`);
|
|
11109
|
-
if (r.gatewayReachable != null) log(` ${label} gatewayReachable: ${r.gatewayReachable}`);
|
|
11110
|
-
for (const acct of r.accounts ?? []) log(` ${label} account ${acct.id}: isWorking=${acct.isWorking} bits=[${acct.bits.join(",")}]`);
|
|
11111
|
-
return r;
|
|
11112
|
-
} catch (e) {
|
|
11113
|
-
log(` ${label} channels probe threw: ${e.message}`);
|
|
11114
|
-
return {
|
|
11115
|
-
available: false,
|
|
11116
|
-
accounts: [],
|
|
11117
|
-
anyAccountWorking: false
|
|
11118
|
-
};
|
|
11119
|
-
}
|
|
11120
|
-
}
|
|
11121
|
-
function runUpgradeLark(opts) {
|
|
11122
|
-
const cwd = opts.cwd ?? "/home/gem/workspace/agent";
|
|
11123
|
-
const configPath = opts.configPath ?? CONFIG_PATH;
|
|
11124
|
-
const logFile = upgradeLarkLogFile(opts.runId);
|
|
11125
|
-
const log = makeLogger(logFile);
|
|
11126
|
-
const fsOpts = {
|
|
11127
|
-
workspaceDir: cwd,
|
|
11128
|
-
configPath,
|
|
11129
|
-
backupDir: node_path.default.join(opts.backupBaseDir ?? "/tmp/openclaw-diagnose", `upgrade-lark-backup-${opts.runId}`),
|
|
11130
|
-
log
|
|
11131
|
-
};
|
|
11132
|
-
const cliScript = opts.cliScript ?? process.argv[1];
|
|
11133
|
-
const statusCheckDelayMs = opts.statusCheckDelayMs ?? 5e3;
|
|
11134
|
-
log(`${"=".repeat(60)}`);
|
|
11135
|
-
log(`upgrade-lark started runId=${opts.runId}`);
|
|
11136
|
-
log(` cwd : ${cwd}`);
|
|
11137
|
-
log(` configPath : ${configPath}`);
|
|
11138
|
-
log(`${"=".repeat(60)}`);
|
|
11139
|
-
log("");
|
|
11140
|
-
log("── [Pre-check A] channels probe(升级前)────────────────");
|
|
11141
|
-
const beforeChannels = probeChannels("before", log, 3e4);
|
|
11142
|
-
log("");
|
|
11143
|
-
log("── [Pre-check B] 版本兼容预检 ───────────────────────────");
|
|
11144
|
-
let versionNeedsUpgrade = false;
|
|
11145
|
-
try {
|
|
11146
|
-
const rawConfig = node_fs.default.readFileSync(configPath, "utf-8");
|
|
11147
|
-
versionNeedsUpgrade = needsLarkUpgrade({
|
|
11148
|
-
config: loadJSON5().parse(rawConfig),
|
|
11149
|
-
configPath,
|
|
11150
|
-
vars: {},
|
|
11151
|
-
providerDeps: {
|
|
11152
|
-
usesMiaodaProvider: false,
|
|
11153
|
-
usesMiaodaSecretProvider: false
|
|
11154
|
-
}
|
|
11155
|
-
});
|
|
11156
|
-
log(` version-compat pre-check: ${versionNeedsUpgrade ? "NEEDS_UPGRADE" : "ok"}`);
|
|
11157
|
-
} catch (e) {
|
|
11158
|
-
log(` version-compat pre-check error: ${e.message} — treating as needs-upgrade`);
|
|
11159
|
-
versionNeedsUpgrade = true;
|
|
11160
|
-
}
|
|
11161
|
-
log("");
|
|
11162
|
-
log("── [Gate] 升级前置条件检查 ───────────────────────────────");
|
|
11163
|
-
log(` version needs upgrade : ${versionNeedsUpgrade}`);
|
|
11164
|
-
log(` channels working before: ${beforeChannels.anyAccountWorking}`);
|
|
11165
|
-
if (!versionNeedsUpgrade) {
|
|
11166
|
-
const reason = "version already compatible — upgrade not needed";
|
|
11167
|
-
log(` SKIP: ${reason}`);
|
|
11168
|
-
log(`${"=".repeat(60)}`);
|
|
11169
|
-
log("upgrade-lark skipped (pre-check gate)");
|
|
11170
|
-
log(`${"=".repeat(60)}`);
|
|
11171
|
-
return {
|
|
11172
|
-
ok: true,
|
|
11173
|
-
skipped: true,
|
|
11174
|
-
skipReason: reason,
|
|
11175
|
-
logFile
|
|
11176
|
-
};
|
|
11177
|
-
}
|
|
11178
|
-
if (beforeChannels.anyAccountWorking) {
|
|
11179
|
-
const reason = "channels are working — upgrade not needed (version mismatch but system is functional)";
|
|
11180
|
-
log(` SKIP: ${reason}`);
|
|
11181
|
-
log(`${"=".repeat(60)}`);
|
|
11182
|
-
log("upgrade-lark skipped (pre-check gate)");
|
|
11183
|
-
log(`${"=".repeat(60)}`);
|
|
11184
|
-
return {
|
|
11185
|
-
ok: true,
|
|
11186
|
-
skipped: true,
|
|
11187
|
-
skipReason: reason,
|
|
11188
|
-
logFile
|
|
11189
|
-
};
|
|
11190
|
-
}
|
|
11191
|
-
log(" PROCEED: version incompatible AND channels not working → running upgrade");
|
|
11192
|
-
log("");
|
|
11193
|
-
log("── [1/6] 文件备份 ────────────────────────────────────────");
|
|
11194
|
-
log(`before-state: botCount=${countFeishuBots(configPath)}`);
|
|
11195
|
-
const backup = backupFiles(fsOpts);
|
|
11196
|
-
if (!backup.ok) {
|
|
11197
|
-
log(`ERROR: ${backup.error}`);
|
|
11198
|
-
return {
|
|
11199
|
-
ok: false,
|
|
11200
|
-
error: backup.error,
|
|
11201
|
-
logFile
|
|
11202
|
-
};
|
|
11203
|
-
}
|
|
11204
|
-
log("backup: ok");
|
|
11205
|
-
logVersionSnapshot("before-versions", snapshotVersions(cwd, log), log);
|
|
11206
|
-
log("");
|
|
11207
|
-
log("── [2/6] 清理本地 openclaw shim ─────────────────────────");
|
|
11208
|
-
const localOpenclawBin = node_path.default.join(cwd, "node_modules", ".bin", "openclaw");
|
|
11209
|
-
if (node_fs.default.existsSync(localOpenclawBin)) try {
|
|
11210
|
-
node_fs.default.rmSync(localOpenclawBin);
|
|
11211
|
-
log(` removed: ${localOpenclawBin}`);
|
|
11212
|
-
} catch (e) {
|
|
11213
|
-
log(` WARN: failed to remove ${localOpenclawBin}: ${e.message}`);
|
|
11214
|
-
}
|
|
11215
|
-
else log(` skipped: ${localOpenclawBin} (not found)`);
|
|
11216
|
-
log("");
|
|
11217
|
-
log("── [3/6] npx install (@larksuite/openclaw-lark-tools update) ──");
|
|
11218
|
-
const npxResult = (0, node_child_process.spawnSync)("npx", [
|
|
11219
|
-
"-y",
|
|
11220
|
-
"@larksuite/openclaw-lark-tools",
|
|
11221
|
-
"update"
|
|
11222
|
-
], {
|
|
11223
|
-
cwd,
|
|
11224
|
-
encoding: "utf-8",
|
|
11225
|
-
stdio: [
|
|
11226
|
-
"ignore",
|
|
11227
|
-
"pipe",
|
|
11228
|
-
"pipe"
|
|
11229
|
-
],
|
|
11230
|
-
timeout: 12e4
|
|
11231
|
-
});
|
|
11232
|
-
const npxStdout = npxResult.stdout?.trim() ?? "";
|
|
11233
|
-
const npxStderr = npxResult.stderr?.trim() ?? "";
|
|
11234
|
-
const npxExitCode = npxResult.status ?? 1;
|
|
11235
|
-
if (npxStdout) log(`npx stdout:\n${npxStdout}`);
|
|
11236
|
-
if (npxStderr) log(`npx stderr:\n${npxStderr}`);
|
|
11237
|
-
log(`npx exit: ${npxExitCode}${npxResult.error ? ` error: ${npxResult.error.message}` : ""}`);
|
|
11238
|
-
if (statusCheckDelayMs > 0) {
|
|
11239
|
-
log("");
|
|
11240
|
-
log(`── 等待 ${statusCheckDelayMs / 1e3}s(让 openclaw 服务完成重启) ─────────────`);
|
|
11241
|
-
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, statusCheckDelayMs);
|
|
11242
|
-
log("wait done");
|
|
11243
|
-
}
|
|
11244
|
-
const doRollback = (reason) => {
|
|
11245
|
-
log(`ERROR: ${reason}`);
|
|
11246
|
-
const rollbackOk = restoreFiles(fsOpts);
|
|
11247
|
-
log(`rollback: ${rollbackOk ? "ok" : "FAILED"}`);
|
|
11248
|
-
return {
|
|
11249
|
-
ok: false,
|
|
11250
|
-
error: reason,
|
|
11251
|
-
validationError: reason,
|
|
11252
|
-
stdout: npxStdout,
|
|
11253
|
-
stderr: npxStderr,
|
|
11254
|
-
exitCode: npxExitCode,
|
|
11255
|
-
rollbackOk,
|
|
11256
|
-
logFile
|
|
11257
|
-
};
|
|
11258
|
-
};
|
|
11259
|
-
log("");
|
|
11260
|
-
log("── [4/6] 插件安装检查 + 版本兼容校验 ───────────────────");
|
|
11261
|
-
const larkExtDir = node_path.default.join(cwd, "extensions", "openclaw-lark");
|
|
11262
|
-
const larkVersion = readPkgVersion(node_path.default.join(larkExtDir, "package.json"));
|
|
11263
|
-
log(` extensions/openclaw-lark: ${node_fs.default.existsSync(larkExtDir) ? "exists" : "missing"}, version=${larkVersion ?? "n/a"}`);
|
|
11264
|
-
if (!node_fs.default.existsSync(larkExtDir)) return doRollback("extensions/openclaw-lark not found after install");
|
|
11265
|
-
if (!larkVersion) return doRollback("extensions/openclaw-lark/package.json has no valid version after install");
|
|
11266
|
-
log(" running doctor version compat check...");
|
|
11267
|
-
const compatArgs = ["doctor"];
|
|
11268
|
-
if (opts.scene) compatArgs.push(`--scene=${opts.scene}`);
|
|
11269
|
-
const compatResult = (0, node_child_process.spawnSync)(process.execPath, [cliScript, ...compatArgs], {
|
|
11270
|
-
cwd,
|
|
11271
|
-
encoding: "utf-8",
|
|
11272
|
-
stdio: [
|
|
11273
|
-
"ignore",
|
|
11274
|
-
"pipe",
|
|
11275
|
-
"pipe"
|
|
11276
|
-
],
|
|
11277
|
-
timeout: 6e4,
|
|
11278
|
-
env: process.env
|
|
11279
|
-
});
|
|
11280
|
-
if (compatResult.stdout?.trim()) log(`doctor(compat) stdout:\n${compatResult.stdout.trim()}`);
|
|
11281
|
-
if (compatResult.stderr?.trim()) log(`doctor(compat) stderr:\n${compatResult.stderr.trim()}`);
|
|
11282
|
-
log(`doctor(compat) exit: ${compatResult.status ?? "null"}${compatResult.error ? ` error: ${compatResult.error.message}` : ""}`);
|
|
11283
|
-
const compatError = checkVersionCompatFromDoctorOutput(compatResult.stdout?.trim() ?? "", log);
|
|
11284
|
-
if (compatError) return doRollback(compatError);
|
|
11285
|
-
log(" version compat: ok");
|
|
11286
|
-
logVersionSnapshot("after-versions", snapshotVersions(cwd, log), log);
|
|
11287
|
-
log("");
|
|
11288
|
-
log("── [5/6] channels probe(升级后)────────────────────────");
|
|
11289
|
-
if (!probeChannels("after", log, 3e4).anyAccountWorking) {
|
|
11290
|
-
log(" channels: not working before or after install — pre-existing issue, skipping rollback");
|
|
11291
|
-
return {
|
|
11292
|
-
ok: false,
|
|
11293
|
-
error: "channels probe: no working account (pre-existing issue, not caused by install)",
|
|
11294
|
-
validationError: "channels probe: no working account (pre-existing)",
|
|
11295
|
-
stdout: npxStdout,
|
|
11296
|
-
stderr: npxStderr,
|
|
11297
|
-
exitCode: npxExitCode,
|
|
11298
|
-
logFile
|
|
11299
|
-
};
|
|
11300
|
-
}
|
|
11301
|
-
log(" channels: ok (recovered after install)");
|
|
11302
|
-
log("");
|
|
11303
|
-
log("── [6/6] doctor --fix ────────────────────────────────────");
|
|
11304
|
-
const fixArgs = ["doctor", "--fix"];
|
|
11305
|
-
if (opts.scene) fixArgs.push(`--scene=${opts.scene}`);
|
|
11306
|
-
const fixResult = (0, node_child_process.spawnSync)(process.execPath, [cliScript, ...fixArgs], {
|
|
11307
|
-
cwd,
|
|
11308
|
-
encoding: "utf-8",
|
|
11309
|
-
stdio: [
|
|
11310
|
-
"ignore",
|
|
11311
|
-
"pipe",
|
|
11312
|
-
"pipe"
|
|
11313
|
-
],
|
|
11314
|
-
timeout: 6e4,
|
|
11315
|
-
env: process.env
|
|
11316
|
-
});
|
|
11317
|
-
if (fixResult.stdout?.trim()) log(`doctor(fix) stdout:\n${fixResult.stdout.trim()}`);
|
|
11318
|
-
if (fixResult.stderr?.trim()) log(`doctor(fix) stderr:\n${fixResult.stderr.trim()}`);
|
|
11319
|
-
log(`doctor(fix) exit: ${fixResult.status ?? "null"}${fixResult.error ? ` error: ${fixResult.error.message}` : ""}`);
|
|
11320
|
-
log("");
|
|
11321
|
-
log(`${"=".repeat(60)}`);
|
|
11322
|
-
log("upgrade-lark completed successfully");
|
|
11323
|
-
log(`${"=".repeat(60)}`);
|
|
11324
|
-
return {
|
|
11325
|
-
ok: true,
|
|
11326
|
-
stdout: npxStdout,
|
|
11327
|
-
stderr: npxStderr,
|
|
11328
|
-
exitCode: npxExitCode,
|
|
11329
|
-
logFile
|
|
11330
|
-
};
|
|
11331
|
-
}
|
|
11332
10870
|
//#endregion
|
|
11333
10871
|
//#region src/index.ts
|
|
11334
10872
|
const args = node_process.default.argv.slice(2);
|
|
11335
10873
|
const mode = args.find((a) => !a.startsWith("-"));
|
|
11336
10874
|
/**
|
|
11337
|
-
* Decode `--ctx=<base64>` into an opaque JSON object. Returns undefined when
|
|
11338
|
-
* the flag isn't present — the caller decides whether to fall back to the
|
|
11339
|
-
* innerapi or to error out.
|
|
11340
|
-
*
|
|
11341
|
-
* The object's shape is not enforced here; downstream code consumes it via
|
|
11342
|
-
* either `normalizeCtx()` (new path) or direct field access for the legacy
|
|
11343
|
-
* check/repair/reset contract still used by sandbox_console push.
|
|
11344
|
-
*/
|
|
11345
|
-
function parseCtxFlag(args) {
|
|
11346
|
-
const ctxArg = args.find((a) => a.startsWith("--ctx="));
|
|
11347
|
-
if (!ctxArg) return void 0;
|
|
11348
|
-
const b64 = ctxArg.slice(6);
|
|
11349
|
-
return JSON.parse(Buffer.from(b64, "base64").toString("utf-8"));
|
|
11350
|
-
}
|
|
11351
|
-
/**
|
|
11352
10875
|
* Pull the first non-flag positional after the mode name.
|
|
11353
10876
|
* (The mode itself is args[0] in the filtered set, so we skip index 0.)
|
|
11354
10877
|
*/
|
|
@@ -11376,8 +10899,8 @@ function getMultiFlag(args, name) {
|
|
|
11376
10899
|
* case but is no longer consulted.
|
|
11377
10900
|
*/
|
|
11378
10901
|
async function reportRun(command, rc, _raw, invocation, durationMs, outcome, slardar = {
|
|
11379
|
-
scene,
|
|
11380
|
-
profile,
|
|
10902
|
+
scene: void 0,
|
|
10903
|
+
profile: "standard",
|
|
11381
10904
|
fix: false
|
|
11382
10905
|
}) {
|
|
11383
10906
|
console.error(`${command}: telemetry calling report_cli_run`);
|
|
@@ -11441,7 +10964,7 @@ async function main() {
|
|
|
11441
10964
|
console.error(`${mode}: begin argv=[${args.join(" ")}] version=${getVersion()} traceId=${traceId ?? "-"} caller=${caller ?? "-"} runIdGenerated=${rc.generated}`);
|
|
11442
10965
|
switch (mode) {
|
|
11443
10966
|
case "check": {
|
|
11444
|
-
const raw =
|
|
10967
|
+
const raw = await fetchCtxViaInnerApi({
|
|
11445
10968
|
populate: planCtxPopulate({
|
|
11446
10969
|
command: "check",
|
|
11447
10970
|
profile
|
|
@@ -11466,7 +10989,7 @@ async function main() {
|
|
|
11466
10989
|
break;
|
|
11467
10990
|
}
|
|
11468
10991
|
case "repair": {
|
|
11469
|
-
const raw =
|
|
10992
|
+
const raw = await fetchCtxViaInnerApi({
|
|
11470
10993
|
populate: planCtxPopulate({
|
|
11471
10994
|
command: "repair",
|
|
11472
10995
|
profile
|
|
@@ -11537,27 +11060,15 @@ async function main() {
|
|
|
11537
11060
|
break;
|
|
11538
11061
|
}
|
|
11539
11062
|
case "reset":
|
|
11540
|
-
if (args.includes("--async"))
|
|
11541
|
-
|
|
11542
|
-
let ctxBase64;
|
|
11543
|
-
if (ctxArg) ctxBase64 = ctxArg.slice(6);
|
|
11544
|
-
else {
|
|
11545
|
-
const fetched = await fetchCtxViaInnerApi({
|
|
11546
|
-
populate: planCtxPopulate({ command: "reset" }),
|
|
11547
|
-
caller,
|
|
11548
|
-
traceId
|
|
11549
|
-
});
|
|
11550
|
-
ctxBase64 = Buffer.from(JSON.stringify(fetched), "utf-8").toString("base64");
|
|
11551
|
-
}
|
|
11552
|
-
console.log(JSON.stringify(startAsyncReset(ctxBase64)));
|
|
11553
|
-
} else if (args.includes("--worker")) {
|
|
11063
|
+
if (args.includes("--async")) console.log(JSON.stringify(startAsyncReset()));
|
|
11064
|
+
else if (args.includes("--worker")) {
|
|
11554
11065
|
const taskId = args.find((a) => a.startsWith("--task-id="))?.slice(10);
|
|
11555
11066
|
if (!taskId) {
|
|
11556
11067
|
console.error("Error: --task-id=<id> is required for worker");
|
|
11557
11068
|
node_process.default.exit(1);
|
|
11558
11069
|
}
|
|
11559
11070
|
const resultFile = resetResultFile(taskId);
|
|
11560
|
-
const raw =
|
|
11071
|
+
const raw = await fetchCtxViaInnerApi({
|
|
11561
11072
|
populate: planCtxPopulate({ command: "reset" }),
|
|
11562
11073
|
caller,
|
|
11563
11074
|
traceId
|
|
@@ -11581,7 +11092,7 @@ async function main() {
|
|
|
11581
11092
|
return;
|
|
11582
11093
|
}
|
|
11583
11094
|
} else {
|
|
11584
|
-
console.error("Usage: reset --async
|
|
11095
|
+
console.error("Usage: reset --async | reset --worker --task-id=<id>");
|
|
11585
11096
|
node_process.default.exit(1);
|
|
11586
11097
|
}
|
|
11587
11098
|
break;
|
|
@@ -11597,14 +11108,14 @@ async function main() {
|
|
|
11597
11108
|
case "install-openclaw": {
|
|
11598
11109
|
const tag = getPositionalTag(args, "install-openclaw");
|
|
11599
11110
|
if (!tag) {
|
|
11600
|
-
console.error("Usage: install-openclaw <tag> [--
|
|
11111
|
+
console.error("Usage: install-openclaw <tag> [--oss_file_map=<base64>]");
|
|
11601
11112
|
node_process.default.exit(1);
|
|
11602
11113
|
}
|
|
11603
11114
|
const ossFileMapFlag = getFlag(args, "oss_file_map");
|
|
11604
11115
|
let installOssFileMap;
|
|
11605
11116
|
let rawForTelemetry;
|
|
11606
11117
|
if (!ossFileMapFlag) {
|
|
11607
|
-
rawForTelemetry =
|
|
11118
|
+
rawForTelemetry = await fetchCtxViaInnerApi({
|
|
11608
11119
|
populate: planCtxPopulate({ command: "install" }),
|
|
11609
11120
|
caller,
|
|
11610
11121
|
traceId
|
|
@@ -11639,7 +11150,7 @@ async function main() {
|
|
|
11639
11150
|
case "install-extension": {
|
|
11640
11151
|
const tag = getPositionalTag(args, "install-extension");
|
|
11641
11152
|
if (!tag) {
|
|
11642
|
-
console.error("Usage: install-extension <tag> (--all | --extension=<name>...) [--home_base=<dir>] [--config_path=<path>] [--skip-config-update] [--
|
|
11153
|
+
console.error("Usage: install-extension <tag> (--all | --extension=<name>...) [--home_base=<dir>] [--config_path=<path>] [--skip-config-update] [--oss_file_map=<base64>]");
|
|
11643
11154
|
node_process.default.exit(1);
|
|
11644
11155
|
}
|
|
11645
11156
|
const all = args.includes("--all");
|
|
@@ -11651,7 +11162,7 @@ async function main() {
|
|
|
11651
11162
|
let installOssFileMap;
|
|
11652
11163
|
let rawForTelemetry;
|
|
11653
11164
|
if (!ossFileMapFlag) {
|
|
11654
|
-
rawForTelemetry =
|
|
11165
|
+
rawForTelemetry = await fetchCtxViaInnerApi({
|
|
11655
11166
|
populate: planCtxPopulate({ command: "install" }),
|
|
11656
11167
|
caller,
|
|
11657
11168
|
traceId
|
|
@@ -11697,12 +11208,12 @@ async function main() {
|
|
|
11697
11208
|
case "install-cli": {
|
|
11698
11209
|
const tag = getPositionalTag(args, "install-cli");
|
|
11699
11210
|
if (!tag) {
|
|
11700
|
-
console.error("Usage: install-cli <tag> --cli=<name>... [--home_base=<dir>] [--
|
|
11211
|
+
console.error("Usage: install-cli <tag> --cli=<name>... [--home_base=<dir>] [--oss_file_map=<base64>]");
|
|
11701
11212
|
node_process.default.exit(1);
|
|
11702
11213
|
}
|
|
11703
11214
|
const names = getMultiFlag(args, "cli");
|
|
11704
11215
|
if (names.length === 0) {
|
|
11705
|
-
console.error("Usage: install-cli <tag> --cli=<name>... [--home_base=<dir>] [--
|
|
11216
|
+
console.error("Usage: install-cli <tag> --cli=<name>... [--home_base=<dir>] [--oss_file_map=<base64>]");
|
|
11706
11217
|
node_process.default.exit(1);
|
|
11707
11218
|
}
|
|
11708
11219
|
const homeBase = getFlag(args, "home_base");
|
|
@@ -11710,7 +11221,7 @@ async function main() {
|
|
|
11710
11221
|
let installOssFileMap;
|
|
11711
11222
|
let rawForTelemetry;
|
|
11712
11223
|
if (!ossFileMapFlag) {
|
|
11713
|
-
rawForTelemetry =
|
|
11224
|
+
rawForTelemetry = await fetchCtxViaInnerApi({
|
|
11714
11225
|
populate: planCtxPopulate({ command: "install" }),
|
|
11715
11226
|
caller,
|
|
11716
11227
|
traceId
|
|
@@ -11758,7 +11269,7 @@ async function main() {
|
|
|
11758
11269
|
case "download-resource": {
|
|
11759
11270
|
const tag = getPositionalTag(args, "download-resource");
|
|
11760
11271
|
if (!tag) {
|
|
11761
|
-
console.error("Usage: download-resource <tag> --role=<role> --name=<name> [--dir=<dir>] [--
|
|
11272
|
+
console.error("Usage: download-resource <tag> --role=<role> --name=<name> [--dir=<dir>] [--oss_file_map=<base64>]");
|
|
11762
11273
|
node_process.default.exit(1);
|
|
11763
11274
|
}
|
|
11764
11275
|
const role = getFlag(args, "role");
|
|
@@ -11772,7 +11283,7 @@ async function main() {
|
|
|
11772
11283
|
let installOssFileMap;
|
|
11773
11284
|
let rawForTelemetry;
|
|
11774
11285
|
if (!ossFileMapFlag) {
|
|
11775
|
-
rawForTelemetry =
|
|
11286
|
+
rawForTelemetry = await fetchCtxViaInnerApi({
|
|
11776
11287
|
populate: planCtxPopulate({ command: "install" }),
|
|
11777
11288
|
caller,
|
|
11778
11289
|
traceId
|
|
@@ -11846,50 +11357,6 @@ async function main() {
|
|
|
11846
11357
|
if (!result.ok) node_process.default.exit(1);
|
|
11847
11358
|
break;
|
|
11848
11359
|
}
|
|
11849
|
-
case "upgrade-lark": {
|
|
11850
|
-
const result = runUpgradeLark({
|
|
11851
|
-
runId: rc.runId,
|
|
11852
|
-
scene
|
|
11853
|
-
});
|
|
11854
|
-
const upgradeDurationMs = Date.now() - t0;
|
|
11855
|
-
console.log(JSON.stringify(result));
|
|
11856
|
-
reportUpgradeLarkToSlardar({
|
|
11857
|
-
scene,
|
|
11858
|
-
durationMs: upgradeDurationMs,
|
|
11859
|
-
success: result.ok,
|
|
11860
|
-
logFile: result.logFile,
|
|
11861
|
-
exitCode: result.exitCode,
|
|
11862
|
-
rollbackOk: result.rollbackOk,
|
|
11863
|
-
validationError: result.validationError,
|
|
11864
|
-
error: result.error
|
|
11865
|
-
});
|
|
11866
|
-
try {
|
|
11867
|
-
await reportCliRun({
|
|
11868
|
-
command: "upgrade-lark",
|
|
11869
|
-
runId: rc.runId,
|
|
11870
|
-
version: getVersion(),
|
|
11871
|
-
invocation: args.join(" "),
|
|
11872
|
-
durationMs: upgradeDurationMs,
|
|
11873
|
-
caller: rc.caller,
|
|
11874
|
-
traceId: rc.traceId,
|
|
11875
|
-
success: result.ok,
|
|
11876
|
-
result,
|
|
11877
|
-
error: result.ok ? void 0 : { message: result.error ?? "upgrade-lark failed" }
|
|
11878
|
-
});
|
|
11879
|
-
} catch (e) {
|
|
11880
|
-
console.error(`[telemetry] reportCliRun failed: ${e.message}`);
|
|
11881
|
-
}
|
|
11882
|
-
if (!result.ok) {
|
|
11883
|
-
node_process.default.exitCode = 1;
|
|
11884
|
-
return;
|
|
11885
|
-
}
|
|
11886
|
-
break;
|
|
11887
|
-
}
|
|
11888
|
-
case "channels-probe": {
|
|
11889
|
-
const result = runChannelsProbe(getFlag(args, "timeout") ? Number(getFlag(args, "timeout")) : void 0);
|
|
11890
|
-
console.log(JSON.stringify(result));
|
|
11891
|
-
break;
|
|
11892
|
-
}
|
|
11893
11360
|
default:
|
|
11894
11361
|
node_process.default.stderr.write(`Unknown command: ${mode}\n\n`);
|
|
11895
11362
|
node_process.default.stderr.write(formatTopLevelHelp(helpFlags.expert));
|
package/package.json
CHANGED