diff --git a/packages/gateway-client/src/client.ts b/packages/gateway-client/src/client.ts index dbf4fd6c11ef..9d02e9c311c7 100644 --- a/packages/gateway-client/src/client.ts +++ b/packages/gateway-client/src/client.ts @@ -7,15 +7,9 @@ import { } from "@openclaw/gateway-protocol/client-info"; import { ConnectErrorDetailCodes, - formatConnectErrorMessage, readConnectErrorDetailCode, } from "@openclaw/gateway-protocol/connect-error-details"; -import type { - ConnectParams, - ErrorShape, - EventFrame, - HelloOk, -} from "@openclaw/gateway-protocol/frame-guards"; +import type { ConnectParams, EventFrame, HelloOk } from "@openclaw/gateway-protocol/frame-guards"; import { resolveGatewayStartupRetryAfterMs } from "@openclaw/gateway-protocol/startup-unavailable"; import { MIN_CLIENT_PROTOCOL_VERSION, @@ -50,6 +44,7 @@ import { } from "./protocol-client.js"; import { GatewayProtocolRequestError } from "./protocol-request.js"; import { shouldPauseGatewayReconnect } from "./reconnect-policy.js"; +import { GatewayClientRequestError } from "./request-error.js"; import { DEFAULT_GATEWAY_REQUEST_TIMEOUT_MS, resolveConnectChallengeTimeoutMs, @@ -245,15 +240,7 @@ export type GatewayClientCloseInfo = { transientPreHelloCleanClose: boolean; }; -export class GatewayClientRequestError extends GatewayProtocolRequestError { - constructor(error: Partial) { - super({ - ...error, - message: formatConnectErrorMessage({ message: error.message, details: error.details }), - }); - this.name = "GatewayClientRequestError"; - } -} +export { GatewayClientRequestError } from "./request-error.js"; export class GatewayClientRequestTimeoutError extends Error { readonly method: string; diff --git a/packages/gateway-client/src/request-error.ts b/packages/gateway-client/src/request-error.ts new file mode 100644 index 000000000000..9bbb5e3816c3 --- /dev/null +++ b/packages/gateway-client/src/request-error.ts @@ -0,0 +1,13 @@ +import { formatConnectErrorMessage } from "@openclaw/gateway-protocol/connect-error-details"; +import type { ErrorShape } from "@openclaw/gateway-protocol/frame-guards"; +import { GatewayProtocolRequestError } from "./protocol-request.js"; + +export class GatewayClientRequestError extends GatewayProtocolRequestError { + constructor(error: Partial) { + super({ + ...error, + message: formatConnectErrorMessage({ message: error.message, details: error.details }), + }); + this.name = "GatewayClientRequestError"; + } +} diff --git a/src/gateway/server-import-boundary.test.ts b/src/gateway/server-import-boundary.test.ts index a148e3756e0c..5379d4c1fd0b 100644 --- a/src/gateway/server-import-boundary.test.ts +++ b/src/gateway/server-import-boundary.test.ts @@ -1,7 +1,8 @@ // Gateway import-boundary tests keep startup-critical modules lazy and prevent // heavyweight cron, doctor, secret, task, and WebSocket handlers from eager loads. -import { readFileSync } from "node:fs"; +import { existsSync, readFileSync } from "node:fs"; import path from "node:path"; +import ts from "typescript"; import { describe, expect, it } from "vitest"; const repoRoot = path.resolve(import.meta.dirname, "../.."); @@ -10,9 +11,83 @@ function readSource(relativePath: string): string { return readFileSync(path.join(repoRoot, relativePath), "utf8"); } +function resolveRelativeSource(importer: string, specifier: string): string | null { + const rawPath = path.resolve(path.dirname(importer), specifier); + const withoutJs = rawPath.replace(/\.(?:mjs|cjs|js)$/u, ""); + for (const candidate of [ + rawPath, + `${withoutJs}.ts`, + `${withoutJs}.mts`, + `${withoutJs}.cts`, + path.join(withoutJs, "index.ts"), + ]) { + if (existsSync(candidate)) { + return candidate; + } + } + return null; +} + +function staticValueSpecifiers(filePath: string, source: string): string[] { + const sourceFile = ts.createSourceFile(filePath, source, ts.ScriptTarget.Latest, true); + const specifiers: string[] = []; + for (const statement of sourceFile.statements) { + if (ts.isImportDeclaration(statement) && ts.isStringLiteral(statement.moduleSpecifier)) { + const clause = statement.importClause; + if (clause?.isTypeOnly) { + continue; + } + if ( + clause?.namedBindings && + ts.isNamedImports(clause.namedBindings) && + !clause.name && + clause.namedBindings.elements.every((element) => element.isTypeOnly) + ) { + continue; + } + specifiers.push(statement.moduleSpecifier.text); + continue; + } + if ( + ts.isExportDeclaration(statement) && + !statement.isTypeOnly && + statement.moduleSpecifier && + ts.isStringLiteral(statement.moduleSpecifier) + ) { + specifiers.push(statement.moduleSpecifier.text); + } + } + return specifiers; +} + +function collectStaticValueImportGraph(entryRelativePath: string): Map { + const entryPath = path.join(repoRoot, entryRelativePath); + const graph = new Map(); + const pending = [entryPath]; + while (pending.length > 0) { + const filePath = pending.pop(); + if (!filePath || graph.has(filePath)) { + continue; + } + const specifiers = staticValueSpecifiers(filePath, readFileSync(filePath, "utf8")); + graph.set(filePath, specifiers); + for (const specifier of specifiers) { + if (!specifier.startsWith(".")) { + continue; + } + const resolved = resolveRelativeSource(filePath, specifier); + if (resolved) { + pending.push(resolved); + } + } + } + return graph; +} + function readServerImplementation(): string { return [ "src/gateway/server-start.ts", + "src/gateway/server-kernel.ts", "src/gateway/server-startup-bootstrap.ts", "src/gateway/server-runtime-state-prepare.ts", "src/gateway/server-lifecycle.ts", @@ -24,6 +99,23 @@ function readServerImplementation(): string { } describe("gateway startup import boundaries", () => { + it("keeps the kernel static import graph free of HTTP server and WebSocket construction", () => { + const graph = collectStaticValueImportGraph("src/gateway/server-kernel.ts"); + const violations: string[] = []; + for (const [filePath, specifiers] of graph) { + for (const specifier of specifiers) { + if (specifier === "node:http" || specifier === "node:https" || specifier === "ws") { + violations.push(`${path.relative(repoRoot, filePath)} -> ${specifier}`); + } + } + } + + expect([...graph.keys()].map((filePath) => path.relative(repoRoot, filePath))).not.toContain( + "src/gateway/server-runtime-state.ts", + ); + expect(violations).toEqual([]); + }); + it("keeps heavy cron and doctor legacy paths out of the server-start import graph", () => { const serverImpl = readServerImplementation(); const validation = readSource("src/config/validation.ts"); diff --git a/src/gateway/server-in-process-dispatch.ts b/src/gateway/server-in-process-dispatch.ts index 7f37af388845..9938cc552ef3 100644 --- a/src/gateway/server-in-process-dispatch.ts +++ b/src/gateway/server-in-process-dispatch.ts @@ -1,5 +1,5 @@ import { randomUUID } from "node:crypto"; -import { GatewayClientRequestError } from "../../packages/gateway-client/src/index.js"; +import { GatewayClientRequestError } from "../../packages/gateway-client/src/request-error.js"; import type { ErrorShape } from "../../packages/gateway-protocol/src/schema/frames.js"; import { createAbortError } from "../infra/abort-signal.js"; import { resolveSafeTimeoutDelayMs } from "../utils/timer-delay.js"; diff --git a/src/gateway/server-kernel.test.ts b/src/gateway/server-kernel.test.ts index f8b226624847..04f640c98266 100644 --- a/src/gateway/server-kernel.test.ts +++ b/src/gateway/server-kernel.test.ts @@ -6,8 +6,8 @@ import { createOpenClawTestState } from "../test-utils/openclaw-test-state.js"; import { getFreePort } from "../test-utils/ports.js"; import { CLI_DEFAULT_OPERATOR_SCOPES } from "./method-scopes.js"; import { dispatchGatewayRequestInProcess } from "./server-in-process-dispatch.js"; +import { createGatewayKernel } from "./server-kernel.js"; import { createSyntheticPluginRuntimeClient } from "./server-plugin-runtime-client.js"; -import { createGatewayKernel } from "./server-start.js"; describe("createGatewayKernel", () => { it("dispatches health and an agent turn without creating a transport", async () => { diff --git a/src/gateway/server-kernel.ts b/src/gateway/server-kernel.ts new file mode 100644 index 000000000000..ffff1d574e1e --- /dev/null +++ b/src/gateway/server-kernel.ts @@ -0,0 +1,170 @@ +import { isNixMode } from "../config/paths.js"; +import { ensureOpenClawCliOnPath } from "../infra/path-env.js"; +import { createSubsystemLogger, runtimeForLogger } from "../logging/subsystem.js"; +import { clearPluginMetadataLifecycleCaches } from "../plugins/plugin-metadata-lifecycle.js"; +import { clearSecretsRuntimeSnapshot } from "../secrets/runtime-state.js"; +import { createLazyRuntimeModule } from "../shared/lazy-runtime.js"; +import { startGatewayCoreRuntime } from "./server-core-runtime.js"; +import { prepareGatewayKernelRequestRuntime } from "./server-kernel-request-runtime.js"; +import { prepareGatewayLifecycle } from "./server-lifecycle.js"; +import type { GatewayServerOptions } from "./server-public.js"; +import { prepareGatewayKernelState } from "./server-runtime-state-prepare.js"; +import { prepareGatewayServerBootstrap } from "./server-startup-bootstrap.js"; + +type LoadGatewayModelCatalog = typeof import("./server-model-catalog.js").loadGatewayModelCatalog; +type LoadGatewayModelCatalogSnapshot = + typeof import("./server-model-catalog.js").loadGatewayModelCatalogSnapshot; +type ReadPreparedGatewayModelCatalog = + typeof import("./server-model-catalog.js").readPreparedGatewayModelCatalog; + +const loadGatewayModelCatalogModule = createLazyRuntimeModule( + () => import("./server-model-catalog.js"), +); +const loadWorkerEnvironmentStartupModule = createLazyRuntimeModule( + () => import("./server-worker-environment-startup.js"), +); +const loadWorkerPlacementStartupModule = createLazyRuntimeModule( + () => import("./server-worker-placement-startup.js"), +); +const loadGatewayStartupEarlyModule = createLazyRuntimeModule( + () => import("./server-startup-early.js"), +); +const loadGatewayPluginBootstrapModule = createLazyRuntimeModule( + () => import("./server-plugin-bootstrap.js"), +); +const loadGatewayCloseModule = createLazyRuntimeModule(() => import("./server-close.runtime.js")); + +const log = createSubsystemLogger("gateway"); +const logDiscovery = log.child("discovery"); +const logTailscale = log.child("tailscale"); +const logChannels = log.child("channels"); +const logHealth = log.child("health"); +const logCron = log.child("cron"); +const logReload = log.child("reload"); +const logHooks = log.child("hooks"); +const logPlugins = log.child("plugins"); +const logWsControl = log.child("ws"); +const logSecrets = log.child("secrets"); + +export const gatewayKernelLogs = { + log, + logTailscale, + logChannels, + logHealth, + logCron, + logReload, + logHooks, + logWsControl, +}; + +const gatewayRuntime = runtimeForLogger(log); +const getChannelRuntime = createLazyRuntimeModule(() => + import("../plugins/runtime/runtime-channel.js").then(({ createRuntimeChannel }) => + createRuntimeChannel(), + ), +); + +const loadGatewayModelCatalog: LoadGatewayModelCatalog = async (...args) => { + const mod = await loadGatewayModelCatalogModule(); + return mod.loadGatewayModelCatalog(...args); +}; +const loadGatewayModelCatalogSnapshot: LoadGatewayModelCatalogSnapshot = async (...args) => { + const mod = await loadGatewayModelCatalogModule(); + return mod.loadGatewayModelCatalogSnapshot(...args); +}; +const readPreparedGatewayModelCatalog: ReadPreparedGatewayModelCatalog = async (...args) => { + const mod = await loadGatewayModelCatalogModule(); + return mod.readPreparedGatewayModelCatalog(...args); +}; + +function formatRuntimeGatewayAuthTokenWarning(): string { + const base = + "Gateway auth token was missing. Generated a runtime token for this startup without changing config; restart will generate a different token."; + if (!isNixMode) { + return `${base} Persist one with \`openclaw config set gateway.auth.mode token\` and \`openclaw config set gateway.auth.token \`.`; + } + return [ + base, + "In Nix mode, set gateway.auth.token in your Nix-managed OpenClaw config and rebuild.", + "For the first-party Nix flow, see https://github.com/openclaw/nix-openclaw#quick-start and https://docs.openclaw.ai/install/nix.", + ].join(" "); +} + +async function closeMcpLoopbackServerOnDemand(): Promise { + const { closeMcpLoopbackServer } = await import("./mcp-http.js"); + await closeMcpLoopbackServer(); +} + +async function stopTaskRegistryMaintenanceOnDemand(): Promise { + const { stopTaskRegistryMaintenance } = await import("../tasks/task-registry.maintenance.js"); + stopTaskRegistryMaintenance(); +} + +export async function resetPreparedModelCatalogForTestCore(): Promise { + const { resetPreparedModelCatalogStateForTest } = await loadGatewayModelCatalogModule(); + await resetPreparedModelCatalogStateForTest(); +} + +/** Builds the Gateway kernel and internal dispatch surface without creating HTTP servers. */ +export async function createGatewayKernel(port = 18789, opts: GatewayServerOptions = {}) { + ensureOpenClawCliOnPath(); + let lifecycleRuntime: Awaited> | undefined; + try { + const bootstrap = await prepareGatewayServerBootstrap({ + port, + opts, + log, + logSecrets, + loadWorkerEnvironmentStartupModule, + formatRuntimeGatewayAuthTokenWarning, + }); + const runtime = await prepareGatewayKernelState({ + bootstrap, + port, + opts, + log, + logChannels, + logHooks, + logPlugins, + gatewayRuntime, + resolveChannelRuntime: getChannelRuntime, + loadWorkerEnvironmentStartupModule, + loadWorkerPlacementStartupModule, + }); + lifecycleRuntime = await prepareGatewayLifecycle({ + runtime, + port, + log, + logCron, + diagnosticsEnabled: bootstrap.diagnosticsEnabled, + loadGatewayCloseModule, + closeMcpLoopbackServerOnDemand, + stopTaskRegistryMaintenanceOnDemand, + }); + if (bootstrap.cfgAtStart.gateway?.tls?.enabled && !runtime.gatewayTls.enabled) { + throw new Error(runtime.gatewayTls.error ?? "gateway tls: failed to enable"); + } + const coreRuntime = await startGatewayCoreRuntime({ + lifecycleRuntime, + port, + log, + logDiscovery, + logHealth, + logChannels, + loadGatewayStartupEarlyModule, + loadGatewayPluginBootstrapModule, + loadGatewayModelCatalog, + loadGatewayModelCatalogSnapshot, + readPreparedGatewayModelCatalog, + }); + return await prepareGatewayKernelRequestRuntime({ coreRuntime, log, logHealth }); + } catch (error) { + if (lifecycleRuntime) { + await lifecycleRuntime.closeOnStartupFailure(); + } else { + clearSecretsRuntimeSnapshot(); + clearPluginMetadataLifecycleCaches(); + } + throw error; + } +} diff --git a/src/gateway/server-runtime-state-prepare.ts b/src/gateway/server-runtime-state-prepare.ts index 44d422b64a42..bd76820c0143 100644 --- a/src/gateway/server-runtime-state-prepare.ts +++ b/src/gateway/server-runtime-state-prepare.ts @@ -22,7 +22,6 @@ 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 { createGatewayHttpTransport } from "./server-runtime-state.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"; @@ -378,46 +377,42 @@ export async function prepareGatewayKernelState(params: { }), ); const transportBridge = createGatewayTransportBridge(); - const createHttpTransport = async () => { - const transport = await createGatewayHttpTransport({ - cfg: cfgAtStart, - getRuntimeConfig, - bindHost, - port, - controlUiEnabled, - controlUiBasePath, - controlUiRoot: controlUiRootLifecycle.state, - openAiChatCompletionsEnabled, - openAiChatCompletionsConfig, - openResponsesEnabled, - openResponsesConfig, - strictTransportSecurityHeader, - resolvedAuth, - rateLimiter: authRateLimiter, - isTerminalEnabled: terminalLaunchPolicy.isEnabled, - gatewayTls, - getResolvedAuth, - hooksConfig: () => runtimeStateRef.current?.hooksConfig ?? initialHooksConfig, - getHookClientIpConfig: () => - runtimeStateRef.current?.hookClientIpConfig ?? initialHookClientIpConfig, - pluginRegistry: pluginRuntime.registry, - getPluginRouteRegistry: () => pluginRuntime.registry, - isStartupPluginRuntimeReady: () => startupState.sidecarsReady, - getGatewayRequestContext: () => pluginGatewayContext.current, - deps, - log, - logHooks, - logPlugins, - getReadiness, - handleWatchNodeRequest: async (req, res) => - (await watchNodeRequestHandler.current?.(req, res)) ?? false, - workerIngressEnabled: Boolean(workerEnvironmentService), - workerDesktopTunnels: workerTunnelManager?.desktop, - clients: connectionState.clients, - }); - transportBridge.attach(transport); - return transport; - }; + const createHttpTransportOptions = () => ({ + cfg: cfgAtStart, + getRuntimeConfig, + bindHost, + port, + controlUiEnabled, + controlUiBasePath, + controlUiRoot: controlUiRootLifecycle.state, + openAiChatCompletionsEnabled, + openAiChatCompletionsConfig, + openResponsesEnabled, + openResponsesConfig, + strictTransportSecurityHeader, + resolvedAuth, + rateLimiter: authRateLimiter, + isTerminalEnabled: terminalLaunchPolicy.isEnabled, + gatewayTls, + getResolvedAuth, + hooksConfig: () => runtimeStateRef.current?.hooksConfig ?? initialHooksConfig, + getHookClientIpConfig: () => + runtimeStateRef.current?.hookClientIpConfig ?? initialHookClientIpConfig, + pluginRegistry: pluginRuntime.registry, + getPluginRouteRegistry: () => pluginRuntime.registry, + isStartupPluginRuntimeReady: () => startupState.sidecarsReady, + getGatewayRequestContext: () => pluginGatewayContext.current, + deps, + log, + logHooks, + logPlugins, + getReadiness, + handleWatchNodeRequest: async (req: IncomingMessage, res: ServerResponse) => + (await watchNodeRequestHandler.current?.(req, res)) ?? false, + workerIngressEnabled: Boolean(workerEnvironmentService), + workerDesktopTunnels: workerTunnelManager?.desktop, + clients: connectionState.clients, + }); const { clients, broadcast, @@ -494,7 +489,7 @@ export async function prepareGatewayKernelState(params: { isGatewayStartupPending, pluginGatewayContext, watchNodeRequestHandler, - createHttpTransport, + createHttpTransportOptions, transportBridge, clients, broadcast, diff --git a/src/gateway/server-start.ts b/src/gateway/server-start.ts index f729d913748b..7950ab49f920 100644 --- a/src/gateway/server-start.ts +++ b/src/gateway/server-start.ts @@ -1,108 +1,22 @@ -import { isNixMode } from "../config/paths.js"; -import { ensureOpenClawCliOnPath } from "../infra/path-env.js"; -import { createSubsystemLogger, runtimeForLogger } from "../logging/subsystem.js"; -import { clearPluginMetadataLifecycleCaches } from "../plugins/plugin-metadata-lifecycle.js"; -import { clearSecretsRuntimeSnapshot } from "../secrets/runtime-state.js"; import { createLazyRuntimeModule } from "../shared/lazy-runtime.js"; -import { startGatewayCoreRuntime } from "./server-core-runtime.js"; -import { prepareGatewayKernelRequestRuntime } from "./server-kernel-request-runtime.js"; -import { prepareGatewayLifecycle } from "./server-lifecycle.js"; +import { + createGatewayKernel, + gatewayKernelLogs, + resetPreparedModelCatalogForTestCore, +} from "./server-kernel.js"; import type { GatewayServer, GatewayServerOptions } from "./server-public.js"; -import { prepareGatewayKernelState } from "./server-runtime-state-prepare.js"; -import { prepareGatewayServerBootstrap } from "./server-startup-bootstrap.js"; +import { createGatewayHttpTransport } from "./server-runtime-state.js"; import { finishGatewayStartup } from "./server-startup-finish.js"; -type LoadGatewayModelCatalog = typeof import("./server-model-catalog.js").loadGatewayModelCatalog; -type LoadGatewayModelCatalogSnapshot = - typeof import("./server-model-catalog.js").loadGatewayModelCatalogSnapshot; -type ReadPreparedGatewayModelCatalog = - typeof import("./server-model-catalog.js").readPreparedGatewayModelCatalog; - -const loadGatewayModelCatalogModule = createLazyRuntimeModule( - () => import("./server-model-catalog.js"), -); -const loadWorkerEnvironmentStartupModule = createLazyRuntimeModule( - () => import("./server-worker-environment-startup.js"), -); -const loadWorkerPlacementStartupModule = createLazyRuntimeModule( - () => import("./server-worker-placement-startup.js"), -); - -export async function resetPreparedModelCatalogForTestCore(): Promise { - const { resetPreparedModelCatalogStateForTest } = await loadGatewayModelCatalogModule(); - await resetPreparedModelCatalogStateForTest(); -} - -const loadGatewayStartupEarlyModule = createLazyRuntimeModule( - () => import("./server-startup-early.js"), -); const loadGatewayStartupPostAttachModule = createLazyRuntimeModule( () => import("./server-startup-post-attach.js"), ); -const log = createSubsystemLogger("gateway"); -const logDiscovery = log.child("discovery"); -const logTailscale = log.child("tailscale"); -const logChannels = log.child("channels"); - -const getChannelRuntime = createLazyRuntimeModule(() => - import("../plugins/runtime/runtime-channel.js").then(({ createRuntimeChannel }) => - createRuntimeChannel(), - ), -); - -async function closeMcpLoopbackServerOnDemand(): Promise { - const { closeMcpLoopbackServer } = await import("./mcp-http.js"); - await closeMcpLoopbackServer(); -} - -const loadGatewayCloseModule = createLazyRuntimeModule(() => import("./server-close.runtime.js")); - -const loadGatewayModelCatalog: LoadGatewayModelCatalog = async (...args) => { - const mod = await loadGatewayModelCatalogModule(); - return mod.loadGatewayModelCatalog(...args); -}; -const loadGatewayModelCatalogSnapshot: LoadGatewayModelCatalogSnapshot = async (...args) => { - const mod = await loadGatewayModelCatalogModule(); - return mod.loadGatewayModelCatalogSnapshot(...args); -}; -const readPreparedGatewayModelCatalog: ReadPreparedGatewayModelCatalog = async (...args) => { - const mod = await loadGatewayModelCatalogModule(); - return mod.readPreparedGatewayModelCatalog(...args); -}; - -const loadGatewayPluginBootstrapModule = createLazyRuntimeModule( - () => import("./server-plugin-bootstrap.js"), -); - -const logHealth = log.child("health"); -const logCron = log.child("cron"); -const logReload = log.child("reload"); -const logHooks = log.child("hooks"); - -const logPlugins = log.child("plugins"); -const logWsControl = log.child("ws"); -const logSecrets = log.child("secrets"); -const gatewayRuntime = runtimeForLogger(log); +const { log, logTailscale, logChannels, logHealth, logCron, logReload, logHooks, logWsControl } = + gatewayKernelLogs; const POST_READY_WORK_START_DELAY_MS = 500; -function formatRuntimeGatewayAuthTokenWarning(): string { - const base = - "Gateway auth token was missing. Generated a runtime token for this startup without changing config; restart will generate a different token."; - if (!isNixMode) { - return `${base} Persist one with \`openclaw config set gateway.auth.mode token\` and \`openclaw config set gateway.auth.token \`.`; - } - return [ - base, - "In Nix mode, set gateway.auth.token in your Nix-managed OpenClaw config and rebuild.", - "For the first-party Nix flow, see https://github.com/openclaw/nix-openclaw#quick-start and https://docs.openclaw.ai/install/nix.", - ].join(" "); -} - -async function stopTaskRegistryMaintenanceOnDemand(): Promise { - const { stopTaskRegistryMaintenance } = await import("../tasks/task-registry.maintenance.js"); - stopTaskRegistryMaintenance(); -} +export { resetPreparedModelCatalogForTestCore }; export async function startGatewayServerCore( port = 18789, @@ -124,7 +38,8 @@ export async function startGatewayServerCore( terminalSessions, } = gatewayKernel; try { - const transport = await gatewayKernel.createHttpTransport(); + const transport = await createGatewayHttpTransport(gatewayKernel.createHttpTransportOptions()); + gatewayKernel.transportBridge.attach(transport); await finishGatewayStartup({ kernelRuntime: { ...gatewayKernel, ...transport }, port, @@ -174,67 +89,3 @@ export async function startGatewayServerCore( }, }; } - -/** Builds the Gateway kernel and internal dispatch surface without creating HTTP servers. */ -export async function createGatewayKernel(port = 18789, opts: GatewayServerOptions = {}) { - ensureOpenClawCliOnPath(); - let lifecycleRuntime: Awaited> | undefined; - try { - const bootstrap = await prepareGatewayServerBootstrap({ - port, - opts, - log, - logSecrets, - loadWorkerEnvironmentStartupModule, - formatRuntimeGatewayAuthTokenWarning, - }); - const runtime = await prepareGatewayKernelState({ - bootstrap, - port, - opts, - log, - logChannels, - logHooks, - logPlugins, - gatewayRuntime, - resolveChannelRuntime: getChannelRuntime, - loadWorkerEnvironmentStartupModule, - loadWorkerPlacementStartupModule, - }); - lifecycleRuntime = await prepareGatewayLifecycle({ - runtime, - port, - log, - logCron, - diagnosticsEnabled: bootstrap.diagnosticsEnabled, - loadGatewayCloseModule, - closeMcpLoopbackServerOnDemand, - stopTaskRegistryMaintenanceOnDemand, - }); - if (bootstrap.cfgAtStart.gateway?.tls?.enabled && !runtime.gatewayTls.enabled) { - throw new Error(runtime.gatewayTls.error ?? "gateway tls: failed to enable"); - } - const coreRuntime = await startGatewayCoreRuntime({ - lifecycleRuntime, - port, - log, - logDiscovery, - logHealth, - logChannels, - loadGatewayStartupEarlyModule, - loadGatewayPluginBootstrapModule, - loadGatewayModelCatalog, - loadGatewayModelCatalogSnapshot, - readPreparedGatewayModelCatalog, - }); - return await prepareGatewayKernelRequestRuntime({ coreRuntime, log, logHealth }); - } catch (error) { - if (lifecycleRuntime) { - await lifecycleRuntime.closeOnStartupFailure(); - } else { - clearSecretsRuntimeSnapshot(); - clearPluginMetadataLifecycleCaches(); - } - throw error; - } -}