{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "agent-status",
  "type": "registry:component",
  "title": "Agent status",
  "description": "A runtime task summary chip with an optional tray for every task in the thread.",
  "dependencies": [
    "@assistant-ui/react@^0.15.23"
  ],
  "registryDependencies": [
    "https://r.assistant-ui.com/elements-agent-status.json",
    "https://r.assistant-ui.com/elements-task-card.json",
    "https://r.assistant-ui.com/elements-task.json",
    "popover"
  ],
  "files": [
    {
      "type": "registry:component",
      "path": "components/assistant-ui/elements/agent-status.aui.tsx",
      "sourcePath": "packages/ui/src/components/react/assistant-ui/elements/agent-status.aui.tsx",
      "content": "\"use client\";\n\nimport { useAuiState, type TaskState } from \"@assistant-ui/react\";\nimport { ChevronDownIcon } from \"lucide-react\";\nimport { type FC, useMemo, useState } from \"react\";\nimport {\n  Popover,\n  PopoverContent,\n  PopoverTrigger,\n} from \"@/components/ui/popover\";\nimport { cn } from \"@/lib/utils\";\nimport {\n  AgentStatus as AgentStatusBase,\n  type AgentState,\n} from \"./agent-status\";\nimport { mono } from \"./surfaces\";\nimport { TaskStateIcon } from \"./task-card\";\nimport {\n  formatElapsed,\n  TASK_PAGE_SIZE,\n  taskLabel,\n  taskMeta,\n  taskStateOf,\n  useTaskElapsed,\n} from \"../utils/task\";\n\nexport type TaskSummary = {\n  readonly total: number;\n  readonly running: number;\n  readonly waiting: number;\n  readonly failed: number;\n  readonly startedAt: number | undefined;\n  readonly runningLabel: string | undefined;\n};\n\nconst summarize = (tasks: readonly TaskState[]): TaskSummary => {\n  let running = 0;\n  let waiting = 0;\n  let failed = 0;\n  let startedAt: number | undefined;\n  let runningLabel: string | undefined;\n  for (const task of tasks) {\n    const state = taskStateOf(task.status, task.isError);\n    if (state === \"working\") {\n      running += 1;\n      runningLabel ??= taskLabel(task.toolName, task.args);\n      const taskStartedAt = task.timing?.startedAt;\n      if (\n        taskStartedAt !== undefined &&\n        (startedAt === undefined || taskStartedAt < startedAt)\n      ) {\n        startedAt = taskStartedAt;\n      }\n    } else if (state === \"waiting\") {\n      waiting += 1;\n    } else if (state === \"failed\") {\n      failed += 1;\n    }\n  }\n  return {\n    total: tasks.length,\n    running,\n    waiting,\n    failed,\n    startedAt,\n    runningLabel,\n  };\n};\n\nexport const useTaskSummary = (): TaskSummary => {\n  const tasks = useAuiState((s) => s.thread.tasks);\n  return useMemo(() => summarize(tasks), [tasks]);\n};\n\nconst plural = (count: number, noun: string) =>\n  `${count} ${count === 1 ? noun : `${noun}s`}`;\n\nexport const summaryState = (summary: TaskSummary): AgentState => {\n  if (summary.running > 0) return \"working\";\n  if (summary.waiting > 0) return \"waiting\";\n  return summary.failed > 0 ? \"failed\" : \"done\";\n};\n\nexport const summaryLabel = (summary: TaskSummary) => {\n  if (summary.running === 1 && summary.runningLabel !== undefined) {\n    return summary.runningLabel;\n  }\n  if (summary.running > 0) {\n    return `${summary.running} of ${plural(summary.total, \"task\")} running`;\n  }\n  if (summary.waiting > 0) {\n    return `${plural(summary.waiting, \"task\")} waiting for input`;\n  }\n  if (summary.failed > 0) {\n    return `${plural(summary.total, \"task\")} done, ${summary.failed} failed`;\n  }\n  return `${plural(summary.total, \"task\")} done`;\n};\n\nexport const AgentStatus: FC<{ className?: string }> = ({ className }) => {\n  const summary = useTaskSummary();\n  const elapsedMs = useTaskElapsed(\n    summary.startedAt === undefined\n      ? undefined\n      : { startedAt: summary.startedAt },\n    summary.running > 0,\n  );\n  if (summary.running === 0 && summary.waiting === 0) return null;\n\n  return (\n    <AgentStatusBase\n      className={className}\n      state={summaryState(summary)}\n      label={summaryLabel(summary)}\n      elapsed={elapsedMs === undefined ? undefined : formatElapsed(elapsedMs)}\n    />\n  );\n};\n\nconst TaskTrayItem: FC<{ task: TaskState }> = ({ task }) => {\n  const state = taskStateOf(task.status, task.isError);\n  const meta = taskMeta(task.args);\n  const elapsedMs = useTaskElapsed(\n    task.timing,\n    task.status.type === \"running\" || task.status.type === \"requires-action\",\n  );\n\n  return (\n    <li\n      data-slot=\"aui_task-tray-item\"\n      data-state={state}\n      className=\"flex items-center gap-2.5 rounded-lg py-2 pe-2.5 text-[13px]\"\n      style={{ paddingInlineStart: `${0.625 + task.depth * 0.75}rem` }}\n    >\n      <TaskStateIcon state={state} />\n      <span className=\"sr-only\">{state}</span>\n      <span className=\"min-w-0 flex-1 truncate\">\n        {taskLabel(task.toolName, task.args)}\n      </span>\n      {meta !== undefined && (\n        <span\n          className={cn(mono, \"text-foreground/35 max-w-24 shrink-0 truncate\")}\n        >\n          {meta}\n        </span>\n      )}\n      {elapsedMs !== undefined && (\n        <span className={cn(mono, \"text-foreground/30 shrink-0 tabular-nums\")}>\n          {formatElapsed(elapsedMs)}\n        </span>\n      )}\n    </li>\n  );\n};\n\nexport const TaskTray: FC<{ className?: string }> = ({ className }) => {\n  const tasks = useAuiState((s) => s.thread.tasks);\n  const summary = useMemo(() => summarize(tasks), [tasks]);\n  const elapsedMs = useTaskElapsed(\n    summary.startedAt === undefined\n      ? undefined\n      : { startedAt: summary.startedAt },\n    summary.running > 0,\n  );\n  const [open, setOpen] = useState(false);\n  const [visible, setVisible] = useState(TASK_PAGE_SIZE);\n  const firstTask = tasks[0];\n  const listKey =\n    firstTask === undefined ? \"\" : `${firstTask.messageId}:${firstTask.id}`;\n  const [seenListKey, setSeenListKey] = useState(listKey);\n  if (seenListKey !== listKey) {\n    setSeenListKey(listKey);\n    setVisible(TASK_PAGE_SIZE);\n  }\n  if (summary.total === 0 && open) setOpen(false);\n  if (summary.total === 0) return null;\n  const hidden = Math.max(0, tasks.length - visible);\n\n  return (\n    <Popover\n      open={open}\n      onOpenChange={(next) => {\n        setOpen(next);\n        if (!next) setVisible(TASK_PAGE_SIZE);\n      }}\n    >\n      <PopoverTrigger\n        className={cn(\n          \"focus-visible:ring-ring/50 cursor-pointer appearance-none rounded-full border-0 bg-transparent p-0 text-start focus-visible:ring-[3px] focus-visible:outline-none\",\n          className,\n        )}\n      >\n        <AgentStatusBase\n          state={summaryState(summary)}\n          label={summaryLabel(summary)}\n          elapsed={\n            elapsedMs === undefined ? undefined : formatElapsed(elapsedMs)\n          }\n          trailing={\n            <ChevronDownIcon\n              className={cn(\n                \"size-3 transition-transform duration-200 motion-reduce:transition-none\",\n                open && \"rotate-180\",\n              )}\n            />\n          }\n        />\n      </PopoverTrigger>\n      <PopoverContent align=\"end\" className=\"w-80 p-1\">\n        <ul\n          data-slot=\"aui_task-tray\"\n          aria-label=\"Tasks\"\n          className=\"flex max-h-80 flex-col overflow-y-auto\"\n        >\n          {tasks.slice(0, visible).map((task, index) => (\n            <TaskTrayItem key={`${index}:${task.id}`} task={task} />\n          ))}\n          {hidden > 0 && (\n            <li className=\"flex\">\n              <button\n                type=\"button\"\n                data-slot=\"aui_task-tray-more\"\n                onClick={() => setVisible((count) => count + TASK_PAGE_SIZE)}\n                className=\"text-muted-foreground hover:text-foreground px-2.5 py-2 text-xs transition-colors\"\n              >\n                Show {Math.min(hidden, TASK_PAGE_SIZE)} more\n              </button>\n            </li>\n          )}\n        </ul>\n      </PopoverContent>\n    </Popover>\n  );\n};\n"
    }
  ]
}