import { ArrowUpRightIcon, ChevronRightIcon } from "lucide-react"; import { useContext, useState, type ReactNode } from "react"; import { cn } from "../lib/cn"; import { useForcedDefaultOpen } from "./disclosure-context"; import type { AgentTitleParts } from "./platform-activity-presentation"; import { ActivityDisclosure, CompactActivityContext } from "./shared"; import { ShimmerText } from "./shimmer-text"; import { useOpenGeniText } from "../messages/runtime"; /* ---------------------------------------------------------------------------- Agent row One row for everything that crosses between agents: spawning one, messaging one, and every update one sends back. It names the agent, previews what was said, opens the full text in place, and deep-links to the agent's session. It reuses the rail's row geometry (chevron, icon, title) so it reads as a sibling of tool rows, and spends color only on failure. -------------------------------------------------------------------------- */ export type AgentRowProps = { icon: ReactNode; title: AgentTitleParts; /** Quiet secondary text shown while collapsed. */ preview?: string | null | undefined; /** Two lines suit inbound messages; spawns and messages use one. */ previewLines?: 1 | 2 | undefined; /** Short trailing note after the title, e.g. "+2 earlier". */ meta?: string | null | undefined; /** The agent session the row deep-links to. */ sessionId?: string | null | undefined; onOpenSession?: ((sessionId: string) => void) | undefined; running?: boolean | undefined; failed?: boolean | undefined; cancelled?: boolean | undefined; /** Expanded body; the row is static when absent. */ children?: ReactNode | undefined; }; export function AgentRow({ icon, title, preview, previewLines = 1, meta, sessionId, onOpenSession, running, failed, cancelled, children, }: AgentRowProps) { const t = useOpenGeniText(); const compact = useContext(CompactActivityContext); const forcedDefaultOpen = useForcedDefaultOpen(); const [open, setOpen] = useState(forcedDefaultOpen ?? false); if (compact) { return ( ); } const hasBody = children != null; const canOpen = Boolean(sessionId && onOpenSession); const openLabel = title.name ? t("timeline.agentRow.openName", { name: title.name }) : t("timeline.agentRow.openAgentSession"); const status = failed ? "failed" : cancelled ? "cancelled" : running ? "running" : undefined; const inner = ( <> {hasBody ? ( ) : ( )} {icon} {title.name ? ( <> {title.before ? {title.before} : null} {title.name} {title.after ? {title.after} : null} ) : ( {title.text} )} {meta ? ( {meta} ) : null} {preview && !open ? ( {preview} ) : null} {failed ? ( {t("timeline.agentRow.failed")} ) : cancelled ? ( {t("timeline.agentRow.interrupted")} ) : null} {canOpen && sessionId && onOpenSession ? ( ) : null} ); const rowClass = cn( "group/agent flex w-full min-w-0 items-start gap-2 rounded-og-sm px-1.5 py-1.5 text-left", "pointer-coarse:min-h-11 pointer-coarse:py-2.5", ); if (!hasBody) { return (
{inner}
); } // A div with button semantics (like ActivityDisclosure) so the open-session // button can live inside the row without nesting native buttons. return (
setOpen((previous) => !previous)} onKeyDown={(event) => { if (event.key === "Enter" || event.key === " ") { event.preventDefault(); setOpen((previous) => !previous); } }} className={cn( rowClass, "cursor-pointer outline-hidden transition-colors duration-150 hover:bg-og-surface-1", "focus-visible:ring-2 focus-visible:ring-og-accent", )} > {inner}
{open ? (
{children}
) : null}
); } /** Plain multi-line text for an expanded agent row. */ export function AgentRowText({ children }: { children: string }) { return

{children}

; } /** A labelled section inside an expanded agent row (Brief, Report, Evidence). */ export function AgentRowSection({ label, children, muted, }: { label: string; children: string; muted?: boolean | undefined; }) { return (

{label}

{children}

); }