@openhands/agent-canvas 1.15.0 → 1.16.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 (739) hide show
  1. package/README.md +15 -1
  2. package/README.windows.md +2 -2
  3. package/build/assets/{acp-providers-BKvmTf82.js → acp-providers-CX3Lh9gj.js} +1 -1
  4. package/build/assets/active-backend-context-CrHTlgvN.js +2 -0
  5. package/build/assets/{agent-context-settings-D3PjSTU1.js → agent-context-settings-DSZlXrLj.js} +1 -1
  6. package/build/assets/{agent-profiles-settings-D4cY5U2T.js → agent-profiles-settings-Dzgb7G9z.js} +1 -1
  7. package/build/assets/agent-server-conversation-service.api-tnqNjbpE.js +86 -0
  8. package/build/assets/agent-settings-DLtsqaOs.js +2 -0
  9. package/build/assets/agent-settings-fFvR-XC2.js +1 -0
  10. package/build/assets/alert-banner-HP6mX-Vr.js +1 -0
  11. package/build/assets/{api-error-message-BJyfWp6p.js → api-error-message-B-XdSk3b.js} +1 -1
  12. package/build/assets/api-key-entry-screen-qTzrmJcr.js +1 -0
  13. package/build/assets/{app-settings-CbHqzQus.js → app-settings-dbnneVaV.js} +1 -1
  14. package/build/assets/automation-detail-BmwlCUAB.js +3 -0
  15. package/build/assets/{automation-export-BRopEHMu.js → automation-export-D3a3of4G.js} +1 -1
  16. package/build/assets/{automation-git-sync-CzyS4cn1.js → automation-git-sync-CprN5DcU.js} +1 -1
  17. package/build/assets/automation-setup-route-C40kXjMY.js +1 -0
  18. package/build/assets/{automation-templates-C_0FZgLG.js → automation-templates-BRppNh99.js} +1 -1
  19. package/build/assets/automations-list-DuNcRawl.js +1 -0
  20. package/build/assets/{back-nav-button-Dm9tIjVf.js → back-nav-button-Cjl_hoxq.js} +1 -1
  21. package/build/assets/backend-form-modal-lhGwzXFI.js +1 -0
  22. package/build/assets/{backend-synced-settings-badge-3fOwg6Bd.js → backend-synced-settings-badge-DPkz30BE.js} +1 -1
  23. package/build/assets/{badge-D4SqUCsR.js → badge-BU3S0VtE.js} +1 -1
  24. package/build/assets/{base-modal-C7_BDl_U.js → base-modal-D_L0oEbG.js} +1 -1
  25. package/build/assets/{bot-C-0Wu7vQ.js → bot-dMYHVfYM.js} +1 -1
  26. package/build/assets/{brand-button-Dxv6PDeN.js → brand-button-BAfk4wGP.js} +1 -1
  27. package/build/assets/browser-DSAo0tz1.js +46 -0
  28. package/build/assets/{browser-tab-BAl4ckJE.js → browser-tab-C9vy56R5.js} +1 -1
  29. package/build/assets/canvas-extension-page-dx1gFrfW.js +1 -0
  30. package/build/assets/canvas-extensions-8A-XhpgP.js +1 -0
  31. package/build/assets/canvas-extensions-runtime-2_x5Ezmi.js +1 -0
  32. package/build/assets/{chat-send-button-DWwR2cgI.js → chat-send-button-MUQqCZCS.js} +1 -1
  33. package/build/assets/check-CS0vmpQQ.js +1 -0
  34. package/build/assets/chevron-down-e1QBpjVy.js +1 -0
  35. package/build/assets/chevron-right-ZG4R5G4Q.js +1 -0
  36. package/build/assets/circle-check-DzWLNsbE.js +1 -0
  37. package/build/assets/{circle-plus-check-toggle-CBh_qomE.js → circle-plus-check-toggle-BO_kFZt3.js} +1 -1
  38. package/build/assets/{cloud-funnel-analytics-287iIERo.js → cloud-funnel-analytics-CztBCCPp.js} +1 -1
  39. package/build/assets/{cog-C4rrw-ZK.js → cog-D-MPQW6G.js} +1 -1
  40. package/build/assets/command-menu--UIlGL72.js +1 -0
  41. package/build/assets/{commits-tab-DZtJgaDM.js → commits-tab-AnZwLvh-.js} +1 -1
  42. package/build/assets/{condenser-settings-BcNmYR4s.js → condenser-settings-DmEOje-7.js} +1 -1
  43. package/build/assets/{confirmation-modal-Bk4JRcCM.js → confirmation-modal-Dv1ktp8K.js} +1 -1
  44. package/build/assets/{context-menu-list-item-lVYETtY5.js → context-menu-list-item-I86vKcmM.js} +1 -1
  45. package/build/assets/conversation-BbGrAO4Z.js +93 -0
  46. package/build/assets/conversation-mYT6NAoj.js +1 -0
  47. package/build/assets/conversation-panel-mYT6NAoj.js +1 -0
  48. package/build/assets/conversation-state-store-B0ImPLHO.js +1 -0
  49. package/build/assets/{conversation-tab-empty-state-HJtkmHZj.js → conversation-tab-empty-state-FIbh_lc8.js} +1 -1
  50. package/build/assets/{copy-to-clipboard-button-Cjkswvnj.js → copy-to-clipboard-button-Ca8iSkkA.js} +1 -1
  51. package/build/assets/{device-verify-EZhHhBT7.js → device-verify-Bhn2e_Ye.js} +1 -1
  52. package/build/assets/{dropdown-classes-DJe-E6MC.js → dropdown-classes-Bc1djN0-.js} +1 -1
  53. package/build/assets/{edit-automation-modal-D-urtv8n.js → edit-automation-modal-DadGbJ2u.js} +1 -1
  54. package/build/assets/empty-state-BoGVDecT.js +1 -0
  55. package/build/assets/{entry.client-C7Bi2tkU.js → entry.client-CqyPPjOq.js} +2 -2
  56. package/build/assets/{enum-filter-dropdown-DliX_Lo0.js → enum-filter-dropdown-Bgca129f.js} +1 -1
  57. package/build/assets/{environment-switch-overlay-CEKrFiMw.js → environment-switch-overlay-BmBRT7gv.js} +1 -1
  58. package/build/assets/{error-state-BALXiBNy.js → error-state-Body8Ngd.js} +1 -1
  59. package/build/assets/extensions-hub-CcDqq_vR.js +1 -0
  60. package/build/assets/extensions-navigation-Cr4yzYk4.js +1 -0
  61. package/build/assets/{external-link-DFpYunfj.js → external-link-DKUlnCQV.js} +1 -1
  62. package/build/assets/files-tab-D4o2VyE_.js +1 -0
  63. package/build/assets/{form-control-classes-CGw40z5h.js → form-control-classes-Bkh5FGrD.js} +1 -1
  64. package/build/assets/format-model-name-CasAP3nd.js +1 -0
  65. package/build/assets/get-git-path-ChKDVHZp.js +1 -0
  66. package/build/assets/{git-repo-dropdown-ai8qaNkD.js → git-repo-dropdown-4ROKl2cB.js} +1 -1
  67. package/build/assets/git-repo-dropdown-BkqzkMmM.js +1 -0
  68. package/build/assets/{highlighted-source-view-C3sxykQi.js → highlighted-source-view-BFRxGT_Q.js} +1 -1
  69. package/build/assets/home-ChpSuepX.js +1 -0
  70. package/build/assets/home-DIsB440S.js +1 -0
  71. package/build/assets/home-automation-run-tooltip-BVNDWjAH.js +1 -0
  72. package/build/assets/home-store-CY9Ej0ux.js +1 -0
  73. package/build/assets/index-home-BdlrFIif.js +1 -0
  74. package/build/assets/info-B6RQawoW.js +1 -0
  75. package/build/assets/install-server-modal-Dpy89p4g.js +1 -0
  76. package/build/assets/kebab-menu-item-content-D8hSNGmm.js +1 -0
  77. package/build/assets/{key-Cn5za3rg.js → key-BmVyxs-2.js} +1 -1
  78. package/build/assets/{launch-C1ieiOGe.js → launch-IrTDEFCs.js} +1 -1
  79. package/build/assets/list-filter-DsEK7NFC.js +1 -0
  80. package/build/assets/{list-todo-Cuu-STFc.js → list-todo-apgLVjMw.js} +1 -1
  81. package/build/assets/llm-not-configured-banner-CIFABh3K.js +27 -0
  82. package/build/assets/llm-settings-B9juXhIa.js +1 -0
  83. package/build/assets/llm-settings-De1d5dfr.js +1 -0
  84. package/build/assets/llm-subscription-service-BpJ0ouQo.js +1 -0
  85. package/build/assets/loader-circle-CC1au6hc.js +1 -0
  86. package/build/assets/{loading-spinner-0L3ZTToZ.js → loading-spinner-BY9N3pOe.js} +1 -1
  87. package/build/assets/manage-backends-modal-BrPV3oB-.js +1 -0
  88. package/build/assets/manifest-80fb1c29.js +1 -0
  89. package/build/assets/{manifest-subpage-layout-BGhiUQbA.js → manifest-subpage-layout-CAOYzrYp.js} +1 -1
  90. package/build/assets/{map-provider-B9LTm23I.js → map-provider-Cu3Zgumb.js} +1 -1
  91. package/build/assets/markdown-renderer-BzTlxI8x.js +8 -0
  92. package/build/assets/{mcp-CKbR4Vft.js → mcp-BTs_SXfZ.js} +1 -1
  93. package/build/assets/{mcp-marketplace-utils-C13fFccR.js → mcp-marketplace-utils-AIBzzHTO.js} +1 -1
  94. package/build/assets/{mcp-page-Bm4K2NQq.js → mcp-page-BEBYqH_K.js} +2 -2
  95. package/build/assets/messages-DKV9cRc2.js +41 -0
  96. package/build/assets/{modal-body-CMOc2tLl.js → modal-body-BxZjsju9.js} +1 -1
  97. package/build/assets/{modal-classes-BAb4Y_BG.js → modal-classes-C8eycCwG.js} +1 -1
  98. package/build/assets/{modal-close-button-CkJ0Ktjj.js → modal-close-button-DDzdRvCs.js} +1 -1
  99. package/build/assets/{mouse-pointer-click-CsgRs_-r.js → mouse-pointer-click-Co2uyaPA.js} +1 -1
  100. package/build/assets/onboarding-DV_OXoeP.js +1 -0
  101. package/build/assets/onboarding-modal-rkyBYkeQ.js +1 -0
  102. package/build/assets/{organization-service.api-C8XpQ0pM.js → organization-service.api-DrYNkEN_.js} +1 -1
  103. package/build/assets/{pencil-Clh1Wjvh.js → pencil-47jAZH_V.js} +1 -1
  104. package/build/assets/pin-By41Vl5J.js +1 -0
  105. package/build/assets/{planner-tab-DQvDU3ap.js → planner-tab-C9uZmhme.js} +1 -1
  106. package/build/assets/{plug-BuQhHSSG.js → plug-CG5HFcNG.js} +1 -1
  107. package/build/assets/plugins-management-service-BMJgC3Ga.js +1 -0
  108. package/build/assets/plus-C7Slq-0I.js +1 -0
  109. package/build/assets/profiles-service.api-ChoKrL0l.js +1 -0
  110. package/build/assets/providers-BhMcY5nv.js +1 -0
  111. package/build/assets/proxy-9Fe1v5na.js +1 -0
  112. package/build/assets/{puzzle-A9pGxxai.js → puzzle-JM_QaRm0.js} +1 -1
  113. package/build/assets/recommended-automations-launcher-6ABrI9Cy.js +1 -0
  114. package/build/assets/{refresh-cw-Bh69DyYO.js → refresh-cw-3q3Frrii.js} +1 -1
  115. package/build/assets/root-Dc8YmC9F.js +2 -0
  116. package/build/assets/root-VDaypvK4.css +1 -0
  117. package/build/assets/root-layout-CXkhSPYh.js +2 -0
  118. package/build/assets/schema-field-D-y9-LDb.js +1 -0
  119. package/build/assets/{sdk-section-page-HTffvm7Q.js → sdk-section-page-BlFiizlG.js} +1 -1
  120. package/build/assets/{secret-form-HTUbYw5o.js → secret-form-BB7the8W.js} +1 -1
  121. package/build/assets/secrets-service-ByZVENbp.js +1 -0
  122. package/build/assets/{secrets-settings-BizaTEr1.js → secrets-settings-B_GpxKW2.js} +1 -1
  123. package/build/assets/{settings-BlmrkwRi.js → settings-C0EWuBtA.js} +1 -1
  124. package/build/assets/settings-CKqk8ZnH.js +1 -0
  125. package/build/assets/{settings-dropdown-input-tM0A0ipy.js → settings-dropdown-input-BcwylHs2.js} +1 -1
  126. package/build/assets/{settings-index-CKq0b0Vg.js → settings-index-5w5UW2K3.js} +1 -1
  127. package/build/assets/{settings-input-C03KUoSC.js → settings-input-Db-1g8Ql.js} +1 -1
  128. package/build/assets/{settings-list-classes-BJ0g8rWj.js → settings-list-classes-BzeSZbF2.js} +1 -1
  129. package/build/assets/{settings-modal-AzEF1QPP.js → settings-modal-DfJ3tcJQ.js} +1 -1
  130. package/build/assets/settings-service.api-DJ1r62Zr.js +1 -0
  131. package/build/assets/{settings-switch-CQE0EGC-.js → settings-switch-CS-dJDmA.js} +1 -1
  132. package/build/assets/{settings-utils-ClYOkbbI.js → settings-utils-UYpEdPuD.js} +1 -1
  133. package/build/assets/shared-conversation-CA5ISEDy.js +1 -0
  134. package/build/assets/{shield-check-CFDViW82.js → shield-check-D2aCRd16.js} +1 -1
  135. package/build/assets/{sidebar-layout-D3hE08wK.js → sidebar-layout-D6fS8-Gl.js} +1 -1
  136. package/build/assets/{sidebar-mobile-menu-toggle-BOtyJHwE.js → sidebar-mobile-menu-toggle-C_fb0Jki.js} +1 -1
  137. package/build/assets/sidebar-nav-link-DQQcMf1A.js +1 -0
  138. package/build/assets/{sidebar-onboarding-checklist-storage-3qYflszY.js → sidebar-onboarding-checklist-storage-BBkeI6Bp.js} +1 -1
  139. package/build/assets/sidebar-store-DGqHkVLw.js +1 -0
  140. package/build/assets/{skill-card-pill-row-CFxjoqxn.js → skill-card-pill-row-B27-xyig.js} +1 -1
  141. package/build/assets/skill-enablement-DXeaImBz.js +1 -0
  142. package/build/assets/skill-filter-w__3UCEE.js +2 -0
  143. package/build/assets/{skill-icon-badge-CPQJ6A-I.js → skill-icon-badge-RQkq8wvl.js} +1 -1
  144. package/build/assets/skill-scope-B42H-XOc.js +1 -0
  145. package/build/assets/skills-plugins-tgF8EiXC.js +1 -0
  146. package/build/assets/skills-settings-BgJ9PaOe.js +1 -0
  147. package/build/assets/{styled-tooltip-Dl06rkpY.js → styled-tooltip-C4jf5Fv9.js} +1 -1
  148. package/build/assets/{task-list-tab-04GNQuii.js → task-list-tab-C5zcaktH.js} +1 -1
  149. package/build/assets/{terminal-Cj0iHIW7.js → terminal-CNElN42a.js} +2 -2
  150. package/build/assets/{toggle-switch-Fd_QGz0r.js → toggle-switch-ChOLXv2n.js} +1 -1
  151. package/build/assets/{trash-2-Dl8_RGz0.js → trash-2-D7K26eDO.js} +1 -1
  152. package/build/assets/{trash-_RMt7Ayc.js → trash-D-HYiVYM.js} +1 -1
  153. package/build/assets/{triangle-alert-SM-q4NU_.js → triangle-alert-CtdXWHUI.js} +1 -1
  154. package/build/assets/{typography-SgFy466J.js → typography-v1KN0tzi.js} +1 -1
  155. package/build/assets/usage-tab-CVDffLPt.js +1 -0
  156. package/build/assets/use-acp-credential-form-XIQmpX6M.js +1 -0
  157. package/build/assets/{use-activate-agent-profile-B2NS8nA5.js → use-activate-agent-profile-BLfmvDiG.js} +1 -1
  158. package/build/assets/use-agent-profiles-Bd9Bvvp1.js +1 -0
  159. package/build/assets/{use-automation-health-BxXdzZMP.js → use-automation-health-E6NZi9rb.js} +1 -1
  160. package/build/assets/{use-automations-zYb0dUny.js → use-automations-xijQKIl9.js} +1 -1
  161. package/build/assets/{use-can-manage-org-profiles-BWMahtUh.js → use-can-manage-org-profiles-CrJP5262.js} +1 -1
  162. package/build/assets/use-canvas-extensions-Bi693ujJ.js +1 -0
  163. package/build/assets/{use-cloud-current-user-id-CPHMDG-C.js → use-cloud-current-user-id-CDfAHpPX.js} +1 -1
  164. package/build/assets/use-config-A9QovgXO.js +1 -0
  165. package/build/assets/{use-create-conversation-DwN5vv2f.js → use-create-conversation-Dx3l7mFZ.js} +1 -1
  166. package/build/assets/use-create-secret-B9cUFWRG.js +1 -0
  167. package/build/assets/{use-get-secrets-BT4ihRwj.js → use-get-secrets-K3jmk-LH.js} +1 -1
  168. package/build/assets/use-is-authed-nTBcM7dE.js +1 -0
  169. package/build/assets/use-llm-configured-BV9nZ1_j.js +1 -0
  170. package/build/assets/use-llm-profiles-BO-P4Oyo.js +1 -0
  171. package/build/assets/use-manifest-capabilities-Cw_flwEz.js +1 -0
  172. package/build/assets/use-onboarding-completion-D8U7k4Qb.js +1 -0
  173. package/build/assets/use-pinned-home-route-CckOZrmb.js +1 -0
  174. package/build/assets/use-plugins-marketplace-7i94Zu3Z.js +1 -0
  175. package/build/assets/use-save-agent-profile-BUjSsDXK.js +1 -0
  176. package/build/assets/{use-save-settings-CUZW6yXP.js → use-save-settings-DYqkKv7T.js} +1 -1
  177. package/build/assets/{use-settings-C5VhGpqd.js → use-settings-DSXLbvBl.js} +1 -1
  178. package/build/assets/{use-settings-nav-items-DHaUu1Dg.js → use-settings-nav-items-CwRw-CmX.js} +1 -1
  179. package/build/assets/use-tracking-D53y3TH9.js +3 -0
  180. package/build/assets/{use-user-conversation-DT0_QCnL.js → use-user-conversation-B9Gebw_g.js} +1 -1
  181. package/build/assets/{useInfiniteQuery-CgeUQPfs.js → useInfiniteQuery-C926xzY4.js} +1 -1
  182. package/build/assets/{useMutation-DFnzkf6f.js → useMutation-B-qc391N.js} +1 -1
  183. package/build/assets/{useQueries-BhbrGEf1.js → useQueries-DlbhlH9I.js} +1 -1
  184. package/build/assets/{useQuery-BXWbZBZl.js → useQuery-BSTgq1rl.js} +1 -1
  185. package/build/assets/utils-CN5ZfZy1.js +1 -0
  186. package/build/assets/{vendor~conversation-panel~conversation-CTvDDiXg.js → vendor~conversation-panel~conversation-C8Fn4XOf.js} +1 -1
  187. package/build/assets/{vendor~entry.client-BFE7Llz9.js → vendor~entry.client-CoCfUXUF.js} +1 -1
  188. package/build/assets/{vendor~entry.client-BaKt_PIp.js → vendor~entry.client-DA2izTjq.js} +1 -1
  189. package/build/assets/{vendor~entry.client~root~root-layout~index-home~home~conversation-panel~conversation~launch~kepq2zme-ZixYGy6E.js → vendor~entry.client~root~root-layout~index-home~home~conversation-panel~conversation~launch~czjee3a3-DqZDXfg2.js} +1 -1
  190. package/build/assets/{vendor~index-home~home~conversation-panel~conversation-BTyukeM-.js → vendor~index-home~home~conversation-panel~conversation-hWywRBaR.js} +1 -1
  191. package/build/assets/{vendor~root-layout-BlUaZ18v.js → vendor~root-layout-C9Ju9DQg.js} +1 -1
  192. package/build/assets/{vendor~root-layout~conversation-panel~conversation-BYAWJwQX.js → vendor~root-layout~conversation-panel~conversation-BMbMgaGu.js} +1 -1
  193. package/build/assets/{vendor~root-layout~conversation-panel~conversation~skills-settings~automations-list~shared-~bovqqk8e-CHaLEBVR.js → vendor~root-layout~conversation-panel~conversation~skills-settings~automations-list~shared-~bovqqk8e-BWir3rJs.js} +1 -1
  194. package/build/assets/vendor~root-layout~index-home~home~conversation-panel~conversation-j5MFqfSy.js +1 -0
  195. package/build/assets/vendor~root-layout~index-home~home~conversation-panel~conversation~mcp~automations-list~aut~ibfbmr9f-cDuKr-WD.js +1 -0
  196. package/build/assets/{vendor~root~root-layout~index-home~home~conversation-panel~conversation~launch~extensions-h~iw7io29p-Dz-MAyNb.js → vendor~root~root-layout~index-home~home~conversation-panel~conversation~launch~extensions-h~iyia17eu-Chf7vmKB.js} +1 -1
  197. package/build/assets/{vendor~root~root-layout~index-home~home~conversation-panel~conversation~launch~extensions-h~hjcjabwg-Dtu7BpYk.js → vendor~root~root-layout~index-home~home~conversation-panel~conversation~launch~extensions-h~nj1hli3r-DP9wmsjo.js} +2 -2
  198. package/build/assets/{vendor~root~root-layout~index-home~home~conversation-panel~conversation~launch~skills-setti~eo2nf80n-DonTKfQD.js → vendor~root~root-layout~index-home~home~conversation-panel~conversation~launch~skills-setti~eo2nf80n-CUmIwQCN.js} +117 -13
  199. package/build/assets/{vendor~root~root-layout~index-home~home~conversation-panel~conversation~launch~skills-setti~jz4f6vo2-BgxDvUcf.js → vendor~root~root-layout~index-home~home~conversation-panel~conversation~launch~skills-setti~jz4f6vo2-C7HiY4Ae.js} +42 -3
  200. package/build/assets/{verification-settings-Bj6bo097.js → verification-settings-gw_q6vkL.js} +1 -1
  201. package/build/assets/{wrench-hi7Q13dF.js → wrench-CUEI1krB.js} +1 -1
  202. package/build/assets/{x-DkgltWa0.js → x-DJdhbdmt.js} +1 -1
  203. package/build/index.html +4 -4
  204. package/build/locales/ar/openhands.json +32 -16
  205. package/build/locales/ca/openhands.json +32 -16
  206. package/build/locales/de/openhands.json +32 -16
  207. package/build/locales/en/openhands.json +32 -16
  208. package/build/locales/es/openhands.json +32 -16
  209. package/build/locales/fr/openhands.json +32 -16
  210. package/build/locales/it/openhands.json +32 -16
  211. package/build/locales/ja/openhands.json +32 -16
  212. package/build/locales/ko-KR/openhands.json +32 -16
  213. package/build/locales/no/openhands.json +32 -16
  214. package/build/locales/pt/openhands.json +32 -16
  215. package/build/locales/tr/openhands.json +32 -16
  216. package/build/locales/uk/openhands.json +32 -16
  217. package/build/locales/zh-CN/openhands.json +32 -16
  218. package/build/locales/zh-TW/openhands.json +32 -16
  219. package/config/defaults.json +9 -5
  220. package/dist/api/agent-profiles-service/profile-field-support.d.ts +26 -0
  221. package/dist/api/agent-server-adapter.cjs +3 -3
  222. package/dist/api/agent-server-adapter.cjs.map +1 -1
  223. package/dist/api/agent-server-adapter.js +118 -116
  224. package/dist/api/agent-server-adapter.js.map +1 -1
  225. package/dist/api/agent-server-compatibility.cjs +1 -1
  226. package/dist/api/agent-server-compatibility.cjs.map +1 -1
  227. package/dist/api/agent-server-compatibility.d.ts +21 -0
  228. package/dist/api/agent-server-compatibility.js +52 -38
  229. package/dist/api/agent-server-compatibility.js.map +1 -1
  230. package/dist/api/agent-server-config.cjs +1 -1
  231. package/dist/api/agent-server-config.cjs.map +1 -1
  232. package/dist/api/agent-server-config.d.ts +26 -0
  233. package/dist/api/agent-server-config.js +18 -5
  234. package/dist/api/agent-server-config.js.map +1 -1
  235. package/dist/api/canvas-extensions-service.cjs +1 -0
  236. package/dist/api/canvas-extensions-service.d.ts +22 -0
  237. package/dist/api/canvas-extensions-service.js +4 -0
  238. package/dist/api/cloud/conversation-service.api.cjs +1 -1
  239. package/dist/api/cloud/conversation-service.api.cjs.map +1 -1
  240. package/dist/api/cloud/conversation-service.api.d.ts +6 -0
  241. package/dist/api/cloud/conversation-service.api.js +14 -6
  242. package/dist/api/cloud/conversation-service.api.js.map +1 -1
  243. package/dist/api/conversation-file-upload.api.cjs +1 -1
  244. package/dist/api/conversation-file-upload.api.js +10 -10
  245. package/dist/api/conversation-service/agent-server-conversation-service.api.cjs +1 -1
  246. package/dist/api/conversation-service/agent-server-conversation-service.api.cjs.map +1 -1
  247. package/dist/api/conversation-service/agent-server-conversation-service.api.d.ts +11 -0
  248. package/dist/api/conversation-service/agent-server-conversation-service.api.js +129 -118
  249. package/dist/api/conversation-service/agent-server-conversation-service.api.js.map +1 -1
  250. package/dist/api/event-service/event-service.api.cjs +1 -1
  251. package/dist/api/event-service/event-service.api.js +12 -12
  252. package/dist/api/git-service/agent-server-git-service.api.cjs +1 -1
  253. package/dist/api/git-service/agent-server-git-service.api.js +8 -8
  254. package/dist/api/runtime-service/agent-server-runtime-service.cjs +1 -1
  255. package/dist/api/runtime-service/agent-server-runtime-service.js +8 -8
  256. package/dist/api/settings-service/settings-service.api.cjs +1 -1
  257. package/dist/api/settings-service/settings-service.api.cjs.map +1 -1
  258. package/dist/api/settings-service/settings-service.api.d.ts +3 -2
  259. package/dist/api/settings-service/settings-service.api.js +2 -1
  260. package/dist/api/settings-service/settings-service.api.js.map +1 -1
  261. package/dist/components/conversation-events/chat/event-message-components/event-group.cjs +1 -1
  262. package/dist/components/conversation-events/chat/event-message-components/event-group.cjs.map +1 -1
  263. package/dist/components/conversation-events/chat/event-message-components/event-group.js +40 -36
  264. package/dist/components/conversation-events/chat/event-message-components/event-group.js.map +1 -1
  265. package/dist/components/features/automations/automation-card.cjs +1 -1
  266. package/dist/components/features/automations/automation-card.cjs.map +1 -1
  267. package/dist/components/features/automations/automation-card.js +114 -104
  268. package/dist/components/features/automations/automation-card.js.map +1 -1
  269. package/dist/components/features/automations/detail/run-phase.cjs +2 -0
  270. package/dist/components/features/automations/detail/run-phase.cjs.map +1 -0
  271. package/dist/components/features/automations/detail/run-phase.d.ts +89 -0
  272. package/dist/components/features/automations/detail/run-phase.js +72 -0
  273. package/dist/components/features/automations/detail/run-phase.js.map +1 -0
  274. package/dist/components/features/backends/backend-form-modal.cjs +1 -1
  275. package/dist/components/features/backends/backend-form-modal.cjs.map +1 -1
  276. package/dist/components/features/backends/backend-form-modal.js +5 -11
  277. package/dist/components/features/backends/backend-form-modal.js.map +1 -1
  278. package/dist/components/features/canvas-extensions/add-canvas-extension-modal.d.ts +6 -0
  279. package/dist/components/features/canvas-extensions/canvas-extension-card.d.ts +9 -0
  280. package/dist/components/features/canvas-extensions/canvas-extensions-runtime.cjs +2 -0
  281. package/dist/components/features/canvas-extensions/canvas-extensions-runtime.cjs.map +1 -0
  282. package/dist/components/features/canvas-extensions/canvas-extensions-runtime.d.ts +23 -0
  283. package/dist/components/features/canvas-extensions/canvas-extensions-runtime.js +19 -0
  284. package/dist/components/features/canvas-extensions/canvas-extensions-runtime.js.map +1 -0
  285. package/dist/components/features/chat/chat-interface.cjs +2 -2
  286. package/dist/components/features/chat/chat-interface.cjs.map +1 -1
  287. package/dist/components/features/chat/chat-interface.js +171 -168
  288. package/dist/components/features/chat/chat-interface.js.map +1 -1
  289. package/dist/components/features/chat/chat-markdown-path-code.cjs +3 -0
  290. package/dist/components/features/chat/chat-markdown-path-code.cjs.map +1 -0
  291. package/dist/components/features/chat/chat-markdown-path-code.d.ts +27 -0
  292. package/dist/components/features/chat/chat-markdown-path-code.js +72 -0
  293. package/dist/components/features/chat/chat-markdown-path-code.js.map +1 -0
  294. package/dist/components/features/chat/path-component.cjs +1 -1
  295. package/dist/components/features/chat/path-component.cjs.map +1 -1
  296. package/dist/components/features/chat/path-component.d.ts +8 -2
  297. package/dist/components/features/chat/path-component.js +33 -21
  298. package/dist/components/features/chat/path-component.js.map +1 -1
  299. package/dist/components/features/chat/tool-visualizers/file-editor/file-editor.cjs +1 -1
  300. package/dist/components/features/chat/tool-visualizers/file-editor/file-editor.cjs.map +1 -1
  301. package/dist/components/features/chat/tool-visualizers/file-editor/file-editor.js +38 -43
  302. package/dist/components/features/chat/tool-visualizers/file-editor/file-editor.js.map +1 -1
  303. package/dist/components/features/chat/tool-visualizers/primitives/file-path-chip.cjs +1 -1
  304. package/dist/components/features/chat/tool-visualizers/primitives/file-path-chip.cjs.map +1 -1
  305. package/dist/components/features/chat/tool-visualizers/primitives/file-path-chip.d.ts +8 -5
  306. package/dist/components/features/chat/tool-visualizers/primitives/file-path-chip.js +22 -16
  307. package/dist/components/features/chat/tool-visualizers/primitives/file-path-chip.js.map +1 -1
  308. package/dist/components/features/chat/user-message-body.cjs +1 -1
  309. package/dist/components/features/chat/user-message-body.cjs.map +1 -1
  310. package/dist/components/features/chat/user-message-body.d.ts +4 -0
  311. package/dist/components/features/chat/user-message-body.js +30 -24
  312. package/dist/components/features/chat/user-message-body.js.map +1 -1
  313. package/dist/components/features/conversation/conversation-overview-skills-panel.cjs +1 -1
  314. package/dist/components/features/conversation/conversation-overview-skills-panel.cjs.map +1 -1
  315. package/dist/components/features/conversation/conversation-overview-skills-panel.js +63 -81
  316. package/dist/components/features/conversation/conversation-overview-skills-panel.js.map +1 -1
  317. package/dist/components/features/conversation/conversation-tabs/conversation-tabs.cjs +1 -1
  318. package/dist/components/features/conversation/conversation-tabs/conversation-tabs.cjs.map +1 -1
  319. package/dist/components/features/conversation/conversation-tabs/conversation-tabs.js +50 -50
  320. package/dist/components/features/conversation/conversation-tabs/conversation-tabs.js.map +1 -1
  321. package/dist/components/features/conversation/conversation-tabs/drawer-vscode-link.cjs +1 -1
  322. package/dist/components/features/conversation/conversation-tabs/drawer-vscode-link.cjs.map +1 -1
  323. package/dist/components/features/conversation/conversation-tabs/drawer-vscode-link.d.ts +1 -1
  324. package/dist/components/features/conversation/conversation-tabs/drawer-vscode-link.js +3 -3
  325. package/dist/components/features/conversation/conversation-tabs/drawer-vscode-link.js.map +1 -1
  326. package/dist/components/features/conversation-panel/skills-modal.cjs +1 -1
  327. package/dist/components/features/conversation-panel/skills-modal.cjs.map +1 -1
  328. package/dist/components/features/conversation-panel/skills-modal.js +29 -28
  329. package/dist/components/features/conversation-panel/skills-modal.js.map +1 -1
  330. package/dist/components/features/files-tab/file-content-viewer.cjs +1 -1
  331. package/dist/components/features/files-tab/file-content-viewer.js +5 -5
  332. package/dist/components/features/home/featured-automations/home-automation-run-tooltip.cjs +1 -1
  333. package/dist/components/features/home/featured-automations/home-automation-run-tooltip.cjs.map +1 -1
  334. package/dist/components/features/home/featured-automations/home-automation-run-tooltip.js +51 -29
  335. package/dist/components/features/home/featured-automations/home-automation-run-tooltip.js.map +1 -1
  336. package/dist/components/features/onboarding/onboarding-host.d.ts +3 -4
  337. package/dist/components/features/onboarding/steps/setup-llm-step.d.ts +5 -9
  338. package/dist/components/features/settings/acp-auth-status-banner.d.ts +23 -6
  339. package/dist/components/features/settings/llm-profiles/llm-settings-local-view.cjs +1 -1
  340. package/dist/components/features/settings/llm-profiles/llm-settings-local-view.js +1 -0
  341. package/dist/components/features/settings/llm-profiles/provider-connection-modal.cjs +1 -1
  342. package/dist/components/features/settings/llm-profiles/provider-connection-modal.js +3 -0
  343. package/dist/components/features/settings/sdk-settings/field-help.cjs +1 -1
  344. package/dist/components/features/settings/sdk-settings/field-help.cjs.map +1 -1
  345. package/dist/components/features/settings/sdk-settings/field-help.d.ts +1 -1
  346. package/dist/components/features/settings/sdk-settings/field-help.js +1 -1
  347. package/dist/components/features/settings/sdk-settings/field-help.js.map +1 -1
  348. package/dist/components/features/settings/sdk-settings/sdk-section-page.cjs +1 -1
  349. package/dist/components/features/settings/sdk-settings/sdk-section-page.js +6 -6
  350. package/dist/components/features/settings/sdk-settings/sdk-section-page.js.map +1 -1
  351. package/dist/components/features/sidebar/sidebar-nav-link.cjs +1 -1
  352. package/dist/components/features/sidebar/sidebar-nav-link.cjs.map +1 -1
  353. package/dist/components/features/sidebar/sidebar-nav-link.d.ts +10 -1
  354. package/dist/components/features/sidebar/sidebar-nav-link.js +54 -36
  355. package/dist/components/features/sidebar/sidebar-nav-link.js.map +1 -1
  356. package/dist/components/features/sidebar/sidebar-rail-body.cjs +1 -1
  357. package/dist/components/features/sidebar/sidebar-rail-body.cjs.map +1 -1
  358. package/dist/components/features/sidebar/sidebar-rail-body.js +143 -120
  359. package/dist/components/features/sidebar/sidebar-rail-body.js.map +1 -1
  360. package/dist/components/features/sidebar/sidebar.cjs +1 -1
  361. package/dist/components/features/sidebar/sidebar.cjs.map +1 -1
  362. package/dist/components/features/sidebar/sidebar.js +1 -1
  363. package/dist/components/features/sidebar/sidebar.js.map +1 -1
  364. package/dist/components/features/skills/build-skill-pills.cjs +1 -1
  365. package/dist/components/features/skills/build-skill-pills.cjs.map +1 -1
  366. package/dist/components/features/skills/build-skill-pills.js +60 -52
  367. package/dist/components/features/skills/build-skill-pills.js.map +1 -1
  368. package/dist/components/features/skills/extensions-navigation.cjs +1 -1
  369. package/dist/components/features/skills/extensions-navigation.cjs.map +1 -1
  370. package/dist/components/features/skills/extensions-navigation.js +54 -43
  371. package/dist/components/features/skills/extensions-navigation.js.map +1 -1
  372. package/dist/components/features/skills/skill-filter.cjs +1 -1
  373. package/dist/components/features/skills/skill-filter.cjs.map +1 -1
  374. package/dist/components/features/skills/skill-filter.d.ts +8 -3
  375. package/dist/components/features/skills/skill-filter.js +69 -52
  376. package/dist/components/features/skills/skill-filter.js.map +1 -1
  377. package/dist/components/shared/modals/settings/settings-form.cjs +1 -1
  378. package/dist/components/shared/modals/settings/settings-form.js +8 -8
  379. package/dist/config/defaults.cjs +1 -1
  380. package/dist/config/defaults.cjs.map +1 -1
  381. package/dist/config/defaults.js +9 -5
  382. package/dist/config/defaults.js.map +1 -1
  383. package/dist/contexts/conversation-websocket-context.cjs +3 -3
  384. package/dist/contexts/conversation-websocket-context.cjs.map +1 -1
  385. package/dist/contexts/conversation-websocket-context.js +5 -5
  386. package/dist/extensions/canvas-extension-module-loader.d.ts +11 -0
  387. package/dist/hooks/mutation/use-manage-canvas-extensions.d.ts +12 -0
  388. package/dist/hooks/query/concurrency-limiter.cjs +2 -0
  389. package/dist/hooks/query/concurrency-limiter.cjs.map +1 -0
  390. package/dist/hooks/query/concurrency-limiter.d.ts +22 -0
  391. package/dist/hooks/query/concurrency-limiter.js +29 -0
  392. package/dist/hooks/query/concurrency-limiter.js.map +1 -0
  393. package/dist/hooks/query/query-keys.cjs.map +1 -1
  394. package/dist/hooks/query/query-keys.d.ts +4 -0
  395. package/dist/hooks/query/query-keys.js.map +1 -1
  396. package/dist/hooks/query/use-active-conversation.cjs +1 -1
  397. package/dist/hooks/query/use-active-conversation.js +4 -4
  398. package/dist/hooks/query/use-automations.cjs.map +1 -1
  399. package/dist/hooks/query/use-automations.js.map +1 -1
  400. package/dist/hooks/query/use-backends-health.cjs +1 -1
  401. package/dist/hooks/query/use-backends-health.cjs.map +1 -1
  402. package/dist/hooks/query/use-backends-health.d.ts +1 -1
  403. package/dist/hooks/query/use-backends-health.js +50 -64
  404. package/dist/hooks/query/use-backends-health.js.map +1 -1
  405. package/dist/hooks/query/use-canvas-extensions.cjs +1 -0
  406. package/dist/hooks/query/use-canvas-extensions.d.ts +5 -0
  407. package/dist/hooks/query/use-canvas-extensions.js +3 -0
  408. package/dist/hooks/query/use-commit-changes.cjs +1 -1
  409. package/dist/hooks/query/use-commit-changes.js +4 -4
  410. package/dist/hooks/query/use-latest-automation-runs.cjs +1 -1
  411. package/dist/hooks/query/use-latest-automation-runs.js +1 -0
  412. package/dist/hooks/query/use-paginated-conversations.cjs +1 -1
  413. package/dist/hooks/query/use-paginated-conversations.cjs.map +1 -1
  414. package/dist/hooks/query/use-paginated-conversations.js +19 -16
  415. package/dist/hooks/query/use-paginated-conversations.js.map +1 -1
  416. package/dist/hooks/query/use-search-providers.cjs +1 -1
  417. package/dist/hooks/query/use-search-providers.cjs.map +1 -1
  418. package/dist/hooks/query/use-search-providers.js +17 -10
  419. package/dist/hooks/query/use-search-providers.js.map +1 -1
  420. package/dist/hooks/query/use-unified-get-git-changes.cjs +1 -1
  421. package/dist/hooks/query/use-unified-get-git-changes.js +4 -4
  422. package/dist/hooks/query/use-unified-git-commits.cjs +1 -1
  423. package/dist/hooks/query/use-unified-git-commits.js +4 -4
  424. package/dist/hooks/query/use-unified-git-diff.cjs +1 -1
  425. package/dist/hooks/query/use-unified-git-diff.js +4 -4
  426. package/dist/hooks/query/use-unified-vscode-url.cjs +1 -1
  427. package/dist/hooks/query/use-unified-vscode-url.cjs.map +1 -1
  428. package/dist/hooks/query/use-unified-vscode-url.d.ts +1 -0
  429. package/dist/hooks/query/use-unified-vscode-url.js +40 -26
  430. package/dist/hooks/query/use-unified-vscode-url.js.map +1 -1
  431. package/dist/hooks/query/use-workspace-file-content.cjs +1 -1
  432. package/dist/hooks/query/use-workspace-file-content.js +5 -5
  433. package/dist/hooks/query/use-workspace-files.cjs +1 -1
  434. package/dist/hooks/query/use-workspace-files.js +6 -6
  435. package/dist/hooks/use-acp-credential-form.d.ts +6 -0
  436. package/dist/hooks/use-conversation-overview-git-diff-stats.cjs +1 -1
  437. package/dist/hooks/use-conversation-overview-git-diff-stats.js +4 -4
  438. package/dist/hooks/use-pinned-home-route.cjs +2 -0
  439. package/dist/hooks/use-pinned-home-route.cjs.map +1 -0
  440. package/dist/hooks/use-pinned-home-route.d.ts +33 -0
  441. package/dist/hooks/use-pinned-home-route.js +34 -0
  442. package/dist/hooks/use-pinned-home-route.js.map +1 -0
  443. package/dist/hooks/use-skill-enablement.cjs +2 -0
  444. package/dist/hooks/use-skill-enablement.cjs.map +1 -0
  445. package/dist/hooks/use-skill-enablement.d.ts +14 -0
  446. package/dist/hooks/use-skill-enablement.js +80 -0
  447. package/dist/hooks/use-skill-enablement.js.map +1 -0
  448. package/dist/i18n/declaration.cjs +1 -1
  449. package/dist/i18n/declaration.cjs.map +1 -1
  450. package/dist/i18n/declaration.d.ts +29 -13
  451. package/dist/i18n/declaration.js +1 -1
  452. package/dist/i18n/declaration.js.map +1 -1
  453. package/dist/i18n/translation.cjs +3 -3
  454. package/dist/i18n/translation.cjs.map +1 -1
  455. package/dist/i18n/translation.js +538 -266
  456. package/dist/i18n/translation.js.map +1 -1
  457. package/dist/index.cjs +1 -1
  458. package/dist/index.js +15 -14
  459. package/dist/lib/index.cjs +1 -1
  460. package/dist/lib/index.d.ts +1 -0
  461. package/dist/lib/index.js +1 -0
  462. package/dist/locales/ar/openhands.json +32 -16
  463. package/dist/locales/ca/openhands.json +32 -16
  464. package/dist/locales/de/openhands.json +32 -16
  465. package/dist/locales/en/openhands.json +32 -16
  466. package/dist/locales/es/openhands.json +32 -16
  467. package/dist/locales/fr/openhands.json +32 -16
  468. package/dist/locales/it/openhands.json +32 -16
  469. package/dist/locales/ja/openhands.json +32 -16
  470. package/dist/locales/ko-KR/openhands.json +32 -16
  471. package/dist/locales/no/openhands.json +32 -16
  472. package/dist/locales/pt/openhands.json +32 -16
  473. package/dist/locales/tr/openhands.json +32 -16
  474. package/dist/locales/uk/openhands.json +32 -16
  475. package/dist/locales/zh-CN/openhands.json +32 -16
  476. package/dist/locales/zh-TW/openhands.json +32 -16
  477. package/dist/manifests/automation-interface.cjs +1 -1
  478. package/dist/manifests/automation-interface.cjs.map +1 -1
  479. package/dist/manifests/automation-interface.d.ts +6 -0
  480. package/dist/manifests/automation-interface.js +11 -8
  481. package/dist/manifests/automation-interface.js.map +1 -1
  482. package/dist/node_modules/@openhands/extensions/automations/bundle-index.cjs +45 -4
  483. package/dist/node_modules/@openhands/extensions/automations/bundle-index.cjs.map +1 -1
  484. package/dist/node_modules/@openhands/extensions/automations/bundle-index.js +1 -1
  485. package/dist/node_modules/@openhands/extensions/automations/bundle-index.js.map +1 -1
  486. package/dist/node_modules/@openhands/extensions/automations/catalog/github-agents-md-maintainer/manifest.cjs +1 -1
  487. package/dist/node_modules/@openhands/extensions/automations/catalog/github-agents-md-maintainer/manifest.cjs.map +1 -1
  488. package/dist/node_modules/@openhands/extensions/automations/catalog/github-agents-md-maintainer/manifest.js +5 -0
  489. package/dist/node_modules/@openhands/extensions/automations/catalog/github-agents-md-maintainer/manifest.js.map +1 -1
  490. package/dist/node_modules/@openhands/extensions/automations/catalog/github-issue-to-pr/manifest.cjs +1 -1
  491. package/dist/node_modules/@openhands/extensions/automations/catalog/github-issue-to-pr/manifest.cjs.map +1 -1
  492. package/dist/node_modules/@openhands/extensions/automations/catalog/github-issue-to-pr/manifest.js +5 -0
  493. package/dist/node_modules/@openhands/extensions/automations/catalog/github-issue-to-pr/manifest.js.map +1 -1
  494. package/dist/node_modules/@openhands/extensions/automations/catalog/github-pr-reviewer/manifest.cjs +1 -1
  495. package/dist/node_modules/@openhands/extensions/automations/catalog/github-pr-reviewer/manifest.cjs.map +1 -1
  496. package/dist/node_modules/@openhands/extensions/automations/catalog/github-pr-reviewer/manifest.js +5 -0
  497. package/dist/node_modules/@openhands/extensions/automations/catalog/github-pr-reviewer/manifest.js.map +1 -1
  498. package/dist/node_modules/@openhands/extensions/automations/catalog/github-repo-monitor/manifest.cjs +1 -1
  499. package/dist/node_modules/@openhands/extensions/automations/catalog/github-repo-monitor/manifest.cjs.map +1 -1
  500. package/dist/node_modules/@openhands/extensions/automations/catalog/github-repo-monitor/manifest.js +5 -0
  501. package/dist/node_modules/@openhands/extensions/automations/catalog/github-repo-monitor/manifest.js.map +1 -1
  502. package/dist/node_modules/@openhands/extensions/automations/catalog/jira-issue-to-bitbucket-pr/manifest.cjs +9 -0
  503. package/dist/node_modules/@openhands/extensions/automations/catalog/jira-issue-to-bitbucket-pr/manifest.cjs.map +1 -0
  504. package/dist/node_modules/@openhands/extensions/automations/catalog/jira-issue-to-bitbucket-pr/manifest.js +21 -0
  505. package/dist/node_modules/@openhands/extensions/automations/catalog/jira-issue-to-bitbucket-pr/manifest.js.map +1 -0
  506. package/dist/node_modules/@openhands/extensions/automations/catalog/jira-issue-to-gitlab-mr/manifest.cjs +9 -0
  507. package/dist/node_modules/@openhands/extensions/automations/catalog/jira-issue-to-gitlab-mr/manifest.cjs.map +1 -0
  508. package/dist/node_modules/@openhands/extensions/automations/catalog/jira-issue-to-gitlab-mr/manifest.js +21 -0
  509. package/dist/node_modules/@openhands/extensions/automations/catalog/jira-issue-to-gitlab-mr/manifest.js.map +1 -0
  510. package/dist/node_modules/@openhands/extensions/automations/catalog/linear-issue-to-bitbucket-pr/manifest.cjs +9 -0
  511. package/dist/node_modules/@openhands/extensions/automations/catalog/linear-issue-to-bitbucket-pr/manifest.cjs.map +1 -0
  512. package/dist/node_modules/@openhands/extensions/automations/catalog/linear-issue-to-bitbucket-pr/manifest.js +21 -0
  513. package/dist/node_modules/@openhands/extensions/automations/catalog/linear-issue-to-bitbucket-pr/manifest.js.map +1 -0
  514. package/dist/node_modules/@openhands/extensions/automations/catalog/linear-issue-to-github-pr/manifest.cjs +9 -0
  515. package/dist/node_modules/@openhands/extensions/automations/catalog/linear-issue-to-github-pr/manifest.cjs.map +1 -0
  516. package/dist/node_modules/@openhands/extensions/automations/catalog/linear-issue-to-github-pr/manifest.js +21 -0
  517. package/dist/node_modules/@openhands/extensions/automations/catalog/linear-issue-to-github-pr/manifest.js.map +1 -0
  518. package/dist/node_modules/@openhands/extensions/automations/catalog/linear-issue-to-gitlab-mr/manifest.cjs +9 -0
  519. package/dist/node_modules/@openhands/extensions/automations/catalog/linear-issue-to-gitlab-mr/manifest.cjs.map +1 -0
  520. package/dist/node_modules/@openhands/extensions/automations/catalog/linear-issue-to-gitlab-mr/manifest.js +21 -0
  521. package/dist/node_modules/@openhands/extensions/automations/catalog/linear-issue-to-gitlab-mr/manifest.js.map +1 -0
  522. package/dist/node_modules/@openhands/extensions/automations/catalog/news-digest/manifest.cjs +1 -1
  523. package/dist/node_modules/@openhands/extensions/automations/catalog/news-digest/manifest.cjs.map +1 -1
  524. package/dist/node_modules/@openhands/extensions/automations/catalog/news-digest/manifest.js +5 -0
  525. package/dist/node_modules/@openhands/extensions/automations/catalog/news-digest/manifest.js.map +1 -1
  526. package/dist/node_modules/@openhands/extensions/automations/catalog/qa-changes/manifest.cjs +30 -0
  527. package/dist/node_modules/@openhands/extensions/automations/catalog/qa-changes/manifest.cjs.map +1 -0
  528. package/dist/node_modules/@openhands/extensions/automations/catalog/qa-changes/manifest.js +65 -0
  529. package/dist/node_modules/@openhands/extensions/automations/catalog/qa-changes/manifest.js.map +1 -0
  530. package/dist/node_modules/@openhands/extensions/automations/catalog-index.cjs +1 -1
  531. package/dist/node_modules/@openhands/extensions/automations/catalog-index.cjs.map +1 -1
  532. package/dist/node_modules/@openhands/extensions/automations/catalog-index.js +21 -9
  533. package/dist/node_modules/@openhands/extensions/automations/catalog-index.js.map +1 -1
  534. package/dist/node_modules/@openhands/extensions/integrations/catalog/datadog.cjs +1 -1
  535. package/dist/node_modules/@openhands/extensions/integrations/catalog/datadog.cjs.map +1 -1
  536. package/dist/node_modules/@openhands/extensions/integrations/catalog/datadog.js +2 -2
  537. package/dist/node_modules/@openhands/extensions/integrations/catalog/datadog.js.map +1 -1
  538. package/dist/node_modules/@openhands/extensions/integrations/catalog/gitlab.cjs +2 -0
  539. package/dist/node_modules/@openhands/extensions/integrations/catalog/gitlab.cjs.map +1 -0
  540. package/dist/node_modules/@openhands/extensions/integrations/catalog/gitlab.js +40 -0
  541. package/dist/node_modules/@openhands/extensions/integrations/catalog/gitlab.js.map +1 -0
  542. package/dist/node_modules/@openhands/extensions/integrations/catalog/notion.cjs +1 -1
  543. package/dist/node_modules/@openhands/extensions/integrations/catalog/notion.cjs.map +1 -1
  544. package/dist/node_modules/@openhands/extensions/integrations/catalog/notion.js +1 -1
  545. package/dist/node_modules/@openhands/extensions/integrations/catalog/notion.js.map +1 -1
  546. package/dist/node_modules/@openhands/extensions/integrations/catalog-index.cjs +1 -1
  547. package/dist/node_modules/@openhands/extensions/integrations/catalog-index.cjs.map +1 -1
  548. package/dist/node_modules/@openhands/extensions/integrations/catalog-index.js +74 -72
  549. package/dist/node_modules/@openhands/extensions/integrations/catalog-index.js.map +1 -1
  550. package/dist/node_modules/@openhands/extensions/skills/index.cjs +42 -3
  551. package/dist/node_modules/@openhands/extensions/skills/index.cjs.map +1 -1
  552. package/dist/node_modules/@openhands/extensions/skills/index.js +22 -3
  553. package/dist/node_modules/@openhands/extensions/skills/index.js.map +1 -1
  554. package/dist/node_modules/@openhands/typescript-client/dist/conversation/remote-conversation.cjs +1 -1
  555. package/dist/node_modules/@openhands/typescript-client/dist/conversation/remote-conversation.js +1 -1
  556. package/dist/node_modules/@openhands/typescript-client/dist/conversation/remote-state.cjs +1 -1
  557. package/dist/node_modules/@openhands/typescript-client/dist/conversation/remote-state.cjs.map +1 -1
  558. package/dist/node_modules/@openhands/typescript-client/dist/conversation/remote-state.js +8 -7
  559. package/dist/node_modules/@openhands/typescript-client/dist/conversation/remote-state.js.map +1 -1
  560. package/dist/node_modules/@openhands/typescript-client/dist/index.cjs +1 -1
  561. package/dist/node_modules/@openhands/typescript-client/dist/index.js +2 -2
  562. package/dist/node_modules/lucide-react/dist/esm/icons/blocks.cjs +2 -0
  563. package/dist/node_modules/lucide-react/dist/esm/icons/blocks.cjs.map +1 -0
  564. package/dist/node_modules/lucide-react/dist/esm/icons/blocks.js +16 -0
  565. package/dist/node_modules/lucide-react/dist/esm/icons/blocks.js.map +1 -0
  566. package/dist/node_modules/lucide-react/dist/esm/icons/panels-top-left.cjs +2 -0
  567. package/dist/node_modules/lucide-react/dist/esm/icons/panels-top-left.cjs.map +1 -0
  568. package/dist/node_modules/lucide-react/dist/esm/icons/panels-top-left.js +23 -0
  569. package/dist/node_modules/lucide-react/dist/esm/icons/panels-top-left.js.map +1 -0
  570. package/dist/package.cjs +1 -1
  571. package/dist/package.cjs.map +1 -1
  572. package/dist/package.js +3 -3
  573. package/dist/package.js.map +1 -1
  574. package/dist/routes/agent-settings.d.ts +10 -0
  575. package/dist/routes/app-settings.cjs +1 -1
  576. package/dist/routes/app-settings.cjs.map +1 -1
  577. package/dist/routes/app-settings.js +7 -7
  578. package/dist/routes/files-tab.cjs +1 -1
  579. package/dist/routes/files-tab.cjs.map +1 -1
  580. package/dist/routes/files-tab.js +14 -14
  581. package/dist/routes/files-tab.js.map +1 -1
  582. package/dist/routes/llm-settings.cjs +1 -1
  583. package/dist/routes/llm-settings.cjs.map +1 -1
  584. package/dist/routes/llm-settings.js +122 -114
  585. package/dist/routes/llm-settings.js.map +1 -1
  586. package/dist/services/canvas-ui.cjs +1 -1
  587. package/dist/services/canvas-ui.cjs.map +1 -1
  588. package/dist/services/canvas-ui.d.ts +5 -0
  589. package/dist/services/canvas-ui.js +24 -10
  590. package/dist/services/canvas-ui.js.map +1 -1
  591. package/dist/services/settings.cjs +1 -1
  592. package/dist/services/settings.cjs.map +1 -1
  593. package/dist/services/settings.js +3 -2
  594. package/dist/services/settings.js.map +1 -1
  595. package/dist/types/automation.cjs.map +1 -1
  596. package/dist/types/automation.d.ts +20 -0
  597. package/dist/types/automation.js.map +1 -1
  598. package/dist/types/canvas-extension.cjs +2 -0
  599. package/dist/types/canvas-extension.cjs.map +1 -0
  600. package/dist/types/canvas-extension.d.ts +77 -0
  601. package/dist/types/canvas-extension.js +6 -0
  602. package/dist/types/canvas-extension.js.map +1 -0
  603. package/dist/types/settings.d.ts +7 -0
  604. package/dist/ui/help-link.cjs +1 -1
  605. package/dist/ui/help-link.cjs.map +1 -1
  606. package/dist/ui/help-link.d.ts +6 -1
  607. package/dist/ui/help-link.js +32 -18
  608. package/dist/ui/help-link.js.map +1 -1
  609. package/dist/utils/acp-auth-display.d.ts +35 -0
  610. package/dist/utils/cache-utils.cjs +1 -1
  611. package/dist/utils/cache-utils.js +5 -5
  612. package/dist/utils/format-model-name.cjs +1 -1
  613. package/dist/utils/format-model-name.cjs.map +1 -1
  614. package/dist/utils/format-model-name.d.ts +7 -3
  615. package/dist/utils/format-model-name.js +6 -11
  616. package/dist/utils/format-model-name.js.map +1 -1
  617. package/dist/utils/format-relative-time.cjs +1 -1
  618. package/dist/utils/format-relative-time.cjs.map +1 -1
  619. package/dist/utils/format-relative-time.d.ts +7 -0
  620. package/dist/utils/format-relative-time.js +7 -2
  621. package/dist/utils/format-relative-time.js.map +1 -1
  622. package/dist/utils/path-utils.cjs +1 -1
  623. package/dist/utils/path-utils.cjs.map +1 -1
  624. package/dist/utils/path-utils.d.ts +15 -3
  625. package/dist/utils/path-utils.js +29 -3
  626. package/dist/utils/path-utils.js.map +1 -1
  627. package/dist/utils/skill-enablement.cjs +2 -0
  628. package/dist/utils/skill-enablement.cjs.map +1 -0
  629. package/dist/utils/skill-enablement.d.ts +51 -0
  630. package/dist/utils/skill-enablement.js +31 -0
  631. package/dist/utils/skill-enablement.js.map +1 -0
  632. package/dist/utils/vscode-origin.cjs +2 -0
  633. package/dist/utils/vscode-origin.cjs.map +1 -0
  634. package/dist/utils/vscode-origin.d.ts +55 -0
  635. package/dist/utils/vscode-origin.js +27 -0
  636. package/dist/utils/vscode-origin.js.map +1 -0
  637. package/dist/utils/workspace-mode.d.ts +4 -0
  638. package/package.json +3 -3
  639. package/scripts/check-sdk-version-sync.mjs +6 -6
  640. package/scripts/check-translation-completeness.cjs +1 -0
  641. package/scripts/dev-extra-backend.mjs +28 -9
  642. package/scripts/dev-safe.mjs +81 -8
  643. package/scripts/dev-static.mjs +35 -37
  644. package/scripts/dev-with-automation.mjs +82 -1
  645. package/scripts/ingress.mjs +27 -1
  646. package/scripts/static-server.mjs +74 -1
  647. package/tools/canvas_ui_tool.py +19 -0
  648. package/build/assets/active-backend-context-DKFSMbn3.js +0 -2
  649. package/build/assets/agent-server-conversation-service.api-DozDKIW_.js +0 -86
  650. package/build/assets/agent-settings-351vXWc9.js +0 -2
  651. package/build/assets/agent-settings-Ce7BDZ6I.js +0 -1
  652. package/build/assets/alert-banner-Kyy1YWZU.js +0 -1
  653. package/build/assets/api-key-entry-screen-INIxqAMs.js +0 -1
  654. package/build/assets/automation-detail-DXDhUwPq.js +0 -3
  655. package/build/assets/automation-setup-route-FRr05f_g.js +0 -1
  656. package/build/assets/automations-list-CERuQWE6.js +0 -1
  657. package/build/assets/backend-form-modal-DK78-jn1.js +0 -1
  658. package/build/assets/browser-11etCQ04.js +0 -7
  659. package/build/assets/check-Cf2YoT90.js +0 -1
  660. package/build/assets/chevron-down-C3OQ-ehx.js +0 -1
  661. package/build/assets/chevron-right-CN1uUiJ1.js +0 -1
  662. package/build/assets/circle-check-g5yeGMd-.js +0 -1
  663. package/build/assets/command-menu-BZQ8S3Ke.js +0 -1
  664. package/build/assets/conversation-CchMzpGl.js +0 -1
  665. package/build/assets/conversation-Dpzftz57.js +0 -93
  666. package/build/assets/conversation-panel-CchMzpGl.js +0 -1
  667. package/build/assets/conversation-state-store-a9d2fxRc.js +0 -1
  668. package/build/assets/empty-state-CvaVahVr.js +0 -1
  669. package/build/assets/extensions-hub-CFduxBnk.js +0 -1
  670. package/build/assets/extensions-navigation-BeC224yX.js +0 -1
  671. package/build/assets/files-tab-CL3J9b3p.js +0 -1
  672. package/build/assets/format-model-name-BNPKVsIq.js +0 -1
  673. package/build/assets/get-git-path-DlAJa_i6.js +0 -1
  674. package/build/assets/git-repo-dropdown-DGMEhz7d.js +0 -1
  675. package/build/assets/home-BCGlNJCA.js +0 -1
  676. package/build/assets/home-DB1pRqTC.js +0 -1
  677. package/build/assets/home-automation-run-tooltip-CooFQRIG.js +0 -1
  678. package/build/assets/home-store-Dl1dPE58.js +0 -1
  679. package/build/assets/index-home-DB1pRqTC.js +0 -1
  680. package/build/assets/info-uYSVzX86.js +0 -1
  681. package/build/assets/install-server-modal-BbJdhzet.js +0 -1
  682. package/build/assets/kebab-menu-item-content-BYokkRHi.js +0 -1
  683. package/build/assets/list-filter-OFkpRrmi.js +0 -1
  684. package/build/assets/llm-not-configured-banner-BqqYnJ1t.js +0 -27
  685. package/build/assets/llm-settings-CnDbbmVW.js +0 -1
  686. package/build/assets/llm-settings-S8Yjg__1.js +0 -1
  687. package/build/assets/llm-subscription-service-BpPfzW4x.js +0 -1
  688. package/build/assets/loader-circle-nvrwthvj.js +0 -1
  689. package/build/assets/manage-backends-modal-BCUPswyl.js +0 -1
  690. package/build/assets/manifest-58597dde.js +0 -1
  691. package/build/assets/markdown-renderer-Cb2sBWtV.js +0 -8
  692. package/build/assets/messages-BTN5thLe.js +0 -40
  693. package/build/assets/onboarding-Cx_kaiD1.js +0 -1
  694. package/build/assets/onboarding-modal-vMwn7ihv.js +0 -1
  695. package/build/assets/plugins-management-service-BjYeF0b0.js +0 -1
  696. package/build/assets/plus-CEKne-Yp.js +0 -1
  697. package/build/assets/profiles-service.api-QB-T_u-g.js +0 -1
  698. package/build/assets/providers-FfSZ5TXP.js +0 -1
  699. package/build/assets/proxy-tcFL2yTQ.js +0 -1
  700. package/build/assets/recommended-automations-launcher-Bw8uC4V6.js +0 -1
  701. package/build/assets/root-BLeQfBEq.css +0 -1
  702. package/build/assets/root-ZKKYPR1Z.js +0 -2
  703. package/build/assets/root-layout-luJoE9IG.js +0 -2
  704. package/build/assets/schema-field-Csw5fhtN.js +0 -1
  705. package/build/assets/secrets-service-BTor9uAU.js +0 -1
  706. package/build/assets/settings-DcNPxz0v.js +0 -1
  707. package/build/assets/settings-service.api-B2agVfjL.js +0 -1
  708. package/build/assets/shared-conversation-B55pCyBG.js +0 -1
  709. package/build/assets/sidebar-nav-link-CPzhyHQg.js +0 -1
  710. package/build/assets/sidebar-store-DtpU7zaI.js +0 -1
  711. package/build/assets/skill-filter-CWZ5kjcg.js +0 -2
  712. package/build/assets/skill-scope-CV1dkAbW.js +0 -1
  713. package/build/assets/skills-plugins-DGHULTq6.js +0 -1
  714. package/build/assets/skills-settings-CM1lFv3e.js +0 -1
  715. package/build/assets/usage-tab-BcfWV6db.js +0 -1
  716. package/build/assets/use-acp-credential-form-BsSW5NHG.js +0 -1
  717. package/build/assets/use-agent-profiles-DWBPC0Si.js +0 -1
  718. package/build/assets/use-config-Bft35DOK.js +0 -1
  719. package/build/assets/use-create-secret-BSxJMxHp.js +0 -1
  720. package/build/assets/use-is-authed-CFGkY3-Z.js +0 -1
  721. package/build/assets/use-llm-configured-BI6obxsc.js +0 -1
  722. package/build/assets/use-llm-profiles-djGAhTWL.js +0 -1
  723. package/build/assets/use-manifest-capabilities-3z0-1G59.js +0 -1
  724. package/build/assets/use-onboarding-completion-BXpyUSsJ.js +0 -1
  725. package/build/assets/use-plugins-marketplace-DI4_pK56.js +0 -1
  726. package/build/assets/use-save-agent-profile-DdBSTdpN.js +0 -1
  727. package/build/assets/use-tracking-D5t9otIF.js +0 -3
  728. package/build/assets/utils-DaCIEEQP.js +0 -1
  729. package/build/assets/vendor~index-home~home~conversation-panel~conversation~alert-banner-B3qC_oj8.js +0 -1
  730. package/build/assets/vendor~root-layout~index-home~home~conversation-panel~conversation~mcp~automations-list~aut~ibfbmr9f-B2OLKL7N.js +0 -1
  731. package/dist/components/features/tips/random-tip.d.ts +0 -2
  732. package/dist/utils/tips.d.ts +0 -7
  733. package/dist/utils/workspace-relative-path.cjs +0 -2
  734. package/dist/utils/workspace-relative-path.cjs.map +0 -1
  735. package/dist/utils/workspace-relative-path.d.ts +0 -10
  736. package/dist/utils/workspace-relative-path.js +0 -22
  737. package/dist/utils/workspace-relative-path.js.map +0 -1
  738. /package/build/assets/{vendor~entry.client~root~root-layout~index-home~home~conversation-panel~conversation~launch~ew3u153i-rfV8mQ9m.js → vendor~entry.client~root~root-layout~index-home~home~conversation-panel~conversation~launch~ew9ih1s8-rfV8mQ9m.js} +0 -0
  739. /package/build/assets/{vendor~entry.client~root~root-layout~index-home~home~conversation-panel~conversation~launch~ox0jfsa0-cHFhpUBq.js → vendor~entry.client~root~root-layout~index-home~home~conversation-panel~conversation~launch~i58xkef6-cHFhpUBq.js} +0 -0
@@ -1,4 +1,4 @@
1
- const e=require("../_virtual/_rolldown/runtime.cjs"),t=require("../stores/browser-store.cjs"),n=require("../stores/command-store.cjs"),ee=require("../utils/conversation-local-storage.cjs"),r=require("../stores/conversation-store.cjs"),te=require("../stores/conversation-state-store.cjs"),ne=require("../node_modules/@openhands/typescript-client/dist/client/conversation-client.cjs");require("../node_modules/@openhands/typescript-client/dist/clients.cjs");const re=require("../utils/websocket-url.cjs"),ie=require("../api/agent-server-client-options.cjs"),ae=require("../node_modules/@tanstack/react-query/build/modern/QueryClientProvider.cjs"),i=require("../types/agent-server/type-guards.cjs"),a=require("../stores/use-event-store.cjs"),o=require("../hooks/use-websocket.cjs"),oe=require("../constants/server-connection-error.cjs"),s=require("../stores/error-message-store.cjs"),se=require("../stores/optimistic-user-message-store.cjs"),c=require("../stores/goal-store.cjs"),ce=require("../utils/streaming-delta-batcher.cjs"),le=require("../services/canvas-ui.cjs"),l=require("../api/conversation-metadata-store.cjs"),ue=require("../services/child-conversation-launch.cjs"),de=require("../utils/cache-utils.cjs"),fe=require("../api/event-service/event-service.api.cjs"),u=require("../utils/error-handler.cjs"),pe=require("../hooks/mutation/use-read-conversation-file.cjs"),me=require("../stores/metrics-store.cjs"),he=require("../hooks/query/use-conversation-history.cjs"),ge=require("../hooks/chat/record-model-switch-message.cjs"),d=require("../hooks/mutation/conversation-mutation-utils.cjs");let f=require("react");f=e.__toESM(f,1);let _e=require("react/jsx-runtime");var p=(0,f.createContext)(void 0);function m(e){return e.llm_message.content.filter(e=>e.type===`text`).map(e=>e.text).join(``)}function h({children:e,conversationId:h,conversationUrl:g,sessionApiKey:_,subConversations:v,subConversationIds:ve}){let[y,b]=(0,f.useState)(`CONNECTING`),[x,S]=(0,f.useState)(`CONNECTING`),C=f.default.useRef(!1),w=f.default.useRef(!1),T=ae.useQueryClient(),E=a.useEventStore(e=>e.addEvent),ye=a.useEventStore(e=>e.addEvents),be=a.useEventStore(e=>e.clearEventsForConversation),{setErrorMessage:D,removeErrorMessage:O,clearConnectionError:k}=s.useErrorMessageStore(),A=se.useOptimisticUserMessageStore(e=>e.consumeMatchingPendingMessage),{setExecutionStatus:j}=te.useConversationStateStore(),{appendInput:M,appendOutput:N}=n.useCommandStore(),P=t.useBrowserStore(e=>e.reset),F=(0,f.useRef)(null);F.current===null&&(F.current=ce.createStreamingDeltaBatcher(e=>{a.useEventStore.getState().addEvent(e),s.useErrorMessageStore.getState().clearConnectionError()}));let I=(0,f.useRef)(null);I.current===null&&(I.current=ce.createStreamingDeltaBatcher(e=>{a.useEventStore.getState().addEvent({...e,isFromPlanningAgent:!0}),s.useErrorMessageStore.getState().clearConnectionError()}));let[L,R]=(0,f.useState)(!0),[z,xe]=(0,f.useState)(null),{setPlanContent:B}=r.useConversationStore(),{mutate:V}=pe.useReadConversationFile(),H=(0,f.useRef)(0),U=(0,f.useRef)(null),Se=e=>e?.toUpperCase().endsWith(`PLAN.MD`)??!1,W=(0,f.useCallback)(()=>{k()},[k]),G=(0,f.useCallback)(e=>{let t=e.value.usage_to_metrics;if(!t)return;let n=Object.values(t).reduce((e,t)=>{e.cost+=t.accumulated_cost,e.maxBudgetPerTask===null&&t.max_budget_per_task!==null&&(e.maxBudgetPerTask=t.max_budget_per_task);let n=t.accumulated_token_usage;return n&&(e.usage={prompt_tokens:(e.usage?.prompt_tokens??0)+n.prompt_tokens,completion_tokens:(e.usage?.completion_tokens??0)+n.completion_tokens,cache_read_tokens:(e.usage?.cache_read_tokens??0)+n.cache_read_tokens,cache_write_tokens:(e.usage?.cache_write_tokens??0)+n.cache_write_tokens,context_window:Math.max(e.usage?.context_window??0,n.context_window),per_turn_token:Math.max(e.usage?.per_turn_token??0,n.per_turn_token)}),e},{cost:0,maxBudgetPerTask:null,usage:null});me.default.getState().setMetrics({cost:n.cost,max_budget_per_task:n.maxBudgetPerTask,usage:n.usage})},[]),{data:K,isPending:q}=he.useConversationHistory(h),Ce=!!h&&q;(0,f.useLayoutEffect)(()=>{let e=h??null;a.useEventStore.getState().loadedConversationId!==e&&(be(e),P(),me.default.getState().resetMetrics())},[h,be,P]),(0,f.useLayoutEffect)(()=>{if(!(!K||K.events.length===0)&&(ye(K.events),h)){ge.seedModelSwitchesFromHistory(h,a.useEventStore.getState().uiEvents);for(let e of K.events)i.isUserMessageEvent(e)&&A(h,m(e))}},[K,ye,h,A]);let J=(0,f.useMemo)(()=>{let e=K?.events??[],t=e[e.length-1];return!t||!(`timestamp`in t)||!t.timestamp?null:t.timestamp},[K]),Y=(0,f.useMemo)(()=>!h||!g||q?null:re.buildWebSocketUrl(h,g),[h,g,q]),X=(0,f.useMemo)(()=>{if(!v?.length)return null;let e=v[0];return!e?.id||!e.conversation_url?null:re.buildWebSocketUrl(e.id,e.conversation_url)},[v]),we=(0,f.useMemo)(()=>X?y===`CONNECTING`||x===`CONNECTING`?`CONNECTING`:y===`OPEN`&&x===`OPEN`?`OPEN`:y===`CLOSED`&&x===`CLOSED`?`CLOSED`:y===`CLOSING`||x===`CLOSING`?`CLOSING`:`CLOSED`:y,[y,x,X]);(0,f.useEffect)(()=>{z!==null&&H.current>=z&&L&&R(!1)},[z,L,H]),(0,f.useEffect)(()=>{if(!L&&U.current){let{path:e,conversationId:t}=U.current;V({conversationId:t,filePath:e},{onSuccess:e=>{B(e)},onError:e=>{console.warn(`Failed to read conversation file:`,e)}}),U.current=null}},[L,V,B]),(0,f.useEffect)(()=>{C.current=!1,R(!!ve?.length),xe(null),H.current=0,U.current=null},[ve]),(0,f.useEffect)(()=>{C.current=!1,w.current=!1,U.current=null},[h]),(0,f.useEffect)(()=>{let e=F.current,t=I.current;return()=>{e?.reset(),t?.reset()}},[h]);let Te=(0,f.useMemo)(()=>Ce||L,[Ce,L]),Ee=(0,f.useCallback)(e=>{try{let n=JSON.parse(e.data);if(i.isAgentServerEvent(n)){if(i.isStreamingDeltaEvent(n)){F.current?.enqueue(n);return}F.current?.flush();let e=a.useEventStore.getState().eventIds.has(n.id??``),r=i.isSwitchLLMObservationEvent(n)?n:null;if(E(n),e)return;if(i.isDisplayableErrorEvent(n)){let e=n,t=`classification`in e?e.classification:null;u.trackError({source:`conversation`,metadata:{eventId:e.id,errorCode:e.code},classification:t}),D(e.detail,`conversation`,e.code,t)}else W();if(i.isAgentErrorEvent(n)&&u.trackError({source:`agent`,metadata:{eventId:n.id,toolName:n.tool_name,toolCallId:n.tool_call_id},classification:n.classification}),i.isUserMessageEvent(n)&&h&&(A(h,m(n)),ee.setConversationState(h,{draftMessage:null})),i.isActionEvent(n)&&de.handleActionEventCacheInvalidation(n,h||`test-conversation-id`,T),i.isConversationStateUpdateEvent(n)&&(i.isFullStateConversationStateUpdateEvent(n)&&j(n.value.execution_status),i.isAgentStatusConversationStateUpdateEvent(n)&&j(n.value),i.isStatsConversationStateUpdateEvent(n)&&G(n),i.isGoalConversationStateUpdateEvent(n)&&h&&c.useGoalStore.getState().setStatus(h,n.value)),i.isExecuteBashActionEvent(n)&&M(n.action.command),i.isExecuteBashObservationEvent(n)&&N(n.observation.content.filter(e=>e.type===`text`).map(e=>e.text).join(`
2
- `)),i.isBrowserObservationEvent(n)){let{screenshot_data:e}=n.observation;if(e){let n=e.startsWith(`data:`)?e:`data:image/png;base64,${e}`;t.useBrowserStore.getState().setScreenshotSrc(n)}}if(i.isBrowserNavigateActionEvent(n)&&t.useBrowserStore.getState().setUrl(n.action.url),h&&r&&!r.observation.is_error){ge.recordModelSwitchMessage(h,r.observation.profile_name);let e=l.getStoredConversationMetadata(h);l.setStoredConversationMetadata(h,{selected_repository:e?.selected_repository??null,selected_branch:e?.selected_branch??null,git_provider:e?.git_provider??null,selected_workspace:e?.selected_workspace??null,active_profile:r.observation.profile_name,plugins:e?.plugins??null}),r.observation.active_model&&d.updateConversationLlmModelInCache(T,h,r.observation.active_model),d.invalidateConversationQueries(T,h)}i.isCanvasUIActionEvent(n)&&le.handleCanvasUIAction(n.action,h??null),h&&i.isLaunchChildConversationActionEvent(n)&&ue.handleLaunchChildConversationAction(n.action,h,n.tool_call_id)}}catch(e){console.warn(`Failed to parse WebSocket message as JSON:`,e)}},[E,D,A,T,h,j,M,N,G,W]),De=(0,f.useCallback)(e=>{try{let t=JSON.parse(e.data);if(L&&(H.current+=1,z!==null&&H.current>=z&&R(!1)),i.isAgentServerEvent(t)){if(i.isStreamingDeltaEvent(t)){I.current?.enqueue(t);return}I.current?.flush();let e=a.useEventStore.getState().eventIds.has(t.id??``);if(E({...t,isFromPlanningAgent:!0}),e)return;if(i.isDisplayableErrorEvent(t)){let e=t,n=`classification`in e?e.classification:null;u.trackError({source:`planning_conversation`,metadata:{eventId:e.id,errorCode:e.code},classification:n}),D(e.detail,`conversation`,e.code,n)}else W();if(i.isAgentErrorEvent(t)&&u.trackError({source:`planning_agent`,metadata:{eventId:t.id,toolName:t.tool_name,toolCallId:t.tool_call_id},classification:t.classification}),i.isUserMessageEvent(t)&&h&&(A(h,m(t)),ee.setConversationState(h,{draftMessage:null})),i.isActionEvent(t)&&de.handleActionEventCacheInvalidation(t,v?.[0]?.id||`test-conversation-id`,T),i.isConversationStateUpdateEvent(t)&&(i.isFullStateConversationStateUpdateEvent(t)&&j(t.value.execution_status),i.isAgentStatusConversationStateUpdateEvent(t)&&j(t.value),i.isStatsConversationStateUpdateEvent(t)&&G(t),i.isGoalConversationStateUpdateEvent(t)&&h&&c.useGoalStore.getState().setStatus(h,t.value)),i.isExecuteBashActionEvent(t)&&M(t.action.command),i.isExecuteBashObservationEvent(t)&&N(t.observation.content.filter(e=>e.type===`text`).map(e=>e.text).join(`
3
- `)),i.isPlanningFileEditorObservationEvent(t)){let{path:e}=t.observation;if(Se(e)){let t=v?.[0]?.id;t&&e&&(L?U.current={path:e,conversationId:t}:V({conversationId:t,filePath:e},{onSuccess:e=>{B(e)},onError:e=>{console.warn(`Failed to read conversation file:`,e)}}))}}}}catch(e){console.warn(`Failed to parse WebSocket message as JSON:`,e)}},[E,L,z,D,A,T,v,h,j,M,N,V,B,G,W]),Oe=(0,f.useMemo)(()=>({queryParams:J?{resend_mode:`since`,after_timestamp:J}:{resend_mode:`all`},sessionApiKey:_,reconnect:{enabled:!0},onOpen:()=>{b(`OPEN`),C.current=!0,k()},onClose:()=>{b(`CLOSED`)},onError:()=>{b(`CLOSED`),C.current&&D(oe.SERVER_CONNECTION_ERROR_MESSAGE,`connection`)},onMessage:Ee}),[Ee,D,k,_,J]),ke=(0,f.useMemo)(()=>{let e={resend_all:!0},t=v?.[0];return{queryParams:e,sessionApiKey:t?.session_api_key??_,reconnect:{enabled:!0},onOpen:async()=>{if(S(`OPEN`),w.current=!0,k(),t?.id&&t.conversation_url)try{let e=await fe.default.getEventCount(t.id,t.conversation_url,t.session_api_key);xe(e),e===0&&R(!1)}catch{R(!1)}},onClose:()=>{S(`CLOSED`)},onError:()=>{S(`CLOSED`),w.current&&D(oe.SERVER_CONNECTION_ERROR_MESSAGE,`connection`)},onMessage:De}},[De,D,k,_,v]),{socket:Z,reconnect:Ae}=o.useWebSocket(Y||``,Oe),{socket:Q,reconnect:je}=o.useWebSocket(X||``,ke),Me=(0,f.useCallback)(()=>{if(O(),r.useConversationStore.getState().conversationMode===`plan`&&X){je();return}Ae()},[X,Ae,je,O]),$=(0,f.useCallback)(async e=>{let t=r.useConversationStore.getState().conversationMode===`plan`?Q:Z;if(t?.readyState!==WebSocket.OPEN){if(!h){let e=Error(`No conversation ID available`);throw D(e.message),e}try{return await new ne.ConversationClient(ie.getAgentServerClientOptions()).sendEvent(h,{role:`user`,content:e.content},{run:!0}),{queued:!0}}catch(e){throw D(e instanceof Error?e.message:`Failed to queue message for delivery`),e}}try{return t.send(JSON.stringify({...e,run:!0})),{queued:!1}}catch(e){throw D(e instanceof Error?e.message:`Failed to send message`),e}},[Z,Q,D,h]);(0,f.useEffect)(()=>{Z&&Y&&(()=>{switch(Z.readyState){case WebSocket.CONNECTING:b(`CONNECTING`);break;case WebSocket.OPEN:b(`OPEN`);break;case WebSocket.CLOSING:b(`CLOSING`);break;case WebSocket.CLOSED:b(`CLOSED`);break;default:b(`CLOSED`);break}})()},[Z,Y]),(0,f.useEffect)(()=>{Q&&X&&(()=>{switch(Q.readyState){case WebSocket.CONNECTING:S(`CONNECTING`);break;case WebSocket.OPEN:S(`OPEN`);break;case WebSocket.CLOSING:S(`CLOSING`);break;case WebSocket.CLOSED:S(`CLOSED`);break;default:S(`CLOSED`);break}})()},[Q,X]);let Ne=(0,f.useMemo)(()=>({connectionState:we,sendMessage:$,isLoadingHistory:Te,reconnect:Me}),[we,$,Te,Me]);return(0,_e.jsx)(p.Provider,{value:Ne,children:e})}var g=()=>(0,f.useContext)(p)||null;exports.ConversationWebSocketProvider=h,exports.useConversationWebSocket=g;
1
+ const e=require("../_virtual/_rolldown/runtime.cjs"),t=require("../stores/browser-store.cjs"),n=require("../stores/command-store.cjs"),ee=require("../utils/conversation-local-storage.cjs"),r=require("../stores/conversation-store.cjs"),te=require("../stores/conversation-state-store.cjs"),ne=require("../node_modules/@openhands/typescript-client/dist/client/conversation-client.cjs");require("../node_modules/@openhands/typescript-client/dist/clients.cjs");const i=require("../utils/websocket-url.cjs"),re=require("../api/agent-server-client-options.cjs"),ie=require("../node_modules/@tanstack/react-query/build/modern/QueryClientProvider.cjs"),a=require("../types/agent-server/type-guards.cjs"),o=require("../stores/use-event-store.cjs"),s=require("../hooks/use-websocket.cjs"),ae=require("../constants/server-connection-error.cjs"),c=require("../stores/error-message-store.cjs"),oe=require("../stores/optimistic-user-message-store.cjs"),l=require("../stores/goal-store.cjs"),se=require("../utils/streaming-delta-batcher.cjs"),ce=require("../api/conversation-metadata-store.cjs"),le=require("../services/canvas-ui.cjs"),ue=require("../services/child-conversation-launch.cjs"),de=require("../utils/cache-utils.cjs"),fe=require("../api/event-service/event-service.api.cjs"),u=require("../utils/error-handler.cjs"),pe=require("../hooks/mutation/use-read-conversation-file.cjs"),me=require("../stores/metrics-store.cjs"),he=require("../hooks/query/use-conversation-history.cjs"),ge=require("../hooks/chat/record-model-switch-message.cjs"),d=require("../hooks/mutation/conversation-mutation-utils.cjs");let f=require("react");f=e.__toESM(f,1);let _e=require("react/jsx-runtime");var p=(0,f.createContext)(void 0);function m(e){return e.llm_message.content.filter(e=>e.type===`text`).map(e=>e.text).join(``)}function h({children:e,conversationId:h,conversationUrl:g,sessionApiKey:_,subConversations:v,subConversationIds:ve}){let[y,b]=(0,f.useState)(`CONNECTING`),[x,S]=(0,f.useState)(`CONNECTING`),C=f.default.useRef(!1),w=f.default.useRef(!1),T=ie.useQueryClient(),E=o.useEventStore(e=>e.addEvent),ye=o.useEventStore(e=>e.addEvents),be=o.useEventStore(e=>e.clearEventsForConversation),{setErrorMessage:D,removeErrorMessage:O,clearConnectionError:k}=c.useErrorMessageStore(),A=oe.useOptimisticUserMessageStore(e=>e.consumeMatchingPendingMessage),{setExecutionStatus:j}=te.useConversationStateStore(),{appendInput:M,appendOutput:N}=n.useCommandStore(),P=t.useBrowserStore(e=>e.reset),F=(0,f.useRef)(null);F.current===null&&(F.current=se.createStreamingDeltaBatcher(e=>{o.useEventStore.getState().addEvent(e),c.useErrorMessageStore.getState().clearConnectionError()}));let I=(0,f.useRef)(null);I.current===null&&(I.current=se.createStreamingDeltaBatcher(e=>{o.useEventStore.getState().addEvent({...e,isFromPlanningAgent:!0}),c.useErrorMessageStore.getState().clearConnectionError()}));let[L,R]=(0,f.useState)(!0),[z,xe]=(0,f.useState)(null),{setPlanContent:B}=r.useConversationStore(),{mutate:V}=pe.useReadConversationFile(),H=(0,f.useRef)(0),U=(0,f.useRef)(null),Se=e=>e?.toUpperCase().endsWith(`PLAN.MD`)??!1,W=(0,f.useCallback)(()=>{k()},[k]),G=(0,f.useCallback)(e=>{let t=e.value.usage_to_metrics;if(!t)return;let n=Object.values(t).reduce((e,t)=>{e.cost+=t.accumulated_cost,e.maxBudgetPerTask===null&&t.max_budget_per_task!==null&&(e.maxBudgetPerTask=t.max_budget_per_task);let n=t.accumulated_token_usage;return n&&(e.usage={prompt_tokens:(e.usage?.prompt_tokens??0)+n.prompt_tokens,completion_tokens:(e.usage?.completion_tokens??0)+n.completion_tokens,cache_read_tokens:(e.usage?.cache_read_tokens??0)+n.cache_read_tokens,cache_write_tokens:(e.usage?.cache_write_tokens??0)+n.cache_write_tokens,context_window:Math.max(e.usage?.context_window??0,n.context_window),per_turn_token:Math.max(e.usage?.per_turn_token??0,n.per_turn_token)}),e},{cost:0,maxBudgetPerTask:null,usage:null});me.default.getState().setMetrics({cost:n.cost,max_budget_per_task:n.maxBudgetPerTask,usage:n.usage})},[]),{data:K,isPending:q}=he.useConversationHistory(h),Ce=!!h&&q;(0,f.useLayoutEffect)(()=>{let e=h??null;o.useEventStore.getState().loadedConversationId!==e&&(be(e),P(),me.default.getState().resetMetrics())},[h,be,P]),(0,f.useLayoutEffect)(()=>{if(!(!K||K.events.length===0)&&(ye(K.events),h)){ge.seedModelSwitchesFromHistory(h,o.useEventStore.getState().uiEvents);for(let e of K.events)a.isUserMessageEvent(e)&&A(h,m(e))}},[K,ye,h,A]);let J=(0,f.useMemo)(()=>{let e=K?.events??[],t=e[e.length-1];return!t||!(`timestamp`in t)||!t.timestamp?null:t.timestamp},[K]),Y=(0,f.useMemo)(()=>!h||!g||q?null:i.buildWebSocketUrl(h,g),[h,g,q]),X=(0,f.useMemo)(()=>{if(!v?.length)return null;let e=v[0];return!e?.id||!e.conversation_url?null:i.buildWebSocketUrl(e.id,e.conversation_url)},[v]),we=(0,f.useMemo)(()=>X?y===`CONNECTING`||x===`CONNECTING`?`CONNECTING`:y===`OPEN`&&x===`OPEN`?`OPEN`:y===`CLOSED`&&x===`CLOSED`?`CLOSED`:y===`CLOSING`||x===`CLOSING`?`CLOSING`:`CLOSED`:y,[y,x,X]);(0,f.useEffect)(()=>{z!==null&&H.current>=z&&L&&R(!1)},[z,L,H]),(0,f.useEffect)(()=>{if(!L&&U.current){let{path:e,conversationId:t}=U.current;V({conversationId:t,filePath:e},{onSuccess:e=>{B(e)},onError:e=>{console.warn(`Failed to read conversation file:`,e)}}),U.current=null}},[L,V,B]),(0,f.useEffect)(()=>{C.current=!1,R(!!ve?.length),xe(null),H.current=0,U.current=null},[ve]),(0,f.useEffect)(()=>{C.current=!1,w.current=!1,U.current=null},[h]),(0,f.useEffect)(()=>{let e=F.current,t=I.current;return()=>{e?.reset(),t?.reset()}},[h]);let Te=(0,f.useMemo)(()=>Ce||L,[Ce,L]),Ee=(0,f.useCallback)(e=>{try{let n=JSON.parse(e.data);if(a.isAgentServerEvent(n)){if(a.isStreamingDeltaEvent(n)){F.current?.enqueue(n);return}F.current?.flush();let e=o.useEventStore.getState().eventIds.has(n.id??``),r=a.isSwitchLLMObservationEvent(n)?n:null;if(E(n),e)return;if(a.isDisplayableErrorEvent(n)){let e=n,t=`classification`in e?e.classification:null;u.trackError({source:`conversation`,metadata:{eventId:e.id,errorCode:e.code},classification:t}),D(e.detail,`conversation`,e.code,t)}else W();if(a.isAgentErrorEvent(n)&&u.trackError({source:`agent`,metadata:{eventId:n.id,toolName:n.tool_name,toolCallId:n.tool_call_id},classification:n.classification}),a.isUserMessageEvent(n)&&h&&(A(h,m(n)),ee.setConversationState(h,{draftMessage:null})),a.isActionEvent(n)&&de.handleActionEventCacheInvalidation(n,h||`test-conversation-id`,T),a.isConversationStateUpdateEvent(n)&&(a.isFullStateConversationStateUpdateEvent(n)&&j(n.value.execution_status),a.isAgentStatusConversationStateUpdateEvent(n)&&j(n.value),a.isStatsConversationStateUpdateEvent(n)&&G(n),a.isGoalConversationStateUpdateEvent(n)&&h&&l.useGoalStore.getState().setStatus(h,n.value)),a.isExecuteBashActionEvent(n)&&M(n.action.command),a.isExecuteBashObservationEvent(n)&&N(n.observation.content.filter(e=>e.type===`text`).map(e=>e.text).join(`
2
+ `)),a.isBrowserObservationEvent(n)){let{screenshot_data:e}=n.observation;if(e){let n=e.startsWith(`data:`)?e:`data:image/png;base64,${e}`;t.useBrowserStore.getState().setScreenshotSrc(n)}}if(a.isBrowserNavigateActionEvent(n)&&t.useBrowserStore.getState().setUrl(n.action.url),h&&r&&!r.observation.is_error){ge.recordModelSwitchMessage(h,r.observation.profile_name);let e=ce.getStoredConversationMetadata(h);ce.setStoredConversationMetadata(h,{selected_repository:e?.selected_repository??null,selected_branch:e?.selected_branch??null,git_provider:e?.git_provider??null,selected_workspace:e?.selected_workspace??null,active_profile:r.observation.profile_name,plugins:e?.plugins??null}),r.observation.active_model&&d.updateConversationLlmModelInCache(T,h,r.observation.active_model),d.invalidateConversationQueries(T,h)}a.isCanvasUIActionEvent(n)&&le.handleCanvasUIAction(n.action,h??null),h&&a.isLaunchChildConversationActionEvent(n)&&ue.handleLaunchChildConversationAction(n.action,h,n.tool_call_id)}}catch(e){console.warn(`Failed to parse WebSocket message as JSON:`,e)}},[E,D,A,T,h,j,M,N,G,W]),De=(0,f.useCallback)(e=>{try{let t=JSON.parse(e.data);if(L&&(H.current+=1,z!==null&&H.current>=z&&R(!1)),a.isAgentServerEvent(t)){if(a.isStreamingDeltaEvent(t)){I.current?.enqueue(t);return}I.current?.flush();let e=o.useEventStore.getState().eventIds.has(t.id??``);if(E({...t,isFromPlanningAgent:!0}),e)return;if(a.isDisplayableErrorEvent(t)){let e=t,n=`classification`in e?e.classification:null;u.trackError({source:`planning_conversation`,metadata:{eventId:e.id,errorCode:e.code},classification:n}),D(e.detail,`conversation`,e.code,n)}else W();if(a.isAgentErrorEvent(t)&&u.trackError({source:`planning_agent`,metadata:{eventId:t.id,toolName:t.tool_name,toolCallId:t.tool_call_id},classification:t.classification}),a.isUserMessageEvent(t)&&h&&(A(h,m(t)),ee.setConversationState(h,{draftMessage:null})),a.isActionEvent(t)&&de.handleActionEventCacheInvalidation(t,v?.[0]?.id||`test-conversation-id`,T),a.isConversationStateUpdateEvent(t)&&(a.isFullStateConversationStateUpdateEvent(t)&&j(t.value.execution_status),a.isAgentStatusConversationStateUpdateEvent(t)&&j(t.value),a.isStatsConversationStateUpdateEvent(t)&&G(t),a.isGoalConversationStateUpdateEvent(t)&&h&&l.useGoalStore.getState().setStatus(h,t.value)),a.isExecuteBashActionEvent(t)&&M(t.action.command),a.isExecuteBashObservationEvent(t)&&N(t.observation.content.filter(e=>e.type===`text`).map(e=>e.text).join(`
3
+ `)),a.isPlanningFileEditorObservationEvent(t)){let{path:e}=t.observation;if(Se(e)){let t=v?.[0]?.id;t&&e&&(L?U.current={path:e,conversationId:t}:V({conversationId:t,filePath:e},{onSuccess:e=>{B(e)},onError:e=>{console.warn(`Failed to read conversation file:`,e)}}))}}}}catch(e){console.warn(`Failed to parse WebSocket message as JSON:`,e)}},[E,L,z,D,A,T,v,h,j,M,N,V,B,G,W]),Oe=(0,f.useMemo)(()=>({queryParams:J?{resend_mode:`since`,after_timestamp:J}:{resend_mode:`all`},sessionApiKey:_,reconnect:{enabled:!0},onOpen:()=>{b(`OPEN`),C.current=!0,k()},onClose:()=>{b(`CLOSED`)},onError:()=>{b(`CLOSED`),C.current&&D(ae.SERVER_CONNECTION_ERROR_MESSAGE,`connection`)},onMessage:Ee}),[Ee,D,k,_,J]),ke=(0,f.useMemo)(()=>{let e={resend_all:!0},t=v?.[0];return{queryParams:e,sessionApiKey:t?.session_api_key??_,reconnect:{enabled:!0},onOpen:async()=>{if(S(`OPEN`),w.current=!0,k(),t?.id&&t.conversation_url)try{let e=await fe.default.getEventCount(t.id,t.conversation_url,t.session_api_key);xe(e),e===0&&R(!1)}catch{R(!1)}},onClose:()=>{S(`CLOSED`)},onError:()=>{S(`CLOSED`),w.current&&D(ae.SERVER_CONNECTION_ERROR_MESSAGE,`connection`)},onMessage:De}},[De,D,k,_,v]),{socket:Z,reconnect:Ae}=s.useWebSocket(Y||``,Oe),{socket:Q,reconnect:je}=s.useWebSocket(X||``,ke),Me=(0,f.useCallback)(()=>{if(O(),r.useConversationStore.getState().conversationMode===`plan`&&X){je();return}Ae()},[X,Ae,je,O]),$=(0,f.useCallback)(async e=>{let t=r.useConversationStore.getState().conversationMode===`plan`?Q:Z;if(t?.readyState!==WebSocket.OPEN){if(!h){let e=Error(`No conversation ID available`);throw D(e.message),e}try{return await new ne.ConversationClient(re.getAgentServerClientOptions()).sendEvent(h,{role:`user`,content:e.content},{run:!0}),{queued:!0}}catch(e){throw D(e instanceof Error?e.message:`Failed to queue message for delivery`),e}}try{return t.send(JSON.stringify({...e,run:!0})),{queued:!1}}catch(e){throw D(e instanceof Error?e.message:`Failed to send message`),e}},[Z,Q,D,h]);(0,f.useEffect)(()=>{Z&&Y&&(()=>{switch(Z.readyState){case WebSocket.CONNECTING:b(`CONNECTING`);break;case WebSocket.OPEN:b(`OPEN`);break;case WebSocket.CLOSING:b(`CLOSING`);break;case WebSocket.CLOSED:b(`CLOSED`);break;default:b(`CLOSED`);break}})()},[Z,Y]),(0,f.useEffect)(()=>{Q&&X&&(()=>{switch(Q.readyState){case WebSocket.CONNECTING:S(`CONNECTING`);break;case WebSocket.OPEN:S(`OPEN`);break;case WebSocket.CLOSING:S(`CLOSING`);break;case WebSocket.CLOSED:S(`CLOSED`);break;default:S(`CLOSED`);break}})()},[Q,X]);let Ne=(0,f.useMemo)(()=>({connectionState:we,sendMessage:$,isLoadingHistory:Te,reconnect:Me}),[we,$,Te,Me]);return(0,_e.jsx)(p.Provider,{value:Ne,children:e})}var g=()=>(0,f.useContext)(p)||null;exports.ConversationWebSocketProvider=h,exports.useConversationWebSocket=g;
4
4
  //# sourceMappingURL=conversation-websocket-context.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"conversation-websocket-context.cjs","names":[],"sources":["../../src/contexts/conversation-websocket-context.tsx"],"sourcesContent":["import React, {\n createContext,\n useContext,\n useEffect,\n useLayoutEffect,\n useState,\n useCallback,\n useMemo,\n useRef,\n} from \"react\";\nimport { ConversationClient } from \"@openhands/typescript-client/clients\";\n\nimport { useQueryClient } from \"@tanstack/react-query\";\nimport { useWebSocket, WebSocketHookOptions } from \"#/hooks/use-websocket\";\nimport { SERVER_CONNECTION_ERROR_MESSAGE } from \"#/constants/server-connection-error\";\nimport { useEventStore } from \"#/stores/use-event-store\";\nimport { useErrorMessageStore } from \"#/stores/error-message-store\";\nimport { useOptimisticUserMessageStore } from \"#/stores/optimistic-user-message-store\";\nimport { useConversationStateStore } from \"#/stores/conversation-state-store\";\nimport { useCommandStore } from \"#/stores/command-store\";\nimport { useBrowserStore } from \"#/stores/browser-store\";\nimport { useGoalStore } from \"#/stores/goal-store\";\nimport {\n isAgentServerEvent,\n isAgentErrorEvent,\n isUserMessageEvent,\n isActionEvent,\n isConversationStateUpdateEvent,\n isFullStateConversationStateUpdateEvent,\n isAgentStatusConversationStateUpdateEvent,\n isStatsConversationStateUpdateEvent,\n isGoalConversationStateUpdateEvent,\n isExecuteBashActionEvent,\n isExecuteBashObservationEvent,\n isDisplayableErrorEvent,\n isPlanningFileEditorObservationEvent,\n isBrowserObservationEvent,\n isBrowserNavigateActionEvent,\n isSwitchLLMObservationEvent,\n isCanvasUIActionEvent,\n isStreamingDeltaEvent,\n isLaunchChildConversationActionEvent,\n} from \"#/types/agent-server/type-guards\";\nimport {\n createStreamingDeltaBatcher,\n StreamingDeltaBatcher,\n} from \"#/utils/streaming-delta-batcher\";\nimport { handleCanvasUIAction } from \"#/services/canvas-ui\";\nimport { handleLaunchChildConversationAction } from \"#/services/child-conversation-launch\";\nimport { ConversationStateUpdateEventStats } from \"#/types/agent-server/core/events/conversation-state-event\";\nimport type {\n ConversationErrorEvent,\n ServerErrorEvent,\n} from \"#/types/agent-server/core/events/conversation-state-event\";\nimport { handleActionEventCacheInvalidation } from \"#/utils/cache-utils\";\nimport { buildWebSocketUrl } from \"#/utils/websocket-url\";\nimport type {\n AppConversation,\n SendMessageRequest,\n} from \"#/api/conversation-service/agent-server-conversation-service.types\";\nimport EventService from \"#/api/event-service/event-service.api\";\nimport { getAgentServerClientOptions } from \"#/api/agent-server-client-options\";\nimport { useConversationStore } from \"#/stores/conversation-store\";\nimport { trackError } from \"#/utils/error-handler\";\nimport { useReadConversationFile } from \"#/hooks/mutation/use-read-conversation-file\";\nimport useMetricsStore, { type MetricsState } from \"#/stores/metrics-store\";\nimport { useConversationHistory } from \"#/hooks/query/use-conversation-history\";\nimport { setConversationState } from \"#/utils/conversation-local-storage\";\nimport {\n recordModelSwitchMessage,\n seedModelSwitchesFromHistory,\n} from \"#/hooks/chat/record-model-switch-message\";\nimport {\n invalidateConversationQueries,\n updateConversationLlmModelInCache,\n} from \"#/hooks/mutation/conversation-mutation-utils\";\nimport {\n getStoredConversationMetadata,\n setStoredConversationMetadata,\n} from \"#/api/conversation-metadata-store\";\n\nexport type WebSocketConnectionState =\n | \"CONNECTING\"\n | \"OPEN\"\n | \"CLOSED\"\n | \"CLOSING\";\n\ninterface SendMessageResult {\n queued: boolean; // true if message was queued for later delivery, false if sent immediately\n}\n\ninterface ConversationWebSocketContextType {\n connectionState: WebSocketConnectionState;\n sendMessage: (message: SendMessageRequest) => Promise<SendMessageResult>;\n isLoadingHistory: boolean;\n reconnect: () => void;\n}\n\nconst ConversationWebSocketContext = createContext<\n ConversationWebSocketContextType | undefined\n>(undefined);\n\n/**\n * Extract the text body of an echoed user `MessageEvent` for matching against\n * the optimistic pending-message queue. The server wraps the original\n * `args.content` string in one or more `TextContent` entries (alongside any\n * `ImageContent` entries for inline images), so concatenating the `text`\n * fields gives us back the exact prompt we sent.\n */\nfunction extractMessageEventText(\n event: import(\"#/types/agent-server/core/events/message-event\").MessageEvent,\n): string {\n return event.llm_message.content\n .filter(\n (part): part is { type: \"text\"; text: string } => part.type === \"text\",\n )\n .map((part) => part.text)\n .join(\"\");\n}\n\nexport function ConversationWebSocketProvider({\n children,\n conversationId,\n conversationUrl,\n sessionApiKey,\n subConversations,\n subConversationIds,\n}: {\n children: React.ReactNode;\n conversationId?: string;\n conversationUrl?: string | null;\n sessionApiKey?: string | null;\n subConversations?: AppConversation[];\n subConversationIds?: string[];\n}) {\n // Separate connection state tracking for each WebSocket\n const [mainConnectionState, setMainConnectionState] =\n useState<WebSocketConnectionState>(\"CONNECTING\");\n const [planningConnectionState, setPlanningConnectionState] =\n useState<WebSocketConnectionState>(\"CONNECTING\");\n\n // Track if we've ever successfully connected for each connection\n // Don't show errors until after first successful connection\n const hasConnectedRefMain = React.useRef(false);\n const hasConnectedRefPlanning = React.useRef(false);\n\n const queryClient = useQueryClient();\n const addEvent = useEventStore((state) => state.addEvent);\n const addEvents = useEventStore((state) => state.addEvents);\n const clearEventsForConversation = useEventStore(\n (state) => state.clearEventsForConversation,\n );\n const { setErrorMessage, removeErrorMessage, clearConnectionError } =\n useErrorMessageStore();\n const consumeMatchingPendingMessage = useOptimisticUserMessageStore(\n (state) => state.consumeMatchingPendingMessage,\n );\n const { setExecutionStatus } = useConversationStateStore();\n const { appendInput, appendOutput } = useCommandStore();\n const resetBrowserStore = useBrowserStore((state) => state.reset);\n\n // Coalesce streaming deltas to ≤1 store commit/render per frame.\n // Separate batchers keep the main and planning streams from ever merging.\n const mainDeltaBatcherRef = useRef<StreamingDeltaBatcher | null>(null);\n if (mainDeltaBatcherRef.current === null) {\n mainDeltaBatcherRef.current = createStreamingDeltaBatcher((delta) => {\n useEventStore.getState().addEvent(delta);\n // A delta means connectivity recovered — mirror handleNonErrorEvent.\n useErrorMessageStore.getState().clearConnectionError();\n });\n }\n const planningDeltaBatcherRef = useRef<StreamingDeltaBatcher | null>(null);\n if (planningDeltaBatcherRef.current === null) {\n planningDeltaBatcherRef.current = createStreamingDeltaBatcher((delta) => {\n useEventStore\n .getState()\n .addEvent({ ...delta, isFromPlanningAgent: true });\n useErrorMessageStore.getState().clearConnectionError();\n });\n }\n\n // History loading state.\n // - Main conversation history is now loaded via REST (`useConversationHistory`),\n // so its loading state mirrors the REST query state (see below).\n // - Planning sub-conversation history still streams over the WebSocket using\n // `resend_mode='all'`, so we keep the count-based detection for it.\n const [isLoadingHistoryPlanning, setIsLoadingHistoryPlanning] =\n useState(true);\n const [expectedEventCountPlanning, setExpectedEventCountPlanning] = useState<\n number | null\n >(null);\n\n const { setPlanContent } = useConversationStore();\n\n // Hook for reading conversation file\n const { mutate: readConversationFile } = useReadConversationFile();\n\n // Track planning-agent received events (still WS-driven).\n const receivedEventCountRefPlanning = useRef(0);\n\n // Track the latest PlanningFileEditorObservation for Plan.md during history replay\n const latestPlanningFileEventRef = useRef<{\n path: string;\n conversationId: string;\n } | null>(null);\n\n const isPlanFilePath = (path: string | null): boolean =>\n path?.toUpperCase().endsWith(\"PLAN.MD\") ?? false;\n\n const handleNonErrorEvent = useCallback(() => {\n // A normal event means connectivity recovered: clear a transient connection\n // error, but keep sticky conversation errors (e.g. a wrong API key).\n clearConnectionError();\n }, [clearConnectionError]);\n\n // Helper function to update metrics from stats event\n const updateMetricsFromStats = useCallback(\n (event: ConversationStateUpdateEventStats) => {\n const usageToMetrics = event.value.usage_to_metrics;\n if (!usageToMetrics) {\n return;\n }\n\n // usage_to_metrics is keyed by arbitrary LLM usage ids (\"default\",\n // \"condenser\", \"profile:<name>:<uuid>\", …) — combine across all of\n // them, mirroring getCombinedMetrics on the REST path.\n const combined = Object.values(usageToMetrics).reduce<{\n cost: number;\n maxBudgetPerTask: number | null;\n usage: MetricsState[\"usage\"];\n }>(\n (acc, metrics) => {\n acc.cost += metrics.accumulated_cost;\n if (\n acc.maxBudgetPerTask === null &&\n metrics.max_budget_per_task !== null\n ) {\n acc.maxBudgetPerTask = metrics.max_budget_per_task;\n }\n const tokenUsage = metrics.accumulated_token_usage;\n if (tokenUsage) {\n acc.usage = {\n prompt_tokens:\n (acc.usage?.prompt_tokens ?? 0) + tokenUsage.prompt_tokens,\n completion_tokens:\n (acc.usage?.completion_tokens ?? 0) +\n tokenUsage.completion_tokens,\n cache_read_tokens:\n (acc.usage?.cache_read_tokens ?? 0) +\n tokenUsage.cache_read_tokens,\n cache_write_tokens:\n (acc.usage?.cache_write_tokens ?? 0) +\n tokenUsage.cache_write_tokens,\n context_window: Math.max(\n acc.usage?.context_window ?? 0,\n tokenUsage.context_window,\n ),\n per_turn_token: Math.max(\n acc.usage?.per_turn_token ?? 0,\n tokenUsage.per_turn_token,\n ),\n };\n }\n return acc;\n },\n { cost: 0, maxBudgetPerTask: null, usage: null },\n );\n\n useMetricsStore.getState().setMetrics({\n cost: combined.cost,\n max_budget_per_task: combined.maxBudgetPerTask,\n usage: combined.usage,\n });\n },\n [],\n );\n\n // Initial REST history load: fetch the most recent events and seed the\n // store. Older events are paginated in via `useLoadOlderEvents` when the\n // user scrolls to the top of the chat. The WebSocket connection waits for\n // this query so it can subscribe with `resend_mode='since'` and avoid\n // re-streaming everything REST already returned.\n const { data: preloadedHistory, isPending: isPreloadingHistory } =\n useConversationHistory(conversationId);\n\n // Skeleton only on the genuine first load (no cached data yet). On return the\n // cached page is present, so `isPending` is false and we render the\n // last-known discussion immediately while the tail refetch runs in the\n // background — the socket gate below also keys on `isPending`, so that\n // refetch never drops a live socket.\n const isLoadingHistoryMain = !!conversationId && isPreloadingHistory;\n\n // Clear the (global, not conversation-scoped) event store when the active\n // conversation changes, BEFORE the preloaded-history effect below re-seeds\n // it. This MUST live here rather than in the route component: a parent's\n // passive effect runs *after* this child's layout effects, so clearing from\n // the route would wipe the freshly seeded history. On a conversation switch\n // the history page is already cached, so `preloadedHistory` is available\n // synchronously — without ordering the clear first, the user's already-echoed\n // message gets seeded then immediately wiped, leaving only the `since`\n // WebSocket resend (the agent's reply). Re-entering the same conversation is\n // a no-op, so the store survives navigating away to Settings and back.\n useLayoutEffect(() => {\n const nextId = conversationId ?? null;\n if (useEventStore.getState().loadedConversationId === nextId) {\n return;\n }\n // Single atomic action: clears the previous conversation's events and\n // records the new loaded id in one `set`, so no subscriber can observe a\n // half-applied state (events gone but the old id still reported).\n clearEventsForConversation(nextId);\n resetBrowserStore();\n // The metrics store is conversation-scoped state too: without a reset the\n // previous conversation's usage/cost keeps rendering in the new\n // conversation's meter until fresh WS stats arrive — and a brand-new\n // conversation sends none, so the stale figure stuck indefinitely.\n useMetricsStore.getState().resetMetrics();\n }, [conversationId, clearEventsForConversation, resetBrowserStore]);\n\n useLayoutEffect(() => {\n if (!preloadedHistory || preloadedHistory.events.length === 0) {\n return;\n }\n addEvents(preloadedHistory.events);\n\n // The first user message of a cloud start-task conversation is persisted\n // server-side and reaches us via this REST preload, not over the WebSocket\n // (which subscribes with resend_mode='since' after the latest preloaded\n // timestamp). Consume any matching optimistic \"Sending…\" bubble here too —\n // mirroring the WS handler — so it doesn't linger as a duplicate of the echo.\n if (conversationId) {\n // Rebuild inline \"Switched to\" messages from the REST-preloaded history.\n // The live store writers (WS handler / user action) never see preloaded\n // events, so without this past model switches wouldn't render on reload.\n // Read the post-`addEvents` `uiEvents` (actions replaced by observations,\n // Think/Finish observations dropped) — not the raw history — so anchors\n // match the ids the renderer actually mounts.\n seedModelSwitchesFromHistory(\n conversationId,\n useEventStore.getState().uiEvents,\n );\n\n for (const event of preloadedHistory.events) {\n if (isUserMessageEvent(event)) {\n consumeMatchingPendingMessage(\n conversationId,\n extractMessageEventText(event),\n );\n }\n }\n }\n }, [\n preloadedHistory,\n addEvents,\n conversationId,\n consumeMatchingPendingMessage,\n ]);\n\n /**\n * Timestamp of the latest event we already have from REST. Used as\n * `after_timestamp` when opening the WebSocket so the server only resends\n * events strictly after this point. `null` until the first REST page lands\n * (the WS connection is gated on that — see `wsUrl` below). During\n * background refetches `preloadedHistory` keeps the last-known page, so the\n * anchor holds steady instead of flipping to null; reconnects read the\n * freshest value from the options ref at connect time.\n */\n const initialAfterTimestamp = useMemo<string | null>(() => {\n const events = preloadedHistory?.events ?? [];\n const latest = events[events.length - 1];\n if (!latest || !(\"timestamp\" in latest) || !latest.timestamp) return null;\n return latest.timestamp;\n }, [preloadedHistory]);\n\n // Build WebSocket URL from props.\n //\n // We deliberately wait for the FIRST history load (`isPending`: no data for\n // this query key yet) before opening the socket, so the WS subscription can\n // use `resend_mode='since'` with a meaningful `after_timestamp` instead of\n // falling back to `resend_mode='all'`. The gate is intentionally NOT on\n // `isFetching`: background refetches (e.g. the `refetchOnMount` fired when\n // returning to a conversation) must never tear a live socket down — on a\n // flaky link that caused a refetch → teardown → reconnect → refetch loop\n // that kept the conversation stuck at \"Connecting\" for minutes. Connecting\n // during a background refetch anchors `since` to the cached tail; the\n // overlap with the refetched page is deduped by the event store and the\n // `isDuplicateEvent` guards in the message handlers. A query-key reset\n // (backend swap / new session key) makes `isPending` true again, so a\n // genuine reset still re-gates. If the initial load errors, `isPending`\n // flips false and we fall through to connect with `resend_mode='all'` so\n // the user still sees live events.\n const wsUrl = useMemo(() => {\n if (!conversationId || !conversationUrl) {\n return null;\n }\n if (isPreloadingHistory) {\n return null;\n }\n return buildWebSocketUrl(conversationId, conversationUrl);\n }, [conversationId, conversationUrl, isPreloadingHistory]);\n\n const planningAgentWsUrl = useMemo(() => {\n if (!subConversations?.length) {\n return null;\n }\n\n // Currently, there is only one sub-conversation and it uses the planning agent.\n const planningAgentConversation = subConversations[0];\n\n if (\n !planningAgentConversation?.id ||\n !planningAgentConversation.conversation_url\n ) {\n return null;\n }\n\n return buildWebSocketUrl(\n planningAgentConversation.id,\n planningAgentConversation.conversation_url,\n );\n }, [subConversations]);\n\n // Merged connection state - reflects combined status of both connections\n const connectionState = useMemo<WebSocketConnectionState>(() => {\n // If planning agent connection doesn't exist, use main connection state\n if (!planningAgentWsUrl) {\n return mainConnectionState;\n }\n\n // If either is connecting, merged state is connecting\n if (\n mainConnectionState === \"CONNECTING\" ||\n planningConnectionState === \"CONNECTING\"\n ) {\n return \"CONNECTING\";\n }\n\n // If both are open, merged state is open\n if (mainConnectionState === \"OPEN\" && planningConnectionState === \"OPEN\") {\n return \"OPEN\";\n }\n\n // If both are closed, merged state is closed\n if (\n mainConnectionState === \"CLOSED\" &&\n planningConnectionState === \"CLOSED\"\n ) {\n return \"CLOSED\";\n }\n\n // If either is closing, merged state is closing\n if (\n mainConnectionState === \"CLOSING\" ||\n planningConnectionState === \"CLOSING\"\n ) {\n return \"CLOSING\";\n }\n\n // Default to closed if states don't match expected patterns\n return \"CLOSED\";\n }, [mainConnectionState, planningConnectionState, planningAgentWsUrl]);\n\n useEffect(() => {\n if (\n expectedEventCountPlanning !== null &&\n receivedEventCountRefPlanning.current >= expectedEventCountPlanning &&\n isLoadingHistoryPlanning\n ) {\n setIsLoadingHistoryPlanning(false);\n }\n }, [\n expectedEventCountPlanning,\n isLoadingHistoryPlanning,\n receivedEventCountRefPlanning,\n ]);\n\n // Call API once after history loading completes if we tracked any PlanningFileEditorObservation events\n useEffect(() => {\n if (!isLoadingHistoryPlanning && latestPlanningFileEventRef.current) {\n const { path, conversationId: currentPlanningConversationId } =\n latestPlanningFileEventRef.current;\n\n readConversationFile(\n {\n conversationId: currentPlanningConversationId,\n filePath: path,\n },\n {\n onSuccess: (fileContent) => {\n setPlanContent(fileContent);\n },\n onError: (error) => {\n console.warn(\"Failed to read conversation file:\", error);\n },\n },\n );\n\n // Clear the ref after calling the API\n latestPlanningFileEventRef.current = null;\n }\n }, [isLoadingHistoryPlanning, readConversationFile, setPlanContent]);\n\n useEffect(() => {\n hasConnectedRefMain.current = false;\n setIsLoadingHistoryPlanning(!!subConversationIds?.length);\n setExpectedEventCountPlanning(null);\n receivedEventCountRefPlanning.current = 0;\n // Reset the tracked event ref when sub-conversations change\n latestPlanningFileEventRef.current = null;\n }, [subConversationIds]);\n\n // Reset hasConnected flags when the conversation changes.\n useEffect(() => {\n hasConnectedRefMain.current = false;\n hasConnectedRefPlanning.current = false;\n // Reset the tracked event ref when conversation changes\n latestPlanningFileEventRef.current = null;\n }, [conversationId]);\n\n // Drop buffered deltas on conversation switch/unmount: the store is cleared on\n // switch, so flushing them would leak into the next conversation.\n useEffect(() => {\n const mainBatcher = mainDeltaBatcherRef.current;\n const planningBatcher = planningDeltaBatcherRef.current;\n return () => {\n mainBatcher?.reset();\n planningBatcher?.reset();\n };\n }, [conversationId]);\n\n // Merged loading history state - true if either connection is still loading\n const isLoadingHistory = useMemo(\n () => isLoadingHistoryMain || isLoadingHistoryPlanning,\n [isLoadingHistoryMain, isLoadingHistoryPlanning],\n );\n\n // Separate message handlers for each connection\n const handleMainMessage = useCallback(\n (messageEvent: MessageEvent) => {\n try {\n const event = JSON.parse(messageEvent.data);\n\n // History loading for the main conversation is REST-driven now;\n // every WS message is a new event we add to the store.\n\n // Use type guard to validate v1 event structure\n if (isAgentServerEvent(event)) {\n // Buffer deltas; nothing else in this handler applies to them.\n if (isStreamingDeltaEvent(event)) {\n mainDeltaBatcherRef.current?.enqueue(event);\n return;\n }\n // Flush buffered deltas before this event so it can't overtake them.\n mainDeltaBatcherRef.current?.flush();\n\n // A reconnect replays the backlog from a stale anchor. The store\n // dedups by id, but the side-effects below aren't idempotent, so skip\n // them for replayed events (#1656).\n const isDuplicateEvent = useEventStore\n .getState()\n .eventIds.has(event.id ?? \"\");\n const switchLLMObservation = isSwitchLLMObservationEvent(event)\n ? event\n : null;\n addEvent(event);\n if (isDuplicateEvent) {\n return;\n }\n\n // Handle displayable error events - show error banner\n // AgentErrorEvent errors are displayed inline in the chat, not as banners\n if (isDisplayableErrorEvent(event)) {\n const errorEvent = event as\n | ConversationErrorEvent\n | ServerErrorEvent;\n const classification =\n \"classification\" in errorEvent ? errorEvent.classification : null;\n trackError({\n source: \"conversation\",\n metadata: {\n eventId: errorEvent.id,\n errorCode: errorEvent.code,\n },\n classification,\n });\n setErrorMessage(\n errorEvent.detail,\n \"conversation\",\n errorEvent.code,\n classification,\n );\n } else {\n handleNonErrorEvent();\n }\n\n // LLM errors render inline in the chat (see ErrorEventMessage); track\n // them for analytics but keep them out of the banner above the chat box.\n if (isAgentErrorEvent(event)) {\n trackError({\n source: \"agent\",\n metadata: {\n eventId: event.id,\n toolName: event.tool_name,\n toolCallId: event.tool_call_id,\n },\n classification: event.classification,\n });\n }\n\n // Clear optimistic user message when a user message is confirmed.\n // We match by the echoed text content (with FIFO fallback inside the\n // store), so an echo for \"second\" pops \"second\" — not whichever\n // pending entry happens to be oldest — protecting against any\n // out-of-order delivery between conversations or sub-agents.\n if (isUserMessageEvent(event)) {\n if (conversationId) {\n consumeMatchingPendingMessage(\n conversationId,\n extractMessageEventText(event),\n );\n // Clear draft from localStorage - message was successfully delivered\n setConversationState(conversationId, { draftMessage: null });\n }\n }\n\n // Handle cache invalidation for ActionEvent\n if (isActionEvent(event)) {\n const currentConversationId =\n conversationId || \"test-conversation-id\"; // TODO: Get from context\n handleActionEventCacheInvalidation(\n event,\n currentConversationId,\n queryClient,\n );\n }\n\n // Handle conversation state updates\n // TODO: Tests\n if (isConversationStateUpdateEvent(event)) {\n if (isFullStateConversationStateUpdateEvent(event)) {\n setExecutionStatus(event.value.execution_status);\n }\n if (isAgentStatusConversationStateUpdateEvent(event)) {\n setExecutionStatus(event.value);\n }\n if (isStatsConversationStateUpdateEvent(event)) {\n updateMetricsFromStats(event);\n }\n // Mirror goal status into the store. Intentionally duplicated across\n // the main and planning WebSocket handlers (like the execution_status\n // and stats branches above), not a merge artifact.\n if (isGoalConversationStateUpdateEvent(event) && conversationId) {\n useGoalStore.getState().setStatus(conversationId, event.value);\n }\n }\n\n // Handle ExecuteBashAction events - add command as input to terminal\n if (isExecuteBashActionEvent(event)) {\n appendInput(event.action.command);\n }\n\n // Handle ExecuteBashObservation events - add output to terminal\n if (isExecuteBashObservationEvent(event)) {\n // Extract text content from the observation content array\n const textContent = event.observation.content\n .filter((c) => c.type === \"text\")\n .map((c) => c.text)\n .join(\"\\n\");\n appendOutput(textContent);\n }\n\n // Handle BrowserObservation events - update browser store with screenshot\n if (isBrowserObservationEvent(event)) {\n const { screenshot_data: screenshotData } = event.observation;\n if (screenshotData) {\n const screenshotSrc = screenshotData.startsWith(\"data:\")\n ? screenshotData\n : `data:image/png;base64,${screenshotData}`;\n useBrowserStore.getState().setScreenshotSrc(screenshotSrc);\n }\n }\n\n // Handle BrowserNavigateAction events - update browser store with URL\n if (isBrowserNavigateActionEvent(event)) {\n useBrowserStore.getState().setUrl(event.action.url);\n }\n\n if (\n conversationId &&\n switchLLMObservation &&\n !switchLLMObservation.observation.is_error\n ) {\n recordModelSwitchMessage(\n conversationId,\n switchLLMObservation.observation.profile_name,\n );\n\n // Mirror the user-driven `/model` path: persist the profile so the\n // chat-header switcher shows the right name after a reload, even\n // when several profiles share a model (#1082).\n const prevMetadata = getStoredConversationMetadata(conversationId);\n setStoredConversationMetadata(conversationId, {\n selected_repository: prevMetadata?.selected_repository ?? null,\n selected_branch: prevMetadata?.selected_branch ?? null,\n git_provider: prevMetadata?.git_provider ?? null,\n selected_workspace: prevMetadata?.selected_workspace ?? null,\n active_profile: switchLLMObservation.observation.profile_name,\n // Full-object replace: carry the plugins snapshot forward so the\n // in-conversation plugins view survives a profile switch.\n plugins: prevMetadata?.plugins ?? null,\n });\n\n if (switchLLMObservation.observation.active_model) {\n updateConversationLlmModelInCache(\n queryClient,\n conversationId,\n switchLLMObservation.observation.active_model,\n );\n }\n\n invalidateConversationQueries(queryClient, conversationId);\n }\n\n // Handle canvas_ui ActionEvents from both the legacy Python tool and\n // the client-defined JSON tool. The server acknowledges immediately;\n // the actual UI change happens here on the client.\n if (isCanvasUIActionEvent(event)) {\n handleCanvasUIAction(event.action, conversationId ?? null);\n }\n\n // Same client-tool pattern, but the work is a network call: launch\n // the requested child conversation and post the outcome back so the\n // agent learns the id the server-side acknowledgement can't carry.\n if (conversationId && isLaunchChildConversationActionEvent(event)) {\n void handleLaunchChildConversationAction(\n event.action,\n conversationId,\n event.tool_call_id,\n );\n }\n }\n } catch (error) {\n console.warn(\"Failed to parse WebSocket message as JSON:\", error);\n }\n },\n [\n addEvent,\n setErrorMessage,\n consumeMatchingPendingMessage,\n queryClient,\n conversationId,\n setExecutionStatus,\n appendInput,\n appendOutput,\n updateMetricsFromStats,\n handleNonErrorEvent,\n ],\n );\n\n const handlePlanningMessage = useCallback(\n (messageEvent: MessageEvent) => {\n try {\n const event = JSON.parse(messageEvent.data);\n\n // Track received events for history loading (count ALL events from WebSocket)\n // Always count when loading, even if we don't have the expected count yet\n if (isLoadingHistoryPlanning) {\n receivedEventCountRefPlanning.current += 1;\n\n if (\n expectedEventCountPlanning !== null &&\n receivedEventCountRefPlanning.current >= expectedEventCountPlanning\n ) {\n setIsLoadingHistoryPlanning(false);\n }\n }\n\n // Use type guard to validate v1 event structure\n if (isAgentServerEvent(event)) {\n // Buffer deltas (the commit re-applies the planning flag).\n if (isStreamingDeltaEvent(event)) {\n planningDeltaBatcherRef.current?.enqueue(event);\n return;\n }\n // Flush buffered deltas before this event so it can't overtake them.\n planningDeltaBatcherRef.current?.flush();\n\n // Skip non-idempotent side-effects for replayed events, as in the\n // main handler (#1656).\n const isDuplicateEvent = useEventStore\n .getState()\n .eventIds.has(event.id ?? \"\");\n // Mark this event as coming from the planning agent\n const eventWithPlanningFlag = {\n ...event,\n isFromPlanningAgent: true,\n };\n addEvent(eventWithPlanningFlag);\n if (isDuplicateEvent) {\n return;\n }\n\n // Handle displayable error events - show error banner\n // AgentErrorEvent errors are displayed inline in the chat, not as banners\n if (isDisplayableErrorEvent(event)) {\n const errorEvent = event as\n | ConversationErrorEvent\n | ServerErrorEvent;\n const classification =\n \"classification\" in errorEvent ? errorEvent.classification : null;\n trackError({\n source: \"planning_conversation\",\n metadata: {\n eventId: errorEvent.id,\n errorCode: errorEvent.code,\n },\n classification,\n });\n setErrorMessage(\n errorEvent.detail,\n \"conversation\",\n errorEvent.code,\n classification,\n );\n } else {\n handleNonErrorEvent();\n }\n\n // LLM errors render inline in the chat (see ErrorEventMessage); track\n // them for analytics but keep them out of the banner above the chat box.\n if (isAgentErrorEvent(event)) {\n trackError({\n source: \"planning_agent\",\n metadata: {\n eventId: event.id,\n toolName: event.tool_name,\n toolCallId: event.tool_call_id,\n },\n classification: event.classification,\n });\n }\n\n // Clear optimistic user message when a user message is confirmed.\n // Always scope to the main `conversationId` (where the user types)\n // and match on the echoed content so the planning sub-agent's own\n // events can never consume a main-conversation pending entry.\n if (isUserMessageEvent(event)) {\n if (conversationId) {\n consumeMatchingPendingMessage(\n conversationId,\n extractMessageEventText(event),\n );\n setConversationState(conversationId, { draftMessage: null });\n }\n }\n\n // Handle cache invalidation for ActionEvent\n if (isActionEvent(event)) {\n const planningAgentConversation = subConversations?.[0];\n const currentConversationId =\n planningAgentConversation?.id || \"test-conversation-id\"; // TODO: Get from context\n handleActionEventCacheInvalidation(\n event,\n currentConversationId,\n queryClient,\n );\n }\n\n // Handle conversation state updates\n // TODO: Tests\n if (isConversationStateUpdateEvent(event)) {\n if (isFullStateConversationStateUpdateEvent(event)) {\n setExecutionStatus(event.value.execution_status);\n }\n if (isAgentStatusConversationStateUpdateEvent(event)) {\n setExecutionStatus(event.value);\n }\n if (isStatsConversationStateUpdateEvent(event)) {\n updateMetricsFromStats(event);\n }\n // Mirror goal status into the store. Intentionally duplicated across\n // the main and planning WebSocket handlers (like the execution_status\n // and stats branches above), not a merge artifact.\n if (isGoalConversationStateUpdateEvent(event) && conversationId) {\n useGoalStore.getState().setStatus(conversationId, event.value);\n }\n }\n\n // Handle ExecuteBashAction events - add command as input to terminal\n if (isExecuteBashActionEvent(event)) {\n appendInput(event.action.command);\n }\n\n // Handle ExecuteBashObservation events - add output to terminal\n if (isExecuteBashObservationEvent(event)) {\n // Extract text content from the observation content array\n const textContent = event.observation.content\n .filter((c) => c.type === \"text\")\n .map((c) => c.text)\n .join(\"\\n\");\n appendOutput(textContent);\n }\n\n // Handle PlanningFileEditorObservation - only update plan for Plan.md\n if (isPlanningFileEditorObservationEvent(event)) {\n const { path } = event.observation;\n if (isPlanFilePath(path)) {\n const planningAgentConversation = subConversations?.[0];\n const planningConversationId = planningAgentConversation?.id;\n\n if (planningConversationId && path) {\n if (isLoadingHistoryPlanning) {\n latestPlanningFileEventRef.current = {\n path,\n conversationId: planningConversationId,\n };\n } else {\n readConversationFile(\n {\n conversationId: planningConversationId,\n filePath: path,\n },\n {\n onSuccess: (fileContent) => {\n setPlanContent(fileContent);\n },\n onError: (error) => {\n console.warn(\n \"Failed to read conversation file:\",\n error,\n );\n },\n },\n );\n }\n }\n }\n }\n }\n } catch (error) {\n console.warn(\"Failed to parse WebSocket message as JSON:\", error);\n }\n },\n [\n addEvent,\n isLoadingHistoryPlanning,\n expectedEventCountPlanning,\n setErrorMessage,\n consumeMatchingPendingMessage,\n queryClient,\n subConversations,\n conversationId,\n setExecutionStatus,\n appendInput,\n appendOutput,\n readConversationFile,\n setPlanContent,\n updateMetricsFromStats,\n handleNonErrorEvent,\n ],\n );\n\n // Separate WebSocket options for main connection\n const mainWebsocketOptions: WebSocketHookOptions = useMemo(() => {\n // History was already loaded over REST (`useConversationHistory`).\n // Subscribe with `resend_mode='since'` so the server only resends events\n // strictly after the latest one we already have. If REST returned no\n // events at all (brand-new conversation), fall back to `'all'` so any\n // events that may have been written between the REST call and the WS\n // handshake still show up. Dedup in the event store handles overlap.\n const queryParams: Record<string, string | boolean> = initialAfterTimestamp\n ? { resend_mode: \"since\", after_timestamp: initialAfterTimestamp }\n : { resend_mode: \"all\" };\n\n return {\n queryParams,\n sessionApiKey,\n reconnect: { enabled: true },\n onOpen: () => {\n setMainConnectionState(\"OPEN\");\n hasConnectedRefMain.current = true; // Mark that we've successfully connected\n clearConnectionError(); // Clear a previous connection error; keep sticky conversation errors\n },\n onClose: () => {\n setMainConnectionState(\"CLOSED\");\n },\n onError: () => {\n setMainConnectionState(\"CLOSED\");\n // Only show error message if we've previously connected successfully\n if (hasConnectedRefMain.current) {\n setErrorMessage(SERVER_CONNECTION_ERROR_MESSAGE, \"connection\");\n }\n },\n onMessage: handleMainMessage,\n };\n }, [\n handleMainMessage,\n setErrorMessage,\n clearConnectionError,\n sessionApiKey,\n initialAfterTimestamp,\n ]);\n\n // Separate WebSocket options for planning agent connection\n const planningWebsocketOptions: WebSocketHookOptions = useMemo(() => {\n const queryParams: Record<string, string | boolean> = {\n resend_all: true,\n };\n\n const planningAgentConversation = subConversations?.[0];\n const planningApiKey =\n planningAgentConversation?.session_api_key ?? sessionApiKey;\n\n return {\n queryParams,\n sessionApiKey: planningApiKey,\n reconnect: { enabled: true },\n onOpen: async () => {\n setPlanningConnectionState(\"OPEN\");\n hasConnectedRefPlanning.current = true; // Mark that we've successfully connected\n clearConnectionError(); // Clear a previous connection error; keep sticky conversation errors\n\n // Fetch expected event count for history loading detection\n if (\n planningAgentConversation?.id &&\n planningAgentConversation.conversation_url\n ) {\n try {\n const count = await EventService.getEventCount(\n planningAgentConversation.id,\n planningAgentConversation.conversation_url,\n planningAgentConversation.session_api_key,\n );\n setExpectedEventCountPlanning(count);\n\n // If no events expected, mark as loaded immediately\n if (count === 0) {\n setIsLoadingHistoryPlanning(false);\n }\n } catch (error) {\n // Fall back to marking as loaded to avoid infinite loading state\n setIsLoadingHistoryPlanning(false);\n }\n }\n },\n onClose: () => {\n setPlanningConnectionState(\"CLOSED\");\n },\n onError: () => {\n setPlanningConnectionState(\"CLOSED\");\n // Only show error message if we've previously connected successfully\n if (hasConnectedRefPlanning.current) {\n setErrorMessage(SERVER_CONNECTION_ERROR_MESSAGE, \"connection\");\n }\n },\n onMessage: handlePlanningMessage,\n };\n }, [\n handlePlanningMessage,\n setErrorMessage,\n clearConnectionError,\n sessionApiKey,\n subConversations,\n ]);\n\n // Only attempt WebSocket connection when we have a valid URL\n // This prevents connection attempts during task polling phase\n const websocketUrl = wsUrl;\n const { socket: mainSocket, reconnect: reconnectMain } = useWebSocket(\n websocketUrl || \"\",\n mainWebsocketOptions,\n );\n\n const { socket: planningAgentSocket, reconnect: reconnectPlanning } =\n useWebSocket(planningAgentWsUrl || \"\", planningWebsocketOptions);\n\n const reconnect = useCallback(() => {\n removeErrorMessage();\n const currentMode = useConversationStore.getState().conversationMode;\n if (currentMode === \"plan\" && planningAgentWsUrl) {\n reconnectPlanning();\n return;\n }\n reconnectMain();\n }, [\n planningAgentWsUrl,\n reconnectMain,\n reconnectPlanning,\n removeErrorMessage,\n ]);\n\n // V1 send message function via WebSocket\n // Falls back to REST API queue when WebSocket is not connected\n const sendMessage = useCallback(\n async (message: SendMessageRequest): Promise<SendMessageResult> => {\n const currentMode = useConversationStore.getState().conversationMode;\n const currentSocket =\n currentMode === \"plan\" ? planningAgentSocket : mainSocket;\n\n if (currentSocket?.readyState !== WebSocket.OPEN) {\n // WebSocket not connected - queue message via REST API\n // Message will be delivered automatically when conversation becomes ready\n if (!conversationId) {\n const error = new Error(\"No conversation ID available\");\n setErrorMessage(error.message);\n throw error;\n }\n\n try {\n await new ConversationClient(getAgentServerClientOptions()).sendEvent(\n conversationId,\n {\n role: \"user\",\n content: message.content,\n },\n { run: true },\n );\n // Message queued successfully - it will be delivered when ready\n // Return queued: true so caller knows not to show optimistic UI\n return { queued: true };\n } catch (error) {\n const errorMessage =\n error instanceof Error\n ? error.message\n : \"Failed to queue message for delivery\";\n setErrorMessage(errorMessage);\n throw error;\n }\n }\n\n try {\n // Send message through WebSocket as JSON with run: true so the\n // agent loop starts automatically in async mode.\n currentSocket.send(JSON.stringify({ ...message, run: true }));\n return { queued: false };\n } catch (error) {\n const errorMessage =\n error instanceof Error ? error.message : \"Failed to send message\";\n setErrorMessage(errorMessage);\n throw error;\n }\n },\n [mainSocket, planningAgentSocket, setErrorMessage, conversationId],\n );\n\n // Track main socket state changes\n useEffect(() => {\n // Only process socket updates if we have a valid URL and socket\n if (mainSocket && wsUrl) {\n // Update state based on socket readyState\n const updateState = () => {\n switch (mainSocket.readyState) {\n case WebSocket.CONNECTING:\n setMainConnectionState(\"CONNECTING\");\n break;\n case WebSocket.OPEN:\n setMainConnectionState(\"OPEN\");\n break;\n case WebSocket.CLOSING:\n setMainConnectionState(\"CLOSING\");\n break;\n case WebSocket.CLOSED:\n setMainConnectionState(\"CLOSED\");\n break;\n default:\n setMainConnectionState(\"CLOSED\");\n break;\n }\n };\n\n updateState();\n }\n }, [mainSocket, wsUrl]);\n\n // Track planning agent socket state changes\n useEffect(() => {\n // Only process socket updates if we have a valid URL and socket\n if (planningAgentSocket && planningAgentWsUrl) {\n // Update state based on socket readyState\n const updateState = () => {\n switch (planningAgentSocket.readyState) {\n case WebSocket.CONNECTING:\n setPlanningConnectionState(\"CONNECTING\");\n break;\n case WebSocket.OPEN:\n setPlanningConnectionState(\"OPEN\");\n break;\n case WebSocket.CLOSING:\n setPlanningConnectionState(\"CLOSING\");\n break;\n case WebSocket.CLOSED:\n setPlanningConnectionState(\"CLOSED\");\n break;\n default:\n setPlanningConnectionState(\"CLOSED\");\n break;\n }\n };\n\n updateState();\n }\n }, [planningAgentSocket, planningAgentWsUrl]);\n\n const contextValue = useMemo(\n () => ({ connectionState, sendMessage, isLoadingHistory, reconnect }),\n [connectionState, sendMessage, isLoadingHistory, reconnect],\n );\n\n return (\n <ConversationWebSocketContext.Provider value={contextValue}>\n {children}\n </ConversationWebSocketContext.Provider>\n );\n}\n\nexport const useConversationWebSocket =\n (): ConversationWebSocketContextType | null => {\n const context = useContext(ConversationWebSocketContext);\n // Return null instead of throwing when not in provider\n // This allows the hook to be called conditionally based on conversation version\n return context || null;\n };\n"],"mappings":"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"}
1
+ {"version":3,"file":"conversation-websocket-context.cjs","names":[],"sources":["../../src/contexts/conversation-websocket-context.tsx"],"sourcesContent":["import React, {\n createContext,\n useContext,\n useEffect,\n useLayoutEffect,\n useState,\n useCallback,\n useMemo,\n useRef,\n} from \"react\";\nimport { ConversationClient } from \"@openhands/typescript-client/clients\";\n\nimport { useQueryClient } from \"@tanstack/react-query\";\nimport { useWebSocket, WebSocketHookOptions } from \"#/hooks/use-websocket\";\nimport { SERVER_CONNECTION_ERROR_MESSAGE } from \"#/constants/server-connection-error\";\nimport { useEventStore } from \"#/stores/use-event-store\";\nimport { useErrorMessageStore } from \"#/stores/error-message-store\";\nimport { useOptimisticUserMessageStore } from \"#/stores/optimistic-user-message-store\";\nimport { useConversationStateStore } from \"#/stores/conversation-state-store\";\nimport { useCommandStore } from \"#/stores/command-store\";\nimport { useBrowserStore } from \"#/stores/browser-store\";\nimport { useGoalStore } from \"#/stores/goal-store\";\nimport {\n isAgentServerEvent,\n isAgentErrorEvent,\n isUserMessageEvent,\n isActionEvent,\n isConversationStateUpdateEvent,\n isFullStateConversationStateUpdateEvent,\n isAgentStatusConversationStateUpdateEvent,\n isStatsConversationStateUpdateEvent,\n isGoalConversationStateUpdateEvent,\n isExecuteBashActionEvent,\n isExecuteBashObservationEvent,\n isDisplayableErrorEvent,\n isPlanningFileEditorObservationEvent,\n isBrowserObservationEvent,\n isBrowserNavigateActionEvent,\n isSwitchLLMObservationEvent,\n isCanvasUIActionEvent,\n isStreamingDeltaEvent,\n isLaunchChildConversationActionEvent,\n} from \"#/types/agent-server/type-guards\";\nimport {\n createStreamingDeltaBatcher,\n StreamingDeltaBatcher,\n} from \"#/utils/streaming-delta-batcher\";\nimport { handleCanvasUIAction } from \"#/services/canvas-ui\";\nimport { handleLaunchChildConversationAction } from \"#/services/child-conversation-launch\";\nimport { ConversationStateUpdateEventStats } from \"#/types/agent-server/core/events/conversation-state-event\";\nimport type {\n ConversationErrorEvent,\n ServerErrorEvent,\n} from \"#/types/agent-server/core/events/conversation-state-event\";\nimport { handleActionEventCacheInvalidation } from \"#/utils/cache-utils\";\nimport { buildWebSocketUrl } from \"#/utils/websocket-url\";\nimport type {\n AppConversation,\n SendMessageRequest,\n} from \"#/api/conversation-service/agent-server-conversation-service.types\";\nimport EventService from \"#/api/event-service/event-service.api\";\nimport { getAgentServerClientOptions } from \"#/api/agent-server-client-options\";\nimport { useConversationStore } from \"#/stores/conversation-store\";\nimport { trackError } from \"#/utils/error-handler\";\nimport { useReadConversationFile } from \"#/hooks/mutation/use-read-conversation-file\";\nimport useMetricsStore, { type MetricsState } from \"#/stores/metrics-store\";\nimport { useConversationHistory } from \"#/hooks/query/use-conversation-history\";\nimport { setConversationState } from \"#/utils/conversation-local-storage\";\nimport {\n recordModelSwitchMessage,\n seedModelSwitchesFromHistory,\n} from \"#/hooks/chat/record-model-switch-message\";\nimport {\n invalidateConversationQueries,\n updateConversationLlmModelInCache,\n} from \"#/hooks/mutation/conversation-mutation-utils\";\nimport {\n getStoredConversationMetadata,\n setStoredConversationMetadata,\n} from \"#/api/conversation-metadata-store\";\n\nexport type WebSocketConnectionState =\n | \"CONNECTING\"\n | \"OPEN\"\n | \"CLOSED\"\n | \"CLOSING\";\n\ninterface SendMessageResult {\n queued: boolean; // true if message was queued for later delivery, false if sent immediately\n}\n\ninterface ConversationWebSocketContextType {\n connectionState: WebSocketConnectionState;\n sendMessage: (message: SendMessageRequest) => Promise<SendMessageResult>;\n isLoadingHistory: boolean;\n reconnect: () => void;\n}\n\nconst ConversationWebSocketContext = createContext<\n ConversationWebSocketContextType | undefined\n>(undefined);\n\n/**\n * Extract the text body of an echoed user `MessageEvent` for matching against\n * the optimistic pending-message queue. The server wraps the original\n * `args.content` string in one or more `TextContent` entries (alongside any\n * `ImageContent` entries for inline images), so concatenating the `text`\n * fields gives us back the exact prompt we sent.\n */\nfunction extractMessageEventText(\n event: import(\"#/types/agent-server/core/events/message-event\").MessageEvent,\n): string {\n return event.llm_message.content\n .filter(\n (part): part is { type: \"text\"; text: string } => part.type === \"text\",\n )\n .map((part) => part.text)\n .join(\"\");\n}\n\nexport function ConversationWebSocketProvider({\n children,\n conversationId,\n conversationUrl,\n sessionApiKey,\n subConversations,\n subConversationIds,\n}: {\n children: React.ReactNode;\n conversationId?: string;\n conversationUrl?: string | null;\n sessionApiKey?: string | null;\n subConversations?: AppConversation[];\n subConversationIds?: string[];\n}) {\n // Separate connection state tracking for each WebSocket\n const [mainConnectionState, setMainConnectionState] =\n useState<WebSocketConnectionState>(\"CONNECTING\");\n const [planningConnectionState, setPlanningConnectionState] =\n useState<WebSocketConnectionState>(\"CONNECTING\");\n\n // Track if we've ever successfully connected for each connection\n // Don't show errors until after first successful connection\n const hasConnectedRefMain = React.useRef(false);\n const hasConnectedRefPlanning = React.useRef(false);\n\n const queryClient = useQueryClient();\n const addEvent = useEventStore((state) => state.addEvent);\n const addEvents = useEventStore((state) => state.addEvents);\n const clearEventsForConversation = useEventStore(\n (state) => state.clearEventsForConversation,\n );\n const { setErrorMessage, removeErrorMessage, clearConnectionError } =\n useErrorMessageStore();\n const consumeMatchingPendingMessage = useOptimisticUserMessageStore(\n (state) => state.consumeMatchingPendingMessage,\n );\n const { setExecutionStatus } = useConversationStateStore();\n const { appendInput, appendOutput } = useCommandStore();\n const resetBrowserStore = useBrowserStore((state) => state.reset);\n\n // Coalesce streaming deltas to ≤1 store commit/render per frame.\n // Separate batchers keep the main and planning streams from ever merging.\n const mainDeltaBatcherRef = useRef<StreamingDeltaBatcher | null>(null);\n if (mainDeltaBatcherRef.current === null) {\n mainDeltaBatcherRef.current = createStreamingDeltaBatcher((delta) => {\n useEventStore.getState().addEvent(delta);\n // A delta means connectivity recovered — mirror handleNonErrorEvent.\n useErrorMessageStore.getState().clearConnectionError();\n });\n }\n const planningDeltaBatcherRef = useRef<StreamingDeltaBatcher | null>(null);\n if (planningDeltaBatcherRef.current === null) {\n planningDeltaBatcherRef.current = createStreamingDeltaBatcher((delta) => {\n useEventStore\n .getState()\n .addEvent({ ...delta, isFromPlanningAgent: true });\n useErrorMessageStore.getState().clearConnectionError();\n });\n }\n\n // History loading state.\n // - Main conversation history is now loaded via REST (`useConversationHistory`),\n // so its loading state mirrors the REST query state (see below).\n // - Planning sub-conversation history still streams over the WebSocket using\n // `resend_mode='all'`, so we keep the count-based detection for it.\n const [isLoadingHistoryPlanning, setIsLoadingHistoryPlanning] =\n useState(true);\n const [expectedEventCountPlanning, setExpectedEventCountPlanning] = useState<\n number | null\n >(null);\n\n const { setPlanContent } = useConversationStore();\n\n // Hook for reading conversation file\n const { mutate: readConversationFile } = useReadConversationFile();\n\n // Track planning-agent received events (still WS-driven).\n const receivedEventCountRefPlanning = useRef(0);\n\n // Track the latest PlanningFileEditorObservation for Plan.md during history replay\n const latestPlanningFileEventRef = useRef<{\n path: string;\n conversationId: string;\n } | null>(null);\n\n const isPlanFilePath = (path: string | null): boolean =>\n path?.toUpperCase().endsWith(\"PLAN.MD\") ?? false;\n\n const handleNonErrorEvent = useCallback(() => {\n // A normal event means connectivity recovered: clear a transient connection\n // error, but keep sticky conversation errors (e.g. a wrong API key).\n clearConnectionError();\n }, [clearConnectionError]);\n\n // Helper function to update metrics from stats event\n const updateMetricsFromStats = useCallback(\n (event: ConversationStateUpdateEventStats) => {\n const usageToMetrics = event.value.usage_to_metrics;\n if (!usageToMetrics) {\n return;\n }\n\n // usage_to_metrics is keyed by arbitrary LLM usage ids (\"default\",\n // \"condenser\", \"profile:<name>:<uuid>\", …) — combine across all of\n // them, mirroring getCombinedMetrics on the REST path.\n const combined = Object.values(usageToMetrics).reduce<{\n cost: number;\n maxBudgetPerTask: number | null;\n usage: MetricsState[\"usage\"];\n }>(\n (acc, metrics) => {\n acc.cost += metrics.accumulated_cost;\n if (\n acc.maxBudgetPerTask === null &&\n metrics.max_budget_per_task !== null\n ) {\n acc.maxBudgetPerTask = metrics.max_budget_per_task;\n }\n const tokenUsage = metrics.accumulated_token_usage;\n if (tokenUsage) {\n acc.usage = {\n prompt_tokens:\n (acc.usage?.prompt_tokens ?? 0) + tokenUsage.prompt_tokens,\n completion_tokens:\n (acc.usage?.completion_tokens ?? 0) +\n tokenUsage.completion_tokens,\n cache_read_tokens:\n (acc.usage?.cache_read_tokens ?? 0) +\n tokenUsage.cache_read_tokens,\n cache_write_tokens:\n (acc.usage?.cache_write_tokens ?? 0) +\n tokenUsage.cache_write_tokens,\n context_window: Math.max(\n acc.usage?.context_window ?? 0,\n tokenUsage.context_window,\n ),\n per_turn_token: Math.max(\n acc.usage?.per_turn_token ?? 0,\n tokenUsage.per_turn_token,\n ),\n };\n }\n return acc;\n },\n { cost: 0, maxBudgetPerTask: null, usage: null },\n );\n\n useMetricsStore.getState().setMetrics({\n cost: combined.cost,\n max_budget_per_task: combined.maxBudgetPerTask,\n usage: combined.usage,\n });\n },\n [],\n );\n\n // Initial REST history load: fetch the most recent events and seed the\n // store. Older events are paginated in via `useLoadOlderEvents` when the\n // user scrolls to the top of the chat. The WebSocket connection waits for\n // this query so it can subscribe with `resend_mode='since'` and avoid\n // re-streaming everything REST already returned.\n const { data: preloadedHistory, isPending: isPreloadingHistory } =\n useConversationHistory(conversationId);\n\n // Skeleton only on the genuine first load (no cached data yet). On return the\n // cached page is present, so `isPending` is false and we render the\n // last-known discussion immediately while the tail refetch runs in the\n // background — the socket gate below also keys on `isPending`, so that\n // refetch never drops a live socket.\n const isLoadingHistoryMain = !!conversationId && isPreloadingHistory;\n\n // Clear the (global, not conversation-scoped) event store when the active\n // conversation changes, BEFORE the preloaded-history effect below re-seeds\n // it. This MUST live here rather than in the route component: a parent's\n // passive effect runs *after* this child's layout effects, so clearing from\n // the route would wipe the freshly seeded history. On a conversation switch\n // the history page is already cached, so `preloadedHistory` is available\n // synchronously — without ordering the clear first, the user's already-echoed\n // message gets seeded then immediately wiped, leaving only the `since`\n // WebSocket resend (the agent's reply). Re-entering the same conversation is\n // a no-op, so the store survives navigating away to Settings and back.\n useLayoutEffect(() => {\n const nextId = conversationId ?? null;\n if (useEventStore.getState().loadedConversationId === nextId) {\n return;\n }\n // Single atomic action: clears the previous conversation's events and\n // records the new loaded id in one `set`, so no subscriber can observe a\n // half-applied state (events gone but the old id still reported).\n clearEventsForConversation(nextId);\n resetBrowserStore();\n // The metrics store is conversation-scoped state too: without a reset the\n // previous conversation's usage/cost keeps rendering in the new\n // conversation's meter until fresh WS stats arrive — and a brand-new\n // conversation sends none, so the stale figure stuck indefinitely.\n useMetricsStore.getState().resetMetrics();\n }, [conversationId, clearEventsForConversation, resetBrowserStore]);\n\n useLayoutEffect(() => {\n if (!preloadedHistory || preloadedHistory.events.length === 0) {\n return;\n }\n addEvents(preloadedHistory.events);\n\n // The first user message of a cloud start-task conversation is persisted\n // server-side and reaches us via this REST preload, not over the WebSocket\n // (which subscribes with resend_mode='since' after the latest preloaded\n // timestamp). Consume any matching optimistic \"Sending…\" bubble here too —\n // mirroring the WS handler — so it doesn't linger as a duplicate of the echo.\n if (conversationId) {\n // Rebuild inline \"Switched to\" messages from the REST-preloaded history.\n // The live store writers (WS handler / user action) never see preloaded\n // events, so without this past model switches wouldn't render on reload.\n // Read the post-`addEvents` `uiEvents` (actions replaced by observations,\n // Think/Finish observations dropped) — not the raw history — so anchors\n // match the ids the renderer actually mounts.\n seedModelSwitchesFromHistory(\n conversationId,\n useEventStore.getState().uiEvents,\n );\n\n for (const event of preloadedHistory.events) {\n if (isUserMessageEvent(event)) {\n consumeMatchingPendingMessage(\n conversationId,\n extractMessageEventText(event),\n );\n }\n }\n }\n }, [\n preloadedHistory,\n addEvents,\n conversationId,\n consumeMatchingPendingMessage,\n ]);\n\n /**\n * Timestamp of the latest event we already have from REST. Used as\n * `after_timestamp` when opening the WebSocket so the server only resends\n * events strictly after this point. `null` until the first REST page lands\n * (the WS connection is gated on that — see `wsUrl` below). During\n * background refetches `preloadedHistory` keeps the last-known page, so the\n * anchor holds steady instead of flipping to null; reconnects read the\n * freshest value from the options ref at connect time.\n */\n const initialAfterTimestamp = useMemo<string | null>(() => {\n const events = preloadedHistory?.events ?? [];\n const latest = events[events.length - 1];\n if (!latest || !(\"timestamp\" in latest) || !latest.timestamp) return null;\n return latest.timestamp;\n }, [preloadedHistory]);\n\n // Build WebSocket URL from props.\n //\n // We deliberately wait for the FIRST history load (`isPending`: no data for\n // this query key yet) before opening the socket, so the WS subscription can\n // use `resend_mode='since'` with a meaningful `after_timestamp` instead of\n // falling back to `resend_mode='all'`. The gate is intentionally NOT on\n // `isFetching`: background refetches (e.g. the `refetchOnMount` fired when\n // returning to a conversation) must never tear a live socket down — on a\n // flaky link that caused a refetch → teardown → reconnect → refetch loop\n // that kept the conversation stuck at \"Connecting\" for minutes. Connecting\n // during a background refetch anchors `since` to the cached tail; the\n // overlap with the refetched page is deduped by the event store and the\n // `isDuplicateEvent` guards in the message handlers. A query-key reset\n // (backend swap / new session key) makes `isPending` true again, so a\n // genuine reset still re-gates. If the initial load errors, `isPending`\n // flips false and we fall through to connect with `resend_mode='all'` so\n // the user still sees live events.\n const wsUrl = useMemo(() => {\n if (!conversationId || !conversationUrl) {\n return null;\n }\n if (isPreloadingHistory) {\n return null;\n }\n return buildWebSocketUrl(conversationId, conversationUrl);\n }, [conversationId, conversationUrl, isPreloadingHistory]);\n\n const planningAgentWsUrl = useMemo(() => {\n if (!subConversations?.length) {\n return null;\n }\n\n // Currently, there is only one sub-conversation and it uses the planning agent.\n const planningAgentConversation = subConversations[0];\n\n if (\n !planningAgentConversation?.id ||\n !planningAgentConversation.conversation_url\n ) {\n return null;\n }\n\n return buildWebSocketUrl(\n planningAgentConversation.id,\n planningAgentConversation.conversation_url,\n );\n }, [subConversations]);\n\n // Merged connection state - reflects combined status of both connections\n const connectionState = useMemo<WebSocketConnectionState>(() => {\n // If planning agent connection doesn't exist, use main connection state\n if (!planningAgentWsUrl) {\n return mainConnectionState;\n }\n\n // If either is connecting, merged state is connecting\n if (\n mainConnectionState === \"CONNECTING\" ||\n planningConnectionState === \"CONNECTING\"\n ) {\n return \"CONNECTING\";\n }\n\n // If both are open, merged state is open\n if (mainConnectionState === \"OPEN\" && planningConnectionState === \"OPEN\") {\n return \"OPEN\";\n }\n\n // If both are closed, merged state is closed\n if (\n mainConnectionState === \"CLOSED\" &&\n planningConnectionState === \"CLOSED\"\n ) {\n return \"CLOSED\";\n }\n\n // If either is closing, merged state is closing\n if (\n mainConnectionState === \"CLOSING\" ||\n planningConnectionState === \"CLOSING\"\n ) {\n return \"CLOSING\";\n }\n\n // Default to closed if states don't match expected patterns\n return \"CLOSED\";\n }, [mainConnectionState, planningConnectionState, planningAgentWsUrl]);\n\n useEffect(() => {\n if (\n expectedEventCountPlanning !== null &&\n receivedEventCountRefPlanning.current >= expectedEventCountPlanning &&\n isLoadingHistoryPlanning\n ) {\n setIsLoadingHistoryPlanning(false);\n }\n }, [\n expectedEventCountPlanning,\n isLoadingHistoryPlanning,\n receivedEventCountRefPlanning,\n ]);\n\n // Call API once after history loading completes if we tracked any PlanningFileEditorObservation events\n useEffect(() => {\n if (!isLoadingHistoryPlanning && latestPlanningFileEventRef.current) {\n const { path, conversationId: currentPlanningConversationId } =\n latestPlanningFileEventRef.current;\n\n readConversationFile(\n {\n conversationId: currentPlanningConversationId,\n filePath: path,\n },\n {\n onSuccess: (fileContent) => {\n setPlanContent(fileContent);\n },\n onError: (error) => {\n console.warn(\"Failed to read conversation file:\", error);\n },\n },\n );\n\n // Clear the ref after calling the API\n latestPlanningFileEventRef.current = null;\n }\n }, [isLoadingHistoryPlanning, readConversationFile, setPlanContent]);\n\n useEffect(() => {\n hasConnectedRefMain.current = false;\n setIsLoadingHistoryPlanning(!!subConversationIds?.length);\n setExpectedEventCountPlanning(null);\n receivedEventCountRefPlanning.current = 0;\n // Reset the tracked event ref when sub-conversations change\n latestPlanningFileEventRef.current = null;\n }, [subConversationIds]);\n\n // Reset hasConnected flags when the conversation changes.\n useEffect(() => {\n hasConnectedRefMain.current = false;\n hasConnectedRefPlanning.current = false;\n // Reset the tracked event ref when conversation changes\n latestPlanningFileEventRef.current = null;\n }, [conversationId]);\n\n // Drop buffered deltas on conversation switch/unmount: the store is cleared on\n // switch, so flushing them would leak into the next conversation.\n useEffect(() => {\n const mainBatcher = mainDeltaBatcherRef.current;\n const planningBatcher = planningDeltaBatcherRef.current;\n return () => {\n mainBatcher?.reset();\n planningBatcher?.reset();\n };\n }, [conversationId]);\n\n // Merged loading history state - true if either connection is still loading\n const isLoadingHistory = useMemo(\n () => isLoadingHistoryMain || isLoadingHistoryPlanning,\n [isLoadingHistoryMain, isLoadingHistoryPlanning],\n );\n\n // Separate message handlers for each connection\n const handleMainMessage = useCallback(\n (messageEvent: MessageEvent) => {\n try {\n const event = JSON.parse(messageEvent.data);\n\n // History loading for the main conversation is REST-driven now;\n // every WS message is a new event we add to the store.\n\n // Use type guard to validate v1 event structure\n if (isAgentServerEvent(event)) {\n // Buffer deltas; nothing else in this handler applies to them.\n if (isStreamingDeltaEvent(event)) {\n mainDeltaBatcherRef.current?.enqueue(event);\n return;\n }\n // Flush buffered deltas before this event so it can't overtake them.\n mainDeltaBatcherRef.current?.flush();\n\n // A reconnect replays the backlog from a stale anchor. The store\n // dedups by id, but the side-effects below aren't idempotent, so skip\n // them for replayed events (#1656).\n const isDuplicateEvent = useEventStore\n .getState()\n .eventIds.has(event.id ?? \"\");\n const switchLLMObservation = isSwitchLLMObservationEvent(event)\n ? event\n : null;\n addEvent(event);\n if (isDuplicateEvent) {\n return;\n }\n\n // Handle displayable error events - show error banner\n // AgentErrorEvent errors are displayed inline in the chat, not as banners\n if (isDisplayableErrorEvent(event)) {\n const errorEvent = event as\n | ConversationErrorEvent\n | ServerErrorEvent;\n const classification =\n \"classification\" in errorEvent ? errorEvent.classification : null;\n trackError({\n source: \"conversation\",\n metadata: {\n eventId: errorEvent.id,\n errorCode: errorEvent.code,\n },\n classification,\n });\n setErrorMessage(\n errorEvent.detail,\n \"conversation\",\n errorEvent.code,\n classification,\n );\n } else {\n handleNonErrorEvent();\n }\n\n // LLM errors render inline in the chat (see ErrorEventMessage); track\n // them for analytics but keep them out of the banner above the chat box.\n if (isAgentErrorEvent(event)) {\n trackError({\n source: \"agent\",\n metadata: {\n eventId: event.id,\n toolName: event.tool_name,\n toolCallId: event.tool_call_id,\n },\n classification: event.classification,\n });\n }\n\n // Clear optimistic user message when a user message is confirmed.\n // We match by the echoed text content (with FIFO fallback inside the\n // store), so an echo for \"second\" pops \"second\" — not whichever\n // pending entry happens to be oldest — protecting against any\n // out-of-order delivery between conversations or sub-agents.\n if (isUserMessageEvent(event)) {\n if (conversationId) {\n consumeMatchingPendingMessage(\n conversationId,\n extractMessageEventText(event),\n );\n // Clear draft from localStorage - message was successfully delivered\n setConversationState(conversationId, { draftMessage: null });\n }\n }\n\n // Handle cache invalidation for ActionEvent\n if (isActionEvent(event)) {\n const currentConversationId =\n conversationId || \"test-conversation-id\"; // TODO: Get from context\n handleActionEventCacheInvalidation(\n event,\n currentConversationId,\n queryClient,\n );\n }\n\n // Handle conversation state updates\n // TODO: Tests\n if (isConversationStateUpdateEvent(event)) {\n if (isFullStateConversationStateUpdateEvent(event)) {\n setExecutionStatus(event.value.execution_status);\n }\n if (isAgentStatusConversationStateUpdateEvent(event)) {\n setExecutionStatus(event.value);\n }\n if (isStatsConversationStateUpdateEvent(event)) {\n updateMetricsFromStats(event);\n }\n // Mirror goal status into the store. Intentionally duplicated across\n // the main and planning WebSocket handlers (like the execution_status\n // and stats branches above), not a merge artifact.\n if (isGoalConversationStateUpdateEvent(event) && conversationId) {\n useGoalStore.getState().setStatus(conversationId, event.value);\n }\n }\n\n // Handle ExecuteBashAction events - add command as input to terminal\n if (isExecuteBashActionEvent(event)) {\n appendInput(event.action.command);\n }\n\n // Handle ExecuteBashObservation events - add output to terminal\n if (isExecuteBashObservationEvent(event)) {\n // Extract text content from the observation content array\n const textContent = event.observation.content\n .filter((c) => c.type === \"text\")\n .map((c) => c.text)\n .join(\"\\n\");\n appendOutput(textContent);\n }\n\n // Handle BrowserObservation events - update browser store with screenshot\n if (isBrowserObservationEvent(event)) {\n const { screenshot_data: screenshotData } = event.observation;\n if (screenshotData) {\n const screenshotSrc = screenshotData.startsWith(\"data:\")\n ? screenshotData\n : `data:image/png;base64,${screenshotData}`;\n useBrowserStore.getState().setScreenshotSrc(screenshotSrc);\n }\n }\n\n // Handle BrowserNavigateAction events - update browser store with URL\n if (isBrowserNavigateActionEvent(event)) {\n useBrowserStore.getState().setUrl(event.action.url);\n }\n\n if (\n conversationId &&\n switchLLMObservation &&\n !switchLLMObservation.observation.is_error\n ) {\n recordModelSwitchMessage(\n conversationId,\n switchLLMObservation.observation.profile_name,\n );\n\n // Mirror the user-driven `/model` path: persist the profile so the\n // chat-header switcher shows the right name after a reload, even\n // when several profiles share a model (#1082).\n const prevMetadata = getStoredConversationMetadata(conversationId);\n setStoredConversationMetadata(conversationId, {\n selected_repository: prevMetadata?.selected_repository ?? null,\n selected_branch: prevMetadata?.selected_branch ?? null,\n git_provider: prevMetadata?.git_provider ?? null,\n selected_workspace: prevMetadata?.selected_workspace ?? null,\n active_profile: switchLLMObservation.observation.profile_name,\n // Full-object replace: carry the plugins snapshot forward so the\n // in-conversation plugins view survives a profile switch.\n plugins: prevMetadata?.plugins ?? null,\n });\n\n if (switchLLMObservation.observation.active_model) {\n updateConversationLlmModelInCache(\n queryClient,\n conversationId,\n switchLLMObservation.observation.active_model,\n );\n }\n\n invalidateConversationQueries(queryClient, conversationId);\n }\n\n // Handle canvas_ui ActionEvents from both the legacy Python tool and\n // the client-defined JSON tool. The server acknowledges immediately;\n // the actual UI change happens here on the client.\n if (isCanvasUIActionEvent(event)) {\n handleCanvasUIAction(event.action, conversationId ?? null);\n }\n\n // Same client-tool pattern, but the work is a network call: launch\n // the requested child conversation and post the outcome back so the\n // agent learns the id the server-side acknowledgement can't carry.\n if (conversationId && isLaunchChildConversationActionEvent(event)) {\n void handleLaunchChildConversationAction(\n event.action,\n conversationId,\n event.tool_call_id,\n );\n }\n }\n } catch (error) {\n console.warn(\"Failed to parse WebSocket message as JSON:\", error);\n }\n },\n [\n addEvent,\n setErrorMessage,\n consumeMatchingPendingMessage,\n queryClient,\n conversationId,\n setExecutionStatus,\n appendInput,\n appendOutput,\n updateMetricsFromStats,\n handleNonErrorEvent,\n ],\n );\n\n const handlePlanningMessage = useCallback(\n (messageEvent: MessageEvent) => {\n try {\n const event = JSON.parse(messageEvent.data);\n\n // Track received events for history loading (count ALL events from WebSocket)\n // Always count when loading, even if we don't have the expected count yet\n if (isLoadingHistoryPlanning) {\n receivedEventCountRefPlanning.current += 1;\n\n if (\n expectedEventCountPlanning !== null &&\n receivedEventCountRefPlanning.current >= expectedEventCountPlanning\n ) {\n setIsLoadingHistoryPlanning(false);\n }\n }\n\n // Use type guard to validate v1 event structure\n if (isAgentServerEvent(event)) {\n // Buffer deltas (the commit re-applies the planning flag).\n if (isStreamingDeltaEvent(event)) {\n planningDeltaBatcherRef.current?.enqueue(event);\n return;\n }\n // Flush buffered deltas before this event so it can't overtake them.\n planningDeltaBatcherRef.current?.flush();\n\n // Skip non-idempotent side-effects for replayed events, as in the\n // main handler (#1656).\n const isDuplicateEvent = useEventStore\n .getState()\n .eventIds.has(event.id ?? \"\");\n // Mark this event as coming from the planning agent\n const eventWithPlanningFlag = {\n ...event,\n isFromPlanningAgent: true,\n };\n addEvent(eventWithPlanningFlag);\n if (isDuplicateEvent) {\n return;\n }\n\n // Handle displayable error events - show error banner\n // AgentErrorEvent errors are displayed inline in the chat, not as banners\n if (isDisplayableErrorEvent(event)) {\n const errorEvent = event as\n | ConversationErrorEvent\n | ServerErrorEvent;\n const classification =\n \"classification\" in errorEvent ? errorEvent.classification : null;\n trackError({\n source: \"planning_conversation\",\n metadata: {\n eventId: errorEvent.id,\n errorCode: errorEvent.code,\n },\n classification,\n });\n setErrorMessage(\n errorEvent.detail,\n \"conversation\",\n errorEvent.code,\n classification,\n );\n } else {\n handleNonErrorEvent();\n }\n\n // LLM errors render inline in the chat (see ErrorEventMessage); track\n // them for analytics but keep them out of the banner above the chat box.\n if (isAgentErrorEvent(event)) {\n trackError({\n source: \"planning_agent\",\n metadata: {\n eventId: event.id,\n toolName: event.tool_name,\n toolCallId: event.tool_call_id,\n },\n classification: event.classification,\n });\n }\n\n // Clear optimistic user message when a user message is confirmed.\n // Always scope to the main `conversationId` (where the user types)\n // and match on the echoed content so the planning sub-agent's own\n // events can never consume a main-conversation pending entry.\n if (isUserMessageEvent(event)) {\n if (conversationId) {\n consumeMatchingPendingMessage(\n conversationId,\n extractMessageEventText(event),\n );\n setConversationState(conversationId, { draftMessage: null });\n }\n }\n\n // Handle cache invalidation for ActionEvent\n if (isActionEvent(event)) {\n const planningAgentConversation = subConversations?.[0];\n const currentConversationId =\n planningAgentConversation?.id || \"test-conversation-id\"; // TODO: Get from context\n handleActionEventCacheInvalidation(\n event,\n currentConversationId,\n queryClient,\n );\n }\n\n // Handle conversation state updates\n // TODO: Tests\n if (isConversationStateUpdateEvent(event)) {\n if (isFullStateConversationStateUpdateEvent(event)) {\n setExecutionStatus(event.value.execution_status);\n }\n if (isAgentStatusConversationStateUpdateEvent(event)) {\n setExecutionStatus(event.value);\n }\n if (isStatsConversationStateUpdateEvent(event)) {\n updateMetricsFromStats(event);\n }\n // Mirror goal status into the store. Intentionally duplicated across\n // the main and planning WebSocket handlers (like the execution_status\n // and stats branches above), not a merge artifact.\n if (isGoalConversationStateUpdateEvent(event) && conversationId) {\n useGoalStore.getState().setStatus(conversationId, event.value);\n }\n }\n\n // Handle ExecuteBashAction events - add command as input to terminal\n if (isExecuteBashActionEvent(event)) {\n appendInput(event.action.command);\n }\n\n // Handle ExecuteBashObservation events - add output to terminal\n if (isExecuteBashObservationEvent(event)) {\n // Extract text content from the observation content array\n const textContent = event.observation.content\n .filter((c) => c.type === \"text\")\n .map((c) => c.text)\n .join(\"\\n\");\n appendOutput(textContent);\n }\n\n // Handle PlanningFileEditorObservation - only update plan for Plan.md\n if (isPlanningFileEditorObservationEvent(event)) {\n const { path } = event.observation;\n if (isPlanFilePath(path)) {\n const planningAgentConversation = subConversations?.[0];\n const planningConversationId = planningAgentConversation?.id;\n\n if (planningConversationId && path) {\n if (isLoadingHistoryPlanning) {\n latestPlanningFileEventRef.current = {\n path,\n conversationId: planningConversationId,\n };\n } else {\n readConversationFile(\n {\n conversationId: planningConversationId,\n filePath: path,\n },\n {\n onSuccess: (fileContent) => {\n setPlanContent(fileContent);\n },\n onError: (error) => {\n console.warn(\n \"Failed to read conversation file:\",\n error,\n );\n },\n },\n );\n }\n }\n }\n }\n }\n } catch (error) {\n console.warn(\"Failed to parse WebSocket message as JSON:\", error);\n }\n },\n [\n addEvent,\n isLoadingHistoryPlanning,\n expectedEventCountPlanning,\n setErrorMessage,\n consumeMatchingPendingMessage,\n queryClient,\n subConversations,\n conversationId,\n setExecutionStatus,\n appendInput,\n appendOutput,\n readConversationFile,\n setPlanContent,\n updateMetricsFromStats,\n handleNonErrorEvent,\n ],\n );\n\n // Separate WebSocket options for main connection\n const mainWebsocketOptions: WebSocketHookOptions = useMemo(() => {\n // History was already loaded over REST (`useConversationHistory`).\n // Subscribe with `resend_mode='since'` so the server only resends events\n // strictly after the latest one we already have. If REST returned no\n // events at all (brand-new conversation), fall back to `'all'` so any\n // events that may have been written between the REST call and the WS\n // handshake still show up. Dedup in the event store handles overlap.\n const queryParams: Record<string, string | boolean> = initialAfterTimestamp\n ? { resend_mode: \"since\", after_timestamp: initialAfterTimestamp }\n : { resend_mode: \"all\" };\n\n return {\n queryParams,\n sessionApiKey,\n reconnect: { enabled: true },\n onOpen: () => {\n setMainConnectionState(\"OPEN\");\n hasConnectedRefMain.current = true; // Mark that we've successfully connected\n clearConnectionError(); // Clear a previous connection error; keep sticky conversation errors\n },\n onClose: () => {\n setMainConnectionState(\"CLOSED\");\n },\n onError: () => {\n setMainConnectionState(\"CLOSED\");\n // Only show error message if we've previously connected successfully\n if (hasConnectedRefMain.current) {\n setErrorMessage(SERVER_CONNECTION_ERROR_MESSAGE, \"connection\");\n }\n },\n onMessage: handleMainMessage,\n };\n }, [\n handleMainMessage,\n setErrorMessage,\n clearConnectionError,\n sessionApiKey,\n initialAfterTimestamp,\n ]);\n\n // Separate WebSocket options for planning agent connection\n const planningWebsocketOptions: WebSocketHookOptions = useMemo(() => {\n const queryParams: Record<string, string | boolean> = {\n resend_all: true,\n };\n\n const planningAgentConversation = subConversations?.[0];\n const planningApiKey =\n planningAgentConversation?.session_api_key ?? sessionApiKey;\n\n return {\n queryParams,\n sessionApiKey: planningApiKey,\n reconnect: { enabled: true },\n onOpen: async () => {\n setPlanningConnectionState(\"OPEN\");\n hasConnectedRefPlanning.current = true; // Mark that we've successfully connected\n clearConnectionError(); // Clear a previous connection error; keep sticky conversation errors\n\n // Fetch expected event count for history loading detection\n if (\n planningAgentConversation?.id &&\n planningAgentConversation.conversation_url\n ) {\n try {\n const count = await EventService.getEventCount(\n planningAgentConversation.id,\n planningAgentConversation.conversation_url,\n planningAgentConversation.session_api_key,\n );\n setExpectedEventCountPlanning(count);\n\n // If no events expected, mark as loaded immediately\n if (count === 0) {\n setIsLoadingHistoryPlanning(false);\n }\n } catch (error) {\n // Fall back to marking as loaded to avoid infinite loading state\n setIsLoadingHistoryPlanning(false);\n }\n }\n },\n onClose: () => {\n setPlanningConnectionState(\"CLOSED\");\n },\n onError: () => {\n setPlanningConnectionState(\"CLOSED\");\n // Only show error message if we've previously connected successfully\n if (hasConnectedRefPlanning.current) {\n setErrorMessage(SERVER_CONNECTION_ERROR_MESSAGE, \"connection\");\n }\n },\n onMessage: handlePlanningMessage,\n };\n }, [\n handlePlanningMessage,\n setErrorMessage,\n clearConnectionError,\n sessionApiKey,\n subConversations,\n ]);\n\n // Only attempt WebSocket connection when we have a valid URL\n // This prevents connection attempts during task polling phase\n const websocketUrl = wsUrl;\n const { socket: mainSocket, reconnect: reconnectMain } = useWebSocket(\n websocketUrl || \"\",\n mainWebsocketOptions,\n );\n\n const { socket: planningAgentSocket, reconnect: reconnectPlanning } =\n useWebSocket(planningAgentWsUrl || \"\", planningWebsocketOptions);\n\n const reconnect = useCallback(() => {\n removeErrorMessage();\n const currentMode = useConversationStore.getState().conversationMode;\n if (currentMode === \"plan\" && planningAgentWsUrl) {\n reconnectPlanning();\n return;\n }\n reconnectMain();\n }, [\n planningAgentWsUrl,\n reconnectMain,\n reconnectPlanning,\n removeErrorMessage,\n ]);\n\n // V1 send message function via WebSocket\n // Falls back to REST API queue when WebSocket is not connected\n const sendMessage = useCallback(\n async (message: SendMessageRequest): Promise<SendMessageResult> => {\n const currentMode = useConversationStore.getState().conversationMode;\n const currentSocket =\n currentMode === \"plan\" ? planningAgentSocket : mainSocket;\n\n if (currentSocket?.readyState !== WebSocket.OPEN) {\n // WebSocket not connected - queue message via REST API\n // Message will be delivered automatically when conversation becomes ready\n if (!conversationId) {\n const error = new Error(\"No conversation ID available\");\n setErrorMessage(error.message);\n throw error;\n }\n\n try {\n await new ConversationClient(getAgentServerClientOptions()).sendEvent(\n conversationId,\n {\n role: \"user\",\n content: message.content,\n },\n { run: true },\n );\n // Message queued successfully - it will be delivered when ready\n // Return queued: true so caller knows not to show optimistic UI\n return { queued: true };\n } catch (error) {\n const errorMessage =\n error instanceof Error\n ? error.message\n : \"Failed to queue message for delivery\";\n setErrorMessage(errorMessage);\n throw error;\n }\n }\n\n try {\n // Send message through WebSocket as JSON with run: true so the\n // agent loop starts automatically in async mode.\n currentSocket.send(JSON.stringify({ ...message, run: true }));\n return { queued: false };\n } catch (error) {\n const errorMessage =\n error instanceof Error ? error.message : \"Failed to send message\";\n setErrorMessage(errorMessage);\n throw error;\n }\n },\n [mainSocket, planningAgentSocket, setErrorMessage, conversationId],\n );\n\n // Track main socket state changes\n useEffect(() => {\n // Only process socket updates if we have a valid URL and socket\n if (mainSocket && wsUrl) {\n // Update state based on socket readyState\n const updateState = () => {\n switch (mainSocket.readyState) {\n case WebSocket.CONNECTING:\n setMainConnectionState(\"CONNECTING\");\n break;\n case WebSocket.OPEN:\n setMainConnectionState(\"OPEN\");\n break;\n case WebSocket.CLOSING:\n setMainConnectionState(\"CLOSING\");\n break;\n case WebSocket.CLOSED:\n setMainConnectionState(\"CLOSED\");\n break;\n default:\n setMainConnectionState(\"CLOSED\");\n break;\n }\n };\n\n updateState();\n }\n }, [mainSocket, wsUrl]);\n\n // Track planning agent socket state changes\n useEffect(() => {\n // Only process socket updates if we have a valid URL and socket\n if (planningAgentSocket && planningAgentWsUrl) {\n // Update state based on socket readyState\n const updateState = () => {\n switch (planningAgentSocket.readyState) {\n case WebSocket.CONNECTING:\n setPlanningConnectionState(\"CONNECTING\");\n break;\n case WebSocket.OPEN:\n setPlanningConnectionState(\"OPEN\");\n break;\n case WebSocket.CLOSING:\n setPlanningConnectionState(\"CLOSING\");\n break;\n case WebSocket.CLOSED:\n setPlanningConnectionState(\"CLOSED\");\n break;\n default:\n setPlanningConnectionState(\"CLOSED\");\n break;\n }\n };\n\n updateState();\n }\n }, [planningAgentSocket, planningAgentWsUrl]);\n\n const contextValue = useMemo(\n () => ({ connectionState, sendMessage, isLoadingHistory, reconnect }),\n [connectionState, sendMessage, isLoadingHistory, reconnect],\n );\n\n return (\n <ConversationWebSocketContext.Provider value={contextValue}>\n {children}\n </ConversationWebSocketContext.Provider>\n );\n}\n\nexport const useConversationWebSocket =\n (): ConversationWebSocketContextType | null => {\n const context = useContext(ConversationWebSocketContext);\n // Return null instead of throwing when not in provider\n // This allows the hook to be called conditionally based on conversation version\n return context || null;\n };\n"],"mappings":"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"}
@@ -16,8 +16,8 @@ import { useErrorMessageStore as u } from "../stores/error-message-store.js";
16
16
  import { useOptimisticUserMessageStore as xe } from "../stores/optimistic-user-message-store.js";
17
17
  import { useGoalStore as Se } from "../stores/goal-store.js";
18
18
  import { createStreamingDeltaBatcher as Ce } from "../utils/streaming-delta-batcher.js";
19
- import { handleCanvasUIAction as we } from "../services/canvas-ui.js";
20
- import { getStoredConversationMetadata as Te, setStoredConversationMetadata as Ee } from "../api/conversation-metadata-store.js";
19
+ import { getStoredConversationMetadata as we, setStoredConversationMetadata as Te } from "../api/conversation-metadata-store.js";
20
+ import { handleCanvasUIAction as Ee } from "../services/canvas-ui.js";
21
21
  import { handleLaunchChildConversationAction as De } from "../services/child-conversation-launch.js";
22
22
  import { handleActionEventCacheInvalidation as Oe } from "../utils/cache-utils.js";
23
23
  import ke from "../api/event-service/event-service.api.js";
@@ -181,8 +181,8 @@ function y({ children: Re, conversationId: p, conversationUrl: y, sessionApiKey:
181
181
  }
182
182
  if (se(r) && e.getState().setUrl(r.action.url), p && i && !i.observation.is_error) {
183
183
  Ne(p, i.observation.profile_name);
184
- let e = Te(p);
185
- Ee(p, {
184
+ let e = we(p);
185
+ Te(p, {
186
186
  selected_repository: e?.selected_repository ?? null,
187
187
  selected_branch: e?.selected_branch ?? null,
188
188
  git_provider: e?.git_provider ?? null,
@@ -191,7 +191,7 @@ function y({ children: Re, conversationId: p, conversationUrl: y, sessionApiKey:
191
191
  plugins: e?.plugins ?? null
192
192
  }), i.observation.active_model && Ie(O, p, i.observation.active_model), Fe(O, p);
193
193
  }
194
- le(r) && we(r.action, p ?? null), p && me(r) && De(r.action, p, r.tool_call_id);
194
+ le(r) && Ee(r.action, p ?? null), p && me(r) && De(r.action, p, r.tool_call_id);
195
195
  }
196
196
  } catch (e) {
197
197
  console.warn("Failed to parse WebSocket message as JSON:", e);
@@ -0,0 +1,11 @@
1
+ import type { CanvasExtensionModule } from "#/types/canvas-extension";
2
+ export declare class InvalidCanvasExtensionModuleError extends Error {
3
+ constructor(message: string);
4
+ }
5
+ export declare function assertCanvasExtensionModule(value: unknown): asserts value is CanvasExtensionModule;
6
+ /**
7
+ * Import an authenticated, self-contained ESM bundle without putting the
8
+ * Agent Server session key in a URL. The source has already been fetched by
9
+ * Canvas through its authenticated HTTP client.
10
+ */
11
+ export declare function loadCanvasExtensionModule(source: string): Promise<CanvasExtensionModule>;
@@ -0,0 +1,12 @@
1
+ import type { InstallCanvasExtensionRequest } from "#/types/canvas-extension";
2
+ export declare function useInstallCanvasExtension(): import("@tanstack/react-query").UseMutationResult<import("#/types/canvas-extension").InstalledCanvasExtensionInfo, import("axios").AxiosError<unknown, any>, InstallCanvasExtensionRequest, unknown>;
3
+ export declare function useSetCanvasExtensionEnabled(): import("@tanstack/react-query").UseMutationResult<{
4
+ name: string;
5
+ enabled: boolean;
6
+ }, import("axios").AxiosError<unknown, any>, {
7
+ name: string;
8
+ enabled: boolean;
9
+ }, unknown>;
10
+ export declare function useUninstallCanvasExtension(): import("@tanstack/react-query").UseMutationResult<{
11
+ message: string;
12
+ }, import("axios").AxiosError<unknown, any>, string, unknown>;
@@ -0,0 +1,2 @@
1
+ require("../../_virtual/_rolldown/runtime.cjs"),new class{limit;active=0;queue=[];constructor(e){this.limit=e}async run(e,t){await this.acquire();try{if(t?.aborted)throw t.reason??new DOMException(`Aborted`,`AbortError`);return await e()}finally{this.release()}}acquire(){return this.active<this.limit?(this.active+=1,Promise.resolve()):new Promise(e=>{this.queue.push(e)})}release(){let e=this.queue.shift();e?e():--this.active}}(3);
2
+ //# sourceMappingURL=concurrency-limiter.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"concurrency-limiter.cjs","names":[],"sources":["../../../src/hooks/query/concurrency-limiter.ts"],"sourcesContent":["/**\n * A FIFO promise semaphore: at most `limit` wrapped tasks run at once; the\n * rest wait in arrival order. Wrap a queryFn's work in `run` to bound how many\n * of a query family's requests are in flight simultaneously.\n */\nexport class ConcurrencyLimiter {\n private active = 0;\n\n private readonly queue: Array<() => void> = [];\n\n constructor(private readonly limit: number) {}\n\n async run<T>(task: () => Promise<T>, signal?: AbortSignal): Promise<T> {\n await this.acquire();\n try {\n // A query cancelled while queued (e.g. its last observer unmounted)\n // frees its slot without ever hitting the network.\n if (signal?.aborted) {\n throw signal.reason ?? new DOMException(\"Aborted\", \"AbortError\");\n }\n return await task();\n } finally {\n this.release();\n }\n }\n\n private acquire(): Promise<void> {\n if (this.active < this.limit) {\n this.active += 1;\n return Promise.resolve();\n }\n return new Promise((resolve) => {\n this.queue.push(resolve);\n });\n }\n\n private release(): void {\n const next = this.queue.shift();\n if (next) {\n // The waiter inherits the released slot directly, so `active` stays put.\n next();\n } else {\n this.active -= 1;\n }\n }\n}\n\n/**\n * Shared by every per-automation runs query: the dashboard and home page each\n * fan out one GET .../runs request per automation, which can exhaust the\n * automation service's DB connection pool (~15 connections) and stall every\n * request for the pool's 30s acquisition timeout. Three in flight keeps the\n * surfaces responsive without saturating the pool.\n */\nexport const automationRunRequestsLimiter = new ConcurrencyLimiter(3);\n"],"mappings":"gDAsD4C,IAAI,KAjDhB,CAKD,MAJ7B,OAAiB,EAEjB,MAA4C,CAAC,EAE7C,YAAY,EAAgC,CAAf,KAAA,MAAA,CAAgB,CAE7C,MAAM,IAAO,EAAwB,EAAkC,CACrE,MAAM,KAAK,QAAQ,EACnB,GAAI,CAGF,GAAI,GAAQ,QACV,MAAM,EAAO,QAAU,IAAI,aAAa,UAAW,YAAY,EAEjE,OAAO,MAAM,EAAK,CACpB,QAAU,CACR,KAAK,QAAQ,CACf,CACF,CAEA,SAAiC,CAK/B,OAJI,KAAK,OAAS,KAAK,OACrB,KAAK,QAAU,EACR,QAAQ,QAAQ,GAElB,IAAI,QAAS,GAAY,CAC9B,KAAK,MAAM,KAAK,CAAO,CACzB,CAAC,CACH,CAEA,SAAwB,CACtB,IAAM,EAAO,KAAK,MAAM,MAAM,EAC1B,EAEF,EAAK,EAEL,OAAK,MAET,CACF,EASmE,CAAC"}
@@ -0,0 +1,22 @@
1
+ /**
2
+ * A FIFO promise semaphore: at most `limit` wrapped tasks run at once; the
3
+ * rest wait in arrival order. Wrap a queryFn's work in `run` to bound how many
4
+ * of a query family's requests are in flight simultaneously.
5
+ */
6
+ export declare class ConcurrencyLimiter {
7
+ private readonly limit;
8
+ private active;
9
+ private readonly queue;
10
+ constructor(limit: number);
11
+ run<T>(task: () => Promise<T>, signal?: AbortSignal): Promise<T>;
12
+ private acquire;
13
+ private release;
14
+ }
15
+ /**
16
+ * Shared by every per-automation runs query: the dashboard and home page each
17
+ * fan out one GET .../runs request per automation, which can exhaust the
18
+ * automation service's DB connection pool (~15 connections) and stall every
19
+ * request for the pool's 30s acquisition timeout. Three in flight keeps the
20
+ * surfaces responsive without saturating the pool.
21
+ */
22
+ export declare const automationRunRequestsLimiter: ConcurrencyLimiter;
@@ -0,0 +1,29 @@
1
+ new class {
2
+ limit;
3
+ active = 0;
4
+ queue = [];
5
+ constructor(e) {
6
+ this.limit = e;
7
+ }
8
+ async run(e, t) {
9
+ await this.acquire();
10
+ try {
11
+ if (t?.aborted) throw t.reason ?? new DOMException("Aborted", "AbortError");
12
+ return await e();
13
+ } finally {
14
+ this.release();
15
+ }
16
+ }
17
+ acquire() {
18
+ return this.active < this.limit ? (this.active += 1, Promise.resolve()) : new Promise((e) => {
19
+ this.queue.push(e);
20
+ });
21
+ }
22
+ release() {
23
+ let e = this.queue.shift();
24
+ e ? e() : --this.active;
25
+ }
26
+ }(3);
27
+ //#endregion
28
+
29
+ //# sourceMappingURL=concurrency-limiter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"concurrency-limiter.js","names":[],"sources":["../../../src/hooks/query/concurrency-limiter.ts"],"sourcesContent":["/**\n * A FIFO promise semaphore: at most `limit` wrapped tasks run at once; the\n * rest wait in arrival order. Wrap a queryFn's work in `run` to bound how many\n * of a query family's requests are in flight simultaneously.\n */\nexport class ConcurrencyLimiter {\n private active = 0;\n\n private readonly queue: Array<() => void> = [];\n\n constructor(private readonly limit: number) {}\n\n async run<T>(task: () => Promise<T>, signal?: AbortSignal): Promise<T> {\n await this.acquire();\n try {\n // A query cancelled while queued (e.g. its last observer unmounted)\n // frees its slot without ever hitting the network.\n if (signal?.aborted) {\n throw signal.reason ?? new DOMException(\"Aborted\", \"AbortError\");\n }\n return await task();\n } finally {\n this.release();\n }\n }\n\n private acquire(): Promise<void> {\n if (this.active < this.limit) {\n this.active += 1;\n return Promise.resolve();\n }\n return new Promise((resolve) => {\n this.queue.push(resolve);\n });\n }\n\n private release(): void {\n const next = this.queue.shift();\n if (next) {\n // The waiter inherits the released slot directly, so `active` stays put.\n next();\n } else {\n this.active -= 1;\n }\n }\n}\n\n/**\n * Shared by every per-automation runs query: the dashboard and home page each\n * fan out one GET .../runs request per automation, which can exhaust the\n * automation service's DB connection pool (~15 connections) and stall every\n * request for the pool's 30s acquisition timeout. Three in flight keeps the\n * surfaces responsive without saturating the pool.\n */\nexport const automationRunRequestsLimiter = new ConcurrencyLimiter(3);\n"],"mappings":"AAsD4C,IAAI,MAjDhB;CAKD;CAJ7B,SAAiB;CAEjB,QAA4C,CAAC;CAE7C,YAAY,GAAgC;EAAf,KAAA,QAAA;CAAgB;CAE7C,MAAM,IAAO,GAAwB,GAAkC;EACrE,MAAM,KAAK,QAAQ;EACnB,IAAI;GAGF,IAAI,GAAQ,SACV,MAAM,EAAO,UAAU,IAAI,aAAa,WAAW,YAAY;GAEjE,OAAO,MAAM,EAAK;EACpB,UAAU;GACR,KAAK,QAAQ;EACf;CACF;CAEA,UAAiC;EAK/B,OAJI,KAAK,SAAS,KAAK,SACrB,KAAK,UAAU,GACR,QAAQ,QAAQ,KAElB,IAAI,SAAS,MAAY;GAC9B,KAAK,MAAM,KAAK,CAAO;EACzB,CAAC;CACH;CAEA,UAAwB;EACtB,IAAM,IAAO,KAAK,MAAM,MAAM;EAC9B,AAAI,IAEF,EAAK,IAEL,OAAK;CAET;AACF,EASmE,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"query-keys.cjs","names":[],"sources":["../../../src/hooks/query/query-keys.ts"],"sourcesContent":["/**\n * Centralized query keys and cache configuration for TanStack Query.\n * Using constants ensures type safety and prevents typos.\n */\n\nimport { SettingsScope } from \"#/types/settings\";\n\nexport const QUERY_KEYS = {\n /** Web client configuration from the server */\n WEB_CLIENT_CONFIG: [\"web-client-config\"] as const,\n /** Same-origin OpenHands app cookie authentication status */\n MAIN_APP_COOKIE_AUTH: [\"main-app-cookie-auth\"] as const,\n} as const;\n\nexport const SETTINGS_QUERY_KEYS = {\n all: [\"settings\"] as const,\n byScope: (scope: SettingsScope) => [\"settings\", scope] as const,\n personal: () => [\"settings\", \"personal\"] as const,\n} as const;\n\nexport const LLM_PROFILES_QUERY_KEYS = {\n all: [\"llm-profiles\"] as const,\n} as const;\n\nexport const AGENT_PROFILES_QUERY_KEYS = {\n all: [\"agent-profiles\"] as const,\n} as const;\n\nexport const PROVIDER_CONNECTIONS_QUERY_KEYS = {\n all: [\"provider-connections\"] as const,\n} as const;\n\n/** Fail fast when older backends lack the profile endpoint. */\nexport const AGENT_PROFILES_RETRY_OPTIONS = {\n retry: false,\n} as const;\n\nexport const LLM_SUBSCRIPTION_QUERY_KEYS = {\n all: [\"llm-subscription\"] as const,\n openaiStatus: [\"llm-subscription\", \"openai\", \"status\"] as const,\n openaiModels: [\"llm-subscription\", \"openai\", \"models\"] as const,\n} as const;\n\nexport const LOCAL_WORKSPACES_QUERY_KEYS = {\n all: [\"local-workspaces\"] as const,\n} as const;\n\nexport const PLUGINS_QUERY_KEYS = {\n /** Dynamic marketplace catalog (used by `use-plugins-marketplace`). */\n marketplace: [\"plugins-marketplace\"] as const,\n /** Installed plugins from the local agent-server. */\n installed: [\"plugins-installed\"] as const,\n /** Locally-discovered ambient plugins (used by `use-local-plugins`). */\n local: [\"plugins-local\"] as const,\n} as const;\n\nexport const SETUP_QUERY_KEYS = {\n /** What the deployment supports. The same answer for every setup entry. */\n capabilities: () => [\"setup-capabilities\"] as const,\n} as const;\n\nexport const APP_UPDATE_QUERY_KEYS = {\n /** Latest published @openhands/agent-canvas version (npm `latest` dist-tag). */\n latestVersion: [\"agent-canvas-latest-version\"] as const,\n} as const;\n\n/** Cache configuration shared across all config-related queries */\nexport const CONFIG_CACHE_OPTIONS = {\n staleTime: 1000 * 60 * 5, // 5 minutes\n gcTime: 1000 * 60 * 15, // 15 minutes\n} as const;\n\nexport type QueryKeys = (typeof QUERY_KEYS)[keyof typeof QUERY_KEYS];\n"],"mappings":"gDAOA,IAAa,EAAa,CAExB,kBAAmB,CAAC,mBAAmB,EAEvC,qBAAsB,CAAC,sBAAsB,CAC/C,EAEa,EAAsB,CACjC,IAAK,CAAC,UAAU,EAChB,QAAU,GAAyB,CAAC,WAAY,CAAK,EACrD,aAAgB,CAAC,WAAY,UAAU,CACzC,EAEa,EAA0B,CACrC,IAAK,CAAC,cAAc,CACtB,EAEa,EAA4B,CACvC,IAAK,CAAC,gBAAgB,CACxB,EAEa,EAAkC,CAC7C,IAAK,CAAC,sBAAsB,CAC9B,EAGa,EAA+B,CAC1C,MAAO,EACT,EAEa,EAA8B,CACzC,IAAK,CAAC,kBAAkB,EACxB,aAAc,CAAC,mBAAoB,SAAU,QAAQ,EACrD,aAAc,CAAC,mBAAoB,SAAU,QAAQ,CACvD,EAEa,EAA8B,CACzC,IAAK,CAAC,kBAAkB,CAC1B,EAEa,EAAqB,CAEhC,YAAa,CAAC,qBAAqB,EAEnC,UAAW,CAAC,mBAAmB,EAE/B,MAAO,CAAC,eAAe,CACzB,EAOa,EAAwB,CAEnC,cAAe,CAAC,6BAA6B,CAC/C,EAGa,EAAuB,CAClC,UAAW,IAAO,GAAK,EACvB,OAAQ,IAAO,GAAK,EACtB"}
1
+ {"version":3,"file":"query-keys.cjs","names":[],"sources":["../../../src/hooks/query/query-keys.ts"],"sourcesContent":["/**\n * Centralized query keys and cache configuration for TanStack Query.\n * Using constants ensures type safety and prevents typos.\n */\n\nimport { SettingsScope } from \"#/types/settings\";\n\nexport const QUERY_KEYS = {\n /** Web client configuration from the server */\n WEB_CLIENT_CONFIG: [\"web-client-config\"] as const,\n /** Same-origin OpenHands app cookie authentication status */\n MAIN_APP_COOKIE_AUTH: [\"main-app-cookie-auth\"] as const,\n} as const;\n\nexport const SETTINGS_QUERY_KEYS = {\n all: [\"settings\"] as const,\n byScope: (scope: SettingsScope) => [\"settings\", scope] as const,\n personal: () => [\"settings\", \"personal\"] as const,\n} as const;\n\nexport const LLM_PROFILES_QUERY_KEYS = {\n all: [\"llm-profiles\"] as const,\n} as const;\n\nexport const AGENT_PROFILES_QUERY_KEYS = {\n all: [\"agent-profiles\"] as const,\n} as const;\n\nexport const PROVIDER_CONNECTIONS_QUERY_KEYS = {\n all: [\"provider-connections\"] as const,\n} as const;\n\n/** Fail fast when older backends lack the profile endpoint. */\nexport const AGENT_PROFILES_RETRY_OPTIONS = {\n retry: false,\n} as const;\n\nexport const LLM_SUBSCRIPTION_QUERY_KEYS = {\n all: [\"llm-subscription\"] as const,\n openaiStatus: [\"llm-subscription\", \"openai\", \"status\"] as const,\n openaiModels: [\"llm-subscription\", \"openai\", \"models\"] as const,\n} as const;\n\nexport const LOCAL_WORKSPACES_QUERY_KEYS = {\n all: [\"local-workspaces\"] as const,\n} as const;\n\nexport const PLUGINS_QUERY_KEYS = {\n /** Dynamic marketplace catalog (used by `use-plugins-marketplace`). */\n marketplace: [\"plugins-marketplace\"] as const,\n /** Installed plugins from the local agent-server. */\n installed: [\"plugins-installed\"] as const,\n /** Locally-discovered ambient plugins (used by `use-local-plugins`). */\n local: [\"plugins-local\"] as const,\n} as const;\n\nexport const CANVAS_EXTENSIONS_QUERY_KEYS = {\n all: [\"canvas-extensions\"] as const,\n installed: (\n backendId: string,\n orgId: string | null,\n connectionRevision: number,\n ) =>\n [\n \"canvas-extensions\",\n \"installed\",\n backendId,\n orgId,\n connectionRevision,\n ] as const,\n} as const;\n\nexport const SETUP_QUERY_KEYS = {\n /** What the deployment supports. The same answer for every setup entry. */\n capabilities: () => [\"setup-capabilities\"] as const,\n} as const;\n\nexport const APP_UPDATE_QUERY_KEYS = {\n /** Latest published @openhands/agent-canvas version (npm `latest` dist-tag). */\n latestVersion: [\"agent-canvas-latest-version\"] as const,\n} as const;\n\n/** Cache configuration shared across all config-related queries */\nexport const CONFIG_CACHE_OPTIONS = {\n staleTime: 1000 * 60 * 5, // 5 minutes\n gcTime: 1000 * 60 * 15, // 15 minutes\n} as const;\n\nexport type QueryKeys = (typeof QUERY_KEYS)[keyof typeof QUERY_KEYS];\n"],"mappings":"gDAOA,IAAa,EAAa,CAExB,kBAAmB,CAAC,mBAAmB,EAEvC,qBAAsB,CAAC,sBAAsB,CAC/C,EAEa,EAAsB,CACjC,IAAK,CAAC,UAAU,EAChB,QAAU,GAAyB,CAAC,WAAY,CAAK,EACrD,aAAgB,CAAC,WAAY,UAAU,CACzC,EAEa,EAA0B,CACrC,IAAK,CAAC,cAAc,CACtB,EAEa,EAA4B,CACvC,IAAK,CAAC,gBAAgB,CACxB,EAEa,EAAkC,CAC7C,IAAK,CAAC,sBAAsB,CAC9B,EAGa,EAA+B,CAC1C,MAAO,EACT,EAEa,EAA8B,CACzC,IAAK,CAAC,kBAAkB,EACxB,aAAc,CAAC,mBAAoB,SAAU,QAAQ,EACrD,aAAc,CAAC,mBAAoB,SAAU,QAAQ,CACvD,EAEa,EAA8B,CACzC,IAAK,CAAC,kBAAkB,CAC1B,EAEa,EAAqB,CAEhC,YAAa,CAAC,qBAAqB,EAEnC,UAAW,CAAC,mBAAmB,EAE/B,MAAO,CAAC,eAAe,CACzB,EAuBa,EAAwB,CAEnC,cAAe,CAAC,6BAA6B,CAC/C,EAGa,EAAuB,CAClC,UAAW,IAAO,GAAK,EACvB,OAAQ,IAAO,GAAK,EACtB"}
@@ -43,6 +43,10 @@ export declare const PLUGINS_QUERY_KEYS: {
43
43
  /** Locally-discovered ambient plugins (used by `use-local-plugins`). */
44
44
  readonly local: readonly ["plugins-local"];
45
45
  };
46
+ export declare const CANVAS_EXTENSIONS_QUERY_KEYS: {
47
+ readonly all: readonly ["canvas-extensions"];
48
+ readonly installed: (backendId: string, orgId: string | null, connectionRevision: number) => readonly ["canvas-extensions", "installed", string, string | null, number];
49
+ };
46
50
  export declare const SETUP_QUERY_KEYS: {
47
51
  /** What the deployment supports. The same answer for every setup entry. */
48
52
  readonly capabilities: () => readonly ["setup-capabilities"];
@@ -1 +1 @@
1
- {"version":3,"file":"query-keys.js","names":[],"sources":["../../../src/hooks/query/query-keys.ts"],"sourcesContent":["/**\n * Centralized query keys and cache configuration for TanStack Query.\n * Using constants ensures type safety and prevents typos.\n */\n\nimport { SettingsScope } from \"#/types/settings\";\n\nexport const QUERY_KEYS = {\n /** Web client configuration from the server */\n WEB_CLIENT_CONFIG: [\"web-client-config\"] as const,\n /** Same-origin OpenHands app cookie authentication status */\n MAIN_APP_COOKIE_AUTH: [\"main-app-cookie-auth\"] as const,\n} as const;\n\nexport const SETTINGS_QUERY_KEYS = {\n all: [\"settings\"] as const,\n byScope: (scope: SettingsScope) => [\"settings\", scope] as const,\n personal: () => [\"settings\", \"personal\"] as const,\n} as const;\n\nexport const LLM_PROFILES_QUERY_KEYS = {\n all: [\"llm-profiles\"] as const,\n} as const;\n\nexport const AGENT_PROFILES_QUERY_KEYS = {\n all: [\"agent-profiles\"] as const,\n} as const;\n\nexport const PROVIDER_CONNECTIONS_QUERY_KEYS = {\n all: [\"provider-connections\"] as const,\n} as const;\n\n/** Fail fast when older backends lack the profile endpoint. */\nexport const AGENT_PROFILES_RETRY_OPTIONS = {\n retry: false,\n} as const;\n\nexport const LLM_SUBSCRIPTION_QUERY_KEYS = {\n all: [\"llm-subscription\"] as const,\n openaiStatus: [\"llm-subscription\", \"openai\", \"status\"] as const,\n openaiModels: [\"llm-subscription\", \"openai\", \"models\"] as const,\n} as const;\n\nexport const LOCAL_WORKSPACES_QUERY_KEYS = {\n all: [\"local-workspaces\"] as const,\n} as const;\n\nexport const PLUGINS_QUERY_KEYS = {\n /** Dynamic marketplace catalog (used by `use-plugins-marketplace`). */\n marketplace: [\"plugins-marketplace\"] as const,\n /** Installed plugins from the local agent-server. */\n installed: [\"plugins-installed\"] as const,\n /** Locally-discovered ambient plugins (used by `use-local-plugins`). */\n local: [\"plugins-local\"] as const,\n} as const;\n\nexport const SETUP_QUERY_KEYS = {\n /** What the deployment supports. The same answer for every setup entry. */\n capabilities: () => [\"setup-capabilities\"] as const,\n} as const;\n\nexport const APP_UPDATE_QUERY_KEYS = {\n /** Latest published @openhands/agent-canvas version (npm `latest` dist-tag). */\n latestVersion: [\"agent-canvas-latest-version\"] as const,\n} as const;\n\n/** Cache configuration shared across all config-related queries */\nexport const CONFIG_CACHE_OPTIONS = {\n staleTime: 1000 * 60 * 5, // 5 minutes\n gcTime: 1000 * 60 * 15, // 15 minutes\n} as const;\n\nexport type QueryKeys = (typeof QUERY_KEYS)[keyof typeof QUERY_KEYS];\n"],"mappings":";AAOA,IAAa,IAAa;CAExB,mBAAmB,CAAC,mBAAmB;CAEvC,sBAAsB,CAAC,sBAAsB;AAC/C,GAEa,IAAsB;CACjC,KAAK,CAAC,UAAU;CAChB,UAAU,MAAyB,CAAC,YAAY,CAAK;CACrD,gBAAgB,CAAC,YAAY,UAAU;AACzC,GAEa,IAA0B,EACrC,KAAK,CAAC,cAAc,EACtB,GAEa,IAA4B,EACvC,KAAK,CAAC,gBAAgB,EACxB,GAEa,IAAkC,EAC7C,KAAK,CAAC,sBAAsB,EAC9B,GAGa,IAA+B,EAC1C,OAAO,GACT,GAEa,IAA8B;CACzC,KAAK,CAAC,kBAAkB;CACxB,cAAc;EAAC;EAAoB;EAAU;CAAQ;CACrD,cAAc;EAAC;EAAoB;EAAU;CAAQ;AACvD,GAEa,IAA8B,EACzC,KAAK,CAAC,kBAAkB,EAC1B,GAEa,IAAqB;CAEhC,aAAa,CAAC,qBAAqB;CAEnC,WAAW,CAAC,mBAAmB;CAE/B,OAAO,CAAC,eAAe;AACzB,GAOa,IAAwB,EAEnC,eAAe,CAAC,6BAA6B,EAC/C,GAGa,IAAuB;CAClC,WAAW,MAAO,KAAK;CACvB,QAAQ,MAAO,KAAK;AACtB"}
1
+ {"version":3,"file":"query-keys.js","names":[],"sources":["../../../src/hooks/query/query-keys.ts"],"sourcesContent":["/**\n * Centralized query keys and cache configuration for TanStack Query.\n * Using constants ensures type safety and prevents typos.\n */\n\nimport { SettingsScope } from \"#/types/settings\";\n\nexport const QUERY_KEYS = {\n /** Web client configuration from the server */\n WEB_CLIENT_CONFIG: [\"web-client-config\"] as const,\n /** Same-origin OpenHands app cookie authentication status */\n MAIN_APP_COOKIE_AUTH: [\"main-app-cookie-auth\"] as const,\n} as const;\n\nexport const SETTINGS_QUERY_KEYS = {\n all: [\"settings\"] as const,\n byScope: (scope: SettingsScope) => [\"settings\", scope] as const,\n personal: () => [\"settings\", \"personal\"] as const,\n} as const;\n\nexport const LLM_PROFILES_QUERY_KEYS = {\n all: [\"llm-profiles\"] as const,\n} as const;\n\nexport const AGENT_PROFILES_QUERY_KEYS = {\n all: [\"agent-profiles\"] as const,\n} as const;\n\nexport const PROVIDER_CONNECTIONS_QUERY_KEYS = {\n all: [\"provider-connections\"] as const,\n} as const;\n\n/** Fail fast when older backends lack the profile endpoint. */\nexport const AGENT_PROFILES_RETRY_OPTIONS = {\n retry: false,\n} as const;\n\nexport const LLM_SUBSCRIPTION_QUERY_KEYS = {\n all: [\"llm-subscription\"] as const,\n openaiStatus: [\"llm-subscription\", \"openai\", \"status\"] as const,\n openaiModels: [\"llm-subscription\", \"openai\", \"models\"] as const,\n} as const;\n\nexport const LOCAL_WORKSPACES_QUERY_KEYS = {\n all: [\"local-workspaces\"] as const,\n} as const;\n\nexport const PLUGINS_QUERY_KEYS = {\n /** Dynamic marketplace catalog (used by `use-plugins-marketplace`). */\n marketplace: [\"plugins-marketplace\"] as const,\n /** Installed plugins from the local agent-server. */\n installed: [\"plugins-installed\"] as const,\n /** Locally-discovered ambient plugins (used by `use-local-plugins`). */\n local: [\"plugins-local\"] as const,\n} as const;\n\nexport const CANVAS_EXTENSIONS_QUERY_KEYS = {\n all: [\"canvas-extensions\"] as const,\n installed: (\n backendId: string,\n orgId: string | null,\n connectionRevision: number,\n ) =>\n [\n \"canvas-extensions\",\n \"installed\",\n backendId,\n orgId,\n connectionRevision,\n ] as const,\n} as const;\n\nexport const SETUP_QUERY_KEYS = {\n /** What the deployment supports. The same answer for every setup entry. */\n capabilities: () => [\"setup-capabilities\"] as const,\n} as const;\n\nexport const APP_UPDATE_QUERY_KEYS = {\n /** Latest published @openhands/agent-canvas version (npm `latest` dist-tag). */\n latestVersion: [\"agent-canvas-latest-version\"] as const,\n} as const;\n\n/** Cache configuration shared across all config-related queries */\nexport const CONFIG_CACHE_OPTIONS = {\n staleTime: 1000 * 60 * 5, // 5 minutes\n gcTime: 1000 * 60 * 15, // 15 minutes\n} as const;\n\nexport type QueryKeys = (typeof QUERY_KEYS)[keyof typeof QUERY_KEYS];\n"],"mappings":";AAOA,IAAa,IAAa;CAExB,mBAAmB,CAAC,mBAAmB;CAEvC,sBAAsB,CAAC,sBAAsB;AAC/C,GAEa,IAAsB;CACjC,KAAK,CAAC,UAAU;CAChB,UAAU,MAAyB,CAAC,YAAY,CAAK;CACrD,gBAAgB,CAAC,YAAY,UAAU;AACzC,GAEa,IAA0B,EACrC,KAAK,CAAC,cAAc,EACtB,GAEa,IAA4B,EACvC,KAAK,CAAC,gBAAgB,EACxB,GAEa,IAAkC,EAC7C,KAAK,CAAC,sBAAsB,EAC9B,GAGa,IAA+B,EAC1C,OAAO,GACT,GAEa,IAA8B;CACzC,KAAK,CAAC,kBAAkB;CACxB,cAAc;EAAC;EAAoB;EAAU;CAAQ;CACrD,cAAc;EAAC;EAAoB;EAAU;CAAQ;AACvD,GAEa,IAA8B,EACzC,KAAK,CAAC,kBAAkB,EAC1B,GAEa,IAAqB;CAEhC,aAAa,CAAC,qBAAqB;CAEnC,WAAW,CAAC,mBAAmB;CAE/B,OAAO,CAAC,eAAe;AACzB,GAuBa,IAAwB,EAEnC,eAAe,CAAC,6BAA6B,EAC/C,GAGa,IAAuB;CAClC,WAAW,MAAO,KAAK;CACvB,QAAQ,MAAO,KAAK;AACtB"}
@@ -1,2 +1,2 @@
1
- require("../../_virtual/_rolldown/runtime.cjs");const e=require("../../utils/status.cjs"),t=require("../use-conversation-id.cjs"),n=require("./use-user-conversation.cjs"),r=require("../../api/conversation-service/conversation-service.api.cjs");let i=require("react");var a=()=>{let{conversationId:a}=t.useOptionalConversationId(),o=!!a&&a.startsWith(`task-`),s=n.useUserConversation(!a||o?null:a,t=>{let n=t.state.data;return n&&(!n.conversation_url||n.sandbox_status===`PAUSED`||!n.title&&e.isExecutionActive(n.execution_status))?3e3:3e4});return(0,i.useEffect)(()=>{let e=s.data;r.default.setCurrentConversation(e||null)},[a,s.isFetched,s?.data?.execution_status]),s};exports.useActiveConversation=a;
1
+ require("../../_virtual/_rolldown/runtime.cjs");const e=require("../../utils/status.cjs"),t=require("../use-conversation-id.cjs"),n=require("../../api/conversation-service/conversation-service.api.cjs"),r=require("./use-user-conversation.cjs");let i=require("react");var a=()=>{let{conversationId:a}=t.useOptionalConversationId(),o=!!a&&a.startsWith(`task-`),s=r.useUserConversation(!a||o?null:a,t=>{let n=t.state.data;return n&&(!n.conversation_url||n.sandbox_status===`PAUSED`||!n.title&&e.isExecutionActive(n.execution_status))?3e3:3e4});return(0,i.useEffect)(()=>{let e=s.data;n.default.setCurrentConversation(e||null)},[a,s.isFetched,s?.data?.execution_status]),s};exports.useActiveConversation=a;
2
2
  //# sourceMappingURL=use-active-conversation.cjs.map
@@ -1,17 +1,17 @@
1
1
  import { isExecutionActive as e } from "../../utils/status.js";
2
2
  import { useOptionalConversationId as t } from "../use-conversation-id.js";
3
- import { useUserConversation as n } from "./use-user-conversation.js";
4
- import r from "../../api/conversation-service/conversation-service.api.js";
3
+ import n from "../../api/conversation-service/conversation-service.api.js";
4
+ import { useUserConversation as r } from "./use-user-conversation.js";
5
5
  import { useEffect as i } from "react";
6
6
  //#region src/hooks/query/use-active-conversation.ts
7
7
  var a = () => {
8
- let { conversationId: a } = t(), o = !!a && a.startsWith("task-"), s = n(!a || o ? null : a, (t) => {
8
+ let { conversationId: a } = t(), o = !!a && a.startsWith("task-"), s = r(!a || o ? null : a, (t) => {
9
9
  let n = t.state.data;
10
10
  return n && (!n.conversation_url || n.sandbox_status === "PAUSED" || !n.title && e(n.execution_status)) ? 3e3 : 3e4;
11
11
  });
12
12
  return i(() => {
13
13
  let e = s.data;
14
- r.setCurrentConversation(e || null);
14
+ n.setCurrentConversation(e || null);
15
15
  }, [
16
16
  a,
17
17
  s.isFetched,