{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "agent-handoff",
  "title": "Agent Handoff",
  "description": "An agent-to-agent handoff marker — from/to avatars, a track that marches while the transfer is in flight, the reason control moved, chips for the context passed along, and who holds the run; compact divider and detailed card variants.",
  "dependencies": [
    "lucide-react"
  ],
  "registryDependencies": [
    "utils"
  ],
  "files": [
    {
      "path": "src/registry/ui/agent-handoff.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { ArrowRight, Undo2 } from \"lucide-react\"\nimport { cn } from \"@/lib/utils\"\n\n/* -------------------------------------------------------------------------- *\n * Keyframes\n *\n * Shipped with the component through a React 19 hoisted <style> — no Tailwind\n * config edit, and every marker in a long transcript dedupes on the same href.\n *\n * `ah-flow` marches the dashed track by exactly one dash period, so the loop is\n * seamless at any track width; `ah-nudge` leans the arrowhead a couple of pixels\n * toward the receiver; `ah-land` is the arrival ring. A CSS animation plays when\n * its element is INSERTED and can never be replayed by a re-render, which is\n * what makes the arrival pulse a one-shot lock rather than something a parent\n * re-render can retrigger.\n * -------------------------------------------------------------------------- */\nconst KEYFRAMES =\n  \"@keyframes ah-flow{from{background-position:0 0}to{background-position:var(--ah-dash) 0}}\" +\n  \"@keyframes ah-nudge{0%,100%{transform:none}50%{transform:translateX(0.1875rem)}}\" +\n  \"@keyframes ah-land{from{opacity:0.9;transform:scale(0.72)}to{opacity:0;transform:scale(1.75)}}\"\n\nconst STYLES = (\n  <style href=\"zyeon-agent-handoff\" precedence=\"medium\">\n    {KEYFRAMES}\n  </style>\n)\n\n/* -------------------------------------------------------------------------- *\n * Model\n *\n * A handoff is one edge of a run: the sender, the receiver, and which of the two\n * holds the run right now. `status` is the only thing that moves — everything\n * else in this file is a lookup keyed by it.\n * -------------------------------------------------------------------------- */\n\nexport type AgentHandoffStatus = \"pending\" | \"complete\" | \"failed\"\nexport type AgentHandoffVariant = \"divider\" | \"card\"\n/** Index into the host palette's `--chart-1..5`. */\nexport type AgentHandoffTone = 1 | 2 | 3 | 4 | 5\n\nexport interface AgentHandoffParty {\n  /** Stable id. Also the seed for the derived tone, so the same agent keeps its accent. */\n  id?: string\n  name: string\n  /** \"Planner\", \"gpt-4o-mini\", \"Retrieval\" — rendered under the name in the card variant only. */\n  role?: string\n  /** Remote avatar. A dead or slow URL degrades to the initials, never to a broken image. */\n  avatar?: string\n  /** Override the derived initials; at most two glyphs are rendered. */\n  initials?: string\n  /** Pin the accent instead of deriving it from `id ?? name`. */\n  tone?: AgentHandoffTone\n}\n\nexport interface AgentHandoffContextItem {\n  /** What was carried over: \"thread\", \"tool results\", \"budget\". */\n  label: string\n  /** Optional measurement rendered in a monospace slot: \"12 msgs\", \"3.2k tokens\". */\n  value?: string\n}\n\nexport interface AgentHandoffLabels {\n  pending: string\n  complete: string\n  failed: string\n  /** Screen-reader connective for a landed handoff. */\n  handedTo: string\n  /** Screen-reader connective while the receiver has not taken over yet. */\n  handingTo: string\n  /** Screen-reader connective for a handoff that never landed. */\n  failedTo: string\n  /** Appended to the owning party's name for screen readers. */\n  owns: string\n  reason: string\n  /** Accessible name of the context chip list. */\n  context: string\n  more: (count: number) => string\n  less: string\n  /** Where control sits after a failed handoff. */\n  returned: (name: string) => string\n}\n\nconst DEFAULT_LABELS: AgentHandoffLabels = {\n  pending: \"Handing off\",\n  complete: \"Handed off\",\n  failed: \"Handoff failed\",\n  handedTo: \"handed off to\",\n  handingTo: \"is handing off to\",\n  failedTo: \"failed to hand off to\",\n  owns: \"holds the run\",\n  reason: \"Reason\",\n  context: \"Context passed\",\n  more: count => `+${count} more`,\n  less: \"Show less\",\n  returned: name => `${name} keeps the run`,\n}\n\nconst EMPTY_CONTEXT: AgentHandoffContextItem[] = []\n\n/* -------------------------------------------------------------------------- *\n * Derivations\n * -------------------------------------------------------------------------- */\n\n/** First glyph of the first and last word, or the first two glyphs of a single word. */\nfunction initialsOf(name: string): string {\n  const words = name.trim().split(/\\s+/).filter(Boolean)\n  if (words.length === 0) return \"?\"\n  // Array.from, not slice: a name can start with an emoji or an astral glyph and\n  // cutting a surrogate pair in half prints a replacement box.\n  const glyphs =\n    words.length === 1\n      ? Array.from(words[0]).slice(0, 2)\n      : [Array.from(words[0])[0], Array.from(words[words.length - 1])[0]]\n  return glyphs.join(\"\").toUpperCase()\n}\n\n/**\n * FNV-1a over the party's id. Deterministic and integer-only, so the server and\n * the client agree on the colour and the same agent keeps its accent across\n * every handoff in a log.\n */\nfunction toneIndex(seed: string): AgentHandoffTone {\n  let hash = 2166136261\n  for (let i = 0; i < seed.length; i += 1) {\n    hash ^= seed.charCodeAt(i)\n    hash = Math.imul(hash, 16777619)\n  }\n  return (((hash >>> 0) % 5) + 1) as AgentHandoffTone\n}\n\nfunction toneOf(party: AgentHandoffParty): string {\n  return `var(--chart-${party.tone ?? toneIndex(party.id ?? party.name)})`\n}\n\nconst subscribeNoop = () => () => {}\n\n/** false on the server and on the hydrating frame, true from the first commit on. */\nfunction useMounted(): boolean {\n  return React.useSyncExternalStore(\n    subscribeNoop,\n    () => true,\n    () => false,\n  )\n}\n\nconst DEFAULT_TIME_FORMAT = new Intl.DateTimeFormat(undefined, { hour: \"numeric\", minute: \"2-digit\" })\n\ninterface Stamp {\n  date?: Date\n  text?: string\n}\n\n/**\n * A string is rendered verbatim — the caller already formatted it (\"14:32\",\n * \"3 min ago\") — and a Date/number is formatted here. Anything unparseable\n * collapses to `null`, so the meta line never keeps a dangling separator next to\n * an empty slot.\n */\nfunction normalizeStamp(at: Date | number | string | undefined): Stamp | null {\n  if (at === undefined) return null\n  if (typeof at === \"string\") return at.trim() ? { text: at } : null\n  const date = at instanceof Date ? at : new Date(at)\n  return Number.isNaN(date.getTime()) ? null : { date }\n}\n\nfunction HandoffTime({\n  className,\n  format,\n  stamp,\n}: {\n  className?: string\n  format?: (date: Date) => string\n  stamp: Stamp\n}) {\n  const mounted = useMounted()\n  const base = cn(\"shrink-0 tabular-nums text-muted-foreground\", className)\n\n  if (!stamp.date) return <span className={base}>{stamp.text}</span>\n\n  // The machine-readable value is UTC, so it is identical on both sides of\n  // hydration; the human-readable one is the READER's local time, which the\n  // server cannot know — it lands one commit later instead of tearing hydration.\n  return (\n    <time className={base} dateTime={stamp.date.toISOString()}>\n      {mounted ? (format ? format(stamp.date) : DEFAULT_TIME_FORMAT.format(stamp.date)) : null}\n    </time>\n  )\n}\n\n/* -------------------------------------------------------------------------- *\n * Party\n * -------------------------------------------------------------------------- */\n\ninterface PartyViewProps {\n  party: AgentHandoffParty\n  /** Holds the run right now — gets the control ring and the sr-only note. */\n  owner: boolean\n  /** Ownership has not reached this party (in flight, or it never arrived). */\n  provisional: boolean\n  /** Bumped once when this party takes the run over; 0 means \"no arrival yet\". */\n  landKey: number\n  size: \"sm\" | \"md\"\n  ownsLabel: string\n  showRole: boolean\n  className?: string\n}\n\nfunction PartyView({ className, landKey, ownsLabel, owner, party, provisional, showRole, size }: PartyViewProps) {\n  const [failedSrc, setFailedSrc] = React.useState<string | null>(null)\n  const tone = toneOf(party)\n  const src = party.avatar\n  const showImage = Boolean(src) && failedSrc !== src\n  const small = size === \"sm\"\n  const ringInset = small ? \"-inset-[3px]\" : \"-inset-1\"\n\n  return (\n    <span className={cn(\"flex min-w-0 items-center gap-2\", className)}>\n      <span className={cn(\"relative shrink-0\", small ? \"size-6\" : \"size-9\")}>\n        <span\n          aria-hidden=\"true\"\n          className={cn(\n            \"relative flex size-full items-center justify-center overflow-hidden rounded-full border font-semibold text-foreground\",\n            small ? \"text-[0.5625rem]\" : \"text-xs\",\n            // Dashed + dimmed says \"this agent does not own the run yet\" without\n            // leaning on colour alone.\n            provisional && \"border-dashed opacity-70\",\n          )}\n          style={{\n            backgroundColor: `color-mix(in oklab, ${tone} 14%, var(--card))`,\n            borderColor: `color-mix(in oklab, ${tone} 45%, transparent)`,\n          }}\n        >\n          <span>{initialsOf(party.initials ?? party.name)}</span>\n          {src && showImage ? (\n            // The remote image sits ON TOP of the initials, so a slow or dead URL\n            // degrades to initials. The ref re-checks `complete && naturalWidth === 0`\n            // because a cached or already-failed image can finish before React ever\n            // attaches onError — which is every hydration of a prerendered page.\n            // eslint-disable-next-line @next/next/no-img-element -- consumer-supplied remote URL, not a local optimizable asset\n            <img\n              alt=\"\"\n              className=\"absolute inset-0 size-full object-cover\"\n              loading=\"lazy\"\n              onError={() => setFailedSrc(src)}\n              ref={node => {\n                if (node && node.complete && node.naturalWidth === 0) setFailedSrc(src)\n              }}\n              src={src}\n            />\n          ) : null}\n        </span>\n\n        {owner ? (\n          <span\n            aria-hidden=\"true\"\n            className={cn(\"pointer-events-none absolute rounded-full border\", ringInset)}\n            style={{ borderColor: `color-mix(in oklab, ${tone} 55%, transparent)` }}\n          />\n        ) : null}\n\n        {landKey > 0 ? (\n          // Purely decorative: hidden (not merely paused) under reduced motion, and\n          // keyed so a bump inserts a NEW element — the only way to make a CSS\n          // animation fire exactly once per transition.\n          <span\n            aria-hidden=\"true\"\n            className={cn(\n              \"pointer-events-none absolute rounded-full border-2 [animation:ah-land_0.65s_ease-out_forwards] motion-reduce:hidden\",\n              ringInset,\n            )}\n            key={landKey}\n            style={{ borderColor: tone }}\n          />\n        ) : null}\n      </span>\n\n      <span className=\"flex min-w-0 flex-col leading-tight\">\n        <span className={cn(\"truncate font-medium\", small ? \"text-xs\" : \"text-sm\")} title={party.name}>\n          {party.name}\n          {owner ? <span className=\"sr-only\"> ({ownsLabel})</span> : null}\n        </span>\n        {showRole && party.role ? (\n          <span className=\"truncate text-xs text-muted-foreground\" title={party.role}>\n            {party.role}\n          </span>\n        ) : null}\n      </span>\n    </span>\n  )\n}\n\n/* -------------------------------------------------------------------------- *\n * Track\n *\n * The one piece of motion: a dashed line marching from sender to receiver while\n * the handoff is in flight, a solid two-tone gradient once it lands, a static\n * dashed line in the destructive tone when it never did.\n * -------------------------------------------------------------------------- */\n\nconst DASH = \"0.5rem\"\nconst DASH_ON = \"0.1875rem\"\n\nfunction HandoffTrack({\n  className,\n  fromTone,\n  status,\n  toTone,\n}: {\n  className?: string\n  fromTone: string\n  status: AgentHandoffStatus\n  toTone: string\n}) {\n  const failed = status === \"failed\"\n  const pending = status === \"pending\"\n  const accent = failed ? \"var(--destructive)\" : toTone\n  const Icon = failed ? Undo2 : ArrowRight\n\n  const line: React.CSSProperties =\n    pending || failed\n      ? ({\n          \"--ah-dash\": DASH,\n          backgroundImage: `repeating-linear-gradient(90deg, ${accent} 0 ${DASH_ON}, transparent ${DASH_ON} ${DASH})`,\n          // One dash per tile, so shifting the position by exactly one tile loops\n          // with no visible seam whatever the track ends up measuring.\n          backgroundSize: `${DASH} 100%`,\n        } as React.CSSProperties)\n      : { backgroundImage: `linear-gradient(90deg, ${fromTone}, ${toTone})` }\n\n  return (\n    <span aria-hidden=\"true\" className={cn(\"flex min-w-0 items-center gap-1.5\", className)}>\n      <span\n        className={cn(\n          \"h-px min-w-4 flex-1 rounded-full\",\n          pending && \"[animation:ah-flow_0.55s_linear_infinite] motion-reduce:[animation:none]\",\n        )}\n        style={line}\n      />\n      <Icon\n        className={cn(\n          \"size-3.5 shrink-0\",\n          pending && \"[animation:ah-nudge_1.2s_ease-in-out_infinite] motion-reduce:[animation:none]\",\n        )}\n        style={{ color: accent }}\n      />\n    </span>\n  )\n}\n\n/* -------------------------------------------------------------------------- *\n * Context chips\n * -------------------------------------------------------------------------- */\n\nconst CHIP =\n  \"flex min-w-0 items-center gap-1 rounded-full border bg-muted/50 px-2 py-0.5 text-[0.6875rem] text-muted-foreground\"\n\nfunction ContextChips({\n  className,\n  items,\n  label,\n  less,\n  limit,\n  more,\n}: {\n  className?: string\n  items: AgentHandoffContextItem[]\n  label: string\n  less: string\n  limit: number\n  more: (count: number) => string\n}) {\n  const [expanded, setExpanded] = React.useState(false)\n\n  // A recycled row (a virtualised run log reusing this instance for a different\n  // handoff) must not inherit the previous one's expanded state — the chips\n  // underneath belong to another run. The manifest's CONTENT is the identity,\n  // not its length: two handoffs in the same log usually carry the same kinds of\n  // context and differ only in the values. Render-phase reset, so the first\n  // painted frame is already collapsed.\n  const signature = items.map(item => `${item.label}:${item.value ?? \"\"}`).join(\"|\")\n  const [prevSignature, setPrevSignature] = React.useState(signature)\n  if (prevSignature !== signature) {\n    setPrevSignature(signature)\n    setExpanded(false)\n  }\n\n  const hidden = limit > 0 ? Math.max(0, items.length - limit) : 0\n  const shown = expanded || hidden === 0 ? items : items.slice(0, limit)\n\n  return (\n    <ul aria-label={label} className={cn(\"flex min-w-0 flex-wrap items-center gap-1.5\", className)}>\n      {shown.map((item, index) => (\n        <li className={CHIP} key={`${item.label}-${index}`}>\n          <span className=\"truncate\">{item.label}</span>\n          {item.value ? <span className=\"shrink-0 font-mono text-foreground\">{item.value}</span> : null}\n        </li>\n      ))}\n      {hidden > 0 ? (\n        <li>\n          <button\n            aria-expanded={expanded}\n            className={cn(\n              CHIP,\n              \"cursor-pointer border-dashed transition-colors hover:bg-muted hover:text-foreground\",\n              \"focus-visible:ring-2 focus-visible:ring-ring focus-visible:outline-none motion-reduce:transition-none\",\n            )}\n            onClick={() => setExpanded(value => !value)}\n            type=\"button\"\n          >\n            {expanded ? less : more(hidden)}\n          </button>\n        </li>\n      ) : null}\n    </ul>\n  )\n}\n\n/* -------------------------------------------------------------------------- *\n * Component\n * -------------------------------------------------------------------------- */\n\nexport interface AgentHandoffProps extends Omit<React.HTMLAttributes<HTMLDivElement>, \"children\"> {\n  /** The agent giving the run away. */\n  from: AgentHandoffParty\n  /** The agent being asked to take it. */\n  to: AgentHandoffParty\n  /**\n   * `pending` = in flight (the sender still owns it), `complete` = the receiver\n   * took over, `failed` = it never landed and the sender keeps the run.\n   * @default \"complete\"\n   */\n  status?: AgentHandoffStatus\n  /** `divider` slots between messages; `card` is the detailed block. @default \"divider\" */\n  variant?: AgentHandoffVariant\n  /** Why control moved — one clamped line in the divider, a paragraph in the card. */\n  reason?: string\n  /** What travelled with the run: thread window, tool results, budget… */\n  context?: AgentHandoffContextItem[]\n  /** Chips shown before the \"+N more\" expander; 0 disables the cap. @default 3 */\n  maxContext?: number\n  /** A Date/number is formatted locally after mount; a string is printed verbatim. */\n  at?: Date | number | string\n  /** Override the default hour:minute formatting of a Date/number `at`. */\n  formatTime?: (date: Date) => string\n  /** Copy overrides; `more`/`returned` are functions so counts and names interpolate. */\n  labels?: Partial<AgentHandoffLabels>\n  /**\n   * Own a polite live region announcing the transfer. Off by default: the marker's\n   * text is already in the reading order, and one region per row in a long log\n   * talks over itself. Turn it on for a single live run. @default false\n   */\n  announce?: boolean\n  /** Card-only footer slot (Retry / View trace…). The consumer wires the handlers. */\n  actions?: React.ReactNode\n}\n\nexport const AgentHandoff = React.forwardRef<HTMLDivElement, AgentHandoffProps>(function AgentHandoff(\n  {\n    actions,\n    announce = false,\n    at,\n    className,\n    context,\n    formatTime,\n    from,\n    labels,\n    maxContext = 3,\n    reason,\n    status = \"complete\",\n    to,\n    variant = \"divider\",\n    ...props\n  },\n  ref,\n) {\n  const text = { ...DEFAULT_LABELS, ...labels }\n\n  // One-shot arrival lock. Bumping the key INSERTS a fresh ring, and a CSS\n  // animation only plays on insertion — so the pulse fires on the pending →\n  // complete edge and never again, no matter how often the parent re-renders,\n  // and never on mount (a handoff that is already complete has nothing to\n  // celebrate). Render-phase adjustment, not an effect: an effect would leave the\n  // first painted frame one status behind.\n  const [prevStatus, setPrevStatus] = React.useState(status)\n  const [landKey, setLandKey] = React.useState(0)\n  if (prevStatus !== status) {\n    setPrevStatus(status)\n    if (status === \"complete\" && prevStatus === \"pending\") setLandKey(key => key + 1)\n  }\n\n  const failed = status === \"failed\"\n  const pending = status === \"pending\"\n\n  // The control token: exactly one side owns the run at any moment, and the owner\n  // is DERIVED from status — a transfer that has not landed (or that failed)\n  // leaves the sender in charge, which is the fact a reader actually needs.\n  const receiverOwns = status === \"complete\"\n\n  const fromTone = toneOf(from)\n  const toTone = toneOf(to)\n  const accent = failed ? \"var(--destructive)\" : toTone\n  const small = variant === \"divider\"\n\n  const statusLabel = pending ? text.pending : failed ? text.failed : text.complete\n  const connective = pending ? text.handingTo : failed ? text.failedTo : text.handedTo\n  const sentence = [\n    `${from.name} ${connective} ${to.name}`,\n    reason ? `. ${text.reason}: ${reason}` : \"\",\n    failed ? `. ${text.returned(from.name)}` : \"\",\n  ].join(\"\")\n\n  const stamp = normalizeStamp(at)\n  const items = context ?? EMPTY_CONTEXT\n\n  const live = announce ? (\n    // Permanently mounted: several screen readers skip a live region inserted in\n    // the same frame as its text. The sentence changes only when the transfer\n    // does, so an unrelated re-render announces nothing.\n    <span aria-atomic=\"true\" aria-live=\"polite\" className=\"sr-only\" role=\"status\">\n      {sentence}\n    </span>\n  ) : null\n\n  const chips =\n    items.length > 0 ? (\n      <ContextChips\n        className={small ? \"justify-center\" : undefined}\n        items={items}\n        label={text.context}\n        less={text.less}\n        limit={maxContext}\n        more={text.more}\n      />\n    ) : null\n\n  const parties = (\n    <>\n      <PartyView\n        // One arbitrary `flex` shorthand, never `flex-1 basis-36`: those are two\n        // utilities writing the same longhand and the winner is decided by the\n        // stylesheet order, not by the order in this string.\n        className={small ? undefined : \"flex-[1_1_9rem]\"}\n        landKey={0}\n        ownsLabel={text.owns}\n        owner={!receiverOwns}\n        party={from}\n        provisional={false}\n        showRole={!small}\n        size={small ? \"sm\" : \"md\"}\n      />\n      {/* The arrow is decorative; this is the word a screen reader reads between\n          the two names, so the row comes out as one sentence. */}\n      <span className=\"sr-only\">{connective}</span>\n      <HandoffTrack\n        className={small ? \"w-14 shrink-0\" : \"min-w-16 flex-[1_1_3rem]\"}\n        fromTone={fromTone}\n        status={status}\n        toTone={toTone}\n      />\n      <PartyView\n        className={small ? undefined : \"flex-[1_1_9rem]\"}\n        landKey={landKey}\n        ownsLabel={text.owns}\n        owner={receiverOwns}\n        party={to}\n        provisional={!receiverOwns}\n        showRole={!small}\n        size={small ? \"sm\" : \"md\"}\n      />\n    </>\n  )\n\n  if (variant === \"card\") {\n    return (\n      <div\n        className={cn(\n          \"flex w-full min-w-0 flex-col gap-3 rounded-lg border bg-card p-4 text-card-foreground\",\n          failed && \"border-destructive/40\",\n          className,\n        )}\n        data-status={status}\n        data-variant=\"card\"\n        ref={ref}\n        {...props}\n      >\n        {STYLES}\n        {live}\n\n        <div className=\"flex min-w-0 items-center justify-between gap-3\">\n          <span className=\"flex min-w-0 items-center gap-1.5 text-xs font-medium\">\n            <span\n              aria-hidden=\"true\"\n              className={cn(\"size-1.5 shrink-0 rounded-full\", pending && \"animate-pulse motion-reduce:animate-none\")}\n              style={{ backgroundColor: accent }}\n            />\n            <span className={cn(\"truncate\", failed ? \"text-destructive\" : \"text-foreground\")}>{statusLabel}</span>\n          </span>\n          {stamp ? <HandoffTime className=\"text-xs\" format={formatTime} stamp={stamp} /> : null}\n        </div>\n\n        <div className=\"flex min-w-0 flex-wrap items-center gap-3\">{parties}</div>\n\n        {reason ? (\n          <p className=\"min-w-0 text-sm text-muted-foreground\">\n            <span className=\"font-medium text-foreground\">{text.reason}: </span>\n            {reason}\n          </p>\n        ) : null}\n\n        {chips}\n\n        {failed ? <p className=\"text-xs text-muted-foreground\">{text.returned(from.name)}</p> : null}\n        {actions ? <div className=\"flex flex-wrap items-center gap-2\">{actions}</div> : null}\n      </div>\n    )\n  }\n\n  const meta: React.ReactNode[] = []\n  if (status !== \"complete\") {\n    meta.push(\n      <span className={cn(\"shrink-0 font-medium\", failed ? \"text-destructive\" : \"text-foreground\")} key=\"status\">\n        {statusLabel}\n      </span>,\n    )\n  }\n  if (reason) {\n    meta.push(\n      // line-clamp, not truncate: a reason is a sentence, and clamping it to one\n      // line keeps the divider one row tall at any width.\n      <span className=\"line-clamp-1 min-w-0\" key=\"reason\" title={reason}>\n        {reason}\n      </span>,\n    )\n  }\n  if (stamp) meta.push(<HandoffTime format={formatTime} key=\"time\" stamp={stamp} />)\n\n  return (\n    <div\n      className={cn(\"flex w-full min-w-0 flex-col items-center gap-1.5 py-2\", className)}\n      data-status={status}\n      data-variant=\"divider\"\n      ref={ref}\n      {...props}\n    >\n      {STYLES}\n      {live}\n\n      <div className=\"flex w-full min-w-0 items-center gap-3\">\n        <span aria-hidden=\"true\" className=\"h-px min-w-3 flex-1 bg-border\" />\n        <div className=\"flex min-w-0 shrink items-center gap-2\">{parties}</div>\n        <span aria-hidden=\"true\" className=\"h-px min-w-3 flex-1 bg-border\" />\n      </div>\n\n      {meta.length > 0 ? (\n        <div className=\"flex max-w-full min-w-0 flex-wrap items-center justify-center gap-x-2 gap-y-0.5 text-xs text-muted-foreground\">\n          {meta.map((node, index) => (\n            <React.Fragment key={index}>\n              {index > 0 ? <span aria-hidden=\"true\">·</span> : null}\n              {node}\n            </React.Fragment>\n          ))}\n        </div>\n      ) : null}\n\n      {chips ? <div className=\"flex w-full min-w-0 justify-center\">{chips}</div> : null}\n    </div>\n  )\n})\n\nAgentHandoff.displayName = \"AgentHandoff\"\n\nexport default AgentHandoff\n",
      "type": "registry:ui"
    }
  ],
  "type": "registry:ui"
}