@lark-apaas/openclaw-scripts-diagnose-cli 0.1.14-alpha.0 → 0.1.14-alpha.2
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 +855 -219
- 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.2";
|
|
56
56
|
}
|
|
57
57
|
//#endregion
|
|
58
58
|
//#region src/rule-engine/base.ts
|
|
@@ -3038,7 +3038,7 @@ function extractTarballTolerant(tarball, destDir, opts = {}) {
|
|
|
3038
3038
|
}
|
|
3039
3039
|
//#endregion
|
|
3040
3040
|
//#region src/rules/feishu-plugin-state-normalize.ts
|
|
3041
|
-
const PLUGIN_NAME$
|
|
3041
|
+
const PLUGIN_NAME$2 = "openclaw-lark";
|
|
3042
3042
|
const BUILTIN_FEISHU = "feishu";
|
|
3043
3043
|
const LEGACY_PLUGIN_NAME = "feishu-openclaw-plugin";
|
|
3044
3044
|
const LEGACY_DIRS_TO_REMOVE = [LEGACY_PLUGIN_NAME, BUILTIN_FEISHU];
|
|
@@ -3087,7 +3087,7 @@ let FeishuPluginStateNormalizeRule = class FeishuPluginStateNormalizeRule extend
|
|
|
3087
3087
|
validate(ctx) {
|
|
3088
3088
|
if (!isPluginInstalled(ctx)) return { pass: true };
|
|
3089
3089
|
const fails = [];
|
|
3090
|
-
if (!isNewPluginEnabled(ctx.config)) fails.push(`plugins.entries["${PLUGIN_NAME$
|
|
3090
|
+
if (!isNewPluginEnabled(ctx.config)) fails.push(`plugins.entries["${PLUGIN_NAME$2}"].enabled !== true(应启用)`);
|
|
3091
3091
|
if (isBuiltinFeishuEnabled(ctx.config)) fails.push("plugins.entries.feishu.enabled === true(应禁用)");
|
|
3092
3092
|
if (isTopLevelMissingFeishuTools(ctx.config)) fails.push("tools.alsoAllow 缺 feishu_* tools");
|
|
3093
3093
|
const legacyResiduals = findLegacyResiduals(ctx);
|
|
@@ -3099,7 +3099,7 @@ let FeishuPluginStateNormalizeRule = class FeishuPluginStateNormalizeRule extend
|
|
|
3099
3099
|
};
|
|
3100
3100
|
}
|
|
3101
3101
|
repair(ctx) {
|
|
3102
|
-
setEntryEnabled(ctx.config, PLUGIN_NAME$
|
|
3102
|
+
setEntryEnabled(ctx.config, PLUGIN_NAME$2, true);
|
|
3103
3103
|
setEntryEnabled(ctx.config, BUILTIN_FEISHU, false);
|
|
3104
3104
|
ensureFeishuTools(ctx.config);
|
|
3105
3105
|
cleanupLegacyResiduals(ctx);
|
|
@@ -3113,10 +3113,10 @@ FeishuPluginStateNormalizeRule = __decorate([Rule({
|
|
|
3113
3113
|
level: "critical"
|
|
3114
3114
|
})], FeishuPluginStateNormalizeRule);
|
|
3115
3115
|
function isPluginInstalled(ctx) {
|
|
3116
|
-
return node_fs.default.existsSync(node_path.default.join(getExtensionsDir(ctx.configPath), PLUGIN_NAME$
|
|
3116
|
+
return node_fs.default.existsSync(node_path.default.join(getExtensionsDir(ctx.configPath), PLUGIN_NAME$2));
|
|
3117
3117
|
}
|
|
3118
3118
|
function isNewPluginEnabled(config) {
|
|
3119
|
-
return asRecord(getNestedMap(config, "plugins", "entries")?.[PLUGIN_NAME$
|
|
3119
|
+
return asRecord(getNestedMap(config, "plugins", "entries")?.[PLUGIN_NAME$2])?.enabled === true;
|
|
3120
3120
|
}
|
|
3121
3121
|
function isBuiltinFeishuEnabled(config) {
|
|
3122
3122
|
return asRecord(getNestedMap(config, "plugins", "entries")?.[BUILTIN_FEISHU])?.enabled === true;
|
|
@@ -3169,7 +3169,7 @@ function cleanupLegacyResiduals(ctx) {
|
|
|
3169
3169
|
const allow = plugins.allow;
|
|
3170
3170
|
if (Array.isArray(allow)) {
|
|
3171
3171
|
for (let i = allow.length - 1; i >= 0; i--) if (allow[i] === LEGACY_PLUGIN_NAME) allow.splice(i, 1);
|
|
3172
|
-
if (!allow.includes(PLUGIN_NAME$
|
|
3172
|
+
if (!allow.includes(PLUGIN_NAME$2)) allow.push(PLUGIN_NAME$2);
|
|
3173
3173
|
}
|
|
3174
3174
|
}
|
|
3175
3175
|
const extDir = getExtensionsDir(ctx.configPath);
|
|
@@ -3314,7 +3314,7 @@ function findClosestEntry(pluginVersion) {
|
|
|
3314
3314
|
}
|
|
3315
3315
|
//#endregion
|
|
3316
3316
|
//#region src/rules/feishu-plugin-version-compat.ts
|
|
3317
|
-
const PLUGIN_NAME = "openclaw-lark";
|
|
3317
|
+
const PLUGIN_NAME$1 = "openclaw-lark";
|
|
3318
3318
|
const LEGACY_SHORT_NAMES = ["feishu-openclaw-plugin"];
|
|
3319
3319
|
const FORK_SCOPES = ["@lark-apaas"];
|
|
3320
3320
|
/** 特化 fork 版全名:虽免于 VERSION_COMPAT_MAP 检查,仍需 openclaw ≥ 此版本 */
|
|
@@ -3347,7 +3347,6 @@ function resolveUpgradeDirection(installed, ocCur, recommendedOc, isLegacy) {
|
|
|
3347
3347
|
/** 提取公共前置上下文;任何前置条件不满足时返回 null(规则 pass)。 */
|
|
3348
3348
|
function resolveCompatContext(ctx) {
|
|
3349
3349
|
const recommendedOc = ctx.vars.recommendedOpenclawTag;
|
|
3350
|
-
if (!recommendedOc) return null;
|
|
3351
3350
|
const ocCur = getOcVersion();
|
|
3352
3351
|
if (!ocCur) return null;
|
|
3353
3352
|
const installed = getInstalledPlugin(ctx);
|
|
@@ -3368,6 +3367,7 @@ let FeishuPluginOpenclawUpgradeRule = class FeishuPluginOpenclawUpgradeRule exte
|
|
|
3368
3367
|
validate(ctx) {
|
|
3369
3368
|
const cc = resolveCompatContext(ctx);
|
|
3370
3369
|
if (!cc) return { pass: true };
|
|
3370
|
+
if (!cc.recommendedOc) return { pass: true };
|
|
3371
3371
|
const { ocCur, recommendedOc, installed, isLegacy } = cc;
|
|
3372
3372
|
if (isForkPlugin(installed)) return validateForkPlugin(installed, ocCur, recommendedOc);
|
|
3373
3373
|
if (resolveUpgradeDirection(installed, ocCur, recommendedOc, isLegacy) !== "openclaw") return { pass: true };
|
|
@@ -3397,6 +3397,14 @@ let FeishuPluginLarkUpgradeRule = class FeishuPluginLarkUpgradeRule extends Diag
|
|
|
3397
3397
|
if (!cc) return { pass: true };
|
|
3398
3398
|
const { ocCur, recommendedOc, installed, isLegacy } = cc;
|
|
3399
3399
|
if (isForkPlugin(installed)) return { pass: true };
|
|
3400
|
+
if (!recommendedOc) {
|
|
3401
|
+
if (isLegacy || !isVersionCompatible(installed, ocCur)) return {
|
|
3402
|
+
pass: false,
|
|
3403
|
+
action: "upgrade_lark",
|
|
3404
|
+
message: `${buildCompatPrefix(installed, ocCur, isLegacy)};建议升级飞书插件至兼容版本`
|
|
3405
|
+
};
|
|
3406
|
+
return { pass: true };
|
|
3407
|
+
}
|
|
3400
3408
|
if (resolveUpgradeDirection(installed, ocCur, recommendedOc, isLegacy) !== "lark") return { pass: true };
|
|
3401
3409
|
return {
|
|
3402
3410
|
pass: false,
|
|
@@ -3483,13 +3491,13 @@ function detectInstalledPlugin(ctx) {
|
|
|
3483
3491
|
const allow = Array.isArray(allowRaw) ? allowRaw.filter((e) => typeof e === "string") : [];
|
|
3484
3492
|
const extDir = getExtensionsDir(ctx.configPath);
|
|
3485
3493
|
const installs = getNestedMap(ctx.config, "plugins", "installs");
|
|
3486
|
-
for (const name of [PLUGIN_NAME, ...LEGACY_SHORT_NAMES]) {
|
|
3494
|
+
for (const name of [PLUGIN_NAME$1, ...LEGACY_SHORT_NAMES]) {
|
|
3487
3495
|
if (!allow.includes(name)) continue;
|
|
3488
3496
|
const pkgPath = node_path.default.join(extDir, name, "package.json");
|
|
3489
3497
|
if (!node_fs.default.existsSync(pkgPath)) continue;
|
|
3490
3498
|
const pkg = readPluginPackageJson(pkgPath) ?? {};
|
|
3491
3499
|
const installEntry = installs && asRecord(installs[name]);
|
|
3492
|
-
const fullName = pkg.name ?? extractScopedNameFromSpec(installEntry?.spec);
|
|
3500
|
+
const fullName = pkg.name ?? extractScopedNameFromSpec$1(installEntry?.spec);
|
|
3493
3501
|
return {
|
|
3494
3502
|
allowName: name,
|
|
3495
3503
|
fullName,
|
|
@@ -3500,11 +3508,166 @@ function detectInstalledPlugin(ctx) {
|
|
|
3500
3508
|
return null;
|
|
3501
3509
|
}
|
|
3502
3510
|
/** "@scope/name@1.2.3" / "name@1.2.3" / "@scope/name" / "name" → 去掉 @version 后缀 */
|
|
3503
|
-
function extractScopedNameFromSpec(spec) {
|
|
3511
|
+
function extractScopedNameFromSpec$1(spec) {
|
|
3504
3512
|
if (typeof spec !== "string") return void 0;
|
|
3505
3513
|
const at = spec.indexOf("@", 1);
|
|
3506
3514
|
return at === -1 ? spec : spec.slice(0, at);
|
|
3507
3515
|
}
|
|
3516
|
+
/**
|
|
3517
|
+
* Returns true if the installed feishu plugin is version-incompatible with
|
|
3518
|
+
* the current openclaw (or is a legacy plugin that must be replaced).
|
|
3519
|
+
* Used by the upgrade_lark_needed rule and the upgrade-lark pre-check gate.
|
|
3520
|
+
*/
|
|
3521
|
+
function needsLarkUpgrade(ctx) {
|
|
3522
|
+
const cc = resolveCompatContext(ctx);
|
|
3523
|
+
if (!cc) return false;
|
|
3524
|
+
const { ocCur, recommendedOc, installed, isLegacy } = cc;
|
|
3525
|
+
if (isForkPlugin(installed)) return false;
|
|
3526
|
+
if (recommendedOc) return resolveUpgradeDirection(installed, ocCur, recommendedOc, isLegacy) === "lark";
|
|
3527
|
+
return isLegacy || !isVersionCompatible(installed, ocCur);
|
|
3528
|
+
}
|
|
3529
|
+
//#endregion
|
|
3530
|
+
//#region src/channels-probe.ts
|
|
3531
|
+
const CHANNEL_LINE_RE = /^-\s+Feishu\s+([^:]+):\s+(.+)$/;
|
|
3532
|
+
/**
|
|
3533
|
+
* Port of Python `_account_is_working` from the feishu-channel-success-rate skill.
|
|
3534
|
+
*
|
|
3535
|
+
* Strips colon-prefixed key:value bits (dm:, bot:, in:, out:, token:, allow:,
|
|
3536
|
+
* intents:, groups:, health:) and evaluates the canonical health formula.
|
|
3537
|
+
*/
|
|
3538
|
+
function accountIsWorking(bits) {
|
|
3539
|
+
const bitTokens = /* @__PURE__ */ new Set();
|
|
3540
|
+
let hasError = false;
|
|
3541
|
+
let hasProbeFailed = false;
|
|
3542
|
+
for (const raw of bits) {
|
|
3543
|
+
const b = raw.trim();
|
|
3544
|
+
if (!b) continue;
|
|
3545
|
+
if (b.startsWith("error:")) {
|
|
3546
|
+
hasError = true;
|
|
3547
|
+
continue;
|
|
3548
|
+
}
|
|
3549
|
+
if (b === "probe failed") {
|
|
3550
|
+
hasProbeFailed = true;
|
|
3551
|
+
continue;
|
|
3552
|
+
}
|
|
3553
|
+
bitTokens.add(b.split(":")[0]);
|
|
3554
|
+
}
|
|
3555
|
+
if (!bitTokens.has("enabled") || !bitTokens.has("configured")) return false;
|
|
3556
|
+
if (bitTokens.has("works")) return true;
|
|
3557
|
+
if (bitTokens.has("running") && !hasError && !hasProbeFailed) return true;
|
|
3558
|
+
return false;
|
|
3559
|
+
}
|
|
3560
|
+
/**
|
|
3561
|
+
* Parse the raw stdout of `openclaw channels status --probe`.
|
|
3562
|
+
* Port of Python `extract_channels_probe` from the feishu-channel-success-rate skill.
|
|
3563
|
+
*/
|
|
3564
|
+
function parseChannelsProbeOutput(text) {
|
|
3565
|
+
const gatewayReachable = text.includes("Gateway reachable");
|
|
3566
|
+
const accounts = [];
|
|
3567
|
+
let anyAccountWorking = false;
|
|
3568
|
+
for (const line of text.split("\n")) {
|
|
3569
|
+
const m = CHANNEL_LINE_RE.exec(line.trim());
|
|
3570
|
+
if (!m) continue;
|
|
3571
|
+
const [, acct, rest] = m;
|
|
3572
|
+
const bits = rest.split(",").map((b) => b.trim());
|
|
3573
|
+
const isWorking = accountIsWorking(bits);
|
|
3574
|
+
if (isWorking) anyAccountWorking = true;
|
|
3575
|
+
accounts.push({
|
|
3576
|
+
id: acct.trim(),
|
|
3577
|
+
bits,
|
|
3578
|
+
isWorking,
|
|
3579
|
+
raw: line.trim()
|
|
3580
|
+
});
|
|
3581
|
+
}
|
|
3582
|
+
return {
|
|
3583
|
+
gatewayReachable,
|
|
3584
|
+
accounts,
|
|
3585
|
+
anyAccountWorking
|
|
3586
|
+
};
|
|
3587
|
+
}
|
|
3588
|
+
/**
|
|
3589
|
+
* Run `openclaw channels status --probe` and return a structured result.
|
|
3590
|
+
*
|
|
3591
|
+
* The command may exit non-zero when some bot accounts fail their probe — that
|
|
3592
|
+
* is still useful output. We therefore try to parse stdout even when the
|
|
3593
|
+
* process exits with a non-zero code, falling back to an unavailable result
|
|
3594
|
+
* only when there is genuinely no output to parse.
|
|
3595
|
+
*
|
|
3596
|
+
* @param timeoutMs Maximum wait time. Default is 60 s because v2026.4.x
|
|
3597
|
+
* lacks a per-request HTTP timeout and can block indefinitely.
|
|
3598
|
+
*/
|
|
3599
|
+
function runChannelsProbe(timeoutMs = 6e4) {
|
|
3600
|
+
let stdout = "";
|
|
3601
|
+
let execError;
|
|
3602
|
+
try {
|
|
3603
|
+
stdout = (0, node_child_process.execSync)("openclaw channels status --probe", {
|
|
3604
|
+
encoding: "utf-8",
|
|
3605
|
+
timeout: timeoutMs,
|
|
3606
|
+
stdio: [
|
|
3607
|
+
"ignore",
|
|
3608
|
+
"pipe",
|
|
3609
|
+
"pipe"
|
|
3610
|
+
]
|
|
3611
|
+
});
|
|
3612
|
+
} catch (e) {
|
|
3613
|
+
const err = e;
|
|
3614
|
+
stdout = err.stdout ?? "";
|
|
3615
|
+
execError = err.message;
|
|
3616
|
+
const stderrRaw = err.stderr;
|
|
3617
|
+
const stderr = (typeof stderrRaw === "string" ? stderrRaw : stderrRaw?.toString("utf-8") ?? "").trim();
|
|
3618
|
+
if (stderr) console.error(`channels-probe: stderr from CLI: ${stderr}`);
|
|
3619
|
+
}
|
|
3620
|
+
if (stdout.trim()) return {
|
|
3621
|
+
available: true,
|
|
3622
|
+
...parseChannelsProbeOutput(stdout)
|
|
3623
|
+
};
|
|
3624
|
+
return {
|
|
3625
|
+
available: false,
|
|
3626
|
+
gatewayReachable: false,
|
|
3627
|
+
accounts: [],
|
|
3628
|
+
anyAccountWorking: false,
|
|
3629
|
+
error: execError ?? "no output from openclaw channels status --probe"
|
|
3630
|
+
};
|
|
3631
|
+
}
|
|
3632
|
+
//#endregion
|
|
3633
|
+
//#region src/rules/upgrade-lark-needed.ts
|
|
3634
|
+
/**
|
|
3635
|
+
* Detects the condition that warrants running `upgrade-lark`:
|
|
3636
|
+
* - feishu plugin version incompatible with current openclaw, AND
|
|
3637
|
+
* - channels are not working.
|
|
3638
|
+
*
|
|
3639
|
+
* Both conditions must be true simultaneously. If version is compatible or
|
|
3640
|
+
* channels are working, the rule passes (no action needed).
|
|
3641
|
+
*
|
|
3642
|
+
* profile: experimental — runs only in full sweep mode, not in standard doctor.
|
|
3643
|
+
* level: silent — telemetry/sweep-only, does not trigger page-level repair UI.
|
|
3644
|
+
*/
|
|
3645
|
+
let UpgradeLarkNeededRule = class UpgradeLarkNeededRule extends DiagnoseRule {
|
|
3646
|
+
validate(ctx) {
|
|
3647
|
+
if (!needsLarkUpgrade(ctx)) return { pass: true };
|
|
3648
|
+
let anyAccountWorking = false;
|
|
3649
|
+
try {
|
|
3650
|
+
anyAccountWorking = runChannelsProbe(3e4).anyAccountWorking;
|
|
3651
|
+
} catch {
|
|
3652
|
+
return { pass: true };
|
|
3653
|
+
}
|
|
3654
|
+
if (anyAccountWorking) return { pass: true };
|
|
3655
|
+
return {
|
|
3656
|
+
pass: false,
|
|
3657
|
+
action: "upgrade_lark",
|
|
3658
|
+
message: "飞书插件版本不兼容且 channels 不可用,建议执行 upgrade-lark 命令升级飞书插件"
|
|
3659
|
+
};
|
|
3660
|
+
}
|
|
3661
|
+
};
|
|
3662
|
+
UpgradeLarkNeededRule = __decorate([Rule({
|
|
3663
|
+
key: "upgrade_lark_needed",
|
|
3664
|
+
description: "检测飞书插件版本不兼容且 channels 不可用,判断是否需要执行 upgrade-lark 升级",
|
|
3665
|
+
dependsOn: ["feishu_plugin_version_compat_lark"],
|
|
3666
|
+
repairMode: "check-only",
|
|
3667
|
+
level: "silent",
|
|
3668
|
+
profile: "experimental",
|
|
3669
|
+
usesVars: ["recommendedOpenclawTag"]
|
|
3670
|
+
})], UpgradeLarkNeededRule);
|
|
3508
3671
|
//#endregion
|
|
3509
3672
|
//#region src/rules/cleanup-install-backup-dirs.ts
|
|
3510
3673
|
const DIR_PREFIX = ".openclaw-install-";
|
|
@@ -3555,110 +3718,100 @@ CleanupInstallBackupDirsRule = __decorate([Rule({
|
|
|
3555
3718
|
level: "critical"
|
|
3556
3719
|
})], CleanupInstallBackupDirsRule);
|
|
3557
3720
|
//#endregion
|
|
3558
|
-
//#region src/rules/
|
|
3559
|
-
|
|
3560
|
-
|
|
3561
|
-
|
|
3562
|
-
|
|
3563
|
-
|
|
3564
|
-
|
|
3565
|
-
|
|
3566
|
-
|
|
3567
|
-
|
|
3721
|
+
//#region src/rules/lark-cli-missing-for-installed-lark-plugin.ts
|
|
3722
|
+
const PLUGIN_NAME = "openclaw-lark";
|
|
3723
|
+
const FORK_PACKAGE_NAME = "@lark-apaas/openclaw-lark";
|
|
3724
|
+
const TARGET_VERSION = "2026.4.4";
|
|
3725
|
+
const LARK_CLI_NAME$1 = "lark-cli";
|
|
3726
|
+
function readInstalledLarkPlugin(ctx) {
|
|
3727
|
+
const pkgPath = node_path.default.join(getExtensionsDir(ctx.configPath), PLUGIN_NAME, "package.json");
|
|
3728
|
+
if (!node_fs.default.existsSync(pkgPath)) return null;
|
|
3729
|
+
let pkg = {};
|
|
3730
|
+
try {
|
|
3731
|
+
const parsed = JSON.parse(node_fs.default.readFileSync(pkgPath, "utf-8"));
|
|
3732
|
+
pkg = {
|
|
3733
|
+
name: typeof parsed.name === "string" ? parsed.name : void 0,
|
|
3734
|
+
version: typeof parsed.version === "string" ? parsed.version : void 0
|
|
3735
|
+
};
|
|
3736
|
+
} catch {
|
|
3737
|
+
pkg = {};
|
|
3738
|
+
}
|
|
3739
|
+
const installs = getNestedMap(ctx.config, "plugins", "installs");
|
|
3740
|
+
const installEntry = installs ? asRecord(installs[PLUGIN_NAME]) : void 0;
|
|
3741
|
+
return {
|
|
3742
|
+
name: pkg.name ?? extractScopedNameFromSpec(installEntry?.spec),
|
|
3743
|
+
version: pkg.version ?? (typeof installEntry?.version === "string" ? installEntry.version : void 0)
|
|
3744
|
+
};
|
|
3745
|
+
}
|
|
3746
|
+
function isTargetForkPlugin(plugin) {
|
|
3747
|
+
return plugin?.name === FORK_PACKAGE_NAME && plugin.version === TARGET_VERSION;
|
|
3748
|
+
}
|
|
3749
|
+
function extractScopedNameFromSpec(spec) {
|
|
3750
|
+
if (typeof spec !== "string") return void 0;
|
|
3751
|
+
const at = spec.indexOf("@", 1);
|
|
3752
|
+
return at === -1 ? spec : spec.slice(0, at);
|
|
3753
|
+
}
|
|
3754
|
+
function isLarkCliAvailable$1() {
|
|
3755
|
+
try {
|
|
3756
|
+
return (0, node_child_process.spawnSync)(LARK_CLI_NAME$1, ["--version"], {
|
|
3757
|
+
encoding: "utf-8",
|
|
3758
|
+
timeout: 5e3,
|
|
3759
|
+
stdio: [
|
|
3760
|
+
"ignore",
|
|
3761
|
+
"pipe",
|
|
3762
|
+
"ignore"
|
|
3763
|
+
]
|
|
3764
|
+
}).status === 0;
|
|
3765
|
+
} catch {
|
|
3766
|
+
return false;
|
|
3767
|
+
}
|
|
3768
|
+
}
|
|
3769
|
+
function installLarkCliOnce(tag) {
|
|
3770
|
+
const entry = process.argv[1];
|
|
3771
|
+
if (!entry) throw new Error("cannot resolve diagnose-cli entrypoint for lark-cli install");
|
|
3772
|
+
const res = (0, node_child_process.spawnSync)(process.execPath, [
|
|
3773
|
+
entry,
|
|
3774
|
+
"install-cli",
|
|
3775
|
+
tag,
|
|
3776
|
+
"--cli=lark-cli"
|
|
3777
|
+
], {
|
|
3778
|
+
encoding: "utf-8",
|
|
3779
|
+
stdio: [
|
|
3780
|
+
"ignore",
|
|
3781
|
+
"pipe",
|
|
3782
|
+
"pipe"
|
|
3783
|
+
]
|
|
3784
|
+
});
|
|
3785
|
+
const stdout = res.stdout?.trim();
|
|
3786
|
+
const stderr = res.stderr?.trim();
|
|
3787
|
+
if (stdout) console.error(`[lark-cli-missing] install-cli stdout: ${stdout}`);
|
|
3788
|
+
if (stderr) console.error(`[lark-cli-missing] install-cli stderr: ${stderr}`);
|
|
3789
|
+
if (res.error) throw new Error(`install-cli spawn error: ${res.error.message}`);
|
|
3790
|
+
if (res.status !== 0) throw new Error(`install-cli exited with code ${res.status ?? "unknown"}`);
|
|
3791
|
+
}
|
|
3792
|
+
let LarkCliMissingForInstalledLarkPluginRule = class LarkCliMissingForInstalledLarkPluginRule extends DiagnoseRule {
|
|
3568
3793
|
validate(ctx) {
|
|
3569
|
-
|
|
3570
|
-
if (
|
|
3571
|
-
const feishu = asRecord(getNestedMap(ctx.config, "channels", "feishu"));
|
|
3572
|
-
if (!feishu) return { pass: true };
|
|
3573
|
-
const issues = [];
|
|
3574
|
-
const accounts = asRecord(feishu.accounts);
|
|
3575
|
-
if (accounts) for (const [accountId, account] of Object.entries(accounts)) {
|
|
3576
|
-
const bot = asRecord(account);
|
|
3577
|
-
if (!bot) continue;
|
|
3578
|
-
const appId = bot.appId;
|
|
3579
|
-
if (typeof appId !== "string" || !appId.startsWith("cli_")) continue;
|
|
3580
|
-
const larkApp = larkApps.find((e) => e.larkAppID === appId);
|
|
3581
|
-
if (!larkApp) continue;
|
|
3582
|
-
this.checkBot(accountId, bot, larkApp, issues);
|
|
3583
|
-
}
|
|
3584
|
-
const singleAppId = feishu.appId;
|
|
3585
|
-
if (typeof singleAppId === "string" && singleAppId.startsWith("cli_") && !accounts) {
|
|
3586
|
-
const larkApp = larkApps.find((e) => e.larkAppID === singleAppId);
|
|
3587
|
-
if (larkApp) this.checkBot("feishu", feishu, larkApp, issues);
|
|
3588
|
-
}
|
|
3589
|
-
if (issues.length === 0) return { pass: true };
|
|
3794
|
+
if (!isTargetForkPlugin(readInstalledLarkPlugin(ctx))) return { pass: true };
|
|
3795
|
+
if (isLarkCliAvailable$1()) return { pass: true };
|
|
3590
3796
|
return {
|
|
3591
3797
|
pass: false,
|
|
3592
|
-
message:
|
|
3798
|
+
message: `${FORK_PACKAGE_NAME}@${TARGET_VERSION} 已安装,但 lark-cli 不可用;将执行一次 lark-cli 安装`
|
|
3593
3799
|
};
|
|
3594
3800
|
}
|
|
3595
|
-
/** Check a single bot entry (either an account object or the feishu channel itself). */
|
|
3596
|
-
checkBot(label, bot, larkApp, issues) {
|
|
3597
|
-
const creatorOpenID = larkApp.creatorOpenID;
|
|
3598
|
-
const allowFrom = Array.isArray(bot.allowFrom) ? bot.allowFrom : [];
|
|
3599
|
-
if (typeof creatorOpenID === "string" && creatorOpenID !== "") {
|
|
3600
|
-
if (!allowFrom.includes(creatorOpenID)) issues.push(`${label} allowFrom missing creatorOpenID ${creatorOpenID.length > 8 ? creatorOpenID.slice(0, 4) + "***" + creatorOpenID.slice(-4) : "***"}`);
|
|
3601
|
-
} else if (allowFrom.length === 0) issues.push(`${label} allowFrom is empty (creatorOpenID unavailable, cannot auto-fix)`);
|
|
3602
|
-
const secret = bot.appSecret;
|
|
3603
|
-
if (typeof secret === "object" && secret !== null && !Array.isArray(secret)) {
|
|
3604
|
-
if (!matchMap(secret, DEFAULT_FEISHU_APP_SECRET)) issues.push(`${label} appSecret is a provider-ref but not the canonical one`);
|
|
3605
|
-
} else if (typeof secret === "string") {
|
|
3606
|
-
if (secret !== larkApp.appSecret) issues.push(`${label} appSecret plaintext mismatch`);
|
|
3607
|
-
} else issues.push(`${label} appSecret has unexpected type`);
|
|
3608
|
-
}
|
|
3609
3801
|
repair(ctx) {
|
|
3610
|
-
|
|
3611
|
-
if (
|
|
3612
|
-
|
|
3613
|
-
if (!feishu) return;
|
|
3614
|
-
const accounts = asRecord(feishu.accounts);
|
|
3615
|
-
if (accounts) for (const [, account] of Object.entries(accounts)) {
|
|
3616
|
-
const bot = asRecord(account);
|
|
3617
|
-
if (!bot) continue;
|
|
3618
|
-
const appId = bot.appId;
|
|
3619
|
-
if (typeof appId !== "string" || !appId.startsWith("cli_")) continue;
|
|
3620
|
-
const larkApp = larkApps.find((e) => e.larkAppID === appId);
|
|
3621
|
-
if (!larkApp) continue;
|
|
3622
|
-
this.fixBot(bot, larkApp);
|
|
3623
|
-
}
|
|
3624
|
-
const singleAppId = feishu.appId;
|
|
3625
|
-
if (typeof singleAppId === "string" && singleAppId.startsWith("cli_") && !accounts) {
|
|
3626
|
-
const larkApp = larkApps.find((e) => e.larkAppID === singleAppId);
|
|
3627
|
-
if (larkApp) this.fixBot(feishu, larkApp);
|
|
3628
|
-
}
|
|
3629
|
-
}
|
|
3630
|
-
/** Fix a single bot entry in-place. */
|
|
3631
|
-
fixBot(bot, larkApp) {
|
|
3632
|
-
const creatorOpenID = larkApp.creatorOpenID;
|
|
3633
|
-
if (typeof creatorOpenID === "string" && creatorOpenID !== "") {
|
|
3634
|
-
const allowFrom = Array.isArray(bot.allowFrom) ? [...bot.allowFrom] : [];
|
|
3635
|
-
if (!allowFrom.includes(creatorOpenID)) {
|
|
3636
|
-
allowFrom.push(creatorOpenID);
|
|
3637
|
-
bot.allowFrom = allowFrom;
|
|
3638
|
-
}
|
|
3639
|
-
}
|
|
3640
|
-
const secret = bot.appSecret;
|
|
3641
|
-
let needsFix = false;
|
|
3642
|
-
if (typeof secret === "object" && secret !== null && !Array.isArray(secret)) {
|
|
3643
|
-
if (!matchMap(secret, DEFAULT_FEISHU_APP_SECRET)) needsFix = true;
|
|
3644
|
-
} else if (typeof secret === "string") {
|
|
3645
|
-
if (secret !== larkApp.appSecret) needsFix = true;
|
|
3646
|
-
} else needsFix = true;
|
|
3647
|
-
if (needsFix) bot.appSecret = { ...DEFAULT_FEISHU_APP_SECRET };
|
|
3802
|
+
if (!isTargetForkPlugin(readInstalledLarkPlugin(ctx))) return;
|
|
3803
|
+
if (isLarkCliAvailable$1()) return;
|
|
3804
|
+
installLarkCliOnce(ctx.vars.recommendedOpenclawTag ?? TARGET_VERSION);
|
|
3648
3805
|
}
|
|
3649
3806
|
};
|
|
3650
|
-
|
|
3651
|
-
key: "
|
|
3652
|
-
description: "
|
|
3653
|
-
dependsOn: [
|
|
3654
|
-
"config_syntax_check",
|
|
3655
|
-
"feishu_default_account",
|
|
3656
|
-
"feishu_bot_id"
|
|
3657
|
-
],
|
|
3807
|
+
LarkCliMissingForInstalledLarkPluginRule = __decorate([Rule({
|
|
3808
|
+
key: "lark_cli_missing_for_installed_lark_plugin",
|
|
3809
|
+
description: "检测特定飞书插件版本已安装但 lark-cli 缺失的环境,并自动安装 lark-cli 一次",
|
|
3810
|
+
dependsOn: ["config_syntax_check"],
|
|
3658
3811
|
repairMode: "standard",
|
|
3659
|
-
|
|
3660
|
-
|
|
3661
|
-
})],
|
|
3812
|
+
level: "critical",
|
|
3813
|
+
usesVars: ["recommendedOpenclawTag"]
|
|
3814
|
+
})], LarkCliMissingForInstalledLarkPluginRule);
|
|
3662
3815
|
//#endregion
|
|
3663
3816
|
//#region src/check.ts
|
|
3664
3817
|
/** Telemetry-aware entry: returns both the legacy CheckResult (for stdout)
|
|
@@ -4123,6 +4276,9 @@ const PROVIDER_FILE_PATH = "/home/gem/workspace/.force/openclaw/miaoda-provider-
|
|
|
4123
4276
|
const SECRETS_FILE_PATH = "/home/gem/workspace/.force/openclaw/miaoda-openclaw-secrets.json";
|
|
4124
4277
|
/** Absolute path to the openclaw config JSON. */
|
|
4125
4278
|
const CONFIG_PATH = `${WORKSPACE_DIR}/openclaw.json`;
|
|
4279
|
+
function upgradeLarkLogFile(runId) {
|
|
4280
|
+
return `${DIAGNOSE_DIR}/upgrade-lark-${(/* @__PURE__ */ new Date()).toISOString().slice(0, 19).replace(/:/g, "-")}-${runId.slice(0, 8)}.log`;
|
|
4281
|
+
}
|
|
4126
4282
|
//#endregion
|
|
4127
4283
|
//#region src/run-log.ts
|
|
4128
4284
|
let currentRunContext;
|
|
@@ -4259,10 +4415,9 @@ function makeLogger(logFile) {
|
|
|
4259
4415
|
/**
|
|
4260
4416
|
* Start an async reset task: spawn a detached child process and return the taskId.
|
|
4261
4417
|
*
|
|
4262
|
-
* The child process runs: node cli.js reset --worker --task-id=xxx
|
|
4263
|
-
* The worker fetches ctx from innerApi itself — no --ctx passthrough.
|
|
4418
|
+
* The child process runs: node cli.js reset --worker --task-id=xxx --ctx=base64
|
|
4264
4419
|
*/
|
|
4265
|
-
function startAsyncReset() {
|
|
4420
|
+
function startAsyncReset(ctxBase64) {
|
|
4266
4421
|
const taskId = (0, node_crypto.randomUUID)();
|
|
4267
4422
|
const resultFile = resetResultFile(taskId);
|
|
4268
4423
|
const log = makeLogger(resetLogFile(taskId));
|
|
@@ -4286,7 +4441,8 @@ function startAsyncReset() {
|
|
|
4286
4441
|
process.argv[1],
|
|
4287
4442
|
"reset",
|
|
4288
4443
|
"--worker",
|
|
4289
|
-
`--task-id=${taskId}
|
|
4444
|
+
`--task-id=${taskId}`,
|
|
4445
|
+
`--ctx=${ctxBase64}`
|
|
4290
4446
|
], {
|
|
4291
4447
|
detached: true,
|
|
4292
4448
|
stdio: "ignore",
|
|
@@ -6800,60 +6956,6 @@ function mergeCoreBackupAndOrigins(configPath, vars, resetData, log) {
|
|
|
6800
6956
|
log(`allowedOrigins: added ${added.length} (${JSON.stringify(added)}), total now ${mergedOrigins.length}`);
|
|
6801
6957
|
}
|
|
6802
6958
|
/**
|
|
6803
|
-
* Fix bot account allowFrom and appSecret using larkApps from innerApi.
|
|
6804
|
-
*
|
|
6805
|
-
* For each bot account (key starts with `bot-cli_`):
|
|
6806
|
-
* - allowFrom must contain the bot's own creatorOpenID from larkApps
|
|
6807
|
-
* - appSecret must be either the canonical provider-ref or match larkApps plaintext
|
|
6808
|
-
*
|
|
6809
|
-
* Runs after mergeCoreBackupAndOrigins so it operates on the final config state.
|
|
6810
|
-
*/
|
|
6811
|
-
function fixBotChannelConfig(configPath, larkApps, log) {
|
|
6812
|
-
if (!larkApps || larkApps.length === 0) {
|
|
6813
|
-
log("no larkApps data, skip bot channel config fix");
|
|
6814
|
-
return;
|
|
6815
|
-
}
|
|
6816
|
-
const config = loadJSON5().parse(node_fs.default.readFileSync(configPath, "utf-8"));
|
|
6817
|
-
const accounts = asRecord(getNestedMap(config, "channels", "feishu")?.accounts);
|
|
6818
|
-
if (!accounts) {
|
|
6819
|
-
log("no feishu accounts in config, skip bot channel config fix");
|
|
6820
|
-
return;
|
|
6821
|
-
}
|
|
6822
|
-
let fixCount = 0;
|
|
6823
|
-
for (const [, account] of Object.entries(accounts)) {
|
|
6824
|
-
const bot = asRecord(account);
|
|
6825
|
-
if (!bot) continue;
|
|
6826
|
-
const appId = bot.appId;
|
|
6827
|
-
if (typeof appId !== "string" || !appId.startsWith("cli_")) continue;
|
|
6828
|
-
const larkApp = larkApps.find((e) => e.larkAppID === appId);
|
|
6829
|
-
if (!larkApp) continue;
|
|
6830
|
-
const creatorOpenID = larkApp.creatorOpenID;
|
|
6831
|
-
if (typeof creatorOpenID === "string" && creatorOpenID !== "") {
|
|
6832
|
-
const allowFrom = Array.isArray(bot.allowFrom) ? [...bot.allowFrom] : [];
|
|
6833
|
-
if (!allowFrom.includes(creatorOpenID)) {
|
|
6834
|
-
allowFrom.push(creatorOpenID);
|
|
6835
|
-
bot.allowFrom = allowFrom;
|
|
6836
|
-
fixCount++;
|
|
6837
|
-
}
|
|
6838
|
-
}
|
|
6839
|
-
const secret = bot.appSecret;
|
|
6840
|
-
let needsFix = false;
|
|
6841
|
-
if (typeof secret === "object" && secret !== null && !Array.isArray(secret)) {
|
|
6842
|
-
if (!matchMap(secret, DEFAULT_FEISHU_APP_SECRET)) needsFix = true;
|
|
6843
|
-
} else if (typeof secret === "string") {
|
|
6844
|
-
if (secret !== larkApp.appSecret) needsFix = true;
|
|
6845
|
-
} else needsFix = true;
|
|
6846
|
-
if (needsFix) {
|
|
6847
|
-
bot.appSecret = { ...DEFAULT_FEISHU_APP_SECRET };
|
|
6848
|
-
fixCount++;
|
|
6849
|
-
}
|
|
6850
|
-
}
|
|
6851
|
-
if (fixCount > 0) {
|
|
6852
|
-
node_fs.default.writeFileSync(configPath, JSON.stringify(config, null, 2), "utf-8");
|
|
6853
|
-
log(`fixed ${fixCount} bot channel config issue(s) (allowFrom/appSecret)`);
|
|
6854
|
-
} else log("bot channel config ok, no fixes needed");
|
|
6855
|
-
}
|
|
6856
|
-
/**
|
|
6857
6959
|
* Step 7: Verify startup scripts landed in configDir/scripts/.
|
|
6858
6960
|
*
|
|
6859
6961
|
* Scripts are extracted directly to configDir/scripts/ during stageTemplate —
|
|
@@ -6998,7 +7100,6 @@ async function runReset(input, taskId, resultFile) {
|
|
|
6998
7100
|
await step5InstallOpenclaw(openclawTag, ossFileMap, log);
|
|
6999
7101
|
step(6);
|
|
7000
7102
|
mergeCoreBackupAndOrigins(configPath, vars, resetData, log);
|
|
7001
|
-
fixBotChannelConfig(configPath, vars.larkApps, log);
|
|
7002
7103
|
step(7);
|
|
7003
7104
|
verifyStartupScripts(configDir, log);
|
|
7004
7105
|
step(8);
|
|
@@ -7797,8 +7898,7 @@ function normalizeCtx(raw) {
|
|
|
7797
7898
|
reset: {
|
|
7798
7899
|
templateVars: r.reset.templateVars ?? {},
|
|
7799
7900
|
coreBackup: r.reset.coreBackup
|
|
7800
|
-
}
|
|
7801
|
-
larkApps: Array.isArray(r.larkApps) ? r.larkApps : []
|
|
7901
|
+
}
|
|
7802
7902
|
};
|
|
7803
7903
|
}
|
|
7804
7904
|
const vars = r.vars ?? {};
|
|
@@ -7823,8 +7923,7 @@ function normalizeCtx(raw) {
|
|
|
7823
7923
|
reset: {
|
|
7824
7924
|
templateVars: resetData.templateVars ?? {},
|
|
7825
7925
|
coreBackup: resetData.coreBackup
|
|
7826
|
-
}
|
|
7827
|
-
larkApps: Array.isArray(r.larkApps) ? r.larkApps : []
|
|
7926
|
+
}
|
|
7828
7927
|
};
|
|
7829
7928
|
}
|
|
7830
7929
|
function fillApp(src) {
|
|
@@ -7889,8 +7988,7 @@ function buildCheckInput(raw, configPathOverride) {
|
|
|
7889
7988
|
providerFilePath: PROVIDER_FILE_PATH,
|
|
7890
7989
|
secretsFilePath: SECRETS_FILE_PATH,
|
|
7891
7990
|
templateVars: ctx.app.templateVars,
|
|
7892
|
-
recommendedOpenclawTag: ctx.app.recommendedOpenclawTag
|
|
7893
|
-
larkApps: ctx.larkApps
|
|
7991
|
+
recommendedOpenclawTag: ctx.app.recommendedOpenclawTag
|
|
7894
7992
|
},
|
|
7895
7993
|
templateVars: ctx.app.templateVars
|
|
7896
7994
|
};
|
|
@@ -7922,8 +8020,7 @@ function buildRepairInput(raw, configPathOverride) {
|
|
|
7922
8020
|
providerFilePath: PROVIDER_FILE_PATH,
|
|
7923
8021
|
secretsFilePath: SECRETS_FILE_PATH,
|
|
7924
8022
|
templateVars: ctx.app.templateVars,
|
|
7925
|
-
recommendedOpenclawTag: ctx.app.recommendedOpenclawTag
|
|
7926
|
-
larkApps: ctx.larkApps
|
|
8023
|
+
recommendedOpenclawTag: ctx.app.recommendedOpenclawTag
|
|
7927
8024
|
},
|
|
7928
8025
|
repairData: {
|
|
7929
8026
|
secretsContent: ctx.secrets.secretsContent,
|
|
@@ -7959,8 +8056,7 @@ function buildResetInput(raw, configPathOverride) {
|
|
|
7959
8056
|
providerFilePath: PROVIDER_FILE_PATH,
|
|
7960
8057
|
secretsFilePath: SECRETS_FILE_PATH,
|
|
7961
8058
|
templateVars: ctx.app.templateVars,
|
|
7962
|
-
recommendedOpenclawTag: ctx.app.recommendedOpenclawTag
|
|
7963
|
-
larkApps: ctx.larkApps
|
|
8059
|
+
recommendedOpenclawTag: ctx.app.recommendedOpenclawTag
|
|
7964
8060
|
},
|
|
7965
8061
|
resetData: {
|
|
7966
8062
|
templateVars: ctx.reset.templateVars,
|
|
@@ -10270,7 +10366,7 @@ async function reportCliRun(opts) {
|
|
|
10270
10366
|
//#region src/help.ts
|
|
10271
10367
|
const BIN = "mclaw-diagnose";
|
|
10272
10368
|
function versionBanner() {
|
|
10273
|
-
return `v0.1.14-alpha.
|
|
10369
|
+
return `v0.1.14-alpha.2`;
|
|
10274
10370
|
}
|
|
10275
10371
|
const COMMANDS = [
|
|
10276
10372
|
{
|
|
@@ -10374,12 +10470,16 @@ EXIT CODES
|
|
|
10374
10470
|
hidden: true,
|
|
10375
10471
|
summary: "Run rule-engine check only",
|
|
10376
10472
|
help: `USAGE
|
|
10377
|
-
${BIN} check
|
|
10473
|
+
${BIN} check [--ctx=<base64>]
|
|
10378
10474
|
|
|
10379
10475
|
DESCRIPTION
|
|
10380
10476
|
Runs the rule engine against the sandbox's current openclaw config and
|
|
10381
|
-
returns { failedRules }.
|
|
10382
|
-
|
|
10477
|
+
returns { failedRules }. Used by sandbox_console's push-style callers
|
|
10478
|
+
that already own the ctx — end-users should prefer \`doctor\`.
|
|
10479
|
+
|
|
10480
|
+
OPTIONS
|
|
10481
|
+
--ctx=<base64> Opaque ctx JSON (base64). When absent, fetched from
|
|
10482
|
+
innerapi (same path as doctor).
|
|
10383
10483
|
`
|
|
10384
10484
|
},
|
|
10385
10485
|
{
|
|
@@ -10387,11 +10487,16 @@ DESCRIPTION
|
|
|
10387
10487
|
hidden: true,
|
|
10388
10488
|
summary: "Apply standard-mode repairs",
|
|
10389
10489
|
help: `USAGE
|
|
10390
|
-
${BIN} repair
|
|
10490
|
+
${BIN} repair [--ctx=<base64>]
|
|
10391
10491
|
|
|
10392
10492
|
DESCRIPTION
|
|
10393
|
-
Runs repair for the failing rules
|
|
10394
|
-
|
|
10493
|
+
Runs repair for the failing rules listed inside the ctx's repairData.
|
|
10494
|
+
Intended for sandbox_console's push path — end-users should use
|
|
10495
|
+
\`doctor --fix\` instead.
|
|
10496
|
+
|
|
10497
|
+
OPTIONS
|
|
10498
|
+
--ctx=<base64> Opaque ctx JSON (base64). When absent, fetched from
|
|
10499
|
+
innerapi.
|
|
10395
10500
|
`
|
|
10396
10501
|
},
|
|
10397
10502
|
{
|
|
@@ -10399,15 +10504,14 @@ DESCRIPTION
|
|
|
10399
10504
|
hidden: true,
|
|
10400
10505
|
summary: "Re-initialize sandbox via the 9-step reset pipeline",
|
|
10401
10506
|
help: `USAGE
|
|
10402
|
-
${BIN} reset --async
|
|
10403
|
-
${BIN} reset --worker --task-id=<id>
|
|
10507
|
+
${BIN} reset --async [--ctx=<base64>]
|
|
10508
|
+
${BIN} reset --worker --task-id=<id> [--ctx=<base64>]
|
|
10404
10509
|
|
|
10405
10510
|
DESCRIPTION
|
|
10406
10511
|
Two-phase pipeline driven asynchronously: the --async invocation spawns
|
|
10407
10512
|
a detached worker and returns { taskId } immediately; the --worker
|
|
10408
10513
|
invocation (spawned by --async) runs the actual 9 steps and writes
|
|
10409
10514
|
progress to /tmp/openclaw-diagnose/reset-<taskId>.json.
|
|
10410
|
-
Ctx is fetched from innerapi automatically.
|
|
10411
10515
|
|
|
10412
10516
|
Poll progress with \`${BIN} get_reset_task --task-id=<id>\`.
|
|
10413
10517
|
|
|
@@ -10415,6 +10519,7 @@ OPTIONS
|
|
|
10415
10519
|
--async Start a detached worker and return taskId on stdout.
|
|
10416
10520
|
--worker Internal — run the 9-step pipeline (launched by --async).
|
|
10417
10521
|
--task-id=<id> Required with --worker; identifies the progress file.
|
|
10522
|
+
--ctx=<base64> Opaque ctx JSON; fetched from innerapi when absent.
|
|
10418
10523
|
`
|
|
10419
10524
|
},
|
|
10420
10525
|
{
|
|
@@ -10437,7 +10542,7 @@ OPTIONS
|
|
|
10437
10542
|
hidden: true,
|
|
10438
10543
|
summary: "Download + install the openclaw tarball",
|
|
10439
10544
|
help: `USAGE
|
|
10440
|
-
${BIN} install-openclaw <tag> [--oss_file_map=<base64>]
|
|
10545
|
+
${BIN} install-openclaw <tag> [--ctx=<base64> | --oss_file_map=<base64>]
|
|
10441
10546
|
|
|
10442
10547
|
DESCRIPTION
|
|
10443
10548
|
Downloads the openclaw@<tag> tgz via the signed OSS URL found in the
|
|
@@ -10449,9 +10554,9 @@ ARGUMENTS
|
|
|
10449
10554
|
<tag> Openclaw version tag, e.g. 2026.4.11.
|
|
10450
10555
|
|
|
10451
10556
|
OPTIONS
|
|
10557
|
+
--ctx=<base64> Opaque ctx; ossFileMap is extracted from it.
|
|
10452
10558
|
--oss_file_map=... Pre-built OSS URL map (base64 JSON); skips innerapi
|
|
10453
|
-
entirely.
|
|
10454
|
-
innerapi automatically.
|
|
10559
|
+
entirely. Wins over --ctx when both provided.
|
|
10455
10560
|
`
|
|
10456
10561
|
},
|
|
10457
10562
|
{
|
|
@@ -10477,7 +10582,8 @@ OPTIONS
|
|
|
10477
10582
|
--home_base=<dir> Override the /home/gem base (tests).
|
|
10478
10583
|
--config_path=<p> Override the openclaw.json path (tests).
|
|
10479
10584
|
--skip-config-update Leave plugins.installs in openclaw.json untouched.
|
|
10480
|
-
--
|
|
10585
|
+
--ctx=<base64> Opaque ctx; see install-openclaw for semantics.
|
|
10586
|
+
--oss_file_map=... Pre-built OSS URL map (base64 JSON).
|
|
10481
10587
|
`
|
|
10482
10588
|
},
|
|
10483
10589
|
{
|
|
@@ -10504,6 +10610,7 @@ OPTIONS
|
|
|
10504
10610
|
--cli=<name> CLI package to install by short name or scoped
|
|
10505
10611
|
packageName (repeatable, at least one required).
|
|
10506
10612
|
--home_base=<dir> Override the /home/gem base (tests).
|
|
10613
|
+
--ctx=<base64> Opaque ctx; ossFileMap is extracted from it.
|
|
10507
10614
|
--oss_file_map=... Pre-built OSS URL map (base64 JSON); skips innerapi.
|
|
10508
10615
|
|
|
10509
10616
|
EXAMPLES
|
|
@@ -10557,6 +10664,46 @@ OPTIONS
|
|
|
10557
10664
|
EXIT CODES
|
|
10558
10665
|
0 Success or skipped (prerequisites not met).
|
|
10559
10666
|
1 Secret/path unresolvable, lark-cli failed, or config unreadable.
|
|
10667
|
+
`
|
|
10668
|
+
},
|
|
10669
|
+
{
|
|
10670
|
+
name: "upgrade-lark",
|
|
10671
|
+
hidden: false,
|
|
10672
|
+
summary: "Upgrade the Feishu/Lark plugin via @larksuite/openclaw-lark-tools",
|
|
10673
|
+
help: `USAGE
|
|
10674
|
+
${BIN} upgrade-lark [--scene=<scene>] [--caller=<n>] [--trace-id=<id>]
|
|
10675
|
+
|
|
10676
|
+
DESCRIPTION
|
|
10677
|
+
Upgrades the Feishu/Lark plugin by running:
|
|
10678
|
+
npx -y @larksuite/openclaw-lark-tools update --use-existing
|
|
10679
|
+
|
|
10680
|
+
Before the upgrade, the following files are backed up:
|
|
10681
|
+
- openclaw.json
|
|
10682
|
+
- extensions/openclaw-lark/ (if present)
|
|
10683
|
+
- extensions/feishu-openclaw-plugin/ (if present)
|
|
10684
|
+
After the upgrade, the result is validated:
|
|
10685
|
+
- feishu.accounts bot count must not decrease
|
|
10686
|
+
- gateway config structure must remain valid (port/mode/bind/auth/trustedProxies)
|
|
10687
|
+
If the upgrade command fails, or validation fails, the backed-up files are
|
|
10688
|
+
restored to roll back the changes.
|
|
10689
|
+
|
|
10690
|
+
Execution is logged to /tmp/openclaw-diagnose/upgrade-lark-<runId>.log.
|
|
10691
|
+
|
|
10692
|
+
Output is a single JSON object on stdout:
|
|
10693
|
+
{ "ok": true, "stdout": "...", "stderr": "...", "logFile": "..." }
|
|
10694
|
+
{ "ok": false, "error": "...", "stderr": "...", "exitCode": 1,
|
|
10695
|
+
"rollbackOk": true, "validationError": "...", "logFile": "..." }
|
|
10696
|
+
|
|
10697
|
+
OPTIONS
|
|
10698
|
+
--scene=<scene> Telemetry label forwarded to Slardar only.
|
|
10699
|
+
Known values: PageUpgradeLark, etc. Custom strings accepted.
|
|
10700
|
+
--caller=<name> Optional metadata passed to innerapi.
|
|
10701
|
+
--trace-id=<id> Optional log-correlation id.
|
|
10702
|
+
|
|
10703
|
+
EXIT CODES
|
|
10704
|
+
0 Success: upgrade ran and all validations passed.
|
|
10705
|
+
1 Failure: npx error, validation failed, or git commit failed.
|
|
10706
|
+
File rollback was attempted (see rollbackOk in the JSON output).
|
|
10560
10707
|
`
|
|
10561
10708
|
},
|
|
10562
10709
|
{
|
|
@@ -10590,6 +10737,41 @@ EXAMPLES
|
|
|
10590
10737
|
${BIN} rules # all rules
|
|
10591
10738
|
${BIN} rules --rule=gateway # single rule
|
|
10592
10739
|
${BIN} rules --rule=gateway --rule=feishu_channel # multiple rules
|
|
10740
|
+
`
|
|
10741
|
+
},
|
|
10742
|
+
{
|
|
10743
|
+
name: "channels-probe",
|
|
10744
|
+
hidden: true,
|
|
10745
|
+
summary: "Check feishu channel health via openclaw channels status --probe",
|
|
10746
|
+
help: `USAGE
|
|
10747
|
+
${BIN} channels-probe [--timeout=<ms>]
|
|
10748
|
+
|
|
10749
|
+
DESCRIPTION
|
|
10750
|
+
Runs \`openclaw channels status --probe\` and returns a structured JSON
|
|
10751
|
+
summary of whether the current environment's feishu channels are
|
|
10752
|
+
configured and working correctly.
|
|
10753
|
+
|
|
10754
|
+
Output:
|
|
10755
|
+
{
|
|
10756
|
+
"available": true,
|
|
10757
|
+
"gatewayReachable": true,
|
|
10758
|
+
"accounts": [
|
|
10759
|
+
{ "id": "default", "bits": ["enabled","configured","running","works"],
|
|
10760
|
+
"isWorking": true, "raw": "- Feishu default: ..." }
|
|
10761
|
+
],
|
|
10762
|
+
"anyAccountWorking": true
|
|
10763
|
+
}
|
|
10764
|
+
|
|
10765
|
+
An account is considered working when:
|
|
10766
|
+
enabled ∧ configured ∧ ( works ∨ ( running ∧ no error: ∧ no probe failed ) )
|
|
10767
|
+
|
|
10768
|
+
"available": false means the CLI invocation itself failed (openclaw not
|
|
10769
|
+
found, gateway unreachable, or no parseable output returned).
|
|
10770
|
+
|
|
10771
|
+
OPTIONS
|
|
10772
|
+
--timeout=<ms> Max wait in milliseconds (default: 60000). The probe
|
|
10773
|
+
can hang indefinitely on openclaw v2026.4.x due to a
|
|
10774
|
+
missing per-request HTTP timeout — set this accordingly.
|
|
10593
10775
|
`
|
|
10594
10776
|
},
|
|
10595
10777
|
{
|
|
@@ -10610,7 +10792,8 @@ OPTIONS
|
|
|
10610
10792
|
--role=<role> Package role (e.g. template, config).
|
|
10611
10793
|
--name=<name> Package name within the role.
|
|
10612
10794
|
--dir=<dir> Target dir (defaults to dirname(pkg.installPath)).
|
|
10613
|
-
--
|
|
10795
|
+
--ctx=<base64> Opaque ctx; ossFileMap is extracted from it.
|
|
10796
|
+
--oss_file_map=... Pre-built OSS URL map (base64 JSON).
|
|
10614
10797
|
`
|
|
10615
10798
|
}
|
|
10616
10799
|
];
|
|
@@ -10686,31 +10869,31 @@ function planVarsFields(opts = {}) {
|
|
|
10686
10869
|
*
|
|
10687
10870
|
* Per-command group needs:
|
|
10688
10871
|
*
|
|
10689
|
-
* doctor / check app
|
|
10690
|
-
* repair app + secrets
|
|
10691
|
-
* reset app + secrets + install + reset
|
|
10872
|
+
* doctor / check app (rule-driven)
|
|
10873
|
+
* repair app + secrets (writes secretsContent / providerKeyContent)
|
|
10874
|
+
* reset app + secrets + install + reset (the works)
|
|
10692
10875
|
* install-* install only
|
|
10693
10876
|
*
|
|
10694
10877
|
* Empty result (`{}`) means "no group needed" — the CLI can skip the
|
|
10695
10878
|
* `fetchCtxViaInnerApi` call entirely and run with a synthetic empty ctx.
|
|
10879
|
+
* Happens e.g. when the user pinned `--rule=<key>` to a vars-free rule on
|
|
10880
|
+
* `doctor`.
|
|
10696
10881
|
*/
|
|
10697
10882
|
function planCtxPopulate(opts) {
|
|
10698
10883
|
if (opts.command === "install") return { install: true };
|
|
10699
10884
|
const populate = {};
|
|
10700
|
-
|
|
10885
|
+
const appFields = planVarsFields({
|
|
10701
10886
|
disabled: opts.disabled,
|
|
10702
10887
|
onlyRules: opts.onlyRules,
|
|
10703
10888
|
profile: opts.profile
|
|
10704
|
-
})
|
|
10705
|
-
if (
|
|
10706
|
-
|
|
10707
|
-
|
|
10708
|
-
} else if (opts.command === "reset") {
|
|
10889
|
+
});
|
|
10890
|
+
if (appFields.length > 0) populate.app = appFields;
|
|
10891
|
+
if (opts.command === "repair") populate.secrets = true;
|
|
10892
|
+
else if (opts.command === "reset") {
|
|
10709
10893
|
populate.secrets = true;
|
|
10710
10894
|
populate.install = true;
|
|
10711
10895
|
populate.reset = true;
|
|
10712
|
-
|
|
10713
|
-
} else if (opts.command === "doctor" || opts.command === "check") populate.larkApps = true;
|
|
10896
|
+
}
|
|
10714
10897
|
return populate;
|
|
10715
10898
|
}
|
|
10716
10899
|
//#endregion
|
|
@@ -10764,11 +10947,408 @@ function reportDoctorRunToSlardar(opts) {
|
|
|
10764
10947
|
}
|
|
10765
10948
|
});
|
|
10766
10949
|
}
|
|
10950
|
+
function readLogFile(filePath) {
|
|
10951
|
+
try {
|
|
10952
|
+
return node_fs.default.readFileSync(filePath, "utf-8");
|
|
10953
|
+
} catch {
|
|
10954
|
+
return "";
|
|
10955
|
+
}
|
|
10956
|
+
}
|
|
10957
|
+
function reportUpgradeLarkToSlardar(opts) {
|
|
10958
|
+
console.error(`[slardar] upgrade_lark_run scene=${opts.scene ?? ""} success=${opts.success} exitCode=${opts.exitCode ?? ""} rollbackOk=${opts.rollbackOk ?? ""}`);
|
|
10959
|
+
const logContent = readLogFile(opts.logFile);
|
|
10960
|
+
reportTask({
|
|
10961
|
+
eventName: "upgrade_lark_run",
|
|
10962
|
+
durationMs: opts.durationMs,
|
|
10963
|
+
status: opts.success ? "success" : "failed",
|
|
10964
|
+
extraCategories: {
|
|
10965
|
+
scene: opts.scene ?? "",
|
|
10966
|
+
exit_code: String(opts.exitCode ?? ""),
|
|
10967
|
+
rollback_ok: opts.rollbackOk != null ? String(opts.rollbackOk) : "",
|
|
10968
|
+
validation_error: opts.validationError ?? "",
|
|
10969
|
+
error_msg: opts.error ?? "",
|
|
10970
|
+
log_content: logContent
|
|
10971
|
+
}
|
|
10972
|
+
});
|
|
10973
|
+
}
|
|
10974
|
+
//#endregion
|
|
10975
|
+
//#region src/upgrade-lark.ts
|
|
10976
|
+
/** Plugin directories under extensions/ that are backed up before upgrade */
|
|
10977
|
+
const FEISHU_PLUGIN_DIRS = ["openclaw-lark", "feishu-openclaw-plugin"];
|
|
10978
|
+
/** Version compat rule keys checked in the doctor output after install */
|
|
10979
|
+
const VERSION_COMPAT_RULE_KEYS = ["feishu_plugin_version_compat_lark", "feishu_plugin_version_compat_openclaw"];
|
|
10980
|
+
function backupFiles(opts) {
|
|
10981
|
+
const { workspaceDir, configPath, backupDir, log } = opts;
|
|
10982
|
+
try {
|
|
10983
|
+
node_fs.default.mkdirSync(backupDir, { recursive: true });
|
|
10984
|
+
log(`backup dir: ${backupDir}`);
|
|
10985
|
+
if (node_fs.default.existsSync(configPath)) {
|
|
10986
|
+
const stat = node_fs.default.statSync(configPath);
|
|
10987
|
+
node_fs.default.copyFileSync(configPath, node_path.default.join(backupDir, "openclaw.json"));
|
|
10988
|
+
log(` backed up: openclaw.json (${stat.size} bytes)`);
|
|
10989
|
+
} else log(` skipped: openclaw.json (not found)`);
|
|
10990
|
+
const extSrc = node_path.default.join(workspaceDir, "extensions");
|
|
10991
|
+
for (const pluginDir of FEISHU_PLUGIN_DIRS) {
|
|
10992
|
+
const src = node_path.default.join(extSrc, pluginDir);
|
|
10993
|
+
if (node_fs.default.existsSync(src)) {
|
|
10994
|
+
const dst = node_path.default.join(backupDir, "extensions", pluginDir);
|
|
10995
|
+
node_fs.default.cpSync(src, dst, { recursive: true });
|
|
10996
|
+
const version = readPkgVersion(node_path.default.join(src, "package.json"));
|
|
10997
|
+
log(` backed up: extensions/${pluginDir}${version ? ` (version: ${version})` : ""}`);
|
|
10998
|
+
} else log(` skipped: extensions/${pluginDir} (not found)`);
|
|
10999
|
+
}
|
|
11000
|
+
return { ok: true };
|
|
11001
|
+
} catch (e) {
|
|
11002
|
+
return {
|
|
11003
|
+
ok: false,
|
|
11004
|
+
error: `backup failed: ${e.message}`
|
|
11005
|
+
};
|
|
11006
|
+
}
|
|
11007
|
+
}
|
|
11008
|
+
function restoreFiles(opts) {
|
|
11009
|
+
const { workspaceDir, configPath, backupDir, log } = opts;
|
|
11010
|
+
try {
|
|
11011
|
+
const configBackup = node_path.default.join(backupDir, "openclaw.json");
|
|
11012
|
+
if (node_fs.default.existsSync(configBackup)) {
|
|
11013
|
+
node_fs.default.copyFileSync(configBackup, configPath);
|
|
11014
|
+
log(` restored: openclaw.json`);
|
|
11015
|
+
}
|
|
11016
|
+
const extDst = node_path.default.join(workspaceDir, "extensions");
|
|
11017
|
+
for (const pluginDir of FEISHU_PLUGIN_DIRS) {
|
|
11018
|
+
const backupSrc = node_path.default.join(backupDir, "extensions", pluginDir);
|
|
11019
|
+
if (node_fs.default.existsSync(backupSrc)) {
|
|
11020
|
+
const dst = node_path.default.join(extDst, pluginDir);
|
|
11021
|
+
if (node_fs.default.existsSync(dst)) node_fs.default.rmSync(dst, {
|
|
11022
|
+
recursive: true,
|
|
11023
|
+
force: true
|
|
11024
|
+
});
|
|
11025
|
+
node_fs.default.cpSync(backupSrc, dst, { recursive: true });
|
|
11026
|
+
log(` restored: extensions/${pluginDir}`);
|
|
11027
|
+
}
|
|
11028
|
+
}
|
|
11029
|
+
return true;
|
|
11030
|
+
} catch (e) {
|
|
11031
|
+
log(` restore error: ${e.message}`);
|
|
11032
|
+
return false;
|
|
11033
|
+
}
|
|
11034
|
+
}
|
|
11035
|
+
function readPkgVersion(pkgPath) {
|
|
11036
|
+
try {
|
|
11037
|
+
const pkg = JSON.parse(node_fs.default.readFileSync(pkgPath, "utf-8"));
|
|
11038
|
+
return typeof pkg.version === "string" ? pkg.version : null;
|
|
11039
|
+
} catch {
|
|
11040
|
+
return null;
|
|
11041
|
+
}
|
|
11042
|
+
}
|
|
11043
|
+
function snapshotVersions(cwd, log) {
|
|
11044
|
+
const ocResult = (0, node_child_process.spawnSync)("openclaw", ["--version"], {
|
|
11045
|
+
cwd,
|
|
11046
|
+
encoding: "utf-8",
|
|
11047
|
+
stdio: [
|
|
11048
|
+
"ignore",
|
|
11049
|
+
"pipe",
|
|
11050
|
+
"pipe"
|
|
11051
|
+
],
|
|
11052
|
+
timeout: 5e3
|
|
11053
|
+
});
|
|
11054
|
+
const ocRaw = (ocResult.stdout ?? "").trim() || (ocResult.stderr ?? "").trim();
|
|
11055
|
+
const extDir = node_path.default.join(cwd, "extensions");
|
|
11056
|
+
const larkPkg = node_path.default.join(extDir, "openclaw-lark", "package.json");
|
|
11057
|
+
const feishuPkg = node_path.default.join(extDir, "feishu-openclaw-plugin", "package.json");
|
|
11058
|
+
log(` version-check paths: ${larkPkg} [${node_fs.default.existsSync(larkPkg) ? "exists" : "missing"}]`);
|
|
11059
|
+
log(` version-check paths: ${feishuPkg} [${node_fs.default.existsSync(feishuPkg) ? "exists" : "missing"}]`);
|
|
11060
|
+
return {
|
|
11061
|
+
openclaw: ocRaw || null,
|
|
11062
|
+
openclawLark: readPkgVersion(larkPkg),
|
|
11063
|
+
feishuOpenclawPlugin: readPkgVersion(feishuPkg)
|
|
11064
|
+
};
|
|
11065
|
+
}
|
|
11066
|
+
function logVersionSnapshot(label, v, log) {
|
|
11067
|
+
log(`${label}: openclaw=${v.openclaw ?? "n/a"} openclaw-lark=${v.openclawLark ?? "n/a"} feishu-openclaw-plugin=${v.feishuOpenclawPlugin ?? "n/a"}`);
|
|
11068
|
+
}
|
|
11069
|
+
function countFeishuBots(configPath) {
|
|
11070
|
+
try {
|
|
11071
|
+
const raw = node_fs.default.readFileSync(configPath, "utf-8");
|
|
11072
|
+
const config = loadJSON5().parse(raw);
|
|
11073
|
+
const accounts = getNestedMap(config, "channels", "feishu", "accounts");
|
|
11074
|
+
if (accounts) return Object.keys(accounts).length;
|
|
11075
|
+
const feishu = getNestedMap(config, "channels", "feishu");
|
|
11076
|
+
return typeof feishu?.appId === "string" && feishu.appId ? 1 : 0;
|
|
11077
|
+
} catch {
|
|
11078
|
+
return 0;
|
|
11079
|
+
}
|
|
11080
|
+
}
|
|
11081
|
+
/**
|
|
11082
|
+
* Parse doctor stdout (first JSON line) and return an error string if any
|
|
11083
|
+
* version compat rule failed. Returns null on parse failure so a broken doctor
|
|
11084
|
+
* output does not block the install.
|
|
11085
|
+
*/
|
|
11086
|
+
function checkVersionCompatFromDoctorOutput(stdout, log) {
|
|
11087
|
+
const firstLine = stdout.split("\n")[0]?.trim();
|
|
11088
|
+
if (!firstLine) {
|
|
11089
|
+
log(" doctor(compat): empty output, skipping version compat check");
|
|
11090
|
+
return null;
|
|
11091
|
+
}
|
|
11092
|
+
try {
|
|
11093
|
+
const report = JSON.parse(firstLine);
|
|
11094
|
+
for (const outcome of report.results) if (VERSION_COMPAT_RULE_KEYS.includes(outcome.rule)) {
|
|
11095
|
+
if (outcome.status === "failed" || outcome.status === "still-broken" || outcome.status === "error") return `version compat rule ${outcome.rule} ${outcome.status}: ${outcome.message ?? "(no message)"}`;
|
|
11096
|
+
}
|
|
11097
|
+
return null;
|
|
11098
|
+
} catch (e) {
|
|
11099
|
+
log(` doctor(compat): failed to parse output — ${e.message}`);
|
|
11100
|
+
return null;
|
|
11101
|
+
}
|
|
11102
|
+
}
|
|
11103
|
+
/** Run channels probe, log results, and return the result. Never throws. */
|
|
11104
|
+
function probeChannels(label, log, timeoutMs) {
|
|
11105
|
+
try {
|
|
11106
|
+
const r = runChannelsProbe(timeoutMs);
|
|
11107
|
+
log(` ${label} available=${r.available} anyAccountWorking=${r.anyAccountWorking}`);
|
|
11108
|
+
if (r.error) log(` ${label} error: ${r.error}`);
|
|
11109
|
+
if (r.gatewayReachable != null) log(` ${label} gatewayReachable: ${r.gatewayReachable}`);
|
|
11110
|
+
for (const acct of r.accounts ?? []) log(` ${label} account ${acct.id}: isWorking=${acct.isWorking} bits=[${acct.bits.join(",")}]`);
|
|
11111
|
+
return r;
|
|
11112
|
+
} catch (e) {
|
|
11113
|
+
log(` ${label} channels probe threw: ${e.message}`);
|
|
11114
|
+
return {
|
|
11115
|
+
available: false,
|
|
11116
|
+
accounts: [],
|
|
11117
|
+
anyAccountWorking: false
|
|
11118
|
+
};
|
|
11119
|
+
}
|
|
11120
|
+
}
|
|
11121
|
+
function runUpgradeLark(opts) {
|
|
11122
|
+
const cwd = opts.cwd ?? "/home/gem/workspace/agent";
|
|
11123
|
+
const configPath = opts.configPath ?? CONFIG_PATH;
|
|
11124
|
+
const logFile = upgradeLarkLogFile(opts.runId);
|
|
11125
|
+
const log = makeLogger(logFile);
|
|
11126
|
+
const fsOpts = {
|
|
11127
|
+
workspaceDir: cwd,
|
|
11128
|
+
configPath,
|
|
11129
|
+
backupDir: node_path.default.join(opts.backupBaseDir ?? "/tmp/openclaw-diagnose", `upgrade-lark-backup-${opts.runId}`),
|
|
11130
|
+
log
|
|
11131
|
+
};
|
|
11132
|
+
const cliScript = opts.cliScript ?? process.argv[1];
|
|
11133
|
+
const statusCheckDelayMs = opts.statusCheckDelayMs ?? 5e3;
|
|
11134
|
+
log(`${"=".repeat(60)}`);
|
|
11135
|
+
log(`upgrade-lark started runId=${opts.runId}`);
|
|
11136
|
+
log(` cwd : ${cwd}`);
|
|
11137
|
+
log(` configPath : ${configPath}`);
|
|
11138
|
+
log(`${"=".repeat(60)}`);
|
|
11139
|
+
log("");
|
|
11140
|
+
log("── [Pre-check A] channels probe(升级前)────────────────");
|
|
11141
|
+
const beforeChannels = probeChannels("before", log, 3e4);
|
|
11142
|
+
log("");
|
|
11143
|
+
log("── [Pre-check B] 版本兼容预检 ───────────────────────────");
|
|
11144
|
+
let versionNeedsUpgrade = false;
|
|
11145
|
+
try {
|
|
11146
|
+
const rawConfig = node_fs.default.readFileSync(configPath, "utf-8");
|
|
11147
|
+
versionNeedsUpgrade = needsLarkUpgrade({
|
|
11148
|
+
config: loadJSON5().parse(rawConfig),
|
|
11149
|
+
configPath,
|
|
11150
|
+
vars: {},
|
|
11151
|
+
providerDeps: {
|
|
11152
|
+
usesMiaodaProvider: false,
|
|
11153
|
+
usesMiaodaSecretProvider: false
|
|
11154
|
+
}
|
|
11155
|
+
});
|
|
11156
|
+
log(` version-compat pre-check: ${versionNeedsUpgrade ? "NEEDS_UPGRADE" : "ok"}`);
|
|
11157
|
+
} catch (e) {
|
|
11158
|
+
log(` version-compat pre-check error: ${e.message} — treating as needs-upgrade`);
|
|
11159
|
+
versionNeedsUpgrade = true;
|
|
11160
|
+
}
|
|
11161
|
+
log("");
|
|
11162
|
+
log("── [Gate] 升级前置条件检查 ───────────────────────────────");
|
|
11163
|
+
log(` version needs upgrade : ${versionNeedsUpgrade}`);
|
|
11164
|
+
log(` channels working before: ${beforeChannels.anyAccountWorking}`);
|
|
11165
|
+
if (!versionNeedsUpgrade) {
|
|
11166
|
+
const reason = "version already compatible — upgrade not needed";
|
|
11167
|
+
log(` SKIP: ${reason}`);
|
|
11168
|
+
log(`${"=".repeat(60)}`);
|
|
11169
|
+
log("upgrade-lark skipped (pre-check gate)");
|
|
11170
|
+
log(`${"=".repeat(60)}`);
|
|
11171
|
+
return {
|
|
11172
|
+
ok: true,
|
|
11173
|
+
skipped: true,
|
|
11174
|
+
skipReason: reason,
|
|
11175
|
+
logFile
|
|
11176
|
+
};
|
|
11177
|
+
}
|
|
11178
|
+
if (beforeChannels.anyAccountWorking) {
|
|
11179
|
+
const reason = "channels are working — upgrade not needed (version mismatch but system is functional)";
|
|
11180
|
+
log(` SKIP: ${reason}`);
|
|
11181
|
+
log(`${"=".repeat(60)}`);
|
|
11182
|
+
log("upgrade-lark skipped (pre-check gate)");
|
|
11183
|
+
log(`${"=".repeat(60)}`);
|
|
11184
|
+
return {
|
|
11185
|
+
ok: true,
|
|
11186
|
+
skipped: true,
|
|
11187
|
+
skipReason: reason,
|
|
11188
|
+
logFile
|
|
11189
|
+
};
|
|
11190
|
+
}
|
|
11191
|
+
log(" PROCEED: version incompatible AND channels not working → running upgrade");
|
|
11192
|
+
log("");
|
|
11193
|
+
log("── [1/6] 文件备份 ────────────────────────────────────────");
|
|
11194
|
+
log(`before-state: botCount=${countFeishuBots(configPath)}`);
|
|
11195
|
+
const backup = backupFiles(fsOpts);
|
|
11196
|
+
if (!backup.ok) {
|
|
11197
|
+
log(`ERROR: ${backup.error}`);
|
|
11198
|
+
return {
|
|
11199
|
+
ok: false,
|
|
11200
|
+
error: backup.error,
|
|
11201
|
+
logFile
|
|
11202
|
+
};
|
|
11203
|
+
}
|
|
11204
|
+
log("backup: ok");
|
|
11205
|
+
logVersionSnapshot("before-versions", snapshotVersions(cwd, log), log);
|
|
11206
|
+
log("");
|
|
11207
|
+
log("── [2/6] 清理本地 openclaw shim ─────────────────────────");
|
|
11208
|
+
const localOpenclawBin = node_path.default.join(cwd, "node_modules", ".bin", "openclaw");
|
|
11209
|
+
if (node_fs.default.existsSync(localOpenclawBin)) try {
|
|
11210
|
+
node_fs.default.rmSync(localOpenclawBin);
|
|
11211
|
+
log(` removed: ${localOpenclawBin}`);
|
|
11212
|
+
} catch (e) {
|
|
11213
|
+
log(` WARN: failed to remove ${localOpenclawBin}: ${e.message}`);
|
|
11214
|
+
}
|
|
11215
|
+
else log(` skipped: ${localOpenclawBin} (not found)`);
|
|
11216
|
+
log("");
|
|
11217
|
+
log("── [3/6] npx install (@larksuite/openclaw-lark-tools update) ──");
|
|
11218
|
+
const npxResult = (0, node_child_process.spawnSync)("npx", [
|
|
11219
|
+
"-y",
|
|
11220
|
+
"@larksuite/openclaw-lark-tools",
|
|
11221
|
+
"update"
|
|
11222
|
+
], {
|
|
11223
|
+
cwd,
|
|
11224
|
+
encoding: "utf-8",
|
|
11225
|
+
stdio: [
|
|
11226
|
+
"ignore",
|
|
11227
|
+
"pipe",
|
|
11228
|
+
"pipe"
|
|
11229
|
+
],
|
|
11230
|
+
timeout: 12e4
|
|
11231
|
+
});
|
|
11232
|
+
const npxStdout = npxResult.stdout?.trim() ?? "";
|
|
11233
|
+
const npxStderr = npxResult.stderr?.trim() ?? "";
|
|
11234
|
+
const npxExitCode = npxResult.status ?? 1;
|
|
11235
|
+
if (npxStdout) log(`npx stdout:\n${npxStdout}`);
|
|
11236
|
+
if (npxStderr) log(`npx stderr:\n${npxStderr}`);
|
|
11237
|
+
log(`npx exit: ${npxExitCode}${npxResult.error ? ` error: ${npxResult.error.message}` : ""}`);
|
|
11238
|
+
if (statusCheckDelayMs > 0) {
|
|
11239
|
+
log("");
|
|
11240
|
+
log(`── 等待 ${statusCheckDelayMs / 1e3}s(让 openclaw 服务完成重启) ─────────────`);
|
|
11241
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, statusCheckDelayMs);
|
|
11242
|
+
log("wait done");
|
|
11243
|
+
}
|
|
11244
|
+
const doRollback = (reason) => {
|
|
11245
|
+
log(`ERROR: ${reason}`);
|
|
11246
|
+
const rollbackOk = restoreFiles(fsOpts);
|
|
11247
|
+
log(`rollback: ${rollbackOk ? "ok" : "FAILED"}`);
|
|
11248
|
+
return {
|
|
11249
|
+
ok: false,
|
|
11250
|
+
error: reason,
|
|
11251
|
+
validationError: reason,
|
|
11252
|
+
stdout: npxStdout,
|
|
11253
|
+
stderr: npxStderr,
|
|
11254
|
+
exitCode: npxExitCode,
|
|
11255
|
+
rollbackOk,
|
|
11256
|
+
logFile
|
|
11257
|
+
};
|
|
11258
|
+
};
|
|
11259
|
+
log("");
|
|
11260
|
+
log("── [4/6] 插件安装检查 + 版本兼容校验 ───────────────────");
|
|
11261
|
+
const larkExtDir = node_path.default.join(cwd, "extensions", "openclaw-lark");
|
|
11262
|
+
const larkVersion = readPkgVersion(node_path.default.join(larkExtDir, "package.json"));
|
|
11263
|
+
log(` extensions/openclaw-lark: ${node_fs.default.existsSync(larkExtDir) ? "exists" : "missing"}, version=${larkVersion ?? "n/a"}`);
|
|
11264
|
+
if (!node_fs.default.existsSync(larkExtDir)) return doRollback("extensions/openclaw-lark not found after install");
|
|
11265
|
+
if (!larkVersion) return doRollback("extensions/openclaw-lark/package.json has no valid version after install");
|
|
11266
|
+
log(" running doctor version compat check...");
|
|
11267
|
+
const compatArgs = ["doctor"];
|
|
11268
|
+
if (opts.scene) compatArgs.push(`--scene=${opts.scene}`);
|
|
11269
|
+
const compatResult = (0, node_child_process.spawnSync)(process.execPath, [cliScript, ...compatArgs], {
|
|
11270
|
+
cwd,
|
|
11271
|
+
encoding: "utf-8",
|
|
11272
|
+
stdio: [
|
|
11273
|
+
"ignore",
|
|
11274
|
+
"pipe",
|
|
11275
|
+
"pipe"
|
|
11276
|
+
],
|
|
11277
|
+
timeout: 6e4,
|
|
11278
|
+
env: process.env
|
|
11279
|
+
});
|
|
11280
|
+
if (compatResult.stdout?.trim()) log(`doctor(compat) stdout:\n${compatResult.stdout.trim()}`);
|
|
11281
|
+
if (compatResult.stderr?.trim()) log(`doctor(compat) stderr:\n${compatResult.stderr.trim()}`);
|
|
11282
|
+
log(`doctor(compat) exit: ${compatResult.status ?? "null"}${compatResult.error ? ` error: ${compatResult.error.message}` : ""}`);
|
|
11283
|
+
const compatError = checkVersionCompatFromDoctorOutput(compatResult.stdout?.trim() ?? "", log);
|
|
11284
|
+
if (compatError) return doRollback(compatError);
|
|
11285
|
+
log(" version compat: ok");
|
|
11286
|
+
logVersionSnapshot("after-versions", snapshotVersions(cwd, log), log);
|
|
11287
|
+
log("");
|
|
11288
|
+
log("── [5/6] channels probe(升级后)────────────────────────");
|
|
11289
|
+
if (!probeChannels("after", log, 3e4).anyAccountWorking) {
|
|
11290
|
+
log(" channels: not working before or after install — pre-existing issue, skipping rollback");
|
|
11291
|
+
return {
|
|
11292
|
+
ok: false,
|
|
11293
|
+
error: "channels probe: no working account (pre-existing issue, not caused by install)",
|
|
11294
|
+
validationError: "channels probe: no working account (pre-existing)",
|
|
11295
|
+
stdout: npxStdout,
|
|
11296
|
+
stderr: npxStderr,
|
|
11297
|
+
exitCode: npxExitCode,
|
|
11298
|
+
logFile
|
|
11299
|
+
};
|
|
11300
|
+
}
|
|
11301
|
+
log(" channels: ok (recovered after install)");
|
|
11302
|
+
log("");
|
|
11303
|
+
log("── [6/6] doctor --fix ────────────────────────────────────");
|
|
11304
|
+
const fixArgs = ["doctor", "--fix"];
|
|
11305
|
+
if (opts.scene) fixArgs.push(`--scene=${opts.scene}`);
|
|
11306
|
+
const fixResult = (0, node_child_process.spawnSync)(process.execPath, [cliScript, ...fixArgs], {
|
|
11307
|
+
cwd,
|
|
11308
|
+
encoding: "utf-8",
|
|
11309
|
+
stdio: [
|
|
11310
|
+
"ignore",
|
|
11311
|
+
"pipe",
|
|
11312
|
+
"pipe"
|
|
11313
|
+
],
|
|
11314
|
+
timeout: 6e4,
|
|
11315
|
+
env: process.env
|
|
11316
|
+
});
|
|
11317
|
+
if (fixResult.stdout?.trim()) log(`doctor(fix) stdout:\n${fixResult.stdout.trim()}`);
|
|
11318
|
+
if (fixResult.stderr?.trim()) log(`doctor(fix) stderr:\n${fixResult.stderr.trim()}`);
|
|
11319
|
+
log(`doctor(fix) exit: ${fixResult.status ?? "null"}${fixResult.error ? ` error: ${fixResult.error.message}` : ""}`);
|
|
11320
|
+
log("");
|
|
11321
|
+
log(`${"=".repeat(60)}`);
|
|
11322
|
+
log("upgrade-lark completed successfully");
|
|
11323
|
+
log(`${"=".repeat(60)}`);
|
|
11324
|
+
return {
|
|
11325
|
+
ok: true,
|
|
11326
|
+
stdout: npxStdout,
|
|
11327
|
+
stderr: npxStderr,
|
|
11328
|
+
exitCode: npxExitCode,
|
|
11329
|
+
logFile
|
|
11330
|
+
};
|
|
11331
|
+
}
|
|
10767
11332
|
//#endregion
|
|
10768
11333
|
//#region src/index.ts
|
|
10769
11334
|
const args = node_process.default.argv.slice(2);
|
|
10770
11335
|
const mode = args.find((a) => !a.startsWith("-"));
|
|
10771
11336
|
/**
|
|
11337
|
+
* Decode `--ctx=<base64>` into an opaque JSON object. Returns undefined when
|
|
11338
|
+
* the flag isn't present — the caller decides whether to fall back to the
|
|
11339
|
+
* innerapi or to error out.
|
|
11340
|
+
*
|
|
11341
|
+
* The object's shape is not enforced here; downstream code consumes it via
|
|
11342
|
+
* either `normalizeCtx()` (new path) or direct field access for the legacy
|
|
11343
|
+
* check/repair/reset contract still used by sandbox_console push.
|
|
11344
|
+
*/
|
|
11345
|
+
function parseCtxFlag(args) {
|
|
11346
|
+
const ctxArg = args.find((a) => a.startsWith("--ctx="));
|
|
11347
|
+
if (!ctxArg) return void 0;
|
|
11348
|
+
const b64 = ctxArg.slice(6);
|
|
11349
|
+
return JSON.parse(Buffer.from(b64, "base64").toString("utf-8"));
|
|
11350
|
+
}
|
|
11351
|
+
/**
|
|
10772
11352
|
* Pull the first non-flag positional after the mode name.
|
|
10773
11353
|
* (The mode itself is args[0] in the filtered set, so we skip index 0.)
|
|
10774
11354
|
*/
|
|
@@ -10796,8 +11376,8 @@ function getMultiFlag(args, name) {
|
|
|
10796
11376
|
* case but is no longer consulted.
|
|
10797
11377
|
*/
|
|
10798
11378
|
async function reportRun(command, rc, _raw, invocation, durationMs, outcome, slardar = {
|
|
10799
|
-
scene
|
|
10800
|
-
profile
|
|
11379
|
+
scene,
|
|
11380
|
+
profile,
|
|
10801
11381
|
fix: false
|
|
10802
11382
|
}) {
|
|
10803
11383
|
console.error(`${command}: telemetry calling report_cli_run`);
|
|
@@ -10861,7 +11441,7 @@ async function main() {
|
|
|
10861
11441
|
console.error(`${mode}: begin argv=[${args.join(" ")}] version=${getVersion()} traceId=${traceId ?? "-"} caller=${caller ?? "-"} runIdGenerated=${rc.generated}`);
|
|
10862
11442
|
switch (mode) {
|
|
10863
11443
|
case "check": {
|
|
10864
|
-
const raw = await fetchCtxViaInnerApi({
|
|
11444
|
+
const raw = parseCtxFlag(args) ?? await fetchCtxViaInnerApi({
|
|
10865
11445
|
populate: planCtxPopulate({
|
|
10866
11446
|
command: "check",
|
|
10867
11447
|
profile
|
|
@@ -10886,7 +11466,7 @@ async function main() {
|
|
|
10886
11466
|
break;
|
|
10887
11467
|
}
|
|
10888
11468
|
case "repair": {
|
|
10889
|
-
const raw = await fetchCtxViaInnerApi({
|
|
11469
|
+
const raw = parseCtxFlag(args) ?? await fetchCtxViaInnerApi({
|
|
10890
11470
|
populate: planCtxPopulate({
|
|
10891
11471
|
command: "repair",
|
|
10892
11472
|
profile
|
|
@@ -10957,15 +11537,27 @@ async function main() {
|
|
|
10957
11537
|
break;
|
|
10958
11538
|
}
|
|
10959
11539
|
case "reset":
|
|
10960
|
-
if (args.includes("--async"))
|
|
10961
|
-
|
|
11540
|
+
if (args.includes("--async")) {
|
|
11541
|
+
const ctxArg = args.find((a) => a.startsWith("--ctx="));
|
|
11542
|
+
let ctxBase64;
|
|
11543
|
+
if (ctxArg) ctxBase64 = ctxArg.slice(6);
|
|
11544
|
+
else {
|
|
11545
|
+
const fetched = await fetchCtxViaInnerApi({
|
|
11546
|
+
populate: planCtxPopulate({ command: "reset" }),
|
|
11547
|
+
caller,
|
|
11548
|
+
traceId
|
|
11549
|
+
});
|
|
11550
|
+
ctxBase64 = Buffer.from(JSON.stringify(fetched), "utf-8").toString("base64");
|
|
11551
|
+
}
|
|
11552
|
+
console.log(JSON.stringify(startAsyncReset(ctxBase64)));
|
|
11553
|
+
} else if (args.includes("--worker")) {
|
|
10962
11554
|
const taskId = args.find((a) => a.startsWith("--task-id="))?.slice(10);
|
|
10963
11555
|
if (!taskId) {
|
|
10964
11556
|
console.error("Error: --task-id=<id> is required for worker");
|
|
10965
11557
|
node_process.default.exit(1);
|
|
10966
11558
|
}
|
|
10967
11559
|
const resultFile = resetResultFile(taskId);
|
|
10968
|
-
const raw = await fetchCtxViaInnerApi({
|
|
11560
|
+
const raw = parseCtxFlag(args) ?? await fetchCtxViaInnerApi({
|
|
10969
11561
|
populate: planCtxPopulate({ command: "reset" }),
|
|
10970
11562
|
caller,
|
|
10971
11563
|
traceId
|
|
@@ -10989,7 +11581,7 @@ async function main() {
|
|
|
10989
11581
|
return;
|
|
10990
11582
|
}
|
|
10991
11583
|
} else {
|
|
10992
|
-
console.error("Usage: reset --async | reset --worker --task-id=<id>");
|
|
11584
|
+
console.error("Usage: reset --async [--ctx=<base64>] | reset --worker --task-id=<id> [--ctx=<base64>]");
|
|
10993
11585
|
node_process.default.exit(1);
|
|
10994
11586
|
}
|
|
10995
11587
|
break;
|
|
@@ -11005,14 +11597,14 @@ async function main() {
|
|
|
11005
11597
|
case "install-openclaw": {
|
|
11006
11598
|
const tag = getPositionalTag(args, "install-openclaw");
|
|
11007
11599
|
if (!tag) {
|
|
11008
|
-
console.error("Usage: install-openclaw <tag> [--oss_file_map=<base64>]");
|
|
11600
|
+
console.error("Usage: install-openclaw <tag> [--ctx=<base64> | --oss_file_map=<base64>]");
|
|
11009
11601
|
node_process.default.exit(1);
|
|
11010
11602
|
}
|
|
11011
11603
|
const ossFileMapFlag = getFlag(args, "oss_file_map");
|
|
11012
11604
|
let installOssFileMap;
|
|
11013
11605
|
let rawForTelemetry;
|
|
11014
11606
|
if (!ossFileMapFlag) {
|
|
11015
|
-
rawForTelemetry = await fetchCtxViaInnerApi({
|
|
11607
|
+
rawForTelemetry = parseCtxFlag(args) ?? await fetchCtxViaInnerApi({
|
|
11016
11608
|
populate: planCtxPopulate({ command: "install" }),
|
|
11017
11609
|
caller,
|
|
11018
11610
|
traceId
|
|
@@ -11047,7 +11639,7 @@ async function main() {
|
|
|
11047
11639
|
case "install-extension": {
|
|
11048
11640
|
const tag = getPositionalTag(args, "install-extension");
|
|
11049
11641
|
if (!tag) {
|
|
11050
|
-
console.error("Usage: install-extension <tag> (--all | --extension=<name>...) [--home_base=<dir>] [--config_path=<path>] [--skip-config-update] [--oss_file_map=<base64>]");
|
|
11642
|
+
console.error("Usage: install-extension <tag> (--all | --extension=<name>...) [--home_base=<dir>] [--config_path=<path>] [--skip-config-update] [--ctx=<base64> | --oss_file_map=<base64>]");
|
|
11051
11643
|
node_process.default.exit(1);
|
|
11052
11644
|
}
|
|
11053
11645
|
const all = args.includes("--all");
|
|
@@ -11059,7 +11651,7 @@ async function main() {
|
|
|
11059
11651
|
let installOssFileMap;
|
|
11060
11652
|
let rawForTelemetry;
|
|
11061
11653
|
if (!ossFileMapFlag) {
|
|
11062
|
-
rawForTelemetry = await fetchCtxViaInnerApi({
|
|
11654
|
+
rawForTelemetry = parseCtxFlag(args) ?? await fetchCtxViaInnerApi({
|
|
11063
11655
|
populate: planCtxPopulate({ command: "install" }),
|
|
11064
11656
|
caller,
|
|
11065
11657
|
traceId
|
|
@@ -11105,12 +11697,12 @@ async function main() {
|
|
|
11105
11697
|
case "install-cli": {
|
|
11106
11698
|
const tag = getPositionalTag(args, "install-cli");
|
|
11107
11699
|
if (!tag) {
|
|
11108
|
-
console.error("Usage: install-cli <tag> --cli=<name>... [--home_base=<dir>] [--oss_file_map=<base64>]");
|
|
11700
|
+
console.error("Usage: install-cli <tag> --cli=<name>... [--home_base=<dir>] [--ctx=<base64> | --oss_file_map=<base64>]");
|
|
11109
11701
|
node_process.default.exit(1);
|
|
11110
11702
|
}
|
|
11111
11703
|
const names = getMultiFlag(args, "cli");
|
|
11112
11704
|
if (names.length === 0) {
|
|
11113
|
-
console.error("Usage: install-cli <tag> --cli=<name>... [--home_base=<dir>] [--oss_file_map=<base64>]");
|
|
11705
|
+
console.error("Usage: install-cli <tag> --cli=<name>... [--home_base=<dir>] [--ctx=<base64> | --oss_file_map=<base64>]");
|
|
11114
11706
|
node_process.default.exit(1);
|
|
11115
11707
|
}
|
|
11116
11708
|
const homeBase = getFlag(args, "home_base");
|
|
@@ -11118,7 +11710,7 @@ async function main() {
|
|
|
11118
11710
|
let installOssFileMap;
|
|
11119
11711
|
let rawForTelemetry;
|
|
11120
11712
|
if (!ossFileMapFlag) {
|
|
11121
|
-
rawForTelemetry = await fetchCtxViaInnerApi({
|
|
11713
|
+
rawForTelemetry = parseCtxFlag(args) ?? await fetchCtxViaInnerApi({
|
|
11122
11714
|
populate: planCtxPopulate({ command: "install" }),
|
|
11123
11715
|
caller,
|
|
11124
11716
|
traceId
|
|
@@ -11166,7 +11758,7 @@ async function main() {
|
|
|
11166
11758
|
case "download-resource": {
|
|
11167
11759
|
const tag = getPositionalTag(args, "download-resource");
|
|
11168
11760
|
if (!tag) {
|
|
11169
|
-
console.error("Usage: download-resource <tag> --role=<role> --name=<name> [--dir=<dir>] [--oss_file_map=<base64>]");
|
|
11761
|
+
console.error("Usage: download-resource <tag> --role=<role> --name=<name> [--dir=<dir>] [--ctx=<base64> | --oss_file_map=<base64>]");
|
|
11170
11762
|
node_process.default.exit(1);
|
|
11171
11763
|
}
|
|
11172
11764
|
const role = getFlag(args, "role");
|
|
@@ -11180,7 +11772,7 @@ async function main() {
|
|
|
11180
11772
|
let installOssFileMap;
|
|
11181
11773
|
let rawForTelemetry;
|
|
11182
11774
|
if (!ossFileMapFlag) {
|
|
11183
|
-
rawForTelemetry = await fetchCtxViaInnerApi({
|
|
11775
|
+
rawForTelemetry = parseCtxFlag(args) ?? await fetchCtxViaInnerApi({
|
|
11184
11776
|
populate: planCtxPopulate({ command: "install" }),
|
|
11185
11777
|
caller,
|
|
11186
11778
|
traceId
|
|
@@ -11254,6 +11846,50 @@ async function main() {
|
|
|
11254
11846
|
if (!result.ok) node_process.default.exit(1);
|
|
11255
11847
|
break;
|
|
11256
11848
|
}
|
|
11849
|
+
case "upgrade-lark": {
|
|
11850
|
+
const result = runUpgradeLark({
|
|
11851
|
+
runId: rc.runId,
|
|
11852
|
+
scene
|
|
11853
|
+
});
|
|
11854
|
+
const upgradeDurationMs = Date.now() - t0;
|
|
11855
|
+
console.log(JSON.stringify(result));
|
|
11856
|
+
reportUpgradeLarkToSlardar({
|
|
11857
|
+
scene,
|
|
11858
|
+
durationMs: upgradeDurationMs,
|
|
11859
|
+
success: result.ok,
|
|
11860
|
+
logFile: result.logFile,
|
|
11861
|
+
exitCode: result.exitCode,
|
|
11862
|
+
rollbackOk: result.rollbackOk,
|
|
11863
|
+
validationError: result.validationError,
|
|
11864
|
+
error: result.error
|
|
11865
|
+
});
|
|
11866
|
+
try {
|
|
11867
|
+
await reportCliRun({
|
|
11868
|
+
command: "upgrade-lark",
|
|
11869
|
+
runId: rc.runId,
|
|
11870
|
+
version: getVersion(),
|
|
11871
|
+
invocation: args.join(" "),
|
|
11872
|
+
durationMs: upgradeDurationMs,
|
|
11873
|
+
caller: rc.caller,
|
|
11874
|
+
traceId: rc.traceId,
|
|
11875
|
+
success: result.ok,
|
|
11876
|
+
result,
|
|
11877
|
+
error: result.ok ? void 0 : { message: result.error ?? "upgrade-lark failed" }
|
|
11878
|
+
});
|
|
11879
|
+
} catch (e) {
|
|
11880
|
+
console.error(`[telemetry] reportCliRun failed: ${e.message}`);
|
|
11881
|
+
}
|
|
11882
|
+
if (!result.ok) {
|
|
11883
|
+
node_process.default.exitCode = 1;
|
|
11884
|
+
return;
|
|
11885
|
+
}
|
|
11886
|
+
break;
|
|
11887
|
+
}
|
|
11888
|
+
case "channels-probe": {
|
|
11889
|
+
const result = runChannelsProbe(getFlag(args, "timeout") ? Number(getFlag(args, "timeout")) : void 0);
|
|
11890
|
+
console.log(JSON.stringify(result));
|
|
11891
|
+
break;
|
|
11892
|
+
}
|
|
11257
11893
|
default:
|
|
11258
11894
|
node_process.default.stderr.write(`Unknown command: ${mode}\n\n`);
|
|
11259
11895
|
node_process.default.stderr.write(formatTopLevelHelp(helpFlags.expert));
|