@lark-apaas/openclaw-scripts-diagnose-cli 0.1.14-alpha.11 → 0.1.14-alpha.12
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 +774 -228
- 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.12";
|
|
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);
|
|
@@ -3370,6 +3369,7 @@ let FeishuPluginOpenclawUpgradeRule = class FeishuPluginOpenclawUpgradeRule exte
|
|
|
3370
3369
|
if (!cc) return { pass: true };
|
|
3371
3370
|
const { ocCur, recommendedOc, installed, isLegacy } = cc;
|
|
3372
3371
|
if (isForkPlugin(installed)) return validateForkPlugin(installed, ocCur, recommendedOc);
|
|
3372
|
+
if (!recommendedOc) return { pass: true };
|
|
3373
3373
|
if (resolveUpgradeDirection(installed, ocCur, recommendedOc, isLegacy) !== "openclaw") return { pass: true };
|
|
3374
3374
|
return {
|
|
3375
3375
|
pass: false,
|
|
@@ -3397,11 +3397,17 @@ 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 (
|
|
3400
|
+
if (!isLarkUpgradeNeededFromCC(cc)) return { pass: true };
|
|
3401
|
+
const prefix = buildCompatPrefix(installed, ocCur, isLegacy);
|
|
3402
|
+
if (!recommendedOc) return {
|
|
3403
|
+
pass: false,
|
|
3404
|
+
action: "upgrade_lark",
|
|
3405
|
+
message: `${prefix};建议升级飞书插件至兼容版本`
|
|
3406
|
+
};
|
|
3401
3407
|
return {
|
|
3402
3408
|
pass: false,
|
|
3403
3409
|
action: "upgrade_lark",
|
|
3404
|
-
message: `${
|
|
3410
|
+
message: `${prefix};当前 openclaw@${ocCur} 已达推荐版本,可直接升级飞书插件`
|
|
3405
3411
|
};
|
|
3406
3412
|
}
|
|
3407
3413
|
};
|
|
@@ -3413,6 +3419,18 @@ FeishuPluginLarkUpgradeRule = __decorate([Rule({
|
|
|
3413
3419
|
level: "critical",
|
|
3414
3420
|
usesVars: ["recommendedOpenclawTag"]
|
|
3415
3421
|
})], FeishuPluginLarkUpgradeRule);
|
|
3422
|
+
/**
|
|
3423
|
+
* Core predicate: returns true when the lark plugin needs upgrading for a
|
|
3424
|
+
* non-fork plugin, based on version compatibility with the current openclaw.
|
|
3425
|
+
*
|
|
3426
|
+
* Shared by FeishuPluginLarkUpgradeRule.validate and needsLarkUpgrade.
|
|
3427
|
+
* Callers must handle fork plugin cases before invoking this function.
|
|
3428
|
+
*/
|
|
3429
|
+
function isLarkUpgradeNeededFromCC(cc) {
|
|
3430
|
+
const { ocCur, recommendedOc, installed, isLegacy } = cc;
|
|
3431
|
+
if (!recommendedOc) return isLegacy || !isVersionCompatible(installed, ocCur);
|
|
3432
|
+
return resolveUpgradeDirection(installed, ocCur, recommendedOc, isLegacy) === "lark";
|
|
3433
|
+
}
|
|
3416
3434
|
function isForkPlugin(p) {
|
|
3417
3435
|
return p.scope != null && FORK_SCOPES.includes(p.scope);
|
|
3418
3436
|
}
|
|
@@ -3451,14 +3469,16 @@ function describeCompatConstraint(entry, pluginVersion) {
|
|
|
3451
3469
|
/**
|
|
3452
3470
|
* @lark-apaas/openclaw-lark 豁免 VERSION_COMPAT_MAP,但仍要求 openclaw ≥ FORK_LARK_PLUGIN_MIN_OC_VERSION。
|
|
3453
3471
|
* 其他 @lark-apaas scope 的 fork 插件继续无条件 pass。
|
|
3472
|
+
* recommendedOc 可为 undefined(doctor 模式),此时只检测最低版本要求,不指定目标升级版本。
|
|
3454
3473
|
*/
|
|
3455
3474
|
function validateForkPlugin(installed, ocCur, recommendedOc) {
|
|
3456
3475
|
if (installed.fullName !== FORK_LARK_PLUGIN_FULL_NAME) return { pass: true };
|
|
3457
3476
|
if (compareCalVer(ocCur, FORK_LARK_PLUGIN_MIN_OC_VERSION) >= 0) return { pass: true };
|
|
3477
|
+
const recommendation = recommendedOc ? `;将 openclaw 升级到 ${recommendedOc} 即可满足` : `;请升级 openclaw 至 ${FORK_LARK_PLUGIN_MIN_OC_VERSION} 或更高版本`;
|
|
3458
3478
|
return {
|
|
3459
3479
|
pass: false,
|
|
3460
3480
|
action: "upgrade_openclaw",
|
|
3461
|
-
message: `飞书插件 ${describePlugin(installed)}(fork 版)要求 openclaw ≥ ${FORK_LARK_PLUGIN_MIN_OC_VERSION},当前 openclaw@${ocCur}
|
|
3481
|
+
message: `飞书插件 ${describePlugin(installed)}(fork 版)要求 openclaw ≥ ${FORK_LARK_PLUGIN_MIN_OC_VERSION},当前 openclaw@${ocCur} 低于此要求${recommendation}`
|
|
3462
3482
|
};
|
|
3463
3483
|
}
|
|
3464
3484
|
function describePlugin(p) {
|
|
@@ -3505,6 +3525,196 @@ function extractScopedNameFromSpec$1(spec) {
|
|
|
3505
3525
|
const at = spec.indexOf("@", 1);
|
|
3506
3526
|
return at === -1 ? spec : spec.slice(0, at);
|
|
3507
3527
|
}
|
|
3528
|
+
/**
|
|
3529
|
+
* Returns true if the installed feishu plugin is version-incompatible with
|
|
3530
|
+
* the current openclaw (or is a legacy plugin that must be replaced).
|
|
3531
|
+
* Used by the upgrade_lark_needed rule and the upgrade-lark pre-check gate.
|
|
3532
|
+
*/
|
|
3533
|
+
function needsLarkUpgrade(ctx) {
|
|
3534
|
+
const cc = resolveCompatContext(ctx);
|
|
3535
|
+
if (!cc) return false;
|
|
3536
|
+
const { ocCur, recommendedOc, installed } = cc;
|
|
3537
|
+
if (isForkPlugin(installed)) {
|
|
3538
|
+
if (recommendedOc) return false;
|
|
3539
|
+
if (installed.fullName === FORK_LARK_PLUGIN_FULL_NAME) return compareCalVer(ocCur, FORK_LARK_PLUGIN_MIN_OC_VERSION) < 0;
|
|
3540
|
+
return false;
|
|
3541
|
+
}
|
|
3542
|
+
return isLarkUpgradeNeededFromCC(cc);
|
|
3543
|
+
}
|
|
3544
|
+
//#endregion
|
|
3545
|
+
//#region src/channels-probe.ts
|
|
3546
|
+
const FEISHU_INVALID_CONFIG_MSG = "channels.feishu: invalid config: must NOT have additional properties";
|
|
3547
|
+
const CHANNEL_LINE_RE = /^-\s+Feishu\s+([^:]+):\s+(.+)$/;
|
|
3548
|
+
/**
|
|
3549
|
+
* Port of Python `_account_is_working` from the feishu-channel-success-rate skill.
|
|
3550
|
+
*
|
|
3551
|
+
* Strips colon-prefixed key:value bits (dm:, bot:, in:, out:, token:, allow:,
|
|
3552
|
+
* intents:, groups:, health:) and evaluates the canonical health formula.
|
|
3553
|
+
*
|
|
3554
|
+
* @param ignoreProbeFailed When true, "probe failed" is not treated as a
|
|
3555
|
+
* failure condition. Use this when checking whether channels are structurally
|
|
3556
|
+
* working for upgrade-gate purposes (probe failures indicate network issues,
|
|
3557
|
+
* not plugin misconfiguration).
|
|
3558
|
+
*/
|
|
3559
|
+
function accountIsWorking(bits, ignoreProbeFailed = true) {
|
|
3560
|
+
const bitTokens = /* @__PURE__ */ new Set();
|
|
3561
|
+
let hasError = false;
|
|
3562
|
+
let hasProbeFailed = false;
|
|
3563
|
+
for (const raw of bits) {
|
|
3564
|
+
const b = raw.trim();
|
|
3565
|
+
if (!b) continue;
|
|
3566
|
+
if (b.startsWith("error:")) {
|
|
3567
|
+
hasError = true;
|
|
3568
|
+
continue;
|
|
3569
|
+
}
|
|
3570
|
+
if (b === "probe failed") {
|
|
3571
|
+
hasProbeFailed = true;
|
|
3572
|
+
continue;
|
|
3573
|
+
}
|
|
3574
|
+
bitTokens.add(b.split(":")[0]);
|
|
3575
|
+
}
|
|
3576
|
+
if (!bitTokens.has("enabled") || !bitTokens.has("configured")) return false;
|
|
3577
|
+
if (bitTokens.has("works")) return true;
|
|
3578
|
+
if (bitTokens.has("running") && !hasError && (ignoreProbeFailed || !hasProbeFailed)) return true;
|
|
3579
|
+
return false;
|
|
3580
|
+
}
|
|
3581
|
+
/**
|
|
3582
|
+
* Parse the raw stdout of `openclaw channels status --probe`.
|
|
3583
|
+
* Port of Python `extract_channels_probe` from the feishu-channel-success-rate skill.
|
|
3584
|
+
*/
|
|
3585
|
+
function parseChannelsProbeOutput(text, { ignoreProbeFailed = true } = {}) {
|
|
3586
|
+
const gatewayReachable = text.includes("Gateway reachable");
|
|
3587
|
+
const feishuConfigInvalid = text.includes(FEISHU_INVALID_CONFIG_MSG);
|
|
3588
|
+
const accounts = [];
|
|
3589
|
+
let anyAccountWorking = false;
|
|
3590
|
+
for (const line of text.split("\n")) {
|
|
3591
|
+
const m = CHANNEL_LINE_RE.exec(line.trim());
|
|
3592
|
+
if (!m) continue;
|
|
3593
|
+
const [, acct, rest] = m;
|
|
3594
|
+
const bits = rest.split(",").map((b) => b.trim());
|
|
3595
|
+
const isWorking = accountIsWorking(bits, ignoreProbeFailed);
|
|
3596
|
+
if (isWorking) anyAccountWorking = true;
|
|
3597
|
+
accounts.push({
|
|
3598
|
+
id: acct.trim(),
|
|
3599
|
+
bits,
|
|
3600
|
+
isWorking,
|
|
3601
|
+
raw: line.trim()
|
|
3602
|
+
});
|
|
3603
|
+
}
|
|
3604
|
+
return {
|
|
3605
|
+
gatewayReachable,
|
|
3606
|
+
feishuConfigInvalid,
|
|
3607
|
+
accounts,
|
|
3608
|
+
anyAccountWorking
|
|
3609
|
+
};
|
|
3610
|
+
}
|
|
3611
|
+
/**
|
|
3612
|
+
* Run `openclaw channels status --probe` and return a structured result.
|
|
3613
|
+
*
|
|
3614
|
+
* The command may exit non-zero when some bot accounts fail their probe — that
|
|
3615
|
+
* is still useful output. We therefore try to parse stdout even when the
|
|
3616
|
+
* process exits with a non-zero code, falling back to an unavailable result
|
|
3617
|
+
* only when there is genuinely no output to parse.
|
|
3618
|
+
*
|
|
3619
|
+
* @param timeoutMs Maximum wait time. Default is 60 s because v2026.4.x
|
|
3620
|
+
* lacks a per-request HTTP timeout and can block indefinitely.
|
|
3621
|
+
* @param ignoreProbeFailed When true, accounts with "probe failed" are still
|
|
3622
|
+
* counted as working. Pass true for upgrade-gate checks where probe failures
|
|
3623
|
+
* reflect network conditions rather than plugin misconfiguration.
|
|
3624
|
+
*/
|
|
3625
|
+
function runChannelsProbe(timeoutMs = 6e4, { ignoreProbeFailed = true } = {}) {
|
|
3626
|
+
let stdout = "";
|
|
3627
|
+
let stderrText = "";
|
|
3628
|
+
let execError;
|
|
3629
|
+
try {
|
|
3630
|
+
stdout = (0, node_child_process.execSync)("openclaw channels status --probe", {
|
|
3631
|
+
encoding: "utf-8",
|
|
3632
|
+
timeout: timeoutMs,
|
|
3633
|
+
stdio: [
|
|
3634
|
+
"ignore",
|
|
3635
|
+
"pipe",
|
|
3636
|
+
"pipe"
|
|
3637
|
+
]
|
|
3638
|
+
});
|
|
3639
|
+
} catch (e) {
|
|
3640
|
+
const err = e;
|
|
3641
|
+
const stdoutRaw = err.stdout;
|
|
3642
|
+
stdout = typeof stdoutRaw === "string" ? stdoutRaw : stdoutRaw?.toString("utf-8") ?? "";
|
|
3643
|
+
execError = err.message;
|
|
3644
|
+
const stderrRaw = err.stderr;
|
|
3645
|
+
stderrText = (typeof stderrRaw === "string" ? stderrRaw : stderrRaw?.toString("utf-8") ?? "").trim();
|
|
3646
|
+
if (stderrText) console.error(`channels-probe: stderr from CLI: ${stderrText}`);
|
|
3647
|
+
}
|
|
3648
|
+
if (stdout.trim()) return {
|
|
3649
|
+
available: true,
|
|
3650
|
+
...parseChannelsProbeOutput(stdout, { ignoreProbeFailed })
|
|
3651
|
+
};
|
|
3652
|
+
return {
|
|
3653
|
+
available: false,
|
|
3654
|
+
gatewayReachable: false,
|
|
3655
|
+
feishuConfigInvalid: stderrText.includes(FEISHU_INVALID_CONFIG_MSG),
|
|
3656
|
+
accounts: [],
|
|
3657
|
+
anyAccountWorking: false,
|
|
3658
|
+
error: execError ?? "no output from openclaw channels status --probe"
|
|
3659
|
+
};
|
|
3660
|
+
}
|
|
3661
|
+
//#endregion
|
|
3662
|
+
//#region src/rules/upgrade-lark-needed.ts
|
|
3663
|
+
/**
|
|
3664
|
+
* Detects the condition that warrants running `upgrade-lark`:
|
|
3665
|
+
* - feishu plugin version incompatible with current openclaw, OR
|
|
3666
|
+
* - openclaw channels status --probe reports feishu channel config invalid; AND
|
|
3667
|
+
* - channels are not working.
|
|
3668
|
+
*
|
|
3669
|
+
* Both conditions must be true simultaneously. If version is compatible and
|
|
3670
|
+
* feishu config is valid, or channels are working, the rule passes (no action needed).
|
|
3671
|
+
*
|
|
3672
|
+
* feishuConfigInvalid is read from the channels probe output rather than running a
|
|
3673
|
+
* separate `openclaw status` call, since only `openclaw channels status --probe`
|
|
3674
|
+
* reliably surfaces the schema validation error.
|
|
3675
|
+
*
|
|
3676
|
+
* profile: experimental — runs only in full sweep mode, not in standard doctor.
|
|
3677
|
+
* level: silent — telemetry/sweep-only, does not trigger page-level repair UI.
|
|
3678
|
+
*/
|
|
3679
|
+
let UpgradeLarkNeededRule = class UpgradeLarkNeededRule extends DiagnoseRule {
|
|
3680
|
+
validate(ctx) {
|
|
3681
|
+
let versionIncompatible = false;
|
|
3682
|
+
try {
|
|
3683
|
+
versionIncompatible = needsLarkUpgrade(ctx);
|
|
3684
|
+
} catch {
|
|
3685
|
+
versionIncompatible = true;
|
|
3686
|
+
}
|
|
3687
|
+
let probeResult;
|
|
3688
|
+
try {
|
|
3689
|
+
probeResult = runChannelsProbe(6e4);
|
|
3690
|
+
} catch {
|
|
3691
|
+
probeResult = {
|
|
3692
|
+
available: false,
|
|
3693
|
+
gatewayReachable: false,
|
|
3694
|
+
feishuConfigInvalid: false,
|
|
3695
|
+
accounts: [],
|
|
3696
|
+
anyAccountWorking: false
|
|
3697
|
+
};
|
|
3698
|
+
}
|
|
3699
|
+
const feishuConfigInvalid = probeResult.feishuConfigInvalid;
|
|
3700
|
+
if (!(versionIncompatible || feishuConfigInvalid)) return { pass: true };
|
|
3701
|
+
if (probeResult.anyAccountWorking) return { pass: true };
|
|
3702
|
+
return {
|
|
3703
|
+
pass: false,
|
|
3704
|
+
action: "upgrade_lark",
|
|
3705
|
+
message: `飞书插件需要升级且 channels 不可用(版本不兼容=${versionIncompatible}, feishu配置无效=${feishuConfigInvalid}),建议执行 upgrade-lark 命令升级飞书插件`
|
|
3706
|
+
};
|
|
3707
|
+
}
|
|
3708
|
+
};
|
|
3709
|
+
UpgradeLarkNeededRule = __decorate([Rule({
|
|
3710
|
+
key: "upgrade_lark_needed",
|
|
3711
|
+
description: "检测飞书插件版本不兼容且 channels 不可用,判断是否需要执行 upgrade-lark 升级",
|
|
3712
|
+
dependsOn: ["config_syntax_check"],
|
|
3713
|
+
repairMode: "check-only",
|
|
3714
|
+
level: "silent",
|
|
3715
|
+
profile: "experimental",
|
|
3716
|
+
usesVars: ["recommendedOpenclawTag"]
|
|
3717
|
+
})], UpgradeLarkNeededRule);
|
|
3508
3718
|
//#endregion
|
|
3509
3719
|
//#region src/rules/cleanup-install-backup-dirs.ts
|
|
3510
3720
|
const DIR_PREFIX = ".openclaw-install-";
|
|
@@ -3650,117 +3860,6 @@ LarkCliMissingForInstalledLarkPluginRule = __decorate([Rule({
|
|
|
3650
3860
|
usesVars: ["recommendedOpenclawTag"]
|
|
3651
3861
|
})], LarkCliMissingForInstalledLarkPluginRule);
|
|
3652
3862
|
//#endregion
|
|
3653
|
-
//#region src/rules/feishu-bot-channel-config.ts
|
|
3654
|
-
/**
|
|
3655
|
-
* Ensures each bot account's channel config is correct:
|
|
3656
|
-
* 1. `allowFrom` contains its own `creatorOpenID` from larkApps
|
|
3657
|
-
* 2. `appSecret` is either the canonical provider-ref or matches larkApps plaintext
|
|
3658
|
-
*
|
|
3659
|
-
* Covers both multi-account (channels.feishu.accounts) and single-account
|
|
3660
|
-
* (channels.feishu.appId + allowFrom at top level) layouts.
|
|
3661
|
-
*/
|
|
3662
|
-
let FeishuBotChannelConfigRule = class FeishuBotChannelConfigRule extends DiagnoseRule {
|
|
3663
|
-
validate(ctx) {
|
|
3664
|
-
const larkApps = ctx.vars.larkApps;
|
|
3665
|
-
if (!larkApps || larkApps.length === 0) return { pass: true };
|
|
3666
|
-
const feishu = asRecord(getNestedMap(ctx.config, "channels", "feishu"));
|
|
3667
|
-
if (!feishu) return { pass: true };
|
|
3668
|
-
const issues = [];
|
|
3669
|
-
const accounts = asRecord(feishu.accounts);
|
|
3670
|
-
if (accounts) for (const [accountId, account] of Object.entries(accounts)) {
|
|
3671
|
-
const bot = asRecord(account);
|
|
3672
|
-
if (!bot) continue;
|
|
3673
|
-
const appId = bot.appId;
|
|
3674
|
-
if (typeof appId !== "string" || !appId.startsWith("cli_")) continue;
|
|
3675
|
-
const larkApp = larkApps.find((e) => e.larkAppID === appId);
|
|
3676
|
-
if (!larkApp) continue;
|
|
3677
|
-
this.checkBot(accountId, bot, larkApp, issues);
|
|
3678
|
-
}
|
|
3679
|
-
const singleAppId = feishu.appId;
|
|
3680
|
-
if (typeof singleAppId === "string" && singleAppId.startsWith("cli_") && !accounts) {
|
|
3681
|
-
const larkApp = larkApps.find((e) => e.larkAppID === singleAppId);
|
|
3682
|
-
if (larkApp) this.checkBot("feishu", feishu, larkApp, issues);
|
|
3683
|
-
}
|
|
3684
|
-
if (issues.length === 0) return { pass: true };
|
|
3685
|
-
return {
|
|
3686
|
-
pass: false,
|
|
3687
|
-
message: issues.join("; ")
|
|
3688
|
-
};
|
|
3689
|
-
}
|
|
3690
|
-
/** Check a single bot entry (either an account object or the feishu channel itself).
|
|
3691
|
-
* appSecret is validated based on its current type:
|
|
3692
|
-
* - object → must match canonical provider-ref
|
|
3693
|
-
* - string → must match larkApps plaintext
|
|
3694
|
-
*/
|
|
3695
|
-
checkBot(label, bot, larkApp, issues) {
|
|
3696
|
-
const creatorOpenID = larkApp.creatorOpenID;
|
|
3697
|
-
const allowFrom = Array.isArray(bot.allowFrom) ? bot.allowFrom : [];
|
|
3698
|
-
if (typeof creatorOpenID === "string" && creatorOpenID !== "") {
|
|
3699
|
-
if (!allowFrom.includes(creatorOpenID)) issues.push(`${label} allowFrom missing creatorOpenID ${creatorOpenID.length > 8 ? creatorOpenID.slice(0, 4) + "***" + creatorOpenID.slice(-4) : "***"}`);
|
|
3700
|
-
} else if (allowFrom.length === 0) issues.push(`${label} allowFrom is empty (creatorOpenID unavailable, cannot auto-fix)`);
|
|
3701
|
-
const secret = bot.appSecret;
|
|
3702
|
-
if (typeof secret === "object" && secret !== null && !Array.isArray(secret)) {
|
|
3703
|
-
if (!matchMap(secret, DEFAULT_FEISHU_APP_SECRET)) issues.push(`${label} appSecret is a provider-ref but not the canonical one`);
|
|
3704
|
-
} else if (typeof secret === "string") {
|
|
3705
|
-
if (secret !== larkApp.appSecret) issues.push(`${label} appSecret plaintext mismatch`);
|
|
3706
|
-
} else issues.push(`${label} appSecret has unexpected type ${typeof secret}`);
|
|
3707
|
-
}
|
|
3708
|
-
repair(ctx) {
|
|
3709
|
-
const larkApps = ctx.vars.larkApps;
|
|
3710
|
-
if (!larkApps || larkApps.length === 0) return;
|
|
3711
|
-
const feishu = asRecord(getNestedMap(ctx.config, "channels", "feishu"));
|
|
3712
|
-
if (!feishu) return;
|
|
3713
|
-
const accounts = asRecord(feishu.accounts);
|
|
3714
|
-
if (accounts) for (const [, account] of Object.entries(accounts)) {
|
|
3715
|
-
const bot = asRecord(account);
|
|
3716
|
-
if (!bot) continue;
|
|
3717
|
-
const appId = bot.appId;
|
|
3718
|
-
if (typeof appId !== "string" || !appId.startsWith("cli_")) continue;
|
|
3719
|
-
const larkApp = larkApps.find((e) => e.larkAppID === appId);
|
|
3720
|
-
if (!larkApp) continue;
|
|
3721
|
-
this.fixBot(bot, larkApp);
|
|
3722
|
-
}
|
|
3723
|
-
const singleAppId = feishu.appId;
|
|
3724
|
-
if (typeof singleAppId === "string" && singleAppId.startsWith("cli_") && !accounts) {
|
|
3725
|
-
const larkApp = larkApps.find((e) => e.larkAppID === singleAppId);
|
|
3726
|
-
if (larkApp) this.fixBot(feishu, larkApp);
|
|
3727
|
-
}
|
|
3728
|
-
}
|
|
3729
|
-
/** Fix a single bot entry in-place.
|
|
3730
|
-
* appSecret is repaired based on its current type:
|
|
3731
|
-
* - object → fix to canonical provider-ref
|
|
3732
|
-
* - string → fix to larkApps plaintext
|
|
3733
|
-
*/
|
|
3734
|
-
fixBot(bot, larkApp) {
|
|
3735
|
-
const creatorOpenID = larkApp.creatorOpenID;
|
|
3736
|
-
if (typeof creatorOpenID === "string" && creatorOpenID !== "") {
|
|
3737
|
-
const allowFrom = Array.isArray(bot.allowFrom) ? [...bot.allowFrom] : [];
|
|
3738
|
-
if (!allowFrom.includes(creatorOpenID)) {
|
|
3739
|
-
allowFrom.push(creatorOpenID);
|
|
3740
|
-
bot.allowFrom = allowFrom;
|
|
3741
|
-
}
|
|
3742
|
-
}
|
|
3743
|
-
const secret = bot.appSecret;
|
|
3744
|
-
if (typeof secret === "object" && secret !== null && !Array.isArray(secret)) {
|
|
3745
|
-
if (!matchMap(secret, DEFAULT_FEISHU_APP_SECRET)) bot.appSecret = { ...DEFAULT_FEISHU_APP_SECRET };
|
|
3746
|
-
} else if (typeof secret === "string") {
|
|
3747
|
-
if (secret !== larkApp.appSecret) bot.appSecret = larkApp.appSecret;
|
|
3748
|
-
}
|
|
3749
|
-
}
|
|
3750
|
-
};
|
|
3751
|
-
FeishuBotChannelConfigRule = __decorate([Rule({
|
|
3752
|
-
key: "feishu_bot_channel_config",
|
|
3753
|
-
description: "确保飞书配置中 bot 账号的 allowFrom 包含其创建者 openID 且 appSecret 值正确",
|
|
3754
|
-
dependsOn: [
|
|
3755
|
-
"config_syntax_check",
|
|
3756
|
-
"feishu_default_account",
|
|
3757
|
-
"feishu_bot_id"
|
|
3758
|
-
],
|
|
3759
|
-
repairMode: "standard",
|
|
3760
|
-
usesVars: ["larkApps"],
|
|
3761
|
-
level: "critical"
|
|
3762
|
-
})], FeishuBotChannelConfigRule);
|
|
3763
|
-
//#endregion
|
|
3764
3863
|
//#region src/check.ts
|
|
3765
3864
|
/** Telemetry-aware entry: returns both the legacy CheckResult (for stdout)
|
|
3766
3865
|
* AND a DoctorReport-shape payload (for `openclaw.report_cli_run`). The
|
|
@@ -4224,6 +4323,9 @@ const PROVIDER_FILE_PATH = "/home/gem/workspace/.force/openclaw/miaoda-provider-
|
|
|
4224
4323
|
const SECRETS_FILE_PATH = "/home/gem/workspace/.force/openclaw/miaoda-openclaw-secrets.json";
|
|
4225
4324
|
/** Absolute path to the openclaw config JSON. */
|
|
4226
4325
|
const CONFIG_PATH = `${WORKSPACE_DIR}/openclaw.json`;
|
|
4326
|
+
function upgradeLarkLogFile(runId) {
|
|
4327
|
+
return `${DIAGNOSE_DIR}/upgrade-lark-${(/* @__PURE__ */ new Date()).toISOString().slice(0, 19).replace(/:/g, "-")}-${runId.slice(0, 8)}.log`;
|
|
4328
|
+
}
|
|
4227
4329
|
//#endregion
|
|
4228
4330
|
//#region src/run-log.ts
|
|
4229
4331
|
let currentRunContext;
|
|
@@ -4360,10 +4462,9 @@ function makeLogger(logFile) {
|
|
|
4360
4462
|
/**
|
|
4361
4463
|
* Start an async reset task: spawn a detached child process and return the taskId.
|
|
4362
4464
|
*
|
|
4363
|
-
* The child process runs: node cli.js reset --worker --task-id=xxx
|
|
4364
|
-
* The worker fetches ctx from innerApi itself — no --ctx passthrough.
|
|
4465
|
+
* The child process runs: node cli.js reset --worker --task-id=xxx --ctx=base64
|
|
4365
4466
|
*/
|
|
4366
|
-
function startAsyncReset() {
|
|
4467
|
+
function startAsyncReset(ctxBase64) {
|
|
4367
4468
|
const taskId = (0, node_crypto.randomUUID)();
|
|
4368
4469
|
const resultFile = resetResultFile(taskId);
|
|
4369
4470
|
const log = makeLogger(resetLogFile(taskId));
|
|
@@ -4387,7 +4488,8 @@ function startAsyncReset() {
|
|
|
4387
4488
|
process.argv[1],
|
|
4388
4489
|
"reset",
|
|
4389
4490
|
"--worker",
|
|
4390
|
-
`--task-id=${taskId}
|
|
4491
|
+
`--task-id=${taskId}`,
|
|
4492
|
+
`--ctx=${ctxBase64}`
|
|
4391
4493
|
], {
|
|
4392
4494
|
detached: true,
|
|
4393
4495
|
stdio: "ignore",
|
|
@@ -6901,60 +7003,6 @@ function mergeCoreBackupAndOrigins(configPath, vars, resetData, log) {
|
|
|
6901
7003
|
log(`allowedOrigins: added ${added.length} (${JSON.stringify(added)}), total now ${mergedOrigins.length}`);
|
|
6902
7004
|
}
|
|
6903
7005
|
/**
|
|
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
|
-
/**
|
|
6958
7006
|
* Step 7: Verify startup scripts landed in configDir/scripts/.
|
|
6959
7007
|
*
|
|
6960
7008
|
* Scripts are extracted directly to configDir/scripts/ during stageTemplate —
|
|
@@ -7099,7 +7147,6 @@ async function runReset(input, taskId, resultFile) {
|
|
|
7099
7147
|
await step5InstallOpenclaw(openclawTag, ossFileMap, log);
|
|
7100
7148
|
step(6);
|
|
7101
7149
|
mergeCoreBackupAndOrigins(configPath, vars, resetData, log);
|
|
7102
|
-
fixBotChannelConfig(configPath, vars.larkApps, log);
|
|
7103
7150
|
step(7);
|
|
7104
7151
|
verifyStartupScripts(configDir, log);
|
|
7105
7152
|
step(8);
|
|
@@ -7898,8 +7945,7 @@ function normalizeCtx(raw) {
|
|
|
7898
7945
|
reset: {
|
|
7899
7946
|
templateVars: r.reset.templateVars ?? {},
|
|
7900
7947
|
coreBackup: r.reset.coreBackup
|
|
7901
|
-
}
|
|
7902
|
-
larkApps: Array.isArray(r.larkApps) ? r.larkApps : []
|
|
7948
|
+
}
|
|
7903
7949
|
};
|
|
7904
7950
|
}
|
|
7905
7951
|
const vars = r.vars ?? {};
|
|
@@ -7924,8 +7970,7 @@ function normalizeCtx(raw) {
|
|
|
7924
7970
|
reset: {
|
|
7925
7971
|
templateVars: resetData.templateVars ?? {},
|
|
7926
7972
|
coreBackup: resetData.coreBackup
|
|
7927
|
-
}
|
|
7928
|
-
larkApps: Array.isArray(r.larkApps) ? r.larkApps : []
|
|
7973
|
+
}
|
|
7929
7974
|
};
|
|
7930
7975
|
}
|
|
7931
7976
|
function fillApp(src) {
|
|
@@ -7990,8 +8035,7 @@ function buildCheckInput(raw, configPathOverride) {
|
|
|
7990
8035
|
providerFilePath: PROVIDER_FILE_PATH,
|
|
7991
8036
|
secretsFilePath: SECRETS_FILE_PATH,
|
|
7992
8037
|
templateVars: ctx.app.templateVars,
|
|
7993
|
-
recommendedOpenclawTag: ctx.app.recommendedOpenclawTag
|
|
7994
|
-
larkApps: ctx.larkApps
|
|
8038
|
+
recommendedOpenclawTag: ctx.app.recommendedOpenclawTag
|
|
7995
8039
|
},
|
|
7996
8040
|
templateVars: ctx.app.templateVars
|
|
7997
8041
|
};
|
|
@@ -8023,8 +8067,7 @@ function buildRepairInput(raw, configPathOverride) {
|
|
|
8023
8067
|
providerFilePath: PROVIDER_FILE_PATH,
|
|
8024
8068
|
secretsFilePath: SECRETS_FILE_PATH,
|
|
8025
8069
|
templateVars: ctx.app.templateVars,
|
|
8026
|
-
recommendedOpenclawTag: ctx.app.recommendedOpenclawTag
|
|
8027
|
-
larkApps: ctx.larkApps
|
|
8070
|
+
recommendedOpenclawTag: ctx.app.recommendedOpenclawTag
|
|
8028
8071
|
},
|
|
8029
8072
|
repairData: {
|
|
8030
8073
|
secretsContent: ctx.secrets.secretsContent,
|
|
@@ -8060,8 +8103,7 @@ function buildResetInput(raw, configPathOverride) {
|
|
|
8060
8103
|
providerFilePath: PROVIDER_FILE_PATH,
|
|
8061
8104
|
secretsFilePath: SECRETS_FILE_PATH,
|
|
8062
8105
|
templateVars: ctx.app.templateVars,
|
|
8063
|
-
recommendedOpenclawTag: ctx.app.recommendedOpenclawTag
|
|
8064
|
-
larkApps: ctx.larkApps
|
|
8106
|
+
recommendedOpenclawTag: ctx.app.recommendedOpenclawTag
|
|
8065
8107
|
},
|
|
8066
8108
|
resetData: {
|
|
8067
8109
|
templateVars: ctx.reset.templateVars,
|
|
@@ -10371,7 +10413,7 @@ async function reportCliRun(opts) {
|
|
|
10371
10413
|
//#region src/help.ts
|
|
10372
10414
|
const BIN = "mclaw-diagnose";
|
|
10373
10415
|
function versionBanner() {
|
|
10374
|
-
return `v0.1.14-alpha.
|
|
10416
|
+
return `v0.1.14-alpha.12`;
|
|
10375
10417
|
}
|
|
10376
10418
|
const COMMANDS = [
|
|
10377
10419
|
{
|
|
@@ -10475,12 +10517,16 @@ EXIT CODES
|
|
|
10475
10517
|
hidden: true,
|
|
10476
10518
|
summary: "Run rule-engine check only",
|
|
10477
10519
|
help: `USAGE
|
|
10478
|
-
${BIN} check
|
|
10520
|
+
${BIN} check [--ctx=<base64>]
|
|
10479
10521
|
|
|
10480
10522
|
DESCRIPTION
|
|
10481
10523
|
Runs the rule engine against the sandbox's current openclaw config and
|
|
10482
|
-
returns { failedRules }.
|
|
10483
|
-
|
|
10524
|
+
returns { failedRules }. Used by sandbox_console's push-style callers
|
|
10525
|
+
that already own the ctx — end-users should prefer \`doctor\`.
|
|
10526
|
+
|
|
10527
|
+
OPTIONS
|
|
10528
|
+
--ctx=<base64> Opaque ctx JSON (base64). When absent, fetched from
|
|
10529
|
+
innerapi (same path as doctor).
|
|
10484
10530
|
`
|
|
10485
10531
|
},
|
|
10486
10532
|
{
|
|
@@ -10488,11 +10534,16 @@ DESCRIPTION
|
|
|
10488
10534
|
hidden: true,
|
|
10489
10535
|
summary: "Apply standard-mode repairs",
|
|
10490
10536
|
help: `USAGE
|
|
10491
|
-
${BIN} repair
|
|
10537
|
+
${BIN} repair [--ctx=<base64>]
|
|
10492
10538
|
|
|
10493
10539
|
DESCRIPTION
|
|
10494
|
-
Runs repair for the failing rules
|
|
10495
|
-
|
|
10540
|
+
Runs repair for the failing rules listed inside the ctx's repairData.
|
|
10541
|
+
Intended for sandbox_console's push path — end-users should use
|
|
10542
|
+
\`doctor --fix\` instead.
|
|
10543
|
+
|
|
10544
|
+
OPTIONS
|
|
10545
|
+
--ctx=<base64> Opaque ctx JSON (base64). When absent, fetched from
|
|
10546
|
+
innerapi.
|
|
10496
10547
|
`
|
|
10497
10548
|
},
|
|
10498
10549
|
{
|
|
@@ -10500,15 +10551,14 @@ DESCRIPTION
|
|
|
10500
10551
|
hidden: true,
|
|
10501
10552
|
summary: "Re-initialize sandbox via the 9-step reset pipeline",
|
|
10502
10553
|
help: `USAGE
|
|
10503
|
-
${BIN} reset --async
|
|
10504
|
-
${BIN} reset --worker --task-id=<id>
|
|
10554
|
+
${BIN} reset --async [--ctx=<base64>]
|
|
10555
|
+
${BIN} reset --worker --task-id=<id> [--ctx=<base64>]
|
|
10505
10556
|
|
|
10506
10557
|
DESCRIPTION
|
|
10507
10558
|
Two-phase pipeline driven asynchronously: the --async invocation spawns
|
|
10508
10559
|
a detached worker and returns { taskId } immediately; the --worker
|
|
10509
10560
|
invocation (spawned by --async) runs the actual 9 steps and writes
|
|
10510
10561
|
progress to /tmp/openclaw-diagnose/reset-<taskId>.json.
|
|
10511
|
-
Ctx is fetched from innerapi automatically.
|
|
10512
10562
|
|
|
10513
10563
|
Poll progress with \`${BIN} get_reset_task --task-id=<id>\`.
|
|
10514
10564
|
|
|
@@ -10516,6 +10566,7 @@ OPTIONS
|
|
|
10516
10566
|
--async Start a detached worker and return taskId on stdout.
|
|
10517
10567
|
--worker Internal — run the 9-step pipeline (launched by --async).
|
|
10518
10568
|
--task-id=<id> Required with --worker; identifies the progress file.
|
|
10569
|
+
--ctx=<base64> Opaque ctx JSON; fetched from innerapi when absent.
|
|
10519
10570
|
`
|
|
10520
10571
|
},
|
|
10521
10572
|
{
|
|
@@ -10538,7 +10589,7 @@ OPTIONS
|
|
|
10538
10589
|
hidden: true,
|
|
10539
10590
|
summary: "Download + install the openclaw tarball",
|
|
10540
10591
|
help: `USAGE
|
|
10541
|
-
${BIN} install-openclaw <tag> [--oss_file_map=<base64>]
|
|
10592
|
+
${BIN} install-openclaw <tag> [--ctx=<base64> | --oss_file_map=<base64>]
|
|
10542
10593
|
|
|
10543
10594
|
DESCRIPTION
|
|
10544
10595
|
Downloads the openclaw@<tag> tgz via the signed OSS URL found in the
|
|
@@ -10550,9 +10601,9 @@ ARGUMENTS
|
|
|
10550
10601
|
<tag> Openclaw version tag, e.g. 2026.4.11.
|
|
10551
10602
|
|
|
10552
10603
|
OPTIONS
|
|
10604
|
+
--ctx=<base64> Opaque ctx; ossFileMap is extracted from it.
|
|
10553
10605
|
--oss_file_map=... Pre-built OSS URL map (base64 JSON); skips innerapi
|
|
10554
|
-
entirely.
|
|
10555
|
-
innerapi automatically.
|
|
10606
|
+
entirely. Wins over --ctx when both provided.
|
|
10556
10607
|
`
|
|
10557
10608
|
},
|
|
10558
10609
|
{
|
|
@@ -10578,7 +10629,8 @@ OPTIONS
|
|
|
10578
10629
|
--home_base=<dir> Override the /home/gem base (tests).
|
|
10579
10630
|
--config_path=<p> Override the openclaw.json path (tests).
|
|
10580
10631
|
--skip-config-update Leave plugins.installs in openclaw.json untouched.
|
|
10581
|
-
--
|
|
10632
|
+
--ctx=<base64> Opaque ctx; see install-openclaw for semantics.
|
|
10633
|
+
--oss_file_map=... Pre-built OSS URL map (base64 JSON).
|
|
10582
10634
|
`
|
|
10583
10635
|
},
|
|
10584
10636
|
{
|
|
@@ -10605,6 +10657,7 @@ OPTIONS
|
|
|
10605
10657
|
--cli=<name> CLI package to install by short name or scoped
|
|
10606
10658
|
packageName (repeatable, at least one required).
|
|
10607
10659
|
--home_base=<dir> Override the /home/gem base (tests).
|
|
10660
|
+
--ctx=<base64> Opaque ctx; ossFileMap is extracted from it.
|
|
10608
10661
|
--oss_file_map=... Pre-built OSS URL map (base64 JSON); skips innerapi.
|
|
10609
10662
|
|
|
10610
10663
|
EXAMPLES
|
|
@@ -10658,6 +10711,46 @@ OPTIONS
|
|
|
10658
10711
|
EXIT CODES
|
|
10659
10712
|
0 Success or skipped (prerequisites not met).
|
|
10660
10713
|
1 Secret/path unresolvable, lark-cli failed, or config unreadable.
|
|
10714
|
+
`
|
|
10715
|
+
},
|
|
10716
|
+
{
|
|
10717
|
+
name: "upgrade-lark",
|
|
10718
|
+
hidden: false,
|
|
10719
|
+
summary: "Upgrade the Feishu/Lark plugin via @larksuite/openclaw-lark-tools",
|
|
10720
|
+
help: `USAGE
|
|
10721
|
+
${BIN} upgrade-lark [--scene=<scene>] [--caller=<n>] [--trace-id=<id>]
|
|
10722
|
+
|
|
10723
|
+
DESCRIPTION
|
|
10724
|
+
Upgrades the Feishu/Lark plugin by running:
|
|
10725
|
+
npx -y @larksuite/openclaw-lark-tools update --use-existing
|
|
10726
|
+
|
|
10727
|
+
Before the upgrade, the following files are backed up:
|
|
10728
|
+
- openclaw.json
|
|
10729
|
+
- extensions/openclaw-lark/ (if present)
|
|
10730
|
+
- extensions/feishu-openclaw-plugin/ (if present)
|
|
10731
|
+
After the upgrade, the result is validated:
|
|
10732
|
+
- feishu.accounts bot count must not decrease
|
|
10733
|
+
- gateway config structure must remain valid (port/mode/bind/auth/trustedProxies)
|
|
10734
|
+
If the upgrade command fails, or validation fails, the backed-up files are
|
|
10735
|
+
restored to roll back the changes.
|
|
10736
|
+
|
|
10737
|
+
Execution is logged to /tmp/openclaw-diagnose/upgrade-lark-<runId>.log.
|
|
10738
|
+
|
|
10739
|
+
Output is a single JSON object on stdout:
|
|
10740
|
+
{ "ok": true, "stdout": "...", "stderr": "...", "logFile": "..." }
|
|
10741
|
+
{ "ok": false, "error": "...", "stderr": "...", "exitCode": 1,
|
|
10742
|
+
"rollbackOk": true, "validationError": "...", "logFile": "..." }
|
|
10743
|
+
|
|
10744
|
+
OPTIONS
|
|
10745
|
+
--scene=<scene> Telemetry label forwarded to Slardar only.
|
|
10746
|
+
Known values: PageUpgradeLark, etc. Custom strings accepted.
|
|
10747
|
+
--caller=<name> Optional metadata passed to innerapi.
|
|
10748
|
+
--trace-id=<id> Optional log-correlation id.
|
|
10749
|
+
|
|
10750
|
+
EXIT CODES
|
|
10751
|
+
0 Success: upgrade ran and all validations passed.
|
|
10752
|
+
1 Failure: npx error, validation failed, or git commit failed.
|
|
10753
|
+
File rollback was attempted (see rollbackOk in the JSON output).
|
|
10661
10754
|
`
|
|
10662
10755
|
},
|
|
10663
10756
|
{
|
|
@@ -10691,6 +10784,41 @@ EXAMPLES
|
|
|
10691
10784
|
${BIN} rules # all rules
|
|
10692
10785
|
${BIN} rules --rule=gateway # single rule
|
|
10693
10786
|
${BIN} rules --rule=gateway --rule=feishu_channel # multiple rules
|
|
10787
|
+
`
|
|
10788
|
+
},
|
|
10789
|
+
{
|
|
10790
|
+
name: "channels-probe",
|
|
10791
|
+
hidden: true,
|
|
10792
|
+
summary: "Check feishu channel health via openclaw channels status --probe",
|
|
10793
|
+
help: `USAGE
|
|
10794
|
+
${BIN} channels-probe [--timeout=<ms>]
|
|
10795
|
+
|
|
10796
|
+
DESCRIPTION
|
|
10797
|
+
Runs \`openclaw channels status --probe\` and returns a structured JSON
|
|
10798
|
+
summary of whether the current environment's feishu channels are
|
|
10799
|
+
configured and working correctly.
|
|
10800
|
+
|
|
10801
|
+
Output:
|
|
10802
|
+
{
|
|
10803
|
+
"available": true,
|
|
10804
|
+
"gatewayReachable": true,
|
|
10805
|
+
"accounts": [
|
|
10806
|
+
{ "id": "default", "bits": ["enabled","configured","running","works"],
|
|
10807
|
+
"isWorking": true, "raw": "- Feishu default: ..." }
|
|
10808
|
+
],
|
|
10809
|
+
"anyAccountWorking": true
|
|
10810
|
+
}
|
|
10811
|
+
|
|
10812
|
+
An account is considered working when:
|
|
10813
|
+
enabled ∧ configured ∧ ( works ∨ ( running ∧ no error: ∧ no probe failed ) )
|
|
10814
|
+
|
|
10815
|
+
"available": false means the CLI invocation itself failed (openclaw not
|
|
10816
|
+
found, gateway unreachable, or no parseable output returned).
|
|
10817
|
+
|
|
10818
|
+
OPTIONS
|
|
10819
|
+
--timeout=<ms> Max wait in milliseconds (default: 60000). The probe
|
|
10820
|
+
can hang indefinitely on openclaw v2026.4.x due to a
|
|
10821
|
+
missing per-request HTTP timeout — set this accordingly.
|
|
10694
10822
|
`
|
|
10695
10823
|
},
|
|
10696
10824
|
{
|
|
@@ -10711,7 +10839,8 @@ OPTIONS
|
|
|
10711
10839
|
--role=<role> Package role (e.g. template, config).
|
|
10712
10840
|
--name=<name> Package name within the role.
|
|
10713
10841
|
--dir=<dir> Target dir (defaults to dirname(pkg.installPath)).
|
|
10714
|
-
--
|
|
10842
|
+
--ctx=<base64> Opaque ctx; ossFileMap is extracted from it.
|
|
10843
|
+
--oss_file_map=... Pre-built OSS URL map (base64 JSON).
|
|
10715
10844
|
`
|
|
10716
10845
|
}
|
|
10717
10846
|
];
|
|
@@ -10787,31 +10916,31 @@ function planVarsFields(opts = {}) {
|
|
|
10787
10916
|
*
|
|
10788
10917
|
* Per-command group needs:
|
|
10789
10918
|
*
|
|
10790
|
-
* doctor / check app
|
|
10791
|
-
* repair app + secrets
|
|
10792
|
-
* reset app + secrets + install + reset
|
|
10919
|
+
* doctor / check app (rule-driven)
|
|
10920
|
+
* repair app + secrets (writes secretsContent / providerKeyContent)
|
|
10921
|
+
* reset app + secrets + install + reset (the works)
|
|
10793
10922
|
* install-* install only
|
|
10794
10923
|
*
|
|
10795
10924
|
* Empty result (`{}`) means "no group needed" — the CLI can skip the
|
|
10796
10925
|
* `fetchCtxViaInnerApi` call entirely and run with a synthetic empty ctx.
|
|
10926
|
+
* Happens e.g. when the user pinned `--rule=<key>` to a vars-free rule on
|
|
10927
|
+
* `doctor`.
|
|
10797
10928
|
*/
|
|
10798
10929
|
function planCtxPopulate(opts) {
|
|
10799
10930
|
if (opts.command === "install") return { install: true };
|
|
10800
10931
|
const populate = {};
|
|
10801
|
-
|
|
10932
|
+
const appFields = planVarsFields({
|
|
10802
10933
|
disabled: opts.disabled,
|
|
10803
10934
|
onlyRules: opts.onlyRules,
|
|
10804
10935
|
profile: opts.profile
|
|
10805
|
-
})
|
|
10806
|
-
if (
|
|
10807
|
-
|
|
10808
|
-
|
|
10809
|
-
} else if (opts.command === "reset") {
|
|
10936
|
+
});
|
|
10937
|
+
if (appFields.length > 0) populate.app = appFields;
|
|
10938
|
+
if (opts.command === "repair") populate.secrets = true;
|
|
10939
|
+
else if (opts.command === "reset") {
|
|
10810
10940
|
populate.secrets = true;
|
|
10811
10941
|
populate.install = true;
|
|
10812
10942
|
populate.reset = true;
|
|
10813
|
-
|
|
10814
|
-
} else if (opts.command === "doctor" || opts.command === "check") populate.larkApps = true;
|
|
10943
|
+
}
|
|
10815
10944
|
return populate;
|
|
10816
10945
|
}
|
|
10817
10946
|
//#endregion
|
|
@@ -10865,11 +10994,372 @@ function reportDoctorRunToSlardar(opts) {
|
|
|
10865
10994
|
}
|
|
10866
10995
|
});
|
|
10867
10996
|
}
|
|
10997
|
+
function readLogFile(filePath) {
|
|
10998
|
+
try {
|
|
10999
|
+
return node_fs.default.readFileSync(filePath, "utf-8");
|
|
11000
|
+
} catch {
|
|
11001
|
+
return "";
|
|
11002
|
+
}
|
|
11003
|
+
}
|
|
11004
|
+
function reportUpgradeLarkToSlardar(opts) {
|
|
11005
|
+
console.error(`[slardar] upgrade_lark_run scene=${opts.scene ?? ""} success=${opts.success} exitCode=${opts.exitCode ?? ""} rollbackOk=${opts.rollbackOk ?? ""}`);
|
|
11006
|
+
const logContent = readLogFile(opts.logFile);
|
|
11007
|
+
reportTask({
|
|
11008
|
+
eventName: "upgrade_lark_run",
|
|
11009
|
+
durationMs: opts.durationMs,
|
|
11010
|
+
status: opts.success ? "success" : "failed",
|
|
11011
|
+
extraCategories: {
|
|
11012
|
+
scene: opts.scene ?? "",
|
|
11013
|
+
exit_code: String(opts.exitCode ?? ""),
|
|
11014
|
+
rollback_ok: opts.rollbackOk != null ? String(opts.rollbackOk) : "",
|
|
11015
|
+
validation_error: opts.validationError ?? "",
|
|
11016
|
+
error_msg: opts.error ?? "",
|
|
11017
|
+
log_content: logContent
|
|
11018
|
+
}
|
|
11019
|
+
});
|
|
11020
|
+
}
|
|
11021
|
+
//#endregion
|
|
11022
|
+
//#region src/upgrade-lark.ts
|
|
11023
|
+
/** Plugin directories under extensions/ that are backed up before upgrade */
|
|
11024
|
+
const FEISHU_PLUGIN_DIRS = ["openclaw-lark", "feishu-openclaw-plugin"];
|
|
11025
|
+
function backupFiles(opts) {
|
|
11026
|
+
const { workspaceDir, configPath, backupDir, log } = opts;
|
|
11027
|
+
try {
|
|
11028
|
+
node_fs.default.mkdirSync(backupDir, { recursive: true });
|
|
11029
|
+
log(`backup dir: ${backupDir}`);
|
|
11030
|
+
if (node_fs.default.existsSync(configPath)) {
|
|
11031
|
+
const stat = node_fs.default.statSync(configPath);
|
|
11032
|
+
node_fs.default.copyFileSync(configPath, node_path.default.join(backupDir, "openclaw.json"));
|
|
11033
|
+
log(` backed up: openclaw.json (${stat.size} bytes)`);
|
|
11034
|
+
} else log(` skipped: openclaw.json (not found)`);
|
|
11035
|
+
const extSrc = node_path.default.join(workspaceDir, "extensions");
|
|
11036
|
+
for (const pluginDir of FEISHU_PLUGIN_DIRS) {
|
|
11037
|
+
const src = node_path.default.join(extSrc, pluginDir);
|
|
11038
|
+
if (node_fs.default.existsSync(src)) {
|
|
11039
|
+
const dst = node_path.default.join(backupDir, "extensions", pluginDir);
|
|
11040
|
+
node_fs.default.cpSync(src, dst, { recursive: true });
|
|
11041
|
+
const version = readPkgVersion(node_path.default.join(src, "package.json"));
|
|
11042
|
+
log(` backed up: extensions/${pluginDir}${version ? ` (version: ${version})` : ""}`);
|
|
11043
|
+
} else log(` skipped: extensions/${pluginDir} (not found)`);
|
|
11044
|
+
}
|
|
11045
|
+
return { ok: true };
|
|
11046
|
+
} catch (e) {
|
|
11047
|
+
return {
|
|
11048
|
+
ok: false,
|
|
11049
|
+
error: `backup failed: ${e.message}`
|
|
11050
|
+
};
|
|
11051
|
+
}
|
|
11052
|
+
}
|
|
11053
|
+
function restoreFiles(opts) {
|
|
11054
|
+
const { workspaceDir, configPath, backupDir, log } = opts;
|
|
11055
|
+
try {
|
|
11056
|
+
const configBackup = node_path.default.join(backupDir, "openclaw.json");
|
|
11057
|
+
if (node_fs.default.existsSync(configBackup)) {
|
|
11058
|
+
node_fs.default.copyFileSync(configBackup, configPath);
|
|
11059
|
+
log(` restored: openclaw.json`);
|
|
11060
|
+
}
|
|
11061
|
+
const extDst = node_path.default.join(workspaceDir, "extensions");
|
|
11062
|
+
for (const pluginDir of FEISHU_PLUGIN_DIRS) {
|
|
11063
|
+
const backupSrc = node_path.default.join(backupDir, "extensions", pluginDir);
|
|
11064
|
+
if (node_fs.default.existsSync(backupSrc)) {
|
|
11065
|
+
const dst = node_path.default.join(extDst, pluginDir);
|
|
11066
|
+
if (node_fs.default.existsSync(dst)) node_fs.default.rmSync(dst, {
|
|
11067
|
+
recursive: true,
|
|
11068
|
+
force: true
|
|
11069
|
+
});
|
|
11070
|
+
node_fs.default.cpSync(backupSrc, dst, { recursive: true });
|
|
11071
|
+
log(` restored: extensions/${pluginDir}`);
|
|
11072
|
+
}
|
|
11073
|
+
}
|
|
11074
|
+
return true;
|
|
11075
|
+
} catch (e) {
|
|
11076
|
+
log(` restore error: ${e.message}`);
|
|
11077
|
+
return false;
|
|
11078
|
+
}
|
|
11079
|
+
}
|
|
11080
|
+
function readPkgVersion(pkgPath) {
|
|
11081
|
+
try {
|
|
11082
|
+
const pkg = JSON.parse(node_fs.default.readFileSync(pkgPath, "utf-8"));
|
|
11083
|
+
return typeof pkg.version === "string" ? pkg.version : null;
|
|
11084
|
+
} catch {
|
|
11085
|
+
return null;
|
|
11086
|
+
}
|
|
11087
|
+
}
|
|
11088
|
+
function snapshotVersions(cwd, log) {
|
|
11089
|
+
const ocResult = (0, node_child_process.spawnSync)("openclaw", ["--version"], {
|
|
11090
|
+
cwd,
|
|
11091
|
+
encoding: "utf-8",
|
|
11092
|
+
stdio: [
|
|
11093
|
+
"ignore",
|
|
11094
|
+
"pipe",
|
|
11095
|
+
"pipe"
|
|
11096
|
+
],
|
|
11097
|
+
timeout: 5e3
|
|
11098
|
+
});
|
|
11099
|
+
const ocRaw = (ocResult.stdout ?? "").trim() || (ocResult.stderr ?? "").trim();
|
|
11100
|
+
const extDir = node_path.default.join(cwd, "extensions");
|
|
11101
|
+
const larkPkg = node_path.default.join(extDir, "openclaw-lark", "package.json");
|
|
11102
|
+
const feishuPkg = node_path.default.join(extDir, "feishu-openclaw-plugin", "package.json");
|
|
11103
|
+
log(` version-check paths: ${larkPkg} [${node_fs.default.existsSync(larkPkg) ? "exists" : "missing"}]`);
|
|
11104
|
+
log(` version-check paths: ${feishuPkg} [${node_fs.default.existsSync(feishuPkg) ? "exists" : "missing"}]`);
|
|
11105
|
+
return {
|
|
11106
|
+
openclaw: ocRaw || null,
|
|
11107
|
+
openclawLark: readPkgVersion(larkPkg),
|
|
11108
|
+
feishuOpenclawPlugin: readPkgVersion(feishuPkg)
|
|
11109
|
+
};
|
|
11110
|
+
}
|
|
11111
|
+
function logVersionSnapshot(label, v, log) {
|
|
11112
|
+
log(`${label}: openclaw=${v.openclaw ?? "n/a"} openclaw-lark=${v.openclawLark ?? "n/a"} feishu-openclaw-plugin=${v.feishuOpenclawPlugin ?? "n/a"}`);
|
|
11113
|
+
}
|
|
11114
|
+
function countFeishuBots(configPath) {
|
|
11115
|
+
try {
|
|
11116
|
+
const raw = node_fs.default.readFileSync(configPath, "utf-8");
|
|
11117
|
+
const config = loadJSON5().parse(raw);
|
|
11118
|
+
const accounts = getNestedMap(config, "channels", "feishu", "accounts");
|
|
11119
|
+
if (accounts) return Object.keys(accounts).length;
|
|
11120
|
+
const feishu = getNestedMap(config, "channels", "feishu");
|
|
11121
|
+
return typeof feishu?.appId === "string" && feishu.appId ? 1 : 0;
|
|
11122
|
+
} catch {
|
|
11123
|
+
return 0;
|
|
11124
|
+
}
|
|
11125
|
+
}
|
|
11126
|
+
/** Run channels probe, log results, and return the result. Never throws. */
|
|
11127
|
+
function probeChannels(label, log, timeoutMs) {
|
|
11128
|
+
try {
|
|
11129
|
+
const r = runChannelsProbe(timeoutMs);
|
|
11130
|
+
log(` ${label} available=${r.available} anyAccountWorking=${r.anyAccountWorking}`);
|
|
11131
|
+
if (r.error) log(` ${label} error: ${r.error}`);
|
|
11132
|
+
if (r.gatewayReachable != null) log(` ${label} gatewayReachable: ${r.gatewayReachable}`);
|
|
11133
|
+
for (const acct of r.accounts ?? []) log(` ${label} account ${acct.id}: isWorking=${acct.isWorking} bits=[${acct.bits.join(",")}]`);
|
|
11134
|
+
return r;
|
|
11135
|
+
} catch (e) {
|
|
11136
|
+
log(` ${label} channels probe threw: ${e.message}`);
|
|
11137
|
+
return {
|
|
11138
|
+
available: false,
|
|
11139
|
+
gatewayReachable: false,
|
|
11140
|
+
feishuConfigInvalid: false,
|
|
11141
|
+
accounts: [],
|
|
11142
|
+
anyAccountWorking: false
|
|
11143
|
+
};
|
|
11144
|
+
}
|
|
11145
|
+
}
|
|
11146
|
+
function runUpgradeLark(opts) {
|
|
11147
|
+
const cwd = opts.cwd ?? "/home/gem/workspace/agent";
|
|
11148
|
+
const configPath = opts.configPath ?? CONFIG_PATH;
|
|
11149
|
+
const logFile = upgradeLarkLogFile(opts.runId);
|
|
11150
|
+
const log = makeLogger(logFile);
|
|
11151
|
+
const fsOpts = {
|
|
11152
|
+
workspaceDir: cwd,
|
|
11153
|
+
configPath,
|
|
11154
|
+
backupDir: node_path.default.join(opts.backupBaseDir ?? "/tmp/openclaw-diagnose", `upgrade-lark-backup-${opts.runId}`),
|
|
11155
|
+
log
|
|
11156
|
+
};
|
|
11157
|
+
const cliScript = opts.cliScript ?? process.argv[1];
|
|
11158
|
+
const statusCheckDelayMs = opts.statusCheckDelayMs ?? 5e3;
|
|
11159
|
+
log(`${"=".repeat(60)}`);
|
|
11160
|
+
log(`upgrade-lark started runId=${opts.runId}`);
|
|
11161
|
+
log(` cwd : ${cwd}`);
|
|
11162
|
+
log(` configPath : ${configPath}`);
|
|
11163
|
+
log(`${"=".repeat(60)}`);
|
|
11164
|
+
log("");
|
|
11165
|
+
log("── [Pre-check A] channels probe(升级前)────────────────");
|
|
11166
|
+
const beforeChannels = probeChannels("before", log, 6e4);
|
|
11167
|
+
log("");
|
|
11168
|
+
log("── [Pre-check B] 版本兼容预检 ───────────────────────────");
|
|
11169
|
+
let versionIncompatible = false;
|
|
11170
|
+
try {
|
|
11171
|
+
const rawConfig = node_fs.default.readFileSync(configPath, "utf-8");
|
|
11172
|
+
versionIncompatible = needsLarkUpgrade({
|
|
11173
|
+
config: loadJSON5().parse(rawConfig),
|
|
11174
|
+
configPath,
|
|
11175
|
+
vars: {},
|
|
11176
|
+
providerDeps: {
|
|
11177
|
+
usesMiaodaProvider: false,
|
|
11178
|
+
usesMiaodaSecretProvider: false
|
|
11179
|
+
}
|
|
11180
|
+
});
|
|
11181
|
+
log(` version-compat pre-check: ${versionIncompatible ? "NEEDS_UPGRADE" : "ok"}`);
|
|
11182
|
+
} catch (e) {
|
|
11183
|
+
log(` version-compat pre-check error: ${e.message} — treating as needs-upgrade`);
|
|
11184
|
+
versionIncompatible = true;
|
|
11185
|
+
}
|
|
11186
|
+
const feishuConfigInvalid = beforeChannels.feishuConfigInvalid;
|
|
11187
|
+
log(` feishu config invalid : ${feishuConfigInvalid}`);
|
|
11188
|
+
log("");
|
|
11189
|
+
log("── [Gate] 升级前置条件检查 ───────────────────────────────");
|
|
11190
|
+
log(` versionIncompatible : ${versionIncompatible}`);
|
|
11191
|
+
log(` feishuConfigInvalid : ${feishuConfigInvalid}`);
|
|
11192
|
+
log(` channels working before: ${beforeChannels.anyAccountWorking}`);
|
|
11193
|
+
if (!(versionIncompatible || feishuConfigInvalid)) {
|
|
11194
|
+
const reason = "version compatible and feishu channel config valid — upgrade not needed";
|
|
11195
|
+
log(` SKIP: ${reason}`);
|
|
11196
|
+
log(`${"=".repeat(60)}`);
|
|
11197
|
+
log("upgrade-lark skipped (pre-check gate)");
|
|
11198
|
+
log(`${"=".repeat(60)}`);
|
|
11199
|
+
return {
|
|
11200
|
+
ok: true,
|
|
11201
|
+
skipped: true,
|
|
11202
|
+
skipReason: reason,
|
|
11203
|
+
logFile
|
|
11204
|
+
};
|
|
11205
|
+
}
|
|
11206
|
+
if (beforeChannels.anyAccountWorking) {
|
|
11207
|
+
const reason = "channels are working — upgrade not needed (issue detected but system is functional)";
|
|
11208
|
+
log(` SKIP: ${reason}`);
|
|
11209
|
+
log(`${"=".repeat(60)}`);
|
|
11210
|
+
log("upgrade-lark skipped (pre-check gate)");
|
|
11211
|
+
log(`${"=".repeat(60)}`);
|
|
11212
|
+
return {
|
|
11213
|
+
ok: true,
|
|
11214
|
+
skipped: true,
|
|
11215
|
+
skipReason: reason,
|
|
11216
|
+
logFile
|
|
11217
|
+
};
|
|
11218
|
+
}
|
|
11219
|
+
log(` PROCEED: requiresLarkUpgrade=true (version=${versionIncompatible}, feishuConfig=${feishuConfigInvalid}) AND channels not working → running upgrade`);
|
|
11220
|
+
log("");
|
|
11221
|
+
log("── [1/6] 文件备份 ────────────────────────────────────────");
|
|
11222
|
+
log(`before-state: botCount=${countFeishuBots(configPath)}`);
|
|
11223
|
+
const backup = backupFiles(fsOpts);
|
|
11224
|
+
if (!backup.ok) {
|
|
11225
|
+
log(`ERROR: ${backup.error}`);
|
|
11226
|
+
return {
|
|
11227
|
+
ok: false,
|
|
11228
|
+
error: backup.error,
|
|
11229
|
+
logFile
|
|
11230
|
+
};
|
|
11231
|
+
}
|
|
11232
|
+
log("backup: ok");
|
|
11233
|
+
logVersionSnapshot("before-versions", snapshotVersions(cwd, log), log);
|
|
11234
|
+
log("");
|
|
11235
|
+
log("── [2/6] 清理本地 openclaw shim ─────────────────────────");
|
|
11236
|
+
const localOpenclawBin = node_path.default.join(cwd, "node_modules", ".bin", "openclaw");
|
|
11237
|
+
if (node_fs.default.existsSync(localOpenclawBin)) try {
|
|
11238
|
+
node_fs.default.rmSync(localOpenclawBin);
|
|
11239
|
+
log(` removed: ${localOpenclawBin}`);
|
|
11240
|
+
} catch (e) {
|
|
11241
|
+
log(` WARN: failed to remove ${localOpenclawBin}: ${e.message}`);
|
|
11242
|
+
}
|
|
11243
|
+
else log(` skipped: ${localOpenclawBin} (not found)`);
|
|
11244
|
+
log("");
|
|
11245
|
+
log("── [3/6] npx install (@larksuite/openclaw-lark-tools update) ──");
|
|
11246
|
+
const npxResult = (0, node_child_process.spawnSync)("npx", [
|
|
11247
|
+
"-y",
|
|
11248
|
+
"@larksuite/openclaw-lark-tools",
|
|
11249
|
+
"update"
|
|
11250
|
+
], {
|
|
11251
|
+
cwd,
|
|
11252
|
+
encoding: "utf-8",
|
|
11253
|
+
stdio: [
|
|
11254
|
+
"ignore",
|
|
11255
|
+
"pipe",
|
|
11256
|
+
"pipe"
|
|
11257
|
+
],
|
|
11258
|
+
timeout: 12e4
|
|
11259
|
+
});
|
|
11260
|
+
const npxStdout = npxResult.stdout?.trim() ?? "";
|
|
11261
|
+
const npxStderr = npxResult.stderr?.trim() ?? "";
|
|
11262
|
+
const npxExitCode = npxResult.status ?? 1;
|
|
11263
|
+
if (npxStdout) log(`npx stdout:\n${npxStdout}`);
|
|
11264
|
+
if (npxStderr) log(`npx stderr:\n${npxStderr}`);
|
|
11265
|
+
log(`npx exit: ${npxExitCode}${npxResult.error ? ` error: ${npxResult.error.message}` : ""}`);
|
|
11266
|
+
if (statusCheckDelayMs > 0) {
|
|
11267
|
+
log("");
|
|
11268
|
+
log(`── 等待 ${statusCheckDelayMs / 1e3}s(让 openclaw 服务完成重启) ─────────────`);
|
|
11269
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, statusCheckDelayMs);
|
|
11270
|
+
log("wait done");
|
|
11271
|
+
}
|
|
11272
|
+
const doRollback = (reason) => {
|
|
11273
|
+
log(`ERROR: ${reason}`);
|
|
11274
|
+
const rollbackOk = restoreFiles(fsOpts);
|
|
11275
|
+
log(`rollback: ${rollbackOk ? "ok" : "FAILED"}`);
|
|
11276
|
+
return {
|
|
11277
|
+
ok: false,
|
|
11278
|
+
error: reason,
|
|
11279
|
+
validationError: reason,
|
|
11280
|
+
stdout: npxStdout,
|
|
11281
|
+
stderr: npxStderr,
|
|
11282
|
+
exitCode: npxExitCode,
|
|
11283
|
+
rollbackOk,
|
|
11284
|
+
logFile
|
|
11285
|
+
};
|
|
11286
|
+
};
|
|
11287
|
+
log("");
|
|
11288
|
+
log("── [4/5] 安装后诊断校验 ─────────────────────────────────");
|
|
11289
|
+
logVersionSnapshot("after-versions", snapshotVersions(cwd, log), log);
|
|
11290
|
+
let afterVersionIncompatible = false;
|
|
11291
|
+
try {
|
|
11292
|
+
const rawConfig = node_fs.default.readFileSync(configPath, "utf-8");
|
|
11293
|
+
afterVersionIncompatible = needsLarkUpgrade({
|
|
11294
|
+
config: loadJSON5().parse(rawConfig),
|
|
11295
|
+
configPath,
|
|
11296
|
+
vars: {},
|
|
11297
|
+
providerDeps: {
|
|
11298
|
+
usesMiaodaProvider: false,
|
|
11299
|
+
usesMiaodaSecretProvider: false
|
|
11300
|
+
}
|
|
11301
|
+
});
|
|
11302
|
+
log(` version-compat post-check: ${afterVersionIncompatible ? "STILL_INCOMPATIBLE" : "ok"}`);
|
|
11303
|
+
} catch (e) {
|
|
11304
|
+
log(` version-compat post-check error: ${e.message} — treating as still-incompatible`);
|
|
11305
|
+
afterVersionIncompatible = true;
|
|
11306
|
+
}
|
|
11307
|
+
const afterChannels = probeChannels("after", log, 6e4);
|
|
11308
|
+
log(` feishu config invalid after: ${afterChannels.feishuConfigInvalid}`);
|
|
11309
|
+
const stillNeedsUpgrade = (afterVersionIncompatible || afterChannels.feishuConfigInvalid) && !afterChannels.anyAccountWorking;
|
|
11310
|
+
log(` post-check: stillNeedsUpgrade=${stillNeedsUpgrade} (version=${afterVersionIncompatible}, feishuConfig=${afterChannels.feishuConfigInvalid}, channelsWorking=${afterChannels.anyAccountWorking})`);
|
|
11311
|
+
if (stillNeedsUpgrade) return doRollback(`post-install diagnosis still shows anomaly: versionIncompatible=${afterVersionIncompatible}, feishuConfigInvalid=${afterChannels.feishuConfigInvalid}, anyAccountWorking=${afterChannels.anyAccountWorking}`);
|
|
11312
|
+
log(" post-install diagnosis: ok (upgrade conditions resolved)");
|
|
11313
|
+
log("");
|
|
11314
|
+
log("── [6/6] doctor --fix ────────────────────────────────────");
|
|
11315
|
+
const fixArgs = ["doctor", "--fix"];
|
|
11316
|
+
if (opts.scene) fixArgs.push(`--scene=${opts.scene}`);
|
|
11317
|
+
const fixResult = (0, node_child_process.spawnSync)(process.execPath, [cliScript, ...fixArgs], {
|
|
11318
|
+
cwd,
|
|
11319
|
+
encoding: "utf-8",
|
|
11320
|
+
stdio: [
|
|
11321
|
+
"ignore",
|
|
11322
|
+
"pipe",
|
|
11323
|
+
"pipe"
|
|
11324
|
+
],
|
|
11325
|
+
timeout: 6e4,
|
|
11326
|
+
env: process.env
|
|
11327
|
+
});
|
|
11328
|
+
if (fixResult.stdout?.trim()) log(`doctor(fix) stdout:\n${fixResult.stdout.trim()}`);
|
|
11329
|
+
if (fixResult.stderr?.trim()) log(`doctor(fix) stderr:\n${fixResult.stderr.trim()}`);
|
|
11330
|
+
log(`doctor(fix) exit: ${fixResult.status ?? "null"}${fixResult.error ? ` error: ${fixResult.error.message}` : ""}`);
|
|
11331
|
+
log("");
|
|
11332
|
+
log(`${"=".repeat(60)}`);
|
|
11333
|
+
log("upgrade-lark completed successfully");
|
|
11334
|
+
log(`${"=".repeat(60)}`);
|
|
11335
|
+
return {
|
|
11336
|
+
ok: true,
|
|
11337
|
+
stdout: npxStdout,
|
|
11338
|
+
stderr: npxStderr,
|
|
11339
|
+
exitCode: npxExitCode,
|
|
11340
|
+
logFile
|
|
11341
|
+
};
|
|
11342
|
+
}
|
|
10868
11343
|
//#endregion
|
|
10869
11344
|
//#region src/index.ts
|
|
10870
11345
|
const args = node_process.default.argv.slice(2);
|
|
10871
11346
|
const mode = args.find((a) => !a.startsWith("-"));
|
|
10872
11347
|
/**
|
|
11348
|
+
* Decode `--ctx=<base64>` into an opaque JSON object. Returns undefined when
|
|
11349
|
+
* the flag isn't present — the caller decides whether to fall back to the
|
|
11350
|
+
* innerapi or to error out.
|
|
11351
|
+
*
|
|
11352
|
+
* The object's shape is not enforced here; downstream code consumes it via
|
|
11353
|
+
* either `normalizeCtx()` (new path) or direct field access for the legacy
|
|
11354
|
+
* check/repair/reset contract still used by sandbox_console push.
|
|
11355
|
+
*/
|
|
11356
|
+
function parseCtxFlag(args) {
|
|
11357
|
+
const ctxArg = args.find((a) => a.startsWith("--ctx="));
|
|
11358
|
+
if (!ctxArg) return void 0;
|
|
11359
|
+
const b64 = ctxArg.slice(6);
|
|
11360
|
+
return JSON.parse(Buffer.from(b64, "base64").toString("utf-8"));
|
|
11361
|
+
}
|
|
11362
|
+
/**
|
|
10873
11363
|
* Pull the first non-flag positional after the mode name.
|
|
10874
11364
|
* (The mode itself is args[0] in the filtered set, so we skip index 0.)
|
|
10875
11365
|
*/
|
|
@@ -10897,8 +11387,8 @@ function getMultiFlag(args, name) {
|
|
|
10897
11387
|
* case but is no longer consulted.
|
|
10898
11388
|
*/
|
|
10899
11389
|
async function reportRun(command, rc, _raw, invocation, durationMs, outcome, slardar = {
|
|
10900
|
-
scene
|
|
10901
|
-
profile
|
|
11390
|
+
scene,
|
|
11391
|
+
profile,
|
|
10902
11392
|
fix: false
|
|
10903
11393
|
}) {
|
|
10904
11394
|
console.error(`${command}: telemetry calling report_cli_run`);
|
|
@@ -10962,7 +11452,7 @@ async function main() {
|
|
|
10962
11452
|
console.error(`${mode}: begin argv=[${args.join(" ")}] version=${getVersion()} traceId=${traceId ?? "-"} caller=${caller ?? "-"} runIdGenerated=${rc.generated}`);
|
|
10963
11453
|
switch (mode) {
|
|
10964
11454
|
case "check": {
|
|
10965
|
-
const raw = await fetchCtxViaInnerApi({
|
|
11455
|
+
const raw = parseCtxFlag(args) ?? await fetchCtxViaInnerApi({
|
|
10966
11456
|
populate: planCtxPopulate({
|
|
10967
11457
|
command: "check",
|
|
10968
11458
|
profile
|
|
@@ -10987,7 +11477,7 @@ async function main() {
|
|
|
10987
11477
|
break;
|
|
10988
11478
|
}
|
|
10989
11479
|
case "repair": {
|
|
10990
|
-
const raw = await fetchCtxViaInnerApi({
|
|
11480
|
+
const raw = parseCtxFlag(args) ?? await fetchCtxViaInnerApi({
|
|
10991
11481
|
populate: planCtxPopulate({
|
|
10992
11482
|
command: "repair",
|
|
10993
11483
|
profile
|
|
@@ -11058,15 +11548,27 @@ async function main() {
|
|
|
11058
11548
|
break;
|
|
11059
11549
|
}
|
|
11060
11550
|
case "reset":
|
|
11061
|
-
if (args.includes("--async"))
|
|
11062
|
-
|
|
11551
|
+
if (args.includes("--async")) {
|
|
11552
|
+
const ctxArg = args.find((a) => a.startsWith("--ctx="));
|
|
11553
|
+
let ctxBase64;
|
|
11554
|
+
if (ctxArg) ctxBase64 = ctxArg.slice(6);
|
|
11555
|
+
else {
|
|
11556
|
+
const fetched = await fetchCtxViaInnerApi({
|
|
11557
|
+
populate: planCtxPopulate({ command: "reset" }),
|
|
11558
|
+
caller,
|
|
11559
|
+
traceId
|
|
11560
|
+
});
|
|
11561
|
+
ctxBase64 = Buffer.from(JSON.stringify(fetched), "utf-8").toString("base64");
|
|
11562
|
+
}
|
|
11563
|
+
console.log(JSON.stringify(startAsyncReset(ctxBase64)));
|
|
11564
|
+
} else if (args.includes("--worker")) {
|
|
11063
11565
|
const taskId = args.find((a) => a.startsWith("--task-id="))?.slice(10);
|
|
11064
11566
|
if (!taskId) {
|
|
11065
11567
|
console.error("Error: --task-id=<id> is required for worker");
|
|
11066
11568
|
node_process.default.exit(1);
|
|
11067
11569
|
}
|
|
11068
11570
|
const resultFile = resetResultFile(taskId);
|
|
11069
|
-
const raw = await fetchCtxViaInnerApi({
|
|
11571
|
+
const raw = parseCtxFlag(args) ?? await fetchCtxViaInnerApi({
|
|
11070
11572
|
populate: planCtxPopulate({ command: "reset" }),
|
|
11071
11573
|
caller,
|
|
11072
11574
|
traceId
|
|
@@ -11090,7 +11592,7 @@ async function main() {
|
|
|
11090
11592
|
return;
|
|
11091
11593
|
}
|
|
11092
11594
|
} else {
|
|
11093
|
-
console.error("Usage: reset --async | reset --worker --task-id=<id>");
|
|
11595
|
+
console.error("Usage: reset --async [--ctx=<base64>] | reset --worker --task-id=<id> [--ctx=<base64>]");
|
|
11094
11596
|
node_process.default.exit(1);
|
|
11095
11597
|
}
|
|
11096
11598
|
break;
|
|
@@ -11106,14 +11608,14 @@ async function main() {
|
|
|
11106
11608
|
case "install-openclaw": {
|
|
11107
11609
|
const tag = getPositionalTag(args, "install-openclaw");
|
|
11108
11610
|
if (!tag) {
|
|
11109
|
-
console.error("Usage: install-openclaw <tag> [--oss_file_map=<base64>]");
|
|
11611
|
+
console.error("Usage: install-openclaw <tag> [--ctx=<base64> | --oss_file_map=<base64>]");
|
|
11110
11612
|
node_process.default.exit(1);
|
|
11111
11613
|
}
|
|
11112
11614
|
const ossFileMapFlag = getFlag(args, "oss_file_map");
|
|
11113
11615
|
let installOssFileMap;
|
|
11114
11616
|
let rawForTelemetry;
|
|
11115
11617
|
if (!ossFileMapFlag) {
|
|
11116
|
-
rawForTelemetry = await fetchCtxViaInnerApi({
|
|
11618
|
+
rawForTelemetry = parseCtxFlag(args) ?? await fetchCtxViaInnerApi({
|
|
11117
11619
|
populate: planCtxPopulate({ command: "install" }),
|
|
11118
11620
|
caller,
|
|
11119
11621
|
traceId
|
|
@@ -11148,7 +11650,7 @@ async function main() {
|
|
|
11148
11650
|
case "install-extension": {
|
|
11149
11651
|
const tag = getPositionalTag(args, "install-extension");
|
|
11150
11652
|
if (!tag) {
|
|
11151
|
-
console.error("Usage: install-extension <tag> (--all | --extension=<name>...) [--home_base=<dir>] [--config_path=<path>] [--skip-config-update] [--oss_file_map=<base64>]");
|
|
11653
|
+
console.error("Usage: install-extension <tag> (--all | --extension=<name>...) [--home_base=<dir>] [--config_path=<path>] [--skip-config-update] [--ctx=<base64> | --oss_file_map=<base64>]");
|
|
11152
11654
|
node_process.default.exit(1);
|
|
11153
11655
|
}
|
|
11154
11656
|
const all = args.includes("--all");
|
|
@@ -11160,7 +11662,7 @@ async function main() {
|
|
|
11160
11662
|
let installOssFileMap;
|
|
11161
11663
|
let rawForTelemetry;
|
|
11162
11664
|
if (!ossFileMapFlag) {
|
|
11163
|
-
rawForTelemetry = await fetchCtxViaInnerApi({
|
|
11665
|
+
rawForTelemetry = parseCtxFlag(args) ?? await fetchCtxViaInnerApi({
|
|
11164
11666
|
populate: planCtxPopulate({ command: "install" }),
|
|
11165
11667
|
caller,
|
|
11166
11668
|
traceId
|
|
@@ -11206,12 +11708,12 @@ async function main() {
|
|
|
11206
11708
|
case "install-cli": {
|
|
11207
11709
|
const tag = getPositionalTag(args, "install-cli");
|
|
11208
11710
|
if (!tag) {
|
|
11209
|
-
console.error("Usage: install-cli <tag> --cli=<name>... [--home_base=<dir>] [--oss_file_map=<base64>]");
|
|
11711
|
+
console.error("Usage: install-cli <tag> --cli=<name>... [--home_base=<dir>] [--ctx=<base64> | --oss_file_map=<base64>]");
|
|
11210
11712
|
node_process.default.exit(1);
|
|
11211
11713
|
}
|
|
11212
11714
|
const names = getMultiFlag(args, "cli");
|
|
11213
11715
|
if (names.length === 0) {
|
|
11214
|
-
console.error("Usage: install-cli <tag> --cli=<name>... [--home_base=<dir>] [--oss_file_map=<base64>]");
|
|
11716
|
+
console.error("Usage: install-cli <tag> --cli=<name>... [--home_base=<dir>] [--ctx=<base64> | --oss_file_map=<base64>]");
|
|
11215
11717
|
node_process.default.exit(1);
|
|
11216
11718
|
}
|
|
11217
11719
|
const homeBase = getFlag(args, "home_base");
|
|
@@ -11219,7 +11721,7 @@ async function main() {
|
|
|
11219
11721
|
let installOssFileMap;
|
|
11220
11722
|
let rawForTelemetry;
|
|
11221
11723
|
if (!ossFileMapFlag) {
|
|
11222
|
-
rawForTelemetry = await fetchCtxViaInnerApi({
|
|
11724
|
+
rawForTelemetry = parseCtxFlag(args) ?? await fetchCtxViaInnerApi({
|
|
11223
11725
|
populate: planCtxPopulate({ command: "install" }),
|
|
11224
11726
|
caller,
|
|
11225
11727
|
traceId
|
|
@@ -11267,7 +11769,7 @@ async function main() {
|
|
|
11267
11769
|
case "download-resource": {
|
|
11268
11770
|
const tag = getPositionalTag(args, "download-resource");
|
|
11269
11771
|
if (!tag) {
|
|
11270
|
-
console.error("Usage: download-resource <tag> --role=<role> --name=<name> [--dir=<dir>] [--oss_file_map=<base64>]");
|
|
11772
|
+
console.error("Usage: download-resource <tag> --role=<role> --name=<name> [--dir=<dir>] [--ctx=<base64> | --oss_file_map=<base64>]");
|
|
11271
11773
|
node_process.default.exit(1);
|
|
11272
11774
|
}
|
|
11273
11775
|
const role = getFlag(args, "role");
|
|
@@ -11281,7 +11783,7 @@ async function main() {
|
|
|
11281
11783
|
let installOssFileMap;
|
|
11282
11784
|
let rawForTelemetry;
|
|
11283
11785
|
if (!ossFileMapFlag) {
|
|
11284
|
-
rawForTelemetry = await fetchCtxViaInnerApi({
|
|
11786
|
+
rawForTelemetry = parseCtxFlag(args) ?? await fetchCtxViaInnerApi({
|
|
11285
11787
|
populate: planCtxPopulate({ command: "install" }),
|
|
11286
11788
|
caller,
|
|
11287
11789
|
traceId
|
|
@@ -11355,6 +11857,50 @@ async function main() {
|
|
|
11355
11857
|
if (!result.ok) node_process.default.exit(1);
|
|
11356
11858
|
break;
|
|
11357
11859
|
}
|
|
11860
|
+
case "upgrade-lark": {
|
|
11861
|
+
const result = runUpgradeLark({
|
|
11862
|
+
runId: rc.runId,
|
|
11863
|
+
scene
|
|
11864
|
+
});
|
|
11865
|
+
const upgradeDurationMs = Date.now() - t0;
|
|
11866
|
+
console.log(JSON.stringify(result));
|
|
11867
|
+
reportUpgradeLarkToSlardar({
|
|
11868
|
+
scene,
|
|
11869
|
+
durationMs: upgradeDurationMs,
|
|
11870
|
+
success: result.ok,
|
|
11871
|
+
logFile: result.logFile,
|
|
11872
|
+
exitCode: result.exitCode,
|
|
11873
|
+
rollbackOk: result.rollbackOk,
|
|
11874
|
+
validationError: result.validationError,
|
|
11875
|
+
error: result.error
|
|
11876
|
+
});
|
|
11877
|
+
try {
|
|
11878
|
+
await reportCliRun({
|
|
11879
|
+
command: "upgrade-lark",
|
|
11880
|
+
runId: rc.runId,
|
|
11881
|
+
version: getVersion(),
|
|
11882
|
+
invocation: args.join(" "),
|
|
11883
|
+
durationMs: upgradeDurationMs,
|
|
11884
|
+
caller: rc.caller,
|
|
11885
|
+
traceId: rc.traceId,
|
|
11886
|
+
success: result.ok,
|
|
11887
|
+
result,
|
|
11888
|
+
error: result.ok ? void 0 : { message: result.error ?? "upgrade-lark failed" }
|
|
11889
|
+
});
|
|
11890
|
+
} catch (e) {
|
|
11891
|
+
console.error(`[telemetry] reportCliRun failed: ${e.message}`);
|
|
11892
|
+
}
|
|
11893
|
+
if (!result.ok) {
|
|
11894
|
+
node_process.default.exitCode = 1;
|
|
11895
|
+
return;
|
|
11896
|
+
}
|
|
11897
|
+
break;
|
|
11898
|
+
}
|
|
11899
|
+
case "channels-probe": {
|
|
11900
|
+
const result = runChannelsProbe(getFlag(args, "timeout") ? Number(getFlag(args, "timeout")) : void 0);
|
|
11901
|
+
console.log(JSON.stringify(result));
|
|
11902
|
+
break;
|
|
11903
|
+
}
|
|
11358
11904
|
default:
|
|
11359
11905
|
node_process.default.stderr.write(`Unknown command: ${mode}\n\n`);
|
|
11360
11906
|
node_process.default.stderr.write(formatTopLevelHelp(helpFlags.expert));
|
package/package.json
CHANGED