@artooi/ag-ui-web-component 0.38.0 → 0.40.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.
- package/CHANGELOG.md +515 -1
- package/README.md +232 -56
- package/dist/ag-ui-web-component.bundle.js +370 -139
- package/dist/ag-ui-web-component.bundle.js.map +4 -4
- package/dist/constants.d.ts +17 -14
- package/dist/constants.d.ts.map +1 -1
- package/dist/core/activity_registry.d.ts +46 -0
- package/dist/core/activity_registry.d.ts.map +1 -0
- package/dist/core/ag_ui_chat.d.ts +23 -10
- package/dist/core/ag_ui_chat.d.ts.map +1 -1
- package/dist/core/agui_client.d.ts +28 -0
- package/dist/core/agui_client.d.ts.map +1 -1
- package/dist/core/client_seed.d.ts +27 -0
- package/dist/core/client_seed.d.ts.map +1 -0
- package/dist/core/read_max_tool_rounds.d.ts +9 -0
- package/dist/core/read_max_tool_rounds.d.ts.map +1 -0
- package/dist/core/run_handlers.d.ts +80 -0
- package/dist/core/run_handlers.d.ts.map +1 -0
- package/dist/core/storage_scope.d.ts +110 -0
- package/dist/core/storage_scope.d.ts.map +1 -0
- package/dist/core/tool_outcome.d.ts +2 -3
- package/dist/core/tool_outcome.d.ts.map +1 -1
- package/dist/core/utils.d.ts +46 -0
- package/dist/core/utils.d.ts.map +1 -1
- package/dist/index.js +16268 -14747
- package/dist/index.js.map +4 -4
- package/dist/skills/skill_catalog.d.ts +67 -0
- package/dist/skills/skill_catalog.d.ts.map +1 -0
- package/dist/skills/skill_name_from.d.ts +12 -0
- package/dist/skills/skill_name_from.d.ts.map +1 -0
- package/dist/tools/tool_catalog.d.ts +79 -0
- package/dist/tools/tool_catalog.d.ts.map +1 -0
- package/dist/tools/tool_dispatch.d.ts +104 -0
- package/dist/tools/tool_dispatch.d.ts.map +1 -0
- package/dist/ui/composer/attachment_tray.d.ts.map +1 -1
- package/dist/ui/composer/auto_grow.d.ts +10 -0
- package/dist/ui/composer/auto_grow.d.ts.map +1 -0
- package/dist/ui/composer/composer_attachments.d.ts +57 -0
- package/dist/ui/composer/composer_attachments.d.ts.map +1 -0
- package/dist/ui/composer/composer_voice.d.ts +48 -0
- package/dist/ui/composer/composer_voice.d.ts.map +1 -0
- package/dist/ui/composer/voice_input.d.ts.map +1 -1
- package/dist/ui/excerpts/transcript_quote_offer.d.ts +57 -0
- package/dist/ui/excerpts/transcript_quote_offer.d.ts.map +1 -0
- package/dist/ui/fill_ui_string.d.ts +32 -0
- package/dist/ui/fill_ui_string.d.ts.map +1 -0
- package/dist/ui/history/conversation_history.d.ts +203 -0
- package/dist/ui/history/conversation_history.d.ts.map +1 -0
- package/dist/ui/history/relative_time.d.ts.map +1 -1
- package/dist/ui/history/thread_drawer.d.ts +0 -14
- package/dist/ui/history/thread_drawer.d.ts.map +1 -1
- package/dist/ui/interrupts/confirmation_card.d.ts.map +1 -1
- package/dist/ui/interrupts/pending_decision.d.ts +23 -0
- package/dist/ui/interrupts/pending_decision.d.ts.map +1 -0
- package/dist/ui/placement/is_collapsible_placement.d.ts +18 -0
- package/dist/ui/placement/is_collapsible_placement.d.ts.map +1 -0
- package/dist/ui/placement/is_draggable_placement.d.ts +9 -0
- package/dist/ui/placement/is_draggable_placement.d.ts.map +1 -0
- package/dist/ui/placement/launcher_drag.d.ts +6 -0
- package/dist/ui/placement/launcher_drag.d.ts.map +1 -1
- package/dist/ui/placement/panel_placement.d.ts +119 -0
- package/dist/ui/placement/panel_placement.d.ts.map +1 -0
- package/dist/ui/progress/run_announcer.d.ts +41 -0
- package/dist/ui/progress/run_announcer.d.ts.map +1 -0
- package/dist/ui/progress/subagent_progress.d.ts +58 -0
- package/dist/ui/progress/subagent_progress.d.ts.map +1 -0
- package/dist/ui/progress/tool_call_card.d.ts.map +1 -1
- package/dist/ui/shell/adopt_styles.d.ts +23 -0
- package/dist/ui/shell/adopt_styles.d.ts.map +1 -0
- package/dist/ui/shell/glyph_slot.d.ts +7 -0
- package/dist/ui/shell/glyph_slot.d.ts.map +1 -0
- package/dist/ui/shell/header_button.d.ts +10 -0
- package/dist/ui/shell/header_button.d.ts.map +1 -0
- package/dist/ui/shell/icon_element.d.ts +12 -0
- package/dist/ui/shell/icon_element.d.ts.map +1 -0
- package/dist/ui/shell/is_unread_badge_enabled.d.ts +10 -0
- package/dist/ui/shell/is_unread_badge_enabled.d.ts.map +1 -0
- package/dist/ui/shell/read_launcher_icon_url.d.ts +8 -0
- package/dist/ui/shell/read_launcher_icon_url.d.ts.map +1 -0
- package/dist/ui/styles.d.ts +1 -1
- package/dist/ui/styles.d.ts.map +1 -1
- package/dist/ui/transcript/answer_actions.d.ts +39 -0
- package/dist/ui/transcript/answer_actions.d.ts.map +1 -0
- package/dist/ui/transcript/answer_stream.d.ts +56 -0
- package/dist/ui/transcript/answer_stream.d.ts.map +1 -0
- package/dist/ui/transcript/render_or_warn.d.ts +16 -0
- package/dist/ui/transcript/render_or_warn.d.ts.map +1 -0
- package/dist/ui/transcript/starter_chips.d.ts +21 -0
- package/dist/ui/transcript/starter_chips.d.ts.map +1 -0
- package/dist/ui/transcript/transcript.d.ts +171 -0
- package/dist/ui/transcript/transcript.d.ts.map +1 -0
- package/dist/ui/ui_strings.d.ts +29 -1
- package/dist/ui/ui_strings.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/constants.ts +17 -15
- package/src/core/activity_registry.ts +133 -0
- package/src/core/ag_ui_chat.ts +945 -4419
- package/src/core/agui_client.ts +172 -16
- package/src/core/client_seed.ts +27 -0
- package/src/core/read_max_tool_rounds.ts +13 -0
- package/src/core/remote_conversation_store.ts +1 -1
- package/src/core/run_handlers.ts +501 -0
- package/src/core/storage_scope.ts +279 -0
- package/src/core/tool_outcome.ts +5 -3
- package/src/core/utils.ts +109 -0
- package/src/skills/skill_catalog.ts +166 -0
- package/src/skills/skill_name_from.ts +19 -0
- package/src/tools/tool_catalog.ts +354 -0
- package/src/tools/tool_dispatch.ts +521 -0
- package/src/ui/composer/attachment_tray.ts +6 -5
- package/src/ui/composer/auto_grow.ts +12 -0
- package/src/ui/composer/composer_attachments.ts +324 -0
- package/src/ui/composer/composer_voice.ts +93 -0
- package/src/ui/composer/voice_input.ts +4 -4
- package/src/ui/excerpts/transcript_quote_offer.ts +198 -0
- package/src/ui/fill_ui_string.ts +41 -0
- package/src/ui/history/conversation_history.ts +699 -0
- package/src/ui/history/relative_time.ts +5 -4
- package/src/ui/history/thread_drawer.ts +3 -50
- package/src/ui/interrupts/confirmation_card.ts +4 -2
- package/src/ui/interrupts/pending_decision.ts +34 -0
- package/src/ui/placement/is_collapsible_placement.ts +19 -0
- package/src/ui/placement/is_draggable_placement.ts +23 -0
- package/src/ui/placement/launcher_drag.ts +104 -89
- package/src/ui/placement/panel_placement.ts +1130 -0
- package/src/ui/progress/run_announcer.ts +67 -0
- package/src/ui/progress/subagent_progress.ts +201 -0
- package/src/ui/progress/tool_call_card.ts +2 -0
- package/src/ui/shell/adopt_styles.ts +28 -0
- package/src/ui/shell/glyph_slot.ts +12 -0
- package/src/ui/shell/header_button.ts +21 -0
- package/src/ui/shell/icon_element.ts +33 -0
- package/src/ui/shell/is_unread_badge_enabled.ts +11 -0
- package/src/ui/shell/read_launcher_icon_url.ts +9 -0
- package/src/ui/styles.ts +301 -70
- package/src/ui/transcript/answer_actions.ts +139 -0
- package/src/ui/transcript/answer_stream.ts +123 -0
- package/src/ui/transcript/render_or_warn.ts +22 -0
- package/src/ui/transcript/starter_chips.ts +42 -0
- package/src/ui/transcript/transcript.ts +429 -0
- package/src/ui/ui_strings.ts +38 -1
- package/src/version.ts +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,518 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.40.0] — 2026-09-17
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
|
|
14
|
+
- **An `askUserRenderer` that throws now hands the question to the built-in
|
|
15
|
+
question card.** A throw, or a rejected promise, used to escape the `ask_user`
|
|
16
|
+
tool: its card settled as an error quoting the host's message, that message
|
|
17
|
+
went on to the agent as the tool result although it was never written for the
|
|
18
|
+
model, and the pending wait was never closed, so the next Stop aborted the
|
|
19
|
+
signal of a question that had already ended.
|
|
20
|
+
|
|
21
|
+
The renderer decides how the question looks, not whether it is asked -- the
|
|
22
|
+
same rule `approvalRenderer` follows -- so it now falls back to the built-in
|
|
23
|
+
card, with a `console.warn` naming the tool call, and the run carries on as if
|
|
24
|
+
no renderer had been set. The exception is a rejection after the signal has
|
|
25
|
+
fired, which is how a renderer is expected to honour Stop: that wait resolves
|
|
26
|
+
with an empty answer, as the built-in card does on the same signal, with no
|
|
27
|
+
card drawn and no warning.
|
|
28
|
+
|
|
29
|
+
- **On a small viewport the composer stays at the foot of the panel, with the
|
|
30
|
+
greeting over the space above it.** Until a conversation had something in it,
|
|
31
|
+
`page` -- and an `embedded` panel that opts into a greeting -- centred the
|
|
32
|
+
composer between the greeting and the foot at every width. Centring is a shape
|
|
33
|
+
for a screen with room to spare: on a phone the on-screen keyboard takes the
|
|
34
|
+
room, and the composer was left halfway up what remained with an empty band
|
|
35
|
+
under it, which is the part of the screen a thumb is already on. At 600px wide
|
|
36
|
+
and below it now stays at the foot, directly over the keyboard, and the
|
|
37
|
+
greeting takes the space above it. The prompts in `data-starters`, or
|
|
38
|
+
whatever a host slots as `empty`, go to the foot with it and sit against the
|
|
39
|
+
composer: a prompt chip is a way into the conversation, and next to the field
|
|
40
|
+
it starts it reads as one. The greeting takes the middle of what they leave,
|
|
41
|
+
and the middle of the transcript where there are none.
|
|
42
|
+
`data-small-viewport="off"` keeps the centred shape at every width, as it
|
|
43
|
+
keeps the rest of the desktop layout.
|
|
44
|
+
|
|
45
|
+
### Fixed
|
|
46
|
+
|
|
47
|
+
- **A tool card called with no arguments stops drawing an empty ARGUMENTS
|
|
48
|
+
heading.** The card hides the region rather than framing an empty object, but
|
|
49
|
+
the region declares its own `display`, which beats the user-agent rule for
|
|
50
|
+
the hidden attribute, so 42px of card was drawn holding a heading over
|
|
51
|
+
nothing -- on every call an agent made with no arguments, in the display mode
|
|
52
|
+
that shows arguments by default, and on a pending card's result region too.
|
|
53
|
+
|
|
54
|
+
- **A checkpoint continuation is let go of when its first save fails.** The
|
|
55
|
+
save runs through `conversationStore` synchronously inside the send, and a
|
|
56
|
+
store is the host's to replace: the built-in one swallows a write the browser
|
|
57
|
+
refused, a server-backed one need not. A throw there left the element holding
|
|
58
|
+
a client that would never run, so every later Resume or Fork was refused for
|
|
59
|
+
it with no way to clear it -- the composer's button is Send until a run
|
|
60
|
+
reports a start, so Stop was never offered -- and `sharedState` went on
|
|
61
|
+
writing into the dead client, which the next run then sent stale. The failure
|
|
62
|
+
is now reported with a `console.warn` and the panel takes another pick.
|
|
63
|
+
|
|
64
|
+
- **A turn typed between picking a checkpoint and its run starting is parked
|
|
65
|
+
rather than raced.** The composer learns a run is going from its first event,
|
|
66
|
+
a request round trip behind the pick, so its button was still Send: a turn
|
|
67
|
+
sent in that window started a second run against the conversation the
|
|
68
|
+
continuation had already frozen to save against, and whichever saved last
|
|
69
|
+
dropped the other's turn from the store while both answers streamed into one
|
|
70
|
+
transcript. The composer now queues it, as it does during a run, and sends it
|
|
71
|
+
when the continuation settles. `sendMessage` no-ops in the same window, as it
|
|
72
|
+
already did during a run.
|
|
73
|
+
|
|
74
|
+
- **An empty row of skill chips stops taking space above the composer.** The
|
|
75
|
+
row is hidden whenever the host offers no skills, which is most elements most
|
|
76
|
+
of the time, but it declares its own `display`, and an author display beats
|
|
77
|
+
the user-agent rule for the hidden attribute. So it kept its 20px of padding
|
|
78
|
+
under every placement: a band of panel between the transcript and the
|
|
79
|
+
composer, reading as the gap under whatever the transcript ended with. The
|
|
80
|
+
palette and the queued row beside it were fixed for this reason already, and
|
|
81
|
+
a tool card's payload regions turned out to be a fourth case, below.
|
|
82
|
+
|
|
83
|
+
- **A checkpoint continuation waits for the run in flight.** Picking Resume or
|
|
84
|
+
Fork while an answer was streaming started a second run beside it, and an
|
|
85
|
+
earlier continuation kept streaming where Stop no longer reached it. The pick
|
|
86
|
+
is now refused with a composer hint, as Retry is; the run in flight is left
|
|
87
|
+
alone and the typed turn stays in the composer. `continueWhileRunning` is new
|
|
88
|
+
in the string table.
|
|
89
|
+
|
|
90
|
+
- **A continued exchange is kept with its conversation.** It was never saved,
|
|
91
|
+
so a reload lost it, and the next message went out without it and with the
|
|
92
|
+
shared state from before the continuation. Each save now writes the
|
|
93
|
+
conversation with the exchange after it, through the existing `saveMessages`
|
|
94
|
+
-- `ClientConversationStore` is unchanged -- and the next message carries
|
|
95
|
+
both. After continuing from an earlier run, what is saved is what the
|
|
96
|
+
transcript shows, not only the turns up to that run. `sharedState` reads a
|
|
97
|
+
running continuation, and `AgUiClient.annotatedMessages` is new: the history
|
|
98
|
+
in the form a save writes it, with how each tool call ended.
|
|
99
|
+
|
|
100
|
+
- **A full-screen panel stays on the visible screen when a keyboard opens under
|
|
101
|
+
the field.** To show the composer, iOS Safari pans the visible area down the
|
|
102
|
+
page, and a panel anchored at the top of the screen stayed where it was: sized
|
|
103
|
+
to the visible height, it showed only its lower part, with the header and the
|
|
104
|
+
greeting off the top and an empty band above the keyboard. That covered
|
|
105
|
+
`page`, `full`, `side` and `sidebar`, and at phone width the corner placements
|
|
106
|
+
too, because they become the whole screen there, and their keyboard lift never
|
|
107
|
+
moved them: a box with its top, height and bottom all set ignores the bottom.
|
|
108
|
+
The element now also publishes how far the visible area is panned, as
|
|
109
|
+
`--ag-ui-visual-viewport-inset-top`, and every panel anchored at the top moves
|
|
110
|
+
down by as much of it as goes past the host's `--ag-ui-viewport-inset-top`.
|
|
111
|
+
`--ag-ui-keyboard-inset-top` outranks that, as `--ag-ui-keyboard-inset` does
|
|
112
|
+
at the bottom; `0px` opts out.
|
|
113
|
+
|
|
114
|
+
The same panel with a bar reserved at the top also ran past the bottom of the
|
|
115
|
+
visible area by the bar's height whenever a keyboard was up, pan or no pan:
|
|
116
|
+
the measured height replaced the host's box rather than being cut from it, so
|
|
117
|
+
the bar stayed in the position and left the height. The height is now the
|
|
118
|
+
host's box cut to the visible area.
|
|
119
|
+
|
|
120
|
+
At the deepest pan, the one that shows a composer docked at the foot of the
|
|
121
|
+
screen, the panel still went back to the top of the page at full height. The
|
|
122
|
+
element read the page's height from `innerHeight`, which iOS Safari reports
|
|
123
|
+
less the pan for as long as the pan lasts; at that depth it equals the visible
|
|
124
|
+
height, so the element took the two viewports to agree and withdrew its
|
|
125
|
+
measurements. The band below the visible area came out as nothing at any pan
|
|
126
|
+
too, leaving a bottom-anchored panel behind the keyboard. The page's height
|
|
127
|
+
is now read from the root element's `clientHeight`, which an iPhone reported
|
|
128
|
+
unchanged through the pan.
|
|
129
|
+
|
|
130
|
+
- **A full-screen panel no longer runs under Safari's bars with no keyboard
|
|
131
|
+
up.** Where nothing has been measured, the panel's height fell back to
|
|
132
|
+
`100vh`, which iOS Safari resolves to the screen with its bars collapsed, so
|
|
133
|
+
on a page that never scrolls them away the foot of the panel sat under the
|
|
134
|
+
address bar: 40px on the iPhone measured, with a docked composer in it. The
|
|
135
|
+
fallback is now `100dvh`, the screen with the bars as they are.
|
|
136
|
+
|
|
137
|
+
- **An element removed from the document and inserted again is one element.**
|
|
138
|
+
Every insertion built the chrome again into the same shadow root, so a
|
|
139
|
+
re-inserted `<ag-ui-chat>` had two headers, two composers, a second set of
|
|
140
|
+
resize grips, and the conversation drawn a second time beneath the first. The
|
|
141
|
+
listeners on its long-lived parts were added again too:
|
|
142
|
+
- one click on Stop cancelled the run three times;
|
|
143
|
+
- one press of the built-in theme toggle flipped it twice, so it appeared to
|
|
144
|
+
do nothing;
|
|
145
|
+
- a dropped or pasted file was added twice;
|
|
146
|
+
- one arrow press moved the launcher two steps.
|
|
147
|
+
|
|
148
|
+
With `data-threads-url`, a second remote store wrapped the first, so every
|
|
149
|
+
rename and delete reached the server twice. Frameworks do this without being
|
|
150
|
+
asked (a portal, a keyed list, a move between containers), and removing and
|
|
151
|
+
re-inserting is the documented way to apply a connect-time attribute written
|
|
152
|
+
late.
|
|
153
|
+
|
|
154
|
+
Connecting now builds the chrome in place of the last connection's, and ties
|
|
155
|
+
every listener on a long-lived part to the connection, so leaving the document
|
|
156
|
+
removes them. The upload tray and the mic are mounted afresh. The thread store
|
|
157
|
+
is wrapped once, and a `data-threads-url` removed while detached takes its
|
|
158
|
+
remote store away. The transcript is rebuilt from history, as `reload()`
|
|
159
|
+
rebuilds it. The composer's recall history is kept. Re-inserting still applies
|
|
160
|
+
the attributes as they stand when the element comes back. A host that called
|
|
161
|
+
`offerQuoteInPage()` calls it again after re-inserting, as before.
|
|
162
|
+
|
|
163
|
+
## [0.39.0] — 2026-09-17
|
|
164
|
+
|
|
165
|
+
### Changed
|
|
166
|
+
|
|
167
|
+
- **An empty conversation under `placement="page"` now greets the user, with the
|
|
168
|
+
composer centred beneath the greeting.** A full-page chat used to open on a
|
|
169
|
+
blank transcript with the composer at the foot of the screen, which is the
|
|
170
|
+
layout of a conversation in progress rather than one waiting to start. The
|
|
171
|
+
page now shows *Hello, {name}* over a centred composer until the conversation
|
|
172
|
+
has something in it, then lays out as before.
|
|
173
|
+
|
|
174
|
+
This changes what any existing `page` host shows on upgrade, which is why it
|
|
175
|
+
is listed here. **`data-greeting="off"` restores the previous layout.** No
|
|
176
|
+
other placement changes by default; `embedded` opts in with `data-greeting`,
|
|
177
|
+
and the corner placements and the sidebar never show it, because a panel
|
|
178
|
+
opened from a launcher is already mid-task.
|
|
179
|
+
|
|
180
|
+
The surface it adds:
|
|
181
|
+
|
|
182
|
+
- `user-name` / `userName`, a live attribute for the name. Presentation only:
|
|
183
|
+
unlike `user-key` it scopes nothing and is never sent. Absent or blank gives
|
|
184
|
+
*Hello there*.
|
|
185
|
+
- `greeting` and `greetingNoName` in the string table.
|
|
186
|
+
- A `greeting` slot and part, so a host can replace or restyle the text; the
|
|
187
|
+
`empty` slot and `data-starters` still render beneath it.
|
|
188
|
+
- `--ag-ui-greeting-font` and `--ag-ui-greeting-size`.
|
|
189
|
+
- `data-empty`, stamped on the element while the conversation is empty, and
|
|
190
|
+
documented as a styling hook for the chrome around a full-page chat.
|
|
191
|
+
|
|
192
|
+
The centring is two equal flexible halves either side of the composer's rows,
|
|
193
|
+
so the rows stay centred as a draft or an attachment tray grows them, and
|
|
194
|
+
nothing in the DOM moves: focus, the caret and IME composition are untouched.
|
|
195
|
+
Every condition in its selectors is held by a Chromium test that fails with
|
|
196
|
+
that condition removed, since a stylesheet has no coverage to fall back on.
|
|
197
|
+
|
|
198
|
+
**Only a send moves the composer, and it slides.** It moves on send rather
|
|
199
|
+
than on the first keystroke, because a field that moves under the caret moves
|
|
200
|
+
during IME composition too, and because taking the greeting away before
|
|
201
|
+
anything is committed would mean bringing it back when the draft is deleted.
|
|
202
|
+
The slide uses `--ag-ui-motion`, so reduced motion jumps straight to the
|
|
203
|
+
dock, and the rule over the composer fades in with it rather than being drawn
|
|
204
|
+
at its destination while the composer is still on the way. Every other way
|
|
205
|
+
out of the empty state -- a restored conversation, a thread picked from the
|
|
206
|
+
list, a continued run -- and every way back into it snap, because each is a
|
|
207
|
+
change of context rather than a continuation of what the user was doing.
|
|
208
|
+
|
|
209
|
+
A conversation a slow store is still fetching is held in the docked layout
|
|
210
|
+
with the greeting hidden, so a page restoring an existing conversation never
|
|
211
|
+
paints a centred composer that then drops when the messages land. A new chat
|
|
212
|
+
started during that fetch greets at once rather than waiting on it.
|
|
213
|
+
|
|
214
|
+
- **Internal restructuring: `ag_ui_chat.ts` is split into modules that each own
|
|
215
|
+
one concern.** The element was one class of more than six thousand lines in
|
|
216
|
+
which a single concern -- the transcript, the run handlers -- was spread over
|
|
217
|
+
a dozen separate stretches of the file. Each concern moves into a controller
|
|
218
|
+
in the directory it already belonged to, holding its own private state, and
|
|
219
|
+
the element keeps its public members as delegations with their documentation
|
|
220
|
+
unchanged.
|
|
221
|
+
|
|
222
|
+
What has moved, each into the directory its concern already had:
|
|
223
|
+
|
|
224
|
+
- The panel's placement -- the dragged launcher, the dragged header, the
|
|
225
|
+
eight resize grips, the corner the panel opens from and the persisted
|
|
226
|
+
record of all of it -- is `PanelPlacement` in `src/ui/placement/`.
|
|
227
|
+
- Quoting a selection, in the transcript and over the host page, is
|
|
228
|
+
`TranscriptQuoteOffer` in `src/ui/excerpts/`.
|
|
229
|
+
- The skill catalog -- its three sources, their merge into the chips and the
|
|
230
|
+
palette, and what a pick does to the composer -- is `SkillCatalog` in
|
|
231
|
+
`src/skills/`.
|
|
232
|
+
- The composer's attachments (the tray and the picker, drop and paste routes
|
|
233
|
+
into it) and its mic are `ComposerAttachments` and `ComposerVoice` in
|
|
234
|
+
`src/ui/composer/`.
|
|
235
|
+
- Which storage keys are the element's -- the namespace it claims in the
|
|
236
|
+
document, the keys its layout preferences live under, and the built-in
|
|
237
|
+
conversation store scoped to them -- is `StorageScope` in `src/core/`.
|
|
238
|
+
- The answer streaming into its bubble is `AnswerStream` in
|
|
239
|
+
`src/ui/transcript/`, a delegated sub-agent's progress is
|
|
240
|
+
`SubAgentProgress` in `src/ui/progress/`, and the registry of activity
|
|
241
|
+
renderers and the blocks they drew is `ActivityRegistry` in `src/core/`.
|
|
242
|
+
- The decision a run is suspended on, which Stop abandons, is
|
|
243
|
+
`PendingDecision` in `src/ui/interrupts/`: one owner where four places
|
|
244
|
+
used to write one field.
|
|
245
|
+
- The frontend tools the agent is offered -- the host's, the built-ins, the
|
|
246
|
+
`ask_user` question, what the current round advertised, and the server's
|
|
247
|
+
labels for its own tools -- are `ToolCatalog` in `src/tools/`.
|
|
248
|
+
- The transcript -- its bubbles, the open answer group, the pending dots, the
|
|
249
|
+
reasoning region, the tool cards and the scroller that follows the foot --
|
|
250
|
+
is `Transcript` in `src/ui/transcript/`, and the action row under a
|
|
251
|
+
finished answer is `AnswerActions` beside it.
|
|
252
|
+
- Running the frontend tool calls a round produced and answering the
|
|
253
|
+
server's interrupts -- the page each round's context described, the
|
|
254
|
+
confirmation gate and the tools the user waived it for, and the approval
|
|
255
|
+
cards -- is `ToolDispatch` in `src/tools/`.
|
|
256
|
+
- The AG-UI event handlers a run is drawn by, and what one interaction
|
|
257
|
+
accumulates for the host until it settles -- the tool calls it made, the
|
|
258
|
+
keys it invalidated, and whether it already said how it ended -- are
|
|
259
|
+
`RunHandlers` in `src/core/`. The screen-reader status region the run and
|
|
260
|
+
its tool calls report into is `RunAnnouncer` in `src/ui/progress/`.
|
|
261
|
+
- Which conversation is on screen and how one gets there -- the active
|
|
262
|
+
thread, restoring it from the store, switching, renaming and deleting from
|
|
263
|
+
the conversation list, and continuing a run from the checkpoint panel -- is
|
|
264
|
+
`ConversationHistory` in `src/ui/history/`, beside the list and the panel
|
|
265
|
+
it drives.
|
|
266
|
+
- What held no state of its own became plain functions. The builders of the
|
|
267
|
+
element's shell -- a header control, an icon holder, a glyph slot, the
|
|
268
|
+
adopted stylesheet -- and the readers of the launcher's icon URL and its
|
|
269
|
+
unread badge switch are in `src/ui/shell/`. The starter prompts are beside
|
|
270
|
+
the suggestion chips, the composer's sizing is in `src/ui/composer/`,
|
|
271
|
+
whether a placement can collapse is beside whether it can be dragged, and
|
|
272
|
+
the tool-round limit is read in `src/core/`. The attachment tray's `accept`
|
|
273
|
+
list is split by the same comma tokeniser the other opt-in lists use.
|
|
274
|
+
|
|
275
|
+
Nothing a consumer can see changes. Every declaration file the package
|
|
276
|
+
already emitted was compared byte for byte before and after each step, and
|
|
277
|
+
the moves altered one: the internal `core/utils.d.ts` gained the shared comma
|
|
278
|
+
tokeniser. No declaration reachable from the package entry point imports
|
|
279
|
+
that file or any of the new modules' declarations, so a consumer's types
|
|
280
|
+
cannot reach them. No test was edited to make a move pass.
|
|
281
|
+
|
|
282
|
+
The public declarations do differ from the previous release, by the other
|
|
283
|
+
entries in this release and not by this one. The bundle differs by this one
|
|
284
|
+
as well: on its own it asks a re-vendor for no host wiring, and it is still a
|
|
285
|
+
different build.
|
|
286
|
+
|
|
287
|
+
- **A new chat puts focus in the composer.** Starting a conversation is a
|
|
288
|
+
request to type one, but pressing *New chat* left focus on the button, so a
|
|
289
|
+
keyboard user emptied the composer and then had to find their way back to it.
|
|
290
|
+
`newChat()` now focuses the composer whichever control called it -- the
|
|
291
|
+
header's button, the history list's own, or a host's -- unless the widget is
|
|
292
|
+
collapsed and the composer is not on screen. It does so without scrolling, so
|
|
293
|
+
a host resetting a chat further down the page from its own code does not move
|
|
294
|
+
the page to it.
|
|
295
|
+
|
|
296
|
+
- **The conversation list now slides over the transcript at every width,
|
|
297
|
+
including a wide full page, where it used to dock beside it.** Under
|
|
298
|
+
`placement="page"` with at least 900px of panel, opening the list padded the
|
|
299
|
+
rows over to make room for it, so the conversation, the centred greeting and
|
|
300
|
+
the composer all moved sideways the moment it opened. It is now the same
|
|
301
|
+
slide-over there as under every other placement and width: a modal dialog
|
|
302
|
+
over a backdrop, with nothing behind it moving. `data-threads-docked` and
|
|
303
|
+
`--ag-ui-threads-rail-width` no longer exist, so host CSS written against
|
|
304
|
+
either can be deleted.
|
|
305
|
+
|
|
306
|
+
- **The answer well and the controls inside the panel keep round corners when
|
|
307
|
+
the panel's frame is square.** Under `placement="page"` and `"sidebar"`, and
|
|
308
|
+
on a small viewport, the frame is square because it meets the edge of the
|
|
309
|
+
window. Those placements squared it by setting the theme's radius to 0, so
|
|
310
|
+
everything else that reads that radius went square with it: the answer well,
|
|
311
|
+
the conversation list's *New chat* button and filter field, and a run
|
|
312
|
+
notice's *Undo*, beside message bubbles and a composer that stayed round. They
|
|
313
|
+
now square only the frame, through a new `--ag-ui-panel-radius`, and the
|
|
314
|
+
corners inside keep `--ag-ui-radius` (12px unless set). The floating and
|
|
315
|
+
embedded panels look as they did.
|
|
316
|
+
|
|
317
|
+
**A host that sets `--ag-ui-radius` under one of those placements now gets a
|
|
318
|
+
square frame**, where the token used to round the frame too. Set
|
|
319
|
+
`--ag-ui-panel-radius` to round it again. The well also gains
|
|
320
|
+
`--ag-ui-well-radius`, beside `--ag-ui-well-bg` and `--ag-ui-well-border`, to
|
|
321
|
+
round it apart from the rest of the panel.
|
|
322
|
+
|
|
323
|
+
### Fixed
|
|
324
|
+
|
|
325
|
+
- **Stop ends a checkpoint continuation.** While a resumed or forked run was
|
|
326
|
+
streaming, the composer offered Stop and pressing it did nothing; New chat,
|
|
327
|
+
switching conversations and removing the element left it streaming too, so the
|
|
328
|
+
rest of the resumed answer drew itself into whatever came next. The element
|
|
329
|
+
cancelled only the conversation's own client, and a continuation runs on a
|
|
330
|
+
client of its own. Every path that stops the conversation's run now stops a
|
|
331
|
+
continuation as well.
|
|
332
|
+
|
|
333
|
+
- **A run cut off by New chat or a thread switch stays with the conversation it
|
|
334
|
+
belonged to.** Cancelling closes the request, but the run finishes a moment
|
|
335
|
+
later, and by then the element had moved on. So its "Stopped" note appeared at
|
|
336
|
+
the top of the new conversation; its truncated exchange was saved under the new
|
|
337
|
+
thread, which the drawer then listed as a second copy of the old one; and its
|
|
338
|
+
`ag-ui-run-finished` reported a server-side tool as the host's own, because the
|
|
339
|
+
record of what streamed back had been cleared with the transcript. Where a host
|
|
340
|
+
tool's handler was still running -- which cancelling cannot interrupt -- and the
|
|
341
|
+
user sent the next message before it returned, the old run's settle also put the
|
|
342
|
+
new run's Stop button back to Send, and one event carried both runs' tools while
|
|
343
|
+
the new run's own reported none.
|
|
344
|
+
|
|
345
|
+
Each conversation's runs now keep their bookkeeping apart. A run left behind by
|
|
346
|
+
a reset still reports what it ran, since a stopped run may already have written
|
|
347
|
+
something, and saves to the thread it was started in; nothing else it does
|
|
348
|
+
reaches the conversation that replaced it.
|
|
349
|
+
|
|
350
|
+
- **A resumed or forked run now carries the page's shared state, reports the
|
|
351
|
+
state it changes, and stops at `data-max-tool-rounds`.** A checkpoint
|
|
352
|
+
continuation sent an empty state object, so an agent whose tools read the
|
|
353
|
+
page's state resumed without it; a `STATE_SNAPSHOT` it streamed never reached
|
|
354
|
+
`ag-ui-state`; and on a page that had raised the tool-round bound, the resumed
|
|
355
|
+
half of a form-filling task still stopped at ten.
|
|
356
|
+
|
|
357
|
+
The continuation's client was built separately from the conversation's own,
|
|
358
|
+
and both options arrived after continuations did, so each was wired into one
|
|
359
|
+
construction only. There is now one construction, and a continuation differs
|
|
360
|
+
from the conversation's client only in its endpoint, its empty seed, and in
|
|
361
|
+
not writing the conversation store -- which it never did, deliberately: its
|
|
362
|
+
agent holds only the new turn and its answer, so saving them would replace the
|
|
363
|
+
stored conversation with its last exchange.
|
|
364
|
+
|
|
365
|
+
- **Text a user or a server wrote could rewrite the label it was put into.**
|
|
366
|
+
Every string-table template was filled with `template.replace("{token}",
|
|
367
|
+
value)`, and a string replacement interprets dollar patterns in the value: `$&`
|
|
368
|
+
inserts the matched token and `` $` `` and `$'` the text either side of it. So a
|
|
369
|
+
queued message reading `costs $& more` was labelled
|
|
370
|
+
`Do not send "costs {text} more"`, and the same went for a tool name in the
|
|
371
|
+
confirmation prompt, a skill id in the "Using skill" notice and an agent name
|
|
372
|
+
on a delegation row. The skill hint was two replacements chained, so a skill
|
|
373
|
+
titled with `{fields}` in it was filled twice.
|
|
374
|
+
|
|
375
|
+
A string pattern also fills only its first occurrence, so a translation that
|
|
376
|
+
used a token twice was left half filled. That one reached every fill, the
|
|
377
|
+
numeric ones included.
|
|
378
|
+
|
|
379
|
+
Every fill -- seventeen, across the element, the confirmation card, the
|
|
380
|
+
attachment tray, the voice button and the relative timestamps -- now goes
|
|
381
|
+
through one helper that fills every occurrence in a single pass with a replacer
|
|
382
|
+
function, whose return value is never interpreted. Each file that filled has a
|
|
383
|
+
test that failed against the old fill.
|
|
384
|
+
|
|
385
|
+
- **A closed slash-command palette and an empty queued-messages row still took
|
|
386
|
+
up space.** Both set the hidden attribute and both declare their own
|
|
387
|
+
`display`, which beats the browser's rule for hidden elements -- the defect the
|
|
388
|
+
attachment tray's own hidden rule was written for. The palette is mounted on
|
|
389
|
+
every element and closed nearly all of the time, so it painted 2px of border
|
|
390
|
+
and its shadow as a line above the composer; the queued row kept 10px of
|
|
391
|
+
padding. Measured in Chromium, which is where the new tests live, because
|
|
392
|
+
happy-dom lays out no boxes and reports both as zero either way.
|
|
393
|
+
|
|
394
|
+
- **Opening the conversation list or the checkpoints panel did not move focus
|
|
395
|
+
into it.** Each asks for focus as it opens -- the list on *New chat*, the
|
|
396
|
+
checkpoints panel on its first row or itself -- but both eased their
|
|
397
|
+
visibility in so the exit could play, and a transition starts at its first
|
|
398
|
+
value: at the instant of the call the panel was still hidden, so the browser
|
|
399
|
+
dropped it and focus stayed wherever it had been, at every placement and under
|
|
400
|
+
reduced motion. A keyboard user opened a modal dialog they were not in.
|
|
401
|
+
Visibility now changes at once on the way in and is delayed on the way out, so
|
|
402
|
+
the list's slide and the panel's fade are unchanged. happy-dom computes no
|
|
403
|
+
transitions, which is why the unit tests of the same calls passed; the new
|
|
404
|
+
tests are in Chromium.
|
|
405
|
+
|
|
406
|
+
- **A new `user-key` now gets its own confirmation cards.** A tool one principal
|
|
407
|
+
waived with *Always allow* kept running without a card for whoever signed in
|
|
408
|
+
next in the same tab, because the waiver lives in the element's memory rather
|
|
409
|
+
than in the stored conversation that changing `user-key` purges. `user-key`
|
|
410
|
+
exists because a sign-out is a navigation inside one tab, not a remount, so the
|
|
411
|
+
element outlives the person who clicked -- and the next person's destructive
|
|
412
|
+
calls ran on that click, with nothing on screen to say anyone had been asked.
|
|
413
|
+
|
|
414
|
+
Changing the key, or removing it, now forgets every waiver along with the
|
|
415
|
+
transcript. The first key to arrive keeps them, for the same reason it keeps
|
|
416
|
+
the conversation on screen: it names the user who was already there.
|
|
417
|
+
|
|
418
|
+
- **A `confirmPredicate` that throws now refuses the call, and the run carries
|
|
419
|
+
on.** A throw, or a rejected promise, used to end the whole run on an error
|
|
420
|
+
bubble quoting the host's own message, and left that call's tool card reading
|
|
421
|
+
"running..." for good, because dispatch had already taken the card out of the
|
|
422
|
+
sweep that settles leftovers when a run ends. The handler did not run, but
|
|
423
|
+
nothing on screen said so and the agent was never told.
|
|
424
|
+
|
|
425
|
+
The call now fails closed: no confirmation card, no handler, the tool card
|
|
426
|
+
settles as declined, and the agent receives the new `confirmCheckFailed`
|
|
427
|
+
string as the result and goes on to its next round, as it does after a
|
|
428
|
+
decline. It is refused rather than put to a card because for a tool with no
|
|
429
|
+
`x-destructive` flag the predicate is the only guard, and one click would run
|
|
430
|
+
what the host's policy could not vouch for. The error is reported with
|
|
431
|
+
`console.warn`, as a failed `render` already is, and is not sent to the
|
|
432
|
+
endpoint: unlike a handler's message, it was never written for the model.
|
|
433
|
+
|
|
434
|
+
- **An `approvalRenderer` that throws now hands the decision to the built-in
|
|
435
|
+
approval card.** A throw, or a rejected promise, used to end the run on an
|
|
436
|
+
error bubble quoting the host's message without ever answering the server's
|
|
437
|
+
interrupt, and the sweep that settles leftover cards at the end of a run then
|
|
438
|
+
marked the gated call a green "done" -- for a call that never ran and that
|
|
439
|
+
nobody had been asked about. One failing interrupt took every other interrupt
|
|
440
|
+
in its batch down with it.
|
|
441
|
+
|
|
442
|
+
The renderer decides how the question looks, not whether it is asked, so it
|
|
443
|
+
now falls back to the built-in card for that interrupt, with a `console.warn`
|
|
444
|
+
naming the interrupt, the way a failed `render` falls back to the default.
|
|
445
|
+
Nothing runs without a click, and the run carries on as if no renderer had
|
|
446
|
+
been set. The exception is a rejection after the signal has fired, which is
|
|
447
|
+
how a renderer is expected to honour Stop: that wait resolves as denied, as
|
|
448
|
+
the built-in card does on the same signal, with no card drawn and no warning.
|
|
449
|
+
|
|
450
|
+
- **Reloading the page while a tool call waits for approval now shows that call
|
|
451
|
+
as not finished, where it showed a spinner that never stopped.** The run loop
|
|
452
|
+
saves a round's history when its stream ends, which is before it asks about a
|
|
453
|
+
gated call, runs a frontend tool or collects a server-side approval. A reload in
|
|
454
|
+
that window left the call stored with no result, and the request that would
|
|
455
|
+
have produced one had died with the page, so the restored card had no Approve
|
|
456
|
+
or Decline and nothing left to settle it. The next message then went out
|
|
457
|
+
carrying a tool call with no result, which several model providers reject.
|
|
458
|
+
|
|
459
|
+
The card now settles as not finished, and the conversation carries the same
|
|
460
|
+
not-finished result the client gives any call a run left open, so the next
|
|
461
|
+
request is a valid turn and a later reload shows the same. It is not declined,
|
|
462
|
+
although Stop declines an open card, because Stop is a person answering the
|
|
463
|
+
question and a reload is not: the saved history looks the same whether the
|
|
464
|
+
round was waiting on a person or on a frontend tool the reload killed, so a
|
|
465
|
+
decline would be unproven for the first and false for the second, and "not
|
|
466
|
+
finished" is true of both. A server-side approval left open by a
|
|
467
|
+
reload comes back the same way. A navigating tool's call is unchanged: it
|
|
468
|
+
resumes from the page it landed on. A restored card for a call that an earlier
|
|
469
|
+
round went past without a result settles as not finished too, and the restore
|
|
470
|
+
now gives that call its result as well, at the end of its own round.
|
|
471
|
+
|
|
472
|
+
- **A request sent after a reload no longer carries the element's `outcome`
|
|
473
|
+
labels to the server.** The label that lets a reload replay a declined or
|
|
474
|
+
failed card is written onto the stored copy of a tool message only, and the
|
|
475
|
+
README promises it is never sent. A restore seeds the next agent from that
|
|
476
|
+
stored copy, though, so every tool message restored with a label went out
|
|
477
|
+
with it on the next request. The client now takes the labels off the history
|
|
478
|
+
it is seeded with and keeps them beside it, so the request carries none, the
|
|
479
|
+
restored cards still settle as they did, and the next save writes each label
|
|
480
|
+
back.
|
|
481
|
+
|
|
482
|
+
- **A request no longer carries a tool call without a result, however the run
|
|
483
|
+
that made the call ended.** Several model providers reject such a turn, so the
|
|
484
|
+
conversation could not continue past it, and a live run left one in four ways
|
|
485
|
+
with no reload involved: Stop while the stream was still arriving, Stop on an
|
|
486
|
+
open server-side approval, a round that ended on `RUN_ERROR` after emitting
|
|
487
|
+
calls, and a call naming a tool nothing on the page owns. Each was guarded,
|
|
488
|
+
if at all, where it happened, and the guards between them left gaps.
|
|
489
|
+
|
|
490
|
+
The client now answers every call still open before each request it makes,
|
|
491
|
+
in one place, so a path nobody listed cannot send one either. The answer goes
|
|
492
|
+
at the end of the round that made the call, where a provider looks for it.
|
|
493
|
+
It says the call did not finish, in the new `callNotFinished` string, with
|
|
494
|
+
the outcome `interrupted`, pydantic-ai's word for the same thing, because
|
|
495
|
+
nobody refused it; the card settles to a new **not finished** status in the
|
|
496
|
+
same words, with its own `--ag-ui-tool-icon-interrupted` glyph, where it used
|
|
497
|
+
to claim "done" over "No result returned.". The decline stays where a person
|
|
498
|
+
declined: an approval Stop closed while it was open is answered with
|
|
499
|
+
`declinedAction`, and one already approved when Stop landed is answered as not
|
|
500
|
+
finished. The calls a resumed approval is answering are left for the server.
|
|
501
|
+
`UiStrings.noResult` is no longer drawn and is deprecated, and a server's own
|
|
502
|
+
`interrupted` outcome now renders as not finished rather than as done.
|
|
503
|
+
|
|
504
|
+
- **Stop during a frontend tool no longer lets the next call in the same round
|
|
505
|
+
run.** The loop checked for Stop between rounds but not between the calls of
|
|
506
|
+
one round, so when the agent asked for two page actions at once and Stop was
|
|
507
|
+
pressed while the first ran, the second ran anyway as soon as the first
|
|
508
|
+
returned, after the person had asked for the run to end. A handler already
|
|
509
|
+
running still completes and keeps its result, since it cannot be aborted, but
|
|
510
|
+
the calls after it no longer start, and each is answered as not finished.
|
|
511
|
+
|
|
512
|
+
- **Changing `user-key` no longer sends the previous principal's shared state
|
|
513
|
+
on the next principal's first run.** A handover purges the stored
|
|
514
|
+
conversation, the transcript and the tools waived with Always allow, but
|
|
515
|
+
`sharedState` survived it, and it holds whatever the agent last wrote into it
|
|
516
|
+
for the principal who left. The next client is seeded from it, so it went out
|
|
517
|
+
as `RunAgentInput.state` on the new principal's first request. It is now
|
|
518
|
+
cleared on the handover. New chat still keeps it, because shared state is the
|
|
519
|
+
page's object and the same person is still editing it, and the first key to
|
|
520
|
+
arrive keeps it for the same reason it keeps the conversation.
|
|
521
|
+
|
|
10
522
|
## [0.38.0] — 2026-09-14
|
|
11
523
|
|
|
12
524
|
### Changed
|
|
@@ -3614,7 +4126,9 @@ hosts that both arrange the page the way it expects.
|
|
|
3614
4126
|
### Notes
|
|
3615
4127
|
- First release — exercising the automated npm OIDC publish pipeline end-to-end.
|
|
3616
4128
|
|
|
3617
|
-
[Unreleased]: https://github.com/Artui/ag-ui-web-component/compare/v0.
|
|
4129
|
+
[Unreleased]: https://github.com/Artui/ag-ui-web-component/compare/v0.40.0...HEAD
|
|
4130
|
+
[0.40.0]: https://github.com/Artui/ag-ui-web-component/compare/v0.39.0...v0.40.0
|
|
4131
|
+
[0.39.0]: https://github.com/Artui/ag-ui-web-component/compare/v0.38.0...v0.39.0
|
|
3618
4132
|
[0.38.0]: https://github.com/Artui/ag-ui-web-component/compare/v0.37.0...v0.38.0
|
|
3619
4133
|
[0.37.0]: https://github.com/Artui/ag-ui-web-component/compare/v0.36.0...v0.37.0
|
|
3620
4134
|
[0.36.0]: https://github.com/Artui/ag-ui-web-component/compare/v0.35.2...v0.36.0
|