@lark-apaas/openclaw-scripts-diagnose-cli 0.1.14-alpha.10 → 0.1.14-alpha.11
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 +228 -765
- 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.11";
|
|
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);
|
|
@@ -3369,7 +3370,6 @@ let FeishuPluginOpenclawUpgradeRule = class FeishuPluginOpenclawUpgradeRule exte
|
|
|
3369
3370
|
if (!cc) return { pass: true };
|
|
3370
3371
|
const { ocCur, recommendedOc, installed, isLegacy } = cc;
|
|
3371
3372
|
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,17 +3397,11 @@ 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 (
|
|
3401
|
-
const prefix = buildCompatPrefix(installed, ocCur, isLegacy);
|
|
3402
|
-
if (!recommendedOc) return {
|
|
3403
|
-
pass: false,
|
|
3404
|
-
action: "upgrade_lark",
|
|
3405
|
-
message: `${prefix};建议升级飞书插件至兼容版本`
|
|
3406
|
-
};
|
|
3400
|
+
if (resolveUpgradeDirection(installed, ocCur, recommendedOc, isLegacy) !== "lark") return { pass: true };
|
|
3407
3401
|
return {
|
|
3408
3402
|
pass: false,
|
|
3409
3403
|
action: "upgrade_lark",
|
|
3410
|
-
message: `${
|
|
3404
|
+
message: `${buildCompatPrefix(installed, ocCur, isLegacy)};当前 openclaw@${ocCur} 已达推荐版本,可直接升级飞书插件`
|
|
3411
3405
|
};
|
|
3412
3406
|
}
|
|
3413
3407
|
};
|
|
@@ -3419,18 +3413,6 @@ FeishuPluginLarkUpgradeRule = __decorate([Rule({
|
|
|
3419
3413
|
level: "critical",
|
|
3420
3414
|
usesVars: ["recommendedOpenclawTag"]
|
|
3421
3415
|
})], 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
|
-
}
|
|
3434
3416
|
function isForkPlugin(p) {
|
|
3435
3417
|
return p.scope != null && FORK_SCOPES.includes(p.scope);
|
|
3436
3418
|
}
|
|
@@ -3469,16 +3451,14 @@ function describeCompatConstraint(entry, pluginVersion) {
|
|
|
3469
3451
|
/**
|
|
3470
3452
|
* @lark-apaas/openclaw-lark 豁免 VERSION_COMPAT_MAP,但仍要求 openclaw ≥ FORK_LARK_PLUGIN_MIN_OC_VERSION。
|
|
3471
3453
|
* 其他 @lark-apaas scope 的 fork 插件继续无条件 pass。
|
|
3472
|
-
* recommendedOc 可为 undefined(doctor 模式),此时只检测最低版本要求,不指定目标升级版本。
|
|
3473
3454
|
*/
|
|
3474
3455
|
function validateForkPlugin(installed, ocCur, recommendedOc) {
|
|
3475
3456
|
if (installed.fullName !== FORK_LARK_PLUGIN_FULL_NAME) return { pass: true };
|
|
3476
3457
|
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} 或更高版本`;
|
|
3478
3458
|
return {
|
|
3479
3459
|
pass: false,
|
|
3480
3460
|
action: "upgrade_openclaw",
|
|
3481
|
-
message: `飞书插件 ${describePlugin(installed)}(fork 版)要求 openclaw ≥ ${FORK_LARK_PLUGIN_MIN_OC_VERSION},当前 openclaw@${ocCur}
|
|
3461
|
+
message: `飞书插件 ${describePlugin(installed)}(fork 版)要求 openclaw ≥ ${FORK_LARK_PLUGIN_MIN_OC_VERSION},当前 openclaw@${ocCur} 低于此要求;将 openclaw 升级到 ${recommendedOc} 即可满足`
|
|
3482
3462
|
};
|
|
3483
3463
|
}
|
|
3484
3464
|
function describePlugin(p) {
|
|
@@ -3525,187 +3505,6 @@ function extractScopedNameFromSpec$1(spec) {
|
|
|
3525
3505
|
const at = spec.indexOf("@", 1);
|
|
3526
3506
|
return at === -1 ? spec : spec.slice(0, at);
|
|
3527
3507
|
}
|
|
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
|
-
function accountIsWorking(bits) {
|
|
3555
|
-
const bitTokens = /* @__PURE__ */ new Set();
|
|
3556
|
-
let hasError = false;
|
|
3557
|
-
let hasProbeFailed = false;
|
|
3558
|
-
for (const raw of bits) {
|
|
3559
|
-
const b = raw.trim();
|
|
3560
|
-
if (!b) continue;
|
|
3561
|
-
if (b.startsWith("error:")) {
|
|
3562
|
-
hasError = true;
|
|
3563
|
-
continue;
|
|
3564
|
-
}
|
|
3565
|
-
if (b === "probe failed") {
|
|
3566
|
-
hasProbeFailed = true;
|
|
3567
|
-
continue;
|
|
3568
|
-
}
|
|
3569
|
-
bitTokens.add(b.split(":")[0]);
|
|
3570
|
-
}
|
|
3571
|
-
if (!bitTokens.has("enabled") || !bitTokens.has("configured")) return false;
|
|
3572
|
-
if (bitTokens.has("works")) return true;
|
|
3573
|
-
if (bitTokens.has("running") && !hasError && !hasProbeFailed) return true;
|
|
3574
|
-
return false;
|
|
3575
|
-
}
|
|
3576
|
-
/**
|
|
3577
|
-
* Parse the raw stdout of `openclaw channels status --probe`.
|
|
3578
|
-
* Port of Python `extract_channels_probe` from the feishu-channel-success-rate skill.
|
|
3579
|
-
*/
|
|
3580
|
-
function parseChannelsProbeOutput(text) {
|
|
3581
|
-
const gatewayReachable = text.includes("Gateway reachable");
|
|
3582
|
-
const feishuConfigInvalid = text.includes(FEISHU_INVALID_CONFIG_MSG);
|
|
3583
|
-
const accounts = [];
|
|
3584
|
-
let anyAccountWorking = false;
|
|
3585
|
-
for (const line of text.split("\n")) {
|
|
3586
|
-
const m = CHANNEL_LINE_RE.exec(line.trim());
|
|
3587
|
-
if (!m) continue;
|
|
3588
|
-
const [, acct, rest] = m;
|
|
3589
|
-
const bits = rest.split(",").map((b) => b.trim());
|
|
3590
|
-
const isWorking = accountIsWorking(bits);
|
|
3591
|
-
if (isWorking) anyAccountWorking = true;
|
|
3592
|
-
accounts.push({
|
|
3593
|
-
id: acct.trim(),
|
|
3594
|
-
bits,
|
|
3595
|
-
isWorking,
|
|
3596
|
-
raw: line.trim()
|
|
3597
|
-
});
|
|
3598
|
-
}
|
|
3599
|
-
return {
|
|
3600
|
-
gatewayReachable,
|
|
3601
|
-
feishuConfigInvalid,
|
|
3602
|
-
accounts,
|
|
3603
|
-
anyAccountWorking
|
|
3604
|
-
};
|
|
3605
|
-
}
|
|
3606
|
-
/**
|
|
3607
|
-
* Run `openclaw channels status --probe` and return a structured result.
|
|
3608
|
-
*
|
|
3609
|
-
* The command may exit non-zero when some bot accounts fail their probe — that
|
|
3610
|
-
* is still useful output. We therefore try to parse stdout even when the
|
|
3611
|
-
* process exits with a non-zero code, falling back to an unavailable result
|
|
3612
|
-
* only when there is genuinely no output to parse.
|
|
3613
|
-
*
|
|
3614
|
-
* @param timeoutMs Maximum wait time. Default is 60 s because v2026.4.x
|
|
3615
|
-
* lacks a per-request HTTP timeout and can block indefinitely.
|
|
3616
|
-
*/
|
|
3617
|
-
function runChannelsProbe(timeoutMs = 6e4) {
|
|
3618
|
-
let stdout = "";
|
|
3619
|
-
let stderrText = "";
|
|
3620
|
-
let execError;
|
|
3621
|
-
try {
|
|
3622
|
-
stdout = (0, node_child_process.execSync)("openclaw channels status --probe", {
|
|
3623
|
-
encoding: "utf-8",
|
|
3624
|
-
timeout: timeoutMs,
|
|
3625
|
-
stdio: [
|
|
3626
|
-
"ignore",
|
|
3627
|
-
"pipe",
|
|
3628
|
-
"pipe"
|
|
3629
|
-
]
|
|
3630
|
-
});
|
|
3631
|
-
} catch (e) {
|
|
3632
|
-
const err = e;
|
|
3633
|
-
const stdoutRaw = err.stdout;
|
|
3634
|
-
stdout = typeof stdoutRaw === "string" ? stdoutRaw : stdoutRaw?.toString("utf-8") ?? "";
|
|
3635
|
-
execError = err.message;
|
|
3636
|
-
const stderrRaw = err.stderr;
|
|
3637
|
-
stderrText = (typeof stderrRaw === "string" ? stderrRaw : stderrRaw?.toString("utf-8") ?? "").trim();
|
|
3638
|
-
if (stderrText) console.error(`channels-probe: stderr from CLI: ${stderrText}`);
|
|
3639
|
-
}
|
|
3640
|
-
if (stdout.trim()) return {
|
|
3641
|
-
available: true,
|
|
3642
|
-
...parseChannelsProbeOutput(stdout)
|
|
3643
|
-
};
|
|
3644
|
-
return {
|
|
3645
|
-
available: false,
|
|
3646
|
-
gatewayReachable: false,
|
|
3647
|
-
feishuConfigInvalid: stderrText.includes(FEISHU_INVALID_CONFIG_MSG),
|
|
3648
|
-
accounts: [],
|
|
3649
|
-
anyAccountWorking: false,
|
|
3650
|
-
error: execError ?? "no output from openclaw channels status --probe"
|
|
3651
|
-
};
|
|
3652
|
-
}
|
|
3653
|
-
//#endregion
|
|
3654
|
-
//#region src/rules/upgrade-lark-needed.ts
|
|
3655
|
-
/**
|
|
3656
|
-
* Detects the condition that warrants running `upgrade-lark`:
|
|
3657
|
-
* - feishu plugin version incompatible with current openclaw, OR
|
|
3658
|
-
* - openclaw channels status --probe reports feishu channel config invalid; AND
|
|
3659
|
-
* - channels are not working.
|
|
3660
|
-
*
|
|
3661
|
-
* Both conditions must be true simultaneously. If version is compatible and
|
|
3662
|
-
* feishu config is valid, or channels are working, the rule passes (no action needed).
|
|
3663
|
-
*
|
|
3664
|
-
* feishuConfigInvalid is read from the channels probe output rather than running a
|
|
3665
|
-
* separate `openclaw status` call, since only `openclaw channels status --probe`
|
|
3666
|
-
* reliably surfaces the schema validation error.
|
|
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
|
-
let versionIncompatible = false;
|
|
3674
|
-
try {
|
|
3675
|
-
versionIncompatible = needsLarkUpgrade(ctx);
|
|
3676
|
-
} catch {
|
|
3677
|
-
versionIncompatible = true;
|
|
3678
|
-
}
|
|
3679
|
-
let probeResult;
|
|
3680
|
-
try {
|
|
3681
|
-
probeResult = runChannelsProbe(6e4);
|
|
3682
|
-
} catch {
|
|
3683
|
-
probeResult = {
|
|
3684
|
-
available: false,
|
|
3685
|
-
gatewayReachable: false,
|
|
3686
|
-
feishuConfigInvalid: false,
|
|
3687
|
-
accounts: [],
|
|
3688
|
-
anyAccountWorking: false
|
|
3689
|
-
};
|
|
3690
|
-
}
|
|
3691
|
-
const feishuConfigInvalid = probeResult.feishuConfigInvalid;
|
|
3692
|
-
if (!(versionIncompatible || feishuConfigInvalid)) return { pass: true };
|
|
3693
|
-
if (probeResult.anyAccountWorking) return { pass: true };
|
|
3694
|
-
return {
|
|
3695
|
-
pass: false,
|
|
3696
|
-
action: "upgrade_lark",
|
|
3697
|
-
message: `飞书插件需要升级且 channels 不可用(版本不兼容=${versionIncompatible}, feishu配置无效=${feishuConfigInvalid}),建议执行 upgrade-lark 命令升级飞书插件`
|
|
3698
|
-
};
|
|
3699
|
-
}
|
|
3700
|
-
};
|
|
3701
|
-
UpgradeLarkNeededRule = __decorate([Rule({
|
|
3702
|
-
key: "upgrade_lark_needed",
|
|
3703
|
-
description: "检测飞书插件版本不兼容且 channels 不可用,判断是否需要执行 upgrade-lark 升级",
|
|
3704
|
-
repairMode: "check-only",
|
|
3705
|
-
level: "silent",
|
|
3706
|
-
profile: "experimental",
|
|
3707
|
-
usesVars: ["recommendedOpenclawTag"]
|
|
3708
|
-
})], UpgradeLarkNeededRule);
|
|
3709
3508
|
//#endregion
|
|
3710
3509
|
//#region src/rules/cleanup-install-backup-dirs.ts
|
|
3711
3510
|
const DIR_PREFIX = ".openclaw-install-";
|
|
@@ -3851,6 +3650,117 @@ LarkCliMissingForInstalledLarkPluginRule = __decorate([Rule({
|
|
|
3851
3650
|
usesVars: ["recommendedOpenclawTag"]
|
|
3852
3651
|
})], LarkCliMissingForInstalledLarkPluginRule);
|
|
3853
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 → 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
|
|
3854
3764
|
//#region src/check.ts
|
|
3855
3765
|
/** Telemetry-aware entry: returns both the legacy CheckResult (for stdout)
|
|
3856
3766
|
* AND a DoctorReport-shape payload (for `openclaw.report_cli_run`). The
|
|
@@ -4314,9 +4224,6 @@ const PROVIDER_FILE_PATH = "/home/gem/workspace/.force/openclaw/miaoda-provider-
|
|
|
4314
4224
|
const SECRETS_FILE_PATH = "/home/gem/workspace/.force/openclaw/miaoda-openclaw-secrets.json";
|
|
4315
4225
|
/** Absolute path to the openclaw config JSON. */
|
|
4316
4226
|
const CONFIG_PATH = `${WORKSPACE_DIR}/openclaw.json`;
|
|
4317
|
-
function upgradeLarkLogFile(runId) {
|
|
4318
|
-
return `${DIAGNOSE_DIR}/upgrade-lark-${(/* @__PURE__ */ new Date()).toISOString().slice(0, 19).replace(/:/g, "-")}-${runId.slice(0, 8)}.log`;
|
|
4319
|
-
}
|
|
4320
4227
|
//#endregion
|
|
4321
4228
|
//#region src/run-log.ts
|
|
4322
4229
|
let currentRunContext;
|
|
@@ -4453,9 +4360,10 @@ function makeLogger(logFile) {
|
|
|
4453
4360
|
/**
|
|
4454
4361
|
* Start an async reset task: spawn a detached child process and return the taskId.
|
|
4455
4362
|
*
|
|
4456
|
-
* 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.
|
|
4457
4365
|
*/
|
|
4458
|
-
function startAsyncReset(
|
|
4366
|
+
function startAsyncReset() {
|
|
4459
4367
|
const taskId = (0, node_crypto.randomUUID)();
|
|
4460
4368
|
const resultFile = resetResultFile(taskId);
|
|
4461
4369
|
const log = makeLogger(resetLogFile(taskId));
|
|
@@ -4479,8 +4387,7 @@ function startAsyncReset(ctxBase64) {
|
|
|
4479
4387
|
process.argv[1],
|
|
4480
4388
|
"reset",
|
|
4481
4389
|
"--worker",
|
|
4482
|
-
`--task-id=${taskId}
|
|
4483
|
-
`--ctx=${ctxBase64}`
|
|
4390
|
+
`--task-id=${taskId}`
|
|
4484
4391
|
], {
|
|
4485
4392
|
detached: true,
|
|
4486
4393
|
stdio: "ignore",
|
|
@@ -6994,6 +6901,60 @@ function mergeCoreBackupAndOrigins(configPath, vars, resetData, log) {
|
|
|
6994
6901
|
log(`allowedOrigins: added ${added.length} (${JSON.stringify(added)}), total now ${mergedOrigins.length}`);
|
|
6995
6902
|
}
|
|
6996
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
|
+
/**
|
|
6997
6958
|
* Step 7: Verify startup scripts landed in configDir/scripts/.
|
|
6998
6959
|
*
|
|
6999
6960
|
* Scripts are extracted directly to configDir/scripts/ during stageTemplate —
|
|
@@ -7138,6 +7099,7 @@ async function runReset(input, taskId, resultFile) {
|
|
|
7138
7099
|
await step5InstallOpenclaw(openclawTag, ossFileMap, log);
|
|
7139
7100
|
step(6);
|
|
7140
7101
|
mergeCoreBackupAndOrigins(configPath, vars, resetData, log);
|
|
7102
|
+
fixBotChannelConfig(configPath, vars.larkApps, log);
|
|
7141
7103
|
step(7);
|
|
7142
7104
|
verifyStartupScripts(configDir, log);
|
|
7143
7105
|
step(8);
|
|
@@ -7936,7 +7898,8 @@ function normalizeCtx(raw) {
|
|
|
7936
7898
|
reset: {
|
|
7937
7899
|
templateVars: r.reset.templateVars ?? {},
|
|
7938
7900
|
coreBackup: r.reset.coreBackup
|
|
7939
|
-
}
|
|
7901
|
+
},
|
|
7902
|
+
larkApps: Array.isArray(r.larkApps) ? r.larkApps : []
|
|
7940
7903
|
};
|
|
7941
7904
|
}
|
|
7942
7905
|
const vars = r.vars ?? {};
|
|
@@ -7961,7 +7924,8 @@ function normalizeCtx(raw) {
|
|
|
7961
7924
|
reset: {
|
|
7962
7925
|
templateVars: resetData.templateVars ?? {},
|
|
7963
7926
|
coreBackup: resetData.coreBackup
|
|
7964
|
-
}
|
|
7927
|
+
},
|
|
7928
|
+
larkApps: Array.isArray(r.larkApps) ? r.larkApps : []
|
|
7965
7929
|
};
|
|
7966
7930
|
}
|
|
7967
7931
|
function fillApp(src) {
|
|
@@ -8026,7 +7990,8 @@ function buildCheckInput(raw, configPathOverride) {
|
|
|
8026
7990
|
providerFilePath: PROVIDER_FILE_PATH,
|
|
8027
7991
|
secretsFilePath: SECRETS_FILE_PATH,
|
|
8028
7992
|
templateVars: ctx.app.templateVars,
|
|
8029
|
-
recommendedOpenclawTag: ctx.app.recommendedOpenclawTag
|
|
7993
|
+
recommendedOpenclawTag: ctx.app.recommendedOpenclawTag,
|
|
7994
|
+
larkApps: ctx.larkApps
|
|
8030
7995
|
},
|
|
8031
7996
|
templateVars: ctx.app.templateVars
|
|
8032
7997
|
};
|
|
@@ -8058,7 +8023,8 @@ function buildRepairInput(raw, configPathOverride) {
|
|
|
8058
8023
|
providerFilePath: PROVIDER_FILE_PATH,
|
|
8059
8024
|
secretsFilePath: SECRETS_FILE_PATH,
|
|
8060
8025
|
templateVars: ctx.app.templateVars,
|
|
8061
|
-
recommendedOpenclawTag: ctx.app.recommendedOpenclawTag
|
|
8026
|
+
recommendedOpenclawTag: ctx.app.recommendedOpenclawTag,
|
|
8027
|
+
larkApps: ctx.larkApps
|
|
8062
8028
|
},
|
|
8063
8029
|
repairData: {
|
|
8064
8030
|
secretsContent: ctx.secrets.secretsContent,
|
|
@@ -8094,7 +8060,8 @@ function buildResetInput(raw, configPathOverride) {
|
|
|
8094
8060
|
providerFilePath: PROVIDER_FILE_PATH,
|
|
8095
8061
|
secretsFilePath: SECRETS_FILE_PATH,
|
|
8096
8062
|
templateVars: ctx.app.templateVars,
|
|
8097
|
-
recommendedOpenclawTag: ctx.app.recommendedOpenclawTag
|
|
8063
|
+
recommendedOpenclawTag: ctx.app.recommendedOpenclawTag,
|
|
8064
|
+
larkApps: ctx.larkApps
|
|
8098
8065
|
},
|
|
8099
8066
|
resetData: {
|
|
8100
8067
|
templateVars: ctx.reset.templateVars,
|
|
@@ -10404,7 +10371,7 @@ async function reportCliRun(opts) {
|
|
|
10404
10371
|
//#region src/help.ts
|
|
10405
10372
|
const BIN = "mclaw-diagnose";
|
|
10406
10373
|
function versionBanner() {
|
|
10407
|
-
return `v0.1.14-alpha.
|
|
10374
|
+
return `v0.1.14-alpha.11`;
|
|
10408
10375
|
}
|
|
10409
10376
|
const COMMANDS = [
|
|
10410
10377
|
{
|
|
@@ -10508,16 +10475,12 @@ EXIT CODES
|
|
|
10508
10475
|
hidden: true,
|
|
10509
10476
|
summary: "Run rule-engine check only",
|
|
10510
10477
|
help: `USAGE
|
|
10511
|
-
${BIN} check
|
|
10478
|
+
${BIN} check
|
|
10512
10479
|
|
|
10513
10480
|
DESCRIPTION
|
|
10514
10481
|
Runs the rule engine against the sandbox's current openclaw config and
|
|
10515
|
-
returns { failedRules }.
|
|
10516
|
-
|
|
10517
|
-
|
|
10518
|
-
OPTIONS
|
|
10519
|
-
--ctx=<base64> Opaque ctx JSON (base64). When absent, fetched from
|
|
10520
|
-
innerapi (same path as doctor).
|
|
10482
|
+
returns { failedRules }. Ctx is fetched from innerapi automatically.
|
|
10483
|
+
End-users should prefer \`doctor\`.
|
|
10521
10484
|
`
|
|
10522
10485
|
},
|
|
10523
10486
|
{
|
|
@@ -10525,16 +10488,11 @@ OPTIONS
|
|
|
10525
10488
|
hidden: true,
|
|
10526
10489
|
summary: "Apply standard-mode repairs",
|
|
10527
10490
|
help: `USAGE
|
|
10528
|
-
${BIN} repair
|
|
10491
|
+
${BIN} repair
|
|
10529
10492
|
|
|
10530
10493
|
DESCRIPTION
|
|
10531
|
-
Runs repair for the failing rules
|
|
10532
|
-
|
|
10533
|
-
\`doctor --fix\` instead.
|
|
10534
|
-
|
|
10535
|
-
OPTIONS
|
|
10536
|
-
--ctx=<base64> Opaque ctx JSON (base64). When absent, fetched from
|
|
10537
|
-
innerapi.
|
|
10494
|
+
Runs repair for the failing rules. Ctx is fetched from innerapi
|
|
10495
|
+
automatically. End-users should use \`doctor --fix\` instead.
|
|
10538
10496
|
`
|
|
10539
10497
|
},
|
|
10540
10498
|
{
|
|
@@ -10542,14 +10500,15 @@ OPTIONS
|
|
|
10542
10500
|
hidden: true,
|
|
10543
10501
|
summary: "Re-initialize sandbox via the 9-step reset pipeline",
|
|
10544
10502
|
help: `USAGE
|
|
10545
|
-
${BIN} reset --async
|
|
10546
|
-
${BIN} reset --worker --task-id=<id>
|
|
10503
|
+
${BIN} reset --async
|
|
10504
|
+
${BIN} reset --worker --task-id=<id>
|
|
10547
10505
|
|
|
10548
10506
|
DESCRIPTION
|
|
10549
10507
|
Two-phase pipeline driven asynchronously: the --async invocation spawns
|
|
10550
10508
|
a detached worker and returns { taskId } immediately; the --worker
|
|
10551
10509
|
invocation (spawned by --async) runs the actual 9 steps and writes
|
|
10552
10510
|
progress to /tmp/openclaw-diagnose/reset-<taskId>.json.
|
|
10511
|
+
Ctx is fetched from innerapi automatically.
|
|
10553
10512
|
|
|
10554
10513
|
Poll progress with \`${BIN} get_reset_task --task-id=<id>\`.
|
|
10555
10514
|
|
|
@@ -10557,7 +10516,6 @@ OPTIONS
|
|
|
10557
10516
|
--async Start a detached worker and return taskId on stdout.
|
|
10558
10517
|
--worker Internal — run the 9-step pipeline (launched by --async).
|
|
10559
10518
|
--task-id=<id> Required with --worker; identifies the progress file.
|
|
10560
|
-
--ctx=<base64> Opaque ctx JSON; fetched from innerapi when absent.
|
|
10561
10519
|
`
|
|
10562
10520
|
},
|
|
10563
10521
|
{
|
|
@@ -10580,7 +10538,7 @@ OPTIONS
|
|
|
10580
10538
|
hidden: true,
|
|
10581
10539
|
summary: "Download + install the openclaw tarball",
|
|
10582
10540
|
help: `USAGE
|
|
10583
|
-
${BIN} install-openclaw <tag> [--
|
|
10541
|
+
${BIN} install-openclaw <tag> [--oss_file_map=<base64>]
|
|
10584
10542
|
|
|
10585
10543
|
DESCRIPTION
|
|
10586
10544
|
Downloads the openclaw@<tag> tgz via the signed OSS URL found in the
|
|
@@ -10592,9 +10550,9 @@ ARGUMENTS
|
|
|
10592
10550
|
<tag> Openclaw version tag, e.g. 2026.4.11.
|
|
10593
10551
|
|
|
10594
10552
|
OPTIONS
|
|
10595
|
-
--ctx=<base64> Opaque ctx; ossFileMap is extracted from it.
|
|
10596
10553
|
--oss_file_map=... Pre-built OSS URL map (base64 JSON); skips innerapi
|
|
10597
|
-
entirely.
|
|
10554
|
+
entirely. When absent, ossFileMap is fetched from
|
|
10555
|
+
innerapi automatically.
|
|
10598
10556
|
`
|
|
10599
10557
|
},
|
|
10600
10558
|
{
|
|
@@ -10620,8 +10578,7 @@ OPTIONS
|
|
|
10620
10578
|
--home_base=<dir> Override the /home/gem base (tests).
|
|
10621
10579
|
--config_path=<p> Override the openclaw.json path (tests).
|
|
10622
10580
|
--skip-config-update Leave plugins.installs in openclaw.json untouched.
|
|
10623
|
-
--
|
|
10624
|
-
--oss_file_map=... Pre-built OSS URL map (base64 JSON).
|
|
10581
|
+
--oss_file_map=... Pre-built OSS URL map (base64 JSON); skips innerapi.
|
|
10625
10582
|
`
|
|
10626
10583
|
},
|
|
10627
10584
|
{
|
|
@@ -10648,7 +10605,6 @@ OPTIONS
|
|
|
10648
10605
|
--cli=<name> CLI package to install by short name or scoped
|
|
10649
10606
|
packageName (repeatable, at least one required).
|
|
10650
10607
|
--home_base=<dir> Override the /home/gem base (tests).
|
|
10651
|
-
--ctx=<base64> Opaque ctx; ossFileMap is extracted from it.
|
|
10652
10608
|
--oss_file_map=... Pre-built OSS URL map (base64 JSON); skips innerapi.
|
|
10653
10609
|
|
|
10654
10610
|
EXAMPLES
|
|
@@ -10702,46 +10658,6 @@ OPTIONS
|
|
|
10702
10658
|
EXIT CODES
|
|
10703
10659
|
0 Success or skipped (prerequisites not met).
|
|
10704
10660
|
1 Secret/path unresolvable, lark-cli failed, or config unreadable.
|
|
10705
|
-
`
|
|
10706
|
-
},
|
|
10707
|
-
{
|
|
10708
|
-
name: "upgrade-lark",
|
|
10709
|
-
hidden: false,
|
|
10710
|
-
summary: "Upgrade the Feishu/Lark plugin via @larksuite/openclaw-lark-tools",
|
|
10711
|
-
help: `USAGE
|
|
10712
|
-
${BIN} upgrade-lark [--scene=<scene>] [--caller=<n>] [--trace-id=<id>]
|
|
10713
|
-
|
|
10714
|
-
DESCRIPTION
|
|
10715
|
-
Upgrades the Feishu/Lark plugin by running:
|
|
10716
|
-
npx -y @larksuite/openclaw-lark-tools update --use-existing
|
|
10717
|
-
|
|
10718
|
-
Before the upgrade, the following files are backed up:
|
|
10719
|
-
- openclaw.json
|
|
10720
|
-
- extensions/openclaw-lark/ (if present)
|
|
10721
|
-
- extensions/feishu-openclaw-plugin/ (if present)
|
|
10722
|
-
After the upgrade, the result is validated:
|
|
10723
|
-
- feishu.accounts bot count must not decrease
|
|
10724
|
-
- gateway config structure must remain valid (port/mode/bind/auth/trustedProxies)
|
|
10725
|
-
If the upgrade command fails, or validation fails, the backed-up files are
|
|
10726
|
-
restored to roll back the changes.
|
|
10727
|
-
|
|
10728
|
-
Execution is logged to /tmp/openclaw-diagnose/upgrade-lark-<runId>.log.
|
|
10729
|
-
|
|
10730
|
-
Output is a single JSON object on stdout:
|
|
10731
|
-
{ "ok": true, "stdout": "...", "stderr": "...", "logFile": "..." }
|
|
10732
|
-
{ "ok": false, "error": "...", "stderr": "...", "exitCode": 1,
|
|
10733
|
-
"rollbackOk": true, "validationError": "...", "logFile": "..." }
|
|
10734
|
-
|
|
10735
|
-
OPTIONS
|
|
10736
|
-
--scene=<scene> Telemetry label forwarded to Slardar only.
|
|
10737
|
-
Known values: PageUpgradeLark, etc. Custom strings accepted.
|
|
10738
|
-
--caller=<name> Optional metadata passed to innerapi.
|
|
10739
|
-
--trace-id=<id> Optional log-correlation id.
|
|
10740
|
-
|
|
10741
|
-
EXIT CODES
|
|
10742
|
-
0 Success: upgrade ran and all validations passed.
|
|
10743
|
-
1 Failure: npx error, validation failed, or git commit failed.
|
|
10744
|
-
File rollback was attempted (see rollbackOk in the JSON output).
|
|
10745
10661
|
`
|
|
10746
10662
|
},
|
|
10747
10663
|
{
|
|
@@ -10775,41 +10691,6 @@ EXAMPLES
|
|
|
10775
10691
|
${BIN} rules # all rules
|
|
10776
10692
|
${BIN} rules --rule=gateway # single rule
|
|
10777
10693
|
${BIN} rules --rule=gateway --rule=feishu_channel # multiple rules
|
|
10778
|
-
`
|
|
10779
|
-
},
|
|
10780
|
-
{
|
|
10781
|
-
name: "channels-probe",
|
|
10782
|
-
hidden: true,
|
|
10783
|
-
summary: "Check feishu channel health via openclaw channels status --probe",
|
|
10784
|
-
help: `USAGE
|
|
10785
|
-
${BIN} channels-probe [--timeout=<ms>]
|
|
10786
|
-
|
|
10787
|
-
DESCRIPTION
|
|
10788
|
-
Runs \`openclaw channels status --probe\` and returns a structured JSON
|
|
10789
|
-
summary of whether the current environment's feishu channels are
|
|
10790
|
-
configured and working correctly.
|
|
10791
|
-
|
|
10792
|
-
Output:
|
|
10793
|
-
{
|
|
10794
|
-
"available": true,
|
|
10795
|
-
"gatewayReachable": true,
|
|
10796
|
-
"accounts": [
|
|
10797
|
-
{ "id": "default", "bits": ["enabled","configured","running","works"],
|
|
10798
|
-
"isWorking": true, "raw": "- Feishu default: ..." }
|
|
10799
|
-
],
|
|
10800
|
-
"anyAccountWorking": true
|
|
10801
|
-
}
|
|
10802
|
-
|
|
10803
|
-
An account is considered working when:
|
|
10804
|
-
enabled ∧ configured ∧ ( works ∨ ( running ∧ no error: ∧ no probe failed ) )
|
|
10805
|
-
|
|
10806
|
-
"available": false means the CLI invocation itself failed (openclaw not
|
|
10807
|
-
found, gateway unreachable, or no parseable output returned).
|
|
10808
|
-
|
|
10809
|
-
OPTIONS
|
|
10810
|
-
--timeout=<ms> Max wait in milliseconds (default: 60000). The probe
|
|
10811
|
-
can hang indefinitely on openclaw v2026.4.x due to a
|
|
10812
|
-
missing per-request HTTP timeout — set this accordingly.
|
|
10813
10694
|
`
|
|
10814
10695
|
},
|
|
10815
10696
|
{
|
|
@@ -10830,8 +10711,7 @@ OPTIONS
|
|
|
10830
10711
|
--role=<role> Package role (e.g. template, config).
|
|
10831
10712
|
--name=<name> Package name within the role.
|
|
10832
10713
|
--dir=<dir> Target dir (defaults to dirname(pkg.installPath)).
|
|
10833
|
-
--
|
|
10834
|
-
--oss_file_map=... Pre-built OSS URL map (base64 JSON).
|
|
10714
|
+
--oss_file_map=... Pre-built OSS URL map (base64 JSON); skips innerapi.
|
|
10835
10715
|
`
|
|
10836
10716
|
}
|
|
10837
10717
|
];
|
|
@@ -10907,31 +10787,31 @@ function planVarsFields(opts = {}) {
|
|
|
10907
10787
|
*
|
|
10908
10788
|
* Per-command group needs:
|
|
10909
10789
|
*
|
|
10910
|
-
* doctor / check app
|
|
10911
|
-
* repair app + secrets
|
|
10912
|
-
* reset app + secrets + install + reset
|
|
10790
|
+
* doctor / check app + larkApps
|
|
10791
|
+
* repair app + secrets + larkApps
|
|
10792
|
+
* reset app + secrets + install + reset + larkApps
|
|
10913
10793
|
* install-* install only
|
|
10914
10794
|
*
|
|
10915
10795
|
* Empty result (`{}`) means "no group needed" — the CLI can skip the
|
|
10916
10796
|
* `fetchCtxViaInnerApi` call entirely and run with a synthetic empty ctx.
|
|
10917
|
-
* Happens e.g. when the user pinned `--rule=<key>` to a vars-free rule on
|
|
10918
|
-
* `doctor`.
|
|
10919
10797
|
*/
|
|
10920
10798
|
function planCtxPopulate(opts) {
|
|
10921
10799
|
if (opts.command === "install") return { install: true };
|
|
10922
10800
|
const populate = {};
|
|
10923
|
-
|
|
10801
|
+
if (planVarsFields({
|
|
10924
10802
|
disabled: opts.disabled,
|
|
10925
10803
|
onlyRules: opts.onlyRules,
|
|
10926
10804
|
profile: opts.profile
|
|
10927
|
-
});
|
|
10928
|
-
if (
|
|
10929
|
-
|
|
10930
|
-
|
|
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") {
|
|
10931
10810
|
populate.secrets = true;
|
|
10932
10811
|
populate.install = true;
|
|
10933
10812
|
populate.reset = true;
|
|
10934
|
-
|
|
10813
|
+
populate.larkApps = true;
|
|
10814
|
+
} else if (opts.command === "doctor" || opts.command === "check") populate.larkApps = true;
|
|
10935
10815
|
return populate;
|
|
10936
10816
|
}
|
|
10937
10817
|
//#endregion
|
|
@@ -10985,372 +10865,11 @@ function reportDoctorRunToSlardar(opts) {
|
|
|
10985
10865
|
}
|
|
10986
10866
|
});
|
|
10987
10867
|
}
|
|
10988
|
-
function readLogFile(filePath) {
|
|
10989
|
-
try {
|
|
10990
|
-
return node_fs.default.readFileSync(filePath, "utf-8");
|
|
10991
|
-
} catch {
|
|
10992
|
-
return "";
|
|
10993
|
-
}
|
|
10994
|
-
}
|
|
10995
|
-
function reportUpgradeLarkToSlardar(opts) {
|
|
10996
|
-
console.error(`[slardar] upgrade_lark_run scene=${opts.scene ?? ""} success=${opts.success} exitCode=${opts.exitCode ?? ""} rollbackOk=${opts.rollbackOk ?? ""}`);
|
|
10997
|
-
const logContent = readLogFile(opts.logFile);
|
|
10998
|
-
reportTask({
|
|
10999
|
-
eventName: "upgrade_lark_run",
|
|
11000
|
-
durationMs: opts.durationMs,
|
|
11001
|
-
status: opts.success ? "success" : "failed",
|
|
11002
|
-
extraCategories: {
|
|
11003
|
-
scene: opts.scene ?? "",
|
|
11004
|
-
exit_code: String(opts.exitCode ?? ""),
|
|
11005
|
-
rollback_ok: opts.rollbackOk != null ? String(opts.rollbackOk) : "",
|
|
11006
|
-
validation_error: opts.validationError ?? "",
|
|
11007
|
-
error_msg: opts.error ?? "",
|
|
11008
|
-
log_content: logContent
|
|
11009
|
-
}
|
|
11010
|
-
});
|
|
11011
|
-
}
|
|
11012
|
-
//#endregion
|
|
11013
|
-
//#region src/upgrade-lark.ts
|
|
11014
|
-
/** Plugin directories under extensions/ that are backed up before upgrade */
|
|
11015
|
-
const FEISHU_PLUGIN_DIRS = ["openclaw-lark", "feishu-openclaw-plugin"];
|
|
11016
|
-
function backupFiles(opts) {
|
|
11017
|
-
const { workspaceDir, configPath, backupDir, log } = opts;
|
|
11018
|
-
try {
|
|
11019
|
-
node_fs.default.mkdirSync(backupDir, { recursive: true });
|
|
11020
|
-
log(`backup dir: ${backupDir}`);
|
|
11021
|
-
if (node_fs.default.existsSync(configPath)) {
|
|
11022
|
-
const stat = node_fs.default.statSync(configPath);
|
|
11023
|
-
node_fs.default.copyFileSync(configPath, node_path.default.join(backupDir, "openclaw.json"));
|
|
11024
|
-
log(` backed up: openclaw.json (${stat.size} bytes)`);
|
|
11025
|
-
} else log(` skipped: openclaw.json (not found)`);
|
|
11026
|
-
const extSrc = node_path.default.join(workspaceDir, "extensions");
|
|
11027
|
-
for (const pluginDir of FEISHU_PLUGIN_DIRS) {
|
|
11028
|
-
const src = node_path.default.join(extSrc, pluginDir);
|
|
11029
|
-
if (node_fs.default.existsSync(src)) {
|
|
11030
|
-
const dst = node_path.default.join(backupDir, "extensions", pluginDir);
|
|
11031
|
-
node_fs.default.cpSync(src, dst, { recursive: true });
|
|
11032
|
-
const version = readPkgVersion(node_path.default.join(src, "package.json"));
|
|
11033
|
-
log(` backed up: extensions/${pluginDir}${version ? ` (version: ${version})` : ""}`);
|
|
11034
|
-
} else log(` skipped: extensions/${pluginDir} (not found)`);
|
|
11035
|
-
}
|
|
11036
|
-
return { ok: true };
|
|
11037
|
-
} catch (e) {
|
|
11038
|
-
return {
|
|
11039
|
-
ok: false,
|
|
11040
|
-
error: `backup failed: ${e.message}`
|
|
11041
|
-
};
|
|
11042
|
-
}
|
|
11043
|
-
}
|
|
11044
|
-
function restoreFiles(opts) {
|
|
11045
|
-
const { workspaceDir, configPath, backupDir, log } = opts;
|
|
11046
|
-
try {
|
|
11047
|
-
const configBackup = node_path.default.join(backupDir, "openclaw.json");
|
|
11048
|
-
if (node_fs.default.existsSync(configBackup)) {
|
|
11049
|
-
node_fs.default.copyFileSync(configBackup, configPath);
|
|
11050
|
-
log(` restored: openclaw.json`);
|
|
11051
|
-
}
|
|
11052
|
-
const extDst = node_path.default.join(workspaceDir, "extensions");
|
|
11053
|
-
for (const pluginDir of FEISHU_PLUGIN_DIRS) {
|
|
11054
|
-
const backupSrc = node_path.default.join(backupDir, "extensions", pluginDir);
|
|
11055
|
-
if (node_fs.default.existsSync(backupSrc)) {
|
|
11056
|
-
const dst = node_path.default.join(extDst, pluginDir);
|
|
11057
|
-
if (node_fs.default.existsSync(dst)) node_fs.default.rmSync(dst, {
|
|
11058
|
-
recursive: true,
|
|
11059
|
-
force: true
|
|
11060
|
-
});
|
|
11061
|
-
node_fs.default.cpSync(backupSrc, dst, { recursive: true });
|
|
11062
|
-
log(` restored: extensions/${pluginDir}`);
|
|
11063
|
-
}
|
|
11064
|
-
}
|
|
11065
|
-
return true;
|
|
11066
|
-
} catch (e) {
|
|
11067
|
-
log(` restore error: ${e.message}`);
|
|
11068
|
-
return false;
|
|
11069
|
-
}
|
|
11070
|
-
}
|
|
11071
|
-
function readPkgVersion(pkgPath) {
|
|
11072
|
-
try {
|
|
11073
|
-
const pkg = JSON.parse(node_fs.default.readFileSync(pkgPath, "utf-8"));
|
|
11074
|
-
return typeof pkg.version === "string" ? pkg.version : null;
|
|
11075
|
-
} catch {
|
|
11076
|
-
return null;
|
|
11077
|
-
}
|
|
11078
|
-
}
|
|
11079
|
-
function snapshotVersions(cwd, log) {
|
|
11080
|
-
const ocResult = (0, node_child_process.spawnSync)("openclaw", ["--version"], {
|
|
11081
|
-
cwd,
|
|
11082
|
-
encoding: "utf-8",
|
|
11083
|
-
stdio: [
|
|
11084
|
-
"ignore",
|
|
11085
|
-
"pipe",
|
|
11086
|
-
"pipe"
|
|
11087
|
-
],
|
|
11088
|
-
timeout: 5e3
|
|
11089
|
-
});
|
|
11090
|
-
const ocRaw = (ocResult.stdout ?? "").trim() || (ocResult.stderr ?? "").trim();
|
|
11091
|
-
const extDir = node_path.default.join(cwd, "extensions");
|
|
11092
|
-
const larkPkg = node_path.default.join(extDir, "openclaw-lark", "package.json");
|
|
11093
|
-
const feishuPkg = node_path.default.join(extDir, "feishu-openclaw-plugin", "package.json");
|
|
11094
|
-
log(` version-check paths: ${larkPkg} [${node_fs.default.existsSync(larkPkg) ? "exists" : "missing"}]`);
|
|
11095
|
-
log(` version-check paths: ${feishuPkg} [${node_fs.default.existsSync(feishuPkg) ? "exists" : "missing"}]`);
|
|
11096
|
-
return {
|
|
11097
|
-
openclaw: ocRaw || null,
|
|
11098
|
-
openclawLark: readPkgVersion(larkPkg),
|
|
11099
|
-
feishuOpenclawPlugin: readPkgVersion(feishuPkg)
|
|
11100
|
-
};
|
|
11101
|
-
}
|
|
11102
|
-
function logVersionSnapshot(label, v, log) {
|
|
11103
|
-
log(`${label}: openclaw=${v.openclaw ?? "n/a"} openclaw-lark=${v.openclawLark ?? "n/a"} feishu-openclaw-plugin=${v.feishuOpenclawPlugin ?? "n/a"}`);
|
|
11104
|
-
}
|
|
11105
|
-
function countFeishuBots(configPath) {
|
|
11106
|
-
try {
|
|
11107
|
-
const raw = node_fs.default.readFileSync(configPath, "utf-8");
|
|
11108
|
-
const config = loadJSON5().parse(raw);
|
|
11109
|
-
const accounts = getNestedMap(config, "channels", "feishu", "accounts");
|
|
11110
|
-
if (accounts) return Object.keys(accounts).length;
|
|
11111
|
-
const feishu = getNestedMap(config, "channels", "feishu");
|
|
11112
|
-
return typeof feishu?.appId === "string" && feishu.appId ? 1 : 0;
|
|
11113
|
-
} catch {
|
|
11114
|
-
return 0;
|
|
11115
|
-
}
|
|
11116
|
-
}
|
|
11117
|
-
/** Run channels probe, log results, and return the result. Never throws. */
|
|
11118
|
-
function probeChannels(label, log, timeoutMs) {
|
|
11119
|
-
try {
|
|
11120
|
-
const r = runChannelsProbe(timeoutMs);
|
|
11121
|
-
log(` ${label} available=${r.available} anyAccountWorking=${r.anyAccountWorking}`);
|
|
11122
|
-
if (r.error) log(` ${label} error: ${r.error}`);
|
|
11123
|
-
if (r.gatewayReachable != null) log(` ${label} gatewayReachable: ${r.gatewayReachable}`);
|
|
11124
|
-
for (const acct of r.accounts ?? []) log(` ${label} account ${acct.id}: isWorking=${acct.isWorking} bits=[${acct.bits.join(",")}]`);
|
|
11125
|
-
return r;
|
|
11126
|
-
} catch (e) {
|
|
11127
|
-
log(` ${label} channels probe threw: ${e.message}`);
|
|
11128
|
-
return {
|
|
11129
|
-
available: false,
|
|
11130
|
-
gatewayReachable: false,
|
|
11131
|
-
feishuConfigInvalid: false,
|
|
11132
|
-
accounts: [],
|
|
11133
|
-
anyAccountWorking: false
|
|
11134
|
-
};
|
|
11135
|
-
}
|
|
11136
|
-
}
|
|
11137
|
-
function runUpgradeLark(opts) {
|
|
11138
|
-
const cwd = opts.cwd ?? "/home/gem/workspace/agent";
|
|
11139
|
-
const configPath = opts.configPath ?? CONFIG_PATH;
|
|
11140
|
-
const logFile = upgradeLarkLogFile(opts.runId);
|
|
11141
|
-
const log = makeLogger(logFile);
|
|
11142
|
-
const fsOpts = {
|
|
11143
|
-
workspaceDir: cwd,
|
|
11144
|
-
configPath,
|
|
11145
|
-
backupDir: node_path.default.join(opts.backupBaseDir ?? "/tmp/openclaw-diagnose", `upgrade-lark-backup-${opts.runId}`),
|
|
11146
|
-
log
|
|
11147
|
-
};
|
|
11148
|
-
const cliScript = opts.cliScript ?? process.argv[1];
|
|
11149
|
-
const statusCheckDelayMs = opts.statusCheckDelayMs ?? 5e3;
|
|
11150
|
-
log(`${"=".repeat(60)}`);
|
|
11151
|
-
log(`upgrade-lark started runId=${opts.runId}`);
|
|
11152
|
-
log(` cwd : ${cwd}`);
|
|
11153
|
-
log(` configPath : ${configPath}`);
|
|
11154
|
-
log(`${"=".repeat(60)}`);
|
|
11155
|
-
log("");
|
|
11156
|
-
log("── [Pre-check A] channels probe(升级前)────────────────");
|
|
11157
|
-
const beforeChannels = probeChannels("before", log, 6e4);
|
|
11158
|
-
log("");
|
|
11159
|
-
log("── [Pre-check B] 版本兼容预检 ───────────────────────────");
|
|
11160
|
-
let versionIncompatible = false;
|
|
11161
|
-
try {
|
|
11162
|
-
const rawConfig = node_fs.default.readFileSync(configPath, "utf-8");
|
|
11163
|
-
versionIncompatible = needsLarkUpgrade({
|
|
11164
|
-
config: loadJSON5().parse(rawConfig),
|
|
11165
|
-
configPath,
|
|
11166
|
-
vars: {},
|
|
11167
|
-
providerDeps: {
|
|
11168
|
-
usesMiaodaProvider: false,
|
|
11169
|
-
usesMiaodaSecretProvider: false
|
|
11170
|
-
}
|
|
11171
|
-
});
|
|
11172
|
-
log(` version-compat pre-check: ${versionIncompatible ? "NEEDS_UPGRADE" : "ok"}`);
|
|
11173
|
-
} catch (e) {
|
|
11174
|
-
log(` version-compat pre-check error: ${e.message} — treating as needs-upgrade`);
|
|
11175
|
-
versionIncompatible = true;
|
|
11176
|
-
}
|
|
11177
|
-
const feishuConfigInvalid = beforeChannels.feishuConfigInvalid;
|
|
11178
|
-
log(` feishu config invalid : ${feishuConfigInvalid}`);
|
|
11179
|
-
log("");
|
|
11180
|
-
log("── [Gate] 升级前置条件检查 ───────────────────────────────");
|
|
11181
|
-
log(` versionIncompatible : ${versionIncompatible}`);
|
|
11182
|
-
log(` feishuConfigInvalid : ${feishuConfigInvalid}`);
|
|
11183
|
-
log(` channels working before: ${beforeChannels.anyAccountWorking}`);
|
|
11184
|
-
if (!(versionIncompatible || feishuConfigInvalid)) {
|
|
11185
|
-
const reason = "version compatible and feishu channel config valid — upgrade not needed";
|
|
11186
|
-
log(` SKIP: ${reason}`);
|
|
11187
|
-
log(`${"=".repeat(60)}`);
|
|
11188
|
-
log("upgrade-lark skipped (pre-check gate)");
|
|
11189
|
-
log(`${"=".repeat(60)}`);
|
|
11190
|
-
return {
|
|
11191
|
-
ok: true,
|
|
11192
|
-
skipped: true,
|
|
11193
|
-
skipReason: reason,
|
|
11194
|
-
logFile
|
|
11195
|
-
};
|
|
11196
|
-
}
|
|
11197
|
-
if (beforeChannels.anyAccountWorking) {
|
|
11198
|
-
const reason = "channels are working — upgrade not needed (issue detected but system is functional)";
|
|
11199
|
-
log(` SKIP: ${reason}`);
|
|
11200
|
-
log(`${"=".repeat(60)}`);
|
|
11201
|
-
log("upgrade-lark skipped (pre-check gate)");
|
|
11202
|
-
log(`${"=".repeat(60)}`);
|
|
11203
|
-
return {
|
|
11204
|
-
ok: true,
|
|
11205
|
-
skipped: true,
|
|
11206
|
-
skipReason: reason,
|
|
11207
|
-
logFile
|
|
11208
|
-
};
|
|
11209
|
-
}
|
|
11210
|
-
log(` PROCEED: requiresLarkUpgrade=true (version=${versionIncompatible}, feishuConfig=${feishuConfigInvalid}) AND channels not working → running upgrade`);
|
|
11211
|
-
log("");
|
|
11212
|
-
log("── [1/6] 文件备份 ────────────────────────────────────────");
|
|
11213
|
-
log(`before-state: botCount=${countFeishuBots(configPath)}`);
|
|
11214
|
-
const backup = backupFiles(fsOpts);
|
|
11215
|
-
if (!backup.ok) {
|
|
11216
|
-
log(`ERROR: ${backup.error}`);
|
|
11217
|
-
return {
|
|
11218
|
-
ok: false,
|
|
11219
|
-
error: backup.error,
|
|
11220
|
-
logFile
|
|
11221
|
-
};
|
|
11222
|
-
}
|
|
11223
|
-
log("backup: ok");
|
|
11224
|
-
logVersionSnapshot("before-versions", snapshotVersions(cwd, log), log);
|
|
11225
|
-
log("");
|
|
11226
|
-
log("── [2/6] 清理本地 openclaw shim ─────────────────────────");
|
|
11227
|
-
const localOpenclawBin = node_path.default.join(cwd, "node_modules", ".bin", "openclaw");
|
|
11228
|
-
if (node_fs.default.existsSync(localOpenclawBin)) try {
|
|
11229
|
-
node_fs.default.rmSync(localOpenclawBin);
|
|
11230
|
-
log(` removed: ${localOpenclawBin}`);
|
|
11231
|
-
} catch (e) {
|
|
11232
|
-
log(` WARN: failed to remove ${localOpenclawBin}: ${e.message}`);
|
|
11233
|
-
}
|
|
11234
|
-
else log(` skipped: ${localOpenclawBin} (not found)`);
|
|
11235
|
-
log("");
|
|
11236
|
-
log("── [3/6] npx install (@larksuite/openclaw-lark-tools update) ──");
|
|
11237
|
-
const npxResult = (0, node_child_process.spawnSync)("npx", [
|
|
11238
|
-
"-y",
|
|
11239
|
-
"@larksuite/openclaw-lark-tools",
|
|
11240
|
-
"update"
|
|
11241
|
-
], {
|
|
11242
|
-
cwd,
|
|
11243
|
-
encoding: "utf-8",
|
|
11244
|
-
stdio: [
|
|
11245
|
-
"ignore",
|
|
11246
|
-
"pipe",
|
|
11247
|
-
"pipe"
|
|
11248
|
-
],
|
|
11249
|
-
timeout: 12e4
|
|
11250
|
-
});
|
|
11251
|
-
const npxStdout = npxResult.stdout?.trim() ?? "";
|
|
11252
|
-
const npxStderr = npxResult.stderr?.trim() ?? "";
|
|
11253
|
-
const npxExitCode = npxResult.status ?? 1;
|
|
11254
|
-
if (npxStdout) log(`npx stdout:\n${npxStdout}`);
|
|
11255
|
-
if (npxStderr) log(`npx stderr:\n${npxStderr}`);
|
|
11256
|
-
log(`npx exit: ${npxExitCode}${npxResult.error ? ` error: ${npxResult.error.message}` : ""}`);
|
|
11257
|
-
if (statusCheckDelayMs > 0) {
|
|
11258
|
-
log("");
|
|
11259
|
-
log(`── 等待 ${statusCheckDelayMs / 1e3}s(让 openclaw 服务完成重启) ─────────────`);
|
|
11260
|
-
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, statusCheckDelayMs);
|
|
11261
|
-
log("wait done");
|
|
11262
|
-
}
|
|
11263
|
-
const doRollback = (reason) => {
|
|
11264
|
-
log(`ERROR: ${reason}`);
|
|
11265
|
-
const rollbackOk = restoreFiles(fsOpts);
|
|
11266
|
-
log(`rollback: ${rollbackOk ? "ok" : "FAILED"}`);
|
|
11267
|
-
return {
|
|
11268
|
-
ok: false,
|
|
11269
|
-
error: reason,
|
|
11270
|
-
validationError: reason,
|
|
11271
|
-
stdout: npxStdout,
|
|
11272
|
-
stderr: npxStderr,
|
|
11273
|
-
exitCode: npxExitCode,
|
|
11274
|
-
rollbackOk,
|
|
11275
|
-
logFile
|
|
11276
|
-
};
|
|
11277
|
-
};
|
|
11278
|
-
log("");
|
|
11279
|
-
log("── [4/5] 安装后诊断校验 ─────────────────────────────────");
|
|
11280
|
-
logVersionSnapshot("after-versions", snapshotVersions(cwd, log), log);
|
|
11281
|
-
let afterVersionIncompatible = false;
|
|
11282
|
-
try {
|
|
11283
|
-
const rawConfig = node_fs.default.readFileSync(configPath, "utf-8");
|
|
11284
|
-
afterVersionIncompatible = needsLarkUpgrade({
|
|
11285
|
-
config: loadJSON5().parse(rawConfig),
|
|
11286
|
-
configPath,
|
|
11287
|
-
vars: {},
|
|
11288
|
-
providerDeps: {
|
|
11289
|
-
usesMiaodaProvider: false,
|
|
11290
|
-
usesMiaodaSecretProvider: false
|
|
11291
|
-
}
|
|
11292
|
-
});
|
|
11293
|
-
log(` version-compat post-check: ${afterVersionIncompatible ? "STILL_INCOMPATIBLE" : "ok"}`);
|
|
11294
|
-
} catch (e) {
|
|
11295
|
-
log(` version-compat post-check error: ${e.message} — treating as still-incompatible`);
|
|
11296
|
-
afterVersionIncompatible = true;
|
|
11297
|
-
}
|
|
11298
|
-
const afterChannels = probeChannels("after", log, 6e4);
|
|
11299
|
-
log(` feishu config invalid after: ${afterChannels.feishuConfigInvalid}`);
|
|
11300
|
-
const stillNeedsUpgrade = (afterVersionIncompatible || afterChannels.feishuConfigInvalid) && !afterChannels.anyAccountWorking;
|
|
11301
|
-
log(` post-check: stillNeedsUpgrade=${stillNeedsUpgrade} (version=${afterVersionIncompatible}, feishuConfig=${afterChannels.feishuConfigInvalid}, channelsWorking=${afterChannels.anyAccountWorking})`);
|
|
11302
|
-
if (stillNeedsUpgrade) return doRollback(`post-install diagnosis still shows anomaly: versionIncompatible=${afterVersionIncompatible}, feishuConfigInvalid=${afterChannels.feishuConfigInvalid}, anyAccountWorking=${afterChannels.anyAccountWorking}`);
|
|
11303
|
-
log(" post-install diagnosis: ok (upgrade conditions resolved)");
|
|
11304
|
-
log("");
|
|
11305
|
-
log("── [6/6] doctor --fix ────────────────────────────────────");
|
|
11306
|
-
const fixArgs = ["doctor", "--fix"];
|
|
11307
|
-
if (opts.scene) fixArgs.push(`--scene=${opts.scene}`);
|
|
11308
|
-
const fixResult = (0, node_child_process.spawnSync)(process.execPath, [cliScript, ...fixArgs], {
|
|
11309
|
-
cwd,
|
|
11310
|
-
encoding: "utf-8",
|
|
11311
|
-
stdio: [
|
|
11312
|
-
"ignore",
|
|
11313
|
-
"pipe",
|
|
11314
|
-
"pipe"
|
|
11315
|
-
],
|
|
11316
|
-
timeout: 6e4,
|
|
11317
|
-
env: process.env
|
|
11318
|
-
});
|
|
11319
|
-
if (fixResult.stdout?.trim()) log(`doctor(fix) stdout:\n${fixResult.stdout.trim()}`);
|
|
11320
|
-
if (fixResult.stderr?.trim()) log(`doctor(fix) stderr:\n${fixResult.stderr.trim()}`);
|
|
11321
|
-
log(`doctor(fix) exit: ${fixResult.status ?? "null"}${fixResult.error ? ` error: ${fixResult.error.message}` : ""}`);
|
|
11322
|
-
log("");
|
|
11323
|
-
log(`${"=".repeat(60)}`);
|
|
11324
|
-
log("upgrade-lark completed successfully");
|
|
11325
|
-
log(`${"=".repeat(60)}`);
|
|
11326
|
-
return {
|
|
11327
|
-
ok: true,
|
|
11328
|
-
stdout: npxStdout,
|
|
11329
|
-
stderr: npxStderr,
|
|
11330
|
-
exitCode: npxExitCode,
|
|
11331
|
-
logFile
|
|
11332
|
-
};
|
|
11333
|
-
}
|
|
11334
10868
|
//#endregion
|
|
11335
10869
|
//#region src/index.ts
|
|
11336
10870
|
const args = node_process.default.argv.slice(2);
|
|
11337
10871
|
const mode = args.find((a) => !a.startsWith("-"));
|
|
11338
10872
|
/**
|
|
11339
|
-
* Decode `--ctx=<base64>` into an opaque JSON object. Returns undefined when
|
|
11340
|
-
* the flag isn't present — the caller decides whether to fall back to the
|
|
11341
|
-
* innerapi or to error out.
|
|
11342
|
-
*
|
|
11343
|
-
* The object's shape is not enforced here; downstream code consumes it via
|
|
11344
|
-
* either `normalizeCtx()` (new path) or direct field access for the legacy
|
|
11345
|
-
* check/repair/reset contract still used by sandbox_console push.
|
|
11346
|
-
*/
|
|
11347
|
-
function parseCtxFlag(args) {
|
|
11348
|
-
const ctxArg = args.find((a) => a.startsWith("--ctx="));
|
|
11349
|
-
if (!ctxArg) return void 0;
|
|
11350
|
-
const b64 = ctxArg.slice(6);
|
|
11351
|
-
return JSON.parse(Buffer.from(b64, "base64").toString("utf-8"));
|
|
11352
|
-
}
|
|
11353
|
-
/**
|
|
11354
10873
|
* Pull the first non-flag positional after the mode name.
|
|
11355
10874
|
* (The mode itself is args[0] in the filtered set, so we skip index 0.)
|
|
11356
10875
|
*/
|
|
@@ -11378,8 +10897,8 @@ function getMultiFlag(args, name) {
|
|
|
11378
10897
|
* case but is no longer consulted.
|
|
11379
10898
|
*/
|
|
11380
10899
|
async function reportRun(command, rc, _raw, invocation, durationMs, outcome, slardar = {
|
|
11381
|
-
scene,
|
|
11382
|
-
profile,
|
|
10900
|
+
scene: void 0,
|
|
10901
|
+
profile: "standard",
|
|
11383
10902
|
fix: false
|
|
11384
10903
|
}) {
|
|
11385
10904
|
console.error(`${command}: telemetry calling report_cli_run`);
|
|
@@ -11443,7 +10962,7 @@ async function main() {
|
|
|
11443
10962
|
console.error(`${mode}: begin argv=[${args.join(" ")}] version=${getVersion()} traceId=${traceId ?? "-"} caller=${caller ?? "-"} runIdGenerated=${rc.generated}`);
|
|
11444
10963
|
switch (mode) {
|
|
11445
10964
|
case "check": {
|
|
11446
|
-
const raw =
|
|
10965
|
+
const raw = await fetchCtxViaInnerApi({
|
|
11447
10966
|
populate: planCtxPopulate({
|
|
11448
10967
|
command: "check",
|
|
11449
10968
|
profile
|
|
@@ -11468,7 +10987,7 @@ async function main() {
|
|
|
11468
10987
|
break;
|
|
11469
10988
|
}
|
|
11470
10989
|
case "repair": {
|
|
11471
|
-
const raw =
|
|
10990
|
+
const raw = await fetchCtxViaInnerApi({
|
|
11472
10991
|
populate: planCtxPopulate({
|
|
11473
10992
|
command: "repair",
|
|
11474
10993
|
profile
|
|
@@ -11539,27 +11058,15 @@ async function main() {
|
|
|
11539
11058
|
break;
|
|
11540
11059
|
}
|
|
11541
11060
|
case "reset":
|
|
11542
|
-
if (args.includes("--async"))
|
|
11543
|
-
|
|
11544
|
-
let ctxBase64;
|
|
11545
|
-
if (ctxArg) ctxBase64 = ctxArg.slice(6);
|
|
11546
|
-
else {
|
|
11547
|
-
const fetched = await fetchCtxViaInnerApi({
|
|
11548
|
-
populate: planCtxPopulate({ command: "reset" }),
|
|
11549
|
-
caller,
|
|
11550
|
-
traceId
|
|
11551
|
-
});
|
|
11552
|
-
ctxBase64 = Buffer.from(JSON.stringify(fetched), "utf-8").toString("base64");
|
|
11553
|
-
}
|
|
11554
|
-
console.log(JSON.stringify(startAsyncReset(ctxBase64)));
|
|
11555
|
-
} else if (args.includes("--worker")) {
|
|
11061
|
+
if (args.includes("--async")) console.log(JSON.stringify(startAsyncReset()));
|
|
11062
|
+
else if (args.includes("--worker")) {
|
|
11556
11063
|
const taskId = args.find((a) => a.startsWith("--task-id="))?.slice(10);
|
|
11557
11064
|
if (!taskId) {
|
|
11558
11065
|
console.error("Error: --task-id=<id> is required for worker");
|
|
11559
11066
|
node_process.default.exit(1);
|
|
11560
11067
|
}
|
|
11561
11068
|
const resultFile = resetResultFile(taskId);
|
|
11562
|
-
const raw =
|
|
11069
|
+
const raw = await fetchCtxViaInnerApi({
|
|
11563
11070
|
populate: planCtxPopulate({ command: "reset" }),
|
|
11564
11071
|
caller,
|
|
11565
11072
|
traceId
|
|
@@ -11583,7 +11090,7 @@ async function main() {
|
|
|
11583
11090
|
return;
|
|
11584
11091
|
}
|
|
11585
11092
|
} else {
|
|
11586
|
-
console.error("Usage: reset --async
|
|
11093
|
+
console.error("Usage: reset --async | reset --worker --task-id=<id>");
|
|
11587
11094
|
node_process.default.exit(1);
|
|
11588
11095
|
}
|
|
11589
11096
|
break;
|
|
@@ -11599,14 +11106,14 @@ async function main() {
|
|
|
11599
11106
|
case "install-openclaw": {
|
|
11600
11107
|
const tag = getPositionalTag(args, "install-openclaw");
|
|
11601
11108
|
if (!tag) {
|
|
11602
|
-
console.error("Usage: install-openclaw <tag> [--
|
|
11109
|
+
console.error("Usage: install-openclaw <tag> [--oss_file_map=<base64>]");
|
|
11603
11110
|
node_process.default.exit(1);
|
|
11604
11111
|
}
|
|
11605
11112
|
const ossFileMapFlag = getFlag(args, "oss_file_map");
|
|
11606
11113
|
let installOssFileMap;
|
|
11607
11114
|
let rawForTelemetry;
|
|
11608
11115
|
if (!ossFileMapFlag) {
|
|
11609
|
-
rawForTelemetry =
|
|
11116
|
+
rawForTelemetry = await fetchCtxViaInnerApi({
|
|
11610
11117
|
populate: planCtxPopulate({ command: "install" }),
|
|
11611
11118
|
caller,
|
|
11612
11119
|
traceId
|
|
@@ -11641,7 +11148,7 @@ async function main() {
|
|
|
11641
11148
|
case "install-extension": {
|
|
11642
11149
|
const tag = getPositionalTag(args, "install-extension");
|
|
11643
11150
|
if (!tag) {
|
|
11644
|
-
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>]");
|
|
11645
11152
|
node_process.default.exit(1);
|
|
11646
11153
|
}
|
|
11647
11154
|
const all = args.includes("--all");
|
|
@@ -11653,7 +11160,7 @@ async function main() {
|
|
|
11653
11160
|
let installOssFileMap;
|
|
11654
11161
|
let rawForTelemetry;
|
|
11655
11162
|
if (!ossFileMapFlag) {
|
|
11656
|
-
rawForTelemetry =
|
|
11163
|
+
rawForTelemetry = await fetchCtxViaInnerApi({
|
|
11657
11164
|
populate: planCtxPopulate({ command: "install" }),
|
|
11658
11165
|
caller,
|
|
11659
11166
|
traceId
|
|
@@ -11699,12 +11206,12 @@ async function main() {
|
|
|
11699
11206
|
case "install-cli": {
|
|
11700
11207
|
const tag = getPositionalTag(args, "install-cli");
|
|
11701
11208
|
if (!tag) {
|
|
11702
|
-
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>]");
|
|
11703
11210
|
node_process.default.exit(1);
|
|
11704
11211
|
}
|
|
11705
11212
|
const names = getMultiFlag(args, "cli");
|
|
11706
11213
|
if (names.length === 0) {
|
|
11707
|
-
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>]");
|
|
11708
11215
|
node_process.default.exit(1);
|
|
11709
11216
|
}
|
|
11710
11217
|
const homeBase = getFlag(args, "home_base");
|
|
@@ -11712,7 +11219,7 @@ async function main() {
|
|
|
11712
11219
|
let installOssFileMap;
|
|
11713
11220
|
let rawForTelemetry;
|
|
11714
11221
|
if (!ossFileMapFlag) {
|
|
11715
|
-
rawForTelemetry =
|
|
11222
|
+
rawForTelemetry = await fetchCtxViaInnerApi({
|
|
11716
11223
|
populate: planCtxPopulate({ command: "install" }),
|
|
11717
11224
|
caller,
|
|
11718
11225
|
traceId
|
|
@@ -11760,7 +11267,7 @@ async function main() {
|
|
|
11760
11267
|
case "download-resource": {
|
|
11761
11268
|
const tag = getPositionalTag(args, "download-resource");
|
|
11762
11269
|
if (!tag) {
|
|
11763
|
-
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>]");
|
|
11764
11271
|
node_process.default.exit(1);
|
|
11765
11272
|
}
|
|
11766
11273
|
const role = getFlag(args, "role");
|
|
@@ -11774,7 +11281,7 @@ async function main() {
|
|
|
11774
11281
|
let installOssFileMap;
|
|
11775
11282
|
let rawForTelemetry;
|
|
11776
11283
|
if (!ossFileMapFlag) {
|
|
11777
|
-
rawForTelemetry =
|
|
11284
|
+
rawForTelemetry = await fetchCtxViaInnerApi({
|
|
11778
11285
|
populate: planCtxPopulate({ command: "install" }),
|
|
11779
11286
|
caller,
|
|
11780
11287
|
traceId
|
|
@@ -11848,50 +11355,6 @@ async function main() {
|
|
|
11848
11355
|
if (!result.ok) node_process.default.exit(1);
|
|
11849
11356
|
break;
|
|
11850
11357
|
}
|
|
11851
|
-
case "upgrade-lark": {
|
|
11852
|
-
const result = runUpgradeLark({
|
|
11853
|
-
runId: rc.runId,
|
|
11854
|
-
scene
|
|
11855
|
-
});
|
|
11856
|
-
const upgradeDurationMs = Date.now() - t0;
|
|
11857
|
-
console.log(JSON.stringify(result));
|
|
11858
|
-
reportUpgradeLarkToSlardar({
|
|
11859
|
-
scene,
|
|
11860
|
-
durationMs: upgradeDurationMs,
|
|
11861
|
-
success: result.ok,
|
|
11862
|
-
logFile: result.logFile,
|
|
11863
|
-
exitCode: result.exitCode,
|
|
11864
|
-
rollbackOk: result.rollbackOk,
|
|
11865
|
-
validationError: result.validationError,
|
|
11866
|
-
error: result.error
|
|
11867
|
-
});
|
|
11868
|
-
try {
|
|
11869
|
-
await reportCliRun({
|
|
11870
|
-
command: "upgrade-lark",
|
|
11871
|
-
runId: rc.runId,
|
|
11872
|
-
version: getVersion(),
|
|
11873
|
-
invocation: args.join(" "),
|
|
11874
|
-
durationMs: upgradeDurationMs,
|
|
11875
|
-
caller: rc.caller,
|
|
11876
|
-
traceId: rc.traceId,
|
|
11877
|
-
success: result.ok,
|
|
11878
|
-
result,
|
|
11879
|
-
error: result.ok ? void 0 : { message: result.error ?? "upgrade-lark failed" }
|
|
11880
|
-
});
|
|
11881
|
-
} catch (e) {
|
|
11882
|
-
console.error(`[telemetry] reportCliRun failed: ${e.message}`);
|
|
11883
|
-
}
|
|
11884
|
-
if (!result.ok) {
|
|
11885
|
-
node_process.default.exitCode = 1;
|
|
11886
|
-
return;
|
|
11887
|
-
}
|
|
11888
|
-
break;
|
|
11889
|
-
}
|
|
11890
|
-
case "channels-probe": {
|
|
11891
|
-
const result = runChannelsProbe(getFlag(args, "timeout") ? Number(getFlag(args, "timeout")) : void 0);
|
|
11892
|
-
console.log(JSON.stringify(result));
|
|
11893
|
-
break;
|
|
11894
|
-
}
|
|
11895
11358
|
default:
|
|
11896
11359
|
node_process.default.stderr.write(`Unknown command: ${mode}\n\n`);
|
|
11897
11360
|
node_process.default.stderr.write(formatTopLevelHelp(helpFlags.expert));
|
package/package.json
CHANGED