const trustedLiveProviders = new Set(["openai-responses", "codex-cli", "codex-readonly-runner", "codex-stdio"]);
const trustedRunnerProviders = new Set(["codex-stdio"]);
const partialRunnerProviders = new Set(["codex-readonly-runner"]);
const trustedCodexSandboxes = new Set(["workspace-write", "danger-full-access"]);
const readonlySessionCapabilityLevels = new Set(["read-only-tools", "read-only-session-tools"]);
const untrustedProviderPattern = /\b(?:echo|mock|fixture|stub|sample)\b/iu;
const blockedCodeAgentUiLabels = new Set([
  "后端失败",
  "服务受阻",
  "等待超时",
  "Provider 不可用",
  "Runner 忙碌",
  "Session 受阻",
  "Runner 受阻",
  "API 错误",
  "需要配置",
  "能力未开放",
  "安全阻断",
  "仍是 fallback",
  "BLOCKED 凭证缺口"
]);
const timeoutPattern = /\b(?:timeout|timed out|abort|aborted|超时|请求超过|无新事件)\b/iu;

export function classifyCodeAgentChatReadiness(payload, { realDevLive = false, httpStatus = null } = {}) {
  const providerBlock = classifyCodeAgentProviderBlock(payload, { httpStatus });
  if (providerBlock.blocked) {
    return {
      status: "blocked",
      level: providerBlock.level,
      blocker: providerBlock.blocker,
      devLiveReplyPass: false,
      reason: providerBlock.reason,
      ...(providerBlock.providerStatus != null ? { providerStatus: providerBlock.providerStatus } : {})
    };
  }

  const assistantReply = typeof payload?.reply?.content === "string" ? payload.reply.content.trim() : "";
  if (payload?.status === "completed" && assistantReply) {
    const evidence = inspectRealCompletionEvidence(payload);
    if (!evidence.ok) {
      return {
        status: "blocked",
        level: realDevLive ? "BLOCKED/untrusted-completion" : "SOURCE",
        blocker: "untrusted-completion",
        devLiveReplyPass: false,
        reason: `completed response is missing real provider/model/trace/conversation evidence or looks like echo/mock/stub: ${evidence.reason}`
      };
    }
    if (realDevLive) {
      return {
        status: "pass",
        level: "#143 DEV-LIVE reply pass",
        blocker: null,
        devLiveReplyPass: true,
        reason: "real DEV /v1/agent/chat completed with a non-empty assistant reply"
      };
    }
    return {
      status: "blocked",
      level: "SOURCE",
      blocker: "not-dev-live",
      devLiveReplyPass: false,
      reason: "mock, fixture, local stub, or source-only completion cannot be counted as #143 DEV-LIVE reply pass"
    };
  }

  const suffix = httpStatus ? ` (HTTP ${httpStatus})` : "";
  return {
    status: "blocked",
    level: "BLOCKED",
    blocker: "runtime",
    devLiveReplyPass: false,
    reason: `Code Agent chat did not produce a completed non-empty assistant reply${suffix}`
  };
}

export function classifyCodexRunnerCapability(payload, { httpStatus = null } = {}) {
  const providerBlock = classifyCodeAgentProviderBlock(payload, { httpStatus });
  if (providerBlock.blocked) {
    return {
      status: "blocked",
      level: providerBlock.level,
      blocker: providerBlock.blocker,
      capabilityPass: false,
      reason: providerBlock.reason,
      ...(providerBlock.providerStatus != null ? { providerStatus: providerBlock.providerStatus } : {})
    };
  }

  const provider = stringOrNull(payload?.provider);
  const runnerKind = stringOrNull(payload?.runner?.kind);
  const capabilityLevel = stringOrNull(payload?.capabilityLevel);
  const workspace = stringOrNull(payload?.workspace ?? payload?.runner?.workspace);
  const sandbox = stringOrNull(payload?.sandbox ?? payload?.runner?.sandbox);
  const toolCalls = Array.isArray(payload?.toolCalls) ? payload.toolCalls : [];
  const runnerTrace = payload?.runnerTrace && typeof payload.runnerTrace === "object" ? payload.runnerTrace : null;
  const skills = payload?.skills && typeof payload.skills === "object" ? payload.skills : null;
  const sessionMode = stringOrNull(payload?.sessionMode ?? payload?.runner?.sessionMode ?? payload?.runner?.session);
  const implementationType = stringOrNull(payload?.implementationType ?? payload?.runner?.implementationType);
  const runnerLimitations = Array.isArray(payload?.runnerLimitations)
    ? payload.runnerLimitations.filter((item) => typeof item === "string")
    : Array.isArray(payload?.runner?.runnerLimitations)
      ? payload.runner.runnerLimitations.filter((item) => typeof item === "string")
      : [];
  const sessionReuse = payload?.sessionReuse && typeof payload.sessionReuse === "object" ? payload.sessionReuse : null;
  const session = payload?.session && typeof payload.session === "object" ? payload.session : null;
  const longLivedSessionGate = payload?.longLivedSessionGate && typeof payload.longLivedSessionGate === "object"
    ? payload.longLivedSessionGate
    : null;

  if (provider === "openai-responses" || runnerKind === "openai-responses-fallback") {
    return {
      status: "blocked",
      level: "BLOCKED/text-chat-only",
      blocker: "openai-fallback-not-runner",
      capabilityPass: false,
      reason: "OpenAI Responses fallback can answer text, but it has no workspace/tools/skills runner evidence."
    };
  }

  const missing = [];
  if (payload?.status !== "completed") missing.push("completed status");
  if (!trustedRunnerProviders.has(provider)) missing.push("runner provider");
  if (runnerKind !== "codex-app-server-stdio-runner") missing.push("runner.kind=codex-app-server-stdio-runner");
  if (capabilityLevel !== "long-lived-codex-stdio-session") missing.push("capabilityLevel=long-lived-codex-stdio-session");
  if (!workspace) missing.push("workspace");
  if (!trustedCodexSandboxes.has(sandbox)) missing.push("sandbox=workspace-write|danger-full-access");
  if (sessionMode !== "codex-app-server-stdio-long-lived") missing.push("sessionMode=codex-app-server-stdio-long-lived");
  if (implementationType !== "repo-owned-codex-app-server-stdio-session") missing.push("implementationType=repo-owned-codex-app-server-stdio-session");
  if (!session) missing.push("session");
  if (session && !["idle", "ready", "busy"].includes(session.status)) missing.push("session.status active/idle");
  if (session && typeof session.idleTimeoutMs !== "number") missing.push("session.idleTimeoutMs");
  if (session && !session.lastTraceId) missing.push("session.lastTraceId");
  if (session && session.longLivedSession !== true) missing.push("session.longLivedSession=true");
  if (session && session.codexStdio !== true) missing.push("session.codexStdio=true");
  if (!sessionReuse) missing.push("sessionReuse");
  if (toolCalls.length === 0) missing.push("toolCalls");
  if (!runnerTrace) missing.push("runnerTrace");
  if (!skills) missing.push("skills");
  if (payload?.runner?.codexStdio !== true) missing.push("runner.codexStdio=true");
  if (payload?.runner?.writeCapable !== true) missing.push("runner.writeCapable=true");
  if (payload?.runner?.durableSession !== true) missing.push("runner.durableSession=true");
  if (!longLivedSessionGate) missing.push("longLivedSessionGate");
  if (longLivedSessionGate?.status !== "pass" || longLivedSessionGate?.pass !== true) missing.push("longLivedSessionGate=pass");

  if (missing.length > 0) {
    if (partialRunnerProviders.has(provider) && runnerKind === "hwlab-readonly-runner") {
      return {
        status: "blocked",
        level: "BLOCKED/controlled-readonly-long-lived-session",
        blocker: "controlled-readonly-not-long-lived",
        capabilityPass: false,
        reason: "controlled-readonly-session-registry exposes a reusable read-only session, but it is not Codex stdio, a write-capable sandbox, or a full Code Agent capability pass."
      };
    }
    return {
      status: "blocked",
      level: "BLOCKED/runner-capability",
      blocker: "runner-evidence-missing",
      capabilityPass: false,
      reason: `Codex runner capability evidence is incomplete: ${missing.join(", ")}`
    };
  }

  if (toolCalls.some((tool) => tool.status !== "completed")) {
    return {
      status: "blocked",
      level: "BLOCKED/tool",
      blocker: "tool-unavailable",
      capabilityPass: false,
      reason: "At least one runner tool call is not completed."
    };
  }

  return {
    status: "pass",
    level: "#275 long-lived Codex stdio session skeleton pass",
    blocker: null,
    capabilityPass: true,
    longLivedSession: true,
    reason: "completed repo-owned Codex stdio response includes reusable session, write-capable sandbox, toolCalls, skills, runnerTrace, and a passed long-lived session gate"
  };
}

export function classifyCodeAgentProviderBlock(payload, { httpStatus = null } = {}) {
  const payloadStatus = stringOrNull(payload?.status);
  const errorCode = stringOrNull(payload?.error?.code);
  const availabilityStatus = stringOrNull(payload?.availability?.status);
  const missingEnv = Array.isArray(payload?.error?.missingEnv)
    ? payload.error.missingEnv.filter((item) => typeof item === "string")
    : [];
  const providerStatus = numericStatus(payload?.error?.providerStatus);
  const responseStatus = numericStatus(httpStatus);
  const providerHttpBlocked = isHttpNon2xxStatus(providerStatus);
  const responseHttpBlocked = isHttpNon2xxStatus(responseStatus);
  const httpBlocked = providerHttpBlocked || responseHttpBlocked;
  const blockingHttpStatus = providerHttpBlocked ? providerStatus : responseHttpBlocked ? responseStatus : providerStatus ?? responseStatus;
  const providerUnavailable =
    errorCode === "provider_unavailable" ||
    availabilityStatus === "blocked" ||
    payloadStatus === "blocked" ||
    httpBlocked;

  if (!providerUnavailable) {
    return { blocked: false };
  }

  if (httpBlocked) {
    return {
      blocked: true,
      level: "BLOCKED/provider",
      blocker: "provider-upstream",
      providerStatus: blockingHttpStatus,
      missingEnv,
      reason: `Code Agent HTTP non-2xx status ${blockingHttpStatus} means the upstream response is blocked, not completed`
    };
  }

  if (missingEnv.includes("OPENAI_API_KEY")) {
    return {
      blocked: true,
      level: "BLOCKED/credential",
      blocker: "credential",
      providerStatus: blockingHttpStatus,
      missingEnv,
      reason: "provider_unavailable with missing OPENAI_API_KEY means the provider credential contract is incomplete"
    };
  }

  if (missingEnv.includes("HWLAB_CODE_AGENT_OPENAI_BASE_URL")) {
    return {
      blocked: true,
      level: "BLOCKED/provider-config",
      blocker: "provider-config",
      providerStatus: blockingHttpStatus,
      missingEnv,
      reason: "provider_unavailable with missing HWLAB_CODE_AGENT_OPENAI_BASE_URL means the provider egress/base-url contract is incomplete"
    };
  }

  return {
    blocked: true,
    level: "BLOCKED/provider",
    blocker: "provider-unavailable",
    providerStatus: blockingHttpStatus,
    missingEnv,
    reason: "provider_unavailable or availability.status=blocked means the provider path is blocked, not completed"
  };
}

export function summarizeCodeAgentPayload(payload, { httpStatus = null, traceId = null } = {}) {
  const providerBlock = classifyCodeAgentProviderBlock(payload, { httpStatus });
  const hasObjectPayload = payload && typeof payload === "object" && !Array.isArray(payload);

  if (!hasObjectPayload) {
    if (!providerBlock.blocked) return null;
    return {
      status: "failed",
      sourceKind: null,
      evidenceLevel: null,
      provider: "unknown",
      model: "unknown",
      backend: "unknown",
      traceId: traceId || "unknown",
      hasReply: false,
      error: {
        code: "provider_unavailable",
        missingEnv: [],
        missingCommands: [],
        providerStatus: providerBlock.providerStatus
      },
      runner: null,
      workspace: null,
      sandbox: null,
      capabilityLevel: null,
      toolCalls: [],
      skills: null,
      runnerTrace: null,
      sessionMode: null,
      sessionLifecycleStatus: null,
      sessionSummary: null,
      sessionReuse: null,
      session: null,
      implementationType: null,
      runnerLimitations: [],
      codexStdioFeasibility: null,
      longLivedSessionGate: null,
      conversationFacts: null
    };
  }

  const error = normalizeCodeAgentError(payload.error);
  const assistantReply = typeof payload.reply?.content === "string" ? payload.reply.content.trim() : "";
  const provider = stringOrNull(payload.provider) ?? (providerBlock.blocked ? "unknown" : null);
  const model = stringOrNull(payload.model) ?? (providerBlock.blocked ? "unknown" : null);
  const backend = stringOrNull(payload.backend) ?? (providerBlock.blocked ? "unknown" : null);
  const observedTraceId = stringOrNull(payload.traceId) ?? traceId ?? (providerBlock.blocked ? "unknown" : null);

  if (providerBlock.blocked) {
    const blockedError = error
      ? {
          ...error,
          code: error.code ?? "provider_unavailable",
          providerStatus: error.providerStatus ?? providerBlock.providerStatus
        }
      : {
          code: "provider_unavailable",
          missingEnv: providerBlock.missingEnv ?? [],
          missingCommands: [],
          providerStatus: providerBlock.providerStatus
        };
    return {
      status: "failed",
      sourceKind: stringOrNull(payload.sourceKind),
      evidenceLevel: stringOrNull(payload.evidenceLevel),
      provider,
      model,
      backend,
      traceId: observedTraceId,
      hasReply: false,
      error: blockedError,
      blocker: summarizeBlocker(payload.blocker ?? payload.error?.blocker),
      runner: summarizeRunner(payload),
      workspace: stringOrNull(payload.workspace),
      sandbox: stringOrNull(payload.sandbox),
      capabilityLevel: stringOrNull(payload.capabilityLevel),
      toolCalls: summarizeToolCalls(payload.toolCalls),
      skills: summarizeSkills(payload.skills),
      runnerTrace: summarizeRunnerTrace(payload.runnerTrace),
      sessionMode: stringOrNull(payload.sessionMode),
      sessionLifecycleStatus: stringOrNull(payload.sessionLifecycleStatus ?? payload.sessionSummary?.status ?? payload.session?.lifecycleStatus),
      sessionSummary: summarizeSessionLifecycle(payload.sessionSummary ?? payload.sessionLifecycle),
      sessionReuse: summarizeSessionReuse(payload.sessionReuse),
      session: summarizeSession(payload.session),
      implementationType: stringOrNull(payload.implementationType),
      runnerLimitations: summarizeStringList(payload.runnerLimitations),
      codexStdioFeasibility: summarizeCodexStdioFeasibility(payload.codexStdioFeasibility),
      longLivedSessionGate: summarizeLongLivedSessionGate(payload.longLivedSessionGate),
      conversationFacts: summarizeConversationFacts(payload.conversationFacts)
    };
  }

  return {
    status: stringOrNull(payload.status),
    sourceKind: stringOrNull(payload.sourceKind),
    evidenceLevel: stringOrNull(payload.evidenceLevel),
    provider,
    model,
    backend,
    traceId: observedTraceId,
    hasReply: assistantReply.length > 0,
    error,
    blocker: summarizeBlocker(payload.blocker ?? payload.error?.blocker),
    runner: summarizeRunner(payload),
    workspace: stringOrNull(payload.workspace),
    sandbox: stringOrNull(payload.sandbox),
    capabilityLevel: stringOrNull(payload.capabilityLevel),
    toolCalls: summarizeToolCalls(payload.toolCalls),
    skills: summarizeSkills(payload.skills),
    runnerTrace: summarizeRunnerTrace(payload.runnerTrace),
    sessionMode: stringOrNull(payload.sessionMode),
    sessionLifecycleStatus: stringOrNull(payload.sessionLifecycleStatus ?? payload.sessionSummary?.status ?? payload.session?.lifecycleStatus),
    sessionSummary: summarizeSessionLifecycle(payload.sessionSummary ?? payload.sessionLifecycle),
    sessionReuse: summarizeSessionReuse(payload.sessionReuse),
    session: summarizeSession(payload.session),
    implementationType: stringOrNull(payload.implementationType),
    runnerLimitations: summarizeStringList(payload.runnerLimitations),
    codexStdioFeasibility: summarizeCodexStdioFeasibility(payload.codexStdioFeasibility),
    longLivedSessionGate: summarizeLongLivedSessionGate(payload.longLivedSessionGate),
    conversationFacts: summarizeConversationFacts(payload.conversationFacts)
  };
}

export function classifyCodeAgentBrowserJourney({ responseSummary, httpOk = false, httpStatus = null, ui = null } = {}) {
  const providerBlock = classifyCodeAgentProviderBlock(responseSummary, { httpStatus });
  const runtimeBlock = classifyCodeAgentRuntimeBlock(responseSummary, { httpStatus });
  const uiCompleted = ui?.agentChatStatus === "DEV-LIVE 回复" || ui?.agentChatStatus === "已回复" || ui?.completedMessageVisible === true;
  const uiFailed = ui?.failedMessageVisible === true || blockedCodeAgentUiLabels.has(ui?.agentChatStatus);
  const evidence = inspectSanitizedCompletionEvidence(responseSummary);
  const backendCompleted =
    httpOk &&
    responseSummary?.status === "completed" &&
    responseSummary?.hasReply === true &&
    responseSummary.error === null &&
    evidence.ok;

  if (backendCompleted && uiCompleted && !uiFailed) {
    return {
      status: "pass",
      blocker: null,
      reason: "completed Code Agent payload over HTTP 2xx with real provider/backend/model/trace/reply evidence and completed UI state"
    };
  }

  if (runtimeBlock.blocked) {
    return runtimeBlock;
  }

  if (providerBlock.blocked) {
    return {
      status: "blocked",
      blocker: providerBlock.blocker,
      reason: providerBlock.reason,
      ...(providerBlock.providerStatus != null ? { providerStatus: providerBlock.providerStatus } : {})
    };
  }

  if (responseSummary?.status === "completed") {
    return {
      status: "blocked",
      blocker: "untrusted-completion",
      reason: `Code Agent completed-looking payload was not paired with HTTP success, completed UI, and real provider evidence: ${evidence.reason}`
    };
  }

  return {
    status: "blocked",
    blocker: "runtime",
    reason: "Code Agent browser journey did not produce a completed non-error response"
  };
}

export function classifyCodeAgentRuntimeBlock(payload, { httpStatus = null } = {}) {
  const errorCode = stringOrNull(payload?.error?.code);
  const blockerCode = stringOrNull(payload?.blocker?.code ?? payload?.error?.blocker?.code);
  const layer = stringOrNull(payload?.error?.layer ?? payload?.blocker?.layer ?? payload?.error?.blocker?.layer);
  const category = stringOrNull(payload?.error?.category ?? payload?.blocker?.category ?? payload?.error?.blocker?.category);
  const payloadStatus = stringOrNull(payload?.status);

  if (["skill_cli_api_base_missing"].includes(errorCode ?? blockerCode)) {
    return {
      blocked: true,
      status: "blocked",
      blocker: "needs-config",
      category: "needs_config",
      layer,
      reason: "M3 Skill CLI is missing a safe HWLAB API base URL contract; do not show generic send failure."
    };
  }

  if (["hwlab_api_unavailable"].includes(errorCode ?? blockerCode)) {
    return {
      blocked: true,
      status: "blocked",
      blocker: "retryable",
      category: "retryable",
      layer,
      reason: "HWLAB API is unavailable; the UI should show retryable blocker semantics."
    };
  }

  if (["text_chat_only_fallback"].includes(errorCode ?? blockerCode) || category === "fallback") {
    return {
      blocked: true,
      status: "blocked",
      blocker: "text-chat-only-fallback",
      category: "fallback",
      layer,
      reason: "Code Agent response is still text chat fallback and does not satisfy runner/session/tool control."
    };
  }

  if (["m3_readiness_blocked"].includes(errorCode ?? blockerCode) || layer === "m3-readiness") {
    return {
      blocked: true,
      status: "blocked",
      blocker: "capability-unavailable",
      category: category ?? "capability_unavailable",
      layer,
      reason: "M3 readiness is blocked; the UI must not treat this as generic send failure or completed control."
    };
  }

  if (errorCode === "session_busy") {
    return {
      blocked: true,
      status: "blocked",
      blocker: "runner-busy",
      category: "runner_busy",
      reason: "Code Agent read-only runner session is already busy; the UI must keep the input retryable."
    };
  }

  if (["session_expired", "session_reuse_conflict", "session_failed", "session_interrupted", "session_canceled", "session_timeout", "session_error", "session_storage_evicted"].includes(errorCode)) {
    return {
      blocked: true,
      status: "blocked",
      blocker: "session-blocked",
      category: "session_blocked",
      reason: `Code Agent session is blocked by ${errorCode}; fallback must not be reported as a Codex session.`
    };
  }

  if ([
    "runner_unavailable",
    "tool_unavailable",
    "skills_unavailable",
    "security_blocked",
    "codex_stdio_blocked",
    "codex_stdio_failed",
    "codex_stdio_protocol_blocked",
    "codex_stdio_empty_response",
    "codex_stdio_command_probe_failed"
  ].includes(errorCode)) {
    return {
      blocked: true,
      status: "blocked",
      blocker: "runner-blocked",
      category: "runner_blocked",
      reason: `Code Agent read-only runner returned ${errorCode}; this is blocked runner evidence, not a completed fallback.`
    };
  }

  if (["invalid_params", "parse_error", "request_failed", "cloud_api_proxy_failed", "upstream_unavailable"].includes(errorCode)) {
    return {
      blocked: true,
      status: "blocked",
      blocker: "api-error",
      category: "api_error",
      reason: `Code Agent API returned ${errorCode}; the request is retryable and must remain distinguishable from provider failure.`
    };
  }

  if (payloadStatus === "failed" && errorCode && errorCode !== "provider_unavailable") {
    return {
      blocked: true,
      status: "blocked",
      blocker: "api-error",
      category: "api_error",
      reason: `Code Agent returned failed status with ${errorCode}.`
    };
  }

  return { blocked: false };
}

export function classifyCodeAgentBrowserFailure(error, { traceId = null } = {}) {
  const message = error instanceof Error ? error.message : String(error ?? "");
  const observedTraceId = traceId ?? error?.traceId ?? null;
  if (error?.userMessage) {
    return {
      status: "blocked",
      blocker: blockerForStructuredCategory(error.category, error.code),
      category: error.category ?? "runner_blocked",
      chineseSummary: `${error.userMessage}${observedTraceId ? `；traceId=${observedTraceId}` : ""}`,
      reason: message
    };
  }
  if (timeoutPattern.test(message)) {
    return {
      status: "blocked",
      blocker: "timeout",
      category: "timeout",
      chineseSummary: `超时：Code Agent 在配置的活动时间边界内没有新事件${observedTraceId ? `；traceId=${observedTraceId}` : ""}。`,
      reason: message
    };
  }
  if (/session[_ -]?busy|runner busy|already busy/iu.test(message)) {
    return {
      status: "blocked",
      blocker: "runner-busy",
      category: "runner_busy",
      chineseSummary: `Runner 忙碌：Code Agent 正在处理上一轮请求，可稍后重试${observedTraceId ? `；traceId=${observedTraceId}` : ""}。`,
      reason: message
    };
  }
  if (/session[_ -]?(expired|blocked|reuse_conflict)|conversation.*session/iu.test(message)) {
    return {
      status: "blocked",
      blocker: "session-blocked",
      category: "session_blocked",
      chineseSummary: `Session 受阻：Code Agent 会话无法复用或已过期${observedTraceId ? `；traceId=${observedTraceId}` : ""}。`,
      reason: message
    };
  }
  if (/provider|upstream|openai|codex|502|503|504|429|provider_unavailable/iu.test(message)) {
    return {
      status: "blocked",
      blocker: "provider-upstream",
      category: "provider",
      chineseSummary: `Provider 故障：Code Agent 上游或后端返回失败${observedTraceId ? `；traceId=${observedTraceId}` : ""}。`,
      reason: message
    };
  }
  if (/api|http \d{3}|invalid_params|parse_error|request_failed|cloud_api_proxy_failed/iu.test(message)) {
    return {
      status: "blocked",
      blocker: "api-error",
      category: "api_error",
      chineseSummary: `API 错误：Code Agent 接口返回失败，输入应保留可重试${observedTraceId ? `；traceId=${observedTraceId}` : ""}。`,
      reason: message
    };
  }
  return {
    status: "blocked",
    blocker: "browser-runtime",
    category: "browser",
    chineseSummary: `浏览器故障：Workbench 浏览器链路未完成${observedTraceId ? `；traceId=${observedTraceId}` : ""}。`,
    reason: message || "unknown browser error"
  };
}

function blockerForStructuredCategory(category, code) {
  if (category === "needs_config") return "needs-config";
  if (category === "security_blocked" || code === "security_blocked") return "security-blocked";
  if (category === "fallback" || code === "text_chat_only_fallback") return "text-chat-only-fallback";
  if (category === "capability_unavailable") return "capability-unavailable";
  if (category === "retryable") return "retryable";
  if (category === "runner_busy") return "runner-busy";
  if (category === "session_blocked") return "session-blocked";
  if (category === "timeout") return "timeout";
  return "runtime";
}

export function inspectRealCompletionEvidence(payload) {
  const provider = String(payload?.provider ?? "").trim().toLowerCase();
  const backend = String(payload?.backend ?? "").trim().toLowerCase();
  const missing = [];
  if (!payload?.conversationId && !payload?.sessionId) missing.push("conversationId");
  if (!payload?.traceId) missing.push("traceId");
  if (!payload?.messageId) missing.push("messageId");
  if (!payload?.provider) missing.push("provider");
  if (!payload?.model) missing.push("model");
  if (!payload?.backend) missing.push("backend");
  if (missing.length > 0) {
    return { ok: false, reason: `missing ${missing.join(",")}` };
  }
  if (!trustedLiveProviders.has(provider)) {
    return { ok: false, reason: `provider ${payload.provider} is not an accepted real provider` };
  }
  if (provider === "openai-responses" && payload?.capabilityLevel && payload.capabilityLevel !== "text-chat-only") {
    return { ok: false, reason: "openai-responses cannot claim runner capabilityLevel" };
  }
  if (provider === "codex-readonly-runner" && payload?.longLivedSessionGate?.status === "pass") {
    return { ok: false, reason: "read-only runner cannot claim long-lived session gate pass" };
  }
  if (provider === "codex-readonly-runner") {
    return { ok: false, reason: "controlled read-only runner is partial capability only and cannot be reported as full Code Agent completion" };
  }
  if (provider === "openai-responses" && payload?.longLivedSessionGate?.status !== "pass") {
    return { ok: false, reason: "OpenAI Responses fallback is text-chat-only and cannot be reported as full Code Agent completion" };
  }
  if (provider === "codex-stdio" && payload?.longLivedSessionGate?.status !== "pass") {
    return { ok: false, reason: "codex-stdio completion is missing long-lived session gate pass" };
  }
  if (untrustedProviderPattern.test(`${provider} ${backend}`)) {
    return { ok: false, reason: `provider/backend looks non-real: ${payload.provider}/${payload.backend}` };
  }
  return { ok: true, reason: "provider/model/trace/conversation evidence present" };
}

export function normalizeCodeAgentError(error) {
  if (!error || typeof error !== "object") return null;
  return {
    code: stringOrNull(error.code),
    layer: stringOrNull(error.layer),
    category: stringOrNull(error.category),
    retryable: error.retryable === true,
    userMessage: stringOrNull(error.userMessage),
    traceId: stringOrNull(error.traceId),
    route: stringOrNull(error.route),
    toolName: stringOrNull(error.toolName),
    blocker: summarizeBlocker(error.blocker),
    missingConfig: Array.isArray(error.missingConfig) ? error.missingConfig.filter((item) => typeof item === "string") : [],
    missingEnv: Array.isArray(error.missingEnv) ? error.missingEnv.filter((item) => typeof item === "string") : [],
    missingCommands: Array.isArray(error.missingCommands) ? error.missingCommands.filter((item) => typeof item === "string") : [],
    providerStatus: numericStatus(error.providerStatus)
  };
}

export function isHttpNon2xxStatus(value) {
  const status = numericStatus(value);
  return status !== null && (status < 200 || status > 299);
}

function inspectSanitizedCompletionEvidence(summary) {
  const provider = String(summary?.provider ?? "").trim().toLowerCase();
  const backend = String(summary?.backend ?? "").trim().toLowerCase();
  const missing = [];
  if (!summary?.provider) missing.push("provider");
  if (!summary?.model) missing.push("model");
  if (!summary?.backend) missing.push("backend");
  if (!summary?.traceId) missing.push("traceId");
  if (summary?.hasReply !== true) missing.push("reply");
  if (missing.length > 0) {
    return { ok: false, reason: `missing ${missing.join(",")}` };
  }
  if (!trustedLiveProviders.has(provider)) {
    return { ok: false, reason: `provider ${summary.provider} is not an accepted real provider` };
  }
  if (untrustedProviderPattern.test(`${provider} ${backend}`)) {
    return { ok: false, reason: `provider/backend looks non-real: ${summary.provider}/${summary.backend}` };
  }
  return { ok: true, reason: "real provider/backend/model/trace/reply evidence present" };
}

function summarizeRunner(payload) {
  if (!payload?.runner || typeof payload.runner !== "object") return null;
  return {
    kind: stringOrNull(payload.runner.kind),
    backend: stringOrNull(payload.runner.backend),
    workspace: stringOrNull(payload.runner.workspace),
    sandbox: stringOrNull(payload.runner.sandbox),
    session: stringOrNull(payload.runner.session),
    sessionMode: stringOrNull(payload.runner.sessionMode),
    sessionId: stringOrNull(payload.runner.sessionId),
    implementationType: stringOrNull(payload.runner.implementationType),
    codexStdio: payload.runner.codexStdio === true,
    writeCapable: payload.runner.writeCapable === true,
    durableSession: payload.runner.durableSession === true,
    longLivedSession: payload.runner.longLivedSession === true,
    readOnly: payload.runner.readOnly === true,
    capabilityLevel: stringOrNull(payload.runner.capabilityLevel),
    runnerLimitations: summarizeStringList(payload.runner.runnerLimitations)
  };
}

function summarizeToolCalls(value) {
  if (!Array.isArray(value)) return [];
  return value.map((tool) => ({
    name: stringOrNull(tool?.name),
    type: stringOrNull(tool?.type),
    status: stringOrNull(tool?.status),
    route: stringOrNull(tool?.route),
    capabilityLevel: stringOrNull(tool?.capabilityLevel),
    blocker: summarizeBlocker(tool?.blocker),
    exitCode: numericStatus(tool?.exitCode),
    outputTruncated: tool?.outputTruncated === true
  }));
}

function summarizeSkills(value) {
  if (!value || typeof value !== "object") return null;
  return {
    status: stringOrNull(value.status),
    count: typeof value.count === "number" ? value.count : Array.isArray(value.items) ? value.items.length : null,
    totalCount: typeof value.totalCount === "number" ? value.totalCount : null,
    blockers: Array.isArray(value.blockers)
      ? value.blockers.map((blocker) => ({
          code: stringOrNull(blocker.code),
          sourceIssue: stringOrNull(blocker.sourceIssue)
        }))
      : []
  };
}

function summarizeRunnerTrace(value) {
  if (!value || typeof value !== "object") return null;
  return {
    runnerKind: stringOrNull(value.runnerKind),
    route: stringOrNull(value.route),
    capabilityLevel: stringOrNull(value.capabilityLevel),
    blocker: summarizeBlocker(value.blocker),
    sandbox: stringOrNull(value.sandbox),
    sessionMode: stringOrNull(value.sessionMode),
    sessionStatus: stringOrNull(value.sessionStatus),
    sessionLifecycleStatus: stringOrNull(value.sessionLifecycleStatus),
    implementationType: stringOrNull(value.implementationType),
    sessionReused: value.sessionReused === true,
    turn: typeof value.turn === "number" ? value.turn : null,
    outputTruncated: value.outputTruncated === true,
    valuesPrinted: value.valuesPrinted === true
  };
}

function summarizeBlocker(value) {
  if (!value || typeof value !== "object") return null;
  return {
    code: stringOrNull(value.code),
    layer: stringOrNull(value.layer),
    category: stringOrNull(value.category),
    retryable: value.retryable === true,
    userMessage: stringOrNull(value.userMessage),
    route: stringOrNull(value.route),
    toolName: stringOrNull(value.toolName)
  };
}

function summarizeSessionReuse(value) {
  if (!value || typeof value !== "object") return null;
  return {
    conversationId: stringOrNull(value.conversationId),
    sessionId: stringOrNull(value.sessionId),
    mapped: value.mapped === true,
    reused: value.reused === true,
    turn: typeof value.turn === "number" ? value.turn : null,
    previousTurns: typeof value.previousTurns === "number" ? value.previousTurns : null,
    workspace: stringOrNull(value.workspace),
    status: stringOrNull(value.status),
    idleTimeoutMs: typeof value.idleTimeoutMs === "number" ? value.idleTimeoutMs : null,
    lastTraceId: stringOrNull(value.lastTraceId)
  };
}

function summarizeSession(value) {
  if (!value || typeof value !== "object") return null;
  return {
    sessionId: stringOrNull(value.sessionId),
    conversationId: stringOrNull(value.conversationId),
    status: stringOrNull(value.status),
    lifecycleStatus: stringOrNull(value.lifecycleStatus ?? value.lifecycle?.status),
    workspace: stringOrNull(value.workspace),
    sandbox: stringOrNull(value.sandbox),
    runnerKind: stringOrNull(value.runnerKind),
    capabilityLevel: stringOrNull(value.capabilityLevel),
    implementationType: stringOrNull(value.implementationType),
    createdAt: stringOrNull(value.createdAt),
    updatedAt: stringOrNull(value.updatedAt),
    idleTimeoutMs: typeof value.idleTimeoutMs === "number" ? value.idleTimeoutMs : null,
    lastTraceId: stringOrNull(value.lastTraceId),
    turn: typeof value.turn === "number" ? value.turn : null,
    longLivedSession: value.longLivedSession === true,
    codexStdio: value.codexStdio === true,
    writeCapable: value.writeCapable === true,
    secretMaterialStored: value.secretMaterialStored === true
  };
}

function summarizeConversationFacts(value) {
  if (!value || typeof value !== "object") return null;
  return {
    conversationId: stringOrNull(value.conversationId),
    sessionId: stringOrNull(value.sessionId),
    sessionStatus: stringOrNull(value.sessionStatus),
    sessionLifecycleStatus: stringOrNull(value.sessionLifecycleStatus),
    sessionMode: stringOrNull(value.sessionMode),
    capabilityLevel: stringOrNull(value.capabilityLevel),
    runnerKind: stringOrNull(value.runnerKind),
    workspace: stringOrNull(value.workspace),
    sandbox: stringOrNull(value.sandbox),
    turnCount: typeof value.turnCount === "number" ? value.turnCount : null,
    latestTraceId: stringOrNull(value.latestTraceId),
    traceIds: Array.isArray(value.traceIds) ? value.traceIds.map(stringOrNull).filter(Boolean).slice(-6) : [],
    latestSkills: value.latestSkills && typeof value.latestSkills === "object"
      ? {
          status: stringOrNull(value.latestSkills.status),
          count: typeof value.latestSkills.count === "number" ? value.latestSkills.count : null,
          totalCount: typeof value.latestSkills.totalCount === "number" ? value.latestSkills.totalCount : null,
          names: Array.isArray(value.latestSkills.names) ? value.latestSkills.names.map(stringOrNull).filter(Boolean).slice(0, 8) : []
        }
      : null,
    recentToolCalls: Array.isArray(value.recentToolCalls)
      ? value.recentToolCalls.map((tool) => ({
          name: stringOrNull(tool?.name),
          status: stringOrNull(tool?.status)
        })).filter((tool) => tool.name || tool.status).slice(-8)
      : [],
    valuesRedacted: value.valuesRedacted === true,
    secretMaterialStored: value.secretMaterialStored === true
  };
}

function summarizeSessionLifecycle(value) {
  if (!value || typeof value !== "object") return null;
  return {
    status: stringOrNull(value.status),
    rawStatus: stringOrNull(value.rawStatus),
    label: stringOrNull(value.label),
    action: stringOrNull(value.action),
    reusable: value.reusable === true,
    reused: typeof value.reused === "boolean" ? value.reused : null,
    newSession: value.newSession === true,
    busy: value.busy === true,
    requiresNewSession: value.requiresNewSession === true,
    degraded: value.degraded === true,
    fallback: value.fallback === true,
    unsupported: value.unsupported === true,
    reasonCode: stringOrNull(value.reasonCode),
    userMessage: stringOrNull(value.userMessage)
  };
}

function summarizeCodexStdioFeasibility(value) {
  if (!value || typeof value !== "object") return null;
  return {
    status: stringOrNull(value.status),
    canStartLongLivedCodexStdio: value.canStartLongLivedCodexStdio === true,
    currentImplementation: stringOrNull(value.currentImplementation),
    implementationRequired: stringOrNull(value.implementationRequired),
    binaryOnPath: value.binaryOnPath === true,
    binary: value.binary && typeof value.binary === "object"
      ? {
          status: stringOrNull(value.binary.status),
          present: value.binary.present === true,
          command: stringOrNull(value.binary.command),
          version: stringOrNull(value.binary.version),
          versionDetected: value.binary.versionDetected === true
        }
      : null,
    runtimeContract: value.runtimeContract && typeof value.runtimeContract === "object"
      ? {
          status: stringOrNull(value.runtimeContract.status),
          ready: value.runtimeContract.ready === true,
          binaryStatus: stringOrNull(value.runtimeContract.binary?.status),
          stdioProtocolStatus: stringOrNull(value.runtimeContract.stdioProtocol?.status),
          lifecycleStatus: stringOrNull(value.runtimeContract.lifecycleSupervisor?.status),
          workspaceStatus: stringOrNull(value.runtimeContract.workspaceMount?.status),
          tokenBoundaryStatus: stringOrNull(value.runtimeContract.tokenBoundary?.status),
          cancelReapTraceStatus: stringOrNull(value.runtimeContract.cancelReapTraceReadiness?.status)
        }
      : null,
    workspace: stringOrNull(value.workspace),
    sandbox: stringOrNull(value.sandbox),
    ready: value.ready === true,
    sessionLifecycle: value.sessionLifecycle && typeof value.sessionLifecycle === "object"
      ? {
          create: value.sessionLifecycle.create === true,
          reuse: value.sessionLifecycle.reuse === true,
          cancel: value.sessionLifecycle.cancel === true,
          reap: value.sessionLifecycle.reap === true,
          idleTimeout: value.sessionLifecycle.idleTimeout === true,
          traceCapture: value.sessionLifecycle.traceCapture === true,
          idleTimeoutMs: typeof value.sessionLifecycle.idleTimeoutMs === "number" ? value.sessionLifecycle.idleTimeoutMs : null
        }
      : null,
    blockers: Array.isArray(value.blockers)
      ? value.blockers.map((blocker) => ({
          code: stringOrNull(blocker.code),
          sourceIssue: stringOrNull(blocker.sourceIssue)
        }))
      : []
  };
}

function summarizeLongLivedSessionGate(value) {
  if (!value || typeof value !== "object") return null;
  return {
    status: stringOrNull(value.status),
    pass: value.pass === true,
    requiredCapability: stringOrNull(value.requiredCapability),
    currentCapability: stringOrNull(value.currentCapability),
    sessionId: stringOrNull(value.sessionId),
    sessionStatus: stringOrNull(value.sessionStatus),
    runnerKind: stringOrNull(value.runnerKind),
    provider: stringOrNull(value.provider),
    blockers: Array.isArray(value.blockers)
      ? value.blockers.map((blocker) => ({
          code: stringOrNull(blocker.code),
          sourceIssue: stringOrNull(blocker.sourceIssue)
        }))
      : []
  };
}

function summarizeStringList(value) {
  return Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
}

function stringOrNull(value) {
  return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
}

function numericStatus(value) {
  if (typeof value !== "number" || !Number.isInteger(value)) return null;
  return value;
}
