#!/usr/bin/env bun import assert from "node:assert/strict"; import { spawn } from "node:child_process"; import { existsSync } from "node:fs"; import { mkdtemp, readFile, rm } from "node:fs/promises"; import net from "node:net"; import os from "node:os"; import path from "node:path"; import { fileURLToPath } from "node:url"; import { DEV_ENDPOINT, ENVIRONMENT_DEV } from "../internal/protocol/index.mjs"; import { validateCodeAgentChatSchema } from "../internal/cloud/code-agent-chat.ts"; const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), ".."); const children = []; const bunCommand = resolveBunCommand(); const CODEX_STDIO_FEASIBILITY_BLOCKERS = new Set([ "codex_cli_binary_missing", "codex_stdio_supervisor_disabled", "codex_stdio_egress_boundary" ]); function logOk(name) { process.stdout.write(`[m1-smoke] ok ${name}\n`); } function assertCodexStdioFeasibilityBlocker(value, label = "codex stdio blocker") { assert.equal(CODEX_STDIO_FEASIBILITY_BLOCKERS.has(value), true, `${label}: ${value}`); } function freePort() { return new Promise((resolve, reject) => { const server = net.createServer(); server.on("error", reject); server.listen(0, "127.0.0.1", () => { const address = server.address(); const port = typeof address === "object" && address ? address.port : null; server.close((error) => { if (error) { reject(error); } else { resolve(port); } }); }); }); } function startNode(relativeScript, env = {}) { const command = commandForScript(relativeScript); const child = spawn(command.bin, command.args, { cwd: repoRoot, env: { ...process.env, ...env }, stdio: ["ignore", "pipe", "pipe"] }); const output = { stdout: "", stderr: "" }; child.stdout.setEncoding("utf8"); child.stderr.setEncoding("utf8"); child.stdout.on("data", (chunk) => { output.stdout += chunk; }); child.stderr.on("data", (chunk) => { output.stderr += chunk; }); const record = { child, relativeScript, output }; children.push(record); return record; } function resolveBunCommand() { const candidates = [ process.env.HWLAB_BUN_COMMAND, process.env.HOME ? path.join(process.env.HOME, ".bun/bin/bun") : null, "bun" ].filter(Boolean); return candidates.find((candidate) => !candidate.includes("/") || existsSync(candidate)) || "bun"; } function runNodeJson(relativeScript, args = [], env = {}) { return new Promise((resolve, reject) => { const command = commandForScript(relativeScript, args); const child = spawn(command.bin, command.args, { cwd: repoRoot, env: { ...process.env, ...env }, stdio: ["ignore", "pipe", "pipe"] }); let stdout = ""; let stderr = ""; child.stdout.setEncoding("utf8"); child.stderr.setEncoding("utf8"); child.stdout.on("data", (chunk) => { stdout += chunk; }); child.stderr.on("data", (chunk) => { stderr += chunk; }); child.on("error", reject); child.on("close", (code) => { if (code !== 0) { reject(new Error(`${relativeScript} ${args.join(" ")} exited with ${code}\nstdout:\n${stdout}\nstderr:\n${stderr}`)); return; } try { resolve(JSON.parse(stdout)); } catch (error) { reject(new Error(`${relativeScript} ${args.join(" ")} did not return JSON: ${error.message}\nstdout:\n${stdout}\nstderr:\n${stderr}`)); } }); }); } function commandForScript(relativeScript, args = []) { if (relativeScript.endsWith(".ts")) { return { bin: bunCommand, args: ["run", relativeScript, ...args] }; } return { bin: process.execPath, args: [relativeScript, ...args] }; } async function stopNode(record) { const { child } = record; if (child.exitCode !== null || child.signalCode !== null) { return; } await new Promise((resolve) => { const killTimer = setTimeout(() => { child.kill("SIGKILL"); }, 1500); child.once("exit", () => { clearTimeout(killTimer); resolve(); }); child.kill("SIGTERM"); }); } async function requestJson(url, options = {}) { const response = await fetch(url, { ...options, headers: { "content-type": "application/json", ...(options.headers ?? {}) } }); const text = await response.text(); const body = text ? JSON.parse(text) : null; return { response, body }; } async function waitForHttp(url, record) { const startedAt = Date.now(); let lastError; while (Date.now() - startedAt < 5000) { if (record.child.exitCode !== null) { throw new Error( `${record.relativeScript} exited before becoming ready\nstdout:\n${record.output.stdout}\nstderr:\n${record.output.stderr}` ); } try { const { response } = await requestJson(url); if (response.ok) { return; } lastError = new Error(`HTTP ${response.status}`); } catch (error) { lastError = error; } await new Promise((resolve) => setTimeout(resolve, 75)); } throw new Error( `${record.relativeScript} did not become ready at ${url}: ${lastError?.message ?? "timeout"}\nstdout:\n${record.output.stdout}\nstderr:\n${record.output.stderr}` ); } function rpcEnvelope({ id, method, params = {} }) { return { jsonrpc: "2.0", id, method, params, meta: { traceId: `trc_${id}`, serviceId: "hwlab-cloud-api", environment: ENVIRONMENT_DEV } }; } async function smokeCloudApi() { const port = await freePort(); const service = startNode("cmd/hwlab-cloud-api/main.ts", { HWLAB_CLOUD_API_HOST: "127.0.0.1", HWLAB_CLOUD_API_PORT: String(port), HWLAB_CLOUD_DB_URL: "", HWLAB_CLOUD_DB_SSL_MODE: "", HWLAB_CODE_AGENT_PROVIDER: "codex-cli", HWLAB_CODE_AGENT_MODEL: "gpt-m1-local", HWLAB_CODE_AGENT_CODEX_COMMAND: "/tmp/hwlab-missing-codex", OPENAI_API_KEY: "", PATH: "" }); const baseUrl = `http://127.0.0.1:${port}`; await waitForHttp(`${baseUrl}/live`, service); const health = await requestJson(`${baseUrl}/health`); assert.equal(health.response.status, 200); assert.equal(health.body.serviceId, "hwlab-cloud-api"); assert.equal(health.body.environment, ENVIRONMENT_DEV); assert.equal(health.body.status, "degraded"); assert.equal(health.body.db.connected, false); assert.equal(health.body.db.liveConnected, false); assert.equal(health.body.db.liveDbEvidence, false); assert.equal(health.body.db.connectionAttempted, false); assert.equal(health.body.db.connectionResult, "not_attempted_missing_env"); assert.equal(health.body.db.status, "blocked"); assert.equal(health.body.runtime.durable, false); assert.deepEqual(health.body.db.missingEnv, ["HWLAB_CLOUD_DB_URL", "HWLAB_CLOUD_DB_SSL_MODE"]); assert.equal(health.body.db.secretRefs[0].secretName, "hwlab-cloud-api-dev-db"); assert.equal(health.body.db.secretRefs[0].redacted, true); assert.equal(health.body.codeAgent.status, "partial"); assert.match(health.body.codeAgent.blocker, /Codex stdio|long-lived|Codex CLI command/u); assertCodexStdioFeasibilityBlocker(health.body.codeAgent.reason, "health codeAgent reason"); assert.equal(health.body.readiness.provider.status, "blocked"); assert.equal(health.body.readiness.provider.blocker, "provider_config_blocked"); assert.equal(health.body.readiness.sessionRunner.status, "read_only_long_lived_ready"); assert.equal(health.body.readiness.sessionRunner.ready, true); assert.equal(health.body.readiness.codexStdio.status, "blocked"); assertCodexStdioFeasibilityBlocker(health.body.readiness.codexStdio.blocker, "health readiness codexStdio blocker"); assert.equal(health.body.readiness.codeAgent.currentBlockers.includes("provider_config_blocked"), true); assert.equal(health.body.readiness.codeAgent.currentBlockers.includes(health.body.readiness.codexStdio.blocker), true); assert.ok(health.body.codeAgent.missingEnv.includes("OPENAI_API_KEY")); assert.equal(health.body.codeAgent.secretRefs[0].secretName, "hwlab-code-agent-provider"); assert.equal(health.body.codeAgent.secretRefs[0].secretKey, "openai-api-key"); assert.equal(health.body.codeAgent.secretRefs[0].redacted, true); assert.equal(JSON.stringify(health.body.codeAgent).includes("sk-"), false); const live = await requestJson(`${baseUrl}/live`); assert.equal(live.response.status, 200); assert.equal(live.body.status, "live"); const adapter = await requestJson(`${baseUrl}/v1`); assert.equal(adapter.response.status, 200); assert.equal(adapter.body.status, "degraded"); assert.ok(adapter.body.methods.includes("system.health")); assert.ok(adapter.body.methods.includes("gateway.session.register")); assert.ok(adapter.body.methods.includes("box.capability.report")); assert.ok(adapter.body.methods.includes("hardware.operation.request")); assert.ok(adapter.body.methods.includes("audit.event.query")); assert.ok(adapter.body.methods.includes("evidence.record.query")); assert.equal(adapter.body.codeAgent.endpoint, "POST /v1/agent/chat"); assert.equal(adapter.body.codeAgent.status, "partial"); assert.match(adapter.body.codeAgent.blocker, /Codex stdio|long-lived|Codex CLI command/u); assert.equal(adapter.body.codeAgent.provider, "codex-cli"); assert.equal(adapter.body.codeAgent.model, "gpt-m1-local"); assert.equal(adapter.body.codeAgent.backend, "hwlab-cloud-api/codex-cli"); assert.equal(adapter.body.codeAgent.ready, false); assert.equal(adapter.body.readiness.provider.status, "blocked"); assert.equal(adapter.body.readiness.sessionRunner.status, "read_only_long_lived_ready"); assert.equal(adapter.body.readiness.codexStdio.status, "blocked"); assert.ok(adapter.body.codeAgent.missingEnv.includes("OPENAI_API_KEY")); assert.equal(adapter.body.codeAgent.secretRefs[0].secretName, "hwlab-code-agent-provider"); assert.equal(adapter.body.codeAgent.secretRefs[0].secretKey, "openai-api-key"); assert.equal(adapter.body.codeAgent.secretRefs[0].redacted, true); assert.match(adapter.body.codeAgent.summary, /hwlab-code-agent-provider\/openai-api-key/u); assert.equal(JSON.stringify(adapter.body.codeAgent).includes("sk-"), false); const agentChat = await requestJson(`${baseUrl}/v1/agent/chat`, { method: "POST", body: JSON.stringify({ conversationId: "cnv_m1_local_chat", traceId: "trc_m1_local_chat", projectId: "prj_m1_local_contract", message: "请用一句话说明本地 SOURCE/LOCAL 合同。" }) }); assert.equal(agentChat.response.status, 200); validateCodeAgentChatSchema(agentChat.body); assert.equal(agentChat.body.conversationId, "cnv_m1_local_chat"); assert.equal(agentChat.body.sessionId, "cnv_m1_local_chat"); assert.match(agentChat.body.messageId, /^msg_/); assert.equal(agentChat.body.status, "failed"); assert.equal(agentChat.body.traceId, "trc_m1_local_chat"); assert.equal(agentChat.body.projectId, "prj_m1_local_contract"); assert.equal(agentChat.body.provider, "codex-cli"); assert.equal(agentChat.body.model, "gpt-m1-local"); assert.equal(agentChat.body.backend, "hwlab-cloud-api/codex-cli"); assert.equal(agentChat.body.error.code, "codex_cli_binary_missing"); assert.match(agentChat.body.error.message, /Codex CLI command is not available/); assert.deepEqual(agentChat.body.error.missingCommands, ["/tmp/hwlab-missing-codex"]); assert.ok(agentChat.body.error.missingEnv.includes("OPENAI_API_KEY")); assert.equal(agentChat.body.availability.status, "partial"); assert.match(agentChat.body.availability.blocker, /Codex stdio|long-lived|Codex CLI command/u); assertCodexStdioFeasibilityBlocker(agentChat.body.availability.reason, "agent chat availability reason"); assert.equal(agentChat.body.availability.fallback.status, "blocked"); assert.equal(agentChat.body.availability.fallback.reason, "provider_unavailable"); assert.equal(agentChat.body.availability.secretRefs[0].secretName, "hwlab-code-agent-provider"); assert.equal(agentChat.body.availability.secretRefs[0].secretKey, "openai-api-key"); assert.equal(agentChat.body.availability.secretRefs[0].redacted, true); assert.match(agentChat.body.availability.summary, /hwlab-code-agent-provider\/openai-api-key/u); assert.equal(JSON.stringify(agentChat.body).includes("sk-"), false); assert.equal(Object.hasOwn(agentChat.body, "reply"), false); const rpcHealth = await requestJson(`${baseUrl}/rpc`, { method: "POST", body: JSON.stringify(rpcEnvelope({ id: "req_m1_health", method: "system.health" })) }); assert.equal(rpcHealth.response.status, 200); assert.equal(rpcHealth.body.jsonrpc, "2.0"); assert.equal(rpcHealth.body.result.status, "degraded"); assert.equal(rpcHealth.body.result.serviceId, "hwlab-cloud-api"); assert.equal(rpcHealth.body.result.db.status, "blocked"); assert.equal(rpcHealth.body.meta.serviceId, "hwlab-cloud-api"); const projectId = "prj_mvp_topology"; const gatewaySessionId = "gws_m1_runtime"; const resourceId = "res_m1_runtime"; const capabilityId = "cap_m1_runtime"; const rpcGateway = await requestJson(`${baseUrl}/rpc`, { method: "POST", body: JSON.stringify( rpcEnvelope({ id: "req_m1_gateway", method: "gateway.session.register", params: { projectId, gatewaySessionId, gatewayId: "gtw_m1_runtime", serviceId: "hwlab-gateway-simu", endpoint: `http://127.0.0.1:${port}/gateway` } }) ) }); assert.equal(rpcGateway.response.status, 200); assert.equal(rpcGateway.body.result.registered, true); const rpcBox = await requestJson(`${baseUrl}/rpc`, { method: "POST", body: JSON.stringify( rpcEnvelope({ id: "req_m1_box", method: "box.resource.register", params: { projectId, gatewaySessionId, resourceId, boxId: "box_m1_runtime", resourceType: "board" } }) ) }); assert.equal(rpcBox.response.status, 200); assert.equal(rpcBox.body.result.registered, true); const rpcCapability = await requestJson(`${baseUrl}/rpc`, { method: "POST", body: JSON.stringify( rpcEnvelope({ id: "req_m1_capability", method: "box.capability.report", params: { capabilityId, resourceId, projectId, name: "shell.exec", direction: "bidirectional", valueType: "object", mutatesState: true } }) ) }); assert.equal(rpcCapability.response.status, 200); assert.equal(rpcCapability.body.result.reported, true); const rpcHardware = await requestJson(`${baseUrl}/rpc`, { method: "POST", body: JSON.stringify( rpcEnvelope({ id: "req_m1_hardware", method: "hardware.operation.request", params: { projectId, gatewaySessionId, resourceId, capabilityId, input: { command: "echo m1" } } }) ) }); assert.equal(rpcHardware.response.status, 200); assert.equal(rpcHardware.body.result.accepted, true); assert.equal(rpcHardware.body.result.status, "accepted"); assert.equal(rpcHardware.body.result.auditEvent.serviceId, "hwlab-cloud-api"); assert.equal(rpcHardware.body.result.persistence.durable, false); logOk("cloud-api health/live/rpc runtime"); } async function smokePatchPanelAndSimulators() { const gatewayPort = await freePort(); const boxPort = await freePort(); const patchPort = await freePort(); const gateway = startNode("cmd/hwlab-gateway-simu/main.mjs", { PORT: String(gatewayPort), HWLAB_GATEWAY_ID: "gwsimu_m1", HWLAB_BOX_RESOURCES: "res_boxsim_alpha,res_boxsim_beta" }); const box = startNode("cmd/hwlab-box-simu/main.mjs", { PORT: String(boxPort), HWLAB_BOX_ID: "boxsim_alpha" }); const patchPanel = startNode("cmd/hwlab-patch-panel/main.mjs", { PORT: String(patchPort), HWLAB_WIRING_CONFIG_PATH: path.join(repoRoot, "fixtures/mvp-topology/topology.json") }); const gatewayBase = `http://127.0.0.1:${gatewayPort}`; const boxBase = `http://127.0.0.1:${boxPort}`; const patchBase = `http://127.0.0.1:${patchPort}`; await Promise.all([ waitForHttp(`${gatewayBase}/health/live`, gateway), waitForHttp(`${boxBase}/health/live`, box), waitForHttp(`${patchBase}/health/live`, patchPanel) ]); const gatewayStatus = await requestJson(`${gatewayBase}/status`); assert.equal(gatewayStatus.response.status, 200); assert.equal(gatewayStatus.body.serviceId, "hwlab-gateway-simu"); assert.equal(gatewayStatus.body.gatewayId, "gwsimu_m1"); assert.equal(gatewayStatus.body.live, true); assert.deepEqual(gatewayStatus.body.boxes, ["res_boxsim_alpha", "res_boxsim_beta"]); const boxStatus = await requestJson(`${boxBase}/status`); assert.equal(boxStatus.response.status, 200); assert.equal(boxStatus.body.serviceId, "hwlab-box-simu"); assert.equal(boxStatus.body.boxId, "boxsim_alpha"); assert.equal(boxStatus.body.live, true); assert.equal(boxStatus.body.constraints.crossDevicePropagation, "patch-panel-only"); assert.equal(boxStatus.body.constraints.localLoopbackEnabled, false); assert.ok(boxStatus.body.ports.uart0); const writePort = await requestJson(`${boxBase}/ports/write`, { method: "POST", body: JSON.stringify({ port: "uart0", value: "m1-smoke", source: "local" }) }); assert.equal(writePort.response.status, 200); assert.equal(writePort.body.accepted, true); assert.equal(writePort.body.crossDevicePropagation, "patch-panel-only"); const patchStatus = await requestJson(`${patchBase}/status`); assert.equal(patchStatus.response.status, 200); assert.equal(patchStatus.body.serviceId, "hwlab-patch-panel"); assert.equal(patchStatus.body.state, "active"); assert.equal(patchStatus.body.metadata.propagation, "patch-panel-only"); assert.equal(patchStatus.body.activeConnections.length, 2); const routed = await requestJson(`${patchBase}/signals/route`, { method: "POST", body: JSON.stringify({ fromResourceId: "res_boxsim_alpha", fromPort: "uart0", value: "m1-smoke" }) }); assert.equal(routed.response.status, 200); assert.equal(routed.body.accepted, true); assert.equal(routed.body.propagatedBy, "hwlab-patch-panel"); assert.equal(routed.body.deliveryCount, 1); assert.deepEqual(routed.body.deliveries[0], { resourceId: "res_boxsim_beta", port: "uart0", value: "m1-smoke", sourceResourceId: "res_boxsim_alpha", sourcePort: "uart0", observedAt: routed.body.deliveries[0].observedAt }); logOk("patch-panel/sim status and route runtime"); } async function smokeAgentMgrLocalContract() { const stateDir = await mkdtemp(path.join(os.tmpdir(), "hwlab-m1-agent-mgr-")); const agentSessionId = "agt_m1_local_contract"; const agentMgrPort = await freePort(); let agentMgr; try { agentMgr = startNode("cmd/hwlab-agent-mgr/main.ts", { PORT: String(agentMgrPort), HWLAB_AGENT_RUNTIME_STATE_DIR: stateDir, HWLAB_ENVIRONMENT: ENVIRONMENT_DEV }); const baseUrl = `http://127.0.0.1:${agentMgrPort}`; await waitForHttp(`${baseUrl}/health/live`, agentMgr); const healthLive = await requestJson(`${baseUrl}/health/live`); assert.equal(healthLive.response.status, 200); assert.equal(healthLive.body.serviceId, "hwlab-agent-mgr"); assert.equal(healthLive.body.ok, true); assert.equal(healthLive.body.environment, ENVIRONMENT_DEV); assert.equal(healthLive.body.health.serviceId, "hwlab-agent-mgr"); assert.equal(healthLive.body.health.status, "degraded"); assert.equal(healthLive.body.health.runtimeMode, "local-dry-run"); assert.ok(healthLive.body.health.skills.missing.includes("manager.skillCommitId")); assert.ok(healthLive.body.health.skills.missing.includes("manager.skillVersion")); assert.equal(healthLive.body.skillsManifest, null); const health = await runNodeJson("cmd/hwlab-agent-mgr/main.ts", [ "health", "--state-dir", stateDir, "--skill-commit-id", "local-contract", "--skill-version", "m1-local" ]); assert.equal(health.serviceId, "hwlab-agent-mgr"); assert.equal(health.ok, true); assert.equal(health.health.serviceId, "hwlab-agent-mgr"); assert.equal(health.health.status, "ok"); assert.equal(health.health.environment, ENVIRONMENT_DEV); assert.equal(health.health.runtimeMode, "local-dry-run"); assert.equal(health.health.workspaceCleaned, false); assert.equal(health.health.skills.manager.status, "ready"); assert.equal(health.skillsManifest.services[0].serviceId, "hwlab-agent-skills"); assert.equal(health.skillsManifest.environment, ENVIRONMENT_DEV); const created = await runNodeJson("cmd/hwlab-agent-mgr/main.ts", [ "create", "--state-dir", stateDir, "--agent-session-id", agentSessionId, "--project-id", "prj_m1_local_contract", "--goal", "verify M1 local agent manager contract", "--prompt", "local contract sample", "--skill-commit-id", "local-contract", "--skill-version", "m1-local" ]); assert.equal(created.ok, true); assert.equal(created.serviceId, "hwlab-agent-mgr"); assert.equal(created.agentSessionId, agentSessionId); assert.equal(created.status, "ok"); assert.equal(created.environment, ENVIRONMENT_DEV); assert.equal(created.runtimeMode, "local-dry-run"); assert.equal(created.agentSession.serviceId, "hwlab-agent-mgr"); assert.equal(created.agentSession.projectId, "prj_m1_local_contract"); assert.equal(created.task.status, "queued"); assert.equal(created.traceCount, 1); assert.equal(created.evidenceCount, 0); const status = await runNodeJson("cmd/hwlab-agent-mgr/main.ts", [ "status", "--state-dir", stateDir, "--agent-session-id", agentSessionId ]); assert.equal(status.ok, true); assert.equal(status.serviceId, "hwlab-agent-mgr"); assert.equal(status.agentSessionId, agentSessionId); assert.equal(status.status, "ok"); assert.equal(status.task.taskId, `tsk_${agentSessionId}`); const cleanup = await runNodeJson("cmd/hwlab-agent-mgr/main.ts", [ "cleanup", "--state-dir", stateDir, "--agent-session-id", agentSessionId ]); assert.equal(cleanup.ok, true); assert.equal(cleanup.serviceId, "hwlab-agent-mgr"); assert.equal(cleanup.agentSessionId, agentSessionId); assert.equal(cleanup.status, "ok"); assert.equal(cleanup.cleanup.completed, true); assert.equal(cleanup.workspace.existsAfterCleanup, false); logOk("agent-mgr health/sample local contract"); } finally { if (agentMgr) { await stopNode(agentMgr); } await rm(stateDir, { recursive: true, force: true }); } } try { await smokeCloudApi(); await smokePatchPanelAndSimulators(); await smokeAgentMgrLocalContract(); process.stdout.write("[m1-smoke] passed\n"); } finally { await Promise.allSettled(children.map(stopNode)); }