From 93a5784c584168bef3936a9ef7be1d7866a21c2a Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Wed, 10 Dec 2025 16:55:17 +0000 Subject: [PATCH] feat(gateway): allow webchat port override --- src/cli/program.ts | 13 ++++++++++++- src/commands/health.ts | 6 ++++++ src/gateway/server.ts | 9 ++++++--- src/webchat/server.ts | 6 ++++-- 4 files changed, 28 insertions(+), 6 deletions(-) diff --git a/src/cli/program.ts b/src/cli/program.ts index 3a625eb88..e26da3a71 100644 --- a/src/cli/program.ts +++ b/src/cli/program.ts @@ -214,6 +214,10 @@ Examples: .command("gateway") .description("Run the WebSocket Gateway") .option("--port ", "Port for the gateway WebSocket", "18789") + .option( + "--webchat-port ", + "Port for the loopback WebChat HTTP server (default 18788)", + ) .option( "--token ", "Shared token required in hello.auth.token (default: CLAWDIS_GATEWAY_TOKEN env if set)", @@ -231,6 +235,13 @@ Examples: defaultRuntime.error("Invalid port"); defaultRuntime.exit(1); } + const webchatPort = opts.webchatPort + ? Number.parseInt(String(opts.webchatPort), 10) + : undefined; + if (webchatPort !== undefined && (Number.isNaN(webchatPort) || webchatPort <= 0)) { + defaultRuntime.error("Invalid webchat port"); + defaultRuntime.exit(1); + } if (opts.force) { try { const killed = forceFreePort(port); @@ -256,7 +267,7 @@ Examples: process.env.CLAWDIS_GATEWAY_TOKEN = String(opts.token); } try { - await startGatewayServer(port); + await startGatewayServer(port, { webchatPort }); } catch (err) { if (err instanceof GatewayLockError) { defaultRuntime.error(`Gateway failed to start: ${err.message}`); diff --git a/src/commands/health.ts b/src/commands/health.ts index 3ec351a9f..526481375 100644 --- a/src/commands/health.ts +++ b/src/commands/health.ts @@ -22,6 +22,12 @@ export type HealthSummary = { web: { linked: boolean; authAgeMs: number | null; + connect?: { + ok: boolean; + status?: number | null; + error?: string | null; + elapsedMs?: number | null; + }; }; telegram: { configured: boolean; diff --git a/src/gateway/server.ts b/src/gateway/server.ts index 84e2aa29c..dc2a18819 100644 --- a/src/gateway/server.ts +++ b/src/gateway/server.ts @@ -236,7 +236,7 @@ function formatError(err: unknown): string { async function refreshHealthSnapshot(opts?: { probe?: boolean }) { if (!healthRefresh) { healthRefresh = (async () => { - const snap = await getHealthSnapshot(undefined, opts); + const snap = await getHealthSnapshot(undefined); healthCache = snap; healthVersion += 1; if (broadcastHealthUpdate) { @@ -251,7 +251,10 @@ async function refreshHealthSnapshot(opts?: { probe?: boolean }) { return healthRefresh; } -export async function startGatewayServer(port = 18789): Promise { +export async function startGatewayServer( + port = 18789, + opts?: { webchatPort?: number }, +): Promise { const releaseLock = await acquireGatewayLock().catch((err) => { // Bubble known lock errors so callers can present a nice message. if (err instanceof GatewayLockError) throw err; @@ -1146,7 +1149,7 @@ export async function startGatewayServer(port = 18789): Promise { defaultRuntime.log(`gateway log file: ${getResolvedLoggerSettings().file}`); // Start loopback WebChat server (unless disabled via config). - void ensureWebChatServerFromConfig() + void ensureWebChatServerFromConfig(opts?.webchatPort) .then((webchat) => { if (webchat) { defaultRuntime.log( diff --git a/src/webchat/server.ts b/src/webchat/server.ts index 47f744f43..cf460a317 100644 --- a/src/webchat/server.ts +++ b/src/webchat/server.ts @@ -160,10 +160,12 @@ export async function __broadcastGatewayEventForTests() { // no-op } -export async function ensureWebChatServerFromConfig() { +export async function ensureWebChatServerFromConfig( + overridePort?: number, +) { const cfg = loadConfig(); if (cfg.webchat?.enabled === false) return null; - const port = cfg.webchat?.port ?? WEBCHAT_DEFAULT_PORT; + const port = overridePort ?? cfg.webchat?.port ?? WEBCHAT_DEFAULT_PORT; try { return await startWebChatServer(port); } catch (err) {