import { Link } from "@remix-run/react"; import type { DiagnosisBlock } from "@internal/dashboard-agent"; import { Badge } from "~/components/primitives/Badge"; import { toSafeUrl } from "~/components/runs/v3/agent/AgentMessageView"; import { useOptionalEnvironment } from "~/hooks/useEnvironment"; import { useOptionalOrganization } from "~/hooks/useOrganizations"; import { useOptionalProject } from "~/hooks/useProject"; import { cn } from "~/utils/cn"; import { v3RunPath } from "~/utils/pathBuilder"; // The "why did this run fail?" failure card — the first block in the dashboard // agent's view catalog. Rendered from a `diagnosis` block the agent emits via // the render_view tool (see internal-packages/dashboard-agent tool-schemas). // Everything here is plain presentation of validated fields; no markup comes // from the model, so there's nothing to sanitize beyond outbound URLs. const CATEGORY_LABELS: Record = { user_code_error: "Code error", configuration: "Configuration", dependency: "Dependency", timeout: "Timeout", out_of_memory: "Out of memory", rate_limit: "Rate limit", external_service: "External service", infrastructure: "Infrastructure", cancellation: "Cancelled", unknown: "Unknown", }; const CONFIDENCE_STYLES: Record = { high: "border-emerald-500/40 text-emerald-400", medium: "border-amber-500/40 text-amber-400", low: "border-border-bright text-text-dimmed", }; const EVIDENCE_LABELS: Record = { error: "Error", failed_span: "Failed span", child_run: "Child run", logs: "Logs", deploy: "Deploy", source: "Source", historical_match: "History", }; // Build a run-page path in the current org/project/env, or null when that route // context is absent (e.g. the storybook page) so the card degrades to plain // text rather than throwing. function useRunPath(runId: string): string | null { const organization = useOptionalOrganization(); const project = useOptionalProject(); const environment = useOptionalEnvironment(); if (!organization || !project || !environment) return null; return v3RunPath(organization, project, environment, { friendlyId: runId }); } // Internal link to a run page, built from the canonical path builder so it stays // correct if the route shape changes. Falls back to plain text off-context. function RunLink({ runId, className }: { runId: string; className?: string }) { const to = useRunPath(runId); if (!to) return {runId}; return ( {runId} ); } // Render an evidence `reference`: a run id links to its run page, an https URL // becomes an external link, everything else (error id, file:line, version) is // shown as monospace text. function EvidenceReference({ reference }: { reference: string }) { if (/^run_[a-z0-9]+$/i.test(reference)) { return ; } const safeUrl = toSafeUrl(reference); if (safeUrl) { return ( {reference} ); } return {reference}; } function DiagnosisActions({ actions }: { actions: NonNullable }) { const buttonClass = "inline-flex items-center rounded border border-border-bright bg-background-bright px-2.5 py-1 text-xs text-text-bright transition-colors hover:border-border-brightest hover:bg-background-hover"; return (
{actions.map((action, i) => { if (action.kind === "view_run" && /^run_[a-z0-9]+$/i.test(action.target)) { return ( ); } if (action.kind === "docs") { const safeUrl = toSafeUrl(action.target); if (!safeUrl) return null; return ( {action.label} ); } return null; })}
); } function RunActionButton({ runId, label, className, }: { runId: string; label: string; className: string; }) { const to = useRunPath(runId); if (!to) return {label}; return ( {label} ); } export function RunDiagnosisCard({ block }: { block: DiagnosisBlock }) { const evidence = block.evidence ?? []; const nextSteps = block.nextSteps ?? []; const actions = block.actions ?? []; return (
Run diagnosis {CATEGORY_LABELS[block.category] ?? block.category} {block.confidence} confidence {block.runId ? : null}

{block.summary}

{block.likelyCause}

{evidence.length > 0 ? (
    {evidence.map((item, i) => (
  • {EVIDENCE_LABELS[item.type] ?? item.type} {item.detail} {item.reference ? ( ) : null}
  • ))}
) : null} {block.impact ? (

{block.impact}

) : null} {nextSteps.length > 0 ? (
    {nextSteps.map((step, i) => (
  1. {step}
  2. ))}
) : null} {actions.length > 0 ? : null}
); } function Section({ title, children }: { title: string; children: React.ReactNode }) { return (

{title}

{children}
); }