@artooi/ag-ui-web-component 0.29.0 → 0.30.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 +199 -1
  2. package/README.md +196 -33
  3. package/dist/ag-ui-web-component.bundle.js +211 -28
  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 +578 -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 +180 -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,203 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.30.0] — 2026-08-30
11
+
12
+ ### Added
13
+
14
+ - **The sub-agent fixture was re-copied after the server added a `timestamp` to
15
+ every `CUSTOM` event.** Reading the wire rather than the prose is what found
16
+ that gap: `CUSTOM` was the only event type in the stream without one. The
17
+ client needed no change to absorb it — the field is additive and ignored here —
18
+ which is the degradation story working, and the copy is byte-identical to the
19
+ producer's again.
20
+
21
+ - **A delegated sub-agent's progress, on the card that delegated it.** A run that
22
+ hands work to a sub-agent read as a stall: the parent's `delegate_task` card
23
+ sat at "running..." for the child's entire duration, however many tools the
24
+ child called, with nothing on screen. The component now consumes the AG-UI
25
+ `CUSTOM` event named `ag_ui.subagent` and draws it — the first real consumer of
26
+ the `onCustomEvent` carrier opened in 0.29.0.
27
+
28
+ **It attaches rather than floats, because the wire lets it.** `delegationId` is
29
+ the *parent's own* `delegate_task` tool-call id, not the child's run id, so the
30
+ thing being narrated is a card this component already drew on
31
+ `TOOL_CALL_START`. The surface is one collapsed row inside that card, carrying
32
+ the server's pre-rendered `status` line and nothing else, expanding onto the
33
+ child agent's own tool calls. A ten-step child costs one row until somebody
34
+ opens it, and there is no second visual language to learn.
35
+
36
+ Two alternatives were considered and dropped. A bare status line is cheaper and
37
+ gives up the detail entirely. Inline child cards in the main transcript
38
+ interleave parent and child with nothing marking whose is whose — and in an
39
+ order the persisted transcript, which never held the progress, cannot
40
+ reproduce.
41
+
42
+ **A failure carries no exception text on this channel, and none is invented
43
+ here.** That is the same reasoning that redacts a `RUN_ERROR`: an exception's
44
+ words are written for an operator. The detail rides the ordinary
45
+ `TOOL_CALL_RESULT` for that delegation and lands in the same card's result
46
+ region, a few pixels below the row that reported the failure.
47
+
48
+ **Nothing is persisted or replayed.** A `CUSTOM` event never enters the message
49
+ list, which is the correct half of the carrier split — a delegation that was
50
+ live an hour ago is not live now. Reload mid-run and the tool card is still
51
+ there while the nested detail is not; that is the intended behaviour rather
52
+ than a gap. Like `ag_ui.invalidate`, the name is routed rather than forwarded,
53
+ so it does not also arrive as an `ag-ui-custom` event; every other name still
54
+ reaches the host untouched.
55
+
56
+ A child's calls are keyed by the child's own `toolCallId`, so the `tool_call`
57
+ that opens one and the `tool_result` that settles it are one row updated in
58
+ place. The wire's tri-state `ok` is kept as one: absent while the call runs,
59
+ and only then a mark, since flattening "in flight" into "failed" would draw
60
+ every running call as a failure for as long as it ran. The row sits outside the
61
+ card body so it survives every `data-tool-display` mode — a progress line
62
+ visible only in `full` would leave the stall it exists to end.
63
+
64
+ New parts: `tool-card-subagent` on the card's region, and `subagent`,
65
+ `subagent-row`, `subagent-icon`, `subagent-status`, `subagent-steps`,
66
+ `subagent-step`, `subagent-step-icon`, `subagent-step-name` inside it. New
67
+ strings: `subAgentWorking`, `subAgentSteps`.
68
+
69
+ Tested against a fixture generated by the server's own encoder rather than a
70
+ hand-written double, replayed both through the subscriber and, in Chromium,
71
+ through the real `HttpAgent` from Server-Sent Events — with the resulting boxes
72
+ measured at sidebar and phone widths, since the row is a new control in a card
73
+ that already existed.
74
+
75
+ - **`formatToolPayload` — a host hook for what a tool card's body says.** The
76
+ card pretty-printed its two payloads as JSON and offered no way in, so a
77
+ thirty-field result rendered as a wall of text where the host wanted a table
78
+ or a sentence. `ClientTool.render` could not answer it: it is handed the
79
+ *arguments* only, and a server-side tool has no `ClientTool` at all, which
80
+ left the result region the one part of the transcript a host could not reach.
81
+ The hook is asked about each region of each card and may return a `Node`, a
82
+ `string`, or `null` to leave the built-in rendering alone.
83
+
84
+ **Both halves, one hook, told apart by `kind`.** They take different code
85
+ paths — arguments are rendered when the card is built, the result when it
86
+ settles — so covering only the result would have left the other half needing a
87
+ second hook later, and two hooks differing only in which region they draw is a
88
+ worse surface than one that says which. They do not carry the same thing,
89
+ which is why the payload is a discriminated union rather than a flat
90
+ `(toolName, payload, kind)`: `arguments` hands over the parsed record the call
91
+ was made with, `result` the raw string the tool returned plus the outcome it
92
+ settled on. Flattening them would have forced every formatter to re-derive
93
+ which it had, and re-serialised the arguments for nothing.
94
+
95
+ **Scoped to presentation, deliberately.** The card and the model already read
96
+ separate copies of a tool result — the model's is maintained by
97
+ `@ag-ui/client` from the same event and persisted with the history, and the
98
+ card has always shown that string reformatted — so a formatter changes what
99
+ the person reads and nothing the agent reads. That is what makes restyling
100
+ safe here, and equally why *rewording* was rejected: renaming a value belongs
101
+ on the server, where it reaches the model's prose too, instead of leaving the
102
+ card disagreeing with the answer beside it. A returned string is set as text
103
+ and never parsed as markup, so this is not a second HTML channel into the
104
+ transcript.
105
+
106
+ A region a formatter drew is marked `data-formatted`, which relaxes the
107
+ preformatted whitespace the JSON block relies on — a table would otherwise
108
+ inherit it as mangled cell spacing. Whitespace only: the card's face, frame
109
+ and scroll cap stay, because the card is one visual object and a payload sized
110
+ for a wide page must still be contained by a sidebar. `ToolPayload`,
111
+ `ToolPayloadFormatter` and `ToolCallCardOptions` are exported for a host
112
+ building cards itself.
113
+
114
+ - **`data-max-tool-rounds` — the tool-round budget is configurable.** The cap on
115
+ frontend tool-call to re-run rounds within one send was the constant
116
+ `MAX_TOOL_ROUNDS` (10) with one read and no way to change it. Ten suits a chat
117
+ whose tools answer questions; a page-driving deployment reaches it
118
+ legitimately — filling a form is one round per field — and the symptom is not
119
+ an error but an answer that stops mid-task, which reads as the model giving
120
+ up. `AgUiClientConfig.maxToolRounds` is the seam for a host driving the client
121
+ directly. A value below one is ignored rather than honoured: it would not be a
122
+ smaller budget but a send that never runs the agent at all, which would look
123
+ exactly like a broken endpoint. Validation lives in the client, so the
124
+ attribute and the config option cannot drift apart.
125
+
126
+ - **`data-message-actions` — the message action row has an opt-out.** The row
127
+ shipped with `::part()` hooks and no off switch, and ran on every finished
128
+ assistant bubble; a host embedding the component in a constrained surface had
129
+ no way to suppress it. The attribute is a comma list of the actions to keep
130
+ (`copy` / `retry` / `feedback`), and `="false"` — the spelling its sibling
131
+ gesture `data-quote-selection` already uses — leaves none. Absent means all
132
+ three, so the attribute only ever subtracts and a host that never sets it
133
+ keeps exactly what it had.
134
+
135
+ **Per-action rather than one switch**, because the three disappear for
136
+ different reasons: the rating pair is only useful to a host listening for
137
+ `ag-ui-feedback` and is two dead buttons otherwise, retry re-runs the agent
138
+ which a constrained surface may forbid, and copy is the one nobody objects to.
139
+ A single switch would have made dropping either of the first two cost the
140
+ third — and a host wanting one gone would have rebuilt the row from
141
+ `attachMessageActions`, reimplementing the part names, the accessible grouping
142
+ and the retry hand-off in order to lose two buttons. With nothing left the row
143
+ is not built at all: an empty one still takes its margin and still announces
144
+ itself to a screen reader as a group of actions. `MESSAGE_ACTIONS` is exported
145
+ as the token vocabulary.
146
+
147
+ ### Changed
148
+
149
+ - **`MessageActionsOptions.text` is optional**, and its absence is what omits
150
+ the copy button — the same idiom `onFeedback` already used, where what a
151
+ button needs to do its job is also the statement that it belongs. Additive for
152
+ existing callers.
153
+
154
+ ### Documentation
155
+
156
+ - **The README's API reference now agrees with the source, and a test keeps it
157
+ there.** `tests/readme_api_surface.test.ts` asserted only that every exported
158
+ *name* appeared somewhere in the README, which is the weakest claim a document
159
+ can make about a symbol: two of the wrong descriptions below were about
160
+ symbols the README named correctly and then described wrongly, so they passed
161
+ the gate as written. It now checks the claims the README actually makes,
162
+ wherever those can be derived from the source cheaply — the completeness of
163
+ the attribute, method and property lists; the live / connect-time split
164
+ against `observedAttributes` itself; the members of a documented object shape
165
+ against the interface that declares them; the parameter count of a documented
166
+ call or arrow type; that every `chat.x` the README writes names a real member;
167
+ and the markdown mechanics that make a claim readable at all — a link
168
+ resolving to a heading, a table row not split by a bare `|`, a run of rows not
169
+ orphaned from its header. The file states its own boundary in a comment: it reads structure and
170
+ never semantics, so return types, parameter types, prose and inherited
171
+ interface members are deliberately outside it. Reimplementing a TypeScript
172
+ parser here would cost more than the drift it caught.
173
+
174
+ - **`title-text` was documented as "the only observed attribute".** It is one of
175
+ eighteen, and the fourteen it did not mention are exactly the ones whose whole
176
+ purpose is to warn a framework host that a late attribute write is inert — so
177
+ the sentence told a reader that the machinery built for their case does not
178
+ exist. A new *When each attribute is read* subsection splits the observed set
179
+ into the four that are live and the fourteen that are connect-time, says what
180
+ a late write to each does, and names the one attribute
181
+ (`data-launcher-icon-url`) that is read while connecting but is not observed,
182
+ so a late write to that one is inert *and* silent.
183
+
184
+ - **`UploadHandler` was documented without the `signal` that prevents a leak.**
185
+ The type takes a third `signal?: AbortSignal`, fired when the tray removes a
186
+ chip or the element is torn down. A tus or direct-to-S3 adapter written from
187
+ the two-parameter signature orphans a server-side file on every removed chip,
188
+ which is a storage bill rather than a visible bug. `UploadOptions` and
189
+ `TranscribeOptions` were each missing `credentials` for the same reason
190
+ nothing noticed: an omitted option reads as an option that does not exist.
191
+
192
+ - **Other corrections found by the same sweep.** `parseToolCatalog` was still
193
+ documented with its pre-0.28.0 name-to-summary return, though it returns
194
+ `Record<string, ToolCatalogEntry>`; `QuestionRenderer` pointed at
195
+ `AgUiChat.questionRenderer`, which has never existed (the property is
196
+ `askUserRenderer`); `ConfirmationOptions`, `ApprovalOptions` and
197
+ `ApprovalRequest` each omitted a member; `quotableSelection` was documented
198
+ with two of its three parameters; the `TOOL_DISPLAY` constants row omitted
199
+ `inline` though the attribute row had it; the Methods list was eight short and
200
+ the Properties list nine, including the deprecated `registerStateHook` and the
201
+ `closeCheckpoints` / `toggleCheckpoints` pair; two rows hid an unescaped `|`
202
+ inside a code span, which splits the row wherever the README is rendered; a
203
+ paragraph with a code block sat in the middle of the attribute table, breaking
204
+ it into two tables and orphaning the seven rows below it; and two links
205
+ pointed at an `#events` section that has never existed.
206
+
10
207
  ## [0.29.0] — 2026-08-29
11
208
 
12
209
  ### Added
@@ -2365,7 +2562,8 @@ hosts that both arrange the page the way it expects.
2365
2562
  ### Notes
2366
2563
  - First release — exercising the automated npm OIDC publish pipeline end-to-end.
2367
2564
 
2368
- [Unreleased]: https://github.com/Artui/ag-ui-web-component/compare/v0.29.0...HEAD
2565
+ [Unreleased]: https://github.com/Artui/ag-ui-web-component/compare/v0.30.0...HEAD
2566
+ [0.30.0]: https://github.com/Artui/ag-ui-web-component/compare/v0.29.0...v0.30.0
2369
2567
  [0.29.0]: https://github.com/Artui/ag-ui-web-component/compare/v0.28.0...v0.29.0
2370
2568
  [0.28.0]: https://github.com/Artui/ag-ui-web-component/compare/v0.27.0...v0.28.0
2371
2569
  [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,7 @@ 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**.
1301
1443
 
1302
1444
  Retry sits on the **last** answer only. Re-running an older turn is branching,
1303
1445
  and for a page-driving agent editing a past turn is not neutral — those turns
@@ -1316,6 +1458,22 @@ less. This is why a dropped connection is still rendered as an **error** rather
1316
1458
  than demoted to a run notice — a notice "never settles, takes no action, and
1317
1459
  carries no controls", and a failure with a way back needs one.
1318
1460
 
1461
+ The row can be trimmed, or removed, with `data-message-actions` — a comma list of
1462
+ the actions to keep, or `="false"` for none at all:
1463
+
1464
+ ```html
1465
+ <!-- copy only: nothing here listens for a rating, and the surface forbids re-runs -->
1466
+ <ag-ui-chat endpoint="/agent/" data-message-actions="copy"></ag-ui-chat>
1467
+ ```
1468
+
1469
+ It is per-action rather than one switch because the three disappear for
1470
+ different reasons. Thumbs are only useful to a host listening for
1471
+ `ag-ui-feedback`, and two buttons that lead nowhere are worse than none. Retry
1472
+ re-runs the agent, which a constrained surface may not permit. Copy is the one
1473
+ nobody objects to — and with a single switch, dropping either of the others would
1474
+ have cost it too. Nothing survives, and no row is built at all: an empty row
1475
+ still takes its margin and still announces itself as a group of actions.
1476
+
1319
1477
  `retryLastTurn()` is public, for a host driving its own message UI.
1320
1478
 
1321
1479
  ```js
@@ -1963,9 +2121,9 @@ re-export point. Internal modules import from leaf paths.
1963
2121
  | `isNavigates(parameters)` | function | Read the `x-navigates` flag. |
1964
2122
  | `createPageActionTools(enabled, resolveTarget)` | function | Build the opt-in `scroll_to` / `drag_and_drop` tools. |
1965
2123
  | `PAGE_ACTIONS` | const | The page-action opt-in tokens (`scroll` / `drag`). |
1966
- | `ResolvePageTarget` | type | `(target) => HTMLElement | null` — the page-target resolver. |
2124
+ | `ResolvePageTarget` | type | `(target) => HTMLElement \| null` — the page-target resolver. |
1967
2125
  | `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. |
2126
+ | `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
2127
  | `ToolCatalogEntry` | type | One row of that catalog. |
1970
2128
  | `prettifyToolName(name)` | function | Last fallback of the tool-card label chain (`delete_record` reads as *Delete record*). |
1971
2129
 
@@ -2008,15 +2166,15 @@ re-export point. Internal modules import from leaf paths.
2008
2166
  | `RunIndex` | class | Reads a `data-runs-url` run index and derives its resume / fork endpoints. |
2009
2167
  | `RunRow` | type | One run index row (`{ run_id, thread_id, parent_run_id, started_at, continuable, preview? }`). |
2010
2168
  | `CheckpointMenu` | class | The *Continue a run* panel. |
2011
- | `CheckpointVerb` | type | `"resume" | "fork"`. |
2169
+ | `CheckpointVerb` | type | `"resume" \| "fork"`. |
2012
2170
 
2013
2171
  ### Attachments
2014
2172
 
2015
2173
  | Export | Kind | Summary |
2016
2174
  | --- | --- | --- |
2017
2175
  | `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). |
2176
+ | `UploadOptions` | type | `{ url, headers?, credentials?, onProgress?, signal? }`. `credentials` is spelled as a fetch mode but carried by `XMLHttpRequest.withCredentials`, so only `"include"` is distinguishable. |
2177
+ | `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
2178
  | `AttachmentRef` | type | The durable upload ref (`{ id, name, mime, size, url? }`). |
2021
2179
  | `messageAttachments(message)` | function | Read the refs a restored user message carries. |
2022
2180
 
@@ -2025,7 +2183,7 @@ re-export point. Internal modules import from leaf paths.
2025
2183
  | Export | Kind | Summary |
2026
2184
  | --- | --- | --- |
2027
2185
  | `transcribeAudio(audio, options)` | function | The built-in transcription POST (multipart) → the transcript text. |
2028
- | `TranscribeOptions` | type | `{ url, headers? }`. |
2186
+ | `TranscribeOptions` | type | `{ url, headers?, credentials? }` — `credentials` as fetch's own cookie mode. |
2029
2187
  | `TranscribeHandler` | type | `(audio) => Promise<string>` — the `transcribeHandler` swap seam (Web Speech, direct-to-provider). |
2030
2188
 
2031
2189
  ### UI & DOM primitives
@@ -2034,28 +2192,31 @@ re-export point. Internal modules import from leaf paths.
2034
2192
  | --- | --- | --- |
2035
2193
  | `ToolCallCard` | class | A live tool-call card for the transcript. |
2036
2194
  | `ToolCallStatus` / `SettledStatus` / `ToolDisplayMode` | type | Card lifecycle states + display mode. |
2195
+ | `ToolPayloadFormatter` | type | Draws one region of a card's body (`AgUiChat.formatToolPayload`); `null` falls through to the built-in pretty-print. |
2196
+ | `ToolPayload` | type | The region being drawn: `arguments` (the parsed record) or `result` (the raw string and its outcome). |
2197
+ | `ToolCallCardOptions` | type | Per-card wiring beyond name / args / label / strings — currently `formatPayload`. |
2037
2198
  | `requestConfirmation(host, request, options?)` | function | Append the inline confirmation card to the transcript. |
2038
2199
  | `ConfirmationRequest` | type | What the card displays. |
2039
- | `ConfirmationOptions` | type | `{ signal?, strings? }` — abort resolves the card as declined; `strings` localizes it. |
2200
+ | `ConfirmationOptions` | type | `{ signal?, strings?, onAlwaysAllow? }` — abort resolves the card as declined; `strings` localizes it; passing `onAlwaysAllow` is what adds the third button. |
2040
2201
  | `UiStrings` | type | The flat table of every user-facing string. |
2041
2202
  | `DEFAULT_UI_STRINGS` | const | The English defaults (the override floor). |
2042
2203
  | `mergeUiStrings(overrides)` | function | Merge a partial override over the defaults. |
2043
2204
  | `renderMarkdown(text, options?)` | function | Render sanitized markdown/HTML (marked + DOMPurify). |
2044
2205
  | `RenderMarkdownOptions` | type | `{ allowImages? }` — opt `<img>` back into the sanitized output. |
2045
2206
  | `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. |
2207
+ | `ApprovalRequest` | type | What that card displays (`{ message?, toolName?, args? }`). |
2208
+ | `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
2209
  | `ApprovalRenderer` | type | Replace the built-in approval card outright (`AgUiChat.approvalRenderer`). |
2049
2210
  | `requestQuestion(host, request, options?)` | function | Append the inline `ask_user` card (radios and/or free text). |
2050
2211
  | `QuestionRequest` | type | What that card asks. |
2051
2212
  | `QuestionOptions` | type | `{ signal?, strings? }` — abort resolves it with an empty answer. |
2052
- | `QuestionRenderer` | type | Replace the built-in question card outright (`AgUiChat.questionRenderer`). |
2213
+ | `QuestionRenderer` | type | Replace the built-in question card outright (`AgUiChat.askUserRenderer`). |
2053
2214
  | `renderChart(spec)` | function | Draw one spec as a self-contained block, or `null` when it says nothing. |
2054
2215
  | `chartSpecFrom(value)` | function | Read an arbitrary payload into a `ChartSpec`, or `null` if it cannot be drawn honestly. |
2055
2216
  | `ChartSpec` / `ChartSeries` / `ChartKind` | type | A chart as data, one named series, and how it is drawn. |
2056
2217
  | `attachQuoteOffer(options)` | function | The page-side select-then-quote offer, with its guards. `AgUiChat.offerQuoteInPage()` is the one-line form. |
2057
2218
  | `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. |
2219
+ | `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
2220
  | `QuotableSelection` | type | `{ text, rect }` — what was selected, and where it sits. |
2060
2221
  | `asQuote(text)` | function | Shape text as a markdown blockquote with a blank line after it. |
2061
2222
  | `MAX_QUOTE_CHARS` | const | The cap a quotation is truncated to (500). |
@@ -2087,8 +2248,9 @@ re-export point. Internal modules import from leaf paths.
2087
2248
  | `COMPACTION_ACTIVITY_TYPE` | The `ACTIVITY_SNAPSHOT` type reporting a trimmed history. |
2088
2249
  | `LOAD_CAPABILITY_TOOL` | The agent-side capability-loading tool's name. |
2089
2250
  | `MESSAGE_ROLE` | Message role constants. |
2251
+ | `MESSAGE_ACTIONS` | The message-action tokens `data-message-actions` selects by (`copy` / `retry` / `feedback`). |
2090
2252
  | `TOOL_CALL_STATUS` | Tool-call card status constants. |
2091
- | `TOOL_DISPLAY` | Tool-call display-mode constants (`minimal` / `compact` / `full`). |
2253
+ | `TOOL_DISPLAY` | Tool-call display-mode constants (`inline` / `minimal` / `compact` / `full`). |
2092
2254
  | `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
2255
  | `X_SUMMARY_KEY` | JSON-Schema key carrying a short tool-card label. |
2094
2256
  | `MAX_TOOL_ROUNDS` | Upper bound on tool-call → re-run rounds per send. |
@@ -2227,7 +2389,8 @@ component sets, so a new one cannot ship undocumented.
2227
2389
  | Follow-up suggestions | `suggestions`, `suggestion-chip` |
2228
2390
  | Message actions | `message-actions`, `message-action` (plus `message-action-retry`, `message-action-copy`, `message-action-up`, `message-action-down`) |
2229
2391
  | 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` |
2392
+ | 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` |
2393
+ | Delegated sub-agents | `subagent`, `subagent-row`, `subagent-icon`, `subagent-status`, `subagent-steps`, `subagent-step`, `subagent-step-icon`, `subagent-step-name` |
2231
2394
  | Client-side confirmation | `confirm`, `confirm-body`, `confirm-args`, `confirm-actions`, `confirm-button` (plus `confirm-confirm`, `confirm-cancel`, `confirm-always`) |
2232
2395
  | Server-side approval | `approval`, `approval-body`, `approval-actions`, `approval-button` (plus `approval-approve`, `approval-deny`), `approval-edit`, `approval-args`, `approval-error` |
2233
2396
  | Typed question | `question`, `question-body`, `question-options`, `question-choice`, `question-choice-text`, `question-radio`, `question-input`, `question-actions`, `question-button` |