@lark-apaas/openclaw-scripts-diagnose-cli 0.1.14-alpha.1 → 0.1.14-alpha.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +300 -233
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -52,7 +52,7 @@ node_assert = __toESM(node_assert);
|
|
|
52
52
|
* it terse and parseable.
|
|
53
53
|
*/
|
|
54
54
|
function getVersion() {
|
|
55
|
-
return "0.1.14-alpha.
|
|
55
|
+
return "0.1.14-alpha.10";
|
|
56
56
|
}
|
|
57
57
|
//#endregion
|
|
58
58
|
//#region src/rule-engine/base.ts
|
|
@@ -3367,9 +3367,9 @@ let FeishuPluginOpenclawUpgradeRule = class FeishuPluginOpenclawUpgradeRule exte
|
|
|
3367
3367
|
validate(ctx) {
|
|
3368
3368
|
const cc = resolveCompatContext(ctx);
|
|
3369
3369
|
if (!cc) return { pass: true };
|
|
3370
|
-
if (!cc.recommendedOc) return { pass: true };
|
|
3371
3370
|
const { ocCur, recommendedOc, installed, isLegacy } = cc;
|
|
3372
3371
|
if (isForkPlugin(installed)) return validateForkPlugin(installed, ocCur, recommendedOc);
|
|
3372
|
+
if (!recommendedOc) return { pass: true };
|
|
3373
3373
|
if (resolveUpgradeDirection(installed, ocCur, recommendedOc, isLegacy) !== "openclaw") return { pass: true };
|
|
3374
3374
|
return {
|
|
3375
3375
|
pass: false,
|
|
@@ -3397,19 +3397,17 @@ let FeishuPluginLarkUpgradeRule = class FeishuPluginLarkUpgradeRule extends Diag
|
|
|
3397
3397
|
if (!cc) return { pass: true };
|
|
3398
3398
|
const { ocCur, recommendedOc, installed, isLegacy } = cc;
|
|
3399
3399
|
if (isForkPlugin(installed)) return { pass: true };
|
|
3400
|
-
if (!
|
|
3401
|
-
|
|
3402
|
-
|
|
3403
|
-
|
|
3404
|
-
|
|
3405
|
-
}
|
|
3406
|
-
|
|
3407
|
-
}
|
|
3408
|
-
if (resolveUpgradeDirection(installed, ocCur, recommendedOc, isLegacy) !== "lark") return { pass: true };
|
|
3400
|
+
if (!isLarkUpgradeNeededFromCC(cc)) return { pass: true };
|
|
3401
|
+
const prefix = buildCompatPrefix(installed, ocCur, isLegacy);
|
|
3402
|
+
if (!recommendedOc) return {
|
|
3403
|
+
pass: false,
|
|
3404
|
+
action: "upgrade_lark",
|
|
3405
|
+
message: `${prefix};建议升级飞书插件至兼容版本`
|
|
3406
|
+
};
|
|
3409
3407
|
return {
|
|
3410
3408
|
pass: false,
|
|
3411
3409
|
action: "upgrade_lark",
|
|
3412
|
-
message: `${
|
|
3410
|
+
message: `${prefix};当前 openclaw@${ocCur} 已达推荐版本,可直接升级飞书插件`
|
|
3413
3411
|
};
|
|
3414
3412
|
}
|
|
3415
3413
|
};
|
|
@@ -3421,6 +3419,18 @@ FeishuPluginLarkUpgradeRule = __decorate([Rule({
|
|
|
3421
3419
|
level: "critical",
|
|
3422
3420
|
usesVars: ["recommendedOpenclawTag"]
|
|
3423
3421
|
})], FeishuPluginLarkUpgradeRule);
|
|
3422
|
+
/**
|
|
3423
|
+
* Core predicate: returns true when the lark plugin needs upgrading for a
|
|
3424
|
+
* non-fork plugin, based on version compatibility with the current openclaw.
|
|
3425
|
+
*
|
|
3426
|
+
* Shared by FeishuPluginLarkUpgradeRule.validate and needsLarkUpgrade.
|
|
3427
|
+
* Callers must handle fork plugin cases before invoking this function.
|
|
3428
|
+
*/
|
|
3429
|
+
function isLarkUpgradeNeededFromCC(cc) {
|
|
3430
|
+
const { ocCur, recommendedOc, installed, isLegacy } = cc;
|
|
3431
|
+
if (!recommendedOc) return isLegacy || !isVersionCompatible(installed, ocCur);
|
|
3432
|
+
return resolveUpgradeDirection(installed, ocCur, recommendedOc, isLegacy) === "lark";
|
|
3433
|
+
}
|
|
3424
3434
|
function isForkPlugin(p) {
|
|
3425
3435
|
return p.scope != null && FORK_SCOPES.includes(p.scope);
|
|
3426
3436
|
}
|
|
@@ -3459,14 +3469,16 @@ function describeCompatConstraint(entry, pluginVersion) {
|
|
|
3459
3469
|
/**
|
|
3460
3470
|
* @lark-apaas/openclaw-lark 豁免 VERSION_COMPAT_MAP,但仍要求 openclaw ≥ FORK_LARK_PLUGIN_MIN_OC_VERSION。
|
|
3461
3471
|
* 其他 @lark-apaas scope 的 fork 插件继续无条件 pass。
|
|
3472
|
+
* recommendedOc 可为 undefined(doctor 模式),此时只检测最低版本要求,不指定目标升级版本。
|
|
3462
3473
|
*/
|
|
3463
3474
|
function validateForkPlugin(installed, ocCur, recommendedOc) {
|
|
3464
3475
|
if (installed.fullName !== FORK_LARK_PLUGIN_FULL_NAME) return { pass: true };
|
|
3465
3476
|
if (compareCalVer(ocCur, FORK_LARK_PLUGIN_MIN_OC_VERSION) >= 0) return { pass: true };
|
|
3477
|
+
const recommendation = recommendedOc ? `;将 openclaw 升级到 ${recommendedOc} 即可满足` : `;请升级 openclaw 至 ${FORK_LARK_PLUGIN_MIN_OC_VERSION} 或更高版本`;
|
|
3466
3478
|
return {
|
|
3467
3479
|
pass: false,
|
|
3468
3480
|
action: "upgrade_openclaw",
|
|
3469
|
-
message: `飞书插件 ${describePlugin(installed)}(fork 版)要求 openclaw ≥ ${FORK_LARK_PLUGIN_MIN_OC_VERSION},当前 openclaw@${ocCur}
|
|
3481
|
+
message: `飞书插件 ${describePlugin(installed)}(fork 版)要求 openclaw ≥ ${FORK_LARK_PLUGIN_MIN_OC_VERSION},当前 openclaw@${ocCur} 低于此要求${recommendation}`
|
|
3470
3482
|
};
|
|
3471
3483
|
}
|
|
3472
3484
|
function describePlugin(p) {
|
|
@@ -3513,6 +3525,187 @@ function extractScopedNameFromSpec$1(spec) {
|
|
|
3513
3525
|
const at = spec.indexOf("@", 1);
|
|
3514
3526
|
return at === -1 ? spec : spec.slice(0, at);
|
|
3515
3527
|
}
|
|
3528
|
+
/**
|
|
3529
|
+
* Returns true if the installed feishu plugin is version-incompatible with
|
|
3530
|
+
* the current openclaw (or is a legacy plugin that must be replaced).
|
|
3531
|
+
* Used by the upgrade_lark_needed rule and the upgrade-lark pre-check gate.
|
|
3532
|
+
*/
|
|
3533
|
+
function needsLarkUpgrade(ctx) {
|
|
3534
|
+
const cc = resolveCompatContext(ctx);
|
|
3535
|
+
if (!cc) return false;
|
|
3536
|
+
const { ocCur, recommendedOc, installed } = cc;
|
|
3537
|
+
if (isForkPlugin(installed)) {
|
|
3538
|
+
if (recommendedOc) return false;
|
|
3539
|
+
if (installed.fullName === FORK_LARK_PLUGIN_FULL_NAME) return compareCalVer(ocCur, FORK_LARK_PLUGIN_MIN_OC_VERSION) < 0;
|
|
3540
|
+
return false;
|
|
3541
|
+
}
|
|
3542
|
+
return isLarkUpgradeNeededFromCC(cc);
|
|
3543
|
+
}
|
|
3544
|
+
//#endregion
|
|
3545
|
+
//#region src/channels-probe.ts
|
|
3546
|
+
const FEISHU_INVALID_CONFIG_MSG = "channels.feishu: invalid config: must NOT have additional properties";
|
|
3547
|
+
const CHANNEL_LINE_RE = /^-\s+Feishu\s+([^:]+):\s+(.+)$/;
|
|
3548
|
+
/**
|
|
3549
|
+
* Port of Python `_account_is_working` from the feishu-channel-success-rate skill.
|
|
3550
|
+
*
|
|
3551
|
+
* Strips colon-prefixed key:value bits (dm:, bot:, in:, out:, token:, allow:,
|
|
3552
|
+
* intents:, groups:, health:) and evaluates the canonical health formula.
|
|
3553
|
+
*/
|
|
3554
|
+
function accountIsWorking(bits) {
|
|
3555
|
+
const bitTokens = /* @__PURE__ */ new Set();
|
|
3556
|
+
let hasError = false;
|
|
3557
|
+
let hasProbeFailed = false;
|
|
3558
|
+
for (const raw of bits) {
|
|
3559
|
+
const b = raw.trim();
|
|
3560
|
+
if (!b) continue;
|
|
3561
|
+
if (b.startsWith("error:")) {
|
|
3562
|
+
hasError = true;
|
|
3563
|
+
continue;
|
|
3564
|
+
}
|
|
3565
|
+
if (b === "probe failed") {
|
|
3566
|
+
hasProbeFailed = true;
|
|
3567
|
+
continue;
|
|
3568
|
+
}
|
|
3569
|
+
bitTokens.add(b.split(":")[0]);
|
|
3570
|
+
}
|
|
3571
|
+
if (!bitTokens.has("enabled") || !bitTokens.has("configured")) return false;
|
|
3572
|
+
if (bitTokens.has("works")) return true;
|
|
3573
|
+
if (bitTokens.has("running") && !hasError && !hasProbeFailed) return true;
|
|
3574
|
+
return false;
|
|
3575
|
+
}
|
|
3576
|
+
/**
|
|
3577
|
+
* Parse the raw stdout of `openclaw channels status --probe`.
|
|
3578
|
+
* Port of Python `extract_channels_probe` from the feishu-channel-success-rate skill.
|
|
3579
|
+
*/
|
|
3580
|
+
function parseChannelsProbeOutput(text) {
|
|
3581
|
+
const gatewayReachable = text.includes("Gateway reachable");
|
|
3582
|
+
const feishuConfigInvalid = text.includes(FEISHU_INVALID_CONFIG_MSG);
|
|
3583
|
+
const accounts = [];
|
|
3584
|
+
let anyAccountWorking = false;
|
|
3585
|
+
for (const line of text.split("\n")) {
|
|
3586
|
+
const m = CHANNEL_LINE_RE.exec(line.trim());
|
|
3587
|
+
if (!m) continue;
|
|
3588
|
+
const [, acct, rest] = m;
|
|
3589
|
+
const bits = rest.split(",").map((b) => b.trim());
|
|
3590
|
+
const isWorking = accountIsWorking(bits);
|
|
3591
|
+
if (isWorking) anyAccountWorking = true;
|
|
3592
|
+
accounts.push({
|
|
3593
|
+
id: acct.trim(),
|
|
3594
|
+
bits,
|
|
3595
|
+
isWorking,
|
|
3596
|
+
raw: line.trim()
|
|
3597
|
+
});
|
|
3598
|
+
}
|
|
3599
|
+
return {
|
|
3600
|
+
gatewayReachable,
|
|
3601
|
+
feishuConfigInvalid,
|
|
3602
|
+
accounts,
|
|
3603
|
+
anyAccountWorking
|
|
3604
|
+
};
|
|
3605
|
+
}
|
|
3606
|
+
/**
|
|
3607
|
+
* Run `openclaw channels status --probe` and return a structured result.
|
|
3608
|
+
*
|
|
3609
|
+
* The command may exit non-zero when some bot accounts fail their probe — that
|
|
3610
|
+
* is still useful output. We therefore try to parse stdout even when the
|
|
3611
|
+
* process exits with a non-zero code, falling back to an unavailable result
|
|
3612
|
+
* only when there is genuinely no output to parse.
|
|
3613
|
+
*
|
|
3614
|
+
* @param timeoutMs Maximum wait time. Default is 60 s because v2026.4.x
|
|
3615
|
+
* lacks a per-request HTTP timeout and can block indefinitely.
|
|
3616
|
+
*/
|
|
3617
|
+
function runChannelsProbe(timeoutMs = 6e4) {
|
|
3618
|
+
let stdout = "";
|
|
3619
|
+
let stderrText = "";
|
|
3620
|
+
let execError;
|
|
3621
|
+
try {
|
|
3622
|
+
stdout = (0, node_child_process.execSync)("openclaw channels status --probe", {
|
|
3623
|
+
encoding: "utf-8",
|
|
3624
|
+
timeout: timeoutMs,
|
|
3625
|
+
stdio: [
|
|
3626
|
+
"ignore",
|
|
3627
|
+
"pipe",
|
|
3628
|
+
"pipe"
|
|
3629
|
+
]
|
|
3630
|
+
});
|
|
3631
|
+
} catch (e) {
|
|
3632
|
+
const err = e;
|
|
3633
|
+
const stdoutRaw = err.stdout;
|
|
3634
|
+
stdout = typeof stdoutRaw === "string" ? stdoutRaw : stdoutRaw?.toString("utf-8") ?? "";
|
|
3635
|
+
execError = err.message;
|
|
3636
|
+
const stderrRaw = err.stderr;
|
|
3637
|
+
stderrText = (typeof stderrRaw === "string" ? stderrRaw : stderrRaw?.toString("utf-8") ?? "").trim();
|
|
3638
|
+
if (stderrText) console.error(`channels-probe: stderr from CLI: ${stderrText}`);
|
|
3639
|
+
}
|
|
3640
|
+
if (stdout.trim()) return {
|
|
3641
|
+
available: true,
|
|
3642
|
+
...parseChannelsProbeOutput(stdout)
|
|
3643
|
+
};
|
|
3644
|
+
return {
|
|
3645
|
+
available: false,
|
|
3646
|
+
gatewayReachable: false,
|
|
3647
|
+
feishuConfigInvalid: stderrText.includes(FEISHU_INVALID_CONFIG_MSG),
|
|
3648
|
+
accounts: [],
|
|
3649
|
+
anyAccountWorking: false,
|
|
3650
|
+
error: execError ?? "no output from openclaw channels status --probe"
|
|
3651
|
+
};
|
|
3652
|
+
}
|
|
3653
|
+
//#endregion
|
|
3654
|
+
//#region src/rules/upgrade-lark-needed.ts
|
|
3655
|
+
/**
|
|
3656
|
+
* Detects the condition that warrants running `upgrade-lark`:
|
|
3657
|
+
* - feishu plugin version incompatible with current openclaw, OR
|
|
3658
|
+
* - openclaw channels status --probe reports feishu channel config invalid; AND
|
|
3659
|
+
* - channels are not working.
|
|
3660
|
+
*
|
|
3661
|
+
* Both conditions must be true simultaneously. If version is compatible and
|
|
3662
|
+
* feishu config is valid, or channels are working, the rule passes (no action needed).
|
|
3663
|
+
*
|
|
3664
|
+
* feishuConfigInvalid is read from the channels probe output rather than running a
|
|
3665
|
+
* separate `openclaw status` call, since only `openclaw channels status --probe`
|
|
3666
|
+
* reliably surfaces the schema validation error.
|
|
3667
|
+
*
|
|
3668
|
+
* profile: experimental — runs only in full sweep mode, not in standard doctor.
|
|
3669
|
+
* level: silent — telemetry/sweep-only, does not trigger page-level repair UI.
|
|
3670
|
+
*/
|
|
3671
|
+
let UpgradeLarkNeededRule = class UpgradeLarkNeededRule extends DiagnoseRule {
|
|
3672
|
+
validate(ctx) {
|
|
3673
|
+
let versionIncompatible = false;
|
|
3674
|
+
try {
|
|
3675
|
+
versionIncompatible = needsLarkUpgrade(ctx);
|
|
3676
|
+
} catch {
|
|
3677
|
+
versionIncompatible = true;
|
|
3678
|
+
}
|
|
3679
|
+
let probeResult;
|
|
3680
|
+
try {
|
|
3681
|
+
probeResult = runChannelsProbe(6e4);
|
|
3682
|
+
} catch {
|
|
3683
|
+
probeResult = {
|
|
3684
|
+
available: false,
|
|
3685
|
+
gatewayReachable: false,
|
|
3686
|
+
feishuConfigInvalid: false,
|
|
3687
|
+
accounts: [],
|
|
3688
|
+
anyAccountWorking: false
|
|
3689
|
+
};
|
|
3690
|
+
}
|
|
3691
|
+
const feishuConfigInvalid = probeResult.feishuConfigInvalid;
|
|
3692
|
+
if (!(versionIncompatible || feishuConfigInvalid)) return { pass: true };
|
|
3693
|
+
if (probeResult.anyAccountWorking) return { pass: true };
|
|
3694
|
+
return {
|
|
3695
|
+
pass: false,
|
|
3696
|
+
action: "upgrade_lark",
|
|
3697
|
+
message: `飞书插件需要升级且 channels 不可用(版本不兼容=${versionIncompatible}, feishu配置无效=${feishuConfigInvalid}),建议执行 upgrade-lark 命令升级飞书插件`
|
|
3698
|
+
};
|
|
3699
|
+
}
|
|
3700
|
+
};
|
|
3701
|
+
UpgradeLarkNeededRule = __decorate([Rule({
|
|
3702
|
+
key: "upgrade_lark_needed",
|
|
3703
|
+
description: "检测飞书插件版本不兼容且 channels 不可用,判断是否需要执行 upgrade-lark 升级",
|
|
3704
|
+
repairMode: "check-only",
|
|
3705
|
+
level: "silent",
|
|
3706
|
+
profile: "experimental",
|
|
3707
|
+
usesVars: ["recommendedOpenclawTag"]
|
|
3708
|
+
})], UpgradeLarkNeededRule);
|
|
3516
3709
|
//#endregion
|
|
3517
3710
|
//#region src/rules/cleanup-install-backup-dirs.ts
|
|
3518
3711
|
const DIR_PREFIX = ".openclaw-install-";
|
|
@@ -6236,23 +6429,6 @@ function reportError(params) {
|
|
|
6236
6429
|
} catch {}
|
|
6237
6430
|
}
|
|
6238
6431
|
//#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
|
|
6256
6432
|
//#region src/install-cli.ts
|
|
6257
6433
|
const LARK_CLI_NAME = "lark-cli";
|
|
6258
6434
|
const AGENT_SKILLS_NAME = "agent-skills";
|
|
@@ -10149,109 +10325,6 @@ function finalize(results, aborted) {
|
|
|
10149
10325
|
};
|
|
10150
10326
|
}
|
|
10151
10327
|
//#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
|
|
10255
10328
|
//#region src/innerapi/reportCliRun.ts
|
|
10256
10329
|
/**
|
|
10257
10330
|
* CLI-side client for studio_server's `openclaw.report_cli_run` inner
|
|
@@ -10331,7 +10404,7 @@ async function reportCliRun(opts) {
|
|
|
10331
10404
|
//#region src/help.ts
|
|
10332
10405
|
const BIN = "mclaw-diagnose";
|
|
10333
10406
|
function versionBanner() {
|
|
10334
|
-
return `v0.1.14-alpha.
|
|
10407
|
+
return `v0.1.14-alpha.10`;
|
|
10335
10408
|
}
|
|
10336
10409
|
const COMMANDS = [
|
|
10337
10410
|
{
|
|
@@ -10912,18 +10985,16 @@ function reportDoctorRunToSlardar(opts) {
|
|
|
10912
10985
|
}
|
|
10913
10986
|
});
|
|
10914
10987
|
}
|
|
10915
|
-
|
|
10916
|
-
function readFileTail(filePath, maxBytes = 4e3) {
|
|
10988
|
+
function readLogFile(filePath) {
|
|
10917
10989
|
try {
|
|
10918
|
-
|
|
10919
|
-
if (content.length <= maxBytes) return content;
|
|
10920
|
-
return `...(truncated — showing last ${maxBytes} chars)...\n` + content.slice(-maxBytes);
|
|
10990
|
+
return node_fs.default.readFileSync(filePath, "utf-8");
|
|
10921
10991
|
} catch {
|
|
10922
10992
|
return "";
|
|
10923
10993
|
}
|
|
10924
10994
|
}
|
|
10925
10995
|
function reportUpgradeLarkToSlardar(opts) {
|
|
10926
|
-
console.error(`[slardar]
|
|
10996
|
+
console.error(`[slardar] upgrade_lark_run scene=${opts.scene ?? ""} success=${opts.success} exitCode=${opts.exitCode ?? ""} rollbackOk=${opts.rollbackOk ?? ""}`);
|
|
10997
|
+
const logContent = readLogFile(opts.logFile);
|
|
10927
10998
|
reportTask({
|
|
10928
10999
|
eventName: "upgrade_lark_run",
|
|
10929
11000
|
durationMs: opts.durationMs,
|
|
@@ -10932,22 +11003,9 @@ function reportUpgradeLarkToSlardar(opts) {
|
|
|
10932
11003
|
scene: opts.scene ?? "",
|
|
10933
11004
|
exit_code: String(opts.exitCode ?? ""),
|
|
10934
11005
|
rollback_ok: opts.rollbackOk != null ? String(opts.rollbackOk) : "",
|
|
10935
|
-
validation_error:
|
|
10936
|
-
error_msg:
|
|
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) : ""
|
|
11006
|
+
validation_error: opts.validationError ?? "",
|
|
11007
|
+
error_msg: opts.error ?? "",
|
|
11008
|
+
log_content: logContent
|
|
10951
11009
|
}
|
|
10952
11010
|
});
|
|
10953
11011
|
}
|
|
@@ -10955,8 +11013,6 @@ function reportUpgradeLarkToSlardar(opts) {
|
|
|
10955
11013
|
//#region src/upgrade-lark.ts
|
|
10956
11014
|
/** Plugin directories under extensions/ that are backed up before upgrade */
|
|
10957
11015
|
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
11016
|
function backupFiles(opts) {
|
|
10961
11017
|
const { workspaceDir, configPath, backupDir, log } = opts;
|
|
10962
11018
|
try {
|
|
@@ -11058,28 +11114,6 @@ function countFeishuBots(configPath) {
|
|
|
11058
11114
|
return 0;
|
|
11059
11115
|
}
|
|
11060
11116
|
}
|
|
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
11117
|
/** Run channels probe, log results, and return the result. Never throws. */
|
|
11084
11118
|
function probeChannels(label, log, timeoutMs) {
|
|
11085
11119
|
try {
|
|
@@ -11093,6 +11127,8 @@ function probeChannels(label, log, timeoutMs) {
|
|
|
11093
11127
|
log(` ${label} channels probe threw: ${e.message}`);
|
|
11094
11128
|
return {
|
|
11095
11129
|
available: false,
|
|
11130
|
+
gatewayReachable: false,
|
|
11131
|
+
feishuConfigInvalid: false,
|
|
11096
11132
|
accounts: [],
|
|
11097
11133
|
anyAccountWorking: false
|
|
11098
11134
|
};
|
|
@@ -11117,10 +11153,64 @@ function runUpgradeLark(opts) {
|
|
|
11117
11153
|
log(` configPath : ${configPath}`);
|
|
11118
11154
|
log(`${"=".repeat(60)}`);
|
|
11119
11155
|
log("");
|
|
11120
|
-
log("── [
|
|
11121
|
-
|
|
11156
|
+
log("── [Pre-check A] channels probe(升级前)────────────────");
|
|
11157
|
+
const beforeChannels = probeChannels("before", log, 6e4);
|
|
11122
11158
|
log("");
|
|
11123
|
-
log("── [
|
|
11159
|
+
log("── [Pre-check B] 版本兼容预检 ───────────────────────────");
|
|
11160
|
+
let versionIncompatible = false;
|
|
11161
|
+
try {
|
|
11162
|
+
const rawConfig = node_fs.default.readFileSync(configPath, "utf-8");
|
|
11163
|
+
versionIncompatible = needsLarkUpgrade({
|
|
11164
|
+
config: loadJSON5().parse(rawConfig),
|
|
11165
|
+
configPath,
|
|
11166
|
+
vars: {},
|
|
11167
|
+
providerDeps: {
|
|
11168
|
+
usesMiaodaProvider: false,
|
|
11169
|
+
usesMiaodaSecretProvider: false
|
|
11170
|
+
}
|
|
11171
|
+
});
|
|
11172
|
+
log(` version-compat pre-check: ${versionIncompatible ? "NEEDS_UPGRADE" : "ok"}`);
|
|
11173
|
+
} catch (e) {
|
|
11174
|
+
log(` version-compat pre-check error: ${e.message} — treating as needs-upgrade`);
|
|
11175
|
+
versionIncompatible = true;
|
|
11176
|
+
}
|
|
11177
|
+
const feishuConfigInvalid = beforeChannels.feishuConfigInvalid;
|
|
11178
|
+
log(` feishu config invalid : ${feishuConfigInvalid}`);
|
|
11179
|
+
log("");
|
|
11180
|
+
log("── [Gate] 升级前置条件检查 ───────────────────────────────");
|
|
11181
|
+
log(` versionIncompatible : ${versionIncompatible}`);
|
|
11182
|
+
log(` feishuConfigInvalid : ${feishuConfigInvalid}`);
|
|
11183
|
+
log(` channels working before: ${beforeChannels.anyAccountWorking}`);
|
|
11184
|
+
if (!(versionIncompatible || feishuConfigInvalid)) {
|
|
11185
|
+
const reason = "version compatible and feishu channel config valid — upgrade not needed";
|
|
11186
|
+
log(` SKIP: ${reason}`);
|
|
11187
|
+
log(`${"=".repeat(60)}`);
|
|
11188
|
+
log("upgrade-lark skipped (pre-check gate)");
|
|
11189
|
+
log(`${"=".repeat(60)}`);
|
|
11190
|
+
return {
|
|
11191
|
+
ok: true,
|
|
11192
|
+
skipped: true,
|
|
11193
|
+
skipReason: reason,
|
|
11194
|
+
logFile
|
|
11195
|
+
};
|
|
11196
|
+
}
|
|
11197
|
+
if (beforeChannels.anyAccountWorking) {
|
|
11198
|
+
const reason = "channels are working — upgrade not needed (issue detected but system is functional)";
|
|
11199
|
+
log(` SKIP: ${reason}`);
|
|
11200
|
+
log(`${"=".repeat(60)}`);
|
|
11201
|
+
log("upgrade-lark skipped (pre-check gate)");
|
|
11202
|
+
log(`${"=".repeat(60)}`);
|
|
11203
|
+
return {
|
|
11204
|
+
ok: true,
|
|
11205
|
+
skipped: true,
|
|
11206
|
+
skipReason: reason,
|
|
11207
|
+
logFile
|
|
11208
|
+
};
|
|
11209
|
+
}
|
|
11210
|
+
log(` PROCEED: requiresLarkUpgrade=true (version=${versionIncompatible}, feishuConfig=${feishuConfigInvalid}) AND channels not working → running upgrade`);
|
|
11211
|
+
log("");
|
|
11212
|
+
log("── [1/6] 文件备份 ────────────────────────────────────────");
|
|
11213
|
+
log(`before-state: botCount=${countFeishuBots(configPath)}`);
|
|
11124
11214
|
const backup = backupFiles(fsOpts);
|
|
11125
11215
|
if (!backup.ok) {
|
|
11126
11216
|
log(`ERROR: ${backup.error}`);
|
|
@@ -11133,10 +11223,7 @@ function runUpgradeLark(opts) {
|
|
|
11133
11223
|
log("backup: ok");
|
|
11134
11224
|
logVersionSnapshot("before-versions", snapshotVersions(cwd, log), log);
|
|
11135
11225
|
log("");
|
|
11136
|
-
log("── [
|
|
11137
|
-
const beforeChannels = probeChannels("before", log, 3e4);
|
|
11138
|
-
log("");
|
|
11139
|
-
log("── [4/8] 清理本地 openclaw shim ─────────────────────────");
|
|
11226
|
+
log("── [2/6] 清理本地 openclaw shim ─────────────────────────");
|
|
11140
11227
|
const localOpenclawBin = node_path.default.join(cwd, "node_modules", ".bin", "openclaw");
|
|
11141
11228
|
if (node_fs.default.existsSync(localOpenclawBin)) try {
|
|
11142
11229
|
node_fs.default.rmSync(localOpenclawBin);
|
|
@@ -11146,7 +11233,7 @@ function runUpgradeLark(opts) {
|
|
|
11146
11233
|
}
|
|
11147
11234
|
else log(` skipped: ${localOpenclawBin} (not found)`);
|
|
11148
11235
|
log("");
|
|
11149
|
-
log("── [
|
|
11236
|
+
log("── [3/6] npx install (@larksuite/openclaw-lark-tools update) ──");
|
|
11150
11237
|
const npxResult = (0, node_child_process.spawnSync)("npx", [
|
|
11151
11238
|
"-y",
|
|
11152
11239
|
"@larksuite/openclaw-lark-tools",
|
|
@@ -11189,53 +11276,33 @@ function runUpgradeLark(opts) {
|
|
|
11189
11276
|
};
|
|
11190
11277
|
};
|
|
11191
11278
|
log("");
|
|
11192
|
-
log("── [
|
|
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");
|
|
11279
|
+
log("── [4/5] 安装后诊断校验 ─────────────────────────────────");
|
|
11218
11280
|
logVersionSnapshot("after-versions", snapshotVersions(cwd, log), log);
|
|
11281
|
+
let afterVersionIncompatible = false;
|
|
11282
|
+
try {
|
|
11283
|
+
const rawConfig = node_fs.default.readFileSync(configPath, "utf-8");
|
|
11284
|
+
afterVersionIncompatible = needsLarkUpgrade({
|
|
11285
|
+
config: loadJSON5().parse(rawConfig),
|
|
11286
|
+
configPath,
|
|
11287
|
+
vars: {},
|
|
11288
|
+
providerDeps: {
|
|
11289
|
+
usesMiaodaProvider: false,
|
|
11290
|
+
usesMiaodaSecretProvider: false
|
|
11291
|
+
}
|
|
11292
|
+
});
|
|
11293
|
+
log(` version-compat post-check: ${afterVersionIncompatible ? "STILL_INCOMPATIBLE" : "ok"}`);
|
|
11294
|
+
} catch (e) {
|
|
11295
|
+
log(` version-compat post-check error: ${e.message} — treating as still-incompatible`);
|
|
11296
|
+
afterVersionIncompatible = true;
|
|
11297
|
+
}
|
|
11298
|
+
const afterChannels = probeChannels("after", log, 6e4);
|
|
11299
|
+
log(` feishu config invalid after: ${afterChannels.feishuConfigInvalid}`);
|
|
11300
|
+
const stillNeedsUpgrade = (afterVersionIncompatible || afterChannels.feishuConfigInvalid) && !afterChannels.anyAccountWorking;
|
|
11301
|
+
log(` post-check: stillNeedsUpgrade=${stillNeedsUpgrade} (version=${afterVersionIncompatible}, feishuConfig=${afterChannels.feishuConfigInvalid}, channelsWorking=${afterChannels.anyAccountWorking})`);
|
|
11302
|
+
if (stillNeedsUpgrade) return doRollback(`post-install diagnosis still shows anomaly: versionIncompatible=${afterVersionIncompatible}, feishuConfigInvalid=${afterChannels.feishuConfigInvalid}, anyAccountWorking=${afterChannels.anyAccountWorking}`);
|
|
11303
|
+
log(" post-install diagnosis: ok (upgrade conditions resolved)");
|
|
11219
11304
|
log("");
|
|
11220
|
-
log("── [
|
|
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 ────────────────────────────────────");
|
|
11305
|
+
log("── [6/6] doctor --fix ────────────────────────────────────");
|
|
11239
11306
|
const fixArgs = ["doctor", "--fix"];
|
|
11240
11307
|
if (opts.scene) fixArgs.push(`--scene=${opts.scene}`);
|
|
11241
11308
|
const fixResult = (0, node_child_process.spawnSync)(process.execPath, [cliScript, ...fixArgs], {
|
package/package.json
CHANGED