diff --git a/src/gateway/config-reload-plan.ts b/src/gateway/config-reload-plan.ts index 3591287c3257..3cf93596b78b 100644 --- a/src/gateway/config-reload-plan.ts +++ b/src/gateway/config-reload-plan.ts @@ -34,6 +34,22 @@ export type GatewayReloadPlan = { noopPaths: string[]; }; +export function isNoopGatewayReloadPlan(plan: GatewayReloadPlan): boolean { + return ( + !plan.restartGateway && + plan.hotReasons.length === 0 && + !plan.reloadHooks && + !plan.restartGmailWatcher && + !plan.restartCron && + !plan.restartHeartbeat && + !plan.restartHealthMonitor && + !plan.reloadPlugins && + !plan.disposeMcpRuntimes && + plan.restartChannels.size === 0 && + (plan.restartChannelAccounts?.size ?? 0) === 0 + ); +} + type ReloadRule = { prefix: string; kind: "restart" | "hot" | "none"; diff --git a/src/gateway/config-reload.test.ts b/src/gateway/config-reload.test.ts index 417c134b5843..36d7fda2ded4 100644 --- a/src/gateway/config-reload.test.ts +++ b/src/gateway/config-reload.test.ts @@ -37,6 +37,7 @@ import { diffConfigPaths, diffGatewayReloadPaths } from "./config-diff.js"; import { buildGatewayReloadPlan, type ChannelKind, + isNoopGatewayReloadPlan, resolveConfigReloadMetadata, } from "./config-reload-plan.js"; import { resolveGatewayReloadSettings } from "./config-reload-settings.js"; @@ -377,6 +378,7 @@ describe("buildGatewayReloadPlan", () => { expect(plan.restartReasons).toStrictEqual([]); expect(plan.hotReasons).toStrictEqual([]); expect(plan.noopPaths).toEqual([path]); + expect(isNoopGatewayReloadPlan(plan)).toBe(true); }, ); @@ -481,6 +483,7 @@ describe("buildGatewayReloadPlan", () => { expect(plan.restartChannels).toEqual(expectedChannels); expect(plan.restartChannelAccounts).toEqual(expectedAccounts); + expect(isNoopGatewayReloadPlan(plan)).toBe(false); }); it("restarts every channel whose config prefix matches", () => { diff --git a/src/gateway/config-reload.ts b/src/gateway/config-reload.ts index 0c5fb27004d4..c84d9f03ebcd 100644 --- a/src/gateway/config-reload.ts +++ b/src/gateway/config-reload.ts @@ -33,6 +33,7 @@ import { createConfigAppliedRevisionTracker } from "./config-applied-revision.js import { diffConfigPaths, diffGatewayReloadPaths } from "./config-diff.js"; import { buildGatewayReloadPlan, + isNoopGatewayReloadPlan, listPluginInstallTimestampMetadataPaths, listPluginInstallWholeRecordPaths, type GatewayReloadPlan, @@ -86,22 +87,6 @@ function firstSkillsChangedPath(changedPaths: string[]): string | undefined { return changedPaths.find(matchesSkillsInvalidationPrefix); } -function isNoopReloadPlan(plan: GatewayReloadPlan): boolean { - return ( - !plan.restartGateway && - plan.hotReasons.length === 0 && - !plan.reloadHooks && - !plan.restartGmailWatcher && - !plan.restartCron && - !plan.restartHeartbeat && - !plan.restartHealthMonitor && - !plan.reloadPlugins && - !plan.disposeMcpRuntimes && - plan.restartChannels.size === 0 && - (plan.restartChannelAccounts?.size ?? 0) === 0 - ); -} - type GatewayConfigReloader = { stop: () => Promise; hotReloadStatus: () => GatewayHotReloadStatus; @@ -710,7 +695,7 @@ export function startGatewayConfigReloader(opts: { await commitReloadBaseline({ runtimeApplied: false }); return; } - if (isNoopReloadPlan(plan) && !followUp.requiresRestart) { + if (isNoopGatewayReloadPlan(plan) && !followUp.requiresRestart) { await opts.onConfigChange?.(plan, nextConfig); // No-op plans still change the runtime config snapshot. Commit before // marking applied so getRuntimeConfig() readers do not stay stale until restart. diff --git a/src/gateway/server-methods/config-write-flow.ts b/src/gateway/server-methods/config-write-flow.ts index 559af18cdab3..283559bb82c8 100644 --- a/src/gateway/server-methods/config-write-flow.ts +++ b/src/gateway/server-methods/config-write-flow.ts @@ -17,7 +17,7 @@ import { import { scheduleGatewaySigusr1Restart } from "../../infra/restart.js"; import { getActiveSecretsRuntimeSnapshot } from "../../secrets/runtime-state.js"; import { resolveEffectiveSharedGatewayAuth, resolveGatewayAuth } from "../auth.js"; -import { buildGatewayReloadPlan } from "../config-reload-plan.js"; +import { buildGatewayReloadPlan, isNoopGatewayReloadPlan } from "../config-reload-plan.js"; import { resolveGatewayReloadSettings } from "../config-reload-settings.js"; import { formatControlPlaneActor, type ControlPlaneActor } from "../control-plane-audit.js"; import { parseRestartRequestParams } from "./restart-request.js"; @@ -131,29 +131,13 @@ function queueSharedGatewayAuthGenerationRefresh( }); } -function isNoopConfigReloadPlan(plan: ReturnType): boolean { - return ( - !plan.restartGateway && - plan.hotReasons.length === 0 && - !plan.reloadHooks && - !plan.restartGmailWatcher && - !plan.restartCron && - !plan.restartHeartbeat && - !plan.restartHealthMonitor && - !plan.reloadPlugins && - !plan.disposeMcpRuntimes && - plan.restartChannels.size === 0 && - (plan.restartChannelAccounts?.size ?? 0) === 0 - ); -} - function resolveConfigRestartRequirement(params: { changedPaths: string[]; nextConfig: OpenClawConfig; }): { requiresRestart: boolean; scheduleDirectRestart: boolean } { const reloadSettings = resolveGatewayReloadSettings(params.nextConfig); const plan = buildGatewayReloadPlan(params.changedPaths, { candidateConfig: params.nextConfig }); - if (isNoopConfigReloadPlan(plan)) { + if (isNoopGatewayReloadPlan(plan)) { return { requiresRestart: false, scheduleDirectRestart: false }; } if (reloadSettings.mode === "off") {