From 1c45592e620e41788d3fbf4a33c719510d61d6e8 Mon Sep 17 00:00:00 2001 From: Super Zheng Date: Wed, 29 Apr 2026 18:04:21 +0800 Subject: [PATCH] perf(plugins): add O(1) fast-path for empty plugin loads --- src/plugins/loader.test.ts | 33 +++++++++++++++++++++++++++++++++ src/plugins/loader.ts | 20 ++++++++++++++++++++ 2 files changed, 53 insertions(+) diff --git a/src/plugins/loader.test.ts b/src/plugins/loader.test.ts index c1dd8d56093..0b3900178b7 100644 --- a/src/plugins/loader.test.ts +++ b/src/plugins/loader.test.ts @@ -3285,6 +3285,39 @@ module.exports = { id: "throws-after-import", register() {} };`, expect(registry.plugins).toEqual([]); }); + it("skips discovery and manifest registry loading entirely when onlyPluginIds is an explicit empty array", async () => { + useNoBundledPlugins(); + const allowed = writePlugin({ + id: "allowed-empty-scope", + filename: "allowed-empty-scope.cjs", + body: `module.exports = { id: "allowed-empty-scope", register() {} };`, + }); + + const discovery = await import("./discovery.js"); + const manifestRegistry = await import("./manifest-registry.js"); + const discoverySpy = vi.spyOn(discovery, "discoverOpenClawPlugins"); + const manifestSpy = vi.spyOn(manifestRegistry, "loadPluginManifestRegistry"); + + const registry = loadOpenClawPlugins({ + cache: false, + activate: false, + config: { + plugins: { + load: { paths: [allowed.file] }, + allow: ["allowed-empty-scope"], + }, + }, + onlyPluginIds: [], + }); + + expect(registry.plugins).toEqual([]); + expect(discoverySpy).not.toHaveBeenCalled(); + expect(manifestSpy).not.toHaveBeenCalled(); + + discoverySpy.mockRestore(); + manifestSpy.mockRestore(); + }); + it("only publishes plugin commands to the global registry during activating loads", async () => { useNoBundledPlugins(); const plugin = writePlugin({ diff --git a/src/plugins/loader.ts b/src/plugins/loader.ts index f3c7db47fe5..2e738334e82 100644 --- a/src/plugins/loader.ts +++ b/src/plugins/loader.ts @@ -115,6 +115,7 @@ import { serializePluginIdScope, } from "./plugin-scope.js"; import { createPluginRegistry, type PluginRecord, type PluginRegistry } from "./registry.js"; +import { createEmptyPluginRegistry } from "./registry-empty.js"; import { resolvePluginCacheInputs } from "./roots.js"; import { getActivePluginRegistry, @@ -2233,6 +2234,25 @@ export function loadOpenClawPlugins(options: PluginLoadOptions = {}): PluginRegi const logger = options.logger ?? defaultLogger(); const validateOnly = options.mode === "validate"; const onlyPluginIdSet = createPluginIdScopeSet(onlyPluginIds); + + if (onlyPluginIdSet && onlyPluginIdSet.size === 0) { + const emptyRegistry = createEmptyPluginRegistry(); + if (shouldActivate) { + clearAgentHarnesses(); + clearPluginCommands(); + clearPluginInteractiveHandlers(); + clearDetachedTaskLifecycleRuntimeRegistration(); + clearMemoryPluginState(); + activatePluginRegistry( + emptyRegistry, + cacheKey, + runtimeSubagentMode, + options.workspaceDir, + ); + } + return emptyRegistry; + } + const cacheEnabled = options.cache !== false; if (cacheEnabled) { const cached = getCachedPluginRegistry(cacheKey);