@openhands/agent-canvas 1.5.0 → 1.5.2

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 (347) hide show
  1. package/README.md +1 -1
  2. package/README.windows.md +2 -2
  3. package/build/assets/{acp-providers-BQwoUBpe.js → acp-providers-DerAmTyY.js} +1 -1
  4. package/build/assets/{active-backend-context-BCXRv5pm.js → active-backend-context-DHhm10VS.js} +1 -1
  5. package/build/assets/{add-backend-modal-BKHzq5no.js → add-backend-modal-BoMkMyLB.js} +1 -1
  6. package/build/assets/{agent-profiles-settings-CEp5CIz8.js → agent-profiles-settings-CYPloKYK.js} +1 -1
  7. package/build/assets/{agent-server-compatibility-D6DSanlx.js → agent-server-compatibility-C0MtTTwb.js} +1 -1
  8. package/build/assets/{agent-server-conversation-service.api-cylugfDz.js → agent-server-conversation-service.api-lzc8dabY.js} +4 -4
  9. package/build/assets/{agent-settings-C-tann3x.js → agent-settings-GwiXe_6k.js} +1 -1
  10. package/build/assets/agent-settings-OdsyI2gX.js +1 -0
  11. package/build/assets/{alert-banner-Dd4woGbm.js → alert-banner-Dh51WURQ.js} +1 -1
  12. package/build/assets/{api-key-entry-screen-Bll_CkuQ.js → api-key-entry-screen-2gsoZAOK.js} +1 -1
  13. package/build/assets/app-settings-CD9euh81.js +1 -0
  14. package/build/assets/{automation-detail-Z9dN2f5l.js → automation-detail-Bn8q0AG3.js} +1 -1
  15. package/build/assets/{automation-export-D5uL0Tf8.js → automation-export-Djc8Tf0M.js} +1 -1
  16. package/build/assets/{automations-list-BMOWcqzZ.js → automations-list-Dtiolk1h.js} +1 -1
  17. package/build/assets/{back-nav-button-ZUDTMOTB.js → back-nav-button-DVknCcSP.js} +1 -1
  18. package/build/assets/backend-form-modal-B8GtBA-J.js +1 -0
  19. package/build/assets/{backend-synced-settings-badge-Db_REKVw.js → backend-synced-settings-badge-yVh6Kf4E.js} +1 -1
  20. package/build/assets/{base-modal-Cb0C6m0Q.js → base-modal-CIF_785P.js} +1 -1
  21. package/build/assets/{brand-button-2Tp3aXk1.js → brand-button-B1izLAdR.js} +1 -1
  22. package/build/assets/{browser-QYLVr1kS.js → browser-DKaeq_sT.js} +2 -2
  23. package/build/assets/browser-store-BI6uklCG.js +1 -0
  24. package/build/assets/{browser-tab-BTTP1OE2.js → browser-tab-qb_ftCxW.js} +1 -1
  25. package/build/assets/{circle-plus-check-toggle-QQJKUnF6.js → circle-plus-check-toggle-DoFIjVE0.js} +1 -1
  26. package/build/assets/client-source-CtQjJEy6.js +1 -0
  27. package/build/assets/cloud-funnel-analytics-uzzLlLuj.js +1 -0
  28. package/build/assets/{color-themes-bVMmJJG6.js → color-themes-CHizm1xF.js} +1 -1
  29. package/build/assets/{combobox-caret-A5jMVHgE.js → combobox-caret-CxZpqoSR.js} +1 -1
  30. package/build/assets/{command-menu-BMVEme4H.js → command-menu-oTvKDU3Y.js} +1 -1
  31. package/build/assets/{command-store-jrWX_CcW.js → command-store-D8J5LMW1.js} +1 -1
  32. package/build/assets/{condenser-settings-CIa6eyB4.js → condenser-settings-Q1rHh4Qe.js} +1 -1
  33. package/build/assets/{confirmation-modal-4n_944zq.js → confirmation-modal-DGBWM2Ls.js} +1 -1
  34. package/build/assets/{context-menu-list-item-DvfFic5I.js → context-menu-list-item-6psNyl7X.js} +1 -1
  35. package/build/assets/conversation-B8d8_Upd.js +1 -0
  36. package/build/assets/{conversation-D4o4dG2t.js → conversation-Lmqm6VJC.js} +20 -20
  37. package/build/assets/conversation-panel-B8n40Swu.js +1 -0
  38. package/build/assets/{conversation-service.api-0JG_fF9d.js → conversation-service.api-D8QU-BiL.js} +1 -1
  39. package/build/assets/conversation-state-store-B2Bp32XI.js +1 -0
  40. package/build/assets/{conversation-store-BCSpLuEf.js → conversation-store-LptbOvKa.js} +1 -1
  41. package/build/assets/{copy-to-clipboard-button-Dp4Q_hXi.js → copy-to-clipboard-button-CFzH_NOX.js} +1 -1
  42. package/build/assets/{device-verify-CM086oKs.js → device-verify-WBSjJBLH.js} +1 -1
  43. package/build/assets/{dropdown-classes-DZq0BQjb.js → dropdown-classes-D3wMJlXT.js} +1 -1
  44. package/build/assets/{ellipsis-button-TPJdyxnK.js → ellipsis-button-CbNnHlIq.js} +1 -1
  45. package/build/assets/{entry.client-DypYHsYm.js → entry.client-Dnnak-Hq.js} +2 -2
  46. package/build/assets/{enum-filter-dropdown-CW_mBg0e.js → enum-filter-dropdown-JLYv1t8L.js} +1 -1
  47. package/build/assets/{environment-switch-overlay-6laJch-9.js → environment-switch-overlay-B1PFO8P8.js} +1 -1
  48. package/build/assets/{extensions-hub-BJFbFfKS.js → extensions-hub-DqvwvpCi.js} +1 -1
  49. package/build/assets/{extensions-navigation-DqSl7CJw.js → extensions-navigation-BMc9Nluv.js} +1 -1
  50. package/build/assets/{files-tab-c316dKAq.js → files-tab-BAkaV4aO.js} +1 -1
  51. package/build/assets/{files-tab-store-tfCav6bB.js → files-tab-store-DJsPxYdg.js} +1 -1
  52. package/build/assets/{git-provider-icon-DUb2pEos.js → git-provider-icon-xvbukHaT.js} +1 -1
  53. package/build/assets/{home-D7ds4Jyw.js → home-D01kSnmo.js} +1 -1
  54. package/build/assets/home-NZO2bF4N.js +1 -0
  55. package/build/assets/index-home-BsI6i0TC.js +1 -0
  56. package/build/assets/{install-server-modal-CvHvzZj3.js → install-server-modal-BYjqoRyX.js} +1 -1
  57. package/build/assets/{launch-A47TK2Me.js → launch-DlXspDku.js} +1 -1
  58. package/build/assets/lesson-plan-cNUjpdFx.js +4 -0
  59. package/build/assets/{llm-not-configured-banner-B7DR8SPs.js → llm-not-configured-banner-DUPyoFIZ.js} +7 -7
  60. package/build/assets/{llm-settings-Cvi7q4ox.js → llm-settings-Cs_iieFY.js} +1 -1
  61. package/build/assets/llm-settings-J2ke2foG.js +1 -0
  62. package/build/assets/{llm-subscription-service-Cz1yBBvE.js → llm-subscription-service-C0zN0-rH.js} +1 -1
  63. package/build/assets/{manage-backends-modal-DAVhNUCe.js → manage-backends-modal-nMoCZ2a0.js} +1 -1
  64. package/build/assets/manifest-f1b46f62.js +1 -0
  65. package/build/assets/{map-provider-XSFHmdDs.js → map-provider-BbIb12oh.js} +1 -1
  66. package/build/assets/{markdown-renderer-B6cJJ_MN.js → markdown-renderer-B4V2OMFP.js} +1 -1
  67. package/build/assets/{mcp-BwssgMpg.js → mcp-kZM-cAZw.js} +1 -1
  68. package/build/assets/{messages-D8MeG2ZQ.js → messages-CJAv5n4j.js} +1 -1
  69. package/build/assets/{model-selector-BxTbbKub.js → model-selector-D51W2Q9b.js} +1 -1
  70. package/build/assets/{navigation-link-imH6S7h2.js → navigation-link-D8hfwvxI.js} +1 -1
  71. package/build/assets/{onboarding-CyicadGt.js → onboarding-Dx8n8HFr.js} +1 -1
  72. package/build/assets/onboarding-modal-Bxuxpfdz.js +1 -0
  73. package/build/assets/{organization-service.api-QknzKDh3.js → organization-service.api-Da2jiuBS.js} +1 -1
  74. package/build/assets/{path-utils-BECGet7G.js → path-utils-D53ZHUQU.js} +1 -1
  75. package/build/assets/{planner-tab-DPKHmMAX.js → planner-tab-BM-pKx0z.js} +1 -1
  76. package/build/assets/{plugins-management-service-DuTTxquo.js → plugins-management-service-DISfOL13.js} +1 -1
  77. package/build/assets/{profiles-service.api-D6jvZOWu.js → profiles-service.api-DWetW2TL.js} +1 -1
  78. package/build/assets/providers-C7lMslCi.js +1 -0
  79. package/build/assets/{proxy-CuhhyjdX.js → proxy-G4ce0fnQ.js} +1 -1
  80. package/build/assets/{recommended-automations-launcher-DA6DWS7V.js → recommended-automations-launcher-BWRRvMPE.js} +1 -1
  81. package/build/assets/root-layout-ClEl-JlJ.js +2 -0
  82. package/build/assets/root-oT83qVlk.js +2 -0
  83. package/build/assets/{runtime-waiting-state-DeD9G7yJ.js → runtime-waiting-state-CnyVhX4T.js} +1 -1
  84. package/build/assets/{schema-field-BOLdL_U7.js → schema-field-CwdqUOnE.js} +1 -1
  85. package/build/assets/{sdk-section-page-ZIltERxf.js → sdk-section-page-Cwj0T4gF.js} +1 -1
  86. package/build/assets/{sdk-settings-schema-Ba_Pa9uf.js → sdk-settings-schema-CLR01YIK.js} +1 -1
  87. package/build/assets/{secrets-service-50DElFny.js → secrets-service-Ch7RH8eH.js} +1 -1
  88. package/build/assets/{secrets-settings-Digl7JLe.js → secrets-settings-Bc_pBX6F.js} +1 -1
  89. package/build/assets/{settings-dropdown-input-BD5C9fBn.js → settings-dropdown-input-Dhx9ErGd.js} +1 -1
  90. package/build/assets/{settings-index-CODBijya.js → settings-index-BzSIM1-N.js} +1 -1
  91. package/build/assets/{settings-input-CzNOq-Pj.js → settings-input-BsgQWS5R.js} +1 -1
  92. package/build/assets/{settings-list-classes-CuxH9ZoR.js → settings-list-classes-DSJJX99U.js} +1 -1
  93. package/build/assets/{settings-modal-BcSWpKI0.js → settings-modal-BnZ7mMu7.js} +1 -1
  94. package/build/assets/settings-n9a395O0.js +1 -0
  95. package/build/assets/{settings-service.api-Bsu1PvYO.js → settings-service.api-DPTFyRXL.js} +1 -1
  96. package/build/assets/{settings-switch-DqmHAfWJ.js → settings-switch-Bes8dIlV.js} +1 -1
  97. package/build/assets/{settings-utils-B1lBErvV.js → settings-utils-DzCVfiR3.js} +1 -1
  98. package/build/assets/{shared-conversation-CDskdmrb.js → shared-conversation-CIDDti9I.js} +1 -1
  99. package/build/assets/{sidebar-mobile-menu-toggle-DmCmZgq1.js → sidebar-mobile-menu-toggle-CTX7J4wM.js} +1 -1
  100. package/build/assets/{sidebar-nav-link-DMc8AIe7.js → sidebar-nav-link-CHqyFXwV.js} +1 -1
  101. package/build/assets/{sidebar-store-DXWSCFpa.js → sidebar-store-uiUXBU1N.js} +1 -1
  102. package/build/assets/{skill-card-pill-row-DOIWjiMF.js → skill-card-pill-row-BPYKQ3IN.js} +1 -1
  103. package/build/assets/{skills-plugins-DLgINYuY.js → skills-plugins-HZEbNDmX.js} +1 -1
  104. package/build/assets/{skills-settings-DPiCWalR.js → skills-settings-BdqijFlC.js} +1 -1
  105. package/build/assets/{task-list-tab-CuhcDycu.js → task-list-tab-DyfgG4MC.js} +1 -1
  106. package/build/assets/telemetry-SHxpVxk8.js +2 -0
  107. package/build/assets/{terminal-CVqF-tCD.js → terminal-BDTa1HEr.js} +1 -1
  108. package/build/assets/{typography-CQn9wyEX.js → typography-CzfoD9DY.js} +1 -1
  109. package/build/assets/{use-acp-credential-form-LlfZO_DD.js → use-acp-credential-form-DHd_Id-q.js} +1 -1
  110. package/build/assets/{use-activate-agent-profile-DpQjsXM0.js → use-activate-agent-profile-BRDdSwQ0.js} +1 -1
  111. package/build/assets/{use-active-agent-profile-Gw_uJujV.js → use-active-agent-profile-BMDTXsay.js} +1 -1
  112. package/build/assets/{use-active-conversation-B0sttISF.js → use-active-conversation-BbeVgedA.js} +1 -1
  113. package/build/assets/{use-agent-profiles-LHZEK0Gg.js → use-agent-profiles-Dn_S_Tkz.js} +1 -1
  114. package/build/assets/{use-agent-state-C5uA0bRj.js → use-agent-state-5qM3X30V.js} +1 -1
  115. package/build/assets/{use-backends-health-DKt1SmiU.js → use-backends-health-BagmAE_W.js} +1 -1
  116. package/build/assets/{use-can-manage-org-profiles-BAtqsbCi.js → use-can-manage-org-profiles-CyYqfzeo.js} +1 -1
  117. package/build/assets/{use-cloud-current-user-id-C8m1oBy4.js → use-cloud-current-user-id-BwMX7jHW.js} +1 -1
  118. package/build/assets/use-config-B39iHYFi.js +1 -0
  119. package/build/assets/{use-create-conversation-CWQGNQ9J.js → use-create-conversation-CkKUQVbP.js} +1 -1
  120. package/build/assets/{use-create-secret-BIrsX3OB.js → use-create-secret-DEgM0MXZ.js} +1 -1
  121. package/build/assets/{use-event-store-CsD17BKu.js → use-event-store-Bbaz51GR.js} +1 -1
  122. package/build/assets/{use-handle-plan-click-DEuO7Byo.js → use-handle-plan-click-BFcE0Xms.js} +1 -1
  123. package/build/assets/{use-launch-skill-in-chat-CdHOoQGM.js → use-launch-skill-in-chat-DJWEfVc5.js} +1 -1
  124. package/build/assets/{use-llm-profiles-DQpQxVOD.js → use-llm-profiles-CgRtnj74.js} +1 -1
  125. package/build/assets/{use-plugins-marketplace-Bkk07W_M.js → use-plugins-marketplace-DOJp9rlE.js} +1 -1
  126. package/build/assets/use-runtime-is-ready-ziHQvXZQ.js +1 -0
  127. package/build/assets/{use-save-agent-profile-BxEsjnW0.js → use-save-agent-profile-i4FcEP98.js} +1 -1
  128. package/build/assets/{use-save-settings-ioSUMqbE.js → use-save-settings-DiDEbIJw.js} +1 -1
  129. package/build/assets/{use-settings-DNJC4gqu.js → use-settings-BeJap6YL.js} +1 -1
  130. package/build/assets/{use-settings-nav-items-BUsrDbVR.js → use-settings-nav-items-D5JJjFHJ.js} +1 -1
  131. package/build/assets/{use-skills-BsmKm23M.js → use-skills-Blpg2BqE.js} +1 -1
  132. package/build/assets/{use-task-list-rs7Xbzl8.js → use-task-list-CfisapBW.js} +1 -1
  133. package/build/assets/use-tracking-DFoPQUSU.js +1 -0
  134. package/build/assets/{use-user-conversation-B337kUKB.js → use-user-conversation-BrN2xeQU.js} +1 -1
  135. package/build/assets/{vendor~browser-tab-Xn94EtUo.js → vendor~browser-tab-CsMb-pta.js} +1 -1
  136. package/build/assets/{vendor~browser-5Njjw1Cl.js → vendor~browser-wXQ-WZEC.js} +1 -1
  137. package/build/assets/{vendor~entry.client~root~root-layout~index-home~home~conversation-panel~conversation~launch~dhilvkxv-BIieS_OD.js → vendor~entry.client~root~root-layout~index-home~home~conversation-panel~conversation~launch~dhilvkxv-eGLBu60s.js} +2 -2
  138. package/build/assets/{vendor~files-tab-CiC3-lGh.js → vendor~files-tab-B-5fBohe.js} +1 -1
  139. package/build/assets/{vendor~index-home~home~conversation-panel~conversation~shared-conversation~planner-tab~files-tab-DWteoTyr.js → vendor~index-home~home~conversation-panel~conversation~shared-conversation~planner-tab~files-tab-BD7v71DM.js} +1 -1
  140. package/build/assets/{vendor~index-home~home~conversation-panel~conversation~shared-conversation~planner-tab~files-tab-tlf6AnDe.js → vendor~index-home~home~conversation-panel~conversation~shared-conversation~planner-tab~files-tab-BT05Osu4.js} +1 -1
  141. package/build/assets/{vendor~index-home~home~conversation-panel~conversation~shared-conversation~planner-tab~files-tab-B8XNNEQZ.js → vendor~index-home~home~conversation-panel~conversation~shared-conversation~planner-tab~files-tab-COhHANvV.js} +1 -1
  142. package/build/assets/{vendor~index-home~home~conversation-panel~conversation~shared-conversation~planner-tab~files-tab-Dc5nKwLO.js → vendor~index-home~home~conversation-panel~conversation~shared-conversation~planner-tab~files-tab-DgzWSMRc.js} +1 -1
  143. package/build/assets/{vendor~index-home~home~conversation-panel~conversation~shared-conversation~planner-tab~files-tab-CnJRLn5s.js → vendor~index-home~home~conversation-panel~conversation~shared-conversation~planner-tab~files-tab-DqZatR3B.js} +1 -1
  144. package/build/assets/{vendor~launch-28DK5Rsl.js → vendor~launch-_Km3J77E.js} +1 -1
  145. package/build/assets/{vendor~entry.client~root~root-layout~index-home~home~conversation-panel~conversation~launch~g9hift2g-DmmpG9SI.js → vendor~module-D5a_AClf.js} +1 -1
  146. package/build/assets/{vendor~root-layout~conversation-panel~conversation~shared-conversation-lYNG9C1U.js → vendor~root-layout~conversation-panel~conversation~shared-conversation-BrguKXCn.js} +1 -1
  147. package/build/assets/{vendor~root-layout~index-home~home~conversation-panel~conversation-Zhq6xAxR.js → vendor~root-layout~index-home~home~conversation-panel~conversation-BTvhGHHG.js} +1 -1
  148. package/build/assets/{vendor~root-layout~index-home~home~conversation-panel~conversation~shared-conversation~aler~gfu7qdtz-B81b08bw.js → vendor~root-layout~index-home~home~conversation-panel~conversation~shared-conversation~aler~gfu7qdtz-BOKQkbc6.js} +1 -1
  149. package/build/assets/{vendor~root~root-layout~index-home~home~conversation-panel~conversation~launch~extensions-h~ftka0fnu-jpyLM1Nn.js → vendor~root~root-layout~index-home~home~conversation-panel~conversation~launch~extensions-h~ftka0fnu-cFNmfAE6.js} +1 -1
  150. package/build/assets/{verification-settings-HRgEM-_3.js → verification-settings-C8KzAdAX.js} +1 -1
  151. package/build/index.html +4 -4
  152. package/config/defaults.json +1 -1
  153. package/dist/api/cloud/conversation-service.api.cjs +1 -1
  154. package/dist/api/cloud/conversation-service.api.js +4 -4
  155. package/dist/api/option-service/option-service.api.cjs +1 -1
  156. package/dist/api/option-service/option-service.api.cjs.map +1 -1
  157. package/dist/api/option-service/option-service.api.js +0 -1
  158. package/dist/api/option-service/option-service.api.js.map +1 -1
  159. package/dist/api/option-service/option.types.d.ts +0 -1
  160. package/dist/components/features/backends/backend-form-modal.cjs +1 -1
  161. package/dist/components/features/backends/backend-form-modal.cjs.map +1 -1
  162. package/dist/components/features/backends/backend-form-modal.d.ts +1 -1
  163. package/dist/components/features/backends/backend-form-modal.js +137 -144
  164. package/dist/components/features/backends/backend-form-modal.js.map +1 -1
  165. package/dist/components/providers/agent-server-ui-providers.cjs +1 -1
  166. package/dist/components/providers/agent-server-ui-providers.cjs.map +1 -1
  167. package/dist/components/providers/agent-server-ui-providers.d.ts +4 -2
  168. package/dist/components/providers/agent-server-ui-providers.js +9 -2
  169. package/dist/components/providers/agent-server-ui-providers.js.map +1 -1
  170. package/dist/components/providers/index.cjs +1 -1
  171. package/dist/components/providers/index.d.ts +1 -2
  172. package/dist/components/providers/index.js +0 -1
  173. package/dist/components/providers/telemetry-provider.cjs +2 -0
  174. package/dist/components/providers/telemetry-provider.cjs.map +1 -0
  175. package/dist/components/providers/telemetry-provider.d.ts +6 -0
  176. package/dist/components/providers/telemetry-provider.js +64 -0
  177. package/dist/components/providers/telemetry-provider.js.map +1 -0
  178. package/dist/config/defaults.cjs +1 -1
  179. package/dist/config/defaults.cjs.map +1 -1
  180. package/dist/config/defaults.js +1 -1
  181. package/dist/config/defaults.js.map +1 -1
  182. package/dist/contexts/conversation-websocket-context.cjs +3 -3
  183. package/dist/contexts/conversation-websocket-context.cjs.map +1 -1
  184. package/dist/contexts/conversation-websocket-context.js +197 -204
  185. package/dist/contexts/conversation-websocket-context.js.map +1 -1
  186. package/dist/hooks/mutation/use-save-settings.cjs +1 -1
  187. package/dist/hooks/mutation/use-save-settings.cjs.map +1 -1
  188. package/dist/hooks/mutation/use-save-settings.d.ts +3 -1
  189. package/dist/hooks/mutation/use-save-settings.js +6 -5
  190. package/dist/hooks/mutation/use-save-settings.js.map +1 -1
  191. package/dist/hooks/query/use-task-polling.cjs +1 -1
  192. package/dist/hooks/query/use-task-polling.cjs.map +1 -1
  193. package/dist/hooks/query/use-task-polling.d.ts +19 -3
  194. package/dist/hooks/query/use-task-polling.js +36 -35
  195. package/dist/hooks/query/use-task-polling.js.map +1 -1
  196. package/dist/hooks/use-telemetry.cjs +1 -1
  197. package/dist/hooks/use-telemetry.cjs.map +1 -1
  198. package/dist/hooks/use-telemetry.d.ts +3 -3
  199. package/dist/hooks/use-telemetry.js +16 -22
  200. package/dist/hooks/use-telemetry.js.map +1 -1
  201. package/dist/hooks/use-tracking.cjs +1 -1
  202. package/dist/hooks/use-tracking.cjs.map +1 -1
  203. package/dist/hooks/use-tracking.d.ts +3 -4
  204. package/dist/hooks/use-tracking.js +37 -37
  205. package/dist/hooks/use-tracking.js.map +1 -1
  206. package/dist/index.cjs +1 -1
  207. package/dist/index.js +2 -2
  208. package/dist/lib/index.d.ts +2 -2
  209. package/dist/package.cjs +1 -1
  210. package/dist/package.cjs.map +1 -1
  211. package/dist/package.js +2 -2
  212. package/dist/package.js.map +1 -1
  213. package/dist/routes/app-settings.cjs +1 -1
  214. package/dist/routes/app-settings.cjs.map +1 -1
  215. package/dist/routes/app-settings.js +66 -68
  216. package/dist/routes/app-settings.js.map +1 -1
  217. package/dist/routes/conversation.cjs +1 -1
  218. package/dist/routes/conversation.cjs.map +1 -1
  219. package/dist/routes/conversation.js +1 -1
  220. package/dist/routes/conversation.js.map +1 -1
  221. package/dist/routes/llm-settings.cjs +1 -1
  222. package/dist/routes/llm-settings.cjs.map +1 -1
  223. package/dist/routes/llm-settings.js +4 -1
  224. package/dist/routes/llm-settings.js.map +1 -1
  225. package/dist/services/cloud-funnel-analytics.cjs +1 -1
  226. package/dist/services/cloud-funnel-analytics.cjs.map +1 -1
  227. package/dist/services/cloud-funnel-analytics.d.ts +3 -11
  228. package/dist/services/cloud-funnel-analytics.js +16 -30
  229. package/dist/services/cloud-funnel-analytics.js.map +1 -1
  230. package/dist/services/telemetry.cjs +1 -1
  231. package/dist/services/telemetry.cjs.map +1 -1
  232. package/dist/services/telemetry.d.ts +46 -9
  233. package/dist/services/telemetry.js +181 -72
  234. package/dist/services/telemetry.js.map +1 -1
  235. package/dist/themes/color-themes.cjs.map +1 -1
  236. package/dist/themes/color-themes.d.ts +3 -6
  237. package/dist/themes/color-themes.js.map +1 -1
  238. package/dist/utils/error-handler.cjs +1 -1
  239. package/dist/utils/error-handler.cjs.map +1 -1
  240. package/dist/utils/error-handler.d.ts +1 -4
  241. package/dist/utils/error-handler.js +6 -7
  242. package/dist/utils/error-handler.js.map +1 -1
  243. package/dist/utils/map-provider.cjs +1 -1
  244. package/dist/utils/map-provider.cjs.map +1 -1
  245. package/dist/utils/map-provider.d.ts +1 -0
  246. package/dist/utils/map-provider.js +2 -1
  247. package/dist/utils/map-provider.js.map +1 -1
  248. package/package.json +2 -2
  249. package/build/assets/agent-settings-DGUKNSnU.js +0 -1
  250. package/build/assets/app-settings-BpViWwpC.js +0 -1
  251. package/build/assets/backend-form-modal-DhBT8P4g.js +0 -1
  252. package/build/assets/browser-store-BGXOGo-R.js +0 -1
  253. package/build/assets/client-source-Biqarhgr.js +0 -1
  254. package/build/assets/cloud-funnel-analytics-D-27T5Ao.js +0 -1
  255. package/build/assets/conversation-CHnEXGRM.js +0 -1
  256. package/build/assets/conversation-panel-11FjLCId.js +0 -1
  257. package/build/assets/conversation-state-store-JiUQp02U.js +0 -1
  258. package/build/assets/handle-capture-consent-DDnXMC9Y.js +0 -1
  259. package/build/assets/home-BQ-DC06B.js +0 -1
  260. package/build/assets/index-home-BRFDWs_4.js +0 -1
  261. package/build/assets/lesson-plan-DSf95kQ2.js +0 -4
  262. package/build/assets/llm-settings-Ch4ZbW3c.js +0 -1
  263. package/build/assets/manifest-958c741a.js +0 -1
  264. package/build/assets/onboarding-modal-DQwihcTz.js +0 -1
  265. package/build/assets/option-service.api-DAkg_F7p.js +0 -1
  266. package/build/assets/package-Dk_VtWJ4.js +0 -1
  267. package/build/assets/providers-B8pxQK8x.js +0 -1
  268. package/build/assets/root-BhP6bTBB.js +0 -2
  269. package/build/assets/root-layout-oBaHHW4s.js +0 -2
  270. package/build/assets/settings-BfgLHsgd.js +0 -1
  271. package/build/assets/telemetry-DHiTRl5_.js +0 -2
  272. package/build/assets/use-config-CXy4vD0H.js +0 -1
  273. package/build/assets/use-runtime-is-ready-D4pkGT6E.js +0 -1
  274. package/build/assets/use-tracking-poxQHMwR.js +0 -1
  275. package/build/assets/vendor~entry.client~root~root-layout~index-home~home~conversation-panel~conversation~launch~ljq5gbus-B2XD4bi2.js +0 -1
  276. package/dist/components/providers/posthog-wrapper.cjs +0 -2
  277. package/dist/components/providers/posthog-wrapper.cjs.map +0 -1
  278. package/dist/components/providers/posthog-wrapper.d.ts +0 -4
  279. package/dist/components/providers/posthog-wrapper.js +0 -49
  280. package/dist/components/providers/posthog-wrapper.js.map +0 -1
  281. package/dist/node_modules/posthog-js/react/dist/esm/index.cjs +0 -2
  282. package/dist/node_modules/posthog-js/react/dist/esm/index.cjs.map +0 -1
  283. package/dist/node_modules/posthog-js/react/dist/esm/index.js +0 -113
  284. package/dist/node_modules/posthog-js/react/dist/esm/index.js.map +0 -1
  285. package/dist/utils/handle-capture-consent.cjs +0 -2
  286. package/dist/utils/handle-capture-consent.cjs.map +0 -1
  287. package/dist/utils/handle-capture-consent.d.ts +0 -7
  288. package/dist/utils/handle-capture-consent.js +0 -8
  289. package/dist/utils/handle-capture-consent.js.map +0 -1
  290. /package/build/assets/{__vite-browser-external-DU-Cb_sT.js → __vite-browser-external-BQUj0sYu.js} +0 -0
  291. /package/build/assets/{agent-server-ui-style-scope-COnDHTm5.js → agent-server-ui-style-scope-qhnhZHBV.js} +0 -0
  292. /package/build/assets/{automation-BVEmGghX.js → automation-LZB0MjdV.js} +0 -0
  293. /package/build/assets/{canvas-ui-BhxjoA1f.js → canvas-ui-COgjltGz.js} +0 -0
  294. /package/build/assets/{checkmark-DhcMGwtO.js → checkmark-h7udgXMM.js} +0 -0
  295. /package/build/assets/{close-DO5Rq_CS.js → close-DLFb5kfv.js} +0 -0
  296. /package/build/assets/{code-tag-BvjtzUsr.js → code-tag-DYlAhEQY.js} +0 -0
  297. /package/build/assets/{conversation-local-storage-B31voBg7.js → conversation-local-storage-F6zVMM--.js} +0 -0
  298. /package/build/assets/{conversation-tab-empty-state-BUhKVwoi.js → conversation-tab-empty-state-CNUmIms2.js} +0 -0
  299. /package/build/assets/{copy-DLUw2us-.js → copy-BLmGeJcd.js} +0 -0
  300. /package/build/assets/{dist-Dj01FXbZ.js → dist-CodhvuAP.js} +0 -0
  301. /package/build/assets/{environment-switch-store-D9hB9PvY.js → environment-switch-store-DkjlKWzN.js} +0 -0
  302. /package/build/assets/{extension-module-card-classes-BlO4w702.js → extension-module-card-classes-9V3J9J_b.js} +0 -0
  303. /package/build/assets/{folder-ynxP2EIL.js → folder-Bk311brm.js} +0 -0
  304. /package/build/assets/{form-control-classes-CvUaw8_L.js → form-control-classes-CbdN5aO3.js} +0 -0
  305. /package/build/assets/{get-language-from-path-rWEQrOGU.js → get-language-from-path-DBqBsCWH.js} +0 -0
  306. /package/build/assets/{git-status-mapper-C3rfzUex.js → git-status-mapper-DnL9OC8_.js} +0 -0
  307. /package/build/assets/{iconBase-CuvxAQZ5.js → iconBase-Dz51Zn5x.js} +0 -0
  308. /package/build/assets/{link-external-BVDiJfZt.js → link-external-CaBmUEs_.js} +0 -0
  309. /package/build/assets/{llm-subscription-BkdHwtQe.js → llm-subscription-DtsX5Lhg.js} +0 -0
  310. /package/build/assets/{loading-spinner-Txxx1I1p.js → loading-spinner-BcdnT0mN.js} +0 -0
  311. /package/build/assets/{middleware-CWYDMVuG.js → middleware-DYHYNAnP.js} +0 -0
  312. /package/build/assets/{modal-backdrop-Dsmzgr5s.js → modal-backdrop-zVwfOJZr.js} +0 -0
  313. /package/build/assets/{modal-body-CDZdZ-eT.js → modal-body-Cc2zp0xb.js} +0 -0
  314. /package/build/assets/{modal-classes-B4oit_4c.js → modal-classes-3qT9Ap7Q.js} +0 -0
  315. /package/build/assets/{modal-close-button-DQElFMPo.js → modal-close-button-BcIBUdSq.js} +0 -0
  316. /package/build/assets/{navigation-context-SjzfpygH.js → navigation-context-DPs2G0X8.js} +0 -0
  317. /package/build/assets/{objectWithoutPropertiesLoose-CQwg6tC0.js → objectWithoutPropertiesLoose-BgYIAs2y.js} +0 -0
  318. /package/build/assets/{openhands-logo-C-gDp1wr.js → openhands-logo-bcNw5bkY.js} +0 -0
  319. /package/build/assets/{plan-components-CgPHWz3E.js → plan-components-BdZXPyZU.js} +0 -0
  320. /package/build/assets/{react-ao3tWj6K.js → react-DPDgL6du.js} +0 -0
  321. /package/build/assets/{search-pb4UKTfK.js → search-BgOCR7_c.js} +0 -0
  322. /package/build/assets/{segmented-toggle-C1-ZVyr5.js → segmented-toggle-kzwZkLaj.js} +0 -0
  323. /package/build/assets/{settings-gear-D_HVvE5L.js → settings-gear-MXCyZW0x.js} +0 -0
  324. /package/build/assets/{settings-like-page-layout-classes-DLrsPCaI.js → settings-like-page-layout-classes-Cdl6jOe5.js} +0 -0
  325. /package/build/assets/{settings-DksoDv5z.js → settings-mQUckT0g.js} +0 -0
  326. /package/build/assets/{settings-section-header-context-C2MUIG88.js → settings-section-header-context-COMOIaxV.js} +0 -0
  327. /package/build/assets/{sidebar-layout-Cn6eQaEI.js → sidebar-layout-Bm7pa3Vk.js} +0 -0
  328. /package/build/assets/{skills-CH4xgIh-.js → skills-EqzxYBvT.js} +0 -0
  329. /package/build/assets/{styled-tooltip-CvsjHuIf.js → styled-tooltip-B14i_Uac.js} +0 -0
  330. /package/build/assets/{switch-skeleton-BpSOvZ-n.js → switch-skeleton-DqQwalyu.js} +0 -0
  331. /package/build/assets/{terminal-Dokfkxn0.js → terminal-Cgf___rK.js} +0 -0
  332. /package/build/assets/{toggle-switch-CS0ClOy2.js → toggle-switch-DX_k418f.js} +0 -0
  333. /package/build/assets/{u-check-circle-DPWVO-9N.js → u-check-circle-BqoIxNz_.js} +0 -0
  334. /package/build/assets/{u-check-circle-half-BGNAAjOW.js → u-check-circle-half-MsShT8Ec.js} +0 -0
  335. /package/build/assets/{u-edit-BIskOoEG.js → u-edit-DL5LgKon.js} +0 -0
  336. /package/build/assets/{use-breakpoint-OjLm94cF.js → use-breakpoint-BP3lqLMx.js} +0 -0
  337. /package/build/assets/{use-click-outside-element-Jpt2WWd7.js → use-click-outside-element-D9BYdANT.js} +0 -0
  338. /package/build/assets/{use-is-authed-Dvbpiu3B.js → use-is-authed-BezEyfeh.js} +0 -0
  339. /package/build/assets/{use-onboarding-completion-DkRpDTsD.js → use-onboarding-completion-C_Swglzp.js} +0 -0
  340. /package/build/assets/{v4-DPgam_jW.js → v4-DbCkTSoU.js} +0 -0
  341. /package/build/assets/{vendor~browser-BvEKaLBz.js → vendor~browser-By2MVv_N.js} +0 -0
  342. /package/build/assets/{vendor~conversation-panel~conversation~alert-banner-CznX01nZ.js → vendor~conversation-panel~conversation~alert-banner-CxfzCmkN.js} +0 -0
  343. /package/build/assets/{vendor~index-home~home~conversation-panel~conversation~shared-conversation~planner-tab~files-tab-BuL5kjfS.js → vendor~index-home~home~conversation-panel~conversation~shared-conversation~planner-tab~files-tab-FghQ3Z1a.js} +0 -0
  344. /package/build/assets/{vendor~root-layout~index-home~home~conversation-panel~conversation~launch~skills-settings~a~j2jlu22h-DUYl8gbZ.js → vendor~root-layout~index-home~home~conversation-panel~conversation~launch~skills-settings~a~j2jlu22h-mC0v44WR.js} +0 -0
  345. /package/build/assets/{vendor~root-layout~index-home~home~mcp~automations-list~onboarding-modal-pIgQDxTc.js → vendor~root-layout~index-home~home~mcp~automations-list~onboarding-modal-C4-GDgiF.js} +0 -0
  346. /package/build/assets/{vendor~terminal-jAaNQWS9.js → vendor~terminal-CnKZILnC.js} +0 -0
  347. /package/build/assets/{x-mark-C5przfXl.js → x-mark-Batp6gXa.js} +0 -0
@@ -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 * Library consumers can override this by setting VITE_POSTHOG_API_KEY.\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 { PostHog } from \"posthog-js\";\nimport packageJson from \"../../package.json\";\n\nconst TELEMETRY_CONSENT_KEY = \"openhands-telemetry-consent\";\nconst TELEMETRY_FIRST_USE_KEY = \"openhands-telemetry-first-use\";\nconst TELEMETRY_SESSION_KEY = \"openhands-telemetry-session\";\nconst POSTHOG_INSTANCE_NAME = \"agent-canvas\";\n\n// PostHog project keys — one per deployment environment, hardcoded so they\n// are baked into the static bundle at build time and cannot drift at runtime.\n// Replace POSTHOG_STAGING_KEY with a dedicated project key once provisioned.\nconst POSTHOG_PROD_KEY = \"phc_BgzfxKdgsYMLFTmJqt424ZoyVHvKFfrwttLimzdYTKFK\";\nconst POSTHOG_STAGING_KEY = \"phc_kBtz5nKmxVRRQ7HtPwr2QX9eMC5j65zE86QKocVNwb4U\";\n\n// Always use the staging key unless VITE_APP_ENV is explicitly set to\n// \"production\" at bundle time (hardcoded in build:lib and production CI).\n// Library consumers can always override with VITE_POSTHOG_API_KEY.\nconst POSTHOG_API_KEY: string =\n (import.meta.env.VITE_POSTHOG_API_KEY as string | undefined) ||\n (import.meta.env.VITE_APP_ENV === \"production\"\n ? POSTHOG_PROD_KEY\n : POSTHOG_STAGING_KEY);\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 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 POSTHOG_UI_HOST =\n import.meta.env.VITE_POSTHOG_UI_HOST || \"https://us.posthog.com\";\n\nexport type TelemetryConsent = \"granted\" | \"denied\" | \"pending\";\n\nlet isInitialized = false;\nlet posthogInstance: PostHog | null = null;\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 // 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 */\nasync function initializePostHog(\n enableCapturing = false,\n): Promise<PostHog | null> {\n if (isInitialized) {\n return posthogInstance;\n }\n\n const posthog = await getPostHog();\n if (!posthog) {\n return null;\n }\n\n // Canvas telemetry must not share PostHog's default singleton with host-app\n // analytics. A named instance gives it independent configuration, consent,\n // persistence, and delivery state.\n posthogInstance = posthog.init(\n POSTHOG_API_KEY,\n {\n api_host: POSTHOG_HOST,\n // UI host is required when using a reverse proxy so PostHog features work correctly\n ui_host: POSTHOG_UI_HOST,\n // Start with capturing disabled by default - we enable it explicitly when needed\n opt_out_capturing_by_default: !enableCapturing,\n // Don't auto-capture page views - we control when to track\n capture_pageview: false,\n // Don't auto-capture clicks etc.\n autocapture: false,\n // Use localStorage for persistence\n persistence: \"localStorage\",\n persistence_name: POSTHOG_INSTANCE_NAME,\n consent_persistence_name: `${POSTHOG_INSTANCE_NAME}-consent`,\n // Disable session recording\n disable_session_recording: true,\n // Set default properties for all events\n loaded: (ph) => {\n ph.register({\n package_name: packageJson.name,\n package_version: packageJson.version,\n });\n },\n },\n POSTHOG_INSTANCE_NAME,\n );\n\n isInitialized = true;\n return posthogInstance;\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 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 * Set user's telemetry consent preference\n */\nexport async function setTelemetryConsent(\n consent: \"granted\" | \"denied\",\n): Promise<void> {\n if (!isBrowser()) {\n return;\n }\n\n try {\n localStorage.setItem(TELEMETRY_CONSENT_KEY, consent);\n\n // Initialize PostHog if not already done\n const posthog = await initializePostHog();\n if (!posthog) {\n return;\n }\n\n if (consent === \"granted\") {\n // Enable capturing\n posthog.opt_in_capturing();\n } else {\n // Disable capturing and clear any queued events\n posthog.opt_out_capturing();\n }\n } catch {\n // Ignore storage errors\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 initializePostHog(true);\n if (!posthog) {\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/**\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 if (!isTelemetryEnabled()) {\n return;\n }\n\n // Already sent session event this browser session\n if (hasSessionSent()) {\n return;\n }\n\n // Initialize PostHog if needed\n const posthog = await initializePostHog();\n if (!posthog) {\n return;\n }\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/**\n * Track a custom event (respects consent).\n */\nexport async function trackEvent(\n eventName: string,\n properties: Record<string, unknown> = {},\n): Promise<void> {\n if (!isTelemetryEnabled()) {\n return;\n }\n\n // Initialize PostHog if needed\n const posthog = await initializePostHog();\n if (!posthog) {\n return;\n }\n\n posthog.capture(eventName, 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 sessionStorage.removeItem(TELEMETRY_SESSION_KEY);\n\n // Reset PostHog if initialized\n if (isInitialized && posthogInstance) {\n posthogInstance.reset();\n }\n } catch {\n // Ignore storage errors\n }\n}\n\n/**\n * Get the PostHog instance for advanced usage (if needed).\n * Returns the instance if initialized, otherwise null.\n * Note: This is async because PostHog is lazily loaded.\n */\nexport async function getPostHogInstance(): Promise<PostHog | null> {\n if (!isInitialized) {\n return null;\n }\n return posthogInstance;\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 packageJson from \"../../package.json\";\nimport {\n AGENT_CANVAS_CLIENT_SOURCE,\n AGENT_CANVAS_CLIENT_VERSION,\n} from \"#/api/client-source\";\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\";\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 POSTHOG_STAGING_KEY = \"phc_kBtz5nKmxVRRQ7HtPwr2QX9eMC5j65zE86QKocVNwb4U\";\nconst DEFAULT_POSTHOG_API_KEY: string =\n (import.meta.env.VITE_POSTHOG_API_KEY as string | undefined) ||\n POSTHOG_STAGING_KEY;\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\nfunction addCanvasEventProperties(\n event: CaptureResult | null,\n): CaptureResult | null {\n if (!event) return null;\n\n return {\n ...event,\n properties: {\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 capture_pageview: false,\n autocapture: false,\n persistence: \"localStorage\",\n persistence_name: POSTHOG_INSTANCE_NAME,\n consent_persistence_name: `${POSTHOG_INSTANCE_NAME}-consent`,\n person_profiles: \"identified_only\",\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 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 localStorage.setItem(TELEMETRY_CONSENT_KEY, consent);\n if (telemetryDisabled) 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 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()) 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/**\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 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 }\n}\n"],"mappings":"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"}
@@ -18,22 +18,63 @@
18
18
  * - VITE_POSTHOG_UI_HOST: PostHog UI host (defaults to https://us.posthog.com)
19
19
  *
20
20
  * IMPORTANT: By default, telemetry is sent to the OpenHands PostHog project.
21
- * Library consumers can override this by setting VITE_POSTHOG_API_KEY.
21
+ * Source builds can override this with VITE_POSTHOG_API_KEY. Precompiled
22
+ * library consumers can pass the same settings to configureTelemetry().
22
23
  *
23
24
  * Users can disable all telemetry (including install tracking) via:
24
25
  * - Setting VITE_DO_NOT_TRACK=1 environment variable
25
26
  * - Browser's Do Not Track setting
26
27
  */
27
- import type { PostHog } from "posthog-js";
28
+ import type { BootstrapConfig, PostHog } from "posthog-js";
29
+ export interface TelemetryConfig {
30
+ /** PostHog project key. Useful for precompiled library consumers. */
31
+ apiKey?: string;
32
+ /** Event ingestion host or reverse proxy. */
33
+ apiHost?: string;
34
+ /** PostHog UI host used by toolbar links and other UI features. */
35
+ uiHost?: string;
36
+ }
37
+ export type TelemetryConfiguration = TelemetryConfig | false;
28
38
  export type TelemetryConsent = "granted" | "denied" | "pending";
39
+ export type ResolvedTelemetryConsent = Exclude<TelemetryConsent, "pending">;
40
+ export interface SetTelemetryConsentOptions {
41
+ /** Do not persist a value mirrored from backend settings back to Cloud. */
42
+ syncToCloud?: boolean;
43
+ }
44
+ /**
45
+ * Configure the single Canvas telemetry client before its first use.
46
+ * Passing false disables telemetry and install tracking for embedded hosts.
47
+ */
48
+ export declare function configureTelemetry(config: TelemetryConfiguration): void;
49
+ /**
50
+ * Initialize PostHog SDK.
51
+ *
52
+ * @param enableCapturing - If true, enable capturing immediately (for install tracking).
53
+ * If false, start with capturing disabled (for consent-gated tracking).
54
+ */
55
+ export declare function configurePostHogBootstrap(bootstrap: BootstrapConfig | undefined): void;
56
+ export declare function initializePostHogClient(enableCapturing?: boolean): Promise<PostHog | null>;
29
57
  /**
30
58
  * Get user's telemetry consent preference
31
59
  */
32
60
  export declare function getTelemetryConsent(): TelemetryConsent;
61
+ /**
62
+ * Return an explicit browser choice that still needs to survive a Cloud login.
63
+ * It remains pending across local backends so their settings cannot consume a
64
+ * decision that must still be applied after the user connects to Cloud.
65
+ */
66
+ export declare function getPendingCloudTelemetryConsent(): ResolvedTelemetryConsent | null;
67
+ export declare function subscribeTelemetryConsent(listener: () => void): () => void;
68
+ export declare function clearPendingCloudTelemetryConsent(expected?: ResolvedTelemetryConsent): void;
33
69
  /**
34
70
  * Set user's telemetry consent preference
35
71
  */
36
- export declare function setTelemetryConsent(consent: "granted" | "denied"): Promise<void>;
72
+ export declare function setTelemetryConsent(consent: ResolvedTelemetryConsent, { syncToCloud }?: SetTelemetryConsentOptions): Promise<void>;
73
+ /**
74
+ * Declare the current Cloud identity. The telemetry service applies it only
75
+ * after consent and owns all reset/account-switch semantics.
76
+ */
77
+ export declare function setTelemetryIdentity(distinctId: string | null, properties?: Record<string, unknown>): Promise<void>;
37
78
  /**
38
79
  * Check if telemetry is enabled (user has granted consent)
39
80
  */
@@ -64,13 +105,9 @@ export declare function trackSessionStart(): Promise<void>;
64
105
  * Track a custom event (respects consent).
65
106
  */
66
107
  export declare function trackEvent(eventName: string, properties?: Record<string, unknown>): Promise<void>;
108
+ /** Track an exception through the same consent-aware client as custom events. */
109
+ export declare function trackException(error: unknown, properties?: Record<string, unknown>): Promise<void>;
67
110
  /**
68
111
  * Clear all telemetry data (for privacy/GDPR requests)
69
112
  */
70
113
  export declare function clearTelemetryData(): Promise<void>;
71
- /**
72
- * Get the PostHog instance for advanced usage (if needed).
73
- * Returns the instance if initialized, otherwise null.
74
- * Note: This is async because PostHog is lazily loaded.
75
- */
76
- export declare function getPostHogInstance(): Promise<PostHog | null>;
@@ -1,12 +1,65 @@
1
1
  import e from "../package.js";
2
+ import { AGENT_CANVAS_CLIENT_SOURCE as t, AGENT_CANVAS_CLIENT_VERSION as n } from "../api/client-source.js";
2
3
  //#region src/services/telemetry.ts
3
- var t = "openhands-telemetry-consent", n = "openhands-telemetry-first-use", r = "openhands-telemetry-session", i = "agent-canvas", a = "phc_BgzfxKdgsYMLFTmJqt424ZoyVHvKFfrwttLimzdYTKFK", o = "https://z.openhands.dev", s = "https://us.posthog.com", c = !1, l = null;
4
- function u() {
4
+ var r = "openhands-telemetry-consent", i = "openhands-telemetry-consent-pending-cloud-sync", a = "openhands-telemetry-consent-change", o = "openhands-telemetry-first-use", s = "openhands-telemetry-session", c = "agent-canvas", l = "phc_BgzfxKdgsYMLFTmJqt424ZoyVHvKFfrwttLimzdYTKFK", u = "https://z.openhands.dev", d = "https://us.posthog.com", f = null, p = null, m, h = {}, g = !1, _, v = 0, y = -1, b = Object.freeze({
5
+ client_source: t,
6
+ client_version: n,
7
+ package_name: e.name,
8
+ package_version: e.version
9
+ });
10
+ function x(e) {
11
+ return e ? {
12
+ ...e,
13
+ properties: {
14
+ ...e.properties,
15
+ ...b
16
+ }
17
+ } : null;
18
+ }
19
+ function S(e) {
20
+ g || M() !== "granted" ? e.opt_out_capturing() : e.opt_in_capturing();
21
+ }
22
+ function C(e, t = !1) {
23
+ e.reset(t), y = -1, S(e);
24
+ }
25
+ function w(e) {
26
+ if (_ === void 0 || !R()) return;
27
+ let t = _?.distinctId, n = e.get_property("$user_id");
28
+ if (n != null && n !== t && C(e), _ === null) {
29
+ y = v;
30
+ return;
31
+ }
32
+ (e.get_property("$user_id") !== _.distinctId || y !== v) && (e.identify(_.distinctId, _.properties), y = v);
33
+ }
34
+ function T(e) {
35
+ if (e === !1) {
36
+ if (g) return;
37
+ g = !0, f?.opt_out_capturing(), P();
38
+ return;
39
+ }
40
+ let t = g;
41
+ if (g = !1, !f && !p) {
42
+ let t = Object.fromEntries(Object.entries(e).filter(([, e]) => e !== void 0));
43
+ h = {
44
+ ...h,
45
+ ...t
46
+ };
47
+ }
48
+ t && (f && (S(f), w(f)), P());
49
+ }
50
+ function E() {
51
+ return g ? null : {
52
+ apiKey: h.apiKey || l,
53
+ apiHost: h.apiHost || u,
54
+ uiHost: h.uiHost || d
55
+ };
56
+ }
57
+ function D() {
5
58
  return typeof window < "u" && typeof localStorage < "u";
6
59
  }
7
- async function d() {
8
- if (!u()) return null;
9
- if (l) return l;
60
+ async function O() {
61
+ if (!D()) return null;
62
+ if (f) return f;
10
63
  try {
11
64
  let { default: e } = await import("../node_modules/posthog-js/dist/module.js");
12
65
  return e;
@@ -14,106 +67,162 @@ async function d() {
14
67
  return null;
15
68
  }
16
69
  }
17
- function f() {
18
- return typeof process < "u" && process.env?.DO_NOT_TRACK === "1" || typeof navigator < "u" && (navigator.doNotTrack === "1" || typeof window < "u" && window.doNotTrack === "1");
19
- }
20
- async function p(t = !1) {
21
- if (c) return l;
22
- let n = await d();
23
- return n ? (l = n.init(a, {
24
- api_host: o,
25
- ui_host: s,
26
- opt_out_capturing_by_default: !t,
27
- capture_pageview: !1,
28
- autocapture: !1,
29
- persistence: "localStorage",
30
- persistence_name: i,
31
- consent_persistence_name: `${i}-consent`,
32
- disable_session_recording: !0,
33
- loaded: (t) => {
34
- t.register({
35
- package_name: e.name,
36
- package_version: e.version
37
- });
38
- }
39
- }, i), c = !0, l) : null;
70
+ function k() {
71
+ return !!(g || typeof process < "u" && process.env?.DO_NOT_TRACK === "1" || typeof navigator < "u" && (navigator.doNotTrack === "1" || typeof window < "u" && window.doNotTrack === "1"));
72
+ }
73
+ function A(e) {
74
+ f || (m = e);
75
+ }
76
+ async function j(e = !1) {
77
+ if (f) return f;
78
+ if (p) return p;
79
+ p = (async () => {
80
+ let t = E();
81
+ if (!t) return null;
82
+ let n = await O();
83
+ if (!n) return null;
84
+ let r = n.init(t.apiKey, {
85
+ api_host: t.apiHost,
86
+ ui_host: t.uiHost,
87
+ opt_out_capturing_by_default: !e,
88
+ capture_pageview: !1,
89
+ autocapture: !1,
90
+ persistence: "localStorage",
91
+ persistence_name: c,
92
+ consent_persistence_name: `${c}-consent`,
93
+ person_profiles: "identified_only",
94
+ disable_session_recording: !0,
95
+ bootstrap: m,
96
+ before_send: x
97
+ }, c);
98
+ return r ? (f = r, m = void 0, g ? f.opt_out_capturing() : M() === "granted" ? (f.opt_in_capturing(), w(f)) : e || f.opt_out_capturing(), f) : null;
99
+ })();
100
+ try {
101
+ return await p;
102
+ } finally {
103
+ f || (p = null);
104
+ }
40
105
  }
41
- function m() {
42
- if (!u()) return "pending";
43
- if (f()) return "denied";
106
+ function M() {
107
+ if (!D()) return "pending";
108
+ if (k()) return "denied";
44
109
  try {
45
- let e = localStorage.getItem(t);
110
+ let e = localStorage.getItem(r);
46
111
  if (e === "granted" || e === "denied") return e;
47
112
  } catch {}
48
113
  return "pending";
49
114
  }
50
- async function h(e) {
51
- if (u()) try {
52
- localStorage.setItem(t, e);
53
- let n = await p();
54
- if (!n) return;
55
- e === "granted" ? n.opt_in_capturing() : n.opt_out_capturing();
115
+ function N(e) {
116
+ if (!D()) return () => {};
117
+ let t = (t) => {
118
+ (t.key === r || t.key === i) && e();
119
+ };
120
+ return window.addEventListener(a, e), window.addEventListener("storage", t), () => {
121
+ window.removeEventListener(a, e), window.removeEventListener("storage", t);
122
+ };
123
+ }
124
+ function P() {
125
+ D() && window.dispatchEvent(new Event(a));
126
+ }
127
+ function F(e) {
128
+ if (D()) try {
129
+ localStorage.setItem(i, e);
130
+ } catch {}
131
+ }
132
+ function I(e) {
133
+ if (D()) try {
134
+ if (e !== void 0 && localStorage.getItem(i) !== e) return;
135
+ localStorage.removeItem(i), P();
56
136
  } catch {}
57
137
  }
58
- function g() {
59
- return m() === "granted";
138
+ async function L(e, { syncToCloud: t = !0 } = {}) {
139
+ if (D()) try {
140
+ if (localStorage.setItem(r, e), g) return;
141
+ let t = f ?? await j();
142
+ if (!t) return;
143
+ e === "granted" ? (t.opt_in_capturing(), w(t)) : t.get_property("$user_id") == null ? t.opt_out_capturing() : C(t);
144
+ } catch {} finally {
145
+ t && F(e), P();
146
+ }
147
+ }
148
+ function R() {
149
+ return M() === "granted";
60
150
  }
61
- function _() {
62
- if (!u()) return !1;
151
+ function z() {
152
+ if (!D()) return !1;
63
153
  try {
64
- return localStorage.getItem(n) === "true";
154
+ return localStorage.getItem(o) === "true";
65
155
  } catch {
66
156
  return !1;
67
157
  }
68
158
  }
69
- function v() {
70
- if (u()) try {
71
- localStorage.setItem(n, "true");
159
+ function B() {
160
+ if (D()) try {
161
+ localStorage.setItem(o, "true");
72
162
  } catch {}
73
163
  }
74
- async function y() {
75
- if (f() || _()) return;
76
- let e = await p(!0);
77
- e && ((e.has_opted_out_capturing?.() ?? !1) && e.opt_in_capturing(), e.capture("canvas_install", {
164
+ async function V() {
165
+ if (k() || z()) return;
166
+ let e = await j(!0);
167
+ !e || k() || ((e.has_opted_out_capturing?.() ?? !1) && e.opt_in_capturing(), e.capture("canvas_install", {
78
168
  platform: typeof navigator < "u" ? navigator.platform : "unknown",
79
169
  user_agent: typeof navigator < "u" ? navigator.userAgent : "unknown",
80
170
  referrer: typeof document < "u" ? document.referrer : "",
81
171
  url_origin: typeof window < "u" ? window.location.origin : "",
82
172
  embedded: typeof window < "u" && window.self !== window.top
83
- }), v(), m() !== "granted" && e.opt_out_capturing());
173
+ }), B(), M() !== "granted" && e.opt_out_capturing());
84
174
  }
85
- function b() {
86
- if (!u()) return !1;
175
+ function H() {
176
+ if (!D()) return !1;
87
177
  try {
88
- return sessionStorage.getItem(r) === "true";
178
+ return sessionStorage.getItem(s) === "true";
89
179
  } catch {
90
180
  return !1;
91
181
  }
92
182
  }
93
- function x() {
94
- if (u()) try {
95
- sessionStorage.setItem(r, "true");
183
+ function U() {
184
+ if (D()) try {
185
+ sessionStorage.setItem(s, "true");
96
186
  } catch {}
97
187
  }
98
- async function S() {
99
- if (!g() || b()) return;
100
- let e = await p();
101
- e && (e.capture("canvas_new_session", { is_first_use: !_() }), x());
188
+ async function W() {
189
+ if (!R()) return null;
190
+ let e = await j();
191
+ return !e || !R() ? null : (e.has_opted_out_capturing?.() && e.opt_in_capturing(), w(e), e);
102
192
  }
103
- async function C(e, t = {}) {
104
- if (!g()) return;
105
- let n = await p();
106
- n && n.capture(e, t);
193
+ async function G() {
194
+ if (H()) return;
195
+ let e = await W();
196
+ e && (e.capture("canvas_new_session", { is_first_use: !z() }), U());
107
197
  }
108
- async function w() {
109
- if (u()) try {
110
- localStorage.removeItem(t), localStorage.removeItem(n), sessionStorage.removeItem(r), c && l && l.reset();
111
- } catch {}
198
+ async function K(e, t = {}) {
199
+ let n = await W();
200
+ n && n.capture(e, t);
112
201
  }
113
- async function T() {
114
- return c ? l : null;
202
+ async function q(e, t = {}) {
203
+ let n = await W();
204
+ n && n.captureException(e, t);
205
+ }
206
+ async function J() {
207
+ if (D()) {
208
+ try {
209
+ localStorage.removeItem(r), localStorage.removeItem(o);
210
+ } catch {}
211
+ I();
212
+ try {
213
+ sessionStorage.removeItem(s);
214
+ } catch {}
215
+ _ = null, v += 1, y = -1;
216
+ try {
217
+ f && C(f, !0);
218
+ } catch {
219
+ try {
220
+ f?.opt_out_capturing();
221
+ } catch {}
222
+ }
223
+ }
115
224
  }
116
225
  //#endregion
117
- export { w as clearTelemetryData, T as getPostHogInstance, m as getTelemetryConsent, g as isTelemetryEnabled, h as setTelemetryConsent, C as trackEvent, y as trackInstall, S as trackSessionStart };
226
+ export { J as clearTelemetryData, A as configurePostHogBootstrap, T as configureTelemetry, M as getTelemetryConsent, j as initializePostHogClient, R as isTelemetryEnabled, L as setTelemetryConsent, N as subscribeTelemetryConsent, K as trackEvent, q as trackException, V as trackInstall, G as trackSessionStart };
118
227
 
119
228
  //# sourceMappingURL=telemetry.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"telemetry.js","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 * Library consumers can override this by setting VITE_POSTHOG_API_KEY.\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 { PostHog } from \"posthog-js\";\nimport packageJson from \"../../package.json\";\n\nconst TELEMETRY_CONSENT_KEY = \"openhands-telemetry-consent\";\nconst TELEMETRY_FIRST_USE_KEY = \"openhands-telemetry-first-use\";\nconst TELEMETRY_SESSION_KEY = \"openhands-telemetry-session\";\nconst POSTHOG_INSTANCE_NAME = \"agent-canvas\";\n\n// PostHog project keys — one per deployment environment, hardcoded so they\n// are baked into the static bundle at build time and cannot drift at runtime.\n// Replace POSTHOG_STAGING_KEY with a dedicated project key once provisioned.\nconst POSTHOG_PROD_KEY = \"phc_BgzfxKdgsYMLFTmJqt424ZoyVHvKFfrwttLimzdYTKFK\";\nconst POSTHOG_STAGING_KEY = \"phc_kBtz5nKmxVRRQ7HtPwr2QX9eMC5j65zE86QKocVNwb4U\";\n\n// Always use the staging key unless VITE_APP_ENV is explicitly set to\n// \"production\" at bundle time (hardcoded in build:lib and production CI).\n// Library consumers can always override with VITE_POSTHOG_API_KEY.\nconst POSTHOG_API_KEY: string =\n (import.meta.env.VITE_POSTHOG_API_KEY as string | undefined) ||\n (import.meta.env.VITE_APP_ENV === \"production\"\n ? POSTHOG_PROD_KEY\n : POSTHOG_STAGING_KEY);\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 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 POSTHOG_UI_HOST =\n import.meta.env.VITE_POSTHOG_UI_HOST || \"https://us.posthog.com\";\n\nexport type TelemetryConsent = \"granted\" | \"denied\" | \"pending\";\n\nlet isInitialized = false;\nlet posthogInstance: PostHog | null = null;\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 // 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 */\nasync function initializePostHog(\n enableCapturing = false,\n): Promise<PostHog | null> {\n if (isInitialized) {\n return posthogInstance;\n }\n\n const posthog = await getPostHog();\n if (!posthog) {\n return null;\n }\n\n // Canvas telemetry must not share PostHog's default singleton with host-app\n // analytics. A named instance gives it independent configuration, consent,\n // persistence, and delivery state.\n posthogInstance = posthog.init(\n POSTHOG_API_KEY,\n {\n api_host: POSTHOG_HOST,\n // UI host is required when using a reverse proxy so PostHog features work correctly\n ui_host: POSTHOG_UI_HOST,\n // Start with capturing disabled by default - we enable it explicitly when needed\n opt_out_capturing_by_default: !enableCapturing,\n // Don't auto-capture page views - we control when to track\n capture_pageview: false,\n // Don't auto-capture clicks etc.\n autocapture: false,\n // Use localStorage for persistence\n persistence: \"localStorage\",\n persistence_name: POSTHOG_INSTANCE_NAME,\n consent_persistence_name: `${POSTHOG_INSTANCE_NAME}-consent`,\n // Disable session recording\n disable_session_recording: true,\n // Set default properties for all events\n loaded: (ph) => {\n ph.register({\n package_name: packageJson.name,\n package_version: packageJson.version,\n });\n },\n },\n POSTHOG_INSTANCE_NAME,\n );\n\n isInitialized = true;\n return posthogInstance;\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 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 * Set user's telemetry consent preference\n */\nexport async function setTelemetryConsent(\n consent: \"granted\" | \"denied\",\n): Promise<void> {\n if (!isBrowser()) {\n return;\n }\n\n try {\n localStorage.setItem(TELEMETRY_CONSENT_KEY, consent);\n\n // Initialize PostHog if not already done\n const posthog = await initializePostHog();\n if (!posthog) {\n return;\n }\n\n if (consent === \"granted\") {\n // Enable capturing\n posthog.opt_in_capturing();\n } else {\n // Disable capturing and clear any queued events\n posthog.opt_out_capturing();\n }\n } catch {\n // Ignore storage errors\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 initializePostHog(true);\n if (!posthog) {\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/**\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 if (!isTelemetryEnabled()) {\n return;\n }\n\n // Already sent session event this browser session\n if (hasSessionSent()) {\n return;\n }\n\n // Initialize PostHog if needed\n const posthog = await initializePostHog();\n if (!posthog) {\n return;\n }\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/**\n * Track a custom event (respects consent).\n */\nexport async function trackEvent(\n eventName: string,\n properties: Record<string, unknown> = {},\n): Promise<void> {\n if (!isTelemetryEnabled()) {\n return;\n }\n\n // Initialize PostHog if needed\n const posthog = await initializePostHog();\n if (!posthog) {\n return;\n }\n\n posthog.capture(eventName, 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 sessionStorage.removeItem(TELEMETRY_SESSION_KEY);\n\n // Reset PostHog if initialized\n if (isInitialized && posthogInstance) {\n posthogInstance.reset();\n }\n } catch {\n // Ignore storage errors\n }\n}\n\n/**\n * Get the PostHog instance for advanced usage (if needed).\n * Returns the instance if initialized, otherwise null.\n * Note: This is async because PostHog is lazily loaded.\n */\nexport async function getPostHogInstance(): Promise<PostHog | null> {\n if (!isInitialized) {\n return null;\n }\n return posthogInstance;\n}\n"],"mappings":";;AA8BA,IAAM,IAAwB,+BACxB,IAA0B,iCAC1B,IAAwB,+BACxB,IAAwB,gBAWxB,IAGA,oDAMA,IACiC,2BAIjC,IACoC,0BAItC,IAAgB,IAChB,IAAkC;AAKtC,SAAS,IAAqB;AAC5B,QAAO,OAAO,SAAW,OAAe,OAAO,eAAiB;;AAOlE,eAAe,IAAsC;AACnD,KAAI,CAAC,GAAW,CACd,QAAO;AAGT,KAAI,EACF,QAAO;AAGT,KAAI;EACF,IAAM,EAAE,SAAS,MAAY,MAAM,OAAO;AAC1C,SAAO;SACD;AAEN,SAAO;;;AAQX,SAAS,IAA+B;AAwBtC,QAdI,OAAO,UAAY,OAAe,QAAQ,KAAK,iBAAiB,OAMlE,OAAO,YAAc,QACpB,UAAU,eAAe,OAEvB,OAAO,SAAW,OAAe,OAAO,eAAe;;AAc9D,eAAe,EACb,IAAkB,IACO;AACzB,KAAI,EACF,QAAO;CAGT,IAAM,IAAU,MAAM,GAAY;AAsClC,QArCK,KAOL,IAAkB,EAAQ,KACxB,GACA;EACE,UAAU;EAEV,SAAS;EAET,8BAA8B,CAAC;EAE/B,kBAAkB;EAElB,aAAa;EAEb,aAAa;EACb,kBAAkB;EAClB,0BAA0B,GAAG,EAAsB;EAEnD,2BAA2B;EAE3B,SAAS,MAAO;AACd,KAAG,SAAS;IACV,cAAc,EAAY;IAC1B,iBAAiB,EAAY;IAC9B,CAAC;;EAEL,EACD,EACD,EAED,IAAgB,IACT,KApCE;;AA0CX,SAAgB,IAAwC;AACtD,KAAI,CAAC,GAAW,CACd,QAAO;AAIT,KAAI,GAAqB,CACvB,QAAO;AAGT,KAAI;EACF,IAAM,IAAU,aAAa,QAAQ,EAAsB;AAC3D,MAAI,MAAY,aAAa,MAAY,SACvC,QAAO;SAEH;AAIR,QAAO;;AAMT,eAAsB,EACpB,GACe;AACV,QAAW,CAIhB,KAAI;AACF,eAAa,QAAQ,GAAuB,EAAQ;EAGpD,IAAM,IAAU,MAAM,GAAmB;AACzC,MAAI,CAAC,EACH;AAGF,EAAI,MAAY,YAEd,EAAQ,kBAAkB,GAG1B,EAAQ,mBAAmB;SAEvB;;AAQV,SAAgB,IAA8B;AAC5C,QAAO,GAAqB,KAAK;;AAMnC,SAAS,IAA2B;AAClC,KAAI,CAAC,GAAW,CACd,QAAO;AAGT,KAAI;AACF,SAAO,aAAa,QAAQ,EAAwB,KAAK;SACnD;AACN,SAAO;;;AAOX,SAAS,IAAyB;AAC3B,QAAW,CAIhB,KAAI;AACF,eAAa,QAAQ,GAAyB,OAAO;SAC/C;;AAoBV,eAAsB,IAA8B;AAOlD,KALI,GAAqB,IAKrB,GAAiB,CACnB;CAIF,IAAM,IAAU,MAAM,EAAkB,GAAK;AACxC,QAKe,EAAQ,2BAA2B,IAAI,OAEzD,EAAQ,kBAAkB,EAI5B,EAAQ,QAAQ,kBAAkB;EAChC,UAAU,OAAO,YAAc,MAAc,UAAU,WAAW;EAClE,YACE,OAAO,YAAc,MAAc,UAAU,YAAY;EAC3D,UAAU,OAAO,WAAa,MAAc,SAAS,WAAW;EAChE,YAAY,OAAO,SAAW,MAAc,OAAO,SAAS,SAAS;EACrE,UAAU,OAAO,SAAW,OAAe,OAAO,SAAS,OAAO;EACnE,CAAC,EAGF,GAAkB,EAIK,GACnB,KAAmB,aACrB,EAAQ,mBAAmB;;AAO/B,SAAS,IAA0B;AACjC,KAAI,CAAC,GAAW,CACd,QAAO;AAGT,KAAI;AACF,SAAO,eAAe,QAAQ,EAAsB,KAAK;SACnD;AACN,SAAO;;;AAOX,SAAS,IAAwB;AAC1B,QAAW,CAIhB,KAAI;AACF,iBAAe,QAAQ,GAAuB,OAAO;SAC/C;;AAUV,eAAsB,IAAmC;AAMvD,KALI,CAAC,GAAoB,IAKrB,GAAgB,CAClB;CAIF,IAAM,IAAU,MAAM,GAAmB;AACpC,OAIL,EAAQ,QAAQ,sBAAsB,EACpC,cAAc,CAAC,GAAiB,EACjC,CAAC,EAGF,GAAiB;;AAMnB,eAAsB,EACpB,GACA,IAAsC,EAAE,EACzB;AACf,KAAI,CAAC,GAAoB,CACvB;CAIF,IAAM,IAAU,MAAM,GAAmB;AACpC,MAIL,EAAQ,QAAQ,GAAW,EAAW;;AAMxC,eAAsB,IAAoC;AACnD,QAAW,CAIhB,KAAI;AAMF,EALA,aAAa,WAAW,EAAsB,EAC9C,aAAa,WAAW,EAAwB,EAChD,eAAe,WAAW,EAAsB,EAG5C,KAAiB,KACnB,EAAgB,OAAO;SAEnB;;AAUV,eAAsB,IAA8C;AAIlE,QAHK,IAGE,IAFE"}
1
+ {"version":3,"file":"telemetry.js","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 packageJson from \"../../package.json\";\nimport {\n AGENT_CANVAS_CLIENT_SOURCE,\n AGENT_CANVAS_CLIENT_VERSION,\n} from \"#/api/client-source\";\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\";\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 POSTHOG_STAGING_KEY = \"phc_kBtz5nKmxVRRQ7HtPwr2QX9eMC5j65zE86QKocVNwb4U\";\nconst DEFAULT_POSTHOG_API_KEY: string =\n (import.meta.env.VITE_POSTHOG_API_KEY as string | undefined) ||\n POSTHOG_STAGING_KEY;\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\nfunction addCanvasEventProperties(\n event: CaptureResult | null,\n): CaptureResult | null {\n if (!event) return null;\n\n return {\n ...event,\n properties: {\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 capture_pageview: false,\n autocapture: false,\n persistence: \"localStorage\",\n persistence_name: POSTHOG_INSTANCE_NAME,\n consent_persistence_name: `${POSTHOG_INSTANCE_NAME}-consent`,\n person_profiles: \"identified_only\",\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 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 localStorage.setItem(TELEMETRY_CONSENT_KEY, consent);\n if (telemetryDisabled) 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 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()) 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/**\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 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 }\n}\n"],"mappings":"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"}