import assert from "node:assert/strict"; import test from "node:test"; import { classifyCodeAgentStatusSummary } from "./code-agent-status.ts"; const liveRevision = Object.freeze({ healthLive: { ok: true, status: 200, data: { serviceId: "hwlab-cloud-api", status: "ok", commitId: "7de6edd2c41f54e4265e822d2593fa67057fa41a" } } }); test("summarizes long-lived Code Agent as green with current deployment revision only", () => { const summary = classifyCodeAgentStatusSummary({ live: liveRevision, availability: { status: "codex-stdio-feasible", ready: true, provider: "codex-stdio", mode: "codex-stdio", backend: "hwlab-cloud-api/codex-stdio", capabilityLevel: "long-lived-codex-stdio-session", sessionMode: "codex-app-server-stdio-long-lived", implementationType: "repo-owned-codex-app-server-stdio-session", session: { status: "idle", lastTraceId: "trc_ready" }, runner: { kind: "codex-app-server-stdio-runner", codexStdio: true }, longLivedSessionGate: { status: "pass" }, codexStdioFeasibility: { canStartLongLivedCodexStdio: true }, providerTrace: { protocol: "codex-app-server-jsonrpc-stdio", command: "codex app-server --listen stdio://", terminalStatus: "completed", wireApi: "responses" } } }); assert.equal(summary.kind, "long-lived-session"); assert.equal(summary.label, "长会话可用"); assert.equal(summary.tone, "ok"); assert.equal(summary.codeAgentStatus, "codex-stdio-feasible"); assert.equal(summary.providerModeBackend, "codex-stdio / codex-stdio / hwlab-cloud-api/codex-stdio"); assert.equal(summary.capabilityLevel, "long-lived-codex-stdio-session"); assert.equal(summary.sessionSummary, "codex-app-server-stdio-long-lived / idle / idle"); assert.equal(summary.sessionLifecycleStatus, "idle"); assert.match(summary.sessionLifecycleHint, /新会话已建立/u); assert.equal(summary.runnerKind, "codex-app-server-stdio-runner"); assert.equal(summary.protocol, "codex-app-server-jsonrpc-stdio"); assert.equal(summary.implementationType, "repo-owned-codex-app-server-stdio-session"); assert.equal(summary.providerTraceCommand, "codex app-server --listen stdio://"); assert.equal(summary.providerTraceTerminalStatus, "completed"); assert.match(summary.runtimePathLabel, /repo-owned Codex app-server stdio/u); assert.equal(summary.readinessBlockers.length, 0); assert.equal(summary.lastTraceId, "trc_ready"); assert.equal(summary.deploymentRevision, "7de6edd2c41f"); assert.equal(Object.keys(summary.fields).includes("sourceMain"), false); }); test("summarizes Codex app-server stdio thread as reusable long-lived session", () => { const summary = classifyCodeAgentStatusSummary({ latestMessage: { status: "completed", provider: "codex-stdio", mode: "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", session: { sessionId: "ses_app_server", threadId: "thread_app_server", status: "idle", lastTraceId: "trc_app_server", codexStdio: true }, sessionReuse: { sessionId: "ses_app_server", threadId: "thread_app_server", reused: true, turn: 2 }, runner: { kind: "codex-app-server-stdio-runner", codexStdio: true, writeCapable: true, durableSession: true }, longLivedSessionGate: { status: "pass" }, providerTrace: { protocol: "codex-app-server-jsonrpc-stdio", threadId: "thread_app_server", terminalStatus: "completed" } } }); assert.equal(summary.kind, "long-lived-session"); assert.equal(summary.tone, "ok"); assert.equal(summary.sessionIdStatus, "ses_app_server / thread_app_server / idle / idle"); assert.equal(summary.sessionLifecycleLabel, "继续当前会话"); assert.match(summary.sessionLifecycleHint, /可继续当前会话/u); assert.equal(summary.fields.threadId, "thread_app_server"); }); test("maps degraded readonly session tools to warning, not green", () => { const summary = classifyCodeAgentStatusSummary({ availability: { status: "partial", ready: false, provider: "codex-readonly-runner", backend: "hwlab-cloud-api/controlled-readonly-session-registry", capabilityLevel: "read-only-session-tools", sessionMode: "controlled-readonly-session-registry", workspace: "/workspace/hwlab", sandbox: "read-only", session: { sessionId: "ses_readonly", status: "idle", lastTraceId: "trc_readonly", workspace: "/workspace/hwlab", sandbox: "read-only", runnerKind: "hwlab-readonly-runner", sessionMode: "controlled-readonly-session-registry" }, runner: { kind: "hwlab-readonly-runner", writeCapable: false }, 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 }, blockers: [ { code: "stdio_protocol_not_wired" } ] } }); assert.equal(summary.kind, "read-only-session-tools"); assert.equal(summary.label, "只读长会话工具"); assert.equal(summary.tone, "warn"); assert.equal(summary.sessionId, "ses_readonly"); assert.equal(summary.sessionIdStatus, "ses_readonly / idle / idle"); assert.equal(summary.workspace, "/workspace/hwlab"); assert.equal(summary.sandbox, "read-only"); assert.equal(summary.runnerKind, "hwlab-readonly-runner"); assert.equal(summary.toolCalls, "1/1 pwd(completed)"); assert.equal(summary.skills, "not_requested:count=0"); assert.match(summary.runnerTrace, /runnerKind=hwlab-readonly-runner/u); assert.equal(summary.readinessBlockers.includes("stdio_protocol_not_wired"), true); assert.equal(summary.lastTraceId, "trc_readonly"); }); test("summarizes Codex runtimeContract blockers from health availability", () => { const summary = classifyCodeAgentStatusSummary({ availability: { status: "partial", ready: false, provider: "codex-readonly-runner", backend: "hwlab-cloud-api/codex-readonly-runner", capabilityLevel: "read-only-session-tools", sessionMode: "controlled-readonly-session-registry", runner: { kind: "hwlab-readonly-runner", writeCapable: false }, runtimeContract: { binary: { status: "missing" }, lifecycleSupervisor: { status: "blocked" }, stdioProtocol: { status: "blocked" } } } }); assert.equal(summary.kind, "read-only-session-tools"); assert.equal(summary.tone, "warn"); assert.equal(summary.readinessBlockers.includes("codex_cli_binary_missing"), true); assert.equal(summary.readinessBlockers.includes("runner_lifecycle_missing"), true); assert.equal(summary.readinessBlockers.includes("stdio_protocol_not_wired"), true); }); test("maps text fallback to warning with safe labels", () => { const summary = classifyCodeAgentStatusSummary({ availability: { status: "available", ready: true, provider: "openai-responses", mode: "openai", backend: "hwlab-cloud-api/openai-responses", capabilityLevel: "text-chat-only", runner: { kind: "openai-responses-fallback" } } }); assert.equal(summary.kind, "fallback-text-chat-only"); assert.equal(summary.label, "会话降级"); assert.equal(summary.tone, "warn"); assert.equal(summary.provider, "openai-responses"); assert.equal(summary.capabilityLevel, "text-chat-only"); assert.equal(summary.sessionLifecycleStatus, "failed"); assert.match(summary.sessionLifecycleHint, /fallback\/text-only\/read-only/u); }); test("maps stateless one-shot runner separately from long-lived sessions", () => { const summary = classifyCodeAgentStatusSummary({ latestMessage: { status: "completed", provider: "codex-cli", backend: "hwlab-cloud-api/codex-cli", capabilityLevel: "text-chat-only", sessionMode: "ephemeral-one-shot", runner: { kind: "codex-cli-one-shot-ephemeral" }, runnerLimitations: ["one-shot", "not-durable-session"], traceId: "trc_one_shot" } }); assert.equal(summary.kind, "stateless-one-shot"); assert.equal(summary.label, "一次性执行"); assert.equal(summary.tone, "warn"); assert.equal(summary.sessionMode, "ephemeral-one-shot"); assert.equal(summary.lastTraceId, "trc_one_shot"); }); test("summarizes active Code Agent running request as pending with trace and session context", () => { const summary = classifyCodeAgentStatusSummary({ latestMessage: { role: "agent", status: "running", sourceKind: "PENDING", traceId: "trc_pending_heavy_skills", conversationId: "cnv_pending_heavy_skills", sessionId: "cnv_pending_heavy_skills" } }); assert.equal(summary.kind, "busy"); assert.equal(summary.label, "会话忙/请等待"); assert.equal(summary.tone, "pending"); assert.equal(summary.codeAgentStatus, "running"); assert.equal(summary.sessionId, "cnv_pending_heavy_skills"); assert.equal(summary.sessionLifecycleStatus, "busy"); assert.match(summary.sessionLifecycleHint, /会话忙\/请等待/u); assert.equal(summary.lastTraceId, "trc_pending_heavy_skills"); assert.equal(summary.readinessBlockers.length, 0); }); test("summarizes canceled Code Agent request with preserved retry context", () => { const summary = classifyCodeAgentStatusSummary({ latestMessage: { role: "agent", status: "canceled", traceId: "trc_cancel_keep_context", conversationId: "cnv_cancel_keep_context", sessionId: "ses_cancel_keep_context", session: { sessionId: "ses_cancel_keep_context", status: "canceled" }, runnerTrace: { traceId: "trc_cancel_keep_context", sessionStatus: "canceled", lastEvent: { type: "cancel", label: "cancel:canceled", status: "canceled" } }, error: { code: "codex_stdio_canceled", message: "请求已取消" } } }); assert.equal(summary.kind, "canceled"); assert.equal(summary.label, "请求已取消"); assert.equal(summary.tone, "blocked"); assert.equal(summary.codeAgentStatus, "canceled"); assert.equal(summary.sessionId, "ses_cancel_keep_context"); assert.equal(summary.sessionStatus, "canceled"); assert.equal(summary.sessionLifecycleStatus, "interrupted"); assert.match(summary.sessionLifecycleHint, /已中断/u); assert.equal(summary.lastTraceId, "trc_cancel_keep_context"); }); test("summarizes timed out Code Agent request with preserved trace context", () => { const summary = classifyCodeAgentStatusSummary({ latestMessage: { role: "agent", status: "timeout", traceId: "trc_timeout_keep_context", conversationId: "cnv_timeout_keep_context", sessionId: "ses_timeout_keep_context", session: { sessionId: "ses_timeout_keep_context", status: "timeout" }, runnerTrace: { traceId: "trc_timeout_keep_context", sessionStatus: "timeout", lastEvent: { type: "session", label: "session:timeout", status: "timeout" } }, error: { code: "codex_stdio_timeout", message: "请求超时" } } }); assert.equal(summary.kind, "timeout"); assert.equal(summary.label, "等待超时"); assert.equal(summary.tone, "blocked"); assert.equal(summary.codeAgentStatus, "timeout"); assert.equal(summary.sessionId, "ses_timeout_keep_context"); assert.equal(summary.sessionStatus, "timeout"); assert.equal(summary.sessionLifecycleStatus, "failed"); assert.equal(summary.lastTraceId, "trc_timeout_keep_context"); }); test("surfaces expired and failed Code Agent session lifecycle hints", () => { const expired = classifyCodeAgentStatusSummary({ latestMessage: { status: "failed", provider: "codex-stdio", session: { sessionId: "ses_expired_ui", status: "expired", codexStdio: true }, error: { code: "session_expired" } } }); assert.equal(expired.kind, "session-expired"); assert.equal(expired.label, "会话已过期"); assert.equal(expired.sessionLifecycleStatus, "expired"); assert.match(expired.sessionLifecycleHint, /重新发送建立新的 Code Agent session/u); const failed = classifyCodeAgentStatusSummary({ latestMessage: { status: "failed", provider: "codex-stdio", session: { sessionId: "ses_failed_ui", status: "failed", codexStdio: true }, error: { code: "session_failed" } } }); assert.equal(failed.kind, "session-failed"); assert.equal(failed.label, "会话失败"); assert.equal(failed.sessionLifecycleStatus, "failed"); assert.match(failed.sessionLifecycleHint, /需重新发送建立新会话/u); }); test("missing Code Agent session evidence is shown as lifecycle degradation", () => { const summary = classifyCodeAgentStatusSummary({ latestMessage: { status: "completed", provider: "codex-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", codexStdio: true, writeCapable: true }, blocker: { code: "code_agent_session_evidence_missing" } } }); assert.notEqual(summary.tone, "ok"); assert.equal(summary.sessionLifecycleLabel, "会话降级"); assert.match(summary.sessionLifecycleHint, /会话证据不完整/u); }); test("maps blockers to blocked tone and redacts secret-like fields", () => { const summary = classifyCodeAgentStatusSummary({ availability: { status: "blocked", ready: false, provider: "openai-responses", backend: "Secret hwlab-code-agent-provider should not show", capabilityLevel: "blocked", reason: "OPENAI_API_KEY_missing", error: { code: "provider_unavailable", providerStatus: 503 }, missingEnv: ["OPENAI_API_KEY"], traceId: "trc_blocked" } }); assert.equal(summary.kind, "blocked"); assert.equal(summary.label, "服务受阻"); assert.equal(summary.tone, "blocked"); assert.equal(summary.backend, "已隐藏"); assert.equal(summary.readinessBlockers.includes("provider_config_blocked"), true); assert.equal(summary.readinessBlockers.includes("provider_unavailable"), true); assert.equal(summary.readinessBlockers.includes("provider_http_503"), true); assert.equal(summary.lastTraceId, "trc_blocked"); }); test("uses structured readiness current blockers and ignores stale provider blockers", () => { const summary = classifyCodeAgentStatusSummary({ live: { restIndex: { ok: true, status: 200, data: { readiness: { codeAgent: { status: "blocked", ready: false, providerReady: true, durableDbReady: true, sessionRunnerReady: true, codexStdioFeasible: false, currentBlocker: "codex_stdio_supervisor_disabled", currentBlockers: ["codex_stdio_supervisor_disabled"], provider: { status: "ready", ready: true, provider: "openai-responses", backend: "hwlab-cloud-api/openai-responses", mode: "openai", blocker: null }, dbDurable: { status: "ready", ready: true, adapter: "postgres", blocker: null, queryResult: "durable_readiness_ready" }, sessionRunner: { status: "read_only_long_lived_ready", ready: true, kind: "hwlab-readonly-runner", mode: "controlled-readonly-session-registry", capabilityLevel: "read-only-session-tools", longLivedSession: true, durableSession: false, codexStdio: false, writeCapable: false, blocker: null }, codexStdio: { status: "blocked", ready: false, feasible: false, blocker: "codex_stdio_supervisor_disabled", blockerCodes: ["codex_stdio_supervisor_disabled"] } } }, codeAgent: { status: "partial", ready: false, provider: "openai-responses", backend: "hwlab-cloud-api/openai-responses", capabilityLevel: "read-only-session-tools", blockerCodes: [ "provider_unavailable", "runtime_durable_adapter_auth_blocked" ] } } } } }); assert.equal(summary.kind, "read-only-session-tools"); assert.equal(summary.tone, "warn"); assert.equal(summary.provider, "openai-responses"); assert.equal(summary.capabilityLevel, "read-only-session-tools"); assert.deepEqual(summary.readinessBlockers, ["codex_stdio_supervisor_disabled"]); assert.equal(summary.readinessBlockers.includes("provider_unavailable"), false); assert.equal(summary.readinessBlockers.includes("runtime_durable_adapter_auth_blocked"), false); }); test("structured readiness without codeAgent payload degrades into blocked status", () => { const summary = classifyCodeAgentStatusSummary({ live: { healthLive: { ok: true, status: 200, data: { readiness: { codeAgent: { status: "blocked", ready: false, providerReady: false, durableDbReady: true, sessionRunnerReady: false, codexStdioFeasible: false, currentBlocker: "provider_config_blocked", currentBlockers: ["provider_config_blocked"], provider: { status: "blocked", ready: false, provider: "openai-responses", backend: "hwlab-cloud-api/openai-responses", mode: "openai", blocker: "provider_config_blocked" }, sessionRunner: { status: "blocked", ready: false, kind: "unknown", mode: "unknown", capabilityLevel: "blocked", blocker: "session_runner_blocked" }, codexStdio: { status: "blocked", ready: false, blocker: "codex_stdio_supervisor_disabled", blockerCodes: ["codex_stdio_supervisor_disabled"] } } } } } } }); assert.equal(summary.kind, "blocked"); assert.equal(summary.label, "服务受阻"); assert.equal(summary.tone, "blocked"); assert.equal(summary.readinessBlockers.includes("provider_config_blocked"), true); assert.equal(summary.provider, "openai-responses"); });