@lark-apaas/openclaw-scripts-diagnose-cli 0.1.14-alpha.13 → 0.1.14-alpha.14
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 +1006 -375
- 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.14";
|
|
56
56
|
}
|
|
57
57
|
//#endregion
|
|
58
58
|
//#region src/rule-engine/base.ts
|
|
@@ -2520,337 +2520,6 @@ 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
|
|
2854
2523
|
//#region src/rules/miaoda-official-plugins-install-spec-unlock.ts
|
|
2855
2524
|
/**
|
|
2856
2525
|
* Official miaoda-side plugins that must track manifest — version-locked specs
|
|
@@ -2954,11 +2623,11 @@ function getAllow$1(config) {
|
|
|
2954
2623
|
//#region src/rules/lark-plugin-allow.ts
|
|
2955
2624
|
const LARK_PLUGIN = "openclaw-lark";
|
|
2956
2625
|
const LEGACY_LARK_PLUGIN = "feishu-openclaw-plugin";
|
|
2957
|
-
const LARK_PLUGIN_NAMES = [LARK_PLUGIN, LEGACY_LARK_PLUGIN];
|
|
2626
|
+
const LARK_PLUGIN_NAMES$1 = [LARK_PLUGIN, LEGACY_LARK_PLUGIN];
|
|
2958
2627
|
let LarkPluginAllowRule = class LarkPluginAllowRule extends DiagnoseRule {
|
|
2959
2628
|
validate(ctx) {
|
|
2960
2629
|
const allow = getAllow(ctx.config);
|
|
2961
|
-
if (LARK_PLUGIN_NAMES.some((name) => allow.includes(name))) return { pass: true };
|
|
2630
|
+
if (LARK_PLUGIN_NAMES$1.some((name) => allow.includes(name))) return { pass: true };
|
|
2962
2631
|
const installed = detectInstalledLarkPlugin(getExtensionsDir(ctx.configPath));
|
|
2963
2632
|
if (installed == null) return { pass: true };
|
|
2964
2633
|
return {
|
|
@@ -2977,7 +2646,7 @@ let LarkPluginAllowRule = class LarkPluginAllowRule extends DiagnoseRule {
|
|
|
2977
2646
|
const rawAllow = pluginsMap.allow;
|
|
2978
2647
|
const original = Array.isArray(rawAllow) ? rawAllow : [];
|
|
2979
2648
|
const stringAllow = original.filter((e) => typeof e === "string");
|
|
2980
|
-
if (LARK_PLUGIN_NAMES.some((name) => stringAllow.includes(name))) return;
|
|
2649
|
+
if (LARK_PLUGIN_NAMES$1.some((name) => stringAllow.includes(name))) return;
|
|
2981
2650
|
original.push(installed);
|
|
2982
2651
|
pluginsMap.allow = original;
|
|
2983
2652
|
}
|
|
@@ -3678,7 +3347,6 @@ function resolveUpgradeDirection(installed, ocCur, recommendedOc, isLegacy) {
|
|
|
3678
3347
|
/** 提取公共前置上下文;任何前置条件不满足时返回 null(规则 pass)。 */
|
|
3679
3348
|
function resolveCompatContext(ctx) {
|
|
3680
3349
|
const recommendedOc = ctx.vars.recommendedOpenclawTag;
|
|
3681
|
-
if (!recommendedOc) return null;
|
|
3682
3350
|
const ocCur = getOcVersion();
|
|
3683
3351
|
if (!ocCur) return null;
|
|
3684
3352
|
const installed = getInstalledPlugin(ctx);
|
|
@@ -3701,6 +3369,7 @@ let FeishuPluginOpenclawUpgradeRule = class FeishuPluginOpenclawUpgradeRule exte
|
|
|
3701
3369
|
if (!cc) return { pass: true };
|
|
3702
3370
|
const { ocCur, recommendedOc, installed, isLegacy } = cc;
|
|
3703
3371
|
if (isForkPlugin(installed)) return validateForkPlugin(installed, ocCur, recommendedOc);
|
|
3372
|
+
if (!recommendedOc) return { pass: true };
|
|
3704
3373
|
if (resolveUpgradeDirection(installed, ocCur, recommendedOc, isLegacy) !== "openclaw") return { pass: true };
|
|
3705
3374
|
return {
|
|
3706
3375
|
pass: false,
|
|
@@ -3728,11 +3397,17 @@ let FeishuPluginLarkUpgradeRule = class FeishuPluginLarkUpgradeRule extends Diag
|
|
|
3728
3397
|
if (!cc) return { pass: true };
|
|
3729
3398
|
const { ocCur, recommendedOc, installed, isLegacy } = cc;
|
|
3730
3399
|
if (isForkPlugin(installed)) return { pass: true };
|
|
3731
|
-
if (
|
|
3400
|
+
if (!isLarkUpgradeNeededFromCC(cc)) return { pass: true };
|
|
3401
|
+
const prefix = buildCompatPrefix(installed, ocCur, isLegacy);
|
|
3402
|
+
if (!recommendedOc) return {
|
|
3403
|
+
pass: false,
|
|
3404
|
+
action: "upgrade_lark",
|
|
3405
|
+
message: `${prefix};建议升级飞书插件至兼容版本`
|
|
3406
|
+
};
|
|
3732
3407
|
return {
|
|
3733
3408
|
pass: false,
|
|
3734
3409
|
action: "upgrade_lark",
|
|
3735
|
-
message: `${
|
|
3410
|
+
message: `${prefix};当前 openclaw@${ocCur} 已达推荐版本,可直接升级飞书插件`
|
|
3736
3411
|
};
|
|
3737
3412
|
}
|
|
3738
3413
|
};
|
|
@@ -3744,6 +3419,18 @@ FeishuPluginLarkUpgradeRule = __decorate([Rule({
|
|
|
3744
3419
|
level: "critical",
|
|
3745
3420
|
usesVars: ["recommendedOpenclawTag"]
|
|
3746
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
|
+
}
|
|
3747
3434
|
function isForkPlugin(p) {
|
|
3748
3435
|
return p.scope != null && FORK_SCOPES.includes(p.scope);
|
|
3749
3436
|
}
|
|
@@ -3782,14 +3469,16 @@ function describeCompatConstraint(entry, pluginVersion) {
|
|
|
3782
3469
|
/**
|
|
3783
3470
|
* @lark-apaas/openclaw-lark 豁免 VERSION_COMPAT_MAP,但仍要求 openclaw ≥ FORK_LARK_PLUGIN_MIN_OC_VERSION。
|
|
3784
3471
|
* 其他 @lark-apaas scope 的 fork 插件继续无条件 pass。
|
|
3472
|
+
* recommendedOc 可为 undefined(doctor 模式),此时只检测最低版本要求,不指定目标升级版本。
|
|
3785
3473
|
*/
|
|
3786
3474
|
function validateForkPlugin(installed, ocCur, recommendedOc) {
|
|
3787
3475
|
if (installed.fullName !== FORK_LARK_PLUGIN_FULL_NAME) return { pass: true };
|
|
3788
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} 或更高版本`;
|
|
3789
3478
|
return {
|
|
3790
3479
|
pass: false,
|
|
3791
3480
|
action: "upgrade_openclaw",
|
|
3792
|
-
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}`
|
|
3793
3482
|
};
|
|
3794
3483
|
}
|
|
3795
3484
|
function describePlugin(p) {
|
|
@@ -3836,6 +3525,198 @@ function extractScopedNameFromSpec$1(spec) {
|
|
|
3836
3525
|
const at = spec.indexOf("@", 1);
|
|
3837
3526
|
return at === -1 ? spec : spec.slice(0, at);
|
|
3838
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({
|
|
3535
|
+
...ctx,
|
|
3536
|
+
vars: {
|
|
3537
|
+
...ctx.vars,
|
|
3538
|
+
recommendedOpenclawTag: void 0
|
|
3539
|
+
}
|
|
3540
|
+
});
|
|
3541
|
+
if (!cc) return false;
|
|
3542
|
+
const { ocCur, installed } = cc;
|
|
3543
|
+
if (isForkPlugin(installed)) {
|
|
3544
|
+
if (installed.fullName === FORK_LARK_PLUGIN_FULL_NAME) return compareCalVer(ocCur, FORK_LARK_PLUGIN_MIN_OC_VERSION) < 0;
|
|
3545
|
+
return false;
|
|
3546
|
+
}
|
|
3547
|
+
return isLarkUpgradeNeededFromCC(cc);
|
|
3548
|
+
}
|
|
3549
|
+
//#endregion
|
|
3550
|
+
//#region src/channels-probe.ts
|
|
3551
|
+
const FEISHU_INVALID_CONFIG_MSG = "channels.feishu: invalid config: must NOT have additional properties";
|
|
3552
|
+
const CHANNEL_LINE_RE = /^-\s+Feishu\s+([^:]+):\s+(.+)$/;
|
|
3553
|
+
/**
|
|
3554
|
+
* Port of Python `_account_is_working` from the feishu-channel-success-rate skill.
|
|
3555
|
+
*
|
|
3556
|
+
* Strips colon-prefixed key:value bits (dm:, bot:, in:, out:, token:, allow:,
|
|
3557
|
+
* intents:, groups:, health:) and evaluates the canonical health formula.
|
|
3558
|
+
*
|
|
3559
|
+
* @param ignoreProbeFailed When true, "probe failed" is not treated as a
|
|
3560
|
+
* failure condition. Use this when checking whether channels are structurally
|
|
3561
|
+
* working for upgrade-gate purposes (probe failures indicate network issues,
|
|
3562
|
+
* not plugin misconfiguration).
|
|
3563
|
+
*/
|
|
3564
|
+
function accountIsWorking(bits, ignoreProbeFailed = true) {
|
|
3565
|
+
const bitTokens = /* @__PURE__ */ new Set();
|
|
3566
|
+
let hasError = false;
|
|
3567
|
+
let hasProbeFailed = false;
|
|
3568
|
+
for (const raw of bits) {
|
|
3569
|
+
const b = raw.trim();
|
|
3570
|
+
if (!b) continue;
|
|
3571
|
+
if (b.startsWith("error:")) {
|
|
3572
|
+
hasError = true;
|
|
3573
|
+
continue;
|
|
3574
|
+
}
|
|
3575
|
+
if (b === "probe failed") {
|
|
3576
|
+
hasProbeFailed = true;
|
|
3577
|
+
continue;
|
|
3578
|
+
}
|
|
3579
|
+
bitTokens.add(b.split(":")[0]);
|
|
3580
|
+
}
|
|
3581
|
+
if (!bitTokens.has("enabled") || !bitTokens.has("configured")) return false;
|
|
3582
|
+
if (bitTokens.has("works")) return true;
|
|
3583
|
+
if (bitTokens.has("running") && !hasError && (ignoreProbeFailed || !hasProbeFailed)) return true;
|
|
3584
|
+
return false;
|
|
3585
|
+
}
|
|
3586
|
+
/**
|
|
3587
|
+
* Parse the raw stdout of `openclaw channels status --probe`.
|
|
3588
|
+
* Port of Python `extract_channels_probe` from the feishu-channel-success-rate skill.
|
|
3589
|
+
*/
|
|
3590
|
+
function parseChannelsProbeOutput(text, { ignoreProbeFailed = true } = {}) {
|
|
3591
|
+
const gatewayReachable = text.includes("Gateway reachable");
|
|
3592
|
+
const feishuConfigInvalid = text.includes(FEISHU_INVALID_CONFIG_MSG);
|
|
3593
|
+
const accounts = [];
|
|
3594
|
+
let anyAccountWorking = false;
|
|
3595
|
+
for (const line of text.split("\n")) {
|
|
3596
|
+
const m = CHANNEL_LINE_RE.exec(line.trim());
|
|
3597
|
+
if (!m) continue;
|
|
3598
|
+
const [, acct, rest] = m;
|
|
3599
|
+
const bits = rest.split(",").map((b) => b.trim());
|
|
3600
|
+
const isWorking = accountIsWorking(bits, ignoreProbeFailed);
|
|
3601
|
+
if (isWorking) anyAccountWorking = true;
|
|
3602
|
+
accounts.push({
|
|
3603
|
+
id: acct.trim(),
|
|
3604
|
+
bits,
|
|
3605
|
+
isWorking,
|
|
3606
|
+
raw: line.trim()
|
|
3607
|
+
});
|
|
3608
|
+
}
|
|
3609
|
+
return {
|
|
3610
|
+
gatewayReachable,
|
|
3611
|
+
feishuConfigInvalid,
|
|
3612
|
+
accounts,
|
|
3613
|
+
anyAccountWorking
|
|
3614
|
+
};
|
|
3615
|
+
}
|
|
3616
|
+
/**
|
|
3617
|
+
* Run `openclaw channels status --probe` and return a structured result.
|
|
3618
|
+
*
|
|
3619
|
+
* The command may exit non-zero when some bot accounts fail their probe — that
|
|
3620
|
+
* is still useful output. We therefore try to parse stdout even when the
|
|
3621
|
+
* process exits with a non-zero code, falling back to an unavailable result
|
|
3622
|
+
* only when there is genuinely no output to parse.
|
|
3623
|
+
*
|
|
3624
|
+
* @param timeoutMs Maximum wait time. Default is 60 s because v2026.4.x
|
|
3625
|
+
* lacks a per-request HTTP timeout and can block indefinitely.
|
|
3626
|
+
* @param ignoreProbeFailed When true, accounts with "probe failed" are still
|
|
3627
|
+
* counted as working. Pass true for upgrade-gate checks where probe failures
|
|
3628
|
+
* reflect network conditions rather than plugin misconfiguration.
|
|
3629
|
+
*/
|
|
3630
|
+
function runChannelsProbe(timeoutMs = 6e4, { ignoreProbeFailed = true } = {}) {
|
|
3631
|
+
let stdout = "";
|
|
3632
|
+
let stderrText = "";
|
|
3633
|
+
let execError;
|
|
3634
|
+
try {
|
|
3635
|
+
stdout = (0, node_child_process.execSync)("openclaw channels status --probe", {
|
|
3636
|
+
encoding: "utf-8",
|
|
3637
|
+
timeout: timeoutMs,
|
|
3638
|
+
stdio: [
|
|
3639
|
+
"ignore",
|
|
3640
|
+
"pipe",
|
|
3641
|
+
"pipe"
|
|
3642
|
+
]
|
|
3643
|
+
});
|
|
3644
|
+
} catch (e) {
|
|
3645
|
+
const err = e;
|
|
3646
|
+
const stdoutRaw = err.stdout;
|
|
3647
|
+
stdout = typeof stdoutRaw === "string" ? stdoutRaw : stdoutRaw?.toString("utf-8") ?? "";
|
|
3648
|
+
execError = err.message;
|
|
3649
|
+
const stderrRaw = err.stderr;
|
|
3650
|
+
stderrText = (typeof stderrRaw === "string" ? stderrRaw : stderrRaw?.toString("utf-8") ?? "").trim();
|
|
3651
|
+
if (stderrText) console.error(`channels-probe: stderr from CLI: ${stderrText}`);
|
|
3652
|
+
}
|
|
3653
|
+
if (stdout.trim()) return {
|
|
3654
|
+
available: true,
|
|
3655
|
+
...parseChannelsProbeOutput(stdout, { ignoreProbeFailed })
|
|
3656
|
+
};
|
|
3657
|
+
return {
|
|
3658
|
+
available: false,
|
|
3659
|
+
gatewayReachable: false,
|
|
3660
|
+
feishuConfigInvalid: stderrText.includes(FEISHU_INVALID_CONFIG_MSG),
|
|
3661
|
+
accounts: [],
|
|
3662
|
+
anyAccountWorking: false,
|
|
3663
|
+
error: execError ?? "no output from openclaw channels status --probe"
|
|
3664
|
+
};
|
|
3665
|
+
}
|
|
3666
|
+
//#endregion
|
|
3667
|
+
//#region src/rules/upgrade-lark-needed.ts
|
|
3668
|
+
/**
|
|
3669
|
+
* Detects the condition that warrants running `upgrade-lark`:
|
|
3670
|
+
* - feishu plugin version incompatible with current openclaw, OR
|
|
3671
|
+
* - openclaw channels status --probe reports feishu channel config invalid; AND
|
|
3672
|
+
* - channels are not working.
|
|
3673
|
+
*
|
|
3674
|
+
* Both conditions must be true simultaneously. If version is compatible and
|
|
3675
|
+
* feishu config is valid, or channels are working, the rule passes (no action needed).
|
|
3676
|
+
*
|
|
3677
|
+
* feishuConfigInvalid is read from the channels probe output rather than running a
|
|
3678
|
+
* separate `openclaw status` call, since only `openclaw channels status --probe`
|
|
3679
|
+
* reliably surfaces the schema validation error.
|
|
3680
|
+
*
|
|
3681
|
+
* profile: experimental — runs only in full sweep mode, not in standard doctor.
|
|
3682
|
+
* level: silent — telemetry/sweep-only, does not trigger page-level repair UI.
|
|
3683
|
+
*/
|
|
3684
|
+
let UpgradeLarkNeededRule = class UpgradeLarkNeededRule extends DiagnoseRule {
|
|
3685
|
+
validate(ctx) {
|
|
3686
|
+
let versionIncompatible = false;
|
|
3687
|
+
try {
|
|
3688
|
+
versionIncompatible = needsLarkUpgrade(ctx);
|
|
3689
|
+
} catch {}
|
|
3690
|
+
let probeResult;
|
|
3691
|
+
try {
|
|
3692
|
+
probeResult = runChannelsProbe(6e4);
|
|
3693
|
+
} catch {
|
|
3694
|
+
probeResult = {
|
|
3695
|
+
available: false,
|
|
3696
|
+
gatewayReachable: false,
|
|
3697
|
+
feishuConfigInvalid: false,
|
|
3698
|
+
accounts: [],
|
|
3699
|
+
anyAccountWorking: false
|
|
3700
|
+
};
|
|
3701
|
+
}
|
|
3702
|
+
const feishuConfigInvalid = probeResult.feishuConfigInvalid;
|
|
3703
|
+
if (!(versionIncompatible || feishuConfigInvalid)) return { pass: true };
|
|
3704
|
+
if (probeResult.anyAccountWorking) return { pass: true };
|
|
3705
|
+
return {
|
|
3706
|
+
pass: false,
|
|
3707
|
+
action: "upgrade_lark",
|
|
3708
|
+
message: `飞书插件需要升级且 channels 不可用(版本不兼容=${versionIncompatible}, feishu配置无效=${feishuConfigInvalid}),建议执行 upgrade-lark 命令升级飞书插件`
|
|
3709
|
+
};
|
|
3710
|
+
}
|
|
3711
|
+
};
|
|
3712
|
+
UpgradeLarkNeededRule = __decorate([Rule({
|
|
3713
|
+
key: "upgrade_lark_needed",
|
|
3714
|
+
description: "检测飞书插件版本不兼容且 channels 不可用,判断是否需要执行 upgrade-lark 升级",
|
|
3715
|
+
dependsOn: ["config_syntax_check"],
|
|
3716
|
+
repairMode: "check-only",
|
|
3717
|
+
level: "silent",
|
|
3718
|
+
profile: "experimental"
|
|
3719
|
+
})], UpgradeLarkNeededRule);
|
|
3839
3720
|
//#endregion
|
|
3840
3721
|
//#region src/rules/cleanup-install-backup-dirs.ts
|
|
3841
3722
|
const DIR_PREFIX = ".openclaw-install-";
|
|
@@ -3919,7 +3800,7 @@ function extractScopedNameFromSpec(spec) {
|
|
|
3919
3800
|
const at = spec.indexOf("@", 1);
|
|
3920
3801
|
return at === -1 ? spec : spec.slice(0, at);
|
|
3921
3802
|
}
|
|
3922
|
-
function isLarkCliAvailable() {
|
|
3803
|
+
function isLarkCliAvailable$1() {
|
|
3923
3804
|
try {
|
|
3924
3805
|
return (0, node_child_process.spawnSync)(LARK_CLI_NAME$1, ["--version"], {
|
|
3925
3806
|
encoding: "utf-8",
|
|
@@ -3960,7 +3841,7 @@ function installLarkCliOnce(tag) {
|
|
|
3960
3841
|
let LarkCliMissingForInstalledLarkPluginRule = class LarkCliMissingForInstalledLarkPluginRule extends DiagnoseRule {
|
|
3961
3842
|
validate(ctx) {
|
|
3962
3843
|
if (!isTargetForkPlugin(readInstalledLarkPlugin(ctx))) return { pass: true };
|
|
3963
|
-
if (isLarkCliAvailable()) return { pass: true };
|
|
3844
|
+
if (isLarkCliAvailable$1()) return { pass: true };
|
|
3964
3845
|
return {
|
|
3965
3846
|
pass: false,
|
|
3966
3847
|
message: `${FORK_PACKAGE_NAME}@${TARGET_VERSION} 已安装,但 lark-cli 不可用;将执行一次 lark-cli 安装`
|
|
@@ -3968,7 +3849,7 @@ let LarkCliMissingForInstalledLarkPluginRule = class LarkCliMissingForInstalledL
|
|
|
3968
3849
|
}
|
|
3969
3850
|
repair(ctx) {
|
|
3970
3851
|
if (!isTargetForkPlugin(readInstalledLarkPlugin(ctx))) return;
|
|
3971
|
-
if (isLarkCliAvailable()) return;
|
|
3852
|
+
if (isLarkCliAvailable$1()) return;
|
|
3972
3853
|
installLarkCliOnce(ctx.vars.recommendedOpenclawTag ?? TARGET_VERSION);
|
|
3973
3854
|
}
|
|
3974
3855
|
};
|
|
@@ -4421,6 +4302,33 @@ function finalize$1(results, aborted) {
|
|
|
4421
4302
|
};
|
|
4422
4303
|
}
|
|
4423
4304
|
//#endregion
|
|
4305
|
+
//#region src/paths.ts
|
|
4306
|
+
/**
|
|
4307
|
+
* Central directory for all ephemeral diagnose/reset artifacts: task status
|
|
4308
|
+
* files (`reset-<taskId>.json`) and human-readable step logs
|
|
4309
|
+
* (`reset-<taskId>.log`). Having everything under one dir makes debugging a
|
|
4310
|
+
* stuck reset much easier — `ls /tmp/openclaw-diagnose/` shows every recent
|
|
4311
|
+
* run, and each run's log is right next to its state.
|
|
4312
|
+
*/
|
|
4313
|
+
const DIAGNOSE_DIR = "/tmp/openclaw-diagnose";
|
|
4314
|
+
function resetResultFile(taskId) {
|
|
4315
|
+
return `${DIAGNOSE_DIR}/reset-${taskId}.json`;
|
|
4316
|
+
}
|
|
4317
|
+
function resetLogFile(taskId) {
|
|
4318
|
+
return `${DIAGNOSE_DIR}/reset-${taskId}.log`;
|
|
4319
|
+
}
|
|
4320
|
+
/** Sandbox workspace root where openclaw config + agent state lives. */
|
|
4321
|
+
const WORKSPACE_DIR = "/home/gem/workspace/agent";
|
|
4322
|
+
/** File containing the provider key used by the openclaw miaoda provider. */
|
|
4323
|
+
const PROVIDER_FILE_PATH = "/home/gem/workspace/.force/openclaw/miaoda-provider-key";
|
|
4324
|
+
/** File containing the miaoda openclaw secrets JSON. */
|
|
4325
|
+
const SECRETS_FILE_PATH = "/home/gem/workspace/.force/openclaw/miaoda-openclaw-secrets.json";
|
|
4326
|
+
/** Absolute path to the openclaw config JSON. */
|
|
4327
|
+
const CONFIG_PATH = `${WORKSPACE_DIR}/openclaw.json`;
|
|
4328
|
+
function upgradeLarkLogFile(runId) {
|
|
4329
|
+
return `${DIAGNOSE_DIR}/upgrade-lark-${(/* @__PURE__ */ new Date()).toISOString().slice(0, 19).replace(/:/g, "-")}-${runId.slice(0, 8)}.log`;
|
|
4330
|
+
}
|
|
4331
|
+
//#endregion
|
|
4424
4332
|
//#region src/run-log.ts
|
|
4425
4333
|
let currentRunContext;
|
|
4426
4334
|
/**
|
|
@@ -4958,40 +4866,300 @@ function updatePluginInstalls(configPath, installedPkgs) {
|
|
|
4958
4866
|
enabled: true
|
|
4959
4867
|
};
|
|
4960
4868
|
}
|
|
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;
|
|
4869
|
+
const tmpPath = configPath + ".installs-tmp";
|
|
4870
|
+
node_fs.default.writeFileSync(tmpPath, JSON.stringify(config, null, 2), "utf-8");
|
|
4871
|
+
moveSafe(tmpPath, configPath);
|
|
4872
|
+
console.error(`[install-extension] plugins.installs updated: ${updated} entry(ies) in ${configPath}` + (skipped > 0 ? ` (${skipped} package(s) without installMetadata skipped)` : ""));
|
|
4873
|
+
}
|
|
4874
|
+
function installOne$1(pkg, tarball, homeBase) {
|
|
4875
|
+
const destDir = node_path.default.join(homeBase, pkg.installPath);
|
|
4876
|
+
const stagingDir = destDir + ".new";
|
|
4877
|
+
const oldDir = destDir + ".old";
|
|
4878
|
+
node_fs.default.mkdirSync(node_path.default.dirname(destDir), { recursive: true });
|
|
4879
|
+
if (node_fs.default.existsSync(stagingDir)) node_fs.default.rmSync(stagingDir, {
|
|
4880
|
+
recursive: true,
|
|
4881
|
+
force: true
|
|
4882
|
+
});
|
|
4883
|
+
node_fs.default.mkdirSync(stagingDir);
|
|
4884
|
+
try {
|
|
4885
|
+
extractTarballTolerant(tarball, stagingDir, { stripComponents: 1 });
|
|
4886
|
+
if (!node_fs.default.existsSync(node_path.default.join(stagingDir, "package.json"))) throw new Error(`extension tarball missing package.json: ${pkg.name}`);
|
|
4887
|
+
} catch (e) {
|
|
4888
|
+
try {
|
|
4889
|
+
node_fs.default.rmSync(stagingDir, {
|
|
4890
|
+
recursive: true,
|
|
4891
|
+
force: true
|
|
4892
|
+
});
|
|
4893
|
+
} catch {}
|
|
4894
|
+
throw e;
|
|
4895
|
+
}
|
|
4896
|
+
const hadOld = node_fs.default.existsSync(destDir);
|
|
4897
|
+
if (hadOld) moveSafe(destDir, oldDir);
|
|
4898
|
+
moveSafe(stagingDir, destDir);
|
|
4899
|
+
if (hadOld && node_fs.default.existsSync(oldDir)) node_fs.default.rmSync(oldDir, {
|
|
4900
|
+
recursive: true,
|
|
4901
|
+
force: true
|
|
4902
|
+
});
|
|
4903
|
+
}
|
|
4904
|
+
//#endregion
|
|
4905
|
+
//#region src/lark-cli-init.ts
|
|
4906
|
+
const LARK_PLUGIN_NAMES = ["openclaw-lark", "feishu-openclaw-plugin"];
|
|
4907
|
+
const PE_XML_TAG = "lark-cli-pe";
|
|
4908
|
+
const PE_PLACEHOLDER = `
|
|
4909
|
+
<${PE_XML_TAG}>
|
|
4910
|
+
**【强制要求 - 无例外】飞书所有操作必须先读 feishu-lark-cli skill。未读skill不得调用任何飞书工具,违者视为操作失误。**
|
|
4911
|
+
</${PE_XML_TAG}>
|
|
4912
|
+
`;
|
|
4913
|
+
function isLarkPluginInstalled(configPath) {
|
|
4914
|
+
const extDir = getExtensionsDir(configPath);
|
|
4915
|
+
return LARK_PLUGIN_NAMES.some((name) => {
|
|
4916
|
+
try {
|
|
4917
|
+
return node_fs.default.existsSync(node_path.default.join(extDir, name, "package.json"));
|
|
4918
|
+
} catch {
|
|
4919
|
+
return false;
|
|
4920
|
+
}
|
|
4921
|
+
});
|
|
4922
|
+
}
|
|
4923
|
+
function isLarkCliAvailable() {
|
|
4924
|
+
try {
|
|
4925
|
+
return (0, node_child_process.spawnSync)("lark-cli", ["--version"], {
|
|
4926
|
+
encoding: "utf-8",
|
|
4927
|
+
timeout: 5e3,
|
|
4928
|
+
stdio: [
|
|
4929
|
+
"ignore",
|
|
4930
|
+
"pipe",
|
|
4931
|
+
"ignore"
|
|
4932
|
+
]
|
|
4933
|
+
}).status === 0;
|
|
4934
|
+
} catch {
|
|
4935
|
+
return false;
|
|
4936
|
+
}
|
|
4937
|
+
}
|
|
4938
|
+
function readConfig(configPath) {
|
|
4939
|
+
try {
|
|
4940
|
+
const raw = node_fs.default.readFileSync(configPath, "utf-8");
|
|
4941
|
+
const parsed = loadJSON5().parse(raw);
|
|
4942
|
+
return parsed != null && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
|
|
4943
|
+
} catch {
|
|
4944
|
+
return null;
|
|
4945
|
+
}
|
|
4946
|
+
}
|
|
4947
|
+
/**
|
|
4948
|
+
* Resolve the feishu app secret for the given appId.
|
|
4949
|
+
*
|
|
4950
|
+
* Lookup order:
|
|
4951
|
+
* 1. channels.feishu.appSecret (single-agent: feishu.appId === appId)
|
|
4952
|
+
* 2. channels.feishu.accounts[key].appSecret (multi-agent: account.appId === appId)
|
|
4953
|
+
*
|
|
4954
|
+
* Value interpretation:
|
|
4955
|
+
* - string → use directly
|
|
4956
|
+
* - object → secret is managed by a provider; use `feishuAppSecret` param instead
|
|
4957
|
+
*
|
|
4958
|
+
* Returns null when the secret cannot be determined.
|
|
4959
|
+
*/
|
|
4960
|
+
function resolveAppSecret(appId, config, feishuAppSecret) {
|
|
4961
|
+
const feishu = getNestedMap(config, "channels", "feishu");
|
|
4962
|
+
if (!feishu) return null;
|
|
4963
|
+
let rawSecret;
|
|
4964
|
+
if (typeof feishu.appId === "string" && feishu.appId === appId) rawSecret = feishu.appSecret;
|
|
4965
|
+
else {
|
|
4966
|
+
const accounts = asRecord(feishu.accounts);
|
|
4967
|
+
if (accounts) for (const [, val] of Object.entries(accounts)) {
|
|
4968
|
+
const account = asRecord(val);
|
|
4969
|
+
if (account?.appId === appId) {
|
|
4970
|
+
rawSecret = account.appSecret ?? feishu.appSecret;
|
|
4971
|
+
break;
|
|
4972
|
+
}
|
|
4973
|
+
}
|
|
4974
|
+
}
|
|
4975
|
+
if (typeof rawSecret === "string" && rawSecret) return rawSecret;
|
|
4976
|
+
if (rawSecret != null && typeof rawSecret === "object") return feishuAppSecret ?? null;
|
|
4977
|
+
return null;
|
|
4978
|
+
}
|
|
4979
|
+
/**
|
|
4980
|
+
* Resolve the agents.md path for the given appId from the openclaw config.
|
|
4981
|
+
*
|
|
4982
|
+
* Case 1: appId matches channels.feishu.appId (single-agent path)
|
|
4983
|
+
* → WORKSPACE_DIR/AGENTS.md
|
|
4984
|
+
*
|
|
4985
|
+
* Case 2: appId found in channels.feishu.accounts (multi-agent path)
|
|
4986
|
+
* → find account key where account.appId === appId
|
|
4987
|
+
* → find binding where match.channel=feishu && match.accountId=that key
|
|
4988
|
+
* → if agentId === 'main' → WORKSPACE_DIR/agents.md
|
|
4989
|
+
* → else find agent in agents.list by id → agent.workspace/agents.md
|
|
4990
|
+
*
|
|
4991
|
+
* Returns null when the path cannot be determined.
|
|
4992
|
+
*/
|
|
4993
|
+
function resolveAgentsMdPath(appId, config) {
|
|
4994
|
+
const feishu = getNestedMap(config, "channels", "feishu");
|
|
4995
|
+
if (!feishu) {
|
|
4996
|
+
console.error("resolveAgentsMdPath: channels.feishu not found");
|
|
4997
|
+
return null;
|
|
4998
|
+
}
|
|
4999
|
+
if (typeof feishu.appId === "string" && feishu.appId === appId) {
|
|
5000
|
+
console.error(`resolveAgentsMdPath: case=single-agent feishu.appId=${feishu.appId}`);
|
|
5001
|
+
return node_path.default.join(WORKSPACE_DIR, "workspace", "AGENTS.md");
|
|
5002
|
+
}
|
|
5003
|
+
const accounts = asRecord(feishu.accounts);
|
|
5004
|
+
if (!accounts) {
|
|
5005
|
+
console.error("resolveAgentsMdPath: feishu.accounts not found");
|
|
5006
|
+
return null;
|
|
5007
|
+
}
|
|
5008
|
+
let accountId;
|
|
5009
|
+
for (const [key, val] of Object.entries(accounts)) if (asRecord(val)?.appId === appId) {
|
|
5010
|
+
accountId = key;
|
|
5011
|
+
break;
|
|
5012
|
+
}
|
|
5013
|
+
if (!accountId) {
|
|
5014
|
+
console.error(`resolveAgentsMdPath: no account found with appId=${appId} in feishu.accounts keys=[${Object.keys(accounts).join(",")}]`);
|
|
5015
|
+
return null;
|
|
5016
|
+
}
|
|
5017
|
+
console.error(`resolveAgentsMdPath: found accountId=${accountId}`);
|
|
5018
|
+
const bindings = Array.isArray(config.bindings) ? config.bindings : [];
|
|
5019
|
+
let agentId;
|
|
5020
|
+
for (const b of bindings) {
|
|
5021
|
+
const binding = asRecord(b);
|
|
5022
|
+
if (!binding) continue;
|
|
5023
|
+
const match = asRecord(binding.match);
|
|
5024
|
+
if (match?.channel === "feishu" && match?.accountId === accountId) {
|
|
5025
|
+
if (typeof binding.agentId === "string") {
|
|
5026
|
+
agentId = binding.agentId;
|
|
5027
|
+
break;
|
|
5028
|
+
}
|
|
5029
|
+
}
|
|
5030
|
+
}
|
|
5031
|
+
if (!agentId) {
|
|
5032
|
+
console.error(`resolveAgentsMdPath: no binding found for accountId=${accountId} in bindings(count=${bindings.length})`);
|
|
5033
|
+
return null;
|
|
5034
|
+
}
|
|
5035
|
+
console.error(`resolveAgentsMdPath: found agentId=${agentId}`);
|
|
5036
|
+
if (agentId === "main") {
|
|
5037
|
+
console.error("resolveAgentsMdPath: case=multi-agent-main");
|
|
5038
|
+
return node_path.default.join(WORKSPACE_DIR, "workspace", "AGENTS.md");
|
|
5039
|
+
}
|
|
5040
|
+
const agentsObj = asRecord(config.agents);
|
|
5041
|
+
const list = Array.isArray(agentsObj?.list) ? agentsObj.list : [];
|
|
5042
|
+
for (const a of list) {
|
|
5043
|
+
const agent = asRecord(a);
|
|
5044
|
+
if (agent?.id === agentId) {
|
|
5045
|
+
const ws = typeof agent.workspace === "string" ? agent.workspace : node_path.default.join(WORKSPACE_DIR, "workspace");
|
|
5046
|
+
console.error(`resolveAgentsMdPath: case=multi-agent-custom agentId=${agentId} workspace=${ws}`);
|
|
5047
|
+
return node_path.default.join(ws, "AGENTS.md");
|
|
5048
|
+
}
|
|
5049
|
+
}
|
|
5050
|
+
console.error(`resolveAgentsMdPath: agentId=${agentId} not found in agents.list(count=${list.length})`);
|
|
5051
|
+
return null;
|
|
5052
|
+
}
|
|
5053
|
+
function appendPeToAgentsMd(agentsMdPath) {
|
|
5054
|
+
const dir = node_path.default.dirname(agentsMdPath);
|
|
5055
|
+
if (!node_fs.default.existsSync(dir)) node_fs.default.mkdirSync(dir, { recursive: true });
|
|
5056
|
+
const existing = node_fs.default.existsSync(agentsMdPath) ? node_fs.default.readFileSync(agentsMdPath, "utf-8") : "";
|
|
5057
|
+
if (existing.includes(`<${PE_XML_TAG}>`)) {
|
|
5058
|
+
console.error(`lark-cli-init: <${PE_XML_TAG}> already present in ${agentsMdPath}, skipping`);
|
|
5059
|
+
return;
|
|
5060
|
+
}
|
|
5061
|
+
const sep = existing.length > 0 && !existing.endsWith("\n") ? "\n" : "";
|
|
5062
|
+
node_fs.default.appendFileSync(agentsMdPath, `${sep}${PE_PLACEHOLDER}`, "utf-8");
|
|
5063
|
+
console.error(`lark-cli-init: appended PE placeholder to ${agentsMdPath}`);
|
|
5064
|
+
}
|
|
5065
|
+
/**
|
|
5066
|
+
* Collect every Feishu bot appId declared in the openclaw config.
|
|
5067
|
+
* Covers both single-agent (channels.feishu.appId) and multi-agent
|
|
5068
|
+
* (channels.feishu.accounts[*].appId) layouts. Returns a deduplicated list.
|
|
5069
|
+
*/
|
|
5070
|
+
function collectFeishuAppIds(configPath) {
|
|
5071
|
+
const config = readConfig(configPath ?? CONFIG_PATH);
|
|
5072
|
+
if (!config) return [];
|
|
5073
|
+
const feishu = getNestedMap(config, "channels", "feishu");
|
|
5074
|
+
if (!feishu) return [];
|
|
5075
|
+
const appIds = /* @__PURE__ */ new Set();
|
|
5076
|
+
const topAppId = feishu.appId;
|
|
5077
|
+
if (typeof topAppId === "string" && topAppId.trim()) appIds.add(topAppId.trim());
|
|
5078
|
+
const accounts = asRecord(feishu.accounts);
|
|
5079
|
+
if (accounts) for (const val of Object.values(accounts)) {
|
|
5080
|
+
const appId = asRecord(val)?.appId;
|
|
5081
|
+
if (typeof appId === "string" && appId.trim()) appIds.add(appId.trim());
|
|
5082
|
+
}
|
|
5083
|
+
return [...appIds];
|
|
5084
|
+
}
|
|
5085
|
+
function runLarkCliInit(opts) {
|
|
5086
|
+
const configPath = opts.configPath ?? CONFIG_PATH;
|
|
5087
|
+
if (!isLarkPluginInstalled(configPath)) {
|
|
5088
|
+
console.error("lark-cli-init: skipping — openclaw-lark plugin not installed");
|
|
5089
|
+
return {
|
|
5090
|
+
ok: true,
|
|
5091
|
+
skipped: true,
|
|
5092
|
+
skipReason: "openclaw-lark plugin not installed"
|
|
5093
|
+
};
|
|
5094
|
+
}
|
|
5095
|
+
if (!isLarkCliAvailable()) {
|
|
5096
|
+
console.error("lark-cli-init: skipping — lark-cli command not found");
|
|
5097
|
+
return {
|
|
5098
|
+
ok: true,
|
|
5099
|
+
skipped: true,
|
|
5100
|
+
skipReason: "lark-cli command not found"
|
|
5101
|
+
};
|
|
4987
5102
|
}
|
|
4988
|
-
const
|
|
4989
|
-
if (
|
|
4990
|
-
|
|
4991
|
-
|
|
4992
|
-
|
|
4993
|
-
|
|
5103
|
+
const config = readConfig(configPath);
|
|
5104
|
+
if (!config) return {
|
|
5105
|
+
ok: false,
|
|
5106
|
+
error: `could not read config at ${configPath}`
|
|
5107
|
+
};
|
|
5108
|
+
const agentsMdPath = resolveAgentsMdPath(opts.appId, config);
|
|
5109
|
+
console.error(`lark-cli-init: resolved agents.md path=${agentsMdPath ?? "(null)"}`);
|
|
5110
|
+
if (!agentsMdPath) return {
|
|
5111
|
+
ok: false,
|
|
5112
|
+
error: `could not resolve agents.md path for appId=${opts.appId}`
|
|
5113
|
+
};
|
|
5114
|
+
const appSecret = resolveAppSecret(opts.appId, config, opts.feishuAppSecret);
|
|
5115
|
+
if (!appSecret) return {
|
|
5116
|
+
ok: false,
|
|
5117
|
+
error: `could not resolve appSecret for appId=${opts.appId}`
|
|
5118
|
+
};
|
|
5119
|
+
console.error(`lark-cli-init: running lark-cli config init --name ${opts.appId} --app-id ${opts.appId} --brand feishu --app-secret-stdin --force-init`);
|
|
5120
|
+
const initRes = (0, node_child_process.spawnSync)("lark-cli", [
|
|
5121
|
+
"config",
|
|
5122
|
+
"init",
|
|
5123
|
+
"--name",
|
|
5124
|
+
opts.appId,
|
|
5125
|
+
"--app-id",
|
|
5126
|
+
opts.appId,
|
|
5127
|
+
"--brand",
|
|
5128
|
+
"feishu",
|
|
5129
|
+
"--app-secret-stdin",
|
|
5130
|
+
"--force-init"
|
|
5131
|
+
], {
|
|
5132
|
+
stdio: [
|
|
5133
|
+
"pipe",
|
|
5134
|
+
"pipe",
|
|
5135
|
+
"pipe"
|
|
5136
|
+
],
|
|
5137
|
+
encoding: "utf-8",
|
|
5138
|
+
input: appSecret
|
|
4994
5139
|
});
|
|
5140
|
+
const configInitStdout = initRes.stdout?.trim() || void 0;
|
|
5141
|
+
const configInitStderr = initRes.stderr?.trim() || void 0;
|
|
5142
|
+
if (configInitStdout) console.error(`lark-cli config init stdout: ${configInitStdout}`);
|
|
5143
|
+
if (configInitStderr) console.error(`lark-cli config init stderr: ${configInitStderr}`);
|
|
5144
|
+
if (initRes.error) return {
|
|
5145
|
+
ok: false,
|
|
5146
|
+
configInitStdout,
|
|
5147
|
+
configInitStderr,
|
|
5148
|
+
error: `lark-cli config init spawn error: ${initRes.error.message}`
|
|
5149
|
+
};
|
|
5150
|
+
if (initRes.status !== 0) return {
|
|
5151
|
+
ok: false,
|
|
5152
|
+
configInitExitCode: initRes.status ?? void 0,
|
|
5153
|
+
configInitStdout,
|
|
5154
|
+
configInitStderr,
|
|
5155
|
+
error: `lark-cli config init exited with code ${initRes.status}`
|
|
5156
|
+
};
|
|
5157
|
+
appendPeToAgentsMd(agentsMdPath);
|
|
5158
|
+
return {
|
|
5159
|
+
ok: true,
|
|
5160
|
+
configInitExitCode: 0,
|
|
5161
|
+
agentsMdPath
|
|
5162
|
+
};
|
|
4995
5163
|
}
|
|
4996
5164
|
//#endregion
|
|
4997
5165
|
//#region ../../openclaw-slardar/lib/client.js
|
|
@@ -10247,7 +10415,7 @@ async function reportCliRun(opts) {
|
|
|
10247
10415
|
//#region src/help.ts
|
|
10248
10416
|
const BIN = "mclaw-diagnose";
|
|
10249
10417
|
function versionBanner() {
|
|
10250
|
-
return `v0.1.14-alpha.
|
|
10418
|
+
return `v0.1.14-alpha.14`;
|
|
10251
10419
|
}
|
|
10252
10420
|
const COMMANDS = [
|
|
10253
10421
|
{
|
|
@@ -10545,6 +10713,46 @@ OPTIONS
|
|
|
10545
10713
|
EXIT CODES
|
|
10546
10714
|
0 Success or skipped (prerequisites not met).
|
|
10547
10715
|
1 Secret/path unresolvable, lark-cli failed, or config unreadable.
|
|
10716
|
+
`
|
|
10717
|
+
},
|
|
10718
|
+
{
|
|
10719
|
+
name: "upgrade-lark",
|
|
10720
|
+
hidden: false,
|
|
10721
|
+
summary: "Upgrade the Feishu/Lark plugin via @larksuite/openclaw-lark-tools",
|
|
10722
|
+
help: `USAGE
|
|
10723
|
+
${BIN} upgrade-lark [--scene=<scene>] [--caller=<n>] [--trace-id=<id>]
|
|
10724
|
+
|
|
10725
|
+
DESCRIPTION
|
|
10726
|
+
Upgrades the Feishu/Lark plugin by running:
|
|
10727
|
+
npx -y @larksuite/openclaw-lark-tools update --use-existing
|
|
10728
|
+
|
|
10729
|
+
Before the upgrade, the following files are backed up:
|
|
10730
|
+
- openclaw.json
|
|
10731
|
+
- extensions/openclaw-lark/ (if present)
|
|
10732
|
+
- extensions/feishu-openclaw-plugin/ (if present)
|
|
10733
|
+
After the upgrade, the result is validated:
|
|
10734
|
+
- feishu.accounts bot count must not decrease
|
|
10735
|
+
- gateway config structure must remain valid (port/mode/bind/auth/trustedProxies)
|
|
10736
|
+
If the upgrade command fails, or validation fails, the backed-up files are
|
|
10737
|
+
restored to roll back the changes.
|
|
10738
|
+
|
|
10739
|
+
Execution is logged to /tmp/openclaw-diagnose/upgrade-lark-<runId>.log.
|
|
10740
|
+
|
|
10741
|
+
Output is a single JSON object on stdout:
|
|
10742
|
+
{ "ok": true, "stdout": "...", "stderr": "...", "logFile": "..." }
|
|
10743
|
+
{ "ok": false, "error": "...", "stderr": "...", "exitCode": 1,
|
|
10744
|
+
"rollbackOk": true, "validationError": "...", "logFile": "..." }
|
|
10745
|
+
|
|
10746
|
+
OPTIONS
|
|
10747
|
+
--scene=<scene> Telemetry label forwarded to Slardar only.
|
|
10748
|
+
Known values: PageUpgradeLark, etc. Custom strings accepted.
|
|
10749
|
+
--caller=<name> Optional metadata passed to innerapi.
|
|
10750
|
+
--trace-id=<id> Optional log-correlation id.
|
|
10751
|
+
|
|
10752
|
+
EXIT CODES
|
|
10753
|
+
0 Success: upgrade ran and all validations passed.
|
|
10754
|
+
1 Failure: npx error, validation failed, or git commit failed.
|
|
10755
|
+
File rollback was attempted (see rollbackOk in the JSON output).
|
|
10548
10756
|
`
|
|
10549
10757
|
},
|
|
10550
10758
|
{
|
|
@@ -10578,6 +10786,41 @@ EXAMPLES
|
|
|
10578
10786
|
${BIN} rules # all rules
|
|
10579
10787
|
${BIN} rules --rule=gateway # single rule
|
|
10580
10788
|
${BIN} rules --rule=gateway --rule=feishu_channel # multiple rules
|
|
10789
|
+
`
|
|
10790
|
+
},
|
|
10791
|
+
{
|
|
10792
|
+
name: "channels-probe",
|
|
10793
|
+
hidden: true,
|
|
10794
|
+
summary: "Check feishu channel health via openclaw channels status --probe",
|
|
10795
|
+
help: `USAGE
|
|
10796
|
+
${BIN} channels-probe [--timeout=<ms>]
|
|
10797
|
+
|
|
10798
|
+
DESCRIPTION
|
|
10799
|
+
Runs \`openclaw channels status --probe\` and returns a structured JSON
|
|
10800
|
+
summary of whether the current environment's feishu channels are
|
|
10801
|
+
configured and working correctly.
|
|
10802
|
+
|
|
10803
|
+
Output:
|
|
10804
|
+
{
|
|
10805
|
+
"available": true,
|
|
10806
|
+
"gatewayReachable": true,
|
|
10807
|
+
"accounts": [
|
|
10808
|
+
{ "id": "default", "bits": ["enabled","configured","running","works"],
|
|
10809
|
+
"isWorking": true, "raw": "- Feishu default: ..." }
|
|
10810
|
+
],
|
|
10811
|
+
"anyAccountWorking": true
|
|
10812
|
+
}
|
|
10813
|
+
|
|
10814
|
+
An account is considered working when:
|
|
10815
|
+
enabled ∧ configured ∧ ( works ∨ ( running ∧ no error: ∧ no probe failed ) )
|
|
10816
|
+
|
|
10817
|
+
"available": false means the CLI invocation itself failed (openclaw not
|
|
10818
|
+
found, gateway unreachable, or no parseable output returned).
|
|
10819
|
+
|
|
10820
|
+
OPTIONS
|
|
10821
|
+
--timeout=<ms> Max wait in milliseconds (default: 60000). The probe
|
|
10822
|
+
can hang indefinitely on openclaw v2026.4.x due to a
|
|
10823
|
+
missing per-request HTTP timeout — set this accordingly.
|
|
10581
10824
|
`
|
|
10582
10825
|
},
|
|
10583
10826
|
{
|
|
@@ -10753,6 +10996,350 @@ function reportDoctorRunToSlardar(opts) {
|
|
|
10753
10996
|
}
|
|
10754
10997
|
});
|
|
10755
10998
|
}
|
|
10999
|
+
function readLogFile(filePath) {
|
|
11000
|
+
try {
|
|
11001
|
+
return node_fs.default.readFileSync(filePath, "utf-8");
|
|
11002
|
+
} catch {
|
|
11003
|
+
return "";
|
|
11004
|
+
}
|
|
11005
|
+
}
|
|
11006
|
+
function reportUpgradeLarkToSlardar(opts) {
|
|
11007
|
+
console.error(`[slardar] upgrade_lark_run scene=${opts.scene ?? ""} success=${opts.success} exitCode=${opts.exitCode ?? ""} rollbackOk=${opts.rollbackOk ?? ""}`);
|
|
11008
|
+
const logContent = readLogFile(opts.logFile);
|
|
11009
|
+
reportTask({
|
|
11010
|
+
eventName: "upgrade_lark_run",
|
|
11011
|
+
durationMs: opts.durationMs,
|
|
11012
|
+
status: opts.success ? "success" : "failed",
|
|
11013
|
+
extraCategories: {
|
|
11014
|
+
scene: opts.scene ?? "",
|
|
11015
|
+
exit_code: String(opts.exitCode ?? ""),
|
|
11016
|
+
rollback_ok: opts.rollbackOk != null ? String(opts.rollbackOk) : "",
|
|
11017
|
+
validation_error: opts.validationError ?? "",
|
|
11018
|
+
error_msg: opts.error ?? "",
|
|
11019
|
+
log_content: logContent
|
|
11020
|
+
}
|
|
11021
|
+
});
|
|
11022
|
+
}
|
|
11023
|
+
//#endregion
|
|
11024
|
+
//#region src/upgrade-lark.ts
|
|
11025
|
+
/** Plugin directories under extensions/ that are backed up before upgrade */
|
|
11026
|
+
const FEISHU_PLUGIN_DIRS = ["openclaw-lark", "feishu-openclaw-plugin"];
|
|
11027
|
+
function backupFiles(opts) {
|
|
11028
|
+
const { workspaceDir, configPath, backupDir, log } = opts;
|
|
11029
|
+
try {
|
|
11030
|
+
node_fs.default.mkdirSync(backupDir, { recursive: true });
|
|
11031
|
+
log(`backup dir: ${backupDir}`);
|
|
11032
|
+
if (node_fs.default.existsSync(configPath)) {
|
|
11033
|
+
const stat = node_fs.default.statSync(configPath);
|
|
11034
|
+
node_fs.default.copyFileSync(configPath, node_path.default.join(backupDir, "openclaw.json"));
|
|
11035
|
+
log(` backed up: openclaw.json (${stat.size} bytes)`);
|
|
11036
|
+
} else log(` skipped: openclaw.json (not found)`);
|
|
11037
|
+
const extSrc = node_path.default.join(workspaceDir, "extensions");
|
|
11038
|
+
for (const pluginDir of FEISHU_PLUGIN_DIRS) {
|
|
11039
|
+
const src = node_path.default.join(extSrc, pluginDir);
|
|
11040
|
+
if (node_fs.default.existsSync(src)) {
|
|
11041
|
+
const dst = node_path.default.join(backupDir, "extensions", pluginDir);
|
|
11042
|
+
node_fs.default.cpSync(src, dst, { recursive: true });
|
|
11043
|
+
const version = readPkgVersion(node_path.default.join(src, "package.json"));
|
|
11044
|
+
log(` backed up: extensions/${pluginDir}${version ? ` (version: ${version})` : ""}`);
|
|
11045
|
+
} else log(` skipped: extensions/${pluginDir} (not found)`);
|
|
11046
|
+
}
|
|
11047
|
+
return { ok: true };
|
|
11048
|
+
} catch (e) {
|
|
11049
|
+
return {
|
|
11050
|
+
ok: false,
|
|
11051
|
+
error: `backup failed: ${e.message}`
|
|
11052
|
+
};
|
|
11053
|
+
}
|
|
11054
|
+
}
|
|
11055
|
+
function restoreFiles(opts) {
|
|
11056
|
+
const { workspaceDir, configPath, backupDir, log } = opts;
|
|
11057
|
+
try {
|
|
11058
|
+
const configBackup = node_path.default.join(backupDir, "openclaw.json");
|
|
11059
|
+
if (node_fs.default.existsSync(configBackup)) {
|
|
11060
|
+
node_fs.default.copyFileSync(configBackup, configPath);
|
|
11061
|
+
log(` restored: openclaw.json`);
|
|
11062
|
+
}
|
|
11063
|
+
const extDst = node_path.default.join(workspaceDir, "extensions");
|
|
11064
|
+
for (const pluginDir of FEISHU_PLUGIN_DIRS) {
|
|
11065
|
+
const backupSrc = node_path.default.join(backupDir, "extensions", pluginDir);
|
|
11066
|
+
if (node_fs.default.existsSync(backupSrc)) {
|
|
11067
|
+
const dst = node_path.default.join(extDst, pluginDir);
|
|
11068
|
+
if (node_fs.default.existsSync(dst)) node_fs.default.rmSync(dst, {
|
|
11069
|
+
recursive: true,
|
|
11070
|
+
force: true
|
|
11071
|
+
});
|
|
11072
|
+
node_fs.default.cpSync(backupSrc, dst, { recursive: true });
|
|
11073
|
+
log(` restored: extensions/${pluginDir}`);
|
|
11074
|
+
}
|
|
11075
|
+
}
|
|
11076
|
+
return true;
|
|
11077
|
+
} catch (e) {
|
|
11078
|
+
log(` restore error: ${e.message}`);
|
|
11079
|
+
return false;
|
|
11080
|
+
}
|
|
11081
|
+
}
|
|
11082
|
+
function readPkgVersion(pkgPath) {
|
|
11083
|
+
try {
|
|
11084
|
+
const pkg = JSON.parse(node_fs.default.readFileSync(pkgPath, "utf-8"));
|
|
11085
|
+
return typeof pkg.version === "string" ? pkg.version : null;
|
|
11086
|
+
} catch {
|
|
11087
|
+
return null;
|
|
11088
|
+
}
|
|
11089
|
+
}
|
|
11090
|
+
function snapshotVersions(cwd, log) {
|
|
11091
|
+
const ocResult = (0, node_child_process.spawnSync)("openclaw", ["--version"], {
|
|
11092
|
+
cwd,
|
|
11093
|
+
encoding: "utf-8",
|
|
11094
|
+
stdio: [
|
|
11095
|
+
"ignore",
|
|
11096
|
+
"pipe",
|
|
11097
|
+
"pipe"
|
|
11098
|
+
],
|
|
11099
|
+
timeout: 5e3
|
|
11100
|
+
});
|
|
11101
|
+
const ocRaw = (ocResult.stdout ?? "").trim() || (ocResult.stderr ?? "").trim();
|
|
11102
|
+
const extDir = node_path.default.join(cwd, "extensions");
|
|
11103
|
+
const larkPkg = node_path.default.join(extDir, "openclaw-lark", "package.json");
|
|
11104
|
+
const feishuPkg = node_path.default.join(extDir, "feishu-openclaw-plugin", "package.json");
|
|
11105
|
+
log(` version-check paths: ${larkPkg} [${node_fs.default.existsSync(larkPkg) ? "exists" : "missing"}]`);
|
|
11106
|
+
log(` version-check paths: ${feishuPkg} [${node_fs.default.existsSync(feishuPkg) ? "exists" : "missing"}]`);
|
|
11107
|
+
return {
|
|
11108
|
+
openclaw: ocRaw || null,
|
|
11109
|
+
openclawLark: readPkgVersion(larkPkg),
|
|
11110
|
+
feishuOpenclawPlugin: readPkgVersion(feishuPkg)
|
|
11111
|
+
};
|
|
11112
|
+
}
|
|
11113
|
+
function logVersionSnapshot(label, v, log) {
|
|
11114
|
+
log(`${label}: openclaw=${v.openclaw ?? "n/a"} openclaw-lark=${v.openclawLark ?? "n/a"} feishu-openclaw-plugin=${v.feishuOpenclawPlugin ?? "n/a"}`);
|
|
11115
|
+
}
|
|
11116
|
+
function countFeishuBots(configPath) {
|
|
11117
|
+
try {
|
|
11118
|
+
const raw = node_fs.default.readFileSync(configPath, "utf-8");
|
|
11119
|
+
const config = loadJSON5().parse(raw);
|
|
11120
|
+
const accounts = getNestedMap(config, "channels", "feishu", "accounts");
|
|
11121
|
+
if (accounts) return Object.keys(accounts).length;
|
|
11122
|
+
const feishu = getNestedMap(config, "channels", "feishu");
|
|
11123
|
+
return typeof feishu?.appId === "string" && feishu.appId ? 1 : 0;
|
|
11124
|
+
} catch {
|
|
11125
|
+
return 0;
|
|
11126
|
+
}
|
|
11127
|
+
}
|
|
11128
|
+
/** Run channels probe, log results, and return the result. Never throws. */
|
|
11129
|
+
function probeChannels(label, log, timeoutMs) {
|
|
11130
|
+
try {
|
|
11131
|
+
const r = runChannelsProbe(timeoutMs);
|
|
11132
|
+
log(` ${label} available=${r.available} anyAccountWorking=${r.anyAccountWorking}`);
|
|
11133
|
+
if (r.error) log(` ${label} error: ${r.error}`);
|
|
11134
|
+
if (r.gatewayReachable != null) log(` ${label} gatewayReachable: ${r.gatewayReachable}`);
|
|
11135
|
+
for (const acct of r.accounts ?? []) log(` ${label} account ${acct.id}: isWorking=${acct.isWorking} bits=[${acct.bits.join(",")}]`);
|
|
11136
|
+
return r;
|
|
11137
|
+
} catch (e) {
|
|
11138
|
+
log(` ${label} channels probe threw: ${e.message}`);
|
|
11139
|
+
return {
|
|
11140
|
+
available: false,
|
|
11141
|
+
gatewayReachable: false,
|
|
11142
|
+
feishuConfigInvalid: false,
|
|
11143
|
+
accounts: [],
|
|
11144
|
+
anyAccountWorking: false
|
|
11145
|
+
};
|
|
11146
|
+
}
|
|
11147
|
+
}
|
|
11148
|
+
function runUpgradeLark(opts) {
|
|
11149
|
+
const cwd = opts.cwd ?? "/home/gem/workspace/agent";
|
|
11150
|
+
const configPath = opts.configPath ?? CONFIG_PATH;
|
|
11151
|
+
const logFile = upgradeLarkLogFile(opts.runId);
|
|
11152
|
+
const log = makeLogger(logFile);
|
|
11153
|
+
const fsOpts = {
|
|
11154
|
+
workspaceDir: cwd,
|
|
11155
|
+
configPath,
|
|
11156
|
+
backupDir: node_path.default.join(opts.backupBaseDir ?? "/tmp/openclaw-diagnose", `upgrade-lark-backup-${opts.runId}`),
|
|
11157
|
+
log
|
|
11158
|
+
};
|
|
11159
|
+
const cliScript = opts.cliScript ?? process.argv[1];
|
|
11160
|
+
const statusCheckDelayMs = opts.statusCheckDelayMs ?? 5e3;
|
|
11161
|
+
log(`${"=".repeat(60)}`);
|
|
11162
|
+
log(`upgrade-lark started runId=${opts.runId}`);
|
|
11163
|
+
log(` cwd : ${cwd}`);
|
|
11164
|
+
log(` configPath : ${configPath}`);
|
|
11165
|
+
log(`${"=".repeat(60)}`);
|
|
11166
|
+
log("");
|
|
11167
|
+
log("── [Pre-check A] channels probe(升级前)────────────────");
|
|
11168
|
+
const beforeChannels = probeChannels("before", log, 6e4);
|
|
11169
|
+
log("");
|
|
11170
|
+
log("── [Pre-check B] 版本兼容预检 ───────────────────────────");
|
|
11171
|
+
let versionIncompatible = false;
|
|
11172
|
+
try {
|
|
11173
|
+
const rawConfig = node_fs.default.readFileSync(configPath, "utf-8");
|
|
11174
|
+
versionIncompatible = needsLarkUpgrade({
|
|
11175
|
+
config: loadJSON5().parse(rawConfig),
|
|
11176
|
+
configPath,
|
|
11177
|
+
vars: {},
|
|
11178
|
+
providerDeps: {
|
|
11179
|
+
usesMiaodaProvider: false,
|
|
11180
|
+
usesMiaodaSecretProvider: false
|
|
11181
|
+
}
|
|
11182
|
+
});
|
|
11183
|
+
log(` version-compat pre-check: ${versionIncompatible ? "NEEDS_UPGRADE" : "ok"}`);
|
|
11184
|
+
} catch (e) {
|
|
11185
|
+
log(` version-compat pre-check error: ${e.message} — version signal unavailable`);
|
|
11186
|
+
}
|
|
11187
|
+
const feishuConfigInvalid = beforeChannels.feishuConfigInvalid;
|
|
11188
|
+
log(` feishu config invalid : ${feishuConfigInvalid}`);
|
|
11189
|
+
log("");
|
|
11190
|
+
log("── [Gate] 升级前置条件检查 ───────────────────────────────");
|
|
11191
|
+
log(` versionIncompatible : ${versionIncompatible}`);
|
|
11192
|
+
log(` feishuConfigInvalid : ${feishuConfigInvalid}`);
|
|
11193
|
+
log(` channels working before: ${beforeChannels.anyAccountWorking}`);
|
|
11194
|
+
if (!(versionIncompatible || feishuConfigInvalid)) {
|
|
11195
|
+
const reason = "version compatible and feishu channel config valid — upgrade not needed";
|
|
11196
|
+
log(` SKIP: ${reason}`);
|
|
11197
|
+
log(`${"=".repeat(60)}`);
|
|
11198
|
+
log("upgrade-lark skipped (pre-check gate)");
|
|
11199
|
+
log(`${"=".repeat(60)}`);
|
|
11200
|
+
return {
|
|
11201
|
+
ok: true,
|
|
11202
|
+
skipped: true,
|
|
11203
|
+
skipReason: reason,
|
|
11204
|
+
logFile
|
|
11205
|
+
};
|
|
11206
|
+
}
|
|
11207
|
+
if (beforeChannels.anyAccountWorking) {
|
|
11208
|
+
const reason = "channels are working — upgrade not needed (issue detected but system is functional)";
|
|
11209
|
+
log(` SKIP: ${reason}`);
|
|
11210
|
+
log(`${"=".repeat(60)}`);
|
|
11211
|
+
log("upgrade-lark skipped (pre-check gate)");
|
|
11212
|
+
log(`${"=".repeat(60)}`);
|
|
11213
|
+
return {
|
|
11214
|
+
ok: true,
|
|
11215
|
+
skipped: true,
|
|
11216
|
+
skipReason: reason,
|
|
11217
|
+
logFile
|
|
11218
|
+
};
|
|
11219
|
+
}
|
|
11220
|
+
log(` PROCEED: requiresLarkUpgrade=true (version=${versionIncompatible}, feishuConfig=${feishuConfigInvalid}) AND channels not working → running upgrade`);
|
|
11221
|
+
log("");
|
|
11222
|
+
log("── [1/6] 文件备份 ────────────────────────────────────────");
|
|
11223
|
+
log(`before-state: botCount=${countFeishuBots(configPath)}`);
|
|
11224
|
+
const backup = backupFiles(fsOpts);
|
|
11225
|
+
if (!backup.ok) {
|
|
11226
|
+
log(`ERROR: ${backup.error}`);
|
|
11227
|
+
return {
|
|
11228
|
+
ok: false,
|
|
11229
|
+
error: backup.error,
|
|
11230
|
+
logFile
|
|
11231
|
+
};
|
|
11232
|
+
}
|
|
11233
|
+
log("backup: ok");
|
|
11234
|
+
logVersionSnapshot("before-versions", snapshotVersions(cwd, log), log);
|
|
11235
|
+
log("");
|
|
11236
|
+
log("── [2/6] 清理本地 openclaw shim ─────────────────────────");
|
|
11237
|
+
const localOpenclawBin = node_path.default.join(cwd, "node_modules", ".bin", "openclaw");
|
|
11238
|
+
if (node_fs.default.existsSync(localOpenclawBin)) try {
|
|
11239
|
+
node_fs.default.rmSync(localOpenclawBin);
|
|
11240
|
+
log(` removed: ${localOpenclawBin}`);
|
|
11241
|
+
} catch (e) {
|
|
11242
|
+
log(` WARN: failed to remove ${localOpenclawBin}: ${e.message}`);
|
|
11243
|
+
}
|
|
11244
|
+
else log(` skipped: ${localOpenclawBin} (not found)`);
|
|
11245
|
+
log("");
|
|
11246
|
+
log("── [3/6] npx install (@larksuite/openclaw-lark-tools update) ──");
|
|
11247
|
+
const npxResult = (0, node_child_process.spawnSync)("npx", [
|
|
11248
|
+
"-y",
|
|
11249
|
+
"@larksuite/openclaw-lark-tools",
|
|
11250
|
+
"update"
|
|
11251
|
+
], {
|
|
11252
|
+
cwd,
|
|
11253
|
+
encoding: "utf-8",
|
|
11254
|
+
stdio: [
|
|
11255
|
+
"ignore",
|
|
11256
|
+
"pipe",
|
|
11257
|
+
"pipe"
|
|
11258
|
+
],
|
|
11259
|
+
timeout: 12e4
|
|
11260
|
+
});
|
|
11261
|
+
const npxStdout = npxResult.stdout?.trim() ?? "";
|
|
11262
|
+
const npxStderr = npxResult.stderr?.trim() ?? "";
|
|
11263
|
+
const npxExitCode = npxResult.status ?? 1;
|
|
11264
|
+
if (npxStdout) log(`npx stdout:\n${npxStdout}`);
|
|
11265
|
+
if (npxStderr) log(`npx stderr:\n${npxStderr}`);
|
|
11266
|
+
log(`npx exit: ${npxExitCode}${npxResult.error ? ` error: ${npxResult.error.message}` : ""}`);
|
|
11267
|
+
if (statusCheckDelayMs > 0) {
|
|
11268
|
+
log("");
|
|
11269
|
+
log(`── 等待 ${statusCheckDelayMs / 1e3}s(让 openclaw 服务完成重启) ─────────────`);
|
|
11270
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, statusCheckDelayMs);
|
|
11271
|
+
log("wait done");
|
|
11272
|
+
}
|
|
11273
|
+
const doRollback = (reason) => {
|
|
11274
|
+
log(`ERROR: ${reason}`);
|
|
11275
|
+
const rollbackOk = restoreFiles(fsOpts);
|
|
11276
|
+
log(`rollback: ${rollbackOk ? "ok" : "FAILED"}`);
|
|
11277
|
+
return {
|
|
11278
|
+
ok: false,
|
|
11279
|
+
error: reason,
|
|
11280
|
+
validationError: reason,
|
|
11281
|
+
stdout: npxStdout,
|
|
11282
|
+
stderr: npxStderr,
|
|
11283
|
+
exitCode: npxExitCode,
|
|
11284
|
+
rollbackOk,
|
|
11285
|
+
logFile
|
|
11286
|
+
};
|
|
11287
|
+
};
|
|
11288
|
+
log("");
|
|
11289
|
+
log("── [4/5] 安装后诊断校验 ─────────────────────────────────");
|
|
11290
|
+
logVersionSnapshot("after-versions", snapshotVersions(cwd, log), log);
|
|
11291
|
+
let afterVersionIncompatible = false;
|
|
11292
|
+
try {
|
|
11293
|
+
const rawConfig = node_fs.default.readFileSync(configPath, "utf-8");
|
|
11294
|
+
afterVersionIncompatible = needsLarkUpgrade({
|
|
11295
|
+
config: loadJSON5().parse(rawConfig),
|
|
11296
|
+
configPath,
|
|
11297
|
+
vars: {},
|
|
11298
|
+
providerDeps: {
|
|
11299
|
+
usesMiaodaProvider: false,
|
|
11300
|
+
usesMiaodaSecretProvider: false
|
|
11301
|
+
}
|
|
11302
|
+
});
|
|
11303
|
+
log(` version-compat post-check: ${afterVersionIncompatible ? "STILL_INCOMPATIBLE" : "ok"}`);
|
|
11304
|
+
} catch (e) {
|
|
11305
|
+
log(` version-compat post-check error: ${e.message} — version signal unavailable`);
|
|
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
|
+
}
|
|
10756
11343
|
//#endregion
|
|
10757
11344
|
//#region src/index.ts
|
|
10758
11345
|
const args = node_process.default.argv.slice(2);
|
|
@@ -11270,6 +11857,50 @@ async function main() {
|
|
|
11270
11857
|
if (!result.ok) node_process.default.exit(1);
|
|
11271
11858
|
break;
|
|
11272
11859
|
}
|
|
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
|
+
}
|
|
11273
11904
|
default:
|
|
11274
11905
|
node_process.default.stderr.write(`Unknown command: ${mode}\n\n`);
|
|
11275
11906
|
node_process.default.stderr.write(formatTopLevelHelp(helpFlags.expert));
|