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
This commit is contained in:
2026-05-13 22:50:49 +08:00
parent e14643a50b
commit 0ffd84cf7d
2 changed files with 15 additions and 20 deletions
@@ -16,9 +16,8 @@ const HEARTBEAT_INTERVAL_MS = 60_000;
export function startServer( export function startServer(
storageRoot: string, storageRoot: string,
options: ServeOptions, options: ServeOptions,
agentToken: string | null,
): void { ): void {
const app = createApp(storageRoot, agentToken); const app = createApp(storageRoot, null);
const server = serve({ const server = serve({
fetch: app.fetch, fetch: app.fetch,
@@ -95,33 +94,32 @@ export async function dispatchServe(storageRoot: string, argv: string[]): Promis
if (options.gatewaySecret === "") { if (options.gatewaySecret === "") {
// No gateway — local-only mode // No gateway — local-only mode
startServer(storageRoot, options, null); startServer(storageRoot, options);
printCliLine("no WORKFLOW_GATEWAY_SECRET — running in local-only mode"); printCliLine("no WORKFLOW_GATEWAY_SECRET — running in local-only mode");
await new Promise(() => {}); await new Promise(() => {});
return 0; return 0;
} }
// Gateway mode — no HTTP server, WS client calls app.fetch directly
const agentToken = randomUUID(); 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 log = createLogger({ sink: { kind: "stderr" } });
const stopWsClient = startGatewayWsClient({ const stopWsClient = startGatewayWsClient({
gatewayUrl: options.gatewayUrl, gatewayUrl: options.gatewayUrl,
name: options.name, name: options.name,
secret: options.gatewaySecret, secret: options.gatewaySecret,
localPort: options.port, appFetch: app.fetch,
log, log,
}); });
printCliLine("connected to gateway via WebSocket"); printCliLine("connected to gateway via WebSocket (no local HTTP server)");
// Register with gateway for discovery // Register with gateway for discovery
const localUrl = `http://127.0.0.1:${options.port}`;
const registered = await registerWithGateway( const registered = await registerWithGateway(
options.gatewayUrl, options.gatewayUrl,
options.name, options.name,
localUrl, `ws://${options.name}`,
options.gatewaySecret, options.gatewaySecret,
agentToken, agentToken,
); );
@@ -132,7 +130,7 @@ export async function dispatchServe(storageRoot: string, argv: string[]): Promis
const heartbeatTimer = startHeartbeat( const heartbeatTimer = startHeartbeat(
options.gatewayUrl, options.gatewayUrl,
options.name, options.name,
localUrl, `ws://${options.name}`,
options.gatewaySecret, options.gatewaySecret,
agentToken, agentToken,
HEARTBEAT_INTERVAL_MS, HEARTBEAT_INTERVAL_MS,
@@ -5,7 +5,7 @@ export type GatewayWsClientParams = {
gatewayUrl: string; gatewayUrl: string;
name: string; name: string;
secret: string; secret: string;
localPort: number; appFetch: (request: Request) => Response | Promise<Response>;
log: LogFn; log: LogFn;
}; };
@@ -44,20 +44,17 @@ async function handleGatewayMessage(
params.log("ZM8K2PQ1", "gateway WebSocket dropped non-request message"); params.log("ZM8K2PQ1", "gateway WebSocket dropped non-request message");
return; return;
} }
const localUrl = `http://127.0.0.1:${String(params.localPort)}${req.path}`; const localUrl = `http://localhost${req.path}`;
const initHeaders = new Headers(); const headers = new Headers(req.headers);
for (const [k, v] of Object.entries(req.headers)) {
initHeaders.set(k, v);
}
let resp: Response; let resp: Response;
try { try {
resp = await fetch(localUrl, { resp = await params.appFetch(new Request(localUrl, {
method: req.method, method: req.method,
headers: initHeaders, headers,
body: req.body === null ? undefined : req.body, body: req.body === null ? undefined : req.body,
}); }));
} catch (e) { } catch (e) {
params.log("R4N7BQ3C", `local proxy fetch failed: ${String(e)}`); params.log("R4N7BQ3C", `app.fetch failed: ${String(e)}`);
const errBody: WsResponse = { const errBody: WsResponse = {
id: req.id, id: req.id,
status: 502, status: 502,