{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "agent-05",
  "title": "Agent tree",
  "description": "Who started whom as a tree where a branch unfolds under an agent when it starts a sub-agent, with live clocks, subtree costs that roll, folds that sum what they hide, and Approve or Retry right in the row.",
  "dependencies": [
    "motion"
  ],
  "registryDependencies": [
    "https://ui.ndekocode.dev/r/agent-run.json",
    "https://ui.ndekocode.dev/r/badge.json",
    "https://ui.ndekocode.dev/r/button.json",
    "https://ui.ndekocode.dev/r/chat-motion.json",
    "https://ui.ndekocode.dev/r/demonstration.json",
    "https://ui.ndekocode.dev/r/icons.json",
    "https://ui.ndekocode.dev/r/kbd.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-number.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/use-timeouts.json",
    "https://ui.ndekocode.dev/r/utils.json",
    "https://ui.ndekocode.dev/r/words.json"
  ],
  "files": [
    {
      "path": "registry/ndeko/components/agent/agent-05.tsx",
      "content": "'use client';\n\nimport { AnimatePresence, motion } from 'motion/react';\nimport { useId, useRef, useState } from 'react';\n\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 { useTimeouts } from '@/registry/ndeko/hooks/use-timeouts';\nimport {\n  emptyRun,\n  formatCost,\n  pendingApprovalsOf,\n  reduceRun,\n  type AgentStatus as AgentState,\n  type ApprovalRequest,\n  type RunAgent,\n  type RunEvent,\n  type RunSnapshot,\n} from '@/registry/ndeko/lib/agent-run';\nimport { spokenUnless } from '@/registry/ndeko/lib/demonstration';\nimport { ChevronRightIcon } 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 { Badge } from '@/registry/ndeko/ui/badge';\nimport { ClockGate } from '@/registry/ndeko/ui/live-duration';\nimport { LiveRegion } from '@/registry/ndeko/ui/live-region';\nimport { ReplayButton } from '@/registry/ndeko/ui/replay-button';\n\nimport { AgentActions, pendingApprovalOf } from './_parts/agent-actions';\nimport { AgentClock } from './_parts/agent-clock';\nimport {\n  AGENT_STATUS_WORD,\n  AgentStatusDot,\n  pulsingIdsOf,\n} from './_parts/agent-status';\nimport { AgentTile } from './_parts/agent-tile';\nimport { CostFigure } from './_parts/cost-figure';\nimport {\n  CODER,\n  PAGE_ERROR,\n  PLANNER,\n  RESEARCHER,\n  RUN_TITLE,\n  WRITER,\n} from './_parts/crew';\nimport { DECIDE_AFTER_MS, pressedIdOf } from './_parts/demo';\nimport { focusLater } from './_parts/focus-later';\nimport { useAgentAnnouncement } from './_parts/use-agent-announcement';\n\nconst INDENT_PX = 18;\n\nconst GUIDE_OFFSET_PX = 12;\n\ntype TreeRow = {\n  agent: RunAgent;\n  depth: number;\n  position: number;\n  setSize: number;\n  parentId?: string;\n  hasChildren: boolean;\n  isFolded: boolean;\n  subtreeCost: number;\n  hidden: RunAgent[];\n};\n\ntype Folded = Record<string, boolean>;\n\nfunction idsOf(agents: RunAgent[]) {\n  const ids = agents.map((agent) => agent.id);\n  return new Set(ids);\n}\n\nfunction isRootOf(agent: RunAgent, ids: Set<string>) {\n  if (!agent.parentId) {\n    return true;\n  }\n  return !ids.has(agent.parentId);\n}\n\nfunction childrenOf(agents: RunAgent[], parentId: string) {\n  return agents.filter((agent) => agent.parentId === parentId);\n}\n\nfunction descendantsOf(agents: RunAgent[], agentId: string) {\n  const descendants: RunAgent[] = [];\n  const children = childrenOf(agents, agentId);\n  for (const child of children) {\n    descendants.push(child);\n    const deeper = descendantsOf(agents, child.id);\n    descendants.push(...deeper);\n  }\n  return descendants;\n}\n\nfunction costOf(agent: RunAgent) {\n  return agent.costUsd ?? 0;\n}\n\nfunction subtreeCostOf(agent: RunAgent, descendants: RunAgent[]) {\n  let total = costOf(agent);\n  for (const descendant of descendants) {\n    total += costOf(descendant);\n  }\n  return total;\n}\n\nfunction hiddenOf(isFolded: boolean, descendants: RunAgent[]) {\n  if (isFolded) {\n    return descendants;\n  }\n  return [];\n}\n\nfunction appendRows(\n  rows: TreeRow[],\n  agents: RunAgent[],\n  siblings: RunAgent[],\n  depth: number,\n  folded: Folded\n) {\n  siblings.forEach((agent, index) => {\n    const children = childrenOf(agents, agent.id);\n    const descendants = descendantsOf(agents, agent.id);\n    const hasChildren = children.length > 0;\n    const isFolded = folded[agent.id] === true;\n    rows.push({\n      agent,\n      depth,\n      position: index + 1,\n      setSize: siblings.length,\n      parentId: agent.parentId,\n      hasChildren,\n      isFolded,\n      subtreeCost: subtreeCostOf(agent, descendants),\n      hidden: hiddenOf(isFolded, descendants),\n    });\n    const showsChildren = hasChildren && !isFolded;\n    if (showsChildren) {\n      appendRows(rows, agents, children, depth + 1, folded);\n    }\n  });\n}\n\nfunction visibleRowsOf(agents: RunAgent[], folded: Folded) {\n  const ids = idsOf(agents);\n  const roots = agents.filter((agent) => isRootOf(agent, ids));\n  const rows: TreeRow[] = [];\n  appendRows(rows, agents, roots, 0, folded);\n  return rows;\n}\n\nfunction countWithStatus(agents: RunAgent[], status: AgentState) {\n  const matching = agents.filter((agent) => agent.status === status);\n  return matching.length;\n}\n\nfunction foldedSummaryOf(hidden: RunAgent[]) {\n  const inside = `${countOf(hidden.length, 'agent')} inside`;\n  const waitingCount = countWithStatus(hidden, 'waiting');\n  const failedCount = countWithStatus(hidden, 'failed');\n  const notes: string[] = [];\n  if (waitingCount > 0) {\n    notes.push(`${waitingCount} waiting on you`);\n  }\n  if (failedCount > 0) {\n    notes.push(`${failedCount} failed`);\n  }\n  if (notes.length === 0) {\n    return inside;\n  }\n  const noteText = notes.join(', ');\n  return `${inside}: ${noteText}`;\n}\n\nfunction taskToneOf(status: AgentState) {\n  if (status === 'failed') {\n    return 'text-destructive-ink';\n  }\n  if (status === 'waiting') {\n    return 'text-warning-foreground';\n  }\n  return 'text-muted-foreground';\n}\n\nfunction chevronTurnOf(isOpen: boolean) {\n  if (isOpen) {\n    return 'rotate-90';\n  }\n  return 'rotate-0';\n}\n\nfunction tabIndexOf(isTabbable: boolean) {\n  if (isTabbable) {\n    return 0;\n  }\n  return -1;\n}\n\nfunction expandedOf(row: TreeRow) {\n  if (!row.hasChildren) {\n    return undefined;\n  }\n  return !row.isFolded;\n}\n\nfunction tabbableIdOf(focusedId: string | null, rows: TreeRow[]) {\n  const isVisible = rows.some((row) => row.agent.id === focusedId);\n  if (isVisible) {\n    return focusedId;\n  }\n  return rows[0]?.agent.id ?? null;\n}\n\nfunction rowLabelOf(row: TreeRow) {\n  const { agent } = row;\n  const word = AGENT_STATUS_WORD[agent.status];\n  const cost = formatCost(row.subtreeCost);\n  const parts = [agent.name, word, agent.task ?? '', `${cost} for this branch`];\n  if (row.isFolded) {\n    parts.push(foldedSummaryOf(row.hidden));\n  }\n  return parts.join('. ');\n}\n\nfunction shortcutsOf(canApprove: boolean, canRetry: boolean) {\n  if (canApprove) {\n    return 'A';\n  }\n  if (canRetry) {\n    return 'R';\n  }\n  return undefined;\n}\n\nfunction hintOf(canApprove: boolean, canRetry: boolean) {\n  if (canApprove) {\n    return 'Press A to approve';\n  }\n  if (canRetry) {\n    return 'Press R to retry';\n  }\n  return '';\n}\n\nfunction describedByOf(hasHint: boolean, hintId: string) {\n  if (hasHint) {\n    return hintId;\n  }\n  return undefined;\n}\n\nfunction hasModifierKey(event: React.KeyboardEvent) {\n  const hasCommandKey = event.metaKey || event.ctrlKey;\n  return hasCommandKey || event.altKey;\n}\n\n/**\n * The branch that ties a sub-agent to the one that started it draws itself when the row arrives:\n * the stem grows down from the parent, then the twig reaches the row. Two transitions on `scale`.\n */\nconst STEM_DRAW =\n  'origin-top transition-[scale] duration-300 ease-(--ease-out-strong) starting:scale-y-0';\n\nconst TWIG = 'bg-border absolute top-[18px] h-px w-2';\n\nconst TWIG_DRAW =\n  'origin-left transition-[scale] delay-150 duration-200 ease-(--ease-out-strong) rtl:origin-right starting:scale-x-0';\n\nfunction stemOf(isOwnBranch: boolean, drawsBranch: boolean) {\n  if (isOwnBranch && drawsBranch) {\n    return STEM_DRAW;\n  }\n  return undefined;\n}\n\nfunction twigOf(drawsBranch: boolean) {\n  if (drawsBranch) {\n    return TWIG_DRAW;\n  }\n  return undefined;\n}\n\ntype TreeRowContentProps = {\n  row: TreeRow;\n  attempt: number;\n  approvals: ApprovalRequest[];\n  isStill: boolean;\n  /** False for the rows the tree was first shown with: they are there, they do not arrive. */\n  drawsBranch: boolean;\n  isApprovePressed: boolean;\n  isRetryPressed: boolean;\n  onApprove?: (approvalId: string) => void;\n  onRetry?: (agentId: string) => void;\n};\n\nfunction TreeRowContent({\n  row,\n  attempt,\n  approvals,\n  isStill,\n  drawsBranch,\n  isApprovePressed,\n  isRetryPressed,\n  onApprove,\n  onRetry,\n}: TreeRowContentProps) {\n  const { agent } = row;\n  const isOpen = !row.isFolded;\n  const guides = Array.from({ length: row.depth }, (_, level) => level);\n  const ownLevel = row.depth - 1;\n  const hasBranch = row.depth > 0;\n  const branchStart = ownLevel * INDENT_PX + GUIDE_OFFSET_PX;\n  const startPadding = row.depth * INDENT_PX + 4;\n  const wasRetried = attempt > 1;\n  const hasHidden = row.hidden.length > 0;\n  const showsSummary = row.isFolded && hasHidden;\n\n  return (\n    <div\n      className='relative flex items-start gap-1.5 py-2 pe-2.5'\n      style={{ paddingInlineStart: startPadding }}\n    >\n      {guides.map((level) => (\n        <span\n          key={level}\n          aria-hidden='true'\n          className={cn(\n            'bg-border absolute inset-y-0 w-px',\n            stemOf(level === ownLevel, drawsBranch)\n          )}\n          style={{ insetInlineStart: level * INDENT_PX + GUIDE_OFFSET_PX }}\n        />\n      ))}\n      {hasBranch && (\n        <span\n          aria-hidden='true'\n          className={cn(TWIG, twigOf(drawsBranch))}\n          style={{ insetInlineStart: branchStart }}\n        />\n      )}\n      <span className='flex size-4 shrink-0 items-center justify-center pt-1'>\n        {row.hasChildren && (\n          <ChevronRightIcon\n            className={cn(\n              'text-muted-foreground size-3.5 transition-transform duration-200 ease-(--ease-out-strong) rtl:-scale-x-100',\n              chevronTurnOf(isOpen)\n            )}\n          />\n        )}\n      </span>\n      <span className='relative mt-px shrink-0'>\n        <AgentTile name={agent.name} series={agent.series} size='sm' />\n        <AgentStatusDot\n          status={agent.status}\n          isStill={isStill}\n          className='ring-card absolute -end-1 -bottom-1 rounded-full ring-2'\n        />\n      </span>\n      <div className='min-w-0 flex-1 ps-1.5'>\n        <p className='flex items-center gap-2'>\n          <span className='truncate text-[13px] leading-5 font-medium'>\n            {agent.name}\n          </span>\n          {wasRetried && <Badge variant='secondary'>Retried</Badge>}\n        </p>\n        <p\n          className={cn('truncate text-xs leading-5', taskToneOf(agent.status))}\n        >\n          {agent.task}\n        </p>\n        <AnimatePresence initial={false}>\n          {showsSummary && (\n            <motion.p\n              key='summary'\n              initial={{ opacity: 0 }}\n              animate={{ opacity: 1 }}\n              exit={{ opacity: 0 }}\n              className='text-muted-foreground text-xs leading-5'\n            >\n              {foldedSummaryOf(row.hidden)}\n            </motion.p>\n          )}\n        </AnimatePresence>\n        <AgentActions\n          agent={agent}\n          approvals={approvals}\n          onApprove={onApprove}\n          onRetry={onRetry}\n          hasKeyHints\n          isApprovePressed={isApprovePressed}\n          isRetryPressed={isRetryPressed}\n        />\n      </div>\n      <div className='shrink-0 text-end'>\n        <p className='text-[13px] leading-5'>\n          <CostFigure usd={row.subtreeCost} />\n        </p>\n        <p className='text-muted-foreground text-xs leading-5'>\n          <AgentClock agent={agent} />\n        </p>\n      </div>\n    </div>\n  );\n}\n\nfunction foldedRecordOf(agentIds: string[]): Folded {\n  const folded: Folded = {};\n  for (const agentId of agentIds) {\n    folded[agentId] = true;\n  }\n  return folded;\n}\n\nconst NO_APPROVALS: ApprovalRequest[] = [];\n\nconst NO_ATTEMPTS: Record<string, number> = {};\n\nconst NO_FOLDED: string[] = [];\n\n/** Once a person folds or unfolds, every row that shows up arrives: none is quiet any more. */\nconst NO_IDS = new Set<string>();\n\nexport type AgentTreeProps = {\n  agents: RunAgent[];\n  approvals?: ApprovalRequest[];\n  /** How many times each agent ran. From the second, its row says it was retried. */\n  attempts?: Record<string, number>;\n  onApprove?: (approvalId: string) => void;\n  onRetry?: (agentId: string) => void;\n  defaultFolded?: string[];\n  /** The agent whose Approve a demonstration holds down. */\n  pressedApproveId?: string | null;\n  /** The agent whose Retry a demonstration holds down. */\n  pressedRetryId?: string | null;\n  label?: string;\n  className?: string;\n};\n\nexport function AgentTree({\n  agents,\n  approvals = NO_APPROVALS,\n  attempts = NO_ATTEMPTS,\n  onApprove,\n  onRetry,\n  defaultFolded = NO_FOLDED,\n  pressedApproveId = null,\n  pressedRetryId = null,\n  label = 'Agents',\n  className,\n}: AgentTreeProps) {\n  const [folded, setFolded] = useState<Folded>(() =>\n    foldedRecordOf(defaultFolded)\n  );\n  const [focusedId, setFocusedId] = useState<string | null>(null);\n  const [quietIds, setQuietIds] = useState(() => idsOf(agents));\n  const treeRef = useRef<HTMLUListElement>(null);\n  const hintPrefix = useId();\n  const shouldReduceMotion = usePrefersReducedMotion();\n  const rowPresence = rowPresenceOf(shouldReduceMotion);\n  const rows = visibleRowsOf(agents, folded);\n  const tabbableId = tabbableIdOf(focusedId, rows);\n  const pulsingIds = pulsingIdsOf(agents);\n\n  function focusRow(agentId: string) {\n    setFocusedId(agentId);\n    focusLater(treeRef, `[data-agent-row=\"${agentId}\"]`);\n  }\n\n  function focusIndex(index: number) {\n    const row = rows[index];\n    if (row) {\n      focusRow(row.agent.id);\n    }\n  }\n\n  function setFold(agentId: string, isFolded: boolean) {\n    setFolded((current) => ({ ...current, [agentId]: isFolded }));\n    setQuietIds(NO_IDS);\n  }\n\n  function toggle(row: TreeRow) {\n    if (row.hasChildren) {\n      setFold(row.agent.id, !row.isFolded);\n    }\n  }\n\n  function openOrEnter(row: TreeRow, index: number) {\n    if (!row.hasChildren) {\n      return;\n    }\n    if (row.isFolded) {\n      setFold(row.agent.id, false);\n      return;\n    }\n    focusIndex(index + 1);\n  }\n\n  function foldOrLeave(row: TreeRow) {\n    const canFold = row.hasChildren && !row.isFolded;\n    if (canFold) {\n      setFold(row.agent.id, true);\n      return;\n    }\n    if (row.parentId) {\n      focusRow(row.parentId);\n    }\n  }\n\n  function decide(row: TreeRow, key: string) {\n    const pending = pendingApprovalOf(approvals, row.agent.id);\n    const isFailed = row.agent.status === 'failed';\n    if (key === 'a' && pending) {\n      onApprove?.(pending.id);\n      return true;\n    }\n    if (key === 'r' && isFailed) {\n      onRetry?.(row.agent.id);\n      return true;\n    }\n    return false;\n  }\n\n  function handleKeyDown(\n    event: React.KeyboardEvent<HTMLLIElement>,\n    row: TreeRow\n  ) {\n    const isOnRow = event.target === event.currentTarget;\n    if (!isOnRow) {\n      return;\n    }\n    const index = rows.findIndex(\n      (candidate) => candidate.agent.id === row.agent.id\n    );\n    const key = event.key;\n    if (key === 'ArrowDown') {\n      event.preventDefault();\n      focusIndex(index + 1);\n      return;\n    }\n    if (key === 'ArrowUp') {\n      event.preventDefault();\n      focusIndex(index - 1);\n      return;\n    }\n    if (key === 'Home') {\n      event.preventDefault();\n      focusIndex(0);\n      return;\n    }\n    if (key === 'End') {\n      event.preventDefault();\n      focusIndex(rows.length - 1);\n      return;\n    }\n    if (key === 'ArrowRight') {\n      event.preventDefault();\n      openOrEnter(row, index);\n      return;\n    }\n    if (key === 'ArrowLeft') {\n      event.preventDefault();\n      foldOrLeave(row);\n      return;\n    }\n    const isToggleKey = key === 'Enter' || key === ' ';\n    if (isToggleKey) {\n      event.preventDefault();\n      toggle(row);\n      return;\n    }\n    if (hasModifierKey(event)) {\n      return;\n    }\n    const isDecided = decide(row, key.toLowerCase());\n    if (isDecided) {\n      event.preventDefault();\n    }\n  }\n\n  return (\n    <ul\n      ref={treeRef}\n      role='tree'\n      aria-label={label}\n      className={cn('flex flex-col', className)}\n    >\n      <AnimatePresence initial={false}>\n        {rows.map((row) => {\n          const agentId = row.agent.id;\n          const pending = pendingApprovalOf(approvals, agentId);\n          const canApprove = pending !== undefined;\n          const canRetry = row.agent.status === 'failed';\n          const hint = hintOf(canApprove, canRetry);\n          const hintId = `${hintPrefix}-${agentId}`;\n          const hasHint = hint !== '';\n\n          return (\n            <motion.li\n              key={agentId}\n              role='treeitem'\n              aria-level={row.depth + 1}\n              aria-setsize={row.setSize}\n              aria-posinset={row.position}\n              aria-expanded={expandedOf(row)}\n              aria-label={rowLabelOf(row)}\n              aria-describedby={describedByOf(hasHint, hintId)}\n              aria-keyshortcuts={shortcutsOf(canApprove, canRetry)}\n              tabIndex={tabIndexOf(agentId === tabbableId)}\n              data-agent-row={agentId}\n              onKeyDown={(event) => handleKeyDown(event, row)}\n              onFocus={() => setFocusedId(agentId)}\n              onClick={() => toggle(row)}\n              className='group/row hover:bg-foreground/[0.025] focus-visible:ring-ring/25 overflow-hidden rounded-lg outline-none focus-visible:ring-[3px] focus-visible:ring-inset'\n              {...rowPresence}\n            >\n              <TreeRowContent\n                row={row}\n                attempt={attempts[agentId] ?? 1}\n                approvals={approvals}\n                isStill={!pulsingIds.has(agentId)}\n                drawsBranch={!quietIds.has(agentId)}\n                isApprovePressed={pressedApproveId === agentId}\n                isRetryPressed={pressedRetryId === agentId}\n                onApprove={onApprove}\n                onRetry={onRetry}\n              />\n              {hasHint && (\n                <span id={hintId} className='sr-only'>\n                  {hint}\n                </span>\n              )}\n            </motion.li>\n          );\n        })}\n      </AnimatePresence>\n    </ul>\n  );\n}\n\nconst PAGE_READER: RunAgent = {\n  id: 'page-reader',\n  name: 'Page reader',\n  series: 2,\n  status: 'running',\n  model: 'Claude Haiku',\n  parentId: RESEARCHER.id,\n  task: 'Reading brightmoor-supply.example/pricing',\n};\n\nconst SHEET_READER: RunAgent = {\n  id: 'sheet-reader',\n  name: 'Sheet reader',\n  series: 2,\n  status: 'running',\n  model: 'Claude Haiku',\n  parentId: RESEARCHER.id,\n  task: 'Reading q3-invoices.xlsx',\n};\n\nconst CHECKER: RunAgent = {\n  id: 'checker',\n  name: 'Checker',\n  series: 5,\n  status: 'running',\n  model: 'Claude Haiku',\n  parentId: CODER.id,\n  task: 'Trying compare_prices.py on 10 lines',\n};\n\nconst SEND_APPROVAL: ApprovalRequest = {\n  id: 'send-credit-request',\n  title: 'Send the credit request to accounts@brightmoor-supply.example',\n  risk: 'medium',\n  status: 'pending',\n  agentId: WRITER.id,\n};\n\nconst INITIAL_RUN = emptyRun({\n  id: 'brightmoor-tree',\n  title: RUN_TITLE,\n  agents: [{ ...PLANNER, task: 'Waits to start' }],\n});\n\nfunction patch(id: string, changes: Partial<RunAgent>): RunEvent {\n  return { type: 'agent.updated', id, patch: changes };\n}\n\nfunction added(agent: RunAgent): RunEvent {\n  return { type: 'agent.added', agent };\n}\n\n/** An agent joins idle and starts in the same batch, so its clock is stamped from the moment it shows up. */\nfunction joined(agent: RunAgent): RunEvent[] {\n  const idle: RunAgent = { ...agent, status: 'idle' };\n  const starting = patch(agent.id, { status: 'running' });\n  return [added(idle), starting];\n}\n\ntype TreeState = {\n  run: RunSnapshot;\n  attempts: Record<string, number>;\n};\n\ntype TreeAction =\n  | { type: 'events'; events: RunEvent[] }\n  | { type: 'approve'; approvalId: string }\n  | { type: 'retry'; agentId: string }\n  | { type: 'retried'; agentId: string };\n\ntype TreeStep = SessionStep<TreeState, TreeAction>;\n\nconst INITIAL: TreeState = { run: INITIAL_RUN, attempts: {} };\n\nfunction agentOf(state: TreeState, agentId: string) {\n  return state.run.agents.find((agent) => agent.id === agentId);\n}\n\nfunction attemptOf(state: TreeState, agentId: string) {\n  return state.attempts[agentId] ?? 1;\n}\n\nfunction withApproval(state: TreeState, approvalId: string, now: number) {\n  const resolved = reduceRun(\n    state.run,\n    { type: 'approval.resolved', id: approvalId, decision: 'approved' },\n    now\n  );\n  const sending = patch(WRITER.id, {\n    status: 'running',\n    task: 'Sending the credit request',\n  });\n  const run = reduceRun(resolved, sending, now);\n  return { ...state, run };\n}\n\nfunction withRetry(state: TreeState, agentId: string, now: number) {\n  const agent = agentOf(state, agentId);\n  const isFailed = agent?.status === 'failed';\n  if (!isFailed) {\n    return state;\n  }\n  const previous = attemptOf(state, agentId);\n  const attempts = { ...state.attempts, [agentId]: previous + 1 };\n  const secondTry = patch(agentId, {\n    status: 'running',\n    task: 'Second try through the cached copy',\n    startedAt: undefined,\n    endedAt: undefined,\n  });\n  const run = reduceRun(state.run, secondTry, now);\n  return { run, attempts };\n}\n\n/** The script and the hand of a person may both land the same retry: only the first one counts. */\nfunction withRetryLanded(state: TreeState, agentId: string, now: number) {\n  const agent = agentOf(state, agentId);\n  const isRunning = agent?.status === 'running';\n  const wasRetried = attemptOf(state, agentId) > 1;\n  const isRetrying = isRunning && wasRetried;\n  if (!isRetrying) {\n    return state;\n  }\n  const landed = patch(agentId, {\n    status: 'done',\n    task: 'Read the cached pricing page',\n    costUsd: 0.01,\n  });\n  const run = reduceRun(state.run, landed, now);\n  return { ...state, run };\n}\n\nfunction applyAt(state: TreeState, action: TreeAction, now: number): TreeState {\n  if (action.type === 'approve') {\n    return withApproval(state, action.approvalId, now);\n  }\n  if (action.type === 'retry') {\n    return withRetry(state, action.agentId, now);\n  }\n  if (action.type === 'retried') {\n    return withRetryLanded(state, action.agentId, now);\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: TreeState, action: TreeAction) {\n  return applyAt(state, action, Date.now());\n}\n\nfunction landing(after: number, ...events: RunEvent[]): TreeStep {\n  return { after, action: { type: 'events', events } };\n}\n\nfunction hasRetriedPageReader(state: TreeState) {\n  return attemptOf(state, PAGE_READER.id) > 1;\n}\n\nfunction hasDecided(state: TreeState) {\n  const pending = pendingApprovalsOf(state.run);\n  return pending.length === 0;\n}\n\nconst SCRIPT: TreeStep[] = [\n  landing(\n    400,\n    { type: 'run.started' },\n    patch(PLANNER.id, {\n      status: 'running',\n      task: 'Splitting the review between three agents',\n      costUsd: 0.006,\n    })\n  ),\n  landing(\n    900,\n    ...joined({ ...RESEARCHER, task: 'Collecting the price lists' })\n  ),\n  landing(\n    300,\n    patch(PLANNER.id, { task: 'Watching the agents', costUsd: 0.011 })\n  ),\n  landing(600, ...joined(PAGE_READER)),\n  landing(200, ...joined(SHEET_READER)),\n  landing(\n    1300,\n    patch(SHEET_READER.id, {\n      status: 'done',\n      task: 'Read 117 invoice lines',\n      costUsd: 0.018,\n    })\n  ),\n  landing(\n    500,\n    patch(PAGE_READER.id, {\n      status: 'failed',\n      task: PAGE_ERROR,\n      costUsd: 0.004,\n    })\n  ),\n  {\n    after: DECIDE_AFTER_MS,\n    by: 'person',\n    press: 'retry',\n    action: { type: 'retry', agentId: PAGE_READER.id },\n    isAnswered: hasRetriedPageReader,\n  },\n  landing(\n    700,\n    patch(RESEARCHER.id, {\n      status: 'done',\n      task: 'Found 14 vendors with prices',\n      costUsd: 0.031,\n    })\n  ),\n  landing(\n    300,\n    ...joined({ ...CODER, task: 'Comparing invoices with list prices' })\n  ),\n  { after: 600, action: { type: 'retried', agentId: PAGE_READER.id } },\n  landing(100, ...joined(CHECKER)),\n  landing(\n    1400,\n    patch(CHECKER.id, {\n      status: 'done',\n      task: 'The script holds on 10 lines',\n      costUsd: 0.009,\n    })\n  ),\n  landing(\n    900,\n    patch(CODER.id, {\n      status: 'done',\n      task: 'Six lines above the list price',\n      costUsd: 0.052,\n    })\n  ),\n  landing(300, ...joined({ ...WRITER, task: 'Drafting the credit request' })),\n  landing(\n    1600,\n    patch(WRITER.id, {\n      status: 'waiting',\n      task: 'Waits for you before sending',\n      costUsd: 0.014,\n    }),\n    { type: 'approval.requested', approval: SEND_APPROVAL }\n  ),\n  {\n    after: DECIDE_AFTER_MS,\n    by: 'person',\n    press: 'approve',\n    action: { type: 'approve', approvalId: SEND_APPROVAL.id },\n    isAnswered: hasDecided,\n  },\n  landing(\n    900,\n    patch(WRITER.id, {\n      status: 'done',\n      task: 'Sent the credit request',\n      costUsd: 0.019,\n    })\n  ),\n  landing(\n    400,\n    patch(PLANNER.id, {\n      status: 'done',\n      task: 'Review finished',\n      costUsd: 0.016,\n    }),\n    { type: 'run.finished', status: 'done' }\n  ),\n];\n\n/** How long the second try of an agent takes: the script lands it that long after the retry. */\nconst RETRY_MS = 1600;\n\nconst SESSION = {\n  reducer,\n  initial: INITIAL,\n  resting: restingOf(INITIAL, SCRIPT, applyAt),\n  script: SCRIPT,\n  loopAfter: 3000,\n};\n\nfunction announcementOf(agent: RunAgent) {\n  const word = AGENT_STATUS_WORD[agent.status];\n  return `${agent.name}: ${word}. ${agent.task ?? ''}`;\n}\n\nfunction totalCostOf(agents: RunAgent[]) {\n  let total = 0;\n  for (const agent of agents) {\n    total += costOf(agent);\n  }\n  return total;\n}\n\nexport default function Agent05() {\n  const { schedule, clearAll } = useTimeouts();\n  const { ref, isInView, isAutoplaying, state, act, pressed, restart } =\n    useSession(SESSION);\n  const { run, attempts } = state;\n  const agents = run.agents;\n  const message = useAgentAnnouncement(agents, announcementOf);\n  const totalCost = totalCostOf(agents);\n  const isEnded = run.endedAt !== undefined;\n  const canReplay = isEnded && !isAutoplaying;\n\n  function approve(approvalId: string) {\n    act({ type: 'approve', approvalId });\n  }\n\n  function retry(agentId: string) {\n    act({ type: 'retry', agentId });\n    schedule(() => act({ type: 'retried', agentId }), RETRY_MS);\n  }\n\n  function replay() {\n    clearAll();\n    restart();\n  }\n\n  useKeptHeight(ref);\n\n  return (\n    <div\n      ref={ref}\n      className='flex min-h-[35rem] w-full max-w-[34rem] flex-col gap-2'\n    >\n      <section\n        aria-label='Who started whom'\n        className='bg-card hairline-ring ring-border flex flex-col gap-2 rounded-xl p-2 shadow-[0_1px_2px_rgb(0_0_0/0.04)]'\n      >\n        <header className='flex items-baseline justify-between gap-3 px-2 pt-1.5'>\n          <div className='min-w-0'>\n            <h3 className='truncate text-sm font-medium'>{run.title}</h3>\n            <p className='text-muted-foreground text-xs'>\n              {countOf(agents.length, 'agent')}, arrows to move and fold\n            </p>\n          </div>\n          <p className='shrink-0 text-[13px] font-medium'>\n            <CostFigure usd={totalCost} />\n          </p>\n        </header>\n        <ClockGate isActive={isInView}>\n          <AgentTree\n            agents={agents}\n            approvals={run.approvals}\n            attempts={attempts}\n            onApprove={approve}\n            onRetry={retry}\n            pressedApproveId={pressedIdOf(pressed, 'approve', WRITER.id)}\n            pressedRetryId={pressedIdOf(pressed, 'retry', PAGE_READER.id)}\n            label={`Agents on the ${run.title}`}\n          />\n        </ClockGate>\n      </section>\n      <div className='flex h-7 items-center justify-between gap-2 px-1'>\n        <span className='text-muted-foreground text-xs'>\n          Costs are illustrative\n        </span>\n        <ReplayButton isShown={canReplay} onReplay={replay} />\n      </div>\n      <LiveRegion message={spokenUnless(isAutoplaying, message)} />\n    </div>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/agent/agent-05.tsx"
    },
    {
      "path": "registry/ndeko/components/agent/_parts/agent-actions.tsx",
      "content": "'use client';\n\nimport { AnimatePresence, motion } from 'motion/react';\n\nimport { usePrefersReducedMotion } from '@/registry/ndeko/hooks/use-prefers-reduced-motion';\nimport type { ApprovalRequest, RunAgent } from '@/registry/ndeko/lib/agent-run';\nimport {\n  pressedQuietOf,\n  pressedSolidOf,\n} from '@/registry/ndeko/lib/demonstration';\nimport { CheckIcon, RotateCcwIcon } from '@/registry/ndeko/lib/icons';\nimport { rowPresenceOf } from '@/registry/ndeko/lib/motion-presets';\nimport { Button } from '@/registry/ndeko/ui/button';\nimport { Kbd } from '@/registry/ndeko/ui/kbd';\n\nexport function pendingApprovalOf(\n  approvals: ApprovalRequest[],\n  agentId: string\n) {\n  return approvals.find((approval) => {\n    const isForAgent = approval.agentId === agentId;\n    const isPending = approval.status === 'pending';\n    return isForAgent && isPending;\n  });\n}\n\nexport function approvedApprovalOf(\n  approvals: ApprovalRequest[],\n  agentId: string\n) {\n  return approvals.find((approval) => {\n    const isForAgent = approval.agentId === agentId;\n    const isApproved = approval.status === 'approved';\n    return isForAgent && isApproved;\n  });\n}\n\nfunction tabIndexOf(hasKeyHints: boolean) {\n  if (hasKeyHints) {\n    return -1;\n  }\n  return undefined;\n}\n\nconst KEY_HINT = 'ms-0.5 hidden group-focus-visible/row:inline-flex';\n\ntype AgentActionsProps = {\n  agent: RunAgent;\n  approvals: ApprovalRequest[];\n  onApprove?: (approvalId: string) => void;\n  onRetry?: (agentId: string) => void;\n  /**\n   * Inside a tree the row keeps the focus and A or R decide: the buttons leave the tab order and\n   * show their key while the row, marked `group/row`, has visible focus.\n   */\n  hasKeyHints?: boolean;\n  /** Called after Approve or Retry, once the pressed button is about to leave. */\n  onDecided?: (agentId: string) => void;\n  /** Shows Approve held down, for a demonstration that presses it. */\n  isApprovePressed?: boolean;\n  /** Shows Retry held down, for a demonstration that presses it. */\n  isRetryPressed?: boolean;\n};\n\nexport function AgentActions({\n  agent,\n  approvals,\n  onApprove,\n  onRetry,\n  hasKeyHints = false,\n  onDecided,\n  isApprovePressed = false,\n  isRetryPressed = false,\n}: AgentActionsProps) {\n  const shouldReduceMotion = usePrefersReducedMotion();\n  const rowPresence = rowPresenceOf(shouldReduceMotion);\n  const pending = pendingApprovalOf(approvals, agent.id);\n  const approved = approvedApprovalOf(approvals, agent.id);\n  const isFailed = agent.status === 'failed';\n  const canRetry = isFailed && onRetry !== undefined;\n  const isNotPending = pending === undefined;\n  const showsTrace = approved !== undefined && isNotPending;\n  const buttonTabIndex = tabIndexOf(hasKeyHints);\n\n  function approve(event: React.MouseEvent) {\n    event.stopPropagation();\n    if (pending) {\n      onApprove?.(pending.id);\n      onDecided?.(agent.id);\n    }\n  }\n\n  function retry(event: React.MouseEvent) {\n    event.stopPropagation();\n    onRetry?.(agent.id);\n    onDecided?.(agent.id);\n  }\n\n  return (\n    <AnimatePresence initial={false}>\n      {pending && (\n        <motion.div key='approve' className='overflow-hidden' {...rowPresence}>\n          <div className='flex flex-wrap items-center gap-x-3 gap-y-1.5 pt-1.5'>\n            <p className='text-foreground/85 min-w-0 text-xs leading-5'>\n              {pending.title}\n            </p>\n            <Button\n              type='button'\n              size='sm'\n              tabIndex={buttonTabIndex}\n              onClick={approve}\n              aria-label={`Approve: ${pending.title}`}\n              className={pressedSolidOf(isApprovePressed)}\n            >\n              <CheckIcon />\n              Approve\n              {hasKeyHints && <Kbd className={KEY_HINT}>A</Kbd>}\n            </Button>\n          </div>\n        </motion.div>\n      )}\n      {canRetry && (\n        <motion.div key='retry' className='overflow-hidden' {...rowPresence}>\n          <div className='pt-1.5'>\n            <Button\n              type='button'\n              variant='outline'\n              size='sm'\n              tabIndex={buttonTabIndex}\n              onClick={retry}\n              aria-label={`Retry ${agent.name}`}\n              className={pressedQuietOf(isRetryPressed)}\n            >\n              <RotateCcwIcon />\n              Retry\n              {hasKeyHints && <Kbd className={KEY_HINT}>R</Kbd>}\n            </Button>\n          </div>\n        </motion.div>\n      )}\n      {showsTrace && (\n        <motion.p\n          key='approved'\n          className='text-muted-foreground overflow-hidden text-xs'\n          {...rowPresence}\n        >\n          <span className='flex items-center gap-1.5 pt-1'>\n            <CheckIcon className='text-success-foreground size-3.5 shrink-0' />\n            Approved by you\n          </span>\n        </motion.p>\n      )}\n    </AnimatePresence>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/agent/_parts/agent-actions.tsx"
    },
    {
      "path": "registry/ndeko/components/agent/_parts/agent-clock.tsx",
      "content": "'use client';\n\nimport type { RunAgent } from '@/registry/ndeko/lib/agent-run';\nimport { LiveDuration } from '@/registry/ndeko/ui/live-duration';\n\nexport function isLiveAgent(agent: RunAgent) {\n  const isRunning = agent.status === 'running';\n  const isWaiting = agent.status === 'waiting';\n  return isRunning || isWaiting;\n}\n\n/** A duration that ticks keeps its width, so nothing beside it shifts ten times a second. */\nexport function AgentClock({ agent }: { agent: RunAgent }) {\n  const hasStarted = agent.startedAt !== undefined;\n  if (!hasStarted) {\n    return <span>Not started</span>;\n  }\n  return (\n    <LiveDuration\n      item={agent}\n      isRunning={isLiveAgent(agent)}\n      className='inline-block min-w-8'\n    />\n  );\n}\n",
      "type": "registry:component",
      "target": "components/agent/_parts/agent-clock.tsx"
    },
    {
      "path": "registry/ndeko/components/agent/_parts/agent-status.tsx",
      "content": "'use client';\n\nimport { WordRoll } from '@/components/core/word-roll';\nimport type {\n  AgentStatus as AgentState,\n  RunAgent,\n} from '@/registry/ndeko/lib/agent-run';\nimport {\n  CircleCheckIcon,\n  CircleXIcon,\n  HandIcon,\n} from '@/registry/ndeko/lib/icons';\nimport { cn } from '@/registry/ndeko/lib/utils';\nimport { Swap } from '@/registry/ndeko/ui/chat-motion';\n\nexport const AGENT_STATUS_WORD: Record<AgentState, string> = {\n  idle: 'Idle',\n  running: 'Running',\n  waiting: 'Waiting on you',\n  failed: 'Failed',\n  done: 'Done',\n};\n\nexport const AGENT_STATES: AgentState[] = [\n  'idle',\n  'running',\n  'waiting',\n  'failed',\n  'done',\n];\n\nconst STATUS_WORDS = AGENT_STATES.map((state) => AGENT_STATUS_WORD[state]);\n\nconst DOT_TONE: Record<AgentState, string> = {\n  idle: 'bg-muted-foreground/45',\n  running: 'bg-info',\n  waiting: 'bg-warning',\n  failed: 'bg-destructive',\n  done: 'bg-success',\n};\n\nconst LABEL_TONE: Record<AgentState, string> = {\n  idle: 'text-muted-foreground',\n  running: 'text-foreground',\n  waiting: 'text-warning-foreground',\n  failed: 'text-destructive-ink',\n  done: 'text-muted-foreground',\n};\n\nconst DOT_SIZE = {\n  sm: 'size-1.5',\n  md: 'size-2',\n  lg: 'size-2.5',\n} as const;\n\nconst MARK_SIZE = {\n  sm: 'size-3',\n  md: 'size-3.5',\n  lg: 'size-4',\n} as const;\n\nexport type AgentStatusSize = keyof typeof DOT_SIZE;\n\nconst RING_TIMING: React.CSSProperties = { animationDuration: '2s' };\n\n/** A piece lets this many dots pulse at once. Past it, a running agent is told by its colour and its word. */\nexport const MAX_PULSING = 3;\n\n/** The running agents whose dot may pulse: the first ones, in the order given. */\nexport function pulsingIdsOf(\n  agents: Pick<RunAgent, 'id' | 'status'>[],\n  limit = MAX_PULSING\n) {\n  const running = agents.filter((agent) => agent.status === 'running');\n  const pulsing = running.slice(0, limit);\n  const ids = pulsing.map((agent) => agent.id);\n  return new Set(ids);\n}\n\nfunction reservedLabelsOf(shouldReserve: boolean) {\n  if (shouldReserve) {\n    return STATUS_WORDS;\n  }\n  return undefined;\n}\n\ntype AgentStatusDotProps = {\n  status: AgentState;\n  size?: AgentStatusSize;\n  /** Without a visible word next to it, the dot says its state to screen readers. */\n  isLabelled?: boolean;\n  /** Holds a running dot still: where three others already pulse, or where nothing is really running. */\n  isStill?: boolean;\n  className?: string;\n};\n\n/**\n * A running dot sends a soft ring out: one CSS keyframe on transform and opacity, which the theme\n * stops under reduced motion. Every other state holds still and is told by its colour.\n */\nexport function AgentStatusDot({\n  status,\n  size = 'md',\n  isLabelled = false,\n  isStill = false,\n  className,\n}: AgentStatusDotProps) {\n  const isRunning = status === 'running';\n  const isPulsing = isRunning && !isStill;\n  const word = AGENT_STATUS_WORD[status];\n\n  return (\n    <span\n      data-status={status}\n      className={cn('relative inline-flex shrink-0', DOT_SIZE[size], className)}\n    >\n      {isPulsing && (\n        <span\n          aria-hidden='true'\n          style={RING_TIMING}\n          className='bg-info/40 absolute inset-0 animate-ping rounded-full motion-reduce:hidden'\n        />\n      )}\n      <span\n        aria-hidden='true'\n        className={cn(\n          'relative size-full rounded-full transition-colors duration-200',\n          DOT_TONE[status]\n        )}\n      />\n      {isLabelled && <span className='sr-only'>{word}</span>}\n    </span>\n  );\n}\n\ntype StatusGlyphProps = {\n  status: AgentState;\n  size: AgentStatusSize;\n  isStill: boolean;\n};\n\nfunction StatusGlyph({ status, size, isStill }: StatusGlyphProps) {\n  const box = MARK_SIZE[size];\n  if (status === 'waiting') {\n    return <HandIcon className={cn('text-warning-foreground', box)} />;\n  }\n  if (status === 'failed') {\n    return <CircleXIcon className={cn('text-destructive-ink', box)} />;\n  }\n  if (status === 'done') {\n    return <CircleCheckIcon className={cn('text-success-foreground', box)} />;\n  }\n  return (\n    <span className={cn('flex items-center justify-center', box)}>\n      <AgentStatusDot status={status} size={size} isStill={isStill} />\n    </span>\n  );\n}\n\ntype AgentStatusMarkProps = {\n  status: AgentState;\n  size?: AgentStatusSize;\n  isStill?: boolean;\n  className?: string;\n};\n\n/**\n * The mark of a state, drawn so it reads without its colour: a dot while the agent is idle or runs,\n * a hand while it waits, a cross when it failed, a check when it ended. It swaps when the state changes.\n */\nexport function AgentStatusMark({\n  status,\n  size = 'md',\n  isStill = false,\n  className,\n}: AgentStatusMarkProps) {\n  const box = MARK_SIZE[size];\n\n  return (\n    <span\n      data-status={status}\n      aria-hidden='true'\n      className={cn('inline-flex shrink-0', box, className)}\n    >\n      <Swap swapKey={status} className={box}>\n        <StatusGlyph status={status} size={size} isStill={isStill} />\n      </Swap>\n    </span>\n  );\n}\n\nexport type AgentStatusProps = {\n  status: AgentState;\n  /** Leave the word out where space is short: a dot then carries it for screen readers. */\n  showLabel?: boolean;\n  /** Keeps the width of the longest word, so the row beside it never shifts. */\n  reserveWidth?: boolean;\n  size?: AgentStatusSize;\n  /** Holds a running mark still: a piece lets three pulse at most. */\n  isStill?: boolean;\n  className?: string;\n};\n\nexport function AgentStatus({\n  status,\n  showLabel = true,\n  reserveWidth = false,\n  size = 'md',\n  isStill = false,\n  className,\n}: AgentStatusProps) {\n  const word = AGENT_STATUS_WORD[status];\n  const labels = reservedLabelsOf(reserveWidth);\n\n  if (!showLabel) {\n    return (\n      <AgentStatusDot\n        status={status}\n        size={size}\n        isLabelled\n        isStill={isStill}\n        className={className}\n      />\n    );\n  }\n\n  return (\n    <span\n      className={cn(\n        'inline-flex items-center gap-1 text-xs transition-colors duration-200',\n        LABEL_TONE[status],\n        className\n      )}\n    >\n      <AgentStatusMark status={status} size={size} isStill={isStill} />\n      <WordRoll text={word} labels={labels} />\n    </span>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/agent/_parts/agent-status.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/cost-figure.tsx",
      "content": "'use client';\n\nimport { MotionNumber } from '@/components/core/motion-number';\nimport { cn } from '@/registry/ndeko/lib/utils';\n\nconst SMALL_COST: Intl.NumberFormatOptions = {\n  style: 'currency',\n  currency: 'USD',\n  minimumFractionDigits: 3,\n  maximumFractionDigits: 3,\n};\n\nconst LARGE_COST: Intl.NumberFormatOptions = {\n  style: 'currency',\n  currency: 'USD',\n  minimumFractionDigits: 2,\n  maximumFractionDigits: 2,\n};\n\n/** Matches `formatCost`: three decimals under a dollar, two above. */\nexport function costFormatOf(usd: number) {\n  if (usd < 1) {\n    return SMALL_COST;\n  }\n  return LARGE_COST;\n}\n\ntype CostFigureProps = {\n  usd: number;\n  className?: string;\n};\n\nexport function CostFigure({ usd, className }: CostFigureProps) {\n  const format = costFormatOf(usd);\n\n  return (\n    <MotionNumber\n      value={usd}\n      format={format}\n      className={cn('tabular-nums', className)}\n    />\n  );\n}\n",
      "type": "registry:component",
      "target": "components/agent/_parts/cost-figure.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"
    },
    {
      "path": "registry/ndeko/components/agent/_parts/demo.ts",
      "content": "/** A control that waits on a person is pressed by the demonstration after this long. */\nexport const DECIDE_AFTER_MS = 2500;\n\n/** The row a demonstration presses, when the control it holds down is the one named by `key`. */\nexport function pressedIdOf(pressed: string | null, key: string, id: string) {\n  if (pressed === key) {\n    return id;\n  }\n  return null;\n}\n",
      "type": "registry:component",
      "target": "components/agent/_parts/demo.ts"
    },
    {
      "path": "registry/ndeko/components/agent/_parts/focus-later.ts",
      "content": "/** Moves focus once React has committed the change that moved, replaced or removed the focused control. */\nexport function focusLater(\n  root: React.RefObject<HTMLElement | null>,\n  selector: string\n) {\n  requestAnimationFrame(() => {\n    const target = root.current?.querySelector<HTMLElement>(selector);\n    target?.focus();\n  });\n}\n",
      "type": "registry:component",
      "target": "components/agent/_parts/focus-later.ts"
    },
    {
      "path": "registry/ndeko/components/agent/_parts/use-agent-announcement.ts",
      "content": "'use client';\n\nimport { useState } from 'react';\n\nimport type { RunAgent } from '@/registry/ndeko/lib/agent-run';\n\nfunction changedAgentOf(previous: RunAgent[], next: RunAgent[]) {\n  return next.find((agent) => {\n    const before = previous.find((candidate) => candidate.id === agent.id);\n    if (!before) {\n      return true;\n    }\n    return before.status !== agent.status;\n  });\n}\n\nexport function useAgentAnnouncement(\n  agents: RunAgent[],\n  describe: (agent: RunAgent) => string\n) {\n  const [seenAgents, setSeenAgents] = useState(agents);\n  const [message, setMessage] = useState('');\n\n  const hasNewAgents = seenAgents !== agents;\n  if (hasNewAgents) {\n    const changed = changedAgentOf(seenAgents, agents);\n    setSeenAgents(agents);\n    if (changed) {\n      setMessage(describe(changed));\n    }\n  }\n\n  return message;\n}\n",
      "type": "registry:component",
      "target": "components/agent/_parts/use-agent-announcement.ts"
    }
  ],
  "categories": [
    "components",
    "agent"
  ],
  "type": "registry:component"
}