@artooi/ag-ui-web-component 0.45.0 → 0.47.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 (91) hide show
  1. package/CHANGELOG.md +146 -1
  2. package/README.md +191 -25
  3. package/dist/ag-ui-web-component.bundle.js +399 -157
  4. package/dist/ag-ui-web-component.bundle.js.map +4 -4
  5. package/dist/constants.d.ts +14 -1
  6. package/dist/constants.d.ts.map +1 -1
  7. package/dist/core/ag_ui_chat.d.ts +47 -4
  8. package/dist/core/ag_ui_chat.d.ts.map +1 -1
  9. package/dist/core/chat_registry.d.ts +23 -0
  10. package/dist/core/chat_registry.d.ts.map +1 -0
  11. package/dist/core/events/run_finished_detail.d.ts +6 -0
  12. package/dist/core/events/run_finished_detail.d.ts.map +1 -1
  13. package/dist/core/events/thread_detail.d.ts +27 -0
  14. package/dist/core/events/thread_detail.d.ts.map +1 -0
  15. package/dist/core/run_handlers.d.ts +6 -1
  16. package/dist/core/run_handlers.d.ts.map +1 -1
  17. package/dist/dom/animations.d.ts +21 -6
  18. package/dist/dom/animations.d.ts.map +1 -1
  19. package/dist/dom/highlight_overlay.d.ts +7 -1
  20. package/dist/dom/highlight_overlay.d.ts.map +1 -1
  21. package/dist/dom/ring_color.d.ts +14 -0
  22. package/dist/dom/ring_color.d.ts.map +1 -0
  23. package/dist/index.d.ts +3 -2
  24. package/dist/index.d.ts.map +1 -1
  25. package/dist/index.js +857 -86
  26. package/dist/index.js.map +4 -4
  27. package/dist/tools/tool_catalog.d.ts +5 -0
  28. package/dist/tools/tool_catalog.d.ts.map +1 -1
  29. package/dist/tools/tool_dispatch.d.ts.map +1 -1
  30. package/dist/ui/history/conversation_history.d.ts +10 -1
  31. package/dist/ui/history/conversation_history.d.ts.map +1 -1
  32. package/dist/ui/interrupts/confirmation_styles.d.ts.map +1 -1
  33. package/dist/ui/placement/panel_placement.d.ts +6 -0
  34. package/dist/ui/placement/panel_placement.d.ts.map +1 -1
  35. package/dist/ui/placement/small_viewport_styles.d.ts.map +1 -1
  36. package/dist/ui/placement/surface_for.d.ts +12 -1
  37. package/dist/ui/placement/surface_for.d.ts.map +1 -1
  38. package/dist/ui/progress/thoughts_styles.d.ts.map +1 -1
  39. package/dist/ui/progress/tool_call_card.d.ts +8 -0
  40. package/dist/ui/progress/tool_call_card.d.ts.map +1 -1
  41. package/dist/ui/progress/tool_group_styles.d.ts +2 -0
  42. package/dist/ui/progress/tool_group_styles.d.ts.map +1 -0
  43. package/dist/ui/progress/tool_step_group.d.ts +47 -0
  44. package/dist/ui/progress/tool_step_group.d.ts.map +1 -0
  45. package/dist/ui/progress/tool_step_styles.d.ts.map +1 -1
  46. package/dist/ui/shell/token_styles.d.ts.map +1 -1
  47. package/dist/ui/styles.d.ts.map +1 -1
  48. package/dist/ui/themes/ledger_styles.d.ts.map +1 -1
  49. package/dist/ui/themes/soft_styles.d.ts.map +1 -1
  50. package/dist/ui/transcript/groups_tool_steps.d.ts +13 -0
  51. package/dist/ui/transcript/groups_tool_steps.d.ts.map +1 -0
  52. package/dist/ui/transcript/message_styles.d.ts.map +1 -1
  53. package/dist/ui/transcript/transcript.d.ts +16 -1
  54. package/dist/ui/transcript/transcript.d.ts.map +1 -1
  55. package/dist/ui/ui_strings.d.ts +18 -0
  56. package/dist/ui/ui_strings.d.ts.map +1 -1
  57. package/package.json +1 -1
  58. package/src/constants.ts +15 -1
  59. package/src/core/ag_ui_chat.ts +122 -11
  60. package/src/core/chat_registry.ts +53 -0
  61. package/src/core/events/run_finished_detail.ts +6 -0
  62. package/src/core/events/thread_detail.ts +26 -0
  63. package/src/core/run_handlers.ts +9 -4
  64. package/src/dom/animations.ts +30 -31
  65. package/src/dom/highlight_overlay.ts +9 -5
  66. package/src/dom/ring_color.ts +85 -0
  67. package/src/index.ts +3 -0
  68. package/src/tools/tool_catalog.ts +23 -10
  69. package/src/tools/tool_dispatch.ts +26 -11
  70. package/src/ui/history/conversation_history.ts +28 -4
  71. package/src/ui/interrupts/confirmation_styles.ts +0 -2
  72. package/src/ui/placement/embedded_styles.ts +2 -2
  73. package/src/ui/placement/panel_placement.ts +34 -9
  74. package/src/ui/placement/preset_styles.ts +1 -1
  75. package/src/ui/placement/sidebar_styles.ts +1 -1
  76. package/src/ui/placement/small_viewport_styles.ts +5 -0
  77. package/src/ui/placement/surface_for.ts +12 -1
  78. package/src/ui/progress/thoughts_styles.ts +0 -2
  79. package/src/ui/progress/tool_call_card.ts +29 -8
  80. package/src/ui/progress/tool_group_styles.ts +232 -0
  81. package/src/ui/progress/tool_step_group.ts +272 -0
  82. package/src/ui/progress/tool_step_styles.ts +0 -2
  83. package/src/ui/shell/token_styles.ts +4 -2
  84. package/src/ui/styles.ts +5 -0
  85. package/src/ui/themes/ledger_styles.ts +15 -0
  86. package/src/ui/themes/soft_styles.ts +20 -0
  87. package/src/ui/transcript/groups_tool_steps.ts +16 -0
  88. package/src/ui/transcript/message_styles.ts +0 -2
  89. package/src/ui/transcript/transcript.ts +109 -3
  90. package/src/ui/ui_strings.ts +31 -0
  91. package/src/version.ts +1 -1
package/CHANGELOG.md CHANGED
@@ -7,6 +7,149 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.47.0] — 2026-11-07
11
+
12
+ ### Added
13
+
14
+ - **Tool cards name their status as a part.** The `tool-card` part gains a
15
+ status modifier beside it, `tool-card-pending`, `tool-card-deferred`,
16
+ `tool-card-done`, `tool-card-error`, `tool-card-declined` or
17
+ `tool-card-interrupted`, and the status pill's `tool-card-status` gains the
18
+ matching `tool-card-status-<status>`. `::part()` takes no attribute selector,
19
+ so a host could not style failures only; now
20
+ `ag-ui-chat::part(tool-card-status-error)` does. The modifier is replaced as
21
+ the status changes. The folded step group does the same with its state:
22
+ `tool-group-waiting`, `tool-group-running`, `tool-group-done` or
23
+ `tool-group-error` beside `tool-group`. **No opt-in is needed**: the base
24
+ parts are unchanged, so an existing `::part(tool-card)` rule still matches,
25
+ and a host that wants the modifiers just writes the selector.
26
+
27
+ - **`resetSize()`.** Drops a dragged size and its stored copy, so the placement's
28
+ size and the host's `--ag-ui-width` / `--ag-ui-height` apply again. For a host
29
+ entering a sized state of its own, which a user's drag would otherwise outrank.
30
+
31
+ - **A public thread API, for a host that lists conversations in its own
32
+ chrome.** `switchThread(threadId)` puts a stored thread on screen and resolves
33
+ once it is replayed; during a run it cancels the run, as the drawer and
34
+ `newChat()` do. `deleteThread(threadId)` deletes one as the drawer row does,
35
+ replacing the thread on screen with a fresh one when it was that thread. The
36
+ read-only `threadId` is the thread on screen and never mints one, unlike
37
+ `conversationStore.threadId()`. The read-only `running` is the composer's own
38
+ state, across tool rounds, a Retry and a checkpoint continuation, reflected as
39
+ `data-running` on the element. A new `ag-ui-thread` event (`THREAD_EVENT`,
40
+ typed `ThreadDetail`) fires once per change of conversation, after the
41
+ transcript is replaced, with `{ threadId, previousThreadId, reason }` and
42
+ `reason` one of `new`, `switch`, `delete`, `restore` (on connect and on
43
+ `reload()`) or `rescope` (a `user-key` change). `ag-ui-run-finished` gains
44
+ `threadId`, the thread the run belonged to. **No opt-in is needed**; all of it
45
+ is additive. A host can now drop a patch that reached the private thread
46
+ switch, an attribute observer that inferred the running state, and reads of
47
+ `conversationStore.threadId()` made only to learn the thread on screen.
48
+ `ag-ui-submit` keeps its meaning: a turn was sent, not a run started.
49
+
50
+ - **An `above-composer` slot.** Light-DOM content with
51
+ `slot="above-composer"` sits directly above the composer, after the skill
52
+ chips, the hint, the queued turns and the attachment tray, in every placement,
53
+ and moves with the composer in the greeting layout. An empty slot takes no
54
+ space. **Opt-in**: a host uses it by slotting content. A host that slotted
55
+ into `footer` and reordered the panel's flex column through `::part(composer)`
56
+ to lift it above the input can slot here instead and drop the reorder.
57
+
58
+ ### Changed
59
+
60
+ - **Highlight rings on an unthemed page follow the widget's theme.** Since
61
+ 0.45.0 the default accent is Quiet's near-black, but the rings the DOM
62
+ driver draws in the page (`flash`, `focusWithFlash`, `highlightThenClick`,
63
+ `pressThenClick`, `selectOption`, `toggleControl`, `showHighlightOverlay`)
64
+ still fell back to the old indigo. They now take the chat's resolved accent,
65
+ per theme and per scheme. A page that sets `--ag-ui-accent` on `:root`, or
66
+ passes `color`, keeps the rings it has. **No opt-in is needed.**
67
+ - New token `--ag-ui-highlight-ring-color`, beside
68
+ `--ag-ui-highlight-ring-width`. Set on `ag-ui-chat` (or above it) it
69
+ colours every ring with no `color` option, ahead of a target's
70
+ `--ag-ui-accent`; set on the page it reaches the target and wins over the
71
+ chat's.
72
+ - The press and toggle halo is now the ring colour at 40%. A host that
73
+ themes `--ag-ui-accent` had a solid halo in that colour; it is now
74
+ translucent, as the unthemed one always was.
75
+ - The rings read the most recently connected `<ag-ui-chat>`. A page with two
76
+ passes the acting one as the new `chat` option, which every ring-drawing
77
+ primitive and `showHighlightOverlay` take.
78
+
79
+ - **A dragged panel size stays out of the host's tokens and out of the phone
80
+ shape.** The drag, and the size restored before first paint, no longer write
81
+ `--ag-ui-width` / `--ag-ui-height` on the element; they write the private
82
+ `--_drag-width` / `--_drag-height`, still persisted per tab under the same key.
83
+ Above 600px the dragged size beats the host's tokens, `!important` included:
84
+ the private properties are read before the public ones, so a host rule such as
85
+ `ag-ui-chat.expanded { --ag-ui-width: 900px !important }` used to beat a drag
86
+ and now loses to it until `resetSize()` is called. At 600px or less the
87
+ small-viewport block does not read the dragged size, so a persisted or freshly
88
+ dragged size leaves the full-bleed shape alone, including after a window
89
+ narrows. **Who owes what**: a host with no sized state of its own owes nothing.
90
+ A host that enters a sized state (an "expanded" class, a mode switch) and
91
+ relied on `!important` to outrank a user's drag must call `resetSize()` when it
92
+ enters that state. A host that read `--ag-ui-width` back from the element's
93
+ inline style to learn the dragged size must measure the element instead.
94
+
95
+ ## [0.46.0] — 2026-11-05
96
+
97
+ ### Changed
98
+
99
+ - **Tool calls fold into a step group by default.** `data-tool-display`
100
+ gains `grouped`, and it is the new default (also for an absent or unknown
101
+ value). A run's consecutive tool calls fold into one quiet row, "Worked
102
+ through 5 steps", with a note such as "1 failed", and the per-call cards one
103
+ click down as `full` cards. Anything else in the turn ends the group, except
104
+ a call's own rendered output. **No
105
+ opt-in is needed, and a host that wants the old per-call cards opts out with
106
+ `data-tool-display="full"`**; `inline`, `minimal`, `compact` and `full`
107
+ render exactly as in 0.45.0. Grouping is the same under every theme; Soft
108
+ draws the row as a tinted pill and Ledger as a monospace heading.
109
+ - The group opens itself while a step waits on the user (a server-side
110
+ approval, which renders inside its card), once per wait; the user can
111
+ fold it again. It opens before the approval is drawn, so the Approve
112
+ button takes focus and scrolls into view, as it does under `full`. A
113
+ client-side confirmation or an `ask_user` question renders beside the
114
+ group and is in view without it opening, and the row reads "waiting for
115
+ you" for as long as one is open rather than "running".
116
+ - A running sub-agent does not open it: the folded row keeps its spinner
117
+ and names the delegation ("Delegated to researcher").
118
+ - Repeated labels are told apart inside a group, "Fill field" then "Fill
119
+ field (2)", in an element of its own; `tool-card-name` is never rewritten.
120
+ - A tool's rendered output (a chart) is placed after the group, in call
121
+ order, so it is never folded away, and a later call still joins the
122
+ group above it.
123
+ - Grouping is decided as each card is built. Switching to `grouped` folds
124
+ only calls drawn afterwards; switching to a card mode flattens existing
125
+ groups by CSS into the cards that mode builds.
126
+ - A restored conversation folds the same way as the live one.
127
+
128
+ ### Added
129
+
130
+ - Parts `tool-group`, `tool-group-head`, `tool-group-icon`,
131
+ `tool-group-label`, `tool-group-meta`, `tool-group-list`, and
132
+ `tool-card-repeat` on a repeated step.
133
+ - The `--ag-ui-ring` token (the group head's focus outline) and the
134
+ `--ag-ui-ico-chevron` mask image (its disclosure chevron).
135
+ - Strings `toolGroupSteps`, `toolGroupWorking`, `toolGroupWorked`,
136
+ `toolGroupWaiting`, `toolGroupFailed`, `toolGroupDeclined` (`{count}`) and
137
+ `toolStepRepeat` (`{n}`).
138
+ - `TOOL_DISPLAY.GROUPED`.
139
+ - A `data-asking` attribute on a tool-call card while a question or a
140
+ confirmation is open on it. It is internal, read by the step group; the
141
+ card's `data-status` is unchanged.
142
+
143
+ ### Fixed
144
+
145
+ - **The wheel scrolls the transcript when the pointer rests on a tool call's
146
+ arguments or result, a code block, the reasoning or a confirmation's
147
+ arguments.** Those blocks swallowed the wheel even when they had nothing
148
+ left to scroll, so the transcript sat still until the pointer moved to plain
149
+ text. They now hand the wheel on to the transcript, including a wheel that
150
+ carries a little sideways motion, as a real mouse or trackpad nearly always
151
+ does. This is long-standing and needs no opt-in.
152
+
10
153
  ## [0.45.0] — 2026-11-05
11
154
 
12
155
  ### Added
@@ -4703,7 +4846,9 @@ hosts that both arrange the page the way it expects.
4703
4846
  ### Notes
4704
4847
  - First release — exercising the automated npm OIDC publish pipeline end-to-end.
4705
4848
 
4706
- [Unreleased]: https://github.com/Artui/ag-ui-web-component/compare/v0.45.0...HEAD
4849
+ [Unreleased]: https://github.com/Artui/ag-ui-web-component/compare/v0.47.0...HEAD
4850
+ [0.47.0]: https://github.com/Artui/ag-ui-web-component/compare/v0.46.0...v0.47.0
4851
+ [0.46.0]: https://github.com/Artui/ag-ui-web-component/compare/v0.45.0...v0.46.0
4707
4852
  [0.45.0]: https://github.com/Artui/ag-ui-web-component/compare/v0.44.0...v0.45.0
4708
4853
  [0.44.0]: https://github.com/Artui/ag-ui-web-component/compare/v0.43.0...v0.44.0
4709
4854
  [0.43.0]: https://github.com/Artui/ag-ui-web-component/compare/v0.42.0...v0.43.0
package/README.md CHANGED
@@ -177,7 +177,7 @@ another origin, add `credentials="include"` too; see
177
177
  | `endpoint` | `endpoint` | The AG-UI endpoint URL. Required to send: without it a send is refused with a `console.error` naming the missing attribute and a `not-connected` run notice in the transcript. Reflecting getter + setter. |
178
178
  | `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). |
179
179
  | `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). |
180
- | `data-tool-display` | `toolDisplay` | Tool-call card detail: `inline` / `minimal` / `compact` / `full` (default `full`). |
180
+ | `data-tool-display` | `toolDisplay` | How tool calls are drawn: `grouped` (the default: a run's consecutive calls fold into one row) / `inline` / `minimal` / `compact` / `full` (per-call cards; `full` was the default before `grouped`). An unknown value reads as `grouped`. See [Tool-call display modes](#tool-call-display-modes). |
181
181
  | `data-text-animation` | — | Incoming-text reveal: `none` (default) / `fade` / `word`. |
182
182
  | `data-prompt-chips` | — | Present (bare, or any value but `"false"`) to surface skills as chips. |
183
183
  | `data-slash-commands` | — | Present (bare, or any value but `"false"`) to enable the `/`-command palette. |
@@ -210,6 +210,7 @@ another origin, add `credentials="include"` too; see
210
210
  | `data-expand-corner` | — | **Written by the element, not by you.** It stamps the corner a dragged or agent-moved panel opens from, so the collapse animation starts where the panel actually is. Listed because the element reads its own stamp back; setting it yourself is overwritten on the next move. |
211
211
  | `data-greeting` | — | CSS-only: `off` turns the [greeting layout](#the-greeting-on-an-empty-page) off under `placement="page"`, where it is on by default. Any other value turns it on under `placement="embedded"`. No other placement has it. |
212
212
  | `data-empty` | — | **Written by the element, not by you.** Present while the conversation has nothing in it, which is when the greeting shows. Public as a styling hook, so the chrome around a full-page chat can react to the same state, e.g. `ag-ui-chat[data-empty] ~ .page-footer { display: none }`. |
213
+ | `data-running` | — | **Written by the element, not by you.** Present while an interaction is in flight, the same state that shows the Stop button (see the `running` property). Public as a styling hook, as `data-empty` is, so the chrome around the chat can react to it, e.g. `ag-ui-chat[data-running] ~ .toolbar button { pointer-events: none }`. |
213
214
  | `data-small-viewport` | — | CSS-only: `off` keeps the desktop layout at every width, opting out of the [small-viewport override](#small-viewports). Everything that override sets is a token you can re-state; its trigger is a media query, which is the one thing you cannot. |
214
215
  | `data-paste-attach` | — | When to turn a long text paste into an attachment instead of composer text: absent for the 5000-character default, `off` to never, or a positive number of characters. Only acts where `data-attachments-url` (or a custom `uploadHandler`) gives it somewhere to go. |
215
216
  | `data-starters` | — | JSON array of prompts offered on an empty transcript, e.g. `'["Summarise this page"]'`. Fallback content for `slot="empty"`, so slotting your own replaces them. Shares the four-prompt and 120-character limits with the suggestion chips a run pushes. Read once at connect. |
@@ -264,7 +265,7 @@ element after the fact.
264
265
  `approveWithEdits`, `agentFactory`, `getTools`, `getContext`, `routeMap`, `navigate`, `getPageMap`,
265
266
  `autoInjectPageMap`, `conversationStore`, `uploadHandler`, `transcribeHandler`, `navigationResult`,
266
267
  `skillContext`, `toolSummaries`, `formatToolPayload`, `formatRelativeTime`, `strings`,
267
- `resolvePageTarget`, `sharedState`, `extraStyles` ([unstable](#d-extrastyles-the-last-mile)), plus the read-only `unread` and `unhandledActivityTypes`, and
268
+ `resolvePageTarget`, `sharedState`, `extraStyles` ([unstable](#d-extrastyles-the-last-mile)), plus the read-only `unread`, `unhandledActivityTypes`, `threadId` and `running`, and
268
269
  the attribute mirrors `endpoint` / `userKey` / `userName` / `toolDisplay` / `collapsed` /
269
270
  `credentials`.
270
271
 
@@ -289,7 +290,8 @@ labels are fetched automatically — per card, `x-summary` → an explicit
289
290
  `sharedState` is AG-UI shared state, documented under
290
291
  [the run loop](#the-run-loop-and-the-ag-ui-client); `unread` and `unhandledActivityTypes` are
291
292
  read-only counters, covered under [the unread badge](#the-unread-badge) and
292
- [finding out what arrived](#finding-out-what-arrived).
293
+ [finding out what arrived](#finding-out-what-arrived); `threadId` and `running` are covered under
294
+ [driving the conversation from your own chrome](#driving-the-conversation-from-your-own-chrome).
293
295
 
294
296
  Code blocks in an agent's answer carry a **copy button**, revealed on hover or
295
297
  keyboard focus and styleable via the `code-copy` part. Override its labels with
@@ -298,14 +300,17 @@ the `copyCode` / `copied` / `copyFailed` strings.
298
300
  **Methods**: `registerTool`, `registerPageState`, `registerActivityRenderer`, `setSkills`,
299
301
  `sendMessage`, `attachFile`, `appendMessage`, `retryLastTurn`, `quote`, `offerQuoteInPage`,
300
302
  `enableCharts`, `newChat`, `setCollapsed`, `toggleCollapsed`, `describeSurface`, `moveTo`,
301
- `toggleTheme`, `openThreads`, `closeThreads`,
303
+ `resetSize`, `toggleTheme`, `openThreads`, `closeThreads`, `switchThread`, `deleteThread`,
302
304
  `openCheckpoints`, `closeCheckpoints`, `toggleCheckpoints`, `reload`, and the deprecated
303
305
  `registerStateHook` (renamed to `registerPageState`).
304
306
 
305
307
  ### Sending from your own UI
306
308
 
307
309
  `sendMessage(content, attachments?)` sends as if the user had typed it — user bubble,
308
- `ag-ui-submit` event, run started. Use it for an "Ask about this order" button, a command
310
+ `ag-ui-submit` event, run started. `ag-ui-submit` means a turn was sent, not that a run started:
311
+ a Retry and a run continued from the checkpoint panel start runs without one. To know whether
312
+ anything is running, read [`running`](#driving-the-conversation-from-your-own-chrome) or style on
313
+ `data-running`. Use it for an "Ask about this order" button, a command
309
314
  palette, a prompt fired when your page mounts the chat, or a composer of your own replacing the
310
315
  built-in one.
311
316
 
@@ -960,8 +965,8 @@ human-readable speed (configurable; pass small/zero durations in tests):
960
965
  paints outside the border box and any `overflow: hidden` ancestor sharing the element's box (a
961
966
  card, a table cell) clips it away entirely. It holds for **1200 ms** by default and fades out
962
967
  over the last third — a 200 ms blink is not long enough to be *found* by someone who does not
963
- yet know where to look. The colour comes from the target's own `--ag-ui-accent` (so a themed
964
- page is flashed in its own colour), or from `color`.
968
+ yet know where to look. The colour is `color` when passed, else — see
969
+ [Ring colour](#ring-colour) — the widget's own accent.
965
970
  `flash` leaves focus alone; `focusWithFlash` moves it — see "Flash versus focus" below.
966
971
  - `prefersReducedMotion()` — honoured by every primitive that *moves* something: the hold delays
967
972
  in `pressThenClick` / `selectOption` / `toggleControl` collapse to instant, `scrollIntoCenterView`
@@ -999,11 +1004,13 @@ operations a tool handler typically wants:
999
1004
  **Themed from the element you point at, not from the widget.** The overlay is appended to the
1000
1005
  document body so it can escape the clipping it exists to avoid, which means a `var()` in its own
1001
1006
  style would resolve against the body — so every token is read from the *target's* computed style
1002
- instead, the same place the flat ring reads `--ag-ui-accent`. Set them wherever they inherit to
1003
- the elements the agent touches, usually `:root`:
1007
+ instead. Set them wherever they inherit to the elements the agent touches, usually `:root`. The
1008
+ ring colour is the one exception, read from the widget as well; see
1009
+ [Ring colour](#ring-colour).
1004
1010
 
1005
1011
  ```css
1006
1012
  :root {
1013
+ --ag-ui-highlight-ring-color: #0f766e; /* also read from ag-ui-chat; see Ring colour */
1007
1014
  --ag-ui-highlight-scrim: rgba(15, 15, 25, 0.45);
1008
1015
  --ag-ui-highlight-gradient: linear-gradient(115deg, transparent 20%, #4f46e5 50%, transparent 80%);
1009
1016
  --ag-ui-highlight-ring-width: 3; /* unitless; px */
@@ -1047,6 +1054,29 @@ register the change.
1047
1054
  Each takes an element the caller has already located; host packages wrap them with
1048
1055
  environment-aware lookups (e.g. "find `#id_<name>`, then `fillField`").
1049
1056
 
1057
+ #### Ring colour
1058
+
1059
+ Every ring the driver draws — the flash outline, the overlay ring, the press and toggle halo — is
1060
+ drawn in the host's page, outside the widget, so a token set on `ag-ui-chat` never reaches it
1061
+ through the cascade. The driver reads the widget directly instead, so on a page that themes
1062
+ nothing the rings follow the widget's theme: Quiet's near-black, Soft's violet, the dark half in a
1063
+ dark scheme. When a call passes no `color`, the first of these that is set wins:
1064
+
1065
+ 1. `--ag-ui-highlight-ring-color` on the target (so `:root` reaches every ring);
1066
+ 2. `--ag-ui-highlight-ring-color` on the chat — on `ag-ui-chat` or anything above it;
1067
+ 3. the target's `--ag-ui-accent`, so a page that themes its accent keeps the rings it has;
1068
+ 4. the chat's resolved accent, its theme's default included;
1069
+ 5. the package indigo, when no chat is on the page at all.
1070
+
1071
+ ```css
1072
+ /* Rings in the brand colour, whatever the widget's accent. */
1073
+ ag-ui-chat { --ag-ui-highlight-ring-color: #0f766e; }
1074
+ ```
1075
+
1076
+ The halo of `pressThenClick` and `toggleControl` is the chosen colour at 40%. "The chat" is the
1077
+ most recently connected `<ag-ui-chat>`; on a page with two, pass the one acting as the `chat`
1078
+ option (`RingOwnerOptions`, which every ring-drawing primitive and `showHighlightOverlay` take).
1079
+
1050
1080
  ### Page-action tools
1051
1081
 
1052
1082
  Two built-in client tools let the agent perform common page interactions without every host
@@ -1138,6 +1168,9 @@ matching JS API:
1138
1168
  returning whether it went. It takes the axes the same way a user drag does, so the launcher
1139
1169
  travels with it and switching placement hands them back. Returns `false` rather than pretending
1140
1170
  when the placement owns its position or the panel fills the screen.
1171
+ - `resetSize()` — drop a size the user dragged, and its stored copy, so the placement's own
1172
+ size and your `--ag-ui-width` / `--ag-ui-height` apply again. For a host that enters a sized
1173
+ state of its own (an expanded mode): a user's drag outranks host tokens, so call this first.
1141
1174
  - `setCollapsed(collapsed)` / `toggleCollapsed()` — collapse or expand the widget. The state is
1142
1175
  reflected as the boolean `collapsed` attribute/property and persisted per-tab in
1143
1176
  `sessionStorage`, so it survives a reload.
@@ -1152,6 +1185,49 @@ chat.toggleCollapsed();
1152
1185
  chat.addEventListener("ag-ui-toggle", (e) => console.log(e.detail.collapsed));
1153
1186
  ```
1154
1187
 
1188
+ ### Driving the conversation from your own chrome
1189
+
1190
+ A host that lists conversations in its own bar, rather than in the built-in drawer, drives the
1191
+ one on screen with the drawer's own paths:
1192
+
1193
+ - `switchThread(threadId)` — put a stored thread on screen and replay it, as picking its row in
1194
+ the drawer does. Returns a `Promise<void>` that resolves once the thread is drawn. **A run in
1195
+ flight is cancelled**, as the drawer and `newChat()` cancel it; read `running` first if you
1196
+ would rather wait. Switching to the thread already on screen does nothing, and so does a call
1197
+ before the element connects.
1198
+ - `deleteThread(threadId)` — delete a stored thread, as the drawer row's delete does. Deleting
1199
+ the thread on screen cancels its run and puts a fresh thread in its place; deleting another
1200
+ leaves the screen alone. Prefer it to `conversationStore.clear()`, which deletes the record and
1201
+ nothing else: the run, the transcript and the drawer are left pointing at it.
1202
+ - `threadId` *(read-only)* — the thread on screen, `null` before the element first connects.
1203
+ It only reads. `conversationStore.threadId()` mints and stores a thread when the store points
1204
+ at none, so asking it which conversation is showing can start one.
1205
+ - `running` *(read-only)* — whether an interaction is in flight: the composer's own state, the
1206
+ one it shows Stop for, across every tool round, a Retry and a checkpoint continuation as well
1207
+ as a send. Reflected on the element as the `data-running` attribute, written by the element
1208
+ rather than by you, as `data-empty` is, so the chrome around the chat can style on it.
1209
+
1210
+ Every change of conversation fires one **`ag-ui-thread`** event (the `THREAD_EVENT` constant),
1211
+ after the transcript has been replaced, so a listener reads the conversation it is told about.
1212
+ `detail` is `{ threadId, previousThreadId, reason }` (typed `ThreadDetail`). `previousThreadId`
1213
+ is the thread the previous event reported, `null` before the first, and `reason` is one of:
1214
+
1215
+ | `reason` | When |
1216
+ | --- | --- |
1217
+ | `new` | `newChat()` or the built-in New chat button. |
1218
+ | `switch` | `switchThread()` or a row picked in the drawer. Two switches made before the first had replayed report only the second. |
1219
+ | `delete` | The thread on screen was deleted, and a fresh one took its place. |
1220
+ | `restore` | The stored thread was replayed: when the element connects (`previousThreadId` is `null` the first time), or by `reload()`. |
1221
+ | `rescope` | A `user-key` change handed the element to another principal, live or while it was out of the document. |
1222
+
1223
+ A page reload keeps restoring the thread, as [MPA durability](#mpa-durability-surviving-full-page-reloads) describes, and
1224
+ reports it as `restore`. Ignore a `reason` you do not know: more may be added.
1225
+
1226
+ ```js
1227
+ chat.addEventListener("ag-ui-thread", (e) => myBar.markActive(e.detail.threadId));
1228
+ myBar.onPick = (threadId) => chat.switchThread(threadId);
1229
+ ```
1230
+
1155
1231
  ### The composer's own keys
1156
1232
 
1157
1233
  **Enter during a run queues.** A second run cannot start while one is in flight —
@@ -1475,15 +1551,22 @@ is the honest answer to "I pushed a chart and nothing happened".
1475
1551
 
1476
1552
  ## Tool-call display modes
1477
1553
 
1478
- How much a tool-call card shows is set via the `data-tool-display` attribute (or `toolDisplay`
1479
- property), one of `inline` / `minimal` / `compact` / `full` (default `full`):
1554
+ How tool calls are drawn is set via the `data-tool-display` attribute (or `toolDisplay`
1555
+ property), one of `grouped` / `inline` / `minimal` / `compact` / `full` (default `grouped`; an
1556
+ absent or unknown value reads as `grouped`):
1480
1557
 
1558
+ - `grouped` — the default. A run's consecutive tool calls fold into one quiet row, **"Worked
1559
+ through 5 steps"**, with a note such as **"1 failed"** beside it, and the calls one click down
1560
+ as `full` cards. Anything else in the turn (text, a confirmation, a question) ends the group, and
1561
+ the next call starts a new one; a call's own rendered output does not. A group of one call shows that call's own label. See
1562
+ [The step group](#the-step-group).
1481
1563
  - `inline` — the lightest mode: a single status row (icon + summary, no card chrome) with the
1482
1564
  result behind its own toggle. Reads as one line of the answer — pairs with [the answer
1483
1565
  well](#the-answer-well).
1484
1566
  - `minimal` — tool name + status pill only.
1485
1567
  - `compact` — name + status, with arguments *and* result behind a single collapsed toggle.
1486
- - `full` — arguments visible, result behind the toggle (the default).
1568
+ - `full` — arguments visible, result behind the toggle. The default before `grouped`: set
1569
+ `data-tool-display="full"` to keep per-call cards.
1487
1570
 
1488
1571
  Whichever mode is on, a settled card's body holds **two labelled regions** — `Arguments` and
1489
1572
  `Result` (or `Error` / `Declined`) — each with its own part and each pretty-printed. They are
@@ -1491,9 +1574,66 @@ never run together into one block, so where the call ends and the answer begins
1491
1574
  visible. Style them via the `tool-card-args` / `tool-card-result` parts, their headings via
1492
1575
  `tool-card-section-label`, and the whole body via `tool-card-body`.
1493
1576
 
1494
- **The attribute is live.** Changing `data-tool-display` restyles every card already in the
1495
- transcript, the way `data-answer-well` does — the modes are pure visibility over one DOM shape,
1496
- selected by the shadow CSS from the host attribute.
1577
+ **A card names its status as a part.** `::part()` takes no attribute selector, so a rule on
1578
+ `tool-card-status` reaches every outcome alike. The card and its status pill each carry a modifier
1579
+ beside the base part, one of `pending`, `deferred`, `done`, `error`, `declined` or `interrupted`,
1580
+ and it is replaced whenever the status changes:
1581
+
1582
+ ```css
1583
+ ag-ui-chat::part(tool-card-error) { border-color: crimson; }
1584
+ ag-ui-chat::part(tool-card-status-error) { font-weight: 700; }
1585
+ ```
1586
+
1587
+ **The attribute is live.** Changing `data-tool-display` between the four card modes restyles every
1588
+ card already in the transcript, the way `data-answer-well` does — the modes are pure visibility
1589
+ over one DOM shape, selected by the shadow CSS from the host attribute. Grouping is decided as
1590
+ each card is built: switching **to** `grouped` folds only the calls drawn afterwards, and
1591
+ switching **away** from it flattens the groups already drawn, by CSS, into exactly the cards the
1592
+ new mode builds (the group stops generating a box and its row is hidden).
1593
+
1594
+ ### The step group
1595
+
1596
+ Under `grouped` the row reads, by state:
1597
+
1598
+ | State (`data-state` on the `tool-group` part) | Label | Mark |
1599
+ | --- | --- | --- |
1600
+ | `running` | "Working through 3 steps", and the running step's label as a note | spinner |
1601
+ | `waiting` | "3 steps", with "1 waiting for you" | accent dot |
1602
+ | `done` | "Worked through 3 steps" | check |
1603
+ | `error` | "Worked through 3 steps", with "1 failed" in the danger colour | error mark |
1604
+
1605
+ "N declined" is added as a note whenever a step was declined. `data-count` carries the number of
1606
+ steps and `data-open` whether the cards are showing; the head is a button with `aria-expanded`.
1607
+
1608
+ - **It opens itself while a step waits on you.** A server-side approval renders inside the gated
1609
+ call's own card, so a folded group would hide the question it is asking: the group opens when a
1610
+ step goes `deferred`, once per wait, and stays folded if you fold it again. It opens before the
1611
+ approval is drawn, so the Approve button takes focus and scrolls into view. A client-side
1612
+ confirmation and an `ask_user` question render in the answer beside the group rather than inside
1613
+ a card, so they are in view without it opening; the row still reads `waiting` ("1 waiting for
1614
+ you") while one is open, as the card carries a `data-asking` flag for the group to read, and its
1615
+ `data-status` stays `pending`.
1616
+ - **A running sub-agent does not open it.** The row keeps its spinner and names the delegation
1617
+ instead, "Delegated to researcher" (the `subAgentDelegatedTo` string).
1618
+ - **Repeated labels are told apart.** Two "Fill field" steps read "Fill field" and "Fill field
1619
+ (2)": the ordinal is an element of its own (part `tool-card-repeat`) beside the card's name, so
1620
+ `tool-card-name` is never rewritten, and a card mode hides the ordinal.
1621
+ - **Rendered output stays in view.** A tool's `render` output is placed after the group, in call
1622
+ order, rather than inside it, so a chart is never one click down. It does not end the group: a
1623
+ later call joins the group above it, which is also how a restored conversation draws the run.
1624
+
1625
+ Parts: `tool-group` (with the state beside it as `tool-group-waiting`, `tool-group-running`,
1626
+ `tool-group-done` or `tool-group-error`), `tool-group-head` (the button), `tool-group-icon`, `tool-group-label`,
1627
+ `tool-group-meta` (holding one `.tool-group-note` per note), `tool-group-list` (the cards), and
1628
+ `tool-card-repeat` on a repeated step. The focus ring is `--ag-ui-ring` and the disclosure chevron
1629
+ `--ag-ui-ico-chevron`. Strings (`strings` / `data-strings`): `toolGroupSteps`,
1630
+ `toolGroupWorking`, `toolGroupWorked` (each with `{count}`, always 2 or more), `toolGroupWaiting`,
1631
+ `toolGroupFailed`, `toolGroupDeclined` (each with `{count}`) and `toolStepRepeat` (with `{n}`).
1632
+
1633
+ ```html
1634
+ <!-- Keep per-call cards, as before grouped was the default -->
1635
+ <ag-ui-chat endpoint="/agent/" data-tool-display="full"></ag-ui-chat>
1636
+ ```
1497
1637
 
1498
1638
  ### Drawing a card's body yourself
1499
1639
 
@@ -1669,11 +1809,30 @@ and which grip carries the keyboard.
1669
1809
 
1670
1810
  ### Sizes, keyboard, and parts
1671
1811
 
1672
- A drag writes `--ag-ui-width` / `--ag-ui-height` on the host as custom
1673
- properties.
1674
-
1675
- **That alone does not leave placement in charge** — an inline custom property
1676
- still outranks a `:host([placement=…])` rule setting the same property. So the
1812
+ A drag writes the private `--_drag-width` / `--_drag-height` on the host, not
1813
+ your `--ag-ui-width` / `--ag-ui-height` tokens, which stay yours. The precedence
1814
+ is:
1815
+
1816
+ - **Above 600px**, the dragged size beats a host's `--ag-ui-width` /
1817
+ `--ag-ui-height`, `!important` included, so a user who resized the panel keeps
1818
+ their size. A host that enters a sized state of its own (an "expanded" class,
1819
+ a mode switch) calls `resetSize()` when it does, which drops the dragged size
1820
+ and its stored copy and hands control back to the host tokens. A host with no
1821
+ such state needs nothing.
1822
+ - **At 600px or less**, the small-viewport shape ignores the dragged size
1823
+ entirely, for the placements that block covers (`floating`, `bottom-left`,
1824
+ `sidebar`, `side` and the default): the panel is full-bleed whatever was
1825
+ dragged or stored, and a window narrowed after a wide-window drag goes
1826
+ full-bleed too. `page` is already that shape, and `embedded` is left to the
1827
+ host's own box, so neither is touched by the block. `data-small-viewport="off"`
1828
+ keeps a dragged size at every width on the covered placements.
1829
+
1830
+ A dragged size is a private property, so a host should not read it back from
1831
+ the element's inline style; use `getBoundingClientRect()`, or `resetSize()` to
1832
+ clear it.
1833
+
1834
+ **A custom property alone does not leave placement in charge** — an inline custom
1835
+ property still outranks a `:host([placement=…])` rule setting the same property. So the
1677
1836
  component enforces the split directly: **a placement owns the axes it fixes**,
1678
1837
  and a dragged or persisted size is only ever applied to the ones it leaves free.
1679
1838
  Switching placement hands the owned axes back. Without that, a height dragged
@@ -2397,8 +2556,10 @@ chat.navigate = (path) => router.push(path); // SPA: in-page, no reload
2397
2556
  Route map + `navigate()` and the reload model are the same feature seen from two ends.
2398
2557
 
2399
2558
  **`ag-ui-run-finished`** *(event)* — an interaction has ended, and here is what ran in it.
2400
- `detail: { tools: readonly ToolRun[] }` (typed `RunFinishedDetail`), where each `ToolRun` is
2401
- `{ name, side: "server" | "client" }` in settle order.
2559
+ `detail: { threadId, tools: readonly ToolRun[], invalidated }` (typed `RunFinishedDetail`), where
2560
+ each `ToolRun` is `{ name, side: "server" | "client" }` in settle order. `threadId` is the thread
2561
+ the run belonged to, which is not always the one on screen: a run that a new chat or a thread
2562
+ switch stopped still reports once its request closes.
2402
2563
 
2403
2564
  **This is the seam for a host that renders data the agent can change.** A server-side tool writes
2404
2565
  without your page's knowledge: nothing else the element dispatches implies "something may have moved
@@ -2681,6 +2842,7 @@ re-export point. Internal modules import from leaf paths.
2681
2842
  | `defineAgUiChat()` | function | Idempotently register the element. |
2682
2843
  | `MessageRole` | type | Role of a rendered chat message. |
2683
2844
  | `SubmitDetail` | type | `detail` shape of the submit event. |
2845
+ | `ThreadDetail` | type | `detail` shape of the `ag-ui-thread` event (`{ threadId, previousThreadId, reason }`). |
2684
2846
  | `ToggleDetail` | type | `detail` shape of the `ag-ui-toggle` event (`{ collapsed }`). |
2685
2847
  | `UnreadDetail` | type | `detail` shape of the `ag-ui-unread` event (`{ unread }`). |
2686
2848
  | `AttachmentsDetail` | type | `detail` shape of the `ag-ui-attachments` event (`{ attachments, pending }`). |
@@ -2822,7 +2984,7 @@ re-export point. Internal modules import from leaf paths.
2822
2984
  | `HighlightOverlayOptions` | type | Options for `showHighlightOverlay`. |
2823
2985
  | `fillField` / `clickElement` / `pressButton` / `selectControl` / `setControlValue` / `toggleCheckbox` | function | DOM-driver primitives. |
2824
2986
  | `setNativeValue` / `setNativeChecked` | function | Set a control via its native prototype setter (React-controlled inputs). |
2825
- | `TypeOptions` / `HighlightClickOptions` / `PressOptions` / `SelectOptions` / `ToggleOptions` / `FlashOptions` / `ScrollOptions` / `FillFieldOptions` / `TextLikeElement` | type | Primitive option shapes. |
2987
+ | `TypeOptions` / `HighlightClickOptions` / `PressOptions` / `SelectOptions` / `ToggleOptions` / `FlashOptions` / `ScrollOptions` / `FillFieldOptions` / `TextLikeElement` / `RingOwnerOptions` | type | Primitive option shapes. `RingOwnerOptions` is the `chat` the ring-drawing ones share; see [Ring colour](#ring-colour). |
2826
2988
 
2827
2989
  ### Constants
2828
2990
 
@@ -2830,6 +2992,7 @@ re-export point. Internal modules import from leaf paths.
2830
2992
  | --- | --- |
2831
2993
  | `ELEMENT_TAG` | The registered tag name (`ag-ui-chat`). |
2832
2994
  | `SUBMIT_EVENT` | The submit CustomEvent name. |
2995
+ | `THREAD_EVENT` | The conversation-changed CustomEvent name (`ag-ui-thread`). |
2833
2996
  | `TOGGLE_EVENT` | The collapse-toggle CustomEvent name (`ag-ui-toggle`). |
2834
2997
  | `UNREAD_EVENT` | The unread-count CustomEvent name (`ag-ui-unread`). |
2835
2998
  | `RUN_FINISHED_EVENT` | The interaction-finished CustomEvent name (`ag-ui-run-finished`). |
@@ -2960,7 +3123,8 @@ first, so each stays an override point. The tokens the themes add:
2960
3123
  placement squares or flattens.
2961
3124
  - **Charts:** `--ag-ui-chart-1` to `--ag-ui-chart-6`. A host's value beats the theme's.
2962
3125
  - **Status marks:** `--ag-ui-ico-check`, `--ag-ui-ico-error`, `--ag-ui-ico-declined`, mask images
2963
- drawn in the state's colour.
3126
+ drawn in the state's colour, and `--ag-ui-ico-chevron`, the step group's disclosure chevron.
3127
+ - **Focus:** `--ag-ui-ring`, the step group head's focus outline (the accent at 55%).
2964
3128
 
2965
3129
  The font is part of the theme: Quiet, Soft and Ledger set their own stack (Inter, then the system
2966
3130
  UI font), and Classic inherits the page's font as the component always did. Set
@@ -3297,7 +3461,8 @@ component sets, so a new one cannot ship undocumented.
3297
3461
  | Message actions | `message-actions`, `message-action` (plus `message-action-retry`, `message-action-copy`, `message-action-up`, `message-action-down`), and the icon holder inside each: `message-action-icon` (plus `message-action-icon-retry`, `message-action-icon-copy`, `message-action-icon-up`, `message-action-icon-down`) |
3298
3462
  | Queued messages | `queued`, `queued-chip` |
3299
3463
  | 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-surface`, `run-notice-not-connected`), `run-notice-icon`, `run-notice-text`, `run-notice-undo` |
3300
- | 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` |
3464
+ | Tool cards | `tool-card` (plus one status modifier: `tool-card-pending`, `tool-card-deferred`, `tool-card-done`, `tool-card-error`, `tool-card-declined`, `tool-card-interrupted`), `tool-card-head`, `tool-card-icon`, `tool-card-name`, `tool-card-status` (plus `tool-card-status-pending`, `tool-card-status-deferred`, `tool-card-status-done`, `tool-card-status-error`, `tool-card-status-declined`, `tool-card-status-interrupted`), `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`, `tool-card-repeat` |
3465
+ | Tool-step group | `tool-group` (plus one state modifier: `tool-group-waiting`, `tool-group-running`, `tool-group-done`, `tool-group-error`), `tool-group-head`, `tool-group-icon`, `tool-group-label`, `tool-group-meta`, `tool-group-list` |
3301
3466
  | Delegated sub-agents | `subagent`, `subagent-row`, `subagent-icon`, `subagent-status`, `subagent-steps`, `subagent-step`, `subagent-step-icon`, `subagent-step-name` |
3302
3467
  | Client-side confirmation | `confirm`, `confirm-body`, `confirm-args`, `confirm-actions`, `confirm-button` (plus `confirm-confirm`, `confirm-cancel`, `confirm-always`) |
3303
3468
  | Server-side approval | `approval`, `approval-body`, `approval-actions`, `approval-button` (plus `approval-approve`, `approval-deny`), `approval-edit`, `approval-args`, `approval-error` |
@@ -3338,6 +3503,7 @@ with a matching `slot=`):
3338
3503
  | `header-actions` | Extra controls between the title and the built-in buttons. |
3339
3504
  | `greeting` | The greeting's text, above the empty state, where the [greeting layout](#the-greeting-on-an-empty-page) is on. Replaces the text only; `empty` and the starters still render beneath it. |
3340
3505
  | `empty` | The empty-state shown before any message. |
3506
+ | `above-composer` | Directly above the composer, below the skill chips, hint, queued turns and attachment tray. Takes no space when empty, and moves with the composer in the [greeting layout](#the-greeting-on-an-empty-page). |
3341
3507
  | `footer` | Below the composer. |
3342
3508
  | `launcher` | The collapsed widget's mark — the floating launcher, or the sidebar rail. |
3343
3509
  | `icon-send` / `icon-stop` | The composer button's two glyphs (idle and mid-run). |