import assert from "node:assert/strict"; import { existsSync } from "node:fs"; import { chmod, mkdir, mkdtemp, rm, writeFile } from "node:fs/promises"; import os from "node:os"; import path from "node:path"; import test from "node:test"; import { createCodeAgentSessionRegistry } from "./code-agent-session-registry.mjs"; import { handleCodeAgentChat, validateCodeAgentChatSchema } from "./code-agent-chat.mjs"; import { codexAppServerArgs, codexAppServerProviderBaseUrl, createCodexStdioSessionManager } from "./codex-stdio-session.mjs"; import { classifyCodexRunnerCapability, classifyCodeAgentChatReadiness } from "../../scripts/src/code-agent-response-contract.mjs"; import { HWLAB_M3_IO_API_BASE_URL_ENV, HWLAB_M3_IO_CAPABILITY_LEVELS, HWLAB_M3_IO_API_ROUTE, HWLAB_M3_STATUS_API_ROUTE } from "../../skills/hwlab-agent-runtime/scripts/src/m3-io-skill-client.mjs"; test("code agent session registry creates, reuses, and expires sessions without storing secrets", () => { const registry = createCodeAgentSessionRegistry({ idleTimeoutMs: 1000, idFactory: () => "ses_test-registry" }); const first = registry.acquire({ conversationId: "cnv_registry", workspace: "/workspace/hwlab", sandbox: "read-only", runnerKind: "hwlab-readonly-runner", capabilityLevel: "read-only-session-tools", traceId: "trc_registry_1", now: "2026-05-23T00:00:00.000Z" }); assert.equal(first.ok, true); assert.equal(first.reused, false); assert.equal(first.session.sessionId, "ses_test-registry"); assert.equal(first.session.status, "busy"); assert.equal(first.session.workspace, "/workspace/hwlab"); assert.equal(first.session.sandbox, "read-only"); assert.equal(first.session.runnerKind, "hwlab-readonly-runner"); assert.equal(first.session.sessionMode, "controlled-readonly-session-registry"); assert.equal(first.session.capabilityLevel, "read-only-session-tools"); assert.equal(first.session.longLivedSession, true); assert.equal(first.session.durableSession, false); assert.equal(first.session.codexStdio, false); assert.equal(first.session.writeCapable, false); assert.equal(first.session.readOnly, true); assert.equal(first.session.idleTimeoutMs, 1000); assert.equal(first.session.lastTraceId, "trc_registry_1"); assert.equal(first.session.secretMaterialStored, false); assert.equal(first.session.valuesRedacted, true); const describedCreating = registry.describe(); assert.equal(describedCreating.sessionMode, "controlled-readonly-session-registry"); assert.equal(describedCreating.longLivedSession, true); assert.equal(describedCreating.longLivedCodexStdio, false); assert.equal(describedCreating.recentSessions[0].sessionId, "ses_test-registry"); assert.equal(describedCreating.recentSessions[0].sessionMode, "controlled-readonly-session-registry"); assert.equal(describedCreating.recentSessions[0].workspace, "/workspace/hwlab"); assert.equal(describedCreating.recentSessions[0].secretMaterialStored, false); registry.release(first.session.sessionId, { conversationId: "cnv_registry", traceId: "trc_registry_1", reused: first.session.reused, now: "2026-05-23T00:00:00.010Z" }); const second = registry.acquire({ conversationId: "cnv_registry", workspace: "/workspace/hwlab", sandbox: "read-only", runnerKind: "hwlab-readonly-runner", capabilityLevel: "read-only-session-tools", traceId: "trc_registry_2", now: "2026-05-23T00:00:00.500Z" }); assert.equal(second.ok, true); assert.equal(second.reused, true); assert.equal(second.session.sessionId, "ses_test-registry"); assert.equal(second.session.turn, 2); assert.equal(second.session.lastTraceId, "trc_registry_2"); registry.release(second.session.sessionId, { conversationId: "cnv_registry", traceId: "trc_registry_2", reused: second.session.reused, now: "2026-05-23T00:00:00.510Z" }); const expired = registry.acquire({ conversationId: "cnv_registry", workspace: "/workspace/hwlab", sandbox: "read-only", runnerKind: "hwlab-readonly-runner", capabilityLevel: "read-only-session-tools", traceId: "trc_registry_expired", now: "2026-05-23T00:00:02.000Z" }); assert.equal(expired.ok, false); assert.equal(expired.code, "session_expired"); assert.equal(expired.session.status, "expired"); assert.equal(expired.blocker.sourceIssue, "pikasTech/HWLAB#317"); }); test("code agent session registry stores bounded non-sensitive conversation facts", () => { const registry = createCodeAgentSessionRegistry({ maxConversationFacts: 2, maxFactSkills: 2, idFactory: () => "ses_fact_registry" }); const acquired = registry.acquire({ conversationId: "cnv_fact_registry", workspace: "/workspace/hwlab", traceId: "trc_fact_registry_1", now: "2026-05-23T00:00:00.000Z" }); registry.release(acquired.session.sessionId, { conversationId: "cnv_fact_registry", traceId: "trc_fact_registry_1", now: "2026-05-23T00:00:00.010Z" }); registry.recordFact("cnv_fact_registry", { traceId: "trc_fact_registry_1", provider: "codex-readonly-runner", backend: "hwlab-cloud-api/codex-readonly-runner", workspace: "/workspace/hwlab", sandbox: "read-only", session: acquired.session, sessionMode: "controlled-readonly-session-registry", capabilityLevel: "read-only-session-tools", runner: { kind: "hwlab-readonly-runner", sessionId: "ses_fact_registry" }, runnerTrace: { traceId: "trc_fact_registry_1", runnerKind: "hwlab-readonly-runner", sessionMode: "controlled-readonly-session-registry", events: ["tool:pwd:completed"], valuesPrinted: false }, toolCalls: [{ name: "pwd", status: "completed", stdout: "must-not-store-output sk-secret-value OPENAI_API_KEY=secret", traceId: "trc_fact_registry_1" }], skills: { status: "ready", items: [ { name: "alpha" }, { name: "beta" }, { name: "gamma" } ], count: 3, totalCount: 3 } }, { now: "2026-05-23T00:00:00.020Z" }); registry.recordFact("cnv_fact_registry", { traceId: "trc_fact_registry_2", workspace: "/workspace/hwlab", runner: { kind: "hwlab-readonly-runner" }, toolCalls: [{ name: "skills.discover", status: "completed", stdout: "must-not-store-output sk-secret-value OPENAI_API_KEY=secret" }], skills: { status: "ready", items: [ { name: "alpha" }, { name: "beta" }, { name: "gamma" } ], count: 3, totalCount: 3 } }, { now: "2026-05-23T00:00:00.030Z" }); registry.recordFact("cnv_fact_registry", { traceId: "trc_fact_registry_3", workspace: "/workspace/hwlab", runner: { kind: "hwlab-readonly-runner" }, toolCalls: [{ name: "session.context", status: "completed" }] }, { now: "2026-05-23T00:00:00.040Z" }); const facts = registry.getConversationFacts("cnv_fact_registry"); assert.equal(facts.conversationId, "cnv_fact_registry"); assert.equal(facts.sessionId, "ses_fact_registry"); assert.equal(facts.workspace, "/workspace/hwlab"); assert.equal(facts.turnCount, 2); assert.deepEqual(facts.traceIds, ["trc_fact_registry_2", "trc_fact_registry_3"]); assert.equal(facts.valuesRedacted, true); assert.equal(facts.secretMaterialStored, false); assert.deepEqual(facts.latestSkills.names, ["alpha", "beta"]); assert.equal(facts.latestSkills.truncated, true); assert.equal(JSON.stringify(facts).includes("must-not-store-output"), false); assert.equal(JSON.stringify(facts).includes("sk-secret-value"), false); assert.equal(JSON.stringify(facts).includes("OPENAI_API_KEY=secret"), false); assert.match(facts.summary, /workspace=\/workspace\/hwlab/u); }); test("code agent session registry reports busy sessions as structured blocker", () => { const registry = createCodeAgentSessionRegistry({ idFactory: () => "ses_busy" }); const first = registry.acquire({ conversationId: "cnv_busy", workspace: "/workspace/hwlab", sandbox: "read-only", runnerKind: "hwlab-readonly-runner", capabilityLevel: "read-only-session-tools", traceId: "trc_busy_1", now: "2026-05-23T00:00:00.000Z" }); assert.equal(first.ok, true); const busy = registry.acquire({ conversationId: "cnv_busy", workspace: "/workspace/hwlab", sandbox: "read-only", runnerKind: "hwlab-readonly-runner", capabilityLevel: "read-only-session-tools", traceId: "trc_busy_2", now: "2026-05-23T00:00:01.000Z" }); assert.equal(busy.ok, false); assert.equal(busy.code, "session_busy"); assert.equal(busy.session.status, "busy"); assert.equal(busy.session.currentTraceId, "trc_busy_1"); }); test("code agent session registry blocks failed and interrupted sessions without reusing them", () => { const failedRegistry = createCodeAgentSessionRegistry({ idFactory: () => "ses_failed" }); const failedSeed = failedRegistry.acquire({ conversationId: "cnv_failed", workspace: "/workspace/hwlab", traceId: "trc_failed_1", now: "2026-05-23T00:00:00.000Z" }); assert.equal(failedSeed.ok, true); failedRegistry.fail(failedSeed.session.sessionId, { conversationId: "cnv_failed", traceId: "trc_failed_1", now: "2026-05-23T00:00:00.010Z" }); const failed = failedRegistry.acquire({ conversationId: "cnv_failed", workspace: "/workspace/hwlab", traceId: "trc_failed_2", now: "2026-05-23T00:00:01.000Z" }); assert.equal(failed.ok, false); assert.equal(failed.code, "session_failed"); assert.equal(failed.session.status, "failed"); assert.equal(failed.blocker.sourceIssue, "pikasTech/HWLAB#317"); const interruptedRegistry = createCodeAgentSessionRegistry({ idFactory: () => "ses_interrupted" }); const interruptedSeed = interruptedRegistry.acquire({ conversationId: "cnv_interrupted", workspace: "/workspace/hwlab", traceId: "trc_interrupted_1", now: "2026-05-23T00:00:00.000Z" }); assert.equal(interruptedSeed.ok, true); interruptedRegistry.interrupt(interruptedSeed.session.sessionId, { conversationId: "cnv_interrupted", traceId: "trc_interrupted_1", now: "2026-05-23T00:00:00.010Z" }); const interrupted = interruptedRegistry.acquire({ conversationId: "cnv_interrupted", workspace: "/workspace/hwlab", traceId: "trc_interrupted_2", now: "2026-05-23T00:00:01.000Z" }); assert.equal(interrupted.ok, false); assert.equal(interrupted.code, "session_interrupted"); assert.equal(interrupted.session.status, "interrupted"); }); test("read-only runner returns session registry evidence and blocked long-lived gate", async () => { const registry = createCodeAgentSessionRegistry({ idFactory: () => "ses_chat_registry" }); const first = await handleCodeAgentChat( { conversationId: "cnv_chat_registry", traceId: "trc_chat_registry_pwd", message: "pwd" }, { now: () => "2026-05-23T00:01:00.000Z", sessionRegistry: registry, env: { PATH: process.env.PATH, HWLAB_CODE_AGENT_WORKSPACE: process.cwd() } } ); validateCodeAgentChatSchema(first); assert.equal(first.status, "completed"); assert.equal(first.sessionId, "ses_chat_registry"); assert.equal(first.session.sessionId, "ses_chat_registry"); assert.equal(first.session.status, "idle"); assert.equal(first.session.sessionMode, "controlled-readonly-session-registry"); assert.equal(first.session.workspace, process.cwd()); assert.equal(first.session.lastTraceId, "trc_chat_registry_pwd"); assert.equal(first.capabilityLevel, "read-only-session-tools"); assert.equal(first.session.longLivedSession, true); assert.equal(first.runner.longLivedSession, true); assert.equal(first.longLivedSessionGate.status, "blocked"); assert.equal(first.longLivedSessionGate.pass, false); assert.ok(first.longLivedSessionGate.blockers.some((blocker) => blocker.code === "codex_stdio_blocked_readonly_session_available")); const second = await handleCodeAgentChat( { conversationId: "cnv_chat_registry", traceId: "trc_chat_registry_ls", message: "ls ." }, { now: () => "2026-05-23T00:01:01.000Z", sessionRegistry: registry, env: { PATH: process.env.PATH, HWLAB_CODE_AGENT_WORKSPACE: process.cwd() } } ); assert.equal(second.status, "completed"); assert.equal(second.sessionId, first.sessionId); assert.equal(second.sessionReuse.reused, true); assert.equal(second.sessionReuse.turn, 2); assert.equal(second.session.lastTraceId, "trc_chat_registry_ls"); assert.equal(classifyCodexRunnerCapability(second, { httpStatus: 200 }).capabilityPass, false); }); test("expired, busy, and failed session states are surfaced as structured blockers", async () => { const expiredRegistry = createCodeAgentSessionRegistry({ idleTimeoutMs: 10, idFactory: () => "ses_expired_chat" }); const first = expiredRegistry.acquire({ conversationId: "cnv_expired_chat", workspace: process.cwd(), sandbox: "read-only", runnerKind: "hwlab-readonly-runner", capabilityLevel: "read-only-session-tools", traceId: "trc_expired_seed", now: "2026-05-23T00:02:00.000Z" }); expiredRegistry.release(first.session.sessionId, { conversationId: "cnv_expired_chat", traceId: "trc_expired_seed", now: "2026-05-23T00:02:00.001Z" }); expiredRegistry.recordFact("cnv_expired_chat", { traceId: "trc_expired_seed", workspace: process.cwd(), session: first.session, runner: { kind: "hwlab-readonly-runner" }, sessionMode: "controlled-readonly-session-registry", capabilityLevel: "read-only-session-tools", toolCalls: [{ name: "pwd", status: "completed" }] }, { now: "2026-05-23T00:02:00.002Z" }); const expired = await handleCodeAgentChat( { conversationId: "cnv_expired_chat", traceId: "trc_expired_request", message: "pwd" }, { now: () => "2026-05-23T00:02:01.000Z", sessionRegistry: expiredRegistry, env: { PATH: process.env.PATH, HWLAB_CODE_AGENT_WORKSPACE: process.cwd() } } ); assert.equal(expired.status, "failed"); assert.equal(expired.error.code, "session_expired"); assert.equal(expired.error.layer, "session"); assert.equal(expired.error.retryable, true); assert.match(expired.error.userMessage, /session 已过期/u); assert.equal(expired.error.blocker.code, "session_expired"); assert.equal(expired.error.blocker.traceId, "trc_expired_request"); assert.equal(expired.session.status, "expired"); assert.equal(expired.capabilityLevel, "blocked"); assert.equal(expired.longLivedSessionGate.status, "blocked"); assert.equal(expired.conversationFacts.conversationId, "cnv_expired_chat"); assert.equal(expired.conversationFacts.sessionId, "ses_expired_chat"); assert.equal(expired.conversationFacts.turnCount, 1); assert.equal(expired.conversationFacts.recentToolCalls[0].name, "pwd"); assert.equal(expired.error.blocker.conversationFacts.sessionId, "ses_expired_chat"); const busyRegistry = createCodeAgentSessionRegistry({ idFactory: () => "ses_busy_chat" }); busyRegistry.acquire({ conversationId: "cnv_busy_chat", workspace: process.cwd(), sandbox: "read-only", runnerKind: "hwlab-readonly-runner", capabilityLevel: "read-only-session-tools", traceId: "trc_busy_seed", now: "2026-05-23T00:03:00.000Z" }); busyRegistry.recordFact("cnv_busy_chat", { traceId: "trc_busy_seed", workspace: process.cwd(), sessionId: "ses_busy_chat", runner: { kind: "hwlab-readonly-runner" }, sessionMode: "controlled-readonly-session-registry", capabilityLevel: "read-only-session-tools", toolCalls: [{ name: "skills.discover", status: "completed" }], skills: { status: "ready", items: [{ name: "alpha-skill" }], count: 1, totalCount: 1 } }, { now: "2026-05-23T00:03:00.001Z" }); const busy = await handleCodeAgentChat( { conversationId: "cnv_busy_chat", traceId: "trc_busy_request", message: "pwd" }, { now: () => "2026-05-23T00:03:01.000Z", sessionRegistry: busyRegistry, env: { PATH: process.env.PATH, HWLAB_CODE_AGENT_WORKSPACE: process.cwd() } } ); assert.equal(busy.status, "failed"); assert.equal(busy.error.code, "session_busy"); assert.equal(busy.error.layer, "session"); assert.equal(busy.error.retryable, true); assert.match(busy.error.userMessage, /正在处理上一轮/u); assert.equal(busy.error.blocker.code, "session_busy"); assert.equal(busy.session.status, "busy"); assert.equal(busy.capabilityLevel, "blocked"); assert.equal(busy.error.blockers[0].sourceIssue, "pikasTech/HWLAB#317"); assert.equal(busy.conversationFacts.conversationId, "cnv_busy_chat"); assert.equal(busy.conversationFacts.sessionId, "ses_busy_chat"); assert.equal(busy.conversationFacts.latestSkills.names[0], "alpha-skill"); const failedRegistry = createCodeAgentSessionRegistry({ idFactory: () => "ses_failed_chat" }); const failedSeed = failedRegistry.acquire({ conversationId: "cnv_failed_chat", workspace: process.cwd(), sandbox: "read-only", runnerKind: "hwlab-readonly-runner", capabilityLevel: "read-only-session-tools", traceId: "trc_failed_seed", now: "2026-05-23T00:04:00.000Z" }); failedRegistry.fail(failedSeed.session.sessionId, { conversationId: "cnv_failed_chat", traceId: "trc_failed_seed", now: "2026-05-23T00:04:00.001Z" }); failedRegistry.recordFact("cnv_failed_chat", { traceId: "trc_failed_seed", workspace: process.cwd(), session: failedSeed.session, runner: { kind: "hwlab-readonly-runner" }, sessionMode: "controlled-readonly-session-registry", capabilityLevel: "read-only-session-tools", toolCalls: [{ name: "pwd", status: "completed" }] }, { now: "2026-05-23T00:04:00.002Z" }); const failed = await handleCodeAgentChat( { conversationId: "cnv_failed_chat", traceId: "trc_failed_request", message: "pwd" }, { now: () => "2026-05-23T00:04:01.000Z", sessionRegistry: failedRegistry, env: { PATH: process.env.PATH, HWLAB_CODE_AGENT_WORKSPACE: process.cwd() } } ); assert.equal(failed.status, "failed"); assert.equal(failed.error.code, "session_failed"); assert.equal(failed.error.layer, "session"); assert.equal(failed.error.retryable, true); assert.match(failed.error.userMessage, /session 已失败/u); assert.equal(failed.error.blocker.code, "session_failed"); assert.equal(failed.session.status, "failed"); assert.equal(failed.capabilityLevel, "blocked"); assert.equal(failed.conversationFacts.conversationId, "cnv_failed_chat"); assert.equal(failed.conversationFacts.sessionId, "ses_failed_chat"); assert.equal(failed.conversationFacts.recentToolCalls[0].name, "pwd"); }); test("OpenAI fallback and codex one-shot do not pass the long-lived session gate", async () => { const fallback = await handleCodeAgentChat( { conversationId: "cnv_fallback_gate", traceId: "trc_fallback_gate", message: "你好" }, { now: () => "2026-05-23T00:04:00.000Z", callProvider: async ({ providerPlan }) => ({ provider: providerPlan.provider, model: providerPlan.model, backend: providerPlan.backend, content: "普通文本回复。", usage: null, providerTrace: { source: "test-provider" } }) } ); assert.equal(fallback.status, "completed"); assert.equal(fallback.runner.kind, "openai-responses-fallback"); assert.equal(fallback.capabilityLevel, "text-chat-only"); assert.deepEqual(fallback.runnerLimitations, [ "openai-responses-fallback", "text-chat-only", "not-workspace-tools", "not-session-runner" ]); assert.equal(fallback.runnerLimitations.includes("not-codex-stdio"), false); assert.equal(fallback.blocker.code, "text_chat_only_fallback"); assert.equal(fallback.blocker.layer, "provider"); assert.equal(fallback.blocker.retryable, false); assert.match(fallback.blocker.userMessage, /文本 fallback/u); assert.equal(fallback.longLivedSessionGate.status, "blocked"); assert.ok(fallback.longLivedSessionGate.blockers.some((blocker) => blocker.code === "openai_responses_fallback_not_session")); assert.equal(classifyCodexRunnerCapability(fallback, { httpStatus: 200 }).capabilityPass, false); const oneShot = { ...fallback, provider: "codex-cli", backend: "hwlab-cloud-api/codex-cli", runner: { kind: "codex-cli-one-shot-ephemeral", codexStdio: false, writeCapable: false, durableSession: false, longLivedSession: false }, sessionMode: "ephemeral-one-shot", implementationType: "codex-cli-one-shot-ephemeral", longLivedSessionGate: { status: "blocked", pass: false, blockers: [{ code: "one_shot_runner_not_long_lived", sourceIssue: "pikasTech/HWLAB#317" }] } }; assert.equal(classifyCodexRunnerCapability(oneShot, { httpStatus: 200 }).capabilityPass, false); assert.equal(classifyCodeAgentChatReadiness(oneShot, { realDevLive: true, httpStatus: 200 }).devLiveReplyPass, true); }); test("OpenAI provider turns receive bounded prior session facts and record continuity", async () => { const registry = createCodeAgentSessionRegistry({ idFactory: () => "ses_provider_context" }); const providerPrompts = []; const env = { PATH: process.env.PATH, HWLAB_CODE_AGENT_PROVIDER: "openai", HWLAB_CODE_AGENT_ALLOW_TEXT_FALLBACK: "1", HWLAB_CODE_AGENT_WORKSPACE: process.cwd() }; const first = await handleCodeAgentChat( { conversationId: "cnv_provider_context", traceId: "trc_provider_context_pwd", message: "pwd" }, { now: () => "2026-05-23T00:04:10.000Z", sessionRegistry: registry, env } ); assert.equal(first.status, "completed"); assert.equal(first.provider, "codex-readonly-runner"); assert.equal(first.toolCalls[0].name, "pwd"); assert.equal(first.conversationFacts.turnCount, 1); const second = await handleCodeAgentChat( { conversationId: "cnv_provider_context", traceId: "trc_provider_context_openai", message: "请根据刚才的上下文简要说明" }, { now: () => "2026-05-23T00:04:11.000Z", sessionRegistry: registry, env, callProvider: async ({ conversationFacts }) => { const promptFacts = JSON.stringify(conversationFacts); providerPrompts.push(promptFacts); assert.equal(conversationFacts.sessionId, first.sessionId); assert.equal(conversationFacts.workspace, process.cwd()); assert.equal(conversationFacts.recentToolCalls[0].name, "pwd"); return { provider: "openai-responses", model: "gpt-test", backend: "hwlab-cloud-api/openai-responses", content: `上一轮工作目录是 ${conversationFacts.workspace},session=${conversationFacts.sessionId},trace=${conversationFacts.latestTraceId}。`, usage: null, providerTrace: { source: "test-provider" } }; } } ); assert.equal(second.status, "completed"); assert.equal(second.provider, "openai-responses"); assert.equal(second.conversationFacts.turnCount, 2); assert.equal(second.conversationFacts.sessionId, first.sessionId); assert.equal(second.conversationFacts.latestProvider, "openai-responses"); assert.equal(second.conversationFacts.latestBackend, "hwlab-cloud-api/openai-responses"); assert.match(second.reply.content, /上一轮工作目录/u); assert.match(providerPrompts[0], /trc_provider_context_pwd/u); assert.equal(JSON.stringify(second.conversationFacts).includes("sk-"), false); }); test("Code Agent M3 DO write uses Skill CLI to call only HWLAB API /v1/m3/io", async () => { const calls = []; const payload = await handleCodeAgentChat( { conversationId: "cnv_m3_skill_write", traceId: "trc_m3_skill_write", message: "请通过 M3 DO1 写入 true,并回读 DI1" }, { now: () => "2026-05-23T00:05:00.000Z", env: { PATH: process.env.PATH, HWLAB_CODE_AGENT_WORKSPACE: process.cwd(), HWLAB_CODE_AGENT_HWLAB_API_BASE_URL: "http://hwlab-cloud-api.hwlab-dev.svc.cluster.local:6667", OPENAI_API_KEY: "must-not-be-used" }, callProvider: async () => { throw new Error("OpenAI fallback must not be used for M3 IO control"); }, m3IoSkillRequestJson: async (url, request) => { calls.push({ url, request }); const parsed = new URL(url); assert.equal(parsed.pathname, HWLAB_M3_IO_API_ROUTE); assert.equal(parsed.hostname, "hwlab-cloud-api.hwlab-dev.svc.cluster.local"); assert.equal(request.method, "POST"); assert.equal(request.body.action, "do.write"); assert.equal(request.body.resourceId, "res_boxsimu_1"); assert.equal(request.body.port, "DO1"); assert.equal(request.body.value, true); assert.equal(request.body.source, "hwlab-agent-runtime.m3-io"); return { ok: true, status: 200, body: { serviceId: "hwlab-cloud-api", contractVersion: "m3-io-control-v1", status: "succeeded", accepted: true, action: "do.write", traceId: "trc_m3_skill_write", operationId: "op_m3_do_write_skill", auditId: "aud_m3_do_write_skill_succeeded", evidenceId: "evd_m3_do_write_skill_succeeded", auditState: { status: "written_non_durable", durableStatus: { status: "degraded" } }, 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 } } }; } } ); 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.session.sessionMode, "controlled-m3-io-skill-cli"); assert.equal(payload.toolCalls.length, 1); assert.equal(payload.toolCalls[0].name, "hwlab-agent-runtime.m3-io"); assert.equal(payload.toolCalls[0].status, "completed"); assert.equal(payload.toolCalls[0].route, HWLAB_M3_IO_API_ROUTE); assert.equal(payload.toolCalls[0].method, "POST"); assert.equal(payload.toolCalls[0].capabilityLevel, HWLAB_M3_IO_CAPABILITY_LEVELS.ready); assert.equal(payload.toolCalls[0].controlReady, true); assert.equal(payload.toolCalls[0].accepted, true); assert.equal(payload.toolCalls[0].operationId, "op_m3_do_write_skill"); assert.equal(payload.toolCalls[0].traceId, "trc_m3_skill_write"); assert.equal(payload.toolCalls[0].auditId, "aud_m3_do_write_skill_succeeded"); assert.equal(payload.toolCalls[0].evidenceId, "evd_m3_do_write_skill_succeeded"); assert.equal(payload.toolCalls[0].readback.value, true); assert.equal(payload.runnerTrace.route, HWLAB_M3_IO_API_ROUTE); assert.equal(payload.runnerTrace.method, "POST"); assert.equal(payload.runnerTrace.accepted, true); assert.equal(payload.runnerTrace.auditId, "aud_m3_do_write_skill_succeeded"); assert.equal(payload.runnerTrace.evidenceId, "evd_m3_do_write_skill_succeeded"); assert.equal(payload.runnerTrace.readback.value, true); assert.equal(payload.runnerTrace.capabilityLevel, HWLAB_M3_IO_CAPABILITY_LEVELS.ready); assert.equal(payload.runnerTrace.controlReady, true); assert.equal(payload.runner.writeCapable, true); assert.deepEqual(payload.runner.toolPolicy.allowed, [`POST ${HWLAB_M3_IO_API_ROUTE}`]); assert.equal(payload.runner.safety.directGatewayCallsAllowed, false); assert.equal(payload.runner.safety.directBoxSimuCallsAllowed, false); assert.equal(payload.runner.safety.directPatchPanelCallsAllowed, false); assert.equal(payload.providerTrace.fallbackUsed, false); assert.equal(payload.providerTrace.method, "POST"); assert.equal(payload.providerTrace.auditId, "aud_m3_do_write_skill_succeeded"); assert.equal(payload.providerTrace.evidenceId, "evd_m3_do_write_skill_succeeded"); assert.equal(payload.providerTrace.readback.value, true); assert.equal(payload.providerTrace.capabilityLevel, HWLAB_M3_IO_CAPABILITY_LEVELS.ready); assert.equal(payload.providerTrace.controlReady, true); assert.equal(payload.skills.blockers.some((blocker) => blocker.code === "runtime_durable_not_green"), true); assert.match(payload.reply.content, /Skill CLI -> HWLAB API \/v1\/m3\/io/u); assert.equal(calls.length, 1); assert.equal(calls[0].url, `http://hwlab-cloud-api.hwlab-dev.svc.cluster.local:6667${HWLAB_M3_IO_API_ROUTE}`); assert.equal(calls[0].url.includes("gateway"), false); assert.equal(calls[0].url.includes("box-simu"), false); assert.equal(calls[0].url.includes("patch-panel"), false); }); test("Code Agent M3 DI read returns structured blocker from HWLAB API without fallback", async () => { const calls = []; const payload = await handleCodeAgentChat( { conversationId: "cnv_m3_skill_read_blocked", traceId: "trc_m3_skill_read_blocked", message: "读取 M3 DI1 readback" }, { now: () => "2026-05-23T00:06:00.000Z", env: { PATH: process.env.PATH, HWLAB_CODE_AGENT_WORKSPACE: process.cwd(), HWLAB_CODE_AGENT_HWLAB_API_BASE_URL: "http://hwlab-cloud-api.hwlab-dev.svc.cluster.local:6667", OPENAI_API_KEY: "must-not-be-used" }, callProvider: async () => { throw new Error("OpenAI fallback must not be used for M3 IO read"); }, m3IoSkillRequestJson: async (url, request) => { calls.push({ url, request }); return { ok: true, status: 200, body: { serviceId: "hwlab-cloud-api", contractVersion: "m3-io-control-v1", status: "blocked", accepted: false, action: "di.read", traceId: "trc_m3_skill_read_blocked", operationId: "op_m3_di_read_blocked", auditId: "aud_m3_di_read_blocked_failed", evidenceId: "evd_m3_di_read_blocked_failed", blocker: { code: "m3_gateway_session_unavailable", message: "gateway unavailable", zh: "gateway 未注册/不可用" }, blockerClassification: { category: "gateway_session" }, 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" }, controlPath: { cloudApi: true, gatewaySimu: false, boxSimu: false, patchPanel: false, frontendBypass: false } } }; } } ); validateCodeAgentChatSchema(payload); assert.equal(payload.status, "completed"); assert.equal(payload.provider, "hwlab-skill-cli"); assert.equal(payload.capabilityLevel, HWLAB_M3_IO_CAPABILITY_LEVELS.blocked); assert.equal(payload.blocker.code, "m3_readiness_blocked"); assert.equal(payload.blocker.layer, "m3-readiness"); assert.equal(payload.blocker.retryable, false); assert.equal(payload.blocker.route, HWLAB_M3_IO_API_ROUTE); assert.equal(payload.blocker.toolName, "hwlab-agent-runtime.m3-io"); assert.equal(payload.toolCalls[0].status, "blocked"); assert.equal(payload.toolCalls[0].route, HWLAB_M3_IO_API_ROUTE); assert.equal(payload.toolCalls[0].capabilityLevel, HWLAB_M3_IO_CAPABILITY_LEVELS.blocked); assert.equal(payload.toolCalls[0].controlReady, false); assert.equal(payload.toolCalls[0].blocker.code, "m3_gateway_session_unavailable"); assert.equal(payload.skills.blockers[0].code, "m3_gateway_session_unavailable"); assert.equal(payload.runnerTrace.capabilityLevel, HWLAB_M3_IO_CAPABILITY_LEVELS.blocked); assert.equal(payload.runnerTrace.blocker.code, "m3_gateway_session_unavailable"); assert.equal(payload.providerTrace.fallbackUsed, false); assert.equal(payload.providerTrace.capabilityLevel, HWLAB_M3_IO_CAPABILITY_LEVELS.blocked); assert.equal(payload.providerTrace.controlReady, false); assert.equal(calls.length, 1); assert.equal(new URL(calls[0].url).pathname, HWLAB_M3_IO_API_ROUTE); assert.equal(calls[0].request.body.action, "di.read"); assert.equal(calls[0].request.body.resourceId, "res_boxsimu_2"); assert.equal(calls[0].request.body.port, "DI1"); }); test("Code Agent M3 Skill CLI missing API base returns structured config blocker", async () => { const payload = await handleCodeAgentChat( { conversationId: "cnv_m3_skill_api_base_missing", traceId: "trc_m3_skill_api_base_missing", message: "读取 M3 DI1 readback" }, { now: () => "2026-05-23T00:06:30.000Z", env: { PATH: process.env.PATH, HWLAB_CODE_AGENT_WORKSPACE: process.cwd(), HWLAB_CODE_AGENT_REQUIRE_HWLAB_API_BASE_URL: "1" }, m3IoSkillRequestJson: async () => { throw new Error("missing API base must not issue a network request"); } } ); validateCodeAgentChatSchema(payload); assert.equal(payload.status, "completed"); assert.equal(payload.provider, "hwlab-skill-cli"); assert.equal(payload.capabilityLevel, HWLAB_M3_IO_CAPABILITY_LEVELS.blocked); assert.equal(payload.blocker.code, "m3_readiness_blocked"); assert.equal(payload.toolCalls[0].method, "POST"); assert.equal(payload.toolCalls[0].blocker.code, "skill_cli_api_base_missing"); assert.equal(payload.toolCalls[0].blocker.layer, "skill-cli-config"); assert.equal(payload.toolCalls[0].blocker.retryable, false); assert.deepEqual(payload.toolCalls[0].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("://"), false); }); test("Code Agent M3 Skill CLI preserves slow structured blocker beyond legacy 4500ms window", async () => { const startedAt = Date.now(); const payload = await handleCodeAgentChat( { conversationId: "cnv_m3_skill_slow_blocker", traceId: "trc_m3_skill_slow_blocker", message: "通过 HWLAB API 读取 M3 DI1 并返回结构化 blocker" }, { now: () => "2026-05-23T00:06:30.000Z", env: { PATH: process.env.PATH, HWLAB_CODE_AGENT_WORKSPACE: process.cwd(), HWLAB_CODE_AGENT_HWLAB_API_BASE_URL: "http://hwlab-cloud-api.hwlab-dev.svc.cluster.local:6667", OPENAI_API_KEY: "must-not-be-used" }, callProvider: async () => { throw new Error("OpenAI fallback must not be used for M3 IO blockers"); }, m3IoSkillRequestJson: async (url, request) => { assert.equal(new URL(url).pathname, HWLAB_M3_IO_API_ROUTE); assert.equal(request.timeoutMs, 30000); await delay(4600); return { ok: true, status: 200, body: { serviceId: "hwlab-cloud-api", contractVersion: "m3-io-control-v1", status: "blocked", accepted: false, action: "di.read", traceId: "trc_m3_skill_slow_blocker", operationId: "op_m3_di_read_slow_blocker", auditId: "aud_m3_di_read_slow_blocker_failed", evidenceId: "evd_m3_di_read_slow_blocker_failed", blocker: { code: "runtime_durable_not_green", message: "runtime durable evidence is not green", zh: "runtime durable evidence 尚未为 green" }, blockerClassification: { category: "runtime_durability", reason: "durable evidence is blocked" }, durableStatus: { status: "degraded", durable: false, blocker: "runtime_durable_not_green" }, evidenceState: { status: "blocked", sourceKind: "BLOCKED", blocker: "runtime_durable_not_green", writeStatus: "not_written" }, controlPath: { cloudApi: true, gatewaySimu: false, boxSimu: false, patchPanel: false, frontendBypass: false } } }; } } ); validateCodeAgentChatSchema(payload); assert.equal(Date.now() - startedAt >= 4500, true); 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.blocked); assert.equal(payload.sessionMode, "controlled-m3-io-skill-cli"); assert.equal(payload.runner.kind, "hwlab-m3-io-skill-cli"); assert.equal(payload.blocker.code, "m3_readiness_blocked"); assert.equal(payload.blocker.layer, "m3-readiness"); assert.equal(payload.blocker.retryable, false); assert.equal(payload.toolCalls[0].status, "blocked"); assert.equal(payload.toolCalls[0].blocker.code, "runtime_durable_not_green"); assert.equal(payload.toolCalls[0].traceId, "trc_m3_skill_slow_blocker"); assert.equal(payload.skills.status, "used"); assert.equal(payload.skills.blockers[0].code, "runtime_durable_not_green"); assert.equal(payload.runnerTrace.traceId, "trc_m3_skill_slow_blocker"); assert.equal(payload.runnerTrace.blocker.code, "runtime_durable_not_green"); assert.equal(payload.providerTrace.fallbackUsed, false); assert.equal(payload.providerTrace.traceId, "trc_m3_skill_slow_blocker"); assert.equal(Object.hasOwn(payload, "error"), false); assert.match(payload.reply.content, /Blocker: runtime_durable_not_green/u); }); test("Code Agent M3 Skill CLI HWLAB API unavailable returns retryable structured blocker", async () => { const payload = await handleCodeAgentChat( { conversationId: "cnv_m3_skill_hwlab_api_unavailable", traceId: "trc_m3_skill_hwlab_api_unavailable", message: "读取 M3 DI1 readback" }, { now: () => "2026-05-23T00:06:40.000Z", env: { PATH: process.env.PATH, HWLAB_CODE_AGENT_WORKSPACE: process.cwd(), HWLAB_API_BASE_URL: "http://hwlab-cloud-api.hwlab-dev.svc.cluster.local:6667" }, m3IoSkillRequestJson: async () => ({ ok: false, status: 503, body: null, error: "service unavailable" }) } ); validateCodeAgentChatSchema(payload); assert.equal(payload.status, "completed"); assert.equal(payload.capabilityLevel, HWLAB_M3_IO_CAPABILITY_LEVELS.blocked); assert.equal(payload.blocker.code, "hwlab_api_unavailable"); assert.equal(payload.blocker.layer, "hwlab-api"); assert.equal(payload.blocker.retryable, true); assert.equal(payload.blocker.route, HWLAB_M3_IO_API_ROUTE); assert.equal(payload.toolCalls[0].method, "POST"); assert.equal(payload.toolCalls[0].blocker.code, "hwlab_api_unavailable"); assert.equal(payload.toolCalls[0].blocker.retryable, true); }); test("Code Agent M3 DO false write exposes readback and identifier contract", async () => { const calls = []; const payload = await handleCodeAgentChat( { conversationId: "cnv_m3_skill_write_false", traceId: "trc_m3_skill_write_false", message: "通过 HWLAB API 把 res_boxsimu_1 DO1 写成 false 并读取 res_boxsimu_2 DI1" }, { now: () => "2026-05-23T00:06:30.000Z", env: { PATH: process.env.PATH, HWLAB_CODE_AGENT_WORKSPACE: process.cwd(), HWLAB_CODE_AGENT_HWLAB_API_BASE_URL: "http://hwlab-cloud-api.hwlab-dev.svc.cluster.local:6667", OPENAI_API_KEY: "must-not-be-used" }, callProvider: async () => { throw new Error("OpenAI fallback must not be used for M3 IO control"); }, 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_m3_skill_write_false", operationId: "op_m3_do_write_skill_false", auditId: "aud_m3_do_write_skill_false_succeeded", evidenceId: "evd_m3_do_write_skill_false_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, resourceId: "res_boxsimu_2", port: "DI1" } }, controlPath: { status: "succeeded", cloudApi: true, gatewaySimu: true, boxSimu: true, patchPanel: true, frontendBypass: false } } }; } } ); validateCodeAgentChatSchema(payload); assert.equal(payload.status, "completed"); assert.equal(payload.provider, "hwlab-skill-cli"); assert.equal(payload.capabilityLevel, HWLAB_M3_IO_CAPABILITY_LEVELS.ready); const tool = payload.toolCalls[0]; assert.equal(tool.route, HWLAB_M3_IO_API_ROUTE); assert.equal(tool.method, "POST"); assert.equal(tool.accepted, true); assert.equal(tool.status, "completed"); assert.equal(tool.operationId, "op_m3_do_write_skill_false"); assert.equal(tool.traceId, "trc_m3_skill_write_false"); assert.equal(tool.auditId, "aud_m3_do_write_skill_false_succeeded"); assert.equal(tool.evidenceId, "evd_m3_do_write_skill_false_succeeded"); assert.equal(tool.readback.value, false); assert.equal(tool.readback.resourceId, "res_boxsimu_2"); assert.equal(tool.readback.port, "DI1"); assert.equal(payload.runnerTrace.method, "POST"); assert.equal(payload.runnerTrace.readback.value, false); assert.equal(payload.providerTrace.readback.value, false); assert.equal(calls.length, 1); assert.equal(calls[0].url, `http://hwlab-cloud-api.hwlab-dev.svc.cluster.local:6667${HWLAB_M3_IO_API_ROUTE}`); assert.equal(calls[0].request.body.action, "do.write"); assert.equal(calls[0].request.body.value, false); assert.equal(calls[0].url.includes("gateway"), false); assert.equal(calls[0].url.includes("box-simu"), false); assert.equal(calls[0].url.includes("patch-panel"), false); }); test("Code Agent M3 true/false/read requests bypass Codex stdio and use Skill CLI /v1/m3/io", async () => { const calls = []; const codexStdioCalls = []; const forbiddenCodexStdioManager = { describe() { codexStdioCalls.push("describe"); throw new Error("M3 IO must not inspect Codex stdio before Skill CLI control"); }, async probe() { codexStdioCalls.push("probe"); throw new Error("M3 IO must not probe Codex stdio before Skill CLI control"); }, async chat() { codexStdioCalls.push("chat"); throw new Error("M3 IO must not enter Codex stdio chat"); }, cancel() { codexStdioCalls.push("cancel"); }, reapIdle() { codexStdioCalls.push("reapIdle"); } }; let sessionSeq = 0; const sessionRegistry = createCodeAgentSessionRegistry({ idFactory: () => `ses_m3_stdio_bypass_${sessionSeq += 1}` }); const cases = [ { suffix: "true", message: "通过 HWLAB API 把 DO1 置为 true,然后读取 DI1。", action: "do.write", value: true, operationId: "op_m3_do_true_bypass", auditId: "aud_m3_do_true_bypass", evidenceId: "evd_m3_do_true_bypass" }, { suffix: "false", message: "把 res_boxsimu_1 的 DO1 置为 false,并回读 res_boxsimu_2 的 DI1。", action: "do.write", value: false, operationId: "op_m3_do_false_bypass", auditId: "aud_m3_do_false_bypass", evidenceId: "evd_m3_do_false_bypass" }, { suffix: "read", message: "读取 DI1 的当前状态,走 HWLAB API 控制 M3 IO。", action: "di.read", value: false, operationId: "op_m3_di_read_bypass", auditId: "aud_m3_di_read_bypass", evidenceId: "evd_m3_di_read_bypass" } ]; for (const item of cases) { const payload = await handleCodeAgentChat( { conversationId: `cnv_m3_stdio_bypass_${item.suffix}`, traceId: `trc_m3_stdio_bypass_${item.suffix}`, message: item.message }, { now: () => "2026-05-23T00:08:40.000Z", codexStdioManager: forbiddenCodexStdioManager, sessionRegistry, 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_STDIO_ENABLED: "1", HWLAB_CODE_AGENT_CODEX_STDIO_SUPERVISOR: "repo-owned", HWLAB_CODE_AGENT_WORKSPACE: process.cwd(), HWLAB_CODE_AGENT_HWLAB_API_BASE_URL: "http://hwlab-cloud-api.hwlab-dev.svc.cluster.local:6667" }, callProvider: async () => { throw new Error("OpenAI fallback must not be used for M3 IO control"); }, m3IoSkillRequestJson: async (url, request) => { calls.push({ suffix: item.suffix, url, request }); assert.equal(new URL(url).pathname, HWLAB_M3_IO_API_ROUTE); assert.equal(request.method, "POST"); assert.equal(request.body.action, item.action); assert.equal(request.body.source, "hwlab-agent-runtime.m3-io"); if (item.action === "do.write") { assert.equal(request.body.resourceId, "res_boxsimu_1"); assert.equal(request.body.port, "DO1"); assert.equal(request.body.value, item.value); } else { assert.equal(request.body.resourceId, "res_boxsimu_2"); assert.equal(request.body.port, "DI1"); } return { ok: true, status: 200, body: { serviceId: "hwlab-cloud-api", contractVersion: "m3-io-control-v1", status: "succeeded", accepted: true, action: item.action, traceId: `trc_m3_stdio_bypass_${item.suffix}`, operationId: item.operationId, auditId: item.auditId, evidenceId: item.evidenceId, auditState: { status: item.action === "do.write" ? "written_non_durable" : "read" }, evidenceState: { status: "blocked", sourceKind: "BLOCKED", blocker: "runtime_durable_not_green", writeStatus: item.action === "do.write" ? "written_non_durable" : "not_written" }, durableStatus: { status: "degraded", durable: false, blocker: "runtime_durable_not_green" }, result: item.action === "do.write" ? { value: item.value, targetReadback: { status: "succeeded", value: item.value, resourceId: "res_boxsimu_2", port: "DI1" } } : { status: "succeeded", value: item.value, resourceId: "res_boxsimu_2", port: "DI1" }, controlPath: { status: "succeeded", cloudApi: true, gatewaySimu: true, boxSimu: true, patchPanel: true, frontendBypass: false } } }; } } ); 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.codexStdioFeasibility.reason, "m3_io_skill_cli_deterministic_route"); assert.equal(payload.capabilityLevel, HWLAB_M3_IO_CAPABILITY_LEVELS.ready); assert.equal(payload.toolCalls.length, 1); const skillTool = payload.toolCalls[0]; assert.equal(skillTool.type, "skill-cli"); assert.equal(skillTool.name, "hwlab-agent-runtime.m3-io"); assert.equal(skillTool.status, "completed"); assert.equal(skillTool.route, HWLAB_M3_IO_API_ROUTE); assert.equal(skillTool.method, "POST"); assert.equal(skillTool.accepted, true); assert.equal(skillTool.operationId, item.operationId); assert.equal(skillTool.auditId, item.auditId); assert.equal(skillTool.evidenceId, item.evidenceId); assert.equal(skillTool.readback.value, item.value); assert.equal(payload.runnerTrace.route, HWLAB_M3_IO_API_ROUTE); assert.equal(payload.runnerTrace.method, "POST"); assert.equal(payload.providerTrace.fallbackUsed, false); assert.equal(payload.providerTrace.readback.value, item.value); assert.ok(payload.runner.toolPolicy.allowed.includes(`POST ${HWLAB_M3_IO_API_ROUTE}`)); assert.equal(JSON.stringify(payload.toolCalls).includes("gateway-simu"), false); assert.equal(JSON.stringify(payload.toolCalls).includes("box-simu"), false); assert.equal(JSON.stringify(payload.toolCalls).includes("patch-panel"), false); } assert.equal(codexStdioCalls.length, 0); assert.equal(calls.length, cases.length); }); test("Code Agent parses exact Chinese M3 IO requests and returns structured Chinese result", async () => { const calls = []; const cases = [ { suffix: "read_di1", message: "读取 box-simu-2 DI1 状态", action: "di.read", value: true, zhAction: "读取 DI1", targetResource: "res_boxsimu_2:DI1" }, { suffix: "write_do1_true", message: "把 box-simu-1 DO1 设置为 true", action: "do.write", value: true, zhAction: "写入 DO1 并回读 DI1", targetResource: "res_boxsimu_1:DO1" }, { suffix: "write_do1_false", message: "把 box-simu-1 DO1 设置为 false", action: "do.write", value: false, zhAction: "写入 DO1 并回读 DI1", targetResource: "res_boxsimu_1:DO1" } ]; for (const item of cases) { const payload = await handleCodeAgentChat( { conversationId: `cnv_m3_exact_zh_${item.suffix}`, traceId: `trc_m3_exact_zh_${item.suffix}`, message: item.message }, { now: () => "2026-05-24T10:00:00.000Z", env: { PATH: process.env.PATH, HWLAB_CODE_AGENT_WORKSPACE: process.cwd(), HWLAB_CODE_AGENT_HWLAB_API_BASE_URL: "http://hwlab-cloud-api.hwlab-dev.svc.cluster.local:6667", OPENAI_API_KEY: "must-not-be-used" }, callProvider: async () => { throw new Error("OpenAI fallback must not be used for exact Chinese M3 IO"); }, m3IoSkillRequestJson: async (url, request) => { calls.push({ suffix: item.suffix, url, request }); assert.equal(new URL(url).pathname, HWLAB_M3_IO_API_ROUTE); assert.equal(new URL(url).hostname, "hwlab-cloud-api.hwlab-dev.svc.cluster.local"); assert.equal(request.method, "POST"); assert.equal(request.body.action, item.action); assert.equal(request.body.source, "hwlab-agent-runtime.m3-io"); if (item.action === "do.write") { assert.equal(request.body.resourceId, "res_boxsimu_1"); assert.equal(request.body.port, "DO1"); assert.equal(request.body.value, item.value); } else { assert.equal(request.body.resourceId, "res_boxsimu_2"); assert.equal(request.body.port, "DI1"); assert.equal(Object.hasOwn(request.body, "value"), false); } return { ok: true, status: 200, body: { serviceId: "hwlab-cloud-api", contractVersion: "m3-io-control-v1", status: "succeeded", accepted: true, action: item.action, traceId: `trc_m3_exact_zh_${item.suffix}`, operationId: `op_m3_exact_zh_${item.suffix}`, auditId: `aud_m3_exact_zh_${item.suffix}`, evidenceId: `evd_m3_exact_zh_${item.suffix}`, auditState: { status: item.action === "do.write" ? "written_non_durable" : "read" }, evidenceState: { status: "blocked", sourceKind: "BLOCKED", blocker: "runtime_durable_not_green", writeStatus: item.action === "do.write" ? "written_non_durable" : "not_written" }, durableStatus: { status: "degraded", durable: false, blocker: "runtime_durable_not_green" }, result: item.action === "do.write" ? { value: item.value, targetReadback: { status: "succeeded", value: item.value, resourceId: "res_boxsimu_2", port: "DI1" } } : { status: "succeeded", value: item.value, resourceId: "res_boxsimu_2", port: "DI1" }, controlPath: { status: "succeeded", cloudApi: true, gatewaySimu: true, boxSimu: true, patchPanel: true, frontendBypass: false } } }; } } ); validateCodeAgentChatSchema(payload); assert.equal(payload.status, "completed"); assert.equal(payload.provider, "hwlab-skill-cli"); assert.equal(payload.capabilityLevel, HWLAB_M3_IO_CAPABILITY_LEVELS.ready); assert.equal(payload.responseType, "m3_io_result"); assert.equal(payload.m3Io.zh.status, "degraded"); assert.equal(payload.m3Io.zh.targetResource, item.targetResource); assert.equal(payload.m3Io.zh.action, item.zhAction); assert.equal(payload.m3Io.zh.traceId, `trc_m3_exact_zh_${item.suffix}`); assert.equal(payload.m3Io.zh.operationId, `op_m3_exact_zh_${item.suffix}`); assert.equal(payload.m3Io.zh.auditId, `aud_m3_exact_zh_${item.suffix}`); assert.equal(payload.m3Io.zh.evidenceId, `evd_m3_exact_zh_${item.suffix}`); assert.equal(payload.m3Io.zh.controlPath.reachable, true); assert.equal(payload.m3Io.zh.controlPath.cloudApiRouteOnly, true); assert.equal(payload.m3Io.zh.runtime.durableGreen, false); assert.equal(payload.m3Io.zh.runtime.trustedGreen, false); assert.equal(payload.m3Io.zh.runtime.durableBlocker, "runtime_durable_not_green"); assert.match(payload.m3Io.zh.runtime.note, /控制链路可达,但可信记录未 green/u); assert.match(payload.reply.content, /控制链路可达,但可信记录未 green/u); assert.equal(payload.toolCalls[0].route, HWLAB_M3_IO_API_ROUTE); assert.equal(payload.toolCalls[0].method, "POST"); assert.equal(payload.toolCalls[0].hwlabApi.cloudApiOnly, true); assert.equal(payload.toolCalls[0].directGatewayCalls, false); assert.equal(payload.toolCalls[0].directBoxCalls, false); assert.equal(payload.toolCalls[0].directPatchPanelCalls, false); assert.equal(payload.providerTrace.fallbackUsed, false); } assert.equal(calls.length, cases.length); for (const call of calls) { assert.equal(call.url.includes("gateway"), false); assert.equal(call.url.includes("box-simu"), false); assert.equal(call.url.includes("patch-panel"), false); } }); test("Code Agent ambiguous M3 IO control request returns structured blocker without Codex stdio", async () => { const codexStdioCalls = []; const payload = await handleCodeAgentChat( { conversationId: "cnv_m3_ambiguous_stdio_bypass", traceId: "trc_m3_ambiguous_stdio_bypass", message: "通过 HWLAB API 控制 M3 IO。" }, { now: () => "2026-05-23T00:08:50.000Z", codexStdioManager: { describe() { codexStdioCalls.push("describe"); throw new Error("ambiguous M3 IO must not inspect Codex stdio"); }, async probe() { codexStdioCalls.push("probe"); throw new Error("ambiguous M3 IO must not probe Codex stdio"); }, async chat() { codexStdioCalls.push("chat"); throw new Error("ambiguous M3 IO must not enter Codex stdio chat"); }, cancel() { codexStdioCalls.push("cancel"); }, reapIdle() { codexStdioCalls.push("reapIdle"); } }, env: { PATH: process.env.PATH, OPENAI_API_KEY: "test-openai-key-material", HWLAB_CODE_AGENT_PROVIDER: "codex-stdio", HWLAB_CODE_AGENT_CODEX_STDIO_ENABLED: "1", HWLAB_CODE_AGENT_CODEX_STDIO_SUPERVISOR: "repo-owned", HWLAB_CODE_AGENT_WORKSPACE: process.cwd(), HWLAB_CODE_AGENT_HWLAB_API_BASE_URL: "http://hwlab-cloud-api.hwlab-dev.svc.cluster.local:6667" }, callProvider: async () => { throw new Error("OpenAI fallback must not be used for ambiguous M3 IO"); }, m3IoSkillRequestJson: async () => { throw new Error("ambiguous M3 IO must block before any HWLAB API mutation request"); } } ); validateCodeAgentChatSchema(payload); assert.equal(payload.status, "completed"); assert.equal(payload.provider, "hwlab-skill-cli"); assert.equal(payload.capabilityLevel, HWLAB_M3_IO_CAPABILITY_LEVELS.blocked); assert.equal(payload.blocker.code, "m3_readiness_blocked"); assert.equal(payload.toolCalls.length, 1); assert.equal(payload.toolCalls[0].status, "blocked"); assert.equal(payload.toolCalls[0].route, HWLAB_M3_IO_API_ROUTE); assert.equal(payload.toolCalls[0].blocker.code, "m3_io_intent_action_missing"); assert.equal(payload.toolCalls[0].hwlabApi.source, "intent-validation"); assert.equal(payload.toolCalls[0].accepted, false); assert.equal(payload.toolCalls[0].operationId, null); assert.equal(payload.codexStdioFeasibility.skipped, true); assert.equal(payload.providerTrace.fallbackUsed, false); assert.match(payload.reply.content, /Blocker: m3_io_intent_action_missing/u); assert.equal(codexStdioCalls.length, 0); }); test("Code Agent blocks out-of-scope or dangerous Chinese M3 IO requests before HWLAB API call", async () => { const messages = [ { suffix: "real_hardware", message: "把真实硬件 M3 DO1 设置为 true", code: "m3_io_scope_blocked" }, { suffix: "shell", message: "用任意 shell 批量脚本绕过 patch-panel 写入 M3 DO1 true", code: "m3_io_scope_blocked" }, { suffix: "di2", message: "读取 box-simu-2 DI2 状态", code: "m3_io_target_unsupported" }, { suffix: "wrong_box", message: "把 box-simu-2 DO1 设置为 true", code: "m3_io_target_unsupported" }, { suffix: "serial_pwm", message: "通过串口读取 M3 PWM 状态", code: "m3_io_scope_blocked" } ]; for (const item of messages) { const payload = await handleCodeAgentChat( { conversationId: `cnv_m3_dangerous_zh_${item.suffix}`, traceId: `trc_m3_dangerous_zh_${item.suffix}`, message: item.message }, { now: () => "2026-05-24T10:01:00.000Z", env: { PATH: process.env.PATH, HWLAB_CODE_AGENT_WORKSPACE: process.cwd(), HWLAB_CODE_AGENT_HWLAB_API_BASE_URL: "http://hwlab-cloud-api.hwlab-dev.svc.cluster.local:6667", OPENAI_API_KEY: "must-not-be-used" }, callProvider: async () => { throw new Error("OpenAI fallback must not be used for dangerous M3 IO"); }, m3IoSkillRequestJson: async () => { throw new Error("dangerous or out-of-scope M3 IO must block before HWLAB API request"); } } ); validateCodeAgentChatSchema(payload); assert.equal(payload.status, "completed"); assert.equal(payload.provider, "hwlab-skill-cli"); assert.equal(payload.capabilityLevel, HWLAB_M3_IO_CAPABILITY_LEVELS.blocked); assert.equal(payload.responseType, "m3_io_blocker"); assert.equal(payload.blocker.code, "m3_readiness_blocked"); assert.equal(payload.toolCalls.length, 1); assert.equal(payload.toolCalls[0].status, "blocked"); assert.equal(payload.toolCalls[0].route, HWLAB_M3_IO_API_ROUTE); assert.equal(payload.toolCalls[0].blocker.code, item.code); assert.equal(payload.toolCalls[0].hwlabApi.source, "intent-validation"); assert.equal(payload.toolCalls[0].accepted, false); assert.equal(payload.toolCalls[0].operationId, null); assert.equal(payload.toolCalls[0].directGatewayCalls, false); assert.equal(payload.toolCalls[0].directBoxCalls, false); assert.equal(payload.toolCalls[0].directPatchPanelCalls, false); assert.equal(payload.providerTrace.fallbackUsed, false); assert.equal(payload.m3Io.zh.status, "blocked"); assert.equal(payload.m3Io.zh.controlPath.cloudApiRouteOnly, true); assert.match(payload.m3Io.zh.blocker.message, /当前只支持/u); assert.match(payload.reply.content, /M3 IO 阻塞/u); assert.match(payload.reply.content, /Blocker:/u); } }); test("Code Agent M3 status uses Skill CLI GET /v1/m3/status and keeps route evidence", async () => { const calls = []; const payload = await handleCodeAgentChat( { conversationId: "cnv_m3_status_skill", traceId: "trc_m3_status_skill", message: "读取 M3 status 聚合状态" }, { now: () => "2026-05-23T00:09:10.000Z", env: { PATH: process.env.PATH, HWLAB_CODE_AGENT_WORKSPACE: process.cwd(), HWLAB_CODE_AGENT_HWLAB_API_BASE_URL: "http://hwlab-cloud-api.hwlab-dev.svc.cluster.local:6667", OPENAI_API_KEY: "must-not-be-used" }, callProvider: async () => { throw new Error("OpenAI fallback must not be used for M3 status"); }, m3IoSkillRequestJson: async (url, request) => { calls.push({ url, request }); assert.equal(new URL(url).pathname, HWLAB_M3_STATUS_API_ROUTE); assert.equal(request.method, "GET"); return { ok: true, status: 200, body: { status: "live", sourceKind: "DEV-LIVE", traceId: "trc_m3_status_skill", boxes: [{ id: "boxsimu_2", resourceId: "res_boxsimu_2", online: true, ports: { DI1: { value: false } } }], patchPanel: { serviceId: "hwlab-patch-panel", observable: true, connectionActive: true }, trust: { operationId: "op_m3_status_skill", traceId: "trc_m3_status_skill", auditId: "aud_m3_status_skill", evidenceId: "evd_m3_status_skill", durableStatus: "green", blocker: null, readStatus: { audit: "read", evidence: "read" }, runtime: { durable: true } } } }; } } ); validateCodeAgentChatSchema(payload); assert.equal(payload.status, "completed"); assert.equal(payload.provider, "hwlab-skill-cli"); assert.equal(payload.capabilityLevel, HWLAB_M3_IO_CAPABILITY_LEVELS.readonly); assert.equal(payload.toolCalls[0].route, HWLAB_M3_STATUS_API_ROUTE); assert.equal(payload.toolCalls[0].method, "GET"); assert.equal(payload.toolCalls[0].accepted, true); assert.equal(payload.toolCalls[0].operationId, "op_m3_status_skill"); assert.equal(payload.toolCalls[0].readback.value, false); assert.equal(payload.runnerTrace.route, HWLAB_M3_STATUS_API_ROUTE); assert.equal(payload.providerTrace.method, "GET"); assert.equal(payload.providerTrace.fallbackUsed, false); assert.equal(calls.length, 1); }); test("Code Agent blocks direct gateway or patch-panel requests instead of using M3 Skill CLI", async () => { const direct = await handleCodeAgentChat( { conversationId: "cnv_m3_direct_blocked", traceId: "trc_m3_direct_blocked", message: "请直接调用 gateway-simu /invoke 写入 M3 DO1 true" }, { now: () => "2026-05-23T00:07:00.000Z", env: { PATH: process.env.PATH, HWLAB_CODE_AGENT_WORKSPACE: process.cwd(), HWLAB_CODE_AGENT_HWLAB_API_BASE_URL: "http://hwlab-cloud-api.hwlab-dev.svc.cluster.local:6667" }, m3IoSkillRequestJson: async () => { throw new Error("direct gateway request must not reach the M3 skill CLI"); } } ); validateCodeAgentChatSchema(direct); assert.equal(direct.status, "failed"); assert.equal(direct.error.code, "security_blocked"); assert.equal(direct.error.layer, "security"); assert.equal(direct.error.retryable, false); assert.match(direct.error.userMessage, /安全边界/u); assert.equal(direct.error.blocker.toolName, "security.hardware-boundary"); assert.equal(direct.toolCalls[0].name, "security.hardware-boundary"); assert.match(direct.error.message, /Skill CLI -> HWLAB API \/v1\/m3\/io/u); }); function delay(ms) { return new Promise((resolve) => setTimeout(resolve, ms)); } function escapeRegExp(value) { return String(value).replace(/[.*+?^${}()|[\]\\]/gu, "\\$&"); } 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 prepareFakeCodexHome(root = null) { const codexHome = root ?? await mkdtemp(path.join(os.tmpdir(), "hwlab-codex-home-")); await mkdir(codexHome, { recursive: true }); await writeFile(path.join(codexHome, "config.toml"), "model = \"gpt-test\"\n", "utf8"); await writeFile(path.join(codexHome, "auth.json"), "{\"auth\":\"redacted-test-placeholder\"}\n", "utf8"); return codexHome; } function createFakeAppServerClient({ calls, responses = ["first stdio reply", "second stdio reply"], failedTurn = null, retryingTurn = null } = {}) { let notificationHandler = null; let turn = 0; return { async initialize() { calls?.push({ method: "initialize" }); return { initialized: true }; }, setNotificationHandler(handler) { notificationHandler = typeof handler === "function" ? handler : null; }, async startThread(args) { calls?.push({ method: "thread/start", args }); notificationHandler?.({ method: "thread/started", params: { thread: { id: "thread_stdio_ready" } } }); return { threadId: "thread_stdio_ready" }; }, async resumeThread(args) { calls?.push({ method: "thread/resume", args }); notificationHandler?.({ method: "thread/started", params: { thread: { id: args.threadId } } }); return { threadId: args.threadId }; }, async startTurn(args) { calls?.push({ method: "turn/start", args }); turn += 1; const turnId = `turn_stdio_${turn}`; notificationHandler?.({ method: "turn/started", params: { turn: { id: turnId } } }); if (retryingTurn && turn === retryingTurn.turn) { notificationHandler?.({ method: "error", params: { threadId: args.threadId, turnId, willRetry: true, error: { message: retryingTurn.message } } }); } if (failedTurn && turn === failedTurn.turn) { notificationHandler?.({ method: "turn/completed", params: { turn: { id: turnId, status: "failed", error: { message: failedTurn.message } } } }); return { turnId }; } const text = responses[turn - 1] ?? responses.at(-1) ?? "stdio reply"; notificationHandler?.({ method: "item/agentMessage/delta", params: { itemId: `item_${turn}`, delta: text } }); notificationHandler?.({ method: "item/completed", params: { item: { id: `item_${turn}`, type: "agentMessage", text } } }); notificationHandler?.({ method: "turn/completed", params: { turn: { id: turnId, status: "completed" } } }); return { turnId }; }, close() {} }; } function createTimedFakeAppServerClient({ calls, text = "timed stdio reply", eventEveryMs = 20, eventCount = 0, completeAfterMs = null } = {}) { let notificationHandler = null; let turn = 0; const timers = new Set(); const later = (ms, callback) => { const timer = setTimeout(() => { timers.delete(timer); callback(); }, ms); timers.add(timer); }; return { async initialize() { calls?.push({ method: "initialize" }); return { initialized: true }; }, setNotificationHandler(handler) { notificationHandler = typeof handler === "function" ? handler : null; }, async startThread(args) { calls?.push({ method: "thread/start", args }); notificationHandler?.({ method: "thread/started", params: { thread: { id: "thread_stdio_timed" } } }); return { threadId: "thread_stdio_timed" }; }, async resumeThread(args) { calls?.push({ method: "thread/resume", args }); notificationHandler?.({ method: "thread/started", params: { thread: { id: args.threadId } } }); return { threadId: args.threadId }; }, async startTurn(args) { calls?.push({ method: "turn/start", args }); turn += 1; const turnId = `turn_stdio_timed_${turn}`; notificationHandler?.({ method: "turn/started", params: { turn: { id: turnId } } }); for (let index = 1; index <= eventCount; index += 1) { later(eventEveryMs * index, () => { notificationHandler?.({ method: "item/commandExecution/outputDelta", params: { delta: `progress-${index}` } }); }); } if (Number.isInteger(completeAfterMs)) { later(completeAfterMs, () => { notificationHandler?.({ method: "item/agentMessage/delta", params: { itemId: `item_${turn}`, delta: text } }); notificationHandler?.({ method: "item/completed", params: { item: { id: `item_${turn}`, type: "agentMessage", text } } }); notificationHandler?.({ method: "turn/completed", params: { turn: { id: turnId, status: "completed" } } }); }); } return { turnId }; }, close() { for (const timer of timers) clearTimeout(timer); timers.clear(); } }; } test("Code Agent M3 Skill CLI blocks missing service-local HWLAB API base URL before loopback fallback", async () => { const payload = await handleCodeAgentChat( { conversationId: "cnv_m3_missing_api_base", traceId: "trc_m3_missing_api_base", message: "通过 HWLAB API 把 res_boxsimu_1 的 DO1 写成 true,然后读取 res_boxsimu_2 的 DI1。" }, { now: () => "2026-05-23T00:07:30.000Z", env: { PATH: process.env.PATH, HWLAB_CODE_AGENT_WORKSPACE: process.cwd(), OPENAI_API_KEY: "must-not-be-used" }, callProvider: async () => { throw new Error("OpenAI fallback must not be used for M3 IO"); }, m3IoSkillRequestJson: async () => { throw new Error("missing HWLAB API base URL must block before any HTTP request"); } } ); validateCodeAgentChatSchema(payload); assert.equal(payload.status, "completed"); assert.equal(payload.provider, "hwlab-skill-cli"); assert.equal(payload.capabilityLevel, HWLAB_M3_IO_CAPABILITY_LEVELS.blocked); assert.equal(payload.toolCalls.length, 1); assert.equal(payload.toolCalls[0].status, "blocked"); assert.equal(payload.toolCalls[0].blocker.code, "skill_cli_api_base_missing"); assert.equal(payload.toolCalls[0].blocker.layer, "skill-cli-config"); assert.equal(payload.toolCalls[0].blocker.retryable, false); assert.deepEqual(payload.toolCalls[0].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(payload.toolCalls[0].hwlabApi.source, "missing-config"); assert.equal(payload.toolCalls[0].hwlabApi.redactedUrl, null); assert.equal(payload.toolCalls[0].command.includes("127.0.0.1:6667"), false); assert.equal(payload.toolCalls[0].accepted, false); assert.equal(payload.toolCalls[0].operationId, null); assert.equal(payload.session.capabilityLevel, HWLAB_M3_IO_CAPABILITY_LEVELS.blocked); assert.equal(payload.runnerTrace.blocker.code, "skill_cli_api_base_missing"); assert.equal(payload.runnerTrace.capabilityLevel, HWLAB_M3_IO_CAPABILITY_LEVELS.blocked); assert.equal(payload.providerTrace.fallbackUsed, false); assert.equal(payload.skills.blockers[0].code, "skill_cli_api_base_missing"); }); test("Codex stdio manager reports concrete blockers without falling back to readonly", async () => { const manager = createCodexStdioSessionManager({ idFactory: () => "ses_stdio_blocked" }); const blocked = await handleCodeAgentChat( { conversationId: "cnv_stdio_blocked", traceId: "trc_stdio_blocked", message: "请用同一个 Code Agent session 继续" }, { now: () => "2026-05-23T00:05:00.000Z", codexStdioManager: manager, env: { PATH: "", HWLAB_CODE_AGENT_PROVIDER: "codex-stdio", HWLAB_CODE_AGENT_MODEL: "gpt-test", HWLAB_CODE_AGENT_CODEX_COMMAND: "/tmp/hwlab-missing-codex", HWLAB_CODE_AGENT_CODEX_STDIO_ENABLED: "1", HWLAB_CODE_AGENT_CODEX_STDIO_SUPERVISOR: "repo-owned", HWLAB_CODE_AGENT_WORKSPACE: process.cwd() } } ); assert.equal(blocked.status, "failed"); assert.equal(blocked.provider, "codex-stdio"); assert.equal(blocked.backend, "hwlab-cloud-api/codex-app-server-stdio"); assert.equal(blocked.error.code, "codex_cli_binary_missing"); assert.equal(blocked.error.layer, "runner"); assert.equal(blocked.error.retryable, false); assert.match(blocked.error.userMessage, /Codex CLI|Codex stdio/u); assert.equal(blocked.error.blocker.runner, "codex-app-server-stdio-runner"); assert.ok(blocked.error.blockers.some((blocker) => blocker.code === "codex_cli_binary_missing")); assert.ok(blocked.availability.codexStdio.blockerCodes.includes("stdio_protocol_not_wired")); assert.equal(blocked.capabilityLevel, "blocked"); assert.equal(blocked.longLivedSessionGate.status, "blocked"); assert.equal(blocked.availability.ready, false); assert.equal(blocked.availability.codexStdio.status, "blocked"); assert.equal(Object.hasOwn(blocked, "reply"), false); }); test("Codex stdio feasibility verifies writable workspace, CODEX_HOME, and version-probed command", async () => { const workspace = await mkdtemp(path.join(os.tmpdir(), "hwlab-stdio-workspace-")); const codexHome = await mkdtemp(path.join(os.tmpdir(), "hwlab-codex-home-")); const fakeCodex = await createFakeCodexCommand(); try { await prepareFakeCodexHome(codexHome); const manager = createCodexStdioSessionManager(); const availability = manager.describe({ env: { PATH: process.env.PATH, OPENAI_API_KEY: "test-openai-key-material", CODEX_HOME: codexHome, 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_OPENAI_BASE_URL: "http://172.26.26.227:17680/v1/responses" } }); assert.equal(availability.command, fakeCodex.command); assert.equal(availability.binary.present, true); assert.equal(availability.binary.versionDetected, true); assert.equal(availability.workspace, workspace); assert.equal(availability.workspaceState.exists, true); assert.equal(availability.workspaceState.readable, true); assert.equal(availability.workspaceState.writable, true); assert.equal(availability.runtimeContract.workspaceMount.status, "ready"); assert.equal(availability.codexHome, codexHome); assert.equal(availability.codexHomeState.exists, true); assert.equal(availability.codexHomeState.readable, true); assert.equal(availability.codexHomeState.writable, true); assert.equal(availability.runtimeContract.codexHome.status, "ready"); assert.equal(availability.codexHomeFiles.configPresent, true); assert.equal(availability.codexHomeFiles.authPresent, true); assert.equal(availability.childEnvBoundary.forbiddenEnvPresent, false); assert.ok(availability.childEnvBoundary.strippedEnvKeys.includes("OPENAI_API_KEY")); assert.ok(availability.childEnvBoundary.strippedEnvKeys.includes("HWLAB_CODE_AGENT_OPENAI_BASE_URL")); assert.deepEqual(availability.childEnvBoundary.noProxy.missing, []); assert.equal(availability.startupReady, true); assert.equal(availability.canStartLongLivedCodexStdio, true); assert.equal(availability.ready, false); assert.ok(availability.blockerCodes.includes("stdio_protocol_not_wired")); assert.equal(availability.blockerCodes.includes("workspace_mount_missing"), false); assert.equal(availability.blockerCodes.includes("workspace_write_boundary_blocked"), false); assert.equal(availability.blockerCodes.includes("codex_home_missing"), false); assert.equal(availability.blockerCodes.includes("codex_home_write_blocked"), false); } finally { await rm(workspace, { recursive: true, force: true }); await rm(codexHome, { recursive: true, force: true }); await rm(fakeCodex.root, { recursive: true, force: true }); } }); test("Codex app-server args pin repo-owned DEV responses egress without leaking provider env", () => { const env = { HWLAB_CODE_AGENT_OPENAI_BASE_URL: "http://172.26.26.227:17680/v1/responses", OPENAI_API_KEY: "test-openai-key-material" }; assert.equal(codexAppServerProviderBaseUrl(env), "http://172.26.26.227:17680/v1"); const args = codexAppServerArgs(env); assert.deepEqual(args.slice(0, 1), ["app-server"]); assert.deepEqual(args.slice(-2), ["--listen", "stdio://"]); assert.ok(args.includes("model_provider=\"OpenAI\"")); assert.ok(args.includes("model_providers.OpenAI.base_url=\"http://172.26.26.227:17680/v1\"")); assert.ok(args.includes("model_providers.OpenAI.wire_api=\"responses\"")); assert.equal(args.join(" ").includes("test-openai-key-material"), false); assert.equal(args.join(" ").includes("/v1/responses"), false); }); test("repo-owned Codex stdio manager creates and reuses long-lived sessions with trace evidence", async () => { const calls = []; const fakeCodex = await createFakeCodexCommand(); const codexHome = await prepareFakeCodexHome(); const registry = createCodeAgentSessionRegistry(); const manager = createCodexStdioSessionManager({ idFactory: () => "ses_stdio_ready", createRpcClient: async () => createFakeAppServerClient({ calls }) }); const 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: process.cwd(), HWLAB_CODE_AGENT_OPENAI_BASE_URL: "http://172.26.26.227:17680/v1/responses" }; const first = await handleCodeAgentChat( { conversationId: "cnv_stdio_ready", traceId: "trc_stdio_ready_1", message: "first" }, { now: () => "2026-05-23T00:06:00.000Z", codexStdioManager: manager, sessionRegistry: registry, env } ); validateCodeAgentChatSchema(first); assert.equal(first.status, "completed"); assert.equal(first.provider, "codex-stdio"); assert.equal(first.runner.kind, "codex-app-server-stdio-runner"); assert.equal(first.sessionMode, "codex-app-server-stdio-long-lived"); assert.equal(first.implementationType, "repo-owned-codex-app-server-stdio-session"); assert.equal(first.session.longLivedSession, true); assert.equal(first.session.codexStdio, true); assert.equal(first.runner.writeCapable, true); assert.equal(first.runner.durableSession, true); assert.equal(first.capabilityLevel, "long-lived-codex-stdio-session"); assert.equal(first.longLivedSessionGate.status, "pass"); assert.equal(first.longLivedSessionGate.pass, true); assert.deepEqual(first.runnerLimitations, ["hardware-control-via-cloud-api-only", "secret-values-redacted"]); assert.equal(first.runnerLimitations.includes("not-codex-stdio"), false); assert.equal(first.runnerLimitations.includes("not-write-capable"), false); assert.equal(first.runnerLimitations.includes("process-local-session-registry"), false); assert.equal(classifyCodexRunnerCapability(first, { httpStatus: 200 }).capabilityPass, true); const second = await handleCodeAgentChat( { conversationId: "cnv_stdio_ready", traceId: "trc_stdio_ready_2", message: "second" }, { now: () => "2026-05-23T00:06:01.000Z", codexStdioManager: manager, sessionRegistry: registry, env } ); assert.equal(second.status, "completed"); assert.equal(second.sessionId, first.sessionId); assert.equal(second.conversationId, first.conversationId); assert.equal(second.sessionReuse.reused, true); assert.equal(second.sessionReuse.turn, 2); assert.equal(second.runnerTrace.sessionId, first.sessionId); assert.equal(second.runnerTrace.sessionReused, true); assert.equal(second.workspace, first.workspace); assert.equal(second.sandbox, first.sandbox); assert.equal(second.toolCalls[0].name, "codex-app-server.thread/resume+turn/start"); assert.equal(second.reply.content, "second stdio reply"); const turnPathCalls = calls.filter((call) => call.method !== "initialize"); assert.deepEqual(turnPathCalls.map((call) => call.method), ["thread/start", "turn/start", "thread/resume", "turn/start"]); assert.equal(turnPathCalls[0].args.cwd, process.cwd()); assert.equal(turnPathCalls[2].args.threadId, "thread_stdio_ready"); assert.match(turnPathCalls[3].args.prompt, /Prior session facts/u); assert.match(turnPathCalls[3].args.prompt, /ses_stdio_ready/u); assert.match(turnPathCalls[3].args.prompt, /trc_stdio_ready_1/u); assert.match(turnPathCalls[3].args.prompt, new RegExp(escapeRegExp(process.cwd()), "u")); assert.equal(second.providerTrace.protocol, "codex-app-server-jsonrpc-stdio"); await rm(fakeCodex.root, { recursive: true, force: true }); await rm(codexHome, { recursive: true, force: true }); }); test("Codex stdio runner startup failure marks session failed and blocks long-lived gate", async () => { const fakeCodex = await createFakeCodexCommand(); const codexHome = await prepareFakeCodexHome(); const manager = createCodexStdioSessionManager({ idFactory: () => "ses_stdio_start_failed", createProbeRpcClient: async () => ({ async initialize() { return { initialized: true }; }, close() {} }), createRpcClient: async () => ({ async initialize() { throw new Error("app-server initialize failed with sk-test-secret and TOKEN=abc123"); }, close() {} }) }); const 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: process.cwd(), HWLAB_CODE_AGENT_OPENAI_BASE_URL: "http://172.26.26.227:17680/v1/responses" }; const payload = await handleCodeAgentChat( { conversationId: "cnv_stdio_start_failed", traceId: "trc_stdio_start_failed", message: "启动后回答" }, { now: () => "2026-05-23T00:08:00.000Z", codexStdioManager: manager, env } ); validateCodeAgentChatSchema(payload); assert.equal(payload.status, "failed"); assert.equal(payload.provider, "codex-stdio"); assert.equal(payload.error.code, "codex_stdio_failed"); assert.equal(payload.session.sessionId, "ses_stdio_start_failed"); assert.equal(payload.session.status, "failed"); assert.equal(payload.session.sessionMode, "codex-app-server-stdio-long-lived"); assert.equal(payload.session.statusReason, "codex_stdio_failed"); assert.equal(payload.longLivedSessionGate.status, "blocked"); assert.equal(payload.longLivedSessionGate.pass, false); assert.ok(payload.longLivedSessionGate.blockers.some((blocker) => blocker.code === "session_failed")); assert.equal(payload.capabilityLevel, "blocked"); assert.equal(payload.runnerTrace.sessionStatus, "failed"); assert.equal(payload.runnerTrace.valuesPrinted, false); const serialized = JSON.stringify(payload); assert.equal(serialized.includes("sk-test-secret"), false); assert.equal(serialized.includes("TOKEN=abc123"), false); assert.equal(classifyCodexRunnerCapability(payload, { httpStatus: 200 }).capabilityPass, false); await rm(fakeCodex.root, { recursive: true, force: true }); await rm(codexHome, { recursive: true, force: true }); }); test("Codex app-server retrying error notification waits for completed turn", async () => { const calls = []; const fakeCodex = await createFakeCodexCommand(); const codexHome = await prepareFakeCodexHome(); const manager = createCodexStdioSessionManager({ idFactory: () => "ses_stdio_retrying_turn", createRpcClient: async () => createFakeAppServerClient({ calls, responses: ["retry recovered stdio reply"], retryingTurn: { turn: 1, message: "Reconnecting... 1/5" } }) }); const 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: process.cwd(), HWLAB_CODE_AGENT_OPENAI_BASE_URL: "http://172.26.26.227:17680/v1/responses" }; const payload = await handleCodeAgentChat( { conversationId: "cnv_stdio_retrying_turn", traceId: "trc_stdio_retrying_turn", message: "需要长会话" }, { now: () => "2026-05-23T00:08:45.000Z", codexStdioManager: manager, env } ); validateCodeAgentChatSchema(payload); assert.equal(payload.status, "completed"); assert.equal(payload.reply.content, "retry recovered stdio reply"); assert.equal(payload.session.status, "idle"); assert.equal(payload.longLivedSessionGate.status, "pass"); assert.equal(payload.runnerTrace.valuesPrinted, false); assert.ok(payload.runnerTrace.events.some((event) => event.label === "app-server:retrying")); assert.ok(payload.runnerTrace.events.some((event) => event.label === "turn:completed:completed")); assert.equal(payload.runnerTrace.events.some((event) => event.label === "app-server:error"), false); assert.deepEqual(calls.filter((call) => call.method !== "initialize").map((call) => call.method), ["thread/start", "turn/start"]); assert.equal(classifyCodexRunnerCapability(payload, { httpStatus: 200 }).capabilityPass, true); await rm(fakeCodex.root, { recursive: true, force: true }); await rm(codexHome, { recursive: true, force: true }); }); test("Codex app-server activity timeout does not fail an active turn", async () => { const calls = []; const fakeCodex = await createFakeCodexCommand(); const codexHome = await prepareFakeCodexHome(); const manager = createCodexStdioSessionManager({ idFactory: () => "ses_stdio_active_turn", createRpcClient: async () => createTimedFakeAppServerClient({ calls, text: "active turn completed", eventEveryMs: 20, eventCount: 5, completeAfterMs: 130 }) }); const 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: process.cwd(), HWLAB_CODE_AGENT_OPENAI_BASE_URL: "http://172.26.26.227:17680/v1/responses" }; try { const started = Date.now(); const payload = await handleCodeAgentChat( { conversationId: "cnv_stdio_active_turn", traceId: "trc_stdio_active_turn", message: "需要超过 activity timeout 但持续输出事件" }, { now: () => "2026-05-24T00:09:00.000Z", codexStdioManager: manager, env, timeoutMs: 35, hardTimeoutMs: 500 } ); validateCodeAgentChatSchema(payload); assert.equal(payload.status, "completed"); assert.equal(payload.reply.content, "active turn completed"); assert.ok(Date.now() - started >= 100); assert.ok(payload.runnerTrace.events.some((event) => event.label === "item/commandExecution/outputDelta")); assert.equal(payload.runnerTrace.events.some((event) => event.label === "timeout:no_activity"), false); assert.equal(payload.error, undefined); } finally { await rm(fakeCodex.root, { recursive: true, force: true }); await rm(codexHome, { recursive: true, force: true }); } }); test("Codex app-server activity timeout fails only after no new events", async () => { const calls = []; const fakeCodex = await createFakeCodexCommand(); const codexHome = await prepareFakeCodexHome(); const manager = createCodexStdioSessionManager({ idFactory: () => "ses_stdio_idle_timeout", createRpcClient: async () => createTimedFakeAppServerClient({ calls, eventCount: 0, completeAfterMs: null }) }); const 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: process.cwd(), HWLAB_CODE_AGENT_OPENAI_BASE_URL: "http://172.26.26.227:17680/v1/responses" }; try { const started = Date.now(); const payload = await handleCodeAgentChat( { conversationId: "cnv_stdio_idle_timeout", traceId: "trc_stdio_idle_timeout", message: "停止输出事件后触发 idle timeout" }, { now: () => "2026-05-24T00:09:30.000Z", codexStdioManager: manager, env, timeoutMs: 30, hardTimeoutMs: 500 } ); validateCodeAgentChatSchema(payload); assert.equal(payload.status, "timeout"); assert.equal(payload.error.code, "codex_stdio_idle_timeout"); assert.match(payload.error.message, /no new events/u); assert.equal(payload.error.timeoutMs, 30); assert.ok(payload.error.idleMs >= 30); assert.match(payload.error.lastActivityAt, /^\d{4}-\d{2}-\d{2}T/u); assert.equal(payload.error.waitingFor, "turn/completed"); assert.ok(Date.now() - started >= 30); const timeoutEvent = payload.runnerTrace.events.find((event) => event.label === "timeout:no_activity"); assert.ok(timeoutEvent); assert.equal(timeoutEvent.timeoutMs, 30); assert.ok(timeoutEvent.idleMs >= 30); assert.match(timeoutEvent.lastActivityAt, /^\d{4}-\d{2}-\d{2}T/u); assert.equal(timeoutEvent.waitingFor, "turn/completed"); assert.equal(payload.runnerTrace.events.some((event) => event.label === "timeout:hard_cap"), false); } finally { await rm(fakeCodex.root, { recursive: true, force: true }); await rm(codexHome, { recursive: true, force: true }); } }); test("Codex app-server failed turn is not wrapped as completed assistant output", async () => { const calls = []; const fakeCodex = await createFakeCodexCommand(); const codexHome = await prepareFakeCodexHome(); const manager = createCodexStdioSessionManager({ idFactory: () => "ses_stdio_failed_turn", createRpcClient: async () => createFakeAppServerClient({ calls, failedTurn: { turn: 1, message: "provider network failed with sk-test-secret and TOKEN=abc123" } }) }); const 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: process.cwd(), HWLAB_CODE_AGENT_OPENAI_BASE_URL: "http://172.26.26.227:17680/v1/responses" }; const payload = await handleCodeAgentChat( { conversationId: "cnv_stdio_failed_turn", traceId: "trc_stdio_failed_turn", message: "需要长会话" }, { now: () => "2026-05-23T00:08:30.000Z", codexStdioManager: manager, env } ); validateCodeAgentChatSchema(payload); assert.equal(payload.status, "failed"); assert.equal(payload.error.code, "codex_stdio_failed"); assert.equal(Object.hasOwn(payload, "reply"), false); assert.equal(payload.session.status, "failed"); assert.equal(payload.longLivedSessionGate.status, "blocked"); assert.ok(payload.longLivedSessionGate.blockers.some((blocker) => blocker.code === "session_failed")); assert.equal(payload.error.message.includes("sk-test-secret"), false); assert.equal(payload.error.message.includes("TOKEN=abc123"), false); assert.deepEqual(calls.filter((call) => call.method !== "initialize").map((call) => call.method), ["thread/start", "turn/start"]); assert.equal(classifyCodexRunnerCapability(payload, { httpStatus: 200 }).capabilityPass, false); await rm(fakeCodex.root, { recursive: true, force: true }); await rm(codexHome, { recursive: true, force: true }); });