@lark-apaas/openclaw-scripts-diagnose-cli 0.1.14-alpha.0 → 0.1.14-alpha.10
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 +860 -222
- 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.10";
|
|
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);
|
|
@@ -3370,6 +3369,7 @@ let FeishuPluginOpenclawUpgradeRule = class FeishuPluginOpenclawUpgradeRule exte
|
|
|
3370
3369
|
if (!cc) return { pass: true };
|
|
3371
3370
|
const { ocCur, recommendedOc, installed, isLegacy } = cc;
|
|
3372
3371
|
if (isForkPlugin(installed)) return validateForkPlugin(installed, ocCur, recommendedOc);
|
|
3372
|
+
if (!recommendedOc) return { pass: true };
|
|
3373
3373
|
if (resolveUpgradeDirection(installed, ocCur, recommendedOc, isLegacy) !== "openclaw") return { pass: true };
|
|
3374
3374
|
return {
|
|
3375
3375
|
pass: false,
|
|
@@ -3397,11 +3397,17 @@ let FeishuPluginLarkUpgradeRule = class FeishuPluginLarkUpgradeRule extends Diag
|
|
|
3397
3397
|
if (!cc) return { pass: true };
|
|
3398
3398
|
const { ocCur, recommendedOc, installed, isLegacy } = cc;
|
|
3399
3399
|
if (isForkPlugin(installed)) return { pass: true };
|
|
3400
|
-
if (
|
|
3400
|
+
if (!isLarkUpgradeNeededFromCC(cc)) return { pass: true };
|
|
3401
|
+
const prefix = buildCompatPrefix(installed, ocCur, isLegacy);
|
|
3402
|
+
if (!recommendedOc) return {
|
|
3403
|
+
pass: false,
|
|
3404
|
+
action: "upgrade_lark",
|
|
3405
|
+
message: `${prefix};建议升级飞书插件至兼容版本`
|
|
3406
|
+
};
|
|
3401
3407
|
return {
|
|
3402
3408
|
pass: false,
|
|
3403
3409
|
action: "upgrade_lark",
|
|
3404
|
-
message: `${
|
|
3410
|
+
message: `${prefix};当前 openclaw@${ocCur} 已达推荐版本,可直接升级飞书插件`
|
|
3405
3411
|
};
|
|
3406
3412
|
}
|
|
3407
3413
|
};
|
|
@@ -3413,6 +3419,18 @@ FeishuPluginLarkUpgradeRule = __decorate([Rule({
|
|
|
3413
3419
|
level: "critical",
|
|
3414
3420
|
usesVars: ["recommendedOpenclawTag"]
|
|
3415
3421
|
})], FeishuPluginLarkUpgradeRule);
|
|
3422
|
+
/**
|
|
3423
|
+
* Core predicate: returns true when the lark plugin needs upgrading for a
|
|
3424
|
+
* non-fork plugin, based on version compatibility with the current openclaw.
|
|
3425
|
+
*
|
|
3426
|
+
* Shared by FeishuPluginLarkUpgradeRule.validate and needsLarkUpgrade.
|
|
3427
|
+
* Callers must handle fork plugin cases before invoking this function.
|
|
3428
|
+
*/
|
|
3429
|
+
function isLarkUpgradeNeededFromCC(cc) {
|
|
3430
|
+
const { ocCur, recommendedOc, installed, isLegacy } = cc;
|
|
3431
|
+
if (!recommendedOc) return isLegacy || !isVersionCompatible(installed, ocCur);
|
|
3432
|
+
return resolveUpgradeDirection(installed, ocCur, recommendedOc, isLegacy) === "lark";
|
|
3433
|
+
}
|
|
3416
3434
|
function isForkPlugin(p) {
|
|
3417
3435
|
return p.scope != null && FORK_SCOPES.includes(p.scope);
|
|
3418
3436
|
}
|
|
@@ -3451,14 +3469,16 @@ function describeCompatConstraint(entry, pluginVersion) {
|
|
|
3451
3469
|
/**
|
|
3452
3470
|
* @lark-apaas/openclaw-lark 豁免 VERSION_COMPAT_MAP,但仍要求 openclaw ≥ FORK_LARK_PLUGIN_MIN_OC_VERSION。
|
|
3453
3471
|
* 其他 @lark-apaas scope 的 fork 插件继续无条件 pass。
|
|
3472
|
+
* recommendedOc 可为 undefined(doctor 模式),此时只检测最低版本要求,不指定目标升级版本。
|
|
3454
3473
|
*/
|
|
3455
3474
|
function validateForkPlugin(installed, ocCur, recommendedOc) {
|
|
3456
3475
|
if (installed.fullName !== FORK_LARK_PLUGIN_FULL_NAME) return { pass: true };
|
|
3457
3476
|
if (compareCalVer(ocCur, FORK_LARK_PLUGIN_MIN_OC_VERSION) >= 0) return { pass: true };
|
|
3477
|
+
const recommendation = recommendedOc ? `;将 openclaw 升级到 ${recommendedOc} 即可满足` : `;请升级 openclaw 至 ${FORK_LARK_PLUGIN_MIN_OC_VERSION} 或更高版本`;
|
|
3458
3478
|
return {
|
|
3459
3479
|
pass: false,
|
|
3460
3480
|
action: "upgrade_openclaw",
|
|
3461
|
-
message: `飞书插件 ${describePlugin(installed)}(fork 版)要求 openclaw ≥ ${FORK_LARK_PLUGIN_MIN_OC_VERSION},当前 openclaw@${ocCur}
|
|
3481
|
+
message: `飞书插件 ${describePlugin(installed)}(fork 版)要求 openclaw ≥ ${FORK_LARK_PLUGIN_MIN_OC_VERSION},当前 openclaw@${ocCur} 低于此要求${recommendation}`
|
|
3462
3482
|
};
|
|
3463
3483
|
}
|
|
3464
3484
|
function describePlugin(p) {
|
|
@@ -3483,13 +3503,13 @@ function detectInstalledPlugin(ctx) {
|
|
|
3483
3503
|
const allow = Array.isArray(allowRaw) ? allowRaw.filter((e) => typeof e === "string") : [];
|
|
3484
3504
|
const extDir = getExtensionsDir(ctx.configPath);
|
|
3485
3505
|
const installs = getNestedMap(ctx.config, "plugins", "installs");
|
|
3486
|
-
for (const name of [PLUGIN_NAME, ...LEGACY_SHORT_NAMES]) {
|
|
3506
|
+
for (const name of [PLUGIN_NAME$1, ...LEGACY_SHORT_NAMES]) {
|
|
3487
3507
|
if (!allow.includes(name)) continue;
|
|
3488
3508
|
const pkgPath = node_path.default.join(extDir, name, "package.json");
|
|
3489
3509
|
if (!node_fs.default.existsSync(pkgPath)) continue;
|
|
3490
3510
|
const pkg = readPluginPackageJson(pkgPath) ?? {};
|
|
3491
3511
|
const installEntry = installs && asRecord(installs[name]);
|
|
3492
|
-
const fullName = pkg.name ?? extractScopedNameFromSpec(installEntry?.spec);
|
|
3512
|
+
const fullName = pkg.name ?? extractScopedNameFromSpec$1(installEntry?.spec);
|
|
3493
3513
|
return {
|
|
3494
3514
|
allowName: name,
|
|
3495
3515
|
fullName,
|
|
@@ -3500,11 +3520,192 @@ function detectInstalledPlugin(ctx) {
|
|
|
3500
3520
|
return null;
|
|
3501
3521
|
}
|
|
3502
3522
|
/** "@scope/name@1.2.3" / "name@1.2.3" / "@scope/name" / "name" → 去掉 @version 后缀 */
|
|
3503
|
-
function extractScopedNameFromSpec(spec) {
|
|
3523
|
+
function extractScopedNameFromSpec$1(spec) {
|
|
3504
3524
|
if (typeof spec !== "string") return void 0;
|
|
3505
3525
|
const at = spec.indexOf("@", 1);
|
|
3506
3526
|
return at === -1 ? spec : spec.slice(0, at);
|
|
3507
3527
|
}
|
|
3528
|
+
/**
|
|
3529
|
+
* Returns true if the installed feishu plugin is version-incompatible with
|
|
3530
|
+
* the current openclaw (or is a legacy plugin that must be replaced).
|
|
3531
|
+
* Used by the upgrade_lark_needed rule and the upgrade-lark pre-check gate.
|
|
3532
|
+
*/
|
|
3533
|
+
function needsLarkUpgrade(ctx) {
|
|
3534
|
+
const cc = resolveCompatContext(ctx);
|
|
3535
|
+
if (!cc) return false;
|
|
3536
|
+
const { ocCur, recommendedOc, installed } = cc;
|
|
3537
|
+
if (isForkPlugin(installed)) {
|
|
3538
|
+
if (recommendedOc) return false;
|
|
3539
|
+
if (installed.fullName === FORK_LARK_PLUGIN_FULL_NAME) return compareCalVer(ocCur, FORK_LARK_PLUGIN_MIN_OC_VERSION) < 0;
|
|
3540
|
+
return false;
|
|
3541
|
+
}
|
|
3542
|
+
return isLarkUpgradeNeededFromCC(cc);
|
|
3543
|
+
}
|
|
3544
|
+
//#endregion
|
|
3545
|
+
//#region src/channels-probe.ts
|
|
3546
|
+
const FEISHU_INVALID_CONFIG_MSG = "channels.feishu: invalid config: must NOT have additional properties";
|
|
3547
|
+
const CHANNEL_LINE_RE = /^-\s+Feishu\s+([^:]+):\s+(.+)$/;
|
|
3548
|
+
/**
|
|
3549
|
+
* Port of Python `_account_is_working` from the feishu-channel-success-rate skill.
|
|
3550
|
+
*
|
|
3551
|
+
* Strips colon-prefixed key:value bits (dm:, bot:, in:, out:, token:, allow:,
|
|
3552
|
+
* intents:, groups:, health:) and evaluates the canonical health formula.
|
|
3553
|
+
*/
|
|
3554
|
+
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);
|
|
3508
3709
|
//#endregion
|
|
3509
3710
|
//#region src/rules/cleanup-install-backup-dirs.ts
|
|
3510
3711
|
const DIR_PREFIX = ".openclaw-install-";
|
|
@@ -3555,110 +3756,100 @@ CleanupInstallBackupDirsRule = __decorate([Rule({
|
|
|
3555
3756
|
level: "critical"
|
|
3556
3757
|
})], CleanupInstallBackupDirsRule);
|
|
3557
3758
|
//#endregion
|
|
3558
|
-
//#region src/rules/
|
|
3559
|
-
|
|
3560
|
-
|
|
3561
|
-
|
|
3562
|
-
|
|
3563
|
-
|
|
3564
|
-
|
|
3565
|
-
|
|
3566
|
-
|
|
3567
|
-
|
|
3759
|
+
//#region src/rules/lark-cli-missing-for-installed-lark-plugin.ts
|
|
3760
|
+
const PLUGIN_NAME = "openclaw-lark";
|
|
3761
|
+
const FORK_PACKAGE_NAME = "@lark-apaas/openclaw-lark";
|
|
3762
|
+
const TARGET_VERSION = "2026.4.4";
|
|
3763
|
+
const LARK_CLI_NAME$1 = "lark-cli";
|
|
3764
|
+
function readInstalledLarkPlugin(ctx) {
|
|
3765
|
+
const pkgPath = node_path.default.join(getExtensionsDir(ctx.configPath), PLUGIN_NAME, "package.json");
|
|
3766
|
+
if (!node_fs.default.existsSync(pkgPath)) return null;
|
|
3767
|
+
let pkg = {};
|
|
3768
|
+
try {
|
|
3769
|
+
const parsed = JSON.parse(node_fs.default.readFileSync(pkgPath, "utf-8"));
|
|
3770
|
+
pkg = {
|
|
3771
|
+
name: typeof parsed.name === "string" ? parsed.name : void 0,
|
|
3772
|
+
version: typeof parsed.version === "string" ? parsed.version : void 0
|
|
3773
|
+
};
|
|
3774
|
+
} catch {
|
|
3775
|
+
pkg = {};
|
|
3776
|
+
}
|
|
3777
|
+
const installs = getNestedMap(ctx.config, "plugins", "installs");
|
|
3778
|
+
const installEntry = installs ? asRecord(installs[PLUGIN_NAME]) : void 0;
|
|
3779
|
+
return {
|
|
3780
|
+
name: pkg.name ?? extractScopedNameFromSpec(installEntry?.spec),
|
|
3781
|
+
version: pkg.version ?? (typeof installEntry?.version === "string" ? installEntry.version : void 0)
|
|
3782
|
+
};
|
|
3783
|
+
}
|
|
3784
|
+
function isTargetForkPlugin(plugin) {
|
|
3785
|
+
return plugin?.name === FORK_PACKAGE_NAME && plugin.version === TARGET_VERSION;
|
|
3786
|
+
}
|
|
3787
|
+
function extractScopedNameFromSpec(spec) {
|
|
3788
|
+
if (typeof spec !== "string") return void 0;
|
|
3789
|
+
const at = spec.indexOf("@", 1);
|
|
3790
|
+
return at === -1 ? spec : spec.slice(0, at);
|
|
3791
|
+
}
|
|
3792
|
+
function isLarkCliAvailable$1() {
|
|
3793
|
+
try {
|
|
3794
|
+
return (0, node_child_process.spawnSync)(LARK_CLI_NAME$1, ["--version"], {
|
|
3795
|
+
encoding: "utf-8",
|
|
3796
|
+
timeout: 5e3,
|
|
3797
|
+
stdio: [
|
|
3798
|
+
"ignore",
|
|
3799
|
+
"pipe",
|
|
3800
|
+
"ignore"
|
|
3801
|
+
]
|
|
3802
|
+
}).status === 0;
|
|
3803
|
+
} catch {
|
|
3804
|
+
return false;
|
|
3805
|
+
}
|
|
3806
|
+
}
|
|
3807
|
+
function installLarkCliOnce(tag) {
|
|
3808
|
+
const entry = process.argv[1];
|
|
3809
|
+
if (!entry) throw new Error("cannot resolve diagnose-cli entrypoint for lark-cli install");
|
|
3810
|
+
const res = (0, node_child_process.spawnSync)(process.execPath, [
|
|
3811
|
+
entry,
|
|
3812
|
+
"install-cli",
|
|
3813
|
+
tag,
|
|
3814
|
+
"--cli=lark-cli"
|
|
3815
|
+
], {
|
|
3816
|
+
encoding: "utf-8",
|
|
3817
|
+
stdio: [
|
|
3818
|
+
"ignore",
|
|
3819
|
+
"pipe",
|
|
3820
|
+
"pipe"
|
|
3821
|
+
]
|
|
3822
|
+
});
|
|
3823
|
+
const stdout = res.stdout?.trim();
|
|
3824
|
+
const stderr = res.stderr?.trim();
|
|
3825
|
+
if (stdout) console.error(`[lark-cli-missing] install-cli stdout: ${stdout}`);
|
|
3826
|
+
if (stderr) console.error(`[lark-cli-missing] install-cli stderr: ${stderr}`);
|
|
3827
|
+
if (res.error) throw new Error(`install-cli spawn error: ${res.error.message}`);
|
|
3828
|
+
if (res.status !== 0) throw new Error(`install-cli exited with code ${res.status ?? "unknown"}`);
|
|
3829
|
+
}
|
|
3830
|
+
let LarkCliMissingForInstalledLarkPluginRule = class LarkCliMissingForInstalledLarkPluginRule extends DiagnoseRule {
|
|
3568
3831
|
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 };
|
|
3832
|
+
if (!isTargetForkPlugin(readInstalledLarkPlugin(ctx))) return { pass: true };
|
|
3833
|
+
if (isLarkCliAvailable$1()) return { pass: true };
|
|
3590
3834
|
return {
|
|
3591
3835
|
pass: false,
|
|
3592
|
-
message:
|
|
3836
|
+
message: `${FORK_PACKAGE_NAME}@${TARGET_VERSION} 已安装,但 lark-cli 不可用;将执行一次 lark-cli 安装`
|
|
3593
3837
|
};
|
|
3594
3838
|
}
|
|
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
3839
|
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 };
|
|
3840
|
+
if (!isTargetForkPlugin(readInstalledLarkPlugin(ctx))) return;
|
|
3841
|
+
if (isLarkCliAvailable$1()) return;
|
|
3842
|
+
installLarkCliOnce(ctx.vars.recommendedOpenclawTag ?? TARGET_VERSION);
|
|
3648
3843
|
}
|
|
3649
3844
|
};
|
|
3650
|
-
|
|
3651
|
-
key: "
|
|
3652
|
-
description: "
|
|
3653
|
-
dependsOn: [
|
|
3654
|
-
"config_syntax_check",
|
|
3655
|
-
"feishu_default_account",
|
|
3656
|
-
"feishu_bot_id"
|
|
3657
|
-
],
|
|
3845
|
+
LarkCliMissingForInstalledLarkPluginRule = __decorate([Rule({
|
|
3846
|
+
key: "lark_cli_missing_for_installed_lark_plugin",
|
|
3847
|
+
description: "检测特定飞书插件版本已安装但 lark-cli 缺失的环境,并自动安装 lark-cli 一次",
|
|
3848
|
+
dependsOn: ["config_syntax_check"],
|
|
3658
3849
|
repairMode: "standard",
|
|
3659
|
-
|
|
3660
|
-
|
|
3661
|
-
})],
|
|
3850
|
+
level: "critical",
|
|
3851
|
+
usesVars: ["recommendedOpenclawTag"]
|
|
3852
|
+
})], LarkCliMissingForInstalledLarkPluginRule);
|
|
3662
3853
|
//#endregion
|
|
3663
3854
|
//#region src/check.ts
|
|
3664
3855
|
/** Telemetry-aware entry: returns both the legacy CheckResult (for stdout)
|
|
@@ -4123,6 +4314,9 @@ const PROVIDER_FILE_PATH = "/home/gem/workspace/.force/openclaw/miaoda-provider-
|
|
|
4123
4314
|
const SECRETS_FILE_PATH = "/home/gem/workspace/.force/openclaw/miaoda-openclaw-secrets.json";
|
|
4124
4315
|
/** Absolute path to the openclaw config JSON. */
|
|
4125
4316
|
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
|
+
}
|
|
4126
4320
|
//#endregion
|
|
4127
4321
|
//#region src/run-log.ts
|
|
4128
4322
|
let currentRunContext;
|
|
@@ -4259,10 +4453,9 @@ function makeLogger(logFile) {
|
|
|
4259
4453
|
/**
|
|
4260
4454
|
* Start an async reset task: spawn a detached child process and return the taskId.
|
|
4261
4455
|
*
|
|
4262
|
-
* The child process runs: node cli.js reset --worker --task-id=xxx
|
|
4263
|
-
* The worker fetches ctx from innerApi itself — no --ctx passthrough.
|
|
4456
|
+
* The child process runs: node cli.js reset --worker --task-id=xxx --ctx=base64
|
|
4264
4457
|
*/
|
|
4265
|
-
function startAsyncReset() {
|
|
4458
|
+
function startAsyncReset(ctxBase64) {
|
|
4266
4459
|
const taskId = (0, node_crypto.randomUUID)();
|
|
4267
4460
|
const resultFile = resetResultFile(taskId);
|
|
4268
4461
|
const log = makeLogger(resetLogFile(taskId));
|
|
@@ -4286,7 +4479,8 @@ function startAsyncReset() {
|
|
|
4286
4479
|
process.argv[1],
|
|
4287
4480
|
"reset",
|
|
4288
4481
|
"--worker",
|
|
4289
|
-
`--task-id=${taskId}
|
|
4482
|
+
`--task-id=${taskId}`,
|
|
4483
|
+
`--ctx=${ctxBase64}`
|
|
4290
4484
|
], {
|
|
4291
4485
|
detached: true,
|
|
4292
4486
|
stdio: "ignore",
|
|
@@ -6800,60 +6994,6 @@ function mergeCoreBackupAndOrigins(configPath, vars, resetData, log) {
|
|
|
6800
6994
|
log(`allowedOrigins: added ${added.length} (${JSON.stringify(added)}), total now ${mergedOrigins.length}`);
|
|
6801
6995
|
}
|
|
6802
6996
|
/**
|
|
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
6997
|
* Step 7: Verify startup scripts landed in configDir/scripts/.
|
|
6858
6998
|
*
|
|
6859
6999
|
* Scripts are extracted directly to configDir/scripts/ during stageTemplate —
|
|
@@ -6998,7 +7138,6 @@ async function runReset(input, taskId, resultFile) {
|
|
|
6998
7138
|
await step5InstallOpenclaw(openclawTag, ossFileMap, log);
|
|
6999
7139
|
step(6);
|
|
7000
7140
|
mergeCoreBackupAndOrigins(configPath, vars, resetData, log);
|
|
7001
|
-
fixBotChannelConfig(configPath, vars.larkApps, log);
|
|
7002
7141
|
step(7);
|
|
7003
7142
|
verifyStartupScripts(configDir, log);
|
|
7004
7143
|
step(8);
|
|
@@ -7797,8 +7936,7 @@ function normalizeCtx(raw) {
|
|
|
7797
7936
|
reset: {
|
|
7798
7937
|
templateVars: r.reset.templateVars ?? {},
|
|
7799
7938
|
coreBackup: r.reset.coreBackup
|
|
7800
|
-
}
|
|
7801
|
-
larkApps: Array.isArray(r.larkApps) ? r.larkApps : []
|
|
7939
|
+
}
|
|
7802
7940
|
};
|
|
7803
7941
|
}
|
|
7804
7942
|
const vars = r.vars ?? {};
|
|
@@ -7823,8 +7961,7 @@ function normalizeCtx(raw) {
|
|
|
7823
7961
|
reset: {
|
|
7824
7962
|
templateVars: resetData.templateVars ?? {},
|
|
7825
7963
|
coreBackup: resetData.coreBackup
|
|
7826
|
-
}
|
|
7827
|
-
larkApps: Array.isArray(r.larkApps) ? r.larkApps : []
|
|
7964
|
+
}
|
|
7828
7965
|
};
|
|
7829
7966
|
}
|
|
7830
7967
|
function fillApp(src) {
|
|
@@ -7889,8 +8026,7 @@ function buildCheckInput(raw, configPathOverride) {
|
|
|
7889
8026
|
providerFilePath: PROVIDER_FILE_PATH,
|
|
7890
8027
|
secretsFilePath: SECRETS_FILE_PATH,
|
|
7891
8028
|
templateVars: ctx.app.templateVars,
|
|
7892
|
-
recommendedOpenclawTag: ctx.app.recommendedOpenclawTag
|
|
7893
|
-
larkApps: ctx.larkApps
|
|
8029
|
+
recommendedOpenclawTag: ctx.app.recommendedOpenclawTag
|
|
7894
8030
|
},
|
|
7895
8031
|
templateVars: ctx.app.templateVars
|
|
7896
8032
|
};
|
|
@@ -7922,8 +8058,7 @@ function buildRepairInput(raw, configPathOverride) {
|
|
|
7922
8058
|
providerFilePath: PROVIDER_FILE_PATH,
|
|
7923
8059
|
secretsFilePath: SECRETS_FILE_PATH,
|
|
7924
8060
|
templateVars: ctx.app.templateVars,
|
|
7925
|
-
recommendedOpenclawTag: ctx.app.recommendedOpenclawTag
|
|
7926
|
-
larkApps: ctx.larkApps
|
|
8061
|
+
recommendedOpenclawTag: ctx.app.recommendedOpenclawTag
|
|
7927
8062
|
},
|
|
7928
8063
|
repairData: {
|
|
7929
8064
|
secretsContent: ctx.secrets.secretsContent,
|
|
@@ -7959,8 +8094,7 @@ function buildResetInput(raw, configPathOverride) {
|
|
|
7959
8094
|
providerFilePath: PROVIDER_FILE_PATH,
|
|
7960
8095
|
secretsFilePath: SECRETS_FILE_PATH,
|
|
7961
8096
|
templateVars: ctx.app.templateVars,
|
|
7962
|
-
recommendedOpenclawTag: ctx.app.recommendedOpenclawTag
|
|
7963
|
-
larkApps: ctx.larkApps
|
|
8097
|
+
recommendedOpenclawTag: ctx.app.recommendedOpenclawTag
|
|
7964
8098
|
},
|
|
7965
8099
|
resetData: {
|
|
7966
8100
|
templateVars: ctx.reset.templateVars,
|
|
@@ -10270,7 +10404,7 @@ async function reportCliRun(opts) {
|
|
|
10270
10404
|
//#region src/help.ts
|
|
10271
10405
|
const BIN = "mclaw-diagnose";
|
|
10272
10406
|
function versionBanner() {
|
|
10273
|
-
return `v0.1.14-alpha.
|
|
10407
|
+
return `v0.1.14-alpha.10`;
|
|
10274
10408
|
}
|
|
10275
10409
|
const COMMANDS = [
|
|
10276
10410
|
{
|
|
@@ -10374,12 +10508,16 @@ EXIT CODES
|
|
|
10374
10508
|
hidden: true,
|
|
10375
10509
|
summary: "Run rule-engine check only",
|
|
10376
10510
|
help: `USAGE
|
|
10377
|
-
${BIN} check
|
|
10511
|
+
${BIN} check [--ctx=<base64>]
|
|
10378
10512
|
|
|
10379
10513
|
DESCRIPTION
|
|
10380
10514
|
Runs the rule engine against the sandbox's current openclaw config and
|
|
10381
|
-
returns { failedRules }.
|
|
10382
|
-
|
|
10515
|
+
returns { failedRules }. Used by sandbox_console's push-style callers
|
|
10516
|
+
that already own the ctx — end-users should prefer \`doctor\`.
|
|
10517
|
+
|
|
10518
|
+
OPTIONS
|
|
10519
|
+
--ctx=<base64> Opaque ctx JSON (base64). When absent, fetched from
|
|
10520
|
+
innerapi (same path as doctor).
|
|
10383
10521
|
`
|
|
10384
10522
|
},
|
|
10385
10523
|
{
|
|
@@ -10387,11 +10525,16 @@ DESCRIPTION
|
|
|
10387
10525
|
hidden: true,
|
|
10388
10526
|
summary: "Apply standard-mode repairs",
|
|
10389
10527
|
help: `USAGE
|
|
10390
|
-
${BIN} repair
|
|
10528
|
+
${BIN} repair [--ctx=<base64>]
|
|
10391
10529
|
|
|
10392
10530
|
DESCRIPTION
|
|
10393
|
-
Runs repair for the failing rules
|
|
10394
|
-
|
|
10531
|
+
Runs repair for the failing rules listed inside the ctx's repairData.
|
|
10532
|
+
Intended for sandbox_console's push path — end-users should use
|
|
10533
|
+
\`doctor --fix\` instead.
|
|
10534
|
+
|
|
10535
|
+
OPTIONS
|
|
10536
|
+
--ctx=<base64> Opaque ctx JSON (base64). When absent, fetched from
|
|
10537
|
+
innerapi.
|
|
10395
10538
|
`
|
|
10396
10539
|
},
|
|
10397
10540
|
{
|
|
@@ -10399,15 +10542,14 @@ DESCRIPTION
|
|
|
10399
10542
|
hidden: true,
|
|
10400
10543
|
summary: "Re-initialize sandbox via the 9-step reset pipeline",
|
|
10401
10544
|
help: `USAGE
|
|
10402
|
-
${BIN} reset --async
|
|
10403
|
-
${BIN} reset --worker --task-id=<id>
|
|
10545
|
+
${BIN} reset --async [--ctx=<base64>]
|
|
10546
|
+
${BIN} reset --worker --task-id=<id> [--ctx=<base64>]
|
|
10404
10547
|
|
|
10405
10548
|
DESCRIPTION
|
|
10406
10549
|
Two-phase pipeline driven asynchronously: the --async invocation spawns
|
|
10407
10550
|
a detached worker and returns { taskId } immediately; the --worker
|
|
10408
10551
|
invocation (spawned by --async) runs the actual 9 steps and writes
|
|
10409
10552
|
progress to /tmp/openclaw-diagnose/reset-<taskId>.json.
|
|
10410
|
-
Ctx is fetched from innerapi automatically.
|
|
10411
10553
|
|
|
10412
10554
|
Poll progress with \`${BIN} get_reset_task --task-id=<id>\`.
|
|
10413
10555
|
|
|
@@ -10415,6 +10557,7 @@ OPTIONS
|
|
|
10415
10557
|
--async Start a detached worker and return taskId on stdout.
|
|
10416
10558
|
--worker Internal — run the 9-step pipeline (launched by --async).
|
|
10417
10559
|
--task-id=<id> Required with --worker; identifies the progress file.
|
|
10560
|
+
--ctx=<base64> Opaque ctx JSON; fetched from innerapi when absent.
|
|
10418
10561
|
`
|
|
10419
10562
|
},
|
|
10420
10563
|
{
|
|
@@ -10437,7 +10580,7 @@ OPTIONS
|
|
|
10437
10580
|
hidden: true,
|
|
10438
10581
|
summary: "Download + install the openclaw tarball",
|
|
10439
10582
|
help: `USAGE
|
|
10440
|
-
${BIN} install-openclaw <tag> [--oss_file_map=<base64>]
|
|
10583
|
+
${BIN} install-openclaw <tag> [--ctx=<base64> | --oss_file_map=<base64>]
|
|
10441
10584
|
|
|
10442
10585
|
DESCRIPTION
|
|
10443
10586
|
Downloads the openclaw@<tag> tgz via the signed OSS URL found in the
|
|
@@ -10449,9 +10592,9 @@ ARGUMENTS
|
|
|
10449
10592
|
<tag> Openclaw version tag, e.g. 2026.4.11.
|
|
10450
10593
|
|
|
10451
10594
|
OPTIONS
|
|
10595
|
+
--ctx=<base64> Opaque ctx; ossFileMap is extracted from it.
|
|
10452
10596
|
--oss_file_map=... Pre-built OSS URL map (base64 JSON); skips innerapi
|
|
10453
|
-
entirely.
|
|
10454
|
-
innerapi automatically.
|
|
10597
|
+
entirely. Wins over --ctx when both provided.
|
|
10455
10598
|
`
|
|
10456
10599
|
},
|
|
10457
10600
|
{
|
|
@@ -10477,7 +10620,8 @@ OPTIONS
|
|
|
10477
10620
|
--home_base=<dir> Override the /home/gem base (tests).
|
|
10478
10621
|
--config_path=<p> Override the openclaw.json path (tests).
|
|
10479
10622
|
--skip-config-update Leave plugins.installs in openclaw.json untouched.
|
|
10480
|
-
--
|
|
10623
|
+
--ctx=<base64> Opaque ctx; see install-openclaw for semantics.
|
|
10624
|
+
--oss_file_map=... Pre-built OSS URL map (base64 JSON).
|
|
10481
10625
|
`
|
|
10482
10626
|
},
|
|
10483
10627
|
{
|
|
@@ -10504,6 +10648,7 @@ OPTIONS
|
|
|
10504
10648
|
--cli=<name> CLI package to install by short name or scoped
|
|
10505
10649
|
packageName (repeatable, at least one required).
|
|
10506
10650
|
--home_base=<dir> Override the /home/gem base (tests).
|
|
10651
|
+
--ctx=<base64> Opaque ctx; ossFileMap is extracted from it.
|
|
10507
10652
|
--oss_file_map=... Pre-built OSS URL map (base64 JSON); skips innerapi.
|
|
10508
10653
|
|
|
10509
10654
|
EXAMPLES
|
|
@@ -10557,6 +10702,46 @@ OPTIONS
|
|
|
10557
10702
|
EXIT CODES
|
|
10558
10703
|
0 Success or skipped (prerequisites not met).
|
|
10559
10704
|
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).
|
|
10560
10745
|
`
|
|
10561
10746
|
},
|
|
10562
10747
|
{
|
|
@@ -10590,6 +10775,41 @@ EXAMPLES
|
|
|
10590
10775
|
${BIN} rules # all rules
|
|
10591
10776
|
${BIN} rules --rule=gateway # single rule
|
|
10592
10777
|
${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.
|
|
10593
10813
|
`
|
|
10594
10814
|
},
|
|
10595
10815
|
{
|
|
@@ -10610,7 +10830,8 @@ OPTIONS
|
|
|
10610
10830
|
--role=<role> Package role (e.g. template, config).
|
|
10611
10831
|
--name=<name> Package name within the role.
|
|
10612
10832
|
--dir=<dir> Target dir (defaults to dirname(pkg.installPath)).
|
|
10613
|
-
--
|
|
10833
|
+
--ctx=<base64> Opaque ctx; ossFileMap is extracted from it.
|
|
10834
|
+
--oss_file_map=... Pre-built OSS URL map (base64 JSON).
|
|
10614
10835
|
`
|
|
10615
10836
|
}
|
|
10616
10837
|
];
|
|
@@ -10686,31 +10907,31 @@ function planVarsFields(opts = {}) {
|
|
|
10686
10907
|
*
|
|
10687
10908
|
* Per-command group needs:
|
|
10688
10909
|
*
|
|
10689
|
-
* doctor / check app
|
|
10690
|
-
* repair app + secrets
|
|
10691
|
-
* reset app + secrets + install + reset
|
|
10910
|
+
* doctor / check app (rule-driven)
|
|
10911
|
+
* repair app + secrets (writes secretsContent / providerKeyContent)
|
|
10912
|
+
* reset app + secrets + install + reset (the works)
|
|
10692
10913
|
* install-* install only
|
|
10693
10914
|
*
|
|
10694
10915
|
* Empty result (`{}`) means "no group needed" — the CLI can skip the
|
|
10695
10916
|
* `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`.
|
|
10696
10919
|
*/
|
|
10697
10920
|
function planCtxPopulate(opts) {
|
|
10698
10921
|
if (opts.command === "install") return { install: true };
|
|
10699
10922
|
const populate = {};
|
|
10700
|
-
|
|
10923
|
+
const appFields = planVarsFields({
|
|
10701
10924
|
disabled: opts.disabled,
|
|
10702
10925
|
onlyRules: opts.onlyRules,
|
|
10703
10926
|
profile: opts.profile
|
|
10704
|
-
})
|
|
10705
|
-
if (
|
|
10706
|
-
|
|
10707
|
-
|
|
10708
|
-
} else if (opts.command === "reset") {
|
|
10927
|
+
});
|
|
10928
|
+
if (appFields.length > 0) populate.app = appFields;
|
|
10929
|
+
if (opts.command === "repair") populate.secrets = true;
|
|
10930
|
+
else if (opts.command === "reset") {
|
|
10709
10931
|
populate.secrets = true;
|
|
10710
10932
|
populate.install = true;
|
|
10711
10933
|
populate.reset = true;
|
|
10712
|
-
|
|
10713
|
-
} else if (opts.command === "doctor" || opts.command === "check") populate.larkApps = true;
|
|
10934
|
+
}
|
|
10714
10935
|
return populate;
|
|
10715
10936
|
}
|
|
10716
10937
|
//#endregion
|
|
@@ -10764,11 +10985,372 @@ function reportDoctorRunToSlardar(opts) {
|
|
|
10764
10985
|
}
|
|
10765
10986
|
});
|
|
10766
10987
|
}
|
|
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
|
+
}
|
|
10767
11334
|
//#endregion
|
|
10768
11335
|
//#region src/index.ts
|
|
10769
11336
|
const args = node_process.default.argv.slice(2);
|
|
10770
11337
|
const mode = args.find((a) => !a.startsWith("-"));
|
|
10771
11338
|
/**
|
|
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
|
+
/**
|
|
10772
11354
|
* Pull the first non-flag positional after the mode name.
|
|
10773
11355
|
* (The mode itself is args[0] in the filtered set, so we skip index 0.)
|
|
10774
11356
|
*/
|
|
@@ -10796,8 +11378,8 @@ function getMultiFlag(args, name) {
|
|
|
10796
11378
|
* case but is no longer consulted.
|
|
10797
11379
|
*/
|
|
10798
11380
|
async function reportRun(command, rc, _raw, invocation, durationMs, outcome, slardar = {
|
|
10799
|
-
scene
|
|
10800
|
-
profile
|
|
11381
|
+
scene,
|
|
11382
|
+
profile,
|
|
10801
11383
|
fix: false
|
|
10802
11384
|
}) {
|
|
10803
11385
|
console.error(`${command}: telemetry calling report_cli_run`);
|
|
@@ -10861,7 +11443,7 @@ async function main() {
|
|
|
10861
11443
|
console.error(`${mode}: begin argv=[${args.join(" ")}] version=${getVersion()} traceId=${traceId ?? "-"} caller=${caller ?? "-"} runIdGenerated=${rc.generated}`);
|
|
10862
11444
|
switch (mode) {
|
|
10863
11445
|
case "check": {
|
|
10864
|
-
const raw = await fetchCtxViaInnerApi({
|
|
11446
|
+
const raw = parseCtxFlag(args) ?? await fetchCtxViaInnerApi({
|
|
10865
11447
|
populate: planCtxPopulate({
|
|
10866
11448
|
command: "check",
|
|
10867
11449
|
profile
|
|
@@ -10886,7 +11468,7 @@ async function main() {
|
|
|
10886
11468
|
break;
|
|
10887
11469
|
}
|
|
10888
11470
|
case "repair": {
|
|
10889
|
-
const raw = await fetchCtxViaInnerApi({
|
|
11471
|
+
const raw = parseCtxFlag(args) ?? await fetchCtxViaInnerApi({
|
|
10890
11472
|
populate: planCtxPopulate({
|
|
10891
11473
|
command: "repair",
|
|
10892
11474
|
profile
|
|
@@ -10957,15 +11539,27 @@ async function main() {
|
|
|
10957
11539
|
break;
|
|
10958
11540
|
}
|
|
10959
11541
|
case "reset":
|
|
10960
|
-
if (args.includes("--async"))
|
|
10961
|
-
|
|
11542
|
+
if (args.includes("--async")) {
|
|
11543
|
+
const ctxArg = args.find((a) => a.startsWith("--ctx="));
|
|
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")) {
|
|
10962
11556
|
const taskId = args.find((a) => a.startsWith("--task-id="))?.slice(10);
|
|
10963
11557
|
if (!taskId) {
|
|
10964
11558
|
console.error("Error: --task-id=<id> is required for worker");
|
|
10965
11559
|
node_process.default.exit(1);
|
|
10966
11560
|
}
|
|
10967
11561
|
const resultFile = resetResultFile(taskId);
|
|
10968
|
-
const raw = await fetchCtxViaInnerApi({
|
|
11562
|
+
const raw = parseCtxFlag(args) ?? await fetchCtxViaInnerApi({
|
|
10969
11563
|
populate: planCtxPopulate({ command: "reset" }),
|
|
10970
11564
|
caller,
|
|
10971
11565
|
traceId
|
|
@@ -10989,7 +11583,7 @@ async function main() {
|
|
|
10989
11583
|
return;
|
|
10990
11584
|
}
|
|
10991
11585
|
} else {
|
|
10992
|
-
console.error("Usage: reset --async | reset --worker --task-id=<id>");
|
|
11586
|
+
console.error("Usage: reset --async [--ctx=<base64>] | reset --worker --task-id=<id> [--ctx=<base64>]");
|
|
10993
11587
|
node_process.default.exit(1);
|
|
10994
11588
|
}
|
|
10995
11589
|
break;
|
|
@@ -11005,14 +11599,14 @@ async function main() {
|
|
|
11005
11599
|
case "install-openclaw": {
|
|
11006
11600
|
const tag = getPositionalTag(args, "install-openclaw");
|
|
11007
11601
|
if (!tag) {
|
|
11008
|
-
console.error("Usage: install-openclaw <tag> [--oss_file_map=<base64>]");
|
|
11602
|
+
console.error("Usage: install-openclaw <tag> [--ctx=<base64> | --oss_file_map=<base64>]");
|
|
11009
11603
|
node_process.default.exit(1);
|
|
11010
11604
|
}
|
|
11011
11605
|
const ossFileMapFlag = getFlag(args, "oss_file_map");
|
|
11012
11606
|
let installOssFileMap;
|
|
11013
11607
|
let rawForTelemetry;
|
|
11014
11608
|
if (!ossFileMapFlag) {
|
|
11015
|
-
rawForTelemetry = await fetchCtxViaInnerApi({
|
|
11609
|
+
rawForTelemetry = parseCtxFlag(args) ?? await fetchCtxViaInnerApi({
|
|
11016
11610
|
populate: planCtxPopulate({ command: "install" }),
|
|
11017
11611
|
caller,
|
|
11018
11612
|
traceId
|
|
@@ -11047,7 +11641,7 @@ async function main() {
|
|
|
11047
11641
|
case "install-extension": {
|
|
11048
11642
|
const tag = getPositionalTag(args, "install-extension");
|
|
11049
11643
|
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>]");
|
|
11644
|
+
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
11645
|
node_process.default.exit(1);
|
|
11052
11646
|
}
|
|
11053
11647
|
const all = args.includes("--all");
|
|
@@ -11059,7 +11653,7 @@ async function main() {
|
|
|
11059
11653
|
let installOssFileMap;
|
|
11060
11654
|
let rawForTelemetry;
|
|
11061
11655
|
if (!ossFileMapFlag) {
|
|
11062
|
-
rawForTelemetry = await fetchCtxViaInnerApi({
|
|
11656
|
+
rawForTelemetry = parseCtxFlag(args) ?? await fetchCtxViaInnerApi({
|
|
11063
11657
|
populate: planCtxPopulate({ command: "install" }),
|
|
11064
11658
|
caller,
|
|
11065
11659
|
traceId
|
|
@@ -11105,12 +11699,12 @@ async function main() {
|
|
|
11105
11699
|
case "install-cli": {
|
|
11106
11700
|
const tag = getPositionalTag(args, "install-cli");
|
|
11107
11701
|
if (!tag) {
|
|
11108
|
-
console.error("Usage: install-cli <tag> --cli=<name>... [--home_base=<dir>] [--oss_file_map=<base64>]");
|
|
11702
|
+
console.error("Usage: install-cli <tag> --cli=<name>... [--home_base=<dir>] [--ctx=<base64> | --oss_file_map=<base64>]");
|
|
11109
11703
|
node_process.default.exit(1);
|
|
11110
11704
|
}
|
|
11111
11705
|
const names = getMultiFlag(args, "cli");
|
|
11112
11706
|
if (names.length === 0) {
|
|
11113
|
-
console.error("Usage: install-cli <tag> --cli=<name>... [--home_base=<dir>] [--oss_file_map=<base64>]");
|
|
11707
|
+
console.error("Usage: install-cli <tag> --cli=<name>... [--home_base=<dir>] [--ctx=<base64> | --oss_file_map=<base64>]");
|
|
11114
11708
|
node_process.default.exit(1);
|
|
11115
11709
|
}
|
|
11116
11710
|
const homeBase = getFlag(args, "home_base");
|
|
@@ -11118,7 +11712,7 @@ async function main() {
|
|
|
11118
11712
|
let installOssFileMap;
|
|
11119
11713
|
let rawForTelemetry;
|
|
11120
11714
|
if (!ossFileMapFlag) {
|
|
11121
|
-
rawForTelemetry = await fetchCtxViaInnerApi({
|
|
11715
|
+
rawForTelemetry = parseCtxFlag(args) ?? await fetchCtxViaInnerApi({
|
|
11122
11716
|
populate: planCtxPopulate({ command: "install" }),
|
|
11123
11717
|
caller,
|
|
11124
11718
|
traceId
|
|
@@ -11166,7 +11760,7 @@ async function main() {
|
|
|
11166
11760
|
case "download-resource": {
|
|
11167
11761
|
const tag = getPositionalTag(args, "download-resource");
|
|
11168
11762
|
if (!tag) {
|
|
11169
|
-
console.error("Usage: download-resource <tag> --role=<role> --name=<name> [--dir=<dir>] [--oss_file_map=<base64>]");
|
|
11763
|
+
console.error("Usage: download-resource <tag> --role=<role> --name=<name> [--dir=<dir>] [--ctx=<base64> | --oss_file_map=<base64>]");
|
|
11170
11764
|
node_process.default.exit(1);
|
|
11171
11765
|
}
|
|
11172
11766
|
const role = getFlag(args, "role");
|
|
@@ -11180,7 +11774,7 @@ async function main() {
|
|
|
11180
11774
|
let installOssFileMap;
|
|
11181
11775
|
let rawForTelemetry;
|
|
11182
11776
|
if (!ossFileMapFlag) {
|
|
11183
|
-
rawForTelemetry = await fetchCtxViaInnerApi({
|
|
11777
|
+
rawForTelemetry = parseCtxFlag(args) ?? await fetchCtxViaInnerApi({
|
|
11184
11778
|
populate: planCtxPopulate({ command: "install" }),
|
|
11185
11779
|
caller,
|
|
11186
11780
|
traceId
|
|
@@ -11254,6 +11848,50 @@ async function main() {
|
|
|
11254
11848
|
if (!result.ok) node_process.default.exit(1);
|
|
11255
11849
|
break;
|
|
11256
11850
|
}
|
|
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
|
+
}
|
|
11257
11895
|
default:
|
|
11258
11896
|
node_process.default.stderr.write(`Unknown command: ${mode}\n\n`);
|
|
11259
11897
|
node_process.default.stderr.write(formatTopLevelHelp(helpFlags.expert));
|