@lark-apaas/openclaw-scripts-diagnose-cli 0.1.14-alpha.0 → 0.1.14-alpha.1
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 +790 -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.1";
|
|
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,7 +3508,7 @@ 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);
|
|
@@ -3555,110 +3563,100 @@ CleanupInstallBackupDirsRule = __decorate([Rule({
|
|
|
3555
3563
|
level: "critical"
|
|
3556
3564
|
})], CleanupInstallBackupDirsRule);
|
|
3557
3565
|
//#endregion
|
|
3558
|
-
//#region src/rules/
|
|
3559
|
-
|
|
3560
|
-
|
|
3561
|
-
|
|
3562
|
-
|
|
3563
|
-
|
|
3564
|
-
|
|
3565
|
-
|
|
3566
|
-
|
|
3567
|
-
|
|
3566
|
+
//#region src/rules/lark-cli-missing-for-installed-lark-plugin.ts
|
|
3567
|
+
const PLUGIN_NAME = "openclaw-lark";
|
|
3568
|
+
const FORK_PACKAGE_NAME = "@lark-apaas/openclaw-lark";
|
|
3569
|
+
const TARGET_VERSION = "2026.4.4";
|
|
3570
|
+
const LARK_CLI_NAME$1 = "lark-cli";
|
|
3571
|
+
function readInstalledLarkPlugin(ctx) {
|
|
3572
|
+
const pkgPath = node_path.default.join(getExtensionsDir(ctx.configPath), PLUGIN_NAME, "package.json");
|
|
3573
|
+
if (!node_fs.default.existsSync(pkgPath)) return null;
|
|
3574
|
+
let pkg = {};
|
|
3575
|
+
try {
|
|
3576
|
+
const parsed = JSON.parse(node_fs.default.readFileSync(pkgPath, "utf-8"));
|
|
3577
|
+
pkg = {
|
|
3578
|
+
name: typeof parsed.name === "string" ? parsed.name : void 0,
|
|
3579
|
+
version: typeof parsed.version === "string" ? parsed.version : void 0
|
|
3580
|
+
};
|
|
3581
|
+
} catch {
|
|
3582
|
+
pkg = {};
|
|
3583
|
+
}
|
|
3584
|
+
const installs = getNestedMap(ctx.config, "plugins", "installs");
|
|
3585
|
+
const installEntry = installs ? asRecord(installs[PLUGIN_NAME]) : void 0;
|
|
3586
|
+
return {
|
|
3587
|
+
name: pkg.name ?? extractScopedNameFromSpec(installEntry?.spec),
|
|
3588
|
+
version: pkg.version ?? (typeof installEntry?.version === "string" ? installEntry.version : void 0)
|
|
3589
|
+
};
|
|
3590
|
+
}
|
|
3591
|
+
function isTargetForkPlugin(plugin) {
|
|
3592
|
+
return plugin?.name === FORK_PACKAGE_NAME && plugin.version === TARGET_VERSION;
|
|
3593
|
+
}
|
|
3594
|
+
function extractScopedNameFromSpec(spec) {
|
|
3595
|
+
if (typeof spec !== "string") return void 0;
|
|
3596
|
+
const at = spec.indexOf("@", 1);
|
|
3597
|
+
return at === -1 ? spec : spec.slice(0, at);
|
|
3598
|
+
}
|
|
3599
|
+
function isLarkCliAvailable$1() {
|
|
3600
|
+
try {
|
|
3601
|
+
return (0, node_child_process.spawnSync)(LARK_CLI_NAME$1, ["--version"], {
|
|
3602
|
+
encoding: "utf-8",
|
|
3603
|
+
timeout: 5e3,
|
|
3604
|
+
stdio: [
|
|
3605
|
+
"ignore",
|
|
3606
|
+
"pipe",
|
|
3607
|
+
"ignore"
|
|
3608
|
+
]
|
|
3609
|
+
}).status === 0;
|
|
3610
|
+
} catch {
|
|
3611
|
+
return false;
|
|
3612
|
+
}
|
|
3613
|
+
}
|
|
3614
|
+
function installLarkCliOnce(tag) {
|
|
3615
|
+
const entry = process.argv[1];
|
|
3616
|
+
if (!entry) throw new Error("cannot resolve diagnose-cli entrypoint for lark-cli install");
|
|
3617
|
+
const res = (0, node_child_process.spawnSync)(process.execPath, [
|
|
3618
|
+
entry,
|
|
3619
|
+
"install-cli",
|
|
3620
|
+
tag,
|
|
3621
|
+
"--cli=lark-cli"
|
|
3622
|
+
], {
|
|
3623
|
+
encoding: "utf-8",
|
|
3624
|
+
stdio: [
|
|
3625
|
+
"ignore",
|
|
3626
|
+
"pipe",
|
|
3627
|
+
"pipe"
|
|
3628
|
+
]
|
|
3629
|
+
});
|
|
3630
|
+
const stdout = res.stdout?.trim();
|
|
3631
|
+
const stderr = res.stderr?.trim();
|
|
3632
|
+
if (stdout) console.error(`[lark-cli-missing] install-cli stdout: ${stdout}`);
|
|
3633
|
+
if (stderr) console.error(`[lark-cli-missing] install-cli stderr: ${stderr}`);
|
|
3634
|
+
if (res.error) throw new Error(`install-cli spawn error: ${res.error.message}`);
|
|
3635
|
+
if (res.status !== 0) throw new Error(`install-cli exited with code ${res.status ?? "unknown"}`);
|
|
3636
|
+
}
|
|
3637
|
+
let LarkCliMissingForInstalledLarkPluginRule = class LarkCliMissingForInstalledLarkPluginRule extends DiagnoseRule {
|
|
3568
3638
|
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 };
|
|
3639
|
+
if (!isTargetForkPlugin(readInstalledLarkPlugin(ctx))) return { pass: true };
|
|
3640
|
+
if (isLarkCliAvailable$1()) return { pass: true };
|
|
3590
3641
|
return {
|
|
3591
3642
|
pass: false,
|
|
3592
|
-
message:
|
|
3643
|
+
message: `${FORK_PACKAGE_NAME}@${TARGET_VERSION} 已安装,但 lark-cli 不可用;将执行一次 lark-cli 安装`
|
|
3593
3644
|
};
|
|
3594
3645
|
}
|
|
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
3646
|
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 };
|
|
3647
|
+
if (!isTargetForkPlugin(readInstalledLarkPlugin(ctx))) return;
|
|
3648
|
+
if (isLarkCliAvailable$1()) return;
|
|
3649
|
+
installLarkCliOnce(ctx.vars.recommendedOpenclawTag ?? TARGET_VERSION);
|
|
3648
3650
|
}
|
|
3649
3651
|
};
|
|
3650
|
-
|
|
3651
|
-
key: "
|
|
3652
|
-
description: "
|
|
3653
|
-
dependsOn: [
|
|
3654
|
-
"config_syntax_check",
|
|
3655
|
-
"feishu_default_account",
|
|
3656
|
-
"feishu_bot_id"
|
|
3657
|
-
],
|
|
3652
|
+
LarkCliMissingForInstalledLarkPluginRule = __decorate([Rule({
|
|
3653
|
+
key: "lark_cli_missing_for_installed_lark_plugin",
|
|
3654
|
+
description: "检测特定飞书插件版本已安装但 lark-cli 缺失的环境,并自动安装 lark-cli 一次",
|
|
3655
|
+
dependsOn: ["config_syntax_check"],
|
|
3658
3656
|
repairMode: "standard",
|
|
3659
|
-
|
|
3660
|
-
|
|
3661
|
-
})],
|
|
3657
|
+
level: "critical",
|
|
3658
|
+
usesVars: ["recommendedOpenclawTag"]
|
|
3659
|
+
})], LarkCliMissingForInstalledLarkPluginRule);
|
|
3662
3660
|
//#endregion
|
|
3663
3661
|
//#region src/check.ts
|
|
3664
3662
|
/** Telemetry-aware entry: returns both the legacy CheckResult (for stdout)
|
|
@@ -4123,6 +4121,9 @@ const PROVIDER_FILE_PATH = "/home/gem/workspace/.force/openclaw/miaoda-provider-
|
|
|
4123
4121
|
const SECRETS_FILE_PATH = "/home/gem/workspace/.force/openclaw/miaoda-openclaw-secrets.json";
|
|
4124
4122
|
/** Absolute path to the openclaw config JSON. */
|
|
4125
4123
|
const CONFIG_PATH = `${WORKSPACE_DIR}/openclaw.json`;
|
|
4124
|
+
function upgradeLarkLogFile(runId) {
|
|
4125
|
+
return `${DIAGNOSE_DIR}/upgrade-lark-${(/* @__PURE__ */ new Date()).toISOString().slice(0, 19).replace(/:/g, "-")}-${runId.slice(0, 8)}.log`;
|
|
4126
|
+
}
|
|
4126
4127
|
//#endregion
|
|
4127
4128
|
//#region src/run-log.ts
|
|
4128
4129
|
let currentRunContext;
|
|
@@ -4259,10 +4260,9 @@ function makeLogger(logFile) {
|
|
|
4259
4260
|
/**
|
|
4260
4261
|
* Start an async reset task: spawn a detached child process and return the taskId.
|
|
4261
4262
|
*
|
|
4262
|
-
* The child process runs: node cli.js reset --worker --task-id=xxx
|
|
4263
|
-
* The worker fetches ctx from innerApi itself — no --ctx passthrough.
|
|
4263
|
+
* The child process runs: node cli.js reset --worker --task-id=xxx --ctx=base64
|
|
4264
4264
|
*/
|
|
4265
|
-
function startAsyncReset() {
|
|
4265
|
+
function startAsyncReset(ctxBase64) {
|
|
4266
4266
|
const taskId = (0, node_crypto.randomUUID)();
|
|
4267
4267
|
const resultFile = resetResultFile(taskId);
|
|
4268
4268
|
const log = makeLogger(resetLogFile(taskId));
|
|
@@ -4286,7 +4286,8 @@ function startAsyncReset() {
|
|
|
4286
4286
|
process.argv[1],
|
|
4287
4287
|
"reset",
|
|
4288
4288
|
"--worker",
|
|
4289
|
-
`--task-id=${taskId}
|
|
4289
|
+
`--task-id=${taskId}`,
|
|
4290
|
+
`--ctx=${ctxBase64}`
|
|
4290
4291
|
], {
|
|
4291
4292
|
detached: true,
|
|
4292
4293
|
stdio: "ignore",
|
|
@@ -6235,6 +6236,23 @@ function reportError(params) {
|
|
|
6235
6236
|
} catch {}
|
|
6236
6237
|
}
|
|
6237
6238
|
//#endregion
|
|
6239
|
+
//#region ../../openclaw-slardar/lib/report-log.js
|
|
6240
|
+
function reportLog(params) {
|
|
6241
|
+
try {
|
|
6242
|
+
const extra = mergeLogExtra(buildTelemetryFields(params), {
|
|
6243
|
+
event: params.event,
|
|
6244
|
+
phase: params.phase,
|
|
6245
|
+
status: params.status,
|
|
6246
|
+
...params.extra
|
|
6247
|
+
});
|
|
6248
|
+
Slardar.sendLog?.({
|
|
6249
|
+
content: params.message,
|
|
6250
|
+
level: params.level ?? "info",
|
|
6251
|
+
extra
|
|
6252
|
+
});
|
|
6253
|
+
} catch {}
|
|
6254
|
+
}
|
|
6255
|
+
//#endregion
|
|
6238
6256
|
//#region src/install-cli.ts
|
|
6239
6257
|
const LARK_CLI_NAME = "lark-cli";
|
|
6240
6258
|
const AGENT_SKILLS_NAME = "agent-skills";
|
|
@@ -6800,60 +6818,6 @@ function mergeCoreBackupAndOrigins(configPath, vars, resetData, log) {
|
|
|
6800
6818
|
log(`allowedOrigins: added ${added.length} (${JSON.stringify(added)}), total now ${mergedOrigins.length}`);
|
|
6801
6819
|
}
|
|
6802
6820
|
/**
|
|
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
6821
|
* Step 7: Verify startup scripts landed in configDir/scripts/.
|
|
6858
6822
|
*
|
|
6859
6823
|
* Scripts are extracted directly to configDir/scripts/ during stageTemplate —
|
|
@@ -6998,7 +6962,6 @@ async function runReset(input, taskId, resultFile) {
|
|
|
6998
6962
|
await step5InstallOpenclaw(openclawTag, ossFileMap, log);
|
|
6999
6963
|
step(6);
|
|
7000
6964
|
mergeCoreBackupAndOrigins(configPath, vars, resetData, log);
|
|
7001
|
-
fixBotChannelConfig(configPath, vars.larkApps, log);
|
|
7002
6965
|
step(7);
|
|
7003
6966
|
verifyStartupScripts(configDir, log);
|
|
7004
6967
|
step(8);
|
|
@@ -7797,8 +7760,7 @@ function normalizeCtx(raw) {
|
|
|
7797
7760
|
reset: {
|
|
7798
7761
|
templateVars: r.reset.templateVars ?? {},
|
|
7799
7762
|
coreBackup: r.reset.coreBackup
|
|
7800
|
-
}
|
|
7801
|
-
larkApps: Array.isArray(r.larkApps) ? r.larkApps : []
|
|
7763
|
+
}
|
|
7802
7764
|
};
|
|
7803
7765
|
}
|
|
7804
7766
|
const vars = r.vars ?? {};
|
|
@@ -7823,8 +7785,7 @@ function normalizeCtx(raw) {
|
|
|
7823
7785
|
reset: {
|
|
7824
7786
|
templateVars: resetData.templateVars ?? {},
|
|
7825
7787
|
coreBackup: resetData.coreBackup
|
|
7826
|
-
}
|
|
7827
|
-
larkApps: Array.isArray(r.larkApps) ? r.larkApps : []
|
|
7788
|
+
}
|
|
7828
7789
|
};
|
|
7829
7790
|
}
|
|
7830
7791
|
function fillApp(src) {
|
|
@@ -7889,8 +7850,7 @@ function buildCheckInput(raw, configPathOverride) {
|
|
|
7889
7850
|
providerFilePath: PROVIDER_FILE_PATH,
|
|
7890
7851
|
secretsFilePath: SECRETS_FILE_PATH,
|
|
7891
7852
|
templateVars: ctx.app.templateVars,
|
|
7892
|
-
recommendedOpenclawTag: ctx.app.recommendedOpenclawTag
|
|
7893
|
-
larkApps: ctx.larkApps
|
|
7853
|
+
recommendedOpenclawTag: ctx.app.recommendedOpenclawTag
|
|
7894
7854
|
},
|
|
7895
7855
|
templateVars: ctx.app.templateVars
|
|
7896
7856
|
};
|
|
@@ -7922,8 +7882,7 @@ function buildRepairInput(raw, configPathOverride) {
|
|
|
7922
7882
|
providerFilePath: PROVIDER_FILE_PATH,
|
|
7923
7883
|
secretsFilePath: SECRETS_FILE_PATH,
|
|
7924
7884
|
templateVars: ctx.app.templateVars,
|
|
7925
|
-
recommendedOpenclawTag: ctx.app.recommendedOpenclawTag
|
|
7926
|
-
larkApps: ctx.larkApps
|
|
7885
|
+
recommendedOpenclawTag: ctx.app.recommendedOpenclawTag
|
|
7927
7886
|
},
|
|
7928
7887
|
repairData: {
|
|
7929
7888
|
secretsContent: ctx.secrets.secretsContent,
|
|
@@ -7959,8 +7918,7 @@ function buildResetInput(raw, configPathOverride) {
|
|
|
7959
7918
|
providerFilePath: PROVIDER_FILE_PATH,
|
|
7960
7919
|
secretsFilePath: SECRETS_FILE_PATH,
|
|
7961
7920
|
templateVars: ctx.app.templateVars,
|
|
7962
|
-
recommendedOpenclawTag: ctx.app.recommendedOpenclawTag
|
|
7963
|
-
larkApps: ctx.larkApps
|
|
7921
|
+
recommendedOpenclawTag: ctx.app.recommendedOpenclawTag
|
|
7964
7922
|
},
|
|
7965
7923
|
resetData: {
|
|
7966
7924
|
templateVars: ctx.reset.templateVars,
|
|
@@ -10191,6 +10149,109 @@ function finalize(results, aborted) {
|
|
|
10191
10149
|
};
|
|
10192
10150
|
}
|
|
10193
10151
|
//#endregion
|
|
10152
|
+
//#region src/channels-probe.ts
|
|
10153
|
+
const CHANNEL_LINE_RE = /^-\s+Feishu\s+([^:]+):\s+(.+)$/;
|
|
10154
|
+
/**
|
|
10155
|
+
* Port of Python `_account_is_working` from the feishu-channel-success-rate skill.
|
|
10156
|
+
*
|
|
10157
|
+
* Strips colon-prefixed key:value bits (dm:, bot:, in:, out:, token:, allow:,
|
|
10158
|
+
* intents:, groups:, health:) and evaluates the canonical health formula.
|
|
10159
|
+
*/
|
|
10160
|
+
function accountIsWorking(bits) {
|
|
10161
|
+
const bitTokens = /* @__PURE__ */ new Set();
|
|
10162
|
+
let hasError = false;
|
|
10163
|
+
let hasProbeFailed = false;
|
|
10164
|
+
for (const raw of bits) {
|
|
10165
|
+
const b = raw.trim();
|
|
10166
|
+
if (!b) continue;
|
|
10167
|
+
if (b.startsWith("error:")) {
|
|
10168
|
+
hasError = true;
|
|
10169
|
+
continue;
|
|
10170
|
+
}
|
|
10171
|
+
if (b === "probe failed") {
|
|
10172
|
+
hasProbeFailed = true;
|
|
10173
|
+
continue;
|
|
10174
|
+
}
|
|
10175
|
+
bitTokens.add(b.split(":")[0]);
|
|
10176
|
+
}
|
|
10177
|
+
if (!bitTokens.has("enabled") || !bitTokens.has("configured")) return false;
|
|
10178
|
+
if (bitTokens.has("works")) return true;
|
|
10179
|
+
if (bitTokens.has("running") && !hasError && !hasProbeFailed) return true;
|
|
10180
|
+
return false;
|
|
10181
|
+
}
|
|
10182
|
+
/**
|
|
10183
|
+
* Parse the raw stdout of `openclaw channels status --probe`.
|
|
10184
|
+
* Port of Python `extract_channels_probe` from the feishu-channel-success-rate skill.
|
|
10185
|
+
*/
|
|
10186
|
+
function parseChannelsProbeOutput(text) {
|
|
10187
|
+
const gatewayReachable = text.includes("Gateway reachable");
|
|
10188
|
+
const accounts = [];
|
|
10189
|
+
let anyAccountWorking = false;
|
|
10190
|
+
for (const line of text.split("\n")) {
|
|
10191
|
+
const m = CHANNEL_LINE_RE.exec(line.trim());
|
|
10192
|
+
if (!m) continue;
|
|
10193
|
+
const [, acct, rest] = m;
|
|
10194
|
+
const bits = rest.split(",").map((b) => b.trim());
|
|
10195
|
+
const isWorking = accountIsWorking(bits);
|
|
10196
|
+
if (isWorking) anyAccountWorking = true;
|
|
10197
|
+
accounts.push({
|
|
10198
|
+
id: acct.trim(),
|
|
10199
|
+
bits,
|
|
10200
|
+
isWorking,
|
|
10201
|
+
raw: line.trim()
|
|
10202
|
+
});
|
|
10203
|
+
}
|
|
10204
|
+
return {
|
|
10205
|
+
gatewayReachable,
|
|
10206
|
+
accounts,
|
|
10207
|
+
anyAccountWorking
|
|
10208
|
+
};
|
|
10209
|
+
}
|
|
10210
|
+
/**
|
|
10211
|
+
* Run `openclaw channels status --probe` and return a structured result.
|
|
10212
|
+
*
|
|
10213
|
+
* The command may exit non-zero when some bot accounts fail their probe — that
|
|
10214
|
+
* is still useful output. We therefore try to parse stdout even when the
|
|
10215
|
+
* process exits with a non-zero code, falling back to an unavailable result
|
|
10216
|
+
* only when there is genuinely no output to parse.
|
|
10217
|
+
*
|
|
10218
|
+
* @param timeoutMs Maximum wait time. Default is 60 s because v2026.4.x
|
|
10219
|
+
* lacks a per-request HTTP timeout and can block indefinitely.
|
|
10220
|
+
*/
|
|
10221
|
+
function runChannelsProbe(timeoutMs = 6e4) {
|
|
10222
|
+
let stdout = "";
|
|
10223
|
+
let execError;
|
|
10224
|
+
try {
|
|
10225
|
+
stdout = (0, node_child_process.execSync)("openclaw channels status --probe", {
|
|
10226
|
+
encoding: "utf-8",
|
|
10227
|
+
timeout: timeoutMs,
|
|
10228
|
+
stdio: [
|
|
10229
|
+
"ignore",
|
|
10230
|
+
"pipe",
|
|
10231
|
+
"pipe"
|
|
10232
|
+
]
|
|
10233
|
+
});
|
|
10234
|
+
} catch (e) {
|
|
10235
|
+
const err = e;
|
|
10236
|
+
stdout = err.stdout ?? "";
|
|
10237
|
+
execError = err.message;
|
|
10238
|
+
const stderrRaw = err.stderr;
|
|
10239
|
+
const stderr = (typeof stderrRaw === "string" ? stderrRaw : stderrRaw?.toString("utf-8") ?? "").trim();
|
|
10240
|
+
if (stderr) console.error(`channels-probe: stderr from CLI: ${stderr}`);
|
|
10241
|
+
}
|
|
10242
|
+
if (stdout.trim()) return {
|
|
10243
|
+
available: true,
|
|
10244
|
+
...parseChannelsProbeOutput(stdout)
|
|
10245
|
+
};
|
|
10246
|
+
return {
|
|
10247
|
+
available: false,
|
|
10248
|
+
gatewayReachable: false,
|
|
10249
|
+
accounts: [],
|
|
10250
|
+
anyAccountWorking: false,
|
|
10251
|
+
error: execError ?? "no output from openclaw channels status --probe"
|
|
10252
|
+
};
|
|
10253
|
+
}
|
|
10254
|
+
//#endregion
|
|
10194
10255
|
//#region src/innerapi/reportCliRun.ts
|
|
10195
10256
|
/**
|
|
10196
10257
|
* CLI-side client for studio_server's `openclaw.report_cli_run` inner
|
|
@@ -10270,7 +10331,7 @@ async function reportCliRun(opts) {
|
|
|
10270
10331
|
//#region src/help.ts
|
|
10271
10332
|
const BIN = "mclaw-diagnose";
|
|
10272
10333
|
function versionBanner() {
|
|
10273
|
-
return `v0.1.14-alpha.
|
|
10334
|
+
return `v0.1.14-alpha.1`;
|
|
10274
10335
|
}
|
|
10275
10336
|
const COMMANDS = [
|
|
10276
10337
|
{
|
|
@@ -10374,12 +10435,16 @@ EXIT CODES
|
|
|
10374
10435
|
hidden: true,
|
|
10375
10436
|
summary: "Run rule-engine check only",
|
|
10376
10437
|
help: `USAGE
|
|
10377
|
-
${BIN} check
|
|
10438
|
+
${BIN} check [--ctx=<base64>]
|
|
10378
10439
|
|
|
10379
10440
|
DESCRIPTION
|
|
10380
10441
|
Runs the rule engine against the sandbox's current openclaw config and
|
|
10381
|
-
returns { failedRules }.
|
|
10382
|
-
|
|
10442
|
+
returns { failedRules }. Used by sandbox_console's push-style callers
|
|
10443
|
+
that already own the ctx — end-users should prefer \`doctor\`.
|
|
10444
|
+
|
|
10445
|
+
OPTIONS
|
|
10446
|
+
--ctx=<base64> Opaque ctx JSON (base64). When absent, fetched from
|
|
10447
|
+
innerapi (same path as doctor).
|
|
10383
10448
|
`
|
|
10384
10449
|
},
|
|
10385
10450
|
{
|
|
@@ -10387,11 +10452,16 @@ DESCRIPTION
|
|
|
10387
10452
|
hidden: true,
|
|
10388
10453
|
summary: "Apply standard-mode repairs",
|
|
10389
10454
|
help: `USAGE
|
|
10390
|
-
${BIN} repair
|
|
10455
|
+
${BIN} repair [--ctx=<base64>]
|
|
10391
10456
|
|
|
10392
10457
|
DESCRIPTION
|
|
10393
|
-
Runs repair for the failing rules
|
|
10394
|
-
|
|
10458
|
+
Runs repair for the failing rules listed inside the ctx's repairData.
|
|
10459
|
+
Intended for sandbox_console's push path — end-users should use
|
|
10460
|
+
\`doctor --fix\` instead.
|
|
10461
|
+
|
|
10462
|
+
OPTIONS
|
|
10463
|
+
--ctx=<base64> Opaque ctx JSON (base64). When absent, fetched from
|
|
10464
|
+
innerapi.
|
|
10395
10465
|
`
|
|
10396
10466
|
},
|
|
10397
10467
|
{
|
|
@@ -10399,15 +10469,14 @@ DESCRIPTION
|
|
|
10399
10469
|
hidden: true,
|
|
10400
10470
|
summary: "Re-initialize sandbox via the 9-step reset pipeline",
|
|
10401
10471
|
help: `USAGE
|
|
10402
|
-
${BIN} reset --async
|
|
10403
|
-
${BIN} reset --worker --task-id=<id>
|
|
10472
|
+
${BIN} reset --async [--ctx=<base64>]
|
|
10473
|
+
${BIN} reset --worker --task-id=<id> [--ctx=<base64>]
|
|
10404
10474
|
|
|
10405
10475
|
DESCRIPTION
|
|
10406
10476
|
Two-phase pipeline driven asynchronously: the --async invocation spawns
|
|
10407
10477
|
a detached worker and returns { taskId } immediately; the --worker
|
|
10408
10478
|
invocation (spawned by --async) runs the actual 9 steps and writes
|
|
10409
10479
|
progress to /tmp/openclaw-diagnose/reset-<taskId>.json.
|
|
10410
|
-
Ctx is fetched from innerapi automatically.
|
|
10411
10480
|
|
|
10412
10481
|
Poll progress with \`${BIN} get_reset_task --task-id=<id>\`.
|
|
10413
10482
|
|
|
@@ -10415,6 +10484,7 @@ OPTIONS
|
|
|
10415
10484
|
--async Start a detached worker and return taskId on stdout.
|
|
10416
10485
|
--worker Internal — run the 9-step pipeline (launched by --async).
|
|
10417
10486
|
--task-id=<id> Required with --worker; identifies the progress file.
|
|
10487
|
+
--ctx=<base64> Opaque ctx JSON; fetched from innerapi when absent.
|
|
10418
10488
|
`
|
|
10419
10489
|
},
|
|
10420
10490
|
{
|
|
@@ -10437,7 +10507,7 @@ OPTIONS
|
|
|
10437
10507
|
hidden: true,
|
|
10438
10508
|
summary: "Download + install the openclaw tarball",
|
|
10439
10509
|
help: `USAGE
|
|
10440
|
-
${BIN} install-openclaw <tag> [--oss_file_map=<base64>]
|
|
10510
|
+
${BIN} install-openclaw <tag> [--ctx=<base64> | --oss_file_map=<base64>]
|
|
10441
10511
|
|
|
10442
10512
|
DESCRIPTION
|
|
10443
10513
|
Downloads the openclaw@<tag> tgz via the signed OSS URL found in the
|
|
@@ -10449,9 +10519,9 @@ ARGUMENTS
|
|
|
10449
10519
|
<tag> Openclaw version tag, e.g. 2026.4.11.
|
|
10450
10520
|
|
|
10451
10521
|
OPTIONS
|
|
10522
|
+
--ctx=<base64> Opaque ctx; ossFileMap is extracted from it.
|
|
10452
10523
|
--oss_file_map=... Pre-built OSS URL map (base64 JSON); skips innerapi
|
|
10453
|
-
entirely.
|
|
10454
|
-
innerapi automatically.
|
|
10524
|
+
entirely. Wins over --ctx when both provided.
|
|
10455
10525
|
`
|
|
10456
10526
|
},
|
|
10457
10527
|
{
|
|
@@ -10477,7 +10547,8 @@ OPTIONS
|
|
|
10477
10547
|
--home_base=<dir> Override the /home/gem base (tests).
|
|
10478
10548
|
--config_path=<p> Override the openclaw.json path (tests).
|
|
10479
10549
|
--skip-config-update Leave plugins.installs in openclaw.json untouched.
|
|
10480
|
-
--
|
|
10550
|
+
--ctx=<base64> Opaque ctx; see install-openclaw for semantics.
|
|
10551
|
+
--oss_file_map=... Pre-built OSS URL map (base64 JSON).
|
|
10481
10552
|
`
|
|
10482
10553
|
},
|
|
10483
10554
|
{
|
|
@@ -10504,6 +10575,7 @@ OPTIONS
|
|
|
10504
10575
|
--cli=<name> CLI package to install by short name or scoped
|
|
10505
10576
|
packageName (repeatable, at least one required).
|
|
10506
10577
|
--home_base=<dir> Override the /home/gem base (tests).
|
|
10578
|
+
--ctx=<base64> Opaque ctx; ossFileMap is extracted from it.
|
|
10507
10579
|
--oss_file_map=... Pre-built OSS URL map (base64 JSON); skips innerapi.
|
|
10508
10580
|
|
|
10509
10581
|
EXAMPLES
|
|
@@ -10557,6 +10629,46 @@ OPTIONS
|
|
|
10557
10629
|
EXIT CODES
|
|
10558
10630
|
0 Success or skipped (prerequisites not met).
|
|
10559
10631
|
1 Secret/path unresolvable, lark-cli failed, or config unreadable.
|
|
10632
|
+
`
|
|
10633
|
+
},
|
|
10634
|
+
{
|
|
10635
|
+
name: "upgrade-lark",
|
|
10636
|
+
hidden: false,
|
|
10637
|
+
summary: "Upgrade the Feishu/Lark plugin via @larksuite/openclaw-lark-tools",
|
|
10638
|
+
help: `USAGE
|
|
10639
|
+
${BIN} upgrade-lark [--scene=<scene>] [--caller=<n>] [--trace-id=<id>]
|
|
10640
|
+
|
|
10641
|
+
DESCRIPTION
|
|
10642
|
+
Upgrades the Feishu/Lark plugin by running:
|
|
10643
|
+
npx -y @larksuite/openclaw-lark-tools update --use-existing
|
|
10644
|
+
|
|
10645
|
+
Before the upgrade, the following files are backed up:
|
|
10646
|
+
- openclaw.json
|
|
10647
|
+
- extensions/openclaw-lark/ (if present)
|
|
10648
|
+
- extensions/feishu-openclaw-plugin/ (if present)
|
|
10649
|
+
After the upgrade, the result is validated:
|
|
10650
|
+
- feishu.accounts bot count must not decrease
|
|
10651
|
+
- gateway config structure must remain valid (port/mode/bind/auth/trustedProxies)
|
|
10652
|
+
If the upgrade command fails, or validation fails, the backed-up files are
|
|
10653
|
+
restored to roll back the changes.
|
|
10654
|
+
|
|
10655
|
+
Execution is logged to /tmp/openclaw-diagnose/upgrade-lark-<runId>.log.
|
|
10656
|
+
|
|
10657
|
+
Output is a single JSON object on stdout:
|
|
10658
|
+
{ "ok": true, "stdout": "...", "stderr": "...", "logFile": "..." }
|
|
10659
|
+
{ "ok": false, "error": "...", "stderr": "...", "exitCode": 1,
|
|
10660
|
+
"rollbackOk": true, "validationError": "...", "logFile": "..." }
|
|
10661
|
+
|
|
10662
|
+
OPTIONS
|
|
10663
|
+
--scene=<scene> Telemetry label forwarded to Slardar only.
|
|
10664
|
+
Known values: PageUpgradeLark, etc. Custom strings accepted.
|
|
10665
|
+
--caller=<name> Optional metadata passed to innerapi.
|
|
10666
|
+
--trace-id=<id> Optional log-correlation id.
|
|
10667
|
+
|
|
10668
|
+
EXIT CODES
|
|
10669
|
+
0 Success: upgrade ran and all validations passed.
|
|
10670
|
+
1 Failure: npx error, validation failed, or git commit failed.
|
|
10671
|
+
File rollback was attempted (see rollbackOk in the JSON output).
|
|
10560
10672
|
`
|
|
10561
10673
|
},
|
|
10562
10674
|
{
|
|
@@ -10590,6 +10702,41 @@ EXAMPLES
|
|
|
10590
10702
|
${BIN} rules # all rules
|
|
10591
10703
|
${BIN} rules --rule=gateway # single rule
|
|
10592
10704
|
${BIN} rules --rule=gateway --rule=feishu_channel # multiple rules
|
|
10705
|
+
`
|
|
10706
|
+
},
|
|
10707
|
+
{
|
|
10708
|
+
name: "channels-probe",
|
|
10709
|
+
hidden: true,
|
|
10710
|
+
summary: "Check feishu channel health via openclaw channels status --probe",
|
|
10711
|
+
help: `USAGE
|
|
10712
|
+
${BIN} channels-probe [--timeout=<ms>]
|
|
10713
|
+
|
|
10714
|
+
DESCRIPTION
|
|
10715
|
+
Runs \`openclaw channels status --probe\` and returns a structured JSON
|
|
10716
|
+
summary of whether the current environment's feishu channels are
|
|
10717
|
+
configured and working correctly.
|
|
10718
|
+
|
|
10719
|
+
Output:
|
|
10720
|
+
{
|
|
10721
|
+
"available": true,
|
|
10722
|
+
"gatewayReachable": true,
|
|
10723
|
+
"accounts": [
|
|
10724
|
+
{ "id": "default", "bits": ["enabled","configured","running","works"],
|
|
10725
|
+
"isWorking": true, "raw": "- Feishu default: ..." }
|
|
10726
|
+
],
|
|
10727
|
+
"anyAccountWorking": true
|
|
10728
|
+
}
|
|
10729
|
+
|
|
10730
|
+
An account is considered working when:
|
|
10731
|
+
enabled ∧ configured ∧ ( works ∨ ( running ∧ no error: ∧ no probe failed ) )
|
|
10732
|
+
|
|
10733
|
+
"available": false means the CLI invocation itself failed (openclaw not
|
|
10734
|
+
found, gateway unreachable, or no parseable output returned).
|
|
10735
|
+
|
|
10736
|
+
OPTIONS
|
|
10737
|
+
--timeout=<ms> Max wait in milliseconds (default: 60000). The probe
|
|
10738
|
+
can hang indefinitely on openclaw v2026.4.x due to a
|
|
10739
|
+
missing per-request HTTP timeout — set this accordingly.
|
|
10593
10740
|
`
|
|
10594
10741
|
},
|
|
10595
10742
|
{
|
|
@@ -10610,7 +10757,8 @@ OPTIONS
|
|
|
10610
10757
|
--role=<role> Package role (e.g. template, config).
|
|
10611
10758
|
--name=<name> Package name within the role.
|
|
10612
10759
|
--dir=<dir> Target dir (defaults to dirname(pkg.installPath)).
|
|
10613
|
-
--
|
|
10760
|
+
--ctx=<base64> Opaque ctx; ossFileMap is extracted from it.
|
|
10761
|
+
--oss_file_map=... Pre-built OSS URL map (base64 JSON).
|
|
10614
10762
|
`
|
|
10615
10763
|
}
|
|
10616
10764
|
];
|
|
@@ -10686,31 +10834,31 @@ function planVarsFields(opts = {}) {
|
|
|
10686
10834
|
*
|
|
10687
10835
|
* Per-command group needs:
|
|
10688
10836
|
*
|
|
10689
|
-
* doctor / check app
|
|
10690
|
-
* repair app + secrets
|
|
10691
|
-
* reset app + secrets + install + reset
|
|
10837
|
+
* doctor / check app (rule-driven)
|
|
10838
|
+
* repair app + secrets (writes secretsContent / providerKeyContent)
|
|
10839
|
+
* reset app + secrets + install + reset (the works)
|
|
10692
10840
|
* install-* install only
|
|
10693
10841
|
*
|
|
10694
10842
|
* Empty result (`{}`) means "no group needed" — the CLI can skip the
|
|
10695
10843
|
* `fetchCtxViaInnerApi` call entirely and run with a synthetic empty ctx.
|
|
10844
|
+
* Happens e.g. when the user pinned `--rule=<key>` to a vars-free rule on
|
|
10845
|
+
* `doctor`.
|
|
10696
10846
|
*/
|
|
10697
10847
|
function planCtxPopulate(opts) {
|
|
10698
10848
|
if (opts.command === "install") return { install: true };
|
|
10699
10849
|
const populate = {};
|
|
10700
|
-
|
|
10850
|
+
const appFields = planVarsFields({
|
|
10701
10851
|
disabled: opts.disabled,
|
|
10702
10852
|
onlyRules: opts.onlyRules,
|
|
10703
10853
|
profile: opts.profile
|
|
10704
|
-
})
|
|
10705
|
-
if (
|
|
10706
|
-
|
|
10707
|
-
|
|
10708
|
-
} else if (opts.command === "reset") {
|
|
10854
|
+
});
|
|
10855
|
+
if (appFields.length > 0) populate.app = appFields;
|
|
10856
|
+
if (opts.command === "repair") populate.secrets = true;
|
|
10857
|
+
else if (opts.command === "reset") {
|
|
10709
10858
|
populate.secrets = true;
|
|
10710
10859
|
populate.install = true;
|
|
10711
10860
|
populate.reset = true;
|
|
10712
|
-
|
|
10713
|
-
} else if (opts.command === "doctor" || opts.command === "check") populate.larkApps = true;
|
|
10861
|
+
}
|
|
10714
10862
|
return populate;
|
|
10715
10863
|
}
|
|
10716
10864
|
//#endregion
|
|
@@ -10764,11 +10912,378 @@ function reportDoctorRunToSlardar(opts) {
|
|
|
10764
10912
|
}
|
|
10765
10913
|
});
|
|
10766
10914
|
}
|
|
10915
|
+
/** Read the tail of a file, up to maxBytes. Returns empty string on any error. */
|
|
10916
|
+
function readFileTail(filePath, maxBytes = 4e3) {
|
|
10917
|
+
try {
|
|
10918
|
+
const content = node_fs.default.readFileSync(filePath, "utf-8");
|
|
10919
|
+
if (content.length <= maxBytes) return content;
|
|
10920
|
+
return `...(truncated — showing last ${maxBytes} chars)...\n` + content.slice(-maxBytes);
|
|
10921
|
+
} catch {
|
|
10922
|
+
return "";
|
|
10923
|
+
}
|
|
10924
|
+
}
|
|
10925
|
+
function reportUpgradeLarkToSlardar(opts) {
|
|
10926
|
+
console.error(`[slardar] upgrade-lark reportTask scene=${opts.scene ?? ""} success=${opts.success} exitCode=${opts.exitCode ?? ""} rollbackOk=${opts.rollbackOk ?? ""}`);
|
|
10927
|
+
reportTask({
|
|
10928
|
+
eventName: "upgrade_lark_run",
|
|
10929
|
+
durationMs: opts.durationMs,
|
|
10930
|
+
status: opts.success ? "success" : "failed",
|
|
10931
|
+
extraCategories: {
|
|
10932
|
+
scene: opts.scene ?? "",
|
|
10933
|
+
exit_code: String(opts.exitCode ?? ""),
|
|
10934
|
+
rollback_ok: opts.rollbackOk != null ? String(opts.rollbackOk) : "",
|
|
10935
|
+
validation_error: (opts.validationError ?? "").slice(0, 200),
|
|
10936
|
+
error_msg: (opts.error ?? "").slice(0, 200)
|
|
10937
|
+
}
|
|
10938
|
+
});
|
|
10939
|
+
const logContent = readFileTail(opts.logFile);
|
|
10940
|
+
console.error(`[slardar] upgrade_lark_detail logFile=${opts.logFile} contentLength=${logContent.length} sendLogAvailable=${typeof Slardar.sendLog}`);
|
|
10941
|
+
reportLog({
|
|
10942
|
+
event: "upgrade_lark_detail",
|
|
10943
|
+
message: logContent || "(no log content)",
|
|
10944
|
+
level: opts.success ? "info" : "error",
|
|
10945
|
+
extra: {
|
|
10946
|
+
log_file: opts.logFile,
|
|
10947
|
+
scene: opts.scene ?? "",
|
|
10948
|
+
success: String(opts.success),
|
|
10949
|
+
exit_code: String(opts.exitCode ?? ""),
|
|
10950
|
+
rollback_ok: opts.rollbackOk != null ? String(opts.rollbackOk) : ""
|
|
10951
|
+
}
|
|
10952
|
+
});
|
|
10953
|
+
}
|
|
10954
|
+
//#endregion
|
|
10955
|
+
//#region src/upgrade-lark.ts
|
|
10956
|
+
/** Plugin directories under extensions/ that are backed up before upgrade */
|
|
10957
|
+
const FEISHU_PLUGIN_DIRS = ["openclaw-lark", "feishu-openclaw-plugin"];
|
|
10958
|
+
/** Version compat rule keys checked in the doctor output after install */
|
|
10959
|
+
const VERSION_COMPAT_RULE_KEYS = ["feishu_plugin_version_compat_lark", "feishu_plugin_version_compat_openclaw"];
|
|
10960
|
+
function backupFiles(opts) {
|
|
10961
|
+
const { workspaceDir, configPath, backupDir, log } = opts;
|
|
10962
|
+
try {
|
|
10963
|
+
node_fs.default.mkdirSync(backupDir, { recursive: true });
|
|
10964
|
+
log(`backup dir: ${backupDir}`);
|
|
10965
|
+
if (node_fs.default.existsSync(configPath)) {
|
|
10966
|
+
const stat = node_fs.default.statSync(configPath);
|
|
10967
|
+
node_fs.default.copyFileSync(configPath, node_path.default.join(backupDir, "openclaw.json"));
|
|
10968
|
+
log(` backed up: openclaw.json (${stat.size} bytes)`);
|
|
10969
|
+
} else log(` skipped: openclaw.json (not found)`);
|
|
10970
|
+
const extSrc = node_path.default.join(workspaceDir, "extensions");
|
|
10971
|
+
for (const pluginDir of FEISHU_PLUGIN_DIRS) {
|
|
10972
|
+
const src = node_path.default.join(extSrc, pluginDir);
|
|
10973
|
+
if (node_fs.default.existsSync(src)) {
|
|
10974
|
+
const dst = node_path.default.join(backupDir, "extensions", pluginDir);
|
|
10975
|
+
node_fs.default.cpSync(src, dst, { recursive: true });
|
|
10976
|
+
const version = readPkgVersion(node_path.default.join(src, "package.json"));
|
|
10977
|
+
log(` backed up: extensions/${pluginDir}${version ? ` (version: ${version})` : ""}`);
|
|
10978
|
+
} else log(` skipped: extensions/${pluginDir} (not found)`);
|
|
10979
|
+
}
|
|
10980
|
+
return { ok: true };
|
|
10981
|
+
} catch (e) {
|
|
10982
|
+
return {
|
|
10983
|
+
ok: false,
|
|
10984
|
+
error: `backup failed: ${e.message}`
|
|
10985
|
+
};
|
|
10986
|
+
}
|
|
10987
|
+
}
|
|
10988
|
+
function restoreFiles(opts) {
|
|
10989
|
+
const { workspaceDir, configPath, backupDir, log } = opts;
|
|
10990
|
+
try {
|
|
10991
|
+
const configBackup = node_path.default.join(backupDir, "openclaw.json");
|
|
10992
|
+
if (node_fs.default.existsSync(configBackup)) {
|
|
10993
|
+
node_fs.default.copyFileSync(configBackup, configPath);
|
|
10994
|
+
log(` restored: openclaw.json`);
|
|
10995
|
+
}
|
|
10996
|
+
const extDst = node_path.default.join(workspaceDir, "extensions");
|
|
10997
|
+
for (const pluginDir of FEISHU_PLUGIN_DIRS) {
|
|
10998
|
+
const backupSrc = node_path.default.join(backupDir, "extensions", pluginDir);
|
|
10999
|
+
if (node_fs.default.existsSync(backupSrc)) {
|
|
11000
|
+
const dst = node_path.default.join(extDst, pluginDir);
|
|
11001
|
+
if (node_fs.default.existsSync(dst)) node_fs.default.rmSync(dst, {
|
|
11002
|
+
recursive: true,
|
|
11003
|
+
force: true
|
|
11004
|
+
});
|
|
11005
|
+
node_fs.default.cpSync(backupSrc, dst, { recursive: true });
|
|
11006
|
+
log(` restored: extensions/${pluginDir}`);
|
|
11007
|
+
}
|
|
11008
|
+
}
|
|
11009
|
+
return true;
|
|
11010
|
+
} catch (e) {
|
|
11011
|
+
log(` restore error: ${e.message}`);
|
|
11012
|
+
return false;
|
|
11013
|
+
}
|
|
11014
|
+
}
|
|
11015
|
+
function readPkgVersion(pkgPath) {
|
|
11016
|
+
try {
|
|
11017
|
+
const pkg = JSON.parse(node_fs.default.readFileSync(pkgPath, "utf-8"));
|
|
11018
|
+
return typeof pkg.version === "string" ? pkg.version : null;
|
|
11019
|
+
} catch {
|
|
11020
|
+
return null;
|
|
11021
|
+
}
|
|
11022
|
+
}
|
|
11023
|
+
function snapshotVersions(cwd, log) {
|
|
11024
|
+
const ocResult = (0, node_child_process.spawnSync)("openclaw", ["--version"], {
|
|
11025
|
+
cwd,
|
|
11026
|
+
encoding: "utf-8",
|
|
11027
|
+
stdio: [
|
|
11028
|
+
"ignore",
|
|
11029
|
+
"pipe",
|
|
11030
|
+
"pipe"
|
|
11031
|
+
],
|
|
11032
|
+
timeout: 5e3
|
|
11033
|
+
});
|
|
11034
|
+
const ocRaw = (ocResult.stdout ?? "").trim() || (ocResult.stderr ?? "").trim();
|
|
11035
|
+
const extDir = node_path.default.join(cwd, "extensions");
|
|
11036
|
+
const larkPkg = node_path.default.join(extDir, "openclaw-lark", "package.json");
|
|
11037
|
+
const feishuPkg = node_path.default.join(extDir, "feishu-openclaw-plugin", "package.json");
|
|
11038
|
+
log(` version-check paths: ${larkPkg} [${node_fs.default.existsSync(larkPkg) ? "exists" : "missing"}]`);
|
|
11039
|
+
log(` version-check paths: ${feishuPkg} [${node_fs.default.existsSync(feishuPkg) ? "exists" : "missing"}]`);
|
|
11040
|
+
return {
|
|
11041
|
+
openclaw: ocRaw || null,
|
|
11042
|
+
openclawLark: readPkgVersion(larkPkg),
|
|
11043
|
+
feishuOpenclawPlugin: readPkgVersion(feishuPkg)
|
|
11044
|
+
};
|
|
11045
|
+
}
|
|
11046
|
+
function logVersionSnapshot(label, v, log) {
|
|
11047
|
+
log(`${label}: openclaw=${v.openclaw ?? "n/a"} openclaw-lark=${v.openclawLark ?? "n/a"} feishu-openclaw-plugin=${v.feishuOpenclawPlugin ?? "n/a"}`);
|
|
11048
|
+
}
|
|
11049
|
+
function countFeishuBots(configPath) {
|
|
11050
|
+
try {
|
|
11051
|
+
const raw = node_fs.default.readFileSync(configPath, "utf-8");
|
|
11052
|
+
const config = loadJSON5().parse(raw);
|
|
11053
|
+
const accounts = getNestedMap(config, "channels", "feishu", "accounts");
|
|
11054
|
+
if (accounts) return Object.keys(accounts).length;
|
|
11055
|
+
const feishu = getNestedMap(config, "channels", "feishu");
|
|
11056
|
+
return typeof feishu?.appId === "string" && feishu.appId ? 1 : 0;
|
|
11057
|
+
} catch {
|
|
11058
|
+
return 0;
|
|
11059
|
+
}
|
|
11060
|
+
}
|
|
11061
|
+
/**
|
|
11062
|
+
* Parse doctor stdout (first JSON line) and return an error string if any
|
|
11063
|
+
* version compat rule failed. Returns null on parse failure so a broken doctor
|
|
11064
|
+
* output does not block the install.
|
|
11065
|
+
*/
|
|
11066
|
+
function checkVersionCompatFromDoctorOutput(stdout, log) {
|
|
11067
|
+
const firstLine = stdout.split("\n")[0]?.trim();
|
|
11068
|
+
if (!firstLine) {
|
|
11069
|
+
log(" doctor(compat): empty output, skipping version compat check");
|
|
11070
|
+
return null;
|
|
11071
|
+
}
|
|
11072
|
+
try {
|
|
11073
|
+
const report = JSON.parse(firstLine);
|
|
11074
|
+
for (const outcome of report.results) if (VERSION_COMPAT_RULE_KEYS.includes(outcome.rule)) {
|
|
11075
|
+
if (outcome.status === "failed" || outcome.status === "still-broken" || outcome.status === "error") return `version compat rule ${outcome.rule} ${outcome.status}: ${outcome.message ?? "(no message)"}`;
|
|
11076
|
+
}
|
|
11077
|
+
return null;
|
|
11078
|
+
} catch (e) {
|
|
11079
|
+
log(` doctor(compat): failed to parse output — ${e.message}`);
|
|
11080
|
+
return null;
|
|
11081
|
+
}
|
|
11082
|
+
}
|
|
11083
|
+
/** Run channels probe, log results, and return the result. Never throws. */
|
|
11084
|
+
function probeChannels(label, log, timeoutMs) {
|
|
11085
|
+
try {
|
|
11086
|
+
const r = runChannelsProbe(timeoutMs);
|
|
11087
|
+
log(` ${label} available=${r.available} anyAccountWorking=${r.anyAccountWorking}`);
|
|
11088
|
+
if (r.error) log(` ${label} error: ${r.error}`);
|
|
11089
|
+
if (r.gatewayReachable != null) log(` ${label} gatewayReachable: ${r.gatewayReachable}`);
|
|
11090
|
+
for (const acct of r.accounts ?? []) log(` ${label} account ${acct.id}: isWorking=${acct.isWorking} bits=[${acct.bits.join(",")}]`);
|
|
11091
|
+
return r;
|
|
11092
|
+
} catch (e) {
|
|
11093
|
+
log(` ${label} channels probe threw: ${e.message}`);
|
|
11094
|
+
return {
|
|
11095
|
+
available: false,
|
|
11096
|
+
accounts: [],
|
|
11097
|
+
anyAccountWorking: false
|
|
11098
|
+
};
|
|
11099
|
+
}
|
|
11100
|
+
}
|
|
11101
|
+
function runUpgradeLark(opts) {
|
|
11102
|
+
const cwd = opts.cwd ?? "/home/gem/workspace/agent";
|
|
11103
|
+
const configPath = opts.configPath ?? CONFIG_PATH;
|
|
11104
|
+
const logFile = upgradeLarkLogFile(opts.runId);
|
|
11105
|
+
const log = makeLogger(logFile);
|
|
11106
|
+
const fsOpts = {
|
|
11107
|
+
workspaceDir: cwd,
|
|
11108
|
+
configPath,
|
|
11109
|
+
backupDir: node_path.default.join(opts.backupBaseDir ?? "/tmp/openclaw-diagnose", `upgrade-lark-backup-${opts.runId}`),
|
|
11110
|
+
log
|
|
11111
|
+
};
|
|
11112
|
+
const cliScript = opts.cliScript ?? process.argv[1];
|
|
11113
|
+
const statusCheckDelayMs = opts.statusCheckDelayMs ?? 5e3;
|
|
11114
|
+
log(`${"=".repeat(60)}`);
|
|
11115
|
+
log(`upgrade-lark started runId=${opts.runId}`);
|
|
11116
|
+
log(` cwd : ${cwd}`);
|
|
11117
|
+
log(` configPath : ${configPath}`);
|
|
11118
|
+
log(`${"=".repeat(60)}`);
|
|
11119
|
+
log("");
|
|
11120
|
+
log("── [1/8] 升级前状态快照 ──────────────────────────────────");
|
|
11121
|
+
log(`before-state: botCount=${countFeishuBots(configPath)}`);
|
|
11122
|
+
log("");
|
|
11123
|
+
log("── [2/8] 文件备份 ────────────────────────────────────────");
|
|
11124
|
+
const backup = backupFiles(fsOpts);
|
|
11125
|
+
if (!backup.ok) {
|
|
11126
|
+
log(`ERROR: ${backup.error}`);
|
|
11127
|
+
return {
|
|
11128
|
+
ok: false,
|
|
11129
|
+
error: backup.error,
|
|
11130
|
+
logFile
|
|
11131
|
+
};
|
|
11132
|
+
}
|
|
11133
|
+
log("backup: ok");
|
|
11134
|
+
logVersionSnapshot("before-versions", snapshotVersions(cwd, log), log);
|
|
11135
|
+
log("");
|
|
11136
|
+
log("── [3/8] channels probe(升级前)────────────────────────");
|
|
11137
|
+
const beforeChannels = probeChannels("before", log, 3e4);
|
|
11138
|
+
log("");
|
|
11139
|
+
log("── [4/8] 清理本地 openclaw shim ─────────────────────────");
|
|
11140
|
+
const localOpenclawBin = node_path.default.join(cwd, "node_modules", ".bin", "openclaw");
|
|
11141
|
+
if (node_fs.default.existsSync(localOpenclawBin)) try {
|
|
11142
|
+
node_fs.default.rmSync(localOpenclawBin);
|
|
11143
|
+
log(` removed: ${localOpenclawBin}`);
|
|
11144
|
+
} catch (e) {
|
|
11145
|
+
log(` WARN: failed to remove ${localOpenclawBin}: ${e.message}`);
|
|
11146
|
+
}
|
|
11147
|
+
else log(` skipped: ${localOpenclawBin} (not found)`);
|
|
11148
|
+
log("");
|
|
11149
|
+
log("── [5/8] npx install (@larksuite/openclaw-lark-tools update) ──");
|
|
11150
|
+
const npxResult = (0, node_child_process.spawnSync)("npx", [
|
|
11151
|
+
"-y",
|
|
11152
|
+
"@larksuite/openclaw-lark-tools",
|
|
11153
|
+
"update"
|
|
11154
|
+
], {
|
|
11155
|
+
cwd,
|
|
11156
|
+
encoding: "utf-8",
|
|
11157
|
+
stdio: [
|
|
11158
|
+
"ignore",
|
|
11159
|
+
"pipe",
|
|
11160
|
+
"pipe"
|
|
11161
|
+
],
|
|
11162
|
+
timeout: 12e4
|
|
11163
|
+
});
|
|
11164
|
+
const npxStdout = npxResult.stdout?.trim() ?? "";
|
|
11165
|
+
const npxStderr = npxResult.stderr?.trim() ?? "";
|
|
11166
|
+
const npxExitCode = npxResult.status ?? 1;
|
|
11167
|
+
if (npxStdout) log(`npx stdout:\n${npxStdout}`);
|
|
11168
|
+
if (npxStderr) log(`npx stderr:\n${npxStderr}`);
|
|
11169
|
+
log(`npx exit: ${npxExitCode}${npxResult.error ? ` error: ${npxResult.error.message}` : ""}`);
|
|
11170
|
+
if (statusCheckDelayMs > 0) {
|
|
11171
|
+
log("");
|
|
11172
|
+
log(`── 等待 ${statusCheckDelayMs / 1e3}s(让 openclaw 服务完成重启) ─────────────`);
|
|
11173
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, statusCheckDelayMs);
|
|
11174
|
+
log("wait done");
|
|
11175
|
+
}
|
|
11176
|
+
const doRollback = (reason) => {
|
|
11177
|
+
log(`ERROR: ${reason}`);
|
|
11178
|
+
const rollbackOk = restoreFiles(fsOpts);
|
|
11179
|
+
log(`rollback: ${rollbackOk ? "ok" : "FAILED"}`);
|
|
11180
|
+
return {
|
|
11181
|
+
ok: false,
|
|
11182
|
+
error: reason,
|
|
11183
|
+
validationError: reason,
|
|
11184
|
+
stdout: npxStdout,
|
|
11185
|
+
stderr: npxStderr,
|
|
11186
|
+
exitCode: npxExitCode,
|
|
11187
|
+
rollbackOk,
|
|
11188
|
+
logFile
|
|
11189
|
+
};
|
|
11190
|
+
};
|
|
11191
|
+
log("");
|
|
11192
|
+
log("── [6/8] 插件安装检查 + 版本兼容校验 ───────────────────");
|
|
11193
|
+
const larkExtDir = node_path.default.join(cwd, "extensions", "openclaw-lark");
|
|
11194
|
+
const larkVersion = readPkgVersion(node_path.default.join(larkExtDir, "package.json"));
|
|
11195
|
+
log(` extensions/openclaw-lark: ${node_fs.default.existsSync(larkExtDir) ? "exists" : "missing"}, version=${larkVersion ?? "n/a"}`);
|
|
11196
|
+
if (!node_fs.default.existsSync(larkExtDir)) return doRollback("extensions/openclaw-lark not found after install");
|
|
11197
|
+
if (!larkVersion) return doRollback("extensions/openclaw-lark/package.json has no valid version after install");
|
|
11198
|
+
log(" running doctor version compat check...");
|
|
11199
|
+
const compatArgs = ["doctor"];
|
|
11200
|
+
if (opts.scene) compatArgs.push(`--scene=${opts.scene}`);
|
|
11201
|
+
const compatResult = (0, node_child_process.spawnSync)(process.execPath, [cliScript, ...compatArgs], {
|
|
11202
|
+
cwd,
|
|
11203
|
+
encoding: "utf-8",
|
|
11204
|
+
stdio: [
|
|
11205
|
+
"ignore",
|
|
11206
|
+
"pipe",
|
|
11207
|
+
"pipe"
|
|
11208
|
+
],
|
|
11209
|
+
timeout: 6e4,
|
|
11210
|
+
env: process.env
|
|
11211
|
+
});
|
|
11212
|
+
if (compatResult.stdout?.trim()) log(`doctor(compat) stdout:\n${compatResult.stdout.trim()}`);
|
|
11213
|
+
if (compatResult.stderr?.trim()) log(`doctor(compat) stderr:\n${compatResult.stderr.trim()}`);
|
|
11214
|
+
log(`doctor(compat) exit: ${compatResult.status ?? "null"}${compatResult.error ? ` error: ${compatResult.error.message}` : ""}`);
|
|
11215
|
+
const compatError = checkVersionCompatFromDoctorOutput(compatResult.stdout?.trim() ?? "", log);
|
|
11216
|
+
if (compatError) return doRollback(compatError);
|
|
11217
|
+
log(" version compat: ok");
|
|
11218
|
+
logVersionSnapshot("after-versions", snapshotVersions(cwd, log), log);
|
|
11219
|
+
log("");
|
|
11220
|
+
log("── [7/8] channels probe(升级后)────────────────────────");
|
|
11221
|
+
if (!probeChannels("after", log, 3e4).anyAccountWorking) {
|
|
11222
|
+
if (!beforeChannels.anyAccountWorking) {
|
|
11223
|
+
log(" channels: not working before or after install — pre-existing issue, skipping rollback");
|
|
11224
|
+
return {
|
|
11225
|
+
ok: false,
|
|
11226
|
+
error: "channels probe: no working account (pre-existing issue, not caused by install)",
|
|
11227
|
+
validationError: "channels probe: no working account (pre-existing)",
|
|
11228
|
+
stdout: npxStdout,
|
|
11229
|
+
stderr: npxStderr,
|
|
11230
|
+
exitCode: npxExitCode,
|
|
11231
|
+
logFile
|
|
11232
|
+
};
|
|
11233
|
+
}
|
|
11234
|
+
return doRollback("channels probe: no working account after install (was working before)");
|
|
11235
|
+
}
|
|
11236
|
+
log(" channels: ok");
|
|
11237
|
+
log("");
|
|
11238
|
+
log("── [8/8] doctor --fix ────────────────────────────────────");
|
|
11239
|
+
const fixArgs = ["doctor", "--fix"];
|
|
11240
|
+
if (opts.scene) fixArgs.push(`--scene=${opts.scene}`);
|
|
11241
|
+
const fixResult = (0, node_child_process.spawnSync)(process.execPath, [cliScript, ...fixArgs], {
|
|
11242
|
+
cwd,
|
|
11243
|
+
encoding: "utf-8",
|
|
11244
|
+
stdio: [
|
|
11245
|
+
"ignore",
|
|
11246
|
+
"pipe",
|
|
11247
|
+
"pipe"
|
|
11248
|
+
],
|
|
11249
|
+
timeout: 6e4,
|
|
11250
|
+
env: process.env
|
|
11251
|
+
});
|
|
11252
|
+
if (fixResult.stdout?.trim()) log(`doctor(fix) stdout:\n${fixResult.stdout.trim()}`);
|
|
11253
|
+
if (fixResult.stderr?.trim()) log(`doctor(fix) stderr:\n${fixResult.stderr.trim()}`);
|
|
11254
|
+
log(`doctor(fix) exit: ${fixResult.status ?? "null"}${fixResult.error ? ` error: ${fixResult.error.message}` : ""}`);
|
|
11255
|
+
log("");
|
|
11256
|
+
log(`${"=".repeat(60)}`);
|
|
11257
|
+
log("upgrade-lark completed successfully");
|
|
11258
|
+
log(`${"=".repeat(60)}`);
|
|
11259
|
+
return {
|
|
11260
|
+
ok: true,
|
|
11261
|
+
stdout: npxStdout,
|
|
11262
|
+
stderr: npxStderr,
|
|
11263
|
+
exitCode: npxExitCode,
|
|
11264
|
+
logFile
|
|
11265
|
+
};
|
|
11266
|
+
}
|
|
10767
11267
|
//#endregion
|
|
10768
11268
|
//#region src/index.ts
|
|
10769
11269
|
const args = node_process.default.argv.slice(2);
|
|
10770
11270
|
const mode = args.find((a) => !a.startsWith("-"));
|
|
10771
11271
|
/**
|
|
11272
|
+
* Decode `--ctx=<base64>` into an opaque JSON object. Returns undefined when
|
|
11273
|
+
* the flag isn't present — the caller decides whether to fall back to the
|
|
11274
|
+
* innerapi or to error out.
|
|
11275
|
+
*
|
|
11276
|
+
* The object's shape is not enforced here; downstream code consumes it via
|
|
11277
|
+
* either `normalizeCtx()` (new path) or direct field access for the legacy
|
|
11278
|
+
* check/repair/reset contract still used by sandbox_console push.
|
|
11279
|
+
*/
|
|
11280
|
+
function parseCtxFlag(args) {
|
|
11281
|
+
const ctxArg = args.find((a) => a.startsWith("--ctx="));
|
|
11282
|
+
if (!ctxArg) return void 0;
|
|
11283
|
+
const b64 = ctxArg.slice(6);
|
|
11284
|
+
return JSON.parse(Buffer.from(b64, "base64").toString("utf-8"));
|
|
11285
|
+
}
|
|
11286
|
+
/**
|
|
10772
11287
|
* Pull the first non-flag positional after the mode name.
|
|
10773
11288
|
* (The mode itself is args[0] in the filtered set, so we skip index 0.)
|
|
10774
11289
|
*/
|
|
@@ -10796,8 +11311,8 @@ function getMultiFlag(args, name) {
|
|
|
10796
11311
|
* case but is no longer consulted.
|
|
10797
11312
|
*/
|
|
10798
11313
|
async function reportRun(command, rc, _raw, invocation, durationMs, outcome, slardar = {
|
|
10799
|
-
scene
|
|
10800
|
-
profile
|
|
11314
|
+
scene,
|
|
11315
|
+
profile,
|
|
10801
11316
|
fix: false
|
|
10802
11317
|
}) {
|
|
10803
11318
|
console.error(`${command}: telemetry calling report_cli_run`);
|
|
@@ -10861,7 +11376,7 @@ async function main() {
|
|
|
10861
11376
|
console.error(`${mode}: begin argv=[${args.join(" ")}] version=${getVersion()} traceId=${traceId ?? "-"} caller=${caller ?? "-"} runIdGenerated=${rc.generated}`);
|
|
10862
11377
|
switch (mode) {
|
|
10863
11378
|
case "check": {
|
|
10864
|
-
const raw = await fetchCtxViaInnerApi({
|
|
11379
|
+
const raw = parseCtxFlag(args) ?? await fetchCtxViaInnerApi({
|
|
10865
11380
|
populate: planCtxPopulate({
|
|
10866
11381
|
command: "check",
|
|
10867
11382
|
profile
|
|
@@ -10886,7 +11401,7 @@ async function main() {
|
|
|
10886
11401
|
break;
|
|
10887
11402
|
}
|
|
10888
11403
|
case "repair": {
|
|
10889
|
-
const raw = await fetchCtxViaInnerApi({
|
|
11404
|
+
const raw = parseCtxFlag(args) ?? await fetchCtxViaInnerApi({
|
|
10890
11405
|
populate: planCtxPopulate({
|
|
10891
11406
|
command: "repair",
|
|
10892
11407
|
profile
|
|
@@ -10957,15 +11472,27 @@ async function main() {
|
|
|
10957
11472
|
break;
|
|
10958
11473
|
}
|
|
10959
11474
|
case "reset":
|
|
10960
|
-
if (args.includes("--async"))
|
|
10961
|
-
|
|
11475
|
+
if (args.includes("--async")) {
|
|
11476
|
+
const ctxArg = args.find((a) => a.startsWith("--ctx="));
|
|
11477
|
+
let ctxBase64;
|
|
11478
|
+
if (ctxArg) ctxBase64 = ctxArg.slice(6);
|
|
11479
|
+
else {
|
|
11480
|
+
const fetched = await fetchCtxViaInnerApi({
|
|
11481
|
+
populate: planCtxPopulate({ command: "reset" }),
|
|
11482
|
+
caller,
|
|
11483
|
+
traceId
|
|
11484
|
+
});
|
|
11485
|
+
ctxBase64 = Buffer.from(JSON.stringify(fetched), "utf-8").toString("base64");
|
|
11486
|
+
}
|
|
11487
|
+
console.log(JSON.stringify(startAsyncReset(ctxBase64)));
|
|
11488
|
+
} else if (args.includes("--worker")) {
|
|
10962
11489
|
const taskId = args.find((a) => a.startsWith("--task-id="))?.slice(10);
|
|
10963
11490
|
if (!taskId) {
|
|
10964
11491
|
console.error("Error: --task-id=<id> is required for worker");
|
|
10965
11492
|
node_process.default.exit(1);
|
|
10966
11493
|
}
|
|
10967
11494
|
const resultFile = resetResultFile(taskId);
|
|
10968
|
-
const raw = await fetchCtxViaInnerApi({
|
|
11495
|
+
const raw = parseCtxFlag(args) ?? await fetchCtxViaInnerApi({
|
|
10969
11496
|
populate: planCtxPopulate({ command: "reset" }),
|
|
10970
11497
|
caller,
|
|
10971
11498
|
traceId
|
|
@@ -10989,7 +11516,7 @@ async function main() {
|
|
|
10989
11516
|
return;
|
|
10990
11517
|
}
|
|
10991
11518
|
} else {
|
|
10992
|
-
console.error("Usage: reset --async | reset --worker --task-id=<id>");
|
|
11519
|
+
console.error("Usage: reset --async [--ctx=<base64>] | reset --worker --task-id=<id> [--ctx=<base64>]");
|
|
10993
11520
|
node_process.default.exit(1);
|
|
10994
11521
|
}
|
|
10995
11522
|
break;
|
|
@@ -11005,14 +11532,14 @@ async function main() {
|
|
|
11005
11532
|
case "install-openclaw": {
|
|
11006
11533
|
const tag = getPositionalTag(args, "install-openclaw");
|
|
11007
11534
|
if (!tag) {
|
|
11008
|
-
console.error("Usage: install-openclaw <tag> [--oss_file_map=<base64>]");
|
|
11535
|
+
console.error("Usage: install-openclaw <tag> [--ctx=<base64> | --oss_file_map=<base64>]");
|
|
11009
11536
|
node_process.default.exit(1);
|
|
11010
11537
|
}
|
|
11011
11538
|
const ossFileMapFlag = getFlag(args, "oss_file_map");
|
|
11012
11539
|
let installOssFileMap;
|
|
11013
11540
|
let rawForTelemetry;
|
|
11014
11541
|
if (!ossFileMapFlag) {
|
|
11015
|
-
rawForTelemetry = await fetchCtxViaInnerApi({
|
|
11542
|
+
rawForTelemetry = parseCtxFlag(args) ?? await fetchCtxViaInnerApi({
|
|
11016
11543
|
populate: planCtxPopulate({ command: "install" }),
|
|
11017
11544
|
caller,
|
|
11018
11545
|
traceId
|
|
@@ -11047,7 +11574,7 @@ async function main() {
|
|
|
11047
11574
|
case "install-extension": {
|
|
11048
11575
|
const tag = getPositionalTag(args, "install-extension");
|
|
11049
11576
|
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>]");
|
|
11577
|
+
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
11578
|
node_process.default.exit(1);
|
|
11052
11579
|
}
|
|
11053
11580
|
const all = args.includes("--all");
|
|
@@ -11059,7 +11586,7 @@ async function main() {
|
|
|
11059
11586
|
let installOssFileMap;
|
|
11060
11587
|
let rawForTelemetry;
|
|
11061
11588
|
if (!ossFileMapFlag) {
|
|
11062
|
-
rawForTelemetry = await fetchCtxViaInnerApi({
|
|
11589
|
+
rawForTelemetry = parseCtxFlag(args) ?? await fetchCtxViaInnerApi({
|
|
11063
11590
|
populate: planCtxPopulate({ command: "install" }),
|
|
11064
11591
|
caller,
|
|
11065
11592
|
traceId
|
|
@@ -11105,12 +11632,12 @@ async function main() {
|
|
|
11105
11632
|
case "install-cli": {
|
|
11106
11633
|
const tag = getPositionalTag(args, "install-cli");
|
|
11107
11634
|
if (!tag) {
|
|
11108
|
-
console.error("Usage: install-cli <tag> --cli=<name>... [--home_base=<dir>] [--oss_file_map=<base64>]");
|
|
11635
|
+
console.error("Usage: install-cli <tag> --cli=<name>... [--home_base=<dir>] [--ctx=<base64> | --oss_file_map=<base64>]");
|
|
11109
11636
|
node_process.default.exit(1);
|
|
11110
11637
|
}
|
|
11111
11638
|
const names = getMultiFlag(args, "cli");
|
|
11112
11639
|
if (names.length === 0) {
|
|
11113
|
-
console.error("Usage: install-cli <tag> --cli=<name>... [--home_base=<dir>] [--oss_file_map=<base64>]");
|
|
11640
|
+
console.error("Usage: install-cli <tag> --cli=<name>... [--home_base=<dir>] [--ctx=<base64> | --oss_file_map=<base64>]");
|
|
11114
11641
|
node_process.default.exit(1);
|
|
11115
11642
|
}
|
|
11116
11643
|
const homeBase = getFlag(args, "home_base");
|
|
@@ -11118,7 +11645,7 @@ async function main() {
|
|
|
11118
11645
|
let installOssFileMap;
|
|
11119
11646
|
let rawForTelemetry;
|
|
11120
11647
|
if (!ossFileMapFlag) {
|
|
11121
|
-
rawForTelemetry = await fetchCtxViaInnerApi({
|
|
11648
|
+
rawForTelemetry = parseCtxFlag(args) ?? await fetchCtxViaInnerApi({
|
|
11122
11649
|
populate: planCtxPopulate({ command: "install" }),
|
|
11123
11650
|
caller,
|
|
11124
11651
|
traceId
|
|
@@ -11166,7 +11693,7 @@ async function main() {
|
|
|
11166
11693
|
case "download-resource": {
|
|
11167
11694
|
const tag = getPositionalTag(args, "download-resource");
|
|
11168
11695
|
if (!tag) {
|
|
11169
|
-
console.error("Usage: download-resource <tag> --role=<role> --name=<name> [--dir=<dir>] [--oss_file_map=<base64>]");
|
|
11696
|
+
console.error("Usage: download-resource <tag> --role=<role> --name=<name> [--dir=<dir>] [--ctx=<base64> | --oss_file_map=<base64>]");
|
|
11170
11697
|
node_process.default.exit(1);
|
|
11171
11698
|
}
|
|
11172
11699
|
const role = getFlag(args, "role");
|
|
@@ -11180,7 +11707,7 @@ async function main() {
|
|
|
11180
11707
|
let installOssFileMap;
|
|
11181
11708
|
let rawForTelemetry;
|
|
11182
11709
|
if (!ossFileMapFlag) {
|
|
11183
|
-
rawForTelemetry = await fetchCtxViaInnerApi({
|
|
11710
|
+
rawForTelemetry = parseCtxFlag(args) ?? await fetchCtxViaInnerApi({
|
|
11184
11711
|
populate: planCtxPopulate({ command: "install" }),
|
|
11185
11712
|
caller,
|
|
11186
11713
|
traceId
|
|
@@ -11254,6 +11781,50 @@ async function main() {
|
|
|
11254
11781
|
if (!result.ok) node_process.default.exit(1);
|
|
11255
11782
|
break;
|
|
11256
11783
|
}
|
|
11784
|
+
case "upgrade-lark": {
|
|
11785
|
+
const result = runUpgradeLark({
|
|
11786
|
+
runId: rc.runId,
|
|
11787
|
+
scene
|
|
11788
|
+
});
|
|
11789
|
+
const upgradeDurationMs = Date.now() - t0;
|
|
11790
|
+
console.log(JSON.stringify(result));
|
|
11791
|
+
reportUpgradeLarkToSlardar({
|
|
11792
|
+
scene,
|
|
11793
|
+
durationMs: upgradeDurationMs,
|
|
11794
|
+
success: result.ok,
|
|
11795
|
+
logFile: result.logFile,
|
|
11796
|
+
exitCode: result.exitCode,
|
|
11797
|
+
rollbackOk: result.rollbackOk,
|
|
11798
|
+
validationError: result.validationError,
|
|
11799
|
+
error: result.error
|
|
11800
|
+
});
|
|
11801
|
+
try {
|
|
11802
|
+
await reportCliRun({
|
|
11803
|
+
command: "upgrade-lark",
|
|
11804
|
+
runId: rc.runId,
|
|
11805
|
+
version: getVersion(),
|
|
11806
|
+
invocation: args.join(" "),
|
|
11807
|
+
durationMs: upgradeDurationMs,
|
|
11808
|
+
caller: rc.caller,
|
|
11809
|
+
traceId: rc.traceId,
|
|
11810
|
+
success: result.ok,
|
|
11811
|
+
result,
|
|
11812
|
+
error: result.ok ? void 0 : { message: result.error ?? "upgrade-lark failed" }
|
|
11813
|
+
});
|
|
11814
|
+
} catch (e) {
|
|
11815
|
+
console.error(`[telemetry] reportCliRun failed: ${e.message}`);
|
|
11816
|
+
}
|
|
11817
|
+
if (!result.ok) {
|
|
11818
|
+
node_process.default.exitCode = 1;
|
|
11819
|
+
return;
|
|
11820
|
+
}
|
|
11821
|
+
break;
|
|
11822
|
+
}
|
|
11823
|
+
case "channels-probe": {
|
|
11824
|
+
const result = runChannelsProbe(getFlag(args, "timeout") ? Number(getFlag(args, "timeout")) : void 0);
|
|
11825
|
+
console.log(JSON.stringify(result));
|
|
11826
|
+
break;
|
|
11827
|
+
}
|
|
11257
11828
|
default:
|
|
11258
11829
|
node_process.default.stderr.write(`Unknown command: ${mode}\n\n`);
|
|
11259
11830
|
node_process.default.stderr.write(formatTopLevelHelp(helpFlags.expert));
|