@lark-apaas/openclaw-scripts-diagnose-cli 0.1.14-alpha.9 → 0.1.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 +579 -1060
- 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
|
|
55
|
+
return "0.1.14";
|
|
56
56
|
}
|
|
57
57
|
//#endregion
|
|
58
58
|
//#region src/rule-engine/base.ts
|
|
@@ -2520,6 +2520,337 @@ function upsertResourceConstrainedToolsBlock(content) {
|
|
|
2520
2520
|
return `${content}${content.length > 0 && !content.endsWith("\n") ? "\n\n" : "\n"}${RESOURCE_CONSTRAINED_TOOLS_BLOCK}\n`;
|
|
2521
2521
|
}
|
|
2522
2522
|
//#endregion
|
|
2523
|
+
//#region src/paths.ts
|
|
2524
|
+
/**
|
|
2525
|
+
* Central directory for all ephemeral diagnose/reset artifacts: task status
|
|
2526
|
+
* files (`reset-<taskId>.json`) and human-readable step logs
|
|
2527
|
+
* (`reset-<taskId>.log`). Having everything under one dir makes debugging a
|
|
2528
|
+
* stuck reset much easier — `ls /tmp/openclaw-diagnose/` shows every recent
|
|
2529
|
+
* run, and each run's log is right next to its state.
|
|
2530
|
+
*/
|
|
2531
|
+
const DIAGNOSE_DIR = "/tmp/openclaw-diagnose";
|
|
2532
|
+
function resetResultFile(taskId) {
|
|
2533
|
+
return `${DIAGNOSE_DIR}/reset-${taskId}.json`;
|
|
2534
|
+
}
|
|
2535
|
+
function resetLogFile(taskId) {
|
|
2536
|
+
return `${DIAGNOSE_DIR}/reset-${taskId}.log`;
|
|
2537
|
+
}
|
|
2538
|
+
/** Sandbox workspace root where openclaw config + agent state lives. */
|
|
2539
|
+
const WORKSPACE_DIR = "/home/gem/workspace/agent";
|
|
2540
|
+
/** File containing the provider key used by the openclaw miaoda provider. */
|
|
2541
|
+
const PROVIDER_FILE_PATH = "/home/gem/workspace/.force/openclaw/miaoda-provider-key";
|
|
2542
|
+
/** File containing the miaoda openclaw secrets JSON. */
|
|
2543
|
+
const SECRETS_FILE_PATH = "/home/gem/workspace/.force/openclaw/miaoda-openclaw-secrets.json";
|
|
2544
|
+
/** Absolute path to the openclaw config JSON. */
|
|
2545
|
+
const CONFIG_PATH = `${WORKSPACE_DIR}/openclaw.json`;
|
|
2546
|
+
//#endregion
|
|
2547
|
+
//#region src/lark-cli-init.ts
|
|
2548
|
+
const LARK_PLUGIN_NAMES$1 = ["openclaw-lark", "feishu-openclaw-plugin"];
|
|
2549
|
+
const PE_XML_TAG = "lark-cli-pe";
|
|
2550
|
+
const PE_PLACEHOLDER = `
|
|
2551
|
+
<${PE_XML_TAG}>
|
|
2552
|
+
**【强制要求 - 无例外】飞书所有操作必须先读 feishu-lark-cli skill。未读skill不得调用任何飞书工具,违者视为操作失误。**
|
|
2553
|
+
</${PE_XML_TAG}>
|
|
2554
|
+
`;
|
|
2555
|
+
function isLarkPluginInstalled(configPath) {
|
|
2556
|
+
const extDir = getExtensionsDir(configPath);
|
|
2557
|
+
return LARK_PLUGIN_NAMES$1.some((name) => {
|
|
2558
|
+
try {
|
|
2559
|
+
return node_fs.default.existsSync(node_path.default.join(extDir, name, "package.json"));
|
|
2560
|
+
} catch {
|
|
2561
|
+
return false;
|
|
2562
|
+
}
|
|
2563
|
+
});
|
|
2564
|
+
}
|
|
2565
|
+
function isLarkCliAvailable$2() {
|
|
2566
|
+
try {
|
|
2567
|
+
return (0, node_child_process.spawnSync)("lark-cli", ["--version"], {
|
|
2568
|
+
encoding: "utf-8",
|
|
2569
|
+
timeout: 5e3,
|
|
2570
|
+
stdio: [
|
|
2571
|
+
"ignore",
|
|
2572
|
+
"pipe",
|
|
2573
|
+
"ignore"
|
|
2574
|
+
]
|
|
2575
|
+
}).status === 0;
|
|
2576
|
+
} catch {
|
|
2577
|
+
return false;
|
|
2578
|
+
}
|
|
2579
|
+
}
|
|
2580
|
+
function readConfig(configPath) {
|
|
2581
|
+
try {
|
|
2582
|
+
const raw = node_fs.default.readFileSync(configPath, "utf-8");
|
|
2583
|
+
const parsed = loadJSON5().parse(raw);
|
|
2584
|
+
return parsed != null && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
|
|
2585
|
+
} catch {
|
|
2586
|
+
return null;
|
|
2587
|
+
}
|
|
2588
|
+
}
|
|
2589
|
+
/**
|
|
2590
|
+
* Resolve the feishu app secret for the given appId.
|
|
2591
|
+
*
|
|
2592
|
+
* Lookup order:
|
|
2593
|
+
* 1. channels.feishu.appSecret (single-agent: feishu.appId === appId)
|
|
2594
|
+
* 2. channels.feishu.accounts[key].appSecret (multi-agent: account.appId === appId)
|
|
2595
|
+
*
|
|
2596
|
+
* Value interpretation:
|
|
2597
|
+
* - string → use directly
|
|
2598
|
+
* - object → secret is managed by a provider; use `feishuAppSecret` param instead
|
|
2599
|
+
*
|
|
2600
|
+
* Returns null when the secret cannot be determined.
|
|
2601
|
+
*/
|
|
2602
|
+
function resolveAppSecret(appId, config, feishuAppSecret) {
|
|
2603
|
+
const feishu = getNestedMap(config, "channels", "feishu");
|
|
2604
|
+
if (!feishu) return null;
|
|
2605
|
+
let rawSecret;
|
|
2606
|
+
if (typeof feishu.appId === "string" && feishu.appId === appId) rawSecret = feishu.appSecret;
|
|
2607
|
+
else {
|
|
2608
|
+
const accounts = asRecord(feishu.accounts);
|
|
2609
|
+
if (accounts) for (const [, val] of Object.entries(accounts)) {
|
|
2610
|
+
const account = asRecord(val);
|
|
2611
|
+
if (account?.appId === appId) {
|
|
2612
|
+
rawSecret = account.appSecret ?? feishu.appSecret;
|
|
2613
|
+
break;
|
|
2614
|
+
}
|
|
2615
|
+
}
|
|
2616
|
+
}
|
|
2617
|
+
if (typeof rawSecret === "string" && rawSecret) return rawSecret;
|
|
2618
|
+
if (rawSecret != null && typeof rawSecret === "object") return feishuAppSecret ?? null;
|
|
2619
|
+
return null;
|
|
2620
|
+
}
|
|
2621
|
+
/**
|
|
2622
|
+
* Resolve the agents.md path for the given appId from the openclaw config.
|
|
2623
|
+
*
|
|
2624
|
+
* Case 1: appId matches channels.feishu.appId (single-agent path)
|
|
2625
|
+
* → WORKSPACE_DIR/AGENTS.md
|
|
2626
|
+
*
|
|
2627
|
+
* Case 2: appId found in channels.feishu.accounts (multi-agent path)
|
|
2628
|
+
* → find account key where account.appId === appId
|
|
2629
|
+
* → find binding where match.channel=feishu && match.accountId=that key
|
|
2630
|
+
* → if agentId === 'main' → WORKSPACE_DIR/agents.md
|
|
2631
|
+
* → else find agent in agents.list by id → agent.workspace/agents.md
|
|
2632
|
+
*
|
|
2633
|
+
* Returns null when the path cannot be determined.
|
|
2634
|
+
*/
|
|
2635
|
+
function resolveAgentsMdPath(appId, config) {
|
|
2636
|
+
const feishu = getNestedMap(config, "channels", "feishu");
|
|
2637
|
+
if (!feishu) {
|
|
2638
|
+
console.error("resolveAgentsMdPath: channels.feishu not found");
|
|
2639
|
+
return null;
|
|
2640
|
+
}
|
|
2641
|
+
if (typeof feishu.appId === "string" && feishu.appId === appId) {
|
|
2642
|
+
console.error(`resolveAgentsMdPath: case=single-agent feishu.appId=${feishu.appId}`);
|
|
2643
|
+
return node_path.default.join(WORKSPACE_DIR, "workspace", "AGENTS.md");
|
|
2644
|
+
}
|
|
2645
|
+
const accounts = asRecord(feishu.accounts);
|
|
2646
|
+
if (!accounts) {
|
|
2647
|
+
console.error("resolveAgentsMdPath: feishu.accounts not found");
|
|
2648
|
+
return null;
|
|
2649
|
+
}
|
|
2650
|
+
let accountId;
|
|
2651
|
+
for (const [key, val] of Object.entries(accounts)) if (asRecord(val)?.appId === appId) {
|
|
2652
|
+
accountId = key;
|
|
2653
|
+
break;
|
|
2654
|
+
}
|
|
2655
|
+
if (!accountId) {
|
|
2656
|
+
console.error(`resolveAgentsMdPath: no account found with appId=${appId} in feishu.accounts keys=[${Object.keys(accounts).join(",")}]`);
|
|
2657
|
+
return null;
|
|
2658
|
+
}
|
|
2659
|
+
console.error(`resolveAgentsMdPath: found accountId=${accountId}`);
|
|
2660
|
+
const bindings = Array.isArray(config.bindings) ? config.bindings : [];
|
|
2661
|
+
let agentId;
|
|
2662
|
+
for (const b of bindings) {
|
|
2663
|
+
const binding = asRecord(b);
|
|
2664
|
+
if (!binding) continue;
|
|
2665
|
+
const match = asRecord(binding.match);
|
|
2666
|
+
if (match?.channel === "feishu" && match?.accountId === accountId) {
|
|
2667
|
+
if (typeof binding.agentId === "string") {
|
|
2668
|
+
agentId = binding.agentId;
|
|
2669
|
+
break;
|
|
2670
|
+
}
|
|
2671
|
+
}
|
|
2672
|
+
}
|
|
2673
|
+
if (!agentId) {
|
|
2674
|
+
console.error(`resolveAgentsMdPath: no binding found for accountId=${accountId} in bindings(count=${bindings.length})`);
|
|
2675
|
+
return null;
|
|
2676
|
+
}
|
|
2677
|
+
console.error(`resolveAgentsMdPath: found agentId=${agentId}`);
|
|
2678
|
+
if (agentId === "main") {
|
|
2679
|
+
console.error("resolveAgentsMdPath: case=multi-agent-main");
|
|
2680
|
+
return node_path.default.join(WORKSPACE_DIR, "workspace", "AGENTS.md");
|
|
2681
|
+
}
|
|
2682
|
+
const agentsObj = asRecord(config.agents);
|
|
2683
|
+
const list = Array.isArray(agentsObj?.list) ? agentsObj.list : [];
|
|
2684
|
+
for (const a of list) {
|
|
2685
|
+
const agent = asRecord(a);
|
|
2686
|
+
if (agent?.id === agentId) {
|
|
2687
|
+
const ws = typeof agent.workspace === "string" ? agent.workspace : node_path.default.join(WORKSPACE_DIR, "workspace");
|
|
2688
|
+
console.error(`resolveAgentsMdPath: case=multi-agent-custom agentId=${agentId} workspace=${ws}`);
|
|
2689
|
+
return node_path.default.join(ws, "AGENTS.md");
|
|
2690
|
+
}
|
|
2691
|
+
}
|
|
2692
|
+
console.error(`resolveAgentsMdPath: agentId=${agentId} not found in agents.list(count=${list.length})`);
|
|
2693
|
+
return null;
|
|
2694
|
+
}
|
|
2695
|
+
function appendPeToAgentsMd(agentsMdPath) {
|
|
2696
|
+
const dir = node_path.default.dirname(agentsMdPath);
|
|
2697
|
+
if (!node_fs.default.existsSync(dir)) node_fs.default.mkdirSync(dir, { recursive: true });
|
|
2698
|
+
const existing = node_fs.default.existsSync(agentsMdPath) ? node_fs.default.readFileSync(agentsMdPath, "utf-8") : "";
|
|
2699
|
+
if (existing.includes(`<lark-cli-pe>`)) {
|
|
2700
|
+
console.error(`lark-cli-init: <${PE_XML_TAG}> already present in ${agentsMdPath}, skipping`);
|
|
2701
|
+
return;
|
|
2702
|
+
}
|
|
2703
|
+
const sep = existing.length > 0 && !existing.endsWith("\n") ? "\n" : "";
|
|
2704
|
+
node_fs.default.appendFileSync(agentsMdPath, `${sep}${PE_PLACEHOLDER}`, "utf-8");
|
|
2705
|
+
console.error(`lark-cli-init: appended PE placeholder to ${agentsMdPath}`);
|
|
2706
|
+
}
|
|
2707
|
+
/**
|
|
2708
|
+
* Collect every Feishu bot appId declared in the openclaw config.
|
|
2709
|
+
* Covers both single-agent (channels.feishu.appId) and multi-agent
|
|
2710
|
+
* (channels.feishu.accounts[*].appId) layouts. Returns a deduplicated list.
|
|
2711
|
+
*/
|
|
2712
|
+
function collectFeishuAppIds(configPath) {
|
|
2713
|
+
const config = readConfig(configPath ?? CONFIG_PATH);
|
|
2714
|
+
if (!config) return [];
|
|
2715
|
+
const feishu = getNestedMap(config, "channels", "feishu");
|
|
2716
|
+
if (!feishu) return [];
|
|
2717
|
+
const appIds = /* @__PURE__ */ new Set();
|
|
2718
|
+
const topAppId = feishu.appId;
|
|
2719
|
+
if (typeof topAppId === "string" && topAppId.trim()) appIds.add(topAppId.trim());
|
|
2720
|
+
const accounts = asRecord(feishu.accounts);
|
|
2721
|
+
if (accounts) for (const val of Object.values(accounts)) {
|
|
2722
|
+
const appId = asRecord(val)?.appId;
|
|
2723
|
+
if (typeof appId === "string" && appId.trim()) appIds.add(appId.trim());
|
|
2724
|
+
}
|
|
2725
|
+
return [...appIds];
|
|
2726
|
+
}
|
|
2727
|
+
function runLarkCliInit(opts) {
|
|
2728
|
+
const configPath = opts.configPath ?? CONFIG_PATH;
|
|
2729
|
+
if (!isLarkPluginInstalled(configPath)) {
|
|
2730
|
+
console.error("lark-cli-init: skipping — openclaw-lark plugin not installed");
|
|
2731
|
+
return {
|
|
2732
|
+
ok: true,
|
|
2733
|
+
skipped: true,
|
|
2734
|
+
skipReason: "openclaw-lark plugin not installed"
|
|
2735
|
+
};
|
|
2736
|
+
}
|
|
2737
|
+
if (!isLarkCliAvailable$2()) {
|
|
2738
|
+
console.error("lark-cli-init: skipping — lark-cli command not found");
|
|
2739
|
+
return {
|
|
2740
|
+
ok: true,
|
|
2741
|
+
skipped: true,
|
|
2742
|
+
skipReason: "lark-cli command not found"
|
|
2743
|
+
};
|
|
2744
|
+
}
|
|
2745
|
+
const config = readConfig(configPath);
|
|
2746
|
+
if (!config) return {
|
|
2747
|
+
ok: false,
|
|
2748
|
+
error: `could not read config at ${configPath}`
|
|
2749
|
+
};
|
|
2750
|
+
const agentsMdPath = resolveAgentsMdPath(opts.appId, config);
|
|
2751
|
+
console.error(`lark-cli-init: resolved agents.md path=${agentsMdPath ?? "(null)"}`);
|
|
2752
|
+
if (!agentsMdPath) return {
|
|
2753
|
+
ok: false,
|
|
2754
|
+
error: `could not resolve agents.md path for appId=${opts.appId}`
|
|
2755
|
+
};
|
|
2756
|
+
const appSecret = resolveAppSecret(opts.appId, config, opts.feishuAppSecret);
|
|
2757
|
+
if (!appSecret) return {
|
|
2758
|
+
ok: false,
|
|
2759
|
+
error: `could not resolve appSecret for appId=${opts.appId}`
|
|
2760
|
+
};
|
|
2761
|
+
console.error(`lark-cli-init: running lark-cli config init --name ${opts.appId} --app-id ${opts.appId} --brand feishu --app-secret-stdin --force-init`);
|
|
2762
|
+
const initRes = (0, node_child_process.spawnSync)("lark-cli", [
|
|
2763
|
+
"config",
|
|
2764
|
+
"init",
|
|
2765
|
+
"--name",
|
|
2766
|
+
opts.appId,
|
|
2767
|
+
"--app-id",
|
|
2768
|
+
opts.appId,
|
|
2769
|
+
"--brand",
|
|
2770
|
+
"feishu",
|
|
2771
|
+
"--app-secret-stdin",
|
|
2772
|
+
"--force-init"
|
|
2773
|
+
], {
|
|
2774
|
+
stdio: [
|
|
2775
|
+
"pipe",
|
|
2776
|
+
"pipe",
|
|
2777
|
+
"pipe"
|
|
2778
|
+
],
|
|
2779
|
+
encoding: "utf-8",
|
|
2780
|
+
input: appSecret
|
|
2781
|
+
});
|
|
2782
|
+
const configInitStdout = initRes.stdout?.trim() || void 0;
|
|
2783
|
+
const configInitStderr = initRes.stderr?.trim() || void 0;
|
|
2784
|
+
if (configInitStdout) console.error(`lark-cli config init stdout: ${configInitStdout}`);
|
|
2785
|
+
if (configInitStderr) console.error(`lark-cli config init stderr: ${configInitStderr}`);
|
|
2786
|
+
if (initRes.error) return {
|
|
2787
|
+
ok: false,
|
|
2788
|
+
configInitStdout,
|
|
2789
|
+
configInitStderr,
|
|
2790
|
+
error: `lark-cli config init spawn error: ${initRes.error.message}`
|
|
2791
|
+
};
|
|
2792
|
+
if (initRes.status !== 0) return {
|
|
2793
|
+
ok: false,
|
|
2794
|
+
configInitExitCode: initRes.status ?? void 0,
|
|
2795
|
+
configInitStdout,
|
|
2796
|
+
configInitStderr,
|
|
2797
|
+
error: `lark-cli config init exited with code ${initRes.status}`
|
|
2798
|
+
};
|
|
2799
|
+
appendPeToAgentsMd(agentsMdPath);
|
|
2800
|
+
return {
|
|
2801
|
+
ok: true,
|
|
2802
|
+
configInitExitCode: 0,
|
|
2803
|
+
agentsMdPath
|
|
2804
|
+
};
|
|
2805
|
+
}
|
|
2806
|
+
//#endregion
|
|
2807
|
+
//#region src/rules/agents-md-lark-cli-pe.ts
|
|
2808
|
+
function isLarkCliAvailable$1() {
|
|
2809
|
+
try {
|
|
2810
|
+
return (0, node_child_process.spawnSync)("lark-cli", ["--version"], {
|
|
2811
|
+
encoding: "utf-8",
|
|
2812
|
+
timeout: 5e3,
|
|
2813
|
+
stdio: [
|
|
2814
|
+
"ignore",
|
|
2815
|
+
"pipe",
|
|
2816
|
+
"ignore"
|
|
2817
|
+
]
|
|
2818
|
+
}).status === 0;
|
|
2819
|
+
} catch {
|
|
2820
|
+
return false;
|
|
2821
|
+
}
|
|
2822
|
+
}
|
|
2823
|
+
let AgentsMdLarkCliPeRule = class AgentsMdLarkCliPeRule extends DiagnoseRule {
|
|
2824
|
+
validate(ctx) {
|
|
2825
|
+
if (!isLarkCliAvailable$1()) return { pass: true };
|
|
2826
|
+
const missingPath = collectExistingAgentsMdPaths(ctx).find((filePath) => {
|
|
2827
|
+
return !node_fs.default.readFileSync(filePath, "utf-8").includes(`<${PE_XML_TAG}>`);
|
|
2828
|
+
});
|
|
2829
|
+
if (!missingPath) return { pass: true };
|
|
2830
|
+
return {
|
|
2831
|
+
pass: false,
|
|
2832
|
+
message: `${missingPath} 中缺少 lark-cli-pe PE 内容,需要追加`
|
|
2833
|
+
};
|
|
2834
|
+
}
|
|
2835
|
+
repair(ctx) {
|
|
2836
|
+
if (!isLarkCliAvailable$1()) return;
|
|
2837
|
+
for (const filePath of collectExistingAgentsMdPaths(ctx)) {
|
|
2838
|
+
const content = node_fs.default.readFileSync(filePath, "utf-8");
|
|
2839
|
+
if (content.includes(`<lark-cli-pe>`)) continue;
|
|
2840
|
+
const sep = content.length > 0 && !content.endsWith("\n") ? "\n" : "";
|
|
2841
|
+
node_fs.default.appendFileSync(filePath, `${sep}${PE_PLACEHOLDER}`, "utf-8");
|
|
2842
|
+
console.error(`agents-md-lark-cli-pe: appended PE to ${filePath}`);
|
|
2843
|
+
}
|
|
2844
|
+
}
|
|
2845
|
+
};
|
|
2846
|
+
AgentsMdLarkCliPeRule = __decorate([Rule({
|
|
2847
|
+
key: "agents_md_lark_cli_pe",
|
|
2848
|
+
description: "检测各智能体 AGENTS.md 中是否缺失 lark-cli-pe PE 内容,lark-cli 存在时自动追加",
|
|
2849
|
+
dependsOn: ["config_syntax_check"],
|
|
2850
|
+
repairMode: "standard",
|
|
2851
|
+
level: "silent"
|
|
2852
|
+
})], AgentsMdLarkCliPeRule);
|
|
2853
|
+
//#endregion
|
|
2523
2854
|
//#region src/rules/miaoda-official-plugins-install-spec-unlock.ts
|
|
2524
2855
|
/**
|
|
2525
2856
|
* Official miaoda-side plugins that must track manifest — version-locked specs
|
|
@@ -2623,11 +2954,11 @@ function getAllow$1(config) {
|
|
|
2623
2954
|
//#region src/rules/lark-plugin-allow.ts
|
|
2624
2955
|
const LARK_PLUGIN = "openclaw-lark";
|
|
2625
2956
|
const LEGACY_LARK_PLUGIN = "feishu-openclaw-plugin";
|
|
2626
|
-
const LARK_PLUGIN_NAMES
|
|
2957
|
+
const LARK_PLUGIN_NAMES = [LARK_PLUGIN, LEGACY_LARK_PLUGIN];
|
|
2627
2958
|
let LarkPluginAllowRule = class LarkPluginAllowRule extends DiagnoseRule {
|
|
2628
2959
|
validate(ctx) {
|
|
2629
2960
|
const allow = getAllow(ctx.config);
|
|
2630
|
-
if (LARK_PLUGIN_NAMES
|
|
2961
|
+
if (LARK_PLUGIN_NAMES.some((name) => allow.includes(name))) return { pass: true };
|
|
2631
2962
|
const installed = detectInstalledLarkPlugin(getExtensionsDir(ctx.configPath));
|
|
2632
2963
|
if (installed == null) return { pass: true };
|
|
2633
2964
|
return {
|
|
@@ -2646,7 +2977,7 @@ let LarkPluginAllowRule = class LarkPluginAllowRule extends DiagnoseRule {
|
|
|
2646
2977
|
const rawAllow = pluginsMap.allow;
|
|
2647
2978
|
const original = Array.isArray(rawAllow) ? rawAllow : [];
|
|
2648
2979
|
const stringAllow = original.filter((e) => typeof e === "string");
|
|
2649
|
-
if (LARK_PLUGIN_NAMES
|
|
2980
|
+
if (LARK_PLUGIN_NAMES.some((name) => stringAllow.includes(name))) return;
|
|
2650
2981
|
original.push(installed);
|
|
2651
2982
|
pluginsMap.allow = original;
|
|
2652
2983
|
}
|
|
@@ -3347,6 +3678,7 @@ function resolveUpgradeDirection(installed, ocCur, recommendedOc, isLegacy) {
|
|
|
3347
3678
|
/** 提取公共前置上下文;任何前置条件不满足时返回 null(规则 pass)。 */
|
|
3348
3679
|
function resolveCompatContext(ctx) {
|
|
3349
3680
|
const recommendedOc = ctx.vars.recommendedOpenclawTag;
|
|
3681
|
+
if (!recommendedOc) return null;
|
|
3350
3682
|
const ocCur = getOcVersion();
|
|
3351
3683
|
if (!ocCur) return null;
|
|
3352
3684
|
const installed = getInstalledPlugin(ctx);
|
|
@@ -3369,7 +3701,6 @@ let FeishuPluginOpenclawUpgradeRule = class FeishuPluginOpenclawUpgradeRule exte
|
|
|
3369
3701
|
if (!cc) return { pass: true };
|
|
3370
3702
|
const { ocCur, recommendedOc, installed, isLegacy } = cc;
|
|
3371
3703
|
if (isForkPlugin(installed)) return validateForkPlugin(installed, ocCur, recommendedOc);
|
|
3372
|
-
if (!recommendedOc) return { pass: true };
|
|
3373
3704
|
if (resolveUpgradeDirection(installed, ocCur, recommendedOc, isLegacy) !== "openclaw") return { pass: true };
|
|
3374
3705
|
return {
|
|
3375
3706
|
pass: false,
|
|
@@ -3397,14 +3728,6 @@ let FeishuPluginLarkUpgradeRule = class FeishuPluginLarkUpgradeRule extends Diag
|
|
|
3397
3728
|
if (!cc) return { pass: true };
|
|
3398
3729
|
const { ocCur, recommendedOc, installed, isLegacy } = cc;
|
|
3399
3730
|
if (isForkPlugin(installed)) return { pass: true };
|
|
3400
|
-
if (!recommendedOc) {
|
|
3401
|
-
if (isLegacy || !isVersionCompatible(installed, ocCur)) return {
|
|
3402
|
-
pass: false,
|
|
3403
|
-
action: "upgrade_lark",
|
|
3404
|
-
message: `${buildCompatPrefix(installed, ocCur, isLegacy)};建议升级飞书插件至兼容版本`
|
|
3405
|
-
};
|
|
3406
|
-
return { pass: true };
|
|
3407
|
-
}
|
|
3408
3731
|
if (resolveUpgradeDirection(installed, ocCur, recommendedOc, isLegacy) !== "lark") return { pass: true };
|
|
3409
3732
|
return {
|
|
3410
3733
|
pass: false,
|
|
@@ -3459,16 +3782,14 @@ function describeCompatConstraint(entry, pluginVersion) {
|
|
|
3459
3782
|
/**
|
|
3460
3783
|
* @lark-apaas/openclaw-lark 豁免 VERSION_COMPAT_MAP,但仍要求 openclaw ≥ FORK_LARK_PLUGIN_MIN_OC_VERSION。
|
|
3461
3784
|
* 其他 @lark-apaas scope 的 fork 插件继续无条件 pass。
|
|
3462
|
-
* recommendedOc 可为 undefined(doctor 模式),此时只检测最低版本要求,不指定目标升级版本。
|
|
3463
3785
|
*/
|
|
3464
3786
|
function validateForkPlugin(installed, ocCur, recommendedOc) {
|
|
3465
3787
|
if (installed.fullName !== FORK_LARK_PLUGIN_FULL_NAME) return { pass: true };
|
|
3466
3788
|
if (compareCalVer(ocCur, FORK_LARK_PLUGIN_MIN_OC_VERSION) >= 0) return { pass: true };
|
|
3467
|
-
const recommendation = recommendedOc ? `;将 openclaw 升级到 ${recommendedOc} 即可满足` : `;请升级 openclaw 至 ${FORK_LARK_PLUGIN_MIN_OC_VERSION} 或更高版本`;
|
|
3468
3789
|
return {
|
|
3469
3790
|
pass: false,
|
|
3470
3791
|
action: "upgrade_openclaw",
|
|
3471
|
-
message: `飞书插件 ${describePlugin(installed)}(fork 版)要求 openclaw ≥ ${FORK_LARK_PLUGIN_MIN_OC_VERSION},当前 openclaw@${ocCur}
|
|
3792
|
+
message: `飞书插件 ${describePlugin(installed)}(fork 版)要求 openclaw ≥ ${FORK_LARK_PLUGIN_MIN_OC_VERSION},当前 openclaw@${ocCur} 低于此要求;将 openclaw 升级到 ${recommendedOc} 即可满足`
|
|
3472
3793
|
};
|
|
3473
3794
|
}
|
|
3474
3795
|
function describePlugin(p) {
|
|
@@ -3515,188 +3836,6 @@ function extractScopedNameFromSpec$1(spec) {
|
|
|
3515
3836
|
const at = spec.indexOf("@", 1);
|
|
3516
3837
|
return at === -1 ? spec : spec.slice(0, at);
|
|
3517
3838
|
}
|
|
3518
|
-
/**
|
|
3519
|
-
* Returns true if the installed feishu plugin is version-incompatible with
|
|
3520
|
-
* the current openclaw (or is a legacy plugin that must be replaced).
|
|
3521
|
-
* Used by the upgrade_lark_needed rule and the upgrade-lark pre-check gate.
|
|
3522
|
-
*/
|
|
3523
|
-
function needsLarkUpgrade(ctx) {
|
|
3524
|
-
const cc = resolveCompatContext(ctx);
|
|
3525
|
-
if (!cc) return false;
|
|
3526
|
-
const { ocCur, recommendedOc, installed, isLegacy } = cc;
|
|
3527
|
-
if (isForkPlugin(installed)) {
|
|
3528
|
-
if (recommendedOc) return false;
|
|
3529
|
-
if (installed.fullName === FORK_LARK_PLUGIN_FULL_NAME) return compareCalVer(ocCur, FORK_LARK_PLUGIN_MIN_OC_VERSION) < 0;
|
|
3530
|
-
return false;
|
|
3531
|
-
}
|
|
3532
|
-
if (recommendedOc) return resolveUpgradeDirection(installed, ocCur, recommendedOc, isLegacy) === "lark";
|
|
3533
|
-
return isLegacy || !isVersionCompatible(installed, ocCur);
|
|
3534
|
-
}
|
|
3535
|
-
//#endregion
|
|
3536
|
-
//#region src/channels-probe.ts
|
|
3537
|
-
const FEISHU_INVALID_CONFIG_MSG = "channels.feishu: invalid config: must NOT have additional properties";
|
|
3538
|
-
const CHANNEL_LINE_RE = /^-\s+Feishu\s+([^:]+):\s+(.+)$/;
|
|
3539
|
-
/**
|
|
3540
|
-
* Port of Python `_account_is_working` from the feishu-channel-success-rate skill.
|
|
3541
|
-
*
|
|
3542
|
-
* Strips colon-prefixed key:value bits (dm:, bot:, in:, out:, token:, allow:,
|
|
3543
|
-
* intents:, groups:, health:) and evaluates the canonical health formula.
|
|
3544
|
-
*/
|
|
3545
|
-
function accountIsWorking(bits) {
|
|
3546
|
-
const bitTokens = /* @__PURE__ */ new Set();
|
|
3547
|
-
let hasError = false;
|
|
3548
|
-
let hasProbeFailed = false;
|
|
3549
|
-
for (const raw of bits) {
|
|
3550
|
-
const b = raw.trim();
|
|
3551
|
-
if (!b) continue;
|
|
3552
|
-
if (b.startsWith("error:")) {
|
|
3553
|
-
hasError = true;
|
|
3554
|
-
continue;
|
|
3555
|
-
}
|
|
3556
|
-
if (b === "probe failed") {
|
|
3557
|
-
hasProbeFailed = true;
|
|
3558
|
-
continue;
|
|
3559
|
-
}
|
|
3560
|
-
bitTokens.add(b.split(":")[0]);
|
|
3561
|
-
}
|
|
3562
|
-
if (!bitTokens.has("enabled") || !bitTokens.has("configured")) return false;
|
|
3563
|
-
if (bitTokens.has("works")) return true;
|
|
3564
|
-
if (bitTokens.has("running") && !hasError && !hasProbeFailed) return true;
|
|
3565
|
-
return false;
|
|
3566
|
-
}
|
|
3567
|
-
/**
|
|
3568
|
-
* Parse the raw stdout of `openclaw channels status --probe`.
|
|
3569
|
-
* Port of Python `extract_channels_probe` from the feishu-channel-success-rate skill.
|
|
3570
|
-
*/
|
|
3571
|
-
function parseChannelsProbeOutput(text) {
|
|
3572
|
-
const gatewayReachable = text.includes("Gateway reachable");
|
|
3573
|
-
const feishuConfigInvalid = text.includes(FEISHU_INVALID_CONFIG_MSG);
|
|
3574
|
-
const accounts = [];
|
|
3575
|
-
let anyAccountWorking = false;
|
|
3576
|
-
for (const line of text.split("\n")) {
|
|
3577
|
-
const m = CHANNEL_LINE_RE.exec(line.trim());
|
|
3578
|
-
if (!m) continue;
|
|
3579
|
-
const [, acct, rest] = m;
|
|
3580
|
-
const bits = rest.split(",").map((b) => b.trim());
|
|
3581
|
-
const isWorking = accountIsWorking(bits);
|
|
3582
|
-
if (isWorking) anyAccountWorking = true;
|
|
3583
|
-
accounts.push({
|
|
3584
|
-
id: acct.trim(),
|
|
3585
|
-
bits,
|
|
3586
|
-
isWorking,
|
|
3587
|
-
raw: line.trim()
|
|
3588
|
-
});
|
|
3589
|
-
}
|
|
3590
|
-
return {
|
|
3591
|
-
gatewayReachable,
|
|
3592
|
-
feishuConfigInvalid,
|
|
3593
|
-
accounts,
|
|
3594
|
-
anyAccountWorking
|
|
3595
|
-
};
|
|
3596
|
-
}
|
|
3597
|
-
/**
|
|
3598
|
-
* Run `openclaw channels status --probe` and return a structured result.
|
|
3599
|
-
*
|
|
3600
|
-
* The command may exit non-zero when some bot accounts fail their probe — that
|
|
3601
|
-
* is still useful output. We therefore try to parse stdout even when the
|
|
3602
|
-
* process exits with a non-zero code, falling back to an unavailable result
|
|
3603
|
-
* only when there is genuinely no output to parse.
|
|
3604
|
-
*
|
|
3605
|
-
* @param timeoutMs Maximum wait time. Default is 60 s because v2026.4.x
|
|
3606
|
-
* lacks a per-request HTTP timeout and can block indefinitely.
|
|
3607
|
-
*/
|
|
3608
|
-
function runChannelsProbe(timeoutMs = 6e4) {
|
|
3609
|
-
let stdout = "";
|
|
3610
|
-
let stderrText = "";
|
|
3611
|
-
let execError;
|
|
3612
|
-
try {
|
|
3613
|
-
stdout = (0, node_child_process.execSync)("openclaw channels status --probe", {
|
|
3614
|
-
encoding: "utf-8",
|
|
3615
|
-
timeout: timeoutMs,
|
|
3616
|
-
stdio: [
|
|
3617
|
-
"ignore",
|
|
3618
|
-
"pipe",
|
|
3619
|
-
"pipe"
|
|
3620
|
-
]
|
|
3621
|
-
});
|
|
3622
|
-
} catch (e) {
|
|
3623
|
-
const err = e;
|
|
3624
|
-
const stdoutRaw = err.stdout;
|
|
3625
|
-
stdout = typeof stdoutRaw === "string" ? stdoutRaw : stdoutRaw?.toString("utf-8") ?? "";
|
|
3626
|
-
execError = err.message;
|
|
3627
|
-
const stderrRaw = err.stderr;
|
|
3628
|
-
stderrText = (typeof stderrRaw === "string" ? stderrRaw : stderrRaw?.toString("utf-8") ?? "").trim();
|
|
3629
|
-
if (stderrText) console.error(`channels-probe: stderr from CLI: ${stderrText}`);
|
|
3630
|
-
}
|
|
3631
|
-
if (stdout.trim()) return {
|
|
3632
|
-
available: true,
|
|
3633
|
-
...parseChannelsProbeOutput(stdout)
|
|
3634
|
-
};
|
|
3635
|
-
return {
|
|
3636
|
-
available: false,
|
|
3637
|
-
gatewayReachable: false,
|
|
3638
|
-
feishuConfigInvalid: stderrText.includes(FEISHU_INVALID_CONFIG_MSG),
|
|
3639
|
-
accounts: [],
|
|
3640
|
-
anyAccountWorking: false,
|
|
3641
|
-
error: execError ?? "no output from openclaw channels status --probe"
|
|
3642
|
-
};
|
|
3643
|
-
}
|
|
3644
|
-
//#endregion
|
|
3645
|
-
//#region src/rules/upgrade-lark-needed.ts
|
|
3646
|
-
/**
|
|
3647
|
-
* Detects the condition that warrants running `upgrade-lark`:
|
|
3648
|
-
* - feishu plugin version incompatible with current openclaw, OR
|
|
3649
|
-
* - openclaw channels status --probe reports feishu channel config invalid; AND
|
|
3650
|
-
* - channels are not working.
|
|
3651
|
-
*
|
|
3652
|
-
* Both conditions must be true simultaneously. If version is compatible and
|
|
3653
|
-
* feishu config is valid, or channels are working, the rule passes (no action needed).
|
|
3654
|
-
*
|
|
3655
|
-
* feishuConfigInvalid is read from the channels probe output rather than running a
|
|
3656
|
-
* separate `openclaw status` call, since only `openclaw channels status --probe`
|
|
3657
|
-
* reliably surfaces the schema validation error.
|
|
3658
|
-
*
|
|
3659
|
-
* profile: experimental — runs only in full sweep mode, not in standard doctor.
|
|
3660
|
-
* level: silent — telemetry/sweep-only, does not trigger page-level repair UI.
|
|
3661
|
-
*/
|
|
3662
|
-
let UpgradeLarkNeededRule = class UpgradeLarkNeededRule extends DiagnoseRule {
|
|
3663
|
-
validate(ctx) {
|
|
3664
|
-
let versionIncompatible = false;
|
|
3665
|
-
try {
|
|
3666
|
-
versionIncompatible = needsLarkUpgrade(ctx);
|
|
3667
|
-
} catch {
|
|
3668
|
-
versionIncompatible = true;
|
|
3669
|
-
}
|
|
3670
|
-
let probeResult;
|
|
3671
|
-
try {
|
|
3672
|
-
probeResult = runChannelsProbe(6e4);
|
|
3673
|
-
} catch {
|
|
3674
|
-
probeResult = {
|
|
3675
|
-
available: false,
|
|
3676
|
-
gatewayReachable: false,
|
|
3677
|
-
feishuConfigInvalid: false,
|
|
3678
|
-
accounts: [],
|
|
3679
|
-
anyAccountWorking: false
|
|
3680
|
-
};
|
|
3681
|
-
}
|
|
3682
|
-
const feishuConfigInvalid = probeResult.feishuConfigInvalid;
|
|
3683
|
-
if (!(versionIncompatible || feishuConfigInvalid)) return { pass: true };
|
|
3684
|
-
if (probeResult.anyAccountWorking) return { pass: true };
|
|
3685
|
-
return {
|
|
3686
|
-
pass: false,
|
|
3687
|
-
action: "upgrade_lark",
|
|
3688
|
-
message: `飞书插件需要升级且 channels 不可用(版本不兼容=${versionIncompatible}, feishu配置无效=${feishuConfigInvalid}),建议执行 upgrade-lark 命令升级飞书插件`
|
|
3689
|
-
};
|
|
3690
|
-
}
|
|
3691
|
-
};
|
|
3692
|
-
UpgradeLarkNeededRule = __decorate([Rule({
|
|
3693
|
-
key: "upgrade_lark_needed",
|
|
3694
|
-
description: "检测飞书插件版本不兼容且 channels 不可用,判断是否需要执行 upgrade-lark 升级",
|
|
3695
|
-
repairMode: "check-only",
|
|
3696
|
-
level: "silent",
|
|
3697
|
-
profile: "experimental",
|
|
3698
|
-
usesVars: ["recommendedOpenclawTag"]
|
|
3699
|
-
})], UpgradeLarkNeededRule);
|
|
3700
3839
|
//#endregion
|
|
3701
3840
|
//#region src/rules/cleanup-install-backup-dirs.ts
|
|
3702
3841
|
const DIR_PREFIX = ".openclaw-install-";
|
|
@@ -3780,7 +3919,7 @@ function extractScopedNameFromSpec(spec) {
|
|
|
3780
3919
|
const at = spec.indexOf("@", 1);
|
|
3781
3920
|
return at === -1 ? spec : spec.slice(0, at);
|
|
3782
3921
|
}
|
|
3783
|
-
function isLarkCliAvailable
|
|
3922
|
+
function isLarkCliAvailable() {
|
|
3784
3923
|
try {
|
|
3785
3924
|
return (0, node_child_process.spawnSync)(LARK_CLI_NAME$1, ["--version"], {
|
|
3786
3925
|
encoding: "utf-8",
|
|
@@ -3821,26 +3960,137 @@ function installLarkCliOnce(tag) {
|
|
|
3821
3960
|
let LarkCliMissingForInstalledLarkPluginRule = class LarkCliMissingForInstalledLarkPluginRule extends DiagnoseRule {
|
|
3822
3961
|
validate(ctx) {
|
|
3823
3962
|
if (!isTargetForkPlugin(readInstalledLarkPlugin(ctx))) return { pass: true };
|
|
3824
|
-
if (isLarkCliAvailable
|
|
3963
|
+
if (isLarkCliAvailable()) return { pass: true };
|
|
3964
|
+
return {
|
|
3965
|
+
pass: false,
|
|
3966
|
+
message: `${FORK_PACKAGE_NAME}@${TARGET_VERSION} 已安装,但 lark-cli 不可用;将执行一次 lark-cli 安装`
|
|
3967
|
+
};
|
|
3968
|
+
}
|
|
3969
|
+
repair(ctx) {
|
|
3970
|
+
if (!isTargetForkPlugin(readInstalledLarkPlugin(ctx))) return;
|
|
3971
|
+
if (isLarkCliAvailable()) return;
|
|
3972
|
+
installLarkCliOnce(ctx.vars.recommendedOpenclawTag ?? TARGET_VERSION);
|
|
3973
|
+
}
|
|
3974
|
+
};
|
|
3975
|
+
LarkCliMissingForInstalledLarkPluginRule = __decorate([Rule({
|
|
3976
|
+
key: "lark_cli_missing_for_installed_lark_plugin",
|
|
3977
|
+
description: "检测特定飞书插件版本已安装但 lark-cli 缺失的环境,并自动安装 lark-cli 一次",
|
|
3978
|
+
dependsOn: ["config_syntax_check"],
|
|
3979
|
+
repairMode: "standard",
|
|
3980
|
+
level: "critical",
|
|
3981
|
+
usesVars: ["recommendedOpenclawTag"]
|
|
3982
|
+
})], LarkCliMissingForInstalledLarkPluginRule);
|
|
3983
|
+
//#endregion
|
|
3984
|
+
//#region src/rules/feishu-bot-channel-config.ts
|
|
3985
|
+
/**
|
|
3986
|
+
* Ensures each bot account's channel config is correct:
|
|
3987
|
+
* 1. `allowFrom` contains its own `creatorOpenID` from larkApps
|
|
3988
|
+
* 2. `appSecret` is either the canonical provider-ref or matches larkApps plaintext
|
|
3989
|
+
*
|
|
3990
|
+
* Covers both multi-account (channels.feishu.accounts) and single-account
|
|
3991
|
+
* (channels.feishu.appId + allowFrom at top level) layouts.
|
|
3992
|
+
*/
|
|
3993
|
+
let FeishuBotChannelConfigRule = class FeishuBotChannelConfigRule extends DiagnoseRule {
|
|
3994
|
+
validate(ctx) {
|
|
3995
|
+
const larkApps = ctx.vars.larkApps;
|
|
3996
|
+
if (!larkApps || larkApps.length === 0) return { pass: true };
|
|
3997
|
+
const feishu = asRecord(getNestedMap(ctx.config, "channels", "feishu"));
|
|
3998
|
+
if (!feishu) return { pass: true };
|
|
3999
|
+
const issues = [];
|
|
4000
|
+
const accounts = asRecord(feishu.accounts);
|
|
4001
|
+
if (accounts) for (const [accountId, account] of Object.entries(accounts)) {
|
|
4002
|
+
const bot = asRecord(account);
|
|
4003
|
+
if (!bot) continue;
|
|
4004
|
+
const appId = bot.appId;
|
|
4005
|
+
if (typeof appId !== "string" || !appId.startsWith("cli_")) continue;
|
|
4006
|
+
const larkApp = larkApps.find((e) => e.larkAppID === appId);
|
|
4007
|
+
if (!larkApp) continue;
|
|
4008
|
+
this.checkBot(accountId, bot, larkApp, issues);
|
|
4009
|
+
}
|
|
4010
|
+
const singleAppId = feishu.appId;
|
|
4011
|
+
if (typeof singleAppId === "string" && singleAppId.startsWith("cli_") && !accounts) {
|
|
4012
|
+
const larkApp = larkApps.find((e) => e.larkAppID === singleAppId);
|
|
4013
|
+
if (larkApp) this.checkBot("feishu", feishu, larkApp, issues);
|
|
4014
|
+
}
|
|
4015
|
+
if (issues.length === 0) return { pass: true };
|
|
3825
4016
|
return {
|
|
3826
4017
|
pass: false,
|
|
3827
|
-
message:
|
|
4018
|
+
message: issues.join("; ")
|
|
3828
4019
|
};
|
|
3829
4020
|
}
|
|
4021
|
+
/** Check a single bot entry (either an account object or the feishu channel itself).
|
|
4022
|
+
* appSecret is validated based on its current type:
|
|
4023
|
+
* - object → must match canonical provider-ref
|
|
4024
|
+
* - string → must match larkApps plaintext
|
|
4025
|
+
*/
|
|
4026
|
+
checkBot(label, bot, larkApp, issues) {
|
|
4027
|
+
const creatorOpenID = larkApp.creatorOpenID;
|
|
4028
|
+
const allowFrom = Array.isArray(bot.allowFrom) ? bot.allowFrom : [];
|
|
4029
|
+
if (typeof creatorOpenID === "string" && creatorOpenID !== "") {
|
|
4030
|
+
if (!allowFrom.includes(creatorOpenID)) issues.push(`${label} allowFrom missing creatorOpenID ${creatorOpenID.length > 8 ? creatorOpenID.slice(0, 4) + "***" + creatorOpenID.slice(-4) : "***"}`);
|
|
4031
|
+
} else if (allowFrom.length === 0) issues.push(`${label} allowFrom is empty (creatorOpenID unavailable, cannot auto-fix)`);
|
|
4032
|
+
const secret = bot.appSecret;
|
|
4033
|
+
if (typeof secret === "object" && secret !== null && !Array.isArray(secret)) {
|
|
4034
|
+
if (!matchMap(secret, DEFAULT_FEISHU_APP_SECRET)) issues.push(`${label} appSecret is a provider-ref but not the canonical one`);
|
|
4035
|
+
} else if (typeof secret === "string") {
|
|
4036
|
+
if (secret !== larkApp.appSecret) issues.push(`${label} appSecret plaintext mismatch`);
|
|
4037
|
+
} else issues.push(`${label} appSecret has unexpected type ${typeof secret}`);
|
|
4038
|
+
}
|
|
3830
4039
|
repair(ctx) {
|
|
3831
|
-
|
|
3832
|
-
if (
|
|
3833
|
-
|
|
4040
|
+
const larkApps = ctx.vars.larkApps;
|
|
4041
|
+
if (!larkApps || larkApps.length === 0) return;
|
|
4042
|
+
const feishu = asRecord(getNestedMap(ctx.config, "channels", "feishu"));
|
|
4043
|
+
if (!feishu) return;
|
|
4044
|
+
const accounts = asRecord(feishu.accounts);
|
|
4045
|
+
if (accounts) for (const [, account] of Object.entries(accounts)) {
|
|
4046
|
+
const bot = asRecord(account);
|
|
4047
|
+
if (!bot) continue;
|
|
4048
|
+
const appId = bot.appId;
|
|
4049
|
+
if (typeof appId !== "string" || !appId.startsWith("cli_")) continue;
|
|
4050
|
+
const larkApp = larkApps.find((e) => e.larkAppID === appId);
|
|
4051
|
+
if (!larkApp) continue;
|
|
4052
|
+
this.fixBot(bot, larkApp);
|
|
4053
|
+
}
|
|
4054
|
+
const singleAppId = feishu.appId;
|
|
4055
|
+
if (typeof singleAppId === "string" && singleAppId.startsWith("cli_") && !accounts) {
|
|
4056
|
+
const larkApp = larkApps.find((e) => e.larkAppID === singleAppId);
|
|
4057
|
+
if (larkApp) this.fixBot(feishu, larkApp);
|
|
4058
|
+
}
|
|
4059
|
+
}
|
|
4060
|
+
/** Fix a single bot entry in-place.
|
|
4061
|
+
* appSecret is repaired based on its current type:
|
|
4062
|
+
* - object → fix to canonical provider-ref
|
|
4063
|
+
* - string → fix to larkApps plaintext
|
|
4064
|
+
*/
|
|
4065
|
+
fixBot(bot, larkApp) {
|
|
4066
|
+
const creatorOpenID = larkApp.creatorOpenID;
|
|
4067
|
+
if (typeof creatorOpenID === "string" && creatorOpenID !== "") {
|
|
4068
|
+
const allowFrom = Array.isArray(bot.allowFrom) ? [...bot.allowFrom] : [];
|
|
4069
|
+
if (!allowFrom.includes(creatorOpenID)) {
|
|
4070
|
+
allowFrom.push(creatorOpenID);
|
|
4071
|
+
bot.allowFrom = allowFrom;
|
|
4072
|
+
}
|
|
4073
|
+
}
|
|
4074
|
+
const secret = bot.appSecret;
|
|
4075
|
+
if (typeof secret === "object" && secret !== null && !Array.isArray(secret)) {
|
|
4076
|
+
if (!matchMap(secret, DEFAULT_FEISHU_APP_SECRET)) bot.appSecret = { ...DEFAULT_FEISHU_APP_SECRET };
|
|
4077
|
+
} else if (typeof secret === "string") {
|
|
4078
|
+
if (secret !== larkApp.appSecret) bot.appSecret = larkApp.appSecret;
|
|
4079
|
+
}
|
|
3834
4080
|
}
|
|
3835
4081
|
};
|
|
3836
|
-
|
|
3837
|
-
key: "
|
|
3838
|
-
description: "
|
|
3839
|
-
dependsOn: [
|
|
4082
|
+
FeishuBotChannelConfigRule = __decorate([Rule({
|
|
4083
|
+
key: "feishu_bot_channel_config",
|
|
4084
|
+
description: "确保飞书配置中 bot 账号的 allowFrom 包含其创建者 openID 且 appSecret 值正确",
|
|
4085
|
+
dependsOn: [
|
|
4086
|
+
"config_syntax_check",
|
|
4087
|
+
"feishu_default_account",
|
|
4088
|
+
"feishu_bot_id"
|
|
4089
|
+
],
|
|
3840
4090
|
repairMode: "standard",
|
|
3841
|
-
|
|
3842
|
-
|
|
3843
|
-
})],
|
|
4091
|
+
usesVars: ["larkApps"],
|
|
4092
|
+
level: "critical"
|
|
4093
|
+
})], FeishuBotChannelConfigRule);
|
|
3844
4094
|
//#endregion
|
|
3845
4095
|
//#region src/check.ts
|
|
3846
4096
|
/** Telemetry-aware entry: returns both the legacy CheckResult (for stdout)
|
|
@@ -4282,33 +4532,6 @@ function finalize$1(results, aborted) {
|
|
|
4282
4532
|
};
|
|
4283
4533
|
}
|
|
4284
4534
|
//#endregion
|
|
4285
|
-
//#region src/paths.ts
|
|
4286
|
-
/**
|
|
4287
|
-
* Central directory for all ephemeral diagnose/reset artifacts: task status
|
|
4288
|
-
* files (`reset-<taskId>.json`) and human-readable step logs
|
|
4289
|
-
* (`reset-<taskId>.log`). Having everything under one dir makes debugging a
|
|
4290
|
-
* stuck reset much easier — `ls /tmp/openclaw-diagnose/` shows every recent
|
|
4291
|
-
* run, and each run's log is right next to its state.
|
|
4292
|
-
*/
|
|
4293
|
-
const DIAGNOSE_DIR = "/tmp/openclaw-diagnose";
|
|
4294
|
-
function resetResultFile(taskId) {
|
|
4295
|
-
return `${DIAGNOSE_DIR}/reset-${taskId}.json`;
|
|
4296
|
-
}
|
|
4297
|
-
function resetLogFile(taskId) {
|
|
4298
|
-
return `${DIAGNOSE_DIR}/reset-${taskId}.log`;
|
|
4299
|
-
}
|
|
4300
|
-
/** Sandbox workspace root where openclaw config + agent state lives. */
|
|
4301
|
-
const WORKSPACE_DIR = "/home/gem/workspace/agent";
|
|
4302
|
-
/** File containing the provider key used by the openclaw miaoda provider. */
|
|
4303
|
-
const PROVIDER_FILE_PATH = "/home/gem/workspace/.force/openclaw/miaoda-provider-key";
|
|
4304
|
-
/** File containing the miaoda openclaw secrets JSON. */
|
|
4305
|
-
const SECRETS_FILE_PATH = "/home/gem/workspace/.force/openclaw/miaoda-openclaw-secrets.json";
|
|
4306
|
-
/** Absolute path to the openclaw config JSON. */
|
|
4307
|
-
const CONFIG_PATH = `${WORKSPACE_DIR}/openclaw.json`;
|
|
4308
|
-
function upgradeLarkLogFile(runId) {
|
|
4309
|
-
return `${DIAGNOSE_DIR}/upgrade-lark-${(/* @__PURE__ */ new Date()).toISOString().slice(0, 19).replace(/:/g, "-")}-${runId.slice(0, 8)}.log`;
|
|
4310
|
-
}
|
|
4311
|
-
//#endregion
|
|
4312
4535
|
//#region src/run-log.ts
|
|
4313
4536
|
let currentRunContext;
|
|
4314
4537
|
/**
|
|
@@ -4444,9 +4667,10 @@ function makeLogger(logFile) {
|
|
|
4444
4667
|
/**
|
|
4445
4668
|
* Start an async reset task: spawn a detached child process and return the taskId.
|
|
4446
4669
|
*
|
|
4447
|
-
* The child process runs: node cli.js reset --worker --task-id=xxx
|
|
4670
|
+
* The child process runs: node cli.js reset --worker --task-id=xxx
|
|
4671
|
+
* The worker fetches ctx from innerApi itself — no --ctx passthrough.
|
|
4448
4672
|
*/
|
|
4449
|
-
function startAsyncReset(
|
|
4673
|
+
function startAsyncReset() {
|
|
4450
4674
|
const taskId = (0, node_crypto.randomUUID)();
|
|
4451
4675
|
const resultFile = resetResultFile(taskId);
|
|
4452
4676
|
const log = makeLogger(resetLogFile(taskId));
|
|
@@ -4470,8 +4694,7 @@ function startAsyncReset(ctxBase64) {
|
|
|
4470
4694
|
process.argv[1],
|
|
4471
4695
|
"reset",
|
|
4472
4696
|
"--worker",
|
|
4473
|
-
`--task-id=${taskId}
|
|
4474
|
-
`--ctx=${ctxBase64}`
|
|
4697
|
+
`--task-id=${taskId}`
|
|
4475
4698
|
], {
|
|
4476
4699
|
detached: true,
|
|
4477
4700
|
stdio: "ignore",
|
|
@@ -4873,273 +5096,13 @@ function installOne$1(pkg, tarball, homeBase) {
|
|
|
4873
5096
|
} catch {}
|
|
4874
5097
|
throw e;
|
|
4875
5098
|
}
|
|
4876
|
-
const hadOld = node_fs.default.existsSync(destDir);
|
|
4877
|
-
if (hadOld) moveSafe(destDir, oldDir);
|
|
4878
|
-
moveSafe(stagingDir, destDir);
|
|
4879
|
-
if (hadOld && node_fs.default.existsSync(oldDir)) node_fs.default.rmSync(oldDir, {
|
|
4880
|
-
recursive: true,
|
|
4881
|
-
force: true
|
|
4882
|
-
});
|
|
4883
|
-
}
|
|
4884
|
-
//#endregion
|
|
4885
|
-
//#region src/lark-cli-init.ts
|
|
4886
|
-
const LARK_PLUGIN_NAMES = ["openclaw-lark", "feishu-openclaw-plugin"];
|
|
4887
|
-
const PE_XML_TAG = "lark-cli-pe";
|
|
4888
|
-
const PE_PLACEHOLDER = `
|
|
4889
|
-
<${PE_XML_TAG}>
|
|
4890
|
-
**【强制要求 - 无例外】飞书所有操作必须先读 feishu-lark-cli skill。未读skill不得调用任何飞书工具,违者视为操作失误。**
|
|
4891
|
-
</${PE_XML_TAG}>
|
|
4892
|
-
`;
|
|
4893
|
-
function isLarkPluginInstalled(configPath) {
|
|
4894
|
-
const extDir = getExtensionsDir(configPath);
|
|
4895
|
-
return LARK_PLUGIN_NAMES.some((name) => {
|
|
4896
|
-
try {
|
|
4897
|
-
return node_fs.default.existsSync(node_path.default.join(extDir, name, "package.json"));
|
|
4898
|
-
} catch {
|
|
4899
|
-
return false;
|
|
4900
|
-
}
|
|
4901
|
-
});
|
|
4902
|
-
}
|
|
4903
|
-
function isLarkCliAvailable() {
|
|
4904
|
-
try {
|
|
4905
|
-
return (0, node_child_process.spawnSync)("lark-cli", ["--version"], {
|
|
4906
|
-
encoding: "utf-8",
|
|
4907
|
-
timeout: 5e3,
|
|
4908
|
-
stdio: [
|
|
4909
|
-
"ignore",
|
|
4910
|
-
"pipe",
|
|
4911
|
-
"ignore"
|
|
4912
|
-
]
|
|
4913
|
-
}).status === 0;
|
|
4914
|
-
} catch {
|
|
4915
|
-
return false;
|
|
4916
|
-
}
|
|
4917
|
-
}
|
|
4918
|
-
function readConfig(configPath) {
|
|
4919
|
-
try {
|
|
4920
|
-
const raw = node_fs.default.readFileSync(configPath, "utf-8");
|
|
4921
|
-
const parsed = loadJSON5().parse(raw);
|
|
4922
|
-
return parsed != null && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
|
|
4923
|
-
} catch {
|
|
4924
|
-
return null;
|
|
4925
|
-
}
|
|
4926
|
-
}
|
|
4927
|
-
/**
|
|
4928
|
-
* Resolve the feishu app secret for the given appId.
|
|
4929
|
-
*
|
|
4930
|
-
* Lookup order:
|
|
4931
|
-
* 1. channels.feishu.appSecret (single-agent: feishu.appId === appId)
|
|
4932
|
-
* 2. channels.feishu.accounts[key].appSecret (multi-agent: account.appId === appId)
|
|
4933
|
-
*
|
|
4934
|
-
* Value interpretation:
|
|
4935
|
-
* - string → use directly
|
|
4936
|
-
* - object → secret is managed by a provider; use `feishuAppSecret` param instead
|
|
4937
|
-
*
|
|
4938
|
-
* Returns null when the secret cannot be determined.
|
|
4939
|
-
*/
|
|
4940
|
-
function resolveAppSecret(appId, config, feishuAppSecret) {
|
|
4941
|
-
const feishu = getNestedMap(config, "channels", "feishu");
|
|
4942
|
-
if (!feishu) return null;
|
|
4943
|
-
let rawSecret;
|
|
4944
|
-
if (typeof feishu.appId === "string" && feishu.appId === appId) rawSecret = feishu.appSecret;
|
|
4945
|
-
else {
|
|
4946
|
-
const accounts = asRecord(feishu.accounts);
|
|
4947
|
-
if (accounts) for (const [, val] of Object.entries(accounts)) {
|
|
4948
|
-
const account = asRecord(val);
|
|
4949
|
-
if (account?.appId === appId) {
|
|
4950
|
-
rawSecret = account.appSecret ?? feishu.appSecret;
|
|
4951
|
-
break;
|
|
4952
|
-
}
|
|
4953
|
-
}
|
|
4954
|
-
}
|
|
4955
|
-
if (typeof rawSecret === "string" && rawSecret) return rawSecret;
|
|
4956
|
-
if (rawSecret != null && typeof rawSecret === "object") return feishuAppSecret ?? null;
|
|
4957
|
-
return null;
|
|
4958
|
-
}
|
|
4959
|
-
/**
|
|
4960
|
-
* Resolve the agents.md path for the given appId from the openclaw config.
|
|
4961
|
-
*
|
|
4962
|
-
* Case 1: appId matches channels.feishu.appId (single-agent path)
|
|
4963
|
-
* → WORKSPACE_DIR/AGENTS.md
|
|
4964
|
-
*
|
|
4965
|
-
* Case 2: appId found in channels.feishu.accounts (multi-agent path)
|
|
4966
|
-
* → find account key where account.appId === appId
|
|
4967
|
-
* → find binding where match.channel=feishu && match.accountId=that key
|
|
4968
|
-
* → if agentId === 'main' → WORKSPACE_DIR/agents.md
|
|
4969
|
-
* → else find agent in agents.list by id → agent.workspace/agents.md
|
|
4970
|
-
*
|
|
4971
|
-
* Returns null when the path cannot be determined.
|
|
4972
|
-
*/
|
|
4973
|
-
function resolveAgentsMdPath(appId, config) {
|
|
4974
|
-
const feishu = getNestedMap(config, "channels", "feishu");
|
|
4975
|
-
if (!feishu) {
|
|
4976
|
-
console.error("resolveAgentsMdPath: channels.feishu not found");
|
|
4977
|
-
return null;
|
|
4978
|
-
}
|
|
4979
|
-
if (typeof feishu.appId === "string" && feishu.appId === appId) {
|
|
4980
|
-
console.error(`resolveAgentsMdPath: case=single-agent feishu.appId=${feishu.appId}`);
|
|
4981
|
-
return node_path.default.join(WORKSPACE_DIR, "workspace", "AGENTS.md");
|
|
4982
|
-
}
|
|
4983
|
-
const accounts = asRecord(feishu.accounts);
|
|
4984
|
-
if (!accounts) {
|
|
4985
|
-
console.error("resolveAgentsMdPath: feishu.accounts not found");
|
|
4986
|
-
return null;
|
|
4987
|
-
}
|
|
4988
|
-
let accountId;
|
|
4989
|
-
for (const [key, val] of Object.entries(accounts)) if (asRecord(val)?.appId === appId) {
|
|
4990
|
-
accountId = key;
|
|
4991
|
-
break;
|
|
4992
|
-
}
|
|
4993
|
-
if (!accountId) {
|
|
4994
|
-
console.error(`resolveAgentsMdPath: no account found with appId=${appId} in feishu.accounts keys=[${Object.keys(accounts).join(",")}]`);
|
|
4995
|
-
return null;
|
|
4996
|
-
}
|
|
4997
|
-
console.error(`resolveAgentsMdPath: found accountId=${accountId}`);
|
|
4998
|
-
const bindings = Array.isArray(config.bindings) ? config.bindings : [];
|
|
4999
|
-
let agentId;
|
|
5000
|
-
for (const b of bindings) {
|
|
5001
|
-
const binding = asRecord(b);
|
|
5002
|
-
if (!binding) continue;
|
|
5003
|
-
const match = asRecord(binding.match);
|
|
5004
|
-
if (match?.channel === "feishu" && match?.accountId === accountId) {
|
|
5005
|
-
if (typeof binding.agentId === "string") {
|
|
5006
|
-
agentId = binding.agentId;
|
|
5007
|
-
break;
|
|
5008
|
-
}
|
|
5009
|
-
}
|
|
5010
|
-
}
|
|
5011
|
-
if (!agentId) {
|
|
5012
|
-
console.error(`resolveAgentsMdPath: no binding found for accountId=${accountId} in bindings(count=${bindings.length})`);
|
|
5013
|
-
return null;
|
|
5014
|
-
}
|
|
5015
|
-
console.error(`resolveAgentsMdPath: found agentId=${agentId}`);
|
|
5016
|
-
if (agentId === "main") {
|
|
5017
|
-
console.error("resolveAgentsMdPath: case=multi-agent-main");
|
|
5018
|
-
return node_path.default.join(WORKSPACE_DIR, "workspace", "AGENTS.md");
|
|
5019
|
-
}
|
|
5020
|
-
const agentsObj = asRecord(config.agents);
|
|
5021
|
-
const list = Array.isArray(agentsObj?.list) ? agentsObj.list : [];
|
|
5022
|
-
for (const a of list) {
|
|
5023
|
-
const agent = asRecord(a);
|
|
5024
|
-
if (agent?.id === agentId) {
|
|
5025
|
-
const ws = typeof agent.workspace === "string" ? agent.workspace : node_path.default.join(WORKSPACE_DIR, "workspace");
|
|
5026
|
-
console.error(`resolveAgentsMdPath: case=multi-agent-custom agentId=${agentId} workspace=${ws}`);
|
|
5027
|
-
return node_path.default.join(ws, "AGENTS.md");
|
|
5028
|
-
}
|
|
5029
|
-
}
|
|
5030
|
-
console.error(`resolveAgentsMdPath: agentId=${agentId} not found in agents.list(count=${list.length})`);
|
|
5031
|
-
return null;
|
|
5032
|
-
}
|
|
5033
|
-
function appendPeToAgentsMd(agentsMdPath) {
|
|
5034
|
-
const dir = node_path.default.dirname(agentsMdPath);
|
|
5035
|
-
if (!node_fs.default.existsSync(dir)) node_fs.default.mkdirSync(dir, { recursive: true });
|
|
5036
|
-
const existing = node_fs.default.existsSync(agentsMdPath) ? node_fs.default.readFileSync(agentsMdPath, "utf-8") : "";
|
|
5037
|
-
if (existing.includes(`<${PE_XML_TAG}>`)) {
|
|
5038
|
-
console.error(`lark-cli-init: <${PE_XML_TAG}> already present in ${agentsMdPath}, skipping`);
|
|
5039
|
-
return;
|
|
5040
|
-
}
|
|
5041
|
-
const sep = existing.length > 0 && !existing.endsWith("\n") ? "\n" : "";
|
|
5042
|
-
node_fs.default.appendFileSync(agentsMdPath, `${sep}${PE_PLACEHOLDER}`, "utf-8");
|
|
5043
|
-
console.error(`lark-cli-init: appended PE placeholder to ${agentsMdPath}`);
|
|
5044
|
-
}
|
|
5045
|
-
/**
|
|
5046
|
-
* Collect every Feishu bot appId declared in the openclaw config.
|
|
5047
|
-
* Covers both single-agent (channels.feishu.appId) and multi-agent
|
|
5048
|
-
* (channels.feishu.accounts[*].appId) layouts. Returns a deduplicated list.
|
|
5049
|
-
*/
|
|
5050
|
-
function collectFeishuAppIds(configPath) {
|
|
5051
|
-
const config = readConfig(configPath ?? CONFIG_PATH);
|
|
5052
|
-
if (!config) return [];
|
|
5053
|
-
const feishu = getNestedMap(config, "channels", "feishu");
|
|
5054
|
-
if (!feishu) return [];
|
|
5055
|
-
const appIds = /* @__PURE__ */ new Set();
|
|
5056
|
-
const topAppId = feishu.appId;
|
|
5057
|
-
if (typeof topAppId === "string" && topAppId.trim()) appIds.add(topAppId.trim());
|
|
5058
|
-
const accounts = asRecord(feishu.accounts);
|
|
5059
|
-
if (accounts) for (const val of Object.values(accounts)) {
|
|
5060
|
-
const appId = asRecord(val)?.appId;
|
|
5061
|
-
if (typeof appId === "string" && appId.trim()) appIds.add(appId.trim());
|
|
5062
|
-
}
|
|
5063
|
-
return [...appIds];
|
|
5064
|
-
}
|
|
5065
|
-
function runLarkCliInit(opts) {
|
|
5066
|
-
const configPath = opts.configPath ?? CONFIG_PATH;
|
|
5067
|
-
if (!isLarkPluginInstalled(configPath)) {
|
|
5068
|
-
console.error("lark-cli-init: skipping — openclaw-lark plugin not installed");
|
|
5069
|
-
return {
|
|
5070
|
-
ok: true,
|
|
5071
|
-
skipped: true,
|
|
5072
|
-
skipReason: "openclaw-lark plugin not installed"
|
|
5073
|
-
};
|
|
5074
|
-
}
|
|
5075
|
-
if (!isLarkCliAvailable()) {
|
|
5076
|
-
console.error("lark-cli-init: skipping — lark-cli command not found");
|
|
5077
|
-
return {
|
|
5078
|
-
ok: true,
|
|
5079
|
-
skipped: true,
|
|
5080
|
-
skipReason: "lark-cli command not found"
|
|
5081
|
-
};
|
|
5082
|
-
}
|
|
5083
|
-
const config = readConfig(configPath);
|
|
5084
|
-
if (!config) return {
|
|
5085
|
-
ok: false,
|
|
5086
|
-
error: `could not read config at ${configPath}`
|
|
5087
|
-
};
|
|
5088
|
-
const agentsMdPath = resolveAgentsMdPath(opts.appId, config);
|
|
5089
|
-
console.error(`lark-cli-init: resolved agents.md path=${agentsMdPath ?? "(null)"}`);
|
|
5090
|
-
if (!agentsMdPath) return {
|
|
5091
|
-
ok: false,
|
|
5092
|
-
error: `could not resolve agents.md path for appId=${opts.appId}`
|
|
5093
|
-
};
|
|
5094
|
-
const appSecret = resolveAppSecret(opts.appId, config, opts.feishuAppSecret);
|
|
5095
|
-
if (!appSecret) return {
|
|
5096
|
-
ok: false,
|
|
5097
|
-
error: `could not resolve appSecret for appId=${opts.appId}`
|
|
5098
|
-
};
|
|
5099
|
-
console.error(`lark-cli-init: running lark-cli config init --name ${opts.appId} --app-id ${opts.appId} --brand feishu --app-secret-stdin --force-init`);
|
|
5100
|
-
const initRes = (0, node_child_process.spawnSync)("lark-cli", [
|
|
5101
|
-
"config",
|
|
5102
|
-
"init",
|
|
5103
|
-
"--name",
|
|
5104
|
-
opts.appId,
|
|
5105
|
-
"--app-id",
|
|
5106
|
-
opts.appId,
|
|
5107
|
-
"--brand",
|
|
5108
|
-
"feishu",
|
|
5109
|
-
"--app-secret-stdin",
|
|
5110
|
-
"--force-init"
|
|
5111
|
-
], {
|
|
5112
|
-
stdio: [
|
|
5113
|
-
"pipe",
|
|
5114
|
-
"pipe",
|
|
5115
|
-
"pipe"
|
|
5116
|
-
],
|
|
5117
|
-
encoding: "utf-8",
|
|
5118
|
-
input: appSecret
|
|
5099
|
+
const hadOld = node_fs.default.existsSync(destDir);
|
|
5100
|
+
if (hadOld) moveSafe(destDir, oldDir);
|
|
5101
|
+
moveSafe(stagingDir, destDir);
|
|
5102
|
+
if (hadOld && node_fs.default.existsSync(oldDir)) node_fs.default.rmSync(oldDir, {
|
|
5103
|
+
recursive: true,
|
|
5104
|
+
force: true
|
|
5119
5105
|
});
|
|
5120
|
-
const configInitStdout = initRes.stdout?.trim() || void 0;
|
|
5121
|
-
const configInitStderr = initRes.stderr?.trim() || void 0;
|
|
5122
|
-
if (configInitStdout) console.error(`lark-cli config init stdout: ${configInitStdout}`);
|
|
5123
|
-
if (configInitStderr) console.error(`lark-cli config init stderr: ${configInitStderr}`);
|
|
5124
|
-
if (initRes.error) return {
|
|
5125
|
-
ok: false,
|
|
5126
|
-
configInitStdout,
|
|
5127
|
-
configInitStderr,
|
|
5128
|
-
error: `lark-cli config init spawn error: ${initRes.error.message}`
|
|
5129
|
-
};
|
|
5130
|
-
if (initRes.status !== 0) return {
|
|
5131
|
-
ok: false,
|
|
5132
|
-
configInitExitCode: initRes.status ?? void 0,
|
|
5133
|
-
configInitStdout,
|
|
5134
|
-
configInitStderr,
|
|
5135
|
-
error: `lark-cli config init exited with code ${initRes.status}`
|
|
5136
|
-
};
|
|
5137
|
-
appendPeToAgentsMd(agentsMdPath);
|
|
5138
|
-
return {
|
|
5139
|
-
ok: true,
|
|
5140
|
-
configInitExitCode: 0,
|
|
5141
|
-
agentsMdPath
|
|
5142
|
-
};
|
|
5143
5106
|
}
|
|
5144
5107
|
//#endregion
|
|
5145
5108
|
//#region ../../openclaw-slardar/lib/client.js
|
|
@@ -6985,6 +6948,60 @@ function mergeCoreBackupAndOrigins(configPath, vars, resetData, log) {
|
|
|
6985
6948
|
log(`allowedOrigins: added ${added.length} (${JSON.stringify(added)}), total now ${mergedOrigins.length}`);
|
|
6986
6949
|
}
|
|
6987
6950
|
/**
|
|
6951
|
+
* Fix bot account allowFrom and appSecret using larkApps from innerApi.
|
|
6952
|
+
*
|
|
6953
|
+
* For each bot account (key starts with `bot-cli_`):
|
|
6954
|
+
* - allowFrom must contain the bot's own creatorOpenID from larkApps
|
|
6955
|
+
* - appSecret must be either the canonical provider-ref or match larkApps plaintext
|
|
6956
|
+
*
|
|
6957
|
+
* Runs after mergeCoreBackupAndOrigins so it operates on the final config state.
|
|
6958
|
+
*/
|
|
6959
|
+
function fixBotChannelConfig(configPath, larkApps, log) {
|
|
6960
|
+
if (!larkApps || larkApps.length === 0) {
|
|
6961
|
+
log("no larkApps data, skip bot channel config fix");
|
|
6962
|
+
return;
|
|
6963
|
+
}
|
|
6964
|
+
const config = loadJSON5().parse(node_fs.default.readFileSync(configPath, "utf-8"));
|
|
6965
|
+
const accounts = asRecord(getNestedMap(config, "channels", "feishu")?.accounts);
|
|
6966
|
+
if (!accounts) {
|
|
6967
|
+
log("no feishu accounts in config, skip bot channel config fix");
|
|
6968
|
+
return;
|
|
6969
|
+
}
|
|
6970
|
+
let fixCount = 0;
|
|
6971
|
+
for (const [, account] of Object.entries(accounts)) {
|
|
6972
|
+
const bot = asRecord(account);
|
|
6973
|
+
if (!bot) continue;
|
|
6974
|
+
const appId = bot.appId;
|
|
6975
|
+
if (typeof appId !== "string" || !appId.startsWith("cli_")) continue;
|
|
6976
|
+
const larkApp = larkApps.find((e) => e.larkAppID === appId);
|
|
6977
|
+
if (!larkApp) continue;
|
|
6978
|
+
const creatorOpenID = larkApp.creatorOpenID;
|
|
6979
|
+
if (typeof creatorOpenID === "string" && creatorOpenID !== "") {
|
|
6980
|
+
const allowFrom = Array.isArray(bot.allowFrom) ? [...bot.allowFrom] : [];
|
|
6981
|
+
if (!allowFrom.includes(creatorOpenID)) {
|
|
6982
|
+
allowFrom.push(creatorOpenID);
|
|
6983
|
+
bot.allowFrom = allowFrom;
|
|
6984
|
+
fixCount++;
|
|
6985
|
+
}
|
|
6986
|
+
}
|
|
6987
|
+
const secret = bot.appSecret;
|
|
6988
|
+
let needsFix = false;
|
|
6989
|
+
if (typeof secret === "object" && secret !== null && !Array.isArray(secret)) {
|
|
6990
|
+
if (!matchMap(secret, DEFAULT_FEISHU_APP_SECRET)) needsFix = true;
|
|
6991
|
+
} else if (typeof secret === "string") {
|
|
6992
|
+
if (secret !== larkApp.appSecret) needsFix = true;
|
|
6993
|
+
} else needsFix = true;
|
|
6994
|
+
if (needsFix) {
|
|
6995
|
+
bot.appSecret = { ...DEFAULT_FEISHU_APP_SECRET };
|
|
6996
|
+
fixCount++;
|
|
6997
|
+
}
|
|
6998
|
+
}
|
|
6999
|
+
if (fixCount > 0) {
|
|
7000
|
+
node_fs.default.writeFileSync(configPath, JSON.stringify(config, null, 2), "utf-8");
|
|
7001
|
+
log(`fixed ${fixCount} bot channel config issue(s) (allowFrom/appSecret)`);
|
|
7002
|
+
} else log("bot channel config ok, no fixes needed");
|
|
7003
|
+
}
|
|
7004
|
+
/**
|
|
6988
7005
|
* Step 7: Verify startup scripts landed in configDir/scripts/.
|
|
6989
7006
|
*
|
|
6990
7007
|
* Scripts are extracted directly to configDir/scripts/ during stageTemplate —
|
|
@@ -7129,6 +7146,7 @@ async function runReset(input, taskId, resultFile) {
|
|
|
7129
7146
|
await step5InstallOpenclaw(openclawTag, ossFileMap, log);
|
|
7130
7147
|
step(6);
|
|
7131
7148
|
mergeCoreBackupAndOrigins(configPath, vars, resetData, log);
|
|
7149
|
+
fixBotChannelConfig(configPath, vars.larkApps, log);
|
|
7132
7150
|
step(7);
|
|
7133
7151
|
verifyStartupScripts(configDir, log);
|
|
7134
7152
|
step(8);
|
|
@@ -7927,7 +7945,8 @@ function normalizeCtx(raw) {
|
|
|
7927
7945
|
reset: {
|
|
7928
7946
|
templateVars: r.reset.templateVars ?? {},
|
|
7929
7947
|
coreBackup: r.reset.coreBackup
|
|
7930
|
-
}
|
|
7948
|
+
},
|
|
7949
|
+
larkApps: Array.isArray(r.larkApps) ? r.larkApps : []
|
|
7931
7950
|
};
|
|
7932
7951
|
}
|
|
7933
7952
|
const vars = r.vars ?? {};
|
|
@@ -7952,7 +7971,8 @@ function normalizeCtx(raw) {
|
|
|
7952
7971
|
reset: {
|
|
7953
7972
|
templateVars: resetData.templateVars ?? {},
|
|
7954
7973
|
coreBackup: resetData.coreBackup
|
|
7955
|
-
}
|
|
7974
|
+
},
|
|
7975
|
+
larkApps: Array.isArray(r.larkApps) ? r.larkApps : []
|
|
7956
7976
|
};
|
|
7957
7977
|
}
|
|
7958
7978
|
function fillApp(src) {
|
|
@@ -8017,7 +8037,8 @@ function buildCheckInput(raw, configPathOverride) {
|
|
|
8017
8037
|
providerFilePath: PROVIDER_FILE_PATH,
|
|
8018
8038
|
secretsFilePath: SECRETS_FILE_PATH,
|
|
8019
8039
|
templateVars: ctx.app.templateVars,
|
|
8020
|
-
recommendedOpenclawTag: ctx.app.recommendedOpenclawTag
|
|
8040
|
+
recommendedOpenclawTag: ctx.app.recommendedOpenclawTag,
|
|
8041
|
+
larkApps: ctx.larkApps
|
|
8021
8042
|
},
|
|
8022
8043
|
templateVars: ctx.app.templateVars
|
|
8023
8044
|
};
|
|
@@ -8049,7 +8070,8 @@ function buildRepairInput(raw, configPathOverride) {
|
|
|
8049
8070
|
providerFilePath: PROVIDER_FILE_PATH,
|
|
8050
8071
|
secretsFilePath: SECRETS_FILE_PATH,
|
|
8051
8072
|
templateVars: ctx.app.templateVars,
|
|
8052
|
-
recommendedOpenclawTag: ctx.app.recommendedOpenclawTag
|
|
8073
|
+
recommendedOpenclawTag: ctx.app.recommendedOpenclawTag,
|
|
8074
|
+
larkApps: ctx.larkApps
|
|
8053
8075
|
},
|
|
8054
8076
|
repairData: {
|
|
8055
8077
|
secretsContent: ctx.secrets.secretsContent,
|
|
@@ -8085,7 +8107,8 @@ function buildResetInput(raw, configPathOverride) {
|
|
|
8085
8107
|
providerFilePath: PROVIDER_FILE_PATH,
|
|
8086
8108
|
secretsFilePath: SECRETS_FILE_PATH,
|
|
8087
8109
|
templateVars: ctx.app.templateVars,
|
|
8088
|
-
recommendedOpenclawTag: ctx.app.recommendedOpenclawTag
|
|
8110
|
+
recommendedOpenclawTag: ctx.app.recommendedOpenclawTag,
|
|
8111
|
+
larkApps: ctx.larkApps
|
|
8089
8112
|
},
|
|
8090
8113
|
resetData: {
|
|
8091
8114
|
templateVars: ctx.reset.templateVars,
|
|
@@ -10395,7 +10418,7 @@ async function reportCliRun(opts) {
|
|
|
10395
10418
|
//#region src/help.ts
|
|
10396
10419
|
const BIN = "mclaw-diagnose";
|
|
10397
10420
|
function versionBanner() {
|
|
10398
|
-
return `v0.1.14
|
|
10421
|
+
return `v0.1.14`;
|
|
10399
10422
|
}
|
|
10400
10423
|
const COMMANDS = [
|
|
10401
10424
|
{
|
|
@@ -10499,16 +10522,12 @@ EXIT CODES
|
|
|
10499
10522
|
hidden: true,
|
|
10500
10523
|
summary: "Run rule-engine check only",
|
|
10501
10524
|
help: `USAGE
|
|
10502
|
-
${BIN} check
|
|
10525
|
+
${BIN} check
|
|
10503
10526
|
|
|
10504
10527
|
DESCRIPTION
|
|
10505
10528
|
Runs the rule engine against the sandbox's current openclaw config and
|
|
10506
|
-
returns { failedRules }.
|
|
10507
|
-
|
|
10508
|
-
|
|
10509
|
-
OPTIONS
|
|
10510
|
-
--ctx=<base64> Opaque ctx JSON (base64). When absent, fetched from
|
|
10511
|
-
innerapi (same path as doctor).
|
|
10529
|
+
returns { failedRules }. Ctx is fetched from innerapi automatically.
|
|
10530
|
+
End-users should prefer \`doctor\`.
|
|
10512
10531
|
`
|
|
10513
10532
|
},
|
|
10514
10533
|
{
|
|
@@ -10516,16 +10535,11 @@ OPTIONS
|
|
|
10516
10535
|
hidden: true,
|
|
10517
10536
|
summary: "Apply standard-mode repairs",
|
|
10518
10537
|
help: `USAGE
|
|
10519
|
-
${BIN} repair
|
|
10538
|
+
${BIN} repair
|
|
10520
10539
|
|
|
10521
10540
|
DESCRIPTION
|
|
10522
|
-
Runs repair for the failing rules
|
|
10523
|
-
|
|
10524
|
-
\`doctor --fix\` instead.
|
|
10525
|
-
|
|
10526
|
-
OPTIONS
|
|
10527
|
-
--ctx=<base64> Opaque ctx JSON (base64). When absent, fetched from
|
|
10528
|
-
innerapi.
|
|
10541
|
+
Runs repair for the failing rules. Ctx is fetched from innerapi
|
|
10542
|
+
automatically. End-users should use \`doctor --fix\` instead.
|
|
10529
10543
|
`
|
|
10530
10544
|
},
|
|
10531
10545
|
{
|
|
@@ -10533,14 +10547,15 @@ OPTIONS
|
|
|
10533
10547
|
hidden: true,
|
|
10534
10548
|
summary: "Re-initialize sandbox via the 9-step reset pipeline",
|
|
10535
10549
|
help: `USAGE
|
|
10536
|
-
${BIN} reset --async
|
|
10537
|
-
${BIN} reset --worker --task-id=<id>
|
|
10550
|
+
${BIN} reset --async
|
|
10551
|
+
${BIN} reset --worker --task-id=<id>
|
|
10538
10552
|
|
|
10539
10553
|
DESCRIPTION
|
|
10540
10554
|
Two-phase pipeline driven asynchronously: the --async invocation spawns
|
|
10541
10555
|
a detached worker and returns { taskId } immediately; the --worker
|
|
10542
10556
|
invocation (spawned by --async) runs the actual 9 steps and writes
|
|
10543
10557
|
progress to /tmp/openclaw-diagnose/reset-<taskId>.json.
|
|
10558
|
+
Ctx is fetched from innerapi automatically.
|
|
10544
10559
|
|
|
10545
10560
|
Poll progress with \`${BIN} get_reset_task --task-id=<id>\`.
|
|
10546
10561
|
|
|
@@ -10548,7 +10563,6 @@ OPTIONS
|
|
|
10548
10563
|
--async Start a detached worker and return taskId on stdout.
|
|
10549
10564
|
--worker Internal — run the 9-step pipeline (launched by --async).
|
|
10550
10565
|
--task-id=<id> Required with --worker; identifies the progress file.
|
|
10551
|
-
--ctx=<base64> Opaque ctx JSON; fetched from innerapi when absent.
|
|
10552
10566
|
`
|
|
10553
10567
|
},
|
|
10554
10568
|
{
|
|
@@ -10571,7 +10585,7 @@ OPTIONS
|
|
|
10571
10585
|
hidden: true,
|
|
10572
10586
|
summary: "Download + install the openclaw tarball",
|
|
10573
10587
|
help: `USAGE
|
|
10574
|
-
${BIN} install-openclaw <tag> [--
|
|
10588
|
+
${BIN} install-openclaw <tag> [--oss_file_map=<base64>]
|
|
10575
10589
|
|
|
10576
10590
|
DESCRIPTION
|
|
10577
10591
|
Downloads the openclaw@<tag> tgz via the signed OSS URL found in the
|
|
@@ -10583,9 +10597,9 @@ ARGUMENTS
|
|
|
10583
10597
|
<tag> Openclaw version tag, e.g. 2026.4.11.
|
|
10584
10598
|
|
|
10585
10599
|
OPTIONS
|
|
10586
|
-
--ctx=<base64> Opaque ctx; ossFileMap is extracted from it.
|
|
10587
10600
|
--oss_file_map=... Pre-built OSS URL map (base64 JSON); skips innerapi
|
|
10588
|
-
entirely.
|
|
10601
|
+
entirely. When absent, ossFileMap is fetched from
|
|
10602
|
+
innerapi automatically.
|
|
10589
10603
|
`
|
|
10590
10604
|
},
|
|
10591
10605
|
{
|
|
@@ -10611,8 +10625,7 @@ OPTIONS
|
|
|
10611
10625
|
--home_base=<dir> Override the /home/gem base (tests).
|
|
10612
10626
|
--config_path=<p> Override the openclaw.json path (tests).
|
|
10613
10627
|
--skip-config-update Leave plugins.installs in openclaw.json untouched.
|
|
10614
|
-
--
|
|
10615
|
-
--oss_file_map=... Pre-built OSS URL map (base64 JSON).
|
|
10628
|
+
--oss_file_map=... Pre-built OSS URL map (base64 JSON); skips innerapi.
|
|
10616
10629
|
`
|
|
10617
10630
|
},
|
|
10618
10631
|
{
|
|
@@ -10639,7 +10652,6 @@ OPTIONS
|
|
|
10639
10652
|
--cli=<name> CLI package to install by short name or scoped
|
|
10640
10653
|
packageName (repeatable, at least one required).
|
|
10641
10654
|
--home_base=<dir> Override the /home/gem base (tests).
|
|
10642
|
-
--ctx=<base64> Opaque ctx; ossFileMap is extracted from it.
|
|
10643
10655
|
--oss_file_map=... Pre-built OSS URL map (base64 JSON); skips innerapi.
|
|
10644
10656
|
|
|
10645
10657
|
EXAMPLES
|
|
@@ -10693,46 +10705,6 @@ OPTIONS
|
|
|
10693
10705
|
EXIT CODES
|
|
10694
10706
|
0 Success or skipped (prerequisites not met).
|
|
10695
10707
|
1 Secret/path unresolvable, lark-cli failed, or config unreadable.
|
|
10696
|
-
`
|
|
10697
|
-
},
|
|
10698
|
-
{
|
|
10699
|
-
name: "upgrade-lark",
|
|
10700
|
-
hidden: false,
|
|
10701
|
-
summary: "Upgrade the Feishu/Lark plugin via @larksuite/openclaw-lark-tools",
|
|
10702
|
-
help: `USAGE
|
|
10703
|
-
${BIN} upgrade-lark [--scene=<scene>] [--caller=<n>] [--trace-id=<id>]
|
|
10704
|
-
|
|
10705
|
-
DESCRIPTION
|
|
10706
|
-
Upgrades the Feishu/Lark plugin by running:
|
|
10707
|
-
npx -y @larksuite/openclaw-lark-tools update --use-existing
|
|
10708
|
-
|
|
10709
|
-
Before the upgrade, the following files are backed up:
|
|
10710
|
-
- openclaw.json
|
|
10711
|
-
- extensions/openclaw-lark/ (if present)
|
|
10712
|
-
- extensions/feishu-openclaw-plugin/ (if present)
|
|
10713
|
-
After the upgrade, the result is validated:
|
|
10714
|
-
- feishu.accounts bot count must not decrease
|
|
10715
|
-
- gateway config structure must remain valid (port/mode/bind/auth/trustedProxies)
|
|
10716
|
-
If the upgrade command fails, or validation fails, the backed-up files are
|
|
10717
|
-
restored to roll back the changes.
|
|
10718
|
-
|
|
10719
|
-
Execution is logged to /tmp/openclaw-diagnose/upgrade-lark-<runId>.log.
|
|
10720
|
-
|
|
10721
|
-
Output is a single JSON object on stdout:
|
|
10722
|
-
{ "ok": true, "stdout": "...", "stderr": "...", "logFile": "..." }
|
|
10723
|
-
{ "ok": false, "error": "...", "stderr": "...", "exitCode": 1,
|
|
10724
|
-
"rollbackOk": true, "validationError": "...", "logFile": "..." }
|
|
10725
|
-
|
|
10726
|
-
OPTIONS
|
|
10727
|
-
--scene=<scene> Telemetry label forwarded to Slardar only.
|
|
10728
|
-
Known values: PageUpgradeLark, etc. Custom strings accepted.
|
|
10729
|
-
--caller=<name> Optional metadata passed to innerapi.
|
|
10730
|
-
--trace-id=<id> Optional log-correlation id.
|
|
10731
|
-
|
|
10732
|
-
EXIT CODES
|
|
10733
|
-
0 Success: upgrade ran and all validations passed.
|
|
10734
|
-
1 Failure: npx error, validation failed, or git commit failed.
|
|
10735
|
-
File rollback was attempted (see rollbackOk in the JSON output).
|
|
10736
10708
|
`
|
|
10737
10709
|
},
|
|
10738
10710
|
{
|
|
@@ -10766,41 +10738,6 @@ EXAMPLES
|
|
|
10766
10738
|
${BIN} rules # all rules
|
|
10767
10739
|
${BIN} rules --rule=gateway # single rule
|
|
10768
10740
|
${BIN} rules --rule=gateway --rule=feishu_channel # multiple rules
|
|
10769
|
-
`
|
|
10770
|
-
},
|
|
10771
|
-
{
|
|
10772
|
-
name: "channels-probe",
|
|
10773
|
-
hidden: true,
|
|
10774
|
-
summary: "Check feishu channel health via openclaw channels status --probe",
|
|
10775
|
-
help: `USAGE
|
|
10776
|
-
${BIN} channels-probe [--timeout=<ms>]
|
|
10777
|
-
|
|
10778
|
-
DESCRIPTION
|
|
10779
|
-
Runs \`openclaw channels status --probe\` and returns a structured JSON
|
|
10780
|
-
summary of whether the current environment's feishu channels are
|
|
10781
|
-
configured and working correctly.
|
|
10782
|
-
|
|
10783
|
-
Output:
|
|
10784
|
-
{
|
|
10785
|
-
"available": true,
|
|
10786
|
-
"gatewayReachable": true,
|
|
10787
|
-
"accounts": [
|
|
10788
|
-
{ "id": "default", "bits": ["enabled","configured","running","works"],
|
|
10789
|
-
"isWorking": true, "raw": "- Feishu default: ..." }
|
|
10790
|
-
],
|
|
10791
|
-
"anyAccountWorking": true
|
|
10792
|
-
}
|
|
10793
|
-
|
|
10794
|
-
An account is considered working when:
|
|
10795
|
-
enabled ∧ configured ∧ ( works ∨ ( running ∧ no error: ∧ no probe failed ) )
|
|
10796
|
-
|
|
10797
|
-
"available": false means the CLI invocation itself failed (openclaw not
|
|
10798
|
-
found, gateway unreachable, or no parseable output returned).
|
|
10799
|
-
|
|
10800
|
-
OPTIONS
|
|
10801
|
-
--timeout=<ms> Max wait in milliseconds (default: 60000). The probe
|
|
10802
|
-
can hang indefinitely on openclaw v2026.4.x due to a
|
|
10803
|
-
missing per-request HTTP timeout — set this accordingly.
|
|
10804
10741
|
`
|
|
10805
10742
|
},
|
|
10806
10743
|
{
|
|
@@ -10821,8 +10758,7 @@ OPTIONS
|
|
|
10821
10758
|
--role=<role> Package role (e.g. template, config).
|
|
10822
10759
|
--name=<name> Package name within the role.
|
|
10823
10760
|
--dir=<dir> Target dir (defaults to dirname(pkg.installPath)).
|
|
10824
|
-
--
|
|
10825
|
-
--oss_file_map=... Pre-built OSS URL map (base64 JSON).
|
|
10761
|
+
--oss_file_map=... Pre-built OSS URL map (base64 JSON); skips innerapi.
|
|
10826
10762
|
`
|
|
10827
10763
|
}
|
|
10828
10764
|
];
|
|
@@ -10898,31 +10834,31 @@ function planVarsFields(opts = {}) {
|
|
|
10898
10834
|
*
|
|
10899
10835
|
* Per-command group needs:
|
|
10900
10836
|
*
|
|
10901
|
-
* doctor / check app
|
|
10902
|
-
* repair app + secrets
|
|
10903
|
-
* reset app + secrets + install + reset
|
|
10837
|
+
* doctor / check app + larkApps
|
|
10838
|
+
* repair app + secrets + larkApps
|
|
10839
|
+
* reset app + secrets + install + reset + larkApps
|
|
10904
10840
|
* install-* install only
|
|
10905
10841
|
*
|
|
10906
10842
|
* Empty result (`{}`) means "no group needed" — the CLI can skip the
|
|
10907
10843
|
* `fetchCtxViaInnerApi` call entirely and run with a synthetic empty ctx.
|
|
10908
|
-
* Happens e.g. when the user pinned `--rule=<key>` to a vars-free rule on
|
|
10909
|
-
* `doctor`.
|
|
10910
10844
|
*/
|
|
10911
10845
|
function planCtxPopulate(opts) {
|
|
10912
10846
|
if (opts.command === "install") return { install: true };
|
|
10913
10847
|
const populate = {};
|
|
10914
|
-
|
|
10848
|
+
if (planVarsFields({
|
|
10915
10849
|
disabled: opts.disabled,
|
|
10916
10850
|
onlyRules: opts.onlyRules,
|
|
10917
10851
|
profile: opts.profile
|
|
10918
|
-
});
|
|
10919
|
-
if (
|
|
10920
|
-
|
|
10921
|
-
|
|
10852
|
+
}).length > 0) populate.app = true;
|
|
10853
|
+
if (opts.command === "repair") {
|
|
10854
|
+
populate.secrets = true;
|
|
10855
|
+
populate.larkApps = true;
|
|
10856
|
+
} else if (opts.command === "reset") {
|
|
10922
10857
|
populate.secrets = true;
|
|
10923
10858
|
populate.install = true;
|
|
10924
10859
|
populate.reset = true;
|
|
10925
|
-
|
|
10860
|
+
populate.larkApps = true;
|
|
10861
|
+
} else if (opts.command === "doctor" || opts.command === "check") populate.larkApps = true;
|
|
10926
10862
|
return populate;
|
|
10927
10863
|
}
|
|
10928
10864
|
//#endregion
|
|
@@ -10976,372 +10912,11 @@ function reportDoctorRunToSlardar(opts) {
|
|
|
10976
10912
|
}
|
|
10977
10913
|
});
|
|
10978
10914
|
}
|
|
10979
|
-
function readLogFile(filePath) {
|
|
10980
|
-
try {
|
|
10981
|
-
return node_fs.default.readFileSync(filePath, "utf-8");
|
|
10982
|
-
} catch {
|
|
10983
|
-
return "";
|
|
10984
|
-
}
|
|
10985
|
-
}
|
|
10986
|
-
function reportUpgradeLarkToSlardar(opts) {
|
|
10987
|
-
console.error(`[slardar] upgrade_lark_run scene=${opts.scene ?? ""} success=${opts.success} exitCode=${opts.exitCode ?? ""} rollbackOk=${opts.rollbackOk ?? ""}`);
|
|
10988
|
-
const logContent = readLogFile(opts.logFile);
|
|
10989
|
-
reportTask({
|
|
10990
|
-
eventName: "upgrade_lark_run",
|
|
10991
|
-
durationMs: opts.durationMs,
|
|
10992
|
-
status: opts.success ? "success" : "failed",
|
|
10993
|
-
extraCategories: {
|
|
10994
|
-
scene: opts.scene ?? "",
|
|
10995
|
-
exit_code: String(opts.exitCode ?? ""),
|
|
10996
|
-
rollback_ok: opts.rollbackOk != null ? String(opts.rollbackOk) : "",
|
|
10997
|
-
validation_error: opts.validationError ?? "",
|
|
10998
|
-
error_msg: opts.error ?? "",
|
|
10999
|
-
log_content: logContent
|
|
11000
|
-
}
|
|
11001
|
-
});
|
|
11002
|
-
}
|
|
11003
|
-
//#endregion
|
|
11004
|
-
//#region src/upgrade-lark.ts
|
|
11005
|
-
/** Plugin directories under extensions/ that are backed up before upgrade */
|
|
11006
|
-
const FEISHU_PLUGIN_DIRS = ["openclaw-lark", "feishu-openclaw-plugin"];
|
|
11007
|
-
function backupFiles(opts) {
|
|
11008
|
-
const { workspaceDir, configPath, backupDir, log } = opts;
|
|
11009
|
-
try {
|
|
11010
|
-
node_fs.default.mkdirSync(backupDir, { recursive: true });
|
|
11011
|
-
log(`backup dir: ${backupDir}`);
|
|
11012
|
-
if (node_fs.default.existsSync(configPath)) {
|
|
11013
|
-
const stat = node_fs.default.statSync(configPath);
|
|
11014
|
-
node_fs.default.copyFileSync(configPath, node_path.default.join(backupDir, "openclaw.json"));
|
|
11015
|
-
log(` backed up: openclaw.json (${stat.size} bytes)`);
|
|
11016
|
-
} else log(` skipped: openclaw.json (not found)`);
|
|
11017
|
-
const extSrc = node_path.default.join(workspaceDir, "extensions");
|
|
11018
|
-
for (const pluginDir of FEISHU_PLUGIN_DIRS) {
|
|
11019
|
-
const src = node_path.default.join(extSrc, pluginDir);
|
|
11020
|
-
if (node_fs.default.existsSync(src)) {
|
|
11021
|
-
const dst = node_path.default.join(backupDir, "extensions", pluginDir);
|
|
11022
|
-
node_fs.default.cpSync(src, dst, { recursive: true });
|
|
11023
|
-
const version = readPkgVersion(node_path.default.join(src, "package.json"));
|
|
11024
|
-
log(` backed up: extensions/${pluginDir}${version ? ` (version: ${version})` : ""}`);
|
|
11025
|
-
} else log(` skipped: extensions/${pluginDir} (not found)`);
|
|
11026
|
-
}
|
|
11027
|
-
return { ok: true };
|
|
11028
|
-
} catch (e) {
|
|
11029
|
-
return {
|
|
11030
|
-
ok: false,
|
|
11031
|
-
error: `backup failed: ${e.message}`
|
|
11032
|
-
};
|
|
11033
|
-
}
|
|
11034
|
-
}
|
|
11035
|
-
function restoreFiles(opts) {
|
|
11036
|
-
const { workspaceDir, configPath, backupDir, log } = opts;
|
|
11037
|
-
try {
|
|
11038
|
-
const configBackup = node_path.default.join(backupDir, "openclaw.json");
|
|
11039
|
-
if (node_fs.default.existsSync(configBackup)) {
|
|
11040
|
-
node_fs.default.copyFileSync(configBackup, configPath);
|
|
11041
|
-
log(` restored: openclaw.json`);
|
|
11042
|
-
}
|
|
11043
|
-
const extDst = node_path.default.join(workspaceDir, "extensions");
|
|
11044
|
-
for (const pluginDir of FEISHU_PLUGIN_DIRS) {
|
|
11045
|
-
const backupSrc = node_path.default.join(backupDir, "extensions", pluginDir);
|
|
11046
|
-
if (node_fs.default.existsSync(backupSrc)) {
|
|
11047
|
-
const dst = node_path.default.join(extDst, pluginDir);
|
|
11048
|
-
if (node_fs.default.existsSync(dst)) node_fs.default.rmSync(dst, {
|
|
11049
|
-
recursive: true,
|
|
11050
|
-
force: true
|
|
11051
|
-
});
|
|
11052
|
-
node_fs.default.cpSync(backupSrc, dst, { recursive: true });
|
|
11053
|
-
log(` restored: extensions/${pluginDir}`);
|
|
11054
|
-
}
|
|
11055
|
-
}
|
|
11056
|
-
return true;
|
|
11057
|
-
} catch (e) {
|
|
11058
|
-
log(` restore error: ${e.message}`);
|
|
11059
|
-
return false;
|
|
11060
|
-
}
|
|
11061
|
-
}
|
|
11062
|
-
function readPkgVersion(pkgPath) {
|
|
11063
|
-
try {
|
|
11064
|
-
const pkg = JSON.parse(node_fs.default.readFileSync(pkgPath, "utf-8"));
|
|
11065
|
-
return typeof pkg.version === "string" ? pkg.version : null;
|
|
11066
|
-
} catch {
|
|
11067
|
-
return null;
|
|
11068
|
-
}
|
|
11069
|
-
}
|
|
11070
|
-
function snapshotVersions(cwd, log) {
|
|
11071
|
-
const ocResult = (0, node_child_process.spawnSync)("openclaw", ["--version"], {
|
|
11072
|
-
cwd,
|
|
11073
|
-
encoding: "utf-8",
|
|
11074
|
-
stdio: [
|
|
11075
|
-
"ignore",
|
|
11076
|
-
"pipe",
|
|
11077
|
-
"pipe"
|
|
11078
|
-
],
|
|
11079
|
-
timeout: 5e3
|
|
11080
|
-
});
|
|
11081
|
-
const ocRaw = (ocResult.stdout ?? "").trim() || (ocResult.stderr ?? "").trim();
|
|
11082
|
-
const extDir = node_path.default.join(cwd, "extensions");
|
|
11083
|
-
const larkPkg = node_path.default.join(extDir, "openclaw-lark", "package.json");
|
|
11084
|
-
const feishuPkg = node_path.default.join(extDir, "feishu-openclaw-plugin", "package.json");
|
|
11085
|
-
log(` version-check paths: ${larkPkg} [${node_fs.default.existsSync(larkPkg) ? "exists" : "missing"}]`);
|
|
11086
|
-
log(` version-check paths: ${feishuPkg} [${node_fs.default.existsSync(feishuPkg) ? "exists" : "missing"}]`);
|
|
11087
|
-
return {
|
|
11088
|
-
openclaw: ocRaw || null,
|
|
11089
|
-
openclawLark: readPkgVersion(larkPkg),
|
|
11090
|
-
feishuOpenclawPlugin: readPkgVersion(feishuPkg)
|
|
11091
|
-
};
|
|
11092
|
-
}
|
|
11093
|
-
function logVersionSnapshot(label, v, log) {
|
|
11094
|
-
log(`${label}: openclaw=${v.openclaw ?? "n/a"} openclaw-lark=${v.openclawLark ?? "n/a"} feishu-openclaw-plugin=${v.feishuOpenclawPlugin ?? "n/a"}`);
|
|
11095
|
-
}
|
|
11096
|
-
function countFeishuBots(configPath) {
|
|
11097
|
-
try {
|
|
11098
|
-
const raw = node_fs.default.readFileSync(configPath, "utf-8");
|
|
11099
|
-
const config = loadJSON5().parse(raw);
|
|
11100
|
-
const accounts = getNestedMap(config, "channels", "feishu", "accounts");
|
|
11101
|
-
if (accounts) return Object.keys(accounts).length;
|
|
11102
|
-
const feishu = getNestedMap(config, "channels", "feishu");
|
|
11103
|
-
return typeof feishu?.appId === "string" && feishu.appId ? 1 : 0;
|
|
11104
|
-
} catch {
|
|
11105
|
-
return 0;
|
|
11106
|
-
}
|
|
11107
|
-
}
|
|
11108
|
-
/** Run channels probe, log results, and return the result. Never throws. */
|
|
11109
|
-
function probeChannels(label, log, timeoutMs) {
|
|
11110
|
-
try {
|
|
11111
|
-
const r = runChannelsProbe(timeoutMs);
|
|
11112
|
-
log(` ${label} available=${r.available} anyAccountWorking=${r.anyAccountWorking}`);
|
|
11113
|
-
if (r.error) log(` ${label} error: ${r.error}`);
|
|
11114
|
-
if (r.gatewayReachable != null) log(` ${label} gatewayReachable: ${r.gatewayReachable}`);
|
|
11115
|
-
for (const acct of r.accounts ?? []) log(` ${label} account ${acct.id}: isWorking=${acct.isWorking} bits=[${acct.bits.join(",")}]`);
|
|
11116
|
-
return r;
|
|
11117
|
-
} catch (e) {
|
|
11118
|
-
log(` ${label} channels probe threw: ${e.message}`);
|
|
11119
|
-
return {
|
|
11120
|
-
available: false,
|
|
11121
|
-
gatewayReachable: false,
|
|
11122
|
-
feishuConfigInvalid: false,
|
|
11123
|
-
accounts: [],
|
|
11124
|
-
anyAccountWorking: false
|
|
11125
|
-
};
|
|
11126
|
-
}
|
|
11127
|
-
}
|
|
11128
|
-
function runUpgradeLark(opts) {
|
|
11129
|
-
const cwd = opts.cwd ?? "/home/gem/workspace/agent";
|
|
11130
|
-
const configPath = opts.configPath ?? CONFIG_PATH;
|
|
11131
|
-
const logFile = upgradeLarkLogFile(opts.runId);
|
|
11132
|
-
const log = makeLogger(logFile);
|
|
11133
|
-
const fsOpts = {
|
|
11134
|
-
workspaceDir: cwd,
|
|
11135
|
-
configPath,
|
|
11136
|
-
backupDir: node_path.default.join(opts.backupBaseDir ?? "/tmp/openclaw-diagnose", `upgrade-lark-backup-${opts.runId}`),
|
|
11137
|
-
log
|
|
11138
|
-
};
|
|
11139
|
-
const cliScript = opts.cliScript ?? process.argv[1];
|
|
11140
|
-
const statusCheckDelayMs = opts.statusCheckDelayMs ?? 5e3;
|
|
11141
|
-
log(`${"=".repeat(60)}`);
|
|
11142
|
-
log(`upgrade-lark started runId=${opts.runId}`);
|
|
11143
|
-
log(` cwd : ${cwd}`);
|
|
11144
|
-
log(` configPath : ${configPath}`);
|
|
11145
|
-
log(`${"=".repeat(60)}`);
|
|
11146
|
-
log("");
|
|
11147
|
-
log("── [Pre-check A] channels probe(升级前)────────────────");
|
|
11148
|
-
const beforeChannels = probeChannels("before", log, 6e4);
|
|
11149
|
-
log("");
|
|
11150
|
-
log("── [Pre-check B] 版本兼容预检 ───────────────────────────");
|
|
11151
|
-
let versionIncompatible = false;
|
|
11152
|
-
try {
|
|
11153
|
-
const rawConfig = node_fs.default.readFileSync(configPath, "utf-8");
|
|
11154
|
-
versionIncompatible = needsLarkUpgrade({
|
|
11155
|
-
config: loadJSON5().parse(rawConfig),
|
|
11156
|
-
configPath,
|
|
11157
|
-
vars: {},
|
|
11158
|
-
providerDeps: {
|
|
11159
|
-
usesMiaodaProvider: false,
|
|
11160
|
-
usesMiaodaSecretProvider: false
|
|
11161
|
-
}
|
|
11162
|
-
});
|
|
11163
|
-
log(` version-compat pre-check: ${versionIncompatible ? "NEEDS_UPGRADE" : "ok"}`);
|
|
11164
|
-
} catch (e) {
|
|
11165
|
-
log(` version-compat pre-check error: ${e.message} — treating as needs-upgrade`);
|
|
11166
|
-
versionIncompatible = true;
|
|
11167
|
-
}
|
|
11168
|
-
const feishuConfigInvalid = beforeChannels.feishuConfigInvalid;
|
|
11169
|
-
log(` feishu config invalid : ${feishuConfigInvalid}`);
|
|
11170
|
-
log("");
|
|
11171
|
-
log("── [Gate] 升级前置条件检查 ───────────────────────────────");
|
|
11172
|
-
log(` versionIncompatible : ${versionIncompatible}`);
|
|
11173
|
-
log(` feishuConfigInvalid : ${feishuConfigInvalid}`);
|
|
11174
|
-
log(` channels working before: ${beforeChannels.anyAccountWorking}`);
|
|
11175
|
-
if (!(versionIncompatible || feishuConfigInvalid)) {
|
|
11176
|
-
const reason = "version compatible and feishu channel config valid — upgrade not needed";
|
|
11177
|
-
log(` SKIP: ${reason}`);
|
|
11178
|
-
log(`${"=".repeat(60)}`);
|
|
11179
|
-
log("upgrade-lark skipped (pre-check gate)");
|
|
11180
|
-
log(`${"=".repeat(60)}`);
|
|
11181
|
-
return {
|
|
11182
|
-
ok: true,
|
|
11183
|
-
skipped: true,
|
|
11184
|
-
skipReason: reason,
|
|
11185
|
-
logFile
|
|
11186
|
-
};
|
|
11187
|
-
}
|
|
11188
|
-
if (beforeChannels.anyAccountWorking) {
|
|
11189
|
-
const reason = "channels are working — upgrade not needed (issue detected but system is functional)";
|
|
11190
|
-
log(` SKIP: ${reason}`);
|
|
11191
|
-
log(`${"=".repeat(60)}`);
|
|
11192
|
-
log("upgrade-lark skipped (pre-check gate)");
|
|
11193
|
-
log(`${"=".repeat(60)}`);
|
|
11194
|
-
return {
|
|
11195
|
-
ok: true,
|
|
11196
|
-
skipped: true,
|
|
11197
|
-
skipReason: reason,
|
|
11198
|
-
logFile
|
|
11199
|
-
};
|
|
11200
|
-
}
|
|
11201
|
-
log(` PROCEED: requiresLarkUpgrade=true (version=${versionIncompatible}, feishuConfig=${feishuConfigInvalid}) AND channels not working → running upgrade`);
|
|
11202
|
-
log("");
|
|
11203
|
-
log("── [1/6] 文件备份 ────────────────────────────────────────");
|
|
11204
|
-
log(`before-state: botCount=${countFeishuBots(configPath)}`);
|
|
11205
|
-
const backup = backupFiles(fsOpts);
|
|
11206
|
-
if (!backup.ok) {
|
|
11207
|
-
log(`ERROR: ${backup.error}`);
|
|
11208
|
-
return {
|
|
11209
|
-
ok: false,
|
|
11210
|
-
error: backup.error,
|
|
11211
|
-
logFile
|
|
11212
|
-
};
|
|
11213
|
-
}
|
|
11214
|
-
log("backup: ok");
|
|
11215
|
-
logVersionSnapshot("before-versions", snapshotVersions(cwd, log), log);
|
|
11216
|
-
log("");
|
|
11217
|
-
log("── [2/6] 清理本地 openclaw shim ─────────────────────────");
|
|
11218
|
-
const localOpenclawBin = node_path.default.join(cwd, "node_modules", ".bin", "openclaw");
|
|
11219
|
-
if (node_fs.default.existsSync(localOpenclawBin)) try {
|
|
11220
|
-
node_fs.default.rmSync(localOpenclawBin);
|
|
11221
|
-
log(` removed: ${localOpenclawBin}`);
|
|
11222
|
-
} catch (e) {
|
|
11223
|
-
log(` WARN: failed to remove ${localOpenclawBin}: ${e.message}`);
|
|
11224
|
-
}
|
|
11225
|
-
else log(` skipped: ${localOpenclawBin} (not found)`);
|
|
11226
|
-
log("");
|
|
11227
|
-
log("── [3/6] npx install (@larksuite/openclaw-lark-tools update) ──");
|
|
11228
|
-
const npxResult = (0, node_child_process.spawnSync)("npx", [
|
|
11229
|
-
"-y",
|
|
11230
|
-
"@larksuite/openclaw-lark-tools",
|
|
11231
|
-
"update"
|
|
11232
|
-
], {
|
|
11233
|
-
cwd,
|
|
11234
|
-
encoding: "utf-8",
|
|
11235
|
-
stdio: [
|
|
11236
|
-
"ignore",
|
|
11237
|
-
"pipe",
|
|
11238
|
-
"pipe"
|
|
11239
|
-
],
|
|
11240
|
-
timeout: 12e4
|
|
11241
|
-
});
|
|
11242
|
-
const npxStdout = npxResult.stdout?.trim() ?? "";
|
|
11243
|
-
const npxStderr = npxResult.stderr?.trim() ?? "";
|
|
11244
|
-
const npxExitCode = npxResult.status ?? 1;
|
|
11245
|
-
if (npxStdout) log(`npx stdout:\n${npxStdout}`);
|
|
11246
|
-
if (npxStderr) log(`npx stderr:\n${npxStderr}`);
|
|
11247
|
-
log(`npx exit: ${npxExitCode}${npxResult.error ? ` error: ${npxResult.error.message}` : ""}`);
|
|
11248
|
-
if (statusCheckDelayMs > 0) {
|
|
11249
|
-
log("");
|
|
11250
|
-
log(`── 等待 ${statusCheckDelayMs / 1e3}s(让 openclaw 服务完成重启) ─────────────`);
|
|
11251
|
-
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, statusCheckDelayMs);
|
|
11252
|
-
log("wait done");
|
|
11253
|
-
}
|
|
11254
|
-
const doRollback = (reason) => {
|
|
11255
|
-
log(`ERROR: ${reason}`);
|
|
11256
|
-
const rollbackOk = restoreFiles(fsOpts);
|
|
11257
|
-
log(`rollback: ${rollbackOk ? "ok" : "FAILED"}`);
|
|
11258
|
-
return {
|
|
11259
|
-
ok: false,
|
|
11260
|
-
error: reason,
|
|
11261
|
-
validationError: reason,
|
|
11262
|
-
stdout: npxStdout,
|
|
11263
|
-
stderr: npxStderr,
|
|
11264
|
-
exitCode: npxExitCode,
|
|
11265
|
-
rollbackOk,
|
|
11266
|
-
logFile
|
|
11267
|
-
};
|
|
11268
|
-
};
|
|
11269
|
-
log("");
|
|
11270
|
-
log("── [4/5] 安装后诊断校验 ─────────────────────────────────");
|
|
11271
|
-
logVersionSnapshot("after-versions", snapshotVersions(cwd, log), log);
|
|
11272
|
-
let afterVersionIncompatible = false;
|
|
11273
|
-
try {
|
|
11274
|
-
const rawConfig = node_fs.default.readFileSync(configPath, "utf-8");
|
|
11275
|
-
afterVersionIncompatible = needsLarkUpgrade({
|
|
11276
|
-
config: loadJSON5().parse(rawConfig),
|
|
11277
|
-
configPath,
|
|
11278
|
-
vars: {},
|
|
11279
|
-
providerDeps: {
|
|
11280
|
-
usesMiaodaProvider: false,
|
|
11281
|
-
usesMiaodaSecretProvider: false
|
|
11282
|
-
}
|
|
11283
|
-
});
|
|
11284
|
-
log(` version-compat post-check: ${afterVersionIncompatible ? "STILL_INCOMPATIBLE" : "ok"}`);
|
|
11285
|
-
} catch (e) {
|
|
11286
|
-
log(` version-compat post-check error: ${e.message} — treating as still-incompatible`);
|
|
11287
|
-
afterVersionIncompatible = true;
|
|
11288
|
-
}
|
|
11289
|
-
const afterChannels = probeChannels("after", log, 6e4);
|
|
11290
|
-
log(` feishu config invalid after: ${afterChannels.feishuConfigInvalid}`);
|
|
11291
|
-
const stillNeedsUpgrade = (afterVersionIncompatible || afterChannels.feishuConfigInvalid) && !afterChannels.anyAccountWorking;
|
|
11292
|
-
log(` post-check: stillNeedsUpgrade=${stillNeedsUpgrade} (version=${afterVersionIncompatible}, feishuConfig=${afterChannels.feishuConfigInvalid}, channelsWorking=${afterChannels.anyAccountWorking})`);
|
|
11293
|
-
if (stillNeedsUpgrade) return doRollback(`post-install diagnosis still shows anomaly: versionIncompatible=${afterVersionIncompatible}, feishuConfigInvalid=${afterChannels.feishuConfigInvalid}, anyAccountWorking=${afterChannels.anyAccountWorking}`);
|
|
11294
|
-
log(" post-install diagnosis: ok (upgrade conditions resolved)");
|
|
11295
|
-
log("");
|
|
11296
|
-
log("── [6/6] doctor --fix ────────────────────────────────────");
|
|
11297
|
-
const fixArgs = ["doctor", "--fix"];
|
|
11298
|
-
if (opts.scene) fixArgs.push(`--scene=${opts.scene}`);
|
|
11299
|
-
const fixResult = (0, node_child_process.spawnSync)(process.execPath, [cliScript, ...fixArgs], {
|
|
11300
|
-
cwd,
|
|
11301
|
-
encoding: "utf-8",
|
|
11302
|
-
stdio: [
|
|
11303
|
-
"ignore",
|
|
11304
|
-
"pipe",
|
|
11305
|
-
"pipe"
|
|
11306
|
-
],
|
|
11307
|
-
timeout: 6e4,
|
|
11308
|
-
env: process.env
|
|
11309
|
-
});
|
|
11310
|
-
if (fixResult.stdout?.trim()) log(`doctor(fix) stdout:\n${fixResult.stdout.trim()}`);
|
|
11311
|
-
if (fixResult.stderr?.trim()) log(`doctor(fix) stderr:\n${fixResult.stderr.trim()}`);
|
|
11312
|
-
log(`doctor(fix) exit: ${fixResult.status ?? "null"}${fixResult.error ? ` error: ${fixResult.error.message}` : ""}`);
|
|
11313
|
-
log("");
|
|
11314
|
-
log(`${"=".repeat(60)}`);
|
|
11315
|
-
log("upgrade-lark completed successfully");
|
|
11316
|
-
log(`${"=".repeat(60)}`);
|
|
11317
|
-
return {
|
|
11318
|
-
ok: true,
|
|
11319
|
-
stdout: npxStdout,
|
|
11320
|
-
stderr: npxStderr,
|
|
11321
|
-
exitCode: npxExitCode,
|
|
11322
|
-
logFile
|
|
11323
|
-
};
|
|
11324
|
-
}
|
|
11325
10915
|
//#endregion
|
|
11326
10916
|
//#region src/index.ts
|
|
11327
10917
|
const args = node_process.default.argv.slice(2);
|
|
11328
10918
|
const mode = args.find((a) => !a.startsWith("-"));
|
|
11329
10919
|
/**
|
|
11330
|
-
* Decode `--ctx=<base64>` into an opaque JSON object. Returns undefined when
|
|
11331
|
-
* the flag isn't present — the caller decides whether to fall back to the
|
|
11332
|
-
* innerapi or to error out.
|
|
11333
|
-
*
|
|
11334
|
-
* The object's shape is not enforced here; downstream code consumes it via
|
|
11335
|
-
* either `normalizeCtx()` (new path) or direct field access for the legacy
|
|
11336
|
-
* check/repair/reset contract still used by sandbox_console push.
|
|
11337
|
-
*/
|
|
11338
|
-
function parseCtxFlag(args) {
|
|
11339
|
-
const ctxArg = args.find((a) => a.startsWith("--ctx="));
|
|
11340
|
-
if (!ctxArg) return void 0;
|
|
11341
|
-
const b64 = ctxArg.slice(6);
|
|
11342
|
-
return JSON.parse(Buffer.from(b64, "base64").toString("utf-8"));
|
|
11343
|
-
}
|
|
11344
|
-
/**
|
|
11345
10920
|
* Pull the first non-flag positional after the mode name.
|
|
11346
10921
|
* (The mode itself is args[0] in the filtered set, so we skip index 0.)
|
|
11347
10922
|
*/
|
|
@@ -11369,8 +10944,8 @@ function getMultiFlag(args, name) {
|
|
|
11369
10944
|
* case but is no longer consulted.
|
|
11370
10945
|
*/
|
|
11371
10946
|
async function reportRun(command, rc, _raw, invocation, durationMs, outcome, slardar = {
|
|
11372
|
-
scene,
|
|
11373
|
-
profile,
|
|
10947
|
+
scene: void 0,
|
|
10948
|
+
profile: "standard",
|
|
11374
10949
|
fix: false
|
|
11375
10950
|
}) {
|
|
11376
10951
|
console.error(`${command}: telemetry calling report_cli_run`);
|
|
@@ -11434,7 +11009,7 @@ async function main() {
|
|
|
11434
11009
|
console.error(`${mode}: begin argv=[${args.join(" ")}] version=${getVersion()} traceId=${traceId ?? "-"} caller=${caller ?? "-"} runIdGenerated=${rc.generated}`);
|
|
11435
11010
|
switch (mode) {
|
|
11436
11011
|
case "check": {
|
|
11437
|
-
const raw =
|
|
11012
|
+
const raw = await fetchCtxViaInnerApi({
|
|
11438
11013
|
populate: planCtxPopulate({
|
|
11439
11014
|
command: "check",
|
|
11440
11015
|
profile
|
|
@@ -11459,7 +11034,7 @@ async function main() {
|
|
|
11459
11034
|
break;
|
|
11460
11035
|
}
|
|
11461
11036
|
case "repair": {
|
|
11462
|
-
const raw =
|
|
11037
|
+
const raw = await fetchCtxViaInnerApi({
|
|
11463
11038
|
populate: planCtxPopulate({
|
|
11464
11039
|
command: "repair",
|
|
11465
11040
|
profile
|
|
@@ -11530,27 +11105,15 @@ async function main() {
|
|
|
11530
11105
|
break;
|
|
11531
11106
|
}
|
|
11532
11107
|
case "reset":
|
|
11533
|
-
if (args.includes("--async"))
|
|
11534
|
-
|
|
11535
|
-
let ctxBase64;
|
|
11536
|
-
if (ctxArg) ctxBase64 = ctxArg.slice(6);
|
|
11537
|
-
else {
|
|
11538
|
-
const fetched = await fetchCtxViaInnerApi({
|
|
11539
|
-
populate: planCtxPopulate({ command: "reset" }),
|
|
11540
|
-
caller,
|
|
11541
|
-
traceId
|
|
11542
|
-
});
|
|
11543
|
-
ctxBase64 = Buffer.from(JSON.stringify(fetched), "utf-8").toString("base64");
|
|
11544
|
-
}
|
|
11545
|
-
console.log(JSON.stringify(startAsyncReset(ctxBase64)));
|
|
11546
|
-
} else if (args.includes("--worker")) {
|
|
11108
|
+
if (args.includes("--async")) console.log(JSON.stringify(startAsyncReset()));
|
|
11109
|
+
else if (args.includes("--worker")) {
|
|
11547
11110
|
const taskId = args.find((a) => a.startsWith("--task-id="))?.slice(10);
|
|
11548
11111
|
if (!taskId) {
|
|
11549
11112
|
console.error("Error: --task-id=<id> is required for worker");
|
|
11550
11113
|
node_process.default.exit(1);
|
|
11551
11114
|
}
|
|
11552
11115
|
const resultFile = resetResultFile(taskId);
|
|
11553
|
-
const raw =
|
|
11116
|
+
const raw = await fetchCtxViaInnerApi({
|
|
11554
11117
|
populate: planCtxPopulate({ command: "reset" }),
|
|
11555
11118
|
caller,
|
|
11556
11119
|
traceId
|
|
@@ -11574,7 +11137,7 @@ async function main() {
|
|
|
11574
11137
|
return;
|
|
11575
11138
|
}
|
|
11576
11139
|
} else {
|
|
11577
|
-
console.error("Usage: reset --async
|
|
11140
|
+
console.error("Usage: reset --async | reset --worker --task-id=<id>");
|
|
11578
11141
|
node_process.default.exit(1);
|
|
11579
11142
|
}
|
|
11580
11143
|
break;
|
|
@@ -11590,14 +11153,14 @@ async function main() {
|
|
|
11590
11153
|
case "install-openclaw": {
|
|
11591
11154
|
const tag = getPositionalTag(args, "install-openclaw");
|
|
11592
11155
|
if (!tag) {
|
|
11593
|
-
console.error("Usage: install-openclaw <tag> [--
|
|
11156
|
+
console.error("Usage: install-openclaw <tag> [--oss_file_map=<base64>]");
|
|
11594
11157
|
node_process.default.exit(1);
|
|
11595
11158
|
}
|
|
11596
11159
|
const ossFileMapFlag = getFlag(args, "oss_file_map");
|
|
11597
11160
|
let installOssFileMap;
|
|
11598
11161
|
let rawForTelemetry;
|
|
11599
11162
|
if (!ossFileMapFlag) {
|
|
11600
|
-
rawForTelemetry =
|
|
11163
|
+
rawForTelemetry = await fetchCtxViaInnerApi({
|
|
11601
11164
|
populate: planCtxPopulate({ command: "install" }),
|
|
11602
11165
|
caller,
|
|
11603
11166
|
traceId
|
|
@@ -11632,7 +11195,7 @@ async function main() {
|
|
|
11632
11195
|
case "install-extension": {
|
|
11633
11196
|
const tag = getPositionalTag(args, "install-extension");
|
|
11634
11197
|
if (!tag) {
|
|
11635
|
-
console.error("Usage: install-extension <tag> (--all | --extension=<name>...) [--home_base=<dir>] [--config_path=<path>] [--skip-config-update] [--
|
|
11198
|
+
console.error("Usage: install-extension <tag> (--all | --extension=<name>...) [--home_base=<dir>] [--config_path=<path>] [--skip-config-update] [--oss_file_map=<base64>]");
|
|
11636
11199
|
node_process.default.exit(1);
|
|
11637
11200
|
}
|
|
11638
11201
|
const all = args.includes("--all");
|
|
@@ -11644,7 +11207,7 @@ async function main() {
|
|
|
11644
11207
|
let installOssFileMap;
|
|
11645
11208
|
let rawForTelemetry;
|
|
11646
11209
|
if (!ossFileMapFlag) {
|
|
11647
|
-
rawForTelemetry =
|
|
11210
|
+
rawForTelemetry = await fetchCtxViaInnerApi({
|
|
11648
11211
|
populate: planCtxPopulate({ command: "install" }),
|
|
11649
11212
|
caller,
|
|
11650
11213
|
traceId
|
|
@@ -11690,12 +11253,12 @@ async function main() {
|
|
|
11690
11253
|
case "install-cli": {
|
|
11691
11254
|
const tag = getPositionalTag(args, "install-cli");
|
|
11692
11255
|
if (!tag) {
|
|
11693
|
-
console.error("Usage: install-cli <tag> --cli=<name>... [--home_base=<dir>] [--
|
|
11256
|
+
console.error("Usage: install-cli <tag> --cli=<name>... [--home_base=<dir>] [--oss_file_map=<base64>]");
|
|
11694
11257
|
node_process.default.exit(1);
|
|
11695
11258
|
}
|
|
11696
11259
|
const names = getMultiFlag(args, "cli");
|
|
11697
11260
|
if (names.length === 0) {
|
|
11698
|
-
console.error("Usage: install-cli <tag> --cli=<name>... [--home_base=<dir>] [--
|
|
11261
|
+
console.error("Usage: install-cli <tag> --cli=<name>... [--home_base=<dir>] [--oss_file_map=<base64>]");
|
|
11699
11262
|
node_process.default.exit(1);
|
|
11700
11263
|
}
|
|
11701
11264
|
const homeBase = getFlag(args, "home_base");
|
|
@@ -11703,7 +11266,7 @@ async function main() {
|
|
|
11703
11266
|
let installOssFileMap;
|
|
11704
11267
|
let rawForTelemetry;
|
|
11705
11268
|
if (!ossFileMapFlag) {
|
|
11706
|
-
rawForTelemetry =
|
|
11269
|
+
rawForTelemetry = await fetchCtxViaInnerApi({
|
|
11707
11270
|
populate: planCtxPopulate({ command: "install" }),
|
|
11708
11271
|
caller,
|
|
11709
11272
|
traceId
|
|
@@ -11751,7 +11314,7 @@ async function main() {
|
|
|
11751
11314
|
case "download-resource": {
|
|
11752
11315
|
const tag = getPositionalTag(args, "download-resource");
|
|
11753
11316
|
if (!tag) {
|
|
11754
|
-
console.error("Usage: download-resource <tag> --role=<role> --name=<name> [--dir=<dir>] [--
|
|
11317
|
+
console.error("Usage: download-resource <tag> --role=<role> --name=<name> [--dir=<dir>] [--oss_file_map=<base64>]");
|
|
11755
11318
|
node_process.default.exit(1);
|
|
11756
11319
|
}
|
|
11757
11320
|
const role = getFlag(args, "role");
|
|
@@ -11765,7 +11328,7 @@ async function main() {
|
|
|
11765
11328
|
let installOssFileMap;
|
|
11766
11329
|
let rawForTelemetry;
|
|
11767
11330
|
if (!ossFileMapFlag) {
|
|
11768
|
-
rawForTelemetry =
|
|
11331
|
+
rawForTelemetry = await fetchCtxViaInnerApi({
|
|
11769
11332
|
populate: planCtxPopulate({ command: "install" }),
|
|
11770
11333
|
caller,
|
|
11771
11334
|
traceId
|
|
@@ -11839,50 +11402,6 @@ async function main() {
|
|
|
11839
11402
|
if (!result.ok) node_process.default.exit(1);
|
|
11840
11403
|
break;
|
|
11841
11404
|
}
|
|
11842
|
-
case "upgrade-lark": {
|
|
11843
|
-
const result = runUpgradeLark({
|
|
11844
|
-
runId: rc.runId,
|
|
11845
|
-
scene
|
|
11846
|
-
});
|
|
11847
|
-
const upgradeDurationMs = Date.now() - t0;
|
|
11848
|
-
console.log(JSON.stringify(result));
|
|
11849
|
-
reportUpgradeLarkToSlardar({
|
|
11850
|
-
scene,
|
|
11851
|
-
durationMs: upgradeDurationMs,
|
|
11852
|
-
success: result.ok,
|
|
11853
|
-
logFile: result.logFile,
|
|
11854
|
-
exitCode: result.exitCode,
|
|
11855
|
-
rollbackOk: result.rollbackOk,
|
|
11856
|
-
validationError: result.validationError,
|
|
11857
|
-
error: result.error
|
|
11858
|
-
});
|
|
11859
|
-
try {
|
|
11860
|
-
await reportCliRun({
|
|
11861
|
-
command: "upgrade-lark",
|
|
11862
|
-
runId: rc.runId,
|
|
11863
|
-
version: getVersion(),
|
|
11864
|
-
invocation: args.join(" "),
|
|
11865
|
-
durationMs: upgradeDurationMs,
|
|
11866
|
-
caller: rc.caller,
|
|
11867
|
-
traceId: rc.traceId,
|
|
11868
|
-
success: result.ok,
|
|
11869
|
-
result,
|
|
11870
|
-
error: result.ok ? void 0 : { message: result.error ?? "upgrade-lark failed" }
|
|
11871
|
-
});
|
|
11872
|
-
} catch (e) {
|
|
11873
|
-
console.error(`[telemetry] reportCliRun failed: ${e.message}`);
|
|
11874
|
-
}
|
|
11875
|
-
if (!result.ok) {
|
|
11876
|
-
node_process.default.exitCode = 1;
|
|
11877
|
-
return;
|
|
11878
|
-
}
|
|
11879
|
-
break;
|
|
11880
|
-
}
|
|
11881
|
-
case "channels-probe": {
|
|
11882
|
-
const result = runChannelsProbe(getFlag(args, "timeout") ? Number(getFlag(args, "timeout")) : void 0);
|
|
11883
|
-
console.log(JSON.stringify(result));
|
|
11884
|
-
break;
|
|
11885
|
-
}
|
|
11886
11405
|
default:
|
|
11887
11406
|
node_process.default.stderr.write(`Unknown command: ${mode}\n\n`);
|
|
11888
11407
|
node_process.default.stderr.write(formatTopLevelHelp(helpFlags.expert));
|