@lark-apaas/openclaw-scripts-diagnose-cli 0.1.20 → 0.1.21-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +532 -366
- 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.
|
|
55
|
+
return "0.1.21-alpha.1";
|
|
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
|
-
*
|
|
2644
|
-
*
|
|
2645
|
+
* openclaw **内置** 的 `feishu` 插件(非独立扩展)。
|
|
2646
|
+
* OpenClaw 2026.6.6 及之后以该内置能力作为目标态;更早版本仍使用外置 openclaw-lark。
|
|
2645
2647
|
*/
|
|
2646
2648
|
const BUILTIN_FEISHU_PLUGIN_NAME = "feishu";
|
|
2647
2649
|
/**
|
|
@@ -2679,6 +2681,54 @@ const WORKSPACE_AGENT_REL = "workspace/agent";
|
|
|
2679
2681
|
/** openclaw.json 默认相对路径。 */
|
|
2680
2682
|
const DEFAULT_CONFIG_REL = `${WORKSPACE_AGENT_REL}/openclaw.json`;
|
|
2681
2683
|
//#endregion
|
|
2684
|
+
//#region src/plugin-installs-store.ts
|
|
2685
|
+
function getExternalInstallsPath(configPath) {
|
|
2686
|
+
return node_path.default.join(node_path.default.dirname(configPath), "plugins", "installs.json");
|
|
2687
|
+
}
|
|
2688
|
+
function readExternalInstallRecords(filePath) {
|
|
2689
|
+
const raw = node_fs.default.readFileSync(filePath, "utf-8");
|
|
2690
|
+
const records = asRecord(asRecord(JSON.parse(raw))?.installRecords);
|
|
2691
|
+
if (!records) throw new Error(`plugins installs file must contain object installRecords: ${filePath}`);
|
|
2692
|
+
return records;
|
|
2693
|
+
}
|
|
2694
|
+
function readExternalPayload(filePath) {
|
|
2695
|
+
const raw = node_fs.default.readFileSync(filePath, "utf-8");
|
|
2696
|
+
const payload = JSON.parse(raw);
|
|
2697
|
+
if (!asRecord(payload)) throw new Error(`plugins installs file must be an object: ${filePath}`);
|
|
2698
|
+
return payload;
|
|
2699
|
+
}
|
|
2700
|
+
function cloneRecords(records) {
|
|
2701
|
+
return JSON.parse(JSON.stringify(records));
|
|
2702
|
+
}
|
|
2703
|
+
function getPluginInstalls(configPath, config) {
|
|
2704
|
+
const externalPath = getExternalInstallsPath(configPath);
|
|
2705
|
+
if (node_fs.default.existsSync(externalPath)) return {
|
|
2706
|
+
records: readExternalInstallRecords(externalPath),
|
|
2707
|
+
externalPath,
|
|
2708
|
+
hasExternalFile: true
|
|
2709
|
+
};
|
|
2710
|
+
return {
|
|
2711
|
+
records: asRecord(asRecord(config.plugins)?.installs) ?? {},
|
|
2712
|
+
externalPath,
|
|
2713
|
+
hasExternalFile: false
|
|
2714
|
+
};
|
|
2715
|
+
}
|
|
2716
|
+
function getPluginInstallsMap(configPath, config) {
|
|
2717
|
+
return getPluginInstalls(configPath, config).records;
|
|
2718
|
+
}
|
|
2719
|
+
function setPluginInstallsMap(configPath, config, records) {
|
|
2720
|
+
const externalPath = getExternalInstallsPath(configPath);
|
|
2721
|
+
const nextRecords = cloneRecords(records);
|
|
2722
|
+
const plugins = ensureRecord(config, "plugins");
|
|
2723
|
+
plugins.installs = cloneRecords(nextRecords);
|
|
2724
|
+
if (!node_fs.default.existsSync(externalPath)) return;
|
|
2725
|
+
const payload = readExternalPayload(externalPath);
|
|
2726
|
+
payload.installRecords = nextRecords;
|
|
2727
|
+
const tmpPath = `${externalPath}.tmp`;
|
|
2728
|
+
node_fs.default.writeFileSync(tmpPath, JSON.stringify(payload, null, 2), "utf-8");
|
|
2729
|
+
node_fs.default.renameSync(tmpPath, externalPath);
|
|
2730
|
+
}
|
|
2731
|
+
//#endregion
|
|
2682
2732
|
//#region src/rules/miaoda-official-plugins-install-spec-unlock.ts
|
|
2683
2733
|
/**
|
|
2684
2734
|
* Official miaoda-side plugins that must track manifest — version-locked specs
|
|
@@ -2695,9 +2745,7 @@ function unlockSpec(spec) {
|
|
|
2695
2745
|
return spec.slice(0, cut);
|
|
2696
2746
|
}
|
|
2697
2747
|
/** Yield `[key, lockedSpec]` for every official-plugin install whose `spec` is locked. */
|
|
2698
|
-
function* iterLockedOfficialInstalls(
|
|
2699
|
-
const installs = getNestedMap(config, "plugins", "installs");
|
|
2700
|
-
if (!installs) return;
|
|
2748
|
+
function* iterLockedOfficialInstalls(installs) {
|
|
2701
2749
|
for (const [key, entry] of Object.entries(installs)) {
|
|
2702
2750
|
if (!OFFICIAL_EXTENSION_PLUGIN_NAMES.has(key)) continue;
|
|
2703
2751
|
const spec = asRecord(entry)?.spec;
|
|
@@ -2706,7 +2754,7 @@ function* iterLockedOfficialInstalls(config) {
|
|
|
2706
2754
|
}
|
|
2707
2755
|
let MiaodaOfficialPluginsInstallSpecUnlockRule = class MiaodaOfficialPluginsInstallSpecUnlockRule extends DiagnoseRule {
|
|
2708
2756
|
validate(ctx) {
|
|
2709
|
-
const locked = [...iterLockedOfficialInstalls(ctx.config)].map(([k]) => k);
|
|
2757
|
+
const locked = [...iterLockedOfficialInstalls(getPluginInstallsMap(ctx.configPath, ctx.config))].map(([k]) => k);
|
|
2710
2758
|
if (locked.length === 0) return { pass: true };
|
|
2711
2759
|
return {
|
|
2712
2760
|
pass: false,
|
|
@@ -2714,12 +2762,16 @@ let MiaodaOfficialPluginsInstallSpecUnlockRule = class MiaodaOfficialPluginsInst
|
|
|
2714
2762
|
};
|
|
2715
2763
|
}
|
|
2716
2764
|
repair(ctx) {
|
|
2717
|
-
|
|
2718
|
-
|
|
2719
|
-
|
|
2720
|
-
|
|
2721
|
-
|
|
2722
|
-
|
|
2765
|
+
const installs = { ...getPluginInstallsMap(ctx.configPath, ctx.config) };
|
|
2766
|
+
for (const [key, spec] of iterLockedOfficialInstalls(installs)) {
|
|
2767
|
+
const entry = asRecord(installs[key]);
|
|
2768
|
+
if (!entry) continue;
|
|
2769
|
+
installs[key] = {
|
|
2770
|
+
...entry,
|
|
2771
|
+
spec: unlockSpec(spec)
|
|
2772
|
+
};
|
|
2773
|
+
}
|
|
2774
|
+
setPluginInstallsMap(ctx.configPath, ctx.config, installs);
|
|
2723
2775
|
}
|
|
2724
2776
|
};
|
|
2725
2777
|
MiaodaOfficialPluginsInstallSpecUnlockRule = __decorate([Rule({
|
|
@@ -2771,9 +2823,282 @@ function getAllow$1(config) {
|
|
|
2771
2823
|
return allow.filter((e) => typeof e === "string");
|
|
2772
2824
|
}
|
|
2773
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
|
|
2774
3098
|
//#region src/rules/lark-plugin-allow.ts
|
|
2775
3099
|
let LarkPluginAllowRule = class LarkPluginAllowRule extends DiagnoseRule {
|
|
2776
3100
|
validate(ctx) {
|
|
3101
|
+
if (supportsBuiltinFeishu(readOpenclawRuntimeVersion())) return { pass: true };
|
|
2777
3102
|
const allow = getAllow(ctx.config);
|
|
2778
3103
|
if (LARK_PLUGIN_DIR_NAMES.some((name) => allow.includes(name))) return { pass: true };
|
|
2779
3104
|
const installed = detectInstalledLarkPlugin$1(getExtensionsDir(ctx.configPath));
|
|
@@ -2837,11 +3162,11 @@ const OLD_PLUGIN_NAMES = Object.freeze([
|
|
|
2837
3162
|
"feishu-greeting",
|
|
2838
3163
|
"miaoda-keepalive"
|
|
2839
3164
|
]);
|
|
2840
|
-
function getPluginMaps(config) {
|
|
3165
|
+
function getPluginMaps(configPath, config) {
|
|
2841
3166
|
const rawAllow = asRecord(config.plugins)?.allow;
|
|
2842
3167
|
return {
|
|
2843
3168
|
entries: getNestedMap(config, "plugins", "entries"),
|
|
2844
|
-
installs:
|
|
3169
|
+
installs: getPluginInstallsMap(configPath, config),
|
|
2845
3170
|
allow: Array.isArray(rawAllow) ? rawAllow : void 0
|
|
2846
3171
|
};
|
|
2847
3172
|
}
|
|
@@ -2853,7 +3178,7 @@ function findResiduals({ entries, installs, allow }, extensionsDir) {
|
|
|
2853
3178
|
}
|
|
2854
3179
|
let OldMiaodaPluginsCleanupRule = class OldMiaodaPluginsCleanupRule extends DiagnoseRule {
|
|
2855
3180
|
validate(ctx) {
|
|
2856
|
-
const maps = getPluginMaps(ctx.config);
|
|
3181
|
+
const maps = getPluginMaps(ctx.configPath, ctx.config);
|
|
2857
3182
|
if (!hasNewMiaoda(maps)) return { pass: true };
|
|
2858
3183
|
const residuals = findResiduals(maps, getExtensionsDir(ctx.configPath));
|
|
2859
3184
|
if (residuals.length === 0) return { pass: true };
|
|
@@ -2863,7 +3188,7 @@ let OldMiaodaPluginsCleanupRule = class OldMiaodaPluginsCleanupRule extends Diag
|
|
|
2863
3188
|
};
|
|
2864
3189
|
}
|
|
2865
3190
|
repair(ctx) {
|
|
2866
|
-
const maps = getPluginMaps(ctx.config);
|
|
3191
|
+
const maps = getPluginMaps(ctx.configPath, ctx.config);
|
|
2867
3192
|
if (!hasNewMiaoda(maps)) return;
|
|
2868
3193
|
const extensionsDir = getExtensionsDir(ctx.configPath);
|
|
2869
3194
|
const { entries, installs, allow } = maps;
|
|
@@ -2887,6 +3212,7 @@ let OldMiaodaPluginsCleanupRule = class OldMiaodaPluginsCleanupRule extends Diag
|
|
|
2887
3212
|
console.error(`[old_miaoda_plugins_cleanup] rmSync ${target} failed: ${e.message}`);
|
|
2888
3213
|
}
|
|
2889
3214
|
}
|
|
3215
|
+
if (installs) setPluginInstallsMap(ctx.configPath, ctx.config, installs);
|
|
2890
3216
|
}
|
|
2891
3217
|
};
|
|
2892
3218
|
OldMiaodaPluginsCleanupRule = __decorate([Rule({
|
|
@@ -2900,7 +3226,8 @@ OldMiaodaPluginsCleanupRule = __decorate([Rule({
|
|
|
2900
3226
|
//#region src/rules/builtin-plugin-missing.ts
|
|
2901
3227
|
function findMissingBuiltinPlugins(ctx) {
|
|
2902
3228
|
const extDir = getExtensionsDir(ctx.configPath);
|
|
2903
|
-
|
|
3229
|
+
const supportsBuiltin = supportsBuiltinFeishu(readOpenclawRuntimeVersion());
|
|
3230
|
+
return [...OFFICIAL_EXTENSION_PLUGIN_NAMES].filter((name) => !(supportsBuiltin && name === "openclaw-lark")).filter((name) => !isPluginInstalledOnDisk(extDir, name)).sort();
|
|
2904
3231
|
}
|
|
2905
3232
|
let BuiltinPluginMissingRule = class BuiltinPluginMissingRule extends DiagnoseRule {
|
|
2906
3233
|
validate(ctx) {
|
|
@@ -2915,7 +3242,7 @@ let BuiltinPluginMissingRule = class BuiltinPluginMissingRule extends DiagnoseRu
|
|
|
2915
3242
|
};
|
|
2916
3243
|
BuiltinPluginMissingRule = __decorate([Rule({
|
|
2917
3244
|
key: "builtin_plugin_missing",
|
|
2918
|
-
description: "
|
|
3245
|
+
description: "检查当前 OpenClaw 版本需要的内置扩展插件是否已在磁盘安装;缺失时提示重新安装(实验性)",
|
|
2919
3246
|
dependsOn: ["config_syntax_check"],
|
|
2920
3247
|
repairMode: "check-only",
|
|
2921
3248
|
level: "critical",
|
|
@@ -2929,8 +3256,7 @@ BuiltinPluginMissingRule = __decorate([Rule({
|
|
|
2929
3256
|
*/
|
|
2930
3257
|
function findOrphanedInstalls(ctx) {
|
|
2931
3258
|
const extDir = getExtensionsDir(ctx.configPath);
|
|
2932
|
-
const installs =
|
|
2933
|
-
if (!installs) return [];
|
|
3259
|
+
const installs = getPluginInstallsMap(ctx.configPath, ctx.config);
|
|
2934
3260
|
return Object.keys(installs).filter((name) => !isPluginInstalledOnDisk(extDir, name)).sort();
|
|
2935
3261
|
}
|
|
2936
3262
|
let BuiltinPluginInstallsCleanupRule = class BuiltinPluginInstallsCleanupRule extends DiagnoseRule {
|
|
@@ -2946,11 +3272,10 @@ let BuiltinPluginInstallsCleanupRule = class BuiltinPluginInstallsCleanupRule ex
|
|
|
2946
3272
|
const orphaned = findOrphanedInstalls(ctx);
|
|
2947
3273
|
if (orphaned.length === 0) return;
|
|
2948
3274
|
const orphanSet = new Set(orphaned);
|
|
2949
|
-
const
|
|
2950
|
-
|
|
2951
|
-
|
|
2952
|
-
|
|
2953
|
-
const rawAllow = plugins.allow;
|
|
3275
|
+
const installs = { ...getPluginInstallsMap(ctx.configPath, ctx.config) };
|
|
3276
|
+
for (const name of orphaned) delete installs[name];
|
|
3277
|
+
setPluginInstallsMap(ctx.configPath, ctx.config, installs);
|
|
3278
|
+
const rawAllow = asRecord(ctx.config.plugins)?.allow;
|
|
2954
3279
|
if (Array.isArray(rawAllow)) for (let i = rawAllow.length - 1; i >= 0; i--) {
|
|
2955
3280
|
const v = rawAllow[i];
|
|
2956
3281
|
if (typeof v === "string" && orphanSet.has(v)) rawAllow.splice(i, 1);
|
|
@@ -3225,6 +3550,14 @@ const FEISHU_TOOLS = Object.freeze([
|
|
|
3225
3550
|
*/
|
|
3226
3551
|
let FeishuPluginStateNormalizeRule = class FeishuPluginStateNormalizeRule extends DiagnoseRule {
|
|
3227
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
|
+
}
|
|
3228
3561
|
if (!isPluginInstalled(ctx)) return { pass: true };
|
|
3229
3562
|
const fails = [];
|
|
3230
3563
|
if (!isNewPluginEnabled(ctx.config)) fails.push(`plugins.entries["${LARK_PLUGIN_NAME}"].enabled !== true(应启用)`);
|
|
@@ -3240,6 +3573,10 @@ let FeishuPluginStateNormalizeRule = class FeishuPluginStateNormalizeRule extend
|
|
|
3240
3573
|
};
|
|
3241
3574
|
}
|
|
3242
3575
|
repair(ctx) {
|
|
3576
|
+
if (isBuiltinFeishuTarget()) {
|
|
3577
|
+
cleanupExternalLarkResiduals(ctx);
|
|
3578
|
+
return;
|
|
3579
|
+
}
|
|
3243
3580
|
setEntryEnabled(ctx.config, LARK_PLUGIN_NAME, true);
|
|
3244
3581
|
setEntryEnabled(ctx.config, BUILTIN_FEISHU_PLUGIN_NAME, false);
|
|
3245
3582
|
ensureFeishuTools(ctx.config);
|
|
@@ -3253,6 +3590,9 @@ FeishuPluginStateNormalizeRule = __decorate([Rule({
|
|
|
3253
3590
|
repairMode: "standard",
|
|
3254
3591
|
level: "critical"
|
|
3255
3592
|
})], FeishuPluginStateNormalizeRule);
|
|
3593
|
+
function isBuiltinFeishuTarget() {
|
|
3594
|
+
return supportsBuiltinFeishu(readOpenclawRuntimeVersion());
|
|
3595
|
+
}
|
|
3256
3596
|
function isPluginInstalled(ctx) {
|
|
3257
3597
|
return node_fs.default.existsSync(node_path.default.join(getExtensionsDir(ctx.configPath), LARK_PLUGIN_NAME));
|
|
3258
3598
|
}
|
|
@@ -3270,346 +3610,122 @@ function readTopLevelToolsList(config, key) {
|
|
|
3270
3610
|
}
|
|
3271
3611
|
function hasFeishuTool(list) {
|
|
3272
3612
|
if (list.includes("*")) return true;
|
|
3273
|
-
return list.some((t) => FEISHU_TOOLS.includes(t));
|
|
3274
|
-
}
|
|
3275
|
-
/**
|
|
3276
|
-
* 决定 feishu_* 应补充到哪个授权键:
|
|
3277
|
-
* - 已有非空 tools.allow(限制式白名单)→ 'allow'(合并进 allow,避免与 alsoAllow 冲突)
|
|
3278
|
-
* - 否则 → 'alsoAllow'(追加式,叠加在 profile 基线上)
|
|
3279
|
-
* openclaw schema 禁止同一 scope 同时设 allow + alsoAllow,故有 allow 时必须并入 allow,
|
|
3280
|
-
* 否则会触发 tools_allow_also_allow_conflict 规则反复来回改写。
|
|
3281
|
-
*/
|
|
3282
|
-
function feishuGrantTarget(config) {
|
|
3283
|
-
return readTopLevelToolsList(config, "allow").length > 0 ? "allow" : "alsoAllow";
|
|
3284
|
-
}
|
|
3285
|
-
/**
|
|
3286
|
-
* 仅看顶层 tools——agent 级授权是用户对单 agent 的精细化配置,doctor 不动。
|
|
3287
|
-
* 返回缺失的目标键名('allow' / 'alsoAllow'),不缺时返回 null。
|
|
3288
|
-
*
|
|
3289
|
-
* 不关心 lark-cli:tools.alsoAllow(授权层)与 channels.feishu.tools.deny(插件注册层)
|
|
3290
|
-
* 是两个正交的 config key,可并存——授权一个被 deny 的工具是无害空操作。因此本规则始终
|
|
3291
|
-
* 补全 FEISHU_TOOLS(让 openclaw-lark 实际注册的工具都可用),重叠工具的"不可用"由独立的
|
|
3292
|
-
* deny 规则在注册层处理,互不干扰。
|
|
3293
|
-
*/
|
|
3294
|
-
function missingFeishuToolsGrant(config) {
|
|
3295
|
-
const target = feishuGrantTarget(config);
|
|
3296
|
-
return hasFeishuTool(readTopLevelToolsList(config, target)) ? null : target;
|
|
3297
|
-
}
|
|
3298
|
-
function findLegacyResiduals(ctx) {
|
|
3299
|
-
const found = [];
|
|
3300
|
-
const plugins = asRecord(ctx.config.plugins);
|
|
3301
|
-
if (asRecord(plugins?.entries)?.["feishu-openclaw-plugin"] != null) found.push("entries[legacy]");
|
|
3302
|
-
const allow = plugins?.allow;
|
|
3303
|
-
if (Array.isArray(allow) && allow.includes("feishu-openclaw-plugin")) found.push("allow[legacy]");
|
|
3304
|
-
if (asRecord(plugins?.installs)?.["feishu-openclaw-plugin"] != null) found.push("installs[legacy]");
|
|
3305
|
-
const extDir = getExtensionsDir(ctx.configPath);
|
|
3306
|
-
for (const name of LEGACY_DIRS_TO_REMOVE) if (node_fs.default.existsSync(node_path.default.join(extDir, name))) found.push(`fs/${name}`);
|
|
3307
|
-
return found;
|
|
3308
|
-
}
|
|
3309
|
-
function setEntryEnabled(config, key, enabled) {
|
|
3310
|
-
const entries = ensureRecord(ensureRecord(config, "plugins"), "entries");
|
|
3311
|
-
entries[key] = {
|
|
3312
|
-
...asRecord(entries[key]) ?? {},
|
|
3313
|
-
enabled
|
|
3314
|
-
};
|
|
3315
|
-
}
|
|
3316
|
-
function ensureFeishuTools(config) {
|
|
3317
|
-
const target = feishuGrantTarget(config);
|
|
3318
|
-
const current = readTopLevelToolsList(config, target);
|
|
3319
|
-
if (hasFeishuTool(current)) return;
|
|
3320
|
-
ensureRecord(config, "tools")[target] = [...new Set([...current, ...FEISHU_TOOLS])];
|
|
3321
|
-
}
|
|
3322
|
-
function cleanupLegacyResiduals(ctx) {
|
|
3323
|
-
const plugins = asRecord(ctx.config.plugins);
|
|
3324
|
-
if (plugins) {
|
|
3325
|
-
const entries = asRecord(plugins.entries);
|
|
3326
|
-
if (entries && "feishu-openclaw-plugin" in entries) delete entries[LEGACY_LARK_PLUGIN_NAME];
|
|
3327
|
-
const installs = asRecord(plugins.installs);
|
|
3328
|
-
if (installs && "feishu-openclaw-plugin" in installs) delete installs[LEGACY_LARK_PLUGIN_NAME];
|
|
3329
|
-
const allow = plugins.allow;
|
|
3330
|
-
if (Array.isArray(allow)) {
|
|
3331
|
-
for (let i = allow.length - 1; i >= 0; i--) if (allow[i] === "feishu-openclaw-plugin") allow.splice(i, 1);
|
|
3332
|
-
if (!allow.includes("openclaw-lark")) allow.push(LARK_PLUGIN_NAME);
|
|
3333
|
-
}
|
|
3334
|
-
}
|
|
3335
|
-
const extDir = getExtensionsDir(ctx.configPath);
|
|
3336
|
-
for (const name of LEGACY_DIRS_TO_REMOVE) {
|
|
3337
|
-
const target = node_path.default.join(extDir, name);
|
|
3338
|
-
const rel = node_path.default.relative(extDir, target);
|
|
3339
|
-
if (!rel || rel.startsWith("..") || node_path.default.isAbsolute(rel)) continue;
|
|
3340
|
-
try {
|
|
3341
|
-
rmrfTolerant(target);
|
|
3342
|
-
} catch (e) {
|
|
3343
|
-
console.error(`[feishu_plugin_state_normalize] rmrf ${target} failed: ${e.message}`);
|
|
3344
|
-
}
|
|
3345
|
-
}
|
|
3346
|
-
}
|
|
3347
|
-
//#endregion
|
|
3348
|
-
//#region src/version-compat.ts
|
|
3349
|
-
const VERSION_COMPAT_MAP = Object.freeze([
|
|
3350
|
-
{
|
|
3351
|
-
openclawLarkVersion: "2026.5.20",
|
|
3352
|
-
minOpenclawVersion: "2026.5.7"
|
|
3353
|
-
},
|
|
3354
|
-
{
|
|
3355
|
-
openclawLarkVersion: "2026.5.13",
|
|
3356
|
-
minOpenclawVersion: "2026.5.7"
|
|
3357
|
-
},
|
|
3358
|
-
{
|
|
3359
|
-
openclawLarkVersion: "2026.5.12",
|
|
3360
|
-
minOpenclawVersion: "2026.5.7"
|
|
3361
|
-
},
|
|
3362
|
-
{
|
|
3363
|
-
openclawLarkVersion: "2026.5.7",
|
|
3364
|
-
minOpenclawVersion: "2026.5.6"
|
|
3365
|
-
},
|
|
3366
|
-
{
|
|
3367
|
-
openclawLarkVersion: "2026.4.10",
|
|
3368
|
-
minOpenclawVersion: "2026.4.27"
|
|
3369
|
-
},
|
|
3370
|
-
{
|
|
3371
|
-
openclawLarkVersion: "2026.4.9",
|
|
3372
|
-
minOpenclawVersion: "2026.3.28"
|
|
3373
|
-
},
|
|
3374
|
-
{
|
|
3375
|
-
openclawLarkVersion: "2026.4.8",
|
|
3376
|
-
minOpenclawVersion: "2026.3.28"
|
|
3377
|
-
},
|
|
3378
|
-
{
|
|
3379
|
-
openclawLarkVersion: "2026.4.7",
|
|
3380
|
-
minOpenclawVersion: "2026.3.28"
|
|
3381
|
-
},
|
|
3382
|
-
{
|
|
3383
|
-
openclawLarkVersion: "2026.4.1",
|
|
3384
|
-
minOpenclawVersion: "2026.3.28"
|
|
3385
|
-
},
|
|
3386
|
-
{
|
|
3387
|
-
openclawLarkVersion: "2026.3.31",
|
|
3388
|
-
minOpenclawVersion: "2026.3.28"
|
|
3389
|
-
},
|
|
3390
|
-
{
|
|
3391
|
-
openclawLarkVersion: "2026.3.30",
|
|
3392
|
-
minOpenclawVersion: "2026.3.28"
|
|
3393
|
-
},
|
|
3394
|
-
{
|
|
3395
|
-
openclawLarkVersion: "2026.3.29",
|
|
3396
|
-
minOpenclawVersion: "2026.3.28"
|
|
3397
|
-
},
|
|
3398
|
-
{
|
|
3399
|
-
openclawLarkVersion: "2026.3.26",
|
|
3400
|
-
minOpenclawVersion: "2026.3.22"
|
|
3401
|
-
},
|
|
3402
|
-
{
|
|
3403
|
-
openclawLarkVersion: "2026.3.25",
|
|
3404
|
-
minOpenclawVersion: "2026.3.22"
|
|
3405
|
-
},
|
|
3406
|
-
{
|
|
3407
|
-
openclawLarkVersion: "2026.3.24",
|
|
3408
|
-
minOpenclawVersion: "2026.3.22"
|
|
3409
|
-
},
|
|
3410
|
-
{
|
|
3411
|
-
openclawLarkVersion: "2026.3.18",
|
|
3412
|
-
minOpenclawVersion: "2026.2.26",
|
|
3413
|
-
maxOpenclawVersion: "2026.3.13"
|
|
3414
|
-
},
|
|
3415
|
-
{
|
|
3416
|
-
openclawLarkVersion: "2026.3.17",
|
|
3417
|
-
minOpenclawVersion: "2026.2.26",
|
|
3418
|
-
maxOpenclawVersion: "2026.3.13"
|
|
3419
|
-
},
|
|
3420
|
-
{
|
|
3421
|
-
openclawLarkVersion: "2026.3.15",
|
|
3422
|
-
minOpenclawVersion: "2026.2.26",
|
|
3423
|
-
maxOpenclawVersion: "2026.3.13"
|
|
3424
|
-
},
|
|
3425
|
-
{
|
|
3426
|
-
openclawLarkVersion: "2026.3.12",
|
|
3427
|
-
minOpenclawVersion: "2026.2.26",
|
|
3428
|
-
maxOpenclawVersion: "2026.3.13"
|
|
3429
|
-
},
|
|
3430
|
-
{
|
|
3431
|
-
openclawLarkVersion: "2026.3.10",
|
|
3432
|
-
minOpenclawVersion: "2026.2.26",
|
|
3433
|
-
maxOpenclawVersion: "2026.3.13"
|
|
3434
|
-
},
|
|
3435
|
-
{
|
|
3436
|
-
openclawLarkVersion: "2026.3.9",
|
|
3437
|
-
minOpenclawVersion: "2026.2.26",
|
|
3438
|
-
maxOpenclawVersion: "2026.3.13"
|
|
3439
|
-
}
|
|
3440
|
-
]);
|
|
3441
|
-
/**
|
|
3442
|
-
* Coerce a CalVer string ("YYYY.M.D[suffix]") to a clean semver string.
|
|
3443
|
-
* semver.coerce strips non-numeric suffixes (e.g. "-rc.1") and fills in
|
|
3444
|
-
* missing components with 0. Returns "0.0.0" for unparseable input.
|
|
3445
|
-
*/
|
|
3446
|
-
function coerceCalVer(v) {
|
|
3447
|
-
return semver.default.coerce(v)?.version ?? "0.0.0";
|
|
3613
|
+
return list.some((t) => FEISHU_TOOLS.includes(t));
|
|
3448
3614
|
}
|
|
3449
3615
|
/**
|
|
3450
|
-
*
|
|
3451
|
-
*
|
|
3452
|
-
*
|
|
3453
|
-
*
|
|
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 规则反复来回改写。
|
|
3454
3621
|
*/
|
|
3455
|
-
function
|
|
3456
|
-
return
|
|
3622
|
+
function feishuGrantTarget(config) {
|
|
3623
|
+
return readTopLevelToolsList(config, "allow").length > 0 ? "allow" : "alsoAllow";
|
|
3457
3624
|
}
|
|
3458
3625
|
/**
|
|
3459
|
-
*
|
|
3460
|
-
*
|
|
3461
|
-
*
|
|
3462
|
-
* 上界一律为开区间,两种来源:
|
|
3463
|
-
* - 有显式 maxOpenclawVersion → 直接用它(它本身就是「首个不兼容的 openclaw 版本」)。
|
|
3464
|
-
* - 无显式 max → 向更高插件版本(表降序,index 更小)扫描,取首个「严格更高 min」的
|
|
3465
|
-
* minOpenclawVersion,即下一不兼容区间的起点。
|
|
3626
|
+
* 仅看顶层 tools——agent 级授权是用户对单 agent 的精细化配置,doctor 不动。
|
|
3627
|
+
* 返回缺失的目标键名('allow' / 'alsoAllow'),不缺时返回 null。
|
|
3466
3628
|
*
|
|
3467
|
-
*
|
|
3468
|
-
*
|
|
3629
|
+
* 不关心 lark-cli:tools.alsoAllow(授权层)与 channels.feishu.tools.deny(插件注册层)
|
|
3630
|
+
* 是两个正交的 config key,可并存——授权一个被 deny 的工具是无害空操作。因此本规则始终
|
|
3631
|
+
* 补全 FEISHU_TOOLS(让 openclaw-lark 实际注册的工具都可用),重叠工具的"不可用"由独立的
|
|
3632
|
+
* deny 规则在注册层处理,互不干扰。
|
|
3469
3633
|
*/
|
|
3470
|
-
function
|
|
3471
|
-
const
|
|
3472
|
-
|
|
3473
|
-
for (let i = index - 1; i >= 0; i--) if (compareCalVer(VERSION_COMPAT_MAP[i].minOpenclawVersion, cur.minOpenclawVersion) > 0) return VERSION_COMPAT_MAP[i].minOpenclawVersion;
|
|
3634
|
+
function missingFeishuToolsGrant(config) {
|
|
3635
|
+
const target = feishuGrantTarget(config);
|
|
3636
|
+
return hasFeishuTool(readTopLevelToolsList(config, target)) ? null : target;
|
|
3474
3637
|
}
|
|
3475
|
-
|
|
3476
|
-
|
|
3477
|
-
|
|
3478
|
-
|
|
3479
|
-
|
|
3480
|
-
|
|
3481
|
-
|
|
3482
|
-
const
|
|
3483
|
-
|
|
3484
|
-
|
|
3485
|
-
return {
|
|
3486
|
-
entry,
|
|
3487
|
-
min: entry.minOpenclawVersion,
|
|
3488
|
-
max: inferEffectiveMax(index)
|
|
3489
|
-
};
|
|
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;
|
|
3490
3648
|
}
|
|
3491
|
-
|
|
3492
|
-
|
|
3493
|
-
|
|
3494
|
-
|
|
3495
|
-
|
|
3496
|
-
|
|
3497
|
-
|
|
3498
|
-
|
|
3499
|
-
|
|
3500
|
-
|
|
3501
|
-
|
|
3502
|
-
|
|
3503
|
-
|
|
3504
|
-
const r = resolveRange(pluginVersion);
|
|
3505
|
-
if (!r) return {
|
|
3506
|
-
compatible: false,
|
|
3507
|
-
direction: "lark",
|
|
3508
|
-
entry: void 0
|
|
3509
|
-
};
|
|
3510
|
-
const oc = coerceCalVer(openclawVersion);
|
|
3511
|
-
if (semver.default.lt(oc, coerceCalVer(r.min))) return {
|
|
3512
|
-
compatible: false,
|
|
3513
|
-
direction: "openclaw",
|
|
3514
|
-
entry: r.entry
|
|
3515
|
-
};
|
|
3516
|
-
if (r.max !== void 0 && semver.default.gte(oc, coerceCalVer(r.max))) return {
|
|
3517
|
-
compatible: false,
|
|
3518
|
-
direction: "lark",
|
|
3519
|
-
entry: r.entry
|
|
3520
|
-
};
|
|
3521
|
-
return {
|
|
3522
|
-
compatible: true,
|
|
3523
|
-
direction: null,
|
|
3524
|
-
entry: r.entry
|
|
3525
|
-
};
|
|
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;
|
|
3526
3662
|
}
|
|
3527
|
-
|
|
3528
|
-
|
|
3529
|
-
|
|
3530
|
-
|
|
3531
|
-
|
|
3532
|
-
|
|
3533
|
-
* numbering and breaks once the fork version drifts past an official entry.
|
|
3534
|
-
* Both the install-time compat check and the diagnose rule must therefore pin
|
|
3535
|
-
* the fork to its official-equivalent version before consulting the map.
|
|
3536
|
-
*/
|
|
3537
|
-
/** fork 对标的官方 openclaw-lark 版本(与 VERSION_COMPAT_MAP 强耦合,故留在此处)。 */
|
|
3538
|
-
const FORK_LARK_PLUGIN_PINNED_VERSION = "2026.4.1";
|
|
3539
|
-
/**
|
|
3540
|
-
* 返回某插件版本(floor 匹配后)的有效兼容区间 [min, max),含 evaluateVersionRange 所用的
|
|
3541
|
-
* (显式或推断的)开区间上界——区别于仅暴露显式 maxOpenclawVersion 的 findClosestEntry。
|
|
3542
|
-
* undefined = 版本比全表都旧(无 floor 条目)。
|
|
3543
|
-
*/
|
|
3544
|
-
function effectiveCompatRange(pluginVersion) {
|
|
3545
|
-
const r = resolveRange(pluginVersion);
|
|
3546
|
-
return r ? {
|
|
3547
|
-
min: r.min,
|
|
3548
|
-
max: r.max
|
|
3549
|
-
} : void 0;
|
|
3663
|
+
function setEntryEnabled(config, key, enabled) {
|
|
3664
|
+
const entries = ensureRecord(ensureRecord(config, "plugins"), "entries");
|
|
3665
|
+
entries[key] = {
|
|
3666
|
+
...asRecord(entries[key]) ?? {},
|
|
3667
|
+
enabled
|
|
3668
|
+
};
|
|
3550
3669
|
}
|
|
3551
|
-
|
|
3552
|
-
|
|
3553
|
-
|
|
3554
|
-
|
|
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])];
|
|
3555
3675
|
}
|
|
3556
|
-
function
|
|
3557
|
-
|
|
3558
|
-
if (
|
|
3559
|
-
|
|
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
|
+
}
|
|
3560
3703
|
}
|
|
3561
|
-
|
|
3562
|
-
|
|
3563
|
-
|
|
3564
|
-
|
|
3565
|
-
|
|
3566
|
-
|
|
3567
|
-
|
|
3568
|
-
|
|
3569
|
-
|
|
3570
|
-
|
|
3571
|
-
|
|
3572
|
-
|
|
3573
|
-
|
|
3574
|
-
|
|
3575
|
-
|
|
3576
|
-
|
|
3577
|
-
|
|
3578
|
-
const
|
|
3579
|
-
|
|
3580
|
-
|
|
3581
|
-
|
|
3582
|
-
|
|
3583
|
-
|
|
3584
|
-
}
|
|
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
|
+
}
|
|
3585
3728
|
}
|
|
3586
|
-
if ((packageName && packageName.startsWith("@") ? packageName.split("/")[0] : void 0) === "@lark-apaas") return {
|
|
3587
|
-
compatible: true,
|
|
3588
|
-
direction: null,
|
|
3589
|
-
reason: "fork-exempt",
|
|
3590
|
-
resolvedVersion: pluginVersion,
|
|
3591
|
-
entry: void 0
|
|
3592
|
-
};
|
|
3593
|
-
if (extractBaseName(packageName) === "feishu-openclaw-plugin") return {
|
|
3594
|
-
compatible: false,
|
|
3595
|
-
direction: "lark",
|
|
3596
|
-
reason: "legacy",
|
|
3597
|
-
resolvedVersion: pluginVersion,
|
|
3598
|
-
entry: void 0
|
|
3599
|
-
};
|
|
3600
|
-
if (!pluginVersion) return {
|
|
3601
|
-
compatible: false,
|
|
3602
|
-
direction: "lark",
|
|
3603
|
-
reason: "version-unknown",
|
|
3604
|
-
resolvedVersion: void 0,
|
|
3605
|
-
entry: void 0
|
|
3606
|
-
};
|
|
3607
|
-
const r = evaluateVersionRange(pluginVersion, openclawVersion);
|
|
3608
|
-
return {
|
|
3609
|
-
...r,
|
|
3610
|
-
reason: reasonFromRange(r),
|
|
3611
|
-
resolvedVersion: pluginVersion
|
|
3612
|
-
};
|
|
3613
3729
|
}
|
|
3614
3730
|
//#endregion
|
|
3615
3731
|
//#region src/rules/feishu-plugin-version-compat.ts
|
|
@@ -3752,13 +3868,13 @@ function detectInstalledLarkPlugin(ctx) {
|
|
|
3752
3868
|
const allowRaw = asRecord(ctx.config.plugins)?.allow;
|
|
3753
3869
|
const allow = Array.isArray(allowRaw) ? allowRaw.filter((e) => typeof e === "string") : [];
|
|
3754
3870
|
const extDir = getExtensionsDir(ctx.configPath);
|
|
3755
|
-
const installs =
|
|
3871
|
+
const installs = getPluginInstallsMap(ctx.configPath, ctx.config);
|
|
3756
3872
|
for (const name of [LARK_PLUGIN_NAME, ...LEGACY_SHORT_NAMES]) {
|
|
3757
3873
|
if (!allow.includes(name)) continue;
|
|
3758
3874
|
const pkgPath = node_path.default.join(extDir, name, "package.json");
|
|
3759
3875
|
if (!node_fs.default.existsSync(pkgPath)) continue;
|
|
3760
3876
|
const pkg = readPluginPackageJson(pkgPath) ?? {};
|
|
3761
|
-
const installEntry =
|
|
3877
|
+
const installEntry = asRecord(installs[name]);
|
|
3762
3878
|
return {
|
|
3763
3879
|
allowName: name,
|
|
3764
3880
|
fullName: pkg.name ?? extractScopedNameFromSpec$1(installEntry?.spec),
|
|
@@ -4324,8 +4440,7 @@ function readInstalledLarkPlugin(ctx) {
|
|
|
4324
4440
|
} catch {
|
|
4325
4441
|
pkg = {};
|
|
4326
4442
|
}
|
|
4327
|
-
const
|
|
4328
|
-
const installEntry = installs ? asRecord(installs[LARK_PLUGIN_NAME]) : void 0;
|
|
4443
|
+
const installEntry = asRecord(getPluginInstallsMap(ctx.configPath, ctx.config)[LARK_PLUGIN_NAME]);
|
|
4329
4444
|
return {
|
|
4330
4445
|
name: pkg.name ?? extractScopedNameFromSpec(installEntry?.spec),
|
|
4331
4446
|
version: pkg.version ?? (typeof installEntry?.version === "string" ? installEntry.version : void 0)
|
|
@@ -5559,6 +5674,21 @@ async function installExtension(tag, ossFileMap, opts = {}) {
|
|
|
5559
5674
|
}
|
|
5560
5675
|
console.error(`[install-extension] tag=${tag} targets=${targets.length}`);
|
|
5561
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
|
+
}
|
|
5562
5692
|
const larkTarget = opts.skipConfigUpdate ? void 0 : targets.find((p) => p.name === LARK_PLUGIN_NAME);
|
|
5563
5693
|
if (larkTarget) {
|
|
5564
5694
|
if (await installLarkPluginWithCompatCheck({
|
|
@@ -5599,12 +5729,13 @@ async function installExtension(tag, ossFileMap, opts = {}) {
|
|
|
5599
5729
|
}
|
|
5600
5730
|
const PLUGINS_TO_AUTO_ENABLE = [LARK_PLUGIN_NAME, MIAODA_PLUGIN_NAME];
|
|
5601
5731
|
/**
|
|
5602
|
-
* Merge each installed extension's installMetadata into
|
|
5603
|
-
*
|
|
5732
|
+
* Merge each installed extension's installMetadata into the plugin installs
|
|
5733
|
+
* store. Old sandboxes only have openclaw.json.plugins.installs; newer
|
|
5734
|
+
* templates also carry plugins/installs.json and keep openclaw.json as a mirror.
|
|
5604
5735
|
*
|
|
5605
5736
|
* - No openclaw.json → log + return (not an error; some install contexts don't have it yet)
|
|
5606
5737
|
* - Extension without installMetadata in manifest → skip that entry (log)
|
|
5607
|
-
* - Existing
|
|
5738
|
+
* - Existing install entries for other extensions left untouched
|
|
5608
5739
|
*
|
|
5609
5740
|
* Special-case for mem0: when openclaw-mem0-plugin is among the installed
|
|
5610
5741
|
* targets, also append it to plugins.allow (idempotent) and seed
|
|
@@ -5613,7 +5744,7 @@ const PLUGINS_TO_AUTO_ENABLE = [LARK_PLUGIN_NAME, MIAODA_PLUGIN_NAME];
|
|
|
5613
5744
|
*/
|
|
5614
5745
|
function updatePluginInstalls(configPath, installedPkgs) {
|
|
5615
5746
|
if (!node_fs.default.existsSync(configPath)) {
|
|
5616
|
-
console.error(`[install-extension] no config at ${configPath} — skip
|
|
5747
|
+
console.error(`[install-extension] no config at ${configPath} — skip plugin installs update`);
|
|
5617
5748
|
return;
|
|
5618
5749
|
}
|
|
5619
5750
|
const JSON5 = loadJSON5();
|
|
@@ -5621,8 +5752,7 @@ function updatePluginInstalls(configPath, installedPkgs) {
|
|
|
5621
5752
|
const config = JSON5.parse(raw);
|
|
5622
5753
|
if (!config.plugins || typeof config.plugins !== "object") config.plugins = {};
|
|
5623
5754
|
const plugins = config.plugins;
|
|
5624
|
-
|
|
5625
|
-
const installs = plugins.installs;
|
|
5755
|
+
const installs = { ...getPluginInstallsMap(configPath, config) };
|
|
5626
5756
|
let updated = 0;
|
|
5627
5757
|
let skipped = 0;
|
|
5628
5758
|
for (const pkg of installedPkgs) if (pkg.installMetadata) {
|
|
@@ -5650,10 +5780,11 @@ function updatePluginInstalls(configPath, installedPkgs) {
|
|
|
5650
5780
|
enabled: true
|
|
5651
5781
|
};
|
|
5652
5782
|
}
|
|
5783
|
+
setPluginInstallsMap(configPath, config, installs);
|
|
5653
5784
|
const tmpPath = configPath + ".installs-tmp";
|
|
5654
5785
|
node_fs.default.writeFileSync(tmpPath, JSON.stringify(config, null, 2), "utf-8");
|
|
5655
5786
|
moveSafe(tmpPath, configPath);
|
|
5656
|
-
console.error(`[install-extension]
|
|
5787
|
+
console.error(`[install-extension] plugin installs updated: ${updated} entry(ies) for ${configPath}` + (skipped > 0 ? ` (${skipped} package(s) without installMetadata skipped)` : ""));
|
|
5657
5788
|
}
|
|
5658
5789
|
function installOne$1(pkg, tarball, homeBase) {
|
|
5659
5790
|
const destDir = node_path.default.join(homeBase, pkg.installPath);
|
|
@@ -5685,6 +5816,41 @@ function installOne$1(pkg, tarball, homeBase) {
|
|
|
5685
5816
|
force: true
|
|
5686
5817
|
});
|
|
5687
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
|
+
}
|
|
5688
5854
|
/**
|
|
5689
5855
|
* Install openclaw-lark with a pre-flight compatibility check.
|
|
5690
5856
|
*
|
|
@@ -11253,7 +11419,7 @@ async function reportCliRun(opts) {
|
|
|
11253
11419
|
//#region src/help.ts
|
|
11254
11420
|
const BIN = "mclaw-diagnose";
|
|
11255
11421
|
function versionBanner() {
|
|
11256
|
-
return `v0.1.
|
|
11422
|
+
return `v0.1.21-alpha.1`;
|
|
11257
11423
|
}
|
|
11258
11424
|
const COMMANDS = [
|
|
11259
11425
|
{
|
|
@@ -11447,7 +11613,7 @@ OPTIONS
|
|
|
11447
11613
|
DESCRIPTION
|
|
11448
11614
|
Downloads + installs one or more openclaw extension tarballs
|
|
11449
11615
|
(feishu, miaoda, etc.) into <home_base>/workspace/agent/extensions/,
|
|
11450
|
-
then splices installMetadata into
|
|
11616
|
+
then splices installMetadata into the plugin installs store
|
|
11451
11617
|
unless --skip-config-update is passed.
|
|
11452
11618
|
|
|
11453
11619
|
ARGUMENTS
|
|
@@ -11459,7 +11625,7 @@ OPTIONS
|
|
|
11459
11625
|
--extension=<name> Install a specific extension (repeatable).
|
|
11460
11626
|
--home_base=<dir> Override the /home/gem base (tests).
|
|
11461
11627
|
--config_path=<p> Override the openclaw.json path (tests).
|
|
11462
|
-
--skip-config-update Leave
|
|
11628
|
+
--skip-config-update Leave plugin install records untouched.
|
|
11463
11629
|
--oss_file_map=... Pre-built OSS URL map (base64 JSON); skips innerapi.
|
|
11464
11630
|
`
|
|
11465
11631
|
},
|