import assert from "node:assert/strict"; import test from "node:test"; import { codeAgentAttributionFromMessage, codeAgentFactsFromMessage, codeAgentRuntimePathFromMessage } from "./code-agent-facts.ts"; test("codex-readonly-runner is shown as partial read-only session registry", () => { const facts = codeAgentFactsFromMessage({ status: "source", provider: "codex-readonly-runner", model: "read-only-tools", backend: "hwlab-cloud-api/codex-readonly-runner", workspace: "/workspace/hwlab", sandbox: "read-only", capabilityLevel: "read-only-session-tools", sessionMode: "controlled-readonly-session-registry", implementationType: "controlled-readonly-session-registry", session: { sessionId: "ses_readonly", status: "idle", turn: 1, lastTraceId: "trc_readonly" }, sessionReuse: { reused: false, turn: 1, status: "idle" }, runner: { kind: "hwlab-readonly-runner", codexStdio: false, writeCapable: false, durableSession: false, longLivedSession: true }, longLivedSessionGate: { status: "blocked", pass: false, blockers: [{ code: "codex_stdio_blocked_readonly_session_available" }] }, toolCalls: [{ name: "pwd", status: "completed" }], skills: { status: "not_requested", count: 0 }, runnerTrace: { traceId: "trc_readonly", runnerKind: "hwlab-readonly-runner", sessionMode: "controlled-readonly-session-registry", turn: 1 }, traceId: "trc_readonly" }); assert.equal(facts.kind, "controlled-readonly-session-registry"); assert.equal(facts.fullCodeAgent, false); assert.equal(facts.codexSessionGatePass, false); assert.match(facts.statusLabel, /只读长会话/u); assert.match(facts.summary, /可复用/u); assert.match(facts.summary, /Codex stdio/u); assert.match(facts.rows.find((row) => row.label === "runner/provider/backend").value, /runner=hwlab-readonly-runner/u); assert.match(facts.rows.find((row) => row.label === "toolCalls").value, /1\/1 pwd\(completed\)/u); }); test("long-lived Codex app-server stdio session fields are shown as full Code Agent session", () => { const message = { status: "completed", provider: "codex-stdio", model: "gpt-5.5", backend: "hwlab-cloud-api/codex-app-server-stdio", workspace: "/workspace/hwlab", sandbox: "workspace-write", capabilityLevel: "long-lived-codex-stdio-session", sessionMode: "codex-app-server-stdio-long-lived", implementationType: "repo-owned-codex-app-server-stdio-session", session: { sessionId: "ses_stdio", status: "idle", turn: 3, idleTimeoutMs: 1800000, lastTraceId: "trc_stdio", longLivedSession: true, codexStdio: true }, runner: { kind: "codex-app-server-stdio-runner", codexStdio: true, writeCapable: true, durableSession: true }, longLivedSessionGate: { status: "pass", pass: true }, toolCalls: [{ name: "codex-reply", status: "completed" }], skills: { status: "not_requested", count: 0 }, runnerTrace: { traceId: "trc_stdio", runnerKind: "codex-app-server-stdio-runner", sessionMode: "codex-app-server-stdio-long-lived", implementationType: "repo-owned-codex-app-server-stdio-session", turn: 3 }, providerTrace: { transport: "stdio", protocol: "codex-app-server-jsonrpc-stdio", wireApi: "responses", command: "codex app-server --listen stdio://", terminalStatus: "completed" }, traceId: "trc_stdio" }; const facts = codeAgentFactsFromMessage(message); const runtimePath = codeAgentRuntimePathFromMessage({ role: "agent", ...message }); assert.equal(facts.kind, "codex-stdio-long-lived"); assert.equal(facts.statusLabel, "真实 runner:Codex app-server stdio 长会话"); assert.equal(facts.fullCodeAgent, true); assert.equal(facts.codexSessionGatePass, true); assert.match(facts.rows.find((row) => row.label === "sandbox").value, /workspace-write/u); assert.equal(runtimePath.kind, "codex-app-server-stdio"); assert.equal(runtimePath.tone, "ok"); assert.equal(runtimePath.provider, "codex-stdio"); assert.equal(runtimePath.runnerKind, "codex-app-server-stdio-runner"); assert.equal(runtimePath.protocol, "codex-app-server-jsonrpc-stdio"); assert.equal(runtimePath.implementationType, "repo-owned-codex-app-server-stdio-session"); assert.equal(runtimePath.command, "codex app-server --listen stdio://"); assert.equal(runtimePath.terminalStatus, "completed"); assert.equal(runtimePath.wireApi, "responses"); assert.match(runtimePath.summary, /Responses wire API/u); }); test("Codex app-server without providerTrace is degraded with structured missing fields", () => { const message = { role: "agent", status: "completed", provider: "codex-stdio", backend: "hwlab-cloud-api/codex-app-server-stdio", capabilityLevel: "long-lived-codex-stdio-session", sessionMode: "codex-app-server-stdio-long-lived", implementationType: "repo-owned-codex-app-server-stdio-session", runner: { kind: "codex-app-server-stdio-runner" }, longLivedSessionGate: { status: "pass", pass: true }, traceId: "trc_missing_provider_trace" }; const facts = codeAgentFactsFromMessage(message); const runtimePath = codeAgentRuntimePathFromMessage(message); assert.equal(facts.kind, "codex-stdio-provider-trace-missing"); assert.equal(facts.fullCodeAgent, false); assert.equal(runtimePath.providerTraceMissing, true); assert.equal(runtimePath.label, "DEGRADED:providerTrace 缺失"); assert.deepEqual(runtimePath.missingFields, ["protocol", "providerTrace.command", "providerTrace.terminalStatus"]); }); test("openai-responses fallback is explicitly text-chat-only", () => { const message = { status: "source", provider: "openai-responses", model: "gpt-5.5", backend: "hwlab-cloud-api/openai-responses", capabilityLevel: "text-chat-only", sessionMode: "provider-text-request", runner: { kind: "openai-responses-fallback", codexStdio: false, writeCapable: false, durableSession: false }, longLivedSessionGate: { status: "blocked", pass: false, blockers: [{ code: "openai_responses_fallback_not_session" }] }, toolCalls: [], skills: { status: "not_requested", count: 0 }, runnerTrace: { traceId: "trc_fallback", runnerKind: "openai-responses-fallback", events: ["fallback:text-chat-only"] }, traceId: "trc_fallback" }; const facts = codeAgentFactsFromMessage(message); const attribution = codeAgentAttributionFromMessage(message); assert.equal(facts.kind, "text-chat-only"); assert.equal(facts.statusLabel, "OpenAI fallback:只是文本回答 / text-chat-only"); assert.equal(facts.fullCodeAgent, false); assert.equal(facts.codexSessionGatePass, false); assert.match(facts.summary, /只是文本回答/u); assert.match(attribution.label, /只是文本回答/u); assert.match(attribution.summary, /当前不能执行工具/u); assert.equal(attribution.fields.find((field) => field.key === "runnerKind").value, "openai-responses-fallback / 不是 runner 控制"); assert.equal(attribution.fields.find((field) => field.key === "capabilityLevel").value, "text-chat-only / 只是文本回答"); assert.equal(attribution.fields.find((field) => field.key === "toolCalls").value, "0/0 / 无工具调用:只是文本回答"); }); test("stateless one-shot is not wrapped as a long-lived session", () => { const facts = codeAgentFactsFromMessage({ status: "source", provider: "codex-cli", model: "gpt-5.5", backend: "hwlab-cloud-api/codex-cli", workspace: "/workspace/hwlab", sandbox: "read-only", capabilityLevel: "text-chat-only", sessionMode: "ephemeral-one-shot", implementationType: "codex-cli-one-shot-ephemeral", session: { sessionId: "cnv_one_shot", status: "expired", idleTimeoutMs: 0, lastTraceId: "trc_one_shot" }, runner: { kind: "codex-cli-one-shot-ephemeral", codexStdio: false, writeCapable: false, durableSession: false }, longLivedSessionGate: { status: "blocked", pass: false, blockers: [{ code: "one_shot_runner_not_long_lived" }] }, traceId: "trc_one_shot" }); assert.equal(facts.kind, "stateless-one-shot"); assert.equal(facts.fullCodeAgent, false); assert.equal(facts.codexSessionGatePass, false); assert.match(facts.summary, /一次性 runner/u); }); test("missing attribution fields render structured evidence insufficiency", () => { const attribution = codeAgentAttributionFromMessage({ role: "agent", status: "failed", traceId: "trc_missing_fields", error: { code: "provider_unavailable", message: "blocked" } }); assert.equal(attribution.label, "BLOCKED:当前不能执行"); assert.equal(attribution.fields.find((field) => field.key === "traceId").value, "trc_missing_fields"); assert.equal(attribution.fields.find((field) => field.key === "provider").missing, true); assert.equal(attribution.fields.find((field) => field.key === "backend").value, "字段缺失:证据不足"); assert.equal(attribution.fields.find((field) => field.key === "runnerKind").chip, "runnerKind=字段缺失:证据不足"); assert.equal(attribution.fields.find((field) => field.key === "providerTrace.command").missing, true); assert.match(attribution.summary, /字段缺失/u); });