{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "agent-04",
  "title": "Agent handoff",
  "description": "A relay of agents where the task travels down the rail from one agent tile to the next with its note and context, lost items stay struck through and the next station can be dragged to another agent.",
  "dependencies": [
    "motion"
  ],
  "registryDependencies": [
    "https://ui.ndekocode.dev/r/agent-run.json",
    "https://ui.ndekocode.dev/r/button.json",
    "https://ui.ndekocode.dev/r/chat-motion.json",
    "https://ui.ndekocode.dev/r/chip.json",
    "https://ui.ndekocode.dev/r/demonstration.json",
    "https://ui.ndekocode.dev/r/dropdown-menu.json",
    "https://ui.ndekocode.dev/r/icons.json",
    "https://ui.ndekocode.dev/r/live-duration.json",
    "https://ui.ndekocode.dev/r/live-region.json",
    "https://ui.ndekocode.dev/r/mesh-tile.json",
    "https://ui.ndekocode.dev/r/motion-presets.json",
    "https://ui.ndekocode.dev/r/motion-word-roll.json",
    "https://ui.ndekocode.dev/r/replay-button.json",
    "https://ui.ndekocode.dev/r/run-session.json",
    "https://ui.ndekocode.dev/r/theme.json",
    "https://ui.ndekocode.dev/r/use-kept-height.json",
    "https://ui.ndekocode.dev/r/use-prefers-reduced-motion.json",
    "https://ui.ndekocode.dev/r/use-session.json",
    "https://ui.ndekocode.dev/r/utils.json",
    "https://ui.ndekocode.dev/r/words.json"
  ],
  "files": [
    {
      "path": "registry/ndeko/components/agent/agent-04.tsx",
      "content": "'use client';\n\nimport {\n  AnimatePresence,\n  motion,\n  useDragControls,\n  type PanInfo,\n} from 'motion/react';\nimport { useState } from 'react';\n\nimport { WordRoll } from '@/components/core/word-roll';\nimport { useKeptHeight } from '@/registry/ndeko/hooks/use-kept-height';\nimport { usePrefersReducedMotion } from '@/registry/ndeko/hooks/use-prefers-reduced-motion';\nimport {\n  useSession,\n  type SessionStep,\n} from '@/registry/ndeko/hooks/use-session';\nimport {\n  emptyRun,\n  seriesColorOf,\n  type RunAgent,\n  type RunEvent,\n  type RunSnapshot,\n  type ToolCall,\n} from '@/registry/ndeko/lib/agent-run';\nimport {\n  pressedQuietOf,\n  spokenUnless,\n} from '@/registry/ndeko/lib/demonstration';\nimport {\n  CheckIcon,\n  GripVerticalIcon,\n  LoaderIcon,\n  ShuffleIcon,\n} from '@/registry/ndeko/lib/icons';\nimport { rowPresenceOf } from '@/registry/ndeko/lib/motion-presets';\nimport { applyBatchAt, restingOf } from '@/registry/ndeko/lib/run-session';\nimport { cn } from '@/registry/ndeko/lib/utils';\nimport { countOf } from '@/registry/ndeko/lib/words';\nimport { Button } from '@/registry/ndeko/ui/button';\nimport { Swap } from '@/registry/ndeko/ui/chat-motion';\nimport { Chip } from '@/registry/ndeko/ui/chip';\nimport {\n  DropdownMenu,\n  DropdownMenuContent,\n  DropdownMenuGroup,\n  DropdownMenuGroupLabel,\n  DropdownMenuRadioGroup,\n  DropdownMenuRadioItem,\n  DropdownMenuTrigger,\n} from '@/registry/ndeko/ui/dropdown-menu';\nimport { ClockGate, LiveDuration } from '@/registry/ndeko/ui/live-duration';\nimport { LiveRegion } from '@/registry/ndeko/ui/live-region';\nimport { ReplayButton } from '@/registry/ndeko/ui/replay-button';\n\nimport { AgentTile } from './_parts/agent-tile';\nimport {\n  CODER,\n  CREW,\n  PLANNER,\n  RESEARCHER,\n  REVIEWER,\n  RUN_TITLE,\n  WRITER,\n} from './_parts/crew';\n\nexport type HandoffItem = {\n  id: string;\n  label: string;\n  /** False when the item did not reach the next agent: it stays in the view, struck through. */\n  isPassed: boolean;\n};\n\nexport type RelayStation = {\n  id: string;\n  agent: RunAgent;\n  /** The agent the station had before the person moved it. */\n  reassignedFrom?: RunAgent;\n};\n\nexport type HandoffEntry = {\n  /** The hand-off as a call: its label is the note passed along, its status the delivery. */\n  call: ToolCall;\n  toStationId: string;\n  items: HandoffItem[];\n};\n\ntype StationState = 'holding' | 'handing-off' | 'handed-off' | 'upcoming';\n\nconst STATE_WORD: Record<StationState, string> = {\n  holding: 'Working',\n  'handing-off': 'Handing off',\n  'handed-off': 'Handed off',\n  upcoming: 'Next',\n};\n\nconst STATE_WORDS = Object.values(STATE_WORD);\n\n/**\n * The task goes down the rail from one tile to the next: a box as tall as the rail slides in from\n * above with the task at its foot. One transition on `translate`, played once when the hand-off starts.\n */\nconst TASK_TRAVEL =\n  'absolute inset-0 transition-[translate] duration-[1100ms] ease-(--ease-in-out-strong) starting:-translate-y-full';\n\n/** The tile that receives the task settles from a little larger, once. */\nconst TILE_LANDING = 'starting:scale-110 starting:opacity-55';\n\nfunction tailStyleOf(color: string): React.CSSProperties {\n  return {\n    backgroundImage: `linear-gradient(to bottom, transparent, ${color})`,\n  };\n}\n\nfunction TravellingTask({ color }: { color: string }) {\n  return (\n    <span className='absolute -inset-x-1.5 inset-y-0 overflow-hidden'>\n      <span className={TASK_TRAVEL}>\n        <span\n          className='absolute bottom-[9px] left-1/2 h-8 w-px -translate-x-1/2'\n          style={tailStyleOf(color)}\n        />\n        <span\n          className='absolute bottom-0 left-1/2 size-[9px] -translate-x-1/2 rounded-[3px]'\n          style={{ backgroundColor: color }}\n        />\n      </span>\n    </span>\n  );\n}\n\nfunction incomingOf(handoffs: HandoffEntry[], stationId: string) {\n  return handoffs.find((handoff) => handoff.toStationId === stationId);\n}\n\nfunction outgoingOf(\n  handoffs: HandoffEntry[],\n  stations: RelayStation[],\n  index: number\n) {\n  const next = stations[index + 1];\n  if (!next) {\n    return undefined;\n  }\n  return incomingOf(handoffs, next.id);\n}\n\nfunction hasReceived(\n  handoffs: HandoffEntry[],\n  station: RelayStation,\n  index: number,\n  hasStarted: boolean\n) {\n  if (index === 0) {\n    return hasStarted;\n  }\n  const incoming = incomingOf(handoffs, station.id);\n  return incoming?.call.status === 'done';\n}\n\nfunction stateOf(\n  isReceived: boolean,\n  outgoing: HandoffEntry | undefined\n): StationState {\n  if (!isReceived) {\n    return 'upcoming';\n  }\n  if (outgoing?.call.status === 'done') {\n    return 'handed-off';\n  }\n  if (outgoing?.call.status === 'running') {\n    return 'handing-off';\n  }\n  return 'holding';\n}\n\n/** The first station that has not received the work yet: the only one a person can still move. */\nfunction nextStationIndexOf(\n  stations: RelayStation[],\n  handoffs: HandoffEntry[]\n) {\n  return stations.findIndex((station, index) => {\n    if (index === 0) {\n      return false;\n    }\n    const incoming = incomingOf(handoffs, station.id);\n    return incoming === undefined;\n  });\n}\n\nfunction candidatesFor(\n  agents: RunAgent[],\n  giver: RunAgent | undefined,\n  current: RunAgent\n) {\n  return agents.filter((agent) => {\n    const isGiver = agent.id === giver?.id;\n    const isCurrent = agent.id === current.id;\n    return !isGiver && !isCurrent;\n  });\n}\n\nfunction dropTargetAt(point: { x: number; y: number }) {\n  const clientX = point.x - window.scrollX;\n  const clientY = point.y - window.scrollY;\n  const stack = document.elementsFromPoint(clientX, clientY);\n  const target = stack.find((element) =>\n    element.hasAttribute('data-drop-agent')\n  );\n  return target?.getAttribute('data-drop-agent') ?? null;\n}\n\nconst DELIVERY_WORDS = ['Handing off to', 'Received by'];\n\nfunction deliveryWordOf(isRunning: boolean) {\n  if (isRunning) {\n    return 'Handing off to';\n  }\n  return 'Received by';\n}\n\nfunction ItemChip({ item }: { item: HandoffItem }) {\n  if (item.isPassed) {\n    return (\n      <span className='bg-foreground/[0.05] dark:bg-foreground/[0.08] inline-flex h-6 items-center rounded-md px-2 text-xs'>\n        {item.label}\n      </span>\n    );\n  }\n  return (\n    <span className='bg-destructive/[0.06] inline-flex h-6 items-center gap-1.5 rounded-md px-2 text-xs'>\n      <span className='text-muted-foreground decoration-muted-foreground/70 line-through'>\n        {item.label}\n      </span>\n      <span className='text-destructive-ink'>Not passed</span>\n    </span>\n  );\n}\n\nfunction HandoffBlock({\n  handoff,\n  receiver,\n}: {\n  handoff: HandoffEntry;\n  receiver: RunAgent;\n}) {\n  const isRunning = handoff.call.status === 'running';\n  const lostItems = handoff.items.filter((item) => !item.isPassed);\n  const lostCount = lostItems.length;\n  const hasLost = lostCount > 0;\n  const passedCount = handoff.items.length - lostCount;\n\n  return (\n    <div className='bg-foreground/[0.025] dark:bg-foreground/[0.04] mt-2 flex flex-col gap-2 rounded-lg p-3'>\n      <p className='text-muted-foreground flex items-center gap-1.5 text-xs'>\n        <Swap swapKey={handoff.call.status} className='size-3.5'>\n          {isRunning && <LoaderIcon className='size-3.5 animate-spin' />}\n          {!isRunning && (\n            <CheckIcon className='text-success-foreground size-3.5' />\n          )}\n        </Swap>\n        <span className='inline-flex items-baseline gap-1'>\n          <WordRoll text={deliveryWordOf(isRunning)} labels={DELIVERY_WORDS} />\n          <WordRoll text={receiver.name} />\n        </span>\n      </p>\n      <p className='text-[13px] leading-5 text-pretty'>\n        &ldquo;{handoff.call.label}&rdquo;\n      </p>\n      <ul\n        aria-label={`Context: ${countOf(passedCount, 'item')} passed`}\n        className='flex flex-wrap gap-1.5'\n      >\n        {handoff.items.map((item) => (\n          <li key={item.id}>\n            <ItemChip item={item} />\n          </li>\n        ))}\n      </ul>\n      {hasLost && (\n        <p className='text-muted-foreground text-xs leading-5'>\n          {countOf(lostCount, 'item')} did not make it to {receiver.name}.\n        </p>\n      )}\n    </div>\n  );\n}\n\ntype NextStationProps = {\n  station: RelayStation;\n  giver: RunAgent | undefined;\n  candidates: RunAgent[];\n  isReassignPressed: boolean;\n  onReassign?: (stationId: string, agentId: string) => void;\n  onInteractionChange?: (isInteracting: boolean) => void;\n};\n\nfunction NextStation({\n  station,\n  giver,\n  candidates,\n  isReassignPressed,\n  onReassign,\n  onInteractionChange,\n}: NextStationProps) {\n  const [isDragging, setIsDragging] = useState(false);\n  const [overId, setOverId] = useState<string | null>(null);\n  const dragControls = useDragControls();\n  const shouldReduceMotion = usePrefersReducedMotion();\n  const options = [station.agent, ...candidates];\n\n  function pickUp(event: React.PointerEvent<HTMLDivElement>) {\n    const isMouse = event.pointerType === 'mouse';\n    const isOnGrip = isGripTarget(event.target);\n    if (isMouse || isOnGrip) {\n      dragControls.start(event);\n    }\n  }\n\n  function startDrag() {\n    setIsDragging(true);\n    onInteractionChange?.(true);\n  }\n\n  function trackDrag(\n    _event: PointerEvent | MouseEvent | TouchEvent,\n    info: PanInfo\n  ) {\n    const target = dropTargetAt(info.point);\n    setOverId(target);\n  }\n\n  function endDrag(\n    _event: PointerEvent | MouseEvent | TouchEvent,\n    info: PanInfo\n  ) {\n    const target = dropTargetAt(info.point);\n    setIsDragging(false);\n    setOverId(null);\n    onInteractionChange?.(false);\n    if (target) {\n      onReassign?.(station.id, target);\n    }\n  }\n\n  function changeAgent(value: unknown) {\n    const agent = options.find((option) => option.id === value);\n    const isNewAgent = agent !== undefined && agent.id !== station.agent.id;\n    if (isNewAgent) {\n      onReassign?.(station.id, agent.id);\n    }\n  }\n\n  return (\n    <div className='flex flex-col gap-2'>\n      <motion.div\n        drag\n        dragControls={dragControls}\n        dragListener={false}\n        onPointerDown={pickUp}\n        dragSnapToOrigin\n        dragElastic={0.18}\n        dragMomentum={false}\n        onDragStart={startDrag}\n        onDrag={trackDrag}\n        onDragEnd={endDrag}\n        whileDrag={dragLookOf(shouldReduceMotion)}\n        className={cn(\n          'bg-card hairline-ring ring-border relative z-20 flex cursor-grab items-center gap-2 rounded-lg py-1.5 ps-1 pe-2 shadow-[0_1px_2px_rgb(0_0_0/0.04)] active:cursor-grabbing',\n          isDragging && 'surface-raised'\n        )}\n      >\n        <span\n          data-drag-grip\n          aria-hidden='true'\n          className='text-muted-foreground flex size-7 shrink-0 touch-none items-center justify-center'\n        >\n          <GripVerticalIcon className='size-4' />\n        </span>\n        <span className='min-w-0 flex-1 text-[13px] font-medium'>\n          <WordRoll text={station.agent.name} />\n        </span>\n        <DropdownMenu onOpenChange={(isOpen) => onInteractionChange?.(isOpen)}>\n          <DropdownMenuTrigger\n            render={\n              <Button\n                type='button'\n                variant='ghost'\n                size='sm'\n                className={cn(\n                  'text-muted-foreground -me-1',\n                  pressedQuietOf(isReassignPressed)\n                )}\n                onPointerDown={(event) => event.stopPropagation()}\n              />\n            }\n          >\n            <ShuffleIcon />\n            Reassign\n          </DropdownMenuTrigger>\n          <DropdownMenuContent align='end'>\n            <DropdownMenuGroup>\n              <DropdownMenuGroupLabel>Who takes it next</DropdownMenuGroupLabel>\n              <DropdownMenuRadioGroup\n                value={station.agent.id}\n                onValueChange={changeAgent}\n              >\n                {options.map((option) => (\n                  <DropdownMenuRadioItem key={option.id} value={option.id}>\n                    <AgentTile\n                      name={option.name}\n                      series={option.series}\n                      size='sm'\n                    />\n                    {option.name}\n                  </DropdownMenuRadioItem>\n                ))}\n              </DropdownMenuRadioGroup>\n            </DropdownMenuGroup>\n          </DropdownMenuContent>\n        </DropdownMenu>\n      </motion.div>\n      <div aria-hidden='true' className='flex flex-wrap items-center gap-1.5'>\n        <span className='text-muted-foreground text-xs'>Or drop it on</span>\n        {candidates.map((candidate) => {\n          const isOver = overId === candidate.id;\n          return (\n            <span\n              key={candidate.id}\n              data-drop-agent={candidate.id}\n              className={cn(\n                'rounded-md transition-[scale,box-shadow] duration-150 ease-(--ease-out-strong)',\n                isOver && 'ring-primary/40 scale-105 ring-2'\n              )}\n            >\n              <Chip color={seriesColorOf(candidate.series)} className='h-6'>\n                {candidate.name}\n              </Chip>\n            </span>\n          );\n        })}\n      </div>\n      {giver && (\n        <p className='sr-only'>\n          {giver.name} hands the work to {station.agent.name} next. Use Reassign\n          to choose another agent.\n        </p>\n      )}\n    </div>\n  );\n}\n\n/** On touch the card scrolls the page; only its grip picks it up. A mouse can take it anywhere. */\nfunction isGripTarget(target: EventTarget) {\n  if (!(target instanceof Element)) {\n    return false;\n  }\n  const grip = target.closest('[data-drag-grip]');\n  return grip !== null;\n}\n\nconst DRAG_LOOK = { scale: 1.03 };\n\nconst NO_DRAG_LOOK = { scale: 1 };\n\nfunction dragLookOf(shouldReduceMotion: boolean | null) {\n  if (shouldReduceMotion) {\n    return NO_DRAG_LOOK;\n  }\n  return DRAG_LOOK;\n}\n\n/** The last station holds the work until the run ends: its time stops there, not at the clock of the page. */\nfunction timingOf(\n  isReceived: boolean,\n  incoming: HandoffEntry | undefined,\n  outgoing: HandoffEntry | undefined,\n  startedAt: number | undefined,\n  endedAt: number | undefined\n) {\n  if (!isReceived) {\n    return null;\n  }\n  const receivedAt = incoming?.call.endedAt ?? startedAt;\n  const handedAt = outgoing?.call.startedAt ?? endedAt;\n  return { startedAt: receivedAt, endedAt: handedAt };\n}\n\nfunction wordOf(state: StationState) {\n  return STATE_WORD[state];\n}\n\nfunction railToneOf(state: StationState) {\n  if (state === 'upcoming') {\n    return 'opacity-55';\n  }\n  return 'opacity-100';\n}\n\nfunction receivedKeyOf(isReceived: boolean) {\n  if (isReceived) {\n    return 'received';\n  }\n  return 'upcoming';\n}\n\nfunction receivedIdsOf(\n  stations: RelayStation[],\n  handoffs: HandoffEntry[],\n  hasStarted: boolean\n) {\n  const received = stations.filter((station, index) =>\n    hasReceived(handoffs, station, index, hasStarted)\n  );\n  const ids = received.map((station) => station.id);\n  return new Set(ids);\n}\n\n/** A tile lands only when the task reaches it under the eyes of the person, not when the relay is first shown. */\nfunction landingOf(isReceived: boolean, wasReceivedAtFirst: boolean) {\n  if (isReceived && !wasReceivedAtFirst) {\n    return TILE_LANDING;\n  }\n  return undefined;\n}\n\nexport type AgentHandoffProps = {\n  stations: RelayStation[];\n  handoffs: HandoffEntry[];\n  /** Agents who can take the next station. */\n  agents: RunAgent[];\n  startedAt?: number;\n  /** When the run ended, so the station that kept the work stops counting there. */\n  endedAt?: number;\n  isLive?: boolean;\n  onReassign?: (stationId: string, agentId: string) => void;\n  /** True while a person drags the next station or has its menu open. */\n  onInteractionChange?: (isInteracting: boolean) => void;\n  /** Shows Reassign held down, for a demonstration that presses it. */\n  isReassignPressed?: boolean;\n  className?: string;\n};\n\nexport function AgentHandoff({\n  stations,\n  handoffs,\n  agents,\n  startedAt,\n  endedAt,\n  isLive = false,\n  onReassign,\n  onInteractionChange,\n  isReassignPressed = false,\n  className,\n}: AgentHandoffProps) {\n  const shouldReduceMotion = usePrefersReducedMotion();\n  const rowPresence = rowPresenceOf(shouldReduceMotion);\n  const hasStarted = startedAt !== undefined;\n  const [firstReceivedIds] = useState(() =>\n    receivedIdsOf(stations, handoffs, hasStarted)\n  );\n  const nextIndex = nextStationIndexOf(stations, handoffs);\n\n  return (\n    <ol aria-label='Relay of agents' className={cn('flex flex-col', className)}>\n      {stations.map((station, index) => {\n        const isLast = index === stations.length - 1;\n        const incoming = incomingOf(handoffs, station.id);\n        const outgoing = outgoingOf(handoffs, stations, index);\n        const isReceived = hasReceived(handoffs, station, index, hasStarted);\n        const state = stateOf(isReceived, outgoing);\n        const isNext = index === nextIndex;\n        const receiver = stations[index + 1]?.agent;\n        const giver = stations[index - 1]?.agent;\n        const showsHandoff = outgoing !== undefined && receiver !== undefined;\n        const timing = timingOf(\n          isReceived,\n          incoming,\n          outgoing,\n          startedAt,\n          endedAt\n        );\n        const isHolding = state === 'holding';\n        const isTicking = isHolding && isLive;\n        const series = seriesColorOf(station.agent.series);\n        const outgoingKey = outgoing?.call.id ?? 'none';\n        const isOutgoingLive = outgoing?.call.status === 'running';\n        const candidates = candidatesFor(agents, giver, station.agent);\n        const wasReceivedAtFirst = firstReceivedIds.has(station.id);\n\n        return (\n          <li key={station.id} className='relative flex gap-3 pb-5 last:pb-0'>\n            {!isLast && (\n              <span\n                aria-hidden='true'\n                className='bg-border absolute start-[11.5px] top-7 bottom-1 w-px'\n              >\n                {isOutgoingLive && (\n                  <TravellingTask key={outgoingKey} color={series} />\n                )}\n              </span>\n            )}\n            <span\n              key={receivedKeyOf(isReceived)}\n              className={cn(\n                'relative z-10 pt-0.5 transition-[opacity,scale] duration-300 ease-(--ease-out-strong)',\n                railToneOf(state),\n                landingOf(isReceived, wasReceivedAtFirst)\n              )}\n            >\n              <AgentTile\n                name={station.agent.name}\n                series={station.agent.series}\n              />\n            </span>\n            <div className='min-w-0 flex-1'>\n              {isNext && (\n                <NextStation\n                  station={station}\n                  giver={giver}\n                  candidates={candidates}\n                  isReassignPressed={isReassignPressed}\n                  onReassign={onReassign}\n                  onInteractionChange={onInteractionChange}\n                />\n              )}\n              {!isNext && (\n                <div className='flex min-h-7 items-baseline justify-between gap-3'>\n                  <p\n                    className={cn(\n                      'text-[13px] font-medium',\n                      state === 'upcoming' && 'text-muted-foreground'\n                    )}\n                  >\n                    <WordRoll text={station.agent.name} />\n                  </p>\n                  <p className='text-muted-foreground flex items-baseline gap-1.5 text-xs'>\n                    <WordRoll text={wordOf(state)} labels={STATE_WORDS} />\n                    {timing && (\n                      <LiveDuration\n                        item={timing}\n                        isRunning={isTicking}\n                        className='inline-block min-w-8 text-end'\n                      />\n                    )}\n                  </p>\n                </div>\n              )}\n              <AnimatePresence initial={false}>\n                {station.reassignedFrom && (\n                  <motion.p\n                    key={station.reassignedFrom.id}\n                    className='text-muted-foreground overflow-hidden text-xs'\n                    {...rowPresence}\n                  >\n                    <span className='block pt-1'>\n                      Reassigned by you, was {station.reassignedFrom.name}\n                    </span>\n                  </motion.p>\n                )}\n              </AnimatePresence>\n              <AnimatePresence initial={false}>\n                {showsHandoff && (\n                  <motion.div\n                    key={outgoingKey}\n                    className='overflow-hidden'\n                    {...rowPresence}\n                  >\n                    <HandoffBlock handoff={outgoing} receiver={receiver} />\n                  </motion.div>\n                )}\n              </AnimatePresence>\n            </div>\n          </li>\n        );\n      })}\n    </ol>\n  );\n}\n\ntype HandoffPlan = {\n  id: string;\n  toStationId: string;\n  note: string;\n  items: HandoffItem[];\n};\n\nconst STATIONS: RelayStation[] = [\n  { id: 'start', agent: PLANNER },\n  { id: 'prices', agent: RESEARCHER },\n  { id: 'compare', agent: CODER },\n  { id: 'draft', agent: WRITER },\n];\n\nconst HANDOFFS: HandoffPlan[] = [\n  {\n    id: 'to-prices',\n    toStationId: 'prices',\n    note: 'Find the Brightmoor list price for each of our 42 items',\n    items: [\n      { id: 'invoices', label: 'Q3 invoices', isPassed: true },\n      { id: 'items', label: '42 item codes', isPassed: true },\n      { id: 'budget', label: 'Budget: $40', isPassed: true },\n    ],\n  },\n  {\n    id: 'to-compare',\n    toStationId: 'compare',\n    note: 'Here are the 14 vendors with prices',\n    items: [\n      { id: 'price-lists', label: '14 price lists', isPassed: true },\n      { id: 'contacts', label: 'Vendor contacts', isPassed: true },\n      { id: 'page', label: 'Brightmoor pricing page', isPassed: false },\n    ],\n  },\n  {\n    id: 'to-draft',\n    toStationId: 'draft',\n    note: 'Six lines are above list price, $1,840.20 in total',\n    items: [\n      { id: 'table', label: 'Comparison table', isPassed: true },\n      { id: 'script', label: 'compare_prices.py output', isPassed: true },\n      { id: 'raw', label: '117 raw invoice lines', isPassed: false },\n    ],\n  },\n];\n\nfunction handoffStarted(plan: HandoffPlan): RunEvent {\n  return {\n    type: 'call.started',\n    call: {\n      id: plan.id,\n      tool: 'handoff',\n      label: plan.note,\n      status: 'running',\n      input: { to: plan.toStationId },\n    },\n  };\n}\n\nfunction handoffDone(plan: HandoffPlan): RunEvent {\n  return { type: 'call.updated', id: plan.id, patch: { status: 'done' } };\n}\n\nconst [TO_PRICES, TO_COMPARE, TO_DRAFT] = HANDOFFS;\n\ntype RelayState = {\n  run: RunSnapshot;\n  stations: RelayStation[];\n  /** True while a person drags the next station or has its menu open: the relay waits for them. */\n  isInteracting: boolean;\n};\n\ntype RelayAction =\n  | { type: 'events'; events: RunEvent[] }\n  | { type: 'reassign'; stationId: string; agentId: string }\n  | { type: 'interact'; isInteracting: boolean };\n\ntype RelayStep = SessionStep<RelayState, RelayAction>;\n\nconst INITIAL_RUN = emptyRun({ id: 'relay', title: RUN_TITLE, agents: CREW });\n\nconst INITIAL: RelayState = {\n  run: INITIAL_RUN,\n  stations: STATIONS,\n  isInteracting: false,\n};\n\n/** A station given back to its first agent forgets that it was ever moved. */\nfunction reassignedStation(station: RelayStation, agent: RunAgent) {\n  const original = station.reassignedFrom ?? station.agent;\n  const isBack = original.id === agent.id;\n  if (isBack) {\n    return { id: station.id, agent };\n  }\n  return { id: station.id, agent, reassignedFrom: original };\n}\n\nfunction withReassign(state: RelayState, stationId: string, agentId: string) {\n  const agent = CREW.find((candidate) => candidate.id === agentId);\n  if (!agent) {\n    return state;\n  }\n  const stations = state.stations.map((station) => {\n    if (station.id !== stationId) {\n      return station;\n    }\n    return reassignedStation(station, agent);\n  });\n  return { ...state, stations };\n}\n\nfunction applyAt(\n  state: RelayState,\n  action: RelayAction,\n  now: number\n): RelayState {\n  if (action.type === 'reassign') {\n    return withReassign(state, action.stationId, action.agentId);\n  }\n  if (action.type === 'interact') {\n    return { ...state, isInteracting: action.isInteracting };\n  }\n  const run = applyBatchAt(state.run, action, now);\n  return { ...state, run };\n}\n\n/** Times are stamped when an action lands, as a backend would stream it. */\nfunction reducer(state: RelayState, action: RelayAction) {\n  return applyAt(state, action, Date.now());\n}\n\nfunction landing(after: number, ...events: RunEvent[]): RelayStep {\n  return { after, action: { type: 'events', events } };\n}\n\nfunction isHeldByPerson(state: RelayState) {\n  return state.isInteracting;\n}\n\nconst SCRIPT: RelayStep[] = [\n  landing(500, { type: 'run.started' }),\n  landing(1800, handoffStarted(TO_PRICES)),\n  landing(1300, handoffDone(TO_PRICES)),\n  landing(3200, handoffStarted(TO_COMPARE)),\n  landing(1300, handoffDone(TO_COMPARE)),\n  {\n    after: 1500,\n    by: 'demo',\n    press: 'reassign',\n    action: { type: 'reassign', stationId: 'draft', agentId: REVIEWER.id },\n  },\n  landing(2100, handoffStarted(TO_DRAFT)),\n  landing(1300, handoffDone(TO_DRAFT)),\n  landing(3200, { type: 'run.finished', status: 'done' }),\n];\n\nconst SESSION = {\n  reducer,\n  initial: INITIAL,\n  resting: restingOf(INITIAL, SCRIPT, applyAt),\n  script: SCRIPT,\n  loopAfter: 3000,\n  holdWhile: isHeldByPerson,\n};\n\nfunction entriesOf(calls: ToolCall[]): HandoffEntry[] {\n  const entries: HandoffEntry[] = [];\n  for (const call of calls) {\n    const plan = HANDOFFS.find((candidate) => candidate.id === call.id);\n    if (plan) {\n      entries.push({ call, toStationId: plan.toStationId, items: plan.items });\n    }\n  }\n  return entries;\n}\n\nfunction announcementOf(\n  run: RunSnapshot,\n  stations: RelayStation[],\n  handoffs: HandoffEntry[]\n) {\n  const latest = handoffs.at(-1);\n  if (run.status === 'done') {\n    const last = stations.at(-1);\n    return `${last?.agent.name ?? 'The last agent'} finished the work`;\n  }\n  if (!latest) {\n    return `${stations[0].agent.name} starts the work`;\n  }\n  const receiver = stations.find(\n    (station) => station.id === latest.toStationId\n  );\n  const name = receiver?.agent.name ?? '';\n  const lost = latest.items.filter((item) => !item.isPassed);\n  const lostNote = lostNoteOf(lost.length);\n  if (latest.call.status === 'running') {\n    return `Handing off to ${name}: ${latest.call.label}`;\n  }\n  return `${name} received the work.${lostNote}`;\n}\n\nfunction lostNoteOf(lostCount: number) {\n  if (lostCount === 0) {\n    return '';\n  }\n  return ` ${countOf(lostCount, 'item')} not passed.`;\n}\n\nexport default function Agent04() {\n  const [message, setMessage] = useState('');\n  const { ref, isInView, isAutoplaying, state, act, pressed, restart } =\n    useSession(SESSION);\n  const { run, stations } = state;\n  const handoffs = entriesOf(run.calls);\n  const isEnded = run.endedAt !== undefined;\n  const canReplay = isEnded && !isAutoplaying;\n  const isLive = run.status === 'running';\n  const runMessage = announcementOf(run, stations, handoffs);\n\n  function reassign(stationId: string, agentId: string) {\n    const agent = CREW.find((candidate) => candidate.id === agentId);\n    if (!agent) {\n      return;\n    }\n    act({ type: 'reassign', stationId, agentId });\n    setMessage(`The next station goes to ${agent.name}`);\n  }\n\n  function changeInteraction(isInteracting: boolean) {\n    act({ type: 'interact', isInteracting });\n  }\n\n  function replay() {\n    setMessage('');\n    restart();\n  }\n\n  useKeptHeight(ref);\n\n  return (\n    <div\n      ref={ref}\n      className='flex min-h-[47rem] w-full max-w-[30rem] flex-col gap-2'\n    >\n      <section\n        aria-label='Hand-offs'\n        className='bg-card hairline-ring ring-border flex flex-col gap-4 rounded-xl p-4 shadow-[0_1px_2px_rgb(0_0_0/0.04)]'\n      >\n        <header>\n          <h3 className='text-sm font-medium'>{run.title}</h3>\n          <p className='text-muted-foreground text-xs'>\n            Who handed what to whom\n          </p>\n        </header>\n        <ClockGate isActive={isInView}>\n          <AgentHandoff\n            stations={stations}\n            handoffs={handoffs}\n            agents={CREW}\n            startedAt={run.startedAt}\n            endedAt={run.endedAt}\n            isLive={isLive}\n            onReassign={reassign}\n            onInteractionChange={changeInteraction}\n            isReassignPressed={pressed === 'reassign'}\n          />\n        </ClockGate>\n      </section>\n      <div className='flex h-7 items-center justify-end'>\n        <ReplayButton isShown={canReplay} onReplay={replay} />\n      </div>\n      <LiveRegion message={spokenUnless(isAutoplaying, runMessage)} />\n      <LiveRegion message={message} />\n    </div>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/agent/agent-04.tsx"
    },
    {
      "path": "registry/ndeko/components/agent/_parts/agent-tile.tsx",
      "content": "import { seriesColorOf, type SeriesSlot } from '@/registry/ndeko/lib/agent-run';\nimport { cn } from '@/registry/ndeko/lib/utils';\nimport { MeshTile } from '@/registry/ndeko/ui/mesh-tile';\n\nconst SIZE_CLASS = {\n  sm: 'size-5 rounded-[5px]',\n  md: 'size-6 rounded-md',\n  lg: 'size-8 rounded-lg',\n} as const;\n\nexport type AgentTileSize = keyof typeof SIZE_CLASS;\n\ntype AgentTileProps = {\n  name: string;\n  series: SeriesSlot;\n  size?: AgentTileSize;\n  className?: string;\n};\n\n/** The picture of an agent: a gradient drawn from its name, in its series colour. Its name always sits beside it. */\nexport function AgentTile({\n  name,\n  series,\n  size = 'md',\n  className,\n}: AgentTileProps) {\n  const color = seriesColorOf(series);\n  const sizeClass = SIZE_CLASS[size];\n\n  return (\n    <MeshTile seed={name} color={color} className={cn(sizeClass, className)} />\n  );\n}\n",
      "type": "registry:component",
      "target": "components/agent/_parts/agent-tile.tsx"
    },
    {
      "path": "registry/ndeko/components/agent/_parts/crew.ts",
      "content": "import type { RunAgent } from '@/registry/ndeko/lib/agent-run';\n\nexport const RUN_TITLE = 'Q3 vendor review for Brightmoor Supply';\n\nexport const PLANNER: RunAgent = {\n  id: 'planner',\n  name: 'Planner',\n  series: 1,\n  status: 'idle',\n  role: 'Lead',\n  model: 'Claude Opus',\n};\n\nexport const RESEARCHER: RunAgent = {\n  id: 'researcher',\n  name: 'Researcher',\n  series: 2,\n  status: 'idle',\n  model: 'Claude Sonnet',\n  parentId: 'planner',\n};\n\nexport const WRITER: RunAgent = {\n  id: 'writer',\n  name: 'Writer',\n  series: 3,\n  status: 'idle',\n  model: 'Claude Haiku',\n  parentId: 'planner',\n};\n\nexport const REVIEWER: RunAgent = {\n  id: 'reviewer',\n  name: 'Reviewer',\n  series: 4,\n  status: 'idle',\n  model: 'Claude Sonnet',\n  parentId: 'planner',\n};\n\nexport const CODER: RunAgent = {\n  id: 'coder',\n  name: 'Coder',\n  series: 5,\n  status: 'idle',\n  model: 'Claude Opus',\n  parentId: 'planner',\n};\n\nexport const CREW: RunAgent[] = [PLANNER, RESEARCHER, WRITER, REVIEWER, CODER];\n\nexport const PAGE_ERROR = 'The pricing page refused the reader (403)';\n",
      "type": "registry:component",
      "target": "components/agent/_parts/crew.ts"
    }
  ],
  "categories": [
    "components",
    "agent"
  ],
  "type": "registry:component"
}