From 7b4f75509a035fa1acf4df531bdfcc7f934928dd Mon Sep 17 00:00:00 2001 From: lyon Date: Thu, 25 Jun 2026 18:48:09 +0800 Subject: [PATCH] fix: proxy workbench launches with trace context --- internal/dev-entrypoint/cloud-web-proxy.mjs | 3 + .../dev-entrypoint/cloud-web-proxy.test.mjs | 6 ++ internal/dev-entrypoint/cloud-web-routes.mjs | 1 + .../dev-entrypoint/cloud-web-routes.test.mjs | 9 +++ .../dev-entrypoint/cloud-web-runtime.test.mjs | 67 +++++++++++++++++++ 5 files changed, 86 insertions(+) diff --git a/internal/dev-entrypoint/cloud-web-proxy.mjs b/internal/dev-entrypoint/cloud-web-proxy.mjs index ed9589a0..bfd2b927 100644 --- a/internal/dev-entrypoint/cloud-web-proxy.mjs +++ b/internal/dev-entrypoint/cloud-web-proxy.mjs @@ -9,7 +9,10 @@ const FORWARDED_REQUEST_HEADERS = Object.freeze([ "authorization", "cookie", "prefer", + "traceparent", + "tracestate", "x-hwlab-short-connection", + "x-hwlab-otel-trace-id", "x-trace-id", "x-request-id", "x-actor-id", diff --git a/internal/dev-entrypoint/cloud-web-proxy.test.mjs b/internal/dev-entrypoint/cloud-web-proxy.test.mjs index 3e640e84..7ff21ff9 100644 --- a/internal/dev-entrypoint/cloud-web-proxy.test.mjs +++ b/internal/dev-entrypoint/cloud-web-proxy.test.mjs @@ -14,7 +14,10 @@ test("cloud web proxy preserves Code Agent short-connection headers", () => { accept: "application/json", "content-type": "application/json", prefer: "respond-async", + traceparent: "00-11111111111111111111111111111111-2222222222222222-01", + tracestate: "hwlab=launch", "x-hwlab-short-connection": "1", + "x-hwlab-otel-trace-id": "11111111111111111111111111111111", "x-trace-id": "trc_proxy_short_connection", "x-request-id": "req_proxy_short_connection", cookie: "hwlab_session=must-forward", @@ -25,7 +28,10 @@ test("cloud web proxy preserves Code Agent short-connection headers", () => { assert.equal(headers.accept, "application/json"); assert.equal(headers["content-type"], "application/json"); assert.equal(headers.prefer, "respond-async"); + assert.equal(headers.traceparent, "00-11111111111111111111111111111111-2222222222222222-01"); + assert.equal(headers.tracestate, "hwlab=launch"); assert.equal(headers["x-hwlab-short-connection"], "1"); + assert.equal(headers["x-hwlab-otel-trace-id"], "11111111111111111111111111111111"); assert.equal(headers["x-trace-id"], "trc_proxy_short_connection"); assert.equal(headers["x-request-id"], "req_proxy_short_connection"); assert.equal(headers.cookie, "hwlab_session=must-forward"); diff --git a/internal/dev-entrypoint/cloud-web-routes.mjs b/internal/dev-entrypoint/cloud-web-routes.mjs index a5b77f8e..cd1a94e1 100644 --- a/internal/dev-entrypoint/cloud-web-routes.mjs +++ b/internal/dev-entrypoint/cloud-web-routes.mjs @@ -8,6 +8,7 @@ const POST_PROXY_ROUTES = new Set([ "/v1/agent/chat/cancel", "/v1/agent/chat/steer", "/v1/agent/sessions", + "/v1/workbench/launches", "/v1/hwpod-node-ops", "/v1/web-performance", "/v1/m3/io", diff --git a/internal/dev-entrypoint/cloud-web-routes.test.mjs b/internal/dev-entrypoint/cloud-web-routes.test.mjs index 5986e4d8..49297318 100644 --- a/internal/dev-entrypoint/cloud-web-routes.test.mjs +++ b/internal/dev-entrypoint/cloud-web-routes.test.mjs @@ -222,3 +222,12 @@ test("cloud web proxies Code Agent steer through the Web-equivalent public API p }); }); +test("cloud web proxies authenticated Workbench launch writes to cloud-api", () => { + assert.deepEqual(cloudWebProxyRoutePolicy("POST", "/v1/workbench/launches"), { + proxy: true, + authRequired: true, + publicRoute: false, + routeKey: "POST /v1/workbench/launches" + }); +}); + diff --git a/internal/dev-entrypoint/cloud-web-runtime.test.mjs b/internal/dev-entrypoint/cloud-web-runtime.test.mjs index c4ce86e6..54a43009 100644 --- a/internal/dev-entrypoint/cloud-web-runtime.test.mjs +++ b/internal/dev-entrypoint/cloud-web-runtime.test.mjs @@ -54,6 +54,73 @@ test("cloud web auth requests are proxied once", async () => { } }); +test("cloud web proxies Workbench launches with trace context", async () => { + const upstreamRequests = []; + const upstream = createServer(async (request, response) => { + let body = ""; + for await (const chunk of request) body += chunk; + upstreamRequests.push({ + method: request.method, + url: request.url, + cookie: request.headers.cookie, + traceparent: request.headers.traceparent, + otelTraceId: request.headers["x-hwlab-otel-trace-id"], + body + }); + response.writeHead(404, { + "content-type": "application/json", + "x-hwlab-otel-trace-id": "11111111111111111111111111111111" + }); + response.end(JSON.stringify({ error: "not_found" })); + }); + await listen(upstream); + + const cloudWeb = createCloudWebServer({ + serviceId: "hwlab-cloud-web", + cloudApiBaseUrl: serverUrl(upstream), + cloudApiProxyTimeoutMs: 1000, + healthPayload: () => ({ status: "ok" }), + roots: [], + sendJson(response, statusCode, body) { + const payload = JSON.stringify(body); + response.writeHead(statusCode, { + "content-type": "application/json", + "content-length": Buffer.byteLength(payload) + }); + response.end(payload); + } + }); + await listen(cloudWeb); + + try { + const body = JSON.stringify({ projectId: "project_d601", taskRef: "task_2136" }); + const response = await fetch(`${serverUrl(cloudWeb)}/v1/workbench/launches`, { + method: "POST", + headers: { + "content-type": "application/json", + cookie: "hwlab_session=session-launch", + traceparent: "00-11111111111111111111111111111111-2222222222222222-01", + "x-hwlab-otel-trace-id": "11111111111111111111111111111111" + }, + body + }); + assert.equal(response.status, 404); + assert.equal(response.headers.get("x-hwlab-otel-trace-id"), "11111111111111111111111111111111"); + assert.deepEqual(await response.json(), { error: "not_found" }); + assert.deepEqual(upstreamRequests, [{ + method: "POST", + url: "/v1/workbench/launches", + cookie: "hwlab_session=session-launch", + traceparent: "00-11111111111111111111111111111111-2222222222222222-01", + otelTraceId: "11111111111111111111111111111111", + body + }]); + } finally { + await close(cloudWeb); + await close(upstream); + } +}); + test("cloud web proxy reports a unified upstream unavailable error", async () => { const cloudWeb = createCloudWebServer({ serviceId: "hwlab-cloud-web",