From 1c0e02ccb01e564f88d070022804c85dcbecf8fe Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Mon, 24 Aug 2026 05:57:43 -0700 Subject: [PATCH] refactor(gateway): remove unused resident lifecycle registry (#128710) * refactor(gateway): remove unused resident lifecycle registry * chore(gateway): prune deleted registry assertion baseline --- config/assertion-safety-baseline.txt | 1 - src/gateway/server-core-runtime.ts | 45 +--- src/gateway/server-kernel.test.ts | 13 -- src/gateway/server-lifecycle.ts | 50 ++--- src/gateway/server-resident-registry.ts | 35 --- src/gateway/server-runtime-state-prepare.ts | 16 +- src/gateway/server-startup-finish.ts | 206 +++++++----------- .../server-startup-post-attach.test.ts | 2 - src/gateway/server-startup-post-attach.ts | 88 ++------ 9 files changed, 126 insertions(+), 330 deletions(-) delete mode 100644 src/gateway/server-resident-registry.ts diff --git a/config/assertion-safety-baseline.txt b/config/assertion-safety-baseline.txt index a7ecce8f1eba..c4c84403ab74 100644 --- a/config/assertion-safety-baseline.txt +++ b/config/assertion-safety-baseline.txt @@ -3052,7 +3052,6 @@ src/gateway/server-plugins-node-runtime.ts 1 src/gateway/server-plugins.ts 2 src/gateway/server-reload-restart.ts 2 src/gateway/server-reload-utils.ts 1 -src/gateway/server-resident-registry.ts 1 src/gateway/server-restart-sentinel-agent-delivery.ts 2 src/gateway/server-runtime-handles.ts 11 src/gateway/server-runtime-state-prepare.ts 2 diff --git a/src/gateway/server-core-runtime.ts b/src/gateway/server-core-runtime.ts index 741def22f135..6bf14651dd03 100644 --- a/src/gateway/server-core-runtime.ts +++ b/src/gateway/server-core-runtime.ts @@ -151,8 +151,6 @@ export async function startGatewayCoreRuntime(input: { workerEnvironmentStartup, broadcastPluginEvent, activateRuntimeSecrets, - residentRegistry, - shutdownRuntime, } = runtime; let currentPluginMetadataSnapshot = runtime.pluginMetadataSnapshot; if (desktopSessionRegistry) { @@ -171,11 +169,8 @@ export async function startGatewayCoreRuntime(input: { if (secretEgressProxy) { kernel.addGatewayLifetimeSidecar(secretEgressProxy); } - let earlyRuntimePromise: ReturnType< - Awaited>["startGatewayEarlyRuntime"] - > | null = null; - const startEarlyRuntime = () => { - earlyRuntimePromise ??= loadGatewayStartupEarlyModule().then(({ startGatewayEarlyRuntime }) => + const earlyRuntime = await startupTrace.measure("runtime.early", () => + loadGatewayStartupEarlyModule().then(({ startGatewayEarlyRuntime }) => startGatewayEarlyRuntime({ minimalTestGateway, cfgAtStart, @@ -221,25 +216,7 @@ export async function startGatewayCoreRuntime(input: { getRuntimeConfig, startupTrace, }), - ); - return earlyRuntimePromise; - }; - const discoveryResident = residentRegistry.register({ - name: "bonjour-discovery", - start: startEarlyRuntime, - stop: async () => await kernel.swapBonjourStop(null)?.(), - }); - const taskAndSkillsResident = residentRegistry.register({ - name: "task-and-skills-runtime", - start: async () => await discoveryResident.start(), - stop: async () => { - const earlyRuntime = await startEarlyRuntime(); - await earlyRuntime.skillsChangeUnsub(); - shutdownRuntime.stopTaskRegistryMaintenance(); - }, - }); - const earlyRuntime = await startupTrace.measure("runtime.early", () => - taskAndSkillsResident.start(), + ), ); kernel.setEarlyRuntimeHandles(earlyRuntime); @@ -250,9 +227,8 @@ export async function startGatewayCoreRuntime(input: { import("./server-runtime-startup-services.js"), ]), ); - const eventSubscriptionsResident = residentRegistry.register({ - name: "event-subscriptions", - start: () => + const { sessionCompanion, sessionObserver, ...runtimeSubscriptionUnsubs } = + await startupTrace.measure("runtime.subscriptions", () => startGatewayEventSubscriptions({ log, broadcast, @@ -267,16 +243,7 @@ export async function startGatewayCoreRuntime(input: { restartRecoveryCandidates, terminalSessions, }), - stop: async () => { - await runtimeState.agentUnsub?.(); - runtimeState.heartbeatUnsub?.(); - runtimeState.transcriptUnsub?.(); - runtimeState.lifecycleUnsub?.(); - runtimeState.taskUnsub?.(); - }, - }); - const { sessionCompanion, sessionObserver, ...runtimeSubscriptionUnsubs } = - await startupTrace.measure("runtime.subscriptions", () => eventSubscriptionsResident.start()); + ); Object.assign(runtimeState, runtimeSubscriptionUnsubs); await startupTrace.measure("runtime.services", () => diff --git a/src/gateway/server-kernel.test.ts b/src/gateway/server-kernel.test.ts index 7b6591d33679..8c5317468819 100644 --- a/src/gateway/server-kernel.test.ts +++ b/src/gateway/server-kernel.test.ts @@ -56,18 +56,6 @@ describe("createGatewayKernel", () => { expect(getStartup()).toMatchObject({ ok: true, status: "started" }); expect(getReadiness()).toMatchObject({ ready: true, failing: [] }); - const discoveryResident = kernel.residentRegistry - .list() - .find((resident) => resident.name === "bonjour-discovery"); - if (!discoveryResident) { - throw new Error("Expected the Gateway discovery resident"); - } - const residentFirstStop = vi.fn(async () => {}); - kernel.kernel.swapBonjourStop(residentFirstStop); - await discoveryResident.stop(); - expect(residentFirstStop).toHaveBeenCalledOnce(); - expect(kernel.runtimeState.bonjourStop).toBeNull(); - const closeFirstStop = vi.fn(async () => {}); kernel.kernel.swapBonjourStop(closeFirstStop); const configReloaderStop = createDeferred(); @@ -80,7 +68,6 @@ describe("createGatewayKernel", () => { expect(getReadiness()).toMatchObject({ ready: false, failing: ["gateway-draining"] }); configReloaderStop.resolve(); await closing; - await discoveryResident.stop(); expect(closeFirstStop).toHaveBeenCalledOnce(); expect(kernel.runtimeState.bonjourStop).toBeNull(); } finally { diff --git a/src/gateway/server-lifecycle.ts b/src/gateway/server-lifecycle.ts index bdbf573e9f26..8a5243a00f68 100644 --- a/src/gateway/server-lifecycle.ts +++ b/src/gateway/server-lifecycle.ts @@ -85,7 +85,6 @@ export async function prepareGatewayLifecycle(params: { watchNodeRequestHandler, defaultWorkspaceDir, activeTaskCount, - residentRegistry, desktopSessionRegistry, nodeDesktopStreamBroker, nodeDesktopObserveAvailable, @@ -598,35 +597,28 @@ export async function prepareGatewayLifecycle(params: { }); }; - const diagnosticHeartbeatResident = residentRegistry.register({ - name: "diagnostic-heartbeat", - start: () => { - // Gateway lifecycle owns both this existing heartbeat timer and the monitor - // it samples, so startup failure and normal close tear them down together. - startDiagnosticHeartbeat(undefined, { - getConfig: getRuntimeConfig, - startupGraceMs: 60_000, - sampleLiveness: () => { - const sample = readinessEventLoopHealth.persistentDegradationSnapshot(); - if (!sample || sample.degradedSinceMs == null) { - return null; - } - return { - reasons: sample.reasons, - intervalMs: sample.intervalMs, - degradedSinceMs: sample.degradedSinceMs, - eventLoopDelayP99Ms: sample.delayP99Ms, - eventLoopDelayMaxMs: sample.delayMaxMs, - eventLoopUtilization: sample.utilization, - cpuCoreRatio: sample.cpuCoreRatio, - }; - }, - }); - }, - stop: () => stopDiagnosticHeartbeat(), - }); if (diagnosticsEnabled) { - diagnosticHeartbeatResident.start(); + // Gateway lifecycle owns both this existing heartbeat timer and the monitor + // it samples, so startup failure and normal close tear them down together. + startDiagnosticHeartbeat(undefined, { + getConfig: getRuntimeConfig, + startupGraceMs: 60_000, + sampleLiveness: () => { + const sample = readinessEventLoopHealth.persistentDegradationSnapshot(); + if (!sample || sample.degradedSinceMs == null) { + return null; + } + return { + reasons: sample.reasons, + intervalMs: sample.intervalMs, + degradedSinceMs: sample.degradedSinceMs, + eventLoopDelayP99Ms: sample.delayP99Ms, + eventLoopDelayMaxMs: sample.delayMaxMs, + eventLoopUtilization: sample.utilization, + cpuCoreRatio: sample.cpuCoreRatio, + }; + }, + }); } return { diff --git a/src/gateway/server-resident-registry.ts b/src/gateway/server-resident-registry.ts deleted file mode 100644 index 145878cba3e6..000000000000 --- a/src/gateway/server-resident-registry.ts +++ /dev/null @@ -1,35 +0,0 @@ -type GatewayResident< - TStart extends () => unknown = () => unknown, - TStop extends () => unknown = () => unknown, -> = { - name: string; - start: TStart; - stop: TStop; -}; - -export type GatewayResidentRegistry = { - register: unknown, TStop extends () => unknown>( - resident: GatewayResident, - ) => GatewayResident; - list: () => readonly GatewayResident[]; -}; - -/** Records resident lifecycle owners without changing when callers start or stop them. */ -export function createGatewayResidentRegistry(): GatewayResidentRegistry { - const residents: GatewayResident[] = []; - const names = new Set(); - - return { - register: unknown, TStop extends () => unknown>( - resident: GatewayResident, - ) => { - if (names.has(resident.name)) { - throw new Error(`Gateway resident already registered: ${resident.name}`); - } - names.add(resident.name); - residents.push(resident as GatewayResident); - return resident; - }, - list: () => residents, - }; -} diff --git a/src/gateway/server-runtime-state-prepare.ts b/src/gateway/server-runtime-state-prepare.ts index 207234e37094..1439d4bf657f 100644 --- a/src/gateway/server-runtime-state-prepare.ts +++ b/src/gateway/server-runtime-state-prepare.ts @@ -24,7 +24,6 @@ import { createGatewayControlUiRootLifecycle } from "./server-control-ui-root.js import type { GatewayInstanceRuntime } from "./server-instance-runtime.types.js"; import type { GatewayServerLiveState } from "./server-live-state.js"; import type { GatewayRequestContext } from "./server-methods/types.js"; -import { createGatewayResidentRegistry } from "./server-resident-registry.js"; import type { SharedGatewaySessionGenerationState } from "./server-shared-auth-generation.js"; import type { prepareGatewayServerBootstrap } from "./server-startup-bootstrap.js"; import { createGatewayTransportBridge } from "./server-transport-bridge.js"; @@ -380,25 +379,13 @@ export async function prepareGatewayKernelState(params: { const systemAgentSessions: GatewayRequestContext["systemAgentSessions"] = new Map(); const deps = createDefaultDeps(); - const residentRegistry = createGatewayResidentRegistry(); const runtimeStateRef: { current: GatewayServerLiveState | null } = { current: null }; const cronStartState = { handled: false }; const gatewayTls = await startupTrace.measure("tls.runtime", () => loadGatewayTlsRuntime(cfgAtStart.gateway?.tls, log.child("tls")), ); const serverStartedAt = Date.now(); - const eventLoopHealthState: { - current?: ReturnType; - } = {}; - const eventLoopHealthResident = residentRegistry.register({ - name: "event-loop-health", - start: () => { - eventLoopHealthState.current ??= createGatewayEventLoopHealthMonitor(); - return eventLoopHealthState.current; - }, - stop: () => eventLoopHealthState.current?.stop(), - }); - const readinessEventLoopHealth = eventLoopHealthResident.start(); + const readinessEventLoopHealth = createGatewayEventLoopHealthMonitor(); const startupState = { sidecarsReady: minimalTestGateway, pendingReason: "startup-sidecars", @@ -581,7 +568,6 @@ export async function prepareGatewayKernelState(params: { purgeWizardSession, systemAgentSessions, deps, - residentRegistry, runtimeStateRef, cronStartState, gatewayTls, diff --git a/src/gateway/server-startup-finish.ts b/src/gateway/server-startup-finish.ts index 8b2eab605454..544c76384a9f 100644 --- a/src/gateway/server-startup-finish.ts +++ b/src/gateway/server-startup-finish.ts @@ -129,7 +129,6 @@ export async function finishGatewayStartup(params: { chatMetadataLifecycle, gatewayRequestContext, gatewayInstanceRuntime, - residentRegistry, getPluginMetadataSnapshot, } = runtime; const startupPluginRuntimeClaim = kernel.pluginRuntimeGeneration.currentClaim(); @@ -184,20 +183,6 @@ export async function finishGatewayStartup(params: { await startupTrace.measure("http.listen", () => startListening()); kernel.setDispatchReady(true); startupTrace.mark("http.bound"); - let databaseVerifierHandle: { stop: () => void | Promise } | null = null; - const databaseIntegrityResident = residentRegistry.register({ - name: "database-integrity-verifier", - start: async () => { - if (minimalTestGateway) { - return; - } - const { startOpenClawDatabaseIntegrityVerifier } = - await import("../state/openclaw-database-verify.js"); - databaseVerifierHandle = startOpenClawDatabaseIntegrityVerifier({ env: process.env }); - kernel.addGatewayLifetimeSidecar(databaseVerifierHandle); - }, - stop: async () => await databaseVerifierHandle?.stop(), - }); const sessionDeliveryRecoveryMaxEnqueuedAt = Date.now(); let postAttachRuntimeReturned = false; let scheduledServicesActivated = false; @@ -205,43 +190,35 @@ export async function finishGatewayStartup(params: { () => import("./server-runtime-services.js"), { cacheRejections: true }, ); - const scheduledServicesResident = residentRegistry.register({ - name: "scheduled-services", - start: () => { - if ( - lifecycle.closePreludeStarted || - !postAttachRuntimeReturned || - !startupState.sidecarsReady || - scheduledServicesActivated - ) { + const activateScheduledServicesWhenReady = () => { + if ( + lifecycle.closePreludeStarted || + !postAttachRuntimeReturned || + !startupState.sidecarsReady || + scheduledServicesActivated + ) { + return; + } + scheduledServicesActivated = true; + void loadScheduledServicesModule().then((gatewayRuntimeServices) => { + if (lifecycle.closePreludeStarted) { return; } - scheduledServicesActivated = true; - void loadScheduledServicesModule().then((gatewayRuntimeServices) => { - if (lifecycle.closePreludeStarted) { - return; - } - const activated = gatewayRuntimeServices.activateGatewayScheduledServices({ - minimalTestGateway, - cfgAtStart, - deps, - sessionDeliveryRecoveryMaxEnqueuedAt, - cronState: runtimeState.cronState, - cronReconciliation, - startCron: false, - logCron, - log, - resolveGatewayContext: resolvePluginGatewayContext, - }); - kernel.setScheduledServiceHandles(activated); + const activated = gatewayRuntimeServices.activateGatewayScheduledServices({ + minimalTestGateway, + cfgAtStart, + deps, + sessionDeliveryRecoveryMaxEnqueuedAt, + cronState: runtimeState.cronState, + cronReconciliation, + startCron: false, + logCron, + log, + resolveGatewayContext: resolvePluginGatewayContext, }); - }, - stop: async () => { - await runtimeState.stopOutboundDeliveryRecovery(); - runtimeState.heartbeatRunner.stop(); - }, - }); - const activateScheduledServicesWhenReady = scheduledServicesResident.start; + kernel.setScheduledServiceHandles(activated); + }); + }; const { createGatewayServerActiveWorkInspectors } = await import("./server-active-work.js"); const postAttachHandles = await startupTrace.measure("runtime.post-attach", () => loadGatewayStartupPostAttachModule().then(({ startGatewayPostAttachRuntime }) => @@ -348,7 +325,6 @@ export async function finishGatewayStartup(params: { sidecarStartup, waitForPostReadyWork: params.waitForPostReadyWork, activeWorkInspectors: createGatewayServerActiveWorkInspectors(gatewayRequestContext), - residentRegistry, providerAuthPrewarm: { getConfig: getRuntimeConfig, }, @@ -363,7 +339,9 @@ export async function finishGatewayStartup(params: { } finishGatewayRestartTrace("restart.ready", collectGatewayProcessMemoryUsageMb()); if (!minimalTestGateway) { - await databaseIntegrityResident.start(); + const { startOpenClawDatabaseIntegrityVerifier } = + await import("../state/openclaw-database-verify.js"); + kernel.addGatewayLifetimeSidecar(startOpenClawDatabaseIntegrityVerifier({ env: process.env })); } postAttachRuntimeReturned = true; activateScheduledServicesWhenReady(); @@ -450,98 +428,68 @@ export async function finishGatewayStartup(params: { ...(opts.hotReloadRecovery ? { requestRecoveryRestart: opts.hotReloadRecovery } : {}), restartRecoveryAvailable: opts.hotReloadRecovery !== undefined, }; - const configReloaderResident = residentRegistry.register({ - name: "config-reloader", - start: () => startManagedGatewayConfigReloader(configReloaderParams), - stop: async () => await runtimeState.configReloader.stop(), - }); - kernel.setConfigReloaderHandle(configReloaderResident.start()); + kernel.setConfigReloaderHandle(startManagedGatewayConfigReloader(configReloaderParams)); await promoteConfigSnapshotToLastKnownGood(startupLastGoodSnapshot).catch((err: unknown) => { log.warn(`gateway: failed to promote config last-known-good backup: ${String(err)}`); }); if (!minimalTestGateway) { const gatewayRuntimeServices = await loadScheduledServicesModule(); - const maintenanceResident = residentRegistry.register({ - name: "post-ready-maintenance", - start: () => { - postReadyState.maintenanceTimer = - gatewayRuntimeServices.scheduleGatewayPostReadyMaintenance({ - delayMs: POST_READY_MAINTENANCE_DELAY_MS, - isClosing: () => lifecycle.closePreludeStarted, - onStarted: () => { - postReadyState.maintenanceTimer = null; - }, - startMaintenance: async () => { - if (lifecycle.closePreludeStarted) { - return null; - } - return earlyRuntime.startMaintenance(); - }, - applyMaintenance: async (maintenance) => { - if (lifecycle.closePreludeStarted) { - clearInterval(maintenance.tickInterval); - clearInterval(maintenance.healthInterval); - clearInterval(maintenance.dedupeCleanup); - await maintenance.stopMediaCleanup(); - clearInterval(maintenance.worktreeCleanup); - maintenance.skillCuratorCleanup(); - return; - } - // Publish the stop owner before cleanup can touch SQLite or state paths; - // shutdown may begin immediately after this synchronous handoff. - kernel.setMaintenanceHandles(maintenance); - maintenance.startMediaCleanup(); - }, - shouldStartCron: () => !lifecycle.closePreludeStarted && !cronStartState.handled, - markCronStartHandled: () => { - cronStartState.handled = true; - }, - cronState: runtimeState.cronState, - cronReconciliation, - cronConfig: cfgAtStart, - logCron, - log, - recordPostReadyMemory: () => { - startupTrace.detail("memory.post-ready", collectGatewayProcessMemoryUsageMb()); - }, - }); + postReadyState.maintenanceTimer = gatewayRuntimeServices.scheduleGatewayPostReadyMaintenance({ + delayMs: POST_READY_MAINTENANCE_DELAY_MS, + isClosing: () => lifecycle.closePreludeStarted, + onStarted: () => { + postReadyState.maintenanceTimer = null; }, - stop: () => { - if (postReadyState.maintenanceTimer) { - clearTimeout(postReadyState.maintenanceTimer); - postReadyState.maintenanceTimer = null; + startMaintenance: async () => { + if (lifecycle.closePreludeStarted) { + return null; } + return earlyRuntime.startMaintenance(); + }, + applyMaintenance: async (maintenance) => { + if (lifecycle.closePreludeStarted) { + clearInterval(maintenance.tickInterval); + clearInterval(maintenance.healthInterval); + clearInterval(maintenance.dedupeCleanup); + await maintenance.stopMediaCleanup(); + clearInterval(maintenance.worktreeCleanup); + maintenance.skillCuratorCleanup(); + return; + } + // Publish the stop owner before cleanup can touch SQLite or state paths; + // shutdown may begin immediately after this synchronous handoff. + kernel.setMaintenanceHandles(maintenance); + maintenance.startMediaCleanup(); + }, + shouldStartCron: () => !lifecycle.closePreludeStarted && !cronStartState.handled, + markCronStartHandled: () => { + cronStartState.handled = true; + }, + cronState: runtimeState.cronState, + cronReconciliation, + cronConfig: cfgAtStart, + logCron, + log, + recordPostReadyMemory: () => { + startupTrace.detail("memory.post-ready", collectGatewayProcessMemoryUsageMb()); }, }); - maintenanceResident.start(); // The loop closes the previous server before this generation starts, so retired // plugin installs are safe to remove. Wait for an idle window and resolve current // install paths at execution time so cleanup cannot remove active code or delay a turn. - const retainedPluginCleanupResident = residentRegistry.register({ - name: "retained-plugin-cleanup", - start: () => { - postReadyState.retainedPluginCleanupHandle = gatewayRuntimeServices.scheduleGatewayIdleTask( - { - delayMs: RETAINED_PLUGIN_CLEANUP_DELAY_MS, - retryDelayMs: RETAINED_PLUGIN_CLEANUP_DELAY_MS, - isClosing: () => lifecycle.closePreludeStarted, - isBusy: () => getActiveGatewayRootWorkCount({ excludeCurrent: true }) > 0, - run: async () => { - const { cleanupRetainedPluginInstallGenerations } = - await import("./server-retained-plugin-cleanup.js"); - await cleanupRetainedPluginInstallGenerations({ log }); - }, - log, - errorMessage: "retained npm generation cleanup failed", - }, - ); - }, - stop: () => { - postReadyState.retainedPluginCleanupHandle?.stop(); - postReadyState.retainedPluginCleanupHandle = null; + postReadyState.retainedPluginCleanupHandle = gatewayRuntimeServices.scheduleGatewayIdleTask({ + delayMs: RETAINED_PLUGIN_CLEANUP_DELAY_MS, + retryDelayMs: RETAINED_PLUGIN_CLEANUP_DELAY_MS, + isClosing: () => lifecycle.closePreludeStarted, + isBusy: () => getActiveGatewayRootWorkCount({ excludeCurrent: true }) > 0, + run: async () => { + const { cleanupRetainedPluginInstallGenerations } = + await import("./server-retained-plugin-cleanup.js"); + await cleanupRetainedPluginInstallGenerations({ log }); }, + log, + errorMessage: "retained npm generation cleanup failed", }); - retainedPluginCleanupResident.start(); } else { startupTrace.detail("memory.post-ready", collectGatewayProcessMemoryUsageMb()); } diff --git a/src/gateway/server-startup-post-attach.test.ts b/src/gateway/server-startup-post-attach.test.ts index 224ea0c05d55..7a0e37ebd7d8 100644 --- a/src/gateway/server-startup-post-attach.test.ts +++ b/src/gateway/server-startup-post-attach.test.ts @@ -24,7 +24,6 @@ import { } from "../process/gateway-work-admission.js"; import { closeOpenClawStateDatabaseForTest } from "../state/openclaw-state-db.js"; import { withEnvAsync } from "../test-utils/env.js"; -import { createGatewayResidentRegistry } from "./server-resident-registry.js"; import "./server-startup-outcomes.test-support.js"; const hoisted = vi.hoisted(() => { @@ -3850,7 +3849,6 @@ function createPostAttachParams(overrides: Partial = {}): Post error: vi.fn(), }, unlockStartupMethods: vi.fn(), - residentRegistry: createGatewayResidentRegistry(), providerAuthPrewarm: { enabled: false }, unregisterGatewayLifetimeSidecar: vi.fn(), stopRegisteredPostReadySidecars: () => stopTrackedSidecars(publishedPostReadySidecars), diff --git a/src/gateway/server-startup-post-attach.ts b/src/gateway/server-startup-post-attach.ts index 7f714b2c5afc..c5f9d80880f9 100644 --- a/src/gateway/server-startup-post-attach.ts +++ b/src/gateway/server-startup-post-attach.ts @@ -40,7 +40,6 @@ import type { GatewayControlUiRootLifecycle } from "./server-control-ui-root.js" import type { GatewayRecoveryRuntime } from "./server-instance-runtime.types.js"; import type { GatewayClient, GatewayContextResolver } from "./server-methods/shared-types.js"; import type { GatewayPluginRuntimeClaim } from "./server-plugin-runtime-generation.js"; -import type { GatewayResidentRegistry } from "./server-resident-registry.js"; import type { refreshLatestUpdateRestartSentinel } from "./server-restart-sentinel.js"; import type { GatewaySidecarStartupMode } from "./server-sidecar-startup-mode.js"; import { scheduleContextCachePrewarm } from "./server-startup-context-cache-prewarm.js"; @@ -1206,36 +1205,26 @@ export async function startGatewayPostAttachRuntime( }; waitForPostReadyWork?: () => Promise; activeWorkInspectors?: Partial; - residentRegistry: GatewayResidentRegistry; }, runtimeDeps: GatewayPostAttachRuntimeDeps = defaultGatewayPostAttachRuntimeDeps, ) { const controlUiRootLifecycle = params.controlUiRootLifecycle; const mainSessionRecoveryStartupCheckedStorePaths = new Set(); - let controlUiAssetsSidecar: GatewayPostReadySidecarHandle | undefined; - const controlUiAssetsResident = params.residentRegistry.register({ - name: "control-ui-assets", - start: () => { - controlUiAssetsSidecar = - !params.minimalTestGateway && controlUiRootLifecycle?.state?.kind === "preparing" - ? schedulePostReadySidecarTask({ - name: "sidecars.control-ui-assets", - startupTrace: params.startupTrace, - log: params.log, - run: controlUiRootLifecycle.start, - stop: controlUiRootLifecycle.stop, - }) - : undefined; - if (controlUiAssetsSidecar) { - // Publish before the first await: slow CA/plugin startup must not strand - // the dashboard or hide its running builder from Gateway shutdown. - params.onGatewayLifetimeSidecars?.([controlUiAssetsSidecar]); - } - return controlUiAssetsSidecar; - }, - stop: async () => await controlUiAssetsSidecar?.stop(), - }); - controlUiAssetsResident.start(); + const controlUiAssetsSidecar = + !params.minimalTestGateway && controlUiRootLifecycle?.state?.kind === "preparing" + ? schedulePostReadySidecarTask({ + name: "sidecars.control-ui-assets", + startupTrace: params.startupTrace, + log: params.log, + run: controlUiRootLifecycle.start, + stop: controlUiRootLifecycle.stop, + }) + : undefined; + if (controlUiAssetsSidecar) { + // Publish before the first await: slow CA/plugin startup must not strand + // the dashboard or hide its running builder from Gateway shutdown. + params.onGatewayLifetimeSidecars?.([controlUiAssetsSidecar]); + } if (!params.minimalTestGateway) { // The HTTP server is already attached, so keep health probes responsive while the worker @@ -1287,12 +1276,6 @@ export async function startGatewayPostAttachRuntime( })(); return await startupPluginsLoadPromise; }; - const startupPluginsResident = params.residentRegistry.register({ - name: "startup-plugin-load", - start: loadStartupPluginsIfNeeded, - stop: () => {}, - }); - let startupLogPromise: Promise | undefined; const startupLogSettled = createDeferredCore(); // Tailscale and sidecar work can delay the public readiness handle past log failure. @@ -1348,11 +1331,6 @@ export async function startGatewayPostAttachRuntime( activeWorkInspectors: params.activeWorkInspectors, }); - const updateCheckResident = params.residentRegistry.register({ - name: "update-check", - start: updateCheck.start, - stop: updateCheck.stop, - }); let pluginServicesReported = false; let reportedPluginServices: PluginServicesHandle | null = null; const reportPluginServices = (pluginServices: PluginServicesHandle | null) => { @@ -1394,7 +1372,7 @@ export async function startGatewayPostAttachRuntime( skipStartupLog(); return emptySidecarResult(); } - await startupPluginsResident.start(); + await loadStartupPluginsIfNeeded(); if (params.isClosing?.()) { skipStartupLog(); return emptySidecarResult(); @@ -1606,31 +1584,7 @@ export async function startGatewayPostAttachRuntime( params.log.info("gateway ready"); return { ...result, postReadySidecars, gatewayLifetimeSidecars, pluginRegistry }; }); - let startedSidecars: ReturnType | undefined; - const sidecarSequenceResident = params.residentRegistry.register({ - name: "sidecar-sequence", - start: () => { - startedSidecars ??= startSidecars(); - return startedSidecars; - }, - stop: async () => { - const result = await startedSidecars; - for (const sidecar of result?.postReadySidecars ?? []) { - await sidecar.stop(); - } - }, - }); - const perConfigSidecarsResident = params.residentRegistry.register({ - name: "per-config-sidecars", - start: sidecarSequenceResident.start, - stop: async () => { - const result = await startedSidecars; - for (const sidecar of result?.gatewayLifetimeSidecars ?? []) { - await sidecar.stop(); - } - }, - }); - const sidecarsPromise = perConfigSidecarsResident.start(); + const sidecarsPromise = startSidecars(); void sidecarsPromise .then(async (sidecarsResult) => { @@ -1684,15 +1638,15 @@ export async function startGatewayPostAttachRuntime( if (params.sidecarStartup !== "defer") { const sidecarsResult = await sidecarsPromise; - updateCheckResident.start(); + updateCheck.start(); return { - stopGatewayUpdateCheck: updateCheckResident.stop, + stopGatewayUpdateCheck: updateCheck.stop, pluginServices: sidecarsResult.pluginServices, startupSettled: Promise.resolve(), }; } - updateCheckResident.start(); + updateCheck.start(); const startupSettled = Promise.all([sidecarsPromise, startupLogSettled.promise]).then( () => undefined, ); @@ -1701,7 +1655,7 @@ export async function startGatewayPostAttachRuntime( void startupSettled.catch(() => {}); return { - stopGatewayUpdateCheck: updateCheckResident.stop, + stopGatewayUpdateCheck: updateCheck.stop, pluginServices: reportedPluginServices, startupSettled, };