@artooi/ag-ui-web-component 0.10.0 → 0.12.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 (45) hide show
  1. package/CHANGELOG.md +78 -1
  2. package/README.md +144 -7
  3. package/dist/ag-ui-web-component.bundle.js +237 -40
  4. package/dist/ag-ui-web-component.bundle.js.map +4 -4
  5. package/dist/core/ag_ui_chat.d.ts +29 -0
  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/run_index.d.ts +50 -0
  10. package/dist/core/run_index.d.ts.map +1 -0
  11. package/dist/index.d.ts +5 -1
  12. package/dist/index.d.ts.map +1 -1
  13. package/dist/index.js +942 -101
  14. package/dist/index.js.map +4 -4
  15. package/dist/ui/approval_card.d.ts +51 -0
  16. package/dist/ui/approval_card.d.ts.map +1 -0
  17. package/dist/ui/attachment_chips.d.ts.map +1 -1
  18. package/dist/ui/attachment_tray.d.ts.map +1 -1
  19. package/dist/ui/checkpoint_menu.d.ts +32 -0
  20. package/dist/ui/checkpoint_menu.d.ts.map +1 -0
  21. package/dist/ui/question_card.d.ts +52 -0
  22. package/dist/ui/question_card.d.ts.map +1 -0
  23. package/dist/ui/skills_menu.d.ts.map +1 -1
  24. package/dist/ui/styles.d.ts +1 -1
  25. package/dist/ui/styles.d.ts.map +1 -1
  26. package/dist/ui/thoughts_block.d.ts.map +1 -1
  27. package/dist/ui/thread_drawer.d.ts.map +1 -1
  28. package/dist/ui/ui_strings.d.ts +26 -0
  29. package/dist/ui/ui_strings.d.ts.map +1 -1
  30. package/package.json +4 -4
  31. package/src/core/ag_ui_chat.ts +263 -4
  32. package/src/core/agui_client.ts +86 -11
  33. package/src/core/run_index.ts +91 -0
  34. package/src/index.ts +16 -0
  35. package/src/ui/approval_card.ts +119 -0
  36. package/src/ui/attachment_chips.ts +5 -0
  37. package/src/ui/attachment_tray.ts +8 -0
  38. package/src/ui/checkpoint_menu.ts +153 -0
  39. package/src/ui/question_card.ts +216 -0
  40. package/src/ui/skills_menu.ts +6 -0
  41. package/src/ui/styles.ts +197 -0
  42. package/src/ui/thoughts_block.ts +1 -0
  43. package/src/ui/thread_drawer.ts +11 -0
  44. package/src/ui/ui_strings.ts +45 -0
  45. package/src/version.ts +1 -1
package/CHANGELOG.md CHANGED
@@ -7,6 +7,81 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.12.0] — 2026-07-27
11
+
12
+ ### Added
13
+
14
+ - **Resume or fork a run — the checkpoint UI.** With `data-runs-url` pointed at
15
+ django-ag-ui's run index (`RunsView`, 0.23+), a ⭯ button appears in the header
16
+ opening a *Continue a run* panel. Type the next turn, pick a row, and the run
17
+ continues from its last server-side checkpoint — **Resume** to carry on,
18
+ **Fork** to branch without touching the original. The client half of durable
19
+ step persistence, whose server half shipped in django-ag-ui 0.20.0.
20
+ - **Only continuable runs are offered.** The server reports whether a run has
21
+ a snapshot to seed from; one that never reached a provider-valid boundary
22
+ has none, so resuming it would start from nothing. Rows show when the run
23
+ started (id on hover, for correlating with server logs) and mark a branched
24
+ run so a fork doesn't read as a duplicate of its parent.
25
+ - **One URL configures three endpoints.** `resume/<id>/` and `fork/<id>/` are
26
+ siblings of the index — the server mounts all three together — so they are
27
+ derived rather than configured, and a half-configured set isn't expressible.
28
+ - **The client contract is structural, not a rule to remember.** Those
29
+ endpoints require a *fresh run id* and *only the new turn*, because the
30
+ server supplies prior turns from the snapshot and re-sending them would
31
+ duplicate the conversation. A continuation therefore runs on its own
32
+ short-lived agent, pointed at the resume endpoint and seeded with **no**
33
+ history — so the new turn is the only thing it *can* send, the fresh run id
34
+ comes free, and the main agent's history is never touched.
35
+ - A resumed run is otherwise a normal run: frontend tools execute, approval
36
+ interrupts render, and `headers` are re-read per request so a rotated
37
+ token still reaches the endpoint. An unreachable index shows the panel's
38
+ empty state rather than an error.
39
+ - New exports: `RunIndex` / `RunRow`, `CheckpointMenu` / `CheckpointVerb`, and
40
+ five UI strings (`checkpoints`, `noCheckpoints`, `resumeRun`, `forkRun`,
41
+ `forkedRun`) for localization.
42
+
43
+ ## [0.11.0] — 2026-07-14
44
+
45
+ ### Added
46
+
47
+ - **Server-side tool approval — the browser half of the human-in-the-loop gate.**
48
+ When a gated server-side tool defers instead of executing, the run finishes on
49
+ an AG-UI *interrupt*; the client now renders an inline **approval card**
50
+ (`requestApproval`, next to the pending tool-call card) and, on the user's
51
+ decision, resumes the run with the answer via the protocol's `resume[]`.
52
+ Approve runs the tool (its result streams back into the same card); deny sends
53
+ a `cancelled` answer so the model learns it was declined and the card settles
54
+ as declined. No `@ag-ui/*` dependency bump — the interrupt/resume types already
55
+ ship in the pinned `0.0.x`. `AgUiClient` gains a `resolveInterrupts` config hook
56
+ and exports `InterruptResponse` / `ResolveInterrupts`. The card is fully
57
+ customizable: `strings` (`approveAction` / `approvalPrompt` / `approve` /
58
+ `deny`), a `::part()` surface (`approval`, `-body`, `-actions`, `-button`,
59
+ `-approve`, `-deny`), and an `approvalRenderer` hook that fully replaces the UI
60
+ (given the request + a Stop `AbortSignal`, resolves approve/deny). Exports
61
+ `ApprovalRenderer`.
62
+ - **`ask_user` — a built-in typed-question frontend tool (opt-in).** Set
63
+ `askUser = true` on `<ag-ui-chat>` to offer the agent an `ask_user(question,
64
+ options?, allow_custom?)` tool: calling it renders an inline **question card**
65
+ (`requestQuestion` — radio choices and/or a free-text field) and returns the
66
+ chosen or typed answer as the tool result, reusing the existing frontend-tool
67
+ path (no new protocol). Off by default, like the other built-in tool groups, so
68
+ the advertised catalog is unchanged until a host opts in. The card is **fully
69
+ customizable**: localized `strings` (`askUserAction` / `otherOption` /
70
+ `answerPlaceholder` / `submit`), a full `::part()` surface (`question`,
71
+ `question-body`, `-options`, `-choice`, `-radio`, `-input`, `-actions`,
72
+ `-button`), and a `askUserRenderer` hook that fully replaces the UI with a
73
+ host-supplied renderer (given the request + a Stop `AbortSignal`, resolves the
74
+ answer). Exports `QuestionRenderer`.
75
+ - **Complete `::part()` coverage sweep.** Every rendered UI element now exposes a
76
+ `part` for `::part()` styling — closing gaps in the attachment chips
77
+ (`attachment-chips` and the shared `attachment-chip*` parts, now on both the
78
+ composer tray and the read-only chips on sent bubbles), the skills UI
79
+ (`skill-chips` / `skill-chip` / `skill-palette` / `skill-item*` / `skill-hint`),
80
+ the history-drawer row internals (`drawer-row-title` / `-time` / `-preview` /
81
+ `-actions` / `-rename` / `-delete`, the inline `drawer-rename-input`, and the
82
+ `drawer-confirm*` delete prompt), plus `thoughts-label`, `question-choice-text`,
83
+ and the `stopped` note. All are documented in the README "Available parts" list.
84
+
10
85
  ## [0.10.0] — 2026-07-02
11
86
 
12
87
  ### Added
@@ -453,7 +528,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
453
528
  ### Notes
454
529
  - First release — exercising the automated npm OIDC publish pipeline end-to-end.
455
530
 
456
- [Unreleased]: https://github.com/Artui/ag-ui-web-component/compare/v0.10.0...HEAD
531
+ [Unreleased]: https://github.com/Artui/ag-ui-web-component/compare/v0.12.0...HEAD
532
+ [0.12.0]: https://github.com/Artui/ag-ui-web-component/compare/v0.11.0...v0.12.0
533
+ [0.11.0]: https://github.com/Artui/ag-ui-web-component/compare/v0.10.0...v0.11.0
457
534
  [0.10.0]: https://github.com/Artui/ag-ui-web-component/compare/v0.9.0...v0.10.0
458
535
  [0.9.0]: https://github.com/Artui/ag-ui-web-component/compare/v0.8.1...v0.9.0
459
536
  [0.8.1]: https://github.com/Artui/ag-ui-web-component/compare/v0.8.0...v0.8.1
package/README.md CHANGED
@@ -155,6 +155,7 @@ That's the whole integration: an `endpoint` attribute pointing at your AG-UI ser
155
155
  | `data-skills-url` | — | URL of a JSON skill catalog (fetched with `headers`). |
156
156
  | `data-tools-url` | — | URL of a server tool-label catalog (`[{ name, summary, description? }]`), fetched with `headers`; labels tool-call cards for server-side tools. |
157
157
  | `data-threads-url` | — | URL of a server thread index (django-ag-ui's `ThreadsView`); enables durable, cross-device chat history. |
158
+ | `data-runs-url` | — | URL of a server run index (django-ag-ui's `RunsView`); reveals the header's ⭯ *Continue a run* panel. See [Resuming a run](#resuming-a-run). |
158
159
  | `data-attachments-url` | — | URL of the file-upload endpoint (django-ag-ui's `AttachmentsView`); reveals the composer's 📎 picker + drag-and-drop. |
159
160
  | `data-attachment-accept` | — | `<input accept>` list for client-side type filtering (e.g. `image/*,.pdf`). The server stays authoritative. |
160
161
  | `data-attachment-max-bytes` | — | Client-side upload size cap in bytes (default 10 MiB; `0` disables). The server stays authoritative. |
@@ -171,7 +172,7 @@ That's the whole integration: an `endpoint` attribute pointing at your AG-UI ser
171
172
  | `placement` | — | CSS-only: `floating` (default) / `bottom-left` / `side` / `sidebar` / `full` / `page` / `embedded`. |
172
173
 
173
174
  **Properties** (JS only, not attributes): `headers`, `allowImages`, `autoConfirm`,
174
- `confirmPredicate`, `agentFactory`, `getTools`, `getContext`, `routeMap`, `navigate`,
175
+ `confirmPredicate`, `askUser`, `agentFactory`, `getTools`, `getContext`, `routeMap`, `navigate`,
175
176
  `getPageMap`, `autoInjectPageMap`, `conversationStore`, `uploadHandler`, `transcribeHandler`,
176
177
  `navigationResult`, `skillContext`, `toolSummaries`, `strings`, `resolvePageTarget`, plus the
177
178
  mirrors `endpoint` / `toolDisplay` / `collapsed`.
@@ -234,7 +235,8 @@ AG-UI has no server-side cancel route: cancelling **aborts the streaming request
234
235
  round starts. A frontend tool handler already running completes, but its result doesn't trigger
235
236
  a re-run.
236
237
  - An **open confirmation card is declined** (`data-resolved="declined"`) — cancelling the run
237
- answers the pending question.
238
+ answers the pending question. Likewise an open **approval card** is denied and an open
239
+ **question card** (`ask_user`) resolves with an empty answer.
238
240
  - The new `onCancelled()` handler fires instead of `onError()`; `onSettled()` still follows
239
241
  (the terminal-rest guarantee), returning the button to **Send**.
240
242
 
@@ -308,6 +310,73 @@ chat.registerTool({
308
310
  });
309
311
  ```
310
312
 
313
+ The confirmation card gates **client-registered** tools *before* they run. A **server-side**
314
+ tool runs on the server, so the browser can't intercept it the same way — that is what the
315
+ approval card below is for.
316
+
317
+ ### Server-side tool approval (interrupts)
318
+
319
+ When the server gates a destructive tool (e.g. django-ag-ui's `ToolGuard`), the tool **defers**
320
+ instead of executing and the run finishes on an AG-UI *interrupt*. The element then appends an
321
+ **inline approval card** (a `<div class="approval">`) via
322
+ [`requestApproval`](src/ui/approval_card.ts), next to the pending tool-call card:
323
+
324
+ - **Approve** → the run resumes and the server runs the tool; its result streams back into the
325
+ same card.
326
+ - **Deny** → the run resumes carrying a `cancelled` answer, so the model learns the tool was
327
+ declined; the pending card settles as declined.
328
+
329
+ This uses the AG-UI protocol's own interrupt/resume mechanism (`RunAgentInput.resume[]`) — the
330
+ wire stays vanilla AG-UI. A **Stop** while an approval card is open denies every open card and
331
+ cancels the run. No configuration is needed on the client; the gate is enabled server-side.
332
+
333
+ Like the question card, the approval card is customizable at three levels: **text** (`strings`:
334
+ `approveAction` / `approvalPrompt` / `approve` / `deny`), **CSS** (`::part()`: `approval`,
335
+ `approval-body`, `approval-actions`, `approval-button`, `approval-approve`, `approval-deny`), and
336
+ **full replacement** via `chat.approvalRenderer` — given the request (`message` + `toolName`) and
337
+ a Stop `AbortSignal`, render your own UI and resolve `true`/`false`:
338
+
339
+ ```js
340
+ chat.approvalRenderer = (request, { signal }) =>
341
+ myConfirmDialog(request.message ?? `Run ${request.toolName}?`, { signal });
342
+ ```
343
+
344
+ ### Asking the user a question (`ask_user`)
345
+
346
+ Set `chat.askUser = true` to offer the agent a built-in `ask_user` frontend tool. When the agent
347
+ calls it, the element renders an **inline question card** (a `<div class="question">`) via
348
+ [`requestQuestion`](src/ui/question_card.ts) and returns the user's answer as the tool result:
349
+
350
+ ```js
351
+ chat.askUser = true; // opt in; off by default so the tool catalog is unchanged otherwise
352
+ ```
353
+
354
+ `ask_user(question, options?, allow_custom?)` renders `options` as radio buttons, adds a free-text
355
+ field when `allow_custom` is set (or when no options are given), and feeds the chosen or typed
356
+ answer back through the normal frontend-tool path — no new protocol. A **Stop** dismisses an open
357
+ question with an empty answer.
358
+
359
+ The question card is **fully customizable** at three levels:
360
+
361
+ - **Text** — every label is a `strings` key: `askUserAction` (the card's `aria-label`),
362
+ `otherOption`, `answerPlaceholder`, `submit`.
363
+ - **CSS** — every element exposes a `::part()`: `question`, `question-body`, `question-options`,
364
+ `question-choice`, `question-radio`, `question-input`, `question-actions`, `question-button`
365
+ (plus the `--ag-ui-*` theme variables). No shadow piercing.
366
+ - **Full replacement** — set `chat.askUserRenderer` to own the entire UI. Given the parsed request
367
+ and an `AbortSignal` (fired on Stop), render anything — a native modal, a framework component —
368
+ and resolve with the answer (empty string = no answer). The built-in card is bypassed entirely.
369
+
370
+ ```js
371
+ // Level 1+2: restyle the built-in card.
372
+ chat.strings = { submit: "Answer", answerPlaceholder: "Type here…" };
373
+ // ag-ui-chat::part(question) { border-radius: 0; }
374
+
375
+ // Level 3: replace the card with your own UI.
376
+ chat.askUserRenderer = (request, { signal }) =>
377
+ myModal.ask(request.question, request.options, { allowCustom: request.allowCustom, signal });
378
+ ```
379
+
311
380
  ### DOM-driver and animation primitives
312
381
 
313
382
  So the agent can visibly drive the host page, the package ships generic, framework-free
@@ -578,6 +647,56 @@ chat.navigate = (path) => router.push(path); // SPA: in-page, no reload
578
647
 
579
648
  Route map + `navigate()` and the reload model are the same feature seen from two ends.
580
649
 
650
+ ## Resuming a run
651
+
652
+ When the server persists run checkpoints (django-ag-ui's `step_store`), a run
653
+ that stopped part-way can be **continued** rather than restarted. Point the
654
+ component at the run index and a ⭯ button appears in the header:
655
+
656
+ ```html
657
+ <ag-ui-chat endpoint="/agent/" data-runs-url="/agent/runs/"></ag-ui-chat>
658
+ ```
659
+
660
+ The panel lists runs the server marked **continuable** — those with a saved
661
+ snapshot to seed from. A run that never reached a provider-valid boundary has
662
+ none, so it isn't offered: resuming it would start from nothing. Each row shows
663
+ when the run started (the id is on hover, for correlating with server logs) and
664
+ marks a run that branched from another, so a fork doesn't read as a duplicate
665
+ of its parent.
666
+
667
+ Type the next turn in the composer, then pick a row:
668
+
669
+ - **Resume** — continue that run.
670
+ - **Fork** — branch it, leaving the original untouched.
671
+
672
+ Both send to the matching server endpoint and stream into the same transcript.
673
+
674
+ ### One URL, three endpoints
675
+
676
+ `data-runs-url` is the only thing to configure. `resume/<id>/` and `fork/<id>/`
677
+ are siblings of the index — django-ag-ui mounts all three under one prefix
678
+ whenever a step store is set — so they're derived, and there's no way to end up
679
+ with a half-configured set.
680
+
681
+ ### The client contract, handled for you
682
+
683
+ Those endpoints expect a request carrying a **fresh run id** and **only the new
684
+ turn**: the server supplies the prior turns from the snapshot, so re-sending
685
+ them would duplicate the conversation.
686
+
687
+ The component satisfies that structurally rather than by remembering a rule. A
688
+ continuation runs on its own short-lived agent, built pointing at the resume
689
+ endpoint and seeded with **no** history — so "only the new turn" is the only
690
+ thing it *can* send, and the fresh run id comes free because a new agent mints
691
+ one. Your main agent's history is never touched.
692
+
693
+ A resumed run is a normal run in every other respect: frontend tools execute,
694
+ approval interrupts render their card, and `headers` are re-read per request so
695
+ a rotated CSRF token or JWT still reaches the endpoint.
696
+
697
+ If the index can't be reached, the panel shows its empty state rather than an
698
+ error — a history affordance that fails is empty, not broken.
699
+
581
700
  ## File uploads
582
701
 
583
702
  Set **`data-attachments-url`** (django-ag-ui's `AttachmentsView`) to let the user attach files
@@ -692,6 +811,10 @@ re-export point. Internal modules import from leaf paths.
692
811
  | `ClientConversationStore` | type | The persistence seam. |
693
812
  | `ThreadMeta` | type | A thread-drawer row (`{ threadId, title, updatedAt, preview }`). |
694
813
  | `NavigationCheckpoint` | type | The pre-reload checkpoint marker. |
814
+ | `RunIndex` | class | Reads a `data-runs-url` run index and derives its resume / fork endpoints. |
815
+ | `RunRow` | type | One run index row (`{ run_id, thread_id, parent_run_id, started_at, continuable }`). |
816
+ | `CheckpointMenu` | class | The *Continue a run* panel. |
817
+ | `CheckpointVerb` | type | `"resume" | "fork"`. |
695
818
 
696
819
  ### Attachments
697
820
 
@@ -792,14 +915,28 @@ ag-ui-chat::part(tool-card) { font-family: var(--my-mono); }
792
915
 
793
916
  Available parts: `panel`, `header`, `title`, `icon`, `header-controls`, `header-button`
794
917
  (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`
918
+ `answer` (the per-turn group), `thoughts` (plus `thoughts-toggle` / `thoughts-body` /
919
+ `thoughts-label`), `message`
920
+ (plus `message-user` / `message-assistant`), `empty`, `pending`, `stopped` (the "⏹ Stopped" note),
921
+ `tool-card`
797
922
  (plus `tool-card-head` / `-icon` / `-name` / `-status` / `-args` / `-toggle` / `-result`),
798
923
  `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
924
+ `-args` / `-actions` / `-button` / `-cancel` / `-confirm`),
925
+ `approval` (plus `approval-body` / `-actions` / `-button` / `-approve` / `-deny`),
926
+ `question` (plus `question-body` / `-options` / `-choice` / `-choice-text` / `-radio` / `-input` /
927
+ `-actions` / `-button`), `composer`, `input`, `send`,
928
+ `attach-button`, `voice-button`,
929
+ the attachment chips — `attachment-tray` and `attachment-chips` (the read-only chips on sent
930
+ bubbles) with the shared chip parts `attachment-chip` (plus `-icon` / `-name` / `-size` / `-bar` /
931
+ `-bar-fill` / `-retry` / `-remove`),
932
+ the skills UI (`skill-chips`, `skill-chip`, `skill-palette`, `skill-item`, `skill-item-title`,
933
+ `skill-item-desc`, and the missing-placeholder `skill-hint`),
934
+ `launcher`, `launcher-icon`, and the drawer parts
801
935
  (`drawer`, `drawer-backdrop`, `drawer-panel`, `drawer-header`, `drawer-title`, `drawer-new`,
802
- `drawer-list`, `drawer-empty`, `drawer-row`, `drawer-row-select`).
936
+ `drawer-list`, `drawer-empty`, `drawer-row`, `drawer-row-select`, `drawer-row-title`,
937
+ `drawer-row-time`, `drawer-row-preview`, `drawer-row-actions`, `drawer-row-rename`,
938
+ `drawer-row-delete`, `drawer-rename-input`, `drawer-confirm`, `drawer-confirm-label`,
939
+ `drawer-confirm-yes`, `drawer-confirm-no`).
803
940
 
804
941
  Coarse **slots** let you replace whole regions with your own markup (project light-DOM children
805
942
  with a matching `slot=`):