@lark-apaas/openclaw-scripts-diagnose-cli 0.1.15-alpha.2 → 0.1.15-alpha.4
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 +298 -1049
- 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.15-alpha.
|
|
55
|
+
return "0.1.15-alpha.4";
|
|
56
56
|
}
|
|
57
57
|
//#endregion
|
|
58
58
|
//#region src/rule-engine/base.ts
|
|
@@ -2520,341 +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
|
-
/** upgrade-lark 每次运行的日志文件路径,含时间戳便于按时间排序定位。 */
|
|
2547
|
-
function upgradeLarkLogFile(runId) {
|
|
2548
|
-
return `${DIAGNOSE_DIR}/upgrade-lark-${(/* @__PURE__ */ new Date()).toISOString().slice(0, 19).replace(/:/g, "-")}-${runId.slice(0, 8)}.log`;
|
|
2549
|
-
}
|
|
2550
|
-
//#endregion
|
|
2551
|
-
//#region src/lark-cli-init.ts
|
|
2552
|
-
const LARK_PLUGIN_NAMES$1 = ["openclaw-lark", "feishu-openclaw-plugin"];
|
|
2553
|
-
const PE_XML_TAG = "lark-cli-pe";
|
|
2554
|
-
const PE_PLACEHOLDER = `
|
|
2555
|
-
<${PE_XML_TAG}>
|
|
2556
|
-
**【强制要求 - 无例外】飞书所有操作必须先读 feishu-lark-cli skill。未读skill不得调用任何飞书工具,违者视为操作失误。**
|
|
2557
|
-
</${PE_XML_TAG}>
|
|
2558
|
-
`;
|
|
2559
|
-
function isLarkPluginInstalled(configPath) {
|
|
2560
|
-
const extDir = getExtensionsDir(configPath);
|
|
2561
|
-
return LARK_PLUGIN_NAMES$1.some((name) => {
|
|
2562
|
-
try {
|
|
2563
|
-
return node_fs.default.existsSync(node_path.default.join(extDir, name, "package.json"));
|
|
2564
|
-
} catch {
|
|
2565
|
-
return false;
|
|
2566
|
-
}
|
|
2567
|
-
});
|
|
2568
|
-
}
|
|
2569
|
-
function isLarkCliAvailable$2() {
|
|
2570
|
-
try {
|
|
2571
|
-
return (0, node_child_process.spawnSync)("lark-cli", ["--version"], {
|
|
2572
|
-
encoding: "utf-8",
|
|
2573
|
-
timeout: 5e3,
|
|
2574
|
-
stdio: [
|
|
2575
|
-
"ignore",
|
|
2576
|
-
"pipe",
|
|
2577
|
-
"ignore"
|
|
2578
|
-
]
|
|
2579
|
-
}).status === 0;
|
|
2580
|
-
} catch {
|
|
2581
|
-
return false;
|
|
2582
|
-
}
|
|
2583
|
-
}
|
|
2584
|
-
function readConfig(configPath) {
|
|
2585
|
-
try {
|
|
2586
|
-
const raw = node_fs.default.readFileSync(configPath, "utf-8");
|
|
2587
|
-
const parsed = loadJSON5().parse(raw);
|
|
2588
|
-
return parsed != null && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
|
|
2589
|
-
} catch {
|
|
2590
|
-
return null;
|
|
2591
|
-
}
|
|
2592
|
-
}
|
|
2593
|
-
/**
|
|
2594
|
-
* Resolve the feishu app secret for the given appId.
|
|
2595
|
-
*
|
|
2596
|
-
* Lookup order:
|
|
2597
|
-
* 1. channels.feishu.appSecret (single-agent: feishu.appId === appId)
|
|
2598
|
-
* 2. channels.feishu.accounts[key].appSecret (multi-agent: account.appId === appId)
|
|
2599
|
-
*
|
|
2600
|
-
* Value interpretation:
|
|
2601
|
-
* - string → use directly
|
|
2602
|
-
* - object → secret is managed by a provider; use `feishuAppSecret` param instead
|
|
2603
|
-
*
|
|
2604
|
-
* Returns null when the secret cannot be determined.
|
|
2605
|
-
*/
|
|
2606
|
-
function resolveAppSecret(appId, config, feishuAppSecret) {
|
|
2607
|
-
const feishu = getNestedMap(config, "channels", "feishu");
|
|
2608
|
-
if (!feishu) return null;
|
|
2609
|
-
let rawSecret;
|
|
2610
|
-
if (typeof feishu.appId === "string" && feishu.appId === appId) rawSecret = feishu.appSecret;
|
|
2611
|
-
else {
|
|
2612
|
-
const accounts = asRecord(feishu.accounts);
|
|
2613
|
-
if (accounts) for (const [, val] of Object.entries(accounts)) {
|
|
2614
|
-
const account = asRecord(val);
|
|
2615
|
-
if (account?.appId === appId) {
|
|
2616
|
-
rawSecret = account.appSecret ?? feishu.appSecret;
|
|
2617
|
-
break;
|
|
2618
|
-
}
|
|
2619
|
-
}
|
|
2620
|
-
}
|
|
2621
|
-
if (typeof rawSecret === "string" && rawSecret) return rawSecret;
|
|
2622
|
-
if (rawSecret != null && typeof rawSecret === "object") return feishuAppSecret ?? null;
|
|
2623
|
-
return null;
|
|
2624
|
-
}
|
|
2625
|
-
/**
|
|
2626
|
-
* Resolve the agents.md path for the given appId from the openclaw config.
|
|
2627
|
-
*
|
|
2628
|
-
* Case 1: appId matches channels.feishu.appId (single-agent path)
|
|
2629
|
-
* → WORKSPACE_DIR/AGENTS.md
|
|
2630
|
-
*
|
|
2631
|
-
* Case 2: appId found in channels.feishu.accounts (multi-agent path)
|
|
2632
|
-
* → find account key where account.appId === appId
|
|
2633
|
-
* → find binding where match.channel=feishu && match.accountId=that key
|
|
2634
|
-
* → if agentId === 'main' → WORKSPACE_DIR/agents.md
|
|
2635
|
-
* → else find agent in agents.list by id → agent.workspace/agents.md
|
|
2636
|
-
*
|
|
2637
|
-
* Returns null when the path cannot be determined.
|
|
2638
|
-
*/
|
|
2639
|
-
function resolveAgentsMdPath(appId, config) {
|
|
2640
|
-
const feishu = getNestedMap(config, "channels", "feishu");
|
|
2641
|
-
if (!feishu) {
|
|
2642
|
-
console.error("resolveAgentsMdPath: channels.feishu not found");
|
|
2643
|
-
return null;
|
|
2644
|
-
}
|
|
2645
|
-
if (typeof feishu.appId === "string" && feishu.appId === appId) {
|
|
2646
|
-
console.error(`resolveAgentsMdPath: case=single-agent feishu.appId=${feishu.appId}`);
|
|
2647
|
-
return node_path.default.join(WORKSPACE_DIR, "workspace", "AGENTS.md");
|
|
2648
|
-
}
|
|
2649
|
-
const accounts = asRecord(feishu.accounts);
|
|
2650
|
-
if (!accounts) {
|
|
2651
|
-
console.error("resolveAgentsMdPath: feishu.accounts not found");
|
|
2652
|
-
return null;
|
|
2653
|
-
}
|
|
2654
|
-
let accountId;
|
|
2655
|
-
for (const [key, val] of Object.entries(accounts)) if (asRecord(val)?.appId === appId) {
|
|
2656
|
-
accountId = key;
|
|
2657
|
-
break;
|
|
2658
|
-
}
|
|
2659
|
-
if (!accountId) {
|
|
2660
|
-
console.error(`resolveAgentsMdPath: no account found with appId=${appId} in feishu.accounts keys=[${Object.keys(accounts).join(",")}]`);
|
|
2661
|
-
return null;
|
|
2662
|
-
}
|
|
2663
|
-
console.error(`resolveAgentsMdPath: found accountId=${accountId}`);
|
|
2664
|
-
const bindings = Array.isArray(config.bindings) ? config.bindings : [];
|
|
2665
|
-
let agentId;
|
|
2666
|
-
for (const b of bindings) {
|
|
2667
|
-
const binding = asRecord(b);
|
|
2668
|
-
if (!binding) continue;
|
|
2669
|
-
const match = asRecord(binding.match);
|
|
2670
|
-
if (match?.channel === "feishu" && match?.accountId === accountId) {
|
|
2671
|
-
if (typeof binding.agentId === "string") {
|
|
2672
|
-
agentId = binding.agentId;
|
|
2673
|
-
break;
|
|
2674
|
-
}
|
|
2675
|
-
}
|
|
2676
|
-
}
|
|
2677
|
-
if (!agentId) {
|
|
2678
|
-
console.error(`resolveAgentsMdPath: no binding found for accountId=${accountId} in bindings(count=${bindings.length})`);
|
|
2679
|
-
return null;
|
|
2680
|
-
}
|
|
2681
|
-
console.error(`resolveAgentsMdPath: found agentId=${agentId}`);
|
|
2682
|
-
if (agentId === "main") {
|
|
2683
|
-
console.error("resolveAgentsMdPath: case=multi-agent-main");
|
|
2684
|
-
return node_path.default.join(WORKSPACE_DIR, "workspace", "AGENTS.md");
|
|
2685
|
-
}
|
|
2686
|
-
const agentsObj = asRecord(config.agents);
|
|
2687
|
-
const list = Array.isArray(agentsObj?.list) ? agentsObj.list : [];
|
|
2688
|
-
for (const a of list) {
|
|
2689
|
-
const agent = asRecord(a);
|
|
2690
|
-
if (agent?.id === agentId) {
|
|
2691
|
-
const ws = typeof agent.workspace === "string" ? agent.workspace : node_path.default.join(WORKSPACE_DIR, "workspace");
|
|
2692
|
-
console.error(`resolveAgentsMdPath: case=multi-agent-custom agentId=${agentId} workspace=${ws}`);
|
|
2693
|
-
return node_path.default.join(ws, "AGENTS.md");
|
|
2694
|
-
}
|
|
2695
|
-
}
|
|
2696
|
-
console.error(`resolveAgentsMdPath: agentId=${agentId} not found in agents.list(count=${list.length})`);
|
|
2697
|
-
return null;
|
|
2698
|
-
}
|
|
2699
|
-
function appendPeToAgentsMd(agentsMdPath) {
|
|
2700
|
-
const dir = node_path.default.dirname(agentsMdPath);
|
|
2701
|
-
if (!node_fs.default.existsSync(dir)) node_fs.default.mkdirSync(dir, { recursive: true });
|
|
2702
|
-
const existing = node_fs.default.existsSync(agentsMdPath) ? node_fs.default.readFileSync(agentsMdPath, "utf-8") : "";
|
|
2703
|
-
if (existing.includes(`<lark-cli-pe>`)) {
|
|
2704
|
-
console.error(`lark-cli-init: <${PE_XML_TAG}> already present in ${agentsMdPath}, skipping`);
|
|
2705
|
-
return;
|
|
2706
|
-
}
|
|
2707
|
-
const sep = existing.length > 0 && !existing.endsWith("\n") ? "\n" : "";
|
|
2708
|
-
node_fs.default.appendFileSync(agentsMdPath, `${sep}${PE_PLACEHOLDER}`, "utf-8");
|
|
2709
|
-
console.error(`lark-cli-init: appended PE placeholder to ${agentsMdPath}`);
|
|
2710
|
-
}
|
|
2711
|
-
/**
|
|
2712
|
-
* Collect every Feishu bot appId declared in the openclaw config.
|
|
2713
|
-
* Covers both single-agent (channels.feishu.appId) and multi-agent
|
|
2714
|
-
* (channels.feishu.accounts[*].appId) layouts. Returns a deduplicated list.
|
|
2715
|
-
*/
|
|
2716
|
-
function collectFeishuAppIds(configPath) {
|
|
2717
|
-
const config = readConfig(configPath ?? CONFIG_PATH);
|
|
2718
|
-
if (!config) return [];
|
|
2719
|
-
const feishu = getNestedMap(config, "channels", "feishu");
|
|
2720
|
-
if (!feishu) return [];
|
|
2721
|
-
const appIds = /* @__PURE__ */ new Set();
|
|
2722
|
-
const topAppId = feishu.appId;
|
|
2723
|
-
if (typeof topAppId === "string" && topAppId.trim()) appIds.add(topAppId.trim());
|
|
2724
|
-
const accounts = asRecord(feishu.accounts);
|
|
2725
|
-
if (accounts) for (const val of Object.values(accounts)) {
|
|
2726
|
-
const appId = asRecord(val)?.appId;
|
|
2727
|
-
if (typeof appId === "string" && appId.trim()) appIds.add(appId.trim());
|
|
2728
|
-
}
|
|
2729
|
-
return [...appIds];
|
|
2730
|
-
}
|
|
2731
|
-
function runLarkCliInit(opts) {
|
|
2732
|
-
const configPath = opts.configPath ?? CONFIG_PATH;
|
|
2733
|
-
if (!isLarkPluginInstalled(configPath)) {
|
|
2734
|
-
console.error("lark-cli-init: skipping — openclaw-lark plugin not installed");
|
|
2735
|
-
return {
|
|
2736
|
-
ok: true,
|
|
2737
|
-
skipped: true,
|
|
2738
|
-
skipReason: "openclaw-lark plugin not installed"
|
|
2739
|
-
};
|
|
2740
|
-
}
|
|
2741
|
-
if (!isLarkCliAvailable$2()) {
|
|
2742
|
-
console.error("lark-cli-init: skipping — lark-cli command not found");
|
|
2743
|
-
return {
|
|
2744
|
-
ok: true,
|
|
2745
|
-
skipped: true,
|
|
2746
|
-
skipReason: "lark-cli command not found"
|
|
2747
|
-
};
|
|
2748
|
-
}
|
|
2749
|
-
const config = readConfig(configPath);
|
|
2750
|
-
if (!config) return {
|
|
2751
|
-
ok: false,
|
|
2752
|
-
error: `could not read config at ${configPath}`
|
|
2753
|
-
};
|
|
2754
|
-
const agentsMdPath = resolveAgentsMdPath(opts.appId, config);
|
|
2755
|
-
console.error(`lark-cli-init: resolved agents.md path=${agentsMdPath ?? "(null)"}`);
|
|
2756
|
-
if (!agentsMdPath) return {
|
|
2757
|
-
ok: false,
|
|
2758
|
-
error: `could not resolve agents.md path for appId=${opts.appId}`
|
|
2759
|
-
};
|
|
2760
|
-
const appSecret = resolveAppSecret(opts.appId, config, opts.feishuAppSecret);
|
|
2761
|
-
if (!appSecret) return {
|
|
2762
|
-
ok: false,
|
|
2763
|
-
error: `could not resolve appSecret for appId=${opts.appId}`
|
|
2764
|
-
};
|
|
2765
|
-
console.error(`lark-cli-init: running lark-cli config init --name ${opts.appId} --app-id ${opts.appId} --brand feishu --app-secret-stdin --force-init`);
|
|
2766
|
-
const initRes = (0, node_child_process.spawnSync)("lark-cli", [
|
|
2767
|
-
"config",
|
|
2768
|
-
"init",
|
|
2769
|
-
"--name",
|
|
2770
|
-
opts.appId,
|
|
2771
|
-
"--app-id",
|
|
2772
|
-
opts.appId,
|
|
2773
|
-
"--brand",
|
|
2774
|
-
"feishu",
|
|
2775
|
-
"--app-secret-stdin",
|
|
2776
|
-
"--force-init"
|
|
2777
|
-
], {
|
|
2778
|
-
stdio: [
|
|
2779
|
-
"pipe",
|
|
2780
|
-
"pipe",
|
|
2781
|
-
"pipe"
|
|
2782
|
-
],
|
|
2783
|
-
encoding: "utf-8",
|
|
2784
|
-
input: appSecret
|
|
2785
|
-
});
|
|
2786
|
-
const configInitStdout = initRes.stdout?.trim() || void 0;
|
|
2787
|
-
const configInitStderr = initRes.stderr?.trim() || void 0;
|
|
2788
|
-
if (configInitStdout) console.error(`lark-cli config init stdout: ${configInitStdout}`);
|
|
2789
|
-
if (configInitStderr) console.error(`lark-cli config init stderr: ${configInitStderr}`);
|
|
2790
|
-
if (initRes.error) return {
|
|
2791
|
-
ok: false,
|
|
2792
|
-
configInitStdout,
|
|
2793
|
-
configInitStderr,
|
|
2794
|
-
error: `lark-cli config init spawn error: ${initRes.error.message}`
|
|
2795
|
-
};
|
|
2796
|
-
if (initRes.status !== 0) return {
|
|
2797
|
-
ok: false,
|
|
2798
|
-
configInitExitCode: initRes.status ?? void 0,
|
|
2799
|
-
configInitStdout,
|
|
2800
|
-
configInitStderr,
|
|
2801
|
-
error: `lark-cli config init exited with code ${initRes.status}`
|
|
2802
|
-
};
|
|
2803
|
-
appendPeToAgentsMd(agentsMdPath);
|
|
2804
|
-
return {
|
|
2805
|
-
ok: true,
|
|
2806
|
-
configInitExitCode: 0,
|
|
2807
|
-
agentsMdPath
|
|
2808
|
-
};
|
|
2809
|
-
}
|
|
2810
|
-
//#endregion
|
|
2811
|
-
//#region src/rules/agents-md-lark-cli-pe.ts
|
|
2812
|
-
function isLarkCliAvailable$1() {
|
|
2813
|
-
try {
|
|
2814
|
-
return (0, node_child_process.spawnSync)("lark-cli", ["--version"], {
|
|
2815
|
-
encoding: "utf-8",
|
|
2816
|
-
timeout: 5e3,
|
|
2817
|
-
stdio: [
|
|
2818
|
-
"ignore",
|
|
2819
|
-
"pipe",
|
|
2820
|
-
"ignore"
|
|
2821
|
-
]
|
|
2822
|
-
}).status === 0;
|
|
2823
|
-
} catch {
|
|
2824
|
-
return false;
|
|
2825
|
-
}
|
|
2826
|
-
}
|
|
2827
|
-
let AgentsMdLarkCliPeRule = class AgentsMdLarkCliPeRule extends DiagnoseRule {
|
|
2828
|
-
validate(ctx) {
|
|
2829
|
-
if (!isLarkCliAvailable$1()) return { pass: true };
|
|
2830
|
-
const missingPath = collectExistingAgentsMdPaths(ctx).find((filePath) => {
|
|
2831
|
-
return !node_fs.default.readFileSync(filePath, "utf-8").includes(`<${PE_XML_TAG}>`);
|
|
2832
|
-
});
|
|
2833
|
-
if (!missingPath) return { pass: true };
|
|
2834
|
-
return {
|
|
2835
|
-
pass: false,
|
|
2836
|
-
message: `${missingPath} 中缺少 lark-cli-pe PE 内容,需要追加`
|
|
2837
|
-
};
|
|
2838
|
-
}
|
|
2839
|
-
repair(ctx) {
|
|
2840
|
-
if (!isLarkCliAvailable$1()) return;
|
|
2841
|
-
for (const filePath of collectExistingAgentsMdPaths(ctx)) {
|
|
2842
|
-
const content = node_fs.default.readFileSync(filePath, "utf-8");
|
|
2843
|
-
if (content.includes(`<lark-cli-pe>`)) continue;
|
|
2844
|
-
const sep = content.length > 0 && !content.endsWith("\n") ? "\n" : "";
|
|
2845
|
-
node_fs.default.appendFileSync(filePath, `${sep}${PE_PLACEHOLDER}`, "utf-8");
|
|
2846
|
-
console.error(`agents-md-lark-cli-pe: appended PE to ${filePath}`);
|
|
2847
|
-
}
|
|
2848
|
-
}
|
|
2849
|
-
};
|
|
2850
|
-
AgentsMdLarkCliPeRule = __decorate([Rule({
|
|
2851
|
-
key: "agents_md_lark_cli_pe",
|
|
2852
|
-
description: "检测各智能体 AGENTS.md 中是否缺失 lark-cli-pe PE 内容,lark-cli 存在时自动追加",
|
|
2853
|
-
dependsOn: ["config_syntax_check"],
|
|
2854
|
-
repairMode: "standard",
|
|
2855
|
-
level: "silent"
|
|
2856
|
-
})], AgentsMdLarkCliPeRule);
|
|
2857
|
-
//#endregion
|
|
2858
2523
|
//#region src/rules/miaoda-official-plugins-install-spec-unlock.ts
|
|
2859
2524
|
/**
|
|
2860
2525
|
* Official miaoda-side plugins that must track manifest — version-locked specs
|
|
@@ -2958,11 +2623,11 @@ function getAllow$1(config) {
|
|
|
2958
2623
|
//#region src/rules/lark-plugin-allow.ts
|
|
2959
2624
|
const LARK_PLUGIN = "openclaw-lark";
|
|
2960
2625
|
const LEGACY_LARK_PLUGIN = "feishu-openclaw-plugin";
|
|
2961
|
-
const LARK_PLUGIN_NAMES = [LARK_PLUGIN, LEGACY_LARK_PLUGIN];
|
|
2626
|
+
const LARK_PLUGIN_NAMES$1 = [LARK_PLUGIN, LEGACY_LARK_PLUGIN];
|
|
2962
2627
|
let LarkPluginAllowRule = class LarkPluginAllowRule extends DiagnoseRule {
|
|
2963
2628
|
validate(ctx) {
|
|
2964
2629
|
const allow = getAllow(ctx.config);
|
|
2965
|
-
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 };
|
|
2966
2631
|
const installed = detectInstalledLarkPlugin(getExtensionsDir(ctx.configPath));
|
|
2967
2632
|
if (installed == null) return { pass: true };
|
|
2968
2633
|
return {
|
|
@@ -2981,7 +2646,7 @@ let LarkPluginAllowRule = class LarkPluginAllowRule extends DiagnoseRule {
|
|
|
2981
2646
|
const rawAllow = pluginsMap.allow;
|
|
2982
2647
|
const original = Array.isArray(rawAllow) ? rawAllow : [];
|
|
2983
2648
|
const stringAllow = original.filter((e) => typeof e === "string");
|
|
2984
|
-
if (LARK_PLUGIN_NAMES.some((name) => stringAllow.includes(name))) return;
|
|
2649
|
+
if (LARK_PLUGIN_NAMES$1.some((name) => stringAllow.includes(name))) return;
|
|
2985
2650
|
original.push(installed);
|
|
2986
2651
|
pluginsMap.allow = original;
|
|
2987
2652
|
}
|
|
@@ -3682,6 +3347,7 @@ function resolveUpgradeDirection(installed, ocCur, recommendedOc, isLegacy) {
|
|
|
3682
3347
|
/** 提取公共前置上下文;任何前置条件不满足时返回 null(规则 pass)。 */
|
|
3683
3348
|
function resolveCompatContext(ctx) {
|
|
3684
3349
|
const recommendedOc = ctx.vars.recommendedOpenclawTag;
|
|
3350
|
+
if (!recommendedOc) return null;
|
|
3685
3351
|
const ocCur = getOcVersion();
|
|
3686
3352
|
if (!ocCur) return null;
|
|
3687
3353
|
const installed = getInstalledPlugin(ctx);
|
|
@@ -3704,7 +3370,6 @@ let FeishuPluginOpenclawUpgradeRule = class FeishuPluginOpenclawUpgradeRule exte
|
|
|
3704
3370
|
if (!cc) return { pass: true };
|
|
3705
3371
|
const { ocCur, recommendedOc, installed, isLegacy } = cc;
|
|
3706
3372
|
if (isForkPlugin(installed)) return validateForkPlugin(installed, ocCur, recommendedOc);
|
|
3707
|
-
if (!recommendedOc) return { pass: true };
|
|
3708
3373
|
if (resolveUpgradeDirection(installed, ocCur, recommendedOc, isLegacy) !== "openclaw") return { pass: true };
|
|
3709
3374
|
return {
|
|
3710
3375
|
pass: false,
|
|
@@ -3732,17 +3397,11 @@ let FeishuPluginLarkUpgradeRule = class FeishuPluginLarkUpgradeRule extends Diag
|
|
|
3732
3397
|
if (!cc) return { pass: true };
|
|
3733
3398
|
const { ocCur, recommendedOc, installed, isLegacy } = cc;
|
|
3734
3399
|
if (isForkPlugin(installed)) return { pass: true };
|
|
3735
|
-
if (
|
|
3736
|
-
const prefix = buildCompatPrefix(installed, ocCur, isLegacy);
|
|
3737
|
-
if (!recommendedOc) return {
|
|
3738
|
-
pass: false,
|
|
3739
|
-
action: "upgrade_lark",
|
|
3740
|
-
message: `${prefix};建议升级飞书插件至兼容版本`
|
|
3741
|
-
};
|
|
3400
|
+
if (resolveUpgradeDirection(installed, ocCur, recommendedOc, isLegacy) !== "lark") return { pass: true };
|
|
3742
3401
|
return {
|
|
3743
3402
|
pass: false,
|
|
3744
3403
|
action: "upgrade_lark",
|
|
3745
|
-
message: `${
|
|
3404
|
+
message: `${buildCompatPrefix(installed, ocCur, isLegacy)};当前 openclaw@${ocCur} 已达推荐版本,可直接升级飞书插件`
|
|
3746
3405
|
};
|
|
3747
3406
|
}
|
|
3748
3407
|
};
|
|
@@ -3754,21 +3413,6 @@ FeishuPluginLarkUpgradeRule = __decorate([Rule({
|
|
|
3754
3413
|
level: "critical",
|
|
3755
3414
|
usesVars: ["recommendedOpenclawTag"]
|
|
3756
3415
|
})], FeishuPluginLarkUpgradeRule);
|
|
3757
|
-
/**
|
|
3758
|
-
* 核心判断:非 fork 插件是否需要升级 lark,基于当前 openclaw 版本的兼容性。
|
|
3759
|
-
*
|
|
3760
|
-
* 被 FeishuPluginLarkUpgradeRule.validate 和 needsLarkUpgrade 共用。
|
|
3761
|
-
* 调用方需在调用前自行处理 fork 插件的情况(fork 插件不走本函数)。
|
|
3762
|
-
*
|
|
3763
|
-
* - 有 recommendedOc:走 resolveUpgradeDirection 判断方向是否为 'lark'
|
|
3764
|
-
* - 无 recommendedOc(doctor 无推荐版本):legacy 插件直接需要升级;
|
|
3765
|
-
* 非 legacy 则检查当前版本是否在兼容表内
|
|
3766
|
-
*/
|
|
3767
|
-
function isLarkUpgradeNeededFromCC(cc) {
|
|
3768
|
-
const { ocCur, recommendedOc, installed, isLegacy } = cc;
|
|
3769
|
-
if (!recommendedOc) return isLegacy || !isVersionCompatible(installed, ocCur);
|
|
3770
|
-
return resolveUpgradeDirection(installed, ocCur, recommendedOc, isLegacy) === "lark";
|
|
3771
|
-
}
|
|
3772
3416
|
function isForkPlugin(p) {
|
|
3773
3417
|
return p.scope != null && FORK_SCOPES.includes(p.scope);
|
|
3774
3418
|
}
|
|
@@ -3807,16 +3451,14 @@ function describeCompatConstraint(entry, pluginVersion) {
|
|
|
3807
3451
|
/**
|
|
3808
3452
|
* @lark-apaas/openclaw-lark 豁免 VERSION_COMPAT_MAP,但仍要求 openclaw ≥ FORK_LARK_PLUGIN_MIN_OC_VERSION。
|
|
3809
3453
|
* 其他 @lark-apaas scope 的 fork 插件继续无条件 pass。
|
|
3810
|
-
* recommendedOc 可为 undefined(doctor 模式),此时只检测最低版本要求,不指定目标升级版本。
|
|
3811
3454
|
*/
|
|
3812
3455
|
function validateForkPlugin(installed, ocCur, recommendedOc) {
|
|
3813
3456
|
if (installed.fullName !== FORK_LARK_PLUGIN_FULL_NAME) return { pass: true };
|
|
3814
3457
|
if (compareCalVer(ocCur, FORK_LARK_PLUGIN_MIN_OC_VERSION) >= 0) return { pass: true };
|
|
3815
|
-
const recommendation = recommendedOc ? `;将 openclaw 升级到 ${recommendedOc} 即可满足` : `;请升级 openclaw 至 ${FORK_LARK_PLUGIN_MIN_OC_VERSION} 或更高版本`;
|
|
3816
3458
|
return {
|
|
3817
3459
|
pass: false,
|
|
3818
3460
|
action: "upgrade_openclaw",
|
|
3819
|
-
message: `飞书插件 ${describePlugin(installed)}(fork 版)要求 openclaw ≥ ${FORK_LARK_PLUGIN_MIN_OC_VERSION},当前 openclaw@${ocCur}
|
|
3461
|
+
message: `飞书插件 ${describePlugin(installed)}(fork 版)要求 openclaw ≥ ${FORK_LARK_PLUGIN_MIN_OC_VERSION},当前 openclaw@${ocCur} 低于此要求;将 openclaw 升级到 ${recommendedOc} 即可满足`
|
|
3820
3462
|
};
|
|
3821
3463
|
}
|
|
3822
3464
|
function describePlugin(p) {
|
|
@@ -3863,26 +3505,6 @@ function extractScopedNameFromSpec$1(spec) {
|
|
|
3863
3505
|
const at = spec.indexOf("@", 1);
|
|
3864
3506
|
return at === -1 ? spec : spec.slice(0, at);
|
|
3865
3507
|
}
|
|
3866
|
-
/**
|
|
3867
|
-
* 判断已安装的飞书插件是否与当前 openclaw 版本不兼容(或为需要替换的 legacy 插件)。
|
|
3868
|
-
* 被 upgrade-lark 前置检测门控(--check-only 和正式安装模式)调用。
|
|
3869
|
-
*/
|
|
3870
|
-
function needsLarkUpgrade(ctx) {
|
|
3871
|
-
const cc = resolveCompatContext({
|
|
3872
|
-
...ctx,
|
|
3873
|
-
vars: {
|
|
3874
|
-
...ctx.vars,
|
|
3875
|
-
recommendedOpenclawTag: void 0
|
|
3876
|
-
}
|
|
3877
|
-
});
|
|
3878
|
-
if (!cc) return false;
|
|
3879
|
-
const { ocCur, installed } = cc;
|
|
3880
|
-
if (isForkPlugin(installed)) {
|
|
3881
|
-
if (installed.fullName === FORK_LARK_PLUGIN_FULL_NAME) return compareCalVer(ocCur, FORK_LARK_PLUGIN_MIN_OC_VERSION) < 0;
|
|
3882
|
-
return false;
|
|
3883
|
-
}
|
|
3884
|
-
return isLarkUpgradeNeededFromCC(cc);
|
|
3885
|
-
}
|
|
3886
3508
|
//#endregion
|
|
3887
3509
|
//#region src/rules/cleanup-install-backup-dirs.ts
|
|
3888
3510
|
const DIR_PREFIX = ".openclaw-install-";
|
|
@@ -3966,7 +3588,7 @@ function extractScopedNameFromSpec(spec) {
|
|
|
3966
3588
|
const at = spec.indexOf("@", 1);
|
|
3967
3589
|
return at === -1 ? spec : spec.slice(0, at);
|
|
3968
3590
|
}
|
|
3969
|
-
function isLarkCliAvailable() {
|
|
3591
|
+
function isLarkCliAvailable$1() {
|
|
3970
3592
|
try {
|
|
3971
3593
|
return (0, node_child_process.spawnSync)(LARK_CLI_NAME$1, ["--version"], {
|
|
3972
3594
|
encoding: "utf-8",
|
|
@@ -4007,7 +3629,7 @@ function installLarkCliOnce(tag) {
|
|
|
4007
3629
|
let LarkCliMissingForInstalledLarkPluginRule = class LarkCliMissingForInstalledLarkPluginRule extends DiagnoseRule {
|
|
4008
3630
|
validate(ctx) {
|
|
4009
3631
|
if (!isTargetForkPlugin(readInstalledLarkPlugin(ctx))) return { pass: true };
|
|
4010
|
-
if (isLarkCliAvailable()) return { pass: true };
|
|
3632
|
+
if (isLarkCliAvailable$1()) return { pass: true };
|
|
4011
3633
|
return {
|
|
4012
3634
|
pass: false,
|
|
4013
3635
|
message: `${FORK_PACKAGE_NAME}@${TARGET_VERSION} 已安装,但 lark-cli 不可用;将执行一次 lark-cli 安装`
|
|
@@ -4015,7 +3637,7 @@ let LarkCliMissingForInstalledLarkPluginRule = class LarkCliMissingForInstalledL
|
|
|
4015
3637
|
}
|
|
4016
3638
|
repair(ctx) {
|
|
4017
3639
|
if (!isTargetForkPlugin(readInstalledLarkPlugin(ctx))) return;
|
|
4018
|
-
if (isLarkCliAvailable()) return;
|
|
3640
|
+
if (isLarkCliAvailable$1()) return;
|
|
4019
3641
|
installLarkCliOnce(ctx.vars.recommendedOpenclawTag ?? TARGET_VERSION);
|
|
4020
3642
|
}
|
|
4021
3643
|
};
|
|
@@ -4081,7 +3703,7 @@ let FeishuBotChannelConfigRule = class FeishuBotChannelConfigRule extends Diagno
|
|
|
4081
3703
|
if (!matchMap(secret, DEFAULT_FEISHU_APP_SECRET)) issues.push(`${label} appSecret is a provider-ref but not the canonical one`);
|
|
4082
3704
|
} else if (typeof secret === "string") {
|
|
4083
3705
|
if (secret !== larkApp.appSecret) issues.push(`${label} appSecret plaintext mismatch`);
|
|
4084
|
-
} else issues.push(`${label} appSecret has unexpected type
|
|
3706
|
+
} else issues.push(`${label} appSecret is missing or has unexpected type`);
|
|
4085
3707
|
}
|
|
4086
3708
|
repair(ctx) {
|
|
4087
3709
|
const larkApps = ctx.vars.larkApps;
|
|
@@ -4123,7 +3745,7 @@ let FeishuBotChannelConfigRule = class FeishuBotChannelConfigRule extends Diagno
|
|
|
4123
3745
|
if (!matchMap(secret, DEFAULT_FEISHU_APP_SECRET)) bot.appSecret = { ...DEFAULT_FEISHU_APP_SECRET };
|
|
4124
3746
|
} else if (typeof secret === "string") {
|
|
4125
3747
|
if (secret !== larkApp.appSecret) bot.appSecret = larkApp.appSecret;
|
|
4126
|
-
}
|
|
3748
|
+
} else bot.appSecret = larkApp.appSecret;
|
|
4127
3749
|
}
|
|
4128
3750
|
};
|
|
4129
3751
|
FeishuBotChannelConfigRule = __decorate([Rule({
|
|
@@ -4579,6 +4201,30 @@ function finalize$1(results, aborted) {
|
|
|
4579
4201
|
};
|
|
4580
4202
|
}
|
|
4581
4203
|
//#endregion
|
|
4204
|
+
//#region src/paths.ts
|
|
4205
|
+
/**
|
|
4206
|
+
* Central directory for all ephemeral diagnose/reset artifacts: task status
|
|
4207
|
+
* files (`reset-<taskId>.json`) and human-readable step logs
|
|
4208
|
+
* (`reset-<taskId>.log`). Having everything under one dir makes debugging a
|
|
4209
|
+
* stuck reset much easier — `ls /tmp/openclaw-diagnose/` shows every recent
|
|
4210
|
+
* run, and each run's log is right next to its state.
|
|
4211
|
+
*/
|
|
4212
|
+
const DIAGNOSE_DIR = "/tmp/openclaw-diagnose";
|
|
4213
|
+
function resetResultFile(taskId) {
|
|
4214
|
+
return `${DIAGNOSE_DIR}/reset-${taskId}.json`;
|
|
4215
|
+
}
|
|
4216
|
+
function resetLogFile(taskId) {
|
|
4217
|
+
return `${DIAGNOSE_DIR}/reset-${taskId}.log`;
|
|
4218
|
+
}
|
|
4219
|
+
/** Sandbox workspace root where openclaw config + agent state lives. */
|
|
4220
|
+
const WORKSPACE_DIR = "/home/gem/workspace/agent";
|
|
4221
|
+
/** File containing the provider key used by the openclaw miaoda provider. */
|
|
4222
|
+
const PROVIDER_FILE_PATH = "/home/gem/workspace/.force/openclaw/miaoda-provider-key";
|
|
4223
|
+
/** File containing the miaoda openclaw secrets JSON. */
|
|
4224
|
+
const SECRETS_FILE_PATH = "/home/gem/workspace/.force/openclaw/miaoda-openclaw-secrets.json";
|
|
4225
|
+
/** Absolute path to the openclaw config JSON. */
|
|
4226
|
+
const CONFIG_PATH = `${WORKSPACE_DIR}/openclaw.json`;
|
|
4227
|
+
//#endregion
|
|
4582
4228
|
//#region src/run-log.ts
|
|
4583
4229
|
let currentRunContext;
|
|
4584
4230
|
/**
|
|
@@ -5152,6 +4798,266 @@ function installOne$1(pkg, tarball, homeBase) {
|
|
|
5152
4798
|
});
|
|
5153
4799
|
}
|
|
5154
4800
|
//#endregion
|
|
4801
|
+
//#region src/lark-cli-init.ts
|
|
4802
|
+
const LARK_PLUGIN_NAMES = ["openclaw-lark", "feishu-openclaw-plugin"];
|
|
4803
|
+
const PE_XML_TAG = "lark-cli-pe";
|
|
4804
|
+
const PE_PLACEHOLDER = `
|
|
4805
|
+
<${PE_XML_TAG}>
|
|
4806
|
+
**【强制要求 - 无例外】飞书所有操作必须先读 feishu-lark-cli skill。未读skill不得调用任何飞书工具,违者视为操作失误。**
|
|
4807
|
+
</${PE_XML_TAG}>
|
|
4808
|
+
`;
|
|
4809
|
+
function isLarkPluginInstalled(configPath) {
|
|
4810
|
+
const extDir = getExtensionsDir(configPath);
|
|
4811
|
+
return LARK_PLUGIN_NAMES.some((name) => {
|
|
4812
|
+
try {
|
|
4813
|
+
return node_fs.default.existsSync(node_path.default.join(extDir, name, "package.json"));
|
|
4814
|
+
} catch {
|
|
4815
|
+
return false;
|
|
4816
|
+
}
|
|
4817
|
+
});
|
|
4818
|
+
}
|
|
4819
|
+
function isLarkCliAvailable() {
|
|
4820
|
+
try {
|
|
4821
|
+
return (0, node_child_process.spawnSync)("lark-cli", ["--version"], {
|
|
4822
|
+
encoding: "utf-8",
|
|
4823
|
+
timeout: 5e3,
|
|
4824
|
+
stdio: [
|
|
4825
|
+
"ignore",
|
|
4826
|
+
"pipe",
|
|
4827
|
+
"ignore"
|
|
4828
|
+
]
|
|
4829
|
+
}).status === 0;
|
|
4830
|
+
} catch {
|
|
4831
|
+
return false;
|
|
4832
|
+
}
|
|
4833
|
+
}
|
|
4834
|
+
function readConfig(configPath) {
|
|
4835
|
+
try {
|
|
4836
|
+
const raw = node_fs.default.readFileSync(configPath, "utf-8");
|
|
4837
|
+
const parsed = loadJSON5().parse(raw);
|
|
4838
|
+
return parsed != null && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
|
|
4839
|
+
} catch {
|
|
4840
|
+
return null;
|
|
4841
|
+
}
|
|
4842
|
+
}
|
|
4843
|
+
/**
|
|
4844
|
+
* Resolve the feishu app secret for the given appId.
|
|
4845
|
+
*
|
|
4846
|
+
* Lookup order:
|
|
4847
|
+
* 1. channels.feishu.appSecret (single-agent: feishu.appId === appId)
|
|
4848
|
+
* 2. channels.feishu.accounts[key].appSecret (multi-agent: account.appId === appId)
|
|
4849
|
+
*
|
|
4850
|
+
* Value interpretation:
|
|
4851
|
+
* - string → use directly
|
|
4852
|
+
* - object → secret is managed by a provider; use `feishuAppSecret` param instead
|
|
4853
|
+
*
|
|
4854
|
+
* Returns null when the secret cannot be determined.
|
|
4855
|
+
*/
|
|
4856
|
+
function resolveAppSecret(appId, config, feishuAppSecret) {
|
|
4857
|
+
const feishu = getNestedMap(config, "channels", "feishu");
|
|
4858
|
+
if (!feishu) return null;
|
|
4859
|
+
let rawSecret;
|
|
4860
|
+
if (typeof feishu.appId === "string" && feishu.appId === appId) rawSecret = feishu.appSecret;
|
|
4861
|
+
else {
|
|
4862
|
+
const accounts = asRecord(feishu.accounts);
|
|
4863
|
+
if (accounts) for (const [, val] of Object.entries(accounts)) {
|
|
4864
|
+
const account = asRecord(val);
|
|
4865
|
+
if (account?.appId === appId) {
|
|
4866
|
+
rawSecret = account.appSecret ?? feishu.appSecret;
|
|
4867
|
+
break;
|
|
4868
|
+
}
|
|
4869
|
+
}
|
|
4870
|
+
}
|
|
4871
|
+
if (typeof rawSecret === "string" && rawSecret) return rawSecret;
|
|
4872
|
+
if (rawSecret != null && typeof rawSecret === "object") return feishuAppSecret ?? null;
|
|
4873
|
+
return null;
|
|
4874
|
+
}
|
|
4875
|
+
/**
|
|
4876
|
+
* Resolve the agents.md path for the given appId from the openclaw config.
|
|
4877
|
+
*
|
|
4878
|
+
* Case 1: appId matches channels.feishu.appId (single-agent path)
|
|
4879
|
+
* → WORKSPACE_DIR/AGENTS.md
|
|
4880
|
+
*
|
|
4881
|
+
* Case 2: appId found in channels.feishu.accounts (multi-agent path)
|
|
4882
|
+
* → find account key where account.appId === appId
|
|
4883
|
+
* → find binding where match.channel=feishu && match.accountId=that key
|
|
4884
|
+
* → if agentId === 'main' → WORKSPACE_DIR/agents.md
|
|
4885
|
+
* → else find agent in agents.list by id → agent.workspace/agents.md
|
|
4886
|
+
*
|
|
4887
|
+
* Returns null when the path cannot be determined.
|
|
4888
|
+
*/
|
|
4889
|
+
function resolveAgentsMdPath(appId, config) {
|
|
4890
|
+
const feishu = getNestedMap(config, "channels", "feishu");
|
|
4891
|
+
if (!feishu) {
|
|
4892
|
+
console.error("resolveAgentsMdPath: channels.feishu not found");
|
|
4893
|
+
return null;
|
|
4894
|
+
}
|
|
4895
|
+
if (typeof feishu.appId === "string" && feishu.appId === appId) {
|
|
4896
|
+
console.error(`resolveAgentsMdPath: case=single-agent feishu.appId=${feishu.appId}`);
|
|
4897
|
+
return node_path.default.join(WORKSPACE_DIR, "workspace", "AGENTS.md");
|
|
4898
|
+
}
|
|
4899
|
+
const accounts = asRecord(feishu.accounts);
|
|
4900
|
+
if (!accounts) {
|
|
4901
|
+
console.error("resolveAgentsMdPath: feishu.accounts not found");
|
|
4902
|
+
return null;
|
|
4903
|
+
}
|
|
4904
|
+
let accountId;
|
|
4905
|
+
for (const [key, val] of Object.entries(accounts)) if (asRecord(val)?.appId === appId) {
|
|
4906
|
+
accountId = key;
|
|
4907
|
+
break;
|
|
4908
|
+
}
|
|
4909
|
+
if (!accountId) {
|
|
4910
|
+
console.error(`resolveAgentsMdPath: no account found with appId=${appId} in feishu.accounts keys=[${Object.keys(accounts).join(",")}]`);
|
|
4911
|
+
return null;
|
|
4912
|
+
}
|
|
4913
|
+
console.error(`resolveAgentsMdPath: found accountId=${accountId}`);
|
|
4914
|
+
const bindings = Array.isArray(config.bindings) ? config.bindings : [];
|
|
4915
|
+
let agentId;
|
|
4916
|
+
for (const b of bindings) {
|
|
4917
|
+
const binding = asRecord(b);
|
|
4918
|
+
if (!binding) continue;
|
|
4919
|
+
const match = asRecord(binding.match);
|
|
4920
|
+
if (match?.channel === "feishu" && match?.accountId === accountId) {
|
|
4921
|
+
if (typeof binding.agentId === "string") {
|
|
4922
|
+
agentId = binding.agentId;
|
|
4923
|
+
break;
|
|
4924
|
+
}
|
|
4925
|
+
}
|
|
4926
|
+
}
|
|
4927
|
+
if (!agentId) {
|
|
4928
|
+
console.error(`resolveAgentsMdPath: no binding found for accountId=${accountId} in bindings(count=${bindings.length})`);
|
|
4929
|
+
return null;
|
|
4930
|
+
}
|
|
4931
|
+
console.error(`resolveAgentsMdPath: found agentId=${agentId}`);
|
|
4932
|
+
if (agentId === "main") {
|
|
4933
|
+
console.error("resolveAgentsMdPath: case=multi-agent-main");
|
|
4934
|
+
return node_path.default.join(WORKSPACE_DIR, "workspace", "AGENTS.md");
|
|
4935
|
+
}
|
|
4936
|
+
const agentsObj = asRecord(config.agents);
|
|
4937
|
+
const list = Array.isArray(agentsObj?.list) ? agentsObj.list : [];
|
|
4938
|
+
for (const a of list) {
|
|
4939
|
+
const agent = asRecord(a);
|
|
4940
|
+
if (agent?.id === agentId) {
|
|
4941
|
+
const ws = typeof agent.workspace === "string" ? agent.workspace : node_path.default.join(WORKSPACE_DIR, "workspace");
|
|
4942
|
+
console.error(`resolveAgentsMdPath: case=multi-agent-custom agentId=${agentId} workspace=${ws}`);
|
|
4943
|
+
return node_path.default.join(ws, "AGENTS.md");
|
|
4944
|
+
}
|
|
4945
|
+
}
|
|
4946
|
+
console.error(`resolveAgentsMdPath: agentId=${agentId} not found in agents.list(count=${list.length})`);
|
|
4947
|
+
return null;
|
|
4948
|
+
}
|
|
4949
|
+
function appendPeToAgentsMd(agentsMdPath) {
|
|
4950
|
+
const dir = node_path.default.dirname(agentsMdPath);
|
|
4951
|
+
if (!node_fs.default.existsSync(dir)) node_fs.default.mkdirSync(dir, { recursive: true });
|
|
4952
|
+
const existing = node_fs.default.existsSync(agentsMdPath) ? node_fs.default.readFileSync(agentsMdPath, "utf-8") : "";
|
|
4953
|
+
if (existing.includes(`<${PE_XML_TAG}>`)) {
|
|
4954
|
+
console.error(`lark-cli-init: <${PE_XML_TAG}> already present in ${agentsMdPath}, skipping`);
|
|
4955
|
+
return;
|
|
4956
|
+
}
|
|
4957
|
+
const sep = existing.length > 0 && !existing.endsWith("\n") ? "\n" : "";
|
|
4958
|
+
node_fs.default.appendFileSync(agentsMdPath, `${sep}${PE_PLACEHOLDER}`, "utf-8");
|
|
4959
|
+
console.error(`lark-cli-init: appended PE placeholder to ${agentsMdPath}`);
|
|
4960
|
+
}
|
|
4961
|
+
/**
|
|
4962
|
+
* Collect every Feishu bot appId declared in the openclaw config.
|
|
4963
|
+
* Covers both single-agent (channels.feishu.appId) and multi-agent
|
|
4964
|
+
* (channels.feishu.accounts[*].appId) layouts. Returns a deduplicated list.
|
|
4965
|
+
*/
|
|
4966
|
+
function collectFeishuAppIds(configPath) {
|
|
4967
|
+
const config = readConfig(configPath ?? CONFIG_PATH);
|
|
4968
|
+
if (!config) return [];
|
|
4969
|
+
const feishu = getNestedMap(config, "channels", "feishu");
|
|
4970
|
+
if (!feishu) return [];
|
|
4971
|
+
const appIds = /* @__PURE__ */ new Set();
|
|
4972
|
+
const topAppId = feishu.appId;
|
|
4973
|
+
if (typeof topAppId === "string" && topAppId.trim()) appIds.add(topAppId.trim());
|
|
4974
|
+
const accounts = asRecord(feishu.accounts);
|
|
4975
|
+
if (accounts) for (const val of Object.values(accounts)) {
|
|
4976
|
+
const appId = asRecord(val)?.appId;
|
|
4977
|
+
if (typeof appId === "string" && appId.trim()) appIds.add(appId.trim());
|
|
4978
|
+
}
|
|
4979
|
+
return [...appIds];
|
|
4980
|
+
}
|
|
4981
|
+
function runLarkCliInit(opts) {
|
|
4982
|
+
const configPath = opts.configPath ?? CONFIG_PATH;
|
|
4983
|
+
if (!isLarkPluginInstalled(configPath)) {
|
|
4984
|
+
console.error("lark-cli-init: skipping — openclaw-lark plugin not installed");
|
|
4985
|
+
return {
|
|
4986
|
+
ok: true,
|
|
4987
|
+
skipped: true,
|
|
4988
|
+
skipReason: "openclaw-lark plugin not installed"
|
|
4989
|
+
};
|
|
4990
|
+
}
|
|
4991
|
+
if (!isLarkCliAvailable()) {
|
|
4992
|
+
console.error("lark-cli-init: skipping — lark-cli command not found");
|
|
4993
|
+
return {
|
|
4994
|
+
ok: true,
|
|
4995
|
+
skipped: true,
|
|
4996
|
+
skipReason: "lark-cli command not found"
|
|
4997
|
+
};
|
|
4998
|
+
}
|
|
4999
|
+
const config = readConfig(configPath);
|
|
5000
|
+
if (!config) return {
|
|
5001
|
+
ok: false,
|
|
5002
|
+
error: `could not read config at ${configPath}`
|
|
5003
|
+
};
|
|
5004
|
+
const agentsMdPath = resolveAgentsMdPath(opts.appId, config);
|
|
5005
|
+
console.error(`lark-cli-init: resolved agents.md path=${agentsMdPath ?? "(null)"}`);
|
|
5006
|
+
if (!agentsMdPath) return {
|
|
5007
|
+
ok: false,
|
|
5008
|
+
error: `could not resolve agents.md path for appId=${opts.appId}`
|
|
5009
|
+
};
|
|
5010
|
+
const appSecret = resolveAppSecret(opts.appId, config, opts.feishuAppSecret);
|
|
5011
|
+
if (!appSecret) return {
|
|
5012
|
+
ok: false,
|
|
5013
|
+
error: `could not resolve appSecret for appId=${opts.appId}`
|
|
5014
|
+
};
|
|
5015
|
+
console.error(`lark-cli-init: running lark-cli config init --name ${opts.appId} --app-id ${opts.appId} --brand feishu --app-secret-stdin --force-init`);
|
|
5016
|
+
const initRes = (0, node_child_process.spawnSync)("lark-cli", [
|
|
5017
|
+
"config",
|
|
5018
|
+
"init",
|
|
5019
|
+
"--name",
|
|
5020
|
+
opts.appId,
|
|
5021
|
+
"--app-id",
|
|
5022
|
+
opts.appId,
|
|
5023
|
+
"--brand",
|
|
5024
|
+
"feishu",
|
|
5025
|
+
"--app-secret-stdin",
|
|
5026
|
+
"--force-init"
|
|
5027
|
+
], {
|
|
5028
|
+
stdio: [
|
|
5029
|
+
"pipe",
|
|
5030
|
+
"pipe",
|
|
5031
|
+
"pipe"
|
|
5032
|
+
],
|
|
5033
|
+
encoding: "utf-8",
|
|
5034
|
+
input: appSecret
|
|
5035
|
+
});
|
|
5036
|
+
const configInitStdout = initRes.stdout?.trim() || void 0;
|
|
5037
|
+
const configInitStderr = initRes.stderr?.trim() || void 0;
|
|
5038
|
+
if (configInitStdout) console.error(`lark-cli config init stdout: ${configInitStdout}`);
|
|
5039
|
+
if (configInitStderr) console.error(`lark-cli config init stderr: ${configInitStderr}`);
|
|
5040
|
+
if (initRes.error) return {
|
|
5041
|
+
ok: false,
|
|
5042
|
+
configInitStdout,
|
|
5043
|
+
configInitStderr,
|
|
5044
|
+
error: `lark-cli config init spawn error: ${initRes.error.message}`
|
|
5045
|
+
};
|
|
5046
|
+
if (initRes.status !== 0) return {
|
|
5047
|
+
ok: false,
|
|
5048
|
+
configInitExitCode: initRes.status ?? void 0,
|
|
5049
|
+
configInitStdout,
|
|
5050
|
+
configInitStderr,
|
|
5051
|
+
error: `lark-cli config init exited with code ${initRes.status}`
|
|
5052
|
+
};
|
|
5053
|
+
appendPeToAgentsMd(agentsMdPath);
|
|
5054
|
+
return {
|
|
5055
|
+
ok: true,
|
|
5056
|
+
configInitExitCode: 0,
|
|
5057
|
+
agentsMdPath
|
|
5058
|
+
};
|
|
5059
|
+
}
|
|
5060
|
+
//#endregion
|
|
5155
5061
|
//#region ../../openclaw-slardar/lib/client.js
|
|
5156
5062
|
var import_index_cjs = /* @__PURE__ */ __toESM((/* @__PURE__ */ __commonJSMin(((exports) => {
|
|
5157
5063
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
@@ -10386,122 +10292,6 @@ function finalize(results, aborted) {
|
|
|
10386
10292
|
};
|
|
10387
10293
|
}
|
|
10388
10294
|
//#endregion
|
|
10389
|
-
//#region src/channels-probe.ts
|
|
10390
|
-
const FEISHU_INVALID_CONFIG_MSG = "channels.feishu: invalid config: must NOT have additional properties";
|
|
10391
|
-
const CHANNEL_LINE_RE = /^-\s+Feishu\s+([^:]+):\s+(.+)$/;
|
|
10392
|
-
/**
|
|
10393
|
-
* 判断单个飞书账号是否处于"可用"状态。
|
|
10394
|
-
* 移植自 feishu-channel-success-rate skill 的 Python `_account_is_working`。
|
|
10395
|
-
*
|
|
10396
|
-
* 会先剥离 "key:value" 形式的 bit(dm:、bot:、in:、out:、token:、allow:、
|
|
10397
|
-
* intents:、groups:、health: 等),再按以下公式判断:
|
|
10398
|
-
*
|
|
10399
|
-
* @param ignoreProbeFailed 为 true 时忽略 "probe failed" bit,不视为失败。
|
|
10400
|
-
* 升级前置检测中传 true,因为 probe 失败通常反映网络状况而非配置问题。
|
|
10401
|
-
* @param gatewayReachable 为 false 时(gateway 尚未启动),只要 enabled+configured
|
|
10402
|
-
* 即视为可用;为 true 时还需要 running/works 且无 error: bit。
|
|
10403
|
-
*/
|
|
10404
|
-
function accountIsWorking(bits, ignoreProbeFailed = true, gatewayReachable = true) {
|
|
10405
|
-
const bitTokens = /* @__PURE__ */ new Set();
|
|
10406
|
-
let hasError = false;
|
|
10407
|
-
let hasProbeFailed = false;
|
|
10408
|
-
for (const raw of bits) {
|
|
10409
|
-
const b = raw.trim();
|
|
10410
|
-
if (!b) continue;
|
|
10411
|
-
if (b.startsWith("error:")) {
|
|
10412
|
-
hasError = true;
|
|
10413
|
-
continue;
|
|
10414
|
-
}
|
|
10415
|
-
if (b === "probe failed") {
|
|
10416
|
-
hasProbeFailed = true;
|
|
10417
|
-
continue;
|
|
10418
|
-
}
|
|
10419
|
-
bitTokens.add(b.split(":")[0]);
|
|
10420
|
-
}
|
|
10421
|
-
if (!bitTokens.has("enabled") || !bitTokens.has("configured")) return false;
|
|
10422
|
-
if (!gatewayReachable) return true;
|
|
10423
|
-
if (bitTokens.has("works")) return true;
|
|
10424
|
-
if (bitTokens.has("running") && !hasError && (ignoreProbeFailed || !hasProbeFailed)) return true;
|
|
10425
|
-
return false;
|
|
10426
|
-
}
|
|
10427
|
-
/**
|
|
10428
|
-
* 解析 `openclaw channels status --probe` 的原始 stdout。
|
|
10429
|
-
* 移植自 feishu-channel-success-rate skill 的 Python `extract_channels_probe`。
|
|
10430
|
-
*/
|
|
10431
|
-
function parseChannelsProbeOutput(text, { ignoreProbeFailed = true } = {}) {
|
|
10432
|
-
const gatewayReachable = text.includes("Gateway reachable");
|
|
10433
|
-
const feishuConfigInvalid = text.includes(FEISHU_INVALID_CONFIG_MSG);
|
|
10434
|
-
const accounts = [];
|
|
10435
|
-
let anyAccountWorking = false;
|
|
10436
|
-
for (const line of text.split("\n")) {
|
|
10437
|
-
const m = CHANNEL_LINE_RE.exec(line.trim());
|
|
10438
|
-
if (!m) continue;
|
|
10439
|
-
const [, acct, rest] = m;
|
|
10440
|
-
const bits = rest.split(",").map((b) => b.trim());
|
|
10441
|
-
const isWorking = accountIsWorking(bits, ignoreProbeFailed, gatewayReachable);
|
|
10442
|
-
if (isWorking) anyAccountWorking = true;
|
|
10443
|
-
accounts.push({
|
|
10444
|
-
id: acct.trim(),
|
|
10445
|
-
bits,
|
|
10446
|
-
isWorking,
|
|
10447
|
-
raw: line.trim()
|
|
10448
|
-
});
|
|
10449
|
-
}
|
|
10450
|
-
return {
|
|
10451
|
-
gatewayReachable,
|
|
10452
|
-
feishuConfigInvalid,
|
|
10453
|
-
accounts,
|
|
10454
|
-
anyAccountWorking
|
|
10455
|
-
};
|
|
10456
|
-
}
|
|
10457
|
-
/**
|
|
10458
|
-
* 执行 `openclaw channels status --probe`,返回结构化结果。
|
|
10459
|
-
*
|
|
10460
|
-
* 部分 bot 账号 probe 失败时命令会以非零退出码退出,但 stdout 仍有可用内容。
|
|
10461
|
-
* 因此即使退出码非零,也尝试解析 stdout;只有真正没有任何输出时才返回 unavailable。
|
|
10462
|
-
*
|
|
10463
|
-
* @param timeoutMs 最长等待时长,默认 60 s。v2026.4.x 缺少单请求 HTTP 超时,
|
|
10464
|
-
* 可能无限阻塞,此超时是唯一保护。
|
|
10465
|
-
* @param ignoreProbeFailed 为 true 时,"probe failed" 账号仍计入"可用"。
|
|
10466
|
-
* 升级前置检测中应传 true,避免网络抖动导致误判为不可用。
|
|
10467
|
-
*/
|
|
10468
|
-
function runChannelsProbe(timeoutMs = 6e4, { ignoreProbeFailed = true } = {}) {
|
|
10469
|
-
let stdout = "";
|
|
10470
|
-
let stderrText = "";
|
|
10471
|
-
let execError;
|
|
10472
|
-
try {
|
|
10473
|
-
stdout = (0, node_child_process.execSync)("openclaw channels status --probe", {
|
|
10474
|
-
encoding: "utf-8",
|
|
10475
|
-
timeout: timeoutMs,
|
|
10476
|
-
stdio: [
|
|
10477
|
-
"ignore",
|
|
10478
|
-
"pipe",
|
|
10479
|
-
"pipe"
|
|
10480
|
-
]
|
|
10481
|
-
});
|
|
10482
|
-
} catch (e) {
|
|
10483
|
-
const err = e;
|
|
10484
|
-
const stdoutRaw = err.stdout;
|
|
10485
|
-
stdout = typeof stdoutRaw === "string" ? stdoutRaw : stdoutRaw?.toString("utf-8") ?? "";
|
|
10486
|
-
execError = err.message;
|
|
10487
|
-
const stderrRaw = err.stderr;
|
|
10488
|
-
stderrText = (typeof stderrRaw === "string" ? stderrRaw : stderrRaw?.toString("utf-8") ?? "").trim();
|
|
10489
|
-
if (stderrText) console.error(`channels-probe: stderr from CLI: ${stderrText}`);
|
|
10490
|
-
}
|
|
10491
|
-
if (stdout.trim()) return {
|
|
10492
|
-
available: true,
|
|
10493
|
-
...parseChannelsProbeOutput(stdout, { ignoreProbeFailed })
|
|
10494
|
-
};
|
|
10495
|
-
return {
|
|
10496
|
-
available: false,
|
|
10497
|
-
gatewayReachable: false,
|
|
10498
|
-
feishuConfigInvalid: stderrText.includes(FEISHU_INVALID_CONFIG_MSG),
|
|
10499
|
-
accounts: [],
|
|
10500
|
-
anyAccountWorking: false,
|
|
10501
|
-
error: execError ?? "no output from openclaw channels status --probe"
|
|
10502
|
-
};
|
|
10503
|
-
}
|
|
10504
|
-
//#endregion
|
|
10505
10295
|
//#region src/innerapi/reportCliRun.ts
|
|
10506
10296
|
/**
|
|
10507
10297
|
* CLI-side client for studio_server's `openclaw.report_cli_run` inner
|
|
@@ -10581,7 +10371,7 @@ async function reportCliRun(opts) {
|
|
|
10581
10371
|
//#region src/help.ts
|
|
10582
10372
|
const BIN = "mclaw-diagnose";
|
|
10583
10373
|
function versionBanner() {
|
|
10584
|
-
return `v0.1.15-alpha.
|
|
10374
|
+
return `v0.1.15-alpha.4`;
|
|
10585
10375
|
}
|
|
10586
10376
|
const COMMANDS = [
|
|
10587
10377
|
{
|
|
@@ -10868,58 +10658,6 @@ OPTIONS
|
|
|
10868
10658
|
EXIT CODES
|
|
10869
10659
|
0 Success or skipped (prerequisites not met).
|
|
10870
10660
|
1 Secret/path unresolvable, lark-cli failed, or config unreadable.
|
|
10871
|
-
`
|
|
10872
|
-
},
|
|
10873
|
-
{
|
|
10874
|
-
name: "upgrade-lark",
|
|
10875
|
-
hidden: false,
|
|
10876
|
-
summary: "Upgrade the Feishu/Lark plugin via @larksuite/openclaw-lark-tools",
|
|
10877
|
-
help: `USAGE
|
|
10878
|
-
${BIN} upgrade-lark [--check] [--scene=<scene>] [--caller=<n>] [--trace-id=<id>]
|
|
10879
|
-
|
|
10880
|
-
DESCRIPTION
|
|
10881
|
-
Upgrades the Feishu/Lark plugin by running:
|
|
10882
|
-
npx -y @larksuite/openclaw-lark-tools update --use-existing
|
|
10883
|
-
|
|
10884
|
-
Before the upgrade, a pre-check gate runs to verify the upgrade is needed:
|
|
10885
|
-
- version incompatible OR feishu channel config invalid, AND
|
|
10886
|
-
- no feishu account is currently working
|
|
10887
|
-
If the gate is not triggered, the command skips with exit code 0.
|
|
10888
|
-
|
|
10889
|
-
Before the upgrade, the following files are backed up:
|
|
10890
|
-
- openclaw.json
|
|
10891
|
-
- extensions/openclaw-lark/ (if present)
|
|
10892
|
-
- extensions/feishu-openclaw-plugin/ (if present)
|
|
10893
|
-
After the upgrade, the result is validated:
|
|
10894
|
-
- feishu.accounts bot count must not decrease
|
|
10895
|
-
- gateway config structure must remain valid (port/mode/bind/auth/trustedProxies)
|
|
10896
|
-
If the upgrade command fails, or validation fails, the backed-up files are
|
|
10897
|
-
restored to roll back the changes.
|
|
10898
|
-
|
|
10899
|
-
Execution is logged to /tmp/openclaw-diagnose/upgrade-lark-<runId>.log.
|
|
10900
|
-
|
|
10901
|
-
Output is a single JSON object on stdout:
|
|
10902
|
-
{ "ok": true, "stdout": "...", "stderr": "...", "logFile": "..." }
|
|
10903
|
-
{ "ok": false, "error": "...", "stderr": "...", "exitCode": 1,
|
|
10904
|
-
"rollbackOk": true, "validationError": "...", "logFile": "..." }
|
|
10905
|
-
|
|
10906
|
-
With --check:
|
|
10907
|
-
{ "ok": true, "skipped": true, "upgradeNeeded": false, "logFile": "..." }
|
|
10908
|
-
{ "ok": true, "skipped": true, "upgradeNeeded": true, "logFile": "..." } ← exit 1
|
|
10909
|
-
|
|
10910
|
-
OPTIONS
|
|
10911
|
-
--check Diagnose only: run the pre-check gate and report whether
|
|
10912
|
-
upgrade is needed without installing. Exit 1 if needed.
|
|
10913
|
-
--scene=<scene> Telemetry label forwarded to Slardar only.
|
|
10914
|
-
Known values: PageUpgradeLark, etc. Custom strings accepted.
|
|
10915
|
-
--caller=<name> Optional metadata passed to innerapi.
|
|
10916
|
-
--trace-id=<id> Optional log-correlation id.
|
|
10917
|
-
|
|
10918
|
-
EXIT CODES
|
|
10919
|
-
0 Success: upgrade ran and all validations passed; or gate skipped upgrade.
|
|
10920
|
-
1 Failure: npx error, validation failed, or git commit failed.
|
|
10921
|
-
File rollback was attempted (see rollbackOk in the JSON output).
|
|
10922
|
-
With --check: exit 1 means upgrade IS needed.
|
|
10923
10661
|
`
|
|
10924
10662
|
},
|
|
10925
10663
|
{
|
|
@@ -10953,41 +10691,6 @@ EXAMPLES
|
|
|
10953
10691
|
${BIN} rules # all rules
|
|
10954
10692
|
${BIN} rules --rule=gateway # single rule
|
|
10955
10693
|
${BIN} rules --rule=gateway --rule=feishu_channel # multiple rules
|
|
10956
|
-
`
|
|
10957
|
-
},
|
|
10958
|
-
{
|
|
10959
|
-
name: "channels-probe",
|
|
10960
|
-
hidden: true,
|
|
10961
|
-
summary: "Check feishu channel health via openclaw channels status --probe",
|
|
10962
|
-
help: `USAGE
|
|
10963
|
-
${BIN} channels-probe [--timeout=<ms>]
|
|
10964
|
-
|
|
10965
|
-
DESCRIPTION
|
|
10966
|
-
Runs \`openclaw channels status --probe\` and returns a structured JSON
|
|
10967
|
-
summary of whether the current environment's feishu channels are
|
|
10968
|
-
configured and working correctly.
|
|
10969
|
-
|
|
10970
|
-
Output:
|
|
10971
|
-
{
|
|
10972
|
-
"available": true,
|
|
10973
|
-
"gatewayReachable": true,
|
|
10974
|
-
"accounts": [
|
|
10975
|
-
{ "id": "default", "bits": ["enabled","configured","running","works"],
|
|
10976
|
-
"isWorking": true, "raw": "- Feishu default: ..." }
|
|
10977
|
-
],
|
|
10978
|
-
"anyAccountWorking": true
|
|
10979
|
-
}
|
|
10980
|
-
|
|
10981
|
-
An account is considered working when:
|
|
10982
|
-
enabled ∧ configured ∧ ( works ∨ ( running ∧ no error: ∧ no probe failed ) )
|
|
10983
|
-
|
|
10984
|
-
"available": false means the CLI invocation itself failed (openclaw not
|
|
10985
|
-
found, gateway unreachable, or no parseable output returned).
|
|
10986
|
-
|
|
10987
|
-
OPTIONS
|
|
10988
|
-
--timeout=<ms> Max wait in milliseconds (default: 60000). The probe
|
|
10989
|
-
can hang indefinitely on openclaw v2026.4.x due to a
|
|
10990
|
-
missing per-request HTTP timeout — set this accordingly.
|
|
10991
10694
|
`
|
|
10992
10695
|
},
|
|
10993
10696
|
{
|
|
@@ -11162,412 +10865,6 @@ function reportDoctorRunToSlardar(opts) {
|
|
|
11162
10865
|
}
|
|
11163
10866
|
});
|
|
11164
10867
|
}
|
|
11165
|
-
/**
|
|
11166
|
-
* 读取日志文件末尾最多 maxBytes 个字节。
|
|
11167
|
-
* 日志头部为固定 banner,有价值的内容(错误、探测结果、回滚状态)集中在末尾。
|
|
11168
|
-
* 从尾部截取保证 Slardar 字段限制内优先保留关键内容。
|
|
11169
|
-
*/
|
|
11170
|
-
function readLogFileTail(filePath, maxBytes = 4e3) {
|
|
11171
|
-
try {
|
|
11172
|
-
const buf = node_fs.default.readFileSync(filePath);
|
|
11173
|
-
if (buf.length <= maxBytes) return buf.toString("utf-8");
|
|
11174
|
-
let start = buf.length - maxBytes;
|
|
11175
|
-
while (start < buf.length && buf[start] !== 10) start++;
|
|
11176
|
-
return buf.subarray(start + 1).toString("utf-8");
|
|
11177
|
-
} catch {
|
|
11178
|
-
return "";
|
|
11179
|
-
}
|
|
11180
|
-
}
|
|
11181
|
-
/**
|
|
11182
|
-
* 向 Slardar 上报 upgrade-lark 运行结果(upgrade_lark_run 事件)。
|
|
11183
|
-
*
|
|
11184
|
-
* extraCategories 记录字符串维度:scene、exit_code、rollback_ok、
|
|
11185
|
-
* validation_error、error_msg、log_content(日志文件末尾 4000 字节,含关键结果)。
|
|
11186
|
-
*
|
|
11187
|
-
* extraMetrics 记录各阶段耗时(毫秒);未执行的阶段上报 -1 作为哨兵值,
|
|
11188
|
-
* 便于在 Slardar 查询时区分"未运行"和"运行了 0ms"。
|
|
11189
|
-
*/
|
|
11190
|
-
function reportUpgradeLarkToSlardar(opts) {
|
|
11191
|
-
console.error(`[slardar] upgrade_lark_run scene=${opts.scene ?? ""} checkOnly=${opts.checkOnly} success=${opts.success} exitCode=${opts.exitCode ?? ""} rollbackOk=${opts.rollbackOk ?? ""}`);
|
|
11192
|
-
const t = opts.timing ?? {};
|
|
11193
|
-
const logContent = readLogFileTail(opts.logFile);
|
|
11194
|
-
reportTask({
|
|
11195
|
-
eventName: "upgrade_lark_run",
|
|
11196
|
-
durationMs: opts.durationMs,
|
|
11197
|
-
status: opts.success ? "success" : "failed",
|
|
11198
|
-
extraCategories: {
|
|
11199
|
-
scene: opts.scene ?? "",
|
|
11200
|
-
check_only: String(opts.checkOnly),
|
|
11201
|
-
exit_code: String(opts.exitCode ?? ""),
|
|
11202
|
-
rollback_ok: opts.rollbackOk != null ? String(opts.rollbackOk) : "",
|
|
11203
|
-
validation_error: opts.validationError ?? "",
|
|
11204
|
-
error_msg: opts.error ?? "",
|
|
11205
|
-
log_content: logContent
|
|
11206
|
-
},
|
|
11207
|
-
extraMetrics: {
|
|
11208
|
-
pre_probe_ms: t.preProbeMs ?? -1,
|
|
11209
|
-
version_check_ms: t.versionCheckMs ?? -1,
|
|
11210
|
-
backup_ms: t.backupMs ?? -1,
|
|
11211
|
-
npx_install_ms: t.npxInstallMs ?? -1,
|
|
11212
|
-
post_probe_ms: t.postProbeMs ?? -1,
|
|
11213
|
-
doctor_fix_ms: t.doctorFixMs ?? -1
|
|
11214
|
-
}
|
|
11215
|
-
});
|
|
11216
|
-
}
|
|
11217
|
-
//#endregion
|
|
11218
|
-
//#region src/upgrade-lark.ts
|
|
11219
|
-
/** 升级前需备份的 extensions/ 下的插件目录 */
|
|
11220
|
-
const FEISHU_PLUGIN_DIRS = ["openclaw-lark", "feishu-openclaw-plugin"];
|
|
11221
|
-
function backupFiles(opts) {
|
|
11222
|
-
const { workspaceDir, configPath, backupDir, log } = opts;
|
|
11223
|
-
try {
|
|
11224
|
-
node_fs.default.mkdirSync(backupDir, { recursive: true });
|
|
11225
|
-
log(`backup dir: ${backupDir}`);
|
|
11226
|
-
if (node_fs.default.existsSync(configPath)) {
|
|
11227
|
-
const stat = node_fs.default.statSync(configPath);
|
|
11228
|
-
node_fs.default.copyFileSync(configPath, node_path.default.join(backupDir, "openclaw.json"));
|
|
11229
|
-
log(` backed up: openclaw.json (${stat.size} bytes)`);
|
|
11230
|
-
} else log(` skipped: openclaw.json (not found)`);
|
|
11231
|
-
const extSrc = node_path.default.join(workspaceDir, "extensions");
|
|
11232
|
-
for (const pluginDir of FEISHU_PLUGIN_DIRS) {
|
|
11233
|
-
const src = node_path.default.join(extSrc, pluginDir);
|
|
11234
|
-
if (node_fs.default.existsSync(src)) {
|
|
11235
|
-
const dst = node_path.default.join(backupDir, "extensions", pluginDir);
|
|
11236
|
-
node_fs.default.cpSync(src, dst, { recursive: true });
|
|
11237
|
-
const version = readPkgVersion(node_path.default.join(src, "package.json"));
|
|
11238
|
-
log(` backed up: extensions/${pluginDir}${version ? ` (version: ${version})` : ""}`);
|
|
11239
|
-
} else log(` skipped: extensions/${pluginDir} (not found)`);
|
|
11240
|
-
}
|
|
11241
|
-
return { ok: true };
|
|
11242
|
-
} catch (e) {
|
|
11243
|
-
return {
|
|
11244
|
-
ok: false,
|
|
11245
|
-
error: `backup failed: ${e.message}`
|
|
11246
|
-
};
|
|
11247
|
-
}
|
|
11248
|
-
}
|
|
11249
|
-
function restoreFiles(opts) {
|
|
11250
|
-
const { workspaceDir, configPath, backupDir, log } = opts;
|
|
11251
|
-
try {
|
|
11252
|
-
const configBackup = node_path.default.join(backupDir, "openclaw.json");
|
|
11253
|
-
if (node_fs.default.existsSync(configBackup)) {
|
|
11254
|
-
node_fs.default.copyFileSync(configBackup, configPath);
|
|
11255
|
-
log(` restored: openclaw.json`);
|
|
11256
|
-
}
|
|
11257
|
-
const extDst = node_path.default.join(workspaceDir, "extensions");
|
|
11258
|
-
for (const pluginDir of FEISHU_PLUGIN_DIRS) {
|
|
11259
|
-
const backupSrc = node_path.default.join(backupDir, "extensions", pluginDir);
|
|
11260
|
-
if (node_fs.default.existsSync(backupSrc)) {
|
|
11261
|
-
const dst = node_path.default.join(extDst, pluginDir);
|
|
11262
|
-
if (node_fs.default.existsSync(dst)) node_fs.default.rmSync(dst, {
|
|
11263
|
-
recursive: true,
|
|
11264
|
-
force: true
|
|
11265
|
-
});
|
|
11266
|
-
node_fs.default.cpSync(backupSrc, dst, { recursive: true });
|
|
11267
|
-
log(` restored: extensions/${pluginDir}`);
|
|
11268
|
-
}
|
|
11269
|
-
}
|
|
11270
|
-
return true;
|
|
11271
|
-
} catch (e) {
|
|
11272
|
-
log(` restore error: ${e.message}`);
|
|
11273
|
-
return false;
|
|
11274
|
-
}
|
|
11275
|
-
}
|
|
11276
|
-
function readPkgVersion(pkgPath) {
|
|
11277
|
-
try {
|
|
11278
|
-
const pkg = JSON.parse(node_fs.default.readFileSync(pkgPath, "utf-8"));
|
|
11279
|
-
return typeof pkg.version === "string" ? pkg.version : null;
|
|
11280
|
-
} catch {
|
|
11281
|
-
return null;
|
|
11282
|
-
}
|
|
11283
|
-
}
|
|
11284
|
-
function snapshotVersions(cwd, log) {
|
|
11285
|
-
const ocResult = (0, node_child_process.spawnSync)("openclaw", ["--version"], {
|
|
11286
|
-
cwd,
|
|
11287
|
-
encoding: "utf-8",
|
|
11288
|
-
stdio: [
|
|
11289
|
-
"ignore",
|
|
11290
|
-
"pipe",
|
|
11291
|
-
"pipe"
|
|
11292
|
-
],
|
|
11293
|
-
timeout: 5e3
|
|
11294
|
-
});
|
|
11295
|
-
const ocRaw = (ocResult.stdout ?? "").trim() || (ocResult.stderr ?? "").trim();
|
|
11296
|
-
const extDir = node_path.default.join(cwd, "extensions");
|
|
11297
|
-
const larkPkg = node_path.default.join(extDir, "openclaw-lark", "package.json");
|
|
11298
|
-
const feishuPkg = node_path.default.join(extDir, "feishu-openclaw-plugin", "package.json");
|
|
11299
|
-
log(` version-check paths: ${larkPkg} [${node_fs.default.existsSync(larkPkg) ? "exists" : "missing"}]`);
|
|
11300
|
-
log(` version-check paths: ${feishuPkg} [${node_fs.default.existsSync(feishuPkg) ? "exists" : "missing"}]`);
|
|
11301
|
-
return {
|
|
11302
|
-
openclaw: ocRaw || null,
|
|
11303
|
-
openclawLark: readPkgVersion(larkPkg),
|
|
11304
|
-
feishuOpenclawPlugin: readPkgVersion(feishuPkg)
|
|
11305
|
-
};
|
|
11306
|
-
}
|
|
11307
|
-
function logVersionSnapshot(label, v, log) {
|
|
11308
|
-
log(`${label}: openclaw=${v.openclaw ?? "n/a"} openclaw-lark=${v.openclawLark ?? "n/a"} feishu-openclaw-plugin=${v.feishuOpenclawPlugin ?? "n/a"}`);
|
|
11309
|
-
}
|
|
11310
|
-
function countFeishuBots(configPath) {
|
|
11311
|
-
try {
|
|
11312
|
-
const raw = node_fs.default.readFileSync(configPath, "utf-8");
|
|
11313
|
-
const config = loadJSON5().parse(raw);
|
|
11314
|
-
const accounts = getNestedMap(config, "channels", "feishu", "accounts");
|
|
11315
|
-
if (accounts) return Object.keys(accounts).length;
|
|
11316
|
-
const feishu = getNestedMap(config, "channels", "feishu");
|
|
11317
|
-
return typeof feishu?.appId === "string" && feishu.appId ? 1 : 0;
|
|
11318
|
-
} catch {
|
|
11319
|
-
return 0;
|
|
11320
|
-
}
|
|
11321
|
-
}
|
|
11322
|
-
/** 执行 channels probe 并将结果写入日志,从不抛出异常(异常时返回全零结果)。 */
|
|
11323
|
-
function probeChannels(label, log, timeoutMs) {
|
|
11324
|
-
try {
|
|
11325
|
-
const r = runChannelsProbe(timeoutMs);
|
|
11326
|
-
log(` ${label} available=${r.available} anyAccountWorking=${r.anyAccountWorking}`);
|
|
11327
|
-
if (r.error) log(` ${label} error: ${r.error}`);
|
|
11328
|
-
if (r.gatewayReachable != null) log(` ${label} gatewayReachable: ${r.gatewayReachable}`);
|
|
11329
|
-
for (const acct of r.accounts ?? []) log(` ${label} account ${acct.id}: isWorking=${acct.isWorking} bits=[${acct.bits.join(",")}]`);
|
|
11330
|
-
return r;
|
|
11331
|
-
} catch (e) {
|
|
11332
|
-
log(` ${label} channels probe threw: ${e.message}`);
|
|
11333
|
-
return {
|
|
11334
|
-
available: false,
|
|
11335
|
-
gatewayReachable: false,
|
|
11336
|
-
feishuConfigInvalid: false,
|
|
11337
|
-
accounts: [],
|
|
11338
|
-
anyAccountWorking: false
|
|
11339
|
-
};
|
|
11340
|
-
}
|
|
11341
|
-
}
|
|
11342
|
-
function runUpgradeLark(opts) {
|
|
11343
|
-
const cwd = opts.cwd ?? "/home/gem/workspace/agent";
|
|
11344
|
-
const configPath = opts.configPath ?? CONFIG_PATH;
|
|
11345
|
-
const logFile = upgradeLarkLogFile(opts.runId);
|
|
11346
|
-
const log = makeLogger(logFile);
|
|
11347
|
-
const fsOpts = {
|
|
11348
|
-
workspaceDir: cwd,
|
|
11349
|
-
configPath,
|
|
11350
|
-
backupDir: node_path.default.join(opts.backupBaseDir ?? "/tmp/openclaw-diagnose", `upgrade-lark-backup-${opts.runId}`),
|
|
11351
|
-
log
|
|
11352
|
-
};
|
|
11353
|
-
const cliScript = opts.cliScript ?? process.argv[1];
|
|
11354
|
-
const statusCheckDelayMs = opts.statusCheckDelayMs ?? 5e3;
|
|
11355
|
-
log(`${"=".repeat(60)}`);
|
|
11356
|
-
log(`upgrade-lark started runId=${opts.runId}`);
|
|
11357
|
-
log(` cwd : ${cwd}`);
|
|
11358
|
-
log(` configPath : ${configPath}`);
|
|
11359
|
-
log(`${"=".repeat(60)}`);
|
|
11360
|
-
const timing = {};
|
|
11361
|
-
log("");
|
|
11362
|
-
log("── [Pre-check A] channels probe(升级前)────────────────");
|
|
11363
|
-
const t_preProbeStart = Date.now();
|
|
11364
|
-
const beforeChannels = probeChannels("before", log, 6e4);
|
|
11365
|
-
timing.preProbeMs = Date.now() - t_preProbeStart;
|
|
11366
|
-
log("");
|
|
11367
|
-
log("── [Pre-check B] 版本兼容预检 ───────────────────────────");
|
|
11368
|
-
let versionIncompatible = false;
|
|
11369
|
-
const t_versionCheckStart = Date.now();
|
|
11370
|
-
try {
|
|
11371
|
-
const rawConfig = node_fs.default.readFileSync(configPath, "utf-8");
|
|
11372
|
-
versionIncompatible = needsLarkUpgrade({
|
|
11373
|
-
config: loadJSON5().parse(rawConfig),
|
|
11374
|
-
configPath,
|
|
11375
|
-
vars: {},
|
|
11376
|
-
providerDeps: {
|
|
11377
|
-
usesMiaodaProvider: false,
|
|
11378
|
-
usesMiaodaSecretProvider: false
|
|
11379
|
-
}
|
|
11380
|
-
});
|
|
11381
|
-
log(` version-compat pre-check: ${versionIncompatible ? "NEEDS_UPGRADE" : "ok"}`);
|
|
11382
|
-
} catch (e) {
|
|
11383
|
-
log(` version-compat pre-check error: ${e.message} — version signal unavailable`);
|
|
11384
|
-
}
|
|
11385
|
-
timing.versionCheckMs = Date.now() - t_versionCheckStart;
|
|
11386
|
-
const feishuConfigInvalid = beforeChannels.feishuConfigInvalid;
|
|
11387
|
-
log(` feishu config invalid : ${feishuConfigInvalid}`);
|
|
11388
|
-
log("");
|
|
11389
|
-
log("── [Gate] 升级前置条件检查 ───────────────────────────────");
|
|
11390
|
-
log(` versionIncompatible : ${versionIncompatible}`);
|
|
11391
|
-
log(` feishuConfigInvalid : ${feishuConfigInvalid}`);
|
|
11392
|
-
log(` channels working before: ${beforeChannels.anyAccountWorking}`);
|
|
11393
|
-
if (!(versionIncompatible || feishuConfigInvalid)) {
|
|
11394
|
-
const reason = "version compatible and feishu channel config valid — upgrade not needed";
|
|
11395
|
-
log(` SKIP: ${reason}`);
|
|
11396
|
-
log(`${"=".repeat(60)}`);
|
|
11397
|
-
log("upgrade-lark skipped (pre-check gate)");
|
|
11398
|
-
log(`${"=".repeat(60)}`);
|
|
11399
|
-
return {
|
|
11400
|
-
ok: true,
|
|
11401
|
-
skipped: true,
|
|
11402
|
-
skipReason: reason,
|
|
11403
|
-
upgradeNeeded: false,
|
|
11404
|
-
timing,
|
|
11405
|
-
logFile
|
|
11406
|
-
};
|
|
11407
|
-
}
|
|
11408
|
-
if (beforeChannels.anyAccountWorking) {
|
|
11409
|
-
const reason = "channels are working — upgrade not needed (issue detected but system is functional)";
|
|
11410
|
-
log(` SKIP: ${reason}`);
|
|
11411
|
-
log(`${"=".repeat(60)}`);
|
|
11412
|
-
log("upgrade-lark skipped (pre-check gate)");
|
|
11413
|
-
log(`${"=".repeat(60)}`);
|
|
11414
|
-
return {
|
|
11415
|
-
ok: true,
|
|
11416
|
-
skipped: true,
|
|
11417
|
-
skipReason: reason,
|
|
11418
|
-
upgradeNeeded: false,
|
|
11419
|
-
timing,
|
|
11420
|
-
logFile
|
|
11421
|
-
};
|
|
11422
|
-
}
|
|
11423
|
-
log(` PROCEED: requiresLarkUpgrade=true (version=${versionIncompatible}, feishuConfig=${feishuConfigInvalid}) AND channels not working → running upgrade`);
|
|
11424
|
-
if (opts.checkOnly) {
|
|
11425
|
-
log(` --check 模式:需要升级 — 不执行安装,直接返回`);
|
|
11426
|
-
log(`${"=".repeat(60)}`);
|
|
11427
|
-
log("upgrade-lark check complete");
|
|
11428
|
-
log(`${"=".repeat(60)}`);
|
|
11429
|
-
return {
|
|
11430
|
-
ok: true,
|
|
11431
|
-
skipped: true,
|
|
11432
|
-
skipReason: "check",
|
|
11433
|
-
upgradeNeeded: true,
|
|
11434
|
-
timing,
|
|
11435
|
-
logFile
|
|
11436
|
-
};
|
|
11437
|
-
}
|
|
11438
|
-
log("");
|
|
11439
|
-
log("── [1/6] 文件备份 ────────────────────────────────────────");
|
|
11440
|
-
log(`before-state: botCount=${countFeishuBots(configPath)}`);
|
|
11441
|
-
const t_backupStart = Date.now();
|
|
11442
|
-
const backup = backupFiles(fsOpts);
|
|
11443
|
-
timing.backupMs = Date.now() - t_backupStart;
|
|
11444
|
-
if (!backup.ok) {
|
|
11445
|
-
log(`ERROR: ${backup.error}`);
|
|
11446
|
-
return {
|
|
11447
|
-
ok: false,
|
|
11448
|
-
error: backup.error,
|
|
11449
|
-
timing,
|
|
11450
|
-
logFile
|
|
11451
|
-
};
|
|
11452
|
-
}
|
|
11453
|
-
log("backup: ok");
|
|
11454
|
-
logVersionSnapshot("before-versions", snapshotVersions(cwd, log), log);
|
|
11455
|
-
log("");
|
|
11456
|
-
log("── [2/6] 清理本地 openclaw shim ─────────────────────────");
|
|
11457
|
-
const localOpenclawBin = node_path.default.join(cwd, "node_modules", ".bin", "openclaw");
|
|
11458
|
-
if (node_fs.default.existsSync(localOpenclawBin)) try {
|
|
11459
|
-
node_fs.default.rmSync(localOpenclawBin);
|
|
11460
|
-
log(` removed: ${localOpenclawBin}`);
|
|
11461
|
-
} catch (e) {
|
|
11462
|
-
log(` WARN: failed to remove ${localOpenclawBin}: ${e.message}`);
|
|
11463
|
-
}
|
|
11464
|
-
else log(` skipped: ${localOpenclawBin} (not found)`);
|
|
11465
|
-
log("");
|
|
11466
|
-
log("── [3/6] npx install (@larksuite/openclaw-lark-tools update) ──");
|
|
11467
|
-
const t_npxStart = Date.now();
|
|
11468
|
-
const npxResult = (0, node_child_process.spawnSync)("npx", [
|
|
11469
|
-
"-y",
|
|
11470
|
-
"@larksuite/openclaw-lark-tools",
|
|
11471
|
-
"update"
|
|
11472
|
-
], {
|
|
11473
|
-
cwd,
|
|
11474
|
-
encoding: "utf-8",
|
|
11475
|
-
stdio: [
|
|
11476
|
-
"ignore",
|
|
11477
|
-
"pipe",
|
|
11478
|
-
"pipe"
|
|
11479
|
-
],
|
|
11480
|
-
timeout: 12e4
|
|
11481
|
-
});
|
|
11482
|
-
timing.npxInstallMs = Date.now() - t_npxStart;
|
|
11483
|
-
const npxStdout = npxResult.stdout?.trim() ?? "";
|
|
11484
|
-
const npxStderr = npxResult.stderr?.trim() ?? "";
|
|
11485
|
-
const npxExitCode = npxResult.status ?? 1;
|
|
11486
|
-
if (npxStdout) log(`npx stdout:\n${npxStdout}`);
|
|
11487
|
-
if (npxStderr) log(`npx stderr:\n${npxStderr}`);
|
|
11488
|
-
log(`npx exit: ${npxExitCode}${npxResult.error ? ` error: ${npxResult.error.message}` : ""}`);
|
|
11489
|
-
if (statusCheckDelayMs > 0) {
|
|
11490
|
-
log("");
|
|
11491
|
-
log(`── 等待 ${statusCheckDelayMs / 1e3}s(让 openclaw 服务完成重启) ─────────────`);
|
|
11492
|
-
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, statusCheckDelayMs);
|
|
11493
|
-
log("wait done");
|
|
11494
|
-
}
|
|
11495
|
-
const doRollback = (reason) => {
|
|
11496
|
-
log(`ERROR: ${reason}`);
|
|
11497
|
-
const rollbackOk = restoreFiles(fsOpts);
|
|
11498
|
-
log(`rollback: ${rollbackOk ? "ok" : "FAILED"}`);
|
|
11499
|
-
return {
|
|
11500
|
-
ok: false,
|
|
11501
|
-
error: reason,
|
|
11502
|
-
validationError: reason,
|
|
11503
|
-
stdout: npxStdout,
|
|
11504
|
-
stderr: npxStderr,
|
|
11505
|
-
exitCode: npxExitCode,
|
|
11506
|
-
rollbackOk,
|
|
11507
|
-
timing,
|
|
11508
|
-
logFile
|
|
11509
|
-
};
|
|
11510
|
-
};
|
|
11511
|
-
log("");
|
|
11512
|
-
log("── [4/5] 安装后诊断校验 ─────────────────────────────────");
|
|
11513
|
-
logVersionSnapshot("after-versions", snapshotVersions(cwd, log), log);
|
|
11514
|
-
let afterVersionIncompatible = false;
|
|
11515
|
-
try {
|
|
11516
|
-
const rawConfig = node_fs.default.readFileSync(configPath, "utf-8");
|
|
11517
|
-
afterVersionIncompatible = needsLarkUpgrade({
|
|
11518
|
-
config: loadJSON5().parse(rawConfig),
|
|
11519
|
-
configPath,
|
|
11520
|
-
vars: {},
|
|
11521
|
-
providerDeps: {
|
|
11522
|
-
usesMiaodaProvider: false,
|
|
11523
|
-
usesMiaodaSecretProvider: false
|
|
11524
|
-
}
|
|
11525
|
-
});
|
|
11526
|
-
log(` version-compat post-check: ${afterVersionIncompatible ? "STILL_INCOMPATIBLE" : "ok"}`);
|
|
11527
|
-
} catch (e) {
|
|
11528
|
-
log(` version-compat post-check error: ${e.message} — version signal unavailable`);
|
|
11529
|
-
}
|
|
11530
|
-
const t_postProbeStart = Date.now();
|
|
11531
|
-
const afterChannels = probeChannels("after", log, 6e4);
|
|
11532
|
-
timing.postProbeMs = Date.now() - t_postProbeStart;
|
|
11533
|
-
log(` feishu config invalid after: ${afterChannels.feishuConfigInvalid}`);
|
|
11534
|
-
const stillNeedsUpgrade = (afterVersionIncompatible || afterChannels.feishuConfigInvalid) && !afterChannels.anyAccountWorking;
|
|
11535
|
-
log(` post-check: stillNeedsUpgrade=${stillNeedsUpgrade} (version=${afterVersionIncompatible}, feishuConfig=${afterChannels.feishuConfigInvalid}, channelsWorking=${afterChannels.anyAccountWorking})`);
|
|
11536
|
-
if (stillNeedsUpgrade) return doRollback(`post-install diagnosis still shows anomaly: versionIncompatible=${afterVersionIncompatible}, feishuConfigInvalid=${afterChannels.feishuConfigInvalid}, anyAccountWorking=${afterChannels.anyAccountWorking}`);
|
|
11537
|
-
log(" post-install diagnosis: ok (upgrade conditions resolved)");
|
|
11538
|
-
log("");
|
|
11539
|
-
log("── [6/6] doctor --fix ────────────────────────────────────");
|
|
11540
|
-
const fixArgs = ["doctor", "--fix"];
|
|
11541
|
-
if (opts.scene) fixArgs.push(`--scene=${opts.scene}`);
|
|
11542
|
-
const t_doctorFixStart = Date.now();
|
|
11543
|
-
const fixResult = (0, node_child_process.spawnSync)(process.execPath, [cliScript, ...fixArgs], {
|
|
11544
|
-
cwd,
|
|
11545
|
-
encoding: "utf-8",
|
|
11546
|
-
stdio: [
|
|
11547
|
-
"ignore",
|
|
11548
|
-
"pipe",
|
|
11549
|
-
"pipe"
|
|
11550
|
-
],
|
|
11551
|
-
timeout: 6e4,
|
|
11552
|
-
env: process.env
|
|
11553
|
-
});
|
|
11554
|
-
timing.doctorFixMs = Date.now() - t_doctorFixStart;
|
|
11555
|
-
if (fixResult.stdout?.trim()) log(`doctor(fix) stdout:\n${fixResult.stdout.trim()}`);
|
|
11556
|
-
if (fixResult.stderr?.trim()) log(`doctor(fix) stderr:\n${fixResult.stderr.trim()}`);
|
|
11557
|
-
log(`doctor(fix) exit: ${fixResult.status ?? "null"}${fixResult.error ? ` error: ${fixResult.error.message}` : ""}`);
|
|
11558
|
-
log("");
|
|
11559
|
-
log(`${"=".repeat(60)}`);
|
|
11560
|
-
log("upgrade-lark completed successfully");
|
|
11561
|
-
log(`${"=".repeat(60)}`);
|
|
11562
|
-
return {
|
|
11563
|
-
ok: true,
|
|
11564
|
-
stdout: npxStdout,
|
|
11565
|
-
stderr: npxStderr,
|
|
11566
|
-
exitCode: npxExitCode,
|
|
11567
|
-
timing,
|
|
11568
|
-
logFile
|
|
11569
|
-
};
|
|
11570
|
-
}
|
|
11571
10868
|
//#endregion
|
|
11572
10869
|
//#region src/index.ts
|
|
11573
10870
|
const args = node_process.default.argv.slice(2);
|
|
@@ -12058,54 +11355,6 @@ async function main() {
|
|
|
12058
11355
|
if (!result.ok) node_process.default.exit(1);
|
|
12059
11356
|
break;
|
|
12060
11357
|
}
|
|
12061
|
-
case "upgrade-lark": {
|
|
12062
|
-
const checkOnly = args.includes("--check");
|
|
12063
|
-
const result = runUpgradeLark({
|
|
12064
|
-
runId: rc.runId,
|
|
12065
|
-
scene,
|
|
12066
|
-
checkOnly
|
|
12067
|
-
});
|
|
12068
|
-
const upgradeDurationMs = Date.now() - t0;
|
|
12069
|
-
console.log(JSON.stringify(result));
|
|
12070
|
-
reportUpgradeLarkToSlardar({
|
|
12071
|
-
scene,
|
|
12072
|
-
checkOnly,
|
|
12073
|
-
durationMs: upgradeDurationMs,
|
|
12074
|
-
success: result.ok,
|
|
12075
|
-
logFile: result.logFile,
|
|
12076
|
-
exitCode: result.exitCode,
|
|
12077
|
-
rollbackOk: result.rollbackOk,
|
|
12078
|
-
validationError: result.validationError,
|
|
12079
|
-
error: result.error,
|
|
12080
|
-
timing: result.timing
|
|
12081
|
-
});
|
|
12082
|
-
try {
|
|
12083
|
-
await reportCliRun({
|
|
12084
|
-
command: "upgrade-lark",
|
|
12085
|
-
runId: rc.runId,
|
|
12086
|
-
version: getVersion(),
|
|
12087
|
-
invocation: args.join(" "),
|
|
12088
|
-
durationMs: upgradeDurationMs,
|
|
12089
|
-
caller: rc.caller,
|
|
12090
|
-
traceId: rc.traceId,
|
|
12091
|
-
success: result.ok,
|
|
12092
|
-
result,
|
|
12093
|
-
error: result.ok ? void 0 : { message: result.error ?? "upgrade-lark failed" }
|
|
12094
|
-
});
|
|
12095
|
-
} catch (e) {
|
|
12096
|
-
console.error(`[telemetry] reportCliRun failed: ${e.message}`);
|
|
12097
|
-
}
|
|
12098
|
-
if (!result.ok || checkOnly && result.upgradeNeeded) {
|
|
12099
|
-
node_process.default.exitCode = 1;
|
|
12100
|
-
return;
|
|
12101
|
-
}
|
|
12102
|
-
break;
|
|
12103
|
-
}
|
|
12104
|
-
case "channels-probe": {
|
|
12105
|
-
const result = runChannelsProbe(getFlag(args, "timeout") ? Number(getFlag(args, "timeout")) : void 0);
|
|
12106
|
-
console.log(JSON.stringify(result));
|
|
12107
|
-
break;
|
|
12108
|
-
}
|
|
12109
11358
|
default:
|
|
12110
11359
|
node_process.default.stderr.write(`Unknown command: ${mode}\n\n`);
|
|
12111
11360
|
node_process.default.stderr.write(formatTopLevelHelp(helpFlags.expert));
|