fix: align Code Agent session facts

Align Code Agent chat, live health, and workbench UI session facts for Codex stdio vs fallback paths.
This commit is contained in:
Lyon
2026-05-24 07:34:27 +08:00
committed by GitHub
parent 41f6171e09
commit 034c90763c
7 changed files with 427 additions and 38 deletions
+113 -33
View File
@@ -13,6 +13,7 @@ import {
} from "./code-agent-contract.mjs";
import {
CODEX_STDIO_BACKEND,
CODEX_STDIO_CAPABILITY_LEVEL,
CODEX_STDIO_IMPLEMENTATION_TYPE,
CODEX_STDIO_PROVIDER,
CODEX_STDIO_RUNNER_KIND,
@@ -78,6 +79,19 @@ const READONLY_LIMITATION_FLAGS = Object.freeze([
"not-write-capable",
"process-local-session-registry"
]);
const CODEX_STDIO_LIMITATION_FLAGS = Object.freeze([
"hardware-control-via-cloud-api-only",
"no-direct-gateway-link",
"no-direct-box-simu-link",
"no-direct-patch-panel-link",
"secret-values-redacted"
]);
const OPENAI_FALLBACK_LIMITATION_FLAGS = Object.freeze([
"openai-responses-fallback",
"text-chat-only",
"not-workspace-tools",
"not-session-runner"
]);
const SKIPPED_READONLY_DIRS = new Set([
".git",
".hg",
@@ -271,12 +285,7 @@ export async function handleCodeAgentChat(params = {}, options = {}) {
sessionMode: providerResult.sessionMode ?? "provider-text-request",
sessionReuse: providerResult.sessionReuse ?? null,
implementationType: providerResult.implementationType ?? OPENAI_FALLBACK_RUNNER_KIND,
runnerLimitations: providerResult.runnerLimitations ?? [
"text-chat-only",
"not-codex-stdio",
"not-workspace-tools",
"not-session-runner"
],
runnerLimitations: providerResult.runnerLimitations ?? [...OPENAI_FALLBACK_LIMITATION_FLAGS],
codexStdioFeasibility: providerResult.codexStdioFeasibility ?? await inspectCodexStdioFeasibility(options.env ?? process.env, options),
longLivedSessionGate: providerLongLivedGate,
toolCalls: Array.isArray(providerResult.toolCalls) ? providerResult.toolCalls : [],
@@ -499,10 +508,15 @@ export async function describeCodeAgentAvailability(env = process.env, options =
sessionRegistry
});
const codexStdio = await inspectCodexStdioFeasibility(env, options);
const codexStdioReady = codexStdioLongLivedReady(codexStdio);
const codexStdioActive = providerPlan.mode !== "openai" && codexStdioReady;
const activeRunnerAvailability = codexStdioActive
? codexStdioRunnerAvailability(codexStdio)
: runnerAvailability;
const blocked = providerPlan.mode === "openai"
? !providerContract.ready
: providerPlan.mode === "codex-stdio"
? !codexStdio.ready
? !codexStdioReady
: missingEnv.length > 0;
const fallbackAvailability = {
provider: providerPlan.provider,
@@ -520,29 +534,29 @@ export async function describeCodeAgentAvailability(env = process.env, options =
safety: providerContract.safety
};
const longLivedGate = longLivedSessionGate({
provider: codexStdio.ready ? CODEX_STDIO_PROVIDER : runnerAvailability.provider,
runnerKind: codexStdio.ready ? CODEX_STDIO_RUNNER_KIND : runnerAvailability.kind,
sessionMode: codexStdio.ready ? CODEX_STDIO_SESSION_MODE : READONLY_SESSION_MODE,
implementationType: codexStdio.ready ? CODEX_STDIO_IMPLEMENTATION_TYPE : READONLY_IMPLEMENTATION_TYPE,
provider: codexStdioActive ? CODEX_STDIO_PROVIDER : runnerAvailability.provider,
runnerKind: codexStdioActive ? CODEX_STDIO_RUNNER_KIND : runnerAvailability.kind,
sessionMode: codexStdioActive ? CODEX_STDIO_SESSION_MODE : READONLY_SESSION_MODE,
implementationType: codexStdioActive ? CODEX_STDIO_IMPLEMENTATION_TYPE : READONLY_IMPLEMENTATION_TYPE,
codexStdioFeasibility: codexStdio
});
const agentKind = codexStdio.ready
const agentKind = codexStdioActive
? "codex-stdio-feasible"
: runnerAvailability.ready
? "controlled-readonly-session-registry"
: blocked
? "openai-fallback-blocked"
: "openai-fallback";
const capabilityStatus = codexStdio.ready
const capabilityStatus = codexStdioActive
? "codex-stdio-feasible"
: runnerAvailability.ready
? "controlled-readonly-session-registry"
: blocked
? "blocked"
: "openai-fallback";
const codeAgentReady = codexStdio.ready;
const status = codexStdio.ready ? "codex-stdio-feasible" : blocked && !runnerAvailability.ready ? "blocked" : "partial";
const capabilityLevel = codexStdio.ready
const codeAgentReady = codexStdioActive;
const status = codexStdioActive ? "codex-stdio-feasible" : blocked && !runnerAvailability.ready ? "blocked" : "partial";
const capabilityLevel = codexStdioActive
? "long-lived-codex-stdio-session"
: runnerAvailability.ready
? READONLY_SESSION_CAPABILITY_LEVEL
@@ -585,7 +599,7 @@ export async function describeCodeAgentAvailability(env = process.env, options =
"error.missingEnv",
"availability.status"
],
runner: runnerAvailability,
runner: activeRunnerAvailability,
fallback: fallbackAvailability,
codexStdio,
status,
@@ -601,10 +615,14 @@ export async function describeCodeAgentAvailability(env = process.env, options =
egress: providerContract.egress,
safety: providerContract.safety,
capabilityLevel,
sessionRegistry: runnerAvailability.sessionRegistry,
sessionMode: runnerAvailability.sessionMode,
implementationType: runnerAvailability.implementationType,
runnerLimitations: runnerAvailability.runnerLimitations,
workspace: activeRunnerAvailability.workspace,
sandbox: activeRunnerAvailability.sandbox,
session: activeRunnerAvailability.sessionRegistry?.recentSessions?.[0] ?? null,
sessionStatus: activeRunnerAvailability.sessionRegistry?.recentSessions?.[0]?.status ?? null,
sessionRegistry: activeRunnerAvailability.sessionRegistry ?? runnerAvailability.sessionRegistry,
sessionMode: activeRunnerAvailability.sessionMode,
implementationType: activeRunnerAvailability.implementationType,
runnerLimitations: activeRunnerAvailability.runnerLimitations,
codexStdioFeasibility: codexStdio,
longLivedSessionGate: longLivedGate,
blockers: [
@@ -632,6 +650,78 @@ function fallbackBlockerForPlan({ providerPlan, providerContract, codexStdio })
return "凭证缺口";
}
function codexStdioLongLivedReady(codexStdio = {}) {
return Boolean(
(codexStdio.ready === true || codexStdio.canStartLongLivedCodexStdio === true) &&
codexStdio.commandProbe?.ready === true
);
}
function codexStdioRunnerAvailability(codexStdio = {}) {
const workspace = codexStdio.workspace ?? repoRoot;
const sandbox = codexStdio.sandbox ?? "workspace-write";
return {
kind: CODEX_STDIO_RUNNER_KIND,
backend: CODEX_STDIO_BACKEND,
provider: CODEX_STDIO_PROVIDER,
workspace,
sandbox,
mode: CODEX_STDIO_SESSION_MODE,
sessionMode: CODEX_STDIO_SESSION_MODE,
session: CODEX_STDIO_SESSION_MODE,
status: "available",
ready: true,
capabilityLevel: CODEX_STDIO_CAPABILITY_LEVEL,
implementationType: CODEX_STDIO_IMPLEMENTATION_TYPE,
longLivedSession: true,
durableSession: true,
durable: true,
codexStdio: true,
writeCapable: true,
readOnly: false,
runnerLimitations: [...CODEX_STDIO_LIMITATION_FLAGS],
codexStdioFeasibility: codexStdio,
longLivedSessionGate: longLivedSessionGate({
provider: CODEX_STDIO_PROVIDER,
runnerKind: CODEX_STDIO_RUNNER_KIND,
sessionMode: CODEX_STDIO_SESSION_MODE,
implementationType: CODEX_STDIO_IMPLEMENTATION_TYPE,
codexStdioFeasibility: codexStdio
}),
sessionRegistry: codexStdioSessionRegistrySummary(codexStdio),
safety: {
secretsRead: false,
secretValuesPrinted: false,
kubeconfigRead: false,
hardwareControlViaCloudApiOnly: true,
directGatewayCallsAllowed: false,
directBoxSimuCallsAllowed: false,
directPatchPanelCallsAllowed: false,
m3m4m5AcceptanceClaimsAllowed: false
}
};
}
function codexStdioSessionRegistrySummary(codexStdio = {}) {
return {
kind: "codex-stdio-session-registry",
implementationType: CODEX_STDIO_IMPLEMENTATION_TYPE,
status: "available",
sessionCount: codexStdio.sessionLifecycle?.activeSessions ?? codexStdio.lifecycleSupervisor?.activeSessions ?? null,
maxSessions: codexStdio.sessionLifecycle?.maxSessions ?? codexStdio.lifecycleSupervisor?.maxSessions ?? null,
idleTimeoutMs: codexStdio.sessionLifecycle?.idleTimeoutMs ?? codexStdio.lifecycleSupervisor?.idleTimeoutMs ?? null,
statuses: ["creating", "ready", "busy", "idle", "interrupted", "expired", "failed"],
sessionMode: CODEX_STDIO_SESSION_MODE,
recentSessions: Array.isArray(codexStdio.recentSessions) ? codexStdio.recentSessions : [],
longLivedCodexStdio: true,
longLivedSession: true,
durable: true,
codexStdio: true,
secretMaterialStored: false,
valuesRedacted: true
};
}
function primaryCodeAgentBlocker({ blocked, providerContract, providerPlan, codexStdio, runnerAvailability }) {
if (codexStdio.blockers?.length > 0) return codexStdio.blockers[0].summary;
if (blocked && !runnerAvailability.ready) return providerPlan.mode === "openai" ? providerContract.blocker : "凭证缺口";
@@ -2844,12 +2934,7 @@ function openAiFallbackRunnerEvidence({ providerResult, traceId, conversationId,
codexRunner: false,
reason: "OpenAI Responses fallback is text chat only and cannot satisfy the Codex runner capability gate."
},
runnerLimitations: [
"text-chat-only",
"not-codex-stdio",
"not-workspace-tools",
"not-session-runner"
],
runnerLimitations: [...OPENAI_FALLBACK_LIMITATION_FLAGS],
traceId
};
}
@@ -2899,12 +2984,7 @@ async function callOpenAiResponses({ providerPlan, message, conversationId, trac
usage: null,
capabilityLevel: "text-chat-only",
implementationType: OPENAI_FALLBACK_RUNNER_KIND,
runnerLimitations: [
"text-chat-only",
"not-codex-stdio",
"not-workspace-tools",
"not-session-runner"
],
runnerLimitations: [...OPENAI_FALLBACK_LIMITATION_FLAGS],
providerTrace: {
fallbackUsed: true,
fallbackKind: OPENAI_FALLBACK_RUNNER_KIND,
@@ -389,6 +389,13 @@ test("OpenAI fallback and codex one-shot do not pass the long-lived session gate
assert.equal(fallback.status, "completed");
assert.equal(fallback.runner.kind, "openai-responses-fallback");
assert.equal(fallback.capabilityLevel, "text-chat-only");
assert.deepEqual(fallback.runnerLimitations, [
"openai-responses-fallback",
"text-chat-only",
"not-workspace-tools",
"not-session-runner"
]);
assert.equal(fallback.runnerLimitations.includes("not-codex-stdio"), false);
assert.equal(fallback.blocker.code, "text_chat_only_fallback");
assert.equal(fallback.blocker.layer, "provider");
assert.equal(fallback.blocker.retryable, false);
@@ -1388,6 +1395,10 @@ test("repo-owned Codex stdio manager creates and reuses long-lived sessions with
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(
@@ -1404,8 +1415,13 @@ test("repo-owned Codex stdio manager creates and reuses long-lived sessions with
);
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-reply");
assert.equal(second.reply.content, "second stdio reply");
assert.equal(calls[0].name, "codex");
+132
View File
@@ -1811,6 +1811,61 @@ test("OpenAI fallback text chat does not satisfy Codex stdio capability gate", a
}
});
test("cloud api health does not pass long-lived session gate for explicit OpenAI fallback even if Codex stdio is feasible", async () => {
const workspace = await mkdtemp(path.join(os.tmpdir(), "hwlab-agent-openai-health-"));
const codexHome = await mkdtemp(path.join(os.tmpdir(), "hwlab-agent-openai-health-codex-home-"));
const server = createCloudApiServer({
env: {
PATH: process.env.PATH,
CODEX_HOME: codexHome,
HWLAB_CODE_AGENT_CODEX_WORKSPACE: workspace,
HWLAB_CODE_AGENT_PROVIDER: "openai",
HWLAB_CODE_AGENT_MODEL: "gpt-test",
OPENAI_API_KEY: "test-openai-key-material",
HWLAB_CODE_AGENT_OPENAI_BASE_URL: "http://127.0.0.1:65535/v1/responses"
},
codexStdioManager: {
describe() {
return codexStdioReadyFixture({ workspace, codexHome });
},
async probe() {
return codexStdioReadyFixture({ workspace, codexHome });
},
async chat(params = {}) {
return codexStdioChatFixture({ workspace, codexHome, params });
},
cancel() {},
reapIdle() {}
}
});
await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve));
try {
const { port } = server.address();
const response = await fetch(`http://127.0.0.1:${port}/health/live`);
assert.equal(response.status, 200);
const payload = await response.json();
assert.equal(payload.codeAgent.provider, "openai-responses");
assert.equal(payload.codeAgent.mode, "openai");
assert.equal(payload.codeAgent.ready, false);
assert.equal(payload.codeAgent.capabilityLevel, "read-only-session-tools");
assert.equal(payload.codeAgent.runner.kind, "hwlab-readonly-runner");
assert.equal(payload.codeAgent.runner.codexStdio, false);
assert.equal(payload.codeAgent.longLivedSessionGate.status, "blocked");
assert.equal(payload.readiness.sessionRunner.status, "codex_stdio_ready");
assert.equal(payload.readiness.codeAgent.ready, false);
assert.equal(payload.readiness.codeAgent.provider.provider, "openai-responses");
assert.equal(payload.readiness.codeAgent.sessionRunner.codexStdio, true);
assert.equal(JSON.stringify(payload.codeAgent).includes("test-openai-key-material"), false);
} finally {
await new Promise((resolve, reject) => {
server.close((error) => (error ? reject(error) : resolve()));
});
await rm(workspace, { recursive: true, force: true });
await rm(codexHome, { recursive: true, force: true });
}
});
test("cloud api /v1/agent/chat parse and params errors use structured blocker envelope", async () => {
const server = createCloudApiServer({
env: {
@@ -1934,6 +1989,83 @@ test("cloud api /v1/agent/chat runs Codex stdio pwd with session and workspace e
}
});
test("cloud api health reports Codex stdio runner facts without readonly limitations", async () => {
const workspace = await mkdtemp(path.join(os.tmpdir(), "hwlab-agent-health-stdio-"));
const codexHome = await mkdtemp(path.join(os.tmpdir(), "hwlab-agent-health-codex-home-"));
const server = createCloudApiServer({
env: {
PATH: process.env.PATH,
CODEX_HOME: codexHome,
HWLAB_CODE_AGENT_CODEX_WORKSPACE: workspace,
HWLAB_CODE_AGENT_PROVIDER: "codex-stdio",
HWLAB_CODE_AGENT_MODEL: "gpt-test",
OPENAI_API_KEY: "test-openai-key-material",
HWLAB_CODE_AGENT_OPENAI_BASE_URL: "http://127.0.0.1:65535/v1/responses"
},
codexStdioManager: {
describe() {
return codexStdioReadyFixture({ workspace, codexHome });
},
async probe() {
return codexStdioReadyFixture({ workspace, codexHome });
},
async chat(params = {}) {
return codexStdioChatFixture({ workspace, codexHome, params });
},
cancel() {},
reapIdle() {}
}
});
await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve));
try {
const { port } = server.address();
const response = await fetch(`http://127.0.0.1:${port}/health/live`);
assert.equal(response.status, 200);
const payload = await response.json();
assert.equal(payload.codeAgent.status, "codex-stdio-feasible");
assert.equal(payload.codeAgent.ready, true);
assert.equal(payload.codeAgent.provider, "codex-stdio");
assert.equal(payload.codeAgent.runner.kind, "codex-mcp-stdio-runner");
assert.equal(payload.codeAgent.runner.sessionMode, "codex-mcp-stdio-long-lived");
assert.equal(payload.codeAgent.runner.codexStdio, true);
assert.equal(payload.codeAgent.runner.writeCapable, true);
assert.equal(payload.codeAgent.runner.durableSession, true);
assert.equal(payload.codeAgent.workspace, workspace);
assert.equal(payload.codeAgent.sandbox, "workspace-write");
assert.equal(payload.codeAgent.sessionMode, "codex-mcp-stdio-long-lived");
assert.equal(payload.codeAgent.sessionRegistry.kind, "codex-stdio-session-registry");
assert.deepEqual(payload.codeAgent.sessionRegistry.statuses, ["creating", "ready", "busy", "idle", "interrupted", "expired", "failed"]);
assert.equal(payload.codeAgent.capabilityLevel, "long-lived-codex-stdio-session");
assert.equal(payload.codeAgent.longLivedSessionGate.status, "pass");
assert.equal(payload.readiness.sessionRunner.status, "codex_stdio_ready");
assert.equal(payload.readiness.sessionRunner.kind, "codex-mcp-stdio-runner");
assert.equal(payload.readiness.sessionRunner.codexStdio, true);
assert.equal(payload.readiness.sessionRunner.writeCapable, true);
assert.equal(payload.readiness.sessionRunner.capabilityLevel, "long-lived-codex-stdio-session");
assert.equal(payload.readiness.codeAgent.sessionRunner.codexStdio, true);
assert.equal(payload.readiness.codeAgent.sessionRunner.writeCapable, true);
assert.deepEqual(payload.codeAgent.runnerLimitations, [
"hardware-control-via-cloud-api-only",
"no-direct-gateway-link",
"no-direct-box-simu-link",
"no-direct-patch-panel-link",
"secret-values-redacted"
]);
const serialized = JSON.stringify(payload.codeAgent);
assert.equal(serialized.includes("not-codex-stdio"), false);
assert.equal(serialized.includes("not-write-capable"), false);
assert.equal(serialized.includes("process-local-session-registry"), false);
assert.equal(serialized.includes("test-openai-key-material"), false);
} finally {
await new Promise((resolve, reject) => {
server.close((error) => (error ? reject(error) : resolve()));
});
await rm(workspace, { recursive: true, force: true });
await rm(codexHome, { recursive: true, force: true });
}
});
test("cloud api /v1/agent/chat runs Codex stdio pwd through controlled command path without OpenAI fallback", async () => {
const workspace = await mkdtemp(path.join(os.tmpdir(), "hwlab-agent-stdio-pwd-"));
const codexHome = path.join(workspace, "codex-home");
+12 -4
View File
@@ -276,10 +276,18 @@ function hasBlockingSkillCliSignal(payload) {
function isLongLivedReady(payload) {
return (
payload.longLivedSessionGate?.status === "pass" ||
payload.longLivedSessionGate?.pass === true ||
payload.codexStdioFeasibility?.canStartLongLivedCodexStdio === true ||
payload.capabilityLevel === "long-lived-codex-stdio-session" ||
(
payload.longLivedSessionGate?.status === "pass" ||
payload.longLivedSessionGate?.pass === true ||
payload.codexStdioFeasibility?.canStartLongLivedCodexStdio === true
) &&
(
payload.provider === "codex-stdio" ||
payload.runner?.kind === "codex-mcp-stdio-runner" ||
payload.runner?.codexStdio === true ||
payload.session?.codexStdio === true
) &&
payload.capabilityLevel === "long-lived-codex-stdio-session" &&
payload.sessionMode === "codex-mcp-stdio-long-lived"
) && payload.status !== "failed" && payload.status !== "blocked" && payload.ready !== false;
}
@@ -52,6 +52,34 @@ test("summarizes long-lived Code Agent as green with current deployment revision
assert.equal(Object.keys(summary.fields).includes("sourceMain"), false);
});
test("does not mark Codex stdio-shaped fields green without runner and gate evidence", () => {
const summary = classifyCodeAgentStatusSummary({
availability: {
status: "partial",
ready: true,
provider: "openai-responses",
mode: "openai",
backend: "hwlab-cloud-api/openai-responses",
capabilityLevel: "long-lived-codex-stdio-session",
sessionMode: "codex-mcp-stdio-long-lived",
runner: {
kind: "openai-responses-fallback",
codexStdio: false,
writeCapable: false
},
longLivedSessionGate: {
status: "blocked",
pass: false,
blockers: [{ code: "openai_responses_fallback_not_session" }]
}
}
});
assert.notEqual(summary.kind, "long-lived-session");
assert.notEqual(summary.tone, "ok");
assert.equal(summary.readinessBlockers.includes("openai_responses_fallback_not_session"), true);
});
test("maps degraded readonly session tools to warning, not green", () => {
const summary = classifyCodeAgentStatusSummary({
availability: {
+12 -1
View File
@@ -397,7 +397,7 @@ function classifyStructuredCodeAgentProbe(readiness = {}) {
const provider = readiness.provider?.provider ?? "not_observed";
const backend = readiness.provider?.backend ?? "not_observed";
const capability = readiness.sessionRunner?.capabilityLevel ?? "not_observed";
if (readiness.ready === true && readiness.codexStdioFeasible === true) {
if (structuredCodexStdioReady(readiness)) {
return passProbe({
serviceId,
apiPath,
@@ -425,6 +425,17 @@ function classifyStructuredCodeAgentProbe(readiness = {}) {
});
}
function structuredCodexStdioReady(readiness = {}) {
return Boolean(
readiness.sessionRunnerReady === true &&
readiness.codexStdioFeasible === true &&
readiness.sessionRunner?.codexStdio === true &&
readiness.sessionRunner?.writeCapable === true &&
readiness.sessionRunner?.longLivedSession === true &&
readiness.sessionRunner?.capabilityLevel === "long-lived-codex-stdio-session"
);
}
function currentStructuredBlockers(readiness = {}) {
return Array.isArray(readiness.currentBlockers)
? readiness.currentBlockers.filter(Boolean)
@@ -789,6 +789,120 @@ test("keeps ready durable runtime green while attributing read-only mode to Code
assert.doesNotMatch(healthLiveProbe.reason, /blocked/u);
});
test("classifies Codex stdio session facts as pass even when unrelated runtime durability is blocked", () => {
const status = classifyWorkbenchLiveStatus({
healthLive: {
ok: true,
status: 200,
data: {
serviceId: "hwlab-cloud-api",
status: "degraded",
ready: false,
readiness: {
status: "degraded",
ready: false,
durability: {
status: "blocked",
ready: false,
blocker: "runtime_durable_adapter_query_blocked"
},
codeAgent: {
status: "blocked",
ready: false,
providerReady: true,
durableDbReady: false,
sessionRunnerReady: true,
codexStdioFeasible: true,
currentBlocker: "runtime_durable_adapter_query_blocked",
currentBlockers: ["runtime_durable_adapter_query_blocked"],
provider: {
status: "ready",
ready: true,
provider: "codex-stdio",
backend: "hwlab-cloud-api/codex-mcp-stdio",
mode: "codex-stdio"
},
sessionRunner: {
status: "codex_stdio_ready",
ready: true,
kind: "codex-mcp-stdio-runner",
mode: "codex-mcp-stdio-long-lived",
capabilityLevel: "long-lived-codex-stdio-session",
longLivedSession: true,
durableSession: true,
codexStdio: true,
writeCapable: true,
blocker: null
},
codexStdio: {
status: "ready",
ready: true,
feasible: true,
blocker: null,
blockerCodes: []
},
secretMaterialRead: false,
valuesRedacted: true
}
},
codeAgent: {
endpoint: "POST /v1/agent/chat",
status: "codex-stdio-feasible",
ready: true,
provider: "codex-stdio",
backend: "hwlab-cloud-api/codex-mcp-stdio",
capabilityLevel: "long-lived-codex-stdio-session",
sessionMode: "codex-mcp-stdio-long-lived",
workspace: "/workspace/hwlab",
sandbox: "workspace-write",
runner: {
kind: "codex-mcp-stdio-runner",
sessionMode: "codex-mcp-stdio-long-lived",
codexStdio: true,
writeCapable: true,
durableSession: true
},
runnerLimitations: [
"hardware-control-via-cloud-api-only",
"no-direct-gateway-link",
"no-direct-box-simu-link",
"no-direct-patch-panel-link",
"secret-values-redacted"
],
longLivedSessionGate: {
status: "pass",
pass: true
},
codexStdioFeasibility: {
ready: true,
canStartLongLivedCodexStdio: true,
commandProbe: { ready: true },
blockerCodes: []
}
},
runtime: {
status: "degraded",
durable: true,
ready: false,
blocker: "runtime_durable_adapter_query_blocked"
}
}
},
restIndex: okRest,
health: okApi,
adapter: okApi,
m3Control: okM3Control,
m3Status: okM3Status
});
const codeAgentProbe = status.probes.find((probe) => probe.apiPath === "POST /v1/agent/chat");
assert.equal(codeAgentProbe.kind, "pass");
assert.equal(codeAgentProbe.reasonCode, "codex_stdio_ready");
assert.match(codeAgentProbe.reason, /capability=long-lived-codex-stdio-session/u);
assert.doesNotMatch(codeAgentProbe.reason, /not-codex-stdio|not-write-capable|process-local-session-registry/u);
assert.notEqual(status.reasonCode, "codex_stdio_blocked_readonly_session_available");
});
test("fixture matrix keeps durable_readiness_ready green and attributes read-only mode to Code Agent", () => {
const restPayload = {
serviceId: "hwlab-cloud-api",