import { useId, useState } from "react"; import { cn } from "@/lib/utils"; import { ChevronDown, CircleDot, OctagonX, Square, Flag, KeyRound } from "lucide-react"; import { useStreamlinedTaskChatPresentation } from "./presentation-mode"; import type { TaskChatMarkerItem } from "./task-chat-model"; import { Button } from "@/components/ui/button"; import { Link } from "@/lib/router"; import { timeAgo } from "@/lib/timeAgo"; const VARIANT_ICON = { session_start: CircleDot, interrupted: OctagonX, turn_boundary: Flag, } as const; /** * Lifecycle divider — renders a state boundary (session start, interruption, * turn boundary) as a recessed rule, never a bubble. Interruptions use a dashed * destructive rule to read as a distinct state. */ export function TaskChatMarker({ item, onTryAgain, tryAgainPending = false, }: { item: TaskChatMarkerItem; onTryAgain?: () => Promise | void; tryAgainPending?: boolean; }) { const [open, setOpen] = useState(false); const detailsId = useId(); const streamlined = useStreamlinedTaskChatPresentation(); const Icon = item.tone === "neutral" ? Square : VARIANT_ICON[item.variant]; const interrupted = item.variant === "interrupted" && item.tone !== "neutral"; const relative = item.createdAtIso ? timeAgo(item.createdAtIso) : undefined; const handleTryAgain = () => { void Promise.resolve() .then(() => onTryAgain?.()) // The parent mutation owns visible error feedback. .catch(() => undefined); }; if (item.credentialAccess) { const { agentName, credentialName, deniedUser, settingsHref } = item.credentialAccess; return (

{agentName} couldn’t start

{agentName} is configured to use {credentialName || "an AI credential"}, but {deniedUser === "you" ? "you don’t" : `${deniedUser} doesn’t`} have access to that credential.

Choose an AI connection {deniedUser === "you" ? "you can" : `${deniedUser} can`} use, or ask an administrator to update {agentName}’s connection. Then try again.

{item.runHref && }
); } if (item.collapsible) { return (
{onTryAgain && !open ? ( ) : null}
{open ? (
{item.detail ? (
{item.detail}
) : null} {item.runHref || item.planHref || onTryAgain ? (
{item.planHref ? ( ) : null} {item.runHref ? ( ) : null} {onTryAgain ? ( ) : null}
) : null}
) : null}
); } return (
{item.label} {item.detail ? ( · {item.detail} ) : null} {onTryAgain ? ( ) : null}
); }