Skip to content
Component installs need the registry setup
VegaStack Design

Agent Panel

An agent on a record as a collaborator — whom it acts for, its state and actions, instructions, latest messages, a reply box and runs with Undo.

Status
stable
Since
0.25.89
Accessibility pattern
polite announcements, named controls, state not by colour

Last updated

Agent

Claude CodeAgentActing for Priya · Read & write

Working4 min

Reading the 3 linked pages…

Instructions

Draft the proposal from M-15.

Latest

  • Message: Two prices conflict; using v2
  • Change: Status → In progress

History

  • Run · 3 changes

Install

Configure registry credentials first using the Quickstart. Before copying this item or any transitive registry dependency, verify the signed manifest and save the exact bytes for Agent Panel before installation. Retain the digest printed by this command.

pnpm exec vegastack-design verify agent-panel

Install with the pinned shadcn CLI. Bare shadcn does not verify VegaStack signature or integrity.

pnpm dlx shadcn@4.21.0 add @vegastack/agent-panel

Run the exact offline --post-write command printed by the preflight, using its saved item path and independently retained --expected-integrity digest. Use the saved preflight for every transitive registry dependency too; stop on any mismatch before using the components.

The same command installs the registry items it composes: @vegastack/button, @vegastack/collapsible, @vegastack/dropdown-menu, @vegastack/hover-card, @vegastack/input-group, @vegastack/person-avatar, @vegastack/record-aside, @vegastack/record-chip, @vegastack/relative-time, @vegastack/spinner, @vegastack/textarea, @vegastack/tooltip, @vegastack/use-announcer, @vegastack/disclosure-trigger.

Usage

import {
  AgentChip,
  AgentPanel,
  AgentSlot,
  AgentStateRing,
} from "@/components/ui/agent-panel";

<AgentPanel
  agent={{ name: "Claude Code", via: "Priya", access: "Read & write" }}
  state="working"
  startedAt={run.startedAt}
  progress={run.lastNote}
  instructions={delegation.instructions}
  onInstructionsChange={canSteer ? saveInstructions : undefined}
  latest={events}
  latestTotal={eventCount}
  onShowAll={() => filterActivity(agent.id)}
  onReply={canSteer ? reply : undefined}
  runs={runs.map((run) => ({
    ...run,
    onView: () => openRun(run),
    onUndo: () => undo(run),
  }))}
  onViewAgent={openAgent}
  onChangeAgent={canSteer ? openDelegate : undefined}
  onStop={canSteer ? stop : undefined}
  onRemove={canRemove ? remove : undefined}
/>;

The panel is presentational: the host owns the delegation and passes its state, its data and the callbacks. The callbacks decide what shows: pass onReply, onInstructionsChange and onStop only to the person who delegated, onRemove to them and to admins, and onReconnect to the agent's owner. state="none" renders nothing — the Delegate slot carries the empty state.

Each state reads the props it needs: queued (queuedHint once it has waited a while), working (startedAt, progress), question (question, choices, onAnswer), approval (approval, onApprove, onDecline), failed (error, onRetry), blocked (blockedReason, onReconnect), done (finishedAt, durationSeconds, summary, onMarkDone) and stopped (stoppedBy, finishedAt, changeCount, onDelegateAgain). A finished, failed or stopped card whose agent was disconnected since takes disconnected and adds a muted note — it never turns back into Needs you.

Scope

Placement follows one rule: the rail card on a record page (variant="card", directly under Properties — above it while it needs you, data-attention), the section in a peek (variant="section"), the pill in a peek's pill row and a phone's facts row (AgentChip), the one-line Delegate property (AgentSlot) and the state mark or ring in lists and boards (AgentStateBadge, AgentStateRing). AgentHoverCard puts the agent's facts behind its avatar or name anywhere it appears. Choosing an agent is the DelegatePopover; an agent's edit to a document is the AgentEditBanner. Every string comes in through labels and stateLabels, with the English defaults shown here.

Anatomy

AgentPanel — data-slot="agent-panel" | "agent-panel-content" | "agent-panel-header"
AgentSlot — data-slot="agent-slot"
AgentChip — data-slot="agent-chip"
AgentStateBadge — data-slot="agent-state-badge"
AgentStateRing — data-slot="agent-state-ring"
AgentHoverCard — data-slot="agent-hover-card" | "agent-hover-card-avatar" | "agent-hover-card-name"

Examples

Agent

Claude CodeAgentActing for Priya · Read & write

Working4 min

Reading the 3 linked pages…

Instructions

Draft the proposal from M-15.

Latest

  • Message: Two prices conflict; using v2
  • Change: Status → In progress

History

  • Run · 3 changes

States

The same card in every state: Queued with Stop and the hint after a while; Needs you with the question, one-tap answers and Reply…; Wants approval with Approve and Decline; Failed with Retry and Remove; Blocked with Reconnect for the owner; Done with its date, run time, closing message and Mark task done; Stopped with what stays and Delegate again — and a done card whose agent was disconnected since. History's Undo shows disabled with its reason when the run can't be undone.

Agent

Claude CodeAgentActing for Priya · Read & write

QueuedWaiting for Claude Code to pick this up

Claude Code picks up work when it's running on your machine.

Agent

Claude CodeAgentActing for Priya · Read & write

Needs you

Which price list — v2 or the client's?

Agent

Claude CodeAgentActing for Priya · Read & write

Wants approvalMove 3 tasks to Trash

Agent

Claude CodeAgentActing for Priya · Read & write

FailedCouldn't open M-15 — no access.

Agent

Claude CodeAgentActing for Priya · Read & write

Needs youIts token expired

Agent

Claude CodeAgentActing for Priya · Read & write

Done3 minProposal drafted in P-42.

History

  • Run · 12 changes

Agent

Claude CodeAgentActing for Priya · Read & write

Stopped by PriyaIts 12 changes stay — undo them in History

History

  • Run · 12 changes

Agent

Claude CodeAgentActing for Priya · Read & write

Done10 minChecked the MCP tools end to end; nothing else to do.

Agent since disconnected

Section

In a peek the panel is a collapsible section whose header is the one-line summary and the state's quick action. It opens unless the work is done or stopped.

Claude CodeAgentActing for Priya · Read & write

Instructions

Draft the proposal from M-15.

Latest

  • Message: Two prices conflict; using v2
  • Change: Status → In progress

History

  • Run · 3 changes

Claude CodeAgentActing for Priya · Read & write

Needs you

Which price list — v2 or the client's?

Slot, chip, badge and ring

AgentSlot is the Delegate property's value on one line — the agent's name truncates, its state word never does — with the ghost "Delegate to an agent…" when empty and the muted reason when the viewer can't delegate. AgentChip is the same as a pill; AgentStateBadge the state as icon and word; AgentStateRing rings an agent's avatar amber while it needs you and red when it failed. Hover the agent for AgentHoverCard.

Assignee
Priya Raman
Delegate
Delegate
Delegate
Only Priya can delegate — assign it to yourself first
Delegate
QueuedWorkingNeeds youFailedDoneStopped
PR, WorkingPR, Needs youPR, Failed
Hover the agent:

API Reference

PropTypeDefaultDescription
agent*AgentPanelAgent—The agent.
state*AgentState—Where the delegation stands; none renders nothing (the Delegate slot is empty).
approvalReact.ReactNode—What the agent asks to do (state approval): "Move 3 tasks to Trash".
blockedReasonReact.ReactNode—Why it can't go on (state blocked): "Its token expired".
changeCountnumber—How many of its changes stay (stopped).
choicesreadonly AgentPanelChoice[][]One-tap answers to the question.
defaultOpenbooleantrue, false when done or stoppedThe section's initial open state.
disconnectedbooleanfalseThe agent was disconnected since: a finished, failed or stopped card adds the muted "Agent since disconnected" and never turns back into Needs you.
durationSecondsnumber—How long the run took (done), in seconds: "3 min".
errorReact.ReactNode—The agent's problem (state failed): "Couldn't open M-15 — no access."
finishedAtstring | number | Date—When it finished (done) or was stopped (stopped).
instructionsstring | null—What the agent was asked to do.
labelsPartial<AgentPanelLabels>—Replace any of the strings (AgentPanelLabels).
latestreadonly AgentPanelEvent[][]The agent's latest messages and changes, newest first; the first three show.
latestTotalnumber—How many there are in all, for "Show all 5".
nownumber—Pin the clock for elapsed and relative times (docs, tests).
onAnswer((value: string) => void)—Answer the question with a choice; the choice buttons show when set.
onApprove(() => void)—Approve; shows when set.
onChangeAgent(() => void)—Menu: Change agent; shows when set.
onDecline(() => void)—Decline; shows when set.
onDelegateAgain(() => void)—Delegate again (stopped); shows when set.
onInstructionsChange((instructions: string) => void | Promise<void>)—Save edited instructions — only for a viewer who may steer: ✎ shows, and an empty instruction offers "Add instructions". A returned promise keeps the editor open until it settles, and open on a rejection.
onMarkDone(() => void)—Mark the record done (done, when it isn't done on its own); shows when set.
onOpenChange((open: boolean) => void)—Called when the section opens or closes.
onReconnect(() => void)—Reconnect (blocked) — pass it to the agent's owner only.
onRemove(() => void)—Remove the agent from the record (menu, and the Failed line); shows when set.
onReply((text: string) => void | Promise<void>)—Send a reply to the agent — only for a viewer who may steer; the reply box shows when set. A returned promise keeps the text until it resolves.
onRetry(() => void)—Retry (failed); shows when set.
onShowAll(() => void)—Show them all (filter the activity to this agent); "Show all" shows when set.
onStop(() => void)—Stop (queued and working lines, and the menu while it is on the work); shows when set.
onViewAgent(() => void)—Menu: View agent; shows when set.
openboolean—The section's open state (controlled).
progressReact.ReactNode—The agent's latest progress line while working ("Reading the 3 linked pages…").
questionReact.ReactNode—The agent's open question (state question).
queuedHintbooleanfalseQueued a while (the host's 10 minutes): add the "picks up work when it's running" hint.
replyDisabledReasonReact.ReactNode—Why the reply box is disabled ("It's stopped"), shown under it.
runsreadonly AgentPanelRun[][]The agent's runs, newest first, under History.
startedAtstring | number | Date—When the agent started (working: "Working · 4 min").
stateLabelsPartial<AgentStateLabels>—Replace any state word (AgentStateLabels).
stoppedBystring—Who stopped it (stopped): "Priya".
summaryReact.ReactNode—The agent's closing message (done): "Proposal drafted in P-42."
variant"card" | "section""card"card — the record page's rail card. section — the peek's collapsible section, its header a one-line summary; open unless the work is done or stopped.

Data attributes and CSS variables on AgentPanel

AttributeValues
data-attention""
data-slot"agent-panel" | "agent-panel-content" | "agent-panel-header"
data-statemirrors a prop or state value
data-variant"card" | "section"

AgentPanelRun

PropTypeDefaultDescription
at*string | number | Date—When the run made its last change.
changeCount*number—How many changes the run made.
id*string—A stable key.
onUndo(() => void)—Undo the run's changes; Undo shows when set.
onView(() => void)—Open the run's changes; View shows when set.
undoDisabledReasonstring—Why the run can't be undone ("Edited since"): Undo shows disabled with this as its tooltip and description.
undoingbooleanfalseThe undo is running: Undo spins and is disabled.

AgentPanelEvent

PropTypeDefaultDescription
at*string | number | Date—When.
id*string—A stable key.
kind*"change" | "message"—A message from the agent (💬) or a change it made (↻).
text*React.ReactNode—The message, or the change ("Status → In progress").

AgentSlot

PropTypeDefaultDescription
agentAgentPanelAgent | null—The agent delegated to; none shows the ghost placeholder.
disabledReasonReact.ReactNode—Why the viewer can't delegate ("Only Priya can delegate — assign it to yourself first"): the empty slot shows it, muted, as text instead of a button.
loadingboolean—Shows a spinner over the label, blocks activation and sets aria-busy. The label keeps its box at opacity: 0, so the button's width does not move and its accessible name survives (API-5, A11Y-12).
placeholderReact.ReactNode"Delegate to an agent…"The empty slot's ghost text.
readOnlybooleanfalseShow the agent as text, not a button (the viewer can't change it).
size"default" | "icon" | "icon-lg" | "icon-sm" | "icon-xs" | "lg" | "sm" | "xs" | null—
stateAgentState"working" when an agent is setWhere its work stands.
stateLabelsPartial<AgentStateLabels>—Replace any state word (AgentStateLabels).
variant"default" | "destructive" | "ghost" | "link" | "outline" | "secondary" | null—

Data attributes and CSS variables on AgentSlot

AttributeValues
data-disabled""
data-empty""
data-slot"agent-slot"
data-statemirrors a prop or state value

AgentChip

PropTypeDefaultDescription
agentAgentPanelAgent | null—The agent delegated to; none shows the dashed placeholder pill.
loadingboolean—Shows a spinner over the label, blocks activation and sets aria-busy. The label keeps its box at opacity: 0, so the button's width does not move and its accessible name survives (API-5, A11Y-12).
placeholderReact.ReactNode"Delegate"The empty pill's text.
size"default" | "icon" | "icon-lg" | "icon-sm" | "icon-xs" | "lg" | "sm" | "xs" | null—
stateAgentState"working" when an agent is setWhere its work stands.
stateLabelsPartial<AgentStateLabels>—Replace any state word (AgentStateLabels).
variant"default" | "destructive" | "ghost" | "link" | "outline" | "secondary" | null—

Data attributes and CSS variables on AgentChip

AttributeValues
data-empty""
data-slot"agent-chip"
data-statemirrors a prop or state value

AgentStateRing

PropTypeDefaultDescription
state*AgentState—The state: amber ring while it needs you, red when it failed, none otherwise.
srLabelbooleantrueSay the state to screen readers after the avatar ("Needs you"). Turn it off when the state's word is already written beside it (an AgentStateBadge).
stateLabelsPartial<AgentStateLabels>—Replace any state word (AgentStateLabels).

Data attributes and CSS variables on AgentStateRing

AttributeValues
data-slot"agent-state-ring"
data-statemirrors a prop or state value

AgentHoverCard

PropTypeDefaultDescription
agent*AgentPanelAgent—The agent.
children*React.ReactNode—The trigger — the agent's PersonAvatar (trigger="avatar") or its name (trigger="name").
classNamestring—Classes for the trigger button.
labelsPartial<AgentHoverCardLabels>—Replace any of the strings (AgentHoverCardLabels).
onViewAgent(() => void)—Open the agent's page; the View agent link shows when set.
stateAgentState—Where its work on this record stands, shown under its access.
stateLabelsPartial<AgentStateLabels>—Replace any state word (AgentStateLabels).
trigger"avatar" | "name""avatar"avatar — the children sit in a round ghost button named for the agent. name — inline text (an activity's actor) in a quiet text button.

Data attributes and CSS variables on AgentHoverCard

AttributeValues
data-icon"inline-end"
data-slot"agent-hover-card" | "agent-hover-card-avatar" | "agent-hover-card-name"

Accessibility

  • A change of state while the panel is on screen is announced politely ("Claude Code: Needs you"); the state it mounts with is not. Relative times tick silently.
  • The state is always an icon and a word; colour is never the only cue. The ring's state is said to screen readers unless the word is already beside it (srLabel={false}).
  • The ⋯ menu is named for the agent ("More actions for Claude Code") and lists only the actions the viewer has. The agent's bot is decoration; the words name it.
  • A run that can't be undone keeps a focusable, disabled Undo whose reason is its tooltip and its accessible description.
  • The section's header is a heading holding the disclosure button, with aria-expanded.
KeyAction
TabMove through the menu, the state's actions, ✎, Reply and runs.
Enter / SpaceActivate a button, or open and close the section.
Ctrl + EnterSend a reply, or save edited instructions (⌘ on a Mac).
EscCancel editing the instructions.
ContractStates tested
Behaviournone, queued, queued-hint, working, question, approval, failed, blocked, done, stopped, disconnected, instructions-edit, latest, reply, reply-disabled, history-undo, undo-disabled, menu, section-collapsed, slot-empty, slot-disabled, chip, ring, hover-card, labels
Accessibilitypolite-state-announcement, named-menu, decorative-avatar, silent-relative-time, undo-disabled-reason, state-word-not-colour, browser-accessibility-test
Visualcard-states, section, slot, chip, badge-ring, hover-card

Do / Don't

Do
Pass only the callbacks the viewer may use, and let the card's state say what the agent needs next — answer, approve, retry, reconnect.
Don't
Squeeze a delegation into the Assignee cell, or show a finished card as Needs you because its agent was disconnected since.
Do
Keep the Delegate property on one line with AgentSlot and the conversation in the card.
Don't
Repeat the agent's messages in the property row or hide its question deep in the activity feed.

On this page