@artooi/ag-ui-web-component 0.22.0 → 0.23.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (122) hide show
  1. package/CHANGELOG.md +134 -36
  2. package/README.md +7 -7
  3. package/dist/ag-ui-web-component.bundle.js +145 -118
  4. package/dist/ag-ui-web-component.bundle.js.map +3 -3
  5. package/dist/constants.d.ts +65 -78
  6. package/dist/constants.d.ts.map +1 -1
  7. package/dist/core/ag_ui_chat.d.ts +93 -126
  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 +20 -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 +8 -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.js +347 -401
  33. package/dist/index.js.map +2 -2
  34. package/dist/skills/fill_template.d.ts +4 -5
  35. package/dist/skills/fill_template.d.ts.map +1 -1
  36. package/dist/skills/parse_skills.d.ts.map +1 -1
  37. package/dist/skills/skill.d.ts +7 -8
  38. package/dist/skills/skill.d.ts.map +1 -1
  39. package/dist/tools/client_tool_registry.d.ts +2 -2
  40. package/dist/tools/page_action_tools.d.ts +7 -10
  41. package/dist/tools/page_action_tools.d.ts.map +1 -1
  42. package/dist/tools/page_state.d.ts +5 -8
  43. package/dist/tools/page_state.d.ts.map +1 -1
  44. package/dist/tools/route_map.d.ts +7 -10
  45. package/dist/tools/route_map.d.ts.map +1 -1
  46. package/dist/ui/approval_card.d.ts +15 -20
  47. package/dist/ui/approval_card.d.ts.map +1 -1
  48. package/dist/ui/attach_copy_buttons.d.ts +4 -10
  49. package/dist/ui/attach_copy_buttons.d.ts.map +1 -1
  50. package/dist/ui/attachment_chips.d.ts +13 -5
  51. package/dist/ui/attachment_chips.d.ts.map +1 -1
  52. package/dist/ui/attachment_tray.d.ts +6 -6
  53. package/dist/ui/attachment_tray.d.ts.map +1 -1
  54. package/dist/ui/checkpoint_menu.d.ts +7 -8
  55. package/dist/ui/checkpoint_menu.d.ts.map +1 -1
  56. package/dist/ui/confirmation_card.d.ts +10 -15
  57. package/dist/ui/confirmation_card.d.ts.map +1 -1
  58. package/dist/ui/question_card.d.ts +12 -15
  59. package/dist/ui/question_card.d.ts.map +1 -1
  60. package/dist/ui/relative_time.d.ts +5 -7
  61. package/dist/ui/relative_time.d.ts.map +1 -1
  62. package/dist/ui/render_markdown.d.ts +8 -8
  63. package/dist/ui/render_markdown.d.ts.map +1 -1
  64. package/dist/ui/resize_handle.d.ts +21 -34
  65. package/dist/ui/resize_handle.d.ts.map +1 -1
  66. package/dist/ui/run_notice.d.ts +5 -7
  67. package/dist/ui/run_notice.d.ts.map +1 -1
  68. package/dist/ui/skills_menu.d.ts +4 -5
  69. package/dist/ui/skills_menu.d.ts.map +1 -1
  70. package/dist/ui/styles.d.ts +1 -1
  71. package/dist/ui/styles.d.ts.map +1 -1
  72. package/dist/ui/thoughts_block.d.ts +9 -11
  73. package/dist/ui/thoughts_block.d.ts.map +1 -1
  74. package/dist/ui/thread_drawer.d.ts +6 -5
  75. package/dist/ui/thread_drawer.d.ts.map +1 -1
  76. package/dist/ui/tool_call_card.d.ts +17 -25
  77. package/dist/ui/tool_call_card.d.ts.map +1 -1
  78. package/dist/ui/ui_strings.d.ts +6 -12
  79. package/dist/ui/ui_strings.d.ts.map +1 -1
  80. package/dist/ui/voice_input.d.ts +10 -11
  81. package/dist/ui/voice_input.d.ts.map +1 -1
  82. package/package.json +1 -1
  83. package/src/constants.ts +71 -80
  84. package/src/core/ag_ui_chat.ts +242 -292
  85. package/src/core/agui_client.ts +60 -71
  86. package/src/core/attachment.ts +9 -14
  87. package/src/core/conversation_store.ts +25 -33
  88. package/src/core/create_http_agent.ts +18 -22
  89. package/src/core/remote_conversation_store.ts +14 -15
  90. package/src/core/run_index.ts +14 -23
  91. package/src/core/transcribe_audio.ts +9 -10
  92. package/src/core/upload_attachment.ts +18 -21
  93. package/src/core/utils.ts +4 -6
  94. package/src/dom/animations.ts +33 -43
  95. package/src/dom/dom_driver.ts +7 -7
  96. package/src/dom/native_setter.ts +11 -12
  97. package/src/skills/fill_template.ts +4 -5
  98. package/src/skills/parse_skills.ts +3 -4
  99. package/src/skills/skill.ts +7 -8
  100. package/src/tools/client_tool_registry.ts +2 -2
  101. package/src/tools/page_action_tools.ts +12 -15
  102. package/src/tools/page_state.ts +5 -8
  103. package/src/tools/route_map.ts +15 -19
  104. package/src/ui/approval_card.ts +15 -20
  105. package/src/ui/attach_copy_buttons.ts +9 -18
  106. package/src/ui/attachment_chips.ts +19 -10
  107. package/src/ui/attachment_tray.ts +8 -7
  108. package/src/ui/checkpoint_menu.ts +7 -8
  109. package/src/ui/confirmation_card.ts +10 -15
  110. package/src/ui/question_card.ts +12 -15
  111. package/src/ui/relative_time.ts +5 -7
  112. package/src/ui/render_markdown.ts +25 -51
  113. package/src/ui/resize_handle.ts +25 -38
  114. package/src/ui/run_notice.ts +9 -12
  115. package/src/ui/skills_menu.ts +4 -5
  116. package/src/ui/styles.ts +122 -93
  117. package/src/ui/thoughts_block.ts +11 -13
  118. package/src/ui/thread_drawer.ts +6 -5
  119. package/src/ui/tool_call_card.ts +22 -32
  120. package/src/ui/ui_strings.ts +6 -12
  121. package/src/ui/voice_input.ts +10 -11
  122. package/src/version.ts +1 -1
package/dist/index.js CHANGED
@@ -40,6 +40,10 @@ var ICON_STOP = `<svg class="glyph glyph--solid" viewBox="0 0 24 24" aria-hidden
40
40
  var ICON_ATTACH = `<svg class="glyph" viewBox="0 0 24 24" aria-hidden="true"><path d="M17 8.5V15a5 5 0 0 1-10 0V7a3 3 0 0 1 6 0v7.5a1 1 0 0 1-2 0V8.5"/></svg>`;
41
41
  var ICON_VOICE = `<svg class="glyph" viewBox="0 0 24 24" aria-hidden="true"><path d="M12 4a3 3 0 0 1 3 3v5a3 3 0 0 1-6 0V7a3 3 0 0 1 3-3z"/><path d="M5 11v1a7 7 0 0 0 14 0v-1"/><path d="M12 19v3"/></svg>`;
42
42
  var ICON_LAUNCHER = `<svg class="glyph" viewBox="0 0 24 24" aria-hidden="true"><path d="M4 5.5A2.5 2.5 0 0 1 6.5 3h11A2.5 2.5 0 0 1 20 5.5v8a2.5 2.5 0 0 1-2.5 2.5H9l-5 4z"/></svg>`;
43
+ var ICON_FILE = `<svg class="glyph" viewBox="0 0 24 24" aria-hidden="true"><path d="M14 3H7a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V8z"/><path d="M14 3v5h5"/></svg>`;
44
+ var ICON_FILE_IMAGE = `<svg class="glyph" viewBox="0 0 24 24" aria-hidden="true"><rect x="3.5" y="4.5" width="17" height="15" rx="2.5"/><circle cx="9" cy="10" r="1.5"/><path d="M4.5 17.5 9 13.5l3.5 3 3-2.5 4.5 4"/></svg>`;
45
+ var ICON_FILE_PDF = `<svg class="glyph" viewBox="0 0 24 24" aria-hidden="true"><path d="M14 3H7a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V8z"/><path d="M14 3v5h5"/><rect class="glyph--solid" x="7.5" y="13.5" width="9" height="4.5" rx="1"/></svg>`;
46
+ var ICON_FILE_TEXT = `<svg class="glyph" viewBox="0 0 24 24" aria-hidden="true"><path d="M14 3H7a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V8z"/><path d="M14 3v5h5"/><path d="M8.5 13.5h7M8.5 17h4.5"/></svg>`;
43
47
 
44
48
  // src/skills/fill_template.ts
45
49
  var PLACEHOLDER_RE = /\{([a-zA-Z_][a-zA-Z0-9_]*)\}/g;
@@ -79,8 +83,8 @@ var ClientToolRegistry = class {
79
83
  /**
80
84
  * Register a tool, replacing any existing one with the same name.
81
85
  *
82
- * Idempotent on the name so re-fired host refs / React StrictMode's
83
- * double-invoke don't throw re-registering is a replace, not an error.
86
+ * Idempotent on the name so a re-fired host ref or React StrictMode's
87
+ * double-invoke replaces rather than throws.
84
88
  */
85
89
  register(tool) {
86
90
  this.#tools.set(tool.name, tool);
@@ -723,7 +727,7 @@ function renderChip(ref) {
723
727
  const icon = document.createElement("span");
724
728
  icon.className = "attachment-chip-icon";
725
729
  icon.setAttribute("part", "attachment-chip-icon");
726
- icon.textContent = iconFor(ref.mime);
730
+ icon.innerHTML = iconFor(ref.mime);
727
731
  icon.setAttribute("aria-hidden", "true");
728
732
  const name = document.createElement("span");
729
733
  name.className = "attachment-chip-name";
@@ -739,15 +743,15 @@ function renderChip(ref) {
739
743
  }
740
744
  function iconFor(mime) {
741
745
  if (mime.startsWith("image/")) {
742
- return "\u{1F5BC}";
746
+ return ICON_FILE_IMAGE;
743
747
  }
744
748
  if (mime === "application/pdf") {
745
- return "\u{1F4D5}";
749
+ return ICON_FILE_PDF;
746
750
  }
747
751
  if (mime.startsWith("text/")) {
748
- return "\u{1F4C4}";
752
+ return ICON_FILE_TEXT;
749
753
  }
750
- return "\u{1F4CE}";
754
+ return ICON_FILE;
751
755
  }
752
756
  function formatBytes(bytes) {
753
757
  if (bytes < 1024) {
@@ -913,7 +917,7 @@ var AttachmentTray = class {
913
917
  const icon = document.createElement("span");
914
918
  icon.className = "attachment-chip-icon";
915
919
  icon.setAttribute("part", "attachment-chip-icon");
916
- icon.textContent = iconFor(item.file.type);
920
+ icon.innerHTML = iconFor(item.file.type);
917
921
  icon.setAttribute("aria-hidden", "true");
918
922
  const name = document.createElement("span");
919
923
  name.className = "attachment-chip-name";
@@ -4473,12 +4477,11 @@ var STYLES = `
4473
4477
  only the alias is used by the rules below.
4474
4478
 
4475
4479
  The indirection is what makes ancestor theming work. Declaring the public
4476
- name on :host would set it *on the host element*, and a value on an element
4477
- always beats one inherited from an ancestor \u2014 so a page that put the tokens
4478
- on a wrapper would see no effect at all, which is the one thing the API is
4479
- documented to support. Reading the public name with the default as a var()
4480
- fallback leaves it undeclared on the element, so an ancestor's value is
4481
- inherited normally while a value aimed at the element still wins over it.
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, so tokens put on a wrapper
4482
+ would have no effect. Reading the public name with the default as a var()
4483
+ fallback leaves it undeclared on the element, so an ancestor's value
4484
+ inherits normally while one aimed at the element still wins.
4482
4485
 
4483
4486
  Two invariants hold this together:
4484
4487
  1. No rule outside this file's :host blocks may reference a public name
@@ -4505,11 +4508,9 @@ var STYLES = `
4505
4508
  --_border: var(--ag-ui-border, #e2e2ec);
4506
4509
  --_radius: var(--ag-ui-radius, 12px);
4507
4510
 
4508
- /* Body text and raised chrome. Both are referenced by the code-block copy
4509
- button; neither had a default before, so the declarations reading them
4510
- were dropped and the button fell back to the inherited colour over a
4511
- transparent box. The defaults below restate exactly that, so this is a
4512
- rename with no repaint \u2014 see the note on .code-copy. */
4511
+ /* Body text and raised chrome, read only by the code-block copy button.
4512
+ The defaults restate what it inherits, so a host that sets neither sees
4513
+ no repaint \u2014 see the note on .code-copy. */
4513
4514
  --_text: var(--ag-ui-text, var(--_fg));
4514
4515
  --_surface: var(--ag-ui-surface, transparent);
4515
4516
 
@@ -4562,9 +4563,9 @@ var STYLES = `
4562
4563
  --_launcher-inset: var(--ag-ui-launcher-inset, auto 0 0 auto);
4563
4564
 
4564
4565
  /* Motion. One duration and two curves drive every collapse, expand and
4565
- slide-over, so the whole widget accelerates and settles as one thing.
4566
- The default curve is a long-tailed ease-out (a fast start that decelerates
4567
- into place); the pop curve overshoots slightly, for something arriving. */
4566
+ slide-over, so the widget accelerates and settles as one thing. The
4567
+ default curve decelerates into place; the pop curve overshoots slightly,
4568
+ for something arriving. */
4568
4569
  --_motion: var(--ag-ui-motion, 0.28s);
4569
4570
  --_ease: var(--ag-ui-ease, cubic-bezier(0.32, 0.72, 0, 1));
4570
4571
  --_ease-pop: var(--ag-ui-ease-pop, cubic-bezier(0.34, 1.36, 0.64, 1));
@@ -4588,7 +4589,7 @@ var STYLES = `
4588
4589
  /* Reading-column width for placement="page" (full-bleed, centred content). */
4589
4590
  --_content-max-width: var(--ag-ui-content-max-width, 820px);
4590
4591
  /* Slim rail the sidebar placement collapses to. Only that placement reads
4591
- it, but it is declared here so invariant 2 holds for every alias. */
4592
+ it, but it is declared here so every alias has a default in one place. */
4592
4593
  --_rail-width: var(--ag-ui-rail-width, 52px);
4593
4594
 
4594
4595
  position: var(--_position);
@@ -4680,11 +4681,11 @@ var STYLES = `
4680
4681
  --_radius: var(--ag-ui-radius, 0);
4681
4682
  }
4682
4683
 
4683
- /* Page: full-bleed background with a centred reading column. Unlike
4684
- "full" (edge-to-edge, left-aligned messages) the content sits in a column
4685
- capped at --ag-ui-content-max-width. The column is produced by symmetric auto
4686
- padding on the scroll area + composer (no per-row wrapper), so user pills
4687
- still right-align and the assistant well spans the column. */
4684
+ /* Page: full-bleed background with a centred reading column capped at
4685
+ --ag-ui-content-max-width, where "full" is edge-to-edge and left-aligned.
4686
+ The column comes from symmetric auto padding on the scroll area and
4687
+ composer rather than a per-row wrapper, so user pills still right-align and
4688
+ the assistant well spans the column. */
4688
4689
  :host([placement="page"]) {
4689
4690
  --_inset: var(--ag-ui-inset, 0);
4690
4691
  --_width: var(--ag-ui-width, 100vw);
@@ -4702,9 +4703,8 @@ var STYLES = `
4702
4703
  padding-inline: max(12px, calc((100% - var(--_content-max-width)) / 2));
4703
4704
  }
4704
4705
 
4705
- /* The rows between the message list and the composer (skill chips, the
4706
- /-command palette, the missing-placeholder hint, the upload tray) line up
4707
- with the column too \u2014 chips are padding-based, the palette/hint/tray are
4706
+ /* The rows between the message list and the composer line up with the column
4707
+ too. Chips and tray are padding-based while palette and hint are
4708
4708
  margin-based, so each gets its own inline axis nudged by the same gutter. */
4709
4709
  :host([placement="page"]) .skill-chips,
4710
4710
  :host([placement="page"]) .attachment-tray {
@@ -4813,13 +4813,12 @@ var STYLES = `
4813
4813
  visibility var(--_motion) var(--_ease);
4814
4814
  }
4815
4815
 
4816
- /* The launcher grows out of the corner the panel shrank into.
4817
- It is laid out at rest rather than display:none, which is what lets it
4818
- animate in *and* out: an element that was not rendered has no before-change
4819
- style to transition from, and one whose display flips to none cannot
4820
- transition at all. visibility keeps it unpaintable, untabbable and
4821
- unclickable in between -- the expanded panel's own controls sit under it and
4822
- must stay reachable. */
4816
+ /* The launcher grows out of the corner the panel shrank into. It stays laid
4817
+ out at rest rather than display:none, which is what lets it animate both in
4818
+ and out: an unrendered element has no before-change style to transition
4819
+ from, and one flipping display to none cannot transition at all. visibility
4820
+ keeps it unpaintable, untabbable and unclickable in between, so the expanded
4821
+ panel's own controls underneath stay reachable. */
4823
4822
  :host([collapsed]) .launcher {
4824
4823
  opacity: 1;
4825
4824
  transform: none;
@@ -4980,20 +4979,18 @@ var STYLES = `
4980
4979
 
4981
4980
  /* \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
4982
4981
  Collapsing shrinks the widget to the round floating launcher: the panel
4983
- scales down toward the launcher's corner and fades out, the launcher pops in
4984
- from that same point. Both halves are transform and opacity only, so the
4985
- morph runs on the compositor and never reflows the host page.
4986
-
4987
- The host box keeps its expanded size. Animating it would animate layout, and
4988
- a dragged --ag-ui-width would then fight the launcher's own size. Nothing
4989
- paints there once the panel is gone, so the box only has to stop swallowing
4990
- clicks: pointer events go to none, and the launcher takes them back.
4991
-
4992
- Two placements collapse to something else and restore what they need:
4993
- "sidebar" slides to its rail (below), while "embedded" and "page" keep the
4994
- original header bar \u2014 embedded is laid out by the host page, where a
4995
- floating circle would escape the layout, and page is a full-screen route
4996
- with no corner to float in. */
4982
+ scales toward the launcher's corner and fades, the launcher pops in from the
4983
+ same point. Both halves are transform and opacity only, so the morph runs on
4984
+ the compositor and never reflows the host page.
4985
+
4986
+ The host box keeps its expanded size, since animating it would animate
4987
+ layout and a dragged --ag-ui-width would fight the launcher's own size.
4988
+ Nothing paints there once the panel is gone, so the box only has to stop
4989
+ swallowing clicks: pointer events go to none and the launcher takes them.
4990
+
4991
+ Two placements collapse differently: "sidebar" slides to its rail (below),
4992
+ while "embedded" and "page" keep the header bar, having no corner for a
4993
+ floating circle that would escape the host's layout. */
4997
4994
  :host([collapsed]) {
4998
4995
  pointer-events: none;
4999
4996
  }
@@ -5004,11 +5001,11 @@ var STYLES = `
5004
5001
  visibility: hidden;
5005
5002
  }
5006
5003
 
5007
- /* visibility is what keeps the panel out of the tab order and the a11y tree at
5008
- rest without display:none killing the transition. It interpolates so that any
5009
- progress below 1 still counts as visible: the panel stays on screen for the
5010
- whole collapse and flips hidden exactly at the end, and on expand it is
5011
- visible from the first frame. */
5004
+ /* visibility keeps the panel out of the tab order and the a11y tree at rest
5005
+ without display:none killing the transition. It interpolates so any progress
5006
+ below 1 still counts as visible: the panel stays on screen for the whole
5007
+ collapse, flips hidden exactly at the end, and on expand is visible from the
5008
+ first frame. */
5012
5009
  .chat {
5013
5010
  transform-origin: bottom right;
5014
5011
  transition:
@@ -5035,10 +5032,9 @@ var STYLES = `
5035
5032
  visibility: visible;
5036
5033
  }
5037
5034
 
5038
- /* These two keep the header bar, so the launcher must stay out of the way \u2014
5039
- an embedded host is position: static, which would otherwise leave an
5040
- absolutely-positioned circle to escape the layout entirely and land against
5041
- whatever the page happens to position. */
5035
+ /* These two keep the header bar, so the launcher must stay out of the way: an
5036
+ embedded host is position: static, which would let an absolutely-positioned
5037
+ circle escape the layout and land against whatever the page positions. */
5042
5038
  :host([collapsed]:is([placement="embedded"], [placement="page"])) .launcher {
5043
5039
  visibility: hidden;
5044
5040
  opacity: 0;
@@ -5078,7 +5074,7 @@ var STYLES = `
5078
5074
  and the pending indicator so a whole answer reads (and can be boxed) as one
5079
5075
  unit. A flex column on the message-list gap, stretched to the list width so
5080
5076
  its children keep their own left/right alignment. data-answer-well opts into
5081
- the bordered "well"; without it the layout is today's flat stack. */
5077
+ the bordered "well"; without it the turn renders as a flat stack. */
5082
5078
  .answer {
5083
5079
  display: flex;
5084
5080
  flex-direction: column;
@@ -5204,13 +5200,9 @@ var STYLES = `
5204
5200
  position: relative;
5205
5201
  }
5206
5202
 
5207
- /* This button is the only reader of --ag-ui-surface and --ag-ui-text, and
5208
- neither had a default until the alias layer gave them one. Both references
5209
- used to resolve to nothing, dropping the declaration: the background fell
5210
- back to the initial transparent, and the hover/copied colour to the
5211
- inherited body colour. The defaults chosen upstream (transparent, and the
5212
- body foreground) reproduce that, so the rename repaints nothing. A raised
5213
- surface behind the button is a separate design question. */
5203
+ /* The only reader of --ag-ui-surface and --ag-ui-text. Their defaults are
5204
+ transparent and the body foreground, which is what this button rendered as
5205
+ before either token existed; changing them repaints only this control. */
5214
5206
  .code-copy {
5215
5207
  position: absolute;
5216
5208
  top: 4px;
@@ -5256,12 +5248,10 @@ var STYLES = `
5256
5248
  color: var(--_muted);
5257
5249
  }
5258
5250
 
5259
- /* Markdown tables. table/thead/tbody/tr/th/td are all in
5260
- the sanitizer's ALLOWED_TAGS, so an agent emitting one renders it \u2014 and until
5261
- now rendered it entirely unstyled, overflowing the bubble. A wide table
5262
- scrolls inside its own box rather than stretching the message: the bubble is
5263
- width-constrained, so without this the columns either crush or push the
5264
- layout sideways. */
5251
+ /* Markdown tables. table/thead/tbody/tr/th/td are all in the sanitizer's
5252
+ ALLOWED_TAGS, so an agent can emit one. A wide table must scroll inside its
5253
+ own box rather than stretch the message: the bubble is width-constrained, so
5254
+ without this the columns either crush or push the layout sideways. */
5265
5255
  .message--assistant table {
5266
5256
  display: block;
5267
5257
  width: fit-content;
@@ -5518,8 +5508,6 @@ var STYLES = `
5518
5508
  color: var(--_muted);
5519
5509
  }
5520
5510
 
5521
- /* The record of a human decision on a gated call. An approved call used to
5522
- look exactly like one that was never gated. */
5523
5511
  .skill-item-token {
5524
5512
  font-family: ui-monospace, "SF Mono", Menlo, monospace;
5525
5513
  font-size: 0.92em;
@@ -5527,6 +5515,8 @@ var STYLES = `
5527
5515
  margin-right: 6px;
5528
5516
  }
5529
5517
 
5518
+ /* The lasting record of a human decision on a gated call \u2014 without it an
5519
+ approved call looks exactly like one that was never gated. */
5530
5520
  .tool-call-decision {
5531
5521
  flex: none;
5532
5522
  font-size: 11px;
@@ -5549,10 +5539,8 @@ var STYLES = `
5549
5539
  }
5550
5540
 
5551
5541
  /* Display modes are pure visibility over one DOM shape, selected from the host
5552
- attribute rather than a value stamped on the card when it was built. That is
5553
- what lets a host flip data-tool-display and have every card already on screen
5554
- re-read it, the way data-answer-well behaves. Baking the structure per mode
5555
- meant the setting only reached cards created afterwards.
5542
+ attribute rather than a value stamped on the card at build time, so flipping
5543
+ data-tool-display re-styles cards already on screen. See ToolCallCard.
5556
5544
 
5557
5545
  Default (no attribute) is the full mode: arguments always visible, result
5558
5546
  behind the toggle. */
@@ -5649,20 +5637,19 @@ var STYLES = `
5649
5637
  }
5650
5638
 
5651
5639
  /* Then the measurement corrects it. Which edges are held still belongs to the
5652
- host's layout rather than to placement -- a floating panel a host right-aligns
5653
- is anchored bottom-left, not bottom-right -- so the element measures the edges
5654
- that stay put and stamps them here as "<y>-<x>". Equal specificity to the
5655
- rules above, so source order is what lets the measured value win.
5656
-
5657
- These must not be written as [data-resize-anchor~="left"]: the stamped value
5658
- is one hyphenated token, and ~= matches whitespace-separated words, so such a
5659
- selector cannot ever match. It did not, which left the grip drawn at the
5660
- corner that moves while the cursor pointed along the right diagonal.
5661
-
5662
- Each rule also sets both sides of its axis rather than only the side it
5663
- moves. One that flipped a single way could not undo a placement guess that
5664
- had flipped the other, which put the grip on the anchored corner for an
5665
- embedded panel its host right-aligns. */
5640
+ host's layout, not to placement -- a floating panel a host right-aligns is
5641
+ anchored bottom-left -- so the element measures them and stamps a single
5642
+ hyphenated "<y>-<x>" token here. Equal specificity to the rules above, so
5643
+ source order is what lets the measured value win.
5644
+
5645
+ Two traps, both of which silently draw the grip on the corner that moves:
5646
+
5647
+ Never write these as [data-resize-anchor~="left"]. The stamped value is one
5648
+ hyphenated token and ~= matches whitespace-separated words, so it can never
5649
+ match.
5650
+
5651
+ Each rule must set both sides of its axis, not only the side it moves, or it
5652
+ cannot undo a placement guess that flipped the other way. */
5666
5653
  :host([data-resize-anchor$="-left"]) .resize-handle {
5667
5654
  left: auto;
5668
5655
  right: 0;
@@ -5723,10 +5710,9 @@ var STYLES = `
5723
5710
  }
5724
5711
 
5725
5712
  /* \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
5726
- One surface owns the border, the background and the focus ring; the field and
5727
- its tool row sit inside it. The row used to be four siblings stretched to the
5728
- textarea's height, which gave a paperclip the same visual weight as the field
5729
- and turned Send into a full-height slab. */
5713
+ One surface owns the border, the background and the focus ring; the field
5714
+ and its tool row sit inside it, rather than being siblings stretched to the
5715
+ textarea's height. */
5730
5716
  .input-row {
5731
5717
  display: flex;
5732
5718
  padding: 12px;
@@ -5919,11 +5905,25 @@ var STYLES = `
5919
5905
  display: contents;
5920
5906
  }
5921
5907
 
5908
+ /* The padding is the only separation the tray gets: its slot is display:
5909
+ contents, so the tray is a direct child of the .chat column, and that column
5910
+ sets no gap. The bottom value keeps a chip off the composer's top edge.
5911
+ The inline value here is only the default: the page placement overrides
5912
+ padding-inline further down to compute its reading-column gutter, and wins
5913
+ on specificity whichever way this declaration is written. Read the two
5914
+ together before changing either. */
5922
5915
  .attachment-tray {
5923
5916
  display: flex;
5924
5917
  flex-wrap: wrap;
5925
5918
  gap: 6px;
5926
- padding: 8px 12px 0;
5919
+ padding: 8px 12px;
5920
+ }
5921
+
5922
+ /* The tray sets the hidden property while empty, and an author display beats
5923
+ the UA stylesheet's rule for it, so without this the empty tray keeps
5924
+ laying out and its padding is permanent dead space above the composer. */
5925
+ .attachment-tray[hidden] {
5926
+ display: none;
5927
5927
  }
5928
5928
 
5929
5929
  .attachment-chips {
@@ -5959,15 +5959,28 @@ var STYLES = `
5959
5959
  overflow-wrap: anywhere;
5960
5960
  }
5961
5961
 
5962
+ /* A chip carries its own text colour because it carries its own background.
5963
+ The same chip renders in two places with opposite inherited colours: the
5964
+ composer tray gives it the panel's, a sent user bubble gives it the user
5965
+ foreground, which is white on the stock light theme and near-invisible
5966
+ against the chip. Pairing the colour with the background it belongs to makes
5967
+ both placements read alike. Overridden below for an errored chip, which
5968
+ keeps its red. */
5962
5969
  .attachment-chip {
5963
5970
  display: inline-flex;
5964
5971
  align-items: center;
5965
5972
  gap: 6px;
5973
+ /* Without this the 100% below caps the content box, so the chip still
5974
+ overflows its container by its own padding and border. Invisible while a
5975
+ character cap kept names short; routine once the container is what bounds
5976
+ the name. */
5977
+ box-sizing: border-box;
5966
5978
  max-width: 100%;
5967
5979
  padding: 4px 8px;
5968
5980
  border: 1px solid var(--_border);
5969
5981
  border-radius: 999px;
5970
5982
  background: var(--_assistant-bg);
5983
+ color: var(--_text);
5971
5984
  font-size: 0.85em;
5972
5985
  position: relative;
5973
5986
  }
@@ -5977,11 +5990,31 @@ var STYLES = `
5977
5990
  color: var(--_danger);
5978
5991
  }
5979
5992
 
5993
+ /* The type mark. Painted from currentColor, so it carries the chip's state
5994
+ with it and turns red along with an errored one; muted by opacity rather
5995
+ than a colour, which is what keeps that true. Sized from the chip's own text
5996
+ rather than --ag-ui-glyph-size, the way an icon holder's glyph takes the
5997
+ holder's size: the composer's 18px buttons would make a chip button-height. */
5998
+ .attachment-chip-icon {
5999
+ display: inline-flex;
6000
+ flex: none;
6001
+ opacity: 0.75;
6002
+ }
6003
+
6004
+ .attachment-chip-icon .glyph {
6005
+ width: 1.25em;
6006
+ height: 1.25em;
6007
+ }
6008
+
6009
+ /* No character cap: the chip is already max-width 100%, so its container is
6010
+ what bounds the name, and a fixed cap only truncated names the chip had room
6011
+ for. min-width: 0 is what lets the flex item shrink past its content, so the
6012
+ ellipsis appears at the container edge instead of the chip overflowing. */
5980
6013
  .attachment-chip-name {
5981
6014
  overflow: hidden;
5982
6015
  text-overflow: ellipsis;
5983
6016
  white-space: nowrap;
5984
- max-width: 14ch;
6017
+ min-width: 0;
5985
6018
  }
5986
6019
 
5987
6020
  .attachment-chip-size {
@@ -6626,9 +6659,9 @@ var ThoughtsBlock = class {
6626
6659
  this.#body.textContent = buffer;
6627
6660
  }
6628
6661
  /**
6629
- * Fold the region away — called when the answer's first text token arrives.
6630
- * Idempotent (the per-token text handler calls it repeatedly) and flips the
6631
- * header label from "thinking…" to the settled "Thoughts" affordance.
6662
+ * Fold the region away when the answer's first text token arrives, flipping
6663
+ * the header label to its settled form. Idempotent, since the per-token text
6664
+ * handler calls it repeatedly.
6632
6665
  */
6633
6666
  collapse() {
6634
6667
  if (this.#collapsed) {
@@ -6986,11 +7019,8 @@ var ToolCallCard = class {
6986
7019
  }
6987
7020
  /**
6988
7021
  * Record that a human approved or declined this call, as a line in the card.
6989
- *
6990
- * Approval used to leave no trace at all: a declined call became a tool
6991
- * result saying so, while an approved one simply ran, making the transcript
6992
- * of a gated call byte-identical to one that was never gated. The prompt is
6993
- * gone once answered, so this is where the decision lives.
7022
+ * The prompt disappears once answered, so this is the only lasting trace that
7023
+ * the call was gated at all.
6994
7024
  */
6995
7025
  recordDecision(kind) {
6996
7026
  this.element.setAttribute("data-decision", kind);
@@ -7105,11 +7135,10 @@ var VoiceInput = class {
7105
7135
  this.#recorder?.stop();
7106
7136
  }
7107
7137
  /**
7108
- * Tear the control down the teardown path when the host element is removed
7109
- * mid-recording. Stops any live `MediaRecorder`, releases the mic tracks (so
7110
- * the browser's recording indicator clears), and suppresses the pending
7111
- * transcription: a disconnected control must not fire `onText` back into a
7112
- * detached element.
7138
+ * Tear the control down, for a host element removed mid-recording. Stops any
7139
+ * live `MediaRecorder`, releases the mic tracks so the browser's recording
7140
+ * indicator clears, and suppresses the pending transcription — a
7141
+ * disconnected control must not fire `onText` into a detached element.
7113
7142
  */
7114
7143
  dispose() {
7115
7144
  this.#disposed = true;
@@ -7191,10 +7220,9 @@ var AgUiClient = class {
7191
7220
  #resolveInterrupts;
7192
7221
  #onPersist;
7193
7222
  /**
7194
- * Message ids the server has already closed, so a reuse can be reported.
7195
- *
7196
- * Per client rather than per run: the merge happens across runs, which is the
7197
- * case a per-run set would miss entirely.
7223
+ * Message ids the server has already closed, so a reuse can be reported. Per
7224
+ * client rather than per run — the merge happens across runs, which a per-run
7225
+ * set would miss entirely.
7198
7226
  */
7199
7227
  #closedMessageIds = /* @__PURE__ */ new Set();
7200
7228
  #connectionLostMessage;
@@ -7244,9 +7272,8 @@ var AgUiClient = class {
7244
7272
  * tools (bounded by {@link MAX_TOOL_ROUNDS}).
7245
7273
  *
7246
7274
  * `attachments` ride on the user message as a non-standard field so the
7247
- * default client store round-trips them for history replay; the agent learns
7248
- * the ids from the run context (the server's strict validation ignores the
7249
- * unknown message field), then reads bytes via the `read_attachment` tool.
7275
+ * default store round-trips them for history replay; see
7276
+ * {@link messageAttachments}.
7250
7277
  */
7251
7278
  async send(content, attachments = []) {
7252
7279
  const message = { id: randomUUID2(), role: "user", content };
@@ -7260,7 +7287,7 @@ var AgUiClient = class {
7260
7287
  /**
7261
7288
  * Resume the run loop after a navigating tool's result was supplied
7262
7289
  * post-reload (via {@link addToolResult}). Unlike {@link send}, adds no user
7263
- * message it simply continues the conversation already in history.
7290
+ * message; it continues the conversation already in history.
7264
7291
  */
7265
7292
  async resume() {
7266
7293
  await this.#run();
@@ -7402,9 +7429,9 @@ var AgUiClient = class {
7402
7429
  onActivitySnapshotEvent({ event }) {
7403
7430
  h.onActivity(event.activityType, event.content);
7404
7431
  },
7405
- // Reasoning. `@ag-ui/client` already maps the deprecated
7406
- // THINKING_* events onto these REASONING_* callbacks, so handling the
7407
- // reasoning family alone covers both protocol versions.
7432
+ // `@ag-ui/client` maps the deprecated THINKING_* events onto these
7433
+ // REASONING_* callbacks, so the reasoning family alone covers both
7434
+ // protocol versions.
7408
7435
  onReasoningStartEvent() {
7409
7436
  h.onReasoningStart();
7410
7437
  },
@@ -7559,12 +7586,11 @@ var SessionStorageStore = class {
7559
7586
  return `${this.#root}:${suffix}`;
7560
7587
  }
7561
7588
  /**
7562
- * One-time move of pre-namespacing (`ag-ui-chat:*`) keys into this instance's
7563
- * namespace, so an existing conversation isn't orphaned by the upgrade. Only
7564
- * this store's own keys move (thread pointer, drawer index, per-thread
7565
- * messages/checkpoints) the element's `collapsed`/`theme` keys are left
7566
- * alone. The first namespaced instance to mount adopts the legacy data; a
7567
- * second namespace finds it gone and starts fresh.
7589
+ * One-time move of un-namespaced `ag-ui-chat:*` keys into this instance's
7590
+ * namespace, so an existing conversation isn't orphaned. Only this store's own
7591
+ * keys move — the element's `collapsed` / `theme` keys are left alone. The
7592
+ * first namespaced instance to mount adopts the data; a second namespace
7593
+ * finds it gone and starts fresh.
7568
7594
  */
7569
7595
  #migrateLegacyKeys() {
7570
7596
  const legacyRoot = `${KEY_ROOT}:`;
@@ -7645,12 +7671,10 @@ function createHttpAgent(options) {
7645
7671
  headers: options.headers ?? {},
7646
7672
  initialState: { ...options.initialState ?? {} },
7647
7673
  // HttpAgent invokes its configured fetch as a method (`this.fetch(...)`),
7648
- // which would rebind the global `fetch` to the agent instance and trigger
7649
- // "Illegal invocation" in browsers. Wrap it so `fetch` is always called as
7650
- // a free function with the correct receiver. The wrapper also overlays
7651
- // `getHeaders()` per request, so header rotation (CSRF, short-lived JWT)
7652
- // reaches the stream even though the agent instance is cached, and applies
7653
- // the configured cookie policy — the agent's own config has no seam for it.
7674
+ // rebinding the global `fetch` to the agent instance "Illegal invocation"
7675
+ // in browsers. The wrapper keeps `fetch` a free call, and is also where the
7676
+ // per-request `getHeaders()` overlay and the cookie policy go, the agent's
7677
+ // own config having no seam for either.
7654
7678
  fetch: (url, init) => {
7655
7679
  const fresh = options.getHeaders?.();
7656
7680
  if (fresh === void 0) {
@@ -7663,7 +7687,7 @@ function createHttpAgent(options) {
7663
7687
  return fetch(url, withCredentials({ ...init, headers }, options.credentials));
7664
7688
  },
7665
7689
  // Spread conditionally: under `exactOptionalPropertyTypes` an explicit
7666
- // `undefined` is not assignable to these optional config fields.
7690
+ // `undefined` is not assignable to an optional field.
7667
7691
  ...options.threadId !== void 0 ? { threadId: options.threadId } : {},
7668
7692
  ...options.initialMessages !== void 0 ? { initialMessages: [...options.initialMessages] } : {}
7669
7693
  });
@@ -7749,9 +7773,9 @@ var RemoteConversationStore = class {
7749
7773
  return {
7750
7774
  threadId: row.thread_id,
7751
7775
  title: this.#renamed.get(row.thread_id) ?? row.title,
7752
- // `null` or an unparseable date both become `NaN` (Date.parse's own
7753
- // signal), which `relativeTime` renders as a neutral label rather than
7754
- // "~2950w ago" (epoch 0) or "NaNw ago".
7776
+ // `null` and an unparseable date both become NaN, Date.parse's own
7777
+ // signal, which `relativeTime` renders as a neutral label rather than an
7778
+ // epoch-0 or NaN duration.
7755
7779
  updatedAt: row.updated_at === null ? Number.NaN : Date.parse(row.updated_at),
7756
7780
  preview: row.preview
7757
7781
  };
@@ -7795,10 +7819,9 @@ var RunIndex = class {
7795
7819
  this.#credentials = credentials;
7796
7820
  }
7797
7821
  /**
7798
- * The user's runs, or `[]` when the endpoint is unreachable or answers with
7799
- * an error. A history affordance that cannot load is empty, never broken:
7800
- * the caller renders its empty state rather than surfacing a transport fault
7801
- * the user can do nothing about.
7822
+ * The user's runs, or `[]` when the endpoint is unreachable or errors — the
7823
+ * caller renders its empty state rather than a transport fault the user can
7824
+ * do nothing about.
7802
7825
  */
7803
7826
  async list() {
7804
7827
  try {
@@ -7836,9 +7859,9 @@ var RunIndex = class {
7836
7859
  /**
7837
7860
  * `<mount>/<verb>/<runId>/`, derived from the index URL's own prefix.
7838
7861
  *
7839
- * Built by string surgery on the trailing `runs/` rather than with `new URL`,
7840
- * because the configured value may be root-relative (`/agent/runs/`) the
7841
- * common case in a Django template and `new URL` needs an absolute base.
7862
+ * String surgery on the trailing `runs/` rather than `new URL`, because the
7863
+ * configured value may be root-relative (`/agent/runs/`), the common case in
7864
+ * a Django template, and `new URL` needs an absolute base.
7842
7865
  */
7843
7866
  #sibling(verb, runId) {
7844
7867
  const prefix = this.#url.slice(0, -"runs/".length);
@@ -7973,29 +7996,23 @@ var AgUiChat = class extends HTMLElement {
7973
7996
  /** Agent factory; override to inject a custom or fake agent (tests). */
7974
7997
  agentFactory = createHttpAgent;
7975
7998
  /**
7976
- * Static extra HTTP headers, sent with **every** request this element makes
7977
- * the agent run, the thread index and its messages, the tool and skill
7978
- * catalogs, the run index, uploads and transcription.
7999
+ * Static extra HTTP headers, sent with every request this element makes: the
8000
+ * agent run, the thread index and its messages, the tool and skill catalogs,
8001
+ * the run index, uploads and transcription.
7979
8002
  *
7980
- * Right for values fixed for the element's lifetime. A credential that
7981
- * rotates (a short-lived JWT, a re-issued CSRF token) belongs in
7982
- * {@link getHeaders} instead: this is read at request time, but only a
7983
- * re-assignment updates it, so a token captured here is pinned until the host
7984
- * remembers to assign again.
8003
+ * For values fixed for the element's lifetime. A rotating credential belongs
8004
+ * in {@link getHeaders} instead — only a re-assignment updates this, so a
8005
+ * token captured here is pinned until the host assigns again.
7985
8006
  */
7986
8007
  headers = {};
7987
8008
  /**
7988
- * Live header source, consulted immediately before every request — the way to
7989
- * supply rotating credentials.
7990
- *
7991
- * Set it to a function and each request calls it afresh: a token refreshed by
7992
- * the host between two requests reaches the second one, with nothing to
7993
- * re-assign and nothing to keep in sync.
8009
+ * Live header source, called afresh immediately before every request — the
8010
+ * way to supply rotating credentials, with nothing to re-assign or keep in
8011
+ * sync.
7994
8012
  *
7995
- * Composes with {@link headers} rather than replacing it: the two are merged
7996
- * per key with `getHeaders()` winning, so a static `X-Client` and a rotating
7997
- * `Authorization` can be configured independently and neither silently drops
7998
- * the other.
8013
+ * Composes with {@link headers} rather than replacing it: merged per key with
8014
+ * `getHeaders()` winning, so a static `X-Client` and a rotating
8015
+ * `Authorization` are configured independently and neither drops the other.
7999
8016
  */
8000
8017
  getHeaders = null;
8001
8018
  /**
@@ -8009,37 +8026,31 @@ var AgUiChat = class extends HTMLElement {
8009
8026
  autoConfirm = false;
8010
8027
  /**
8011
8028
  * When true, the built-in `ask_user` frontend tool is offered to the agent:
8012
- * calling it renders an inline question card (radio choices and/or a free-text
8013
- * field) and returns the user's answer. Off by default — like the other
8014
- * built-in tool groups (route / page-action), it is opt-in so it doesn't
8015
- * change the advertised catalog until a host asks for it.
8029
+ * calling it renders an inline question card and returns the user's answer.
8030
+ * Off by default, like the other built-in tool groups, so the advertised
8031
+ * catalog does not change until a host asks for it.
8016
8032
  */
8017
8033
  askUser = false;
8018
8034
  /**
8019
- * Optional full replacement for the `ask_user` question UI. When set, calling
8020
- * `ask_user` invokes this instead of the built-in inline card: the host
8021
- * renders whatever it likes (a native modal, a framework component, …) and
8022
- * resolves with the answer. Unset (default) uses the built-in
8023
- * {@link requestQuestion} card — style that via the `strings` override and the
8024
- * `question*` CSS `::part()`s. Requires {@link askUser} to be enabled.
8035
+ * Optional full replacement for the `ask_user` question UI, resolving with
8036
+ * the answer; the same seam as {@link approvalRenderer}, styled via `strings`
8037
+ * and the `question*` `::part()`s when left unset. Requires {@link askUser}.
8025
8038
  */
8026
8039
  askUserRenderer = null;
8027
8040
  /**
8028
- * Optional full replacement for the server-side-tool approval UI. When set, an
8029
- * approval interrupt invokes this instead of the built-in inline approval
8030
- * card: the host renders whatever it likes and resolves `true` to approve /
8031
- * `false` to deny. Unset (default) uses the built-in {@link requestApproval}
8032
- * card style that via the `strings` override and the `approval*` CSS
8033
- * `::part()`s. The gate itself is enabled server-side; this only changes how
8034
- * the decision is collected.
8041
+ * Optional full replacement for the server-side-tool approval UI: an approval
8042
+ * interrupt invokes this instead of the built-in {@link requestApproval}
8043
+ * card, resolving `true` to approve or `false` to deny. Style the built-in
8044
+ * card via `strings` and the `approval*` `::part()`s instead. The gate itself
8045
+ * is enabled server-side; this only changes how the decision is collected.
8035
8046
  */
8036
8047
  approvalRenderer = null;
8037
8048
  /**
8038
- * Optional per-call confirmation predicate. When set, it is authoritative:
8039
- * given a tool name + args it decides whether *this* call needs confirmation
8040
- * (so one tool can be instant for some args and confirmed for others — what a
8041
- * static `x-destructive` flag can't express). When unset, the `x-destructive`
8042
- * schema flag is used. `autoConfirm` short-circuits both.
8049
+ * Optional per-call confirmation predicate. When set it is authoritative,
8050
+ * deciding from the tool name and args whether this particular call needs
8051
+ * confirmation — so one tool can be instant for some args and confirmed for
8052
+ * others, which a static `x-destructive` flag cannot express. When unset the
8053
+ * `x-destructive` flag decides. `autoConfirm` short-circuits both.
8043
8054
  */
8044
8055
  confirmPredicate = null;
8045
8056
  /**
@@ -8057,14 +8068,14 @@ var AgUiChat = class extends HTMLElement {
8057
8068
  ...this.#toolRegistry.tools()
8058
8069
  ];
8059
8070
  /**
8060
- * Per-run context provider. Defaults to the compact page map (when a
8061
- * {@link getPageMap} provider is set and {@link autoInjectPageMap} is on)
8062
- * plus a one-line manifest of the files attached to the message being sent,
8063
- * so the agent knows which `read_attachment` ids are available.
8071
+ * Per-run context provider. Defaults to the compact page map, when a
8072
+ * {@link getPageMap} provider is set and {@link autoInjectPageMap} is on.
8073
+ *
8074
+ * Attachments are deliberately not restated here: the server derives its own
8075
+ * manifest from the refs riding the messages.
8064
8076
  */
8065
8077
  getContext = () => [
8066
- ...createPageMapContext(this.getPageMap, this.autoInjectPageMap),
8067
- ...this.#attachmentContext()
8078
+ ...createPageMapContext(this.getPageMap, this.autoInjectPageMap)
8068
8079
  ];
8069
8080
  /**
8070
8081
  * Navigable routes the agent can jump to via the built-in `route.*` tools.
@@ -8089,21 +8100,17 @@ var AgUiChat = class extends HTMLElement {
8089
8100
  conversationStore = new SessionStorageStore();
8090
8101
  /**
8091
8102
  * How attached files are uploaded. `null` (default) uses the built-in
8092
- * multipart `POST` to `data-attachments-url`. Set a custom
8093
- * {@link UploadHandler} `(file, onProgress) => Promise<AttachmentRef>` to
8094
- * swap the transport (e.g. a `tus-js-client` resumable adapter or
8095
- * direct-to-S3 multipart) without changing the tray, the chips, or the AG-UI
8096
- * wire (refs are transport-agnostic). When set, the 📎 affordance appears even
8097
- * with no `data-attachments-url`; the handler owns its own endpoint + headers.
8103
+ * multipart `POST` to `data-attachments-url`; a custom {@link UploadHandler}
8104
+ * swaps the transport without changing the tray, the chips, or the AG-UI
8105
+ * wire. When set, the 📎 affordance appears even with no
8106
+ * `data-attachments-url`, and the handler owns its own endpoint and headers.
8098
8107
  */
8099
8108
  uploadHandler = null;
8100
8109
  /**
8101
8110
  * How recorded voice clips are transcribed. `null` (default) POSTs the clip
8102
- * to `data-transcribe-url` (django-ag-ui's `TranscribeView`). Set a custom
8103
- * {@link TranscribeHandler} `(audio: Blob) => Promise<string>` to swap the
8104
- * transport (a different STT endpoint, a browser Web Speech adapter) without
8105
- * touching the mic button. When set, the 🎤 affordance appears even with no
8106
- * `data-transcribe-url`.
8111
+ * to `data-transcribe-url`; a custom {@link TranscribeHandler} swaps the
8112
+ * transport without touching the mic button. When set, the 🎤 affordance
8113
+ * appears even with no `data-transcribe-url`.
8107
8114
  */
8108
8115
  transcribeHandler = null;
8109
8116
  /**
@@ -8122,12 +8129,10 @@ var AgUiChat = class extends HTMLElement {
8122
8129
  */
8123
8130
  skillContext = () => ({});
8124
8131
  /**
8125
- * Friendly display labels for tool-call cards, keyed by tool name (e.g.
8126
- * `{ list_projects: "Search projects" }`). Used as a fallback when a tool has
8127
- * no `x-summary` in its own schema chiefly **server-side tools** (drf-mcp,
8128
- * `@tool` registry), whose schema never reaches the browser (AG-UI streams
8129
- * only the tool-call name). Client tools should prefer `x-summary` on their
8130
- * schema; this map is the seam for everything else.
8132
+ * Friendly display labels for tool-call cards, keyed by tool name. The
8133
+ * fallback when a tool has no `x-summary` in its own schema, which chiefly
8134
+ * means server-side tools: AG-UI streams only the tool-call name, so their
8135
+ * schema never reaches the browser. Client tools should prefer `x-summary`.
8131
8136
  */
8132
8137
  toolSummaries = {};
8133
8138
  /**
@@ -8196,8 +8201,6 @@ var AgUiChat = class extends HTMLElement {
8196
8201
  #voice = null;
8197
8202
  /** Whether the element is currently in the DOM; gates the connect-time warning. */
8198
8203
  #connected = false;
8199
- /** Refs attached to the message currently being sent (the context manifest). */
8200
- #runAttachments = [];
8201
8204
  #client = null;
8202
8205
  // Seed for the next client. Once one exists it owns the live value (the
8203
8206
  // agent applies STATE_SNAPSHOT / STATE_DELTA into it), so this is only the
@@ -8301,13 +8304,10 @@ var AgUiChat = class extends HTMLElement {
8301
8304
  * Continue `runId` as a **new** run, seeded server-side from its snapshot.
8302
8305
  *
8303
8306
  * Uses a short-lived agent pointed at the resume / fork endpoint and seeded
8304
- * with **no** history, so the request carries only the turn typed here the
8305
- * contract those endpoints assume, since the server supplies the prior turns
8306
- * from the snapshot and re-sending them would duplicate. Building a separate
8307
- * agent makes that structural: the main agent keeps its own history, and
8308
- * "only the new turn" can't be got wrong by forgetting to clear it. The
8309
- * fresh `run_id` the endpoints also require comes free — a new agent mints
8310
- * one per run.
8307
+ * with no history, because those endpoints supply the prior turns from the
8308
+ * snapshot and re-sending them would duplicate. A separate agent makes that
8309
+ * structural the main agent keeps its own history and mints the fresh
8310
+ * `run_id` the endpoints also require.
8311
8311
  *
8312
8312
  * Handlers are the element's own, so the continuation streams into the same
8313
8313
  * transcript the user is looking at.
@@ -8388,10 +8388,8 @@ var AgUiChat = class extends HTMLElement {
8388
8388
  * when the server streams `STATE_SNAPSHOT` / `STATE_DELTA`. Assigning seeds
8389
8389
  * the next run; reading returns whatever the agent last applied.
8390
8390
  *
8391
- * Listen for {@link STATE_EVENT} to react to server-driven changes.
8392
- *
8393
- * Not to be confused with {@link registerPageState}, which exposes host
8394
- * state to the agent as ordinary *tools*.
8391
+ * Listen for {@link STATE_EVENT} to react to server-driven changes. Distinct
8392
+ * from {@link registerPageState}, which exposes host state as ordinary tools.
8395
8393
  */
8396
8394
  get sharedState() {
8397
8395
  return this.#client?.state ?? this.#sharedState;
@@ -8407,11 +8405,9 @@ var AgUiChat = class extends HTMLElement {
8407
8405
  }
8408
8406
  }
8409
8407
  /**
8410
- * @deprecated Renamed to {@link registerPageState}. The old name read as
8411
- * AG-UI shared-state sync (`STATE_SNAPSHOT` / `STATE_DELTA`), which this
8412
- * component does not implement — these are ordinary client tools over host
8413
- * page state. Behaviour is unchanged; this alias will be removed in a future
8414
- * major.
8408
+ * @deprecated Renamed to {@link registerPageState} the old name read as
8409
+ * AG-UI shared-state sync. Behaviour is unchanged; the alias will be removed
8410
+ * in a future major.
8415
8411
  */
8416
8412
  registerStateHook(binding) {
8417
8413
  this.registerPageState(binding);
@@ -8478,10 +8474,9 @@ var AgUiChat = class extends HTMLElement {
8478
8474
  /**
8479
8475
  * The built-in `ask_user` frontend tool, or `[]` when {@link askUser} is off.
8480
8476
  *
8481
- * A generic "ask the user a typed question" primitive: the agent calls it, the
8482
- * client executes it locally via the normal frontend-tool path (rendering a
8483
- * {@link requestQuestion} card), and the chosen/typed answer flows back as the
8484
- * tool result — no new protocol, reusing the machinery already in place.
8477
+ * The agent calls it, the client executes it locally through the normal
8478
+ * frontend-tool path by rendering a {@link requestQuestion} card, and the
8479
+ * answer flows back as the tool result. No new protocol.
8485
8480
  */
8486
8481
  #askUserTool() {
8487
8482
  if (!this.askUser) {
@@ -8557,19 +8552,18 @@ var AgUiChat = class extends HTMLElement {
8557
8552
  * `credentials` mode (`"omit"` / `"same-origin"` / `"include"`). Mirrored to
8558
8553
  * the `credentials` attribute, so markup embeds can set it without script.
8559
8554
  *
8560
- * `null` (the default) leaves the browser's default of `same-origin` in
8561
- * place. That default sends **no cookies at all** when the endpoints live on
8562
- * a different origin from the page app.example.com calling
8563
- * api.example.com is cross-origin and the request goes out anonymously
8564
- * rather than failing, so the symptom is a 401 from a server that looks
8565
- * correctly configured. A cookie-authenticated cross-origin deployment wants
8566
- * `"include"`, plus `Access-Control-Allow-Credentials: true` and a concrete
8567
- * (non-wildcard) `Access-Control-Allow-Origin` on the server.
8555
+ * `null` (the default) leaves the browser's `same-origin` default in place,
8556
+ * which sends no cookies at all to an endpoint on a different origin — and
8557
+ * the request goes out anonymously rather than failing, so the symptom is a
8558
+ * 401 from a server that looks correctly configured. A cookie-authenticated
8559
+ * cross-origin deployment wants `"include"`, plus
8560
+ * `Access-Control-Allow-Credentials: true` and a concrete, non-wildcard
8561
+ * `Access-Control-Allow-Origin` on the server.
8568
8562
  *
8569
8563
  * Read per request, so a late assignment applies to everything after it.
8570
- * `"omit"` cannot be honoured by the built-in **upload** transport, which is
8571
- * an `XMLHttpRequest` and only has a two-state cookie switch; every other
8572
- * endpoint honours all three modes.
8564
+ * `"omit"` cannot be honoured by the built-in upload transport, an
8565
+ * `XMLHttpRequest` with only a two-state cookie switch; every other endpoint
8566
+ * honours all three modes.
8573
8567
  */
8574
8568
  get credentials() {
8575
8569
  const attr = this.getAttribute("credentials");
@@ -8670,25 +8664,21 @@ var AgUiChat = class extends HTMLElement {
8670
8664
  * The catalog requests the element issues on startup: the tool labels
8671
8665
  * (`data-tools-url`) and the backend skills (`data-skills-url`).
8672
8666
  *
8673
- * Deliberately one microtask behind `connectedCallback`. A host that
8674
- * configures the element through a framework ref necessarily does so *after*
8675
- * inserting the node React attaches refs and runs layout effects in the
8676
- * same commit as the insertion, but strictly afterwards so a request issued
8677
- * from `connectedCallback` itself goes out before `headers`,
8678
- * {@link getHeaders} or {@link credentials} exist, and comes back 401 in a
8679
- * way that reads as a server fault rather than a mis-timed assignment. A
8680
- * microtask lands after that commit and still before paint.
8667
+ * Deliberately one microtask behind `connectedCallback`. A framework ref is
8668
+ * attached after the node is inserted but within the same commit, so a
8669
+ * request issued from `connectedCallback` itself goes out before `headers`,
8670
+ * {@link getHeaders} or {@link credentials} exist and 401s in a way that
8671
+ * reads as a server fault. A microtask lands after that commit, still before
8672
+ * paint.
8673
+ *
8674
+ * It is not a fix for configuration arriving later than the commit (a passive
8675
+ * effect, an awaited token fetch): configure before insertion, or call
8676
+ * {@link reload}, since a longer timer would hide that race rather than close
8677
+ * it.
8681
8678
  *
8682
- * Two things it is **not**. It is not a fix for configuration that arrives
8683
- * later than the commit (a passive `useEffect`, an awaited token fetch):
8684
- * configure before insertion (`createElement` configure `append`) or call
8685
- * {@link reload} once configured, because a longer timer would hide that race
8686
- * rather than close it. And it deliberately excludes the *history* replay,
8687
- * which stays in `connectedCallback`: the replay renders into the transcript,
8688
- * so deferring it lets a `sendMessage()` issued in the same task land first
8689
- * and the replay then duplicate it. The thread history is therefore the one
8690
- * request that can still go out before a ref is attached — {@link reload}
8691
- * covers it.
8679
+ * The history replay stays in `connectedCallback` on purpose. It renders into
8680
+ * the transcript, so deferring it would let a `sendMessage()` in the same
8681
+ * task land first and be duplicated by the replay.
8692
8682
  */
8693
8683
  #startup() {
8694
8684
  if (!this.#connected) {
@@ -8702,15 +8692,13 @@ var AgUiChat = class extends HTMLElement {
8702
8692
  * the backend skill catalog and the thread's history — with the transport
8703
8693
  * configuration as it stands now.
8704
8694
  *
8705
- * This is the answer for a host that can only configure the element after the
8706
- * fact (a token fetched in a passive effect, an async auth handshake): the
8707
- * startup requests already went out with whatever was set then, and this says
8708
- * "try again, properly authenticated" without removing and re-inserting the
8709
- * node.
8695
+ * For a host that can only configure the element after the fact (a token
8696
+ * fetched in a passive effect, an async auth handshake), this re-issues the
8697
+ * startup requests authenticated, without removing and re-inserting the node.
8710
8698
  *
8711
- * A reload, not a merge the in-flight run is cancelled and the transcript
8712
- * is rebuilt from the persisted history, so anything streamed since is
8713
- * dropped. Call it once, when configuration lands; not between turns.
8699
+ * A reload, not a merge: the in-flight run is cancelled and the transcript is
8700
+ * rebuilt from persisted history, so anything streamed since is dropped. Call
8701
+ * it once, when configuration lands, not between turns.
8714
8702
  */
8715
8703
  async reload() {
8716
8704
  this.#cancelRun();
@@ -8719,12 +8707,10 @@ var AgUiChat = class extends HTMLElement {
8719
8707
  await Promise.all([this.#fetchToolCatalog(), this.#fetchSkills(), this.#rehydrate()]);
8720
8708
  }
8721
8709
  /**
8722
- * Tear down live resources when the element leaves the DOM (a removed node, a
8723
- * client-side route swap): cancel the in-flight run so its SSE stream closes,
8724
- * abort any in-flight uploads so they don't orphan server-side files, and
8725
- * release the mic so the browser's recording indicator clears. Without this a
8726
- * removed `<ag-ui-chat>` leaks a streaming request, uploads, and a live
8727
- * `MediaRecorder`.
8710
+ * Tear down live resources when the element leaves the DOM: cancel the
8711
+ * in-flight run so its stream closes, abort in-flight uploads so they do not
8712
+ * orphan server-side files, and release the mic so the browser's recording
8713
+ * indicator clears. Without this a removed element leaks all three.
8728
8714
  */
8729
8715
  disconnectedCallback() {
8730
8716
  this.#connected = false;
@@ -8735,14 +8721,10 @@ var AgUiChat = class extends HTMLElement {
8735
8721
  /**
8736
8722
  * Read an opt-in flag attribute the way HTML reads a boolean attribute.
8737
8723
  *
8738
- * Present means on bare (`data-prompt-chips`), empty
8739
- * (`data-prompt-chips=""`) or any value except the literal `"false"`. These
8740
- * were compared against the string `"true"`, so writing the attribute bare —
8741
- * the spelling every native boolean attribute uses, and the one a reader
8742
- * reaches for first — silently *disabled* the feature it names, with nothing
8743
- * to indicate why the chips never appeared.
8744
- *
8745
- * `="false"` still turns it off, so an explicit opt-out keeps working.
8724
+ * Present means on: bare (`data-prompt-chips`), empty (`=""`), or any value
8725
+ * except the literal `"false"`. Comparing against `"true"` instead would make
8726
+ * the bare spelling every native boolean attribute uses silently disable the
8727
+ * feature it names. `="false"` still turns it off.
8746
8728
  */
8747
8729
  #flag(name) {
8748
8730
  const value = this.getAttribute(name);
@@ -8884,22 +8866,6 @@ var AgUiChat = class extends HTMLElement {
8884
8866
  }
8885
8867
  });
8886
8868
  }
8887
- /** The one-line manifest of the message's attachments, for the run context. */
8888
- #attachmentContext() {
8889
- if (this.#runAttachments.length === 0) {
8890
- return [];
8891
- }
8892
- const lines = this.#runAttachments.map(
8893
- (ref) => `- ${ref.name} (id: ${ref.id}, ${ref.mime || "unknown type"}, ${ref.size} bytes)`
8894
- );
8895
- return [
8896
- {
8897
- description: "Files the user attached to this message",
8898
- value: `${lines.join("\n")}
8899
- Use the read_attachment tool with an id to read a file's contents.`
8900
- }
8901
- ];
8902
- }
8903
8869
  /**
8904
8870
  * When `data-threads-url` is set, route thread enumeration / load / rename /
8905
8871
  * delete through that server endpoint (wrapping the current store as the
@@ -8984,21 +8950,16 @@ Use the read_attachment tool with an id to read a file's contents.`
8984
8950
  /**
8985
8951
  * Act on a picked skill.
8986
8952
  *
8987
- * A skill that ships no `prompt` is **server-resolved**: the catalog carries
8988
- * only its name and label, and picking it sends the bare `/name` token for
8989
- * the agent to expandfrom the harness `Skills` capability, or from the
8990
- * server's own instructions. That is the shape to prefer, because the prompt
8991
- * then never reaches the browser at all: a skill is often where a project's
8992
- * internal workflow is written down most plainly, and a catalog endpoint is a
8993
- * plain GET.
8953
+ * A skill with no `prompt` is server-resolved: picking it sends the bare
8954
+ * `/name` token for the agent to expand, so the wording never reaches the
8955
+ * browser. Prefer that shapea skill often states a project's internal
8956
+ * workflow most plainly, and a catalog endpoint is a plain GET.
8994
8957
  *
8995
- * A skill that does carry a `prompt` keeps the older behaviour the client
8996
- * fills its `{placeholder}`s from the page and sends (or pre-fills) the text.
8997
- * Right for a user-facing convenience, and for placeholders only the page can
8998
- * supply.
8958
+ * A skill carrying a `prompt` has the client fill its `{placeholder}`s from
8959
+ * the page instead, which is right for placeholders only the page can supply.
8999
8960
  *
9000
- * Either way a pick now **sends**, rather than parking text in the composer
9001
- * for a second click; `sendImmediately: false` opts back into pre-filling.
8961
+ * Either way a pick sends; `sendImmediately: false` opts into pre-filling the
8962
+ * composer instead.
9002
8963
  */
9003
8964
  #applySkill(skill) {
9004
8965
  if (skill.prompt === void 0) {
@@ -9110,18 +9071,13 @@ Use the read_attachment tool with an id to read a file's contents.`
9110
9071
  }
9111
9072
  }
9112
9073
  /**
9113
- * Which edges the layout is holding still, by measuring rather than guessing.
9074
+ * Which edges the layout is holding still, by measuring rather than guessing:
9075
+ * nudge the size by a pixel, see which edges stayed put, and undo. One forced
9076
+ * reflow per drag.
9114
9077
  *
9115
- * A resize has to be computed from the edge that does not move, and which
9116
- * edge that is belongs to the **host's layout**, not to `placement`: a
9117
- * floating panel is pinned bottom-right, while an embedded one goes wherever
9118
- * the page's own CSS puts it — flex-start, flex-end, a grid cell. Mapping
9119
- * placement to a corner got this wrong for any host that right-aligns the
9120
- * element, and the symptom is bad enough to read as a broken control: the
9121
- * panel shrinks when dragged outward, travelling by its opposite corner.
9122
- *
9123
- * So: nudge the size by a pixel, see which edges stayed put, and undo. One
9124
- * forced reflow per drag, which is cheap next to being wrong.
9078
+ * `placement` cannot answer this an embedded panel goes wherever the page's
9079
+ * CSS puts it and see {@link createResizeHandle} for why guessing produces
9080
+ * a visibly broken control.
9125
9081
  */
9126
9082
  #measureAnchor() {
9127
9083
  const before = this.getBoundingClientRect();
@@ -9155,14 +9111,13 @@ Use the read_attachment tool with an id to read a file's contents.`
9155
9111
  /**
9156
9112
  * Write a dragged size onto the host, on the axes this placement leaves free.
9157
9113
  *
9158
- * Writing the custom property rather than inline `width` / `height` does
9159
- * **not** by itself leave placement in charge an inline custom property
9160
- * still outranks a `:host([placement=…])` rule setting the same property, so
9161
- * a height dragged while floating capped a docked sidebar that had asked for
9162
- * `100vh`. The cascade cannot arbitrate this; the axis check has to.
9163
- *
9164
- * So the rule is explicit: a placement owns the axes it fixes, and a
9165
- * persisted size is only ever applied to the ones it does not.
9114
+ * Writing the custom property rather than inline `width` / `height` does not
9115
+ * by itself leave placement in charge: an inline custom property still
9116
+ * outranks a `:host([placement=…])` rule setting the same property, so a
9117
+ * height dragged while floating would cap a docked sidebar asking for
9118
+ * `100vh`. The cascade cannot arbitrate this, so the axis check must — a
9119
+ * placement owns the axes it fixes, and a persisted size is applied only to
9120
+ * the ones it leaves free.
9166
9121
  */
9167
9122
  #applySize(size) {
9168
9123
  const axis = this.#resizeAxis();
@@ -9235,12 +9190,10 @@ Use the read_attachment tool with an id to read a file's contents.`
9235
9190
  * Open the thread-history drawer: the imperative route to the control that
9236
9191
  * renders as `::part(history-button)`.
9237
9192
  *
9238
- * A host that hides `::part(header)` to render its own title bar hides the
9239
- * history, new-chat and collapse buttons with it and thread switching then
9240
- * has no route at all, because those controls live inside the header. Each of
9241
- * them has a method, so a host chrome can rebuild the set: this one,
9242
- * {@link openCheckpoints}, {@link newChat}, {@link toggleCollapsed} and
9243
- * {@link toggleTheme}.
9193
+ * A host that hides `::part(header)` for its own title bar hides the history,
9194
+ * new-chat and collapse buttons with it. Each has a method so that chrome can
9195
+ * be rebuilt: this one, {@link openCheckpoints}, {@link newChat},
9196
+ * {@link toggleCollapsed} and {@link toggleTheme}.
9244
9197
  */
9245
9198
  openThreads() {
9246
9199
  void this.#refreshDrawer();
@@ -9279,7 +9232,6 @@ Use the read_attachment tool with an id to read a file's contents.`
9279
9232
  this.#toolCards.clear();
9280
9233
  this.#serverSettled.clear();
9281
9234
  this.#initialMessages = [];
9282
- this.#runAttachments = [];
9283
9235
  this.#attachTray?.clear();
9284
9236
  this.#messages.replaceChildren(this.#emptyWrap);
9285
9237
  this.#updateEmptyState();
@@ -9342,23 +9294,17 @@ Use the read_attachment tool with an id to read a file's contents.`
9342
9294
  /**
9343
9295
  * Notice a previous run that never produced a response.
9344
9296
  *
9345
- * {@link AgUiClient.send} persists the user's message *before* starting the
9346
- * run, so a transcript whose last entry is that user message means nothing
9347
- * ever came back: the page navigated or reloaded mid-run, the tab closed, or
9348
- * the process died. No extra persistence is needed to detect it — the shape
9349
- * of the transcript already says so, which is why this needs no
9350
- * {@link ClientConversationStore} method and no `pagehide` listener (neither
9351
- * of which fires on a crash or a force-quit anyway).
9297
+ * {@link AgUiClient.send} persists the user's message before starting the
9298
+ * run, so a transcript ending on that user message means nothing came back.
9299
+ * The transcript's shape alone detects it, needing no store method and no
9300
+ * `pagehide` listener neither of which fires on a crash or force-quit.
9352
9301
  *
9353
- * The *agent*-initiated reload is not this case: a navigating tool leaves a
9354
- * checkpoint and resumes, which is why the caller returns early on one before
9355
- * reaching here.
9302
+ * An agent-initiated reload is not this case: a navigating tool leaves a
9303
+ * checkpoint and resumes, so the caller returns early on one.
9356
9304
  *
9357
- * Deliberately a notice and never a resume. AG-UI has no
9358
- * resume-an-aborted-run primitive; re-sending the accumulated messages is
9359
- * semantically a **new** run, so any server-side tool the agent had already
9360
- * executed before the interruption would run a second time. Saying plainly
9361
- * that the answer was lost is the honest option, and the user can re-ask.
9305
+ * Deliberately a notice, never a resume. AG-UI has no resume-an-aborted-run
9306
+ * primitive, and re-sending the accumulated messages is semantically a new
9307
+ * run, so any server-side tool already executed would run a second time.
9362
9308
  */
9363
9309
  /**
9364
9310
  * Build a round's context, recording which page it describes.
@@ -9411,19 +9357,16 @@ Use the read_attachment tool with an id to read a file's contents.`
9411
9357
  if (text2 !== "") {
9412
9358
  this.appendMessage(MESSAGE_ROLE.ASSISTANT, text2).classList.add("message--restored");
9413
9359
  }
9414
- const toolCalls = message.toolCalls;
9415
- if (toolCalls !== void 0) {
9416
- for (const call of toolCalls) {
9417
- const restored = {
9418
- id: call.id,
9419
- name: call.function.name,
9420
- args: this.#parseArgs(call.function.arguments)
9421
- };
9422
- if (this.#noticeIfSkillLoad(restored)) {
9423
- continue;
9424
- }
9425
- this.#cardFor(restored);
9360
+ for (const call of restoredToolCalls(message.toolCalls)) {
9361
+ const restored = {
9362
+ id: call.id,
9363
+ name: call.function.name,
9364
+ args: this.#parseArgs(call.function.arguments)
9365
+ };
9366
+ if (this.#noticeIfSkillLoad(restored)) {
9367
+ continue;
9426
9368
  }
9369
+ this.#cardFor(restored);
9427
9370
  }
9428
9371
  return;
9429
9372
  }
@@ -9434,8 +9377,11 @@ Use the read_attachment tool with an id to read a file's contents.`
9434
9377
  }
9435
9378
  }
9436
9379
  }
9437
- /** Parse a tool call's JSON `arguments` string from history into an object. */
9380
+ /** Parse a tool call's JSON `arguments` from history into an object. */
9438
9381
  #parseArgs(raw) {
9382
+ if (typeof raw !== "string") {
9383
+ return {};
9384
+ }
9439
9385
  try {
9440
9386
  const parsed = JSON.parse(raw);
9441
9387
  if (typeof parsed === "object" && parsed !== null) {
@@ -9464,13 +9410,12 @@ Use the read_attachment tool with an id to read a file's contents.`
9464
9410
  /**
9465
9411
  * Append a message bubble and return it.
9466
9412
  *
9467
- * Assistant content is rendered as sanitised markdown/HTML; user content
9468
- * stays literal text (no need to parse what the user typed, and it avoids
9469
- * rendering user-authored markup).
9413
+ * Assistant content renders as sanitised markdown/HTML; user content stays
9414
+ * literal text, which also avoids rendering user-authored markup.
9470
9415
  *
9471
- * Assistant bubbles land in the current answer group, opening one if
9472
- * needed; a user bubble closes the prior group and sits directly in the list
9473
- * (the well wraps the *assistant* turn, the user message precedes it).
9416
+ * Assistant bubbles land in the current answer group, opening one if needed;
9417
+ * a user bubble closes the prior group and sits directly in the list, the
9418
+ * well wrapping only the assistant turn.
9474
9419
  */
9475
9420
  appendMessage(role, content) {
9476
9421
  const bubble = document.createElement("div");
@@ -9663,12 +9608,9 @@ Use the read_attachment tool with an id to read a file's contents.`
9663
9608
  * Build a header control button: a named slot a host can project markup into,
9664
9609
  * with the built-in glyph as the slot's fallback.
9665
9610
  *
9666
- * The glyph used to be the button's own `textContent`, which left a host able
9667
- * to restyle the control through its `part` but unable to replace it — a CSS
9668
- * `content` override could swap one character for another, and nothing could
9669
- * supply a brand `<img>` or `<svg>`. This is the same slot-with-fallback
9670
- * idiom the header icon already uses, so existing embeds render exactly as
9671
- * before.
9611
+ * The slot is what lets a host replace the mark with its own `<img>` or
9612
+ * `<svg>` rather than only restyle it through the `part`; the same
9613
+ * slot-with-fallback idiom the header icon uses.
9672
9614
  */
9673
9615
  #headerButton(modifier, label, glyph) {
9674
9616
  const button2 = document.createElement("button");
@@ -9869,19 +9811,16 @@ Use the read_attachment tool with an id to read a file's contents.`
9869
9811
  * Send a message as if the user had typed it — renders the user bubble,
9870
9812
  * dispatches {@link SUBMIT_EVENT}, and starts the run.
9871
9813
  *
9872
- * The programmatic half of the composer, for a host driving its own input
9873
- * (a "Ask about this order" button, a command palette, a custom composer
9874
- * replacing the built-in one). Everything the built-in Send does happens
9875
- * here; Send itself now reads the composer, clears it, and calls this.
9814
+ * The programmatic half of the composer, for a host driving its own input.
9815
+ * Everything the built-in Send does happens here; Send reads the composer,
9816
+ * clears it, and calls this.
9876
9817
  *
9877
- * `attachments` are durable {@link AttachmentRef}s — the shape
9878
- * {@link attachFile}'s upload resolves to, and the shape
9879
- * {@link ATTACHMENT_EVENT} reports. Pass them to attach files to the message.
9818
+ * `attachments` are durable {@link AttachmentRef}s — what {@link attachFile}
9819
+ * resolves to and what {@link ATTACHMENT_EVENT} reports.
9880
9820
  *
9881
- * No-ops while a run is in flight (a second concurrent run would orphan the
9882
- * first) and for an entirely empty message. Unlike the built-in Send, this
9883
- * does **not** consult the tray: what you pass is what is sent, so a host
9884
- * composer stays in charge of its own state.
9821
+ * No-ops on an empty message, and while a run is in flight, since a second
9822
+ * concurrent run would orphan the first. Unlike the built-in Send it does not
9823
+ * consult the tray: what you pass is what is sent.
9885
9824
  */
9886
9825
  async sendMessage(content, attachments = []) {
9887
9826
  if (this.#running || content === "" && attachments.length === 0) {
@@ -9891,7 +9830,6 @@ Use the read_attachment tool with an id to read a file's contents.`
9891
9830
  if (attachments.length > 0) {
9892
9831
  bubble.appendChild(renderAttachmentChips(attachments));
9893
9832
  }
9894
- this.#runAttachments = attachments;
9895
9833
  this.dispatchEvent(
9896
9834
  new CustomEvent(SUBMIT_EVENT, {
9897
9835
  detail: { content, attachments },
@@ -9906,11 +9844,11 @@ Use the read_attachment tool with an id to read a file's contents.`
9906
9844
  * picker and drag-and-drop do — validation, progress chip, and all.
9907
9845
  *
9908
9846
  * Returns `false` when uploads are not configured (no `data-attachments-url`
9909
- * and no {@link uploadHandler}), which is the only way for a host to tell;
9910
- * the tray does not exist to report anything in that case.
9847
+ * and no {@link uploadHandler}) the only signal a host gets, since the tray
9848
+ * does not exist to report anything then.
9911
9849
  *
9912
9850
  * Uploading is asynchronous: watch {@link ATTACHMENT_EVENT} for the resulting
9913
- * {@link AttachmentRef}, and pass it to {@link sendMessage} once `pending`
9851
+ * {@link AttachmentRef} and pass it to {@link sendMessage} once `pending`
9914
9852
  * reaches zero.
9915
9853
  */
9916
9854
  attachFile(file) {
@@ -10055,12 +9993,11 @@ Use the read_attachment tool with an id to read a file's contents.`
10055
9993
  * Render an approval card per server-side-tool interrupt and collect the
10056
9994
  * user's decisions (approve → run it, deny → decline it).
10057
9995
  *
10058
- * The run is suspended on these cards; a Stop while any is open aborts the
9996
+ * The run is suspended on these cards. A Stop while any is open aborts the
10059
9997
  * shared {@link #confirmAbort} controller, resolving every still-open card as
10060
- * denied (and the client loop then sees the cancellation and stops). An
10061
- * approved tool runs on the follow-up (resume) run and streams its result
10062
- * back into the same pending card; a denied one is settled here, since no
10063
- * result will ever arrive for it.
9998
+ * denied. An approved tool runs on the follow-up resume run and streams its
9999
+ * result into the same pending card; a denied one settles here, since no
10000
+ * result will ever arrive.
10064
10001
  */
10065
10002
  async #resolveInterrupts(interrupts) {
10066
10003
  const responses = {};
@@ -10169,7 +10106,6 @@ Use the read_attachment tool with an id to read a file's contents.`
10169
10106
  this.#hidePending();
10170
10107
  this.#setRunning(false);
10171
10108
  this.#streamingBubble = null;
10172
- this.#runAttachments = [];
10173
10109
  for (const card of this.#toolCards.values()) {
10174
10110
  if (!card.settled) {
10175
10111
  card.settle(TOOL_CALL_STATUS.DONE, this.#strings.noResult);
@@ -10295,6 +10231,16 @@ Use the read_attachment tool with an id to read a file's contents.`
10295
10231
  return card;
10296
10232
  }
10297
10233
  };
10234
+ function restoredToolCalls(value) {
10235
+ return Array.isArray(value) ? value.filter(isRestoredToolCall) : [];
10236
+ }
10237
+ function isRestoredToolCall(value) {
10238
+ if (typeof value !== "object" || value === null) {
10239
+ return false;
10240
+ }
10241
+ const call = value;
10242
+ return typeof call.id === "string" && typeof call.function?.name === "string";
10243
+ }
10298
10244
  function skillNameFrom(call) {
10299
10245
  if (call.name !== LOAD_CAPABILITY_TOOL) {
10300
10246
  return null;
@@ -10347,7 +10293,7 @@ function setControlValue(el, value) {
10347
10293
  }
10348
10294
 
10349
10295
  // src/version.ts
10350
- var VERSION = "0.22.0";
10296
+ var VERSION = "0.23.1";
10351
10297
  export {
10352
10298
  ATTACHMENT_EVENT,
10353
10299
  AgUiChat,