{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "agent-1",
  "title": "Agent 1: live agent run",
  "author": "Ballmac <https://ui.ballmac.com>",
  "description": "One AI agent run as it happens: the task and status, the plan beside it, every note, tool call and approval request in order, approvals that pause the run, the result with copy, and a follow-up box.",
  "dependencies": [
    "lucide-react"
  ],
  "registryDependencies": [
    "utils",
    "https://ui.ballmac.com/r/agent-plan.json",
    "https://ui.ballmac.com/r/approval-card.json",
    "https://ui.ballmac.com/r/button.json",
    "https://ui.ballmac.com/r/skeleton.json",
    "https://ui.ballmac.com/r/state-view.json",
    "https://ui.ballmac.com/r/tool-call-card.json"
  ],
  "files": [
    {
      "path": "registry/ballmac/components/blocks/agent-1/agent-1.tsx",
      "content": "// Ballmac UI: Agent 1. https://ui.ballmac.com/blocks/agent-1\n\"use client\"\n\nimport * as React from \"react\"\nimport { Bot, Check, Copy, RotateCw, SendHorizontal, Square } from \"lucide-react\"\n\nimport { AgentPlan, type PlanStep } from \"@/components/ballmac/agent-plan\"\nimport { ApprovalCard, type ApprovalDetail, type ApprovalRisk, type ApprovalStatus } from \"@/components/ballmac/approval-card\"\nimport { Button } from \"@/components/ballmac/button\"\nimport { Skeleton } from \"@/components/ballmac/skeleton\"\nimport { frameProps, StateView, type StateProps } from \"@/components/ballmac/state-view\"\nimport { ToolCallCard } from \"@/components/ballmac/tool-call-card\"\nimport { cn } from \"@/lib/utils\"\n\ntype Agent1Event =\n  | { id: string; type: \"message\"; text: string }\n  | { id: string; type: \"tool\"; name: string; status: \"pending\" | \"running\" | \"success\" | \"error\"; description?: string; duration?: number; input?: unknown; result?: unknown }\n  | { id: string; type: \"approval\"; title: string; description?: string; risk?: ApprovalRisk; details?: ApprovalDetail[]; preview?: React.ReactNode; status: ApprovalStatus }\n\ntype Agent1RunStatus = \"running\" | \"waiting\" | \"done\" | \"failed\" | \"stopped\"\n\ntype Agent1Run = {\n  /** What the agent was asked to do. */\n  task: string\n  /** The agent's name. */\n  agent?: string\n  status: Agent1RunStatus\n  /** Already written for people (\"2 min 14 s\"). */\n  elapsed?: string\n  /** The plan, as Agent Plan steps. */\n  steps: PlanStep[]\n  /** What happened, oldest first: messages, tool calls and approval requests. */\n  events: Agent1Event[]\n  /** The final answer, once the run is done. */\n  output?: string\n  /** Tokens used so far, and the cost already formatted (\"$0.42\"). */\n  usage?: { tokens: number; cost?: string }\n}\n\ntype Agent1Props = Omit<React.ComponentProps<\"section\">, \"title\"> &\n  StateProps & {\n    /** The run to show. */\n    run?: Agent1Run\n    /** Locale for numbers. */\n    locale?: string\n    /** Called when an approval request is approved. The block marks it approved straight away. */\n    onApprove?: (eventId: string) => void\n    /** Called when an approval request is denied. */\n    onDeny?: (eventId: string) => void\n    /** Called by Stop while the run is going or waiting. */\n    onStop?: () => void\n    /** Called by \"Run again\" after a run failed or was stopped. */\n    onRunAgain?: () => void\n    /** Called with a follow-up instruction. Without it the composer is hidden. */\n    onFollowUp?: (text: string) => void\n  }\n\nconst sample: Agent1Run = {\n  task: \"Refund the duplicate charge on order 4821 and tell the customer\",\n  agent: \"Support agent\",\n  status: \"waiting\",\n  elapsed: \"48 s\",\n  usage: { tokens: 18420, cost: \"$0.06\" },\n  steps: [\n    { id: \"1\", title: \"Find the order and its payments\", status: \"done\", duration: \"6 s\" },\n    { id: \"2\", title: \"Confirm the second charge is a duplicate\", status: \"done\", duration: \"11 s\" },\n    { id: \"3\", title: \"Refund the duplicate charge\", status: \"running\", description: \"Waiting for your approval\" },\n    { id: \"4\", title: \"Email the customer\", status: \"pending\" },\n  ],\n  events: [\n    { id: \"e1\", type: \"message\", text: \"I’ll look up order 4821 and check both charges before refunding anything.\" },\n    { id: \"e2\", type: \"tool\", name: \"orders.get\", status: \"success\", duration: 420, description: \"Order 4821\", input: { id: 4821 }, result: { customer: \"jordan@acme.com\", total: \"$129.00\", payments: 2 } },\n    { id: \"e3\", type: \"tool\", name: \"payments.list\", status: \"success\", duration: 610, description: \"Two charges of $129.00, 40 seconds apart\", input: { order: 4821 }, result: [{ id: \"ch_1\", amount: 12900 }, { id: \"ch_2\", amount: 12900 }] },\n    { id: \"e4\", type: \"message\", text: \"The second charge was made 40 seconds after the first with the same card and amount. It is a duplicate.\" },\n    {\n      id: \"e5\",\n      type: \"approval\",\n      title: \"Refund $129.00 to the customer’s card\",\n      description: \"Refunds charge ch_2. The customer keeps the order.\",\n      risk: \"high\",\n      details: [\n        { label: \"Charge\", value: \"ch_2\" },\n        { label: \"Amount\", value: \"$129.00\" },\n      ],\n      status: \"pending\",\n    },\n  ],\n}\n\nconst statusCopy: Record<Agent1RunStatus, { label: string; tone: string }> = {\n  running: { label: \"Working\", tone: \"bg-chart-1\" },\n  waiting: { label: \"Waiting for you\", tone: \"bg-chart-3\" },\n  done: { label: \"Done\", tone: \"bg-chart-2\" },\n  failed: { label: \"Failed\", tone: \"bg-destructive\" },\n  stopped: { label: \"Stopped\", tone: \"bg-muted-foreground\" },\n}\n\nfunction RunSkeleton() {\n  return (\n    <div className=\"grid gap-6 lg:grid-cols-[18rem_1fr]\" aria-hidden=\"true\">\n      <div className=\"grid content-start gap-3 rounded-xl border p-4\">\n        {[0, 1, 2, 3].map((i) => (\n          <div key={i} className=\"flex items-center gap-3\">\n            <Skeleton className=\"size-5 rounded-full\" />\n            <Skeleton className=\"h-3.5 flex-1\" />\n          </div>\n        ))}\n      </div>\n      <div className=\"grid content-start gap-3\">\n        <Skeleton className=\"h-12 w-4/5 rounded-xl\" />\n        <Skeleton className=\"h-14 rounded-xl\" />\n        <Skeleton className=\"h-14 rounded-xl\" />\n        <Skeleton className=\"h-32 rounded-xl\" />\n      </div>\n    </div>\n  )\n}\n\n/**\n * One agent run, live: the task and its status, the plan on the side, and everything the agent did in order (its notes, each tool\n * call with input and result, and approval requests that pause the run until someone decides), then the answer and a follow-up box.\n */\nfunction Agent1({\n  run = sample,\n  locale = \"en-US\",\n  onApprove,\n  onDeny,\n  onStop,\n  onRunAgain,\n  onFollowUp,\n  state = \"ready\",\n  onRetry,\n  emptyState,\n  errorState,\n  className,\n  ...props\n}: Agent1Props) {\n  const [decisions, setDecisions] = React.useState<Record<string, ApprovalStatus>>({})\n  const [draft, setDraft] = React.useState(\"\")\n  const [copied, setCopied] = React.useState(false)\n  const id = React.useId()\n  const status = statusCopy[run.status]\n  const active = run.status === \"running\" || run.status === \"waiting\"\n  const nf = new Intl.NumberFormat(locale)\n  React.useEffect(() => {\n    if (!copied) return\n    const t = setTimeout(() => setCopied(false), 1600)\n    return () => clearTimeout(t)\n  }, [copied])\n\n  return (\n    <section data-slot=\"agent-1\" data-state={state} className={cn(\"mx-auto w-full max-w-6xl px-4 py-8 sm:px-6\", className)} {...frameProps(props)}>\n      <header className=\"flex flex-wrap items-start gap-4 border-b pb-5\">\n        <span aria-hidden=\"true\" className=\"bg-muted flex size-10 shrink-0 items-center justify-center rounded-xl border\">\n          <Bot className=\"size-5\" />\n        </span>\n        <div className=\"min-w-0 flex-1 basis-64\">\n          <p className=\"text-muted-foreground text-xs\">{run.agent ?? \"Agent\"}</p>\n          <h2 id={`${id}-task`} className=\"mt-0.5 text-lg font-semibold tracking-tight text-pretty\">\n            {run.task}\n          </h2>\n          <div className=\"text-muted-foreground mt-2 flex flex-wrap items-center gap-x-4 gap-y-1 text-xs tabular-nums\">\n            <span role=\"status\" className=\"text-foreground inline-flex items-center gap-1.5 font-medium\">\n              <span aria-hidden=\"true\" className={cn(\"size-2 rounded-full\", status.tone)} />\n              {status.label}\n            </span>\n            {run.elapsed ? <span>{run.elapsed}</span> : null}\n            {run.usage ? (\n              <span>\n                {nf.format(run.usage.tokens)} tokens{run.usage.cost ? ` · ${run.usage.cost}` : \"\"}\n              </span>\n            ) : null}\n          </div>\n        </div>\n        <div className=\"flex gap-2\">\n          {active && onStop ? (\n            <Button variant=\"outline\" size=\"sm\" onClick={onStop}>\n              <Square aria-hidden=\"true\" className=\"fill-current\" />\n              Stop\n            </Button>\n          ) : null}\n          {(run.status === \"failed\" || run.status === \"stopped\") && onRunAgain ? (\n            <Button size=\"sm\" onClick={onRunAgain}>\n              <RotateCw aria-hidden=\"true\" />\n              Run again\n            </Button>\n          ) : null}\n        </div>\n      </header>\n\n      <StateView\n        state={state}\n        onRetry={onRetry}\n        label=\"Loading the run\"\n        loading={<RunSkeleton />}\n        empty={{ title: emptyState?.title ?? \"No run yet\", description: emptyState?.description ?? \"Give the agent a task and its plan and progress appear here.\", icon: emptyState?.icon ?? <Bot />, action: emptyState?.action }}\n        error={{ title: errorState?.title ?? \"The run could not be loaded\", ...errorState }}\n        className=\"mt-6\"\n      >\n        <div className=\"grid gap-6 lg:grid-cols-[18rem_1fr]\">\n          <AgentPlan steps={run.steps} title=\"Plan\" className=\"h-fit lg:sticky lg:top-6\" />\n\n          <div className=\"grid min-w-0 content-start gap-3\">\n            <h3 className=\"sr-only\">Activity</h3>\n            <ol aria-label=\"Activity\" className=\"grid gap-3\">\n              {run.events.map((e) => (\n                <li key={e.id} className=\"min-w-0\">\n                  {e.type === \"message\" ? (\n                    <div className=\"flex gap-3\">\n                      <span aria-hidden=\"true\" className=\"bg-muted mt-0.5 flex size-6 shrink-0 items-center justify-center rounded-full border [&_svg]:size-3.5\">\n                        <Bot />\n                      </span>\n                      <p className=\"text-sm leading-relaxed text-pretty\">{e.text}</p>\n                    </div>\n                  ) : e.type === \"tool\" ? (\n                    <ToolCallCard name={e.name} status={e.status} duration={e.duration} input={e.input} result={e.result} description={e.description} />\n                  ) : (\n                    <ApprovalCard\n                      title={e.title}\n                      description={e.description}\n                      risk={e.risk}\n                      details={e.details}\n                      preview={e.preview}\n                      status={decisions[e.id] ?? e.status}\n                      onApprove={() => {\n                        setDecisions((d) => ({ ...d, [e.id]: \"approved\" }))\n                        onApprove?.(e.id)\n                      }}\n                      onDeny={() => {\n                        setDecisions((d) => ({ ...d, [e.id]: \"denied\" }))\n                        onDeny?.(e.id)\n                      }}\n                    />\n                  )}\n                </li>\n              ))}\n            </ol>\n\n            {run.status === \"done\" && run.output ? (\n              <div className=\"bg-card rounded-xl border p-5\">\n                <div className=\"flex items-center justify-between gap-3\">\n                  <h3 className=\"flex items-center gap-2 text-sm font-semibold\">\n                    <Check aria-hidden=\"true\" className=\"text-chart-2 size-4\" />\n                    Result\n                  </h3>\n                  <Button\n                    variant=\"ghost\"\n                    size=\"sm\"\n                    onClick={async () => {\n                      try {\n                        await navigator.clipboard?.writeText(run.output ?? \"\")\n                        setCopied(true)\n                      } catch {}\n                    }}\n                  >\n                    {copied ? <Check aria-hidden=\"true\" /> : <Copy aria-hidden=\"true\" />}\n                    {copied ? \"Copied\" : \"Copy\"}\n                  </Button>\n                </div>\n                <p className=\"mt-3 text-sm leading-relaxed whitespace-pre-wrap text-pretty\">{run.output}</p>\n              </div>\n            ) : null}\n\n            {onFollowUp ? (\n              <form\n                className=\"bg-card focus-within:ring-ring/50 mt-2 flex items-center gap-2 rounded-xl border p-1.5 ps-3 focus-within:ring-[3px]\"\n                onSubmit={(e) => {\n                  e.preventDefault()\n                  const text = draft.trim()\n                  if (!text) return\n                  onFollowUp(text)\n                  setDraft(\"\")\n                }}\n              >\n                <input\n                  value={draft}\n                  onChange={(e) => setDraft(e.target.value)}\n                  aria-label=\"Follow-up instruction\"\n                  aria-describedby={`${id}-task`}\n                  placeholder={active ? \"Add an instruction while it works…\" : \"Ask a follow-up…\"}\n                  className=\"placeholder:text-muted-foreground min-w-0 flex-1 bg-transparent text-sm outline-none\"\n                />\n                <Button type=\"submit\" size=\"icon-sm\" aria-label=\"Send\" disabled={!draft.trim()}>\n                  <SendHorizontal aria-hidden=\"true\" className=\"rtl:-scale-x-100\" />\n                </Button>\n              </form>\n            ) : null}\n          </div>\n        </div>\n      </StateView>\n    </section>\n  )\n}\n\nexport { Agent1, type Agent1Props, type Agent1Run, type Agent1Event, type Agent1RunStatus }\n",
      "type": "registry:component",
      "target": "@components/ballmac/blocks/agent-1/agent-1.tsx"
    }
  ],
  "meta": {
    "schema": 1,
    "tier": "free",
    "tags": [
      "ai",
      "agent",
      "run",
      "tool calls",
      "approval",
      "human in the loop",
      "plan",
      "trace",
      "autonomous"
    ],
    "version": "1.0.0",
    "updated": "2026-10-17",
    "tests": true,
    "ai": {
      "summary": "An agent run view. <Agent1 run={{ task, agent, status: 'running' | 'waiting' | 'done' | 'failed' | 'stopped', elapsed, usage: { tokens, cost }, steps: PlanStep[], events: [{ type: 'message', text } | { type: 'tool', name, status, input, result, duration } | { type: 'approval', title, risk, details, preview, status }], output }} onApprove={(id) => …} onDeny onStop onRunAgain onFollowUp />. Map your agent loop's stream (AI SDK tool calls, LangGraph interrupts) onto events.",
      "whenToUse": [
        "Watching an autonomous agent work and approving its risky steps",
        "The detail page of a background agent task"
      ],
      "whenNotToUse": [
        "A chat conversation (use ai-chat-1 or ai-chat-2)",
        "A list of many runs (use agent-3)",
        "A queue of approvals from many agents (use agent-2)"
      ],
      "composesWith": [
        "agent-2",
        "agent-3",
        "app-shell-1"
      ],
      "a11y": [
        {
          "keys": "Status",
          "action": "The run status is a live status region, so \"Waiting for you\" is announced"
        },
        {
          "keys": "Tab",
          "action": "Reaches each tool call's details toggle and each approval's Approve and Deny"
        },
        {
          "keys": "Enter in the follow-up box",
          "action": "Sends the instruction (onFollowUp)"
        }
      ],
      "customization": [
        "Approvals update in place when pressed; pass the new run from your backend to confirm",
        "preview on an approval takes any node: a diff-viewer, a code-block or an email draft",
        "locale formats the token count"
      ]
    },
    "states": [
      {
        "id": "loading",
        "title": "Loading",
        "example": "agent-1-loading",
        "trigger": "state=\"loading\"",
        "note": "A skeleton of the plan and the activity under the real header."
      },
      {
        "id": "empty",
        "title": "No run yet",
        "example": "agent-1-empty",
        "trigger": "state=\"empty\"",
        "note": "Explains that the plan and progress will appear; emptyState can add a Start button."
      },
      {
        "id": "error",
        "title": "Could not load",
        "example": "agent-1-error",
        "trigger": "state=\"error\"",
        "note": "An alert with Try again (onRetry)."
      }
    ],
    "examples": [
      "agent-1-demo",
      "agent-1-done",
      "agent-1-loading",
      "agent-1-empty",
      "agent-1-error"
    ],
    "url": "https://ui.ballmac.com/components/agent-1"
  },
  "categories": [
    "blocks",
    "agents"
  ],
  "type": "registry:block"
}