Skip to content
BlocksAgent opsMenu

Agent ops

Mission control for background agents: every run in one inbox, every approval in one queue, the selected run's live trace and an activity feed. Approve from anywhere and the run picks up where it stopped.

Background runs, approvals waiting on you, the selected run's trace and notifications.

The demo runs on a scripted fleet, so it needs no API key. The selected run streams its trace and stops for an edit approval, which approves itself after 4 s if you leave it. Around it, research finishes, a queued run starts and a dependency bump opens a pull request. Approve the Next.js upgrade in Needs you to unblock it. Open /preview/agent-ops-demo to see it full screen.

Installation

$ pnpm dlx shadcn@latest add https://chatcn.dev/r/agent-ops.json

The block installs to components/chatcn/agent-ops/agent-ops.tsx together with every component it composes. It is your code from here: add a New task button to the header, swap the trace renderers, drop the activity column.

Usage

Pass your runs as data. Each run is a BackgroundRunData (title, status, context, latest step, progress) plus the task prompt and the agent's parts in the chatcn message contract. Tool parts in approval-requested show up in Needs you on their own, and in the run's trace with Allow and Deny.

app/agents/page.tsx
"use client"


export default function AgentsPage() {
  const { runs, events, approve, deny, cancel, retry, markRead } = useRuns()
  return (
    <AgentOps
      fullHeight
      runs={runs}
      notifications={events}
      onOpenRun={markRead}
      onApprove={(approvalId, { scope, runId }) => approve(runId, approvalId, scope)}
      onDeny={(approvalId, reason, { runId }) => deny(runId, approvalId, reason)}
      onCancelRun={cancel}
      onRetryRun={retry}
    />
  )
}

Keep runs in your own store and update them from your backend over SSE or a websocket. The block never polls and never imports a runtime: a new runs array is the whole update. When a run finishes or needs you, append to notifications; new ones are toasted when the activity column is out of view and the run is not already on screen.

A run
const run: AgentOpsRun = {
  id: "run_8f2",
  title: "Fix flaky checkout test",
  context: "tallyline/web · fix/checkout-flake",
  prompt: "The checkout e2e test fails about one run in five on CI.",
  status: "needs-input",
  steps: { done: 3, total: 5 },
  startedAt: 1761060000000,
  parts: [
    { type: "reasoning", text: "Reproduce the flake first.", state: "done" },
    {
      type: "tool-edit_file",
      toolCallId: "call_1",
      state: "approval-requested",
      input: { path: "tests/e2e/checkout.spec.ts", diff },
      approval: { id: "ap_1", requestReason: "Test code only." },
    },
  ],
}

Components it composes

ComponentRole
Run inboxThe run list with filters, unread dots and arrow keys
Background runRun rows, status glyphs and the 2 px progress line under the run header
Approval inboxNeeds you: every pending approval across runs, with batch Allow and Deny
Run statusStatus and elapsed time in the run header
TraceThe rail that holds the selected run's steps
ReasoningThinking blocks inside the trace
Tool fallbackRoutes each tool part to its renderer; unknown tools fall back
Tool bash, Tool file edit, Tool file read, Tool search code, Tool web search, Tool web fetchThe tool calls in the trace, with inline approvals
MarkdownWhat the agent writes between steps
ConversationStick-to-bottom scrolling for the live trace
Agent notificationThe activity feed and the toasts
ResizableColumns you can drag, with keyboard resizing
PopoverThe activity feed behind the bell on narrower layouts

Responsive

Block widthLayout
1152 px and upThree columns: runs, the selected run, then Needs you and Activity
768 to 1151 pxTwo columns. Needs you sits above the runs; activity moves behind the bell and toasts
Under 768 pxOne column. Needs you, then runs; picking a run pushes it with a back button

The layout follows the block's own width, not the viewport, so it works in a docs column, a split pane and a full page alike. Rows and buttons grow to 44 px on touch screens.

Props

PropTypeNotes
runsAgentOpsRun[]Shown in the order you pass. Approvals are derived from their parts
selectedRunId, defaultSelectedRunId, onSelectedRunChangestring | nullControlled or uncontrolled. Defaults to the first run that needs you
onOpenRun(runId) => voidA run was opened from the list. Mark it read
onMarkAllRead() => voidShows Mark all read while anything is unread
onApprove(approvalId, { scope, runId }) => voidFrom Needs you or from the trace
onDeny(approvalId, reason, { runId }) => voidThe reason is the note the person typed, if any
onCancelRun, onRetryRun(runId) => voidCancel on active runs, Retry on failed ones
notificationsAgentOpsNotification[]Newest last: kind, title, description, runId, createdAt
toastsbooleanToast new notifications while the activity column is hidden. Default true
titleReactNodeHeader title. Default Agents
headerActionsReactNodeExtra controls before the bell
toolRenderersToolRendererMapTool name to renderer for the trace
loadingbooleanSkeleton rows in the run list
fullHeightbooleanFill the viewport. Otherwise give the parent a height

Keyboard

KeyAction
Arrow up, Arrow down, Home, EndMove between runs in the list
Enter, SpaceOpen the focused run
A, D, XAllow, deny or select the focused row in Needs you
Arrow left, Arrow rightResize the focused column divider. Enter collapses it
EscapeBack to the list from a pushed run on phones
Alt+TFocus the toasts

Accessibility

  • The run list, Needs you, the run and Activity are labelled regions with their own headings.
  • Run status changes are announced through polite live regions; the header counts are one too.
  • The selected run row carries aria-current, and the bell's label includes the number of new events.
  • Column dividers are focusable separators with their current size, per the window splitter pattern.
  • Status is never color alone: every state has a glyph and a label.

Motion

  • Status glyphs swap with a 150 ms blur crossfade, and the latest step line rises 4 px as it changes.
  • The progress line under the run header fills with a 280 ms transform.
  • New activity slides in 4 px over 280 ms; toasts follow the agent notification stack.
  • A pushed run on phones enters from 16 px over 280 ms. Keyboard moves and selection never animate.
  • Under reduced motion only the opacity fades remain.