From 776e5d8a0847a79ddbc87d84c58b04bb42ae669b Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Mon, 16 Mar 2026 01:41:14 -0700 Subject: [PATCH] Gateway: lazily resolve channel runtime --- src/gateway/server-channels.ts | 18 ++++++++++++++++-- src/gateway/server.impl.ts | 9 ++++++++- 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/src/gateway/server-channels.ts b/src/gateway/server-channels.ts index 075fac382a3..a016826f69b 100644 --- a/src/gateway/server-channels.ts +++ b/src/gateway/server-channels.ts @@ -105,6 +105,14 @@ type ChannelManagerOptions = { * @see {@link ChannelGatewayContext.channelRuntime} */ channelRuntime?: PluginRuntime["channel"]; + /** + * Lazily resolves optional channel runtime helpers for external channel plugins. + * + * Use this when the caller wants to avoid instantiating the full plugin channel + * runtime during gateway startup. The manager only needs the runtime surface once + * a channel account actually starts. + */ + resolveChannelRuntime?: () => PluginRuntime["channel"]; }; type StartChannelOptions = { @@ -125,7 +133,8 @@ export type ChannelManager = { // Channel docking: lifecycle hooks (`plugin.gateway`) flow through this manager. export function createChannelManager(opts: ChannelManagerOptions): ChannelManager { - const { loadConfig, channelLogs, channelRuntimeEnvs, channelRuntime } = opts; + const { loadConfig, channelLogs, channelRuntimeEnvs, channelRuntime, resolveChannelRuntime } = + opts; const channelStores = new Map(); // Tracks restart attempts per channel:account. Reset on successful start. @@ -219,6 +228,10 @@ export function createChannelManager(opts: ChannelManagerOptions): ChannelManage return next; }; + const getChannelRuntime = (): PluginRuntime["channel"] | undefined => { + return channelRuntime ?? resolveChannelRuntime?.(); + }; + const startChannelInternal = async ( channelId: ChannelId, accountId?: string, @@ -297,6 +310,7 @@ export function createChannelManager(opts: ChannelManagerOptions): ChannelManage }); const log = channelLogs[channelId]; + const resolvedChannelRuntime = getChannelRuntime(); const task = startAccount({ cfg, accountId: id, @@ -306,7 +320,7 @@ export function createChannelManager(opts: ChannelManagerOptions): ChannelManage log, getStatus: () => getRuntime(channelId, id), setStatus: (next) => setRuntime(channelId, id, next), - ...(channelRuntime ? { channelRuntime } : {}), + ...(resolvedChannelRuntime ? { channelRuntime: resolvedChannelRuntime } : {}), }); const trackedPromise = Promise.resolve(task) .catch((err) => { diff --git a/src/gateway/server.impl.ts b/src/gateway/server.impl.ts index 6210f63464c..4c22e94bddf 100644 --- a/src/gateway/server.impl.ts +++ b/src/gateway/server.impl.ts @@ -138,6 +138,13 @@ const logDiscovery = log.child("discovery"); const logTailscale = log.child("tailscale"); const logChannels = log.child("channels"); const logBrowser = log.child("browser"); + +let cachedChannelRuntime: ReturnType["channel"] | null = null; + +function getChannelRuntime() { + cachedChannelRuntime ??= createPluginRuntime().channel; + return cachedChannelRuntime; +} const logHealth = log.child("health"); const logCron = log.child("cron"); const logReload = log.child("reload"); @@ -575,7 +582,7 @@ export async function startGatewayServer( loadConfig, channelLogs, channelRuntimeEnvs, - channelRuntime: createPluginRuntime().channel, + resolveChannelRuntime: getChannelRuntime, }); const getReadiness = createReadinessChecker({ channelManager,