@artooi/ag-ui-web-component 0.1.1 → 0.2.1

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 (109) hide show
  1. package/CHANGELOG.md +99 -1
  2. package/README.md +242 -50
  3. package/dist/ag-ui-web-component.bundle.js +451 -46
  4. package/dist/ag-ui-web-component.bundle.js.map +4 -4
  5. package/dist/constants.d.ts +34 -0
  6. package/dist/constants.d.ts.map +1 -1
  7. package/dist/{ag_ui_chat.d.ts → core/ag_ui_chat.d.ts} +63 -6
  8. package/dist/core/ag_ui_chat.d.ts.map +1 -0
  9. package/dist/{agui_client.d.ts → core/agui_client.d.ts} +13 -0
  10. package/dist/core/agui_client.d.ts.map +1 -0
  11. package/dist/core/conversation_store.d.ts.map +1 -0
  12. package/dist/core/create_http_agent.d.ts.map +1 -0
  13. package/dist/core/define_ag_ui_chat.d.ts.map +1 -0
  14. package/dist/dom/animations.d.ts +71 -0
  15. package/dist/dom/animations.d.ts.map +1 -0
  16. package/dist/{dom_driver.d.ts → dom/dom_driver.d.ts} +7 -1
  17. package/dist/dom/dom_driver.d.ts.map +1 -0
  18. package/dist/dom/native_setter.d.ts +5 -0
  19. package/dist/dom/native_setter.d.ts.map +1 -0
  20. package/dist/index.d.ts +19 -16
  21. package/dist/index.d.ts.map +1 -1
  22. package/dist/index.js +3934 -373
  23. package/dist/index.js.map +4 -4
  24. package/dist/skills/fill_template.d.ts +17 -0
  25. package/dist/skills/fill_template.d.ts.map +1 -0
  26. package/dist/skills/parse_skills.d.ts +8 -0
  27. package/dist/skills/parse_skills.d.ts.map +1 -0
  28. package/dist/skills/skill.d.ts +24 -0
  29. package/dist/skills/skill.d.ts.map +1 -0
  30. package/dist/{client_tool_registry.d.ts → tools/client_tool_registry.d.ts} +6 -1
  31. package/dist/tools/client_tool_registry.d.ts.map +1 -0
  32. package/dist/tools/is_destructive.d.ts.map +1 -0
  33. package/dist/tools/is_navigates.d.ts.map +1 -0
  34. package/dist/tools/page_map.d.ts.map +1 -0
  35. package/dist/tools/route_map.d.ts +40 -0
  36. package/dist/tools/route_map.d.ts.map +1 -0
  37. package/dist/tools/state_hook.d.ts.map +1 -0
  38. package/dist/ui/confirmation_card.d.ts +22 -0
  39. package/dist/ui/confirmation_card.d.ts.map +1 -0
  40. package/dist/ui/render_markdown.d.ts +13 -0
  41. package/dist/ui/render_markdown.d.ts.map +1 -0
  42. package/dist/ui/reveal_words.d.ts +8 -0
  43. package/dist/ui/reveal_words.d.ts.map +1 -0
  44. package/dist/ui/skills_menu.d.ts +35 -0
  45. package/dist/ui/skills_menu.d.ts.map +1 -0
  46. package/dist/ui/styles.d.ts +2 -0
  47. package/dist/ui/styles.d.ts.map +1 -0
  48. package/dist/ui/tool_call_card.d.ts +35 -0
  49. package/dist/ui/tool_call_card.d.ts.map +1 -0
  50. package/package.json +5 -3
  51. package/src/constants.ts +38 -0
  52. package/src/core/ag_ui_chat.ts +851 -0
  53. package/src/{agui_client.ts → core/agui_client.ts} +19 -1
  54. package/src/{define_ag_ui_chat.ts → core/define_ag_ui_chat.ts} +1 -1
  55. package/src/dom/animations.ts +201 -0
  56. package/src/{dom_driver.ts → dom/dom_driver.ts} +35 -2
  57. package/src/dom/native_setter.ts +42 -0
  58. package/src/index.ts +61 -32
  59. package/src/skills/fill_template.ts +32 -0
  60. package/src/skills/parse_skills.ts +26 -0
  61. package/src/skills/skill.ts +23 -0
  62. package/src/{client_tool_registry.ts → tools/client_tool_registry.ts} +6 -4
  63. package/src/{is_destructive.ts → tools/is_destructive.ts} +1 -1
  64. package/src/{is_navigates.ts → tools/is_navigates.ts} +1 -1
  65. package/src/tools/route_map.ts +140 -0
  66. package/src/{state_hook.ts → tools/state_hook.ts} +1 -1
  67. package/src/ui/confirmation_card.ts +71 -0
  68. package/src/ui/render_markdown.ts +68 -0
  69. package/src/ui/reveal_words.ts +41 -0
  70. package/src/ui/skills_menu.ts +171 -0
  71. package/src/ui/styles.ts +631 -0
  72. package/src/{tool_call_card.ts → ui/tool_call_card.ts} +50 -19
  73. package/src/version.ts +1 -1
  74. package/dist/ag_ui_chat.d.ts.map +0 -1
  75. package/dist/agui_client.d.ts.map +0 -1
  76. package/dist/animations.d.ts +0 -33
  77. package/dist/animations.d.ts.map +0 -1
  78. package/dist/client_tool_registry.d.ts.map +0 -1
  79. package/dist/confirmation_modal.d.ts +0 -14
  80. package/dist/confirmation_modal.d.ts.map +0 -1
  81. package/dist/conversation_store.d.ts.map +0 -1
  82. package/dist/create_http_agent.d.ts.map +0 -1
  83. package/dist/define_ag_ui_chat.d.ts.map +0 -1
  84. package/dist/dom_driver.d.ts.map +0 -1
  85. package/dist/is_destructive.d.ts.map +0 -1
  86. package/dist/is_navigates.d.ts.map +0 -1
  87. package/dist/page_map.d.ts.map +0 -1
  88. package/dist/route_map.d.ts +0 -27
  89. package/dist/route_map.d.ts.map +0 -1
  90. package/dist/state_hook.d.ts.map +0 -1
  91. package/dist/styles.d.ts +0 -2
  92. package/dist/styles.d.ts.map +0 -1
  93. package/dist/tool_call_card.d.ts +0 -29
  94. package/dist/tool_call_card.d.ts.map +0 -1
  95. package/src/ag_ui_chat.ts +0 -411
  96. package/src/animations.ts +0 -86
  97. package/src/confirmation_modal.ts +0 -69
  98. package/src/route_map.ts +0 -83
  99. package/src/styles.ts +0 -296
  100. /package/dist/{conversation_store.d.ts → core/conversation_store.d.ts} +0 -0
  101. /package/dist/{create_http_agent.d.ts → core/create_http_agent.d.ts} +0 -0
  102. /package/dist/{define_ag_ui_chat.d.ts → core/define_ag_ui_chat.d.ts} +0 -0
  103. /package/dist/{is_destructive.d.ts → tools/is_destructive.d.ts} +0 -0
  104. /package/dist/{is_navigates.d.ts → tools/is_navigates.d.ts} +0 -0
  105. /package/dist/{page_map.d.ts → tools/page_map.d.ts} +0 -0
  106. /package/dist/{state_hook.d.ts → tools/state_hook.d.ts} +0 -0
  107. /package/src/{conversation_store.ts → core/conversation_store.ts} +0 -0
  108. /package/src/{create_http_agent.ts → core/create_http_agent.ts} +0 -0
  109. /package/src/{page_map.ts → tools/page_map.ts} +0 -0
package/CHANGELOG.md CHANGED
@@ -7,6 +7,102 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.2.1] — 2026-06-02
11
+
12
+ ### Added
13
+ - **Server-side tool results in the card.** The element now subscribes to
14
+ AG-UI's `TOOL_CALL_RESULT` event and settles the matching tool-call card with
15
+ the real server output (honouring the `data-tool-display` mode), instead of
16
+ the generic "Executed on the server." placeholder — which remains only as a
17
+ fallback when no result event is streamed.
18
+ - **Tool calls and results survive a page refresh.** History replay now
19
+ reconstructs tool-call cards (from assistant `toolCalls`) and settles them
20
+ from the persisted `tool` result messages, so a rehydrated transcript shows
21
+ the full tool activity, not just the prose. Applies to every conversation
22
+ store (the data was already persisted; only the replay was incomplete).
23
+
24
+ ### Fixed
25
+ - **Pending indicator could hang after a server-only round.** A round whose
26
+ tool calls were all server-side re-showed the "thinking" indicator after the
27
+ run had already finished, leaving it stuck. The indicator is no longer shown
28
+ speculatively for server tools, and a terminal `onSettled` guarantee clears
29
+ it (and re-enables input) on every run-loop exit — including the
30
+ `MAX_TOOL_ROUNDS` ceiling and errors.
31
+
32
+ ## [0.2.0] — 2026-06-02
33
+
34
+ ### Added
35
+ - **Markdown + HTML rendering** in assistant message bubbles (`renderMarkdown`),
36
+ sanitised with DOMPurify (scripts, event handlers, and `javascript:` URLs
37
+ stripped; links hardened with `target`/`rel`). User messages stay literal.
38
+ - **Pending indicator** — an animated "thinking" indicator shown while the agent
39
+ is awaited (before the first token and between tool rounds), honouring
40
+ `prefers-reduced-motion`.
41
+ - **New-chat button** in the header — clears the transcript, the persisted
42
+ conversation, and the in-memory run state, and mints a fresh thread.
43
+ - **Collapse seam** — a reflected `collapsed` attribute, a built-in header
44
+ toggle, a persisted (per-tab) collapsed state, and a `TOGGLE_EVENT`
45
+ (`ag-ui-toggle`) so a host can drive its own chrome.
46
+ - **Tool-call display modes** (`TOOL_DISPLAY`, `data-tool-display`): `minimal`
47
+ (name + status), `compact` (args + result behind one "Details" toggle), and
48
+ `full` (the default; original behaviour).
49
+ - **Richer action animations**: `pressThenClick`, `selectOption`,
50
+ `toggleControl` (+ `pressButton` / `selectControl` / `toggleCheckbox` driver
51
+ wrappers), all honouring `prefers-reduced-motion`.
52
+ - **Dynamic route syntax**: `Route.path` supports `:name` placeholders;
53
+ `navigate_to_route` substitutes path params (leftover params → query string)
54
+ and `list_routes` advertises each route's `pathParams`.
55
+ - `X_CONFIRM_KEY` (`x-confirm`) tool metadata for a human-readable confirmation
56
+ prompt.
57
+ - `setNativeValue` / `setNativeChecked` utilities (also used internally).
58
+ - **Theming** — a `theme` attribute (`light` / `dark` / `auto` / `code`); `auto`
59
+ follows `prefers-color-scheme`, `code` is a monospace terminal palette. Plus a
60
+ wider set of themeable `--ag-ui-*` variables.
61
+ - **Density + placement presets** — `density` (`comfortable` / `compact`) and
62
+ `placement` (`bottom-left` / `side` / `full` / `embedded`); `embedded` drops
63
+ the floating chrome and high z-index so the widget lives in the host layout.
64
+ - **Incoming-text animations** — `data-text-animation` (`none` / `fade` /
65
+ `word`), the last revealing assistant text word-by-word; honours
66
+ `prefers-reduced-motion`.
67
+ - **`confirmPredicate`** — a per-call `(toolName, args) => boolean | Promise`
68
+ hook deciding confirmation dynamically (authoritative over `x-destructive`).
69
+ - **Built-in `read_page` tool** — present when a `getPageMap` provider is set, so
70
+ the agent can re-read the page mid-turn after acting.
71
+ - **`x-summary`** tool metadata (`X_SUMMARY_KEY`) — a friendly label shown on the
72
+ tool-call card instead of the raw tool name.
73
+ - `observedAttributes` / `attributeChangedCallback` so a late `title-text`
74
+ change updates the header.
75
+ - Accessibility: `role="log"`/`aria-live` on the transcript, `role="status"` on
76
+ the pending indicator, `role="group"` on the confirmation card, input label.
77
+ - **Skills** — pre-defined prompts surfaced as **chips** (`data-prompt-chips`)
78
+ and/or a **`/`-command palette** (`data-slash-commands`), both opt-in over one
79
+ catalog. Catalog from the `skills` setter (`setSkills`), a `data-skills` JSON
80
+ embed, and/or a fetched `data-skills-url` (merged backend → embed → client).
81
+ Picking pre-fills the input (or auto-sends with `sendImmediately`); prompts
82
+ support `{placeholder}`s filled from `skillContext`, with a missing value
83
+ blocking the send and showing a hint. Exports the `Skill` type.
84
+
85
+ ### Changed
86
+ - The destructive-action confirmation is now an **inline card in the transcript**
87
+ (Confirm / Cancel, with the `x-confirm` message) instead of a focus-stealing
88
+ modal overlay. `requestConfirmation` now renders inline; the
89
+ `confirmation_modal` module was removed.
90
+ - **Framework-controlled inputs now work.** `fillField` / `typeInto` /
91
+ `selectOption` / `toggleControl` / `setControlValue` set `value` / `checked`
92
+ through the **native prototype setter** before dispatching `input`, so
93
+ React/Vue/Svelte value-tracking sees the change (previously the field looked
94
+ filled but host state stayed empty).
95
+ - **Framework interop:** reflecting **property setters** for `endpoint`,
96
+ `toolDisplay`, and `collapsed` (React 19 assigns matching props as element
97
+ properties — getter-only props previously threw).
98
+ - `registerTool` is now **idempotent** — re-registering a tool name replaces it
99
+ instead of throwing (re-fired refs / React StrictMode).
100
+
101
+ ### Fixed
102
+ - The Markdown/HTML allowlist now permits sanitised **`<img>`** (safe-scheme
103
+ `src`, no event handlers); `javascript:` srcs and disallowed tags (e.g.
104
+ `iframe`) are still stripped.
105
+
10
106
  ## [0.1.1] — 2026-06-01
11
107
 
12
108
  ### Changed
@@ -33,6 +129,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
33
129
  ### Notes
34
130
  - First release — exercising the automated npm OIDC publish pipeline end-to-end.
35
131
 
36
- [Unreleased]: https://github.com/Artui/ag-ui-web-component/compare/v0.1.1...HEAD
132
+ [Unreleased]: https://github.com/Artui/ag-ui-web-component/compare/v0.2.1...HEAD
133
+ [0.2.1]: https://github.com/Artui/ag-ui-web-component/compare/v0.2.0...v0.2.1
134
+ [0.2.0]: https://github.com/Artui/ag-ui-web-component/compare/v0.1.1...v0.2.0
37
135
  [0.1.1]: https://github.com/Artui/ag-ui-web-component/compare/v0.1.0...v0.1.1
38
136
  [0.1.0]: https://github.com/Artui/ag-ui-web-component/releases/tag/v0.1.0
package/README.md CHANGED
@@ -17,8 +17,13 @@ It wraps [`@ag-ui/client`](https://www.npmjs.com/package/@ag-ui/client)'s `HttpA
17
17
  - Generic **DOM-driver primitives** (`fillField`, `clickElement`, `setControlValue`) and
18
18
  **animation primitives** (`typeInto`, `highlightThenClick`, …) so the agent can drive the page
19
19
  at human-readable speed.
20
- - A **confirmation modal** that intercepts destructive tool calls (those whose JSON Schema
21
- carries `x-destructive: true`) before the handler runs.
20
+ - An **inline confirmation card** that intercepts tool calls needing confirmation (those whose
21
+ JSON Schema carries `x-destructive: true`, or a per-call `confirmPredicate`) before the handler
22
+ runs — rendered right in the transcript, never a modal overlay.
23
+ - **Markdown + HTML rendering** of assistant replies (sanitized `marked` + DOMPurify), with
24
+ themes, density/placement presets, incoming-text animations, tool-call display modes, an
25
+ animated thinking indicator, and an opt-in **skills** palette (prompt chips + `/`-commands).
26
+ - A **new-chat** button and a **collapse** toggle built into the header.
22
27
  - An **MPA durability story**: a durable conversation store, a stable thread id, and a resumable
23
28
  run loop that survives full page reloads (`x-navigates` + `navigationResult`).
24
29
  - **Host seams** for SPAs: a route map, an auto-injected page map, state hooks, and an optional
@@ -36,12 +41,16 @@ No framework, no Django, no admin specifics live here. Downstream consumers (e.g
36
41
  - [Core concepts](#core-concepts)
37
42
  - [The run loop and the AG-UI client](#the-run-loop-and-the-ag-ui-client)
38
43
  - [Registering tools](#registering-tools)
39
- - [The confirmation modal (`x-destructive`)](#the-confirmation-modal-x-destructive)
44
+ - [Inline confirmation (`x-destructive` / `x-confirm` / `confirmPredicate`)](#inline-confirmation-x-destructive--x-confirm--confirmpredicate)
40
45
  - [DOM-driver and animation primitives](#dom-driver-and-animation-primitives)
46
+ - [New chat and collapse](#new-chat-and-collapse)
47
+ - [Tool-call display modes](#tool-call-display-modes)
48
+ - [Markdown rendering](#markdown-rendering)
49
+ - [Skills: prompt chips and slash palette](#skills-prompt-chips-and-slash-palette)
41
50
  - [MPA durability: surviving full page reloads](#mpa-durability-surviving-full-page-reloads)
42
51
  - [Host seams: the SPA story](#host-seams-the-spa-story)
43
52
  - [Public API surface](#public-api-surface)
44
- - [Theming](#theming)
53
+ - [Theming, density, and placement](#theming-density-and-placement)
45
54
  - [Building the bundle](#building-the-bundle)
46
55
  - [Compatibility](#compatibility)
47
56
 
@@ -110,7 +119,7 @@ Drop the element into your page and register the tools the agent may call:
110
119
  });
111
120
 
112
121
  // A destructive tool: x-destructive at the JSON-Schema root gates it behind
113
- // the confirmation modal before the handler runs.
122
+ // the inline confirmation card before the handler runs.
114
123
  chat.registerTool({
115
124
  name: "save_article",
116
125
  description: "Save the article. Destructive — asks for confirmation.",
@@ -130,14 +139,32 @@ That's the whole integration: an `endpoint` attribute pointing at your AG-UI ser
130
139
 
131
140
  ### Attributes and properties
132
141
 
142
+ **Attributes** (set in HTML; the CSS-only ones are styling presets with no JS API):
143
+
133
144
  | Attribute | Property | Notes |
134
145
  | --- | --- | --- |
135
- | `endpoint` | `endpoint` (getter) | The AG-UI endpoint URL. Required to send. |
136
- | `title-text` | — | Header label; defaults to `"Assistant"`. |
137
- | | `headers` | `Record<string, string>` of extra HTTP headers. |
138
- | | `autoConfirm` | When `true`, destructive tools run without the modal. |
139
-
140
- A self-contained working example lives in [`demo/`](demo/) run `make demo` to serve it against a
146
+ | `endpoint` | `endpoint` | The AG-UI endpoint URL. Required to send. Reflecting getter + setter. |
147
+ | `title-text` | — | Header label; defaults to `"Assistant"`. The only **observed** attribute (live-updates the header). |
148
+ | `data-tool-display` | `toolDisplay` | Tool-call card detail: `minimal` / `compact` / `full` (default `full`). |
149
+ | `data-text-animation` | | Incoming-text reveal: `none` (default) / `fade` / `word`. |
150
+ | `data-prompt-chips` | — | `"true"` to surface skills as chips. |
151
+ | `data-slash-commands` || `"true"` to enable the `/`-command palette. |
152
+ | `data-skills` | — | Inline JSON skill catalog. |
153
+ | `data-skills-url` | — | URL of a JSON skill catalog (fetched with `headers`). |
154
+ | `collapsed` | `collapsed` | Reflected boolean; collapses the widget. Persisted per-tab in `sessionStorage`. |
155
+ | `theme` | — | CSS-only: `light` (default) / `dark` / `auto` / `code`. |
156
+ | `density` | — | CSS-only: `comfortable` (default) / `compact`. |
157
+ | `placement` | — | CSS-only: `floating` (default) / `bottom-left` / `side` / `full` / `embedded`. |
158
+
159
+ **Properties** (JS only, not attributes): `headers`, `autoConfirm`, `confirmPredicate`,
160
+ `agentFactory`, `getTools`, `getContext`, `routeMap`, `navigate`, `getPageMap`,
161
+ `autoInjectPageMap`, `conversationStore`, `navigationResult`, `skillContext`, plus the mirrors
162
+ `endpoint` / `toolDisplay` / `collapsed`.
163
+
164
+ **Methods**: `registerTool`, `registerStateHook`, `setSkills`, `appendMessage`, `newChat`,
165
+ `setCollapsed`, `toggleCollapsed`.
166
+
167
+ A self-contained live playground lives in [`demo/`](demo/) — run `make demo` to serve it against a
141
168
  mock AG-UI server.
142
169
 
143
170
  ---
@@ -146,9 +173,9 @@ mock AG-UI server.
146
173
 
147
174
  ### The run loop and the AG-UI client
148
175
 
149
- `<ag-ui-chat>` is the view; [`AgUiClient`](src/agui_client.ts) is the orchestration layer over an
150
- AG-UI `AbstractAgent`. On the first send the element builds a client (via the overridable
151
- `agentFactory`, which defaults to [`createHttpAgent`](src/create_http_agent.ts)). Each turn:
176
+ `<ag-ui-chat>` is the view; [`AgUiClient`](src/core/agui_client.ts) is the orchestration layer over
177
+ an AG-UI `AbstractAgent`. On the first send the element builds a client (via the overridable
178
+ `agentFactory`, which defaults to [`createHttpAgent`](src/core/create_http_agent.ts)). Each turn:
152
179
 
153
180
  1. The user message is appended and the agent runs once.
154
181
  2. AG-UI subscriber events are translated into the element's handlers — streaming text deltas
@@ -158,8 +185,10 @@ AG-UI `AbstractAgent`. On the first send the element builds a client (via the ov
158
185
  4. This repeats until the agent stops calling frontend tools, bounded by `MAX_TOOL_ROUNDS`.
159
186
 
160
187
  Tool calls the client doesn't own (server-side tools the server already executed) are left alone —
161
- the loop doesn't re-run them. The current tool catalog and context are read **fresh on every run**
162
- (`getTools()` / `getContext()`), so they always reflect the current page state.
188
+ the loop doesn't re-run them, but their streamed `TOOL_CALL_RESULT` is rendered into the tool-call
189
+ card (honouring `data-tool-display`), so server-side output is visible too. The current tool catalog
190
+ and context are read **fresh on every run** (`getTools()` / `getContext()`), so they always reflect
191
+ the current page state.
163
192
 
164
193
  ### Registering tools
165
194
 
@@ -184,48 +213,181 @@ Names must be unique (registering a duplicate throws). Each `<ag-ui-chat>` eleme
184
213
  registry, AG-UI client, and Shadow DOM, so **multiple instances on one page never interfere** —
185
214
  there is no module-level shared state anywhere in the package.
186
215
 
187
- ### The confirmation modal (`x-destructive`)
216
+ ### Inline confirmation (`x-destructive` / `x-confirm` / `confirmPredicate`)
217
+
218
+ When a tool call needs confirmation, the element appends an **inline confirmation card** (a
219
+ `<div class="confirm">`) to the transcript via
220
+ [`requestConfirmation`](src/ui/confirmation_card.ts) — it is not a modal overlay. The card reads
221
+ naturally after the assistant's explanation, never steals focus from the page, and stays in the
222
+ transcript as a resolved record after the decision:
223
+
224
+ - **Confirm** → the handler runs and the result is posted back.
225
+ - **Cancel** → a `"User declined the action."` result is posted; the agent acknowledges on its
226
+ next turn.
227
+
228
+ Whether a call is gated is decided in this order:
229
+
230
+ 1. If `chat.autoConfirm === true`, the call **never** prompts (an "autopilot" toggle).
231
+ 2. Else if `chat.confirmPredicate` is set, its boolean return is authoritative — given the tool
232
+ name + parsed args it decides per-call (so one tool can be instant for some args and confirmed
233
+ for others, which a static flag can't express).
234
+ 3. Else the element falls back to [`isDestructive(parameters)`](src/tools/is_destructive.ts),
235
+ which reads the `x-destructive` JSON-Schema flag.
188
236
 
189
237
  AG-UI has no built-in risk flag, so destructiveness is carried as a JSON-Schema extension at the
190
238
  **schema root**: `parameters["x-destructive"] = true` (use the exported `X_DESTRUCTIVE_KEY`
191
- constant). There is no parallel metadata channel the flag lives on the schema, the registry
192
- forwards it verbatim to `RunAgentInput.tools`, and [`isDestructive`](src/is_destructive.ts) reads
193
- it back.
239
+ constant). There is no parallel metadata channel and no name heuristic destructiveness is exactly
240
+ the `x-destructive` flag (or `confirmPredicate`). The registry forwards the flag verbatim to
241
+ `RunAgentInput.tools`.
194
242
 
195
- When the agent calls a destructive tool, the element shows the
196
- [confirmation modal](src/confirmation_modal.ts) (rendered inside its own Shadow DOM) **before**
197
- dispatching to the handler:
243
+ If the schema carries an `x-confirm` string (use `X_CONFIRM_KEY`), the card shows it as the prompt;
244
+ otherwise it falls back to a generic `Run "<tool>"?`.
198
245
 
199
- - **Confirm** → the handler runs and the result is posted back.
200
- - **Cancel / dismiss** → a `"User declined the action."` result is posted; the agent acknowledges
201
- on its next turn.
246
+ ```js
247
+ // Per-call: confirm a delete only when it would remove more than one row.
248
+ chat.confirmPredicate = (name, args) =>
249
+ name === "delete_rows" && Array.isArray(args.ids) && args.ids.length > 1;
202
250
 
203
- Set `chat.autoConfirm = true` to bypass the modal (an "autopilot" toggle).
251
+ // Prompt text via x-confirm:
252
+ chat.registerTool({
253
+ name: "activate_project",
254
+ description: "Activate the current project.",
255
+ parameters: { type: "object", properties: {}, [X_DESTRUCTIVE_KEY]: true, [X_CONFIRM_KEY]: "Activate this project?" },
256
+ handler: async () => await api.activate(),
257
+ });
258
+ ```
204
259
 
205
260
  ### DOM-driver and animation primitives
206
261
 
207
262
  So the agent can visibly drive the host page, the package ships generic, framework-free
208
- primitives. The **animation** primitives ([`animations.ts`](src/animations.ts)) operate at
263
+ primitives. The **animation** primitives ([`animations.ts`](src/dom/animations.ts)) operate at
209
264
  human-readable speed (configurable; pass small/zero durations in tests):
210
265
 
211
266
  - `typeInto(el, value, { charDelayMs })` — clears and types a value character by character,
212
267
  firing `input`/`change` events as a real user would.
213
- - `highlightThenClick(el, { highlightMs })` outlines an element, pauses, then clicks.
268
+ - `highlightThenClick(el, { highlightMs })` / `pressThenClick(el, options)` outline/press an
269
+ element, pause, then click.
270
+ - `selectOption(el, value)` / `toggleControl(el, checked)` — animate a `<select>` / checkbox.
214
271
  - `scrollIntoCenterView(el)` / `focusWithFlash(el, { flashMs })`.
272
+ - `prefersReducedMotion()` — honoured throughout so animations collapse to instant when the user
273
+ asks for reduced motion.
215
274
 
216
- The **DOM-driver** primitives ([`dom_driver.ts`](src/dom_driver.ts)) compose those into the
275
+ The **DOM-driver** primitives ([`dom_driver.ts`](src/dom/dom_driver.ts)) compose those into the
217
276
  operations a tool handler typically wants:
218
277
 
219
278
  - `fillField(el, value, options)` — scroll to, focus-flash, and type into a text field.
220
- - `clickElement(el, options)` — scroll to, highlight, and click.
279
+ - `clickElement(el, options)` / `pressButton(el, options)` — scroll to, highlight/press, and click.
280
+ - `selectControl(el, value)` / `toggleCheckbox(el, checked)` — animate a `<select>` / checkbox.
221
281
  - `setControlValue(el, value)` — set a `<select>` or checkbox without animation, dispatching
222
282
  `input`/`change`.
223
283
 
284
+ The native-setter helpers ([`native_setter.ts`](src/dom/native_setter.ts)) — `setNativeValue` /
285
+ `setNativeChecked` — set a control through its native prototype setter so React-controlled inputs
286
+ register the change.
287
+
224
288
  Each takes an element the caller has already located; host packages wrap them with
225
289
  environment-aware lookups (e.g. "find `#id_<name>`, then `fillField`").
226
290
 
227
291
  ---
228
292
 
293
+ ## New chat and collapse
294
+
295
+ The header carries two built-in buttons: a new-chat (✚) button and a collapse (—) toggle. The
296
+ matching JS API:
297
+
298
+ - `newChat()` — clears the transcript and the persisted history, drops the in-memory run state,
299
+ and mints a new thread id.
300
+ - `setCollapsed(collapsed)` / `toggleCollapsed()` — collapse or expand the widget. The state is
301
+ reflected as the boolean `collapsed` attribute/property and persisted per-tab in
302
+ `sessionStorage`, so it survives a reload.
303
+
304
+ Each change emits an `ag-ui-toggle` event (the `TOGGLE_EVENT` constant) with
305
+ `detail: { collapsed: boolean }` (typed `ToggleDetail`), so a host can mirror the state in its own
306
+ chrome — or hide the built-in toggle and drive the `collapsed` attribute itself.
307
+
308
+ ```js
309
+ chat.newChat();
310
+ chat.toggleCollapsed();
311
+ chat.addEventListener("ag-ui-toggle", (e) => console.log(e.detail.collapsed));
312
+ ```
313
+
314
+ ---
315
+
316
+ ## Tool-call display modes
317
+
318
+ How much a tool-call card shows is set via the `data-tool-display` attribute (or `toolDisplay`
319
+ property), one of `minimal` / `compact` / `full` (default `full`):
320
+
321
+ - `minimal` — tool name + status pill only.
322
+ - `compact` — name + status, with args *and* result behind a single collapsed "Details" toggle.
323
+ - `full` — args inline, result behind its own toggle (the original behaviour).
324
+
325
+ If a tool's schema carries an `x-summary` string (use `X_SUMMARY_KEY`), the card shows it on the
326
+ label instead of the raw tool name.
327
+
328
+ ```html
329
+ <ag-ui-chat endpoint="/agent/" data-tool-display="compact"></ag-ui-chat>
330
+ ```
331
+
332
+ ---
333
+
334
+ ## Markdown rendering
335
+
336
+ Assistant bubbles render sanitized markdown/HTML via [`marked`](https://www.npmjs.com/package/marked)
337
+ (GitHub-flavoured, single-newline line breaks) piped through
338
+ [DOMPurify](https://www.npmjs.com/package/dompurify). User messages stay literal text. The
339
+ allowlist permits emphasis, code, lists, quotes, headings, links, tables, and images (`img`); links
340
+ are hardened with `target="_blank" rel="noopener noreferrer"`; `iframe`/`style`/scripting are
341
+ excluded. The exported helper `renderMarkdown(text)` does this standalone. `marked` and `dompurify`
342
+ are runtime dependencies.
343
+
344
+ An animated 3-dot "thinking" indicator (`role="status"`, with an aria-label) appears before the
345
+ first token and between tool rounds, honouring `prefers-reduced-motion`. It has no public API.
346
+
347
+ ### Incoming-text animations
348
+
349
+ The `data-text-animation` attribute controls how a fully-received assistant message reveals:
350
+ `none` (default) / `fade` (a CSS fade) / `word` (JS word-by-word via the internal `wrapWords`
351
+ reveal). It honours `prefers-reduced-motion` (collapsing to instant).
352
+
353
+ ```html
354
+ <ag-ui-chat endpoint="/agent/" data-text-animation="word"></ag-ui-chat>
355
+ ```
356
+
357
+ ---
358
+
359
+ ## Skills: prompt chips and slash palette
360
+
361
+ Skills are pre-defined prompts the user can launch from a chip or the `/`-command palette. They are
362
+ opt-in via two attributes:
363
+
364
+ ```html
365
+ <ag-ui-chat endpoint="/agent/" data-prompt-chips="true" data-slash-commands="true"></ag-ui-chat>
366
+ ```
367
+
368
+ A `Skill` is `{ name, title, description?, prompt, sendImmediately?, chip? }`. Skills are merged
369
+ from three sources — **backend → embed → client** (later wins by `name`):
370
+
371
+ - `data-skills-url` — a JSON endpoint, fetched with the element's `headers`.
372
+ - `data-skills` — an inline JSON catalog.
373
+ - `setSkills(skills)` — set the client catalog from JS.
374
+
375
+ ```js
376
+ chat.setSkills([
377
+ { name: "summarize", title: "Summarize page", prompt: "Summarize {title}.", chip: true },
378
+ ]);
379
+ ```
380
+
381
+ A skill `prompt` may contain `{placeholder}` tokens; the `skillContext` property
382
+ (`() => Record<string, unknown>`) supplies the values, filled in before send. A missing placeholder
383
+ blocks the send and shows a hint instead.
384
+
385
+ ```js
386
+ chat.skillContext = () => ({ title: document.title });
387
+ ```
388
+
389
+ ---
390
+
229
391
  ## MPA durability: surviving full page reloads
230
392
 
231
393
  In a multi-page app, a tool that navigates reloads the whole page and destroys the in-memory run
@@ -234,10 +396,10 @@ mechanisms.
234
396
 
235
397
  **1. Thread identity.** AG-UI's `thread_id` is the conversation key. It is generated once and
236
398
  persisted (so the element reattaches after a reload) by the
237
- [`ClientConversationStore`](src/conversation_store.ts).
399
+ [`ClientConversationStore`](src/core/conversation_store.ts).
238
400
 
239
401
  **2. Durable conversation.** A pluggable `ClientConversationStore` holds the message list. The
240
- default [`SessionStorageStore`](src/conversation_store.ts) keeps everything per-tab in
402
+ default [`SessionStorageStore`](src/core/conversation_store.ts) keeps everything per-tab in
241
403
  `sessionStorage`, so the chat survives full page reloads and clears on tab close. `loadMessages`
242
404
  is async-friendly, so a host can inject a server-backed store (e.g. one that rehydrates from a
243
405
  history endpoint) for cross-tab/device durability:
@@ -246,10 +408,12 @@ history endpoint) for cross-tab/device durability:
246
408
  chat.conversationStore = new MyServerBackedStore();
247
409
  ```
248
410
 
249
- On mount the element rehydrates the transcript from the store, so the chat looks continuous.
411
+ On mount the element rehydrates the transcript from the store, so the chat looks continuous
412
+ including tool-call cards and their results (reconstructed from the persisted `toolCalls` and `tool`
413
+ messages), not just the text turns.
250
414
 
251
415
  **3. Resumable loop (`x-navigates` + `navigationResult`).** A tool whose schema carries
252
- `x-navigates: true` (use `X_NAVIGATES_KEY`; read back by [`isNavigates`](src/is_navigates.ts))
416
+ `x-navigates: true` (use `X_NAVIGATES_KEY`; read back by [`isNavigates`](src/tools/is_navigates.ts))
253
417
  triggers a full reload. Before the handler navigates, the element writes a checkpoint
254
418
  (`{ toolCallId }`) to the store. On the next page mount it:
255
419
 
@@ -276,10 +440,16 @@ rather than by exploring:
276
440
  - `list_routes` — read-only; lists the routes.
277
441
  - `navigate_to_route(route_id, params?)` — resolves the id to a path and navigates.
278
442
 
443
+ A route `path` may contain `:param` segments — e.g. `/projects/:id/users/:userId/`.
444
+ `navigate_to_route` substitutes the path params (URL-encoded) and sends any leftover params as a
445
+ query string; a missing or empty required path param throws. The resolved shape is the exported
446
+ `RouteWithParams` type.
447
+
279
448
  ```js
280
449
  chat.routeMap = [
281
450
  { id: "users", path: "/users", title: "Users", description: "Manage user accounts" },
282
451
  { id: "billing", path: "/billing", title: "Billing" },
452
+ { id: "user-detail", path: "/projects/:id/users/:userId/", title: "User detail" },
283
453
  ];
284
454
  ```
285
455
 
@@ -333,6 +503,7 @@ re-export point. Internal modules import from leaf paths.
333
503
  | `defineAgUiChat()` | function | Idempotently register the element. |
334
504
  | `MessageRole` | type | Role of a rendered chat message. |
335
505
  | `SubmitDetail` | type | `detail` shape of the submit event. |
506
+ | `ToggleDetail` | type | `detail` shape of the `ag-ui-toggle` event (`{ collapsed }`). |
336
507
 
337
508
  ### AG-UI client & agent
338
509
 
@@ -360,10 +531,12 @@ re-export point. Internal modules import from leaf paths.
360
531
  | --- | --- | --- |
361
532
  | `createRouteTools(...)` | function | Build the built-in `route.*` tools. |
362
533
  | `Route` / `RouteMap` | type | Navigable-route shapes. |
534
+ | `RouteWithParams` | type | A route resolved with `:param` path segments + leftover query params. |
363
535
  | `createPageMapContext(...)` | function | Build the per-run `page_map` context entry. |
364
536
  | `PageMap` | type | The compact page-surface shape. |
365
537
  | `createStateHookTools(hook)` | function | Build `read_<name>` / `set_<name>` tools. |
366
538
  | `StateHook` | type | A state-binding declaration. |
539
+ | `Skill` | type | A launchable prompt (chip / `/`-command). |
367
540
 
368
541
  ### Durability
369
542
 
@@ -378,12 +551,14 @@ re-export point. Internal modules import from leaf paths.
378
551
  | Export | Kind | Summary |
379
552
  | --- | --- | --- |
380
553
  | `ToolCallCard` | class | A live tool-call card for the transcript. |
381
- | `ToolCallStatus` / `SettledStatus` | type | Card lifecycle states. |
382
- | `requestConfirmation(host, request)` | function | Render the confirmation modal. |
383
- | `ConfirmationRequest` | type | What the modal displays. |
384
- | `typeInto` / `highlightThenClick` / `scrollIntoCenterView` / `focusWithFlash` | function | Animation primitives. |
385
- | `fillField` / `clickElement` / `setControlValue` | function | DOM-driver primitives. |
386
- | `TypeOptions` / `HighlightClickOptions` / `FlashOptions` / `FillFieldOptions` / `TextLikeElement` | type | Primitive option shapes. |
554
+ | `ToolCallStatus` / `SettledStatus` / `ToolDisplayMode` | type | Card lifecycle states + display mode. |
555
+ | `requestConfirmation(host, request)` | function | Append the inline confirmation card to the transcript. |
556
+ | `ConfirmationRequest` | type | What the card displays. |
557
+ | `renderMarkdown(text)` | function | Render sanitized markdown/HTML (marked + DOMPurify). |
558
+ | `typeInto` / `highlightThenClick` / `pressThenClick` / `selectOption` / `toggleControl` / `scrollIntoCenterView` / `focusWithFlash` / `prefersReducedMotion` | function | Animation primitives. |
559
+ | `fillField` / `clickElement` / `pressButton` / `selectControl` / `setControlValue` / `toggleCheckbox` | function | DOM-driver primitives. |
560
+ | `setNativeValue` / `setNativeChecked` | function | Set a control via its native prototype setter (React-controlled inputs). |
561
+ | `TypeOptions` / `HighlightClickOptions` / `PressOptions` / `SelectOptions` / `ToggleOptions` / `FlashOptions` / `FillFieldOptions` / `TextLikeElement` | type | Primitive option shapes. |
387
562
 
388
563
  ### Constants
389
564
 
@@ -391,17 +566,22 @@ re-export point. Internal modules import from leaf paths.
391
566
  | --- | --- |
392
567
  | `ELEMENT_TAG` | The registered tag name (`ag-ui-chat`). |
393
568
  | `SUBMIT_EVENT` | The submit CustomEvent name. |
569
+ | `TOGGLE_EVENT` | The collapse-toggle CustomEvent name (`ag-ui-toggle`). |
394
570
  | `MESSAGE_ROLE` | Message role constants. |
395
571
  | `TOOL_CALL_STATUS` | Tool-call card status constants. |
572
+ | `TOOL_DISPLAY` | Tool-call display-mode constants (`minimal` / `compact` / `full`). |
573
+ | `X_CONFIRM_KEY` | JSON-Schema key carrying a confirmation prompt. |
574
+ | `X_SUMMARY_KEY` | JSON-Schema key carrying a short tool-card label. |
396
575
  | `MAX_TOOL_ROUNDS` | Upper bound on tool-call → re-run rounds per send. |
397
576
  | `VERSION` | The package version string. |
398
577
 
399
578
  ---
400
579
 
401
- ## Theming
580
+ ## Theming, density, and placement
402
581
 
403
- The chat shell is styled inside its Shadow DOM and exposes CSS custom properties on `:host`, so you
404
- theme it from outside without piercing the shadow boundary. A few of the knobs:
582
+ The chat shell is styled inside its Shadow DOM and exposes a large set of `--ag-ui-*` CSS custom
583
+ properties on `:host` (colors, status, surface, spacing, layout), so you theme it from outside
584
+ without piercing the shadow boundary. A few of the knobs:
405
585
 
406
586
  ```css
407
587
  ag-ui-chat {
@@ -410,8 +590,7 @@ ag-ui-chat {
410
590
  --ag-ui-fg: #1a1a2e;
411
591
  --ag-ui-radius: 12px;
412
592
 
413
- /* Layout — float (default) or embed in your own flow with --ag-ui-position: static */
414
- --ag-ui-position: fixed;
593
+ /* Layout */
415
594
  --ag-ui-width: 380px;
416
595
  --ag-ui-height: 560px;
417
596
  --ag-ui-inset: auto 24px 24px auto;
@@ -419,8 +598,21 @@ ag-ui-chat {
419
598
  }
420
599
  ```
421
600
 
422
- See [`src/styles.ts`](src/styles.ts) for the full list, and [`demo/themes/`](demo/themes/) for
423
- worked examples (default, dark, embedded, and a "claude" theme).
601
+ For the common cases there are three CSS-reactive **preset attributes** (no JS API), so you don't
602
+ have to hand-tune the variables:
603
+
604
+ - `theme` — `light` (default) / `dark` / `auto` (follow the OS) / `code`.
605
+ - `density` — `comfortable` (default) / `compact`.
606
+ - `placement` — `floating` (default) / `bottom-left` / `side` / `full` / `embedded`. `embedded`
607
+ drops the fixed positioning and z-index so the widget sits in normal document flow.
608
+
609
+ ```html
610
+ <ag-ui-chat endpoint="/agent/" theme="dark" density="compact" placement="side"></ag-ui-chat>
611
+ ```
612
+
613
+ See [`src/ui/styles.ts`](src/ui/styles.ts) for the full variable + preset list. The
614
+ [`demo/`](demo/) live playground (`node demo/mock-server.mjs`) flips theme, density, placement,
615
+ text-animation, and tool-display live from a single page.
424
616
 
425
617
  ---
426
618
 
@@ -449,7 +641,7 @@ Other workflow targets (all identical in name to the sibling Python packages):
449
641
  | `make test` | Vitest with a 100% line + branch + function + statement coverage gate. |
450
642
  | `make lint` | `biome check .` + `tsc --noEmit`. |
451
643
  | `make format` | `biome format --write .`. |
452
- | `make demo` | Build, then serve `demo/` against a mock AG-UI server. |
644
+ | `make demo` | Build, then serve the live playground (`demo/themes/index.html`) on port 5173 via `demo/mock-server.mjs`. |
453
645
 
454
646
  ---
455
647
 
@@ -457,7 +649,7 @@ Other workflow targets (all identical in name to the sibling Python packages):
457
649
 
458
650
  | Component | Floor | Tested |
459
651
  | --- | --- | --- |
460
- | Node (tooling/tests only) | 20 | 20, 22, 24 |
652
+ | Node (tooling/tests only) | 22 | 22, 24 |
461
653
  | Browsers (runtime target) | ES2022 / evergreen | Chrome / Firefox / Safari 17+ |
462
654
  | `@ag-ui/client` | latest 0.x | — |
463
655