@lark-apaas/openclaw-scripts-diagnose-cli 0.1.6 → 0.1.7-alpha.1
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 +533 -334
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -50,7 +50,7 @@ node_assert = __toESM(node_assert);
|
|
|
50
50
|
* it terse and parseable.
|
|
51
51
|
*/
|
|
52
52
|
function getVersion() {
|
|
53
|
-
return "0.1.
|
|
53
|
+
return "0.1.7-alpha.1";
|
|
54
54
|
}
|
|
55
55
|
//#endregion
|
|
56
56
|
//#region src/rule-engine/base.ts
|
|
@@ -4134,7 +4134,7 @@ async function installExtension(tag, ossFileMap, opts = {}) {
|
|
|
4134
4134
|
};
|
|
4135
4135
|
}));
|
|
4136
4136
|
for (const { pkg, tarball } of tarballs) {
|
|
4137
|
-
installOne(pkg, tarball, homeBase);
|
|
4137
|
+
installOne$1(pkg, tarball, homeBase);
|
|
4138
4138
|
console.error(`[install-extension] ${pkg.name}: installed`);
|
|
4139
4139
|
}
|
|
4140
4140
|
if (!opts.skipConfigUpdate) updatePluginInstalls(opts.configPath ?? node_path.default.join(homeBase, "workspace/agent/openclaw.json"), targets);
|
|
@@ -4187,7 +4187,7 @@ function updatePluginInstalls(configPath, installedPkgs) {
|
|
|
4187
4187
|
moveSafe(tmpPath, configPath);
|
|
4188
4188
|
console.error(`[install-extension] plugins.installs updated: ${updated} entry(ies) in ${configPath}` + (skipped > 0 ? ` (${skipped} package(s) without installMetadata skipped)` : ""));
|
|
4189
4189
|
}
|
|
4190
|
-
function installOne(pkg, tarball, homeBase) {
|
|
4190
|
+
function installOne$1(pkg, tarball, homeBase) {
|
|
4191
4191
|
const destDir = node_path.default.join(homeBase, pkg.installPath);
|
|
4192
4192
|
const stagingDir = destDir + ".new";
|
|
4193
4193
|
const oldDir = destDir + ".old";
|
|
@@ -4729,6 +4729,356 @@ function sleepSync(ms) {
|
|
|
4729
4729
|
Atomics.wait(arr, 0, 0, ms);
|
|
4730
4730
|
}
|
|
4731
4731
|
//#endregion
|
|
4732
|
+
//#region src/lark-cli-init.ts
|
|
4733
|
+
const LARK_PLUGIN_NAMES = ["openclaw-lark", "feishu-openclaw-plugin"];
|
|
4734
|
+
const PE_XML_TAG = "lark-cli-pe";
|
|
4735
|
+
const PE_PLACEHOLDER = `
|
|
4736
|
+
<${PE_XML_TAG}>
|
|
4737
|
+
**【强制要求 - 无例外】飞书所有操作必须先读 feishu-lark-cli skill。未读skill不得调用任何飞书工具,违者视为操作失误。**
|
|
4738
|
+
</${PE_XML_TAG}>
|
|
4739
|
+
`;
|
|
4740
|
+
function isLarkPluginInstalled(configPath) {
|
|
4741
|
+
const extDir = getExtensionsDir(configPath);
|
|
4742
|
+
return LARK_PLUGIN_NAMES.some((name) => {
|
|
4743
|
+
try {
|
|
4744
|
+
return node_fs.default.existsSync(node_path.default.join(extDir, name, "package.json"));
|
|
4745
|
+
} catch {
|
|
4746
|
+
return false;
|
|
4747
|
+
}
|
|
4748
|
+
});
|
|
4749
|
+
}
|
|
4750
|
+
function isLarkCliAvailable() {
|
|
4751
|
+
try {
|
|
4752
|
+
return (0, node_child_process.spawnSync)("lark-cli", ["--version"], {
|
|
4753
|
+
encoding: "utf-8",
|
|
4754
|
+
timeout: 5e3,
|
|
4755
|
+
stdio: [
|
|
4756
|
+
"ignore",
|
|
4757
|
+
"pipe",
|
|
4758
|
+
"ignore"
|
|
4759
|
+
]
|
|
4760
|
+
}).status === 0;
|
|
4761
|
+
} catch {
|
|
4762
|
+
return false;
|
|
4763
|
+
}
|
|
4764
|
+
}
|
|
4765
|
+
function readConfig(configPath) {
|
|
4766
|
+
try {
|
|
4767
|
+
const raw = node_fs.default.readFileSync(configPath, "utf-8");
|
|
4768
|
+
const parsed = loadJSON5().parse(raw);
|
|
4769
|
+
return parsed != null && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
|
|
4770
|
+
} catch {
|
|
4771
|
+
return null;
|
|
4772
|
+
}
|
|
4773
|
+
}
|
|
4774
|
+
/**
|
|
4775
|
+
* Resolve the feishu app secret for the given appId.
|
|
4776
|
+
*
|
|
4777
|
+
* Lookup order:
|
|
4778
|
+
* 1. channels.feishu.appSecret (single-agent: feishu.appId === appId)
|
|
4779
|
+
* 2. channels.feishu.accounts[key].appSecret (multi-agent: account.appId === appId)
|
|
4780
|
+
*
|
|
4781
|
+
* Value interpretation:
|
|
4782
|
+
* - string → use directly
|
|
4783
|
+
* - object → secret is managed by a provider; use `feishuAppSecret` param instead
|
|
4784
|
+
*
|
|
4785
|
+
* Returns null when the secret cannot be determined.
|
|
4786
|
+
*/
|
|
4787
|
+
function resolveAppSecret(appId, config, feishuAppSecret) {
|
|
4788
|
+
const feishu = getNestedMap(config, "channels", "feishu");
|
|
4789
|
+
if (!feishu) return null;
|
|
4790
|
+
let rawSecret;
|
|
4791
|
+
if (typeof feishu.appId === "string" && feishu.appId === appId) rawSecret = feishu.appSecret;
|
|
4792
|
+
else {
|
|
4793
|
+
const accounts = asRecord(feishu.accounts);
|
|
4794
|
+
if (accounts) for (const [, val] of Object.entries(accounts)) {
|
|
4795
|
+
const account = asRecord(val);
|
|
4796
|
+
if (account?.appId === appId) {
|
|
4797
|
+
rawSecret = account.appSecret ?? feishu.appSecret;
|
|
4798
|
+
break;
|
|
4799
|
+
}
|
|
4800
|
+
}
|
|
4801
|
+
}
|
|
4802
|
+
if (typeof rawSecret === "string" && rawSecret) return rawSecret;
|
|
4803
|
+
if (rawSecret != null && typeof rawSecret === "object") return feishuAppSecret ?? null;
|
|
4804
|
+
return null;
|
|
4805
|
+
}
|
|
4806
|
+
/**
|
|
4807
|
+
* Resolve the agents.md path for the given appId from the openclaw config.
|
|
4808
|
+
*
|
|
4809
|
+
* Case 1: appId matches channels.feishu.appId (single-agent path)
|
|
4810
|
+
* → WORKSPACE_DIR/AGENTS.md
|
|
4811
|
+
*
|
|
4812
|
+
* Case 2: appId found in channels.feishu.accounts (multi-agent path)
|
|
4813
|
+
* → find account key where account.appId === appId
|
|
4814
|
+
* → find binding where match.channel=feishu && match.accountId=that key
|
|
4815
|
+
* → if agentId === 'main' → WORKSPACE_DIR/agents.md
|
|
4816
|
+
* → else find agent in agents.list by id → agent.workspace/agents.md
|
|
4817
|
+
*
|
|
4818
|
+
* Returns null when the path cannot be determined.
|
|
4819
|
+
*/
|
|
4820
|
+
function resolveAgentsMdPath(appId, config) {
|
|
4821
|
+
const feishu = getNestedMap(config, "channels", "feishu");
|
|
4822
|
+
if (!feishu) {
|
|
4823
|
+
console.error("resolveAgentsMdPath: channels.feishu not found");
|
|
4824
|
+
return null;
|
|
4825
|
+
}
|
|
4826
|
+
if (typeof feishu.appId === "string" && feishu.appId === appId) {
|
|
4827
|
+
console.error(`resolveAgentsMdPath: case=single-agent feishu.appId=${feishu.appId}`);
|
|
4828
|
+
return node_path.default.join(WORKSPACE_DIR, "workspace", "AGENTS.md");
|
|
4829
|
+
}
|
|
4830
|
+
const accounts = asRecord(feishu.accounts);
|
|
4831
|
+
if (!accounts) {
|
|
4832
|
+
console.error("resolveAgentsMdPath: feishu.accounts not found");
|
|
4833
|
+
return null;
|
|
4834
|
+
}
|
|
4835
|
+
let accountId;
|
|
4836
|
+
for (const [key, val] of Object.entries(accounts)) if (asRecord(val)?.appId === appId) {
|
|
4837
|
+
accountId = key;
|
|
4838
|
+
break;
|
|
4839
|
+
}
|
|
4840
|
+
if (!accountId) {
|
|
4841
|
+
console.error(`resolveAgentsMdPath: no account found with appId=${appId} in feishu.accounts keys=[${Object.keys(accounts).join(",")}]`);
|
|
4842
|
+
return null;
|
|
4843
|
+
}
|
|
4844
|
+
console.error(`resolveAgentsMdPath: found accountId=${accountId}`);
|
|
4845
|
+
const bindings = Array.isArray(config.bindings) ? config.bindings : [];
|
|
4846
|
+
let agentId;
|
|
4847
|
+
for (const b of bindings) {
|
|
4848
|
+
const binding = asRecord(b);
|
|
4849
|
+
if (!binding) continue;
|
|
4850
|
+
const match = asRecord(binding.match);
|
|
4851
|
+
if (match?.channel === "feishu" && match?.accountId === accountId) {
|
|
4852
|
+
if (typeof binding.agentId === "string") {
|
|
4853
|
+
agentId = binding.agentId;
|
|
4854
|
+
break;
|
|
4855
|
+
}
|
|
4856
|
+
}
|
|
4857
|
+
}
|
|
4858
|
+
if (!agentId) {
|
|
4859
|
+
console.error(`resolveAgentsMdPath: no binding found for accountId=${accountId} in bindings(count=${bindings.length})`);
|
|
4860
|
+
return null;
|
|
4861
|
+
}
|
|
4862
|
+
console.error(`resolveAgentsMdPath: found agentId=${agentId}`);
|
|
4863
|
+
if (agentId === "main") {
|
|
4864
|
+
console.error("resolveAgentsMdPath: case=multi-agent-main");
|
|
4865
|
+
return node_path.default.join(WORKSPACE_DIR, "workspace", "AGENTS.md");
|
|
4866
|
+
}
|
|
4867
|
+
const agentsObj = asRecord(config.agents);
|
|
4868
|
+
const list = Array.isArray(agentsObj?.list) ? agentsObj.list : [];
|
|
4869
|
+
for (const a of list) {
|
|
4870
|
+
const agent = asRecord(a);
|
|
4871
|
+
if (agent?.id === agentId) {
|
|
4872
|
+
const ws = typeof agent.workspace === "string" ? agent.workspace : node_path.default.join(WORKSPACE_DIR, "workspace");
|
|
4873
|
+
console.error(`resolveAgentsMdPath: case=multi-agent-custom agentId=${agentId} workspace=${ws}`);
|
|
4874
|
+
return node_path.default.join(ws, "AGENTS.md");
|
|
4875
|
+
}
|
|
4876
|
+
}
|
|
4877
|
+
console.error(`resolveAgentsMdPath: agentId=${agentId} not found in agents.list(count=${list.length})`);
|
|
4878
|
+
return null;
|
|
4879
|
+
}
|
|
4880
|
+
function appendPeToAgentsMd(agentsMdPath) {
|
|
4881
|
+
const dir = node_path.default.dirname(agentsMdPath);
|
|
4882
|
+
if (!node_fs.default.existsSync(dir)) node_fs.default.mkdirSync(dir, { recursive: true });
|
|
4883
|
+
const existing = node_fs.default.existsSync(agentsMdPath) ? node_fs.default.readFileSync(agentsMdPath, "utf-8") : "";
|
|
4884
|
+
if (existing.includes(`<${PE_XML_TAG}>`)) {
|
|
4885
|
+
console.error(`lark-cli-init: <${PE_XML_TAG}> already present in ${agentsMdPath}, skipping`);
|
|
4886
|
+
return;
|
|
4887
|
+
}
|
|
4888
|
+
const sep = existing.length > 0 && !existing.endsWith("\n") ? "\n" : "";
|
|
4889
|
+
node_fs.default.appendFileSync(agentsMdPath, `${sep}${PE_PLACEHOLDER}`, "utf-8");
|
|
4890
|
+
console.error(`lark-cli-init: appended PE placeholder to ${agentsMdPath}`);
|
|
4891
|
+
}
|
|
4892
|
+
/**
|
|
4893
|
+
* Collect every Feishu bot appId declared in the openclaw config.
|
|
4894
|
+
* Covers both single-agent (channels.feishu.appId) and multi-agent
|
|
4895
|
+
* (channels.feishu.accounts[*].appId) layouts. Returns a deduplicated list.
|
|
4896
|
+
*/
|
|
4897
|
+
function collectFeishuAppIds(configPath) {
|
|
4898
|
+
const config = readConfig(configPath ?? CONFIG_PATH);
|
|
4899
|
+
if (!config) return [];
|
|
4900
|
+
const feishu = getNestedMap(config, "channels", "feishu");
|
|
4901
|
+
if (!feishu) return [];
|
|
4902
|
+
const appIds = /* @__PURE__ */ new Set();
|
|
4903
|
+
if (typeof feishu.appId === "string" && feishu.appId) appIds.add(feishu.appId);
|
|
4904
|
+
const accounts = asRecord(feishu.accounts);
|
|
4905
|
+
if (accounts) for (const val of Object.values(accounts)) {
|
|
4906
|
+
const account = asRecord(val);
|
|
4907
|
+
if (typeof account?.appId === "string" && account.appId) appIds.add(account.appId);
|
|
4908
|
+
}
|
|
4909
|
+
return [...appIds];
|
|
4910
|
+
}
|
|
4911
|
+
function runLarkCliInit(opts) {
|
|
4912
|
+
const configPath = opts.configPath ?? CONFIG_PATH;
|
|
4913
|
+
if (!isLarkPluginInstalled(configPath)) {
|
|
4914
|
+
console.error("lark-cli-init: skipping — openclaw-lark plugin not installed");
|
|
4915
|
+
return {
|
|
4916
|
+
ok: true,
|
|
4917
|
+
skipped: true,
|
|
4918
|
+
skipReason: "openclaw-lark plugin not installed"
|
|
4919
|
+
};
|
|
4920
|
+
}
|
|
4921
|
+
if (!isLarkCliAvailable()) {
|
|
4922
|
+
console.error("lark-cli-init: skipping — lark-cli command not found");
|
|
4923
|
+
return {
|
|
4924
|
+
ok: true,
|
|
4925
|
+
skipped: true,
|
|
4926
|
+
skipReason: "lark-cli command not found"
|
|
4927
|
+
};
|
|
4928
|
+
}
|
|
4929
|
+
const config = readConfig(configPath);
|
|
4930
|
+
if (!config) return {
|
|
4931
|
+
ok: false,
|
|
4932
|
+
error: `could not read config at ${configPath}`
|
|
4933
|
+
};
|
|
4934
|
+
const agentsMdPath = resolveAgentsMdPath(opts.appId, config);
|
|
4935
|
+
console.error(`lark-cli-init: resolved agents.md path=${agentsMdPath ?? "(null)"}`);
|
|
4936
|
+
if (!agentsMdPath) return {
|
|
4937
|
+
ok: false,
|
|
4938
|
+
error: `could not resolve agents.md path for appId=${opts.appId}`
|
|
4939
|
+
};
|
|
4940
|
+
const appSecret = resolveAppSecret(opts.appId, config, opts.feishuAppSecret);
|
|
4941
|
+
if (!appSecret) return {
|
|
4942
|
+
ok: false,
|
|
4943
|
+
error: `could not resolve appSecret for appId=${opts.appId}`
|
|
4944
|
+
};
|
|
4945
|
+
console.error(`lark-cli-init: running lark-cli config init --name ${opts.appId} --app-id ${opts.appId} --brand feishu --app-secret-stdin --force-init`);
|
|
4946
|
+
const initRes = (0, node_child_process.spawnSync)("lark-cli", [
|
|
4947
|
+
"config",
|
|
4948
|
+
"init",
|
|
4949
|
+
"--name",
|
|
4950
|
+
opts.appId,
|
|
4951
|
+
"--app-id",
|
|
4952
|
+
opts.appId,
|
|
4953
|
+
"--brand",
|
|
4954
|
+
"feishu",
|
|
4955
|
+
"--app-secret-stdin",
|
|
4956
|
+
"--force-init"
|
|
4957
|
+
], {
|
|
4958
|
+
stdio: [
|
|
4959
|
+
"pipe",
|
|
4960
|
+
"pipe",
|
|
4961
|
+
"pipe"
|
|
4962
|
+
],
|
|
4963
|
+
encoding: "utf-8",
|
|
4964
|
+
input: appSecret
|
|
4965
|
+
});
|
|
4966
|
+
const configInitStdout = initRes.stdout?.trim() || void 0;
|
|
4967
|
+
const configInitStderr = initRes.stderr?.trim() || void 0;
|
|
4968
|
+
if (configInitStdout) console.error(`lark-cli config init stdout: ${configInitStdout}`);
|
|
4969
|
+
if (configInitStderr) console.error(`lark-cli config init stderr: ${configInitStderr}`);
|
|
4970
|
+
if (initRes.error) return {
|
|
4971
|
+
ok: false,
|
|
4972
|
+
configInitStdout,
|
|
4973
|
+
configInitStderr,
|
|
4974
|
+
error: `lark-cli config init spawn error: ${initRes.error.message}`
|
|
4975
|
+
};
|
|
4976
|
+
if (initRes.status !== 0) return {
|
|
4977
|
+
ok: false,
|
|
4978
|
+
configInitExitCode: initRes.status ?? void 0,
|
|
4979
|
+
configInitStdout,
|
|
4980
|
+
configInitStderr,
|
|
4981
|
+
error: `lark-cli config init exited with code ${initRes.status}`
|
|
4982
|
+
};
|
|
4983
|
+
appendPeToAgentsMd(agentsMdPath);
|
|
4984
|
+
return {
|
|
4985
|
+
ok: true,
|
|
4986
|
+
configInitExitCode: 0,
|
|
4987
|
+
agentsMdPath
|
|
4988
|
+
};
|
|
4989
|
+
}
|
|
4990
|
+
const LARK_CLI_NAME = "lark-cli";
|
|
4991
|
+
async function installClis(tag, ossFileMap, opts) {
|
|
4992
|
+
const homeBase = opts.homeBase ?? "/home/gem";
|
|
4993
|
+
if (opts.names.length === 0) throw new Error("install-clis: must provide at least one --cli=<name>");
|
|
4994
|
+
const manifest = await fetchManifest(ossFileMap, tag);
|
|
4995
|
+
console.error(`[install-clis] manifest=${JSON.stringify(manifest)}`);
|
|
4996
|
+
const allClis = manifest.packages.filter((p) => p.role === "cli" && p.name !== "openclaw");
|
|
4997
|
+
const wanted = new Set(opts.names);
|
|
4998
|
+
const targets = allClis.filter((p) => wanted.has(p.name) || p.packageName != null && wanted.has(p.packageName));
|
|
4999
|
+
const foundKeys = /* @__PURE__ */ new Set();
|
|
5000
|
+
for (const t of targets) {
|
|
5001
|
+
foundKeys.add(t.name);
|
|
5002
|
+
if (t.packageName) foundKeys.add(t.packageName);
|
|
5003
|
+
}
|
|
5004
|
+
const missing = opts.names.filter((n) => !foundKeys.has(n));
|
|
5005
|
+
if (missing.length > 0) throw new Error(`install-clis: not found in manifest: ${missing.join(", ")}`);
|
|
5006
|
+
console.error(`[install-clis] tag=${tag} targets=${targets.length}`);
|
|
5007
|
+
const t0 = Date.now();
|
|
5008
|
+
const tarballs = await Promise.all(targets.map(async (p) => {
|
|
5009
|
+
const tb = await downloadWithCache(p, ossFileMap, opts);
|
|
5010
|
+
console.error(`[install-clis] ${p.name}: downloaded`);
|
|
5011
|
+
return {
|
|
5012
|
+
pkg: p,
|
|
5013
|
+
tarball: tb
|
|
5014
|
+
};
|
|
5015
|
+
}));
|
|
5016
|
+
for (const { pkg, tarball } of tarballs) {
|
|
5017
|
+
installOne(pkg, tarball, homeBase, opts.tmpRoot);
|
|
5018
|
+
console.error(`[install-clis] ${pkg.name}: installed`);
|
|
5019
|
+
}
|
|
5020
|
+
if (targets.some((p) => p.name === LARK_CLI_NAME)) {
|
|
5021
|
+
const appIds = collectFeishuAppIds();
|
|
5022
|
+
console.error(`[install-clis] lark-cli installed — running lark-cli-init for ${appIds.length} appId(s): [${appIds.join(", ")}]`);
|
|
5023
|
+
for (const appId of appIds) {
|
|
5024
|
+
console.error(`[install-clis] lark-cli-init: appId=${appId}`);
|
|
5025
|
+
const result = runLarkCliInit({
|
|
5026
|
+
appId,
|
|
5027
|
+
feishuAppSecret: opts.feishuAppSecret
|
|
5028
|
+
});
|
|
5029
|
+
console.error(`[install-clis] lark-cli-init: appId=${appId} ok=${result.ok}` + (result.skipped ? ` skipped=true reason=${result.skipReason}` : "") + (result.error ? ` error=${result.error}` : ""));
|
|
5030
|
+
}
|
|
5031
|
+
}
|
|
5032
|
+
console.error(`[install-clis] done ${targets.length}/${targets.length} in ${Date.now() - t0}ms`);
|
|
5033
|
+
}
|
|
5034
|
+
function installOne(pkg, tarball, homeBase, tmpRoot) {
|
|
5035
|
+
const targetDir = node_path.default.join(homeBase, pkg.installPath);
|
|
5036
|
+
const bakDir = targetDir + ".bak";
|
|
5037
|
+
const newDir = targetDir + ".new";
|
|
5038
|
+
node_fs.default.mkdirSync(node_path.default.dirname(targetDir), { recursive: true });
|
|
5039
|
+
if (node_fs.default.existsSync(newDir)) node_fs.default.rmSync(newDir, {
|
|
5040
|
+
recursive: true,
|
|
5041
|
+
force: true
|
|
5042
|
+
});
|
|
5043
|
+
if (node_fs.default.existsSync(bakDir)) node_fs.default.rmSync(bakDir, {
|
|
5044
|
+
recursive: true,
|
|
5045
|
+
force: true
|
|
5046
|
+
});
|
|
5047
|
+
const tmpStage = node_fs.default.mkdtempSync(node_path.default.join(tmpRoot ?? node_os.default.tmpdir(), "cli-install-"));
|
|
5048
|
+
try {
|
|
5049
|
+
extractTarballTolerant(tarball, tmpStage, { stripComponents: 1 });
|
|
5050
|
+
if (!node_fs.default.existsSync(node_path.default.join(tmpStage, "package.json"))) throw new Error(`cli tarball missing package.json: ${pkg.name}`);
|
|
5051
|
+
moveSafe(tmpStage, newDir);
|
|
5052
|
+
const hadExisting = node_fs.default.existsSync(targetDir);
|
|
5053
|
+
try {
|
|
5054
|
+
if (hadExisting) moveSafe(targetDir, bakDir);
|
|
5055
|
+
moveSafe(newDir, targetDir);
|
|
5056
|
+
} catch (e) {
|
|
5057
|
+
if (hadExisting && !node_fs.default.existsSync(targetDir) && node_fs.default.existsSync(bakDir)) try {
|
|
5058
|
+
moveSafe(bakDir, targetDir);
|
|
5059
|
+
} catch {}
|
|
5060
|
+
try {
|
|
5061
|
+
node_fs.default.rmSync(newDir, {
|
|
5062
|
+
recursive: true,
|
|
5063
|
+
force: true
|
|
5064
|
+
});
|
|
5065
|
+
} catch {}
|
|
5066
|
+
throw e;
|
|
5067
|
+
}
|
|
5068
|
+
if (hadExisting && node_fs.default.existsSync(bakDir)) node_fs.default.rmSync(bakDir, {
|
|
5069
|
+
recursive: true,
|
|
5070
|
+
force: true
|
|
5071
|
+
});
|
|
5072
|
+
} finally {
|
|
5073
|
+
if (node_fs.default.existsSync(tmpStage)) try {
|
|
5074
|
+
node_fs.default.rmSync(tmpStage, {
|
|
5075
|
+
recursive: true,
|
|
5076
|
+
force: true
|
|
5077
|
+
});
|
|
5078
|
+
} catch {}
|
|
5079
|
+
}
|
|
5080
|
+
}
|
|
5081
|
+
//#endregion
|
|
4732
5082
|
//#region src/oss/resolveOssFileMap.ts
|
|
4733
5083
|
/**
|
|
4734
5084
|
* Pick an OssFileMap in the order of decreasing specificity:
|
|
@@ -7731,342 +8081,102 @@ async function runDoctor(rawCtx, opts) {
|
|
|
7731
8081
|
aborted = true;
|
|
7732
8082
|
break outer;
|
|
7733
8083
|
}
|
|
7734
|
-
if (v2.pass) {
|
|
7735
|
-
console.error(`rule ${key}: revalidate -> pass (fixed) durationMs=${Date.now() - tRevalidate}`);
|
|
7736
|
-
results.push({
|
|
7737
|
-
rule: key,
|
|
7738
|
-
status: "fixed",
|
|
7739
|
-
before: v1.message
|
|
7740
|
-
});
|
|
7741
|
-
} else {
|
|
7742
|
-
console.error(`rule ${key}: revalidate -> still-broken durationMs=${Date.now() - tRevalidate} after=${v2.message ?? ""}`);
|
|
7743
|
-
results.push({
|
|
7744
|
-
rule: key,
|
|
7745
|
-
status: "still-broken",
|
|
7746
|
-
before: v1.message,
|
|
7747
|
-
after: v2.message
|
|
7748
|
-
});
|
|
7749
|
-
failedKeys.add(key);
|
|
7750
|
-
}
|
|
7751
|
-
}
|
|
7752
|
-
let backupPath = null;
|
|
7753
|
-
if (configDirty && !aborted) {
|
|
7754
|
-
backupPath = backupConfigSync(ctx.configPath);
|
|
7755
|
-
const serialized = JSON.stringify(ctx.config, null, 2);
|
|
7756
|
-
try {
|
|
7757
|
-
writeFile(ctx.configPath, serialized);
|
|
7758
|
-
console.error(`runDoctor: writeback ok path=${ctx.configPath} bytes=${serialized.length}`);
|
|
7759
|
-
} catch (e) {
|
|
7760
|
-
const msg = e.message;
|
|
7761
|
-
console.error(`runDoctor: writeback failed path=${ctx.configPath} message=${msg}`);
|
|
7762
|
-
results.push({
|
|
7763
|
-
rule: "*config-writeback*",
|
|
7764
|
-
status: "error",
|
|
7765
|
-
message: "config write failed: " + msg
|
|
7766
|
-
});
|
|
7767
|
-
aborted = true;
|
|
7768
|
-
}
|
|
7769
|
-
} else if (configDirty && aborted) console.error("runDoctor: writeback skipped (aborted, leaving on-disk config untouched)");
|
|
7770
|
-
else console.error("runDoctor: writeback skipped (no rule transitioned to fixed)");
|
|
7771
|
-
console.error(`runDoctor: end aborted=${aborted} results=${results.length}`);
|
|
7772
|
-
if (originalConfig !== null) {
|
|
7773
|
-
const d = (0, import_lib.diffString)(originalConfig, ctx.config, { color: false });
|
|
7774
|
-
console.error(`runDoctor: diff backupPath=${backupPath ?? "(none)"} changed=${!!d}`);
|
|
7775
|
-
if (d) {
|
|
7776
|
-
process.stdout.write(`original: ${backupPath ?? "(none)"}\n`);
|
|
7777
|
-
process.stdout.write(`after fixed: ${ctx.configPath}\n`);
|
|
7778
|
-
process.stdout.write("diff:\n");
|
|
7779
|
-
process.stdout.write(d);
|
|
7780
|
-
if (!d.endsWith("\n")) process.stdout.write("\n");
|
|
7781
|
-
} else process.stdout.write("(no openclaw.json changes)\n");
|
|
7782
|
-
}
|
|
7783
|
-
return finalize(results, aborted);
|
|
7784
|
-
}
|
|
7785
|
-
/** Deep-clone a JSON-shaped value. Uses structuredClone where available
|
|
7786
|
-
* (Node 17+); falls back to JSON round-trip otherwise. The config we
|
|
7787
|
-
* clone here is JSON5-parsed so it's strictly tree-shaped — no
|
|
7788
|
-
* references, no functions — making both paths equivalent. */
|
|
7789
|
-
function deepClone(v) {
|
|
7790
|
-
if (typeof structuredClone === "function") return structuredClone(v);
|
|
7791
|
-
return JSON.parse(JSON.stringify(v));
|
|
7792
|
-
}
|
|
7793
|
-
function finalize(results, aborted) {
|
|
7794
|
-
const summary = {
|
|
7795
|
-
pass: 0,
|
|
7796
|
-
failed: 0,
|
|
7797
|
-
fixed: 0,
|
|
7798
|
-
stillBroken: 0,
|
|
7799
|
-
skipped: 0,
|
|
7800
|
-
error: 0,
|
|
7801
|
-
unknown: 0
|
|
7802
|
-
};
|
|
7803
|
-
for (const r of results) switch (r.status) {
|
|
7804
|
-
case "pass":
|
|
7805
|
-
summary.pass++;
|
|
7806
|
-
break;
|
|
7807
|
-
case "failed":
|
|
7808
|
-
summary.failed++;
|
|
7809
|
-
break;
|
|
7810
|
-
case "fixed":
|
|
7811
|
-
summary.fixed++;
|
|
7812
|
-
break;
|
|
7813
|
-
case "still-broken":
|
|
7814
|
-
summary.stillBroken++;
|
|
7815
|
-
break;
|
|
7816
|
-
case "skipped":
|
|
7817
|
-
summary.skipped++;
|
|
7818
|
-
break;
|
|
7819
|
-
case "error":
|
|
7820
|
-
summary.error++;
|
|
7821
|
-
break;
|
|
7822
|
-
case "unknown":
|
|
7823
|
-
summary.unknown++;
|
|
7824
|
-
break;
|
|
7825
|
-
}
|
|
7826
|
-
return {
|
|
7827
|
-
results,
|
|
7828
|
-
summary,
|
|
7829
|
-
aborted
|
|
7830
|
-
};
|
|
7831
|
-
}
|
|
7832
|
-
//#endregion
|
|
7833
|
-
//#region src/lark-cli-init.ts
|
|
7834
|
-
const LARK_PLUGIN_NAMES = ["openclaw-lark", "feishu-openclaw-plugin"];
|
|
7835
|
-
const PE_XML_TAG = "lark-cli-pe";
|
|
7836
|
-
const PE_PLACEHOLDER = `
|
|
7837
|
-
<${PE_XML_TAG}>
|
|
7838
|
-
**【强制要求 - 无例外】飞书所有操作必须先读 feishu-lark-cli skill。未读skill不得调用任何飞书工具,违者视为操作失误。**
|
|
7839
|
-
</${PE_XML_TAG}>
|
|
7840
|
-
`;
|
|
7841
|
-
function isLarkPluginInstalled(configPath) {
|
|
7842
|
-
const extDir = getExtensionsDir(configPath);
|
|
7843
|
-
return LARK_PLUGIN_NAMES.some((name) => {
|
|
7844
|
-
try {
|
|
7845
|
-
return node_fs.default.existsSync(node_path.default.join(extDir, name, "package.json"));
|
|
7846
|
-
} catch {
|
|
7847
|
-
return false;
|
|
7848
|
-
}
|
|
7849
|
-
});
|
|
7850
|
-
}
|
|
7851
|
-
function isLarkCliAvailable() {
|
|
7852
|
-
try {
|
|
7853
|
-
return (0, node_child_process.spawnSync)("lark-cli", ["--version"], {
|
|
7854
|
-
encoding: "utf-8",
|
|
7855
|
-
timeout: 5e3,
|
|
7856
|
-
stdio: [
|
|
7857
|
-
"ignore",
|
|
7858
|
-
"pipe",
|
|
7859
|
-
"ignore"
|
|
7860
|
-
]
|
|
7861
|
-
}).status === 0;
|
|
7862
|
-
} catch {
|
|
7863
|
-
return false;
|
|
7864
|
-
}
|
|
7865
|
-
}
|
|
7866
|
-
function readConfig(configPath) {
|
|
7867
|
-
try {
|
|
7868
|
-
const raw = node_fs.default.readFileSync(configPath, "utf-8");
|
|
7869
|
-
const parsed = loadJSON5().parse(raw);
|
|
7870
|
-
return parsed != null && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
|
|
7871
|
-
} catch {
|
|
7872
|
-
return null;
|
|
7873
|
-
}
|
|
7874
|
-
}
|
|
7875
|
-
/**
|
|
7876
|
-
* Resolve the feishu app secret for the given appId.
|
|
7877
|
-
*
|
|
7878
|
-
* Lookup order:
|
|
7879
|
-
* 1. channels.feishu.appSecret (single-agent: feishu.appId === appId)
|
|
7880
|
-
* 2. channels.feishu.accounts[key].appSecret (multi-agent: account.appId === appId)
|
|
7881
|
-
*
|
|
7882
|
-
* Value interpretation:
|
|
7883
|
-
* - string → use directly
|
|
7884
|
-
* - object → secret is managed by a provider; use `feishuAppSecret` param instead
|
|
7885
|
-
*
|
|
7886
|
-
* Returns null when the secret cannot be determined.
|
|
7887
|
-
*/
|
|
7888
|
-
function resolveAppSecret(appId, config, feishuAppSecret) {
|
|
7889
|
-
const feishu = getNestedMap(config, "channels", "feishu");
|
|
7890
|
-
if (!feishu) return null;
|
|
7891
|
-
let rawSecret;
|
|
7892
|
-
if (typeof feishu.appId === "string" && feishu.appId === appId) rawSecret = feishu.appSecret;
|
|
7893
|
-
else {
|
|
7894
|
-
const accounts = asRecord(feishu.accounts);
|
|
7895
|
-
if (accounts) for (const [, val] of Object.entries(accounts)) {
|
|
7896
|
-
const account = asRecord(val);
|
|
7897
|
-
if (account?.appId === appId) {
|
|
7898
|
-
rawSecret = account.appSecret ?? feishu.appSecret;
|
|
7899
|
-
break;
|
|
7900
|
-
}
|
|
7901
|
-
}
|
|
7902
|
-
}
|
|
7903
|
-
if (typeof rawSecret === "string" && rawSecret) return rawSecret;
|
|
7904
|
-
if (rawSecret != null && typeof rawSecret === "object") return feishuAppSecret ?? null;
|
|
7905
|
-
return null;
|
|
7906
|
-
}
|
|
7907
|
-
/**
|
|
7908
|
-
* Resolve the agents.md path for the given appId from the openclaw config.
|
|
7909
|
-
*
|
|
7910
|
-
* Case 1: appId matches channels.feishu.appId (single-agent path)
|
|
7911
|
-
* → WORKSPACE_DIR/AGENTS.md
|
|
7912
|
-
*
|
|
7913
|
-
* Case 2: appId found in channels.feishu.accounts (multi-agent path)
|
|
7914
|
-
* → find account key where account.appId === appId
|
|
7915
|
-
* → find binding where match.channel=feishu && match.accountId=that key
|
|
7916
|
-
* → if agentId === 'main' → WORKSPACE_DIR/agents.md
|
|
7917
|
-
* → else find agent in agents.list by id → agent.workspace/agents.md
|
|
7918
|
-
*
|
|
7919
|
-
* Returns null when the path cannot be determined.
|
|
7920
|
-
*/
|
|
7921
|
-
function resolveAgentsMdPath(appId, config) {
|
|
7922
|
-
const feishu = getNestedMap(config, "channels", "feishu");
|
|
7923
|
-
if (!feishu) {
|
|
7924
|
-
console.error("resolveAgentsMdPath: channels.feishu not found");
|
|
7925
|
-
return null;
|
|
7926
|
-
}
|
|
7927
|
-
if (typeof feishu.appId === "string" && feishu.appId === appId) {
|
|
7928
|
-
console.error(`resolveAgentsMdPath: case=single-agent feishu.appId=${feishu.appId}`);
|
|
7929
|
-
return node_path.default.join(WORKSPACE_DIR, "workspace", "AGENTS.md");
|
|
7930
|
-
}
|
|
7931
|
-
const accounts = asRecord(feishu.accounts);
|
|
7932
|
-
if (!accounts) {
|
|
7933
|
-
console.error("resolveAgentsMdPath: feishu.accounts not found");
|
|
7934
|
-
return null;
|
|
7935
|
-
}
|
|
7936
|
-
let accountId;
|
|
7937
|
-
for (const [key, val] of Object.entries(accounts)) if (asRecord(val)?.appId === appId) {
|
|
7938
|
-
accountId = key;
|
|
7939
|
-
break;
|
|
7940
|
-
}
|
|
7941
|
-
if (!accountId) {
|
|
7942
|
-
console.error(`resolveAgentsMdPath: no account found with appId=${appId} in feishu.accounts keys=[${Object.keys(accounts).join(",")}]`);
|
|
7943
|
-
return null;
|
|
7944
|
-
}
|
|
7945
|
-
console.error(`resolveAgentsMdPath: found accountId=${accountId}`);
|
|
7946
|
-
const bindings = Array.isArray(config.bindings) ? config.bindings : [];
|
|
7947
|
-
let agentId;
|
|
7948
|
-
for (const b of bindings) {
|
|
7949
|
-
const binding = asRecord(b);
|
|
7950
|
-
if (!binding) continue;
|
|
7951
|
-
const match = asRecord(binding.match);
|
|
7952
|
-
if (match?.channel === "feishu" && match?.accountId === accountId) {
|
|
7953
|
-
if (typeof binding.agentId === "string") {
|
|
7954
|
-
agentId = binding.agentId;
|
|
7955
|
-
break;
|
|
7956
|
-
}
|
|
7957
|
-
}
|
|
7958
|
-
}
|
|
7959
|
-
if (!agentId) {
|
|
7960
|
-
console.error(`resolveAgentsMdPath: no binding found for accountId=${accountId} in bindings(count=${bindings.length})`);
|
|
7961
|
-
return null;
|
|
7962
|
-
}
|
|
7963
|
-
console.error(`resolveAgentsMdPath: found agentId=${agentId}`);
|
|
7964
|
-
if (agentId === "main") {
|
|
7965
|
-
console.error("resolveAgentsMdPath: case=multi-agent-main");
|
|
7966
|
-
return node_path.default.join(WORKSPACE_DIR, "workspace", "AGENTS.md");
|
|
8084
|
+
if (v2.pass) {
|
|
8085
|
+
console.error(`rule ${key}: revalidate -> pass (fixed) durationMs=${Date.now() - tRevalidate}`);
|
|
8086
|
+
results.push({
|
|
8087
|
+
rule: key,
|
|
8088
|
+
status: "fixed",
|
|
8089
|
+
before: v1.message
|
|
8090
|
+
});
|
|
8091
|
+
} else {
|
|
8092
|
+
console.error(`rule ${key}: revalidate -> still-broken durationMs=${Date.now() - tRevalidate} after=${v2.message ?? ""}`);
|
|
8093
|
+
results.push({
|
|
8094
|
+
rule: key,
|
|
8095
|
+
status: "still-broken",
|
|
8096
|
+
before: v1.message,
|
|
8097
|
+
after: v2.message
|
|
8098
|
+
});
|
|
8099
|
+
failedKeys.add(key);
|
|
8100
|
+
}
|
|
7967
8101
|
}
|
|
7968
|
-
|
|
7969
|
-
|
|
7970
|
-
|
|
7971
|
-
const
|
|
7972
|
-
|
|
7973
|
-
|
|
7974
|
-
console.error(`
|
|
7975
|
-
|
|
8102
|
+
let backupPath = null;
|
|
8103
|
+
if (configDirty && !aborted) {
|
|
8104
|
+
backupPath = backupConfigSync(ctx.configPath);
|
|
8105
|
+
const serialized = JSON.stringify(ctx.config, null, 2);
|
|
8106
|
+
try {
|
|
8107
|
+
writeFile(ctx.configPath, serialized);
|
|
8108
|
+
console.error(`runDoctor: writeback ok path=${ctx.configPath} bytes=${serialized.length}`);
|
|
8109
|
+
} catch (e) {
|
|
8110
|
+
const msg = e.message;
|
|
8111
|
+
console.error(`runDoctor: writeback failed path=${ctx.configPath} message=${msg}`);
|
|
8112
|
+
results.push({
|
|
8113
|
+
rule: "*config-writeback*",
|
|
8114
|
+
status: "error",
|
|
8115
|
+
message: "config write failed: " + msg
|
|
8116
|
+
});
|
|
8117
|
+
aborted = true;
|
|
7976
8118
|
}
|
|
8119
|
+
} else if (configDirty && aborted) console.error("runDoctor: writeback skipped (aborted, leaving on-disk config untouched)");
|
|
8120
|
+
else console.error("runDoctor: writeback skipped (no rule transitioned to fixed)");
|
|
8121
|
+
console.error(`runDoctor: end aborted=${aborted} results=${results.length}`);
|
|
8122
|
+
if (originalConfig !== null) {
|
|
8123
|
+
const d = (0, import_lib.diffString)(originalConfig, ctx.config, { color: false });
|
|
8124
|
+
console.error(`runDoctor: diff backupPath=${backupPath ?? "(none)"} changed=${!!d}`);
|
|
8125
|
+
if (d) {
|
|
8126
|
+
process.stdout.write(`original: ${backupPath ?? "(none)"}\n`);
|
|
8127
|
+
process.stdout.write(`after fixed: ${ctx.configPath}\n`);
|
|
8128
|
+
process.stdout.write("diff:\n");
|
|
8129
|
+
process.stdout.write(d);
|
|
8130
|
+
if (!d.endsWith("\n")) process.stdout.write("\n");
|
|
8131
|
+
} else process.stdout.write("(no openclaw.json changes)\n");
|
|
7977
8132
|
}
|
|
7978
|
-
|
|
7979
|
-
return null;
|
|
8133
|
+
return finalize(results, aborted);
|
|
7980
8134
|
}
|
|
7981
|
-
|
|
7982
|
-
|
|
7983
|
-
|
|
7984
|
-
|
|
7985
|
-
|
|
7986
|
-
|
|
7987
|
-
|
|
7988
|
-
}
|
|
7989
|
-
const sep = existing.length > 0 && !existing.endsWith("\n") ? "\n" : "";
|
|
7990
|
-
node_fs.default.appendFileSync(agentsMdPath, `${sep}${PE_PLACEHOLDER}`, "utf-8");
|
|
7991
|
-
console.error(`lark-cli-init: appended PE placeholder to ${agentsMdPath}`);
|
|
8135
|
+
/** Deep-clone a JSON-shaped value. Uses structuredClone where available
|
|
8136
|
+
* (Node 17+); falls back to JSON round-trip otherwise. The config we
|
|
8137
|
+
* clone here is JSON5-parsed so it's strictly tree-shaped — no
|
|
8138
|
+
* references, no functions — making both paths equivalent. */
|
|
8139
|
+
function deepClone(v) {
|
|
8140
|
+
if (typeof structuredClone === "function") return structuredClone(v);
|
|
8141
|
+
return JSON.parse(JSON.stringify(v));
|
|
7992
8142
|
}
|
|
7993
|
-
function
|
|
7994
|
-
const
|
|
7995
|
-
|
|
7996
|
-
|
|
7997
|
-
|
|
7998
|
-
|
|
7999
|
-
|
|
8000
|
-
|
|
8001
|
-
|
|
8002
|
-
}
|
|
8003
|
-
if (!isLarkCliAvailable()) {
|
|
8004
|
-
console.error("lark-cli-init: skipping — lark-cli command not found");
|
|
8005
|
-
return {
|
|
8006
|
-
ok: true,
|
|
8007
|
-
skipped: true,
|
|
8008
|
-
skipReason: "lark-cli command not found"
|
|
8009
|
-
};
|
|
8010
|
-
}
|
|
8011
|
-
const config = readConfig(configPath);
|
|
8012
|
-
if (!config) return {
|
|
8013
|
-
ok: false,
|
|
8014
|
-
error: `could not read config at ${configPath}`
|
|
8015
|
-
};
|
|
8016
|
-
const agentsMdPath = resolveAgentsMdPath(opts.appId, config);
|
|
8017
|
-
console.error(`lark-cli-init: resolved agents.md path=${agentsMdPath ?? "(null)"}`);
|
|
8018
|
-
if (!agentsMdPath) return {
|
|
8019
|
-
ok: false,
|
|
8020
|
-
error: `could not resolve agents.md path for appId=${opts.appId}`
|
|
8021
|
-
};
|
|
8022
|
-
const appSecret = resolveAppSecret(opts.appId, config, opts.feishuAppSecret);
|
|
8023
|
-
if (!appSecret) return {
|
|
8024
|
-
ok: false,
|
|
8025
|
-
error: `could not resolve appSecret for appId=${opts.appId}`
|
|
8026
|
-
};
|
|
8027
|
-
console.error(`lark-cli-init: running lark-cli config init --name ${opts.appId} --app-id ${opts.appId} --brand feishu --app-secret-stdin --force-init`);
|
|
8028
|
-
const initRes = (0, node_child_process.spawnSync)("lark-cli", [
|
|
8029
|
-
"config",
|
|
8030
|
-
"init",
|
|
8031
|
-
"--name",
|
|
8032
|
-
opts.appId,
|
|
8033
|
-
"--app-id",
|
|
8034
|
-
opts.appId,
|
|
8035
|
-
"--brand",
|
|
8036
|
-
"feishu",
|
|
8037
|
-
"--app-secret-stdin",
|
|
8038
|
-
"--force-init"
|
|
8039
|
-
], {
|
|
8040
|
-
stdio: [
|
|
8041
|
-
"pipe",
|
|
8042
|
-
"pipe",
|
|
8043
|
-
"pipe"
|
|
8044
|
-
],
|
|
8045
|
-
encoding: "utf-8",
|
|
8046
|
-
input: appSecret
|
|
8047
|
-
});
|
|
8048
|
-
const configInitStdout = initRes.stdout?.trim() || void 0;
|
|
8049
|
-
const configInitStderr = initRes.stderr?.trim() || void 0;
|
|
8050
|
-
if (configInitStdout) console.error(`lark-cli config init stdout: ${configInitStdout}`);
|
|
8051
|
-
if (configInitStderr) console.error(`lark-cli config init stderr: ${configInitStderr}`);
|
|
8052
|
-
if (initRes.error) return {
|
|
8053
|
-
ok: false,
|
|
8054
|
-
configInitStdout,
|
|
8055
|
-
configInitStderr,
|
|
8056
|
-
error: `lark-cli config init spawn error: ${initRes.error.message}`
|
|
8057
|
-
};
|
|
8058
|
-
if (initRes.status !== 0) return {
|
|
8059
|
-
ok: false,
|
|
8060
|
-
configInitExitCode: initRes.status ?? void 0,
|
|
8061
|
-
configInitStdout,
|
|
8062
|
-
configInitStderr,
|
|
8063
|
-
error: `lark-cli config init exited with code ${initRes.status}`
|
|
8143
|
+
function finalize(results, aborted) {
|
|
8144
|
+
const summary = {
|
|
8145
|
+
pass: 0,
|
|
8146
|
+
failed: 0,
|
|
8147
|
+
fixed: 0,
|
|
8148
|
+
stillBroken: 0,
|
|
8149
|
+
skipped: 0,
|
|
8150
|
+
error: 0,
|
|
8151
|
+
unknown: 0
|
|
8064
8152
|
};
|
|
8065
|
-
|
|
8153
|
+
for (const r of results) switch (r.status) {
|
|
8154
|
+
case "pass":
|
|
8155
|
+
summary.pass++;
|
|
8156
|
+
break;
|
|
8157
|
+
case "failed":
|
|
8158
|
+
summary.failed++;
|
|
8159
|
+
break;
|
|
8160
|
+
case "fixed":
|
|
8161
|
+
summary.fixed++;
|
|
8162
|
+
break;
|
|
8163
|
+
case "still-broken":
|
|
8164
|
+
summary.stillBroken++;
|
|
8165
|
+
break;
|
|
8166
|
+
case "skipped":
|
|
8167
|
+
summary.skipped++;
|
|
8168
|
+
break;
|
|
8169
|
+
case "error":
|
|
8170
|
+
summary.error++;
|
|
8171
|
+
break;
|
|
8172
|
+
case "unknown":
|
|
8173
|
+
summary.unknown++;
|
|
8174
|
+
break;
|
|
8175
|
+
}
|
|
8066
8176
|
return {
|
|
8067
|
-
|
|
8068
|
-
|
|
8069
|
-
|
|
8177
|
+
results,
|
|
8178
|
+
summary,
|
|
8179
|
+
aborted
|
|
8070
8180
|
};
|
|
8071
8181
|
}
|
|
8072
8182
|
//#endregion
|
|
@@ -8149,7 +8259,7 @@ async function reportCliRun(opts) {
|
|
|
8149
8259
|
//#region src/help.ts
|
|
8150
8260
|
const BIN = "mclaw-diagnose";
|
|
8151
8261
|
function versionBanner() {
|
|
8152
|
-
return `v0.1.
|
|
8262
|
+
return `v0.1.7-alpha.1`;
|
|
8153
8263
|
}
|
|
8154
8264
|
const COMMANDS = [
|
|
8155
8265
|
{
|
|
@@ -8367,6 +8477,37 @@ OPTIONS
|
|
|
8367
8477
|
--skip-config-update Leave plugins.installs in openclaw.json untouched.
|
|
8368
8478
|
--ctx=<base64> Opaque ctx; see install-openclaw for semantics.
|
|
8369
8479
|
--oss_file_map=... Pre-built OSS URL map (base64 JSON).
|
|
8480
|
+
`
|
|
8481
|
+
},
|
|
8482
|
+
{
|
|
8483
|
+
name: "install-clis",
|
|
8484
|
+
hidden: true,
|
|
8485
|
+
summary: "Install CLI package(s) (role=cli) from the manifest",
|
|
8486
|
+
help: `USAGE
|
|
8487
|
+
${BIN} install-clis <tag> --cli=<name>... [options]
|
|
8488
|
+
|
|
8489
|
+
DESCRIPTION
|
|
8490
|
+
Downloads + installs one or more CLI tarballs (role=cli in the manifest)
|
|
8491
|
+
into <home_base>/<pkg.installPath>. Currently the only CLI in this
|
|
8492
|
+
category is lark-cli (@larksuite/cli); openclaw itself is handled by the
|
|
8493
|
+
dedicated install-openclaw command.
|
|
8494
|
+
|
|
8495
|
+
Each package is first extracted into a temporary directory (os.tmpdir(),
|
|
8496
|
+
typically tmpfs) to avoid overlayfs race conditions, then ferried to a
|
|
8497
|
+
sibling .new directory and atomically swapped into the final target path.
|
|
8498
|
+
|
|
8499
|
+
ARGUMENTS
|
|
8500
|
+
<tag> Openclaw version tag, e.g. 2026.4.11.
|
|
8501
|
+
|
|
8502
|
+
OPTIONS
|
|
8503
|
+
--cli=<name> CLI package to install by short name or scoped
|
|
8504
|
+
packageName (repeatable, at least one required).
|
|
8505
|
+
--home_base=<dir> Override the /home/gem base (tests).
|
|
8506
|
+
--ctx=<base64> Opaque ctx; ossFileMap is extracted from it.
|
|
8507
|
+
--oss_file_map=... Pre-built OSS URL map (base64 JSON); skips innerapi.
|
|
8508
|
+
|
|
8509
|
+
EXAMPLES
|
|
8510
|
+
${BIN} install-clis 2026.4.11 --cli=lark-cli
|
|
8370
8511
|
`
|
|
8371
8512
|
},
|
|
8372
8513
|
{
|
|
@@ -9862,7 +10003,7 @@ function parseCtxFlag(args) {
|
|
|
9862
10003
|
* (The mode itself is args[0] in the filtered set, so we skip index 0.)
|
|
9863
10004
|
*/
|
|
9864
10005
|
function getPositionalTag(args, modeName) {
|
|
9865
|
-
return args.find((a, i) => i > 0 && !a.startsWith("
|
|
10006
|
+
return args.find((a, i) => i > 0 && !a.startsWith("-") && a !== modeName);
|
|
9866
10007
|
}
|
|
9867
10008
|
function getFlag(args, name) {
|
|
9868
10009
|
const prefix = `--${name}=`;
|
|
@@ -10203,6 +10344,64 @@ async function main() {
|
|
|
10203
10344
|
if (error) throw error;
|
|
10204
10345
|
break;
|
|
10205
10346
|
}
|
|
10347
|
+
case "install-clis": {
|
|
10348
|
+
const tag = getPositionalTag(args, "install-clis");
|
|
10349
|
+
if (!tag) {
|
|
10350
|
+
console.error("Usage: install-clis <tag> --cli=<name>... [--home_base=<dir>] [--ctx=<base64> | --oss_file_map=<base64>]");
|
|
10351
|
+
node_process.default.exit(1);
|
|
10352
|
+
}
|
|
10353
|
+
const names = getMultiFlag(args, "cli");
|
|
10354
|
+
const homeBase = getFlag(args, "home_base");
|
|
10355
|
+
const ossFileMapFlag = getFlag(args, "oss_file_map");
|
|
10356
|
+
let installOssFileMap;
|
|
10357
|
+
let rawForTelemetry;
|
|
10358
|
+
if (!ossFileMapFlag) {
|
|
10359
|
+
rawForTelemetry = parseCtxFlag(args) ?? await fetchCtxViaInnerApi({
|
|
10360
|
+
populate: planCtxPopulate({ command: "install" }),
|
|
10361
|
+
caller,
|
|
10362
|
+
traceId
|
|
10363
|
+
});
|
|
10364
|
+
installOssFileMap = normalizeCtx(rawForTelemetry).install.ossFileMap;
|
|
10365
|
+
}
|
|
10366
|
+
const ossFileMap = resolveOssFileMap({
|
|
10367
|
+
ossFileMapFlag,
|
|
10368
|
+
installOssFileMap
|
|
10369
|
+
});
|
|
10370
|
+
console.error(`[install-clis] ossFileMap=${JSON.stringify(ossFileMap)}`);
|
|
10371
|
+
let feishuAppSecret;
|
|
10372
|
+
if (names.includes("lark-cli")) feishuAppSecret = normalizeCtx(await fetchCtxViaInnerApi({
|
|
10373
|
+
populate: { app: ["feishuAppSecret"] },
|
|
10374
|
+
caller,
|
|
10375
|
+
traceId
|
|
10376
|
+
})).app.feishuAppSecret || void 0;
|
|
10377
|
+
let success = true;
|
|
10378
|
+
let error;
|
|
10379
|
+
try {
|
|
10380
|
+
await installClis(tag, ossFileMap, {
|
|
10381
|
+
names,
|
|
10382
|
+
homeBase,
|
|
10383
|
+
feishuAppSecret
|
|
10384
|
+
});
|
|
10385
|
+
} catch (e) {
|
|
10386
|
+
success = false;
|
|
10387
|
+
error = e;
|
|
10388
|
+
}
|
|
10389
|
+
if (success) console.log(JSON.stringify({ ok: true }));
|
|
10390
|
+
await reportRun("install-clis", rc, rawForTelemetry, args.join(" "), Date.now() - t0, {
|
|
10391
|
+
success,
|
|
10392
|
+
result: {
|
|
10393
|
+
tag,
|
|
10394
|
+
names
|
|
10395
|
+
},
|
|
10396
|
+
error
|
|
10397
|
+
}, {
|
|
10398
|
+
scene,
|
|
10399
|
+
profile,
|
|
10400
|
+
fix: false
|
|
10401
|
+
});
|
|
10402
|
+
if (error) throw error;
|
|
10403
|
+
break;
|
|
10404
|
+
}
|
|
10206
10405
|
case "download-resource": {
|
|
10207
10406
|
const tag = getPositionalTag(args, "download-resource");
|
|
10208
10407
|
if (!tag) {
|