Prevent disabled plugins from warming the gateway plugin graph · openclaw/openclaw@f078444
2026-04-28
·
via Recent Commits to openclaw:main
| Original file line number | Diff line number | Diff line change |
|---|
@@ -362,4 +362,34 @@ describe("doctor preview warnings", () => {
|
362 | 362 | ]); |
363 | 363 | expect(warnings[0]).not.toContain("first-time setup mode"); |
364 | 364 | }); |
| 365 | + |
| 366 | +it("keeps global plugin-disable blocker warnings but omits stale plugin cleanup warnings", async () => { |
| 367 | +manifestState.plugins = [channelManifest("telegram", "telegram")]; |
| 368 | + |
| 369 | +const warnings = await collectDoctorPreviewWarnings({ |
| 370 | +cfg: { |
| 371 | +channels: { |
| 372 | +telegram: { |
| 373 | +botToken: "123:abc", |
| 374 | +groupPolicy: "allowlist", |
| 375 | +}, |
| 376 | +}, |
| 377 | +plugins: { |
| 378 | +enabled: false, |
| 379 | +allow: ["acpx"], |
| 380 | +entries: { |
| 381 | +acpx: { enabled: true }, |
| 382 | +}, |
| 383 | +}, |
| 384 | +}, |
| 385 | +doctorFixCommand: "openclaw doctor --fix", |
| 386 | +}); |
| 387 | + |
| 388 | +expect(warnings).toEqual([ |
| 389 | +expect.stringContaining( |
| 390 | +"channels.telegram: channel is configured, but plugins.enabled=false blocks channel plugins globally.", |
| 391 | +), |
| 392 | +]); |
| 393 | +expect(warnings.join("\n")).not.toContain("stale plugin reference"); |
| 394 | +}); |
365 | 395 | }); |
| Original file line number | Diff line number | Diff line change |
|---|
@@ -123,7 +123,7 @@ export async function collectDoctorPreviewWarnings(params: {
|
123 | 123 | } |
124 | 124 | } |
125 | 125 | |
126 | | -if (hasPluginConfig || hasChannelConfig) { |
| 126 | +if ((hasPluginConfig || hasChannelConfig) && params.cfg.plugins?.enabled !== false) { |
127 | 127 | const { |
128 | 128 | collectStalePluginConfigWarnings, |
129 | 129 | isStalePluginAutoRepairBlocked, |
|
| Original file line number | Diff line number | Diff line change |
|---|
@@ -198,6 +198,27 @@ describe("doctor stale plugin config helpers", () => {
|
198 | 198 | expect(maybeRepairStalePluginConfig(cfg)).toEqual({ config: cfg, changes: [] }); |
199 | 199 | }); |
200 | 200 | |
| 201 | +it("treats stale plugin refs as inert while plugins are globally disabled", () => { |
| 202 | +const cfg = { |
| 203 | +plugins: { |
| 204 | +enabled: false, |
| 205 | +allow: ["acpx"], |
| 206 | +entries: { |
| 207 | +acpx: { enabled: true }, |
| 208 | +}, |
| 209 | +}, |
| 210 | +channels: { |
| 211 | +"openclaw-weixin": { |
| 212 | +enabled: true, |
| 213 | +}, |
| 214 | +}, |
| 215 | +} as OpenClawConfig; |
| 216 | + |
| 217 | +expect(scanStalePluginConfig(cfg)).toEqual([]); |
| 218 | +expect(maybeRepairStalePluginConfig(cfg)).toEqual({ config: cfg, changes: [] }); |
| 219 | +expect(manifestRegistry.loadPluginManifestRegistry).not.toHaveBeenCalled(); |
| 220 | +}); |
| 221 | + |
201 | 222 | it("uses missing persisted install records as stale channel evidence", () => { |
202 | 223 | installedPluginIndexMocks.loadInstalledPluginIndexInstallRecordsSync.mockReturnValue({ |
203 | 224 | "openclaw-weixin": { |
|
| Original file line number | Diff line number | Diff line change |
|---|
@@ -74,13 +74,19 @@ export function isStalePluginAutoRepairBlocked(
|
74 | 74 | cfg: OpenClawConfig, |
75 | 75 | env?: NodeJS.ProcessEnv, |
76 | 76 | ): boolean { |
| 77 | +if (cfg.plugins?.enabled === false) { |
| 78 | +return false; |
| 79 | +} |
77 | 80 | return collectPluginRegistryState(cfg, env).hasDiscoveryErrors; |
78 | 81 | } |
79 | 82 | |
80 | 83 | export function scanStalePluginConfig( |
81 | 84 | cfg: OpenClawConfig, |
82 | 85 | env?: NodeJS.ProcessEnv, |
83 | 86 | ): StalePluginConfigHit[] { |
| 87 | +if (cfg.plugins?.enabled === false) { |
| 88 | +return []; |
| 89 | +} |
84 | 90 | return scanStalePluginConfigWithState(cfg, collectPluginRegistryState(cfg, env)); |
85 | 91 | } |
86 | 92 | |
@@ -268,6 +274,9 @@ export function maybeRepairStalePluginConfig(
|
268 | 274 | config: OpenClawConfig; |
269 | 275 | changes: string[]; |
270 | 276 | } { |
| 277 | +if (cfg.plugins?.enabled === false) { |
| 278 | +return { config: cfg, changes: [] }; |
| 279 | +} |
271 | 280 | const registryState = collectPluginRegistryState(cfg, env); |
272 | 281 | if (registryState.hasDiscoveryErrors) { |
273 | 282 | return { config: cfg, changes: [] }; |
|
| Original file line number | Diff line number | Diff line change |
|---|
@@ -720,6 +720,21 @@ describe("applyPluginAutoEnable core", () => {
|
720 | 720 | }); |
721 | 721 | |
722 | 722 | it("skips when plugins are globally disabled", () => { |
| 723 | +expect( |
| 724 | +detectPluginAutoEnableCandidates({ |
| 725 | +config: { |
| 726 | +channels: { slack: { botToken: "x" } }, |
| 727 | +plugins: { |
| 728 | +enabled: false, |
| 729 | +allow: ["slack"], |
| 730 | +entries: { slack: { config: { botToken: "x" } } }, |
| 731 | +}, |
| 732 | +}, |
| 733 | + env, |
| 734 | +manifestRegistry: makeRegistry([{ id: "slack", channels: ["slack"] }]), |
| 735 | +}), |
| 736 | +).toEqual([]); |
| 737 | + |
723 | 738 | const result = applyPluginAutoEnable({ |
724 | 739 | config: { |
725 | 740 | channels: { slack: { botToken: "x" } }, |
|
| Original file line number | Diff line number | Diff line change |
|---|
@@ -466,7 +466,14 @@ function hasConfiguredProviderModelOrHarness(cfg: OpenClawConfig, env: NodeJS.Pr
|
466 | 466 | return hasConfiguredEmbeddedHarnessRuntime(cfg, env); |
467 | 467 | } |
468 | 468 | |
| 469 | +function arePluginsGloballyDisabled(cfg: OpenClawConfig): boolean { |
| 470 | +return cfg.plugins?.enabled === false; |
| 471 | +} |
| 472 | + |
469 | 473 | function configMayNeedPluginManifestRegistry(cfg: OpenClawConfig, env: NodeJS.ProcessEnv): boolean { |
| 474 | +if (arePluginsGloballyDisabled(cfg)) { |
| 475 | +return false; |
| 476 | +} |
470 | 477 | if (hasPluginAllowlistWithMaterialEntries(cfg)) { |
471 | 478 | return true; |
472 | 479 | } |
@@ -493,6 +500,9 @@ export function configMayNeedPluginAutoEnable(
|
493 | 500 | cfg: OpenClawConfig, |
494 | 501 | env: NodeJS.ProcessEnv, |
495 | 502 | ): boolean { |
| 503 | +if (arePluginsGloballyDisabled(cfg)) { |
| 504 | +return false; |
| 505 | +} |
496 | 506 | if (hasPluginAllowlistWithMaterialEntries(cfg)) { |
497 | 507 | return true; |
498 | 508 | } |
|
| Original file line number | Diff line number | Diff line change |
|---|
@@ -410,4 +410,50 @@ describe("prepareGatewayPluginBootstrap runtime-deps staging", () => {
|
410 | 410 | "bundledRuntimeDepsInstaller", |
411 | 411 | ); |
412 | 412 | }); |
| 413 | + |
| 414 | +it("bypasses plugin lookup and runtime-deps staging when plugins are globally disabled", async () => { |
| 415 | +const cfg = { |
| 416 | +channels: { |
| 417 | +telegram: { |
| 418 | +botToken: "token", |
| 419 | +}, |
| 420 | +}, |
| 421 | +plugins: { |
| 422 | +enabled: false, |
| 423 | +allow: ["telegram"], |
| 424 | +entries: { |
| 425 | +telegram: { enabled: true }, |
| 426 | +}, |
| 427 | +}, |
| 428 | +} as OpenClawConfig; |
| 429 | +const log = createLog(); |
| 430 | +const { prepareGatewayPluginBootstrap } = await import("./server-startup-plugins.js"); |
| 431 | + |
| 432 | +await expect( |
| 433 | +prepareGatewayPluginBootstrap({ |
| 434 | +cfgAtStart: cfg, |
| 435 | +startupRuntimeConfig: cfg, |
| 436 | +minimalTestGateway: false, |
| 437 | + log, |
| 438 | +}), |
| 439 | +).resolves.toMatchObject({ |
| 440 | +startupPluginIds: [], |
| 441 | +deferredConfiguredChannelPluginIds: [], |
| 442 | +pluginLookUpTable: undefined, |
| 443 | +baseGatewayMethods: ["ping"], |
| 444 | +}); |
| 445 | + |
| 446 | +expect(loadPluginLookUpTable).not.toHaveBeenCalled(); |
| 447 | +expect(scanBundledPluginRuntimeDeps).not.toHaveBeenCalled(); |
| 448 | +expect(repairBundledRuntimeDepsInstallRootAsync).not.toHaveBeenCalled(); |
| 449 | +expect(loadGatewayStartupPlugins).toHaveBeenCalledWith( |
| 450 | +expect.objectContaining({ |
| 451 | + cfg, |
| 452 | +pluginIds: [], |
| 453 | +pluginLookUpTable: undefined, |
| 454 | +preferSetupRuntimeForChannelPlugins: false, |
| 455 | +suppressPluginInfoLogs: false, |
| 456 | +}), |
| 457 | +); |
| 458 | +}); |
413 | 459 | }); |
| Original file line number | Diff line number | Diff line change |
|---|
@@ -155,17 +155,19 @@ export async function prepareGatewayPluginBootstrap(params: {
|
155 | 155 | : {}), |
156 | 156 | }).config, |
157 | 157 | }); |
| 158 | +const pluginsGloballyDisabled = gatewayPluginConfig.plugins?.enabled === false; |
158 | 159 | const defaultAgentId = resolveDefaultAgentId(gatewayPluginConfig); |
159 | 160 | const defaultWorkspaceDir = resolveAgentWorkspaceDir(gatewayPluginConfig, defaultAgentId); |
160 | | -const pluginLookUpTable = params.minimalTestGateway |
161 | | - ? undefined |
162 | | - : loadPluginLookUpTable({ |
163 | | -config: gatewayPluginConfig, |
164 | | -workspaceDir: defaultWorkspaceDir, |
165 | | -env: process.env, |
166 | | - activationSourceConfig, |
167 | | -metadataSnapshot: params.pluginMetadataSnapshot, |
168 | | -}); |
| 161 | +const pluginLookUpTable = |
| 162 | +params.minimalTestGateway || pluginsGloballyDisabled |
| 163 | + ? undefined |
| 164 | + : loadPluginLookUpTable({ |
| 165 | +config: gatewayPluginConfig, |
| 166 | +workspaceDir: defaultWorkspaceDir, |
| 167 | +env: process.env, |
| 168 | + activationSourceConfig, |
| 169 | +metadataSnapshot: params.pluginMetadataSnapshot, |
| 170 | +}); |
169 | 171 | const deferredConfiguredChannelPluginIds = [ |
170 | 172 | ...(pluginLookUpTable?.startup.configuredDeferredChannelPluginIds ?? []), |
171 | 173 | ]; |
|
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。