Agent status
ProA compact live status pill for a running AI agent: the agent's mark in a live ring, the current activity and an elapsed timer, opening into the steps so far.
- An app header, title bar or composer that needs to show at a glance that an agent is running, what it is doing and for how long.
- Coding, research or browser agents that pause for approval, so the person can approve or deny without opening a full run view.
- Background runs that people check on occasionally: the pill stays small, and the panel shows recent steps and controls on demand.
- Use agent-run or research-run when the run itself is the page and every step, tool call and output needs room.
- Use agent-approvals to review a queue of pending approvals across several agents.
- Use agent-orchestrator or agent-tasks to compare many agents or runs at once.
- Use text-shimmer alone for a single inline working label without a timer, steps or controls.
Installation
Pro source and install commands unlock with a Pro plan.
import { AgentStatus } from "@/components/arc/agent-status/agent-status";import { FileSearch, FilePen, FlaskConical } from "lucide-react"; export function RunHeader({ run, agent }) { return ( <AgentStatus status={run.status} // "working" | "needs-input" | "paused" | "done" | ... startedAt={run.startedAt} agent={{ name: "Claude Sonnet 5.5", mark: <ClaudeMark style={{ color: "#D97757" }} /> }} activity={{ label: "Running tests", icon: FlaskConical, progress: { value: 8, total: 12 } }} steps={[ { id: "read", label: "Read 12 files", icon: FileSearch, status: "done", durationMs: 1900 }, { id: "edit", label: "Edited src/billing/invoice.ts", icon: FilePen, status: "done", durationMs: 2500 }, { id: "tests", label: "Running tests", icon: FlaskConical, status: "active", startedAt: run.stepStartedAt }, { id: "push", label: "Push the branch", status: "pending" }, ]} onPause={() => agent.pause(run.id)} onResume={() => agent.resume(run.id)} onStop={() => agent.cancel(run.id)} onApprove={() => agent.approve(run.id)} onDeny={() => agent.deny(run.id)} onRetry={() => agent.retry(run.id)} onOpenRun={() => router.push(`/runs/${run.id}`)} /> );} // With no props it plays the simulated coding run.<AgentStatus />API reference
2 parts. The first is the root.
AgentStatus
A compact live status pill for a running AI agent, made for an app header or the space above a composer. Collapsed it shows the agent's mark inside a thin live ring, the current activity with a matching icon, and an elapsed timer in tabular numerals. Each new activity rolls in from below while the pill's width follows on a spring. A click expands the pill in place into a small floating panel with the latest steps (icon, text, duration), a thin step progress bar and Pause, Stop and Open run controls; Escape or a press outside closes it. It covers idle (Ready), working, needs input (an accent dot with inline Approve and Deny), paused, done (the ring closes and the mark morphs into a check, then it settles back to Ready), failed (with Retry) and stopped. With no props it plays a simulated coding run.
status"idle" | "working" | "needs-input" | "paused" | "done" | "failed" | "stopped"–The run's state. Leave it out to play the built in simulated coding run (Claude Sonnet 5.5) with working controls.activity{ label: string; icon?: AgentIcon; progress?: { value: number; total: number } }–What the agent is doing now, in the present tense ("Reading 12 files", "Editing src/billing/invoice.ts"). The icon rolls in with the label. Progress ticks in place as "8 of 12" without rolling the label. In needs input it is the request ("Needs your approval to run git push"), in done the outcome appended to "Done in 1m 12s" ("6 files changed"), and in failed the failure ("Tests failed · 2 errors").stepsAgentStep[][]Every step of the run, pending ones included so the progress bar knows the total. Each step has id, label, optional icon, status ("pending" | "active" | "waiting" | "done" | "failed" | "skipped"), startedAt for a live step and durationMs for a finished one. Write active labels in the present tense and finished ones in the past tense ("Running tests" then "Passed 12 of 12 tests"), so the text itself carries the status.startedAtnumber | Date–When the run started. The timer counts from here and leaves out time spent paused or failed, so it never jumps when the run resumes.endedAtnumber | Date–When the run ended. Defaults to the moment the status turned done or stopped.agent{ name: string; mark?: ReactNode }{ name: "Agent" }The model or agent and its brand mark, drawn at 12px inside the live ring. Give a mono mark its brand color, for example the Claude mark in #D97757.onPause() => void–Shows Pause in the panel while working.onResume() => void–Shows Resume in the panel while paused. Pause and Resume share one button, so focus stays put.onStop() => void–Shows Stop in the panel while working or paused.onApprove() => void–Shows Approve in the accent color, inline in the pill and in the panel, while the status is needs-input.onDeny() => void–Shows Deny next to Approve while the status is needs-input.onRetry() => void–Shows Retry in the accent color, inline and in the panel, when the run failed.onOpenRun() => void–Shows an Open run button in the panel for a full log or trace view. It confirms with a check for a moment after it is pressed.settleAfternumber | null4000Milliseconds after done or stopped before the pill settles back to Ready. The panel keeps the last run's steps. Null keeps the result on screen.align"start" | "center" | "end""center"Which edge the panel grows from. Use start or end when the pill sits against a header edge.expandedboolean–Controls the panel. Pair with onExpandedChange.defaultExpandedbooleanfalseOpens the panel on first render when uncontrolled.onExpandedChange(expanded: boolean) => void–Called when the pill is pressed, Escape is pressed or a press lands outside the open panel.maxStepsnumber5How many of the latest steps the panel lists. Older steps slide out at the top as new ones arrive.noticestring–A one-off message for screen readers, such as the result of an action. It joins the automatic status announcement.classNamestring–Class on the root element.useAgentRunSimulation
The scripted runs behind the preview, exported for prototypes and tests. Returns every AgentStatus prop for a run plus replay(). Nothing runs for real: approvals, pushes, searches and Open run only update local state and say so through notice.
scenarioId"coding" | "research""coding"Coding is Claude Sonnet 5.5 fixing a proration bug, ending on an approval to run git push. Research is Gemini 4 Argon comparing vendor pricing; one fetch times out on the first try so Retry can be seen.startDelaynumber900Milliseconds of Ready before the run starts.- Tab
- Moves to the pill, then to the inline Deny and Approve or Retry buttons when they show. With the panel open, Tab moves through Pause or Resume, Stop or Deny, and Open run.
- Enter, Space
- On the pill, opens or closes the panel. On a button, activates it.
- Escape
- Closes the panel from anywhere inside it and returns focus to the pill.
- The pill is a button with aria-expanded and aria-controls pointing at the panel, and an accessible name that reads the agent, the activity, the count and the elapsed time.
- A polite status region announces each new activity and the outcome ("Claude Sonnet 5.5: done in 14 seconds, 6 files changed"), not every tick of a count or the timer. Pass notice to add the result of an action.
- Status never relies on color: every state pairs the ring and glyph with words (Ready, Paused, Needs your approval, Done in, Failed, Stopped after), and steps use present tense while running and past tense once finished.
- The step progress bar is a progressbar with a value text such as "4 of 6 steps finished".
- Pause and Resume, and Approve and the Pause that follows it, share one button, so keyboard focus stays in place. If a focused control disappears with a state change, focus moves to the pill instead of the page.
- In narrow containers the inline Approve, Deny and Retry and the Open run button become icon buttons with accessible names. A truncated activity stays readable in full through the pill's name and the panel's step list.
- Pointer focus draws no ring; keyboard focus shows Arc's shared focus ring, inset on the pill.
- A new activity rolls in vertically in about 180ms while the old one leaves upward; the pill's width follows on the morph spring, and the label fades at its right edge only while it is wider than its room, so nothing snaps or wraps.
- Opening the panel springs width and height together on the smooth spring from the pill's own footprint; the header row stays where it is and the panel content fades in after a beat. Closing reverses it with a faster fade.
- The live ring spins a short arc while working, freezes in place when paused, gives way to an accent dot with one soft ping when input is needed, and closes around the glyph as the mark morphs into a drawn check (done) or cross (failed).
- The working label carries a calm shimmer that stops in every other state. Counts and the timer change in place with tabular numerals.
- New steps fade in at the bottom of the panel while older ones slide up; segments of the progress bar fill from the left.
- Reduced motion swaps every spring for an instant change, drops the roll, the spin, the ping and the shimmer, and keeps all states, text, focus and announcements.
- The pill sizes to its content and never exceeds its parent's width; a long activity fades out at its right edge instead of wrapping.
- Below 400px of available width the inline actions become icon buttons, the timer steps aside while they show, and Open run in the panel becomes an icon button.
- The panel opens at 384px or the pill's width, whichever is wider, capped by the parent, and floats over the page with a shadow so nothing below moves.
- Step labels in the panel wrap rather than truncate, so long file paths stay readable on a phone.
- The timer runs on a small per-instance clock that ticks four times a second only while the run is live and stops when paused, done or idle.
- Width and height animate motion values directly; natural widths come from two hidden, inert measuring rows watched by a ResizeObserver, so the pill never measures in the middle of an animation.
- The spinner and shimmer are CSS animations that pause or stop outside the working state.
Notes for AI
Give your coding assistant the Markdown reference instead of screenshots.
- Drive it from your agent's event stream: set status, keep activity to the latest tool call or phase in the present tense, and append steps as they finish. Keep the pill's label short; the panel carries the history.
- Keep startedAt fixed for the whole run; the component freezes paused and failed time itself. Pass endedAt from your backend when you have it.
- Use needs-input for any approval gate (shell commands, pushes, payments, emails) and phrase activity.label as the request: "Needs your approval to run git push".
- For done, put only the outcome in activity.label ("6 files changed"); the component writes "Done in 1m 12s · 6 files changed".
- Pass only the handlers your backend supports; each control appears only when its handler exists.
- Place it in a container that sets its width, such as a header slot or a flex row: the pill measures its parent to truncate cleanly and to size the panel.
The full library index for assistants is at /llms.txt.