Files
pikasTech-HWLAB/internal/cloud/code-agent-session-registry.test.mjs
T
2026-05-24 14:53:14 +08:00

1924 lines
76 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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 { 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 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 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 } = {}) {
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 (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() {}
};
}
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("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 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 });
});