fix: tighten gateway startup plugin loading

This commit is contained in:
Peter Steinberger 2026-04-01 00:19:26 +09:00
parent 1ca12ec8bf
commit 85611f0021
No known key found for this signature in database
6 changed files with 74 additions and 22 deletions

View File

@ -24,6 +24,7 @@ type GatewayPluginBootstrapParams = {
log: GatewayPluginBootstrapLog;
coreGatewayHandlers: Record<string, GatewayRequestHandler>;
baseMethods: string[];
pluginIds?: string[];
preferSetupRuntimeForChannelPlugins?: boolean;
logDiagnostics?: boolean;
beforePrimeRegistry?: (pluginRegistry: PluginRegistry) => void;
@ -68,6 +69,7 @@ export function prepareGatewayPluginLoad(params: GatewayPluginBootstrapParams) {
log: params.log,
coreGatewayHandlers: params.coreGatewayHandlers,
baseMethods: params.baseMethods,
pluginIds: params.pluginIds,
preferSetupRuntimeForChannelPlugins: params.preferSetupRuntimeForChannelPlugins,
});
params.beforePrimeRegistry?.(loaded.pluginRegistry);

View File

@ -257,6 +257,21 @@ describe("loadGatewayPlugins", () => {
);
});
test("reuses the provided startup plugin scope without recomputing it", async () => {
loadOpenClawPlugins.mockReturnValue(createRegistry([]));
loadGatewayPluginsForTest({
pluginIds: ["browser"],
});
expect(resolveGatewayStartupPluginIds).not.toHaveBeenCalled();
expect(loadOpenClawPlugins).toHaveBeenCalledWith(
expect.objectContaining({
onlyPluginIds: ["browser"],
}),
);
});
test("loads gateway plugins from the auto-enabled config snapshot", async () => {
const autoEnabledConfig = { channels: { slack: { enabled: true } }, autoEnabled: true };
applyPluginAutoEnable.mockReturnValue({ config: autoEnabledConfig, changes: [] });
@ -634,6 +649,28 @@ describe("loadGatewayPlugins", () => {
expect(log.info).not.toHaveBeenCalled();
});
test("reuses the initial startup plugin scope during deferred reloads", async () => {
const { reloadDeferredGatewayPlugins } = serverPluginBootstrapModule;
loadOpenClawPlugins.mockReturnValue(createRegistry([]));
reloadDeferredGatewayPlugins({
cfg: {},
workspaceDir: "/tmp",
log: createTestLog(),
coreGatewayHandlers: {},
baseMethods: [],
pluginIds: ["discord"],
logDiagnostics: false,
});
expect(resolveGatewayStartupPluginIds).not.toHaveBeenCalled();
expect(loadOpenClawPlugins).toHaveBeenCalledWith(
expect.objectContaining({
onlyPluginIds: ["discord"],
}),
);
});
test("runs registry hook before priming configured bindings", async () => {
const { prepareGatewayPluginLoad } = serverPluginBootstrapModule;
const order: string[] = [];

View File

@ -389,20 +389,24 @@ export function loadGatewayPlugins(params: {
};
coreGatewayHandlers: Record<string, GatewayRequestHandler>;
baseMethods: string[];
pluginIds?: string[];
preferSetupRuntimeForChannelPlugins?: boolean;
}) {
const resolvedConfig = applyPluginAutoEnable({
config: params.cfg,
env: process.env,
}).config;
const pluginRegistry = loadOpenClawPlugins({
config: resolvedConfig,
workspaceDir: params.workspaceDir,
onlyPluginIds: resolveGatewayStartupPluginIds({
const pluginIds =
params.pluginIds ??
resolveGatewayStartupPluginIds({
config: resolvedConfig,
workspaceDir: params.workspaceDir,
env: process.env,
}),
});
const pluginRegistry = loadOpenClawPlugins({
config: resolvedConfig,
workspaceDir: params.workspaceDir,
onlyPluginIds: pluginIds,
logger: {
info: (msg) => params.log.info(msg),
warn: (msg) => params.log.warn(msg),

View File

@ -54,7 +54,10 @@ import { scheduleGatewayUpdateCheck } from "../infra/update-startup.js";
import { startDiagnosticHeartbeat, stopDiagnosticHeartbeat } from "../logging/diagnostic.js";
import { createSubsystemLogger, runtimeForLogger } from "../logging/subsystem.js";
import { resolveBundledPluginInstallCommandHint } from "../plugins/bundled-sources.js";
import { resolveConfiguredDeferredChannelPluginIds } from "../plugins/channel-plugin-ids.js";
import {
resolveConfiguredDeferredChannelPluginIds,
resolveGatewayStartupPluginIds,
} from "../plugins/channel-plugin-ids.js";
import { getGlobalHookRunner, runGlobalGatewayStopSafely } from "../plugins/hook-runner-global.js";
import { createEmptyPluginRegistry } from "../plugins/registry.js";
import { setActivePluginRegistry } from "../plugins/runtime.js";
@ -582,6 +585,13 @@ export async function startGatewayServer(
workspaceDir: defaultWorkspaceDir,
env: process.env,
});
const startupPluginIds = minimalTestGateway
? []
: resolveGatewayStartupPluginIds({
config: gatewayPluginConfigAtStart,
workspaceDir: defaultWorkspaceDir,
env: process.env,
});
const baseMethods = listGatewayMethods();
const emptyPluginRegistry = createEmptyPluginRegistry();
let pluginRegistry = emptyPluginRegistry;
@ -593,6 +603,7 @@ export async function startGatewayServer(
log,
coreGatewayHandlers,
baseMethods,
pluginIds: startupPluginIds,
preferSetupRuntimeForChannelPlugins: deferredConfiguredChannelPluginIds.length > 0,
}));
} else {
@ -1348,6 +1359,7 @@ export async function startGatewayServer(
log,
coreGatewayHandlers,
baseMethods,
pluginIds: startupPluginIds,
logDiagnostics: false,
}));
}

View File

@ -133,30 +133,31 @@ describe("resolveGatewayStartupPluginIds", () => {
it.each([
[
"includes configured channels, explicit bundled sidecars, and enabled non-bundled sidecars",
"includes configured channels and explicitly enabled bundled sidecars",
createStartupConfig({
enabledPluginIds: ["demo-bundled-sidecar"],
modelId: "demo-cli/demo-model",
}),
[
"demo-channel",
"demo-default-on-sidecar",
"demo-provider-plugin",
"demo-bundled-sidecar",
"demo-global-sidecar",
],
["demo-channel", "demo-provider-plugin", "demo-bundled-sidecar"],
],
[
"includes bundled plugins with enabledByDefault: true",
"skips bundled plugins with enabledByDefault: true until something references them",
{} as OpenClawConfig,
["demo-channel", "demo-default-on-sidecar", "demo-global-sidecar"],
["demo-channel"],
],
[
"auto-loads bundled plugins referenced by configured provider ids",
createStartupConfig({
providerIds: ["demo-provider"],
}),
["demo-channel", "demo-default-on-sidecar", "demo-provider-plugin", "demo-global-sidecar"],
["demo-channel", "demo-provider-plugin"],
],
[
"keeps non-bundled sidecars out of startup unless explicitly enabled",
createStartupConfig({
enabledPluginIds: ["demo-global-sidecar"],
}),
["demo-channel", "demo-global-sidecar"],
],
] as const)("%s", (_name, config, expected) => {
expectStartupPluginIdsCase({ config, expected });

View File

@ -331,14 +331,10 @@ export function resolveGatewayStartupPluginIds(params: {
if (!enabled) {
return false;
}
if (plugin.origin !== "bundled") {
return true;
}
return (
pluginsConfig.allow.includes(plugin.id) ||
pluginsConfig.entries[plugin.id]?.enabled === true ||
pluginsConfig.slots.memory === plugin.id ||
plugin.enabledByDefault === true
pluginsConfig.slots.memory === plugin.id
);
})
.map((plugin) => plugin.id);