From 7d0299f89ac883c4d6e948d96dc4f6081450ff0c Mon Sep 17 00:00:00 2001 From: root Date: Sun, 19 Jul 2026 20:12:01 +0200 Subject: [PATCH] =?UTF-8?q?fix:=20=E9=9D=9E=E9=98=BB=E5=A1=9E=E5=9B=9E?= =?UTF-8?q?=E9=80=80=20Workbench=20API=20=E8=B0=83=E4=BC=98=E9=85=8D?= =?UTF-8?q?=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cmd/hwlab-workbench-api/main.ts | 14 ++++++++++++-- scripts/gitops-render.test.ts | 2 +- scripts/workbench-native-smoke.mjs | 6 ++++-- 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/cmd/hwlab-workbench-api/main.ts b/cmd/hwlab-workbench-api/main.ts index 38293f26..06ebea33 100644 --- a/cmd/hwlab-workbench-api/main.ts +++ b/cmd/hwlab-workbench-api/main.ts @@ -8,10 +8,20 @@ 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 idleTimeout = positiveInteger(process.env.WORKBENCH_API_IDLE_TIMEOUT_SECONDS, "WORKBENCH_API_IDLE_TIMEOUT_SECONDS"); +const idleTimeoutConfig = positiveIntegerOrDefault(process.env.WORKBENCH_API_IDLE_TIMEOUT_SECONDS, 60); +const idleTimeout = idleTimeoutConfig.value; +if (idleTimeoutConfig.warningReason) { + process.stderr.write(`${JSON.stringify({ event: "hwlab.config.warning", code: "workbench_api_idle_timeout_defaulted", configKey: "WORKBENCH_API_IDLE_TIMEOUT_SECONDS", reason: idleTimeoutConfig.warningReason, defaultValue: idleTimeout, blocking: false, valuesPrinted: false })}\n`); +} 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; } +function positiveIntegerOrDefault(value: string | undefined, fallback: number) { + const raw = String(value ?? "").trim(); + if (!raw) return { value: fallback, warningReason: "missing" }; + const parsed = Number.parseInt(raw, 10); + if (!Number.isSafeInteger(parsed) || parsed < 1 || String(parsed) !== raw) return { value: fallback, warningReason: "invalid-positive-integer" }; + return { value: parsed, warningReason: null }; +} diff --git a/scripts/gitops-render.test.ts b/scripts/gitops-render.test.ts index 78e6148c..0bdeb23b 100644 --- a/scripts/gitops-render.test.ts +++ b/scripts/gitops-render.test.ts @@ -12,7 +12,7 @@ import { externalPostgresConfigForLane, externalPostgresManifest, opencodeEgress import { CLOUD_CORE_MIGRATIONS, CLOUD_TRANSACTIONAL_REALTIME_MIGRATION_ID } from "../internal/db/schema.ts"; -test("v03 Workbench API declares its required Bun idle timeout in owning YAML", async () => { +test("v03 Workbench API declares its preferred Bun idle timeout in owning YAML", async () => { const deploy = parseYaml(await readFile("deploy/deploy.yaml", "utf8")); const service = deploy.lanes.v03.services.find((item: { serviceId?: string }) => item.serviceId === "hwlab-workbench-api"); assert.ok(service, "missing v03 hwlab-workbench-api service declaration"); diff --git a/scripts/workbench-native-smoke.mjs b/scripts/workbench-native-smoke.mjs index 77afabf8..28fca460 100755 --- a/scripts/workbench-native-smoke.mjs +++ b/scripts/workbench-native-smoke.mjs @@ -20,8 +20,7 @@ try { const nativeEnv = { ...process.env, WORKBENCH_MODE: "native-test", - WORKBENCH_NATIVE_STATE_FILE: path.join(stateDir, "state.json"), - WORKBENCH_API_IDLE_TIMEOUT_SECONDS: "60" + WORKBENCH_NATIVE_STATE_FILE: path.join(stateDir, "state.json") }; const api = start(["bun", "cmd/hwlab-workbench-api/main.ts"], { ...nativeEnv, @@ -30,6 +29,9 @@ try { }, "api"); children.push(api); await waitFor(`${apiUrl}/health/ready`); + if (!logs.some((item) => item.service === "api" && item.stream === "stderr" && item.line.includes('"code":"workbench_api_idle_timeout_defaulted"') && item.line.includes('"blocking":false'))) { + throw new Error("missing non-blocking idle timeout fallback warning"); + } const web = start(["bun", "run", "workbench:web:dev"], { ...process.env, WORKBENCH_WEB_PORT: String(webPort) }, "web"); children.push(web);