@lark-apaas/openclaw-scripts-diagnose-cli 0.1.14-alpha.4 → 0.1.14-alpha.6
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 +225 -790
- 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.6";
|
|
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,188 +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 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);
|
|
3698
3508
|
//#endregion
|
|
3699
3509
|
//#region src/rules/cleanup-install-backup-dirs.ts
|
|
3700
3510
|
const DIR_PREFIX = ".openclaw-install-";
|
|
@@ -3840,6 +3650,117 @@ LarkCliMissingForInstalledLarkPluginRule = __decorate([Rule({
|
|
|
3840
3650
|
usesVars: ["recommendedOpenclawTag"]
|
|
3841
3651
|
})], LarkCliMissingForInstalledLarkPluginRule);
|
|
3842
3652
|
//#endregion
|
|
3653
|
+
//#region src/rules/feishu-bot-channel-config.ts
|
|
3654
|
+
/**
|
|
3655
|
+
* Ensures each bot account's channel config is correct:
|
|
3656
|
+
* 1. `allowFrom` contains its own `creatorOpenID` from larkApps
|
|
3657
|
+
* 2. `appSecret` is either the canonical provider-ref or matches larkApps plaintext
|
|
3658
|
+
*
|
|
3659
|
+
* Covers both multi-account (channels.feishu.accounts) and single-account
|
|
3660
|
+
* (channels.feishu.appId + allowFrom at top level) layouts.
|
|
3661
|
+
*/
|
|
3662
|
+
let FeishuBotChannelConfigRule = class FeishuBotChannelConfigRule extends DiagnoseRule {
|
|
3663
|
+
validate(ctx) {
|
|
3664
|
+
const larkApps = ctx.vars.larkApps;
|
|
3665
|
+
if (!larkApps || larkApps.length === 0) return { pass: true };
|
|
3666
|
+
const feishu = asRecord(getNestedMap(ctx.config, "channels", "feishu"));
|
|
3667
|
+
if (!feishu) return { pass: true };
|
|
3668
|
+
const issues = [];
|
|
3669
|
+
const accounts = asRecord(feishu.accounts);
|
|
3670
|
+
if (accounts) for (const [accountId, account] of Object.entries(accounts)) {
|
|
3671
|
+
const bot = asRecord(account);
|
|
3672
|
+
if (!bot) continue;
|
|
3673
|
+
const appId = bot.appId;
|
|
3674
|
+
if (typeof appId !== "string" || !appId.startsWith("cli_")) continue;
|
|
3675
|
+
const larkApp = larkApps.find((e) => e.larkAppID === appId);
|
|
3676
|
+
if (!larkApp) continue;
|
|
3677
|
+
this.checkBot(accountId, bot, larkApp, issues);
|
|
3678
|
+
}
|
|
3679
|
+
const singleAppId = feishu.appId;
|
|
3680
|
+
if (typeof singleAppId === "string" && singleAppId.startsWith("cli_") && !accounts) {
|
|
3681
|
+
const larkApp = larkApps.find((e) => e.larkAppID === singleAppId);
|
|
3682
|
+
if (larkApp) this.checkBot("feishu", feishu, larkApp, issues);
|
|
3683
|
+
}
|
|
3684
|
+
if (issues.length === 0) return { pass: true };
|
|
3685
|
+
return {
|
|
3686
|
+
pass: false,
|
|
3687
|
+
message: issues.join("; ")
|
|
3688
|
+
};
|
|
3689
|
+
}
|
|
3690
|
+
/** Check a single bot entry (either an account object or the feishu channel itself).
|
|
3691
|
+
* appSecret is validated based on its current type:
|
|
3692
|
+
* - object → must match canonical provider-ref
|
|
3693
|
+
* - string → must match larkApps plaintext
|
|
3694
|
+
*/
|
|
3695
|
+
checkBot(label, bot, larkApp, issues) {
|
|
3696
|
+
const creatorOpenID = larkApp.creatorOpenID;
|
|
3697
|
+
const allowFrom = Array.isArray(bot.allowFrom) ? bot.allowFrom : [];
|
|
3698
|
+
if (typeof creatorOpenID === "string" && creatorOpenID !== "") {
|
|
3699
|
+
if (!allowFrom.includes(creatorOpenID)) issues.push(`${label} allowFrom missing creatorOpenID ${creatorOpenID.length > 8 ? creatorOpenID.slice(0, 4) + "***" + creatorOpenID.slice(-4) : "***"}`);
|
|
3700
|
+
} else if (allowFrom.length === 0) issues.push(`${label} allowFrom is empty (creatorOpenID unavailable, cannot auto-fix)`);
|
|
3701
|
+
const secret = bot.appSecret;
|
|
3702
|
+
if (typeof secret === "object" && secret !== null && !Array.isArray(secret)) {
|
|
3703
|
+
if (!matchMap(secret, DEFAULT_FEISHU_APP_SECRET)) issues.push(`${label} appSecret is a provider-ref but not the canonical one`);
|
|
3704
|
+
} else if (typeof secret === "string") {
|
|
3705
|
+
if (secret !== larkApp.appSecret) issues.push(`${label} appSecret plaintext mismatch`);
|
|
3706
|
+
} else issues.push(`${label} appSecret has unexpected type ${typeof secret}`);
|
|
3707
|
+
}
|
|
3708
|
+
repair(ctx) {
|
|
3709
|
+
const larkApps = ctx.vars.larkApps;
|
|
3710
|
+
if (!larkApps || larkApps.length === 0) return;
|
|
3711
|
+
const feishu = asRecord(getNestedMap(ctx.config, "channels", "feishu"));
|
|
3712
|
+
if (!feishu) return;
|
|
3713
|
+
const accounts = asRecord(feishu.accounts);
|
|
3714
|
+
if (accounts) for (const [, account] of Object.entries(accounts)) {
|
|
3715
|
+
const bot = asRecord(account);
|
|
3716
|
+
if (!bot) continue;
|
|
3717
|
+
const appId = bot.appId;
|
|
3718
|
+
if (typeof appId !== "string" || !appId.startsWith("cli_")) continue;
|
|
3719
|
+
const larkApp = larkApps.find((e) => e.larkAppID === appId);
|
|
3720
|
+
if (!larkApp) continue;
|
|
3721
|
+
this.fixBot(bot, larkApp);
|
|
3722
|
+
}
|
|
3723
|
+
const singleAppId = feishu.appId;
|
|
3724
|
+
if (typeof singleAppId === "string" && singleAppId.startsWith("cli_") && !accounts) {
|
|
3725
|
+
const larkApp = larkApps.find((e) => e.larkAppID === singleAppId);
|
|
3726
|
+
if (larkApp) this.fixBot(feishu, larkApp);
|
|
3727
|
+
}
|
|
3728
|
+
}
|
|
3729
|
+
/** Fix a single bot entry in-place.
|
|
3730
|
+
* appSecret is repaired based on its current type:
|
|
3731
|
+
* - object → canonical provider-ref
|
|
3732
|
+
* - string → plaintext from larkApps
|
|
3733
|
+
*/
|
|
3734
|
+
fixBot(bot, larkApp) {
|
|
3735
|
+
const creatorOpenID = larkApp.creatorOpenID;
|
|
3736
|
+
if (typeof creatorOpenID === "string" && creatorOpenID !== "") {
|
|
3737
|
+
const allowFrom = Array.isArray(bot.allowFrom) ? [...bot.allowFrom] : [];
|
|
3738
|
+
if (!allowFrom.includes(creatorOpenID)) {
|
|
3739
|
+
allowFrom.push(creatorOpenID);
|
|
3740
|
+
bot.allowFrom = allowFrom;
|
|
3741
|
+
}
|
|
3742
|
+
}
|
|
3743
|
+
const secret = bot.appSecret;
|
|
3744
|
+
if (typeof secret === "object" && secret !== null && !Array.isArray(secret)) {
|
|
3745
|
+
if (!matchMap(secret, DEFAULT_FEISHU_APP_SECRET)) bot.appSecret = { ...DEFAULT_FEISHU_APP_SECRET };
|
|
3746
|
+
} else if (typeof secret === "string") {
|
|
3747
|
+
if (secret !== larkApp.appSecret) bot.appSecret = larkApp.appSecret;
|
|
3748
|
+
} else bot.appSecret = { ...DEFAULT_FEISHU_APP_SECRET };
|
|
3749
|
+
}
|
|
3750
|
+
};
|
|
3751
|
+
FeishuBotChannelConfigRule = __decorate([Rule({
|
|
3752
|
+
key: "feishu_bot_channel_config",
|
|
3753
|
+
description: "确保飞书配置中 bot 账号的 allowFrom 包含其创建者 openID 且 appSecret 值正确",
|
|
3754
|
+
dependsOn: [
|
|
3755
|
+
"config_syntax_check",
|
|
3756
|
+
"feishu_default_account",
|
|
3757
|
+
"feishu_bot_id"
|
|
3758
|
+
],
|
|
3759
|
+
repairMode: "standard",
|
|
3760
|
+
usesVars: ["larkApps"],
|
|
3761
|
+
level: "critical"
|
|
3762
|
+
})], FeishuBotChannelConfigRule);
|
|
3763
|
+
//#endregion
|
|
3843
3764
|
//#region src/check.ts
|
|
3844
3765
|
/** Telemetry-aware entry: returns both the legacy CheckResult (for stdout)
|
|
3845
3766
|
* AND a DoctorReport-shape payload (for `openclaw.report_cli_run`). The
|
|
@@ -4303,9 +4224,6 @@ const PROVIDER_FILE_PATH = "/home/gem/workspace/.force/openclaw/miaoda-provider-
|
|
|
4303
4224
|
const SECRETS_FILE_PATH = "/home/gem/workspace/.force/openclaw/miaoda-openclaw-secrets.json";
|
|
4304
4225
|
/** Absolute path to the openclaw config JSON. */
|
|
4305
4226
|
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
|
-
}
|
|
4309
4227
|
//#endregion
|
|
4310
4228
|
//#region src/run-log.ts
|
|
4311
4229
|
let currentRunContext;
|
|
@@ -4442,9 +4360,10 @@ function makeLogger(logFile) {
|
|
|
4442
4360
|
/**
|
|
4443
4361
|
* Start an async reset task: spawn a detached child process and return the taskId.
|
|
4444
4362
|
*
|
|
4445
|
-
* The child process runs: node cli.js reset --worker --task-id=xxx
|
|
4363
|
+
* The child process runs: node cli.js reset --worker --task-id=xxx
|
|
4364
|
+
* The worker fetches ctx from innerApi itself — no --ctx passthrough.
|
|
4446
4365
|
*/
|
|
4447
|
-
function startAsyncReset(
|
|
4366
|
+
function startAsyncReset() {
|
|
4448
4367
|
const taskId = (0, node_crypto.randomUUID)();
|
|
4449
4368
|
const resultFile = resetResultFile(taskId);
|
|
4450
4369
|
const log = makeLogger(resetLogFile(taskId));
|
|
@@ -4468,8 +4387,7 @@ function startAsyncReset(ctxBase64) {
|
|
|
4468
4387
|
process.argv[1],
|
|
4469
4388
|
"reset",
|
|
4470
4389
|
"--worker",
|
|
4471
|
-
`--task-id=${taskId}
|
|
4472
|
-
`--ctx=${ctxBase64}`
|
|
4390
|
+
`--task-id=${taskId}`
|
|
4473
4391
|
], {
|
|
4474
4392
|
detached: true,
|
|
4475
4393
|
stdio: "ignore",
|
|
@@ -6983,6 +6901,60 @@ function mergeCoreBackupAndOrigins(configPath, vars, resetData, log) {
|
|
|
6983
6901
|
log(`allowedOrigins: added ${added.length} (${JSON.stringify(added)}), total now ${mergedOrigins.length}`);
|
|
6984
6902
|
}
|
|
6985
6903
|
/**
|
|
6904
|
+
* Fix bot account allowFrom and appSecret using larkApps from innerApi.
|
|
6905
|
+
*
|
|
6906
|
+
* For each bot account (key starts with `bot-cli_`):
|
|
6907
|
+
* - allowFrom must contain the bot's own creatorOpenID from larkApps
|
|
6908
|
+
* - appSecret must be either the canonical provider-ref or match larkApps plaintext
|
|
6909
|
+
*
|
|
6910
|
+
* Runs after mergeCoreBackupAndOrigins so it operates on the final config state.
|
|
6911
|
+
*/
|
|
6912
|
+
function fixBotChannelConfig(configPath, larkApps, log) {
|
|
6913
|
+
if (!larkApps || larkApps.length === 0) {
|
|
6914
|
+
log("no larkApps data, skip bot channel config fix");
|
|
6915
|
+
return;
|
|
6916
|
+
}
|
|
6917
|
+
const config = loadJSON5().parse(node_fs.default.readFileSync(configPath, "utf-8"));
|
|
6918
|
+
const accounts = asRecord(getNestedMap(config, "channels", "feishu")?.accounts);
|
|
6919
|
+
if (!accounts) {
|
|
6920
|
+
log("no feishu accounts in config, skip bot channel config fix");
|
|
6921
|
+
return;
|
|
6922
|
+
}
|
|
6923
|
+
let fixCount = 0;
|
|
6924
|
+
for (const [, account] of Object.entries(accounts)) {
|
|
6925
|
+
const bot = asRecord(account);
|
|
6926
|
+
if (!bot) continue;
|
|
6927
|
+
const appId = bot.appId;
|
|
6928
|
+
if (typeof appId !== "string" || !appId.startsWith("cli_")) continue;
|
|
6929
|
+
const larkApp = larkApps.find((e) => e.larkAppID === appId);
|
|
6930
|
+
if (!larkApp) continue;
|
|
6931
|
+
const creatorOpenID = larkApp.creatorOpenID;
|
|
6932
|
+
if (typeof creatorOpenID === "string" && creatorOpenID !== "") {
|
|
6933
|
+
const allowFrom = Array.isArray(bot.allowFrom) ? [...bot.allowFrom] : [];
|
|
6934
|
+
if (!allowFrom.includes(creatorOpenID)) {
|
|
6935
|
+
allowFrom.push(creatorOpenID);
|
|
6936
|
+
bot.allowFrom = allowFrom;
|
|
6937
|
+
fixCount++;
|
|
6938
|
+
}
|
|
6939
|
+
}
|
|
6940
|
+
const secret = bot.appSecret;
|
|
6941
|
+
let needsFix = false;
|
|
6942
|
+
if (typeof secret === "object" && secret !== null && !Array.isArray(secret)) {
|
|
6943
|
+
if (!matchMap(secret, DEFAULT_FEISHU_APP_SECRET)) needsFix = true;
|
|
6944
|
+
} else if (typeof secret === "string") {
|
|
6945
|
+
if (secret !== larkApp.appSecret) needsFix = true;
|
|
6946
|
+
} else needsFix = true;
|
|
6947
|
+
if (needsFix) {
|
|
6948
|
+
bot.appSecret = { ...DEFAULT_FEISHU_APP_SECRET };
|
|
6949
|
+
fixCount++;
|
|
6950
|
+
}
|
|
6951
|
+
}
|
|
6952
|
+
if (fixCount > 0) {
|
|
6953
|
+
node_fs.default.writeFileSync(configPath, JSON.stringify(config, null, 2), "utf-8");
|
|
6954
|
+
log(`fixed ${fixCount} bot channel config issue(s) (allowFrom/appSecret)`);
|
|
6955
|
+
} else log("bot channel config ok, no fixes needed");
|
|
6956
|
+
}
|
|
6957
|
+
/**
|
|
6986
6958
|
* Step 7: Verify startup scripts landed in configDir/scripts/.
|
|
6987
6959
|
*
|
|
6988
6960
|
* Scripts are extracted directly to configDir/scripts/ during stageTemplate —
|
|
@@ -7127,6 +7099,7 @@ async function runReset(input, taskId, resultFile) {
|
|
|
7127
7099
|
await step5InstallOpenclaw(openclawTag, ossFileMap, log);
|
|
7128
7100
|
step(6);
|
|
7129
7101
|
mergeCoreBackupAndOrigins(configPath, vars, resetData, log);
|
|
7102
|
+
fixBotChannelConfig(configPath, vars.larkApps, log);
|
|
7130
7103
|
step(7);
|
|
7131
7104
|
verifyStartupScripts(configDir, log);
|
|
7132
7105
|
step(8);
|
|
@@ -7925,7 +7898,8 @@ function normalizeCtx(raw) {
|
|
|
7925
7898
|
reset: {
|
|
7926
7899
|
templateVars: r.reset.templateVars ?? {},
|
|
7927
7900
|
coreBackup: r.reset.coreBackup
|
|
7928
|
-
}
|
|
7901
|
+
},
|
|
7902
|
+
larkApps: Array.isArray(r.larkApps) ? r.larkApps : []
|
|
7929
7903
|
};
|
|
7930
7904
|
}
|
|
7931
7905
|
const vars = r.vars ?? {};
|
|
@@ -7950,7 +7924,8 @@ function normalizeCtx(raw) {
|
|
|
7950
7924
|
reset: {
|
|
7951
7925
|
templateVars: resetData.templateVars ?? {},
|
|
7952
7926
|
coreBackup: resetData.coreBackup
|
|
7953
|
-
}
|
|
7927
|
+
},
|
|
7928
|
+
larkApps: Array.isArray(r.larkApps) ? r.larkApps : []
|
|
7954
7929
|
};
|
|
7955
7930
|
}
|
|
7956
7931
|
function fillApp(src) {
|
|
@@ -8015,7 +7990,8 @@ function buildCheckInput(raw, configPathOverride) {
|
|
|
8015
7990
|
providerFilePath: PROVIDER_FILE_PATH,
|
|
8016
7991
|
secretsFilePath: SECRETS_FILE_PATH,
|
|
8017
7992
|
templateVars: ctx.app.templateVars,
|
|
8018
|
-
recommendedOpenclawTag: ctx.app.recommendedOpenclawTag
|
|
7993
|
+
recommendedOpenclawTag: ctx.app.recommendedOpenclawTag,
|
|
7994
|
+
larkApps: ctx.larkApps
|
|
8019
7995
|
},
|
|
8020
7996
|
templateVars: ctx.app.templateVars
|
|
8021
7997
|
};
|
|
@@ -8047,7 +8023,8 @@ function buildRepairInput(raw, configPathOverride) {
|
|
|
8047
8023
|
providerFilePath: PROVIDER_FILE_PATH,
|
|
8048
8024
|
secretsFilePath: SECRETS_FILE_PATH,
|
|
8049
8025
|
templateVars: ctx.app.templateVars,
|
|
8050
|
-
recommendedOpenclawTag: ctx.app.recommendedOpenclawTag
|
|
8026
|
+
recommendedOpenclawTag: ctx.app.recommendedOpenclawTag,
|
|
8027
|
+
larkApps: ctx.larkApps
|
|
8051
8028
|
},
|
|
8052
8029
|
repairData: {
|
|
8053
8030
|
secretsContent: ctx.secrets.secretsContent,
|
|
@@ -8083,7 +8060,8 @@ function buildResetInput(raw, configPathOverride) {
|
|
|
8083
8060
|
providerFilePath: PROVIDER_FILE_PATH,
|
|
8084
8061
|
secretsFilePath: SECRETS_FILE_PATH,
|
|
8085
8062
|
templateVars: ctx.app.templateVars,
|
|
8086
|
-
recommendedOpenclawTag: ctx.app.recommendedOpenclawTag
|
|
8063
|
+
recommendedOpenclawTag: ctx.app.recommendedOpenclawTag,
|
|
8064
|
+
larkApps: ctx.larkApps
|
|
8087
8065
|
},
|
|
8088
8066
|
resetData: {
|
|
8089
8067
|
templateVars: ctx.reset.templateVars,
|
|
@@ -10393,7 +10371,7 @@ async function reportCliRun(opts) {
|
|
|
10393
10371
|
//#region src/help.ts
|
|
10394
10372
|
const BIN = "mclaw-diagnose";
|
|
10395
10373
|
function versionBanner() {
|
|
10396
|
-
return `v0.1.14-alpha.
|
|
10374
|
+
return `v0.1.14-alpha.6`;
|
|
10397
10375
|
}
|
|
10398
10376
|
const COMMANDS = [
|
|
10399
10377
|
{
|
|
@@ -10497,16 +10475,12 @@ EXIT CODES
|
|
|
10497
10475
|
hidden: true,
|
|
10498
10476
|
summary: "Run rule-engine check only",
|
|
10499
10477
|
help: `USAGE
|
|
10500
|
-
${BIN} check
|
|
10478
|
+
${BIN} check
|
|
10501
10479
|
|
|
10502
10480
|
DESCRIPTION
|
|
10503
10481
|
Runs the rule engine against the sandbox's current openclaw config and
|
|
10504
|
-
returns { failedRules }.
|
|
10505
|
-
|
|
10506
|
-
|
|
10507
|
-
OPTIONS
|
|
10508
|
-
--ctx=<base64> Opaque ctx JSON (base64). When absent, fetched from
|
|
10509
|
-
innerapi (same path as doctor).
|
|
10482
|
+
returns { failedRules }. Ctx is fetched from innerapi automatically.
|
|
10483
|
+
End-users should prefer \`doctor\`.
|
|
10510
10484
|
`
|
|
10511
10485
|
},
|
|
10512
10486
|
{
|
|
@@ -10514,16 +10488,11 @@ OPTIONS
|
|
|
10514
10488
|
hidden: true,
|
|
10515
10489
|
summary: "Apply standard-mode repairs",
|
|
10516
10490
|
help: `USAGE
|
|
10517
|
-
${BIN} repair
|
|
10491
|
+
${BIN} repair
|
|
10518
10492
|
|
|
10519
10493
|
DESCRIPTION
|
|
10520
|
-
Runs repair for the failing rules
|
|
10521
|
-
|
|
10522
|
-
\`doctor --fix\` instead.
|
|
10523
|
-
|
|
10524
|
-
OPTIONS
|
|
10525
|
-
--ctx=<base64> Opaque ctx JSON (base64). When absent, fetched from
|
|
10526
|
-
innerapi.
|
|
10494
|
+
Runs repair for the failing rules. Ctx is fetched from innerapi
|
|
10495
|
+
automatically. End-users should use \`doctor --fix\` instead.
|
|
10527
10496
|
`
|
|
10528
10497
|
},
|
|
10529
10498
|
{
|
|
@@ -10531,14 +10500,15 @@ OPTIONS
|
|
|
10531
10500
|
hidden: true,
|
|
10532
10501
|
summary: "Re-initialize sandbox via the 9-step reset pipeline",
|
|
10533
10502
|
help: `USAGE
|
|
10534
|
-
${BIN} reset --async
|
|
10535
|
-
${BIN} reset --worker --task-id=<id>
|
|
10503
|
+
${BIN} reset --async
|
|
10504
|
+
${BIN} reset --worker --task-id=<id>
|
|
10536
10505
|
|
|
10537
10506
|
DESCRIPTION
|
|
10538
10507
|
Two-phase pipeline driven asynchronously: the --async invocation spawns
|
|
10539
10508
|
a detached worker and returns { taskId } immediately; the --worker
|
|
10540
10509
|
invocation (spawned by --async) runs the actual 9 steps and writes
|
|
10541
10510
|
progress to /tmp/openclaw-diagnose/reset-<taskId>.json.
|
|
10511
|
+
Ctx is fetched from innerapi automatically.
|
|
10542
10512
|
|
|
10543
10513
|
Poll progress with \`${BIN} get_reset_task --task-id=<id>\`.
|
|
10544
10514
|
|
|
@@ -10546,7 +10516,6 @@ OPTIONS
|
|
|
10546
10516
|
--async Start a detached worker and return taskId on stdout.
|
|
10547
10517
|
--worker Internal — run the 9-step pipeline (launched by --async).
|
|
10548
10518
|
--task-id=<id> Required with --worker; identifies the progress file.
|
|
10549
|
-
--ctx=<base64> Opaque ctx JSON; fetched from innerapi when absent.
|
|
10550
10519
|
`
|
|
10551
10520
|
},
|
|
10552
10521
|
{
|
|
@@ -10569,7 +10538,7 @@ OPTIONS
|
|
|
10569
10538
|
hidden: true,
|
|
10570
10539
|
summary: "Download + install the openclaw tarball",
|
|
10571
10540
|
help: `USAGE
|
|
10572
|
-
${BIN} install-openclaw <tag> [--
|
|
10541
|
+
${BIN} install-openclaw <tag> [--oss_file_map=<base64>]
|
|
10573
10542
|
|
|
10574
10543
|
DESCRIPTION
|
|
10575
10544
|
Downloads the openclaw@<tag> tgz via the signed OSS URL found in the
|
|
@@ -10581,9 +10550,9 @@ ARGUMENTS
|
|
|
10581
10550
|
<tag> Openclaw version tag, e.g. 2026.4.11.
|
|
10582
10551
|
|
|
10583
10552
|
OPTIONS
|
|
10584
|
-
--ctx=<base64> Opaque ctx; ossFileMap is extracted from it.
|
|
10585
10553
|
--oss_file_map=... Pre-built OSS URL map (base64 JSON); skips innerapi
|
|
10586
|
-
entirely.
|
|
10554
|
+
entirely. When absent, ossFileMap is fetched from
|
|
10555
|
+
innerapi automatically.
|
|
10587
10556
|
`
|
|
10588
10557
|
},
|
|
10589
10558
|
{
|
|
@@ -10609,8 +10578,7 @@ OPTIONS
|
|
|
10609
10578
|
--home_base=<dir> Override the /home/gem base (tests).
|
|
10610
10579
|
--config_path=<p> Override the openclaw.json path (tests).
|
|
10611
10580
|
--skip-config-update Leave plugins.installs in openclaw.json untouched.
|
|
10612
|
-
--
|
|
10613
|
-
--oss_file_map=... Pre-built OSS URL map (base64 JSON).
|
|
10581
|
+
--oss_file_map=... Pre-built OSS URL map (base64 JSON); skips innerapi.
|
|
10614
10582
|
`
|
|
10615
10583
|
},
|
|
10616
10584
|
{
|
|
@@ -10637,7 +10605,6 @@ OPTIONS
|
|
|
10637
10605
|
--cli=<name> CLI package to install by short name or scoped
|
|
10638
10606
|
packageName (repeatable, at least one required).
|
|
10639
10607
|
--home_base=<dir> Override the /home/gem base (tests).
|
|
10640
|
-
--ctx=<base64> Opaque ctx; ossFileMap is extracted from it.
|
|
10641
10608
|
--oss_file_map=... Pre-built OSS URL map (base64 JSON); skips innerapi.
|
|
10642
10609
|
|
|
10643
10610
|
EXAMPLES
|
|
@@ -10691,46 +10658,6 @@ OPTIONS
|
|
|
10691
10658
|
EXIT CODES
|
|
10692
10659
|
0 Success or skipped (prerequisites not met).
|
|
10693
10660
|
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).
|
|
10734
10661
|
`
|
|
10735
10662
|
},
|
|
10736
10663
|
{
|
|
@@ -10764,41 +10691,6 @@ EXAMPLES
|
|
|
10764
10691
|
${BIN} rules # all rules
|
|
10765
10692
|
${BIN} rules --rule=gateway # single rule
|
|
10766
10693
|
${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.
|
|
10802
10694
|
`
|
|
10803
10695
|
},
|
|
10804
10696
|
{
|
|
@@ -10819,8 +10711,7 @@ OPTIONS
|
|
|
10819
10711
|
--role=<role> Package role (e.g. template, config).
|
|
10820
10712
|
--name=<name> Package name within the role.
|
|
10821
10713
|
--dir=<dir> Target dir (defaults to dirname(pkg.installPath)).
|
|
10822
|
-
--
|
|
10823
|
-
--oss_file_map=... Pre-built OSS URL map (base64 JSON).
|
|
10714
|
+
--oss_file_map=... Pre-built OSS URL map (base64 JSON); skips innerapi.
|
|
10824
10715
|
`
|
|
10825
10716
|
}
|
|
10826
10717
|
];
|
|
@@ -10896,31 +10787,31 @@ function planVarsFields(opts = {}) {
|
|
|
10896
10787
|
*
|
|
10897
10788
|
* Per-command group needs:
|
|
10898
10789
|
*
|
|
10899
|
-
* doctor / check app
|
|
10900
|
-
* repair app + secrets
|
|
10901
|
-
* reset app + secrets + install + reset
|
|
10790
|
+
* doctor / check app + larkApps
|
|
10791
|
+
* repair app + secrets + larkApps
|
|
10792
|
+
* reset app + secrets + install + reset + larkApps
|
|
10902
10793
|
* install-* install only
|
|
10903
10794
|
*
|
|
10904
10795
|
* Empty result (`{}`) means "no group needed" — the CLI can skip the
|
|
10905
10796
|
* `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`.
|
|
10908
10797
|
*/
|
|
10909
10798
|
function planCtxPopulate(opts) {
|
|
10910
10799
|
if (opts.command === "install") return { install: true };
|
|
10911
10800
|
const populate = {};
|
|
10912
|
-
|
|
10801
|
+
if (planVarsFields({
|
|
10913
10802
|
disabled: opts.disabled,
|
|
10914
10803
|
onlyRules: opts.onlyRules,
|
|
10915
10804
|
profile: opts.profile
|
|
10916
|
-
});
|
|
10917
|
-
if (
|
|
10918
|
-
|
|
10919
|
-
|
|
10805
|
+
}).length > 0) populate.app = true;
|
|
10806
|
+
if (opts.command === "repair") {
|
|
10807
|
+
populate.secrets = true;
|
|
10808
|
+
populate.larkApps = true;
|
|
10809
|
+
} else if (opts.command === "reset") {
|
|
10920
10810
|
populate.secrets = true;
|
|
10921
10811
|
populate.install = true;
|
|
10922
10812
|
populate.reset = true;
|
|
10923
|
-
|
|
10813
|
+
populate.larkApps = true;
|
|
10814
|
+
} else if (opts.command === "doctor" || opts.command === "check") populate.larkApps = true;
|
|
10924
10815
|
return populate;
|
|
10925
10816
|
}
|
|
10926
10817
|
//#endregion
|
|
@@ -10974,411 +10865,11 @@ function reportDoctorRunToSlardar(opts) {
|
|
|
10974
10865
|
}
|
|
10975
10866
|
});
|
|
10976
10867
|
}
|
|
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
|
-
}
|
|
11362
10868
|
//#endregion
|
|
11363
10869
|
//#region src/index.ts
|
|
11364
10870
|
const args = node_process.default.argv.slice(2);
|
|
11365
10871
|
const mode = args.find((a) => !a.startsWith("-"));
|
|
11366
10872
|
/**
|
|
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
|
-
/**
|
|
11382
10873
|
* Pull the first non-flag positional after the mode name.
|
|
11383
10874
|
* (The mode itself is args[0] in the filtered set, so we skip index 0.)
|
|
11384
10875
|
*/
|
|
@@ -11406,8 +10897,8 @@ function getMultiFlag(args, name) {
|
|
|
11406
10897
|
* case but is no longer consulted.
|
|
11407
10898
|
*/
|
|
11408
10899
|
async function reportRun(command, rc, _raw, invocation, durationMs, outcome, slardar = {
|
|
11409
|
-
scene,
|
|
11410
|
-
profile,
|
|
10900
|
+
scene: void 0,
|
|
10901
|
+
profile: "standard",
|
|
11411
10902
|
fix: false
|
|
11412
10903
|
}) {
|
|
11413
10904
|
console.error(`${command}: telemetry calling report_cli_run`);
|
|
@@ -11471,7 +10962,7 @@ async function main() {
|
|
|
11471
10962
|
console.error(`${mode}: begin argv=[${args.join(" ")}] version=${getVersion()} traceId=${traceId ?? "-"} caller=${caller ?? "-"} runIdGenerated=${rc.generated}`);
|
|
11472
10963
|
switch (mode) {
|
|
11473
10964
|
case "check": {
|
|
11474
|
-
const raw =
|
|
10965
|
+
const raw = await fetchCtxViaInnerApi({
|
|
11475
10966
|
populate: planCtxPopulate({
|
|
11476
10967
|
command: "check",
|
|
11477
10968
|
profile
|
|
@@ -11496,7 +10987,7 @@ async function main() {
|
|
|
11496
10987
|
break;
|
|
11497
10988
|
}
|
|
11498
10989
|
case "repair": {
|
|
11499
|
-
const raw =
|
|
10990
|
+
const raw = await fetchCtxViaInnerApi({
|
|
11500
10991
|
populate: planCtxPopulate({
|
|
11501
10992
|
command: "repair",
|
|
11502
10993
|
profile
|
|
@@ -11567,27 +11058,15 @@ async function main() {
|
|
|
11567
11058
|
break;
|
|
11568
11059
|
}
|
|
11569
11060
|
case "reset":
|
|
11570
|
-
if (args.includes("--async"))
|
|
11571
|
-
|
|
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")) {
|
|
11061
|
+
if (args.includes("--async")) console.log(JSON.stringify(startAsyncReset()));
|
|
11062
|
+
else if (args.includes("--worker")) {
|
|
11584
11063
|
const taskId = args.find((a) => a.startsWith("--task-id="))?.slice(10);
|
|
11585
11064
|
if (!taskId) {
|
|
11586
11065
|
console.error("Error: --task-id=<id> is required for worker");
|
|
11587
11066
|
node_process.default.exit(1);
|
|
11588
11067
|
}
|
|
11589
11068
|
const resultFile = resetResultFile(taskId);
|
|
11590
|
-
const raw =
|
|
11069
|
+
const raw = await fetchCtxViaInnerApi({
|
|
11591
11070
|
populate: planCtxPopulate({ command: "reset" }),
|
|
11592
11071
|
caller,
|
|
11593
11072
|
traceId
|
|
@@ -11611,7 +11090,7 @@ async function main() {
|
|
|
11611
11090
|
return;
|
|
11612
11091
|
}
|
|
11613
11092
|
} else {
|
|
11614
|
-
console.error("Usage: reset --async
|
|
11093
|
+
console.error("Usage: reset --async | reset --worker --task-id=<id>");
|
|
11615
11094
|
node_process.default.exit(1);
|
|
11616
11095
|
}
|
|
11617
11096
|
break;
|
|
@@ -11627,14 +11106,14 @@ async function main() {
|
|
|
11627
11106
|
case "install-openclaw": {
|
|
11628
11107
|
const tag = getPositionalTag(args, "install-openclaw");
|
|
11629
11108
|
if (!tag) {
|
|
11630
|
-
console.error("Usage: install-openclaw <tag> [--
|
|
11109
|
+
console.error("Usage: install-openclaw <tag> [--oss_file_map=<base64>]");
|
|
11631
11110
|
node_process.default.exit(1);
|
|
11632
11111
|
}
|
|
11633
11112
|
const ossFileMapFlag = getFlag(args, "oss_file_map");
|
|
11634
11113
|
let installOssFileMap;
|
|
11635
11114
|
let rawForTelemetry;
|
|
11636
11115
|
if (!ossFileMapFlag) {
|
|
11637
|
-
rawForTelemetry =
|
|
11116
|
+
rawForTelemetry = await fetchCtxViaInnerApi({
|
|
11638
11117
|
populate: planCtxPopulate({ command: "install" }),
|
|
11639
11118
|
caller,
|
|
11640
11119
|
traceId
|
|
@@ -11669,7 +11148,7 @@ async function main() {
|
|
|
11669
11148
|
case "install-extension": {
|
|
11670
11149
|
const tag = getPositionalTag(args, "install-extension");
|
|
11671
11150
|
if (!tag) {
|
|
11672
|
-
console.error("Usage: install-extension <tag> (--all | --extension=<name>...) [--home_base=<dir>] [--config_path=<path>] [--skip-config-update] [--
|
|
11151
|
+
console.error("Usage: install-extension <tag> (--all | --extension=<name>...) [--home_base=<dir>] [--config_path=<path>] [--skip-config-update] [--oss_file_map=<base64>]");
|
|
11673
11152
|
node_process.default.exit(1);
|
|
11674
11153
|
}
|
|
11675
11154
|
const all = args.includes("--all");
|
|
@@ -11681,7 +11160,7 @@ async function main() {
|
|
|
11681
11160
|
let installOssFileMap;
|
|
11682
11161
|
let rawForTelemetry;
|
|
11683
11162
|
if (!ossFileMapFlag) {
|
|
11684
|
-
rawForTelemetry =
|
|
11163
|
+
rawForTelemetry = await fetchCtxViaInnerApi({
|
|
11685
11164
|
populate: planCtxPopulate({ command: "install" }),
|
|
11686
11165
|
caller,
|
|
11687
11166
|
traceId
|
|
@@ -11727,12 +11206,12 @@ async function main() {
|
|
|
11727
11206
|
case "install-cli": {
|
|
11728
11207
|
const tag = getPositionalTag(args, "install-cli");
|
|
11729
11208
|
if (!tag) {
|
|
11730
|
-
console.error("Usage: install-cli <tag> --cli=<name>... [--home_base=<dir>] [--
|
|
11209
|
+
console.error("Usage: install-cli <tag> --cli=<name>... [--home_base=<dir>] [--oss_file_map=<base64>]");
|
|
11731
11210
|
node_process.default.exit(1);
|
|
11732
11211
|
}
|
|
11733
11212
|
const names = getMultiFlag(args, "cli");
|
|
11734
11213
|
if (names.length === 0) {
|
|
11735
|
-
console.error("Usage: install-cli <tag> --cli=<name>... [--home_base=<dir>] [--
|
|
11214
|
+
console.error("Usage: install-cli <tag> --cli=<name>... [--home_base=<dir>] [--oss_file_map=<base64>]");
|
|
11736
11215
|
node_process.default.exit(1);
|
|
11737
11216
|
}
|
|
11738
11217
|
const homeBase = getFlag(args, "home_base");
|
|
@@ -11740,7 +11219,7 @@ async function main() {
|
|
|
11740
11219
|
let installOssFileMap;
|
|
11741
11220
|
let rawForTelemetry;
|
|
11742
11221
|
if (!ossFileMapFlag) {
|
|
11743
|
-
rawForTelemetry =
|
|
11222
|
+
rawForTelemetry = await fetchCtxViaInnerApi({
|
|
11744
11223
|
populate: planCtxPopulate({ command: "install" }),
|
|
11745
11224
|
caller,
|
|
11746
11225
|
traceId
|
|
@@ -11788,7 +11267,7 @@ async function main() {
|
|
|
11788
11267
|
case "download-resource": {
|
|
11789
11268
|
const tag = getPositionalTag(args, "download-resource");
|
|
11790
11269
|
if (!tag) {
|
|
11791
|
-
console.error("Usage: download-resource <tag> --role=<role> --name=<name> [--dir=<dir>] [--
|
|
11270
|
+
console.error("Usage: download-resource <tag> --role=<role> --name=<name> [--dir=<dir>] [--oss_file_map=<base64>]");
|
|
11792
11271
|
node_process.default.exit(1);
|
|
11793
11272
|
}
|
|
11794
11273
|
const role = getFlag(args, "role");
|
|
@@ -11802,7 +11281,7 @@ async function main() {
|
|
|
11802
11281
|
let installOssFileMap;
|
|
11803
11282
|
let rawForTelemetry;
|
|
11804
11283
|
if (!ossFileMapFlag) {
|
|
11805
|
-
rawForTelemetry =
|
|
11284
|
+
rawForTelemetry = await fetchCtxViaInnerApi({
|
|
11806
11285
|
populate: planCtxPopulate({ command: "install" }),
|
|
11807
11286
|
caller,
|
|
11808
11287
|
traceId
|
|
@@ -11876,50 +11355,6 @@ async function main() {
|
|
|
11876
11355
|
if (!result.ok) node_process.default.exit(1);
|
|
11877
11356
|
break;
|
|
11878
11357
|
}
|
|
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
|
-
}
|
|
11923
11358
|
default:
|
|
11924
11359
|
node_process.default.stderr.write(`Unknown command: ${mode}\n\n`);
|
|
11925
11360
|
node_process.default.stderr.write(formatTopLevelHelp(helpFlags.expert));
|
package/package.json
CHANGED