@lark-apaas/openclaw-scripts-diagnose-cli 0.1.21-alpha.0 → 0.1.21-alpha.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.cjs +466 -352
  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.21-alpha.0";
55
+ return "0.1.21-alpha.2";
56
56
  }
57
57
  //#endregion
58
58
  //#region src/rule-engine/base.ts
@@ -2639,9 +2639,11 @@ const LARK_PLUGIN_NAME = "openclaw-lark";
2639
2639
  * 版本号自成体系、不在 VERSION_COMPAT_MAP 内,按对标的官方版本判定兼容性。
2640
2640
  */
2641
2641
  const FORK_LARK_PLUGIN_FULL_NAME = "@lark-apaas/openclaw-lark";
2642
+ /** OpenClaw 从该版本起使用内置飞书插件作为目标态,不再安装外置 openclaw-lark。 */
2643
+ const BUILTIN_FEISHU_MIN_OPENCLAW_VERSION = "2026.6.6";
2642
2644
  /**
2643
- * 社区插件:openclaw **内置** 的 `feishu` 插件(非独立扩展)。
2644
- * 与官方/ fork openclaw-lark 互斥,规范化时应禁用以让位 openclaw-lark。
2645
+ * openclaw **内置** 的 `feishu` 插件(非独立扩展)。
2646
+ * OpenClaw 2026.6.6 及之后以该内置能力作为目标态;更早版本仍使用外置 openclaw-lark。
2645
2647
  */
2646
2648
  const BUILTIN_FEISHU_PLUGIN_NAME = "feishu";
2647
2649
  /**
@@ -2821,9 +2823,282 @@ function getAllow$1(config) {
2821
2823
  return allow.filter((e) => typeof e === "string");
2822
2824
  }
2823
2825
  //#endregion
2826
+ //#region src/version-compat.ts
2827
+ const VERSION_COMPAT_MAP = Object.freeze([
2828
+ {
2829
+ openclawLarkVersion: "2026.5.20",
2830
+ minOpenclawVersion: "2026.5.7"
2831
+ },
2832
+ {
2833
+ openclawLarkVersion: "2026.5.13",
2834
+ minOpenclawVersion: "2026.5.7"
2835
+ },
2836
+ {
2837
+ openclawLarkVersion: "2026.5.12",
2838
+ minOpenclawVersion: "2026.5.7"
2839
+ },
2840
+ {
2841
+ openclawLarkVersion: "2026.5.7",
2842
+ minOpenclawVersion: "2026.5.6"
2843
+ },
2844
+ {
2845
+ openclawLarkVersion: "2026.4.10",
2846
+ minOpenclawVersion: "2026.4.27"
2847
+ },
2848
+ {
2849
+ openclawLarkVersion: "2026.4.9",
2850
+ minOpenclawVersion: "2026.3.28"
2851
+ },
2852
+ {
2853
+ openclawLarkVersion: "2026.4.8",
2854
+ minOpenclawVersion: "2026.3.28"
2855
+ },
2856
+ {
2857
+ openclawLarkVersion: "2026.4.7",
2858
+ minOpenclawVersion: "2026.3.28"
2859
+ },
2860
+ {
2861
+ openclawLarkVersion: "2026.4.1",
2862
+ minOpenclawVersion: "2026.3.28"
2863
+ },
2864
+ {
2865
+ openclawLarkVersion: "2026.3.31",
2866
+ minOpenclawVersion: "2026.3.28"
2867
+ },
2868
+ {
2869
+ openclawLarkVersion: "2026.3.30",
2870
+ minOpenclawVersion: "2026.3.28"
2871
+ },
2872
+ {
2873
+ openclawLarkVersion: "2026.3.29",
2874
+ minOpenclawVersion: "2026.3.28"
2875
+ },
2876
+ {
2877
+ openclawLarkVersion: "2026.3.26",
2878
+ minOpenclawVersion: "2026.3.22"
2879
+ },
2880
+ {
2881
+ openclawLarkVersion: "2026.3.25",
2882
+ minOpenclawVersion: "2026.3.22"
2883
+ },
2884
+ {
2885
+ openclawLarkVersion: "2026.3.24",
2886
+ minOpenclawVersion: "2026.3.22"
2887
+ },
2888
+ {
2889
+ openclawLarkVersion: "2026.3.18",
2890
+ minOpenclawVersion: "2026.2.26",
2891
+ maxOpenclawVersion: "2026.3.13"
2892
+ },
2893
+ {
2894
+ openclawLarkVersion: "2026.3.17",
2895
+ minOpenclawVersion: "2026.2.26",
2896
+ maxOpenclawVersion: "2026.3.13"
2897
+ },
2898
+ {
2899
+ openclawLarkVersion: "2026.3.15",
2900
+ minOpenclawVersion: "2026.2.26",
2901
+ maxOpenclawVersion: "2026.3.13"
2902
+ },
2903
+ {
2904
+ openclawLarkVersion: "2026.3.12",
2905
+ minOpenclawVersion: "2026.2.26",
2906
+ maxOpenclawVersion: "2026.3.13"
2907
+ },
2908
+ {
2909
+ openclawLarkVersion: "2026.3.10",
2910
+ minOpenclawVersion: "2026.2.26",
2911
+ maxOpenclawVersion: "2026.3.13"
2912
+ },
2913
+ {
2914
+ openclawLarkVersion: "2026.3.9",
2915
+ minOpenclawVersion: "2026.2.26",
2916
+ maxOpenclawVersion: "2026.3.13"
2917
+ }
2918
+ ]);
2919
+ /**
2920
+ * Coerce a CalVer string ("YYYY.M.D[suffix]") to a clean semver string.
2921
+ * semver.coerce strips non-numeric suffixes (e.g. "-rc.1") and fills in
2922
+ * missing components with 0. Returns "0.0.0" for unparseable input.
2923
+ */
2924
+ function coerceCalVer(v) {
2925
+ return semver.default.coerce(v)?.version ?? "0.0.0";
2926
+ }
2927
+ /**
2928
+ * Compare two CalVer strings ("YYYY.M.D") numerically.
2929
+ * Delegates to semver for correct numeric component ordering —
2930
+ * plain string comparison would give wrong results (e.g. '2026.3.18' < '2026.3.9').
2931
+ * Suffixes like "-rc.1" are stripped via coercion before comparison.
2932
+ */
2933
+ function compareCalVer(a, b) {
2934
+ return semver.default.compare(coerceCalVer(a), coerceCalVer(b));
2935
+ }
2936
+ /** Whether this OpenClaw version should use the built-in feishu plugin target state. */
2937
+ function supportsBuiltinFeishu(openclawVersion) {
2938
+ if (!openclawVersion) return false;
2939
+ return semver.default.gte(coerceCalVer(openclawVersion), coerceCalVer(BUILTIN_FEISHU_MIN_OPENCLAW_VERSION));
2940
+ }
2941
+ /**
2942
+ * 该条目的有效上界——开区间(EXCLUSIVE):openclaw ≥ 此值即不兼容。
2943
+ * undefined = 最高档,无上界。
2944
+ *
2945
+ * 上界一律为开区间,两种来源:
2946
+ * - 有显式 maxOpenclawVersion → 直接用它(它本身就是「首个不兼容的 openclaw 版本」)。
2947
+ * - 无显式 max → 向更高插件版本(表降序,index 更小)扫描,取首个「严格更高 min」的
2948
+ * minOpenclawVersion,即下一不兼容区间的起点。
2949
+ *
2950
+ * 只接受严格更高的 min(跳过相同 min 的同档;跳过更低 min 防止非单调表产生空区间
2951
+ * [min, max) 把本条目永久判为不兼容)。
2952
+ */
2953
+ function inferEffectiveMax(index) {
2954
+ const cur = VERSION_COMPAT_MAP[index];
2955
+ if (cur.maxOpenclawVersion != null) return cur.maxOpenclawVersion;
2956
+ for (let i = index - 1; i >= 0; i--) if (compareCalVer(VERSION_COMPAT_MAP[i].minOpenclawVersion, cur.minOpenclawVersion) > 0) return VERSION_COMPAT_MAP[i].minOpenclawVersion;
2957
+ }
2958
+ /**
2959
+ * 单一真相源:floor 匹配 + 有效上界,得到某插件版本的半开兼容区间 [min, max)(含命中条目)。
2960
+ * evaluateVersionRange / findClosestEntry / effectiveCompatRange 全部基于它,
2961
+ * 避免 floor 匹配与 inferEffectiveMax 在多处重复。max 一律为开区间上界(EXCLUSIVE);
2962
+ * 最高档 max 为 undefined(无上界)。undefined = 版本比全表都旧(无 floor 条目)。
2963
+ */
2964
+ function resolveRange(pluginVersion) {
2965
+ const index = VERSION_COMPAT_MAP.findIndex((e) => compareCalVer(e.openclawLarkVersion, pluginVersion) <= 0);
2966
+ if (index === -1) return void 0;
2967
+ const entry = VERSION_COMPAT_MAP[index];
2968
+ return {
2969
+ entry,
2970
+ min: entry.minOpenclawVersion,
2971
+ max: inferEffectiveMax(index)
2972
+ };
2973
+ }
2974
+ /**
2975
+ * 对一个**已对标到 VERSION_COMPAT_MAP 键**的版本评估其与 openclaw 的兼容性,
2976
+ * 并在不兼容时给出方向。fork 的 pin、legacy 分类等在 `checkPluginCompat` 处理,
2977
+ * 本函数只负责「版本 × openclaw 区间」这一段唯一逻辑。
2978
+ *
2979
+ * - index === -1(插件版本比全表都旧,无 floor 条目)→ 无区间可判,视为插件过旧 → 'lark'
2980
+ * - oc < entry.minOpenclawVersion → 'openclaw'
2981
+ * - oc ≥ 有效上界(开区间)→ 'lark'
2982
+ * - 否则兼容
2983
+ *
2984
+ * 区间统一为半开 [min, max):min 闭、max 开(显式与推断上界一律 EXCLUSIVE)。
2985
+ */
2986
+ function evaluateVersionRange(pluginVersion, openclawVersion) {
2987
+ const r = resolveRange(pluginVersion);
2988
+ if (!r) return {
2989
+ compatible: false,
2990
+ direction: "lark",
2991
+ entry: void 0
2992
+ };
2993
+ const oc = coerceCalVer(openclawVersion);
2994
+ if (semver.default.lt(oc, coerceCalVer(r.min))) return {
2995
+ compatible: false,
2996
+ direction: "openclaw",
2997
+ entry: r.entry
2998
+ };
2999
+ if (r.max !== void 0 && semver.default.gte(oc, coerceCalVer(r.max))) return {
3000
+ compatible: false,
3001
+ direction: "lark",
3002
+ entry: r.entry
3003
+ };
3004
+ return {
3005
+ compatible: true,
3006
+ direction: null,
3007
+ entry: r.entry
3008
+ };
3009
+ }
3010
+ /**
3011
+ * Fork plugin handling.
3012
+ *
3013
+ * `@lark-apaas/openclaw-lark` is an internal fork whose own version numbers
3014
+ * (e.g. 2026.4.3, 2026.4.4) are NOT keys in VERSION_COMPAT_MAP. Floor-matching
3015
+ * a fork version against the official-keyed map only works by coincidence of
3016
+ * numbering and breaks once the fork version drifts past an official entry.
3017
+ * Both the install-time compat check and the diagnose rule must therefore pin
3018
+ * the fork to its official-equivalent version before consulting the map.
3019
+ */
3020
+ /** fork 对标的官方 openclaw-lark 版本(与 VERSION_COMPAT_MAP 强耦合,故留在此处)。 */
3021
+ const FORK_LARK_PLUGIN_PINNED_VERSION = "2026.4.1";
3022
+ /**
3023
+ * 返回某插件版本(floor 匹配后)的有效兼容区间 [min, max),含 evaluateVersionRange 所用的
3024
+ * (显式或推断的)开区间上界——区别于仅暴露显式 maxOpenclawVersion 的 findClosestEntry。
3025
+ * undefined = 版本比全表都旧(无 floor 条目)。
3026
+ */
3027
+ function effectiveCompatRange(pluginVersion) {
3028
+ const r = resolveRange(pluginVersion);
3029
+ return r ? {
3030
+ min: r.min,
3031
+ max: r.max
3032
+ } : void 0;
3033
+ }
3034
+ /** "@scope/name" → "name";无 scope 原样返回。 */
3035
+ function extractBaseName(name) {
3036
+ if (!name) return void 0;
3037
+ return name.startsWith("@") ? name.split("/")[1] : name;
3038
+ }
3039
+ function reasonFromRange(r) {
3040
+ if (r.compatible) return "compatible";
3041
+ if (r.direction === "openclaw") return "oc-below-min";
3042
+ return r.entry ? "oc-above-max" : "version-not-in-map";
3043
+ }
3044
+ /**
3045
+ * 统一的飞书插件兼容判定入口——唯一的判定真相源。
3046
+ *
3047
+ * 入参只有三个:插件包名、插件版本、当前 openclaw 版本(**不涉及 recommendedOpenclawTag**,
3048
+ * 推荐版本的处理交由各业务点)。判定算法:
3049
+ *
3050
+ * 1. fork `@lark-apaas/openclaw-lark`:先 pin 到对标官方版本(FORK_LARK_PLUGIN_PINNED_VERSION)
3051
+ * 再按区间判定上下界。
3052
+ * 2. 其他 `@lark-apaas/*` fork:无条件豁免(兼容)。
3053
+ * 3. legacy `feishu-openclaw-plugin`(含带 scope 形式):一律不兼容,方向 = 换飞书插件。
3054
+ * 4. 官方版及其余包:用自身版本按 VERSION_COMPAT_MAP 区间判定;读不到版本号 → 不兼容(换插件)。
3055
+ *
3056
+ * 所有消费方(install-extension 预检、feishu_plugin_version_compat 两条规则、needsLarkUpgrade)
3057
+ * 都应消费本方法,不再各自实现兼容/方向逻辑,避免口径漂移。
3058
+ */
3059
+ function checkPluginCompat(packageName, pluginVersion, openclawVersion) {
3060
+ if (packageName === "@lark-apaas/openclaw-lark") {
3061
+ const resolvedVersion = FORK_LARK_PLUGIN_PINNED_VERSION;
3062
+ const r = evaluateVersionRange(resolvedVersion, openclawVersion);
3063
+ return {
3064
+ ...r,
3065
+ reason: reasonFromRange(r),
3066
+ resolvedVersion
3067
+ };
3068
+ }
3069
+ if ((packageName && packageName.startsWith("@") ? packageName.split("/")[0] : void 0) === "@lark-apaas") return {
3070
+ compatible: true,
3071
+ direction: null,
3072
+ reason: "fork-exempt",
3073
+ resolvedVersion: pluginVersion,
3074
+ entry: void 0
3075
+ };
3076
+ if (extractBaseName(packageName) === "feishu-openclaw-plugin") return {
3077
+ compatible: false,
3078
+ direction: "lark",
3079
+ reason: "legacy",
3080
+ resolvedVersion: pluginVersion,
3081
+ entry: void 0
3082
+ };
3083
+ if (!pluginVersion) return {
3084
+ compatible: false,
3085
+ direction: "lark",
3086
+ reason: "version-unknown",
3087
+ resolvedVersion: void 0,
3088
+ entry: void 0
3089
+ };
3090
+ const r = evaluateVersionRange(pluginVersion, openclawVersion);
3091
+ return {
3092
+ ...r,
3093
+ reason: reasonFromRange(r),
3094
+ resolvedVersion: pluginVersion
3095
+ };
3096
+ }
3097
+ //#endregion
2824
3098
  //#region src/rules/lark-plugin-allow.ts
2825
3099
  let LarkPluginAllowRule = class LarkPluginAllowRule extends DiagnoseRule {
2826
3100
  validate(ctx) {
3101
+ if (supportsBuiltinFeishu(readOpenclawRuntimeVersion())) return { pass: true };
2827
3102
  const allow = getAllow(ctx.config);
2828
3103
  if (LARK_PLUGIN_DIR_NAMES.some((name) => allow.includes(name))) return { pass: true };
2829
3104
  const installed = detectInstalledLarkPlugin$1(getExtensionsDir(ctx.configPath));
@@ -2951,7 +3226,8 @@ OldMiaodaPluginsCleanupRule = __decorate([Rule({
2951
3226
  //#region src/rules/builtin-plugin-missing.ts
2952
3227
  function findMissingBuiltinPlugins(ctx) {
2953
3228
  const extDir = getExtensionsDir(ctx.configPath);
2954
- return [...OFFICIAL_EXTENSION_PLUGIN_NAMES].filter((name) => !isPluginInstalledOnDisk(extDir, name)).sort();
3229
+ const supportsBuiltin = supportsBuiltinFeishu(readOpenclawRuntimeVersion());
3230
+ return [...OFFICIAL_EXTENSION_PLUGIN_NAMES].filter((name) => !(supportsBuiltin && name === "openclaw-lark")).filter((name) => !isPluginInstalledOnDisk(extDir, name)).sort();
2955
3231
  }
2956
3232
  let BuiltinPluginMissingRule = class BuiltinPluginMissingRule extends DiagnoseRule {
2957
3233
  validate(ctx) {
@@ -2966,7 +3242,7 @@ let BuiltinPluginMissingRule = class BuiltinPluginMissingRule extends DiagnoseRu
2966
3242
  };
2967
3243
  BuiltinPluginMissingRule = __decorate([Rule({
2968
3244
  key: "builtin_plugin_missing",
2969
- description: "检查所有内置扩展插件(openclaw-lark、openclaw-extension-miaoda 等)是否已在磁盘安装;缺失时提示重新安装(实验性)",
3245
+ description: "检查当前 OpenClaw 版本需要的内置扩展插件是否已在磁盘安装;缺失时提示重新安装(实验性)",
2970
3246
  dependsOn: ["config_syntax_check"],
2971
3247
  repairMode: "check-only",
2972
3248
  level: "critical",
@@ -3274,6 +3550,14 @@ const FEISHU_TOOLS = Object.freeze([
3274
3550
  */
3275
3551
  let FeishuPluginStateNormalizeRule = class FeishuPluginStateNormalizeRule extends DiagnoseRule {
3276
3552
  validate(ctx) {
3553
+ if (isBuiltinFeishuTarget()) {
3554
+ const residuals = findExternalLarkResiduals(ctx);
3555
+ if (residuals.length === 0) return { pass: true };
3556
+ return {
3557
+ pass: false,
3558
+ message: `外置飞书插件残留:${residuals.join(", ")}`
3559
+ };
3560
+ }
3277
3561
  if (!isPluginInstalled(ctx)) return { pass: true };
3278
3562
  const fails = [];
3279
3563
  if (!isNewPluginEnabled(ctx.config)) fails.push(`plugins.entries["${LARK_PLUGIN_NAME}"].enabled !== true(应启用)`);
@@ -3289,6 +3573,10 @@ let FeishuPluginStateNormalizeRule = class FeishuPluginStateNormalizeRule extend
3289
3573
  };
3290
3574
  }
3291
3575
  repair(ctx) {
3576
+ if (isBuiltinFeishuTarget()) {
3577
+ cleanupExternalLarkResiduals(ctx);
3578
+ return;
3579
+ }
3292
3580
  setEntryEnabled(ctx.config, LARK_PLUGIN_NAME, true);
3293
3581
  setEntryEnabled(ctx.config, BUILTIN_FEISHU_PLUGIN_NAME, false);
3294
3582
  ensureFeishuTools(ctx.config);
@@ -3302,367 +3590,143 @@ FeishuPluginStateNormalizeRule = __decorate([Rule({
3302
3590
  repairMode: "standard",
3303
3591
  level: "critical"
3304
3592
  })], FeishuPluginStateNormalizeRule);
3593
+ function isBuiltinFeishuTarget() {
3594
+ return supportsBuiltinFeishu(readOpenclawRuntimeVersion());
3595
+ }
3305
3596
  function isPluginInstalled(ctx) {
3306
3597
  return node_fs.default.existsSync(node_path.default.join(getExtensionsDir(ctx.configPath), LARK_PLUGIN_NAME));
3307
3598
  }
3308
- function isNewPluginEnabled(config) {
3309
- return asRecord(getNestedMap(config, "plugins", "entries")?.[LARK_PLUGIN_NAME])?.enabled === true;
3310
- }
3311
- function isBuiltinFeishuEnabled(config) {
3312
- return asRecord(getNestedMap(config, "plugins", "entries")?.[BUILTIN_FEISHU_PLUGIN_NAME])?.enabled === true;
3313
- }
3314
- /** 读取顶层 tools.<key>(allow / alsoAllow)的字符串数组。 */
3315
- function readTopLevelToolsList(config, key) {
3316
- const arr = asRecord(asRecord(config)?.tools)?.[key];
3317
- if (!Array.isArray(arr)) return [];
3318
- return arr.filter((e) => typeof e === "string");
3319
- }
3320
- function hasFeishuTool(list) {
3321
- if (list.includes("*")) return true;
3322
- return list.some((t) => FEISHU_TOOLS.includes(t));
3323
- }
3324
- /**
3325
- * 决定 feishu_* 应补充到哪个授权键:
3326
- * - 已有非空 tools.allow(限制式白名单)→ 'allow'(合并进 allow,避免与 alsoAllow 冲突)
3327
- * - 否则 → 'alsoAllow'(追加式,叠加在 profile 基线上)
3328
- * openclaw schema 禁止同一 scope 同时设 allow + alsoAllow,故有 allow 时必须并入 allow,
3329
- * 否则会触发 tools_allow_also_allow_conflict 规则反复来回改写。
3330
- */
3331
- function feishuGrantTarget(config) {
3332
- return readTopLevelToolsList(config, "allow").length > 0 ? "allow" : "alsoAllow";
3333
- }
3334
- /**
3335
- * 仅看顶层 tools——agent 级授权是用户对单 agent 的精细化配置,doctor 不动。
3336
- * 返回缺失的目标键名('allow' / 'alsoAllow'),不缺时返回 null。
3337
- *
3338
- * 不关心 lark-cli:tools.alsoAllow(授权层)与 channels.feishu.tools.deny(插件注册层)
3339
- * 是两个正交的 config key,可并存——授权一个被 deny 的工具是无害空操作。因此本规则始终
3340
- * 补全 FEISHU_TOOLS(让 openclaw-lark 实际注册的工具都可用),重叠工具的"不可用"由独立的
3341
- * deny 规则在注册层处理,互不干扰。
3342
- */
3343
- function missingFeishuToolsGrant(config) {
3344
- const target = feishuGrantTarget(config);
3345
- return hasFeishuTool(readTopLevelToolsList(config, target)) ? null : target;
3346
- }
3347
- function findLegacyResiduals(ctx) {
3348
- const found = [];
3349
- const plugins = asRecord(ctx.config.plugins);
3350
- if (asRecord(plugins?.entries)?.["feishu-openclaw-plugin"] != null) found.push("entries[legacy]");
3351
- const allow = plugins?.allow;
3352
- if (Array.isArray(allow) && allow.includes("feishu-openclaw-plugin")) found.push("allow[legacy]");
3353
- if (asRecord(getPluginInstallsMap(ctx.configPath, ctx.config)["feishu-openclaw-plugin"]) != null) found.push("installs[legacy]");
3354
- const extDir = getExtensionsDir(ctx.configPath);
3355
- for (const name of LEGACY_DIRS_TO_REMOVE) if (node_fs.default.existsSync(node_path.default.join(extDir, name))) found.push(`fs/${name}`);
3356
- return found;
3357
- }
3358
- function setEntryEnabled(config, key, enabled) {
3359
- const entries = ensureRecord(ensureRecord(config, "plugins"), "entries");
3360
- entries[key] = {
3361
- ...asRecord(entries[key]) ?? {},
3362
- enabled
3363
- };
3364
- }
3365
- function ensureFeishuTools(config) {
3366
- const target = feishuGrantTarget(config);
3367
- const current = readTopLevelToolsList(config, target);
3368
- if (hasFeishuTool(current)) return;
3369
- ensureRecord(config, "tools")[target] = [...new Set([...current, ...FEISHU_TOOLS])];
3370
- }
3371
- function cleanupLegacyResiduals(ctx) {
3372
- const plugins = asRecord(ctx.config.plugins);
3373
- if (plugins) {
3374
- const entries = asRecord(plugins.entries);
3375
- if (entries && "feishu-openclaw-plugin" in entries) delete entries[LEGACY_LARK_PLUGIN_NAME];
3376
- const allow = plugins.allow;
3377
- if (Array.isArray(allow)) {
3378
- for (let i = allow.length - 1; i >= 0; i--) if (allow[i] === "feishu-openclaw-plugin") allow.splice(i, 1);
3379
- if (!allow.includes("openclaw-lark")) allow.push(LARK_PLUGIN_NAME);
3380
- }
3381
- }
3382
- const installs = { ...getPluginInstallsMap(ctx.configPath, ctx.config) };
3383
- if ("feishu-openclaw-plugin" in installs) {
3384
- delete installs[LEGACY_LARK_PLUGIN_NAME];
3385
- setPluginInstallsMap(ctx.configPath, ctx.config, installs);
3386
- }
3387
- const extDir = getExtensionsDir(ctx.configPath);
3388
- for (const name of LEGACY_DIRS_TO_REMOVE) {
3389
- const target = node_path.default.join(extDir, name);
3390
- const rel = node_path.default.relative(extDir, target);
3391
- if (!rel || rel.startsWith("..") || node_path.default.isAbsolute(rel)) continue;
3392
- try {
3393
- rmrfTolerant(target);
3394
- } catch (e) {
3395
- console.error(`[feishu_plugin_state_normalize] rmrf ${target} failed: ${e.message}`);
3396
- }
3397
- }
3398
- }
3399
- //#endregion
3400
- //#region src/version-compat.ts
3401
- const VERSION_COMPAT_MAP = Object.freeze([
3402
- {
3403
- openclawLarkVersion: "2026.5.20",
3404
- minOpenclawVersion: "2026.5.7"
3405
- },
3406
- {
3407
- openclawLarkVersion: "2026.5.13",
3408
- minOpenclawVersion: "2026.5.7"
3409
- },
3410
- {
3411
- openclawLarkVersion: "2026.5.12",
3412
- minOpenclawVersion: "2026.5.7"
3413
- },
3414
- {
3415
- openclawLarkVersion: "2026.5.7",
3416
- minOpenclawVersion: "2026.5.6"
3417
- },
3418
- {
3419
- openclawLarkVersion: "2026.4.10",
3420
- minOpenclawVersion: "2026.4.27"
3421
- },
3422
- {
3423
- openclawLarkVersion: "2026.4.9",
3424
- minOpenclawVersion: "2026.3.28"
3425
- },
3426
- {
3427
- openclawLarkVersion: "2026.4.8",
3428
- minOpenclawVersion: "2026.3.28"
3429
- },
3430
- {
3431
- openclawLarkVersion: "2026.4.7",
3432
- minOpenclawVersion: "2026.3.28"
3433
- },
3434
- {
3435
- openclawLarkVersion: "2026.4.1",
3436
- minOpenclawVersion: "2026.3.28"
3437
- },
3438
- {
3439
- openclawLarkVersion: "2026.3.31",
3440
- minOpenclawVersion: "2026.3.28"
3441
- },
3442
- {
3443
- openclawLarkVersion: "2026.3.30",
3444
- minOpenclawVersion: "2026.3.28"
3445
- },
3446
- {
3447
- openclawLarkVersion: "2026.3.29",
3448
- minOpenclawVersion: "2026.3.28"
3449
- },
3450
- {
3451
- openclawLarkVersion: "2026.3.26",
3452
- minOpenclawVersion: "2026.3.22"
3453
- },
3454
- {
3455
- openclawLarkVersion: "2026.3.25",
3456
- minOpenclawVersion: "2026.3.22"
3457
- },
3458
- {
3459
- openclawLarkVersion: "2026.3.24",
3460
- minOpenclawVersion: "2026.3.22"
3461
- },
3462
- {
3463
- openclawLarkVersion: "2026.3.18",
3464
- minOpenclawVersion: "2026.2.26",
3465
- maxOpenclawVersion: "2026.3.13"
3466
- },
3467
- {
3468
- openclawLarkVersion: "2026.3.17",
3469
- minOpenclawVersion: "2026.2.26",
3470
- maxOpenclawVersion: "2026.3.13"
3471
- },
3472
- {
3473
- openclawLarkVersion: "2026.3.15",
3474
- minOpenclawVersion: "2026.2.26",
3475
- maxOpenclawVersion: "2026.3.13"
3476
- },
3477
- {
3478
- openclawLarkVersion: "2026.3.12",
3479
- minOpenclawVersion: "2026.2.26",
3480
- maxOpenclawVersion: "2026.3.13"
3481
- },
3482
- {
3483
- openclawLarkVersion: "2026.3.10",
3484
- minOpenclawVersion: "2026.2.26",
3485
- maxOpenclawVersion: "2026.3.13"
3486
- },
3487
- {
3488
- openclawLarkVersion: "2026.3.9",
3489
- minOpenclawVersion: "2026.2.26",
3490
- maxOpenclawVersion: "2026.3.13"
3491
- }
3492
- ]);
3493
- /**
3494
- * Coerce a CalVer string ("YYYY.M.D[suffix]") to a clean semver string.
3495
- * semver.coerce strips non-numeric suffixes (e.g. "-rc.1") and fills in
3496
- * missing components with 0. Returns "0.0.0" for unparseable input.
3497
- */
3498
- function coerceCalVer(v) {
3499
- return semver.default.coerce(v)?.version ?? "0.0.0";
3500
- }
3501
- /**
3502
- * Compare two CalVer strings ("YYYY.M.D") numerically.
3503
- * Delegates to semver for correct numeric component ordering —
3504
- * plain string comparison would give wrong results (e.g. '2026.3.18' < '2026.3.9').
3505
- * Suffixes like "-rc.1" are stripped via coercion before comparison.
3506
- */
3507
- function compareCalVer(a, b) {
3508
- return semver.default.compare(coerceCalVer(a), coerceCalVer(b));
3509
- }
3510
- /**
3511
- * 该条目的有效上界——开区间(EXCLUSIVE):openclaw ≥ 此值即不兼容。
3512
- * undefined = 最高档,无上界。
3513
- *
3514
- * 上界一律为开区间,两种来源:
3515
- * - 有显式 maxOpenclawVersion → 直接用它(它本身就是「首个不兼容的 openclaw 版本」)。
3516
- * - 无显式 max → 向更高插件版本(表降序,index 更小)扫描,取首个「严格更高 min」的
3517
- * minOpenclawVersion,即下一不兼容区间的起点。
3518
- *
3519
- * 只接受严格更高的 min(跳过相同 min 的同档;跳过更低 min 防止非单调表产生空区间
3520
- * [min, max) 把本条目永久判为不兼容)。
3521
- */
3522
- function inferEffectiveMax(index) {
3523
- const cur = VERSION_COMPAT_MAP[index];
3524
- if (cur.maxOpenclawVersion != null) return cur.maxOpenclawVersion;
3525
- for (let i = index - 1; i >= 0; i--) if (compareCalVer(VERSION_COMPAT_MAP[i].minOpenclawVersion, cur.minOpenclawVersion) > 0) return VERSION_COMPAT_MAP[i].minOpenclawVersion;
3526
- }
3527
- /**
3528
- * 单一真相源:floor 匹配 + 有效上界,得到某插件版本的半开兼容区间 [min, max)(含命中条目)。
3529
- * evaluateVersionRange / findClosestEntry / effectiveCompatRange 全部基于它,
3530
- * 避免 floor 匹配与 inferEffectiveMax 在多处重复。max 一律为开区间上界(EXCLUSIVE);
3531
- * 最高档 max 为 undefined(无上界)。undefined = 版本比全表都旧(无 floor 条目)。
3532
- */
3533
- function resolveRange(pluginVersion) {
3534
- const index = VERSION_COMPAT_MAP.findIndex((e) => compareCalVer(e.openclawLarkVersion, pluginVersion) <= 0);
3535
- if (index === -1) return void 0;
3536
- const entry = VERSION_COMPAT_MAP[index];
3537
- return {
3538
- entry,
3539
- min: entry.minOpenclawVersion,
3540
- max: inferEffectiveMax(index)
3541
- };
3599
+ function isNewPluginEnabled(config) {
3600
+ return asRecord(getNestedMap(config, "plugins", "entries")?.[LARK_PLUGIN_NAME])?.enabled === true;
3601
+ }
3602
+ function isBuiltinFeishuEnabled(config) {
3603
+ return asRecord(getNestedMap(config, "plugins", "entries")?.[BUILTIN_FEISHU_PLUGIN_NAME])?.enabled === true;
3604
+ }
3605
+ /** 读取顶层 tools.<key>(allow / alsoAllow)的字符串数组。 */
3606
+ function readTopLevelToolsList(config, key) {
3607
+ const arr = asRecord(asRecord(config)?.tools)?.[key];
3608
+ if (!Array.isArray(arr)) return [];
3609
+ return arr.filter((e) => typeof e === "string");
3610
+ }
3611
+ function hasFeishuTool(list) {
3612
+ if (list.includes("*")) return true;
3613
+ return list.some((t) => FEISHU_TOOLS.includes(t));
3542
3614
  }
3543
3615
  /**
3544
- * 对一个**已对标到 VERSION_COMPAT_MAP 键**的版本评估其与 openclaw 的兼容性,
3545
- * 并在不兼容时给出方向。fork pin、legacy 分类等在 `checkPluginCompat` 处理,
3546
- * 本函数只负责「版本 × openclaw 区间」这一段唯一逻辑。
3547
- *
3548
- * - index === -1(插件版本比全表都旧,无 floor 条目)→ 无区间可判,视为插件过旧 → 'lark'
3549
- * - oc < entry.minOpenclawVersion → 'openclaw'
3550
- * - oc ≥ 有效上界(开区间)→ 'lark'
3551
- * - 否则兼容
3552
- *
3553
- * 区间统一为半开 [min, max):min 闭、max 开(显式与推断上界一律 EXCLUSIVE)。
3616
+ * 决定 feishu_* 应补充到哪个授权键:
3617
+ * - 已有非空 tools.allow(限制式白名单)→ 'allow'(合并进 allow,避免与 alsoAllow 冲突)
3618
+ * - 否则 'alsoAllow'(追加式,叠加在 profile 基线上)
3619
+ * openclaw schema 禁止同一 scope 同时设 allow + alsoAllow,故有 allow 时必须并入 allow,
3620
+ * 否则会触发 tools_allow_also_allow_conflict 规则反复来回改写。
3554
3621
  */
3555
- function evaluateVersionRange(pluginVersion, openclawVersion) {
3556
- const r = resolveRange(pluginVersion);
3557
- if (!r) return {
3558
- compatible: false,
3559
- direction: "lark",
3560
- entry: void 0
3561
- };
3562
- const oc = coerceCalVer(openclawVersion);
3563
- if (semver.default.lt(oc, coerceCalVer(r.min))) return {
3564
- compatible: false,
3565
- direction: "openclaw",
3566
- entry: r.entry
3567
- };
3568
- if (r.max !== void 0 && semver.default.gte(oc, coerceCalVer(r.max))) return {
3569
- compatible: false,
3570
- direction: "lark",
3571
- entry: r.entry
3572
- };
3573
- return {
3574
- compatible: true,
3575
- direction: null,
3576
- entry: r.entry
3577
- };
3622
+ function feishuGrantTarget(config) {
3623
+ return readTopLevelToolsList(config, "allow").length > 0 ? "allow" : "alsoAllow";
3578
3624
  }
3579
3625
  /**
3580
- * Fork plugin handling.
3626
+ * 仅看顶层 tools——agent 级授权是用户对单 agent 的精细化配置,doctor 不动。
3627
+ * 返回缺失的目标键名('allow' / 'alsoAllow'),不缺时返回 null。
3581
3628
  *
3582
- * `@lark-apaas/openclaw-lark` is an internal fork whose own version numbers
3583
- * (e.g. 2026.4.3, 2026.4.4) are NOT keys in VERSION_COMPAT_MAP. Floor-matching
3584
- * a fork version against the official-keyed map only works by coincidence of
3585
- * numbering and breaks once the fork version drifts past an official entry.
3586
- * Both the install-time compat check and the diagnose rule must therefore pin
3587
- * the fork to its official-equivalent version before consulting the map.
3588
- */
3589
- /** fork 对标的官方 openclaw-lark 版本(与 VERSION_COMPAT_MAP 强耦合,故留在此处)。 */
3590
- const FORK_LARK_PLUGIN_PINNED_VERSION = "2026.4.1";
3591
- /**
3592
- * 返回某插件版本(floor 匹配后)的有效兼容区间 [min, max),含 evaluateVersionRange 所用的
3593
- * (显式或推断的)开区间上界——区别于仅暴露显式 maxOpenclawVersion 的 findClosestEntry。
3594
- * undefined = 版本比全表都旧(无 floor 条目)。
3629
+ * 不关心 lark-cli:tools.alsoAllow(授权层)与 channels.feishu.tools.deny(插件注册层)
3630
+ * 是两个正交的 config key,可并存——授权一个被 deny 的工具是无害空操作。因此本规则始终
3631
+ * 补全 FEISHU_TOOLS(让 openclaw-lark 实际注册的工具都可用),重叠工具的"不可用"由独立的
3632
+ * deny 规则在注册层处理,互不干扰。
3595
3633
  */
3596
- function effectiveCompatRange(pluginVersion) {
3597
- const r = resolveRange(pluginVersion);
3598
- return r ? {
3599
- min: r.min,
3600
- max: r.max
3601
- } : void 0;
3602
- }
3603
- /** "@scope/name" → "name";无 scope 原样返回。 */
3604
- function extractBaseName(name) {
3605
- if (!name) return void 0;
3606
- return name.startsWith("@") ? name.split("/")[1] : name;
3634
+ function missingFeishuToolsGrant(config) {
3635
+ const target = feishuGrantTarget(config);
3636
+ return hasFeishuTool(readTopLevelToolsList(config, target)) ? null : target;
3607
3637
  }
3608
- function reasonFromRange(r) {
3609
- if (r.compatible) return "compatible";
3610
- if (r.direction === "openclaw") return "oc-below-min";
3611
- return r.entry ? "oc-above-max" : "version-not-in-map";
3638
+ function findLegacyResiduals(ctx) {
3639
+ const found = [];
3640
+ const plugins = asRecord(ctx.config.plugins);
3641
+ if (asRecord(plugins?.entries)?.["feishu-openclaw-plugin"] != null) found.push("entries[legacy]");
3642
+ const allow = plugins?.allow;
3643
+ if (Array.isArray(allow) && allow.includes("feishu-openclaw-plugin")) found.push("allow[legacy]");
3644
+ if (asRecord(getPluginInstallsMap(ctx.configPath, ctx.config)["feishu-openclaw-plugin"]) != null) found.push("installs[legacy]");
3645
+ const extDir = getExtensionsDir(ctx.configPath);
3646
+ for (const name of LEGACY_DIRS_TO_REMOVE) if (node_fs.default.existsSync(node_path.default.join(extDir, name))) found.push(`fs/${name}`);
3647
+ return found;
3612
3648
  }
3613
- /**
3614
- * 统一的飞书插件兼容判定入口——唯一的判定真相源。
3615
- *
3616
- * 入参只有三个:插件包名、插件版本、当前 openclaw 版本(**不涉及 recommendedOpenclawTag**,
3617
- * 推荐版本的处理交由各业务点)。判定算法:
3618
- *
3619
- * 1. fork `@lark-apaas/openclaw-lark`:先 pin 到对标官方版本(FORK_LARK_PLUGIN_PINNED_VERSION)
3620
- * 再按区间判定上下界。
3621
- * 2. 其他 `@lark-apaas/*` fork:无条件豁免(兼容)。
3622
- * 3. legacy `feishu-openclaw-plugin`(含带 scope 形式):一律不兼容,方向 = 换飞书插件。
3623
- * 4. 官方版及其余包:用自身版本按 VERSION_COMPAT_MAP 区间判定;读不到版本号 → 不兼容(换插件)。
3624
- *
3625
- * 所有消费方(install-extension 预检、feishu_plugin_version_compat 两条规则、needsLarkUpgrade)
3626
- * 都应消费本方法,不再各自实现兼容/方向逻辑,避免口径漂移。
3627
- */
3628
- function checkPluginCompat(packageName, pluginVersion, openclawVersion) {
3629
- if (packageName === "@lark-apaas/openclaw-lark") {
3630
- const resolvedVersion = FORK_LARK_PLUGIN_PINNED_VERSION;
3631
- const r = evaluateVersionRange(resolvedVersion, openclawVersion);
3632
- return {
3633
- ...r,
3634
- reason: reasonFromRange(r),
3635
- resolvedVersion
3636
- };
3649
+ function findExternalLarkResiduals(ctx) {
3650
+ const found = [];
3651
+ const plugins = asRecord(ctx.config.plugins);
3652
+ const entries = asRecord(plugins?.entries);
3653
+ const installs = getPluginInstallsMap(ctx.configPath, ctx.config);
3654
+ const allow = plugins?.allow;
3655
+ for (const name of [LARK_PLUGIN_NAME, LEGACY_LARK_PLUGIN_NAME]) {
3656
+ if (entries?.[name] != null) found.push(`entries[${name}]`);
3657
+ if (Array.isArray(allow) && allow.includes(name)) found.push(`allow[${name}]`);
3658
+ if (asRecord(installs[name]) != null) found.push(`installs[${name}]`);
3659
+ if (node_fs.default.existsSync(node_path.default.join(getExtensionsDir(ctx.configPath), name))) found.push(`fs/${name}`);
3637
3660
  }
3638
- if ((packageName && packageName.startsWith("@") ? packageName.split("/")[0] : void 0) === "@lark-apaas") return {
3639
- compatible: true,
3640
- direction: null,
3641
- reason: "fork-exempt",
3642
- resolvedVersion: pluginVersion,
3643
- entry: void 0
3644
- };
3645
- if (extractBaseName(packageName) === "feishu-openclaw-plugin") return {
3646
- compatible: false,
3647
- direction: "lark",
3648
- reason: "legacy",
3649
- resolvedVersion: pluginVersion,
3650
- entry: void 0
3651
- };
3652
- if (!pluginVersion) return {
3653
- compatible: false,
3654
- direction: "lark",
3655
- reason: "version-unknown",
3656
- resolvedVersion: void 0,
3657
- entry: void 0
3658
- };
3659
- const r = evaluateVersionRange(pluginVersion, openclawVersion);
3660
- return {
3661
- ...r,
3662
- reason: reasonFromRange(r),
3663
- resolvedVersion: pluginVersion
3661
+ return found;
3662
+ }
3663
+ function setEntryEnabled(config, key, enabled) {
3664
+ const entries = ensureRecord(ensureRecord(config, "plugins"), "entries");
3665
+ entries[key] = {
3666
+ ...asRecord(entries[key]) ?? {},
3667
+ enabled
3664
3668
  };
3665
3669
  }
3670
+ function ensureFeishuTools(config) {
3671
+ const target = feishuGrantTarget(config);
3672
+ const current = readTopLevelToolsList(config, target);
3673
+ if (hasFeishuTool(current)) return;
3674
+ ensureRecord(config, "tools")[target] = [...new Set([...current, ...FEISHU_TOOLS])];
3675
+ }
3676
+ function cleanupLegacyResiduals(ctx) {
3677
+ const plugins = asRecord(ctx.config.plugins);
3678
+ if (plugins) {
3679
+ const entries = asRecord(plugins.entries);
3680
+ if (entries && "feishu-openclaw-plugin" in entries) delete entries[LEGACY_LARK_PLUGIN_NAME];
3681
+ const allow = plugins.allow;
3682
+ if (Array.isArray(allow)) {
3683
+ for (let i = allow.length - 1; i >= 0; i--) if (allow[i] === "feishu-openclaw-plugin") allow.splice(i, 1);
3684
+ if (!allow.includes("openclaw-lark")) allow.push(LARK_PLUGIN_NAME);
3685
+ }
3686
+ }
3687
+ const installs = { ...getPluginInstallsMap(ctx.configPath, ctx.config) };
3688
+ if ("feishu-openclaw-plugin" in installs) {
3689
+ delete installs[LEGACY_LARK_PLUGIN_NAME];
3690
+ setPluginInstallsMap(ctx.configPath, ctx.config, installs);
3691
+ }
3692
+ const extDir = getExtensionsDir(ctx.configPath);
3693
+ for (const name of LEGACY_DIRS_TO_REMOVE) {
3694
+ const target = node_path.default.join(extDir, name);
3695
+ const rel = node_path.default.relative(extDir, target);
3696
+ if (!rel || rel.startsWith("..") || node_path.default.isAbsolute(rel)) continue;
3697
+ try {
3698
+ rmrfTolerant(target);
3699
+ } catch (e) {
3700
+ console.error(`[feishu_plugin_state_normalize] rmrf ${target} failed: ${e.message}`);
3701
+ }
3702
+ }
3703
+ }
3704
+ function cleanupExternalLarkResiduals(ctx) {
3705
+ const externalNames = new Set([LARK_PLUGIN_NAME, LEGACY_LARK_PLUGIN_NAME]);
3706
+ const plugins = asRecord(ctx.config.plugins);
3707
+ if (plugins) {
3708
+ const entries = asRecord(plugins.entries);
3709
+ if (entries) for (const name of externalNames) delete entries[name];
3710
+ const allow = plugins.allow;
3711
+ if (Array.isArray(allow)) {
3712
+ for (let i = allow.length - 1; i >= 0; i--) if (externalNames.has(String(allow[i]))) allow.splice(i, 1);
3713
+ }
3714
+ }
3715
+ const installs = { ...getPluginInstallsMap(ctx.configPath, ctx.config) };
3716
+ for (const name of externalNames) delete installs[name];
3717
+ setPluginInstallsMap(ctx.configPath, ctx.config, installs);
3718
+ const extDir = getExtensionsDir(ctx.configPath);
3719
+ for (const name of externalNames) {
3720
+ const target = node_path.default.join(extDir, name);
3721
+ const rel = node_path.default.relative(extDir, target);
3722
+ if (!rel || rel.startsWith("..") || node_path.default.isAbsolute(rel)) continue;
3723
+ try {
3724
+ rmrfTolerant(target);
3725
+ } catch (e) {
3726
+ console.error(`[feishu_plugin_state_normalize] rmrf ${target} failed: ${e.message}`);
3727
+ }
3728
+ }
3729
+ }
3666
3730
  //#endregion
3667
3731
  //#region src/rules/feishu-plugin-version-compat.ts
3668
3732
  const LEGACY_SHORT_NAMES = [LEGACY_LARK_PLUGIN_NAME];
@@ -5610,6 +5674,21 @@ async function installExtension(tag, ossFileMap, opts = {}) {
5610
5674
  }
5611
5675
  console.error(`[install-extension] tag=${tag} targets=${targets.length}`);
5612
5676
  const t0 = Date.now();
5677
+ const targetOpenclawVersion = resolveInstallTargetOpenclawVersion(tag);
5678
+ if (supportsBuiltinFeishu(targetOpenclawVersion) && targets.some((p) => p.name === "openclaw-lark")) {
5679
+ const configPath = opts.configPath ?? node_path.default.join(homeBase, DEFAULT_CONFIG_REL);
5680
+ console.error(`[install-extension] openclaw@${targetOpenclawVersion} uses built-in feishu — skipping external ${LARK_PLUGIN_NAME}`);
5681
+ cleanupExternalLarkForBuiltinFeishu({
5682
+ homeBase,
5683
+ configPath,
5684
+ updateConfig: !opts.skipConfigUpdate
5685
+ });
5686
+ targets = targets.filter((p) => p.name !== LARK_PLUGIN_NAME);
5687
+ if (targets.length === 0) {
5688
+ console.error(`[install-extension] done in ${Date.now() - t0}ms`);
5689
+ return;
5690
+ }
5691
+ }
5613
5692
  const larkTarget = opts.skipConfigUpdate ? void 0 : targets.find((p) => p.name === LARK_PLUGIN_NAME);
5614
5693
  if (larkTarget) {
5615
5694
  if (await installLarkPluginWithCompatCheck({
@@ -5737,6 +5816,41 @@ function installOne$1(pkg, tarball, homeBase) {
5737
5816
  force: true
5738
5817
  });
5739
5818
  }
5819
+ function resolveInstallTargetOpenclawVersion(tag) {
5820
+ return tag.match(/\d+\.\d+\.\d+/)?.[0] ?? readOpenclawRuntimeVersion();
5821
+ }
5822
+ function cleanupExternalLarkForBuiltinFeishu(opts) {
5823
+ const externalNames = new Set([LARK_PLUGIN_NAME, LEGACY_LARK_PLUGIN_NAME]);
5824
+ const extDir = node_path.default.join(opts.homeBase, "workspace", "agent", "extensions");
5825
+ for (const name of externalNames) {
5826
+ const target = node_path.default.join(extDir, name);
5827
+ try {
5828
+ node_fs.default.rmSync(target, {
5829
+ recursive: true,
5830
+ force: true
5831
+ });
5832
+ } catch (e) {
5833
+ console.error(`[install-extension] WARN: failed to remove ${target}: ${e.message}`);
5834
+ }
5835
+ }
5836
+ if (!opts.updateConfig || !node_fs.default.existsSync(opts.configPath)) return;
5837
+ const config = loadJSON5().parse(node_fs.default.readFileSync(opts.configPath, "utf-8"));
5838
+ const plugins = asRecord(config.plugins);
5839
+ if (plugins) {
5840
+ const allow = plugins.allow;
5841
+ if (Array.isArray(allow)) {
5842
+ for (let i = allow.length - 1; i >= 0; i--) if (externalNames.has(String(allow[i]))) allow.splice(i, 1);
5843
+ }
5844
+ const entries = asRecord(plugins.entries);
5845
+ if (entries) for (const name of externalNames) delete entries[name];
5846
+ }
5847
+ const installs = { ...getPluginInstallsMap(opts.configPath, config) };
5848
+ for (const name of externalNames) delete installs[name];
5849
+ setPluginInstallsMap(opts.configPath, config, installs);
5850
+ const tmpPath = opts.configPath + ".builtin-feishu-tmp";
5851
+ node_fs.default.writeFileSync(tmpPath, JSON.stringify(config, null, 2), "utf-8");
5852
+ moveSafe(tmpPath, opts.configPath);
5853
+ }
5740
5854
  /**
5741
5855
  * Install openclaw-lark with a pre-flight compatibility check.
5742
5856
  *
@@ -11305,7 +11419,7 @@ async function reportCliRun(opts) {
11305
11419
  //#region src/help.ts
11306
11420
  const BIN = "mclaw-diagnose";
11307
11421
  function versionBanner() {
11308
- return `v0.1.21-alpha.0`;
11422
+ return `v0.1.21-alpha.2`;
11309
11423
  }
11310
11424
  const COMMANDS = [
11311
11425
  {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lark-apaas/openclaw-scripts-diagnose-cli",
3
- "version": "0.1.21-alpha.0",
3
+ "version": "0.1.21-alpha.2",
4
4
  "description": "CLI for OpenClaw config diagnose and repair with JSON5 support",
5
5
  "main": "dist/index.cjs",
6
6
  "bin": {