From 0ffd84cf7d7941c8b4e33949d14cb09665e085ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=98=9F=E6=9C=88?= Date: Wed, 13 May 2026 22:50:49 +0800 Subject: [PATCH] refactor(serve): WS client calls app.fetch directly, no HTTP server in gateway mode - WS client receives app.fetch function instead of localPort - Gateway mode no longer starts a local HTTP server - Local-only mode (no secret) still starts HTTP server as before - Removes unnecessary HTTP round-trip for gateway requests --- .../cli-workflow/src/commands/serve/serve.ts | 18 ++++++++---------- .../src/commands/serve/ws-client.ts | 17 +++++++---------- 2 files changed, 15 insertions(+), 20 deletions(-) diff --git a/packages/cli-workflow/src/commands/serve/serve.ts b/packages/cli-workflow/src/commands/serve/serve.ts index 3117f62..e7d9bb4 100644 --- a/packages/cli-workflow/src/commands/serve/serve.ts +++ b/packages/cli-workflow/src/commands/serve/serve.ts @@ -16,9 +16,8 @@ const HEARTBEAT_INTERVAL_MS = 60_000; export function startServer( storageRoot: string, options: ServeOptions, - agentToken: string | null, ): void { - const app = createApp(storageRoot, agentToken); + const app = createApp(storageRoot, null); const server = serve({ fetch: app.fetch, @@ -95,33 +94,32 @@ export async function dispatchServe(storageRoot: string, argv: string[]): Promis if (options.gatewaySecret === "") { // No gateway — local-only mode - startServer(storageRoot, options, null); + startServer(storageRoot, options); printCliLine("no WORKFLOW_GATEWAY_SECRET — running in local-only mode"); await new Promise(() => {}); return 0; } + // Gateway mode — no HTTP server, WS client calls app.fetch directly const agentToken = randomUUID(); - startServer(storageRoot, options, agentToken); + const app = createApp(storageRoot, agentToken); - // Start WebSocket reverse connection to gateway const log = createLogger({ sink: { kind: "stderr" } }); const stopWsClient = startGatewayWsClient({ gatewayUrl: options.gatewayUrl, name: options.name, secret: options.gatewaySecret, - localPort: options.port, + appFetch: app.fetch, log, }); - printCliLine("connected to gateway via WebSocket"); + printCliLine("connected to gateway via WebSocket (no local HTTP server)"); // Register with gateway for discovery - const localUrl = `http://127.0.0.1:${options.port}`; const registered = await registerWithGateway( options.gatewayUrl, options.name, - localUrl, + `ws://${options.name}`, options.gatewaySecret, agentToken, ); @@ -132,7 +130,7 @@ export async function dispatchServe(storageRoot: string, argv: string[]): Promis const heartbeatTimer = startHeartbeat( options.gatewayUrl, options.name, - localUrl, + `ws://${options.name}`, options.gatewaySecret, agentToken, HEARTBEAT_INTERVAL_MS, diff --git a/packages/cli-workflow/src/commands/serve/ws-client.ts b/packages/cli-workflow/src/commands/serve/ws-client.ts index b53be3d..aeaa5c1 100644 --- a/packages/cli-workflow/src/commands/serve/ws-client.ts +++ b/packages/cli-workflow/src/commands/serve/ws-client.ts @@ -5,7 +5,7 @@ export type GatewayWsClientParams = { gatewayUrl: string; name: string; secret: string; - localPort: number; + appFetch: (request: Request) => Response | Promise; log: LogFn; }; @@ -44,20 +44,17 @@ async function handleGatewayMessage( params.log("ZM8K2PQ1", "gateway WebSocket dropped non-request message"); return; } - const localUrl = `http://127.0.0.1:${String(params.localPort)}${req.path}`; - const initHeaders = new Headers(); - for (const [k, v] of Object.entries(req.headers)) { - initHeaders.set(k, v); - } + const localUrl = `http://localhost${req.path}`; + const headers = new Headers(req.headers); let resp: Response; try { - resp = await fetch(localUrl, { + resp = await params.appFetch(new Request(localUrl, { method: req.method, - headers: initHeaders, + headers, body: req.body === null ? undefined : req.body, - }); + })); } catch (e) { - params.log("R4N7BQ3C", `local proxy fetch failed: ${String(e)}`); + params.log("R4N7BQ3C", `app.fetch failed: ${String(e)}`); const errBody: WsResponse = { id: req.id, status: 502,