























@@ -33,6 +33,20 @@ import type { PluginKind } from "./plugin-kind.types.js";
3333export const PLUGIN_MANIFEST_FILENAME = "openclaw.plugin.json";
3434export const PLUGIN_MANIFEST_FILENAMES = [PLUGIN_MANIFEST_FILENAME] as const;
3535export const MAX_PLUGIN_MANIFEST_BYTES = 256 * 1024;
36+const MAX_PLUGIN_MANIFEST_LOAD_CACHE_ENTRIES = 512;
37+38+type PluginManifestLoadCacheEntry = {
39+result: PluginManifestLoadResult;
40+size: number;
41+mtimeMs: number;
42+ctimeMs: number;
43+};
44+45+const pluginManifestLoadCache = new Map<string, PluginManifestLoadCacheEntry>();
46+47+export function clearPluginManifestLoadCache(): void {
48+pluginManifestLoadCache.clear();
49+}
36503751export type PluginManifestChannelConfig = {
3852schema: JsonSchemaObject;
@@ -1148,6 +1162,62 @@ export function resolvePluginManifestPath(rootDir: string): string {
11481162return path.join(rootDir, PLUGIN_MANIFEST_FILENAME);
11491163}
115011641165+function buildPluginManifestLoadCacheKey(params: {
1166+manifestPath: string;
1167+rejectHardlinks: boolean;
1168+rootRealPath?: string;
1169+stats: fs.Stats;
1170+}): string {
1171+return JSON.stringify([
1172+path.resolve(params.manifestPath),
1173+params.rejectHardlinks,
1174+params.rootRealPath ?? "",
1175+params.stats.dev,
1176+params.stats.ino,
1177+params.stats.size,
1178+params.stats.mtimeMs,
1179+params.stats.ctimeMs,
1180+]);
1181+}
1182+1183+function getCachedPluginManifestLoadResult(
1184+key: string,
1185+stats: fs.Stats,
1186+): PluginManifestLoadResult | undefined {
1187+const entry = pluginManifestLoadCache.get(key);
1188+if (
1189+!entry ||
1190+entry.size !== stats.size ||
1191+entry.mtimeMs !== stats.mtimeMs ||
1192+entry.ctimeMs !== stats.ctimeMs
1193+) {
1194+return undefined;
1195+}
1196+pluginManifestLoadCache.delete(key);
1197+pluginManifestLoadCache.set(key, entry);
1198+return entry.result;
1199+}
1200+1201+function setCachedPluginManifestLoadResult(
1202+key: string,
1203+stats: fs.Stats,
1204+result: PluginManifestLoadResult,
1205+): void {
1206+pluginManifestLoadCache.set(key, {
1207+ result,
1208+size: stats.size,
1209+mtimeMs: stats.mtimeMs,
1210+ctimeMs: stats.ctimeMs,
1211+});
1212+if (pluginManifestLoadCache.size <= MAX_PLUGIN_MANIFEST_LOAD_CACHE_ENTRIES) {
1213+return;
1214+}
1215+const oldestKey = pluginManifestLoadCache.keys().next().value;
1216+if (typeof oldestKey === "string") {
1217+pluginManifestLoadCache.delete(oldestKey);
1218+}
1219+}
1220+11511221function parsePluginKind(raw: unknown): PluginKind | PluginKind[] | undefined {
11521222if (typeof raw === "string") {
11531223return raw as PluginKind;
@@ -1186,28 +1256,44 @@ export function loadPluginManifest(
11861256}),
11871257});
11881258}
1259+const stats = opened.stat;
1260+const cacheKey = buildPluginManifestLoadCacheKey({
1261+ manifestPath,
1262+ rejectHardlinks,
1263+ ...(rootRealPath !== undefined ? { rootRealPath } : {}),
1264+ stats,
1265+});
1266+const cached = getCachedPluginManifestLoadResult(cacheKey, stats);
1267+if (cached) {
1268+fs.closeSync(opened.fd);
1269+return cached;
1270+}
1271+const cacheResult = (result: PluginManifestLoadResult): PluginManifestLoadResult => {
1272+setCachedPluginManifestLoadResult(cacheKey, stats, result);
1273+return result;
1274+};
11891275let raw: unknown;
11901276try {
11911277raw = parseJsonWithJson5Fallback(fs.readFileSync(opened.fd, "utf-8"));
11921278} catch (err) {
1193-return {
1279+return cacheResult({
11941280ok: false,
11951281error: `failed to parse plugin manifest: ${String(err)}`,
11961282 manifestPath,
1197-};
1283+});
11981284} finally {
11991285fs.closeSync(opened.fd);
12001286}
12011287if (!isRecord(raw)) {
1202-return { ok: false, error: "plugin manifest must be an object", manifestPath };
1288+return cacheResult({ ok: false, error: "plugin manifest must be an object", manifestPath });
12031289}
12041290const id = normalizeOptionalString(raw.id) ?? "";
12051291if (!id) {
1206-return { ok: false, error: "plugin manifest requires id", manifestPath };
1292+return cacheResult({ ok: false, error: "plugin manifest requires id", manifestPath });
12071293}
12081294const configSchema = isRecord(raw.configSchema) ? raw.configSchema : null;
12091295if (!configSchema) {
1210-return { ok: false, error: "plugin manifest requires configSchema", manifestPath };
1296+return cacheResult({ ok: false, error: "plugin manifest requires configSchema", manifestPath });
12111297}
1212129812131299const kind = parsePluginKind(raw.kind);
@@ -1260,7 +1346,7 @@ export function loadPluginManifest(
12601346uiHints = raw.uiHints as Record<string, PluginConfigUiHint>;
12611347}
126213481263-return {
1349+return cacheResult({
12641350ok: true,
12651351manifest: {
12661352 id,
@@ -1302,7 +1388,7 @@ export function loadPluginManifest(
13021388 channelConfigs,
13031389},
13041390 manifestPath,
1305-};
1391+});
13061392}
1307139313081394// package.json "openclaw" metadata (used for setup/catalog)
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。