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

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 +329 -443
  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.2";
55
+ return "0.1.21-beta.0";
56
56
  }
57
57
  //#endregion
58
58
  //#region src/rule-engine/base.ts
@@ -2639,11 +2639,9 @@ 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";
2644
2642
  /**
2645
- * openclaw **内置** 的 `feishu` 插件(非独立扩展)。
2646
- * OpenClaw 2026.6.6 及之后以该内置能力作为目标态;更早版本仍使用外置 openclaw-lark。
2643
+ * 社区插件:openclaw **内置** 的 `feishu` 插件(非独立扩展)。
2644
+ * 与官方/ fork openclaw-lark 互斥,规范化时应禁用以让位 openclaw-lark。
2647
2645
  */
2648
2646
  const BUILTIN_FEISHU_PLUGIN_NAME = "feishu";
2649
2647
  /**
@@ -2823,282 +2821,9 @@ function getAllow$1(config) {
2823
2821
  return allow.filter((e) => typeof e === "string");
2824
2822
  }
2825
2823
  //#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
3098
2824
  //#region src/rules/lark-plugin-allow.ts
3099
2825
  let LarkPluginAllowRule = class LarkPluginAllowRule extends DiagnoseRule {
3100
2826
  validate(ctx) {
3101
- if (supportsBuiltinFeishu(readOpenclawRuntimeVersion())) return { pass: true };
3102
2827
  const allow = getAllow(ctx.config);
3103
2828
  if (LARK_PLUGIN_DIR_NAMES.some((name) => allow.includes(name))) return { pass: true };
3104
2829
  const installed = detectInstalledLarkPlugin$1(getExtensionsDir(ctx.configPath));
@@ -3226,8 +2951,7 @@ OldMiaodaPluginsCleanupRule = __decorate([Rule({
3226
2951
  //#region src/rules/builtin-plugin-missing.ts
3227
2952
  function findMissingBuiltinPlugins(ctx) {
3228
2953
  const extDir = getExtensionsDir(ctx.configPath);
3229
- const supportsBuiltin = supportsBuiltinFeishu(readOpenclawRuntimeVersion());
3230
- return [...OFFICIAL_EXTENSION_PLUGIN_NAMES].filter((name) => !(supportsBuiltin && name === "openclaw-lark")).filter((name) => !isPluginInstalledOnDisk(extDir, name)).sort();
2954
+ return [...OFFICIAL_EXTENSION_PLUGIN_NAMES].filter((name) => !isPluginInstalledOnDisk(extDir, name)).sort();
3231
2955
  }
3232
2956
  let BuiltinPluginMissingRule = class BuiltinPluginMissingRule extends DiagnoseRule {
3233
2957
  validate(ctx) {
@@ -3242,7 +2966,7 @@ let BuiltinPluginMissingRule = class BuiltinPluginMissingRule extends DiagnoseRu
3242
2966
  };
3243
2967
  BuiltinPluginMissingRule = __decorate([Rule({
3244
2968
  key: "builtin_plugin_missing",
3245
- description: "检查当前 OpenClaw 版本需要的内置扩展插件是否已在磁盘安装;缺失时提示重新安装(实验性)",
2969
+ description: "检查所有内置扩展插件(openclaw-lark、openclaw-extension-miaoda 等)是否已在磁盘安装;缺失时提示重新安装(实验性)",
3246
2970
  dependsOn: ["config_syntax_check"],
3247
2971
  repairMode: "check-only",
3248
2972
  level: "critical",
@@ -3550,14 +3274,6 @@ const FEISHU_TOOLS = Object.freeze([
3550
3274
  */
3551
3275
  let FeishuPluginStateNormalizeRule = class FeishuPluginStateNormalizeRule extends DiagnoseRule {
3552
3276
  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
- }
3561
3277
  if (!isPluginInstalled(ctx)) return { pass: true };
3562
3278
  const fails = [];
3563
3279
  if (!isNewPluginEnabled(ctx.config)) fails.push(`plugins.entries["${LARK_PLUGIN_NAME}"].enabled !== true(应启用)`);
@@ -3573,10 +3289,6 @@ let FeishuPluginStateNormalizeRule = class FeishuPluginStateNormalizeRule extend
3573
3289
  };
3574
3290
  }
3575
3291
  repair(ctx) {
3576
- if (isBuiltinFeishuTarget()) {
3577
- cleanupExternalLarkResiduals(ctx);
3578
- return;
3579
- }
3580
3292
  setEntryEnabled(ctx.config, LARK_PLUGIN_NAME, true);
3581
3293
  setEntryEnabled(ctx.config, BUILTIN_FEISHU_PLUGIN_NAME, false);
3582
3294
  ensureFeishuTools(ctx.config);
@@ -3590,9 +3302,6 @@ FeishuPluginStateNormalizeRule = __decorate([Rule({
3590
3302
  repairMode: "standard",
3591
3303
  level: "critical"
3592
3304
  })], FeishuPluginStateNormalizeRule);
3593
- function isBuiltinFeishuTarget() {
3594
- return supportsBuiltinFeishu(readOpenclawRuntimeVersion());
3595
- }
3596
3305
  function isPluginInstalled(ctx) {
3597
3306
  return node_fs.default.existsSync(node_path.default.join(getExtensionsDir(ctx.configPath), LARK_PLUGIN_NAME));
3598
3307
  }
@@ -3619,113 +3328,340 @@ function hasFeishuTool(list) {
3619
3328
  * openclaw schema 禁止同一 scope 同时设 allow + alsoAllow,故有 allow 时必须并入 allow,
3620
3329
  * 否则会触发 tools_allow_also_allow_conflict 规则反复来回改写。
3621
3330
  */
3622
- function feishuGrantTarget(config) {
3623
- return readTopLevelToolsList(config, "allow").length > 0 ? "allow" : "alsoAllow";
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));
3624
3509
  }
3625
3510
  /**
3626
- * 仅看顶层 tools——agent 级授权是用户对单 agent 的精细化配置,doctor 不动。
3627
- * 返回缺失的目标键名('allow' / 'alsoAllow'),不缺时返回 null。
3511
+ * 该条目的有效上界——开区间(EXCLUSIVE):openclaw 此值即不兼容。
3512
+ * undefined = 最高档,无上界。
3628
3513
  *
3629
- * 不关心 lark-cli:tools.alsoAllow(授权层)与 channels.feishu.tools.deny(插件注册层)
3630
- * 是两个正交的 config key,可并存——授权一个被 deny 的工具是无害空操作。因此本规则始终
3631
- * 补全 FEISHU_TOOLS(让 openclaw-lark 实际注册的工具都可用),重叠工具的"不可用"由独立的
3632
- * deny 规则在注册层处理,互不干扰。
3514
+ * 上界一律为开区间,两种来源:
3515
+ * - 有显式 maxOpenclawVersion 直接用它(它本身就是「首个不兼容的 openclaw 版本」)。
3516
+ * - 无显式 max 向更高插件版本(表降序,index 更小)扫描,取首个「严格更高 min」的
3517
+ * minOpenclawVersion,即下一不兼容区间的起点。
3518
+ *
3519
+ * 只接受严格更高的 min(跳过相同 min 的同档;跳过更低 min 防止非单调表产生空区间
3520
+ * [min, max) 把本条目永久判为不兼容)。
3633
3521
  */
3634
- function missingFeishuToolsGrant(config) {
3635
- const target = feishuGrantTarget(config);
3636
- return hasFeishuTool(readTopLevelToolsList(config, target)) ? null : target;
3637
- }
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;
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;
3648
3526
  }
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}`);
3660
- }
3661
- return found;
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
+ };
3662
3542
  }
3663
- function setEntryEnabled(config, key, enabled) {
3664
- const entries = ensureRecord(ensureRecord(config, "plugins"), "entries");
3665
- entries[key] = {
3666
- ...asRecord(entries[key]) ?? {},
3667
- enabled
3543
+ /**
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)。
3554
+ */
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
3668
3577
  };
3669
3578
  }
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])];
3579
+ /**
3580
+ * Fork plugin handling.
3581
+ *
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 条目)。
3595
+ */
3596
+ function effectiveCompatRange(pluginVersion) {
3597
+ const r = resolveRange(pluginVersion);
3598
+ return r ? {
3599
+ min: r.min,
3600
+ max: r.max
3601
+ } : void 0;
3675
3602
  }
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
- }
3603
+ /** "@scope/name" → "name";无 scope 原样返回。 */
3604
+ function extractBaseName(name) {
3605
+ if (!name) return void 0;
3606
+ return name.startsWith("@") ? name.split("/")[1] : name;
3703
3607
  }
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
- }
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";
3612
+ }
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
+ };
3728
3637
  }
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
3664
+ };
3729
3665
  }
3730
3666
  //#endregion
3731
3667
  //#region src/rules/feishu-plugin-version-compat.ts
@@ -5674,21 +5610,6 @@ async function installExtension(tag, ossFileMap, opts = {}) {
5674
5610
  }
5675
5611
  console.error(`[install-extension] tag=${tag} targets=${targets.length}`);
5676
5612
  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
- }
5692
5613
  const larkTarget = opts.skipConfigUpdate ? void 0 : targets.find((p) => p.name === LARK_PLUGIN_NAME);
5693
5614
  if (larkTarget) {
5694
5615
  if (await installLarkPluginWithCompatCheck({
@@ -5816,41 +5737,6 @@ function installOne$1(pkg, tarball, homeBase) {
5816
5737
  force: true
5817
5738
  });
5818
5739
  }
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
- }
5854
5740
  /**
5855
5741
  * Install openclaw-lark with a pre-flight compatibility check.
5856
5742
  *
@@ -11419,7 +11305,7 @@ async function reportCliRun(opts) {
11419
11305
  //#region src/help.ts
11420
11306
  const BIN = "mclaw-diagnose";
11421
11307
  function versionBanner() {
11422
- return `v0.1.21-alpha.2`;
11308
+ return `v0.1.21-beta.0`;
11423
11309
  }
11424
11310
  const COMMANDS = [
11425
11311
  {
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.2",
3
+ "version": "0.1.21-beta.0",
4
4
  "description": "CLI for OpenClaw config diagnose and repair with JSON5 support",
5
5
  "main": "dist/index.cjs",
6
6
  "bin": {