@artooi/ag-ui-web-component 0.29.0 → 0.31.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/CHANGELOG.md +226 -1
  2. package/README.md +202 -33
  3. package/dist/ag-ui-web-component.bundle.js +212 -29
  4. package/dist/ag-ui-web-component.bundle.js.map +4 -4
  5. package/dist/constants.d.ts +60 -1
  6. package/dist/constants.d.ts.map +1 -1
  7. package/dist/core/ag_ui_chat.d.ts +25 -1
  8. package/dist/core/ag_ui_chat.d.ts.map +1 -1
  9. package/dist/core/agui_client.d.ts +18 -1
  10. package/dist/core/agui_client.d.ts.map +1 -1
  11. package/dist/index.d.ts +2 -2
  12. package/dist/index.d.ts.map +1 -1
  13. package/dist/index.js +585 -35
  14. package/dist/index.js.map +4 -4
  15. package/dist/ui/message_actions.d.ts +14 -4
  16. package/dist/ui/message_actions.d.ts.map +1 -1
  17. package/dist/ui/styles.d.ts +1 -1
  18. package/dist/ui/styles.d.ts.map +1 -1
  19. package/dist/ui/subagent_panel.d.ts +92 -0
  20. package/dist/ui/subagent_panel.d.ts.map +1 -0
  21. package/dist/ui/subagent_update.d.ts +19 -0
  22. package/dist/ui/subagent_update.d.ts.map +1 -0
  23. package/dist/ui/tool_call_card.d.ts +73 -1
  24. package/dist/ui/tool_call_card.d.ts.map +1 -1
  25. package/dist/ui/ui_strings.d.ts +10 -0
  26. package/dist/ui/ui_strings.d.ts.map +1 -1
  27. package/package.json +1 -1
  28. package/src/constants.ts +63 -1
  29. package/src/core/ag_ui_chat.ts +187 -26
  30. package/src/core/agui_client.ts +26 -2
  31. package/src/index.ts +4 -0
  32. package/src/ui/message_actions.ts +20 -8
  33. package/src/ui/styles.ts +183 -0
  34. package/src/ui/subagent_panel.ts +213 -0
  35. package/src/ui/subagent_update.ts +80 -0
  36. package/src/ui/tool_call_card.ts +129 -3
  37. package/src/ui/ui_strings.ts +15 -0
  38. package/src/version.ts +1 -1
package/CHANGELOG.md CHANGED
@@ -7,6 +7,229 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.31.0] — 2026-08-30
11
+
12
+ ### Changed
13
+
14
+ - **The rating pair is off unless a host asks for it.** `data-message-actions`
15
+ now defaults to `copy,retry`; thumbs need
16
+ `data-message-actions="copy,retry,feedback"`.
17
+
18
+ Copy and retry work with nothing wired -- copy reads the DOM, retry drives this
19
+ element. The rating pair does not: it fires `ag-ui-feedback` and stores nothing
20
+ by design, because a rating belongs to whatever the host already uses for
21
+ product signal. With no listener the buttons still latch `aria-pressed`, which
22
+ is deliberate -- a rating is a standing statement about a message -- so a
23
+ reader is told their rating was taken, a screen reader announces it as pressed,
24
+ and nothing recorded anything.
25
+
26
+ This README has said since the row shipped that two buttons leading nowhere are
27
+ worse than none. Defaulting them on was that sentence being false. Neither
28
+ known consumer of this component listens for the event, so both were shipping
29
+ exactly the buttons the sentence warns about.
30
+
31
+ The row shipped two days ago in 0.29.0, so the population relying on the old
32
+ default is close to nobody, and a host that does listen restores them with one
33
+ attribute.
34
+
35
+
36
+ ## [0.30.0] — 2026-08-30
37
+
38
+ ### Added
39
+
40
+ - **The sub-agent fixture was re-copied after the server added a `timestamp` to
41
+ every `CUSTOM` event.** Reading the wire rather than the prose is what found
42
+ that gap: `CUSTOM` was the only event type in the stream without one. The
43
+ client needed no change to absorb it — the field is additive and ignored here —
44
+ which is the degradation story working, and the copy is byte-identical to the
45
+ producer's again.
46
+
47
+ - **A delegated sub-agent's progress, on the card that delegated it.** A run that
48
+ hands work to a sub-agent read as a stall: the parent's `delegate_task` card
49
+ sat at "running..." for the child's entire duration, however many tools the
50
+ child called, with nothing on screen. The component now consumes the AG-UI
51
+ `CUSTOM` event named `ag_ui.subagent` and draws it — the first real consumer of
52
+ the `onCustomEvent` carrier opened in 0.29.0.
53
+
54
+ **It attaches rather than floats, because the wire lets it.** `delegationId` is
55
+ the *parent's own* `delegate_task` tool-call id, not the child's run id, so the
56
+ thing being narrated is a card this component already drew on
57
+ `TOOL_CALL_START`. The surface is one collapsed row inside that card, carrying
58
+ the server's pre-rendered `status` line and nothing else, expanding onto the
59
+ child agent's own tool calls. A ten-step child costs one row until somebody
60
+ opens it, and there is no second visual language to learn.
61
+
62
+ Two alternatives were considered and dropped. A bare status line is cheaper and
63
+ gives up the detail entirely. Inline child cards in the main transcript
64
+ interleave parent and child with nothing marking whose is whose — and in an
65
+ order the persisted transcript, which never held the progress, cannot
66
+ reproduce.
67
+
68
+ **A failure carries no exception text on this channel, and none is invented
69
+ here.** That is the same reasoning that redacts a `RUN_ERROR`: an exception's
70
+ words are written for an operator. The detail rides the ordinary
71
+ `TOOL_CALL_RESULT` for that delegation and lands in the same card's result
72
+ region, a few pixels below the row that reported the failure.
73
+
74
+ **Nothing is persisted or replayed.** A `CUSTOM` event never enters the message
75
+ list, which is the correct half of the carrier split — a delegation that was
76
+ live an hour ago is not live now. Reload mid-run and the tool card is still
77
+ there while the nested detail is not; that is the intended behaviour rather
78
+ than a gap. Like `ag_ui.invalidate`, the name is routed rather than forwarded,
79
+ so it does not also arrive as an `ag-ui-custom` event; every other name still
80
+ reaches the host untouched.
81
+
82
+ A child's calls are keyed by the child's own `toolCallId`, so the `tool_call`
83
+ that opens one and the `tool_result` that settles it are one row updated in
84
+ place. The wire's tri-state `ok` is kept as one: absent while the call runs,
85
+ and only then a mark, since flattening "in flight" into "failed" would draw
86
+ every running call as a failure for as long as it ran. The row sits outside the
87
+ card body so it survives every `data-tool-display` mode — a progress line
88
+ visible only in `full` would leave the stall it exists to end.
89
+
90
+ New parts: `tool-card-subagent` on the card's region, and `subagent`,
91
+ `subagent-row`, `subagent-icon`, `subagent-status`, `subagent-steps`,
92
+ `subagent-step`, `subagent-step-icon`, `subagent-step-name` inside it. New
93
+ strings: `subAgentWorking`, `subAgentSteps`.
94
+
95
+ Tested against a fixture generated by the server's own encoder rather than a
96
+ hand-written double, replayed both through the subscriber and, in Chromium,
97
+ through the real `HttpAgent` from Server-Sent Events — with the resulting boxes
98
+ measured at sidebar and phone widths, since the row is a new control in a card
99
+ that already existed.
100
+
101
+ - **`formatToolPayload` — a host hook for what a tool card's body says.** The
102
+ card pretty-printed its two payloads as JSON and offered no way in, so a
103
+ thirty-field result rendered as a wall of text where the host wanted a table
104
+ or a sentence. `ClientTool.render` could not answer it: it is handed the
105
+ *arguments* only, and a server-side tool has no `ClientTool` at all, which
106
+ left the result region the one part of the transcript a host could not reach.
107
+ The hook is asked about each region of each card and may return a `Node`, a
108
+ `string`, or `null` to leave the built-in rendering alone.
109
+
110
+ **Both halves, one hook, told apart by `kind`.** They take different code
111
+ paths — arguments are rendered when the card is built, the result when it
112
+ settles — so covering only the result would have left the other half needing a
113
+ second hook later, and two hooks differing only in which region they draw is a
114
+ worse surface than one that says which. They do not carry the same thing,
115
+ which is why the payload is a discriminated union rather than a flat
116
+ `(toolName, payload, kind)`: `arguments` hands over the parsed record the call
117
+ was made with, `result` the raw string the tool returned plus the outcome it
118
+ settled on. Flattening them would have forced every formatter to re-derive
119
+ which it had, and re-serialised the arguments for nothing.
120
+
121
+ **Scoped to presentation, deliberately.** The card and the model already read
122
+ separate copies of a tool result — the model's is maintained by
123
+ `@ag-ui/client` from the same event and persisted with the history, and the
124
+ card has always shown that string reformatted — so a formatter changes what
125
+ the person reads and nothing the agent reads. That is what makes restyling
126
+ safe here, and equally why *rewording* was rejected: renaming a value belongs
127
+ on the server, where it reaches the model's prose too, instead of leaving the
128
+ card disagreeing with the answer beside it. A returned string is set as text
129
+ and never parsed as markup, so this is not a second HTML channel into the
130
+ transcript.
131
+
132
+ A region a formatter drew is marked `data-formatted`, which relaxes the
133
+ preformatted whitespace the JSON block relies on — a table would otherwise
134
+ inherit it as mangled cell spacing. Whitespace only: the card's face, frame
135
+ and scroll cap stay, because the card is one visual object and a payload sized
136
+ for a wide page must still be contained by a sidebar. `ToolPayload`,
137
+ `ToolPayloadFormatter` and `ToolCallCardOptions` are exported for a host
138
+ building cards itself.
139
+
140
+ - **`data-max-tool-rounds` — the tool-round budget is configurable.** The cap on
141
+ frontend tool-call to re-run rounds within one send was the constant
142
+ `MAX_TOOL_ROUNDS` (10) with one read and no way to change it. Ten suits a chat
143
+ whose tools answer questions; a page-driving deployment reaches it
144
+ legitimately — filling a form is one round per field — and the symptom is not
145
+ an error but an answer that stops mid-task, which reads as the model giving
146
+ up. `AgUiClientConfig.maxToolRounds` is the seam for a host driving the client
147
+ directly. A value below one is ignored rather than honoured: it would not be a
148
+ smaller budget but a send that never runs the agent at all, which would look
149
+ exactly like a broken endpoint. Validation lives in the client, so the
150
+ attribute and the config option cannot drift apart.
151
+
152
+ - **`data-message-actions` — the message action row has an opt-out.** The row
153
+ shipped with `::part()` hooks and no off switch, and ran on every finished
154
+ assistant bubble; a host embedding the component in a constrained surface had
155
+ no way to suppress it. The attribute is a comma list of the actions to keep
156
+ (`copy` / `retry` / `feedback`), and `="false"` — the spelling its sibling
157
+ gesture `data-quote-selection` already uses — leaves none. Absent means all
158
+ three, so the attribute only ever subtracts and a host that never sets it
159
+ keeps exactly what it had.
160
+
161
+ **Per-action rather than one switch**, because the three disappear for
162
+ different reasons: the rating pair is only useful to a host listening for
163
+ `ag-ui-feedback` and is two dead buttons otherwise, retry re-runs the agent
164
+ which a constrained surface may forbid, and copy is the one nobody objects to.
165
+ A single switch would have made dropping either of the first two cost the
166
+ third — and a host wanting one gone would have rebuilt the row from
167
+ `attachMessageActions`, reimplementing the part names, the accessible grouping
168
+ and the retry hand-off in order to lose two buttons. With nothing left the row
169
+ is not built at all: an empty one still takes its margin and still announces
170
+ itself to a screen reader as a group of actions. `MESSAGE_ACTIONS` is exported
171
+ as the token vocabulary.
172
+
173
+ ### Changed
174
+
175
+ - **`MessageActionsOptions.text` is optional**, and its absence is what omits
176
+ the copy button — the same idiom `onFeedback` already used, where what a
177
+ button needs to do its job is also the statement that it belongs. Additive for
178
+ existing callers.
179
+
180
+ ### Documentation
181
+
182
+ - **The README's API reference now agrees with the source, and a test keeps it
183
+ there.** `tests/readme_api_surface.test.ts` asserted only that every exported
184
+ *name* appeared somewhere in the README, which is the weakest claim a document
185
+ can make about a symbol: two of the wrong descriptions below were about
186
+ symbols the README named correctly and then described wrongly, so they passed
187
+ the gate as written. It now checks the claims the README actually makes,
188
+ wherever those can be derived from the source cheaply — the completeness of
189
+ the attribute, method and property lists; the live / connect-time split
190
+ against `observedAttributes` itself; the members of a documented object shape
191
+ against the interface that declares them; the parameter count of a documented
192
+ call or arrow type; that every `chat.x` the README writes names a real member;
193
+ and the markdown mechanics that make a claim readable at all — a link
194
+ resolving to a heading, a table row not split by a bare `|`, a run of rows not
195
+ orphaned from its header. The file states its own boundary in a comment: it reads structure and
196
+ never semantics, so return types, parameter types, prose and inherited
197
+ interface members are deliberately outside it. Reimplementing a TypeScript
198
+ parser here would cost more than the drift it caught.
199
+
200
+ - **`title-text` was documented as "the only observed attribute".** It is one of
201
+ eighteen, and the fourteen it did not mention are exactly the ones whose whole
202
+ purpose is to warn a framework host that a late attribute write is inert — so
203
+ the sentence told a reader that the machinery built for their case does not
204
+ exist. A new *When each attribute is read* subsection splits the observed set
205
+ into the four that are live and the fourteen that are connect-time, says what
206
+ a late write to each does, and names the one attribute
207
+ (`data-launcher-icon-url`) that is read while connecting but is not observed,
208
+ so a late write to that one is inert *and* silent.
209
+
210
+ - **`UploadHandler` was documented without the `signal` that prevents a leak.**
211
+ The type takes a third `signal?: AbortSignal`, fired when the tray removes a
212
+ chip or the element is torn down. A tus or direct-to-S3 adapter written from
213
+ the two-parameter signature orphans a server-side file on every removed chip,
214
+ which is a storage bill rather than a visible bug. `UploadOptions` and
215
+ `TranscribeOptions` were each missing `credentials` for the same reason
216
+ nothing noticed: an omitted option reads as an option that does not exist.
217
+
218
+ - **Other corrections found by the same sweep.** `parseToolCatalog` was still
219
+ documented with its pre-0.28.0 name-to-summary return, though it returns
220
+ `Record<string, ToolCatalogEntry>`; `QuestionRenderer` pointed at
221
+ `AgUiChat.questionRenderer`, which has never existed (the property is
222
+ `askUserRenderer`); `ConfirmationOptions`, `ApprovalOptions` and
223
+ `ApprovalRequest` each omitted a member; `quotableSelection` was documented
224
+ with two of its three parameters; the `TOOL_DISPLAY` constants row omitted
225
+ `inline` though the attribute row had it; the Methods list was eight short and
226
+ the Properties list nine, including the deprecated `registerStateHook` and the
227
+ `closeCheckpoints` / `toggleCheckpoints` pair; two rows hid an unescaped `|`
228
+ inside a code span, which splits the row wherever the README is rendered; a
229
+ paragraph with a code block sat in the middle of the attribute table, breaking
230
+ it into two tables and orphaning the seven rows below it; and two links
231
+ pointed at an `#events` section that has never existed.
232
+
10
233
  ## [0.29.0] — 2026-08-29
11
234
 
12
235
  ### Added
@@ -2365,7 +2588,9 @@ hosts that both arrange the page the way it expects.
2365
2588
  ### Notes
2366
2589
  - First release — exercising the automated npm OIDC publish pipeline end-to-end.
2367
2590
 
2368
- [Unreleased]: https://github.com/Artui/ag-ui-web-component/compare/v0.29.0...HEAD
2591
+ [Unreleased]: https://github.com/Artui/ag-ui-web-component/compare/v0.31.0...HEAD
2592
+ [0.31.0]: https://github.com/Artui/ag-ui-web-component/compare/v0.30.0...v0.31.0
2593
+ [0.30.0]: https://github.com/Artui/ag-ui-web-component/compare/v0.29.0...v0.30.0
2369
2594
  [0.29.0]: https://github.com/Artui/ag-ui-web-component/compare/v0.28.0...v0.29.0
2370
2595
  [0.28.0]: https://github.com/Artui/ag-ui-web-component/compare/v0.27.0...v0.28.0
2371
2596
  [0.27.0]: https://github.com/Artui/ag-ui-web-component/compare/v0.26.1...v0.27.0
package/README.md CHANGED
@@ -49,6 +49,7 @@ No framework, no Django, no admin specifics live here. Downstream consumers (e.g
49
49
  - [New chat and collapse](#new-chat-and-collapse)
50
50
  - [Collapsing to the launcher](#collapsing-to-the-launcher)
51
51
  - [Tool-call display modes](#tool-call-display-modes)
52
+ - [Delegated sub-agents](#delegated-sub-agents)
52
53
  - [Markdown rendering](#markdown-rendering)
53
54
  - [Follow-up suggestions](#follow-up-suggestions)
54
55
  - [Editing a gated call before approving it](#editing-a-gated-call-before-approving-it)
@@ -161,7 +162,7 @@ another origin, add `credentials="include"` too; see
161
162
  | --- | --- | --- |
162
163
  | `endpoint` | `endpoint` | The AG-UI endpoint URL. Required to send. Reflecting getter + setter. |
163
164
  | `credentials` | `credentials` | Cookie policy for every request the element makes: `omit` / `same-origin` / `include`. Unset means the browser default (`same-origin`), which sends no cookies cross-origin. See [Authenticating requests](#authenticating-requests). |
164
- | `title-text` | — | Header label; defaults to `"Assistant"`. The only **observed** attribute (live-updates the header). |
165
+ | `title-text` | — | Header label; defaults to `"Assistant"`. Live: writing it after the element connects re-labels the header. See [When each attribute is read](#when-each-attribute-is-read). |
165
166
  | `data-tool-display` | `toolDisplay` | Tool-call card detail: `inline` / `minimal` / `compact` / `full` (default `full`). |
166
167
  | `data-text-animation` | — | Incoming-text reveal: `none` (default) / `fade` / `word`. |
167
168
  | `data-prompt-chips` | — | Present (bare, or any value but `"false"`) to surface skills as chips. |
@@ -183,6 +184,15 @@ another origin, add `credentials="include"` too; see
183
184
  | `data-launcher-icon-url` | — | Icon image URL for the collapsed launcher only, when it should differ from the header's. Falls back to `data-icon-url`; a slotted `slot="launcher"` wins over both. |
184
185
  | `data-unread-badge` | — | **On by default.** `="false"` hides the launcher's unread badge; the count and the `ag-ui-unread` event keep running. See [Collapsing to the launcher](#collapsing-to-the-launcher). |
185
186
  | `data-quote-selection` | — | **On by default.** `="false"` stops the transcript offering to quote a selection. `quote()` keeps working either way. See [Quoting a selection](#quoting-a-selection). |
187
+ | `data-message-actions` | — | **All on by default.** A comma list of the actions a finished answer keeps: `copy` / `retry` / `feedback` (e.g. `"copy,retry"`). `="false"` removes the row entirely. See [Message actions](#message-actions-copy-retry-feedback). |
188
+ | `data-max-tool-rounds` | — | Upper bound on frontend tool-call → re-run rounds within one send (default 10; a value below 1 is ignored). Raise it for a page-driving agent whose turn takes many small steps. See [The run loop](#the-run-loop-and-the-ag-ui-client). |
189
+ | `data-page-actions` | — | Opt-in built-in page-action tools: a comma list of `scroll` / `drag` (e.g. `"scroll,drag"`). See [Page-action tools](#page-action-tools). |
190
+ | `data-side` | — | CSS-only, for `placement="sidebar"`: which edge it docks to — `right` (default) / `left`. |
191
+ | `data-answer-well` | — | CSS-only boolean: box each assistant turn (its text, tool cards, and thinking) in one bordered "well". Off by default. See [The answer well](#the-answer-well). |
192
+ | `collapsed` | `collapsed` | Reflected boolean; collapses the widget to its [launcher](#collapsing-to-the-launcher) (a rail under `placement="sidebar"`, the header bar under `embedded` / `page`). Persisted per-tab in `sessionStorage`. |
193
+ | `theme` | — | CSS-only: `light` (default) / `dark` / `auto` / `code`. |
194
+ | `density` | — | CSS-only: `comfortable` (default) / `compact`. |
195
+ | `placement` | — | CSS-only: `floating` (default) / `bottom-left` / `side` / `sidebar` / `full` / `page` / `embedded`. |
186
196
 
187
197
  Each header control also takes its own icon slot — `icon-history`, `icon-checkpoints`,
188
198
  `icon-new`, `icon-collapse` — with the built-in glyph as the fallback, so a host can project a
@@ -194,19 +204,39 @@ same way: `icon-send`, `icon-stop`, `icon-attach`, `icon-voice`.
194
204
  <svg slot="icon-new" width="16" height="16"><!-- ... --></svg>
195
205
  </ag-ui-chat>
196
206
  ```
197
- | `data-page-actions` | — | Opt-in built-in page-action tools: a comma list of `scroll` / `drag` (e.g. `"scroll,drag"`). See [Page-action tools](#page-action-tools). |
198
- | `data-side` | — | CSS-only, for `placement="sidebar"`: which edge it docks to — `right` (default) / `left`. |
199
- | `data-answer-well` | — | CSS-only boolean: box each assistant turn (its text, tool cards, and thinking) in one bordered "well". Off by default. See [The answer well](#the-answer-well). |
200
- | `collapsed` | `collapsed` | Reflected boolean; collapses the widget to its [launcher](#collapsing-to-the-launcher) (a rail under `placement="sidebar"`, the header bar under `embedded` / `page`). Persisted per-tab in `sessionStorage`. |
201
- | `theme` | — | CSS-only: `light` (default) / `dark` / `auto` / `code`. |
202
- | `density` | — | CSS-only: `comfortable` (default) / `compact`. |
203
- | `placement` | — | CSS-only: `floating` (default) / `bottom-left` / `side` / `sidebar` / `full` / `page` / `embedded`. |
204
207
 
205
- **Properties** (JS only, not attributes): `headers`, `getHeaders`, `allowImages`, `autoConfirm`,
206
- `confirmPredicate`, `askUser`, `agentFactory`, `getTools`, `getContext`, `routeMap`, `navigate`,
207
- `getPageMap`, `autoInjectPageMap`, `conversationStore`, `uploadHandler`, `transcribeHandler`,
208
- `navigationResult`, `skillContext`, `toolSummaries`, `strings`, `resolvePageTarget`, plus the
209
- mirrors `endpoint` / `userKey` / `toolDisplay` / `collapsed` / `credentials`.
208
+ #### When each attribute is read
209
+
210
+ The element observes two groups of attributes, and they behave differently once it is in the DOM.
211
+ Nothing outside those groups is observed: a CSS-only attribute (`theme`, `density`, `data-side`,
212
+ `data-answer-well`) is read by the stylesheet rather than by script, and `endpoint`,
213
+ `data-tool-display`, `data-text-animation`, `data-runs-url`, `data-page-actions`,
214
+ `data-message-actions`, `data-max-tool-rounds`, `data-unread-badge` and `data-quote-selection` are
215
+ re-read at each use, so a late write to any of those simply takes effect. The one attribute in
216
+ neither camp is `data-launcher-icon-url`: it is read while the element connects, like the group
217
+ below, but is not observed, so a late write is inert and says nothing.
218
+
219
+ **Live attributes.** Written at any time, before or after the element connects, and acted on
220
+ either way: `title-text`, `placement`, `credentials`, `user-key`.
221
+
222
+ **Connect-time attributes.** Read once, while the element connects, to decide what chrome exists at
223
+ all — the tray, the mic, the skills menu, the header mark. Writing one afterwards has **no effect**;
224
+ the element logs a console warning naming the attribute rather than failing silently, because the
225
+ symptom is an affordance that never appears and that reads as a broken component. Set them before
226
+ the element enters the DOM, or remove and re-insert it. See
227
+ [Framework hosts](#framework-hosts-configure-before-you-insert), where the boundary bites hardest.
228
+ The list: `data-attachments-url`, `data-attachment-accept`, `data-attachment-max-bytes`,
229
+ `data-transcribe-url`, `data-threads-url`, `data-threads-cache`, `data-tools-url`,
230
+ `data-skills-url`, `data-skills`, `data-prompt-chips`, `data-slash-commands`, `data-theme-toggle`,
231
+ `data-strings`, `data-icon-url`.
232
+
233
+ **Properties** (JS only, not attributes): `headers`, `getHeaders`, `trustedOrigins`, `allowImages`,
234
+ `autoConfirm`, `confirmPredicate`, `askUser`, `askUserRenderer`, `approvalRenderer`,
235
+ `approveWithEdits`, `agentFactory`, `getTools`, `getContext`, `routeMap`, `navigate`, `getPageMap`,
236
+ `autoInjectPageMap`, `conversationStore`, `uploadHandler`, `transcribeHandler`, `navigationResult`,
237
+ `skillContext`, `toolSummaries`, `formatToolPayload`, `formatRelativeTime`, `strings`,
238
+ `resolvePageTarget`, `sharedState`, plus the read-only `unread` and `unhandledActivityTypes`, and
239
+ the attribute mirrors `endpoint` / `userKey` / `toolDisplay` / `collapsed` / `credentials`.
210
240
 
211
241
  `headers` and `getHeaders` authenticate **every** request the element makes, not only the agent
212
242
  run; `getHeaders` is the one to use for a credential that rotates. See
@@ -226,15 +256,20 @@ the django-ag-ui `@tool` registry), whose schema never reaches the browser — e
226
256
  labels are fetched automatically — per card, `x-summary` → an explicit
227
257
  `toolSummaries` entry → the fetched catalog → the raw name.
228
258
 
229
- **Properties** (selected): `sharedState` AG-UI shared state (documented under Tools & state).
259
+ `sharedState` is AG-UI shared state, documented under
260
+ [the run loop](#the-run-loop-and-the-ag-ui-client); `unread` and `unhandledActivityTypes` are
261
+ read-only counters, covered under [the unread badge](#the-unread-badge) and
262
+ [finding out what arrived](#finding-out-what-arrived).
230
263
 
231
264
  Code blocks in an agent's answer carry a **copy button**, revealed on hover or
232
265
  keyboard focus and styleable via the `code-copy` part. Override its labels with
233
266
  the `copyCode` / `copied` / `copyFailed` strings.
234
267
 
235
- **Methods**: `registerTool`, `registerPageState`, `setSkills`, `sendMessage`, `attachFile`,
236
- `appendMessage`, `newChat`, `setCollapsed`, `toggleCollapsed`, `toggleTheme`, `openThreads`,
237
- `openCheckpoints`, `reload`.
268
+ **Methods**: `registerTool`, `registerPageState`, `registerActivityRenderer`, `setSkills`,
269
+ `sendMessage`, `attachFile`, `appendMessage`, `retryLastTurn`, `quote`, `offerQuoteInPage`,
270
+ `enableCharts`, `newChat`, `setCollapsed`, `toggleCollapsed`, `toggleTheme`, `openThreads`,
271
+ `openCheckpoints`, `closeCheckpoints`, `toggleCheckpoints`, `reload`, and the deprecated
272
+ `registerStateHook` (renamed to `registerPageState`).
238
273
 
239
274
  ### Sending from your own UI
240
275
 
@@ -486,7 +521,12 @@ an AG-UI `AbstractAgent`. On the first send the element builds a client (via the
486
521
  render into a bubble; each `TOOL_CALL_END` becomes a tool-call card.
487
522
  3. Any **frontend** tool calls collected during the run are executed locally, their results are
488
523
  appended as `tool` messages, and the agent is re-run with the results.
489
- 4. This repeats until the agent stops calling frontend tools, bounded by `MAX_TOOL_ROUNDS`.
524
+ 4. This repeats until the agent stops calling frontend tools, bounded by `MAX_TOOL_ROUNDS`
525
+ (10) — raise it with `data-max-tool-rounds`, or `AgUiClientConfig.maxToolRounds` when you
526
+ drive the client yourself. The default suits a chat whose tools answer questions; a
527
+ page-driving deployment reaches it legitimately, one round per field filled, and the symptom
528
+ is not an error but an answer that stops mid-task. A value below 1 is ignored rather than
529
+ honoured — it would be a send that never runs the agent at all.
490
530
 
491
531
  Tool calls the client doesn't own (server-side tools the server already executed) are left alone —
492
532
  the loop doesn't re-run them, but their streamed `TOOL_CALL_RESULT` is rendered into the tool-call
@@ -1076,7 +1116,7 @@ interchangeable:
1076
1116
  | | Carrier | Reaches | Persisted | Replayed |
1077
1117
  | --- | --- | --- | --- | --- |
1078
1118
  | **Content** | `ACTIVITY_SNAPSHOT` | the transcript | yes | yes |
1079
- | **Imperative** | `CUSTOM` | your page, as [`ag-ui-custom`](#events) | no | no |
1119
+ | **Imperative** | `CUSTOM` | your page, as [`ag-ui-custom`](#host-seams-the-spa-story) | no | no |
1080
1120
 
1081
1121
  ⇒ **Content has a place in the conversation and should come back. An imperative
1082
1122
  has no place and no meaning once acted on** — replaying "refetch the board" on
@@ -1118,6 +1158,42 @@ visible. Style them via the `tool-card-args` / `tool-card-result` parts, their h
1118
1158
  transcript, the way `data-answer-well` does — the modes are pure visibility over one DOM shape,
1119
1159
  selected by the shadow CSS from the host attribute.
1120
1160
 
1161
+ ### Drawing a card's body yourself
1162
+
1163
+ A thirty-field result is a wall of JSON where a host wanted a table, or a
1164
+ sentence. `formatToolPayload` is the seam: it is asked about each region of each
1165
+ card and may return a `Node` to take it over, a `string` to replace its text, or
1166
+ `null` to leave the built-in pretty-print alone.
1167
+
1168
+ ```js
1169
+ chat.formatToolPayload = (payload) => {
1170
+ if (payload.kind !== "result" || payload.toolName !== "list_orders") {
1171
+ return null; // everything else keeps the default rendering
1172
+ }
1173
+ const table = document.createElement("table");
1174
+ // ... build it from JSON.parse(payload.text)
1175
+ return table;
1176
+ };
1177
+ ```
1178
+
1179
+ Both halves come through the same hook, told apart by `kind`: `arguments`
1180
+ carries the parsed record the call was made with, `result` the raw string the
1181
+ tool returned plus the outcome it settled on. A region a formatter took over is
1182
+ marked `data-formatted`, which relaxes the preformatted whitespace the default
1183
+ JSON block relies on — a table would otherwise inherit it as mangled cell
1184
+ spacing. Whitespace only: the card's face, frame and scroll cap stay, so one long
1185
+ payload still cannot stretch the transcript, and a host wanting different
1186
+ typography restyles the `tool-card-result` part.
1187
+
1188
+ This is **presentation, not translation.** The card and the model already read
1189
+ separate copies of a tool result — the model's is maintained by `@ag-ui/client`
1190
+ from the same event — so a formatter changes what the person reads and nothing
1191
+ the agent reads. That is what makes restyling safe here, and it is also why
1192
+ *rewording* belongs on the server: renamed there, the new wording reaches the
1193
+ model's prose too, instead of leaving the card disagreeing with the answer beside
1194
+ it. A returned string is set as text, never parsed as markup — this is not a
1195
+ second HTML channel into the transcript.
1196
+
1121
1197
  A gated call carries the decision (`approved by you` / `declined by you`, part
1122
1198
  `tool-card-decision`, attribute `data-decision`) — from the client-side confirmation card and
1123
1199
  from the server-side approval interrupt alike. The prompt itself disappears once answered: a
@@ -1144,6 +1220,72 @@ speed; the spin respects `prefers-reduced-motion`).
1144
1220
 
1145
1221
  ---
1146
1222
 
1223
+ ## Delegated sub-agents
1224
+
1225
+ A run that hands work to a sub-agent reads as a stall. The parent's
1226
+ `delegate_task` card sits at "running…" for the child's entire duration —
1227
+ however many tools the child calls, however long it takes — with nothing on
1228
+ screen to say anything is happening.
1229
+
1230
+ If your server narrates that, the component draws it. The wire is an ordinary
1231
+ AG-UI `CUSTOM` event named `ag_ui.subagent`, carrying:
1232
+
1233
+ | Key | Meaning |
1234
+ | --- | --- |
1235
+ | `delegationId` | the **parent's own `delegate_task` tool-call id** — not the child's run id |
1236
+ | `agent` | the child agent's name |
1237
+ | `phase` | one of `started`, `tool_call`, `tool_result`, `finished`, `failed` |
1238
+ | `status` | a pre-rendered line, ready to show |
1239
+ | `tool` | `toolCallId`, `name` and `ok`, on the two tool phases only |
1240
+
1241
+ Exactly one `started` opens a delegation and exactly one `finished` or `failed`
1242
+ closes it. `ok` is a tri-state: `null` while the child's call runs, `true` on a
1243
+ result it accepted, `false` on one that came back to it.
1244
+
1245
+ Because the key is the *parent's* call id, the surface attaches to a card that
1246
+ already exists rather than floating a second element with the same identity: one
1247
+ **collapsed row per delegation**, live, carrying the server's `status` line and
1248
+ nothing else, expanding onto the child's own tool calls. A ten-step child costs
1249
+ one row until somebody opens it, and there is no second visual language — it
1250
+ reads the way tool cards already read.
1251
+
1252
+ `status` is why the collapsed row needs no wording of its own. The structured
1253
+ keys are there for a host that would rather write its own.
1254
+
1255
+ The row shows in **every** [display mode](#tool-call-display-modes), including
1256
+ `minimal`. It sits outside the card body rather than in it, because the body is
1257
+ what the density modes hide — and a live progress line that only appeared in
1258
+ `full` would leave exactly the stall it exists to end. Same reasoning that shows
1259
+ a deferred card's arguments whatever the mode.
1260
+
1261
+ **A failure carries no exception text on this channel, deliberately** — the same
1262
+ reasoning that redacts a `RUN_ERROR`, since an exception's words are written for
1263
+ an operator. The detail rides the ordinary tool result for that delegation, which
1264
+ lands in the same card's `Result` region a few pixels below. Nothing here invents
1265
+ words the server declined to send.
1266
+
1267
+ **None of it is persisted.** A `CUSTOM` event never enters the message list, so
1268
+ nothing replays on a thread restore — which is the right half of the
1269
+ [carrier split](#which-carrier-should-the-server-use): a delegation that was live
1270
+ an hour ago is not live now, and replaying its progress would be a lie about a
1271
+ run that is over. Reload mid-run and the tool card is still there; the nested
1272
+ detail is not. That is the intended behaviour.
1273
+
1274
+ Like `ag_ui.invalidate`, this name is **routed** rather than forwarded: it draws
1275
+ itself and does not also arrive as an `ag-ui-custom` event. Every other name
1276
+ still reaches your page untouched.
1277
+
1278
+ Style it through `tool-card-subagent` (the region inside the card), `subagent`,
1279
+ `subagent-row`, `subagent-icon`, `subagent-status`, `subagent-steps`,
1280
+ `subagent-step`, `subagent-step-icon` and `subagent-step-name`. The two glyph
1281
+ states reuse the card's own `--ag-ui-tool-icon-done` / `--ag-ui-tool-icon-error`
1282
+ properties and its spinner speed, so re-theming the cards re-themes these. The
1283
+ row's own chrome comes from `subAgentWorking` and `subAgentSteps` in
1284
+ [`UiStrings`](#internationalization-i18n); everything else on the row is the
1285
+ server's text.
1286
+
1287
+ ---
1288
+
1147
1289
  ## Resizing the panel
1148
1290
 
1149
1291
  The panel carries a drag handle on its leading corner (or leading edge, docked),
@@ -1297,7 +1439,10 @@ message's own text.
1297
1439
  rather than being told its last answer was wrong.
1298
1440
  - **Copy** puts the message's text on the clipboard, and says so on the button.
1299
1441
  A refused clipboard permission is reported there too, rather than thrown.
1300
- - **Thumbs up / down** fire [`ag-ui-feedback`](#events) and **store nothing**.
1442
+ - **Thumbs up / down** fire `ag-ui-feedback` (wired below) and **store nothing**.
1443
+ **Off unless you ask for them** — `data-message-actions="copy,retry,feedback"`.
1444
+ Without a listener the buttons still latch, so a reader is told a rating was
1445
+ taken while nothing recorded it.
1301
1446
 
1302
1447
  Retry sits on the **last** answer only. Re-running an older turn is branching,
1303
1448
  and for a page-driving agent editing a past turn is not neutral — those turns
@@ -1316,6 +1461,25 @@ less. This is why a dropped connection is still rendered as an **error** rather
1316
1461
  than demoted to a run notice — a notice "never settles, takes no action, and
1317
1462
  carries no controls", and a failure with a way back needs one.
1318
1463
 
1464
+ The row can be trimmed, or removed, with `data-message-actions` — a comma list of
1465
+ the actions to keep, or `="false"` for none at all:
1466
+
1467
+ ```html
1468
+ <!-- copy only: nothing here listens for a rating, and the surface forbids re-runs -->
1469
+ <ag-ui-chat endpoint="/agent/" data-message-actions="copy"></ag-ui-chat>
1470
+ ```
1471
+
1472
+ The default is `copy,retry`. Those two work with nothing wired; the rating pair
1473
+ needs a listener, so it is asked for rather than assumed.
1474
+
1475
+ It is per-action rather than one switch because the three disappear for
1476
+ different reasons. Thumbs are only useful to a host listening for
1477
+ `ag-ui-feedback`, and two buttons that lead nowhere are worse than none. Retry
1478
+ re-runs the agent, which a constrained surface may not permit. Copy is the one
1479
+ nobody objects to — and with a single switch, dropping either of the others would
1480
+ have cost it too. Nothing survives, and no row is built at all: an empty row
1481
+ still takes its margin and still announces itself as a group of actions.
1482
+
1319
1483
  `retryLastTurn()` is public, for a host driving its own message UI.
1320
1484
 
1321
1485
  ```js
@@ -1963,9 +2127,9 @@ re-export point. Internal modules import from leaf paths.
1963
2127
  | `isNavigates(parameters)` | function | Read the `x-navigates` flag. |
1964
2128
  | `createPageActionTools(enabled, resolveTarget)` | function | Build the opt-in `scroll_to` / `drag_and_drop` tools. |
1965
2129
  | `PAGE_ACTIONS` | const | The page-action opt-in tokens (`scroll` / `drag`). |
1966
- | `ResolvePageTarget` | type | `(target) => HTMLElement | null` — the page-target resolver. |
2130
+ | `ResolvePageTarget` | type | `(target) => HTMLElement \| null` — the page-target resolver. |
1967
2131
  | `X_DESTRUCTIVE_KEY` / `X_NAVIGATES_KEY` | const | The JSON-Schema extension keys. |
1968
- | `parseToolCatalog(data)` | function | Parse a fetched `data-tools-url` catalog into a `name` `summary` map. |
2132
+ | `parseToolCatalog(data)` | function | Parse a fetched `data-tools-url` catalog into a `Record<string, ToolCatalogEntry>` — whole entries, not bare summaries, so a caller can reach `description` too. Malformed input yields an empty map rather than throwing. |
1969
2133
  | `ToolCatalogEntry` | type | One row of that catalog. |
1970
2134
  | `prettifyToolName(name)` | function | Last fallback of the tool-card label chain (`delete_record` reads as *Delete record*). |
1971
2135
 
@@ -2008,15 +2172,15 @@ re-export point. Internal modules import from leaf paths.
2008
2172
  | `RunIndex` | class | Reads a `data-runs-url` run index and derives its resume / fork endpoints. |
2009
2173
  | `RunRow` | type | One run index row (`{ run_id, thread_id, parent_run_id, started_at, continuable, preview? }`). |
2010
2174
  | `CheckpointMenu` | class | The *Continue a run* panel. |
2011
- | `CheckpointVerb` | type | `"resume" | "fork"`. |
2175
+ | `CheckpointVerb` | type | `"resume" \| "fork"`. |
2012
2176
 
2013
2177
  ### Attachments
2014
2178
 
2015
2179
  | Export | Kind | Summary |
2016
2180
  | --- | --- | --- |
2017
2181
  | `uploadAttachment(file, options)` | function | The built-in upload (multipart, progress) → `AttachmentRef`. |
2018
- | `UploadOptions` | type | `{ url, headers?, onProgress?, signal? }`. |
2019
- | `UploadHandler` | type | `(file, onProgress) => Promise<AttachmentRef>` — the `uploadHandler` swap seam (TUS / S3). |
2182
+ | `UploadOptions` | type | `{ url, headers?, credentials?, onProgress?, signal? }`. `credentials` is spelled as a fetch mode but carried by `XMLHttpRequest.withCredentials`, so only `"include"` is distinguishable. |
2183
+ | `UploadHandler` | type | `(file, onProgress, signal?) => Promise<AttachmentRef>` — the `uploadHandler` swap seam (tus / S3). The signal fires when the tray removes a chip or the element is torn down; a handler that honours it aborts its own transport, so a cancelled upload leaves no orphaned file on the server. |
2020
2184
  | `AttachmentRef` | type | The durable upload ref (`{ id, name, mime, size, url? }`). |
2021
2185
  | `messageAttachments(message)` | function | Read the refs a restored user message carries. |
2022
2186
 
@@ -2025,7 +2189,7 @@ re-export point. Internal modules import from leaf paths.
2025
2189
  | Export | Kind | Summary |
2026
2190
  | --- | --- | --- |
2027
2191
  | `transcribeAudio(audio, options)` | function | The built-in transcription POST (multipart) → the transcript text. |
2028
- | `TranscribeOptions` | type | `{ url, headers? }`. |
2192
+ | `TranscribeOptions` | type | `{ url, headers?, credentials? }` — `credentials` as fetch's own cookie mode. |
2029
2193
  | `TranscribeHandler` | type | `(audio) => Promise<string>` — the `transcribeHandler` swap seam (Web Speech, direct-to-provider). |
2030
2194
 
2031
2195
  ### UI & DOM primitives
@@ -2034,28 +2198,31 @@ re-export point. Internal modules import from leaf paths.
2034
2198
  | --- | --- | --- |
2035
2199
  | `ToolCallCard` | class | A live tool-call card for the transcript. |
2036
2200
  | `ToolCallStatus` / `SettledStatus` / `ToolDisplayMode` | type | Card lifecycle states + display mode. |
2201
+ | `ToolPayloadFormatter` | type | Draws one region of a card's body (`AgUiChat.formatToolPayload`); `null` falls through to the built-in pretty-print. |
2202
+ | `ToolPayload` | type | The region being drawn: `arguments` (the parsed record) or `result` (the raw string and its outcome). |
2203
+ | `ToolCallCardOptions` | type | Per-card wiring beyond name / args / label / strings — currently `formatPayload`. |
2037
2204
  | `requestConfirmation(host, request, options?)` | function | Append the inline confirmation card to the transcript. |
2038
2205
  | `ConfirmationRequest` | type | What the card displays. |
2039
- | `ConfirmationOptions` | type | `{ signal?, strings? }` — abort resolves the card as declined; `strings` localizes it. |
2206
+ | `ConfirmationOptions` | type | `{ signal?, strings?, onAlwaysAllow? }` — abort resolves the card as declined; `strings` localizes it; passing `onAlwaysAllow` is what adds the third button. |
2040
2207
  | `UiStrings` | type | The flat table of every user-facing string. |
2041
2208
  | `DEFAULT_UI_STRINGS` | const | The English defaults (the override floor). |
2042
2209
  | `mergeUiStrings(overrides)` | function | Merge a partial override over the defaults. |
2043
2210
  | `renderMarkdown(text, options?)` | function | Render sanitized markdown/HTML (marked + DOMPurify). |
2044
2211
  | `RenderMarkdownOptions` | type | `{ allowImages? }` — opt `<img>` back into the sanitized output. |
2045
2212
  | `requestApproval(host, request, options?)` | function | Append the inline approval card that gates a server-side tool. |
2046
- | `ApprovalRequest` | type | What that card displays (`{ message?, toolName? }`). |
2047
- | `ApprovalOptions` | type | `{ signal?, strings? }` — abort resolves the card as denied; `strings` localizes it. |
2213
+ | `ApprovalRequest` | type | What that card displays (`{ message?, toolName?, args? }`). |
2214
+ | `ApprovalOptions` | type | `{ signal?, strings?, onEdit? }` — abort resolves the card as denied; `strings` localizes it; passing `onEdit` offers the call's arguments for editing and is called only when they actually changed. |
2048
2215
  | `ApprovalRenderer` | type | Replace the built-in approval card outright (`AgUiChat.approvalRenderer`). |
2049
2216
  | `requestQuestion(host, request, options?)` | function | Append the inline `ask_user` card (radios and/or free text). |
2050
2217
  | `QuestionRequest` | type | What that card asks. |
2051
2218
  | `QuestionOptions` | type | `{ signal?, strings? }` — abort resolves it with an empty answer. |
2052
- | `QuestionRenderer` | type | Replace the built-in question card outright (`AgUiChat.questionRenderer`). |
2219
+ | `QuestionRenderer` | type | Replace the built-in question card outright (`AgUiChat.askUserRenderer`). |
2053
2220
  | `renderChart(spec)` | function | Draw one spec as a self-contained block, or `null` when it says nothing. |
2054
2221
  | `chartSpecFrom(value)` | function | Read an arbitrary payload into a `ChartSpec`, or `null` if it cannot be drawn honestly. |
2055
2222
  | `ChartSpec` / `ChartSeries` / `ChartKind` | type | A chart as data, one named series, and how it is drawn. |
2056
2223
  | `attachQuoteOffer(options)` | function | The page-side select-then-quote offer, with its guards. `AgUiChat.offerQuoteInPage()` is the one-line form. |
2057
2224
  | `PageQuoteOffer` / `PageQuoteOfferOptions` | type | The live offer (`{ element, detach }`) and what it takes. |
2058
- | `quotableSelection(container, roots)` | function | The current selection when it lies inside `container`, read through the shadow-aware API where the engine has one. |
2225
+ | `quotableSelection(container, roots, near?)` | function | The current selection when it lies inside `container`, read through the shadow-aware API where the engine has one. `near` is where the gesture ended, used to pick the line the offer hangs from. |
2059
2226
  | `QuotableSelection` | type | `{ text, rect }` — what was selected, and where it sits. |
2060
2227
  | `asQuote(text)` | function | Shape text as a markdown blockquote with a blank line after it. |
2061
2228
  | `MAX_QUOTE_CHARS` | const | The cap a quotation is truncated to (500). |
@@ -2087,8 +2254,9 @@ re-export point. Internal modules import from leaf paths.
2087
2254
  | `COMPACTION_ACTIVITY_TYPE` | The `ACTIVITY_SNAPSHOT` type reporting a trimmed history. |
2088
2255
  | `LOAD_CAPABILITY_TOOL` | The agent-side capability-loading tool's name. |
2089
2256
  | `MESSAGE_ROLE` | Message role constants. |
2257
+ | `MESSAGE_ACTIONS` | The message-action tokens `data-message-actions` selects by (`copy` / `retry` / `feedback`). |
2090
2258
  | `TOOL_CALL_STATUS` | Tool-call card status constants. |
2091
- | `TOOL_DISPLAY` | Tool-call display-mode constants (`minimal` / `compact` / `full`). |
2259
+ | `TOOL_DISPLAY` | Tool-call display-mode constants (`inline` / `minimal` / `compact` / `full`). |
2092
2260
  | `X_CONFIRM_KEY` | Confirmation-prompt key: on a tool's JSON Schema for a client-side confirmation, and in an AG-UI interrupt's `metadata` for a server-side approval. |
2093
2261
  | `X_SUMMARY_KEY` | JSON-Schema key carrying a short tool-card label. |
2094
2262
  | `MAX_TOOL_ROUNDS` | Upper bound on tool-call → re-run rounds per send. |
@@ -2227,7 +2395,8 @@ component sets, so a new one cannot ship undocumented.
2227
2395
  | Follow-up suggestions | `suggestions`, `suggestion-chip` |
2228
2396
  | Message actions | `message-actions`, `message-action` (plus `message-action-retry`, `message-action-copy`, `message-action-up`, `message-action-down`) |
2229
2397
  | Run notices | `run-notice` (plus `run-notice-interrupted`, `run-notice-attachment-pending`, `run-notice-compaction`, `run-notice-skill`, `run-notice-history-replaced`, `run-notice-chart-undrawable`), `run-notice-icon`, `run-notice-text` |
2230
- | Tool cards | `tool-card`, `tool-card-head`, `tool-card-icon`, `tool-card-name`, `tool-card-status`, `tool-card-decision`, `tool-card-toggle`, `tool-card-body`, `tool-card-section` (plus `tool-card-args-section`, `tool-card-result-section`), `tool-card-section-label` (plus `tool-card-args-label`, `tool-card-result-label`), `tool-card-args`, `tool-card-result`, `tool-card-approval` |
2398
+ | Tool cards | `tool-card`, `tool-card-head`, `tool-card-icon`, `tool-card-name`, `tool-card-status`, `tool-card-decision`, `tool-card-toggle`, `tool-card-body`, `tool-card-section` (plus `tool-card-args-section`, `tool-card-result-section`), `tool-card-section-label` (plus `tool-card-args-label`, `tool-card-result-label`), `tool-card-args`, `tool-card-result`, `tool-card-approval`, `tool-card-subagent` |
2399
+ | Delegated sub-agents | `subagent`, `subagent-row`, `subagent-icon`, `subagent-status`, `subagent-steps`, `subagent-step`, `subagent-step-icon`, `subagent-step-name` |
2231
2400
  | Client-side confirmation | `confirm`, `confirm-body`, `confirm-args`, `confirm-actions`, `confirm-button` (plus `confirm-confirm`, `confirm-cancel`, `confirm-always`) |
2232
2401
  | Server-side approval | `approval`, `approval-body`, `approval-actions`, `approval-button` (plus `approval-approve`, `approval-deny`), `approval-edit`, `approval-args`, `approval-error` |
2233
2402
  | Typed question | `question`, `question-body`, `question-options`, `question-choice`, `question-choice-text`, `question-radio`, `question-input`, `question-actions`, `question-button` |