@artooi/ag-ui-web-component 0.9.0 → 0.11.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 (61) hide show
  1. package/CHANGELOG.md +104 -7
  2. package/README.md +89 -7
  3. package/dist/ag-ui-web-component.bundle.js +168 -47
  4. package/dist/ag-ui-web-component.bundle.js.map +4 -4
  5. package/dist/core/ag_ui_chat.d.ts +39 -1
  6. package/dist/core/ag_ui_chat.d.ts.map +1 -1
  7. package/dist/core/agui_client.d.ts +28 -1
  8. package/dist/core/agui_client.d.ts.map +1 -1
  9. package/dist/core/attachment.d.ts +5 -0
  10. package/dist/core/attachment.d.ts.map +1 -1
  11. package/dist/core/conversation_store.d.ts +8 -0
  12. package/dist/core/conversation_store.d.ts.map +1 -1
  13. package/dist/core/remote_conversation_store.d.ts.map +1 -1
  14. package/dist/core/upload_attachment.d.ts +8 -2
  15. package/dist/core/upload_attachment.d.ts.map +1 -1
  16. package/dist/index.d.ts +3 -1
  17. package/dist/index.d.ts.map +1 -1
  18. package/dist/index.js +887 -128
  19. package/dist/index.js.map +4 -4
  20. package/dist/ui/approval_card.d.ts +51 -0
  21. package/dist/ui/approval_card.d.ts.map +1 -0
  22. package/dist/ui/attachment_chips.d.ts.map +1 -1
  23. package/dist/ui/attachment_tray.d.ts +7 -1
  24. package/dist/ui/attachment_tray.d.ts.map +1 -1
  25. package/dist/ui/question_card.d.ts +52 -0
  26. package/dist/ui/question_card.d.ts.map +1 -0
  27. package/dist/ui/relative_time.d.ts +5 -3
  28. package/dist/ui/relative_time.d.ts.map +1 -1
  29. package/dist/ui/skills_menu.d.ts.map +1 -1
  30. package/dist/ui/styles.d.ts +1 -1
  31. package/dist/ui/styles.d.ts.map +1 -1
  32. package/dist/ui/thoughts_block.d.ts +2 -2
  33. package/dist/ui/thoughts_block.d.ts.map +1 -1
  34. package/dist/ui/thread_drawer.d.ts.map +1 -1
  35. package/dist/ui/tool_call_card.d.ts.map +1 -1
  36. package/dist/ui/ui_strings.d.ts +16 -0
  37. package/dist/ui/ui_strings.d.ts.map +1 -1
  38. package/dist/ui/voice_input.d.ts +9 -1
  39. package/dist/ui/voice_input.d.ts.map +1 -1
  40. package/dist/version.d.ts.map +1 -1
  41. package/package.json +4 -4
  42. package/src/core/ag_ui_chat.ts +251 -14
  43. package/src/core/agui_client.ts +95 -9
  44. package/src/core/attachment.ts +21 -1
  45. package/src/core/conversation_store.ts +84 -18
  46. package/src/core/remote_conversation_store.ts +24 -3
  47. package/src/core/upload_attachment.ts +8 -1
  48. package/src/index.ts +14 -0
  49. package/src/ui/approval_card.ts +119 -0
  50. package/src/ui/attachment_chips.ts +5 -0
  51. package/src/ui/attachment_tray.ts +50 -5
  52. package/src/ui/question_card.ts +216 -0
  53. package/src/ui/relative_time.ts +8 -3
  54. package/src/ui/skills_menu.ts +6 -0
  55. package/src/ui/styles.ts +130 -9
  56. package/src/ui/thoughts_block.ts +3 -2
  57. package/src/ui/thread_drawer.ts +94 -9
  58. package/src/ui/tool_call_card.ts +6 -0
  59. package/src/ui/ui_strings.ts +30 -0
  60. package/src/ui/voice_input.ts +21 -1
  61. package/src/version.ts +1 -1
package/CHANGELOG.md CHANGED
@@ -7,25 +7,120 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.11.0] — 2026-07-14
11
+
12
+ ### Added
13
+
14
+ - **Server-side tool approval — the browser half of the human-in-the-loop gate.**
15
+ When a gated server-side tool defers instead of executing, the run finishes on
16
+ an AG-UI *interrupt*; the client now renders an inline **approval card**
17
+ (`requestApproval`, next to the pending tool-call card) and, on the user's
18
+ decision, resumes the run with the answer via the protocol's `resume[]`.
19
+ Approve runs the tool (its result streams back into the same card); deny sends
20
+ a `cancelled` answer so the model learns it was declined and the card settles
21
+ as declined. No `@ag-ui/*` dependency bump — the interrupt/resume types already
22
+ ship in the pinned `0.0.x`. `AgUiClient` gains a `resolveInterrupts` config hook
23
+ and exports `InterruptResponse` / `ResolveInterrupts`. The card is fully
24
+ customizable: `strings` (`approveAction` / `approvalPrompt` / `approve` /
25
+ `deny`), a `::part()` surface (`approval`, `-body`, `-actions`, `-button`,
26
+ `-approve`, `-deny`), and an `approvalRenderer` hook that fully replaces the UI
27
+ (given the request + a Stop `AbortSignal`, resolves approve/deny). Exports
28
+ `ApprovalRenderer`.
29
+ - **`ask_user` — a built-in typed-question frontend tool (opt-in).** Set
30
+ `askUser = true` on `<ag-ui-chat>` to offer the agent an `ask_user(question,
31
+ options?, allow_custom?)` tool: calling it renders an inline **question card**
32
+ (`requestQuestion` — radio choices and/or a free-text field) and returns the
33
+ chosen or typed answer as the tool result, reusing the existing frontend-tool
34
+ path (no new protocol). Off by default, like the other built-in tool groups, so
35
+ the advertised catalog is unchanged until a host opts in. The card is **fully
36
+ customizable**: localized `strings` (`askUserAction` / `otherOption` /
37
+ `answerPlaceholder` / `submit`), a full `::part()` surface (`question`,
38
+ `question-body`, `-options`, `-choice`, `-radio`, `-input`, `-actions`,
39
+ `-button`), and a `askUserRenderer` hook that fully replaces the UI with a
40
+ host-supplied renderer (given the request + a Stop `AbortSignal`, resolves the
41
+ answer). Exports `QuestionRenderer`.
42
+ - **Complete `::part()` coverage sweep.** Every rendered UI element now exposes a
43
+ `part` for `::part()` styling — closing gaps in the attachment chips
44
+ (`attachment-chips` and the shared `attachment-chip*` parts, now on both the
45
+ composer tray and the read-only chips on sent bubbles), the skills UI
46
+ (`skill-chips` / `skill-chip` / `skill-palette` / `skill-item*` / `skill-hint`),
47
+ the history-drawer row internals (`drawer-row-title` / `-time` / `-preview` /
48
+ `-actions` / `-rename` / `-delete`, the inline `drawer-rename-input`, and the
49
+ `drawer-confirm*` delete prompt), plus `thoughts-label`, `question-choice-text`,
50
+ and the `stopped` note. All are documented in the README "Available parts" list.
51
+
52
+ ## [0.10.0] — 2026-07-02
53
+
54
+ ### Added
55
+
56
+ - **Upload cancellation.** `UploadHandler` gains an optional third argument,
57
+ `signal: AbortSignal`. Removing a pending chip, clearing the tray, or removing
58
+ the element now aborts the in-flight upload, so a cancelled transfer no longer
59
+ orphans a server-side file. Non-breaking: existing two-argument handlers keep
60
+ working; a custom handler that honours the signal should abort its own
61
+ transport when it fires.
62
+ - **Teardown on disconnect.** `<ag-ui-chat>` now cleans up when it leaves the
63
+ DOM (a removed node or a client-side route swap): it cancels the in-flight
64
+ run so the SSE stream closes, aborts in-flight uploads, and releases the
65
+ microphone so the browser's recording indicator clears.
66
+ - **Accessible history drawer.** The chat-history drawer is now a proper modal
67
+ dialog — Escape closes it, focus moves into the panel on open and is restored
68
+ to the opener on close, Tab is trapped within the panel, and an inline rename
69
+ commits on blur.
70
+ - **Per-instance storage scoping.** The collapsed / theme / active-thread state
71
+ and the default thread store are now namespaced by the element's `id` (else
72
+ its `endpoint`), so two `<ag-ui-chat>` instances — or two apps — on the same
73
+ origin no longer share state. Pre-existing per-tab state migrates into the
74
+ namespace automatically on first load.
75
+
76
+ ### Fixed
77
+
78
+ - **Submit while running.** Pressing Enter during a live run no longer starts a
79
+ second, concurrent SSE run (which orphaned the first and could corrupt its
80
+ pending tool cards). Enter now matches the Send/Stop button and is ignored
81
+ while a run is in flight.
82
+ - **Thread-switch race.** Rapidly switching threads against a slow remote store
83
+ can no longer interleave two replays into one transcript — a stale replay is
84
+ dropped once a newer switch begins.
85
+ - **Malformed thread responses.** A `200` from the threads endpoint whose body
86
+ isn't valid JSON (a proxy's HTML error page, a truncated stream) now falls
87
+ back to the local cache instead of silently failing to load the drawer or
88
+ history.
89
+ - **Relative timestamps.** An unparseable or missing `updated_at` now renders a
90
+ neutral "just now" rather than `"NaNw ago"` or `"~2950w ago"`.
91
+ - **Corrupt attachment refs.** Malformed entries in a message's persisted
92
+ attachments are dropped instead of throwing and aborting the whole history
93
+ replay.
94
+ - **Retry of a rejected upload.** Retrying a chip that was rejected client-side
95
+ (oversize / disallowed type) now re-applies the guard instead of uploading the
96
+ file in full.
97
+ - **Duplicate tool result.** A repeated `TOOL_CALL_RESULT` (or a replayed tool
98
+ message for an already-settled card) no longer appends a second result
99
+ section.
100
+ - **Run-error continuation.** A run that ends in an error is now terminal: the
101
+ loop no longer proceeds into frontend-tool execution or another round, so a
102
+ failed run can't surface a confusing second error. Pending tool cards still
103
+ settle.
104
+
10
105
  ## [0.9.0] — 2026-06-30
11
106
 
12
107
  ### Added
13
108
 
14
- - **Model thoughts (THINK-1).** When the server forwards a reasoning model's
109
+ - **Model thoughts.** When the server forwards a reasoning model's
15
110
  chain-of-thought, the element now renders a muted, collapsible **thoughts
16
111
  region** (part `thoughts`) at the top of the current answer group — it streams
17
112
  while the model reasons and folds away on the answer's first token (the reader
18
113
  can reopen it). `AgUiClientHandlers` gains `onReasoningStart` / `onReasoningDelta`
19
114
  / `onReasoningEnd`, wired from `@ag-ui/client`'s `REASONING_*` subscriber
20
115
  callbacks (which also cover the deprecated `THINKING_*` family).
21
- - **Voice input (VOICE-1).** Set `data-transcribe-url` (django-ag-ui's
116
+ - **Voice input.** Set `data-transcribe-url` (django-ag-ui's
22
117
  `TranscribeView`) to reveal a 🎤 mic button in the composer (part
23
118
  `voice-button`): it records via `MediaRecorder`, POSTs the clip, and drops the
24
119
  returned transcript into the textarea. A pluggable `transcribeHandler` —
25
120
  `(audio: Blob) => Promise<string>` — swaps the transport (a different STT
26
121
  endpoint, a Web Speech adapter) and reveals the mic even without the attribute.
27
122
  New exports: `transcribeAudio`, `TranscribeOptions`, `TranscribeHandler`.
28
- - **Built-in theme toggle (THEME-1).** The boolean `data-theme-toggle` attribute
123
+ - **Built-in theme toggle.** The boolean `data-theme-toggle` attribute
29
124
  adds an optional light⇄dark toggle to the header (part `theme-toggle`,
30
125
  `toggleTheme()`) that flips `theme` and persists per tab. Off by default, so a
31
126
  host-supplied switch in `slot="header-actions"` stays unaffected.
@@ -44,7 +139,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
44
139
 
45
140
  ### Added
46
141
 
47
- - **Per-turn answer group + opt-in well (WELL-1).** Each assistant turn now
142
+ - **Per-turn answer group + opt-in well.** Each assistant turn now
48
143
  renders inside one `.answer` group (part `answer`) that holds its streamed
49
144
  text, tool cards, and pending indicator — so a turn that calls tools reads as
50
145
  a single answer instead of loose siblings. The group spans the whole
@@ -54,12 +149,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
54
149
  `data-answer-well` attribute to box that group in a bordered, padded "well"
55
150
  (themeable via `--ag-ui-well-bg` / `--ag-ui-well-border`); without it the
56
151
  layout is the flat stack as before. Pure CSS, turn-scoped, no JS API.
57
- - **Full-screen page placement (PAGE-1).** New `placement="page"`: a full-bleed
152
+ - **Full-screen page placement.** New `placement="page"`: a full-bleed
58
153
  background with the conversation in a centred reading column (default ~820px,
59
154
  set via `--ag-ui-content-max-width`). The assistant turn spans the column
60
155
  while the user message stays a right-aligned pill — the layout for a dedicated
61
156
  chat page (distinct from `full`'s edge-to-edge, left-aligned messages).
62
- - **Inline tool-display mode + themeable status icons (CARD-1).** New
157
+ - **Inline tool-display mode + themeable status icons.** New
63
158
  `data-tool-display="inline"`: the lightest card — a one-line status row (icon
64
159
  + summary, no box chrome) with the result behind its own toggle. Every
65
160
  tool-call card now leads with a CSS-drawn **status icon** (part
@@ -400,7 +495,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
400
495
  ### Notes
401
496
  - First release — exercising the automated npm OIDC publish pipeline end-to-end.
402
497
 
403
- [Unreleased]: https://github.com/Artui/ag-ui-web-component/compare/v0.9.0...HEAD
498
+ [Unreleased]: https://github.com/Artui/ag-ui-web-component/compare/v0.11.0...HEAD
499
+ [0.11.0]: https://github.com/Artui/ag-ui-web-component/compare/v0.10.0...v0.11.0
500
+ [0.10.0]: https://github.com/Artui/ag-ui-web-component/compare/v0.9.0...v0.10.0
404
501
  [0.9.0]: https://github.com/Artui/ag-ui-web-component/compare/v0.8.1...v0.9.0
405
502
  [0.8.1]: https://github.com/Artui/ag-ui-web-component/compare/v0.8.0...v0.8.1
406
503
  [0.8.0]: https://github.com/Artui/ag-ui-web-component/compare/v0.7.0...v0.8.0
package/README.md CHANGED
@@ -171,7 +171,7 @@ That's the whole integration: an `endpoint` attribute pointing at your AG-UI ser
171
171
  | `placement` | — | CSS-only: `floating` (default) / `bottom-left` / `side` / `sidebar` / `full` / `page` / `embedded`. |
172
172
 
173
173
  **Properties** (JS only, not attributes): `headers`, `allowImages`, `autoConfirm`,
174
- `confirmPredicate`, `agentFactory`, `getTools`, `getContext`, `routeMap`, `navigate`,
174
+ `confirmPredicate`, `askUser`, `agentFactory`, `getTools`, `getContext`, `routeMap`, `navigate`,
175
175
  `getPageMap`, `autoInjectPageMap`, `conversationStore`, `uploadHandler`, `transcribeHandler`,
176
176
  `navigationResult`, `skillContext`, `toolSummaries`, `strings`, `resolvePageTarget`, plus the
177
177
  mirrors `endpoint` / `toolDisplay` / `collapsed`.
@@ -234,7 +234,8 @@ AG-UI has no server-side cancel route: cancelling **aborts the streaming request
234
234
  round starts. A frontend tool handler already running completes, but its result doesn't trigger
235
235
  a re-run.
236
236
  - An **open confirmation card is declined** (`data-resolved="declined"`) — cancelling the run
237
- answers the pending question.
237
+ answers the pending question. Likewise an open **approval card** is denied and an open
238
+ **question card** (`ask_user`) resolves with an empty answer.
238
239
  - The new `onCancelled()` handler fires instead of `onError()`; `onSettled()` still follows
239
240
  (the terminal-rest guarantee), returning the button to **Send**.
240
241
 
@@ -308,6 +309,73 @@ chat.registerTool({
308
309
  });
309
310
  ```
310
311
 
312
+ The confirmation card gates **client-registered** tools *before* they run. A **server-side**
313
+ tool runs on the server, so the browser can't intercept it the same way — that is what the
314
+ approval card below is for.
315
+
316
+ ### Server-side tool approval (interrupts)
317
+
318
+ When the server gates a destructive tool (e.g. django-ag-ui's `ToolGuard`), the tool **defers**
319
+ instead of executing and the run finishes on an AG-UI *interrupt*. The element then appends an
320
+ **inline approval card** (a `<div class="approval">`) via
321
+ [`requestApproval`](src/ui/approval_card.ts), next to the pending tool-call card:
322
+
323
+ - **Approve** → the run resumes and the server runs the tool; its result streams back into the
324
+ same card.
325
+ - **Deny** → the run resumes carrying a `cancelled` answer, so the model learns the tool was
326
+ declined; the pending card settles as declined.
327
+
328
+ This uses the AG-UI protocol's own interrupt/resume mechanism (`RunAgentInput.resume[]`) — the
329
+ wire stays vanilla AG-UI. A **Stop** while an approval card is open denies every open card and
330
+ cancels the run. No configuration is needed on the client; the gate is enabled server-side.
331
+
332
+ Like the question card, the approval card is customizable at three levels: **text** (`strings`:
333
+ `approveAction` / `approvalPrompt` / `approve` / `deny`), **CSS** (`::part()`: `approval`,
334
+ `approval-body`, `approval-actions`, `approval-button`, `approval-approve`, `approval-deny`), and
335
+ **full replacement** via `chat.approvalRenderer` — given the request (`message` + `toolName`) and
336
+ a Stop `AbortSignal`, render your own UI and resolve `true`/`false`:
337
+
338
+ ```js
339
+ chat.approvalRenderer = (request, { signal }) =>
340
+ myConfirmDialog(request.message ?? `Run ${request.toolName}?`, { signal });
341
+ ```
342
+
343
+ ### Asking the user a question (`ask_user`)
344
+
345
+ Set `chat.askUser = true` to offer the agent a built-in `ask_user` frontend tool. When the agent
346
+ calls it, the element renders an **inline question card** (a `<div class="question">`) via
347
+ [`requestQuestion`](src/ui/question_card.ts) and returns the user's answer as the tool result:
348
+
349
+ ```js
350
+ chat.askUser = true; // opt in; off by default so the tool catalog is unchanged otherwise
351
+ ```
352
+
353
+ `ask_user(question, options?, allow_custom?)` renders `options` as radio buttons, adds a free-text
354
+ field when `allow_custom` is set (or when no options are given), and feeds the chosen or typed
355
+ answer back through the normal frontend-tool path — no new protocol. A **Stop** dismisses an open
356
+ question with an empty answer.
357
+
358
+ The question card is **fully customizable** at three levels:
359
+
360
+ - **Text** — every label is a `strings` key: `askUserAction` (the card's `aria-label`),
361
+ `otherOption`, `answerPlaceholder`, `submit`.
362
+ - **CSS** — every element exposes a `::part()`: `question`, `question-body`, `question-options`,
363
+ `question-choice`, `question-radio`, `question-input`, `question-actions`, `question-button`
364
+ (plus the `--ag-ui-*` theme variables). No shadow piercing.
365
+ - **Full replacement** — set `chat.askUserRenderer` to own the entire UI. Given the parsed request
366
+ and an `AbortSignal` (fired on Stop), render anything — a native modal, a framework component —
367
+ and resolve with the answer (empty string = no answer). The built-in card is bypassed entirely.
368
+
369
+ ```js
370
+ // Level 1+2: restyle the built-in card.
371
+ chat.strings = { submit: "Answer", answerPlaceholder: "Type here…" };
372
+ // ag-ui-chat::part(question) { border-radius: 0; }
373
+
374
+ // Level 3: replace the card with your own UI.
375
+ chat.askUserRenderer = (request, { signal }) =>
376
+ myModal.ask(request.question, request.options, { allowCustom: request.allowCustom, signal });
377
+ ```
378
+
311
379
  ### DOM-driver and animation primitives
312
380
 
313
381
  So the agent can visibly drive the host page, the package ships generic, framework-free
@@ -792,14 +860,28 @@ ag-ui-chat::part(tool-card) { font-family: var(--my-mono); }
792
860
 
793
861
  Available parts: `panel`, `header`, `title`, `icon`, `header-controls`, `header-button`
794
862
  (plus `history-button` / `new-button` / `collapse-button` / `theme-toggle`), `messages`,
795
- `answer` (the per-turn group), `thoughts` (plus `thoughts-toggle` / `thoughts-body`), `message`
796
- (plus `message-user` / `message-assistant`), `empty`, `pending`, `tool-card`
863
+ `answer` (the per-turn group), `thoughts` (plus `thoughts-toggle` / `thoughts-body` /
864
+ `thoughts-label`), `message`
865
+ (plus `message-user` / `message-assistant`), `empty`, `pending`, `stopped` (the "⏹ Stopped" note),
866
+ `tool-card`
797
867
  (plus `tool-card-head` / `-icon` / `-name` / `-status` / `-args` / `-toggle` / `-result`),
798
868
  `confirm` (plus `confirm-body` /
799
- `-args` / `-actions` / `-button` / `-cancel` / `-confirm`), `composer`, `input`, `send`,
800
- `attach-button`, `voice-button`, `attachment-tray`, `launcher`, `launcher-icon`, and the drawer parts
869
+ `-args` / `-actions` / `-button` / `-cancel` / `-confirm`),
870
+ `approval` (plus `approval-body` / `-actions` / `-button` / `-approve` / `-deny`),
871
+ `question` (plus `question-body` / `-options` / `-choice` / `-choice-text` / `-radio` / `-input` /
872
+ `-actions` / `-button`), `composer`, `input`, `send`,
873
+ `attach-button`, `voice-button`,
874
+ the attachment chips — `attachment-tray` and `attachment-chips` (the read-only chips on sent
875
+ bubbles) with the shared chip parts `attachment-chip` (plus `-icon` / `-name` / `-size` / `-bar` /
876
+ `-bar-fill` / `-retry` / `-remove`),
877
+ the skills UI (`skill-chips`, `skill-chip`, `skill-palette`, `skill-item`, `skill-item-title`,
878
+ `skill-item-desc`, and the missing-placeholder `skill-hint`),
879
+ `launcher`, `launcher-icon`, and the drawer parts
801
880
  (`drawer`, `drawer-backdrop`, `drawer-panel`, `drawer-header`, `drawer-title`, `drawer-new`,
802
- `drawer-list`, `drawer-empty`, `drawer-row`, `drawer-row-select`).
881
+ `drawer-list`, `drawer-empty`, `drawer-row`, `drawer-row-select`, `drawer-row-title`,
882
+ `drawer-row-time`, `drawer-row-preview`, `drawer-row-actions`, `drawer-row-rename`,
883
+ `drawer-row-delete`, `drawer-rename-input`, `drawer-confirm`, `drawer-confirm-label`,
884
+ `drawer-confirm-yes`, `drawer-confirm-no`).
803
885
 
804
886
  Coarse **slots** let you replace whole regions with your own markup (project light-DOM children
805
887
  with a matching `slot=`):