mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 07:40:44 +00:00
Prevent disabled plugins from warming the gateway plugin graph
A local containment profile uses plugins.enabled=false to stop plugin and channel runtime churn. The previous startup path still built plugin lookup tables and doctor stale scans despite the global disable, which made the switch noisy and slow. Constraint: plugins.enabled=false must leave channel blocker warnings intact while treating stale plugin config as inert. Rejected: Clear user plugin config automatically | would mutate a reversible containment setting. Confidence: high Scope-risk: narrow Directive: Do not reintroduce plugin registry discovery before checking plugins.enabled. Tested: pnpm test src/gateway/server-startup-plugins.test.ts src/config/plugin-auto-enable.core.test.ts src/commands/doctor/shared/stale-plugin-config.test.ts src/commands/doctor/shared/preview-warnings.test.ts Tested: pnpm check:changed Tested: pnpm build
This commit is contained in:
committed by
Peter Steinberger
parent
5bdfc251ff
commit
f07844450c
@@ -362,4 +362,34 @@ describe("doctor preview warnings", () => {
|
||||
]);
|
||||
expect(warnings[0]).not.toContain("first-time setup mode");
|
||||
});
|
||||
|
||||
it("keeps global plugin-disable blocker warnings but omits stale plugin cleanup warnings", async () => {
|
||||
manifestState.plugins = [channelManifest("telegram", "telegram")];
|
||||
|
||||
const warnings = await collectDoctorPreviewWarnings({
|
||||
cfg: {
|
||||
channels: {
|
||||
telegram: {
|
||||
botToken: "123:abc",
|
||||
groupPolicy: "allowlist",
|
||||
},
|
||||
},
|
||||
plugins: {
|
||||
enabled: false,
|
||||
allow: ["acpx"],
|
||||
entries: {
|
||||
acpx: { enabled: true },
|
||||
},
|
||||
},
|
||||
},
|
||||
doctorFixCommand: "openclaw doctor --fix",
|
||||
});
|
||||
|
||||
expect(warnings).toEqual([
|
||||
expect.stringContaining(
|
||||
"channels.telegram: channel is configured, but plugins.enabled=false blocks channel plugins globally.",
|
||||
),
|
||||
]);
|
||||
expect(warnings.join("\n")).not.toContain("stale plugin reference");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -123,7 +123,7 @@ export async function collectDoctorPreviewWarnings(params: {
|
||||
}
|
||||
}
|
||||
|
||||
if (hasPluginConfig || hasChannelConfig) {
|
||||
if ((hasPluginConfig || hasChannelConfig) && params.cfg.plugins?.enabled !== false) {
|
||||
const {
|
||||
collectStalePluginConfigWarnings,
|
||||
isStalePluginAutoRepairBlocked,
|
||||
|
||||
@@ -198,6 +198,27 @@ describe("doctor stale plugin config helpers", () => {
|
||||
expect(maybeRepairStalePluginConfig(cfg)).toEqual({ config: cfg, changes: [] });
|
||||
});
|
||||
|
||||
it("treats stale plugin refs as inert while plugins are globally disabled", () => {
|
||||
const cfg = {
|
||||
plugins: {
|
||||
enabled: false,
|
||||
allow: ["acpx"],
|
||||
entries: {
|
||||
acpx: { enabled: true },
|
||||
},
|
||||
},
|
||||
channels: {
|
||||
"openclaw-weixin": {
|
||||
enabled: true,
|
||||
},
|
||||
},
|
||||
} as OpenClawConfig;
|
||||
|
||||
expect(scanStalePluginConfig(cfg)).toEqual([]);
|
||||
expect(maybeRepairStalePluginConfig(cfg)).toEqual({ config: cfg, changes: [] });
|
||||
expect(manifestRegistry.loadPluginManifestRegistry).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("uses missing persisted install records as stale channel evidence", () => {
|
||||
installedPluginIndexMocks.loadInstalledPluginIndexInstallRecordsSync.mockReturnValue({
|
||||
"openclaw-weixin": {
|
||||
|
||||
@@ -74,6 +74,9 @@ export function isStalePluginAutoRepairBlocked(
|
||||
cfg: OpenClawConfig,
|
||||
env?: NodeJS.ProcessEnv,
|
||||
): boolean {
|
||||
if (cfg.plugins?.enabled === false) {
|
||||
return false;
|
||||
}
|
||||
return collectPluginRegistryState(cfg, env).hasDiscoveryErrors;
|
||||
}
|
||||
|
||||
@@ -81,6 +84,9 @@ export function scanStalePluginConfig(
|
||||
cfg: OpenClawConfig,
|
||||
env?: NodeJS.ProcessEnv,
|
||||
): StalePluginConfigHit[] {
|
||||
if (cfg.plugins?.enabled === false) {
|
||||
return [];
|
||||
}
|
||||
return scanStalePluginConfigWithState(cfg, collectPluginRegistryState(cfg, env));
|
||||
}
|
||||
|
||||
@@ -268,6 +274,9 @@ export function maybeRepairStalePluginConfig(
|
||||
config: OpenClawConfig;
|
||||
changes: string[];
|
||||
} {
|
||||
if (cfg.plugins?.enabled === false) {
|
||||
return { config: cfg, changes: [] };
|
||||
}
|
||||
const registryState = collectPluginRegistryState(cfg, env);
|
||||
if (registryState.hasDiscoveryErrors) {
|
||||
return { config: cfg, changes: [] };
|
||||
|
||||
@@ -720,6 +720,21 @@ describe("applyPluginAutoEnable core", () => {
|
||||
});
|
||||
|
||||
it("skips when plugins are globally disabled", () => {
|
||||
expect(
|
||||
detectPluginAutoEnableCandidates({
|
||||
config: {
|
||||
channels: { slack: { botToken: "x" } },
|
||||
plugins: {
|
||||
enabled: false,
|
||||
allow: ["slack"],
|
||||
entries: { slack: { config: { botToken: "x" } } },
|
||||
},
|
||||
},
|
||||
env,
|
||||
manifestRegistry: makeRegistry([{ id: "slack", channels: ["slack"] }]),
|
||||
}),
|
||||
).toEqual([]);
|
||||
|
||||
const result = applyPluginAutoEnable({
|
||||
config: {
|
||||
channels: { slack: { botToken: "x" } },
|
||||
|
||||
@@ -466,7 +466,14 @@ function hasConfiguredProviderModelOrHarness(cfg: OpenClawConfig, env: NodeJS.Pr
|
||||
return hasConfiguredEmbeddedHarnessRuntime(cfg, env);
|
||||
}
|
||||
|
||||
function arePluginsGloballyDisabled(cfg: OpenClawConfig): boolean {
|
||||
return cfg.plugins?.enabled === false;
|
||||
}
|
||||
|
||||
function configMayNeedPluginManifestRegistry(cfg: OpenClawConfig, env: NodeJS.ProcessEnv): boolean {
|
||||
if (arePluginsGloballyDisabled(cfg)) {
|
||||
return false;
|
||||
}
|
||||
if (hasPluginAllowlistWithMaterialEntries(cfg)) {
|
||||
return true;
|
||||
}
|
||||
@@ -493,6 +500,9 @@ export function configMayNeedPluginAutoEnable(
|
||||
cfg: OpenClawConfig,
|
||||
env: NodeJS.ProcessEnv,
|
||||
): boolean {
|
||||
if (arePluginsGloballyDisabled(cfg)) {
|
||||
return false;
|
||||
}
|
||||
if (hasPluginAllowlistWithMaterialEntries(cfg)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -410,4 +410,50 @@ describe("prepareGatewayPluginBootstrap runtime-deps staging", () => {
|
||||
"bundledRuntimeDepsInstaller",
|
||||
);
|
||||
});
|
||||
|
||||
it("bypasses plugin lookup and runtime-deps staging when plugins are globally disabled", async () => {
|
||||
const cfg = {
|
||||
channels: {
|
||||
telegram: {
|
||||
botToken: "token",
|
||||
},
|
||||
},
|
||||
plugins: {
|
||||
enabled: false,
|
||||
allow: ["telegram"],
|
||||
entries: {
|
||||
telegram: { enabled: true },
|
||||
},
|
||||
},
|
||||
} as OpenClawConfig;
|
||||
const log = createLog();
|
||||
const { prepareGatewayPluginBootstrap } = await import("./server-startup-plugins.js");
|
||||
|
||||
await expect(
|
||||
prepareGatewayPluginBootstrap({
|
||||
cfgAtStart: cfg,
|
||||
startupRuntimeConfig: cfg,
|
||||
minimalTestGateway: false,
|
||||
log,
|
||||
}),
|
||||
).resolves.toMatchObject({
|
||||
startupPluginIds: [],
|
||||
deferredConfiguredChannelPluginIds: [],
|
||||
pluginLookUpTable: undefined,
|
||||
baseGatewayMethods: ["ping"],
|
||||
});
|
||||
|
||||
expect(loadPluginLookUpTable).not.toHaveBeenCalled();
|
||||
expect(scanBundledPluginRuntimeDeps).not.toHaveBeenCalled();
|
||||
expect(repairBundledRuntimeDepsInstallRootAsync).not.toHaveBeenCalled();
|
||||
expect(loadGatewayStartupPlugins).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
cfg,
|
||||
pluginIds: [],
|
||||
pluginLookUpTable: undefined,
|
||||
preferSetupRuntimeForChannelPlugins: false,
|
||||
suppressPluginInfoLogs: false,
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -155,17 +155,19 @@ export async function prepareGatewayPluginBootstrap(params: {
|
||||
: {}),
|
||||
}).config,
|
||||
});
|
||||
const pluginsGloballyDisabled = gatewayPluginConfig.plugins?.enabled === false;
|
||||
const defaultAgentId = resolveDefaultAgentId(gatewayPluginConfig);
|
||||
const defaultWorkspaceDir = resolveAgentWorkspaceDir(gatewayPluginConfig, defaultAgentId);
|
||||
const pluginLookUpTable = params.minimalTestGateway
|
||||
? undefined
|
||||
: loadPluginLookUpTable({
|
||||
config: gatewayPluginConfig,
|
||||
workspaceDir: defaultWorkspaceDir,
|
||||
env: process.env,
|
||||
activationSourceConfig,
|
||||
metadataSnapshot: params.pluginMetadataSnapshot,
|
||||
});
|
||||
const pluginLookUpTable =
|
||||
params.minimalTestGateway || pluginsGloballyDisabled
|
||||
? undefined
|
||||
: loadPluginLookUpTable({
|
||||
config: gatewayPluginConfig,
|
||||
workspaceDir: defaultWorkspaceDir,
|
||||
env: process.env,
|
||||
activationSourceConfig,
|
||||
metadataSnapshot: params.pluginMetadataSnapshot,
|
||||
});
|
||||
const deferredConfiguredChannelPluginIds = [
|
||||
...(pluginLookUpTable?.startup.configuredDeferredChannelPluginIds ?? []),
|
||||
];
|
||||
|
||||
Reference in New Issue
Block a user