@artooi/ag-ui-web-component 0.28.0 → 0.29.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/CHANGELOG.md +417 -1
  2. package/README.md +371 -5
  3. package/dist/ag-ui-web-component.bundle.js +308 -50
  4. package/dist/ag-ui-web-component.bundle.js.map +4 -4
  5. package/dist/constants.d.ts +69 -0
  6. package/dist/constants.d.ts.map +1 -1
  7. package/dist/core/ag_ui_chat.d.ts +207 -0
  8. package/dist/core/ag_ui_chat.d.ts.map +1 -1
  9. package/dist/core/agui_client.d.ts +38 -0
  10. package/dist/core/agui_client.d.ts.map +1 -1
  11. package/dist/index.d.ts +7 -2
  12. package/dist/index.d.ts.map +1 -1
  13. package/dist/index.js +1516 -76
  14. package/dist/index.js.map +4 -4
  15. package/dist/ui/approval_card.d.ts +18 -0
  16. package/dist/ui/approval_card.d.ts.map +1 -1
  17. package/dist/ui/checkpoint_menu.d.ts +10 -0
  18. package/dist/ui/checkpoint_menu.d.ts.map +1 -1
  19. package/dist/ui/confirmation_card.d.ts +16 -0
  20. package/dist/ui/confirmation_card.d.ts.map +1 -1
  21. package/dist/ui/message_actions.d.ts +46 -0
  22. package/dist/ui/message_actions.d.ts.map +1 -0
  23. package/dist/ui/page_quote_offer.d.ts +33 -0
  24. package/dist/ui/page_quote_offer.d.ts.map +1 -0
  25. package/dist/ui/quote_selection.d.ts +66 -0
  26. package/dist/ui/quote_selection.d.ts.map +1 -0
  27. package/dist/ui/relative_time.d.ts +10 -0
  28. package/dist/ui/relative_time.d.ts.map +1 -1
  29. package/dist/ui/stick_to_bottom.d.ts +55 -0
  30. package/dist/ui/stick_to_bottom.d.ts.map +1 -0
  31. package/dist/ui/styles.d.ts +1 -1
  32. package/dist/ui/styles.d.ts.map +1 -1
  33. package/dist/ui/suggestion_chips.d.ts +29 -0
  34. package/dist/ui/suggestion_chips.d.ts.map +1 -0
  35. package/dist/ui/thread_drawer.d.ts +10 -0
  36. package/dist/ui/thread_drawer.d.ts.map +1 -1
  37. package/dist/ui/tool_call_card.d.ts +8 -0
  38. package/dist/ui/tool_call_card.d.ts.map +1 -1
  39. package/dist/ui/ui_strings.d.ts +40 -0
  40. package/dist/ui/ui_strings.d.ts.map +1 -1
  41. package/package.json +1 -1
  42. package/src/constants.ts +75 -0
  43. package/src/core/ag_ui_chat.ts +927 -73
  44. package/src/core/agui_client.ts +63 -0
  45. package/src/index.ts +39 -0
  46. package/src/ui/approval_card.ts +90 -2
  47. package/src/ui/checkpoint_menu.ts +22 -5
  48. package/src/ui/confirmation_card.ts +29 -1
  49. package/src/ui/message_actions.ts +158 -0
  50. package/src/ui/page_quote_offer.ts +215 -0
  51. package/src/ui/quote_selection.ts +345 -0
  52. package/src/ui/relative_time.ts +11 -0
  53. package/src/ui/stick_to_bottom.ts +126 -0
  54. package/src/ui/styles.ts +227 -0
  55. package/src/ui/suggestion_chips.ts +73 -0
  56. package/src/ui/thread_drawer.ts +22 -2
  57. package/src/ui/tool_call_card.ts +9 -0
  58. package/src/ui/ui_strings.ts +60 -0
  59. package/src/version.ts +1 -1
package/README.md CHANGED
@@ -50,6 +50,11 @@ No framework, no Django, no admin specifics live here. Downstream consumers (e.g
50
50
  - [Collapsing to the launcher](#collapsing-to-the-launcher)
51
51
  - [Tool-call display modes](#tool-call-display-modes)
52
52
  - [Markdown rendering](#markdown-rendering)
53
+ - [Follow-up suggestions](#follow-up-suggestions)
54
+ - [Editing a gated call before approving it](#editing-a-gated-call-before-approving-it)
55
+ - [Localizing the timestamps](#localizing-the-timestamps)
56
+ - [Message actions: copy, retry, feedback](#message-actions-copy-retry-feedback)
57
+ - [Quoting a selection](#quoting-a-selection)
53
58
  - [Run notices: compaction and agent skills](#run-notices-compaction-and-agent-skills)
54
59
  - [Skills: prompt chips and slash palette](#skills-prompt-chips-and-slash-palette)
55
60
  - [MPA durability: surviving full page reloads](#mpa-durability-surviving-full-page-reloads)
@@ -177,6 +182,7 @@ another origin, add `credentials="include"` too; see
177
182
  | `data-icon-url` | — | Header (and launcher) icon image URL. A slotted `slot="icon"` wins; see [Header & launcher icon](#header-and-launcher-icon). |
178
183
  | `data-launcher-icon-url` | — | Icon image URL for the collapsed launcher only, when it should differ from the header's. Falls back to `data-icon-url`; a slotted `slot="launcher"` wins over both. |
179
184
  | `data-unread-badge` | — | **On by default.** `="false"` hides the launcher's unread badge; the count and the `ag-ui-unread` event keep running. See [Collapsing to the launcher](#collapsing-to-the-launcher). |
185
+ | `data-quote-selection` | — | **On by default.** `="false"` stops the transcript offering to quote a selection. `quote()` keeps working either way. See [Quoting a selection](#quoting-a-selection). |
180
186
 
181
187
  Each header control also takes its own icon slot — `icon-history`, `icon-checkpoints`,
182
188
  `icon-new`, `icon-collapse` — with the built-in glyph as the fallback, so a host can project a
@@ -561,6 +567,8 @@ transcript as a resolved record after the decision:
561
567
  - **Confirm** → the handler runs and the result is posted back.
562
568
  - **Cancel** → a `"User declined the action."` result is posted; the agent acknowledges on its
563
569
  next turn.
570
+ - **Always allow** → the handler runs *and* this tool stops prompting for the rest of the
571
+ session. See below for when this button appears.
564
572
 
565
573
  Whether a call is gated is decided in this order:
566
574
 
@@ -568,9 +576,27 @@ Whether a call is gated is decided in this order:
568
576
  2. Else if `chat.confirmPredicate` is set, its boolean return is authoritative — given the tool
569
577
  name + parsed args it decides per-call (so one tool can be instant for some args and confirmed
570
578
  for others, which a static flag can't express).
571
- 3. Else the element falls back to [`isDestructive(parameters)`](src/tools/is_destructive.ts),
579
+ 3. Else if the user has waived this tool name for the session, the call runs.
580
+ 4. Else the element falls back to [`isDestructive(parameters)`](src/tools/is_destructive.ts),
572
581
  which reads the `x-destructive` JSON-Schema flag.
573
582
 
583
+ #### "Always allow", and why only sometimes
584
+
585
+ A prompt that is approved nearly every time is not a decision, it is a speed bump — and the
586
+ reflex it trains is what makes the rare refusal easy to miss. Anthropic published that users
587
+ approve **~93%** of Claude Code permission prompts manually and called interactive confirmation
588
+ *"behaviorally unreliable as a sole safety mechanism"* on that basis. The waiver exists so the
589
+ prompts that remain still mean something.
590
+
591
+ **The button is offered only on cards raised by step 4** — the `x-destructive` default. Where
592
+ `confirmPredicate` is what gated the call, there is no button, because that predicate is
593
+ documented as authoritative and letting one click retire it would silently defeat a host policy.
594
+ The offer and the allowlist sit on the same path, so there is no dead button either.
595
+
596
+ The waiver is **per tool name and per element**, held in memory and never persisted. A session
597
+ decision that outlived the tab would be a permanent grant made by one click — which is what
598
+ `autoConfirm` already exists to say deliberately. It is cleared when the element goes away.
599
+
574
600
  AG-UI has no built-in risk flag, so destructiveness is carried as a JSON-Schema extension at the
575
601
  **schema root**: `parameters["x-destructive"] = true` (use the exported `X_DESTRUCTIVE_KEY`
576
602
  constant). There is no parallel metadata channel and no name heuristic — destructiveness is exactly
@@ -1009,6 +1035,67 @@ it runs again every time the conversation is restored.
1009
1035
 
1010
1036
  ---
1011
1037
 
1038
+ ## Drawing other things the server pushes
1039
+
1040
+ `activityType` is an open string the protocol does not enumerate — `"chart"` is
1041
+ just the one the component ships a renderer for. Register your own and the
1042
+ server can push anything it likes into the transcript:
1043
+
1044
+ ```js
1045
+ chat.registerActivityRenderer({
1046
+ type: "build_status",
1047
+ render: (content) => {
1048
+ const el = document.createElement("div");
1049
+ el.className = "build";
1050
+ el.textContent = `Build ${content.status}`;
1051
+ return el; // return null for content not worth drawing
1052
+ },
1053
+ });
1054
+ ```
1055
+
1056
+ **`render` runs again on every thread load**, so it carries the same contract as
1057
+ a client tool's `render`: a pure function of `content`, deterministic, and free
1058
+ of effects outside the node it returns. Activities are materialised into
1059
+ `role: "activity"` messages and persisted with the transcript, so a renderer that
1060
+ writes to the page instead of returning DOM fires again on every restore.
1061
+
1062
+ The component places what you return, keyed by the activity's `messageId`, so a
1063
+ server repeating an id **replaces** your node rather than adding a second one —
1064
+ the same in-place update charts get. Returning `null` removes whatever was there:
1065
+ live and reload should agree, and the stored content is the version that could
1066
+ not be drawn.
1067
+
1068
+ `chart` and `compaction` are registrations exactly like yours, not privileged
1069
+ branches, so registering either name **replaces the built-in**.
1070
+
1071
+ ### Which carrier should the server use?
1072
+
1073
+ AG-UI leaves exactly two payload names open, and they are not
1074
+ interchangeable:
1075
+
1076
+ | | Carrier | Reaches | Persisted | Replayed |
1077
+ | --- | --- | --- | --- | --- |
1078
+ | **Content** | `ACTIVITY_SNAPSHOT` | the transcript | yes | yes |
1079
+ | **Imperative** | `CUSTOM` | your page, as [`ag-ui-custom`](#events) | no | no |
1080
+
1081
+ ⇒ **Content has a place in the conversation and should come back. An imperative
1082
+ has no place and no meaning once acted on** — replaying "refetch the board" on
1083
+ every thread load is a bug, not a feature. If it has to survive a reload, it is
1084
+ content.
1085
+
1086
+ ### Finding out what arrived
1087
+
1088
+ An activity nobody registered for draws nothing and logs nothing — that is the
1089
+ protocol's own answer, and warning would fire on every forward-compatible
1090
+ server. But silence is hard to debug, so the names are readable:
1091
+
1092
+ ```js
1093
+ chat.unhandledActivityTypes; // ["pydantic_ai_thinking", …]
1094
+ ```
1095
+
1096
+ Note `"chart"` appears there until you call `enableCharts(["activity"])`, which
1097
+ is the honest answer to "I pushed a chart and nothing happened".
1098
+
1012
1099
  ## Tool-call display modes
1013
1100
 
1014
1101
  How much a tool-call card shows is set via the `data-tool-display` attribute (or `toolDisplay`
@@ -1127,6 +1214,181 @@ reveal). It honours `prefers-reduced-motion` (collapsing to instant).
1127
1214
 
1128
1215
  ---
1129
1216
 
1217
+ ## Follow-up suggestions
1218
+
1219
+ The agent can offer the *next* question, not just answer this one. Registered
1220
+ skill chips are static and host-configured, so they can say "summarize this" but
1221
+ never "want me to update the shipping address too?" after a tool has run.
1222
+
1223
+ The server pushes an `ACTIVITY_SNAPSHOT` under the `suggestions` type — with
1224
+ django-ag-ui, `suggestions_activity([...])`. The component draws each prompt as
1225
+ a chip; clicking one sends it as the user's message, exactly as if they had
1226
+ typed it.
1227
+
1228
+ ```json
1229
+ { "activityType": "suggestions",
1230
+ "content": { "prompts": ["Update the shipping address too"] } }
1231
+ ```
1232
+
1233
+ **Chips are content.** They ride the same activity envelope as charts, so they
1234
+ persist and a reload puts them back, and a set pushed under an id already on
1235
+ screen replaces that row rather than adding a second one.
1236
+
1237
+ At most **4** prompts of **120** characters each — `MAX_SUGGESTIONS` and
1238
+ `MAX_SUGGESTION_CHARS`, both exported. The server-side helper *raises* past
1239
+ those bounds while this side silently drops: that asymmetry is deliberate, since
1240
+ the producer can report the problem and the client cannot.
1241
+
1242
+ Nothing to enable. A `suggestions` activity from a server that pushes one is
1243
+ drawn; an `activity_type` this component does not know is ignored, which is what
1244
+ the open field is for.
1245
+
1246
+ ## Editing a gated call before approving it
1247
+
1248
+ AG-UI's resume payload carries `editedArgs`, and the protocol gates it on the
1249
+ agent's own `approveWithEdits` capability. The approval card can offer it:
1250
+
1251
+ ```js
1252
+ chat.approveWithEdits = true; // your server accepts editedArgs
1253
+ ```
1254
+
1255
+ **Off by default, and an assertion about your server rather than a
1256
+ negotiation** — capabilities are not on the wire this component reads, so it
1257
+ cannot check. Turned on against a server that ignores `editedArgs`, a user would
1258
+ edit arguments it silently discards, which is worse than not offering.
1259
+
1260
+ The card then shows the call's arguments as editable JSON. `editedArgs` rides
1261
+ the resume payload **only when something actually changed**, so a server can tell
1262
+ "approved as proposed" from "approved, but like this" without diffing what it
1263
+ already sent. Unparseable JSON, or JSON that is not an object, keeps the card
1264
+ open with the reason on it rather than approving the original behind the user's
1265
+ back.
1266
+
1267
+ Only offered for an interrupt naming a tool call this component holds a card
1268
+ for — the card is where the arguments still are.
1269
+
1270
+ ## Localizing the timestamps
1271
+
1272
+ There is **no `Intl` anywhere in this component**. The relative timestamps in the
1273
+ thread drawer and checkpoint panel (`"5m ago"`, `"2d ago"`) are deliberately
1274
+ locale-neutral: a component that guessed a locale would disagree with the page
1275
+ it is embedded in, and being wrong in a second language is worse than being
1276
+ neutral in one.
1277
+
1278
+ That is a good default and a bad requirement, so it is replaceable:
1279
+
1280
+ ```js
1281
+ const rtf = new Intl.RelativeTimeFormat("de", { numeric: "auto" });
1282
+ chat.formatRelativeTime = (ts) =>
1283
+ rtf.format(Math.round((ts - Date.now()) / 60000), "minute");
1284
+ ```
1285
+
1286
+ `relativeTime` is exported too, for a host that wants to build on the built-in
1287
+ rather than replace it.
1288
+
1289
+ ## Message actions: copy, retry, feedback
1290
+
1291
+ Every finished assistant message carries a small row of actions beneath it —
1292
+ a **sibling** of the bubble, never a child, so the buttons never join the
1293
+ message's own text.
1294
+
1295
+ - **Retry** re-asks the question. History is truncated to the most recent user
1296
+ message inclusive and the run repeats, so the agent answers what it was asked
1297
+ rather than being told its last answer was wrong.
1298
+ - **Copy** puts the message's text on the clipboard, and says so on the button.
1299
+ A refused clipboard permission is reported there too, rather than thrown.
1300
+ - **Thumbs up / down** fire [`ag-ui-feedback`](#events) and **store nothing**.
1301
+
1302
+ Retry sits on the **last** answer only. Re-running an older turn is branching,
1303
+ and for a page-driving agent editing a past turn is not neutral — those turns
1304
+ clicked buttons, and re-running turn 3 does not un-save what turn 5 saved.
1305
+
1306
+ !!! note
1307
+ A retried turn **re-runs its tools.** The previous attempt already did what
1308
+ it did, and this does not undo it. Confirmation still applies, so a
1309
+ destructive tool asks again — unless the user waived it for this session
1310
+ with *Always allow*.
1311
+
1312
+ A failed run gets the same row, with Retry and Copy and no rating: error text is
1313
+ what people paste into a bug report, but "the connection dropped" is not a
1314
+ statement about answer quality and mixing it into feedback makes that signal say
1315
+ less. This is why a dropped connection is still rendered as an **error** rather
1316
+ than demoted to a run notice — a notice "never settles, takes no action, and
1317
+ carries no controls", and a failure with a way back needs one.
1318
+
1319
+ `retryLastTurn()` is public, for a host driving its own message UI.
1320
+
1321
+ ```js
1322
+ chat.addEventListener("ag-ui-feedback", (e) => {
1323
+ analytics.track("assistant_rating", e.detail); // { content, rating }
1324
+ });
1325
+
1326
+ await chat.retryLastTurn(); // false when there is nothing to ask again
1327
+ ```
1328
+
1329
+ ## Quoting a selection
1330
+
1331
+ Select any text in the transcript and a small **Quote** offer floats beside it.
1332
+ Taking it drops the selection into the composer as a markdown blockquote and
1333
+ leaves the caret on a fresh line under it — a quotation is how a question
1334
+ narrows to one part of an answer, so nothing is sent until you say what you are
1335
+ asking.
1336
+
1337
+ Quoting **appends**, after whatever is already typed, so a second quotation is a
1338
+ second thing being asked about rather than a replacement for the first. Long
1339
+ selections are capped at 500 characters: select-all-then-quote is a gesture the
1340
+ transcript already answers, and pasting the whole conversation back costs tokens
1341
+ to say nothing.
1342
+
1343
+ Set `data-quote-selection="false"` to turn the offer off. The `quote-selection`
1344
+ `part` styles it.
1345
+
1346
+ ### The half that matters: selection in **your** page
1347
+
1348
+ The transcript is the easy half. A chat mounted beside a table, a diff or a
1349
+ report is sitting in the surface the user actually works in — and *that*
1350
+ selection is one no hosted chat can reach.
1351
+
1352
+ `offerQuoteInPage()` extends the same select-then-offer gesture to the whole
1353
+ page. It is opt-in, because it listens on your document:
1354
+
1355
+ ```js
1356
+ const stop = chat.offerQuoteInPage(); // the whole page
1357
+ chat.offerQuoteInPage(document.querySelector("#report")); // or one region
1358
+ ```
1359
+
1360
+ For a deliberate trigger instead of a selection, `quote(text)` is the seam
1361
+ underneath:
1362
+
1363
+ ```js
1364
+ // "Ask about this row" — a button on each row of your own table.
1365
+ row.querySelector(".ask").addEventListener("click", () => {
1366
+ chat.quote(row.innerText);
1367
+ });
1368
+ ```
1369
+
1370
+ `quote()` never sends — pair it with [`sendMessage()`](#sending-from-your-own-ui)
1371
+ if you want a one-click "explain this" that skips the composer entirely.
1372
+
1373
+ > **Do not write the four-line version of `offerQuoteInPage()`.**
1374
+ > A `mouseup` listener that quotes every settled selection appends to the
1375
+ > composer on every drag the user made to *read*, to copy, or to fix a typo —
1376
+ > and it cannot tell a selection in your prose from one inside the user's own
1377
+ > half-typed `<input>`, because Chrome reports a field's internal selection
1378
+ > through `document.getSelection()` as an ordinary range over the field's
1379
+ > **wrapper**. The text reads back perfectly and nothing about the range says
1380
+ > where it came from; the only signal is `document.activeElement`. That guard,
1381
+ > plus skipping the widget's own transcript, plus retiring a fixed-position
1382
+ > affordance on scroll, is what the method is for.
1383
+
1384
+ > **Reading a selection out of a shadow tree takes care too.**
1385
+ > Engines disagree about what `document.getSelection()` reports for a selection
1386
+ > made *inside* a shadow root: WebKit rescopes the endpoints to the host element,
1387
+ > so you get the whole widget and none of the words, while Chromium hands back
1388
+ > the shadow nodes directly. `getComposedRanges` settles it, and this component
1389
+ > uses it where it exists. `quotableSelection(container, roots)` is exported if
1390
+ > you have the same problem in your own component.
1391
+
1130
1392
  ## Run notices: compaction and agent skills
1131
1393
 
1132
1394
  Some things a run does are neither text nor a tool the user asked for — the server condensed
@@ -1467,6 +1729,83 @@ substitute the other way round either, because shared state requires the *agent*
1467
1729
  `STATE_SNAPSHOT`, which is not the host's decision to make. Use state when the two ends edit one
1468
1730
  object; use this when your page owns the data and just needs to know it moved.
1469
1731
 
1732
+ **`ag-ui-custom`** *(event)* — the agent sent an AG-UI `CUSTOM` event.
1733
+ `detail: { name, value }` (typed `CustomAgentDetail`), both verbatim and uninterpreted.
1734
+
1735
+ ```js
1736
+ chat.addEventListener("ag-ui-custom", (e) => {
1737
+ if (e.detail.name === "invalidate") {
1738
+ void refetch(e.detail.value);
1739
+ }
1740
+ // Any other name: no listener, nothing happens. That is the intended outcome.
1741
+ });
1742
+ ```
1743
+
1744
+ `CUSTOM` is one of exactly two AG-UI carriers whose payload name is an open
1745
+ string the protocol does not enumerate, and it is the **imperative** one:
1746
+ something for your page to *do*. Its sibling `ACTIVITY_SNAPSHOT` carries
1747
+ transcript **content**, which is why an activity is materialised into a message,
1748
+ persisted with the thread and replayed on restore, and this is not.
1749
+
1750
+ **That asymmetry is the rule for choosing between them.** Content has a place in
1751
+ the conversation and should replay. An imperative has no place and no meaning
1752
+ once acted on — replaying "refetch the board" on every thread load is a bug, not
1753
+ a feature. If it must survive a reload, it belongs on the other carrier.
1754
+
1755
+ The element takes no view of what a name means and forwards every one, so a name
1756
+ it has never heard of reaches you unchanged. A host with no listener for a name
1757
+ simply ignores it, which is the graceful outcome an open field exists for.
1758
+
1759
+ **`ag-ui-invalidate`** *(event)* — the agent named resources its write moved.
1760
+ `detail: { keys, reason }` (typed `InvalidateDetail`).
1761
+
1762
+ One `CUSTOM` name routed to its own event so you do not have to string-match;
1763
+ every other name still arrives as `ag-ui-custom`. It fires **as each
1764
+ announcement arrives**, during the run, and the same keys ride
1765
+ `ag-ui-run-finished` again at the end as `invalidated`, de-duplicated.
1766
+
1767
+ > **Do not reload the page on this.** The user was probably typing. An
1768
+ > agent-triggered reload or a blind refetch into a live form destroys unsaved
1769
+ > input, and from their side the page threw their work away on its own. Check
1770
+ > first, and offer rather than act:
1771
+
1772
+ ```js
1773
+ chat.addEventListener("ag-ui-invalidate", (e) => {
1774
+ if (formIsDirty()) {
1775
+ showBanner("This data changed. Refresh when you're ready.", e.detail.keys);
1776
+ return;
1777
+ }
1778
+ refetch(e.detail.keys); // e.detail.keys → ["orders", "orders/42"]
1779
+ });
1780
+ ```
1781
+
1782
+ **Keys are opaque and matching is exact.** `orders/42` does not imply `orders` —
1783
+ a prefix rule would be the component guessing at a scheme it does not own, and
1784
+ `orders/1` would match `orders/11`. A server that wants the collection refreshed
1785
+ names it. Your own matching may be hierarchical, because in your vocabulary the
1786
+ scheme is known; that is what TanStack query keys are built for.
1787
+
1788
+ Already listening on `ag-ui-run-finished`? Upgrading is one line, and the `else`
1789
+ is the whole compatibility story:
1790
+
1791
+ ```js
1792
+ if (detail.invalidated.length > 0) refetchOnly(detail.invalidated);
1793
+ else if (detail.tools.some((t) => t.side === "server")) refetchEverything();
1794
+ ```
1795
+
1796
+ | Server | Client | Result |
1797
+ | --- | --- | --- |
1798
+ | old | old | coarse refetch, as today |
1799
+ | new | old | the `CUSTOM` event is ignored; coarse refetch still fires |
1800
+ | old | new | `invalidated` is empty; the `else` branch runs |
1801
+ | new | new | precise, and live during the run |
1802
+
1803
+ Nothing negotiates and nothing handshakes, which is what makes this shippable
1804
+ across repos with independent release cadences.
1805
+
1806
+ **Note:** it reaches **the page that started the run**, during the run. There is one
1807
+ response stream per run and no channel to anybody else's browser.
1808
+
1470
1809
  ## Resuming a run
1471
1810
 
1472
1811
  When the server persists run checkpoints (django-ag-ui's `step_store`), a run
@@ -1643,6 +1982,18 @@ re-export point. Internal modules import from leaf paths.
1643
1982
  | `PageState` | type | A page-state binding declaration. |
1644
1983
  | `Skill` | type | A launchable prompt (chip / `/`-command). |
1645
1984
  | `RunFinishedDetail` / `ToolRun` | type | `ag-ui-run-finished` detail: the tools an interaction ran, and which side ran them. |
1985
+ | `CustomAgentDetail` | type | `ag-ui-custom` detail: an AG-UI `CUSTOM` event's `name` and `value`, verbatim. |
1986
+ | `InvalidateDetail` | type | `ag-ui-invalidate` detail: the resource `keys` that moved, and the `reason`. |
1987
+ | `FeedbackDetail` | type | `ag-ui-feedback` detail: the rated message's `content` and the `rating`. |
1988
+ | `relativeTime` | function | The built-in locale-neutral timestamp formatter (`"5m ago"`), for a host building on it. |
1989
+ | `RelativeTimeFormatter` | type | What `formatRelativeTime` takes: an epoch-ms timestamp in, row text out. |
1990
+ | `renderSuggestionChips` | function | Draw a `suggestions` activity as chips that send themselves; `null` when nothing survives. |
1991
+ | `suggestionPrompts` | function | The usable prompts in a `suggestions` activity's content, bounded and trimmed. |
1992
+ | `attachMessageActions` | function | Give a finished bubble its action row (copy, and feedback when a handler is passed). |
1993
+ | `messageActionBar` | function | The empty action row for a bubble, created if it has none — the shared shell both callers use. |
1994
+ | `MessageActionsOptions` | type | What `attachMessageActions` takes: `strings`, a `text` source, an optional `onFeedback`. |
1995
+ | `ActivityRenderer` | type | Draws one activity from its `content`. Pure: it runs again on every restore. |
1996
+ | `ActivityRegistration` | type | What `registerActivityRenderer` takes: `type`, `render`, and an optional `removedNotice`. |
1646
1997
  | `createStateHookTools(binding)` / `StateHook` | deprecated | The former names for `createPageStateTools` / `PageState`. |
1647
1998
 
1648
1999
  ### Durability
@@ -1702,6 +2053,12 @@ re-export point. Internal modules import from leaf paths.
1702
2053
  | `renderChart(spec)` | function | Draw one spec as a self-contained block, or `null` when it says nothing. |
1703
2054
  | `chartSpecFrom(value)` | function | Read an arbitrary payload into a `ChartSpec`, or `null` if it cannot be drawn honestly. |
1704
2055
  | `ChartSpec` / `ChartSeries` / `ChartKind` | type | A chart as data, one named series, and how it is drawn. |
2056
+ | `attachQuoteOffer(options)` | function | The page-side select-then-quote offer, with its guards. `AgUiChat.offerQuoteInPage()` is the one-line form. |
2057
+ | `PageQuoteOffer` / `PageQuoteOfferOptions` | type | The live offer (`{ element, detach }`) and what it takes. |
2058
+ | `quotableSelection(container, roots)` | function | The current selection when it lies inside `container`, read through the shadow-aware API where the engine has one. |
2059
+ | `QuotableSelection` | type | `{ text, rect }` — what was selected, and where it sits. |
2060
+ | `asQuote(text)` | function | Shape text as a markdown blockquote with a blank line after it. |
2061
+ | `MAX_QUOTE_CHARS` | const | The cap a quotation is truncated to (500). |
1705
2062
  | `typeInto` / `highlightThenClick` / `pressThenClick` / `selectOption` / `toggleControl` / `scrollIntoCenterView` / `flash` / `focusWithFlash` / `prefersReducedMotion` | function | Animation primitives. |
1706
2063
  | `fillField` / `clickElement` / `pressButton` / `selectControl` / `setControlValue` / `toggleCheckbox` | function | DOM-driver primitives. |
1707
2064
  | `setNativeValue` / `setNativeChecked` | function | Set a control via its native prototype setter (React-controlled inputs). |
@@ -1716,6 +2073,13 @@ re-export point. Internal modules import from leaf paths.
1716
2073
  | `TOGGLE_EVENT` | The collapse-toggle CustomEvent name (`ag-ui-toggle`). |
1717
2074
  | `UNREAD_EVENT` | The unread-count CustomEvent name (`ag-ui-unread`). |
1718
2075
  | `RUN_FINISHED_EVENT` | The interaction-finished CustomEvent name (`ag-ui-run-finished`). |
2076
+ | `CUSTOM_AGENT_EVENT` | The agent-`CUSTOM` CustomEvent name (`ag-ui-custom`). |
2077
+ | `INVALIDATE_EVENT` | The resource-invalidation CustomEvent name (`ag-ui-invalidate`). |
2078
+ | `FEEDBACK_EVENT` | The message-rating CustomEvent name (`ag-ui-feedback`). |
2079
+ | `SUGGESTIONS_ACTIVITY_TYPE` | The `activity_type` carrying follow-up prompts (`suggestions`). |
2080
+ | `MAX_SUGGESTIONS` | Most prompts one push draws (4). Mirrors the server's cap. |
2081
+ | `MAX_SUGGESTION_CHARS` | Longest one prompt may be (120). Mirrors the server's cap. |
2082
+ | `INVALIDATE_CUSTOM_NAME` | The AG-UI `CUSTOM` `name` that carries one (`ag_ui.invalidate`). |
1719
2083
  | `ATTACHMENT_EVENT` | The attachments-changed CustomEvent name (`ag-ui-attachments`). |
1720
2084
  | `STATE_EVENT` | The shared-state CustomEvent name (`ag-ui-state`). |
1721
2085
  | `CHART_ACTIVITY_TYPE` | The `ACTIVITY_SNAPSHOT` type a server sets to push a chart. |
@@ -1855,15 +2219,17 @@ component sets, so a new one cannot ship undocumented.
1855
2219
 
1856
2220
  | Feature | Parts |
1857
2221
  | --- | --- |
1858
- | Shell | `panel`, `header`, `title`, `icon`, `header-controls`, `messages`, `empty`, `pending`, `stopped`, `resize-handle` |
2222
+ | Shell | `panel`, `header`, `title`, `icon`, `header-controls`, `messages`, `empty`, `pending`, `stopped`, `jump-latest`, `resize-handle` |
1859
2223
  | Header buttons | `header-button` on each, plus `history-button`, `checkpoints-button`, `new-button`, `collapse-button`, `theme-toggle` |
1860
2224
  | Collapsed widget | `launcher`, `launcher-icon`, `launcher-badge` |
1861
2225
  | Answers | `answer` (the per-turn group), `message` (plus `message-user`, `message-assistant`), `code-copy` |
1862
2226
  | Reasoning | `thoughts`, `thoughts-toggle`, `thoughts-body`, `thoughts-label` |
1863
- | Run notices | `run-notice` (plus `run-notice-interrupted`, `run-notice-attachment-pending`, `run-notice-compaction`, `run-notice-skill`), `run-notice-icon`, `run-notice-text` |
2227
+ | Follow-up suggestions | `suggestions`, `suggestion-chip` |
2228
+ | Message actions | `message-actions`, `message-action` (plus `message-action-retry`, `message-action-copy`, `message-action-up`, `message-action-down`) |
2229
+ | Run notices | `run-notice` (plus `run-notice-interrupted`, `run-notice-attachment-pending`, `run-notice-compaction`, `run-notice-skill`, `run-notice-history-replaced`, `run-notice-chart-undrawable`), `run-notice-icon`, `run-notice-text` |
1864
2230
  | Tool cards | `tool-card`, `tool-card-head`, `tool-card-icon`, `tool-card-name`, `tool-card-status`, `tool-card-decision`, `tool-card-toggle`, `tool-card-body`, `tool-card-section` (plus `tool-card-args-section`, `tool-card-result-section`), `tool-card-section-label` (plus `tool-card-args-label`, `tool-card-result-label`), `tool-card-args`, `tool-card-result`, `tool-card-approval` |
1865
- | Client-side confirmation | `confirm`, `confirm-body`, `confirm-args`, `confirm-actions`, `confirm-button` (plus `confirm-confirm`, `confirm-cancel`) |
1866
- | Server-side approval | `approval`, `approval-body`, `approval-actions`, `approval-button` (plus `approval-approve`, `approval-deny`) |
2231
+ | Client-side confirmation | `confirm`, `confirm-body`, `confirm-args`, `confirm-actions`, `confirm-button` (plus `confirm-confirm`, `confirm-cancel`, `confirm-always`) |
2232
+ | Server-side approval | `approval`, `approval-body`, `approval-actions`, `approval-button` (plus `approval-approve`, `approval-deny`), `approval-edit`, `approval-args`, `approval-error` |
1867
2233
  | Typed question | `question`, `question-body`, `question-options`, `question-choice`, `question-choice-text`, `question-radio`, `question-input`, `question-actions`, `question-button` |
1868
2234
  | Composer | `composer`, `composer-surface`, `composer-tools`, `input`, `send`, `attach-button`, `voice-button` |
1869
2235
  | Attachments | `attachment-tray`, `attachment-chips` (the read-only chips on sent bubbles), and the shared chip parts `attachment-chip`, `attachment-chip-icon`, `attachment-chip-name`, `attachment-chip-size`, `attachment-chip-bar`, `attachment-chip-bar-fill`, `attachment-chip-retry`, `attachment-chip-remove` |