@lark-apaas/openclaw-scripts-diagnose-cli 0.1.15-alpha.0 → 0.1.15-alpha.2
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 +816 -611
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -52,7 +52,7 @@ node_assert = __toESM(node_assert);
|
|
|
52
52
|
* it terse and parseable.
|
|
53
53
|
*/
|
|
54
54
|
function getVersion() {
|
|
55
|
-
return "0.1.15-alpha.
|
|
55
|
+
return "0.1.15-alpha.2";
|
|
56
56
|
}
|
|
57
57
|
//#endregion
|
|
58
58
|
//#region src/rule-engine/base.ts
|
|
@@ -2520,6 +2520,341 @@ function upsertResourceConstrainedToolsBlock(content) {
|
|
|
2520
2520
|
return `${content}${content.length > 0 && !content.endsWith("\n") ? "\n\n" : "\n"}${RESOURCE_CONSTRAINED_TOOLS_BLOCK}\n`;
|
|
2521
2521
|
}
|
|
2522
2522
|
//#endregion
|
|
2523
|
+
//#region src/paths.ts
|
|
2524
|
+
/**
|
|
2525
|
+
* Central directory for all ephemeral diagnose/reset artifacts: task status
|
|
2526
|
+
* files (`reset-<taskId>.json`) and human-readable step logs
|
|
2527
|
+
* (`reset-<taskId>.log`). Having everything under one dir makes debugging a
|
|
2528
|
+
* stuck reset much easier — `ls /tmp/openclaw-diagnose/` shows every recent
|
|
2529
|
+
* run, and each run's log is right next to its state.
|
|
2530
|
+
*/
|
|
2531
|
+
const DIAGNOSE_DIR = "/tmp/openclaw-diagnose";
|
|
2532
|
+
function resetResultFile(taskId) {
|
|
2533
|
+
return `${DIAGNOSE_DIR}/reset-${taskId}.json`;
|
|
2534
|
+
}
|
|
2535
|
+
function resetLogFile(taskId) {
|
|
2536
|
+
return `${DIAGNOSE_DIR}/reset-${taskId}.log`;
|
|
2537
|
+
}
|
|
2538
|
+
/** Sandbox workspace root where openclaw config + agent state lives. */
|
|
2539
|
+
const WORKSPACE_DIR = "/home/gem/workspace/agent";
|
|
2540
|
+
/** File containing the provider key used by the openclaw miaoda provider. */
|
|
2541
|
+
const PROVIDER_FILE_PATH = "/home/gem/workspace/.force/openclaw/miaoda-provider-key";
|
|
2542
|
+
/** File containing the miaoda openclaw secrets JSON. */
|
|
2543
|
+
const SECRETS_FILE_PATH = "/home/gem/workspace/.force/openclaw/miaoda-openclaw-secrets.json";
|
|
2544
|
+
/** Absolute path to the openclaw config JSON. */
|
|
2545
|
+
const CONFIG_PATH = `${WORKSPACE_DIR}/openclaw.json`;
|
|
2546
|
+
/** upgrade-lark 每次运行的日志文件路径,含时间戳便于按时间排序定位。 */
|
|
2547
|
+
function upgradeLarkLogFile(runId) {
|
|
2548
|
+
return `${DIAGNOSE_DIR}/upgrade-lark-${(/* @__PURE__ */ new Date()).toISOString().slice(0, 19).replace(/:/g, "-")}-${runId.slice(0, 8)}.log`;
|
|
2549
|
+
}
|
|
2550
|
+
//#endregion
|
|
2551
|
+
//#region src/lark-cli-init.ts
|
|
2552
|
+
const LARK_PLUGIN_NAMES$1 = ["openclaw-lark", "feishu-openclaw-plugin"];
|
|
2553
|
+
const PE_XML_TAG = "lark-cli-pe";
|
|
2554
|
+
const PE_PLACEHOLDER = `
|
|
2555
|
+
<${PE_XML_TAG}>
|
|
2556
|
+
**【强制要求 - 无例外】飞书所有操作必须先读 feishu-lark-cli skill。未读skill不得调用任何飞书工具,违者视为操作失误。**
|
|
2557
|
+
</${PE_XML_TAG}>
|
|
2558
|
+
`;
|
|
2559
|
+
function isLarkPluginInstalled(configPath) {
|
|
2560
|
+
const extDir = getExtensionsDir(configPath);
|
|
2561
|
+
return LARK_PLUGIN_NAMES$1.some((name) => {
|
|
2562
|
+
try {
|
|
2563
|
+
return node_fs.default.existsSync(node_path.default.join(extDir, name, "package.json"));
|
|
2564
|
+
} catch {
|
|
2565
|
+
return false;
|
|
2566
|
+
}
|
|
2567
|
+
});
|
|
2568
|
+
}
|
|
2569
|
+
function isLarkCliAvailable$2() {
|
|
2570
|
+
try {
|
|
2571
|
+
return (0, node_child_process.spawnSync)("lark-cli", ["--version"], {
|
|
2572
|
+
encoding: "utf-8",
|
|
2573
|
+
timeout: 5e3,
|
|
2574
|
+
stdio: [
|
|
2575
|
+
"ignore",
|
|
2576
|
+
"pipe",
|
|
2577
|
+
"ignore"
|
|
2578
|
+
]
|
|
2579
|
+
}).status === 0;
|
|
2580
|
+
} catch {
|
|
2581
|
+
return false;
|
|
2582
|
+
}
|
|
2583
|
+
}
|
|
2584
|
+
function readConfig(configPath) {
|
|
2585
|
+
try {
|
|
2586
|
+
const raw = node_fs.default.readFileSync(configPath, "utf-8");
|
|
2587
|
+
const parsed = loadJSON5().parse(raw);
|
|
2588
|
+
return parsed != null && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
|
|
2589
|
+
} catch {
|
|
2590
|
+
return null;
|
|
2591
|
+
}
|
|
2592
|
+
}
|
|
2593
|
+
/**
|
|
2594
|
+
* Resolve the feishu app secret for the given appId.
|
|
2595
|
+
*
|
|
2596
|
+
* Lookup order:
|
|
2597
|
+
* 1. channels.feishu.appSecret (single-agent: feishu.appId === appId)
|
|
2598
|
+
* 2. channels.feishu.accounts[key].appSecret (multi-agent: account.appId === appId)
|
|
2599
|
+
*
|
|
2600
|
+
* Value interpretation:
|
|
2601
|
+
* - string → use directly
|
|
2602
|
+
* - object → secret is managed by a provider; use `feishuAppSecret` param instead
|
|
2603
|
+
*
|
|
2604
|
+
* Returns null when the secret cannot be determined.
|
|
2605
|
+
*/
|
|
2606
|
+
function resolveAppSecret(appId, config, feishuAppSecret) {
|
|
2607
|
+
const feishu = getNestedMap(config, "channels", "feishu");
|
|
2608
|
+
if (!feishu) return null;
|
|
2609
|
+
let rawSecret;
|
|
2610
|
+
if (typeof feishu.appId === "string" && feishu.appId === appId) rawSecret = feishu.appSecret;
|
|
2611
|
+
else {
|
|
2612
|
+
const accounts = asRecord(feishu.accounts);
|
|
2613
|
+
if (accounts) for (const [, val] of Object.entries(accounts)) {
|
|
2614
|
+
const account = asRecord(val);
|
|
2615
|
+
if (account?.appId === appId) {
|
|
2616
|
+
rawSecret = account.appSecret ?? feishu.appSecret;
|
|
2617
|
+
break;
|
|
2618
|
+
}
|
|
2619
|
+
}
|
|
2620
|
+
}
|
|
2621
|
+
if (typeof rawSecret === "string" && rawSecret) return rawSecret;
|
|
2622
|
+
if (rawSecret != null && typeof rawSecret === "object") return feishuAppSecret ?? null;
|
|
2623
|
+
return null;
|
|
2624
|
+
}
|
|
2625
|
+
/**
|
|
2626
|
+
* Resolve the agents.md path for the given appId from the openclaw config.
|
|
2627
|
+
*
|
|
2628
|
+
* Case 1: appId matches channels.feishu.appId (single-agent path)
|
|
2629
|
+
* → WORKSPACE_DIR/AGENTS.md
|
|
2630
|
+
*
|
|
2631
|
+
* Case 2: appId found in channels.feishu.accounts (multi-agent path)
|
|
2632
|
+
* → find account key where account.appId === appId
|
|
2633
|
+
* → find binding where match.channel=feishu && match.accountId=that key
|
|
2634
|
+
* → if agentId === 'main' → WORKSPACE_DIR/agents.md
|
|
2635
|
+
* → else find agent in agents.list by id → agent.workspace/agents.md
|
|
2636
|
+
*
|
|
2637
|
+
* Returns null when the path cannot be determined.
|
|
2638
|
+
*/
|
|
2639
|
+
function resolveAgentsMdPath(appId, config) {
|
|
2640
|
+
const feishu = getNestedMap(config, "channels", "feishu");
|
|
2641
|
+
if (!feishu) {
|
|
2642
|
+
console.error("resolveAgentsMdPath: channels.feishu not found");
|
|
2643
|
+
return null;
|
|
2644
|
+
}
|
|
2645
|
+
if (typeof feishu.appId === "string" && feishu.appId === appId) {
|
|
2646
|
+
console.error(`resolveAgentsMdPath: case=single-agent feishu.appId=${feishu.appId}`);
|
|
2647
|
+
return node_path.default.join(WORKSPACE_DIR, "workspace", "AGENTS.md");
|
|
2648
|
+
}
|
|
2649
|
+
const accounts = asRecord(feishu.accounts);
|
|
2650
|
+
if (!accounts) {
|
|
2651
|
+
console.error("resolveAgentsMdPath: feishu.accounts not found");
|
|
2652
|
+
return null;
|
|
2653
|
+
}
|
|
2654
|
+
let accountId;
|
|
2655
|
+
for (const [key, val] of Object.entries(accounts)) if (asRecord(val)?.appId === appId) {
|
|
2656
|
+
accountId = key;
|
|
2657
|
+
break;
|
|
2658
|
+
}
|
|
2659
|
+
if (!accountId) {
|
|
2660
|
+
console.error(`resolveAgentsMdPath: no account found with appId=${appId} in feishu.accounts keys=[${Object.keys(accounts).join(",")}]`);
|
|
2661
|
+
return null;
|
|
2662
|
+
}
|
|
2663
|
+
console.error(`resolveAgentsMdPath: found accountId=${accountId}`);
|
|
2664
|
+
const bindings = Array.isArray(config.bindings) ? config.bindings : [];
|
|
2665
|
+
let agentId;
|
|
2666
|
+
for (const b of bindings) {
|
|
2667
|
+
const binding = asRecord(b);
|
|
2668
|
+
if (!binding) continue;
|
|
2669
|
+
const match = asRecord(binding.match);
|
|
2670
|
+
if (match?.channel === "feishu" && match?.accountId === accountId) {
|
|
2671
|
+
if (typeof binding.agentId === "string") {
|
|
2672
|
+
agentId = binding.agentId;
|
|
2673
|
+
break;
|
|
2674
|
+
}
|
|
2675
|
+
}
|
|
2676
|
+
}
|
|
2677
|
+
if (!agentId) {
|
|
2678
|
+
console.error(`resolveAgentsMdPath: no binding found for accountId=${accountId} in bindings(count=${bindings.length})`);
|
|
2679
|
+
return null;
|
|
2680
|
+
}
|
|
2681
|
+
console.error(`resolveAgentsMdPath: found agentId=${agentId}`);
|
|
2682
|
+
if (agentId === "main") {
|
|
2683
|
+
console.error("resolveAgentsMdPath: case=multi-agent-main");
|
|
2684
|
+
return node_path.default.join(WORKSPACE_DIR, "workspace", "AGENTS.md");
|
|
2685
|
+
}
|
|
2686
|
+
const agentsObj = asRecord(config.agents);
|
|
2687
|
+
const list = Array.isArray(agentsObj?.list) ? agentsObj.list : [];
|
|
2688
|
+
for (const a of list) {
|
|
2689
|
+
const agent = asRecord(a);
|
|
2690
|
+
if (agent?.id === agentId) {
|
|
2691
|
+
const ws = typeof agent.workspace === "string" ? agent.workspace : node_path.default.join(WORKSPACE_DIR, "workspace");
|
|
2692
|
+
console.error(`resolveAgentsMdPath: case=multi-agent-custom agentId=${agentId} workspace=${ws}`);
|
|
2693
|
+
return node_path.default.join(ws, "AGENTS.md");
|
|
2694
|
+
}
|
|
2695
|
+
}
|
|
2696
|
+
console.error(`resolveAgentsMdPath: agentId=${agentId} not found in agents.list(count=${list.length})`);
|
|
2697
|
+
return null;
|
|
2698
|
+
}
|
|
2699
|
+
function appendPeToAgentsMd(agentsMdPath) {
|
|
2700
|
+
const dir = node_path.default.dirname(agentsMdPath);
|
|
2701
|
+
if (!node_fs.default.existsSync(dir)) node_fs.default.mkdirSync(dir, { recursive: true });
|
|
2702
|
+
const existing = node_fs.default.existsSync(agentsMdPath) ? node_fs.default.readFileSync(agentsMdPath, "utf-8") : "";
|
|
2703
|
+
if (existing.includes(`<lark-cli-pe>`)) {
|
|
2704
|
+
console.error(`lark-cli-init: <${PE_XML_TAG}> already present in ${agentsMdPath}, skipping`);
|
|
2705
|
+
return;
|
|
2706
|
+
}
|
|
2707
|
+
const sep = existing.length > 0 && !existing.endsWith("\n") ? "\n" : "";
|
|
2708
|
+
node_fs.default.appendFileSync(agentsMdPath, `${sep}${PE_PLACEHOLDER}`, "utf-8");
|
|
2709
|
+
console.error(`lark-cli-init: appended PE placeholder to ${agentsMdPath}`);
|
|
2710
|
+
}
|
|
2711
|
+
/**
|
|
2712
|
+
* Collect every Feishu bot appId declared in the openclaw config.
|
|
2713
|
+
* Covers both single-agent (channels.feishu.appId) and multi-agent
|
|
2714
|
+
* (channels.feishu.accounts[*].appId) layouts. Returns a deduplicated list.
|
|
2715
|
+
*/
|
|
2716
|
+
function collectFeishuAppIds(configPath) {
|
|
2717
|
+
const config = readConfig(configPath ?? CONFIG_PATH);
|
|
2718
|
+
if (!config) return [];
|
|
2719
|
+
const feishu = getNestedMap(config, "channels", "feishu");
|
|
2720
|
+
if (!feishu) return [];
|
|
2721
|
+
const appIds = /* @__PURE__ */ new Set();
|
|
2722
|
+
const topAppId = feishu.appId;
|
|
2723
|
+
if (typeof topAppId === "string" && topAppId.trim()) appIds.add(topAppId.trim());
|
|
2724
|
+
const accounts = asRecord(feishu.accounts);
|
|
2725
|
+
if (accounts) for (const val of Object.values(accounts)) {
|
|
2726
|
+
const appId = asRecord(val)?.appId;
|
|
2727
|
+
if (typeof appId === "string" && appId.trim()) appIds.add(appId.trim());
|
|
2728
|
+
}
|
|
2729
|
+
return [...appIds];
|
|
2730
|
+
}
|
|
2731
|
+
function runLarkCliInit(opts) {
|
|
2732
|
+
const configPath = opts.configPath ?? CONFIG_PATH;
|
|
2733
|
+
if (!isLarkPluginInstalled(configPath)) {
|
|
2734
|
+
console.error("lark-cli-init: skipping — openclaw-lark plugin not installed");
|
|
2735
|
+
return {
|
|
2736
|
+
ok: true,
|
|
2737
|
+
skipped: true,
|
|
2738
|
+
skipReason: "openclaw-lark plugin not installed"
|
|
2739
|
+
};
|
|
2740
|
+
}
|
|
2741
|
+
if (!isLarkCliAvailable$2()) {
|
|
2742
|
+
console.error("lark-cli-init: skipping — lark-cli command not found");
|
|
2743
|
+
return {
|
|
2744
|
+
ok: true,
|
|
2745
|
+
skipped: true,
|
|
2746
|
+
skipReason: "lark-cli command not found"
|
|
2747
|
+
};
|
|
2748
|
+
}
|
|
2749
|
+
const config = readConfig(configPath);
|
|
2750
|
+
if (!config) return {
|
|
2751
|
+
ok: false,
|
|
2752
|
+
error: `could not read config at ${configPath}`
|
|
2753
|
+
};
|
|
2754
|
+
const agentsMdPath = resolveAgentsMdPath(opts.appId, config);
|
|
2755
|
+
console.error(`lark-cli-init: resolved agents.md path=${agentsMdPath ?? "(null)"}`);
|
|
2756
|
+
if (!agentsMdPath) return {
|
|
2757
|
+
ok: false,
|
|
2758
|
+
error: `could not resolve agents.md path for appId=${opts.appId}`
|
|
2759
|
+
};
|
|
2760
|
+
const appSecret = resolveAppSecret(opts.appId, config, opts.feishuAppSecret);
|
|
2761
|
+
if (!appSecret) return {
|
|
2762
|
+
ok: false,
|
|
2763
|
+
error: `could not resolve appSecret for appId=${opts.appId}`
|
|
2764
|
+
};
|
|
2765
|
+
console.error(`lark-cli-init: running lark-cli config init --name ${opts.appId} --app-id ${opts.appId} --brand feishu --app-secret-stdin --force-init`);
|
|
2766
|
+
const initRes = (0, node_child_process.spawnSync)("lark-cli", [
|
|
2767
|
+
"config",
|
|
2768
|
+
"init",
|
|
2769
|
+
"--name",
|
|
2770
|
+
opts.appId,
|
|
2771
|
+
"--app-id",
|
|
2772
|
+
opts.appId,
|
|
2773
|
+
"--brand",
|
|
2774
|
+
"feishu",
|
|
2775
|
+
"--app-secret-stdin",
|
|
2776
|
+
"--force-init"
|
|
2777
|
+
], {
|
|
2778
|
+
stdio: [
|
|
2779
|
+
"pipe",
|
|
2780
|
+
"pipe",
|
|
2781
|
+
"pipe"
|
|
2782
|
+
],
|
|
2783
|
+
encoding: "utf-8",
|
|
2784
|
+
input: appSecret
|
|
2785
|
+
});
|
|
2786
|
+
const configInitStdout = initRes.stdout?.trim() || void 0;
|
|
2787
|
+
const configInitStderr = initRes.stderr?.trim() || void 0;
|
|
2788
|
+
if (configInitStdout) console.error(`lark-cli config init stdout: ${configInitStdout}`);
|
|
2789
|
+
if (configInitStderr) console.error(`lark-cli config init stderr: ${configInitStderr}`);
|
|
2790
|
+
if (initRes.error) return {
|
|
2791
|
+
ok: false,
|
|
2792
|
+
configInitStdout,
|
|
2793
|
+
configInitStderr,
|
|
2794
|
+
error: `lark-cli config init spawn error: ${initRes.error.message}`
|
|
2795
|
+
};
|
|
2796
|
+
if (initRes.status !== 0) return {
|
|
2797
|
+
ok: false,
|
|
2798
|
+
configInitExitCode: initRes.status ?? void 0,
|
|
2799
|
+
configInitStdout,
|
|
2800
|
+
configInitStderr,
|
|
2801
|
+
error: `lark-cli config init exited with code ${initRes.status}`
|
|
2802
|
+
};
|
|
2803
|
+
appendPeToAgentsMd(agentsMdPath);
|
|
2804
|
+
return {
|
|
2805
|
+
ok: true,
|
|
2806
|
+
configInitExitCode: 0,
|
|
2807
|
+
agentsMdPath
|
|
2808
|
+
};
|
|
2809
|
+
}
|
|
2810
|
+
//#endregion
|
|
2811
|
+
//#region src/rules/agents-md-lark-cli-pe.ts
|
|
2812
|
+
function isLarkCliAvailable$1() {
|
|
2813
|
+
try {
|
|
2814
|
+
return (0, node_child_process.spawnSync)("lark-cli", ["--version"], {
|
|
2815
|
+
encoding: "utf-8",
|
|
2816
|
+
timeout: 5e3,
|
|
2817
|
+
stdio: [
|
|
2818
|
+
"ignore",
|
|
2819
|
+
"pipe",
|
|
2820
|
+
"ignore"
|
|
2821
|
+
]
|
|
2822
|
+
}).status === 0;
|
|
2823
|
+
} catch {
|
|
2824
|
+
return false;
|
|
2825
|
+
}
|
|
2826
|
+
}
|
|
2827
|
+
let AgentsMdLarkCliPeRule = class AgentsMdLarkCliPeRule extends DiagnoseRule {
|
|
2828
|
+
validate(ctx) {
|
|
2829
|
+
if (!isLarkCliAvailable$1()) return { pass: true };
|
|
2830
|
+
const missingPath = collectExistingAgentsMdPaths(ctx).find((filePath) => {
|
|
2831
|
+
return !node_fs.default.readFileSync(filePath, "utf-8").includes(`<${PE_XML_TAG}>`);
|
|
2832
|
+
});
|
|
2833
|
+
if (!missingPath) return { pass: true };
|
|
2834
|
+
return {
|
|
2835
|
+
pass: false,
|
|
2836
|
+
message: `${missingPath} 中缺少 lark-cli-pe PE 内容,需要追加`
|
|
2837
|
+
};
|
|
2838
|
+
}
|
|
2839
|
+
repair(ctx) {
|
|
2840
|
+
if (!isLarkCliAvailable$1()) return;
|
|
2841
|
+
for (const filePath of collectExistingAgentsMdPaths(ctx)) {
|
|
2842
|
+
const content = node_fs.default.readFileSync(filePath, "utf-8");
|
|
2843
|
+
if (content.includes(`<lark-cli-pe>`)) continue;
|
|
2844
|
+
const sep = content.length > 0 && !content.endsWith("\n") ? "\n" : "";
|
|
2845
|
+
node_fs.default.appendFileSync(filePath, `${sep}${PE_PLACEHOLDER}`, "utf-8");
|
|
2846
|
+
console.error(`agents-md-lark-cli-pe: appended PE to ${filePath}`);
|
|
2847
|
+
}
|
|
2848
|
+
}
|
|
2849
|
+
};
|
|
2850
|
+
AgentsMdLarkCliPeRule = __decorate([Rule({
|
|
2851
|
+
key: "agents_md_lark_cli_pe",
|
|
2852
|
+
description: "检测各智能体 AGENTS.md 中是否缺失 lark-cli-pe PE 内容,lark-cli 存在时自动追加",
|
|
2853
|
+
dependsOn: ["config_syntax_check"],
|
|
2854
|
+
repairMode: "standard",
|
|
2855
|
+
level: "silent"
|
|
2856
|
+
})], AgentsMdLarkCliPeRule);
|
|
2857
|
+
//#endregion
|
|
2523
2858
|
//#region src/rules/miaoda-official-plugins-install-spec-unlock.ts
|
|
2524
2859
|
/**
|
|
2525
2860
|
* Official miaoda-side plugins that must track manifest — version-locked specs
|
|
@@ -2623,11 +2958,11 @@ function getAllow$1(config) {
|
|
|
2623
2958
|
//#region src/rules/lark-plugin-allow.ts
|
|
2624
2959
|
const LARK_PLUGIN = "openclaw-lark";
|
|
2625
2960
|
const LEGACY_LARK_PLUGIN = "feishu-openclaw-plugin";
|
|
2626
|
-
const LARK_PLUGIN_NAMES
|
|
2961
|
+
const LARK_PLUGIN_NAMES = [LARK_PLUGIN, LEGACY_LARK_PLUGIN];
|
|
2627
2962
|
let LarkPluginAllowRule = class LarkPluginAllowRule extends DiagnoseRule {
|
|
2628
2963
|
validate(ctx) {
|
|
2629
2964
|
const allow = getAllow(ctx.config);
|
|
2630
|
-
if (LARK_PLUGIN_NAMES
|
|
2965
|
+
if (LARK_PLUGIN_NAMES.some((name) => allow.includes(name))) return { pass: true };
|
|
2631
2966
|
const installed = detectInstalledLarkPlugin(getExtensionsDir(ctx.configPath));
|
|
2632
2967
|
if (installed == null) return { pass: true };
|
|
2633
2968
|
return {
|
|
@@ -2646,7 +2981,7 @@ let LarkPluginAllowRule = class LarkPluginAllowRule extends DiagnoseRule {
|
|
|
2646
2981
|
const rawAllow = pluginsMap.allow;
|
|
2647
2982
|
const original = Array.isArray(rawAllow) ? rawAllow : [];
|
|
2648
2983
|
const stringAllow = original.filter((e) => typeof e === "string");
|
|
2649
|
-
if (LARK_PLUGIN_NAMES
|
|
2984
|
+
if (LARK_PLUGIN_NAMES.some((name) => stringAllow.includes(name))) return;
|
|
2650
2985
|
original.push(installed);
|
|
2651
2986
|
pluginsMap.allow = original;
|
|
2652
2987
|
}
|
|
@@ -3420,11 +3755,14 @@ FeishuPluginLarkUpgradeRule = __decorate([Rule({
|
|
|
3420
3755
|
usesVars: ["recommendedOpenclawTag"]
|
|
3421
3756
|
})], FeishuPluginLarkUpgradeRule);
|
|
3422
3757
|
/**
|
|
3423
|
-
*
|
|
3424
|
-
*
|
|
3758
|
+
* 核心判断:非 fork 插件是否需要升级 lark,基于当前 openclaw 版本的兼容性。
|
|
3759
|
+
*
|
|
3760
|
+
* 被 FeishuPluginLarkUpgradeRule.validate 和 needsLarkUpgrade 共用。
|
|
3761
|
+
* 调用方需在调用前自行处理 fork 插件的情况(fork 插件不走本函数)。
|
|
3425
3762
|
*
|
|
3426
|
-
*
|
|
3427
|
-
*
|
|
3763
|
+
* - 有 recommendedOc:走 resolveUpgradeDirection 判断方向是否为 'lark'
|
|
3764
|
+
* - 无 recommendedOc(doctor 无推荐版本):legacy 插件直接需要升级;
|
|
3765
|
+
* 非 legacy 则检查当前版本是否在兼容表内
|
|
3428
3766
|
*/
|
|
3429
3767
|
function isLarkUpgradeNeededFromCC(cc) {
|
|
3430
3768
|
const { ocCur, recommendedOc, installed, isLegacy } = cc;
|
|
@@ -3526,9 +3864,8 @@ function extractScopedNameFromSpec$1(spec) {
|
|
|
3526
3864
|
return at === -1 ? spec : spec.slice(0, at);
|
|
3527
3865
|
}
|
|
3528
3866
|
/**
|
|
3529
|
-
*
|
|
3530
|
-
*
|
|
3531
|
-
* Used by the upgrade_lark_needed rule and the upgrade-lark pre-check gate.
|
|
3867
|
+
* 判断已安装的飞书插件是否与当前 openclaw 版本不兼容(或为需要替换的 legacy 插件)。
|
|
3868
|
+
* 被 upgrade-lark 前置检测门控(--check-only 和正式安装模式)调用。
|
|
3532
3869
|
*/
|
|
3533
3870
|
function needsLarkUpgrade(ctx) {
|
|
3534
3871
|
const cc = resolveCompatContext({
|
|
@@ -3547,177 +3884,6 @@ function needsLarkUpgrade(ctx) {
|
|
|
3547
3884
|
return isLarkUpgradeNeededFromCC(cc);
|
|
3548
3885
|
}
|
|
3549
3886
|
//#endregion
|
|
3550
|
-
//#region src/channels-probe.ts
|
|
3551
|
-
const FEISHU_INVALID_CONFIG_MSG = "channels.feishu: invalid config: must NOT have additional properties";
|
|
3552
|
-
const CHANNEL_LINE_RE = /^-\s+Feishu\s+([^:]+):\s+(.+)$/;
|
|
3553
|
-
/**
|
|
3554
|
-
* Port of Python `_account_is_working` from the feishu-channel-success-rate skill.
|
|
3555
|
-
*
|
|
3556
|
-
* Strips colon-prefixed key:value bits (dm:, bot:, in:, out:, token:, allow:,
|
|
3557
|
-
* intents:, groups:, health:) and evaluates the canonical health formula.
|
|
3558
|
-
*
|
|
3559
|
-
* @param ignoreProbeFailed When true, "probe failed" is not treated as a failure condition.
|
|
3560
|
-
* @param gatewayReachable When false, only enabled+configured is required — the service
|
|
3561
|
-
* is not started yet so "running" cannot be present, but config presence is sufficient.
|
|
3562
|
-
*/
|
|
3563
|
-
function accountIsWorking(bits, ignoreProbeFailed = true, gatewayReachable = true) {
|
|
3564
|
-
const bitTokens = /* @__PURE__ */ new Set();
|
|
3565
|
-
let hasError = false;
|
|
3566
|
-
let hasProbeFailed = false;
|
|
3567
|
-
for (const raw of bits) {
|
|
3568
|
-
const b = raw.trim();
|
|
3569
|
-
if (!b) continue;
|
|
3570
|
-
if (b.startsWith("error:")) {
|
|
3571
|
-
hasError = true;
|
|
3572
|
-
continue;
|
|
3573
|
-
}
|
|
3574
|
-
if (b === "probe failed") {
|
|
3575
|
-
hasProbeFailed = true;
|
|
3576
|
-
continue;
|
|
3577
|
-
}
|
|
3578
|
-
bitTokens.add(b.split(":")[0]);
|
|
3579
|
-
}
|
|
3580
|
-
if (!bitTokens.has("enabled") || !bitTokens.has("configured")) return false;
|
|
3581
|
-
if (!gatewayReachable) return true;
|
|
3582
|
-
if (bitTokens.has("works")) return true;
|
|
3583
|
-
if (bitTokens.has("running") && !hasError && (ignoreProbeFailed || !hasProbeFailed)) return true;
|
|
3584
|
-
return false;
|
|
3585
|
-
}
|
|
3586
|
-
/**
|
|
3587
|
-
* Parse the raw stdout of `openclaw channels status --probe`.
|
|
3588
|
-
* Port of Python `extract_channels_probe` from the feishu-channel-success-rate skill.
|
|
3589
|
-
*/
|
|
3590
|
-
function parseChannelsProbeOutput(text, { ignoreProbeFailed = true } = {}) {
|
|
3591
|
-
const gatewayReachable = text.includes("Gateway reachable");
|
|
3592
|
-
const feishuConfigInvalid = text.includes(FEISHU_INVALID_CONFIG_MSG);
|
|
3593
|
-
const accounts = [];
|
|
3594
|
-
let anyAccountWorking = false;
|
|
3595
|
-
for (const line of text.split("\n")) {
|
|
3596
|
-
const m = CHANNEL_LINE_RE.exec(line.trim());
|
|
3597
|
-
if (!m) continue;
|
|
3598
|
-
const [, acct, rest] = m;
|
|
3599
|
-
const bits = rest.split(",").map((b) => b.trim());
|
|
3600
|
-
const isWorking = accountIsWorking(bits, ignoreProbeFailed, gatewayReachable);
|
|
3601
|
-
if (isWorking) anyAccountWorking = true;
|
|
3602
|
-
accounts.push({
|
|
3603
|
-
id: acct.trim(),
|
|
3604
|
-
bits,
|
|
3605
|
-
isWorking,
|
|
3606
|
-
raw: line.trim()
|
|
3607
|
-
});
|
|
3608
|
-
}
|
|
3609
|
-
return {
|
|
3610
|
-
gatewayReachable,
|
|
3611
|
-
feishuConfigInvalid,
|
|
3612
|
-
accounts,
|
|
3613
|
-
anyAccountWorking
|
|
3614
|
-
};
|
|
3615
|
-
}
|
|
3616
|
-
/**
|
|
3617
|
-
* Run `openclaw channels status --probe` and return a structured result.
|
|
3618
|
-
*
|
|
3619
|
-
* The command may exit non-zero when some bot accounts fail their probe — that
|
|
3620
|
-
* is still useful output. We therefore try to parse stdout even when the
|
|
3621
|
-
* process exits with a non-zero code, falling back to an unavailable result
|
|
3622
|
-
* only when there is genuinely no output to parse.
|
|
3623
|
-
*
|
|
3624
|
-
* @param timeoutMs Maximum wait time. Default is 60 s because v2026.4.x
|
|
3625
|
-
* lacks a per-request HTTP timeout and can block indefinitely.
|
|
3626
|
-
* @param ignoreProbeFailed When true, accounts with "probe failed" are still
|
|
3627
|
-
* counted as working. Pass true for upgrade-gate checks where probe failures
|
|
3628
|
-
* reflect network conditions rather than plugin misconfiguration.
|
|
3629
|
-
*/
|
|
3630
|
-
function runChannelsProbe(timeoutMs = 6e4, { ignoreProbeFailed = true } = {}) {
|
|
3631
|
-
let stdout = "";
|
|
3632
|
-
let stderrText = "";
|
|
3633
|
-
let execError;
|
|
3634
|
-
try {
|
|
3635
|
-
stdout = (0, node_child_process.execSync)("openclaw channels status --probe", {
|
|
3636
|
-
encoding: "utf-8",
|
|
3637
|
-
timeout: timeoutMs,
|
|
3638
|
-
stdio: [
|
|
3639
|
-
"ignore",
|
|
3640
|
-
"pipe",
|
|
3641
|
-
"pipe"
|
|
3642
|
-
]
|
|
3643
|
-
});
|
|
3644
|
-
} catch (e) {
|
|
3645
|
-
const err = e;
|
|
3646
|
-
const stdoutRaw = err.stdout;
|
|
3647
|
-
stdout = typeof stdoutRaw === "string" ? stdoutRaw : stdoutRaw?.toString("utf-8") ?? "";
|
|
3648
|
-
execError = err.message;
|
|
3649
|
-
const stderrRaw = err.stderr;
|
|
3650
|
-
stderrText = (typeof stderrRaw === "string" ? stderrRaw : stderrRaw?.toString("utf-8") ?? "").trim();
|
|
3651
|
-
if (stderrText) console.error(`channels-probe: stderr from CLI: ${stderrText}`);
|
|
3652
|
-
}
|
|
3653
|
-
if (stdout.trim()) return {
|
|
3654
|
-
available: true,
|
|
3655
|
-
...parseChannelsProbeOutput(stdout, { ignoreProbeFailed })
|
|
3656
|
-
};
|
|
3657
|
-
return {
|
|
3658
|
-
available: false,
|
|
3659
|
-
gatewayReachable: false,
|
|
3660
|
-
feishuConfigInvalid: stderrText.includes(FEISHU_INVALID_CONFIG_MSG),
|
|
3661
|
-
accounts: [],
|
|
3662
|
-
anyAccountWorking: false,
|
|
3663
|
-
error: execError ?? "no output from openclaw channels status --probe"
|
|
3664
|
-
};
|
|
3665
|
-
}
|
|
3666
|
-
//#endregion
|
|
3667
|
-
//#region src/rules/upgrade-lark-needed.ts
|
|
3668
|
-
/**
|
|
3669
|
-
* Detects the condition that warrants running `upgrade-lark`:
|
|
3670
|
-
* - feishu plugin version incompatible with current openclaw, OR
|
|
3671
|
-
* - openclaw channels status --probe reports feishu channel config invalid; AND
|
|
3672
|
-
* - channels are not working.
|
|
3673
|
-
*
|
|
3674
|
-
* Both conditions must be true simultaneously. If version is compatible and
|
|
3675
|
-
* feishu config is valid, or channels are working, the rule passes (no action needed).
|
|
3676
|
-
*
|
|
3677
|
-
* feishuConfigInvalid is read from the channels probe output rather than running a
|
|
3678
|
-
* separate `openclaw status` call, since only `openclaw channels status --probe`
|
|
3679
|
-
* reliably surfaces the schema validation error.
|
|
3680
|
-
*
|
|
3681
|
-
* profile: experimental — runs only in full sweep mode, not in standard doctor.
|
|
3682
|
-
* level: silent — telemetry/sweep-only, does not trigger page-level repair UI.
|
|
3683
|
-
*/
|
|
3684
|
-
let UpgradeLarkNeededRule = class UpgradeLarkNeededRule extends DiagnoseRule {
|
|
3685
|
-
validate(ctx) {
|
|
3686
|
-
let versionIncompatible = false;
|
|
3687
|
-
try {
|
|
3688
|
-
versionIncompatible = needsLarkUpgrade(ctx);
|
|
3689
|
-
} catch {}
|
|
3690
|
-
let probeResult;
|
|
3691
|
-
try {
|
|
3692
|
-
probeResult = runChannelsProbe(6e4);
|
|
3693
|
-
} catch {
|
|
3694
|
-
probeResult = {
|
|
3695
|
-
available: false,
|
|
3696
|
-
gatewayReachable: false,
|
|
3697
|
-
feishuConfigInvalid: false,
|
|
3698
|
-
accounts: [],
|
|
3699
|
-
anyAccountWorking: false
|
|
3700
|
-
};
|
|
3701
|
-
}
|
|
3702
|
-
const feishuConfigInvalid = probeResult.feishuConfigInvalid;
|
|
3703
|
-
if (!(versionIncompatible || feishuConfigInvalid)) return { pass: true };
|
|
3704
|
-
if (probeResult.anyAccountWorking) return { pass: true };
|
|
3705
|
-
return {
|
|
3706
|
-
pass: false,
|
|
3707
|
-
action: "upgrade_lark",
|
|
3708
|
-
message: `飞书插件需要升级且 channels 不可用(版本不兼容=${versionIncompatible}, feishu配置无效=${feishuConfigInvalid}),建议执行 upgrade-lark 命令升级飞书插件`
|
|
3709
|
-
};
|
|
3710
|
-
}
|
|
3711
|
-
};
|
|
3712
|
-
UpgradeLarkNeededRule = __decorate([Rule({
|
|
3713
|
-
key: "upgrade_lark_needed",
|
|
3714
|
-
description: "检测飞书插件版本不兼容且 channels 不可用,判断是否需要执行 upgrade-lark 升级",
|
|
3715
|
-
dependsOn: ["config_syntax_check"],
|
|
3716
|
-
repairMode: "check-only",
|
|
3717
|
-
level: "silent",
|
|
3718
|
-
profile: "experimental"
|
|
3719
|
-
})], UpgradeLarkNeededRule);
|
|
3720
|
-
//#endregion
|
|
3721
3887
|
//#region src/rules/cleanup-install-backup-dirs.ts
|
|
3722
3888
|
const DIR_PREFIX = ".openclaw-install-";
|
|
3723
3889
|
function resolveExtensionsDir(configPath) {
|
|
@@ -3800,7 +3966,7 @@ function extractScopedNameFromSpec(spec) {
|
|
|
3800
3966
|
const at = spec.indexOf("@", 1);
|
|
3801
3967
|
return at === -1 ? spec : spec.slice(0, at);
|
|
3802
3968
|
}
|
|
3803
|
-
function isLarkCliAvailable
|
|
3969
|
+
function isLarkCliAvailable() {
|
|
3804
3970
|
try {
|
|
3805
3971
|
return (0, node_child_process.spawnSync)(LARK_CLI_NAME$1, ["--version"], {
|
|
3806
3972
|
encoding: "utf-8",
|
|
@@ -3841,7 +4007,7 @@ function installLarkCliOnce(tag) {
|
|
|
3841
4007
|
let LarkCliMissingForInstalledLarkPluginRule = class LarkCliMissingForInstalledLarkPluginRule extends DiagnoseRule {
|
|
3842
4008
|
validate(ctx) {
|
|
3843
4009
|
if (!isTargetForkPlugin(readInstalledLarkPlugin(ctx))) return { pass: true };
|
|
3844
|
-
if (isLarkCliAvailable
|
|
4010
|
+
if (isLarkCliAvailable()) return { pass: true };
|
|
3845
4011
|
return {
|
|
3846
4012
|
pass: false,
|
|
3847
4013
|
message: `${FORK_PACKAGE_NAME}@${TARGET_VERSION} 已安装,但 lark-cli 不可用;将执行一次 lark-cli 安装`
|
|
@@ -3849,7 +4015,7 @@ let LarkCliMissingForInstalledLarkPluginRule = class LarkCliMissingForInstalledL
|
|
|
3849
4015
|
}
|
|
3850
4016
|
repair(ctx) {
|
|
3851
4017
|
if (!isTargetForkPlugin(readInstalledLarkPlugin(ctx))) return;
|
|
3852
|
-
if (isLarkCliAvailable
|
|
4018
|
+
if (isLarkCliAvailable()) return;
|
|
3853
4019
|
installLarkCliOnce(ctx.vars.recommendedOpenclawTag ?? TARGET_VERSION);
|
|
3854
4020
|
}
|
|
3855
4021
|
};
|
|
@@ -3862,6 +4028,117 @@ LarkCliMissingForInstalledLarkPluginRule = __decorate([Rule({
|
|
|
3862
4028
|
usesVars: ["recommendedOpenclawTag"]
|
|
3863
4029
|
})], LarkCliMissingForInstalledLarkPluginRule);
|
|
3864
4030
|
//#endregion
|
|
4031
|
+
//#region src/rules/feishu-bot-channel-config.ts
|
|
4032
|
+
/**
|
|
4033
|
+
* Ensures each bot account's channel config is correct:
|
|
4034
|
+
* 1. `allowFrom` contains its own `creatorOpenID` from larkApps
|
|
4035
|
+
* 2. `appSecret` is either the canonical provider-ref or matches larkApps plaintext
|
|
4036
|
+
*
|
|
4037
|
+
* Covers both multi-account (channels.feishu.accounts) and single-account
|
|
4038
|
+
* (channels.feishu.appId + allowFrom at top level) layouts.
|
|
4039
|
+
*/
|
|
4040
|
+
let FeishuBotChannelConfigRule = class FeishuBotChannelConfigRule extends DiagnoseRule {
|
|
4041
|
+
validate(ctx) {
|
|
4042
|
+
const larkApps = ctx.vars.larkApps;
|
|
4043
|
+
if (!larkApps || larkApps.length === 0) return { pass: true };
|
|
4044
|
+
const feishu = asRecord(getNestedMap(ctx.config, "channels", "feishu"));
|
|
4045
|
+
if (!feishu) return { pass: true };
|
|
4046
|
+
const issues = [];
|
|
4047
|
+
const accounts = asRecord(feishu.accounts);
|
|
4048
|
+
if (accounts) for (const [accountId, account] of Object.entries(accounts)) {
|
|
4049
|
+
const bot = asRecord(account);
|
|
4050
|
+
if (!bot) continue;
|
|
4051
|
+
const appId = bot.appId;
|
|
4052
|
+
if (typeof appId !== "string" || !appId.startsWith("cli_")) continue;
|
|
4053
|
+
const larkApp = larkApps.find((e) => e.larkAppID === appId);
|
|
4054
|
+
if (!larkApp) continue;
|
|
4055
|
+
this.checkBot(accountId, bot, larkApp, issues);
|
|
4056
|
+
}
|
|
4057
|
+
const singleAppId = feishu.appId;
|
|
4058
|
+
if (typeof singleAppId === "string" && singleAppId.startsWith("cli_") && !accounts) {
|
|
4059
|
+
const larkApp = larkApps.find((e) => e.larkAppID === singleAppId);
|
|
4060
|
+
if (larkApp) this.checkBot("feishu", feishu, larkApp, issues);
|
|
4061
|
+
}
|
|
4062
|
+
if (issues.length === 0) return { pass: true };
|
|
4063
|
+
return {
|
|
4064
|
+
pass: false,
|
|
4065
|
+
message: issues.join("; ")
|
|
4066
|
+
};
|
|
4067
|
+
}
|
|
4068
|
+
/** Check a single bot entry (either an account object or the feishu channel itself).
|
|
4069
|
+
* appSecret is validated based on its current type:
|
|
4070
|
+
* - object → must match canonical provider-ref
|
|
4071
|
+
* - string → must match larkApps plaintext
|
|
4072
|
+
*/
|
|
4073
|
+
checkBot(label, bot, larkApp, issues) {
|
|
4074
|
+
const creatorOpenID = larkApp.creatorOpenID;
|
|
4075
|
+
const allowFrom = Array.isArray(bot.allowFrom) ? bot.allowFrom : [];
|
|
4076
|
+
if (typeof creatorOpenID === "string" && creatorOpenID !== "") {
|
|
4077
|
+
if (!allowFrom.includes(creatorOpenID)) issues.push(`${label} allowFrom missing creatorOpenID ${creatorOpenID.length > 8 ? creatorOpenID.slice(0, 4) + "***" + creatorOpenID.slice(-4) : "***"}`);
|
|
4078
|
+
} else if (allowFrom.length === 0) issues.push(`${label} allowFrom is empty (creatorOpenID unavailable, cannot auto-fix)`);
|
|
4079
|
+
const secret = bot.appSecret;
|
|
4080
|
+
if (typeof secret === "object" && secret !== null && !Array.isArray(secret)) {
|
|
4081
|
+
if (!matchMap(secret, DEFAULT_FEISHU_APP_SECRET)) issues.push(`${label} appSecret is a provider-ref but not the canonical one`);
|
|
4082
|
+
} else if (typeof secret === "string") {
|
|
4083
|
+
if (secret !== larkApp.appSecret) issues.push(`${label} appSecret plaintext mismatch`);
|
|
4084
|
+
} else issues.push(`${label} appSecret has unexpected type ${typeof secret}`);
|
|
4085
|
+
}
|
|
4086
|
+
repair(ctx) {
|
|
4087
|
+
const larkApps = ctx.vars.larkApps;
|
|
4088
|
+
if (!larkApps || larkApps.length === 0) return;
|
|
4089
|
+
const feishu = asRecord(getNestedMap(ctx.config, "channels", "feishu"));
|
|
4090
|
+
if (!feishu) return;
|
|
4091
|
+
const accounts = asRecord(feishu.accounts);
|
|
4092
|
+
if (accounts) for (const [, account] of Object.entries(accounts)) {
|
|
4093
|
+
const bot = asRecord(account);
|
|
4094
|
+
if (!bot) continue;
|
|
4095
|
+
const appId = bot.appId;
|
|
4096
|
+
if (typeof appId !== "string" || !appId.startsWith("cli_")) continue;
|
|
4097
|
+
const larkApp = larkApps.find((e) => e.larkAppID === appId);
|
|
4098
|
+
if (!larkApp) continue;
|
|
4099
|
+
this.fixBot(bot, larkApp);
|
|
4100
|
+
}
|
|
4101
|
+
const singleAppId = feishu.appId;
|
|
4102
|
+
if (typeof singleAppId === "string" && singleAppId.startsWith("cli_") && !accounts) {
|
|
4103
|
+
const larkApp = larkApps.find((e) => e.larkAppID === singleAppId);
|
|
4104
|
+
if (larkApp) this.fixBot(feishu, larkApp);
|
|
4105
|
+
}
|
|
4106
|
+
}
|
|
4107
|
+
/** Fix a single bot entry in-place.
|
|
4108
|
+
* appSecret is repaired based on its current type:
|
|
4109
|
+
* - object → fix to canonical provider-ref
|
|
4110
|
+
* - string → fix to larkApps plaintext
|
|
4111
|
+
*/
|
|
4112
|
+
fixBot(bot, larkApp) {
|
|
4113
|
+
const creatorOpenID = larkApp.creatorOpenID;
|
|
4114
|
+
if (typeof creatorOpenID === "string" && creatorOpenID !== "") {
|
|
4115
|
+
const allowFrom = Array.isArray(bot.allowFrom) ? [...bot.allowFrom] : [];
|
|
4116
|
+
if (!allowFrom.includes(creatorOpenID)) {
|
|
4117
|
+
allowFrom.push(creatorOpenID);
|
|
4118
|
+
bot.allowFrom = allowFrom;
|
|
4119
|
+
}
|
|
4120
|
+
}
|
|
4121
|
+
const secret = bot.appSecret;
|
|
4122
|
+
if (typeof secret === "object" && secret !== null && !Array.isArray(secret)) {
|
|
4123
|
+
if (!matchMap(secret, DEFAULT_FEISHU_APP_SECRET)) bot.appSecret = { ...DEFAULT_FEISHU_APP_SECRET };
|
|
4124
|
+
} else if (typeof secret === "string") {
|
|
4125
|
+
if (secret !== larkApp.appSecret) bot.appSecret = larkApp.appSecret;
|
|
4126
|
+
}
|
|
4127
|
+
}
|
|
4128
|
+
};
|
|
4129
|
+
FeishuBotChannelConfigRule = __decorate([Rule({
|
|
4130
|
+
key: "feishu_bot_channel_config",
|
|
4131
|
+
description: "确保飞书配置中 bot 账号的 allowFrom 包含其创建者 openID 且 appSecret 值正确",
|
|
4132
|
+
dependsOn: [
|
|
4133
|
+
"config_syntax_check",
|
|
4134
|
+
"feishu_default_account",
|
|
4135
|
+
"feishu_bot_id"
|
|
4136
|
+
],
|
|
4137
|
+
repairMode: "standard",
|
|
4138
|
+
usesVars: ["larkApps"],
|
|
4139
|
+
level: "critical"
|
|
4140
|
+
})], FeishuBotChannelConfigRule);
|
|
4141
|
+
//#endregion
|
|
3865
4142
|
//#region src/check.ts
|
|
3866
4143
|
/** Telemetry-aware entry: returns both the legacy CheckResult (for stdout)
|
|
3867
4144
|
* AND a DoctorReport-shape payload (for `openclaw.report_cli_run`). The
|
|
@@ -4302,33 +4579,6 @@ function finalize$1(results, aborted) {
|
|
|
4302
4579
|
};
|
|
4303
4580
|
}
|
|
4304
4581
|
//#endregion
|
|
4305
|
-
//#region src/paths.ts
|
|
4306
|
-
/**
|
|
4307
|
-
* Central directory for all ephemeral diagnose/reset artifacts: task status
|
|
4308
|
-
* files (`reset-<taskId>.json`) and human-readable step logs
|
|
4309
|
-
* (`reset-<taskId>.log`). Having everything under one dir makes debugging a
|
|
4310
|
-
* stuck reset much easier — `ls /tmp/openclaw-diagnose/` shows every recent
|
|
4311
|
-
* run, and each run's log is right next to its state.
|
|
4312
|
-
*/
|
|
4313
|
-
const DIAGNOSE_DIR = "/tmp/openclaw-diagnose";
|
|
4314
|
-
function resetResultFile(taskId) {
|
|
4315
|
-
return `${DIAGNOSE_DIR}/reset-${taskId}.json`;
|
|
4316
|
-
}
|
|
4317
|
-
function resetLogFile(taskId) {
|
|
4318
|
-
return `${DIAGNOSE_DIR}/reset-${taskId}.log`;
|
|
4319
|
-
}
|
|
4320
|
-
/** Sandbox workspace root where openclaw config + agent state lives. */
|
|
4321
|
-
const WORKSPACE_DIR = "/home/gem/workspace/agent";
|
|
4322
|
-
/** File containing the provider key used by the openclaw miaoda provider. */
|
|
4323
|
-
const PROVIDER_FILE_PATH = "/home/gem/workspace/.force/openclaw/miaoda-provider-key";
|
|
4324
|
-
/** File containing the miaoda openclaw secrets JSON. */
|
|
4325
|
-
const SECRETS_FILE_PATH = "/home/gem/workspace/.force/openclaw/miaoda-openclaw-secrets.json";
|
|
4326
|
-
/** Absolute path to the openclaw config JSON. */
|
|
4327
|
-
const CONFIG_PATH = `${WORKSPACE_DIR}/openclaw.json`;
|
|
4328
|
-
function upgradeLarkLogFile(runId) {
|
|
4329
|
-
return `${DIAGNOSE_DIR}/upgrade-lark-${(/* @__PURE__ */ new Date()).toISOString().slice(0, 19).replace(/:/g, "-")}-${runId.slice(0, 8)}.log`;
|
|
4330
|
-
}
|
|
4331
|
-
//#endregion
|
|
4332
4582
|
//#region src/run-log.ts
|
|
4333
4583
|
let currentRunContext;
|
|
4334
4584
|
/**
|
|
@@ -4464,9 +4714,10 @@ function makeLogger(logFile) {
|
|
|
4464
4714
|
/**
|
|
4465
4715
|
* Start an async reset task: spawn a detached child process and return the taskId.
|
|
4466
4716
|
*
|
|
4467
|
-
* The child process runs: node cli.js reset --worker --task-id=xxx
|
|
4717
|
+
* The child process runs: node cli.js reset --worker --task-id=xxx
|
|
4718
|
+
* The worker fetches ctx from innerApi itself — no --ctx passthrough.
|
|
4468
4719
|
*/
|
|
4469
|
-
function startAsyncReset(
|
|
4720
|
+
function startAsyncReset() {
|
|
4470
4721
|
const taskId = (0, node_crypto.randomUUID)();
|
|
4471
4722
|
const resultFile = resetResultFile(taskId);
|
|
4472
4723
|
const log = makeLogger(resetLogFile(taskId));
|
|
@@ -4490,8 +4741,7 @@ function startAsyncReset(ctxBase64) {
|
|
|
4490
4741
|
process.argv[1],
|
|
4491
4742
|
"reset",
|
|
4492
4743
|
"--worker",
|
|
4493
|
-
`--task-id=${taskId}
|
|
4494
|
-
`--ctx=${ctxBase64}`
|
|
4744
|
+
`--task-id=${taskId}`
|
|
4495
4745
|
], {
|
|
4496
4746
|
detached: true,
|
|
4497
4747
|
stdio: "ignore",
|
|
@@ -4859,307 +5109,47 @@ function updatePluginInstalls(configPath, installedPkgs) {
|
|
|
4859
5109
|
if (!Array.isArray(plugins.allow)) plugins.allow = [];
|
|
4860
5110
|
const allow = plugins.allow;
|
|
4861
5111
|
if (!allow.includes(pkg.name)) allow.push(pkg.name);
|
|
4862
|
-
if (!plugins.entries || typeof plugins.entries !== "object" || Array.isArray(plugins.entries)) plugins.entries = {};
|
|
4863
|
-
const entries = plugins.entries;
|
|
4864
|
-
entries[pkg.name] = {
|
|
4865
|
-
...asRecord(entries[pkg.name]) ?? {},
|
|
4866
|
-
enabled: true
|
|
4867
|
-
};
|
|
4868
|
-
}
|
|
4869
|
-
const tmpPath = configPath + ".installs-tmp";
|
|
4870
|
-
node_fs.default.writeFileSync(tmpPath, JSON.stringify(config, null, 2), "utf-8");
|
|
4871
|
-
moveSafe(tmpPath, configPath);
|
|
4872
|
-
console.error(`[install-extension] plugins.installs updated: ${updated} entry(ies) in ${configPath}` + (skipped > 0 ? ` (${skipped} package(s) without installMetadata skipped)` : ""));
|
|
4873
|
-
}
|
|
4874
|
-
function installOne$1(pkg, tarball, homeBase) {
|
|
4875
|
-
const destDir = node_path.default.join(homeBase, pkg.installPath);
|
|
4876
|
-
const stagingDir = destDir + ".new";
|
|
4877
|
-
const oldDir = destDir + ".old";
|
|
4878
|
-
node_fs.default.mkdirSync(node_path.default.dirname(destDir), { recursive: true });
|
|
4879
|
-
if (node_fs.default.existsSync(stagingDir)) node_fs.default.rmSync(stagingDir, {
|
|
4880
|
-
recursive: true,
|
|
4881
|
-
force: true
|
|
4882
|
-
});
|
|
4883
|
-
node_fs.default.mkdirSync(stagingDir);
|
|
4884
|
-
try {
|
|
4885
|
-
extractTarballTolerant(tarball, stagingDir, { stripComponents: 1 });
|
|
4886
|
-
if (!node_fs.default.existsSync(node_path.default.join(stagingDir, "package.json"))) throw new Error(`extension tarball missing package.json: ${pkg.name}`);
|
|
4887
|
-
} catch (e) {
|
|
4888
|
-
try {
|
|
4889
|
-
node_fs.default.rmSync(stagingDir, {
|
|
4890
|
-
recursive: true,
|
|
4891
|
-
force: true
|
|
4892
|
-
});
|
|
4893
|
-
} catch {}
|
|
4894
|
-
throw e;
|
|
4895
|
-
}
|
|
4896
|
-
const hadOld = node_fs.default.existsSync(destDir);
|
|
4897
|
-
if (hadOld) moveSafe(destDir, oldDir);
|
|
4898
|
-
moveSafe(stagingDir, destDir);
|
|
4899
|
-
if (hadOld && node_fs.default.existsSync(oldDir)) node_fs.default.rmSync(oldDir, {
|
|
4900
|
-
recursive: true,
|
|
4901
|
-
force: true
|
|
4902
|
-
});
|
|
4903
|
-
}
|
|
4904
|
-
//#endregion
|
|
4905
|
-
//#region src/lark-cli-init.ts
|
|
4906
|
-
const LARK_PLUGIN_NAMES = ["openclaw-lark", "feishu-openclaw-plugin"];
|
|
4907
|
-
const PE_XML_TAG = "lark-cli-pe";
|
|
4908
|
-
const PE_PLACEHOLDER = `
|
|
4909
|
-
<${PE_XML_TAG}>
|
|
4910
|
-
**【强制要求 - 无例外】飞书所有操作必须先读 feishu-lark-cli skill。未读skill不得调用任何飞书工具,违者视为操作失误。**
|
|
4911
|
-
</${PE_XML_TAG}>
|
|
4912
|
-
`;
|
|
4913
|
-
function isLarkPluginInstalled(configPath) {
|
|
4914
|
-
const extDir = getExtensionsDir(configPath);
|
|
4915
|
-
return LARK_PLUGIN_NAMES.some((name) => {
|
|
4916
|
-
try {
|
|
4917
|
-
return node_fs.default.existsSync(node_path.default.join(extDir, name, "package.json"));
|
|
4918
|
-
} catch {
|
|
4919
|
-
return false;
|
|
4920
|
-
}
|
|
4921
|
-
});
|
|
4922
|
-
}
|
|
4923
|
-
function isLarkCliAvailable() {
|
|
4924
|
-
try {
|
|
4925
|
-
return (0, node_child_process.spawnSync)("lark-cli", ["--version"], {
|
|
4926
|
-
encoding: "utf-8",
|
|
4927
|
-
timeout: 5e3,
|
|
4928
|
-
stdio: [
|
|
4929
|
-
"ignore",
|
|
4930
|
-
"pipe",
|
|
4931
|
-
"ignore"
|
|
4932
|
-
]
|
|
4933
|
-
}).status === 0;
|
|
4934
|
-
} catch {
|
|
4935
|
-
return false;
|
|
4936
|
-
}
|
|
4937
|
-
}
|
|
4938
|
-
function readConfig(configPath) {
|
|
4939
|
-
try {
|
|
4940
|
-
const raw = node_fs.default.readFileSync(configPath, "utf-8");
|
|
4941
|
-
const parsed = loadJSON5().parse(raw);
|
|
4942
|
-
return parsed != null && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
|
|
4943
|
-
} catch {
|
|
4944
|
-
return null;
|
|
4945
|
-
}
|
|
4946
|
-
}
|
|
4947
|
-
/**
|
|
4948
|
-
* Resolve the feishu app secret for the given appId.
|
|
4949
|
-
*
|
|
4950
|
-
* Lookup order:
|
|
4951
|
-
* 1. channels.feishu.appSecret (single-agent: feishu.appId === appId)
|
|
4952
|
-
* 2. channels.feishu.accounts[key].appSecret (multi-agent: account.appId === appId)
|
|
4953
|
-
*
|
|
4954
|
-
* Value interpretation:
|
|
4955
|
-
* - string → use directly
|
|
4956
|
-
* - object → secret is managed by a provider; use `feishuAppSecret` param instead
|
|
4957
|
-
*
|
|
4958
|
-
* Returns null when the secret cannot be determined.
|
|
4959
|
-
*/
|
|
4960
|
-
function resolveAppSecret(appId, config, feishuAppSecret) {
|
|
4961
|
-
const feishu = getNestedMap(config, "channels", "feishu");
|
|
4962
|
-
if (!feishu) return null;
|
|
4963
|
-
let rawSecret;
|
|
4964
|
-
if (typeof feishu.appId === "string" && feishu.appId === appId) rawSecret = feishu.appSecret;
|
|
4965
|
-
else {
|
|
4966
|
-
const accounts = asRecord(feishu.accounts);
|
|
4967
|
-
if (accounts) for (const [, val] of Object.entries(accounts)) {
|
|
4968
|
-
const account = asRecord(val);
|
|
4969
|
-
if (account?.appId === appId) {
|
|
4970
|
-
rawSecret = account.appSecret ?? feishu.appSecret;
|
|
4971
|
-
break;
|
|
4972
|
-
}
|
|
4973
|
-
}
|
|
4974
|
-
}
|
|
4975
|
-
if (typeof rawSecret === "string" && rawSecret) return rawSecret;
|
|
4976
|
-
if (rawSecret != null && typeof rawSecret === "object") return feishuAppSecret ?? null;
|
|
4977
|
-
return null;
|
|
4978
|
-
}
|
|
4979
|
-
/**
|
|
4980
|
-
* Resolve the agents.md path for the given appId from the openclaw config.
|
|
4981
|
-
*
|
|
4982
|
-
* Case 1: appId matches channels.feishu.appId (single-agent path)
|
|
4983
|
-
* → WORKSPACE_DIR/AGENTS.md
|
|
4984
|
-
*
|
|
4985
|
-
* Case 2: appId found in channels.feishu.accounts (multi-agent path)
|
|
4986
|
-
* → find account key where account.appId === appId
|
|
4987
|
-
* → find binding where match.channel=feishu && match.accountId=that key
|
|
4988
|
-
* → if agentId === 'main' → WORKSPACE_DIR/agents.md
|
|
4989
|
-
* → else find agent in agents.list by id → agent.workspace/agents.md
|
|
4990
|
-
*
|
|
4991
|
-
* Returns null when the path cannot be determined.
|
|
4992
|
-
*/
|
|
4993
|
-
function resolveAgentsMdPath(appId, config) {
|
|
4994
|
-
const feishu = getNestedMap(config, "channels", "feishu");
|
|
4995
|
-
if (!feishu) {
|
|
4996
|
-
console.error("resolveAgentsMdPath: channels.feishu not found");
|
|
4997
|
-
return null;
|
|
4998
|
-
}
|
|
4999
|
-
if (typeof feishu.appId === "string" && feishu.appId === appId) {
|
|
5000
|
-
console.error(`resolveAgentsMdPath: case=single-agent feishu.appId=${feishu.appId}`);
|
|
5001
|
-
return node_path.default.join(WORKSPACE_DIR, "workspace", "AGENTS.md");
|
|
5002
|
-
}
|
|
5003
|
-
const accounts = asRecord(feishu.accounts);
|
|
5004
|
-
if (!accounts) {
|
|
5005
|
-
console.error("resolveAgentsMdPath: feishu.accounts not found");
|
|
5006
|
-
return null;
|
|
5007
|
-
}
|
|
5008
|
-
let accountId;
|
|
5009
|
-
for (const [key, val] of Object.entries(accounts)) if (asRecord(val)?.appId === appId) {
|
|
5010
|
-
accountId = key;
|
|
5011
|
-
break;
|
|
5012
|
-
}
|
|
5013
|
-
if (!accountId) {
|
|
5014
|
-
console.error(`resolveAgentsMdPath: no account found with appId=${appId} in feishu.accounts keys=[${Object.keys(accounts).join(",")}]`);
|
|
5015
|
-
return null;
|
|
5016
|
-
}
|
|
5017
|
-
console.error(`resolveAgentsMdPath: found accountId=${accountId}`);
|
|
5018
|
-
const bindings = Array.isArray(config.bindings) ? config.bindings : [];
|
|
5019
|
-
let agentId;
|
|
5020
|
-
for (const b of bindings) {
|
|
5021
|
-
const binding = asRecord(b);
|
|
5022
|
-
if (!binding) continue;
|
|
5023
|
-
const match = asRecord(binding.match);
|
|
5024
|
-
if (match?.channel === "feishu" && match?.accountId === accountId) {
|
|
5025
|
-
if (typeof binding.agentId === "string") {
|
|
5026
|
-
agentId = binding.agentId;
|
|
5027
|
-
break;
|
|
5028
|
-
}
|
|
5029
|
-
}
|
|
5030
|
-
}
|
|
5031
|
-
if (!agentId) {
|
|
5032
|
-
console.error(`resolveAgentsMdPath: no binding found for accountId=${accountId} in bindings(count=${bindings.length})`);
|
|
5033
|
-
return null;
|
|
5034
|
-
}
|
|
5035
|
-
console.error(`resolveAgentsMdPath: found agentId=${agentId}`);
|
|
5036
|
-
if (agentId === "main") {
|
|
5037
|
-
console.error("resolveAgentsMdPath: case=multi-agent-main");
|
|
5038
|
-
return node_path.default.join(WORKSPACE_DIR, "workspace", "AGENTS.md");
|
|
5039
|
-
}
|
|
5040
|
-
const agentsObj = asRecord(config.agents);
|
|
5041
|
-
const list = Array.isArray(agentsObj?.list) ? agentsObj.list : [];
|
|
5042
|
-
for (const a of list) {
|
|
5043
|
-
const agent = asRecord(a);
|
|
5044
|
-
if (agent?.id === agentId) {
|
|
5045
|
-
const ws = typeof agent.workspace === "string" ? agent.workspace : node_path.default.join(WORKSPACE_DIR, "workspace");
|
|
5046
|
-
console.error(`resolveAgentsMdPath: case=multi-agent-custom agentId=${agentId} workspace=${ws}`);
|
|
5047
|
-
return node_path.default.join(ws, "AGENTS.md");
|
|
5048
|
-
}
|
|
5049
|
-
}
|
|
5050
|
-
console.error(`resolveAgentsMdPath: agentId=${agentId} not found in agents.list(count=${list.length})`);
|
|
5051
|
-
return null;
|
|
5052
|
-
}
|
|
5053
|
-
function appendPeToAgentsMd(agentsMdPath) {
|
|
5054
|
-
const dir = node_path.default.dirname(agentsMdPath);
|
|
5055
|
-
if (!node_fs.default.existsSync(dir)) node_fs.default.mkdirSync(dir, { recursive: true });
|
|
5056
|
-
const existing = node_fs.default.existsSync(agentsMdPath) ? node_fs.default.readFileSync(agentsMdPath, "utf-8") : "";
|
|
5057
|
-
if (existing.includes(`<${PE_XML_TAG}>`)) {
|
|
5058
|
-
console.error(`lark-cli-init: <${PE_XML_TAG}> already present in ${agentsMdPath}, skipping`);
|
|
5059
|
-
return;
|
|
5060
|
-
}
|
|
5061
|
-
const sep = existing.length > 0 && !existing.endsWith("\n") ? "\n" : "";
|
|
5062
|
-
node_fs.default.appendFileSync(agentsMdPath, `${sep}${PE_PLACEHOLDER}`, "utf-8");
|
|
5063
|
-
console.error(`lark-cli-init: appended PE placeholder to ${agentsMdPath}`);
|
|
5064
|
-
}
|
|
5065
|
-
/**
|
|
5066
|
-
* Collect every Feishu bot appId declared in the openclaw config.
|
|
5067
|
-
* Covers both single-agent (channels.feishu.appId) and multi-agent
|
|
5068
|
-
* (channels.feishu.accounts[*].appId) layouts. Returns a deduplicated list.
|
|
5069
|
-
*/
|
|
5070
|
-
function collectFeishuAppIds(configPath) {
|
|
5071
|
-
const config = readConfig(configPath ?? CONFIG_PATH);
|
|
5072
|
-
if (!config) return [];
|
|
5073
|
-
const feishu = getNestedMap(config, "channels", "feishu");
|
|
5074
|
-
if (!feishu) return [];
|
|
5075
|
-
const appIds = /* @__PURE__ */ new Set();
|
|
5076
|
-
const topAppId = feishu.appId;
|
|
5077
|
-
if (typeof topAppId === "string" && topAppId.trim()) appIds.add(topAppId.trim());
|
|
5078
|
-
const accounts = asRecord(feishu.accounts);
|
|
5079
|
-
if (accounts) for (const val of Object.values(accounts)) {
|
|
5080
|
-
const appId = asRecord(val)?.appId;
|
|
5081
|
-
if (typeof appId === "string" && appId.trim()) appIds.add(appId.trim());
|
|
5082
|
-
}
|
|
5083
|
-
return [...appIds];
|
|
5084
|
-
}
|
|
5085
|
-
function runLarkCliInit(opts) {
|
|
5086
|
-
const configPath = opts.configPath ?? CONFIG_PATH;
|
|
5087
|
-
if (!isLarkPluginInstalled(configPath)) {
|
|
5088
|
-
console.error("lark-cli-init: skipping — openclaw-lark plugin not installed");
|
|
5089
|
-
return {
|
|
5090
|
-
ok: true,
|
|
5091
|
-
skipped: true,
|
|
5092
|
-
skipReason: "openclaw-lark plugin not installed"
|
|
5112
|
+
if (!plugins.entries || typeof plugins.entries !== "object" || Array.isArray(plugins.entries)) plugins.entries = {};
|
|
5113
|
+
const entries = plugins.entries;
|
|
5114
|
+
entries[pkg.name] = {
|
|
5115
|
+
...asRecord(entries[pkg.name]) ?? {},
|
|
5116
|
+
enabled: true
|
|
5093
5117
|
};
|
|
5094
5118
|
}
|
|
5095
|
-
|
|
5096
|
-
|
|
5097
|
-
|
|
5098
|
-
|
|
5099
|
-
|
|
5100
|
-
|
|
5101
|
-
|
|
5119
|
+
const tmpPath = configPath + ".installs-tmp";
|
|
5120
|
+
node_fs.default.writeFileSync(tmpPath, JSON.stringify(config, null, 2), "utf-8");
|
|
5121
|
+
moveSafe(tmpPath, configPath);
|
|
5122
|
+
console.error(`[install-extension] plugins.installs updated: ${updated} entry(ies) in ${configPath}` + (skipped > 0 ? ` (${skipped} package(s) without installMetadata skipped)` : ""));
|
|
5123
|
+
}
|
|
5124
|
+
function installOne$1(pkg, tarball, homeBase) {
|
|
5125
|
+
const destDir = node_path.default.join(homeBase, pkg.installPath);
|
|
5126
|
+
const stagingDir = destDir + ".new";
|
|
5127
|
+
const oldDir = destDir + ".old";
|
|
5128
|
+
node_fs.default.mkdirSync(node_path.default.dirname(destDir), { recursive: true });
|
|
5129
|
+
if (node_fs.default.existsSync(stagingDir)) node_fs.default.rmSync(stagingDir, {
|
|
5130
|
+
recursive: true,
|
|
5131
|
+
force: true
|
|
5132
|
+
});
|
|
5133
|
+
node_fs.default.mkdirSync(stagingDir);
|
|
5134
|
+
try {
|
|
5135
|
+
extractTarballTolerant(tarball, stagingDir, { stripComponents: 1 });
|
|
5136
|
+
if (!node_fs.default.existsSync(node_path.default.join(stagingDir, "package.json"))) throw new Error(`extension tarball missing package.json: ${pkg.name}`);
|
|
5137
|
+
} catch (e) {
|
|
5138
|
+
try {
|
|
5139
|
+
node_fs.default.rmSync(stagingDir, {
|
|
5140
|
+
recursive: true,
|
|
5141
|
+
force: true
|
|
5142
|
+
});
|
|
5143
|
+
} catch {}
|
|
5144
|
+
throw e;
|
|
5102
5145
|
}
|
|
5103
|
-
const
|
|
5104
|
-
if (
|
|
5105
|
-
|
|
5106
|
-
|
|
5107
|
-
|
|
5108
|
-
|
|
5109
|
-
console.error(`lark-cli-init: resolved agents.md path=${agentsMdPath ?? "(null)"}`);
|
|
5110
|
-
if (!agentsMdPath) return {
|
|
5111
|
-
ok: false,
|
|
5112
|
-
error: `could not resolve agents.md path for appId=${opts.appId}`
|
|
5113
|
-
};
|
|
5114
|
-
const appSecret = resolveAppSecret(opts.appId, config, opts.feishuAppSecret);
|
|
5115
|
-
if (!appSecret) return {
|
|
5116
|
-
ok: false,
|
|
5117
|
-
error: `could not resolve appSecret for appId=${opts.appId}`
|
|
5118
|
-
};
|
|
5119
|
-
console.error(`lark-cli-init: running lark-cli config init --name ${opts.appId} --app-id ${opts.appId} --brand feishu --app-secret-stdin --force-init`);
|
|
5120
|
-
const initRes = (0, node_child_process.spawnSync)("lark-cli", [
|
|
5121
|
-
"config",
|
|
5122
|
-
"init",
|
|
5123
|
-
"--name",
|
|
5124
|
-
opts.appId,
|
|
5125
|
-
"--app-id",
|
|
5126
|
-
opts.appId,
|
|
5127
|
-
"--brand",
|
|
5128
|
-
"feishu",
|
|
5129
|
-
"--app-secret-stdin",
|
|
5130
|
-
"--force-init"
|
|
5131
|
-
], {
|
|
5132
|
-
stdio: [
|
|
5133
|
-
"pipe",
|
|
5134
|
-
"pipe",
|
|
5135
|
-
"pipe"
|
|
5136
|
-
],
|
|
5137
|
-
encoding: "utf-8",
|
|
5138
|
-
input: appSecret
|
|
5146
|
+
const hadOld = node_fs.default.existsSync(destDir);
|
|
5147
|
+
if (hadOld) moveSafe(destDir, oldDir);
|
|
5148
|
+
moveSafe(stagingDir, destDir);
|
|
5149
|
+
if (hadOld && node_fs.default.existsSync(oldDir)) node_fs.default.rmSync(oldDir, {
|
|
5150
|
+
recursive: true,
|
|
5151
|
+
force: true
|
|
5139
5152
|
});
|
|
5140
|
-
const configInitStdout = initRes.stdout?.trim() || void 0;
|
|
5141
|
-
const configInitStderr = initRes.stderr?.trim() || void 0;
|
|
5142
|
-
if (configInitStdout) console.error(`lark-cli config init stdout: ${configInitStdout}`);
|
|
5143
|
-
if (configInitStderr) console.error(`lark-cli config init stderr: ${configInitStderr}`);
|
|
5144
|
-
if (initRes.error) return {
|
|
5145
|
-
ok: false,
|
|
5146
|
-
configInitStdout,
|
|
5147
|
-
configInitStderr,
|
|
5148
|
-
error: `lark-cli config init spawn error: ${initRes.error.message}`
|
|
5149
|
-
};
|
|
5150
|
-
if (initRes.status !== 0) return {
|
|
5151
|
-
ok: false,
|
|
5152
|
-
configInitExitCode: initRes.status ?? void 0,
|
|
5153
|
-
configInitStdout,
|
|
5154
|
-
configInitStderr,
|
|
5155
|
-
error: `lark-cli config init exited with code ${initRes.status}`
|
|
5156
|
-
};
|
|
5157
|
-
appendPeToAgentsMd(agentsMdPath);
|
|
5158
|
-
return {
|
|
5159
|
-
ok: true,
|
|
5160
|
-
configInitExitCode: 0,
|
|
5161
|
-
agentsMdPath
|
|
5162
|
-
};
|
|
5163
5153
|
}
|
|
5164
5154
|
//#endregion
|
|
5165
5155
|
//#region ../../openclaw-slardar/lib/client.js
|
|
@@ -7005,6 +6995,60 @@ function mergeCoreBackupAndOrigins(configPath, vars, resetData, log) {
|
|
|
7005
6995
|
log(`allowedOrigins: added ${added.length} (${JSON.stringify(added)}), total now ${mergedOrigins.length}`);
|
|
7006
6996
|
}
|
|
7007
6997
|
/**
|
|
6998
|
+
* Fix bot account allowFrom and appSecret using larkApps from innerApi.
|
|
6999
|
+
*
|
|
7000
|
+
* For each bot account (key starts with `bot-cli_`):
|
|
7001
|
+
* - allowFrom must contain the bot's own creatorOpenID from larkApps
|
|
7002
|
+
* - appSecret must be either the canonical provider-ref or match larkApps plaintext
|
|
7003
|
+
*
|
|
7004
|
+
* Runs after mergeCoreBackupAndOrigins so it operates on the final config state.
|
|
7005
|
+
*/
|
|
7006
|
+
function fixBotChannelConfig(configPath, larkApps, log) {
|
|
7007
|
+
if (!larkApps || larkApps.length === 0) {
|
|
7008
|
+
log("no larkApps data, skip bot channel config fix");
|
|
7009
|
+
return;
|
|
7010
|
+
}
|
|
7011
|
+
const config = loadJSON5().parse(node_fs.default.readFileSync(configPath, "utf-8"));
|
|
7012
|
+
const accounts = asRecord(getNestedMap(config, "channels", "feishu")?.accounts);
|
|
7013
|
+
if (!accounts) {
|
|
7014
|
+
log("no feishu accounts in config, skip bot channel config fix");
|
|
7015
|
+
return;
|
|
7016
|
+
}
|
|
7017
|
+
let fixCount = 0;
|
|
7018
|
+
for (const [, account] of Object.entries(accounts)) {
|
|
7019
|
+
const bot = asRecord(account);
|
|
7020
|
+
if (!bot) continue;
|
|
7021
|
+
const appId = bot.appId;
|
|
7022
|
+
if (typeof appId !== "string" || !appId.startsWith("cli_")) continue;
|
|
7023
|
+
const larkApp = larkApps.find((e) => e.larkAppID === appId);
|
|
7024
|
+
if (!larkApp) continue;
|
|
7025
|
+
const creatorOpenID = larkApp.creatorOpenID;
|
|
7026
|
+
if (typeof creatorOpenID === "string" && creatorOpenID !== "") {
|
|
7027
|
+
const allowFrom = Array.isArray(bot.allowFrom) ? [...bot.allowFrom] : [];
|
|
7028
|
+
if (!allowFrom.includes(creatorOpenID)) {
|
|
7029
|
+
allowFrom.push(creatorOpenID);
|
|
7030
|
+
bot.allowFrom = allowFrom;
|
|
7031
|
+
fixCount++;
|
|
7032
|
+
}
|
|
7033
|
+
}
|
|
7034
|
+
const secret = bot.appSecret;
|
|
7035
|
+
let needsFix = false;
|
|
7036
|
+
if (typeof secret === "object" && secret !== null && !Array.isArray(secret)) {
|
|
7037
|
+
if (!matchMap(secret, DEFAULT_FEISHU_APP_SECRET)) needsFix = true;
|
|
7038
|
+
} else if (typeof secret === "string") {
|
|
7039
|
+
if (secret !== larkApp.appSecret) needsFix = true;
|
|
7040
|
+
} else needsFix = true;
|
|
7041
|
+
if (needsFix) {
|
|
7042
|
+
bot.appSecret = { ...DEFAULT_FEISHU_APP_SECRET };
|
|
7043
|
+
fixCount++;
|
|
7044
|
+
}
|
|
7045
|
+
}
|
|
7046
|
+
if (fixCount > 0) {
|
|
7047
|
+
node_fs.default.writeFileSync(configPath, JSON.stringify(config, null, 2), "utf-8");
|
|
7048
|
+
log(`fixed ${fixCount} bot channel config issue(s) (allowFrom/appSecret)`);
|
|
7049
|
+
} else log("bot channel config ok, no fixes needed");
|
|
7050
|
+
}
|
|
7051
|
+
/**
|
|
7008
7052
|
* Step 7: Verify startup scripts landed in configDir/scripts/.
|
|
7009
7053
|
*
|
|
7010
7054
|
* Scripts are extracted directly to configDir/scripts/ during stageTemplate —
|
|
@@ -7149,6 +7193,7 @@ async function runReset(input, taskId, resultFile) {
|
|
|
7149
7193
|
await step5InstallOpenclaw(openclawTag, ossFileMap, log);
|
|
7150
7194
|
step(6);
|
|
7151
7195
|
mergeCoreBackupAndOrigins(configPath, vars, resetData, log);
|
|
7196
|
+
fixBotChannelConfig(configPath, vars.larkApps, log);
|
|
7152
7197
|
step(7);
|
|
7153
7198
|
verifyStartupScripts(configDir, log);
|
|
7154
7199
|
step(8);
|
|
@@ -7947,7 +7992,8 @@ function normalizeCtx(raw) {
|
|
|
7947
7992
|
reset: {
|
|
7948
7993
|
templateVars: r.reset.templateVars ?? {},
|
|
7949
7994
|
coreBackup: r.reset.coreBackup
|
|
7950
|
-
}
|
|
7995
|
+
},
|
|
7996
|
+
larkApps: Array.isArray(r.larkApps) ? r.larkApps : []
|
|
7951
7997
|
};
|
|
7952
7998
|
}
|
|
7953
7999
|
const vars = r.vars ?? {};
|
|
@@ -7972,7 +8018,8 @@ function normalizeCtx(raw) {
|
|
|
7972
8018
|
reset: {
|
|
7973
8019
|
templateVars: resetData.templateVars ?? {},
|
|
7974
8020
|
coreBackup: resetData.coreBackup
|
|
7975
|
-
}
|
|
8021
|
+
},
|
|
8022
|
+
larkApps: Array.isArray(r.larkApps) ? r.larkApps : []
|
|
7976
8023
|
};
|
|
7977
8024
|
}
|
|
7978
8025
|
function fillApp(src) {
|
|
@@ -8037,7 +8084,8 @@ function buildCheckInput(raw, configPathOverride) {
|
|
|
8037
8084
|
providerFilePath: PROVIDER_FILE_PATH,
|
|
8038
8085
|
secretsFilePath: SECRETS_FILE_PATH,
|
|
8039
8086
|
templateVars: ctx.app.templateVars,
|
|
8040
|
-
recommendedOpenclawTag: ctx.app.recommendedOpenclawTag
|
|
8087
|
+
recommendedOpenclawTag: ctx.app.recommendedOpenclawTag,
|
|
8088
|
+
larkApps: ctx.larkApps
|
|
8041
8089
|
},
|
|
8042
8090
|
templateVars: ctx.app.templateVars
|
|
8043
8091
|
};
|
|
@@ -8069,7 +8117,8 @@ function buildRepairInput(raw, configPathOverride) {
|
|
|
8069
8117
|
providerFilePath: PROVIDER_FILE_PATH,
|
|
8070
8118
|
secretsFilePath: SECRETS_FILE_PATH,
|
|
8071
8119
|
templateVars: ctx.app.templateVars,
|
|
8072
|
-
recommendedOpenclawTag: ctx.app.recommendedOpenclawTag
|
|
8120
|
+
recommendedOpenclawTag: ctx.app.recommendedOpenclawTag,
|
|
8121
|
+
larkApps: ctx.larkApps
|
|
8073
8122
|
},
|
|
8074
8123
|
repairData: {
|
|
8075
8124
|
secretsContent: ctx.secrets.secretsContent,
|
|
@@ -8105,7 +8154,8 @@ function buildResetInput(raw, configPathOverride) {
|
|
|
8105
8154
|
providerFilePath: PROVIDER_FILE_PATH,
|
|
8106
8155
|
secretsFilePath: SECRETS_FILE_PATH,
|
|
8107
8156
|
templateVars: ctx.app.templateVars,
|
|
8108
|
-
recommendedOpenclawTag: ctx.app.recommendedOpenclawTag
|
|
8157
|
+
recommendedOpenclawTag: ctx.app.recommendedOpenclawTag,
|
|
8158
|
+
larkApps: ctx.larkApps
|
|
8109
8159
|
},
|
|
8110
8160
|
resetData: {
|
|
8111
8161
|
templateVars: ctx.reset.templateVars,
|
|
@@ -10336,6 +10386,122 @@ function finalize(results, aborted) {
|
|
|
10336
10386
|
};
|
|
10337
10387
|
}
|
|
10338
10388
|
//#endregion
|
|
10389
|
+
//#region src/channels-probe.ts
|
|
10390
|
+
const FEISHU_INVALID_CONFIG_MSG = "channels.feishu: invalid config: must NOT have additional properties";
|
|
10391
|
+
const CHANNEL_LINE_RE = /^-\s+Feishu\s+([^:]+):\s+(.+)$/;
|
|
10392
|
+
/**
|
|
10393
|
+
* 判断单个飞书账号是否处于"可用"状态。
|
|
10394
|
+
* 移植自 feishu-channel-success-rate skill 的 Python `_account_is_working`。
|
|
10395
|
+
*
|
|
10396
|
+
* 会先剥离 "key:value" 形式的 bit(dm:、bot:、in:、out:、token:、allow:、
|
|
10397
|
+
* intents:、groups:、health: 等),再按以下公式判断:
|
|
10398
|
+
*
|
|
10399
|
+
* @param ignoreProbeFailed 为 true 时忽略 "probe failed" bit,不视为失败。
|
|
10400
|
+
* 升级前置检测中传 true,因为 probe 失败通常反映网络状况而非配置问题。
|
|
10401
|
+
* @param gatewayReachable 为 false 时(gateway 尚未启动),只要 enabled+configured
|
|
10402
|
+
* 即视为可用;为 true 时还需要 running/works 且无 error: bit。
|
|
10403
|
+
*/
|
|
10404
|
+
function accountIsWorking(bits, ignoreProbeFailed = true, gatewayReachable = true) {
|
|
10405
|
+
const bitTokens = /* @__PURE__ */ new Set();
|
|
10406
|
+
let hasError = false;
|
|
10407
|
+
let hasProbeFailed = false;
|
|
10408
|
+
for (const raw of bits) {
|
|
10409
|
+
const b = raw.trim();
|
|
10410
|
+
if (!b) continue;
|
|
10411
|
+
if (b.startsWith("error:")) {
|
|
10412
|
+
hasError = true;
|
|
10413
|
+
continue;
|
|
10414
|
+
}
|
|
10415
|
+
if (b === "probe failed") {
|
|
10416
|
+
hasProbeFailed = true;
|
|
10417
|
+
continue;
|
|
10418
|
+
}
|
|
10419
|
+
bitTokens.add(b.split(":")[0]);
|
|
10420
|
+
}
|
|
10421
|
+
if (!bitTokens.has("enabled") || !bitTokens.has("configured")) return false;
|
|
10422
|
+
if (!gatewayReachable) return true;
|
|
10423
|
+
if (bitTokens.has("works")) return true;
|
|
10424
|
+
if (bitTokens.has("running") && !hasError && (ignoreProbeFailed || !hasProbeFailed)) return true;
|
|
10425
|
+
return false;
|
|
10426
|
+
}
|
|
10427
|
+
/**
|
|
10428
|
+
* 解析 `openclaw channels status --probe` 的原始 stdout。
|
|
10429
|
+
* 移植自 feishu-channel-success-rate skill 的 Python `extract_channels_probe`。
|
|
10430
|
+
*/
|
|
10431
|
+
function parseChannelsProbeOutput(text, { ignoreProbeFailed = true } = {}) {
|
|
10432
|
+
const gatewayReachable = text.includes("Gateway reachable");
|
|
10433
|
+
const feishuConfigInvalid = text.includes(FEISHU_INVALID_CONFIG_MSG);
|
|
10434
|
+
const accounts = [];
|
|
10435
|
+
let anyAccountWorking = false;
|
|
10436
|
+
for (const line of text.split("\n")) {
|
|
10437
|
+
const m = CHANNEL_LINE_RE.exec(line.trim());
|
|
10438
|
+
if (!m) continue;
|
|
10439
|
+
const [, acct, rest] = m;
|
|
10440
|
+
const bits = rest.split(",").map((b) => b.trim());
|
|
10441
|
+
const isWorking = accountIsWorking(bits, ignoreProbeFailed, gatewayReachable);
|
|
10442
|
+
if (isWorking) anyAccountWorking = true;
|
|
10443
|
+
accounts.push({
|
|
10444
|
+
id: acct.trim(),
|
|
10445
|
+
bits,
|
|
10446
|
+
isWorking,
|
|
10447
|
+
raw: line.trim()
|
|
10448
|
+
});
|
|
10449
|
+
}
|
|
10450
|
+
return {
|
|
10451
|
+
gatewayReachable,
|
|
10452
|
+
feishuConfigInvalid,
|
|
10453
|
+
accounts,
|
|
10454
|
+
anyAccountWorking
|
|
10455
|
+
};
|
|
10456
|
+
}
|
|
10457
|
+
/**
|
|
10458
|
+
* 执行 `openclaw channels status --probe`,返回结构化结果。
|
|
10459
|
+
*
|
|
10460
|
+
* 部分 bot 账号 probe 失败时命令会以非零退出码退出,但 stdout 仍有可用内容。
|
|
10461
|
+
* 因此即使退出码非零,也尝试解析 stdout;只有真正没有任何输出时才返回 unavailable。
|
|
10462
|
+
*
|
|
10463
|
+
* @param timeoutMs 最长等待时长,默认 60 s。v2026.4.x 缺少单请求 HTTP 超时,
|
|
10464
|
+
* 可能无限阻塞,此超时是唯一保护。
|
|
10465
|
+
* @param ignoreProbeFailed 为 true 时,"probe failed" 账号仍计入"可用"。
|
|
10466
|
+
* 升级前置检测中应传 true,避免网络抖动导致误判为不可用。
|
|
10467
|
+
*/
|
|
10468
|
+
function runChannelsProbe(timeoutMs = 6e4, { ignoreProbeFailed = true } = {}) {
|
|
10469
|
+
let stdout = "";
|
|
10470
|
+
let stderrText = "";
|
|
10471
|
+
let execError;
|
|
10472
|
+
try {
|
|
10473
|
+
stdout = (0, node_child_process.execSync)("openclaw channels status --probe", {
|
|
10474
|
+
encoding: "utf-8",
|
|
10475
|
+
timeout: timeoutMs,
|
|
10476
|
+
stdio: [
|
|
10477
|
+
"ignore",
|
|
10478
|
+
"pipe",
|
|
10479
|
+
"pipe"
|
|
10480
|
+
]
|
|
10481
|
+
});
|
|
10482
|
+
} catch (e) {
|
|
10483
|
+
const err = e;
|
|
10484
|
+
const stdoutRaw = err.stdout;
|
|
10485
|
+
stdout = typeof stdoutRaw === "string" ? stdoutRaw : stdoutRaw?.toString("utf-8") ?? "";
|
|
10486
|
+
execError = err.message;
|
|
10487
|
+
const stderrRaw = err.stderr;
|
|
10488
|
+
stderrText = (typeof stderrRaw === "string" ? stderrRaw : stderrRaw?.toString("utf-8") ?? "").trim();
|
|
10489
|
+
if (stderrText) console.error(`channels-probe: stderr from CLI: ${stderrText}`);
|
|
10490
|
+
}
|
|
10491
|
+
if (stdout.trim()) return {
|
|
10492
|
+
available: true,
|
|
10493
|
+
...parseChannelsProbeOutput(stdout, { ignoreProbeFailed })
|
|
10494
|
+
};
|
|
10495
|
+
return {
|
|
10496
|
+
available: false,
|
|
10497
|
+
gatewayReachable: false,
|
|
10498
|
+
feishuConfigInvalid: stderrText.includes(FEISHU_INVALID_CONFIG_MSG),
|
|
10499
|
+
accounts: [],
|
|
10500
|
+
anyAccountWorking: false,
|
|
10501
|
+
error: execError ?? "no output from openclaw channels status --probe"
|
|
10502
|
+
};
|
|
10503
|
+
}
|
|
10504
|
+
//#endregion
|
|
10339
10505
|
//#region src/innerapi/reportCliRun.ts
|
|
10340
10506
|
/**
|
|
10341
10507
|
* CLI-side client for studio_server's `openclaw.report_cli_run` inner
|
|
@@ -10415,7 +10581,7 @@ async function reportCliRun(opts) {
|
|
|
10415
10581
|
//#region src/help.ts
|
|
10416
10582
|
const BIN = "mclaw-diagnose";
|
|
10417
10583
|
function versionBanner() {
|
|
10418
|
-
return `v0.1.15-alpha.
|
|
10584
|
+
return `v0.1.15-alpha.2`;
|
|
10419
10585
|
}
|
|
10420
10586
|
const COMMANDS = [
|
|
10421
10587
|
{
|
|
@@ -10519,16 +10685,12 @@ EXIT CODES
|
|
|
10519
10685
|
hidden: true,
|
|
10520
10686
|
summary: "Run rule-engine check only",
|
|
10521
10687
|
help: `USAGE
|
|
10522
|
-
${BIN} check
|
|
10688
|
+
${BIN} check
|
|
10523
10689
|
|
|
10524
10690
|
DESCRIPTION
|
|
10525
10691
|
Runs the rule engine against the sandbox's current openclaw config and
|
|
10526
|
-
returns { failedRules }.
|
|
10527
|
-
|
|
10528
|
-
|
|
10529
|
-
OPTIONS
|
|
10530
|
-
--ctx=<base64> Opaque ctx JSON (base64). When absent, fetched from
|
|
10531
|
-
innerapi (same path as doctor).
|
|
10692
|
+
returns { failedRules }. Ctx is fetched from innerapi automatically.
|
|
10693
|
+
End-users should prefer \`doctor\`.
|
|
10532
10694
|
`
|
|
10533
10695
|
},
|
|
10534
10696
|
{
|
|
@@ -10536,16 +10698,11 @@ OPTIONS
|
|
|
10536
10698
|
hidden: true,
|
|
10537
10699
|
summary: "Apply standard-mode repairs",
|
|
10538
10700
|
help: `USAGE
|
|
10539
|
-
${BIN} repair
|
|
10701
|
+
${BIN} repair
|
|
10540
10702
|
|
|
10541
10703
|
DESCRIPTION
|
|
10542
|
-
Runs repair for the failing rules
|
|
10543
|
-
|
|
10544
|
-
\`doctor --fix\` instead.
|
|
10545
|
-
|
|
10546
|
-
OPTIONS
|
|
10547
|
-
--ctx=<base64> Opaque ctx JSON (base64). When absent, fetched from
|
|
10548
|
-
innerapi.
|
|
10704
|
+
Runs repair for the failing rules. Ctx is fetched from innerapi
|
|
10705
|
+
automatically. End-users should use \`doctor --fix\` instead.
|
|
10549
10706
|
`
|
|
10550
10707
|
},
|
|
10551
10708
|
{
|
|
@@ -10553,14 +10710,15 @@ OPTIONS
|
|
|
10553
10710
|
hidden: true,
|
|
10554
10711
|
summary: "Re-initialize sandbox via the 9-step reset pipeline",
|
|
10555
10712
|
help: `USAGE
|
|
10556
|
-
${BIN} reset --async
|
|
10557
|
-
${BIN} reset --worker --task-id=<id>
|
|
10713
|
+
${BIN} reset --async
|
|
10714
|
+
${BIN} reset --worker --task-id=<id>
|
|
10558
10715
|
|
|
10559
10716
|
DESCRIPTION
|
|
10560
10717
|
Two-phase pipeline driven asynchronously: the --async invocation spawns
|
|
10561
10718
|
a detached worker and returns { taskId } immediately; the --worker
|
|
10562
10719
|
invocation (spawned by --async) runs the actual 9 steps and writes
|
|
10563
10720
|
progress to /tmp/openclaw-diagnose/reset-<taskId>.json.
|
|
10721
|
+
Ctx is fetched from innerapi automatically.
|
|
10564
10722
|
|
|
10565
10723
|
Poll progress with \`${BIN} get_reset_task --task-id=<id>\`.
|
|
10566
10724
|
|
|
@@ -10568,7 +10726,6 @@ OPTIONS
|
|
|
10568
10726
|
--async Start a detached worker and return taskId on stdout.
|
|
10569
10727
|
--worker Internal — run the 9-step pipeline (launched by --async).
|
|
10570
10728
|
--task-id=<id> Required with --worker; identifies the progress file.
|
|
10571
|
-
--ctx=<base64> Opaque ctx JSON; fetched from innerapi when absent.
|
|
10572
10729
|
`
|
|
10573
10730
|
},
|
|
10574
10731
|
{
|
|
@@ -10591,7 +10748,7 @@ OPTIONS
|
|
|
10591
10748
|
hidden: true,
|
|
10592
10749
|
summary: "Download + install the openclaw tarball",
|
|
10593
10750
|
help: `USAGE
|
|
10594
|
-
${BIN} install-openclaw <tag> [--
|
|
10751
|
+
${BIN} install-openclaw <tag> [--oss_file_map=<base64>]
|
|
10595
10752
|
|
|
10596
10753
|
DESCRIPTION
|
|
10597
10754
|
Downloads the openclaw@<tag> tgz via the signed OSS URL found in the
|
|
@@ -10603,9 +10760,9 @@ ARGUMENTS
|
|
|
10603
10760
|
<tag> Openclaw version tag, e.g. 2026.4.11.
|
|
10604
10761
|
|
|
10605
10762
|
OPTIONS
|
|
10606
|
-
--ctx=<base64> Opaque ctx; ossFileMap is extracted from it.
|
|
10607
10763
|
--oss_file_map=... Pre-built OSS URL map (base64 JSON); skips innerapi
|
|
10608
|
-
entirely.
|
|
10764
|
+
entirely. When absent, ossFileMap is fetched from
|
|
10765
|
+
innerapi automatically.
|
|
10609
10766
|
`
|
|
10610
10767
|
},
|
|
10611
10768
|
{
|
|
@@ -10631,8 +10788,7 @@ OPTIONS
|
|
|
10631
10788
|
--home_base=<dir> Override the /home/gem base (tests).
|
|
10632
10789
|
--config_path=<p> Override the openclaw.json path (tests).
|
|
10633
10790
|
--skip-config-update Leave plugins.installs in openclaw.json untouched.
|
|
10634
|
-
--
|
|
10635
|
-
--oss_file_map=... Pre-built OSS URL map (base64 JSON).
|
|
10791
|
+
--oss_file_map=... Pre-built OSS URL map (base64 JSON); skips innerapi.
|
|
10636
10792
|
`
|
|
10637
10793
|
},
|
|
10638
10794
|
{
|
|
@@ -10659,7 +10815,6 @@ OPTIONS
|
|
|
10659
10815
|
--cli=<name> CLI package to install by short name or scoped
|
|
10660
10816
|
packageName (repeatable, at least one required).
|
|
10661
10817
|
--home_base=<dir> Override the /home/gem base (tests).
|
|
10662
|
-
--ctx=<base64> Opaque ctx; ossFileMap is extracted from it.
|
|
10663
10818
|
--oss_file_map=... Pre-built OSS URL map (base64 JSON); skips innerapi.
|
|
10664
10819
|
|
|
10665
10820
|
EXAMPLES
|
|
@@ -10720,12 +10875,17 @@ EXIT CODES
|
|
|
10720
10875
|
hidden: false,
|
|
10721
10876
|
summary: "Upgrade the Feishu/Lark plugin via @larksuite/openclaw-lark-tools",
|
|
10722
10877
|
help: `USAGE
|
|
10723
|
-
${BIN} upgrade-lark [--scene=<scene>] [--caller=<n>] [--trace-id=<id>]
|
|
10878
|
+
${BIN} upgrade-lark [--check] [--scene=<scene>] [--caller=<n>] [--trace-id=<id>]
|
|
10724
10879
|
|
|
10725
10880
|
DESCRIPTION
|
|
10726
10881
|
Upgrades the Feishu/Lark plugin by running:
|
|
10727
10882
|
npx -y @larksuite/openclaw-lark-tools update --use-existing
|
|
10728
10883
|
|
|
10884
|
+
Before the upgrade, a pre-check gate runs to verify the upgrade is needed:
|
|
10885
|
+
- version incompatible OR feishu channel config invalid, AND
|
|
10886
|
+
- no feishu account is currently working
|
|
10887
|
+
If the gate is not triggered, the command skips with exit code 0.
|
|
10888
|
+
|
|
10729
10889
|
Before the upgrade, the following files are backed up:
|
|
10730
10890
|
- openclaw.json
|
|
10731
10891
|
- extensions/openclaw-lark/ (if present)
|
|
@@ -10743,16 +10903,23 @@ DESCRIPTION
|
|
|
10743
10903
|
{ "ok": false, "error": "...", "stderr": "...", "exitCode": 1,
|
|
10744
10904
|
"rollbackOk": true, "validationError": "...", "logFile": "..." }
|
|
10745
10905
|
|
|
10906
|
+
With --check:
|
|
10907
|
+
{ "ok": true, "skipped": true, "upgradeNeeded": false, "logFile": "..." }
|
|
10908
|
+
{ "ok": true, "skipped": true, "upgradeNeeded": true, "logFile": "..." } ← exit 1
|
|
10909
|
+
|
|
10746
10910
|
OPTIONS
|
|
10911
|
+
--check Diagnose only: run the pre-check gate and report whether
|
|
10912
|
+
upgrade is needed without installing. Exit 1 if needed.
|
|
10747
10913
|
--scene=<scene> Telemetry label forwarded to Slardar only.
|
|
10748
10914
|
Known values: PageUpgradeLark, etc. Custom strings accepted.
|
|
10749
10915
|
--caller=<name> Optional metadata passed to innerapi.
|
|
10750
10916
|
--trace-id=<id> Optional log-correlation id.
|
|
10751
10917
|
|
|
10752
10918
|
EXIT CODES
|
|
10753
|
-
0 Success: upgrade ran and all validations passed.
|
|
10919
|
+
0 Success: upgrade ran and all validations passed; or gate skipped upgrade.
|
|
10754
10920
|
1 Failure: npx error, validation failed, or git commit failed.
|
|
10755
10921
|
File rollback was attempted (see rollbackOk in the JSON output).
|
|
10922
|
+
With --check: exit 1 means upgrade IS needed.
|
|
10756
10923
|
`
|
|
10757
10924
|
},
|
|
10758
10925
|
{
|
|
@@ -10841,8 +11008,7 @@ OPTIONS
|
|
|
10841
11008
|
--role=<role> Package role (e.g. template, config).
|
|
10842
11009
|
--name=<name> Package name within the role.
|
|
10843
11010
|
--dir=<dir> Target dir (defaults to dirname(pkg.installPath)).
|
|
10844
|
-
--
|
|
10845
|
-
--oss_file_map=... Pre-built OSS URL map (base64 JSON).
|
|
11011
|
+
--oss_file_map=... Pre-built OSS URL map (base64 JSON); skips innerapi.
|
|
10846
11012
|
`
|
|
10847
11013
|
}
|
|
10848
11014
|
];
|
|
@@ -10918,31 +11084,31 @@ function planVarsFields(opts = {}) {
|
|
|
10918
11084
|
*
|
|
10919
11085
|
* Per-command group needs:
|
|
10920
11086
|
*
|
|
10921
|
-
* doctor / check app
|
|
10922
|
-
* repair app + secrets
|
|
10923
|
-
* reset app + secrets + install + reset
|
|
11087
|
+
* doctor / check app + larkApps
|
|
11088
|
+
* repair app + secrets + larkApps
|
|
11089
|
+
* reset app + secrets + install + reset + larkApps
|
|
10924
11090
|
* install-* install only
|
|
10925
11091
|
*
|
|
10926
11092
|
* Empty result (`{}`) means "no group needed" — the CLI can skip the
|
|
10927
11093
|
* `fetchCtxViaInnerApi` call entirely and run with a synthetic empty ctx.
|
|
10928
|
-
* Happens e.g. when the user pinned `--rule=<key>` to a vars-free rule on
|
|
10929
|
-
* `doctor`.
|
|
10930
11094
|
*/
|
|
10931
11095
|
function planCtxPopulate(opts) {
|
|
10932
11096
|
if (opts.command === "install") return { install: true };
|
|
10933
11097
|
const populate = {};
|
|
10934
|
-
|
|
11098
|
+
if (planVarsFields({
|
|
10935
11099
|
disabled: opts.disabled,
|
|
10936
11100
|
onlyRules: opts.onlyRules,
|
|
10937
11101
|
profile: opts.profile
|
|
10938
|
-
});
|
|
10939
|
-
if (
|
|
10940
|
-
|
|
10941
|
-
|
|
11102
|
+
}).length > 0) populate.app = true;
|
|
11103
|
+
if (opts.command === "repair") {
|
|
11104
|
+
populate.secrets = true;
|
|
11105
|
+
populate.larkApps = true;
|
|
11106
|
+
} else if (opts.command === "reset") {
|
|
10942
11107
|
populate.secrets = true;
|
|
10943
11108
|
populate.install = true;
|
|
10944
11109
|
populate.reset = true;
|
|
10945
|
-
|
|
11110
|
+
populate.larkApps = true;
|
|
11111
|
+
} else if (opts.command === "doctor" || opts.command === "check") populate.larkApps = true;
|
|
10946
11112
|
return populate;
|
|
10947
11113
|
}
|
|
10948
11114
|
//#endregion
|
|
@@ -10996,33 +11162,61 @@ function reportDoctorRunToSlardar(opts) {
|
|
|
10996
11162
|
}
|
|
10997
11163
|
});
|
|
10998
11164
|
}
|
|
10999
|
-
|
|
11165
|
+
/**
|
|
11166
|
+
* 读取日志文件末尾最多 maxBytes 个字节。
|
|
11167
|
+
* 日志头部为固定 banner,有价值的内容(错误、探测结果、回滚状态)集中在末尾。
|
|
11168
|
+
* 从尾部截取保证 Slardar 字段限制内优先保留关键内容。
|
|
11169
|
+
*/
|
|
11170
|
+
function readLogFileTail(filePath, maxBytes = 4e3) {
|
|
11000
11171
|
try {
|
|
11001
|
-
|
|
11172
|
+
const buf = node_fs.default.readFileSync(filePath);
|
|
11173
|
+
if (buf.length <= maxBytes) return buf.toString("utf-8");
|
|
11174
|
+
let start = buf.length - maxBytes;
|
|
11175
|
+
while (start < buf.length && buf[start] !== 10) start++;
|
|
11176
|
+
return buf.subarray(start + 1).toString("utf-8");
|
|
11002
11177
|
} catch {
|
|
11003
11178
|
return "";
|
|
11004
11179
|
}
|
|
11005
11180
|
}
|
|
11181
|
+
/**
|
|
11182
|
+
* 向 Slardar 上报 upgrade-lark 运行结果(upgrade_lark_run 事件)。
|
|
11183
|
+
*
|
|
11184
|
+
* extraCategories 记录字符串维度:scene、exit_code、rollback_ok、
|
|
11185
|
+
* validation_error、error_msg、log_content(日志文件末尾 4000 字节,含关键结果)。
|
|
11186
|
+
*
|
|
11187
|
+
* extraMetrics 记录各阶段耗时(毫秒);未执行的阶段上报 -1 作为哨兵值,
|
|
11188
|
+
* 便于在 Slardar 查询时区分"未运行"和"运行了 0ms"。
|
|
11189
|
+
*/
|
|
11006
11190
|
function reportUpgradeLarkToSlardar(opts) {
|
|
11007
|
-
console.error(`[slardar] upgrade_lark_run scene=${opts.scene ?? ""} success=${opts.success} exitCode=${opts.exitCode ?? ""} rollbackOk=${opts.rollbackOk ?? ""}`);
|
|
11008
|
-
const
|
|
11191
|
+
console.error(`[slardar] upgrade_lark_run scene=${opts.scene ?? ""} checkOnly=${opts.checkOnly} success=${opts.success} exitCode=${opts.exitCode ?? ""} rollbackOk=${opts.rollbackOk ?? ""}`);
|
|
11192
|
+
const t = opts.timing ?? {};
|
|
11193
|
+
const logContent = readLogFileTail(opts.logFile);
|
|
11009
11194
|
reportTask({
|
|
11010
11195
|
eventName: "upgrade_lark_run",
|
|
11011
11196
|
durationMs: opts.durationMs,
|
|
11012
11197
|
status: opts.success ? "success" : "failed",
|
|
11013
11198
|
extraCategories: {
|
|
11014
11199
|
scene: opts.scene ?? "",
|
|
11200
|
+
check_only: String(opts.checkOnly),
|
|
11015
11201
|
exit_code: String(opts.exitCode ?? ""),
|
|
11016
11202
|
rollback_ok: opts.rollbackOk != null ? String(opts.rollbackOk) : "",
|
|
11017
11203
|
validation_error: opts.validationError ?? "",
|
|
11018
11204
|
error_msg: opts.error ?? "",
|
|
11019
11205
|
log_content: logContent
|
|
11206
|
+
},
|
|
11207
|
+
extraMetrics: {
|
|
11208
|
+
pre_probe_ms: t.preProbeMs ?? -1,
|
|
11209
|
+
version_check_ms: t.versionCheckMs ?? -1,
|
|
11210
|
+
backup_ms: t.backupMs ?? -1,
|
|
11211
|
+
npx_install_ms: t.npxInstallMs ?? -1,
|
|
11212
|
+
post_probe_ms: t.postProbeMs ?? -1,
|
|
11213
|
+
doctor_fix_ms: t.doctorFixMs ?? -1
|
|
11020
11214
|
}
|
|
11021
11215
|
});
|
|
11022
11216
|
}
|
|
11023
11217
|
//#endregion
|
|
11024
11218
|
//#region src/upgrade-lark.ts
|
|
11025
|
-
/**
|
|
11219
|
+
/** 升级前需备份的 extensions/ 下的插件目录 */
|
|
11026
11220
|
const FEISHU_PLUGIN_DIRS = ["openclaw-lark", "feishu-openclaw-plugin"];
|
|
11027
11221
|
function backupFiles(opts) {
|
|
11028
11222
|
const { workspaceDir, configPath, backupDir, log } = opts;
|
|
@@ -11125,7 +11319,7 @@ function countFeishuBots(configPath) {
|
|
|
11125
11319
|
return 0;
|
|
11126
11320
|
}
|
|
11127
11321
|
}
|
|
11128
|
-
/**
|
|
11322
|
+
/** 执行 channels probe 并将结果写入日志,从不抛出异常(异常时返回全零结果)。 */
|
|
11129
11323
|
function probeChannels(label, log, timeoutMs) {
|
|
11130
11324
|
try {
|
|
11131
11325
|
const r = runChannelsProbe(timeoutMs);
|
|
@@ -11163,12 +11357,16 @@ function runUpgradeLark(opts) {
|
|
|
11163
11357
|
log(` cwd : ${cwd}`);
|
|
11164
11358
|
log(` configPath : ${configPath}`);
|
|
11165
11359
|
log(`${"=".repeat(60)}`);
|
|
11360
|
+
const timing = {};
|
|
11166
11361
|
log("");
|
|
11167
11362
|
log("── [Pre-check A] channels probe(升级前)────────────────");
|
|
11363
|
+
const t_preProbeStart = Date.now();
|
|
11168
11364
|
const beforeChannels = probeChannels("before", log, 6e4);
|
|
11365
|
+
timing.preProbeMs = Date.now() - t_preProbeStart;
|
|
11169
11366
|
log("");
|
|
11170
11367
|
log("── [Pre-check B] 版本兼容预检 ───────────────────────────");
|
|
11171
11368
|
let versionIncompatible = false;
|
|
11369
|
+
const t_versionCheckStart = Date.now();
|
|
11172
11370
|
try {
|
|
11173
11371
|
const rawConfig = node_fs.default.readFileSync(configPath, "utf-8");
|
|
11174
11372
|
versionIncompatible = needsLarkUpgrade({
|
|
@@ -11184,6 +11382,7 @@ function runUpgradeLark(opts) {
|
|
|
11184
11382
|
} catch (e) {
|
|
11185
11383
|
log(` version-compat pre-check error: ${e.message} — version signal unavailable`);
|
|
11186
11384
|
}
|
|
11385
|
+
timing.versionCheckMs = Date.now() - t_versionCheckStart;
|
|
11187
11386
|
const feishuConfigInvalid = beforeChannels.feishuConfigInvalid;
|
|
11188
11387
|
log(` feishu config invalid : ${feishuConfigInvalid}`);
|
|
11189
11388
|
log("");
|
|
@@ -11201,6 +11400,8 @@ function runUpgradeLark(opts) {
|
|
|
11201
11400
|
ok: true,
|
|
11202
11401
|
skipped: true,
|
|
11203
11402
|
skipReason: reason,
|
|
11403
|
+
upgradeNeeded: false,
|
|
11404
|
+
timing,
|
|
11204
11405
|
logFile
|
|
11205
11406
|
};
|
|
11206
11407
|
}
|
|
@@ -11214,19 +11415,38 @@ function runUpgradeLark(opts) {
|
|
|
11214
11415
|
ok: true,
|
|
11215
11416
|
skipped: true,
|
|
11216
11417
|
skipReason: reason,
|
|
11418
|
+
upgradeNeeded: false,
|
|
11419
|
+
timing,
|
|
11217
11420
|
logFile
|
|
11218
11421
|
};
|
|
11219
11422
|
}
|
|
11220
11423
|
log(` PROCEED: requiresLarkUpgrade=true (version=${versionIncompatible}, feishuConfig=${feishuConfigInvalid}) AND channels not working → running upgrade`);
|
|
11424
|
+
if (opts.checkOnly) {
|
|
11425
|
+
log(` --check 模式:需要升级 — 不执行安装,直接返回`);
|
|
11426
|
+
log(`${"=".repeat(60)}`);
|
|
11427
|
+
log("upgrade-lark check complete");
|
|
11428
|
+
log(`${"=".repeat(60)}`);
|
|
11429
|
+
return {
|
|
11430
|
+
ok: true,
|
|
11431
|
+
skipped: true,
|
|
11432
|
+
skipReason: "check",
|
|
11433
|
+
upgradeNeeded: true,
|
|
11434
|
+
timing,
|
|
11435
|
+
logFile
|
|
11436
|
+
};
|
|
11437
|
+
}
|
|
11221
11438
|
log("");
|
|
11222
11439
|
log("── [1/6] 文件备份 ────────────────────────────────────────");
|
|
11223
11440
|
log(`before-state: botCount=${countFeishuBots(configPath)}`);
|
|
11441
|
+
const t_backupStart = Date.now();
|
|
11224
11442
|
const backup = backupFiles(fsOpts);
|
|
11443
|
+
timing.backupMs = Date.now() - t_backupStart;
|
|
11225
11444
|
if (!backup.ok) {
|
|
11226
11445
|
log(`ERROR: ${backup.error}`);
|
|
11227
11446
|
return {
|
|
11228
11447
|
ok: false,
|
|
11229
11448
|
error: backup.error,
|
|
11449
|
+
timing,
|
|
11230
11450
|
logFile
|
|
11231
11451
|
};
|
|
11232
11452
|
}
|
|
@@ -11244,6 +11464,7 @@ function runUpgradeLark(opts) {
|
|
|
11244
11464
|
else log(` skipped: ${localOpenclawBin} (not found)`);
|
|
11245
11465
|
log("");
|
|
11246
11466
|
log("── [3/6] npx install (@larksuite/openclaw-lark-tools update) ──");
|
|
11467
|
+
const t_npxStart = Date.now();
|
|
11247
11468
|
const npxResult = (0, node_child_process.spawnSync)("npx", [
|
|
11248
11469
|
"-y",
|
|
11249
11470
|
"@larksuite/openclaw-lark-tools",
|
|
@@ -11258,6 +11479,7 @@ function runUpgradeLark(opts) {
|
|
|
11258
11479
|
],
|
|
11259
11480
|
timeout: 12e4
|
|
11260
11481
|
});
|
|
11482
|
+
timing.npxInstallMs = Date.now() - t_npxStart;
|
|
11261
11483
|
const npxStdout = npxResult.stdout?.trim() ?? "";
|
|
11262
11484
|
const npxStderr = npxResult.stderr?.trim() ?? "";
|
|
11263
11485
|
const npxExitCode = npxResult.status ?? 1;
|
|
@@ -11282,6 +11504,7 @@ function runUpgradeLark(opts) {
|
|
|
11282
11504
|
stderr: npxStderr,
|
|
11283
11505
|
exitCode: npxExitCode,
|
|
11284
11506
|
rollbackOk,
|
|
11507
|
+
timing,
|
|
11285
11508
|
logFile
|
|
11286
11509
|
};
|
|
11287
11510
|
};
|
|
@@ -11304,7 +11527,9 @@ function runUpgradeLark(opts) {
|
|
|
11304
11527
|
} catch (e) {
|
|
11305
11528
|
log(` version-compat post-check error: ${e.message} — version signal unavailable`);
|
|
11306
11529
|
}
|
|
11530
|
+
const t_postProbeStart = Date.now();
|
|
11307
11531
|
const afterChannels = probeChannels("after", log, 6e4);
|
|
11532
|
+
timing.postProbeMs = Date.now() - t_postProbeStart;
|
|
11308
11533
|
log(` feishu config invalid after: ${afterChannels.feishuConfigInvalid}`);
|
|
11309
11534
|
const stillNeedsUpgrade = (afterVersionIncompatible || afterChannels.feishuConfigInvalid) && !afterChannels.anyAccountWorking;
|
|
11310
11535
|
log(` post-check: stillNeedsUpgrade=${stillNeedsUpgrade} (version=${afterVersionIncompatible}, feishuConfig=${afterChannels.feishuConfigInvalid}, channelsWorking=${afterChannels.anyAccountWorking})`);
|
|
@@ -11314,6 +11539,7 @@ function runUpgradeLark(opts) {
|
|
|
11314
11539
|
log("── [6/6] doctor --fix ────────────────────────────────────");
|
|
11315
11540
|
const fixArgs = ["doctor", "--fix"];
|
|
11316
11541
|
if (opts.scene) fixArgs.push(`--scene=${opts.scene}`);
|
|
11542
|
+
const t_doctorFixStart = Date.now();
|
|
11317
11543
|
const fixResult = (0, node_child_process.spawnSync)(process.execPath, [cliScript, ...fixArgs], {
|
|
11318
11544
|
cwd,
|
|
11319
11545
|
encoding: "utf-8",
|
|
@@ -11325,6 +11551,7 @@ function runUpgradeLark(opts) {
|
|
|
11325
11551
|
timeout: 6e4,
|
|
11326
11552
|
env: process.env
|
|
11327
11553
|
});
|
|
11554
|
+
timing.doctorFixMs = Date.now() - t_doctorFixStart;
|
|
11328
11555
|
if (fixResult.stdout?.trim()) log(`doctor(fix) stdout:\n${fixResult.stdout.trim()}`);
|
|
11329
11556
|
if (fixResult.stderr?.trim()) log(`doctor(fix) stderr:\n${fixResult.stderr.trim()}`);
|
|
11330
11557
|
log(`doctor(fix) exit: ${fixResult.status ?? "null"}${fixResult.error ? ` error: ${fixResult.error.message}` : ""}`);
|
|
@@ -11337,6 +11564,7 @@ function runUpgradeLark(opts) {
|
|
|
11337
11564
|
stdout: npxStdout,
|
|
11338
11565
|
stderr: npxStderr,
|
|
11339
11566
|
exitCode: npxExitCode,
|
|
11567
|
+
timing,
|
|
11340
11568
|
logFile
|
|
11341
11569
|
};
|
|
11342
11570
|
}
|
|
@@ -11345,21 +11573,6 @@ function runUpgradeLark(opts) {
|
|
|
11345
11573
|
const args = node_process.default.argv.slice(2);
|
|
11346
11574
|
const mode = args.find((a) => !a.startsWith("-"));
|
|
11347
11575
|
/**
|
|
11348
|
-
* Decode `--ctx=<base64>` into an opaque JSON object. Returns undefined when
|
|
11349
|
-
* the flag isn't present — the caller decides whether to fall back to the
|
|
11350
|
-
* innerapi or to error out.
|
|
11351
|
-
*
|
|
11352
|
-
* The object's shape is not enforced here; downstream code consumes it via
|
|
11353
|
-
* either `normalizeCtx()` (new path) or direct field access for the legacy
|
|
11354
|
-
* check/repair/reset contract still used by sandbox_console push.
|
|
11355
|
-
*/
|
|
11356
|
-
function parseCtxFlag(args) {
|
|
11357
|
-
const ctxArg = args.find((a) => a.startsWith("--ctx="));
|
|
11358
|
-
if (!ctxArg) return void 0;
|
|
11359
|
-
const b64 = ctxArg.slice(6);
|
|
11360
|
-
return JSON.parse(Buffer.from(b64, "base64").toString("utf-8"));
|
|
11361
|
-
}
|
|
11362
|
-
/**
|
|
11363
11576
|
* Pull the first non-flag positional after the mode name.
|
|
11364
11577
|
* (The mode itself is args[0] in the filtered set, so we skip index 0.)
|
|
11365
11578
|
*/
|
|
@@ -11387,8 +11600,8 @@ function getMultiFlag(args, name) {
|
|
|
11387
11600
|
* case but is no longer consulted.
|
|
11388
11601
|
*/
|
|
11389
11602
|
async function reportRun(command, rc, _raw, invocation, durationMs, outcome, slardar = {
|
|
11390
|
-
scene,
|
|
11391
|
-
profile,
|
|
11603
|
+
scene: void 0,
|
|
11604
|
+
profile: "standard",
|
|
11392
11605
|
fix: false
|
|
11393
11606
|
}) {
|
|
11394
11607
|
console.error(`${command}: telemetry calling report_cli_run`);
|
|
@@ -11452,7 +11665,7 @@ async function main() {
|
|
|
11452
11665
|
console.error(`${mode}: begin argv=[${args.join(" ")}] version=${getVersion()} traceId=${traceId ?? "-"} caller=${caller ?? "-"} runIdGenerated=${rc.generated}`);
|
|
11453
11666
|
switch (mode) {
|
|
11454
11667
|
case "check": {
|
|
11455
|
-
const raw =
|
|
11668
|
+
const raw = await fetchCtxViaInnerApi({
|
|
11456
11669
|
populate: planCtxPopulate({
|
|
11457
11670
|
command: "check",
|
|
11458
11671
|
profile
|
|
@@ -11477,7 +11690,7 @@ async function main() {
|
|
|
11477
11690
|
break;
|
|
11478
11691
|
}
|
|
11479
11692
|
case "repair": {
|
|
11480
|
-
const raw =
|
|
11693
|
+
const raw = await fetchCtxViaInnerApi({
|
|
11481
11694
|
populate: planCtxPopulate({
|
|
11482
11695
|
command: "repair",
|
|
11483
11696
|
profile
|
|
@@ -11548,27 +11761,15 @@ async function main() {
|
|
|
11548
11761
|
break;
|
|
11549
11762
|
}
|
|
11550
11763
|
case "reset":
|
|
11551
|
-
if (args.includes("--async"))
|
|
11552
|
-
|
|
11553
|
-
let ctxBase64;
|
|
11554
|
-
if (ctxArg) ctxBase64 = ctxArg.slice(6);
|
|
11555
|
-
else {
|
|
11556
|
-
const fetched = await fetchCtxViaInnerApi({
|
|
11557
|
-
populate: planCtxPopulate({ command: "reset" }),
|
|
11558
|
-
caller,
|
|
11559
|
-
traceId
|
|
11560
|
-
});
|
|
11561
|
-
ctxBase64 = Buffer.from(JSON.stringify(fetched), "utf-8").toString("base64");
|
|
11562
|
-
}
|
|
11563
|
-
console.log(JSON.stringify(startAsyncReset(ctxBase64)));
|
|
11564
|
-
} else if (args.includes("--worker")) {
|
|
11764
|
+
if (args.includes("--async")) console.log(JSON.stringify(startAsyncReset()));
|
|
11765
|
+
else if (args.includes("--worker")) {
|
|
11565
11766
|
const taskId = args.find((a) => a.startsWith("--task-id="))?.slice(10);
|
|
11566
11767
|
if (!taskId) {
|
|
11567
11768
|
console.error("Error: --task-id=<id> is required for worker");
|
|
11568
11769
|
node_process.default.exit(1);
|
|
11569
11770
|
}
|
|
11570
11771
|
const resultFile = resetResultFile(taskId);
|
|
11571
|
-
const raw =
|
|
11772
|
+
const raw = await fetchCtxViaInnerApi({
|
|
11572
11773
|
populate: planCtxPopulate({ command: "reset" }),
|
|
11573
11774
|
caller,
|
|
11574
11775
|
traceId
|
|
@@ -11592,7 +11793,7 @@ async function main() {
|
|
|
11592
11793
|
return;
|
|
11593
11794
|
}
|
|
11594
11795
|
} else {
|
|
11595
|
-
console.error("Usage: reset --async
|
|
11796
|
+
console.error("Usage: reset --async | reset --worker --task-id=<id>");
|
|
11596
11797
|
node_process.default.exit(1);
|
|
11597
11798
|
}
|
|
11598
11799
|
break;
|
|
@@ -11608,14 +11809,14 @@ async function main() {
|
|
|
11608
11809
|
case "install-openclaw": {
|
|
11609
11810
|
const tag = getPositionalTag(args, "install-openclaw");
|
|
11610
11811
|
if (!tag) {
|
|
11611
|
-
console.error("Usage: install-openclaw <tag> [--
|
|
11812
|
+
console.error("Usage: install-openclaw <tag> [--oss_file_map=<base64>]");
|
|
11612
11813
|
node_process.default.exit(1);
|
|
11613
11814
|
}
|
|
11614
11815
|
const ossFileMapFlag = getFlag(args, "oss_file_map");
|
|
11615
11816
|
let installOssFileMap;
|
|
11616
11817
|
let rawForTelemetry;
|
|
11617
11818
|
if (!ossFileMapFlag) {
|
|
11618
|
-
rawForTelemetry =
|
|
11819
|
+
rawForTelemetry = await fetchCtxViaInnerApi({
|
|
11619
11820
|
populate: planCtxPopulate({ command: "install" }),
|
|
11620
11821
|
caller,
|
|
11621
11822
|
traceId
|
|
@@ -11650,7 +11851,7 @@ async function main() {
|
|
|
11650
11851
|
case "install-extension": {
|
|
11651
11852
|
const tag = getPositionalTag(args, "install-extension");
|
|
11652
11853
|
if (!tag) {
|
|
11653
|
-
console.error("Usage: install-extension <tag> (--all | --extension=<name>...) [--home_base=<dir>] [--config_path=<path>] [--skip-config-update] [--
|
|
11854
|
+
console.error("Usage: install-extension <tag> (--all | --extension=<name>...) [--home_base=<dir>] [--config_path=<path>] [--skip-config-update] [--oss_file_map=<base64>]");
|
|
11654
11855
|
node_process.default.exit(1);
|
|
11655
11856
|
}
|
|
11656
11857
|
const all = args.includes("--all");
|
|
@@ -11662,7 +11863,7 @@ async function main() {
|
|
|
11662
11863
|
let installOssFileMap;
|
|
11663
11864
|
let rawForTelemetry;
|
|
11664
11865
|
if (!ossFileMapFlag) {
|
|
11665
|
-
rawForTelemetry =
|
|
11866
|
+
rawForTelemetry = await fetchCtxViaInnerApi({
|
|
11666
11867
|
populate: planCtxPopulate({ command: "install" }),
|
|
11667
11868
|
caller,
|
|
11668
11869
|
traceId
|
|
@@ -11708,12 +11909,12 @@ async function main() {
|
|
|
11708
11909
|
case "install-cli": {
|
|
11709
11910
|
const tag = getPositionalTag(args, "install-cli");
|
|
11710
11911
|
if (!tag) {
|
|
11711
|
-
console.error("Usage: install-cli <tag> --cli=<name>... [--home_base=<dir>] [--
|
|
11912
|
+
console.error("Usage: install-cli <tag> --cli=<name>... [--home_base=<dir>] [--oss_file_map=<base64>]");
|
|
11712
11913
|
node_process.default.exit(1);
|
|
11713
11914
|
}
|
|
11714
11915
|
const names = getMultiFlag(args, "cli");
|
|
11715
11916
|
if (names.length === 0) {
|
|
11716
|
-
console.error("Usage: install-cli <tag> --cli=<name>... [--home_base=<dir>] [--
|
|
11917
|
+
console.error("Usage: install-cli <tag> --cli=<name>... [--home_base=<dir>] [--oss_file_map=<base64>]");
|
|
11717
11918
|
node_process.default.exit(1);
|
|
11718
11919
|
}
|
|
11719
11920
|
const homeBase = getFlag(args, "home_base");
|
|
@@ -11721,7 +11922,7 @@ async function main() {
|
|
|
11721
11922
|
let installOssFileMap;
|
|
11722
11923
|
let rawForTelemetry;
|
|
11723
11924
|
if (!ossFileMapFlag) {
|
|
11724
|
-
rawForTelemetry =
|
|
11925
|
+
rawForTelemetry = await fetchCtxViaInnerApi({
|
|
11725
11926
|
populate: planCtxPopulate({ command: "install" }),
|
|
11726
11927
|
caller,
|
|
11727
11928
|
traceId
|
|
@@ -11769,7 +11970,7 @@ async function main() {
|
|
|
11769
11970
|
case "download-resource": {
|
|
11770
11971
|
const tag = getPositionalTag(args, "download-resource");
|
|
11771
11972
|
if (!tag) {
|
|
11772
|
-
console.error("Usage: download-resource <tag> --role=<role> --name=<name> [--dir=<dir>] [--
|
|
11973
|
+
console.error("Usage: download-resource <tag> --role=<role> --name=<name> [--dir=<dir>] [--oss_file_map=<base64>]");
|
|
11773
11974
|
node_process.default.exit(1);
|
|
11774
11975
|
}
|
|
11775
11976
|
const role = getFlag(args, "role");
|
|
@@ -11783,7 +11984,7 @@ async function main() {
|
|
|
11783
11984
|
let installOssFileMap;
|
|
11784
11985
|
let rawForTelemetry;
|
|
11785
11986
|
if (!ossFileMapFlag) {
|
|
11786
|
-
rawForTelemetry =
|
|
11987
|
+
rawForTelemetry = await fetchCtxViaInnerApi({
|
|
11787
11988
|
populate: planCtxPopulate({ command: "install" }),
|
|
11788
11989
|
caller,
|
|
11789
11990
|
traceId
|
|
@@ -11858,21 +12059,25 @@ async function main() {
|
|
|
11858
12059
|
break;
|
|
11859
12060
|
}
|
|
11860
12061
|
case "upgrade-lark": {
|
|
12062
|
+
const checkOnly = args.includes("--check");
|
|
11861
12063
|
const result = runUpgradeLark({
|
|
11862
12064
|
runId: rc.runId,
|
|
11863
|
-
scene
|
|
12065
|
+
scene,
|
|
12066
|
+
checkOnly
|
|
11864
12067
|
});
|
|
11865
12068
|
const upgradeDurationMs = Date.now() - t0;
|
|
11866
12069
|
console.log(JSON.stringify(result));
|
|
11867
12070
|
reportUpgradeLarkToSlardar({
|
|
11868
12071
|
scene,
|
|
12072
|
+
checkOnly,
|
|
11869
12073
|
durationMs: upgradeDurationMs,
|
|
11870
12074
|
success: result.ok,
|
|
11871
12075
|
logFile: result.logFile,
|
|
11872
12076
|
exitCode: result.exitCode,
|
|
11873
12077
|
rollbackOk: result.rollbackOk,
|
|
11874
12078
|
validationError: result.validationError,
|
|
11875
|
-
error: result.error
|
|
12079
|
+
error: result.error,
|
|
12080
|
+
timing: result.timing
|
|
11876
12081
|
});
|
|
11877
12082
|
try {
|
|
11878
12083
|
await reportCliRun({
|
|
@@ -11890,7 +12095,7 @@ async function main() {
|
|
|
11890
12095
|
} catch (e) {
|
|
11891
12096
|
console.error(`[telemetry] reportCliRun failed: ${e.message}`);
|
|
11892
12097
|
}
|
|
11893
|
-
if (!result.ok) {
|
|
12098
|
+
if (!result.ok || checkOnly && result.upgradeNeeded) {
|
|
11894
12099
|
node_process.default.exitCode = 1;
|
|
11895
12100
|
return;
|
|
11896
12101
|
}
|