refactor(gateway): finalize kernel composition (#122014)

This commit is contained in:
Peter Steinberger
2026-08-11 04:54:54 -07:00
committed by GitHub
parent 3cfb344f9f
commit f12fe48075
8 changed files with 329 additions and 221 deletions
+3 -16
View File
@@ -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<ErrorShape>) {
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;
@@ -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<ErrorShape>) {
super({
...error,
message: formatConnectErrorMessage({ message: error.message, details: error.details }),
});
this.name = "GatewayClientRequestError";
}
}
+93 -1
View File
@@ -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<string, string[]> {
const entryPath = path.join(repoRoot, entryRelativePath);
const graph = new Map<string, string[]>();
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");
+1 -1
View File
@@ -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";
+1 -1
View File
@@ -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 () => {
+170
View File
@@ -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 <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<void> {
const { closeMcpLoopbackServer } = await import("./mcp-http.js");
await closeMcpLoopbackServer();
}
async function stopTaskRegistryMaintenanceOnDemand(): Promise<void> {
const { stopTaskRegistryMaintenance } = await import("../tasks/task-registry.maintenance.js");
stopTaskRegistryMaintenance();
}
export async function resetPreparedModelCatalogForTestCore(): Promise<void> {
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<ReturnType<typeof prepareGatewayLifecycle>> | 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;
}
}
+37 -42
View File
@@ -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,
+11 -160
View File
@@ -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<void> {
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<void> {
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 <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<void> {
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<ReturnType<typeof prepareGatewayLifecycle>> | 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;
}
}