plugins: exclude runtimeSubagentMode from loader cache key

The plugin loader cache key included runtimeSubagentMode, which is
derived from allowGatewaySubagentBinding. Since different call sites in
the message processing pipeline pass different values for this flag,
each call produced a distinct cache key, triggering redundant
register() calls (40+ in 24 seconds after startup).

runtimeSubagentMode does not affect which plugins are loaded or how
they are configured — it is only metadata stored alongside the active
registry state. Removing it from the cache key lets all call sites
share the same cached registry regardless of their binding mode.

Fixes #61756
This commit is contained in:
HansY
2026-04-06 12:14:54 +00:00
committed by Peter Steinberger
parent f18a705d19
commit c78defdc2f
2 changed files with 35 additions and 11 deletions

View File

@@ -1753,20 +1753,20 @@ module.exports = { id: "throws-after-import", register() {} };`,
},
},
{
name: "does not reuse cached registries across gateway subagent binding modes",
name: "does not reuse cached registries across different plugin SDK resolution preferences",
setup: () => {
useNoBundledPlugins();
const plugin = writePlugin({
id: "cache-gateway-bindable",
filename: "cache-gateway-bindable.cjs",
body: `module.exports = { id: "cache-gateway-bindable", register() {} };`,
id: "cache-sdk-resolution",
filename: "cache-sdk-resolution.cjs",
body: `module.exports = { id: "cache-sdk-resolution", register() {} };`,
});
const options = {
workspaceDir: plugin.dir,
config: {
plugins: {
allow: ["cache-gateway-bindable"],
allow: ["cache-sdk-resolution"],
load: {
paths: [plugin.file],
},
@@ -1779,9 +1779,7 @@ module.exports = { id: "throws-after-import", register() {} };`,
loadVariant: () =>
loadOpenClawPlugins({
...options,
runtimeOptions: {
allowGatewaySubagentBinding: true,
},
pluginSdkResolution: "workspace" as const,
}),
};
},
@@ -1790,6 +1788,34 @@ module.exports = { id: "throws-after-import", register() {} };`,
expectCacheMissThenHit(setup());
});
it("reuses cached registry across gateway subagent binding modes", () => {
useNoBundledPlugins();
const plugin = writePlugin({
id: "cache-gateway-shared",
filename: "cache-gateway-shared.cjs",
body: `module.exports = { id: "cache-gateway-shared", register() {} };`,
});
const options = {
workspaceDir: plugin.dir,
config: {
plugins: {
allow: ["cache-gateway-shared"],
load: {
paths: [plugin.file],
},
},
},
};
const first = loadOpenClawPlugins(options);
const second = loadOpenClawPlugins({
...options,
runtimeOptions: { allowGatewaySubagentBinding: true },
});
expect(second).toBe(first);
});
it("evicts least recently used registries when the loader cache exceeds its cap", () => {
useNoBundledPlugins();
const plugin = writePlugin({

View File

@@ -285,7 +285,6 @@ function buildCacheKey(params: {
includeSetupOnlyChannelPlugins?: boolean;
preferSetupRuntimeForChannelPlugins?: boolean;
loadModules?: boolean;
runtimeSubagentMode?: "default" | "explicit" | "gateway-bindable";
pluginSdkResolution?: PluginSdkResolutionPreference;
coreGatewayMethodNames?: string[];
}): string {
@@ -321,7 +320,7 @@ function buildCacheKey(params: {
installs,
loadPaths,
activationMetadataKey: params.activationMetadataKey ?? "",
})}::${scopeKey}::${setupOnlyKey}::${startupChannelMode}::${moduleLoadMode}::${params.runtimeSubagentMode ?? "default"}::${params.pluginSdkResolution ?? "auto"}::${gatewayMethodsKey}`;
})}::${scopeKey}::${setupOnlyKey}::${startupChannelMode}::${moduleLoadMode}::${params.pluginSdkResolution ?? "auto"}::${gatewayMethodsKey}`;
}
function normalizeScopedPluginIds(ids?: string[]): string[] | undefined {
@@ -434,7 +433,6 @@ function resolvePluginLoadCacheContext(options: PluginLoadOptions = {}) {
includeSetupOnlyChannelPlugins,
preferSetupRuntimeForChannelPlugins,
loadModules: options.loadModules,
runtimeSubagentMode: resolveRuntimeSubagentMode(options.runtimeOptions),
pluginSdkResolution: options.pluginSdkResolution,
coreGatewayMethodNames,
});