@lark-apaas/openclaw-scripts-diagnose-cli 0.1.18-alpha.1 → 0.1.18-alpha.3

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.
Files changed (2) hide show
  1. package/dist/index.cjs +276 -164
  2. 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.1";
55
+ return "0.1.18-alpha.3";
56
56
  }
57
57
  //#endregion
58
58
  //#region src/rule-engine/base.ts
@@ -2655,17 +2655,71 @@ function upgradeLarkLogFile(runId, checkOnly = false) {
2655
2655
  return `${DIAGNOSE_DIR}/upgrade-lark${checkOnly ? "-check" : ""}-${ts}-${runId.slice(0, 8)}.log`;
2656
2656
  }
2657
2657
  //#endregion
2658
+ //#region src/constants.ts
2659
+ /**
2660
+ * 全局共享常量(插件名 / CLI 名 / 路径 / 插件集合)。
2661
+ *
2662
+ * 历史上这些字面量散落在 install-*、upgrade-lark、各 rule 等十余个文件里重复定义,
2663
+ * 易漂移。统一收口到此处,按用途分组,单一来源。
2664
+ */
2665
+ /**
2666
+ * 官方插件:`openclaw-lark`(plugins.allow 中的短名;npm 发布名 `@larksuite/openclaw-lark`)。
2667
+ * 走 VERSION_COMPAT_MAP 按版本判定与 openclaw 的兼容性。
2668
+ */
2669
+ const LARK_PLUGIN_NAME = "openclaw-lark";
2670
+ /**
2671
+ * fork 插件:`@lark-apaas/openclaw-lark`(@lark-apaas 内部 fork,全名带 scope)。
2672
+ * 版本号自成体系、不在 VERSION_COMPAT_MAP 内,按对标的官方版本判定兼容性。
2673
+ */
2674
+ const FORK_LARK_PLUGIN_FULL_NAME = "@lark-apaas/openclaw-lark";
2675
+ /** fork 插件的 scope 前缀。 */
2676
+ const FORK_LARK_PLUGIN_SCOPE = "@lark-apaas";
2677
+ /**
2678
+ * 社区插件:openclaw **内置** 的 `feishu` 插件(非独立扩展)。
2679
+ * 与官方/ fork openclaw-lark 互斥,规范化时应禁用以让位 openclaw-lark。
2680
+ */
2681
+ const BUILTIN_FEISHU_PLUGIN_NAME = "feishu";
2682
+ /**
2683
+ * 旧版插件:已弃用的 `feishu-openclaw-plugin`,始终不兼容,需替换为官方 openclaw-lark。
2684
+ */
2685
+ const LEGACY_LARK_PLUGIN_NAME = "feishu-openclaw-plugin";
2686
+ const MIAODA_PLUGIN_NAME = "openclaw-extension-miaoda";
2687
+ const MIAODA_CODING_PLUGIN_NAME = "openclaw-extension-miaoda-coding";
2688
+ const GUARDIAN_PLUGIN_NAME = "openclaw-guardian-plugin";
2689
+ const MEM0_PLUGIN_NAME = "openclaw-mem0-plugin";
2690
+ /** 飞书 lark-cli 命令名(独立 CLI,与 openclaw-lark 插件配套)。 */
2691
+ const LARK_CLI_NAME = "lark-cli";
2692
+ /** agent-skills 模板包名(manifest role=template,随 lark-cli 一起安装)。 */
2693
+ const AGENT_SKILLS_NAME = "agent-skills";
2694
+ /** 飞书插件目录集合(官方短名 + 旧版名):备份 / 清理 / 安装时迭代用。 */
2695
+ const FEISHU_PLUGIN_DIR_NAMES = [LARK_PLUGIN_NAME, LEGACY_LARK_PLUGIN_NAME];
2696
+ /**
2697
+ * install-extension --all 安装的官方扩展插件集合(manifest role=extension)。
2698
+ * builtin-plugin-missing 与 miaoda-official-plugins-install-spec-unlock 共用此单一来源,
2699
+ * 二者此前各维护一份且注释明示"必须保持一致"。
2700
+ */
2701
+ const OFFICIAL_EXTENSION_PLUGIN_NAMES = new Set([
2702
+ LARK_PLUGIN_NAME,
2703
+ MIAODA_PLUGIN_NAME,
2704
+ MIAODA_CODING_PLUGIN_NAME,
2705
+ GUARDIAN_PLUGIN_NAME,
2706
+ MEM0_PLUGIN_NAME
2707
+ ]);
2708
+ /** agent 工作区相对路径。 */
2709
+ const WORKSPACE_AGENT_REL = "workspace/agent";
2710
+ /** openclaw.json 默认相对路径。 */
2711
+ const DEFAULT_CONFIG_REL = `${WORKSPACE_AGENT_REL}/openclaw.json`;
2712
+ //#endregion
2658
2713
  //#region src/lark-cli-init.ts
2659
- const LARK_PLUGIN_NAMES$1 = ["openclaw-lark", "feishu-openclaw-plugin"];
2660
2714
  const PE_XML_TAG = "lark-cli-pe";
2661
2715
  const PE_PLACEHOLDER = `
2662
2716
  <${PE_XML_TAG}>
2663
2717
  **【强制要求 - 无例外】飞书所有操作必须先读 feishu-lark-cli skill。未读skill不得调用任何飞书工具,违者视为操作失误。**
2664
2718
  </${PE_XML_TAG}>
2665
2719
  `;
2666
- function isLarkPluginInstalled(configPath) {
2720
+ function isLarkPluginInstalled$1(configPath) {
2667
2721
  const extDir = getExtensionsDir(configPath);
2668
- return LARK_PLUGIN_NAMES$1.some((name) => {
2722
+ return FEISHU_PLUGIN_DIR_NAMES.some((name) => {
2669
2723
  try {
2670
2724
  return node_fs.default.existsSync(node_path.default.join(extDir, name, "package.json"));
2671
2725
  } catch {
@@ -2837,7 +2891,7 @@ function collectFeishuAppIds(configPath) {
2837
2891
  }
2838
2892
  function runLarkCliInit(opts) {
2839
2893
  const configPath = opts.configPath ?? CONFIG_PATH;
2840
- if (!isLarkPluginInstalled(configPath)) {
2894
+ if (!isLarkPluginInstalled$1(configPath)) {
2841
2895
  console.error("lark-cli-init: skipping — openclaw-lark plugin not installed");
2842
2896
  return {
2843
2897
  ok: true,
@@ -2915,7 +2969,66 @@ function runLarkCliInit(opts) {
2915
2969
  };
2916
2970
  }
2917
2971
  //#endregion
2918
- //#region src/rules/agents-md-lark-cli-pe.ts
2972
+ //#region src/utils/feishu-tools-deny.ts
2973
+ /**
2974
+ * openclaw-lark 中与 lark-cli 功能重叠的工具,写入 channels.feishu.tools.deny 后
2975
+ * 由 openclaw-lark 插件自身的 shouldRegisterTool() 跳过注册,避免与 lark-cli 重复。
2976
+ * 支持尾部 `*` 通配(openclaw-lark matchesAnyPattern 识别)。
2977
+ *
2978
+ * 保留(IM / 鉴权 / 诊断类,与 lark-cli 无重叠):feishu_chat*、feishu_get_user、
2979
+ * feishu_search_user、feishu_im_*、feishu_oauth*、feishu_auth、feishu_diagnose、feishu_doctor。
2980
+ *
2981
+ * install-cli(安装 lark-cli 后即时写)与 agents_md_lark_cli_pe 规则(doctor --fix
2982
+ * 自愈)共用本常量与合并逻辑,单一来源。
2983
+ */
2984
+ const LARK_CLI_OVERLAP_TOOL_DENY = Object.freeze([
2985
+ "feishu_create_doc",
2986
+ "feishu_fetch_doc",
2987
+ "feishu_update_doc",
2988
+ "feishu_doc_comments",
2989
+ "feishu_doc_media",
2990
+ "feishu_drive_file",
2991
+ "feishu_wiki_space",
2992
+ "feishu_wiki_space_node",
2993
+ "feishu_search_doc_wiki",
2994
+ "feishu_bitable_*",
2995
+ "feishu_calendar_*",
2996
+ "feishu_task_*",
2997
+ "feishu_sheet"
2998
+ ]);
2999
+ /** 读取 channels.feishu.tools.deny 的字符串数组(缺失/非法时返回空数组)。 */
3000
+ function readDenyList(config) {
3001
+ const deny = asRecord(asRecord(asRecord(config.channels)?.feishu)?.tools)?.deny;
3002
+ return Array.isArray(deny) ? deny.filter((e) => typeof e === "string") : [];
3003
+ }
3004
+ /** 返回 deny 列表中尚缺的重叠工具(用于 validate 判定)。 */
3005
+ function larkCliToolDenyMissing(config) {
3006
+ const existing = new Set(readDenyList(config));
3007
+ return LARK_CLI_OVERLAP_TOOL_DENY.filter((t) => !existing.has(t));
3008
+ }
3009
+ /**
3010
+ * 确保 channels.feishu.tools.deny 含全部重叠工具(只增不删,幂等合并)。
3011
+ * 直接 mutate 传入的 config 对象;返回是否发生了变更。
3012
+ */
3013
+ function ensureLarkCliToolDeny(config) {
3014
+ if (larkCliToolDenyMissing(config).length === 0) return false;
3015
+ if (!config.channels || typeof config.channels !== "object" || Array.isArray(config.channels)) config.channels = {};
3016
+ const channels = config.channels;
3017
+ if (!channels.feishu || typeof channels.feishu !== "object" || Array.isArray(channels.feishu)) channels.feishu = {};
3018
+ const feishu = channels.feishu;
3019
+ if (!feishu.tools || typeof feishu.tools !== "object" || Array.isArray(feishu.tools)) feishu.tools = {};
3020
+ const tools = feishu.tools;
3021
+ const existing = readDenyList(config);
3022
+ tools.deny = [...new Set([...existing, ...LARK_CLI_OVERLAP_TOOL_DENY])];
3023
+ return true;
3024
+ }
3025
+ //#endregion
3026
+ //#region src/utils/lark-cli-detect.ts
3027
+ /**
3028
+ * 探测 lark-cli 是否可用(`lark-cli --version` 退出码为 0)。
3029
+ * 多条规则共用同一判定:feishu_plugin_state_normalize(是否补 feishu_* 授权)、
3030
+ * agents_md_lark_cli_pe(是否补 PE / deny)等。
3031
+ */
2919
3032
  function isLarkCliAvailable$1() {
2920
3033
  try {
2921
3034
  return (0, node_child_process.spawnSync)("lark-cli", ["--version"], {
@@ -2931,17 +3044,38 @@ function isLarkCliAvailable$1() {
2931
3044
  return false;
2932
3045
  }
2933
3046
  }
3047
+ //#endregion
3048
+ //#region src/rules/agents-md-lark-cli-pe.ts
3049
+ /** openclaw-lark 插件是否已落盘安装(deny 仅在插件存在时有意义)。 */
3050
+ function isLarkPluginInstalled(ctx) {
3051
+ return node_fs.default.existsSync(node_path.default.join(getExtensionsDir(ctx.configPath), LARK_PLUGIN_NAME));
3052
+ }
3053
+ /** 第一个缺失 PE 内容的 AGENTS.md 路径;无则 undefined。 */
3054
+ function findAgentsMdMissingPe(ctx) {
3055
+ return collectExistingAgentsMdPaths(ctx).find((filePath) => {
3056
+ return !node_fs.default.readFileSync(filePath, "utf-8").includes(`<${PE_XML_TAG}>`);
3057
+ });
3058
+ }
3059
+ /**
3060
+ * lark-cli 存在时统一规范化环境:
3061
+ * 1. 给各智能体 AGENTS.md 追加 lark-cli-pe PE 内容
3062
+ * 2. openclaw-lark 已装时,把与 lark-cli 重叠的工具写入 channels.feishu.tools.deny
3063
+ * (只增不删,避免与 lark-cli 重复注册)
3064
+ * 两者门禁同为「lark-cli 可用」,故合并为一条规则。
3065
+ */
2934
3066
  let AgentsMdLarkCliPeRule = class AgentsMdLarkCliPeRule extends DiagnoseRule {
2935
3067
  validate(ctx) {
2936
3068
  if (!isLarkCliAvailable$1()) return { pass: true };
2937
- const missingPath = collectExistingAgentsMdPaths(ctx).find((filePath) => {
2938
- return !node_fs.default.readFileSync(filePath, "utf-8").includes(`<${PE_XML_TAG}>`);
2939
- });
2940
- if (!missingPath) return { pass: true };
2941
- return {
3069
+ const missingPath = findAgentsMdMissingPe(ctx);
3070
+ if (missingPath) return {
2942
3071
  pass: false,
2943
3072
  message: `${missingPath} 中缺少 lark-cli-pe PE 内容,需要追加`
2944
3073
  };
3074
+ if (isLarkPluginInstalled(ctx) && larkCliToolDenyMissing(ctx.config).length > 0) return {
3075
+ pass: false,
3076
+ message: "channels.feishu.tools.deny 缺少与 lark-cli 重叠的 openclaw-lark 工具,需要补充"
3077
+ };
3078
+ return { pass: true };
2945
3079
  }
2946
3080
  repair(ctx) {
2947
3081
  if (!isLarkCliAvailable$1()) return;
@@ -2952,11 +3086,12 @@ let AgentsMdLarkCliPeRule = class AgentsMdLarkCliPeRule extends DiagnoseRule {
2952
3086
  node_fs.default.appendFileSync(filePath, `${sep}${PE_PLACEHOLDER}`, "utf-8");
2953
3087
  console.error(`agents-md-lark-cli-pe: appended PE to ${filePath}`);
2954
3088
  }
3089
+ if (isLarkPluginInstalled(ctx) && ensureLarkCliToolDeny(ctx.config)) console.error("agents-md-lark-cli-pe: merged lark-cli overlap tools into channels.feishu.tools.deny");
2955
3090
  }
2956
3091
  };
2957
3092
  AgentsMdLarkCliPeRule = __decorate([Rule({
2958
3093
  key: "agents_md_lark_cli_pe",
2959
- description: "检测各智能体 AGENTS.md 中是否缺失 lark-cli-pe PE 内容,lark-cli 存在时自动追加",
3094
+ description: "lark-cli 存在时规范化环境:AGENTS.md 追加 lark-cli-pe PE + openclaw-lark 重叠工具写入 channels.feishu.tools.deny",
2960
3095
  dependsOn: ["config_syntax_check"],
2961
3096
  repairMode: "standard",
2962
3097
  level: "silent"
@@ -2966,15 +3101,8 @@ AgentsMdLarkCliPeRule = __decorate([Rule({
2966
3101
  /**
2967
3102
  * Official miaoda-side plugins that must track manifest — version-locked specs
2968
3103
  * here block upgrades. Third-party / user-installed plugins are intentionally
2969
- * out of scope (users may pin them deliberately).
3104
+ * out of scope (users may pin them deliberately). 集合见 constants.OFFICIAL_EXTENSION_PLUGIN_NAMES。
2970
3105
  */
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
3106
  const LOCKED_NPM_SPEC = /^(@[a-z0-9][\w.-]*\/)?[a-z0-9][\w.-]*@[^@/:#\s]+$/i;
2979
3107
  function isLockedNpmSpec(spec) {
2980
3108
  return typeof spec === "string" && LOCKED_NPM_SPEC.test(spec);
@@ -2989,7 +3117,7 @@ function* iterLockedOfficialInstalls(config) {
2989
3117
  const installs = getNestedMap(config, "plugins", "installs");
2990
3118
  if (!installs) return;
2991
3119
  for (const [key, entry] of Object.entries(installs)) {
2992
- if (!OFFICIAL_PLUGIN_NAMES.has(key)) continue;
3120
+ if (!OFFICIAL_EXTENSION_PLUGIN_NAMES.has(key)) continue;
2993
3121
  const spec = asRecord(entry)?.spec;
2994
3122
  if (isLockedNpmSpec(spec)) yield [key, spec];
2995
3123
  }
@@ -3021,28 +3149,27 @@ MiaodaOfficialPluginsInstallSpecUnlockRule = __decorate([Rule({
3021
3149
  })], MiaodaOfficialPluginsInstallSpecUnlockRule);
3022
3150
  //#endregion
3023
3151
  //#region src/rules/miaoda-plugin-allow.ts
3024
- const MIAODA_PLUGIN = "openclaw-extension-miaoda";
3025
3152
  let MiaodaPluginAllowRule = class MiaodaPluginAllowRule extends DiagnoseRule {
3026
3153
  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 };
3154
+ if (!isPluginInstalledOnDisk(getExtensionsDir(ctx.configPath), "openclaw-extension-miaoda")) return { pass: true };
3155
+ if (getAllow$1(ctx.config).includes("openclaw-extension-miaoda")) return { pass: true };
3029
3156
  return {
3030
3157
  pass: false,
3031
- message: `plugins.allow 缺少 ${MIAODA_PLUGIN}(已在 extensions/ 下装但未启用)`
3158
+ message: `plugins.allow 缺少 ${MIAODA_PLUGIN_NAME}(已在 extensions/ 下装但未启用)`
3032
3159
  };
3033
3160
  }
3034
3161
  repair(ctx) {
3035
- if (!isPluginInstalledOnDisk(getExtensionsDir(ctx.configPath), MIAODA_PLUGIN)) return;
3162
+ if (!isPluginInstalledOnDisk(getExtensionsDir(ctx.configPath), "openclaw-extension-miaoda")) return;
3036
3163
  const plugins = ctx.config.plugins;
3037
3164
  if (plugins == null || typeof plugins !== "object" || Array.isArray(plugins)) {
3038
- ctx.config.plugins = { allow: [MIAODA_PLUGIN] };
3165
+ ctx.config.plugins = { allow: [MIAODA_PLUGIN_NAME] };
3039
3166
  return;
3040
3167
  }
3041
3168
  const pluginsMap = plugins;
3042
3169
  const rawAllow = pluginsMap.allow;
3043
3170
  const allow = Array.isArray(rawAllow) ? rawAllow : [];
3044
- if (allow.includes(MIAODA_PLUGIN)) return;
3045
- allow.push(MIAODA_PLUGIN);
3171
+ if (allow.includes("openclaw-extension-miaoda")) return;
3172
+ allow.push(MIAODA_PLUGIN_NAME);
3046
3173
  pluginsMap.allow = allow;
3047
3174
  }
3048
3175
  };
@@ -3063,9 +3190,7 @@ function getAllow$1(config) {
3063
3190
  }
3064
3191
  //#endregion
3065
3192
  //#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];
3193
+ const LARK_PLUGIN_NAMES = FEISHU_PLUGIN_DIR_NAMES;
3069
3194
  let LarkPluginAllowRule = class LarkPluginAllowRule extends DiagnoseRule {
3070
3195
  validate(ctx) {
3071
3196
  const allow = getAllow(ctx.config);
@@ -3113,7 +3238,7 @@ function getAllow(config) {
3113
3238
  * 不读 package.json 内容,只判存在性,避开 JSON 损坏。
3114
3239
  */
3115
3240
  function detectInstalledLarkPlugin(extDir) {
3116
- for (const name of [LARK_PLUGIN, LEGACY_LARK_PLUGIN]) if (pluginPackageJsonExists(extDir, name)) return name;
3241
+ for (const name of LARK_PLUGIN_NAMES) if (pluginPackageJsonExists(extDir, name)) return name;
3117
3242
  return null;
3118
3243
  }
3119
3244
  function pluginPackageJsonExists(extDir, pluginDir) {
@@ -3125,7 +3250,6 @@ function pluginPackageJsonExists(extDir, pluginDir) {
3125
3250
  }
3126
3251
  //#endregion
3127
3252
  //#region src/rules/old-miaoda-plugins-cleanup.ts
3128
- const NEW_MIAODA = "openclaw-extension-miaoda";
3129
3253
  const OLD_PLUGIN_NAMES = Object.freeze([
3130
3254
  "openclaw-feishu-greeting",
3131
3255
  "openclaw-miaoda-keepalive",
@@ -3141,7 +3265,7 @@ function getPluginMaps(config) {
3141
3265
  };
3142
3266
  }
3143
3267
  function hasNewMiaoda({ entries, installs, allow }) {
3144
- return asRecord(entries?.[NEW_MIAODA]) != null || asRecord(installs?.[NEW_MIAODA]) != null || (allow?.includes(NEW_MIAODA) ?? false);
3268
+ return asRecord(entries?.["openclaw-extension-miaoda"]) != null || asRecord(installs?.["openclaw-extension-miaoda"]) != null || (allow?.includes("openclaw-extension-miaoda") ?? false);
3145
3269
  }
3146
3270
  function findResiduals({ entries, installs, allow }, extensionsDir) {
3147
3271
  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)));
@@ -3193,20 +3317,9 @@ OldMiaodaPluginsCleanupRule = __decorate([Rule({
3193
3317
  })], OldMiaodaPluginsCleanupRule);
3194
3318
  //#endregion
3195
3319
  //#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
3320
  function findMissingBuiltinPlugins(ctx) {
3208
3321
  const extDir = getExtensionsDir(ctx.configPath);
3209
- return [...BUILTIN_EXTENSION_PLUGINS].filter((name) => !isPluginInstalledOnDisk(extDir, name)).sort();
3322
+ return [...OFFICIAL_EXTENSION_PLUGIN_NAMES].filter((name) => !isPluginInstalledOnDisk(extDir, name)).sort();
3210
3323
  }
3211
3324
  let BuiltinPluginMissingRule = class BuiltinPluginMissingRule extends DiagnoseRule {
3212
3325
  validate(ctx) {
@@ -3487,10 +3600,7 @@ function extractTarballTolerant(tarball, destDir, opts = {}) {
3487
3600
  }
3488
3601
  //#endregion
3489
3602
  //#region src/rules/feishu-plugin-state-normalize.ts
3490
- const PLUGIN_NAME$2 = "openclaw-lark";
3491
- const BUILTIN_FEISHU = "feishu";
3492
- const LEGACY_PLUGIN_NAME = "feishu-openclaw-plugin";
3493
- const LEGACY_DIRS_TO_REMOVE = [LEGACY_PLUGIN_NAME, BUILTIN_FEISHU];
3603
+ const LEGACY_DIRS_TO_REMOVE = [LEGACY_LARK_PLUGIN_NAME, BUILTIN_FEISHU_PLUGIN_NAME];
3494
3604
  const FEISHU_TOOLS = Object.freeze([
3495
3605
  "feishu_bitable_app",
3496
3606
  "feishu_bitable_app_table",
@@ -3536,9 +3646,10 @@ let FeishuPluginStateNormalizeRule = class FeishuPluginStateNormalizeRule extend
3536
3646
  validate(ctx) {
3537
3647
  if (!isPluginInstalled(ctx)) return { pass: true };
3538
3648
  const fails = [];
3539
- if (!isNewPluginEnabled(ctx.config)) fails.push(`plugins.entries["${PLUGIN_NAME$2}"].enabled !== true(应启用)`);
3649
+ if (!isNewPluginEnabled(ctx.config)) fails.push(`plugins.entries["${LARK_PLUGIN_NAME}"].enabled !== true(应启用)`);
3540
3650
  if (isBuiltinFeishuEnabled(ctx.config)) fails.push("plugins.entries.feishu.enabled === true(应禁用)");
3541
- if (isTopLevelMissingFeishuTools(ctx.config)) fails.push("tools.alsoAllow 缺 feishu_* tools");
3651
+ const grantGap = missingFeishuToolsGrant(ctx.config);
3652
+ if (grantGap) fails.push(`tools.${grantGap} 缺 feishu_* tools`);
3542
3653
  const legacyResiduals = findLegacyResiduals(ctx);
3543
3654
  if (legacyResiduals.length > 0) fails.push(`legacy 飞书插件残留:${legacyResiduals.join(", ")}`);
3544
3655
  if (fails.length === 0) return { pass: true };
@@ -3548,8 +3659,8 @@ let FeishuPluginStateNormalizeRule = class FeishuPluginStateNormalizeRule extend
3548
3659
  };
3549
3660
  }
3550
3661
  repair(ctx) {
3551
- setEntryEnabled(ctx.config, PLUGIN_NAME$2, true);
3552
- setEntryEnabled(ctx.config, BUILTIN_FEISHU, false);
3662
+ setEntryEnabled(ctx.config, LARK_PLUGIN_NAME, true);
3663
+ setEntryEnabled(ctx.config, BUILTIN_FEISHU_PLUGIN_NAME, false);
3553
3664
  ensureFeishuTools(ctx.config);
3554
3665
  cleanupLegacyResiduals(ctx);
3555
3666
  }
@@ -3562,36 +3673,54 @@ FeishuPluginStateNormalizeRule = __decorate([Rule({
3562
3673
  level: "critical"
3563
3674
  })], FeishuPluginStateNormalizeRule);
3564
3675
  function isPluginInstalled(ctx) {
3565
- return node_fs.default.existsSync(node_path.default.join(getExtensionsDir(ctx.configPath), PLUGIN_NAME$2));
3676
+ return node_fs.default.existsSync(node_path.default.join(getExtensionsDir(ctx.configPath), LARK_PLUGIN_NAME));
3566
3677
  }
3567
3678
  function isNewPluginEnabled(config) {
3568
- return asRecord(getNestedMap(config, "plugins", "entries")?.[PLUGIN_NAME$2])?.enabled === true;
3679
+ return asRecord(getNestedMap(config, "plugins", "entries")?.[LARK_PLUGIN_NAME])?.enabled === true;
3569
3680
  }
3570
3681
  function isBuiltinFeishuEnabled(config) {
3571
- return asRecord(getNestedMap(config, "plugins", "entries")?.[BUILTIN_FEISHU])?.enabled === true;
3572
- }
3573
- /** 仅看顶层 tools.alsoAllow——agent 级 alsoAllow 是用户对单 agent 的精细化授权,doctor 不动。
3574
- * 含 "*" 或任一 feishu_* 即视为已配。 */
3575
- function isTopLevelMissingFeishuTools(config) {
3576
- return !hasFeishuTool(readAlsoAllow(config));
3682
+ return asRecord(getNestedMap(config, "plugins", "entries")?.[BUILTIN_FEISHU_PLUGIN_NAME])?.enabled === true;
3577
3683
  }
3578
- function readAlsoAllow(host) {
3579
- const tools = asRecord(host)?.tools;
3580
- const arr = asRecord(tools)?.alsoAllow;
3684
+ /** 读取顶层 tools.<key>(allow / alsoAllow)的字符串数组。 */
3685
+ function readTopLevelToolsList(config, key) {
3686
+ const arr = asRecord(asRecord(config)?.tools)?.[key];
3581
3687
  if (!Array.isArray(arr)) return [];
3582
3688
  return arr.filter((e) => typeof e === "string");
3583
3689
  }
3584
- function hasFeishuTool(alsoAllow) {
3585
- if (alsoAllow.includes("*")) return true;
3586
- return alsoAllow.some((t) => FEISHU_TOOLS.includes(t));
3690
+ function hasFeishuTool(list) {
3691
+ if (list.includes("*")) return true;
3692
+ return list.some((t) => FEISHU_TOOLS.includes(t));
3693
+ }
3694
+ /**
3695
+ * 决定 feishu_* 应补充到哪个授权键:
3696
+ * - 已有非空 tools.allow(限制式白名单)→ 'allow'(合并进 allow,避免与 alsoAllow 冲突)
3697
+ * - 否则 → 'alsoAllow'(追加式,叠加在 profile 基线上)
3698
+ * openclaw schema 禁止同一 scope 同时设 allow + alsoAllow,故有 allow 时必须并入 allow,
3699
+ * 否则会触发 tools_allow_also_allow_conflict 规则反复来回改写。
3700
+ */
3701
+ function feishuGrantTarget(config) {
3702
+ return readTopLevelToolsList(config, "allow").length > 0 ? "allow" : "alsoAllow";
3703
+ }
3704
+ /**
3705
+ * 仅看顶层 tools——agent 级授权是用户对单 agent 的精细化配置,doctor 不动。
3706
+ * 返回缺失的目标键名('allow' / 'alsoAllow'),不缺时返回 null。
3707
+ *
3708
+ * 不关心 lark-cli:tools.alsoAllow(授权层)与 channels.feishu.tools.deny(插件注册层)
3709
+ * 是两个正交的 config key,可并存——授权一个被 deny 的工具是无害空操作。因此本规则始终
3710
+ * 补全 FEISHU_TOOLS(让 openclaw-lark 实际注册的工具都可用),重叠工具的"不可用"由独立的
3711
+ * deny 规则在注册层处理,互不干扰。
3712
+ */
3713
+ function missingFeishuToolsGrant(config) {
3714
+ const target = feishuGrantTarget(config);
3715
+ return hasFeishuTool(readTopLevelToolsList(config, target)) ? null : target;
3587
3716
  }
3588
3717
  function findLegacyResiduals(ctx) {
3589
3718
  const found = [];
3590
3719
  const plugins = asRecord(ctx.config.plugins);
3591
- if (asRecord(plugins?.entries)?.[LEGACY_PLUGIN_NAME] != null) found.push("entries[legacy]");
3720
+ if (asRecord(plugins?.entries)?.["feishu-openclaw-plugin"] != null) found.push("entries[legacy]");
3592
3721
  const allow = plugins?.allow;
3593
- if (Array.isArray(allow) && allow.includes(LEGACY_PLUGIN_NAME)) found.push("allow[legacy]");
3594
- if (asRecord(plugins?.installs)?.[LEGACY_PLUGIN_NAME] != null) found.push("installs[legacy]");
3722
+ if (Array.isArray(allow) && allow.includes("feishu-openclaw-plugin")) found.push("allow[legacy]");
3723
+ if (asRecord(plugins?.installs)?.["feishu-openclaw-plugin"] != null) found.push("installs[legacy]");
3595
3724
  const extDir = getExtensionsDir(ctx.configPath);
3596
3725
  for (const name of LEGACY_DIRS_TO_REMOVE) if (node_fs.default.existsSync(node_path.default.join(extDir, name))) found.push(`fs/${name}`);
3597
3726
  return found;
@@ -3604,21 +3733,22 @@ function setEntryEnabled(config, key, enabled) {
3604
3733
  };
3605
3734
  }
3606
3735
  function ensureFeishuTools(config) {
3607
- const alsoAllow = readAlsoAllow(config);
3608
- if (hasFeishuTool(alsoAllow)) return;
3609
- ensureRecord(config, "tools").alsoAllow = [...new Set([...alsoAllow, ...FEISHU_TOOLS])];
3736
+ const target = feishuGrantTarget(config);
3737
+ const current = readTopLevelToolsList(config, target);
3738
+ if (hasFeishuTool(current)) return;
3739
+ ensureRecord(config, "tools")[target] = [...new Set([...current, ...FEISHU_TOOLS])];
3610
3740
  }
3611
3741
  function cleanupLegacyResiduals(ctx) {
3612
3742
  const plugins = asRecord(ctx.config.plugins);
3613
3743
  if (plugins) {
3614
3744
  const entries = asRecord(plugins.entries);
3615
- if (entries && LEGACY_PLUGIN_NAME in entries) delete entries[LEGACY_PLUGIN_NAME];
3745
+ if (entries && "feishu-openclaw-plugin" in entries) delete entries[LEGACY_LARK_PLUGIN_NAME];
3616
3746
  const installs = asRecord(plugins.installs);
3617
- if (installs && LEGACY_PLUGIN_NAME in installs) delete installs[LEGACY_PLUGIN_NAME];
3747
+ if (installs && "feishu-openclaw-plugin" in installs) delete installs[LEGACY_LARK_PLUGIN_NAME];
3618
3748
  const allow = plugins.allow;
3619
3749
  if (Array.isArray(allow)) {
3620
- for (let i = allow.length - 1; i >= 0; i--) if (allow[i] === LEGACY_PLUGIN_NAME) allow.splice(i, 1);
3621
- if (!allow.includes(PLUGIN_NAME$2)) allow.push(PLUGIN_NAME$2);
3750
+ for (let i = allow.length - 1; i >= 0; i--) if (allow[i] === "feishu-openclaw-plugin") allow.splice(i, 1);
3751
+ if (!allow.includes("openclaw-lark")) allow.push(LARK_PLUGIN_NAME);
3622
3752
  }
3623
3753
  }
3624
3754
  const extDir = getExtensionsDir(ctx.configPath);
@@ -3792,6 +3922,27 @@ function effectiveCompatible(pluginVersion, openclawVersion) {
3792
3922
  return maxInfo.exclusive ? semver.default.lt(oc, max) : !semver.default.gt(oc, max);
3793
3923
  }
3794
3924
  /**
3925
+ * Fork plugin handling.
3926
+ *
3927
+ * `@lark-apaas/openclaw-lark` is an internal fork whose own version numbers
3928
+ * (e.g. 2026.4.3, 2026.4.4) are NOT keys in VERSION_COMPAT_MAP. Floor-matching
3929
+ * a fork version against the official-keyed map only works by coincidence of
3930
+ * numbering and breaks once the fork version drifts past an official entry.
3931
+ * Both the install-time compat check and the diagnose rule must therefore pin
3932
+ * the fork to its official-equivalent version before consulting the map.
3933
+ */
3934
+ /** fork 对标的官方 openclaw-lark 版本(与 VERSION_COMPAT_MAP 强耦合,故留在此处)。 */
3935
+ const FORK_LARK_PLUGIN_PINNED_VERSION = "2026.4.1";
3936
+ /**
3937
+ * Resolve the version to evaluate against VERSION_COMPAT_MAP for a given plugin.
3938
+ * The fork is pinned to FORK_LARK_PLUGIN_PINNED_VERSION regardless of its own
3939
+ * version; everything else uses its real version.
3940
+ */
3941
+ function resolveCompatVersion(packageName, version) {
3942
+ if (packageName === "@lark-apaas/openclaw-lark") return FORK_LARK_PLUGIN_PINNED_VERSION;
3943
+ return version;
3944
+ }
3945
+ /**
3795
3946
  * Floor match: find the entry with the largest openclawLarkVersion that is
3796
3947
  * ≤ pluginVersion ("closest lower-or-equal version").
3797
3948
  *
@@ -3804,22 +3955,14 @@ function findClosestEntry(pluginVersion) {
3804
3955
  }
3805
3956
  //#endregion
3806
3957
  //#region src/rules/feishu-plugin-version-compat.ts
3807
- const PLUGIN_NAME$1 = "openclaw-lark";
3808
- const LEGACY_SHORT_NAMES = ["feishu-openclaw-plugin"];
3809
- const FORK_SCOPES = ["@lark-apaas"];
3810
- /** 特化 fork 版全名:复用官方 effectiveCompatible,按对标版本取完整兼容区间 */
3811
- const FORK_LARK_PLUGIN_FULL_NAME = "@lark-apaas/openclaw-lark";
3812
- /**
3813
- * fork 版对标的官方 openclaw-lark 版本。fork 自身版本不在 VERSION_COMPAT_MAP 内,
3814
- * 兼容性按此对标版本经 effectiveCompatible 取完整区间(含推断上界)判定。
3815
- */
3816
- const FORK_LARK_PLUGIN_PINNED_VERSION = "2026.4.1";
3958
+ const LEGACY_SHORT_NAMES = [LEGACY_LARK_PLUGIN_NAME];
3959
+ const FORK_SCOPES = [FORK_LARK_PLUGIN_SCOPE];
3817
3960
  /**
3818
3961
  * fork 版兼容区间的下界,取自 VERSION_COMPAT_MAP 中 openclawLarkVersion=2026.4.1
3819
3962
  * 的 minOpenclawVersion,避免与映射表脱钩写死。仅用于区分升级方向
3820
3963
  * (oc 低于下界 → 升 openclaw;高于上界 → 升 lark);该条目被移除时回退到已知值。
3821
3964
  */
3822
- const FORK_LARK_PLUGIN_MIN_OC_VERSION = findEntry(FORK_LARK_PLUGIN_PINNED_VERSION)?.minOpenclawVersion ?? "2026.3.28";
3965
+ const FORK_LARK_PLUGIN_MIN_OC_VERSION = findEntry("2026.4.1")?.minOpenclawVersion ?? "2026.3.28";
3823
3966
  let _ocVersion = void 0;
3824
3967
  function getOcVersion() {
3825
3968
  if (_ocVersion === void 0) _ocVersion = readOpenclawRuntimeVersion();
@@ -3896,7 +4039,7 @@ let FeishuPluginLarkUpgradeRule = class FeishuPluginLarkUpgradeRule extends Diag
3896
4039
  if (!cc) return { pass: true };
3897
4040
  const { ocCur, recommendedOc, installed, isLegacy } = cc;
3898
4041
  if (isForkPlugin(installed)) {
3899
- if (installed.fullName !== FORK_LARK_PLUGIN_FULL_NAME) return { pass: true };
4042
+ if (installed.fullName !== "@lark-apaas/openclaw-lark") return { pass: true };
3900
4043
  if (resolveForkUpgradeDirection(ocCur) !== "lark") return { pass: true };
3901
4044
  return {
3902
4045
  pass: false,
@@ -3983,7 +4126,7 @@ function describeCompatConstraint(entry, pluginVersion) {
3983
4126
  * 'lark' → oc 高于区间上界(插件相对当前 openclaw 过旧),需升级飞书插件
3984
4127
  */
3985
4128
  function resolveForkUpgradeDirection(ocCur) {
3986
- if (effectiveCompatible(FORK_LARK_PLUGIN_PINNED_VERSION, ocCur)) return null;
4129
+ if (effectiveCompatible("2026.4.1", ocCur)) return null;
3987
4130
  return compareCalVer(ocCur, FORK_LARK_PLUGIN_MIN_OC_VERSION) < 0 ? "openclaw" : "lark";
3988
4131
  }
3989
4132
  /**
@@ -3993,7 +4136,7 @@ function resolveForkUpgradeDirection(ocCur) {
3993
4136
  * recommendedOc 可为 undefined(doctor 模式),此时不指定目标升级版本。
3994
4137
  */
3995
4138
  function validateForkPlugin(installed, ocCur, recommendedOc) {
3996
- if (installed.fullName !== FORK_LARK_PLUGIN_FULL_NAME) return { pass: true };
4139
+ if (installed.fullName !== "@lark-apaas/openclaw-lark") return { pass: true };
3997
4140
  if (resolveForkUpgradeDirection(ocCur) !== "openclaw") return { pass: true };
3998
4141
  const recommendation = recommendedOc ? `;将 openclaw 升级到 ${recommendedOc} 即可满足` : `;请升级 openclaw 至 ${FORK_LARK_PLUGIN_MIN_OC_VERSION} 或更高版本`;
3999
4142
  return {
@@ -4024,7 +4167,7 @@ function detectInstalledPlugin(ctx) {
4024
4167
  const allow = Array.isArray(allowRaw) ? allowRaw.filter((e) => typeof e === "string") : [];
4025
4168
  const extDir = getExtensionsDir(ctx.configPath);
4026
4169
  const installs = getNestedMap(ctx.config, "plugins", "installs");
4027
- for (const name of [PLUGIN_NAME$1, ...LEGACY_SHORT_NAMES]) {
4170
+ for (const name of [LARK_PLUGIN_NAME, ...LEGACY_SHORT_NAMES]) {
4028
4171
  if (!allow.includes(name)) continue;
4029
4172
  const pkgPath = node_path.default.join(extDir, name, "package.json");
4030
4173
  if (!node_fs.default.existsSync(pkgPath)) continue;
@@ -4061,7 +4204,7 @@ function needsLarkUpgrade(ctx) {
4061
4204
  if (!cc) return false;
4062
4205
  const { ocCur, installed } = cc;
4063
4206
  if (isForkPlugin(installed)) {
4064
- if (installed.fullName === FORK_LARK_PLUGIN_FULL_NAME) return !effectiveCompatible(FORK_LARK_PLUGIN_PINNED_VERSION, ocCur);
4207
+ if (installed.fullName === "@lark-apaas/openclaw-lark") return !effectiveCompatible(FORK_LARK_PLUGIN_PINNED_VERSION, ocCur);
4065
4208
  return false;
4066
4209
  }
4067
4210
  return isLarkUpgradeNeededFromCC(cc);
@@ -4117,12 +4260,10 @@ CleanupInstallBackupDirsRule = __decorate([Rule({
4117
4260
  })], CleanupInstallBackupDirsRule);
4118
4261
  //#endregion
4119
4262
  //#region src/rules/lark-cli-missing-for-installed-lark-plugin.ts
4120
- const PLUGIN_NAME = "openclaw-lark";
4121
- const FORK_PACKAGE_NAME = "@lark-apaas/openclaw-lark";
4263
+ /** 触发本规则自动安装 lark-cli 的特定 fork 版本(与对标的官方兼容版本无关)。 */
4122
4264
  const TARGET_VERSION = "2026.4.4";
4123
- const LARK_CLI_NAME$1 = "lark-cli";
4124
4265
  function readInstalledLarkPlugin(ctx) {
4125
- const pkgPath = node_path.default.join(getExtensionsDir(ctx.configPath), PLUGIN_NAME, "package.json");
4266
+ const pkgPath = node_path.default.join(getExtensionsDir(ctx.configPath), LARK_PLUGIN_NAME, "package.json");
4126
4267
  if (!node_fs.default.existsSync(pkgPath)) return null;
4127
4268
  let pkg = {};
4128
4269
  try {
@@ -4135,14 +4276,14 @@ function readInstalledLarkPlugin(ctx) {
4135
4276
  pkg = {};
4136
4277
  }
4137
4278
  const installs = getNestedMap(ctx.config, "plugins", "installs");
4138
- const installEntry = installs ? asRecord(installs[PLUGIN_NAME]) : void 0;
4279
+ const installEntry = installs ? asRecord(installs[LARK_PLUGIN_NAME]) : void 0;
4139
4280
  return {
4140
4281
  name: pkg.name ?? extractScopedNameFromSpec(installEntry?.spec),
4141
4282
  version: pkg.version ?? (typeof installEntry?.version === "string" ? installEntry.version : void 0)
4142
4283
  };
4143
4284
  }
4144
4285
  function isTargetForkPlugin(plugin) {
4145
- return plugin?.name === FORK_PACKAGE_NAME && plugin.version === TARGET_VERSION;
4286
+ return plugin?.name === "@lark-apaas/openclaw-lark" && plugin.version === TARGET_VERSION;
4146
4287
  }
4147
4288
  function extractScopedNameFromSpec(spec) {
4148
4289
  if (typeof spec !== "string") return void 0;
@@ -4151,7 +4292,7 @@ function extractScopedNameFromSpec(spec) {
4151
4292
  }
4152
4293
  function isLarkCliAvailable() {
4153
4294
  try {
4154
- return (0, node_child_process.spawnSync)(LARK_CLI_NAME$1, ["--version"], {
4295
+ return (0, node_child_process.spawnSync)(LARK_CLI_NAME, ["--version"], {
4155
4296
  encoding: "utf-8",
4156
4297
  timeout: 5e3,
4157
4298
  stdio: [
@@ -4193,7 +4334,7 @@ let LarkCliMissingForInstalledLarkPluginRule = class LarkCliMissingForInstalledL
4193
4334
  if (isLarkCliAvailable()) return { pass: true };
4194
4335
  return {
4195
4336
  pass: false,
4196
- message: `${FORK_PACKAGE_NAME}@${TARGET_VERSION} 已安装,但 lark-cli 不可用;将执行一次 lark-cli 安装`
4337
+ message: `${FORK_LARK_PLUGIN_FULL_NAME}@${TARGET_VERSION} 已安装,但 lark-cli 不可用;将执行一次 lark-cli 安装`
4197
4338
  };
4198
4339
  }
4199
4340
  repair(ctx) {
@@ -5207,7 +5348,7 @@ async function installOpenclaw(openclawTag, ossFileMap, opts = {}) {
5207
5348
  console.error(`[install-openclaw] done in ${Date.now() - t0}ms`);
5208
5349
  }
5209
5350
  //#endregion
5210
- //#region src/lark-tools-update.ts
5351
+ //#region src/utils/lark-tools-update.ts
5211
5352
  /**
5212
5353
  * 共享的飞书插件备份 / 恢复 / `openclaw-lark-tools update` 调用。
5213
5354
  *
@@ -5218,8 +5359,8 @@ async function installOpenclaw(openclawTag, ossFileMap, opts = {}) {
5218
5359
  * `console.error`,独立调用方不传则静默。
5219
5360
  */
5220
5361
  const NOOP_LOG = () => {};
5221
- /** 升级前需备份的 extensions/ 下的插件目录(含 legacy */
5222
- const FEISHU_PLUGIN_DIRS = ["openclaw-lark", "feishu-openclaw-plugin"];
5362
+ /** 升级前需备份的 extensions/ 下的插件目录(官方短名 + legacy)。 */
5363
+ const FEISHU_PLUGIN_DIRS = FEISHU_PLUGIN_DIR_NAMES;
5223
5364
  /** 读取 package.json 的 version 字段,失败返回 null(仅用于日志) */
5224
5365
  function readPkgVersion(pkgPath) {
5225
5366
  try {
@@ -5339,6 +5480,8 @@ function runLarkToolsUpdate(opts) {
5339
5480
  spawnError: r.error?.message
5340
5481
  };
5341
5482
  }
5483
+ //#endregion
5484
+ //#region src/install-extension.ts
5342
5485
  async function installExtension(tag, ossFileMap, opts = {}) {
5343
5486
  const homeBase = resolveHomeBase(opts.homeBase);
5344
5487
  const hasAll = !!opts.all;
@@ -5361,18 +5504,18 @@ async function installExtension(tag, ossFileMap, opts = {}) {
5361
5504
  }
5362
5505
  console.error(`[install-extension] tag=${tag} targets=${targets.length}`);
5363
5506
  const t0 = Date.now();
5364
- const larkTarget = opts.skipConfigUpdate ? void 0 : targets.find((p) => p.name === LARK_PLUGIN_NAME$1);
5507
+ const larkTarget = opts.skipConfigUpdate ? void 0 : targets.find((p) => p.name === LARK_PLUGIN_NAME);
5365
5508
  if (larkTarget) {
5366
5509
  if (await installLarkPluginWithCompatCheck({
5367
5510
  tag,
5368
5511
  pkg: larkTarget,
5369
5512
  homeBase,
5370
- configPath: opts.configPath ?? node_path.default.join(homeBase, "workspace/agent/openclaw.json"),
5513
+ configPath: opts.configPath ?? node_path.default.join(homeBase, DEFAULT_CONFIG_REL),
5371
5514
  workspaceDir: node_path.default.join(homeBase, "workspace", "agent"),
5372
5515
  ossFileMap,
5373
5516
  downloadOpts: opts
5374
5517
  })) {
5375
- targets = targets.filter((p) => p.name !== LARK_PLUGIN_NAME$1);
5518
+ targets = targets.filter((p) => p.name !== LARK_PLUGIN_NAME);
5376
5519
  if (targets.length === 0) {
5377
5520
  console.error(`[install-extension] done in ${Date.now() - t0}ms`);
5378
5521
  return;
@@ -5391,13 +5534,11 @@ async function installExtension(tag, ossFileMap, opts = {}) {
5391
5534
  installOne$1(pkg, tarball, homeBase);
5392
5535
  console.error(`[install-extension] ${pkg.name}: installed`);
5393
5536
  }
5394
- if (!opts.skipConfigUpdate) updatePluginInstalls(opts.configPath ?? node_path.default.join(homeBase, "workspace/agent/openclaw.json"), targets);
5537
+ if (!opts.skipConfigUpdate) updatePluginInstalls(opts.configPath ?? node_path.default.join(homeBase, DEFAULT_CONFIG_REL), targets);
5395
5538
  else console.error(`[install-extension] skipConfigUpdate=true — not touching openclaw.json`);
5396
5539
  console.error(`[install-extension] done ${targets.length}/${targets.length} in ${Date.now() - t0}ms`);
5397
5540
  }
5398
- const LARK_PLUGIN_NAME$1 = "openclaw-lark";
5399
- const MEM0_PLUGIN_NAME = "openclaw-mem0-plugin";
5400
- const PLUGINS_TO_AUTO_ENABLE = ["openclaw-lark", "openclaw-extension-miaoda"];
5541
+ const PLUGINS_TO_AUTO_ENABLE = [LARK_PLUGIN_NAME, MIAODA_PLUGIN_NAME];
5401
5542
  /**
5402
5543
  * Merge each installed extension's installMetadata into openclaw.json's
5403
5544
  * plugins.installs[<pkg.name>]. Atomic write via tmp + rename.
@@ -5429,14 +5570,14 @@ function updatePluginInstalls(configPath, installedPkgs) {
5429
5570
  installs[pkg.name] = pkg.installMetadata;
5430
5571
  updated++;
5431
5572
  } else skipped++;
5432
- if (installedPkgs.some((p) => p.name === MEM0_PLUGIN_NAME)) {
5573
+ if (installedPkgs.some((p) => p.name === "openclaw-mem0-plugin")) {
5433
5574
  const allowRaw = plugins.allow;
5434
5575
  const allow = Array.isArray(allowRaw) ? allowRaw : [];
5435
- if (!allow.filter((e) => typeof e === "string").includes(MEM0_PLUGIN_NAME)) allow.push(MEM0_PLUGIN_NAME);
5576
+ if (!allow.filter((e) => typeof e === "string").includes("openclaw-mem0-plugin")) allow.push(MEM0_PLUGIN_NAME);
5436
5577
  plugins.allow = allow;
5437
5578
  if (!plugins.entries || typeof plugins.entries !== "object" || Array.isArray(plugins.entries)) plugins.entries = {};
5438
5579
  const entries = plugins.entries;
5439
- if (!(MEM0_PLUGIN_NAME in entries)) entries[MEM0_PLUGIN_NAME] = { enabled: false };
5580
+ if (!("openclaw-mem0-plugin" in entries)) entries[MEM0_PLUGIN_NAME] = { enabled: false };
5440
5581
  }
5441
5582
  for (const pkg of installedPkgs) {
5442
5583
  if (!PLUGINS_TO_AUTO_ENABLE.includes(pkg.name)) continue;
@@ -5503,8 +5644,9 @@ async function installLarkPluginWithCompatCheck(opts) {
5503
5644
  console.error("[install-extension] WARN: cannot read openclaw version — falling back to direct tarball install for openclaw-lark");
5504
5645
  return false;
5505
5646
  }
5506
- const compatible = pkg.version ? effectiveCompatible(pkg.version, ocCur) : true;
5507
- console.error(`[install-extension] openclaw-lark@${pkg.version ?? "unknown"} compat with openclaw@${ocCur}: ${compatible}`);
5647
+ const compatVersion = resolveCompatVersion(pkg.packageName, pkg.version);
5648
+ const compatible = compatVersion ? effectiveCompatible(compatVersion, ocCur) : true;
5649
+ console.error(`[install-extension] ${pkg.packageName ?? pkg.name}@${pkg.version ?? "unknown"} (compat-as ${compatVersion ?? "unknown"}) vs openclaw@${ocCur}: ${compatible}`);
5508
5650
  if (compatible) return false;
5509
5651
  console.error(`[install-extension] openclaw-lark@${pkg.version} incompatible with openclaw@${ocCur} — using openclaw-lark-tools update`);
5510
5652
  const log = (msg) => console.error(`[install-extension] ${msg}`);
@@ -5553,7 +5695,9 @@ async function installLarkPluginWithCompatCheck(opts) {
5553
5695
  recursive: true,
5554
5696
  force: true
5555
5697
  });
5556
- } catch {}
5698
+ } catch (e) {
5699
+ console.error(`[install-extension] WARN: failed to clean up backup dir ${backupDir}: ${e.message}`);
5700
+ }
5557
5701
  }
5558
5702
  }
5559
5703
  /**
@@ -5562,7 +5706,7 @@ async function installLarkPluginWithCompatCheck(opts) {
5562
5706
  */
5563
5707
  function validateLarkPluginInstall(opts) {
5564
5708
  const { homeBase, configPath } = opts;
5565
- const pkgJson = node_path.default.join(homeBase, "workspace", "agent", "extensions", LARK_PLUGIN_NAME$1, "package.json");
5709
+ const pkgJson = node_path.default.join(homeBase, "workspace", "agent", "extensions", LARK_PLUGIN_NAME, "package.json");
5566
5710
  if (!node_fs.default.existsSync(pkgJson)) return {
5567
5711
  ok: false,
5568
5712
  error: `plugin directory missing: ${pkgJson}`
@@ -5572,9 +5716,9 @@ function validateLarkPluginInstall(opts) {
5572
5716
  error: `openclaw.json not found at ${configPath}`
5573
5717
  };
5574
5718
  try {
5575
- if (asRecord(getNestedMap(loadJSON5().parse(node_fs.default.readFileSync(configPath, "utf-8")), "plugins", "entries")?.[LARK_PLUGIN_NAME$1])?.enabled !== true) return {
5719
+ if (asRecord(getNestedMap(loadJSON5().parse(node_fs.default.readFileSync(configPath, "utf-8")), "plugins", "entries")?.["openclaw-lark"])?.enabled !== true) return {
5576
5720
  ok: false,
5577
- error: `plugins.entries["${LARK_PLUGIN_NAME$1}"].enabled is not true`
5721
+ error: `plugins.entries["${LARK_PLUGIN_NAME}"].enabled is not true`
5578
5722
  };
5579
5723
  } catch (e) {
5580
5724
  return {
@@ -6864,34 +7008,6 @@ function reportError(params) {
6864
7008
  }
6865
7009
  //#endregion
6866
7010
  //#region src/install-cli.ts
6867
- const LARK_CLI_NAME = "lark-cli";
6868
- const AGENT_SKILLS_NAME = "agent-skills";
6869
- const WORKSPACE_AGENT_REL = "workspace/agent";
6870
- const LARK_PLUGIN_NAME = "openclaw-lark";
6871
- /**
6872
- * openclaw-lark tools that overlap with lark-cli functionality.
6873
- * Written to channels.feishu.tools.deny after lark-cli is installed so that
6874
- * openclaw-lark's shouldRegisterTool() skips them, avoiding duplicate tools.
6875
- * Supports trailing-* wildcards (handled by openclaw-lark's matchesAnyPattern).
6876
- *
6877
- * Kept: feishu_chat*, feishu_get_user, feishu_search_user, feishu_im_*,
6878
- * feishu_oauth*, feishu_auth, feishu_diagnose, feishu_doctor
6879
- */
6880
- const LARK_CLI_OVERLAP_TOOL_DENY = Object.freeze([
6881
- "feishu_create_doc",
6882
- "feishu_fetch_doc",
6883
- "feishu_update_doc",
6884
- "feishu_doc_comments",
6885
- "feishu_doc_media",
6886
- "feishu_drive_file",
6887
- "feishu_wiki_space",
6888
- "feishu_wiki_space_node",
6889
- "feishu_search_doc_wiki",
6890
- "feishu_bitable_*",
6891
- "feishu_calendar_*",
6892
- "feishu_task_*",
6893
- "feishu_sheet"
6894
- ]);
6895
7011
  async function installClis(tag, ossFileMap, opts) {
6896
7012
  const homeBase = resolveHomeBase(opts.homeBase);
6897
7013
  if (opts.names.length === 0) throw new Error("install-cli: must provide at least one --cli=<name>");
@@ -6931,7 +7047,7 @@ async function installClis(tag, ossFileMap, opts) {
6931
7047
  linkBins(pkg, homeBase);
6932
7048
  console.error(`[install-cli] ${pkg.name}: installed`);
6933
7049
  }
6934
- if (targets.some((p) => p.name === LARK_CLI_NAME)) {
7050
+ if (targets.some((p) => p.name === "lark-cli")) {
6935
7051
  const skillsInstallPath = await installAgentSkills(manifest, ossFileMap, opts, homeBase, opts.tmpRoot);
6936
7052
  if (skillsInstallPath) linkAgentSkills(homeBase, skillsInstallPath);
6937
7053
  const appIds = collectFeishuAppIds();
@@ -6993,7 +7109,7 @@ function linkBins(pkg, homeBase) {
6993
7109
  }
6994
7110
  }
6995
7111
  async function installAgentSkills(manifest, ossFileMap, downloadOpts, homeBase, tmpRoot) {
6996
- const pkg = manifest.packages.find((p) => p.role === "template" && p.name === AGENT_SKILLS_NAME);
7112
+ const pkg = manifest.packages.find((p) => p.role === "template" && p.name === "agent-skills");
6997
7113
  if (!pkg) {
6998
7114
  console.error(`[install-cli] installAgentSkills: ${AGENT_SKILLS_NAME} not found in manifest (tag may not bundle it) — skipping skill install`);
6999
7115
  return null;
@@ -7151,18 +7267,14 @@ function disableLarkCliOverlapTools(configPath, homeBase) {
7151
7267
  }
7152
7268
  try {
7153
7269
  const config = loadJSON5().parse(node_fs.default.readFileSync(configPath, "utf-8"));
7154
- if (!config.channels || typeof config.channels !== "object") config.channels = {};
7155
- const channels = config.channels;
7156
- if (!channels.feishu || typeof channels.feishu !== "object") channels.feishu = {};
7157
- const feishu = channels.feishu;
7158
- if (!feishu.tools || typeof feishu.tools !== "object") feishu.tools = {};
7159
- const tools = feishu.tools;
7160
- const existing = Array.isArray(tools.deny) ? tools.deny : [];
7161
- tools.deny = [...new Set([...existing, ...LARK_CLI_OVERLAP_TOOL_DENY])];
7270
+ if (!ensureLarkCliToolDeny(config)) {
7271
+ console.error("[install-cli] disableLarkCliOverlapTools: deny already up to date");
7272
+ return;
7273
+ }
7162
7274
  const tmp = configPath + ".lark-cli-deny-tmp";
7163
7275
  node_fs.default.writeFileSync(tmp, JSON.stringify(config, null, 2), "utf-8");
7164
7276
  moveSafe(tmp, configPath);
7165
- console.error(`[install-cli] disableLarkCliOverlapTools: channels.feishu.tools.deny updated (${LARK_CLI_OVERLAP_TOOL_DENY.length} patterns)`);
7277
+ console.error("[install-cli] disableLarkCliOverlapTools: channels.feishu.tools.deny updated");
7166
7278
  } catch (e) {
7167
7279
  console.error(`[install-cli] WARN: disableLarkCliOverlapTools failed: ${e.message}`);
7168
7280
  }
@@ -11102,7 +11214,7 @@ async function reportCliRun(opts) {
11102
11214
  //#region src/help.ts
11103
11215
  const BIN = "mclaw-diagnose";
11104
11216
  function versionBanner() {
11105
- return `v0.1.18-alpha.1`;
11217
+ return `v0.1.18-alpha.3`;
11106
11218
  }
11107
11219
  const COMMANDS = [
11108
11220
  {
@@ -11801,8 +11913,8 @@ function snapshotVersions(cwd, log) {
11801
11913
  });
11802
11914
  const ocRaw = (ocResult.stdout ?? "").trim() || (ocResult.stderr ?? "").trim();
11803
11915
  const extDir = node_path.default.join(cwd, "extensions");
11804
- const larkPkg = node_path.default.join(extDir, "openclaw-lark", "package.json");
11805
- const feishuPkg = node_path.default.join(extDir, "feishu-openclaw-plugin", "package.json");
11916
+ const larkPkg = node_path.default.join(extDir, LARK_PLUGIN_NAME, "package.json");
11917
+ const feishuPkg = node_path.default.join(extDir, LEGACY_LARK_PLUGIN_NAME, "package.json");
11806
11918
  log(` version-check paths: ${larkPkg} [${node_fs.default.existsSync(larkPkg) ? "exists" : "missing"}]`);
11807
11919
  log(` version-check paths: ${feishuPkg} [${node_fs.default.existsSync(feishuPkg) ? "exists" : "missing"}]`);
11808
11920
  return {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lark-apaas/openclaw-scripts-diagnose-cli",
3
- "version": "0.1.18-alpha.1",
3
+ "version": "0.1.18-alpha.3",
4
4
  "description": "CLI for OpenClaw config diagnose and repair with JSON5 support",
5
5
  "main": "dist/index.cjs",
6
6
  "bin": {