

























@@ -71,6 +71,7 @@ import {
7171restorePluginInteractiveHandlers,
7272} from "./interactive-registry.js";
7373import { getCachedPluginJitiLoader, type PluginJitiLoaderCache } from "./jiti-loader-cache.js";
74+import { PluginLoaderCacheState } from "./loader-cache-state.js";
7475import { loadPluginManifestRegistry, type PluginManifestRecord } from "./manifest-registry.js";
7576import type { PluginBundleFormat, PluginDiagnostic, PluginFormat } from "./manifest-types.js";
7677import type { PluginManifestContracts } from "./manifest.js";
@@ -134,6 +135,7 @@ import type {
134135} from "./types.js";
135136136137export type PluginLoadResult = PluginRegistry;
138+export { PluginLoadReentryError } from "./loader-cache-state.js";
137139138140export type PluginLoadOptions = {
139141config?: OpenClawConfig;
@@ -207,16 +209,6 @@ export class PluginLoadFailureError extends Error {
207209}
208210}
209211210-export class PluginLoadReentryError extends Error {
211-readonly cacheKey: string;
212-213-constructor(cacheKey: string) {
214-super(`plugin load reentry detected for cache key: ${cacheKey}`);
215-this.name = "PluginLoadReentryError";
216-this.cacheKey = cacheKey;
217-}
218-}
219-220212type CachedPluginState = {
221213registry: PluginRegistry;
222214detachedTaskRuntimeRegistration: ReturnType<typeof getDetachedTaskLifecycleRuntimeRegistration>;
@@ -234,10 +226,9 @@ type CachedPluginState = {
234226};
235227236228const MAX_PLUGIN_REGISTRY_CACHE_ENTRIES = 128;
237-let pluginRegistryCacheEntryCap = MAX_PLUGIN_REGISTRY_CACHE_ENTRIES;
238-const registryCache = new Map<string, CachedPluginState>();
239-const inFlightPluginRegistryLoads = new Set<string>();
240-const openAllowlistWarningCache = new Set<string>();
229+const pluginLoaderCacheState = new PluginLoaderCacheState<CachedPluginState>(
230+MAX_PLUGIN_REGISTRY_CACHE_ENTRIES,
231+);
241232const LAZY_RUNTIME_REFLECTION_KEYS = [
242233"version",
243234"config",
@@ -255,9 +246,7 @@ const LAZY_RUNTIME_REFLECTION_KEYS = [
255246] as const satisfies readonly (keyof PluginRuntime)[];
256247257248export function clearPluginLoaderCache(): void {
258-registryCache.clear();
259-inFlightPluginRegistryLoads.clear();
260-openAllowlistWarningCache.clear();
249+pluginLoaderCacheState.clear();
261250clearBundledRuntimeDependencyNodePaths();
262251bundledRuntimeDependencyJitiAliases.clear();
263252clearAgentHarnesses();
@@ -949,39 +938,19 @@ export const __testing = {
949938 getCompatibleActivePluginRegistry,
950939 resolvePluginLoadCacheContext,
951940get maxPluginRegistryCacheEntries() {
952-return pluginRegistryCacheEntryCap;
941+return pluginLoaderCacheState.maxEntries;
953942},
954943setMaxPluginRegistryCacheEntriesForTest(value?: number) {
955-pluginRegistryCacheEntryCap =
956-typeof value === "number" && Number.isFinite(value) && value > 0
957- ? Math.max(1, Math.floor(value))
958- : MAX_PLUGIN_REGISTRY_CACHE_ENTRIES;
944+pluginLoaderCacheState.setMaxEntriesForTest(value);
959945},
960946};
961947962948function getCachedPluginRegistry(cacheKey: string): CachedPluginState | undefined {
963-const cached = registryCache.get(cacheKey);
964-if (!cached) {
965-return undefined;
966-}
967-// Refresh insertion order so frequently reused registries survive eviction.
968-registryCache.delete(cacheKey);
969-registryCache.set(cacheKey, cached);
970-return cached;
949+return pluginLoaderCacheState.get(cacheKey);
971950}
972951973952function setCachedPluginRegistry(cacheKey: string, state: CachedPluginState): void {
974-if (registryCache.has(cacheKey)) {
975-registryCache.delete(cacheKey);
976-}
977-registryCache.set(cacheKey, state);
978-while (registryCache.size > pluginRegistryCacheEntryCap) {
979-const oldestKey = registryCache.keys().next().value;
980-if (!oldestKey) {
981-break;
982-}
983-registryCache.delete(oldestKey);
984-}
953+pluginLoaderCacheState.set(cacheKey, state);
985954}
986955987956function buildCacheKey(params: {
@@ -1398,7 +1367,7 @@ export function resolvePluginRegistryLoadCacheKey(options: PluginLoadOptions = {
13981367}
1399136814001369export function isPluginRegistryLoadInFlight(options: PluginLoadOptions = {}): boolean {
1401-return inFlightPluginRegistryLoads.has(resolvePluginRegistryLoadCacheKey(options));
1370+return pluginLoaderCacheState.isLoadInFlight(resolvePluginRegistryLoadCacheKey(options));
14021371}
1403137214041373export function resolveCompatibleRuntimePluginRegistry(
@@ -2089,15 +2058,15 @@ function warnWhenAllowlistIsOpen(params: {
20892058if (autoDiscoverable.length === 0) {
20902059return;
20912060}
2092-if (openAllowlistWarningCache.has(params.warningCacheKey)) {
2061+if (pluginLoaderCacheState.hasOpenAllowlistWarning(params.warningCacheKey)) {
20932062return;
20942063}
20952064const preview = autoDiscoverable
20962065.slice(0, 6)
20972066.map((entry) => `${entry.id} (${entry.source})`)
20982067.join(", ");
20992068const extra = autoDiscoverable.length > 6 ? ` (+${autoDiscoverable.length - 6} more)` : "";
2100-openAllowlistWarningCache.add(params.warningCacheKey);
2069+pluginLoaderCacheState.recordOpenAllowlistWarning(params.warningCacheKey);
21012070params.logger.warn(
21022071`[plugins] plugins.allow is empty; discovered non-bundled plugins may auto-load: ${preview}${extra}. Set plugins.allow to explicit trusted ids.`,
21032072);
@@ -2210,10 +2179,7 @@ export function loadOpenClawPlugins(options: PluginLoadOptions = {}): PluginRegi
22102179return cached.registry;
22112180}
22122181}
2213-if (inFlightPluginRegistryLoads.has(cacheKey)) {
2214-throw new PluginLoadReentryError(cacheKey);
2215-}
2216-inFlightPluginRegistryLoads.add(cacheKey);
2182+pluginLoaderCacheState.beginLoad(cacheKey);
22172183try {
22182184// Clear previously registered plugin state before reloading.
22192185// Skip for non-activating (snapshot) loads to avoid wiping commands from other plugins.
@@ -3195,7 +3161,7 @@ export function loadOpenClawPlugins(options: PluginLoadOptions = {}): PluginRegi
31953161}
31963162return registry;
31973163} finally {
3198-inFlightPluginRegistryLoads.delete(cacheKey);
3164+pluginLoaderCacheState.finishLoad(cacheKey);
31993165}
32003166}
32013167此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。