Agent Task Queue
ProInbox for work an agent is doing on its own: queued, running, done. Progress derived from a plan that checks itself off, an ETA extrapolated from observed pace, and cancel or retry per task. Four variants — inbox, compact, grouped and detail.
import { SvAgentTaskQueueComponent } from '@styloviz-pro/agent-task-queue';Demo & usage
Basic usage
ProUsage example — open the gallery for the full interactive demo.
An interactive demo for this component lives in the gallery.
Open live demoMore examples
Let the plan be the progress
ProOmit progress and give a plan — the steps that complete become the percentage.
Compact, for a header popover
ProOne row per task when the only question is whether anything is still running.
Triage a backlog
ProSections ordered by urgency, empty ones omitted.
Read-only queue
ProKeeps the information, removes cancel and retry.
When to use
- Showing work an agent is doing unattended, where the user needs to know what is running and how far along it is.
- Surfacing a long-running job behind a header bell or sidebar rail, in the compact variant.
- Debugging a run: the plan shows what the agent intended, the step notes show what it actually found.
- Giving an operator a way to stop work that is going wrong, and restart work that failed.
Async states
- loading
- error
- retry
- offline
Item identity & owned state
- Restorable state
- collapsedIds
- pendingTaskIds
Bind these to put a saved view back — the open sections, the hidden columns, the selected rows — and read their
Changeevents to store what the user does.
Inputs
| Name | Type | Default | Description |
|---|---|---|---|
| loading | boolean | false | True while the data is being fetched. |
| error | SvDataError | string | null | null | Load failure. Use `toSvDataError()` from `@styloviz/core` to normalize an `HttpErrorResponse`. |
| tasks | readonly AgentTask[] | [] | The queue. |
| connection | SvConnectionState | 'idle' | State of your `EventSource` / WebSocket. A queue that stops moving looks the same whether the workers are busy or the socket died — and this one shows progress bars and ETAs, which quietly go stale with it. |
| reconnectable | boolean | false | Offer a manual reconnect while the feed is down. |
| selectedId | string | null | null | Which task the `detail` variant shows. Falls back to the first running one. |
| variant | AgentTaskQueueVariant | 'inbox' | Presentation preset. |
| showStats | boolean | true | Show the roll-up counts above the queue. |
| showPlan | boolean | true | Show each task's plan checklist. |
| showEta | boolean | true | Show the computed ETA on running tasks. |
| actionable | boolean | true | Offer cancel and retry. |
| ariaLabel | string | 'Agent tasks' | Accessible label for the queue. |
| now | number | null | null | Clock used for elapsed time and ETA, injectable so a test is not at the mercy of the wall clock. Leave it unset and the component ticks its own clock every `tickMs`. The default used to be a bare `Date.now()`, which an input default evaluates **once at construction** — so "3 minutes ago" stayed "3 minutes ago" for as long as the page stayed open, which is exactly wrong for a live queue. |
| tickMs | number | 1000 | How often the internal clock advances, in milliseconds. Set `0` to stop it — useful when you drive `now` yourself. |
| pendingTaskIds | readonly string[] | ReadonlySet<string> | [] | Ids of tasks with an action in flight. Their Cancel and Retry buttons disable and report `aria-busy`, and the handlers refuse too — so a second click cannot fire the same request twice. Add the id in your `(action)` handler and drop it when the request settles. |
| collapsedIds | ReadonlySet<string> (two-way) | new Set() | Task ids whose plan the reader collapsed (two-way). A `model` so a host can restore or persist which tasks were folded. |
| maxTasks | number | 200 | How many tasks the queue lists at once. `0` = no ceiling. A QUEUE IS WHERE BACKLOG ACCUMULATES BY DEFINITION, and every row here carries a progress bar, an ETA and a plan — so a thousand queued tasks is a thousand animated rows, on a surface an operator opens precisely when the system is already struggling. THE WINDOW KEEPS THE HOST'S ORDER FROM THE TOP, and here that is the right end: a queue is ordered by what happens NEXT, so the rows worth rendering are the first ones. That is the opposite of the transcript and the feed, which is exactly why `svScaleWindow` takes the direction rather than guessing it. |
Outputs
| Event | Payload type | Description |
|---|---|---|
| retryLoad | void | The host should re-run the failed load. Named `retryLoad` because this component already publishes `retry` for re-running a single item — stealing that name would silently change what existing handlers receive. |
| reconnect | void | The user asked to re-open the feed. |
| action | AgentTaskAction | The reader cancelled or retried a task. |
| taskSelected | AgentTask | A task row or card was activated. |
| stepClick | AgentTaskStepClick | A plan step was activated — hook this to open the underlying work. |
| collapsedIdsChange | ReadonlySet<string> | Emits the new `collapsedIds` whenever it changes — the output half of `[(collapsedIds)]`. |
Wire it to your API
This component is headless about transport: it renders what you give it and reports what the user asks for. Nothing below requires @styloviz/data — drive it from whatever you already use.
Template
ProBound to the names this component actually exposes.
Host class
ProDrive the three async inputs from your own fetch.
- This component does not emit a query, so it renders what you give it. Fetch on your own trigger and bind `tasks`, `loading` and `error`.
- `connection` reports the state of your `EventSource` or WebSocket. The component cannot infer it: a list that stops growing looks identical whether the feed is quiet or the socket died.
- The load-level retry is `retryLoad` here, because `retry` already means a per-item action on this component.
- Normalize failures with `toSvDataError()` from `@styloviz/core`. It derives `retryable` from the status, so a 404 does not get a "Try again" button that can only fail again.
Full walkthrough: Connecting to a backend.
Customization & theming
- `variant` chooses the shape: `inbox` when the queue is the page, `compact` for a rail, `grouped` for triage, `detail` for one run.
- `showPlan`, `showEta` and `showStats` drop the checklist, the estimate and the roll-up independently.
- `actionable` false, or `cancellable: false` on a single task, removes the interrupt controls.
- `priority` tints a task title so a high-priority item reads differently in a long list.
- The `planProgress`, `taskProgress`, `estimateRemainingMs`, `elapsedMs`, `formatDuration` and `queueStats` helpers are exported, so a header badge or notification can use the same maths as the component.
- A scale ceiling: `[maxTasks]` (200) bounds the list, windowed from the TOP because a queue is ordered by what happens next — the opposite direction from a transcript. The grouped variant windows before grouping, so sections cannot render the whole queue by splitting it up, and the summary line keeps counting the whole backlog: "12 running, 940 queued" is the number an operator acts on. `0` turns it off.
- The clock stops when nobody is looking: the internal tick pauses while the tab is hidden (`createVisibilityPause`) and while the queue's own element is scrolled out of view (`createOffscreenPause`). It catches up the instant it resumes, so a reader who comes back sees the right elapsed time rather than an old one for a further second. With no `IntersectionObserver` it keeps ticking — the fallback is never a frozen queue.
- `(action)` carries `observedStatus` and `at` as well as the verb: a cancel is the conditional instruction "stop this, which I was looking at as running". Compare with `svTaskActionIsStale(action, current)` and, when it is stale, treat the request as a **no-op** and push the true status back — `svTaskActionNoopMessage()` is the sentence for it. A 409 at a reader who pressed a button the queue offered them is the failure this avoids.
- `pendingTaskIds` disables a task's controls while its action is in flight, which is the optimistic half: the row shows the request has been made without claiming it succeeded.
Best practices
- Do: Prefer a `plan` over an explicit `progress`. Two sources of truth drift; a plan that checks itself off cannot.
- Do: Set `status: 'active'` on the step currently executing — it counts as half, so a long plan does not read as 0% while the first step runs.
- Do: Set `startedAt` on running tasks. Without it there is no pace to extrapolate from and no ETA can be shown.
- Do: Use step `note` to explain a skip or a failure. "Skipped — unchanged since last run" answers the question the strikethrough raises.
- Do: Pass `now` in tests so ETA and elapsed assertions are deterministic.
- Don't: Do not reach for this when a human owns the work — that is Kanban, where the point is dragging a card between columns. Here the agent moves the state and the reader only interrupts.
- Don't: Do not fabricate an ETA for a task that just started. The component withholds one deliberately; "0s remaining" reads as broken.
- Don't: Do not mutate the task list in response to `action` without going through the runtime — the component reports intent, it does not own state.
Accessibility
- Each progress bar is `role="progressbar"` with `aria-valuenow` and a label naming its task, so progress is available without seeing the fill.
- A running task's status chip is `role="status"`, so a task starting or finishing while the operator is elsewhere is announced.
- Step state is carried in visually-hidden text ("— done", "— active", "— skipped") as well as by colour, icon and strikethrough.
- The plan is an ordered list labelled with its task, and its expand control is a real `aria-expanded` / `aria-controls` disclosure.
- Group headings separate the label from the count with hidden text, so a screen reader reads "Running — 1 tasks" rather than "Running1".
- Cancel and retry carry explicit labels naming the task, since the visible text is only the verb.
Feedback on Agent Task Queue
Spotted a gap in this page, or something that didn't work as described? Comments are GitHub Discussions in the public styloviz/community repo, so posting needs a GitHub account.