fix: scope subagent registry reuse to tool loading (#56240) (thanks @GodsBoy)

* fix(plugins): reuse active registry for sub-agent tool resolution

* test(plugins): harden resolveRuntimePluginRegistry with per-field, caller-shape, and cold-start tests

Add 11 regression tests covering:
- R1: Per-field isolation (coreGatewayHandlers, includeSetupOnlyChannelPlugins,
  preferSetupRuntimeForChannelPlugins each independently prevent fallback;
  empty onlyPluginIds[] treated as non-gateway-scoped)
- R2: Caller-shape regression (tools.ts, memory-runtime.ts,
  channel-resolution.ts shapes fall back; web-search-providers.runtime.ts
  with onlyPluginIds does not)
- R3: Cold-start path (null active registry falls through to loadOpenClawPlugins)

Add debug logging to resolveRuntimePluginRegistry recording which exit path
was taken (no-options, cache-key-match, non-gateway-scoped fallback, fresh load).

* refactor: simplify plugin registry resolution tests and trim happy-path debug logs

* fix(plugins): address review comments on registry fallback

- Fix cold-start test assertion: loadOpenClawPlugins always activates
  the registry (shouldActivate defaults to true), so getActivePluginRegistry()
  is not null after the call. Updated assertion to match actual behavior.

- Add safety comment documenting why the non-gateway-scoped fallback is
  safe despite cache-key mismatch: single-gateway-per-process model means
  sub-agents share workspaceDir, config, and env with the gateway.

* test(plugins): restructure per-field isolation tests to avoid load timeouts

Test isGatewayScopedLoad directly instead of going through the full
resolveRuntimePluginRegistry path which triggers expensive plugin
discovery. This fixes the includeSetupOnlyChannelPlugins test timing
out in CI while providing more precise coverage of the predicate.

* fix(plugins): expand safety comment to address startup-scoped registry concern

* fix(plugins): scope subagent registry reuse to tool loading

---------

Co-authored-by: Ayaan Zaidi <hi@obviy.us>
This commit is contained in:
Dewaldt Huysamen
2026-03-29 06:06:09 +02:00
committed by GitHub
parent 6883f688e8
commit 27188fa39f
2 changed files with 57 additions and 8 deletions

View File

@@ -21,6 +21,7 @@ vi.mock("../config/plugin-auto-enable.js", () => ({
let resolvePluginTools: typeof import("./tools.js").resolvePluginTools;
let resetPluginRuntimeStateForTest: typeof import("./runtime.js").resetPluginRuntimeStateForTest;
let setActivePluginRegistry: typeof import("./runtime.js").setActivePluginRegistry;
function makeTool(name: string) {
return {
@@ -204,7 +205,7 @@ describe("resolvePluginTools optional tools", () => {
config,
changes: [],
}));
({ resetPluginRuntimeStateForTest } = await import("./runtime.js"));
({ resetPluginRuntimeStateForTest, setActivePluginRegistry } = await import("./runtime.js"));
resetPluginRuntimeStateForTest();
({ resolvePluginTools } = await import("./tools.js"));
});
@@ -345,4 +346,39 @@ describe("resolvePluginTools optional tools", () => {
expectResolvedToolNames(tools, ["optional_tool"]);
expect(loadOpenClawPluginsMock).not.toHaveBeenCalled();
});
it("reuses the active registry for gateway-bindable tool loads before reloading", () => {
const activeRegistry = createOptionalDemoActiveRegistry();
setActivePluginRegistry(activeRegistry as never, "gateway-startup");
resolveRuntimePluginRegistryMock.mockReturnValue(undefined);
const tools = resolvePluginTools(
createResolveToolsParams({
toolAllowlist: ["optional_tool"],
allowGatewaySubagentBinding: true,
}),
);
expectResolvedToolNames(tools, ["optional_tool"]);
expect(resolveRuntimePluginRegistryMock).not.toHaveBeenCalled();
expect(loadOpenClawPluginsMock).not.toHaveBeenCalled();
});
it("loads plugin tools when gateway-bindable tool loads have no active registry", () => {
setOptionalDemoRegistry();
const tools = resolvePluginTools(
createResolveToolsParams({
toolAllowlist: ["optional_tool"],
allowGatewaySubagentBinding: true,
}),
);
expectResolvedToolNames(tools, ["optional_tool"]);
expectLoaderCall({
runtimeOptions: {
allowGatewaySubagentBinding: true,
},
});
});
});