diff --git a/src/gateway/server-runtime-services.ts b/src/gateway/server-runtime-services.ts index 752fe49fd599..044da1a34edb 100644 --- a/src/gateway/server-runtime-services.ts +++ b/src/gateway/server-runtime-services.ts @@ -2,10 +2,13 @@ import type { OpenClawConfig } from "../config/types.openclaw.js"; import { isVitestRuntimeEnv } from "../infra/env.js"; import { startHeartbeatRunner, type HeartbeatRunner } from "../infra/heartbeat-runner.js"; import type { PluginMetadataRegistryView } from "../plugins/plugin-metadata-snapshot.types.js"; -import type { ChannelHealthMonitor } from "./channel-health-monitor.js"; -import { startChannelHealthMonitor } from "./channel-health-monitor.js"; import { isGatewayModelPricingEnabled } from "./model-pricing-config.js"; import type { startGatewayMaintenanceTimers } from "./server-maintenance.js"; +export { + startGatewayChannelHealthMonitor, + startGatewayRuntimeServices, + type GatewayChannelManager, +} from "./server-runtime-startup-services.js"; type GatewayRuntimeServiceLogger = { child: (name: string) => { @@ -22,10 +25,6 @@ export type GatewayMaintenanceHandles = NonNullable< Awaited> >; -export type GatewayChannelManager = Parameters< - typeof startChannelHealthMonitor ->[0]["channelManager"]; - function createNoopHeartbeatRunner(): HeartbeatRunner { return { stop: () => {}, @@ -33,26 +32,6 @@ function createNoopHeartbeatRunner(): HeartbeatRunner { }; } -export function startGatewayChannelHealthMonitor(params: { - cfg: OpenClawConfig; - channelManager: GatewayChannelManager; -}): ChannelHealthMonitor | null { - const healthCheckMinutes = params.cfg.gateway?.channelHealthCheckMinutes; - if (healthCheckMinutes === 0) { - return null; - } - const staleEventThresholdMinutes = params.cfg.gateway?.channelStaleEventThresholdMinutes; - const maxRestartsPerHour = params.cfg.gateway?.channelMaxRestartsPerHour; - return startChannelHealthMonitor({ - channelManager: params.channelManager, - checkIntervalMs: (healthCheckMinutes ?? 5) * 60_000, - ...(staleEventThresholdMinutes != null && { - staleEventThresholdMs: staleEventThresholdMinutes * 60_000, - }), - ...(maxRestartsPerHour != null && { maxRestartsPerHour }), - }); -} - export function startGatewayCronWithLogging(params: { cron: { start: () => Promise }; logCron: { error: (message: string) => void }; @@ -220,28 +199,6 @@ function startGatewayModelPricingRefreshOnDemand(params: { }; } -export function startGatewayRuntimeServices(params: { - minimalTestGateway: boolean; - cfgAtStart: OpenClawConfig; - channelManager: GatewayChannelManager; - log: GatewayRuntimeServiceLogger; -}): { - heartbeatRunner: HeartbeatRunner; - channelHealthMonitor: ChannelHealthMonitor | null; - stopModelPricingRefresh: () => void; -} { - const channelHealthMonitor = startGatewayChannelHealthMonitor({ - cfg: params.cfgAtStart, - channelManager: params.channelManager, - }); - - return { - heartbeatRunner: createNoopHeartbeatRunner(), - channelHealthMonitor, - stopModelPricingRefresh: () => {}, - }; -} - export function activateGatewayScheduledServices(params: { minimalTestGateway: boolean; cfgAtStart: OpenClawConfig; diff --git a/src/gateway/server-runtime-startup-services.ts b/src/gateway/server-runtime-startup-services.ts new file mode 100644 index 000000000000..a062c2fe1a58 --- /dev/null +++ b/src/gateway/server-runtime-startup-services.ts @@ -0,0 +1,65 @@ +import type { OpenClawConfig } from "../config/types.openclaw.js"; +import type { ChannelHealthMonitor } from "./channel-health-monitor.js"; +import { startChannelHealthMonitor } from "./channel-health-monitor.js"; + +type GatewayRuntimeServiceLogger = { + child: (name: string) => { + info: (message: string) => void; + warn: (message: string) => void; + error: (message: string) => void; + }; + error: (message: string) => void; +}; + +export type GatewayChannelManager = Parameters< + typeof startChannelHealthMonitor +>[0]["channelManager"]; + +function createNoopHeartbeatRunner() { + return { + stop: () => {}, + updateConfig: (_cfg: OpenClawConfig) => {}, + }; +} + +export function startGatewayChannelHealthMonitor(params: { + cfg: OpenClawConfig; + channelManager: GatewayChannelManager; +}): ChannelHealthMonitor | null { + const healthCheckMinutes = params.cfg.gateway?.channelHealthCheckMinutes; + if (healthCheckMinutes === 0) { + return null; + } + const staleEventThresholdMinutes = params.cfg.gateway?.channelStaleEventThresholdMinutes; + const maxRestartsPerHour = params.cfg.gateway?.channelMaxRestartsPerHour; + return startChannelHealthMonitor({ + channelManager: params.channelManager, + checkIntervalMs: (healthCheckMinutes ?? 5) * 60_000, + ...(staleEventThresholdMinutes != null && { + staleEventThresholdMs: staleEventThresholdMinutes * 60_000, + }), + ...(maxRestartsPerHour != null && { maxRestartsPerHour }), + }); +} + +export function startGatewayRuntimeServices(params: { + minimalTestGateway: boolean; + cfgAtStart: OpenClawConfig; + channelManager: GatewayChannelManager; + log: GatewayRuntimeServiceLogger; +}): { + heartbeatRunner: ReturnType; + channelHealthMonitor: ChannelHealthMonitor | null; + stopModelPricingRefresh: () => void; +} { + const channelHealthMonitor = startGatewayChannelHealthMonitor({ + cfg: params.cfgAtStart, + channelManager: params.channelManager, + }); + + return { + heartbeatRunner: createNoopHeartbeatRunner(), + channelHealthMonitor, + stopModelPricingRefresh: () => {}, + }; +} diff --git a/src/gateway/server.impl.ts b/src/gateway/server.impl.ts index a1750c983822..18055d3242c4 100644 --- a/src/gateway/server.impl.ts +++ b/src/gateway/server.impl.ts @@ -1114,14 +1114,13 @@ export async function startGatewayServer( getActiveTaskCount = earlyRuntime.getActiveTaskCount; runtimeState.skillsChangeUnsub = earlyRuntime.skillsChangeUnsub; - const [{ startGatewayEventSubscriptions }, gatewayRuntimeServices] = await startupTrace.measure( - "runtime.post-early-imports", - () => + const [{ startGatewayEventSubscriptions }, { startGatewayRuntimeServices }] = + await startupTrace.measure("runtime.post-early-imports", () => Promise.all([ import("./server-runtime-subscriptions.js"), - import("./server-runtime-services.js"), + import("./server-runtime-startup-services.js"), ]), - ); + ); const runtimeSubscriptions = await startupTrace.measure("runtime.subscriptions", () => startGatewayEventSubscriptions({ broadcast, @@ -1138,7 +1137,7 @@ export async function startGatewayServer( Object.assign(runtimeState, runtimeSubscriptions); const runtimeServices = await startupTrace.measure("runtime.services", () => - gatewayRuntimeServices.startGatewayRuntimeServices({ + startGatewayRuntimeServices({ minimalTestGateway, cfgAtStart, channelManager, @@ -1516,6 +1515,13 @@ export async function startGatewayServer( const sessionDeliveryRecoveryMaxEnqueuedAt = Date.now(); let postAttachRuntimeReturned = false; let scheduledServicesActivated = false; + let scheduledServicesModulePromise: Promise< + typeof import("./server-runtime-services.js") + > | null = null; + const loadScheduledServicesModule = () => { + scheduledServicesModulePromise ??= import("./server-runtime-services.js"); + return scheduledServicesModulePromise; + }; const activateScheduledServicesWhenReady = () => { if ( closePreludeStarted || @@ -1525,20 +1531,25 @@ export async function startGatewayServer( ) { return; } - const activated = gatewayRuntimeServices.activateGatewayScheduledServices({ - minimalTestGateway, - cfgAtStart, - deps, - sessionDeliveryRecoveryMaxEnqueuedAt, - cron: runtimeState.cronState.cron, - startCron: false, - logCron, - log, - pluginLookUpTable, - }); scheduledServicesActivated = true; - runtimeState.heartbeatRunner = activated.heartbeatRunner; - runtimeState.stopModelPricingRefresh = activated.stopModelPricingRefresh; + void loadScheduledServicesModule().then((gatewayRuntimeServices) => { + if (closePreludeStarted) { + return; + } + const activated = gatewayRuntimeServices.activateGatewayScheduledServices({ + minimalTestGateway, + cfgAtStart, + deps, + sessionDeliveryRecoveryMaxEnqueuedAt, + cron: runtimeState.cronState.cron, + startCron: false, + logCron, + log, + pluginLookUpTable, + }); + runtimeState.heartbeatRunner = activated.heartbeatRunner; + runtimeState.stopModelPricingRefresh = activated.stopModelPricingRefresh; + }); }; ({ stopGatewayUpdateCheck: runtimeState.stopGatewayUpdateCheck, @@ -1693,6 +1704,7 @@ export async function startGatewayServer( log.warn(`gateway: failed to promote config last-known-good backup: ${String(err)}`); }); if (!minimalTestGateway) { + const gatewayRuntimeServices = await loadScheduledServicesModule(); postReadyMaintenanceTimer = gatewayRuntimeServices.scheduleGatewayPostReadyMaintenance({ delayMs: POST_READY_MAINTENANCE_DELAY_MS, isClosing: () => closePreludeStarted,