@lark-apaas/openclaw-scripts-diagnose-cli 0.1.14-alpha.12 → 0.1.14-alpha.13

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.
Files changed (2) hide show
  1. package/dist/index.cjs +381 -1012
  2. 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.12";
55
+ return "0.1.14-alpha.13";
56
56
  }
57
57
  //#endregion
58
58
  //#region src/rule-engine/base.ts
@@ -2520,6 +2520,337 @@ function upsertResourceConstrainedToolsBlock(content) {
2520
2520
  return `${content}${content.length > 0 && !content.endsWith("\n") ? "\n\n" : "\n"}${RESOURCE_CONSTRAINED_TOOLS_BLOCK}\n`;
2521
2521
  }
2522
2522
  //#endregion
2523
+ //#region src/paths.ts
2524
+ /**
2525
+ * Central directory for all ephemeral diagnose/reset artifacts: task status
2526
+ * files (`reset-<taskId>.json`) and human-readable step logs
2527
+ * (`reset-<taskId>.log`). Having everything under one dir makes debugging a
2528
+ * stuck reset much easier — `ls /tmp/openclaw-diagnose/` shows every recent
2529
+ * run, and each run's log is right next to its state.
2530
+ */
2531
+ const DIAGNOSE_DIR = "/tmp/openclaw-diagnose";
2532
+ function resetResultFile(taskId) {
2533
+ return `${DIAGNOSE_DIR}/reset-${taskId}.json`;
2534
+ }
2535
+ function resetLogFile(taskId) {
2536
+ return `${DIAGNOSE_DIR}/reset-${taskId}.log`;
2537
+ }
2538
+ /** Sandbox workspace root where openclaw config + agent state lives. */
2539
+ const WORKSPACE_DIR = "/home/gem/workspace/agent";
2540
+ /** File containing the provider key used by the openclaw miaoda provider. */
2541
+ const PROVIDER_FILE_PATH = "/home/gem/workspace/.force/openclaw/miaoda-provider-key";
2542
+ /** File containing the miaoda openclaw secrets JSON. */
2543
+ const SECRETS_FILE_PATH = "/home/gem/workspace/.force/openclaw/miaoda-openclaw-secrets.json";
2544
+ /** Absolute path to the openclaw config JSON. */
2545
+ const CONFIG_PATH = `${WORKSPACE_DIR}/openclaw.json`;
2546
+ //#endregion
2547
+ //#region src/lark-cli-init.ts
2548
+ const LARK_PLUGIN_NAMES$1 = ["openclaw-lark", "feishu-openclaw-plugin"];
2549
+ const PE_XML_TAG = "lark-cli-pe";
2550
+ const PE_PLACEHOLDER = `
2551
+ <${PE_XML_TAG}>
2552
+ **【强制要求 - 无例外】飞书所有操作必须先读 feishu-lark-cli skill。未读skill不得调用任何飞书工具,违者视为操作失误。**
2553
+ </${PE_XML_TAG}>
2554
+ `;
2555
+ function isLarkPluginInstalled(configPath) {
2556
+ const extDir = getExtensionsDir(configPath);
2557
+ return LARK_PLUGIN_NAMES$1.some((name) => {
2558
+ try {
2559
+ return node_fs.default.existsSync(node_path.default.join(extDir, name, "package.json"));
2560
+ } catch {
2561
+ return false;
2562
+ }
2563
+ });
2564
+ }
2565
+ function isLarkCliAvailable$2() {
2566
+ try {
2567
+ return (0, node_child_process.spawnSync)("lark-cli", ["--version"], {
2568
+ encoding: "utf-8",
2569
+ timeout: 5e3,
2570
+ stdio: [
2571
+ "ignore",
2572
+ "pipe",
2573
+ "ignore"
2574
+ ]
2575
+ }).status === 0;
2576
+ } catch {
2577
+ return false;
2578
+ }
2579
+ }
2580
+ function readConfig(configPath) {
2581
+ try {
2582
+ const raw = node_fs.default.readFileSync(configPath, "utf-8");
2583
+ const parsed = loadJSON5().parse(raw);
2584
+ return parsed != null && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
2585
+ } catch {
2586
+ return null;
2587
+ }
2588
+ }
2589
+ /**
2590
+ * Resolve the feishu app secret for the given appId.
2591
+ *
2592
+ * Lookup order:
2593
+ * 1. channels.feishu.appSecret (single-agent: feishu.appId === appId)
2594
+ * 2. channels.feishu.accounts[key].appSecret (multi-agent: account.appId === appId)
2595
+ *
2596
+ * Value interpretation:
2597
+ * - string → use directly
2598
+ * - object → secret is managed by a provider; use `feishuAppSecret` param instead
2599
+ *
2600
+ * Returns null when the secret cannot be determined.
2601
+ */
2602
+ function resolveAppSecret(appId, config, feishuAppSecret) {
2603
+ const feishu = getNestedMap(config, "channels", "feishu");
2604
+ if (!feishu) return null;
2605
+ let rawSecret;
2606
+ if (typeof feishu.appId === "string" && feishu.appId === appId) rawSecret = feishu.appSecret;
2607
+ else {
2608
+ const accounts = asRecord(feishu.accounts);
2609
+ if (accounts) for (const [, val] of Object.entries(accounts)) {
2610
+ const account = asRecord(val);
2611
+ if (account?.appId === appId) {
2612
+ rawSecret = account.appSecret ?? feishu.appSecret;
2613
+ break;
2614
+ }
2615
+ }
2616
+ }
2617
+ if (typeof rawSecret === "string" && rawSecret) return rawSecret;
2618
+ if (rawSecret != null && typeof rawSecret === "object") return feishuAppSecret ?? null;
2619
+ return null;
2620
+ }
2621
+ /**
2622
+ * Resolve the agents.md path for the given appId from the openclaw config.
2623
+ *
2624
+ * Case 1: appId matches channels.feishu.appId (single-agent path)
2625
+ * → WORKSPACE_DIR/AGENTS.md
2626
+ *
2627
+ * Case 2: appId found in channels.feishu.accounts (multi-agent path)
2628
+ * → find account key where account.appId === appId
2629
+ * → find binding where match.channel=feishu && match.accountId=that key
2630
+ * → if agentId === 'main' → WORKSPACE_DIR/agents.md
2631
+ * → else find agent in agents.list by id → agent.workspace/agents.md
2632
+ *
2633
+ * Returns null when the path cannot be determined.
2634
+ */
2635
+ function resolveAgentsMdPath(appId, config) {
2636
+ const feishu = getNestedMap(config, "channels", "feishu");
2637
+ if (!feishu) {
2638
+ console.error("resolveAgentsMdPath: channels.feishu not found");
2639
+ return null;
2640
+ }
2641
+ if (typeof feishu.appId === "string" && feishu.appId === appId) {
2642
+ console.error(`resolveAgentsMdPath: case=single-agent feishu.appId=${feishu.appId}`);
2643
+ return node_path.default.join(WORKSPACE_DIR, "workspace", "AGENTS.md");
2644
+ }
2645
+ const accounts = asRecord(feishu.accounts);
2646
+ if (!accounts) {
2647
+ console.error("resolveAgentsMdPath: feishu.accounts not found");
2648
+ return null;
2649
+ }
2650
+ let accountId;
2651
+ for (const [key, val] of Object.entries(accounts)) if (asRecord(val)?.appId === appId) {
2652
+ accountId = key;
2653
+ break;
2654
+ }
2655
+ if (!accountId) {
2656
+ console.error(`resolveAgentsMdPath: no account found with appId=${appId} in feishu.accounts keys=[${Object.keys(accounts).join(",")}]`);
2657
+ return null;
2658
+ }
2659
+ console.error(`resolveAgentsMdPath: found accountId=${accountId}`);
2660
+ const bindings = Array.isArray(config.bindings) ? config.bindings : [];
2661
+ let agentId;
2662
+ for (const b of bindings) {
2663
+ const binding = asRecord(b);
2664
+ if (!binding) continue;
2665
+ const match = asRecord(binding.match);
2666
+ if (match?.channel === "feishu" && match?.accountId === accountId) {
2667
+ if (typeof binding.agentId === "string") {
2668
+ agentId = binding.agentId;
2669
+ break;
2670
+ }
2671
+ }
2672
+ }
2673
+ if (!agentId) {
2674
+ console.error(`resolveAgentsMdPath: no binding found for accountId=${accountId} in bindings(count=${bindings.length})`);
2675
+ return null;
2676
+ }
2677
+ console.error(`resolveAgentsMdPath: found agentId=${agentId}`);
2678
+ if (agentId === "main") {
2679
+ console.error("resolveAgentsMdPath: case=multi-agent-main");
2680
+ return node_path.default.join(WORKSPACE_DIR, "workspace", "AGENTS.md");
2681
+ }
2682
+ const agentsObj = asRecord(config.agents);
2683
+ const list = Array.isArray(agentsObj?.list) ? agentsObj.list : [];
2684
+ for (const a of list) {
2685
+ const agent = asRecord(a);
2686
+ if (agent?.id === agentId) {
2687
+ const ws = typeof agent.workspace === "string" ? agent.workspace : node_path.default.join(WORKSPACE_DIR, "workspace");
2688
+ console.error(`resolveAgentsMdPath: case=multi-agent-custom agentId=${agentId} workspace=${ws}`);
2689
+ return node_path.default.join(ws, "AGENTS.md");
2690
+ }
2691
+ }
2692
+ console.error(`resolveAgentsMdPath: agentId=${agentId} not found in agents.list(count=${list.length})`);
2693
+ return null;
2694
+ }
2695
+ function appendPeToAgentsMd(agentsMdPath) {
2696
+ const dir = node_path.default.dirname(agentsMdPath);
2697
+ if (!node_fs.default.existsSync(dir)) node_fs.default.mkdirSync(dir, { recursive: true });
2698
+ const existing = node_fs.default.existsSync(agentsMdPath) ? node_fs.default.readFileSync(agentsMdPath, "utf-8") : "";
2699
+ if (existing.includes(`<lark-cli-pe>`)) {
2700
+ console.error(`lark-cli-init: <${PE_XML_TAG}> already present in ${agentsMdPath}, skipping`);
2701
+ return;
2702
+ }
2703
+ const sep = existing.length > 0 && !existing.endsWith("\n") ? "\n" : "";
2704
+ node_fs.default.appendFileSync(agentsMdPath, `${sep}${PE_PLACEHOLDER}`, "utf-8");
2705
+ console.error(`lark-cli-init: appended PE placeholder to ${agentsMdPath}`);
2706
+ }
2707
+ /**
2708
+ * Collect every Feishu bot appId declared in the openclaw config.
2709
+ * Covers both single-agent (channels.feishu.appId) and multi-agent
2710
+ * (channels.feishu.accounts[*].appId) layouts. Returns a deduplicated list.
2711
+ */
2712
+ function collectFeishuAppIds(configPath) {
2713
+ const config = readConfig(configPath ?? CONFIG_PATH);
2714
+ if (!config) return [];
2715
+ const feishu = getNestedMap(config, "channels", "feishu");
2716
+ if (!feishu) return [];
2717
+ const appIds = /* @__PURE__ */ new Set();
2718
+ const topAppId = feishu.appId;
2719
+ if (typeof topAppId === "string" && topAppId.trim()) appIds.add(topAppId.trim());
2720
+ const accounts = asRecord(feishu.accounts);
2721
+ if (accounts) for (const val of Object.values(accounts)) {
2722
+ const appId = asRecord(val)?.appId;
2723
+ if (typeof appId === "string" && appId.trim()) appIds.add(appId.trim());
2724
+ }
2725
+ return [...appIds];
2726
+ }
2727
+ function runLarkCliInit(opts) {
2728
+ const configPath = opts.configPath ?? CONFIG_PATH;
2729
+ if (!isLarkPluginInstalled(configPath)) {
2730
+ console.error("lark-cli-init: skipping — openclaw-lark plugin not installed");
2731
+ return {
2732
+ ok: true,
2733
+ skipped: true,
2734
+ skipReason: "openclaw-lark plugin not installed"
2735
+ };
2736
+ }
2737
+ if (!isLarkCliAvailable$2()) {
2738
+ console.error("lark-cli-init: skipping — lark-cli command not found");
2739
+ return {
2740
+ ok: true,
2741
+ skipped: true,
2742
+ skipReason: "lark-cli command not found"
2743
+ };
2744
+ }
2745
+ const config = readConfig(configPath);
2746
+ if (!config) return {
2747
+ ok: false,
2748
+ error: `could not read config at ${configPath}`
2749
+ };
2750
+ const agentsMdPath = resolveAgentsMdPath(opts.appId, config);
2751
+ console.error(`lark-cli-init: resolved agents.md path=${agentsMdPath ?? "(null)"}`);
2752
+ if (!agentsMdPath) return {
2753
+ ok: false,
2754
+ error: `could not resolve agents.md path for appId=${opts.appId}`
2755
+ };
2756
+ const appSecret = resolveAppSecret(opts.appId, config, opts.feishuAppSecret);
2757
+ if (!appSecret) return {
2758
+ ok: false,
2759
+ error: `could not resolve appSecret for appId=${opts.appId}`
2760
+ };
2761
+ console.error(`lark-cli-init: running lark-cli config init --name ${opts.appId} --app-id ${opts.appId} --brand feishu --app-secret-stdin --force-init`);
2762
+ const initRes = (0, node_child_process.spawnSync)("lark-cli", [
2763
+ "config",
2764
+ "init",
2765
+ "--name",
2766
+ opts.appId,
2767
+ "--app-id",
2768
+ opts.appId,
2769
+ "--brand",
2770
+ "feishu",
2771
+ "--app-secret-stdin",
2772
+ "--force-init"
2773
+ ], {
2774
+ stdio: [
2775
+ "pipe",
2776
+ "pipe",
2777
+ "pipe"
2778
+ ],
2779
+ encoding: "utf-8",
2780
+ input: appSecret
2781
+ });
2782
+ const configInitStdout = initRes.stdout?.trim() || void 0;
2783
+ const configInitStderr = initRes.stderr?.trim() || void 0;
2784
+ if (configInitStdout) console.error(`lark-cli config init stdout: ${configInitStdout}`);
2785
+ if (configInitStderr) console.error(`lark-cli config init stderr: ${configInitStderr}`);
2786
+ if (initRes.error) return {
2787
+ ok: false,
2788
+ configInitStdout,
2789
+ configInitStderr,
2790
+ error: `lark-cli config init spawn error: ${initRes.error.message}`
2791
+ };
2792
+ if (initRes.status !== 0) return {
2793
+ ok: false,
2794
+ configInitExitCode: initRes.status ?? void 0,
2795
+ configInitStdout,
2796
+ configInitStderr,
2797
+ error: `lark-cli config init exited with code ${initRes.status}`
2798
+ };
2799
+ appendPeToAgentsMd(agentsMdPath);
2800
+ return {
2801
+ ok: true,
2802
+ configInitExitCode: 0,
2803
+ agentsMdPath
2804
+ };
2805
+ }
2806
+ //#endregion
2807
+ //#region src/rules/agents-md-lark-cli-pe.ts
2808
+ function isLarkCliAvailable$1() {
2809
+ try {
2810
+ return (0, node_child_process.spawnSync)("lark-cli", ["--version"], {
2811
+ encoding: "utf-8",
2812
+ timeout: 5e3,
2813
+ stdio: [
2814
+ "ignore",
2815
+ "pipe",
2816
+ "ignore"
2817
+ ]
2818
+ }).status === 0;
2819
+ } catch {
2820
+ return false;
2821
+ }
2822
+ }
2823
+ let AgentsMdLarkCliPeRule = class AgentsMdLarkCliPeRule extends DiagnoseRule {
2824
+ validate(ctx) {
2825
+ if (!isLarkCliAvailable$1()) return { pass: true };
2826
+ const missingPath = collectExistingAgentsMdPaths(ctx).find((filePath) => {
2827
+ return !node_fs.default.readFileSync(filePath, "utf-8").includes(`<${PE_XML_TAG}>`);
2828
+ });
2829
+ if (!missingPath) return { pass: true };
2830
+ return {
2831
+ pass: false,
2832
+ message: `${missingPath} 中缺少 lark-cli-pe PE 内容,需要追加`
2833
+ };
2834
+ }
2835
+ repair(ctx) {
2836
+ if (!isLarkCliAvailable$1()) return;
2837
+ for (const filePath of collectExistingAgentsMdPaths(ctx)) {
2838
+ const content = node_fs.default.readFileSync(filePath, "utf-8");
2839
+ if (content.includes(`<lark-cli-pe>`)) continue;
2840
+ const sep = content.length > 0 && !content.endsWith("\n") ? "\n" : "";
2841
+ node_fs.default.appendFileSync(filePath, `${sep}${PE_PLACEHOLDER}`, "utf-8");
2842
+ console.error(`agents-md-lark-cli-pe: appended PE to ${filePath}`);
2843
+ }
2844
+ }
2845
+ };
2846
+ AgentsMdLarkCliPeRule = __decorate([Rule({
2847
+ key: "agents_md_lark_cli_pe",
2848
+ description: "检测各智能体 AGENTS.md 中是否缺失 lark-cli-pe PE 内容,lark-cli 存在时自动追加",
2849
+ dependsOn: ["config_syntax_check"],
2850
+ repairMode: "standard",
2851
+ level: "silent"
2852
+ })], AgentsMdLarkCliPeRule);
2853
+ //#endregion
2523
2854
  //#region src/rules/miaoda-official-plugins-install-spec-unlock.ts
2524
2855
  /**
2525
2856
  * Official miaoda-side plugins that must track manifest — version-locked specs
@@ -2623,11 +2954,11 @@ function getAllow$1(config) {
2623
2954
  //#region src/rules/lark-plugin-allow.ts
2624
2955
  const LARK_PLUGIN = "openclaw-lark";
2625
2956
  const LEGACY_LARK_PLUGIN = "feishu-openclaw-plugin";
2626
- const LARK_PLUGIN_NAMES$1 = [LARK_PLUGIN, LEGACY_LARK_PLUGIN];
2957
+ const LARK_PLUGIN_NAMES = [LARK_PLUGIN, LEGACY_LARK_PLUGIN];
2627
2958
  let LarkPluginAllowRule = class LarkPluginAllowRule extends DiagnoseRule {
2628
2959
  validate(ctx) {
2629
2960
  const allow = getAllow(ctx.config);
2630
- if (LARK_PLUGIN_NAMES$1.some((name) => allow.includes(name))) return { pass: true };
2961
+ if (LARK_PLUGIN_NAMES.some((name) => allow.includes(name))) return { pass: true };
2631
2962
  const installed = detectInstalledLarkPlugin(getExtensionsDir(ctx.configPath));
2632
2963
  if (installed == null) return { pass: true };
2633
2964
  return {
@@ -2646,7 +2977,7 @@ let LarkPluginAllowRule = class LarkPluginAllowRule extends DiagnoseRule {
2646
2977
  const rawAllow = pluginsMap.allow;
2647
2978
  const original = Array.isArray(rawAllow) ? rawAllow : [];
2648
2979
  const stringAllow = original.filter((e) => typeof e === "string");
2649
- if (LARK_PLUGIN_NAMES$1.some((name) => stringAllow.includes(name))) return;
2980
+ if (LARK_PLUGIN_NAMES.some((name) => stringAllow.includes(name))) return;
2650
2981
  original.push(installed);
2651
2982
  pluginsMap.allow = original;
2652
2983
  }
@@ -3347,6 +3678,7 @@ function resolveUpgradeDirection(installed, ocCur, recommendedOc, isLegacy) {
3347
3678
  /** 提取公共前置上下文;任何前置条件不满足时返回 null(规则 pass)。 */
3348
3679
  function resolveCompatContext(ctx) {
3349
3680
  const recommendedOc = ctx.vars.recommendedOpenclawTag;
3681
+ if (!recommendedOc) return null;
3350
3682
  const ocCur = getOcVersion();
3351
3683
  if (!ocCur) return null;
3352
3684
  const installed = getInstalledPlugin(ctx);
@@ -3369,7 +3701,6 @@ let FeishuPluginOpenclawUpgradeRule = class FeishuPluginOpenclawUpgradeRule exte
3369
3701
  if (!cc) return { pass: true };
3370
3702
  const { ocCur, recommendedOc, installed, isLegacy } = cc;
3371
3703
  if (isForkPlugin(installed)) return validateForkPlugin(installed, ocCur, recommendedOc);
3372
- if (!recommendedOc) return { pass: true };
3373
3704
  if (resolveUpgradeDirection(installed, ocCur, recommendedOc, isLegacy) !== "openclaw") return { pass: true };
3374
3705
  return {
3375
3706
  pass: false,
@@ -3397,17 +3728,11 @@ let FeishuPluginLarkUpgradeRule = class FeishuPluginLarkUpgradeRule extends Diag
3397
3728
  if (!cc) return { pass: true };
3398
3729
  const { ocCur, recommendedOc, installed, isLegacy } = cc;
3399
3730
  if (isForkPlugin(installed)) 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
- };
3731
+ if (resolveUpgradeDirection(installed, ocCur, recommendedOc, isLegacy) !== "lark") return { pass: true };
3407
3732
  return {
3408
3733
  pass: false,
3409
3734
  action: "upgrade_lark",
3410
- message: `${prefix};当前 openclaw@${ocCur} 已达推荐版本,可直接升级飞书插件`
3735
+ message: `${buildCompatPrefix(installed, ocCur, isLegacy)};当前 openclaw@${ocCur} 已达推荐版本,可直接升级飞书插件`
3411
3736
  };
3412
3737
  }
3413
3738
  };
@@ -3419,18 +3744,6 @@ FeishuPluginLarkUpgradeRule = __decorate([Rule({
3419
3744
  level: "critical",
3420
3745
  usesVars: ["recommendedOpenclawTag"]
3421
3746
  })], 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
- }
3434
3747
  function isForkPlugin(p) {
3435
3748
  return p.scope != null && FORK_SCOPES.includes(p.scope);
3436
3749
  }
@@ -3469,16 +3782,14 @@ function describeCompatConstraint(entry, pluginVersion) {
3469
3782
  /**
3470
3783
  * @lark-apaas/openclaw-lark 豁免 VERSION_COMPAT_MAP,但仍要求 openclaw ≥ FORK_LARK_PLUGIN_MIN_OC_VERSION。
3471
3784
  * 其他 @lark-apaas scope 的 fork 插件继续无条件 pass。
3472
- * recommendedOc 可为 undefined(doctor 模式),此时只检测最低版本要求,不指定目标升级版本。
3473
3785
  */
3474
3786
  function validateForkPlugin(installed, ocCur, recommendedOc) {
3475
3787
  if (installed.fullName !== FORK_LARK_PLUGIN_FULL_NAME) return { pass: true };
3476
3788
  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} 或更高版本`;
3478
3789
  return {
3479
3790
  pass: false,
3480
3791
  action: "upgrade_openclaw",
3481
- message: `飞书插件 ${describePlugin(installed)}(fork 版)要求 openclaw ≥ ${FORK_LARK_PLUGIN_MIN_OC_VERSION},当前 openclaw@${ocCur} 低于此要求${recommendation}`
3792
+ message: `飞书插件 ${describePlugin(installed)}(fork 版)要求 openclaw ≥ ${FORK_LARK_PLUGIN_MIN_OC_VERSION},当前 openclaw@${ocCur} 低于此要求;将 openclaw 升级到 ${recommendedOc} 即可满足`
3482
3793
  };
3483
3794
  }
3484
3795
  function describePlugin(p) {
@@ -3523,198 +3834,8 @@ function detectInstalledPlugin(ctx) {
3523
3834
  function extractScopedNameFromSpec$1(spec) {
3524
3835
  if (typeof spec !== "string") return void 0;
3525
3836
  const at = spec.indexOf("@", 1);
3526
- return at === -1 ? spec : spec.slice(0, at);
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
- * @param ignoreProbeFailed When true, "probe failed" is not treated as a
3555
- * failure condition. Use this when checking whether channels are structurally
3556
- * working for upgrade-gate purposes (probe failures indicate network issues,
3557
- * not plugin misconfiguration).
3558
- */
3559
- function accountIsWorking(bits, ignoreProbeFailed = true) {
3560
- const bitTokens = /* @__PURE__ */ new Set();
3561
- let hasError = false;
3562
- let hasProbeFailed = false;
3563
- for (const raw of bits) {
3564
- const b = raw.trim();
3565
- if (!b) continue;
3566
- if (b.startsWith("error:")) {
3567
- hasError = true;
3568
- continue;
3569
- }
3570
- if (b === "probe failed") {
3571
- hasProbeFailed = true;
3572
- continue;
3573
- }
3574
- bitTokens.add(b.split(":")[0]);
3575
- }
3576
- if (!bitTokens.has("enabled") || !bitTokens.has("configured")) return false;
3577
- if (bitTokens.has("works")) return true;
3578
- if (bitTokens.has("running") && !hasError && (ignoreProbeFailed || !hasProbeFailed)) return true;
3579
- return false;
3580
- }
3581
- /**
3582
- * Parse the raw stdout of `openclaw channels status --probe`.
3583
- * Port of Python `extract_channels_probe` from the feishu-channel-success-rate skill.
3584
- */
3585
- function parseChannelsProbeOutput(text, { ignoreProbeFailed = true } = {}) {
3586
- const gatewayReachable = text.includes("Gateway reachable");
3587
- const feishuConfigInvalid = text.includes(FEISHU_INVALID_CONFIG_MSG);
3588
- const accounts = [];
3589
- let anyAccountWorking = false;
3590
- for (const line of text.split("\n")) {
3591
- const m = CHANNEL_LINE_RE.exec(line.trim());
3592
- if (!m) continue;
3593
- const [, acct, rest] = m;
3594
- const bits = rest.split(",").map((b) => b.trim());
3595
- const isWorking = accountIsWorking(bits, ignoreProbeFailed);
3596
- if (isWorking) anyAccountWorking = true;
3597
- accounts.push({
3598
- id: acct.trim(),
3599
- bits,
3600
- isWorking,
3601
- raw: line.trim()
3602
- });
3603
- }
3604
- return {
3605
- gatewayReachable,
3606
- feishuConfigInvalid,
3607
- accounts,
3608
- anyAccountWorking
3609
- };
3610
- }
3611
- /**
3612
- * Run `openclaw channels status --probe` and return a structured result.
3613
- *
3614
- * The command may exit non-zero when some bot accounts fail their probe — that
3615
- * is still useful output. We therefore try to parse stdout even when the
3616
- * process exits with a non-zero code, falling back to an unavailable result
3617
- * only when there is genuinely no output to parse.
3618
- *
3619
- * @param timeoutMs Maximum wait time. Default is 60 s because v2026.4.x
3620
- * lacks a per-request HTTP timeout and can block indefinitely.
3621
- * @param ignoreProbeFailed When true, accounts with "probe failed" are still
3622
- * counted as working. Pass true for upgrade-gate checks where probe failures
3623
- * reflect network conditions rather than plugin misconfiguration.
3624
- */
3625
- function runChannelsProbe(timeoutMs = 6e4, { ignoreProbeFailed = true } = {}) {
3626
- let stdout = "";
3627
- let stderrText = "";
3628
- let execError;
3629
- try {
3630
- stdout = (0, node_child_process.execSync)("openclaw channels status --probe", {
3631
- encoding: "utf-8",
3632
- timeout: timeoutMs,
3633
- stdio: [
3634
- "ignore",
3635
- "pipe",
3636
- "pipe"
3637
- ]
3638
- });
3639
- } catch (e) {
3640
- const err = e;
3641
- const stdoutRaw = err.stdout;
3642
- stdout = typeof stdoutRaw === "string" ? stdoutRaw : stdoutRaw?.toString("utf-8") ?? "";
3643
- execError = err.message;
3644
- const stderrRaw = err.stderr;
3645
- stderrText = (typeof stderrRaw === "string" ? stderrRaw : stderrRaw?.toString("utf-8") ?? "").trim();
3646
- if (stderrText) console.error(`channels-probe: stderr from CLI: ${stderrText}`);
3647
- }
3648
- if (stdout.trim()) return {
3649
- available: true,
3650
- ...parseChannelsProbeOutput(stdout, { ignoreProbeFailed })
3651
- };
3652
- return {
3653
- available: false,
3654
- gatewayReachable: false,
3655
- feishuConfigInvalid: stderrText.includes(FEISHU_INVALID_CONFIG_MSG),
3656
- accounts: [],
3657
- anyAccountWorking: false,
3658
- error: execError ?? "no output from openclaw channels status --probe"
3659
- };
3660
- }
3661
- //#endregion
3662
- //#region src/rules/upgrade-lark-needed.ts
3663
- /**
3664
- * Detects the condition that warrants running `upgrade-lark`:
3665
- * - feishu plugin version incompatible with current openclaw, OR
3666
- * - openclaw channels status --probe reports feishu channel config invalid; AND
3667
- * - channels are not working.
3668
- *
3669
- * Both conditions must be true simultaneously. If version is compatible and
3670
- * feishu config is valid, or channels are working, the rule passes (no action needed).
3671
- *
3672
- * feishuConfigInvalid is read from the channels probe output rather than running a
3673
- * separate `openclaw status` call, since only `openclaw channels status --probe`
3674
- * reliably surfaces the schema validation error.
3675
- *
3676
- * profile: experimental — runs only in full sweep mode, not in standard doctor.
3677
- * level: silent — telemetry/sweep-only, does not trigger page-level repair UI.
3678
- */
3679
- let UpgradeLarkNeededRule = class UpgradeLarkNeededRule extends DiagnoseRule {
3680
- validate(ctx) {
3681
- let versionIncompatible = false;
3682
- try {
3683
- versionIncompatible = needsLarkUpgrade(ctx);
3684
- } catch {
3685
- versionIncompatible = true;
3686
- }
3687
- let probeResult;
3688
- try {
3689
- probeResult = runChannelsProbe(6e4);
3690
- } catch {
3691
- probeResult = {
3692
- available: false,
3693
- gatewayReachable: false,
3694
- feishuConfigInvalid: false,
3695
- accounts: [],
3696
- anyAccountWorking: false
3697
- };
3698
- }
3699
- const feishuConfigInvalid = probeResult.feishuConfigInvalid;
3700
- if (!(versionIncompatible || feishuConfigInvalid)) return { pass: true };
3701
- if (probeResult.anyAccountWorking) return { pass: true };
3702
- return {
3703
- pass: false,
3704
- action: "upgrade_lark",
3705
- message: `飞书插件需要升级且 channels 不可用(版本不兼容=${versionIncompatible}, feishu配置无效=${feishuConfigInvalid}),建议执行 upgrade-lark 命令升级飞书插件`
3706
- };
3707
- }
3708
- };
3709
- UpgradeLarkNeededRule = __decorate([Rule({
3710
- key: "upgrade_lark_needed",
3711
- description: "检测飞书插件版本不兼容且 channels 不可用,判断是否需要执行 upgrade-lark 升级",
3712
- dependsOn: ["config_syntax_check"],
3713
- repairMode: "check-only",
3714
- level: "silent",
3715
- profile: "experimental",
3716
- usesVars: ["recommendedOpenclawTag"]
3717
- })], UpgradeLarkNeededRule);
3837
+ return at === -1 ? spec : spec.slice(0, at);
3838
+ }
3718
3839
  //#endregion
3719
3840
  //#region src/rules/cleanup-install-backup-dirs.ts
3720
3841
  const DIR_PREFIX = ".openclaw-install-";
@@ -3798,7 +3919,7 @@ function extractScopedNameFromSpec(spec) {
3798
3919
  const at = spec.indexOf("@", 1);
3799
3920
  return at === -1 ? spec : spec.slice(0, at);
3800
3921
  }
3801
- function isLarkCliAvailable$1() {
3922
+ function isLarkCliAvailable() {
3802
3923
  try {
3803
3924
  return (0, node_child_process.spawnSync)(LARK_CLI_NAME$1, ["--version"], {
3804
3925
  encoding: "utf-8",
@@ -3839,7 +3960,7 @@ function installLarkCliOnce(tag) {
3839
3960
  let LarkCliMissingForInstalledLarkPluginRule = class LarkCliMissingForInstalledLarkPluginRule extends DiagnoseRule {
3840
3961
  validate(ctx) {
3841
3962
  if (!isTargetForkPlugin(readInstalledLarkPlugin(ctx))) return { pass: true };
3842
- if (isLarkCliAvailable$1()) return { pass: true };
3963
+ if (isLarkCliAvailable()) return { pass: true };
3843
3964
  return {
3844
3965
  pass: false,
3845
3966
  message: `${FORK_PACKAGE_NAME}@${TARGET_VERSION} 已安装,但 lark-cli 不可用;将执行一次 lark-cli 安装`
@@ -3847,7 +3968,7 @@ let LarkCliMissingForInstalledLarkPluginRule = class LarkCliMissingForInstalledL
3847
3968
  }
3848
3969
  repair(ctx) {
3849
3970
  if (!isTargetForkPlugin(readInstalledLarkPlugin(ctx))) return;
3850
- if (isLarkCliAvailable$1()) return;
3971
+ if (isLarkCliAvailable()) return;
3851
3972
  installLarkCliOnce(ctx.vars.recommendedOpenclawTag ?? TARGET_VERSION);
3852
3973
  }
3853
3974
  };
@@ -4300,33 +4421,6 @@ function finalize$1(results, aborted) {
4300
4421
  };
4301
4422
  }
4302
4423
  //#endregion
4303
- //#region src/paths.ts
4304
- /**
4305
- * Central directory for all ephemeral diagnose/reset artifacts: task status
4306
- * files (`reset-<taskId>.json`) and human-readable step logs
4307
- * (`reset-<taskId>.log`). Having everything under one dir makes debugging a
4308
- * stuck reset much easier — `ls /tmp/openclaw-diagnose/` shows every recent
4309
- * run, and each run's log is right next to its state.
4310
- */
4311
- const DIAGNOSE_DIR = "/tmp/openclaw-diagnose";
4312
- function resetResultFile(taskId) {
4313
- return `${DIAGNOSE_DIR}/reset-${taskId}.json`;
4314
- }
4315
- function resetLogFile(taskId) {
4316
- return `${DIAGNOSE_DIR}/reset-${taskId}.log`;
4317
- }
4318
- /** Sandbox workspace root where openclaw config + agent state lives. */
4319
- const WORKSPACE_DIR = "/home/gem/workspace/agent";
4320
- /** File containing the provider key used by the openclaw miaoda provider. */
4321
- const PROVIDER_FILE_PATH = "/home/gem/workspace/.force/openclaw/miaoda-provider-key";
4322
- /** File containing the miaoda openclaw secrets JSON. */
4323
- const SECRETS_FILE_PATH = "/home/gem/workspace/.force/openclaw/miaoda-openclaw-secrets.json";
4324
- /** Absolute path to the openclaw config JSON. */
4325
- const CONFIG_PATH = `${WORKSPACE_DIR}/openclaw.json`;
4326
- function upgradeLarkLogFile(runId) {
4327
- return `${DIAGNOSE_DIR}/upgrade-lark-${(/* @__PURE__ */ new Date()).toISOString().slice(0, 19).replace(/:/g, "-")}-${runId.slice(0, 8)}.log`;
4328
- }
4329
- //#endregion
4330
4424
  //#region src/run-log.ts
4331
4425
  let currentRunContext;
4332
4426
  /**
@@ -4858,306 +4952,46 @@ function updatePluginInstalls(configPath, installedPkgs) {
4858
4952
  const allow = plugins.allow;
4859
4953
  if (!allow.includes(pkg.name)) allow.push(pkg.name);
4860
4954
  if (!plugins.entries || typeof plugins.entries !== "object" || Array.isArray(plugins.entries)) plugins.entries = {};
4861
- const entries = plugins.entries;
4862
- entries[pkg.name] = {
4863
- ...asRecord(entries[pkg.name]) ?? {},
4864
- enabled: true
4865
- };
4866
- }
4867
- const tmpPath = configPath + ".installs-tmp";
4868
- node_fs.default.writeFileSync(tmpPath, JSON.stringify(config, null, 2), "utf-8");
4869
- moveSafe(tmpPath, configPath);
4870
- console.error(`[install-extension] plugins.installs updated: ${updated} entry(ies) in ${configPath}` + (skipped > 0 ? ` (${skipped} package(s) without installMetadata skipped)` : ""));
4871
- }
4872
- function installOne$1(pkg, tarball, homeBase) {
4873
- const destDir = node_path.default.join(homeBase, pkg.installPath);
4874
- const stagingDir = destDir + ".new";
4875
- const oldDir = destDir + ".old";
4876
- node_fs.default.mkdirSync(node_path.default.dirname(destDir), { recursive: true });
4877
- if (node_fs.default.existsSync(stagingDir)) node_fs.default.rmSync(stagingDir, {
4878
- recursive: true,
4879
- force: true
4880
- });
4881
- node_fs.default.mkdirSync(stagingDir);
4882
- try {
4883
- extractTarballTolerant(tarball, stagingDir, { stripComponents: 1 });
4884
- if (!node_fs.default.existsSync(node_path.default.join(stagingDir, "package.json"))) throw new Error(`extension tarball missing package.json: ${pkg.name}`);
4885
- } catch (e) {
4886
- try {
4887
- node_fs.default.rmSync(stagingDir, {
4888
- recursive: true,
4889
- force: true
4890
- });
4891
- } catch {}
4892
- throw e;
4893
- }
4894
- const hadOld = node_fs.default.existsSync(destDir);
4895
- if (hadOld) moveSafe(destDir, oldDir);
4896
- moveSafe(stagingDir, destDir);
4897
- if (hadOld && node_fs.default.existsSync(oldDir)) node_fs.default.rmSync(oldDir, {
4898
- recursive: true,
4899
- force: true
4900
- });
4901
- }
4902
- //#endregion
4903
- //#region src/lark-cli-init.ts
4904
- const LARK_PLUGIN_NAMES = ["openclaw-lark", "feishu-openclaw-plugin"];
4905
- const PE_XML_TAG = "lark-cli-pe";
4906
- const PE_PLACEHOLDER = `
4907
- <${PE_XML_TAG}>
4908
- **【强制要求 - 无例外】飞书所有操作必须先读 feishu-lark-cli skill。未读skill不得调用任何飞书工具,违者视为操作失误。**
4909
- </${PE_XML_TAG}>
4910
- `;
4911
- function isLarkPluginInstalled(configPath) {
4912
- const extDir = getExtensionsDir(configPath);
4913
- return LARK_PLUGIN_NAMES.some((name) => {
4914
- try {
4915
- return node_fs.default.existsSync(node_path.default.join(extDir, name, "package.json"));
4916
- } catch {
4917
- return false;
4918
- }
4919
- });
4920
- }
4921
- function isLarkCliAvailable() {
4922
- try {
4923
- return (0, node_child_process.spawnSync)("lark-cli", ["--version"], {
4924
- encoding: "utf-8",
4925
- timeout: 5e3,
4926
- stdio: [
4927
- "ignore",
4928
- "pipe",
4929
- "ignore"
4930
- ]
4931
- }).status === 0;
4932
- } catch {
4933
- return false;
4934
- }
4935
- }
4936
- function readConfig(configPath) {
4937
- try {
4938
- const raw = node_fs.default.readFileSync(configPath, "utf-8");
4939
- const parsed = loadJSON5().parse(raw);
4940
- return parsed != null && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
4941
- } catch {
4942
- return null;
4943
- }
4944
- }
4945
- /**
4946
- * Resolve the feishu app secret for the given appId.
4947
- *
4948
- * Lookup order:
4949
- * 1. channels.feishu.appSecret (single-agent: feishu.appId === appId)
4950
- * 2. channels.feishu.accounts[key].appSecret (multi-agent: account.appId === appId)
4951
- *
4952
- * Value interpretation:
4953
- * - string → use directly
4954
- * - object → secret is managed by a provider; use `feishuAppSecret` param instead
4955
- *
4956
- * Returns null when the secret cannot be determined.
4957
- */
4958
- function resolveAppSecret(appId, config, feishuAppSecret) {
4959
- const feishu = getNestedMap(config, "channels", "feishu");
4960
- if (!feishu) return null;
4961
- let rawSecret;
4962
- if (typeof feishu.appId === "string" && feishu.appId === appId) rawSecret = feishu.appSecret;
4963
- else {
4964
- const accounts = asRecord(feishu.accounts);
4965
- if (accounts) for (const [, val] of Object.entries(accounts)) {
4966
- const account = asRecord(val);
4967
- if (account?.appId === appId) {
4968
- rawSecret = account.appSecret ?? feishu.appSecret;
4969
- break;
4970
- }
4971
- }
4972
- }
4973
- if (typeof rawSecret === "string" && rawSecret) return rawSecret;
4974
- if (rawSecret != null && typeof rawSecret === "object") return feishuAppSecret ?? null;
4975
- return null;
4976
- }
4977
- /**
4978
- * Resolve the agents.md path for the given appId from the openclaw config.
4979
- *
4980
- * Case 1: appId matches channels.feishu.appId (single-agent path)
4981
- * → WORKSPACE_DIR/AGENTS.md
4982
- *
4983
- * Case 2: appId found in channels.feishu.accounts (multi-agent path)
4984
- * → find account key where account.appId === appId
4985
- * → find binding where match.channel=feishu && match.accountId=that key
4986
- * → if agentId === 'main' → WORKSPACE_DIR/agents.md
4987
- * → else find agent in agents.list by id → agent.workspace/agents.md
4988
- *
4989
- * Returns null when the path cannot be determined.
4990
- */
4991
- function resolveAgentsMdPath(appId, config) {
4992
- const feishu = getNestedMap(config, "channels", "feishu");
4993
- if (!feishu) {
4994
- console.error("resolveAgentsMdPath: channels.feishu not found");
4995
- return null;
4996
- }
4997
- if (typeof feishu.appId === "string" && feishu.appId === appId) {
4998
- console.error(`resolveAgentsMdPath: case=single-agent feishu.appId=${feishu.appId}`);
4999
- return node_path.default.join(WORKSPACE_DIR, "workspace", "AGENTS.md");
5000
- }
5001
- const accounts = asRecord(feishu.accounts);
5002
- if (!accounts) {
5003
- console.error("resolveAgentsMdPath: feishu.accounts not found");
5004
- return null;
5005
- }
5006
- let accountId;
5007
- for (const [key, val] of Object.entries(accounts)) if (asRecord(val)?.appId === appId) {
5008
- accountId = key;
5009
- break;
5010
- }
5011
- if (!accountId) {
5012
- console.error(`resolveAgentsMdPath: no account found with appId=${appId} in feishu.accounts keys=[${Object.keys(accounts).join(",")}]`);
5013
- return null;
5014
- }
5015
- console.error(`resolveAgentsMdPath: found accountId=${accountId}`);
5016
- const bindings = Array.isArray(config.bindings) ? config.bindings : [];
5017
- let agentId;
5018
- for (const b of bindings) {
5019
- const binding = asRecord(b);
5020
- if (!binding) continue;
5021
- const match = asRecord(binding.match);
5022
- if (match?.channel === "feishu" && match?.accountId === accountId) {
5023
- if (typeof binding.agentId === "string") {
5024
- agentId = binding.agentId;
5025
- break;
5026
- }
5027
- }
5028
- }
5029
- if (!agentId) {
5030
- console.error(`resolveAgentsMdPath: no binding found for accountId=${accountId} in bindings(count=${bindings.length})`);
5031
- return null;
5032
- }
5033
- console.error(`resolveAgentsMdPath: found agentId=${agentId}`);
5034
- if (agentId === "main") {
5035
- console.error("resolveAgentsMdPath: case=multi-agent-main");
5036
- return node_path.default.join(WORKSPACE_DIR, "workspace", "AGENTS.md");
5037
- }
5038
- const agentsObj = asRecord(config.agents);
5039
- const list = Array.isArray(agentsObj?.list) ? agentsObj.list : [];
5040
- for (const a of list) {
5041
- const agent = asRecord(a);
5042
- if (agent?.id === agentId) {
5043
- const ws = typeof agent.workspace === "string" ? agent.workspace : node_path.default.join(WORKSPACE_DIR, "workspace");
5044
- console.error(`resolveAgentsMdPath: case=multi-agent-custom agentId=${agentId} workspace=${ws}`);
5045
- return node_path.default.join(ws, "AGENTS.md");
5046
- }
5047
- }
5048
- console.error(`resolveAgentsMdPath: agentId=${agentId} not found in agents.list(count=${list.length})`);
5049
- return null;
5050
- }
5051
- function appendPeToAgentsMd(agentsMdPath) {
5052
- const dir = node_path.default.dirname(agentsMdPath);
5053
- if (!node_fs.default.existsSync(dir)) node_fs.default.mkdirSync(dir, { recursive: true });
5054
- const existing = node_fs.default.existsSync(agentsMdPath) ? node_fs.default.readFileSync(agentsMdPath, "utf-8") : "";
5055
- if (existing.includes(`<${PE_XML_TAG}>`)) {
5056
- console.error(`lark-cli-init: <${PE_XML_TAG}> already present in ${agentsMdPath}, skipping`);
5057
- return;
5058
- }
5059
- const sep = existing.length > 0 && !existing.endsWith("\n") ? "\n" : "";
5060
- node_fs.default.appendFileSync(agentsMdPath, `${sep}${PE_PLACEHOLDER}`, "utf-8");
5061
- console.error(`lark-cli-init: appended PE placeholder to ${agentsMdPath}`);
5062
- }
5063
- /**
5064
- * Collect every Feishu bot appId declared in the openclaw config.
5065
- * Covers both single-agent (channels.feishu.appId) and multi-agent
5066
- * (channels.feishu.accounts[*].appId) layouts. Returns a deduplicated list.
5067
- */
5068
- function collectFeishuAppIds(configPath) {
5069
- const config = readConfig(configPath ?? CONFIG_PATH);
5070
- if (!config) return [];
5071
- const feishu = getNestedMap(config, "channels", "feishu");
5072
- if (!feishu) return [];
5073
- const appIds = /* @__PURE__ */ new Set();
5074
- const topAppId = feishu.appId;
5075
- if (typeof topAppId === "string" && topAppId.trim()) appIds.add(topAppId.trim());
5076
- const accounts = asRecord(feishu.accounts);
5077
- if (accounts) for (const val of Object.values(accounts)) {
5078
- const appId = asRecord(val)?.appId;
5079
- if (typeof appId === "string" && appId.trim()) appIds.add(appId.trim());
5080
- }
5081
- return [...appIds];
5082
- }
5083
- function runLarkCliInit(opts) {
5084
- const configPath = opts.configPath ?? CONFIG_PATH;
5085
- if (!isLarkPluginInstalled(configPath)) {
5086
- console.error("lark-cli-init: skipping — openclaw-lark plugin not installed");
5087
- return {
5088
- ok: true,
5089
- skipped: true,
5090
- skipReason: "openclaw-lark plugin not installed"
4955
+ const entries = plugins.entries;
4956
+ entries[pkg.name] = {
4957
+ ...asRecord(entries[pkg.name]) ?? {},
4958
+ enabled: true
5091
4959
  };
5092
4960
  }
5093
- if (!isLarkCliAvailable()) {
5094
- console.error("lark-cli-init: skipping lark-cli command not found");
5095
- return {
5096
- ok: true,
5097
- skipped: true,
5098
- skipReason: "lark-cli command not found"
5099
- };
4961
+ const tmpPath = configPath + ".installs-tmp";
4962
+ node_fs.default.writeFileSync(tmpPath, JSON.stringify(config, null, 2), "utf-8");
4963
+ moveSafe(tmpPath, configPath);
4964
+ console.error(`[install-extension] plugins.installs updated: ${updated} entry(ies) in ${configPath}` + (skipped > 0 ? ` (${skipped} package(s) without installMetadata skipped)` : ""));
4965
+ }
4966
+ function installOne$1(pkg, tarball, homeBase) {
4967
+ const destDir = node_path.default.join(homeBase, pkg.installPath);
4968
+ const stagingDir = destDir + ".new";
4969
+ const oldDir = destDir + ".old";
4970
+ node_fs.default.mkdirSync(node_path.default.dirname(destDir), { recursive: true });
4971
+ if (node_fs.default.existsSync(stagingDir)) node_fs.default.rmSync(stagingDir, {
4972
+ recursive: true,
4973
+ force: true
4974
+ });
4975
+ node_fs.default.mkdirSync(stagingDir);
4976
+ try {
4977
+ extractTarballTolerant(tarball, stagingDir, { stripComponents: 1 });
4978
+ if (!node_fs.default.existsSync(node_path.default.join(stagingDir, "package.json"))) throw new Error(`extension tarball missing package.json: ${pkg.name}`);
4979
+ } catch (e) {
4980
+ try {
4981
+ node_fs.default.rmSync(stagingDir, {
4982
+ recursive: true,
4983
+ force: true
4984
+ });
4985
+ } catch {}
4986
+ throw e;
5100
4987
  }
5101
- const config = readConfig(configPath);
5102
- if (!config) return {
5103
- ok: false,
5104
- error: `could not read config at ${configPath}`
5105
- };
5106
- const agentsMdPath = resolveAgentsMdPath(opts.appId, config);
5107
- console.error(`lark-cli-init: resolved agents.md path=${agentsMdPath ?? "(null)"}`);
5108
- if (!agentsMdPath) return {
5109
- ok: false,
5110
- error: `could not resolve agents.md path for appId=${opts.appId}`
5111
- };
5112
- const appSecret = resolveAppSecret(opts.appId, config, opts.feishuAppSecret);
5113
- if (!appSecret) return {
5114
- ok: false,
5115
- error: `could not resolve appSecret for appId=${opts.appId}`
5116
- };
5117
- console.error(`lark-cli-init: running lark-cli config init --name ${opts.appId} --app-id ${opts.appId} --brand feishu --app-secret-stdin --force-init`);
5118
- const initRes = (0, node_child_process.spawnSync)("lark-cli", [
5119
- "config",
5120
- "init",
5121
- "--name",
5122
- opts.appId,
5123
- "--app-id",
5124
- opts.appId,
5125
- "--brand",
5126
- "feishu",
5127
- "--app-secret-stdin",
5128
- "--force-init"
5129
- ], {
5130
- stdio: [
5131
- "pipe",
5132
- "pipe",
5133
- "pipe"
5134
- ],
5135
- encoding: "utf-8",
5136
- input: appSecret
4988
+ const hadOld = node_fs.default.existsSync(destDir);
4989
+ if (hadOld) moveSafe(destDir, oldDir);
4990
+ moveSafe(stagingDir, destDir);
4991
+ if (hadOld && node_fs.default.existsSync(oldDir)) node_fs.default.rmSync(oldDir, {
4992
+ recursive: true,
4993
+ force: true
5137
4994
  });
5138
- const configInitStdout = initRes.stdout?.trim() || void 0;
5139
- const configInitStderr = initRes.stderr?.trim() || void 0;
5140
- if (configInitStdout) console.error(`lark-cli config init stdout: ${configInitStdout}`);
5141
- if (configInitStderr) console.error(`lark-cli config init stderr: ${configInitStderr}`);
5142
- if (initRes.error) return {
5143
- ok: false,
5144
- configInitStdout,
5145
- configInitStderr,
5146
- error: `lark-cli config init spawn error: ${initRes.error.message}`
5147
- };
5148
- if (initRes.status !== 0) return {
5149
- ok: false,
5150
- configInitExitCode: initRes.status ?? void 0,
5151
- configInitStdout,
5152
- configInitStderr,
5153
- error: `lark-cli config init exited with code ${initRes.status}`
5154
- };
5155
- appendPeToAgentsMd(agentsMdPath);
5156
- return {
5157
- ok: true,
5158
- configInitExitCode: 0,
5159
- agentsMdPath
5160
- };
5161
4995
  }
5162
4996
  //#endregion
5163
4997
  //#region ../../openclaw-slardar/lib/client.js
@@ -10413,7 +10247,7 @@ async function reportCliRun(opts) {
10413
10247
  //#region src/help.ts
10414
10248
  const BIN = "mclaw-diagnose";
10415
10249
  function versionBanner() {
10416
- return `v0.1.14-alpha.12`;
10250
+ return `v0.1.14-alpha.13`;
10417
10251
  }
10418
10252
  const COMMANDS = [
10419
10253
  {
@@ -10711,46 +10545,6 @@ OPTIONS
10711
10545
  EXIT CODES
10712
10546
  0 Success or skipped (prerequisites not met).
10713
10547
  1 Secret/path unresolvable, lark-cli failed, or config unreadable.
10714
- `
10715
- },
10716
- {
10717
- name: "upgrade-lark",
10718
- hidden: false,
10719
- summary: "Upgrade the Feishu/Lark plugin via @larksuite/openclaw-lark-tools",
10720
- help: `USAGE
10721
- ${BIN} upgrade-lark [--scene=<scene>] [--caller=<n>] [--trace-id=<id>]
10722
-
10723
- DESCRIPTION
10724
- Upgrades the Feishu/Lark plugin by running:
10725
- npx -y @larksuite/openclaw-lark-tools update --use-existing
10726
-
10727
- Before the upgrade, the following files are backed up:
10728
- - openclaw.json
10729
- - extensions/openclaw-lark/ (if present)
10730
- - extensions/feishu-openclaw-plugin/ (if present)
10731
- After the upgrade, the result is validated:
10732
- - feishu.accounts bot count must not decrease
10733
- - gateway config structure must remain valid (port/mode/bind/auth/trustedProxies)
10734
- If the upgrade command fails, or validation fails, the backed-up files are
10735
- restored to roll back the changes.
10736
-
10737
- Execution is logged to /tmp/openclaw-diagnose/upgrade-lark-<runId>.log.
10738
-
10739
- Output is a single JSON object on stdout:
10740
- { "ok": true, "stdout": "...", "stderr": "...", "logFile": "..." }
10741
- { "ok": false, "error": "...", "stderr": "...", "exitCode": 1,
10742
- "rollbackOk": true, "validationError": "...", "logFile": "..." }
10743
-
10744
- OPTIONS
10745
- --scene=<scene> Telemetry label forwarded to Slardar only.
10746
- Known values: PageUpgradeLark, etc. Custom strings accepted.
10747
- --caller=<name> Optional metadata passed to innerapi.
10748
- --trace-id=<id> Optional log-correlation id.
10749
-
10750
- EXIT CODES
10751
- 0 Success: upgrade ran and all validations passed.
10752
- 1 Failure: npx error, validation failed, or git commit failed.
10753
- File rollback was attempted (see rollbackOk in the JSON output).
10754
10548
  `
10755
10549
  },
10756
10550
  {
@@ -10784,41 +10578,6 @@ EXAMPLES
10784
10578
  ${BIN} rules # all rules
10785
10579
  ${BIN} rules --rule=gateway # single rule
10786
10580
  ${BIN} rules --rule=gateway --rule=feishu_channel # multiple rules
10787
- `
10788
- },
10789
- {
10790
- name: "channels-probe",
10791
- hidden: true,
10792
- summary: "Check feishu channel health via openclaw channels status --probe",
10793
- help: `USAGE
10794
- ${BIN} channels-probe [--timeout=<ms>]
10795
-
10796
- DESCRIPTION
10797
- Runs \`openclaw channels status --probe\` and returns a structured JSON
10798
- summary of whether the current environment's feishu channels are
10799
- configured and working correctly.
10800
-
10801
- Output:
10802
- {
10803
- "available": true,
10804
- "gatewayReachable": true,
10805
- "accounts": [
10806
- { "id": "default", "bits": ["enabled","configured","running","works"],
10807
- "isWorking": true, "raw": "- Feishu default: ..." }
10808
- ],
10809
- "anyAccountWorking": true
10810
- }
10811
-
10812
- An account is considered working when:
10813
- enabled ∧ configured ∧ ( works ∨ ( running ∧ no error: ∧ no probe failed ) )
10814
-
10815
- "available": false means the CLI invocation itself failed (openclaw not
10816
- found, gateway unreachable, or no parseable output returned).
10817
-
10818
- OPTIONS
10819
- --timeout=<ms> Max wait in milliseconds (default: 60000). The probe
10820
- can hang indefinitely on openclaw v2026.4.x due to a
10821
- missing per-request HTTP timeout — set this accordingly.
10822
10581
  `
10823
10582
  },
10824
10583
  {
@@ -10994,352 +10753,6 @@ function reportDoctorRunToSlardar(opts) {
10994
10753
  }
10995
10754
  });
10996
10755
  }
10997
- function readLogFile(filePath) {
10998
- try {
10999
- return node_fs.default.readFileSync(filePath, "utf-8");
11000
- } catch {
11001
- return "";
11002
- }
11003
- }
11004
- function reportUpgradeLarkToSlardar(opts) {
11005
- console.error(`[slardar] upgrade_lark_run scene=${opts.scene ?? ""} success=${opts.success} exitCode=${opts.exitCode ?? ""} rollbackOk=${opts.rollbackOk ?? ""}`);
11006
- const logContent = readLogFile(opts.logFile);
11007
- reportTask({
11008
- eventName: "upgrade_lark_run",
11009
- durationMs: opts.durationMs,
11010
- status: opts.success ? "success" : "failed",
11011
- extraCategories: {
11012
- scene: opts.scene ?? "",
11013
- exit_code: String(opts.exitCode ?? ""),
11014
- rollback_ok: opts.rollbackOk != null ? String(opts.rollbackOk) : "",
11015
- validation_error: opts.validationError ?? "",
11016
- error_msg: opts.error ?? "",
11017
- log_content: logContent
11018
- }
11019
- });
11020
- }
11021
- //#endregion
11022
- //#region src/upgrade-lark.ts
11023
- /** Plugin directories under extensions/ that are backed up before upgrade */
11024
- const FEISHU_PLUGIN_DIRS = ["openclaw-lark", "feishu-openclaw-plugin"];
11025
- function backupFiles(opts) {
11026
- const { workspaceDir, configPath, backupDir, log } = opts;
11027
- try {
11028
- node_fs.default.mkdirSync(backupDir, { recursive: true });
11029
- log(`backup dir: ${backupDir}`);
11030
- if (node_fs.default.existsSync(configPath)) {
11031
- const stat = node_fs.default.statSync(configPath);
11032
- node_fs.default.copyFileSync(configPath, node_path.default.join(backupDir, "openclaw.json"));
11033
- log(` backed up: openclaw.json (${stat.size} bytes)`);
11034
- } else log(` skipped: openclaw.json (not found)`);
11035
- const extSrc = node_path.default.join(workspaceDir, "extensions");
11036
- for (const pluginDir of FEISHU_PLUGIN_DIRS) {
11037
- const src = node_path.default.join(extSrc, pluginDir);
11038
- if (node_fs.default.existsSync(src)) {
11039
- const dst = node_path.default.join(backupDir, "extensions", pluginDir);
11040
- node_fs.default.cpSync(src, dst, { recursive: true });
11041
- const version = readPkgVersion(node_path.default.join(src, "package.json"));
11042
- log(` backed up: extensions/${pluginDir}${version ? ` (version: ${version})` : ""}`);
11043
- } else log(` skipped: extensions/${pluginDir} (not found)`);
11044
- }
11045
- return { ok: true };
11046
- } catch (e) {
11047
- return {
11048
- ok: false,
11049
- error: `backup failed: ${e.message}`
11050
- };
11051
- }
11052
- }
11053
- function restoreFiles(opts) {
11054
- const { workspaceDir, configPath, backupDir, log } = opts;
11055
- try {
11056
- const configBackup = node_path.default.join(backupDir, "openclaw.json");
11057
- if (node_fs.default.existsSync(configBackup)) {
11058
- node_fs.default.copyFileSync(configBackup, configPath);
11059
- log(` restored: openclaw.json`);
11060
- }
11061
- const extDst = node_path.default.join(workspaceDir, "extensions");
11062
- for (const pluginDir of FEISHU_PLUGIN_DIRS) {
11063
- const backupSrc = node_path.default.join(backupDir, "extensions", pluginDir);
11064
- if (node_fs.default.existsSync(backupSrc)) {
11065
- const dst = node_path.default.join(extDst, pluginDir);
11066
- if (node_fs.default.existsSync(dst)) node_fs.default.rmSync(dst, {
11067
- recursive: true,
11068
- force: true
11069
- });
11070
- node_fs.default.cpSync(backupSrc, dst, { recursive: true });
11071
- log(` restored: extensions/${pluginDir}`);
11072
- }
11073
- }
11074
- return true;
11075
- } catch (e) {
11076
- log(` restore error: ${e.message}`);
11077
- return false;
11078
- }
11079
- }
11080
- function readPkgVersion(pkgPath) {
11081
- try {
11082
- const pkg = JSON.parse(node_fs.default.readFileSync(pkgPath, "utf-8"));
11083
- return typeof pkg.version === "string" ? pkg.version : null;
11084
- } catch {
11085
- return null;
11086
- }
11087
- }
11088
- function snapshotVersions(cwd, log) {
11089
- const ocResult = (0, node_child_process.spawnSync)("openclaw", ["--version"], {
11090
- cwd,
11091
- encoding: "utf-8",
11092
- stdio: [
11093
- "ignore",
11094
- "pipe",
11095
- "pipe"
11096
- ],
11097
- timeout: 5e3
11098
- });
11099
- const ocRaw = (ocResult.stdout ?? "").trim() || (ocResult.stderr ?? "").trim();
11100
- const extDir = node_path.default.join(cwd, "extensions");
11101
- const larkPkg = node_path.default.join(extDir, "openclaw-lark", "package.json");
11102
- const feishuPkg = node_path.default.join(extDir, "feishu-openclaw-plugin", "package.json");
11103
- log(` version-check paths: ${larkPkg} [${node_fs.default.existsSync(larkPkg) ? "exists" : "missing"}]`);
11104
- log(` version-check paths: ${feishuPkg} [${node_fs.default.existsSync(feishuPkg) ? "exists" : "missing"}]`);
11105
- return {
11106
- openclaw: ocRaw || null,
11107
- openclawLark: readPkgVersion(larkPkg),
11108
- feishuOpenclawPlugin: readPkgVersion(feishuPkg)
11109
- };
11110
- }
11111
- function logVersionSnapshot(label, v, log) {
11112
- log(`${label}: openclaw=${v.openclaw ?? "n/a"} openclaw-lark=${v.openclawLark ?? "n/a"} feishu-openclaw-plugin=${v.feishuOpenclawPlugin ?? "n/a"}`);
11113
- }
11114
- function countFeishuBots(configPath) {
11115
- try {
11116
- const raw = node_fs.default.readFileSync(configPath, "utf-8");
11117
- const config = loadJSON5().parse(raw);
11118
- const accounts = getNestedMap(config, "channels", "feishu", "accounts");
11119
- if (accounts) return Object.keys(accounts).length;
11120
- const feishu = getNestedMap(config, "channels", "feishu");
11121
- return typeof feishu?.appId === "string" && feishu.appId ? 1 : 0;
11122
- } catch {
11123
- return 0;
11124
- }
11125
- }
11126
- /** Run channels probe, log results, and return the result. Never throws. */
11127
- function probeChannels(label, log, timeoutMs) {
11128
- try {
11129
- const r = runChannelsProbe(timeoutMs);
11130
- log(` ${label} available=${r.available} anyAccountWorking=${r.anyAccountWorking}`);
11131
- if (r.error) log(` ${label} error: ${r.error}`);
11132
- if (r.gatewayReachable != null) log(` ${label} gatewayReachable: ${r.gatewayReachable}`);
11133
- for (const acct of r.accounts ?? []) log(` ${label} account ${acct.id}: isWorking=${acct.isWorking} bits=[${acct.bits.join(",")}]`);
11134
- return r;
11135
- } catch (e) {
11136
- log(` ${label} channels probe threw: ${e.message}`);
11137
- return {
11138
- available: false,
11139
- gatewayReachable: false,
11140
- feishuConfigInvalid: false,
11141
- accounts: [],
11142
- anyAccountWorking: false
11143
- };
11144
- }
11145
- }
11146
- function runUpgradeLark(opts) {
11147
- const cwd = opts.cwd ?? "/home/gem/workspace/agent";
11148
- const configPath = opts.configPath ?? CONFIG_PATH;
11149
- const logFile = upgradeLarkLogFile(opts.runId);
11150
- const log = makeLogger(logFile);
11151
- const fsOpts = {
11152
- workspaceDir: cwd,
11153
- configPath,
11154
- backupDir: node_path.default.join(opts.backupBaseDir ?? "/tmp/openclaw-diagnose", `upgrade-lark-backup-${opts.runId}`),
11155
- log
11156
- };
11157
- const cliScript = opts.cliScript ?? process.argv[1];
11158
- const statusCheckDelayMs = opts.statusCheckDelayMs ?? 5e3;
11159
- log(`${"=".repeat(60)}`);
11160
- log(`upgrade-lark started runId=${opts.runId}`);
11161
- log(` cwd : ${cwd}`);
11162
- log(` configPath : ${configPath}`);
11163
- log(`${"=".repeat(60)}`);
11164
- log("");
11165
- log("── [Pre-check A] channels probe(升级前)────────────────");
11166
- const beforeChannels = probeChannels("before", log, 6e4);
11167
- log("");
11168
- log("── [Pre-check B] 版本兼容预检 ───────────────────────────");
11169
- let versionIncompatible = false;
11170
- try {
11171
- const rawConfig = node_fs.default.readFileSync(configPath, "utf-8");
11172
- versionIncompatible = needsLarkUpgrade({
11173
- config: loadJSON5().parse(rawConfig),
11174
- configPath,
11175
- vars: {},
11176
- providerDeps: {
11177
- usesMiaodaProvider: false,
11178
- usesMiaodaSecretProvider: false
11179
- }
11180
- });
11181
- log(` version-compat pre-check: ${versionIncompatible ? "NEEDS_UPGRADE" : "ok"}`);
11182
- } catch (e) {
11183
- log(` version-compat pre-check error: ${e.message} — treating as needs-upgrade`);
11184
- versionIncompatible = true;
11185
- }
11186
- const feishuConfigInvalid = beforeChannels.feishuConfigInvalid;
11187
- log(` feishu config invalid : ${feishuConfigInvalid}`);
11188
- log("");
11189
- log("── [Gate] 升级前置条件检查 ───────────────────────────────");
11190
- log(` versionIncompatible : ${versionIncompatible}`);
11191
- log(` feishuConfigInvalid : ${feishuConfigInvalid}`);
11192
- log(` channels working before: ${beforeChannels.anyAccountWorking}`);
11193
- if (!(versionIncompatible || feishuConfigInvalid)) {
11194
- const reason = "version compatible and feishu channel config valid — upgrade not needed";
11195
- log(` SKIP: ${reason}`);
11196
- log(`${"=".repeat(60)}`);
11197
- log("upgrade-lark skipped (pre-check gate)");
11198
- log(`${"=".repeat(60)}`);
11199
- return {
11200
- ok: true,
11201
- skipped: true,
11202
- skipReason: reason,
11203
- logFile
11204
- };
11205
- }
11206
- if (beforeChannels.anyAccountWorking) {
11207
- const reason = "channels are working — upgrade not needed (issue detected but system is functional)";
11208
- log(` SKIP: ${reason}`);
11209
- log(`${"=".repeat(60)}`);
11210
- log("upgrade-lark skipped (pre-check gate)");
11211
- log(`${"=".repeat(60)}`);
11212
- return {
11213
- ok: true,
11214
- skipped: true,
11215
- skipReason: reason,
11216
- logFile
11217
- };
11218
- }
11219
- log(` PROCEED: requiresLarkUpgrade=true (version=${versionIncompatible}, feishuConfig=${feishuConfigInvalid}) AND channels not working → running upgrade`);
11220
- log("");
11221
- log("── [1/6] 文件备份 ────────────────────────────────────────");
11222
- log(`before-state: botCount=${countFeishuBots(configPath)}`);
11223
- const backup = backupFiles(fsOpts);
11224
- if (!backup.ok) {
11225
- log(`ERROR: ${backup.error}`);
11226
- return {
11227
- ok: false,
11228
- error: backup.error,
11229
- logFile
11230
- };
11231
- }
11232
- log("backup: ok");
11233
- logVersionSnapshot("before-versions", snapshotVersions(cwd, log), log);
11234
- log("");
11235
- log("── [2/6] 清理本地 openclaw shim ─────────────────────────");
11236
- const localOpenclawBin = node_path.default.join(cwd, "node_modules", ".bin", "openclaw");
11237
- if (node_fs.default.existsSync(localOpenclawBin)) try {
11238
- node_fs.default.rmSync(localOpenclawBin);
11239
- log(` removed: ${localOpenclawBin}`);
11240
- } catch (e) {
11241
- log(` WARN: failed to remove ${localOpenclawBin}: ${e.message}`);
11242
- }
11243
- else log(` skipped: ${localOpenclawBin} (not found)`);
11244
- log("");
11245
- log("── [3/6] npx install (@larksuite/openclaw-lark-tools update) ──");
11246
- const npxResult = (0, node_child_process.spawnSync)("npx", [
11247
- "-y",
11248
- "@larksuite/openclaw-lark-tools",
11249
- "update"
11250
- ], {
11251
- cwd,
11252
- encoding: "utf-8",
11253
- stdio: [
11254
- "ignore",
11255
- "pipe",
11256
- "pipe"
11257
- ],
11258
- timeout: 12e4
11259
- });
11260
- const npxStdout = npxResult.stdout?.trim() ?? "";
11261
- const npxStderr = npxResult.stderr?.trim() ?? "";
11262
- const npxExitCode = npxResult.status ?? 1;
11263
- if (npxStdout) log(`npx stdout:\n${npxStdout}`);
11264
- if (npxStderr) log(`npx stderr:\n${npxStderr}`);
11265
- log(`npx exit: ${npxExitCode}${npxResult.error ? ` error: ${npxResult.error.message}` : ""}`);
11266
- if (statusCheckDelayMs > 0) {
11267
- log("");
11268
- log(`── 等待 ${statusCheckDelayMs / 1e3}s(让 openclaw 服务完成重启) ─────────────`);
11269
- Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, statusCheckDelayMs);
11270
- log("wait done");
11271
- }
11272
- const doRollback = (reason) => {
11273
- log(`ERROR: ${reason}`);
11274
- const rollbackOk = restoreFiles(fsOpts);
11275
- log(`rollback: ${rollbackOk ? "ok" : "FAILED"}`);
11276
- return {
11277
- ok: false,
11278
- error: reason,
11279
- validationError: reason,
11280
- stdout: npxStdout,
11281
- stderr: npxStderr,
11282
- exitCode: npxExitCode,
11283
- rollbackOk,
11284
- logFile
11285
- };
11286
- };
11287
- log("");
11288
- log("── [4/5] 安装后诊断校验 ─────────────────────────────────");
11289
- logVersionSnapshot("after-versions", snapshotVersions(cwd, log), log);
11290
- let afterVersionIncompatible = false;
11291
- try {
11292
- const rawConfig = node_fs.default.readFileSync(configPath, "utf-8");
11293
- afterVersionIncompatible = needsLarkUpgrade({
11294
- config: loadJSON5().parse(rawConfig),
11295
- configPath,
11296
- vars: {},
11297
- providerDeps: {
11298
- usesMiaodaProvider: false,
11299
- usesMiaodaSecretProvider: false
11300
- }
11301
- });
11302
- log(` version-compat post-check: ${afterVersionIncompatible ? "STILL_INCOMPATIBLE" : "ok"}`);
11303
- } catch (e) {
11304
- log(` version-compat post-check error: ${e.message} — treating as still-incompatible`);
11305
- afterVersionIncompatible = true;
11306
- }
11307
- const afterChannels = probeChannels("after", log, 6e4);
11308
- log(` feishu config invalid after: ${afterChannels.feishuConfigInvalid}`);
11309
- const stillNeedsUpgrade = (afterVersionIncompatible || afterChannels.feishuConfigInvalid) && !afterChannels.anyAccountWorking;
11310
- log(` post-check: stillNeedsUpgrade=${stillNeedsUpgrade} (version=${afterVersionIncompatible}, feishuConfig=${afterChannels.feishuConfigInvalid}, channelsWorking=${afterChannels.anyAccountWorking})`);
11311
- if (stillNeedsUpgrade) return doRollback(`post-install diagnosis still shows anomaly: versionIncompatible=${afterVersionIncompatible}, feishuConfigInvalid=${afterChannels.feishuConfigInvalid}, anyAccountWorking=${afterChannels.anyAccountWorking}`);
11312
- log(" post-install diagnosis: ok (upgrade conditions resolved)");
11313
- log("");
11314
- log("── [6/6] doctor --fix ────────────────────────────────────");
11315
- const fixArgs = ["doctor", "--fix"];
11316
- if (opts.scene) fixArgs.push(`--scene=${opts.scene}`);
11317
- const fixResult = (0, node_child_process.spawnSync)(process.execPath, [cliScript, ...fixArgs], {
11318
- cwd,
11319
- encoding: "utf-8",
11320
- stdio: [
11321
- "ignore",
11322
- "pipe",
11323
- "pipe"
11324
- ],
11325
- timeout: 6e4,
11326
- env: process.env
11327
- });
11328
- if (fixResult.stdout?.trim()) log(`doctor(fix) stdout:\n${fixResult.stdout.trim()}`);
11329
- if (fixResult.stderr?.trim()) log(`doctor(fix) stderr:\n${fixResult.stderr.trim()}`);
11330
- log(`doctor(fix) exit: ${fixResult.status ?? "null"}${fixResult.error ? ` error: ${fixResult.error.message}` : ""}`);
11331
- log("");
11332
- log(`${"=".repeat(60)}`);
11333
- log("upgrade-lark completed successfully");
11334
- log(`${"=".repeat(60)}`);
11335
- return {
11336
- ok: true,
11337
- stdout: npxStdout,
11338
- stderr: npxStderr,
11339
- exitCode: npxExitCode,
11340
- logFile
11341
- };
11342
- }
11343
10756
  //#endregion
11344
10757
  //#region src/index.ts
11345
10758
  const args = node_process.default.argv.slice(2);
@@ -11857,50 +11270,6 @@ async function main() {
11857
11270
  if (!result.ok) node_process.default.exit(1);
11858
11271
  break;
11859
11272
  }
11860
- case "upgrade-lark": {
11861
- const result = runUpgradeLark({
11862
- runId: rc.runId,
11863
- scene
11864
- });
11865
- const upgradeDurationMs = Date.now() - t0;
11866
- console.log(JSON.stringify(result));
11867
- reportUpgradeLarkToSlardar({
11868
- scene,
11869
- durationMs: upgradeDurationMs,
11870
- success: result.ok,
11871
- logFile: result.logFile,
11872
- exitCode: result.exitCode,
11873
- rollbackOk: result.rollbackOk,
11874
- validationError: result.validationError,
11875
- error: result.error
11876
- });
11877
- try {
11878
- await reportCliRun({
11879
- command: "upgrade-lark",
11880
- runId: rc.runId,
11881
- version: getVersion(),
11882
- invocation: args.join(" "),
11883
- durationMs: upgradeDurationMs,
11884
- caller: rc.caller,
11885
- traceId: rc.traceId,
11886
- success: result.ok,
11887
- result,
11888
- error: result.ok ? void 0 : { message: result.error ?? "upgrade-lark failed" }
11889
- });
11890
- } catch (e) {
11891
- console.error(`[telemetry] reportCliRun failed: ${e.message}`);
11892
- }
11893
- if (!result.ok) {
11894
- node_process.default.exitCode = 1;
11895
- return;
11896
- }
11897
- break;
11898
- }
11899
- case "channels-probe": {
11900
- const result = runChannelsProbe(getFlag(args, "timeout") ? Number(getFlag(args, "timeout")) : void 0);
11901
- console.log(JSON.stringify(result));
11902
- break;
11903
- }
11904
11273
  default:
11905
11274
  node_process.default.stderr.write(`Unknown command: ${mode}\n\n`);
11906
11275
  node_process.default.stderr.write(formatTopLevelHelp(helpFlags.expert));