@lark-apaas/openclaw-scripts-diagnose-cli 0.1.14-alpha.3 → 0.1.14-alpha.4
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 +790 -227
- 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.4";
|
|
56
56
|
}
|
|
57
57
|
//#endregion
|
|
58
58
|
//#region src/rule-engine/base.ts
|
|
@@ -3347,7 +3347,6 @@ 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;
|
|
3351
3350
|
const ocCur = getOcVersion();
|
|
3352
3351
|
if (!ocCur) return null;
|
|
3353
3352
|
const installed = getInstalledPlugin(ctx);
|
|
@@ -3368,6 +3367,7 @@ let FeishuPluginOpenclawUpgradeRule = class FeishuPluginOpenclawUpgradeRule exte
|
|
|
3368
3367
|
validate(ctx) {
|
|
3369
3368
|
const cc = resolveCompatContext(ctx);
|
|
3370
3369
|
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,6 +3397,14 @@ 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
|
+
}
|
|
3400
3408
|
if (resolveUpgradeDirection(installed, ocCur, recommendedOc, isLegacy) !== "lark") return { pass: true };
|
|
3401
3409
|
return {
|
|
3402
3410
|
pass: false,
|
|
@@ -3505,6 +3513,188 @@ function extractScopedNameFromSpec$1(spec) {
|
|
|
3505
3513
|
const at = spec.indexOf("@", 1);
|
|
3506
3514
|
return at === -1 ? spec : spec.slice(0, at);
|
|
3507
3515
|
}
|
|
3516
|
+
/**
|
|
3517
|
+
* Returns true if the installed feishu plugin is version-incompatible with
|
|
3518
|
+
* the current openclaw (or is a legacy plugin that must be replaced).
|
|
3519
|
+
* Used by the upgrade_lark_needed rule and the upgrade-lark pre-check gate.
|
|
3520
|
+
*/
|
|
3521
|
+
function needsLarkUpgrade(ctx) {
|
|
3522
|
+
const cc = resolveCompatContext(ctx);
|
|
3523
|
+
if (!cc) return false;
|
|
3524
|
+
const { ocCur, recommendedOc, installed, isLegacy } = cc;
|
|
3525
|
+
if (isForkPlugin(installed)) return false;
|
|
3526
|
+
if (recommendedOc) return resolveUpgradeDirection(installed, ocCur, recommendedOc, isLegacy) === "lark";
|
|
3527
|
+
return isLegacy || !isVersionCompatible(installed, ocCur);
|
|
3528
|
+
}
|
|
3529
|
+
//#endregion
|
|
3530
|
+
//#region src/channels-probe.ts
|
|
3531
|
+
const FEISHU_INVALID_CONFIG_MSG = "channels.feishu: invalid config: must NOT have additional properties";
|
|
3532
|
+
/**
|
|
3533
|
+
* Run `openclaw status` and check whether the output contains the feishu
|
|
3534
|
+
* channel config validation error. This error appears when openclaw.json
|
|
3535
|
+
* has extra fields that are no longer recognised by the new plugin schema.
|
|
3536
|
+
*
|
|
3537
|
+
* Returns false on any execution error so callers can safely treat it as
|
|
3538
|
+
* "error not present" rather than aborting.
|
|
3539
|
+
*/
|
|
3540
|
+
function isFeishuChannelConfigInvalid(cwd, timeoutMs = 1e4) {
|
|
3541
|
+
try {
|
|
3542
|
+
return (0, node_child_process.execSync)("openclaw status", {
|
|
3543
|
+
encoding: "utf-8",
|
|
3544
|
+
timeout: timeoutMs,
|
|
3545
|
+
cwd,
|
|
3546
|
+
stdio: [
|
|
3547
|
+
"ignore",
|
|
3548
|
+
"pipe",
|
|
3549
|
+
"pipe"
|
|
3550
|
+
]
|
|
3551
|
+
}).includes(FEISHU_INVALID_CONFIG_MSG);
|
|
3552
|
+
} catch (e) {
|
|
3553
|
+
const err = e;
|
|
3554
|
+
return ((err.stdout ?? "") + (err.stderr ?? "")).includes(FEISHU_INVALID_CONFIG_MSG);
|
|
3555
|
+
}
|
|
3556
|
+
}
|
|
3557
|
+
const CHANNEL_LINE_RE = /^-\s+Feishu\s+([^:]+):\s+(.+)$/;
|
|
3558
|
+
/**
|
|
3559
|
+
* Port of Python `_account_is_working` from the feishu-channel-success-rate skill.
|
|
3560
|
+
*
|
|
3561
|
+
* Strips colon-prefixed key:value bits (dm:, bot:, in:, out:, token:, allow:,
|
|
3562
|
+
* intents:, groups:, health:) and evaluates the canonical health formula.
|
|
3563
|
+
*/
|
|
3564
|
+
function accountIsWorking(bits) {
|
|
3565
|
+
const bitTokens = /* @__PURE__ */ new Set();
|
|
3566
|
+
let hasError = false;
|
|
3567
|
+
let hasProbeFailed = false;
|
|
3568
|
+
for (const raw of bits) {
|
|
3569
|
+
const b = raw.trim();
|
|
3570
|
+
if (!b) continue;
|
|
3571
|
+
if (b.startsWith("error:")) {
|
|
3572
|
+
hasError = true;
|
|
3573
|
+
continue;
|
|
3574
|
+
}
|
|
3575
|
+
if (b === "probe failed") {
|
|
3576
|
+
hasProbeFailed = true;
|
|
3577
|
+
continue;
|
|
3578
|
+
}
|
|
3579
|
+
bitTokens.add(b.split(":")[0]);
|
|
3580
|
+
}
|
|
3581
|
+
if (!bitTokens.has("enabled") || !bitTokens.has("configured")) return false;
|
|
3582
|
+
if (bitTokens.has("works")) return true;
|
|
3583
|
+
if (bitTokens.has("running") && !hasError && !hasProbeFailed) return true;
|
|
3584
|
+
return false;
|
|
3585
|
+
}
|
|
3586
|
+
/**
|
|
3587
|
+
* Parse the raw stdout of `openclaw channels status --probe`.
|
|
3588
|
+
* Port of Python `extract_channels_probe` from the feishu-channel-success-rate skill.
|
|
3589
|
+
*/
|
|
3590
|
+
function parseChannelsProbeOutput(text) {
|
|
3591
|
+
const gatewayReachable = text.includes("Gateway reachable");
|
|
3592
|
+
const accounts = [];
|
|
3593
|
+
let anyAccountWorking = false;
|
|
3594
|
+
for (const line of text.split("\n")) {
|
|
3595
|
+
const m = CHANNEL_LINE_RE.exec(line.trim());
|
|
3596
|
+
if (!m) continue;
|
|
3597
|
+
const [, acct, rest] = m;
|
|
3598
|
+
const bits = rest.split(",").map((b) => b.trim());
|
|
3599
|
+
const isWorking = accountIsWorking(bits);
|
|
3600
|
+
if (isWorking) anyAccountWorking = true;
|
|
3601
|
+
accounts.push({
|
|
3602
|
+
id: acct.trim(),
|
|
3603
|
+
bits,
|
|
3604
|
+
isWorking,
|
|
3605
|
+
raw: line.trim()
|
|
3606
|
+
});
|
|
3607
|
+
}
|
|
3608
|
+
return {
|
|
3609
|
+
gatewayReachable,
|
|
3610
|
+
accounts,
|
|
3611
|
+
anyAccountWorking
|
|
3612
|
+
};
|
|
3613
|
+
}
|
|
3614
|
+
/**
|
|
3615
|
+
* Run `openclaw channels status --probe` and return a structured result.
|
|
3616
|
+
*
|
|
3617
|
+
* The command may exit non-zero when some bot accounts fail their probe — that
|
|
3618
|
+
* is still useful output. We therefore try to parse stdout even when the
|
|
3619
|
+
* process exits with a non-zero code, falling back to an unavailable result
|
|
3620
|
+
* only when there is genuinely no output to parse.
|
|
3621
|
+
*
|
|
3622
|
+
* @param timeoutMs Maximum wait time. Default is 60 s because v2026.4.x
|
|
3623
|
+
* lacks a per-request HTTP timeout and can block indefinitely.
|
|
3624
|
+
*/
|
|
3625
|
+
function runChannelsProbe(timeoutMs = 6e4) {
|
|
3626
|
+
let stdout = "";
|
|
3627
|
+
let execError;
|
|
3628
|
+
try {
|
|
3629
|
+
stdout = (0, node_child_process.execSync)("openclaw channels status --probe", {
|
|
3630
|
+
encoding: "utf-8",
|
|
3631
|
+
timeout: timeoutMs,
|
|
3632
|
+
stdio: [
|
|
3633
|
+
"ignore",
|
|
3634
|
+
"pipe",
|
|
3635
|
+
"pipe"
|
|
3636
|
+
]
|
|
3637
|
+
});
|
|
3638
|
+
} catch (e) {
|
|
3639
|
+
const err = e;
|
|
3640
|
+
stdout = err.stdout ?? "";
|
|
3641
|
+
execError = err.message;
|
|
3642
|
+
const stderrRaw = err.stderr;
|
|
3643
|
+
const stderr = (typeof stderrRaw === "string" ? stderrRaw : stderrRaw?.toString("utf-8") ?? "").trim();
|
|
3644
|
+
if (stderr) console.error(`channels-probe: stderr from CLI: ${stderr}`);
|
|
3645
|
+
}
|
|
3646
|
+
if (stdout.trim()) return {
|
|
3647
|
+
available: true,
|
|
3648
|
+
...parseChannelsProbeOutput(stdout)
|
|
3649
|
+
};
|
|
3650
|
+
return {
|
|
3651
|
+
available: false,
|
|
3652
|
+
gatewayReachable: false,
|
|
3653
|
+
accounts: [],
|
|
3654
|
+
anyAccountWorking: false,
|
|
3655
|
+
error: execError ?? "no output from openclaw channels status --probe"
|
|
3656
|
+
};
|
|
3657
|
+
}
|
|
3658
|
+
//#endregion
|
|
3659
|
+
//#region src/rules/upgrade-lark-needed.ts
|
|
3660
|
+
/**
|
|
3661
|
+
* Detects the condition that warrants running `upgrade-lark`:
|
|
3662
|
+
* - feishu plugin version incompatible with current openclaw, AND
|
|
3663
|
+
* - channels are not working.
|
|
3664
|
+
*
|
|
3665
|
+
* Both conditions must be true simultaneously. If version is compatible or
|
|
3666
|
+
* channels are working, the rule passes (no action needed).
|
|
3667
|
+
*
|
|
3668
|
+
* profile: experimental — runs only in full sweep mode, not in standard doctor.
|
|
3669
|
+
* level: silent — telemetry/sweep-only, does not trigger page-level repair UI.
|
|
3670
|
+
*/
|
|
3671
|
+
let UpgradeLarkNeededRule = class UpgradeLarkNeededRule extends DiagnoseRule {
|
|
3672
|
+
validate(ctx) {
|
|
3673
|
+
const versionIncompatible = needsLarkUpgrade(ctx);
|
|
3674
|
+
const feishuConfigInvalid = !versionIncompatible && isFeishuChannelConfigInvalid(node_path.default.dirname(ctx.configPath));
|
|
3675
|
+
if (!(versionIncompatible || feishuConfigInvalid)) return { pass: true };
|
|
3676
|
+
let anyAccountWorking = false;
|
|
3677
|
+
try {
|
|
3678
|
+
anyAccountWorking = runChannelsProbe(3e4).anyAccountWorking;
|
|
3679
|
+
} catch {
|
|
3680
|
+
return { pass: true };
|
|
3681
|
+
}
|
|
3682
|
+
if (anyAccountWorking) return { pass: true };
|
|
3683
|
+
return {
|
|
3684
|
+
pass: false,
|
|
3685
|
+
action: "upgrade_lark",
|
|
3686
|
+
message: `飞书插件需要升级且 channels 不可用(版本不兼容=${versionIncompatible}, feishu配置无效=${feishuConfigInvalid}),建议执行 upgrade-lark 命令升级飞书插件`
|
|
3687
|
+
};
|
|
3688
|
+
}
|
|
3689
|
+
};
|
|
3690
|
+
UpgradeLarkNeededRule = __decorate([Rule({
|
|
3691
|
+
key: "upgrade_lark_needed",
|
|
3692
|
+
description: "检测飞书插件版本不兼容且 channels 不可用,判断是否需要执行 upgrade-lark 升级",
|
|
3693
|
+
repairMode: "check-only",
|
|
3694
|
+
level: "silent",
|
|
3695
|
+
profile: "experimental",
|
|
3696
|
+
usesVars: ["recommendedOpenclawTag"]
|
|
3697
|
+
})], UpgradeLarkNeededRule);
|
|
3508
3698
|
//#endregion
|
|
3509
3699
|
//#region src/rules/cleanup-install-backup-dirs.ts
|
|
3510
3700
|
const DIR_PREFIX = ".openclaw-install-";
|
|
@@ -3650,119 +3840,6 @@ LarkCliMissingForInstalledLarkPluginRule = __decorate([Rule({
|
|
|
3650
3840
|
usesVars: ["recommendedOpenclawTag"]
|
|
3651
3841
|
})], LarkCliMissingForInstalledLarkPluginRule);
|
|
3652
3842
|
//#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
|
|
3766
3843
|
//#region src/check.ts
|
|
3767
3844
|
/** Telemetry-aware entry: returns both the legacy CheckResult (for stdout)
|
|
3768
3845
|
* AND a DoctorReport-shape payload (for `openclaw.report_cli_run`). The
|
|
@@ -4226,6 +4303,9 @@ const PROVIDER_FILE_PATH = "/home/gem/workspace/.force/openclaw/miaoda-provider-
|
|
|
4226
4303
|
const SECRETS_FILE_PATH = "/home/gem/workspace/.force/openclaw/miaoda-openclaw-secrets.json";
|
|
4227
4304
|
/** Absolute path to the openclaw config JSON. */
|
|
4228
4305
|
const CONFIG_PATH = `${WORKSPACE_DIR}/openclaw.json`;
|
|
4306
|
+
function upgradeLarkLogFile(runId) {
|
|
4307
|
+
return `${DIAGNOSE_DIR}/upgrade-lark-${(/* @__PURE__ */ new Date()).toISOString().slice(0, 19).replace(/:/g, "-")}-${runId.slice(0, 8)}.log`;
|
|
4308
|
+
}
|
|
4229
4309
|
//#endregion
|
|
4230
4310
|
//#region src/run-log.ts
|
|
4231
4311
|
let currentRunContext;
|
|
@@ -4362,10 +4442,9 @@ function makeLogger(logFile) {
|
|
|
4362
4442
|
/**
|
|
4363
4443
|
* Start an async reset task: spawn a detached child process and return the taskId.
|
|
4364
4444
|
*
|
|
4365
|
-
* The child process runs: node cli.js reset --worker --task-id=xxx
|
|
4366
|
-
* The worker fetches ctx from innerApi itself — no --ctx passthrough.
|
|
4445
|
+
* The child process runs: node cli.js reset --worker --task-id=xxx --ctx=base64
|
|
4367
4446
|
*/
|
|
4368
|
-
function startAsyncReset() {
|
|
4447
|
+
function startAsyncReset(ctxBase64) {
|
|
4369
4448
|
const taskId = (0, node_crypto.randomUUID)();
|
|
4370
4449
|
const resultFile = resetResultFile(taskId);
|
|
4371
4450
|
const log = makeLogger(resetLogFile(taskId));
|
|
@@ -4389,7 +4468,8 @@ function startAsyncReset() {
|
|
|
4389
4468
|
process.argv[1],
|
|
4390
4469
|
"reset",
|
|
4391
4470
|
"--worker",
|
|
4392
|
-
`--task-id=${taskId}
|
|
4471
|
+
`--task-id=${taskId}`,
|
|
4472
|
+
`--ctx=${ctxBase64}`
|
|
4393
4473
|
], {
|
|
4394
4474
|
detached: true,
|
|
4395
4475
|
stdio: "ignore",
|
|
@@ -6903,60 +6983,6 @@ function mergeCoreBackupAndOrigins(configPath, vars, resetData, log) {
|
|
|
6903
6983
|
log(`allowedOrigins: added ${added.length} (${JSON.stringify(added)}), total now ${mergedOrigins.length}`);
|
|
6904
6984
|
}
|
|
6905
6985
|
/**
|
|
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
|
-
/**
|
|
6960
6986
|
* Step 7: Verify startup scripts landed in configDir/scripts/.
|
|
6961
6987
|
*
|
|
6962
6988
|
* Scripts are extracted directly to configDir/scripts/ during stageTemplate —
|
|
@@ -7101,7 +7127,6 @@ async function runReset(input, taskId, resultFile) {
|
|
|
7101
7127
|
await step5InstallOpenclaw(openclawTag, ossFileMap, log);
|
|
7102
7128
|
step(6);
|
|
7103
7129
|
mergeCoreBackupAndOrigins(configPath, vars, resetData, log);
|
|
7104
|
-
fixBotChannelConfig(configPath, vars.larkApps, log);
|
|
7105
7130
|
step(7);
|
|
7106
7131
|
verifyStartupScripts(configDir, log);
|
|
7107
7132
|
step(8);
|
|
@@ -7900,8 +7925,7 @@ function normalizeCtx(raw) {
|
|
|
7900
7925
|
reset: {
|
|
7901
7926
|
templateVars: r.reset.templateVars ?? {},
|
|
7902
7927
|
coreBackup: r.reset.coreBackup
|
|
7903
|
-
}
|
|
7904
|
-
larkApps: Array.isArray(r.larkApps) ? r.larkApps : []
|
|
7928
|
+
}
|
|
7905
7929
|
};
|
|
7906
7930
|
}
|
|
7907
7931
|
const vars = r.vars ?? {};
|
|
@@ -7926,8 +7950,7 @@ function normalizeCtx(raw) {
|
|
|
7926
7950
|
reset: {
|
|
7927
7951
|
templateVars: resetData.templateVars ?? {},
|
|
7928
7952
|
coreBackup: resetData.coreBackup
|
|
7929
|
-
}
|
|
7930
|
-
larkApps: Array.isArray(r.larkApps) ? r.larkApps : []
|
|
7953
|
+
}
|
|
7931
7954
|
};
|
|
7932
7955
|
}
|
|
7933
7956
|
function fillApp(src) {
|
|
@@ -7992,8 +8015,7 @@ function buildCheckInput(raw, configPathOverride) {
|
|
|
7992
8015
|
providerFilePath: PROVIDER_FILE_PATH,
|
|
7993
8016
|
secretsFilePath: SECRETS_FILE_PATH,
|
|
7994
8017
|
templateVars: ctx.app.templateVars,
|
|
7995
|
-
recommendedOpenclawTag: ctx.app.recommendedOpenclawTag
|
|
7996
|
-
larkApps: ctx.larkApps
|
|
8018
|
+
recommendedOpenclawTag: ctx.app.recommendedOpenclawTag
|
|
7997
8019
|
},
|
|
7998
8020
|
templateVars: ctx.app.templateVars
|
|
7999
8021
|
};
|
|
@@ -8025,8 +8047,7 @@ function buildRepairInput(raw, configPathOverride) {
|
|
|
8025
8047
|
providerFilePath: PROVIDER_FILE_PATH,
|
|
8026
8048
|
secretsFilePath: SECRETS_FILE_PATH,
|
|
8027
8049
|
templateVars: ctx.app.templateVars,
|
|
8028
|
-
recommendedOpenclawTag: ctx.app.recommendedOpenclawTag
|
|
8029
|
-
larkApps: ctx.larkApps
|
|
8050
|
+
recommendedOpenclawTag: ctx.app.recommendedOpenclawTag
|
|
8030
8051
|
},
|
|
8031
8052
|
repairData: {
|
|
8032
8053
|
secretsContent: ctx.secrets.secretsContent,
|
|
@@ -8062,8 +8083,7 @@ function buildResetInput(raw, configPathOverride) {
|
|
|
8062
8083
|
providerFilePath: PROVIDER_FILE_PATH,
|
|
8063
8084
|
secretsFilePath: SECRETS_FILE_PATH,
|
|
8064
8085
|
templateVars: ctx.app.templateVars,
|
|
8065
|
-
recommendedOpenclawTag: ctx.app.recommendedOpenclawTag
|
|
8066
|
-
larkApps: ctx.larkApps
|
|
8086
|
+
recommendedOpenclawTag: ctx.app.recommendedOpenclawTag
|
|
8067
8087
|
},
|
|
8068
8088
|
resetData: {
|
|
8069
8089
|
templateVars: ctx.reset.templateVars,
|
|
@@ -10373,7 +10393,7 @@ async function reportCliRun(opts) {
|
|
|
10373
10393
|
//#region src/help.ts
|
|
10374
10394
|
const BIN = "mclaw-diagnose";
|
|
10375
10395
|
function versionBanner() {
|
|
10376
|
-
return `v0.1.14-alpha.
|
|
10396
|
+
return `v0.1.14-alpha.4`;
|
|
10377
10397
|
}
|
|
10378
10398
|
const COMMANDS = [
|
|
10379
10399
|
{
|
|
@@ -10477,12 +10497,16 @@ EXIT CODES
|
|
|
10477
10497
|
hidden: true,
|
|
10478
10498
|
summary: "Run rule-engine check only",
|
|
10479
10499
|
help: `USAGE
|
|
10480
|
-
${BIN} check
|
|
10500
|
+
${BIN} check [--ctx=<base64>]
|
|
10481
10501
|
|
|
10482
10502
|
DESCRIPTION
|
|
10483
10503
|
Runs the rule engine against the sandbox's current openclaw config and
|
|
10484
|
-
returns { failedRules }.
|
|
10485
|
-
|
|
10504
|
+
returns { failedRules }. Used by sandbox_console's push-style callers
|
|
10505
|
+
that already own the ctx — end-users should prefer \`doctor\`.
|
|
10506
|
+
|
|
10507
|
+
OPTIONS
|
|
10508
|
+
--ctx=<base64> Opaque ctx JSON (base64). When absent, fetched from
|
|
10509
|
+
innerapi (same path as doctor).
|
|
10486
10510
|
`
|
|
10487
10511
|
},
|
|
10488
10512
|
{
|
|
@@ -10490,11 +10514,16 @@ DESCRIPTION
|
|
|
10490
10514
|
hidden: true,
|
|
10491
10515
|
summary: "Apply standard-mode repairs",
|
|
10492
10516
|
help: `USAGE
|
|
10493
|
-
${BIN} repair
|
|
10517
|
+
${BIN} repair [--ctx=<base64>]
|
|
10494
10518
|
|
|
10495
10519
|
DESCRIPTION
|
|
10496
|
-
Runs repair for the failing rules
|
|
10497
|
-
|
|
10520
|
+
Runs repair for the failing rules listed inside the ctx's repairData.
|
|
10521
|
+
Intended for sandbox_console's push path — end-users should use
|
|
10522
|
+
\`doctor --fix\` instead.
|
|
10523
|
+
|
|
10524
|
+
OPTIONS
|
|
10525
|
+
--ctx=<base64> Opaque ctx JSON (base64). When absent, fetched from
|
|
10526
|
+
innerapi.
|
|
10498
10527
|
`
|
|
10499
10528
|
},
|
|
10500
10529
|
{
|
|
@@ -10502,15 +10531,14 @@ DESCRIPTION
|
|
|
10502
10531
|
hidden: true,
|
|
10503
10532
|
summary: "Re-initialize sandbox via the 9-step reset pipeline",
|
|
10504
10533
|
help: `USAGE
|
|
10505
|
-
${BIN} reset --async
|
|
10506
|
-
${BIN} reset --worker --task-id=<id>
|
|
10534
|
+
${BIN} reset --async [--ctx=<base64>]
|
|
10535
|
+
${BIN} reset --worker --task-id=<id> [--ctx=<base64>]
|
|
10507
10536
|
|
|
10508
10537
|
DESCRIPTION
|
|
10509
10538
|
Two-phase pipeline driven asynchronously: the --async invocation spawns
|
|
10510
10539
|
a detached worker and returns { taskId } immediately; the --worker
|
|
10511
10540
|
invocation (spawned by --async) runs the actual 9 steps and writes
|
|
10512
10541
|
progress to /tmp/openclaw-diagnose/reset-<taskId>.json.
|
|
10513
|
-
Ctx is fetched from innerapi automatically.
|
|
10514
10542
|
|
|
10515
10543
|
Poll progress with \`${BIN} get_reset_task --task-id=<id>\`.
|
|
10516
10544
|
|
|
@@ -10518,6 +10546,7 @@ OPTIONS
|
|
|
10518
10546
|
--async Start a detached worker and return taskId on stdout.
|
|
10519
10547
|
--worker Internal — run the 9-step pipeline (launched by --async).
|
|
10520
10548
|
--task-id=<id> Required with --worker; identifies the progress file.
|
|
10549
|
+
--ctx=<base64> Opaque ctx JSON; fetched from innerapi when absent.
|
|
10521
10550
|
`
|
|
10522
10551
|
},
|
|
10523
10552
|
{
|
|
@@ -10540,7 +10569,7 @@ OPTIONS
|
|
|
10540
10569
|
hidden: true,
|
|
10541
10570
|
summary: "Download + install the openclaw tarball",
|
|
10542
10571
|
help: `USAGE
|
|
10543
|
-
${BIN} install-openclaw <tag> [--oss_file_map=<base64>]
|
|
10572
|
+
${BIN} install-openclaw <tag> [--ctx=<base64> | --oss_file_map=<base64>]
|
|
10544
10573
|
|
|
10545
10574
|
DESCRIPTION
|
|
10546
10575
|
Downloads the openclaw@<tag> tgz via the signed OSS URL found in the
|
|
@@ -10552,9 +10581,9 @@ ARGUMENTS
|
|
|
10552
10581
|
<tag> Openclaw version tag, e.g. 2026.4.11.
|
|
10553
10582
|
|
|
10554
10583
|
OPTIONS
|
|
10584
|
+
--ctx=<base64> Opaque ctx; ossFileMap is extracted from it.
|
|
10555
10585
|
--oss_file_map=... Pre-built OSS URL map (base64 JSON); skips innerapi
|
|
10556
|
-
entirely.
|
|
10557
|
-
innerapi automatically.
|
|
10586
|
+
entirely. Wins over --ctx when both provided.
|
|
10558
10587
|
`
|
|
10559
10588
|
},
|
|
10560
10589
|
{
|
|
@@ -10580,7 +10609,8 @@ OPTIONS
|
|
|
10580
10609
|
--home_base=<dir> Override the /home/gem base (tests).
|
|
10581
10610
|
--config_path=<p> Override the openclaw.json path (tests).
|
|
10582
10611
|
--skip-config-update Leave plugins.installs in openclaw.json untouched.
|
|
10583
|
-
--
|
|
10612
|
+
--ctx=<base64> Opaque ctx; see install-openclaw for semantics.
|
|
10613
|
+
--oss_file_map=... Pre-built OSS URL map (base64 JSON).
|
|
10584
10614
|
`
|
|
10585
10615
|
},
|
|
10586
10616
|
{
|
|
@@ -10607,6 +10637,7 @@ OPTIONS
|
|
|
10607
10637
|
--cli=<name> CLI package to install by short name or scoped
|
|
10608
10638
|
packageName (repeatable, at least one required).
|
|
10609
10639
|
--home_base=<dir> Override the /home/gem base (tests).
|
|
10640
|
+
--ctx=<base64> Opaque ctx; ossFileMap is extracted from it.
|
|
10610
10641
|
--oss_file_map=... Pre-built OSS URL map (base64 JSON); skips innerapi.
|
|
10611
10642
|
|
|
10612
10643
|
EXAMPLES
|
|
@@ -10660,6 +10691,46 @@ OPTIONS
|
|
|
10660
10691
|
EXIT CODES
|
|
10661
10692
|
0 Success or skipped (prerequisites not met).
|
|
10662
10693
|
1 Secret/path unresolvable, lark-cli failed, or config unreadable.
|
|
10694
|
+
`
|
|
10695
|
+
},
|
|
10696
|
+
{
|
|
10697
|
+
name: "upgrade-lark",
|
|
10698
|
+
hidden: false,
|
|
10699
|
+
summary: "Upgrade the Feishu/Lark plugin via @larksuite/openclaw-lark-tools",
|
|
10700
|
+
help: `USAGE
|
|
10701
|
+
${BIN} upgrade-lark [--scene=<scene>] [--caller=<n>] [--trace-id=<id>]
|
|
10702
|
+
|
|
10703
|
+
DESCRIPTION
|
|
10704
|
+
Upgrades the Feishu/Lark plugin by running:
|
|
10705
|
+
npx -y @larksuite/openclaw-lark-tools update --use-existing
|
|
10706
|
+
|
|
10707
|
+
Before the upgrade, the following files are backed up:
|
|
10708
|
+
- openclaw.json
|
|
10709
|
+
- extensions/openclaw-lark/ (if present)
|
|
10710
|
+
- extensions/feishu-openclaw-plugin/ (if present)
|
|
10711
|
+
After the upgrade, the result is validated:
|
|
10712
|
+
- feishu.accounts bot count must not decrease
|
|
10713
|
+
- gateway config structure must remain valid (port/mode/bind/auth/trustedProxies)
|
|
10714
|
+
If the upgrade command fails, or validation fails, the backed-up files are
|
|
10715
|
+
restored to roll back the changes.
|
|
10716
|
+
|
|
10717
|
+
Execution is logged to /tmp/openclaw-diagnose/upgrade-lark-<runId>.log.
|
|
10718
|
+
|
|
10719
|
+
Output is a single JSON object on stdout:
|
|
10720
|
+
{ "ok": true, "stdout": "...", "stderr": "...", "logFile": "..." }
|
|
10721
|
+
{ "ok": false, "error": "...", "stderr": "...", "exitCode": 1,
|
|
10722
|
+
"rollbackOk": true, "validationError": "...", "logFile": "..." }
|
|
10723
|
+
|
|
10724
|
+
OPTIONS
|
|
10725
|
+
--scene=<scene> Telemetry label forwarded to Slardar only.
|
|
10726
|
+
Known values: PageUpgradeLark, etc. Custom strings accepted.
|
|
10727
|
+
--caller=<name> Optional metadata passed to innerapi.
|
|
10728
|
+
--trace-id=<id> Optional log-correlation id.
|
|
10729
|
+
|
|
10730
|
+
EXIT CODES
|
|
10731
|
+
0 Success: upgrade ran and all validations passed.
|
|
10732
|
+
1 Failure: npx error, validation failed, or git commit failed.
|
|
10733
|
+
File rollback was attempted (see rollbackOk in the JSON output).
|
|
10663
10734
|
`
|
|
10664
10735
|
},
|
|
10665
10736
|
{
|
|
@@ -10693,6 +10764,41 @@ EXAMPLES
|
|
|
10693
10764
|
${BIN} rules # all rules
|
|
10694
10765
|
${BIN} rules --rule=gateway # single rule
|
|
10695
10766
|
${BIN} rules --rule=gateway --rule=feishu_channel # multiple rules
|
|
10767
|
+
`
|
|
10768
|
+
},
|
|
10769
|
+
{
|
|
10770
|
+
name: "channels-probe",
|
|
10771
|
+
hidden: true,
|
|
10772
|
+
summary: "Check feishu channel health via openclaw channels status --probe",
|
|
10773
|
+
help: `USAGE
|
|
10774
|
+
${BIN} channels-probe [--timeout=<ms>]
|
|
10775
|
+
|
|
10776
|
+
DESCRIPTION
|
|
10777
|
+
Runs \`openclaw channels status --probe\` and returns a structured JSON
|
|
10778
|
+
summary of whether the current environment's feishu channels are
|
|
10779
|
+
configured and working correctly.
|
|
10780
|
+
|
|
10781
|
+
Output:
|
|
10782
|
+
{
|
|
10783
|
+
"available": true,
|
|
10784
|
+
"gatewayReachable": true,
|
|
10785
|
+
"accounts": [
|
|
10786
|
+
{ "id": "default", "bits": ["enabled","configured","running","works"],
|
|
10787
|
+
"isWorking": true, "raw": "- Feishu default: ..." }
|
|
10788
|
+
],
|
|
10789
|
+
"anyAccountWorking": true
|
|
10790
|
+
}
|
|
10791
|
+
|
|
10792
|
+
An account is considered working when:
|
|
10793
|
+
enabled ∧ configured ∧ ( works ∨ ( running ∧ no error: ∧ no probe failed ) )
|
|
10794
|
+
|
|
10795
|
+
"available": false means the CLI invocation itself failed (openclaw not
|
|
10796
|
+
found, gateway unreachable, or no parseable output returned).
|
|
10797
|
+
|
|
10798
|
+
OPTIONS
|
|
10799
|
+
--timeout=<ms> Max wait in milliseconds (default: 60000). The probe
|
|
10800
|
+
can hang indefinitely on openclaw v2026.4.x due to a
|
|
10801
|
+
missing per-request HTTP timeout — set this accordingly.
|
|
10696
10802
|
`
|
|
10697
10803
|
},
|
|
10698
10804
|
{
|
|
@@ -10713,7 +10819,8 @@ OPTIONS
|
|
|
10713
10819
|
--role=<role> Package role (e.g. template, config).
|
|
10714
10820
|
--name=<name> Package name within the role.
|
|
10715
10821
|
--dir=<dir> Target dir (defaults to dirname(pkg.installPath)).
|
|
10716
|
-
--
|
|
10822
|
+
--ctx=<base64> Opaque ctx; ossFileMap is extracted from it.
|
|
10823
|
+
--oss_file_map=... Pre-built OSS URL map (base64 JSON).
|
|
10717
10824
|
`
|
|
10718
10825
|
}
|
|
10719
10826
|
];
|
|
@@ -10789,31 +10896,31 @@ function planVarsFields(opts = {}) {
|
|
|
10789
10896
|
*
|
|
10790
10897
|
* Per-command group needs:
|
|
10791
10898
|
*
|
|
10792
|
-
* doctor / check app
|
|
10793
|
-
* repair app + secrets
|
|
10794
|
-
* reset app + secrets + install + reset
|
|
10899
|
+
* doctor / check app (rule-driven)
|
|
10900
|
+
* repair app + secrets (writes secretsContent / providerKeyContent)
|
|
10901
|
+
* reset app + secrets + install + reset (the works)
|
|
10795
10902
|
* install-* install only
|
|
10796
10903
|
*
|
|
10797
10904
|
* Empty result (`{}`) means "no group needed" — the CLI can skip the
|
|
10798
10905
|
* `fetchCtxViaInnerApi` call entirely and run with a synthetic empty ctx.
|
|
10906
|
+
* Happens e.g. when the user pinned `--rule=<key>` to a vars-free rule on
|
|
10907
|
+
* `doctor`.
|
|
10799
10908
|
*/
|
|
10800
10909
|
function planCtxPopulate(opts) {
|
|
10801
10910
|
if (opts.command === "install") return { install: true };
|
|
10802
10911
|
const populate = {};
|
|
10803
|
-
|
|
10912
|
+
const appFields = planVarsFields({
|
|
10804
10913
|
disabled: opts.disabled,
|
|
10805
10914
|
onlyRules: opts.onlyRules,
|
|
10806
10915
|
profile: opts.profile
|
|
10807
|
-
})
|
|
10808
|
-
if (
|
|
10809
|
-
|
|
10810
|
-
|
|
10811
|
-
} else if (opts.command === "reset") {
|
|
10916
|
+
});
|
|
10917
|
+
if (appFields.length > 0) populate.app = appFields;
|
|
10918
|
+
if (opts.command === "repair") populate.secrets = true;
|
|
10919
|
+
else if (opts.command === "reset") {
|
|
10812
10920
|
populate.secrets = true;
|
|
10813
10921
|
populate.install = true;
|
|
10814
10922
|
populate.reset = true;
|
|
10815
|
-
|
|
10816
|
-
} else if (opts.command === "doctor" || opts.command === "check") populate.larkApps = true;
|
|
10923
|
+
}
|
|
10817
10924
|
return populate;
|
|
10818
10925
|
}
|
|
10819
10926
|
//#endregion
|
|
@@ -10867,11 +10974,411 @@ function reportDoctorRunToSlardar(opts) {
|
|
|
10867
10974
|
}
|
|
10868
10975
|
});
|
|
10869
10976
|
}
|
|
10977
|
+
function readLogFile(filePath) {
|
|
10978
|
+
try {
|
|
10979
|
+
return node_fs.default.readFileSync(filePath, "utf-8");
|
|
10980
|
+
} catch {
|
|
10981
|
+
return "";
|
|
10982
|
+
}
|
|
10983
|
+
}
|
|
10984
|
+
function reportUpgradeLarkToSlardar(opts) {
|
|
10985
|
+
console.error(`[slardar] upgrade_lark_run scene=${opts.scene ?? ""} success=${opts.success} exitCode=${opts.exitCode ?? ""} rollbackOk=${opts.rollbackOk ?? ""}`);
|
|
10986
|
+
const logContent = readLogFile(opts.logFile);
|
|
10987
|
+
reportTask({
|
|
10988
|
+
eventName: "upgrade_lark_run",
|
|
10989
|
+
durationMs: opts.durationMs,
|
|
10990
|
+
status: opts.success ? "success" : "failed",
|
|
10991
|
+
extraCategories: {
|
|
10992
|
+
scene: opts.scene ?? "",
|
|
10993
|
+
exit_code: String(opts.exitCode ?? ""),
|
|
10994
|
+
rollback_ok: opts.rollbackOk != null ? String(opts.rollbackOk) : "",
|
|
10995
|
+
validation_error: opts.validationError ?? "",
|
|
10996
|
+
error_msg: opts.error ?? "",
|
|
10997
|
+
log_content: logContent
|
|
10998
|
+
}
|
|
10999
|
+
});
|
|
11000
|
+
}
|
|
11001
|
+
//#endregion
|
|
11002
|
+
//#region src/upgrade-lark.ts
|
|
11003
|
+
/** Plugin directories under extensions/ that are backed up before upgrade */
|
|
11004
|
+
const FEISHU_PLUGIN_DIRS = ["openclaw-lark", "feishu-openclaw-plugin"];
|
|
11005
|
+
/** Version compat rule keys checked in the doctor output after install */
|
|
11006
|
+
const VERSION_COMPAT_RULE_KEYS = ["feishu_plugin_version_compat_lark", "feishu_plugin_version_compat_openclaw"];
|
|
11007
|
+
function backupFiles(opts) {
|
|
11008
|
+
const { workspaceDir, configPath, backupDir, log } = opts;
|
|
11009
|
+
try {
|
|
11010
|
+
node_fs.default.mkdirSync(backupDir, { recursive: true });
|
|
11011
|
+
log(`backup dir: ${backupDir}`);
|
|
11012
|
+
if (node_fs.default.existsSync(configPath)) {
|
|
11013
|
+
const stat = node_fs.default.statSync(configPath);
|
|
11014
|
+
node_fs.default.copyFileSync(configPath, node_path.default.join(backupDir, "openclaw.json"));
|
|
11015
|
+
log(` backed up: openclaw.json (${stat.size} bytes)`);
|
|
11016
|
+
} else log(` skipped: openclaw.json (not found)`);
|
|
11017
|
+
const extSrc = node_path.default.join(workspaceDir, "extensions");
|
|
11018
|
+
for (const pluginDir of FEISHU_PLUGIN_DIRS) {
|
|
11019
|
+
const src = node_path.default.join(extSrc, pluginDir);
|
|
11020
|
+
if (node_fs.default.existsSync(src)) {
|
|
11021
|
+
const dst = node_path.default.join(backupDir, "extensions", pluginDir);
|
|
11022
|
+
node_fs.default.cpSync(src, dst, { recursive: true });
|
|
11023
|
+
const version = readPkgVersion(node_path.default.join(src, "package.json"));
|
|
11024
|
+
log(` backed up: extensions/${pluginDir}${version ? ` (version: ${version})` : ""}`);
|
|
11025
|
+
} else log(` skipped: extensions/${pluginDir} (not found)`);
|
|
11026
|
+
}
|
|
11027
|
+
return { ok: true };
|
|
11028
|
+
} catch (e) {
|
|
11029
|
+
return {
|
|
11030
|
+
ok: false,
|
|
11031
|
+
error: `backup failed: ${e.message}`
|
|
11032
|
+
};
|
|
11033
|
+
}
|
|
11034
|
+
}
|
|
11035
|
+
function restoreFiles(opts) {
|
|
11036
|
+
const { workspaceDir, configPath, backupDir, log } = opts;
|
|
11037
|
+
try {
|
|
11038
|
+
const configBackup = node_path.default.join(backupDir, "openclaw.json");
|
|
11039
|
+
if (node_fs.default.existsSync(configBackup)) {
|
|
11040
|
+
node_fs.default.copyFileSync(configBackup, configPath);
|
|
11041
|
+
log(` restored: openclaw.json`);
|
|
11042
|
+
}
|
|
11043
|
+
const extDst = node_path.default.join(workspaceDir, "extensions");
|
|
11044
|
+
for (const pluginDir of FEISHU_PLUGIN_DIRS) {
|
|
11045
|
+
const backupSrc = node_path.default.join(backupDir, "extensions", pluginDir);
|
|
11046
|
+
if (node_fs.default.existsSync(backupSrc)) {
|
|
11047
|
+
const dst = node_path.default.join(extDst, pluginDir);
|
|
11048
|
+
if (node_fs.default.existsSync(dst)) node_fs.default.rmSync(dst, {
|
|
11049
|
+
recursive: true,
|
|
11050
|
+
force: true
|
|
11051
|
+
});
|
|
11052
|
+
node_fs.default.cpSync(backupSrc, dst, { recursive: true });
|
|
11053
|
+
log(` restored: extensions/${pluginDir}`);
|
|
11054
|
+
}
|
|
11055
|
+
}
|
|
11056
|
+
return true;
|
|
11057
|
+
} catch (e) {
|
|
11058
|
+
log(` restore error: ${e.message}`);
|
|
11059
|
+
return false;
|
|
11060
|
+
}
|
|
11061
|
+
}
|
|
11062
|
+
function readPkgVersion(pkgPath) {
|
|
11063
|
+
try {
|
|
11064
|
+
const pkg = JSON.parse(node_fs.default.readFileSync(pkgPath, "utf-8"));
|
|
11065
|
+
return typeof pkg.version === "string" ? pkg.version : null;
|
|
11066
|
+
} catch {
|
|
11067
|
+
return null;
|
|
11068
|
+
}
|
|
11069
|
+
}
|
|
11070
|
+
function snapshotVersions(cwd, log) {
|
|
11071
|
+
const ocResult = (0, node_child_process.spawnSync)("openclaw", ["--version"], {
|
|
11072
|
+
cwd,
|
|
11073
|
+
encoding: "utf-8",
|
|
11074
|
+
stdio: [
|
|
11075
|
+
"ignore",
|
|
11076
|
+
"pipe",
|
|
11077
|
+
"pipe"
|
|
11078
|
+
],
|
|
11079
|
+
timeout: 5e3
|
|
11080
|
+
});
|
|
11081
|
+
const ocRaw = (ocResult.stdout ?? "").trim() || (ocResult.stderr ?? "").trim();
|
|
11082
|
+
const extDir = node_path.default.join(cwd, "extensions");
|
|
11083
|
+
const larkPkg = node_path.default.join(extDir, "openclaw-lark", "package.json");
|
|
11084
|
+
const feishuPkg = node_path.default.join(extDir, "feishu-openclaw-plugin", "package.json");
|
|
11085
|
+
log(` version-check paths: ${larkPkg} [${node_fs.default.existsSync(larkPkg) ? "exists" : "missing"}]`);
|
|
11086
|
+
log(` version-check paths: ${feishuPkg} [${node_fs.default.existsSync(feishuPkg) ? "exists" : "missing"}]`);
|
|
11087
|
+
return {
|
|
11088
|
+
openclaw: ocRaw || null,
|
|
11089
|
+
openclawLark: readPkgVersion(larkPkg),
|
|
11090
|
+
feishuOpenclawPlugin: readPkgVersion(feishuPkg)
|
|
11091
|
+
};
|
|
11092
|
+
}
|
|
11093
|
+
function logVersionSnapshot(label, v, log) {
|
|
11094
|
+
log(`${label}: openclaw=${v.openclaw ?? "n/a"} openclaw-lark=${v.openclawLark ?? "n/a"} feishu-openclaw-plugin=${v.feishuOpenclawPlugin ?? "n/a"}`);
|
|
11095
|
+
}
|
|
11096
|
+
function countFeishuBots(configPath) {
|
|
11097
|
+
try {
|
|
11098
|
+
const raw = node_fs.default.readFileSync(configPath, "utf-8");
|
|
11099
|
+
const config = loadJSON5().parse(raw);
|
|
11100
|
+
const accounts = getNestedMap(config, "channels", "feishu", "accounts");
|
|
11101
|
+
if (accounts) return Object.keys(accounts).length;
|
|
11102
|
+
const feishu = getNestedMap(config, "channels", "feishu");
|
|
11103
|
+
return typeof feishu?.appId === "string" && feishu.appId ? 1 : 0;
|
|
11104
|
+
} catch {
|
|
11105
|
+
return 0;
|
|
11106
|
+
}
|
|
11107
|
+
}
|
|
11108
|
+
/**
|
|
11109
|
+
* Parse doctor stdout (first JSON line) and return an error string if any
|
|
11110
|
+
* version compat rule failed. Returns null on parse failure so a broken doctor
|
|
11111
|
+
* output does not block the install.
|
|
11112
|
+
*/
|
|
11113
|
+
function checkVersionCompatFromDoctorOutput(stdout, log) {
|
|
11114
|
+
const firstLine = stdout.split("\n")[0]?.trim();
|
|
11115
|
+
if (!firstLine) {
|
|
11116
|
+
log(" doctor(compat): empty output, skipping version compat check");
|
|
11117
|
+
return null;
|
|
11118
|
+
}
|
|
11119
|
+
try {
|
|
11120
|
+
const report = JSON.parse(firstLine);
|
|
11121
|
+
for (const outcome of report.results) if (VERSION_COMPAT_RULE_KEYS.includes(outcome.rule)) {
|
|
11122
|
+
if (outcome.status === "failed" || outcome.status === "still-broken" || outcome.status === "error") return `version compat rule ${outcome.rule} ${outcome.status}: ${outcome.message ?? "(no message)"}`;
|
|
11123
|
+
}
|
|
11124
|
+
return null;
|
|
11125
|
+
} catch (e) {
|
|
11126
|
+
log(` doctor(compat): failed to parse output — ${e.message}`);
|
|
11127
|
+
return null;
|
|
11128
|
+
}
|
|
11129
|
+
}
|
|
11130
|
+
/** Run channels probe, log results, and return the result. Never throws. */
|
|
11131
|
+
function probeChannels(label, log, timeoutMs) {
|
|
11132
|
+
try {
|
|
11133
|
+
const r = runChannelsProbe(timeoutMs);
|
|
11134
|
+
log(` ${label} available=${r.available} anyAccountWorking=${r.anyAccountWorking}`);
|
|
11135
|
+
if (r.error) log(` ${label} error: ${r.error}`);
|
|
11136
|
+
if (r.gatewayReachable != null) log(` ${label} gatewayReachable: ${r.gatewayReachable}`);
|
|
11137
|
+
for (const acct of r.accounts ?? []) log(` ${label} account ${acct.id}: isWorking=${acct.isWorking} bits=[${acct.bits.join(",")}]`);
|
|
11138
|
+
return r;
|
|
11139
|
+
} catch (e) {
|
|
11140
|
+
log(` ${label} channels probe threw: ${e.message}`);
|
|
11141
|
+
return {
|
|
11142
|
+
available: false,
|
|
11143
|
+
accounts: [],
|
|
11144
|
+
anyAccountWorking: false
|
|
11145
|
+
};
|
|
11146
|
+
}
|
|
11147
|
+
}
|
|
11148
|
+
function runUpgradeLark(opts) {
|
|
11149
|
+
const cwd = opts.cwd ?? "/home/gem/workspace/agent";
|
|
11150
|
+
const configPath = opts.configPath ?? CONFIG_PATH;
|
|
11151
|
+
const logFile = upgradeLarkLogFile(opts.runId);
|
|
11152
|
+
const log = makeLogger(logFile);
|
|
11153
|
+
const fsOpts = {
|
|
11154
|
+
workspaceDir: cwd,
|
|
11155
|
+
configPath,
|
|
11156
|
+
backupDir: node_path.default.join(opts.backupBaseDir ?? "/tmp/openclaw-diagnose", `upgrade-lark-backup-${opts.runId}`),
|
|
11157
|
+
log
|
|
11158
|
+
};
|
|
11159
|
+
const cliScript = opts.cliScript ?? process.argv[1];
|
|
11160
|
+
const statusCheckDelayMs = opts.statusCheckDelayMs ?? 5e3;
|
|
11161
|
+
log(`${"=".repeat(60)}`);
|
|
11162
|
+
log(`upgrade-lark started runId=${opts.runId}`);
|
|
11163
|
+
log(` cwd : ${cwd}`);
|
|
11164
|
+
log(` configPath : ${configPath}`);
|
|
11165
|
+
log(`${"=".repeat(60)}`);
|
|
11166
|
+
log("");
|
|
11167
|
+
log("── [Pre-check A] channels probe(升级前)────────────────");
|
|
11168
|
+
const beforeChannels = probeChannels("before", log, 3e4);
|
|
11169
|
+
log("");
|
|
11170
|
+
log("── [Pre-check B] 版本兼容预检 + feishu config 错误检测 ──");
|
|
11171
|
+
let versionIncompatible = false;
|
|
11172
|
+
try {
|
|
11173
|
+
const rawConfig = node_fs.default.readFileSync(configPath, "utf-8");
|
|
11174
|
+
versionIncompatible = needsLarkUpgrade({
|
|
11175
|
+
config: loadJSON5().parse(rawConfig),
|
|
11176
|
+
configPath,
|
|
11177
|
+
vars: {},
|
|
11178
|
+
providerDeps: {
|
|
11179
|
+
usesMiaodaProvider: false,
|
|
11180
|
+
usesMiaodaSecretProvider: false
|
|
11181
|
+
}
|
|
11182
|
+
});
|
|
11183
|
+
log(` version-compat pre-check: ${versionIncompatible ? "NEEDS_UPGRADE" : "ok"}`);
|
|
11184
|
+
} catch (e) {
|
|
11185
|
+
log(` version-compat pre-check error: ${e.message} — treating as needs-upgrade`);
|
|
11186
|
+
versionIncompatible = true;
|
|
11187
|
+
}
|
|
11188
|
+
const feishuConfigInvalid = !versionIncompatible && isFeishuChannelConfigInvalid(cwd);
|
|
11189
|
+
log(` feishu config invalid : ${feishuConfigInvalid}`);
|
|
11190
|
+
log("");
|
|
11191
|
+
log("── [Gate] 升级前置条件检查 ───────────────────────────────");
|
|
11192
|
+
log(` versionIncompatible : ${versionIncompatible}`);
|
|
11193
|
+
log(` feishuConfigInvalid : ${feishuConfigInvalid}`);
|
|
11194
|
+
log(` channels working before: ${beforeChannels.anyAccountWorking}`);
|
|
11195
|
+
if (!(versionIncompatible || feishuConfigInvalid)) {
|
|
11196
|
+
const reason = "version compatible and feishu channel config valid — upgrade not needed";
|
|
11197
|
+
log(` SKIP: ${reason}`);
|
|
11198
|
+
log(`${"=".repeat(60)}`);
|
|
11199
|
+
log("upgrade-lark skipped (pre-check gate)");
|
|
11200
|
+
log(`${"=".repeat(60)}`);
|
|
11201
|
+
return {
|
|
11202
|
+
ok: true,
|
|
11203
|
+
skipped: true,
|
|
11204
|
+
skipReason: reason,
|
|
11205
|
+
logFile
|
|
11206
|
+
};
|
|
11207
|
+
}
|
|
11208
|
+
if (beforeChannels.anyAccountWorking) {
|
|
11209
|
+
const reason = "channels are working — upgrade not needed (issue detected but system is functional)";
|
|
11210
|
+
log(` SKIP: ${reason}`);
|
|
11211
|
+
log(`${"=".repeat(60)}`);
|
|
11212
|
+
log("upgrade-lark skipped (pre-check gate)");
|
|
11213
|
+
log(`${"=".repeat(60)}`);
|
|
11214
|
+
return {
|
|
11215
|
+
ok: true,
|
|
11216
|
+
skipped: true,
|
|
11217
|
+
skipReason: reason,
|
|
11218
|
+
logFile
|
|
11219
|
+
};
|
|
11220
|
+
}
|
|
11221
|
+
log(` PROCEED: requiresLarkUpgrade=true (version=${versionIncompatible}, feishuConfig=${feishuConfigInvalid}) AND channels not working → running upgrade`);
|
|
11222
|
+
log("");
|
|
11223
|
+
log("── [1/6] 文件备份 ────────────────────────────────────────");
|
|
11224
|
+
log(`before-state: botCount=${countFeishuBots(configPath)}`);
|
|
11225
|
+
const backup = backupFiles(fsOpts);
|
|
11226
|
+
if (!backup.ok) {
|
|
11227
|
+
log(`ERROR: ${backup.error}`);
|
|
11228
|
+
return {
|
|
11229
|
+
ok: false,
|
|
11230
|
+
error: backup.error,
|
|
11231
|
+
logFile
|
|
11232
|
+
};
|
|
11233
|
+
}
|
|
11234
|
+
log("backup: ok");
|
|
11235
|
+
logVersionSnapshot("before-versions", snapshotVersions(cwd, log), log);
|
|
11236
|
+
log("");
|
|
11237
|
+
log("── [2/6] 清理本地 openclaw shim ─────────────────────────");
|
|
11238
|
+
const localOpenclawBin = node_path.default.join(cwd, "node_modules", ".bin", "openclaw");
|
|
11239
|
+
if (node_fs.default.existsSync(localOpenclawBin)) try {
|
|
11240
|
+
node_fs.default.rmSync(localOpenclawBin);
|
|
11241
|
+
log(` removed: ${localOpenclawBin}`);
|
|
11242
|
+
} catch (e) {
|
|
11243
|
+
log(` WARN: failed to remove ${localOpenclawBin}: ${e.message}`);
|
|
11244
|
+
}
|
|
11245
|
+
else log(` skipped: ${localOpenclawBin} (not found)`);
|
|
11246
|
+
log("");
|
|
11247
|
+
log("── [3/6] npx install (@larksuite/openclaw-lark-tools update) ──");
|
|
11248
|
+
const npxResult = (0, node_child_process.spawnSync)("npx", [
|
|
11249
|
+
"-y",
|
|
11250
|
+
"@larksuite/openclaw-lark-tools",
|
|
11251
|
+
"update"
|
|
11252
|
+
], {
|
|
11253
|
+
cwd,
|
|
11254
|
+
encoding: "utf-8",
|
|
11255
|
+
stdio: [
|
|
11256
|
+
"ignore",
|
|
11257
|
+
"pipe",
|
|
11258
|
+
"pipe"
|
|
11259
|
+
],
|
|
11260
|
+
timeout: 12e4
|
|
11261
|
+
});
|
|
11262
|
+
const npxStdout = npxResult.stdout?.trim() ?? "";
|
|
11263
|
+
const npxStderr = npxResult.stderr?.trim() ?? "";
|
|
11264
|
+
const npxExitCode = npxResult.status ?? 1;
|
|
11265
|
+
if (npxStdout) log(`npx stdout:\n${npxStdout}`);
|
|
11266
|
+
if (npxStderr) log(`npx stderr:\n${npxStderr}`);
|
|
11267
|
+
log(`npx exit: ${npxExitCode}${npxResult.error ? ` error: ${npxResult.error.message}` : ""}`);
|
|
11268
|
+
if (statusCheckDelayMs > 0) {
|
|
11269
|
+
log("");
|
|
11270
|
+
log(`── 等待 ${statusCheckDelayMs / 1e3}s(让 openclaw 服务完成重启) ─────────────`);
|
|
11271
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, statusCheckDelayMs);
|
|
11272
|
+
log("wait done");
|
|
11273
|
+
}
|
|
11274
|
+
const doRollback = (reason) => {
|
|
11275
|
+
log(`ERROR: ${reason}`);
|
|
11276
|
+
const rollbackOk = restoreFiles(fsOpts);
|
|
11277
|
+
log(`rollback: ${rollbackOk ? "ok" : "FAILED"}`);
|
|
11278
|
+
return {
|
|
11279
|
+
ok: false,
|
|
11280
|
+
error: reason,
|
|
11281
|
+
validationError: reason,
|
|
11282
|
+
stdout: npxStdout,
|
|
11283
|
+
stderr: npxStderr,
|
|
11284
|
+
exitCode: npxExitCode,
|
|
11285
|
+
rollbackOk,
|
|
11286
|
+
logFile
|
|
11287
|
+
};
|
|
11288
|
+
};
|
|
11289
|
+
log("");
|
|
11290
|
+
log("── [4/6] 插件安装检查 + 版本兼容校验 ───────────────────");
|
|
11291
|
+
const larkExtDir = node_path.default.join(cwd, "extensions", "openclaw-lark");
|
|
11292
|
+
const larkVersion = readPkgVersion(node_path.default.join(larkExtDir, "package.json"));
|
|
11293
|
+
log(` extensions/openclaw-lark: ${node_fs.default.existsSync(larkExtDir) ? "exists" : "missing"}, version=${larkVersion ?? "n/a"}`);
|
|
11294
|
+
if (!node_fs.default.existsSync(larkExtDir)) return doRollback("extensions/openclaw-lark not found after install");
|
|
11295
|
+
if (!larkVersion) return doRollback("extensions/openclaw-lark/package.json has no valid version after install");
|
|
11296
|
+
log(" running doctor version compat check...");
|
|
11297
|
+
const compatArgs = ["doctor"];
|
|
11298
|
+
if (opts.scene) compatArgs.push(`--scene=${opts.scene}`);
|
|
11299
|
+
const compatResult = (0, node_child_process.spawnSync)(process.execPath, [cliScript, ...compatArgs], {
|
|
11300
|
+
cwd,
|
|
11301
|
+
encoding: "utf-8",
|
|
11302
|
+
stdio: [
|
|
11303
|
+
"ignore",
|
|
11304
|
+
"pipe",
|
|
11305
|
+
"pipe"
|
|
11306
|
+
],
|
|
11307
|
+
timeout: 6e4,
|
|
11308
|
+
env: process.env
|
|
11309
|
+
});
|
|
11310
|
+
if (compatResult.stdout?.trim()) log(`doctor(compat) stdout:\n${compatResult.stdout.trim()}`);
|
|
11311
|
+
if (compatResult.stderr?.trim()) log(`doctor(compat) stderr:\n${compatResult.stderr.trim()}`);
|
|
11312
|
+
log(`doctor(compat) exit: ${compatResult.status ?? "null"}${compatResult.error ? ` error: ${compatResult.error.message}` : ""}`);
|
|
11313
|
+
const compatError = checkVersionCompatFromDoctorOutput(compatResult.stdout?.trim() ?? "", log);
|
|
11314
|
+
if (compatError) return doRollback(compatError);
|
|
11315
|
+
log(" version compat: ok");
|
|
11316
|
+
logVersionSnapshot("after-versions", snapshotVersions(cwd, log), log);
|
|
11317
|
+
log("");
|
|
11318
|
+
log("── [5/6] channels probe(升级后)────────────────────────");
|
|
11319
|
+
if (!probeChannels("after", log, 3e4).anyAccountWorking) {
|
|
11320
|
+
log(" channels: not working before or after install — pre-existing issue, skipping rollback");
|
|
11321
|
+
return {
|
|
11322
|
+
ok: false,
|
|
11323
|
+
error: "channels probe: no working account (pre-existing issue, not caused by install)",
|
|
11324
|
+
validationError: "channels probe: no working account (pre-existing)",
|
|
11325
|
+
stdout: npxStdout,
|
|
11326
|
+
stderr: npxStderr,
|
|
11327
|
+
exitCode: npxExitCode,
|
|
11328
|
+
logFile
|
|
11329
|
+
};
|
|
11330
|
+
}
|
|
11331
|
+
log(" channels: ok (recovered after install)");
|
|
11332
|
+
log("");
|
|
11333
|
+
log("── [6/6] doctor --fix ────────────────────────────────────");
|
|
11334
|
+
const fixArgs = ["doctor", "--fix"];
|
|
11335
|
+
if (opts.scene) fixArgs.push(`--scene=${opts.scene}`);
|
|
11336
|
+
const fixResult = (0, node_child_process.spawnSync)(process.execPath, [cliScript, ...fixArgs], {
|
|
11337
|
+
cwd,
|
|
11338
|
+
encoding: "utf-8",
|
|
11339
|
+
stdio: [
|
|
11340
|
+
"ignore",
|
|
11341
|
+
"pipe",
|
|
11342
|
+
"pipe"
|
|
11343
|
+
],
|
|
11344
|
+
timeout: 6e4,
|
|
11345
|
+
env: process.env
|
|
11346
|
+
});
|
|
11347
|
+
if (fixResult.stdout?.trim()) log(`doctor(fix) stdout:\n${fixResult.stdout.trim()}`);
|
|
11348
|
+
if (fixResult.stderr?.trim()) log(`doctor(fix) stderr:\n${fixResult.stderr.trim()}`);
|
|
11349
|
+
log(`doctor(fix) exit: ${fixResult.status ?? "null"}${fixResult.error ? ` error: ${fixResult.error.message}` : ""}`);
|
|
11350
|
+
log("");
|
|
11351
|
+
log(`${"=".repeat(60)}`);
|
|
11352
|
+
log("upgrade-lark completed successfully");
|
|
11353
|
+
log(`${"=".repeat(60)}`);
|
|
11354
|
+
return {
|
|
11355
|
+
ok: true,
|
|
11356
|
+
stdout: npxStdout,
|
|
11357
|
+
stderr: npxStderr,
|
|
11358
|
+
exitCode: npxExitCode,
|
|
11359
|
+
logFile
|
|
11360
|
+
};
|
|
11361
|
+
}
|
|
10870
11362
|
//#endregion
|
|
10871
11363
|
//#region src/index.ts
|
|
10872
11364
|
const args = node_process.default.argv.slice(2);
|
|
10873
11365
|
const mode = args.find((a) => !a.startsWith("-"));
|
|
10874
11366
|
/**
|
|
11367
|
+
* Decode `--ctx=<base64>` into an opaque JSON object. Returns undefined when
|
|
11368
|
+
* the flag isn't present — the caller decides whether to fall back to the
|
|
11369
|
+
* innerapi or to error out.
|
|
11370
|
+
*
|
|
11371
|
+
* The object's shape is not enforced here; downstream code consumes it via
|
|
11372
|
+
* either `normalizeCtx()` (new path) or direct field access for the legacy
|
|
11373
|
+
* check/repair/reset contract still used by sandbox_console push.
|
|
11374
|
+
*/
|
|
11375
|
+
function parseCtxFlag(args) {
|
|
11376
|
+
const ctxArg = args.find((a) => a.startsWith("--ctx="));
|
|
11377
|
+
if (!ctxArg) return void 0;
|
|
11378
|
+
const b64 = ctxArg.slice(6);
|
|
11379
|
+
return JSON.parse(Buffer.from(b64, "base64").toString("utf-8"));
|
|
11380
|
+
}
|
|
11381
|
+
/**
|
|
10875
11382
|
* Pull the first non-flag positional after the mode name.
|
|
10876
11383
|
* (The mode itself is args[0] in the filtered set, so we skip index 0.)
|
|
10877
11384
|
*/
|
|
@@ -10899,8 +11406,8 @@ function getMultiFlag(args, name) {
|
|
|
10899
11406
|
* case but is no longer consulted.
|
|
10900
11407
|
*/
|
|
10901
11408
|
async function reportRun(command, rc, _raw, invocation, durationMs, outcome, slardar = {
|
|
10902
|
-
scene
|
|
10903
|
-
profile
|
|
11409
|
+
scene,
|
|
11410
|
+
profile,
|
|
10904
11411
|
fix: false
|
|
10905
11412
|
}) {
|
|
10906
11413
|
console.error(`${command}: telemetry calling report_cli_run`);
|
|
@@ -10964,7 +11471,7 @@ async function main() {
|
|
|
10964
11471
|
console.error(`${mode}: begin argv=[${args.join(" ")}] version=${getVersion()} traceId=${traceId ?? "-"} caller=${caller ?? "-"} runIdGenerated=${rc.generated}`);
|
|
10965
11472
|
switch (mode) {
|
|
10966
11473
|
case "check": {
|
|
10967
|
-
const raw = await fetchCtxViaInnerApi({
|
|
11474
|
+
const raw = parseCtxFlag(args) ?? await fetchCtxViaInnerApi({
|
|
10968
11475
|
populate: planCtxPopulate({
|
|
10969
11476
|
command: "check",
|
|
10970
11477
|
profile
|
|
@@ -10989,7 +11496,7 @@ async function main() {
|
|
|
10989
11496
|
break;
|
|
10990
11497
|
}
|
|
10991
11498
|
case "repair": {
|
|
10992
|
-
const raw = await fetchCtxViaInnerApi({
|
|
11499
|
+
const raw = parseCtxFlag(args) ?? await fetchCtxViaInnerApi({
|
|
10993
11500
|
populate: planCtxPopulate({
|
|
10994
11501
|
command: "repair",
|
|
10995
11502
|
profile
|
|
@@ -11060,15 +11567,27 @@ async function main() {
|
|
|
11060
11567
|
break;
|
|
11061
11568
|
}
|
|
11062
11569
|
case "reset":
|
|
11063
|
-
if (args.includes("--async"))
|
|
11064
|
-
|
|
11570
|
+
if (args.includes("--async")) {
|
|
11571
|
+
const ctxArg = args.find((a) => a.startsWith("--ctx="));
|
|
11572
|
+
let ctxBase64;
|
|
11573
|
+
if (ctxArg) ctxBase64 = ctxArg.slice(6);
|
|
11574
|
+
else {
|
|
11575
|
+
const fetched = await fetchCtxViaInnerApi({
|
|
11576
|
+
populate: planCtxPopulate({ command: "reset" }),
|
|
11577
|
+
caller,
|
|
11578
|
+
traceId
|
|
11579
|
+
});
|
|
11580
|
+
ctxBase64 = Buffer.from(JSON.stringify(fetched), "utf-8").toString("base64");
|
|
11581
|
+
}
|
|
11582
|
+
console.log(JSON.stringify(startAsyncReset(ctxBase64)));
|
|
11583
|
+
} else if (args.includes("--worker")) {
|
|
11065
11584
|
const taskId = args.find((a) => a.startsWith("--task-id="))?.slice(10);
|
|
11066
11585
|
if (!taskId) {
|
|
11067
11586
|
console.error("Error: --task-id=<id> is required for worker");
|
|
11068
11587
|
node_process.default.exit(1);
|
|
11069
11588
|
}
|
|
11070
11589
|
const resultFile = resetResultFile(taskId);
|
|
11071
|
-
const raw = await fetchCtxViaInnerApi({
|
|
11590
|
+
const raw = parseCtxFlag(args) ?? await fetchCtxViaInnerApi({
|
|
11072
11591
|
populate: planCtxPopulate({ command: "reset" }),
|
|
11073
11592
|
caller,
|
|
11074
11593
|
traceId
|
|
@@ -11092,7 +11611,7 @@ async function main() {
|
|
|
11092
11611
|
return;
|
|
11093
11612
|
}
|
|
11094
11613
|
} else {
|
|
11095
|
-
console.error("Usage: reset --async | reset --worker --task-id=<id>");
|
|
11614
|
+
console.error("Usage: reset --async [--ctx=<base64>] | reset --worker --task-id=<id> [--ctx=<base64>]");
|
|
11096
11615
|
node_process.default.exit(1);
|
|
11097
11616
|
}
|
|
11098
11617
|
break;
|
|
@@ -11108,14 +11627,14 @@ async function main() {
|
|
|
11108
11627
|
case "install-openclaw": {
|
|
11109
11628
|
const tag = getPositionalTag(args, "install-openclaw");
|
|
11110
11629
|
if (!tag) {
|
|
11111
|
-
console.error("Usage: install-openclaw <tag> [--oss_file_map=<base64>]");
|
|
11630
|
+
console.error("Usage: install-openclaw <tag> [--ctx=<base64> | --oss_file_map=<base64>]");
|
|
11112
11631
|
node_process.default.exit(1);
|
|
11113
11632
|
}
|
|
11114
11633
|
const ossFileMapFlag = getFlag(args, "oss_file_map");
|
|
11115
11634
|
let installOssFileMap;
|
|
11116
11635
|
let rawForTelemetry;
|
|
11117
11636
|
if (!ossFileMapFlag) {
|
|
11118
|
-
rawForTelemetry = await fetchCtxViaInnerApi({
|
|
11637
|
+
rawForTelemetry = parseCtxFlag(args) ?? await fetchCtxViaInnerApi({
|
|
11119
11638
|
populate: planCtxPopulate({ command: "install" }),
|
|
11120
11639
|
caller,
|
|
11121
11640
|
traceId
|
|
@@ -11150,7 +11669,7 @@ async function main() {
|
|
|
11150
11669
|
case "install-extension": {
|
|
11151
11670
|
const tag = getPositionalTag(args, "install-extension");
|
|
11152
11671
|
if (!tag) {
|
|
11153
|
-
console.error("Usage: install-extension <tag> (--all | --extension=<name>...) [--home_base=<dir>] [--config_path=<path>] [--skip-config-update] [--oss_file_map=<base64>]");
|
|
11672
|
+
console.error("Usage: install-extension <tag> (--all | --extension=<name>...) [--home_base=<dir>] [--config_path=<path>] [--skip-config-update] [--ctx=<base64> | --oss_file_map=<base64>]");
|
|
11154
11673
|
node_process.default.exit(1);
|
|
11155
11674
|
}
|
|
11156
11675
|
const all = args.includes("--all");
|
|
@@ -11162,7 +11681,7 @@ async function main() {
|
|
|
11162
11681
|
let installOssFileMap;
|
|
11163
11682
|
let rawForTelemetry;
|
|
11164
11683
|
if (!ossFileMapFlag) {
|
|
11165
|
-
rawForTelemetry = await fetchCtxViaInnerApi({
|
|
11684
|
+
rawForTelemetry = parseCtxFlag(args) ?? await fetchCtxViaInnerApi({
|
|
11166
11685
|
populate: planCtxPopulate({ command: "install" }),
|
|
11167
11686
|
caller,
|
|
11168
11687
|
traceId
|
|
@@ -11208,12 +11727,12 @@ async function main() {
|
|
|
11208
11727
|
case "install-cli": {
|
|
11209
11728
|
const tag = getPositionalTag(args, "install-cli");
|
|
11210
11729
|
if (!tag) {
|
|
11211
|
-
console.error("Usage: install-cli <tag> --cli=<name>... [--home_base=<dir>] [--oss_file_map=<base64>]");
|
|
11730
|
+
console.error("Usage: install-cli <tag> --cli=<name>... [--home_base=<dir>] [--ctx=<base64> | --oss_file_map=<base64>]");
|
|
11212
11731
|
node_process.default.exit(1);
|
|
11213
11732
|
}
|
|
11214
11733
|
const names = getMultiFlag(args, "cli");
|
|
11215
11734
|
if (names.length === 0) {
|
|
11216
|
-
console.error("Usage: install-cli <tag> --cli=<name>... [--home_base=<dir>] [--oss_file_map=<base64>]");
|
|
11735
|
+
console.error("Usage: install-cli <tag> --cli=<name>... [--home_base=<dir>] [--ctx=<base64> | --oss_file_map=<base64>]");
|
|
11217
11736
|
node_process.default.exit(1);
|
|
11218
11737
|
}
|
|
11219
11738
|
const homeBase = getFlag(args, "home_base");
|
|
@@ -11221,7 +11740,7 @@ async function main() {
|
|
|
11221
11740
|
let installOssFileMap;
|
|
11222
11741
|
let rawForTelemetry;
|
|
11223
11742
|
if (!ossFileMapFlag) {
|
|
11224
|
-
rawForTelemetry = await fetchCtxViaInnerApi({
|
|
11743
|
+
rawForTelemetry = parseCtxFlag(args) ?? await fetchCtxViaInnerApi({
|
|
11225
11744
|
populate: planCtxPopulate({ command: "install" }),
|
|
11226
11745
|
caller,
|
|
11227
11746
|
traceId
|
|
@@ -11269,7 +11788,7 @@ async function main() {
|
|
|
11269
11788
|
case "download-resource": {
|
|
11270
11789
|
const tag = getPositionalTag(args, "download-resource");
|
|
11271
11790
|
if (!tag) {
|
|
11272
|
-
console.error("Usage: download-resource <tag> --role=<role> --name=<name> [--dir=<dir>] [--oss_file_map=<base64>]");
|
|
11791
|
+
console.error("Usage: download-resource <tag> --role=<role> --name=<name> [--dir=<dir>] [--ctx=<base64> | --oss_file_map=<base64>]");
|
|
11273
11792
|
node_process.default.exit(1);
|
|
11274
11793
|
}
|
|
11275
11794
|
const role = getFlag(args, "role");
|
|
@@ -11283,7 +11802,7 @@ async function main() {
|
|
|
11283
11802
|
let installOssFileMap;
|
|
11284
11803
|
let rawForTelemetry;
|
|
11285
11804
|
if (!ossFileMapFlag) {
|
|
11286
|
-
rawForTelemetry = await fetchCtxViaInnerApi({
|
|
11805
|
+
rawForTelemetry = parseCtxFlag(args) ?? await fetchCtxViaInnerApi({
|
|
11287
11806
|
populate: planCtxPopulate({ command: "install" }),
|
|
11288
11807
|
caller,
|
|
11289
11808
|
traceId
|
|
@@ -11357,6 +11876,50 @@ async function main() {
|
|
|
11357
11876
|
if (!result.ok) node_process.default.exit(1);
|
|
11358
11877
|
break;
|
|
11359
11878
|
}
|
|
11879
|
+
case "upgrade-lark": {
|
|
11880
|
+
const result = runUpgradeLark({
|
|
11881
|
+
runId: rc.runId,
|
|
11882
|
+
scene
|
|
11883
|
+
});
|
|
11884
|
+
const upgradeDurationMs = Date.now() - t0;
|
|
11885
|
+
console.log(JSON.stringify(result));
|
|
11886
|
+
reportUpgradeLarkToSlardar({
|
|
11887
|
+
scene,
|
|
11888
|
+
durationMs: upgradeDurationMs,
|
|
11889
|
+
success: result.ok,
|
|
11890
|
+
logFile: result.logFile,
|
|
11891
|
+
exitCode: result.exitCode,
|
|
11892
|
+
rollbackOk: result.rollbackOk,
|
|
11893
|
+
validationError: result.validationError,
|
|
11894
|
+
error: result.error
|
|
11895
|
+
});
|
|
11896
|
+
try {
|
|
11897
|
+
await reportCliRun({
|
|
11898
|
+
command: "upgrade-lark",
|
|
11899
|
+
runId: rc.runId,
|
|
11900
|
+
version: getVersion(),
|
|
11901
|
+
invocation: args.join(" "),
|
|
11902
|
+
durationMs: upgradeDurationMs,
|
|
11903
|
+
caller: rc.caller,
|
|
11904
|
+
traceId: rc.traceId,
|
|
11905
|
+
success: result.ok,
|
|
11906
|
+
result,
|
|
11907
|
+
error: result.ok ? void 0 : { message: result.error ?? "upgrade-lark failed" }
|
|
11908
|
+
});
|
|
11909
|
+
} catch (e) {
|
|
11910
|
+
console.error(`[telemetry] reportCliRun failed: ${e.message}`);
|
|
11911
|
+
}
|
|
11912
|
+
if (!result.ok) {
|
|
11913
|
+
node_process.default.exitCode = 1;
|
|
11914
|
+
return;
|
|
11915
|
+
}
|
|
11916
|
+
break;
|
|
11917
|
+
}
|
|
11918
|
+
case "channels-probe": {
|
|
11919
|
+
const result = runChannelsProbe(getFlag(args, "timeout") ? Number(getFlag(args, "timeout")) : void 0);
|
|
11920
|
+
console.log(JSON.stringify(result));
|
|
11921
|
+
break;
|
|
11922
|
+
}
|
|
11360
11923
|
default:
|
|
11361
11924
|
node_process.default.stderr.write(`Unknown command: ${mode}\n\n`);
|
|
11362
11925
|
node_process.default.stderr.write(formatTopLevelHelp(helpFlags.expert));
|
package/package.json
CHANGED