diff --git a/src/plugins/loader.runtime-registry.test.ts b/src/plugins/loader.runtime-registry.test.ts index 85fb4e18364..e0bb9c8f47d 100644 --- a/src/plugins/loader.runtime-registry.test.ts +++ b/src/plugins/loader.runtime-registry.test.ts @@ -1,5 +1,10 @@ import { afterEach, describe, expect, it } from "vitest"; -import { __testing, clearPluginLoaderCache, resolveRuntimePluginRegistry } from "./loader.js"; +import { + __testing, + clearPluginLoaderCache, + loadOpenClawPlugins, + resolveRuntimePluginRegistry, +} from "./loader.js"; import { resetPluginLoaderTestStateForTest } from "./loader.test-fixtures.js"; import { getMemoryEmbeddingProvider, @@ -392,6 +397,28 @@ describe("resolveRuntimePluginRegistry", () => { expect(scopedEmpty).not.toBe(registry); expect(scopedEmpty?.plugins).toEqual([]); }); + + it("keeps the full workspace registry warm when scoped cron registries churn", () => { + __testing.setMaxPluginRegistryCacheEntriesForTest(2); + try { + const loadOptions = { + config: { + plugins: { + allow: ["alpha", "bravo", "charlie"], + }, + }, + workspaceDir: "/tmp/workspace-a", + }; + const fullRegistry = loadOpenClawPlugins(loadOptions); + + loadOpenClawPlugins({ ...loadOptions, onlyPluginIds: ["alpha"] }); + loadOpenClawPlugins({ ...loadOptions, onlyPluginIds: ["bravo"] }); + + expect(resolveRuntimePluginRegistry(loadOptions)).toBe(fullRegistry); + } finally { + __testing.setMaxPluginRegistryCacheEntriesForTest(); + } + }); }); describe("clearPluginLoaderCache", () => { diff --git a/src/plugins/loader.ts b/src/plugins/loader.ts index 76c565a9052..f8dfc6edec0 100644 --- a/src/plugins/loader.ts +++ b/src/plugins/loader.ts @@ -249,6 +249,9 @@ const MAX_PLUGIN_REGISTRY_CACHE_ENTRIES = 128; const pluginLoaderCacheState = new PluginLoaderCacheState( MAX_PLUGIN_REGISTRY_CACHE_ENTRIES, ); +const fullWorkspacePluginLoaderCacheState = new PluginLoaderCacheState( + MAX_PLUGIN_REGISTRY_CACHE_ENTRIES, +); const LAZY_RUNTIME_REFLECTION_KEYS = [ "version", "config", @@ -281,6 +284,7 @@ function createPluginCandidatesFromManifestRegistry( export function clearPluginLoaderCache(): void { pluginLoaderCacheState.clear(); + fullWorkspacePluginLoaderCacheState.clear(); clearAgentHarnesses(); clearPluginCommands(); clearCompactionProviders(); @@ -525,15 +529,27 @@ export const __testing = { }, setMaxPluginRegistryCacheEntriesForTest(value?: number) { pluginLoaderCacheState.setMaxEntriesForTest(value); + fullWorkspacePluginLoaderCacheState.setMaxEntriesForTest(value); }, }; -function getCachedPluginRegistry(cacheKey: string): CachedPluginState | undefined { - return pluginLoaderCacheState.get(cacheKey); +function getPluginRegistryCache(onlyPluginIds?: string[]) { + return onlyPluginIds ? pluginLoaderCacheState : fullWorkspacePluginLoaderCacheState; } -function setCachedPluginRegistry(cacheKey: string, state: CachedPluginState): void { - pluginLoaderCacheState.set(cacheKey, state); +function getCachedPluginRegistry( + cacheKey: string, + onlyPluginIds?: string[], +): CachedPluginState | undefined { + return getPluginRegistryCache(onlyPluginIds).get(cacheKey); +} + +function setCachedPluginRegistry( + cacheKey: string, + state: CachedPluginState, + onlyPluginIds?: string[], +): void { + getPluginRegistryCache(onlyPluginIds).set(cacheKey, state); } function resolveBundledPackageRootForCache(stockRoot?: string): string | undefined { @@ -1225,7 +1241,7 @@ export function loadOpenClawPlugins(options: PluginLoadOptions = {}): PluginRegi const cacheEnabled = options.cache !== false; if (cacheEnabled) { - const cached = getCachedPluginRegistry(cacheKey); + const cached = getCachedPluginRegistry(cacheKey, onlyPluginIds); if (cached) { if (shouldActivate) { restoreRegisteredAgentHarnesses(cached.agentHarnesses); @@ -2142,21 +2158,25 @@ export function loadOpenClawPlugins(options: PluginLoadOptions = {}): PluginRegi } if (cacheEnabled) { - setCachedPluginRegistry(cacheKey, { - commands: listRegisteredPluginCommands(), - detachedTaskRuntimeRegistration: getDetachedTaskLifecycleRuntimeRegistration(), - interactiveHandlers: listPluginInteractiveHandlers(), - memoryCapability: getMemoryCapabilityRegistration(), - memoryCorpusSupplements: listMemoryCorpusSupplements(), - registry, - agentHarnesses: listRegisteredAgentHarnesses(), - compactionProviders: listRegisteredCompactionProviders(), - memoryEmbeddingProviders: listRegisteredMemoryEmbeddingProviders(), - memoryFlushPlanResolver: getMemoryFlushPlanResolver(), - memoryPromptBuilder: getMemoryPromptSectionBuilder(), - memoryPromptSupplements: listMemoryPromptSupplements(), - memoryRuntime: getMemoryRuntime(), - }); + setCachedPluginRegistry( + cacheKey, + { + commands: listRegisteredPluginCommands(), + detachedTaskRuntimeRegistration: getDetachedTaskLifecycleRuntimeRegistration(), + interactiveHandlers: listPluginInteractiveHandlers(), + memoryCapability: getMemoryCapabilityRegistration(), + memoryCorpusSupplements: listMemoryCorpusSupplements(), + registry, + agentHarnesses: listRegisteredAgentHarnesses(), + compactionProviders: listRegisteredCompactionProviders(), + memoryEmbeddingProviders: listRegisteredMemoryEmbeddingProviders(), + memoryFlushPlanResolver: getMemoryFlushPlanResolver(), + memoryPromptBuilder: getMemoryPromptSectionBuilder(), + memoryPromptSupplements: listMemoryPromptSupplements(), + memoryRuntime: getMemoryRuntime(), + }, + onlyPluginIds, + ); } if (shouldActivate) { activatePluginRegistry(registry, cacheKey, runtimeSubagentMode, options.workspaceDir);