import assert from "node:assert/strict"; import { mkdtemp, readFile, rm, writeFile } from "node:fs/promises"; import { createServer } from "node:http"; import { connect } from "node:net"; import os from "node:os"; import path from "node:path"; import test from "node:test"; import { gzipSync } from "node:zlib"; import { createCloudWebServer, isClientDisconnectError, serveCloudWeb } from "./cloud-web-runtime.mjs"; test("cloud web classifies Bun client disconnect ECONNRESET as non-fatal", () => { const error = new Error("aborted"); error.code = "ECONNRESET"; assert.equal(isClientDisconnectError(error), true); assert.equal(isClientDisconnectError(new Error("database migration failed")), false); }); test("repo feature schema owns one unique key per product feature", async () => { const schema = JSON.parse(await readFile(new URL("../../config/feature-config.schema.json", import.meta.url), "utf8")); assert.equal(schema.$schema, "https://json-schema.org/draft/2020-12/schema"); assert.deepEqual(Object.keys(schema.properties), ["tracePanel", "rawEvents", "debugReplay", "views"]); const features = Object.values(schema.properties).map((property) => property["x-unidesk-feature"]); assert.equal(features.every((feature) => typeof feature === "string" && feature.length > 0), true); assert.equal(new Set(features).size, features.length); const encoded = JSON.stringify(schema); for (const architectureEnv of [ "HWLAB_KAFKA_DIRECT_PUBLISH_ENABLED", "HWLAB_WORKBENCH_LIVE_KAFKA_SSE_ENABLED", "HWLAB_WORKBENCH_KAFKA_REFRESH_REPLAY_ENABLED", "HWLAB_KAFKA_TRANSACTIONAL_PROJECTOR_ENABLED", "HWLAB_KAFKA_PROJECTION_OUTBOX_RELAY_ENABLED", "HWLAB_WORKBENCH_PROJECTION_REALTIME_ENABLED" ]) assert.equal(encoded.includes(architectureEnv), false); }); test("serveCloudWeb starts without obsolete architecture capabilities", async () => { const restoreEnv = withEnv({ HWLAB_CLOUD_WEB_DISPLAY_TIME_ZONE: "Asia/Shanghai", HWLAB_CLOUD_WEB_DISPLAY_TIME_LOCALE: "zh-CN", HWLAB_CLOUD_WEB_DISPLAY_TIME_LABEL: "北京时间", HWLAB_WORKBENCH_LIVE_KAFKA_SSE_ENABLED: "true", HWLAB_WORKBENCH_KAFKA_REFRESH_REPLAY_ENABLED: undefined, HWLAB_WORKBENCH_PROJECTION_REALTIME_ENABLED: "false", HWLAB_WORKBENCH_KAFKA_DEBUG_REPLAY_ENABLED: "true", HWLAB_WORKBENCH_RAW_HWLAB_EVENT_WINDOW_ENABLED: "true", HWLAB_WORKBENCH_RAW_HWLAB_EVENT_WINDOW_MAX_ENTRIES: "200", HWLAB_WORKBENCH_RAW_HWLAB_EVENT_WINDOW_MAX_RETAINED_BYTES: "1048576" }); const port = await unusedPort(); let server = null; try { server = await serveCloudWeb({ port, serviceId: "hwlab-cloud-web", healthPayload: () => ({ status: "ok" }), sendJson(response, statusCode, body) { response.writeHead(statusCode, { "content-type": "application/json" }); response.end(JSON.stringify(body)); } }); assert.equal(await canConnect(port), true); } finally { if (server) await close(server); restoreEnv(); } }); test("cloud web auth requests are proxied once", async () => { let upstreamRequests = 0; const upstream = createServer((request, response) => { upstreamRequests += 1; request.resume(); response.writeHead(200, { "content-type": "application/json" }); response.end(JSON.stringify({ authenticated: true, path: request.url })); }); await listen(upstream); const cloudWeb = createCloudWebServer({ serviceId: "hwlab-cloud-web", cloudApiBaseUrl: serverUrl(upstream), cloudApiProxyTimeoutMs: 1000, healthPayload: () => ({ status: "ok" }), 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 response = await fetch(`${serverUrl(cloudWeb)}/auth/session`, { headers: { cookie: "hwlab_session=token" } }); assert.equal(response.status, 200); assert.deepEqual(await response.json(), { authenticated: true, path: "/auth/session" }); assert.equal(upstreamRequests, 1); } finally { await close(cloudWeb); await close(upstream); } }); 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", cloudApiBaseUrl: "http://127.0.0.1:1", 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 response = await fetch(`${serverUrl(cloudWeb)}/v1/web-performance/summary?window=15m`, { headers: { accept: "application/json", "x-trace-id": "trc_proxy_upstream_unavailable" } }); assert.equal(response.status, 502); const payload = await response.json(); assert.equal(payload.status, "failed"); assert.equal(payload.error.code, "upstream_unavailable"); assert.equal(payload.error.layer, "proxy"); assert.equal(payload.error.retryable, true); assert.equal(payload.error.userMessage, "暂时无法连接上游。"); assert.equal(payload.error.message, "暂时无法连接上游。"); assert.equal(payload.error.blocker.code, "upstream_unavailable"); assert.equal(payload.error.blocker.summary, "暂时无法连接上游。"); assert.equal(payload.reason, "暂时无法连接上游。"); assert.doesNotMatch(JSON.stringify(payload), /Code Agent|输入已保留|稍后重试/u); } finally { await close(cloudWeb); } }); test("cloud web proxies Admin Access write routes", 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, authorization: request.headers.authorization, cookie: request.headers.cookie, body }); response.writeHead(200, { "content-type": "application/json" }); response.end(JSON.stringify({ ok: true, path: request.url })); }); 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 checkBody = JSON.stringify({ user: "usr_alice", relation: "can_use", object: "tool:hwpod" }); const checkResponse = await fetch(`${serverUrl(cloudWeb)}/v1/admin/access/check`, { method: "POST", headers: { "content-type": "application/json", authorization: "Bearer hwl_live_admin", cookie: "hwlab_session=session-a" }, body: checkBody }); assert.equal(checkResponse.status, 200); assert.deepEqual(await checkResponse.json(), { ok: true, path: "/v1/admin/access/check" }); const grantResponse = await fetch(`${serverUrl(cloudWeb)}/v1/admin/access/users/usr_alice/tools/hwpod/can-use`, { method: "PUT", headers: { "content-type": "application/json", authorization: "Bearer hwl_live_admin" }, body: "{}" }); assert.equal(grantResponse.status, 200); assert.equal(upstreamRequests.length, 2); assert.deepEqual(upstreamRequests[0], { method: "POST", url: "/v1/admin/access/check", authorization: "Bearer hwl_live_admin", cookie: "hwlab_session=session-a", body: checkBody }); assert.deepEqual(upstreamRequests[1], { method: "PUT", url: "/v1/admin/access/users/usr_alice/tools/hwpod/can-use", authorization: "Bearer hwl_live_admin", cookie: undefined, body: "{}" }); } finally { await close(cloudWeb); await close(upstream); } }); test("cloud web proxies provider profile management write routes", 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, authorization: request.headers.authorization, body }); response.writeHead(200, { "content-type": "application/json" }); response.end(JSON.stringify({ ok: true, path: request.url })); }); 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 credentialBody = JSON.stringify({ apiKey: "sk-test-redacted" }); const credentialResponse = await fetch(`${serverUrl(cloudWeb)}/v1/admin/provider-profiles/deepseek/credential`, { method: "PUT", headers: { "content-type": "application/json", authorization: "Bearer hwl_live_admin" }, body: credentialBody }); assert.equal(credentialResponse.status, 200); const validateResponse = await fetch(`${serverUrl(cloudWeb)}/v1/admin/provider-profiles/deepseek/validate`, { method: "POST", headers: { "content-type": "application/json", authorization: "Bearer hwl_live_admin" }, body: "{}" }); assert.equal(validateResponse.status, 200); const testBody = JSON.stringify({ model: "gpt-5.4-mini", prompt: "只回复 OK" }); const testResponse = await fetch(`${serverUrl(cloudWeb)}/v1/admin/provider-profiles/deepseek/test`, { method: "POST", headers: { "content-type": "application/json", authorization: "Bearer hwl_live_admin" }, body: testBody }); assert.equal(testResponse.status, 200); assert.equal(upstreamRequests.length, 3); assert.deepEqual(upstreamRequests[0], { method: "PUT", url: "/v1/admin/provider-profiles/deepseek/credential", authorization: "Bearer hwl_live_admin", body: credentialBody }); assert.deepEqual(upstreamRequests[1], { method: "POST", url: "/v1/admin/provider-profiles/deepseek/validate", authorization: "Bearer hwl_live_admin", body: "{}" }); assert.deepEqual(upstreamRequests[2], { method: "POST", url: "/v1/admin/provider-profiles/deepseek/test", authorization: "Bearer hwl_live_admin", body: testBody }); } finally { await close(cloudWeb); await close(upstream); } }); test("cloud web forwards hwpod node-ops plans to cloud-api", 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, authorization: request.headers.authorization, body }); response.writeHead(200, { "content-type": "application/json" }); response.end(JSON.stringify({ ok: false, status: "blocked", path: request.url, blocker: { code: "hwpod_node_unavailable" } })); }); 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 plan = { contractVersion: "hwpod-node-ops-v1", planId: "hwpod_plan_proxy_test", hwpodId: "hwpod-test", nodeId: "node-test", ops: [{ opId: "op_01", op: "node.health", args: {} }] }; const response = await fetch(`${serverUrl(cloudWeb)}/v1/hwpod-node-ops`, { method: "POST", headers: { "content-type": "application/json", authorization: "Bearer hwl_live_operator" }, body: JSON.stringify(plan) }); assert.equal(response.status, 200); assert.deepEqual(await response.json(), { ok: false, status: "blocked", path: "/v1/hwpod-node-ops", blocker: { code: "hwpod_node_unavailable" } }); assert.equal(upstreamRequests.length, 1); assert.deepEqual(upstreamRequests[0], { method: "POST", url: "/v1/hwpod-node-ops", authorization: "Bearer hwl_live_operator", body: JSON.stringify(plan) }); } finally { await close(cloudWeb); await close(upstream); } }); test("cloud web remains healthy after a client disconnects during static response", async () => { const root = await mkdtemp(path.join(os.tmpdir(), "hwlab-cloud-web-runtime-")); await writeFile(path.join(root, "large.txt"), "x".repeat(512 * 1024), "utf8"); const cloudWeb = createCloudWebServer({ serviceId: "hwlab-cloud-web", roots: [root], cloudApiBaseUrl: "http://127.0.0.1:1", healthPayload: () => ({ status: "ok", process: { uptimeSeconds: 1 } }), 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 { await disconnectDuringResponse(serverUrl(cloudWeb), "/large.txt"); const response = await fetch(`${serverUrl(cloudWeb)}/health/live`); assert.equal(response.status, 200); assert.equal((await response.json()).status, "ok"); } finally { await close(cloudWeb); await rm(root, { recursive: true, force: true }); } }); test("cloud web serves client deep links through the Vue shell", async () => { const restoreEnv = withEnv({ HWLAB_CLOUD_WEB_DISPLAY_TIME_ZONE: "Asia/Shanghai", HWLAB_CLOUD_WEB_DISPLAY_TIME_LOCALE: "zh-CN", HWLAB_CLOUD_WEB_DISPLAY_TIME_LABEL: "北京时间", HWLAB_WORKBENCH_LIVE_KAFKA_SSE_ENABLED: "true", HWLAB_WORKBENCH_KAFKA_REFRESH_REPLAY_ENABLED: "true", HWLAB_WORKBENCH_PROJECTION_REALTIME_ENABLED: "false", HWLAB_WORKBENCH_KAFKA_DEBUG_REPLAY_ENABLED: "true", HWLAB_WORKBENCH_RAW_HWLAB_EVENT_WINDOW_ENABLED: "true", HWLAB_WORKBENCH_RAW_HWLAB_EVENT_WINDOW_MAX_ENTRIES: "200", HWLAB_WORKBENCH_RAW_HWLAB_EVENT_WINDOW_MAX_RETAINED_BYTES: "1048576" }); const root = await mkdtemp(path.join(os.tmpdir(), "hwlab-cloud-web-runtime-")); await writeFile(path.join(root, "index.html"), "
\n", "utf8"); await writeFile(path.join(root, "asset.txt"), "asset body\n", "utf8"); const cloudWeb = createCloudWebServer({ serviceId: "hwlab-cloud-web", roots: [root], cloudApiBaseUrl: "http://127.0.0.1:1", healthPayload: () => ({ status: "ok" }), 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 { for (const route of ["/access", "/register", "/api-keys", "/admin/users"]) { const response = await fetch(`${serverUrl(cloudWeb)}${route}`, { headers: { accept: "text/html" } }); assert.equal(response.status, 200, route); assert.equal(response.headers.get("content-type"), "text/html; charset=utf-8", route); const html = await response.text(); assert.match(html, /
<\/div>/u, route); assert.match(html, /HWLAB_CLOUD_WEB_CONFIG/u, route); assert.doesNotMatch(html, /realtimeFeatures|liveKafkaSse|kafkaRefreshReplay|projectionRealtime/u, route); assert.match(html, /"debugCapabilities":\{"isolatedKafka":true,"rawHwlabEventWindow":\{"enabled":true,"maxEntries":200,"maxRetainedBytes":1048576\}\}/u, route); } const assetResponse = await fetch(`${serverUrl(cloudWeb)}/asset.txt`, { headers: { accept: "text/plain" } }); assert.equal(assetResponse.status, 200); assert.equal(await assetResponse.text(), "asset body\n"); const missingAssetResponse = await fetch(`${serverUrl(cloudWeb)}/missing.css`, { headers: { accept: "text/css,*/*" } }); assert.equal(missingAssetResponse.status, 404); } finally { restoreEnv(); await close(cloudWeb); await rm(root, { recursive: true, force: true }); } }); test("cloud web injects trace explorer runtime config from env", async () => { const restoreEnv = withEnv({ HWLAB_CLOUD_WEB_DISPLAY_TIME_ZONE: "Asia/Shanghai", HWLAB_CLOUD_WEB_DISPLAY_TIME_LOCALE: "zh-CN", HWLAB_CLOUD_WEB_DISPLAY_TIME_LABEL: "北京时间", HWLAB_WORKBENCH_TRACE_EXPLORER_URL_TEMPLATE: "/v1/workbench/traces/{trace_id}/events", HWLAB_WORKBENCH_LIVE_KAFKA_SSE_ENABLED: "true", HWLAB_WORKBENCH_KAFKA_REFRESH_REPLAY_ENABLED: "false", HWLAB_WORKBENCH_PROJECTION_REALTIME_ENABLED: "false", HWLAB_WORKBENCH_KAFKA_DEBUG_REPLAY_ENABLED: "true", HWLAB_WORKBENCH_RAW_HWLAB_EVENT_WINDOW_ENABLED: "true", HWLAB_WORKBENCH_RAW_HWLAB_EVENT_WINDOW_MAX_ENTRIES: "200", HWLAB_WORKBENCH_RAW_HWLAB_EVENT_WINDOW_MAX_RETAINED_BYTES: "1048576" }); const root = await mkdtemp(path.join(os.tmpdir(), "hwlab-cloud-web-runtime-")); await writeFile(path.join(root, "index.html"), "
\n", "utf8"); const cloudWeb = createCloudWebServer({ serviceId: "hwlab-cloud-web", roots: [root], cloudApiBaseUrl: "http://127.0.0.1:1", healthPayload: () => ({ status: "ok" }), 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 response = await fetch(`${serverUrl(cloudWeb)}/workbench`, { headers: { accept: "text/html" } }); assert.equal(response.status, 200); const html = await response.text(); assert.match(html, /traceExplorerUrlTemplate":"\/v1\/workbench\/traces\/\{trace_id\}\/events/u); assert.doesNotMatch(html, /api key|secret|password/iu); } finally { restoreEnv(); await close(cloudWeb); await rm(root, { recursive: true, force: true }); } }); test("cloud web OpenCode proxy requires an authenticated HWLAB session", async () => { const authRequests = []; let opencodeRequests = 0; const cloudApi = createServer((request, response) => { authRequests.push({ traceparent: request.headers.traceparent, otelTraceId: request.headers["x-hwlab-otel-trace-id"], requestId: request.headers["x-request-id"], sourceServiceId: request.headers["x-source-service-id"] }); request.resume(); response.writeHead(401, { "content-type": "application/json" }); response.end(JSON.stringify({ authenticated: false })); }); const opencode = createServer((request, response) => { opencodeRequests += 1; request.resume(); response.writeHead(200, { "content-type": "text/plain" }); response.end("opencode should not be reached"); }); await listen(cloudApi); await listen(opencode); const cloudWeb = createCloudWebServer({ serviceId: "hwlab-cloud-web", roots: [], cloudApiBaseUrl: serverUrl(cloudApi), cloudApiProxyTimeoutMs: 1000, opencodeUpstreamUrl: serverUrl(opencode), opencodeProxyHost: "127.0.0.1", opencodeProxyTimeoutMs: 1000, opencodeUsername: "oc_user", opencodePassword: "oc_password", healthPayload: () => ({ status: "ok" }), 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 response = await fetch(`${serverUrl(cloudWeb)}/`, { headers: { accept: "text/html" } }); assert.equal(response.status, 401); const body = await response.json(); assert.equal(body.error, "opencode_auth_required"); assert.match(response.headers.get("traceparent") ?? "", /^00-[0-9a-f]{32}-[0-9a-f]{16}-01$/u); assert.match(response.headers.get("x-hwlab-otel-trace-id") ?? "", /^[0-9a-f]{32}$/u); assert.match(response.headers.get("x-request-id") ?? "", /^req_[0-9a-f]{32}$/u); assert.equal(body.diagnostic.traceId, response.headers.get("x-hwlab-otel-trace-id")); assert.equal(body.diagnostic.requestId, response.headers.get("x-request-id")); assert.deepEqual(authRequests, [{ traceparent: response.headers.get("traceparent"), otelTraceId: response.headers.get("x-hwlab-otel-trace-id"), requestId: response.headers.get("x-request-id"), sourceServiceId: "hwlab-cloud-web" }]); assert.equal(opencodeRequests, 0); } finally { await close(cloudWeb); await close(opencode); await close(cloudApi); } }); test("cloud web OpenCode frame-url endpoint mints traced tickets", async () => { const restoreEnv = withEnv({ HWLAB_CLOUD_WEB_DISPLAY_TIME_ZONE: "Asia/Shanghai", HWLAB_CLOUD_WEB_DISPLAY_TIME_LOCALE: "zh-CN", HWLAB_CLOUD_WEB_DISPLAY_TIME_LABEL: "北京时间", HWLAB_CLOUD_WEB_OPENCODE_URL: "https://opencode.example.test", HWLAB_WORKBENCH_LIVE_KAFKA_SSE_ENABLED: "true", HWLAB_WORKBENCH_KAFKA_REFRESH_REPLAY_ENABLED: "false", HWLAB_WORKBENCH_PROJECTION_REALTIME_ENABLED: "false", HWLAB_WORKBENCH_KAFKA_DEBUG_REPLAY_ENABLED: "true", HWLAB_WORKBENCH_RAW_HWLAB_EVENT_WINDOW_ENABLED: "true", HWLAB_WORKBENCH_RAW_HWLAB_EVENT_WINDOW_MAX_ENTRIES: "200", HWLAB_WORKBENCH_RAW_HWLAB_EVENT_WINDOW_MAX_RETAINED_BYTES: "1048576" }); const cloudApiRequests = []; const opencodeRequests = []; const cloudApi = createServer((request, response) => { cloudApiRequests.push({ cookie: request.headers.cookie, traceparent: request.headers.traceparent, otelTraceId: request.headers["x-hwlab-otel-trace-id"], requestId: request.headers["x-request-id"], sourceServiceId: request.headers["x-source-service-id"] }); request.resume(); response.writeHead(200, { "content-type": "application/json" }); response.end(JSON.stringify({ authenticated: request.headers.cookie === "hwlab_session=session-a" })); }); const opencode = createServer((request, response) => { opencodeRequests.push({ url: request.url, cookie: request.headers.cookie }); request.resume(); response.writeHead(200, { "content-type": "text/plain" }); response.end("opencode ok\n"); }); await listen(cloudApi); await listen(opencode); const cloudWeb = createCloudWebServer({ serviceId: "hwlab-cloud-web", roots: [], cloudApiBaseUrl: serverUrl(cloudApi), cloudApiProxyTimeoutMs: 1000, opencodeUpstreamUrl: serverUrl(opencode), opencodeProxyHost: "", opencodeProxyTimeoutMs: 1000, opencodeUsername: "oc_user", opencodePassword: "oc_password", healthPayload: () => ({ status: "ok" }), 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 response = await fetch(`${serverUrl(cloudWeb)}/opencode/frame-url`, { headers: { accept: "application/json", cookie: "hwlab_session=session-a", "x-request-id": "req_opencode_frame_url" } }); assert.equal(response.status, 200); assert.equal(response.headers.get("x-request-id"), "req_opencode_frame_url"); assert.match(response.headers.get("traceparent") ?? "", /^00-[0-9a-f]{32}-[0-9a-f]{16}-01$/u); const body = await response.json(); const frameUrl = new URL(body.url); const ticket = frameUrl.searchParams.get("hwlab_opencode_ticket"); assert.ok(ticket, "dynamic endpoint should append an OpenCode ticket"); assert.equal(body.diagnostic.traceId, response.headers.get("x-hwlab-otel-trace-id")); assert.deepEqual(cloudApiRequests, [{ cookie: "hwlab_session=session-a", traceparent: response.headers.get("traceparent"), otelTraceId: response.headers.get("x-hwlab-otel-trace-id"), requestId: "req_opencode_frame_url", sourceServiceId: "hwlab-cloud-web" }]); const proxied = await fetch(`${serverUrl(cloudWeb)}/_opencode/?hwlab_opencode_ticket=${encodeURIComponent(ticket)}`, { headers: { accept: "text/plain" } }); assert.equal(proxied.status, 200); assert.equal(await proxied.text(), "opencode ok\n"); assert.deepEqual(opencodeRequests, [{ url: "/", cookie: undefined }]); } finally { restoreEnv(); await close(cloudWeb); await close(opencode); await close(cloudApi); } }); test("cloud web OpenCode proxy bootstraps the default project for iframe HTML", async () => { const restoreEnv = withEnv({ HWLAB_CLOUD_WEB_OPENCODE_URL: "https://opencode.example.test" }); const cloudApi = createServer((request, response) => { request.resume(); response.writeHead(200, { "content-type": "application/json" }); response.end(JSON.stringify({ authenticated: request.headers.cookie === "hwlab_session=session-a" })); }); const opencodeRequests = []; const opencodeHtml = "
\n"; const opencodeHtmlGzip = gzipSync(Buffer.from(opencodeHtml, "utf8")); const opencode = createServer((request, response) => { opencodeRequests.push({ url: request.url, authorization: request.headers.authorization, cookie: request.headers.cookie }); request.resume(); response.writeHead(200, { "content-type": "text/html; charset=utf-8", "content-encoding": "gzip", etag: "upstream-html-etag" }); response.end(opencodeHtmlGzip); }); await listen(cloudApi); await listen(opencode); const cloudWeb = createCloudWebServer({ serviceId: "hwlab-cloud-web", roots: [], cloudApiBaseUrl: serverUrl(cloudApi), cloudApiProxyTimeoutMs: 1000, opencodeUpstreamUrl: serverUrl(opencode), opencodeProxyHost: "", opencodeProxyTimeoutMs: 1000, opencodeUsername: "oc_user", opencodePassword: "oc_password", opencodeDefaultProject: "/workspace", healthPayload: () => ({ status: "ok" }), 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 frame = await fetch(`${serverUrl(cloudWeb)}/opencode/frame-url`, { headers: { accept: "application/json", cookie: "hwlab_session=session-a" } }); const ticket = new URL((await frame.json()).url).searchParams.get("hwlab_opencode_ticket"); const proxied = await fetch(`${serverUrl(cloudWeb)}/_opencode/?hwlab_opencode_ticket=${encodeURIComponent(ticket)}`, { headers: { accept: "text/html" } }); assert.equal(proxied.status, 200); assert.equal(proxied.headers.get("content-encoding"), null); assert.equal(proxied.headers.get("etag"), null); const html = await proxied.text(); assert.match(html, /data-hwlab-opencode-default-project/u); assert.match(html, /src="\/__hwlab\/opencode-default-project\.js"/u); assert.doesNotMatch(html, /localStorage/u); const ticketCookie = proxied.headers.get("set-cookie")?.match(/hwlab_opencode_ticket=([^;]+)/u)?.[1] || ""; assert.ok(ticketCookie, "expected iframe HTML response to set OpenCode ticket cookie"); const bootstrap = await fetch(`${serverUrl(cloudWeb)}/_opencode/__hwlab/opencode-default-project.js`, { headers: { accept: "text/javascript", cookie: `hwlab_opencode_ticket=${ticketCookie}` } }); assert.equal(bootstrap.status, 200); assert.equal(bootstrap.headers.get("content-type"), "text/javascript; charset=utf-8"); const bootstrapScript = await bootstrap.text(); assert.match(bootstrapScript, /opencode\.global\.dat:server/u); assert.match(bootstrapScript, /opencode\.global\.dat:layout\.page/u); assert.match(bootstrapScript, /\/workspace/u); assert.deepEqual(opencodeRequests, [{ url: "/", authorization: `Basic ${Buffer.from("oc_user:oc_password", "utf8").toString("base64")}`, cookie: undefined }]); } finally { restoreEnv(); await close(cloudWeb); await close(opencode); await close(cloudApi); } }); test("cloud web OpenCode proxy injects upstream Basic Auth without forwarding HWLAB cookie", async () => { const opencodeRequests = []; const cloudApi = createServer((request, response) => { request.resume(); response.writeHead(200, { "content-type": "application/json" }); response.end(JSON.stringify({ authenticated: request.headers.cookie === "hwlab_session=session-a" })); }); const opencode = createServer((request, response) => { opencodeRequests.push({ url: request.url, authorization: request.headers.authorization, cookie: request.headers.cookie }); request.resume(); response.writeHead(200, { "content-type": "text/javascript" }); response.end("export const ok = true;\n"); }); await listen(cloudApi); await listen(opencode); const cloudWeb = createCloudWebServer({ serviceId: "hwlab-cloud-web", roots: [], cloudApiBaseUrl: serverUrl(cloudApi), cloudApiProxyTimeoutMs: 1000, opencodeUpstreamUrl: serverUrl(opencode), opencodeProxyHost: "127.0.0.1", opencodeProxyTimeoutMs: 1000, opencodeUsername: "oc_user", opencodePassword: "oc_password", healthPayload: () => ({ status: "ok" }), 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 response = await fetch(`${serverUrl(cloudWeb)}/assets/index.js`, { headers: { accept: "text/javascript", cookie: "hwlab_session=session-a", "x-trace-id": "trc_opencode_proxy" } }); assert.equal(response.status, 200); assert.equal(await response.text(), "export const ok = true;\n"); assert.deepEqual(opencodeRequests, [{ url: "/assets/index.js", authorization: `Basic ${Buffer.from("oc_user:oc_password", "utf8").toString("base64")}`, cookie: undefined }]); } finally { await close(cloudWeb); await close(opencode); await close(cloudApi); } }); test("cloud web OpenCode proxy rewrites configured event stream directory", async () => { const cloudApi = createServer((request, response) => { request.resume(); response.writeHead(200, { "content-type": "application/json" }); response.end(JSON.stringify({ authenticated: request.headers.cookie === "hwlab_session=session-a" })); }); const opencode = createServer((request, response) => { request.resume(); response.writeHead(200, { "content-type": "text/event-stream; charset=utf-8" }); response.write('data: {"directory":"/workspace","payload":{"type":"session.status","properties":{"sessionID":"ses_1","status":{"type":"idle"}}}}\n\n'); response.write('data: {"directory":"/other","payload":{"type":"server.heartbeat","properties":{}}}\n\n'); response.end(); }); await listen(cloudApi); await listen(opencode); const cloudWeb = createCloudWebServer({ serviceId: "hwlab-cloud-web", roots: [], cloudApiBaseUrl: serverUrl(cloudApi), cloudApiProxyTimeoutMs: 1000, opencodeUpstreamUrl: serverUrl(opencode), opencodeProxyHost: "127.0.0.1", opencodeProxyTimeoutMs: 1000, opencodeUsername: "oc_user", opencodePassword: "oc_password", opencodeEventDirectoryRewrite: { from: "/workspace", to: "/" }, healthPayload: () => ({ status: "ok" }), 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 response = await fetch(`${serverUrl(cloudWeb)}/global/event`, { headers: { accept: "text/event-stream", cookie: "hwlab_session=session-a" } }); assert.equal(response.status, 200); const text = await response.text(); assert.match(text, /"directory":"\/"/u); assert.doesNotMatch(text, /"directory":"\/workspace"/u); assert.match(text, /"directory":"\/other"/u); } finally { await close(cloudWeb); await close(opencode); await close(cloudApi); } }); test("cloud web OpenCode proxy emits bounded OTLP span", async () => { const otelBodies = []; const otel = createServer(async (request, response) => { let body = ""; for await (const chunk of request) body += chunk; otelBodies.push(JSON.parse(body)); response.writeHead(200, { "content-type": "application/json" }); response.end("{}\n"); }); await listen(otel); const restoreEnv = withEnv({ OTEL_EXPORTER_OTLP_TRACES_ENDPOINT: `${serverUrl(otel)}/v1/traces` }); const cloudApi = createServer((request, response) => { request.resume(); response.writeHead(200, { "content-type": "application/json" }); response.end(JSON.stringify({ authenticated: request.headers.cookie === "hwlab_session=session-a" })); }); const opencode = createServer((request, response) => { request.resume(); response.writeHead(200, { "content-type": "application/json" }); response.end(JSON.stringify({ ok: true })); }); await listen(cloudApi); await listen(opencode); const cloudWeb = createCloudWebServer({ serviceId: "hwlab-cloud-web", roots: [], cloudApiBaseUrl: serverUrl(cloudApi), cloudApiProxyTimeoutMs: 1000, opencodeUpstreamUrl: serverUrl(opencode), opencodeProxyHost: "127.0.0.1", opencodeProxyTimeoutMs: 1000, opencodeUsername: "oc_user", opencodePassword: "oc_password", healthPayload: () => ({ status: "ok" }), 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 response = await fetch(`${serverUrl(cloudWeb)}/session/ses_otel/prompt_async`, { method: "POST", headers: { accept: "application/json", "content-type": "application/json", cookie: "hwlab_session=session-a" }, body: JSON.stringify({ parts: [{ type: "text", text: "hi" }] }) }); assert.equal(response.status, 200); await waitFor(() => otelBodies.length > 0, "expected OpenCode proxy OTel span"); const spans = otelBodies.flatMap((body) => body.resourceSpans?.flatMap((resourceSpan) => resourceSpan.scopeSpans?.flatMap((scopeSpan) => scopeSpan.spans ?? []) ?? []) ?? []); const span = spans.find((item) => item.name === "opencode.proxy.request"); assert.ok(span, "expected opencode.proxy.request span"); const attrs = new Map((span.attributes ?? []).map((entry) => [entry.key, entry.value?.stringValue ?? entry.value?.intValue ?? entry.value?.boolValue])); assert.equal(attrs.get("http.route"), "/session/:sessionID/prompt_async"); assert.equal(attrs.get("http.response.status_class"), "2xx"); assert.equal(attrs.get("valuesPrinted"), false); } finally { restoreEnv(); await close(cloudWeb); await close(opencode); await close(cloudApi); await close(otel); } }); test("cloud web OpenCode event stream emits rewrite OTLP stats when the client closes", async () => { const otelBodies = []; const otel = createServer(async (request, response) => { let body = ""; for await (const chunk of request) body += chunk; otelBodies.push(JSON.parse(body)); response.writeHead(200, { "content-type": "application/json" }); response.end("{}\n"); }); await listen(otel); const restoreEnv = withEnv({ OTEL_EXPORTER_OTLP_TRACES_ENDPOINT: `${serverUrl(otel)}/v1/traces` }); const cloudApi = createServer((request, response) => { request.resume(); response.writeHead(200, { "content-type": "application/json" }); response.end(JSON.stringify({ authenticated: request.headers.cookie === "hwlab_session=session-a" })); }); const opencodeResponses = []; const opencode = createServer((request, response) => { request.resume(); opencodeResponses.push(response); response.writeHead(200, { "content-type": "text/event-stream; charset=utf-8" }); response.write('data: {"directory":"/workspace","payload":{"type":"session.status","properties":{"sessionID":"ses_1"}}}\n\n'); }); await listen(cloudApi); await listen(opencode); const cloudWeb = createCloudWebServer({ serviceId: "hwlab-cloud-web", roots: [], cloudApiBaseUrl: serverUrl(cloudApi), cloudApiProxyTimeoutMs: 1000, opencodeUpstreamUrl: serverUrl(opencode), opencodeProxyHost: "127.0.0.1", opencodeProxyTimeoutMs: 1000, opencodeUsername: "oc_user", opencodePassword: "oc_password", opencodeEventDirectoryRewrite: { from: "/workspace", to: "/" }, healthPayload: () => ({ status: "ok" }), 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 response = await fetch(`${serverUrl(cloudWeb)}/global/event`, { headers: { accept: "text/event-stream", cookie: "hwlab_session=session-a" } }); assert.equal(response.status, 200); const reader = response.body.getReader(); const first = await reader.read(); assert.equal(first.done, false); const text = new TextDecoder().decode(first.value); assert.match(text, /"directory":"\/"/u); const collectSpans = () => otelBodies.flatMap((body) => body.resourceSpans?.flatMap((resourceSpan) => resourceSpan.scopeSpans?.flatMap((scopeSpan) => scopeSpan.spans ?? []) ?? []) ?? []); await waitFor(() => collectSpans().some((item) => item.name === "opencode.proxy.stream.start"), "expected OpenCode event stream start OTel span"); const startSpan = collectSpans().find((item) => item.name === "opencode.proxy.stream.start"); assert.ok(startSpan, "expected opencode.proxy.stream.start span"); const startAttrs = new Map((startSpan.attributes ?? []).map((entry) => [entry.key, entry.value?.stringValue ?? entry.value?.intValue ?? entry.value?.boolValue])); assert.equal(startAttrs.get("http.route"), "/global/event"); assert.equal(startAttrs.get("opencode.proxy.phase"), "stream_start"); assert.equal(startAttrs.get("opencode.proxy.streaming"), true); assert.equal(startAttrs.get("opencode.proxy.sse.directory_rewrite_enabled"), true); assert.equal(startAttrs.get("opencode.proxy.sse.directory_rewrite_from"), "/workspace"); assert.equal(startAttrs.get("opencode.proxy.sse.directory_rewrite_to"), "/"); await reader.cancel(); await waitFor(() => collectSpans().some((item) => item.name === "opencode.proxy.request"), "expected OpenCode event stream completion OTel span"); const spans = collectSpans(); const span = spans.find((item) => item.name === "opencode.proxy.request"); assert.ok(span, "expected opencode.proxy.request span"); const attrs = new Map((span.attributes ?? []).map((entry) => [entry.key, entry.value?.stringValue ?? entry.value?.intValue ?? entry.value?.boolValue])); assert.equal(attrs.get("http.route"), "/global/event"); assert.equal(attrs.get("opencode.proxy.streaming"), true); assert.equal(attrs.get("opencode.proxy.sse.directory_rewrite_enabled"), true); assert.equal(attrs.get("opencode.proxy.sse.directory_rewrite_from"), "/workspace"); assert.equal(attrs.get("opencode.proxy.sse.directory_rewrite_to"), "/"); assert.equal(attrs.get("opencode.proxy.sse.directory_rewrite_data_lines"), "1"); assert.equal(attrs.get("opencode.proxy.sse.directory_rewrite_events"), "1"); assert.equal(attrs.get("opencode.proxy.sse.directory_rewrite_json_errors"), "0"); } finally { for (const response of opencodeResponses) response.destroy(); restoreEnv(); await close(cloudWeb); await close(opencode); await close(cloudApi); await close(otel); } }); test("cloud web OpenCode proxy accepts short-lived tickets minted by the shell", async () => { const restoreEnv = withEnv({ HWLAB_CLOUD_WEB_DISPLAY_TIME_ZONE: "Asia/Shanghai", HWLAB_CLOUD_WEB_DISPLAY_TIME_LOCALE: "zh-CN", HWLAB_CLOUD_WEB_DISPLAY_TIME_LABEL: "北京时间", HWLAB_CLOUD_WEB_OPENCODE_URL: "https://opencode.example.test", HWLAB_WORKBENCH_LIVE_KAFKA_SSE_ENABLED: "true", HWLAB_WORKBENCH_KAFKA_REFRESH_REPLAY_ENABLED: "false", HWLAB_WORKBENCH_PROJECTION_REALTIME_ENABLED: "false", HWLAB_WORKBENCH_KAFKA_DEBUG_REPLAY_ENABLED: "true", HWLAB_WORKBENCH_RAW_HWLAB_EVENT_WINDOW_ENABLED: "true", HWLAB_WORKBENCH_RAW_HWLAB_EVENT_WINDOW_MAX_ENTRIES: "200", HWLAB_WORKBENCH_RAW_HWLAB_EVENT_WINDOW_MAX_RETAINED_BYTES: "1048576" }); const root = await mkdtemp(path.join(os.tmpdir(), "hwlab-cloud-web-runtime-")); await writeFile(path.join(root, "index.html"), "
\n", "utf8"); const cloudApiCookies = []; const opencodeRequests = []; const cloudApi = createServer((request, response) => { cloudApiCookies.push(request.headers.cookie); request.resume(); response.writeHead(200, { "content-type": "application/json" }); response.end(JSON.stringify({ authenticated: request.headers.cookie === "hwlab_session=session-a" })); }); const opencode = createServer((request, response) => { opencodeRequests.push({ url: request.url, authorization: request.headers.authorization, cookie: request.headers.cookie }); request.resume(); response.writeHead(200, { "content-type": "text/plain" }); response.end("opencode ok\n"); }); await listen(cloudApi); await listen(opencode); const cloudWeb = createCloudWebServer({ serviceId: "hwlab-cloud-web", roots: [root], cloudApiBaseUrl: serverUrl(cloudApi), cloudApiProxyTimeoutMs: 1000, opencodeUpstreamUrl: serverUrl(opencode), opencodeProxyHost: "", opencodeProxyTimeoutMs: 1000, opencodeUsername: "oc_user", opencodePassword: "oc_password", healthPayload: () => ({ status: "ok" }), 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 shell = await fetch(`${serverUrl(cloudWeb)}/opencode`, { headers: { accept: "text/html", cookie: "hwlab_session=session-a" } }); assert.equal(shell.status, 200); const html = await shell.text(); const config = JSON.parse(html.match(/window\.HWLAB_CLOUD_WEB_CONFIG=([^<]+);<\/script>/u)?.[1] ?? "null"); const frameUrl = new URL(config.opencode.url); const ticket = frameUrl.searchParams.get("hwlab_opencode_ticket"); assert.ok(ticket, "ticket should be appended to OpenCode iframe URL"); const first = await fetch(`${serverUrl(cloudWeb)}/_opencode/?hwlab_opencode_ticket=${encodeURIComponent(ticket)}`, { headers: { accept: "text/plain" } }); assert.equal(first.status, 200); assert.equal(await first.text(), "opencode ok\n"); const ticketCookie = first.headers.get("set-cookie") ?? ""; assert.match(ticketCookie, /hwlab_opencode_ticket=/u); assert.match(ticketCookie, /HttpOnly/u); assert.match(ticketCookie, /SameSite=None/u); const second = await fetch(`${serverUrl(cloudWeb)}/_opencode/assets/index.js`, { headers: { accept: "text/plain", cookie: ticketCookie.split(";")[0] } }); assert.equal(second.status, 200); assert.equal(await second.text(), "opencode ok\n"); assert.deepEqual(cloudApiCookies, ["hwlab_session=session-a", "hwlab_session=session-a", "hwlab_session=session-a"]); assert.deepEqual(opencodeRequests, [ { url: "/", authorization: `Basic ${Buffer.from("oc_user:oc_password", "utf8").toString("base64")}`, cookie: undefined }, { url: "/assets/index.js", authorization: `Basic ${Buffer.from("oc_user:oc_password", "utf8").toString("base64")}`, cookie: undefined } ]); } finally { restoreEnv(); await close(cloudWeb); await close(opencode); await close(cloudApi); await rm(root, { recursive: true, force: true }); } }); function withEnv(values) { const previous = new Map(); for (const [key, value] of Object.entries(values)) { previous.set(key, process.env[key]); process.env[key] = value; } return () => { for (const [key, value] of previous.entries()) { if (value === undefined) delete process.env[key]; else process.env[key] = value; } }; } function listen(server) { return new Promise((resolve, reject) => { server.once("error", reject); server.listen(0, "127.0.0.1", resolve); }); } async function unusedPort() { const server = createServer(); await listen(server); const port = server.address().port; await close(server); return port; } function canConnect(port) { return new Promise((resolve) => { const socket = connect(port, "127.0.0.1"); socket.once("connect", () => { socket.destroy(); resolve(true); }); socket.once("error", () => resolve(false)); }); } function close(server) { return new Promise((resolve, reject) => { server.close((error) => (error ? reject(error) : resolve())); }); } async function waitFor(predicate, message, timeoutMs = 1000) { const deadline = Date.now() + timeoutMs; while (Date.now() < deadline) { if (predicate()) return; await new Promise((resolve) => setTimeout(resolve, 20)); } assert.fail(message); } function disconnectDuringResponse(baseUrl, pathname) { const url = new URL(pathname, baseUrl); return new Promise((resolve, reject) => { const socket = connect(Number(url.port), url.hostname); const timeout = setTimeout(() => { socket.destroy(); reject(new Error("client disconnect probe timed out")); }, 2000); socket.once("connect", () => { socket.write(`GET ${url.pathname} HTTP/1.1\r\nHost: ${url.host}\r\nConnection: close\r\n\r\n`); }); socket.once("data", () => { clearTimeout(timeout); socket.destroy(); setTimeout(resolve, 50); }); socket.once("error", (error) => { clearTimeout(timeout); reject(error); }); }); } function serverUrl(server) { const address = server.address(); return `http://127.0.0.1:${address.port}`; }