@lark-apaas/openclaw-scripts-diagnose-cli 0.1.14 → 0.1.15-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 +700 -6
- 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.
|
|
55
|
+
return "0.1.15-alpha.1";
|
|
56
56
|
}
|
|
57
57
|
//#endregion
|
|
58
58
|
//#region src/rule-engine/base.ts
|
|
@@ -2543,6 +2543,10 @@ const PROVIDER_FILE_PATH = "/home/gem/workspace/.force/openclaw/miaoda-provider-
|
|
|
2543
2543
|
const SECRETS_FILE_PATH = "/home/gem/workspace/.force/openclaw/miaoda-openclaw-secrets.json";
|
|
2544
2544
|
/** Absolute path to the openclaw config JSON. */
|
|
2545
2545
|
const CONFIG_PATH = `${WORKSPACE_DIR}/openclaw.json`;
|
|
2546
|
+
/** upgrade-lark 每次运行的日志文件路径,含时间戳便于按时间排序定位。 */
|
|
2547
|
+
function upgradeLarkLogFile(runId) {
|
|
2548
|
+
return `${DIAGNOSE_DIR}/upgrade-lark-${(/* @__PURE__ */ new Date()).toISOString().slice(0, 19).replace(/:/g, "-")}-${runId.slice(0, 8)}.log`;
|
|
2549
|
+
}
|
|
2546
2550
|
//#endregion
|
|
2547
2551
|
//#region src/lark-cli-init.ts
|
|
2548
2552
|
const LARK_PLUGIN_NAMES$1 = ["openclaw-lark", "feishu-openclaw-plugin"];
|
|
@@ -3678,7 +3682,6 @@ function resolveUpgradeDirection(installed, ocCur, recommendedOc, isLegacy) {
|
|
|
3678
3682
|
/** 提取公共前置上下文;任何前置条件不满足时返回 null(规则 pass)。 */
|
|
3679
3683
|
function resolveCompatContext(ctx) {
|
|
3680
3684
|
const recommendedOc = ctx.vars.recommendedOpenclawTag;
|
|
3681
|
-
if (!recommendedOc) return null;
|
|
3682
3685
|
const ocCur = getOcVersion();
|
|
3683
3686
|
if (!ocCur) return null;
|
|
3684
3687
|
const installed = getInstalledPlugin(ctx);
|
|
@@ -3701,6 +3704,7 @@ let FeishuPluginOpenclawUpgradeRule = class FeishuPluginOpenclawUpgradeRule exte
|
|
|
3701
3704
|
if (!cc) return { pass: true };
|
|
3702
3705
|
const { ocCur, recommendedOc, installed, isLegacy } = cc;
|
|
3703
3706
|
if (isForkPlugin(installed)) return validateForkPlugin(installed, ocCur, recommendedOc);
|
|
3707
|
+
if (!recommendedOc) return { pass: true };
|
|
3704
3708
|
if (resolveUpgradeDirection(installed, ocCur, recommendedOc, isLegacy) !== "openclaw") return { pass: true };
|
|
3705
3709
|
return {
|
|
3706
3710
|
pass: false,
|
|
@@ -3728,11 +3732,17 @@ let FeishuPluginLarkUpgradeRule = class FeishuPluginLarkUpgradeRule extends Diag
|
|
|
3728
3732
|
if (!cc) return { pass: true };
|
|
3729
3733
|
const { ocCur, recommendedOc, installed, isLegacy } = cc;
|
|
3730
3734
|
if (isForkPlugin(installed)) return { pass: true };
|
|
3731
|
-
if (
|
|
3735
|
+
if (!isLarkUpgradeNeededFromCC(cc)) return { pass: true };
|
|
3736
|
+
const prefix = buildCompatPrefix(installed, ocCur, isLegacy);
|
|
3737
|
+
if (!recommendedOc) return {
|
|
3738
|
+
pass: false,
|
|
3739
|
+
action: "upgrade_lark",
|
|
3740
|
+
message: `${prefix};建议升级飞书插件至兼容版本`
|
|
3741
|
+
};
|
|
3732
3742
|
return {
|
|
3733
3743
|
pass: false,
|
|
3734
3744
|
action: "upgrade_lark",
|
|
3735
|
-
message: `${
|
|
3745
|
+
message: `${prefix};当前 openclaw@${ocCur} 已达推荐版本,可直接升级飞书插件`
|
|
3736
3746
|
};
|
|
3737
3747
|
}
|
|
3738
3748
|
};
|
|
@@ -3744,6 +3754,21 @@ FeishuPluginLarkUpgradeRule = __decorate([Rule({
|
|
|
3744
3754
|
level: "critical",
|
|
3745
3755
|
usesVars: ["recommendedOpenclawTag"]
|
|
3746
3756
|
})], FeishuPluginLarkUpgradeRule);
|
|
3757
|
+
/**
|
|
3758
|
+
* 核心判断:非 fork 插件是否需要升级 lark,基于当前 openclaw 版本的兼容性。
|
|
3759
|
+
*
|
|
3760
|
+
* 被 FeishuPluginLarkUpgradeRule.validate 和 needsLarkUpgrade 共用。
|
|
3761
|
+
* 调用方需在调用前自行处理 fork 插件的情况(fork 插件不走本函数)。
|
|
3762
|
+
*
|
|
3763
|
+
* - 有 recommendedOc:走 resolveUpgradeDirection 判断方向是否为 'lark'
|
|
3764
|
+
* - 无 recommendedOc(doctor 无推荐版本):legacy 插件直接需要升级;
|
|
3765
|
+
* 非 legacy 则检查当前版本是否在兼容表内
|
|
3766
|
+
*/
|
|
3767
|
+
function isLarkUpgradeNeededFromCC(cc) {
|
|
3768
|
+
const { ocCur, recommendedOc, installed, isLegacy } = cc;
|
|
3769
|
+
if (!recommendedOc) return isLegacy || !isVersionCompatible(installed, ocCur);
|
|
3770
|
+
return resolveUpgradeDirection(installed, ocCur, recommendedOc, isLegacy) === "lark";
|
|
3771
|
+
}
|
|
3747
3772
|
function isForkPlugin(p) {
|
|
3748
3773
|
return p.scope != null && FORK_SCOPES.includes(p.scope);
|
|
3749
3774
|
}
|
|
@@ -3782,14 +3807,16 @@ function describeCompatConstraint(entry, pluginVersion) {
|
|
|
3782
3807
|
/**
|
|
3783
3808
|
* @lark-apaas/openclaw-lark 豁免 VERSION_COMPAT_MAP,但仍要求 openclaw ≥ FORK_LARK_PLUGIN_MIN_OC_VERSION。
|
|
3784
3809
|
* 其他 @lark-apaas scope 的 fork 插件继续无条件 pass。
|
|
3810
|
+
* recommendedOc 可为 undefined(doctor 模式),此时只检测最低版本要求,不指定目标升级版本。
|
|
3785
3811
|
*/
|
|
3786
3812
|
function validateForkPlugin(installed, ocCur, recommendedOc) {
|
|
3787
3813
|
if (installed.fullName !== FORK_LARK_PLUGIN_FULL_NAME) return { pass: true };
|
|
3788
3814
|
if (compareCalVer(ocCur, FORK_LARK_PLUGIN_MIN_OC_VERSION) >= 0) return { pass: true };
|
|
3815
|
+
const recommendation = recommendedOc ? `;将 openclaw 升级到 ${recommendedOc} 即可满足` : `;请升级 openclaw 至 ${FORK_LARK_PLUGIN_MIN_OC_VERSION} 或更高版本`;
|
|
3789
3816
|
return {
|
|
3790
3817
|
pass: false,
|
|
3791
3818
|
action: "upgrade_openclaw",
|
|
3792
|
-
message: `飞书插件 ${describePlugin(installed)}(fork 版)要求 openclaw ≥ ${FORK_LARK_PLUGIN_MIN_OC_VERSION},当前 openclaw@${ocCur}
|
|
3819
|
+
message: `飞书插件 ${describePlugin(installed)}(fork 版)要求 openclaw ≥ ${FORK_LARK_PLUGIN_MIN_OC_VERSION},当前 openclaw@${ocCur} 低于此要求${recommendation}`
|
|
3793
3820
|
};
|
|
3794
3821
|
}
|
|
3795
3822
|
function describePlugin(p) {
|
|
@@ -3836,6 +3863,26 @@ function extractScopedNameFromSpec$1(spec) {
|
|
|
3836
3863
|
const at = spec.indexOf("@", 1);
|
|
3837
3864
|
return at === -1 ? spec : spec.slice(0, at);
|
|
3838
3865
|
}
|
|
3866
|
+
/**
|
|
3867
|
+
* 判断已安装的飞书插件是否与当前 openclaw 版本不兼容(或为需要替换的 legacy 插件)。
|
|
3868
|
+
* 被 upgrade-lark 前置检测门控(--check-only 和正式安装模式)调用。
|
|
3869
|
+
*/
|
|
3870
|
+
function needsLarkUpgrade(ctx) {
|
|
3871
|
+
const cc = resolveCompatContext({
|
|
3872
|
+
...ctx,
|
|
3873
|
+
vars: {
|
|
3874
|
+
...ctx.vars,
|
|
3875
|
+
recommendedOpenclawTag: void 0
|
|
3876
|
+
}
|
|
3877
|
+
});
|
|
3878
|
+
if (!cc) return false;
|
|
3879
|
+
const { ocCur, installed } = cc;
|
|
3880
|
+
if (isForkPlugin(installed)) {
|
|
3881
|
+
if (installed.fullName === FORK_LARK_PLUGIN_FULL_NAME) return compareCalVer(ocCur, FORK_LARK_PLUGIN_MIN_OC_VERSION) < 0;
|
|
3882
|
+
return false;
|
|
3883
|
+
}
|
|
3884
|
+
return isLarkUpgradeNeededFromCC(cc);
|
|
3885
|
+
}
|
|
3839
3886
|
//#endregion
|
|
3840
3887
|
//#region src/rules/cleanup-install-backup-dirs.ts
|
|
3841
3888
|
const DIR_PREFIX = ".openclaw-install-";
|
|
@@ -10339,6 +10386,122 @@ function finalize(results, aborted) {
|
|
|
10339
10386
|
};
|
|
10340
10387
|
}
|
|
10341
10388
|
//#endregion
|
|
10389
|
+
//#region src/channels-probe.ts
|
|
10390
|
+
const FEISHU_INVALID_CONFIG_MSG = "channels.feishu: invalid config: must NOT have additional properties";
|
|
10391
|
+
const CHANNEL_LINE_RE = /^-\s+Feishu\s+([^:]+):\s+(.+)$/;
|
|
10392
|
+
/**
|
|
10393
|
+
* 判断单个飞书账号是否处于"可用"状态。
|
|
10394
|
+
* 移植自 feishu-channel-success-rate skill 的 Python `_account_is_working`。
|
|
10395
|
+
*
|
|
10396
|
+
* 会先剥离 "key:value" 形式的 bit(dm:、bot:、in:、out:、token:、allow:、
|
|
10397
|
+
* intents:、groups:、health: 等),再按以下公式判断:
|
|
10398
|
+
*
|
|
10399
|
+
* @param ignoreProbeFailed 为 true 时忽略 "probe failed" bit,不视为失败。
|
|
10400
|
+
* 升级前置检测中传 true,因为 probe 失败通常反映网络状况而非配置问题。
|
|
10401
|
+
* @param gatewayReachable 为 false 时(gateway 尚未启动),只要 enabled+configured
|
|
10402
|
+
* 即视为可用;为 true 时还需要 running/works 且无 error: bit。
|
|
10403
|
+
*/
|
|
10404
|
+
function accountIsWorking(bits, ignoreProbeFailed = true, gatewayReachable = true) {
|
|
10405
|
+
const bitTokens = /* @__PURE__ */ new Set();
|
|
10406
|
+
let hasError = false;
|
|
10407
|
+
let hasProbeFailed = false;
|
|
10408
|
+
for (const raw of bits) {
|
|
10409
|
+
const b = raw.trim();
|
|
10410
|
+
if (!b) continue;
|
|
10411
|
+
if (b.startsWith("error:")) {
|
|
10412
|
+
hasError = true;
|
|
10413
|
+
continue;
|
|
10414
|
+
}
|
|
10415
|
+
if (b === "probe failed") {
|
|
10416
|
+
hasProbeFailed = true;
|
|
10417
|
+
continue;
|
|
10418
|
+
}
|
|
10419
|
+
bitTokens.add(b.split(":")[0]);
|
|
10420
|
+
}
|
|
10421
|
+
if (!bitTokens.has("enabled") || !bitTokens.has("configured")) return false;
|
|
10422
|
+
if (!gatewayReachable) return true;
|
|
10423
|
+
if (bitTokens.has("works")) return true;
|
|
10424
|
+
if (bitTokens.has("running") && !hasError && (ignoreProbeFailed || !hasProbeFailed)) return true;
|
|
10425
|
+
return false;
|
|
10426
|
+
}
|
|
10427
|
+
/**
|
|
10428
|
+
* 解析 `openclaw channels status --probe` 的原始 stdout。
|
|
10429
|
+
* 移植自 feishu-channel-success-rate skill 的 Python `extract_channels_probe`。
|
|
10430
|
+
*/
|
|
10431
|
+
function parseChannelsProbeOutput(text, { ignoreProbeFailed = true } = {}) {
|
|
10432
|
+
const gatewayReachable = text.includes("Gateway reachable");
|
|
10433
|
+
const feishuConfigInvalid = text.includes(FEISHU_INVALID_CONFIG_MSG);
|
|
10434
|
+
const accounts = [];
|
|
10435
|
+
let anyAccountWorking = false;
|
|
10436
|
+
for (const line of text.split("\n")) {
|
|
10437
|
+
const m = CHANNEL_LINE_RE.exec(line.trim());
|
|
10438
|
+
if (!m) continue;
|
|
10439
|
+
const [, acct, rest] = m;
|
|
10440
|
+
const bits = rest.split(",").map((b) => b.trim());
|
|
10441
|
+
const isWorking = accountIsWorking(bits, ignoreProbeFailed, gatewayReachable);
|
|
10442
|
+
if (isWorking) anyAccountWorking = true;
|
|
10443
|
+
accounts.push({
|
|
10444
|
+
id: acct.trim(),
|
|
10445
|
+
bits,
|
|
10446
|
+
isWorking,
|
|
10447
|
+
raw: line.trim()
|
|
10448
|
+
});
|
|
10449
|
+
}
|
|
10450
|
+
return {
|
|
10451
|
+
gatewayReachable,
|
|
10452
|
+
feishuConfigInvalid,
|
|
10453
|
+
accounts,
|
|
10454
|
+
anyAccountWorking
|
|
10455
|
+
};
|
|
10456
|
+
}
|
|
10457
|
+
/**
|
|
10458
|
+
* 执行 `openclaw channels status --probe`,返回结构化结果。
|
|
10459
|
+
*
|
|
10460
|
+
* 部分 bot 账号 probe 失败时命令会以非零退出码退出,但 stdout 仍有可用内容。
|
|
10461
|
+
* 因此即使退出码非零,也尝试解析 stdout;只有真正没有任何输出时才返回 unavailable。
|
|
10462
|
+
*
|
|
10463
|
+
* @param timeoutMs 最长等待时长,默认 60 s。v2026.4.x 缺少单请求 HTTP 超时,
|
|
10464
|
+
* 可能无限阻塞,此超时是唯一保护。
|
|
10465
|
+
* @param ignoreProbeFailed 为 true 时,"probe failed" 账号仍计入"可用"。
|
|
10466
|
+
* 升级前置检测中应传 true,避免网络抖动导致误判为不可用。
|
|
10467
|
+
*/
|
|
10468
|
+
function runChannelsProbe(timeoutMs = 6e4, { ignoreProbeFailed = true } = {}) {
|
|
10469
|
+
let stdout = "";
|
|
10470
|
+
let stderrText = "";
|
|
10471
|
+
let execError;
|
|
10472
|
+
try {
|
|
10473
|
+
stdout = (0, node_child_process.execSync)("openclaw channels status --probe", {
|
|
10474
|
+
encoding: "utf-8",
|
|
10475
|
+
timeout: timeoutMs,
|
|
10476
|
+
stdio: [
|
|
10477
|
+
"ignore",
|
|
10478
|
+
"pipe",
|
|
10479
|
+
"pipe"
|
|
10480
|
+
]
|
|
10481
|
+
});
|
|
10482
|
+
} catch (e) {
|
|
10483
|
+
const err = e;
|
|
10484
|
+
const stdoutRaw = err.stdout;
|
|
10485
|
+
stdout = typeof stdoutRaw === "string" ? stdoutRaw : stdoutRaw?.toString("utf-8") ?? "";
|
|
10486
|
+
execError = err.message;
|
|
10487
|
+
const stderrRaw = err.stderr;
|
|
10488
|
+
stderrText = (typeof stderrRaw === "string" ? stderrRaw : stderrRaw?.toString("utf-8") ?? "").trim();
|
|
10489
|
+
if (stderrText) console.error(`channels-probe: stderr from CLI: ${stderrText}`);
|
|
10490
|
+
}
|
|
10491
|
+
if (stdout.trim()) return {
|
|
10492
|
+
available: true,
|
|
10493
|
+
...parseChannelsProbeOutput(stdout, { ignoreProbeFailed })
|
|
10494
|
+
};
|
|
10495
|
+
return {
|
|
10496
|
+
available: false,
|
|
10497
|
+
gatewayReachable: false,
|
|
10498
|
+
feishuConfigInvalid: stderrText.includes(FEISHU_INVALID_CONFIG_MSG),
|
|
10499
|
+
accounts: [],
|
|
10500
|
+
anyAccountWorking: false,
|
|
10501
|
+
error: execError ?? "no output from openclaw channels status --probe"
|
|
10502
|
+
};
|
|
10503
|
+
}
|
|
10504
|
+
//#endregion
|
|
10342
10505
|
//#region src/innerapi/reportCliRun.ts
|
|
10343
10506
|
/**
|
|
10344
10507
|
* CLI-side client for studio_server's `openclaw.report_cli_run` inner
|
|
@@ -10418,7 +10581,7 @@ async function reportCliRun(opts) {
|
|
|
10418
10581
|
//#region src/help.ts
|
|
10419
10582
|
const BIN = "mclaw-diagnose";
|
|
10420
10583
|
function versionBanner() {
|
|
10421
|
-
return `v0.1.
|
|
10584
|
+
return `v0.1.15-alpha.1`;
|
|
10422
10585
|
}
|
|
10423
10586
|
const COMMANDS = [
|
|
10424
10587
|
{
|
|
@@ -10705,6 +10868,58 @@ OPTIONS
|
|
|
10705
10868
|
EXIT CODES
|
|
10706
10869
|
0 Success or skipped (prerequisites not met).
|
|
10707
10870
|
1 Secret/path unresolvable, lark-cli failed, or config unreadable.
|
|
10871
|
+
`
|
|
10872
|
+
},
|
|
10873
|
+
{
|
|
10874
|
+
name: "upgrade-lark",
|
|
10875
|
+
hidden: false,
|
|
10876
|
+
summary: "Upgrade the Feishu/Lark plugin via @larksuite/openclaw-lark-tools",
|
|
10877
|
+
help: `USAGE
|
|
10878
|
+
${BIN} upgrade-lark [--check-only] [--scene=<scene>] [--caller=<n>] [--trace-id=<id>]
|
|
10879
|
+
|
|
10880
|
+
DESCRIPTION
|
|
10881
|
+
Upgrades the Feishu/Lark plugin by running:
|
|
10882
|
+
npx -y @larksuite/openclaw-lark-tools update --use-existing
|
|
10883
|
+
|
|
10884
|
+
Before the upgrade, a pre-check gate runs to verify the upgrade is needed:
|
|
10885
|
+
- version incompatible OR feishu channel config invalid, AND
|
|
10886
|
+
- no feishu account is currently working
|
|
10887
|
+
If the gate is not triggered, the command skips with exit code 0.
|
|
10888
|
+
|
|
10889
|
+
Before the upgrade, the following files are backed up:
|
|
10890
|
+
- openclaw.json
|
|
10891
|
+
- extensions/openclaw-lark/ (if present)
|
|
10892
|
+
- extensions/feishu-openclaw-plugin/ (if present)
|
|
10893
|
+
After the upgrade, the result is validated:
|
|
10894
|
+
- feishu.accounts bot count must not decrease
|
|
10895
|
+
- gateway config structure must remain valid (port/mode/bind/auth/trustedProxies)
|
|
10896
|
+
If the upgrade command fails, or validation fails, the backed-up files are
|
|
10897
|
+
restored to roll back the changes.
|
|
10898
|
+
|
|
10899
|
+
Execution is logged to /tmp/openclaw-diagnose/upgrade-lark-<runId>.log.
|
|
10900
|
+
|
|
10901
|
+
Output is a single JSON object on stdout:
|
|
10902
|
+
{ "ok": true, "stdout": "...", "stderr": "...", "logFile": "..." }
|
|
10903
|
+
{ "ok": false, "error": "...", "stderr": "...", "exitCode": 1,
|
|
10904
|
+
"rollbackOk": true, "validationError": "...", "logFile": "..." }
|
|
10905
|
+
|
|
10906
|
+
With --check-only:
|
|
10907
|
+
{ "ok": true, "skipped": true, "upgradeNeeded": false, "logFile": "..." }
|
|
10908
|
+
{ "ok": true, "skipped": true, "upgradeNeeded": true, "logFile": "..." } ← exit 1
|
|
10909
|
+
|
|
10910
|
+
OPTIONS
|
|
10911
|
+
--check-only Diagnose only: run the pre-check gate and report whether
|
|
10912
|
+
upgrade is needed without installing. Exit 1 if needed.
|
|
10913
|
+
--scene=<scene> Telemetry label forwarded to Slardar only.
|
|
10914
|
+
Known values: PageUpgradeLark, etc. Custom strings accepted.
|
|
10915
|
+
--caller=<name> Optional metadata passed to innerapi.
|
|
10916
|
+
--trace-id=<id> Optional log-correlation id.
|
|
10917
|
+
|
|
10918
|
+
EXIT CODES
|
|
10919
|
+
0 Success: upgrade ran and all validations passed; or gate skipped upgrade.
|
|
10920
|
+
1 Failure: npx error, validation failed, or git commit failed.
|
|
10921
|
+
File rollback was attempted (see rollbackOk in the JSON output).
|
|
10922
|
+
With --check-only: exit 1 means upgrade IS needed.
|
|
10708
10923
|
`
|
|
10709
10924
|
},
|
|
10710
10925
|
{
|
|
@@ -10738,6 +10953,41 @@ EXAMPLES
|
|
|
10738
10953
|
${BIN} rules # all rules
|
|
10739
10954
|
${BIN} rules --rule=gateway # single rule
|
|
10740
10955
|
${BIN} rules --rule=gateway --rule=feishu_channel # multiple rules
|
|
10956
|
+
`
|
|
10957
|
+
},
|
|
10958
|
+
{
|
|
10959
|
+
name: "channels-probe",
|
|
10960
|
+
hidden: true,
|
|
10961
|
+
summary: "Check feishu channel health via openclaw channels status --probe",
|
|
10962
|
+
help: `USAGE
|
|
10963
|
+
${BIN} channels-probe [--timeout=<ms>]
|
|
10964
|
+
|
|
10965
|
+
DESCRIPTION
|
|
10966
|
+
Runs \`openclaw channels status --probe\` and returns a structured JSON
|
|
10967
|
+
summary of whether the current environment's feishu channels are
|
|
10968
|
+
configured and working correctly.
|
|
10969
|
+
|
|
10970
|
+
Output:
|
|
10971
|
+
{
|
|
10972
|
+
"available": true,
|
|
10973
|
+
"gatewayReachable": true,
|
|
10974
|
+
"accounts": [
|
|
10975
|
+
{ "id": "default", "bits": ["enabled","configured","running","works"],
|
|
10976
|
+
"isWorking": true, "raw": "- Feishu default: ..." }
|
|
10977
|
+
],
|
|
10978
|
+
"anyAccountWorking": true
|
|
10979
|
+
}
|
|
10980
|
+
|
|
10981
|
+
An account is considered working when:
|
|
10982
|
+
enabled ∧ configured ∧ ( works ∨ ( running ∧ no error: ∧ no probe failed ) )
|
|
10983
|
+
|
|
10984
|
+
"available": false means the CLI invocation itself failed (openclaw not
|
|
10985
|
+
found, gateway unreachable, or no parseable output returned).
|
|
10986
|
+
|
|
10987
|
+
OPTIONS
|
|
10988
|
+
--timeout=<ms> Max wait in milliseconds (default: 60000). The probe
|
|
10989
|
+
can hang indefinitely on openclaw v2026.4.x due to a
|
|
10990
|
+
missing per-request HTTP timeout — set this accordingly.
|
|
10741
10991
|
`
|
|
10742
10992
|
},
|
|
10743
10993
|
{
|
|
@@ -10912,6 +11162,403 @@ function reportDoctorRunToSlardar(opts) {
|
|
|
10912
11162
|
}
|
|
10913
11163
|
});
|
|
10914
11164
|
}
|
|
11165
|
+
/** 读取日志文件全文;文件不存在或读取失败时返回空字符串。 */
|
|
11166
|
+
function readLogFile(filePath) {
|
|
11167
|
+
try {
|
|
11168
|
+
return node_fs.default.readFileSync(filePath, "utf-8");
|
|
11169
|
+
} catch {
|
|
11170
|
+
return "";
|
|
11171
|
+
}
|
|
11172
|
+
}
|
|
11173
|
+
/**
|
|
11174
|
+
* 向 Slardar 上报 upgrade-lark 运行结果(upgrade_lark_run 事件)。
|
|
11175
|
+
*
|
|
11176
|
+
* extraCategories 记录字符串维度:scene、exit_code、rollback_ok、
|
|
11177
|
+
* validation_error、error_msg、log_content(日志文件全文)。
|
|
11178
|
+
*
|
|
11179
|
+
* extraMetrics 记录各阶段耗时(毫秒);未执行的阶段上报 -1 作为哨兵值,
|
|
11180
|
+
* 便于在 Slardar 查询时区分"未运行"和"运行了 0ms"。
|
|
11181
|
+
*/
|
|
11182
|
+
function reportUpgradeLarkToSlardar(opts) {
|
|
11183
|
+
console.error(`[slardar] upgrade_lark_run scene=${opts.scene ?? ""} success=${opts.success} exitCode=${opts.exitCode ?? ""} rollbackOk=${opts.rollbackOk ?? ""}`);
|
|
11184
|
+
const t = opts.timing ?? {};
|
|
11185
|
+
const logContent = readLogFile(opts.logFile);
|
|
11186
|
+
reportTask({
|
|
11187
|
+
eventName: "upgrade_lark_run",
|
|
11188
|
+
durationMs: opts.durationMs,
|
|
11189
|
+
status: opts.success ? "success" : "failed",
|
|
11190
|
+
extraCategories: {
|
|
11191
|
+
scene: opts.scene ?? "",
|
|
11192
|
+
exit_code: String(opts.exitCode ?? ""),
|
|
11193
|
+
rollback_ok: opts.rollbackOk != null ? String(opts.rollbackOk) : "",
|
|
11194
|
+
validation_error: opts.validationError ?? "",
|
|
11195
|
+
error_msg: opts.error ?? "",
|
|
11196
|
+
log_content: logContent
|
|
11197
|
+
},
|
|
11198
|
+
extraMetrics: {
|
|
11199
|
+
pre_probe_ms: t.preProbeMs ?? -1,
|
|
11200
|
+
version_check_ms: t.versionCheckMs ?? -1,
|
|
11201
|
+
backup_ms: t.backupMs ?? -1,
|
|
11202
|
+
npx_install_ms: t.npxInstallMs ?? -1,
|
|
11203
|
+
post_probe_ms: t.postProbeMs ?? -1,
|
|
11204
|
+
doctor_fix_ms: t.doctorFixMs ?? -1
|
|
11205
|
+
}
|
|
11206
|
+
});
|
|
11207
|
+
}
|
|
11208
|
+
//#endregion
|
|
11209
|
+
//#region src/upgrade-lark.ts
|
|
11210
|
+
/** 升级前需备份的 extensions/ 下的插件目录 */
|
|
11211
|
+
const FEISHU_PLUGIN_DIRS = ["openclaw-lark", "feishu-openclaw-plugin"];
|
|
11212
|
+
function backupFiles(opts) {
|
|
11213
|
+
const { workspaceDir, configPath, backupDir, log } = opts;
|
|
11214
|
+
try {
|
|
11215
|
+
node_fs.default.mkdirSync(backupDir, { recursive: true });
|
|
11216
|
+
log(`backup dir: ${backupDir}`);
|
|
11217
|
+
if (node_fs.default.existsSync(configPath)) {
|
|
11218
|
+
const stat = node_fs.default.statSync(configPath);
|
|
11219
|
+
node_fs.default.copyFileSync(configPath, node_path.default.join(backupDir, "openclaw.json"));
|
|
11220
|
+
log(` backed up: openclaw.json (${stat.size} bytes)`);
|
|
11221
|
+
} else log(` skipped: openclaw.json (not found)`);
|
|
11222
|
+
const extSrc = node_path.default.join(workspaceDir, "extensions");
|
|
11223
|
+
for (const pluginDir of FEISHU_PLUGIN_DIRS) {
|
|
11224
|
+
const src = node_path.default.join(extSrc, pluginDir);
|
|
11225
|
+
if (node_fs.default.existsSync(src)) {
|
|
11226
|
+
const dst = node_path.default.join(backupDir, "extensions", pluginDir);
|
|
11227
|
+
node_fs.default.cpSync(src, dst, { recursive: true });
|
|
11228
|
+
const version = readPkgVersion(node_path.default.join(src, "package.json"));
|
|
11229
|
+
log(` backed up: extensions/${pluginDir}${version ? ` (version: ${version})` : ""}`);
|
|
11230
|
+
} else log(` skipped: extensions/${pluginDir} (not found)`);
|
|
11231
|
+
}
|
|
11232
|
+
return { ok: true };
|
|
11233
|
+
} catch (e) {
|
|
11234
|
+
return {
|
|
11235
|
+
ok: false,
|
|
11236
|
+
error: `backup failed: ${e.message}`
|
|
11237
|
+
};
|
|
11238
|
+
}
|
|
11239
|
+
}
|
|
11240
|
+
function restoreFiles(opts) {
|
|
11241
|
+
const { workspaceDir, configPath, backupDir, log } = opts;
|
|
11242
|
+
try {
|
|
11243
|
+
const configBackup = node_path.default.join(backupDir, "openclaw.json");
|
|
11244
|
+
if (node_fs.default.existsSync(configBackup)) {
|
|
11245
|
+
node_fs.default.copyFileSync(configBackup, configPath);
|
|
11246
|
+
log(` restored: openclaw.json`);
|
|
11247
|
+
}
|
|
11248
|
+
const extDst = node_path.default.join(workspaceDir, "extensions");
|
|
11249
|
+
for (const pluginDir of FEISHU_PLUGIN_DIRS) {
|
|
11250
|
+
const backupSrc = node_path.default.join(backupDir, "extensions", pluginDir);
|
|
11251
|
+
if (node_fs.default.existsSync(backupSrc)) {
|
|
11252
|
+
const dst = node_path.default.join(extDst, pluginDir);
|
|
11253
|
+
if (node_fs.default.existsSync(dst)) node_fs.default.rmSync(dst, {
|
|
11254
|
+
recursive: true,
|
|
11255
|
+
force: true
|
|
11256
|
+
});
|
|
11257
|
+
node_fs.default.cpSync(backupSrc, dst, { recursive: true });
|
|
11258
|
+
log(` restored: extensions/${pluginDir}`);
|
|
11259
|
+
}
|
|
11260
|
+
}
|
|
11261
|
+
return true;
|
|
11262
|
+
} catch (e) {
|
|
11263
|
+
log(` restore error: ${e.message}`);
|
|
11264
|
+
return false;
|
|
11265
|
+
}
|
|
11266
|
+
}
|
|
11267
|
+
function readPkgVersion(pkgPath) {
|
|
11268
|
+
try {
|
|
11269
|
+
const pkg = JSON.parse(node_fs.default.readFileSync(pkgPath, "utf-8"));
|
|
11270
|
+
return typeof pkg.version === "string" ? pkg.version : null;
|
|
11271
|
+
} catch {
|
|
11272
|
+
return null;
|
|
11273
|
+
}
|
|
11274
|
+
}
|
|
11275
|
+
function snapshotVersions(cwd, log) {
|
|
11276
|
+
const ocResult = (0, node_child_process.spawnSync)("openclaw", ["--version"], {
|
|
11277
|
+
cwd,
|
|
11278
|
+
encoding: "utf-8",
|
|
11279
|
+
stdio: [
|
|
11280
|
+
"ignore",
|
|
11281
|
+
"pipe",
|
|
11282
|
+
"pipe"
|
|
11283
|
+
],
|
|
11284
|
+
timeout: 5e3
|
|
11285
|
+
});
|
|
11286
|
+
const ocRaw = (ocResult.stdout ?? "").trim() || (ocResult.stderr ?? "").trim();
|
|
11287
|
+
const extDir = node_path.default.join(cwd, "extensions");
|
|
11288
|
+
const larkPkg = node_path.default.join(extDir, "openclaw-lark", "package.json");
|
|
11289
|
+
const feishuPkg = node_path.default.join(extDir, "feishu-openclaw-plugin", "package.json");
|
|
11290
|
+
log(` version-check paths: ${larkPkg} [${node_fs.default.existsSync(larkPkg) ? "exists" : "missing"}]`);
|
|
11291
|
+
log(` version-check paths: ${feishuPkg} [${node_fs.default.existsSync(feishuPkg) ? "exists" : "missing"}]`);
|
|
11292
|
+
return {
|
|
11293
|
+
openclaw: ocRaw || null,
|
|
11294
|
+
openclawLark: readPkgVersion(larkPkg),
|
|
11295
|
+
feishuOpenclawPlugin: readPkgVersion(feishuPkg)
|
|
11296
|
+
};
|
|
11297
|
+
}
|
|
11298
|
+
function logVersionSnapshot(label, v, log) {
|
|
11299
|
+
log(`${label}: openclaw=${v.openclaw ?? "n/a"} openclaw-lark=${v.openclawLark ?? "n/a"} feishu-openclaw-plugin=${v.feishuOpenclawPlugin ?? "n/a"}`);
|
|
11300
|
+
}
|
|
11301
|
+
function countFeishuBots(configPath) {
|
|
11302
|
+
try {
|
|
11303
|
+
const raw = node_fs.default.readFileSync(configPath, "utf-8");
|
|
11304
|
+
const config = loadJSON5().parse(raw);
|
|
11305
|
+
const accounts = getNestedMap(config, "channels", "feishu", "accounts");
|
|
11306
|
+
if (accounts) return Object.keys(accounts).length;
|
|
11307
|
+
const feishu = getNestedMap(config, "channels", "feishu");
|
|
11308
|
+
return typeof feishu?.appId === "string" && feishu.appId ? 1 : 0;
|
|
11309
|
+
} catch {
|
|
11310
|
+
return 0;
|
|
11311
|
+
}
|
|
11312
|
+
}
|
|
11313
|
+
/** 执行 channels probe 并将结果写入日志,从不抛出异常(异常时返回全零结果)。 */
|
|
11314
|
+
function probeChannels(label, log, timeoutMs) {
|
|
11315
|
+
try {
|
|
11316
|
+
const r = runChannelsProbe(timeoutMs);
|
|
11317
|
+
log(` ${label} available=${r.available} anyAccountWorking=${r.anyAccountWorking}`);
|
|
11318
|
+
if (r.error) log(` ${label} error: ${r.error}`);
|
|
11319
|
+
if (r.gatewayReachable != null) log(` ${label} gatewayReachable: ${r.gatewayReachable}`);
|
|
11320
|
+
for (const acct of r.accounts ?? []) log(` ${label} account ${acct.id}: isWorking=${acct.isWorking} bits=[${acct.bits.join(",")}]`);
|
|
11321
|
+
return r;
|
|
11322
|
+
} catch (e) {
|
|
11323
|
+
log(` ${label} channels probe threw: ${e.message}`);
|
|
11324
|
+
return {
|
|
11325
|
+
available: false,
|
|
11326
|
+
gatewayReachable: false,
|
|
11327
|
+
feishuConfigInvalid: false,
|
|
11328
|
+
accounts: [],
|
|
11329
|
+
anyAccountWorking: false
|
|
11330
|
+
};
|
|
11331
|
+
}
|
|
11332
|
+
}
|
|
11333
|
+
function runUpgradeLark(opts) {
|
|
11334
|
+
const cwd = opts.cwd ?? "/home/gem/workspace/agent";
|
|
11335
|
+
const configPath = opts.configPath ?? CONFIG_PATH;
|
|
11336
|
+
const logFile = upgradeLarkLogFile(opts.runId);
|
|
11337
|
+
const log = makeLogger(logFile);
|
|
11338
|
+
const fsOpts = {
|
|
11339
|
+
workspaceDir: cwd,
|
|
11340
|
+
configPath,
|
|
11341
|
+
backupDir: node_path.default.join(opts.backupBaseDir ?? "/tmp/openclaw-diagnose", `upgrade-lark-backup-${opts.runId}`),
|
|
11342
|
+
log
|
|
11343
|
+
};
|
|
11344
|
+
const cliScript = opts.cliScript ?? process.argv[1];
|
|
11345
|
+
const statusCheckDelayMs = opts.statusCheckDelayMs ?? 5e3;
|
|
11346
|
+
log(`${"=".repeat(60)}`);
|
|
11347
|
+
log(`upgrade-lark started runId=${opts.runId}`);
|
|
11348
|
+
log(` cwd : ${cwd}`);
|
|
11349
|
+
log(` configPath : ${configPath}`);
|
|
11350
|
+
log(`${"=".repeat(60)}`);
|
|
11351
|
+
const timing = {};
|
|
11352
|
+
log("");
|
|
11353
|
+
log("── [Pre-check A] channels probe(升级前)────────────────");
|
|
11354
|
+
const t_preProbeStart = Date.now();
|
|
11355
|
+
const beforeChannels = probeChannels("before", log, 6e4);
|
|
11356
|
+
timing.preProbeMs = Date.now() - t_preProbeStart;
|
|
11357
|
+
log("");
|
|
11358
|
+
log("── [Pre-check B] 版本兼容预检 ───────────────────────────");
|
|
11359
|
+
let versionIncompatible = false;
|
|
11360
|
+
const t_versionCheckStart = Date.now();
|
|
11361
|
+
try {
|
|
11362
|
+
const rawConfig = node_fs.default.readFileSync(configPath, "utf-8");
|
|
11363
|
+
versionIncompatible = needsLarkUpgrade({
|
|
11364
|
+
config: loadJSON5().parse(rawConfig),
|
|
11365
|
+
configPath,
|
|
11366
|
+
vars: {},
|
|
11367
|
+
providerDeps: {
|
|
11368
|
+
usesMiaodaProvider: false,
|
|
11369
|
+
usesMiaodaSecretProvider: false
|
|
11370
|
+
}
|
|
11371
|
+
});
|
|
11372
|
+
log(` version-compat pre-check: ${versionIncompatible ? "NEEDS_UPGRADE" : "ok"}`);
|
|
11373
|
+
} catch (e) {
|
|
11374
|
+
log(` version-compat pre-check error: ${e.message} — version signal unavailable`);
|
|
11375
|
+
}
|
|
11376
|
+
timing.versionCheckMs = Date.now() - t_versionCheckStart;
|
|
11377
|
+
const feishuConfigInvalid = beforeChannels.feishuConfigInvalid;
|
|
11378
|
+
log(` feishu config invalid : ${feishuConfigInvalid}`);
|
|
11379
|
+
log("");
|
|
11380
|
+
log("── [Gate] 升级前置条件检查 ───────────────────────────────");
|
|
11381
|
+
log(` versionIncompatible : ${versionIncompatible}`);
|
|
11382
|
+
log(` feishuConfigInvalid : ${feishuConfigInvalid}`);
|
|
11383
|
+
log(` channels working before: ${beforeChannels.anyAccountWorking}`);
|
|
11384
|
+
if (!(versionIncompatible || feishuConfigInvalid)) {
|
|
11385
|
+
const reason = "version compatible and feishu channel config valid — upgrade not needed";
|
|
11386
|
+
log(` SKIP: ${reason}`);
|
|
11387
|
+
log(`${"=".repeat(60)}`);
|
|
11388
|
+
log("upgrade-lark skipped (pre-check gate)");
|
|
11389
|
+
log(`${"=".repeat(60)}`);
|
|
11390
|
+
return {
|
|
11391
|
+
ok: true,
|
|
11392
|
+
skipped: true,
|
|
11393
|
+
skipReason: reason,
|
|
11394
|
+
upgradeNeeded: false,
|
|
11395
|
+
timing,
|
|
11396
|
+
logFile
|
|
11397
|
+
};
|
|
11398
|
+
}
|
|
11399
|
+
if (beforeChannels.anyAccountWorking) {
|
|
11400
|
+
const reason = "channels are working — upgrade not needed (issue detected but system is functional)";
|
|
11401
|
+
log(` SKIP: ${reason}`);
|
|
11402
|
+
log(`${"=".repeat(60)}`);
|
|
11403
|
+
log("upgrade-lark skipped (pre-check gate)");
|
|
11404
|
+
log(`${"=".repeat(60)}`);
|
|
11405
|
+
return {
|
|
11406
|
+
ok: true,
|
|
11407
|
+
skipped: true,
|
|
11408
|
+
skipReason: reason,
|
|
11409
|
+
upgradeNeeded: false,
|
|
11410
|
+
timing,
|
|
11411
|
+
logFile
|
|
11412
|
+
};
|
|
11413
|
+
}
|
|
11414
|
+
log(` PROCEED: requiresLarkUpgrade=true (version=${versionIncompatible}, feishuConfig=${feishuConfigInvalid}) AND channels not working → running upgrade`);
|
|
11415
|
+
if (opts.checkOnly) {
|
|
11416
|
+
log(` check-only: upgrade IS needed — returning without installing`);
|
|
11417
|
+
log(`${"=".repeat(60)}`);
|
|
11418
|
+
log("upgrade-lark check-only complete");
|
|
11419
|
+
log(`${"=".repeat(60)}`);
|
|
11420
|
+
return {
|
|
11421
|
+
ok: true,
|
|
11422
|
+
skipped: true,
|
|
11423
|
+
skipReason: "check-only",
|
|
11424
|
+
upgradeNeeded: true,
|
|
11425
|
+
timing,
|
|
11426
|
+
logFile
|
|
11427
|
+
};
|
|
11428
|
+
}
|
|
11429
|
+
log("");
|
|
11430
|
+
log("── [1/6] 文件备份 ────────────────────────────────────────");
|
|
11431
|
+
log(`before-state: botCount=${countFeishuBots(configPath)}`);
|
|
11432
|
+
const t_backupStart = Date.now();
|
|
11433
|
+
const backup = backupFiles(fsOpts);
|
|
11434
|
+
timing.backupMs = Date.now() - t_backupStart;
|
|
11435
|
+
if (!backup.ok) {
|
|
11436
|
+
log(`ERROR: ${backup.error}`);
|
|
11437
|
+
return {
|
|
11438
|
+
ok: false,
|
|
11439
|
+
error: backup.error,
|
|
11440
|
+
timing,
|
|
11441
|
+
logFile
|
|
11442
|
+
};
|
|
11443
|
+
}
|
|
11444
|
+
log("backup: ok");
|
|
11445
|
+
logVersionSnapshot("before-versions", snapshotVersions(cwd, log), log);
|
|
11446
|
+
log("");
|
|
11447
|
+
log("── [2/6] 清理本地 openclaw shim ─────────────────────────");
|
|
11448
|
+
const localOpenclawBin = node_path.default.join(cwd, "node_modules", ".bin", "openclaw");
|
|
11449
|
+
if (node_fs.default.existsSync(localOpenclawBin)) try {
|
|
11450
|
+
node_fs.default.rmSync(localOpenclawBin);
|
|
11451
|
+
log(` removed: ${localOpenclawBin}`);
|
|
11452
|
+
} catch (e) {
|
|
11453
|
+
log(` WARN: failed to remove ${localOpenclawBin}: ${e.message}`);
|
|
11454
|
+
}
|
|
11455
|
+
else log(` skipped: ${localOpenclawBin} (not found)`);
|
|
11456
|
+
log("");
|
|
11457
|
+
log("── [3/6] npx install (@larksuite/openclaw-lark-tools update) ──");
|
|
11458
|
+
const t_npxStart = Date.now();
|
|
11459
|
+
const npxResult = (0, node_child_process.spawnSync)("npx", [
|
|
11460
|
+
"-y",
|
|
11461
|
+
"@larksuite/openclaw-lark-tools",
|
|
11462
|
+
"update"
|
|
11463
|
+
], {
|
|
11464
|
+
cwd,
|
|
11465
|
+
encoding: "utf-8",
|
|
11466
|
+
stdio: [
|
|
11467
|
+
"ignore",
|
|
11468
|
+
"pipe",
|
|
11469
|
+
"pipe"
|
|
11470
|
+
],
|
|
11471
|
+
timeout: 12e4
|
|
11472
|
+
});
|
|
11473
|
+
timing.npxInstallMs = Date.now() - t_npxStart;
|
|
11474
|
+
const npxStdout = npxResult.stdout?.trim() ?? "";
|
|
11475
|
+
const npxStderr = npxResult.stderr?.trim() ?? "";
|
|
11476
|
+
const npxExitCode = npxResult.status ?? 1;
|
|
11477
|
+
if (npxStdout) log(`npx stdout:\n${npxStdout}`);
|
|
11478
|
+
if (npxStderr) log(`npx stderr:\n${npxStderr}`);
|
|
11479
|
+
log(`npx exit: ${npxExitCode}${npxResult.error ? ` error: ${npxResult.error.message}` : ""}`);
|
|
11480
|
+
if (statusCheckDelayMs > 0) {
|
|
11481
|
+
log("");
|
|
11482
|
+
log(`── 等待 ${statusCheckDelayMs / 1e3}s(让 openclaw 服务完成重启) ─────────────`);
|
|
11483
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, statusCheckDelayMs);
|
|
11484
|
+
log("wait done");
|
|
11485
|
+
}
|
|
11486
|
+
const doRollback = (reason) => {
|
|
11487
|
+
log(`ERROR: ${reason}`);
|
|
11488
|
+
const rollbackOk = restoreFiles(fsOpts);
|
|
11489
|
+
log(`rollback: ${rollbackOk ? "ok" : "FAILED"}`);
|
|
11490
|
+
return {
|
|
11491
|
+
ok: false,
|
|
11492
|
+
error: reason,
|
|
11493
|
+
validationError: reason,
|
|
11494
|
+
stdout: npxStdout,
|
|
11495
|
+
stderr: npxStderr,
|
|
11496
|
+
exitCode: npxExitCode,
|
|
11497
|
+
rollbackOk,
|
|
11498
|
+
timing,
|
|
11499
|
+
logFile
|
|
11500
|
+
};
|
|
11501
|
+
};
|
|
11502
|
+
log("");
|
|
11503
|
+
log("── [4/5] 安装后诊断校验 ─────────────────────────────────");
|
|
11504
|
+
logVersionSnapshot("after-versions", snapshotVersions(cwd, log), log);
|
|
11505
|
+
let afterVersionIncompatible = false;
|
|
11506
|
+
try {
|
|
11507
|
+
const rawConfig = node_fs.default.readFileSync(configPath, "utf-8");
|
|
11508
|
+
afterVersionIncompatible = needsLarkUpgrade({
|
|
11509
|
+
config: loadJSON5().parse(rawConfig),
|
|
11510
|
+
configPath,
|
|
11511
|
+
vars: {},
|
|
11512
|
+
providerDeps: {
|
|
11513
|
+
usesMiaodaProvider: false,
|
|
11514
|
+
usesMiaodaSecretProvider: false
|
|
11515
|
+
}
|
|
11516
|
+
});
|
|
11517
|
+
log(` version-compat post-check: ${afterVersionIncompatible ? "STILL_INCOMPATIBLE" : "ok"}`);
|
|
11518
|
+
} catch (e) {
|
|
11519
|
+
log(` version-compat post-check error: ${e.message} — version signal unavailable`);
|
|
11520
|
+
}
|
|
11521
|
+
const t_postProbeStart = Date.now();
|
|
11522
|
+
const afterChannels = probeChannels("after", log, 6e4);
|
|
11523
|
+
timing.postProbeMs = Date.now() - t_postProbeStart;
|
|
11524
|
+
log(` feishu config invalid after: ${afterChannels.feishuConfigInvalid}`);
|
|
11525
|
+
const stillNeedsUpgrade = (afterVersionIncompatible || afterChannels.feishuConfigInvalid) && !afterChannels.anyAccountWorking;
|
|
11526
|
+
log(` post-check: stillNeedsUpgrade=${stillNeedsUpgrade} (version=${afterVersionIncompatible}, feishuConfig=${afterChannels.feishuConfigInvalid}, channelsWorking=${afterChannels.anyAccountWorking})`);
|
|
11527
|
+
if (stillNeedsUpgrade) return doRollback(`post-install diagnosis still shows anomaly: versionIncompatible=${afterVersionIncompatible}, feishuConfigInvalid=${afterChannels.feishuConfigInvalid}, anyAccountWorking=${afterChannels.anyAccountWorking}`);
|
|
11528
|
+
log(" post-install diagnosis: ok (upgrade conditions resolved)");
|
|
11529
|
+
log("");
|
|
11530
|
+
log("── [6/6] doctor --fix ────────────────────────────────────");
|
|
11531
|
+
const fixArgs = ["doctor", "--fix"];
|
|
11532
|
+
if (opts.scene) fixArgs.push(`--scene=${opts.scene}`);
|
|
11533
|
+
const t_doctorFixStart = Date.now();
|
|
11534
|
+
const fixResult = (0, node_child_process.spawnSync)(process.execPath, [cliScript, ...fixArgs], {
|
|
11535
|
+
cwd,
|
|
11536
|
+
encoding: "utf-8",
|
|
11537
|
+
stdio: [
|
|
11538
|
+
"ignore",
|
|
11539
|
+
"pipe",
|
|
11540
|
+
"pipe"
|
|
11541
|
+
],
|
|
11542
|
+
timeout: 6e4,
|
|
11543
|
+
env: process.env
|
|
11544
|
+
});
|
|
11545
|
+
timing.doctorFixMs = Date.now() - t_doctorFixStart;
|
|
11546
|
+
if (fixResult.stdout?.trim()) log(`doctor(fix) stdout:\n${fixResult.stdout.trim()}`);
|
|
11547
|
+
if (fixResult.stderr?.trim()) log(`doctor(fix) stderr:\n${fixResult.stderr.trim()}`);
|
|
11548
|
+
log(`doctor(fix) exit: ${fixResult.status ?? "null"}${fixResult.error ? ` error: ${fixResult.error.message}` : ""}`);
|
|
11549
|
+
log("");
|
|
11550
|
+
log(`${"=".repeat(60)}`);
|
|
11551
|
+
log("upgrade-lark completed successfully");
|
|
11552
|
+
log(`${"=".repeat(60)}`);
|
|
11553
|
+
return {
|
|
11554
|
+
ok: true,
|
|
11555
|
+
stdout: npxStdout,
|
|
11556
|
+
stderr: npxStderr,
|
|
11557
|
+
exitCode: npxExitCode,
|
|
11558
|
+
timing,
|
|
11559
|
+
logFile
|
|
11560
|
+
};
|
|
11561
|
+
}
|
|
10915
11562
|
//#endregion
|
|
10916
11563
|
//#region src/index.ts
|
|
10917
11564
|
const args = node_process.default.argv.slice(2);
|
|
@@ -11402,6 +12049,53 @@ async function main() {
|
|
|
11402
12049
|
if (!result.ok) node_process.default.exit(1);
|
|
11403
12050
|
break;
|
|
11404
12051
|
}
|
|
12052
|
+
case "upgrade-lark": {
|
|
12053
|
+
const checkOnly = args.includes("--check-only");
|
|
12054
|
+
const result = runUpgradeLark({
|
|
12055
|
+
runId: rc.runId,
|
|
12056
|
+
scene,
|
|
12057
|
+
checkOnly
|
|
12058
|
+
});
|
|
12059
|
+
const upgradeDurationMs = Date.now() - t0;
|
|
12060
|
+
console.log(JSON.stringify(result));
|
|
12061
|
+
reportUpgradeLarkToSlardar({
|
|
12062
|
+
scene,
|
|
12063
|
+
durationMs: upgradeDurationMs,
|
|
12064
|
+
success: result.ok,
|
|
12065
|
+
logFile: result.logFile,
|
|
12066
|
+
exitCode: result.exitCode,
|
|
12067
|
+
rollbackOk: result.rollbackOk,
|
|
12068
|
+
validationError: result.validationError,
|
|
12069
|
+
error: result.error,
|
|
12070
|
+
timing: result.timing
|
|
12071
|
+
});
|
|
12072
|
+
try {
|
|
12073
|
+
await reportCliRun({
|
|
12074
|
+
command: "upgrade-lark",
|
|
12075
|
+
runId: rc.runId,
|
|
12076
|
+
version: getVersion(),
|
|
12077
|
+
invocation: args.join(" "),
|
|
12078
|
+
durationMs: upgradeDurationMs,
|
|
12079
|
+
caller: rc.caller,
|
|
12080
|
+
traceId: rc.traceId,
|
|
12081
|
+
success: result.ok,
|
|
12082
|
+
result,
|
|
12083
|
+
error: result.ok ? void 0 : { message: result.error ?? "upgrade-lark failed" }
|
|
12084
|
+
});
|
|
12085
|
+
} catch (e) {
|
|
12086
|
+
console.error(`[telemetry] reportCliRun failed: ${e.message}`);
|
|
12087
|
+
}
|
|
12088
|
+
if (!result.ok || checkOnly && result.upgradeNeeded) {
|
|
12089
|
+
node_process.default.exitCode = 1;
|
|
12090
|
+
return;
|
|
12091
|
+
}
|
|
12092
|
+
break;
|
|
12093
|
+
}
|
|
12094
|
+
case "channels-probe": {
|
|
12095
|
+
const result = runChannelsProbe(getFlag(args, "timeout") ? Number(getFlag(args, "timeout")) : void 0);
|
|
12096
|
+
console.log(JSON.stringify(result));
|
|
12097
|
+
break;
|
|
12098
|
+
}
|
|
11405
12099
|
default:
|
|
11406
12100
|
node_process.default.stderr.write(`Unknown command: ${mode}\n\n`);
|
|
11407
12101
|
node_process.default.stderr.write(formatTopLevelHelp(helpFlags.expert));
|