@artooi/agents-web-components 0.0.0-stage → 0.1.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 (543) hide show
  1. package/CHANGELOG.md +68 -0
  2. package/LICENSE +21 -0
  3. package/README.md +4397 -2
  4. package/dist/agents-web-components.bundle.js +5708 -0
  5. package/dist/agents-web-components.bundle.js.map +7 -0
  6. package/dist/bundle.d.ts +3 -0
  7. package/dist/bundle.d.ts.map +1 -0
  8. package/dist/chat.d.ts +3 -0
  9. package/dist/chat.d.ts.map +1 -0
  10. package/dist/chat.js +16373 -0
  11. package/dist/chat.js.map +7 -0
  12. package/dist/chunk-ARNLFGCC.js +7715 -0
  13. package/dist/chunk-ARNLFGCC.js.map +7 -0
  14. package/dist/constants.d.ts +575 -0
  15. package/dist/constants.d.ts.map +1 -0
  16. package/dist/core/activity_registration.d.ts +19 -0
  17. package/dist/core/activity_registration.d.ts.map +1 -0
  18. package/dist/core/activity_registry.d.ts +46 -0
  19. package/dist/core/activity_registry.d.ts.map +1 -0
  20. package/dist/core/activity_renderer.d.ts +20 -0
  21. package/dist/core/activity_renderer.d.ts.map +1 -0
  22. package/dist/core/ag_ui_chat.d.ts +836 -0
  23. package/dist/core/ag_ui_chat.d.ts.map +1 -0
  24. package/dist/core/agui_client.d.ts +370 -0
  25. package/dist/core/agui_client.d.ts.map +1 -0
  26. package/dist/core/attachment.d.ts +42 -0
  27. package/dist/core/attachment.d.ts.map +1 -0
  28. package/dist/core/chat_registry.d.ts +23 -0
  29. package/dist/core/chat_registry.d.ts.map +1 -0
  30. package/dist/core/client_seed.d.ts +27 -0
  31. package/dist/core/client_seed.d.ts.map +1 -0
  32. package/dist/core/conversation_store.d.ts +195 -0
  33. package/dist/core/conversation_store.d.ts.map +1 -0
  34. package/dist/core/create_http_agent.d.ts +57 -0
  35. package/dist/core/create_http_agent.d.ts.map +1 -0
  36. package/dist/core/define_chat.d.ts +13 -0
  37. package/dist/core/define_chat.d.ts.map +1 -0
  38. package/dist/core/events/attachments_detail.d.ts +9 -0
  39. package/dist/core/events/attachments_detail.d.ts.map +1 -0
  40. package/dist/core/events/bar_height_detail.d.ts +6 -0
  41. package/dist/core/events/bar_height_detail.d.ts.map +1 -0
  42. package/dist/core/events/custom_agent_detail.d.ts +8 -0
  43. package/dist/core/events/custom_agent_detail.d.ts.map +1 -0
  44. package/dist/core/events/dock_width_detail.d.ts +8 -0
  45. package/dist/core/events/dock_width_detail.d.ts.map +1 -0
  46. package/dist/core/events/feedback_detail.d.ts +28 -0
  47. package/dist/core/events/feedback_detail.d.ts.map +1 -0
  48. package/dist/core/events/invalidate_detail.d.ts +16 -0
  49. package/dist/core/events/invalidate_detail.d.ts.map +1 -0
  50. package/dist/core/events/maximize_detail.d.ts +5 -0
  51. package/dist/core/events/maximize_detail.d.ts.map +1 -0
  52. package/dist/core/events/run_finished_detail.d.ts +36 -0
  53. package/dist/core/events/run_finished_detail.d.ts.map +1 -0
  54. package/dist/core/events/state_detail.d.ts +5 -0
  55. package/dist/core/events/state_detail.d.ts.map +1 -0
  56. package/dist/core/events/submit_detail.d.ts +8 -0
  57. package/dist/core/events/submit_detail.d.ts.map +1 -0
  58. package/dist/core/events/thread_detail.d.ts +27 -0
  59. package/dist/core/events/thread_detail.d.ts.map +1 -0
  60. package/dist/core/events/thread_title_detail.d.ts +8 -0
  61. package/dist/core/events/thread_title_detail.d.ts.map +1 -0
  62. package/dist/core/events/toggle_detail.d.ts +5 -0
  63. package/dist/core/events/toggle_detail.d.ts.map +1 -0
  64. package/dist/core/events/tool_run.d.ts +11 -0
  65. package/dist/core/events/tool_run.d.ts.map +1 -0
  66. package/dist/core/events/unread_detail.d.ts +11 -0
  67. package/dist/core/events/unread_detail.d.ts.map +1 -0
  68. package/dist/core/message_role.d.ts +4 -0
  69. package/dist/core/message_role.d.ts.map +1 -0
  70. package/dist/core/read_flag.d.ts +11 -0
  71. package/dist/core/read_flag.d.ts.map +1 -0
  72. package/dist/core/read_json_attribute.d.ts +15 -0
  73. package/dist/core/read_json_attribute.d.ts.map +1 -0
  74. package/dist/core/read_max_tool_rounds.d.ts +9 -0
  75. package/dist/core/read_max_tool_rounds.d.ts.map +1 -0
  76. package/dist/core/remote_conversation_store.d.ts +71 -0
  77. package/dist/core/remote_conversation_store.d.ts.map +1 -0
  78. package/dist/core/run_handlers.d.ts +85 -0
  79. package/dist/core/run_handlers.d.ts.map +1 -0
  80. package/dist/core/run_index.d.ts +58 -0
  81. package/dist/core/run_index.d.ts.map +1 -0
  82. package/dist/core/storage_scope.d.ts +118 -0
  83. package/dist/core/storage_scope.d.ts.map +1 -0
  84. package/dist/core/tool_outcome.d.ts +27 -0
  85. package/dist/core/tool_outcome.d.ts.map +1 -0
  86. package/dist/core/transcribe_audio.d.ts +32 -0
  87. package/dist/core/transcribe_audio.d.ts.map +1 -0
  88. package/dist/core/upload_attachment.d.ts +44 -0
  89. package/dist/core/upload_attachment.d.ts.map +1 -0
  90. package/dist/core/utils.d.ts +117 -0
  91. package/dist/core/utils.d.ts.map +1 -0
  92. package/dist/dom/animations.d.ts +122 -0
  93. package/dist/dom/animations.d.ts.map +1 -0
  94. package/dist/dom/dom_driver.d.ts +28 -0
  95. package/dist/dom/dom_driver.d.ts.map +1 -0
  96. package/dist/dom/highlight_overlay.d.ts +39 -0
  97. package/dist/dom/highlight_overlay.d.ts.map +1 -0
  98. package/dist/dom/native_setter.d.ts +5 -0
  99. package/dist/dom/native_setter.d.ts.map +1 -0
  100. package/dist/dom/ring_color.d.ts +16 -0
  101. package/dist/dom/ring_color.d.ts.map +1 -0
  102. package/dist/dom/utils.d.ts +3 -0
  103. package/dist/dom/utils.d.ts.map +1 -0
  104. package/dist/index.d.ts +62 -0
  105. package/dist/index.d.ts.map +1 -0
  106. package/dist/index.js +223 -0
  107. package/dist/index.js.map +7 -0
  108. package/dist/skills/fill_template.d.ts +16 -0
  109. package/dist/skills/fill_template.d.ts.map +1 -0
  110. package/dist/skills/parse_skills.d.ts +8 -0
  111. package/dist/skills/parse_skills.d.ts.map +1 -0
  112. package/dist/skills/skill.d.ts +33 -0
  113. package/dist/skills/skill.d.ts.map +1 -0
  114. package/dist/skills/skill_catalog.d.ts +69 -0
  115. package/dist/skills/skill_catalog.d.ts.map +1 -0
  116. package/dist/skills/skill_name_from.d.ts +12 -0
  117. package/dist/skills/skill_name_from.d.ts.map +1 -0
  118. package/dist/tools/chat_surface_tools.d.ts +77 -0
  119. package/dist/tools/chat_surface_tools.d.ts.map +1 -0
  120. package/dist/tools/client_tool_registry.d.ts +58 -0
  121. package/dist/tools/client_tool_registry.d.ts.map +1 -0
  122. package/dist/tools/is_destructive.d.ts +14 -0
  123. package/dist/tools/is_destructive.d.ts.map +1 -0
  124. package/dist/tools/is_navigates.d.ts +9 -0
  125. package/dist/tools/is_navigates.d.ts.map +1 -0
  126. package/dist/tools/page_action_tools.d.ts +30 -0
  127. package/dist/tools/page_action_tools.d.ts.map +1 -0
  128. package/dist/tools/page_map.d.ts +16 -0
  129. package/dist/tools/page_map.d.ts.map +1 -0
  130. package/dist/tools/page_state.d.ts +27 -0
  131. package/dist/tools/page_state.d.ts.map +1 -0
  132. package/dist/tools/parse_tool_catalog.d.ts +21 -0
  133. package/dist/tools/parse_tool_catalog.d.ts.map +1 -0
  134. package/dist/tools/route_map.d.ts +37 -0
  135. package/dist/tools/route_map.d.ts.map +1 -0
  136. package/dist/tools/tool_catalog.d.ts +86 -0
  137. package/dist/tools/tool_catalog.d.ts.map +1 -0
  138. package/dist/tools/tool_dispatch.d.ts +82 -0
  139. package/dist/tools/tool_dispatch.d.ts.map +1 -0
  140. package/dist/ui/charts/chart_block.d.ts +57 -0
  141. package/dist/ui/charts/chart_block.d.ts.map +1 -0
  142. package/dist/ui/charts/chart_spec_from.d.ts +12 -0
  143. package/dist/ui/charts/chart_spec_from.d.ts.map +1 -0
  144. package/dist/ui/charts/chart_styles.d.ts +2 -0
  145. package/dist/ui/charts/chart_styles.d.ts.map +1 -0
  146. package/dist/ui/charts/chart_tool.d.ts +15 -0
  147. package/dist/ui/charts/chart_tool.d.ts.map +1 -0
  148. package/dist/ui/composer/attachment_styles.d.ts +2 -0
  149. package/dist/ui/composer/attachment_styles.d.ts.map +1 -0
  150. package/dist/ui/composer/attachment_tray.d.ts +53 -0
  151. package/dist/ui/composer/attachment_tray.d.ts.map +1 -0
  152. package/dist/ui/composer/auto_grow.d.ts +10 -0
  153. package/dist/ui/composer/auto_grow.d.ts.map +1 -0
  154. package/dist/ui/composer/build_composer.d.ts +32 -0
  155. package/dist/ui/composer/build_composer.d.ts.map +1 -0
  156. package/dist/ui/composer/composer_attachments.d.ts +57 -0
  157. package/dist/ui/composer/composer_attachments.d.ts.map +1 -0
  158. package/dist/ui/composer/composer_styles.d.ts +2 -0
  159. package/dist/ui/composer/composer_styles.d.ts.map +1 -0
  160. package/dist/ui/composer/composer_voice.d.ts +48 -0
  161. package/dist/ui/composer/composer_voice.d.ts.map +1 -0
  162. package/dist/ui/composer/skill_styles.d.ts +2 -0
  163. package/dist/ui/composer/skill_styles.d.ts.map +1 -0
  164. package/dist/ui/composer/skills_menu.d.ts +34 -0
  165. package/dist/ui/composer/skills_menu.d.ts.map +1 -0
  166. package/dist/ui/composer/voice_input.d.ts +46 -0
  167. package/dist/ui/composer/voice_input.d.ts.map +1 -0
  168. package/dist/ui/excerpts/attach_copy_buttons.d.ts +13 -0
  169. package/dist/ui/excerpts/attach_copy_buttons.d.ts.map +1 -0
  170. package/dist/ui/excerpts/copy_payload.d.ts +30 -0
  171. package/dist/ui/excerpts/copy_payload.d.ts.map +1 -0
  172. package/dist/ui/excerpts/page_quote_offer.d.ts +33 -0
  173. package/dist/ui/excerpts/page_quote_offer.d.ts.map +1 -0
  174. package/dist/ui/excerpts/place_offer.d.ts +9 -0
  175. package/dist/ui/excerpts/place_offer.d.ts.map +1 -0
  176. package/dist/ui/excerpts/quote_selection.d.ts +66 -0
  177. package/dist/ui/excerpts/quote_selection.d.ts.map +1 -0
  178. package/dist/ui/excerpts/transcript_quote_offer.d.ts +57 -0
  179. package/dist/ui/excerpts/transcript_quote_offer.d.ts.map +1 -0
  180. package/dist/ui/fill_ui_string.d.ts +32 -0
  181. package/dist/ui/fill_ui_string.d.ts.map +1 -0
  182. package/dist/ui/history/checkpoint_menu.d.ts +41 -0
  183. package/dist/ui/history/checkpoint_menu.d.ts.map +1 -0
  184. package/dist/ui/history/conversation_history.d.ts +317 -0
  185. package/dist/ui/history/conversation_history.d.ts.map +1 -0
  186. package/dist/ui/history/history_styles.d.ts +2 -0
  187. package/dist/ui/history/history_styles.d.ts.map +1 -0
  188. package/dist/ui/history/relative_time.d.ts +23 -0
  189. package/dist/ui/history/relative_time.d.ts.map +1 -0
  190. package/dist/ui/history/thread_drawer.d.ts +64 -0
  191. package/dist/ui/history/thread_drawer.d.ts.map +1 -0
  192. package/dist/ui/history/utils.d.ts +2 -0
  193. package/dist/ui/history/utils.d.ts.map +1 -0
  194. package/dist/ui/interrupts/approval_card.d.ts +64 -0
  195. package/dist/ui/interrupts/approval_card.d.ts.map +1 -0
  196. package/dist/ui/interrupts/approval_styles.d.ts +2 -0
  197. package/dist/ui/interrupts/approval_styles.d.ts.map +1 -0
  198. package/dist/ui/interrupts/confirmation_card.d.ts +51 -0
  199. package/dist/ui/interrupts/confirmation_card.d.ts.map +1 -0
  200. package/dist/ui/interrupts/confirmation_styles.d.ts +2 -0
  201. package/dist/ui/interrupts/confirmation_styles.d.ts.map +1 -0
  202. package/dist/ui/interrupts/pending_decision.d.ts +22 -0
  203. package/dist/ui/interrupts/pending_decision.d.ts.map +1 -0
  204. package/dist/ui/interrupts/question_card.d.ts +49 -0
  205. package/dist/ui/interrupts/question_card.d.ts.map +1 -0
  206. package/dist/ui/interrupts/question_styles.d.ts +2 -0
  207. package/dist/ui/interrupts/question_styles.d.ts.map +1 -0
  208. package/dist/ui/place/place.d.ts +17 -0
  209. package/dist/ui/place/place.d.ts.map +1 -0
  210. package/dist/ui/place/place_chip.d.ts +26 -0
  211. package/dist/ui/place/place_chip.d.ts.map +1 -0
  212. package/dist/ui/place/place_from_route.d.ts +9 -0
  213. package/dist/ui/place/place_from_route.d.ts.map +1 -0
  214. package/dist/ui/place/place_styles.d.ts +2 -0
  215. package/dist/ui/place/place_styles.d.ts.map +1 -0
  216. package/dist/ui/placement/clamp_launcher.d.ts +20 -0
  217. package/dist/ui/placement/clamp_launcher.d.ts.map +1 -0
  218. package/dist/ui/placement/clamp_panel.d.ts +13 -0
  219. package/dist/ui/placement/clamp_panel.d.ts.map +1 -0
  220. package/dist/ui/placement/collapse_styles.d.ts +2 -0
  221. package/dist/ui/placement/collapse_styles.d.ts.map +1 -0
  222. package/dist/ui/placement/dock_styles.d.ts +2 -0
  223. package/dist/ui/placement/dock_styles.d.ts.map +1 -0
  224. package/dist/ui/placement/embedded_styles.d.ts +2 -0
  225. package/dist/ui/placement/embedded_styles.d.ts.map +1 -0
  226. package/dist/ui/placement/launcher_drag.d.ts +49 -0
  227. package/dist/ui/placement/launcher_drag.d.ts.map +1 -0
  228. package/dist/ui/placement/launcher_placement.d.ts +77 -0
  229. package/dist/ui/placement/launcher_placement.d.ts.map +1 -0
  230. package/dist/ui/placement/launcher_styles.d.ts +2 -0
  231. package/dist/ui/placement/launcher_styles.d.ts.map +1 -0
  232. package/dist/ui/placement/page_styles.d.ts +2 -0
  233. package/dist/ui/placement/page_styles.d.ts.map +1 -0
  234. package/dist/ui/placement/panel_drag.d.ts +40 -0
  235. package/dist/ui/placement/panel_drag.d.ts.map +1 -0
  236. package/dist/ui/placement/panel_placement.d.ts +173 -0
  237. package/dist/ui/placement/panel_placement.d.ts.map +1 -0
  238. package/dist/ui/placement/place_widget.d.ts +31 -0
  239. package/dist/ui/placement/place_widget.d.ts.map +1 -0
  240. package/dist/ui/placement/preset_styles.d.ts +2 -0
  241. package/dist/ui/placement/preset_styles.d.ts.map +1 -0
  242. package/dist/ui/placement/resize_handle.d.ts +78 -0
  243. package/dist/ui/placement/resize_handle.d.ts.map +1 -0
  244. package/dist/ui/placement/resize_styles.d.ts +2 -0
  245. package/dist/ui/placement/resize_styles.d.ts.map +1 -0
  246. package/dist/ui/placement/sidebar_styles.d.ts +2 -0
  247. package/dist/ui/placement/sidebar_styles.d.ts.map +1 -0
  248. package/dist/ui/placement/small_viewport_styles.d.ts +2 -0
  249. package/dist/ui/placement/small_viewport_styles.d.ts.map +1 -0
  250. package/dist/ui/placement/surface_for.d.ts +93 -0
  251. package/dist/ui/placement/surface_for.d.ts.map +1 -0
  252. package/dist/ui/progress/prettify_tool_name.d.ts +11 -0
  253. package/dist/ui/progress/prettify_tool_name.d.ts.map +1 -0
  254. package/dist/ui/progress/run_announcer.d.ts +41 -0
  255. package/dist/ui/progress/run_announcer.d.ts.map +1 -0
  256. package/dist/ui/progress/run_notice.d.ts +21 -0
  257. package/dist/ui/progress/run_notice.d.ts.map +1 -0
  258. package/dist/ui/progress/run_notice_styles.d.ts +2 -0
  259. package/dist/ui/progress/run_notice_styles.d.ts.map +1 -0
  260. package/dist/ui/progress/subagent_panel.d.ts +101 -0
  261. package/dist/ui/progress/subagent_panel.d.ts.map +1 -0
  262. package/dist/ui/progress/subagent_progress.d.ts +57 -0
  263. package/dist/ui/progress/subagent_progress.d.ts.map +1 -0
  264. package/dist/ui/progress/subagent_update.d.ts +28 -0
  265. package/dist/ui/progress/subagent_update.d.ts.map +1 -0
  266. package/dist/ui/progress/thoughts_block.d.ts +57 -0
  267. package/dist/ui/progress/thoughts_block.d.ts.map +1 -0
  268. package/dist/ui/progress/thoughts_styles.d.ts +2 -0
  269. package/dist/ui/progress/thoughts_styles.d.ts.map +1 -0
  270. package/dist/ui/progress/tool_call_card.d.ts +167 -0
  271. package/dist/ui/progress/tool_call_card.d.ts.map +1 -0
  272. package/dist/ui/progress/tool_group_styles.d.ts +2 -0
  273. package/dist/ui/progress/tool_group_styles.d.ts.map +1 -0
  274. package/dist/ui/progress/tool_step_group.d.ts +50 -0
  275. package/dist/ui/progress/tool_step_group.d.ts.map +1 -0
  276. package/dist/ui/progress/tool_step_styles.d.ts +2 -0
  277. package/dist/ui/progress/tool_step_styles.d.ts.map +1 -0
  278. package/dist/ui/shell/adopt_styles.d.ts +28 -0
  279. package/dist/ui/shell/adopt_styles.d.ts.map +1 -0
  280. package/dist/ui/shell/build_header.d.ts +45 -0
  281. package/dist/ui/shell/build_header.d.ts.map +1 -0
  282. package/dist/ui/shell/build_launcher.d.ts +33 -0
  283. package/dist/ui/shell/build_launcher.d.ts.map +1 -0
  284. package/dist/ui/shell/collapse_glyph.d.ts +14 -0
  285. package/dist/ui/shell/collapse_glyph.d.ts.map +1 -0
  286. package/dist/ui/shell/frame_styles.d.ts +2 -0
  287. package/dist/ui/shell/frame_styles.d.ts.map +1 -0
  288. package/dist/ui/shell/glyph_slot.d.ts +7 -0
  289. package/dist/ui/shell/glyph_slot.d.ts.map +1 -0
  290. package/dist/ui/shell/header_button.d.ts +11 -0
  291. package/dist/ui/shell/header_button.d.ts.map +1 -0
  292. package/dist/ui/shell/icon_element.d.ts +12 -0
  293. package/dist/ui/shell/icon_element.d.ts.map +1 -0
  294. package/dist/ui/shell/is_unread_badge_enabled.d.ts +10 -0
  295. package/dist/ui/shell/is_unread_badge_enabled.d.ts.map +1 -0
  296. package/dist/ui/shell/read_launcher_icon_url.d.ts +8 -0
  297. package/dist/ui/shell/read_launcher_icon_url.d.ts.map +1 -0
  298. package/dist/ui/shell/theme_styles.d.ts +2 -0
  299. package/dist/ui/shell/theme_styles.d.ts.map +1 -0
  300. package/dist/ui/shell/token_styles.d.ts +2 -0
  301. package/dist/ui/shell/token_styles.d.ts.map +1 -0
  302. package/dist/ui/styles.d.ts +2 -0
  303. package/dist/ui/styles.d.ts.map +1 -0
  304. package/dist/ui/themes/classic_styles.d.ts +2 -0
  305. package/dist/ui/themes/classic_styles.d.ts.map +1 -0
  306. package/dist/ui/themes/classic_tokens.d.ts +2 -0
  307. package/dist/ui/themes/classic_tokens.d.ts.map +1 -0
  308. package/dist/ui/themes/code_tokens.d.ts +2 -0
  309. package/dist/ui/themes/code_tokens.d.ts.map +1 -0
  310. package/dist/ui/themes/ledger_styles.d.ts +2 -0
  311. package/dist/ui/themes/ledger_styles.d.ts.map +1 -0
  312. package/dist/ui/themes/ledger_tokens.d.ts +2 -0
  313. package/dist/ui/themes/ledger_tokens.d.ts.map +1 -0
  314. package/dist/ui/themes/modern_styles.d.ts +2 -0
  315. package/dist/ui/themes/modern_styles.d.ts.map +1 -0
  316. package/dist/ui/themes/quiet_styles.d.ts +2 -0
  317. package/dist/ui/themes/quiet_styles.d.ts.map +1 -0
  318. package/dist/ui/themes/scheme_styles.d.ts +2 -0
  319. package/dist/ui/themes/scheme_styles.d.ts.map +1 -0
  320. package/dist/ui/themes/soft_styles.d.ts +2 -0
  321. package/dist/ui/themes/soft_styles.d.ts.map +1 -0
  322. package/dist/ui/themes/soft_tokens.d.ts +2 -0
  323. package/dist/ui/themes/soft_tokens.d.ts.map +1 -0
  324. package/dist/ui/themes/theme_hosts.d.ts +15 -0
  325. package/dist/ui/themes/theme_hosts.d.ts.map +1 -0
  326. package/dist/ui/transcript/answer_actions.d.ts +50 -0
  327. package/dist/ui/transcript/answer_actions.d.ts.map +1 -0
  328. package/dist/ui/transcript/answer_stream.d.ts +56 -0
  329. package/dist/ui/transcript/answer_stream.d.ts.map +1 -0
  330. package/dist/ui/transcript/attachment_chip_styles.d.ts +2 -0
  331. package/dist/ui/transcript/attachment_chip_styles.d.ts.map +1 -0
  332. package/dist/ui/transcript/attachment_chips.d.ts +21 -0
  333. package/dist/ui/transcript/attachment_chips.d.ts.map +1 -0
  334. package/dist/ui/transcript/build_transcript.d.ts +21 -0
  335. package/dist/ui/transcript/build_transcript.d.ts.map +1 -0
  336. package/dist/ui/transcript/greeting_styles.d.ts +2 -0
  337. package/dist/ui/transcript/greeting_styles.d.ts.map +1 -0
  338. package/dist/ui/transcript/groups_tool_steps.d.ts +13 -0
  339. package/dist/ui/transcript/groups_tool_steps.d.ts.map +1 -0
  340. package/dist/ui/transcript/message_action_styles.d.ts +2 -0
  341. package/dist/ui/transcript/message_action_styles.d.ts.map +1 -0
  342. package/dist/ui/transcript/message_actions.d.ts +74 -0
  343. package/dist/ui/transcript/message_actions.d.ts.map +1 -0
  344. package/dist/ui/transcript/message_styles.d.ts +2 -0
  345. package/dist/ui/transcript/message_styles.d.ts.map +1 -0
  346. package/dist/ui/transcript/render_markdown.d.ts +40 -0
  347. package/dist/ui/transcript/render_markdown.d.ts.map +1 -0
  348. package/dist/ui/transcript/render_or_warn.d.ts +16 -0
  349. package/dist/ui/transcript/render_or_warn.d.ts.map +1 -0
  350. package/dist/ui/transcript/reveal_words.d.ts +8 -0
  351. package/dist/ui/transcript/reveal_words.d.ts.map +1 -0
  352. package/dist/ui/transcript/starter_chips.d.ts +21 -0
  353. package/dist/ui/transcript/starter_chips.d.ts.map +1 -0
  354. package/dist/ui/transcript/stick_to_bottom.d.ts +55 -0
  355. package/dist/ui/transcript/stick_to_bottom.d.ts.map +1 -0
  356. package/dist/ui/transcript/suggestion_chips.d.ts +29 -0
  357. package/dist/ui/transcript/suggestion_chips.d.ts.map +1 -0
  358. package/dist/ui/transcript/transcript.d.ts +196 -0
  359. package/dist/ui/transcript/transcript.d.ts.map +1 -0
  360. package/dist/ui/transcript/transcript_styles.d.ts +2 -0
  361. package/dist/ui/transcript/transcript_styles.d.ts.map +1 -0
  362. package/dist/ui/ui_strings.d.ts +352 -0
  363. package/dist/ui/ui_strings.d.ts.map +1 -0
  364. package/dist/version.d.ts +2 -0
  365. package/dist/version.d.ts.map +1 -0
  366. package/package.json +86 -3
  367. package/src/bundle.ts +2 -0
  368. package/src/chat.ts +2 -0
  369. package/src/constants.ts +650 -0
  370. package/src/core/activity_registration.ts +19 -0
  371. package/src/core/activity_registry.ts +133 -0
  372. package/src/core/activity_renderer.ts +19 -0
  373. package/src/core/ag_ui_chat.ts +3966 -0
  374. package/src/core/agui_client.ts +1028 -0
  375. package/src/core/attachment.ts +62 -0
  376. package/src/core/chat_registry.ts +53 -0
  377. package/src/core/client_seed.ts +27 -0
  378. package/src/core/conversation_store.ts +478 -0
  379. package/src/core/create_http_agent.ts +97 -0
  380. package/src/core/define_chat.ts +25 -0
  381. package/src/core/events/attachments_detail.ts +9 -0
  382. package/src/core/events/bar_height_detail.ts +5 -0
  383. package/src/core/events/custom_agent_detail.ts +7 -0
  384. package/src/core/events/dock_width_detail.ts +7 -0
  385. package/src/core/events/feedback_detail.ts +27 -0
  386. package/src/core/events/invalidate_detail.ts +15 -0
  387. package/src/core/events/maximize_detail.ts +4 -0
  388. package/src/core/events/run_finished_detail.ts +36 -0
  389. package/src/core/events/state_detail.ts +4 -0
  390. package/src/core/events/submit_detail.ts +8 -0
  391. package/src/core/events/thread_detail.ts +26 -0
  392. package/src/core/events/thread_title_detail.ts +7 -0
  393. package/src/core/events/toggle_detail.ts +4 -0
  394. package/src/core/events/tool_run.ts +10 -0
  395. package/src/core/events/unread_detail.ts +10 -0
  396. package/src/core/message_role.ts +4 -0
  397. package/src/core/read_flag.ts +13 -0
  398. package/src/core/read_json_attribute.ts +30 -0
  399. package/src/core/read_max_tool_rounds.ts +13 -0
  400. package/src/core/remote_conversation_store.ts +279 -0
  401. package/src/core/run_handlers.ts +524 -0
  402. package/src/core/run_index.ts +114 -0
  403. package/src/core/storage_scope.ts +291 -0
  404. package/src/core/tool_outcome.ts +39 -0
  405. package/src/core/transcribe_audio.ts +76 -0
  406. package/src/core/upload_attachment.ts +130 -0
  407. package/src/core/utils.ts +230 -0
  408. package/src/dom/animations.ts +341 -0
  409. package/src/dom/dom_driver.ts +91 -0
  410. package/src/dom/highlight_overlay.ts +204 -0
  411. package/src/dom/native_setter.ts +40 -0
  412. package/src/dom/ring_color.ts +206 -0
  413. package/src/dom/utils.ts +5 -0
  414. package/src/index.ts +220 -0
  415. package/src/skills/fill_template.ts +31 -0
  416. package/src/skills/parse_skills.ts +31 -0
  417. package/src/skills/skill.ts +32 -0
  418. package/src/skills/skill_catalog.ts +172 -0
  419. package/src/skills/skill_name_from.ts +19 -0
  420. package/src/tools/chat_surface_tools.ts +183 -0
  421. package/src/tools/client_tool_registry.ts +80 -0
  422. package/src/tools/is_destructive.ts +17 -0
  423. package/src/tools/is_navigates.ts +12 -0
  424. package/src/tools/page_action_tools.ts +123 -0
  425. package/src/tools/page_map.ts +26 -0
  426. package/src/tools/page_state.ts +57 -0
  427. package/src/tools/parse_tool_catalog.ts +41 -0
  428. package/src/tools/route_map.ts +132 -0
  429. package/src/tools/tool_catalog.ts +334 -0
  430. package/src/tools/tool_dispatch.ts +453 -0
  431. package/src/ui/charts/chart_block.ts +519 -0
  432. package/src/ui/charts/chart_spec_from.ts +125 -0
  433. package/src/ui/charts/chart_styles.ts +63 -0
  434. package/src/ui/charts/chart_tool.ts +72 -0
  435. package/src/ui/composer/attachment_styles.ts +71 -0
  436. package/src/ui/composer/attachment_tray.ts +295 -0
  437. package/src/ui/composer/auto_grow.ts +12 -0
  438. package/src/ui/composer/build_composer.ts +114 -0
  439. package/src/ui/composer/composer_attachments.ts +324 -0
  440. package/src/ui/composer/composer_styles.ts +177 -0
  441. package/src/ui/composer/composer_voice.ts +93 -0
  442. package/src/ui/composer/skill_styles.ts +120 -0
  443. package/src/ui/composer/skills_menu.ts +188 -0
  444. package/src/ui/composer/voice_input.ts +282 -0
  445. package/src/ui/excerpts/attach_copy_buttons.ts +73 -0
  446. package/src/ui/excerpts/copy_payload.ts +156 -0
  447. package/src/ui/excerpts/page_quote_offer.ts +203 -0
  448. package/src/ui/excerpts/place_offer.ts +24 -0
  449. package/src/ui/excerpts/quote_selection.ts +345 -0
  450. package/src/ui/excerpts/transcript_quote_offer.ts +175 -0
  451. package/src/ui/fill_ui_string.ts +41 -0
  452. package/src/ui/history/checkpoint_menu.ts +300 -0
  453. package/src/ui/history/conversation_history.ts +1169 -0
  454. package/src/ui/history/history_styles.ts +492 -0
  455. package/src/ui/history/relative_time.ts +50 -0
  456. package/src/ui/history/thread_drawer.ts +471 -0
  457. package/src/ui/history/utils.ts +11 -0
  458. package/src/ui/interrupts/approval_card.ts +202 -0
  459. package/src/ui/interrupts/approval_styles.ts +52 -0
  460. package/src/ui/interrupts/confirmation_card.ts +138 -0
  461. package/src/ui/interrupts/confirmation_styles.ts +114 -0
  462. package/src/ui/interrupts/pending_decision.ts +33 -0
  463. package/src/ui/interrupts/question_card.ts +213 -0
  464. package/src/ui/interrupts/question_styles.ts +73 -0
  465. package/src/ui/place/place.ts +16 -0
  466. package/src/ui/place/place_chip.ts +146 -0
  467. package/src/ui/place/place_from_route.ts +27 -0
  468. package/src/ui/place/place_styles.ts +108 -0
  469. package/src/ui/placement/clamp_launcher.ts +38 -0
  470. package/src/ui/placement/clamp_panel.ts +30 -0
  471. package/src/ui/placement/collapse_styles.ts +472 -0
  472. package/src/ui/placement/dock_styles.ts +116 -0
  473. package/src/ui/placement/embedded_styles.ts +14 -0
  474. package/src/ui/placement/launcher_drag.ts +206 -0
  475. package/src/ui/placement/launcher_placement.ts +133 -0
  476. package/src/ui/placement/launcher_styles.ts +189 -0
  477. package/src/ui/placement/page_styles.ts +47 -0
  478. package/src/ui/placement/panel_drag.ts +138 -0
  479. package/src/ui/placement/panel_placement.ts +1975 -0
  480. package/src/ui/placement/place_widget.ts +64 -0
  481. package/src/ui/placement/preset_styles.ts +49 -0
  482. package/src/ui/placement/resize_handle.ts +229 -0
  483. package/src/ui/placement/resize_styles.ts +201 -0
  484. package/src/ui/placement/sidebar_styles.ts +113 -0
  485. package/src/ui/placement/small_viewport_styles.ts +70 -0
  486. package/src/ui/placement/surface_for.ts +183 -0
  487. package/src/ui/progress/prettify_tool_name.ts +16 -0
  488. package/src/ui/progress/run_announcer.ts +67 -0
  489. package/src/ui/progress/run_notice.ts +61 -0
  490. package/src/ui/progress/run_notice_styles.ts +52 -0
  491. package/src/ui/progress/subagent_panel.ts +227 -0
  492. package/src/ui/progress/subagent_progress.ts +200 -0
  493. package/src/ui/progress/subagent_update.ts +89 -0
  494. package/src/ui/progress/thoughts_block.ts +165 -0
  495. package/src/ui/progress/thoughts_styles.ts +74 -0
  496. package/src/ui/progress/tool_call_card.ts +429 -0
  497. package/src/ui/progress/tool_group_styles.ts +313 -0
  498. package/src/ui/progress/tool_step_group.ts +317 -0
  499. package/src/ui/progress/tool_step_styles.ts +486 -0
  500. package/src/ui/shell/adopt_styles.ts +34 -0
  501. package/src/ui/shell/build_header.ts +121 -0
  502. package/src/ui/shell/build_launcher.ts +79 -0
  503. package/src/ui/shell/collapse_glyph.ts +42 -0
  504. package/src/ui/shell/frame_styles.ts +147 -0
  505. package/src/ui/shell/glyph_slot.ts +12 -0
  506. package/src/ui/shell/header_button.ts +22 -0
  507. package/src/ui/shell/icon_element.ts +33 -0
  508. package/src/ui/shell/is_unread_badge_enabled.ts +11 -0
  509. package/src/ui/shell/read_launcher_icon_url.ts +9 -0
  510. package/src/ui/shell/theme_styles.ts +11 -0
  511. package/src/ui/shell/token_styles.ts +480 -0
  512. package/src/ui/styles.ts +110 -0
  513. package/src/ui/themes/classic_styles.ts +32 -0
  514. package/src/ui/themes/classic_tokens.ts +51 -0
  515. package/src/ui/themes/code_tokens.ts +21 -0
  516. package/src/ui/themes/ledger_styles.ts +169 -0
  517. package/src/ui/themes/ledger_tokens.ts +33 -0
  518. package/src/ui/themes/modern_styles.ts +80 -0
  519. package/src/ui/themes/quiet_styles.ts +118 -0
  520. package/src/ui/themes/scheme_styles.ts +26 -0
  521. package/src/ui/themes/soft_styles.ts +149 -0
  522. package/src/ui/themes/soft_tokens.ts +29 -0
  523. package/src/ui/themes/theme_hosts.ts +14 -0
  524. package/src/ui/transcript/answer_actions.ts +165 -0
  525. package/src/ui/transcript/answer_stream.ts +123 -0
  526. package/src/ui/transcript/attachment_chip_styles.ts +116 -0
  527. package/src/ui/transcript/attachment_chips.ts +82 -0
  528. package/src/ui/transcript/build_transcript.ts +67 -0
  529. package/src/ui/transcript/greeting_styles.ts +171 -0
  530. package/src/ui/transcript/groups_tool_steps.ts +16 -0
  531. package/src/ui/transcript/message_action_styles.ts +155 -0
  532. package/src/ui/transcript/message_actions.ts +234 -0
  533. package/src/ui/transcript/message_styles.ts +262 -0
  534. package/src/ui/transcript/render_markdown.ts +181 -0
  535. package/src/ui/transcript/render_or_warn.ts +22 -0
  536. package/src/ui/transcript/reveal_words.ts +41 -0
  537. package/src/ui/transcript/starter_chips.ts +42 -0
  538. package/src/ui/transcript/stick_to_bottom.ts +126 -0
  539. package/src/ui/transcript/suggestion_chips.ts +73 -0
  540. package/src/ui/transcript/transcript.ts +555 -0
  541. package/src/ui/transcript/transcript_styles.ts +153 -0
  542. package/src/ui/ui_strings.ts +550 -0
  543. package/src/version.ts +1 -0
@@ -0,0 +1,1169 @@
1
+ import { randomUUID } from "@ag-ui/client";
2
+ import { contentToText, type Message } from "@ag-ui/core";
3
+ import { MESSAGE_ROLE, TOOL_CALL_STATUS, TOOL_OUTCOME } from "../../constants.js";
4
+ import type { ActivityRegistry } from "../../core/activity_registry.js";
5
+ import type { AgUiClient } from "../../core/agui_client.js";
6
+ import { messageAttachments } from "../../core/attachment.js";
7
+ import type { ClientSeed } from "../../core/client_seed.js";
8
+ import type {
9
+ ClientConversationStore,
10
+ NavigationCheckpoint,
11
+ ThreadMeta,
12
+ } from "../../core/conversation_store.js";
13
+ import type { ThreadDetail } from "../../core/events/thread_detail.js";
14
+ import type { MessageRole } from "../../core/message_role.js";
15
+ import { RunIndex } from "../../core/run_index.js";
16
+ import { toolStatusFromOutcome } from "../../core/tool_outcome.js";
17
+ import { answerUnansweredCalls, metadataOrTopLevel, mintThread } from "../../core/utils.js";
18
+ import type { ToolCatalog } from "../../tools/tool_catalog.js";
19
+ import type { AnswerActions } from "../transcript/answer_actions.js";
20
+ import { renderAttachmentChips } from "../transcript/attachment_chips.js";
21
+ import type { Transcript } from "../transcript/transcript.js";
22
+ import type { UiStrings } from "../ui_strings.js";
23
+ import type { CheckpointMenu, CheckpointVerb } from "./checkpoint_menu.js";
24
+ import type { RelativeTimeFormatter } from "./relative_time.js";
25
+ import type { ThreadDrawer } from "./thread_drawer.js";
26
+
27
+ /** What conversation history needs from the element that owns it. */
28
+ export interface ConversationHistoryHost {
29
+ /**
30
+ * The custom element: the attributes history reads and stamps, and the
31
+ * `this` a host's `agentFactory` and `navigationResult` are called with.
32
+ */
33
+ readonly element: HTMLElement;
34
+ /** The conversation list, which the element builds and renders. */
35
+ readonly drawer: ThreadDrawer;
36
+ /** The checkpoint panel, which the element builds and renders. */
37
+ readonly checkpoints: CheckpointMenu;
38
+ /** The transcript a restored conversation is replayed into. */
39
+ readonly transcript: Transcript;
40
+ /** The action row under each finished answer. */
41
+ readonly actions: AnswerActions;
42
+ /** The activity renderers, which redraw a restored activity. */
43
+ readonly activities: ActivityRegistry;
44
+ /** The frontend tools, whose renderers a restored call is redrawn with. */
45
+ readonly tools: ToolCatalog;
46
+ /** The composer, whose text a continuation sends. */
47
+ readonly input: HTMLTextAreaElement;
48
+ /** The one-line hint above the composer. */
49
+ readonly hint: HTMLDivElement;
50
+ /** The resolved string table. */
51
+ readonly strings: () => UiStrings;
52
+ /** The element's `conversationStore`, read per use. */
53
+ readonly conversationStore: () => ClientConversationStore;
54
+ /** The element's `formatRelativeTime`. */
55
+ readonly formatRelativeTime: () => RelativeTimeFormatter | null;
56
+ /** The element's `navigationResult`. */
57
+ readonly navigationResult: () => (checkpoint: NavigationCheckpoint) => unknown;
58
+ /** The request headers, having first reported the destination if it is foreign. */
59
+ readonly headersFor: (url: string) => Record<string, string>;
60
+ /** The configured cookie policy as `fetch` spells it. */
61
+ readonly requestCredentials: () => RequestCredentials | undefined;
62
+ /** The element's `appendMessage`, which a restored bubble opens through. */
63
+ readonly appendMessage: (role: MessageRole, content: string) => HTMLDivElement;
64
+ /**
65
+ * Draw a turn the user sent and dispatch the submit event, as the element's
66
+ * own send does before it starts a run.
67
+ */
68
+ readonly announceTurn: (content: string) => void;
69
+ /**
70
+ * Record a turn taken from the composer for arrow-key recall, as the
71
+ * element's own send records one.
72
+ */
73
+ readonly recordTurn: (content: string) => void;
74
+ /** Resize the composer to its content. */
75
+ readonly autoGrow: () => void;
76
+ /**
77
+ * A continuation has ended, whether or not it ever ran.
78
+ *
79
+ * The composer parks a turn typed while one is in flight, and learns a run
80
+ * has settled from its own events -- which never arrive for a continuation
81
+ * that failed before starting one. Without this, such a turn stayed parked
82
+ * with nothing left to release it.
83
+ */
84
+ readonly continuationEnded: () => void;
85
+ /** The conversation's own client, or `null` until one is built. */
86
+ readonly client: () => AgUiClient | null;
87
+ /** The conversation's own client, built on first use. */
88
+ readonly ensureClient: () => AgUiClient;
89
+ /**
90
+ * A client from the same construction as the conversation's own, differing
91
+ * only in what `seed` says. A continuation's comes from here so that nothing
92
+ * added to one can be missing from the other.
93
+ */
94
+ readonly buildClient: (seed: ClientSeed) => AgUiClient;
95
+ /**
96
+ * Forget the conversation's own client, so the next one is built from
97
+ * {@link ConversationHistory.restored}. Nothing is cancelled: this is for a
98
+ * client that is not running.
99
+ */
100
+ readonly releaseClient: () => void;
101
+ /**
102
+ * Whether an interaction is in flight, which the composer owns: from the
103
+ * moment a send or a retry is taken, or a restore starts loading, not only
104
+ * once its run has started.
105
+ */
106
+ readonly running: () => boolean;
107
+ /**
108
+ * Hold the composer as a send holds it from the moment it is taken: the
109
+ * built-in Send parks its turn, a Retry and a pick refuse. Let go of when the
110
+ * run starts, by a Stop, or by {@link RunHold.release}.
111
+ *
112
+ * A host's send refuses too, unless `restored` is given: then the hold is a
113
+ * restore's with no run to resume, and a host's send waits for `restored` to
114
+ * settle and sends after it, with the conversation drawn. `restored` settles
115
+ * once that restore has ended, or has been stopped, and never rejects.
116
+ */
117
+ readonly holdRun: (restored: Promise<void> | null) => RunHold;
118
+ /** Stop the in-flight run. */
119
+ readonly cancelRun: () => void;
120
+ /** Drop the in-memory run and transcript, leaving the thread untouched. */
121
+ readonly resetState: () => void;
122
+ /** Swap the composer between Send and Stop, which the composer owns. */
123
+ readonly setRunning: (running: boolean) => void;
124
+ /**
125
+ * Say the conversation on screen changed, once its transcript is replaced.
126
+ * The element fills in the thread it replaced, which it last reported.
127
+ */
128
+ readonly threadChanged: (threadId: string, reason: ThreadDetail["reason"]) => void;
129
+ /** Hear the rows the conversation list was just given, for the title on screen. */
130
+ readonly threadsListed: (threads: readonly ThreadMeta[]) => void;
131
+ /** A thread was deleted, so whatever the element kept about it goes too. */
132
+ readonly threadDeleted: (threadId: string) => void;
133
+ }
134
+
135
+ /**
136
+ * A hold {@link ConversationHistoryHost.holdRun} took.
137
+ *
138
+ * It says nothing about whether the restore that took it was stopped. A
139
+ * restore asks its generation that, because a host's send that waited on a
140
+ * restore with no run to resume takes the hold over from it, and a restore
141
+ * stopped by a newer one finds that one's hold in its place -- see
142
+ * {@link ConversationHistory.rehydrate}.
143
+ */
144
+ interface RunHold {
145
+ /**
146
+ * Let go, if nothing else has, and send what the composer parked behind it,
147
+ * because no run is going to settle and send it.
148
+ */
149
+ readonly release: () => void;
150
+ }
151
+
152
+ /**
153
+ * The conversation a restore set out to restore: which restore, by its
154
+ * generation, and the store and thread it reads.
155
+ */
156
+ interface RestoreClaim {
157
+ readonly generation: number;
158
+ readonly store: ClientConversationStore;
159
+ readonly threadId: string;
160
+ }
161
+
162
+ /**
163
+ * Which conversation is on screen, and how one gets there: restoring it from
164
+ * the store, switching to another from the conversation list, and continuing
165
+ * a run from the checkpoint panel.
166
+ *
167
+ * The one writer of the active thread's id and of the messages a restore
168
+ * seeded the next client with. The element reads both when it builds its
169
+ * client, and asks for a transition -- adopt the store's thread, mint a new
170
+ * one -- rather than writing either. Clearing the run, the transcript and the
171
+ * composer around a transition stays the element's, because it spans every
172
+ * part of it; history calls back for that.
173
+ *
174
+ * Owned one-to-one by an `<ag-ui-chat>`, and holding no state outside the
175
+ * instance.
176
+ */
177
+ export class ConversationHistory {
178
+ readonly #host: ConversationHistoryHost;
179
+ /** The active thread's id. Empty until the element connects. */
180
+ #threadId = "";
181
+ /**
182
+ * The conversation as last written for the next client the element builds to
183
+ * start from: what the last restore replayed, or what a checkpoint
184
+ * continuation last saved after it. Emptied with the rest of the in-memory run.
185
+ */
186
+ #restored: readonly Message[] = [];
187
+ /**
188
+ * Counts the conversations cleared away, so a continuation can tell whether
189
+ * the one it continued is still on screen when it saves.
190
+ */
191
+ #cleared = 0;
192
+ /**
193
+ * Which restore may draw: bumped by every rehydrate, and by every clear of
194
+ * the conversation on screen. A restore whose generation is stale draws
195
+ * nothing more and resumes nothing: a newer one has started, or New chat, a
196
+ * thread switch or a `user-key` handover has cleared what it was restoring.
197
+ */
198
+ #generation = 0;
199
+ /**
200
+ * The conversation the latest restore claimed, written by every restore as
201
+ * it starts; `null` before the first. It speaks for the restore now current
202
+ * only while its generation is the current one, because clearing the
203
+ * conversation moves that on. See `#forgetStopped` for the one reader.
204
+ */
205
+ #claim: RestoreClaim | null = null;
206
+ /** Built lazily from `data-runs-url`; `null` when the host didn't opt in. */
207
+ #runIndex: RunIndex | null = null;
208
+ /**
209
+ * The checkpoint continuation in flight, so stopping the conversation's run
210
+ * reaches it. `null` when none is running.
211
+ */
212
+ #continuation: AgUiClient | null = null;
213
+ /**
214
+ * Settles what a host's send waits on while the restore now current, one
215
+ * with no run to resume, loads and replays; `null` when no such restore is
216
+ * under way. Called when that restore ends, and as soon as it is stopped by
217
+ * anything that moves the generation, so a send waiting on a conversation
218
+ * that is no longer on screen hears so at once rather than when an
219
+ * abandoned load answers.
220
+ *
221
+ * Also what tells a pick refused for a load from one refused for a run.
222
+ */
223
+ #loading: (() => void) | null = null;
224
+ /** The thread switch under way and the promise that settles it, or `null`. */
225
+ #switching: { readonly threadId: string; readonly done: Promise<void> } | null = null;
226
+
227
+ constructor(host: ConversationHistoryHost) {
228
+ this.#host = host;
229
+ }
230
+
231
+ /** The active thread's id. */
232
+ get threadId(): string {
233
+ return this.#threadId;
234
+ }
235
+
236
+ /** The conversation as last written, for seeding the next client. */
237
+ get restored(): readonly Message[] {
238
+ return this.#restored;
239
+ }
240
+
241
+ /** The checkpoint continuation in flight, or `null` when none is running. */
242
+ get continuation(): AgUiClient | null {
243
+ return this.#continuation;
244
+ }
245
+
246
+ /** Point at the thread the store says is active. */
247
+ adoptActiveThread(): void {
248
+ this.#threadId = this.#host.conversationStore().threadId();
249
+ }
250
+
251
+ /** Make a freshly minted thread the active one. */
252
+ startThread(): void {
253
+ this.#threadId = mintThread(this.#host.conversationStore());
254
+ }
255
+
256
+ /**
257
+ * Stop the checkpoint continuation in flight, if there is one.
258
+ *
259
+ * A continuation runs on a client of its own, which the element never held,
260
+ * so Stop cancelled the conversation's client and left this one streaming:
261
+ * the button read Stop and did nothing, and New chat, a thread switch or
262
+ * removing the element carried on drawing the resumed answer into whatever
263
+ * came next. The element calls this wherever it stops its own run.
264
+ */
265
+ stopContinuation(): void {
266
+ this.#continuation?.cancel();
267
+ this.#continuation = null;
268
+ }
269
+
270
+ /** Forget the messages the last restore seeded, with the rest of the run. */
271
+ forgetRestored(): void {
272
+ this.#restored = [];
273
+ // The element clears the conversation through here, so a continuation
274
+ // still saving afterwards learns the conversation is no longer this one.
275
+ this.#cleared += 1;
276
+ // A restore still loading or replaying must stop too, or New chat during a
277
+ // slow load would draw the conversation being left into the new one and hand
278
+ // it to the new chat's client as its history.
279
+ this.#generation += 1;
280
+ // A host's send waiting on that restore is addressed to the conversation
281
+ // just cleared. The element's Stop has let go of it already; this wakes it
282
+ // to find so, rather than leaving it waiting on a load nobody wants -- for
283
+ // good, if that load never answers. The drop tests in
284
+ // `ag_ui_chat_send_during_restore.test.ts` hang without it.
285
+ this.#endWait();
286
+ }
287
+
288
+ /** Settle what waits on the restore now loading, if anything does, and forget it. */
289
+ #endWait(): void {
290
+ const end = this.#loading;
291
+ this.#loading = null;
292
+ end?.();
293
+ }
294
+
295
+ /** Delete the active thread if nothing was ever sent in it. */
296
+ reapUnsent(): void {
297
+ // A thread nothing was ever sent in has nothing to come back to, and the
298
+ // drawer never listed it — so reap it here rather than strand one record
299
+ // per press of a button whose whole use is being pressed again.
300
+ if (this.#host.conversationStore().isUnsent?.(this.#threadId) === true) {
301
+ this.#host.conversationStore().clear(this.#threadId);
302
+ }
303
+ }
304
+
305
+ /** The run index, built once from `data-runs-url`; `null` when unset. */
306
+ runs(): RunIndex | null {
307
+ const url = this.#host.element.getAttribute("data-runs-url");
308
+ if (url === null || url === "") {
309
+ return null;
310
+ }
311
+ if (this.#runIndex === null) {
312
+ this.#runIndex = new RunIndex(
313
+ url,
314
+ () => this.#host.headersFor(url),
315
+ () => this.#host.requestCredentials(),
316
+ );
317
+ }
318
+ return this.#runIndex;
319
+ }
320
+
321
+ /** Open the conversation list, dismissing the checkpoint panel. */
322
+ openThreads(): void {
323
+ // Two overlapping surfaces, so opening one dismisses the other. Clicking away
324
+ // already covers the built-in buttons, but a host driving its own chrome
325
+ // through these methods raises no pointer event — and the drawer would then
326
+ // open *underneath* a popover still floating over it.
327
+ this.#host.checkpoints.close();
328
+ void this.refreshDrawer();
329
+ this.#host.drawer.open();
330
+ }
331
+
332
+ /** Open the checkpoint panel, dismissing the conversation list. */
333
+ openCheckpoints(): void {
334
+ // The other half of the pair — see `openThreads`.
335
+ this.#host.drawer.close();
336
+ void this.#refreshCheckpoints();
337
+ this.#host.checkpoints.open();
338
+ }
339
+
340
+ /**
341
+ * Switch the active conversation to an existing thread and replay it.
342
+ *
343
+ * A call for the thread a switch is already loading is handed that switch's
344
+ * own promise, so it settles when the thread is drawn rather than at once,
345
+ * before the replay.
346
+ *
347
+ * A store that rejects the load leaves the element on the thread it was on:
348
+ * the switch has already cleared the transcript, so it points back at the
349
+ * old thread and draws it again, saying nothing, and then rejects with the
350
+ * store's error. Left as it was, the element named a thread it was not
351
+ * showing and never reported it.
352
+ */
353
+ switchThread(threadId: string): Promise<void> {
354
+ if (threadId === this.#threadId) {
355
+ return this.#switching?.threadId === threadId ? this.#switching.done : Promise.resolve();
356
+ }
357
+ const previous = this.#threadId;
358
+ const done = this.#switchTo(threadId, previous).finally(() => {
359
+ if (this.#switching === entry) {
360
+ this.#switching = null;
361
+ }
362
+ });
363
+ const entry = { threadId, done };
364
+ this.#switching = entry;
365
+ return done;
366
+ }
367
+
368
+ async #switchTo(threadId: string, previous: string): Promise<void> {
369
+ this.#host.cancelRun();
370
+ this.#host.resetState();
371
+ this.#host.conversationStore().setActiveThread(threadId);
372
+ this.#threadId = threadId;
373
+ this.#host.setRunning(false);
374
+ try {
375
+ await this.rehydrate("switch");
376
+ } catch (error) {
377
+ // Only while this switch is still the one on screen: a newer switch, a
378
+ // new chat or a delete has moved the thread on, and undoing that would
379
+ // put the old one back over it.
380
+ if (this.#threadId === threadId) {
381
+ this.#threadId = previous;
382
+ this.#host.conversationStore().setActiveThread(previous);
383
+ // Drawn again without a word: the thread on screen is the one the last
384
+ // event reported, so there is nothing to tell a host. A store that
385
+ // cannot answer for it either leaves the screen as it is.
386
+ await this.rehydrate(null).catch(() => undefined);
387
+ }
388
+ throw error;
389
+ }
390
+ }
391
+
392
+ /** Rename a thread, then reload the list that shows its title. */
393
+ renameThread(threadId: string, title: string): void {
394
+ this.#host.conversationStore().renameThread(threadId, title);
395
+ void this.refreshDrawer();
396
+ }
397
+
398
+ /** Delete a thread; if it was the active one, fall back to a fresh chat. */
399
+ deleteThread(threadId: string): void {
400
+ const wasActive = threadId === this.#threadId;
401
+ if (wasActive) {
402
+ this.#host.cancelRun();
403
+ }
404
+ this.#host.conversationStore().clear(threadId);
405
+ this.#host.threadDeleted(threadId);
406
+ if (wasActive) {
407
+ this.#host.resetState();
408
+ this.adoptActiveThread();
409
+ this.#host.setRunning(false);
410
+ // No replay to wait for: the thread that took its place is a fresh one.
411
+ this.#host.threadChanged(this.#threadId, "delete");
412
+ }
413
+ void this.refreshDrawer();
414
+ }
415
+
416
+ /** Reload the drawer's thread list, marking the active thread. */
417
+ async refreshDrawer(): Promise<void> {
418
+ this.#host.drawer.setRelativeTimeFormatter(this.#host.formatRelativeTime());
419
+ const threads = await this.#host.conversationStore().listThreads();
420
+ this.#host.drawer.setThreads(threads, this.#threadId);
421
+ this.#host.threadsListed(threads);
422
+ }
423
+
424
+ /** Load the checkpoint panel with the runs that can actually be continued. */
425
+ async #refreshCheckpoints(): Promise<void> {
426
+ const index = this.runs();
427
+ // Pushed at render rather than at connect: `formatRelativeTime` is a
428
+ // property, so a host may set it long after the element mounted.
429
+ this.#host.checkpoints.setRelativeTimeFormatter(this.#host.formatRelativeTime());
430
+ this.#host.checkpoints.setRuns(index === null ? [] : await index.continuable());
431
+ }
432
+
433
+ /**
434
+ * Continue `runId` as a **new** run, seeded server-side from its snapshot.
435
+ *
436
+ * Uses a short-lived agent pointed at the resume / fork endpoint and seeded
437
+ * with no history, because those endpoints supply the prior turns from the
438
+ * snapshot and re-sending them would duplicate. A separate agent makes that
439
+ * structural and mints the fresh `run_id` the endpoints also require.
440
+ *
441
+ * Built by the same construction as the conversation's own client, so the
442
+ * continuation streams into the same transcript the user is looking at and
443
+ * runs under the same state, tools and bounds. It is the run in flight while
444
+ * it lasts: {@link stopContinuation} is how the element's Stop reaches it.
445
+ *
446
+ * What it adds joins the conversation. Its saves write the conversation on
447
+ * screen ahead of its exchange, and each one hands that whole list to the
448
+ * next client the element builds, so the next ordinary message is sent with
449
+ * the exchange it follows. The conversation's own client never held it, and
450
+ * was sending without it.
451
+ */
452
+ async continueRun(runId: string, verb: CheckpointVerb): Promise<void> {
453
+ const index = this.runs();
454
+ if (index === null) {
455
+ // Unreachable from the built-in control: the header button is only
456
+ // rendered when `runs()` is configured, so a row to pick cannot exist
457
+ // without one. A host calling `openCheckpoints()` regardless gets the
458
+ // documented empty panel, which has no rows either. Typed, not silent.
459
+ return;
460
+ }
461
+ if (this.#host.running() || this.#continuation !== null) {
462
+ // Refused rather than started beside it. The panel opens and its rows
463
+ // take a pick while a run streams, and a second run would draw its answer
464
+ // into the turn still arriving; a second continuation would also replace
465
+ // the one Stop reaches, leaving the first streaming where nothing could
466
+ // end it. Nor is the earlier run cancelled for it: a pick in a panel is not
467
+ // a Stop, and what is streaming may be the answer the user is waiting on.
468
+ //
469
+ // Both checks, because they see different things. `running` is the
470
+ // composer's own state: it spans every round of an interaction, from the
471
+ // moment a send or a retry is taken rather than from its run's first
472
+ // event, which is a microtask behind it. A continuation is not the
473
+ // composer's, and is recorded here the moment it starts.
474
+ //
475
+ // Said at the composer, as an empty composer is below, because the row
476
+ // closed the panel before this ran. The typed turn stays where it is --
477
+ // it is what the user wants sent once the run is done -- and the caret
478
+ // goes back to it, where Escape stops the run.
479
+ //
480
+ // A restore with no run to resume holds the composer too, and there is
481
+ // no answer to wait for and nothing Escape can stop, so it says the
482
+ // conversation is still loading instead. A restore that will resume a
483
+ // run keeps the running wording: what it is loading is that run.
484
+ const strings = this.#host.strings();
485
+ this.#refuse(
486
+ this.#loading === null ? strings.continueWhileRunning : strings.continueWhileLoading,
487
+ );
488
+ return;
489
+ }
490
+ const content = this.#host.input.value.trim();
491
+ if (content === "") {
492
+ // A continuation sends *only* the next turn -- the snapshot supplies
493
+ // everything before it -- so with an empty composer there is nothing to
494
+ // send. Returning here was the same failure the endpoint guard above had:
495
+ // the row's button closes the panel before this runs, so the widget
496
+ // visibly reacted and then did nothing, which reads as a resume that was
497
+ // attempted and lost rather than one that never started.
498
+ //
499
+ // Said at the composer rather than in the transcript, because that is
500
+ // where the fix goes and because the hint clears itself on the first
501
+ // keystroke -- a transcript notice for a recoverable slip would outlive
502
+ // the slip. Focus follows for the same reason applying a skill moves it when
503
+ // a template is short of a field.
504
+ this.#refuse(this.#host.strings().continueNeedsTurn);
505
+ return;
506
+ }
507
+ // Typed in the composer and sent by a press, so it is recallable as any
508
+ // other turn is. Recorded as it leaves the box, where the element's own
509
+ // send records one, rather than once it is sent: the box is empty from
510
+ // here, and a continuation that fails before its run starts has still
511
+ // taken the turn out of it.
512
+ this.#host.recordTurn(content);
513
+ this.#host.input.value = "";
514
+ this.#host.autoGrow();
515
+ const cleared = this.#cleared;
516
+ const client = this.#host.buildClient({
517
+ endpoint: verb === "resume" ? index.resumeUrl(runId) : index.forkUrl(runId),
518
+ // The seed the endpoints assume: nothing. The snapshot is the history.
519
+ initialMessages: [],
520
+ // What its saves write ahead of the exchange: the conversation on screen,
521
+ // in the form the store holds it. From the conversation's own client when
522
+ // there is one, because that client keeps how each call ended beside its
523
+ // messages rather than on them; otherwise what the last restore or
524
+ // continuation wrote, which is already in that form.
525
+ //
526
+ // All of it, even where this forks an earlier run and the server's
527
+ // snapshot stops there: what is saved is what the screen shows.
528
+ follows: this.#host.client()?.messages ?? this.#restored,
529
+ onSaved: (conversation) => {
530
+ // A continuation stopped by New chat or a thread switch saves once its
531
+ // request closes, into its own thread; the conversation now on screen
532
+ // is not the one it continued.
533
+ if (cleared !== this.#cleared) {
534
+ return;
535
+ }
536
+ // The conversation's own client holds the conversation without this
537
+ // exchange, and would send it that way. Released rather than patched:
538
+ // the next client is built from this list exactly as a reload builds
539
+ // one from the store, outcomes included.
540
+ //
541
+ // Nothing can run on the released client in between, because the
542
+ // composer refuses to start one while a continuation is in flight --
543
+ // it parks the turn instead. It said here that only a script in the
544
+ // same task could, which was wrong by a whole request: `running` does
545
+ // not turn on until the continuation's first event, and a person who
546
+ // typed in that window got a second run against the snapshot
547
+ // `follows` had already frozen, so whichever saved last dropped the
548
+ // other's turn.
549
+ this.#restored = conversation;
550
+ this.#host.releaseClient();
551
+ },
552
+ });
553
+ this.#continuation = client;
554
+ try {
555
+ // Drawn and announced as the element's own send does, because this is
556
+ // the same act sent somewhere else. Without the bubble the answer arrived
557
+ // under no question, while the save below wrote the turn, so a reload
558
+ // showed what the live transcript never had.
559
+ //
560
+ // The submit event fires too. A host listens to it as "the user sent
561
+ // something", which a continued turn is: typed in the composer and sent
562
+ // by a press. Only the endpoint differs, and the host chose that when it
563
+ // configured the panel; leaving it out would make a host's own record of
564
+ // the conversation miss exactly the turns this panel exists to add.
565
+ //
566
+ // Only once the continuation is recorded, because a listener runs here:
567
+ // a host's own send from it is refused as overlapping the run in flight,
568
+ // as it would be from any later moment of this one.
569
+ this.#host.announceTurn(content);
570
+ // A listener can also have stopped it: New chat, a thread switch, or
571
+ // moving the element all reach the stop. That cancelled a client whose
572
+ // run had not begun, and a run resets its own cancellation when it
573
+ // starts, so sending now would start one that nothing holds and nothing
574
+ // can stop -- in a conversation the user has already left, with its
575
+ // tools still driving the page.
576
+ if (this.#continuation !== client) {
577
+ return;
578
+ }
579
+ await client.send(content);
580
+ } finally {
581
+ // Only if it is still the one in flight: stopping forgets it at once, and
582
+ // a continuation started after that one is not this one to forget.
583
+ //
584
+ // In a finally because the send can fail before its run ever starts. The
585
+ // first save goes through the host's `conversationStore` synchronously
586
+ // inside it, and a store is the host's to replace: the built-in one
587
+ // swallows a write the browser refused, a server-backed one need not. A
588
+ // throw there left this pointing at a client that would never run, so
589
+ // every later pick was refused with no Stop to clear it -- the composer's
590
+ // button is Send until a run reports a start -- and `#liveClient` went on
591
+ // handing that dead client the shared state a host wrote.
592
+ if (this.#continuation === client) {
593
+ this.#continuation = null;
594
+ this.#host.continuationEnded();
595
+ }
596
+ }
597
+ }
598
+
599
+ /**
600
+ * Say at the composer why a picked run did not continue, and put the caret
601
+ * there. The hint clears itself on the next keystroke.
602
+ */
603
+ #refuse(reason: string): void {
604
+ this.#host.hint.textContent = reason;
605
+ this.#host.hint.hidden = false;
606
+ this.#host.input.focus();
607
+ }
608
+
609
+ /**
610
+ * Restore the conversation from the store on mount, then — if a navigating
611
+ * tool reloaded the page mid-run — resume the loop by supplying that tool's
612
+ * result from the page we landed on.
613
+ *
614
+ * Any other call the stored run left unanswered is answered on the way in, as
615
+ * not finished, because no run is left to answer it.
616
+ *
617
+ * **What the store holds after a reload mid-run.** The run loop persists a
618
+ * round when its stream ends, which is *before* it asks about a gated call,
619
+ * runs a frontend tool, or collects a server-side approval. A reload in that
620
+ * window leaves the round's calls stored with no result, and the request that
621
+ * would have produced one died with the page.
622
+ *
623
+ * **Why not finished, and not declined.** Stop declines an open card because
624
+ * pressing it is a person answering the question. A reload answers nothing, and
625
+ * the stored shape is the same whether the round waited on a person or on a
626
+ * handler the reload killed, so declined would be unproven for the first and
627
+ * false for the second. A marker saved while a card is open could tell them
628
+ * apart, but every host store would have to round-trip it, and a store that
629
+ * dropped it would fall back to this wording anyway -- which is true of both.
630
+ *
631
+ * The answers come from the same helper the client runs before each request,
632
+ * so a restored card and the result the next request carries cannot disagree.
633
+ * Giving them to the replay, rather than leaving the client to add them, is
634
+ * what settles each card from its result. The checkpointed call is excluded,
635
+ * because the resume path answers it from the page the reload landed on; that
636
+ * exclusion is held by "resumes with the landed page's result, and its card says
637
+ * so" in `ag_ui_chat_reload_mid_run.test.ts`.
638
+ *
639
+ * **Every restore holds the composer from before the load.** Until the
640
+ * store answers, the conversation's client can only be seeded from nothing.
641
+ * With a remote store that is a real request, and a send, a Retry or a pick
642
+ * in it was refused by nothing: each built the conversation's client before
643
+ * the conversation had loaded, so a send went out carrying none of it, its
644
+ * first save replaced the stored one, and the replay then drew the stored
645
+ * conversation under the new turn. The client stayed cached without it, so
646
+ * every later turn went out without it too. So the composer is held as a
647
+ * send holds it: what was typed parks and goes out after the replay, and a
648
+ * Retry and a pick are refused -- a Retry has nothing on screen to retry, and
649
+ * after the load it would re-ask the previous visit's last question.
650
+ *
651
+ * **A host's send waits for a restore with no run to resume.** It is
652
+ * usually a prompt fired as the element mounts, which no host could time
653
+ * against a load nothing announces, and refusing it would lose the turn.
654
+ * So it waits for this restore to end, taking the hold over as it does,
655
+ * and then sends with the conversation drawn above it; what the built-in
656
+ * Send parked goes out after its run. Stopped while it waits, it is dropped,
657
+ * as a send stopped by its own submit listener is.
658
+ *
659
+ * **A restore that resumes refuses a host's send instead.** The resume is a
660
+ * run, and the restore ends by starting it, so a send that waited would only
661
+ * reach that run and be refused there. The resume gives way to nothing: what
662
+ * was typed parks behind the resumed run and goes out after its answer.
663
+ *
664
+ * The other way round, the resume standing down for the send, was rejected.
665
+ * The send that got in first had already gone out without the conversation,
666
+ * so standing down kept that loss, and it also dropped the run the navigation
667
+ * was part of, which the user asked for before the reload: the agent stopped
668
+ * halfway through the task, with the navigating call never answered.
669
+ *
670
+ * **A restore stops where it was stopped, held or not.** New chat, a thread
671
+ * switch, a `user-key` handover, a newer restore or the element leaving the
672
+ * page can each come while the store answers, and from host code the restore
673
+ * runs itself: the activity and tool renderers each replayed message draws
674
+ * through, and `navigationResult`. From then on it draws nothing more and
675
+ * resumes nothing. The hold cannot say so, because a host's send that
676
+ * waited may hold it by then. So the restore asks its generation, which clearing
677
+ * the conversation moves on as a newer restore does, and asks it after the
678
+ * load, after each replayed message -- as a Retry asks after each one -- and
679
+ * once more before the resume. Stopped by anything but the element leaving
680
+ * the page, it also forgets the checkpoint it loaded, since that was a Stop
681
+ * for the run it would have resumed -- unless what replaced it is a restore
682
+ * of the same conversation, still current, which answers for that run
683
+ * instead; see `#forgetStopped`.
684
+ *
685
+ * `reason` is what the thread event this restore ends with reports as why
686
+ * the conversation changed. A restore that is stopped reports nothing, and
687
+ * neither does one given `null`, which draws the thread already reported.
688
+ */
689
+ async rehydrate(reason: ThreadDetail["reason"] | null): Promise<void> {
690
+ // Claimed before awaiting, and checked by `#restoring` wherever the restore
691
+ // has waited or run host code. With a slow remote store, picking thread B
692
+ // then C interleaved both replays into one transcript, and New chat drew
693
+ // the conversation being left into the new one.
694
+ this.#generation += 1;
695
+ const generation = this.#generation;
696
+ // Read before the load rather than after it, because a checkpoint means
697
+ // this restore ends by starting a run, and the window a send could start a
698
+ // second one in is the load, not the resume -- see "A restore that resumes"
699
+ // above. The store's checkpoint is synchronous and the built-in remote store
700
+ // keeps it in this tab, so nothing the load fetches can change it. And
701
+ // before the replay, which has to know the one unanswered call a reload was
702
+ // *expected* by, so as not to settle it as abandoned.
703
+ //
704
+ // The thread and the store are taken once, here, because a restore that is
705
+ // stopped forgets its checkpoint where it read it: by then the active
706
+ // thread can be a new chat's, and the store scoped to the next principal.
707
+ const threadId = this.#threadId;
708
+ const store = this.#host.conversationStore();
709
+ // Claimed with the generation, store and thread just taken, whether or not
710
+ // there is a checkpoint: a restore of the same conversation that finds none
711
+ // has found it resumed or forgotten already, so an older one has nothing
712
+ // left there to forget. Claiming only with a checkpoint would differ only
713
+ // where one was written after this restore started, which an older restore
714
+ // should not wipe either, so no test tells the two apart. See
715
+ // `#claimedByCurrent`.
716
+ this.#claim = { generation, store, threadId };
717
+ const checkpoint = store.loadCheckpoint(threadId);
718
+ // What a host's send waits on, for a restore with no run to resume, and
719
+ // the hold either kind takes. Taken before the load for the same reason
720
+ // the checkpoint is read before it: the load is the window.
721
+ //
722
+ // No earlier wait is left to settle here: every restore but the first
723
+ // follows a clear of the conversation on screen -- a reload, a thread
724
+ // switch, a handover, the element coming back -- and `forgetRestored`
725
+ // settled and forgot it then. So a restore that will resume finds none.
726
+ const restored =
727
+ checkpoint === null
728
+ ? new Promise<void>((resolve) => {
729
+ this.#loading = resolve;
730
+ })
731
+ : null;
732
+ const waiting = this.#loading;
733
+ const hold = this.#host.holdRun(restored);
734
+ try {
735
+ // Held while the store answers. A remote store answers after first paint,
736
+ // and a conversation it is still fetching is more likely to have messages
737
+ // than not, so without this the page would paint the greeting and a centred
738
+ // composer and then drop the composer the moment they land. The built-in
739
+ // store answers in a microtask, before paint, so for it this never reaches
740
+ // the screen. Released in `finally` so a store that rejects cannot leave the
741
+ // layout held for good, and only by the restore that is still current.
742
+ this.#host.element.setAttribute("data-restoring", "");
743
+ let messages: readonly Message[] | null;
744
+ try {
745
+ messages = await store.loadMessages(threadId);
746
+ } finally {
747
+ if (generation === this.#generation) {
748
+ this.#host.element.removeAttribute("data-restoring");
749
+ }
750
+ }
751
+ // Stopped while the store answered. Going on drew the conversation being
752
+ // left into the chat that replaced it, and resumed its run there, on a
753
+ // client seeded from what the restore had just drawn.
754
+ if (!this.#restoring(generation)) {
755
+ this.#forgetStopped(store, threadId, checkpoint);
756
+ return;
757
+ }
758
+ if (messages !== null) {
759
+ const unfinished = this.#host.strings().callNotFinished;
760
+ const restored = answerUnansweredCalls(
761
+ messages,
762
+ // The shape the store holds for a call the client answered the same
763
+ // way: its tool message, with the outcome in its metadata.
764
+ (toolCallId) => ({
765
+ id: randomUUID(),
766
+ role: "tool",
767
+ content: unfinished,
768
+ toolCallId,
769
+ metadata: { outcome: TOOL_OUTCOME.INTERRUPTED },
770
+ }),
771
+ new Set(checkpoint === null ? [] : [checkpoint.toolCallId]),
772
+ );
773
+ this.#restored = restored;
774
+ for (const message of restored) {
775
+ this.replay(message);
776
+ // A replay draws through the host's activity and tool renderers,
777
+ // which can stop the restore as well as anything outside it can.
778
+ // Checked after each one, as a Retry checks, so the rest of the
779
+ // conversation being left is not drawn into the chat that replaced it,
780
+ // and after the last, so its run is not resumed there.
781
+ if (!this.#restoring(generation)) {
782
+ this.#forgetStopped(store, threadId, checkpoint);
783
+ return;
784
+ }
785
+ }
786
+ }
787
+ if (checkpoint === null) {
788
+ this.#noticeIfRunUnfinished(messages);
789
+ }
790
+ // Said once the transcript is the restored one, and only by a restore
791
+ // still current: one that was stopped returned above, so two quick
792
+ // switches report the second alone. Before a resume, because the resume
793
+ // is a run, and waiting for it reported the change a whole answer late.
794
+ if (reason !== null) {
795
+ this.#host.threadChanged(threadId, reason);
796
+ }
797
+ if (checkpoint !== null) {
798
+ // A listener is host code, and can stop the restore as a replayed
799
+ // renderer can. Resuming then answered the call into the conversation
800
+ // that replaced it, and left the checkpoint for a later visit to resume.
801
+ if (!this.#restoring(generation)) {
802
+ this.#forgetStopped(store, threadId, checkpoint);
803
+ return;
804
+ }
805
+ await this.#resumeFrom(checkpoint, generation);
806
+ }
807
+ } finally {
808
+ // The resumed run starting has let go of the hold already, as a send's
809
+ // does, and a request that then fails still started one: the client
810
+ // reports a run before it sends. Still held here only if none started --
811
+ // the store rejected the load, or the store or the host threw on the way
812
+ // to the resume -- and then this is what sends a turn the composer parked
813
+ // meanwhile, since no run is going to settle and send it.
814
+ //
815
+ // A host's send that waited on this restore has taken the hold over, so
816
+ // the release does nothing and the send goes first; what was parked goes
817
+ // out after its run. Woken only after the replay, here, rather than when
818
+ // the store answers, or its turn would be drawn above the conversation.
819
+ // Only this restore's wait: one that was stopped was woken by what
820
+ // stopped it, and the wait now current, if any, is a newer restore's.
821
+ // Waking that one here sent its turn before its conversation was drawn,
822
+ // which "drops the waiting send on a drawer switch, and a send then
823
+ // carries the picked thread" in `ag_ui_chat_send_during_restore.test.ts`
824
+ // holds.
825
+ hold.release();
826
+ if (this.#loading === waiting) {
827
+ this.#endWait();
828
+ }
829
+ }
830
+ }
831
+
832
+ /**
833
+ * Whether the restore that claimed `generation` may go on: nothing has
834
+ * replaced or cleared the conversation it is restoring, and the element is
835
+ * still on the page.
836
+ *
837
+ * On the page, because leaving it is the one stop that clears nothing: the
838
+ * element is not reset until it connects again, and connecting restores the
839
+ * conversation afresh. A restore with nothing to resume went on regardless,
840
+ * drawing into a node that had gone and running the host's renderers for it.
841
+ * One that resumes learnt it from the Stop letting go of its hold; nothing
842
+ * asks the hold that now, so this is what keeps the resume's request in.
843
+ *
844
+ * One `&&`, so coverage cannot tell whether either half is held. Removing the
845
+ * generation fails "draws nothing into a new chat started while it loads" in
846
+ * `conversation_history.test.ts`, and "drops a stale thread replay when a
847
+ * newer switch started" in `ag_ui_chat.test.ts`; removing the connection
848
+ * fails "draws nothing into an element that left the page while it loads".
849
+ */
850
+ #restoring(generation: number): boolean {
851
+ return generation === this.#generation && this.#host.element.isConnected;
852
+ }
853
+
854
+ /**
855
+ * Forget the checkpoint a stopped restore loaded, when what stopped it also
856
+ * stopped the run it was about to resume.
857
+ *
858
+ * New chat, a thread switch, a `user-key` handover and a newer restore all
859
+ * move the generation, and each is a Stop for that run. Left in the store,
860
+ * the checkpoint outlived the Stop: coming back to the conversation later
861
+ * resumed the navigating call with whatever page was current by then.
862
+ * Forgotten, the call is answered as not finished there, as any stopped call
863
+ * is.
864
+ *
865
+ * Not while the restore now current claimed the same conversation, which it
866
+ * has in two cases. It is this restore, when only the element left the page,
867
+ * which moves no generation: a move or a framework re-render is not a
868
+ * farewell, and the restore connecting starts has to find the checkpoint to
869
+ * resume from. Or it is a newer restore of the same thread in the same store
870
+ * -- `reload()`, switching away and back, or the element connecting again --
871
+ * which read the checkpoint as it started, or found it gone, and answers for
872
+ * it from then on: its resume clears it, and when it is stopped in turn this
873
+ * same rule decides.
874
+ *
875
+ * A newer restore's case does not forget it: if the element leaves the page
876
+ * while the newer one loads, that one stands down keeping the checkpoint for
877
+ * the reconnect, and a store that no longer held it would leave no run to resume.
878
+ *
879
+ * Written to the store and the thread it was read from. After a handover
880
+ * that is the previous principal's: the built-in store's clear is a removal
881
+ * from a namespace the handover has already purged, so it changes nothing,
882
+ * and nothing reaches the next principal's. A host's store is the same object
883
+ * either side of a handover; one the host replaced is held by "still refuses
884
+ * a send for the newer restore once the older has stood down".
885
+ *
886
+ * Two conditions. Clearing with no checkpoint fails "draws nothing into a new
887
+ * chat started while it loads", which runs no store code for one. Clearing
888
+ * what the current restore claimed fails "resumes nothing once the element
889
+ * leaves the page while the store answers", where the claim is this
890
+ * restore's own and the re-inserted element resumes, and "resumes once the
891
+ * element is back when the host reloads and it then leaves the page", where
892
+ * it is the reload's. The claim's own three are named on `#claimedByCurrent`.
893
+ */
894
+ #forgetStopped(
895
+ store: ClientConversationStore,
896
+ threadId: string,
897
+ checkpoint: NavigationCheckpoint | null,
898
+ ): void {
899
+ if (checkpoint !== null && !this.#claimedByCurrent(store, threadId)) {
900
+ store.saveCheckpoint(threadId, null);
901
+ }
902
+ }
903
+
904
+ /**
905
+ * Whether the restore now current claimed the conversation a stopped restore
906
+ * read its checkpoint from: the same thread, in the same store.
907
+ *
908
+ * A claim from an earlier generation speaks for nobody: the conversation was
909
+ * cleared after it was made, so whatever it would have resumed was stopped,
910
+ * and that holds of a stopped restore's own claim as much as of any other.
911
+ * The store is compared as an object, because a store the host assigned
912
+ * since is a different record of the conversation, and the one the
913
+ * checkpoint was read from still has to forget it.
914
+ *
915
+ * As an object, a store the element builds for itself -- the built-in one
916
+ * under a namespace, and the remote one `data-threads-url` wraps around it
917
+ * -- is a different one after a reconnect, which builds it afresh over the
918
+ * same storage, so a restore from before the reconnect forgets the
919
+ * checkpoint there once it answers. The reconnect's own restore read it as
920
+ * it started and resumes from that copy, as a newer restore always did here;
921
+ * what that copy cannot survive is the element leaving again before the
922
+ * load answers.
923
+ *
924
+ * One `&&` of three, so coverage cannot tell whether any is held. Dropping
925
+ * the generation fails "forgets the checkpoint when New chat stops the
926
+ * resume, so the call ends as not finished", where the claim is the stopped
927
+ * restore's own; dropping the store fails "still refuses a send for the newer
928
+ * restore once the older has stood down", whose host replaces the store
929
+ * between the two; dropping the thread fails "forgets the checkpoint of a
930
+ * thread switched away from, though the next has one of its own".
931
+ */
932
+ #claimedByCurrent(store: ClientConversationStore, threadId: string): boolean {
933
+ const claim = this.#claim;
934
+ return (
935
+ claim?.generation === this.#generation && claim.store === store && claim.threadId === threadId
936
+ );
937
+ }
938
+
939
+ /**
940
+ * Notice a previous run that never produced a response.
941
+ *
942
+ * {@link AgUiClient.send} persists the user's message before starting the
943
+ * run, so a transcript ending on that user message means nothing came back.
944
+ * The transcript's shape alone detects it, needing no store method and no
945
+ * `pagehide` listener — neither of which fires on a crash or force-quit.
946
+ *
947
+ * An agent-initiated reload is not this case: a navigating tool leaves a
948
+ * checkpoint and resumes, so the caller returns early on one.
949
+ *
950
+ * Deliberately a notice, never a resume. AG-UI has no resume-an-aborted-run
951
+ * primitive, and re-sending the accumulated messages is semantically a new
952
+ * run, so any server-side tool already executed would run a second time.
953
+ */
954
+ #noticeIfRunUnfinished(messages: readonly Message[] | null): void {
955
+ const last = messages?.at(-1);
956
+ if (last === undefined || last.role !== MESSAGE_ROLE.USER) {
957
+ return;
958
+ }
959
+ this.#host.transcript.appendNotice("⚠", this.#host.strings().runInterrupted, "interrupted");
960
+ }
961
+
962
+ /**
963
+ * Replay a restored message: text bubbles *and* tool activity. An assistant
964
+ * turn may carry `toolCalls` (rendered as cards) and/or text; a `tool` turn
965
+ * carries a result that settles the matching card. So a refreshed page shows
966
+ * the full transcript — tool calls and their results — not just the prose.
967
+ */
968
+ replay(message: Message): void {
969
+ const text = typeof message.content === "string" ? message.content : "";
970
+ if (message.role === MESSAGE_ROLE.USER) {
971
+ const attachments = messageAttachments(message);
972
+ if (text !== "" || attachments.length > 0) {
973
+ const bubble = this.#host.appendMessage(MESSAGE_ROLE.USER, text);
974
+ if (attachments.length > 0) {
975
+ bubble.appendChild(renderAttachmentChips(attachments));
976
+ }
977
+ }
978
+ return;
979
+ }
980
+ if (message.role === MESSAGE_ROLE.ASSISTANT) {
981
+ if (text !== "") {
982
+ // Restored history must appear statically — entrance animations
983
+ // (fade / word) are for freshly-arriving messages. On reload the whole
984
+ // transcript mounts at once, so animating every bubble's text in
985
+ // parallel looks wrong. Mark it so the fade CSS skips it, and don't
986
+ // wrap words.
987
+ const restoredBubble = this.#host.appendMessage(MESSAGE_ROLE.ASSISTANT, text);
988
+ restoredBubble.classList.add("message--restored");
989
+ // The stored id, which is the streamed one wherever the store kept
990
+ // it. A Retry re-renders the turns it keeps through here too, so a
991
+ // kept answer stays the message it was. No run id: a stored message
992
+ // does not carry one.
993
+ //
994
+ // Narrowed rather than trusted, like the fields below: a host store or
995
+ // a `data-threads-url` server can return a message with no id, and a
996
+ // rating that names nothing is a pair of buttons that lead nowhere.
997
+ // Such an answer keeps its copy and loses only the rating.
998
+ const id: unknown = message.id;
999
+ this.#host.actions.attach(
1000
+ restoredBubble,
1001
+ typeof id === "string" && id !== "" ? { messageId: id } : { rateable: false },
1002
+ );
1003
+ }
1004
+ // Narrowed rather than trusted, for the same reason `messageAttachments`
1005
+ // narrows the neighbouring field: anything that throws in this loop aborts
1006
+ // the replay at this message, and every later turn silently disappears from
1007
+ // the transcript. See `restoredToolCalls`.
1008
+ for (const call of restoredToolCalls(message.toolCalls)) {
1009
+ const restored = {
1010
+ id: call.id,
1011
+ name: call.function.name,
1012
+ args: this.#parseArgs(call.function.arguments),
1013
+ };
1014
+ // Restored history goes through the same interception as the live
1015
+ // stream — otherwise a reload resurrects the raw `load_capability`
1016
+ // card the live path deliberately replaced.
1017
+ if (this.#host.transcript.noticeIfSkillLoad(restored)) {
1018
+ continue;
1019
+ }
1020
+ this.#host.transcript.setCardElement(
1021
+ restored.id,
1022
+ this.#host.transcript.cardFor(restored).element,
1023
+ );
1024
+ // Only `render` is replayed, never `handler`. A restored transcript
1025
+ // redraws what the call drew; it must not re-run what the call *did*.
1026
+ // Only the renderer is handed over, never the tool. The guarantee that
1027
+ // a reload cannot re-run a tool's *effect* is worth more than a comment
1028
+ // saying so: this signature cannot reach `handler`, so a later
1029
+ // maintainer adding a "no render? fall back to the handler" convenience
1030
+ // here has to change the type first, which is exactly the moment the
1031
+ // question should be asked.
1032
+ const render = this.#host.tools.resolve(restored.name)?.render;
1033
+ if (render !== undefined) {
1034
+ this.#host.transcript.renderToolOutput(render, restored);
1035
+ }
1036
+ }
1037
+ return;
1038
+ }
1039
+ if (message.role === "activity") {
1040
+ // The client materialises a pushed activity as a message of its own, so a
1041
+ // chart's data is in the transcript already and survives a reload. Only
1042
+ // the drawing had to be put back.
1043
+ const activity = message as unknown as { activityType?: unknown; content?: unknown };
1044
+ if (typeof activity.activityType === "string") {
1045
+ this.#host.activities.draw(message.id, activity.activityType, activity.content);
1046
+ }
1047
+ return;
1048
+ }
1049
+ if (message.role === "tool") {
1050
+ const card = this.#host.transcript.card(message.toolCallId);
1051
+ if (card !== undefined) {
1052
+ // The outcome in the message's metadata -- folded there from a server's
1053
+ // result by `@ag-ui/client`, or written there by `AgUiClient` for a
1054
+ // result it made itself -- read back through the same mapping the live
1055
+ // path uses, so a card that said "declined" before the reload still
1056
+ // says it after. History stored by an earlier release has it at the
1057
+ // top level instead, which is read when the metadata has none. Narrowed
1058
+ // off `unknown` rather than trusted, like every other field read out of
1059
+ // the store: metadata is open by key, a host store may not round-trip
1060
+ // it, and history written before outcomes existed has none. All three
1061
+ // land on DONE, which is what this line did unconditionally.
1062
+ card.settle(
1063
+ toolStatusFromOutcome(metadataOrTopLevel(message, "outcome")),
1064
+ // Flattened exactly as the live path flattens it, so a reload shows
1065
+ // the card the user watched settle.
1066
+ contentToText(message.content),
1067
+ );
1068
+ }
1069
+ }
1070
+ }
1071
+
1072
+ /** Parse a tool call's JSON `arguments` from history into an object. */
1073
+ #parseArgs(raw: unknown): Record<string, unknown> {
1074
+ if (typeof raw !== "string") {
1075
+ // A restored call whose `arguments` are missing or not a string still has
1076
+ // a name worth showing, so this renders an empty-args card rather than
1077
+ // dropping the card.
1078
+ return {};
1079
+ }
1080
+ try {
1081
+ const parsed: unknown = JSON.parse(raw);
1082
+ if (typeof parsed === "object" && parsed !== null) {
1083
+ return parsed as Record<string, unknown>;
1084
+ }
1085
+ } catch {
1086
+ // Malformed history — fall back to empty args rather than failing replay.
1087
+ }
1088
+ return {};
1089
+ }
1090
+
1091
+ /**
1092
+ * Complete the checkpointed navigating tool call and continue the run.
1093
+ *
1094
+ * The call's card is settled here, from the result the continuation carries.
1095
+ * The restore left it open on purpose -- it is the one call a reload was
1096
+ * expected by -- and nothing after this answers it: the run that follows
1097
+ * streams no result for a call the client answered itself. Left open, it
1098
+ * spun through the whole continuation, and the sweep that closes a run then
1099
+ * found it pending and called it not finished -- on the one call known to
1100
+ * have finished, since the page it moved to is the page doing the asking.
1101
+ *
1102
+ * Settled from the string the stored message holds, and with no outcome,
1103
+ * since `addToolResult` writes none, so the card is the one a later reload
1104
+ * draws from that message. The card can be missing, when the stored
1105
+ * transcript does not hold the call -- a store that kept the checkpoint and
1106
+ * lost the turn that made it -- and then there is nothing to settle. That
1107
+ * guard is held by "resumes a checkpointed navigating tool call on mount" in
1108
+ * `ag_ui_chat.test.ts`, whose transcript never made the call.
1109
+ */
1110
+ async #resumeFrom(checkpoint: NavigationCheckpoint, generation: number): Promise<void> {
1111
+ this.#host.conversationStore().saveCheckpoint(this.#threadId, null);
1112
+ const client = this.#host.ensureClient();
1113
+ // Called on the element, as `this.navigationResult(...)` always was.
1114
+ const landed = JSON.stringify(
1115
+ this.#host.navigationResult().call(this.#host.element, checkpoint),
1116
+ );
1117
+ // The store's save and `navigationResult` are host code, and either can
1118
+ // have stopped the restore. Going on answered the call and resumed anyway:
1119
+ // on the client taken before `navigationResult` ran, which nothing held any
1120
+ // more, with its tools still driving the page -- or, stopped by the save, on
1121
+ // the new chat's own client, which the line above had just built.
1122
+ if (!this.#restoring(generation)) {
1123
+ return;
1124
+ }
1125
+ client.addToolResult(checkpoint.toolCallId, landed);
1126
+ this.#host.transcript.card(checkpoint.toolCallId)?.settle(TOOL_CALL_STATUS.DONE, landed);
1127
+ await client.resume();
1128
+ }
1129
+ }
1130
+
1131
+ /** One tool call as a restored assistant message carries it. */
1132
+ interface RestoredToolCall {
1133
+ readonly id: string;
1134
+ readonly function: { readonly name: string; readonly arguments?: unknown };
1135
+ }
1136
+
1137
+ /**
1138
+ * The tool calls a restored assistant turn carries, with anything shapeless dropped.
1139
+ *
1140
+ * Narrowing here rather than trusting the declared type, for three reasons that
1141
+ * point the same way.
1142
+ *
1143
+ * **`null` is a value this field really takes.** `@ag-ui/core` types `toolCalls`
1144
+ * as optional (`z.ZodOptional`), so TypeScript offers only `undefined` — but the
1145
+ * protocol's Python models declare `tool_calls: list[ToolCall] | None`, and a
1146
+ * server dumping them without `exclude_none` sends `null`. The two SDKs disagree
1147
+ * about the wire, and a client cannot afford to take either one's word for it.
1148
+ *
1149
+ * **A throw here costs the rest of the transcript.** This runs inside the replay
1150
+ * of stored history, one message at a time; an exception aborts the whole replay,
1151
+ * so a single bad entry silently truncates the conversation from that point on —
1152
+ * with no error state and nothing on screen to explain the gap.
1153
+ *
1154
+ * **Storage is untrusted anyway** — hand-edited, truncated, written by an older
1155
+ * version, or supplied by a host's own store. `messageAttachments` already takes
1156
+ * exactly this stance for the neighbouring field on the same message.
1157
+ */
1158
+ function restoredToolCalls(value: unknown): readonly RestoredToolCall[] {
1159
+ return Array.isArray(value) ? value.filter(isRestoredToolCall) : [];
1160
+ }
1161
+
1162
+ /** Whether an unknown history entry has enough shape to render a tool card. */
1163
+ function isRestoredToolCall(value: unknown): value is RestoredToolCall {
1164
+ if (typeof value !== "object" || value === null) {
1165
+ return false;
1166
+ }
1167
+ const call = value as { id?: unknown; function?: { name?: unknown } };
1168
+ return typeof call.id === "string" && typeof call.function?.name === "string";
1169
+ }