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

2842 lines
115 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 {
codexAppServerArgs,
codexAppServerProviderBaseUrl,
createCodexStdioSessionManager
} from "./codex-stdio-session.mjs";
import {
classifyCodeAgentChatReadiness,
classifyCodexRunnerCapability
} 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.lifecycleStatus, "busy");
assert.equal(first.session.lifecycle.busy, true);
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);
assert.deepEqual(describedCreating.lifecycleStatuses, ["creating", "ready", "busy", "idle", "interrupted", "expired", "failed"]);
assert.equal(describedCreating.statusAliases.canceled, "interrupted");
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");
assert.equal(second.session.lifecycleStatus, "busy");
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.session.lifecycleStatus, "expired");
assert.match(expired.session.lifecycle.userMessage, /会话已过期/u);
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.sessionLifecycleStatus, "idle");
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.lifecycleStatus, "busy");
assert.match(busy.session.lifecycle.userMessage, /会话忙\/请等待/u);
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.session.lifecycleStatus, "failed");
assert.match(failed.session.lifecycle.userMessage, /需重新发送建立新会话/u);
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");
assert.equal(interrupted.session.lifecycleStatus, "interrupted");
});
test("Codex stdio readiness blocker does not use read-only session fallback", async () => {
const registry = createCodeAgentSessionRegistry({
idFactory: () => "ses_chat_registry"
});
const payload = await handleCodeAgentChat(
{
conversationId: "cnv_chat_registry",
traceId: "trc_chat_registry_pwd",
message: "pwd"
},
{
now: () => "2026-05-23T00:01:00.000Z",
sessionRegistry: registry,
env: {
PATH: "",
HWLAB_CODE_AGENT_PROVIDER: "codex-stdio",
HWLAB_CODE_AGENT_CODEX_COMMAND: path.join(os.tmpdir(), "hwlab-missing-codex"),
HWLAB_CODE_AGENT_WORKSPACE: process.cwd(),
HWLAB_CODE_AGENT_CODEX_WORKSPACE: process.cwd()
}
}
);
validateCodeAgentChatSchema(payload);
assert.equal(payload.status, "failed");
assert.equal(payload.provider, "codex-stdio");
assert.equal(payload.backend, "hwlab-cloud-api/codex-app-server-stdio");
assert.equal(payload.error.code, "codex_cli_binary_missing");
assert.equal(payload.runner.kind, "codex-app-server-stdio-runner");
assert.equal(payload.runner.kind === "hwlab-readonly-runner", false);
assert.equal(payload.capabilityLevel, "blocked");
assert.equal(payload.skills.status, "not_requested");
assert.deepEqual(payload.toolCalls, []);
assert.equal(payload.session, null);
assert.equal(payload.sessionLifecycleStatus, "failed");
assert.equal(payload.sessionSummary.degraded, true);
assert.equal(payload.longLivedSessionGate.status, "blocked");
assert.equal(payload.longLivedSessionGate.pass, false);
assert.ok(payload.longLivedSessionGate.blockers.some((blocker) => blocker.code === "codex_cli_binary_missing"));
assert.equal(payload.runnerLimitations.includes("no-controlled-readonly-fallback"), true);
assert.equal(Object.hasOwn(payload, "reply"), false);
assert.equal(classifyCodexRunnerCapability(payload, { httpStatus: 200 }).capabilityPass, false);
});
test("stale read-only conversation facts do not mask Codex stdio blockers", async () => {
const registry = createCodeAgentSessionRegistry({
idFactory: () => "ses_stale_readonly"
});
const acquired = registry.acquire({
conversationId: "cnv_stale_readonly",
workspace: process.cwd(),
sandbox: "read-only",
runnerKind: "hwlab-readonly-runner",
capabilityLevel: "read-only-session-tools",
traceId: "trc_stale_readonly_seed",
now: "2026-05-23T00:02:00.000Z"
});
registry.release(acquired.session.sessionId, {
conversationId: "cnv_stale_readonly",
traceId: "trc_stale_readonly_seed",
now: "2026-05-23T00:02:00.001Z"
});
registry.recordFact("cnv_stale_readonly", {
traceId: "trc_stale_readonly_seed",
workspace: process.cwd(),
session: acquired.session,
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:02:00.002Z" });
const payload = await handleCodeAgentChat(
{
conversationId: "cnv_stale_readonly",
traceId: "trc_stale_readonly_request",
message: "根据上一轮列出你能使用的所有skill"
},
{
now: () => "2026-05-23T00:02:01.000Z",
sessionRegistry: registry,
env: {
PATH: "",
HWLAB_CODE_AGENT_PROVIDER: "codex-stdio",
HWLAB_CODE_AGENT_CODEX_COMMAND: path.join(os.tmpdir(), "hwlab-missing-codex"),
HWLAB_CODE_AGENT_WORKSPACE: process.cwd(),
HWLAB_CODE_AGENT_CODEX_WORKSPACE: process.cwd()
}
}
);
validateCodeAgentChatSchema(payload);
assert.equal(payload.status, "failed");
assert.equal(payload.error.code, "codex_cli_binary_missing");
assert.equal(payload.provider, "codex-stdio");
assert.equal(payload.runner.kind, "codex-app-server-stdio-runner");
assert.equal(payload.capabilityLevel, "blocked");
assert.equal(payload.skills.status, "not_requested");
assert.equal(Object.hasOwn(payload, "reply"), false);
assert.equal(JSON.stringify(payload).includes("alpha-skill"), true);
assert.equal(payload.conversationFacts.latestSkills.names[0], "alpha-skill");
assert.equal(payload.error.blocker.conversationFacts.latestSkills.names[0], "alpha-skill");
assert.equal(payload.runner.kind === "hwlab-readonly-runner", false);
assert.equal(payload.provider === "openai-responses", 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 expiredAcquire = expiredRegistry.acquire({
conversationId: "cnv_expired_chat",
workspace: process.cwd(),
sandbox: "read-only",
runnerKind: "hwlab-readonly-runner",
capabilityLevel: "read-only-session-tools",
traceId: "trc_expired_request",
now: "2026-05-23T00:02:01.000Z"
});
const expired = codeAgentBlockedPayloadFromAcquire(expiredAcquire, {
conversationId: "cnv_expired_chat",
traceId: "trc_expired_request",
conversationFacts: expiredRegistry.getConversationFacts("cnv_expired_chat")
});
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.sessionLifecycleStatus, "expired");
assert.equal(expired.sessionSummary.requiresNewSession, true);
assert.match(expired.sessionSummary.userMessage, /会话已过期/u);
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 busyAcquire = busyRegistry.acquire({
conversationId: "cnv_busy_chat",
workspace: process.cwd(),
sandbox: "read-only",
runnerKind: "hwlab-readonly-runner",
capabilityLevel: "read-only-session-tools",
traceId: "trc_busy_request",
now: "2026-05-23T00:03:01.000Z"
});
const busy = codeAgentBlockedPayloadFromAcquire(busyAcquire, {
conversationId: "cnv_busy_chat",
traceId: "trc_busy_request",
conversationFacts: busyRegistry.getConversationFacts("cnv_busy_chat")
});
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.sessionLifecycleStatus, "busy");
assert.equal(busy.sessionSummary.busy, true);
assert.match(busy.sessionSummary.userMessage, /会话忙\/请等待/u);
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 failedAcquire = failedRegistry.acquire({
conversationId: "cnv_failed_chat",
workspace: process.cwd(),
sandbox: "read-only",
runnerKind: "hwlab-readonly-runner",
capabilityLevel: "read-only-session-tools",
traceId: "trc_failed_request",
now: "2026-05-23T00:04:01.000Z"
});
const failed = codeAgentBlockedPayloadFromAcquire(failedAcquire, {
conversationId: "cnv_failed_chat",
traceId: "trc_failed_request",
conversationFacts: failedRegistry.getConversationFacts("cnv_failed_chat")
});
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.sessionLifecycleStatus, "failed");
assert.equal(failed.sessionSummary.requiresNewSession, true);
assert.match(failed.sessionSummary.userMessage, /需重新发送建立新会话/u);
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 hook is not used when Codex stdio is unavailable", async () => {
let providerCalled = false;
const payload = await handleCodeAgentChat(
{
conversationId: "cnv_fallback_gate",
traceId: "trc_fallback_gate",
message: "你好"
},
{
now: () => "2026-05-23T00:04:00.000Z",
env: {
PATH: "",
HWLAB_CODE_AGENT_PROVIDER: "openai",
HWLAB_CODE_AGENT_ALLOW_TEXT_FALLBACK: "1",
HWLAB_CODE_AGENT_CODEX_COMMAND: path.join(os.tmpdir(), "hwlab-missing-codex"),
HWLAB_CODE_AGENT_WORKSPACE: process.cwd(),
HWLAB_CODE_AGENT_CODEX_WORKSPACE: process.cwd()
},
callProvider: async () => {
providerCalled = true;
return {
provider: "openai-responses",
model: "gpt-test",
backend: "hwlab-cloud-api/openai-responses",
content: "普通文本回复。",
usage: null,
providerTrace: { source: "test-provider" }
};
}
}
);
validateCodeAgentChatSchema(payload);
assert.equal(payload.status, "failed");
assert.equal(payload.error.code, "codex_cli_binary_missing");
assert.equal(payload.provider, "codex-stdio");
assert.equal(payload.runner.kind, "codex-app-server-stdio-runner");
assert.equal(payload.capabilityLevel, "blocked");
assert.equal(payload.runner.kind === "openai-responses-fallback", false);
assert.equal(payload.provider === "openai-responses", false);
assert.equal(Object.hasOwn(payload, "reply"), false);
assert.equal(providerCalled, false);
assert.equal(payload.sessionLifecycleStatus, "failed");
assert.equal(payload.sessionSummary.degraded, true);
assert.equal(payload.runnerLimitations.includes("no-text-fallback"), true);
assert.equal(classifyCodexRunnerCapability(payload, { httpStatus: 200 }).capabilityPass, false);
const oneShot = {
...payload,
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, false);
});
test("OpenAI provider mode still preserves facts but does not fallback without Codex stdio", async () => {
const registry = createCodeAgentSessionRegistry({
idFactory: () => "ses_provider_context"
});
let providerCalled = false;
const env = {
PATH: "",
HWLAB_CODE_AGENT_PROVIDER: "openai",
HWLAB_CODE_AGENT_ALLOW_TEXT_FALLBACK: "1",
HWLAB_CODE_AGENT_CODEX_COMMAND: path.join(os.tmpdir(), "hwlab-missing-codex"),
HWLAB_CODE_AGENT_WORKSPACE: process.cwd(),
HWLAB_CODE_AGENT_CODEX_WORKSPACE: process.cwd()
};
const seed = registry.acquire({
conversationId: "cnv_provider_context",
workspace: process.cwd(),
sandbox: "read-only",
runnerKind: "hwlab-readonly-runner",
capabilityLevel: "read-only-session-tools",
traceId: "trc_provider_context_pwd",
now: "2026-05-23T00:04:09.000Z"
});
const released = registry.release(seed.session.sessionId, {
conversationId: "cnv_provider_context",
traceId: "trc_provider_context_pwd",
now: "2026-05-23T00:04:09.010Z"
});
registry.recordFact("cnv_provider_context", {
traceId: "trc_provider_context_pwd",
workspace: process.cwd(),
session: released,
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:09.020Z" });
const payload = await handleCodeAgentChat(
{
conversationId: "cnv_provider_context",
traceId: "trc_provider_context_openai",
message: "请根据刚才的上下文简要说明"
},
{
now: () => "2026-05-23T00:04:11.000Z",
sessionRegistry: registry,
env,
callProvider: async () => {
providerCalled = true;
return {
provider: "openai-responses",
model: "gpt-test",
backend: "hwlab-cloud-api/openai-responses",
content: "普通 fallback 回复不应被调用。",
usage: null,
providerTrace: { source: "test-provider" }
};
}
}
);
assert.equal(payload.status, "failed");
assert.equal(payload.error.code, "codex_cli_binary_missing");
assert.equal(payload.provider, "codex-stdio");
assert.equal(payload.runner.kind, "codex-app-server-stdio-runner");
assert.equal(payload.conversationFacts.turnCount, 1);
assert.equal(payload.conversationFacts.sessionId, released.sessionId);
assert.equal(payload.conversationFacts.recentToolCalls[0].name, "pwd");
assert.equal(payload.conversationFacts.latestTraceId, "trc_provider_context_pwd");
assert.equal(payload.error.blocker.conversationFacts.sessionId, released.sessionId);
assert.equal(Object.hasOwn(payload, "reply"), false);
assert.equal(providerCalled, false);
assert.equal(JSON.stringify(payload.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);
});
test("Code Agent PC gateway prompt reaches Codex stdio instead of internal hardware shortcut", async () => {
const calls = [];
let providerCalled = false;
const fakeCodex = await createFakeCodexCommand();
const codexHome = await prepareFakeCodexHome();
const registry = createCodeAgentSessionRegistry();
const manager = createCodexStdioSessionManager({
idFactory: () => "ses_pc_gateway_stdio",
createRpcClient: async () => createFakeAppServerClient({
calls,
responses: ["已通过受控 wrapper 执行 PC gateway 命令。"]
})
});
try {
const payload = await handleCodeAgentChat(
{
conversationId: "cnv_pc_gateway_stdio",
traceId: "trc_pc_gateway_stdio",
projectId: "prj_mvp_topology",
gatewaySessionId: "gws_DESKTOP-1MHOD9I",
resourceId: "res_windows_host",
capabilityId: "cap_windows_cmd_exec",
message: "请通过 PC gateway shell.exec 执行 Windows cmdcmd /c echo HWLAB-PC-GW && hostname"
},
{
now: () => "2026-05-23T00:08:00.000Z",
env: {
PATH: process.env.PATH,
OPENAI_API_KEY: "test-openai-key-material",
CODEX_HOME: codexHome,
HWLAB_CODE_AGENT_PROVIDER: "codex-stdio",
HWLAB_CODE_AGENT_MODEL: "gpt-test",
HWLAB_CODE_AGENT_CODEX_COMMAND: fakeCodex.command,
HWLAB_CODE_AGENT_CODEX_STDIO_ENABLED: "1",
HWLAB_CODE_AGENT_CODEX_STDIO_SUPERVISOR: "repo-owned",
HWLAB_CODE_AGENT_CODEX_SANDBOX: "danger-full-access",
HWLAB_CODE_AGENT_WORKSPACE: process.cwd(),
HWLAB_CODE_AGENT_OPENAI_BASE_URL: "http://172.26.26.227:17680/v1/responses"
},
sessionRegistry: registry,
codexStdioManager: manager,
callProvider: async () => {
providerCalled = true;
throw new Error("text fallback must not be used");
}
}
);
validateCodeAgentChatSchema(payload);
assert.equal(payload.status, "completed");
assert.equal(payload.provider, "codex-stdio");
assert.equal(payload.backend, "hwlab-cloud-api/codex-app-server-stdio");
assert.equal(payload.sandbox, "danger-full-access");
assert.equal(payload.runner.kind, "codex-app-server-stdio-runner");
assert.equal(payload.capabilityLevel, "long-lived-codex-stdio-session");
assert.equal(payload.toolCalls.some((toolCall) => toolCall.name === "hardware.invoke.shell"), false);
assert.equal(providerCalled, false);
const turn = calls.find((call) => call.method === "turn/start");
assert.ok(turn, "Codex stdio turn/start should be called");
assert.match(turn.args.prompt, /\/app\/tools\/hwlab-gateway-shell\.mjs/u);
assert.match(turn.args.prompt, /C:\\Users\\liang\\\.agents\\skills/u);
assert.match(turn.args.prompt, /cmd \/c echo HWLAB-PC-GW && hostname/u);
} finally {
await rm(fakeCodex.root, { recursive: true, force: true });
await rm(codexHome, { recursive: true, force: true });
}
});
test("Code Agent Keil gateway prompt is not blocked by M3 IO intent guard", async () => {
const calls = [];
let providerCalled = false;
const fakeCodex = await createFakeCodexCommand();
const codexHome = await prepareFakeCodexHome();
const registry = createCodeAgentSessionRegistry();
const manager = createCodexStdioSessionManager({
idFactory: () => "ses_keil_gateway_stdio",
createRpcClient: async () => createFakeAppServerClient({
calls,
responses: ["已通过 PC gateway 调用 Windows Keil skillsuccess=truereturn_code=0,生成 FREQ_Controller_FW.hex。"]
})
});
try {
const payload = await handleCodeAgentChat(
{
conversationId: "cnv_keil_gateway_stdio",
traceId: "trc_keil_gateway_stdio",
projectId: "prj_mvp_topology",
message: String.raw`请用 /app/tools/hwlab-gateway-shell.mjs 通过 PC gateway 调用 Windows cmd,进入 C:\Users\liang\.agents\skills\keil,然后运行 py -3 keil-cli.py build -p "F:\Work\constart\projects\71-00075-11\FirmWare\MDK-ARM\FREQ_Controller_FW.uvprojx" -t FREQ_Controller_FW;如果返回 job id,请用 py -3 keil-cli.py job-status <job_id> 轮询到终态。`
},
{
now: () => "2026-05-24T14:35:00.000Z",
env: {
PATH: process.env.PATH,
OPENAI_API_KEY: "test-openai-key-material",
CODEX_HOME: codexHome,
HWLAB_CODE_AGENT_PROVIDER: "codex-stdio",
HWLAB_CODE_AGENT_MODEL: "gpt-test",
HWLAB_CODE_AGENT_CODEX_COMMAND: fakeCodex.command,
HWLAB_CODE_AGENT_CODEX_STDIO_ENABLED: "1",
HWLAB_CODE_AGENT_CODEX_STDIO_SUPERVISOR: "repo-owned",
HWLAB_CODE_AGENT_CODEX_SANDBOX: "danger-full-access",
HWLAB_CODE_AGENT_WORKSPACE: process.cwd(),
HWLAB_CODE_AGENT_OPENAI_BASE_URL: "http://172.26.26.227:17680/v1/responses"
},
sessionRegistry: registry,
codexStdioManager: manager,
callProvider: async () => {
providerCalled = true;
throw new Error("text fallback must not be used");
},
m3IoSkillRequestJson: async () => {
throw new Error("Keil gateway prompt must not be routed to M3 IO");
}
}
);
validateCodeAgentChatSchema(payload);
assert.equal(payload.status, "completed");
assert.equal(payload.provider, "codex-stdio");
assert.equal(payload.responseType, undefined);
assert.notEqual(payload.capabilityLevel, HWLAB_M3_IO_CAPABILITY_LEVELS.blocked);
assert.equal(providerCalled, false);
const turn = calls.find((call) => call.method === "turn/start");
assert.ok(turn, "Codex stdio turn/start should be called");
assert.match(turn.args.prompt, /\/app\/tools\/hwlab-gateway-shell\.mjs/u);
assert.match(turn.args.prompt, /keil-cli\.py build/u);
assert.match(turn.args.prompt, /job-status/u);
assert.match(turn.args.prompt, /FREQ_Controller_FW\.uvprojx/u);
} finally {
await rm(fakeCodex.root, { recursive: true, force: true });
await rm(codexHome, { recursive: true, force: true });
}
});
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 codeAgentBlockedPayloadFromAcquire(acquireResult, { conversationId, traceId, conversationFacts }) {
const code = acquireResult.code;
const userMessage = code === "session_busy"
? "Code Agent session 正在处理上一轮请求,请稍后重试。"
: code === "session_expired"
? "Code Agent session 已过期,可重新发送建立新的 session。"
: "Code Agent session 已失败,可重新发送建立新的 session。";
const sessionSummary = acquireResult.session.lifecycle;
const blocker = {
code,
sourceIssue: acquireResult.blocker.sourceIssue,
summary: acquireResult.blocker.summary,
traceId,
conversationFacts
};
return {
status: "failed",
conversationId,
traceId,
error: {
code,
layer: "session",
retryable: true,
userMessage,
blocker,
blockers: [acquireResult.blocker]
},
blocker,
session: acquireResult.session,
sessionLifecycleStatus: sessionSummary.status,
sessionSummary,
capabilityLevel: "blocked",
longLivedSessionGate: {
status: "blocked"
},
conversationFacts
};
}
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 skills discovery returns bounded manifest facts instead of generic model text", async () => {
const root = await mkdtemp(path.join(os.tmpdir(), "hwlab-stdio-skills-ready-"));
const workspace = path.join(root, "workspace");
const skillsDir = path.join(root, "skills");
const codexHome = await prepareFakeCodexHome(path.join(root, "codex-home"));
const fakeCodex = await createFakeCodexCommand();
const calls = [];
await mkdir(path.join(workspace), { recursive: true });
await mkdir(path.join(skillsDir, "alpha"), { recursive: true });
await writeFile(path.join(skillsDir, "alpha", "SKILL.md"), [
"---",
"name: alpha-skill",
"description: Alpha manifest summary from SKILL.md.",
"version: v1.2.3",
"commitId: abc123def456",
"---",
"",
"# Alpha"
].join("\n"), "utf8");
try {
const manager = createCodexStdioSessionManager({
idFactory: () => "ses_stdio_skills_ready",
createRpcClient: async () => createFakeAppServerClient({
calls,
responses: ["模型泛化回答:我可以使用很多 skill。"]
})
});
const payload = await handleCodeAgentChat(
{
conversationId: "cnv_stdio_skills_ready",
traceId: "trc_stdio_skills_ready",
message: "列出你能使用的所有skill"
},
{
now: () => "2026-05-24T00:10:00.000Z",
codexStdioManager: manager,
env: {
PATH: process.env.PATH,
OPENAI_API_KEY: "test-openai-key-material",
CODEX_HOME: codexHome,
HWLAB_CODE_AGENT_PROVIDER: "codex-stdio",
HWLAB_CODE_AGENT_MODEL: "gpt-test",
HWLAB_CODE_AGENT_CODEX_COMMAND: fakeCodex.command,
HWLAB_CODE_AGENT_CODEX_STDIO_ENABLED: "1",
HWLAB_CODE_AGENT_CODEX_STDIO_SUPERVISOR: "repo-owned",
HWLAB_CODE_AGENT_CODEX_WORKSPACE: workspace,
HWLAB_CODE_AGENT_SKILLS_DIRS: skillsDir,
HWLAB_CODE_AGENT_SKILLS_STRICT: "1",
HWLAB_CODE_AGENT_OPENAI_BASE_URL: "http://172.26.26.227:17680/v1/responses"
}
}
);
validateCodeAgentChatSchema(payload);
assert.equal(payload.status, "completed");
assert.equal(payload.provider, "codex-stdio");
assert.equal(payload.backend, "hwlab-cloud-api/codex-app-server-stdio");
assert.equal(payload.runner.kind, "codex-app-server-stdio-runner");
assert.equal(payload.capabilityLevel, "long-lived-codex-stdio-session");
assert.equal(payload.skills.status, "ready");
assert.equal(payload.skills.count, 1);
assert.equal(payload.skills.totalCount, 1);
assert.equal(payload.skills.items[0].name, "alpha-skill");
assert.equal(payload.skills.items[0].summary, "Alpha manifest summary from SKILL.md.");
assert.equal(payload.skills.items[0].version, "v1.2.3");
assert.equal(payload.skills.items[0].commitId, "abc123def456");
assert.equal(payload.skills.items[0].source, path.join(skillsDir, "alpha", "SKILL.md"));
assert.equal(payload.skills.items[0].manifest.path, path.join(skillsDir, "alpha", "SKILL.md"));
assert.match(payload.reply.content, /真实 skill manifest 清单/u);
assert.match(payload.reply.content, /数量:本次返回 1 个;已发现总数 1 个/u);
assert.match(payload.reply.content, /alpha-skill/u);
assert.match(payload.reply.content, /version=v1\.2\.3/u);
assert.match(payload.reply.content, /commitId=abc123def456/u);
assert.doesNotMatch(payload.reply.content, /模型泛化回答/u);
assert.ok(payload.toolCalls.some((toolCall) => toolCall.name === "skills.discover" && toolCall.status === "completed"));
assert.ok(payload.toolCalls.some((toolCall) => toolCall.name === "codex-app-server.thread/start+turn/start" && toolCall.status === "completed"));
const turnPrompt = calls.find((call) => call.method === "turn/start")?.args?.prompt ?? "";
assert.match(turnPrompt, /Current skills discovery facts/u);
assert.match(turnPrompt, /alpha-skill/u);
assert.match(turnPrompt, /commitId=abc123def456/u);
assert.equal(JSON.stringify(payload).includes("test-openai-key-material"), false);
} finally {
await rm(root, { recursive: true, force: true });
await rm(fakeCodex.root, { recursive: true, force: true });
}
});
test("Codex stdio skills discovery returns structured blocker when manifests are missing", async () => {
const root = await mkdtemp(path.join(os.tmpdir(), "hwlab-stdio-skills-missing-"));
const workspace = path.join(root, "workspace");
const codexHome = await prepareFakeCodexHome(path.join(root, "codex-home"));
const fakeCodex = await createFakeCodexCommand();
await mkdir(workspace, { recursive: true });
try {
const manager = createCodexStdioSessionManager({
idFactory: () => "ses_stdio_skills_missing",
createRpcClient: async () => createFakeAppServerClient({
responses: ["模型泛化回答:我可以使用 alpha-skill 和 beta-skill。"]
})
});
const payload = await handleCodeAgentChat(
{
conversationId: "cnv_stdio_skills_missing",
traceId: "trc_stdio_skills_missing",
message: "列出你可用的skills"
},
{
now: () => "2026-05-24T00:10:30.000Z",
codexStdioManager: manager,
env: {
PATH: process.env.PATH,
OPENAI_API_KEY: "test-openai-key-material",
CODEX_HOME: codexHome,
HWLAB_CODE_AGENT_PROVIDER: "codex-stdio",
HWLAB_CODE_AGENT_MODEL: "gpt-test",
HWLAB_CODE_AGENT_CODEX_COMMAND: fakeCodex.command,
HWLAB_CODE_AGENT_CODEX_STDIO_ENABLED: "1",
HWLAB_CODE_AGENT_CODEX_STDIO_SUPERVISOR: "repo-owned",
HWLAB_CODE_AGENT_CODEX_WORKSPACE: workspace,
HWLAB_CODE_AGENT_SKILLS_DIRS: path.join(root, "missing-skills"),
HWLAB_CODE_AGENT_SKILLS_STRICT: "1",
HWLAB_CODE_AGENT_OPENAI_BASE_URL: "http://172.26.26.227:17680/v1/responses"
}
}
);
validateCodeAgentChatSchema(payload);
assert.equal(payload.status, "failed");
assert.equal(payload.provider, "codex-stdio");
assert.equal(payload.backend, "hwlab-cloud-api/codex-app-server-stdio");
assert.equal(payload.runner.kind, "codex-app-server-stdio-runner");
assert.equal(payload.runnerTrace.runnerKind, "codex-app-server-stdio-runner");
assert.equal(payload.error.code, "skills_unavailable");
assert.equal(payload.error.retryable, false);
assert.equal(payload.skills.status, "blocked");
assert.equal(payload.skills.count, 0);
assert.deepEqual(payload.skills.sourceIssues, ["pikasTech/HWLAB#136", "pikasTech/HWLAB#237"]);
assert.ok(payload.skills.blockers.some((blocker) => blocker.sourceIssue === "pikasTech/HWLAB#136"));
assert.ok(payload.skills.blockers.some((blocker) => blocker.sourceIssue === "pikasTech/HWLAB#237"));
assert.ok(payload.toolCalls.some((toolCall) => toolCall.name === "skills.discover" && toolCall.status === "blocked"));
assert.equal(payload.toolCalls.some((toolCall) => toolCall.name === "codex-app-server.thread/start+turn/start"), false);
assert.equal(payload.session.status, "idle");
assert.equal(Object.hasOwn(payload, "reply"), false);
assert.notEqual(payload.runner.kind, "openai-responses-fallback");
assert.notEqual(payload.provider, "openai-responses");
assert.equal(JSON.stringify(payload).includes("alpha-skill"), false);
assert.ok(payload.runnerTrace.events.some((event) => event.label === "stdio:ready"));
assert.ok(payload.runnerTrace.events.some((event) => event.label === "skills.discover:blocked"));
} finally {
await rm(root, { recursive: true, force: true });
await rm(fakeCodex.root, { 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 });
});