mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-12 21:53:00 -06:00
refactor(gateway): finalize kernel composition (#122014)
This commit is contained in:
committed by
GitHub
parent
3cfb344f9f
commit
f12fe48075
@@ -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";
|
||||
}
|
||||
}
|
||||
@@ -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,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";
|
||||
|
||||
@@ -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 () => {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user