@lark-apaas/openclaw-scripts-diagnose-cli 0.1.18-alpha.2 → 0.1.18-alpha.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +1078 -964
- 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.18-alpha.
|
|
55
|
+
return "0.1.18-alpha.4";
|
|
56
56
|
}
|
|
57
57
|
//#endregion
|
|
58
58
|
//#region src/rule-engine/base.ts
|
|
@@ -1155,6 +1155,18 @@ function isValidJWT(token) {
|
|
|
1155
1155
|
function asRecord(val) {
|
|
1156
1156
|
return val != null && typeof val === "object" && !Array.isArray(val) ? val : void 0;
|
|
1157
1157
|
}
|
|
1158
|
+
/**
|
|
1159
|
+
* Get-or-create a nested record at `obj[key]`: returns the existing plain object,
|
|
1160
|
+
* or replaces a missing / non-object / array value with a fresh `{}` and returns it.
|
|
1161
|
+
* Mutates `obj`. Chainable for deep paths: `ensureRecord(ensureRecord(o,'a'),'b')`.
|
|
1162
|
+
*/
|
|
1163
|
+
function ensureRecord(obj, key) {
|
|
1164
|
+
const cur = obj[key];
|
|
1165
|
+
if (cur != null && typeof cur === "object" && !Array.isArray(cur)) return cur;
|
|
1166
|
+
const fresh = {};
|
|
1167
|
+
obj[key] = fresh;
|
|
1168
|
+
return fresh;
|
|
1169
|
+
}
|
|
1158
1170
|
/** Collect existing AGENTS.md files from the default workspace and configured agent workspaces. */
|
|
1159
1171
|
function collectExistingAgentsMdPaths(ctx) {
|
|
1160
1172
|
const paths = /* @__PURE__ */ new Set();
|
|
@@ -2610,603 +2622,285 @@ function upsertResourceConstrainedToolsBlock(content) {
|
|
|
2610
2622
|
return `${content}${content.length > 0 && !content.endsWith("\n") ? "\n\n" : "\n"}${RESOURCE_CONSTRAINED_TOOLS_BLOCK}\n`;
|
|
2611
2623
|
}
|
|
2612
2624
|
//#endregion
|
|
2613
|
-
//#region src/
|
|
2625
|
+
//#region src/constants.ts
|
|
2614
2626
|
/**
|
|
2615
|
-
*
|
|
2616
|
-
*
|
|
2617
|
-
*
|
|
2618
|
-
*
|
|
2619
|
-
* run, and each run's log is right next to its state.
|
|
2627
|
+
* 全局共享常量(插件名 / CLI 名 / 路径 / 插件集合)。
|
|
2628
|
+
*
|
|
2629
|
+
* 历史上这些字面量散落在 install-*、upgrade-lark、各 rule 等十余个文件里重复定义,
|
|
2630
|
+
* 易漂移。统一收口到此处,按用途分组,单一来源。
|
|
2620
2631
|
*/
|
|
2621
|
-
const DIAGNOSE_DIR = "/tmp/openclaw-diagnose";
|
|
2622
|
-
function resetResultFile(taskId) {
|
|
2623
|
-
return `${DIAGNOSE_DIR}/reset-${taskId}.json`;
|
|
2624
|
-
}
|
|
2625
|
-
function resetLogFile(taskId) {
|
|
2626
|
-
return `${DIAGNOSE_DIR}/reset-${taskId}.log`;
|
|
2627
|
-
}
|
|
2628
|
-
/** Sandbox workspace root where openclaw config + agent state lives. */
|
|
2629
|
-
const WORKSPACE_DIR = "/home/gem/workspace/agent";
|
|
2630
|
-
/** File containing the provider key used by the openclaw miaoda provider. */
|
|
2631
|
-
const PROVIDER_FILE_PATH = "/home/gem/workspace/.force/openclaw/miaoda-provider-key";
|
|
2632
|
-
/** File containing the miaoda openclaw secrets JSON. */
|
|
2633
|
-
const SECRETS_FILE_PATH = "/home/gem/workspace/.force/openclaw/miaoda-openclaw-secrets.json";
|
|
2634
|
-
/** Absolute path to the openclaw config JSON. */
|
|
2635
|
-
const CONFIG_PATH = `${WORKSPACE_DIR}/openclaw.json`;
|
|
2636
2632
|
/**
|
|
2637
|
-
*
|
|
2638
|
-
*
|
|
2639
|
-
* 文件内容为完整的 UpgradeLarkResult JSON,供外部进程轮询感知升级结果。
|
|
2633
|
+
* 官方插件:`openclaw-lark`(plugins.allow 中的短名;npm 发布名 `@larksuite/openclaw-lark`)。
|
|
2634
|
+
* 走 VERSION_COMPAT_MAP 按版本判定与 openclaw 的兼容性。
|
|
2640
2635
|
*/
|
|
2641
|
-
const
|
|
2636
|
+
const LARK_PLUGIN_NAME = "openclaw-lark";
|
|
2642
2637
|
/**
|
|
2643
|
-
*
|
|
2644
|
-
*
|
|
2645
|
-
* 共享此锁,同一时刻只允许一个安装指令运行。
|
|
2646
|
-
* 锁文件内容:{ pid, command, startedAt }。
|
|
2638
|
+
* fork 插件:`@lark-apaas/openclaw-lark`(@lark-apaas 内部 fork,全名带 scope)。
|
|
2639
|
+
* 版本号自成体系、不在 VERSION_COMPAT_MAP 内,按对标的官方版本判定兼容性。
|
|
2647
2640
|
*/
|
|
2648
|
-
const
|
|
2641
|
+
const FORK_LARK_PLUGIN_FULL_NAME = "@lark-apaas/openclaw-lark";
|
|
2649
2642
|
/**
|
|
2650
|
-
*
|
|
2651
|
-
*
|
|
2643
|
+
* 社区插件:openclaw **内置** 的 `feishu` 插件(非独立扩展)。
|
|
2644
|
+
* 与官方/ fork openclaw-lark 互斥,规范化时应禁用以让位 openclaw-lark。
|
|
2652
2645
|
*/
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
|
|
2656
|
-
|
|
2646
|
+
const BUILTIN_FEISHU_PLUGIN_NAME = "feishu";
|
|
2647
|
+
/**
|
|
2648
|
+
* 旧版插件:已弃用的 `feishu-openclaw-plugin`,始终不兼容,需替换为官方 openclaw-lark。
|
|
2649
|
+
*/
|
|
2650
|
+
const LEGACY_LARK_PLUGIN_NAME = "feishu-openclaw-plugin";
|
|
2651
|
+
const MIAODA_PLUGIN_NAME = "openclaw-extension-miaoda";
|
|
2652
|
+
const MIAODA_CODING_PLUGIN_NAME = "openclaw-extension-miaoda-coding";
|
|
2653
|
+
const GUARDIAN_PLUGIN_NAME = "openclaw-guardian-plugin";
|
|
2654
|
+
const MEM0_PLUGIN_NAME = "openclaw-mem0-plugin";
|
|
2655
|
+
/** 飞书 lark-cli 命令名(独立 CLI,与 openclaw-lark 插件配套)。 */
|
|
2656
|
+
const LARK_CLI_NAME = "lark-cli";
|
|
2657
|
+
/** agent-skills 模板包名(manifest role=template,随 lark-cli 一起安装)。 */
|
|
2658
|
+
const AGENT_SKILLS_NAME = "agent-skills";
|
|
2659
|
+
/**
|
|
2660
|
+
* lark 系**有 extension 目录**的插件名(官方 openclaw-lark + 旧版 feishu-openclaw-plugin):
|
|
2661
|
+
* 备份 / 清理 / 安装、以及 plugins.allow 成员判定时迭代用。
|
|
2662
|
+
* 注意与「可启用的飞书插件集」区分——后者还含**无目录**的内置 feishu(见各规则局部定义)。
|
|
2663
|
+
*/
|
|
2664
|
+
const LARK_PLUGIN_DIR_NAMES = [LARK_PLUGIN_NAME, LEGACY_LARK_PLUGIN_NAME];
|
|
2665
|
+
/**
|
|
2666
|
+
* install-extension --all 安装的官方扩展插件集合(manifest role=extension)。
|
|
2667
|
+
* builtin-plugin-missing 与 miaoda-official-plugins-install-spec-unlock 共用此单一来源,
|
|
2668
|
+
* 二者此前各维护一份且注释明示"必须保持一致"。
|
|
2669
|
+
*/
|
|
2670
|
+
const OFFICIAL_EXTENSION_PLUGIN_NAMES = new Set([
|
|
2671
|
+
LARK_PLUGIN_NAME,
|
|
2672
|
+
MIAODA_PLUGIN_NAME,
|
|
2673
|
+
MIAODA_CODING_PLUGIN_NAME,
|
|
2674
|
+
GUARDIAN_PLUGIN_NAME,
|
|
2675
|
+
MEM0_PLUGIN_NAME
|
|
2676
|
+
]);
|
|
2677
|
+
/** agent 工作区相对路径。 */
|
|
2678
|
+
const WORKSPACE_AGENT_REL = "workspace/agent";
|
|
2679
|
+
/** openclaw.json 默认相对路径。 */
|
|
2680
|
+
const DEFAULT_CONFIG_REL = `${WORKSPACE_AGENT_REL}/openclaw.json`;
|
|
2657
2681
|
//#endregion
|
|
2658
|
-
//#region src/
|
|
2659
|
-
|
|
2660
|
-
|
|
2661
|
-
|
|
2662
|
-
|
|
2663
|
-
|
|
2664
|
-
|
|
2665
|
-
|
|
2666
|
-
|
|
2667
|
-
const extDir = getExtensionsDir(configPath);
|
|
2668
|
-
return LARK_PLUGIN_NAMES$1.some((name) => {
|
|
2669
|
-
try {
|
|
2670
|
-
return node_fs.default.existsSync(node_path.default.join(extDir, name, "package.json"));
|
|
2671
|
-
} catch {
|
|
2672
|
-
return false;
|
|
2673
|
-
}
|
|
2674
|
-
});
|
|
2682
|
+
//#region src/rules/miaoda-official-plugins-install-spec-unlock.ts
|
|
2683
|
+
/**
|
|
2684
|
+
* Official miaoda-side plugins that must track manifest — version-locked specs
|
|
2685
|
+
* here block upgrades. Third-party / user-installed plugins are intentionally
|
|
2686
|
+
* out of scope (users may pin them deliberately). 集合见 constants.OFFICIAL_EXTENSION_PLUGIN_NAMES。
|
|
2687
|
+
*/
|
|
2688
|
+
const LOCKED_NPM_SPEC = /^(@[a-z0-9][\w.-]*\/)?[a-z0-9][\w.-]*@[^@/:#\s]+$/i;
|
|
2689
|
+
function isLockedNpmSpec(spec) {
|
|
2690
|
+
return typeof spec === "string" && LOCKED_NPM_SPEC.test(spec);
|
|
2675
2691
|
}
|
|
2676
|
-
function
|
|
2677
|
-
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
timeout: 5e3,
|
|
2681
|
-
stdio: [
|
|
2682
|
-
"ignore",
|
|
2683
|
-
"pipe",
|
|
2684
|
-
"ignore"
|
|
2685
|
-
]
|
|
2686
|
-
}).status === 0;
|
|
2687
|
-
} catch {
|
|
2688
|
-
return false;
|
|
2689
|
-
}
|
|
2692
|
+
function unlockSpec(spec) {
|
|
2693
|
+
const slash = spec.indexOf("/");
|
|
2694
|
+
const cut = slash === -1 ? spec.indexOf("@") : spec.indexOf("@", slash + 1);
|
|
2695
|
+
return spec.slice(0, cut);
|
|
2690
2696
|
}
|
|
2691
|
-
|
|
2692
|
-
|
|
2693
|
-
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
|
|
2697
|
-
|
|
2697
|
+
/** Yield `[key, lockedSpec]` for every official-plugin install whose `spec` is locked. */
|
|
2698
|
+
function* iterLockedOfficialInstalls(config) {
|
|
2699
|
+
const installs = getNestedMap(config, "plugins", "installs");
|
|
2700
|
+
if (!installs) return;
|
|
2701
|
+
for (const [key, entry] of Object.entries(installs)) {
|
|
2702
|
+
if (!OFFICIAL_EXTENSION_PLUGIN_NAMES.has(key)) continue;
|
|
2703
|
+
const spec = asRecord(entry)?.spec;
|
|
2704
|
+
if (isLockedNpmSpec(spec)) yield [key, spec];
|
|
2698
2705
|
}
|
|
2699
2706
|
}
|
|
2700
|
-
|
|
2701
|
-
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
* - string → use directly
|
|
2709
|
-
* - object → secret is managed by a provider; use `feishuAppSecret` param instead
|
|
2710
|
-
*
|
|
2711
|
-
* Returns null when the secret cannot be determined.
|
|
2712
|
-
*/
|
|
2713
|
-
function resolveAppSecret(appId, config, feishuAppSecret) {
|
|
2714
|
-
const feishu = getNestedMap(config, "channels", "feishu");
|
|
2715
|
-
if (!feishu) return null;
|
|
2716
|
-
let rawSecret;
|
|
2717
|
-
if (typeof feishu.appId === "string" && feishu.appId === appId) rawSecret = feishu.appSecret;
|
|
2718
|
-
else {
|
|
2719
|
-
const accounts = asRecord(feishu.accounts);
|
|
2720
|
-
if (accounts) for (const [, val] of Object.entries(accounts)) {
|
|
2721
|
-
const account = asRecord(val);
|
|
2722
|
-
if (account?.appId === appId) {
|
|
2723
|
-
rawSecret = account.appSecret ?? feishu.appSecret;
|
|
2724
|
-
break;
|
|
2725
|
-
}
|
|
2726
|
-
}
|
|
2707
|
+
let MiaodaOfficialPluginsInstallSpecUnlockRule = class MiaodaOfficialPluginsInstallSpecUnlockRule extends DiagnoseRule {
|
|
2708
|
+
validate(ctx) {
|
|
2709
|
+
const locked = [...iterLockedOfficialInstalls(ctx.config)].map(([k]) => k);
|
|
2710
|
+
if (locked.length === 0) return { pass: true };
|
|
2711
|
+
return {
|
|
2712
|
+
pass: false,
|
|
2713
|
+
message: "plugins.installs 中官方插件存在锁版本的 spec: " + locked.sort().join(",")
|
|
2714
|
+
};
|
|
2727
2715
|
}
|
|
2728
|
-
|
|
2729
|
-
|
|
2730
|
-
|
|
2731
|
-
|
|
2732
|
-
|
|
2733
|
-
|
|
2734
|
-
|
|
2735
|
-
* Case 1: appId matches channels.feishu.appId (single-agent path)
|
|
2736
|
-
* → WORKSPACE_DIR/AGENTS.md
|
|
2737
|
-
*
|
|
2738
|
-
* Case 2: appId found in channels.feishu.accounts (multi-agent path)
|
|
2739
|
-
* → find account key where account.appId === appId
|
|
2740
|
-
* → find binding where match.channel=feishu && match.accountId=that key
|
|
2741
|
-
* → if agentId === 'main' → WORKSPACE_DIR/agents.md
|
|
2742
|
-
* → else find agent in agents.list by id → agent.workspace/agents.md
|
|
2743
|
-
*
|
|
2744
|
-
* Returns null when the path cannot be determined.
|
|
2745
|
-
*/
|
|
2746
|
-
function resolveAgentsMdPath(appId, config) {
|
|
2747
|
-
const feishu = getNestedMap(config, "channels", "feishu");
|
|
2748
|
-
if (!feishu) {
|
|
2749
|
-
console.error("resolveAgentsMdPath: channels.feishu not found");
|
|
2750
|
-
return null;
|
|
2716
|
+
repair(ctx) {
|
|
2717
|
+
for (const [key, spec] of iterLockedOfficialInstalls(ctx.config)) setNestedValue(ctx.config, [
|
|
2718
|
+
"plugins",
|
|
2719
|
+
"installs",
|
|
2720
|
+
key,
|
|
2721
|
+
"spec"
|
|
2722
|
+
], unlockSpec(spec));
|
|
2751
2723
|
}
|
|
2752
|
-
|
|
2753
|
-
|
|
2754
|
-
|
|
2724
|
+
};
|
|
2725
|
+
MiaodaOfficialPluginsInstallSpecUnlockRule = __decorate([Rule({
|
|
2726
|
+
key: "miaoda_official_plugins_install_spec_unlock",
|
|
2727
|
+
description: "移除官方妙搭插件安装条目中的锁版本 npm spec,使其跟随最新 manifest 版本",
|
|
2728
|
+
dependsOn: ["config_syntax_check"],
|
|
2729
|
+
repairMode: "standard",
|
|
2730
|
+
level: "silent"
|
|
2731
|
+
})], MiaodaOfficialPluginsInstallSpecUnlockRule);
|
|
2732
|
+
//#endregion
|
|
2733
|
+
//#region src/rules/miaoda-plugin-allow.ts
|
|
2734
|
+
let MiaodaPluginAllowRule = class MiaodaPluginAllowRule extends DiagnoseRule {
|
|
2735
|
+
validate(ctx) {
|
|
2736
|
+
if (!isPluginInstalledOnDisk(getExtensionsDir(ctx.configPath), "openclaw-extension-miaoda")) return { pass: true };
|
|
2737
|
+
if (getAllow$1(ctx.config).includes("openclaw-extension-miaoda")) return { pass: true };
|
|
2738
|
+
return {
|
|
2739
|
+
pass: false,
|
|
2740
|
+
message: `plugins.allow 缺少 ${MIAODA_PLUGIN_NAME}(已在 extensions/ 下装但未启用)`
|
|
2741
|
+
};
|
|
2755
2742
|
}
|
|
2756
|
-
|
|
2757
|
-
|
|
2758
|
-
|
|
2759
|
-
|
|
2743
|
+
repair(ctx) {
|
|
2744
|
+
if (!isPluginInstalledOnDisk(getExtensionsDir(ctx.configPath), "openclaw-extension-miaoda")) return;
|
|
2745
|
+
const plugins = ctx.config.plugins;
|
|
2746
|
+
if (plugins == null || typeof plugins !== "object" || Array.isArray(plugins)) {
|
|
2747
|
+
ctx.config.plugins = { allow: [MIAODA_PLUGIN_NAME] };
|
|
2748
|
+
return;
|
|
2749
|
+
}
|
|
2750
|
+
const pluginsMap = plugins;
|
|
2751
|
+
const rawAllow = pluginsMap.allow;
|
|
2752
|
+
const allow = Array.isArray(rawAllow) ? rawAllow : [];
|
|
2753
|
+
if (allow.includes("openclaw-extension-miaoda")) return;
|
|
2754
|
+
allow.push(MIAODA_PLUGIN_NAME);
|
|
2755
|
+
pluginsMap.allow = allow;
|
|
2760
2756
|
}
|
|
2761
|
-
|
|
2762
|
-
|
|
2763
|
-
|
|
2764
|
-
|
|
2757
|
+
};
|
|
2758
|
+
MiaodaPluginAllowRule = __decorate([Rule({
|
|
2759
|
+
key: "miaoda_plugin_allow",
|
|
2760
|
+
description: "当 openclaw-extension-miaoda 已在磁盘安装但未在 allow 列表中时,将其添加到 plugins.allow(实验性)",
|
|
2761
|
+
dependsOn: ["config_syntax_check"],
|
|
2762
|
+
repairMode: "standard",
|
|
2763
|
+
level: "critical",
|
|
2764
|
+
profile: "standard"
|
|
2765
|
+
})], MiaodaPluginAllowRule);
|
|
2766
|
+
function getAllow$1(config) {
|
|
2767
|
+
const plugins = config.plugins;
|
|
2768
|
+
if (plugins == null || typeof plugins !== "object" || Array.isArray(plugins)) return [];
|
|
2769
|
+
const allow = plugins.allow;
|
|
2770
|
+
if (!Array.isArray(allow)) return [];
|
|
2771
|
+
return allow.filter((e) => typeof e === "string");
|
|
2772
|
+
}
|
|
2773
|
+
//#endregion
|
|
2774
|
+
//#region src/rules/lark-plugin-allow.ts
|
|
2775
|
+
let LarkPluginAllowRule = class LarkPluginAllowRule extends DiagnoseRule {
|
|
2776
|
+
validate(ctx) {
|
|
2777
|
+
const allow = getAllow(ctx.config);
|
|
2778
|
+
if (LARK_PLUGIN_DIR_NAMES.some((name) => allow.includes(name))) return { pass: true };
|
|
2779
|
+
const installed = detectInstalledLarkPlugin$1(getExtensionsDir(ctx.configPath));
|
|
2780
|
+
if (installed == null) return { pass: true };
|
|
2781
|
+
return {
|
|
2782
|
+
pass: false,
|
|
2783
|
+
message: `plugins.allow 缺少飞书插件 ${installed}(已在 extensions/ 下装但未启用)`
|
|
2784
|
+
};
|
|
2765
2785
|
}
|
|
2766
|
-
|
|
2767
|
-
|
|
2768
|
-
|
|
2786
|
+
repair(ctx) {
|
|
2787
|
+
const installed = detectInstalledLarkPlugin$1(getExtensionsDir(ctx.configPath));
|
|
2788
|
+
if (installed == null) return;
|
|
2789
|
+
if (ctx.config.plugins == null || typeof ctx.config.plugins !== "object" || Array.isArray(ctx.config.plugins)) {
|
|
2790
|
+
ctx.config.plugins = { allow: [installed] };
|
|
2791
|
+
return;
|
|
2792
|
+
}
|
|
2793
|
+
const pluginsMap = ctx.config.plugins;
|
|
2794
|
+
const rawAllow = pluginsMap.allow;
|
|
2795
|
+
const original = Array.isArray(rawAllow) ? rawAllow : [];
|
|
2796
|
+
const stringAllow = original.filter((e) => typeof e === "string");
|
|
2797
|
+
if (LARK_PLUGIN_DIR_NAMES.some((name) => stringAllow.includes(name))) return;
|
|
2798
|
+
original.push(installed);
|
|
2799
|
+
pluginsMap.allow = original;
|
|
2769
2800
|
}
|
|
2770
|
-
|
|
2771
|
-
|
|
2772
|
-
|
|
2773
|
-
|
|
2774
|
-
|
|
2775
|
-
|
|
2776
|
-
|
|
2777
|
-
|
|
2778
|
-
|
|
2779
|
-
|
|
2780
|
-
|
|
2781
|
-
|
|
2782
|
-
|
|
2783
|
-
|
|
2784
|
-
if (!agentId) {
|
|
2785
|
-
console.error(`resolveAgentsMdPath: no binding found for accountId=${accountId} in bindings(count=${bindings.length})`);
|
|
2786
|
-
return null;
|
|
2787
|
-
}
|
|
2788
|
-
console.error(`resolveAgentsMdPath: found agentId=${agentId}`);
|
|
2789
|
-
if (agentId === "main") {
|
|
2790
|
-
console.error("resolveAgentsMdPath: case=multi-agent-main");
|
|
2791
|
-
return node_path.default.join(WORKSPACE_DIR, "workspace", "AGENTS.md");
|
|
2792
|
-
}
|
|
2793
|
-
const agentsObj = asRecord(config.agents);
|
|
2794
|
-
const list = Array.isArray(agentsObj?.list) ? agentsObj.list : [];
|
|
2795
|
-
for (const a of list) {
|
|
2796
|
-
const agent = asRecord(a);
|
|
2797
|
-
if (agent?.id === agentId) {
|
|
2798
|
-
const ws = typeof agent.workspace === "string" ? agent.workspace : node_path.default.join(WORKSPACE_DIR, "workspace");
|
|
2799
|
-
console.error(`resolveAgentsMdPath: case=multi-agent-custom agentId=${agentId} workspace=${ws}`);
|
|
2800
|
-
return node_path.default.join(ws, "AGENTS.md");
|
|
2801
|
-
}
|
|
2802
|
-
}
|
|
2803
|
-
console.error(`resolveAgentsMdPath: agentId=${agentId} not found in agents.list(count=${list.length})`);
|
|
2804
|
-
return null;
|
|
2805
|
-
}
|
|
2806
|
-
function appendPeToAgentsMd(agentsMdPath) {
|
|
2807
|
-
const dir = node_path.default.dirname(agentsMdPath);
|
|
2808
|
-
if (!node_fs.default.existsSync(dir)) node_fs.default.mkdirSync(dir, { recursive: true });
|
|
2809
|
-
const existing = node_fs.default.existsSync(agentsMdPath) ? node_fs.default.readFileSync(agentsMdPath, "utf-8") : "";
|
|
2810
|
-
if (existing.includes(`<lark-cli-pe>`)) {
|
|
2811
|
-
console.error(`lark-cli-init: <${PE_XML_TAG}> already present in ${agentsMdPath}, skipping`);
|
|
2812
|
-
return;
|
|
2813
|
-
}
|
|
2814
|
-
const sep = existing.length > 0 && !existing.endsWith("\n") ? "\n" : "";
|
|
2815
|
-
node_fs.default.appendFileSync(agentsMdPath, `${sep}${PE_PLACEHOLDER}`, "utf-8");
|
|
2816
|
-
console.error(`lark-cli-init: appended PE placeholder to ${agentsMdPath}`);
|
|
2801
|
+
};
|
|
2802
|
+
LarkPluginAllowRule = __decorate([Rule({
|
|
2803
|
+
key: "lark_plugin_allow",
|
|
2804
|
+
description: "当飞书插件(openclaw-lark 或旧版名)已在磁盘安装但未加入 plugins.allow 时,自动添加",
|
|
2805
|
+
dependsOn: ["config_syntax_check"],
|
|
2806
|
+
repairMode: "standard",
|
|
2807
|
+
level: "critical"
|
|
2808
|
+
})], LarkPluginAllowRule);
|
|
2809
|
+
function getAllow(config) {
|
|
2810
|
+
const plugins = config.plugins;
|
|
2811
|
+
if (plugins == null || typeof plugins !== "object" || Array.isArray(plugins)) return [];
|
|
2812
|
+
const allow = plugins.allow;
|
|
2813
|
+
if (!Array.isArray(allow)) return [];
|
|
2814
|
+
return allow.filter((e) => typeof e === "string");
|
|
2817
2815
|
}
|
|
2818
2816
|
/**
|
|
2819
|
-
*
|
|
2820
|
-
*
|
|
2821
|
-
*
|
|
2817
|
+
* fs-only 检测:`<extDir>/<name>/package.json` 存在即视为已装。
|
|
2818
|
+
* 优先级 openclaw-lark(新版)> feishu-openclaw-plugin(legacy)。
|
|
2819
|
+
* 不读 package.json 内容,只判存在性,避开 JSON 损坏。
|
|
2822
2820
|
*/
|
|
2823
|
-
function
|
|
2824
|
-
const
|
|
2825
|
-
|
|
2826
|
-
const feishu = getNestedMap(config, "channels", "feishu");
|
|
2827
|
-
if (!feishu) return [];
|
|
2828
|
-
const appIds = /* @__PURE__ */ new Set();
|
|
2829
|
-
const topAppId = feishu.appId;
|
|
2830
|
-
if (typeof topAppId === "string" && topAppId.trim()) appIds.add(topAppId.trim());
|
|
2831
|
-
const accounts = asRecord(feishu.accounts);
|
|
2832
|
-
if (accounts) for (const val of Object.values(accounts)) {
|
|
2833
|
-
const appId = asRecord(val)?.appId;
|
|
2834
|
-
if (typeof appId === "string" && appId.trim()) appIds.add(appId.trim());
|
|
2835
|
-
}
|
|
2836
|
-
return [...appIds];
|
|
2837
|
-
}
|
|
2838
|
-
function runLarkCliInit(opts) {
|
|
2839
|
-
const configPath = opts.configPath ?? CONFIG_PATH;
|
|
2840
|
-
if (!isLarkPluginInstalled(configPath)) {
|
|
2841
|
-
console.error("lark-cli-init: skipping — openclaw-lark plugin not installed");
|
|
2842
|
-
return {
|
|
2843
|
-
ok: true,
|
|
2844
|
-
skipped: true,
|
|
2845
|
-
skipReason: "openclaw-lark plugin not installed"
|
|
2846
|
-
};
|
|
2847
|
-
}
|
|
2848
|
-
if (!isLarkCliAvailable$2()) {
|
|
2849
|
-
console.error("lark-cli-init: skipping — lark-cli command not found");
|
|
2850
|
-
return {
|
|
2851
|
-
ok: true,
|
|
2852
|
-
skipped: true,
|
|
2853
|
-
skipReason: "lark-cli command not found"
|
|
2854
|
-
};
|
|
2855
|
-
}
|
|
2856
|
-
const config = readConfig(configPath);
|
|
2857
|
-
if (!config) return {
|
|
2858
|
-
ok: false,
|
|
2859
|
-
error: `could not read config at ${configPath}`
|
|
2860
|
-
};
|
|
2861
|
-
const agentsMdPath = resolveAgentsMdPath(opts.appId, config);
|
|
2862
|
-
console.error(`lark-cli-init: resolved agents.md path=${agentsMdPath ?? "(null)"}`);
|
|
2863
|
-
if (!agentsMdPath) return {
|
|
2864
|
-
ok: false,
|
|
2865
|
-
error: `could not resolve agents.md path for appId=${opts.appId}`
|
|
2866
|
-
};
|
|
2867
|
-
const appSecret = resolveAppSecret(opts.appId, config, opts.feishuAppSecret);
|
|
2868
|
-
if (!appSecret) return {
|
|
2869
|
-
ok: false,
|
|
2870
|
-
error: `could not resolve appSecret for appId=${opts.appId}`
|
|
2871
|
-
};
|
|
2872
|
-
console.error(`lark-cli-init: running lark-cli config init --name ${opts.appId} --app-id ${opts.appId} --brand feishu --app-secret-stdin --force-init`);
|
|
2873
|
-
const initRes = (0, node_child_process.spawnSync)("lark-cli", [
|
|
2874
|
-
"config",
|
|
2875
|
-
"init",
|
|
2876
|
-
"--name",
|
|
2877
|
-
opts.appId,
|
|
2878
|
-
"--app-id",
|
|
2879
|
-
opts.appId,
|
|
2880
|
-
"--brand",
|
|
2881
|
-
"feishu",
|
|
2882
|
-
"--app-secret-stdin",
|
|
2883
|
-
"--force-init"
|
|
2884
|
-
], {
|
|
2885
|
-
stdio: [
|
|
2886
|
-
"pipe",
|
|
2887
|
-
"pipe",
|
|
2888
|
-
"pipe"
|
|
2889
|
-
],
|
|
2890
|
-
encoding: "utf-8",
|
|
2891
|
-
input: appSecret
|
|
2892
|
-
});
|
|
2893
|
-
const configInitStdout = initRes.stdout?.trim() || void 0;
|
|
2894
|
-
const configInitStderr = initRes.stderr?.trim() || void 0;
|
|
2895
|
-
if (configInitStdout) console.error(`lark-cli config init stdout: ${configInitStdout}`);
|
|
2896
|
-
if (configInitStderr) console.error(`lark-cli config init stderr: ${configInitStderr}`);
|
|
2897
|
-
if (initRes.error) return {
|
|
2898
|
-
ok: false,
|
|
2899
|
-
configInitStdout,
|
|
2900
|
-
configInitStderr,
|
|
2901
|
-
error: `lark-cli config init spawn error: ${initRes.error.message}`
|
|
2902
|
-
};
|
|
2903
|
-
if (initRes.status !== 0) return {
|
|
2904
|
-
ok: false,
|
|
2905
|
-
configInitExitCode: initRes.status ?? void 0,
|
|
2906
|
-
configInitStdout,
|
|
2907
|
-
configInitStderr,
|
|
2908
|
-
error: `lark-cli config init exited with code ${initRes.status}`
|
|
2909
|
-
};
|
|
2910
|
-
appendPeToAgentsMd(agentsMdPath);
|
|
2911
|
-
return {
|
|
2912
|
-
ok: true,
|
|
2913
|
-
configInitExitCode: 0,
|
|
2914
|
-
agentsMdPath
|
|
2915
|
-
};
|
|
2821
|
+
function detectInstalledLarkPlugin$1(extDir) {
|
|
2822
|
+
for (const name of LARK_PLUGIN_DIR_NAMES) if (pluginPackageJsonExists(extDir, name)) return name;
|
|
2823
|
+
return null;
|
|
2916
2824
|
}
|
|
2917
|
-
|
|
2918
|
-
//#region src/rules/agents-md-lark-cli-pe.ts
|
|
2919
|
-
function isLarkCliAvailable$1() {
|
|
2825
|
+
function pluginPackageJsonExists(extDir, pluginDir) {
|
|
2920
2826
|
try {
|
|
2921
|
-
return (
|
|
2922
|
-
encoding: "utf-8",
|
|
2923
|
-
timeout: 5e3,
|
|
2924
|
-
stdio: [
|
|
2925
|
-
"ignore",
|
|
2926
|
-
"pipe",
|
|
2927
|
-
"ignore"
|
|
2928
|
-
]
|
|
2929
|
-
}).status === 0;
|
|
2827
|
+
return node_fs.default.existsSync(node_path.default.join(extDir, pluginDir, "package.json"));
|
|
2930
2828
|
} catch {
|
|
2931
2829
|
return false;
|
|
2932
2830
|
}
|
|
2933
2831
|
}
|
|
2934
|
-
|
|
2832
|
+
//#endregion
|
|
2833
|
+
//#region src/rules/old-miaoda-plugins-cleanup.ts
|
|
2834
|
+
const OLD_PLUGIN_NAMES = Object.freeze([
|
|
2835
|
+
"openclaw-feishu-greeting",
|
|
2836
|
+
"openclaw-miaoda-keepalive",
|
|
2837
|
+
"feishu-greeting",
|
|
2838
|
+
"miaoda-keepalive"
|
|
2839
|
+
]);
|
|
2840
|
+
function getPluginMaps(config) {
|
|
2841
|
+
const rawAllow = asRecord(config.plugins)?.allow;
|
|
2842
|
+
return {
|
|
2843
|
+
entries: getNestedMap(config, "plugins", "entries"),
|
|
2844
|
+
installs: getNestedMap(config, "plugins", "installs"),
|
|
2845
|
+
allow: Array.isArray(rawAllow) ? rawAllow : void 0
|
|
2846
|
+
};
|
|
2847
|
+
}
|
|
2848
|
+
function hasNewMiaoda({ entries, installs, allow }) {
|
|
2849
|
+
return asRecord(entries?.["openclaw-extension-miaoda"]) != null || asRecord(installs?.["openclaw-extension-miaoda"]) != null || (allow?.includes("openclaw-extension-miaoda") ?? false);
|
|
2850
|
+
}
|
|
2851
|
+
function findResiduals({ entries, installs, allow }, extensionsDir) {
|
|
2852
|
+
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)));
|
|
2853
|
+
}
|
|
2854
|
+
let OldMiaodaPluginsCleanupRule = class OldMiaodaPluginsCleanupRule extends DiagnoseRule {
|
|
2935
2855
|
validate(ctx) {
|
|
2936
|
-
|
|
2937
|
-
|
|
2938
|
-
|
|
2939
|
-
}
|
|
2940
|
-
if (!missingPath) return { pass: true };
|
|
2856
|
+
const maps = getPluginMaps(ctx.config);
|
|
2857
|
+
if (!hasNewMiaoda(maps)) return { pass: true };
|
|
2858
|
+
const residuals = findResiduals(maps, getExtensionsDir(ctx.configPath));
|
|
2859
|
+
if (residuals.length === 0) return { pass: true };
|
|
2941
2860
|
return {
|
|
2942
2861
|
pass: false,
|
|
2943
|
-
message:
|
|
2862
|
+
message: "旧 miaoda 插件残留: " + residuals.sort().join(",")
|
|
2944
2863
|
};
|
|
2945
2864
|
}
|
|
2946
2865
|
repair(ctx) {
|
|
2947
|
-
|
|
2948
|
-
|
|
2949
|
-
|
|
2950
|
-
|
|
2951
|
-
|
|
2952
|
-
|
|
2953
|
-
|
|
2866
|
+
const maps = getPluginMaps(ctx.config);
|
|
2867
|
+
if (!hasNewMiaoda(maps)) return;
|
|
2868
|
+
const extensionsDir = getExtensionsDir(ctx.configPath);
|
|
2869
|
+
const { entries, installs, allow } = maps;
|
|
2870
|
+
const oldSet = new Set(OLD_PLUGIN_NAMES);
|
|
2871
|
+
if (allow) for (let i = allow.length - 1; i >= 0; i--) {
|
|
2872
|
+
const v = allow[i];
|
|
2873
|
+
if (typeof v === "string" && oldSet.has(v)) allow.splice(i, 1);
|
|
2874
|
+
}
|
|
2875
|
+
for (const name of OLD_PLUGIN_NAMES) {
|
|
2876
|
+
if (entries && name in entries) delete entries[name];
|
|
2877
|
+
if (installs && name in installs) delete installs[name];
|
|
2878
|
+
const target = node_path.default.join(extensionsDir, name);
|
|
2879
|
+
const rel = node_path.default.relative(extensionsDir, target);
|
|
2880
|
+
if (!rel || rel.startsWith("..") || node_path.default.isAbsolute(rel)) continue;
|
|
2881
|
+
try {
|
|
2882
|
+
node_fs.default.rmSync(target, {
|
|
2883
|
+
recursive: true,
|
|
2884
|
+
force: true
|
|
2885
|
+
});
|
|
2886
|
+
} catch (e) {
|
|
2887
|
+
console.error(`[old_miaoda_plugins_cleanup] rmSync ${target} failed: ${e.message}`);
|
|
2888
|
+
}
|
|
2954
2889
|
}
|
|
2955
2890
|
}
|
|
2956
2891
|
};
|
|
2957
|
-
|
|
2958
|
-
key: "
|
|
2959
|
-
description: "
|
|
2892
|
+
OldMiaodaPluginsCleanupRule = __decorate([Rule({
|
|
2893
|
+
key: "old_miaoda_plugins_cleanup",
|
|
2894
|
+
description: "当新版 openclaw-extension-miaoda 已存在时,清理过时插件引用(openclaw-feishu-greeting、openclaw-miaoda-keepalive 等)",
|
|
2960
2895
|
dependsOn: ["config_syntax_check"],
|
|
2961
2896
|
repairMode: "standard",
|
|
2962
2897
|
level: "silent"
|
|
2963
|
-
})],
|
|
2898
|
+
})], OldMiaodaPluginsCleanupRule);
|
|
2964
2899
|
//#endregion
|
|
2965
|
-
//#region src/rules/
|
|
2966
|
-
|
|
2967
|
-
|
|
2968
|
-
|
|
2969
|
-
* out of scope (users may pin them deliberately).
|
|
2970
|
-
*/
|
|
2971
|
-
const OFFICIAL_PLUGIN_NAMES = new Set([
|
|
2972
|
-
"openclaw-extension-miaoda",
|
|
2973
|
-
"openclaw-extension-miaoda-coding",
|
|
2974
|
-
"openclaw-guardian-plugin",
|
|
2975
|
-
"openclaw-mem0-plugin",
|
|
2976
|
-
"openclaw-lark"
|
|
2977
|
-
]);
|
|
2978
|
-
const LOCKED_NPM_SPEC = /^(@[a-z0-9][\w.-]*\/)?[a-z0-9][\w.-]*@[^@/:#\s]+$/i;
|
|
2979
|
-
function isLockedNpmSpec(spec) {
|
|
2980
|
-
return typeof spec === "string" && LOCKED_NPM_SPEC.test(spec);
|
|
2981
|
-
}
|
|
2982
|
-
function unlockSpec(spec) {
|
|
2983
|
-
const slash = spec.indexOf("/");
|
|
2984
|
-
const cut = slash === -1 ? spec.indexOf("@") : spec.indexOf("@", slash + 1);
|
|
2985
|
-
return spec.slice(0, cut);
|
|
2986
|
-
}
|
|
2987
|
-
/** Yield `[key, lockedSpec]` for every official-plugin install whose `spec` is locked. */
|
|
2988
|
-
function* iterLockedOfficialInstalls(config) {
|
|
2989
|
-
const installs = getNestedMap(config, "plugins", "installs");
|
|
2990
|
-
if (!installs) return;
|
|
2991
|
-
for (const [key, entry] of Object.entries(installs)) {
|
|
2992
|
-
if (!OFFICIAL_PLUGIN_NAMES.has(key)) continue;
|
|
2993
|
-
const spec = asRecord(entry)?.spec;
|
|
2994
|
-
if (isLockedNpmSpec(spec)) yield [key, spec];
|
|
2995
|
-
}
|
|
2996
|
-
}
|
|
2997
|
-
let MiaodaOfficialPluginsInstallSpecUnlockRule = class MiaodaOfficialPluginsInstallSpecUnlockRule extends DiagnoseRule {
|
|
2998
|
-
validate(ctx) {
|
|
2999
|
-
const locked = [...iterLockedOfficialInstalls(ctx.config)].map(([k]) => k);
|
|
3000
|
-
if (locked.length === 0) return { pass: true };
|
|
3001
|
-
return {
|
|
3002
|
-
pass: false,
|
|
3003
|
-
message: "plugins.installs 中官方插件存在锁版本的 spec: " + locked.sort().join(",")
|
|
3004
|
-
};
|
|
3005
|
-
}
|
|
3006
|
-
repair(ctx) {
|
|
3007
|
-
for (const [key, spec] of iterLockedOfficialInstalls(ctx.config)) setNestedValue(ctx.config, [
|
|
3008
|
-
"plugins",
|
|
3009
|
-
"installs",
|
|
3010
|
-
key,
|
|
3011
|
-
"spec"
|
|
3012
|
-
], unlockSpec(spec));
|
|
3013
|
-
}
|
|
3014
|
-
};
|
|
3015
|
-
MiaodaOfficialPluginsInstallSpecUnlockRule = __decorate([Rule({
|
|
3016
|
-
key: "miaoda_official_plugins_install_spec_unlock",
|
|
3017
|
-
description: "移除官方妙搭插件安装条目中的锁版本 npm spec,使其跟随最新 manifest 版本",
|
|
3018
|
-
dependsOn: ["config_syntax_check"],
|
|
3019
|
-
repairMode: "standard",
|
|
3020
|
-
level: "silent"
|
|
3021
|
-
})], MiaodaOfficialPluginsInstallSpecUnlockRule);
|
|
3022
|
-
//#endregion
|
|
3023
|
-
//#region src/rules/miaoda-plugin-allow.ts
|
|
3024
|
-
const MIAODA_PLUGIN = "openclaw-extension-miaoda";
|
|
3025
|
-
let MiaodaPluginAllowRule = class MiaodaPluginAllowRule extends DiagnoseRule {
|
|
3026
|
-
validate(ctx) {
|
|
3027
|
-
if (!isPluginInstalledOnDisk(getExtensionsDir(ctx.configPath), MIAODA_PLUGIN)) return { pass: true };
|
|
3028
|
-
if (getAllow$1(ctx.config).includes(MIAODA_PLUGIN)) return { pass: true };
|
|
3029
|
-
return {
|
|
3030
|
-
pass: false,
|
|
3031
|
-
message: `plugins.allow 缺少 ${MIAODA_PLUGIN}(已在 extensions/ 下装但未启用)`
|
|
3032
|
-
};
|
|
3033
|
-
}
|
|
3034
|
-
repair(ctx) {
|
|
3035
|
-
if (!isPluginInstalledOnDisk(getExtensionsDir(ctx.configPath), MIAODA_PLUGIN)) return;
|
|
3036
|
-
const plugins = ctx.config.plugins;
|
|
3037
|
-
if (plugins == null || typeof plugins !== "object" || Array.isArray(plugins)) {
|
|
3038
|
-
ctx.config.plugins = { allow: [MIAODA_PLUGIN] };
|
|
3039
|
-
return;
|
|
3040
|
-
}
|
|
3041
|
-
const pluginsMap = plugins;
|
|
3042
|
-
const rawAllow = pluginsMap.allow;
|
|
3043
|
-
const allow = Array.isArray(rawAllow) ? rawAllow : [];
|
|
3044
|
-
if (allow.includes(MIAODA_PLUGIN)) return;
|
|
3045
|
-
allow.push(MIAODA_PLUGIN);
|
|
3046
|
-
pluginsMap.allow = allow;
|
|
3047
|
-
}
|
|
3048
|
-
};
|
|
3049
|
-
MiaodaPluginAllowRule = __decorate([Rule({
|
|
3050
|
-
key: "miaoda_plugin_allow",
|
|
3051
|
-
description: "当 openclaw-extension-miaoda 已在磁盘安装但未在 allow 列表中时,将其添加到 plugins.allow(实验性)",
|
|
3052
|
-
dependsOn: ["config_syntax_check"],
|
|
3053
|
-
repairMode: "standard",
|
|
3054
|
-
level: "critical",
|
|
3055
|
-
profile: "standard"
|
|
3056
|
-
})], MiaodaPluginAllowRule);
|
|
3057
|
-
function getAllow$1(config) {
|
|
3058
|
-
const plugins = config.plugins;
|
|
3059
|
-
if (plugins == null || typeof plugins !== "object" || Array.isArray(plugins)) return [];
|
|
3060
|
-
const allow = plugins.allow;
|
|
3061
|
-
if (!Array.isArray(allow)) return [];
|
|
3062
|
-
return allow.filter((e) => typeof e === "string");
|
|
3063
|
-
}
|
|
3064
|
-
//#endregion
|
|
3065
|
-
//#region src/rules/lark-plugin-allow.ts
|
|
3066
|
-
const LARK_PLUGIN = "openclaw-lark";
|
|
3067
|
-
const LEGACY_LARK_PLUGIN = "feishu-openclaw-plugin";
|
|
3068
|
-
const LARK_PLUGIN_NAMES = [LARK_PLUGIN, LEGACY_LARK_PLUGIN];
|
|
3069
|
-
let LarkPluginAllowRule = class LarkPluginAllowRule extends DiagnoseRule {
|
|
3070
|
-
validate(ctx) {
|
|
3071
|
-
const allow = getAllow(ctx.config);
|
|
3072
|
-
if (LARK_PLUGIN_NAMES.some((name) => allow.includes(name))) return { pass: true };
|
|
3073
|
-
const installed = detectInstalledLarkPlugin(getExtensionsDir(ctx.configPath));
|
|
3074
|
-
if (installed == null) return { pass: true };
|
|
3075
|
-
return {
|
|
3076
|
-
pass: false,
|
|
3077
|
-
message: `plugins.allow 缺少飞书插件 ${installed}(已在 extensions/ 下装但未启用)`
|
|
3078
|
-
};
|
|
3079
|
-
}
|
|
3080
|
-
repair(ctx) {
|
|
3081
|
-
const installed = detectInstalledLarkPlugin(getExtensionsDir(ctx.configPath));
|
|
3082
|
-
if (installed == null) return;
|
|
3083
|
-
if (ctx.config.plugins == null || typeof ctx.config.plugins !== "object" || Array.isArray(ctx.config.plugins)) {
|
|
3084
|
-
ctx.config.plugins = { allow: [installed] };
|
|
3085
|
-
return;
|
|
3086
|
-
}
|
|
3087
|
-
const pluginsMap = ctx.config.plugins;
|
|
3088
|
-
const rawAllow = pluginsMap.allow;
|
|
3089
|
-
const original = Array.isArray(rawAllow) ? rawAllow : [];
|
|
3090
|
-
const stringAllow = original.filter((e) => typeof e === "string");
|
|
3091
|
-
if (LARK_PLUGIN_NAMES.some((name) => stringAllow.includes(name))) return;
|
|
3092
|
-
original.push(installed);
|
|
3093
|
-
pluginsMap.allow = original;
|
|
3094
|
-
}
|
|
3095
|
-
};
|
|
3096
|
-
LarkPluginAllowRule = __decorate([Rule({
|
|
3097
|
-
key: "lark_plugin_allow",
|
|
3098
|
-
description: "当飞书插件(openclaw-lark 或旧版名)已在磁盘安装但未加入 plugins.allow 时,自动添加",
|
|
3099
|
-
dependsOn: ["config_syntax_check"],
|
|
3100
|
-
repairMode: "standard",
|
|
3101
|
-
level: "critical"
|
|
3102
|
-
})], LarkPluginAllowRule);
|
|
3103
|
-
function getAllow(config) {
|
|
3104
|
-
const plugins = config.plugins;
|
|
3105
|
-
if (plugins == null || typeof plugins !== "object" || Array.isArray(plugins)) return [];
|
|
3106
|
-
const allow = plugins.allow;
|
|
3107
|
-
if (!Array.isArray(allow)) return [];
|
|
3108
|
-
return allow.filter((e) => typeof e === "string");
|
|
3109
|
-
}
|
|
3110
|
-
/**
|
|
3111
|
-
* fs-only 检测:`<extDir>/<name>/package.json` 存在即视为已装。
|
|
3112
|
-
* 优先级 openclaw-lark(新版)> feishu-openclaw-plugin(legacy)。
|
|
3113
|
-
* 不读 package.json 内容,只判存在性,避开 JSON 损坏。
|
|
3114
|
-
*/
|
|
3115
|
-
function detectInstalledLarkPlugin(extDir) {
|
|
3116
|
-
for (const name of [LARK_PLUGIN, LEGACY_LARK_PLUGIN]) if (pluginPackageJsonExists(extDir, name)) return name;
|
|
3117
|
-
return null;
|
|
3118
|
-
}
|
|
3119
|
-
function pluginPackageJsonExists(extDir, pluginDir) {
|
|
3120
|
-
try {
|
|
3121
|
-
return node_fs.default.existsSync(node_path.default.join(extDir, pluginDir, "package.json"));
|
|
3122
|
-
} catch {
|
|
3123
|
-
return false;
|
|
3124
|
-
}
|
|
3125
|
-
}
|
|
3126
|
-
//#endregion
|
|
3127
|
-
//#region src/rules/old-miaoda-plugins-cleanup.ts
|
|
3128
|
-
const NEW_MIAODA = "openclaw-extension-miaoda";
|
|
3129
|
-
const OLD_PLUGIN_NAMES = Object.freeze([
|
|
3130
|
-
"openclaw-feishu-greeting",
|
|
3131
|
-
"openclaw-miaoda-keepalive",
|
|
3132
|
-
"feishu-greeting",
|
|
3133
|
-
"miaoda-keepalive"
|
|
3134
|
-
]);
|
|
3135
|
-
function getPluginMaps(config) {
|
|
3136
|
-
const rawAllow = asRecord(config.plugins)?.allow;
|
|
3137
|
-
return {
|
|
3138
|
-
entries: getNestedMap(config, "plugins", "entries"),
|
|
3139
|
-
installs: getNestedMap(config, "plugins", "installs"),
|
|
3140
|
-
allow: Array.isArray(rawAllow) ? rawAllow : void 0
|
|
3141
|
-
};
|
|
3142
|
-
}
|
|
3143
|
-
function hasNewMiaoda({ entries, installs, allow }) {
|
|
3144
|
-
return asRecord(entries?.[NEW_MIAODA]) != null || asRecord(installs?.[NEW_MIAODA]) != null || (allow?.includes(NEW_MIAODA) ?? false);
|
|
3145
|
-
}
|
|
3146
|
-
function findResiduals({ entries, installs, allow }, extensionsDir) {
|
|
3147
|
-
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)));
|
|
3148
|
-
}
|
|
3149
|
-
let OldMiaodaPluginsCleanupRule = class OldMiaodaPluginsCleanupRule extends DiagnoseRule {
|
|
3150
|
-
validate(ctx) {
|
|
3151
|
-
const maps = getPluginMaps(ctx.config);
|
|
3152
|
-
if (!hasNewMiaoda(maps)) return { pass: true };
|
|
3153
|
-
const residuals = findResiduals(maps, getExtensionsDir(ctx.configPath));
|
|
3154
|
-
if (residuals.length === 0) return { pass: true };
|
|
3155
|
-
return {
|
|
3156
|
-
pass: false,
|
|
3157
|
-
message: "旧 miaoda 插件残留: " + residuals.sort().join(",")
|
|
3158
|
-
};
|
|
3159
|
-
}
|
|
3160
|
-
repair(ctx) {
|
|
3161
|
-
const maps = getPluginMaps(ctx.config);
|
|
3162
|
-
if (!hasNewMiaoda(maps)) return;
|
|
3163
|
-
const extensionsDir = getExtensionsDir(ctx.configPath);
|
|
3164
|
-
const { entries, installs, allow } = maps;
|
|
3165
|
-
const oldSet = new Set(OLD_PLUGIN_NAMES);
|
|
3166
|
-
if (allow) for (let i = allow.length - 1; i >= 0; i--) {
|
|
3167
|
-
const v = allow[i];
|
|
3168
|
-
if (typeof v === "string" && oldSet.has(v)) allow.splice(i, 1);
|
|
3169
|
-
}
|
|
3170
|
-
for (const name of OLD_PLUGIN_NAMES) {
|
|
3171
|
-
if (entries && name in entries) delete entries[name];
|
|
3172
|
-
if (installs && name in installs) delete installs[name];
|
|
3173
|
-
const target = node_path.default.join(extensionsDir, name);
|
|
3174
|
-
const rel = node_path.default.relative(extensionsDir, target);
|
|
3175
|
-
if (!rel || rel.startsWith("..") || node_path.default.isAbsolute(rel)) continue;
|
|
3176
|
-
try {
|
|
3177
|
-
node_fs.default.rmSync(target, {
|
|
3178
|
-
recursive: true,
|
|
3179
|
-
force: true
|
|
3180
|
-
});
|
|
3181
|
-
} catch (e) {
|
|
3182
|
-
console.error(`[old_miaoda_plugins_cleanup] rmSync ${target} failed: ${e.message}`);
|
|
3183
|
-
}
|
|
3184
|
-
}
|
|
3185
|
-
}
|
|
3186
|
-
};
|
|
3187
|
-
OldMiaodaPluginsCleanupRule = __decorate([Rule({
|
|
3188
|
-
key: "old_miaoda_plugins_cleanup",
|
|
3189
|
-
description: "当新版 openclaw-extension-miaoda 已存在时,清理过时插件引用(openclaw-feishu-greeting、openclaw-miaoda-keepalive 等)",
|
|
3190
|
-
dependsOn: ["config_syntax_check"],
|
|
3191
|
-
repairMode: "standard",
|
|
3192
|
-
level: "silent"
|
|
3193
|
-
})], OldMiaodaPluginsCleanupRule);
|
|
3194
|
-
//#endregion
|
|
3195
|
-
//#region src/rules/builtin-plugin-missing.ts
|
|
3196
|
-
/**
|
|
3197
|
-
* install-extension --all 安装的内置扩展插件列表(manifest role=extension)。
|
|
3198
|
-
* 与 miaoda-official-plugins-install-spec-unlock.ts 中的 OFFICIAL_PLUGIN_NAMES 保持一致。
|
|
3199
|
-
*/
|
|
3200
|
-
const BUILTIN_EXTENSION_PLUGINS = new Set([
|
|
3201
|
-
"openclaw-lark",
|
|
3202
|
-
"openclaw-extension-miaoda",
|
|
3203
|
-
"openclaw-extension-miaoda-coding",
|
|
3204
|
-
"openclaw-guardian-plugin",
|
|
3205
|
-
"openclaw-mem0-plugin"
|
|
3206
|
-
]);
|
|
3207
|
-
function findMissingBuiltinPlugins(ctx) {
|
|
3208
|
-
const extDir = getExtensionsDir(ctx.configPath);
|
|
3209
|
-
return [...BUILTIN_EXTENSION_PLUGINS].filter((name) => !isPluginInstalledOnDisk(extDir, name)).sort();
|
|
2900
|
+
//#region src/rules/builtin-plugin-missing.ts
|
|
2901
|
+
function findMissingBuiltinPlugins(ctx) {
|
|
2902
|
+
const extDir = getExtensionsDir(ctx.configPath);
|
|
2903
|
+
return [...OFFICIAL_EXTENSION_PLUGIN_NAMES].filter((name) => !isPluginInstalledOnDisk(extDir, name)).sort();
|
|
3210
2904
|
}
|
|
3211
2905
|
let BuiltinPluginMissingRule = class BuiltinPluginMissingRule extends DiagnoseRule {
|
|
3212
2906
|
validate(ctx) {
|
|
@@ -3487,10 +3181,7 @@ function extractTarballTolerant(tarball, destDir, opts = {}) {
|
|
|
3487
3181
|
}
|
|
3488
3182
|
//#endregion
|
|
3489
3183
|
//#region src/rules/feishu-plugin-state-normalize.ts
|
|
3490
|
-
const
|
|
3491
|
-
const BUILTIN_FEISHU = "feishu";
|
|
3492
|
-
const LEGACY_PLUGIN_NAME = "feishu-openclaw-plugin";
|
|
3493
|
-
const LEGACY_DIRS_TO_REMOVE = [LEGACY_PLUGIN_NAME, BUILTIN_FEISHU];
|
|
3184
|
+
const LEGACY_DIRS_TO_REMOVE = [LEGACY_LARK_PLUGIN_NAME, BUILTIN_FEISHU_PLUGIN_NAME];
|
|
3494
3185
|
const FEISHU_TOOLS = Object.freeze([
|
|
3495
3186
|
"feishu_bitable_app",
|
|
3496
3187
|
"feishu_bitable_app_table",
|
|
@@ -3536,9 +3227,10 @@ let FeishuPluginStateNormalizeRule = class FeishuPluginStateNormalizeRule extend
|
|
|
3536
3227
|
validate(ctx) {
|
|
3537
3228
|
if (!isPluginInstalled(ctx)) return { pass: true };
|
|
3538
3229
|
const fails = [];
|
|
3539
|
-
if (!isNewPluginEnabled(ctx.config)) fails.push(`plugins.entries["${
|
|
3230
|
+
if (!isNewPluginEnabled(ctx.config)) fails.push(`plugins.entries["${LARK_PLUGIN_NAME}"].enabled !== true(应启用)`);
|
|
3540
3231
|
if (isBuiltinFeishuEnabled(ctx.config)) fails.push("plugins.entries.feishu.enabled === true(应禁用)");
|
|
3541
|
-
|
|
3232
|
+
const grantGap = missingFeishuToolsGrant(ctx.config);
|
|
3233
|
+
if (grantGap) fails.push(`tools.${grantGap} 缺 feishu_* tools`);
|
|
3542
3234
|
const legacyResiduals = findLegacyResiduals(ctx);
|
|
3543
3235
|
if (legacyResiduals.length > 0) fails.push(`legacy 飞书插件残留:${legacyResiduals.join(", ")}`);
|
|
3544
3236
|
if (fails.length === 0) return { pass: true };
|
|
@@ -3548,8 +3240,8 @@ let FeishuPluginStateNormalizeRule = class FeishuPluginStateNormalizeRule extend
|
|
|
3548
3240
|
};
|
|
3549
3241
|
}
|
|
3550
3242
|
repair(ctx) {
|
|
3551
|
-
setEntryEnabled(ctx.config,
|
|
3552
|
-
setEntryEnabled(ctx.config,
|
|
3243
|
+
setEntryEnabled(ctx.config, LARK_PLUGIN_NAME, true);
|
|
3244
|
+
setEntryEnabled(ctx.config, BUILTIN_FEISHU_PLUGIN_NAME, false);
|
|
3553
3245
|
ensureFeishuTools(ctx.config);
|
|
3554
3246
|
cleanupLegacyResiduals(ctx);
|
|
3555
3247
|
}
|
|
@@ -3562,36 +3254,54 @@ FeishuPluginStateNormalizeRule = __decorate([Rule({
|
|
|
3562
3254
|
level: "critical"
|
|
3563
3255
|
})], FeishuPluginStateNormalizeRule);
|
|
3564
3256
|
function isPluginInstalled(ctx) {
|
|
3565
|
-
return node_fs.default.existsSync(node_path.default.join(getExtensionsDir(ctx.configPath),
|
|
3257
|
+
return node_fs.default.existsSync(node_path.default.join(getExtensionsDir(ctx.configPath), LARK_PLUGIN_NAME));
|
|
3566
3258
|
}
|
|
3567
3259
|
function isNewPluginEnabled(config) {
|
|
3568
|
-
return asRecord(getNestedMap(config, "plugins", "entries")?.[
|
|
3260
|
+
return asRecord(getNestedMap(config, "plugins", "entries")?.[LARK_PLUGIN_NAME])?.enabled === true;
|
|
3569
3261
|
}
|
|
3570
3262
|
function isBuiltinFeishuEnabled(config) {
|
|
3571
|
-
return asRecord(getNestedMap(config, "plugins", "entries")?.[
|
|
3572
|
-
}
|
|
3573
|
-
/** 仅看顶层 tools.alsoAllow——agent 级 alsoAllow 是用户对单 agent 的精细化授权,doctor 不动。
|
|
3574
|
-
* 含 "*" 或任一 feishu_* 即视为已配。 */
|
|
3575
|
-
function isTopLevelMissingFeishuTools(config) {
|
|
3576
|
-
return !hasFeishuTool(readAlsoAllow(config));
|
|
3263
|
+
return asRecord(getNestedMap(config, "plugins", "entries")?.[BUILTIN_FEISHU_PLUGIN_NAME])?.enabled === true;
|
|
3577
3264
|
}
|
|
3578
|
-
|
|
3579
|
-
|
|
3580
|
-
const arr = asRecord(tools)?.
|
|
3265
|
+
/** 读取顶层 tools.<key>(allow / alsoAllow)的字符串数组。 */
|
|
3266
|
+
function readTopLevelToolsList(config, key) {
|
|
3267
|
+
const arr = asRecord(asRecord(config)?.tools)?.[key];
|
|
3581
3268
|
if (!Array.isArray(arr)) return [];
|
|
3582
3269
|
return arr.filter((e) => typeof e === "string");
|
|
3583
3270
|
}
|
|
3584
|
-
function hasFeishuTool(
|
|
3585
|
-
if (
|
|
3586
|
-
return
|
|
3271
|
+
function hasFeishuTool(list) {
|
|
3272
|
+
if (list.includes("*")) return true;
|
|
3273
|
+
return list.some((t) => FEISHU_TOOLS.includes(t));
|
|
3274
|
+
}
|
|
3275
|
+
/**
|
|
3276
|
+
* 决定 feishu_* 应补充到哪个授权键:
|
|
3277
|
+
* - 已有非空 tools.allow(限制式白名单)→ 'allow'(合并进 allow,避免与 alsoAllow 冲突)
|
|
3278
|
+
* - 否则 → 'alsoAllow'(追加式,叠加在 profile 基线上)
|
|
3279
|
+
* openclaw schema 禁止同一 scope 同时设 allow + alsoAllow,故有 allow 时必须并入 allow,
|
|
3280
|
+
* 否则会触发 tools_allow_also_allow_conflict 规则反复来回改写。
|
|
3281
|
+
*/
|
|
3282
|
+
function feishuGrantTarget(config) {
|
|
3283
|
+
return readTopLevelToolsList(config, "allow").length > 0 ? "allow" : "alsoAllow";
|
|
3284
|
+
}
|
|
3285
|
+
/**
|
|
3286
|
+
* 仅看顶层 tools——agent 级授权是用户对单 agent 的精细化配置,doctor 不动。
|
|
3287
|
+
* 返回缺失的目标键名('allow' / 'alsoAllow'),不缺时返回 null。
|
|
3288
|
+
*
|
|
3289
|
+
* 不关心 lark-cli:tools.alsoAllow(授权层)与 channels.feishu.tools.deny(插件注册层)
|
|
3290
|
+
* 是两个正交的 config key,可并存——授权一个被 deny 的工具是无害空操作。因此本规则始终
|
|
3291
|
+
* 补全 FEISHU_TOOLS(让 openclaw-lark 实际注册的工具都可用),重叠工具的"不可用"由独立的
|
|
3292
|
+
* deny 规则在注册层处理,互不干扰。
|
|
3293
|
+
*/
|
|
3294
|
+
function missingFeishuToolsGrant(config) {
|
|
3295
|
+
const target = feishuGrantTarget(config);
|
|
3296
|
+
return hasFeishuTool(readTopLevelToolsList(config, target)) ? null : target;
|
|
3587
3297
|
}
|
|
3588
3298
|
function findLegacyResiduals(ctx) {
|
|
3589
3299
|
const found = [];
|
|
3590
3300
|
const plugins = asRecord(ctx.config.plugins);
|
|
3591
|
-
if (asRecord(plugins?.entries)?.[
|
|
3301
|
+
if (asRecord(plugins?.entries)?.["feishu-openclaw-plugin"] != null) found.push("entries[legacy]");
|
|
3592
3302
|
const allow = plugins?.allow;
|
|
3593
|
-
if (Array.isArray(allow) && allow.includes(
|
|
3594
|
-
if (asRecord(plugins?.installs)?.[
|
|
3303
|
+
if (Array.isArray(allow) && allow.includes("feishu-openclaw-plugin")) found.push("allow[legacy]");
|
|
3304
|
+
if (asRecord(plugins?.installs)?.["feishu-openclaw-plugin"] != null) found.push("installs[legacy]");
|
|
3595
3305
|
const extDir = getExtensionsDir(ctx.configPath);
|
|
3596
3306
|
for (const name of LEGACY_DIRS_TO_REMOVE) if (node_fs.default.existsSync(node_path.default.join(extDir, name))) found.push(`fs/${name}`);
|
|
3597
3307
|
return found;
|
|
@@ -3604,21 +3314,22 @@ function setEntryEnabled(config, key, enabled) {
|
|
|
3604
3314
|
};
|
|
3605
3315
|
}
|
|
3606
3316
|
function ensureFeishuTools(config) {
|
|
3607
|
-
const
|
|
3608
|
-
|
|
3609
|
-
|
|
3317
|
+
const target = feishuGrantTarget(config);
|
|
3318
|
+
const current = readTopLevelToolsList(config, target);
|
|
3319
|
+
if (hasFeishuTool(current)) return;
|
|
3320
|
+
ensureRecord(config, "tools")[target] = [...new Set([...current, ...FEISHU_TOOLS])];
|
|
3610
3321
|
}
|
|
3611
3322
|
function cleanupLegacyResiduals(ctx) {
|
|
3612
3323
|
const plugins = asRecord(ctx.config.plugins);
|
|
3613
3324
|
if (plugins) {
|
|
3614
3325
|
const entries = asRecord(plugins.entries);
|
|
3615
|
-
if (entries &&
|
|
3326
|
+
if (entries && "feishu-openclaw-plugin" in entries) delete entries[LEGACY_LARK_PLUGIN_NAME];
|
|
3616
3327
|
const installs = asRecord(plugins.installs);
|
|
3617
|
-
if (installs &&
|
|
3328
|
+
if (installs && "feishu-openclaw-plugin" in installs) delete installs[LEGACY_LARK_PLUGIN_NAME];
|
|
3618
3329
|
const allow = plugins.allow;
|
|
3619
3330
|
if (Array.isArray(allow)) {
|
|
3620
|
-
for (let i = allow.length - 1; i >= 0; i--) if (allow[i] ===
|
|
3621
|
-
if (!allow.includes(
|
|
3331
|
+
for (let i = allow.length - 1; i >= 0; i--) if (allow[i] === "feishu-openclaw-plugin") allow.splice(i, 1);
|
|
3332
|
+
if (!allow.includes("openclaw-lark")) allow.push(LARK_PLUGIN_NAME);
|
|
3622
3333
|
}
|
|
3623
3334
|
}
|
|
3624
3335
|
const extDir = getExtensionsDir(ctx.configPath);
|
|
@@ -3633,13 +3344,6 @@ function cleanupLegacyResiduals(ctx) {
|
|
|
3633
3344
|
}
|
|
3634
3345
|
}
|
|
3635
3346
|
}
|
|
3636
|
-
function ensureRecord(obj, key) {
|
|
3637
|
-
const cur = obj[key];
|
|
3638
|
-
if (cur != null && typeof cur === "object" && !Array.isArray(cur)) return cur;
|
|
3639
|
-
const fresh = {};
|
|
3640
|
-
obj[key] = fresh;
|
|
3641
|
-
return fresh;
|
|
3642
|
-
}
|
|
3643
3347
|
//#endregion
|
|
3644
3348
|
//#region src/version-compat.ts
|
|
3645
3349
|
const VERSION_COMPAT_MAP = Object.freeze([
|
|
@@ -3743,10 +3447,6 @@ function coerceCalVer(v) {
|
|
|
3743
3447
|
function compareCalVer(a, b) {
|
|
3744
3448
|
return semver.default.compare(coerceCalVer(a), coerceCalVer(b));
|
|
3745
3449
|
}
|
|
3746
|
-
/** Look up an entry by exact plugin version; undefined if not in the table. */
|
|
3747
|
-
function findEntry(pluginVersion) {
|
|
3748
|
-
return VERSION_COMPAT_MAP.find((e) => e.openclawLarkVersion === pluginVersion);
|
|
3749
|
-
}
|
|
3750
3450
|
/**
|
|
3751
3451
|
* Infer the effective upper bound for a compat entry that has no explicit maxOpenclawVersion.
|
|
3752
3452
|
*
|
|
@@ -3769,38 +3469,58 @@ function inferEffectiveMax(index) {
|
|
|
3769
3469
|
};
|
|
3770
3470
|
}
|
|
3771
3471
|
/**
|
|
3772
|
-
*
|
|
3773
|
-
*
|
|
3472
|
+
* 对一个**已对标到 VERSION_COMPAT_MAP 键**的版本评估其与 openclaw 的兼容性,
|
|
3473
|
+
* 并在不兼容时给出方向。fork 的 pin、legacy 分类等在 `checkPluginCompat` 处理,
|
|
3474
|
+
* 本函数只负责「版本 × openclaw 区间」这一段唯一逻辑。
|
|
3774
3475
|
*
|
|
3775
|
-
*
|
|
3776
|
-
*
|
|
3777
|
-
*
|
|
3476
|
+
* - index === -1(插件版本比全表都旧,无 floor 条目)→ 无区间可判,视为插件过旧 → 'lark'
|
|
3477
|
+
* - oc < entry.minOpenclawVersion → 'openclaw'
|
|
3478
|
+
* - oc ≥ 推断/显式上界 → 'lark'
|
|
3479
|
+
* - 否则兼容
|
|
3778
3480
|
*
|
|
3779
|
-
*
|
|
3780
|
-
* exclusive max of '2026.5.6', so openclaw@2026.5.7 is correctly detected as incompatible
|
|
3781
|
-
* whereas the old compatible() call would have returned true.
|
|
3481
|
+
* 上界语义:显式 maxOpenclawVersion 为 INCLUSIVE;推断上界为 EXCLUSIVE。
|
|
3782
3482
|
*/
|
|
3783
|
-
function
|
|
3483
|
+
function evaluateVersionRange(pluginVersion, openclawVersion) {
|
|
3784
3484
|
const index = VERSION_COMPAT_MAP.findIndex((e) => compareCalVer(e.openclawLarkVersion, pluginVersion) <= 0);
|
|
3785
|
-
if (index === -1) return
|
|
3485
|
+
if (index === -1) return {
|
|
3486
|
+
compatible: false,
|
|
3487
|
+
direction: "lark",
|
|
3488
|
+
entry: void 0
|
|
3489
|
+
};
|
|
3786
3490
|
const entry = VERSION_COMPAT_MAP[index];
|
|
3787
3491
|
const oc = coerceCalVer(openclawVersion);
|
|
3788
|
-
if (semver.default.lt(oc, coerceCalVer(entry.minOpenclawVersion))) return
|
|
3492
|
+
if (semver.default.lt(oc, coerceCalVer(entry.minOpenclawVersion))) return {
|
|
3493
|
+
compatible: false,
|
|
3494
|
+
direction: "openclaw",
|
|
3495
|
+
entry
|
|
3496
|
+
};
|
|
3789
3497
|
const maxInfo = inferEffectiveMax(index);
|
|
3790
|
-
if (
|
|
3791
|
-
|
|
3792
|
-
|
|
3498
|
+
if (maxInfo) {
|
|
3499
|
+
const max = coerceCalVer(maxInfo.max);
|
|
3500
|
+
if (maxInfo.exclusive ? !semver.default.lt(oc, max) : semver.default.gt(oc, max)) return {
|
|
3501
|
+
compatible: false,
|
|
3502
|
+
direction: "lark",
|
|
3503
|
+
entry
|
|
3504
|
+
};
|
|
3505
|
+
}
|
|
3506
|
+
return {
|
|
3507
|
+
compatible: true,
|
|
3508
|
+
direction: null,
|
|
3509
|
+
entry
|
|
3510
|
+
};
|
|
3793
3511
|
}
|
|
3794
|
-
const FORK_LARK_PLUGIN_PINNED_VERSION = "2026.4.1";
|
|
3795
3512
|
/**
|
|
3796
|
-
*
|
|
3797
|
-
*
|
|
3798
|
-
*
|
|
3513
|
+
* Fork plugin handling.
|
|
3514
|
+
*
|
|
3515
|
+
* `@lark-apaas/openclaw-lark` is an internal fork whose own version numbers
|
|
3516
|
+
* (e.g. 2026.4.3, 2026.4.4) are NOT keys in VERSION_COMPAT_MAP. Floor-matching
|
|
3517
|
+
* a fork version against the official-keyed map only works by coincidence of
|
|
3518
|
+
* numbering and breaks once the fork version drifts past an official entry.
|
|
3519
|
+
* Both the install-time compat check and the diagnose rule must therefore pin
|
|
3520
|
+
* the fork to its official-equivalent version before consulting the map.
|
|
3799
3521
|
*/
|
|
3800
|
-
|
|
3801
|
-
|
|
3802
|
-
return version;
|
|
3803
|
-
}
|
|
3522
|
+
/** fork 对标的官方 openclaw-lark 版本(与 VERSION_COMPAT_MAP 强耦合,故留在此处)。 */
|
|
3523
|
+
const FORK_LARK_PLUGIN_PINNED_VERSION = "2026.4.1";
|
|
3804
3524
|
/**
|
|
3805
3525
|
* Floor match: find the entry with the largest openclawLarkVersion that is
|
|
3806
3526
|
* ≤ pluginVersion ("closest lower-or-equal version").
|
|
@@ -3812,263 +3532,702 @@ function resolveCompatVersion(packageName, version) {
|
|
|
3812
3532
|
function findClosestEntry(pluginVersion) {
|
|
3813
3533
|
return VERSION_COMPAT_MAP.find((e) => compareCalVer(e.openclawLarkVersion, pluginVersion) <= 0);
|
|
3814
3534
|
}
|
|
3535
|
+
/** "@scope/name" → "name";无 scope 原样返回。 */
|
|
3536
|
+
function extractBaseName(name) {
|
|
3537
|
+
if (!name) return void 0;
|
|
3538
|
+
return name.startsWith("@") ? name.split("/")[1] : name;
|
|
3539
|
+
}
|
|
3540
|
+
function reasonFromRange(r) {
|
|
3541
|
+
if (r.compatible) return "compatible";
|
|
3542
|
+
if (r.direction === "openclaw") return "oc-below-min";
|
|
3543
|
+
return r.entry ? "oc-above-max" : "version-not-in-map";
|
|
3544
|
+
}
|
|
3545
|
+
/**
|
|
3546
|
+
* 统一的飞书插件兼容判定入口——唯一的判定真相源。
|
|
3547
|
+
*
|
|
3548
|
+
* 入参只有三个:插件包名、插件版本、当前 openclaw 版本(**不涉及 recommendedOpenclawTag**,
|
|
3549
|
+
* 推荐版本的处理交由各业务点)。判定算法:
|
|
3550
|
+
*
|
|
3551
|
+
* 1. fork `@lark-apaas/openclaw-lark`:先 pin 到对标官方版本(FORK_LARK_PLUGIN_PINNED_VERSION)
|
|
3552
|
+
* 再按区间判定上下界。
|
|
3553
|
+
* 2. 其他 `@lark-apaas/*` fork:无条件豁免(兼容)。
|
|
3554
|
+
* 3. legacy `feishu-openclaw-plugin`(含带 scope 形式):一律不兼容,方向 = 换飞书插件。
|
|
3555
|
+
* 4. 官方版及其余包:用自身版本按 VERSION_COMPAT_MAP 区间判定;读不到版本号 → 不兼容(换插件)。
|
|
3556
|
+
*
|
|
3557
|
+
* 所有消费方(install-extension 预检、feishu_plugin_version_compat 两条规则、needsLarkUpgrade)
|
|
3558
|
+
* 都应消费本方法,不再各自实现兼容/方向逻辑,避免口径漂移。
|
|
3559
|
+
*/
|
|
3560
|
+
function checkPluginCompat(packageName, pluginVersion, openclawVersion) {
|
|
3561
|
+
if (packageName === "@lark-apaas/openclaw-lark") {
|
|
3562
|
+
const resolvedVersion = FORK_LARK_PLUGIN_PINNED_VERSION;
|
|
3563
|
+
const r = evaluateVersionRange(resolvedVersion, openclawVersion);
|
|
3564
|
+
return {
|
|
3565
|
+
...r,
|
|
3566
|
+
reason: reasonFromRange(r),
|
|
3567
|
+
resolvedVersion
|
|
3568
|
+
};
|
|
3569
|
+
}
|
|
3570
|
+
if ((packageName && packageName.startsWith("@") ? packageName.split("/")[0] : void 0) === "@lark-apaas") return {
|
|
3571
|
+
compatible: true,
|
|
3572
|
+
direction: null,
|
|
3573
|
+
reason: "fork-exempt",
|
|
3574
|
+
resolvedVersion: pluginVersion,
|
|
3575
|
+
entry: void 0
|
|
3576
|
+
};
|
|
3577
|
+
if (extractBaseName(packageName) === "feishu-openclaw-plugin") return {
|
|
3578
|
+
compatible: false,
|
|
3579
|
+
direction: "lark",
|
|
3580
|
+
reason: "legacy",
|
|
3581
|
+
resolvedVersion: pluginVersion,
|
|
3582
|
+
entry: void 0
|
|
3583
|
+
};
|
|
3584
|
+
if (!pluginVersion) return {
|
|
3585
|
+
compatible: false,
|
|
3586
|
+
direction: "lark",
|
|
3587
|
+
reason: "version-unknown",
|
|
3588
|
+
resolvedVersion: void 0,
|
|
3589
|
+
entry: void 0
|
|
3590
|
+
};
|
|
3591
|
+
const r = evaluateVersionRange(pluginVersion, openclawVersion);
|
|
3592
|
+
return {
|
|
3593
|
+
...r,
|
|
3594
|
+
reason: reasonFromRange(r),
|
|
3595
|
+
resolvedVersion: pluginVersion
|
|
3596
|
+
};
|
|
3597
|
+
}
|
|
3598
|
+
//#endregion
|
|
3599
|
+
//#region src/rules/feishu-plugin-version-compat.ts
|
|
3600
|
+
const LEGACY_SHORT_NAMES = [LEGACY_LARK_PLUGIN_NAME];
|
|
3601
|
+
let _ocVersion = void 0;
|
|
3602
|
+
function getOcVersion() {
|
|
3603
|
+
if (_ocVersion === void 0) _ocVersion = readOpenclawRuntimeVersion();
|
|
3604
|
+
return _ocVersion;
|
|
3605
|
+
}
|
|
3606
|
+
const _installedCache = /* @__PURE__ */ new Map();
|
|
3607
|
+
function getInstalledPlugin(ctx) {
|
|
3608
|
+
if (!_installedCache.has(ctx.configPath)) _installedCache.set(ctx.configPath, detectInstalledLarkPlugin(ctx));
|
|
3609
|
+
return _installedCache.get(ctx.configPath);
|
|
3610
|
+
}
|
|
3611
|
+
/** 提取公共前置上下文;任何前置条件不满足时返回 null(规则 pass)。 */
|
|
3612
|
+
function resolveCompatContext(ctx) {
|
|
3613
|
+
const ocCur = getOcVersion();
|
|
3614
|
+
if (!ocCur) return null;
|
|
3615
|
+
const installed = getInstalledPlugin(ctx);
|
|
3616
|
+
if (installed == null) return null;
|
|
3617
|
+
const compatResult = checkPluginCompat(pluginIdentifier(installed), installed.version, ocCur);
|
|
3618
|
+
return {
|
|
3619
|
+
ocCur,
|
|
3620
|
+
installed,
|
|
3621
|
+
isLegacy: isLegacyPlugin(installed),
|
|
3622
|
+
compatResult
|
|
3623
|
+
};
|
|
3624
|
+
}
|
|
3625
|
+
/**
|
|
3626
|
+
* 飞书插件与当前 openclaw 不兼容,且「升级 openclaw 有望转兼容」时 → 提示调整 openclaw。
|
|
3627
|
+
*
|
|
3628
|
+
* 判定顺序:
|
|
3629
|
+
* 1. 无 recommendedOpenclawTag(如 doctor 模式无目标)→ 前置短路 pass,连兼容性都不判
|
|
3630
|
+
* (upgrade_openclaw 语义是「升到推荐目标」,没有目标就无从建议)。
|
|
3631
|
+
* 2. checkPluginCompat 判为兼容 → pass。
|
|
3632
|
+
* 3. 不兼容时只比较 ocCur 与 recommendedOc:仅当 ocCur < recommendedOc(升 openclaw 能拉到
|
|
3633
|
+
* 更高版本、有望满足插件要求)才报 upgrade_openclaw;ocCur ≥ recommendedOc 说明升 openclaw
|
|
3634
|
+
* 无益,交由 Rule 2 报 upgrade_lark。
|
|
3635
|
+
*
|
|
3636
|
+
*/
|
|
3637
|
+
let FeishuPluginOpenclawUpgradeRule = class FeishuPluginOpenclawUpgradeRule extends DiagnoseRule {
|
|
3638
|
+
validate(ctx) {
|
|
3639
|
+
const recommendedOc = ctx.vars.recommendedOpenclawTag;
|
|
3640
|
+
if (!recommendedOc) return { pass: true };
|
|
3641
|
+
const cc = resolveCompatContext(ctx);
|
|
3642
|
+
if (!cc) return { pass: true };
|
|
3643
|
+
const { ocCur, installed, isLegacy, compatResult } = cc;
|
|
3644
|
+
if (compatResult.compatible) return { pass: true };
|
|
3645
|
+
if (compareCalVer(ocCur, recommendedOc) >= 0) return { pass: true };
|
|
3646
|
+
return {
|
|
3647
|
+
pass: false,
|
|
3648
|
+
action: "upgrade_openclaw",
|
|
3649
|
+
message: `${buildCompatPrefix(installed, ocCur, isLegacy)};将 openclaw 升级到 ${recommendedOc} 即可满足(飞书插件随之同步)`
|
|
3650
|
+
};
|
|
3651
|
+
}
|
|
3652
|
+
};
|
|
3653
|
+
FeishuPluginOpenclawUpgradeRule = __decorate([Rule({
|
|
3654
|
+
key: "feishu_plugin_version_compat_openclaw",
|
|
3655
|
+
description: "检查飞书插件是否要求更高版本的 openclaw;是则提示升级 openclaw",
|
|
3656
|
+
dependsOn: ["config_syntax_check"],
|
|
3657
|
+
repairMode: "user-confirm",
|
|
3658
|
+
level: "critical",
|
|
3659
|
+
usesVars: ["recommendedOpenclawTag"]
|
|
3660
|
+
})], FeishuPluginOpenclawUpgradeRule);
|
|
3661
|
+
/**
|
|
3662
|
+
* 飞书插件与当前 openclaw 不兼容 → 提示调整飞书插件(重装 / 换版本,含 legacy 旧包替换)。
|
|
3663
|
+
*
|
|
3664
|
+
* 仅判断兼容性,不依赖 recommendedOc:compatResult.compatible 为真 → pass,否则 → upgrade_lark。
|
|
3665
|
+
*
|
|
3666
|
+
* 与 Rule 1 的协作靠 dependsOn(LOAD-BEARING,非仅排序):当「不兼容且 ocCur < recommendedOc」时
|
|
3667
|
+
* Rule 1 先报 upgrade_openclaw 并失败,dependsOn 使本规则被跳过,避免对同一不兼容同时报两个动作;
|
|
3668
|
+
* 其余不兼容场景(ocCur ≥ recommendedOc / 无推荐目标)Rule 1 pass,本规则如实报 upgrade_lark。
|
|
3669
|
+
* (--rule=lark 单独运行不经 Rule 1,会对任何不兼容直接报 upgrade_lark。)
|
|
3670
|
+
*/
|
|
3671
|
+
let FeishuPluginLarkUpgradeRule = class FeishuPluginLarkUpgradeRule extends DiagnoseRule {
|
|
3672
|
+
validate(ctx) {
|
|
3673
|
+
const cc = resolveCompatContext(ctx);
|
|
3674
|
+
if (!cc) return { pass: true };
|
|
3675
|
+
const { ocCur, installed, isLegacy, compatResult } = cc;
|
|
3676
|
+
if (compatResult.compatible) return { pass: true };
|
|
3677
|
+
return {
|
|
3678
|
+
pass: false,
|
|
3679
|
+
action: "upgrade_lark",
|
|
3680
|
+
message: `${buildCompatPrefix(installed, ocCur, isLegacy)};需要将飞书插件安装/升级到与当前 openclaw 兼容的版本`
|
|
3681
|
+
};
|
|
3682
|
+
}
|
|
3683
|
+
};
|
|
3684
|
+
FeishuPluginLarkUpgradeRule = __decorate([Rule({
|
|
3685
|
+
key: "feishu_plugin_version_compat_lark",
|
|
3686
|
+
description: "检查飞书插件版本是否落后于当前 openclaw;是则提示升级飞书插件",
|
|
3687
|
+
dependsOn: ["config_syntax_check", "feishu_plugin_version_compat_openclaw"],
|
|
3688
|
+
repairMode: "user-confirm",
|
|
3689
|
+
level: "critical"
|
|
3690
|
+
})], FeishuPluginLarkUpgradeRule);
|
|
3691
|
+
/**
|
|
3692
|
+
* 传给 checkPluginCompat 的包标识:优先 package.json name(含 scope,用于识别 fork),
|
|
3693
|
+
* 退化到 plugins.allow 短名(legacy 旧包靠短名 feishu-openclaw-plugin 识别)。
|
|
3694
|
+
*/
|
|
3695
|
+
function pluginIdentifier(p) {
|
|
3696
|
+
return p.fullName ?? p.allowName;
|
|
3697
|
+
}
|
|
3698
|
+
function isLegacyPlugin(p) {
|
|
3699
|
+
return LEGACY_SHORT_NAMES.includes(p.allowName);
|
|
3700
|
+
}
|
|
3701
|
+
function buildCompatPrefix(installed, ocCur, isLegacy) {
|
|
3702
|
+
const desc = describePlugin(installed);
|
|
3703
|
+
if (isLegacy) return `检测到已弃用的旧包 ${desc}(包名 "${installed.allowName}" 已停止维护,需替换为 "openclaw-lark")`;
|
|
3704
|
+
return `飞书插件 ${desc} 与当前 openclaw@${ocCur} 不兼容(${describeCompatConstraint(installed.version ? findClosestEntry(installed.version) : void 0, installed.version)})`;
|
|
3705
|
+
}
|
|
3706
|
+
function describeCompatConstraint(entry, pluginVersion) {
|
|
3707
|
+
if (!entry) return `插件版本 ${pluginVersion ?? "未知"} 不在版本兼容表中`;
|
|
3708
|
+
if (entry.maxOpenclawVersion) return `该插件版本要求 openclaw ∈ [${entry.minOpenclawVersion}, ${entry.maxOpenclawVersion}]`;
|
|
3709
|
+
return `该插件版本要求 openclaw ≥ ${entry.minOpenclawVersion}`;
|
|
3710
|
+
}
|
|
3711
|
+
function describePlugin(p) {
|
|
3712
|
+
return (p.fullName ?? p.allowName) + (p.version ? `@${p.version}` : "");
|
|
3713
|
+
}
|
|
3714
|
+
function readPluginPackageJson(filePath) {
|
|
3715
|
+
try {
|
|
3716
|
+
if (!node_fs.default.existsSync(filePath)) return null;
|
|
3717
|
+
const raw = node_fs.default.readFileSync(filePath, "utf-8");
|
|
3718
|
+
const parsed = JSON.parse(raw);
|
|
3719
|
+
return {
|
|
3720
|
+
name: typeof parsed.name === "string" ? parsed.name : void 0,
|
|
3721
|
+
version: typeof parsed.version === "string" ? parsed.version : void 0
|
|
3722
|
+
};
|
|
3723
|
+
} catch {
|
|
3724
|
+
return null;
|
|
3725
|
+
}
|
|
3726
|
+
}
|
|
3727
|
+
/** "已装" = plugins.allow 含名 AND extensions/<name>/package.json 真实存在。 */
|
|
3728
|
+
function detectInstalledLarkPlugin(ctx) {
|
|
3729
|
+
const allowRaw = asRecord(ctx.config.plugins)?.allow;
|
|
3730
|
+
const allow = Array.isArray(allowRaw) ? allowRaw.filter((e) => typeof e === "string") : [];
|
|
3731
|
+
const extDir = getExtensionsDir(ctx.configPath);
|
|
3732
|
+
const installs = getNestedMap(ctx.config, "plugins", "installs");
|
|
3733
|
+
for (const name of [LARK_PLUGIN_NAME, ...LEGACY_SHORT_NAMES]) {
|
|
3734
|
+
if (!allow.includes(name)) continue;
|
|
3735
|
+
const pkgPath = node_path.default.join(extDir, name, "package.json");
|
|
3736
|
+
if (!node_fs.default.existsSync(pkgPath)) continue;
|
|
3737
|
+
const pkg = readPluginPackageJson(pkgPath) ?? {};
|
|
3738
|
+
const installEntry = installs && asRecord(installs[name]);
|
|
3739
|
+
return {
|
|
3740
|
+
allowName: name,
|
|
3741
|
+
fullName: pkg.name ?? extractScopedNameFromSpec$1(installEntry?.spec),
|
|
3742
|
+
version: pkg.version ?? (typeof installEntry?.version === "string" ? installEntry.version : void 0)
|
|
3743
|
+
};
|
|
3744
|
+
}
|
|
3745
|
+
return null;
|
|
3746
|
+
}
|
|
3747
|
+
/** "@scope/name@1.2.3" / "name@1.2.3" / "@scope/name" / "name" → 去掉 @version 后缀 */
|
|
3748
|
+
function extractScopedNameFromSpec$1(spec) {
|
|
3749
|
+
if (typeof spec !== "string") return void 0;
|
|
3750
|
+
const at = spec.indexOf("@", 1);
|
|
3751
|
+
return at === -1 ? spec : spec.slice(0, at);
|
|
3752
|
+
}
|
|
3753
|
+
/**
|
|
3754
|
+
* 判断已安装的飞书插件是否与当前 openclaw 版本不兼容(或为需要替换的 legacy 插件)。
|
|
3755
|
+
* 被 upgrade-lark 前置检测门控(--check-only 和正式安装模式)调用。
|
|
3756
|
+
*/
|
|
3757
|
+
function needsLarkUpgrade(ctx) {
|
|
3758
|
+
const cc = resolveCompatContext(ctx);
|
|
3759
|
+
if (!cc) return false;
|
|
3760
|
+
return !cc.compatResult.compatible;
|
|
3761
|
+
}
|
|
3762
|
+
//#endregion
|
|
3763
|
+
//#region src/paths.ts
|
|
3764
|
+
/**
|
|
3765
|
+
* Central directory for all ephemeral diagnose/reset artifacts: task status
|
|
3766
|
+
* files (`reset-<taskId>.json`) and human-readable step logs
|
|
3767
|
+
* (`reset-<taskId>.log`). Having everything under one dir makes debugging a
|
|
3768
|
+
* stuck reset much easier — `ls /tmp/openclaw-diagnose/` shows every recent
|
|
3769
|
+
* run, and each run's log is right next to its state.
|
|
3770
|
+
*/
|
|
3771
|
+
const DIAGNOSE_DIR = "/tmp/openclaw-diagnose";
|
|
3772
|
+
function resetResultFile(taskId) {
|
|
3773
|
+
return `${DIAGNOSE_DIR}/reset-${taskId}.json`;
|
|
3774
|
+
}
|
|
3775
|
+
function resetLogFile(taskId) {
|
|
3776
|
+
return `${DIAGNOSE_DIR}/reset-${taskId}.log`;
|
|
3777
|
+
}
|
|
3778
|
+
/** Sandbox workspace root where openclaw config + agent state lives. */
|
|
3779
|
+
const WORKSPACE_DIR = "/home/gem/workspace/agent";
|
|
3780
|
+
/** File containing the provider key used by the openclaw miaoda provider. */
|
|
3781
|
+
const PROVIDER_FILE_PATH = "/home/gem/workspace/.force/openclaw/miaoda-provider-key";
|
|
3782
|
+
/** File containing the miaoda openclaw secrets JSON. */
|
|
3783
|
+
const SECRETS_FILE_PATH = "/home/gem/workspace/.force/openclaw/miaoda-openclaw-secrets.json";
|
|
3784
|
+
/** Absolute path to the openclaw config JSON. */
|
|
3785
|
+
const CONFIG_PATH = `${WORKSPACE_DIR}/openclaw.json`;
|
|
3786
|
+
/**
|
|
3787
|
+
* upgrade-lark 场景专属修复状态的信号文件目录。
|
|
3788
|
+
* fixStatus 有值时在此目录下创建同名文件(如 /tmp/event/PORT_FIX_READY),
|
|
3789
|
+
* 文件内容为完整的 UpgradeLarkResult JSON,供外部进程轮询感知升级结果。
|
|
3790
|
+
*/
|
|
3791
|
+
const FIX_EVENT_DIR = "/tmp/event";
|
|
3792
|
+
/**
|
|
3793
|
+
* 安装指令互斥锁文件路径。
|
|
3794
|
+
* upgrade-lark / install-openclaw / install-extension / install-cli / reset --worker
|
|
3795
|
+
* 共享此锁,同一时刻只允许一个安装指令运行。
|
|
3796
|
+
* 锁文件内容:{ pid, command, startedAt }。
|
|
3797
|
+
*/
|
|
3798
|
+
const INSTALL_LOCK_FILE = `${DIAGNOSE_DIR}/install.lock`;
|
|
3799
|
+
/**
|
|
3800
|
+
* upgrade-lark 每次运行的日志文件路径,含时间戳便于按时间排序定位。
|
|
3801
|
+
* checkOnly=true 时文件名含 "-check" 后缀,便于与正式安装日志区分。
|
|
3802
|
+
*/
|
|
3803
|
+
function upgradeLarkLogFile(runId, checkOnly = false) {
|
|
3804
|
+
const ts = (/* @__PURE__ */ new Date()).toISOString().slice(0, 19).replace(/:/g, "-");
|
|
3805
|
+
return `${DIAGNOSE_DIR}/upgrade-lark${checkOnly ? "-check" : ""}-${ts}-${runId.slice(0, 8)}.log`;
|
|
3806
|
+
}
|
|
3807
|
+
//#endregion
|
|
3808
|
+
//#region src/lark-cli-init.ts
|
|
3809
|
+
const PE_XML_TAG = "lark-cli-pe";
|
|
3810
|
+
const PE_PLACEHOLDER = `
|
|
3811
|
+
<${PE_XML_TAG}>
|
|
3812
|
+
**【强制要求 - 无例外】飞书所有操作必须先读 feishu-lark-cli skill。未读skill不得调用任何飞书工具,违者视为操作失误。**
|
|
3813
|
+
</${PE_XML_TAG}>
|
|
3814
|
+
`;
|
|
3815
|
+
function isLarkPluginInstalled(configPath) {
|
|
3816
|
+
const extDir = getExtensionsDir(configPath);
|
|
3817
|
+
return LARK_PLUGIN_DIR_NAMES.some((name) => {
|
|
3818
|
+
try {
|
|
3819
|
+
return node_fs.default.existsSync(node_path.default.join(extDir, name, "package.json"));
|
|
3820
|
+
} catch {
|
|
3821
|
+
return false;
|
|
3822
|
+
}
|
|
3823
|
+
});
|
|
3824
|
+
}
|
|
3825
|
+
function isLarkCliAvailable$2() {
|
|
3826
|
+
try {
|
|
3827
|
+
return (0, node_child_process.spawnSync)("lark-cli", ["--version"], {
|
|
3828
|
+
encoding: "utf-8",
|
|
3829
|
+
timeout: 5e3,
|
|
3830
|
+
stdio: [
|
|
3831
|
+
"ignore",
|
|
3832
|
+
"pipe",
|
|
3833
|
+
"ignore"
|
|
3834
|
+
]
|
|
3835
|
+
}).status === 0;
|
|
3836
|
+
} catch {
|
|
3837
|
+
return false;
|
|
3838
|
+
}
|
|
3839
|
+
}
|
|
3840
|
+
function readConfig(configPath) {
|
|
3841
|
+
try {
|
|
3842
|
+
const raw = node_fs.default.readFileSync(configPath, "utf-8");
|
|
3843
|
+
const parsed = loadJSON5().parse(raw);
|
|
3844
|
+
return parsed != null && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
|
|
3845
|
+
} catch {
|
|
3846
|
+
return null;
|
|
3847
|
+
}
|
|
3848
|
+
}
|
|
3849
|
+
/**
|
|
3850
|
+
* Resolve the feishu app secret for the given appId.
|
|
3851
|
+
*
|
|
3852
|
+
* Lookup order:
|
|
3853
|
+
* 1. channels.feishu.appSecret (single-agent: feishu.appId === appId)
|
|
3854
|
+
* 2. channels.feishu.accounts[key].appSecret (multi-agent: account.appId === appId)
|
|
3855
|
+
*
|
|
3856
|
+
* Value interpretation:
|
|
3857
|
+
* - string → use directly
|
|
3858
|
+
* - object → secret is managed by a provider; use `feishuAppSecret` param instead
|
|
3859
|
+
*
|
|
3860
|
+
* Returns null when the secret cannot be determined.
|
|
3861
|
+
*/
|
|
3862
|
+
function resolveAppSecret(appId, config, feishuAppSecret) {
|
|
3863
|
+
const feishu = getNestedMap(config, "channels", "feishu");
|
|
3864
|
+
if (!feishu) return null;
|
|
3865
|
+
let rawSecret;
|
|
3866
|
+
if (typeof feishu.appId === "string" && feishu.appId === appId) rawSecret = feishu.appSecret;
|
|
3867
|
+
else {
|
|
3868
|
+
const accounts = asRecord(feishu.accounts);
|
|
3869
|
+
if (accounts) for (const [, val] of Object.entries(accounts)) {
|
|
3870
|
+
const account = asRecord(val);
|
|
3871
|
+
if (account?.appId === appId) {
|
|
3872
|
+
rawSecret = account.appSecret ?? feishu.appSecret;
|
|
3873
|
+
break;
|
|
3874
|
+
}
|
|
3875
|
+
}
|
|
3876
|
+
}
|
|
3877
|
+
if (typeof rawSecret === "string" && rawSecret) return rawSecret;
|
|
3878
|
+
if (rawSecret != null && typeof rawSecret === "object") return feishuAppSecret ?? null;
|
|
3879
|
+
return null;
|
|
3880
|
+
}
|
|
3881
|
+
/**
|
|
3882
|
+
* Resolve the agents.md path for the given appId from the openclaw config.
|
|
3883
|
+
*
|
|
3884
|
+
* Case 1: appId matches channels.feishu.appId (single-agent path)
|
|
3885
|
+
* → WORKSPACE_DIR/AGENTS.md
|
|
3886
|
+
*
|
|
3887
|
+
* Case 2: appId found in channels.feishu.accounts (multi-agent path)
|
|
3888
|
+
* → find account key where account.appId === appId
|
|
3889
|
+
* → find binding where match.channel=feishu && match.accountId=that key
|
|
3890
|
+
* → if agentId === 'main' → WORKSPACE_DIR/agents.md
|
|
3891
|
+
* → else find agent in agents.list by id → agent.workspace/agents.md
|
|
3892
|
+
*
|
|
3893
|
+
* Returns null when the path cannot be determined.
|
|
3894
|
+
*/
|
|
3895
|
+
function resolveAgentsMdPath(appId, config) {
|
|
3896
|
+
const feishu = getNestedMap(config, "channels", "feishu");
|
|
3897
|
+
if (!feishu) {
|
|
3898
|
+
console.error("resolveAgentsMdPath: channels.feishu not found");
|
|
3899
|
+
return null;
|
|
3900
|
+
}
|
|
3901
|
+
if (typeof feishu.appId === "string" && feishu.appId === appId) {
|
|
3902
|
+
console.error(`resolveAgentsMdPath: case=single-agent feishu.appId=${feishu.appId}`);
|
|
3903
|
+
return node_path.default.join(WORKSPACE_DIR, "workspace", "AGENTS.md");
|
|
3904
|
+
}
|
|
3905
|
+
const accounts = asRecord(feishu.accounts);
|
|
3906
|
+
if (!accounts) {
|
|
3907
|
+
console.error("resolveAgentsMdPath: feishu.accounts not found");
|
|
3908
|
+
return null;
|
|
3909
|
+
}
|
|
3910
|
+
let accountId;
|
|
3911
|
+
for (const [key, val] of Object.entries(accounts)) if (asRecord(val)?.appId === appId) {
|
|
3912
|
+
accountId = key;
|
|
3913
|
+
break;
|
|
3914
|
+
}
|
|
3915
|
+
if (!accountId) {
|
|
3916
|
+
console.error(`resolveAgentsMdPath: no account found with appId=${appId} in feishu.accounts keys=[${Object.keys(accounts).join(",")}]`);
|
|
3917
|
+
return null;
|
|
3918
|
+
}
|
|
3919
|
+
console.error(`resolveAgentsMdPath: found accountId=${accountId}`);
|
|
3920
|
+
const bindings = Array.isArray(config.bindings) ? config.bindings : [];
|
|
3921
|
+
let agentId;
|
|
3922
|
+
for (const b of bindings) {
|
|
3923
|
+
const binding = asRecord(b);
|
|
3924
|
+
if (!binding) continue;
|
|
3925
|
+
const match = asRecord(binding.match);
|
|
3926
|
+
if (match?.channel === "feishu" && match?.accountId === accountId) {
|
|
3927
|
+
if (typeof binding.agentId === "string") {
|
|
3928
|
+
agentId = binding.agentId;
|
|
3929
|
+
break;
|
|
3930
|
+
}
|
|
3931
|
+
}
|
|
3932
|
+
}
|
|
3933
|
+
if (!agentId) {
|
|
3934
|
+
console.error(`resolveAgentsMdPath: no binding found for accountId=${accountId} in bindings(count=${bindings.length})`);
|
|
3935
|
+
return null;
|
|
3936
|
+
}
|
|
3937
|
+
console.error(`resolveAgentsMdPath: found agentId=${agentId}`);
|
|
3938
|
+
if (agentId === "main") {
|
|
3939
|
+
console.error("resolveAgentsMdPath: case=multi-agent-main");
|
|
3940
|
+
return node_path.default.join(WORKSPACE_DIR, "workspace", "AGENTS.md");
|
|
3941
|
+
}
|
|
3942
|
+
const agentsObj = asRecord(config.agents);
|
|
3943
|
+
const list = Array.isArray(agentsObj?.list) ? agentsObj.list : [];
|
|
3944
|
+
for (const a of list) {
|
|
3945
|
+
const agent = asRecord(a);
|
|
3946
|
+
if (agent?.id === agentId) {
|
|
3947
|
+
const ws = typeof agent.workspace === "string" ? agent.workspace : node_path.default.join(WORKSPACE_DIR, "workspace");
|
|
3948
|
+
console.error(`resolveAgentsMdPath: case=multi-agent-custom agentId=${agentId} workspace=${ws}`);
|
|
3949
|
+
return node_path.default.join(ws, "AGENTS.md");
|
|
3950
|
+
}
|
|
3951
|
+
}
|
|
3952
|
+
console.error(`resolveAgentsMdPath: agentId=${agentId} not found in agents.list(count=${list.length})`);
|
|
3953
|
+
return null;
|
|
3954
|
+
}
|
|
3955
|
+
function appendPeToAgentsMd(agentsMdPath) {
|
|
3956
|
+
const dir = node_path.default.dirname(agentsMdPath);
|
|
3957
|
+
if (!node_fs.default.existsSync(dir)) node_fs.default.mkdirSync(dir, { recursive: true });
|
|
3958
|
+
const existing = node_fs.default.existsSync(agentsMdPath) ? node_fs.default.readFileSync(agentsMdPath, "utf-8") : "";
|
|
3959
|
+
if (existing.includes(`<lark-cli-pe>`)) {
|
|
3960
|
+
console.error(`lark-cli-init: <${PE_XML_TAG}> already present in ${agentsMdPath}, skipping`);
|
|
3961
|
+
return;
|
|
3962
|
+
}
|
|
3963
|
+
const sep = existing.length > 0 && !existing.endsWith("\n") ? "\n" : "";
|
|
3964
|
+
node_fs.default.appendFileSync(agentsMdPath, `${sep}${PE_PLACEHOLDER}`, "utf-8");
|
|
3965
|
+
console.error(`lark-cli-init: appended PE placeholder to ${agentsMdPath}`);
|
|
3966
|
+
}
|
|
3967
|
+
/**
|
|
3968
|
+
* Collect every Feishu bot appId declared in the openclaw config.
|
|
3969
|
+
* Covers both single-agent (channels.feishu.appId) and multi-agent
|
|
3970
|
+
* (channels.feishu.accounts[*].appId) layouts. Returns a deduplicated list.
|
|
3971
|
+
*/
|
|
3972
|
+
function collectFeishuAppIds(configPath) {
|
|
3973
|
+
const config = readConfig(configPath ?? CONFIG_PATH);
|
|
3974
|
+
if (!config) return [];
|
|
3975
|
+
const feishu = getNestedMap(config, "channels", "feishu");
|
|
3976
|
+
if (!feishu) return [];
|
|
3977
|
+
const appIds = /* @__PURE__ */ new Set();
|
|
3978
|
+
const topAppId = feishu.appId;
|
|
3979
|
+
if (typeof topAppId === "string" && topAppId.trim()) appIds.add(topAppId.trim());
|
|
3980
|
+
const accounts = asRecord(feishu.accounts);
|
|
3981
|
+
if (accounts) for (const val of Object.values(accounts)) {
|
|
3982
|
+
const appId = asRecord(val)?.appId;
|
|
3983
|
+
if (typeof appId === "string" && appId.trim()) appIds.add(appId.trim());
|
|
3984
|
+
}
|
|
3985
|
+
return [...appIds];
|
|
3986
|
+
}
|
|
3987
|
+
function runLarkCliInit(opts) {
|
|
3988
|
+
const configPath = opts.configPath ?? CONFIG_PATH;
|
|
3989
|
+
if (!isLarkPluginInstalled(configPath)) {
|
|
3990
|
+
console.error("lark-cli-init: skipping — openclaw-lark plugin not installed");
|
|
3991
|
+
return {
|
|
3992
|
+
ok: true,
|
|
3993
|
+
skipped: true,
|
|
3994
|
+
skipReason: "openclaw-lark plugin not installed"
|
|
3995
|
+
};
|
|
3996
|
+
}
|
|
3997
|
+
if (!isLarkCliAvailable$2()) {
|
|
3998
|
+
console.error("lark-cli-init: skipping — lark-cli command not found");
|
|
3999
|
+
return {
|
|
4000
|
+
ok: true,
|
|
4001
|
+
skipped: true,
|
|
4002
|
+
skipReason: "lark-cli command not found"
|
|
4003
|
+
};
|
|
4004
|
+
}
|
|
4005
|
+
const config = readConfig(configPath);
|
|
4006
|
+
if (!config) return {
|
|
4007
|
+
ok: false,
|
|
4008
|
+
error: `could not read config at ${configPath}`
|
|
4009
|
+
};
|
|
4010
|
+
const agentsMdPath = resolveAgentsMdPath(opts.appId, config);
|
|
4011
|
+
console.error(`lark-cli-init: resolved agents.md path=${agentsMdPath ?? "(null)"}`);
|
|
4012
|
+
if (!agentsMdPath) return {
|
|
4013
|
+
ok: false,
|
|
4014
|
+
error: `could not resolve agents.md path for appId=${opts.appId}`
|
|
4015
|
+
};
|
|
4016
|
+
const appSecret = resolveAppSecret(opts.appId, config, opts.feishuAppSecret);
|
|
4017
|
+
if (!appSecret) return {
|
|
4018
|
+
ok: false,
|
|
4019
|
+
error: `could not resolve appSecret for appId=${opts.appId}`
|
|
4020
|
+
};
|
|
4021
|
+
console.error(`lark-cli-init: running lark-cli config init --name ${opts.appId} --app-id ${opts.appId} --brand feishu --app-secret-stdin --force-init`);
|
|
4022
|
+
const initRes = (0, node_child_process.spawnSync)("lark-cli", [
|
|
4023
|
+
"config",
|
|
4024
|
+
"init",
|
|
4025
|
+
"--name",
|
|
4026
|
+
opts.appId,
|
|
4027
|
+
"--app-id",
|
|
4028
|
+
opts.appId,
|
|
4029
|
+
"--brand",
|
|
4030
|
+
"feishu",
|
|
4031
|
+
"--app-secret-stdin",
|
|
4032
|
+
"--force-init"
|
|
4033
|
+
], {
|
|
4034
|
+
stdio: [
|
|
4035
|
+
"pipe",
|
|
4036
|
+
"pipe",
|
|
4037
|
+
"pipe"
|
|
4038
|
+
],
|
|
4039
|
+
encoding: "utf-8",
|
|
4040
|
+
input: appSecret
|
|
4041
|
+
});
|
|
4042
|
+
const configInitStdout = initRes.stdout?.trim() || void 0;
|
|
4043
|
+
const configInitStderr = initRes.stderr?.trim() || void 0;
|
|
4044
|
+
if (configInitStdout) console.error(`lark-cli config init stdout: ${configInitStdout}`);
|
|
4045
|
+
if (configInitStderr) console.error(`lark-cli config init stderr: ${configInitStderr}`);
|
|
4046
|
+
if (initRes.error) return {
|
|
4047
|
+
ok: false,
|
|
4048
|
+
configInitStdout,
|
|
4049
|
+
configInitStderr,
|
|
4050
|
+
error: `lark-cli config init spawn error: ${initRes.error.message}`
|
|
4051
|
+
};
|
|
4052
|
+
if (initRes.status !== 0) return {
|
|
4053
|
+
ok: false,
|
|
4054
|
+
configInitExitCode: initRes.status ?? void 0,
|
|
4055
|
+
configInitStdout,
|
|
4056
|
+
configInitStderr,
|
|
4057
|
+
error: `lark-cli config init exited with code ${initRes.status}`
|
|
4058
|
+
};
|
|
4059
|
+
appendPeToAgentsMd(agentsMdPath);
|
|
4060
|
+
return {
|
|
4061
|
+
ok: true,
|
|
4062
|
+
configInitExitCode: 0,
|
|
4063
|
+
agentsMdPath
|
|
4064
|
+
};
|
|
4065
|
+
}
|
|
3815
4066
|
//#endregion
|
|
3816
|
-
//#region src/
|
|
3817
|
-
const PLUGIN_NAME$1 = "openclaw-lark";
|
|
3818
|
-
const LEGACY_SHORT_NAMES = ["feishu-openclaw-plugin"];
|
|
3819
|
-
const FORK_SCOPES = ["@lark-apaas"];
|
|
4067
|
+
//#region src/utils/feishu-tools-deny.ts
|
|
3820
4068
|
/**
|
|
3821
|
-
*
|
|
3822
|
-
*
|
|
3823
|
-
*
|
|
4069
|
+
* openclaw-lark 中与 lark-cli 功能重叠的工具,写入 channels.feishu.tools.deny 后
|
|
4070
|
+
* 由 openclaw-lark 插件自身的 shouldRegisterTool() 跳过注册,避免与 lark-cli 重复。
|
|
4071
|
+
* 支持尾部 `*` 通配(openclaw-lark matchesAnyPattern 识别)。
|
|
4072
|
+
*
|
|
4073
|
+
* 保留(IM / 鉴权 / 诊断类,与 lark-cli 无重叠):feishu_chat*、feishu_get_user、
|
|
4074
|
+
* feishu_search_user、feishu_im_*、feishu_oauth*、feishu_auth、feishu_diagnose、feishu_doctor。
|
|
4075
|
+
*
|
|
4076
|
+
* install-cli(安装 lark-cli 后即时写)与 agents_md_lark_cli_pe 规则(doctor --fix
|
|
4077
|
+
* 自愈)共用本常量与合并逻辑,单一来源。
|
|
3824
4078
|
*/
|
|
3825
|
-
const
|
|
3826
|
-
|
|
3827
|
-
|
|
3828
|
-
|
|
3829
|
-
|
|
4079
|
+
const LARK_CLI_OVERLAP_TOOL_DENY = Object.freeze([
|
|
4080
|
+
"feishu_create_doc",
|
|
4081
|
+
"feishu_fetch_doc",
|
|
4082
|
+
"feishu_update_doc",
|
|
4083
|
+
"feishu_doc_comments",
|
|
4084
|
+
"feishu_doc_media",
|
|
4085
|
+
"feishu_drive_file",
|
|
4086
|
+
"feishu_wiki_space",
|
|
4087
|
+
"feishu_wiki_space_node",
|
|
4088
|
+
"feishu_search_doc_wiki",
|
|
4089
|
+
"feishu_bitable_*",
|
|
4090
|
+
"feishu_calendar_*",
|
|
4091
|
+
"feishu_task_*",
|
|
4092
|
+
"feishu_sheet"
|
|
4093
|
+
]);
|
|
4094
|
+
/** 读取 channels.feishu.tools.deny 的字符串数组(缺失/非法时返回空数组)。 */
|
|
4095
|
+
function readDenyList(config) {
|
|
4096
|
+
const deny = asRecord(asRecord(asRecord(config.channels)?.feishu)?.tools)?.deny;
|
|
4097
|
+
return Array.isArray(deny) ? deny.filter((e) => typeof e === "string") : [];
|
|
3830
4098
|
}
|
|
3831
|
-
|
|
3832
|
-
function
|
|
3833
|
-
|
|
3834
|
-
return
|
|
4099
|
+
/** 返回 deny 列表中尚缺的重叠工具(用于 validate 判定)。 */
|
|
4100
|
+
function larkCliToolDenyMissing(config) {
|
|
4101
|
+
const existing = new Set(readDenyList(config));
|
|
4102
|
+
return LARK_CLI_OVERLAP_TOOL_DENY.filter((t) => !existing.has(t));
|
|
3835
4103
|
}
|
|
3836
4104
|
/**
|
|
3837
|
-
*
|
|
3838
|
-
*
|
|
3839
|
-
* - isVersionCompatible → null(兼容)
|
|
3840
|
-
* - isRecommendedBelowEntryMin → null(推荐版本未到位,豁免)
|
|
3841
|
-
* - ocCur < recommendedOc → 'openclaw'
|
|
3842
|
-
* - ocCur ≥ recommendedOc → 'lark'
|
|
4105
|
+
* 确保 channels.feishu.tools.deny 含全部重叠工具(只增不删,幂等合并)。
|
|
4106
|
+
* 直接 mutate 传入的 config 对象;返回是否发生了变更。
|
|
3843
4107
|
*/
|
|
3844
|
-
function
|
|
3845
|
-
if (
|
|
3846
|
-
|
|
3847
|
-
|
|
3848
|
-
|
|
3849
|
-
|
|
3850
|
-
function resolveCompatContext(ctx) {
|
|
3851
|
-
const recommendedOc = ctx.vars.recommendedOpenclawTag;
|
|
3852
|
-
const ocCur = getOcVersion();
|
|
3853
|
-
if (!ocCur) return null;
|
|
3854
|
-
const installed = getInstalledPlugin(ctx);
|
|
3855
|
-
if (installed == null) return null;
|
|
3856
|
-
return {
|
|
3857
|
-
ocCur,
|
|
3858
|
-
recommendedOc,
|
|
3859
|
-
installed,
|
|
3860
|
-
isLegacy: isLegacyPlugin(installed)
|
|
3861
|
-
};
|
|
4108
|
+
function ensureLarkCliToolDeny(config) {
|
|
4109
|
+
if (larkCliToolDenyMissing(config).length === 0) return false;
|
|
4110
|
+
const tools = ensureRecord(ensureRecord(ensureRecord(config, "channels"), "feishu"), "tools");
|
|
4111
|
+
const existing = readDenyList(config);
|
|
4112
|
+
tools.deny = [...new Set([...existing, ...LARK_CLI_OVERLAP_TOOL_DENY])];
|
|
4113
|
+
return true;
|
|
3862
4114
|
}
|
|
3863
4115
|
/**
|
|
3864
|
-
*
|
|
3865
|
-
*
|
|
3866
|
-
*
|
|
4116
|
+
* 文件级:读 openclaw.json → 合并 deny(ensureLarkCliToolDeny)→ 原子回写。
|
|
4117
|
+
* 仅当确实有变更时才写盘;config 不存在或解析失败仅记日志、不抛。
|
|
4118
|
+
*
|
|
4119
|
+
* 调用方负责门控「何时该写 deny」(即 lark-cli 存在时)——本函数不做 lark-cli 探测,
|
|
4120
|
+
* 只做「把 deny 合并进配置文件」这一件事,供 install-cli / install-extension /
|
|
4121
|
+
* upgrade-lark 在装好 lark-cli 或 openclaw-lark 后调用。
|
|
4122
|
+
*
|
|
4123
|
+
* @returns 是否发生了写盘
|
|
3867
4124
|
*/
|
|
3868
|
-
|
|
3869
|
-
|
|
3870
|
-
|
|
3871
|
-
|
|
3872
|
-
const { ocCur, recommendedOc, installed, isLegacy } = cc;
|
|
3873
|
-
if (isForkPlugin(installed)) return validateForkPlugin(installed, ocCur, recommendedOc);
|
|
3874
|
-
if (!recommendedOc) return { pass: true };
|
|
3875
|
-
if (resolveUpgradeDirection(installed, ocCur, recommendedOc, isLegacy) !== "openclaw") return { pass: true };
|
|
3876
|
-
return {
|
|
3877
|
-
pass: false,
|
|
3878
|
-
action: "upgrade_openclaw",
|
|
3879
|
-
message: `${buildCompatPrefix(installed, ocCur, isLegacy)};将 openclaw 升级到 ${recommendedOc},飞书插件会随之同步升级`
|
|
3880
|
-
};
|
|
4125
|
+
function ensureLarkCliToolDenyInFile(configPath, log = () => {}) {
|
|
4126
|
+
if (!node_fs.default.existsSync(configPath)) {
|
|
4127
|
+
log(`tools.deny: config not found at ${configPath} — skip`);
|
|
4128
|
+
return false;
|
|
3881
4129
|
}
|
|
3882
|
-
|
|
3883
|
-
|
|
3884
|
-
|
|
3885
|
-
|
|
3886
|
-
|
|
3887
|
-
repairMode: "user-confirm",
|
|
3888
|
-
level: "critical",
|
|
3889
|
-
usesVars: ["recommendedOpenclawTag"]
|
|
3890
|
-
})], FeishuPluginOpenclawUpgradeRule);
|
|
3891
|
-
/**
|
|
3892
|
-
* 检测是否需要升级飞书插件(仅在 Rule 1 pass 后执行):
|
|
3893
|
-
* - fork 版已由 Rule 1 处理,这里直接 pass
|
|
3894
|
-
* - legacy / 官方版不兼容且 ocCur ≥ recommendedOc → upgrade_lark
|
|
3895
|
-
*/
|
|
3896
|
-
let FeishuPluginLarkUpgradeRule = class FeishuPluginLarkUpgradeRule extends DiagnoseRule {
|
|
3897
|
-
validate(ctx) {
|
|
3898
|
-
const cc = resolveCompatContext(ctx);
|
|
3899
|
-
if (!cc) return { pass: true };
|
|
3900
|
-
const { ocCur, recommendedOc, installed, isLegacy } = cc;
|
|
3901
|
-
if (isForkPlugin(installed)) {
|
|
3902
|
-
if (installed.fullName !== "@lark-apaas/openclaw-lark") return { pass: true };
|
|
3903
|
-
if (resolveForkUpgradeDirection(ocCur) !== "lark") return { pass: true };
|
|
3904
|
-
return {
|
|
3905
|
-
pass: false,
|
|
3906
|
-
action: "upgrade_lark",
|
|
3907
|
-
message: `飞书插件 ${describePlugin(installed)}(fork 版,对标 openclaw-lark@${FORK_LARK_PLUGIN_PINNED_VERSION})与当前 openclaw@${ocCur} 不兼容;建议升级飞书插件至兼容版本`
|
|
3908
|
-
};
|
|
4130
|
+
try {
|
|
4131
|
+
const config = loadJSON5().parse(node_fs.default.readFileSync(configPath, "utf-8"));
|
|
4132
|
+
if (!ensureLarkCliToolDeny(config)) {
|
|
4133
|
+
log("tools.deny: already up to date");
|
|
4134
|
+
return false;
|
|
3909
4135
|
}
|
|
3910
|
-
|
|
3911
|
-
|
|
3912
|
-
|
|
3913
|
-
|
|
3914
|
-
|
|
3915
|
-
|
|
3916
|
-
};
|
|
3917
|
-
return
|
|
3918
|
-
pass: false,
|
|
3919
|
-
action: "upgrade_lark",
|
|
3920
|
-
message: `${prefix};当前 openclaw@${ocCur} 已达推荐版本,可直接升级飞书插件`
|
|
3921
|
-
};
|
|
4136
|
+
const tmp = configPath + ".lark-cli-deny-tmp";
|
|
4137
|
+
node_fs.default.writeFileSync(tmp, JSON.stringify(config, null, 2), "utf-8");
|
|
4138
|
+
moveSafe(tmp, configPath);
|
|
4139
|
+
log("channels.feishu.tools.deny updated");
|
|
4140
|
+
return true;
|
|
4141
|
+
} catch (e) {
|
|
4142
|
+
log(`WARN: ensure tools.deny failed: ${e.message}`);
|
|
4143
|
+
return false;
|
|
3922
4144
|
}
|
|
3923
|
-
};
|
|
3924
|
-
FeishuPluginLarkUpgradeRule = __decorate([Rule({
|
|
3925
|
-
key: "feishu_plugin_version_compat_lark",
|
|
3926
|
-
description: "检查飞书插件版本是否落后于当前 openclaw;是则提示升级飞书插件",
|
|
3927
|
-
dependsOn: ["config_syntax_check", "feishu_plugin_version_compat_openclaw"],
|
|
3928
|
-
repairMode: "user-confirm",
|
|
3929
|
-
level: "critical",
|
|
3930
|
-
usesVars: ["recommendedOpenclawTag"]
|
|
3931
|
-
})], FeishuPluginLarkUpgradeRule);
|
|
3932
|
-
/**
|
|
3933
|
-
* 核心判断:非 fork 插件是否需要升级 lark,基于当前 openclaw 版本的兼容性。
|
|
3934
|
-
*
|
|
3935
|
-
* 被 FeishuPluginLarkUpgradeRule.validate 和 needsLarkUpgrade 共用。
|
|
3936
|
-
* 调用方需在调用前自行处理 fork 插件的情况(fork 插件不走本函数)。
|
|
3937
|
-
*
|
|
3938
|
-
* - 有 recommendedOc:走 resolveUpgradeDirection 判断方向是否为 'lark'
|
|
3939
|
-
* - 无 recommendedOc(doctor 无推荐版本):legacy 插件直接需要升级;
|
|
3940
|
-
* 非 legacy 则检查当前版本是否在兼容表内
|
|
3941
|
-
*/
|
|
3942
|
-
function isLarkUpgradeNeededFromCC(cc) {
|
|
3943
|
-
const { ocCur, recommendedOc, installed, isLegacy } = cc;
|
|
3944
|
-
if (!recommendedOc) return isLegacy || !isVersionCompatible(installed, ocCur);
|
|
3945
|
-
return resolveUpgradeDirection(installed, ocCur, recommendedOc, isLegacy) === "lark";
|
|
3946
|
-
}
|
|
3947
|
-
function isForkPlugin(p) {
|
|
3948
|
-
return p.scope != null && FORK_SCOPES.includes(p.scope);
|
|
3949
|
-
}
|
|
3950
|
-
function isLegacyPlugin(p) {
|
|
3951
|
-
return LEGACY_SHORT_NAMES.includes(p.allowName);
|
|
3952
|
-
}
|
|
3953
|
-
/**
|
|
3954
|
-
* 检查已装插件版本与当前 openclaw 版本是否兼容。
|
|
3955
|
-
* 使用 effectiveCompatible() 以正确推断无显式 maxOpenclawVersion 条目的上界。
|
|
3956
|
-
*/
|
|
3957
|
-
function isVersionCompatible(p, ocCur) {
|
|
3958
|
-
if (!p.version) return false;
|
|
3959
|
-
return effectiveCompatible(p.version, ocCur);
|
|
3960
4145
|
}
|
|
4146
|
+
//#endregion
|
|
4147
|
+
//#region src/utils/lark-cli-detect.ts
|
|
3961
4148
|
/**
|
|
3962
|
-
*
|
|
3963
|
-
*
|
|
4149
|
+
* 探测 lark-cli 是否可用(`lark-cli --version` 退出码为 0)。
|
|
4150
|
+
* 多条规则共用同一判定:feishu_plugin_state_normalize(是否补 feishu_* 授权)、
|
|
4151
|
+
* agents_md_lark_cli_pe(是否补 PE / deny)等。
|
|
3964
4152
|
*/
|
|
3965
|
-
function
|
|
3966
|
-
|
|
3967
|
-
|
|
3968
|
-
|
|
3969
|
-
|
|
3970
|
-
|
|
3971
|
-
|
|
3972
|
-
|
|
3973
|
-
|
|
3974
|
-
|
|
3975
|
-
}
|
|
3976
|
-
|
|
3977
|
-
|
|
3978
|
-
|
|
3979
|
-
return `该插件版本要求 openclaw ≥ ${entry.minOpenclawVersion}`;
|
|
4153
|
+
function isLarkCliAvailable$1() {
|
|
4154
|
+
try {
|
|
4155
|
+
return (0, node_child_process.spawnSync)("lark-cli", ["--version"], {
|
|
4156
|
+
encoding: "utf-8",
|
|
4157
|
+
timeout: 5e3,
|
|
4158
|
+
stdio: [
|
|
4159
|
+
"ignore",
|
|
4160
|
+
"pipe",
|
|
4161
|
+
"ignore"
|
|
4162
|
+
]
|
|
4163
|
+
}).status === 0;
|
|
4164
|
+
} catch {
|
|
4165
|
+
return false;
|
|
4166
|
+
}
|
|
3980
4167
|
}
|
|
4168
|
+
//#endregion
|
|
4169
|
+
//#region src/rules/agents-md-lark-cli-pe.ts
|
|
4170
|
+
/** 会注册 feishu_* 工具的三种飞书插件:官方 / 旧版 / 内置。 */
|
|
4171
|
+
const FEISHU_PLUGIN_NAMES = [
|
|
4172
|
+
LARK_PLUGIN_NAME,
|
|
4173
|
+
LEGACY_LARK_PLUGIN_NAME,
|
|
4174
|
+
BUILTIN_FEISHU_PLUGIN_NAME
|
|
4175
|
+
];
|
|
3981
4176
|
/**
|
|
3982
|
-
*
|
|
3983
|
-
*
|
|
3984
|
-
*
|
|
3985
|
-
* 'openclaw' → oc 低于区间下界,需升级 openclaw
|
|
3986
|
-
* 'lark' → oc 高于区间上界(插件相对当前 openclaw 过旧),需升级飞书插件
|
|
4177
|
+
* 任一飞书插件是否在 config 中**启用**(plugins.entries[name].enabled === true)。
|
|
4178
|
+
* deny 的意义在于「有飞书插件会注册 feishu_* 工具」,故按启用判定——不看是否落盘安装:
|
|
4179
|
+
* 启用的可能是官方 openclaw-lark、旧版 feishu-openclaw-plugin 或内置 feishu 中的任意一个。
|
|
3987
4180
|
*/
|
|
3988
|
-
function
|
|
3989
|
-
|
|
3990
|
-
return
|
|
4181
|
+
function isAnyFeishuPluginEnabled(config) {
|
|
4182
|
+
const entries = getNestedMap(config, "plugins", "entries");
|
|
4183
|
+
return FEISHU_PLUGIN_NAMES.some((name) => asRecord(entries?.[name])?.enabled === true);
|
|
4184
|
+
}
|
|
4185
|
+
/** 第一个缺失 PE 内容的 AGENTS.md 路径;无则 undefined。 */
|
|
4186
|
+
function findAgentsMdMissingPe(ctx) {
|
|
4187
|
+
return collectExistingAgentsMdPaths(ctx).find((filePath) => {
|
|
4188
|
+
return !node_fs.default.readFileSync(filePath, "utf-8").includes(`<${PE_XML_TAG}>`);
|
|
4189
|
+
});
|
|
3991
4190
|
}
|
|
3992
4191
|
/**
|
|
3993
|
-
*
|
|
3994
|
-
*
|
|
3995
|
-
*
|
|
3996
|
-
*
|
|
4192
|
+
* lark-cli 存在时统一规范化环境:
|
|
4193
|
+
* 1. 给各智能体 AGENTS.md 追加 lark-cli-pe PE 内容
|
|
4194
|
+
* 2. 有飞书插件启用时,把与 lark-cli 重叠的工具写入 channels.feishu.tools.deny
|
|
4195
|
+
* (只增不删,避免与 lark-cli 重复注册)
|
|
4196
|
+
* 两者门禁同为「lark-cli 可用」,故合并为一条规则。
|
|
3997
4197
|
*/
|
|
3998
|
-
|
|
3999
|
-
|
|
4000
|
-
|
|
4001
|
-
|
|
4002
|
-
|
|
4003
|
-
|
|
4004
|
-
|
|
4005
|
-
message: `飞书插件 ${describePlugin(installed)}(fork 版)要求 openclaw ≥ ${FORK_LARK_PLUGIN_MIN_OC_VERSION},当前 openclaw@${ocCur} 低于此要求${recommendation}`
|
|
4006
|
-
};
|
|
4007
|
-
}
|
|
4008
|
-
function describePlugin(p) {
|
|
4009
|
-
return (p.fullName ?? p.allowName) + (p.version ? `@${p.version}` : "");
|
|
4010
|
-
}
|
|
4011
|
-
function readPluginPackageJson(filePath) {
|
|
4012
|
-
try {
|
|
4013
|
-
if (!node_fs.default.existsSync(filePath)) return null;
|
|
4014
|
-
const raw = node_fs.default.readFileSync(filePath, "utf-8");
|
|
4015
|
-
const parsed = JSON.parse(raw);
|
|
4016
|
-
return {
|
|
4017
|
-
name: typeof parsed.name === "string" ? parsed.name : void 0,
|
|
4018
|
-
version: typeof parsed.version === "string" ? parsed.version : void 0
|
|
4198
|
+
let AgentsMdLarkCliPeRule = class AgentsMdLarkCliPeRule extends DiagnoseRule {
|
|
4199
|
+
validate(ctx) {
|
|
4200
|
+
if (!isLarkCliAvailable$1()) return { pass: true };
|
|
4201
|
+
const missingPath = findAgentsMdMissingPe(ctx);
|
|
4202
|
+
if (missingPath) return {
|
|
4203
|
+
pass: false,
|
|
4204
|
+
message: `${missingPath} 中缺少 lark-cli-pe PE 内容,需要追加`
|
|
4019
4205
|
};
|
|
4020
|
-
|
|
4021
|
-
|
|
4022
|
-
|
|
4023
|
-
}
|
|
4024
|
-
/** "已装" = plugins.allow 含名 AND extensions/<name>/package.json 真实存在。 */
|
|
4025
|
-
function detectInstalledPlugin(ctx) {
|
|
4026
|
-
const allowRaw = asRecord(ctx.config.plugins)?.allow;
|
|
4027
|
-
const allow = Array.isArray(allowRaw) ? allowRaw.filter((e) => typeof e === "string") : [];
|
|
4028
|
-
const extDir = getExtensionsDir(ctx.configPath);
|
|
4029
|
-
const installs = getNestedMap(ctx.config, "plugins", "installs");
|
|
4030
|
-
for (const name of [PLUGIN_NAME$1, ...LEGACY_SHORT_NAMES]) {
|
|
4031
|
-
if (!allow.includes(name)) continue;
|
|
4032
|
-
const pkgPath = node_path.default.join(extDir, name, "package.json");
|
|
4033
|
-
if (!node_fs.default.existsSync(pkgPath)) continue;
|
|
4034
|
-
const pkg = readPluginPackageJson(pkgPath) ?? {};
|
|
4035
|
-
const installEntry = installs && asRecord(installs[name]);
|
|
4036
|
-
const fullName = pkg.name ?? extractScopedNameFromSpec$1(installEntry?.spec);
|
|
4037
|
-
return {
|
|
4038
|
-
allowName: name,
|
|
4039
|
-
fullName,
|
|
4040
|
-
scope: fullName?.startsWith("@") ? fullName.split("/")[0] : void 0,
|
|
4041
|
-
version: pkg.version ?? (typeof installEntry?.version === "string" ? installEntry.version : void 0)
|
|
4206
|
+
if (isAnyFeishuPluginEnabled(ctx.config) && larkCliToolDenyMissing(ctx.config).length > 0) return {
|
|
4207
|
+
pass: false,
|
|
4208
|
+
message: "channels.feishu.tools.deny 缺少与 lark-cli 重叠的飞书工具,需要补充"
|
|
4042
4209
|
};
|
|
4210
|
+
return { pass: true };
|
|
4043
4211
|
}
|
|
4044
|
-
|
|
4045
|
-
|
|
4046
|
-
|
|
4047
|
-
|
|
4048
|
-
|
|
4049
|
-
|
|
4050
|
-
|
|
4051
|
-
}
|
|
4052
|
-
/**
|
|
4053
|
-
* 判断已安装的飞书插件是否与当前 openclaw 版本不兼容(或为需要替换的 legacy 插件)。
|
|
4054
|
-
* 被 upgrade-lark 前置检测门控(--check-only 和正式安装模式)调用。
|
|
4055
|
-
*/
|
|
4056
|
-
function needsLarkUpgrade(ctx) {
|
|
4057
|
-
const cc = resolveCompatContext({
|
|
4058
|
-
...ctx,
|
|
4059
|
-
vars: {
|
|
4060
|
-
...ctx.vars,
|
|
4061
|
-
recommendedOpenclawTag: void 0
|
|
4212
|
+
repair(ctx) {
|
|
4213
|
+
if (!isLarkCliAvailable$1()) return;
|
|
4214
|
+
for (const filePath of collectExistingAgentsMdPaths(ctx)) {
|
|
4215
|
+
const content = node_fs.default.readFileSync(filePath, "utf-8");
|
|
4216
|
+
if (content.includes(`<lark-cli-pe>`)) continue;
|
|
4217
|
+
const sep = content.length > 0 && !content.endsWith("\n") ? "\n" : "";
|
|
4218
|
+
node_fs.default.appendFileSync(filePath, `${sep}${PE_PLACEHOLDER}`, "utf-8");
|
|
4219
|
+
console.error(`agents-md-lark-cli-pe: appended PE to ${filePath}`);
|
|
4062
4220
|
}
|
|
4063
|
-
|
|
4064
|
-
if (!cc) return false;
|
|
4065
|
-
const { ocCur, installed } = cc;
|
|
4066
|
-
if (isForkPlugin(installed)) {
|
|
4067
|
-
if (installed.fullName === "@lark-apaas/openclaw-lark") return !effectiveCompatible(FORK_LARK_PLUGIN_PINNED_VERSION, ocCur);
|
|
4068
|
-
return false;
|
|
4221
|
+
if (isAnyFeishuPluginEnabled(ctx.config) && ensureLarkCliToolDeny(ctx.config)) console.error("agents-md-lark-cli-pe: merged lark-cli overlap tools into channels.feishu.tools.deny");
|
|
4069
4222
|
}
|
|
4070
|
-
|
|
4071
|
-
|
|
4223
|
+
};
|
|
4224
|
+
AgentsMdLarkCliPeRule = __decorate([Rule({
|
|
4225
|
+
key: "agents_md_lark_cli_pe",
|
|
4226
|
+
description: "lark-cli 存在时规范化环境:AGENTS.md 追加 lark-cli-pe PE + 飞书插件重叠工具写入 channels.feishu.tools.deny",
|
|
4227
|
+
dependsOn: ["config_syntax_check"],
|
|
4228
|
+
repairMode: "standard",
|
|
4229
|
+
level: "silent"
|
|
4230
|
+
})], AgentsMdLarkCliPeRule);
|
|
4072
4231
|
//#endregion
|
|
4073
4232
|
//#region src/rules/cleanup-install-backup-dirs.ts
|
|
4074
4233
|
const DIR_PREFIX = ".openclaw-install-";
|
|
@@ -4120,12 +4279,10 @@ CleanupInstallBackupDirsRule = __decorate([Rule({
|
|
|
4120
4279
|
})], CleanupInstallBackupDirsRule);
|
|
4121
4280
|
//#endregion
|
|
4122
4281
|
//#region src/rules/lark-cli-missing-for-installed-lark-plugin.ts
|
|
4123
|
-
|
|
4124
|
-
const FORK_PACKAGE_NAME = "@lark-apaas/openclaw-lark";
|
|
4282
|
+
/** 触发本规则自动安装 lark-cli 的特定 fork 版本(与对标的官方兼容版本无关)。 */
|
|
4125
4283
|
const TARGET_VERSION = "2026.4.4";
|
|
4126
|
-
const LARK_CLI_NAME$1 = "lark-cli";
|
|
4127
4284
|
function readInstalledLarkPlugin(ctx) {
|
|
4128
|
-
const pkgPath = node_path.default.join(getExtensionsDir(ctx.configPath),
|
|
4285
|
+
const pkgPath = node_path.default.join(getExtensionsDir(ctx.configPath), LARK_PLUGIN_NAME, "package.json");
|
|
4129
4286
|
if (!node_fs.default.existsSync(pkgPath)) return null;
|
|
4130
4287
|
let pkg = {};
|
|
4131
4288
|
try {
|
|
@@ -4138,14 +4295,14 @@ function readInstalledLarkPlugin(ctx) {
|
|
|
4138
4295
|
pkg = {};
|
|
4139
4296
|
}
|
|
4140
4297
|
const installs = getNestedMap(ctx.config, "plugins", "installs");
|
|
4141
|
-
const installEntry = installs ? asRecord(installs[
|
|
4298
|
+
const installEntry = installs ? asRecord(installs[LARK_PLUGIN_NAME]) : void 0;
|
|
4142
4299
|
return {
|
|
4143
4300
|
name: pkg.name ?? extractScopedNameFromSpec(installEntry?.spec),
|
|
4144
4301
|
version: pkg.version ?? (typeof installEntry?.version === "string" ? installEntry.version : void 0)
|
|
4145
4302
|
};
|
|
4146
4303
|
}
|
|
4147
4304
|
function isTargetForkPlugin(plugin) {
|
|
4148
|
-
return plugin?.name ===
|
|
4305
|
+
return plugin?.name === "@lark-apaas/openclaw-lark" && plugin.version === TARGET_VERSION;
|
|
4149
4306
|
}
|
|
4150
4307
|
function extractScopedNameFromSpec(spec) {
|
|
4151
4308
|
if (typeof spec !== "string") return void 0;
|
|
@@ -4154,7 +4311,7 @@ function extractScopedNameFromSpec(spec) {
|
|
|
4154
4311
|
}
|
|
4155
4312
|
function isLarkCliAvailable() {
|
|
4156
4313
|
try {
|
|
4157
|
-
return (0, node_child_process.spawnSync)(LARK_CLI_NAME
|
|
4314
|
+
return (0, node_child_process.spawnSync)(LARK_CLI_NAME, ["--version"], {
|
|
4158
4315
|
encoding: "utf-8",
|
|
4159
4316
|
timeout: 5e3,
|
|
4160
4317
|
stdio: [
|
|
@@ -4196,7 +4353,7 @@ let LarkCliMissingForInstalledLarkPluginRule = class LarkCliMissingForInstalledL
|
|
|
4196
4353
|
if (isLarkCliAvailable()) return { pass: true };
|
|
4197
4354
|
return {
|
|
4198
4355
|
pass: false,
|
|
4199
|
-
message: `${
|
|
4356
|
+
message: `${FORK_LARK_PLUGIN_FULL_NAME}@${TARGET_VERSION} 已安装,但 lark-cli 不可用;将执行一次 lark-cli 安装`
|
|
4200
4357
|
};
|
|
4201
4358
|
}
|
|
4202
4359
|
repair(ctx) {
|
|
@@ -5210,7 +5367,7 @@ async function installOpenclaw(openclawTag, ossFileMap, opts = {}) {
|
|
|
5210
5367
|
console.error(`[install-openclaw] done in ${Date.now() - t0}ms`);
|
|
5211
5368
|
}
|
|
5212
5369
|
//#endregion
|
|
5213
|
-
//#region src/lark-tools-update.ts
|
|
5370
|
+
//#region src/utils/lark-tools-update.ts
|
|
5214
5371
|
/**
|
|
5215
5372
|
* 共享的飞书插件备份 / 恢复 / `openclaw-lark-tools update` 调用。
|
|
5216
5373
|
*
|
|
@@ -5221,8 +5378,6 @@ async function installOpenclaw(openclawTag, ossFileMap, opts = {}) {
|
|
|
5221
5378
|
* `console.error`,独立调用方不传则静默。
|
|
5222
5379
|
*/
|
|
5223
5380
|
const NOOP_LOG = () => {};
|
|
5224
|
-
/** 升级前需备份的 extensions/ 下的插件目录(含 legacy) */
|
|
5225
|
-
const FEISHU_PLUGIN_DIRS = ["openclaw-lark", "feishu-openclaw-plugin"];
|
|
5226
5381
|
/** 读取 package.json 的 version 字段,失败返回 null(仅用于日志) */
|
|
5227
5382
|
function readPkgVersion(pkgPath) {
|
|
5228
5383
|
try {
|
|
@@ -5233,13 +5388,20 @@ function readPkgVersion(pkgPath) {
|
|
|
5233
5388
|
}
|
|
5234
5389
|
}
|
|
5235
5390
|
/**
|
|
5236
|
-
* 备份 openclaw.json +
|
|
5391
|
+
* 备份 openclaw.json + LARK_PLUGIN_DIR_NAMES 下存在的插件目录到 backupDir。
|
|
5237
5392
|
* 只备份当前存在的文件;不存在的记日志后跳过(恢复时据此判断「升级前是否存在」)。
|
|
5238
5393
|
*/
|
|
5239
5394
|
function backupFeishuPlugins(opts) {
|
|
5240
5395
|
const { workspaceDir, configPath, backupDir } = opts;
|
|
5241
5396
|
const log = opts.log ?? NOOP_LOG;
|
|
5242
5397
|
try {
|
|
5398
|
+
if (node_fs.default.existsSync(backupDir)) {
|
|
5399
|
+
node_fs.default.rmSync(backupDir, {
|
|
5400
|
+
recursive: true,
|
|
5401
|
+
force: true
|
|
5402
|
+
});
|
|
5403
|
+
log(`cleaned stale backup dir: ${backupDir}`);
|
|
5404
|
+
}
|
|
5243
5405
|
node_fs.default.mkdirSync(backupDir, { recursive: true });
|
|
5244
5406
|
log(`backup dir: ${backupDir}`);
|
|
5245
5407
|
if (node_fs.default.existsSync(configPath)) {
|
|
@@ -5249,7 +5411,7 @@ function backupFeishuPlugins(opts) {
|
|
|
5249
5411
|
} else log(` skipped: openclaw.json (not found)`);
|
|
5250
5412
|
node_fs.default.mkdirSync(node_path.default.join(backupDir, "extensions"), { recursive: true });
|
|
5251
5413
|
const extSrc = node_path.default.join(workspaceDir, "extensions");
|
|
5252
|
-
for (const pluginDir of
|
|
5414
|
+
for (const pluginDir of LARK_PLUGIN_DIR_NAMES) {
|
|
5253
5415
|
const src = node_path.default.join(extSrc, pluginDir);
|
|
5254
5416
|
if (node_fs.default.existsSync(src)) {
|
|
5255
5417
|
const dst = node_path.default.join(backupDir, "extensions", pluginDir);
|
|
@@ -5281,7 +5443,7 @@ function restoreFeishuPlugins(opts) {
|
|
|
5281
5443
|
log(` deleted: openclaw.json`);
|
|
5282
5444
|
}
|
|
5283
5445
|
const extDst = node_path.default.join(workspaceDir, "extensions");
|
|
5284
|
-
for (const pluginDir of
|
|
5446
|
+
for (const pluginDir of LARK_PLUGIN_DIR_NAMES) {
|
|
5285
5447
|
const dst = node_path.default.join(extDst, pluginDir);
|
|
5286
5448
|
if (node_fs.default.existsSync(dst)) {
|
|
5287
5449
|
node_fs.default.rmSync(dst, {
|
|
@@ -5296,7 +5458,7 @@ function restoreFeishuPlugins(opts) {
|
|
|
5296
5458
|
node_fs.default.copyFileSync(configBackup, configPath);
|
|
5297
5459
|
log(` restored: openclaw.json`);
|
|
5298
5460
|
} else log(` skipped restore: openclaw.json (not in backup — was not present before upgrade)`);
|
|
5299
|
-
for (const pluginDir of
|
|
5461
|
+
for (const pluginDir of LARK_PLUGIN_DIR_NAMES) {
|
|
5300
5462
|
const backupSrc = node_path.default.join(backupDir, "extensions", pluginDir);
|
|
5301
5463
|
if (node_fs.default.existsSync(backupSrc)) {
|
|
5302
5464
|
node_fs.default.cpSync(backupSrc, node_path.default.join(extDst, pluginDir), { recursive: true });
|
|
@@ -5338,10 +5500,11 @@ function runLarkToolsUpdate(opts) {
|
|
|
5338
5500
|
return {
|
|
5339
5501
|
exitCode,
|
|
5340
5502
|
stdout,
|
|
5341
|
-
stderr
|
|
5342
|
-
spawnError: r.error?.message
|
|
5503
|
+
stderr
|
|
5343
5504
|
};
|
|
5344
5505
|
}
|
|
5506
|
+
//#endregion
|
|
5507
|
+
//#region src/install-extension.ts
|
|
5345
5508
|
async function installExtension(tag, ossFileMap, opts = {}) {
|
|
5346
5509
|
const homeBase = resolveHomeBase(opts.homeBase);
|
|
5347
5510
|
const hasAll = !!opts.all;
|
|
@@ -5364,18 +5527,18 @@ async function installExtension(tag, ossFileMap, opts = {}) {
|
|
|
5364
5527
|
}
|
|
5365
5528
|
console.error(`[install-extension] tag=${tag} targets=${targets.length}`);
|
|
5366
5529
|
const t0 = Date.now();
|
|
5367
|
-
const larkTarget = opts.skipConfigUpdate ? void 0 : targets.find((p) => p.name === LARK_PLUGIN_NAME
|
|
5530
|
+
const larkTarget = opts.skipConfigUpdate ? void 0 : targets.find((p) => p.name === LARK_PLUGIN_NAME);
|
|
5368
5531
|
if (larkTarget) {
|
|
5369
5532
|
if (await installLarkPluginWithCompatCheck({
|
|
5370
5533
|
tag,
|
|
5371
5534
|
pkg: larkTarget,
|
|
5372
5535
|
homeBase,
|
|
5373
|
-
configPath: opts.configPath ?? node_path.default.join(homeBase,
|
|
5536
|
+
configPath: opts.configPath ?? node_path.default.join(homeBase, DEFAULT_CONFIG_REL),
|
|
5374
5537
|
workspaceDir: node_path.default.join(homeBase, "workspace", "agent"),
|
|
5375
5538
|
ossFileMap,
|
|
5376
5539
|
downloadOpts: opts
|
|
5377
5540
|
})) {
|
|
5378
|
-
targets = targets.filter((p) => p.name !== LARK_PLUGIN_NAME
|
|
5541
|
+
targets = targets.filter((p) => p.name !== LARK_PLUGIN_NAME);
|
|
5379
5542
|
if (targets.length === 0) {
|
|
5380
5543
|
console.error(`[install-extension] done in ${Date.now() - t0}ms`);
|
|
5381
5544
|
return;
|
|
@@ -5394,13 +5557,14 @@ async function installExtension(tag, ossFileMap, opts = {}) {
|
|
|
5394
5557
|
installOne$1(pkg, tarball, homeBase);
|
|
5395
5558
|
console.error(`[install-extension] ${pkg.name}: installed`);
|
|
5396
5559
|
}
|
|
5397
|
-
if (!opts.skipConfigUpdate)
|
|
5398
|
-
|
|
5560
|
+
if (!opts.skipConfigUpdate) {
|
|
5561
|
+
const configPath = opts.configPath ?? node_path.default.join(homeBase, DEFAULT_CONFIG_REL);
|
|
5562
|
+
updatePluginInstalls(configPath, targets);
|
|
5563
|
+
if (targets.some((p) => p.name === "openclaw-lark") && isLarkCliAvailable$1()) ensureLarkCliToolDenyInFile(configPath, (msg) => console.error(`[install-extension] ${msg}`));
|
|
5564
|
+
} else console.error(`[install-extension] skipConfigUpdate=true — not touching openclaw.json`);
|
|
5399
5565
|
console.error(`[install-extension] done ${targets.length}/${targets.length} in ${Date.now() - t0}ms`);
|
|
5400
5566
|
}
|
|
5401
|
-
const
|
|
5402
|
-
const MEM0_PLUGIN_NAME = "openclaw-mem0-plugin";
|
|
5403
|
-
const PLUGINS_TO_AUTO_ENABLE = ["openclaw-lark", "openclaw-extension-miaoda"];
|
|
5567
|
+
const PLUGINS_TO_AUTO_ENABLE = [LARK_PLUGIN_NAME, MIAODA_PLUGIN_NAME];
|
|
5404
5568
|
/**
|
|
5405
5569
|
* Merge each installed extension's installMetadata into openclaw.json's
|
|
5406
5570
|
* plugins.installs[<pkg.name>]. Atomic write via tmp + rename.
|
|
@@ -5432,14 +5596,14 @@ function updatePluginInstalls(configPath, installedPkgs) {
|
|
|
5432
5596
|
installs[pkg.name] = pkg.installMetadata;
|
|
5433
5597
|
updated++;
|
|
5434
5598
|
} else skipped++;
|
|
5435
|
-
if (installedPkgs.some((p) => p.name ===
|
|
5599
|
+
if (installedPkgs.some((p) => p.name === "openclaw-mem0-plugin")) {
|
|
5436
5600
|
const allowRaw = plugins.allow;
|
|
5437
5601
|
const allow = Array.isArray(allowRaw) ? allowRaw : [];
|
|
5438
|
-
if (!allow.filter((e) => typeof e === "string").includes(
|
|
5602
|
+
if (!allow.filter((e) => typeof e === "string").includes("openclaw-mem0-plugin")) allow.push(MEM0_PLUGIN_NAME);
|
|
5439
5603
|
plugins.allow = allow;
|
|
5440
5604
|
if (!plugins.entries || typeof plugins.entries !== "object" || Array.isArray(plugins.entries)) plugins.entries = {};
|
|
5441
5605
|
const entries = plugins.entries;
|
|
5442
|
-
if (!(
|
|
5606
|
+
if (!("openclaw-mem0-plugin" in entries)) entries[MEM0_PLUGIN_NAME] = { enabled: false };
|
|
5443
5607
|
}
|
|
5444
5608
|
for (const pkg of installedPkgs) {
|
|
5445
5609
|
if (!PLUGINS_TO_AUTO_ENABLE.includes(pkg.name)) continue;
|
|
@@ -5506,9 +5670,9 @@ async function installLarkPluginWithCompatCheck(opts) {
|
|
|
5506
5670
|
console.error("[install-extension] WARN: cannot read openclaw version — falling back to direct tarball install for openclaw-lark");
|
|
5507
5671
|
return false;
|
|
5508
5672
|
}
|
|
5509
|
-
const
|
|
5510
|
-
const compatible =
|
|
5511
|
-
console.error(`[install-extension] ${pkg.packageName ?? pkg.name}@${pkg.version ?? "unknown"} (compat-as ${
|
|
5673
|
+
const compatResult = checkPluginCompat(pkg.packageName, pkg.version, ocCur);
|
|
5674
|
+
const compatible = compatResult.resolvedVersion ? compatResult.compatible : true;
|
|
5675
|
+
console.error(`[install-extension] ${pkg.packageName ?? pkg.name}@${pkg.version ?? "unknown"} (compat-as ${compatResult.resolvedVersion ?? "unknown"}) vs openclaw@${ocCur}: ${compatible}`);
|
|
5512
5676
|
if (compatible) return false;
|
|
5513
5677
|
console.error(`[install-extension] openclaw-lark@${pkg.version} incompatible with openclaw@${ocCur} — using openclaw-lark-tools update`);
|
|
5514
5678
|
const log = (msg) => console.error(`[install-extension] ${msg}`);
|
|
@@ -5549,6 +5713,7 @@ async function installLarkPluginWithCompatCheck(opts) {
|
|
|
5549
5713
|
});
|
|
5550
5714
|
throw new Error(`openclaw-lark post-install validation failed: ${validation.error}`);
|
|
5551
5715
|
}
|
|
5716
|
+
ensureLarkCliToolDenyInFile(configPath, (msg) => console.error(`[install-extension] ${msg}`));
|
|
5552
5717
|
console.error("[install-extension] openclaw-lark-tools update succeeded");
|
|
5553
5718
|
return true;
|
|
5554
5719
|
} finally {
|
|
@@ -5557,7 +5722,9 @@ async function installLarkPluginWithCompatCheck(opts) {
|
|
|
5557
5722
|
recursive: true,
|
|
5558
5723
|
force: true
|
|
5559
5724
|
});
|
|
5560
|
-
} catch {
|
|
5725
|
+
} catch (e) {
|
|
5726
|
+
console.error(`[install-extension] WARN: failed to clean up backup dir ${backupDir}: ${e.message}`);
|
|
5727
|
+
}
|
|
5561
5728
|
}
|
|
5562
5729
|
}
|
|
5563
5730
|
/**
|
|
@@ -5566,7 +5733,7 @@ async function installLarkPluginWithCompatCheck(opts) {
|
|
|
5566
5733
|
*/
|
|
5567
5734
|
function validateLarkPluginInstall(opts) {
|
|
5568
5735
|
const { homeBase, configPath } = opts;
|
|
5569
|
-
const pkgJson = node_path.default.join(homeBase, "workspace", "agent", "extensions", LARK_PLUGIN_NAME
|
|
5736
|
+
const pkgJson = node_path.default.join(homeBase, "workspace", "agent", "extensions", LARK_PLUGIN_NAME, "package.json");
|
|
5570
5737
|
if (!node_fs.default.existsSync(pkgJson)) return {
|
|
5571
5738
|
ok: false,
|
|
5572
5739
|
error: `plugin directory missing: ${pkgJson}`
|
|
@@ -5576,9 +5743,9 @@ function validateLarkPluginInstall(opts) {
|
|
|
5576
5743
|
error: `openclaw.json not found at ${configPath}`
|
|
5577
5744
|
};
|
|
5578
5745
|
try {
|
|
5579
|
-
if (asRecord(getNestedMap(loadJSON5().parse(node_fs.default.readFileSync(configPath, "utf-8")), "plugins", "entries")?.[
|
|
5746
|
+
if (asRecord(getNestedMap(loadJSON5().parse(node_fs.default.readFileSync(configPath, "utf-8")), "plugins", "entries")?.["openclaw-lark"])?.enabled !== true) return {
|
|
5580
5747
|
ok: false,
|
|
5581
|
-
error: `plugins.entries["${LARK_PLUGIN_NAME
|
|
5748
|
+
error: `plugins.entries["${LARK_PLUGIN_NAME}"].enabled is not true`
|
|
5582
5749
|
};
|
|
5583
5750
|
} catch (e) {
|
|
5584
5751
|
return {
|
|
@@ -6868,34 +7035,6 @@ function reportError(params) {
|
|
|
6868
7035
|
}
|
|
6869
7036
|
//#endregion
|
|
6870
7037
|
//#region src/install-cli.ts
|
|
6871
|
-
const LARK_CLI_NAME = "lark-cli";
|
|
6872
|
-
const AGENT_SKILLS_NAME = "agent-skills";
|
|
6873
|
-
const WORKSPACE_AGENT_REL = "workspace/agent";
|
|
6874
|
-
const LARK_PLUGIN_NAME = "openclaw-lark";
|
|
6875
|
-
/**
|
|
6876
|
-
* openclaw-lark tools that overlap with lark-cli functionality.
|
|
6877
|
-
* Written to channels.feishu.tools.deny after lark-cli is installed so that
|
|
6878
|
-
* openclaw-lark's shouldRegisterTool() skips them, avoiding duplicate tools.
|
|
6879
|
-
* Supports trailing-* wildcards (handled by openclaw-lark's matchesAnyPattern).
|
|
6880
|
-
*
|
|
6881
|
-
* Kept: feishu_chat*, feishu_get_user, feishu_search_user, feishu_im_*,
|
|
6882
|
-
* feishu_oauth*, feishu_auth, feishu_diagnose, feishu_doctor
|
|
6883
|
-
*/
|
|
6884
|
-
const LARK_CLI_OVERLAP_TOOL_DENY = Object.freeze([
|
|
6885
|
-
"feishu_create_doc",
|
|
6886
|
-
"feishu_fetch_doc",
|
|
6887
|
-
"feishu_update_doc",
|
|
6888
|
-
"feishu_doc_comments",
|
|
6889
|
-
"feishu_doc_media",
|
|
6890
|
-
"feishu_drive_file",
|
|
6891
|
-
"feishu_wiki_space",
|
|
6892
|
-
"feishu_wiki_space_node",
|
|
6893
|
-
"feishu_search_doc_wiki",
|
|
6894
|
-
"feishu_bitable_*",
|
|
6895
|
-
"feishu_calendar_*",
|
|
6896
|
-
"feishu_task_*",
|
|
6897
|
-
"feishu_sheet"
|
|
6898
|
-
]);
|
|
6899
7038
|
async function installClis(tag, ossFileMap, opts) {
|
|
6900
7039
|
const homeBase = resolveHomeBase(opts.homeBase);
|
|
6901
7040
|
if (opts.names.length === 0) throw new Error("install-cli: must provide at least one --cli=<name>");
|
|
@@ -6922,7 +7061,15 @@ async function installClis(tag, ossFileMap, opts) {
|
|
|
6922
7061
|
}
|
|
6923
7062
|
console.error(`[install-cli] tag=${tag} targets=${targets.length}`);
|
|
6924
7063
|
const t0 = Date.now();
|
|
7064
|
+
const larkCliAlreadyAvailable = targets.some((p) => p.name === "lark-cli") && isLarkCliAvailable$1();
|
|
6925
7065
|
const tarballs = await Promise.all(targets.map(async (p) => {
|
|
7066
|
+
if (p.name === "lark-cli" && larkCliAlreadyAvailable) {
|
|
7067
|
+
console.error(`[install-cli] ${p.name}: already available (lark-cli --version) — skip download/extract/link`);
|
|
7068
|
+
return {
|
|
7069
|
+
pkg: p,
|
|
7070
|
+
tarball: null
|
|
7071
|
+
};
|
|
7072
|
+
}
|
|
6926
7073
|
const tb = await downloadWithCache(p, ossFileMap, opts);
|
|
6927
7074
|
console.error(`[install-cli] ${p.name}: downloaded`);
|
|
6928
7075
|
return {
|
|
@@ -6931,11 +7078,12 @@ async function installClis(tag, ossFileMap, opts) {
|
|
|
6931
7078
|
};
|
|
6932
7079
|
}));
|
|
6933
7080
|
for (const { pkg, tarball } of tarballs) {
|
|
7081
|
+
if (tarball === null) continue;
|
|
6934
7082
|
installOne(pkg, tarball, homeBase, opts.tmpRoot);
|
|
6935
7083
|
linkBins(pkg, homeBase);
|
|
6936
7084
|
console.error(`[install-cli] ${pkg.name}: installed`);
|
|
6937
7085
|
}
|
|
6938
|
-
if (targets.some((p) => p.name ===
|
|
7086
|
+
if (targets.some((p) => p.name === "lark-cli")) {
|
|
6939
7087
|
const skillsInstallPath = await installAgentSkills(manifest, ossFileMap, opts, homeBase, opts.tmpRoot);
|
|
6940
7088
|
if (skillsInstallPath) linkAgentSkills(homeBase, skillsInstallPath);
|
|
6941
7089
|
const appIds = collectFeishuAppIds();
|
|
@@ -6960,7 +7108,9 @@ async function installClis(tag, ossFileMap, opts) {
|
|
|
6960
7108
|
}
|
|
6961
7109
|
});
|
|
6962
7110
|
}
|
|
6963
|
-
|
|
7111
|
+
const configPath = node_path.default.join(homeBase, WORKSPACE_AGENT_REL, "openclaw.json");
|
|
7112
|
+
if (node_fs.default.existsSync(node_path.default.join(homeBase, "workspace/agent", "extensions", "openclaw-lark"))) ensureLarkCliToolDenyInFile(configPath, (msg) => console.error(`[install-cli] ${msg}`));
|
|
7113
|
+
else console.error(`[install-cli] ${LARK_PLUGIN_NAME} not installed — skip tools.deny`);
|
|
6964
7114
|
}
|
|
6965
7115
|
console.error(`[install-cli] done ${targets.length}/${targets.length} in ${Date.now() - t0}ms`);
|
|
6966
7116
|
}
|
|
@@ -6997,7 +7147,7 @@ function linkBins(pkg, homeBase) {
|
|
|
6997
7147
|
}
|
|
6998
7148
|
}
|
|
6999
7149
|
async function installAgentSkills(manifest, ossFileMap, downloadOpts, homeBase, tmpRoot) {
|
|
7000
|
-
const pkg = manifest.packages.find((p) => p.role === "template" && p.name ===
|
|
7150
|
+
const pkg = manifest.packages.find((p) => p.role === "template" && p.name === "agent-skills");
|
|
7001
7151
|
if (!pkg) {
|
|
7002
7152
|
console.error(`[install-cli] installAgentSkills: ${AGENT_SKILLS_NAME} not found in manifest (tag may not bundle it) — skipping skill install`);
|
|
7003
7153
|
return null;
|
|
@@ -7135,42 +7285,6 @@ function installOne(pkg, tarball, homeBase, tmpRoot) {
|
|
|
7135
7285
|
} catch {}
|
|
7136
7286
|
}
|
|
7137
7287
|
}
|
|
7138
|
-
/**
|
|
7139
|
-
* Write overlapping tool names to channels.feishu.tools.deny in openclaw.json so that
|
|
7140
|
-
* openclaw-lark's shouldRegisterTool() skips them when lark-cli is present.
|
|
7141
|
-
*
|
|
7142
|
-
* Only runs when openclaw-lark is installed (extensions/openclaw-lark/ exists).
|
|
7143
|
-
* Idempotent: merges with any existing deny entries.
|
|
7144
|
-
* Failures are non-fatal (logged as warnings).
|
|
7145
|
-
*/
|
|
7146
|
-
function disableLarkCliOverlapTools(configPath, homeBase) {
|
|
7147
|
-
const larkPluginDir = node_path.default.join(homeBase, WORKSPACE_AGENT_REL, "extensions", LARK_PLUGIN_NAME);
|
|
7148
|
-
if (!node_fs.default.existsSync(larkPluginDir)) {
|
|
7149
|
-
console.error(`[install-cli] disableLarkCliOverlapTools: ${LARK_PLUGIN_NAME} not installed — skipping`);
|
|
7150
|
-
return;
|
|
7151
|
-
}
|
|
7152
|
-
if (!node_fs.default.existsSync(configPath)) {
|
|
7153
|
-
console.error(`[install-cli] disableLarkCliOverlapTools: config not found at ${configPath} — skipping`);
|
|
7154
|
-
return;
|
|
7155
|
-
}
|
|
7156
|
-
try {
|
|
7157
|
-
const config = loadJSON5().parse(node_fs.default.readFileSync(configPath, "utf-8"));
|
|
7158
|
-
if (!config.channels || typeof config.channels !== "object") config.channels = {};
|
|
7159
|
-
const channels = config.channels;
|
|
7160
|
-
if (!channels.feishu || typeof channels.feishu !== "object") channels.feishu = {};
|
|
7161
|
-
const feishu = channels.feishu;
|
|
7162
|
-
if (!feishu.tools || typeof feishu.tools !== "object") feishu.tools = {};
|
|
7163
|
-
const tools = feishu.tools;
|
|
7164
|
-
const existing = Array.isArray(tools.deny) ? tools.deny : [];
|
|
7165
|
-
tools.deny = [...new Set([...existing, ...LARK_CLI_OVERLAP_TOOL_DENY])];
|
|
7166
|
-
const tmp = configPath + ".lark-cli-deny-tmp";
|
|
7167
|
-
node_fs.default.writeFileSync(tmp, JSON.stringify(config, null, 2), "utf-8");
|
|
7168
|
-
moveSafe(tmp, configPath);
|
|
7169
|
-
console.error(`[install-cli] disableLarkCliOverlapTools: channels.feishu.tools.deny updated (${LARK_CLI_OVERLAP_TOOL_DENY.length} patterns)`);
|
|
7170
|
-
} catch (e) {
|
|
7171
|
-
console.error(`[install-cli] WARN: disableLarkCliOverlapTools failed: ${e.message}`);
|
|
7172
|
-
}
|
|
7173
|
-
}
|
|
7174
7288
|
//#endregion
|
|
7175
7289
|
//#region src/download-resource.ts
|
|
7176
7290
|
/**
|
|
@@ -11106,7 +11220,7 @@ async function reportCliRun(opts) {
|
|
|
11106
11220
|
//#region src/help.ts
|
|
11107
11221
|
const BIN = "mclaw-diagnose";
|
|
11108
11222
|
function versionBanner() {
|
|
11109
|
-
return `v0.1.18-alpha.
|
|
11223
|
+
return `v0.1.18-alpha.4`;
|
|
11110
11224
|
}
|
|
11111
11225
|
const COMMANDS = [
|
|
11112
11226
|
{
|
|
@@ -11805,8 +11919,8 @@ function snapshotVersions(cwd, log) {
|
|
|
11805
11919
|
});
|
|
11806
11920
|
const ocRaw = (ocResult.stdout ?? "").trim() || (ocResult.stderr ?? "").trim();
|
|
11807
11921
|
const extDir = node_path.default.join(cwd, "extensions");
|
|
11808
|
-
const larkPkg = node_path.default.join(extDir,
|
|
11809
|
-
const feishuPkg = node_path.default.join(extDir,
|
|
11922
|
+
const larkPkg = node_path.default.join(extDir, LARK_PLUGIN_NAME, "package.json");
|
|
11923
|
+
const feishuPkg = node_path.default.join(extDir, LEGACY_LARK_PLUGIN_NAME, "package.json");
|
|
11810
11924
|
log(` version-check paths: ${larkPkg} [${node_fs.default.existsSync(larkPkg) ? "exists" : "missing"}]`);
|
|
11811
11925
|
log(` version-check paths: ${feishuPkg} [${node_fs.default.existsSync(feishuPkg) ? "exists" : "missing"}]`);
|
|
11812
11926
|
return {
|
|
@@ -12044,7 +12158,7 @@ function runUpgradeLark(opts) {
|
|
|
12044
12158
|
});
|
|
12045
12159
|
}
|
|
12046
12160
|
log("");
|
|
12047
|
-
log("── [1/
|
|
12161
|
+
log("── [1/7] 文件备份 ────────────────────────────────────────");
|
|
12048
12162
|
log(`before-state: botCount=${countFeishuBots(configPath)}`);
|
|
12049
12163
|
const t_backupStart = Date.now();
|
|
12050
12164
|
const backup = backupFeishuPlugins(fsOpts);
|
|
@@ -12062,7 +12176,7 @@ function runUpgradeLark(opts) {
|
|
|
12062
12176
|
log("backup: ok");
|
|
12063
12177
|
logVersionSnapshot("before-versions", snapshotVersions(cwd, log), log);
|
|
12064
12178
|
log("");
|
|
12065
|
-
log("── [2/
|
|
12179
|
+
log("── [2/7] 清理本地 openclaw shim ─────────────────────────");
|
|
12066
12180
|
const localOpenclawBin = node_path.default.join(cwd, "node_modules", ".bin", "openclaw");
|
|
12067
12181
|
if (node_fs.default.existsSync(localOpenclawBin)) try {
|
|
12068
12182
|
node_fs.default.rmSync(localOpenclawBin);
|
|
@@ -12072,7 +12186,7 @@ function runUpgradeLark(opts) {
|
|
|
12072
12186
|
}
|
|
12073
12187
|
else log(` skipped: ${localOpenclawBin} (not found)`);
|
|
12074
12188
|
log("");
|
|
12075
|
-
log("── [3/
|
|
12189
|
+
log("── [3/7] npx install (@larksuite/openclaw-lark-tools update) ──");
|
|
12076
12190
|
const t_npxStart = Date.now();
|
|
12077
12191
|
const { exitCode: npxExitCode, stdout: npxStdout, stderr: npxStderr } = runLarkToolsUpdate({
|
|
12078
12192
|
cwd,
|
|
@@ -12103,7 +12217,7 @@ function runUpgradeLark(opts) {
|
|
|
12103
12217
|
});
|
|
12104
12218
|
};
|
|
12105
12219
|
log("");
|
|
12106
|
-
log("── [4/
|
|
12220
|
+
log("── [4/7] 安装后诊断校验 ─────────────────────────────────");
|
|
12107
12221
|
logVersionSnapshot("after-versions", snapshotVersions(cwd, log), log);
|
|
12108
12222
|
let afterVersionIncompatible = false;
|
|
12109
12223
|
try {
|
|
@@ -12132,7 +12246,7 @@ function runUpgradeLark(opts) {
|
|
|
12132
12246
|
if (isNewDefaultOnly) log(" post-install diagnosis: ok (new default account — plugin installed, awaiting configuration)");
|
|
12133
12247
|
else log(" post-install diagnosis: ok (upgrade conditions resolved)");
|
|
12134
12248
|
log("");
|
|
12135
|
-
log("── [
|
|
12249
|
+
log("── [5/7] doctor --fix ────────────────────────────────────");
|
|
12136
12250
|
const fixArgs = ["doctor", "--fix"];
|
|
12137
12251
|
if (opts.scene) fixArgs.push(`--scene=${opts.scene}`);
|
|
12138
12252
|
const t_doctorFixStart = Date.now();
|
|
@@ -12152,7 +12266,7 @@ function runUpgradeLark(opts) {
|
|
|
12152
12266
|
if (fixResult.stderr?.trim()) log(`doctor(fix) stderr:\n${fixResult.stderr.trim()}`);
|
|
12153
12267
|
log(`doctor(fix) exit: ${fixResult.status ?? "null"}${fixResult.error ? ` error: ${fixResult.error.message}` : ""}`);
|
|
12154
12268
|
log("");
|
|
12155
|
-
log("── [7
|
|
12269
|
+
log("── [6/7] 重启 openclaw 服务 ──────────────────────────────");
|
|
12156
12270
|
const restartScript = "/opt/force/bin/openclaw_scripts/restart.sh";
|
|
12157
12271
|
let restartExecuted = false;
|
|
12158
12272
|
if (opts.skipRestart) log(" skipped: --skip-restart");
|
|
@@ -12174,7 +12288,7 @@ function runUpgradeLark(opts) {
|
|
|
12174
12288
|
restartExecuted = true;
|
|
12175
12289
|
} else log(` skipped: ${restartScript} not found`);
|
|
12176
12290
|
log("");
|
|
12177
|
-
log("── [
|
|
12291
|
+
log("── [7/7] 端口存活检测 ────────────────────────────────────");
|
|
12178
12292
|
let portCheckOk;
|
|
12179
12293
|
if (!restartExecuted) log(" skipped: restart was not executed");
|
|
12180
12294
|
else {
|