@artooi/ag-ui-web-component 0.42.0 → 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 CHANGED
@@ -7,6 +7,133 @@ 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
+
10
137
  ## [0.42.0] — 2026-10-07
11
138
 
12
139
  ### Fixed
@@ -4424,7 +4551,8 @@ hosts that both arrange the page the way it expects.
4424
4551
  ### Notes
4425
4552
  - First release — exercising the automated npm OIDC publish pipeline end-to-end.
4426
4553
 
4427
- [Unreleased]: https://github.com/Artui/ag-ui-web-component/compare/v0.42.0...HEAD
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
4428
4556
  [0.42.0]: https://github.com/Artui/ag-ui-web-component/compare/v0.41.3...v0.42.0
4429
4557
  [0.41.3]: https://github.com/Artui/ag-ui-web-component/compare/v0.41.2...v0.41.3
4430
4558
  [0.41.2]: https://github.com/Artui/ag-ui-web-component/compare/v0.41.1...v0.41.2
package/README.md CHANGED
@@ -305,23 +305,49 @@ 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, or a composer of your own replacing the built-in one. It no-ops for an entirely
309
- empty message, and while a run — or a checkpoint continuation picked from the panel — is in
310
- flight. A continuation counts from the pick, and a send from the call rather than from when its
311
- run starts, so a `sendMessage` from an `ag-ui-submit` listener, or a second call in the same
312
- task, is refused rather than starting a second run. So is one made while a reload resumes a run
313
- a navigating tool interrupted: the resume counts from the moment the element asks its
314
- conversation store for the conversation, so a `sendMessage` while a remote store is still
315
- answering is refused, and the built-in Send queues behind the resumed answer. Your own code
316
- that runs on the way to the request — the conversation store's save, `getContext`, `getTools` —
317
- can stop a send by starting a new chat or changing `user-key`, and then nothing is sent. Unlike
318
- the built-in Send it does **not** queue, so your composer keeps what it tried to send, and it
319
- does **not** consult the attachment tray: what you pass is what is sent, so your composer stays
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
320
344
  in charge of its own state.
321
345
 
322
346
  `retryLastTurn()` keeps the same rules, because it starts a run on the same conversation. It
323
- returns `false` while a run, a send, another Retry, a continuation or a reload's resume is in
324
- flight, and it counts from the call as a send does: until its run starts, a `sendMessage` or a
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
325
351
  second Retry is refused, the built-in Send queues, and a checkpoint pick is refused. It also
326
352
  returns `false` when your own code stops it before its run starts: a conversation store, or an
327
353
  activity or tool renderer it redraws through, that starts a new chat or changes `user-key`. A
@@ -499,9 +525,10 @@ function Assistant() {
499
525
 
500
526
  Writing it as `<ag-ui-chat ref={...} />` in JSX and configuring in the ref callback mostly works —
501
527
  the catalog requests are held back one microtask precisely so a ref assigned in the same commit is
502
- honoured — but the thread-history request is **not** deferred (a deferred replay could land after a
503
- `sendMessage()` and duplicate the transcript), so that one goes out with whatever was configured at
504
- insertion.
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.
505
532
 
506
533
  If your credentials can only arrive later still — an awaited token, a passive effect — call
507
534
  **`reload()`** once they land:
@@ -1889,7 +1916,7 @@ chat.addEventListener("ag-ui-feedback", (e) => {
1889
1916
  analytics.track("assistant_rating", e.detail); // { content, rating }
1890
1917
  });
1891
1918
 
1892
- await chat.retryLastTurn(); // false when there is nothing to ask again, or a run is in flight
1919
+ await chat.retryLastTurn(); // false when there is nothing to ask again, a run is in flight, or a restore is loading
1893
1920
  ```
1894
1921
 
1895
1922
  ## Quoting a selection
@@ -2481,6 +2508,11 @@ stop it, and what you typed stays in the composer for the pick after. The run in
2481
2508
  flight is not cancelled for you, since a continuation starting over it would
2482
2509
  leave two answers streaming into one transcript with Stop reaching only one.
2483
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
+
2484
2516
  ### One URL, three endpoints
2485
2517
 
2486
2518
  `data-runs-url` is the only thing to configure. `resume/<id>/` and `fork/<id>/`