"use client"; // MissionArtifacts — the mission's captured FILES, and a reader for them. // // Distinct from MissionOutputReader, which shows agent turn output: that is the // agent's ACCOUNT of the work, this is the work. For a repo-less research // mission these artifacts are the only durable result — see // `cm-api/src/mission_outputs.rs`. // // Bodies are fetched on demand. The mission detail carries paths only, and a // research phase can leave dozens of documents. import { useCallback, useState } from "react"; import { FileText } from "lucide-react"; import { getArtifactContent, type ArtifactContent, type MissionDetail, } from "@/lib/api/missions"; import { MarkdownBlock } from "./MarkdownBlock"; const mono = "ui-monospace, SFMono-Regular, SF Mono, Menlo, Monaco, Consolas, monospace"; export function MissionArtifacts({ mission, secondaryBtn, }: { mission: MissionDetail; secondaryBtn: React.CSSProperties; }) { const missionId = mission.id; const artifacts = mission.artifacts; const [openId, setOpenId] = useState(null); const [text, setText] = useState(null); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); /** * Open one artifact, fetching its text. Clicking the open one closes it. * * Errors surface in place rather than being swallowed: a file the server * refuses to read (outside `_outputs`, or reaped with its mission) has to say * so, or the panel sits empty and reads as a slow load that never finishes. */ const open = useCallback( async (artifactId: string) => { if (openId === artifactId) { setOpenId(null); return; } setOpenId(artifactId); setText(null); setError(null); setLoading(true); try { setText(await getArtifactContent(missionId, artifactId)); } catch (e) { setError(e instanceof Error ? e.message : "could not read this artifact"); } finally { setLoading(false); } }, [missionId, openId], ); if (artifacts.length === 0) { return (
no artifacts yet — phases produce them as they run
); } return (
{artifacts.map((a) => { const isOpen = openId === a.id; return (
{a.title ?? a.path.split("/").pop() ?? a.path}
{a.kind} · {a.path}
{isOpen && (
{loading ? (
loading…
) : error ? (
{error}
) : text?.truncated ? (
too large to display inline ({text.bytes.toLocaleString()} bytes)
) : text ? ( ) : null}
)}
); })}
); }