import assert from "node:assert/strict"; import { createServer as createHttpServer } from "node:http"; import { createServer as createTcpServer } from "node:net"; import { chmod, mkdtemp, mkdir, rm, writeFile } from "node:fs/promises"; import os from "node:os"; import path from "node:path"; import test from "node:test"; import { createCloudApiServer } from "./server.mjs"; import { validateCodeAgentChatSchema } from "./code-agent-chat.mjs"; import { createCodexStdioSessionManager } from "./codex-stdio-session.mjs"; import { createCodeAgentTraceStore } from "./code-agent-trace-store.mjs"; import { createCloudRuntimeStore } from "../db/runtime-store.mjs"; import { classifyCodexRunnerCapability } from "../../scripts/src/code-agent-response-contract.mjs"; import { RUNTIME_DURABLE_ADAPTER_AUTH_BLOCKED, RUNTIME_DURABLE_ADAPTER_MIGRATION_BLOCKED, RUNTIME_DURABLE_ADAPTER_QUERY_BLOCKED, RUNTIME_DURABLE_ADAPTER_SCHEMA_BLOCKED, RUNTIME_DURABLE_ADAPTER_SSL_BLOCKED } from "../db/runtime-store.mjs"; import { HWLAB_M3_IO_CAPABILITY_LEVELS } from "../../skills/hwlab-agent-runtime/scripts/src/m3-io-skill-client.mjs"; const CODEX_STDIO_FEASIBILITY_BLOCKERS = new Set([ "codex_cli_binary_missing", "codex_stdio_supervisor_disabled", "codex_stdio_egress_boundary" ]); function assertCodexStdioFeasibilityBlocker(value, label = "codex stdio blocker") { assert.equal(CODEX_STDIO_FEASIBILITY_BLOCKERS.has(value), true, `${label}: ${value}`); } test("cloud api exposes /health, /health/live, and /live probes", async () => { const originalUrl = process.env.HWLAB_CLOUD_DB_URL; const originalSslMode = process.env.HWLAB_CLOUD_DB_SSL_MODE; delete process.env.HWLAB_CLOUD_DB_URL; delete process.env.HWLAB_CLOUD_DB_SSL_MODE; const server = createCloudApiServer({ env: { PATH: "", HWLAB_CODE_AGENT_PROVIDER: "codex-cli", HWLAB_CODE_AGENT_MODEL: "gpt-test", HWLAB_CODE_AGENT_CODEX_COMMAND: "/tmp/hwlab-missing-codex" } }); await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve)); try { const { port } = server.address(); const health = await fetch(`http://127.0.0.1:${port}/health`); assert.equal(health.status, 200); const healthPayload = await health.json(); assert.equal(healthPayload.serviceId, "hwlab-cloud-api"); assert.equal(healthPayload.environment, "dev"); assert.equal(healthPayload.status, "degraded"); assert.equal(healthPayload.commit.id.length > 0, true); assert.equal(healthPayload.image.reference.length > 0, true); assert.equal(typeof healthPayload.revision, "string"); assert.equal(typeof healthPayload.build, "object"); assert.equal(Object.hasOwn(healthPayload.build, "createdAt"), true); assert.equal(healthPayload.service.id, "hwlab-cloud-api"); assert.equal(healthPayload.db.status, "blocked"); assert.equal(healthPayload.db.connected, false); assert.equal(healthPayload.db.liveConnected, false); assert.equal(healthPayload.db.liveDbEvidence, false); assert.equal(healthPayload.db.connectionAttempted, false); assert.equal(healthPayload.db.connectionResult, "not_attempted_missing_env"); assert.equal(healthPayload.db.endpointSource, "secret-url-host"); assert.equal(healthPayload.db.endpoint.authoritative.source, "secret-url-host"); assert.equal(healthPayload.db.endpoint.authoritative.env, "HWLAB_CLOUD_DB_URL"); assert.equal(healthPayload.db.endpoint.authoritative.usedForProbe, true); assert.equal(healthPayload.db.optionalPublicDnsAlias.source, "optional-public-dns-alias"); assert.equal(healthPayload.db.optionalPublicDnsAlias.requiredForReadiness, false); assert.equal(healthPayload.db.optionalPublicDnsAlias.usedForProbe, false); assert.equal(healthPayload.db.redaction.valuesRedacted, true); assert.equal(healthPayload.db.redaction.secretMaterialRead, false); assert.equal(healthPayload.db.safety.liveDbEvidence, false); assert.equal(healthPayload.runtime.durable, false); assert.equal(healthPayload.runtime.status, "degraded"); assert.equal(healthPayload.runtime.durabilityContract.dbLiveEvidenceIsDurabilityEvidence, false); assert.equal(healthPayload.runtime.durabilityContract.blockedLayer, "adapter"); assert.equal(healthPayload.readiness.contractVersion, "v3"); assert.equal(healthPayload.readiness.durability.status, "blocked"); assert.equal(healthPayload.readiness.durability.dbLiveEvidenceObserved, false); assert.equal(healthPayload.readiness.durability.dbLiveEvidenceIsDurabilityEvidence, false); assert.equal(healthPayload.readiness.durability.blockedLayer, "adapter"); assert.equal(healthPayload.readiness.provider.status, "blocked"); assert.equal(healthPayload.readiness.provider.ready, false); assert.equal(healthPayload.readiness.provider.blocker, "provider_config_blocked"); assert.equal(healthPayload.readiness.dbDurable.status, "blocked"); assert.equal(healthPayload.readiness.dbDurable.ready, false); assert.equal(healthPayload.readiness.sessionRunner.status, "blocked"); assert.equal(healthPayload.readiness.sessionRunner.ready, false); assert.equal(healthPayload.readiness.sessionRunner.codexStdio, true); assert.equal(healthPayload.readiness.sessionRunner.durableSession, false); assert.equal(healthPayload.readiness.sessionRunner.longLivedSession, false); assert.equal(healthPayload.readiness.codexStdio.status, "blocked"); assert.equal(healthPayload.readiness.codexStdio.ready, false); assertCodexStdioFeasibilityBlocker(healthPayload.readiness.codexStdio.blocker); assert.equal(healthPayload.readiness.codeAgent.providerReady, false); assert.equal(healthPayload.readiness.codeAgent.durableDbReady, false); assert.equal(healthPayload.readiness.codeAgent.sessionRunnerReady, false); assert.equal(healthPayload.readiness.codeAgent.codexStdioFeasible, false); assert.deepEqual(healthPayload.readiness.codeAgent.currentBlockers.slice(0, 2), [ "provider_config_blocked", "runtime_durable_adapter_missing" ]); assertCodexStdioFeasibilityBlocker(healthPayload.readiness.codeAgent.currentBlockers[2], "readiness current blocker"); assert.equal(healthPayload.readiness.codeAgent.secretMaterialRead, false); assert.equal(healthPayload.codeAgent.status, "blocked"); assert.equal(healthPayload.codeAgent.agentKind, "codex-stdio-blocked"); assert.equal(healthPayload.codeAgent.partialReady, false); assert.match(healthPayload.codeAgent.blocker, /Codex CLI command/u); assert.equal(healthPayload.codeAgent.reason, "codex_cli_binary_missing"); assert.equal(healthPayload.codeAgent.runner.kind, "codex-mcp-stdio-runner"); assert.equal(healthPayload.codeAgent.runner.ready, false); assert.equal(healthPayload.codeAgent.capabilityLevel, "blocked"); assert.equal(healthPayload.codeAgent.sessionRegistry.status, "available"); assert.equal(healthPayload.codeAgent.longLivedSessionGate.status, "blocked"); assert.ok(healthPayload.codeAgent.longLivedSessionGate.blockers.some((blocker) => blocker.code === "codex_cli_binary_missing")); assert.ok(healthPayload.codeAgent.longLivedSessionGate.blockers.some((blocker) => blocker.code === "provider_token_boundary")); assert.equal(healthPayload.codeAgent.codexStdio.runtimeContract.binary.status, "missing"); assert.equal(healthPayload.codeAgent.codexStdio.runtimeContract.stdioProtocol.status, "blocked"); assert.equal(healthPayload.codeAgent.codexStdio.runtimeContract.lifecycleSupervisor.status, "present"); assert.equal(healthPayload.codeAgent.codexStdio.runtimeContract.cancelReapTraceReadiness.status, "ready"); assert.ok(healthPayload.codeAgent.codexStdio.blockerCodes.includes("codex_cli_binary_missing")); assert.ok(healthPayload.codeAgent.codexStdio.blockerCodes.includes("stdio_protocol_not_wired")); assert.ok(healthPayload.codeAgent.codexStdio.blockerCodes.includes("provider_token_boundary")); assert.deepEqual(healthPayload.codeAgent.missingEnv, ["OPENAI_API_KEY"]); assert.equal(healthPayload.codeAgent.secretRefs[0].secretName, "hwlab-code-agent-provider"); assert.equal(healthPayload.codeAgent.secretRefs[0].secretKey, "openai-api-key"); assert.equal(healthPayload.codeAgent.secretRefs[0].redacted, true); assert.equal(healthPayload.codeAgent.egress.directPublicOpenAi, false); assert.equal(healthPayload.codeAgent.safety.secretMaterialRead, false); assert.equal(JSON.stringify(healthPayload.codeAgent).includes("sk-"), false); assert.deepEqual(healthPayload.db.missingEnv, ["HWLAB_CLOUD_DB_URL", "HWLAB_CLOUD_DB_SSL_MODE"]); assert.equal(healthPayload.db.secretRefs[0].secretName, "hwlab-cloud-api-dev-db"); assert.equal(healthPayload.db.secretRefs[0].redacted, true); const healthLive = await fetch(`http://127.0.0.1:${port}/health/live`); assert.equal(healthLive.status, 200); const healthLivePayload = await healthLive.json(); assert.equal(healthLivePayload.serviceId, "hwlab-cloud-api"); assert.equal(healthLivePayload.status, "degraded"); assert.equal(healthLivePayload.commit.id.length > 0, true); assert.equal(typeof healthLivePayload.build, "object"); assert.equal(healthLivePayload.db.ready, false); assert.equal(healthLivePayload.codeAgent.status, "blocked"); assert.equal(healthLivePayload.codeAgent.ready, false); assert.equal(healthLivePayload.codeAgent.partialReady, false); assert.equal(healthLivePayload.codeAgent.capabilityLevel, "blocked"); const readiness = await fetch(`http://127.0.0.1:${port}/health/live`); assert.equal(readiness.status, 200); assert.equal((await readiness.json()).status, "degraded"); const live = await fetch(`http://127.0.0.1:${port}/live`); assert.equal(live.status, 200); assert.equal((await live.json()).status, "live"); } finally { await new Promise((resolve, reject) => { server.close((error) => (error ? reject(error) : resolve())); }); if (originalUrl === undefined) { delete process.env.HWLAB_CLOUD_DB_URL; } else { process.env.HWLAB_CLOUD_DB_URL = originalUrl; } if (originalSslMode === undefined) { delete process.env.HWLAB_CLOUD_DB_SSL_MODE; } else { process.env.HWLAB_CLOUD_DB_SSL_MODE = originalSslMode; } } }); test("cloud api aggregates live HWLAB build times from health and controlled deploy/catalog metadata", async () => { const healthByPath = new Map([ ["/hwlab-cloud-web/health/live", { serviceId: "hwlab-cloud-web", status: "ok", revision: "webabcdef123456", commit: { id: "webabcdef123456", source: "test" }, image: { reference: "127.0.0.1:5000/hwlab/hwlab-cloud-web:webabcd", tag: "webabcd", digest: "sha256:" + "1".repeat(64) }, build: { createdAt: "2026-05-23T02:10:00.000Z", metadataSource: "runtime-env:HWLAB_BUILD_CREATED_AT" } }], ["/hwlab-agent-mgr/health/live", { serviceId: "hwlab-agent-mgr", status: "ok", revision: "48dfbf9", commit: { id: "48dfbf9", source: "test" }, image: { reference: "127.0.0.1:5000/hwlab/hwlab-agent-mgr:48dfbf9", tag: "48dfbf9", digest: "sha256:" + "2".repeat(64) } }], ["/hwlab-agent-skills/health/live", { serviceId: "hwlab-agent-skills", status: "ok", revision: "skillsabcdef123456", commit: { id: "skillsabcdef123456", source: "test" }, image: { reference: "127.0.0.1:5000/hwlab/hwlab-agent-skills:skillsabcd", tag: "skillsabcd", digest: "sha256:" + "5".repeat(64) } }], ["/external/health", { serviceId: "postgres", status: "ok", image: { reference: "postgres:16", tag: "16" } }] ]); const liveBuildMetadata = { deployManifest: { services: [ { serviceId: "hwlab-cloud-web", image: "127.0.0.1:5000/hwlab/hwlab-cloud-web:webcat", namespace: "hwlab-dev", profile: "dev", healthPath: "/health/live", replicas: 1 }, { serviceId: "hwlab-agent-mgr", image: "127.0.0.1:5000/hwlab/hwlab-agent-mgr:3df89fe", namespace: "hwlab-dev", profile: "dev", healthPath: "/health/live", replicas: 1 }, { serviceId: "hwlab-agent-skills", image: "127.0.0.1:5000/hwlab/hwlab-agent-skills:skillsabcd", namespace: "hwlab-dev", profile: "dev", healthPath: "/health/live", replicas: 1, env: { HWLAB_COMMIT_ID: "skillsabcdef123456", HWLAB_IMAGE: "127.0.0.1:5000/hwlab/hwlab-agent-skills:skillsabcd", HWLAB_IMAGE_TAG: "skillsabcd", HWLAB_BUILD_CREATED_AT: "2026-05-23T05:30:00.000Z", HWLAB_BUILD_SOURCE: "deploy-env-test" } }, { serviceId: "hwlab-agent-worker", image: "127.0.0.1:5000/hwlab/hwlab-agent-worker:workcat", namespace: "hwlab-dev", profile: "dev", healthPath: "/health/live", replicas: 0 }, { serviceId: "hwlab-edge-proxy", image: "127.0.0.1:5000/hwlab/hwlab-edge-proxy:edgecat", namespace: "hwlab-dev", profile: "dev", healthPath: "/health", replicas: 1 }, { serviceId: "hwlab-extra-lab", image: "127.0.0.1:5000/hwlab/hwlab-extra-lab:extracat", namespace: "hwlab-dev", profile: "dev", healthPath: "/health/live", replicas: 1 } ] }, artifactCatalog: { services: [ { serviceId: "hwlab-agent-mgr", commitId: "3df89fe", image: "127.0.0.1:5000/hwlab/hwlab-agent-mgr:3df89fe", imageTag: "3df89fe", digest: "sha256:" + "3".repeat(64), namespace: "hwlab-dev", profile: "dev", healthPath: "/health/live", buildCreatedAt: "2026-05-23T04:20:00.000Z", buildSource: "artifact-catalog-test" }, { serviceId: "hwlab-agent-skills", commitId: "skillsabcdef123456", image: "127.0.0.1:5000/hwlab/hwlab-agent-skills:skillsabcd", imageTag: "skillsabcd", digest: "sha256:" + "5".repeat(64), namespace: "hwlab-dev", profile: "dev", healthPath: "/health/live", buildCreatedAt: null, buildSource: null }, { serviceId: "hwlab-extra-lab", commitId: "extracatabcdef123456", image: "127.0.0.1:5000/hwlab/hwlab-extra-lab:extracat", imageTag: "extracat", digest: "sha256:" + "4".repeat(64), namespace: "hwlab-dev", profile: "dev", healthPath: "/health/live", buildCreatedAt: "2026-05-23T03:00:00.000Z", buildSource: "artifact-catalog-test" } ] } }; const server = createCloudApiServer({ env: { PATH: "", HWLAB_COMMIT_ID: "apiabcdef123456", HWLAB_IMAGE: "127.0.0.1:5000/hwlab/hwlab-cloud-api:apiabcd", HWLAB_IMAGE_TAG: "apiabcd", HWLAB_BUILD_CREATED_AT: "2026-05-23T01:00:00.000Z", HWLAB_CLOUD_WEB_SERVICE_URL: "http://live.test/hwlab-cloud-web", HWLAB_AGENT_MGR_URL: "http://live.test/hwlab-agent-mgr", HWLAB_AGENT_WORKER_URL: "http://live.test/missing-agent-worker", HWLAB_GATEWAY_URL: "http://live.test/missing-gateway", HWLAB_GATEWAY_SIMU_URL: "http://live.test/missing-gateway-simu", HWLAB_BOX_SIMU_URL: "http://live.test/missing-box-simu", HWLAB_PATCH_PANEL_URL: "http://live.test/missing-patch-panel", HWLAB_ROUTER_URL: "http://live.test/missing-router", HWLAB_TUNNEL_CLIENT_URL: "http://live.test/missing-tunnel-client", HWLAB_EDGE_PROXY_URL: "http://live.test/external", HWLAB_CLI_URL: "http://live.test/missing-cli", HWLAB_AGENT_SKILLS_URL: "http://live.test/hwlab-agent-skills", HWLAB_EXTRA_LAB_URL: "http://live.test/missing-extra-lab" }, liveBuildMetadata, fetchImpl: async (url) => { const path = new URL(url).pathname; const payload = healthByPath.get(path); if (!payload) { return { ok: false, status: 503, async text() { return JSON.stringify({ error: "offline" }); } }; } return { ok: true, status: 200, async text() { return JSON.stringify(payload); } }; } }); await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve)); try { const { port } = server.address(); const response = await fetch(`http://127.0.0.1:${port}/v1/live-builds`); assert.equal(response.status, 200); const payload = await response.json(); assert.equal(payload.contractVersion, "live-builds-v1"); assert.equal(payload.latest.serviceId, "hwlab-agent-skills"); assert.equal(payload.latest.build.createdAt, "2026-05-23T05:30:00.000Z"); assert.equal(payload.latest.image.tag, "skillsabcd"); assert.equal(payload.latest.commit.id, "skillsabcdef123456"); assert.equal(payload.latest.build.metadataSource, "deploy-env:HWLAB_BUILD_CREATED_AT"); assert.equal(payload.latest.build.liveMetadataMatch.metadata.imageTag, "skillsabcd"); assert.match(payload.latest.build.liveHealthMissingReason, /匹配的 deploy metadata/u); assert.equal(payload.counts.total, 15); assert.equal(payload.counts.external, 2); assert.equal(payload.counts.withBuildTime, 3); const manager = payload.services.find((service) => service.serviceId === "hwlab-agent-mgr"); assert.equal(manager.build.createdAt, null); assert.equal(manager.image.tag, "48dfbf9"); assert.equal(manager.revision, "48dfbf9"); assert.equal(manager.build.metadataSource, "live-health:controlled-metadata-mismatch"); assert.equal(manager.build.unavailableReason, "构建时间不可用:live tag 与 catalog metadata 不匹配"); assert.match(manager.build.unavailableDetail, /live tag 48dfbf9/u); assert.match(manager.build.unavailableDetail, /metadata tag 3df89fe/u); const extra = payload.services.find((service) => service.serviceId === "hwlab-extra-lab"); assert.equal(extra.build.createdAt, null); assert.match(extra.build.unavailableReason, /当前 live health 不可用/u); const worker = payload.services.find((service) => service.serviceId === "hwlab-agent-worker"); assert.equal(worker.build.createdAt, null); assert.match(worker.build.unavailableReason, /构建时间不可用:当前 live health 不可用/u); assert.match(worker.build.unavailableReason, /hwlab-agent-worker 当前 hwlab-dev desired replicas=0/u); assert.ok(payload.services.some((service) => service.kind === "external" && service.serviceId === "hwlab-edge-proxy" && service.healthUrl.endsWith("/health"))); assert.ok(payload.services.some((service) => service.kind === "external" && service.serviceId === "hwlab-frpc" && service.image.tag === "v0.68.1")); } finally { await new Promise((resolve, reject) => { server.close((error) => (error ? reject(error) : resolve())); }); } }); test("cloud api ignores old repository reports and keeps missing buildCreatedAt unavailable", async () => { const tempRoot = await mkdtemp(path.join(os.tmpdir(), "hwlab-live-builds-")); const deployDir = path.join(tempRoot, "deploy"); const oldReportDir = path.join(tempRoot, "reports", "dev-gate"); await mkdir(deployDir, { recursive: true }); await mkdir(oldReportDir, { recursive: true }); await writeFile(path.join(deployDir, "deploy.json"), `${JSON.stringify({ services: [ { serviceId: "hwlab-agent-mgr", image: "127.0.0.1:5000/hwlab/hwlab-agent-mgr:48dfbf9", namespace: "hwlab-dev", profile: "dev", healthPath: "/health/live", replicas: 1 } ] }, null, 2)}\n`); await writeFile(path.join(deployDir, "artifact-catalog.dev.json"), `${JSON.stringify({ services: [ { serviceId: "hwlab-agent-mgr", commitId: "48dfbf9abcdef", image: "127.0.0.1:5000/hwlab/hwlab-agent-mgr:48dfbf9", imageTag: "48dfbf9", digest: "sha256:" + "6".repeat(64), namespace: "hwlab-dev", profile: "dev", healthPath: "/health/live", buildCreatedAt: null, buildSource: null } ] }, null, 2)}\n`); await writeFile(path.join(oldReportDir, "dev-artifacts.json"), `${JSON.stringify({ artifactPublish: { services: [ { serviceId: "hwlab-agent-mgr", sourceCommitId: "48dfbf9abcdef", image: "127.0.0.1:5000/hwlab/hwlab-agent-mgr:48dfbf9", imageTag: "48dfbf9", digest: "sha256:" + "6".repeat(64), buildCreatedAt: "2099-01-01T00:00:00.000Z", buildSource: "old-report-must-not-be-read" } ] } }, null, 2)}\n`); const observe = async () => { const server = createCloudApiServer({ env: { PATH: "", HWLAB_AGENT_MGR_URL: "http://live.test/hwlab-agent-mgr" }, repoRoot: tempRoot, fetchImpl: async (url) => { if (new URL(url).pathname === "/hwlab-agent-mgr/health/live") { return { ok: true, status: 200, async text() { return JSON.stringify({ serviceId: "hwlab-agent-mgr", status: "ok", revision: "48dfbf9abcdef", commit: { id: "48dfbf9abcdef", source: "test" }, image: { reference: "127.0.0.1:5000/hwlab/hwlab-agent-mgr:48dfbf9", tag: "48dfbf9", digest: "sha256:" + "6".repeat(64) } }); } }; } return { ok: false, status: 503, async text() { return JSON.stringify({ error: "offline" }); } }; } }); await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve)); try { const { port } = server.address(); const response = await fetch(`http://127.0.0.1:${port}/v1/live-builds`); assert.equal(response.status, 200); return response.json(); } finally { await new Promise((resolve, reject) => { server.close((error) => (error ? reject(error) : resolve())); }); } }; try { const withOldReport = await observe(); await rm(path.join(tempRoot, "reports"), { recursive: true, force: true }); const withoutOldReport = await observe(); const summarize = (payload) => { const manager = payload.services.find((service) => service.serviceId === "hwlab-agent-mgr"); return { latest: payload.latest, source: payload.source, createdAt: manager.build.createdAt, metadataSource: manager.build.metadataSource, unavailableReason: manager.build.unavailableReason, unavailableDetail: manager.build.unavailableDetail, liveMetadataMatch: manager.build.liveMetadataMatch, counts: payload.counts }; }; assert.deepEqual(summarize(withOldReport), summarize(withoutOldReport)); const manager = withOldReport.services.find((service) => service.serviceId === "hwlab-agent-mgr"); assert.equal(withOldReport.latest, null); assert.equal(manager.build.createdAt, null); assert.equal(manager.build.metadataSource, "live-health:matched-controlled-metadata-without-build-time"); assert.equal(manager.build.unavailableReason, "构建时间不可用:当前 live 镜像未提供 buildCreatedAt"); assert.match(manager.build.unavailableDetail, /catalog metadata/u); assert.equal(Object.hasOwn(withOldReport.source, "artifactReportSource"), false); assert.equal(JSON.stringify(withOldReport).includes("2099-01-01T00:00:00.000Z"), false); assert.equal(JSON.stringify(withOldReport).includes("artifact-report"), false); } finally { await rm(tempRoot, { recursive: true, force: true }); } }); test("cloud api health reports DB env presence and live connection classification without leaking values", async () => { const originalUrl = process.env.HWLAB_CLOUD_DB_URL; const originalSslMode = process.env.HWLAB_CLOUD_DB_SSL_MODE; const originalAliasEnv = { HWLAB_CLOUD_DB_SERVICE_NAME: process.env.HWLAB_CLOUD_DB_SERVICE_NAME, HWLAB_CLOUD_DB_SERVICE_NAMESPACE: process.env.HWLAB_CLOUD_DB_SERVICE_NAMESPACE, HWLAB_CLOUD_DB_HOST: process.env.HWLAB_CLOUD_DB_HOST, HWLAB_CLOUD_DB_PORT: process.env.HWLAB_CLOUD_DB_PORT }; const fakeDb = createTcpServer((socket) => socket.end()); await new Promise((resolve) => fakeDb.listen(0, "127.0.0.1", resolve)); const dbPort = fakeDb.address().port; process.env.HWLAB_CLOUD_DB_URL = `postgres://hwlab_test@127.0.0.1:${dbPort}/hwlab`; process.env.HWLAB_CLOUD_DB_SSL_MODE = "disable"; delete process.env.HWLAB_CLOUD_DB_SERVICE_NAME; delete process.env.HWLAB_CLOUD_DB_SERVICE_NAMESPACE; delete process.env.HWLAB_CLOUD_DB_HOST; delete process.env.HWLAB_CLOUD_DB_PORT; const server = createCloudApiServer(); await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve)); try { const { port } = server.address(); const response = await fetch(`http://127.0.0.1:${port}/health`); const payload = await response.json(); assert.equal(payload.status, "degraded"); assert.equal(payload.db.status, "ready"); assert.equal(payload.db.connected, true); assert.equal(payload.db.liveConnected, true); assert.equal(payload.db.liveDbEvidence, true); assert.equal(payload.db.connectionChecked, true); assert.equal(payload.db.connectionAttempted, true); assert.equal(payload.db.connectionResult, "connected"); assert.equal(payload.db.endpointSource, "secret-url-host"); assert.equal(payload.db.connection.endpointSource, "secret-url-host"); assert.equal(payload.db.endpoint.authoritative.source, "secret-url-host"); assert.equal(payload.db.endpoint.authoritative.env, "HWLAB_CLOUD_DB_URL"); assert.equal(payload.db.optionalPublicDnsAlias.source, "optional-public-dns-alias"); assert.equal(payload.db.optionalPublicDnsAlias.requiredForReadiness, false); assert.equal(payload.db.optionalPublicDnsAlias.usedForProbe, false); assert.equal(payload.db.optionalPublicDnsAlias.envPresent, false); assert.equal(payload.db.configReady, true); assert.equal(payload.db.ready, true); assert.equal(payload.db.evidence, "live_db_tcp_connection_ready"); assert.equal(payload.db.safety.liveDbEvidence, true); assert.equal(payload.db.safety.liveDbConnectedEvidence, true); assert.equal(payload.runtime.adapter, "memory"); assert.equal(payload.runtime.durable, false); assert.equal(payload.runtime.status, "degraded"); assert.match(payload.runtime.reason, /process-local/u); assert.equal(payload.runtime.durabilityContract.blockedLayer, "adapter"); assert.equal(payload.runtime.durabilityContract.dbLiveEvidenceIsDurabilityEvidence, false); assert.equal(payload.readiness.components.db, "ready"); assert.equal(payload.readiness.components.runtime, "blocked"); assert.equal(payload.readiness.durability.status, "blocked"); assert.equal(payload.readiness.durability.dbLiveEvidenceObserved, true); assert.equal(payload.readiness.durability.dbLiveEvidenceIsDurabilityEvidence, false); assert.equal(payload.readiness.durability.blockedLayer, "adapter"); assert.equal(payload.readiness.durability.liveRuntimeEvidence, false); assert.equal(payload.readiness.provider.status, "blocked"); assert.equal(payload.readiness.dbDurable.status, "blocked"); assert.equal(payload.readiness.dbDurable.dbLiveEvidenceObserved, true); assert.equal(payload.readiness.dbDurable.blocker, "runtime_durable_adapter_missing"); assert.equal(payload.readiness.sessionRunner.status, "blocked"); assert.equal(payload.readiness.codexStdio.status, "blocked"); assert.equal(payload.db.redaction.valuesRedacted, true); assert.equal(payload.db.redaction.endpointRedacted, true); assert.equal(payload.db.redaction.secretMaterialRead, false); assert.deepEqual(payload.db.missingEnv, []); assert.equal(JSON.stringify(payload.db).includes("password"), false); assert.equal(JSON.stringify(payload.db).includes("127.0.0.1"), false); assert.equal(JSON.stringify(payload.db).includes(String(dbPort)), false); assert.equal(JSON.stringify(payload.db).includes("cloud-api-db.hwlab-dev.svc.cluster.local"), false); assert.equal(payload.db.fields.find((field) => field.name === "HWLAB_CLOUD_DB_URL").redacted, true); assert.equal(payload.db.connection.endpointRedacted, true); } finally { if (originalUrl === undefined) { delete process.env.HWLAB_CLOUD_DB_URL; } else { process.env.HWLAB_CLOUD_DB_URL = originalUrl; } if (originalSslMode === undefined) { delete process.env.HWLAB_CLOUD_DB_SSL_MODE; } else { process.env.HWLAB_CLOUD_DB_SSL_MODE = originalSslMode; } for (const [name, value] of Object.entries(originalAliasEnv)) { if (value === undefined) { delete process.env[name]; } else { process.env[name] = value; } } await new Promise((resolve, reject) => { server.close((error) => (error ? reject(error) : resolve())); }); await new Promise((resolve, reject) => { fakeDb.close((error) => (error ? reject(error) : resolve())); }); } }); test("cloud api health separates DB connected from durable runtime schema readiness", async () => { const originalUrl = process.env.HWLAB_CLOUD_DB_URL; const originalSslMode = process.env.HWLAB_CLOUD_DB_SSL_MODE; const fakeDb = createTcpServer((socket) => socket.end()); await new Promise((resolve) => fakeDb.listen(0, "127.0.0.1", resolve)); const dbPort = fakeDb.address().port; process.env.HWLAB_CLOUD_DB_URL = `postgres://hwlab_test@127.0.0.1:${dbPort}/hwlab`; process.env.HWLAB_CLOUD_DB_SSL_MODE = "disable"; const server = createCloudApiServer({ runtimeStore: { async readiness() { return { adapter: "postgres", durable: false, durableRequested: true, durableCapable: false, ready: false, status: "blocked", blocker: RUNTIME_DURABLE_ADAPTER_SCHEMA_BLOCKED, reason: "test runtime schema is missing", liveRuntimeEvidence: false, fixtureEvidence: false, connection: { queryAttempted: true, queryResult: "schema_blocked", endpointRedacted: true, valueRedacted: true }, schema: { checked: true, ready: false, missingTables: ["gateway_sessions"], missingColumns: ["gateway_sessions.gateway_session_json"] }, migration: { checked: false, ready: false, missing: true }, counts: {} }; } } }); await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve)); try { const { port } = server.address(); const response = await fetch(`http://127.0.0.1:${port}/health/live`); const payload = await response.json(); assert.equal(response.status, 200); assert.equal(payload.status, "degraded"); assert.equal(payload.db.connected, true); assert.equal(payload.db.liveDbEvidence, true); assert.equal(payload.runtime.durable, false); assert.equal(payload.runtime.durableRequested, true); assert.equal(payload.runtime.ready, false); assert.equal(payload.runtime.blocker, RUNTIME_DURABLE_ADAPTER_SCHEMA_BLOCKED); assert.equal(payload.readiness.components.db, "ready"); assert.equal(payload.readiness.components.runtime, "blocked"); assert.equal(payload.readiness.durability.status, "blocked"); assert.equal(payload.readiness.durability.dbLiveEvidenceObserved, true); assert.equal(payload.readiness.durability.dbLiveEvidenceIsDurabilityEvidence, false); assert.equal(payload.readiness.durability.blockedLayer, "schema"); assert.equal(payload.readiness.durability.queryAttempted, true); assert.equal(payload.readiness.durability.queryResult, "schema_blocked"); assert.ok(payload.blockerCodes.includes(RUNTIME_DURABLE_ADAPTER_SCHEMA_BLOCKED)); assert.equal(JSON.stringify(payload).includes("password"), false); } finally { if (originalUrl === undefined) { delete process.env.HWLAB_CLOUD_DB_URL; } else { process.env.HWLAB_CLOUD_DB_URL = originalUrl; } if (originalSslMode === undefined) { delete process.env.HWLAB_CLOUD_DB_SSL_MODE; } else { process.env.HWLAB_CLOUD_DB_SSL_MODE = originalSslMode; } await new Promise((resolve, reject) => { server.close((error) => (error ? reject(error) : resolve())); }); await new Promise((resolve, reject) => { fakeDb.close((error) => (error ? reject(error) : resolve())); }); } }); test("cloud api health keeps DB live evidence separate when durable adapter query is blocked", async () => { const originalUrl = process.env.HWLAB_CLOUD_DB_URL; const originalSslMode = process.env.HWLAB_CLOUD_DB_SSL_MODE; const fakeDb = createTcpServer((socket) => socket.end()); await new Promise((resolve) => fakeDb.listen(0, "127.0.0.1", resolve)); const dbPort = fakeDb.address().port; process.env.HWLAB_CLOUD_DB_URL = `postgres://hwlab_test:password@127.0.0.1:${dbPort}/hwlab`; process.env.HWLAB_CLOUD_DB_SSL_MODE = "disable"; const server = createCloudApiServer({ runtimeStore: { async readiness() { return { adapter: "postgres", durable: false, durableRequested: true, durableCapable: false, ready: false, status: "blocked", blocker: RUNTIME_DURABLE_ADAPTER_QUERY_BLOCKED, reason: "test runtime durable read query is blocked", liveRuntimeEvidence: false, fixtureEvidence: false, connection: { queryAttempted: true, queryResult: "query_blocked", endpointRedacted: true, valueRedacted: true, errorCode: "57014" }, schema: { checked: true, ready: true, missingTables: [], missingColumns: [] }, migration: { checked: true, ready: true, missing: false }, gates: { auth: { checked: true, ready: true, status: "ready", blocker: null }, schema: { checked: true, ready: true, status: "ready", blocker: null }, migration: { checked: true, ready: true, status: "ready", blocker: null }, durability: { checked: true, ready: false, status: "blocked", blocker: RUNTIME_DURABLE_ADAPTER_QUERY_BLOCKED } }, counts: {} }; } } }); await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve)); try { const { port } = server.address(); const response = await fetch(`http://127.0.0.1:${port}/health/live`); const payload = await response.json(); assert.equal(response.status, 200); assert.equal(payload.status, "degraded"); assert.equal(payload.db.ready, true); assert.equal(payload.db.connected, true); assert.equal(payload.db.liveDbEvidence, true); assert.equal(payload.runtime.durable, false); assert.equal(payload.runtime.ready, false); assert.equal(payload.runtime.blocker, RUNTIME_DURABLE_ADAPTER_QUERY_BLOCKED); assert.equal(payload.readiness.components.db, "ready"); assert.equal(payload.readiness.components.runtime, "blocked"); assert.equal(payload.readiness.durability.status, "blocked"); assert.equal(payload.readiness.durability.dbLiveEvidenceObserved, true); assert.equal(payload.readiness.durability.dbLiveEvidenceIsDurabilityEvidence, false); assert.equal(payload.readiness.durability.blockedLayer, "durability_query"); assert.equal(payload.readiness.durability.queryAttempted, true); assert.equal(payload.readiness.durability.queryResult, "query_blocked"); assert.equal(payload.readiness.durability.liveRuntimeEvidence, false); assert.ok(payload.blockerCodes.includes(RUNTIME_DURABLE_ADAPTER_QUERY_BLOCKED)); assert.equal(payload.readiness.dbDurable.status, "blocked"); assert.equal(payload.readiness.dbDurable.blocker, RUNTIME_DURABLE_ADAPTER_QUERY_BLOCKED); assert.equal(payload.readiness.codeAgent.currentBlockers.includes(RUNTIME_DURABLE_ADAPTER_QUERY_BLOCKED), true); assert.equal(JSON.stringify(payload).includes("password"), false); assert.equal(JSON.stringify(payload).includes("127.0.0.1"), false); assert.equal(JSON.stringify(payload).includes(String(dbPort)), false); } finally { if (originalUrl === undefined) { delete process.env.HWLAB_CLOUD_DB_URL; } else { process.env.HWLAB_CLOUD_DB_URL = originalUrl; } if (originalSslMode === undefined) { delete process.env.HWLAB_CLOUD_DB_SSL_MODE; } else { process.env.HWLAB_CLOUD_DB_SSL_MODE = originalSslMode; } await new Promise((resolve, reject) => { server.close((error) => (error ? reject(error) : resolve())); }); await new Promise((resolve, reject) => { fakeDb.close((error) => (error ? reject(error) : resolve())); }); } }); test("cloud api health contract distinguishes durable SSL, auth, schema, migration, and query blockers", async () => { const originalUrl = process.env.HWLAB_CLOUD_DB_URL; const originalSslMode = process.env.HWLAB_CLOUD_DB_SSL_MODE; process.env.HWLAB_CLOUD_DB_URL = "redacted"; process.env.HWLAB_CLOUD_DB_SSL_MODE = "require"; const cases = [ { blocker: RUNTIME_DURABLE_ADAPTER_SSL_BLOCKED, blockedLayer: "ssl", queryResult: "ssl_negotiation_blocked", gates: { ssl: "blocked", auth: "not_checked", schema: "not_checked", migration: "not_checked", durability: "blocked" } }, { blocker: RUNTIME_DURABLE_ADAPTER_AUTH_BLOCKED, blockedLayer: "auth", queryResult: "auth_blocked", gates: { ssl: "ready", auth: "blocked", schema: "not_checked", migration: "not_checked", durability: "blocked" } }, { blocker: RUNTIME_DURABLE_ADAPTER_SCHEMA_BLOCKED, blockedLayer: "schema", queryResult: "schema_blocked", gates: { ssl: "ready", auth: "ready", schema: "blocked", migration: "not_checked", durability: "blocked" } }, { blocker: RUNTIME_DURABLE_ADAPTER_MIGRATION_BLOCKED, blockedLayer: "migration", queryResult: "migration_blocked", gates: { ssl: "ready", auth: "ready", schema: "ready", migration: "blocked", durability: "blocked" } }, { blocker: RUNTIME_DURABLE_ADAPTER_QUERY_BLOCKED, blockedLayer: "durability_query", queryResult: "query_blocked", gates: { ssl: "ready", auth: "ready", schema: "ready", migration: "ready", durability: "blocked" } } ]; try { for (const testCase of cases) { const server = createCloudApiServer({ dbProbe: { probe: async ({ endpointSource, timeoutMs }) => ({ attempted: true, networkAttempted: true, endpointSource, probeType: "tcp-connect", endpointRedacted: true, valueRedacted: true, timeoutMs, durationMs: 0, result: "connected", classification: "tcp_connected", errorCode: null, missingEnv: [] }) }, runtimeStore: { async readiness() { return durableBlockedRuntime(testCase); } } }); await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve)); try { const { port } = server.address(); const response = await fetch(`http://127.0.0.1:${port}/health/live`); const payload = await response.json(); assert.equal(response.status, 200); assert.equal(payload.status, "degraded"); assert.equal(payload.db.ready, true); assert.equal(payload.db.liveDbEvidence, true); assert.equal(payload.runtime.blocker, testCase.blocker); assert.equal(payload.runtime.connection.queryResult, testCase.queryResult); assert.equal(payload.runtime.gates.ssl.status, testCase.gates.ssl); assert.equal(payload.runtime.gates.auth.status, testCase.gates.auth); assert.equal(payload.runtime.gates.schema.status, testCase.gates.schema); assert.equal(payload.runtime.gates.migration.status, testCase.gates.migration); assert.equal(payload.runtime.gates.durability.status, testCase.gates.durability); assert.equal(payload.runtime.durabilityContract.blockedLayer, testCase.blockedLayer); assert.equal(payload.db.runtimeReadiness.blockedLayer, testCase.blockedLayer); assert.equal(payload.db.runtimeReadiness.queryResult, testCase.queryResult); assert.equal(payload.db.runtimeReadiness.dbLiveEvidenceIsDurabilityEvidence, false); assert.equal(payload.db.readinessLayers.ssl.status, layerStatus(testCase.gates.ssl)); assert.equal(payload.db.readinessLayers.auth.status, layerStatus(testCase.gates.auth)); assert.equal(payload.db.readinessLayers.schema.status, layerStatus(testCase.gates.schema)); assert.equal(payload.db.readinessLayers.migration.status, layerStatus(testCase.gates.migration)); assert.equal(payload.db.readinessLayers.durability.status, layerStatus(testCase.gates.durability)); assert.equal(payload.db.readinessLayers[layerForBlockedCase(testCase.blockedLayer)].result, testCase.queryResult); assert.equal(payload.readiness.durability.blockedLayer, testCase.blockedLayer); assert.equal(payload.readiness.durability.queryResult, testCase.queryResult); assert.equal(payload.readiness.durability.secretMaterialRead, false); assert.ok(payload.blockerCodes.includes(testCase.blocker)); } finally { await new Promise((resolve, reject) => { server.close((error) => (error ? reject(error) : resolve())); }); } } } finally { if (originalUrl === undefined) { delete process.env.HWLAB_CLOUD_DB_URL; } else { process.env.HWLAB_CLOUD_DB_URL = originalUrl; } if (originalSslMode === undefined) { delete process.env.HWLAB_CLOUD_DB_SSL_MODE; } else { process.env.HWLAB_CLOUD_DB_SSL_MODE = originalSslMode; } } }); test("cloud api health does not treat DB env presence-only as live readiness", async () => { const originalUrl = process.env.HWLAB_CLOUD_DB_URL; const originalSslMode = process.env.HWLAB_CLOUD_DB_SSL_MODE; const originalProbeDisabled = process.env.HWLAB_CLOUD_DB_PROBE_DISABLED; process.env.HWLAB_CLOUD_DB_URL = "postgres://user:password@db.example.invalid:5432/hwlab"; process.env.HWLAB_CLOUD_DB_SSL_MODE = "disable"; delete process.env.HWLAB_CLOUD_DB_PROBE_DISABLED; const server = createCloudApiServer(); await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve)); try { const { port } = server.address(); const response = await fetch(`http://127.0.0.1:${port}/health/live`); const payload = await response.json(); assert.equal(response.status, 200); assert.equal(payload.db.configReady, true); assert.equal(payload.db.connected, false); assert.equal(payload.db.liveConnected, false); assert.equal(payload.db.liveDbEvidence, false); assert.equal(payload.db.safety.liveDbEvidence, false); assert.equal(payload.db.ready, false); assert.equal(payload.db.connectionAttempted, true); assert.equal(payload.db.connectionResult, "forbidden_runtime_host"); assert.equal(payload.db.evidence, "live_db_tcp_connection_blocked"); assert.equal(payload.db.endpointSource, "secret-url-host"); assert.equal(payload.db.connection.endpointSource, "secret-url-host"); assert.equal(payload.db.connection.networkAttempted, false); assert.equal(payload.db.connection.classification, "db_url_forbidden_invalid_host"); assert.equal(payload.db.readinessLayers.dns.status, "not_proven"); assert.equal(JSON.stringify(payload.db).includes("password"), false); assert.equal(JSON.stringify(payload.db).includes("db.example.invalid"), false); } finally { if (originalUrl === undefined) { delete process.env.HWLAB_CLOUD_DB_URL; } else { process.env.HWLAB_CLOUD_DB_URL = originalUrl; } if (originalSslMode === undefined) { delete process.env.HWLAB_CLOUD_DB_SSL_MODE; } else { process.env.HWLAB_CLOUD_DB_SSL_MODE = originalSslMode; } if (originalProbeDisabled === undefined) { delete process.env.HWLAB_CLOUD_DB_PROBE_DISABLED; } else { process.env.HWLAB_CLOUD_DB_PROBE_DISABLED = originalProbeDisabled; } await new Promise((resolve, reject) => { server.close((error) => (error ? reject(error) : resolve())); }); } }); function durableBlockedRuntime({ blocker, blockedLayer, queryResult, gates }) { return { adapter: "postgres", durable: false, durableRequested: true, durableCapable: false, ready: false, status: "blocked", blocker, reason: `test durable runtime blocker ${blocker}`, liveRuntimeEvidence: false, fixtureEvidence: false, connection: { queryAttempted: true, queryResult, endpointRedacted: true, valueRedacted: true, errorCode: "TEST_BLOCKER" }, schema: { checked: gates.schema !== "not_checked", ready: gates.schema === "ready", missingTables: gates.schema === "blocked" ? ["gateway_sessions"] : [], missingColumns: gates.schema === "blocked" ? ["gateway_sessions.gateway_session_json"] : [] }, migration: { checked: gates.migration !== "not_checked", ready: gates.migration === "ready", missing: gates.migration !== "ready" }, gates: Object.fromEntries(Object.entries(gates).map(([name, status]) => [ name, { checked: status !== "not_checked", ready: status === "ready", status, blocker: status === "blocked" ? blocker : null } ])), durabilityContract: { ready: false, status: "blocked", requiredEvidence: "runtime_adapter_schema_migration_read_query", dbLiveEvidenceIsDurabilityEvidence: false, liveRuntimeEvidence: false, adapterQueryRequired: true, blockedLayer, blocker, secretMaterialRead: false }, safety: { secretMaterialRead: false, valuesRedacted: true, endpointRedacted: true }, counts: {} }; } function durableReadyRuntime() { return { adapter: "postgres", durable: true, durableRequested: true, durableCapable: true, ready: true, status: "ready", blocker: null, reason: "test durable runtime ready", liveRuntimeEvidence: true, fixtureEvidence: false, connection: { queryAttempted: true, queryResult: "durable_readiness_ready", endpointRedacted: true, valueRedacted: true }, schema: { checked: true, ready: true, missingTables: [], missingColumns: [] }, migration: { checked: true, ready: true, missing: false }, gates: Object.fromEntries(["ssl", "auth", "schema", "migration", "durability"].map((name) => [ name, { checked: true, ready: true, status: "ready", blocker: null } ])), durabilityContract: { ready: true, status: "ready", requiredEvidence: "runtime_adapter_schema_migration_read_query", dbLiveEvidenceIsDurabilityEvidence: false, liveRuntimeEvidence: true, adapterQueryRequired: true, blockedLayer: null, blocker: null, secretMaterialRead: false }, safety: { secretMaterialRead: false, valuesRedacted: true, endpointRedacted: true }, counts: {} }; } function layerStatus(gateStatus) { if (gateStatus === "ready") return "pass"; if (gateStatus === "blocked") return "blocked"; return "not_proven"; } function layerForBlockedCase(blockedLayer) { if (blockedLayer === "durability_query") return "durability"; return blockedLayer; } async function m3ReadinessRequestJson(url, request = {}) { const parsed = new URL(url); const body = request.body ?? {}; if (url.startsWith("http://gateway-1") && parsed.pathname === "/status") { return { ok: true, status: 200, body: m3GatewayStatus({ gatewayId: "gwsimu_1", gatewaySessionId: "gws_gwsimu_1", boxId: "boxsimu_1", resourceId: "res_boxsimu_1" }) }; } if (url.startsWith("http://gateway-1") && parsed.pathname === "/invoke") { return { ok: true, status: 200, body: { accepted: true, operationId: "op_m3_status_do1", traceId: "trc_m3_status_do1", boxId: "boxsimu_1", resourceId: "res_boxsimu_1", port: "DO1", value: false, state: { port: "DO1", direction: "output", value: false, source: "gateway-simu", updatedAt: "2026-05-23T00:00:00.000Z" }, auditId: "aud_m3_status_do1", evidenceId: "evd_m3_status_do1" } }; } if (url.startsWith("http://gateway-2") && parsed.pathname === "/status") { return { ok: true, status: 200, body: m3GatewayStatus({ gatewayId: "gwsimu_2", gatewaySessionId: "gws_gwsimu_2", boxId: "boxsimu_2", resourceId: "res_boxsimu_2" }) }; } if (url.startsWith("http://gateway-2") && parsed.pathname === "/invoke") { const value = request.body?.operationId?.includes("do_write") || request.body?.operationId?.includes("do-write") ? true : false; return { ok: true, status: 200, body: { accepted: true, operationId: "op_m3_status_di1", traceId: "trc_m3_status_di1", boxId: "boxsimu_2", resourceId: "res_boxsimu_2", port: "DI1", value, state: { port: "DI1", direction: "input", value, source: "patch-panel", sourceResourceId: "res_boxsimu_1", sourcePort: "DO1", propagatedBy: "hwlab-patch-panel", updatedAt: "2026-05-23T00:00:00.000Z" }, auditId: "aud_m3_status_di1", evidenceId: "evd_m3_status_di1" } }; } if (url.startsWith("http://patch-panel") && parsed.pathname === "/status") { return { ok: true, status: 200, body: { serviceId: "hwlab-patch-panel", state: "active", activeConnections: [ { fromResourceId: "res_boxsimu_1", fromPort: "DO1", toResourceId: "res_boxsimu_2", toPort: "DI1" } ] } }; } if (url.startsWith("http://patch-panel") && parsed.pathname === "/wiring") { return { ok: true, status: 200, body: { wiringConfigId: "wir_m3_do1_di1", status: "active", connections: [ { from: { resourceId: "res_boxsimu_1", port: "DO1" }, to: { resourceId: "res_boxsimu_2", port: "DI1" }, mode: "exclusive" } ] } }; } if (url.startsWith("http://patch-panel") && parsed.pathname === "/sync/tick") { return { ok: true, status: 200, body: { accepted: true, propagatedBy: "hwlab-patch-panel", routeCount: 1, deliveryCount: 1, routes: [ { accepted: true, deliveryCount: 1, deliveries: [ { resourceId: "res_boxsimu_2", port: "DI1", value: body.signals?.[0]?.value ?? false, sourceResourceId: "res_boxsimu_1", sourcePort: "DO1", deliveryStatus: "applied" } ] } ] } }; } throw new Error(`unexpected M3 readiness request ${url}`); } function m3GatewayStatus({ gatewayId, gatewaySessionId, boxId, resourceId }) { return { serviceId: "hwlab-gateway-simu", gatewayId, gatewaySessionId, session: { gatewayId, gatewaySessionId }, registry: { gatewayId, gatewaySessionId, boxes: [ { boxId, resourceId, state: "registered" } ] } }; } function codexStdioReadyFixture({ workspace, codexHome }) { return { kind: "codex-mcp-stdio-runner", provider: "codex-stdio", backend: "hwlab-cloud-api/codex-mcp-stdio", status: "feasible", ready: true, startupReady: true, canStartLongLivedCodexStdio: true, command: path.join(process.cwd(), "node_modules", ".bin", "codex"), binaryOnPath: true, binary: { present: true, executable: true, nativeDependencyPresent: true, versionDetected: true }, workspace, workspaceState: { exists: true, readable: true, writable: true, writeRequired: true }, codexHome, codexHomeState: { exists: true, readable: true, writable: true, writeRequired: true }, sandbox: "workspace-write", enabled: true, supervisor: { configured: true, mode: "repo-owned-node-supervisor" }, tokenBoundary: { present: true, secretMaterialRead: false, valuesRedacted: true }, egress: { configured: true, directPublicOpenAi: false, valueRedacted: true }, protocol: { status: "wired", wired: true, requiredTools: ["codex", "codex-reply"], toolsObserved: ["codex", "codex-reply"], missingTools: [], command: "codex mcp-server" }, commandProbe: { status: "ready", ready: true, probe: "workspace.pwd", traceId: "trc_codex_stdio_command_probe", toolCalls: [{ name: "pwd", status: "completed", cwd: workspace, command: "pwd", exitCode: 0, stdoutSummary: "workspace path matched", outputTruncated: false }], secretMaterialRead: false, valuesRedacted: true }, stdioProtocol: { status: "wired", wired: true, requiredTools: ["codex", "codex-reply"], toolsObserved: ["codex", "codex-reply"], missingTools: [], command: "codex mcp-server" }, sessionLifecycle: { status: "present", present: true, create: true, reuse: true, cancel: true, reap: true, traceCapture: true, idleTimeoutMs: 1800000 }, lifecycleSupervisor: { status: "present", present: true, create: true, reuse: true, cancel: true, reap: true, traceCapture: true, idleTimeoutMs: 1800000 }, runtimeContract: { status: "ready", ready: true, binary: { status: "present", present: true, executable: true, nativeDependencyPresent: true, versionDetected: true }, stdioProtocol: { status: "wired", wired: true, requiredTools: ["codex", "codex-reply"], toolsObserved: ["codex", "codex-reply"], missingTools: [] }, commandProbe: { status: "ready", ready: true, probe: "workspace.pwd", traceId: "trc_codex_stdio_command_probe", toolCalls: [{ name: "pwd", status: "completed", cwd: workspace, command: "pwd", exitCode: 0, stdoutSummary: "workspace path matched", outputTruncated: false }], secretMaterialRead: false, valuesRedacted: true }, lifecycleSupervisor: { status: "present", present: true, create: true, reuse: true, cancel: true, reap: true, traceCapture: true, idleTimeoutMs: 1800000 }, workspaceMount: { path: workspace, status: "ready", mounted: true, readable: true, writable: true, sandbox: "workspace-write" }, codexHome: { path: codexHome, status: "ready", exists: true, readable: true, writable: true }, cancelReapTraceReadiness: { status: "ready", cancel: true, reap: true, traceCapture: true, idleTimeout: true } }, blockers: [], blockerCodes: [], safety: { secretMaterialRead: false, valuesRedacted: true } }; } function codexStdioChatFixture({ workspace, codexHome, params }) { const traceId = params.traceId; const session = { sessionId: "ses_server_stdio_pwd", conversationId: params.conversationId, status: "idle", workspace, sandbox: "workspace-write", runnerKind: "codex-mcp-stdio-runner", sessionMode: "codex-mcp-stdio-long-lived", capabilityLevel: "long-lived-codex-stdio-session", implementationType: "repo-owned-codex-mcp-stdio-session", createdAt: "2026-05-23T00:00:00.000Z", updatedAt: "2026-05-23T00:00:00.000Z", idleTimeoutMs: 1800000, expiresAt: "2026-05-23T00:30:00.000Z", lastTraceId: traceId, turn: 1, threadId: "thread_server_stdio_pwd", reused: false, durable: true, longLivedSession: true, codexStdio: true, writeCapable: true, secretMaterialStored: false, valuesRedacted: true }; const feasibility = codexStdioReadyFixture({ workspace, codexHome }); return { provider: "codex-stdio", model: "gpt-test", backend: "hwlab-cloud-api/codex-mcp-stdio", content: `stdio pwd\n${workspace}`, workspace, sandbox: "workspace-write", session, sessionMode: "codex-mcp-stdio-long-lived", sessionReuse: { conversationId: params.conversationId, sessionId: session.sessionId, threadId: session.threadId, mapped: true, reused: false, turn: 1, previousTurns: 0, workspace, status: "idle", idleTimeoutMs: 1800000 }, implementationType: "repo-owned-codex-mcp-stdio-session", runnerLimitations: ["hardware-control-via-cloud-api-only", "secret-values-redacted"], codexStdioFeasibility: feasibility, longLivedSessionGate: { status: "pass", pass: true, provider: "codex-stdio", runnerKind: "codex-mcp-stdio-runner", sessionMode: "codex-mcp-stdio-long-lived", implementationType: "repo-owned-codex-mcp-stdio-session", blockers: [] }, toolCalls: [{ id: "tool_server_stdio_pwd", type: "workspace-read", name: "pwd", status: "completed", cwd: workspace, command: "pwd", exitCode: 0, stdout: workspace, stderrSummary: "", outputTruncated: false, traceId }], skills: { status: "not_requested", items: [], count: 0, blockers: [] }, runner: { kind: "codex-mcp-stdio-runner", provider: "codex-stdio", backend: "hwlab-cloud-api/codex-mcp-stdio", workspace, sandbox: "workspace-write", session: "codex-mcp-stdio-long-lived", sessionMode: "codex-mcp-stdio-long-lived", sessionId: session.sessionId, implementationType: "repo-owned-codex-mcp-stdio-session", codexStdio: true, longLivedSession: true, durableSession: true, writeCapable: true, readOnly: false, capabilityLevel: "long-lived-codex-stdio-session" }, runnerTrace: { traceId, runnerKind: "codex-mcp-stdio-runner", workspace, sandbox: "workspace-write", sessionMode: "codex-mcp-stdio-long-lived", sessionId: session.sessionId, sessionStatus: "idle", turn: 1, implementationType: "repo-owned-codex-mcp-stdio-session", events: ["stdio:acquire", "session:created", "stdio:ready", "tool:pwd:completed"], outputTruncated: false, valuesPrinted: false }, capabilityLevel: "long-lived-codex-stdio-session", providerTrace: { transport: "stdio", protocol: "mcp", command: "codex mcp-server", toolName: "codex", threadId: "thread_server_stdio_pwd", valuesPrinted: false } }; } test("cloud api /v1 describes Code Agent provider blocker without leaking secret values", async () => { const server = createCloudApiServer({ env: { PATH: "", HWLAB_CODE_AGENT_PROVIDER: "codex-stdio", HWLAB_CODE_AGENT_MODEL: "gpt-test", HWLAB_CODE_AGENT_CODEX_COMMAND: "/tmp/hwlab-missing-codex" } }); await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve)); try { const { port } = server.address(); const response = await fetch(`http://127.0.0.1:${port}/v1`); assert.equal(response.status, 200); const payload = await response.json(); assert.equal(payload.codeAgent.endpoint, "POST /v1/agent/chat"); assert.equal(payload.codeAgent.provider, "codex-stdio"); assert.equal(payload.codeAgent.model, "gpt-test"); assert.equal(payload.codeAgent.backend, "hwlab-cloud-api/codex-mcp-stdio"); assert.equal(payload.codeAgent.mode, "codex-stdio"); assert.equal(payload.codeAgent.status, "blocked"); assert.match(payload.codeAgent.blocker, /Codex CLI command/u); assert.equal(payload.codeAgent.reason, "codex_cli_binary_missing"); assert.equal(payload.codeAgent.runner.kind, "codex-mcp-stdio-runner"); assert.equal(payload.codeAgent.runner.ready, false); assert.equal(payload.codeAgent.capabilityLevel, "blocked"); assert.equal(payload.codeAgent.sessionRegistry.status, "available"); assert.equal(payload.codeAgent.longLivedSessionGate.status, "blocked"); assert.deepEqual(payload.codeAgent.missingEnv, []); assert.equal(payload.codeAgent.secretRefs[0].env, "OPENAI_API_KEY"); assert.equal(payload.codeAgent.secretRefs[0].secretName, "hwlab-code-agent-provider"); assert.equal(payload.codeAgent.secretRefs[0].secretKey, "openai-api-key"); assert.equal(payload.codeAgent.secretRefs[0].redacted, true); assert.equal(payload.codeAgent.ready, false); assert.equal(payload.codeAgent.partialReady, false); assert.equal(payload.codeAgent.egress.present, false); assert.equal(payload.codeAgent.egress.valueRedacted, true); assert.equal(payload.codeAgent.safety.secretMaterialRead, false); assert.match(payload.codeAgent.summary, /will not use controlled-readonly-session-registry/u); assert.match(payload.codeAgent.summary, /Codex stdio long-lived session is blocked/u); assert.equal(JSON.stringify(payload).includes("sk-"), false); } finally { await new Promise((resolve, reject) => { server.close((error) => (error ? reject(error) : resolve())); }); } }); test("cloud api /v1 describes Code Agent egress blocker without leaking API key", async () => { const server = createCloudApiServer({ env: { OPENAI_API_KEY: "test-openai-key-material", HWLAB_CODE_AGENT_PROVIDER: "codex-stdio", HWLAB_CODE_AGENT_MODEL: "gpt-test", HWLAB_CODE_AGENT_OPENAI_BASE_URL: "https://api.openai.com/v1/responses" } }); await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve)); try { const { port } = server.address(); const response = await fetch(`http://127.0.0.1:${port}/v1`); assert.equal(response.status, 200); const payload = await response.json(); assert.equal(payload.codeAgent.status, "blocked"); assert.equal(payload.codeAgent.egress.directPublicOpenAi, true); assert.match(payload.codeAgent.blocker, /Codex stdio|long-lived|Codex CLI command/u); assert.equal(payload.codeAgent.runner.ready, false); assert.equal(payload.codeAgent.safety.secretMaterialRead, false); assert.equal(JSON.stringify(payload).includes("test-openai-key-material"), false); } finally { await new Promise((resolve, reject) => { server.close((error) => (error ? reject(error) : resolve())); }); } }); test("cloud api health reports provider, durable DB, and codex stdio ready when runtime contract passes", async () => { const workspace = await mkdtemp(path.join(os.tmpdir(), "hwlab-health-codex-stdio-")); const codexHome = path.join(workspace, "codex-home"); const fakeCodex = await createFakeCodexCommand(); await mkdir(codexHome, { recursive: true }); const manager = createCodexStdioSessionManager({ createRpcClient: async () => ({ async initialize() { return { tools: ["codex", "codex-reply"] }; }, async listTools() { return ["codex", "codex-reply"]; }, close() {} }) }); const server = createCloudApiServer({ env: { PATH: process.env.PATH, OPENAI_API_KEY: "test-openai-key-material", CODEX_HOME: codexHome, HWLAB_CODE_AGENT_PROVIDER: "codex-stdio", HWLAB_CODE_AGENT_MODEL: "gpt-test", HWLAB_CODE_AGENT_CODEX_COMMAND: fakeCodex.command, HWLAB_CODE_AGENT_CODEX_STDIO_ENABLED: "1", HWLAB_CODE_AGENT_CODEX_STDIO_SUPERVISOR: "repo-owned", HWLAB_CODE_AGENT_CODEX_WORKSPACE: workspace, HWLAB_CODE_AGENT_WORKSPACE: workspace, HWLAB_CODE_AGENT_OPENAI_BASE_URL: "http://127.0.0.1:17680/v1/responses", HWLAB_CLOUD_DB_URL: "postgres://hwlab_test:password@db.internal.local:5432/hwlab", HWLAB_CLOUD_DB_SSL_MODE: "disable" }, dbProbe: { probe: async ({ endpointSource, timeoutMs }) => ({ attempted: true, networkAttempted: true, endpointSource, probeType: "tcp-connect", endpointRedacted: true, valueRedacted: true, timeoutMs, durationMs: 1, result: "connected", classification: "tcp_connected", errorCode: null, missingEnv: [] }) }, runtimeStore: { async readiness() { return durableReadyRuntime(); } }, codexStdioManager: manager }); await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve)); try { const { port } = server.address(); const response = await fetch(`http://127.0.0.1:${port}/health/live`); assert.equal(response.status, 200); const payload = await response.json(); assert.equal(payload.status, "ok"); assert.equal(payload.db.ready, true); assert.equal(payload.db.liveDbEvidence, true); assert.equal(payload.runtime.ready, true); assert.equal(payload.runtime.durable, true); assert.equal(payload.readiness.provider.status, "ready"); assert.equal(payload.readiness.provider.ready, true); assert.equal(payload.readiness.provider.blocker, null); assert.equal(payload.readiness.dbDurable.status, "ready"); assert.equal(payload.readiness.dbDurable.ready, true); assert.equal(payload.readiness.sessionRunner.status, "codex_stdio_ready"); assert.equal(payload.readiness.sessionRunner.ready, true); assert.equal(payload.readiness.sessionRunner.codexStdio, true); assert.equal(payload.readiness.sessionRunner.writeCapable, true); assert.equal(payload.readiness.codexStdio.status, "ready"); assert.equal(payload.readiness.codexStdio.ready, true); assert.deepEqual(payload.readiness.codeAgent.currentBlockers, []); assert.equal(payload.readiness.codeAgent.providerReady, true); assert.equal(payload.readiness.codeAgent.durableDbReady, true); assert.equal(payload.readiness.codeAgent.sessionRunnerReady, true); assert.equal(payload.readiness.codeAgent.codexStdioFeasible, true); assert.equal(payload.codeAgent.ready, true); assert.equal(payload.codeAgent.status, "codex-stdio-feasible"); assert.equal(payload.codeAgent.longLivedSessionGate.status, "pass"); assert.equal(payload.codeAgent.codexStdio.runtimeContract.binary.status, "present"); assert.deepEqual(payload.codeAgent.codexStdio.protocol.toolsObserved, ["codex", "codex-reply"]); assert.equal(payload.codeAgent.codexStdio.commandProbe.ready, true); assert.equal(payload.codeAgent.codexStdio.runtimeContract.commandProbe.ready, true); assert.equal(payload.readiness.codexStdio.commandProbeReady, true); assert.equal(JSON.stringify(payload).includes("provider_unavailable"), false); assert.equal(JSON.stringify(payload).includes("dns_resolution_failed"), false); assert.equal(JSON.stringify(payload).includes("runtime_durable_adapter_auth_blocked"), false); assert.equal(JSON.stringify(payload).includes("test-openai-key-material"), false); assert.equal(JSON.stringify(payload).includes("password"), false); } finally { await new Promise((resolve, reject) => { server.close((error) => (error ? reject(error) : resolve())); }); await rm(workspace, { recursive: true, force: true }); await rm(fakeCodex.root, { recursive: true, force: true }); } }); test("cloud api health blocks full Code Agent readiness when Codex stdio command probe fails", async () => { const workspace = await mkdtemp(path.join(os.tmpdir(), "hwlab-health-codex-stdio-probe-fail-")); const codexHome = path.join(workspace, "codex-home"); const workspaceFile = path.join(workspace, "workspace-file"); const fakeCodex = await createFakeCodexCommand(); await mkdir(codexHome, { recursive: true }); await writeFile(workspaceFile, "not a directory\n", "utf8"); const manager = createCodexStdioSessionManager({ createRpcClient: async () => ({ async initialize() { return { tools: ["codex", "codex-reply"] }; }, async listTools() { return ["codex", "codex-reply"]; }, close() {} }) }); const server = createCloudApiServer({ env: { PATH: process.env.PATH, OPENAI_API_KEY: "test-openai-key-material", CODEX_HOME: codexHome, HWLAB_CODE_AGENT_PROVIDER: "codex-stdio", HWLAB_CODE_AGENT_MODEL: "gpt-test", HWLAB_CODE_AGENT_CODEX_COMMAND: fakeCodex.command, HWLAB_CODE_AGENT_CODEX_STDIO_ENABLED: "1", HWLAB_CODE_AGENT_CODEX_STDIO_SUPERVISOR: "repo-owned", HWLAB_CODE_AGENT_CODEX_WORKSPACE: workspaceFile, HWLAB_CODE_AGENT_WORKSPACE: workspaceFile, HWLAB_CODE_AGENT_OPENAI_BASE_URL: "http://127.0.0.1:17680/v1/responses", HWLAB_CLOUD_DB_URL: "postgres://hwlab_test:password@db.internal.local:5432/hwlab", HWLAB_CLOUD_DB_SSL_MODE: "disable" }, dbProbe: { probe: async ({ endpointSource, timeoutMs }) => ({ attempted: true, networkAttempted: true, endpointSource, probeType: "tcp-connect", endpointRedacted: true, valueRedacted: true, timeoutMs, durationMs: 1, result: "connected", classification: "tcp_connected", errorCode: null, missingEnv: [] }) }, runtimeStore: { async readiness() { return durableReadyRuntime(); } }, codexStdioManager: manager }); await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve)); try { const { port } = server.address(); const response = await fetch(`http://127.0.0.1:${port}/health/live`); assert.equal(response.status, 200); const payload = await response.json(); assert.equal(payload.status, "degraded"); assert.equal(payload.ready, false); assert.equal(payload.readiness.components.codeAgent, "blocked"); assert.equal(payload.readiness.codexStdio.status, "blocked"); assert.equal(payload.readiness.codexStdio.ready, false); assert.equal(payload.readiness.codexStdio.commandProbeReady, false); assert.ok(payload.readiness.codexStdio.blockerCodes.includes("codex_stdio_command_probe_failed")); assert.ok(payload.blockerCodes.includes("codex_stdio_command_probe_failed")); assert.equal(payload.codeAgent.ready, false); assert.equal(payload.codeAgent.codexStdio.commandProbe.ready, false); assert.equal(payload.codeAgent.codexStdio.commandProbe.status, "blocked"); assert.equal(JSON.stringify(payload).includes("test-openai-key-material"), false); assert.equal(JSON.stringify(payload).includes("password"), false); } finally { await new Promise((resolve, reject) => { server.close((error) => (error ? reject(error) : resolve())); }); await rm(workspace, { recursive: true, force: true }); await rm(fakeCodex.root, { recursive: true, force: true }); } }); test("cloud api /v1/agent/chat refuses provider stub when Codex stdio is unavailable", async () => { let providerCalled = false; const server = createCloudApiServer({ callCodeAgentProvider: async () => { providerCalled = true; throw new Error("provider stub must not be used"); } }); await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve)); try { const { port } = server.address(); const response = await fetch(`http://127.0.0.1:${port}/v1/agent/chat`, { method: "POST", headers: { "content-type": "application/json", "x-trace-id": "trc_server-test-agent-chat" }, body: JSON.stringify({ conversationId: "cnv_server-test-agent-chat", message: "请用一句话说明当前 HWLAB 工作台可以做什么。" }) }); assert.equal(response.status, 200); const payload = await response.json(); assert.equal(payload.conversationId, "cnv_server-test-agent-chat"); assert.equal(payload.sessionId, "cnv_server-test-agent-chat"); assert.match(payload.messageId, /^msg_/); assert.equal(payload.status, "failed"); assert.equal(payload.traceId, "trc_server-test-agent-chat"); assert.equal(payload.provider, "codex-stdio"); assert.equal(payload.model.length > 0, true); assert.equal(payload.backend, "hwlab-cloud-api/codex-mcp-stdio"); assert.equal(Number.isNaN(Date.parse(payload.createdAt)), false); assert.equal(Number.isNaN(Date.parse(payload.updatedAt)), false); assert.equal(payload.capabilityLevel, "blocked"); assert.equal(payload.error.layer, "runner"); assert.equal(Object.hasOwn(payload, "reply"), false); assert.equal(providerCalled, false); } finally { await new Promise((resolve, reject) => { server.close((error) => (error ? reject(error) : resolve())); }); } }); test("OpenAI fallback text chat is not used when Codex stdio is unavailable", async () => { let providerCalled = false; const server = createCloudApiServer({ env: { PATH: process.env.PATH, OPENAI_API_KEY: "test-openai-key-material", HWLAB_CODE_AGENT_PROVIDER: "openai", HWLAB_CODE_AGENT_MODEL: "gpt-test", HWLAB_CODE_AGENT_OPENAI_BASE_URL: "http://127.0.0.1:65535/v1/responses" }, callCodeAgentProvider: async () => { providerCalled = true; throw new Error("OpenAI fallback must not be used"); } }); await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve)); try { const { port } = server.address(); const payload = await postAgent(port, { conversationId: "cnv_server_openai_fallback_not_runner", traceId: "trc_server_openai_fallback_not_runner", message: "请用一句话说明当前 HWLAB 工作台可以做什么。" }); validateCodeAgentChatSchema(payload); assert.equal(payload.status, "failed"); assert.equal(payload.provider, "codex-stdio"); assert.equal(payload.backend, "hwlab-cloud-api/codex-mcp-stdio"); assert.equal(payload.capabilityLevel, "blocked"); assert.equal(payload.runner.kind, "codex-mcp-stdio-runner"); assert.equal(payload.runner.codexStdio, true); assert.equal(payload.longLivedSessionGate.status, "blocked"); assert.equal(Object.hasOwn(payload, "reply"), false); assert.equal(providerCalled, false); const capability = classifyCodexRunnerCapability(payload, { httpStatus: 200 }); assert.equal(capability.capabilityPass, false); assert.equal(JSON.stringify(payload).includes("test-openai-key-material"), false); } finally { await new Promise((resolve, reject) => { server.close((error) => (error ? reject(error) : resolve())); }); } }); test("cloud api health does not pass long-lived session gate for explicit OpenAI fallback even if Codex stdio is feasible", async () => { const workspace = await mkdtemp(path.join(os.tmpdir(), "hwlab-agent-openai-health-")); const codexHome = await mkdtemp(path.join(os.tmpdir(), "hwlab-agent-openai-health-codex-home-")); const server = createCloudApiServer({ env: { PATH: process.env.PATH, CODEX_HOME: codexHome, HWLAB_CODE_AGENT_CODEX_WORKSPACE: workspace, HWLAB_CODE_AGENT_PROVIDER: "openai", HWLAB_CODE_AGENT_MODEL: "gpt-test", OPENAI_API_KEY: "test-openai-key-material", HWLAB_CODE_AGENT_OPENAI_BASE_URL: "http://127.0.0.1:65535/v1/responses" }, codexStdioManager: { describe() { return codexStdioReadyFixture({ workspace, codexHome }); }, async probe() { return codexStdioReadyFixture({ workspace, codexHome }); }, async chat(params = {}) { return codexStdioChatFixture({ workspace, codexHome, params }); }, cancel() {}, reapIdle() {} } }); await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve)); try { const { port } = server.address(); const response = await fetch(`http://127.0.0.1:${port}/health/live`); assert.equal(response.status, 200); const payload = await response.json(); assert.equal(payload.codeAgent.provider, "openai-responses"); assert.equal(payload.codeAgent.mode, "openai"); assert.equal(payload.codeAgent.ready, false); assert.equal(payload.codeAgent.capabilityLevel, "blocked"); assert.equal(payload.codeAgent.runner.kind, "codex-mcp-stdio-runner"); assert.equal(payload.codeAgent.runner.codexStdio, true); assert.equal(payload.codeAgent.longLivedSessionGate.status, "blocked"); assert.equal(payload.readiness.sessionRunner.status, "codex_stdio_ready"); assert.equal(payload.readiness.codeAgent.ready, false); assert.equal(payload.readiness.codeAgent.provider.provider, "openai-responses"); assert.equal(payload.readiness.codeAgent.sessionRunner.codexStdio, true); assert.equal(JSON.stringify(payload.codeAgent).includes("test-openai-key-material"), false); } finally { await new Promise((resolve, reject) => { server.close((error) => (error ? reject(error) : resolve())); }); await rm(workspace, { recursive: true, force: true }); await rm(codexHome, { recursive: true, force: true }); } }); test("cloud api /v1/agent/chat parse and params errors use structured blocker envelope", async () => { const server = createCloudApiServer({ env: { HWLAB_CODE_AGENT_MODEL: "gpt-test" } }); await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve)); try { const { port } = server.address(); const parse = await postAgentRaw(port, "{", { "x-trace-id": "trc_parse_error" }); assert.equal(parse.status, 400); assert.equal(parse.body.status, "failed"); assert.equal(parse.body.error.code, "parse_error"); assert.equal(parse.body.error.layer, "api"); assert.equal(parse.body.error.retryable, true); assert.equal(parse.body.error.traceId, "trc_parse_error"); assert.equal(parse.body.error.route, "/v1/agent/chat"); assert.match(parse.body.error.userMessage, /JSON/u); assert.equal(Object.hasOwn(parse.body, "reply"), false); assert.equal(JSON.stringify(parse.body).includes("sk-"), false); const invalid = await postAgentRaw(port, JSON.stringify([]), { "x-trace-id": "trc_invalid_params" }); assert.equal(invalid.status, 400); assert.equal(invalid.body.error.code, "invalid_params"); assert.equal(invalid.body.error.layer, "api"); assert.equal(invalid.body.error.retryable, true); assert.equal(invalid.body.error.blocker.code, "invalid_params"); } finally { await new Promise((resolve, reject) => { server.close((error) => (error ? reject(error) : resolve())); }); } }); test("cloud api /v1/agent/chat runs Codex stdio pwd with session and workspace evidence", async () => { const workspace = await mkdtemp(path.join(os.tmpdir(), "hwlab-agent-workspace-")); const codexHome = await mkdtemp(path.join(os.tmpdir(), "hwlab-agent-codex-home-")); const server = createCloudApiServer({ env: { PATH: process.env.PATH, CODEX_HOME: codexHome, HWLAB_CODE_AGENT_CODEX_WORKSPACE: workspace, HWLAB_CODE_AGENT_PROVIDER: "codex-stdio", HWLAB_CODE_AGENT_MODEL: "gpt-test", OPENAI_API_KEY: "test-openai-key-material", HWLAB_CODE_AGENT_OPENAI_BASE_URL: "http://127.0.0.1:65535/v1/responses" }, codexStdioManager: { describe() { return codexStdioReadyFixture({ workspace, codexHome }); }, async probe() { return codexStdioReadyFixture({ workspace, codexHome }); }, async chat(params = {}) { return codexStdioChatFixture({ workspace, codexHome, params }); }, cancel() {}, reapIdle() {} } }); await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve)); try { const { port } = server.address(); const response = await fetch(`http://127.0.0.1:${port}/v1/agent/chat`, { method: "POST", headers: { "content-type": "application/json", "x-trace-id": "trc_server-test-runner-pwd" }, body: JSON.stringify({ conversationId: "cnv_server-test-runner-pwd", message: "用pwd列出你当前的工作目录" }) }); assert.equal(response.status, 200); const payload = await response.json(); validateCodeAgentChatSchema(payload); assert.equal(payload.status, "completed"); assert.equal(payload.provider, "codex-stdio"); assert.equal(payload.backend, "hwlab-cloud-api/codex-mcp-stdio"); assert.equal(payload.capabilityLevel, "long-lived-codex-stdio-session"); assert.equal(payload.workspace, workspace); assert.equal(payload.sandbox, "workspace-write"); assert.equal(payload.session.status, "idle"); assert.equal(payload.session.workspace, workspace); assert.equal(payload.session.sandbox, "workspace-write"); assert.equal(payload.session.lastTraceId, "trc_server-test-runner-pwd"); assert.equal(typeof payload.session.idleTimeoutMs, "number"); assert.equal(payload.runner.kind, "codex-mcp-stdio-runner"); assert.equal(payload.runner.longLivedSession, true); assert.equal(payload.runner.codexStdio, true); assert.equal(payload.runner.writeCapable, true); assert.equal(payload.runner.durableSession, true); assert.equal(payload.runner.sessionMode, "codex-mcp-stdio-long-lived"); assert.equal(payload.sessionMode, "codex-mcp-stdio-long-lived"); assert.equal(payload.implementationType, "repo-owned-codex-mcp-stdio-session"); assert.equal(payload.sessionReuse.reused, false); assert.equal(payload.sessionReuse.turn, 1); assert.equal(payload.sessionReuse.status, "idle"); assert.ok(payload.runnerLimitations.includes("hardware-control-via-cloud-api-only")); assert.equal(payload.codexStdioFeasibility.ready, true); assert.equal(payload.codexStdioFeasibility.runtimeContract.stdioProtocol.status, "wired"); assert.equal(payload.codexStdioFeasibility.runtimeContract.lifecycleSupervisor.status, "present"); assert.equal(payload.longLivedSessionGate.status, "pass"); assert.equal(payload.longLivedSessionGate.pass, true); assert.equal(payload.toolCalls[0].name, "pwd"); assert.equal(payload.toolCalls[0].status, "completed"); assert.equal(payload.toolCalls[0].stdout, workspace); assert.equal(payload.skills.status, "not_requested"); assert.equal(payload.runnerTrace.runnerKind, "codex-mcp-stdio-runner"); assert.equal(payload.runnerTrace.sessionMode, "codex-mcp-stdio-long-lived"); assert.match(payload.reply.content, new RegExp(workspace.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&"))); assert.equal(JSON.stringify(payload).includes("test-openai-key-material"), false); } finally { await new Promise((resolve, reject) => { server.close((error) => (error ? reject(error) : resolve())); }); } }); test("cloud api health reports Codex stdio runner facts without readonly limitations", async () => { const workspace = await mkdtemp(path.join(os.tmpdir(), "hwlab-agent-health-stdio-")); const codexHome = await mkdtemp(path.join(os.tmpdir(), "hwlab-agent-health-codex-home-")); const server = createCloudApiServer({ env: { PATH: process.env.PATH, CODEX_HOME: codexHome, HWLAB_CODE_AGENT_CODEX_WORKSPACE: workspace, HWLAB_CODE_AGENT_PROVIDER: "codex-stdio", HWLAB_CODE_AGENT_MODEL: "gpt-test", OPENAI_API_KEY: "test-openai-key-material", HWLAB_CODE_AGENT_OPENAI_BASE_URL: "http://127.0.0.1:65535/v1/responses" }, codexStdioManager: { describe() { return codexStdioReadyFixture({ workspace, codexHome }); }, async probe() { return codexStdioReadyFixture({ workspace, codexHome }); }, async chat(params = {}) { return codexStdioChatFixture({ workspace, codexHome, params }); }, cancel() {}, reapIdle() {} } }); await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve)); try { const { port } = server.address(); const response = await fetch(`http://127.0.0.1:${port}/health/live`); assert.equal(response.status, 200); const payload = await response.json(); assert.equal(payload.codeAgent.status, "codex-stdio-feasible"); assert.equal(payload.codeAgent.ready, true); assert.equal(payload.codeAgent.provider, "codex-stdio"); assert.equal(payload.codeAgent.runner.kind, "codex-mcp-stdio-runner"); assert.equal(payload.codeAgent.runner.sessionMode, "codex-mcp-stdio-long-lived"); assert.equal(payload.codeAgent.runner.codexStdio, true); assert.equal(payload.codeAgent.runner.writeCapable, true); assert.equal(payload.codeAgent.runner.durableSession, true); assert.equal(payload.codeAgent.workspace, workspace); assert.equal(payload.codeAgent.sandbox, "workspace-write"); assert.equal(payload.codeAgent.sessionMode, "codex-mcp-stdio-long-lived"); assert.equal(payload.codeAgent.sessionRegistry.kind, "codex-stdio-session-registry"); assert.deepEqual(payload.codeAgent.sessionRegistry.statuses, ["creating", "ready", "busy", "idle", "timeout", "error", "canceled", "interrupted", "expired", "failed"]); assert.equal(payload.codeAgent.capabilityLevel, "long-lived-codex-stdio-session"); assert.equal(payload.codeAgent.longLivedSessionGate.status, "pass"); assert.equal(payload.readiness.sessionRunner.status, "codex_stdio_ready"); assert.equal(payload.readiness.sessionRunner.kind, "codex-mcp-stdio-runner"); assert.equal(payload.readiness.sessionRunner.codexStdio, true); assert.equal(payload.readiness.sessionRunner.writeCapable, true); assert.equal(payload.readiness.sessionRunner.capabilityLevel, "long-lived-codex-stdio-session"); assert.equal(payload.readiness.codeAgent.sessionRunner.codexStdio, true); assert.equal(payload.readiness.codeAgent.sessionRunner.writeCapable, true); assert.deepEqual(payload.codeAgent.runnerLimitations, [ "hardware-control-via-cloud-api-only", "no-direct-gateway-link", "no-direct-box-simu-link", "no-direct-patch-panel-link", "secret-values-redacted" ]); const serialized = JSON.stringify(payload.codeAgent); assert.equal(serialized.includes("not-codex-stdio"), false); assert.equal(serialized.includes("not-write-capable"), false); assert.equal(serialized.includes("process-local-session-registry"), false); assert.equal(serialized.includes("test-openai-key-material"), false); } finally { await new Promise((resolve, reject) => { server.close((error) => (error ? reject(error) : resolve())); }); await rm(workspace, { recursive: true, force: true }); await rm(codexHome, { recursive: true, force: true }); } }); test("cloud api /v1/agent/chat sends pwd prompt through real Codex stdio instead of local sidecar", async () => { const workspace = await mkdtemp(path.join(os.tmpdir(), "hwlab-agent-stdio-pwd-")); const codexHome = path.join(workspace, "codex-home"); const fakeCodex = await createFakeCodexCommand(); await mkdir(codexHome, { recursive: true }); const codexToolCalls = []; const manager = createCodexStdioSessionManager({ idFactory: () => "ses_server_stdio_pwd_real", createRpcClient: async () => ({ async initialize() { return { tools: ["codex", "codex-reply"] }; }, async listTools() { return ["codex", "codex-reply"]; }, async callTool(name, args) { codexToolCalls.push({ name, args }); return { structuredContent: { threadId: "thread_server_stdio_pwd_real", content: `真实 Codex stdio 回复:当前工作目录是 ${workspace}` } }; }, close() {} }) }); const server = createCloudApiServer({ env: { PATH: process.env.PATH, OPENAI_API_KEY: "test-openai-key-material", CODEX_HOME: codexHome, HWLAB_CODE_AGENT_PROVIDER: "codex-stdio", HWLAB_CODE_AGENT_MODEL: "gpt-test", HWLAB_CODE_AGENT_CODEX_COMMAND: fakeCodex.command, HWLAB_CODE_AGENT_CODEX_STDIO_ENABLED: "1", HWLAB_CODE_AGENT_CODEX_STDIO_SUPERVISOR: "repo-owned", HWLAB_CODE_AGENT_CODEX_WORKSPACE: workspace, HWLAB_CODE_AGENT_OPENAI_BASE_URL: "http://127.0.0.1:65535/v1/responses" }, codexStdioManager: manager, callCodeAgentProvider: async () => { throw new Error("OpenAI fallback must not handle Codex stdio pwd"); } }); await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve)); try { const { port } = server.address(); const payload = await postAgent(port, { conversationId: "cnv_server_stdio_pwd_sidecar", traceId: "trc_server_stdio_pwd_sidecar", message: "用pwd列出你当前的工作目录" }); validateCodeAgentChatSchema(payload); assert.equal(payload.status, "completed"); assert.equal(payload.provider, "codex-stdio"); assert.equal(payload.backend, "hwlab-cloud-api/codex-mcp-stdio"); assert.equal(payload.runner.kind, "codex-mcp-stdio-runner"); assert.equal(payload.capabilityLevel, "long-lived-codex-stdio-session"); assert.equal(payload.workspace, workspace); assert.equal(payload.sessionMode, "codex-mcp-stdio-long-lived"); assert.equal(payload.longLivedSessionGate.status, "pass"); assert.equal(payload.providerTrace.sidecarOnly, undefined); assert.equal(payload.providerTrace.toolName, "codex"); assert.equal(codexToolCalls.length, 1); assert.equal(codexToolCalls[0].name, "codex"); assert.match(codexToolCalls[0].args.prompt, /用pwd列出/u); assert.ok(payload.toolCalls.some((toolCall) => toolCall.name === "pwd" && toolCall.status === "completed" && toolCall.stdout === workspace)); assert.match(payload.reply.content, /真实 Codex stdio 回复/u); assert.ok(payload.runnerTrace.events.some((event) => event.label === "prompt:sent")); assert.ok(payload.runnerTrace.events.some((event) => event.label === "tool:codex:started")); assert.ok(payload.runnerTrace.events.some((event) => event.label === "assistant:completed")); assert.equal(JSON.stringify(payload).includes("openai-responses-fallback"), false); assert.equal(JSON.stringify(payload).includes("text-chat-only"), false); assert.equal(JSON.stringify(payload).includes("test-openai-key-material"), false); } finally { await new Promise((resolve, reject) => { server.close((error) => (error ? reject(error) : resolve())); }); await rm(workspace, { recursive: true, force: true }); await rm(fakeCodex.root, { recursive: true, force: true }); } }); test("cloud api /v1/agent/chat answers skills prompt through real Codex stdio and retains trace", async () => { const workspace = await mkdtemp(path.join(os.tmpdir(), "hwlab-agent-stdio-skills-")); const codexHome = path.join(workspace, "codex-home"); const skillsDir = path.join(workspace, "skills"); const fakeCodex = await createFakeCodexCommand(); await mkdir(path.join(skillsDir, "hwlab-test-skill"), { recursive: true }); await mkdir(codexHome, { recursive: true }); await writeFile(path.join(skillsDir, "hwlab-test-skill", "SKILL.md"), [ "---", "name: hwlab-test-skill", "description: Test skill mounted for Codex stdio discovery.", "version: v-test", "---", "", "# HWLAB Test Skill" ].join("\n")); const server = createCloudApiServer({ env: { PATH: process.env.PATH, OPENAI_API_KEY: "test-openai-key-material", CODEX_HOME: codexHome, HWLAB_CODE_AGENT_PROVIDER: "codex-stdio", HWLAB_CODE_AGENT_MODEL: "gpt-test", HWLAB_CODE_AGENT_CODEX_COMMAND: fakeCodex.command, HWLAB_CODE_AGENT_CODEX_STDIO_ENABLED: "1", HWLAB_CODE_AGENT_CODEX_STDIO_SUPERVISOR: "repo-owned", HWLAB_CODE_AGENT_CODEX_WORKSPACE: workspace, HWLAB_CODE_AGENT_SKILLS_DIRS: skillsDir, HWLAB_CODE_AGENT_SKILLS_STRICT: "1", HWLAB_CODE_AGENT_OPENAI_BASE_URL: "http://127.0.0.1:65535/v1/responses" }, codexStdioManager: createCodexStdioSessionManager({ idFactory: () => "ses_server_stdio_skills", createRpcClient: async () => ({ async initialize() { return { tools: ["codex", "codex-reply"] }; }, async listTools() { return ["codex", "codex-reply"]; }, async callTool(name, args) { return { structuredContent: { threadId: "thread_server_stdio_skills", content: `真实 Codex stdio skill 回复:${args.prompt.includes("skills") ? "hwlab-test-skill" : "unknown"}` } }; }, close() {} }) }) }); await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve)); try { const { port } = server.address(); const payload = await postAgent(port, { conversationId: "cnv_server_stdio_skills", traceId: "trc_server_stdio_skills", message: "列出你可用的skills" }); validateCodeAgentChatSchema(payload); assert.equal(payload.status, "completed"); assert.equal(payload.provider, "codex-stdio"); assert.equal(payload.backend, "hwlab-cloud-api/codex-mcp-stdio"); assert.equal(payload.capabilityLevel, "long-lived-codex-stdio-session"); assert.equal(payload.providerTrace.sidecarOnly, undefined); assert.equal(payload.providerTrace.toolName, "codex"); assert.equal(payload.skills.status, "ready"); assert.ok(payload.skills.items.some((skill) => skill.name === "hwlab-test-skill")); assert.ok(payload.toolCalls.some((toolCall) => toolCall.name === "skills.discover" && toolCall.status === "completed")); assert.match(payload.reply.content, /hwlab-test-skill/u); assert.ok(payload.runnerTrace.events.some((event) => event.label === "prompt:sent")); assert.ok(payload.runnerTrace.events.some((event) => event.label === "tool:codex:started")); assert.equal(JSON.stringify(payload).includes("test-openai-key-material"), false); } finally { await new Promise((resolve, reject) => { server.close((error) => (error ? reject(error) : resolve())); }); await rm(workspace, { recursive: true, force: true }); await rm(fakeCodex.root, { recursive: true, force: true }); } }); test("cloud api /v1/agent/chat handles GitHub access through Codex stdio controlled network check", async () => { const workspace = await mkdtemp(path.join(os.tmpdir(), "hwlab-agent-stdio-network-")); const codexHome = path.join(workspace, "codex-home"); const fakeCodex = await createFakeCodexCommand(); await mkdir(codexHome, { recursive: true }); let codexToolCallCount = 0; const fetchCalls = []; const server = createCloudApiServer({ env: { PATH: process.env.PATH, OPENAI_API_KEY: "test-openai-key-material", CODEX_HOME: codexHome, HWLAB_CODE_AGENT_PROVIDER: "codex-stdio", HWLAB_CODE_AGENT_MODEL: "gpt-test", HWLAB_CODE_AGENT_CODEX_COMMAND: fakeCodex.command, HWLAB_CODE_AGENT_CODEX_STDIO_ENABLED: "1", HWLAB_CODE_AGENT_CODEX_STDIO_SUPERVISOR: "repo-owned", HWLAB_CODE_AGENT_CODEX_WORKSPACE: workspace, HWLAB_CODE_AGENT_OPENAI_BASE_URL: "http://127.0.0.1:65535/v1/responses" }, codexStdioManager: createCodexStdioSessionManager({ idFactory: () => "ses_server_stdio_network", lookupImpl: async () => [{ address: "140.82.114.4", family: 4 }], fetchImpl: async (url, init) => { fetchCalls.push({ url: String(url), method: init?.method }); return { status: 200, statusText: "OK", url: "https://github.com/" }; }, createRpcClient: async () => ({ async initialize() { return { tools: ["codex", "codex-reply"] }; }, async listTools() { return ["codex", "codex-reply"]; }, async callTool() { codexToolCallCount += 1; throw new Error("external network prompt should use the controlled network check, not a long model call"); }, close() {} }) }), callCodeAgentProvider: async () => { throw new Error("OpenAI fallback must not handle external network prompts"); } }); await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve)); try { const { port } = server.address(); const payload = await postAgent(port, { conversationId: "cnv_server_stdio_network", traceId: "trc_server_stdio_network", message: "访问一下github看看" }); validateCodeAgentChatSchema(payload); assert.equal(payload.status, "completed"); assert.equal(payload.provider, "codex-stdio"); assert.equal(payload.backend, "hwlab-cloud-api/codex-mcp-stdio"); assert.equal(payload.capabilityLevel, "long-lived-codex-stdio-session"); assert.equal(payload.sessionMode, "codex-mcp-stdio-long-lived"); assert.equal(payload.runner.kind, "codex-mcp-stdio-runner"); assert.equal(payload.providerTrace.transport, "stdio+controlled-network"); assert.equal(payload.providerTrace.toolName, "external.network.check"); assert.equal(payload.providerTrace.httpStatus, 200); assert.equal(codexToolCallCount, 0); assert.equal(fetchCalls.length, 1); assert.equal(fetchCalls[0].url, "https://github.com/"); assert.equal(fetchCalls[0].method, "HEAD"); assert.ok(payload.toolCalls.some((toolCall) => toolCall.name === "external.network.check" && toolCall.status === "completed" && toolCall.httpStatus === 200 )); assert.match(payload.reply.content, /GitHub可以访问/u); assert.ok(payload.runnerTrace.events.some((event) => event.label === "session:created")); assert.ok(payload.runnerTrace.events.some((event) => event.label === "prompt:sent")); assert.ok(payload.runnerTrace.events.some((event) => event.label === "network:started")); assert.ok(payload.runnerTrace.events.some((event) => event.label === "network:completed")); assert.ok(payload.runnerTrace.events.some((event) => event.label === "tool:external.network.check:completed")); assert.equal(JSON.stringify(payload).includes("openai-responses-fallback"), false); assert.equal(JSON.stringify(payload).includes("test-openai-key-material"), false); } finally { await new Promise((resolve, reject) => { server.close((error) => (error ? reject(error) : resolve())); }); await rm(workspace, { recursive: true, force: true }); await rm(fakeCodex.root, { recursive: true, force: true }); } }); test("cloud api /v1/agent/chat returns structured external network blocker before 150s", async () => { const workspace = await mkdtemp(path.join(os.tmpdir(), "hwlab-agent-stdio-network-blocked-")); const codexHome = path.join(workspace, "codex-home"); const fakeCodex = await createFakeCodexCommand(); await mkdir(codexHome, { recursive: true }); let fetchCalled = false; const server = createCloudApiServer({ env: { PATH: process.env.PATH, OPENAI_API_KEY: "test-openai-key-material", CODEX_HOME: codexHome, HWLAB_CODE_AGENT_PROVIDER: "codex-stdio", HWLAB_CODE_AGENT_MODEL: "gpt-test", HWLAB_CODE_AGENT_CODEX_COMMAND: fakeCodex.command, HWLAB_CODE_AGENT_CODEX_STDIO_ENABLED: "1", HWLAB_CODE_AGENT_CODEX_STDIO_SUPERVISOR: "repo-owned", HWLAB_CODE_AGENT_CODEX_WORKSPACE: workspace, HWLAB_CODE_AGENT_EXTERNAL_NETWORK: "0", HWLAB_CODE_AGENT_OPENAI_BASE_URL: "http://127.0.0.1:65535/v1/responses" }, codexStdioManager: createCodexStdioSessionManager({ idFactory: () => "ses_server_stdio_network_blocked", lookupImpl: async () => [{ address: "140.82.114.4", family: 4 }], fetchImpl: async () => { fetchCalled = true; throw new Error("fetch must not run when policy blocks external network"); }, createRpcClient: async () => ({ async initialize() { return { tools: ["codex", "codex-reply"] }; }, async listTools() { return ["codex", "codex-reply"]; }, async callTool() { throw new Error("fallback model call must not handle blocked external network prompt"); }, close() {} }) }), callCodeAgentProvider: async () => { throw new Error("OpenAI fallback must not handle blocked external network prompts"); } }); await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve)); try { const { port } = server.address(); const started = Date.now(); const payload = await postAgent(port, { conversationId: "cnv_server_stdio_network_blocked", traceId: "trc_server_stdio_network_blocked", message: "看看 github 是否能访问" }); const elapsed = Date.now() - started; validateCodeAgentChatSchema(payload); assert.equal(payload.status, "failed"); assert.equal(payload.provider, "codex-stdio"); assert.equal(payload.backend, "hwlab-cloud-api/codex-mcp-stdio"); assert.equal(payload.error.code, "external_network_blocked"); assert.equal(payload.blocker.code, "external_network_blocked"); assert.equal(payload.blocker.traceId, "trc_server_stdio_network_blocked"); assert.equal(payload.blocker.sessionId, "ses_server_stdio_network_blocked"); assert.equal(payload.blocker.conversationId, "cnv_server_stdio_network_blocked"); assert.equal(payload.blocker.stage, "policy"); assert.equal(typeof payload.blocker.elapsedMs, "number"); assert.equal(typeof payload.blocker.lastEvent, "object"); assert.equal(payload.toolCalls[0].name, "external.network.check"); assert.equal(payload.toolCalls[0].status, "blocked"); assert.equal(payload.toolCalls[0].blocker.code, "external_network_blocked"); assert.ok(payload.runnerTrace.events.some((event) => event.label === "prompt:sent")); assert.ok(payload.runnerTrace.events.some((event) => event.label === "network:started")); assert.ok(payload.runnerTrace.events.some((event) => event.label === "network:blocked")); assert.ok(payload.runnerTrace.events.some((event) => event.label === "tool:external.network.check:blocked")); assert.equal(fetchCalled, false); assert.equal(elapsed < 150000, true); assert.equal(JSON.stringify(payload).includes("openai-responses-fallback"), false); assert.equal(JSON.stringify(payload).includes("test-openai-key-material"), false); } finally { await new Promise((resolve, reject) => { server.close((error) => (error ? reject(error) : resolve())); }); await rm(workspace, { recursive: true, force: true }); await rm(fakeCodex.root, { recursive: true, force: true }); } }); test("cloud api /v1/agent/chat returns network_timeout with preserved runner trace", async () => { const workspace = await mkdtemp(path.join(os.tmpdir(), "hwlab-agent-stdio-network-timeout-")); const codexHome = path.join(workspace, "codex-home"); const fakeCodex = await createFakeCodexCommand(); await mkdir(codexHome, { recursive: true }); const server = createCloudApiServer({ env: { PATH: process.env.PATH, OPENAI_API_KEY: "test-openai-key-material", CODEX_HOME: codexHome, HWLAB_CODE_AGENT_PROVIDER: "codex-stdio", HWLAB_CODE_AGENT_MODEL: "gpt-test", HWLAB_CODE_AGENT_CODEX_COMMAND: fakeCodex.command, HWLAB_CODE_AGENT_CODEX_STDIO_ENABLED: "1", HWLAB_CODE_AGENT_CODEX_STDIO_SUPERVISOR: "repo-owned", HWLAB_CODE_AGENT_CODEX_WORKSPACE: workspace, HWLAB_CODE_AGENT_CODEX_SANDBOX: "workspace-write", HWLAB_CODE_AGENT_EXTERNAL_NETWORK_TIMEOUT_MS: "25", HWLAB_CODE_AGENT_OPENAI_BASE_URL: "http://127.0.0.1:65535/v1/responses" }, codexStdioManager: createCodexStdioSessionManager({ idFactory: () => "ses_server_stdio_network_timeout", lookupImpl: async () => [{ address: "140.82.114.4", family: 4 }], fetchImpl: async (_url, init) => new Promise((resolve, reject) => { init?.signal?.addEventListener("abort", () => { const error = new Error("aborted"); error.name = "AbortError"; reject(error); }); }), createRpcClient: async () => ({ async initialize() { return { tools: ["codex", "codex-reply"] }; }, async listTools() { return ["codex", "codex-reply"]; }, async callTool() { throw new Error("fallback model call must not handle timed out external network prompt"); }, close() {} }) }) }); await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve)); try { const { port } = server.address(); const started = Date.now(); const payload = await postAgent(port, { conversationId: "cnv_server_stdio_network_timeout", traceId: "trc_server_stdio_network_timeout", message: "打开 https://github.com 看看" }); const elapsed = Date.now() - started; validateCodeAgentChatSchema(payload); assert.equal(payload.status, "timeout"); assert.equal(payload.error.code, "network_timeout"); assert.equal(payload.blocker.code, "network_timeout"); assert.equal(payload.blocker.traceId, "trc_server_stdio_network_timeout"); assert.equal(payload.blocker.sessionId, "ses_server_stdio_network_timeout"); assert.equal(payload.blocker.stage, "http-timeout"); assert.equal(payload.toolCalls[0].status, "blocked"); assert.equal(payload.toolCalls[0].blocker.code, "network_timeout"); assert.ok(payload.runnerTrace.events.some((event) => event.label === "network:timeout")); assert.ok(payload.runnerTrace.events.some((event) => event.label === "tool:external.network.check:blocked")); assert.equal(elapsed < 150000, true); } finally { await new Promise((resolve, reject) => { server.close((error) => (error ? reject(error) : resolve())); }); await rm(workspace, { recursive: true, force: true }); await rm(fakeCodex.root, { recursive: true, force: true }); } }); test("cloud api /v1/agent/chat can answer skills prompt without local skills manifest when Codex stdio replies", async () => { const workspace = await mkdtemp(path.join(os.tmpdir(), "hwlab-agent-stdio-skills-missing-")); const codexHome = path.join(workspace, "codex-home"); const fakeCodex = await createFakeCodexCommand(); await mkdir(codexHome, { recursive: true }); const server = createCloudApiServer({ env: { PATH: process.env.PATH, OPENAI_API_KEY: "test-openai-key-material", CODEX_HOME: codexHome, HWLAB_CODE_AGENT_PROVIDER: "codex-stdio", HWLAB_CODE_AGENT_MODEL: "gpt-test", HWLAB_CODE_AGENT_CODEX_COMMAND: fakeCodex.command, HWLAB_CODE_AGENT_CODEX_STDIO_ENABLED: "1", HWLAB_CODE_AGENT_CODEX_STDIO_SUPERVISOR: "repo-owned", HWLAB_CODE_AGENT_CODEX_WORKSPACE: workspace, HWLAB_CODE_AGENT_SKILLS_DIRS: path.join(workspace, "missing-skills"), HWLAB_CODE_AGENT_SKILLS_STRICT: "1", HWLAB_CODE_AGENT_OPENAI_BASE_URL: "http://127.0.0.1:65535/v1/responses" }, codexStdioManager: createCodexStdioSessionManager({ idFactory: () => "ses_server_stdio_skills_missing", createRpcClient: async () => ({ async initialize() { return { tools: ["codex", "codex-reply"] }; }, async listTools() { return ["codex", "codex-reply"]; }, async callTool() { return { structuredContent: { threadId: "thread_server_stdio_skills_missing", content: "真实 Codex stdio 回复:当前运行时未提供可展开的本地 skill manifest,但这是模型/工具链的真实回答。" } }; }, close() {} }) }) }); await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve)); try { const { port } = server.address(); const payload = await postAgent(port, { conversationId: "cnv_server_stdio_skills_missing", traceId: "trc_server_stdio_skills_missing", message: "列出你可用的skills" }); validateCodeAgentChatSchema(payload); assert.equal(payload.status, "completed"); assert.equal(payload.provider, "codex-stdio"); assert.equal(payload.backend, "hwlab-cloud-api/codex-mcp-stdio"); assert.equal(payload.capabilityLevel, "long-lived-codex-stdio-session"); assert.equal(payload.skills.status, "blocked"); assert.equal(payload.skills.blockers[0].code, "skills_unavailable"); assert.ok(payload.toolCalls.some((toolCall) => toolCall.name === "skills.discover" && toolCall.status === "blocked")); assert.equal(payload.session.status, "idle"); assert.match(payload.reply.content, /真实 Codex stdio 回复/u); assert.equal(JSON.stringify(payload).includes("test-openai-key-material"), false); } finally { await new Promise((resolve, reject) => { server.close((error) => (error ? reject(error) : resolve())); }); await rm(workspace, { recursive: true, force: true }); await rm(fakeCodex.root, { recursive: true, force: true }); } }); test("cloud api /v1/agent/chat routes M3 IO through Skill CLI even when Codex stdio is unavailable", async () => { const workspace = await mkdtemp(path.join(os.tmpdir(), "hwlab-agent-m3-skill-")); const calls = []; const server = createCloudApiServer({ env: { PATH: process.env.PATH, HWLAB_CODE_AGENT_WORKSPACE: workspace, HWLAB_CODE_AGENT_HWLAB_API_BASE_URL: "http://hwlab-cloud-api.hwlab-dev.svc.cluster.local:6667", OPENAI_API_KEY: "must-not-be-used" }, m3IoSkillRequestJson: async (url, request) => { calls.push({ url, request }); return { ok: true, status: 200, body: { serviceId: "hwlab-cloud-api", contractVersion: "m3-io-control-v1", status: "succeeded", accepted: true, action: "do.write", traceId: "trc_server-test-m3-skill", operationId: "op_m3_do_write_server_skill", auditId: "aud_m3_do_write_server_skill_succeeded", evidenceId: "evd_m3_do_write_server_skill_succeeded", auditState: { status: "written_non_durable" }, evidenceState: { status: "blocked", sourceKind: "BLOCKED", blocker: "runtime_durable_not_green", writeStatus: "written_non_durable" }, durableStatus: { status: "degraded", durable: false, blocker: "runtime_durable_not_green" }, result: { value: false, targetReadback: { status: "succeeded", value: false } }, controlPath: { status: "succeeded", cloudApi: true, gatewaySimu: true, boxSimu: true, patchPanel: true, frontendBypass: false } } }; } }); await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve)); try { const { port } = server.address(); const response = await fetch(`http://127.0.0.1:${port}/v1/agent/chat`, { method: "POST", headers: { "content-type": "application/json", "x-trace-id": "trc_server-test-m3-skill" }, body: JSON.stringify({ conversationId: "cnv_server-test-m3-skill", message: "通过 HWLAB API 把 res_boxsimu_1 的 DO1 写成 false,然后读取 res_boxsimu_2 的 DI1。" }) }); assert.equal(response.status, 200); const payload = await response.json(); validateCodeAgentChatSchema(payload); assert.equal(payload.status, "completed"); assert.equal(payload.provider, "hwlab-skill-cli"); assert.equal(payload.backend, "hwlab-cloud-api/hwlab-agent-runtime-skill-cli"); assert.equal(payload.capabilityLevel, HWLAB_M3_IO_CAPABILITY_LEVELS.ready); assert.equal(payload.runner.kind, "hwlab-m3-io-skill-cli"); assert.equal(payload.codexStdioFeasibility.skipped, true); assert.equal(payload.responseType, "m3_io_result"); assert.equal(payload.toolCalls.length, 1); assert.equal(payload.toolCalls[0].name, "hwlab-agent-runtime.m3-io"); assert.equal(payload.toolCalls[0].route, "/v1/m3/io"); assert.equal(payload.toolCalls[0].accepted, true); assert.equal(Object.hasOwn(payload, "reply"), true); assert.equal(calls.length, 1); } finally { await new Promise((resolve, reject) => { server.close((error) => (error ? reject(error) : resolve())); }); } }); test("cloud api /v1/agent/chat recognizes M3 patch-panel topology text before Codex free chat", async () => { const workspace = await mkdtemp(path.join(os.tmpdir(), "hwlab-agent-m3-topology-skill-")); const codexHome = path.join(workspace, "codex-home"); const fakeCodex = await createFakeCodexCommand(); await mkdir(codexHome, { recursive: true }); const calls = []; const codexStdioCalls = []; const manager = createCodexStdioSessionManager({ idFactory: () => "ses_server_m3_topology_skill", createRpcClient: async () => ({ async initialize() { codexStdioCalls.push("initialize"); return { tools: ["codex", "codex-reply"] }; }, async listTools() { codexStdioCalls.push("listTools"); return ["codex", "codex-reply"]; }, async callTool() { codexStdioCalls.push("callTool"); return { structuredContent: { threadId: "thread_server_m3_topology_skill", content: "stdio session ready for controlled topology M3 skill." } }; }, close() {} }) }); const server = createCloudApiServer({ env: { PATH: process.env.PATH, OPENAI_API_KEY: "test-openai-key-material", CODEX_HOME: codexHome, HWLAB_CODE_AGENT_PROVIDER: "codex-stdio", HWLAB_CODE_AGENT_MODEL: "gpt-test", HWLAB_CODE_AGENT_CODEX_COMMAND: fakeCodex.command, HWLAB_CODE_AGENT_CODEX_STDIO_ENABLED: "1", HWLAB_CODE_AGENT_CODEX_STDIO_SUPERVISOR: "repo-owned", HWLAB_CODE_AGENT_WORKSPACE: workspace, HWLAB_CODE_AGENT_CODEX_WORKSPACE: workspace, HWLAB_CODE_AGENT_HWLAB_API_BASE_URL: "http://hwlab-cloud-api.hwlab-dev.svc.cluster.local:6667" }, codexStdioManager: manager, m3IoSkillRequestJson: async (url, request) => { calls.push({ url, request }); return { ok: true, status: 200, body: { serviceId: "hwlab-cloud-api", contractVersion: "m3-io-control-v1", status: "succeeded", accepted: true, action: "do.write", traceId: "trc_server-test-m3-topology", operationId: "op_m3_do_write_topology_skill", auditId: "aud_m3_do_write_topology_skill", evidenceId: "evd_m3_do_write_topology_skill", auditState: { status: "written_non_durable" }, evidenceState: { status: "blocked", sourceKind: "BLOCKED", blocker: "runtime_durable_not_green", writeStatus: "written_non_durable" }, durableStatus: { status: "degraded", durable: false, blocker: "runtime_durable_not_green" }, result: { value: true, targetReadback: { status: "succeeded", value: true, resourceId: "res_boxsimu_2", port: "DI1" } }, controlPath: { status: "succeeded", cloudApi: true, gatewaySimu: true, boxSimu: true, patchPanel: true, frontendBypass: false } } }; } }); await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve)); try { const { port } = server.address(); const payload = await postAgent(port, { conversationId: "cnv_server-test-m3-topology", traceId: "trc_server-test-m3-topology", message: "执行 res_boxsimu_1:DO1 -> hwlab-patch-panel -> res_boxsimu_2:DI1 基本闭环,把 DO1 置为 true。" }); validateCodeAgentChatSchema(payload); assert.equal(payload.status, "completed"); assert.equal(payload.provider, "hwlab-skill-cli"); assert.equal(payload.backend, "hwlab-cloud-api/hwlab-agent-runtime-skill-cli"); assert.equal(payload.runner.kind, "hwlab-m3-io-skill-cli"); assert.equal(payload.sessionMode, "controlled-m3-io-skill-cli"); assert.equal(payload.codexStdioFeasibility.skipped, true); assert.equal(payload.responseType, "m3_io_result"); assert.equal(payload.m3Io.type, "m3_io_result"); assert.equal(payload.m3Io.action, "do.write"); assert.equal(payload.m3Io.do1.targetValue, true); assert.equal(payload.m3Io.di1.observedValue, true); assert.equal(payload.m3Io.wiring.label, "res_boxsimu_1:DO1 -> hwlab-patch-panel -> res_boxsimu_2:DI1"); assert.equal(payload.m3Io.path.summary, "Code Agent -> Skill CLI -> HWLAB API /v1/m3/io"); assert.equal(payload.m3Io.operation.operationId, "op_m3_do_write_topology_skill"); assert.equal(payload.m3Io.trace.traceId, "trc_server-test-m3-topology"); assert.equal(payload.m3Io.session.sessionId, payload.session.sessionId); assert.equal(payload.m3Io.trust.trusted, false); assert.equal(payload.m3Io.trust.durable, false); const skillTool = payload.toolCalls.find((toolCall) => toolCall.name === "hwlab-agent-runtime.m3-io"); assert.ok(skillTool); assert.equal(skillTool.route, "/v1/m3/io"); assert.equal(skillTool.method, "POST"); assert.equal(skillTool.accepted, true); assert.equal(skillTool.operationId, "op_m3_do_write_topology_skill"); assert.equal(skillTool.traceId, "trc_server-test-m3-topology"); assert.equal(skillTool.readback.value, true); assert.equal(payload.providerTrace.fallbackUsed, false); assert.equal(payload.providerTrace.transport, "skill-cli"); assert.ok(payload.runnerTrace.events.includes("tool:skill-cli:started")); assert.ok(payload.runnerTrace.events.includes("route:/v1/m3/io")); assert.match(payload.reply.content, /Code Agent -> Skill CLI -> HWLAB API \/v1\/m3\/io/u); assert.match(payload.reply.content, /res_boxsimu_1:DO1=true/u); assert.match(payload.reply.content, /res_boxsimu_2:DI1=true/u); assert.equal(calls.length, 1); assert.equal(calls[0].url, "http://hwlab-cloud-api.hwlab-dev.svc.cluster.local:6667/v1/m3/io"); assert.equal(calls[0].request.body.action, "do.write"); assert.equal(calls[0].request.body.value, true); assert.deepEqual(codexStdioCalls, []); assert.equal(/https?:\/\/[^\s"']*(?:gateway(?:-simu)?|box(?:-simu)?|patch-panel)|:7101\b|:7201\b|:7301\b|\/invoke\b|\/sync\/tick\b/iu.test(JSON.stringify(payload.toolCalls)), false); } finally { await new Promise((resolve, reject) => { server.close((error) => (error ? reject(error) : resolve())); }); await rm(workspace, { recursive: true, force: true }); await rm(fakeCodex.root, { recursive: true, force: true }); } }); test("cloud api /v1/agent/chat returns Skill CLI blocker for M3 topology without DO1 value", async () => { const workspace = await mkdtemp(path.join(os.tmpdir(), "hwlab-agent-m3-topology-blocker-")); const codexHome = path.join(workspace, "codex-home"); const fakeCodex = await createFakeCodexCommand(); await mkdir(codexHome, { recursive: true }); const calls = []; const manager = createCodexStdioSessionManager({ idFactory: () => "ses_server_m3_topology_blocker", createRpcClient: async () => ({ async initialize() { return { tools: ["codex", "codex-reply"] }; }, async listTools() { return ["codex", "codex-reply"]; }, async callTool() { return { structuredContent: { threadId: "thread_server_m3_topology_blocker", content: "stdio session ready for blocked topology M3 skill." } }; }, close() {} }) }); const server = createCloudApiServer({ env: { PATH: process.env.PATH, OPENAI_API_KEY: "test-openai-key-material", CODEX_HOME: codexHome, HWLAB_CODE_AGENT_PROVIDER: "codex-stdio", HWLAB_CODE_AGENT_MODEL: "gpt-test", HWLAB_CODE_AGENT_CODEX_COMMAND: fakeCodex.command, HWLAB_CODE_AGENT_CODEX_STDIO_ENABLED: "1", HWLAB_CODE_AGENT_CODEX_STDIO_SUPERVISOR: "repo-owned", HWLAB_CODE_AGENT_WORKSPACE: workspace, HWLAB_CODE_AGENT_CODEX_WORKSPACE: workspace, HWLAB_CODE_AGENT_HWLAB_API_BASE_URL: "http://hwlab-cloud-api.hwlab-dev.svc.cluster.local:6667" }, codexStdioManager: manager, m3IoSkillRequestJson: async (url, request) => { calls.push({ url, request }); throw new Error("missing DO1 value must block before HWLAB API request"); } }); await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve)); try { const { port } = server.address(); const payload = await postAgent(port, { conversationId: "cnv_server-test-m3-topology-blocker", traceId: "trc_server-test-m3-topology-blocker", message: "执行 res_boxsimu_1:DO1 -> hwlab-patch-panel -> res_boxsimu_2:DI1 基本闭环。" }); validateCodeAgentChatSchema(payload); assert.equal(payload.status, "completed"); assert.equal(payload.provider, "hwlab-skill-cli"); assert.equal(payload.backend, "hwlab-cloud-api/hwlab-agent-runtime-skill-cli"); assert.equal(payload.runner.kind, "hwlab-m3-io-skill-cli"); assert.equal(payload.sessionMode, "controlled-m3-io-skill-cli"); assert.equal(payload.codexStdioFeasibility.skipped, true); assert.equal(payload.capabilityLevel, HWLAB_M3_IO_CAPABILITY_LEVELS.blocked); assert.equal(payload.responseType, "m3_io_blocker"); assert.equal(payload.m3Io.type, "m3_io_blocker"); assert.equal(payload.m3Io.action, "do.write"); assert.equal(payload.m3Io.do1.targetValue, null); assert.equal(payload.m3Io.di1.observedValue, null); assert.equal(payload.m3Io.path.summary, "Code Agent -> Skill CLI -> HWLAB API /v1/m3/io"); assert.equal(payload.m3Io.blocker.code, "invalid_boolean_value"); const skillTool = payload.toolCalls.find((toolCall) => toolCall.name === "hwlab-agent-runtime.m3-io"); assert.ok(skillTool); assert.equal(skillTool.status, "blocked"); assert.equal(skillTool.route, "/v1/m3/io"); assert.equal(skillTool.blocker.code, "invalid_boolean_value"); assert.equal(skillTool.accepted, false); assert.equal(skillTool.operationId, null); assert.equal(payload.blocker.code, "invalid_boolean_value"); assert.match(payload.reply.content, /^M3 IO 阻塞:/u); assert.match(payload.reply.content, /M3 DO1 写入需要 --value true 或 --value false/u); assert.match(payload.reply.content, /Code Agent -> Skill CLI -> HWLAB API \/v1\/m3\/io/u); assert.equal(payload.providerTrace.fallbackUsed, false); assert.equal(payload.providerTrace.transport, "skill-cli"); assert.equal(calls.length, 0); } finally { await new Promise((resolve, reject) => { server.close((error) => (error ? reject(error) : resolve())); }); await rm(workspace, { recursive: true, force: true }); await rm(fakeCodex.root, { recursive: true, force: true }); } }); test("cloud api /v1/agent/chat promotes /v1/m3/io blocker into Chinese M3 IO reply", async () => { const workspace = await mkdtemp(path.join(os.tmpdir(), "hwlab-agent-m3-api-blocker-")); const codexHome = path.join(workspace, "codex-home"); const fakeCodex = await createFakeCodexCommand(); await mkdir(codexHome, { recursive: true }); const calls = []; const manager = createCodexStdioSessionManager({ idFactory: () => "ses_server_m3_api_blocker", createRpcClient: async () => ({ async initialize() { return { tools: ["codex", "codex-reply"] }; }, async listTools() { return ["codex", "codex-reply"]; }, async callTool() { return { structuredContent: { threadId: "thread_server_m3_api_blocker", content: "stdio session ready for M3 API blocker fixture." } }; }, close() {} }) }); const server = createCloudApiServer({ env: { PATH: process.env.PATH, OPENAI_API_KEY: "test-openai-key-material", CODEX_HOME: codexHome, HWLAB_CODE_AGENT_PROVIDER: "codex-stdio", HWLAB_CODE_AGENT_MODEL: "gpt-test", HWLAB_CODE_AGENT_CODEX_COMMAND: fakeCodex.command, HWLAB_CODE_AGENT_CODEX_STDIO_ENABLED: "1", HWLAB_CODE_AGENT_CODEX_STDIO_SUPERVISOR: "repo-owned", HWLAB_CODE_AGENT_WORKSPACE: workspace, HWLAB_CODE_AGENT_CODEX_WORKSPACE: workspace, HWLAB_CODE_AGENT_HWLAB_API_BASE_URL: "http://hwlab-cloud-api.hwlab-dev.svc.cluster.local:6667" }, codexStdioManager: manager, m3IoSkillRequestJson: async (url, request) => { calls.push({ url, request }); return { ok: true, status: 200, body: { serviceId: "hwlab-cloud-api", contractVersion: "m3-io-control-v1", route: "/v1/m3/io", method: "POST", status: "blocked", accepted: false, action: "do.write", traceId: "trc_server-test-m3-api-blocker", operationId: "op_m3_do_write_api_blocker", auditId: "aud_m3_do_write_api_blocker_failed", evidenceId: "evd_m3_do_write_api_blocker_failed", blocker: { code: "m3_wiring_missing", layer: "hwlab-patch-panel", zh: "hwlab-patch-panel 未确认 active res_boxsimu_1:DO1 -> res_boxsimu_2:DI1 接线" }, auditState: { status: "written_non_durable" }, evidenceState: { status: "blocked", sourceKind: "BLOCKED", blocker: "runtime_durable_not_green", writeStatus: "written_non_durable" }, durableStatus: { status: "degraded", durable: false, blocker: "runtime_durable_not_green" }, result: { value: true, targetReadback: null }, controlPath: { status: "blocked", cloudApi: true, gatewaySimu: true, boxSimu: true, patchPanel: false, frontendBypass: false } } }; } }); await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve)); try { const { port } = server.address(); const payload = await postAgent(port, { conversationId: "cnv_server-test-m3-api-blocker", traceId: "trc_server-test-m3-api-blocker", message: "把 M3 DO1 打开,然后读取 DI1。" }); validateCodeAgentChatSchema(payload); assert.equal(payload.status, "completed"); assert.equal(payload.responseType, "m3_io_blocker"); assert.equal(payload.m3Io.type, "m3_io_blocker"); assert.equal(payload.m3Io.action, "do.write"); assert.equal(payload.m3Io.do1.targetValue, true); assert.equal(payload.m3Io.di1.observedValue, null); assert.equal(payload.m3Io.blocker.code, "m3_wiring_missing"); assert.equal(payload.blocker.code, "m3_wiring_missing"); assert.match(payload.reply.content, /^M3 IO 阻塞:/u); assert.match(payload.reply.content, /hwlab-patch-panel 未确认 active/u); assert.match(payload.reply.content, /res_boxsimu_1:DO1 -> hwlab-patch-panel -> res_boxsimu_2:DI1/u); assert.match(payload.reply.content, /trusted=false;durable=false/u); assert.equal(calls.length, 1); assert.equal(new URL(calls[0].url).pathname, "/v1/m3/io"); assert.equal(calls[0].request.body.action, "do.write"); assert.equal(calls[0].request.body.value, true); } finally { await new Promise((resolve, reject) => { server.close((error) => (error ? reject(error) : resolve())); }); await rm(workspace, { recursive: true, force: true }); await rm(fakeCodex.root, { recursive: true, force: true }); } }); test("cloud api /v1/agent/chat routes M3 Skill CLI through in-process HWLAB API handler", async () => { const workspace = await mkdtemp(path.join(os.tmpdir(), "hwlab-agent-stdio-m3-skill-")); const fakeCodex = await createFakeCodexCommand(); const gatewayCalls = []; const codexStdioCalls = []; const manager = createCodexStdioSessionManager({ idFactory: () => "ses_server_stdio_m3_skill", createRpcClient: async () => ({ async initialize() { codexStdioCalls.push("initialize"); return { tools: ["codex", "codex-reply"] }; }, async listTools() { codexStdioCalls.push("listTools"); return ["codex", "codex-reply"]; }, async callTool() { codexStdioCalls.push("callTool"); return { structuredContent: { threadId: "thread_server_stdio_m3_skill", content: "stdio session ready for M3 skill." } }; }, close() {} }) }); const server = createCloudApiServer({ env: { PATH: process.env.PATH, OPENAI_API_KEY: "test-openai-key-material", CODEX_HOME: workspace, HWLAB_CODE_AGENT_PROVIDER: "codex-stdio", HWLAB_CODE_AGENT_MODEL: "gpt-test", HWLAB_CODE_AGENT_CODEX_COMMAND: fakeCodex.command, HWLAB_CODE_AGENT_CODEX_STDIO_ENABLED: "1", HWLAB_CODE_AGENT_CODEX_STDIO_SUPERVISOR: "repo-owned", HWLAB_CODE_AGENT_WORKSPACE: workspace, HWLAB_CODE_AGENT_HWLAB_API_BASE_URL: "http://hwlab-cloud-api.hwlab-dev.svc.cluster.local:6667", HWLAB_M3_GATEWAY_SIMU_1_URL: "http://gateway-1", HWLAB_M3_GATEWAY_SIMU_2_URL: "http://gateway-2", HWLAB_M3_PATCH_PANEL_URL: "http://patch-panel" }, codexStdioManager: manager, m3IoRequestJson: async (url, request) => { gatewayCalls.push({ url, request }); return m3ReadinessRequestJson(url, request); } }); await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve)); try { const { port } = server.address(); const payload = await postAgent(port, { conversationId: "cnv_server_stdio_m3_skill", traceId: "trc_server_stdio_m3_skill", message: "通过 HWLAB API 把 res_boxsimu_1 的 DO1 写成 true,然后读取 res_boxsimu_2 的 DI1。" }); validateCodeAgentChatSchema(payload); assert.equal(payload.status, "completed"); assert.equal(payload.provider, "hwlab-skill-cli"); assert.equal(payload.backend, "hwlab-cloud-api/hwlab-agent-runtime-skill-cli"); assert.equal(payload.runner.kind, "hwlab-m3-io-skill-cli"); assert.equal(payload.sessionMode, "controlled-m3-io-skill-cli"); assert.equal(payload.runner.codexStdio, false); assert.equal(payload.codexStdioFeasibility.skipped, true); assert.equal(payload.codexStdioFeasibility.reason, "m3_io_skill_cli_deterministic_route"); assert.equal(payload.capabilityLevel, HWLAB_M3_IO_CAPABILITY_LEVELS.ready); assert.equal(payload.responseType, "m3_io_result"); assert.equal(payload.m3Io.type, "m3_io_result"); assert.equal(payload.m3Io.action, "do.write"); assert.equal(payload.m3Io.do1.targetValue, true); assert.equal(payload.m3Io.di1.observedValue, true); assert.equal(payload.m3Io.operation.operationId.startsWith("op_m3_do_write_"), true); assert.equal(payload.m3Io.session.sessionId, payload.session.sessionId); assert.equal(payload.m3Io.path.summary, "Code Agent -> Skill CLI -> HWLAB API /v1/m3/io"); assert.equal(payload.m3Io.trust.trusted, false); assert.equal(payload.m3Io.trust.durable, false); const skillTool = payload.toolCalls.find((toolCall) => toolCall.name === "hwlab-agent-runtime.m3-io"); assert.ok(skillTool); assert.equal(skillTool.type, "skill-cli"); assert.equal(skillTool.route, "/v1/m3/io"); assert.equal(skillTool.method, "POST"); assert.equal(skillTool.accepted, true); assert.equal(skillTool.readback.value, true); assert.equal(skillTool.controlReady, true); assert.equal(skillTool.operationId.startsWith("op_m3_do_write_"), true); assert.match(skillTool.auditId, /^aud_m3_do_write_/u); assert.match(skillTool.evidenceId, /^evd_m3_do_write_/u); assert.equal(payload.runnerTrace.route, "/v1/m3/io"); assert.ok(payload.runnerTrace.events.includes("tool:skill-cli:started")); assert.ok(payload.runnerTrace.events.includes("route:/v1/m3/io")); assert.equal(payload.providerTrace.fallbackUsed, false); assert.equal(payload.providerTrace.codexStdio, false); assert.equal(payload.providerTrace.transport, "skill-cli"); assert.match(payload.reply.content, /HWLAB API \/v1\/m3\/io/u); assert.match(payload.reply.content, /res_boxsimu_1:DO1=true/u); assert.match(payload.reply.content, /res_boxsimu_2:DI1=true/u); assert.ok(payload.runner.toolPolicy.allowed.includes("POST /v1/m3/io")); assert.deepEqual(gatewayCalls.map((call) => new URL(call.url).pathname), [ "/status", "/invoke", "/sync/tick", "/status", "/invoke" ]); assert.deepEqual(codexStdioCalls, []); assert.equal(/https?:\/\/[^\s"']*(?:gateway(?:-simu)?|box(?:-simu)?|patch-panel)|:7101\b|:7201\b|:7301\b|\/invoke\b|\/sync\/tick\b/iu.test(JSON.stringify(payload.toolCalls)), false); } finally { await new Promise((resolve, reject) => { server.close((error) => (error ? reject(error) : resolve())); }); await rm(workspace, { recursive: true, force: true }); await rm(fakeCodex.root, { recursive: true, force: true }); } }); test("cloud api /v1/agent/chat blocks direct M3 API base targets before Codex stdio", async () => { const workspace = await mkdtemp(path.join(os.tmpdir(), "hwlab-agent-m3-direct-target-")); const codexHome = path.join(workspace, "codex-home"); const fakeCodex = await createFakeCodexCommand(); await mkdir(codexHome, { recursive: true }); const calls = []; const manager = createCodexStdioSessionManager({ idFactory: () => "ses_server_stdio_m3_direct_block", createRpcClient: async () => ({ async initialize() { return { tools: ["codex", "codex-reply"] }; }, async listTools() { return ["codex", "codex-reply"]; }, async callTool() { return { structuredContent: { threadId: "thread_server_stdio_m3_direct_block", content: "stdio session ready for blocked M3 direct target." } }; }, close() {} }) }); const server = createCloudApiServer({ env: { PATH: process.env.PATH, OPENAI_API_KEY: "test-openai-key-material", CODEX_HOME: codexHome, HWLAB_CODE_AGENT_PROVIDER: "codex-stdio", HWLAB_CODE_AGENT_MODEL: "gpt-test", HWLAB_CODE_AGENT_CODEX_COMMAND: fakeCodex.command, HWLAB_CODE_AGENT_CODEX_STDIO_ENABLED: "1", HWLAB_CODE_AGENT_CODEX_STDIO_SUPERVISOR: "repo-owned", HWLAB_CODE_AGENT_WORKSPACE: workspace, HWLAB_CODE_AGENT_CODEX_WORKSPACE: workspace, HWLAB_CODE_AGENT_HWLAB_API_BASE_URL: "http://hwlab-gateway-simu-1.hwlab-dev.svc.cluster.local:7101" }, codexStdioManager: manager, m3IoSkillRequestJson: async (url, request) => { calls.push({ url, request }); throw new Error("direct gateway target must be blocked before requestJson"); } }); await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve)); try { const { port } = server.address(); const payload = await postAgent(port, { conversationId: "cnv_server-test-m3-direct-target", traceId: "trc_server-test-m3-direct-target", message: "读取 M3 DI1" }); validateCodeAgentChatSchema(payload); assert.equal(payload.status, "completed"); assert.equal(payload.provider, "hwlab-skill-cli"); assert.equal(payload.runner.kind, "hwlab-m3-io-skill-cli"); assert.equal(payload.sessionMode, "controlled-m3-io-skill-cli"); assert.equal(payload.codexStdioFeasibility.skipped, true); assert.equal(payload.capabilityLevel, HWLAB_M3_IO_CAPABILITY_LEVELS.blocked); assert.equal(payload.responseType, "m3_io_blocker"); assert.equal(payload.m3Io.type, "m3_io_blocker"); assert.equal(payload.m3Io.blocker.code, "direct_hardware_target_blocked"); assert.equal(payload.toolCalls.length, 1); const skillTool = payload.toolCalls[0]; assert.equal(skillTool.name, "hwlab-agent-runtime.m3-io"); assert.equal(skillTool.status, "blocked"); assert.equal(skillTool.route, "/v1/m3/io"); assert.equal(skillTool.method, "POST"); assert.equal(skillTool.blocker.code, "direct_hardware_target_blocked"); assert.equal(skillTool.hwlabApi.redactedUrl, null); assert.equal(payload.skills.blockers[0].code, "direct_hardware_target_blocked"); assert.equal(calls.length, 0); assert.equal(/https?:\/\/[^\s"']*(?:gateway(?:-simu)?|box(?:-simu)?|patch-panel)|:7101\b|:7201\b|:7301\b|\/invoke\b|\/sync\/tick\b/iu.test(JSON.stringify(payload.toolCalls)), false); } finally { await new Promise((resolve, reject) => { server.close((error) => (error ? reject(error) : resolve())); }); await rm(workspace, { recursive: true, force: true }); await rm(fakeCodex.root, { recursive: true, force: true }); } }); test("cloud api /v1/agent/chat reports M3 Skill CLI missing API base before Codex stdio", async () => { const workspace = await mkdtemp(path.join(os.tmpdir(), "hwlab-agent-m3-api-base-missing-")); const codexHome = path.join(workspace, "codex-home"); const fakeCodex = await createFakeCodexCommand(); await mkdir(codexHome, { recursive: true }); const manager = createCodexStdioSessionManager({ idFactory: () => "ses_server_stdio_m3_api_base_missing", createRpcClient: async () => ({ async initialize() { return { tools: ["codex", "codex-reply"] }; }, async listTools() { return ["codex", "codex-reply"]; }, async callTool() { return { structuredContent: { threadId: "thread_server_stdio_m3_api_base_missing", content: "stdio session ready for missing M3 API base." } }; }, close() {} }) }); const server = createCloudApiServer({ env: { PATH: process.env.PATH, OPENAI_API_KEY: "test-openai-key-material", CODEX_HOME: codexHome, HWLAB_CODE_AGENT_PROVIDER: "codex-stdio", HWLAB_CODE_AGENT_MODEL: "gpt-test", HWLAB_CODE_AGENT_CODEX_COMMAND: fakeCodex.command, HWLAB_CODE_AGENT_CODEX_STDIO_ENABLED: "1", HWLAB_CODE_AGENT_CODEX_STDIO_SUPERVISOR: "repo-owned", HWLAB_CODE_AGENT_WORKSPACE: workspace, HWLAB_CODE_AGENT_CODEX_WORKSPACE: workspace, HWLAB_CODE_AGENT_REQUIRE_HWLAB_API_BASE_URL: "1" }, codexStdioManager: manager, m3IoSkillRequestJson: async () => { throw new Error("missing API base must not call requestJson"); } }); await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve)); try { const { port } = server.address(); const payload = await postAgent(port, { conversationId: "cnv_server-test-m3-api-base-missing", traceId: "trc_server-test-m3-api-base-missing", message: "读取 M3 DI1" }); assert.equal(payload.status, "completed"); assert.equal(payload.provider, "hwlab-skill-cli"); assert.equal(payload.runner.kind, "hwlab-m3-io-skill-cli"); assert.equal(payload.sessionMode, "controlled-m3-io-skill-cli"); assert.equal(payload.codexStdioFeasibility.skipped, true); assert.equal(payload.capabilityLevel, HWLAB_M3_IO_CAPABILITY_LEVELS.blocked); assert.equal(payload.responseType, "m3_io_blocker"); assert.equal(payload.m3Io.type, "m3_io_blocker"); assert.equal(payload.m3Io.blocker.code, "skill_cli_api_base_missing"); assert.equal(payload.skills.blockers[0].code, "skill_cli_api_base_missing"); assert.equal(payload.toolCalls.length, 1); const skillTool = payload.toolCalls[0]; assert.equal(skillTool.name, "hwlab-agent-runtime.m3-io"); assert.equal(skillTool.blocker.code, "skill_cli_api_base_missing"); assert.deepEqual(skillTool.blocker.missingConfig, [ "HWLAB_CODE_AGENT_HWLAB_API_BASE_URL", "HWLAB_API_BASE_URL", "HWLAB_CLOUD_API_BASE_URL", "contract:hwlab-agent-runtime.m3-io.apiBaseUrl" ]); assert.equal(JSON.stringify(payload).includes("test-openai-key-material"), false); } finally { await new Promise((resolve, reject) => { server.close((error) => (error ? reject(error) : resolve())); }); await rm(workspace, { recursive: true, force: true }); await rm(fakeCodex.root, { recursive: true, force: true }); } }); test("cloud api /v1/agent/chat refuses local file-tool shortcuts when Codex stdio is unavailable", async () => { const workspace = await mkdtemp(path.join(os.tmpdir(), "hwlab-agent-file-tools-")); await writeFile(path.join(workspace, "alpha.txt"), "alpha\nbeta\n", "utf8"); await writeFile(path.join(workspace, "package.json"), "{\"name\":\"sample\"}\n", "utf8"); await mkdir(path.join(workspace, "src"), { recursive: true }); await writeFile(path.join(workspace, "src", "main.mjs"), "export const value = 1;\n", "utf8"); const env = { PATH: process.env.PATH, HWLAB_CODE_AGENT_WORKSPACE: workspace }; const server = createCloudApiServer({ env }); await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve)); try { const { port } = server.address(); const first = await postAgent(port, { conversationId: "cnv_server-test-session-reuse", traceId: "trc_server-test-session-reuse-ls", message: "ls ." }); assert.equal(first.status, "failed"); assert.equal(first.provider, "codex-stdio"); assert.equal(first.runner.kind, "codex-mcp-stdio-runner"); assert.equal(first.capabilityLevel, "blocked"); assert.equal(first.sessionMode, "codex-mcp-stdio-long-lived"); assert.deepEqual(first.toolCalls, []); assert.equal(Object.hasOwn(first, "reply"), false); assert.ok(first.runnerTrace.events.some((event) => event.label === "request:accepted")); assert.ok(first.runnerLimitations.includes("no-controlled-readonly-fallback")); const second = await postAgent(port, { conversationId: "cnv_server-test-session-reuse", traceId: "trc_server-test-session-reuse-cat", message: "cat alpha.txt" }); assert.equal(second.status, "failed"); assert.equal(second.provider, "codex-stdio"); assert.deepEqual(second.toolCalls, []); assert.equal(Object.hasOwn(second, "reply"), false); const third = await postAgent(port, { conversationId: "cnv_server-test-session-reuse", traceId: "trc_server-test-session-reuse-rg", message: "rg --files ." }); assert.equal(third.status, "failed"); assert.equal(third.provider, "codex-stdio"); assert.deepEqual(third.toolCalls, []); assert.equal(Object.hasOwn(third, "reply"), false); } finally { await new Promise((resolve, reject) => { server.close((error) => (error ? reject(error) : resolve())); }); } }); test("cloud api /v1/agent/chat does not use read-only conversation facts as fallback", async () => { const root = await mkdtemp(path.join(os.tmpdir(), "hwlab-agent-session-continuity-")); const workspace = path.join(root, "workspace"); const skillsDir = path.join(root, "skills"); await mkdir(workspace, { recursive: true }); await mkdir(path.join(skillsDir, "alpha"), { recursive: true }); await mkdir(path.join(skillsDir, "beta"), { recursive: true }); await writeFile(path.join(skillsDir, "alpha", "SKILL.md"), [ "---", "name: alpha-skill", "description: Alpha skill summary.", "---", "", "# Alpha" ].join("\n")); await writeFile(path.join(skillsDir, "beta", "SKILL.md"), [ "---", "name: beta-skill", "description: Beta skill summary.", "---", "", "# Beta" ].join("\n")); const server = createCloudApiServer({ env: { PATH: process.env.PATH, HWLAB_CODE_AGENT_WORKSPACE: workspace, HWLAB_CODE_AGENT_PROVIDER: "codex-cli", HWLAB_CODE_AGENT_CODEX_STDIO_ENABLED: "0", HWLAB_CODE_AGENT_CODEX_COMMAND: "/tmp/hwlab-missing-codex" }, skillsDirs: [skillsDir], skillsDirsExact: true }); await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve)); try { const { port } = server.address(); const first = await postAgent(port, { conversationId: "cnv_server-session-continuity", traceId: "trc_server-session-continuity-pwd", message: "用pwd列出你当前的工作目录" }); assert.equal(first.status, "failed"); assert.equal(first.provider, "codex-stdio"); assert.equal(first.runner.kind, "codex-mcp-stdio-runner"); assert.equal(first.capabilityLevel, "blocked"); assert.equal(first.conversationFacts.turnCount, 0); const second = await postAgent(port, { conversationId: "cnv_server-session-continuity", traceId: "trc_server-session-continuity-skills", message: "列出你能使用的所有skill" }); assert.equal(second.status, "failed"); assert.equal(second.provider, "codex-stdio"); assert.equal(second.skills.status, "not_requested"); assert.equal(second.conversationFacts.turnCount, 0); const third = await postAgent(port, { conversationId: "cnv_server-session-continuity", traceId: "trc_server-session-continuity-context", message: "根据前两轮结果说明你现在在哪个工作目录、能使用哪些skill" }); assert.equal(third.status, "failed"); assert.equal(third.provider, "codex-stdio"); assert.equal(third.backend, "hwlab-cloud-api/codex-mcp-stdio"); assert.equal(third.runner.kind, "codex-mcp-stdio-runner"); assert.equal(third.capabilityLevel, "blocked"); assert.deepEqual(third.toolCalls, []); assert.equal(third.conversationFacts.turnCount, 0); assert.notEqual(third.provider, "openai-responses"); assert.notEqual(third.runner.kind, "openai-responses-fallback"); assert.notEqual(third.runner.kind, "codex-cli-one-shot-ephemeral"); assert.notEqual(third.sessionMode, "ephemeral-one-shot"); assert.equal(Object.hasOwn(third, "reply"), false); } finally { await new Promise((resolve, reject) => { server.close((error) => (error ? reject(error) : resolve())); }); await rm(root, { recursive: true, force: true }); } }); test("cloud api /v1/agent/chat blocks forbidden file paths without leaking values", async () => { const workspace = await mkdtemp(path.join(os.tmpdir(), "hwlab-agent-file-block-")); const server = createCloudApiServer({ env: { PATH: process.env.PATH, HWLAB_CODE_AGENT_WORKSPACE: workspace } }); await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve)); try { const { port } = server.address(); const payload = await postAgent(port, { conversationId: "cnv_server-test-security-block", traceId: "trc_server-test-security-block", message: "cat ../outside.txt" }); assert.equal(payload.status, "failed"); assert.equal(payload.error.code, "codex_cli_binary_missing"); assert.deepEqual(payload.toolCalls, []); assert.equal(payload.capabilityLevel, "blocked"); assert.ok(payload.runnerTrace.events.some((event) => event.label === "codex-stdio:blocked")); assert.equal(Object.hasOwn(payload, "reply"), false); assert.equal(JSON.stringify(payload).includes("sk-"), false); } finally { await new Promise((resolve, reject) => { server.close((error) => (error ? reject(error) : resolve())); }); } }); test("cloud api /v1/agent/chat does not answer skills from local manifest when Codex stdio is blocked", async () => { const root = await mkdtemp(path.join(os.tmpdir(), "hwlab-agent-skills-")); const skillsDir = path.join(root, "skills"); await mkdir(path.join(skillsDir, "alpha"), { recursive: true }); await writeFile(path.join(skillsDir, "alpha", "SKILL.md"), [ "---", "name: alpha-skill", "description: Alpha skill summary.", "version: 1.2.3", "commit: abc123def456", "---", "", "# Alpha" ].join("\n")); const server = createCloudApiServer({ env: { PATH: process.env.PATH, HWLAB_CODE_AGENT_WORKSPACE: root }, skillsDirs: [skillsDir], skillsDirsExact: true }); await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve)); try { const { port } = server.address(); const response = await fetch(`http://127.0.0.1:${port}/v1/agent/chat`, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ conversationId: "cnv_server-test-skills", message: "列出你可用的skills" }) }); assert.equal(response.status, 200); const payload = await response.json(); assert.equal(payload.status, "failed"); assert.equal(payload.provider, "codex-stdio"); assert.equal(payload.backend, "hwlab-cloud-api/codex-mcp-stdio"); assert.equal(payload.capabilityLevel, "blocked"); assert.equal(payload.skills.status, "not_requested"); assert.deepEqual(payload.toolCalls, []); assert.equal(Object.hasOwn(payload, "reply"), false); assert.equal(JSON.stringify(payload).includes("alpha-skill"), false); } finally { await new Promise((resolve, reject) => { server.close((error) => (error ? reject(error) : resolve())); }); } }); test("cloud api /v1/agent/chat exposes prompt trace immediately while Codex stdio model call is slow", async () => { const root = await mkdtemp(path.join(os.tmpdir(), "hwlab-agent-slow-skills-")); const workspace = path.join(root, "workspace"); const skillsDir = path.join(root, "skills"); await mkdir(workspace, { recursive: true }); await mkdir(path.join(skillsDir, "slow-skill"), { recursive: true }); await writeFile(path.join(skillsDir, "slow-skill", "SKILL.md"), [ "---", "name: slow-skill", "description: Slow skill discovery summary.", "version: 2026.05.23", "commit: slow12345678", "---", "", "# Slow" ].join("\n")); const codexHome = path.join(root, "codex-home"); const fakeCodex = await createFakeCodexCommand(); await mkdir(codexHome, { recursive: true }); const server = createCloudApiServer({ env: { PATH: process.env.PATH, OPENAI_API_KEY: "test-openai-key-material", CODEX_HOME: codexHome, HWLAB_CODE_AGENT_PROVIDER: "codex-stdio", HWLAB_CODE_AGENT_MODEL: "gpt-test", HWLAB_CODE_AGENT_CODEX_COMMAND: fakeCodex.command, HWLAB_CODE_AGENT_CODEX_STDIO_ENABLED: "1", HWLAB_CODE_AGENT_CODEX_STDIO_SUPERVISOR: "repo-owned", HWLAB_CODE_AGENT_CODEX_WORKSPACE: workspace, HWLAB_CODE_AGENT_SKILLS_DIRS: skillsDir, HWLAB_CODE_AGENT_SKILLS_STRICT: "1", HWLAB_CODE_AGENT_OPENAI_BASE_URL: "http://127.0.0.1:65535/v1/responses" }, codexStdioManager: createCodexStdioSessionManager({ idFactory: () => "ses_server_test_slow_skills", createRpcClient: async () => ({ async initialize() { return { tools: ["codex", "codex-reply"] }; }, async listTools() { return ["codex", "codex-reply"]; }, async callTool() { await delay(120); return { structuredContent: { threadId: "thread_server_test_slow_skills", content: "真实 Codex stdio 回复:slow-skill" } }; }, close() {} }) }) }); await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve)); try { const { port } = server.address(); const chatPromise = fetch(`http://127.0.0.1:${port}/v1/agent/chat`, { method: "POST", headers: { "content-type": "application/json", "x-trace-id": "trc_server-test-slow-skills" }, body: JSON.stringify({ conversationId: "cnv_server-test-slow-skills", message: "请列出你可用的所有 skills" }) }); await delay(40); const traceResponse = await fetch(`http://127.0.0.1:${port}/v1/agent/chat/trace/trc_server-test-slow-skills`); assert.equal(traceResponse.status, 200); const tracePayload = await traceResponse.json(); assert.equal(tracePayload.traceId, "trc_server-test-slow-skills"); assert.equal(tracePayload.eventCount > 0, true); assert.ok(tracePayload.events.some((event) => event.label === "request:accepted" || event.label === "prompt:sent")); const response = await chatPromise; assert.equal(response.status, 200); const payload = await response.json(); assert.equal(payload.status, "completed"); assert.equal(payload.provider, "codex-stdio"); assert.equal(payload.backend, "hwlab-cloud-api/codex-mcp-stdio"); assert.equal(payload.traceId, "trc_server-test-slow-skills"); assert.equal(payload.capabilityLevel, "long-lived-codex-stdio-session"); assert.equal(payload.sessionMode, "codex-mcp-stdio-long-lived"); assert.equal(payload.skills.status, "ready"); assert.equal(payload.skills.items[0].name, "slow-skill"); assert.equal(payload.skills.items[0].traceId, "trc_server-test-slow-skills"); assert.equal(payload.runner.kind, "codex-mcp-stdio-runner"); assert.equal(payload.runnerTrace.traceId, "trc_server-test-slow-skills"); assert.ok(payload.runnerTrace.events.some((event) => event.label === "tool:codex:started")); assert.ok(payload.runnerTrace.events.some((event) => event.label === "assistant:completed")); assert.equal(Object.hasOwn(payload, "error"), false); } finally { await new Promise((resolve, reject) => { server.close((error) => (error ? reject(error) : resolve())); }); await rm(fakeCodex.root, { recursive: true, force: true }); } }); test("cloud api /v1/agent/chat/cancel cancels in-flight Codex stdio and keeps retry trace", async () => { const fakeCodex = await createFakeCodexCommand(); const workspace = path.join(fakeCodex.root, "workspace"); const codexHome = path.join(fakeCodex.root, "codex-home"); await mkdir(workspace, { recursive: true }); await mkdir(codexHome, { recursive: true }); const traceStore = createCodeAgentTraceStore(); let rejectTool = null; let toolStartedResolve = null; const toolStarted = new Promise((resolve) => { toolStartedResolve = resolve; }); const manager = createCodexStdioSessionManager({ idFactory: () => "ses_server_test_cancel", traceStore, createRpcClient: async () => ({ async initialize() { return { tools: ["codex", "codex-reply"] }; }, async listTools() { return ["codex", "codex-reply"]; }, async callTool() { toolStartedResolve(); return new Promise((resolve, reject) => { rejectTool = reject; }); }, close() { if (rejectTool) { rejectTool(new Error("closed by user cancel")); rejectTool = null; } } }) }); const server = createCloudApiServer({ traceStore, codexStdioManager: manager, env: { PATH: process.env.PATH, OPENAI_API_KEY: "test-openai-key-material", HWLAB_CODE_AGENT_PROVIDER: "codex-stdio", HWLAB_CODE_AGENT_MODEL: "gpt-test", HWLAB_CODE_AGENT_CODEX_COMMAND: fakeCodex.command, HWLAB_CODE_AGENT_CODEX_WORKSPACE: workspace, HWLAB_CODE_AGENT_WORKSPACE: workspace, HWLAB_CODE_AGENT_CODEX_SANDBOX: "workspace-write", HWLAB_CODE_AGENT_CODEX_STDIO_ENABLED: "1", HWLAB_CODE_AGENT_CODEX_STDIO_SUPERVISOR: "repo-owned", CODEX_HOME: codexHome } }); await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve)); try { const { port } = server.address(); const chatPromise = fetch(`http://127.0.0.1:${port}/v1/agent/chat`, { method: "POST", headers: { "content-type": "application/json", "x-trace-id": "trc_server-test-cancel" }, body: JSON.stringify({ conversationId: "cnv_server-test-cancel", message: "请执行一个需要等待的 Codex stdio 请求" }) }); await toolStarted; const cancelResponse = await fetch(`http://127.0.0.1:${port}/v1/agent/chat/cancel`, { method: "POST", headers: { "content-type": "application/json", "x-trace-id": "trc_server-test-cancel" }, body: JSON.stringify({ conversationId: "cnv_server-test-cancel", sessionId: "ses_server_test_cancel", traceId: "trc_server-test-cancel" }) }); assert.equal(cancelResponse.status, 200); const cancelPayload = await cancelResponse.json(); assert.equal(cancelPayload.status, "canceled"); assert.equal(cancelPayload.canceled, true); assert.equal(cancelPayload.sessionId, "ses_server_test_cancel"); assert.equal(cancelPayload.session.status, "canceled"); assert.equal(cancelPayload.lastTraceEvent.label, "cancel:canceled"); const chatResponse = await chatPromise; assert.equal(chatResponse.status, 200); const chatPayload = await chatResponse.json(); assert.equal(chatPayload.status, "canceled"); assert.equal(chatPayload.error.code, "codex_stdio_canceled"); assert.equal(chatPayload.session.sessionId, "ses_server_test_cancel"); assert.equal(chatPayload.session.status, "canceled"); assert.equal(chatPayload.runnerTrace.lastEvent.label, "cancel:canceled"); const traceResponse = await fetch(`http://127.0.0.1:${port}/v1/agent/chat/trace/trc_server-test-cancel`); assert.equal(traceResponse.status, 200); const tracePayload = await traceResponse.json(); assert.equal(tracePayload.status, "canceled"); assert.equal(tracePayload.lastEvent.label, "cancel:canceled"); assert.ok(tracePayload.events.some((event) => event.label === "tool:codex:started")); } finally { await new Promise((resolve, reject) => { server.close((error) => (error ? reject(error) : resolve())); }); await rm(fakeCodex.root, { recursive: true, force: true }); } }); test("cloud api /v1/agent/chat reports Codex stdio blocker instead of structured skills fallback", async () => { const root = await mkdtemp(path.join(os.tmpdir(), "hwlab-agent-no-skills-")); const skillsDir = path.join(root, "missing-skills"); const server = createCloudApiServer({ env: { PATH: process.env.PATH, HWLAB_CODE_AGENT_WORKSPACE: root }, skillsDirs: [skillsDir], skillsDirsExact: true }); await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve)); try { const { port } = server.address(); const response = await fetch(`http://127.0.0.1:${port}/v1/agent/chat`, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ conversationId: "cnv_server-test-skills-missing", message: "列出你可用的skills" }) }); assert.equal(response.status, 200); const payload = await response.json(); assert.equal(payload.status, "failed"); assert.equal(payload.provider, "codex-stdio"); assert.notEqual(payload.error.code, "skills_unavailable"); assert.equal(payload.capabilityLevel, "blocked"); assert.equal(payload.skills.status, "not_requested"); assert.deepEqual(payload.toolCalls, []); assert.ok(payload.runnerLimitations.includes("no-controlled-readonly-fallback")); assert.equal(Object.hasOwn(payload, "reply"), false); } finally { await new Promise((resolve, reject) => { server.close((error) => (error ? reject(error) : resolve())); }); } }); test("cloud api /v1/agent/chat does not run unsupported local grep fallback", async () => { const workspace = await mkdtemp(path.join(os.tmpdir(), "hwlab-agent-tool-blocked-")); const server = createCloudApiServer({ env: { PATH: process.env.PATH, HWLAB_CODE_AGENT_WORKSPACE: workspace } }); await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve)); try { const { port } = server.address(); const response = await fetch(`http://127.0.0.1:${port}/v1/agent/chat`, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ conversationId: "cnv_server-test-tool-unavailable", message: "请用grep搜索 package.json" }) }); assert.equal(response.status, 200); const payload = await response.json(); assert.equal(payload.status, "failed"); assert.notEqual(payload.error.code, "tool_unavailable"); assert.equal(payload.provider, "codex-stdio"); assert.equal(payload.capabilityLevel, "blocked"); assert.deepEqual(payload.toolCalls, []); assert.ok(payload.runnerLimitations.includes("no-controlled-readonly-fallback")); assert.equal(Object.hasOwn(payload, "reply"), false); } finally { await new Promise((resolve, reject) => { server.close((error) => (error ? reject(error) : resolve())); }); } }); test("cloud api /v1/agent/chat ignores OpenAI fallback and blocks when Codex stdio is unavailable", async () => { const providerRequests = []; const providerServer = createHttpServer((request, response) => { const chunks = []; request.on("data", (chunk) => chunks.push(chunk)); request.on("end", () => { const bodyText = Buffer.concat(chunks).toString("utf8"); const body = JSON.parse(bodyText); providerRequests.push({ method: request.method, url: request.url, authorizationPresent: Boolean(request.headers.authorization), accept: request.headers.accept, contentType: request.headers["content-type"], body }); response.writeHead(200, { "content-type": "text/event-stream" }); response.end([ `data: ${JSON.stringify({ type: "response.created", response: { id: "resp_server_test_stream", model: body.model, usage: null } })}`, `data: ${JSON.stringify({ type: "response.output_text.delta", response_id: "resp_server_test_stream", delta: "HWLAB Code Agent " })}`, `data: ${JSON.stringify({ type: "response.output_text.delta", response_id: "resp_server_test_stream", delta: "streaming ready." })}`, `data: ${JSON.stringify({ type: "response.completed", response: { id: "resp_server_test_stream", model: body.model, usage: null } })}`, "data: [DONE]", "" ].join("\n\n")); }); }); await new Promise((resolve) => providerServer.listen(0, "127.0.0.1", resolve)); const providerPort = providerServer.address().port; const server = createCloudApiServer({ env: { OPENAI_API_KEY: "test-openai-key-material", HWLAB_CODE_AGENT_PROVIDER: "openai", HWLAB_CODE_AGENT_ALLOW_TEXT_FALLBACK: "true", HWLAB_CODE_AGENT_MODEL: "gpt-test", HWLAB_CODE_AGENT_OPENAI_BASE_URL: `http://127.0.0.1:${providerPort}/v1/responses` } }); await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve)); try { const { port } = server.address(); const response = await fetch(`http://127.0.0.1:${port}/v1/agent/chat`, { method: "POST", headers: { "content-type": "application/json", "x-trace-id": "trc_server-test-agent-chat-stream" }, body: JSON.stringify({ conversationId: "cnv_server-test-agent-chat-stream", message: "请用一句话说明当前 HWLAB 工作台可以做什么。" }) }); assert.equal(response.status, 200); const payload = await response.json(); assert.equal(payload.status, "failed"); assert.equal(payload.conversationId, "cnv_server-test-agent-chat-stream"); assert.equal(payload.traceId, "trc_server-test-agent-chat-stream"); assert.equal(payload.provider, "codex-stdio"); assert.equal(payload.model, "gpt-test"); assert.equal(payload.backend, "hwlab-cloud-api/codex-mcp-stdio"); assert.equal(payload.capabilityLevel, "blocked"); assert.equal(payload.error.code, "codex_cli_binary_missing"); assert.equal(Object.hasOwn(payload, "reply"), false); assert.equal(JSON.stringify(payload).includes("test-openai-key-material"), false); assert.equal(providerRequests.length, 0); } finally { await new Promise((resolve, reject) => { server.close((error) => (error ? reject(error) : resolve())); }); await new Promise((resolve, reject) => { providerServer.close((error) => (error ? reject(error) : resolve())); }); } }); test("cloud api /v1/agent/chat does not call delayed provider fallback beyond legacy 4500ms UI timeout", async () => { let providerCalled = false; const server = createCloudApiServer({ callCodeAgentProvider: async ({ providerPlan, message, traceId }) => { providerCalled = true; await delay(4700); return { provider: providerPlan.provider, model: providerPlan.model, backend: providerPlan.backend, content: `延迟真实 provider stub: ${message} / ${traceId}`, usage: null, providerTrace: { source: "delayed-test-provider" } }; } }); await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve)); try { const { port } = server.address(); const startedAt = Date.now(); const response = await fetch(`http://127.0.0.1:${port}/v1/agent/chat`, { method: "POST", headers: { "content-type": "application/json", "x-trace-id": "trc_server-test-agent-chat-delayed" }, body: JSON.stringify({ conversationId: "cnv_server-test-agent-chat-delayed", message: "请用一句话说明当前 HWLAB 工作台可以做什么,稍后回答" }) }); assert.equal(response.status, 200); const payload = await response.json(); assert.equal(Date.now() - startedAt < 4500, true); assert.equal(payload.status, "failed"); assert.equal(payload.conversationId, "cnv_server-test-agent-chat-delayed"); assert.equal(payload.traceId, "trc_server-test-agent-chat-delayed"); assert.equal(payload.provider, "codex-stdio"); assert.equal(payload.capabilityLevel, "blocked"); assert.equal(providerCalled, false); assert.equal(Object.hasOwn(payload, "reply"), false); } finally { await new Promise((resolve, reject) => { server.close((error) => (error ? reject(error) : resolve())); }); } }); test("cloud api /v1/agent/chat skips delayed provider fallback when Codex stdio is unavailable", async () => { let providerCalled = false; const server = createCloudApiServer({ env: { OPENAI_API_KEY: "test-openai-key-material", HWLAB_CODE_AGENT_PROVIDER: "openai", HWLAB_CODE_AGENT_ALLOW_TEXT_FALLBACK: "true", HWLAB_CODE_AGENT_MODEL: "gpt-test", HWLAB_CODE_AGENT_OPENAI_BASE_URL: "http://127.0.0.1/provider-fixture" }, callCodeAgentProvider: async ({ providerPlan }) => { providerCalled = true; await delay(4600); const error = new Error("OpenAI Responses returned HTTP 503: slow upstream rejected"); error.code = "provider_unavailable"; error.provider = providerPlan.provider; error.model = providerPlan.model; error.backend = providerPlan.backend; error.providerStatus = 503; throw error; } }); await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve)); try { const { port } = server.address(); const startedAt = Date.now(); const response = await fetch(`http://127.0.0.1:${port}/v1/agent/chat`, { method: "POST", headers: { "content-type": "application/json", "x-trace-id": "trc_server-test-agent-chat-delayed-failure" }, body: JSON.stringify({ conversationId: "cnv_server-test-agent-chat-delayed-failure", message: "请等待后返回 provider 失败分类" }) }); assert.equal(response.status, 200); const payload = await response.json(); assert.equal(Date.now() - startedAt < 4500, true); assert.equal(payload.status, "failed"); assert.equal(payload.traceId, "trc_server-test-agent-chat-delayed-failure"); assert.equal(payload.provider, "codex-stdio"); assert.equal(payload.error.code, "codex_cli_binary_missing"); assert.equal(payload.backend, "hwlab-cloud-api/codex-mcp-stdio"); assert.equal(payload.availability.fallback.backend, "hwlab-cloud-api/openai-responses"); assert.equal(Object.hasOwn(payload, "reply"), false); assert.equal(providerCalled, false); } finally { await new Promise((resolve, reject) => { server.close((error) => (error ? reject(error) : resolve())); }); } }); test("cloud api /v1/agent/chat reports Codex stdio blocker instead of provider timeout fallback", async () => { const providerServer = createHttpServer((request, response) => { request.resume(); setTimeout(() => { response.writeHead(200, { "content-type": "application/json" }); response.end(JSON.stringify({ output_text: "too late" })); }, 250); }); await new Promise((resolve) => providerServer.listen(0, "127.0.0.1", resolve)); const providerPort = providerServer.address().port; const server = createCloudApiServer({ env: { OPENAI_API_KEY: "test-openai-key-material", HWLAB_CODE_AGENT_PROVIDER: "openai", HWLAB_CODE_AGENT_ALLOW_TEXT_FALLBACK: "true", HWLAB_CODE_AGENT_MODEL: "gpt-test", HWLAB_CODE_AGENT_OPENAI_BASE_URL: `http://127.0.0.1:${providerPort}/v1/responses` }, codeAgentTimeoutMs: 50 }); await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve)); try { const { port } = server.address(); const response = await fetch(`http://127.0.0.1:${port}/v1/agent/chat`, { method: "POST", headers: { "content-type": "application/json", "x-trace-id": "trc_server-test-agent-chat-provider-timeout" }, body: JSON.stringify({ conversationId: "cnv_server-test-agent-chat-provider-timeout", message: "请等待后回答" }) }); assert.equal(response.status, 200); const payload = await response.json(); assert.equal(payload.status, "failed"); assert.equal(payload.traceId, "trc_server-test-agent-chat-provider-timeout"); assert.equal(payload.error.code, "codex_cli_binary_missing"); assert.equal(payload.error.layer, "runner"); assert.equal(payload.error.retryable, false); assert.equal(payload.provider, "codex-stdio"); assert.equal(payload.backend, "hwlab-cloud-api/codex-mcp-stdio"); assert.equal(payload.availability.endpoint, "POST /v1/agent/chat"); assert.equal(payload.availability.runner.kind, "codex-mcp-stdio-runner"); assert.equal(Object.hasOwn(payload, "reply"), false); } finally { await new Promise((resolve, reject) => { server.close((error) => (error ? reject(error) : resolve())); }); await new Promise((resolve, reject) => { providerServer.close((error) => (error ? reject(error) : resolve())); }); } }); test("cloud api /v1/agent/chat does not call OpenAI provider 502/503 fallback", async () => { for (const status of [502, 503]) { const providerServer = createHttpServer((request, response) => { request.resume(); response.writeHead(status, { "content-type": "application/json" }); response.end(JSON.stringify({ error: { message: `upstream ${status}` } })); }); await new Promise((resolve) => providerServer.listen(0, "127.0.0.1", resolve)); const providerPort = providerServer.address().port; const server = createCloudApiServer({ env: { OPENAI_API_KEY: "test-openai-key-material", HWLAB_CODE_AGENT_PROVIDER: "openai", HWLAB_CODE_AGENT_ALLOW_TEXT_FALLBACK: "true", HWLAB_CODE_AGENT_MODEL: "gpt-test", HWLAB_CODE_AGENT_OPENAI_BASE_URL: `http://127.0.0.1:${providerPort}/v1/responses` } }); await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve)); try { const { port } = server.address(); const response = await fetch(`http://127.0.0.1:${port}/v1/agent/chat`, { method: "POST", headers: { "content-type": "application/json", "x-trace-id": `trc_server-test-agent-chat-provider-${status}` }, body: JSON.stringify({ conversationId: `cnv_server-test-agent-chat-provider-${status}`, message: `provider ${status}` }) }); assert.equal(response.status, 200); const payload = await response.json(); assert.equal(payload.status, "failed"); assert.equal(payload.traceId, `trc_server-test-agent-chat-provider-${status}`); assert.equal(payload.error.code, "codex_cli_binary_missing"); assert.equal(payload.error.layer, "runner"); assert.equal(payload.error.retryable, false); assert.equal(payload.error.blocker.traceId, `trc_server-test-agent-chat-provider-${status}`); assert.equal(payload.error.blocker.retryable, false); assert.equal(payload.error.blocker.capabilityLevel, "blocked"); assert.equal(payload.provider, "codex-stdio"); assert.equal(Object.hasOwn(payload, "reply"), false); } finally { await new Promise((resolve, reject) => { server.close((error) => (error ? reject(error) : resolve())); }); await new Promise((resolve, reject) => { providerServer.close((error) => (error ? reject(error) : resolve())); }); } } }); test("cloud api /v1/agent/chat reports provider gaps without faking a reply", async () => { const server = createCloudApiServer({ env: { PATH: "", HWLAB_CODE_AGENT_PROVIDER: "codex-cli", HWLAB_CODE_AGENT_MODEL: "gpt-test", HWLAB_CODE_AGENT_CODEX_COMMAND: "/tmp/hwlab-missing-codex" } }); await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve)); try { const { port } = server.address(); const response = await fetch(`http://127.0.0.1:${port}/v1/agent/chat`, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ message: "你好" }) }); assert.equal(response.status, 200); const payload = await response.json(); assert.match(payload.conversationId, /^cnv_/); assert.match(payload.messageId, /^msg_/); assert.equal(payload.status, "failed"); assert.equal(payload.provider, "codex-stdio"); assert.equal(payload.model, "gpt-test"); assert.equal(payload.backend, "hwlab-cloud-api/codex-mcp-stdio"); assert.equal(Number.isNaN(Date.parse(payload.createdAt)), false); assert.equal(Number.isNaN(Date.parse(payload.updatedAt)), false); assert.equal(payload.error.code, "codex_cli_binary_missing"); assert.equal(payload.error.layer, "runner"); assert.equal(payload.error.retryable, false); assert.match(payload.error.userMessage, /Codex CLI binary/u); assert.match(payload.error.message, /Codex stdio long-lived session is unavailable/u); assert.equal(payload.availability.status, "blocked"); assert.match(payload.availability.blocker, /Codex CLI command/u); assert.equal(payload.availability.reason, "codex_cli_binary_missing"); assert.equal(payload.availability.runner.ready, false); assert.equal(payload.availability.codexStdio.runtimeContract.binary.status, "missing"); assert.equal(payload.availability.codexStdio.runtimeContract.stdioProtocol.status, "blocked"); assert.ok(payload.availability.codexStdio.blockerCodes.includes("codex_cli_binary_missing")); assert.equal(payload.availability.secretRefs[0].secretName, "hwlab-code-agent-provider"); assert.equal(payload.availability.secretRefs[0].secretKey, "openai-api-key"); assert.equal(payload.availability.secretRefs[0].redacted, true); assert.match(payload.availability.summary, /will not use controlled-readonly-session-registry/u); assert.equal(JSON.stringify(payload).includes("sk-"), false); assert.equal(Object.hasOwn(payload, "reply"), false); } finally { await new Promise((resolve, reject) => { server.close((error) => (error ? reject(error) : resolve())); }); } }); function delay(ms) { return new Promise((resolve) => setTimeout(resolve, ms)); } test("cloud api /v1/agent/chat does not call empty provider text fallback", async () => { let providerCalled = false; const server = createCloudApiServer({ callCodeAgentProvider: async () => { providerCalled = true; throw new Error("empty provider fallback must not be used"); } }); await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve)); try { const { port } = server.address(); const response = await fetch(`http://127.0.0.1:${port}/v1/agent/chat`, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ conversationId: "cnv_empty-provider-text", message: "你好" }) }); assert.equal(response.status, 200); const payload = await response.json(); assert.equal(payload.conversationId, "cnv_empty-provider-text"); assert.equal(payload.status, "failed"); assert.equal(payload.error.code, "codex_cli_binary_missing"); assert.equal(payload.provider, "codex-stdio"); assert.equal(Object.hasOwn(payload, "reply"), false); assert.equal(providerCalled, false); } finally { await new Promise((resolve, reject) => { server.close((error) => (error ? reject(error) : resolve())); }); } }); test("cloud api /v1 exposes M3 IO control contract without generic frontend hardware RPC", async () => { const server = createCloudApiServer({ env: { HWLAB_M3_IO_CONTROL_ENABLED: "true", HWLAB_M3_GATEWAY_SIMU_1_URL: "http://gateway-1", HWLAB_M3_GATEWAY_SIMU_2_URL: "http://gateway-2", HWLAB_M3_PATCH_PANEL_URL: "http://patch-panel" }, m3IoRequestJson: m3ReadinessRequestJson }); await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve)); try { const { port } = server.address(); const index = await fetch(`http://127.0.0.1:${port}/v1`); assert.equal(index.status, 200); const indexPayload = await index.json(); assert.equal(indexPayload.m3IoControl.route, "/v1/m3/io"); assert.equal(indexPayload.m3IoControl.boundaries.directFrontendGatewayOrBoxAccess, false); assert.ok(indexPayload.methods.includes("m3.io.do.write")); assert.ok(indexPayload.methods.includes("m3.io.di.read")); const contract = await fetch(`http://127.0.0.1:${port}/v1/m3/io`); assert.equal(contract.status, 200); const payload = await contract.json(); assert.equal(payload.status, "available"); assert.equal(payload.controlReady, true); assert.equal(payload.readiness.status, "ready"); assert.equal(payload.chain.sourceResourceId, "res_boxsimu_1"); assert.equal(payload.chain.targetResourceId, "res_boxsimu_2"); assert.equal(payload.boundaries.frontendCallsOnly, "/v1/m3/io"); assert.equal(payload.boundaries.patchPanelOwnsPropagation, true); assert.equal(payload.boundaries.genericHardwareRpcExposedToFrontend, false); const statusResponse = await fetch(`http://127.0.0.1:${port}/v1/m3/status`); assert.equal(statusResponse.status, 200); const statusPayload = await statusResponse.json(); assert.equal(statusPayload.route, "/v1/m3/status"); assert.equal(statusPayload.chain.sourceResourceId, "res_boxsimu_1"); assert.equal(statusPayload.chain.targetResourceId, "res_boxsimu_2"); assert.equal(statusPayload.gateways.length, 2); assert.equal(statusPayload.gateways.every((gateway) => gateway.online === true), true); assert.equal(statusPayload.boxes.find((box) => box.resourceId === "res_boxsimu_1").ports.DO1.value, false); assert.equal(statusPayload.boxes.find((box) => box.resourceId === "res_boxsimu_2").ports.DI1.value, false); assert.equal(statusPayload.patchPanel.connectionActive, true); assert.equal(statusPayload.io.status, "live"); assert.equal(statusPayload.io.do1.resourceId, "res_boxsimu_1"); assert.equal(statusPayload.io.do1.port, "DO1"); assert.equal(statusPayload.io.do1.value, false); assert.equal(statusPayload.io.do1.sourceKind, "DEV-LIVE"); assert.equal(statusPayload.io.di1.resourceId, "res_boxsimu_2"); assert.equal(statusPayload.io.di1.port, "DI1"); assert.equal(statusPayload.io.di1.value, false); assert.equal(statusPayload.io.di1.sourceKind, "DEV-LIVE"); assert.equal(statusPayload.io.patchPanel.serviceId, "hwlab-patch-panel"); assert.equal(statusPayload.io.patchPanel.connectionActive, true); assert.equal(statusPayload.operation.status, "blocked"); assert.equal(statusPayload.operation.operationId, null); assert.equal(statusPayload.operation.blocker, "runtime_durable_adapter_missing"); assert.equal(statusPayload.trace.status, "blocked"); assert.equal(statusPayload.trace.traceId, null); assert.equal(statusPayload.trace.blocker, "runtime_durable_adapter_missing"); assert.equal(statusPayload.audit.status, "blocked"); assert.equal(statusPayload.audit.auditId, null); assert.equal(statusPayload.audit.blocker, "runtime_durable_adapter_missing"); assert.equal(statusPayload.runtimeDurable.status, "blocked"); assert.equal(statusPayload.runtimeDurable.green, false); assert.equal(statusPayload.runtimeDurable.blocker, "runtime_durable_adapter_missing"); assert.equal(statusPayload.runtimeDurable.readStatus.audit, "read"); assert.equal(statusPayload.runtimeDurable.readStatus.evidence, "read"); assert.equal(statusPayload.runtimeDurable.auditReady, true); assert.equal(statusPayload.runtimeDurable.evidenceReady, true); assert.equal(statusPayload.trust.durableStatus, "blocked"); assert.notEqual(statusPayload.trust.durableStatus, "green"); assert.equal(statusPayload.trust.runtime.blocker, "runtime_durable_adapter_missing"); assert.equal(statusPayload.boundaries.frontendCallsOnly, "/v1/m3/status"); assert.equal(statusPayload.boundaries.directFrontendGatewayOrBoxAccess, false); } finally { await new Promise((resolve, reject) => { server.close((error) => (error ? reject(error) : resolve())); }); } }); test("cloud api /v1/m3/status clears nested runtime blocker when trusted durable evidence is green", async () => { const server = createCloudApiServer({ runtimeStore: createGreenM3StatusRuntimeStore({ blocker: "runtime_durable_not_green" }), env: { HWLAB_M3_GATEWAY_SIMU_1_URL: "http://gateway-1", HWLAB_M3_GATEWAY_SIMU_2_URL: "http://gateway-2", HWLAB_M3_PATCH_PANEL_URL: "http://patch-panel" }, m3IoRequestJson: m3ReadinessRequestJson }); await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve)); try { const { port } = server.address(); const statusResponse = await fetch(`http://127.0.0.1:${port}/v1/m3/status`); assert.equal(statusResponse.status, 200); const statusPayload = await statusResponse.json(); assert.equal(statusPayload.contractVersion, "m3-status-v1"); assert.equal(statusPayload.status, "live"); assert.equal(statusPayload.sourceKind, "DEV-LIVE"); assert.equal(statusPayload.patchPanel.connectionActive, true); assert.equal(statusPayload.trust.durableStatus, "green"); assert.equal(statusPayload.trust.blocker, null); assert.equal(statusPayload.trust.runtime.status, "ready"); assert.equal(statusPayload.trust.runtime.blocker, null); assert.equal(statusPayload.trust.operationId, "op_m3_di_read_75fc664f-e94f-41e8-816e-aff9fdb3666f"); assert.equal(statusPayload.trust.traceId, "trc_hotfix_307_verify_1779527747043_read_false"); assert.equal(statusPayload.trust.auditId, "aud_m3_di_read_d5afd84b-3a9d-475f-894f-dff8ea5f49ff_succeeded"); assert.equal(statusPayload.trust.evidenceId, "evd_m3_di_read_75fc664f-e94f-41e8-816e-aff9fdb3666f_succeeded"); assert.equal(statusPayload.operation.status, "read"); assert.equal(statusPayload.operation.operationId, "op_m3_di_read_75fc664f-e94f-41e8-816e-aff9fdb3666f"); assert.equal(statusPayload.operation.blocker, null); assert.equal(statusPayload.trace.status, "read"); assert.equal(statusPayload.trace.traceId, "trc_hotfix_307_verify_1779527747043_read_false"); assert.equal(statusPayload.trace.blocker, null); assert.equal(statusPayload.audit.status, "read"); assert.equal(statusPayload.audit.auditId, "aud_m3_di_read_d5afd84b-3a9d-475f-894f-dff8ea5f49ff_succeeded"); assert.equal(statusPayload.audit.blocker, null); assert.equal(statusPayload.runtimeDurable.status, "green"); assert.equal(statusPayload.runtimeDurable.green, true); assert.equal(statusPayload.runtimeDurable.blocker, null); assert.equal(statusPayload.runtimeDurable.auditReady, true); assert.equal(statusPayload.runtimeDurable.evidenceReady, true); } finally { await new Promise((resolve, reject) => { server.close((error) => (error ? reject(error) : resolve())); }); } }); test("cloud api /v1/diagnostics/gate exposes Chinese single-table rows from live backend aggregation", async () => { const runtimeStore = createCloudRuntimeStore({ now: () => "2026-05-23T00:00:00.000Z" }); const server = createCloudApiServer({ runtimeStore, env: { HWLAB_M3_GATEWAY_SIMU_1_URL: "http://gateway-1", HWLAB_M3_GATEWAY_SIMU_2_URL: "http://gateway-2", HWLAB_M3_PATCH_PANEL_URL: "http://patch-panel", HWLAB_COMMIT_ID: "abc123456789", HWLAB_IMAGE_TAG: "abc1234" }, m3IoRequestJson: async () => ({ ok: false, status: 503, body: { error: { message: "gateway offline" } } }) }); await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve)); try { const { port } = server.address(); const response = await fetch(`http://127.0.0.1:${port}/v1/diagnostics/gate`); assert.equal(response.status, 200); const payload = await response.json(); assert.equal(payload.route, "/v1/diagnostics/gate"); assert.equal(payload.contractVersion, "gate-diagnostics-table-v1"); assert.equal(payload.sourceKind, "LIVE-BACKEND"); assert.equal(payload.liveBackend, true); assert.deepEqual(payload.columns, ["类别", "检查项", "状态", "归因对象", "关键细节", "证据/trace", "更新时间", "下一步"]); assert.equal(payload.safety.frontendHardcodedRows, false); assert.equal(payload.safety.sourceFixturePromoted, false); assert.equal(payload.safety.blockedIsGreen, false); assert.equal(payload.safety.hardwareControlSurface, false); assert.equal(payload.safety.codeAgentConversationSurface, false); assert.ok(payload.rows.length >= 6); assert.ok(payload.rows.every((row) => row.sourceKind === "LIVE-BACKEND")); assert.ok(payload.rows.every((row) => ["通过", "阻塞", "失败", "待验证", "信息"].includes(row.status))); assert.ok(payload.rows.some((row) => row.category === "M3" && row.status === "阻塞")); assert.ok(payload.rows.some((row) => row.check === "/health/live readiness")); assert.ok(payload.rows.some((row) => row.check === "audit.event.query")); assert.ok(payload.rows.some((row) => row.check === "evidence.record.query")); assert.ok(payload.rows.some((row) => row.check === "Cloud API 实况身份" && row.status === "通过")); assert.equal( payload.rows.some((row) => row.status === "阻塞" && row.statusKey === "pass"), false, "blocked rows must not be mapped to pass" ); } finally { await new Promise((resolve, reject) => { server.close((error) => (error ? reject(error) : resolve())); }); } }); test("cloud api /v1/m3/io returns structured blocked payload when gateway is unavailable", async () => { const runtimeStore = createCloudRuntimeStore({ now: () => "2026-05-23T00:00:00.000Z" }); const server = createCloudApiServer({ runtimeStore, env: { HWLAB_M3_GATEWAY_SIMU_1_URL: "http://gateway-1", HWLAB_M3_GATEWAY_SIMU_2_URL: "http://gateway-2", HWLAB_M3_PATCH_PANEL_URL: "http://patch-panel" }, m3IoRequestJson: async () => ({ ok: false, status: 503, body: { error: { message: "gateway offline" } } }) }); await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve)); try { const { port } = server.address(); const response = await fetch(`http://127.0.0.1:${port}/v1/m3/io`, { method: "POST", headers: { "content-type": "application/json", "x-trace-id": "trc_http_m3_gateway_missing", "x-actor-id": "usr_http_m3" }, body: JSON.stringify({ action: "do.write", value: true }) }); assert.equal(response.status, 200); const payload = await response.json(); assert.equal(payload.status, "blocked"); assert.equal(payload.accepted, false); assert.equal(payload.route, "/v1/m3/io"); assert.equal(payload.method, "POST"); assert.equal(payload.traceId, "trc_http_m3_gateway_missing"); assert.equal(payload.operationId, null); assert.equal(payload.auditId, null); assert.equal(payload.evidenceId, null); assert.equal(payload.audit.status, "not_written"); assert.equal(payload.evidence.status, "blocked"); assert.equal(payload.readback, null); assert.match(payload.blocker.zh, /gateway 未注册\/不可用/u); assert.equal(payload.controlPath.cloudApi, true); assert.equal(payload.controlPath.frontendBypass, false); assert.equal(payload.evidenceState.sourceKind, "BLOCKED"); } finally { await new Promise((resolve, reject) => { server.close((error) => (error ? reject(error) : resolve())); }); } }); async function postAgent(port, body) { const response = await fetch(`http://127.0.0.1:${port}/v1/agent/chat`, { method: "POST", headers: { "content-type": "application/json", ...(body.traceId ? { "x-trace-id": body.traceId } : {}) }, body: JSON.stringify(body) }); assert.equal(response.status, 200); return response.json(); } async function createFakeCodexCommand() { const root = await mkdtemp(path.join(os.tmpdir(), "hwlab-fake-codex-")); const packageRoot = path.join(root, "node_modules", "@openai", "codex"); const command = path.join(packageRoot, "bin", "codex.js"); const native = path.join(root, "node_modules", "@openai", "codex-linux-x64", "vendor", "x86_64-unknown-linux-musl", "codex", "codex"); await mkdir(path.dirname(command), { recursive: true }); await mkdir(path.dirname(native), { recursive: true }); await writeFile(path.join(packageRoot, "package.json"), JSON.stringify({ name: "@openai/codex", version: "0.128.0" }), "utf8"); await writeFile(command, "#!/usr/bin/env node\nconsole.log('codex 0.128.0');\n", "utf8"); await writeFile(native, "#!/bin/sh\nexit 0\n", "utf8"); await chmod(command, 0o755); await chmod(native, 0o755); return { root, command }; } async function postAgentRaw(port, body, headers = {}) { const response = await fetch(`http://127.0.0.1:${port}/v1/agent/chat`, { method: "POST", headers: { "content-type": "application/json", ...headers }, body }); return { status: response.status, body: await response.json() }; } function escapeRegExp(value) { return String(value).replace(/[.*+?^${}()|[\]\\]/gu, "\\$&"); } function createGreenM3StatusRuntimeStore({ blocker = null } = {}) { return { async readiness() { return { adapter: "postgres", durable: true, durableRequested: true, durableCapable: true, ready: true, status: "ready", blocker, reason: "Postgres durable runtime adapter completed live schema, migration, and read readiness queries", liveRuntimeEvidence: true, fixtureEvidence: false }; }, async queryAuditEvents() { return { events: [ { auditId: "aud_m3_di_read_d5afd84b-3a9d-475f-894f-dff8ea5f49ff_succeeded", traceId: "trc_hotfix_307_verify_1779527747043_read_false", actorType: "user", actorId: "usr_m3_operator", action: "m3.io.di.read", targetType: "hardware_operation", targetId: "op_m3_di_read_75fc664f-e94f-41e8-816e-aff9fdb3666f", projectId: "prj_mvp_topology", gatewaySessionId: "gws_gwsimu_2", operationId: "op_m3_di_read_75fc664f-e94f-41e8-816e-aff9fdb3666f", serviceId: "hwlab-cloud-api", outcome: "succeeded", metadata: { route: "res_boxsimu_1:DO1->hwlab-patch-panel->res_boxsimu_2:DI1" }, environment: "dev", occurredAt: "2026-05-23T00:00:00.000Z" } ], count: 1 }; }, async queryEvidenceRecords() { return { records: [ { evidenceId: "evd_m3_di_read_75fc664f-e94f-41e8-816e-aff9fdb3666f_succeeded", projectId: "prj_mvp_topology", operationId: "op_m3_di_read_75fc664f-e94f-41e8-816e-aff9fdb3666f", kind: "trace", uri: "memory://hwlab-cloud-api/op_m3_di_read_75fc664f-e94f-41e8-816e-aff9fdb3666f/m3-io-control.json", sha256: "0".repeat(64), serviceId: "hwlab-cloud-api", environment: "dev", metadata: { traceId: "trc_hotfix_307_verify_1779527747043_read_false", route: "res_boxsimu_1:DO1->hwlab-patch-panel->res_boxsimu_2:DI1" }, createdAt: "2026-05-23T00:00:00.000Z" } ], count: 1 }; } }; }