@artooi/ag-ui-web-component 0.23.0 → 0.24.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 (124) hide show
  1. package/CHANGELOG.md +149 -36
  2. package/README.md +140 -9
  3. package/dist/ag-ui-web-component.bundle.js +113 -122
  4. package/dist/ag-ui-web-component.bundle.js.map +3 -3
  5. package/dist/constants.d.ts +71 -85
  6. package/dist/constants.d.ts.map +1 -1
  7. package/dist/core/ag_ui_chat.d.ts +105 -127
  8. package/dist/core/ag_ui_chat.d.ts.map +1 -1
  9. package/dist/core/agui_client.d.ts +24 -30
  10. package/dist/core/agui_client.d.ts.map +1 -1
  11. package/dist/core/attachment.d.ts +9 -14
  12. package/dist/core/attachment.d.ts.map +1 -1
  13. package/dist/core/conversation_store.d.ts +38 -27
  14. package/dist/core/conversation_store.d.ts.map +1 -1
  15. package/dist/core/create_http_agent.d.ts +13 -15
  16. package/dist/core/create_http_agent.d.ts.map +1 -1
  17. package/dist/core/remote_conversation_store.d.ts +10 -9
  18. package/dist/core/remote_conversation_store.d.ts.map +1 -1
  19. package/dist/core/run_index.d.ts +11 -20
  20. package/dist/core/run_index.d.ts.map +1 -1
  21. package/dist/core/transcribe_audio.d.ts +8 -8
  22. package/dist/core/transcribe_audio.d.ts.map +1 -1
  23. package/dist/core/upload_attachment.d.ts +15 -18
  24. package/dist/core/upload_attachment.d.ts.map +1 -1
  25. package/dist/core/utils.d.ts +4 -6
  26. package/dist/core/utils.d.ts.map +1 -1
  27. package/dist/dom/animations.d.ts +22 -30
  28. package/dist/dom/animations.d.ts.map +1 -1
  29. package/dist/dom/dom_driver.d.ts +7 -7
  30. package/dist/dom/native_setter.d.ts +2 -2
  31. package/dist/dom/native_setter.d.ts.map +1 -1
  32. package/dist/index.d.ts +2 -2
  33. package/dist/index.d.ts.map +1 -1
  34. package/dist/index.js +357 -383
  35. package/dist/index.js.map +2 -2
  36. package/dist/skills/fill_template.d.ts +4 -5
  37. package/dist/skills/fill_template.d.ts.map +1 -1
  38. package/dist/skills/parse_skills.d.ts.map +1 -1
  39. package/dist/skills/skill.d.ts +7 -8
  40. package/dist/skills/skill.d.ts.map +1 -1
  41. package/dist/tools/client_tool_registry.d.ts +2 -2
  42. package/dist/tools/page_action_tools.d.ts +7 -10
  43. package/dist/tools/page_action_tools.d.ts.map +1 -1
  44. package/dist/tools/page_state.d.ts +5 -8
  45. package/dist/tools/page_state.d.ts.map +1 -1
  46. package/dist/tools/route_map.d.ts +7 -10
  47. package/dist/tools/route_map.d.ts.map +1 -1
  48. package/dist/ui/approval_card.d.ts +15 -20
  49. package/dist/ui/approval_card.d.ts.map +1 -1
  50. package/dist/ui/attach_copy_buttons.d.ts +4 -10
  51. package/dist/ui/attach_copy_buttons.d.ts.map +1 -1
  52. package/dist/ui/attachment_chips.d.ts +7 -10
  53. package/dist/ui/attachment_chips.d.ts.map +1 -1
  54. package/dist/ui/attachment_tray.d.ts +6 -6
  55. package/dist/ui/checkpoint_menu.d.ts +7 -8
  56. package/dist/ui/checkpoint_menu.d.ts.map +1 -1
  57. package/dist/ui/confirmation_card.d.ts +10 -15
  58. package/dist/ui/confirmation_card.d.ts.map +1 -1
  59. package/dist/ui/question_card.d.ts +12 -15
  60. package/dist/ui/question_card.d.ts.map +1 -1
  61. package/dist/ui/relative_time.d.ts +5 -7
  62. package/dist/ui/relative_time.d.ts.map +1 -1
  63. package/dist/ui/render_markdown.d.ts +8 -8
  64. package/dist/ui/render_markdown.d.ts.map +1 -1
  65. package/dist/ui/resize_handle.d.ts +21 -34
  66. package/dist/ui/resize_handle.d.ts.map +1 -1
  67. package/dist/ui/run_notice.d.ts +5 -7
  68. package/dist/ui/run_notice.d.ts.map +1 -1
  69. package/dist/ui/skills_menu.d.ts +4 -5
  70. package/dist/ui/skills_menu.d.ts.map +1 -1
  71. package/dist/ui/styles.d.ts +1 -1
  72. package/dist/ui/styles.d.ts.map +1 -1
  73. package/dist/ui/thoughts_block.d.ts +9 -11
  74. package/dist/ui/thoughts_block.d.ts.map +1 -1
  75. package/dist/ui/thread_drawer.d.ts +6 -5
  76. package/dist/ui/thread_drawer.d.ts.map +1 -1
  77. package/dist/ui/tool_call_card.d.ts +17 -25
  78. package/dist/ui/tool_call_card.d.ts.map +1 -1
  79. package/dist/ui/ui_strings.d.ts +6 -12
  80. package/dist/ui/ui_strings.d.ts.map +1 -1
  81. package/dist/ui/voice_input.d.ts +10 -11
  82. package/dist/ui/voice_input.d.ts.map +1 -1
  83. package/package.json +1 -1
  84. package/src/constants.ts +74 -87
  85. package/src/core/ag_ui_chat.ts +313 -269
  86. package/src/core/agui_client.ts +60 -71
  87. package/src/core/attachment.ts +9 -14
  88. package/src/core/conversation_store.ts +55 -33
  89. package/src/core/create_http_agent.ts +18 -22
  90. package/src/core/remote_conversation_store.ts +28 -15
  91. package/src/core/run_index.ts +14 -23
  92. package/src/core/transcribe_audio.ts +9 -10
  93. package/src/core/upload_attachment.ts +18 -21
  94. package/src/core/utils.ts +4 -6
  95. package/src/dom/animations.ts +33 -43
  96. package/src/dom/dom_driver.ts +7 -7
  97. package/src/dom/native_setter.ts +11 -12
  98. package/src/index.ts +3 -0
  99. package/src/skills/fill_template.ts +4 -5
  100. package/src/skills/parse_skills.ts +3 -4
  101. package/src/skills/skill.ts +7 -8
  102. package/src/tools/client_tool_registry.ts +2 -2
  103. package/src/tools/page_action_tools.ts +12 -15
  104. package/src/tools/page_state.ts +5 -8
  105. package/src/tools/route_map.ts +15 -19
  106. package/src/ui/approval_card.ts +15 -20
  107. package/src/ui/attach_copy_buttons.ts +9 -18
  108. package/src/ui/attachment_chips.ts +7 -10
  109. package/src/ui/attachment_tray.ts +6 -6
  110. package/src/ui/checkpoint_menu.ts +7 -8
  111. package/src/ui/confirmation_card.ts +10 -15
  112. package/src/ui/question_card.ts +12 -15
  113. package/src/ui/relative_time.ts +5 -7
  114. package/src/ui/render_markdown.ts +25 -51
  115. package/src/ui/resize_handle.ts +25 -38
  116. package/src/ui/run_notice.ts +9 -12
  117. package/src/ui/skills_menu.ts +4 -5
  118. package/src/ui/styles.ts +92 -101
  119. package/src/ui/thoughts_block.ts +11 -13
  120. package/src/ui/thread_drawer.ts +6 -5
  121. package/src/ui/tool_call_card.ts +22 -32
  122. package/src/ui/ui_strings.ts +6 -12
  123. package/src/ui/voice_input.ts +10 -11
  124. package/src/version.ts +1 -1
package/dist/index.js CHANGED
@@ -5,6 +5,7 @@ var TOGGLE_EVENT = "ag-ui-toggle";
5
5
  var UNREAD_EVENT = "ag-ui-unread";
6
6
  var STATE_EVENT = "ag-ui-state";
7
7
  var ATTACHMENT_EVENT = "ag-ui-attachments";
8
+ var RUN_FINISHED_EVENT = "ag-ui-run-finished";
8
9
  var MESSAGE_ROLE = {
9
10
  USER: "user",
10
11
  ASSISTANT: "assistant"
@@ -83,8 +84,8 @@ var ClientToolRegistry = class {
83
84
  /**
84
85
  * Register a tool, replacing any existing one with the same name.
85
86
  *
86
- * Idempotent on the name so re-fired host refs / React StrictMode's
87
- * double-invoke don't throw re-registering is a replace, not an error.
87
+ * Idempotent on the name so a re-fired host ref or React StrictMode's
88
+ * double-invoke replaces rather than throws.
88
89
  */
89
90
  register(tool) {
90
91
  this.#tools.set(tool.name, tool);
@@ -4477,12 +4478,11 @@ var STYLES = `
4477
4478
  only the alias is used by the rules below.
4478
4479
 
4479
4480
  The indirection is what makes ancestor theming work. Declaring the public
4480
- name on :host would set it *on the host element*, and a value on an element
4481
- always beats one inherited from an ancestor \u2014 so a page that put the tokens
4482
- on a wrapper would see no effect at all, which is the one thing the API is
4483
- documented to support. Reading the public name with the default as a var()
4484
- fallback leaves it undeclared on the element, so an ancestor's value is
4485
- inherited normally while a value aimed at the element still wins over it.
4481
+ name on :host would set it on the host element, and a value on an element
4482
+ always beats one inherited from an ancestor, so tokens put on a wrapper
4483
+ would have no effect. Reading the public name with the default as a var()
4484
+ fallback leaves it undeclared on the element, so an ancestor's value
4485
+ inherits normally while one aimed at the element still wins.
4486
4486
 
4487
4487
  Two invariants hold this together:
4488
4488
  1. No rule outside this file's :host blocks may reference a public name
@@ -4509,11 +4509,9 @@ var STYLES = `
4509
4509
  --_border: var(--ag-ui-border, #e2e2ec);
4510
4510
  --_radius: var(--ag-ui-radius, 12px);
4511
4511
 
4512
- /* Body text and raised chrome. Both are referenced by the code-block copy
4513
- button; neither had a default before, so the declarations reading them
4514
- were dropped and the button fell back to the inherited colour over a
4515
- transparent box. The defaults below restate exactly that, so this is a
4516
- rename with no repaint \u2014 see the note on .code-copy. */
4512
+ /* Body text and raised chrome, read only by the code-block copy button.
4513
+ The defaults restate what it inherits, so a host that sets neither sees
4514
+ no repaint \u2014 see the note on .code-copy. */
4517
4515
  --_text: var(--ag-ui-text, var(--_fg));
4518
4516
  --_surface: var(--ag-ui-surface, transparent);
4519
4517
 
@@ -4566,9 +4564,9 @@ var STYLES = `
4566
4564
  --_launcher-inset: var(--ag-ui-launcher-inset, auto 0 0 auto);
4567
4565
 
4568
4566
  /* Motion. One duration and two curves drive every collapse, expand and
4569
- slide-over, so the whole widget accelerates and settles as one thing.
4570
- The default curve is a long-tailed ease-out (a fast start that decelerates
4571
- into place); the pop curve overshoots slightly, for something arriving. */
4567
+ slide-over, so the widget accelerates and settles as one thing. The
4568
+ default curve decelerates into place; the pop curve overshoots slightly,
4569
+ for something arriving. */
4572
4570
  --_motion: var(--ag-ui-motion, 0.28s);
4573
4571
  --_ease: var(--ag-ui-ease, cubic-bezier(0.32, 0.72, 0, 1));
4574
4572
  --_ease-pop: var(--ag-ui-ease-pop, cubic-bezier(0.34, 1.36, 0.64, 1));
@@ -4592,7 +4590,7 @@ var STYLES = `
4592
4590
  /* Reading-column width for placement="page" (full-bleed, centred content). */
4593
4591
  --_content-max-width: var(--ag-ui-content-max-width, 820px);
4594
4592
  /* Slim rail the sidebar placement collapses to. Only that placement reads
4595
- it, but it is declared here so invariant 2 holds for every alias. */
4593
+ it, but it is declared here so every alias has a default in one place. */
4596
4594
  --_rail-width: var(--ag-ui-rail-width, 52px);
4597
4595
 
4598
4596
  position: var(--_position);
@@ -4684,11 +4682,11 @@ var STYLES = `
4684
4682
  --_radius: var(--ag-ui-radius, 0);
4685
4683
  }
4686
4684
 
4687
- /* Page: full-bleed background with a centred reading column. Unlike
4688
- "full" (edge-to-edge, left-aligned messages) the content sits in a column
4689
- capped at --ag-ui-content-max-width. The column is produced by symmetric auto
4690
- padding on the scroll area + composer (no per-row wrapper), so user pills
4691
- still right-align and the assistant well spans the column. */
4685
+ /* Page: full-bleed background with a centred reading column capped at
4686
+ --ag-ui-content-max-width, where "full" is edge-to-edge and left-aligned.
4687
+ The column comes from symmetric auto padding on the scroll area and
4688
+ composer rather than a per-row wrapper, so user pills still right-align and
4689
+ the assistant well spans the column. */
4692
4690
  :host([placement="page"]) {
4693
4691
  --_inset: var(--ag-ui-inset, 0);
4694
4692
  --_width: var(--ag-ui-width, 100vw);
@@ -4706,9 +4704,8 @@ var STYLES = `
4706
4704
  padding-inline: max(12px, calc((100% - var(--_content-max-width)) / 2));
4707
4705
  }
4708
4706
 
4709
- /* The rows between the message list and the composer (skill chips, the
4710
- /-command palette, the missing-placeholder hint, the upload tray) line up
4711
- with the column too \u2014 chips are padding-based, the palette/hint/tray are
4707
+ /* The rows between the message list and the composer line up with the column
4708
+ too. Chips and tray are padding-based while palette and hint are
4712
4709
  margin-based, so each gets its own inline axis nudged by the same gutter. */
4713
4710
  :host([placement="page"]) .skill-chips,
4714
4711
  :host([placement="page"]) .attachment-tray {
@@ -4817,13 +4814,12 @@ var STYLES = `
4817
4814
  visibility var(--_motion) var(--_ease);
4818
4815
  }
4819
4816
 
4820
- /* The launcher grows out of the corner the panel shrank into.
4821
- It is laid out at rest rather than display:none, which is what lets it
4822
- animate in *and* out: an element that was not rendered has no before-change
4823
- style to transition from, and one whose display flips to none cannot
4824
- transition at all. visibility keeps it unpaintable, untabbable and
4825
- unclickable in between -- the expanded panel's own controls sit under it and
4826
- must stay reachable. */
4817
+ /* The launcher grows out of the corner the panel shrank into. It stays laid
4818
+ out at rest rather than display:none, which is what lets it animate both in
4819
+ and out: an unrendered element has no before-change style to transition
4820
+ from, and one flipping display to none cannot transition at all. visibility
4821
+ keeps it unpaintable, untabbable and unclickable in between, so the expanded
4822
+ panel's own controls underneath stay reachable. */
4827
4823
  :host([collapsed]) .launcher {
4828
4824
  opacity: 1;
4829
4825
  transform: none;
@@ -4984,20 +4980,18 @@ var STYLES = `
4984
4980
 
4985
4981
  /* \u2500\u2500 Collapse \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
4986
4982
  Collapsing shrinks the widget to the round floating launcher: the panel
4987
- scales down toward the launcher's corner and fades out, the launcher pops in
4988
- from that same point. Both halves are transform and opacity only, so the
4989
- morph runs on the compositor and never reflows the host page.
4990
-
4991
- The host box keeps its expanded size. Animating it would animate layout, and
4992
- a dragged --ag-ui-width would then fight the launcher's own size. Nothing
4993
- paints there once the panel is gone, so the box only has to stop swallowing
4994
- clicks: pointer events go to none, and the launcher takes them back.
4995
-
4996
- Two placements collapse to something else and restore what they need:
4997
- "sidebar" slides to its rail (below), while "embedded" and "page" keep the
4998
- original header bar \u2014 embedded is laid out by the host page, where a
4999
- floating circle would escape the layout, and page is a full-screen route
5000
- with no corner to float in. */
4983
+ scales toward the launcher's corner and fades, the launcher pops in from the
4984
+ same point. Both halves are transform and opacity only, so the morph runs on
4985
+ the compositor and never reflows the host page.
4986
+
4987
+ The host box keeps its expanded size, since animating it would animate
4988
+ layout and a dragged --ag-ui-width would fight the launcher's own size.
4989
+ Nothing paints there once the panel is gone, so the box only has to stop
4990
+ swallowing clicks: pointer events go to none and the launcher takes them.
4991
+
4992
+ Two placements collapse differently: "sidebar" slides to its rail (below),
4993
+ while "embedded" and "page" keep the header bar, having no corner for a
4994
+ floating circle that would escape the host's layout. */
5001
4995
  :host([collapsed]) {
5002
4996
  pointer-events: none;
5003
4997
  }
@@ -5008,11 +5002,11 @@ var STYLES = `
5008
5002
  visibility: hidden;
5009
5003
  }
5010
5004
 
5011
- /* visibility is what keeps the panel out of the tab order and the a11y tree at
5012
- rest without display:none killing the transition. It interpolates so that any
5013
- progress below 1 still counts as visible: the panel stays on screen for the
5014
- whole collapse and flips hidden exactly at the end, and on expand it is
5015
- visible from the first frame. */
5005
+ /* visibility keeps the panel out of the tab order and the a11y tree at rest
5006
+ without display:none killing the transition. It interpolates so any progress
5007
+ below 1 still counts as visible: the panel stays on screen for the whole
5008
+ collapse, flips hidden exactly at the end, and on expand is visible from the
5009
+ first frame. */
5016
5010
  .chat {
5017
5011
  transform-origin: bottom right;
5018
5012
  transition:
@@ -5039,10 +5033,9 @@ var STYLES = `
5039
5033
  visibility: visible;
5040
5034
  }
5041
5035
 
5042
- /* These two keep the header bar, so the launcher must stay out of the way \u2014
5043
- an embedded host is position: static, which would otherwise leave an
5044
- absolutely-positioned circle to escape the layout entirely and land against
5045
- whatever the page happens to position. */
5036
+ /* These two keep the header bar, so the launcher must stay out of the way: an
5037
+ embedded host is position: static, which would let an absolutely-positioned
5038
+ circle escape the layout and land against whatever the page positions. */
5046
5039
  :host([collapsed]:is([placement="embedded"], [placement="page"])) .launcher {
5047
5040
  visibility: hidden;
5048
5041
  opacity: 0;
@@ -5082,7 +5075,7 @@ var STYLES = `
5082
5075
  and the pending indicator so a whole answer reads (and can be boxed) as one
5083
5076
  unit. A flex column on the message-list gap, stretched to the list width so
5084
5077
  its children keep their own left/right alignment. data-answer-well opts into
5085
- the bordered "well"; without it the layout is today's flat stack. */
5078
+ the bordered "well"; without it the turn renders as a flat stack. */
5086
5079
  .answer {
5087
5080
  display: flex;
5088
5081
  flex-direction: column;
@@ -5208,13 +5201,9 @@ var STYLES = `
5208
5201
  position: relative;
5209
5202
  }
5210
5203
 
5211
- /* This button is the only reader of --ag-ui-surface and --ag-ui-text, and
5212
- neither had a default until the alias layer gave them one. Both references
5213
- used to resolve to nothing, dropping the declaration: the background fell
5214
- back to the initial transparent, and the hover/copied colour to the
5215
- inherited body colour. The defaults chosen upstream (transparent, and the
5216
- body foreground) reproduce that, so the rename repaints nothing. A raised
5217
- surface behind the button is a separate design question. */
5204
+ /* The only reader of --ag-ui-surface and --ag-ui-text. Their defaults are
5205
+ transparent and the body foreground, which is what this button rendered as
5206
+ before either token existed; changing them repaints only this control. */
5218
5207
  .code-copy {
5219
5208
  position: absolute;
5220
5209
  top: 4px;
@@ -5260,12 +5249,10 @@ var STYLES = `
5260
5249
  color: var(--_muted);
5261
5250
  }
5262
5251
 
5263
- /* Markdown tables. table/thead/tbody/tr/th/td are all in
5264
- the sanitizer's ALLOWED_TAGS, so an agent emitting one renders it \u2014 and until
5265
- now rendered it entirely unstyled, overflowing the bubble. A wide table
5266
- scrolls inside its own box rather than stretching the message: the bubble is
5267
- width-constrained, so without this the columns either crush or push the
5268
- layout sideways. */
5252
+ /* Markdown tables. table/thead/tbody/tr/th/td are all in the sanitizer's
5253
+ ALLOWED_TAGS, so an agent can emit one. A wide table must scroll inside its
5254
+ own box rather than stretch the message: the bubble is width-constrained, so
5255
+ without this the columns either crush or push the layout sideways. */
5269
5256
  .message--assistant table {
5270
5257
  display: block;
5271
5258
  width: fit-content;
@@ -5406,18 +5393,28 @@ var STYLES = `
5406
5393
  color: var(--_tool-fg);
5407
5394
  }
5408
5395
 
5396
+ /* Wraps, because the name is the only flexible child and every badge the row
5397
+ gains is taken out of it. An approved call adds a third fixed badge, which in
5398
+ a sidebar-width panel left the name 37px and broke it mid-word. Badges drop to
5399
+ their own row instead. */
5409
5400
  .tool-call-head {
5410
5401
  display: flex;
5402
+ flex-wrap: wrap;
5411
5403
  align-items: center;
5412
5404
  justify-content: space-between;
5413
5405
  gap: 8px;
5414
5406
  }
5415
5407
 
5416
5408
  .tool-call-name {
5417
- flex: 1;
5418
- min-width: 0;
5409
+ /* An auto basis, and a min-width floor rather than zero: the name may shrink,
5410
+ but not below something readable, so wrapping moves a badge instead of
5411
+ shredding a word. Breaking anywhere still applies to a name that cannot fit
5412
+ on a line of its own, which is what keeps a long unbroken tool name inside
5413
+ the card. */
5414
+ flex: 1 1 auto;
5415
+ min-width: 6ch;
5419
5416
  font-weight: 600;
5420
- word-break: break-word;
5417
+ overflow-wrap: anywhere;
5421
5418
  }
5422
5419
 
5423
5420
  /* Leading status icon. Empty in the DOM \u2014 the glyph/spinner is drawn
@@ -5522,8 +5519,6 @@ var STYLES = `
5522
5519
  color: var(--_muted);
5523
5520
  }
5524
5521
 
5525
- /* The record of a human decision on a gated call. An approved call used to
5526
- look exactly like one that was never gated. */
5527
5522
  .skill-item-token {
5528
5523
  font-family: ui-monospace, "SF Mono", Menlo, monospace;
5529
5524
  font-size: 0.92em;
@@ -5531,6 +5526,8 @@ var STYLES = `
5531
5526
  margin-right: 6px;
5532
5527
  }
5533
5528
 
5529
+ /* The lasting record of a human decision on a gated call \u2014 without it an
5530
+ approved call looks exactly like one that was never gated. */
5534
5531
  .tool-call-decision {
5535
5532
  flex: none;
5536
5533
  font-size: 11px;
@@ -5553,10 +5550,8 @@ var STYLES = `
5553
5550
  }
5554
5551
 
5555
5552
  /* Display modes are pure visibility over one DOM shape, selected from the host
5556
- attribute rather than a value stamped on the card when it was built. That is
5557
- what lets a host flip data-tool-display and have every card already on screen
5558
- re-read it, the way data-answer-well behaves. Baking the structure per mode
5559
- meant the setting only reached cards created afterwards.
5553
+ attribute rather than a value stamped on the card at build time, so flipping
5554
+ data-tool-display re-styles cards already on screen. See ToolCallCard.
5560
5555
 
5561
5556
  Default (no attribute) is the full mode: arguments always visible, result
5562
5557
  behind the toggle. */
@@ -5653,20 +5648,19 @@ var STYLES = `
5653
5648
  }
5654
5649
 
5655
5650
  /* Then the measurement corrects it. Which edges are held still belongs to the
5656
- host's layout rather than to placement -- a floating panel a host right-aligns
5657
- is anchored bottom-left, not bottom-right -- so the element measures the edges
5658
- that stay put and stamps them here as "<y>-<x>". Equal specificity to the
5659
- rules above, so source order is what lets the measured value win.
5660
-
5661
- These must not be written as [data-resize-anchor~="left"]: the stamped value
5662
- is one hyphenated token, and ~= matches whitespace-separated words, so such a
5663
- selector cannot ever match. It did not, which left the grip drawn at the
5664
- corner that moves while the cursor pointed along the right diagonal.
5665
-
5666
- Each rule also sets both sides of its axis rather than only the side it
5667
- moves. One that flipped a single way could not undo a placement guess that
5668
- had flipped the other, which put the grip on the anchored corner for an
5669
- embedded panel its host right-aligns. */
5651
+ host's layout, not to placement -- a floating panel a host right-aligns is
5652
+ anchored bottom-left -- so the element measures them and stamps a single
5653
+ hyphenated "<y>-<x>" token here. Equal specificity to the rules above, so
5654
+ source order is what lets the measured value win.
5655
+
5656
+ Two traps, both of which silently draw the grip on the corner that moves:
5657
+
5658
+ Never write these as [data-resize-anchor~="left"]. The stamped value is one
5659
+ hyphenated token and ~= matches whitespace-separated words, so it can never
5660
+ match.
5661
+
5662
+ Each rule must set both sides of its axis, not only the side it moves, or it
5663
+ cannot undo a placement guess that flipped the other way. */
5670
5664
  :host([data-resize-anchor$="-left"]) .resize-handle {
5671
5665
  left: auto;
5672
5666
  right: 0;
@@ -5727,10 +5721,9 @@ var STYLES = `
5727
5721
  }
5728
5722
 
5729
5723
  /* \u2500\u2500 Composer \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
5730
- One surface owns the border, the background and the focus ring; the field and
5731
- its tool row sit inside it. The row used to be four siblings stretched to the
5732
- textarea's height, which gave a paperclip the same visual weight as the field
5733
- and turned Send into a full-height slab. */
5724
+ One surface owns the border, the background and the focus ring; the field
5725
+ and its tool row sit inside it, rather than being siblings stretched to the
5726
+ textarea's height. */
5734
5727
  .input-row {
5735
5728
  display: flex;
5736
5729
  padding: 12px;
@@ -5978,13 +5971,12 @@ var STYLES = `
5978
5971
  }
5979
5972
 
5980
5973
  /* A chip carries its own text colour because it carries its own background.
5981
- The same chip renders in two places with opposite inherited colours: in the
5982
- composer tray it inherits the panel's, but on a sent user bubble it inherits
5983
- the user foreground, which is white on the stock light theme. Against the
5984
- assistant surface underneath that was a 1.13:1 filename \u2014 invisible, and only
5985
- the size stayed legible because it sets its own muted colour. Pair the colour
5986
- with the background it belongs to, and both placements read the same.
5987
- Overridden below for an errored chip, which must keep its red. */
5974
+ The same chip renders in two places with opposite inherited colours: the
5975
+ composer tray gives it the panel's, a sent user bubble gives it the user
5976
+ foreground, which is white on the stock light theme and near-invisible
5977
+ against the chip. Pairing the colour with the background it belongs to makes
5978
+ both placements read alike. Overridden below for an errored chip, which
5979
+ keeps its red. */
5988
5980
  .attachment-chip {
5989
5981
  display: inline-flex;
5990
5982
  align-items: center;
@@ -6678,9 +6670,9 @@ var ThoughtsBlock = class {
6678
6670
  this.#body.textContent = buffer;
6679
6671
  }
6680
6672
  /**
6681
- * Fold the region away — called when the answer's first text token arrives.
6682
- * Idempotent (the per-token text handler calls it repeatedly) and flips the
6683
- * header label from "thinking…" to the settled "Thoughts" affordance.
6673
+ * Fold the region away when the answer's first text token arrives, flipping
6674
+ * the header label to its settled form. Idempotent, since the per-token text
6675
+ * handler calls it repeatedly.
6684
6676
  */
6685
6677
  collapse() {
6686
6678
  if (this.#collapsed) {
@@ -7038,11 +7030,8 @@ var ToolCallCard = class {
7038
7030
  }
7039
7031
  /**
7040
7032
  * Record that a human approved or declined this call, as a line in the card.
7041
- *
7042
- * Approval used to leave no trace at all: a declined call became a tool
7043
- * result saying so, while an approved one simply ran, making the transcript
7044
- * of a gated call byte-identical to one that was never gated. The prompt is
7045
- * gone once answered, so this is where the decision lives.
7033
+ * The prompt disappears once answered, so this is the only lasting trace that
7034
+ * the call was gated at all.
7046
7035
  */
7047
7036
  recordDecision(kind) {
7048
7037
  this.element.setAttribute("data-decision", kind);
@@ -7157,11 +7146,10 @@ var VoiceInput = class {
7157
7146
  this.#recorder?.stop();
7158
7147
  }
7159
7148
  /**
7160
- * Tear the control down the teardown path when the host element is removed
7161
- * mid-recording. Stops any live `MediaRecorder`, releases the mic tracks (so
7162
- * the browser's recording indicator clears), and suppresses the pending
7163
- * transcription: a disconnected control must not fire `onText` back into a
7164
- * detached element.
7149
+ * Tear the control down, for a host element removed mid-recording. Stops any
7150
+ * live `MediaRecorder`, releases the mic tracks so the browser's recording
7151
+ * indicator clears, and suppresses the pending transcription — a
7152
+ * disconnected control must not fire `onText` into a detached element.
7165
7153
  */
7166
7154
  dispose() {
7167
7155
  this.#disposed = true;
@@ -7243,10 +7231,9 @@ var AgUiClient = class {
7243
7231
  #resolveInterrupts;
7244
7232
  #onPersist;
7245
7233
  /**
7246
- * Message ids the server has already closed, so a reuse can be reported.
7247
- *
7248
- * Per client rather than per run: the merge happens across runs, which is the
7249
- * case a per-run set would miss entirely.
7234
+ * Message ids the server has already closed, so a reuse can be reported. Per
7235
+ * client rather than per run — the merge happens across runs, which a per-run
7236
+ * set would miss entirely.
7250
7237
  */
7251
7238
  #closedMessageIds = /* @__PURE__ */ new Set();
7252
7239
  #connectionLostMessage;
@@ -7296,9 +7283,8 @@ var AgUiClient = class {
7296
7283
  * tools (bounded by {@link MAX_TOOL_ROUNDS}).
7297
7284
  *
7298
7285
  * `attachments` ride on the user message as a non-standard field so the
7299
- * default client store round-trips them for history replay; the agent learns
7300
- * the ids from the run context (the server's strict validation ignores the
7301
- * unknown message field), then reads bytes via the `read_attachment` tool.
7286
+ * default store round-trips them for history replay; see
7287
+ * {@link messageAttachments}.
7302
7288
  */
7303
7289
  async send(content, attachments = []) {
7304
7290
  const message = { id: randomUUID2(), role: "user", content };
@@ -7312,7 +7298,7 @@ var AgUiClient = class {
7312
7298
  /**
7313
7299
  * Resume the run loop after a navigating tool's result was supplied
7314
7300
  * post-reload (via {@link addToolResult}). Unlike {@link send}, adds no user
7315
- * message it simply continues the conversation already in history.
7301
+ * message; it continues the conversation already in history.
7316
7302
  */
7317
7303
  async resume() {
7318
7304
  await this.#run();
@@ -7454,9 +7440,9 @@ var AgUiClient = class {
7454
7440
  onActivitySnapshotEvent({ event }) {
7455
7441
  h.onActivity(event.activityType, event.content);
7456
7442
  },
7457
- // Reasoning. `@ag-ui/client` already maps the deprecated
7458
- // THINKING_* events onto these REASONING_* callbacks, so handling the
7459
- // reasoning family alone covers both protocol versions.
7443
+ // `@ag-ui/client` maps the deprecated THINKING_* events onto these
7444
+ // REASONING_* callbacks, so the reasoning family alone covers both
7445
+ // protocol versions.
7460
7446
  onReasoningStartEvent() {
7461
7447
  h.onReasoningStart();
7462
7448
  },
@@ -7508,6 +7494,7 @@ var THREAD_SUFFIX = "thread";
7508
7494
  var THREADS_SUFFIX = "threads";
7509
7495
  var MESSAGES_SUFFIX = "messages:";
7510
7496
  var CHECKPOINT_SUFFIX = "checkpoint:";
7497
+ var MINTED_SUFFIX = "minted:";
7511
7498
  var TITLE_LIMIT = 60;
7512
7499
  var PREVIEW_LIMIT = 100;
7513
7500
  var DEFAULT_TITLE = "New conversation";
@@ -7527,13 +7514,18 @@ var SessionStorageStore = class {
7527
7514
  }
7528
7515
  const id = randomUUID3();
7529
7516
  sessionStorage.setItem(key, id);
7517
+ sessionStorage.setItem(this.#key(MINTED_SUFFIX + id), "1");
7530
7518
  return id;
7531
7519
  }
7520
+ isUnsent(threadId) {
7521
+ return sessionStorage.getItem(this.#key(MINTED_SUFFIX + threadId)) !== null && sessionStorage.getItem(this.#key(MESSAGES_SUFFIX + threadId)) === null;
7522
+ }
7532
7523
  loadMessages(threadId) {
7533
7524
  return Promise.resolve(this.#readJson(this.#key(MESSAGES_SUFFIX + threadId)));
7534
7525
  }
7535
7526
  saveMessages(threadId, messages) {
7536
7527
  sessionStorage.setItem(this.#key(MESSAGES_SUFFIX + threadId), JSON.stringify(messages));
7528
+ sessionStorage.removeItem(this.#key(MINTED_SUFFIX + threadId));
7537
7529
  this.#touchThread(threadId, messages);
7538
7530
  }
7539
7531
  loadCheckpoint(threadId) {
@@ -7550,6 +7542,7 @@ var SessionStorageStore = class {
7550
7542
  clear(threadId) {
7551
7543
  sessionStorage.removeItem(this.#key(MESSAGES_SUFFIX + threadId));
7552
7544
  sessionStorage.removeItem(this.#key(CHECKPOINT_SUFFIX + threadId));
7545
+ sessionStorage.removeItem(this.#key(MINTED_SUFFIX + threadId));
7553
7546
  this.#writeThreads(this.#readThreads().filter((thread) => thread.threadId !== threadId));
7554
7547
  if (sessionStorage.getItem(this.#key(THREAD_SUFFIX)) === threadId) {
7555
7548
  sessionStorage.removeItem(this.#key(THREAD_SUFFIX));
@@ -7611,12 +7604,11 @@ var SessionStorageStore = class {
7611
7604
  return `${this.#root}:${suffix}`;
7612
7605
  }
7613
7606
  /**
7614
- * One-time move of pre-namespacing (`ag-ui-chat:*`) keys into this instance's
7615
- * namespace, so an existing conversation isn't orphaned by the upgrade. Only
7616
- * this store's own keys move (thread pointer, drawer index, per-thread
7617
- * messages/checkpoints) the element's `collapsed`/`theme` keys are left
7618
- * alone. The first namespaced instance to mount adopts the legacy data; a
7619
- * second namespace finds it gone and starts fresh.
7607
+ * One-time move of un-namespaced `ag-ui-chat:*` keys into this instance's
7608
+ * namespace, so an existing conversation isn't orphaned. Only this store's own
7609
+ * keys move — the element's `collapsed` / `theme` keys are left alone. The
7610
+ * first namespaced instance to mount adopts the data; a second namespace
7611
+ * finds it gone and starts fresh.
7620
7612
  */
7621
7613
  #migrateLegacyKeys() {
7622
7614
  const legacyRoot = `${KEY_ROOT}:`;
@@ -7697,12 +7689,10 @@ function createHttpAgent(options) {
7697
7689
  headers: options.headers ?? {},
7698
7690
  initialState: { ...options.initialState ?? {} },
7699
7691
  // HttpAgent invokes its configured fetch as a method (`this.fetch(...)`),
7700
- // which would rebind the global `fetch` to the agent instance and trigger
7701
- // "Illegal invocation" in browsers. Wrap it so `fetch` is always called as
7702
- // a free function with the correct receiver. The wrapper also overlays
7703
- // `getHeaders()` per request, so header rotation (CSRF, short-lived JWT)
7704
- // reaches the stream even though the agent instance is cached, and applies
7705
- // the configured cookie policy — the agent's own config has no seam for it.
7692
+ // rebinding the global `fetch` to the agent instance "Illegal invocation"
7693
+ // in browsers. The wrapper keeps `fetch` a free call, and is also where the
7694
+ // per-request `getHeaders()` overlay and the cookie policy go, the agent's
7695
+ // own config having no seam for either.
7706
7696
  fetch: (url, init) => {
7707
7697
  const fresh = options.getHeaders?.();
7708
7698
  if (fresh === void 0) {
@@ -7715,7 +7705,7 @@ function createHttpAgent(options) {
7715
7705
  return fetch(url, withCredentials({ ...init, headers }, options.credentials));
7716
7706
  },
7717
7707
  // Spread conditionally: under `exactOptionalPropertyTypes` an explicit
7718
- // `undefined` is not assignable to these optional config fields.
7708
+ // `undefined` is not assignable to an optional field.
7719
7709
  ...options.threadId !== void 0 ? { threadId: options.threadId } : {},
7720
7710
  ...options.initialMessages !== void 0 ? { initialMessages: [...options.initialMessages] } : {}
7721
7711
  });
@@ -7741,6 +7731,10 @@ var RemoteConversationStore = class {
7741
7731
  setActiveThread(threadId) {
7742
7732
  this.#local.setActiveThread(threadId);
7743
7733
  }
7734
+ /** Delegated, so wrapping a store does not lose what it knows about its own ids. */
7735
+ isUnsent(threadId) {
7736
+ return this.#local.isUnsent?.(threadId) === true;
7737
+ }
7744
7738
  saveMessages(threadId, messages) {
7745
7739
  this.#local.saveMessages(threadId, messages);
7746
7740
  }
@@ -7768,6 +7762,9 @@ var RemoteConversationStore = class {
7768
7762
  return rows.filter((row) => !this.#dropped.has(row.thread_id)).map((row) => this.#toMeta(row));
7769
7763
  }
7770
7764
  async loadMessages(threadId) {
7765
+ if (this.#local.isUnsent?.(threadId) === true) {
7766
+ return null;
7767
+ }
7771
7768
  const response = await this.#get(`${this.#url}${encodeURIComponent(threadId)}/`);
7772
7769
  if (response === null || !response.ok) {
7773
7770
  return this.#local.loadMessages(threadId);
@@ -7801,9 +7798,9 @@ var RemoteConversationStore = class {
7801
7798
  return {
7802
7799
  threadId: row.thread_id,
7803
7800
  title: this.#renamed.get(row.thread_id) ?? row.title,
7804
- // `null` or an unparseable date both become `NaN` (Date.parse's own
7805
- // signal), which `relativeTime` renders as a neutral label rather than
7806
- // "~2950w ago" (epoch 0) or "NaNw ago".
7801
+ // `null` and an unparseable date both become NaN, Date.parse's own
7802
+ // signal, which `relativeTime` renders as a neutral label rather than an
7803
+ // epoch-0 or NaN duration.
7807
7804
  updatedAt: row.updated_at === null ? Number.NaN : Date.parse(row.updated_at),
7808
7805
  preview: row.preview
7809
7806
  };
@@ -7847,10 +7844,9 @@ var RunIndex = class {
7847
7844
  this.#credentials = credentials;
7848
7845
  }
7849
7846
  /**
7850
- * The user's runs, or `[]` when the endpoint is unreachable or answers with
7851
- * an error. A history affordance that cannot load is empty, never broken:
7852
- * the caller renders its empty state rather than surfacing a transport fault
7853
- * the user can do nothing about.
7847
+ * The user's runs, or `[]` when the endpoint is unreachable or errors — the
7848
+ * caller renders its empty state rather than a transport fault the user can
7849
+ * do nothing about.
7854
7850
  */
7855
7851
  async list() {
7856
7852
  try {
@@ -7888,9 +7884,9 @@ var RunIndex = class {
7888
7884
  /**
7889
7885
  * `<mount>/<verb>/<runId>/`, derived from the index URL's own prefix.
7890
7886
  *
7891
- * Built by string surgery on the trailing `runs/` rather than with `new URL`,
7892
- * because the configured value may be root-relative (`/agent/runs/`) the
7893
- * common case in a Django template and `new URL` needs an absolute base.
7887
+ * String surgery on the trailing `runs/` rather than `new URL`, because the
7888
+ * configured value may be root-relative (`/agent/runs/`), the common case in
7889
+ * a Django template, and `new URL` needs an absolute base.
7894
7890
  */
7895
7891
  #sibling(verb, runId) {
7896
7892
  const prefix = this.#url.slice(0, -"runs/".length);
@@ -8025,29 +8021,23 @@ var AgUiChat = class extends HTMLElement {
8025
8021
  /** Agent factory; override to inject a custom or fake agent (tests). */
8026
8022
  agentFactory = createHttpAgent;
8027
8023
  /**
8028
- * Static extra HTTP headers, sent with **every** request this element makes
8029
- * the agent run, the thread index and its messages, the tool and skill
8030
- * catalogs, the run index, uploads and transcription.
8024
+ * Static extra HTTP headers, sent with every request this element makes: the
8025
+ * agent run, the thread index and its messages, the tool and skill catalogs,
8026
+ * the run index, uploads and transcription.
8031
8027
  *
8032
- * Right for values fixed for the element's lifetime. A credential that
8033
- * rotates (a short-lived JWT, a re-issued CSRF token) belongs in
8034
- * {@link getHeaders} instead: this is read at request time, but only a
8035
- * re-assignment updates it, so a token captured here is pinned until the host
8036
- * remembers to assign again.
8028
+ * For values fixed for the element's lifetime. A rotating credential belongs
8029
+ * in {@link getHeaders} instead — only a re-assignment updates this, so a
8030
+ * token captured here is pinned until the host assigns again.
8037
8031
  */
8038
8032
  headers = {};
8039
8033
  /**
8040
- * Live header source, consulted immediately before every request — the way to
8041
- * supply rotating credentials.
8042
- *
8043
- * Set it to a function and each request calls it afresh: a token refreshed by
8044
- * the host between two requests reaches the second one, with nothing to
8045
- * re-assign and nothing to keep in sync.
8034
+ * Live header source, called afresh immediately before every request — the
8035
+ * way to supply rotating credentials, with nothing to re-assign or keep in
8036
+ * sync.
8046
8037
  *
8047
- * Composes with {@link headers} rather than replacing it: the two are merged
8048
- * per key with `getHeaders()` winning, so a static `X-Client` and a rotating
8049
- * `Authorization` can be configured independently and neither silently drops
8050
- * the other.
8038
+ * Composes with {@link headers} rather than replacing it: merged per key with
8039
+ * `getHeaders()` winning, so a static `X-Client` and a rotating
8040
+ * `Authorization` are configured independently and neither drops the other.
8051
8041
  */
8052
8042
  getHeaders = null;
8053
8043
  /**
@@ -8061,37 +8051,31 @@ var AgUiChat = class extends HTMLElement {
8061
8051
  autoConfirm = false;
8062
8052
  /**
8063
8053
  * When true, the built-in `ask_user` frontend tool is offered to the agent:
8064
- * calling it renders an inline question card (radio choices and/or a free-text
8065
- * field) and returns the user's answer. Off by default — like the other
8066
- * built-in tool groups (route / page-action), it is opt-in so it doesn't
8067
- * change the advertised catalog until a host asks for it.
8054
+ * calling it renders an inline question card and returns the user's answer.
8055
+ * Off by default, like the other built-in tool groups, so the advertised
8056
+ * catalog does not change until a host asks for it.
8068
8057
  */
8069
8058
  askUser = false;
8070
8059
  /**
8071
- * Optional full replacement for the `ask_user` question UI. When set, calling
8072
- * `ask_user` invokes this instead of the built-in inline card: the host
8073
- * renders whatever it likes (a native modal, a framework component, …) and
8074
- * resolves with the answer. Unset (default) uses the built-in
8075
- * {@link requestQuestion} card — style that via the `strings` override and the
8076
- * `question*` CSS `::part()`s. Requires {@link askUser} to be enabled.
8060
+ * Optional full replacement for the `ask_user` question UI, resolving with
8061
+ * the answer; the same seam as {@link approvalRenderer}, styled via `strings`
8062
+ * and the `question*` `::part()`s when left unset. Requires {@link askUser}.
8077
8063
  */
8078
8064
  askUserRenderer = null;
8079
8065
  /**
8080
- * Optional full replacement for the server-side-tool approval UI. When set, an
8081
- * approval interrupt invokes this instead of the built-in inline approval
8082
- * card: the host renders whatever it likes and resolves `true` to approve /
8083
- * `false` to deny. Unset (default) uses the built-in {@link requestApproval}
8084
- * card style that via the `strings` override and the `approval*` CSS
8085
- * `::part()`s. The gate itself is enabled server-side; this only changes how
8086
- * the decision is collected.
8066
+ * Optional full replacement for the server-side-tool approval UI: an approval
8067
+ * interrupt invokes this instead of the built-in {@link requestApproval}
8068
+ * card, resolving `true` to approve or `false` to deny. Style the built-in
8069
+ * card via `strings` and the `approval*` `::part()`s instead. The gate itself
8070
+ * is enabled server-side; this only changes how the decision is collected.
8087
8071
  */
8088
8072
  approvalRenderer = null;
8089
8073
  /**
8090
- * Optional per-call confirmation predicate. When set, it is authoritative:
8091
- * given a tool name + args it decides whether *this* call needs confirmation
8092
- * (so one tool can be instant for some args and confirmed for others — what a
8093
- * static `x-destructive` flag can't express). When unset, the `x-destructive`
8094
- * schema flag is used. `autoConfirm` short-circuits both.
8074
+ * Optional per-call confirmation predicate. When set it is authoritative,
8075
+ * deciding from the tool name and args whether this particular call needs
8076
+ * confirmation — so one tool can be instant for some args and confirmed for
8077
+ * others, which a static `x-destructive` flag cannot express. When unset the
8078
+ * `x-destructive` flag decides. `autoConfirm` short-circuits both.
8095
8079
  */
8096
8080
  confirmPredicate = null;
8097
8081
  /**
@@ -8112,11 +8096,8 @@ var AgUiChat = class extends HTMLElement {
8112
8096
  * Per-run context provider. Defaults to the compact page map, when a
8113
8097
  * {@link getPageMap} provider is set and {@link autoInjectPageMap} is on.
8114
8098
  *
8115
- * It used to append a one-line manifest of the message's attachments too.
8116
- * The server now derives that manifest from the refs riding the messages, so
8117
- * the client's copy only duplicated it, on exactly the turn a file was
8118
- * attached. Attachments still reach the agent; they reach it through the
8119
- * message, which is where they were already.
8099
+ * Attachments are deliberately not restated here: the server derives its own
8100
+ * manifest from the refs riding the messages.
8120
8101
  */
8121
8102
  getContext = () => [
8122
8103
  ...createPageMapContext(this.getPageMap, this.autoInjectPageMap)
@@ -8144,21 +8125,17 @@ var AgUiChat = class extends HTMLElement {
8144
8125
  conversationStore = new SessionStorageStore();
8145
8126
  /**
8146
8127
  * How attached files are uploaded. `null` (default) uses the built-in
8147
- * multipart `POST` to `data-attachments-url`. Set a custom
8148
- * {@link UploadHandler} `(file, onProgress) => Promise<AttachmentRef>` to
8149
- * swap the transport (e.g. a `tus-js-client` resumable adapter or
8150
- * direct-to-S3 multipart) without changing the tray, the chips, or the AG-UI
8151
- * wire (refs are transport-agnostic). When set, the 📎 affordance appears even
8152
- * with no `data-attachments-url`; the handler owns its own endpoint + headers.
8128
+ * multipart `POST` to `data-attachments-url`; a custom {@link UploadHandler}
8129
+ * swaps the transport without changing the tray, the chips, or the AG-UI
8130
+ * wire. When set, the 📎 affordance appears even with no
8131
+ * `data-attachments-url`, and the handler owns its own endpoint and headers.
8153
8132
  */
8154
8133
  uploadHandler = null;
8155
8134
  /**
8156
8135
  * How recorded voice clips are transcribed. `null` (default) POSTs the clip
8157
- * to `data-transcribe-url` (django-ag-ui's `TranscribeView`). Set a custom
8158
- * {@link TranscribeHandler} `(audio: Blob) => Promise<string>` to swap the
8159
- * transport (a different STT endpoint, a browser Web Speech adapter) without
8160
- * touching the mic button. When set, the 🎤 affordance appears even with no
8161
- * `data-transcribe-url`.
8136
+ * to `data-transcribe-url`; a custom {@link TranscribeHandler} swaps the
8137
+ * transport without touching the mic button. When set, the 🎤 affordance
8138
+ * appears even with no `data-transcribe-url`.
8162
8139
  */
8163
8140
  transcribeHandler = null;
8164
8141
  /**
@@ -8177,12 +8154,10 @@ var AgUiChat = class extends HTMLElement {
8177
8154
  */
8178
8155
  skillContext = () => ({});
8179
8156
  /**
8180
- * Friendly display labels for tool-call cards, keyed by tool name (e.g.
8181
- * `{ list_projects: "Search projects" }`). Used as a fallback when a tool has
8182
- * no `x-summary` in its own schema chiefly **server-side tools** (drf-mcp,
8183
- * `@tool` registry), whose schema never reaches the browser (AG-UI streams
8184
- * only the tool-call name). Client tools should prefer `x-summary` on their
8185
- * schema; this map is the seam for everything else.
8157
+ * Friendly display labels for tool-call cards, keyed by tool name. The
8158
+ * fallback when a tool has no `x-summary` in its own schema, which chiefly
8159
+ * means server-side tools: AG-UI streams only the tool-call name, so their
8160
+ * schema never reaches the browser. Client tools should prefer `x-summary`.
8186
8161
  */
8187
8162
  toolSummaries = {};
8188
8163
  /**
@@ -8216,6 +8191,12 @@ var AgUiChat = class extends HTMLElement {
8216
8191
  * the real output with the generic "executed on the server" fallback.
8217
8192
  */
8218
8193
  #serverSettled = /* @__PURE__ */ new Set();
8194
+ /**
8195
+ * Tool calls made during the current interaction, in the order they started,
8196
+ * so {@link RUN_FINISHED_EVENT} can report them once the whole thing settles.
8197
+ * Spans tool rounds and an approval interrupt; cleared when the event fires.
8198
+ */
8199
+ #runTools = [];
8219
8200
  #root;
8220
8201
  #chat;
8221
8202
  #messages;
@@ -8354,13 +8335,10 @@ var AgUiChat = class extends HTMLElement {
8354
8335
  * Continue `runId` as a **new** run, seeded server-side from its snapshot.
8355
8336
  *
8356
8337
  * Uses a short-lived agent pointed at the resume / fork endpoint and seeded
8357
- * with **no** history, so the request carries only the turn typed here the
8358
- * contract those endpoints assume, since the server supplies the prior turns
8359
- * from the snapshot and re-sending them would duplicate. Building a separate
8360
- * agent makes that structural: the main agent keeps its own history, and
8361
- * "only the new turn" can't be got wrong by forgetting to clear it. The
8362
- * fresh `run_id` the endpoints also require comes free — a new agent mints
8363
- * one per run.
8338
+ * with no history, because those endpoints supply the prior turns from the
8339
+ * snapshot and re-sending them would duplicate. A separate agent makes that
8340
+ * structural the main agent keeps its own history and mints the fresh
8341
+ * `run_id` the endpoints also require.
8364
8342
  *
8365
8343
  * Handlers are the element's own, so the continuation streams into the same
8366
8344
  * transcript the user is looking at.
@@ -8441,10 +8419,8 @@ var AgUiChat = class extends HTMLElement {
8441
8419
  * when the server streams `STATE_SNAPSHOT` / `STATE_DELTA`. Assigning seeds
8442
8420
  * the next run; reading returns whatever the agent last applied.
8443
8421
  *
8444
- * Listen for {@link STATE_EVENT} to react to server-driven changes.
8445
- *
8446
- * Not to be confused with {@link registerPageState}, which exposes host
8447
- * state to the agent as ordinary *tools*.
8422
+ * Listen for {@link STATE_EVENT} to react to server-driven changes. Distinct
8423
+ * from {@link registerPageState}, which exposes host state as ordinary tools.
8448
8424
  */
8449
8425
  get sharedState() {
8450
8426
  return this.#client?.state ?? this.#sharedState;
@@ -8460,11 +8436,9 @@ var AgUiChat = class extends HTMLElement {
8460
8436
  }
8461
8437
  }
8462
8438
  /**
8463
- * @deprecated Renamed to {@link registerPageState}. The old name read as
8464
- * AG-UI shared-state sync (`STATE_SNAPSHOT` / `STATE_DELTA`), which this
8465
- * component does not implement — these are ordinary client tools over host
8466
- * page state. Behaviour is unchanged; this alias will be removed in a future
8467
- * major.
8439
+ * @deprecated Renamed to {@link registerPageState} the old name read as
8440
+ * AG-UI shared-state sync. Behaviour is unchanged; the alias will be removed
8441
+ * in a future major.
8468
8442
  */
8469
8443
  registerStateHook(binding) {
8470
8444
  this.registerPageState(binding);
@@ -8531,10 +8505,9 @@ var AgUiChat = class extends HTMLElement {
8531
8505
  /**
8532
8506
  * The built-in `ask_user` frontend tool, or `[]` when {@link askUser} is off.
8533
8507
  *
8534
- * A generic "ask the user a typed question" primitive: the agent calls it, the
8535
- * client executes it locally via the normal frontend-tool path (rendering a
8536
- * {@link requestQuestion} card), and the chosen/typed answer flows back as the
8537
- * tool result — no new protocol, reusing the machinery already in place.
8508
+ * The agent calls it, the client executes it locally through the normal
8509
+ * frontend-tool path by rendering a {@link requestQuestion} card, and the
8510
+ * answer flows back as the tool result. No new protocol.
8538
8511
  */
8539
8512
  #askUserTool() {
8540
8513
  if (!this.askUser) {
@@ -8610,19 +8583,18 @@ var AgUiChat = class extends HTMLElement {
8610
8583
  * `credentials` mode (`"omit"` / `"same-origin"` / `"include"`). Mirrored to
8611
8584
  * the `credentials` attribute, so markup embeds can set it without script.
8612
8585
  *
8613
- * `null` (the default) leaves the browser's default of `same-origin` in
8614
- * place. That default sends **no cookies at all** when the endpoints live on
8615
- * a different origin from the page app.example.com calling
8616
- * api.example.com is cross-origin and the request goes out anonymously
8617
- * rather than failing, so the symptom is a 401 from a server that looks
8618
- * correctly configured. A cookie-authenticated cross-origin deployment wants
8619
- * `"include"`, plus `Access-Control-Allow-Credentials: true` and a concrete
8620
- * (non-wildcard) `Access-Control-Allow-Origin` on the server.
8586
+ * `null` (the default) leaves the browser's `same-origin` default in place,
8587
+ * which sends no cookies at all to an endpoint on a different origin — and
8588
+ * the request goes out anonymously rather than failing, so the symptom is a
8589
+ * 401 from a server that looks correctly configured. A cookie-authenticated
8590
+ * cross-origin deployment wants `"include"`, plus
8591
+ * `Access-Control-Allow-Credentials: true` and a concrete, non-wildcard
8592
+ * `Access-Control-Allow-Origin` on the server.
8621
8593
  *
8622
8594
  * Read per request, so a late assignment applies to everything after it.
8623
- * `"omit"` cannot be honoured by the built-in **upload** transport, which is
8624
- * an `XMLHttpRequest` and only has a two-state cookie switch; every other
8625
- * endpoint honours all three modes.
8595
+ * `"omit"` cannot be honoured by the built-in upload transport, an
8596
+ * `XMLHttpRequest` with only a two-state cookie switch; every other endpoint
8597
+ * honours all three modes.
8626
8598
  */
8627
8599
  get credentials() {
8628
8600
  const attr = this.getAttribute("credentials");
@@ -8723,25 +8695,21 @@ var AgUiChat = class extends HTMLElement {
8723
8695
  * The catalog requests the element issues on startup: the tool labels
8724
8696
  * (`data-tools-url`) and the backend skills (`data-skills-url`).
8725
8697
  *
8726
- * Deliberately one microtask behind `connectedCallback`. A host that
8727
- * configures the element through a framework ref necessarily does so *after*
8728
- * inserting the node React attaches refs and runs layout effects in the
8729
- * same commit as the insertion, but strictly afterwards so a request issued
8730
- * from `connectedCallback` itself goes out before `headers`,
8731
- * {@link getHeaders} or {@link credentials} exist, and comes back 401 in a
8732
- * way that reads as a server fault rather than a mis-timed assignment. A
8733
- * microtask lands after that commit and still before paint.
8698
+ * Deliberately one microtask behind `connectedCallback`. A framework ref is
8699
+ * attached after the node is inserted but within the same commit, so a
8700
+ * request issued from `connectedCallback` itself goes out before `headers`,
8701
+ * {@link getHeaders} or {@link credentials} exist and 401s in a way that
8702
+ * reads as a server fault. A microtask lands after that commit, still before
8703
+ * paint.
8734
8704
  *
8735
- * Two things it is **not**. It is not a fix for configuration that arrives
8736
- * later than the commit (a passive `useEffect`, an awaited token fetch):
8737
- * configure before insertion (`createElement` configure `append`) or call
8738
- * {@link reload} once configured, because a longer timer would hide that race
8739
- * rather than close it. And it deliberately excludes the *history* replay,
8740
- * which stays in `connectedCallback`: the replay renders into the transcript,
8741
- * so deferring it lets a `sendMessage()` issued in the same task land first
8742
- * and the replay then duplicate it. The thread history is therefore the one
8743
- * request that can still go out before a ref is attached — {@link reload}
8744
- * covers it.
8705
+ * It is not a fix for configuration arriving later than the commit (a passive
8706
+ * effect, an awaited token fetch): configure before insertion, or call
8707
+ * {@link reload}, since a longer timer would hide that race rather than close
8708
+ * it.
8709
+ *
8710
+ * The history replay stays in `connectedCallback` on purpose. It renders into
8711
+ * the transcript, so deferring it would let a `sendMessage()` in the same
8712
+ * task land first and be duplicated by the replay.
8745
8713
  */
8746
8714
  #startup() {
8747
8715
  if (!this.#connected) {
@@ -8755,15 +8723,13 @@ var AgUiChat = class extends HTMLElement {
8755
8723
  * the backend skill catalog and the thread's history — with the transport
8756
8724
  * configuration as it stands now.
8757
8725
  *
8758
- * This is the answer for a host that can only configure the element after the
8759
- * fact (a token fetched in a passive effect, an async auth handshake): the
8760
- * startup requests already went out with whatever was set then, and this says
8761
- * "try again, properly authenticated" without removing and re-inserting the
8762
- * node.
8726
+ * For a host that can only configure the element after the fact (a token
8727
+ * fetched in a passive effect, an async auth handshake), this re-issues the
8728
+ * startup requests authenticated, without removing and re-inserting the node.
8763
8729
  *
8764
- * A reload, not a merge the in-flight run is cancelled and the transcript
8765
- * is rebuilt from the persisted history, so anything streamed since is
8766
- * dropped. Call it once, when configuration lands; not between turns.
8730
+ * A reload, not a merge: the in-flight run is cancelled and the transcript is
8731
+ * rebuilt from persisted history, so anything streamed since is dropped. Call
8732
+ * it once, when configuration lands, not between turns.
8767
8733
  */
8768
8734
  async reload() {
8769
8735
  this.#cancelRun();
@@ -8772,12 +8738,10 @@ var AgUiChat = class extends HTMLElement {
8772
8738
  await Promise.all([this.#fetchToolCatalog(), this.#fetchSkills(), this.#rehydrate()]);
8773
8739
  }
8774
8740
  /**
8775
- * Tear down live resources when the element leaves the DOM (a removed node, a
8776
- * client-side route swap): cancel the in-flight run so its SSE stream closes,
8777
- * abort any in-flight uploads so they don't orphan server-side files, and
8778
- * release the mic so the browser's recording indicator clears. Without this a
8779
- * removed `<ag-ui-chat>` leaks a streaming request, uploads, and a live
8780
- * `MediaRecorder`.
8741
+ * Tear down live resources when the element leaves the DOM: cancel the
8742
+ * in-flight run so its stream closes, abort in-flight uploads so they do not
8743
+ * orphan server-side files, and release the mic so the browser's recording
8744
+ * indicator clears. Without this a removed element leaks all three.
8781
8745
  */
8782
8746
  disconnectedCallback() {
8783
8747
  this.#connected = false;
@@ -8788,14 +8752,10 @@ var AgUiChat = class extends HTMLElement {
8788
8752
  /**
8789
8753
  * Read an opt-in flag attribute the way HTML reads a boolean attribute.
8790
8754
  *
8791
- * Present means on bare (`data-prompt-chips`), empty
8792
- * (`data-prompt-chips=""`) or any value except the literal `"false"`. These
8793
- * were compared against the string `"true"`, so writing the attribute bare —
8794
- * the spelling every native boolean attribute uses, and the one a reader
8795
- * reaches for first — silently *disabled* the feature it names, with nothing
8796
- * to indicate why the chips never appeared.
8797
- *
8798
- * `="false"` still turns it off, so an explicit opt-out keeps working.
8755
+ * Present means on: bare (`data-prompt-chips`), empty (`=""`), or any value
8756
+ * except the literal `"false"`. Comparing against `"true"` instead would make
8757
+ * the bare spelling every native boolean attribute uses silently disable the
8758
+ * feature it names. `="false"` still turns it off.
8799
8759
  */
8800
8760
  #flag(name) {
8801
8761
  const value = this.getAttribute(name);
@@ -9021,21 +8981,16 @@ var AgUiChat = class extends HTMLElement {
9021
8981
  /**
9022
8982
  * Act on a picked skill.
9023
8983
  *
9024
- * A skill that ships no `prompt` is **server-resolved**: the catalog carries
9025
- * only its name and label, and picking it sends the bare `/name` token for
9026
- * the agent to expandfrom the harness `Skills` capability, or from the
9027
- * server's own instructions. That is the shape to prefer, because the prompt
9028
- * then never reaches the browser at all: a skill is often where a project's
9029
- * internal workflow is written down most plainly, and a catalog endpoint is a
9030
- * plain GET.
8984
+ * A skill with no `prompt` is server-resolved: picking it sends the bare
8985
+ * `/name` token for the agent to expand, so the wording never reaches the
8986
+ * browser. Prefer that shapea skill often states a project's internal
8987
+ * workflow most plainly, and a catalog endpoint is a plain GET.
9031
8988
  *
9032
- * A skill that does carry a `prompt` keeps the older behaviour the client
9033
- * fills its `{placeholder}`s from the page and sends (or pre-fills) the text.
9034
- * Right for a user-facing convenience, and for placeholders only the page can
9035
- * supply.
8989
+ * A skill carrying a `prompt` has the client fill its `{placeholder}`s from
8990
+ * the page instead, which is right for placeholders only the page can supply.
9036
8991
  *
9037
- * Either way a pick now **sends**, rather than parking text in the composer
9038
- * for a second click; `sendImmediately: false` opts back into pre-filling.
8992
+ * Either way a pick sends; `sendImmediately: false` opts into pre-filling the
8993
+ * composer instead.
9039
8994
  */
9040
8995
  #applySkill(skill) {
9041
8996
  if (skill.prompt === void 0) {
@@ -9147,18 +9102,13 @@ var AgUiChat = class extends HTMLElement {
9147
9102
  }
9148
9103
  }
9149
9104
  /**
9150
- * Which edges the layout is holding still, by measuring rather than guessing.
9151
- *
9152
- * A resize has to be computed from the edge that does not move, and which
9153
- * edge that is belongs to the **host's layout**, not to `placement`: a
9154
- * floating panel is pinned bottom-right, while an embedded one goes wherever
9155
- * the page's own CSS puts it — flex-start, flex-end, a grid cell. Mapping
9156
- * placement to a corner got this wrong for any host that right-aligns the
9157
- * element, and the symptom is bad enough to read as a broken control: the
9158
- * panel shrinks when dragged outward, travelling by its opposite corner.
9105
+ * Which edges the layout is holding still, by measuring rather than guessing:
9106
+ * nudge the size by a pixel, see which edges stayed put, and undo. One forced
9107
+ * reflow per drag.
9159
9108
  *
9160
- * So: nudge the size by a pixel, see which edges stayed put, and undo. One
9161
- * forced reflow per drag, which is cheap next to being wrong.
9109
+ * `placement` cannot answer this an embedded panel goes wherever the page's
9110
+ * CSS puts it and see {@link createResizeHandle} for why guessing produces
9111
+ * a visibly broken control.
9162
9112
  */
9163
9113
  #measureAnchor() {
9164
9114
  const before = this.getBoundingClientRect();
@@ -9192,14 +9142,13 @@ var AgUiChat = class extends HTMLElement {
9192
9142
  /**
9193
9143
  * Write a dragged size onto the host, on the axes this placement leaves free.
9194
9144
  *
9195
- * Writing the custom property rather than inline `width` / `height` does
9196
- * **not** by itself leave placement in charge an inline custom property
9197
- * still outranks a `:host([placement=…])` rule setting the same property, so
9198
- * a height dragged while floating capped a docked sidebar that had asked for
9199
- * `100vh`. The cascade cannot arbitrate this; the axis check has to.
9200
- *
9201
- * So the rule is explicit: a placement owns the axes it fixes, and a
9202
- * persisted size is only ever applied to the ones it does not.
9145
+ * Writing the custom property rather than inline `width` / `height` does not
9146
+ * by itself leave placement in charge: an inline custom property still
9147
+ * outranks a `:host([placement=…])` rule setting the same property, so a
9148
+ * height dragged while floating would cap a docked sidebar asking for
9149
+ * `100vh`. The cascade cannot arbitrate this, so the axis check must — a
9150
+ * placement owns the axes it fixes, and a persisted size is applied only to
9151
+ * the ones it leaves free.
9203
9152
  */
9204
9153
  #applySize(size) {
9205
9154
  const axis = this.#resizeAxis();
@@ -9272,12 +9221,10 @@ var AgUiChat = class extends HTMLElement {
9272
9221
  * Open the thread-history drawer: the imperative route to the control that
9273
9222
  * renders as `::part(history-button)`.
9274
9223
  *
9275
- * A host that hides `::part(header)` to render its own title bar hides the
9276
- * history, new-chat and collapse buttons with it and thread switching then
9277
- * has no route at all, because those controls live inside the header. Each of
9278
- * them has a method, so a host chrome can rebuild the set: this one,
9279
- * {@link openCheckpoints}, {@link newChat}, {@link toggleCollapsed} and
9280
- * {@link toggleTheme}.
9224
+ * A host that hides `::part(header)` for its own title bar hides the history,
9225
+ * new-chat and collapse buttons with it. Each has a method so that chrome can
9226
+ * be rebuilt: this one, {@link openCheckpoints}, {@link newChat},
9227
+ * {@link toggleCollapsed} and {@link toggleTheme}.
9281
9228
  */
9282
9229
  openThreads() {
9283
9230
  void this.#refreshDrawer();
@@ -9378,23 +9325,17 @@ var AgUiChat = class extends HTMLElement {
9378
9325
  /**
9379
9326
  * Notice a previous run that never produced a response.
9380
9327
  *
9381
- * {@link AgUiClient.send} persists the user's message *before* starting the
9382
- * run, so a transcript whose last entry is that user message means nothing
9383
- * ever came back: the page navigated or reloaded mid-run, the tab closed, or
9384
- * the process died. No extra persistence is needed to detect it — the shape
9385
- * of the transcript already says so, which is why this needs no
9386
- * {@link ClientConversationStore} method and no `pagehide` listener (neither
9387
- * of which fires on a crash or a force-quit anyway).
9328
+ * {@link AgUiClient.send} persists the user's message before starting the
9329
+ * run, so a transcript ending on that user message means nothing came back.
9330
+ * The transcript's shape alone detects it, needing no store method and no
9331
+ * `pagehide` listener neither of which fires on a crash or force-quit.
9388
9332
  *
9389
- * The *agent*-initiated reload is not this case: a navigating tool leaves a
9390
- * checkpoint and resumes, which is why the caller returns early on one before
9391
- * reaching here.
9333
+ * An agent-initiated reload is not this case: a navigating tool leaves a
9334
+ * checkpoint and resumes, so the caller returns early on one.
9392
9335
  *
9393
- * Deliberately a notice and never a resume. AG-UI has no
9394
- * resume-an-aborted-run primitive; re-sending the accumulated messages is
9395
- * semantically a **new** run, so any server-side tool the agent had already
9396
- * executed before the interruption would run a second time. Saying plainly
9397
- * that the answer was lost is the honest option, and the user can re-ask.
9336
+ * Deliberately a notice, never a resume. AG-UI has no resume-an-aborted-run
9337
+ * primitive, and re-sending the accumulated messages is semantically a new
9338
+ * run, so any server-side tool already executed would run a second time.
9398
9339
  */
9399
9340
  /**
9400
9341
  * Build a round's context, recording which page it describes.
@@ -9447,19 +9388,16 @@ var AgUiChat = class extends HTMLElement {
9447
9388
  if (text2 !== "") {
9448
9389
  this.appendMessage(MESSAGE_ROLE.ASSISTANT, text2).classList.add("message--restored");
9449
9390
  }
9450
- const toolCalls = message.toolCalls;
9451
- if (toolCalls !== void 0) {
9452
- for (const call of toolCalls) {
9453
- const restored = {
9454
- id: call.id,
9455
- name: call.function.name,
9456
- args: this.#parseArgs(call.function.arguments)
9457
- };
9458
- if (this.#noticeIfSkillLoad(restored)) {
9459
- continue;
9460
- }
9461
- this.#cardFor(restored);
9391
+ for (const call of restoredToolCalls(message.toolCalls)) {
9392
+ const restored = {
9393
+ id: call.id,
9394
+ name: call.function.name,
9395
+ args: this.#parseArgs(call.function.arguments)
9396
+ };
9397
+ if (this.#noticeIfSkillLoad(restored)) {
9398
+ continue;
9462
9399
  }
9400
+ this.#cardFor(restored);
9463
9401
  }
9464
9402
  return;
9465
9403
  }
@@ -9470,8 +9408,11 @@ var AgUiChat = class extends HTMLElement {
9470
9408
  }
9471
9409
  }
9472
9410
  }
9473
- /** Parse a tool call's JSON `arguments` string from history into an object. */
9411
+ /** Parse a tool call's JSON `arguments` from history into an object. */
9474
9412
  #parseArgs(raw) {
9413
+ if (typeof raw !== "string") {
9414
+ return {};
9415
+ }
9475
9416
  try {
9476
9417
  const parsed = JSON.parse(raw);
9477
9418
  if (typeof parsed === "object" && parsed !== null) {
@@ -9500,13 +9441,12 @@ var AgUiChat = class extends HTMLElement {
9500
9441
  /**
9501
9442
  * Append a message bubble and return it.
9502
9443
  *
9503
- * Assistant content is rendered as sanitised markdown/HTML; user content
9504
- * stays literal text (no need to parse what the user typed, and it avoids
9505
- * rendering user-authored markup).
9444
+ * Assistant content renders as sanitised markdown/HTML; user content stays
9445
+ * literal text, which also avoids rendering user-authored markup.
9506
9446
  *
9507
- * Assistant bubbles land in the current answer group, opening one if
9508
- * needed; a user bubble closes the prior group and sits directly in the list
9509
- * (the well wraps the *assistant* turn, the user message precedes it).
9447
+ * Assistant bubbles land in the current answer group, opening one if needed;
9448
+ * a user bubble closes the prior group and sits directly in the list, the
9449
+ * well wrapping only the assistant turn.
9510
9450
  */
9511
9451
  appendMessage(role, content) {
9512
9452
  const bubble = document.createElement("div");
@@ -9699,12 +9639,9 @@ var AgUiChat = class extends HTMLElement {
9699
9639
  * Build a header control button: a named slot a host can project markup into,
9700
9640
  * with the built-in glyph as the slot's fallback.
9701
9641
  *
9702
- * The glyph used to be the button's own `textContent`, which left a host able
9703
- * to restyle the control through its `part` but unable to replace it — a CSS
9704
- * `content` override could swap one character for another, and nothing could
9705
- * supply a brand `<img>` or `<svg>`. This is the same slot-with-fallback
9706
- * idiom the header icon already uses, so existing embeds render exactly as
9707
- * before.
9642
+ * The slot is what lets a host replace the mark with its own `<img>` or
9643
+ * `<svg>` rather than only restyle it through the `part`; the same
9644
+ * slot-with-fallback idiom the header icon uses.
9708
9645
  */
9709
9646
  #headerButton(modifier, label, glyph) {
9710
9647
  const button2 = document.createElement("button");
@@ -9905,19 +9842,16 @@ var AgUiChat = class extends HTMLElement {
9905
9842
  * Send a message as if the user had typed it — renders the user bubble,
9906
9843
  * dispatches {@link SUBMIT_EVENT}, and starts the run.
9907
9844
  *
9908
- * The programmatic half of the composer, for a host driving its own input
9909
- * (a "Ask about this order" button, a command palette, a custom composer
9910
- * replacing the built-in one). Everything the built-in Send does happens
9911
- * here; Send itself now reads the composer, clears it, and calls this.
9845
+ * The programmatic half of the composer, for a host driving its own input.
9846
+ * Everything the built-in Send does happens here; Send reads the composer,
9847
+ * clears it, and calls this.
9912
9848
  *
9913
- * `attachments` are durable {@link AttachmentRef}s — the shape
9914
- * {@link attachFile}'s upload resolves to, and the shape
9915
- * {@link ATTACHMENT_EVENT} reports. Pass them to attach files to the message.
9849
+ * `attachments` are durable {@link AttachmentRef}s — what {@link attachFile}
9850
+ * resolves to and what {@link ATTACHMENT_EVENT} reports.
9916
9851
  *
9917
- * No-ops while a run is in flight (a second concurrent run would orphan the
9918
- * first) and for an entirely empty message. Unlike the built-in Send, this
9919
- * does **not** consult the tray: what you pass is what is sent, so a host
9920
- * composer stays in charge of its own state.
9852
+ * No-ops on an empty message, and while a run is in flight, since a second
9853
+ * concurrent run would orphan the first. Unlike the built-in Send it does not
9854
+ * consult the tray: what you pass is what is sent.
9921
9855
  */
9922
9856
  async sendMessage(content, attachments = []) {
9923
9857
  if (this.#running || content === "" && attachments.length === 0) {
@@ -9941,11 +9875,11 @@ var AgUiChat = class extends HTMLElement {
9941
9875
  * picker and drag-and-drop do — validation, progress chip, and all.
9942
9876
  *
9943
9877
  * Returns `false` when uploads are not configured (no `data-attachments-url`
9944
- * and no {@link uploadHandler}), which is the only way for a host to tell;
9945
- * the tray does not exist to report anything in that case.
9878
+ * and no {@link uploadHandler}) the only signal a host gets, since the tray
9879
+ * does not exist to report anything then.
9946
9880
  *
9947
9881
  * Uploading is asynchronous: watch {@link ATTACHMENT_EVENT} for the resulting
9948
- * {@link AttachmentRef}, and pass it to {@link sendMessage} once `pending`
9882
+ * {@link AttachmentRef} and pass it to {@link sendMessage} once `pending`
9949
9883
  * reaches zero.
9950
9884
  */
9951
9885
  attachFile(file) {
@@ -10090,12 +10024,11 @@ var AgUiChat = class extends HTMLElement {
10090
10024
  * Render an approval card per server-side-tool interrupt and collect the
10091
10025
  * user's decisions (approve → run it, deny → decline it).
10092
10026
  *
10093
- * The run is suspended on these cards; a Stop while any is open aborts the
10027
+ * The run is suspended on these cards. A Stop while any is open aborts the
10094
10028
  * shared {@link #confirmAbort} controller, resolving every still-open card as
10095
- * denied (and the client loop then sees the cancellation and stops). An
10096
- * approved tool runs on the follow-up (resume) run and streams its result
10097
- * back into the same pending card; a denied one is settled here, since no
10098
- * result will ever arrive for it.
10029
+ * denied. An approved tool runs on the follow-up resume run and streams its
10030
+ * result into the same pending card; a denied one settles here, since no
10031
+ * result will ever arrive.
10099
10032
  */
10100
10033
  async #resolveInterrupts(interrupts) {
10101
10034
  const responses = {};
@@ -10103,8 +10036,9 @@ var AgUiChat = class extends HTMLElement {
10103
10036
  this.#hidePending();
10104
10037
  for (const interrupt of interrupts) {
10105
10038
  const request = {};
10106
- if (interrupt.message !== void 0) {
10107
- request.message = interrupt.message;
10039
+ const phrase = confirmPhrase(interrupt) ?? interrupt.message;
10040
+ if (phrase !== void 0) {
10041
+ request.message = phrase;
10108
10042
  }
10109
10043
  const card = interrupt.toolCallId !== void 0 ? this.#toolCards.get(interrupt.toolCallId) : void 0;
10110
10044
  const toolName = card?.element.getAttribute("data-tool-name");
@@ -10162,6 +10096,7 @@ var AgUiChat = class extends HTMLElement {
10162
10096
  if (this.#noticeIfSkillLoad(call)) {
10163
10097
  return;
10164
10098
  }
10099
+ this.#runTools.push({ id: call.id, name: call.name });
10165
10100
  this.#cardFor(call);
10166
10101
  },
10167
10102
  onActivity: (activityType, content) => {
@@ -10215,9 +10150,33 @@ var AgUiChat = class extends HTMLElement {
10215
10150
  }
10216
10151
  this.#currentGroup = null;
10217
10152
  this.#thoughts = null;
10153
+ this.#dispatchRunFinished();
10218
10154
  }
10219
10155
  };
10220
10156
  }
10157
+ /**
10158
+ * Tell the host the interaction is over and what ran in it.
10159
+ *
10160
+ * Last thing in `onSettled`, so a listener that refetches sees a transcript
10161
+ * that has already stopped changing. `side` is read from the streamed-result
10162
+ * bookkeeping rather than from the tool list: whether a call executed on the
10163
+ * server is a fact about the run, and a name can appear on both sides across a
10164
+ * conversation.
10165
+ */
10166
+ #dispatchRunFinished() {
10167
+ const tools = this.#runTools.map(({ id, name }) => ({
10168
+ name,
10169
+ side: this.#serverSettled.has(id) ? "server" : "client"
10170
+ }));
10171
+ this.#runTools = [];
10172
+ this.dispatchEvent(
10173
+ new CustomEvent(RUN_FINISHED_EVENT, {
10174
+ detail: { tools },
10175
+ bubbles: true,
10176
+ composed: true
10177
+ })
10178
+ );
10179
+ }
10221
10180
  /** A muted "⏹ Stopped" line in the transcript (distinct from the ⚠️ error bubble). */
10222
10181
  #appendStoppedNote() {
10223
10182
  const note = document.createElement("div");
@@ -10329,6 +10288,20 @@ var AgUiChat = class extends HTMLElement {
10329
10288
  return card;
10330
10289
  }
10331
10290
  };
10291
+ function confirmPhrase(interrupt) {
10292
+ const phrase = interrupt.metadata?.[X_CONFIRM_KEY];
10293
+ return typeof phrase === "string" && phrase.trim() !== "" ? phrase : void 0;
10294
+ }
10295
+ function restoredToolCalls(value) {
10296
+ return Array.isArray(value) ? value.filter(isRestoredToolCall) : [];
10297
+ }
10298
+ function isRestoredToolCall(value) {
10299
+ if (typeof value !== "object" || value === null) {
10300
+ return false;
10301
+ }
10302
+ const call = value;
10303
+ return typeof call.id === "string" && typeof call.function?.name === "string";
10304
+ }
10332
10305
  function skillNameFrom(call) {
10333
10306
  if (call.name !== LOAD_CAPABILITY_TOOL) {
10334
10307
  return null;
@@ -10381,7 +10354,7 @@ function setControlValue(el, value) {
10381
10354
  }
10382
10355
 
10383
10356
  // src/version.ts
10384
- var VERSION = "0.23.0";
10357
+ var VERSION = "0.24.0";
10385
10358
  export {
10386
10359
  ATTACHMENT_EVENT,
10387
10360
  AgUiChat,
@@ -10396,6 +10369,7 @@ export {
10396
10369
  MAX_TOOL_ROUNDS,
10397
10370
  MESSAGE_ROLE,
10398
10371
  PAGE_ACTIONS,
10372
+ RUN_FINISHED_EVENT,
10399
10373
  RemoteConversationStore,
10400
10374
  RunIndex,
10401
10375
  STATE_EVENT,