From e496aea6f63b4f0195904a30afe42e7281568830 Mon Sep 17 00:00:00 2001 From: root Date: Sat, 18 Jul 2026 06:24:56 +0200 Subject: [PATCH] fix: keep native Workbench SSE alive Co-Authored-By: Claude Opus 4.7 --- cmd/hwlab-workbench-api/main.ts | 6 ++++-- tools/src/workbench-cli.ts | 5 ++++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/cmd/hwlab-workbench-api/main.ts b/cmd/hwlab-workbench-api/main.ts index c1e11b95..38293f26 100644 --- a/cmd/hwlab-workbench-api/main.ts +++ b/cmd/hwlab-workbench-api/main.ts @@ -8,8 +8,10 @@ const kafkaEventBridge = runtime.mode === "agentrun-native" ? startHwlabKafkaEve const app = createWorkbenchHttpApp({ dispatch: runtime.dispatch, snapshot: runtime.application.snapshot?.bind(runtime.application), authorization: process.env.WORKBENCH_API_AUTHORIZATION, mode: runtime.mode, kafkaEventBridge, close: async () => { await kafkaEventBridge?.stop?.(); await runtime.close(); } }); const host = process.env.WORKBENCH_API_HOST || "0.0.0.0"; const port = positivePort(process.env.WORKBENCH_API_PORT || process.env.PORT, 6677); -const server = Bun.serve({ hostname: host, port, fetch: (request) => app.fetch(request) }); -process.stdout.write(`${JSON.stringify({ serviceId: "hwlab-workbench-api", status: "listening", mode: runtime.mode, host, port: server.port, temporalNamespace: runtime.temporalNamespace, taskQueue: runtime.taskQueue })}\n`); +const idleTimeout = positiveInteger(process.env.WORKBENCH_API_IDLE_TIMEOUT_SECONDS, "WORKBENCH_API_IDLE_TIMEOUT_SECONDS"); +const server = Bun.serve({ hostname: host, port, idleTimeout, fetch: (request) => app.fetch(request) }); +process.stdout.write(`${JSON.stringify({ serviceId: "hwlab-workbench-api", status: "listening", mode: runtime.mode, host, port: server.port, idleTimeout, temporalNamespace: runtime.temporalNamespace, taskQueue: runtime.taskQueue })}\n`); for (const signal of ["SIGINT", "SIGTERM"] as const) process.once(signal, async () => { server.stop(true); await app.close(); process.exit(0); }); function positivePort(value: string | undefined, fallback: number) { const parsed = Number.parseInt(String(value ?? fallback), 10); if (!Number.isInteger(parsed) || parsed < 1 || parsed > 65535) throw new Error("WORKBENCH_API_PORT must be a valid port"); return parsed; } +function positiveInteger(value: string | undefined, name: string) { const parsed = Number.parseInt(String(value ?? ""), 10); if (!Number.isSafeInteger(parsed) || parsed < 1) throw new Error(`${name} must be a positive integer`); return parsed; } diff --git a/tools/src/workbench-cli.ts b/tools/src/workbench-cli.ts index 90fd207d..7175761a 100644 --- a/tools/src/workbench-cli.ts +++ b/tools/src/workbench-cli.ts @@ -78,7 +78,10 @@ async function inspectEvents(parsed: Parsed, env: Record entry.name === "hwlab.event.v1").length; + if (error?.name !== "AbortError" && !(error?.code === "ECONNRESET" && businessCount >= minEvents)) { + throw codedError("workbench_sse_read_failed", `Workbench SSE read failed: ${error?.code ?? error?.name ?? "unknown"}; frames=${frames.length}; businessEvents=${businessCount}; connected=${frames.some((entry) => entry.name === "workbench.connected")}`); + } } finally { clearTimeout(timer); }