@lark-apaas/openclaw-scripts-diagnose-cli 0.1.14-alpha.11 → 0.1.14-alpha.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +902 -987
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -52,7 +52,7 @@ node_assert = __toESM(node_assert);
|
|
|
52
52
|
* it terse and parseable.
|
|
53
53
|
*/
|
|
54
54
|
function getVersion() {
|
|
55
|
-
return "0.1.14-alpha.
|
|
55
|
+
return "0.1.14-alpha.13";
|
|
56
56
|
}
|
|
57
57
|
//#endregion
|
|
58
58
|
//#region src/rule-engine/base.ts
|
|
@@ -2520,255 +2520,586 @@ 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/
|
|
2523
|
+
//#region src/paths.ts
|
|
2524
2524
|
/**
|
|
2525
|
-
*
|
|
2526
|
-
*
|
|
2527
|
-
*
|
|
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.
|
|
2528
2530
|
*/
|
|
2529
|
-
const
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
"openclaw-guardian-plugin",
|
|
2533
|
-
"openclaw-mem0-plugin",
|
|
2534
|
-
"openclaw-lark"
|
|
2535
|
-
]);
|
|
2536
|
-
const LOCKED_NPM_SPEC = /^(@[a-z0-9][\w.-]*\/)?[a-z0-9][\w.-]*@[^@/:#\s]+$/i;
|
|
2537
|
-
function isLockedNpmSpec(spec) {
|
|
2538
|
-
return typeof spec === "string" && LOCKED_NPM_SPEC.test(spec);
|
|
2539
|
-
}
|
|
2540
|
-
function unlockSpec(spec) {
|
|
2541
|
-
const slash = spec.indexOf("/");
|
|
2542
|
-
const cut = slash === -1 ? spec.indexOf("@") : spec.indexOf("@", slash + 1);
|
|
2543
|
-
return spec.slice(0, cut);
|
|
2531
|
+
const DIAGNOSE_DIR = "/tmp/openclaw-diagnose";
|
|
2532
|
+
function resetResultFile(taskId) {
|
|
2533
|
+
return `${DIAGNOSE_DIR}/reset-${taskId}.json`;
|
|
2544
2534
|
}
|
|
2545
|
-
|
|
2546
|
-
|
|
2547
|
-
const installs = getNestedMap(config, "plugins", "installs");
|
|
2548
|
-
if (!installs) return;
|
|
2549
|
-
for (const [key, entry] of Object.entries(installs)) {
|
|
2550
|
-
if (!OFFICIAL_PLUGIN_NAMES.has(key)) continue;
|
|
2551
|
-
const spec = asRecord(entry)?.spec;
|
|
2552
|
-
if (isLockedNpmSpec(spec)) yield [key, spec];
|
|
2553
|
-
}
|
|
2535
|
+
function resetLogFile(taskId) {
|
|
2536
|
+
return `${DIAGNOSE_DIR}/reset-${taskId}.log`;
|
|
2554
2537
|
}
|
|
2555
|
-
|
|
2556
|
-
|
|
2557
|
-
|
|
2558
|
-
|
|
2559
|
-
|
|
2560
|
-
|
|
2561
|
-
|
|
2562
|
-
|
|
2563
|
-
}
|
|
2564
|
-
repair(ctx) {
|
|
2565
|
-
for (const [key, spec] of iterLockedOfficialInstalls(ctx.config)) setNestedValue(ctx.config, [
|
|
2566
|
-
"plugins",
|
|
2567
|
-
"installs",
|
|
2568
|
-
key,
|
|
2569
|
-
"spec"
|
|
2570
|
-
], unlockSpec(spec));
|
|
2571
|
-
}
|
|
2572
|
-
};
|
|
2573
|
-
MiaodaOfficialPluginsInstallSpecUnlockRule = __decorate([Rule({
|
|
2574
|
-
key: "miaoda_official_plugins_install_spec_unlock",
|
|
2575
|
-
description: "移除官方妙搭插件安装条目中的锁版本 npm spec,使其跟随最新 manifest 版本",
|
|
2576
|
-
dependsOn: ["config_syntax_check"],
|
|
2577
|
-
repairMode: "standard",
|
|
2578
|
-
level: "silent"
|
|
2579
|
-
})], MiaodaOfficialPluginsInstallSpecUnlockRule);
|
|
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`;
|
|
2580
2546
|
//#endregion
|
|
2581
|
-
//#region src/
|
|
2582
|
-
const
|
|
2583
|
-
|
|
2584
|
-
|
|
2585
|
-
|
|
2586
|
-
|
|
2587
|
-
|
|
2588
|
-
|
|
2589
|
-
|
|
2590
|
-
|
|
2591
|
-
|
|
2592
|
-
|
|
2593
|
-
|
|
2594
|
-
|
|
2595
|
-
|
|
2596
|
-
ctx.config.plugins = { allow: [MIAODA_PLUGIN] };
|
|
2597
|
-
return;
|
|
2547
|
+
//#region src/lark-cli-init.ts
|
|
2548
|
+
const LARK_PLUGIN_NAMES$1 = ["openclaw-lark", "feishu-openclaw-plugin"];
|
|
2549
|
+
const PE_XML_TAG = "lark-cli-pe";
|
|
2550
|
+
const PE_PLACEHOLDER = `
|
|
2551
|
+
<${PE_XML_TAG}>
|
|
2552
|
+
**【强制要求 - 无例外】飞书所有操作必须先读 feishu-lark-cli skill。未读skill不得调用任何飞书工具,违者视为操作失误。**
|
|
2553
|
+
</${PE_XML_TAG}>
|
|
2554
|
+
`;
|
|
2555
|
+
function isLarkPluginInstalled(configPath) {
|
|
2556
|
+
const extDir = getExtensionsDir(configPath);
|
|
2557
|
+
return LARK_PLUGIN_NAMES$1.some((name) => {
|
|
2558
|
+
try {
|
|
2559
|
+
return node_fs.default.existsSync(node_path.default.join(extDir, name, "package.json"));
|
|
2560
|
+
} catch {
|
|
2561
|
+
return false;
|
|
2598
2562
|
}
|
|
2599
|
-
|
|
2600
|
-
const rawAllow = pluginsMap.allow;
|
|
2601
|
-
const allow = Array.isArray(rawAllow) ? rawAllow : [];
|
|
2602
|
-
if (allow.includes(MIAODA_PLUGIN)) return;
|
|
2603
|
-
allow.push(MIAODA_PLUGIN);
|
|
2604
|
-
pluginsMap.allow = allow;
|
|
2605
|
-
}
|
|
2606
|
-
};
|
|
2607
|
-
MiaodaPluginAllowRule = __decorate([Rule({
|
|
2608
|
-
key: "miaoda_plugin_allow",
|
|
2609
|
-
description: "当 openclaw-extension-miaoda 已在磁盘安装但未在 allow 列表中时,将其添加到 plugins.allow(实验性)",
|
|
2610
|
-
dependsOn: ["config_syntax_check"],
|
|
2611
|
-
repairMode: "standard",
|
|
2612
|
-
level: "critical",
|
|
2613
|
-
profile: "standard"
|
|
2614
|
-
})], MiaodaPluginAllowRule);
|
|
2615
|
-
function getAllow$1(config) {
|
|
2616
|
-
const plugins = config.plugins;
|
|
2617
|
-
if (plugins == null || typeof plugins !== "object" || Array.isArray(plugins)) return [];
|
|
2618
|
-
const allow = plugins.allow;
|
|
2619
|
-
if (!Array.isArray(allow)) return [];
|
|
2620
|
-
return allow.filter((e) => typeof e === "string");
|
|
2563
|
+
});
|
|
2621
2564
|
}
|
|
2622
|
-
|
|
2623
|
-
|
|
2624
|
-
|
|
2625
|
-
|
|
2626
|
-
|
|
2627
|
-
|
|
2628
|
-
|
|
2629
|
-
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
|
|
2633
|
-
|
|
2634
|
-
|
|
2635
|
-
message: `plugins.allow 缺少飞书插件 ${installed}(已在 extensions/ 下装但未启用)`
|
|
2636
|
-
};
|
|
2565
|
+
function isLarkCliAvailable$2() {
|
|
2566
|
+
try {
|
|
2567
|
+
return (0, node_child_process.spawnSync)("lark-cli", ["--version"], {
|
|
2568
|
+
encoding: "utf-8",
|
|
2569
|
+
timeout: 5e3,
|
|
2570
|
+
stdio: [
|
|
2571
|
+
"ignore",
|
|
2572
|
+
"pipe",
|
|
2573
|
+
"ignore"
|
|
2574
|
+
]
|
|
2575
|
+
}).status === 0;
|
|
2576
|
+
} catch {
|
|
2577
|
+
return false;
|
|
2637
2578
|
}
|
|
2638
|
-
|
|
2639
|
-
|
|
2640
|
-
|
|
2641
|
-
|
|
2642
|
-
|
|
2643
|
-
|
|
2644
|
-
|
|
2645
|
-
|
|
2646
|
-
const rawAllow = pluginsMap.allow;
|
|
2647
|
-
const original = Array.isArray(rawAllow) ? rawAllow : [];
|
|
2648
|
-
const stringAllow = original.filter((e) => typeof e === "string");
|
|
2649
|
-
if (LARK_PLUGIN_NAMES$1.some((name) => stringAllow.includes(name))) return;
|
|
2650
|
-
original.push(installed);
|
|
2651
|
-
pluginsMap.allow = original;
|
|
2579
|
+
}
|
|
2580
|
+
function readConfig(configPath) {
|
|
2581
|
+
try {
|
|
2582
|
+
const raw = node_fs.default.readFileSync(configPath, "utf-8");
|
|
2583
|
+
const parsed = loadJSON5().parse(raw);
|
|
2584
|
+
return parsed != null && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
|
|
2585
|
+
} catch {
|
|
2586
|
+
return null;
|
|
2652
2587
|
}
|
|
2653
|
-
};
|
|
2654
|
-
LarkPluginAllowRule = __decorate([Rule({
|
|
2655
|
-
key: "lark_plugin_allow",
|
|
2656
|
-
description: "当飞书插件(openclaw-lark 或旧版名)已在磁盘安装但未加入 plugins.allow 时,自动添加",
|
|
2657
|
-
dependsOn: ["config_syntax_check"],
|
|
2658
|
-
repairMode: "standard",
|
|
2659
|
-
level: "critical"
|
|
2660
|
-
})], LarkPluginAllowRule);
|
|
2661
|
-
function getAllow(config) {
|
|
2662
|
-
const plugins = config.plugins;
|
|
2663
|
-
if (plugins == null || typeof plugins !== "object" || Array.isArray(plugins)) return [];
|
|
2664
|
-
const allow = plugins.allow;
|
|
2665
|
-
if (!Array.isArray(allow)) return [];
|
|
2666
|
-
return allow.filter((e) => typeof e === "string");
|
|
2667
2588
|
}
|
|
2668
2589
|
/**
|
|
2669
|
-
*
|
|
2670
|
-
*
|
|
2671
|
-
*
|
|
2590
|
+
* Resolve the feishu app secret for the given appId.
|
|
2591
|
+
*
|
|
2592
|
+
* Lookup order:
|
|
2593
|
+
* 1. channels.feishu.appSecret (single-agent: feishu.appId === appId)
|
|
2594
|
+
* 2. channels.feishu.accounts[key].appSecret (multi-agent: account.appId === appId)
|
|
2595
|
+
*
|
|
2596
|
+
* Value interpretation:
|
|
2597
|
+
* - string → use directly
|
|
2598
|
+
* - object → secret is managed by a provider; use `feishuAppSecret` param instead
|
|
2599
|
+
*
|
|
2600
|
+
* Returns null when the secret cannot be determined.
|
|
2672
2601
|
*/
|
|
2673
|
-
function
|
|
2674
|
-
|
|
2602
|
+
function resolveAppSecret(appId, config, feishuAppSecret) {
|
|
2603
|
+
const feishu = getNestedMap(config, "channels", "feishu");
|
|
2604
|
+
if (!feishu) return null;
|
|
2605
|
+
let rawSecret;
|
|
2606
|
+
if (typeof feishu.appId === "string" && feishu.appId === appId) rawSecret = feishu.appSecret;
|
|
2607
|
+
else {
|
|
2608
|
+
const accounts = asRecord(feishu.accounts);
|
|
2609
|
+
if (accounts) for (const [, val] of Object.entries(accounts)) {
|
|
2610
|
+
const account = asRecord(val);
|
|
2611
|
+
if (account?.appId === appId) {
|
|
2612
|
+
rawSecret = account.appSecret ?? feishu.appSecret;
|
|
2613
|
+
break;
|
|
2614
|
+
}
|
|
2615
|
+
}
|
|
2616
|
+
}
|
|
2617
|
+
if (typeof rawSecret === "string" && rawSecret) return rawSecret;
|
|
2618
|
+
if (rawSecret != null && typeof rawSecret === "object") return feishuAppSecret ?? null;
|
|
2675
2619
|
return null;
|
|
2676
2620
|
}
|
|
2677
|
-
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
|
|
2681
|
-
|
|
2621
|
+
/**
|
|
2622
|
+
* Resolve the agents.md path for the given appId from the openclaw config.
|
|
2623
|
+
*
|
|
2624
|
+
* Case 1: appId matches channels.feishu.appId (single-agent path)
|
|
2625
|
+
* → WORKSPACE_DIR/AGENTS.md
|
|
2626
|
+
*
|
|
2627
|
+
* Case 2: appId found in channels.feishu.accounts (multi-agent path)
|
|
2628
|
+
* → find account key where account.appId === appId
|
|
2629
|
+
* → find binding where match.channel=feishu && match.accountId=that key
|
|
2630
|
+
* → if agentId === 'main' → WORKSPACE_DIR/agents.md
|
|
2631
|
+
* → else find agent in agents.list by id → agent.workspace/agents.md
|
|
2632
|
+
*
|
|
2633
|
+
* Returns null when the path cannot be determined.
|
|
2634
|
+
*/
|
|
2635
|
+
function resolveAgentsMdPath(appId, config) {
|
|
2636
|
+
const feishu = getNestedMap(config, "channels", "feishu");
|
|
2637
|
+
if (!feishu) {
|
|
2638
|
+
console.error("resolveAgentsMdPath: channels.feishu not found");
|
|
2639
|
+
return null;
|
|
2682
2640
|
}
|
|
2641
|
+
if (typeof feishu.appId === "string" && feishu.appId === appId) {
|
|
2642
|
+
console.error(`resolveAgentsMdPath: case=single-agent feishu.appId=${feishu.appId}`);
|
|
2643
|
+
return node_path.default.join(WORKSPACE_DIR, "workspace", "AGENTS.md");
|
|
2644
|
+
}
|
|
2645
|
+
const accounts = asRecord(feishu.accounts);
|
|
2646
|
+
if (!accounts) {
|
|
2647
|
+
console.error("resolveAgentsMdPath: feishu.accounts not found");
|
|
2648
|
+
return null;
|
|
2649
|
+
}
|
|
2650
|
+
let accountId;
|
|
2651
|
+
for (const [key, val] of Object.entries(accounts)) if (asRecord(val)?.appId === appId) {
|
|
2652
|
+
accountId = key;
|
|
2653
|
+
break;
|
|
2654
|
+
}
|
|
2655
|
+
if (!accountId) {
|
|
2656
|
+
console.error(`resolveAgentsMdPath: no account found with appId=${appId} in feishu.accounts keys=[${Object.keys(accounts).join(",")}]`);
|
|
2657
|
+
return null;
|
|
2658
|
+
}
|
|
2659
|
+
console.error(`resolveAgentsMdPath: found accountId=${accountId}`);
|
|
2660
|
+
const bindings = Array.isArray(config.bindings) ? config.bindings : [];
|
|
2661
|
+
let agentId;
|
|
2662
|
+
for (const b of bindings) {
|
|
2663
|
+
const binding = asRecord(b);
|
|
2664
|
+
if (!binding) continue;
|
|
2665
|
+
const match = asRecord(binding.match);
|
|
2666
|
+
if (match?.channel === "feishu" && match?.accountId === accountId) {
|
|
2667
|
+
if (typeof binding.agentId === "string") {
|
|
2668
|
+
agentId = binding.agentId;
|
|
2669
|
+
break;
|
|
2670
|
+
}
|
|
2671
|
+
}
|
|
2672
|
+
}
|
|
2673
|
+
if (!agentId) {
|
|
2674
|
+
console.error(`resolveAgentsMdPath: no binding found for accountId=${accountId} in bindings(count=${bindings.length})`);
|
|
2675
|
+
return null;
|
|
2676
|
+
}
|
|
2677
|
+
console.error(`resolveAgentsMdPath: found agentId=${agentId}`);
|
|
2678
|
+
if (agentId === "main") {
|
|
2679
|
+
console.error("resolveAgentsMdPath: case=multi-agent-main");
|
|
2680
|
+
return node_path.default.join(WORKSPACE_DIR, "workspace", "AGENTS.md");
|
|
2681
|
+
}
|
|
2682
|
+
const agentsObj = asRecord(config.agents);
|
|
2683
|
+
const list = Array.isArray(agentsObj?.list) ? agentsObj.list : [];
|
|
2684
|
+
for (const a of list) {
|
|
2685
|
+
const agent = asRecord(a);
|
|
2686
|
+
if (agent?.id === agentId) {
|
|
2687
|
+
const ws = typeof agent.workspace === "string" ? agent.workspace : node_path.default.join(WORKSPACE_DIR, "workspace");
|
|
2688
|
+
console.error(`resolveAgentsMdPath: case=multi-agent-custom agentId=${agentId} workspace=${ws}`);
|
|
2689
|
+
return node_path.default.join(ws, "AGENTS.md");
|
|
2690
|
+
}
|
|
2691
|
+
}
|
|
2692
|
+
console.error(`resolveAgentsMdPath: agentId=${agentId} not found in agents.list(count=${list.length})`);
|
|
2693
|
+
return null;
|
|
2683
2694
|
}
|
|
2684
|
-
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
const
|
|
2688
|
-
|
|
2689
|
-
|
|
2690
|
-
|
|
2691
|
-
|
|
2692
|
-
|
|
2693
|
-
|
|
2694
|
-
|
|
2695
|
-
return {
|
|
2696
|
-
entries: getNestedMap(config, "plugins", "entries"),
|
|
2697
|
-
installs: getNestedMap(config, "plugins", "installs"),
|
|
2698
|
-
allow: Array.isArray(rawAllow) ? rawAllow : void 0
|
|
2699
|
-
};
|
|
2700
|
-
}
|
|
2701
|
-
function hasNewMiaoda({ entries, installs, allow }) {
|
|
2702
|
-
return asRecord(entries?.[NEW_MIAODA]) != null || asRecord(installs?.[NEW_MIAODA]) != null || (allow?.includes(NEW_MIAODA) ?? false);
|
|
2695
|
+
function appendPeToAgentsMd(agentsMdPath) {
|
|
2696
|
+
const dir = node_path.default.dirname(agentsMdPath);
|
|
2697
|
+
if (!node_fs.default.existsSync(dir)) node_fs.default.mkdirSync(dir, { recursive: true });
|
|
2698
|
+
const existing = node_fs.default.existsSync(agentsMdPath) ? node_fs.default.readFileSync(agentsMdPath, "utf-8") : "";
|
|
2699
|
+
if (existing.includes(`<lark-cli-pe>`)) {
|
|
2700
|
+
console.error(`lark-cli-init: <${PE_XML_TAG}> already present in ${agentsMdPath}, skipping`);
|
|
2701
|
+
return;
|
|
2702
|
+
}
|
|
2703
|
+
const sep = existing.length > 0 && !existing.endsWith("\n") ? "\n" : "";
|
|
2704
|
+
node_fs.default.appendFileSync(agentsMdPath, `${sep}${PE_PLACEHOLDER}`, "utf-8");
|
|
2705
|
+
console.error(`lark-cli-init: appended PE placeholder to ${agentsMdPath}`);
|
|
2703
2706
|
}
|
|
2704
|
-
|
|
2705
|
-
|
|
2707
|
+
/**
|
|
2708
|
+
* Collect every Feishu bot appId declared in the openclaw config.
|
|
2709
|
+
* Covers both single-agent (channels.feishu.appId) and multi-agent
|
|
2710
|
+
* (channels.feishu.accounts[*].appId) layouts. Returns a deduplicated list.
|
|
2711
|
+
*/
|
|
2712
|
+
function collectFeishuAppIds(configPath) {
|
|
2713
|
+
const config = readConfig(configPath ?? CONFIG_PATH);
|
|
2714
|
+
if (!config) return [];
|
|
2715
|
+
const feishu = getNestedMap(config, "channels", "feishu");
|
|
2716
|
+
if (!feishu) return [];
|
|
2717
|
+
const appIds = /* @__PURE__ */ new Set();
|
|
2718
|
+
const topAppId = feishu.appId;
|
|
2719
|
+
if (typeof topAppId === "string" && topAppId.trim()) appIds.add(topAppId.trim());
|
|
2720
|
+
const accounts = asRecord(feishu.accounts);
|
|
2721
|
+
if (accounts) for (const val of Object.values(accounts)) {
|
|
2722
|
+
const appId = asRecord(val)?.appId;
|
|
2723
|
+
if (typeof appId === "string" && appId.trim()) appIds.add(appId.trim());
|
|
2724
|
+
}
|
|
2725
|
+
return [...appIds];
|
|
2706
2726
|
}
|
|
2707
|
-
|
|
2727
|
+
function runLarkCliInit(opts) {
|
|
2728
|
+
const configPath = opts.configPath ?? CONFIG_PATH;
|
|
2729
|
+
if (!isLarkPluginInstalled(configPath)) {
|
|
2730
|
+
console.error("lark-cli-init: skipping — openclaw-lark plugin not installed");
|
|
2731
|
+
return {
|
|
2732
|
+
ok: true,
|
|
2733
|
+
skipped: true,
|
|
2734
|
+
skipReason: "openclaw-lark plugin not installed"
|
|
2735
|
+
};
|
|
2736
|
+
}
|
|
2737
|
+
if (!isLarkCliAvailable$2()) {
|
|
2738
|
+
console.error("lark-cli-init: skipping — lark-cli command not found");
|
|
2739
|
+
return {
|
|
2740
|
+
ok: true,
|
|
2741
|
+
skipped: true,
|
|
2742
|
+
skipReason: "lark-cli command not found"
|
|
2743
|
+
};
|
|
2744
|
+
}
|
|
2745
|
+
const config = readConfig(configPath);
|
|
2746
|
+
if (!config) return {
|
|
2747
|
+
ok: false,
|
|
2748
|
+
error: `could not read config at ${configPath}`
|
|
2749
|
+
};
|
|
2750
|
+
const agentsMdPath = resolveAgentsMdPath(opts.appId, config);
|
|
2751
|
+
console.error(`lark-cli-init: resolved agents.md path=${agentsMdPath ?? "(null)"}`);
|
|
2752
|
+
if (!agentsMdPath) return {
|
|
2753
|
+
ok: false,
|
|
2754
|
+
error: `could not resolve agents.md path for appId=${opts.appId}`
|
|
2755
|
+
};
|
|
2756
|
+
const appSecret = resolveAppSecret(opts.appId, config, opts.feishuAppSecret);
|
|
2757
|
+
if (!appSecret) return {
|
|
2758
|
+
ok: false,
|
|
2759
|
+
error: `could not resolve appSecret for appId=${opts.appId}`
|
|
2760
|
+
};
|
|
2761
|
+
console.error(`lark-cli-init: running lark-cli config init --name ${opts.appId} --app-id ${opts.appId} --brand feishu --app-secret-stdin --force-init`);
|
|
2762
|
+
const initRes = (0, node_child_process.spawnSync)("lark-cli", [
|
|
2763
|
+
"config",
|
|
2764
|
+
"init",
|
|
2765
|
+
"--name",
|
|
2766
|
+
opts.appId,
|
|
2767
|
+
"--app-id",
|
|
2768
|
+
opts.appId,
|
|
2769
|
+
"--brand",
|
|
2770
|
+
"feishu",
|
|
2771
|
+
"--app-secret-stdin",
|
|
2772
|
+
"--force-init"
|
|
2773
|
+
], {
|
|
2774
|
+
stdio: [
|
|
2775
|
+
"pipe",
|
|
2776
|
+
"pipe",
|
|
2777
|
+
"pipe"
|
|
2778
|
+
],
|
|
2779
|
+
encoding: "utf-8",
|
|
2780
|
+
input: appSecret
|
|
2781
|
+
});
|
|
2782
|
+
const configInitStdout = initRes.stdout?.trim() || void 0;
|
|
2783
|
+
const configInitStderr = initRes.stderr?.trim() || void 0;
|
|
2784
|
+
if (configInitStdout) console.error(`lark-cli config init stdout: ${configInitStdout}`);
|
|
2785
|
+
if (configInitStderr) console.error(`lark-cli config init stderr: ${configInitStderr}`);
|
|
2786
|
+
if (initRes.error) return {
|
|
2787
|
+
ok: false,
|
|
2788
|
+
configInitStdout,
|
|
2789
|
+
configInitStderr,
|
|
2790
|
+
error: `lark-cli config init spawn error: ${initRes.error.message}`
|
|
2791
|
+
};
|
|
2792
|
+
if (initRes.status !== 0) return {
|
|
2793
|
+
ok: false,
|
|
2794
|
+
configInitExitCode: initRes.status ?? void 0,
|
|
2795
|
+
configInitStdout,
|
|
2796
|
+
configInitStderr,
|
|
2797
|
+
error: `lark-cli config init exited with code ${initRes.status}`
|
|
2798
|
+
};
|
|
2799
|
+
appendPeToAgentsMd(agentsMdPath);
|
|
2800
|
+
return {
|
|
2801
|
+
ok: true,
|
|
2802
|
+
configInitExitCode: 0,
|
|
2803
|
+
agentsMdPath
|
|
2804
|
+
};
|
|
2805
|
+
}
|
|
2806
|
+
//#endregion
|
|
2807
|
+
//#region src/rules/agents-md-lark-cli-pe.ts
|
|
2808
|
+
function isLarkCliAvailable$1() {
|
|
2809
|
+
try {
|
|
2810
|
+
return (0, node_child_process.spawnSync)("lark-cli", ["--version"], {
|
|
2811
|
+
encoding: "utf-8",
|
|
2812
|
+
timeout: 5e3,
|
|
2813
|
+
stdio: [
|
|
2814
|
+
"ignore",
|
|
2815
|
+
"pipe",
|
|
2816
|
+
"ignore"
|
|
2817
|
+
]
|
|
2818
|
+
}).status === 0;
|
|
2819
|
+
} catch {
|
|
2820
|
+
return false;
|
|
2821
|
+
}
|
|
2822
|
+
}
|
|
2823
|
+
let AgentsMdLarkCliPeRule = class AgentsMdLarkCliPeRule extends DiagnoseRule {
|
|
2708
2824
|
validate(ctx) {
|
|
2709
|
-
|
|
2710
|
-
|
|
2711
|
-
|
|
2712
|
-
|
|
2825
|
+
if (!isLarkCliAvailable$1()) return { pass: true };
|
|
2826
|
+
const missingPath = collectExistingAgentsMdPaths(ctx).find((filePath) => {
|
|
2827
|
+
return !node_fs.default.readFileSync(filePath, "utf-8").includes(`<${PE_XML_TAG}>`);
|
|
2828
|
+
});
|
|
2829
|
+
if (!missingPath) return { pass: true };
|
|
2713
2830
|
return {
|
|
2714
2831
|
pass: false,
|
|
2715
|
-
message:
|
|
2832
|
+
message: `${missingPath} 中缺少 lark-cli-pe PE 内容,需要追加`
|
|
2716
2833
|
};
|
|
2717
2834
|
}
|
|
2718
2835
|
repair(ctx) {
|
|
2719
|
-
|
|
2720
|
-
|
|
2721
|
-
|
|
2722
|
-
|
|
2723
|
-
|
|
2724
|
-
|
|
2725
|
-
|
|
2726
|
-
if (typeof v === "string" && oldSet.has(v)) allow.splice(i, 1);
|
|
2727
|
-
}
|
|
2728
|
-
for (const name of OLD_PLUGIN_NAMES) {
|
|
2729
|
-
if (entries && name in entries) delete entries[name];
|
|
2730
|
-
if (installs && name in installs) delete installs[name];
|
|
2731
|
-
const target = node_path.default.join(extensionsDir, name);
|
|
2732
|
-
const rel = node_path.default.relative(extensionsDir, target);
|
|
2733
|
-
if (!rel || rel.startsWith("..") || node_path.default.isAbsolute(rel)) continue;
|
|
2734
|
-
try {
|
|
2735
|
-
node_fs.default.rmSync(target, {
|
|
2736
|
-
recursive: true,
|
|
2737
|
-
force: true
|
|
2738
|
-
});
|
|
2739
|
-
} catch (e) {
|
|
2740
|
-
console.error(`[old_miaoda_plugins_cleanup] rmSync ${target} failed: ${e.message}`);
|
|
2741
|
-
}
|
|
2836
|
+
if (!isLarkCliAvailable$1()) return;
|
|
2837
|
+
for (const filePath of collectExistingAgentsMdPaths(ctx)) {
|
|
2838
|
+
const content = node_fs.default.readFileSync(filePath, "utf-8");
|
|
2839
|
+
if (content.includes(`<lark-cli-pe>`)) continue;
|
|
2840
|
+
const sep = content.length > 0 && !content.endsWith("\n") ? "\n" : "";
|
|
2841
|
+
node_fs.default.appendFileSync(filePath, `${sep}${PE_PLACEHOLDER}`, "utf-8");
|
|
2842
|
+
console.error(`agents-md-lark-cli-pe: appended PE to ${filePath}`);
|
|
2742
2843
|
}
|
|
2743
2844
|
}
|
|
2744
2845
|
};
|
|
2745
|
-
|
|
2746
|
-
key: "
|
|
2747
|
-
description: "
|
|
2846
|
+
AgentsMdLarkCliPeRule = __decorate([Rule({
|
|
2847
|
+
key: "agents_md_lark_cli_pe",
|
|
2848
|
+
description: "检测各智能体 AGENTS.md 中是否缺失 lark-cli-pe PE 内容,lark-cli 存在时自动追加",
|
|
2748
2849
|
dependsOn: ["config_syntax_check"],
|
|
2749
2850
|
repairMode: "standard",
|
|
2750
2851
|
level: "silent"
|
|
2751
|
-
})],
|
|
2852
|
+
})], AgentsMdLarkCliPeRule);
|
|
2752
2853
|
//#endregion
|
|
2753
|
-
//#region src/rules/
|
|
2854
|
+
//#region src/rules/miaoda-official-plugins-install-spec-unlock.ts
|
|
2754
2855
|
/**
|
|
2755
|
-
*
|
|
2756
|
-
*
|
|
2856
|
+
* Official miaoda-side plugins that must track manifest — version-locked specs
|
|
2857
|
+
* here block upgrades. Third-party / user-installed plugins are intentionally
|
|
2858
|
+
* out of scope (users may pin them deliberately).
|
|
2757
2859
|
*/
|
|
2758
|
-
const
|
|
2759
|
-
"openclaw-lark",
|
|
2860
|
+
const OFFICIAL_PLUGIN_NAMES = new Set([
|
|
2760
2861
|
"openclaw-extension-miaoda",
|
|
2761
2862
|
"openclaw-extension-miaoda-coding",
|
|
2762
2863
|
"openclaw-guardian-plugin",
|
|
2763
|
-
"openclaw-mem0-plugin"
|
|
2864
|
+
"openclaw-mem0-plugin",
|
|
2865
|
+
"openclaw-lark"
|
|
2764
2866
|
]);
|
|
2765
|
-
|
|
2766
|
-
|
|
2767
|
-
return
|
|
2867
|
+
const LOCKED_NPM_SPEC = /^(@[a-z0-9][\w.-]*\/)?[a-z0-9][\w.-]*@[^@/:#\s]+$/i;
|
|
2868
|
+
function isLockedNpmSpec(spec) {
|
|
2869
|
+
return typeof spec === "string" && LOCKED_NPM_SPEC.test(spec);
|
|
2768
2870
|
}
|
|
2769
|
-
|
|
2871
|
+
function unlockSpec(spec) {
|
|
2872
|
+
const slash = spec.indexOf("/");
|
|
2873
|
+
const cut = slash === -1 ? spec.indexOf("@") : spec.indexOf("@", slash + 1);
|
|
2874
|
+
return spec.slice(0, cut);
|
|
2875
|
+
}
|
|
2876
|
+
/** Yield `[key, lockedSpec]` for every official-plugin install whose `spec` is locked. */
|
|
2877
|
+
function* iterLockedOfficialInstalls(config) {
|
|
2878
|
+
const installs = getNestedMap(config, "plugins", "installs");
|
|
2879
|
+
if (!installs) return;
|
|
2880
|
+
for (const [key, entry] of Object.entries(installs)) {
|
|
2881
|
+
if (!OFFICIAL_PLUGIN_NAMES.has(key)) continue;
|
|
2882
|
+
const spec = asRecord(entry)?.spec;
|
|
2883
|
+
if (isLockedNpmSpec(spec)) yield [key, spec];
|
|
2884
|
+
}
|
|
2885
|
+
}
|
|
2886
|
+
let MiaodaOfficialPluginsInstallSpecUnlockRule = class MiaodaOfficialPluginsInstallSpecUnlockRule extends DiagnoseRule {
|
|
2770
2887
|
validate(ctx) {
|
|
2771
|
-
const
|
|
2888
|
+
const locked = [...iterLockedOfficialInstalls(ctx.config)].map(([k]) => k);
|
|
2889
|
+
if (locked.length === 0) return { pass: true };
|
|
2890
|
+
return {
|
|
2891
|
+
pass: false,
|
|
2892
|
+
message: "plugins.installs 中官方插件存在锁版本的 spec: " + locked.sort().join(",")
|
|
2893
|
+
};
|
|
2894
|
+
}
|
|
2895
|
+
repair(ctx) {
|
|
2896
|
+
for (const [key, spec] of iterLockedOfficialInstalls(ctx.config)) setNestedValue(ctx.config, [
|
|
2897
|
+
"plugins",
|
|
2898
|
+
"installs",
|
|
2899
|
+
key,
|
|
2900
|
+
"spec"
|
|
2901
|
+
], unlockSpec(spec));
|
|
2902
|
+
}
|
|
2903
|
+
};
|
|
2904
|
+
MiaodaOfficialPluginsInstallSpecUnlockRule = __decorate([Rule({
|
|
2905
|
+
key: "miaoda_official_plugins_install_spec_unlock",
|
|
2906
|
+
description: "移除官方妙搭插件安装条目中的锁版本 npm spec,使其跟随最新 manifest 版本",
|
|
2907
|
+
dependsOn: ["config_syntax_check"],
|
|
2908
|
+
repairMode: "standard",
|
|
2909
|
+
level: "silent"
|
|
2910
|
+
})], MiaodaOfficialPluginsInstallSpecUnlockRule);
|
|
2911
|
+
//#endregion
|
|
2912
|
+
//#region src/rules/miaoda-plugin-allow.ts
|
|
2913
|
+
const MIAODA_PLUGIN = "openclaw-extension-miaoda";
|
|
2914
|
+
let MiaodaPluginAllowRule = class MiaodaPluginAllowRule extends DiagnoseRule {
|
|
2915
|
+
validate(ctx) {
|
|
2916
|
+
if (!isPluginInstalledOnDisk(getExtensionsDir(ctx.configPath), MIAODA_PLUGIN)) return { pass: true };
|
|
2917
|
+
if (getAllow$1(ctx.config).includes(MIAODA_PLUGIN)) return { pass: true };
|
|
2918
|
+
return {
|
|
2919
|
+
pass: false,
|
|
2920
|
+
message: `plugins.allow 缺少 ${MIAODA_PLUGIN}(已在 extensions/ 下装但未启用)`
|
|
2921
|
+
};
|
|
2922
|
+
}
|
|
2923
|
+
repair(ctx) {
|
|
2924
|
+
if (!isPluginInstalledOnDisk(getExtensionsDir(ctx.configPath), MIAODA_PLUGIN)) return;
|
|
2925
|
+
const plugins = ctx.config.plugins;
|
|
2926
|
+
if (plugins == null || typeof plugins !== "object" || Array.isArray(plugins)) {
|
|
2927
|
+
ctx.config.plugins = { allow: [MIAODA_PLUGIN] };
|
|
2928
|
+
return;
|
|
2929
|
+
}
|
|
2930
|
+
const pluginsMap = plugins;
|
|
2931
|
+
const rawAllow = pluginsMap.allow;
|
|
2932
|
+
const allow = Array.isArray(rawAllow) ? rawAllow : [];
|
|
2933
|
+
if (allow.includes(MIAODA_PLUGIN)) return;
|
|
2934
|
+
allow.push(MIAODA_PLUGIN);
|
|
2935
|
+
pluginsMap.allow = allow;
|
|
2936
|
+
}
|
|
2937
|
+
};
|
|
2938
|
+
MiaodaPluginAllowRule = __decorate([Rule({
|
|
2939
|
+
key: "miaoda_plugin_allow",
|
|
2940
|
+
description: "当 openclaw-extension-miaoda 已在磁盘安装但未在 allow 列表中时,将其添加到 plugins.allow(实验性)",
|
|
2941
|
+
dependsOn: ["config_syntax_check"],
|
|
2942
|
+
repairMode: "standard",
|
|
2943
|
+
level: "critical",
|
|
2944
|
+
profile: "standard"
|
|
2945
|
+
})], MiaodaPluginAllowRule);
|
|
2946
|
+
function getAllow$1(config) {
|
|
2947
|
+
const plugins = config.plugins;
|
|
2948
|
+
if (plugins == null || typeof plugins !== "object" || Array.isArray(plugins)) return [];
|
|
2949
|
+
const allow = plugins.allow;
|
|
2950
|
+
if (!Array.isArray(allow)) return [];
|
|
2951
|
+
return allow.filter((e) => typeof e === "string");
|
|
2952
|
+
}
|
|
2953
|
+
//#endregion
|
|
2954
|
+
//#region src/rules/lark-plugin-allow.ts
|
|
2955
|
+
const LARK_PLUGIN = "openclaw-lark";
|
|
2956
|
+
const LEGACY_LARK_PLUGIN = "feishu-openclaw-plugin";
|
|
2957
|
+
const LARK_PLUGIN_NAMES = [LARK_PLUGIN, LEGACY_LARK_PLUGIN];
|
|
2958
|
+
let LarkPluginAllowRule = class LarkPluginAllowRule extends DiagnoseRule {
|
|
2959
|
+
validate(ctx) {
|
|
2960
|
+
const allow = getAllow(ctx.config);
|
|
2961
|
+
if (LARK_PLUGIN_NAMES.some((name) => allow.includes(name))) return { pass: true };
|
|
2962
|
+
const installed = detectInstalledLarkPlugin(getExtensionsDir(ctx.configPath));
|
|
2963
|
+
if (installed == null) return { pass: true };
|
|
2964
|
+
return {
|
|
2965
|
+
pass: false,
|
|
2966
|
+
message: `plugins.allow 缺少飞书插件 ${installed}(已在 extensions/ 下装但未启用)`
|
|
2967
|
+
};
|
|
2968
|
+
}
|
|
2969
|
+
repair(ctx) {
|
|
2970
|
+
const installed = detectInstalledLarkPlugin(getExtensionsDir(ctx.configPath));
|
|
2971
|
+
if (installed == null) return;
|
|
2972
|
+
if (ctx.config.plugins == null || typeof ctx.config.plugins !== "object" || Array.isArray(ctx.config.plugins)) {
|
|
2973
|
+
ctx.config.plugins = { allow: [installed] };
|
|
2974
|
+
return;
|
|
2975
|
+
}
|
|
2976
|
+
const pluginsMap = ctx.config.plugins;
|
|
2977
|
+
const rawAllow = pluginsMap.allow;
|
|
2978
|
+
const original = Array.isArray(rawAllow) ? rawAllow : [];
|
|
2979
|
+
const stringAllow = original.filter((e) => typeof e === "string");
|
|
2980
|
+
if (LARK_PLUGIN_NAMES.some((name) => stringAllow.includes(name))) return;
|
|
2981
|
+
original.push(installed);
|
|
2982
|
+
pluginsMap.allow = original;
|
|
2983
|
+
}
|
|
2984
|
+
};
|
|
2985
|
+
LarkPluginAllowRule = __decorate([Rule({
|
|
2986
|
+
key: "lark_plugin_allow",
|
|
2987
|
+
description: "当飞书插件(openclaw-lark 或旧版名)已在磁盘安装但未加入 plugins.allow 时,自动添加",
|
|
2988
|
+
dependsOn: ["config_syntax_check"],
|
|
2989
|
+
repairMode: "standard",
|
|
2990
|
+
level: "critical"
|
|
2991
|
+
})], LarkPluginAllowRule);
|
|
2992
|
+
function getAllow(config) {
|
|
2993
|
+
const plugins = config.plugins;
|
|
2994
|
+
if (plugins == null || typeof plugins !== "object" || Array.isArray(plugins)) return [];
|
|
2995
|
+
const allow = plugins.allow;
|
|
2996
|
+
if (!Array.isArray(allow)) return [];
|
|
2997
|
+
return allow.filter((e) => typeof e === "string");
|
|
2998
|
+
}
|
|
2999
|
+
/**
|
|
3000
|
+
* fs-only 检测:`<extDir>/<name>/package.json` 存在即视为已装。
|
|
3001
|
+
* 优先级 openclaw-lark(新版)> feishu-openclaw-plugin(legacy)。
|
|
3002
|
+
* 不读 package.json 内容,只判存在性,避开 JSON 损坏。
|
|
3003
|
+
*/
|
|
3004
|
+
function detectInstalledLarkPlugin(extDir) {
|
|
3005
|
+
for (const name of [LARK_PLUGIN, LEGACY_LARK_PLUGIN]) if (pluginPackageJsonExists(extDir, name)) return name;
|
|
3006
|
+
return null;
|
|
3007
|
+
}
|
|
3008
|
+
function pluginPackageJsonExists(extDir, pluginDir) {
|
|
3009
|
+
try {
|
|
3010
|
+
return node_fs.default.existsSync(node_path.default.join(extDir, pluginDir, "package.json"));
|
|
3011
|
+
} catch {
|
|
3012
|
+
return false;
|
|
3013
|
+
}
|
|
3014
|
+
}
|
|
3015
|
+
//#endregion
|
|
3016
|
+
//#region src/rules/old-miaoda-plugins-cleanup.ts
|
|
3017
|
+
const NEW_MIAODA = "openclaw-extension-miaoda";
|
|
3018
|
+
const OLD_PLUGIN_NAMES = Object.freeze([
|
|
3019
|
+
"openclaw-feishu-greeting",
|
|
3020
|
+
"openclaw-miaoda-keepalive",
|
|
3021
|
+
"feishu-greeting",
|
|
3022
|
+
"miaoda-keepalive"
|
|
3023
|
+
]);
|
|
3024
|
+
function getPluginMaps(config) {
|
|
3025
|
+
const rawAllow = asRecord(config.plugins)?.allow;
|
|
3026
|
+
return {
|
|
3027
|
+
entries: getNestedMap(config, "plugins", "entries"),
|
|
3028
|
+
installs: getNestedMap(config, "plugins", "installs"),
|
|
3029
|
+
allow: Array.isArray(rawAllow) ? rawAllow : void 0
|
|
3030
|
+
};
|
|
3031
|
+
}
|
|
3032
|
+
function hasNewMiaoda({ entries, installs, allow }) {
|
|
3033
|
+
return asRecord(entries?.[NEW_MIAODA]) != null || asRecord(installs?.[NEW_MIAODA]) != null || (allow?.includes(NEW_MIAODA) ?? false);
|
|
3034
|
+
}
|
|
3035
|
+
function findResiduals({ entries, installs, allow }, extensionsDir) {
|
|
3036
|
+
return OLD_PLUGIN_NAMES.filter((name) => entries?.[name] != null || installs?.[name] != null || (allow?.includes(name) ?? false) || node_fs.default.existsSync(node_path.default.join(extensionsDir, name)));
|
|
3037
|
+
}
|
|
3038
|
+
let OldMiaodaPluginsCleanupRule = class OldMiaodaPluginsCleanupRule extends DiagnoseRule {
|
|
3039
|
+
validate(ctx) {
|
|
3040
|
+
const maps = getPluginMaps(ctx.config);
|
|
3041
|
+
if (!hasNewMiaoda(maps)) return { pass: true };
|
|
3042
|
+
const residuals = findResiduals(maps, getExtensionsDir(ctx.configPath));
|
|
3043
|
+
if (residuals.length === 0) return { pass: true };
|
|
3044
|
+
return {
|
|
3045
|
+
pass: false,
|
|
3046
|
+
message: "旧 miaoda 插件残留: " + residuals.sort().join(",")
|
|
3047
|
+
};
|
|
3048
|
+
}
|
|
3049
|
+
repair(ctx) {
|
|
3050
|
+
const maps = getPluginMaps(ctx.config);
|
|
3051
|
+
if (!hasNewMiaoda(maps)) return;
|
|
3052
|
+
const extensionsDir = getExtensionsDir(ctx.configPath);
|
|
3053
|
+
const { entries, installs, allow } = maps;
|
|
3054
|
+
const oldSet = new Set(OLD_PLUGIN_NAMES);
|
|
3055
|
+
if (allow) for (let i = allow.length - 1; i >= 0; i--) {
|
|
3056
|
+
const v = allow[i];
|
|
3057
|
+
if (typeof v === "string" && oldSet.has(v)) allow.splice(i, 1);
|
|
3058
|
+
}
|
|
3059
|
+
for (const name of OLD_PLUGIN_NAMES) {
|
|
3060
|
+
if (entries && name in entries) delete entries[name];
|
|
3061
|
+
if (installs && name in installs) delete installs[name];
|
|
3062
|
+
const target = node_path.default.join(extensionsDir, name);
|
|
3063
|
+
const rel = node_path.default.relative(extensionsDir, target);
|
|
3064
|
+
if (!rel || rel.startsWith("..") || node_path.default.isAbsolute(rel)) continue;
|
|
3065
|
+
try {
|
|
3066
|
+
node_fs.default.rmSync(target, {
|
|
3067
|
+
recursive: true,
|
|
3068
|
+
force: true
|
|
3069
|
+
});
|
|
3070
|
+
} catch (e) {
|
|
3071
|
+
console.error(`[old_miaoda_plugins_cleanup] rmSync ${target} failed: ${e.message}`);
|
|
3072
|
+
}
|
|
3073
|
+
}
|
|
3074
|
+
}
|
|
3075
|
+
};
|
|
3076
|
+
OldMiaodaPluginsCleanupRule = __decorate([Rule({
|
|
3077
|
+
key: "old_miaoda_plugins_cleanup",
|
|
3078
|
+
description: "当新版 openclaw-extension-miaoda 已存在时,清理过时插件引用(openclaw-feishu-greeting、openclaw-miaoda-keepalive 等)",
|
|
3079
|
+
dependsOn: ["config_syntax_check"],
|
|
3080
|
+
repairMode: "standard",
|
|
3081
|
+
level: "silent"
|
|
3082
|
+
})], OldMiaodaPluginsCleanupRule);
|
|
3083
|
+
//#endregion
|
|
3084
|
+
//#region src/rules/builtin-plugin-missing.ts
|
|
3085
|
+
/**
|
|
3086
|
+
* install-extension --all 安装的内置扩展插件列表(manifest role=extension)。
|
|
3087
|
+
* 与 miaoda-official-plugins-install-spec-unlock.ts 中的 OFFICIAL_PLUGIN_NAMES 保持一致。
|
|
3088
|
+
*/
|
|
3089
|
+
const BUILTIN_EXTENSION_PLUGINS = new Set([
|
|
3090
|
+
"openclaw-lark",
|
|
3091
|
+
"openclaw-extension-miaoda",
|
|
3092
|
+
"openclaw-extension-miaoda-coding",
|
|
3093
|
+
"openclaw-guardian-plugin",
|
|
3094
|
+
"openclaw-mem0-plugin"
|
|
3095
|
+
]);
|
|
3096
|
+
function findMissingBuiltinPlugins(ctx) {
|
|
3097
|
+
const extDir = getExtensionsDir(ctx.configPath);
|
|
3098
|
+
return [...BUILTIN_EXTENSION_PLUGINS].filter((name) => !isPluginInstalledOnDisk(extDir, name)).sort();
|
|
3099
|
+
}
|
|
3100
|
+
let BuiltinPluginMissingRule = class BuiltinPluginMissingRule extends DiagnoseRule {
|
|
3101
|
+
validate(ctx) {
|
|
3102
|
+
const missing = findMissingBuiltinPlugins(ctx);
|
|
2772
3103
|
if (missing.length === 0) return { pass: true };
|
|
2773
3104
|
return {
|
|
2774
3105
|
pass: false,
|
|
@@ -3588,7 +3919,7 @@ function extractScopedNameFromSpec(spec) {
|
|
|
3588
3919
|
const at = spec.indexOf("@", 1);
|
|
3589
3920
|
return at === -1 ? spec : spec.slice(0, at);
|
|
3590
3921
|
}
|
|
3591
|
-
function isLarkCliAvailable
|
|
3922
|
+
function isLarkCliAvailable() {
|
|
3592
3923
|
try {
|
|
3593
3924
|
return (0, node_child_process.spawnSync)(LARK_CLI_NAME$1, ["--version"], {
|
|
3594
3925
|
encoding: "utf-8",
|
|
@@ -3629,7 +3960,7 @@ function installLarkCliOnce(tag) {
|
|
|
3629
3960
|
let LarkCliMissingForInstalledLarkPluginRule = class LarkCliMissingForInstalledLarkPluginRule extends DiagnoseRule {
|
|
3630
3961
|
validate(ctx) {
|
|
3631
3962
|
if (!isTargetForkPlugin(readInstalledLarkPlugin(ctx))) return { pass: true };
|
|
3632
|
-
if (isLarkCliAvailable
|
|
3963
|
+
if (isLarkCliAvailable()) return { pass: true };
|
|
3633
3964
|
return {
|
|
3634
3965
|
pass: false,
|
|
3635
3966
|
message: `${FORK_PACKAGE_NAME}@${TARGET_VERSION} 已安装,但 lark-cli 不可用;将执行一次 lark-cli 安装`
|
|
@@ -3637,7 +3968,7 @@ let LarkCliMissingForInstalledLarkPluginRule = class LarkCliMissingForInstalledL
|
|
|
3637
3968
|
}
|
|
3638
3969
|
repair(ctx) {
|
|
3639
3970
|
if (!isTargetForkPlugin(readInstalledLarkPlugin(ctx))) return;
|
|
3640
|
-
if (isLarkCliAvailable
|
|
3971
|
+
if (isLarkCliAvailable()) return;
|
|
3641
3972
|
installLarkCliOnce(ctx.vars.recommendedOpenclawTag ?? TARGET_VERSION);
|
|
3642
3973
|
}
|
|
3643
3974
|
};
|
|
@@ -3650,117 +3981,6 @@ LarkCliMissingForInstalledLarkPluginRule = __decorate([Rule({
|
|
|
3650
3981
|
usesVars: ["recommendedOpenclawTag"]
|
|
3651
3982
|
})], LarkCliMissingForInstalledLarkPluginRule);
|
|
3652
3983
|
//#endregion
|
|
3653
|
-
//#region src/rules/feishu-bot-channel-config.ts
|
|
3654
|
-
/**
|
|
3655
|
-
* Ensures each bot account's channel config is correct:
|
|
3656
|
-
* 1. `allowFrom` contains its own `creatorOpenID` from larkApps
|
|
3657
|
-
* 2. `appSecret` is either the canonical provider-ref or matches larkApps plaintext
|
|
3658
|
-
*
|
|
3659
|
-
* Covers both multi-account (channels.feishu.accounts) and single-account
|
|
3660
|
-
* (channels.feishu.appId + allowFrom at top level) layouts.
|
|
3661
|
-
*/
|
|
3662
|
-
let FeishuBotChannelConfigRule = class FeishuBotChannelConfigRule extends DiagnoseRule {
|
|
3663
|
-
validate(ctx) {
|
|
3664
|
-
const larkApps = ctx.vars.larkApps;
|
|
3665
|
-
if (!larkApps || larkApps.length === 0) return { pass: true };
|
|
3666
|
-
const feishu = asRecord(getNestedMap(ctx.config, "channels", "feishu"));
|
|
3667
|
-
if (!feishu) return { pass: true };
|
|
3668
|
-
const issues = [];
|
|
3669
|
-
const accounts = asRecord(feishu.accounts);
|
|
3670
|
-
if (accounts) for (const [accountId, account] of Object.entries(accounts)) {
|
|
3671
|
-
const bot = asRecord(account);
|
|
3672
|
-
if (!bot) continue;
|
|
3673
|
-
const appId = bot.appId;
|
|
3674
|
-
if (typeof appId !== "string" || !appId.startsWith("cli_")) continue;
|
|
3675
|
-
const larkApp = larkApps.find((e) => e.larkAppID === appId);
|
|
3676
|
-
if (!larkApp) continue;
|
|
3677
|
-
this.checkBot(accountId, bot, larkApp, issues);
|
|
3678
|
-
}
|
|
3679
|
-
const singleAppId = feishu.appId;
|
|
3680
|
-
if (typeof singleAppId === "string" && singleAppId.startsWith("cli_") && !accounts) {
|
|
3681
|
-
const larkApp = larkApps.find((e) => e.larkAppID === singleAppId);
|
|
3682
|
-
if (larkApp) this.checkBot("feishu", feishu, larkApp, issues);
|
|
3683
|
-
}
|
|
3684
|
-
if (issues.length === 0) return { pass: true };
|
|
3685
|
-
return {
|
|
3686
|
-
pass: false,
|
|
3687
|
-
message: issues.join("; ")
|
|
3688
|
-
};
|
|
3689
|
-
}
|
|
3690
|
-
/** Check a single bot entry (either an account object or the feishu channel itself).
|
|
3691
|
-
* appSecret is validated based on its current type:
|
|
3692
|
-
* - object → must match canonical provider-ref
|
|
3693
|
-
* - string → must match larkApps plaintext
|
|
3694
|
-
*/
|
|
3695
|
-
checkBot(label, bot, larkApp, issues) {
|
|
3696
|
-
const creatorOpenID = larkApp.creatorOpenID;
|
|
3697
|
-
const allowFrom = Array.isArray(bot.allowFrom) ? bot.allowFrom : [];
|
|
3698
|
-
if (typeof creatorOpenID === "string" && creatorOpenID !== "") {
|
|
3699
|
-
if (!allowFrom.includes(creatorOpenID)) issues.push(`${label} allowFrom missing creatorOpenID ${creatorOpenID.length > 8 ? creatorOpenID.slice(0, 4) + "***" + creatorOpenID.slice(-4) : "***"}`);
|
|
3700
|
-
} else if (allowFrom.length === 0) issues.push(`${label} allowFrom is empty (creatorOpenID unavailable, cannot auto-fix)`);
|
|
3701
|
-
const secret = bot.appSecret;
|
|
3702
|
-
if (typeof secret === "object" && secret !== null && !Array.isArray(secret)) {
|
|
3703
|
-
if (!matchMap(secret, DEFAULT_FEISHU_APP_SECRET)) issues.push(`${label} appSecret is a provider-ref but not the canonical one`);
|
|
3704
|
-
} else if (typeof secret === "string") {
|
|
3705
|
-
if (secret !== larkApp.appSecret) issues.push(`${label} appSecret plaintext mismatch`);
|
|
3706
|
-
} else issues.push(`${label} appSecret has unexpected type ${typeof secret}`);
|
|
3707
|
-
}
|
|
3708
|
-
repair(ctx) {
|
|
3709
|
-
const larkApps = ctx.vars.larkApps;
|
|
3710
|
-
if (!larkApps || larkApps.length === 0) return;
|
|
3711
|
-
const feishu = asRecord(getNestedMap(ctx.config, "channels", "feishu"));
|
|
3712
|
-
if (!feishu) return;
|
|
3713
|
-
const accounts = asRecord(feishu.accounts);
|
|
3714
|
-
if (accounts) for (const [, account] of Object.entries(accounts)) {
|
|
3715
|
-
const bot = asRecord(account);
|
|
3716
|
-
if (!bot) continue;
|
|
3717
|
-
const appId = bot.appId;
|
|
3718
|
-
if (typeof appId !== "string" || !appId.startsWith("cli_")) continue;
|
|
3719
|
-
const larkApp = larkApps.find((e) => e.larkAppID === appId);
|
|
3720
|
-
if (!larkApp) continue;
|
|
3721
|
-
this.fixBot(bot, larkApp);
|
|
3722
|
-
}
|
|
3723
|
-
const singleAppId = feishu.appId;
|
|
3724
|
-
if (typeof singleAppId === "string" && singleAppId.startsWith("cli_") && !accounts) {
|
|
3725
|
-
const larkApp = larkApps.find((e) => e.larkAppID === singleAppId);
|
|
3726
|
-
if (larkApp) this.fixBot(feishu, larkApp);
|
|
3727
|
-
}
|
|
3728
|
-
}
|
|
3729
|
-
/** Fix a single bot entry in-place.
|
|
3730
|
-
* appSecret is repaired based on its current type:
|
|
3731
|
-
* - object → fix to canonical provider-ref
|
|
3732
|
-
* - string → fix to larkApps plaintext
|
|
3733
|
-
*/
|
|
3734
|
-
fixBot(bot, larkApp) {
|
|
3735
|
-
const creatorOpenID = larkApp.creatorOpenID;
|
|
3736
|
-
if (typeof creatorOpenID === "string" && creatorOpenID !== "") {
|
|
3737
|
-
const allowFrom = Array.isArray(bot.allowFrom) ? [...bot.allowFrom] : [];
|
|
3738
|
-
if (!allowFrom.includes(creatorOpenID)) {
|
|
3739
|
-
allowFrom.push(creatorOpenID);
|
|
3740
|
-
bot.allowFrom = allowFrom;
|
|
3741
|
-
}
|
|
3742
|
-
}
|
|
3743
|
-
const secret = bot.appSecret;
|
|
3744
|
-
if (typeof secret === "object" && secret !== null && !Array.isArray(secret)) {
|
|
3745
|
-
if (!matchMap(secret, DEFAULT_FEISHU_APP_SECRET)) bot.appSecret = { ...DEFAULT_FEISHU_APP_SECRET };
|
|
3746
|
-
} else if (typeof secret === "string") {
|
|
3747
|
-
if (secret !== larkApp.appSecret) bot.appSecret = larkApp.appSecret;
|
|
3748
|
-
}
|
|
3749
|
-
}
|
|
3750
|
-
};
|
|
3751
|
-
FeishuBotChannelConfigRule = __decorate([Rule({
|
|
3752
|
-
key: "feishu_bot_channel_config",
|
|
3753
|
-
description: "确保飞书配置中 bot 账号的 allowFrom 包含其创建者 openID 且 appSecret 值正确",
|
|
3754
|
-
dependsOn: [
|
|
3755
|
-
"config_syntax_check",
|
|
3756
|
-
"feishu_default_account",
|
|
3757
|
-
"feishu_bot_id"
|
|
3758
|
-
],
|
|
3759
|
-
repairMode: "standard",
|
|
3760
|
-
usesVars: ["larkApps"],
|
|
3761
|
-
level: "critical"
|
|
3762
|
-
})], FeishuBotChannelConfigRule);
|
|
3763
|
-
//#endregion
|
|
3764
3984
|
//#region src/check.ts
|
|
3765
3985
|
/** Telemetry-aware entry: returns both the legacy CheckResult (for stdout)
|
|
3766
3986
|
* AND a DoctorReport-shape payload (for `openclaw.report_cli_run`). The
|
|
@@ -4201,32 +4421,8 @@ function finalize$1(results, aborted) {
|
|
|
4201
4421
|
};
|
|
4202
4422
|
}
|
|
4203
4423
|
//#endregion
|
|
4204
|
-
//#region src/
|
|
4205
|
-
|
|
4206
|
-
* Central directory for all ephemeral diagnose/reset artifacts: task status
|
|
4207
|
-
* files (`reset-<taskId>.json`) and human-readable step logs
|
|
4208
|
-
* (`reset-<taskId>.log`). Having everything under one dir makes debugging a
|
|
4209
|
-
* stuck reset much easier — `ls /tmp/openclaw-diagnose/` shows every recent
|
|
4210
|
-
* run, and each run's log is right next to its state.
|
|
4211
|
-
*/
|
|
4212
|
-
const DIAGNOSE_DIR = "/tmp/openclaw-diagnose";
|
|
4213
|
-
function resetResultFile(taskId) {
|
|
4214
|
-
return `${DIAGNOSE_DIR}/reset-${taskId}.json`;
|
|
4215
|
-
}
|
|
4216
|
-
function resetLogFile(taskId) {
|
|
4217
|
-
return `${DIAGNOSE_DIR}/reset-${taskId}.log`;
|
|
4218
|
-
}
|
|
4219
|
-
/** Sandbox workspace root where openclaw config + agent state lives. */
|
|
4220
|
-
const WORKSPACE_DIR = "/home/gem/workspace/agent";
|
|
4221
|
-
/** File containing the provider key used by the openclaw miaoda provider. */
|
|
4222
|
-
const PROVIDER_FILE_PATH = "/home/gem/workspace/.force/openclaw/miaoda-provider-key";
|
|
4223
|
-
/** File containing the miaoda openclaw secrets JSON. */
|
|
4224
|
-
const SECRETS_FILE_PATH = "/home/gem/workspace/.force/openclaw/miaoda-openclaw-secrets.json";
|
|
4225
|
-
/** Absolute path to the openclaw config JSON. */
|
|
4226
|
-
const CONFIG_PATH = `${WORKSPACE_DIR}/openclaw.json`;
|
|
4227
|
-
//#endregion
|
|
4228
|
-
//#region src/run-log.ts
|
|
4229
|
-
let currentRunContext;
|
|
4424
|
+
//#region src/run-log.ts
|
|
4425
|
+
let currentRunContext;
|
|
4230
4426
|
/**
|
|
4231
4427
|
* Install the run context for this CLI execution. Must be called once at
|
|
4232
4428
|
* the top of `main()` before any subcommand work; idempotent (last call
|
|
@@ -4360,10 +4556,9 @@ function makeLogger(logFile) {
|
|
|
4360
4556
|
/**
|
|
4361
4557
|
* Start an async reset task: spawn a detached child process and return the taskId.
|
|
4362
4558
|
*
|
|
4363
|
-
* The child process runs: node cli.js reset --worker --task-id=xxx
|
|
4364
|
-
* The worker fetches ctx from innerApi itself — no --ctx passthrough.
|
|
4559
|
+
* The child process runs: node cli.js reset --worker --task-id=xxx --ctx=base64
|
|
4365
4560
|
*/
|
|
4366
|
-
function startAsyncReset() {
|
|
4561
|
+
function startAsyncReset(ctxBase64) {
|
|
4367
4562
|
const taskId = (0, node_crypto.randomUUID)();
|
|
4368
4563
|
const resultFile = resetResultFile(taskId);
|
|
4369
4564
|
const log = makeLogger(resetLogFile(taskId));
|
|
@@ -4387,7 +4582,8 @@ function startAsyncReset() {
|
|
|
4387
4582
|
process.argv[1],
|
|
4388
4583
|
"reset",
|
|
4389
4584
|
"--worker",
|
|
4390
|
-
`--task-id=${taskId}
|
|
4585
|
+
`--task-id=${taskId}`,
|
|
4586
|
+
`--ctx=${ctxBase64}`
|
|
4391
4587
|
], {
|
|
4392
4588
|
detached: true,
|
|
4393
4589
|
stdio: "ignore",
|
|
@@ -4505,557 +4701,297 @@ function describeCause(c, depth = 0) {
|
|
|
4505
4701
|
const head = code ? `${code} ${c.message}` : c.message;
|
|
4506
4702
|
const inner = describeCause(c.cause, depth + 1);
|
|
4507
4703
|
return inner ? `${head} <- ${inner}` : head;
|
|
4508
|
-
}
|
|
4509
|
-
return String(c);
|
|
4510
|
-
}
|
|
4511
|
-
/** Strip query string — OSS signed URLs put the auth token there. Keeps
|
|
4512
|
-
* protocol/host/path so the operator can tell *which* OSS bucket / object
|
|
4513
|
-
* was being fetched. */
|
|
4514
|
-
function redactUrl(raw) {
|
|
4515
|
-
try {
|
|
4516
|
-
const u = new URL(raw);
|
|
4517
|
-
const tail = u.search ? "?<redacted>" : "";
|
|
4518
|
-
return `${u.protocol}//${u.host}${u.pathname}${tail}`;
|
|
4519
|
-
} catch {
|
|
4520
|
-
return "<invalid-url>";
|
|
4521
|
-
}
|
|
4522
|
-
}
|
|
4523
|
-
function originOf(raw) {
|
|
4524
|
-
try {
|
|
4525
|
-
return new URL(raw).host;
|
|
4526
|
-
} catch {
|
|
4527
|
-
return "unknown-host";
|
|
4528
|
-
}
|
|
4529
|
-
}
|
|
4530
|
-
//#endregion
|
|
4531
|
-
//#region src/oss/fetchManifest.ts
|
|
4532
|
-
const MANIFEST_PREFIX = "builtin/manifests/openclaw/recommended/";
|
|
4533
|
-
const MANIFEST_SUFFIX = ".json";
|
|
4534
|
-
const manifestCache = /* @__PURE__ */ new Map();
|
|
4535
|
-
async function fetchManifest(ossFileMap, tag) {
|
|
4536
|
-
const key = `${MANIFEST_PREFIX}${tag}${MANIFEST_SUFFIX}`;
|
|
4537
|
-
const url = ossFileMap[key];
|
|
4538
|
-
if (!url) {
|
|
4539
|
-
const available = Object.keys(ossFileMap).filter((k) => k.startsWith(MANIFEST_PREFIX) && k.endsWith(MANIFEST_SUFFIX)).map((k) => k.slice(39, -5));
|
|
4540
|
-
const availStr = available.length ? available.join(", ") : "(none)";
|
|
4541
|
-
throw new Error(`manifest signed URL missing for tag "${tag}" (key ${key}). Available tags in ossFileMap: ${availStr}. Either pass an available tag or update the studio_server TCC openclaw_upgrade_config supported_versions.`);
|
|
4542
|
-
}
|
|
4543
|
-
const cached = manifestCache.get(url);
|
|
4544
|
-
if (cached) return cached;
|
|
4545
|
-
const label = `openclaw manifest tag=${tag}`;
|
|
4546
|
-
const res = await fetchWithDiag(url, { label });
|
|
4547
|
-
if (!res.ok) {
|
|
4548
|
-
const body = await readPreview(res);
|
|
4549
|
-
throw new Error(`${label}: HTTP ${res.status} ${res.statusText}` + (body ? ` body=${body}` : ""));
|
|
4550
|
-
}
|
|
4551
|
-
const manifest = await res.json();
|
|
4552
|
-
manifestCache.set(url, manifest);
|
|
4553
|
-
return manifest;
|
|
4554
|
-
}
|
|
4555
|
-
/** Best-effort body preview for HTTP-error diagnostics. OSS errors are
|
|
4556
|
-
* XML; first 256 chars usually contain the <Code> + <Message> we care
|
|
4557
|
-
* about. Failures here are swallowed (logging-only, not load-bearing). */
|
|
4558
|
-
async function readPreview(res) {
|
|
4559
|
-
try {
|
|
4560
|
-
return (await res.text()).slice(0, 256);
|
|
4561
|
-
} catch {
|
|
4562
|
-
return "";
|
|
4563
|
-
}
|
|
4564
|
-
}
|
|
4565
|
-
async function downloadWithCache(pkg, ossFileMap, opts = {}) {
|
|
4566
|
-
const cacheRoot = opts.cacheRoot ?? "/tmp/openclaw-diagnose/resources";
|
|
4567
|
-
const shortHash = pkg.shasum.slice(0, 16);
|
|
4568
|
-
const destDir = node_path.default.join(cacheRoot, shortHash);
|
|
4569
|
-
const destFile = node_path.default.join(destDir, node_path.default.posix.basename(pkg.ossKey));
|
|
4570
|
-
node_fs.default.mkdirSync(destDir, { recursive: true });
|
|
4571
|
-
if (node_fs.default.existsSync(destFile)) return destFile;
|
|
4572
|
-
const url = ossFileMap[pkg.ossKey];
|
|
4573
|
-
if (!url) throw new Error(`signed URL missing for ${pkg.ossKey}`);
|
|
4574
|
-
if (!pkg.integrity.startsWith("sha512-")) throw new Error(`unsupported integrity format: ${pkg.integrity}`);
|
|
4575
|
-
const expected = pkg.integrity.slice(7);
|
|
4576
|
-
const tmpFile = node_path.default.join(destDir, `.tmp.${process.pid}.${node_crypto.default.randomBytes(4).toString("hex")}`);
|
|
4577
|
-
try {
|
|
4578
|
-
const label = `download ${pkg.ossKey}`;
|
|
4579
|
-
const res = await fetchWithDiag(url, {
|
|
4580
|
-
label,
|
|
4581
|
-
timeoutMs: 6e4
|
|
4582
|
-
});
|
|
4583
|
-
if (!res.ok) {
|
|
4584
|
-
let preview = "";
|
|
4585
|
-
try {
|
|
4586
|
-
preview = (await res.text()).slice(0, 256);
|
|
4587
|
-
} catch {}
|
|
4588
|
-
throw new Error(`${label}: HTTP ${res.status} ${res.statusText}` + (preview ? ` body=${preview}` : ""));
|
|
4589
|
-
}
|
|
4590
|
-
if (!res.body) throw new Error(`${label}: empty body`);
|
|
4591
|
-
const hasher = node_crypto.default.createHash("sha512");
|
|
4592
|
-
const source = node_stream.Readable.fromWeb(res.body);
|
|
4593
|
-
async function* teeAndHash(src) {
|
|
4594
|
-
for await (const chunk of src) {
|
|
4595
|
-
hasher.update(chunk);
|
|
4596
|
-
yield chunk;
|
|
4597
|
-
}
|
|
4598
|
-
}
|
|
4599
|
-
await (0, node_stream_promises.pipeline)(source, teeAndHash, node_fs.default.createWriteStream(tmpFile));
|
|
4600
|
-
const actual = hasher.digest("base64");
|
|
4601
|
-
if (actual !== expected) {
|
|
4602
|
-
const envBypass = process.env.OPENCLAW_DEBUG_SKIP_INTEGRITY === "1";
|
|
4603
|
-
if (opts.skipIntegrity || envBypass) {
|
|
4604
|
-
const sourceLabel = opts.skipIntegrity ? "skipIntegrity=true" : "OPENCLAW_DEBUG_SKIP_INTEGRITY=1";
|
|
4605
|
-
console.error(`⚠ [downloadWithCache] INTEGRITY BYPASS for ${pkg.ossKey}: expected ${expected.slice(0, 12)}… got ${actual.slice(0, 12)}… — ${sourceLabel}. DO NOT use this flag in production.`);
|
|
4606
|
-
} else throw new Error(`integrity mismatch for ${pkg.ossKey}: expected ${expected} got ${actual}`);
|
|
4607
|
-
}
|
|
4608
|
-
moveSafe(tmpFile, destFile);
|
|
4609
|
-
return destFile;
|
|
4610
|
-
} catch (e) {
|
|
4611
|
-
try {
|
|
4612
|
-
node_fs.default.unlinkSync(tmpFile);
|
|
4613
|
-
} catch {}
|
|
4614
|
-
throw e;
|
|
4615
|
-
}
|
|
4616
|
-
}
|
|
4617
|
-
//#endregion
|
|
4618
|
-
//#region src/install-openclaw.ts
|
|
4619
|
-
async function installOpenclaw(openclawTag, ossFileMap, opts = {}) {
|
|
4620
|
-
const homeBase = resolveHomeBase(opts.homeBase);
|
|
4621
|
-
const t0 = Date.now();
|
|
4622
|
-
const pkg = (await fetchManifest(ossFileMap, openclawTag)).packages.find((p) => p.role === "cli" && p.name === "openclaw");
|
|
4623
|
-
if (!pkg) throw new Error("install-openclaw: role=cli,name=openclaw not found in manifest");
|
|
4624
|
-
const targetDir = opts.targetDir ?? node_path.default.join(homeBase, pkg.installPath);
|
|
4625
|
-
const bakDir = targetDir + ".bak";
|
|
4626
|
-
const newDir = targetDir + ".new";
|
|
4627
|
-
const tarball = await downloadWithCache(pkg, ossFileMap, opts);
|
|
4628
|
-
console.error(`[install-openclaw] tag=${openclawTag} shasum=${pkg.shasum.slice(0, 12)}...`);
|
|
4629
|
-
if (node_fs.default.existsSync(newDir)) node_fs.default.rmSync(newDir, {
|
|
4630
|
-
recursive: true,
|
|
4631
|
-
force: true
|
|
4632
|
-
});
|
|
4633
|
-
if (node_fs.default.existsSync(bakDir)) node_fs.default.rmSync(bakDir, {
|
|
4634
|
-
recursive: true,
|
|
4635
|
-
force: true
|
|
4636
|
-
});
|
|
4637
|
-
node_fs.default.mkdirSync(node_path.default.dirname(targetDir), { recursive: true });
|
|
4638
|
-
const tmpStage = node_fs.default.mkdtempSync(node_path.default.join(opts.tmpRoot ?? node_os.default.tmpdir(), "openclaw-install-"));
|
|
4639
|
-
try {
|
|
4640
|
-
extractTarballTolerant(tarball, tmpStage, { stripComponents: 1 });
|
|
4641
|
-
if (!node_fs.default.existsSync(node_path.default.join(tmpStage, "package.json"))) throw new Error("extracted tarball missing package.json");
|
|
4642
|
-
moveSafe(tmpStage, newDir);
|
|
4643
|
-
const hadExisting = node_fs.default.existsSync(targetDir);
|
|
4644
|
-
try {
|
|
4645
|
-
if (hadExisting) moveSafe(targetDir, bakDir);
|
|
4646
|
-
moveSafe(newDir, targetDir);
|
|
4647
|
-
} catch (e) {
|
|
4648
|
-
if (hadExisting && !node_fs.default.existsSync(targetDir) && node_fs.default.existsSync(bakDir)) try {
|
|
4649
|
-
moveSafe(bakDir, targetDir);
|
|
4650
|
-
} catch {}
|
|
4651
|
-
try {
|
|
4652
|
-
node_fs.default.rmSync(newDir, {
|
|
4653
|
-
recursive: true,
|
|
4654
|
-
force: true
|
|
4655
|
-
});
|
|
4656
|
-
} catch {}
|
|
4657
|
-
throw e;
|
|
4658
|
-
}
|
|
4659
|
-
if (hadExisting && node_fs.default.existsSync(bakDir)) node_fs.default.rmSync(bakDir, {
|
|
4660
|
-
recursive: true,
|
|
4661
|
-
force: true
|
|
4662
|
-
});
|
|
4663
|
-
} finally {
|
|
4664
|
-
if (node_fs.default.existsSync(tmpStage)) try {
|
|
4665
|
-
node_fs.default.rmSync(tmpStage, {
|
|
4666
|
-
recursive: true,
|
|
4667
|
-
force: true
|
|
4668
|
-
});
|
|
4669
|
-
} catch {}
|
|
4670
|
-
}
|
|
4671
|
-
console.error(`[install-openclaw] done in ${Date.now() - t0}ms`);
|
|
4672
|
-
}
|
|
4673
|
-
async function installExtension(tag, ossFileMap, opts = {}) {
|
|
4674
|
-
const homeBase = resolveHomeBase(opts.homeBase);
|
|
4675
|
-
const hasAll = !!opts.all;
|
|
4676
|
-
const hasNames = (opts.names?.length ?? 0) > 0;
|
|
4677
|
-
if (hasAll && hasNames) throw new Error("install-extension: --all and --extension are mutually exclusive");
|
|
4678
|
-
if (!hasAll && !hasNames) throw new Error("install-extension: must provide --all or --extension=<name>");
|
|
4679
|
-
const allExts = (await fetchManifest(ossFileMap, tag)).packages.filter((p) => p.role === "extension");
|
|
4680
|
-
let targets;
|
|
4681
|
-
if (hasAll) targets = allExts;
|
|
4682
|
-
else {
|
|
4683
|
-
const wanted = new Set(opts.names);
|
|
4684
|
-
targets = allExts.filter((p) => wanted.has(p.name) || p.packageName != null && wanted.has(p.packageName));
|
|
4685
|
-
const foundKeys = /* @__PURE__ */ new Set();
|
|
4686
|
-
for (const t of targets) {
|
|
4687
|
-
foundKeys.add(t.name);
|
|
4688
|
-
if (t.packageName) foundKeys.add(t.packageName);
|
|
4689
|
-
}
|
|
4690
|
-
const missing = opts.names.filter((n) => !foundKeys.has(n));
|
|
4691
|
-
if (missing.length > 0) throw new Error(`install-extension: not found in manifest: ${missing.join(", ")}`);
|
|
4692
|
-
}
|
|
4693
|
-
console.error(`[install-extension] tag=${tag} targets=${targets.length}`);
|
|
4694
|
-
const t0 = Date.now();
|
|
4695
|
-
const tarballs = await Promise.all(targets.map(async (p) => {
|
|
4696
|
-
const tb = await downloadWithCache(p, ossFileMap, opts);
|
|
4697
|
-
console.error(`[install-extension] ${p.name}: downloaded`);
|
|
4698
|
-
return {
|
|
4699
|
-
pkg: p,
|
|
4700
|
-
tarball: tb
|
|
4701
|
-
};
|
|
4702
|
-
}));
|
|
4703
|
-
for (const { pkg, tarball } of tarballs) {
|
|
4704
|
-
installOne$1(pkg, tarball, homeBase);
|
|
4705
|
-
console.error(`[install-extension] ${pkg.name}: installed`);
|
|
4706
|
-
}
|
|
4707
|
-
if (!opts.skipConfigUpdate) updatePluginInstalls(opts.configPath ?? node_path.default.join(homeBase, "workspace/agent/openclaw.json"), targets);
|
|
4708
|
-
else console.error(`[install-extension] skipConfigUpdate=true — not touching openclaw.json`);
|
|
4709
|
-
console.error(`[install-extension] done ${targets.length}/${targets.length} in ${Date.now() - t0}ms`);
|
|
4710
|
-
}
|
|
4711
|
-
const MEM0_PLUGIN_NAME = "openclaw-mem0-plugin";
|
|
4712
|
-
const PLUGINS_TO_AUTO_ENABLE = ["openclaw-lark", "openclaw-extension-miaoda"];
|
|
4713
|
-
/**
|
|
4714
|
-
* Merge each installed extension's installMetadata into openclaw.json's
|
|
4715
|
-
* plugins.installs[<pkg.name>]. Atomic write via tmp + rename.
|
|
4716
|
-
*
|
|
4717
|
-
* - No openclaw.json → log + return (not an error; some install contexts don't have it yet)
|
|
4718
|
-
* - Extension without installMetadata in manifest → skip that entry (log)
|
|
4719
|
-
* - Existing plugins.installs entries for other extensions left untouched
|
|
4720
|
-
*
|
|
4721
|
-
* Special-case for mem0: when openclaw-mem0-plugin is among the installed
|
|
4722
|
-
* targets, also append it to plugins.allow (idempotent) and seed
|
|
4723
|
-
* plugins.entries.openclaw-mem0-plugin = { enabled: false } when the key
|
|
4724
|
-
* is absent. Existing values are preserved (user toggles survive).
|
|
4725
|
-
*/
|
|
4726
|
-
function updatePluginInstalls(configPath, installedPkgs) {
|
|
4727
|
-
if (!node_fs.default.existsSync(configPath)) {
|
|
4728
|
-
console.error(`[install-extension] no config at ${configPath} — skip plugins.installs update`);
|
|
4729
|
-
return;
|
|
4730
|
-
}
|
|
4731
|
-
const JSON5 = loadJSON5();
|
|
4732
|
-
const raw = node_fs.default.readFileSync(configPath, "utf-8");
|
|
4733
|
-
const config = JSON5.parse(raw);
|
|
4734
|
-
if (!config.plugins || typeof config.plugins !== "object") config.plugins = {};
|
|
4735
|
-
const plugins = config.plugins;
|
|
4736
|
-
if (!plugins.installs || typeof plugins.installs !== "object") plugins.installs = {};
|
|
4737
|
-
const installs = plugins.installs;
|
|
4738
|
-
let updated = 0;
|
|
4739
|
-
let skipped = 0;
|
|
4740
|
-
for (const pkg of installedPkgs) if (pkg.installMetadata) {
|
|
4741
|
-
installs[pkg.name] = pkg.installMetadata;
|
|
4742
|
-
updated++;
|
|
4743
|
-
} else skipped++;
|
|
4744
|
-
if (installedPkgs.some((p) => p.name === MEM0_PLUGIN_NAME)) {
|
|
4745
|
-
const allowRaw = plugins.allow;
|
|
4746
|
-
const allow = Array.isArray(allowRaw) ? allowRaw : [];
|
|
4747
|
-
if (!allow.filter((e) => typeof e === "string").includes(MEM0_PLUGIN_NAME)) allow.push(MEM0_PLUGIN_NAME);
|
|
4748
|
-
plugins.allow = allow;
|
|
4749
|
-
if (!plugins.entries || typeof plugins.entries !== "object" || Array.isArray(plugins.entries)) plugins.entries = {};
|
|
4750
|
-
const entries = plugins.entries;
|
|
4751
|
-
if (!(MEM0_PLUGIN_NAME in entries)) entries[MEM0_PLUGIN_NAME] = { enabled: false };
|
|
4752
|
-
}
|
|
4753
|
-
for (const pkg of installedPkgs) {
|
|
4754
|
-
if (!PLUGINS_TO_AUTO_ENABLE.includes(pkg.name)) continue;
|
|
4755
|
-
if (!Array.isArray(plugins.allow)) plugins.allow = [];
|
|
4756
|
-
const allow = plugins.allow;
|
|
4757
|
-
if (!allow.includes(pkg.name)) allow.push(pkg.name);
|
|
4758
|
-
if (!plugins.entries || typeof plugins.entries !== "object" || Array.isArray(plugins.entries)) plugins.entries = {};
|
|
4759
|
-
const entries = plugins.entries;
|
|
4760
|
-
entries[pkg.name] = {
|
|
4761
|
-
...asRecord(entries[pkg.name]) ?? {},
|
|
4762
|
-
enabled: true
|
|
4763
|
-
};
|
|
4764
|
-
}
|
|
4765
|
-
const tmpPath = configPath + ".installs-tmp";
|
|
4766
|
-
node_fs.default.writeFileSync(tmpPath, JSON.stringify(config, null, 2), "utf-8");
|
|
4767
|
-
moveSafe(tmpPath, configPath);
|
|
4768
|
-
console.error(`[install-extension] plugins.installs updated: ${updated} entry(ies) in ${configPath}` + (skipped > 0 ? ` (${skipped} package(s) without installMetadata skipped)` : ""));
|
|
4769
|
-
}
|
|
4770
|
-
function installOne$1(pkg, tarball, homeBase) {
|
|
4771
|
-
const destDir = node_path.default.join(homeBase, pkg.installPath);
|
|
4772
|
-
const stagingDir = destDir + ".new";
|
|
4773
|
-
const oldDir = destDir + ".old";
|
|
4774
|
-
node_fs.default.mkdirSync(node_path.default.dirname(destDir), { recursive: true });
|
|
4775
|
-
if (node_fs.default.existsSync(stagingDir)) node_fs.default.rmSync(stagingDir, {
|
|
4776
|
-
recursive: true,
|
|
4777
|
-
force: true
|
|
4778
|
-
});
|
|
4779
|
-
node_fs.default.mkdirSync(stagingDir);
|
|
4780
|
-
try {
|
|
4781
|
-
extractTarballTolerant(tarball, stagingDir, { stripComponents: 1 });
|
|
4782
|
-
if (!node_fs.default.existsSync(node_path.default.join(stagingDir, "package.json"))) throw new Error(`extension tarball missing package.json: ${pkg.name}`);
|
|
4783
|
-
} catch (e) {
|
|
4784
|
-
try {
|
|
4785
|
-
node_fs.default.rmSync(stagingDir, {
|
|
4786
|
-
recursive: true,
|
|
4787
|
-
force: true
|
|
4788
|
-
});
|
|
4789
|
-
} catch {}
|
|
4790
|
-
throw e;
|
|
4791
|
-
}
|
|
4792
|
-
const hadOld = node_fs.default.existsSync(destDir);
|
|
4793
|
-
if (hadOld) moveSafe(destDir, oldDir);
|
|
4794
|
-
moveSafe(stagingDir, destDir);
|
|
4795
|
-
if (hadOld && node_fs.default.existsSync(oldDir)) node_fs.default.rmSync(oldDir, {
|
|
4796
|
-
recursive: true,
|
|
4797
|
-
force: true
|
|
4798
|
-
});
|
|
4799
|
-
}
|
|
4800
|
-
//#endregion
|
|
4801
|
-
//#region src/lark-cli-init.ts
|
|
4802
|
-
const LARK_PLUGIN_NAMES = ["openclaw-lark", "feishu-openclaw-plugin"];
|
|
4803
|
-
const PE_XML_TAG = "lark-cli-pe";
|
|
4804
|
-
const PE_PLACEHOLDER = `
|
|
4805
|
-
<${PE_XML_TAG}>
|
|
4806
|
-
**【强制要求 - 无例外】飞书所有操作必须先读 feishu-lark-cli skill。未读skill不得调用任何飞书工具,违者视为操作失误。**
|
|
4807
|
-
</${PE_XML_TAG}>
|
|
4808
|
-
`;
|
|
4809
|
-
function isLarkPluginInstalled(configPath) {
|
|
4810
|
-
const extDir = getExtensionsDir(configPath);
|
|
4811
|
-
return LARK_PLUGIN_NAMES.some((name) => {
|
|
4812
|
-
try {
|
|
4813
|
-
return node_fs.default.existsSync(node_path.default.join(extDir, name, "package.json"));
|
|
4814
|
-
} catch {
|
|
4815
|
-
return false;
|
|
4816
|
-
}
|
|
4817
|
-
});
|
|
4704
|
+
}
|
|
4705
|
+
return String(c);
|
|
4818
4706
|
}
|
|
4819
|
-
|
|
4707
|
+
/** Strip query string — OSS signed URLs put the auth token there. Keeps
|
|
4708
|
+
* protocol/host/path so the operator can tell *which* OSS bucket / object
|
|
4709
|
+
* was being fetched. */
|
|
4710
|
+
function redactUrl(raw) {
|
|
4820
4711
|
try {
|
|
4821
|
-
|
|
4822
|
-
|
|
4823
|
-
|
|
4824
|
-
stdio: [
|
|
4825
|
-
"ignore",
|
|
4826
|
-
"pipe",
|
|
4827
|
-
"ignore"
|
|
4828
|
-
]
|
|
4829
|
-
}).status === 0;
|
|
4712
|
+
const u = new URL(raw);
|
|
4713
|
+
const tail = u.search ? "?<redacted>" : "";
|
|
4714
|
+
return `${u.protocol}//${u.host}${u.pathname}${tail}`;
|
|
4830
4715
|
} catch {
|
|
4831
|
-
return
|
|
4716
|
+
return "<invalid-url>";
|
|
4832
4717
|
}
|
|
4833
4718
|
}
|
|
4834
|
-
function
|
|
4719
|
+
function originOf(raw) {
|
|
4835
4720
|
try {
|
|
4836
|
-
|
|
4837
|
-
const parsed = loadJSON5().parse(raw);
|
|
4838
|
-
return parsed != null && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
|
|
4721
|
+
return new URL(raw).host;
|
|
4839
4722
|
} catch {
|
|
4840
|
-
return
|
|
4841
|
-
}
|
|
4842
|
-
}
|
|
4843
|
-
/**
|
|
4844
|
-
* Resolve the feishu app secret for the given appId.
|
|
4845
|
-
*
|
|
4846
|
-
* Lookup order:
|
|
4847
|
-
* 1. channels.feishu.appSecret (single-agent: feishu.appId === appId)
|
|
4848
|
-
* 2. channels.feishu.accounts[key].appSecret (multi-agent: account.appId === appId)
|
|
4849
|
-
*
|
|
4850
|
-
* Value interpretation:
|
|
4851
|
-
* - string → use directly
|
|
4852
|
-
* - object → secret is managed by a provider; use `feishuAppSecret` param instead
|
|
4853
|
-
*
|
|
4854
|
-
* Returns null when the secret cannot be determined.
|
|
4855
|
-
*/
|
|
4856
|
-
function resolveAppSecret(appId, config, feishuAppSecret) {
|
|
4857
|
-
const feishu = getNestedMap(config, "channels", "feishu");
|
|
4858
|
-
if (!feishu) return null;
|
|
4859
|
-
let rawSecret;
|
|
4860
|
-
if (typeof feishu.appId === "string" && feishu.appId === appId) rawSecret = feishu.appSecret;
|
|
4861
|
-
else {
|
|
4862
|
-
const accounts = asRecord(feishu.accounts);
|
|
4863
|
-
if (accounts) for (const [, val] of Object.entries(accounts)) {
|
|
4864
|
-
const account = asRecord(val);
|
|
4865
|
-
if (account?.appId === appId) {
|
|
4866
|
-
rawSecret = account.appSecret ?? feishu.appSecret;
|
|
4867
|
-
break;
|
|
4868
|
-
}
|
|
4869
|
-
}
|
|
4723
|
+
return "unknown-host";
|
|
4870
4724
|
}
|
|
4871
|
-
if (typeof rawSecret === "string" && rawSecret) return rawSecret;
|
|
4872
|
-
if (rawSecret != null && typeof rawSecret === "object") return feishuAppSecret ?? null;
|
|
4873
|
-
return null;
|
|
4874
4725
|
}
|
|
4875
|
-
|
|
4876
|
-
|
|
4877
|
-
|
|
4878
|
-
|
|
4879
|
-
|
|
4880
|
-
|
|
4881
|
-
|
|
4882
|
-
|
|
4883
|
-
|
|
4884
|
-
|
|
4885
|
-
|
|
4886
|
-
|
|
4887
|
-
* Returns null when the path cannot be determined.
|
|
4888
|
-
*/
|
|
4889
|
-
function resolveAgentsMdPath(appId, config) {
|
|
4890
|
-
const feishu = getNestedMap(config, "channels", "feishu");
|
|
4891
|
-
if (!feishu) {
|
|
4892
|
-
console.error("resolveAgentsMdPath: channels.feishu not found");
|
|
4893
|
-
return null;
|
|
4894
|
-
}
|
|
4895
|
-
if (typeof feishu.appId === "string" && feishu.appId === appId) {
|
|
4896
|
-
console.error(`resolveAgentsMdPath: case=single-agent feishu.appId=${feishu.appId}`);
|
|
4897
|
-
return node_path.default.join(WORKSPACE_DIR, "workspace", "AGENTS.md");
|
|
4898
|
-
}
|
|
4899
|
-
const accounts = asRecord(feishu.accounts);
|
|
4900
|
-
if (!accounts) {
|
|
4901
|
-
console.error("resolveAgentsMdPath: feishu.accounts not found");
|
|
4902
|
-
return null;
|
|
4726
|
+
//#endregion
|
|
4727
|
+
//#region src/oss/fetchManifest.ts
|
|
4728
|
+
const MANIFEST_PREFIX = "builtin/manifests/openclaw/recommended/";
|
|
4729
|
+
const MANIFEST_SUFFIX = ".json";
|
|
4730
|
+
const manifestCache = /* @__PURE__ */ new Map();
|
|
4731
|
+
async function fetchManifest(ossFileMap, tag) {
|
|
4732
|
+
const key = `${MANIFEST_PREFIX}${tag}${MANIFEST_SUFFIX}`;
|
|
4733
|
+
const url = ossFileMap[key];
|
|
4734
|
+
if (!url) {
|
|
4735
|
+
const available = Object.keys(ossFileMap).filter((k) => k.startsWith(MANIFEST_PREFIX) && k.endsWith(MANIFEST_SUFFIX)).map((k) => k.slice(39, -5));
|
|
4736
|
+
const availStr = available.length ? available.join(", ") : "(none)";
|
|
4737
|
+
throw new Error(`manifest signed URL missing for tag "${tag}" (key ${key}). Available tags in ossFileMap: ${availStr}. Either pass an available tag or update the studio_server TCC openclaw_upgrade_config supported_versions.`);
|
|
4903
4738
|
}
|
|
4904
|
-
|
|
4905
|
-
|
|
4906
|
-
|
|
4907
|
-
|
|
4739
|
+
const cached = manifestCache.get(url);
|
|
4740
|
+
if (cached) return cached;
|
|
4741
|
+
const label = `openclaw manifest tag=${tag}`;
|
|
4742
|
+
const res = await fetchWithDiag(url, { label });
|
|
4743
|
+
if (!res.ok) {
|
|
4744
|
+
const body = await readPreview(res);
|
|
4745
|
+
throw new Error(`${label}: HTTP ${res.status} ${res.statusText}` + (body ? ` body=${body}` : ""));
|
|
4908
4746
|
}
|
|
4909
|
-
|
|
4910
|
-
|
|
4911
|
-
|
|
4747
|
+
const manifest = await res.json();
|
|
4748
|
+
manifestCache.set(url, manifest);
|
|
4749
|
+
return manifest;
|
|
4750
|
+
}
|
|
4751
|
+
/** Best-effort body preview for HTTP-error diagnostics. OSS errors are
|
|
4752
|
+
* XML; first 256 chars usually contain the <Code> + <Message> we care
|
|
4753
|
+
* about. Failures here are swallowed (logging-only, not load-bearing). */
|
|
4754
|
+
async function readPreview(res) {
|
|
4755
|
+
try {
|
|
4756
|
+
return (await res.text()).slice(0, 256);
|
|
4757
|
+
} catch {
|
|
4758
|
+
return "";
|
|
4912
4759
|
}
|
|
4913
|
-
|
|
4914
|
-
|
|
4915
|
-
|
|
4916
|
-
|
|
4917
|
-
|
|
4918
|
-
|
|
4919
|
-
|
|
4920
|
-
|
|
4921
|
-
|
|
4922
|
-
|
|
4923
|
-
|
|
4760
|
+
}
|
|
4761
|
+
async function downloadWithCache(pkg, ossFileMap, opts = {}) {
|
|
4762
|
+
const cacheRoot = opts.cacheRoot ?? "/tmp/openclaw-diagnose/resources";
|
|
4763
|
+
const shortHash = pkg.shasum.slice(0, 16);
|
|
4764
|
+
const destDir = node_path.default.join(cacheRoot, shortHash);
|
|
4765
|
+
const destFile = node_path.default.join(destDir, node_path.default.posix.basename(pkg.ossKey));
|
|
4766
|
+
node_fs.default.mkdirSync(destDir, { recursive: true });
|
|
4767
|
+
if (node_fs.default.existsSync(destFile)) return destFile;
|
|
4768
|
+
const url = ossFileMap[pkg.ossKey];
|
|
4769
|
+
if (!url) throw new Error(`signed URL missing for ${pkg.ossKey}`);
|
|
4770
|
+
if (!pkg.integrity.startsWith("sha512-")) throw new Error(`unsupported integrity format: ${pkg.integrity}`);
|
|
4771
|
+
const expected = pkg.integrity.slice(7);
|
|
4772
|
+
const tmpFile = node_path.default.join(destDir, `.tmp.${process.pid}.${node_crypto.default.randomBytes(4).toString("hex")}`);
|
|
4773
|
+
try {
|
|
4774
|
+
const label = `download ${pkg.ossKey}`;
|
|
4775
|
+
const res = await fetchWithDiag(url, {
|
|
4776
|
+
label,
|
|
4777
|
+
timeoutMs: 6e4
|
|
4778
|
+
});
|
|
4779
|
+
if (!res.ok) {
|
|
4780
|
+
let preview = "";
|
|
4781
|
+
try {
|
|
4782
|
+
preview = (await res.text()).slice(0, 256);
|
|
4783
|
+
} catch {}
|
|
4784
|
+
throw new Error(`${label}: HTTP ${res.status} ${res.statusText}` + (preview ? ` body=${preview}` : ""));
|
|
4785
|
+
}
|
|
4786
|
+
if (!res.body) throw new Error(`${label}: empty body`);
|
|
4787
|
+
const hasher = node_crypto.default.createHash("sha512");
|
|
4788
|
+
const source = node_stream.Readable.fromWeb(res.body);
|
|
4789
|
+
async function* teeAndHash(src) {
|
|
4790
|
+
for await (const chunk of src) {
|
|
4791
|
+
hasher.update(chunk);
|
|
4792
|
+
yield chunk;
|
|
4924
4793
|
}
|
|
4925
4794
|
}
|
|
4795
|
+
await (0, node_stream_promises.pipeline)(source, teeAndHash, node_fs.default.createWriteStream(tmpFile));
|
|
4796
|
+
const actual = hasher.digest("base64");
|
|
4797
|
+
if (actual !== expected) {
|
|
4798
|
+
const envBypass = process.env.OPENCLAW_DEBUG_SKIP_INTEGRITY === "1";
|
|
4799
|
+
if (opts.skipIntegrity || envBypass) {
|
|
4800
|
+
const sourceLabel = opts.skipIntegrity ? "skipIntegrity=true" : "OPENCLAW_DEBUG_SKIP_INTEGRITY=1";
|
|
4801
|
+
console.error(`⚠ [downloadWithCache] INTEGRITY BYPASS for ${pkg.ossKey}: expected ${expected.slice(0, 12)}… got ${actual.slice(0, 12)}… — ${sourceLabel}. DO NOT use this flag in production.`);
|
|
4802
|
+
} else throw new Error(`integrity mismatch for ${pkg.ossKey}: expected ${expected} got ${actual}`);
|
|
4803
|
+
}
|
|
4804
|
+
moveSafe(tmpFile, destFile);
|
|
4805
|
+
return destFile;
|
|
4806
|
+
} catch (e) {
|
|
4807
|
+
try {
|
|
4808
|
+
node_fs.default.unlinkSync(tmpFile);
|
|
4809
|
+
} catch {}
|
|
4810
|
+
throw e;
|
|
4926
4811
|
}
|
|
4927
|
-
|
|
4928
|
-
|
|
4929
|
-
|
|
4930
|
-
|
|
4931
|
-
|
|
4932
|
-
|
|
4933
|
-
|
|
4934
|
-
|
|
4935
|
-
|
|
4936
|
-
const
|
|
4937
|
-
const
|
|
4938
|
-
|
|
4939
|
-
|
|
4940
|
-
|
|
4941
|
-
|
|
4942
|
-
|
|
4943
|
-
|
|
4812
|
+
}
|
|
4813
|
+
//#endregion
|
|
4814
|
+
//#region src/install-openclaw.ts
|
|
4815
|
+
async function installOpenclaw(openclawTag, ossFileMap, opts = {}) {
|
|
4816
|
+
const homeBase = resolveHomeBase(opts.homeBase);
|
|
4817
|
+
const t0 = Date.now();
|
|
4818
|
+
const pkg = (await fetchManifest(ossFileMap, openclawTag)).packages.find((p) => p.role === "cli" && p.name === "openclaw");
|
|
4819
|
+
if (!pkg) throw new Error("install-openclaw: role=cli,name=openclaw not found in manifest");
|
|
4820
|
+
const targetDir = opts.targetDir ?? node_path.default.join(homeBase, pkg.installPath);
|
|
4821
|
+
const bakDir = targetDir + ".bak";
|
|
4822
|
+
const newDir = targetDir + ".new";
|
|
4823
|
+
const tarball = await downloadWithCache(pkg, ossFileMap, opts);
|
|
4824
|
+
console.error(`[install-openclaw] tag=${openclawTag} shasum=${pkg.shasum.slice(0, 12)}...`);
|
|
4825
|
+
if (node_fs.default.existsSync(newDir)) node_fs.default.rmSync(newDir, {
|
|
4826
|
+
recursive: true,
|
|
4827
|
+
force: true
|
|
4828
|
+
});
|
|
4829
|
+
if (node_fs.default.existsSync(bakDir)) node_fs.default.rmSync(bakDir, {
|
|
4830
|
+
recursive: true,
|
|
4831
|
+
force: true
|
|
4832
|
+
});
|
|
4833
|
+
node_fs.default.mkdirSync(node_path.default.dirname(targetDir), { recursive: true });
|
|
4834
|
+
const tmpStage = node_fs.default.mkdtempSync(node_path.default.join(opts.tmpRoot ?? node_os.default.tmpdir(), "openclaw-install-"));
|
|
4835
|
+
try {
|
|
4836
|
+
extractTarballTolerant(tarball, tmpStage, { stripComponents: 1 });
|
|
4837
|
+
if (!node_fs.default.existsSync(node_path.default.join(tmpStage, "package.json"))) throw new Error("extracted tarball missing package.json");
|
|
4838
|
+
moveSafe(tmpStage, newDir);
|
|
4839
|
+
const hadExisting = node_fs.default.existsSync(targetDir);
|
|
4840
|
+
try {
|
|
4841
|
+
if (hadExisting) moveSafe(targetDir, bakDir);
|
|
4842
|
+
moveSafe(newDir, targetDir);
|
|
4843
|
+
} catch (e) {
|
|
4844
|
+
if (hadExisting && !node_fs.default.existsSync(targetDir) && node_fs.default.existsSync(bakDir)) try {
|
|
4845
|
+
moveSafe(bakDir, targetDir);
|
|
4846
|
+
} catch {}
|
|
4847
|
+
try {
|
|
4848
|
+
node_fs.default.rmSync(newDir, {
|
|
4849
|
+
recursive: true,
|
|
4850
|
+
force: true
|
|
4851
|
+
});
|
|
4852
|
+
} catch {}
|
|
4853
|
+
throw e;
|
|
4944
4854
|
}
|
|
4855
|
+
if (hadExisting && node_fs.default.existsSync(bakDir)) node_fs.default.rmSync(bakDir, {
|
|
4856
|
+
recursive: true,
|
|
4857
|
+
force: true
|
|
4858
|
+
});
|
|
4859
|
+
} finally {
|
|
4860
|
+
if (node_fs.default.existsSync(tmpStage)) try {
|
|
4861
|
+
node_fs.default.rmSync(tmpStage, {
|
|
4862
|
+
recursive: true,
|
|
4863
|
+
force: true
|
|
4864
|
+
});
|
|
4865
|
+
} catch {}
|
|
4945
4866
|
}
|
|
4946
|
-
console.error(`
|
|
4947
|
-
return null;
|
|
4867
|
+
console.error(`[install-openclaw] done in ${Date.now() - t0}ms`);
|
|
4948
4868
|
}
|
|
4949
|
-
function
|
|
4950
|
-
const
|
|
4951
|
-
|
|
4952
|
-
const
|
|
4953
|
-
if (
|
|
4954
|
-
|
|
4955
|
-
|
|
4869
|
+
async function installExtension(tag, ossFileMap, opts = {}) {
|
|
4870
|
+
const homeBase = resolveHomeBase(opts.homeBase);
|
|
4871
|
+
const hasAll = !!opts.all;
|
|
4872
|
+
const hasNames = (opts.names?.length ?? 0) > 0;
|
|
4873
|
+
if (hasAll && hasNames) throw new Error("install-extension: --all and --extension are mutually exclusive");
|
|
4874
|
+
if (!hasAll && !hasNames) throw new Error("install-extension: must provide --all or --extension=<name>");
|
|
4875
|
+
const allExts = (await fetchManifest(ossFileMap, tag)).packages.filter((p) => p.role === "extension");
|
|
4876
|
+
let targets;
|
|
4877
|
+
if (hasAll) targets = allExts;
|
|
4878
|
+
else {
|
|
4879
|
+
const wanted = new Set(opts.names);
|
|
4880
|
+
targets = allExts.filter((p) => wanted.has(p.name) || p.packageName != null && wanted.has(p.packageName));
|
|
4881
|
+
const foundKeys = /* @__PURE__ */ new Set();
|
|
4882
|
+
for (const t of targets) {
|
|
4883
|
+
foundKeys.add(t.name);
|
|
4884
|
+
if (t.packageName) foundKeys.add(t.packageName);
|
|
4885
|
+
}
|
|
4886
|
+
const missing = opts.names.filter((n) => !foundKeys.has(n));
|
|
4887
|
+
if (missing.length > 0) throw new Error(`install-extension: not found in manifest: ${missing.join(", ")}`);
|
|
4956
4888
|
}
|
|
4957
|
-
|
|
4958
|
-
|
|
4959
|
-
|
|
4889
|
+
console.error(`[install-extension] tag=${tag} targets=${targets.length}`);
|
|
4890
|
+
const t0 = Date.now();
|
|
4891
|
+
const tarballs = await Promise.all(targets.map(async (p) => {
|
|
4892
|
+
const tb = await downloadWithCache(p, ossFileMap, opts);
|
|
4893
|
+
console.error(`[install-extension] ${p.name}: downloaded`);
|
|
4894
|
+
return {
|
|
4895
|
+
pkg: p,
|
|
4896
|
+
tarball: tb
|
|
4897
|
+
};
|
|
4898
|
+
}));
|
|
4899
|
+
for (const { pkg, tarball } of tarballs) {
|
|
4900
|
+
installOne$1(pkg, tarball, homeBase);
|
|
4901
|
+
console.error(`[install-extension] ${pkg.name}: installed`);
|
|
4902
|
+
}
|
|
4903
|
+
if (!opts.skipConfigUpdate) updatePluginInstalls(opts.configPath ?? node_path.default.join(homeBase, "workspace/agent/openclaw.json"), targets);
|
|
4904
|
+
else console.error(`[install-extension] skipConfigUpdate=true — not touching openclaw.json`);
|
|
4905
|
+
console.error(`[install-extension] done ${targets.length}/${targets.length} in ${Date.now() - t0}ms`);
|
|
4960
4906
|
}
|
|
4907
|
+
const MEM0_PLUGIN_NAME = "openclaw-mem0-plugin";
|
|
4908
|
+
const PLUGINS_TO_AUTO_ENABLE = ["openclaw-lark", "openclaw-extension-miaoda"];
|
|
4961
4909
|
/**
|
|
4962
|
-
*
|
|
4963
|
-
*
|
|
4964
|
-
*
|
|
4910
|
+
* Merge each installed extension's installMetadata into openclaw.json's
|
|
4911
|
+
* plugins.installs[<pkg.name>]. Atomic write via tmp + rename.
|
|
4912
|
+
*
|
|
4913
|
+
* - No openclaw.json → log + return (not an error; some install contexts don't have it yet)
|
|
4914
|
+
* - Extension without installMetadata in manifest → skip that entry (log)
|
|
4915
|
+
* - Existing plugins.installs entries for other extensions left untouched
|
|
4916
|
+
*
|
|
4917
|
+
* Special-case for mem0: when openclaw-mem0-plugin is among the installed
|
|
4918
|
+
* targets, also append it to plugins.allow (idempotent) and seed
|
|
4919
|
+
* plugins.entries.openclaw-mem0-plugin = { enabled: false } when the key
|
|
4920
|
+
* is absent. Existing values are preserved (user toggles survive).
|
|
4965
4921
|
*/
|
|
4966
|
-
function
|
|
4967
|
-
|
|
4968
|
-
|
|
4969
|
-
|
|
4970
|
-
if (!feishu) return [];
|
|
4971
|
-
const appIds = /* @__PURE__ */ new Set();
|
|
4972
|
-
const topAppId = feishu.appId;
|
|
4973
|
-
if (typeof topAppId === "string" && topAppId.trim()) appIds.add(topAppId.trim());
|
|
4974
|
-
const accounts = asRecord(feishu.accounts);
|
|
4975
|
-
if (accounts) for (const val of Object.values(accounts)) {
|
|
4976
|
-
const appId = asRecord(val)?.appId;
|
|
4977
|
-
if (typeof appId === "string" && appId.trim()) appIds.add(appId.trim());
|
|
4922
|
+
function updatePluginInstalls(configPath, installedPkgs) {
|
|
4923
|
+
if (!node_fs.default.existsSync(configPath)) {
|
|
4924
|
+
console.error(`[install-extension] no config at ${configPath} — skip plugins.installs update`);
|
|
4925
|
+
return;
|
|
4978
4926
|
}
|
|
4979
|
-
|
|
4980
|
-
|
|
4981
|
-
|
|
4982
|
-
|
|
4983
|
-
|
|
4984
|
-
|
|
4985
|
-
|
|
4986
|
-
|
|
4987
|
-
|
|
4988
|
-
|
|
4989
|
-
|
|
4927
|
+
const JSON5 = loadJSON5();
|
|
4928
|
+
const raw = node_fs.default.readFileSync(configPath, "utf-8");
|
|
4929
|
+
const config = JSON5.parse(raw);
|
|
4930
|
+
if (!config.plugins || typeof config.plugins !== "object") config.plugins = {};
|
|
4931
|
+
const plugins = config.plugins;
|
|
4932
|
+
if (!plugins.installs || typeof plugins.installs !== "object") plugins.installs = {};
|
|
4933
|
+
const installs = plugins.installs;
|
|
4934
|
+
let updated = 0;
|
|
4935
|
+
let skipped = 0;
|
|
4936
|
+
for (const pkg of installedPkgs) if (pkg.installMetadata) {
|
|
4937
|
+
installs[pkg.name] = pkg.installMetadata;
|
|
4938
|
+
updated++;
|
|
4939
|
+
} else skipped++;
|
|
4940
|
+
if (installedPkgs.some((p) => p.name === MEM0_PLUGIN_NAME)) {
|
|
4941
|
+
const allowRaw = plugins.allow;
|
|
4942
|
+
const allow = Array.isArray(allowRaw) ? allowRaw : [];
|
|
4943
|
+
if (!allow.filter((e) => typeof e === "string").includes(MEM0_PLUGIN_NAME)) allow.push(MEM0_PLUGIN_NAME);
|
|
4944
|
+
plugins.allow = allow;
|
|
4945
|
+
if (!plugins.entries || typeof plugins.entries !== "object" || Array.isArray(plugins.entries)) plugins.entries = {};
|
|
4946
|
+
const entries = plugins.entries;
|
|
4947
|
+
if (!(MEM0_PLUGIN_NAME in entries)) entries[MEM0_PLUGIN_NAME] = { enabled: false };
|
|
4990
4948
|
}
|
|
4991
|
-
|
|
4992
|
-
|
|
4993
|
-
|
|
4994
|
-
|
|
4995
|
-
|
|
4996
|
-
|
|
4949
|
+
for (const pkg of installedPkgs) {
|
|
4950
|
+
if (!PLUGINS_TO_AUTO_ENABLE.includes(pkg.name)) continue;
|
|
4951
|
+
if (!Array.isArray(plugins.allow)) plugins.allow = [];
|
|
4952
|
+
const allow = plugins.allow;
|
|
4953
|
+
if (!allow.includes(pkg.name)) allow.push(pkg.name);
|
|
4954
|
+
if (!plugins.entries || typeof plugins.entries !== "object" || Array.isArray(plugins.entries)) plugins.entries = {};
|
|
4955
|
+
const entries = plugins.entries;
|
|
4956
|
+
entries[pkg.name] = {
|
|
4957
|
+
...asRecord(entries[pkg.name]) ?? {},
|
|
4958
|
+
enabled: true
|
|
4997
4959
|
};
|
|
4998
4960
|
}
|
|
4999
|
-
const
|
|
5000
|
-
|
|
5001
|
-
|
|
5002
|
-
|
|
5003
|
-
|
|
5004
|
-
|
|
5005
|
-
|
|
5006
|
-
|
|
5007
|
-
|
|
5008
|
-
|
|
5009
|
-
|
|
5010
|
-
|
|
5011
|
-
|
|
5012
|
-
|
|
5013
|
-
|
|
5014
|
-
|
|
5015
|
-
|
|
5016
|
-
|
|
5017
|
-
|
|
5018
|
-
|
|
5019
|
-
|
|
5020
|
-
|
|
5021
|
-
|
|
5022
|
-
|
|
5023
|
-
|
|
5024
|
-
|
|
5025
|
-
|
|
5026
|
-
|
|
5027
|
-
|
|
5028
|
-
|
|
5029
|
-
|
|
5030
|
-
|
|
5031
|
-
|
|
5032
|
-
],
|
|
5033
|
-
encoding: "utf-8",
|
|
5034
|
-
input: appSecret
|
|
4961
|
+
const tmpPath = configPath + ".installs-tmp";
|
|
4962
|
+
node_fs.default.writeFileSync(tmpPath, JSON.stringify(config, null, 2), "utf-8");
|
|
4963
|
+
moveSafe(tmpPath, configPath);
|
|
4964
|
+
console.error(`[install-extension] plugins.installs updated: ${updated} entry(ies) in ${configPath}` + (skipped > 0 ? ` (${skipped} package(s) without installMetadata skipped)` : ""));
|
|
4965
|
+
}
|
|
4966
|
+
function installOne$1(pkg, tarball, homeBase) {
|
|
4967
|
+
const destDir = node_path.default.join(homeBase, pkg.installPath);
|
|
4968
|
+
const stagingDir = destDir + ".new";
|
|
4969
|
+
const oldDir = destDir + ".old";
|
|
4970
|
+
node_fs.default.mkdirSync(node_path.default.dirname(destDir), { recursive: true });
|
|
4971
|
+
if (node_fs.default.existsSync(stagingDir)) node_fs.default.rmSync(stagingDir, {
|
|
4972
|
+
recursive: true,
|
|
4973
|
+
force: true
|
|
4974
|
+
});
|
|
4975
|
+
node_fs.default.mkdirSync(stagingDir);
|
|
4976
|
+
try {
|
|
4977
|
+
extractTarballTolerant(tarball, stagingDir, { stripComponents: 1 });
|
|
4978
|
+
if (!node_fs.default.existsSync(node_path.default.join(stagingDir, "package.json"))) throw new Error(`extension tarball missing package.json: ${pkg.name}`);
|
|
4979
|
+
} catch (e) {
|
|
4980
|
+
try {
|
|
4981
|
+
node_fs.default.rmSync(stagingDir, {
|
|
4982
|
+
recursive: true,
|
|
4983
|
+
force: true
|
|
4984
|
+
});
|
|
4985
|
+
} catch {}
|
|
4986
|
+
throw e;
|
|
4987
|
+
}
|
|
4988
|
+
const hadOld = node_fs.default.existsSync(destDir);
|
|
4989
|
+
if (hadOld) moveSafe(destDir, oldDir);
|
|
4990
|
+
moveSafe(stagingDir, destDir);
|
|
4991
|
+
if (hadOld && node_fs.default.existsSync(oldDir)) node_fs.default.rmSync(oldDir, {
|
|
4992
|
+
recursive: true,
|
|
4993
|
+
force: true
|
|
5035
4994
|
});
|
|
5036
|
-
const configInitStdout = initRes.stdout?.trim() || void 0;
|
|
5037
|
-
const configInitStderr = initRes.stderr?.trim() || void 0;
|
|
5038
|
-
if (configInitStdout) console.error(`lark-cli config init stdout: ${configInitStdout}`);
|
|
5039
|
-
if (configInitStderr) console.error(`lark-cli config init stderr: ${configInitStderr}`);
|
|
5040
|
-
if (initRes.error) return {
|
|
5041
|
-
ok: false,
|
|
5042
|
-
configInitStdout,
|
|
5043
|
-
configInitStderr,
|
|
5044
|
-
error: `lark-cli config init spawn error: ${initRes.error.message}`
|
|
5045
|
-
};
|
|
5046
|
-
if (initRes.status !== 0) return {
|
|
5047
|
-
ok: false,
|
|
5048
|
-
configInitExitCode: initRes.status ?? void 0,
|
|
5049
|
-
configInitStdout,
|
|
5050
|
-
configInitStderr,
|
|
5051
|
-
error: `lark-cli config init exited with code ${initRes.status}`
|
|
5052
|
-
};
|
|
5053
|
-
appendPeToAgentsMd(agentsMdPath);
|
|
5054
|
-
return {
|
|
5055
|
-
ok: true,
|
|
5056
|
-
configInitExitCode: 0,
|
|
5057
|
-
agentsMdPath
|
|
5058
|
-
};
|
|
5059
4995
|
}
|
|
5060
4996
|
//#endregion
|
|
5061
4997
|
//#region ../../openclaw-slardar/lib/client.js
|
|
@@ -6901,60 +6837,6 @@ function mergeCoreBackupAndOrigins(configPath, vars, resetData, log) {
|
|
|
6901
6837
|
log(`allowedOrigins: added ${added.length} (${JSON.stringify(added)}), total now ${mergedOrigins.length}`);
|
|
6902
6838
|
}
|
|
6903
6839
|
/**
|
|
6904
|
-
* Fix bot account allowFrom and appSecret using larkApps from innerApi.
|
|
6905
|
-
*
|
|
6906
|
-
* For each bot account (key starts with `bot-cli_`):
|
|
6907
|
-
* - allowFrom must contain the bot's own creatorOpenID from larkApps
|
|
6908
|
-
* - appSecret must be either the canonical provider-ref or match larkApps plaintext
|
|
6909
|
-
*
|
|
6910
|
-
* Runs after mergeCoreBackupAndOrigins so it operates on the final config state.
|
|
6911
|
-
*/
|
|
6912
|
-
function fixBotChannelConfig(configPath, larkApps, log) {
|
|
6913
|
-
if (!larkApps || larkApps.length === 0) {
|
|
6914
|
-
log("no larkApps data, skip bot channel config fix");
|
|
6915
|
-
return;
|
|
6916
|
-
}
|
|
6917
|
-
const config = loadJSON5().parse(node_fs.default.readFileSync(configPath, "utf-8"));
|
|
6918
|
-
const accounts = asRecord(getNestedMap(config, "channels", "feishu")?.accounts);
|
|
6919
|
-
if (!accounts) {
|
|
6920
|
-
log("no feishu accounts in config, skip bot channel config fix");
|
|
6921
|
-
return;
|
|
6922
|
-
}
|
|
6923
|
-
let fixCount = 0;
|
|
6924
|
-
for (const [, account] of Object.entries(accounts)) {
|
|
6925
|
-
const bot = asRecord(account);
|
|
6926
|
-
if (!bot) continue;
|
|
6927
|
-
const appId = bot.appId;
|
|
6928
|
-
if (typeof appId !== "string" || !appId.startsWith("cli_")) continue;
|
|
6929
|
-
const larkApp = larkApps.find((e) => e.larkAppID === appId);
|
|
6930
|
-
if (!larkApp) continue;
|
|
6931
|
-
const creatorOpenID = larkApp.creatorOpenID;
|
|
6932
|
-
if (typeof creatorOpenID === "string" && creatorOpenID !== "") {
|
|
6933
|
-
const allowFrom = Array.isArray(bot.allowFrom) ? [...bot.allowFrom] : [];
|
|
6934
|
-
if (!allowFrom.includes(creatorOpenID)) {
|
|
6935
|
-
allowFrom.push(creatorOpenID);
|
|
6936
|
-
bot.allowFrom = allowFrom;
|
|
6937
|
-
fixCount++;
|
|
6938
|
-
}
|
|
6939
|
-
}
|
|
6940
|
-
const secret = bot.appSecret;
|
|
6941
|
-
let needsFix = false;
|
|
6942
|
-
if (typeof secret === "object" && secret !== null && !Array.isArray(secret)) {
|
|
6943
|
-
if (!matchMap(secret, DEFAULT_FEISHU_APP_SECRET)) needsFix = true;
|
|
6944
|
-
} else if (typeof secret === "string") {
|
|
6945
|
-
if (secret !== larkApp.appSecret) needsFix = true;
|
|
6946
|
-
} else needsFix = true;
|
|
6947
|
-
if (needsFix) {
|
|
6948
|
-
bot.appSecret = { ...DEFAULT_FEISHU_APP_SECRET };
|
|
6949
|
-
fixCount++;
|
|
6950
|
-
}
|
|
6951
|
-
}
|
|
6952
|
-
if (fixCount > 0) {
|
|
6953
|
-
node_fs.default.writeFileSync(configPath, JSON.stringify(config, null, 2), "utf-8");
|
|
6954
|
-
log(`fixed ${fixCount} bot channel config issue(s) (allowFrom/appSecret)`);
|
|
6955
|
-
} else log("bot channel config ok, no fixes needed");
|
|
6956
|
-
}
|
|
6957
|
-
/**
|
|
6958
6840
|
* Step 7: Verify startup scripts landed in configDir/scripts/.
|
|
6959
6841
|
*
|
|
6960
6842
|
* Scripts are extracted directly to configDir/scripts/ during stageTemplate —
|
|
@@ -7099,7 +6981,6 @@ async function runReset(input, taskId, resultFile) {
|
|
|
7099
6981
|
await step5InstallOpenclaw(openclawTag, ossFileMap, log);
|
|
7100
6982
|
step(6);
|
|
7101
6983
|
mergeCoreBackupAndOrigins(configPath, vars, resetData, log);
|
|
7102
|
-
fixBotChannelConfig(configPath, vars.larkApps, log);
|
|
7103
6984
|
step(7);
|
|
7104
6985
|
verifyStartupScripts(configDir, log);
|
|
7105
6986
|
step(8);
|
|
@@ -7898,8 +7779,7 @@ function normalizeCtx(raw) {
|
|
|
7898
7779
|
reset: {
|
|
7899
7780
|
templateVars: r.reset.templateVars ?? {},
|
|
7900
7781
|
coreBackup: r.reset.coreBackup
|
|
7901
|
-
}
|
|
7902
|
-
larkApps: Array.isArray(r.larkApps) ? r.larkApps : []
|
|
7782
|
+
}
|
|
7903
7783
|
};
|
|
7904
7784
|
}
|
|
7905
7785
|
const vars = r.vars ?? {};
|
|
@@ -7924,8 +7804,7 @@ function normalizeCtx(raw) {
|
|
|
7924
7804
|
reset: {
|
|
7925
7805
|
templateVars: resetData.templateVars ?? {},
|
|
7926
7806
|
coreBackup: resetData.coreBackup
|
|
7927
|
-
}
|
|
7928
|
-
larkApps: Array.isArray(r.larkApps) ? r.larkApps : []
|
|
7807
|
+
}
|
|
7929
7808
|
};
|
|
7930
7809
|
}
|
|
7931
7810
|
function fillApp(src) {
|
|
@@ -7990,8 +7869,7 @@ function buildCheckInput(raw, configPathOverride) {
|
|
|
7990
7869
|
providerFilePath: PROVIDER_FILE_PATH,
|
|
7991
7870
|
secretsFilePath: SECRETS_FILE_PATH,
|
|
7992
7871
|
templateVars: ctx.app.templateVars,
|
|
7993
|
-
recommendedOpenclawTag: ctx.app.recommendedOpenclawTag
|
|
7994
|
-
larkApps: ctx.larkApps
|
|
7872
|
+
recommendedOpenclawTag: ctx.app.recommendedOpenclawTag
|
|
7995
7873
|
},
|
|
7996
7874
|
templateVars: ctx.app.templateVars
|
|
7997
7875
|
};
|
|
@@ -8023,8 +7901,7 @@ function buildRepairInput(raw, configPathOverride) {
|
|
|
8023
7901
|
providerFilePath: PROVIDER_FILE_PATH,
|
|
8024
7902
|
secretsFilePath: SECRETS_FILE_PATH,
|
|
8025
7903
|
templateVars: ctx.app.templateVars,
|
|
8026
|
-
recommendedOpenclawTag: ctx.app.recommendedOpenclawTag
|
|
8027
|
-
larkApps: ctx.larkApps
|
|
7904
|
+
recommendedOpenclawTag: ctx.app.recommendedOpenclawTag
|
|
8028
7905
|
},
|
|
8029
7906
|
repairData: {
|
|
8030
7907
|
secretsContent: ctx.secrets.secretsContent,
|
|
@@ -8060,8 +7937,7 @@ function buildResetInput(raw, configPathOverride) {
|
|
|
8060
7937
|
providerFilePath: PROVIDER_FILE_PATH,
|
|
8061
7938
|
secretsFilePath: SECRETS_FILE_PATH,
|
|
8062
7939
|
templateVars: ctx.app.templateVars,
|
|
8063
|
-
recommendedOpenclawTag: ctx.app.recommendedOpenclawTag
|
|
8064
|
-
larkApps: ctx.larkApps
|
|
7940
|
+
recommendedOpenclawTag: ctx.app.recommendedOpenclawTag
|
|
8065
7941
|
},
|
|
8066
7942
|
resetData: {
|
|
8067
7943
|
templateVars: ctx.reset.templateVars,
|
|
@@ -10371,7 +10247,7 @@ async function reportCliRun(opts) {
|
|
|
10371
10247
|
//#region src/help.ts
|
|
10372
10248
|
const BIN = "mclaw-diagnose";
|
|
10373
10249
|
function versionBanner() {
|
|
10374
|
-
return `v0.1.14-alpha.
|
|
10250
|
+
return `v0.1.14-alpha.13`;
|
|
10375
10251
|
}
|
|
10376
10252
|
const COMMANDS = [
|
|
10377
10253
|
{
|
|
@@ -10475,12 +10351,16 @@ EXIT CODES
|
|
|
10475
10351
|
hidden: true,
|
|
10476
10352
|
summary: "Run rule-engine check only",
|
|
10477
10353
|
help: `USAGE
|
|
10478
|
-
${BIN} check
|
|
10354
|
+
${BIN} check [--ctx=<base64>]
|
|
10479
10355
|
|
|
10480
10356
|
DESCRIPTION
|
|
10481
10357
|
Runs the rule engine against the sandbox's current openclaw config and
|
|
10482
|
-
returns { failedRules }.
|
|
10483
|
-
|
|
10358
|
+
returns { failedRules }. Used by sandbox_console's push-style callers
|
|
10359
|
+
that already own the ctx — end-users should prefer \`doctor\`.
|
|
10360
|
+
|
|
10361
|
+
OPTIONS
|
|
10362
|
+
--ctx=<base64> Opaque ctx JSON (base64). When absent, fetched from
|
|
10363
|
+
innerapi (same path as doctor).
|
|
10484
10364
|
`
|
|
10485
10365
|
},
|
|
10486
10366
|
{
|
|
@@ -10488,11 +10368,16 @@ DESCRIPTION
|
|
|
10488
10368
|
hidden: true,
|
|
10489
10369
|
summary: "Apply standard-mode repairs",
|
|
10490
10370
|
help: `USAGE
|
|
10491
|
-
${BIN} repair
|
|
10371
|
+
${BIN} repair [--ctx=<base64>]
|
|
10492
10372
|
|
|
10493
10373
|
DESCRIPTION
|
|
10494
|
-
Runs repair for the failing rules
|
|
10495
|
-
|
|
10374
|
+
Runs repair for the failing rules listed inside the ctx's repairData.
|
|
10375
|
+
Intended for sandbox_console's push path — end-users should use
|
|
10376
|
+
\`doctor --fix\` instead.
|
|
10377
|
+
|
|
10378
|
+
OPTIONS
|
|
10379
|
+
--ctx=<base64> Opaque ctx JSON (base64). When absent, fetched from
|
|
10380
|
+
innerapi.
|
|
10496
10381
|
`
|
|
10497
10382
|
},
|
|
10498
10383
|
{
|
|
@@ -10500,15 +10385,14 @@ DESCRIPTION
|
|
|
10500
10385
|
hidden: true,
|
|
10501
10386
|
summary: "Re-initialize sandbox via the 9-step reset pipeline",
|
|
10502
10387
|
help: `USAGE
|
|
10503
|
-
${BIN} reset --async
|
|
10504
|
-
${BIN} reset --worker --task-id=<id>
|
|
10388
|
+
${BIN} reset --async [--ctx=<base64>]
|
|
10389
|
+
${BIN} reset --worker --task-id=<id> [--ctx=<base64>]
|
|
10505
10390
|
|
|
10506
10391
|
DESCRIPTION
|
|
10507
10392
|
Two-phase pipeline driven asynchronously: the --async invocation spawns
|
|
10508
10393
|
a detached worker and returns { taskId } immediately; the --worker
|
|
10509
10394
|
invocation (spawned by --async) runs the actual 9 steps and writes
|
|
10510
10395
|
progress to /tmp/openclaw-diagnose/reset-<taskId>.json.
|
|
10511
|
-
Ctx is fetched from innerapi automatically.
|
|
10512
10396
|
|
|
10513
10397
|
Poll progress with \`${BIN} get_reset_task --task-id=<id>\`.
|
|
10514
10398
|
|
|
@@ -10516,6 +10400,7 @@ OPTIONS
|
|
|
10516
10400
|
--async Start a detached worker and return taskId on stdout.
|
|
10517
10401
|
--worker Internal — run the 9-step pipeline (launched by --async).
|
|
10518
10402
|
--task-id=<id> Required with --worker; identifies the progress file.
|
|
10403
|
+
--ctx=<base64> Opaque ctx JSON; fetched from innerapi when absent.
|
|
10519
10404
|
`
|
|
10520
10405
|
},
|
|
10521
10406
|
{
|
|
@@ -10538,7 +10423,7 @@ OPTIONS
|
|
|
10538
10423
|
hidden: true,
|
|
10539
10424
|
summary: "Download + install the openclaw tarball",
|
|
10540
10425
|
help: `USAGE
|
|
10541
|
-
${BIN} install-openclaw <tag> [--oss_file_map=<base64>]
|
|
10426
|
+
${BIN} install-openclaw <tag> [--ctx=<base64> | --oss_file_map=<base64>]
|
|
10542
10427
|
|
|
10543
10428
|
DESCRIPTION
|
|
10544
10429
|
Downloads the openclaw@<tag> tgz via the signed OSS URL found in the
|
|
@@ -10550,9 +10435,9 @@ ARGUMENTS
|
|
|
10550
10435
|
<tag> Openclaw version tag, e.g. 2026.4.11.
|
|
10551
10436
|
|
|
10552
10437
|
OPTIONS
|
|
10438
|
+
--ctx=<base64> Opaque ctx; ossFileMap is extracted from it.
|
|
10553
10439
|
--oss_file_map=... Pre-built OSS URL map (base64 JSON); skips innerapi
|
|
10554
|
-
entirely.
|
|
10555
|
-
innerapi automatically.
|
|
10440
|
+
entirely. Wins over --ctx when both provided.
|
|
10556
10441
|
`
|
|
10557
10442
|
},
|
|
10558
10443
|
{
|
|
@@ -10578,7 +10463,8 @@ OPTIONS
|
|
|
10578
10463
|
--home_base=<dir> Override the /home/gem base (tests).
|
|
10579
10464
|
--config_path=<p> Override the openclaw.json path (tests).
|
|
10580
10465
|
--skip-config-update Leave plugins.installs in openclaw.json untouched.
|
|
10581
|
-
--
|
|
10466
|
+
--ctx=<base64> Opaque ctx; see install-openclaw for semantics.
|
|
10467
|
+
--oss_file_map=... Pre-built OSS URL map (base64 JSON).
|
|
10582
10468
|
`
|
|
10583
10469
|
},
|
|
10584
10470
|
{
|
|
@@ -10605,6 +10491,7 @@ OPTIONS
|
|
|
10605
10491
|
--cli=<name> CLI package to install by short name or scoped
|
|
10606
10492
|
packageName (repeatable, at least one required).
|
|
10607
10493
|
--home_base=<dir> Override the /home/gem base (tests).
|
|
10494
|
+
--ctx=<base64> Opaque ctx; ossFileMap is extracted from it.
|
|
10608
10495
|
--oss_file_map=... Pre-built OSS URL map (base64 JSON); skips innerapi.
|
|
10609
10496
|
|
|
10610
10497
|
EXAMPLES
|
|
@@ -10711,7 +10598,8 @@ OPTIONS
|
|
|
10711
10598
|
--role=<role> Package role (e.g. template, config).
|
|
10712
10599
|
--name=<name> Package name within the role.
|
|
10713
10600
|
--dir=<dir> Target dir (defaults to dirname(pkg.installPath)).
|
|
10714
|
-
--
|
|
10601
|
+
--ctx=<base64> Opaque ctx; ossFileMap is extracted from it.
|
|
10602
|
+
--oss_file_map=... Pre-built OSS URL map (base64 JSON).
|
|
10715
10603
|
`
|
|
10716
10604
|
}
|
|
10717
10605
|
];
|
|
@@ -10787,31 +10675,31 @@ function planVarsFields(opts = {}) {
|
|
|
10787
10675
|
*
|
|
10788
10676
|
* Per-command group needs:
|
|
10789
10677
|
*
|
|
10790
|
-
* doctor / check app
|
|
10791
|
-
* repair app + secrets
|
|
10792
|
-
* reset app + secrets + install + reset
|
|
10678
|
+
* doctor / check app (rule-driven)
|
|
10679
|
+
* repair app + secrets (writes secretsContent / providerKeyContent)
|
|
10680
|
+
* reset app + secrets + install + reset (the works)
|
|
10793
10681
|
* install-* install only
|
|
10794
10682
|
*
|
|
10795
10683
|
* Empty result (`{}`) means "no group needed" — the CLI can skip the
|
|
10796
10684
|
* `fetchCtxViaInnerApi` call entirely and run with a synthetic empty ctx.
|
|
10685
|
+
* Happens e.g. when the user pinned `--rule=<key>` to a vars-free rule on
|
|
10686
|
+
* `doctor`.
|
|
10797
10687
|
*/
|
|
10798
10688
|
function planCtxPopulate(opts) {
|
|
10799
10689
|
if (opts.command === "install") return { install: true };
|
|
10800
10690
|
const populate = {};
|
|
10801
|
-
|
|
10691
|
+
const appFields = planVarsFields({
|
|
10802
10692
|
disabled: opts.disabled,
|
|
10803
10693
|
onlyRules: opts.onlyRules,
|
|
10804
10694
|
profile: opts.profile
|
|
10805
|
-
})
|
|
10806
|
-
if (
|
|
10807
|
-
|
|
10808
|
-
|
|
10809
|
-
} else if (opts.command === "reset") {
|
|
10695
|
+
});
|
|
10696
|
+
if (appFields.length > 0) populate.app = appFields;
|
|
10697
|
+
if (opts.command === "repair") populate.secrets = true;
|
|
10698
|
+
else if (opts.command === "reset") {
|
|
10810
10699
|
populate.secrets = true;
|
|
10811
10700
|
populate.install = true;
|
|
10812
10701
|
populate.reset = true;
|
|
10813
|
-
|
|
10814
|
-
} else if (opts.command === "doctor" || opts.command === "check") populate.larkApps = true;
|
|
10702
|
+
}
|
|
10815
10703
|
return populate;
|
|
10816
10704
|
}
|
|
10817
10705
|
//#endregion
|
|
@@ -10870,6 +10758,21 @@ function reportDoctorRunToSlardar(opts) {
|
|
|
10870
10758
|
const args = node_process.default.argv.slice(2);
|
|
10871
10759
|
const mode = args.find((a) => !a.startsWith("-"));
|
|
10872
10760
|
/**
|
|
10761
|
+
* Decode `--ctx=<base64>` into an opaque JSON object. Returns undefined when
|
|
10762
|
+
* the flag isn't present — the caller decides whether to fall back to the
|
|
10763
|
+
* innerapi or to error out.
|
|
10764
|
+
*
|
|
10765
|
+
* The object's shape is not enforced here; downstream code consumes it via
|
|
10766
|
+
* either `normalizeCtx()` (new path) or direct field access for the legacy
|
|
10767
|
+
* check/repair/reset contract still used by sandbox_console push.
|
|
10768
|
+
*/
|
|
10769
|
+
function parseCtxFlag(args) {
|
|
10770
|
+
const ctxArg = args.find((a) => a.startsWith("--ctx="));
|
|
10771
|
+
if (!ctxArg) return void 0;
|
|
10772
|
+
const b64 = ctxArg.slice(6);
|
|
10773
|
+
return JSON.parse(Buffer.from(b64, "base64").toString("utf-8"));
|
|
10774
|
+
}
|
|
10775
|
+
/**
|
|
10873
10776
|
* Pull the first non-flag positional after the mode name.
|
|
10874
10777
|
* (The mode itself is args[0] in the filtered set, so we skip index 0.)
|
|
10875
10778
|
*/
|
|
@@ -10897,8 +10800,8 @@ function getMultiFlag(args, name) {
|
|
|
10897
10800
|
* case but is no longer consulted.
|
|
10898
10801
|
*/
|
|
10899
10802
|
async function reportRun(command, rc, _raw, invocation, durationMs, outcome, slardar = {
|
|
10900
|
-
scene
|
|
10901
|
-
profile
|
|
10803
|
+
scene,
|
|
10804
|
+
profile,
|
|
10902
10805
|
fix: false
|
|
10903
10806
|
}) {
|
|
10904
10807
|
console.error(`${command}: telemetry calling report_cli_run`);
|
|
@@ -10962,7 +10865,7 @@ async function main() {
|
|
|
10962
10865
|
console.error(`${mode}: begin argv=[${args.join(" ")}] version=${getVersion()} traceId=${traceId ?? "-"} caller=${caller ?? "-"} runIdGenerated=${rc.generated}`);
|
|
10963
10866
|
switch (mode) {
|
|
10964
10867
|
case "check": {
|
|
10965
|
-
const raw = await fetchCtxViaInnerApi({
|
|
10868
|
+
const raw = parseCtxFlag(args) ?? await fetchCtxViaInnerApi({
|
|
10966
10869
|
populate: planCtxPopulate({
|
|
10967
10870
|
command: "check",
|
|
10968
10871
|
profile
|
|
@@ -10987,7 +10890,7 @@ async function main() {
|
|
|
10987
10890
|
break;
|
|
10988
10891
|
}
|
|
10989
10892
|
case "repair": {
|
|
10990
|
-
const raw = await fetchCtxViaInnerApi({
|
|
10893
|
+
const raw = parseCtxFlag(args) ?? await fetchCtxViaInnerApi({
|
|
10991
10894
|
populate: planCtxPopulate({
|
|
10992
10895
|
command: "repair",
|
|
10993
10896
|
profile
|
|
@@ -11058,15 +10961,27 @@ async function main() {
|
|
|
11058
10961
|
break;
|
|
11059
10962
|
}
|
|
11060
10963
|
case "reset":
|
|
11061
|
-
if (args.includes("--async"))
|
|
11062
|
-
|
|
10964
|
+
if (args.includes("--async")) {
|
|
10965
|
+
const ctxArg = args.find((a) => a.startsWith("--ctx="));
|
|
10966
|
+
let ctxBase64;
|
|
10967
|
+
if (ctxArg) ctxBase64 = ctxArg.slice(6);
|
|
10968
|
+
else {
|
|
10969
|
+
const fetched = await fetchCtxViaInnerApi({
|
|
10970
|
+
populate: planCtxPopulate({ command: "reset" }),
|
|
10971
|
+
caller,
|
|
10972
|
+
traceId
|
|
10973
|
+
});
|
|
10974
|
+
ctxBase64 = Buffer.from(JSON.stringify(fetched), "utf-8").toString("base64");
|
|
10975
|
+
}
|
|
10976
|
+
console.log(JSON.stringify(startAsyncReset(ctxBase64)));
|
|
10977
|
+
} else if (args.includes("--worker")) {
|
|
11063
10978
|
const taskId = args.find((a) => a.startsWith("--task-id="))?.slice(10);
|
|
11064
10979
|
if (!taskId) {
|
|
11065
10980
|
console.error("Error: --task-id=<id> is required for worker");
|
|
11066
10981
|
node_process.default.exit(1);
|
|
11067
10982
|
}
|
|
11068
10983
|
const resultFile = resetResultFile(taskId);
|
|
11069
|
-
const raw = await fetchCtxViaInnerApi({
|
|
10984
|
+
const raw = parseCtxFlag(args) ?? await fetchCtxViaInnerApi({
|
|
11070
10985
|
populate: planCtxPopulate({ command: "reset" }),
|
|
11071
10986
|
caller,
|
|
11072
10987
|
traceId
|
|
@@ -11090,7 +11005,7 @@ async function main() {
|
|
|
11090
11005
|
return;
|
|
11091
11006
|
}
|
|
11092
11007
|
} else {
|
|
11093
|
-
console.error("Usage: reset --async | reset --worker --task-id=<id>");
|
|
11008
|
+
console.error("Usage: reset --async [--ctx=<base64>] | reset --worker --task-id=<id> [--ctx=<base64>]");
|
|
11094
11009
|
node_process.default.exit(1);
|
|
11095
11010
|
}
|
|
11096
11011
|
break;
|
|
@@ -11106,14 +11021,14 @@ async function main() {
|
|
|
11106
11021
|
case "install-openclaw": {
|
|
11107
11022
|
const tag = getPositionalTag(args, "install-openclaw");
|
|
11108
11023
|
if (!tag) {
|
|
11109
|
-
console.error("Usage: install-openclaw <tag> [--oss_file_map=<base64>]");
|
|
11024
|
+
console.error("Usage: install-openclaw <tag> [--ctx=<base64> | --oss_file_map=<base64>]");
|
|
11110
11025
|
node_process.default.exit(1);
|
|
11111
11026
|
}
|
|
11112
11027
|
const ossFileMapFlag = getFlag(args, "oss_file_map");
|
|
11113
11028
|
let installOssFileMap;
|
|
11114
11029
|
let rawForTelemetry;
|
|
11115
11030
|
if (!ossFileMapFlag) {
|
|
11116
|
-
rawForTelemetry = await fetchCtxViaInnerApi({
|
|
11031
|
+
rawForTelemetry = parseCtxFlag(args) ?? await fetchCtxViaInnerApi({
|
|
11117
11032
|
populate: planCtxPopulate({ command: "install" }),
|
|
11118
11033
|
caller,
|
|
11119
11034
|
traceId
|
|
@@ -11148,7 +11063,7 @@ async function main() {
|
|
|
11148
11063
|
case "install-extension": {
|
|
11149
11064
|
const tag = getPositionalTag(args, "install-extension");
|
|
11150
11065
|
if (!tag) {
|
|
11151
|
-
console.error("Usage: install-extension <tag> (--all | --extension=<name>...) [--home_base=<dir>] [--config_path=<path>] [--skip-config-update] [--oss_file_map=<base64>]");
|
|
11066
|
+
console.error("Usage: install-extension <tag> (--all | --extension=<name>...) [--home_base=<dir>] [--config_path=<path>] [--skip-config-update] [--ctx=<base64> | --oss_file_map=<base64>]");
|
|
11152
11067
|
node_process.default.exit(1);
|
|
11153
11068
|
}
|
|
11154
11069
|
const all = args.includes("--all");
|
|
@@ -11160,7 +11075,7 @@ async function main() {
|
|
|
11160
11075
|
let installOssFileMap;
|
|
11161
11076
|
let rawForTelemetry;
|
|
11162
11077
|
if (!ossFileMapFlag) {
|
|
11163
|
-
rawForTelemetry = await fetchCtxViaInnerApi({
|
|
11078
|
+
rawForTelemetry = parseCtxFlag(args) ?? await fetchCtxViaInnerApi({
|
|
11164
11079
|
populate: planCtxPopulate({ command: "install" }),
|
|
11165
11080
|
caller,
|
|
11166
11081
|
traceId
|
|
@@ -11206,12 +11121,12 @@ async function main() {
|
|
|
11206
11121
|
case "install-cli": {
|
|
11207
11122
|
const tag = getPositionalTag(args, "install-cli");
|
|
11208
11123
|
if (!tag) {
|
|
11209
|
-
console.error("Usage: install-cli <tag> --cli=<name>... [--home_base=<dir>] [--oss_file_map=<base64>]");
|
|
11124
|
+
console.error("Usage: install-cli <tag> --cli=<name>... [--home_base=<dir>] [--ctx=<base64> | --oss_file_map=<base64>]");
|
|
11210
11125
|
node_process.default.exit(1);
|
|
11211
11126
|
}
|
|
11212
11127
|
const names = getMultiFlag(args, "cli");
|
|
11213
11128
|
if (names.length === 0) {
|
|
11214
|
-
console.error("Usage: install-cli <tag> --cli=<name>... [--home_base=<dir>] [--oss_file_map=<base64>]");
|
|
11129
|
+
console.error("Usage: install-cli <tag> --cli=<name>... [--home_base=<dir>] [--ctx=<base64> | --oss_file_map=<base64>]");
|
|
11215
11130
|
node_process.default.exit(1);
|
|
11216
11131
|
}
|
|
11217
11132
|
const homeBase = getFlag(args, "home_base");
|
|
@@ -11219,7 +11134,7 @@ async function main() {
|
|
|
11219
11134
|
let installOssFileMap;
|
|
11220
11135
|
let rawForTelemetry;
|
|
11221
11136
|
if (!ossFileMapFlag) {
|
|
11222
|
-
rawForTelemetry = await fetchCtxViaInnerApi({
|
|
11137
|
+
rawForTelemetry = parseCtxFlag(args) ?? await fetchCtxViaInnerApi({
|
|
11223
11138
|
populate: planCtxPopulate({ command: "install" }),
|
|
11224
11139
|
caller,
|
|
11225
11140
|
traceId
|
|
@@ -11267,7 +11182,7 @@ async function main() {
|
|
|
11267
11182
|
case "download-resource": {
|
|
11268
11183
|
const tag = getPositionalTag(args, "download-resource");
|
|
11269
11184
|
if (!tag) {
|
|
11270
|
-
console.error("Usage: download-resource <tag> --role=<role> --name=<name> [--dir=<dir>] [--oss_file_map=<base64>]");
|
|
11185
|
+
console.error("Usage: download-resource <tag> --role=<role> --name=<name> [--dir=<dir>] [--ctx=<base64> | --oss_file_map=<base64>]");
|
|
11271
11186
|
node_process.default.exit(1);
|
|
11272
11187
|
}
|
|
11273
11188
|
const role = getFlag(args, "role");
|
|
@@ -11281,7 +11196,7 @@ async function main() {
|
|
|
11281
11196
|
let installOssFileMap;
|
|
11282
11197
|
let rawForTelemetry;
|
|
11283
11198
|
if (!ossFileMapFlag) {
|
|
11284
|
-
rawForTelemetry = await fetchCtxViaInnerApi({
|
|
11199
|
+
rawForTelemetry = parseCtxFlag(args) ?? await fetchCtxViaInnerApi({
|
|
11285
11200
|
populate: planCtxPopulate({ command: "install" }),
|
|
11286
11201
|
caller,
|
|
11287
11202
|
traceId
|