@artooi/ag-ui-web-component 0.41.3 → 0.43.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 +304 -1
- package/README.md +113 -24
- package/dist/ag-ui-web-component.bundle.js +33 -33
- package/dist/ag-ui-web-component.bundle.js.map +3 -3
- package/dist/constants.d.ts +3 -1
- package/dist/constants.d.ts.map +1 -1
- package/dist/core/ag_ui_chat.d.ts +49 -9
- package/dist/core/ag_ui_chat.d.ts.map +1 -1
- package/dist/core/agui_client.d.ts +20 -7
- package/dist/core/agui_client.d.ts.map +1 -1
- package/dist/core/storage_scope.d.ts +14 -4
- package/dist/core/storage_scope.d.ts.map +1 -1
- package/dist/index.js +729 -115
- package/dist/index.js.map +3 -3
- package/dist/tools/tool_dispatch.d.ts +23 -0
- package/dist/tools/tool_dispatch.d.ts.map +1 -1
- package/dist/ui/composer/voice_input.d.ts +10 -4
- package/dist/ui/composer/voice_input.d.ts.map +1 -1
- package/dist/ui/history/conversation_history.d.ts +84 -1
- package/dist/ui/history/conversation_history.d.ts.map +1 -1
- package/dist/ui/ui_strings.d.ts +5 -0
- package/dist/ui/ui_strings.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/constants.ts +3 -1
- package/src/core/ag_ui_chat.ts +498 -70
- package/src/core/agui_client.ts +55 -12
- package/src/core/storage_scope.ts +21 -8
- package/src/tools/tool_dispatch.ts +153 -6
- package/src/ui/composer/voice_input.ts +79 -14
- package/src/ui/history/conversation_history.ts +392 -56
- package/src/ui/ui_strings.ts +6 -0
- package/src/version.ts +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,307 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.43.0] — 2026-10-10
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
|
|
14
|
+
- **A second click on the mic while its permission prompt is open no longer
|
|
15
|
+
leaves a recording nothing can stop.** The mic button reads idle for as long
|
|
16
|
+
as the browser's prompt is up, which is seconds the first time an origin asks
|
|
17
|
+
and a few milliseconds after that, so a double click or an impatient second
|
|
18
|
+
click started twice. Both starts recorded and held a live track, but the
|
|
19
|
+
control kept only the second: one stop click ended that one, the first kept
|
|
20
|
+
recording with the browser's recording indicator on for a button reading
|
|
21
|
+
idle, and `dispose()` could not reach it either. The first start's two-minute
|
|
22
|
+
cap timer was orphaned too, and when it fired it stopped whatever recording
|
|
23
|
+
was current, so a later, unrelated one was cut short with "Stopped at the
|
|
24
|
+
2-minute limit". If the second start's recorder then failed, the failure
|
|
25
|
+
released the second stream and hid the first. The latest click now wins: a
|
|
26
|
+
start that settles after a newer one began, or after the control was disposed,
|
|
27
|
+
releases the microphone it was granted and touches nothing the newer start
|
|
28
|
+
owns, and a refusal on such a start no longer draws an error over the newer
|
|
29
|
+
one. The button is not blocked while a prompt is open, so a prompt the user
|
|
30
|
+
dismissed without answering, which a browser may leave pending forever, does
|
|
31
|
+
not stop the next click from asking again. Nothing for a host to opt into.
|
|
32
|
+
|
|
33
|
+
- **A send made while a stored conversation loads no longer goes out without
|
|
34
|
+
it.** Every restore asks the conversation store for the conversation first:
|
|
35
|
+
on connect, on `reload()`, when a thread is picked from the drawer, and when
|
|
36
|
+
`user-key` changes from one principal to another. With `data-threads-url`
|
|
37
|
+
that is a request to the server,
|
|
38
|
+
and nothing held the composer while it was out unless the restore was about
|
|
39
|
+
to resume a run. So a send in that time went out carrying only its own turn,
|
|
40
|
+
and the agent answered without the conversation. Its save replaced the
|
|
41
|
+
stored conversation with the new exchange, and the replay then drew the
|
|
42
|
+
stored conversation under the new turn. The client the send built stayed
|
|
43
|
+
cached without the history, so every later turn on that page went out
|
|
44
|
+
without it too. A Retry in the window was refused, but it left the same
|
|
45
|
+
empty client cached, so the first send after the load lost the history the
|
|
46
|
+
same way. With the built-in store the window is one microtask, so it was
|
|
47
|
+
reached only by a send in the same task as the insertion: a script right
|
|
48
|
+
after `append`, a framework ref callback, or a layout effect.
|
|
49
|
+
Now every restore holds the composer until the conversation is drawn:
|
|
50
|
+
- A host's `sendMessage()` waits for the restore, then sends with the
|
|
51
|
+
conversation. Its bubble, its `ag-ui-submit` event and its request all move
|
|
52
|
+
to after the replay, so they arrive later than the call by the length of
|
|
53
|
+
the load. A host that listens on `ag-ui-submit` hears it that much later.
|
|
54
|
+
- If New chat, a thread switch, a change from one `user-key` to another or
|
|
55
|
+
`reload()` comes while the send waits, the send is dropped and resolves
|
|
56
|
+
`false` at once, because the conversation it was addressed to is no longer
|
|
57
|
+
on screen. If the element leaves the page, the send is dropped too, but it
|
|
58
|
+
resolves `false` only once the load answers or the element is inserted
|
|
59
|
+
again. A first `user-key` on an anonymous conversation adopts it rather than
|
|
60
|
+
replacing it, so a send waiting then still goes out. A second
|
|
61
|
+
`sendMessage()` while one waits is refused.
|
|
62
|
+
- The built-in Send queues what is typed during the load, showing it as
|
|
63
|
+
"Waiting to send", and sends it after the conversation is drawn, behind a
|
|
64
|
+
`sendMessage()` that was waiting.
|
|
65
|
+
- `retryLastTurn()` resolves `false` during the load, before it builds a
|
|
66
|
+
client.
|
|
67
|
+
- A checkpoint pick is refused during the load with a new composer hint,
|
|
68
|
+
`continueWhileLoading` ("Wait for the conversation to finish loading, then
|
|
69
|
+
pick a run to continue."). Hosts that translate the UI strings can override
|
|
70
|
+
it in `data-strings`; until they do, it shows in English. `UiStrings` gains
|
|
71
|
+
the key. The `strings` property and `data-strings` take a partial table, so
|
|
72
|
+
only code that builds a complete `UiStrings` object of its own has to add
|
|
73
|
+
it.
|
|
74
|
+
- A restore that will resume a run a navigating tool interrupted keeps
|
|
75
|
+
refusing a host's `sendMessage()`, as in 0.42.0, because that restore ends
|
|
76
|
+
by starting the run.
|
|
77
|
+
|
|
78
|
+
No host opt-in is needed: this applies to every element as soon as it is
|
|
79
|
+
upgraded. A host that sends on mount gets the conversation without changing
|
|
80
|
+
anything. The cost is that the composer queues for as long as each load
|
|
81
|
+
takes, and the load has no timeout of its own, so a request that hangs keeps
|
|
82
|
+
it queueing until the request fails. A store whose load rejects ends the wait
|
|
83
|
+
too, and what waited then goes out into the empty conversation the page is left
|
|
84
|
+
showing, so a custom store that rejects loses its stored copy to that send, as
|
|
85
|
+
a turn typed after the failure always has. The built-in remote store falls back
|
|
86
|
+
to its local cache on a failed fetch instead of rejecting.
|
|
87
|
+
|
|
88
|
+
- **Clearing the conversation while a frontend tool waits no longer leaves a
|
|
89
|
+
"thinking" indicator in the next one.** A handler cannot be aborted, and a
|
|
90
|
+
confirmation card that is open when the run stops is declined, so the call
|
|
91
|
+
resumes after the conversation has been cleared. It then showed the run
|
|
92
|
+
waiting on its next round in whatever conversation had replaced it: the
|
|
93
|
+
indicator's answer group hid the greeting and the starter chips, and nothing
|
|
94
|
+
took it down until the next run there ended. This happened after New chat,
|
|
95
|
+
picking another thread from the drawer, deleting the active thread,
|
|
96
|
+
`reload()` and re-inserting the element, each with a handler still running
|
|
97
|
+
or a confirmation card open. A `user-key` handover already drew nothing there, as of 0.42.0.
|
|
98
|
+
Now the call draws nothing once the conversation it was dispatched in has
|
|
99
|
+
been cleared. Everything else it did is unchanged:
|
|
100
|
+
- Its card still settles as done, failed or declined.
|
|
101
|
+
- A failed navigating tool still clears its checkpoint from the thread it
|
|
102
|
+
was written to.
|
|
103
|
+
- Its result still goes to the run it belonged to, as after a Stop.
|
|
104
|
+
|
|
105
|
+
No host opt-in is needed: this applies to every element as soon as it is
|
|
106
|
+
upgraded, and nothing about the tool registry or its handlers changes.
|
|
107
|
+
|
|
108
|
+
- **A microphone the browser grants but cannot record no longer fails silently.**
|
|
109
|
+
With `data-transcribe-url` set, the mic button asks for the microphone and
|
|
110
|
+
then builds a `MediaRecorder` on the stream. If the browser refuses that, as
|
|
111
|
+
Chromium does with `NotSupportedError` for a stream whose tracks have already
|
|
112
|
+
ended, the click ended in an unhandled promise rejection and the button stayed
|
|
113
|
+
idle with no message. Any track of the stream that was still live kept
|
|
114
|
+
running, so the browser's recording indicator stayed lit, and starting again
|
|
115
|
+
replaced the stream without stopping it. Now a recorder that cannot start is
|
|
116
|
+
treated as a refused microphone: the tracks are released, the button shows
|
|
117
|
+
"Transcription failed" as it does for a denied permission, and it stays idle,
|
|
118
|
+
ready to try again. The message reuses that existing string, so `UiStrings` is
|
|
119
|
+
unchanged.
|
|
120
|
+
|
|
121
|
+
No host opt-in is needed: this applies to every element as soon as it is
|
|
122
|
+
upgraded.
|
|
123
|
+
|
|
124
|
+
### Changed
|
|
125
|
+
|
|
126
|
+
- **`sendMessage()` resolves to whether it sent.** It returns
|
|
127
|
+
`Promise<boolean>` where it returned `Promise<void>`. It resolves `true` once
|
|
128
|
+
the turn was handed to the agent and the interaction settled, and `false`
|
|
129
|
+
when nothing was sent: an empty message, a run or a send already in flight, a
|
|
130
|
+
reload's resume, no `endpoint`, or a send dropped while it waited for a
|
|
131
|
+
restore. Code that ignores the result behaves as before, but type-checking
|
|
132
|
+
breaks wherever `sendMessage` is annotated, implemented, overridden or
|
|
133
|
+
replaced as returning `Promise<void>`: a wrapper's ref type declaring it, a
|
|
134
|
+
subclass override, and a test double that replaces it on the element all have
|
|
135
|
+
to return a boolean to type-check.
|
|
136
|
+
|
|
137
|
+
## [0.42.0] — 2026-10-07
|
|
138
|
+
|
|
139
|
+
### Fixed
|
|
140
|
+
|
|
141
|
+
- **A send is in flight from the moment it is taken, not from when its run
|
|
142
|
+
starts.** A send draws its turn and dispatches `ag-ui-submit` before it asks
|
|
143
|
+
the client for a run, and only the run counted. So a `sendMessage()` from an
|
|
144
|
+
`ag-ui-submit` listener, or a second call in the same task, passed every
|
|
145
|
+
guard and went out as a second request on the same client, with the
|
|
146
|
+
listener's turn drawn between the user's turn and its answer. Both are now
|
|
147
|
+
refused, so a listener that sends hears the event once, and the built-in
|
|
148
|
+
Send queues in that time as it does during a run. A listener that stops the
|
|
149
|
+
send -- a new chat, a thread switch, a change of `user-key`, moving the
|
|
150
|
+
element -- now stops it before its request is made. It used to go out into
|
|
151
|
+
the conversation that replaced it, which after a change of `user-key` put one
|
|
152
|
+
principal's turn at the top of the next one's conversation.
|
|
153
|
+
- **ArrowUp reaches a turn queued behind a run, and one sent to resume or
|
|
154
|
+
fork a run.** Arrow-key recall recorded only a turn the composer sent at
|
|
155
|
+
once. A turn typed while a run was going entered it only if Stop discarded
|
|
156
|
+
the queue, so ArrowUp skipped it while it waited and after the queue sent it.
|
|
157
|
+
A turn sent from the checkpoint panel never entered it, though it was typed
|
|
158
|
+
in the composer like any other. Both are now recorded as they leave the
|
|
159
|
+
composer, and a turn taken back from the queue stays recallable, as one Stop
|
|
160
|
+
declined to send already did. Stop no longer adds the queue again, so the
|
|
161
|
+
history holds each turn once. Queueing or continuing with a turn you walked
|
|
162
|
+
back to now starts the next walk from the newest turn, as sending one did.
|
|
163
|
+
Turns a host sends with `sendMessage` are still not recorded, and the purge a
|
|
164
|
+
change of `user-key` makes still clears them.
|
|
165
|
+
- **Retry and a checkpoint pick keep the rules a send keeps.** `retryLastTurn()`
|
|
166
|
+
checked only for a run in flight. A Retry made before a send's run had
|
|
167
|
+
started, or while a picked checkpoint was in flight, started a second run in
|
|
168
|
+
the same conversation. A Retry is now refused in both cases. It also counts
|
|
169
|
+
from the call, as a send does, so a `sendMessage()` or a second Retry made
|
|
170
|
+
before its run starts is refused and the built-in Send queues. A pick made
|
|
171
|
+
before a send's or a Retry's run has started is now refused with the same hint
|
|
172
|
+
as a pick during a run. A conversation store, or an activity or tool renderer
|
|
173
|
+
the Retry redraws through, that starts a new chat or changes `user-key` now
|
|
174
|
+
stops it, and `retryLastTurn()` returns `false`. The Retry used to go ahead,
|
|
175
|
+
drawing the old conversation into the new one and asking its answer again.
|
|
176
|
+
One new refusal follows from the same rule: a Retry from an
|
|
177
|
+
`ag-ui-run-finished` listener for a picked checkpoint's run returns `false`,
|
|
178
|
+
because the continuation is still in flight when that event fires. A
|
|
179
|
+
`sendMessage()` from the same place was already refused for that reason.
|
|
180
|
+
- **A recall walk ends on any write into the composer, not only on typing.**
|
|
181
|
+
Walking back to a past turn with Up and then quoting text, from the
|
|
182
|
+
transcript or from the page through `offerQuoteInPage`, or clicking a skill
|
|
183
|
+
chip that prefills the composer, left the walk running: the next Up replaced
|
|
184
|
+
what had just been written with an older turn, and Down emptied the box. The
|
|
185
|
+
walk now checks that the composer still holds the turn it put there, so any
|
|
186
|
+
other write ends it, including a host page writing the composer itself.
|
|
187
|
+
- **A `user-key` changed while the element is out of the document now hands
|
|
188
|
+
over when it is inserted again.** The attribute was acted on only while the
|
|
189
|
+
element was connected, and inserting it again is a reload that keeps the
|
|
190
|
+
composer's recall on purpose. So removing the element, changing the key and
|
|
191
|
+
inserting it again left the previous principal's stored conversation in
|
|
192
|
+
`sessionStorage`, their turns one ArrowUp away, their Always allow waivers
|
|
193
|
+
live, their shared state seeded into the next principal's first run, their
|
|
194
|
+
unread count on the launcher, and their threads in a drawer left open.
|
|
195
|
+
Connecting now compares the key the element's state belongs to with the
|
|
196
|
+
attribute, and runs the handover a live change runs when they differ. The
|
|
197
|
+
first key to arrive is still an adoption rather than a purge, and a move that
|
|
198
|
+
keeps the key, a key that goes away and comes back while detached, and a
|
|
199
|
+
first connect hand nothing over.
|
|
200
|
+
- **A Stop your own code makes while a turn is starting now stops it.** A
|
|
201
|
+
conversation store, a `getContext` or `getTools`, or the save that answers a
|
|
202
|
+
tool call left open, that started a new chat or changed `user-key` during a
|
|
203
|
+
send or a Retry stopped nothing: the request still went out into the
|
|
204
|
+
conversation being left, with all of its history. Nothing is sent now. On
|
|
205
|
+
`AgUiClient`, a `cancel()` from `onPersist`, `getContext` or `getTools` before
|
|
206
|
+
the request is honoured and fires `onCancelled` and `onSettled` as any Stop
|
|
207
|
+
does, and a `cancel()` with nothing in flight is still forgotten by the next
|
|
208
|
+
`send()` or `resume()`. A Retry stopped this way resolves `true`, because the
|
|
209
|
+
provider runs after the Retry has handed its turn to the client.
|
|
210
|
+
- **A Retry whose save the store refuses leaves the conversation as it was.**
|
|
211
|
+
`truncateToLastUser()` shortened the history before saving it, so a store
|
|
212
|
+
that threw left the answer on screen but out of the next request, and the
|
|
213
|
+
agent was then asked a follow-up about an answer it was never told it gave.
|
|
214
|
+
It now saves first, so a refused save changes nothing, and `retryLastTurn()`
|
|
215
|
+
rejects with the store's error.
|
|
216
|
+
- **A reload that resumes a run no longer lets a second run start beside it.**
|
|
217
|
+
A reload in the middle of a navigating tool's run resumes that run once the
|
|
218
|
+
conversation store answers, and with a remote store that answer is a real
|
|
219
|
+
request. A send, a Retry or a checkpoint pick made while it was out was not
|
|
220
|
+
refused: the send went out without the stored conversation and its save
|
|
221
|
+
replaced it, then the resume ran beside it on the same client, answering a
|
|
222
|
+
call the conversation no longer held. A restore that will resume now counts
|
|
223
|
+
as in flight from the moment it asks the store, so `sendMessage()` returns
|
|
224
|
+
without sending, the built-in Send queues the turn behind the resumed answer,
|
|
225
|
+
`retryLastTurn()` returns `false`, and a pick says why at the composer. New
|
|
226
|
+
chat while the store answers stands the resume down. A host calling
|
|
227
|
+
`sendMessage()` as the element connects, on a page a navigating tool
|
|
228
|
+
reloaded, now has that call refused, as it would be behind any run.
|
|
229
|
+
- **A conversation restore stops where New chat, leaving the page or host
|
|
230
|
+
code stopped it.** A restore stood down only for a newer restore or, when it
|
|
231
|
+
was about to resume a run, for the Stop New chat makes; a restore with
|
|
232
|
+
nothing to resume holds nothing for a Stop to let go of. So New chat pressed
|
|
233
|
+
while a remote store was still answering drew the conversation being left
|
|
234
|
+
into the new chat, and the first turn sent there carried that whole
|
|
235
|
+
conversation. An element removed meanwhile ran the host's renderers for a
|
|
236
|
+
node no longer on the page. An activity or tool renderer that started a new
|
|
237
|
+
chat during the replay had the rest of the old conversation drawn into the
|
|
238
|
+
new one, and after a reload mid-run, a renderer, the store's save of the
|
|
239
|
+
checkpoint or `navigationResult` doing the same still let the resume go out,
|
|
240
|
+
answering the navigating call from the new chat. Once any of these happens,
|
|
241
|
+
the restore now draws nothing more and resumes nothing. The navigation
|
|
242
|
+
checkpoint such a stop leaves behind is forgotten, so coming back to that
|
|
243
|
+
conversation later answers its navigating call as not finished rather than
|
|
244
|
+
resuming it from whatever page is current then. When the element only left
|
|
245
|
+
the page, the checkpoint is kept, and the run resumes once the element is
|
|
246
|
+
put back. That holds after a `reload()` the host made while the store was
|
|
247
|
+
answering, too: a restore standing down for a newer restore of the same
|
|
248
|
+
conversation leaves the checkpoint to it, and the restore that resumes is
|
|
249
|
+
the one that clears it.
|
|
250
|
+
- **A run stopped by a change of `user-key` no longer saves into the next
|
|
251
|
+
principal's storage.** The handover stopped the run, but a stopped run saves
|
|
252
|
+
what it had once its request closes, and by then the store had been purged
|
|
253
|
+
and scoped to whoever arrived. The previous principal's question and partial
|
|
254
|
+
answer were filed under their own thread id in the next principal's
|
|
255
|
+
namespace, and the history drawer listed a conversation titled with a
|
|
256
|
+
question the new user never asked. A tool result returned after the Stop, a
|
|
257
|
+
stopped checkpoint continuation, and a key changed while the element was
|
|
258
|
+
detached did the same. A state snapshot already read off the wire put the
|
|
259
|
+
previous principal's shared state back after the handover had emptied it, so
|
|
260
|
+
it went out on the next principal's first run. A client built before a
|
|
261
|
+
handover now writes nothing at all. A key's first arrival is still an
|
|
262
|
+
adoption, so a run in flight across it keeps saving into the namespace it
|
|
263
|
+
moved to, and New chat still files a stopped run's last save under the
|
|
264
|
+
conversation it left.
|
|
265
|
+
- **A change of `user-key` no longer replaces a conversation store the host
|
|
266
|
+
assigned after the element connected.** Connecting remembered the element's
|
|
267
|
+
own store, and a later key change rebuilt that store under the new namespace
|
|
268
|
+
whether or not it was still the one in use. A store the host assigned to a
|
|
269
|
+
connected element was swapped for a `sessionStorage` one, and a
|
|
270
|
+
`RemoteConversationStore` built with `cacheMessages: false` to keep message
|
|
271
|
+
bodies off the client started caching every one of them in the tab. A store
|
|
272
|
+
the host assigned is now left alone whenever it was assigned, and scopes
|
|
273
|
+
itself. The one exception is unchanged: a plain `SessionStorageStore`
|
|
274
|
+
assigned before connecting is the same kind as the element's own default,
|
|
275
|
+
so the element still namespaces it as its own.
|
|
276
|
+
- **A change of `user-key` clears the composer.** The transcript and recall
|
|
277
|
+
history were already cleared so the previous principal's words were not in
|
|
278
|
+
front of the next one, but a turn they had typed and not sent stayed in the
|
|
279
|
+
box, on a live change and on one made while detached. A handover now empties
|
|
280
|
+
the composer, which takes a quotation or a skill's template with it. It also
|
|
281
|
+
takes down the skill hint, closes a slash palette the text had opened, and on
|
|
282
|
+
a live change stops a voice recording in progress before it is transcribed.
|
|
283
|
+
The first arrival of a key, a move that keeps the key, and New chat leave the
|
|
284
|
+
text where it is.
|
|
285
|
+
- **A tool call whose run was stopped or handed over while it waited no longer
|
|
286
|
+
acts afterwards.** Dispatch awaited the host's `confirmPredicate`, the
|
|
287
|
+
confirmation card and the handler, and checked after none of them whether
|
|
288
|
+
the run was still going. A predicate that answered after Stop opened its
|
|
289
|
+
card or ran its handler for a run the user had ended, and a Confirm clicked
|
|
290
|
+
in the same task as Stop still ran the call. Across a change of `user-key`,
|
|
291
|
+
the previous principal's handler ran on the next principal's page, a
|
|
292
|
+
navigating tool's checkpoint went into the next principal's storage, and a
|
|
293
|
+
handler failing after the handover cleared their checkpoint instead of its
|
|
294
|
+
own. Such a call now settles as not finished and posts nothing, so the next
|
|
295
|
+
request answers it as not finished, and after a handover it draws and
|
|
296
|
+
writes nothing. Both checkpoint writes go to the thread the call was
|
|
297
|
+
dispatched under. A plain Stop while a handler runs still keeps its result,
|
|
298
|
+
since that action did happen on the page.
|
|
299
|
+
- **A voice clip that comes back after a change of `user-key` is dropped, not
|
|
300
|
+
written into the next principal's composer.** The mic checked whether it had
|
|
301
|
+
been taken down only before it posted the clip, so a transcript already on
|
|
302
|
+
its way when the key changed landed in the composer the change had just
|
|
303
|
+
emptied, and a failed one drew its error on the old button. The permission
|
|
304
|
+
prompt had the same gap: a mic granted after the key changed or the element
|
|
305
|
+
left the page started a recording nobody could see or stop, and its tracks
|
|
306
|
+
were never released, so the browser's recording indicator stayed lit.
|
|
307
|
+
Removing the element while a clip transcribes now drops that clip too, as
|
|
308
|
+
removing it mid-recording already did, and that holds for a move that keeps
|
|
309
|
+
the key.
|
|
310
|
+
|
|
10
311
|
## [0.41.3] — 2026-10-07
|
|
11
312
|
|
|
12
313
|
### Fixed
|
|
@@ -4250,7 +4551,9 @@ hosts that both arrange the page the way it expects.
|
|
|
4250
4551
|
### Notes
|
|
4251
4552
|
- First release — exercising the automated npm OIDC publish pipeline end-to-end.
|
|
4252
4553
|
|
|
4253
|
-
[Unreleased]: https://github.com/Artui/ag-ui-web-component/compare/v0.
|
|
4554
|
+
[Unreleased]: https://github.com/Artui/ag-ui-web-component/compare/v0.43.0...HEAD
|
|
4555
|
+
[0.43.0]: https://github.com/Artui/ag-ui-web-component/compare/v0.42.0...v0.43.0
|
|
4556
|
+
[0.42.0]: https://github.com/Artui/ag-ui-web-component/compare/v0.41.3...v0.42.0
|
|
4254
4557
|
[0.41.3]: https://github.com/Artui/ag-ui-web-component/compare/v0.41.2...v0.41.3
|
|
4255
4558
|
[0.41.2]: https://github.com/Artui/ag-ui-web-component/compare/v0.41.1...v0.41.2
|
|
4256
4559
|
[0.41.1]: https://github.com/Artui/ag-ui-web-component/compare/v0.41.0...v0.41.1
|
package/README.md
CHANGED
|
@@ -305,13 +305,56 @@ the `copyCode` / `copied` / `copyFailed` strings.
|
|
|
305
305
|
|
|
306
306
|
`sendMessage(content, attachments?)` sends as if the user had typed it — user bubble,
|
|
307
307
|
`ag-ui-submit` event, run started. Use it for an "Ask about this order" button, a command
|
|
308
|
-
palette,
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
308
|
+
palette, a prompt fired when your page mounts the chat, or a composer of your own replacing the
|
|
309
|
+
built-in one.
|
|
310
|
+
|
|
311
|
+
It returns a `Promise<boolean>`. It resolves `true` once the turn has been handed to the agent
|
|
312
|
+
and its run has settled, frontend-tool rounds included, and `false` when nothing was sent:
|
|
313
|
+
|
|
314
|
+
- for an entirely empty message;
|
|
315
|
+
- while a run, or a checkpoint continuation picked from the panel, is in flight. A continuation
|
|
316
|
+
counts from the pick, and a send from the call rather than from when its run starts, so a
|
|
317
|
+
`sendMessage` from an `ag-ui-submit` listener, or a second call in the same task, is refused
|
|
318
|
+
rather than starting a second run;
|
|
319
|
+
- with no `endpoint` set, which the transcript also says;
|
|
320
|
+
- while a reload resumes a run a navigating tool interrupted. The resume counts from the moment
|
|
321
|
+
the element asks its conversation store for the conversation, and it ends by starting that
|
|
322
|
+
run, so a send then is refused, and the built-in Send queues behind the resumed answer;
|
|
323
|
+
- when your own code that runs on the way to the request — an `ag-ui-submit` listener, the
|
|
324
|
+
conversation store's save — stops it by starting a new chat or changing `user-key`. A
|
|
325
|
+
`getContext` or `getTools` that does the same stops it too, but after the turn was handed
|
|
326
|
+
over, so the call resolves `true`.
|
|
327
|
+
|
|
328
|
+
**While the element restores a stored conversation, a send waits for it.** Every restore —
|
|
329
|
+
when the element connects, on `reload()`, when a thread is picked from the drawer, and when
|
|
330
|
+
`user-key` changes from one principal to another — first asks the conversation store for the conversation, and with
|
|
331
|
+
`data-threads-url` that is a request to your server. A `sendMessage` made in that time waits
|
|
332
|
+
until the conversation is drawn, then sends with it: the bubble, the `ag-ui-submit` event and the
|
|
333
|
+
request all come after the replay, later than the call by the length of the load. So a prompt
|
|
334
|
+
fired on mount needs no timing of its own. If New chat, a thread switch, a change from one
|
|
335
|
+
`user-key` to another or a `reload()` comes while it waits, the conversation it was addressed
|
|
336
|
+
to is gone, so it is dropped and resolves `false` at once. The element leaving the page drops it
|
|
337
|
+
too, but it resolves `false` only once the load answers or the element is inserted again. A
|
|
338
|
+
first `user-key` on an anonymous conversation adopts that conversation rather than replacing
|
|
339
|
+
it, so a send waiting then still goes out. A second `sendMessage` while one waits is refused. The built-in Send queues what is typed during the load ("Waiting to send"),
|
|
340
|
+
and sends it after the conversation is drawn, behind a `sendMessage` that was waiting.
|
|
341
|
+
|
|
342
|
+
Unlike the built-in Send it does **not** queue, so your composer keeps what it tried to send, and
|
|
343
|
+
it does **not** consult the attachment tray: what you pass is what is sent, so your composer stays
|
|
344
|
+
in charge of its own state.
|
|
345
|
+
|
|
346
|
+
`retryLastTurn()` keeps the same rules, because it starts a run on the same conversation. It
|
|
347
|
+
returns `false` while a run, a send, another Retry or a continuation is in flight, and while
|
|
348
|
+
any restore of the stored conversation is still loading. It does not wait for the load as a send
|
|
349
|
+
does: until the conversation is drawn there is nothing on screen to retry, and afterwards it
|
|
350
|
+
would ask the previous visit's last question again. It counts from the call as a send does: until its run starts, a `sendMessage` or a
|
|
351
|
+
second Retry is refused, the built-in Send queues, and a checkpoint pick is refused. It also
|
|
352
|
+
returns `false` when your own code stops it before its run starts: a conversation store, or an
|
|
353
|
+
activity or tool renderer it redraws through, that starts a new chat or changes `user-key`. A
|
|
354
|
+
`getContext` or `getTools` that does the same stops it too, before anything is sent, but it runs
|
|
355
|
+
after the Retry has handed its turn to the client, so the call resolves `true`. A store that
|
|
356
|
+
throws from the Retry's save leaves the conversation as it was, and `retryLastTurn()` rejects
|
|
357
|
+
with its error.
|
|
315
358
|
|
|
316
359
|
`attachFile(file)` queues a file into the tray exactly as the picker and drag-and-drop do, with
|
|
317
360
|
the same validation and progress chip. It returns `false` when uploads are not configured
|
|
@@ -482,9 +525,10 @@ function Assistant() {
|
|
|
482
525
|
|
|
483
526
|
Writing it as `<ag-ui-chat ref={...} />` in JSX and configuring in the ref callback mostly works —
|
|
484
527
|
the catalog requests are held back one microtask precisely so a ref assigned in the same commit is
|
|
485
|
-
honoured — but the thread-history request is **not** deferred
|
|
486
|
-
|
|
487
|
-
|
|
528
|
+
honoured — but the thread-history request is **not** deferred, so that one goes out with whatever
|
|
529
|
+
was configured at insertion. It starts at insertion because the restore is what a `sendMessage()`
|
|
530
|
+
from the same commit waits on: a restore that had not started yet would have nothing to hold it,
|
|
531
|
+
and the send would go out without the conversation.
|
|
488
532
|
|
|
489
533
|
If your credentials can only arrive later still — an awaited token, a passive effect — call
|
|
490
534
|
**`reload()`** once they land:
|
|
@@ -655,7 +699,10 @@ AG-UI has no server-side cancel route: cancelling **aborts the streaming request
|
|
|
655
699
|
`cancel()` with no run in flight is a safe no-op. `newChat()` cancels any in-flight run before
|
|
656
700
|
discarding the client, and so do switching conversations, `reload()` and removing the element. A
|
|
657
701
|
[checkpoint continuation](#resuming-a-run) is the run in flight while it lasts, so Stop and all of
|
|
658
|
-
these end it too.
|
|
702
|
+
these end it too. So does a turn that has not made its request yet: called from host code a send
|
|
703
|
+
or a resume runs on the way — the conversation store's save, `getContext`, `getTools` —
|
|
704
|
+
`cancel()` stops it before anything is sent, and `onCancelled()` and `onSettled()` fire as for any
|
|
705
|
+
other Stop.
|
|
659
706
|
|
|
660
707
|
A cancelled run ends a moment later, once its request has closed or a running tool handler has
|
|
661
708
|
returned. What it does then stays with the conversation it belonged to: its truncated exchange is
|
|
@@ -717,7 +764,8 @@ Whether a call is gated is decided in this order:
|
|
|
717
764
|
for others, which a static flag can't express). **A predicate that throws or rejects refuses
|
|
718
765
|
the call**: no card, the handler does not run, the tool card settles as declined, and the agent
|
|
719
766
|
gets the `confirmCheckFailed` string as the result and carries on, as after a decline. The
|
|
720
|
-
error goes to the console, never to the endpoint.
|
|
767
|
+
error goes to the console, never to the endpoint. A predicate still pending when the run is
|
|
768
|
+
stopped decides nothing: the call does not run, and its card settles as not finished.
|
|
721
769
|
3. Else if the user has waived this tool name for the session, the call runs.
|
|
722
770
|
4. Else the element falls back to [`isDestructive(parameters)`](src/tools/is_destructive.ts),
|
|
723
771
|
which reads the `x-destructive` JSON-Schema flag.
|
|
@@ -1078,7 +1126,10 @@ matching JS API:
|
|
|
1078
1126
|
server-backed store it stays on the server. Deleting one is the drawer row's own action. A chat
|
|
1079
1127
|
nothing was ever sent in is the exception — it was never listed, so it is dropped rather than
|
|
1080
1128
|
left behind. Focus moves to the composer, whichever control started the new chat, unless the
|
|
1081
|
-
widget is collapsed, and the page is not scrolled to it.
|
|
1129
|
+
widget is collapsed, and the page is not scrolled to it. A restore still loading or replaying
|
|
1130
|
+
the conversation being left stops there, so nothing of it is drawn into the new chat, and a
|
|
1131
|
+
run it was about to resume after a reload is not resumed: coming back to that conversation
|
|
1132
|
+
later answers the interrupted call as not finished.
|
|
1082
1133
|
- `describeSurface()` — where the panel is and what can be done to it: placement, collapsed,
|
|
1083
1134
|
whether it can be moved, whether it fills the screen, its box and the viewport. `movable` folds
|
|
1084
1135
|
the two reasons a move can fail into the one answer a caller needs.
|
|
@@ -1110,17 +1161,24 @@ that gap is parked too rather than racing it. What
|
|
|
1110
1161
|
is waiting shows above the composer as chips, each of which takes its message
|
|
1111
1162
|
back when pressed, and the next one is sent when the run settles. Stopping the
|
|
1112
1163
|
run discards them: sending into a conversation someone has just stopped is the
|
|
1113
|
-
opposite of what stopping meant.
|
|
1114
|
-
|
|
1115
|
-
|
|
1164
|
+
opposite of what stopping meant. Nothing is lost, though — a queued message
|
|
1165
|
+
entered the recall history below when it left the composer, so **Up** gets it
|
|
1166
|
+
back after a Stop, and after its chip is taken back.
|
|
1116
1167
|
|
|
1117
1168
|
The composer also walks back through what you have already sent, on **Up** and
|
|
1118
|
-
**Down** — the shape every shell and every coding agent uses.
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1169
|
+
**Down** — the shape every shell and every coding agent uses. It holds every turn
|
|
1170
|
+
that left the composer: one sent at once, one queued behind a run (from the
|
|
1171
|
+
moment it is queued), and one sent to resume or fork a run from the checkpoint
|
|
1172
|
+
panel. A message sent with `sendMessage` is not in it, because it was never
|
|
1173
|
+
typed there. Only from an empty composer and only with the skills palette closed:
|
|
1174
|
+
an arrow inside text is how you move the caret, and taking it unconditionally
|
|
1175
|
+
would break editing to add a shortcut. Arrowing forward past the newest turn
|
|
1176
|
+
empties the box again, so the way out is the key that got you in. A walk lasts
|
|
1177
|
+
only while the composer holds the turn it put there: anything else that writes
|
|
1178
|
+
the box — typing, a quotation, a skill, dictation, or the host page itself — ends
|
|
1179
|
+
it, so the next arrow moves the caret in what was written rather than replacing
|
|
1180
|
+
it. The history is this conversation's: starting a new chat, switching threads
|
|
1181
|
+
or changing `user-key` clears it with the transcript.
|
|
1124
1182
|
|
|
1125
1183
|
### Collapsing to the launcher
|
|
1126
1184
|
|
|
@@ -1858,7 +1916,7 @@ chat.addEventListener("ag-ui-feedback", (e) => {
|
|
|
1858
1916
|
analytics.track("assistant_rating", e.detail); // { content, rating }
|
|
1859
1917
|
});
|
|
1860
1918
|
|
|
1861
|
-
await chat.retryLastTurn(); // false when there is nothing to ask again
|
|
1919
|
+
await chat.retryLastTurn(); // false when there is nothing to ask again, a run is in flight, or a restore is loading
|
|
1862
1920
|
```
|
|
1863
1921
|
|
|
1864
1922
|
## Quoting a selection
|
|
@@ -2091,6 +2149,16 @@ waived with *Always allow* are forgotten with it, so the next principal is asked
|
|
|
2091
2149
|
sent on the next principal's first run, so a host that seeds shared state assigns it again for the
|
|
2092
2150
|
principal who arrived.
|
|
2093
2151
|
|
|
2152
|
+
A handover also ends what the previous principal had in flight. Their run is stopped, and nothing
|
|
2153
|
+
it does afterwards is written into the next principal's storage or shared state: not the save a
|
|
2154
|
+
stopped run makes once its request closes, not a tool result that comes back after the Stop, not
|
|
2155
|
+
state already on its way. A tool call still waiting on `confirmPredicate` or its confirmation card
|
|
2156
|
+
does not run, one whose handler is still running draws nothing when it returns, and a navigating
|
|
2157
|
+
tool's checkpoint is never written into the next principal's thread. What they had typed into the
|
|
2158
|
+
composer and not sent is cleared with the transcript, quotations and skill templates included. A
|
|
2159
|
+
voice recording in progress is stopped before it is transcribed, and a clip already sent for
|
|
2160
|
+
transcription is dropped when its transcript comes back.
|
|
2161
|
+
|
|
2094
2162
|
Set it live, from script, as part of signing out or in:
|
|
2095
2163
|
|
|
2096
2164
|
```js
|
|
@@ -2102,10 +2170,17 @@ out through its own router without remounting anything, so the host naming the n
|
|
|
2102
2170
|
dropping the attribute — is the only signal the element will ever get. Removing the attribute
|
|
2103
2171
|
purges too, so a sign-out that simply clears it is safe.
|
|
2104
2172
|
|
|
2173
|
+
It holds for an element that is out of the document too, such as a view a router keeps alive.
|
|
2174
|
+
Nothing is purged while it is detached: the handover runs when it is inserted again, against the
|
|
2175
|
+
key it has by then, so a key that changes and changes back while detached hands nothing over. An
|
|
2176
|
+
element that is never inserted again leaves the previous principal's conversation in
|
|
2177
|
+
`sessionStorage` under their key, where only an element naming that principal again can reach it.
|
|
2178
|
+
|
|
2105
2179
|
The **first** value to arrive is treated as a host naming the user who was already there, not as a
|
|
2106
2180
|
handover: the conversation in progress moves into the principal's namespace instead of being
|
|
2107
2181
|
destroyed. So an element configured by an async auth handshake — the shape described in
|
|
2108
|
-
[Framework hosts](#framework-hosts-configure-before-you-insert) — keeps what is on screen
|
|
2182
|
+
[Framework hosts](#framework-hosts-configure-before-you-insert) — keeps what is on screen,
|
|
2183
|
+
including a run still streaming and whatever is in the composer.
|
|
2109
2184
|
|
|
2110
2185
|
Two things it deliberately does not do. It does not scope the panel's own collapsed / dragged-size
|
|
2111
2186
|
/ theme preferences, which are this element's UI state and carry no conversation content. And it
|
|
@@ -2141,6 +2216,15 @@ chat.conversationStore = new RemoteConversationStore(
|
|
|
2141
2216
|
);
|
|
2142
2217
|
```
|
|
2143
2218
|
|
|
2219
|
+
A conversation store you assign yourself is never replaced by a change of `user-key`, whether you
|
|
2220
|
+
assign it before the element connects or after. The one exception is a plain `SessionStorageStore`
|
|
2221
|
+
assigned before the element connects: that is the same kind as the element's own default, which is
|
|
2222
|
+
all connecting can see, so the element namespaces it as its own and moves it on every key change.
|
|
2223
|
+
Assign it after connecting, or wrap it, to keep it yours. A store that keeps its data somewhere
|
|
2224
|
+
the element cannot see has to scope itself; the element purges only its own `sessionStorage`
|
|
2225
|
+
namespace and clears what is on screen. If your store wraps a `SessionStorageStore`, build a new
|
|
2226
|
+
one per principal.
|
|
2227
|
+
|
|
2144
2228
|
`SessionStorageStore.purge(namespace)` is the same primitive the element uses, for a host driving
|
|
2145
2229
|
its own store from its own sign-out path.
|
|
2146
2230
|
|
|
@@ -2424,6 +2508,11 @@ stop it, and what you typed stays in the composer for the pick after. The run in
|
|
|
2424
2508
|
flight is not cancelled for you, since a continuation starting over it would
|
|
2425
2509
|
leave two answers streaming into one transcript with Stop reaching only one.
|
|
2426
2510
|
|
|
2511
|
+
Picking a row while a stored conversation is still loading is refused the same
|
|
2512
|
+
way, with a hint asking you to wait for the load: a continuation sends only the
|
|
2513
|
+
new turn, so it must not start before the history it continues is on screen. The
|
|
2514
|
+
wording is the `continueWhileLoading` string.
|
|
2515
|
+
|
|
2427
2516
|
### One URL, three endpoints
|
|
2428
2517
|
|
|
2429
2518
|
`data-runs-url` is the only thing to configure. `resume/<id>/` and `fork/<id>/`
|