perf: scope reply runtime plugin startup

This commit is contained in:
Shakker
2026-05-01 19:29:52 +01:00
parent 44afab628e
commit fac06a2320
5 changed files with 265 additions and 19 deletions

View File

@@ -25,6 +25,7 @@ vi.mock("../config/plugin-auto-enable.js", () => ({
let resolvePluginTools: typeof import("./tools.js").resolvePluginTools;
let buildPluginToolMetadataKey: typeof import("./tools.js").buildPluginToolMetadataKey;
let pinActivePluginChannelRegistry: typeof import("./runtime.js").pinActivePluginChannelRegistry;
let resetPluginRuntimeStateForTest: typeof import("./runtime.js").resetPluginRuntimeStateForTest;
let setActivePluginRegistry: typeof import("./runtime.js").setActivePluginRegistry;
@@ -226,7 +227,8 @@ function expectConflictingCoreNameResolution(params: {
describe("resolvePluginTools optional tools", () => {
beforeAll(async () => {
({ buildPluginToolMetadataKey, resolvePluginTools } = await import("./tools.js"));
({ resetPluginRuntimeStateForTest, setActivePluginRegistry } = await import("./runtime.js"));
({ pinActivePluginChannelRegistry, resetPluginRuntimeStateForTest, setActivePluginRegistry } =
await import("./runtime.js"));
});
beforeEach(() => {
@@ -544,7 +546,7 @@ describe("resolvePluginTools optional tools", () => {
expect(loadOpenClawPluginsMock).not.toHaveBeenCalled();
});
it("routes gateway-bindable tool loads through scoped runtime compatibility", () => {
it("reuses the gateway-bindable registry when it covers the tool runtime scope", () => {
const activeRegistry = createOptionalDemoActiveRegistry();
setActivePluginRegistry(activeRegistry as never, "gateway-startup", "gateway-bindable");
resolveRuntimePluginRegistryMock.mockReturnValue(activeRegistry);
@@ -557,14 +559,7 @@ describe("resolvePluginTools optional tools", () => {
);
expectResolvedToolNames(tools, ["optional_tool"]);
expect(resolveRuntimePluginRegistryMock).toHaveBeenCalledWith(
expect.objectContaining({
onlyPluginIds: ["optional-demo"],
runtimeOptions: {
allowGatewaySubagentBinding: true,
},
}),
);
expect(resolveRuntimePluginRegistryMock).not.toHaveBeenCalled();
expect(loadOpenClawPluginsMock).not.toHaveBeenCalled();
});
@@ -597,6 +592,55 @@ describe("resolvePluginTools optional tools", () => {
);
});
it("reuses the pinned gateway channel registry after provider runtime loads replace active registry", () => {
const gatewayRegistry = createOptionalDemoActiveRegistry();
pinActivePluginChannelRegistry(gatewayRegistry as never);
setActivePluginRegistry(
{
tools: [],
diagnostics: [],
} as never,
"provider-runtime",
"default",
);
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("reuses the pinned gateway channel registry even when the caller omits gateway binding", () => {
const gatewayRegistry = createOptionalDemoActiveRegistry();
pinActivePluginChannelRegistry(gatewayRegistry as never);
setActivePluginRegistry(
{
tools: [],
diagnostics: [],
} as never,
"provider-runtime",
"default",
);
resolveRuntimePluginRegistryMock.mockReturnValue(undefined);
const tools = resolvePluginTools(
createResolveToolsParams({
toolAllowlist: ["optional_tool"],
}),
);
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();

View File

@@ -5,7 +5,12 @@ import { applyTestPluginDefaults, normalizePluginsConfig } from "./config-state.
import { listEnabledInstalledPluginRecords } from "./installed-plugin-index.js";
import { resolveRuntimePluginRegistry, type PluginLoadOptions } from "./loader.js";
import { loadPluginRegistrySnapshot } from "./plugin-registry-snapshot.js";
import { getActivePluginRegistry } from "./runtime.js";
import {
getActivePluginChannelRegistry,
getActivePluginRegistry,
getActivePluginRegistryKey,
getActivePluginRuntimeSubagentMode,
} from "./runtime.js";
import {
buildPluginRuntimeLoadOptions,
resolvePluginRuntimeLoadContext,
@@ -194,18 +199,25 @@ function describeMalformedPluginTool(tool: unknown): string | undefined {
return undefined;
}
function addLoadedPluginIdsFromRegistry(
registry: ReturnType<typeof getActivePluginRegistry>,
pluginIds: Set<string>,
): void {
for (const plugin of registry?.plugins ?? []) {
if (plugin.status === undefined || plugin.status === "loaded") {
pluginIds.add(plugin.id);
}
}
}
function resolvePluginToolRuntimePluginIds(params: {
config: PluginLoadOptions["config"];
workspaceDir?: string;
env: NodeJS.ProcessEnv;
}): string[] | undefined {
const pluginIds = new Set<string>();
const activeRegistry = getActivePluginRegistry();
for (const plugin of activeRegistry?.plugins ?? []) {
if (plugin.status === undefined || plugin.status === "loaded") {
pluginIds.add(plugin.id);
}
}
addLoadedPluginIdsFromRegistry(getActivePluginChannelRegistry(), pluginIds);
addLoadedPluginIdsFromRegistry(getActivePluginRegistry(), pluginIds);
const index = loadPluginRegistrySnapshot({
config: params.config,
workspaceDir: params.workspaceDir,
@@ -219,6 +231,37 @@ function resolvePluginToolRuntimePluginIds(params: {
: undefined;
}
function registryContainsPluginIds(
registry: ReturnType<typeof getActivePluginRegistry>,
pluginIds?: readonly string[],
): boolean {
if (!registry || pluginIds === undefined) {
return false;
}
const loadedPluginIds = new Set<string>();
addLoadedPluginIdsFromRegistry(registry, loadedPluginIds);
return pluginIds.every((pluginId) => loadedPluginIds.has(pluginId));
}
function resolvePluginToolRegistry(params: {
loadOptions: PluginLoadOptions;
onlyPluginIds?: readonly string[];
}) {
const activeRegistry = getActivePluginRegistry();
const channelRegistry = getActivePluginChannelRegistry();
const activeRegistryIsGatewayBindable =
getActivePluginRegistryKey() && getActivePluginRuntimeSubagentMode() === "gateway-bindable";
const hasPinnedGatewayRegistry = Boolean(channelRegistry && channelRegistry !== activeRegistry);
if (
channelRegistry &&
(activeRegistryIsGatewayBindable || hasPinnedGatewayRegistry) &&
registryContainsPluginIds(channelRegistry, params.onlyPluginIds)
) {
return channelRegistry;
}
return resolveRuntimePluginRegistry(params.loadOptions);
}
export function resolvePluginTools(params: {
context: OpenClawPluginToolContext;
existingToolNames?: Set<string>;
@@ -255,7 +298,10 @@ export function resolvePluginTools(params: {
...(onlyPluginIds !== undefined ? { onlyPluginIds } : {}),
runtimeOptions,
});
const registry = resolveRuntimePluginRegistry(loadOptions);
const registry = resolvePluginToolRegistry({
loadOptions,
onlyPluginIds,
});
if (!registry) {
return [];
}