UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

869 lines (868 loc) 36.2 kB
import { t as isTruthyEnvValue } from "./env-Di49gJwo.js"; import { y as resolveStateDir } from "./paths-mvMm5bYV.js"; import { i as getPluginModuleLoaderStats } from "./plugin-module-loader-cache-lz0qhu-X.js"; import { t as STARTUP_UNAVAILABLE_GATEWAY_METHODS } from "./core-descriptors-CqQ0q50E.js"; import { t as GATEWAY_EVENT_UPDATE_AVAILABLE } from "./events-CrZXFXYx.js"; import { t as hasConfiguredInternalHooks } from "./configured-pV8SaeM2.js"; import fs from "node:fs"; import path from "node:path"; import os from "node:os"; import { monitorEventLoopDelay, performance } from "node:perf_hooks"; import { setTimeout as setTimeout$1 } from "node:timers/promises"; //#region src/gateway/server-startup-post-attach.ts const ACP_BACKEND_READY_TIMEOUT_MS = 5e3; const ACP_BACKEND_READY_POLL_MS = 50; const PRIMARY_MODEL_PREWARM_TIMEOUT_MS = 5e3; const STARTUP_PROVIDER_DISCOVERY_TIMEOUT_MS = 5e3; const PROVIDER_AUTH_PREWARM_START_DELAY_MS = 5e3; const PROVIDER_AUTH_REWARM_DELAY_MS = 1e3; const AGENT_RUNTIME_PLUGIN_PREWARM_START_DELAY_MS = 1e4; const DEFERRED_SIDECAR_START_DELAY_MS = 100; const SESSION_LOCK_CLEANUP_CONCURRENCY = 4; const SKIP_STARTUP_MODEL_PREWARM_ENV = "OPENCLAW_SKIP_STARTUP_MODEL_PREWARM"; const QMD_STARTUP_IDLE_DELAY_MS = 12e4; const RESTART_SENTINEL_FILENAME = "restart-sentinel.json"; let mainSessionRestartRecoveryModulePromise = null; let agentDefaultsModulePromise = null; let agentModelSelectionModulePromise = null; let internalHooksModulePromise = null; let gatewayRestartSentinelModulePromise = null; const loadMainSessionRestartRecoveryModule = async () => { mainSessionRestartRecoveryModulePromise ??= import("./main-session-restart-recovery-FE3cfva8.js"); return await mainSessionRestartRecoveryModulePromise; }; const loadAgentDefaultsModule = async () => { agentDefaultsModulePromise ??= import("./defaults-RurGC76M.js"); return await agentDefaultsModulePromise; }; const loadAgentModelSelectionModule = async () => { agentModelSelectionModulePromise ??= import("./model-selection-CZSV2CxT.js"); return await agentModelSelectionModulePromise; }; const loadInternalHooksModule = async () => { internalHooksModulePromise ??= import("./internal-hooks-CV0MmhNy.js"); return await internalHooksModulePromise; }; const loadGatewayRestartSentinelModule = async () => { gatewayRestartSentinelModulePromise ??= import("./server-restart-sentinel-vz7ikyhF.js"); return await gatewayRestartSentinelModulePromise; }; /** Stop sidecars immediately when shutdown has already started before they are reported. */ function stopPostReadySidecarsAfterCloseStarted(params) { if (!params.closeStarted) return; for (const postReadySidecar of params.postReadySidecars) postReadySidecar.stop(); } /** Measure a post-attach startup step when tracing is active. */ async function measureStartup(startupTrace, name, run) { return startupTrace ? startupTrace.measure(name, run) : await run(); } /** Measure provider-auth warming without letting event-loop stalls hide in wall time. */ async function measureProviderAuthWarm(run) { const eventLoopDelay = monitorEventLoopDelay({ resolution: 10 }); eventLoopDelay.enable(); const startMs = performance.now(); try { await run(); } finally { eventLoopDelay.disable(); } return { elapsedMs: performance.now() - startMs, eventLoopMaxMs: eventLoopDelay.max / 1e6 }; } function formatProviderAuthWarmMetrics(metrics) { return `in ${metrics.elapsedMs.toFixed(0)}ms eventLoopMax=${metrics.eventLoopMaxMs.toFixed(1)}ms`; } function shouldCheckRestartSentinel(env = process.env) { return !env.VITEST && env.NODE_ENV !== "test"; } function shouldSkipStartupModelPrewarm(env = process.env) { const raw = env[SKIP_STARTUP_MODEL_PREWARM_ENV]?.trim().toLowerCase(); return raw === "1" || raw === "true" || raw === "yes" || raw === "on"; } function resolveGatewayMemoryStartupPolicy(cfg) { if (cfg.memory?.backend !== "qmd") return { mode: "off" }; const startup = cfg.memory.qmd?.update?.startup; if (startup === "immediate") return { mode: "immediate" }; if (startup === "idle") { const rawDelayMs = cfg.memory.qmd?.update?.startupDelayMs; return { mode: "idle", delayMs: typeof rawDelayMs === "number" && Number.isFinite(rawDelayMs) && rawDelayMs >= 0 ? Math.floor(rawDelayMs) : QMD_STARTUP_IDLE_DELAY_MS }; } return { mode: "off" }; } function scheduleGatewayMemoryBackend(params) { if (params.policy.mode === "off") return; const start = () => { import("./server-startup-memory-CSDNFZEZ.js").then(({ startGatewayMemoryBackend }) => startGatewayMemoryBackend({ cfg: params.cfg, log: params.log })).catch((err) => { params.log.warn(`qmd memory startup initialization failed: ${String(err)}`); }); }; if (params.policy.mode === "immediate") { setImmediate(start); return; } setTimeout(start, params.policy.delayMs).unref?.(); } function schedulePostAttachUpdateSentinelRefresh(params) { setImmediate(() => { measureStartup(params.startupTrace, "post-attach.update-sentinel", async () => { try { await params.refreshLatestUpdateRestartSentinel(); } catch (err) { params.log.warn(`restart sentinel refresh failed: ${String(err)}`); } }).catch((err) => { params.log.warn(`restart sentinel refresh failed: ${String(err)}`); }); }).unref?.(); } function scheduleProviderAuthStatePrewarm(params) { let stopped = false; let startupTimer; let rewarmTimer; let rewarmInFlight = false; let pendingRewarmReason; const isStopped = () => stopped; const delayMs = params.delayMs ?? PROVIDER_AUTH_PREWARM_START_DELAY_MS; (async () => { const [{ setAuthProfileFailureHook }, { clearCurrentProviderAuthState }] = await Promise.all([import("./failure-hook-DL7zdDho.js"), import("./model-provider-auth-state-CKEnjneK.js")]); const loadProviderAuthWarmModule = () => import("./model-provider-auth-BUkpjs9K.js"); const runRewarm = async (reason) => { if (isStopped()) return; const cfg = params.getConfig(); rewarmInFlight = true; try { const { warmCurrentProviderAuthStateOffMainThread } = await loadProviderAuthWarmModule(); const metrics = await measureProviderAuthWarm(() => warmCurrentProviderAuthStateOffMainThread(cfg, { isCancelled: isStopped })); if (isStopped()) return; params.log.info(`provider auth state re-warmed (${reason}) ${formatProviderAuthWarmMetrics(metrics)}`); } catch (err) { params.log.warn(`provider auth state rewarm failed: ${String(err)}`); } finally { rewarmInFlight = false; const nextReason = pendingRewarmReason; pendingRewarmReason = void 0; if (nextReason && !isStopped()) scheduleAuthMapRewarm(nextReason); } }; const scheduleAuthMapRewarm = (reason) => { if (isStopped()) return; pendingRewarmReason = reason; if (rewarmTimer || rewarmInFlight) return; rewarmTimer = setTimeout(() => { rewarmTimer = void 0; const nextReason = pendingRewarmReason ?? reason; pendingRewarmReason = void 0; runRewarm(nextReason); }, PROVIDER_AUTH_REWARM_DELAY_MS); rewarmTimer.unref?.(); }; if (isStopped()) return; setAuthProfileFailureHook(() => { if (isStopped()) return; clearCurrentProviderAuthState(); scheduleAuthMapRewarm("auth-profile-failure"); }); startupTimer = setTimeout(() => { (async () => { if (isStopped()) return; const cfg = params.getConfig(); const { warmCurrentProviderAuthStateOffMainThread } = await loadProviderAuthWarmModule(); const metrics = await measureProviderAuthWarm(() => warmCurrentProviderAuthStateOffMainThread(cfg, { isCancelled: isStopped })); if (isStopped()) return; params.log.info(`provider auth state pre-warmed ${formatProviderAuthWarmMetrics(metrics)}`); })().catch((err) => { params.log.warn(`provider auth state pre-warm failed: ${String(err)}`); }); }, Math.max(0, delayMs)); startupTimer.unref?.(); })().catch((err) => { params.log.warn(`provider auth state pre-warm setup failed: ${String(err)}`); }); return { stop: () => { stopped = true; if (startupTimer) { clearTimeout(startupTimer); startupTimer = void 0; } if (rewarmTimer) { clearTimeout(rewarmTimer); rewarmTimer = void 0; } } }; } function scheduleAgentRuntimePluginPrewarm(params) { let stopped = false; let timer; const isStopped = () => stopped; timer = setTimeout(() => { timer = void 0; measureStartup(params.startupTrace, "post-ready.agent-runtime-plugins", async () => { if (isStopped()) return; const started = performance.now(); const { ensureRuntimePluginsLoaded } = await import("./runtime-plugins-Bglx8Z0D.js"); const cfg = params.getConfig(); if (isStopped()) return; ensureRuntimePluginsLoaded({ config: cfg, workspaceDir: params.workspaceDir, allowGatewaySubagentBinding: true }); if (!isStopped()) params.log.info(`agent runtime plugins pre-warmed in ${(performance.now() - started).toFixed(0)}ms`); }).catch((err) => { params.log.warn(`agent runtime plugin pre-warm failed: ${String(err)}`); }); }, Math.max(0, params.delayMs ?? AGENT_RUNTIME_PLUGIN_PREWARM_START_DELAY_MS)); timer.unref?.(); return { stop: () => { stopped = true; if (timer) { clearTimeout(timer); timer = void 0; } } }; } function schedulePostReadySidecarTask(params) { let stopped = false; const abortController = new AbortController(); const isStopped = () => stopped; const handle = setImmediate(() => { if (isStopped()) return; measureStartup(params.startupTrace, params.name, () => params.run(isStopped, abortController.signal)).catch((err) => { params.log.warn(`${params.name} failed after gateway ready: ${String(err)}`); }); }); handle.unref?.(); return { stop: async () => { stopped = true; abortController.abort(); clearImmediate(handle); await params.stop?.(); } }; } async function cleanupStaleSessionLocks(params) { const concurrency = Math.max(1, Math.min(params.sessionDirs.length, Math.floor(params.concurrency ?? SESSION_LOCK_CLEANUP_CONCURRENCY))); let nextIndex = 0; let markRestartAbortedMainSessionsFromLocks = params.markRestartAbortedMainSessionsFromLocks ?? null; const getMarker = async () => { markRestartAbortedMainSessionsFromLocks ??= (await loadMainSessionRestartRecoveryModule()).markRestartAbortedMainSessionsFromLocks; return markRestartAbortedMainSessionsFromLocks; }; const worker = async () => { while (!params.isStopped()) { const sessionsDir = params.sessionDirs[nextIndex]; nextIndex += 1; if (!sessionsDir) return; const result = await params.cleanStaleLockFiles({ sessionsDir, config: params.cfg, removeStale: true, log: { warn: (message) => params.log.warn(message) } }); if (result.cleaned.length === 0) continue; await (await getMarker())({ sessionsDir, cleanedLocks: result.cleaned }); } }; await Promise.all(Array.from({ length: concurrency }, () => worker())); } function scheduleTranscriptsAutoStartSidecar(params) { let stopTranscriptsAutoStart; return schedulePostReadySidecarTask({ startupTrace: params.startupTrace, name: "sidecars.transcripts-auto-start", log: params.log, run: async (isStopped) => { const { createTranscriptsAutoStartService } = await import("./transcripts-tool-Cb_XFUc-.js"); if (isStopped()) return; const service = createTranscriptsAutoStartService({ config: params.cfg, stateDir: resolveStateDir(), logger: params.log }); stopTranscriptsAutoStart = () => service.stop(); service.start(); }, stop: async () => { await stopTranscriptsAutoStart?.(); } }); } async function pathExists(filePath) { try { await fs.promises.access(filePath); return true; } catch { return false; } } async function resolveRestartSentinelPathFast(env = process.env) { const normalizePathEnv = (value) => { const trimmed = value?.trim(); return trimmed && trimmed !== "undefined" && trimmed !== "null" ? trimmed : void 0; }; const resolveRawOsHome = () => normalizePathEnv(env.HOME) ?? normalizePathEnv(env.USERPROFILE); const expandHomePrefix = (input, home) => input.replace(/^~(?=$|[\\/])/, home); const resolveHome = () => { const explicitHome = normalizePathEnv(env.OPENCLAW_HOME); if (explicitHome) { const osHome = resolveRawOsHome() ?? os.homedir(); return path.resolve(expandHomePrefix(explicitHome, osHome)); } return path.resolve(resolveRawOsHome() ?? os.homedir()); }; const resolveUserPath = (input) => { const trimmed = input.trim(); if (trimmed.startsWith("~")) return path.resolve(expandHomePrefix(trimmed, resolveHome())); return path.resolve(trimmed); }; const override = normalizePathEnv(env.OPENCLAW_STATE_DIR); if (override) return path.join(resolveUserPath(override), RESTART_SENTINEL_FILENAME); const home = resolveHome(); const newStateDir = path.join(home, ".openclaw"); if (env.OPENCLAW_TEST_FAST === "1" || await pathExists(newStateDir)) return path.join(newStateDir, RESTART_SENTINEL_FILENAME); const legacyStateDir = path.join(home, ".clawdbot"); if (await pathExists(legacyStateDir)) return path.join(legacyStateDir, RESTART_SENTINEL_FILENAME); return path.join(newStateDir, RESTART_SENTINEL_FILENAME); } async function hasRestartSentinelFileFast(env = process.env) { try { return await pathExists(await resolveRestartSentinelPathFast(env)); } catch { return false; } } async function refreshLatestUpdateRestartSentinelIfPresent() { if (!await hasRestartSentinelFileFast()) return null; return await (await loadGatewayRestartSentinelModule()).refreshLatestUpdateRestartSentinel(); } function hasGatewayStartHooks(pluginRegistry) { return pluginRegistry.typedHooks.some((hook) => hook.hookName === "gateway_start"); } function isConfiguredCliBackendPrimary(params) { const slashIndex = params.explicitPrimary.indexOf("/"); if (slashIndex <= 0) return false; const provider = params.normalizeProviderId(params.explicitPrimary.slice(0, slashIndex)); return Object.keys(params.cfg.agents?.defaults?.cliBackends ?? {}).some((backend) => params.normalizeProviderId(backend) === provider); } async function hasGatewayStartupInternalHookListeners() { const { hasInternalHookListeners } = await loadInternalHooksModule(); return hasInternalHookListeners("gateway", "startup"); } async function waitForAcpRuntimeBackendReady(params) { const { getAcpRuntimeBackend } = await import("./registry-BCUxOlSv.js"); const timeoutMs = params.timeoutMs ?? ACP_BACKEND_READY_TIMEOUT_MS; const pollMs = params.pollMs ?? ACP_BACKEND_READY_POLL_MS; const deadline = Date.now() + timeoutMs; do { const backend = getAcpRuntimeBackend(params.backendId); if (backend) try { if (!backend.healthy || backend.healthy()) return true; } catch {} await setTimeout$1(pollMs, void 0, { ref: false }); } while (Date.now() < deadline); return false; } async function prewarmConfiguredPrimaryModel(params) { const { resolveAgentModelPrimaryValue } = await import("./model-input-CTwgL4Oh.js"); const explicitPrimary = resolveAgentModelPrimaryValue(params.cfg.agents?.defaults?.model)?.trim(); if (!explicitPrimary) return; const { normalizeProviderId } = await import("./provider-id-wkQ8aDtH.js"); if (isConfiguredCliBackendPrimary({ cfg: params.cfg, explicitPrimary, normalizeProviderId })) return; const [{ resolveAgentWorkspaceDir, resolveDefaultAgentDir, resolveDefaultAgentId }, { DEFAULT_MODEL, DEFAULT_PROVIDER }, { isCliProvider, resolveConfiguredModelRef }] = await Promise.all([ import("./agent-scope-Do4dPjL6.js"), loadAgentDefaultsModule(), loadAgentModelSelectionModule() ]); const { provider, model } = resolveConfiguredModelRef({ cfg: params.cfg, defaultProvider: DEFAULT_PROVIDER, defaultModel: DEFAULT_MODEL }); if (isCliProvider(provider, params.cfg)) return; const { ensureOpenClawModelsJson } = await import("./models-config-DUvR7jcM.js"); const agentDir = resolveDefaultAgentDir(params.cfg); const workspaceDir = params.workspaceDir ?? resolveAgentWorkspaceDir(params.cfg, resolveDefaultAgentId(params.cfg)); try { await ensureOpenClawModelsJson(params.cfg, agentDir, { workspaceDir, providerDiscoveryProviderIds: [provider], providerDiscoveryTimeoutMs: STARTUP_PROVIDER_DISCOVERY_TIMEOUT_MS, providerDiscoveryEntriesOnly: true }); } catch (err) { params.log.warn(`startup model warmup failed for ${provider}/${model}: ${String(err)}`); } } async function prewarmConfiguredPrimaryModelWithTimeout(params, prewarm = prewarmConfiguredPrimaryModel) { let settled = false; const warmup = prewarm(params).catch((err) => { params.log.warn(`startup model warmup failed: ${String(err)}`); }).finally(() => { settled = true; }); const timeout = setTimeout$1(params.timeoutMs ?? PRIMARY_MODEL_PREWARM_TIMEOUT_MS, void 0, { ref: false }).then(() => { if (!settled) params.log.warn(`startup model warmup timed out after ${params.timeoutMs ?? PRIMARY_MODEL_PREWARM_TIMEOUT_MS}ms; continuing without waiting`); }); await Promise.race([warmup, timeout]); } function schedulePrimaryModelPrewarm(params, prewarm = prewarmConfiguredPrimaryModel) { if (shouldSkipStartupModelPrewarm()) return; measureStartup(params.startupTrace, "sidecars.model-prewarm", () => prewarmConfiguredPrimaryModelWithTimeout({ cfg: params.cfg, ...params.workspaceDir ? { workspaceDir: params.workspaceDir } : {}, log: params.log }, prewarm)).catch((err) => { params.log.warn(`startup model warmup failed: ${String(err)}`); }); } /** Start post-ready sidecars such as channels, hooks, plugin services, and cleanup tasks. */ async function startGatewaySidecars(params) { const postReadySidecars = []; const internalHooksConfigured = hasConfiguredInternalHooks(params.cfg); await measureStartup(params.startupTrace, "sidecars.internal-hooks", async () => { try { if (internalHooksConfigured) { const [{ setInternalHooksEnabled }, { loadInternalHooks }] = await Promise.all([loadInternalHooksModule(), import("./loader-KhPD7boM.js")]); setInternalHooksEnabled(params.cfg.hooks?.internal?.enabled !== false); const loadedCount = await loadInternalHooks(params.cfg, params.defaultWorkspaceDir); if (loadedCount > 0) params.logHooks.info(`loaded ${loadedCount} internal hook handler${loadedCount > 1 ? "s" : ""}`); } } catch (err) { params.logHooks.error(`failed to load hooks: ${String(err)}`); } }); const skipChannels = isTruthyEnvValue(process.env.OPENCLAW_SKIP_CHANNELS) || isTruthyEnvValue(process.env.OPENCLAW_SKIP_PROVIDERS); await measureStartup(params.startupTrace, "sidecars.main-session-recovery", async () => { try { const { markStartupOrphanedMainSessionsForRecovery } = await loadMainSessionRestartRecoveryModule(); await markStartupOrphanedMainSessionsForRecovery({ cfg: params.cfg }); } catch (err) { params.log.warn(`main-session startup orphan marking failed before channel startup: ${String(err)}`); } }); await measureStartup(params.startupTrace, "sidecars.channels", async () => { if (!skipChannels) try { schedulePrimaryModelPrewarm({ cfg: params.cfg, workspaceDir: params.defaultWorkspaceDir, log: params.log, startupTrace: params.startupTrace }, params.prewarmPrimaryModel); await measureStartup(params.startupTrace, "sidecars.channel-start", () => params.startChannels()); } catch (err) { params.logChannels.error(`channel startup failed: ${String(err)}`); } else await measureStartup(params.startupTrace, "sidecars.channel-skip", () => params.logChannels.info("skipping channel start (OPENCLAW_SKIP_CHANNELS=1 or OPENCLAW_SKIP_PROVIDERS=1)")); }); await params.onChannelsStarted?.(); let pluginServices = params.shouldStartPluginServices?.() === false ? null : await measureStartup(params.startupTrace, "sidecars.plugin-services", async () => { try { const { startPluginServices } = await import("./services-Be8nnJ8N.js"); return await startPluginServices({ registry: params.pluginRegistry, config: params.cfg, workspaceDir: params.defaultWorkspaceDir, startupTrace: params.startupTrace }); } catch (err) { params.log.warn(`plugin services failed to start: ${String(err)}`); return null; } }); if (pluginServices && params.shouldStartPluginServices?.() === false) { await pluginServices.stop().catch((err) => { params.log.warn(`plugin services stop after close failed: ${String(err)}`); }); pluginServices = null; } params.onPluginServices?.(pluginServices); if (internalHooksConfigured || await hasGatewayStartupInternalHookListeners()) setTimeout(() => { loadInternalHooksModule().then(({ createInternalHookEvent, triggerInternalHook }) => { triggerInternalHook(createInternalHookEvent("gateway", "startup", "gateway:startup", { cfg: params.cfg, deps: params.deps, workspaceDir: params.defaultWorkspaceDir })); }); }, 250); if (params.cfg.acp?.enabled) (async () => { const ready = await measureStartup(params.startupTrace, "sidecars.acp.runtime-ready", () => waitForAcpRuntimeBackendReady({ backendId: params.cfg.acp?.backend })); params.startupTrace?.detail("sidecars.acp.runtime-ready", [["readyCount", ready ? 1 : 0], ["backend", params.cfg.acp?.backend ?? "default"]]); await measureStartup(params.startupTrace, "sidecars.acp.identity-reconcile", async () => { const [{ getAcpSessionManager }, { ACP_SESSION_IDENTITY_RENDERER_VERSION }] = await Promise.all([import("./acp/control-plane/manager.js"), import("./acp-core/runtime/session-identifiers.js")]); const result = await getAcpSessionManager().reconcilePendingSessionIdentities({ cfg: params.cfg }); if (result.checked === 0) return; params.log.warn(`acp startup identity reconcile (renderer=${ACP_SESSION_IDENTITY_RENDERER_VERSION}): checked=${result.checked} resolved=${result.resolved} failed=${result.failed}`); }); })().catch((err) => { params.log.warn(`acp startup identity reconcile failed: ${String(err)}`); }); await measureStartup(params.startupTrace, "sidecars.memory", async () => { const policy = resolveGatewayMemoryStartupPolicy(params.cfg); if (policy.mode === "off") return; scheduleGatewayMemoryBackend({ cfg: params.cfg, log: params.log, policy }); }); schedulePostReadySidecarTask({ startupTrace: params.startupTrace, name: "sidecars.session-locks", log: params.log, run: async (isStopped) => { try { const [{ resolveAgentSessionDirs }, { cleanStaleLockFiles }] = await Promise.all([import("./session-dirs-CB5lg0u-.js"), import("./session-write-lock-C8RVfRgG.js")]); await cleanupStaleSessionLocks({ sessionDirs: await resolveAgentSessionDirs(resolveStateDir(process.env)), cfg: params.cfg, log: params.log, isStopped, cleanStaleLockFiles }); } catch (err) { params.log.warn(`session lock cleanup failed on startup: ${String(err)}`); } } }); schedulePostReadySidecarTask({ startupTrace: params.startupTrace, name: "sidecars.restart-sentinel", log: params.log, run: async () => { if (!shouldCheckRestartSentinel()) return; if (!await hasRestartSentinelFileFast()) return; setTimeout(() => { loadGatewayRestartSentinelModule().then(({ scheduleRestartSentinelWake }) => scheduleRestartSentinelWake({ deps: params.deps })).catch((err) => { params.log.warn(`restart sentinel wake failed to schedule: ${String(err)}`); }); }, 750); } }); schedulePostReadySidecarTask({ startupTrace: params.startupTrace, name: "sidecars.subagent-recovery", log: params.log, run: async () => { const { scheduleSubagentOrphanRecovery } = await import("./subagent-registry-C9AeC_7M.js"); scheduleSubagentOrphanRecovery(); } }); if (params.cfg.hooks?.enabled && params.cfg.hooks.gmail?.account) postReadySidecars.push(schedulePostReadySidecarTask({ startupTrace: params.startupTrace, name: "sidecars.gmail-watch", log: params.log, run: async (isStopped, signal) => { const { startGmailWatcherWithLogs } = await import("./gmail-watcher-lifecycle-Dkg6iU7k.js"); if (isStopped()) return; await startGmailWatcherWithLogs({ cfg: params.cfg, log: params.logHooks, isCancelled: isStopped, signal }); } })); if (params.cfg.hooks?.gmail?.model) postReadySidecars.push(schedulePostReadySidecarTask({ startupTrace: params.startupTrace, name: "sidecars.gmail-model", log: params.log, run: async (isStopped) => { const [{ DEFAULT_MODEL, DEFAULT_PROVIDER }, { loadModelCatalog }, { getModelRefStatus, resolveConfiguredModelRef, resolveHooksGmailModel }] = await Promise.all([ loadAgentDefaultsModule(), import("./model-catalog-BflZHqiC.js"), loadAgentModelSelectionModule() ]); if (isStopped()) return; const hooksModelRef = resolveHooksGmailModel({ cfg: params.cfg, defaultProvider: DEFAULT_PROVIDER }); if (hooksModelRef) { const { provider: resolvedDefaultProvider, model: defaultModel } = resolveConfiguredModelRef({ cfg: params.cfg, defaultProvider: DEFAULT_PROVIDER, defaultModel: DEFAULT_MODEL }); const catalog = await loadModelCatalog({ config: params.cfg }); const status = getModelRefStatus({ cfg: params.cfg, catalog, ref: hooksModelRef, defaultProvider: resolvedDefaultProvider, defaultModel }); if (!status.allowed) params.logHooks.warn(`hooks.gmail.model "${status.key}" not in agents.defaults.models allowlist (will use primary instead)`); if (!status.inCatalog) params.logHooks.warn(`hooks.gmail.model "${status.key}" not in the model catalog (may fail at runtime)`); } } })); return { pluginServices, postReadySidecars }; } const defaultGatewayPostAttachRuntimeDeps = { getGlobalHookRunner: async () => (await import("./plugins/hook-runner-global.js")).getGlobalHookRunner(), logGatewayStartup: async (params) => (await import("./server-startup-log-D35S4WB-.js")).logGatewayStartup(params), refreshLatestUpdateRestartSentinel: refreshLatestUpdateRestartSentinelIfPresent, scheduleGatewayUpdateCheck: async (...args) => (await import("./update-startup-DY-Noz8L.js")).scheduleGatewayUpdateCheck(...args), startGatewaySidecars, startGatewayTailscaleExposure: async (...args) => (await import("./server-tailscale-Dk04PLrk.js")).startGatewayTailscaleExposure(...args) }; function createDeferredGatewayUpdateCheck(params) { let started = false; let stopped = false; let stopUpdateCheck = null; const stop = () => { stopped = true; stopUpdateCheck?.(); stopUpdateCheck = null; }; const start = () => { if (started || stopped) return; started = true; setImmediate(() => { if (stopped) return; measureStartup(params.startupTrace, "post-attach.update-check", () => params.runtimeDeps.scheduleGatewayUpdateCheck({ cfg: params.cfg, log: params.log, isNixMode: params.isNixMode, onUpdateAvailableChange: (updateAvailable) => { const payload = { updateAvailable }; params.broadcast(GATEWAY_EVENT_UPDATE_AVAILABLE, payload, { dropIfSlow: true }); } })).then((nextStop) => { if (stopped) { nextStop(); return; } stopUpdateCheck = nextStop; }).catch((err) => { if (stopped) return; params.log.warn(`gateway update check failed to start: ${String(err)}`); }); }); }; return { start, stop }; } /** Start work that depends on the HTTP server being attached and visible. */ async function startGatewayPostAttachRuntime(params, runtimeDeps = defaultGatewayPostAttachRuntimeDeps) { let pluginRegistry = params.pluginRegistry; let startupPluginsLoaded = false; let startupPluginsLoadPromise = null; const loadStartupPluginsIfNeeded = async () => { if (params.minimalTestGateway || !params.loadStartupPlugins) return { pluginRegistry, gatewayMethods: [] }; if (startupPluginsLoaded) return { pluginRegistry, gatewayMethods: [] }; startupPluginsLoadPromise ??= (async () => { params.onStartupPluginsLoading?.(); const loaded = await measureStartup(params.startupTrace, "plugins.runtime-post-bind", () => params.loadStartupPlugins()); pluginRegistry = loaded.pluginRegistry; startupPluginsLoaded = true; params.startupTrace?.detail("plugins.runtime-post-bind", [["loadedPluginCount", pluginRegistry.plugins.filter((plugin) => plugin.status === "loaded").length], ["gatewayMethodCount", loaded.gatewayMethods.length]]); await params.onStartupPluginsLoaded?.(loaded); return loaded; })(); return await startupPluginsLoadPromise; }; await loadStartupPluginsIfNeeded(); const startupLogPromise = measureStartup(params.startupTrace, "post-attach.log", () => runtimeDeps.logGatewayStartup({ cfg: params.cfgAtStart, bindHost: params.bindHost, bindHosts: params.bindHosts, port: params.port, tlsEnabled: params.tlsEnabled, loadedPluginIds: pluginRegistry.plugins.filter((plugin) => plugin.status === "loaded").map((plugin) => plugin.id), log: params.log, isNixMode: params.isNixMode, startupStartedAt: params.startupStartedAt })); const updateCheck = params.minimalTestGateway ? { start: () => {}, stop: () => {} } : createDeferredGatewayUpdateCheck({ startupTrace: params.startupTrace, runtimeDeps, cfg: params.cfgAtStart, log: params.log, isNixMode: params.isNixMode, broadcast: params.broadcast }); const tailscaleCleanupPromise = params.minimalTestGateway ? Promise.resolve(null) : params.tailscaleMode === "off" && !params.resetOnExit ? Promise.resolve(null) : measureStartup(params.startupTrace, "post-attach.tailscale", () => runtimeDeps.startGatewayTailscaleExposure({ tailscaleMode: params.tailscaleMode, resetOnExit: params.resetOnExit, serviceName: params.serviceName, preserveFunnel: params.preserveFunnel, port: params.port, controlUiBasePath: params.controlUiBasePath, logTailscale: params.logTailscale })); let pluginServicesReported = false; let reportedPluginServices = null; const reportPluginServices = (pluginServices) => { pluginServicesReported = true; reportedPluginServices = pluginServices; params.onPluginServices?.(pluginServices); }; const waitForSidecarStartTurn = () => new Promise((resolve) => { if (params.deferSidecars === true) { setTimeout(resolve, DEFERRED_SIDECAR_START_DELAY_MS).unref?.(); return; } setImmediate(resolve); }); const sidecarsPromise = params.minimalTestGateway ? Promise.resolve({ pluginServices: null, pluginRegistry, postReadySidecars: [] }) : waitForSidecarStartTurn().then(async () => { await loadStartupPluginsIfNeeded(); params.log.info("starting channels and sidecars..."); const loaderStatsBefore = getPluginModuleLoaderStats(); const result = await measureStartup(params.startupTrace, "sidecars.total", () => runtimeDeps.startGatewaySidecars({ cfg: params.gatewayPluginConfigAtStart, pluginRegistry, defaultWorkspaceDir: params.defaultWorkspaceDir, deps: params.deps, startChannels: params.startChannels, log: params.log, logHooks: params.logHooks, logChannels: params.logChannels, startupTrace: params.startupTrace, onChannelsStarted: params.onChannelsStarted, onPluginServices: reportPluginServices, shouldStartPluginServices: () => params.isClosing?.() !== true })); const loaderStatsAfter = getPluginModuleLoaderStats(); params.startupTrace?.detail("sidecars.plugin-loader", [ ["callsCount", loaderStatsAfter.calls - loaderStatsBefore.calls], ["nativeHitsCount", loaderStatsAfter.nativeHits - loaderStatsBefore.nativeHits], ["nativeMissesCount", loaderStatsAfter.nativeMisses - loaderStatsBefore.nativeMisses], ["sourceTransformForcedCount", loaderStatsAfter.sourceTransformForced - loaderStatsBefore.sourceTransformForced], ["sourceTransformFallbacksCount", loaderStatsAfter.sourceTransformFallbacks - loaderStatsBefore.sourceTransformFallbacks] ]); for (const method of STARTUP_UNAVAILABLE_GATEWAY_METHODS) params.unavailableGatewayMethods.delete(method); try { const { scheduleRestartAbortedMainSessionRecovery } = await loadMainSessionRestartRecoveryModule(); scheduleRestartAbortedMainSessionRecovery({ cfg: params.cfgAtStart }); } catch (err) { params.log.warn(`main-session restart recovery failed to schedule: ${String(err)}`); } if (!pluginServicesReported) reportPluginServices(result.pluginServices); const postReadySidecars = [...result.postReadySidecars]; const gatewayLifetimeSidecars = []; if (params.agentRuntimePluginPrewarm?.enabled !== false) gatewayLifetimeSidecars.push(scheduleAgentRuntimePluginPrewarm({ getConfig: params.agentRuntimePluginPrewarm?.getConfig ?? params.providerAuthPrewarm?.getConfig ?? (() => params.gatewayPluginConfigAtStart), workspaceDir: params.defaultWorkspaceDir, startupTrace: params.startupTrace, log: params.log, delayMs: params.agentRuntimePluginPrewarm?.delayMs })); if (params.providerAuthPrewarm?.enabled !== false) gatewayLifetimeSidecars.push(scheduleProviderAuthStatePrewarm({ getConfig: params.providerAuthPrewarm?.getConfig ?? (() => params.cfgAtStart), log: params.log, delayMs: params.providerAuthPrewarm?.delayMs })); if (params.gatewayPluginConfigAtStart.transcripts?.autoStart?.length) gatewayLifetimeSidecars.push(scheduleTranscriptsAutoStartSidecar({ cfg: params.gatewayPluginConfigAtStart, startupTrace: params.startupTrace, log: params.log })); params.onPostReadySidecars?.(postReadySidecars); params.onGatewayLifetimeSidecars?.(gatewayLifetimeSidecars); params.onSidecarsReady?.(); params.startupTrace?.detail("sidecars.ready", [["loadedPluginCount", pluginRegistry.plugins.filter((plugin) => plugin.status === "loaded").length], ["postReadySidecarCount", postReadySidecars.length + gatewayLifetimeSidecars.length]]); params.startupTrace?.mark("sidecars.ready"); if (params.logReadyOnSidecars !== false) params.log.info("gateway ready"); return { ...result, postReadySidecars, gatewayLifetimeSidecars, pluginRegistry }; }); sidecarsPromise.then(async (sidecarsResult) => { if (params.minimalTestGateway) return; schedulePostAttachUpdateSentinelRefresh({ startupTrace: params.startupTrace, log: params.log, refreshLatestUpdateRestartSentinel: runtimeDeps.refreshLatestUpdateRestartSentinel }); if (!hasGatewayStartHooks(sidecarsResult.pluginRegistry)) return; await new Promise((resolve) => { setImmediate(resolve); }); const hookRunner = await runtimeDeps.getGlobalHookRunner(); if (hookRunner?.hasHooks("gateway_start")) { const { withPluginHttpRouteRegistry } = await import("./http-registry-lvOBuX8b.js"); withPluginHttpRouteRegistry(sidecarsResult.pluginRegistry, () => hookRunner.runGatewayStart({ port: params.port }, { port: params.port, config: params.gatewayPluginConfigAtStart, workspaceDir: params.defaultWorkspaceDir, getCron: () => params.getCronService?.() ?? params.deps.cron })).catch((err) => { params.log.warn(`gateway_start hook failed: ${String(err)}`); }); } }).catch((err) => { params.log.warn(`gateway sidecars failed to start: ${String(err)}`); }); if (params.deferSidecars !== true) { const [, tailscaleCleanup, sidecarsResult] = await Promise.all([ startupLogPromise, tailscaleCleanupPromise, sidecarsPromise ]); updateCheck.start(); return { stopGatewayUpdateCheck: updateCheck.stop, tailscaleCleanup, pluginServices: sidecarsResult.pluginServices }; } const [, tailscaleCleanup] = await Promise.all([startupLogPromise, tailscaleCleanupPromise]); updateCheck.start(); return { stopGatewayUpdateCheck: updateCheck.stop, tailscaleCleanup, pluginServices: reportedPluginServices }; } const testing = { providerAuthPrewarmStartDelayMs: PROVIDER_AUTH_PREWARM_START_DELAY_MS, hasRestartSentinelFileFast, prewarmConfiguredPrimaryModel, prewarmConfiguredPrimaryModelWithTimeout, refreshLatestUpdateRestartSentinelIfPresent, resolveGatewayMemoryStartupPolicy, cleanupStaleSessionLocks, scheduleProviderAuthStatePrewarm, schedulePrimaryModelPrewarm, shouldSkipStartupModelPrewarm, stopPostReadySidecarsAfterCloseStarted }; //#endregion export { testing as __testing, testing, startGatewayPostAttachRuntime, startGatewaySidecars, stopPostReadySidecarsAfterCloseStarted };