@openhands/agent-canvas 1.7.2 → 1.8.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 (353) hide show
  1. package/README.md +1 -1
  2. package/README.windows.md +2 -2
  3. package/build/assets/{Trans-DP5rcauH.js → Trans-B7lYw4uD.js} +1 -1
  4. package/build/assets/{acp-providers-BHKZvEm5.js → acp-providers-WUD4tfSq.js} +1 -1
  5. package/build/assets/active-backend-context-mY7Gff5c.js +2 -0
  6. package/build/assets/{agent-context-settings-CTaN-Iw9.js → agent-context-settings-BTUmWEzL.js} +1 -1
  7. package/build/assets/{agent-profiles-settings-CR4VTqv_.js → agent-profiles-settings-Ilqid_R5.js} +1 -1
  8. package/build/assets/agent-server-conversation-service.api-C_7YnjzP.js +41 -0
  9. package/build/assets/{agent-settings-BIOsUglV.js → agent-settings-BGTyoT6X.js} +2 -2
  10. package/build/assets/agent-settings-CxfT4ksO.js +1 -0
  11. package/build/assets/{alert-banner-C1nzoXBC.js → alert-banner-eRyIwDyU.js} +1 -1
  12. package/build/assets/api-key-entry-screen-JOx3SH1-.js +1 -0
  13. package/build/assets/app-settings-D53QZy5p.js +1 -0
  14. package/build/assets/automation-detail-nEflClmc.js +2 -0
  15. package/build/assets/{automation-export-Bu-r-EzL.js → automation-export-BO_kfqC8.js} +1 -1
  16. package/build/assets/{automations-list-CYw7_6FR.js → automations-list-BYncC7gv.js} +1 -1
  17. package/build/assets/{back-nav-button-Be_L157B.js → back-nav-button-C02hGa8t.js} +1 -1
  18. package/build/assets/backend-form-modal-CKrBF80P.js +1 -0
  19. package/build/assets/{backend-synced-settings-badge-BNpEylNP.js → backend-synced-settings-badge-CQ9xtNWm.js} +1 -1
  20. package/build/assets/{badge-DwgrwohM.js → badge-CzXbf7tR.js} +1 -1
  21. package/build/assets/{base-modal-CSHhJTSr.js → base-modal-DkVyzFMt.js} +1 -1
  22. package/build/assets/{brand-button-CQO8oqVo.js → brand-button-BUlDL-mz.js} +1 -1
  23. package/build/assets/{browser-BUDJnBg4.js → browser-3QjrEr1D.js} +1 -1
  24. package/build/assets/{browser-tab-CEApjoEV.js → browser-tab-BijpUbdt.js} +1 -1
  25. package/build/assets/{chat-send-button-D4614wUz.js → chat-send-button-BKsVjhtA.js} +1 -1
  26. package/build/assets/check-CrxQfJZ2.js +1 -0
  27. package/build/assets/chevron-down-D_LsyTA7.js +1 -0
  28. package/build/assets/circle-check-DZn9HkLE.js +1 -0
  29. package/build/assets/{circle-plus-check-toggle-E3Ecb_gg.js → circle-plus-check-toggle-DdjUiOo1.js} +1 -1
  30. package/build/assets/cloud-funnel-analytics-NlmDoeo2.js +1 -0
  31. package/build/assets/{combobox-caret-C8_eGdp2.js → combobox-caret-Dl9UtvD6.js} +1 -1
  32. package/build/assets/{command-menu-N_BriUy8.js → command-menu-CvLPs7dg.js} +1 -1
  33. package/build/assets/{condenser-settings-Czi8cKxV.js → condenser-settings-C4Y9CzQV.js} +1 -1
  34. package/build/assets/{confirmation-modal-gZtbojCm.js → confirmation-modal-CRBJSllP.js} +1 -1
  35. package/build/assets/{context-menu-list-item-BTHGFhIx.js → context-menu-list-item-FGaafXGh.js} +1 -1
  36. package/build/assets/conversation-D_ORtpKC.js +1 -0
  37. package/build/assets/conversation-Dpc5nmGi.js +90 -0
  38. package/build/assets/conversation-panel-D_ORtpKC.js +1 -0
  39. package/build/assets/conversation-state-store-CGRQemtx.js +1 -0
  40. package/build/assets/{conversation-tab-empty-state-BCMHkGQk.js → conversation-tab-empty-state-CDjvNgpG.js} +1 -1
  41. package/build/assets/{copy-B8Y5ufoR.js → copy-Drwu97Gx.js} +1 -1
  42. package/build/assets/{copy-to-clipboard-button-C4l3CMSd.js → copy-to-clipboard-button-NbN1clVr.js} +1 -1
  43. package/build/assets/{device-verify-C_M7Z8xs.js → device-verify-CDm_WrVu.js} +1 -1
  44. package/build/assets/{dropdown-classes-DQlj-MpS.js → dropdown-classes-CqA2BTCL.js} +1 -1
  45. package/build/assets/{entry.client-DETaouwO.js → entry.client-BO2KQj0y.js} +2 -2
  46. package/build/assets/{enum-filter-dropdown-paB-lFSP.js → enum-filter-dropdown-DSqPZnsD.js} +1 -1
  47. package/build/assets/{environment-switch-overlay-D0FgCMqs.js → environment-switch-overlay-mGl2pqqx.js} +1 -1
  48. package/build/assets/extensions-hub-Br3Lgynp.js +1 -0
  49. package/build/assets/extensions-navigation-BQmstKDZ.js +1 -0
  50. package/build/assets/{external-link-Cnnd6ikC.js → external-link-D9B2QMPf.js} +1 -1
  51. package/build/assets/files-tab-4c9HFSLo.js +1 -0
  52. package/build/assets/{form-control-classes-CSPevi7c.js → form-control-classes-CtVAktAy.js} +1 -1
  53. package/build/assets/{format-time-delta-DuhE50B-.js → format-time-delta-DICE64sz.js} +1 -1
  54. package/build/assets/git-provider-icon-0PqmIS_S.js +1 -0
  55. package/build/assets/{highlighted-source-view-suUKd5if.js → highlighted-source-view-KMsA9c4a.js} +1 -1
  56. package/build/assets/home-D9JsyEmE.js +1 -0
  57. package/build/assets/{home-sN90Xcxp.js → home-TFhnmZLx.js} +1 -1
  58. package/build/assets/index-home-D9JsyEmE.js +1 -0
  59. package/build/assets/install-server-modal-BgGRnqA0.js +1 -0
  60. package/build/assets/{launch-Ccnhte5b.js → launch-DjkKw55l.js} +1 -1
  61. package/build/assets/{list-todo-_BXQzOiB.js → list-todo-BpAf7aOT.js} +1 -1
  62. package/build/assets/{llm-not-configured-banner-DTWzISnk.js → llm-not-configured-banner-Y4W9-sar.js} +4 -4
  63. package/build/assets/llm-settings-D8zNkIlP.js +1 -0
  64. package/build/assets/llm-settings-nPwv7hKu.js +1 -0
  65. package/build/assets/{llm-subscription-service-W5BprsNA.js → llm-subscription-service-COTgZc1C.js} +1 -1
  66. package/build/assets/loader-circle-CGD6-Vak.js +1 -0
  67. package/build/assets/{loading-spinner-CcpnCr_d.js → loading-spinner-DRFyh6Iy.js} +1 -1
  68. package/build/assets/manage-backends-modal-DiRWE3tJ.js +1 -0
  69. package/build/assets/manifest-1cf33a7b.js +1 -0
  70. package/build/assets/markdown-renderer-U40HF0Ak.js +8 -0
  71. package/build/assets/mcp-xDMaHzOw.js +9 -0
  72. package/build/assets/{messages-DINDkPNK.js → messages-CdNfZPgd.js} +2 -2
  73. package/build/assets/{modal-body-DQhiw-AR.js → modal-body-C3bGicn4.js} +1 -1
  74. package/build/assets/{modal-classes-YaEEa5eZ.js → modal-classes-DFFO6BMU.js} +1 -1
  75. package/build/assets/{modal-close-button-turIYn0t.js → modal-close-button-vxkAIRyZ.js} +1 -1
  76. package/build/assets/{onboarding-BegDrOKn.js → onboarding-DWuFieEA.js} +1 -1
  77. package/build/assets/onboarding-modal-C8m72Dpb.js +1 -0
  78. package/build/assets/organization-service.api-CQ73CB-S.js +1 -0
  79. package/build/assets/{pencil-8_lvWDoZ.js → pencil-DzW2GB2M.js} +1 -1
  80. package/build/assets/{planner-tab-w6HT0N_A.js → planner-tab-YG0bGxDY.js} +1 -1
  81. package/build/assets/plugins-management-service-ijhH8OSh.js +1 -0
  82. package/build/assets/plus-CTGLo3aa.js +1 -0
  83. package/build/assets/profiles-service.api-C32pPm70.js +1 -0
  84. package/build/assets/providers-DuSn9s2i.js +1 -0
  85. package/build/assets/proxy-BiyDhMBd.js +1 -0
  86. package/build/assets/{puzzle-DXWdfaa7.js → puzzle-DlqT0yvr.js} +1 -1
  87. package/build/assets/{recommended-automations-launcher-BF2mtAKw.js → recommended-automations-launcher-Dru1yN46.js} +2 -2
  88. package/build/assets/{refresh-cw-D7CYLy-d.js → refresh-cw-Br5254f4.js} +1 -1
  89. package/build/assets/root-DOLLQteU.js +2 -0
  90. package/build/assets/root-layout-xmylWoew.js +3 -0
  91. package/build/assets/schema-field-htfNouTG.js +1 -0
  92. package/build/assets/{sdk-section-page-BW-i45yr.js → sdk-section-page-D0K1dBQY.js} +1 -1
  93. package/build/assets/search-BrpWXWUi.js +1 -0
  94. package/build/assets/secrets-service-Dbv5Zb8G.js +1 -0
  95. package/build/assets/{secrets-settings-D7U1ErCH.js → secrets-settings-Da4SSaX9.js} +1 -1
  96. package/build/assets/{segmented-toggle-DbbD3Tve.js → segmented-toggle-CZKOdIly.js} +1 -1
  97. package/build/assets/settings-F4a09IxY.js +1 -0
  98. package/build/assets/{settings-dropdown-input-Dx6vlkgf.js → settings-dropdown-input-jOiv5Yrk.js} +1 -1
  99. package/build/assets/{settings-index-COAdxm2Q.js → settings-index-CyJBf5NA.js} +1 -1
  100. package/build/assets/{settings-input-BjW_zyTN.js → settings-input-BEaTCem1.js} +1 -1
  101. package/build/assets/{settings-list-classes-gQdBahdt.js → settings-list-classes-VqMzkYYt.js} +1 -1
  102. package/build/assets/{settings-modal-CFnbQ8_4.js → settings-modal-C-S1NpbC.js} +1 -1
  103. package/build/assets/settings-service.api-CTJRrvMu.js +1 -0
  104. package/build/assets/{settings-switch-UNSf8K8I.js → settings-switch-B405oU_v.js} +1 -1
  105. package/build/assets/{shared-conversation-CnMxy7p_.js → shared-conversation-BvWtTwES.js} +1 -1
  106. package/build/assets/{sidebar-layout-DXHtbEj1.js → sidebar-layout-BGMuyC86.js} +1 -1
  107. package/build/assets/{sidebar-mobile-menu-toggle-U78hQRJ1.js → sidebar-mobile-menu-toggle-xiKoUfGa.js} +1 -1
  108. package/build/assets/{sidebar-nav-link-pvUivfK2.js → sidebar-nav-link-DA4_ct9G.js} +1 -1
  109. package/build/assets/{sidebar-store-DwrYKwYr.js → sidebar-store-FRJ3rADB.js} +1 -1
  110. package/build/assets/{skill-card-pill-row-D7XCz5uk.js → skill-card-pill-row-BMdhr_sd.js} +1 -1
  111. package/build/assets/{skill-icon-badge-HqBWZ2Eq.js → skill-icon-badge-CGUkgcVH.js} +1 -1
  112. package/build/assets/{skills-plugins-BPhaNpfx.js → skills-plugins-C5eJnyft.js} +1 -1
  113. package/build/assets/{skills-settings-CcwMtnnJ.js → skills-settings-pUxMr6WZ.js} +1 -1
  114. package/build/assets/{styled-tooltip-DsjDnR_W.js → styled-tooltip-B3kIdVyH.js} +1 -1
  115. package/build/assets/{task-list-tab-BmaTwjaS.js → task-list-tab-B3k4W1cK.js} +1 -1
  116. package/build/assets/{terminal-BRcoba5B.js → terminal-CBI_41Zc.js} +1 -1
  117. package/build/assets/{toggle-switch-BptY3XRG.js → toggle-switch-DYRr9CAR.js} +1 -1
  118. package/build/assets/{trash-2-Bq4lHbTB.js → trash-2-gZPlx23R.js} +1 -1
  119. package/build/assets/{typography-C6fuRTTT.js → typography-B1UxuuEC.js} +1 -1
  120. package/build/assets/{use-acp-credential-form-B0_pZ_uS.js → use-acp-credential-form-CjSvKWFH.js} +1 -1
  121. package/build/assets/use-activate-agent-profile-DCRf47e-.js +1 -0
  122. package/build/assets/use-agent-profiles-DmT9zxAN.js +1 -0
  123. package/build/assets/{use-can-manage-org-profiles-CrG1km7R.js → use-can-manage-org-profiles-vBiP7rHF.js} +1 -1
  124. package/build/assets/use-cloud-current-user-id-BMq9gw93.js +1 -0
  125. package/build/assets/use-config-C959QcRC.js +1 -0
  126. package/build/assets/{use-create-conversation-Dsa4nzU9.js → use-create-conversation-CakhLF0X.js} +1 -1
  127. package/build/assets/{use-create-secret-BnxuAjDt.js → use-create-secret-RT4QtSS9.js} +1 -1
  128. package/build/assets/use-llm-profiles-CdaxVpyY.js +1 -0
  129. package/build/assets/use-onboarding-completion-CJU4MNAY.js +1 -0
  130. package/build/assets/use-plugins-marketplace-D-93nqtJ.js +1 -0
  131. package/build/assets/{use-save-agent-profile-2Mk9ZX37.js → use-save-agent-profile-DGJ3-u0M.js} +1 -1
  132. package/build/assets/{use-save-settings-BRh9p_fv.js → use-save-settings--C6LZXfI.js} +1 -1
  133. package/build/assets/use-settings-DIAo5KFk.js +1 -0
  134. package/build/assets/use-settings-nav-items-DPK5sxvR.js +16 -0
  135. package/build/assets/use-skills-D_mLhYNa.js +1 -0
  136. package/build/assets/use-tracking-COqZLobY.js +1 -0
  137. package/build/assets/{use-user-conversation-GnGhs_Ob.js → use-user-conversation-DhcAEyJX.js} +1 -1
  138. package/build/assets/{useMutation-DyQXZFEF.js → useMutation-DHLpZ6J0.js} +1 -1
  139. package/build/assets/utils-DsWhWn2F.js +1 -0
  140. package/build/assets/{vendor~entry.client~root~root-layout~index-home~home~conversation-panel~conversation~launch~g4xrpilz-CJ7amdtQ.js → vendor~entry.client~root~root-layout~index-home~home~conversation-panel~conversation~launch~g4xrpilz-CYPMCkX1.js} +1 -1
  141. package/build/assets/{vendor~files-tab-vm3f67Pz.js → vendor~files-tab-atebg0iG.js} +1 -1
  142. package/build/assets/{vendor~index-home~home~conversation-panel~conversation-kX4LrGQC.js → vendor~index-home~home~conversation-panel~conversation-G9DMrR7m.js} +1 -1
  143. package/build/assets/{vendor~root-layout~conversation-panel~conversation-DpA6fir3.js → vendor~root-layout~conversation-panel~conversation-DEhW1h3I.js} +1 -1
  144. package/build/assets/{vendor~root-layout~index-home~home~conversation-panel~conversation-EShpjxey.js → vendor~root-layout~index-home~home~conversation-panel~conversation-Acmf1sKj.js} +1 -1
  145. package/build/assets/{vendor~root-layout~index-home~home~conversation-panel~conversation~shared-conversation-DD_UNO02.js → vendor~root-layout~index-home~home~conversation-panel~conversation~shared-conversation-CmzCY1BT.js} +1 -1
  146. package/build/assets/{vendor~root-layout~index-home~home~mcp~automations-list~onboarding-modal-Be63Q2Ln.js → vendor~root-layout~index-home~home~mcp~automations-list~onboarding-modal-C7Yh77RT.js} +1 -1
  147. package/build/assets/{vendor~root~root-layout~index-home~home~conversation-panel~conversation~launch~extensions-h~bi2r7bko-Bw4AuHN1.js → vendor~root~root-layout~index-home~home~conversation-panel~conversation~launch~extensions-h~bi2r7bko-VFnwct3q.js} +1 -1
  148. package/build/assets/{verification-settings-CaCzojbc.js → verification-settings-Vr5cN4md.js} +1 -1
  149. package/build/assets/x-Dx_JbpHA.js +1 -0
  150. package/build/favicon-16x16.png +0 -0
  151. package/build/favicon-32x32.png +0 -0
  152. package/build/favicon.ico +0 -0
  153. package/build/index.html +4 -4
  154. package/build/locales/ar/openhands.json +2 -0
  155. package/build/locales/ca/openhands.json +2 -0
  156. package/build/locales/de/openhands.json +2 -0
  157. package/build/locales/en/openhands.json +2 -0
  158. package/build/locales/es/openhands.json +2 -0
  159. package/build/locales/fr/openhands.json +2 -0
  160. package/build/locales/it/openhands.json +2 -0
  161. package/build/locales/ja/openhands.json +2 -0
  162. package/build/locales/ko-KR/openhands.json +2 -0
  163. package/build/locales/no/openhands.json +2 -0
  164. package/build/locales/pt/openhands.json +2 -0
  165. package/build/locales/tr/openhands.json +2 -0
  166. package/build/locales/uk/openhands.json +2 -0
  167. package/build/locales/zh-CN/openhands.json +2 -0
  168. package/build/locales/zh-TW/openhands.json +2 -0
  169. package/config/defaults.json +4 -4
  170. package/dist/api/agent-server-adapter.cjs +1 -1
  171. package/dist/api/agent-server-adapter.cjs.map +1 -1
  172. package/dist/api/agent-server-adapter.js +7 -7
  173. package/dist/api/agent-server-adapter.js.map +1 -1
  174. package/dist/api/automation-service/automation-service.api.cjs +1 -1
  175. package/dist/api/automation-service/automation-service.api.cjs.map +1 -1
  176. package/dist/api/automation-service/automation-service.api.js +95 -95
  177. package/dist/api/automation-service/automation-service.api.js.map +1 -1
  178. package/dist/api/backend-registry/storage.cjs +1 -1
  179. package/dist/api/backend-registry/storage.cjs.map +1 -1
  180. package/dist/api/backend-registry/storage.js +1 -1
  181. package/dist/api/backend-registry/storage.js.map +1 -1
  182. package/dist/api/backend-registry/types.d.ts +2 -0
  183. package/dist/api/cloud/conversation-service.api.cjs +1 -1
  184. package/dist/api/cloud/conversation-service.api.js +22 -22
  185. package/dist/api/mcp-service/mcp-service.api.cjs +1 -1
  186. package/dist/api/mcp-service/mcp-service.api.cjs.map +1 -1
  187. package/dist/api/mcp-service/mcp-service.api.js +19 -19
  188. package/dist/api/mcp-service/mcp-service.api.js.map +1 -1
  189. package/dist/api/settings-service/settings-service.api.cjs +1 -1
  190. package/dist/api/settings-service/settings-service.api.cjs.map +1 -1
  191. package/dist/api/settings-service/settings-service.api.js +26 -12
  192. package/dist/api/settings-service/settings-service.api.js.map +1 -1
  193. package/dist/components/features/mcp-page/custom-server-editor.cjs +1 -1
  194. package/dist/components/features/mcp-page/custom-server-editor.js +16 -16
  195. package/dist/components/features/mcp-page/installed-server-card.cjs +1 -1
  196. package/dist/components/features/mcp-page/installed-server-card.cjs.map +1 -1
  197. package/dist/components/features/mcp-page/installed-server-card.d.ts +2 -2
  198. package/dist/components/features/mcp-page/installed-server-card.js +6 -7
  199. package/dist/components/features/mcp-page/installed-server-card.js.map +1 -1
  200. package/dist/components/features/mcp-page/installed-servers-section.cjs +1 -1
  201. package/dist/components/features/mcp-page/installed-servers-section.cjs.map +1 -1
  202. package/dist/components/features/mcp-page/installed-servers-section.d.ts +2 -2
  203. package/dist/components/features/mcp-page/installed-servers-section.js +2 -2
  204. package/dist/components/features/mcp-page/installed-servers-section.js.map +1 -1
  205. package/dist/components/features/mcp-page/mcp-server-health.cjs +1 -1
  206. package/dist/components/features/mcp-page/mcp-server-health.js +4 -4
  207. package/dist/components/features/settings/mcp-settings/mcp-server-form.cjs +1 -1
  208. package/dist/components/features/settings/mcp-settings/mcp-server-form.cjs.map +1 -1
  209. package/dist/components/features/settings/mcp-settings/mcp-server-form.js +2 -1
  210. package/dist/components/features/settings/mcp-settings/mcp-server-form.js.map +1 -1
  211. package/dist/config/defaults.cjs +1 -1
  212. package/dist/config/defaults.cjs.map +1 -1
  213. package/dist/config/defaults.js +3 -3
  214. package/dist/config/defaults.js.map +1 -1
  215. package/dist/contexts/active-backend-context.cjs +1 -1
  216. package/dist/contexts/active-backend-context.cjs.map +1 -1
  217. package/dist/contexts/active-backend-context.d.ts +3 -2
  218. package/dist/contexts/active-backend-context.js +55 -42
  219. package/dist/contexts/active-backend-context.js.map +1 -1
  220. package/dist/favicon-16x16.png +0 -0
  221. package/dist/favicon-32x32.png +0 -0
  222. package/dist/favicon.ico +0 -0
  223. package/dist/hooks/mutation/use-update-mcp-server.cjs +1 -1
  224. package/dist/hooks/mutation/use-update-mcp-server.js +5 -5
  225. package/dist/hooks/query/use-cloud-current-user-id.cjs +1 -1
  226. package/dist/hooks/query/use-cloud-current-user-id.cjs.map +1 -1
  227. package/dist/hooks/query/use-cloud-current-user-id.js +7 -5
  228. package/dist/hooks/query/use-cloud-current-user-id.js.map +1 -1
  229. package/dist/hooks/query/use-cloud-organizations.cjs +1 -1
  230. package/dist/hooks/query/use-cloud-organizations.cjs.map +1 -1
  231. package/dist/hooks/query/use-cloud-organizations.js +5 -1
  232. package/dist/hooks/query/use-cloud-organizations.js.map +1 -1
  233. package/dist/hooks/use-can-manage-org-profiles.cjs +1 -1
  234. package/dist/hooks/use-can-manage-org-profiles.cjs.map +1 -1
  235. package/dist/hooks/use-can-manage-org-profiles.js +2 -1
  236. package/dist/hooks/use-can-manage-org-profiles.js.map +1 -1
  237. package/dist/hooks/use-tracking.cjs +1 -1
  238. package/dist/hooks/use-tracking.js +9 -9
  239. package/dist/i18n/declaration.cjs +1 -1
  240. package/dist/i18n/declaration.cjs.map +1 -1
  241. package/dist/i18n/declaration.d.ts +2 -0
  242. package/dist/i18n/declaration.js +1 -1
  243. package/dist/i18n/declaration.js.map +1 -1
  244. package/dist/i18n/translation.cjs +1 -1
  245. package/dist/i18n/translation.cjs.map +1 -1
  246. package/dist/i18n/translation.js +34 -0
  247. package/dist/i18n/translation.js.map +1 -1
  248. package/dist/locales/ar/openhands.json +2 -0
  249. package/dist/locales/ca/openhands.json +2 -0
  250. package/dist/locales/de/openhands.json +2 -0
  251. package/dist/locales/en/openhands.json +2 -0
  252. package/dist/locales/es/openhands.json +2 -0
  253. package/dist/locales/fr/openhands.json +2 -0
  254. package/dist/locales/it/openhands.json +2 -0
  255. package/dist/locales/ja/openhands.json +2 -0
  256. package/dist/locales/ko-KR/openhands.json +2 -0
  257. package/dist/locales/no/openhands.json +2 -0
  258. package/dist/locales/pt/openhands.json +2 -0
  259. package/dist/locales/tr/openhands.json +2 -0
  260. package/dist/locales/uk/openhands.json +2 -0
  261. package/dist/locales/zh-CN/openhands.json +2 -0
  262. package/dist/locales/zh-TW/openhands.json +2 -0
  263. package/dist/node_modules/@openhands/typescript-client/dist/client/settings-client.cjs +1 -1
  264. package/dist/node_modules/@openhands/typescript-client/dist/client/settings-client.cjs.map +1 -1
  265. package/dist/node_modules/@openhands/typescript-client/dist/client/settings-client.js +9 -0
  266. package/dist/node_modules/@openhands/typescript-client/dist/client/settings-client.js.map +1 -1
  267. package/dist/package.cjs +1 -1
  268. package/dist/package.cjs.map +1 -1
  269. package/dist/package.js +2 -2
  270. package/dist/package.js.map +1 -1
  271. package/dist/routes/app-settings.cjs +1 -1
  272. package/dist/routes/app-settings.js +5 -5
  273. package/dist/routes/mcp.cjs +1 -1
  274. package/dist/routes/mcp.cjs.map +1 -1
  275. package/dist/routes/mcp.js +73 -82
  276. package/dist/routes/mcp.js.map +1 -1
  277. package/dist/services/telemetry.cjs +1 -1
  278. package/dist/services/telemetry.cjs.map +1 -1
  279. package/dist/services/telemetry.d.ts +8 -0
  280. package/dist/services/telemetry.js +163 -116
  281. package/dist/services/telemetry.js.map +1 -1
  282. package/dist/types/mcp-server.d.ts +5 -0
  283. package/dist/types/settings.d.ts +3 -0
  284. package/dist/utils/mcp-config.cjs +1 -1
  285. package/dist/utils/mcp-config.cjs.map +1 -1
  286. package/dist/utils/mcp-config.d.ts +8 -0
  287. package/dist/utils/mcp-config.js +25 -20
  288. package/dist/utils/mcp-config.js.map +1 -1
  289. package/dist/utils/mcp-installed-servers.cjs +1 -1
  290. package/dist/utils/mcp-installed-servers.cjs.map +1 -1
  291. package/dist/utils/mcp-installed-servers.js +6 -3
  292. package/dist/utils/mcp-installed-servers.js.map +1 -1
  293. package/package.json +2 -2
  294. package/scripts/check-sdk-version-sync.mjs +6 -6
  295. package/scripts/dev-safe.mjs +2 -2
  296. package/build/assets/active-backend-context-BlZPzZNU.js +0 -1
  297. package/build/assets/agent-server-conversation-service.api-Btmi4q6d.js +0 -41
  298. package/build/assets/agent-settings-KQs-occT.js +0 -1
  299. package/build/assets/api-key-entry-screen-BhR24gkM.js +0 -1
  300. package/build/assets/app-settings-Bs4sY8WP.js +0 -1
  301. package/build/assets/automation-detail-CwDnJbYR.js +0 -2
  302. package/build/assets/backend-form-modal-Ce-KRHOJ.js +0 -1
  303. package/build/assets/check-BMOG005H.js +0 -1
  304. package/build/assets/chevron-down-CV1zZP_K.js +0 -1
  305. package/build/assets/circle-check-lvN_jFN5.js +0 -1
  306. package/build/assets/client-source-1v-kWLNU.js +0 -1
  307. package/build/assets/cloud-funnel-analytics-CasMM7EO.js +0 -1
  308. package/build/assets/conversation-C25pc2dT.js +0 -1
  309. package/build/assets/conversation-CObLYSJd.js +0 -90
  310. package/build/assets/conversation-panel-C25pc2dT.js +0 -1
  311. package/build/assets/conversation-state-store-DTq4hFoP.js +0 -1
  312. package/build/assets/extensions-hub-BKftOQ-X.js +0 -1
  313. package/build/assets/extensions-navigation-DF24q1H1.js +0 -1
  314. package/build/assets/files-tab-SAiFCnS5.js +0 -1
  315. package/build/assets/git-provider-icon-BREx-yUX.js +0 -1
  316. package/build/assets/home-EPNFgzUh.js +0 -1
  317. package/build/assets/index-home-EPNFgzUh.js +0 -1
  318. package/build/assets/install-server-modal-BI8yUfmr.js +0 -1
  319. package/build/assets/llm-settings-BXk5_Pac.js +0 -1
  320. package/build/assets/llm-settings-C1vpgWam.js +0 -1
  321. package/build/assets/loader-circle-6zlEYCJw.js +0 -1
  322. package/build/assets/manage-backends-modal-DhflyGTf.js +0 -1
  323. package/build/assets/manifest-8082e879.js +0 -1
  324. package/build/assets/markdown-renderer-CvOO0YIr.js +0 -8
  325. package/build/assets/mcp-CxLPda-W.js +0 -9
  326. package/build/assets/onboarding-modal-DdvE6rzw.js +0 -1
  327. package/build/assets/organization-service.api-CT2OQlLz.js +0 -1
  328. package/build/assets/plugins-management-service-DVkoM3eA.js +0 -1
  329. package/build/assets/plus-vIbGD459.js +0 -1
  330. package/build/assets/profiles-service.api-CBafmAPO.js +0 -1
  331. package/build/assets/providers-9UzRB9mT.js +0 -1
  332. package/build/assets/proxy-C79PCMjo.js +0 -1
  333. package/build/assets/root-Sj7W28yE.js +0 -2
  334. package/build/assets/root-layout-DbhKVoc7.js +0 -3
  335. package/build/assets/schema-field-CV6bFnoL.js +0 -1
  336. package/build/assets/search-sKuYKllf.js +0 -1
  337. package/build/assets/secrets-service-BjK-bPN2.js +0 -1
  338. package/build/assets/settings-Bj5Oo45A.js +0 -1
  339. package/build/assets/settings-service.api-DryhKdSr.js +0 -1
  340. package/build/assets/telemetry-cmCL3o6Z.js +0 -2
  341. package/build/assets/use-activate-agent-profile-DH33MvVj.js +0 -1
  342. package/build/assets/use-agent-profiles-Dmd_4H8q.js +0 -1
  343. package/build/assets/use-cloud-current-user-id-B6DP3s47.js +0 -1
  344. package/build/assets/use-config-Csy8EAP9.js +0 -1
  345. package/build/assets/use-llm-profiles-BMQoc612.js +0 -1
  346. package/build/assets/use-onboarding-completion-B-2BTS-y.js +0 -1
  347. package/build/assets/use-plugins-marketplace-PBc_XNz_.js +0 -1
  348. package/build/assets/use-settings-DBI44RnH.js +0 -1
  349. package/build/assets/use-settings-nav-items-Cva28C-c.js +0 -16
  350. package/build/assets/use-skills-RQsCtdzN.js +0 -1
  351. package/build/assets/use-tracking-CN1nIMnY.js +0 -1
  352. package/build/assets/utils-qjs_KuS4.js +0 -1
  353. package/build/assets/x-EjvKGawc.js +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"mcp.cjs","names":[],"sources":["../../src/routes/mcp.tsx"],"sourcesContent":["import React from \"react\";\nimport { AxiosError } from \"axios\";\nimport { ExtensionsNavigation } from \"#/components/features/skills/extensions-navigation\";\nimport { useTranslation } from \"react-i18next\";\nimport { I18nKey } from \"#/i18n/declaration\";\nimport { BrandButton } from \"#/components/features/settings/brand-button\";\nimport { ConfirmationModal } from \"#/components/shared/modals/confirmation-modal\";\nimport { useSettings } from \"#/hooks/query/use-settings\";\nimport { useDeleteMcpServer } from \"#/hooks/mutation/use-delete-mcp-server\";\nimport { parseMcpConfig } from \"#/utils/mcp-config\";\nimport {\n displayErrorToast,\n displaySuccessToast,\n} from \"#/utils/custom-toast-handlers\";\nimport { retrieveAxiosErrorMessage } from \"#/utils/retrieve-axios-error-message\";\nimport { settingsLikeMainScrollClassName } from \"#/utils/settings-like-page-layout-classes\";\nimport {\n findCatalogEntryForServer,\n getMcpMarketplaceCatalog,\n installedServerMatchesQuery,\n} from \"#/utils/mcp-marketplace-utils\";\nimport {\n INTEGRATION_CATALOG as MCP_MARKETPLACE,\n type IntegrationCatalogEntry as MarketplaceEntry,\n} from \"@openhands/extensions/integrations\";\nimport { MCPServerConfig } from \"#/types/mcp-server\";\nimport { flattenMcpConfig } from \"#/utils/mcp-installed-servers\";\nimport {\n InstalledServersSection,\n McpToolbar,\n MarketplaceSection,\n InstallServerModal,\n CustomServerEditor,\n type McpSectionFilter,\n} from \"#/components/features/mcp-page\";\n\n// No ACP guard here (unlike `/settings` and `/settings/condenser`): MCP\n// servers configured via `agent_settings.mcp_config` are now forwarded to\n// the ACP subprocess at session creation, so this page is meaningful for\n// both OpenHands and ACP agents. The same editor and MCP settings storage\n// drive both kinds.\n\nexport default function MCPPage() {\n const { t } = useTranslation(\"openhands\");\n const { data: settings, isLoading } = useSettings();\n const { mutate: deleteMcpServer, isPending: isDeleting } =\n useDeleteMcpServer();\n\n const [installEntry, setInstallEntry] =\n React.useState<MarketplaceEntry | null>(null);\n const [editingServer, setEditingServer] =\n React.useState<MCPServerConfig | null>(null);\n const [serverToDelete, setServerToDelete] =\n React.useState<MCPServerConfig | null>(null);\n const [searchQuery, setSearchQuery] = React.useState(\"\");\n const [sectionFilter, setSectionFilter] =\n React.useState<McpSectionFilter>(\"all\");\n\n const mcpConfig = parseMcpConfig(settings?.agent_settings?.mcp_config);\n const allServers = flattenMcpConfig(mcpConfig);\n const mcpMarketplace = getMcpMarketplaceCatalog(MCP_MARKETPLACE);\n\n // Filter installed servers by the search query. We pair each server\n // with its catalog entry (if any) so the search can match friendly\n // names like \"Slack\" against a stdio server whose own `.name` is\n // just \"slack\".\n const filteredInstalledServers = allServers.filter((server) =>\n installedServerMatchesQuery(\n server,\n findCatalogEntryForServer(server, mcpMarketplace),\n searchQuery,\n ),\n );\n\n const handleMarketplaceInstall = (entry: MarketplaceEntry) => {\n setInstallEntry(entry);\n };\n\n const handleEdit = (server: MCPServerConfig) => {\n setEditingServer(server);\n };\n\n const handleDeleteClick = (serverId: string) => {\n const target = allServers.find((s) => s.id === serverId);\n if (target) setServerToDelete(target);\n };\n\n const handleConfirmDelete = () => {\n if (!serverToDelete) return;\n // Pass the full server config — useDeleteMcpServer re-resolves its\n // position against the fresh settings at mutation time, so a\n // background refresh between this click and confirm cannot point\n // us at the wrong index.\n deleteMcpServer(serverToDelete, {\n onSuccess: () => {\n displaySuccessToast(t(I18nKey.MCP$REMOVE_SUCCESS));\n setServerToDelete(null);\n },\n onError: (err) => {\n const message = retrieveAxiosErrorMessage(err as AxiosError);\n displayErrorToast(message || t(I18nKey.ERROR$GENERIC));\n setServerToDelete(null);\n },\n });\n };\n\n if (isLoading || !settings) {\n return (\n <div\n data-testid=\"mcp-page\"\n className=\"flex h-full gap-4 md:gap-6 md:pl-8 lg:gap-10 lg:pl-10\"\n >\n <ExtensionsNavigation />\n <div className=\"flex h-full flex-1 items-center justify-center px-4 md:px-0\">\n <div className=\"h-8 w-8 rounded-full border-2 border-transparent border-t-white animate-spin\" />\n </div>\n </div>\n );\n }\n\n return (\n <div\n data-testid=\"mcp-page\"\n className=\"flex h-full gap-4 md:gap-6 md:pl-8 lg:gap-10 lg:pl-10\"\n >\n <ExtensionsNavigation />\n <main className={settingsLikeMainScrollClassName}>\n <div className=\"mx-auto flex w-full min-w-0 max-w-[800px] flex-col gap-6\">\n <div className=\"min-w-0\">\n <div className=\"flex items-start justify-between gap-4\">\n <div className=\"space-y-1\">\n <h2 className=\"text-xl font-medium leading-6 text-foreground\">\n {t(I18nKey.SETTINGS$MCP_TITLE)}\n </h2>\n <div className=\"max-w-2xl text-sm text-tertiary-light\">\n {t(I18nKey.MCP$PAGE_DESCRIPTION)}\n </div>\n </div>\n <BrandButton\n type=\"button\"\n variant=\"secondary\"\n testId=\"mcp-add-custom-server\"\n className=\"flex-shrink-0 whitespace-nowrap\"\n onClick={() => setEditingServer({ id: \"\", type: \"sse\" })}\n >\n {t(I18nKey.MCP$ADD_CUSTOM)}\n </BrandButton>\n </div>\n </div>\n\n <McpToolbar\n search={searchQuery}\n onSearchChange={setSearchQuery}\n sectionFilter={sectionFilter}\n onSectionFilterChange={setSectionFilter}\n />\n\n {sectionFilter !== \"library\" ? (\n <section className=\"flex flex-col gap-3\">\n <h2 className=\"text-base font-semibold text-foreground\">\n {t(I18nKey.MCP$INSTALLED_TITLE)}\n </h2>\n <InstalledServersSection\n servers={filteredInstalledServers}\n hasAnyInstalled={allServers.length > 0}\n query={searchQuery}\n onEdit={handleEdit}\n onDelete={handleDeleteClick}\n />\n </section>\n ) : null}\n\n {sectionFilter !== \"installed\" ? (\n <MarketplaceSection\n onSelect={handleMarketplaceInstall}\n onAdd={handleMarketplaceInstall}\n query={searchQuery}\n />\n ) : null}\n </div>\n\n {installEntry && (\n <InstallServerModal\n entry={installEntry}\n existingServers={allServers}\n onClose={() => setInstallEntry(null)}\n />\n )}\n\n {/* Custom (or non-marketplace) server editor. The empty-id\n sentinel (`{ id: \"\", type: \"sse\" }`) means \"add new\". */}\n {editingServer && (\n <CustomServerEditor\n server={editingServer}\n existingServers={allServers}\n onClose={() => setEditingServer(null)}\n />\n )}\n\n {serverToDelete && (\n <ConfirmationModal\n text={t(I18nKey.SETTINGS$MCP_CONFIRM_DELETE)}\n onCancel={() => setServerToDelete(null)}\n onConfirm={handleConfirmDelete}\n isConfirming={isDeleting}\n />\n )}\n </main>\n </div>\n );\n}\n"],"mappings":"oxCA0CA,SAAwB,GAAU,CAChC,GAAM,CAAE,KAAM,EAAA,eAAe,WAAW,EAClC,CAAE,KAAM,EAAU,aAAc,EAAA,YAAY,EAC5C,CAAE,OAAQ,EAAiB,UAAW,GAC1C,EAAA,mBAAmB,EAEf,CAAC,EAAc,GACnB,EAAA,QAAM,SAAkC,IAAI,EACxC,CAAC,EAAe,GACpB,EAAA,QAAM,SAAiC,IAAI,EACvC,CAAC,EAAgB,GACrB,EAAA,QAAM,SAAiC,IAAI,EACvC,CAAC,EAAa,GAAkB,EAAA,QAAM,SAAS,EAAE,EACjD,CAAC,EAAe,GACpB,EAAA,QAAM,SAA2B,KAAK,EAGlC,EAAa,EAAA,iBADD,EAAA,eAAe,GAAU,gBAAgB,UACvB,CAAS,EACvC,EAAiB,EAAA,yBAAyB,EAAA,OAAe,EAMzD,EAA2B,EAAW,OAAQ,GAClD,EAAA,4BACE,EACA,EAAA,0BAA0B,EAAQ,CAAc,EAChD,CACF,CACF,EAEM,EAA4B,GAA4B,CAC5D,EAAgB,CAAK,CACvB,EA4CA,OAdI,GAAa,CAAC,GAEd,EAAA,EAAA,MAAC,MAAD,CACE,cAAY,WACZ,UAAU,iEAFZ,EAIE,EAAA,EAAA,KAAC,EAAA,qBAAD,CAAuB,CAAA,GACvB,EAAA,EAAA,KAAC,MAAD,CAAK,UAAU,wEACb,EAAA,EAAA,KAAC,MAAD,CAAK,UAAU,8EAAgF,CAAA,CAC5F,CAAA,CACF,KAKP,EAAA,EAAA,MAAC,MAAD,CACE,cAAY,WACZ,UAAU,iEAFZ,EAIE,EAAA,EAAA,KAAC,EAAA,qBAAD,CAAuB,CAAA,GACvB,EAAA,EAAA,MAAC,OAAD,CAAM,UAAW,EAAA,yCAAjB,EACE,EAAA,EAAA,MAAC,MAAD,CAAK,UAAU,oEAAf,EACE,EAAA,EAAA,KAAC,MAAD,CAAK,UAAU,oBACb,EAAA,EAAA,MAAC,MAAD,CAAK,UAAU,kDAAf,EACE,EAAA,EAAA,MAAC,MAAD,CAAK,UAAU,qBAAf,EACE,EAAA,EAAA,KAAC,KAAD,CAAI,UAAU,yDACX,EAAE,EAAA,QAAQ,kBAAkB,CAC3B,CAAA,GACJ,EAAA,EAAA,KAAC,MAAD,CAAK,UAAU,iDACZ,EAAE,EAAA,QAAQ,oBAAoB,CAC5B,CAAA,CACF,KACL,EAAA,EAAA,KAAC,EAAA,YAAD,CACE,KAAK,SACL,QAAQ,YACR,OAAO,wBACP,UAAU,kCACV,YAAe,EAAiB,CAAE,GAAI,GAAI,KAAM,KAAM,CAAC,WAEtD,EAAE,EAAA,QAAQ,cAAc,CACd,CAAA,CACV,GACF,CAAA,GAEL,EAAA,EAAA,KAAC,EAAA,WAAD,CACE,OAAQ,EACR,eAAgB,EACD,gBACf,sBAAuB,CACxB,CAAA,EAEA,IAAkB,UAaf,MAZF,EAAA,EAAA,MAAC,UAAD,CAAS,UAAU,+BAAnB,EACE,EAAA,EAAA,KAAC,KAAD,CAAI,UAAU,mDACX,EAAE,EAAA,QAAQ,mBAAmB,CAC5B,CAAA,GACJ,EAAA,EAAA,KAAC,EAAA,wBAAD,CACE,QAAS,EACT,gBAAiB,EAAW,OAAS,EACrC,MAAO,EACP,OAxFM,GAA4B,CAC9C,EAAiB,CAAM,CACzB,EAuFc,SArFa,GAAqB,CAC9C,IAAM,EAAS,EAAW,KAAM,GAAM,EAAE,KAAO,CAAQ,EACnD,GAAQ,EAAkB,CAAM,CACtC,CAmFa,CAAA,CACM,IAGV,IAAkB,YAMf,MALF,EAAA,EAAA,KAAC,EAAA,mBAAD,CACE,SAAU,EACV,MAAO,EACP,MAAO,CACR,CAAA,CAEA,IAEJ,IACC,EAAA,EAAA,KAAC,EAAA,mBAAD,CACE,MAAO,EACP,gBAAiB,EACjB,YAAe,EAAgB,IAAI,CACpC,CAAA,EAKF,IACC,EAAA,EAAA,KAAC,EAAA,mBAAD,CACE,OAAQ,EACR,gBAAiB,EACjB,YAAe,EAAiB,IAAI,CACrC,CAAA,EAGF,IACC,EAAA,EAAA,KAAC,EAAA,kBAAD,CACE,KAAM,EAAE,EAAA,QAAQ,2BAA2B,EAC3C,aAAgB,EAAkB,IAAI,EACtC,cApHwB,CAC3B,GAKL,EAAgB,EAAgB,CAC9B,cAAiB,CACf,EAAA,oBAAoB,EAAE,EAAA,QAAQ,kBAAkB,CAAC,EACjD,EAAkB,IAAI,CACxB,EACA,QAAU,GAAQ,CAEhB,EAAA,kBADgB,EAAA,0BAA0B,CACxB,GAAW,EAAE,EAAA,QAAQ,aAAa,CAAC,EACrD,EAAkB,IAAI,CACxB,CACF,CAAC,CACH,EAoGU,aAAc,CACf,CAAA,CAEC,GACH,GAET"}
1
+ {"version":3,"file":"mcp.cjs","names":[],"sources":["../../src/routes/mcp.tsx"],"sourcesContent":["import React from \"react\";\nimport { AxiosError } from \"axios\";\nimport { ExtensionsNavigation } from \"#/components/features/skills/extensions-navigation\";\nimport { useTranslation } from \"react-i18next\";\nimport { I18nKey } from \"#/i18n/declaration\";\nimport { BrandButton } from \"#/components/features/settings/brand-button\";\nimport { useSettings } from \"#/hooks/query/use-settings\";\nimport { useUpdateMcpServer } from \"#/hooks/mutation/use-update-mcp-server\";\nimport { parseMcpConfig } from \"#/utils/mcp-config\";\nimport { displayErrorToast } from \"#/utils/custom-toast-handlers\";\nimport { retrieveAxiosErrorMessage } from \"#/utils/retrieve-axios-error-message\";\nimport { settingsLikeMainScrollClassName } from \"#/utils/settings-like-page-layout-classes\";\nimport {\n findCatalogEntryForServer,\n getMcpMarketplaceCatalog,\n installedServerMatchesQuery,\n} from \"#/utils/mcp-marketplace-utils\";\nimport {\n INTEGRATION_CATALOG as MCP_MARKETPLACE,\n type IntegrationCatalogEntry as MarketplaceEntry,\n} from \"@openhands/extensions/integrations\";\nimport { MCPServerConfig } from \"#/types/mcp-server\";\nimport { flattenMcpConfig } from \"#/utils/mcp-installed-servers\";\nimport {\n InstalledServersSection,\n McpToolbar,\n MarketplaceSection,\n InstallServerModal,\n CustomServerEditor,\n type McpSectionFilter,\n} from \"#/components/features/mcp-page\";\n\n// No ACP guard here (unlike `/settings` and `/settings/condenser`): MCP\n// servers configured via `agent_settings.mcp_config` are now forwarded to\n// the ACP subprocess at session creation, so this page is meaningful for\n// both OpenHands and ACP agents. The same editor and MCP settings storage\n// drive both kinds.\n\nexport default function MCPPage() {\n const { t } = useTranslation(\"openhands\");\n const { data: settings, isLoading } = useSettings();\n const { mutate: updateMcpServer } = useUpdateMcpServer();\n\n const [installEntry, setInstallEntry] =\n React.useState<MarketplaceEntry | null>(null);\n const [editingServer, setEditingServer] =\n React.useState<MCPServerConfig | null>(null);\n const [searchQuery, setSearchQuery] = React.useState(\"\");\n const [sectionFilter, setSectionFilter] =\n React.useState<McpSectionFilter>(\"all\");\n\n const mcpConfig = parseMcpConfig(settings?.agent_settings?.mcp_config);\n const allServers = flattenMcpConfig(mcpConfig);\n const mcpMarketplace = getMcpMarketplaceCatalog(MCP_MARKETPLACE);\n\n // Filter installed servers by the search query. We pair each server\n // with its catalog entry (if any) so the search can match friendly\n // names like \"Slack\" against a stdio server whose own `.name` is\n // just \"slack\".\n const filteredInstalledServers = allServers.filter((server) =>\n installedServerMatchesQuery(\n server,\n findCatalogEntryForServer(server, mcpMarketplace),\n searchQuery,\n ),\n );\n\n const handleMarketplaceInstall = (entry: MarketplaceEntry) => {\n setInstallEntry(entry);\n };\n\n const handleEdit = (server: MCPServerConfig) => {\n setEditingServer(server);\n };\n\n // Disabling withholds the server from the agent while leaving its entry —\n // and its secrets — in `mcp_config`. `useUpdateMcpServer` is reused rather\n // than given a bespoke hook because it already substitutes redacted\n // credentials, without which the save would drop them.\n const handleToggleEnabled = (server: MCPServerConfig, enabled: boolean) => {\n updateMcpServer(\n { serverId: server.id, server: { ...server, enabled } },\n {\n onError: (err) => {\n const message = retrieveAxiosErrorMessage(err as AxiosError);\n displayErrorToast(message || t(I18nKey.ERROR$GENERIC));\n },\n },\n );\n };\n\n if (isLoading || !settings) {\n return (\n <div\n data-testid=\"mcp-page\"\n className=\"flex h-full gap-4 md:gap-6 md:pl-8 lg:gap-10 lg:pl-10\"\n >\n <ExtensionsNavigation />\n <div className=\"flex h-full flex-1 items-center justify-center px-4 md:px-0\">\n <div className=\"h-8 w-8 rounded-full border-2 border-transparent border-t-white animate-spin\" />\n </div>\n </div>\n );\n }\n\n return (\n <div\n data-testid=\"mcp-page\"\n className=\"flex h-full gap-4 md:gap-6 md:pl-8 lg:gap-10 lg:pl-10\"\n >\n <ExtensionsNavigation />\n <main className={settingsLikeMainScrollClassName}>\n <div className=\"mx-auto flex w-full min-w-0 max-w-[800px] flex-col gap-6\">\n <div className=\"min-w-0\">\n <div className=\"flex items-start justify-between gap-4\">\n <div className=\"space-y-1\">\n <h2 className=\"text-xl font-medium leading-6 text-foreground\">\n {t(I18nKey.SETTINGS$MCP_TITLE)}\n </h2>\n <div className=\"max-w-2xl text-sm text-tertiary-light\">\n {t(I18nKey.MCP$PAGE_DESCRIPTION)}\n </div>\n </div>\n <BrandButton\n type=\"button\"\n variant=\"secondary\"\n testId=\"mcp-add-custom-server\"\n className=\"flex-shrink-0 whitespace-nowrap\"\n onClick={() => setEditingServer({ id: \"\", type: \"sse\" })}\n >\n {t(I18nKey.MCP$ADD_CUSTOM)}\n </BrandButton>\n </div>\n </div>\n\n <McpToolbar\n search={searchQuery}\n onSearchChange={setSearchQuery}\n sectionFilter={sectionFilter}\n onSectionFilterChange={setSectionFilter}\n />\n\n {sectionFilter !== \"library\" ? (\n <section className=\"flex flex-col gap-3\">\n <h2 className=\"text-base font-semibold text-foreground\">\n {t(I18nKey.MCP$INSTALLED_TITLE)}\n </h2>\n <InstalledServersSection\n servers={filteredInstalledServers}\n hasAnyInstalled={allServers.length > 0}\n query={searchQuery}\n onEdit={handleEdit}\n onToggleEnabled={handleToggleEnabled}\n />\n </section>\n ) : null}\n\n {sectionFilter !== \"installed\" ? (\n <MarketplaceSection\n onSelect={handleMarketplaceInstall}\n onAdd={handleMarketplaceInstall}\n query={searchQuery}\n />\n ) : null}\n </div>\n\n {installEntry && (\n <InstallServerModal\n entry={installEntry}\n existingServers={allServers}\n onClose={() => setInstallEntry(null)}\n />\n )}\n\n {/* Custom (or non-marketplace) server editor. The empty-id\n sentinel (`{ id: \"\", type: \"sse\" }`) means \"add new\". */}\n {editingServer && (\n <CustomServerEditor\n server={editingServer}\n existingServers={allServers}\n onClose={() => setEditingServer(null)}\n />\n )}\n </main>\n </div>\n );\n}\n"],"mappings":"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"}
@@ -1,133 +1,124 @@
1
1
  import { useTranslation as e } from "../node_modules/react-i18next/dist/es/useTranslation.js";
2
2
  import { I18nKey as t } from "../i18n/declaration.js";
3
3
  import { retrieveAxiosErrorMessage as n } from "../utils/retrieve-axios-error-message.js";
4
- import { displayErrorToast as r, displaySuccessToast as i } from "../utils/custom-toast-handlers.js";
5
- import { parseMcpConfig as a } from "../utils/mcp-config.js";
6
- import { useSettings as o } from "../hooks/query/use-settings.js";
7
- import { BrandButton as s } from "../components/features/settings/brand-button.js";
8
- import { settingsLikeMainScrollClassName as c } from "../utils/settings-like-page-layout-classes.js";
9
- import { ExtensionsNavigation as l } from "../components/features/skills/extensions-navigation.js";
10
- import { ConfirmationModal as u } from "../components/shared/modals/confirmation-modal.js";
11
- import { useDeleteMcpServer as d } from "../hooks/mutation/use-delete-mcp-server.js";
12
- import { findCatalogEntryForServer as f, getMcpMarketplaceCatalog as p, installedServerMatchesQuery as m } from "../utils/mcp-marketplace-utils.js";
13
- import h from "../node_modules/@openhands/extensions/integrations/index.js";
14
- import { flattenMcpConfig as g } from "../utils/mcp-installed-servers.js";
15
- import { InstalledServersSection as _ } from "../components/features/mcp-page/installed-servers-section.js";
16
- import { MarketplaceSection as v } from "../components/features/mcp-page/marketplace-section.js";
17
- import { InstallServerModal as y } from "../components/features/mcp-page/install-server-modal.js";
18
- import { CustomServerEditor as b } from "../components/features/mcp-page/custom-server-editor.js";
19
- import { McpToolbar as x } from "../components/features/mcp-page/mcp-toolbar.js";
4
+ import { displayErrorToast as r } from "../utils/custom-toast-handlers.js";
5
+ import { parseMcpConfig as i } from "../utils/mcp-config.js";
6
+ import { useSettings as a } from "../hooks/query/use-settings.js";
7
+ import { BrandButton as o } from "../components/features/settings/brand-button.js";
8
+ import { settingsLikeMainScrollClassName as s } from "../utils/settings-like-page-layout-classes.js";
9
+ import { ExtensionsNavigation as c } from "../components/features/skills/extensions-navigation.js";
10
+ import { useUpdateMcpServer as l } from "../hooks/mutation/use-update-mcp-server.js";
11
+ import { findCatalogEntryForServer as u, getMcpMarketplaceCatalog as d, installedServerMatchesQuery as f } from "../utils/mcp-marketplace-utils.js";
12
+ import p from "../node_modules/@openhands/extensions/integrations/index.js";
13
+ import { flattenMcpConfig as m } from "../utils/mcp-installed-servers.js";
14
+ import { InstalledServersSection as h } from "../components/features/mcp-page/installed-servers-section.js";
15
+ import { MarketplaceSection as g } from "../components/features/mcp-page/marketplace-section.js";
16
+ import { InstallServerModal as _ } from "../components/features/mcp-page/install-server-modal.js";
17
+ import { CustomServerEditor as v } from "../components/features/mcp-page/custom-server-editor.js";
18
+ import { McpToolbar as y } from "../components/features/mcp-page/mcp-toolbar.js";
20
19
  import "../components/features/mcp-page/index.js";
21
- import S from "react";
22
- import { jsx as C, jsxs as w } from "react/jsx-runtime";
20
+ import b from "react";
21
+ import { jsx as x, jsxs as S } from "react/jsx-runtime";
23
22
  //#region src/routes/mcp.tsx
24
- function T() {
25
- let { t: T } = e("openhands"), { data: E, isLoading: D } = o(), { mutate: O, isPending: k } = d(), [A, j] = S.useState(null), [M, N] = S.useState(null), [P, F] = S.useState(null), [I, L] = S.useState(""), [R, z] = S.useState("all"), B = g(a(E?.agent_settings?.mcp_config)), V = p(h), H = B.filter((e) => m(e, f(e, V), I)), U = (e) => {
26
- j(e);
23
+ function C() {
24
+ let { t: C } = e("openhands"), { data: w, isLoading: T } = a(), { mutate: E } = l(), [D, O] = b.useState(null), [k, A] = b.useState(null), [j, M] = b.useState(""), [N, P] = b.useState("all"), F = m(i(w?.agent_settings?.mcp_config)), I = d(p), L = F.filter((e) => f(e, u(e, I), j)), R = (e) => {
25
+ O(e);
27
26
  };
28
- return D || !E ? /* @__PURE__ */ w("div", {
27
+ return T || !w ? /* @__PURE__ */ S("div", {
29
28
  "data-testid": "mcp-page",
30
29
  className: "flex h-full gap-4 md:gap-6 md:pl-8 lg:gap-10 lg:pl-10",
31
- children: [/* @__PURE__ */ C(l, {}), /* @__PURE__ */ C("div", {
30
+ children: [/* @__PURE__ */ x(c, {}), /* @__PURE__ */ x("div", {
32
31
  className: "flex h-full flex-1 items-center justify-center px-4 md:px-0",
33
- children: /* @__PURE__ */ C("div", { className: "h-8 w-8 rounded-full border-2 border-transparent border-t-white animate-spin" })
32
+ children: /* @__PURE__ */ x("div", { className: "h-8 w-8 rounded-full border-2 border-transparent border-t-white animate-spin" })
34
33
  })]
35
- }) : /* @__PURE__ */ w("div", {
34
+ }) : /* @__PURE__ */ S("div", {
36
35
  "data-testid": "mcp-page",
37
36
  className: "flex h-full gap-4 md:gap-6 md:pl-8 lg:gap-10 lg:pl-10",
38
- children: [/* @__PURE__ */ C(l, {}), /* @__PURE__ */ w("main", {
39
- className: c,
37
+ children: [/* @__PURE__ */ x(c, {}), /* @__PURE__ */ S("main", {
38
+ className: s,
40
39
  children: [
41
- /* @__PURE__ */ w("div", {
40
+ /* @__PURE__ */ S("div", {
42
41
  className: "mx-auto flex w-full min-w-0 max-w-[800px] flex-col gap-6",
43
42
  children: [
44
- /* @__PURE__ */ C("div", {
43
+ /* @__PURE__ */ x("div", {
45
44
  className: "min-w-0",
46
- children: /* @__PURE__ */ w("div", {
45
+ children: /* @__PURE__ */ S("div", {
47
46
  className: "flex items-start justify-between gap-4",
48
- children: [/* @__PURE__ */ w("div", {
47
+ children: [/* @__PURE__ */ S("div", {
49
48
  className: "space-y-1",
50
- children: [/* @__PURE__ */ C("h2", {
49
+ children: [/* @__PURE__ */ x("h2", {
51
50
  className: "text-xl font-medium leading-6 text-foreground",
52
- children: T(t.SETTINGS$MCP_TITLE)
53
- }), /* @__PURE__ */ C("div", {
51
+ children: C(t.SETTINGS$MCP_TITLE)
52
+ }), /* @__PURE__ */ x("div", {
54
53
  className: "max-w-2xl text-sm text-tertiary-light",
55
- children: T(t.MCP$PAGE_DESCRIPTION)
54
+ children: C(t.MCP$PAGE_DESCRIPTION)
56
55
  })]
57
- }), /* @__PURE__ */ C(s, {
56
+ }), /* @__PURE__ */ x(o, {
58
57
  type: "button",
59
58
  variant: "secondary",
60
59
  testId: "mcp-add-custom-server",
61
60
  className: "flex-shrink-0 whitespace-nowrap",
62
- onClick: () => N({
61
+ onClick: () => A({
63
62
  id: "",
64
63
  type: "sse"
65
64
  }),
66
- children: T(t.MCP$ADD_CUSTOM)
65
+ children: C(t.MCP$ADD_CUSTOM)
67
66
  })]
68
67
  })
69
68
  }),
70
- /* @__PURE__ */ C(x, {
71
- search: I,
72
- onSearchChange: L,
73
- sectionFilter: R,
74
- onSectionFilterChange: z
69
+ /* @__PURE__ */ x(y, {
70
+ search: j,
71
+ onSearchChange: M,
72
+ sectionFilter: N,
73
+ onSectionFilterChange: P
75
74
  }),
76
- R === "library" ? null : /* @__PURE__ */ w("section", {
75
+ N === "library" ? null : /* @__PURE__ */ S("section", {
77
76
  className: "flex flex-col gap-3",
78
- children: [/* @__PURE__ */ C("h2", {
77
+ children: [/* @__PURE__ */ x("h2", {
79
78
  className: "text-base font-semibold text-foreground",
80
- children: T(t.MCP$INSTALLED_TITLE)
81
- }), /* @__PURE__ */ C(_, {
82
- servers: H,
83
- hasAnyInstalled: B.length > 0,
84
- query: I,
79
+ children: C(t.MCP$INSTALLED_TITLE)
80
+ }), /* @__PURE__ */ x(h, {
81
+ servers: L,
82
+ hasAnyInstalled: F.length > 0,
83
+ query: j,
85
84
  onEdit: (e) => {
86
- N(e);
85
+ A(e);
87
86
  },
88
- onDelete: (e) => {
89
- let t = B.find((t) => t.id === e);
90
- t && F(t);
87
+ onToggleEnabled: (e, i) => {
88
+ E({
89
+ serverId: e.id,
90
+ server: {
91
+ ...e,
92
+ enabled: i
93
+ }
94
+ }, { onError: (e) => {
95
+ r(n(e) || C(t.ERROR$GENERIC));
96
+ } });
91
97
  }
92
98
  })]
93
99
  }),
94
- R === "installed" ? null : /* @__PURE__ */ C(v, {
95
- onSelect: U,
96
- onAdd: U,
97
- query: I
100
+ N === "installed" ? null : /* @__PURE__ */ x(g, {
101
+ onSelect: R,
102
+ onAdd: R,
103
+ query: j
98
104
  })
99
105
  ]
100
106
  }),
101
- A && /* @__PURE__ */ C(y, {
102
- entry: A,
103
- existingServers: B,
104
- onClose: () => j(null)
107
+ D && /* @__PURE__ */ x(_, {
108
+ entry: D,
109
+ existingServers: F,
110
+ onClose: () => O(null)
105
111
  }),
106
- M && /* @__PURE__ */ C(b, {
107
- server: M,
108
- existingServers: B,
109
- onClose: () => N(null)
110
- }),
111
- P && /* @__PURE__ */ C(u, {
112
- text: T(t.SETTINGS$MCP_CONFIRM_DELETE),
113
- onCancel: () => F(null),
114
- onConfirm: () => {
115
- P && O(P, {
116
- onSuccess: () => {
117
- i(T(t.MCP$REMOVE_SUCCESS)), F(null);
118
- },
119
- onError: (e) => {
120
- r(n(e) || T(t.ERROR$GENERIC)), F(null);
121
- }
122
- });
123
- },
124
- isConfirming: k
112
+ k && /* @__PURE__ */ x(v, {
113
+ server: k,
114
+ existingServers: F,
115
+ onClose: () => A(null)
125
116
  })
126
117
  ]
127
118
  })]
128
119
  });
129
120
  }
130
121
  //#endregion
131
- export { T as default };
122
+ export { C as default };
132
123
 
133
124
  //# sourceMappingURL=mcp.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"mcp.js","names":[],"sources":["../../src/routes/mcp.tsx"],"sourcesContent":["import React from \"react\";\nimport { AxiosError } from \"axios\";\nimport { ExtensionsNavigation } from \"#/components/features/skills/extensions-navigation\";\nimport { useTranslation } from \"react-i18next\";\nimport { I18nKey } from \"#/i18n/declaration\";\nimport { BrandButton } from \"#/components/features/settings/brand-button\";\nimport { ConfirmationModal } from \"#/components/shared/modals/confirmation-modal\";\nimport { useSettings } from \"#/hooks/query/use-settings\";\nimport { useDeleteMcpServer } from \"#/hooks/mutation/use-delete-mcp-server\";\nimport { parseMcpConfig } from \"#/utils/mcp-config\";\nimport {\n displayErrorToast,\n displaySuccessToast,\n} from \"#/utils/custom-toast-handlers\";\nimport { retrieveAxiosErrorMessage } from \"#/utils/retrieve-axios-error-message\";\nimport { settingsLikeMainScrollClassName } from \"#/utils/settings-like-page-layout-classes\";\nimport {\n findCatalogEntryForServer,\n getMcpMarketplaceCatalog,\n installedServerMatchesQuery,\n} from \"#/utils/mcp-marketplace-utils\";\nimport {\n INTEGRATION_CATALOG as MCP_MARKETPLACE,\n type IntegrationCatalogEntry as MarketplaceEntry,\n} from \"@openhands/extensions/integrations\";\nimport { MCPServerConfig } from \"#/types/mcp-server\";\nimport { flattenMcpConfig } from \"#/utils/mcp-installed-servers\";\nimport {\n InstalledServersSection,\n McpToolbar,\n MarketplaceSection,\n InstallServerModal,\n CustomServerEditor,\n type McpSectionFilter,\n} from \"#/components/features/mcp-page\";\n\n// No ACP guard here (unlike `/settings` and `/settings/condenser`): MCP\n// servers configured via `agent_settings.mcp_config` are now forwarded to\n// the ACP subprocess at session creation, so this page is meaningful for\n// both OpenHands and ACP agents. The same editor and MCP settings storage\n// drive both kinds.\n\nexport default function MCPPage() {\n const { t } = useTranslation(\"openhands\");\n const { data: settings, isLoading } = useSettings();\n const { mutate: deleteMcpServer, isPending: isDeleting } =\n useDeleteMcpServer();\n\n const [installEntry, setInstallEntry] =\n React.useState<MarketplaceEntry | null>(null);\n const [editingServer, setEditingServer] =\n React.useState<MCPServerConfig | null>(null);\n const [serverToDelete, setServerToDelete] =\n React.useState<MCPServerConfig | null>(null);\n const [searchQuery, setSearchQuery] = React.useState(\"\");\n const [sectionFilter, setSectionFilter] =\n React.useState<McpSectionFilter>(\"all\");\n\n const mcpConfig = parseMcpConfig(settings?.agent_settings?.mcp_config);\n const allServers = flattenMcpConfig(mcpConfig);\n const mcpMarketplace = getMcpMarketplaceCatalog(MCP_MARKETPLACE);\n\n // Filter installed servers by the search query. We pair each server\n // with its catalog entry (if any) so the search can match friendly\n // names like \"Slack\" against a stdio server whose own `.name` is\n // just \"slack\".\n const filteredInstalledServers = allServers.filter((server) =>\n installedServerMatchesQuery(\n server,\n findCatalogEntryForServer(server, mcpMarketplace),\n searchQuery,\n ),\n );\n\n const handleMarketplaceInstall = (entry: MarketplaceEntry) => {\n setInstallEntry(entry);\n };\n\n const handleEdit = (server: MCPServerConfig) => {\n setEditingServer(server);\n };\n\n const handleDeleteClick = (serverId: string) => {\n const target = allServers.find((s) => s.id === serverId);\n if (target) setServerToDelete(target);\n };\n\n const handleConfirmDelete = () => {\n if (!serverToDelete) return;\n // Pass the full server config — useDeleteMcpServer re-resolves its\n // position against the fresh settings at mutation time, so a\n // background refresh between this click and confirm cannot point\n // us at the wrong index.\n deleteMcpServer(serverToDelete, {\n onSuccess: () => {\n displaySuccessToast(t(I18nKey.MCP$REMOVE_SUCCESS));\n setServerToDelete(null);\n },\n onError: (err) => {\n const message = retrieveAxiosErrorMessage(err as AxiosError);\n displayErrorToast(message || t(I18nKey.ERROR$GENERIC));\n setServerToDelete(null);\n },\n });\n };\n\n if (isLoading || !settings) {\n return (\n <div\n data-testid=\"mcp-page\"\n className=\"flex h-full gap-4 md:gap-6 md:pl-8 lg:gap-10 lg:pl-10\"\n >\n <ExtensionsNavigation />\n <div className=\"flex h-full flex-1 items-center justify-center px-4 md:px-0\">\n <div className=\"h-8 w-8 rounded-full border-2 border-transparent border-t-white animate-spin\" />\n </div>\n </div>\n );\n }\n\n return (\n <div\n data-testid=\"mcp-page\"\n className=\"flex h-full gap-4 md:gap-6 md:pl-8 lg:gap-10 lg:pl-10\"\n >\n <ExtensionsNavigation />\n <main className={settingsLikeMainScrollClassName}>\n <div className=\"mx-auto flex w-full min-w-0 max-w-[800px] flex-col gap-6\">\n <div className=\"min-w-0\">\n <div className=\"flex items-start justify-between gap-4\">\n <div className=\"space-y-1\">\n <h2 className=\"text-xl font-medium leading-6 text-foreground\">\n {t(I18nKey.SETTINGS$MCP_TITLE)}\n </h2>\n <div className=\"max-w-2xl text-sm text-tertiary-light\">\n {t(I18nKey.MCP$PAGE_DESCRIPTION)}\n </div>\n </div>\n <BrandButton\n type=\"button\"\n variant=\"secondary\"\n testId=\"mcp-add-custom-server\"\n className=\"flex-shrink-0 whitespace-nowrap\"\n onClick={() => setEditingServer({ id: \"\", type: \"sse\" })}\n >\n {t(I18nKey.MCP$ADD_CUSTOM)}\n </BrandButton>\n </div>\n </div>\n\n <McpToolbar\n search={searchQuery}\n onSearchChange={setSearchQuery}\n sectionFilter={sectionFilter}\n onSectionFilterChange={setSectionFilter}\n />\n\n {sectionFilter !== \"library\" ? (\n <section className=\"flex flex-col gap-3\">\n <h2 className=\"text-base font-semibold text-foreground\">\n {t(I18nKey.MCP$INSTALLED_TITLE)}\n </h2>\n <InstalledServersSection\n servers={filteredInstalledServers}\n hasAnyInstalled={allServers.length > 0}\n query={searchQuery}\n onEdit={handleEdit}\n onDelete={handleDeleteClick}\n />\n </section>\n ) : null}\n\n {sectionFilter !== \"installed\" ? (\n <MarketplaceSection\n onSelect={handleMarketplaceInstall}\n onAdd={handleMarketplaceInstall}\n query={searchQuery}\n />\n ) : null}\n </div>\n\n {installEntry && (\n <InstallServerModal\n entry={installEntry}\n existingServers={allServers}\n onClose={() => setInstallEntry(null)}\n />\n )}\n\n {/* Custom (or non-marketplace) server editor. The empty-id\n sentinel (`{ id: \"\", type: \"sse\" }`) means \"add new\". */}\n {editingServer && (\n <CustomServerEditor\n server={editingServer}\n existingServers={allServers}\n onClose={() => setEditingServer(null)}\n />\n )}\n\n {serverToDelete && (\n <ConfirmationModal\n text={t(I18nKey.SETTINGS$MCP_CONFIRM_DELETE)}\n onCancel={() => setServerToDelete(null)}\n onConfirm={handleConfirmDelete}\n isConfirming={isDeleting}\n />\n )}\n </main>\n </div>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"mcp.js","names":[],"sources":["../../src/routes/mcp.tsx"],"sourcesContent":["import React from \"react\";\nimport { AxiosError } from \"axios\";\nimport { ExtensionsNavigation } from \"#/components/features/skills/extensions-navigation\";\nimport { useTranslation } from \"react-i18next\";\nimport { I18nKey } from \"#/i18n/declaration\";\nimport { BrandButton } from \"#/components/features/settings/brand-button\";\nimport { useSettings } from \"#/hooks/query/use-settings\";\nimport { useUpdateMcpServer } from \"#/hooks/mutation/use-update-mcp-server\";\nimport { parseMcpConfig } from \"#/utils/mcp-config\";\nimport { displayErrorToast } from \"#/utils/custom-toast-handlers\";\nimport { retrieveAxiosErrorMessage } from \"#/utils/retrieve-axios-error-message\";\nimport { settingsLikeMainScrollClassName } from \"#/utils/settings-like-page-layout-classes\";\nimport {\n findCatalogEntryForServer,\n getMcpMarketplaceCatalog,\n installedServerMatchesQuery,\n} from \"#/utils/mcp-marketplace-utils\";\nimport {\n INTEGRATION_CATALOG as MCP_MARKETPLACE,\n type IntegrationCatalogEntry as MarketplaceEntry,\n} from \"@openhands/extensions/integrations\";\nimport { MCPServerConfig } from \"#/types/mcp-server\";\nimport { flattenMcpConfig } from \"#/utils/mcp-installed-servers\";\nimport {\n InstalledServersSection,\n McpToolbar,\n MarketplaceSection,\n InstallServerModal,\n CustomServerEditor,\n type McpSectionFilter,\n} from \"#/components/features/mcp-page\";\n\n// No ACP guard here (unlike `/settings` and `/settings/condenser`): MCP\n// servers configured via `agent_settings.mcp_config` are now forwarded to\n// the ACP subprocess at session creation, so this page is meaningful for\n// both OpenHands and ACP agents. The same editor and MCP settings storage\n// drive both kinds.\n\nexport default function MCPPage() {\n const { t } = useTranslation(\"openhands\");\n const { data: settings, isLoading } = useSettings();\n const { mutate: updateMcpServer } = useUpdateMcpServer();\n\n const [installEntry, setInstallEntry] =\n React.useState<MarketplaceEntry | null>(null);\n const [editingServer, setEditingServer] =\n React.useState<MCPServerConfig | null>(null);\n const [searchQuery, setSearchQuery] = React.useState(\"\");\n const [sectionFilter, setSectionFilter] =\n React.useState<McpSectionFilter>(\"all\");\n\n const mcpConfig = parseMcpConfig(settings?.agent_settings?.mcp_config);\n const allServers = flattenMcpConfig(mcpConfig);\n const mcpMarketplace = getMcpMarketplaceCatalog(MCP_MARKETPLACE);\n\n // Filter installed servers by the search query. We pair each server\n // with its catalog entry (if any) so the search can match friendly\n // names like \"Slack\" against a stdio server whose own `.name` is\n // just \"slack\".\n const filteredInstalledServers = allServers.filter((server) =>\n installedServerMatchesQuery(\n server,\n findCatalogEntryForServer(server, mcpMarketplace),\n searchQuery,\n ),\n );\n\n const handleMarketplaceInstall = (entry: MarketplaceEntry) => {\n setInstallEntry(entry);\n };\n\n const handleEdit = (server: MCPServerConfig) => {\n setEditingServer(server);\n };\n\n // Disabling withholds the server from the agent while leaving its entry —\n // and its secrets — in `mcp_config`. `useUpdateMcpServer` is reused rather\n // than given a bespoke hook because it already substitutes redacted\n // credentials, without which the save would drop them.\n const handleToggleEnabled = (server: MCPServerConfig, enabled: boolean) => {\n updateMcpServer(\n { serverId: server.id, server: { ...server, enabled } },\n {\n onError: (err) => {\n const message = retrieveAxiosErrorMessage(err as AxiosError);\n displayErrorToast(message || t(I18nKey.ERROR$GENERIC));\n },\n },\n );\n };\n\n if (isLoading || !settings) {\n return (\n <div\n data-testid=\"mcp-page\"\n className=\"flex h-full gap-4 md:gap-6 md:pl-8 lg:gap-10 lg:pl-10\"\n >\n <ExtensionsNavigation />\n <div className=\"flex h-full flex-1 items-center justify-center px-4 md:px-0\">\n <div className=\"h-8 w-8 rounded-full border-2 border-transparent border-t-white animate-spin\" />\n </div>\n </div>\n );\n }\n\n return (\n <div\n data-testid=\"mcp-page\"\n className=\"flex h-full gap-4 md:gap-6 md:pl-8 lg:gap-10 lg:pl-10\"\n >\n <ExtensionsNavigation />\n <main className={settingsLikeMainScrollClassName}>\n <div className=\"mx-auto flex w-full min-w-0 max-w-[800px] flex-col gap-6\">\n <div className=\"min-w-0\">\n <div className=\"flex items-start justify-between gap-4\">\n <div className=\"space-y-1\">\n <h2 className=\"text-xl font-medium leading-6 text-foreground\">\n {t(I18nKey.SETTINGS$MCP_TITLE)}\n </h2>\n <div className=\"max-w-2xl text-sm text-tertiary-light\">\n {t(I18nKey.MCP$PAGE_DESCRIPTION)}\n </div>\n </div>\n <BrandButton\n type=\"button\"\n variant=\"secondary\"\n testId=\"mcp-add-custom-server\"\n className=\"flex-shrink-0 whitespace-nowrap\"\n onClick={() => setEditingServer({ id: \"\", type: \"sse\" })}\n >\n {t(I18nKey.MCP$ADD_CUSTOM)}\n </BrandButton>\n </div>\n </div>\n\n <McpToolbar\n search={searchQuery}\n onSearchChange={setSearchQuery}\n sectionFilter={sectionFilter}\n onSectionFilterChange={setSectionFilter}\n />\n\n {sectionFilter !== \"library\" ? (\n <section className=\"flex flex-col gap-3\">\n <h2 className=\"text-base font-semibold text-foreground\">\n {t(I18nKey.MCP$INSTALLED_TITLE)}\n </h2>\n <InstalledServersSection\n servers={filteredInstalledServers}\n hasAnyInstalled={allServers.length > 0}\n query={searchQuery}\n onEdit={handleEdit}\n onToggleEnabled={handleToggleEnabled}\n />\n </section>\n ) : null}\n\n {sectionFilter !== \"installed\" ? (\n <MarketplaceSection\n onSelect={handleMarketplaceInstall}\n onAdd={handleMarketplaceInstall}\n query={searchQuery}\n />\n ) : null}\n </div>\n\n {installEntry && (\n <InstallServerModal\n entry={installEntry}\n existingServers={allServers}\n onClose={() => setInstallEntry(null)}\n />\n )}\n\n {/* Custom (or non-marketplace) server editor. The empty-id\n sentinel (`{ id: \"\", type: \"sse\" }`) means \"add new\". */}\n {editingServer && (\n <CustomServerEditor\n server={editingServer}\n existingServers={allServers}\n onClose={() => setEditingServer(null)}\n />\n )}\n </main>\n </div>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;AAsCA,SAAwB,IAAU;CAChC,IAAM,EAAE,SAAM,EAAe,WAAW,GAClC,EAAE,MAAM,GAAU,iBAAc,EAAY,GAC5C,EAAE,QAAQ,MAAoB,EAAmB,GAEjD,CAAC,GAAc,KACnB,EAAM,SAAkC,IAAI,GACxC,CAAC,GAAe,KACpB,EAAM,SAAiC,IAAI,GACvC,CAAC,GAAa,KAAkB,EAAM,SAAS,EAAE,GACjD,CAAC,GAAe,KACpB,EAAM,SAA2B,KAAK,GAGlC,IAAa,EADD,EAAe,GAAU,gBAAgB,UACvB,CAAS,GACvC,IAAiB,EAAyB,CAAe,GAMzD,IAA2B,EAAW,QAAQ,MAClD,EACE,GACA,EAA0B,GAAQ,CAAc,GAChD,CACF,CACF,GAEM,KAA4B,MAA4B;EAC5D,EAAgB,CAAK;CACvB;CAoCA,OAdI,KAAa,CAAC,IAEd,kBAAC,OAAD;EACE,eAAY;EACZ,WAAU;YAFZ,CAIE,kBAAC,GAAD,CAAuB,CAAA,GACvB,kBAAC,OAAD;GAAK,WAAU;aACb,kBAAC,OAAD,EAAK,WAAU,+EAAgF,CAAA;EAC5F,CAAA,CACF;MAKP,kBAAC,OAAD;EACE,eAAY;EACZ,WAAU;YAFZ,CAIE,kBAAC,GAAD,CAAuB,CAAA,GACvB,kBAAC,QAAD;GAAM,WAAW;aAAjB;IACE,kBAAC,OAAD;KAAK,WAAU;eAAf;MACE,kBAAC,OAAD;OAAK,WAAU;iBACb,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,OAAD;SAAK,WAAU;mBAAf,CACE,kBAAC,MAAD;UAAI,WAAU;oBACX,EAAE,EAAQ,kBAAkB;SAC3B,CAAA,GACJ,kBAAC,OAAD;UAAK,WAAU;oBACZ,EAAE,EAAQ,oBAAoB;SAC5B,CAAA,CACF;YACL,kBAAC,GAAD;SACE,MAAK;SACL,SAAQ;SACR,QAAO;SACP,WAAU;SACV,eAAe,EAAiB;UAAE,IAAI;UAAI,MAAM;SAAM,CAAC;mBAEtD,EAAE,EAAQ,cAAc;QACd,CAAA,CACV;;MACF,CAAA;MAEL,kBAAC,GAAD;OACE,QAAQ;OACR,gBAAgB;OACD;OACf,uBAAuB;MACxB,CAAA;MAEA,MAAkB,YAaf,OAZF,kBAAC,WAAD;OAAS,WAAU;iBAAnB,CACE,kBAAC,MAAD;QAAI,WAAU;kBACX,EAAE,EAAQ,mBAAmB;OAC5B,CAAA,GACJ,kBAAC,GAAD;QACE,SAAS;QACT,iBAAiB,EAAW,SAAS;QACrC,OAAO;QACP,SAhFM,MAA4B;SAC9C,EAAiB,CAAM;QACzB;QA+Ec,kBAzEe,GAAyB,MAAqB;SACzE,EACE;UAAE,UAAU,EAAO;UAAI,QAAQ;WAAE,GAAG;WAAQ;UAAQ;SAAE,GACtD,EACE,UAAU,MAAQ;UAEhB,EADgB,EAA0B,CACxB,KAAW,EAAE,EAAQ,aAAa,CAAC;SACvD,EACF,CACF;QACF;OAgEa,CAAA,CACM;;MAGV,MAAkB,cAMf,OALF,kBAAC,GAAD;OACE,UAAU;OACV,OAAO;OACP,OAAO;MACR,CAAA;KAEA;;IAEJ,KACC,kBAAC,GAAD;KACE,OAAO;KACP,iBAAiB;KACjB,eAAe,EAAgB,IAAI;IACpC,CAAA;IAKF,KACC,kBAAC,GAAD;KACE,QAAQ;KACR,iBAAiB;KACjB,eAAe,EAAiB,IAAI;IACrC,CAAA;GAEC;IACH;;AAET"}
@@ -1,2 +1,2 @@
1
- require("../_virtual/_rolldown/runtime.cjs");const e=require("../api/agent-server-config.cjs"),t=require("../config/defaults.cjs"),n=require("../package.cjs"),r=require("../api/client-source.cjs"),i=require("./telemetry-context.cjs");var a=`openhands-telemetry-consent`,o=`openhands-telemetry-consent-pending-cloud-sync`,s=`openhands-telemetry-consent-change`,c=`openhands-telemetry-first-use`,l=`openhands-telemetry-session`,u=`agent-canvas`,d=`history_change`,f=t.default.telemetry.posthogApiKey,p=`https://z.openhands.dev`,m=`https://us.posthog.com`,h=null,g=null,_,v={},y=!1,b=Object.freeze({client_source:r.AGENT_CANVAS_CLIENT_SOURCE,client_version:r.AGENT_CANVAS_CLIENT_VERSION,package_name:n.default.name,package_version:n.default.version}),x=i.getBackendTelemetryProperties({}),S=i.getCloudTelemetryProperties();function C(e){x=i.getBackendTelemetryProperties(e)}function w(e){return e?{...e,properties:{...x,...S,...e.properties,...b}}:null}function T(e){y||F()!==`granted`?e.opt_out_capturing():e.opt_in_capturing()}function E(e,t=!1){e.reset(t),T(e)}function D(e){e.get_property?.(`$user_id`)!=null&&E(e)}function O(e){if(e===!1){if(y)return;y=!0,h?.opt_out_capturing(),L();return}let t=y;if(y=!1,!h&&!g){let t=Object.fromEntries(Object.entries(e).filter(([,e])=>e!==void 0));v={...v,...t}}t&&(h&&T(h),L())}function k(){return y?null:{apiKey:v.apiKey||f,apiHost:v.apiHost||p,uiHost:v.uiHost||m}}function A(){return typeof window<`u`&&typeof localStorage<`u`}async function j(){if(!A())return null;if(h)return h;try{let{default:e}=await Promise.resolve().then(()=>require("../node_modules/posthog-js/dist/module.cjs"));return e}catch{return null}}function M(){return!!(y||typeof process<`u`&&process.env?.DO_NOT_TRACK===`1`||typeof navigator<`u`&&(navigator.doNotTrack===`1`||typeof window<`u`&&window.doNotTrack===`1`))}function N(e){h||(_=e)}async function P(e=!1){if(h)return h;if(g)return g;g=(async()=>{let t=k();if(!t)return null;let n=await j();if(!n)return null;let r=_,i=n.init(t.apiKey,{api_host:t.apiHost,ui_host:t.uiHost,opt_out_capturing_by_default:!e,persistence:`localStorage`,persistence_name:u,consent_persistence_name:`${u}-consent`,person_profiles:`always`,capture_pageview:d,autocapture:!0,disable_session_recording:!0,bootstrap:r,before_send:w},u);return i?(h=i,_=void 0,r||D(h),y?h.opt_out_capturing():F()===`granted`?h.opt_in_capturing():e||h.opt_out_capturing(),h):null})();try{return await g}finally{h||(g=null)}}function F(){if(!A())return`pending`;if(M())return`denied`;if(e.getLockedCloudAuthMode()===`cookie`)return`granted`;try{let e=localStorage.getItem(a);if(e===`granted`||e===`denied`)return e}catch{}return`pending`}function I(e){if(!A())return()=>{};let t=t=>{(t.key===a||t.key===o)&&e()};return window.addEventListener(s,e),window.addEventListener(`storage`,t),()=>{window.removeEventListener(s,e),window.removeEventListener(`storage`,t)}}function L(){A()&&window.dispatchEvent(new Event(s))}function R(e){if(A())try{localStorage.setItem(o,e)}catch{}}function z(e){if(A())try{if(e!==void 0&&localStorage.getItem(o)!==e)return;localStorage.removeItem(o),L()}catch{}}async function B(e,{syncToCloud:t=!0}={}){if(A())try{let t=F()===e;if(localStorage.setItem(a,e),y||t)return;let n=h??await P();if(!n)return;e===`granted`?n.opt_in_capturing():n.get_property?.(`$user_id`)==null?n.opt_out_capturing():E(n)}catch{}finally{t&&R(e),L()}}function V(){return F()===`granted`}function H(){if(!A())return!1;try{return localStorage.getItem(c)===`true`}catch{return!1}}function U(){if(A())try{localStorage.setItem(c,`true`)}catch{}}async function W(){if(M()||H())return;let e=await P(!0);!e||M()||((e.has_opted_out_capturing?.()??!1)&&e.opt_in_capturing(),e.capture(`canvas_install`,{platform:typeof navigator<`u`?navigator.platform:`unknown`,user_agent:typeof navigator<`u`?navigator.userAgent:`unknown`,referrer:typeof document<`u`?document.referrer:``,url_origin:typeof window<`u`?window.location.origin:``,embedded:typeof window<`u`&&window.self!==window.top}),U(),F()!==`granted`&&e.opt_out_capturing())}function G(){if(!A())return!1;try{return sessionStorage.getItem(l)===`true`}catch{return!1}}function K(){if(A())try{sessionStorage.setItem(l,`true`)}catch{}}async function q(){if(!V()||F()!==`granted`)return null;let e=await P();return!e||!V()?null:(e.has_opted_out_capturing?.()&&e.opt_in_capturing(),e)}async function J(){if(G())return;let e=await q();e&&(e.capture(`canvas_new_session`,{is_first_use:!H()}),K())}async function Y(){return(await q())?.get_distinct_id?.()??null}async function X(){return!A()||y||M()?null:(await P())?.get_distinct_id?.()??null}async function Z(e,t={}){let n=await q();n&&n.capture(e,t)}async function Q(e,t={}){let n=await q();n&&n.captureException(e,t)}async function $(){if(A()){try{localStorage.removeItem(a),localStorage.removeItem(c)}catch{}z();try{sessionStorage.removeItem(l)}catch{}x=i.getBackendTelemetryProperties({}),S=i.getCloudTelemetryProperties();try{h&&E(h,!0)}catch{try{h?.opt_out_capturing()}catch{}}}}exports.clearTelemetryData=$,exports.configurePostHogBootstrap=N,exports.configureTelemetry=O,exports.getTelemetryConsent=F,exports.getTelemetryDistinctId=Y,exports.getTelemetryDistinctIdForConsentSync=X,exports.initializePostHogClient=P,exports.isTelemetryEnabled=V,exports.setTelemetryBackendContext=C,exports.setTelemetryConsent=B,exports.subscribeTelemetryConsent=I,exports.trackEvent=Z,exports.trackException=Q,exports.trackInstall=W,exports.trackSessionStart=J;
1
+ require("../_virtual/_rolldown/runtime.cjs");const e=require("../api/agent-server-config.cjs"),t=require("../package.cjs"),n=require("../api/client-source.cjs"),r=require("./telemetry-context.cjs");var i=`openhands-telemetry-consent`,a=`openhands-telemetry-consent-pending-cloud-sync`,o=`openhands-telemetry-consent-pending-local-revocation`,s=`openhands-telemetry-consent-change`,c=`openhands-telemetry-first-use`,l=`openhands-telemetry-session`,u=`agent-canvas`,d=`history_change`,f=`phc_BgzfxKdgsYMLFTmJqt424ZoyVHvKFfrwttLimzdYTKFK`,p=`https://z.openhands.dev`,m=`https://us.posthog.com`,h=null,g=null,_,v={},y=!1,b,x=0,S=-1,C=Object.freeze({client_source:n.AGENT_CANVAS_CLIENT_SOURCE,client_version:n.AGENT_CANVAS_CLIENT_VERSION,package_name:t.default.name,package_version:t.default.version}),w=r.getBackendTelemetryProperties({}),T=r.getCloudTelemetryProperties();function E(e){w=r.getBackendTelemetryProperties(e)}function D(e){T=r.getCloudTelemetryProperties(e)}function O(e){return e?{...e,properties:{...w,...T,...e.properties,...C}}:null}function k(e){y||z()!==`granted`?e.opt_out_capturing():e.opt_in_capturing()}function A(e,t=!1){e.reset(t),S=-1,k(e)}function j(e){if(b===void 0||!Y())return;let t=b?.distinctId,n=e.get_property(`$user_id`);if(n!=null&&n!==t&&A(e),b===null){S=x;return}(e.get_property(`$user_id`)!==b.distinctId||S!==x)&&(e.identify(b.distinctId,b.properties),S=x)}function M(e,t){let n=Object.keys(e);return n.length===Object.keys(t).length&&n.every(n=>e[n]===t[n])}function ee(e){if(e===!1){if(y)return;y=!0,h?.opt_out_capturing(),H();return}let t=y;if(y=!1,!h&&!g){let t=Object.fromEntries(Object.entries(e).filter(([,e])=>e!==void 0));v={...v,...t}}t&&(h&&(k(h),j(h)),H())}function N(){return y?null:{apiKey:v.apiKey||f,apiHost:v.apiHost||p,uiHost:v.uiHost||m}}function P(){return typeof window<`u`&&typeof localStorage<`u`}async function F(){if(!P())return null;if(h)return h;try{let{default:e}=await Promise.resolve().then(()=>require("../node_modules/posthog-js/dist/module.cjs"));return e}catch{return null}}function I(){return!!(y||typeof process<`u`&&process.env?.DO_NOT_TRACK===`1`||typeof navigator<`u`&&(navigator.doNotTrack===`1`||typeof window<`u`&&window.doNotTrack===`1`))}function L(e){h||(_=e)}async function R(e=!1){if(h)return h;if(g)return g;g=(async()=>{let t=N();if(!t)return null;let n=await F();if(!n)return null;let r=n.init(t.apiKey,{api_host:t.apiHost,ui_host:t.uiHost,opt_out_capturing_by_default:!e,persistence:`localStorage`,persistence_name:u,consent_persistence_name:`${u}-consent`,person_profiles:`always`,capture_pageview:d,autocapture:!0,disable_session_recording:!0,bootstrap:_,before_send:O},u);return r?(h=r,_=void 0,y?h.opt_out_capturing():z()===`granted`?(h.opt_in_capturing(),j(h)):e||h.opt_out_capturing(),h):null})();try{return await g}finally{h||(g=null)}}function z(){if(!P())return`pending`;if(I())return`denied`;if(e.getLockedCloudAuthMode()===`cookie`)return`granted`;try{let e=localStorage.getItem(i);if(e===`granted`||e===`denied`)return e}catch{}return`pending`}function B(){if(!P())return null;try{return localStorage.getItem(o)}catch{return null}}function V(e){if(!P())return()=>{};let t=t=>{(t.key===i||t.key===a)&&e()};return window.addEventListener(s,e),window.addEventListener(`storage`,t),()=>{window.removeEventListener(s,e),window.removeEventListener(`storage`,t)}}function H(){P()&&window.dispatchEvent(new Event(s))}function U(e){if(P())try{localStorage.setItem(a,e)}catch{}}function W(e){if(!(!P()||!e))try{localStorage.setItem(o,e)}catch{}}function G(e){if(P())try{if(localStorage.getItem(o)!==e)return;localStorage.removeItem(o)}catch{}}function K(e){if(P())try{if(e!==void 0&&localStorage.getItem(a)!==e)return;localStorage.removeItem(a),H()}catch{}}async function q(e,{syncToCloud:t=!0}={}){if(P())try{let t=z()===e;if(localStorage.setItem(i,e),y||t)return;let n=h??await R();if(!n)return;e===`granted`?(n.opt_in_capturing(),j(n)):(W(n.get_distinct_id?.()??null),n.get_property?.(`$user_id`)==null?n.opt_out_capturing():A(n))}catch{}finally{t&&U(e),H()}}async function J(e,t={}){let n=e===null?null:{distinctId:e,properties:t};if(b===n||b!=null&&n!==null&&b.distinctId===n.distinctId&&M(b.properties,t)||(b=n,x+=1,S=-1,!Y()))return;let r=h??await R();r&&Y()&&j(r)}function Y(){return z()===`granted`}function X(){if(!P())return!1;try{return localStorage.getItem(c)===`true`}catch{return!1}}function Z(){if(P())try{localStorage.setItem(c,`true`)}catch{}}async function Q(){if(I()||X())return;let e=await R(!0);!e||I()||((e.has_opted_out_capturing?.()??!1)&&e.opt_in_capturing(),e.capture(`canvas_install`,{platform:typeof navigator<`u`?navigator.platform:`unknown`,user_agent:typeof navigator<`u`?navigator.userAgent:`unknown`,referrer:typeof document<`u`?document.referrer:``,url_origin:typeof window<`u`?window.location.origin:``,embedded:typeof window<`u`&&window.self!==window.top}),Z(),z()!==`granted`&&e.opt_out_capturing())}function te(){if(!P())return!1;try{return sessionStorage.getItem(l)===`true`}catch{return!1}}function ne(){if(P())try{sessionStorage.setItem(l,`true`)}catch{}}async function $(){if(!Y()||z()!==`granted`)return null;let e=await R();return!e||!Y()?null:(e.has_opted_out_capturing?.()&&e.opt_in_capturing(),j(e),e)}async function re(){if(te())return;let e=await $();e&&(e.capture(`canvas_new_session`,{is_first_use:!X()}),ne())}async function ie(){return(await $())?.get_distinct_id?.()??null}async function ae(){if(!P())return null;if(z()!==`granted`){let e=B();if(e)return e}return y||I()?null:(await R())?.get_distinct_id?.()??null}async function oe(e,t={}){let n=await $();n&&n.capture(e,t)}async function se(e,t={}){let n=await $();n&&n.captureException(e,t)}async function ce(){if(P()){try{W(h?.get_distinct_id?.()??null),localStorage.removeItem(i),localStorage.removeItem(c)}catch{}K();try{sessionStorage.removeItem(l)}catch{}w=r.getBackendTelemetryProperties({}),T=r.getCloudTelemetryProperties(),b=null,x+=1,S=-1;try{h&&A(h,!0)}catch{try{h?.opt_out_capturing()}catch{}}finally{H()}}}exports.clearPendingLocalTelemetryRevocation=G,exports.clearTelemetryData=ce,exports.configurePostHogBootstrap=L,exports.configureTelemetry=ee,exports.getTelemetryConsent=z,exports.getTelemetryDistinctId=ie,exports.getTelemetryDistinctIdForConsentSync=ae,exports.initializePostHogClient=R,exports.isTelemetryEnabled=Y,exports.setTelemetryBackendContext=E,exports.setTelemetryCloudContext=D,exports.setTelemetryConsent=q,exports.setTelemetryIdentity=J,exports.subscribeTelemetryConsent=V,exports.trackEvent=oe,exports.trackException=se,exports.trackInstall=Q,exports.trackSessionStart=re;
2
2
  //# sourceMappingURL=telemetry.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"telemetry.cjs","names":[],"sources":["../../src/services/telemetry.ts"],"sourcesContent":["/**\n * Telemetry service for tracking library usage.\n *\n * This module handles anonymous telemetry for the @openhands/agent-canvas package\n * using the PostHog SDK for reliable event delivery with batching, retry logic,\n * and offline support.\n *\n * TRACKING PHILOSOPHY:\n * - Install event (canvas_install): Sent immediately on first use, regardless of consent.\n * This is anonymous and contains no PII - just basic browser info and a random ID.\n * - Session/custom events: Only sent after user grants consent via the consent modal.\n * - Users can opt out of all future tracking by declining consent.\n *\n * AD BLOCKER BYPASS:\n * By default, telemetry is routed through OpenHands' reverse proxy (z.openhands.dev)\n * to avoid being blocked by ad blockers. Library consumers can override this with:\n * - VITE_POSTHOG_HOST: Custom proxy URL or direct PostHog URL\n * - VITE_POSTHOG_UI_HOST: PostHog UI host (defaults to https://us.posthog.com)\n *\n * IMPORTANT: By default, telemetry is sent to the OpenHands PostHog project.\n * Source builds can override this with VITE_POSTHOG_API_KEY. Precompiled\n * library consumers can pass the same settings to configureTelemetry().\n *\n * Users can disable all telemetry (including install tracking) via:\n * - Setting VITE_DO_NOT_TRACK=1 environment variable\n * - Browser's Do Not Track setting\n */\n\nimport type { BootstrapConfig, CaptureResult, PostHog } from \"posthog-js\";\nimport { getLockedCloudAuthMode } from \"#/api/agent-server-config\";\nimport defaults from \"../../config/defaults.json\";\n\nimport packageJson from \"../../package.json\";\nimport {\n AGENT_CANVAS_CLIENT_SOURCE,\n AGENT_CANVAS_CLIENT_VERSION,\n} from \"#/api/client-source\";\nimport {\n getBackendTelemetryProperties,\n getCloudTelemetryProperties,\n type BackendTelemetryContextInput,\n type CloudTelemetryContextInput,\n} from \"#/services/telemetry-context\";\n\nconst TELEMETRY_CONSENT_KEY = \"openhands-telemetry-consent\";\nconst TELEMETRY_CONSENT_PENDING_CLOUD_SYNC_KEY =\n \"openhands-telemetry-consent-pending-cloud-sync\";\nconst TELEMETRY_CONSENT_CHANGE_EVENT = \"openhands-telemetry-consent-change\";\nconst TELEMETRY_FIRST_USE_KEY = \"openhands-telemetry-first-use\";\nconst TELEMETRY_SESSION_KEY = \"openhands-telemetry-session\";\nconst POSTHOG_INSTANCE_NAME = \"agent-canvas\";\nconst POSTHOG_PAGEVIEW_CAPTURE_MODE = \"history_change\";\n\n// Unconfigured source builds use staging. Production release workflows pass\n// VITE_POSTHOG_API_KEY explicitly for both the app and library artifacts.\nconst DEFAULT_POSTHOG_API_KEY: string =\n (import.meta.env.VITE_POSTHOG_API_KEY as string | undefined) ||\n defaults.telemetry.posthogApiKey;\n\n// Default to OpenHands' reverse proxy to bypass ad blockers.\n// The proxy at z.openhands.dev routes to PostHog's US region.\n// Library consumers can override this with their own proxy or direct PostHog URL.\nconst DEFAULT_POSTHOG_HOST =\n import.meta.env.VITE_POSTHOG_HOST || \"https://z.openhands.dev\";\n\n// UI host is needed for PostHog features like toolbar to work correctly\n// when using a reverse proxy. Defaults to US region.\nconst DEFAULT_POSTHOG_UI_HOST =\n import.meta.env.VITE_POSTHOG_UI_HOST || \"https://us.posthog.com\";\n\nexport interface TelemetryConfig {\n /** PostHog project key. Useful for precompiled library consumers. */\n apiKey?: string;\n /** Event ingestion host or reverse proxy. */\n apiHost?: string;\n /** PostHog UI host used by toolbar links and other UI features. */\n uiHost?: string;\n}\n\nexport type TelemetryConfiguration = TelemetryConfig | false;\n\nexport type TelemetryConsent = \"granted\" | \"denied\" | \"pending\";\nexport type ResolvedTelemetryConsent = Exclude<TelemetryConsent, \"pending\">;\n\nexport interface SetTelemetryConsentOptions {\n /** Do not persist a value mirrored from backend settings back to Cloud. */\n syncToCloud?: boolean;\n}\n\nlet posthogInstance: PostHog | null = null;\nlet initializationPromise: Promise<PostHog | null> | null = null;\nlet pendingBootstrap: BootstrapConfig | undefined;\nlet telemetryConfig: TelemetryConfig = {};\nlet telemetryDisabled = false;\n\nconst CANVAS_EVENT_PROPERTIES = Object.freeze({\n client_source: AGENT_CANVAS_CLIENT_SOURCE,\n client_version: AGENT_CANVAS_CLIENT_VERSION,\n package_name: packageJson.name,\n package_version: packageJson.version,\n});\n\nlet telemetryBackendContext = getBackendTelemetryProperties({});\nlet telemetryCloudContext = getCloudTelemetryProperties();\n\nexport function setTelemetryBackendContext(\n context: BackendTelemetryContextInput,\n): void {\n telemetryBackendContext = getBackendTelemetryProperties(context);\n}\n\nexport function setTelemetryCloudContext(\n context: CloudTelemetryContextInput | null,\n): void {\n telemetryCloudContext = getCloudTelemetryProperties(context);\n}\n\nfunction addCanvasEventProperties(\n event: CaptureResult | null,\n): CaptureResult | null {\n if (!event) return null;\n\n return {\n ...event,\n properties: {\n ...telemetryBackendContext,\n ...telemetryCloudContext,\n ...event.properties,\n ...CANVAS_EVENT_PROPERTIES,\n },\n };\n}\n\nfunction restorePostHogConsent(posthog: PostHog): void {\n if (telemetryDisabled || getTelemetryConsent() !== \"granted\") {\n posthog.opt_out_capturing();\n } else {\n posthog.opt_in_capturing();\n }\n}\n\nfunction resetPostHogIdentity(posthog: PostHog, resetDeviceId = false): void {\n posthog.reset(resetDeviceId);\n // PostHog reset clears its own consent persistence, so immediately restore\n // the canonical Canvas decision kept in localStorage.\n restorePostHogConsent(posthog);\n}\n\nfunction clearLegacyIdentifiedUser(posthog: PostHog): void {\n if (posthog.get_property?.(\"$user_id\") != null) {\n resetPostHogIdentity(posthog);\n }\n}\n\n/**\n * Configure the single Canvas telemetry client before its first use.\n * Passing false disables telemetry and install tracking for embedded hosts.\n */\nexport function configureTelemetry(config: TelemetryConfiguration): void {\n if (config === false) {\n if (telemetryDisabled) return;\n telemetryDisabled = true;\n posthogInstance?.opt_out_capturing();\n notifyTelemetryConsentListeners();\n return;\n }\n\n const wasDisabled = telemetryDisabled;\n telemetryDisabled = false;\n\n if (!posthogInstance && !initializationPromise) {\n const definedConfig = Object.fromEntries(\n Object.entries(config).filter(([, value]) => value !== undefined),\n ) as TelemetryConfig;\n telemetryConfig = { ...telemetryConfig, ...definedConfig };\n }\n\n if (wasDisabled) {\n if (posthogInstance) {\n restorePostHogConsent(posthogInstance);\n }\n notifyTelemetryConsentListeners();\n }\n}\n\nfunction getResolvedTelemetryConfig(): Required<TelemetryConfig> | null {\n if (telemetryDisabled) return null;\n\n return {\n apiKey: telemetryConfig.apiKey || DEFAULT_POSTHOG_API_KEY,\n apiHost: telemetryConfig.apiHost || DEFAULT_POSTHOG_HOST,\n uiHost: telemetryConfig.uiHost || DEFAULT_POSTHOG_UI_HOST,\n };\n}\n\n/**\n * Check if we're in a browser environment\n */\nfunction isBrowser(): boolean {\n return typeof window !== \"undefined\" && typeof localStorage !== \"undefined\";\n}\n\n/**\n * Lazily load PostHog to avoid SSR/Node.js issues.\n * PostHog is a browser-only library, so we dynamically import it only when needed.\n */\nasync function getPostHog(): Promise<PostHog | null> {\n if (!isBrowser()) {\n return null;\n }\n\n if (posthogInstance) {\n return posthogInstance;\n }\n\n try {\n const { default: posthog } = await import(\"posthog-js\");\n return posthog;\n } catch {\n // Failed to load PostHog - telemetry will be disabled\n return null;\n }\n}\n\n/**\n * Check if telemetry is disabled via environment variable or browser setting.\n * Works in both Node.js and browser (Vite) environments.\n */\nfunction isDoNotTrackEnabled(): boolean {\n if (telemetryDisabled) {\n return true;\n }\n\n // Check Vite environment variable (browser)\n if (\n typeof import.meta !== \"undefined\" &&\n import.meta.env?.VITE_DO_NOT_TRACK === \"1\"\n ) {\n return true;\n }\n\n // Check Node.js environment variable (SSR/testing)\n if (typeof process !== \"undefined\" && process.env?.DO_NOT_TRACK === \"1\") {\n return true;\n }\n\n // Check browser's navigator.doNotTrack standard\n if (\n typeof navigator !== \"undefined\" &&\n (navigator.doNotTrack === \"1\" ||\n // @ts-expect-error - Some browsers use window.doNotTrack\n (typeof window !== \"undefined\" && window.doNotTrack === \"1\"))\n ) {\n return true;\n }\n\n return false;\n}\n\n/**\n * Initialize PostHog SDK.\n *\n * @param enableCapturing - If true, enable capturing immediately (for install tracking).\n * If false, start with capturing disabled (for consent-gated tracking).\n */\nexport function configurePostHogBootstrap(\n bootstrap: BootstrapConfig | undefined,\n): void {\n if (!posthogInstance) {\n pendingBootstrap = bootstrap;\n }\n}\n\nexport async function initializePostHogClient(\n enableCapturing = false,\n): Promise<PostHog | null> {\n if (posthogInstance) {\n return posthogInstance;\n }\n\n if (initializationPromise) {\n return initializationPromise;\n }\n\n initializationPromise = (async () => {\n const config = getResolvedTelemetryConfig();\n if (!config) {\n return null;\n }\n\n const posthog = await getPostHog();\n if (!posthog) {\n return null;\n }\n\n // A named instance isolates Canvas configuration, consent, identity, and\n // persistence from a host application's default PostHog singleton.\n const bootstrap = pendingBootstrap;\n const initializedPostHog = posthog.init(\n config.apiKey,\n {\n api_host: config.apiHost,\n ui_host: config.uiHost,\n opt_out_capturing_by_default: !enableCapturing,\n persistence: \"localStorage\",\n persistence_name: POSTHOG_INSTANCE_NAME,\n consent_persistence_name: `${POSTHOG_INSTANCE_NAME}-consent`,\n person_profiles: \"always\",\n capture_pageview: POSTHOG_PAGEVIEW_CAPTURE_MODE,\n autocapture: true,\n disable_session_recording: true,\n bootstrap,\n before_send: addCanvasEventProperties,\n },\n POSTHOG_INSTANCE_NAME,\n );\n if (!initializedPostHog) return null;\n\n posthogInstance = initializedPostHog;\n pendingBootstrap = undefined;\n if (!bootstrap) {\n clearLegacyIdentifiedUser(posthogInstance);\n }\n\n if (telemetryDisabled) {\n posthogInstance.opt_out_capturing();\n } else if (getTelemetryConsent() === \"granted\") {\n posthogInstance.opt_in_capturing();\n } else if (!enableCapturing) {\n posthogInstance.opt_out_capturing();\n }\n\n return posthogInstance;\n })();\n\n try {\n return await initializationPromise;\n } finally {\n if (!posthogInstance) {\n initializationPromise = null;\n }\n }\n}\n\n/**\n * Get user's telemetry consent preference\n */\nexport function getTelemetryConsent(): TelemetryConsent {\n if (!isBrowser()) {\n return \"pending\";\n }\n\n // Check environment variable for opt-out\n if (isDoNotTrackEnabled()) {\n return \"denied\";\n }\n\n if (getLockedCloudAuthMode() === \"cookie\") {\n return \"granted\";\n }\n\n try {\n const consent = localStorage.getItem(TELEMETRY_CONSENT_KEY);\n if (consent === \"granted\" || consent === \"denied\") {\n return consent;\n }\n } catch {\n // Ignore storage errors\n }\n\n return \"pending\";\n}\n\n/**\n * Return an explicit browser choice that still needs to survive a Cloud login.\n * It remains pending across local backends so their settings cannot consume a\n * decision that must still be applied after the user connects to Cloud.\n */\nexport function getPendingCloudTelemetryConsent(): ResolvedTelemetryConsent | null {\n if (!isBrowser()) return null;\n\n try {\n const consent = localStorage.getItem(\n TELEMETRY_CONSENT_PENDING_CLOUD_SYNC_KEY,\n );\n return consent === \"granted\" || consent === \"denied\" ? consent : null;\n } catch {\n return null;\n }\n}\n\nexport function subscribeTelemetryConsent(listener: () => void): () => void {\n if (!isBrowser()) return () => {};\n const handleStorage = (event: StorageEvent) => {\n if (\n event.key === TELEMETRY_CONSENT_KEY ||\n event.key === TELEMETRY_CONSENT_PENDING_CLOUD_SYNC_KEY\n ) {\n listener();\n }\n };\n window.addEventListener(TELEMETRY_CONSENT_CHANGE_EVENT, listener);\n window.addEventListener(\"storage\", handleStorage);\n\n return () => {\n window.removeEventListener(TELEMETRY_CONSENT_CHANGE_EVENT, listener);\n window.removeEventListener(\"storage\", handleStorage);\n };\n}\n\nfunction notifyTelemetryConsentListeners(): void {\n if (isBrowser())\n window.dispatchEvent(new Event(TELEMETRY_CONSENT_CHANGE_EVENT));\n}\n\nfunction markTelemetryConsentForCloudSync(\n consent: ResolvedTelemetryConsent,\n): void {\n if (!isBrowser()) return;\n\n try {\n localStorage.setItem(TELEMETRY_CONSENT_PENDING_CLOUD_SYNC_KEY, consent);\n } catch {\n // Ignore storage errors; the in-browser consent decision still applies.\n }\n}\n\nexport function clearPendingCloudTelemetryConsent(\n expected?: ResolvedTelemetryConsent,\n): void {\n if (!isBrowser()) return;\n\n try {\n if (\n expected !== undefined &&\n localStorage.getItem(TELEMETRY_CONSENT_PENDING_CLOUD_SYNC_KEY) !==\n expected\n ) {\n return;\n }\n localStorage.removeItem(TELEMETRY_CONSENT_PENDING_CLOUD_SYNC_KEY);\n notifyTelemetryConsentListeners();\n } catch {\n // Ignore storage errors.\n }\n}\n\n/**\n * Set user's telemetry consent preference\n */\nexport async function setTelemetryConsent(\n consent: ResolvedTelemetryConsent,\n { syncToCloud = true }: SetTelemetryConsentOptions = {},\n): Promise<void> {\n if (!isBrowser()) {\n return;\n }\n\n try {\n const previousConsent = getTelemetryConsent();\n const unchanged = previousConsent === consent;\n localStorage.setItem(TELEMETRY_CONSENT_KEY, consent);\n if (telemetryDisabled) return;\n if (unchanged) return;\n\n // Reuse an initialized client synchronously so a same-flush identify()\n // cannot run before consent is applied. Only the cold path awaits import.\n const posthog = posthogInstance ?? (await initializePostHogClient());\n if (!posthog) {\n return;\n }\n\n if (consent === \"granted\") {\n posthog.opt_in_capturing();\n } else {\n if (posthog.get_property?.(\"$user_id\") != null) {\n resetPostHogIdentity(posthog);\n } else {\n posthog.opt_out_capturing();\n }\n }\n } catch {\n // Ignore storage errors\n } finally {\n // Notify UI/backend/identity reconcilers only after the browser capture state\n // reflects this decision. Otherwise a pre-login grant can trigger an\n // identify while PostHog is still opted out and lose funnel continuity.\n if (syncToCloud) {\n markTelemetryConsentForCloudSync(consent);\n }\n notifyTelemetryConsentListeners();\n }\n}\n\n/**\n * Check if telemetry is enabled (user has granted consent)\n */\nexport function isTelemetryEnabled(): boolean {\n return getTelemetryConsent() === \"granted\";\n}\n\n/**\n * Check if first use event has already been sent\n */\nfunction hasFirstUseSent(): boolean {\n if (!isBrowser()) {\n return false;\n }\n\n try {\n return localStorage.getItem(TELEMETRY_FIRST_USE_KEY) === \"true\";\n } catch {\n return false;\n }\n}\n\n/**\n * Mark first use event as sent\n */\nfunction markFirstUseSent(): void {\n if (!isBrowser()) {\n return;\n }\n\n try {\n localStorage.setItem(TELEMETRY_FIRST_USE_KEY, \"true\");\n } catch {\n // Ignore storage errors\n }\n}\n\n/**\n * Track the initial install of the library.\n *\n * IMPORTANT: This is sent immediately on first use, regardless of consent status.\n * This allows us to track library adoption even if users haven't made a consent choice yet.\n *\n * The event is:\n * - Completely anonymous (no PII, just a random PostHog distinct_id)\n * - Sent only once per installation (tracked via localStorage, persists across sessions)\n * - Still respects DO_NOT_TRACK environment variable and browser setting\n *\n * Users who want complete privacy can:\n * - Set VITE_DO_NOT_TRACK=1 or browser's Do Not Track\n * - Later deny consent to prevent all future tracking\n */\nexport async function trackInstall(): Promise<void> {\n // Respect hard opt-out via environment variable or browser setting\n if (isDoNotTrackEnabled()) {\n return;\n }\n\n // Already sent install event (persisted in localStorage - survives app relaunches)\n if (hasFirstUseSent()) {\n return;\n }\n\n // Initialize PostHog with capturing enabled (for this one event)\n const posthog = await initializePostHogClient(true);\n if (!posthog || isDoNotTrackEnabled()) {\n return;\n }\n\n // Temporarily enable capturing if it was disabled\n const wasOptedOut = posthog.has_opted_out_capturing?.() ?? false;\n if (wasOptedOut) {\n posthog.opt_in_capturing();\n }\n\n // Capture the install event\n posthog.capture(\"canvas_install\", {\n platform: typeof navigator !== \"undefined\" ? navigator.platform : \"unknown\",\n user_agent:\n typeof navigator !== \"undefined\" ? navigator.userAgent : \"unknown\",\n referrer: typeof document !== \"undefined\" ? document.referrer : \"\",\n url_origin: typeof window !== \"undefined\" ? window.location.origin : \"\",\n embedded: typeof window !== \"undefined\" && window.self !== window.top,\n });\n\n // Mark as sent (stored in localStorage - persists across browser sessions)\n markFirstUseSent();\n\n // Restore opt-out state if user hasn't granted consent yet\n // This ensures we only send the install event, not subsequent events\n const currentConsent = getTelemetryConsent();\n if (currentConsent !== \"granted\") {\n posthog.opt_out_capturing();\n }\n}\n\n/**\n * Check if session start event has already been sent (this browser session)\n */\nfunction hasSessionSent(): boolean {\n if (!isBrowser()) {\n return false;\n }\n\n try {\n return sessionStorage.getItem(TELEMETRY_SESSION_KEY) === \"true\";\n } catch {\n return false;\n }\n}\n\n/**\n * Mark session start event as sent (uses sessionStorage so it resets on new tabs/sessions)\n */\nfunction markSessionSent(): void {\n if (!isBrowser()) {\n return;\n }\n\n try {\n sessionStorage.setItem(TELEMETRY_SESSION_KEY, \"true\");\n } catch {\n // Ignore storage errors\n }\n}\n\n/** Return the shared client only when a consented capture is safe to emit. */\nasync function getPostHogForConsentedCapture(): Promise<PostHog | null> {\n if (!isTelemetryEnabled() || getTelemetryConsent() !== \"granted\") return null;\n\n const posthog = await initializePostHogClient();\n if (!posthog || !isTelemetryEnabled()) return null;\n\n // The browser preference is the canonical capture decision. PostHog may\n // still carry an older opt-out marker while backend consent is loading or\n // after a previous backend temporarily reported a stale value. Heal that\n // drift at the event boundary so capture() cannot silently discard an event\n // that the user has explicitly allowed.\n if (posthog.has_opted_out_capturing?.()) {\n posthog.opt_in_capturing();\n }\n\n return posthog;\n}\n\n/**\n * Track a session start event.\n * Called each time a new browser session starts (respects consent).\n * Uses sessionStorage for deduplication - only sends once per browser session.\n */\nexport async function trackSessionStart(): Promise<void> {\n // Already sent session event this browser session\n if (hasSessionSent()) {\n return;\n }\n\n const posthog = await getPostHogForConsentedCapture();\n if (!posthog) return;\n\n posthog.capture(\"canvas_new_session\", {\n is_first_use: !hasFirstUseSent(),\n });\n\n // Mark as sent for this session\n markSessionSent();\n}\n\n/** Return the active PostHog distinct ID when consent allows capture. */\nexport async function getTelemetryDistinctId(): Promise<string | null> {\n const posthog = await getPostHogForConsentedCapture();\n return posthog?.get_distinct_id?.() ?? null;\n}\n\n/** Return the PostHog distinct ID for local consent sync without emitting capture. */\nexport async function getTelemetryDistinctIdForConsentSync(): Promise<\n string | null\n> {\n if (!isBrowser() || telemetryDisabled || isDoNotTrackEnabled()) return null;\n\n const posthog = await initializePostHogClient();\n return posthog?.get_distinct_id?.() ?? null;\n}\n\n/**\n * Track a custom event (respects consent).\n */\nexport async function trackEvent(\n eventName: string,\n properties: Record<string, unknown> = {},\n): Promise<void> {\n const posthog = await getPostHogForConsentedCapture();\n if (!posthog) return;\n\n posthog.capture(eventName, properties);\n}\n\n/** Track an exception through the same consent-aware client as custom events. */\nexport async function trackException(\n error: unknown,\n properties: Record<string, unknown> = {},\n): Promise<void> {\n const posthog = await getPostHogForConsentedCapture();\n if (!posthog) return;\n\n posthog.captureException(error, properties);\n}\n\n/**\n * Clear all telemetry data (for privacy/GDPR requests)\n */\nexport async function clearTelemetryData(): Promise<void> {\n if (!isBrowser()) {\n return;\n }\n\n try {\n localStorage.removeItem(TELEMETRY_CONSENT_KEY);\n localStorage.removeItem(TELEMETRY_FIRST_USE_KEY);\n } catch {\n // Continue clearing the in-memory and SDK identity if storage is blocked.\n }\n clearPendingCloudTelemetryConsent();\n try {\n sessionStorage.removeItem(TELEMETRY_SESSION_KEY);\n } catch {\n // Continue clearing the in-memory and SDK identity if storage is blocked.\n }\n\n telemetryBackendContext = getBackendTelemetryProperties({});\n telemetryCloudContext = getCloudTelemetryProperties();\n\n try {\n if (posthogInstance) {\n resetPostHogIdentity(posthogInstance, true);\n }\n } catch {\n // A reset failure must not leave capture enabled after a privacy clear.\n try {\n posthogInstance?.opt_out_capturing();\n } catch {\n // Telemetry failures must not break the application.\n }\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"telemetry.cjs","names":[],"sources":["../../src/services/telemetry.ts"],"sourcesContent":["/**\n * Telemetry service for tracking library usage.\n *\n * This module handles anonymous telemetry for the @openhands/agent-canvas package\n * using the PostHog SDK for reliable event delivery with batching, retry logic,\n * and offline support.\n *\n * TRACKING PHILOSOPHY:\n * - Install event (canvas_install): Sent immediately on first use, regardless of consent.\n * This is anonymous and contains no PII - just basic browser info and a random ID.\n * - Session/custom events: Only sent after user grants consent via the consent modal.\n * - Users can opt out of all future tracking by declining consent.\n *\n * AD BLOCKER BYPASS:\n * By default, telemetry is routed through OpenHands' reverse proxy (z.openhands.dev)\n * to avoid being blocked by ad blockers. Library consumers can override this with:\n * - VITE_POSTHOG_HOST: Custom proxy URL or direct PostHog URL\n * - VITE_POSTHOG_UI_HOST: PostHog UI host (defaults to https://us.posthog.com)\n *\n * IMPORTANT: By default, telemetry is sent to the OpenHands PostHog project.\n * Source builds can override this with VITE_POSTHOG_API_KEY. Precompiled\n * library consumers can pass the same settings to configureTelemetry().\n *\n * Users can disable all telemetry (including install tracking) via:\n * - Setting VITE_DO_NOT_TRACK=1 environment variable\n * - Browser's Do Not Track setting\n */\n\nimport type { BootstrapConfig, CaptureResult, PostHog } from \"posthog-js\";\nimport { getLockedCloudAuthMode } from \"#/api/agent-server-config\";\nimport defaults from \"../../config/defaults.json\";\n\nimport packageJson from \"../../package.json\";\nimport {\n AGENT_CANVAS_CLIENT_SOURCE,\n AGENT_CANVAS_CLIENT_VERSION,\n} from \"#/api/client-source\";\nimport {\n getBackendTelemetryProperties,\n getCloudTelemetryProperties,\n type BackendTelemetryContextInput,\n type CloudTelemetryContextInput,\n} from \"#/services/telemetry-context\";\n\nconst TELEMETRY_CONSENT_KEY = \"openhands-telemetry-consent\";\nconst TELEMETRY_CONSENT_PENDING_CLOUD_SYNC_KEY =\n \"openhands-telemetry-consent-pending-cloud-sync\";\nconst TELEMETRY_CONSENT_PENDING_LOCAL_REVOCATION_KEY =\n \"openhands-telemetry-consent-pending-local-revocation\";\nconst TELEMETRY_CONSENT_CHANGE_EVENT = \"openhands-telemetry-consent-change\";\nconst TELEMETRY_FIRST_USE_KEY = \"openhands-telemetry-first-use\";\nconst TELEMETRY_SESSION_KEY = \"openhands-telemetry-session\";\nconst POSTHOG_INSTANCE_NAME = \"agent-canvas\";\nconst POSTHOG_PAGEVIEW_CAPTURE_MODE = \"history_change\";\n\n// Unconfigured source builds use staging. Production release workflows pass\n// VITE_POSTHOG_API_KEY explicitly for both the app and library artifacts.\nconst DEFAULT_POSTHOG_API_KEY: string =\n (import.meta.env.VITE_POSTHOG_API_KEY as string | undefined) ||\n defaults.telemetry.posthogApiKey;\n\n// Default to OpenHands' reverse proxy to bypass ad blockers.\n// The proxy at z.openhands.dev routes to PostHog's US region.\n// Library consumers can override this with their own proxy or direct PostHog URL.\nconst DEFAULT_POSTHOG_HOST =\n import.meta.env.VITE_POSTHOG_HOST || \"https://z.openhands.dev\";\n\n// UI host is needed for PostHog features like toolbar to work correctly\n// when using a reverse proxy. Defaults to US region.\nconst DEFAULT_POSTHOG_UI_HOST =\n import.meta.env.VITE_POSTHOG_UI_HOST || \"https://us.posthog.com\";\n\nexport interface TelemetryConfig {\n /** PostHog project key. Useful for precompiled library consumers. */\n apiKey?: string;\n /** Event ingestion host or reverse proxy. */\n apiHost?: string;\n /** PostHog UI host used by toolbar links and other UI features. */\n uiHost?: string;\n}\n\nexport type TelemetryConfiguration = TelemetryConfig | false;\n\nexport type TelemetryConsent = \"granted\" | \"denied\" | \"pending\";\nexport type ResolvedTelemetryConsent = Exclude<TelemetryConsent, \"pending\">;\n\nexport interface SetTelemetryConsentOptions {\n /** Do not persist a value mirrored from backend settings back to Cloud. */\n syncToCloud?: boolean;\n}\n\nlet posthogInstance: PostHog | null = null;\nlet initializationPromise: Promise<PostHog | null> | null = null;\nlet pendingBootstrap: BootstrapConfig | undefined;\nlet telemetryConfig: TelemetryConfig = {};\nlet telemetryDisabled = false;\n\ninterface TelemetryIdentity {\n distinctId: string;\n properties: Record<string, unknown>;\n}\n\n// undefined means that Cloud identity has not resolved yet; null means that it\n// resolved without a user. This distinction prevents startup from resetting a\n// persisted identity while the current account is still loading.\nlet desiredTelemetryIdentity: TelemetryIdentity | null | undefined;\nlet desiredIdentityRevision = 0;\nlet appliedIdentityRevision = -1;\n\nconst CANVAS_EVENT_PROPERTIES = Object.freeze({\n client_source: AGENT_CANVAS_CLIENT_SOURCE,\n client_version: AGENT_CANVAS_CLIENT_VERSION,\n package_name: packageJson.name,\n package_version: packageJson.version,\n});\n\nlet telemetryBackendContext = getBackendTelemetryProperties({});\nlet telemetryCloudContext = getCloudTelemetryProperties();\n\nexport function setTelemetryBackendContext(\n context: BackendTelemetryContextInput,\n): void {\n telemetryBackendContext = getBackendTelemetryProperties(context);\n}\n\nexport function setTelemetryCloudContext(\n context: CloudTelemetryContextInput | null,\n): void {\n telemetryCloudContext = getCloudTelemetryProperties(context);\n}\n\nfunction addCanvasEventProperties(\n event: CaptureResult | null,\n): CaptureResult | null {\n if (!event) return null;\n\n return {\n ...event,\n properties: {\n ...telemetryBackendContext,\n ...telemetryCloudContext,\n ...event.properties,\n ...CANVAS_EVENT_PROPERTIES,\n },\n };\n}\n\nfunction restorePostHogConsent(posthog: PostHog): void {\n if (telemetryDisabled || getTelemetryConsent() !== \"granted\") {\n posthog.opt_out_capturing();\n } else {\n posthog.opt_in_capturing();\n }\n}\n\nfunction resetPostHogIdentity(posthog: PostHog, resetDeviceId = false): void {\n posthog.reset(resetDeviceId);\n appliedIdentityRevision = -1;\n // PostHog reset clears its own consent persistence, so immediately restore\n // the canonical Canvas decision kept in localStorage.\n restorePostHogConsent(posthog);\n}\n\nfunction applyDesiredTelemetryIdentity(posthog: PostHog): void {\n if (desiredTelemetryIdentity === undefined || !isTelemetryEnabled()) return;\n\n const desiredId = desiredTelemetryIdentity?.distinctId;\n const currentId = posthog.get_property(\"$user_id\");\n if (currentId != null && currentId !== desiredId) {\n resetPostHogIdentity(posthog);\n }\n\n if (desiredTelemetryIdentity === null) {\n appliedIdentityRevision = desiredIdentityRevision;\n return;\n }\n\n if (\n posthog.get_property(\"$user_id\") !== desiredTelemetryIdentity.distinctId ||\n appliedIdentityRevision !== desiredIdentityRevision\n ) {\n posthog.identify(\n desiredTelemetryIdentity.distinctId,\n desiredTelemetryIdentity.properties,\n );\n appliedIdentityRevision = desiredIdentityRevision;\n }\n}\n\nfunction propertiesEqual(\n left: Record<string, unknown>,\n right: Record<string, unknown>,\n): boolean {\n const keys = Object.keys(left);\n return (\n keys.length === Object.keys(right).length &&\n keys.every((key) => left[key] === right[key])\n );\n}\n\n/**\n * Configure the single Canvas telemetry client before its first use.\n * Passing false disables telemetry and install tracking for embedded hosts.\n */\nexport function configureTelemetry(config: TelemetryConfiguration): void {\n if (config === false) {\n if (telemetryDisabled) return;\n telemetryDisabled = true;\n posthogInstance?.opt_out_capturing();\n notifyTelemetryConsentListeners();\n return;\n }\n\n const wasDisabled = telemetryDisabled;\n telemetryDisabled = false;\n\n if (!posthogInstance && !initializationPromise) {\n const definedConfig = Object.fromEntries(\n Object.entries(config).filter(([, value]) => value !== undefined),\n ) as TelemetryConfig;\n telemetryConfig = { ...telemetryConfig, ...definedConfig };\n }\n\n if (wasDisabled) {\n if (posthogInstance) {\n restorePostHogConsent(posthogInstance);\n applyDesiredTelemetryIdentity(posthogInstance);\n }\n notifyTelemetryConsentListeners();\n }\n}\n\nfunction getResolvedTelemetryConfig(): Required<TelemetryConfig> | null {\n if (telemetryDisabled) return null;\n\n return {\n apiKey: telemetryConfig.apiKey || DEFAULT_POSTHOG_API_KEY,\n apiHost: telemetryConfig.apiHost || DEFAULT_POSTHOG_HOST,\n uiHost: telemetryConfig.uiHost || DEFAULT_POSTHOG_UI_HOST,\n };\n}\n\n/**\n * Check if we're in a browser environment\n */\nfunction isBrowser(): boolean {\n return typeof window !== \"undefined\" && typeof localStorage !== \"undefined\";\n}\n\n/**\n * Lazily load PostHog to avoid SSR/Node.js issues.\n * PostHog is a browser-only library, so we dynamically import it only when needed.\n */\nasync function getPostHog(): Promise<PostHog | null> {\n if (!isBrowser()) {\n return null;\n }\n\n if (posthogInstance) {\n return posthogInstance;\n }\n\n try {\n const { default: posthog } = await import(\"posthog-js\");\n return posthog;\n } catch {\n // Failed to load PostHog - telemetry will be disabled\n return null;\n }\n}\n\n/**\n * Check if telemetry is disabled via environment variable or browser setting.\n * Works in both Node.js and browser (Vite) environments.\n */\nfunction isDoNotTrackEnabled(): boolean {\n if (telemetryDisabled) {\n return true;\n }\n\n // Check Vite environment variable (browser)\n if (\n typeof import.meta !== \"undefined\" &&\n import.meta.env?.VITE_DO_NOT_TRACK === \"1\"\n ) {\n return true;\n }\n\n // Check Node.js environment variable (SSR/testing)\n if (typeof process !== \"undefined\" && process.env?.DO_NOT_TRACK === \"1\") {\n return true;\n }\n\n // Check browser's navigator.doNotTrack standard\n if (\n typeof navigator !== \"undefined\" &&\n (navigator.doNotTrack === \"1\" ||\n // @ts-expect-error - Some browsers use window.doNotTrack\n (typeof window !== \"undefined\" && window.doNotTrack === \"1\"))\n ) {\n return true;\n }\n\n return false;\n}\n\n/**\n * Initialize PostHog SDK.\n *\n * @param enableCapturing - If true, enable capturing immediately (for install tracking).\n * If false, start with capturing disabled (for consent-gated tracking).\n */\nexport function configurePostHogBootstrap(\n bootstrap: BootstrapConfig | undefined,\n): void {\n if (!posthogInstance) {\n pendingBootstrap = bootstrap;\n }\n}\n\nexport async function initializePostHogClient(\n enableCapturing = false,\n): Promise<PostHog | null> {\n if (posthogInstance) {\n return posthogInstance;\n }\n\n if (initializationPromise) {\n return initializationPromise;\n }\n\n initializationPromise = (async () => {\n const config = getResolvedTelemetryConfig();\n if (!config) {\n return null;\n }\n\n const posthog = await getPostHog();\n if (!posthog) {\n return null;\n }\n\n // A named instance isolates Canvas configuration, consent, identity, and\n // persistence from a host application's default PostHog singleton.\n const initializedPostHog = posthog.init(\n config.apiKey,\n {\n api_host: config.apiHost,\n ui_host: config.uiHost,\n opt_out_capturing_by_default: !enableCapturing,\n persistence: \"localStorage\",\n persistence_name: POSTHOG_INSTANCE_NAME,\n consent_persistence_name: `${POSTHOG_INSTANCE_NAME}-consent`,\n person_profiles: \"always\",\n capture_pageview: POSTHOG_PAGEVIEW_CAPTURE_MODE,\n autocapture: true,\n disable_session_recording: true,\n bootstrap: pendingBootstrap,\n before_send: addCanvasEventProperties,\n },\n POSTHOG_INSTANCE_NAME,\n );\n if (!initializedPostHog) return null;\n\n posthogInstance = initializedPostHog;\n pendingBootstrap = undefined;\n\n if (telemetryDisabled) {\n posthogInstance.opt_out_capturing();\n } else if (getTelemetryConsent() === \"granted\") {\n posthogInstance.opt_in_capturing();\n applyDesiredTelemetryIdentity(posthogInstance);\n } else if (!enableCapturing) {\n posthogInstance.opt_out_capturing();\n }\n\n return posthogInstance;\n })();\n\n try {\n return await initializationPromise;\n } finally {\n if (!posthogInstance) {\n initializationPromise = null;\n }\n }\n}\n\n/**\n * Get user's telemetry consent preference\n */\nexport function getTelemetryConsent(): TelemetryConsent {\n if (!isBrowser()) {\n return \"pending\";\n }\n\n // Check environment variable for opt-out\n if (isDoNotTrackEnabled()) {\n return \"denied\";\n }\n\n if (getLockedCloudAuthMode() === \"cookie\") {\n return \"granted\";\n }\n\n try {\n const consent = localStorage.getItem(TELEMETRY_CONSENT_KEY);\n if (consent === \"granted\" || consent === \"denied\") {\n return consent;\n }\n } catch {\n // Ignore storage errors\n }\n\n return \"pending\";\n}\n\n/**\n * Return an explicit browser choice that still needs to survive a Cloud login.\n * It remains pending across local backends so their settings cannot consume a\n * decision that must still be applied after the user connects to Cloud.\n */\nexport function getPendingCloudTelemetryConsent(): ResolvedTelemetryConsent | null {\n if (!isBrowser()) return null;\n\n try {\n const consent = localStorage.getItem(\n TELEMETRY_CONSENT_PENDING_CLOUD_SYNC_KEY,\n );\n return consent === \"granted\" || consent === \"denied\" ? consent : null;\n } catch {\n return null;\n }\n}\n\n/** Return the pre-reset actor whose local Automation consent must be revoked. */\nexport function getPendingLocalTelemetryRevocationId(): string | null {\n if (!isBrowser()) return null;\n\n try {\n return localStorage.getItem(TELEMETRY_CONSENT_PENDING_LOCAL_REVOCATION_KEY);\n } catch {\n return null;\n }\n}\n\nexport function subscribeTelemetryConsent(listener: () => void): () => void {\n if (!isBrowser()) return () => {};\n const handleStorage = (event: StorageEvent) => {\n if (\n event.key === TELEMETRY_CONSENT_KEY ||\n event.key === TELEMETRY_CONSENT_PENDING_CLOUD_SYNC_KEY\n ) {\n listener();\n }\n };\n window.addEventListener(TELEMETRY_CONSENT_CHANGE_EVENT, listener);\n window.addEventListener(\"storage\", handleStorage);\n\n return () => {\n window.removeEventListener(TELEMETRY_CONSENT_CHANGE_EVENT, listener);\n window.removeEventListener(\"storage\", handleStorage);\n };\n}\n\nfunction notifyTelemetryConsentListeners(): void {\n if (isBrowser())\n window.dispatchEvent(new Event(TELEMETRY_CONSENT_CHANGE_EVENT));\n}\n\nfunction markTelemetryConsentForCloudSync(\n consent: ResolvedTelemetryConsent,\n): void {\n if (!isBrowser()) return;\n\n try {\n localStorage.setItem(TELEMETRY_CONSENT_PENDING_CLOUD_SYNC_KEY, consent);\n } catch {\n // Ignore storage errors; the in-browser consent decision still applies.\n }\n}\n\nfunction markPendingLocalTelemetryRevocation(distinctId: string | null): void {\n if (!isBrowser() || !distinctId) return;\n\n try {\n localStorage.setItem(\n TELEMETRY_CONSENT_PENDING_LOCAL_REVOCATION_KEY,\n distinctId,\n );\n } catch {\n // Ignore storage errors; browser capture is still disabled below.\n }\n}\n\nexport function clearPendingLocalTelemetryRevocation(\n expectedDistinctId: string,\n): void {\n if (!isBrowser()) return;\n\n try {\n if (\n localStorage.getItem(TELEMETRY_CONSENT_PENDING_LOCAL_REVOCATION_KEY) !==\n expectedDistinctId\n ) {\n return;\n }\n localStorage.removeItem(TELEMETRY_CONSENT_PENDING_LOCAL_REVOCATION_KEY);\n } catch {\n // Ignore storage errors.\n }\n}\n\nexport function clearPendingCloudTelemetryConsent(\n expected?: ResolvedTelemetryConsent,\n): void {\n if (!isBrowser()) return;\n\n try {\n if (\n expected !== undefined &&\n localStorage.getItem(TELEMETRY_CONSENT_PENDING_CLOUD_SYNC_KEY) !==\n expected\n ) {\n return;\n }\n localStorage.removeItem(TELEMETRY_CONSENT_PENDING_CLOUD_SYNC_KEY);\n notifyTelemetryConsentListeners();\n } catch {\n // Ignore storage errors.\n }\n}\n\n/**\n * Set user's telemetry consent preference\n */\nexport async function setTelemetryConsent(\n consent: ResolvedTelemetryConsent,\n { syncToCloud = true }: SetTelemetryConsentOptions = {},\n): Promise<void> {\n if (!isBrowser()) {\n return;\n }\n\n try {\n const previousConsent = getTelemetryConsent();\n const unchanged = previousConsent === consent;\n localStorage.setItem(TELEMETRY_CONSENT_KEY, consent);\n if (telemetryDisabled) return;\n if (unchanged) return;\n\n // Reuse an initialized client synchronously so a same-flush identify()\n // cannot run before consent is applied. Only the cold path awaits import.\n const posthog = posthogInstance ?? (await initializePostHogClient());\n if (!posthog) {\n return;\n }\n\n if (consent === \"granted\") {\n posthog.opt_in_capturing();\n applyDesiredTelemetryIdentity(posthog);\n } else {\n markPendingLocalTelemetryRevocation(posthog.get_distinct_id?.() ?? null);\n if (posthog.get_property?.(\"$user_id\") != null) {\n resetPostHogIdentity(posthog);\n } else {\n posthog.opt_out_capturing();\n }\n }\n } catch {\n // Ignore storage errors\n } finally {\n // Notify UI/backend/identity reconcilers only after the browser capture state\n // reflects this decision. Otherwise a pre-login grant can trigger an\n // identify while PostHog is still opted out and lose funnel continuity.\n if (syncToCloud) {\n markTelemetryConsentForCloudSync(consent);\n }\n notifyTelemetryConsentListeners();\n }\n}\n\n/**\n * Declare the current Cloud identity. The telemetry service applies it only\n * after consent and owns all reset/account-switch semantics.\n */\nexport async function setTelemetryIdentity(\n distinctId: string | null,\n properties: Record<string, unknown> = {},\n): Promise<void> {\n const nextIdentity = distinctId === null ? null : { distinctId, properties };\n const unchanged =\n desiredTelemetryIdentity === nextIdentity ||\n (desiredTelemetryIdentity !== undefined &&\n desiredTelemetryIdentity !== null &&\n nextIdentity !== null &&\n desiredTelemetryIdentity.distinctId === nextIdentity.distinctId &&\n propertiesEqual(desiredTelemetryIdentity.properties, properties));\n if (unchanged) return;\n\n desiredTelemetryIdentity = nextIdentity;\n desiredIdentityRevision += 1;\n appliedIdentityRevision = -1;\n\n if (!isTelemetryEnabled()) return;\n const posthog = posthogInstance ?? (await initializePostHogClient());\n if (posthog && isTelemetryEnabled()) {\n // Read the desired identity after the await so a newer account always wins\n // if identity changes while the SDK is loading.\n applyDesiredTelemetryIdentity(posthog);\n }\n}\n\n/**\n * Check if telemetry is enabled (user has granted consent)\n */\nexport function isTelemetryEnabled(): boolean {\n return getTelemetryConsent() === \"granted\";\n}\n\n/**\n * Check if first use event has already been sent\n */\nfunction hasFirstUseSent(): boolean {\n if (!isBrowser()) {\n return false;\n }\n\n try {\n return localStorage.getItem(TELEMETRY_FIRST_USE_KEY) === \"true\";\n } catch {\n return false;\n }\n}\n\n/**\n * Mark first use event as sent\n */\nfunction markFirstUseSent(): void {\n if (!isBrowser()) {\n return;\n }\n\n try {\n localStorage.setItem(TELEMETRY_FIRST_USE_KEY, \"true\");\n } catch {\n // Ignore storage errors\n }\n}\n\n/**\n * Track the initial install of the library.\n *\n * IMPORTANT: This is sent immediately on first use, regardless of consent status.\n * This allows us to track library adoption even if users haven't made a consent choice yet.\n *\n * The event is:\n * - Completely anonymous (no PII, just a random PostHog distinct_id)\n * - Sent only once per installation (tracked via localStorage, persists across sessions)\n * - Still respects DO_NOT_TRACK environment variable and browser setting\n *\n * Users who want complete privacy can:\n * - Set VITE_DO_NOT_TRACK=1 or browser's Do Not Track\n * - Later deny consent to prevent all future tracking\n */\nexport async function trackInstall(): Promise<void> {\n // Respect hard opt-out via environment variable or browser setting\n if (isDoNotTrackEnabled()) {\n return;\n }\n\n // Already sent install event (persisted in localStorage - survives app relaunches)\n if (hasFirstUseSent()) {\n return;\n }\n\n // Initialize PostHog with capturing enabled (for this one event)\n const posthog = await initializePostHogClient(true);\n if (!posthog || isDoNotTrackEnabled()) {\n return;\n }\n\n // Temporarily enable capturing if it was disabled\n const wasOptedOut = posthog.has_opted_out_capturing?.() ?? false;\n if (wasOptedOut) {\n posthog.opt_in_capturing();\n }\n\n // Capture the install event\n posthog.capture(\"canvas_install\", {\n platform: typeof navigator !== \"undefined\" ? navigator.platform : \"unknown\",\n user_agent:\n typeof navigator !== \"undefined\" ? navigator.userAgent : \"unknown\",\n referrer: typeof document !== \"undefined\" ? document.referrer : \"\",\n url_origin: typeof window !== \"undefined\" ? window.location.origin : \"\",\n embedded: typeof window !== \"undefined\" && window.self !== window.top,\n });\n\n // Mark as sent (stored in localStorage - persists across browser sessions)\n markFirstUseSent();\n\n // Restore opt-out state if user hasn't granted consent yet\n // This ensures we only send the install event, not subsequent events\n const currentConsent = getTelemetryConsent();\n if (currentConsent !== \"granted\") {\n posthog.opt_out_capturing();\n }\n}\n\n/**\n * Check if session start event has already been sent (this browser session)\n */\nfunction hasSessionSent(): boolean {\n if (!isBrowser()) {\n return false;\n }\n\n try {\n return sessionStorage.getItem(TELEMETRY_SESSION_KEY) === \"true\";\n } catch {\n return false;\n }\n}\n\n/**\n * Mark session start event as sent (uses sessionStorage so it resets on new tabs/sessions)\n */\nfunction markSessionSent(): void {\n if (!isBrowser()) {\n return;\n }\n\n try {\n sessionStorage.setItem(TELEMETRY_SESSION_KEY, \"true\");\n } catch {\n // Ignore storage errors\n }\n}\n\n/** Return the shared client only when a consented capture is safe to emit. */\nasync function getPostHogForConsentedCapture(): Promise<PostHog | null> {\n if (!isTelemetryEnabled() || getTelemetryConsent() !== \"granted\") return null;\n\n const posthog = await initializePostHogClient();\n if (!posthog || !isTelemetryEnabled()) return null;\n\n // The browser preference is the canonical capture decision. PostHog may\n // still carry an older opt-out marker while backend consent is loading or\n // after a previous backend temporarily reported a stale value. Heal that\n // drift at the event boundary so capture() cannot silently discard an event\n // that the user has explicitly allowed.\n if (posthog.has_opted_out_capturing?.()) {\n posthog.opt_in_capturing();\n }\n\n applyDesiredTelemetryIdentity(posthog);\n\n return posthog;\n}\n\n/**\n * Track a session start event.\n * Called each time a new browser session starts (respects consent).\n * Uses sessionStorage for deduplication - only sends once per browser session.\n */\nexport async function trackSessionStart(): Promise<void> {\n // Already sent session event this browser session\n if (hasSessionSent()) {\n return;\n }\n\n const posthog = await getPostHogForConsentedCapture();\n if (!posthog) return;\n\n posthog.capture(\"canvas_new_session\", {\n is_first_use: !hasFirstUseSent(),\n });\n\n // Mark as sent for this session\n markSessionSent();\n}\n\n/** Return the active PostHog distinct ID when consent allows capture. */\nexport async function getTelemetryDistinctId(): Promise<string | null> {\n const posthog = await getPostHogForConsentedCapture();\n return posthog?.get_distinct_id?.() ?? null;\n}\n\n/** Return the PostHog distinct ID for local consent sync without emitting capture. */\nexport async function getTelemetryDistinctIdForConsentSync(): Promise<\n string | null\n> {\n if (!isBrowser()) return null;\n\n if (getTelemetryConsent() !== \"granted\") {\n const pendingRevocationId = getPendingLocalTelemetryRevocationId();\n if (pendingRevocationId) return pendingRevocationId;\n }\n\n if (telemetryDisabled || isDoNotTrackEnabled()) return null;\n\n const posthog = await initializePostHogClient();\n return posthog?.get_distinct_id?.() ?? null;\n}\n\n/**\n * Track a custom event (respects consent).\n */\nexport async function trackEvent(\n eventName: string,\n properties: Record<string, unknown> = {},\n): Promise<void> {\n const posthog = await getPostHogForConsentedCapture();\n if (!posthog) return;\n\n posthog.capture(eventName, properties);\n}\n\n/** Track an exception through the same consent-aware client as custom events. */\nexport async function trackException(\n error: unknown,\n properties: Record<string, unknown> = {},\n): Promise<void> {\n const posthog = await getPostHogForConsentedCapture();\n if (!posthog) return;\n\n posthog.captureException(error, properties);\n}\n\n/**\n * Clear all telemetry data (for privacy/GDPR requests)\n */\nexport async function clearTelemetryData(): Promise<void> {\n if (!isBrowser()) {\n return;\n }\n\n try {\n markPendingLocalTelemetryRevocation(\n posthogInstance?.get_distinct_id?.() ?? null,\n );\n localStorage.removeItem(TELEMETRY_CONSENT_KEY);\n localStorage.removeItem(TELEMETRY_FIRST_USE_KEY);\n } catch {\n // Continue clearing the in-memory and SDK identity if storage is blocked.\n }\n clearPendingCloudTelemetryConsent();\n try {\n sessionStorage.removeItem(TELEMETRY_SESSION_KEY);\n } catch {\n // Continue clearing the in-memory and SDK identity if storage is blocked.\n }\n\n telemetryBackendContext = getBackendTelemetryProperties({});\n telemetryCloudContext = getCloudTelemetryProperties();\n desiredTelemetryIdentity = null;\n desiredIdentityRevision += 1;\n appliedIdentityRevision = -1;\n\n try {\n if (posthogInstance) {\n resetPostHogIdentity(posthogInstance, true);\n }\n } catch {\n // A reset failure must not leave capture enabled after a privacy clear.\n try {\n posthogInstance?.opt_out_capturing();\n } catch {\n // Telemetry failures must not break the application.\n }\n } finally {\n notifyTelemetryConsentListeners();\n }\n}\n"],"mappings":"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"}
@@ -67,12 +67,20 @@ export declare function getTelemetryConsent(): TelemetryConsent;
67
67
  * decision that must still be applied after the user connects to Cloud.
68
68
  */
69
69
  export declare function getPendingCloudTelemetryConsent(): ResolvedTelemetryConsent | null;
70
+ /** Return the pre-reset actor whose local Automation consent must be revoked. */
71
+ export declare function getPendingLocalTelemetryRevocationId(): string | null;
70
72
  export declare function subscribeTelemetryConsent(listener: () => void): () => void;
73
+ export declare function clearPendingLocalTelemetryRevocation(expectedDistinctId: string): void;
71
74
  export declare function clearPendingCloudTelemetryConsent(expected?: ResolvedTelemetryConsent): void;
72
75
  /**
73
76
  * Set user's telemetry consent preference
74
77
  */
75
78
  export declare function setTelemetryConsent(consent: ResolvedTelemetryConsent, { syncToCloud }?: SetTelemetryConsentOptions): Promise<void>;
79
+ /**
80
+ * Declare the current Cloud identity. The telemetry service applies it only
81
+ * after consent and owns all reset/account-switch semantics.
82
+ */
83
+ export declare function setTelemetryIdentity(distinctId: string | null, properties?: Record<string, unknown>): Promise<void>;
76
84
  /**
77
85
  * Check if telemetry is enabled (user has granted consent)
78
86
  */