@agent-native/core 0.113.0 → 0.114.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (1638) hide show
  1. package/agent-native.eject.json +164 -0
  2. package/corpus/README.md +3 -3
  3. package/corpus/core/CHANGELOG.md +20 -0
  4. package/corpus/core/README.md +61 -174
  5. package/corpus/core/agent-native.eject.json +164 -0
  6. package/corpus/core/docs/content/agent-native-toolkit.mdx +38 -0
  7. package/corpus/core/docs/content/package-lifecycle.mdx +54 -4
  8. package/corpus/core/package.json +15 -2
  9. package/corpus/core/src/cli/eject.ts +1594 -0
  10. package/corpus/core/src/cli/index.ts +16 -0
  11. package/corpus/core/src/client/build-compatibility.ts +77 -0
  12. package/corpus/core/src/client/index.ts +4 -0
  13. package/corpus/core/src/client/resources/McpConnectionSuggestion.tsx +8 -2
  14. package/corpus/core/src/client/resources/McpIntegrationDialog.tsx +7 -2
  15. package/corpus/core/src/client/resources/ResourcesPanel.tsx +17 -3
  16. package/corpus/core/src/client/resources/index.ts +19 -0
  17. package/corpus/core/src/client/resources/mcp-integration-catalog.ts +9 -1
  18. package/corpus/core/src/client/resources/runtime.ts +1 -0
  19. package/corpus/core/src/client/route-chunk-recovery/index.ts +1 -0
  20. package/corpus/core/src/client/setup-connections/catalog.ts +6 -0
  21. package/corpus/core/src/client/setup-connections/index.ts +1 -0
  22. package/corpus/core/src/client/setup-connections/runtime.ts +1 -0
  23. package/corpus/core/src/client/use-action.ts +32 -1
  24. package/corpus/core/src/connections/catalog.ts +41 -24
  25. package/corpus/core/src/connections/index.ts +1 -0
  26. package/corpus/core/src/connections/runtime.ts +1 -0
  27. package/corpus/core/src/deploy/build.ts +4 -1
  28. package/corpus/core/src/eject/provider-api-definitions.ts +193 -0
  29. package/corpus/core/src/eject/remote-mcp-presets.ts +137 -0
  30. package/corpus/core/src/eject/setup-readiness-ui.tsx +29 -0
  31. package/corpus/core/src/eject/workspace-connections.ts +92 -0
  32. package/corpus/core/src/index.ts +1 -0
  33. package/corpus/core/src/integrations/adapter-overrides.ts +12 -0
  34. package/corpus/core/src/integrations/eject/messaging-adapters.ts +85 -0
  35. package/corpus/core/src/integrations/index.ts +5 -1
  36. package/corpus/core/src/integrations/plugin.ts +34 -12
  37. package/corpus/core/src/integrations/runtime.ts +1 -0
  38. package/corpus/core/src/integrations/types.ts +3 -1
  39. package/corpus/core/src/org/context.ts +56 -30
  40. package/corpus/core/src/package-lifecycle/eject-manifest.ts +345 -0
  41. package/corpus/core/src/package-lifecycle/index.ts +1 -0
  42. package/corpus/core/src/provider-api/index.ts +46 -12
  43. package/corpus/core/src/provider-api/runtime.ts +1 -0
  44. package/corpus/core/src/server/action-routes.ts +52 -2
  45. package/corpus/core/src/server/auth-plugin.ts +9 -4
  46. package/corpus/core/src/server/better-auth-instance.ts +5 -1
  47. package/corpus/core/src/setup-connections/index.ts +1 -0
  48. package/corpus/core/src/shared/mcp-embed-headers.ts +1 -1
  49. package/corpus/core/src/shared/merge-by-id.ts +14 -0
  50. package/corpus/core/src/templates/default/.agents/skills/agent-native-docs/SKILL.md +15 -13
  51. package/corpus/core/src/templates/default/.agents/skills/customizing-agent-native/SKILL.md +63 -20
  52. package/corpus/core/src/templates/default/.agents/skills/self-modifying-code/SKILL.md +9 -6
  53. package/corpus/core/src/templates/default/AGENTS.md +6 -5
  54. package/corpus/core/src/templates/headless/.agents/skills/agent-native-docs/SKILL.md +15 -13
  55. package/corpus/core/src/templates/headless/.agents/skills/customizing-agent-native/SKILL.md +63 -20
  56. package/corpus/core/src/templates/headless/AGENTS.md +5 -4
  57. package/corpus/core/src/templates/workspace-core/.agents/skills/agent-native-docs/SKILL.md +15 -13
  58. package/corpus/core/src/templates/workspace-core/.agents/skills/agent-native-toolkit/SKILL.md +3 -2
  59. package/corpus/core/src/templates/workspace-core/.agents/skills/customizing-agent-native/SKILL.md +63 -20
  60. package/corpus/core/src/templates/workspace-core/.agents/skills/self-modifying-code/SKILL.md +9 -6
  61. package/corpus/core/src/templates/workspace-core/AGENTS.md +4 -4
  62. package/corpus/core/src/templates/workspace-root/AGENTS.md +4 -4
  63. package/corpus/core/src/vite/client.ts +42 -2
  64. package/corpus/templates/analytics/.agents/skills/customizing-agent-native/SKILL.md +63 -20
  65. package/corpus/templates/analytics/.agents/skills/self-modifying-code/SKILL.md +9 -6
  66. package/corpus/templates/assets/.agents/skills/customizing-agent-native/SKILL.md +63 -20
  67. package/corpus/templates/assets/.agents/skills/self-modifying-code/SKILL.md +9 -6
  68. package/corpus/templates/brain/.agents/skills/customizing-agent-native/SKILL.md +63 -20
  69. package/corpus/templates/brain/.agents/skills/self-modifying-code/SKILL.md +9 -6
  70. package/corpus/templates/brain/actions/remediate-legacy-capture-audiences.ts +167 -0
  71. package/corpus/templates/brain/app/lib/brain-chat-readiness.ts +6 -0
  72. package/corpus/templates/brain/app/routes/_index.tsx +8 -0
  73. package/corpus/templates/brain/app/routes.ts +1 -0
  74. package/corpus/templates/brain/changelog/2026-07-19-ask-starts-immediately-when-builder-s-managed-ai-connection-.md +6 -0
  75. package/corpus/templates/brain/changelog/2026-07-19-slack-and-granola-backfills-now-process-captures-concurrentl.md +6 -0
  76. package/corpus/templates/brain/changelog/2026-07-19-source-review-settings-now-control-knowledge-approval.md +6 -0
  77. package/corpus/templates/brain/changelog/2026-07-19-the-manage-agent-page-now-opens-correctly-from-brain-navigat.md +6 -0
  78. package/corpus/templates/brain/server/db/schema.ts +3 -0
  79. package/corpus/templates/brain/server/lib/brain.ts +41 -2
  80. package/corpus/templates/brain/server/lib/connectors.ts +379 -113
  81. package/corpus/templates/brain/server/lib/meeting-audience.ts +38 -0
  82. package/corpus/templates/brain/server/plugins/db.ts +10 -0
  83. package/corpus/templates/brain/server/routes/api/_agent-native/brain/ingest.post.ts +6 -4
  84. package/corpus/templates/calendar/.agents/skills/customizing-agent-native/SKILL.md +63 -20
  85. package/corpus/templates/calendar/.agents/skills/self-modifying-code/SKILL.md +9 -6
  86. package/corpus/templates/chat/.agents/skills/agent-native-docs/SKILL.md +15 -13
  87. package/corpus/templates/chat/.agents/skills/customizing-agent-native/SKILL.md +63 -20
  88. package/corpus/templates/chat/.agents/skills/self-modifying-code/SKILL.md +9 -6
  89. package/corpus/templates/chat/AGENTS.md +3 -2
  90. package/corpus/templates/clips/.agents/skills/customizing-agent-native/SKILL.md +63 -20
  91. package/corpus/templates/clips/.agents/skills/self-modifying-code/SKILL.md +9 -6
  92. package/corpus/templates/content/.agents/skills/customizing-agent-native/SKILL.md +63 -20
  93. package/corpus/templates/content/.agents/skills/self-modifying-code/SKILL.md +9 -6
  94. package/corpus/templates/content/AGENTS.md +2 -0
  95. package/corpus/templates/content/actions/_content-database-personal-view.ts +49 -5
  96. package/corpus/templates/content/actions/_content-document-access.ts +21 -0
  97. package/corpus/templates/content/actions/_content-favorites.ts +91 -0
  98. package/corpus/templates/content/actions/_content-files.ts +79 -23
  99. package/corpus/templates/content/actions/_content-sidebar-state.ts +24 -0
  100. package/corpus/templates/content/actions/_content-space-access.ts +15 -17
  101. package/corpus/templates/content/actions/_content-space-catalog-guards.ts +20 -0
  102. package/corpus/templates/content/actions/_content-spaces.ts +364 -167
  103. package/corpus/templates/content/actions/_database-source-utils.ts +8 -0
  104. package/corpus/templates/content/actions/_database-utils.ts +249 -15
  105. package/corpus/templates/content/actions/_delete-content-space.ts +84 -0
  106. package/corpus/templates/content/actions/_files-system-properties.ts +499 -0
  107. package/corpus/templates/content/actions/_local-file-documents.ts +0 -1
  108. package/corpus/templates/content/actions/_property-utils.ts +18 -9
  109. package/corpus/templates/content/actions/bind-content-database-source-field.ts +3 -0
  110. package/corpus/templates/content/actions/configure-document-property.ts +3 -0
  111. package/corpus/templates/content/actions/create-content-database.ts +2 -2
  112. package/corpus/templates/content/actions/create-content-space.ts +34 -0
  113. package/corpus/templates/content/actions/create-document.ts +4 -1
  114. package/corpus/templates/content/actions/delete-content-space.ts +19 -0
  115. package/corpus/templates/content/actions/delete-database-items.ts +28 -1
  116. package/corpus/templates/content/actions/delete-document-property.ts +3 -0
  117. package/corpus/templates/content/actions/delete-document.ts +39 -2
  118. package/corpus/templates/content/actions/duplicate-database-item.ts +6 -0
  119. package/corpus/templates/content/actions/duplicate-database-items.ts +6 -0
  120. package/corpus/templates/content/actions/duplicate-document-property.ts +3 -0
  121. package/corpus/templates/content/actions/get-content-database.ts +11 -2
  122. package/corpus/templates/content/actions/get-content-sidebar-state.ts +23 -0
  123. package/corpus/templates/content/actions/get-document.ts +32 -7
  124. package/corpus/templates/content/actions/import-content-source.ts +33 -1
  125. package/corpus/templates/content/actions/list-content-spaces.ts +34 -2
  126. package/corpus/templates/content/actions/list-documents.ts +39 -7
  127. package/corpus/templates/content/actions/reorder-document-property.ts +9 -0
  128. package/corpus/templates/content/actions/set-document-property.ts +41 -23
  129. package/corpus/templates/content/actions/share-local-file-document.ts +15 -0
  130. package/corpus/templates/content/actions/submit-content-database-form.ts +7 -0
  131. package/corpus/templates/content/actions/sync-local-folder-source.ts +14 -0
  132. package/corpus/templates/content/actions/update-content-database-view.ts +1 -0
  133. package/corpus/templates/content/actions/update-content-sidebar-state.ts +27 -0
  134. package/corpus/templates/content/actions/update-document.ts +158 -66
  135. package/corpus/templates/content/app/components/EmptyState.tsx +4 -57
  136. package/corpus/templates/content/app/components/editor/CommentsSidebar.tsx +8 -1
  137. package/corpus/templates/content/app/components/editor/DocumentEditor.tsx +519 -178
  138. package/corpus/templates/content/app/components/editor/DocumentInfoPanel.tsx +39 -0
  139. package/corpus/templates/content/app/components/editor/DocumentProperties.tsx +1 -1
  140. package/corpus/templates/content/app/components/editor/DocumentToolbar.tsx +266 -7
  141. package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +430 -152
  142. package/corpus/templates/content/app/components/editor/database/sidebar.tsx +467 -66
  143. package/corpus/templates/content/app/components/editor/database/view-config.ts +9 -3
  144. package/corpus/templates/content/app/components/sidebar/DocumentSidebar.tsx +720 -196
  145. package/corpus/templates/content/app/components/sidebar/DocumentTreeItem.tsx +173 -1
  146. package/corpus/templates/content/app/components/sidebar/document-sidebar-sections.ts +1 -1
  147. package/corpus/templates/content/app/components/sidebar/select-content-space.ts +91 -16
  148. package/corpus/templates/content/app/entry.client.tsx +5 -1
  149. package/corpus/templates/content/app/hooks/use-content-database.ts +84 -3
  150. package/corpus/templates/content/app/hooks/use-content-spaces.ts +60 -2
  151. package/corpus/templates/content/app/hooks/use-create-page.ts +30 -0
  152. package/corpus/templates/content/app/hooks/use-documents.ts +270 -19
  153. package/corpus/templates/content/app/i18n-data.ts +243 -20
  154. package/corpus/templates/content/app/lib/local-folder-picker-safety.ts +54 -0
  155. package/corpus/templates/content/app/lib/local-folder-picker-support.ts +18 -0
  156. package/corpus/templates/content/app/routes/_app.favorites.tsx +30 -0
  157. package/corpus/templates/content/app/routes/_app.local-files.tsx +18 -6
  158. package/corpus/templates/content/changelog/2026-07-17-workspace-files-now-stay-in-a-loading-state-until-automatic-.md +6 -0
  159. package/corpus/templates/content/changelog/2026-07-17-workspace-switching-now-opens-the-selected-files-database-pr.md +6 -0
  160. package/corpus/templates/content/changelog/2026-07-17-workspaces-now-expand-above-their-own-files-list-without-dup.md +6 -0
  161. package/corpus/templates/content/changelog/2026-07-18-create-named-workspaces-each-with-its-own-private-files-data.md +6 -0
  162. package/corpus/templates/content/changelog/2026-07-18-favorites-align-with-workspace-and-file-rows-in-the-sidebar.md +6 -0
  163. package/corpus/templates/content/changelog/2026-07-18-local-folder-selection-no-longer-invokes-the-unsafe-native-p.md +6 -0
  164. package/corpus/templates/content/changelog/2026-07-18-made-content-workspaces-independently-expandable-restored-or.md +6 -0
  165. package/corpus/templates/content/changelog/2026-07-18-page-details-now-live-in-a-shared-info-and-comments-rail-dat.md +6 -0
  166. package/corpus/templates/content/changelog/2026-07-18-workspace-database-views-now-control-sidebar-workspace-navig.md +6 -0
  167. package/corpus/templates/content/changelog/2026-07-18-workspace-navigation-now-aligns-favorites-with-workspace-pag.md +6 -0
  168. package/corpus/templates/content/changelog/2026-07-19-favorites-now-open-as-a-table-and-compact-breadcrumbs-make-w.md +6 -0
  169. package/corpus/templates/content/changelog/2026-07-19-files-databases-now-start-unfiltered-sidebar-rows-exactly-fo.md +6 -0
  170. package/corpus/templates/content/changelog/2026-07-19-sidebar-page-trees-now-open-only-when-expanded-remember-thei.md +6 -0
  171. package/corpus/templates/content/changelog/2026-07-19-workspace-file-databases-now-use-the-workspace-name-open-as-.md +6 -0
  172. package/corpus/templates/content/changelog/2026-07-19-workspace-navigation-now-stays-stable-on-hover-uses-folder-i.md +6 -0
  173. package/corpus/templates/content/changelog/2026-07-19-workspace-rows-now-use-folder-icons-and-deleting-a-user-crea.md +6 -0
  174. package/corpus/templates/content/changelog/2026-07-19-workspaces-can-be-created-without-leaving-the-workspaces-dat.md +6 -0
  175. package/corpus/templates/content/e2e/global-setup.ts +3 -2
  176. package/corpus/templates/content/parity/matrix.md +28 -28
  177. package/corpus/templates/content/parity/matrix.ts +8 -2
  178. package/corpus/templates/content/server/db/index.ts +1 -0
  179. package/corpus/templates/content/server/db/schema.ts +10 -0
  180. package/corpus/templates/content/server/lib/documents.ts +12 -7
  181. package/corpus/templates/content/server/plugins/db.ts +17 -0
  182. package/corpus/templates/content/shared/api.ts +9 -0
  183. package/corpus/templates/content/shared/database-form.ts +11 -2
  184. package/corpus/templates/content/vite.config.ts +1 -0
  185. package/corpus/templates/design/.agents/skills/customizing-agent-native/SKILL.md +63 -20
  186. package/corpus/templates/design/.agents/skills/self-modifying-code/SKILL.md +9 -6
  187. package/corpus/templates/dispatch/.agents/skills/customizing-agent-native/SKILL.md +63 -20
  188. package/corpus/templates/dispatch/.agents/skills/self-modifying-code/SKILL.md +9 -6
  189. package/corpus/templates/forms/.agents/skills/customizing-agent-native/SKILL.md +63 -20
  190. package/corpus/templates/forms/.agents/skills/self-modifying-code/SKILL.md +9 -6
  191. package/corpus/templates/macros/.agents/skills/customizing-agent-native/SKILL.md +63 -20
  192. package/corpus/templates/mail/.agents/skills/customizing-agent-native/SKILL.md +63 -20
  193. package/corpus/templates/mail/.agents/skills/self-modifying-code/SKILL.md +9 -6
  194. package/corpus/templates/plan/.agents/skills/customizing-agent-native/SKILL.md +63 -20
  195. package/corpus/templates/plan/.agents/skills/self-modifying-code/SKILL.md +9 -6
  196. package/corpus/templates/slides/.agents/skills/customizing-agent-native/SKILL.md +63 -20
  197. package/corpus/templates/slides/.agents/skills/self-modifying-code/SKILL.md +9 -6
  198. package/corpus/templates/tasks/.agents/skills/agent-native-docs/SKILL.md +15 -13
  199. package/corpus/templates/tasks/.agents/skills/customizing-agent-native/SKILL.md +63 -20
  200. package/corpus/templates/tasks/.agents/skills/self-modifying-code/SKILL.md +9 -6
  201. package/corpus/toolkit/CHANGELOG.md +6 -0
  202. package/corpus/toolkit/agent-native.eject.json +411 -0
  203. package/corpus/toolkit/package.json +5 -2
  204. package/dist/cli/eject.d.ts +21 -0
  205. package/dist/cli/eject.d.ts.map +1 -0
  206. package/dist/cli/eject.js +1145 -0
  207. package/dist/cli/eject.js.map +1 -0
  208. package/dist/cli/index.js +15 -0
  209. package/dist/cli/index.js.map +1 -1
  210. package/dist/client/build-compatibility.d.ts +11 -0
  211. package/dist/client/build-compatibility.d.ts.map +1 -0
  212. package/dist/client/build-compatibility.js +56 -0
  213. package/dist/client/build-compatibility.js.map +1 -0
  214. package/dist/client/index.d.ts +1 -1
  215. package/dist/client/index.d.ts.map +1 -1
  216. package/dist/client/index.js +1 -1
  217. package/dist/client/index.js.map +1 -1
  218. package/dist/client/resources/McpConnectionSuggestion.d.ts +4 -3
  219. package/dist/client/resources/McpConnectionSuggestion.d.ts.map +1 -1
  220. package/dist/client/resources/McpConnectionSuggestion.js +3 -3
  221. package/dist/client/resources/McpConnectionSuggestion.js.map +1 -1
  222. package/dist/client/resources/McpIntegrationDialog.d.ts +4 -3
  223. package/dist/client/resources/McpIntegrationDialog.d.ts.map +1 -1
  224. package/dist/client/resources/McpIntegrationDialog.js +2 -2
  225. package/dist/client/resources/McpIntegrationDialog.js.map +1 -1
  226. package/dist/client/resources/ResourcesPanel.d.ts +4 -1
  227. package/dist/client/resources/ResourcesPanel.d.ts.map +1 -1
  228. package/dist/client/resources/ResourcesPanel.js +9 -7
  229. package/dist/client/resources/ResourcesPanel.js.map +1 -1
  230. package/dist/client/resources/index.d.ts +3 -0
  231. package/dist/client/resources/index.d.ts.map +1 -1
  232. package/dist/client/resources/index.js +3 -0
  233. package/dist/client/resources/index.js.map +1 -1
  234. package/dist/client/resources/mcp-integration-catalog.d.ts +2 -1
  235. package/dist/client/resources/mcp-integration-catalog.d.ts.map +1 -1
  236. package/dist/client/resources/mcp-integration-catalog.js +6 -2
  237. package/dist/client/resources/mcp-integration-catalog.js.map +1 -1
  238. package/dist/client/resources/runtime.d.ts +2 -0
  239. package/dist/client/resources/runtime.d.ts.map +1 -0
  240. package/dist/client/resources/runtime.js +2 -0
  241. package/dist/client/resources/runtime.js.map +1 -0
  242. package/dist/client/route-chunk-recovery/index.d.ts +1 -0
  243. package/dist/client/route-chunk-recovery/index.d.ts.map +1 -1
  244. package/dist/client/route-chunk-recovery/index.js +1 -0
  245. package/dist/client/route-chunk-recovery/index.js.map +1 -1
  246. package/dist/client/setup-connections/catalog.d.ts +2 -0
  247. package/dist/client/setup-connections/catalog.d.ts.map +1 -0
  248. package/dist/client/setup-connections/catalog.js +7 -0
  249. package/dist/client/setup-connections/catalog.js.map +1 -0
  250. package/dist/client/setup-connections/index.d.ts +1 -0
  251. package/dist/client/setup-connections/index.d.ts.map +1 -1
  252. package/dist/client/setup-connections/index.js +1 -0
  253. package/dist/client/setup-connections/index.js.map +1 -1
  254. package/dist/client/setup-connections/runtime.d.ts +2 -0
  255. package/dist/client/setup-connections/runtime.d.ts.map +1 -0
  256. package/dist/client/setup-connections/runtime.js +2 -0
  257. package/dist/client/setup-connections/runtime.js.map +1 -0
  258. package/dist/client/use-action.d.ts.map +1 -1
  259. package/dist/client/use-action.js +19 -1
  260. package/dist/client/use-action.js.map +1 -1
  261. package/dist/collab/awareness.d.ts +2 -2
  262. package/dist/collab/awareness.d.ts.map +1 -1
  263. package/dist/collab/struct-routes.d.ts +1 -1
  264. package/dist/connections/catalog.d.ts +5 -3
  265. package/dist/connections/catalog.d.ts.map +1 -1
  266. package/dist/connections/catalog.js +16 -8
  267. package/dist/connections/catalog.js.map +1 -1
  268. package/dist/connections/index.d.ts +1 -1
  269. package/dist/connections/index.d.ts.map +1 -1
  270. package/dist/connections/index.js +1 -1
  271. package/dist/connections/index.js.map +1 -1
  272. package/dist/connections/runtime.d.ts +2 -0
  273. package/dist/connections/runtime.d.ts.map +1 -0
  274. package/dist/connections/runtime.js +2 -0
  275. package/dist/connections/runtime.js.map +1 -0
  276. package/dist/deploy/build.js +2 -1
  277. package/dist/deploy/build.js.map +1 -1
  278. package/dist/eject/provider-api-definitions.d.ts +34 -0
  279. package/dist/eject/provider-api-definitions.d.ts.map +1 -0
  280. package/dist/eject/provider-api-definitions.js +88 -0
  281. package/dist/eject/provider-api-definitions.js.map +1 -0
  282. package/dist/eject/remote-mcp-presets.d.ts +17 -0
  283. package/dist/eject/remote-mcp-presets.d.ts.map +1 -0
  284. package/dist/eject/remote-mcp-presets.js +81 -0
  285. package/dist/eject/remote-mcp-presets.js.map +1 -0
  286. package/dist/eject/setup-readiness-ui.d.ts +7 -0
  287. package/dist/eject/setup-readiness-ui.d.ts.map +1 -0
  288. package/dist/eject/setup-readiness-ui.js +13 -0
  289. package/dist/eject/setup-readiness-ui.js.map +1 -0
  290. package/dist/eject/workspace-connections.d.ts +11 -0
  291. package/dist/eject/workspace-connections.d.ts.map +1 -0
  292. package/dist/eject/workspace-connections.js +47 -0
  293. package/dist/eject/workspace-connections.js.map +1 -0
  294. package/dist/file-upload/actions/upload-image.d.ts +1 -1
  295. package/dist/index.d.ts +1 -1
  296. package/dist/index.d.ts.map +1 -1
  297. package/dist/index.js +1 -1
  298. package/dist/index.js.map +1 -1
  299. package/dist/integrations/adapter-overrides.d.ts +3 -0
  300. package/dist/integrations/adapter-overrides.d.ts.map +1 -0
  301. package/dist/integrations/adapter-overrides.js +7 -0
  302. package/dist/integrations/adapter-overrides.js.map +1 -0
  303. package/dist/integrations/eject/messaging-adapters.d.ts +12 -0
  304. package/dist/integrations/eject/messaging-adapters.d.ts.map +1 -0
  305. package/dist/integrations/eject/messaging-adapters.js +49 -0
  306. package/dist/integrations/eject/messaging-adapters.js.map +1 -0
  307. package/dist/integrations/index.d.ts +3 -2
  308. package/dist/integrations/index.d.ts.map +1 -1
  309. package/dist/integrations/index.js +2 -1
  310. package/dist/integrations/index.js.map +1 -1
  311. package/dist/integrations/plugin.d.ts +32 -1
  312. package/dist/integrations/plugin.d.ts.map +1 -1
  313. package/dist/integrations/plugin.js +19 -12
  314. package/dist/integrations/plugin.js.map +1 -1
  315. package/dist/integrations/runtime.d.ts +2 -0
  316. package/dist/integrations/runtime.d.ts.map +1 -0
  317. package/dist/integrations/runtime.js +2 -0
  318. package/dist/integrations/runtime.js.map +1 -0
  319. package/dist/integrations/types.d.ts +3 -1
  320. package/dist/integrations/types.d.ts.map +1 -1
  321. package/dist/integrations/types.js.map +1 -1
  322. package/dist/notifications/routes.d.ts +2 -2
  323. package/dist/observability/routes.d.ts +5 -5
  324. package/dist/org/context.d.ts +4 -2
  325. package/dist/org/context.d.ts.map +1 -1
  326. package/dist/org/context.js +44 -22
  327. package/dist/org/context.js.map +1 -1
  328. package/dist/package-lifecycle/eject-manifest.d.ts +43 -0
  329. package/dist/package-lifecycle/eject-manifest.d.ts.map +1 -0
  330. package/dist/package-lifecycle/eject-manifest.js +219 -0
  331. package/dist/package-lifecycle/eject-manifest.js.map +1 -0
  332. package/dist/package-lifecycle/index.d.ts +1 -0
  333. package/dist/package-lifecycle/index.d.ts.map +1 -1
  334. package/dist/package-lifecycle/index.js +1 -0
  335. package/dist/package-lifecycle/index.js.map +1 -1
  336. package/dist/provider-api/actions/custom-provider-registration.d.ts +11 -11
  337. package/dist/provider-api/actions/provider-api.d.ts +11 -11
  338. package/dist/provider-api/index.d.ts +6 -2
  339. package/dist/provider-api/index.d.ts.map +1 -1
  340. package/dist/provider-api/index.js +25 -12
  341. package/dist/provider-api/index.js.map +1 -1
  342. package/dist/provider-api/runtime.d.ts +2 -0
  343. package/dist/provider-api/runtime.d.ts.map +1 -0
  344. package/dist/provider-api/runtime.js +2 -0
  345. package/dist/provider-api/runtime.js.map +1 -0
  346. package/dist/resources/handlers.d.ts +1 -1
  347. package/dist/server/action-routes.d.ts.map +1 -1
  348. package/dist/server/action-routes.js +32 -2
  349. package/dist/server/action-routes.js.map +1 -1
  350. package/dist/server/auth-plugin.d.ts.map +1 -1
  351. package/dist/server/auth-plugin.js +9 -5
  352. package/dist/server/auth-plugin.js.map +1 -1
  353. package/dist/server/better-auth-instance.d.ts.map +1 -1
  354. package/dist/server/better-auth-instance.js +4 -1
  355. package/dist/server/better-auth-instance.js.map +1 -1
  356. package/dist/setup-connections/index.d.ts +1 -1
  357. package/dist/setup-connections/index.d.ts.map +1 -1
  358. package/dist/setup-connections/index.js +1 -1
  359. package/dist/setup-connections/index.js.map +1 -1
  360. package/dist/shared/mcp-embed-headers.d.ts +1 -1
  361. package/dist/shared/mcp-embed-headers.d.ts.map +1 -1
  362. package/dist/shared/mcp-embed-headers.js +1 -1
  363. package/dist/shared/mcp-embed-headers.js.map +1 -1
  364. package/dist/shared/merge-by-id.d.ts +5 -0
  365. package/dist/shared/merge-by-id.d.ts.map +1 -0
  366. package/dist/shared/merge-by-id.js +7 -0
  367. package/dist/shared/merge-by-id.js.map +1 -0
  368. package/dist/templates/chat/.agents/skills/agent-native-docs/SKILL.md +15 -13
  369. package/dist/templates/chat/.agents/skills/customizing-agent-native/SKILL.md +63 -20
  370. package/dist/templates/chat/.agents/skills/self-modifying-code/SKILL.md +9 -6
  371. package/dist/templates/chat/AGENTS.md +3 -2
  372. package/dist/templates/default/.agents/skills/agent-native-docs/SKILL.md +15 -13
  373. package/dist/templates/default/.agents/skills/customizing-agent-native/SKILL.md +63 -20
  374. package/dist/templates/default/.agents/skills/self-modifying-code/SKILL.md +9 -6
  375. package/dist/templates/default/AGENTS.md +6 -5
  376. package/dist/templates/headless/.agents/skills/agent-native-docs/SKILL.md +15 -13
  377. package/dist/templates/headless/.agents/skills/customizing-agent-native/SKILL.md +63 -20
  378. package/dist/templates/headless/AGENTS.md +5 -4
  379. package/dist/templates/workspace-core/.agents/skills/agent-native-docs/SKILL.md +15 -13
  380. package/dist/templates/workspace-core/.agents/skills/agent-native-toolkit/SKILL.md +3 -2
  381. package/dist/templates/workspace-core/.agents/skills/customizing-agent-native/SKILL.md +63 -20
  382. package/dist/templates/workspace-core/.agents/skills/self-modifying-code/SKILL.md +9 -6
  383. package/dist/templates/workspace-core/AGENTS.md +4 -4
  384. package/dist/templates/workspace-root/AGENTS.md +4 -4
  385. package/dist/vite/client.d.ts +6 -0
  386. package/dist/vite/client.d.ts.map +1 -1
  387. package/dist/vite/client.js +30 -2
  388. package/dist/vite/client.js.map +1 -1
  389. package/docs/content/agent-native-toolkit.mdx +38 -0
  390. package/docs/content/package-lifecycle.mdx +54 -4
  391. package/package.json +15 -2
  392. package/src/a2a/agent-card.ts +83 -0
  393. package/src/a2a/artifact-response.ts +1648 -0
  394. package/src/a2a/auth-policy.ts +66 -0
  395. package/src/a2a/caller-auth.ts +102 -0
  396. package/src/a2a/client.ts +876 -0
  397. package/src/a2a/correlation.ts +50 -0
  398. package/src/a2a/handlers.ts +1285 -0
  399. package/src/a2a/index.ts +50 -0
  400. package/src/a2a/invoke.ts +348 -0
  401. package/src/a2a/response-text.ts +53 -0
  402. package/src/a2a/server.ts +586 -0
  403. package/src/a2a/task-store.ts +917 -0
  404. package/src/a2a/types.ts +221 -0
  405. package/src/a2a-claims.ts +52 -0
  406. package/src/action-change-marker.ts +74 -0
  407. package/src/action-ui.ts +38 -0
  408. package/src/action.ts +1406 -0
  409. package/src/adapters/cli/index.ts +6 -0
  410. package/src/adapters/cli/registry.ts +73 -0
  411. package/src/adapters/cli/shell-adapter.ts +92 -0
  412. package/src/adapters/cli/types.ts +27 -0
  413. package/src/agent/action-continuation-guidance.ts +38 -0
  414. package/src/agent/app-model-defaults.ts +201 -0
  415. package/src/agent/context-xray/actions/context-evict.ts +56 -0
  416. package/src/agent/context-xray/actions/context-manifest-get.ts +75 -0
  417. package/src/agent/context-xray/actions/context-pin.ts +56 -0
  418. package/src/agent/context-xray/actions/context-preview-get.ts +134 -0
  419. package/src/agent/context-xray/actions/context-report.ts +75 -0
  420. package/src/agent/context-xray/actions/context-restore.ts +51 -0
  421. package/src/agent/context-xray/actions/errors.ts +40 -0
  422. package/src/agent/context-xray/apply-directives.ts +137 -0
  423. package/src/agent/context-xray/directives-store.ts +178 -0
  424. package/src/agent/context-xray/identity.ts +37 -0
  425. package/src/agent/context-xray/manifest.ts +284 -0
  426. package/src/agent/context-xray/migrations.ts +32 -0
  427. package/src/agent/context-xray/plugin.ts +23 -0
  428. package/src/agent/context-xray/schema.ts +17 -0
  429. package/src/agent/context-xray/segments.ts +194 -0
  430. package/src/agent/context-xray/tokenize.ts +93 -0
  431. package/src/agent/default-model.ts +5 -0
  432. package/src/agent/durable-background.ts +547 -0
  433. package/src/agent/engine/ai-sdk-engine.ts +565 -0
  434. package/src/agent/engine/anthropic-engine.ts +320 -0
  435. package/src/agent/engine/builder-engine.ts +1110 -0
  436. package/src/agent/engine/builder-gateway-headers.ts +44 -0
  437. package/src/agent/engine/builtin.ts +122 -0
  438. package/src/agent/engine/context-directives-transform.ts +73 -0
  439. package/src/agent/engine/continuation-dispatch-retry.ts +168 -0
  440. package/src/agent/engine/credential-errors.ts +69 -0
  441. package/src/agent/engine/index.ts +54 -0
  442. package/src/agent/engine/openai-compatible-endpoint.ts +25 -0
  443. package/src/agent/engine/output-tokens.ts +181 -0
  444. package/src/agent/engine/provider-env-vars.ts +69 -0
  445. package/src/agent/engine/registry.ts +853 -0
  446. package/src/agent/engine/tool-call-journal-seed.ts +85 -0
  447. package/src/agent/engine/translate-ai-sdk.ts +407 -0
  448. package/src/agent/engine/translate-anthropic.ts +611 -0
  449. package/src/agent/engine/types.ts +310 -0
  450. package/src/agent/harness/acp-adapter.ts +893 -0
  451. package/src/agent/harness/acp-builtin.ts +70 -0
  452. package/src/agent/harness/ai-sdk-adapter.ts +509 -0
  453. package/src/agent/harness/background.ts +365 -0
  454. package/src/agent/harness/builtin.ts +50 -0
  455. package/src/agent/harness/index.ts +86 -0
  456. package/src/agent/harness/lifecycle.ts +498 -0
  457. package/src/agent/harness/registry.ts +97 -0
  458. package/src/agent/harness/runner.ts +182 -0
  459. package/src/agent/harness/store.ts +466 -0
  460. package/src/agent/harness/translate.ts +130 -0
  461. package/src/agent/harness/types.ts +129 -0
  462. package/src/agent/index.ts +32 -0
  463. package/src/agent/loop-settings.ts +186 -0
  464. package/src/agent/model-config.ts +363 -0
  465. package/src/agent/observational-memory/compactor.ts +78 -0
  466. package/src/agent/observational-memory/config.ts +78 -0
  467. package/src/agent/observational-memory/index.ts +79 -0
  468. package/src/agent/observational-memory/internal-run.ts +89 -0
  469. package/src/agent/observational-memory/message-text.ts +49 -0
  470. package/src/agent/observational-memory/migrations.ts +44 -0
  471. package/src/agent/observational-memory/observer.ts +128 -0
  472. package/src/agent/observational-memory/plugin.ts +35 -0
  473. package/src/agent/observational-memory/prompts.ts +53 -0
  474. package/src/agent/observational-memory/read.ts +131 -0
  475. package/src/agent/observational-memory/reflector.ts +119 -0
  476. package/src/agent/observational-memory/schema.ts +48 -0
  477. package/src/agent/observational-memory/store.ts +275 -0
  478. package/src/agent/observational-memory/types.ts +65 -0
  479. package/src/agent/processors.ts +209 -0
  480. package/src/agent/production-agent.ts +8254 -0
  481. package/src/agent/run-loop-with-resume.ts +394 -0
  482. package/src/agent/run-manager.ts +1825 -0
  483. package/src/agent/run-ownership.ts +75 -0
  484. package/src/agent/run-store.ts +2601 -0
  485. package/src/agent/runtime-context.ts +169 -0
  486. package/src/agent/thread-data-builder.ts +1584 -0
  487. package/src/agent/tool-call-journal.ts +368 -0
  488. package/src/agent/tool-error-redaction.ts +52 -0
  489. package/src/agent/tool-result-images.ts +178 -0
  490. package/src/agent/tool-search.ts +376 -0
  491. package/src/agent/types.ts +372 -0
  492. package/src/agent-native/index.ts +198 -0
  493. package/src/agent-web/config.ts +253 -0
  494. package/src/agent-web/generator.ts +382 -0
  495. package/src/agent-web/index.ts +36 -0
  496. package/src/appearance/actions/change-appearance.ts +35 -0
  497. package/src/application-state/emitter.ts +81 -0
  498. package/src/application-state/handlers.ts +121 -0
  499. package/src/application-state/index.ts +44 -0
  500. package/src/application-state/script-helpers.ts +155 -0
  501. package/src/application-state/store.ts +274 -0
  502. package/src/assets/branding/agent-native.icon/Assets/background.svg +3 -0
  503. package/src/assets/branding/agent-native.icon/Assets/foreground.svg +12 -0
  504. package/src/assets/branding/agent-native.icon/icon.json +49 -0
  505. package/src/assets/branding/favicon.png +0 -0
  506. package/src/assets/branding/favicon.svg +1 -0
  507. package/src/assets/branding/icon-on-dark.svg +10 -0
  508. package/src/assets/branding/icon-on-light.svg +10 -0
  509. package/src/assets/branding/mac-app-icon.svg +1 -0
  510. package/src/assets/branding/slack-bot/agent-native-1024.png +0 -0
  511. package/src/assets/branding/slack-bot/agent-native-512.png +0 -0
  512. package/src/assets/branding/tray-icon.svg +6 -0
  513. package/src/assets/branding/wordmark-on-dark.svg +21 -0
  514. package/src/assets/branding/wordmark-on-light.svg +21 -0
  515. package/src/assets/fonts/LICENSE_LIBERATION +96 -0
  516. package/src/assets/fonts/LICENSE_NOTO_NASKH_ARABIC +93 -0
  517. package/src/assets/fonts/LiberationSans-Bold.ttf +0 -0
  518. package/src/assets/fonts/LiberationSans-Regular.ttf +0 -0
  519. package/src/assets/fonts/NotoNaskhArabic-Variable.ttf +0 -0
  520. package/src/audit/actions/export-audit-events.ts +162 -0
  521. package/src/audit/actions/get-audit-event.ts +24 -0
  522. package/src/audit/actions/list-audit-events.ts +66 -0
  523. package/src/audit/cleanup-job.ts +100 -0
  524. package/src/audit/config.ts +90 -0
  525. package/src/audit/index.ts +43 -0
  526. package/src/audit/record.ts +180 -0
  527. package/src/audit/redact.ts +109 -0
  528. package/src/audit/store.ts +346 -0
  529. package/src/audit/types.ts +141 -0
  530. package/src/automation/index.ts +802 -0
  531. package/src/brand-kit/brand-signals.ts +124 -0
  532. package/src/brand-kit/fig/index.ts +64 -0
  533. package/src/brand-kit/index.ts +72 -0
  534. package/src/brand-kit/types.ts +113 -0
  535. package/src/browser-sessions/actions.ts +302 -0
  536. package/src/browser-sessions/routes.ts +234 -0
  537. package/src/browser-sessions/store.ts +679 -0
  538. package/src/browser-sessions/types.ts +84 -0
  539. package/src/changelog/parse.ts +328 -0
  540. package/src/chat-threads/emitter.ts +22 -0
  541. package/src/chat-threads/schema.ts +43 -0
  542. package/src/chat-threads/store.ts +1311 -0
  543. package/src/checkpoints/index.ts +15 -0
  544. package/src/checkpoints/service.ts +142 -0
  545. package/src/checkpoints/store.ts +153 -0
  546. package/src/cli/add.ts +425 -0
  547. package/src/cli/agent.ts +424 -0
  548. package/src/cli/agents.ts +122 -0
  549. package/src/cli/app-skill.ts +1455 -0
  550. package/src/cli/atomic-json-file.ts +324 -0
  551. package/src/cli/audit-agent-web.ts +324 -0
  552. package/src/cli/changelog.ts +199 -0
  553. package/src/cli/claude-code-participant.ts +515 -0
  554. package/src/cli/code-agent-commands.ts +270 -0
  555. package/src/cli/code-agent-connector.ts +1109 -0
  556. package/src/cli/code-agent-executor.ts +1970 -0
  557. package/src/cli/code-agent-output-smoother.ts +140 -0
  558. package/src/cli/code-agent-runs.concurrent-worker.ts +33 -0
  559. package/src/cli/code-agent-runs.ts +548 -0
  560. package/src/cli/code.ts +1805 -0
  561. package/src/cli/codex-cli-participant.ts +496 -0
  562. package/src/cli/connect.ts +2714 -0
  563. package/src/cli/content-local.ts +442 -0
  564. package/src/cli/context-xray-local.ts +2193 -0
  565. package/src/cli/create-workspace.ts +30 -0
  566. package/src/cli/create.ts +2247 -0
  567. package/src/cli/deploy-build.ts +50 -0
  568. package/src/cli/design-connect.ts +3187 -0
  569. package/src/cli/doctor.ts +472 -0
  570. package/src/cli/eject.ts +1594 -0
  571. package/src/cli/eval.ts +134 -0
  572. package/src/cli/gateway-helpers.ts +70 -0
  573. package/src/cli/index.ts +1217 -0
  574. package/src/cli/info.ts +105 -0
  575. package/src/cli/invoke.ts +288 -0
  576. package/src/cli/mcp-config-writers.ts +857 -0
  577. package/src/cli/mcp.ts +594 -0
  578. package/src/cli/migrate.ts +1813 -0
  579. package/src/cli/migration-codemod.ts +646 -0
  580. package/src/cli/multi-frontier-runs.ts +1952 -0
  581. package/src/cli/package-lifecycle.ts +823 -0
  582. package/src/cli/plan-blocks.ts +143 -0
  583. package/src/cli/plan-local.ts +3189 -0
  584. package/src/cli/plan-publish-store.ts +138 -0
  585. package/src/cli/pr-visual-recap-workflow.ts +2 -0
  586. package/src/cli/recap.ts +5 -0
  587. package/src/cli/setup-agents.ts +120 -0
  588. package/src/cli/skills-content/assets-skill.ts +83 -0
  589. package/src/cli/skills-content/canvas.ts +153 -0
  590. package/src/cli/skills-content/connection.ts +52 -0
  591. package/src/cli/skills-content/content-skill.ts +120 -0
  592. package/src/cli/skills-content/design-exploration-skill.ts +118 -0
  593. package/src/cli/skills-content/design-visual-edit-skill.ts +228 -0
  594. package/src/cli/skills-content/document-quality.ts +196 -0
  595. package/src/cli/skills-content/exemplar.ts +69 -0
  596. package/src/cli/skills-content/help.ts +77 -0
  597. package/src/cli/skills-content/index.ts +22 -0
  598. package/src/cli/skills-content/local-files.ts +100 -0
  599. package/src/cli/skills-content/plan-setup-auth.ts +80 -0
  600. package/src/cli/skills-content/visual-plan-skill.ts +493 -0
  601. package/src/cli/skills-content/visual-recap-skill.ts +553 -0
  602. package/src/cli/skills-content/visualize-repo-skill.ts +93 -0
  603. package/src/cli/skills-content/wireframe.ts +349 -0
  604. package/src/cli/skills.ts +4340 -0
  605. package/src/cli/sync-builder-starter-manifest.ts +631 -0
  606. package/src/cli/telemetry.ts +136 -0
  607. package/src/cli/templates-meta.ts +269 -0
  608. package/src/cli/upgrade.ts +956 -0
  609. package/src/cli/visualize-repo.ts +715 -0
  610. package/src/cli/workspace-dev.ts +1486 -0
  611. package/src/cli/workspacify.ts +258 -0
  612. package/src/client/AgentAskPopover.tsx +89 -0
  613. package/src/client/AgentChatHome.tsx +66 -0
  614. package/src/client/AgentNative.tsx +161 -0
  615. package/src/client/AgentNativeEmbedded.tsx +288 -0
  616. package/src/client/AgentNativeFrame.tsx +151 -0
  617. package/src/client/AgentPanel.tsx +3437 -0
  618. package/src/client/AgentTaskCard.tsx +273 -0
  619. package/src/client/AppearancePicker.tsx +79 -0
  620. package/src/client/AssistantChat.tsx +5793 -0
  621. package/src/client/ClientOnly.tsx +22 -0
  622. package/src/client/CommandMenu.tsx +726 -0
  623. package/src/client/ConnectBuilderCard.tsx +390 -0
  624. package/src/client/DefaultSpinner.tsx +47 -0
  625. package/src/client/ErrorBoundary.tsx +359 -0
  626. package/src/client/ErrorReportActions.tsx +69 -0
  627. package/src/client/FeedbackButton.tsx +553 -0
  628. package/src/client/HighlightedCodeBlock.tsx +173 -0
  629. package/src/client/IframeEmbed.tsx +151 -0
  630. package/src/client/KeepTabOpenNotice.tsx +181 -0
  631. package/src/client/MultiTabAssistantChat.tsx +2576 -0
  632. package/src/client/NewWorkspaceAppFlow.tsx +525 -0
  633. package/src/client/PoweredByBadge.tsx +284 -0
  634. package/src/client/RunStuckBanner.tsx +431 -0
  635. package/src/client/StarfieldBackground.tsx +466 -0
  636. package/src/client/Turnstile.tsx +125 -0
  637. package/src/client/active-run-state.ts +139 -0
  638. package/src/client/agent-chat/index.ts +279 -0
  639. package/src/client/agent-chat-adapter.ts +3692 -0
  640. package/src/client/agent-chat.ts +1233 -0
  641. package/src/client/agent-engine-key.ts +122 -0
  642. package/src/client/agent-page/AgentContextTab.tsx +487 -0
  643. package/src/client/agent-page/AgentEmptyState.tsx +44 -0
  644. package/src/client/agent-page/AgentJobsTab.tsx +441 -0
  645. package/src/client/agent-page/AgentTabFrame.tsx +39 -0
  646. package/src/client/agent-page/AgentTabsPage.tsx +1096 -0
  647. package/src/client/agent-page/types.ts +18 -0
  648. package/src/client/agent-page/use-jobs.ts +150 -0
  649. package/src/client/agent-sidebar-state.ts +166 -0
  650. package/src/client/analytics/index.ts +40 -0
  651. package/src/client/analytics-session.ts +70 -0
  652. package/src/client/analytics.ts +1757 -0
  653. package/src/client/api-path.ts +124 -0
  654. package/src/client/app-providers.tsx +230 -0
  655. package/src/client/appearance.ts +130 -0
  656. package/src/client/application-state.ts +154 -0
  657. package/src/client/assistant-ui-recovery.tsx +205 -0
  658. package/src/client/automation/index.ts +4 -0
  659. package/src/client/automation.ts +22 -0
  660. package/src/client/blocks/AiEditableField.tsx +30 -0
  661. package/src/client/blocks/BlockView.tsx +145 -0
  662. package/src/client/blocks/RegistryBlockDataProvider.tsx +121 -0
  663. package/src/client/blocks/SchemaBlockEditor.tsx +457 -0
  664. package/src/client/blocks/agent.ts +95 -0
  665. package/src/client/blocks/code-block-direction.ts +21 -0
  666. package/src/client/blocks/index.ts +308 -0
  667. package/src/client/blocks/library/AnnotatedCodeBlock.tsx +710 -0
  668. package/src/client/blocks/library/ApiEndpointBlock.tsx +1120 -0
  669. package/src/client/blocks/library/DataModelBlock.tsx +775 -0
  670. package/src/client/blocks/library/DiffBlock.tsx +1854 -0
  671. package/src/client/blocks/library/FileTreeBlock.tsx +868 -0
  672. package/src/client/blocks/library/HighlightedCode.tsx +260 -0
  673. package/src/client/blocks/library/JsonExplorerBlock.tsx +608 -0
  674. package/src/client/blocks/library/MermaidBlock.tsx +362 -0
  675. package/src/client/blocks/library/OpenApiSpecBlock.tsx +998 -0
  676. package/src/client/blocks/library/annotated-code.config.ts +101 -0
  677. package/src/client/blocks/library/annotation-rail.tsx +1020 -0
  678. package/src/client/blocks/library/api-endpoint.config.ts +207 -0
  679. package/src/client/blocks/library/block-copy.ts +399 -0
  680. package/src/client/blocks/library/callout.config.ts +57 -0
  681. package/src/client/blocks/library/callout.tsx +149 -0
  682. package/src/client/blocks/library/checklist.config.ts +56 -0
  683. package/src/client/blocks/library/checklist.tsx +208 -0
  684. package/src/client/blocks/library/code-filename-label.tsx +59 -0
  685. package/src/client/blocks/library/code-highlight.tsx +178 -0
  686. package/src/client/blocks/library/code-tabs.config.ts +70 -0
  687. package/src/client/blocks/library/code-tabs.tsx +606 -0
  688. package/src/client/blocks/library/code.config.ts +69 -0
  689. package/src/client/blocks/library/code.tsx +440 -0
  690. package/src/client/blocks/library/columns.config.ts +85 -0
  691. package/src/client/blocks/library/columns.tsx +334 -0
  692. package/src/client/blocks/library/data-model.config.ts +152 -0
  693. package/src/client/blocks/library/dev-doc-ui.tsx +191 -0
  694. package/src/client/blocks/library/diagram.config.ts +222 -0
  695. package/src/client/blocks/library/diagram.tsx +958 -0
  696. package/src/client/blocks/library/diff.config.ts +115 -0
  697. package/src/client/blocks/library/file-tree.config.ts +92 -0
  698. package/src/client/blocks/library/html.config.ts +79 -0
  699. package/src/client/blocks/library/html.tsx +258 -0
  700. package/src/client/blocks/library/json-explorer.config.ts +74 -0
  701. package/src/client/blocks/library/mermaid.config.ts +49 -0
  702. package/src/client/blocks/library/narrow-container.tsx +37 -0
  703. package/src/client/blocks/library/openapi-spec.config.ts +66 -0
  704. package/src/client/blocks/library/question-form.config.ts +127 -0
  705. package/src/client/blocks/library/question-form.tsx +944 -0
  706. package/src/client/blocks/library/sanitize-html.ts +343 -0
  707. package/src/client/blocks/library/server-specs.ts +338 -0
  708. package/src/client/blocks/library/specs.tsx +308 -0
  709. package/src/client/blocks/library/table.config.ts +61 -0
  710. package/src/client/blocks/library/table.tsx +449 -0
  711. package/src/client/blocks/library/tabs.config.ts +96 -0
  712. package/src/client/blocks/library/tabs.tsx +665 -0
  713. package/src/client/blocks/library/wireframe-icons.ts +210 -0
  714. package/src/client/blocks/library/wireframe-kit.tsx +1850 -0
  715. package/src/client/blocks/library/wireframe.config.ts +539 -0
  716. package/src/client/blocks/library/wireframe.tsx +535 -0
  717. package/src/client/blocks/mdx.ts +364 -0
  718. package/src/client/blocks/provider.tsx +51 -0
  719. package/src/client/blocks/registry.ts +83 -0
  720. package/src/client/blocks/schema-form/introspect.ts +201 -0
  721. package/src/client/blocks/server.ts +170 -0
  722. package/src/client/blocks/types.ts +415 -0
  723. package/src/client/browser-session-bridge.ts +548 -0
  724. package/src/client/browser-tab-id.ts +36 -0
  725. package/src/client/build-compatibility.ts +77 -0
  726. package/src/client/builder-frame.ts +194 -0
  727. package/src/client/builder-mark.tsx +21 -0
  728. package/src/client/changelog/Changelog.tsx +333 -0
  729. package/src/client/changelog/index.ts +1 -0
  730. package/src/client/chat/ChatHistoryList.tsx +7 -0
  731. package/src/client/chat/attachment-adapters.ts +426 -0
  732. package/src/client/chat/connectors.ts +1579 -0
  733. package/src/client/chat/index.ts +160 -0
  734. package/src/client/chat/markdown-renderer.tsx +693 -0
  735. package/src/client/chat/message-components.tsx +1326 -0
  736. package/src/client/chat/repo-helpers.ts +281 -0
  737. package/src/client/chat/run-recovery.tsx +994 -0
  738. package/src/client/chat/runtime.ts +1810 -0
  739. package/src/client/chat/tool-call-display.tsx +1270 -0
  740. package/src/client/chat/tool-render-registry.tsx +144 -0
  741. package/src/client/chat/use-agent-chat-lifecycle-tracking.ts +74 -0
  742. package/src/client/chat/use-reconnect-reader-owner.ts +28 -0
  743. package/src/client/chat/widgets/DataChartRenderer.tsx +242 -0
  744. package/src/client/chat/widgets/DataChartWidget.tsx +51 -0
  745. package/src/client/chat/widgets/DataInsightsWidget.tsx +59 -0
  746. package/src/client/chat/widgets/DataTableWidget.tsx +240 -0
  747. package/src/client/chat/widgets/InlineExtensionWidget.tsx +60 -0
  748. package/src/client/chat/widgets/builtin-tool-renderers.tsx +213 -0
  749. package/src/client/chat/widgets/data-widget-types.ts +1 -0
  750. package/src/client/chat/widgets/inline-extension-result.ts +86 -0
  751. package/src/client/chat-model-groups.ts +171 -0
  752. package/src/client/chat-thread-url.ts +11 -0
  753. package/src/client/chat-view-transition.ts +213 -0
  754. package/src/client/client-action.ts +21 -0
  755. package/src/client/client-bootstrap.ts +7 -0
  756. package/src/client/client-surface.ts +41 -0
  757. package/src/client/clipboard.ts +89 -0
  758. package/src/client/code-agent-chat-adapter.ts +537 -0
  759. package/src/client/collab/index.ts +60 -0
  760. package/src/client/command-navigation/index.ts +15 -0
  761. package/src/client/comments-review/index.ts +1 -0
  762. package/src/client/components/AgentPresenceChip.tsx +4 -0
  763. package/src/client/components/ApiKeySettings.tsx +309 -0
  764. package/src/client/components/CodeAgentIndicator.tsx +59 -0
  765. package/src/client/components/CodeRequiredDialog.tsx +470 -0
  766. package/src/client/components/LiveCursorOverlay.tsx +5 -0
  767. package/src/client/components/MissingKeyCard.tsx +61 -0
  768. package/src/client/components/PresenceBar.tsx +4 -0
  769. package/src/client/components/RecentEditHighlights.tsx +4 -0
  770. package/src/client/components/RemoteSelectionRings.tsx +5 -0
  771. package/src/client/components/icons/AgentNativeIcon.tsx +46 -0
  772. package/src/client/components/ui/dialog.tsx +1 -0
  773. package/src/client/components/ui/dropdown-menu.tsx +1 -0
  774. package/src/client/components/ui/hover-card.tsx +1 -0
  775. package/src/client/components/ui/message-scroller.tsx +131 -0
  776. package/src/client/components/ui/popover.tsx +1 -0
  777. package/src/client/components/ui/sheet.tsx +1 -0
  778. package/src/client/components/ui/tooltip.tsx +1 -0
  779. package/src/client/composer/index.ts +11 -0
  780. package/src/client/composer/runtime-adapters.tsx +123 -0
  781. package/src/client/composer/use-mention-search.ts +7 -0
  782. package/src/client/composer/wired-components.tsx +53 -0
  783. package/src/client/context-xray/ContextMeter.tsx +124 -0
  784. package/src/client/context-xray/ContextSegmentRow.tsx +27 -0
  785. package/src/client/context-xray/ContextTreemap.tsx +24 -0
  786. package/src/client/context-xray/ContextXRayPanel.tsx +59 -0
  787. package/src/client/context-xray/SegmentProvenancePopover.tsx +18 -0
  788. package/src/client/context-xray/format.ts +27 -0
  789. package/src/client/conversation/AgentConversation.tsx +555 -0
  790. package/src/client/conversation/code-agent-transcript.ts +283 -0
  791. package/src/client/conversation/index.ts +22 -0
  792. package/src/client/conversation/types.ts +85 -0
  793. package/src/client/conversation/use-near-bottom-autoscroll.ts +278 -0
  794. package/src/client/create-query-client.ts +86 -0
  795. package/src/client/db-admin/DataGrid.tsx +551 -0
  796. package/src/client/db-admin/DbAdminPage.tsx +218 -0
  797. package/src/client/db-admin/DevDatabaseLink.tsx +16 -0
  798. package/src/client/db-admin/EditableCell.tsx +451 -0
  799. package/src/client/db-admin/FilterBar.tsx +203 -0
  800. package/src/client/db-admin/ResultsGrid.tsx +114 -0
  801. package/src/client/db-admin/RowSidePanel.tsx +337 -0
  802. package/src/client/db-admin/SqlEditor.tsx +840 -0
  803. package/src/client/db-admin/TableBrowser.tsx +212 -0
  804. package/src/client/db-admin/TableEditor.tsx +760 -0
  805. package/src/client/db-admin/cell-format.ts +246 -0
  806. package/src/client/db-admin/changeset.ts +296 -0
  807. package/src/client/db-admin/export-utils.ts +78 -0
  808. package/src/client/db-admin/index.ts +33 -0
  809. package/src/client/db-admin/sql-storage.ts +134 -0
  810. package/src/client/db-admin/storage.ts +68 -0
  811. package/src/client/db-admin/useAgentSync.ts +226 -0
  812. package/src/client/db-admin/useDbAdmin.ts +294 -0
  813. package/src/client/dev-overlay/DevOverlay.tsx +576 -0
  814. package/src/client/dev-overlay/builtins.ts +40 -0
  815. package/src/client/dev-overlay/index.ts +23 -0
  816. package/src/client/dev-overlay/registry.ts +67 -0
  817. package/src/client/dev-overlay/types.ts +64 -0
  818. package/src/client/dev-overlay/use-dev-option.ts +78 -0
  819. package/src/client/dev-overlay/use-dev-overlay-shortcut.ts +31 -0
  820. package/src/client/dynamic-suggestions.ts +455 -0
  821. package/src/client/embed-auth.ts +544 -0
  822. package/src/client/embed.ts +54 -0
  823. package/src/client/error-capture.ts +690 -0
  824. package/src/client/error-format.ts +211 -0
  825. package/src/client/error-reporting.ts +100 -0
  826. package/src/client/extensions/AgentNativeExtensionFrame.e2e-host.tsx +136 -0
  827. package/src/client/extensions/AgentNativeExtensionFrame.tsx +708 -0
  828. package/src/client/extensions/EmbeddedExtension.tsx +604 -0
  829. package/src/client/extensions/ExtensionEditor.tsx +450 -0
  830. package/src/client/extensions/ExtensionQueryErrorState.tsx +47 -0
  831. package/src/client/extensions/ExtensionSlot.tsx +345 -0
  832. package/src/client/extensions/ExtensionViewer.tsx +1502 -0
  833. package/src/client/extensions/ExtensionViewerPage.tsx +32 -0
  834. package/src/client/extensions/ExtensionsListPage.tsx +432 -0
  835. package/src/client/extensions/ExtensionsSidebarSection.tsx +1259 -0
  836. package/src/client/extensions/InlineExtensionFrame.tsx +660 -0
  837. package/src/client/extensions/agent-native-extension-runtime.ts +495 -0
  838. package/src/client/extensions/delete-extension.ts +76 -0
  839. package/src/client/extensions/extension-load-error.ts +26 -0
  840. package/src/client/extensions/extension-order.ts +49 -0
  841. package/src/client/extensions/extension-popularity.ts +61 -0
  842. package/src/client/extensions/iframe-bridge.ts +377 -0
  843. package/src/client/extensions/index.ts +84 -0
  844. package/src/client/extensions/portable-extension.ts +712 -0
  845. package/src/client/feature-flags/FeatureFlagsPanel.tsx +519 -0
  846. package/src/client/feature-flags/helpers.ts +77 -0
  847. package/src/client/feature-flags/index.ts +13 -0
  848. package/src/client/feature-flags/types.ts +29 -0
  849. package/src/client/feature-flags/use-feature-flag.ts +26 -0
  850. package/src/client/feedback-context.ts +90 -0
  851. package/src/client/frame-protocol.ts +168 -0
  852. package/src/client/frame.ts +329 -0
  853. package/src/client/guided-questions.tsx +1152 -0
  854. package/src/client/history/VersionHistoryPanel.tsx +175 -0
  855. package/src/client/history/index.ts +21 -0
  856. package/src/client/history/use-history.ts +114 -0
  857. package/src/client/hooks/index.ts +46 -0
  858. package/src/client/host/index.ts +170 -0
  859. package/src/client/host-bridge.ts +1272 -0
  860. package/src/client/host-tools.ts +190 -0
  861. package/src/client/i18n.tsx +798 -0
  862. package/src/client/index.ts +370 -0
  863. package/src/client/integrations/IntegrationCard.tsx +186 -0
  864. package/src/client/integrations/IntegrationsPanel.tsx +633 -0
  865. package/src/client/integrations/api.ts +295 -0
  866. package/src/client/integrations/index.ts +29 -0
  867. package/src/client/integrations/useIntegrationStatus.ts +52 -0
  868. package/src/client/mcp-app-host.ts +720 -0
  869. package/src/client/mcp-apps/McpAppRenderer.tsx +760 -0
  870. package/src/client/navigation/index.ts +54 -0
  871. package/src/client/notifications/NotificationsBell.tsx +383 -0
  872. package/src/client/notifications/index.ts +1 -0
  873. package/src/client/observability/ObservabilityDashboard.tsx +890 -0
  874. package/src/client/observability/ThumbsFeedback.tsx +160 -0
  875. package/src/client/observability/index.ts +20 -0
  876. package/src/client/observability/useObservability.ts +266 -0
  877. package/src/client/onboarding/OnboardingBanner.tsx +73 -0
  878. package/src/client/onboarding/OnboardingPanel.tsx +1036 -0
  879. package/src/client/onboarding/SetupButton.tsx +64 -0
  880. package/src/client/onboarding/index.ts +25 -0
  881. package/src/client/onboarding/use-onboarding.ts +164 -0
  882. package/src/client/onboarding/use-preview-mode.ts +40 -0
  883. package/src/client/org/InvitationBanner.tsx +148 -0
  884. package/src/client/org/OrgSwitcher.tsx +754 -0
  885. package/src/client/org/RequireActiveOrg.tsx +337 -0
  886. package/src/client/org/TeamPage.tsx +1432 -0
  887. package/src/client/org/hooks.ts +359 -0
  888. package/src/client/org/index.ts +70 -0
  889. package/src/client/org/workspace-app-links.ts +367 -0
  890. package/src/client/org-team/index.ts +1 -0
  891. package/src/client/progress/RunsTray.tsx +680 -0
  892. package/src/client/progress/index.ts +1 -0
  893. package/src/client/require-session.tsx +142 -0
  894. package/src/client/resources/BuiltinCapabilityDetail.tsx +258 -0
  895. package/src/client/resources/McpConnectionSuggestion.tsx +276 -0
  896. package/src/client/resources/McpIntegrationDialog.tsx +808 -0
  897. package/src/client/resources/McpServerDetail.tsx +230 -0
  898. package/src/client/resources/ResourceEditor.tsx +872 -0
  899. package/src/client/resources/ResourceTree.tsx +860 -0
  900. package/src/client/resources/ResourcesPanel.tsx +1960 -0
  901. package/src/client/resources/index.ts +25 -0
  902. package/src/client/resources/mcp-integration-catalog.ts +771 -0
  903. package/src/client/resources/mcp-integration-logos.ts +108 -0
  904. package/src/client/resources/runtime.ts +1 -0
  905. package/src/client/resources/use-builtin-capabilities.ts +113 -0
  906. package/src/client/resources/use-mcp-servers.ts +217 -0
  907. package/src/client/resources/use-resources.ts +402 -0
  908. package/src/client/review/ReviewCommentComposer.tsx +123 -0
  909. package/src/client/review/ReviewStatusBadge.tsx +39 -0
  910. package/src/client/review/ReviewThreadPanel.tsx +625 -0
  911. package/src/client/review/index.ts +38 -0
  912. package/src/client/review/use-review.ts +186 -0
  913. package/src/client/route-chunk-recovery/index.ts +2 -0
  914. package/src/client/route-chunk-recovery.ts +331 -0
  915. package/src/client/route-state.ts +455 -0
  916. package/src/client/route-warmup.tsx +543 -0
  917. package/src/client/session-replay-iframe.e2e-host.tsx +100 -0
  918. package/src/client/session-replay.ts +3417 -0
  919. package/src/client/settings/AgentsSection.tsx +441 -0
  920. package/src/client/settings/AutomationsSection.tsx +516 -0
  921. package/src/client/settings/BackgroundAgentSection.tsx +189 -0
  922. package/src/client/settings/BrowserSection.tsx +110 -0
  923. package/src/client/settings/DemoModeSection.tsx +48 -0
  924. package/src/client/settings/SecretsSection.tsx +875 -0
  925. package/src/client/settings/SettingsPanel.tsx +3162 -0
  926. package/src/client/settings/SettingsSection.tsx +283 -0
  927. package/src/client/settings/SettingsTabsPage.tsx +592 -0
  928. package/src/client/settings/UsageSection.tsx +401 -0
  929. package/src/client/settings/VoiceTranscriptionSection.tsx +960 -0
  930. package/src/client/settings/agent-settings-search.ts +220 -0
  931. package/src/client/settings/index.ts +27 -0
  932. package/src/client/settings/useBuilderStatus.ts +1055 -0
  933. package/src/client/setup-connections/BuilderConnectCard.tsx +89 -0
  934. package/src/client/setup-connections/ProviderReadinessBadge.tsx +73 -0
  935. package/src/client/setup-connections/SetupConnectionsPage.tsx +94 -0
  936. package/src/client/setup-connections/catalog.ts +6 -0
  937. package/src/client/setup-connections/index.ts +47 -0
  938. package/src/client/setup-connections/runtime.ts +1 -0
  939. package/src/client/sharing/ShareButton.tsx +1748 -0
  940. package/src/client/sharing/ShareDialog.tsx +846 -0
  941. package/src/client/sharing/VisibilityBadge.tsx +4 -0
  942. package/src/client/sharing/index.ts +2 -0
  943. package/src/client/sse-event-processor.ts +2002 -0
  944. package/src/client/terminal/AgentTerminal.tsx +488 -0
  945. package/src/client/terminal/index.ts +7 -0
  946. package/src/client/theme.ts +27 -0
  947. package/src/client/tombstone/agent-presence-chip.ts +17 -0
  948. package/src/client/tombstone/editor.ts +265 -0
  949. package/src/client/tombstone/live-cursor-overlay.ts +21 -0
  950. package/src/client/tombstone/presence-bar.ts +17 -0
  951. package/src/client/tombstone/recent-edit-highlights.ts +17 -0
  952. package/src/client/tombstone/remote-selection-rings.ts +21 -0
  953. package/src/client/tombstone/rich-markdown-editor.ts +265 -0
  954. package/src/client/tombstone/ui-dialog.ts +49 -0
  955. package/src/client/tombstone/ui-dropdown-menu.ts +69 -0
  956. package/src/client/tombstone/ui-hover-card.ts +21 -0
  957. package/src/client/tombstone/ui-popover.ts +25 -0
  958. package/src/client/tombstone/ui-sheet.ts +49 -0
  959. package/src/client/tombstone/ui-tooltip.ts +29 -0
  960. package/src/client/tombstone/visual-style-controls.ts +61 -0
  961. package/src/client/tool-cells/BashCell.tsx +164 -0
  962. package/src/client/tool-cells/EditCell.tsx +303 -0
  963. package/src/client/tool-cells/FilesChangedSummary.tsx +208 -0
  964. package/src/client/tool-cells/WriteCell.tsx +145 -0
  965. package/src/client/tool-cells/index.ts +7 -0
  966. package/src/client/tool-display.ts +41 -0
  967. package/src/client/track.ts +57 -0
  968. package/src/client/transcription/BuilderTranscriptionCta.tsx +120 -0
  969. package/src/client/transcription/use-live-transcription.ts +292 -0
  970. package/src/client/ui/index.ts +55 -0
  971. package/src/client/uploads/index.ts +6 -0
  972. package/src/client/uploads/upload-editor-image.ts +79 -0
  973. package/src/client/uploads/use-upload-resource.ts +24 -0
  974. package/src/client/url-scrub.ts +51 -0
  975. package/src/client/use-action.ts +797 -0
  976. package/src/client/use-agent-chat-context.ts +63 -0
  977. package/src/client/use-agent-chat-home-handoff.ts +207 -0
  978. package/src/client/use-agent-chat.ts +53 -0
  979. package/src/client/use-agent-engine-configured.ts +211 -0
  980. package/src/client/use-avatar.ts +127 -0
  981. package/src/client/use-change-version.ts +150 -0
  982. package/src/client/use-chat-models.ts +252 -0
  983. package/src/client/use-chat-threads.ts +1262 -0
  984. package/src/client/use-db-sync.ts +1112 -0
  985. package/src/client/use-demo-mode-status.ts +30 -0
  986. package/src/client/use-dev-mode.ts +177 -0
  987. package/src/client/use-external-value.ts +40 -0
  988. package/src/client/use-pausing-interval.ts +65 -0
  989. package/src/client/use-pinch-zoom.ts +202 -0
  990. package/src/client/use-run-stuck-detection.ts +427 -0
  991. package/src/client/use-send-to-agent-chat.ts +85 -0
  992. package/src/client/use-session.ts +114 -0
  993. package/src/client/utils.ts +7 -0
  994. package/src/client/visual-style-controls.tsx +1 -0
  995. package/src/client/vite-dev-recovery-script.ts +226 -0
  996. package/src/client/voice-provider-status.ts +44 -0
  997. package/src/client/widgets/index.ts +41 -0
  998. package/src/code-agents/background-controller.ts +398 -0
  999. package/src/code-agents/background-run.ts +156 -0
  1000. package/src/code-agents/index.ts +95 -0
  1001. package/src/code-agents/prompt-attachments.ts +39 -0
  1002. package/src/code-agents/transcript-normalizer.ts +750 -0
  1003. package/src/code-agents/transcript-order.ts +73 -0
  1004. package/src/coding-tools/index.ts +742 -0
  1005. package/src/coding-tools/run-code.ts +1662 -0
  1006. package/src/coding-tools/sandbox/adapter.ts +82 -0
  1007. package/src/coding-tools/sandbox/background.ts +406 -0
  1008. package/src/coding-tools/sandbox/executions-store.ts +517 -0
  1009. package/src/coding-tools/sandbox/index.ts +132 -0
  1010. package/src/coding-tools/sandbox/local-child-process-adapter.ts +217 -0
  1011. package/src/collab/agent-identity.ts +5 -0
  1012. package/src/collab/agent-presence.ts +341 -0
  1013. package/src/collab/awareness-store.ts +196 -0
  1014. package/src/collab/awareness.ts +317 -0
  1015. package/src/collab/client-struct.ts +366 -0
  1016. package/src/collab/client.ts +1205 -0
  1017. package/src/collab/emitter.ts +31 -0
  1018. package/src/collab/follow-mode.ts +107 -0
  1019. package/src/collab/index.ts +151 -0
  1020. package/src/collab/json-to-yjs.ts +504 -0
  1021. package/src/collab/presence.ts +207 -0
  1022. package/src/collab/recent-edits.ts +215 -0
  1023. package/src/collab/routes.ts +191 -0
  1024. package/src/collab/storage.ts +228 -0
  1025. package/src/collab/struct-routes.ts +133 -0
  1026. package/src/collab/text-to-yjs.ts +79 -0
  1027. package/src/collab/undo.ts +432 -0
  1028. package/src/collab/xml-ops.ts +68 -0
  1029. package/src/collab/ydoc-manager.ts +544 -0
  1030. package/src/command-navigation/actions.ts +100 -0
  1031. package/src/command-navigation/index.ts +12 -0
  1032. package/src/comments-review/index.ts +1 -0
  1033. package/src/connections/catalog.ts +451 -0
  1034. package/src/connections/index.ts +48 -0
  1035. package/src/connections/reader.ts +911 -0
  1036. package/src/connections/runtime.ts +1 -0
  1037. package/src/credentials/index.ts +187 -0
  1038. package/src/data-programs/actions.ts +335 -0
  1039. package/src/data-programs/contract.ts +284 -0
  1040. package/src/data-programs/execute.ts +693 -0
  1041. package/src/data-programs/index.ts +72 -0
  1042. package/src/data-programs/schema.ts +148 -0
  1043. package/src/data-programs/store.ts +755 -0
  1044. package/src/data-widgets/index.ts +376 -0
  1045. package/src/db/client.ts +1627 -0
  1046. package/src/db/create-get-db.ts +579 -0
  1047. package/src/db/ddl-guard.ts +302 -0
  1048. package/src/db/drizzle-config.ts +190 -0
  1049. package/src/db/ensure-additive-columns.ts +430 -0
  1050. package/src/db/index.ts +57 -0
  1051. package/src/db/migrations.ts +691 -0
  1052. package/src/db/request-telemetry.ts +154 -0
  1053. package/src/db/runtime-diagnostics.ts +331 -0
  1054. package/src/db/schema.ts +121 -0
  1055. package/src/db/widen-columns.ts +75 -0
  1056. package/src/db-admin/agent-tools.ts +202 -0
  1057. package/src/db-admin/index.ts +26 -0
  1058. package/src/db-admin/operations.ts +803 -0
  1059. package/src/db-admin/routes.ts +174 -0
  1060. package/src/db-admin/types.ts +110 -0
  1061. package/src/demo/browser-state.ts +47 -0
  1062. package/src/demo/fetch-interceptor.ts +175 -0
  1063. package/src/demo/redact.ts +575 -0
  1064. package/src/deploy/build.ts +3938 -0
  1065. package/src/deploy/immutable-assets.ts +65 -0
  1066. package/src/deploy/route-discovery.ts +458 -0
  1067. package/src/deploy/workspace-core.ts +282 -0
  1068. package/src/deploy/workspace-deploy.ts +1488 -0
  1069. package/src/eject/provider-api-definitions.ts +193 -0
  1070. package/src/eject/remote-mcp-presets.ts +137 -0
  1071. package/src/eject/setup-readiness-ui.tsx +29 -0
  1072. package/src/eject/workspace-connections.ts +92 -0
  1073. package/src/embedding/agent.ts +159 -0
  1074. package/src/embedding/bridge.ts +297 -0
  1075. package/src/embedding/index.ts +43 -0
  1076. package/src/embedding/protocol.ts +185 -0
  1077. package/src/embedding/react.tsx +319 -0
  1078. package/src/embeddings/index.ts +233 -0
  1079. package/src/eval/agent-runner.ts +210 -0
  1080. package/src/eval/define-eval.ts +55 -0
  1081. package/src/eval/index.ts +49 -0
  1082. package/src/eval/report.ts +75 -0
  1083. package/src/eval/runner.ts +369 -0
  1084. package/src/eval/scorer.ts +244 -0
  1085. package/src/eval/types.ts +188 -0
  1086. package/src/event-bus/bus.ts +140 -0
  1087. package/src/event-bus/index.ts +3 -0
  1088. package/src/event-bus/registry.ts +79 -0
  1089. package/src/event-bus/types.ts +29 -0
  1090. package/src/extensions/actions.ts +1636 -0
  1091. package/src/extensions/change-marker.ts +54 -0
  1092. package/src/extensions/content-patch.ts +468 -0
  1093. package/src/extensions/fetch-tool.ts +461 -0
  1094. package/src/extensions/html-shell.ts +803 -0
  1095. package/src/extensions/local.ts +462 -0
  1096. package/src/extensions/path.ts +40 -0
  1097. package/src/extensions/proxy-security.ts +171 -0
  1098. package/src/extensions/routes.ts +1080 -0
  1099. package/src/extensions/schema.ts +269 -0
  1100. package/src/extensions/session-replay-iframe.ts +131 -0
  1101. package/src/extensions/slots/routes.ts +126 -0
  1102. package/src/extensions/slots/schema.ts +87 -0
  1103. package/src/extensions/slots/store.ts +450 -0
  1104. package/src/extensions/store.ts +1347 -0
  1105. package/src/extensions/theme.ts +114 -0
  1106. package/src/extensions/url-safety.ts +316 -0
  1107. package/src/extensions/web-content.ts +663 -0
  1108. package/src/extensions/web-search-tool.ts +437 -0
  1109. package/src/feature-flags/a2a-action-route.ts +56 -0
  1110. package/src/feature-flags/actions/get-feature-flags.ts +24 -0
  1111. package/src/feature-flags/actions/list-feature-flags.ts +69 -0
  1112. package/src/feature-flags/actions/set-feature-flag.ts +89 -0
  1113. package/src/feature-flags/index.ts +21 -0
  1114. package/src/feature-flags/permissions.ts +53 -0
  1115. package/src/feature-flags/plugin.ts +44 -0
  1116. package/src/feature-flags/registry.ts +96 -0
  1117. package/src/feature-flags/store.ts +176 -0
  1118. package/src/file-upload/actions/upload-image.ts +213 -0
  1119. package/src/file-upload/builder.ts +431 -0
  1120. package/src/file-upload/index.ts +24 -0
  1121. package/src/file-upload/pre-upload-attachments.ts +305 -0
  1122. package/src/file-upload/registry.ts +125 -0
  1123. package/src/file-upload/types.ts +89 -0
  1124. package/src/guards/db-tool-scoping.ts +129 -0
  1125. package/src/guards/index.ts +23 -0
  1126. package/src/guards/no-drizzle-push.ts +116 -0
  1127. package/src/guards/no-env-credentials.ts +229 -0
  1128. package/src/guards/no-env-mutation.ts +114 -0
  1129. package/src/guards/no-localhost-fallback.ts +124 -0
  1130. package/src/guards/no-unscoped-credentials.ts +227 -0
  1131. package/src/guards/no-unscoped-queries.ts +834 -0
  1132. package/src/guards/scan-utils.ts +105 -0
  1133. package/src/guards/types.ts +30 -0
  1134. package/src/history/actions/create-resource-version.ts +92 -0
  1135. package/src/history/actions/get-resource-version.ts +60 -0
  1136. package/src/history/actions/list-resource-history.ts +53 -0
  1137. package/src/history/actions/list-resource-versions.ts +43 -0
  1138. package/src/history/actions/restore-resource-version.ts +96 -0
  1139. package/src/history/index.ts +26 -0
  1140. package/src/history/registry.ts +111 -0
  1141. package/src/history/store.ts +379 -0
  1142. package/src/history/types.ts +69 -0
  1143. package/src/index.browser.ts +155 -0
  1144. package/src/index.ts +408 -0
  1145. package/src/ingestion/docx.ts +158 -0
  1146. package/src/ingestion/figma-node-to-html.ts +1860 -0
  1147. package/src/ingestion/figma.ts +548 -0
  1148. package/src/ingestion/index.ts +107 -0
  1149. package/src/ingestion/media.ts +264 -0
  1150. package/src/ingestion/notion.ts +121 -0
  1151. package/src/ingestion/office.ts +213 -0
  1152. package/src/ingestion/orchestration.ts +160 -0
  1153. package/src/ingestion/pptx.ts +331 -0
  1154. package/src/ingestion/selection.ts +81 -0
  1155. package/src/ingestion/website.ts +151 -0
  1156. package/src/integrations/a2a-continuation-marker.ts +2 -0
  1157. package/src/integrations/a2a-continuation-processor.ts +888 -0
  1158. package/src/integrations/a2a-continuations-store.ts +643 -0
  1159. package/src/integrations/adapter-overrides.ts +12 -0
  1160. package/src/integrations/adapters/discord.ts +364 -0
  1161. package/src/integrations/adapters/email.ts +1164 -0
  1162. package/src/integrations/adapters/google-docs.ts +371 -0
  1163. package/src/integrations/adapters/index.ts +29 -0
  1164. package/src/integrations/adapters/microsoft-teams.ts +413 -0
  1165. package/src/integrations/adapters/slack.ts +1914 -0
  1166. package/src/integrations/adapters/telegram.ts +386 -0
  1167. package/src/integrations/adapters/whatsapp.ts +358 -0
  1168. package/src/integrations/awaiting-input-store.ts +130 -0
  1169. package/src/integrations/catalog.ts +571 -0
  1170. package/src/integrations/computer-supervision-store.ts +423 -0
  1171. package/src/integrations/computer-supervision.ts +213 -0
  1172. package/src/integrations/config-store.ts +131 -0
  1173. package/src/integrations/controls-store.ts +187 -0
  1174. package/src/integrations/eject/messaging-adapters.ts +85 -0
  1175. package/src/integrations/google-docs-poller.ts +671 -0
  1176. package/src/integrations/identity-links-store.ts +210 -0
  1177. package/src/integrations/identity.ts +196 -0
  1178. package/src/integrations/index.ts +220 -0
  1179. package/src/integrations/installations-store.ts +677 -0
  1180. package/src/integrations/integration-memory.ts +187 -0
  1181. package/src/integrations/internal-token.ts +106 -0
  1182. package/src/integrations/pending-tasks-retry-job.ts +286 -0
  1183. package/src/integrations/pending-tasks-store.ts +468 -0
  1184. package/src/integrations/plugin.ts +3048 -0
  1185. package/src/integrations/remote-commands-store.ts +690 -0
  1186. package/src/integrations/remote-devices-store.ts +492 -0
  1187. package/src/integrations/remote-json-safety.ts +127 -0
  1188. package/src/integrations/remote-push-delivery-job.ts +48 -0
  1189. package/src/integrations/remote-push-delivery.ts +370 -0
  1190. package/src/integrations/remote-push-store.ts +664 -0
  1191. package/src/integrations/remote-retry-job.ts +55 -0
  1192. package/src/integrations/remote-run-events-store.ts +222 -0
  1193. package/src/integrations/remote-types.ts +186 -0
  1194. package/src/integrations/runtime.ts +1 -0
  1195. package/src/integrations/scope-store.ts +498 -0
  1196. package/src/integrations/slack-manifest.ts +78 -0
  1197. package/src/integrations/slack-oauth.ts +344 -0
  1198. package/src/integrations/task-queue-stats.ts +144 -0
  1199. package/src/integrations/thread-mapping-store.ts +134 -0
  1200. package/src/integrations/types.ts +485 -0
  1201. package/src/integrations/usage-budget-store.ts +906 -0
  1202. package/src/integrations/webhook-delivery.ts +55 -0
  1203. package/src/integrations/webhook-handler.ts +1939 -0
  1204. package/src/jobs/actions/list-recurring-jobs.ts +105 -0
  1205. package/src/jobs/actions/manage-recurring-job.ts +85 -0
  1206. package/src/jobs/cron.ts +126 -0
  1207. package/src/jobs/index.ts +9 -0
  1208. package/src/jobs/scheduler.ts +703 -0
  1209. package/src/jobs/tools.ts +367 -0
  1210. package/src/local-artifacts/index.ts +1372 -0
  1211. package/src/localization/actions/get-localization-preference.ts +24 -0
  1212. package/src/localization/actions/set-localization-preference.ts +32 -0
  1213. package/src/localization/default-messages.ts +1009 -0
  1214. package/src/localization/index.ts +2 -0
  1215. package/src/localization/server.ts +131 -0
  1216. package/src/localization/shared.ts +208 -0
  1217. package/src/mcp/actions/call-mcp-tool.ts +19 -0
  1218. package/src/mcp/actions/create-org-service-token.ts +87 -0
  1219. package/src/mcp/actions/list-mcp-tools.ts +18 -0
  1220. package/src/mcp/actions/list-org-service-tokens.ts +44 -0
  1221. package/src/mcp/actions/revoke-org-service-token.ts +30 -0
  1222. package/src/mcp/actions/service-token-access.ts +121 -0
  1223. package/src/mcp/ask-app-inline-tasks.ts +125 -0
  1224. package/src/mcp/build-server.ts +2356 -0
  1225. package/src/mcp/builtin-tools.ts +1506 -0
  1226. package/src/mcp/connect-route.ts +1460 -0
  1227. package/src/mcp/connect-store.ts +680 -0
  1228. package/src/mcp/embed-app.ts +2051 -0
  1229. package/src/mcp/embed-route.ts +84 -0
  1230. package/src/mcp/external-agent-policy.ts +18 -0
  1231. package/src/mcp/index.ts +47 -0
  1232. package/src/mcp/oauth-route.ts +1016 -0
  1233. package/src/mcp/oauth-store.ts +581 -0
  1234. package/src/mcp/oauth-token.ts +227 -0
  1235. package/src/mcp/org-directory.ts +343 -0
  1236. package/src/mcp/route-paths.ts +34 -0
  1237. package/src/mcp/screen-memory-stdio.ts +290 -0
  1238. package/src/mcp/server.ts +507 -0
  1239. package/src/mcp/stdio.ts +328 -0
  1240. package/src/mcp/workspace-resolve.ts +261 -0
  1241. package/src/mcp-client/app-api.ts +136 -0
  1242. package/src/mcp-client/app-result.ts +64 -0
  1243. package/src/mcp-client/builtin-capabilities.ts +146 -0
  1244. package/src/mcp-client/builtin-store.ts +93 -0
  1245. package/src/mcp-client/config.ts +281 -0
  1246. package/src/mcp-client/errors.ts +54 -0
  1247. package/src/mcp-client/hub-client.ts +172 -0
  1248. package/src/mcp-client/hub-routes.ts +198 -0
  1249. package/src/mcp-client/index.ts +504 -0
  1250. package/src/mcp-client/manager.ts +968 -0
  1251. package/src/mcp-client/oauth-client.ts +462 -0
  1252. package/src/mcp-client/oauth-routes.ts +307 -0
  1253. package/src/mcp-client/remote-store.ts +674 -0
  1254. package/src/mcp-client/remote-url.ts +97 -0
  1255. package/src/mcp-client/routes.ts +1093 -0
  1256. package/src/mcp-client/screen-memory-local.ts +461 -0
  1257. package/src/mcp-client/tool-policy.ts +182 -0
  1258. package/src/mcp-client/visibility.ts +56 -0
  1259. package/src/mcp-client/workspace-servers.ts +379 -0
  1260. package/src/navigation/index.ts +170 -0
  1261. package/src/notifications/actions.ts +151 -0
  1262. package/src/notifications/channels.ts +410 -0
  1263. package/src/notifications/index.ts +22 -0
  1264. package/src/notifications/registry.ts +216 -0
  1265. package/src/notifications/routes.ts +106 -0
  1266. package/src/notifications/store.ts +236 -0
  1267. package/src/notifications/types.ts +52 -0
  1268. package/src/oauth-tokens/google-refresh.ts +175 -0
  1269. package/src/oauth-tokens/index.ts +15 -0
  1270. package/src/oauth-tokens/store.ts +384 -0
  1271. package/src/observability/cleanup-job.ts +124 -0
  1272. package/src/observability/evals.ts +507 -0
  1273. package/src/observability/experiments.ts +359 -0
  1274. package/src/observability/feedback.ts +332 -0
  1275. package/src/observability/hosted-model-experiment.ts +118 -0
  1276. package/src/observability/index.ts +62 -0
  1277. package/src/observability/plugin.ts +31 -0
  1278. package/src/observability/routes.ts +431 -0
  1279. package/src/observability/sentiment.ts +261 -0
  1280. package/src/observability/store.ts +1347 -0
  1281. package/src/observability/traces.ts +789 -0
  1282. package/src/observability/tracing.ts +157 -0
  1283. package/src/observability/tracking-identity.ts +55 -0
  1284. package/src/observability/types.ts +211 -0
  1285. package/src/onboarding/default-steps.ts +428 -0
  1286. package/src/onboarding/index.ts +22 -0
  1287. package/src/onboarding/plugin.ts +262 -0
  1288. package/src/onboarding/registry.ts +47 -0
  1289. package/src/onboarding/types.ts +96 -0
  1290. package/src/org/accept-pending.ts +98 -0
  1291. package/src/org/auto-join-domain.ts +113 -0
  1292. package/src/org/context.ts +640 -0
  1293. package/src/org/free-email-providers.ts +124 -0
  1294. package/src/org/handlers.ts +1153 -0
  1295. package/src/org/index.ts +63 -0
  1296. package/src/org/migrations.ts +105 -0
  1297. package/src/org/permissions.ts +30 -0
  1298. package/src/org/plugin.ts +255 -0
  1299. package/src/org/schema.ts +28 -0
  1300. package/src/org/types.ts +57 -0
  1301. package/src/org-team/index.ts +1 -0
  1302. package/src/package-lifecycle/deprecated-imports.ts +179 -0
  1303. package/src/package-lifecycle/eject-manifest.ts +345 -0
  1304. package/src/package-lifecycle/index.ts +7 -0
  1305. package/src/package-lifecycle/manifest.ts +120 -0
  1306. package/src/package-lifecycle/migration-manifest.ts +159 -0
  1307. package/src/package-lifecycle/migration-message.ts +6 -0
  1308. package/src/package-lifecycle/tombstone.ts +58 -0
  1309. package/src/package-lifecycle/upgrade-error.ts +37 -0
  1310. package/src/private-blob/index.ts +18 -0
  1311. package/src/private-blob/registry.ts +242 -0
  1312. package/src/private-blob/types.ts +55 -0
  1313. package/src/progress/actions.ts +179 -0
  1314. package/src/progress/index.ts +16 -0
  1315. package/src/progress/registry.ts +127 -0
  1316. package/src/progress/routes.ts +89 -0
  1317. package/src/progress/store.ts +353 -0
  1318. package/src/progress/types.ts +59 -0
  1319. package/src/provider-api/actions/custom-provider-registration.ts +197 -0
  1320. package/src/provider-api/actions/delete-staged-dataset.ts +5 -0
  1321. package/src/provider-api/actions/github-repo-files.ts +266 -0
  1322. package/src/provider-api/actions/list-staged-datasets.ts +5 -0
  1323. package/src/provider-api/actions/provider-api-audit.ts +88 -0
  1324. package/src/provider-api/actions/provider-api.ts +460 -0
  1325. package/src/provider-api/actions/query-staged-dataset.ts +5 -0
  1326. package/src/provider-api/actions/staged-datasets.ts +359 -0
  1327. package/src/provider-api/corpus-jobs-store.ts +522 -0
  1328. package/src/provider-api/corpus-jobs.ts +1817 -0
  1329. package/src/provider-api/custom-registry.ts +456 -0
  1330. package/src/provider-api/github-repo.ts +675 -0
  1331. package/src/provider-api/gong.ts +108 -0
  1332. package/src/provider-api/index.ts +5444 -0
  1333. package/src/provider-api/quota-governor.ts +562 -0
  1334. package/src/provider-api/runtime.ts +1 -0
  1335. package/src/provider-api/staged-datasets-aggregate.ts +304 -0
  1336. package/src/provider-api/staged-datasets-store.ts +441 -0
  1337. package/src/provider-api/staging.ts +483 -0
  1338. package/src/resources/agents.ts +65 -0
  1339. package/src/resources/emitter.ts +54 -0
  1340. package/src/resources/handlers.ts +728 -0
  1341. package/src/resources/index.ts +4 -0
  1342. package/src/resources/metadata.ts +252 -0
  1343. package/src/resources/script-helpers.ts +160 -0
  1344. package/src/resources/store.ts +1618 -0
  1345. package/src/review/actions/consume-review-feedback.ts +48 -0
  1346. package/src/review/actions/create-review-comment.ts +108 -0
  1347. package/src/review/actions/delete-review-comment.ts +67 -0
  1348. package/src/review/actions/get-review-feedback.ts +49 -0
  1349. package/src/review/actions/list-review-comments.ts +87 -0
  1350. package/src/review/actions/reply-review-comment.ts +118 -0
  1351. package/src/review/actions/resolve-review-thread.ts +94 -0
  1352. package/src/review/actions/send-review-thread-to-agent.ts +62 -0
  1353. package/src/review/actions/set-review-status.ts +58 -0
  1354. package/src/review/identity.ts +101 -0
  1355. package/src/review/index.ts +45 -0
  1356. package/src/review/mentions.ts +44 -0
  1357. package/src/review/registry.ts +111 -0
  1358. package/src/review/store.ts +997 -0
  1359. package/src/review/types.ts +101 -0
  1360. package/src/router/index.ts +24 -0
  1361. package/src/scripts/agent-engines/list-agent-engines.ts +130 -0
  1362. package/src/scripts/agent-engines/manage-agent-engine.ts +197 -0
  1363. package/src/scripts/agent-engines/set-agent-engine.ts +84 -0
  1364. package/src/scripts/agent-engines/test-agent-engine.ts +174 -0
  1365. package/src/scripts/call-agent.ts +792 -0
  1366. package/src/scripts/chat/index.ts +8 -0
  1367. package/src/scripts/chat/open-chat.ts +56 -0
  1368. package/src/scripts/chat/search-chats.ts +121 -0
  1369. package/src/scripts/core-scripts.ts +22 -0
  1370. package/src/scripts/db/check-scoping.ts +212 -0
  1371. package/src/scripts/db/exec.ts +791 -0
  1372. package/src/scripts/db/index.ts +19 -0
  1373. package/src/scripts/db/migrate-encrypt-credentials.ts +223 -0
  1374. package/src/scripts/db/migrate-encrypt-oauth-tokens.ts +213 -0
  1375. package/src/scripts/db/migrate-user-api-keys.ts +257 -0
  1376. package/src/scripts/db/patch.ts +820 -0
  1377. package/src/scripts/db/query.ts +299 -0
  1378. package/src/scripts/db/reset-dev-owner.ts +285 -0
  1379. package/src/scripts/db/safety.ts +286 -0
  1380. package/src/scripts/db/schema.ts +364 -0
  1381. package/src/scripts/db/scoping.ts +341 -0
  1382. package/src/scripts/db/sqlite-client.ts +78 -0
  1383. package/src/scripts/db/tool-mode.ts +23 -0
  1384. package/src/scripts/db/tool-schemas.ts +31 -0
  1385. package/src/scripts/db/wipe-leaked-builder-keys.ts +201 -0
  1386. package/src/scripts/dev/index.ts +296 -0
  1387. package/src/scripts/dev/list-files.ts +119 -0
  1388. package/src/scripts/dev/read-file.ts +77 -0
  1389. package/src/scripts/dev/search-files.ts +151 -0
  1390. package/src/scripts/dev/shell.ts +111 -0
  1391. package/src/scripts/dev/write-file.ts +56 -0
  1392. package/src/scripts/dev-session.ts +96 -0
  1393. package/src/scripts/docs/index.ts +8 -0
  1394. package/src/scripts/docs/search.ts +274 -0
  1395. package/src/scripts/docs/source-search.ts +363 -0
  1396. package/src/scripts/index.ts +15 -0
  1397. package/src/scripts/manage-agent-loop-settings.ts +84 -0
  1398. package/src/scripts/parse-args.ts +46 -0
  1399. package/src/scripts/resources/delete-memory.ts +67 -0
  1400. package/src/scripts/resources/delete.ts +69 -0
  1401. package/src/scripts/resources/effective.ts +71 -0
  1402. package/src/scripts/resources/index.ts +18 -0
  1403. package/src/scripts/resources/list.ts +103 -0
  1404. package/src/scripts/resources/migrate-learnings.ts +40 -0
  1405. package/src/scripts/resources/read.ts +107 -0
  1406. package/src/scripts/resources/save-memory.ts +99 -0
  1407. package/src/scripts/resources/write.ts +158 -0
  1408. package/src/scripts/runner.ts +339 -0
  1409. package/src/scripts/utils.ts +130 -0
  1410. package/src/search/index.ts +413 -0
  1411. package/src/search-utils/index.ts +112 -0
  1412. package/src/secrets/crypto.ts +228 -0
  1413. package/src/secrets/index.ts +65 -0
  1414. package/src/secrets/onboarding.ts +101 -0
  1415. package/src/secrets/register-framework-secrets.ts +228 -0
  1416. package/src/secrets/register.ts +129 -0
  1417. package/src/secrets/routes.ts +697 -0
  1418. package/src/secrets/schema.ts +52 -0
  1419. package/src/secrets/storage.ts +561 -0
  1420. package/src/secrets/substitution.ts +305 -0
  1421. package/src/server/__snapshots__/agent-chat-plugin.surface.spec.ts.snap +150 -0
  1422. package/src/server/action-change.ts +66 -0
  1423. package/src/server/action-discovery.ts +774 -0
  1424. package/src/server/action-routes.ts +658 -0
  1425. package/src/server/agent-access.ts +72 -0
  1426. package/src/server/agent-chat/action-filters-a2a.ts +339 -0
  1427. package/src/server/agent-chat/browser-team-tools.ts +510 -0
  1428. package/src/server/agent-chat/context-tools.ts +599 -0
  1429. package/src/server/agent-chat/framework-prompts.ts +467 -0
  1430. package/src/server/agent-chat/lazy-fs.ts +11 -0
  1431. package/src/server/agent-chat/mcp-glue.ts +91 -0
  1432. package/src/server/agent-chat/plugin-options.ts +379 -0
  1433. package/src/server/agent-chat/process-run-failure.ts +62 -0
  1434. package/src/server/agent-chat/prompt-resources.ts +1004 -0
  1435. package/src/server/agent-chat/recurring-jobs-runtime.ts +80 -0
  1436. package/src/server/agent-chat/request-surface.ts +71 -0
  1437. package/src/server/agent-chat/run-code-tools.ts +67 -0
  1438. package/src/server/agent-chat/script-entries.ts +796 -0
  1439. package/src/server/agent-chat/shared-thread.ts +301 -0
  1440. package/src/server/agent-chat/skill-frontmatter.ts +41 -0
  1441. package/src/server/agent-chat-plugin.ts +5930 -0
  1442. package/src/server/agent-discovery.ts +706 -0
  1443. package/src/server/agent-engine-api-key-route.ts +236 -0
  1444. package/src/server/agent-run-context.ts +204 -0
  1445. package/src/server/agent-teams-run-queue.ts +344 -0
  1446. package/src/server/agent-teams.ts +2257 -0
  1447. package/src/server/agents-bundle.ts +464 -0
  1448. package/src/server/analytics.ts +105 -0
  1449. package/src/server/app-base-path.ts +79 -0
  1450. package/src/server/app-name.ts +53 -0
  1451. package/src/server/app-url.ts +184 -0
  1452. package/src/server/attachment-actions.ts +276 -0
  1453. package/src/server/attribution.ts +214 -0
  1454. package/src/server/auth-marketing.ts +260 -0
  1455. package/src/server/auth-plugin.ts +32 -0
  1456. package/src/server/auth.ts +3803 -0
  1457. package/src/server/better-auth-instance.ts +1201 -0
  1458. package/src/server/builder-browser.ts +1701 -0
  1459. package/src/server/builder-design-systems.ts +668 -0
  1460. package/src/server/builder-space.ts +176 -0
  1461. package/src/server/captcha.ts +63 -0
  1462. package/src/server/capture-error.ts +64 -0
  1463. package/src/server/cli-capture.ts +141 -0
  1464. package/src/server/collab-plugin.ts +391 -0
  1465. package/src/server/complete-text.ts +231 -0
  1466. package/src/server/cookie-namespace.ts +159 -0
  1467. package/src/server/core-routes-plugin.ts +4122 -0
  1468. package/src/server/cors-origins.ts +60 -0
  1469. package/src/server/create-server.ts +268 -0
  1470. package/src/server/credential-provider.ts +1202 -0
  1471. package/src/server/csrf.ts +229 -0
  1472. package/src/server/date-utils.ts +42 -0
  1473. package/src/server/deep-link.ts +119 -0
  1474. package/src/server/derived-secret.ts +32 -0
  1475. package/src/server/design-token-utils.ts +1032 -0
  1476. package/src/server/desktop-sso.ts +83 -0
  1477. package/src/server/edge.ts +69 -0
  1478. package/src/server/email-actions.ts +218 -0
  1479. package/src/server/email-template.ts +192 -0
  1480. package/src/server/email-templates.ts +148 -0
  1481. package/src/server/email.ts +219 -0
  1482. package/src/server/embed-route.ts +283 -0
  1483. package/src/server/embed-session.ts +805 -0
  1484. package/src/server/embedded.ts +252 -0
  1485. package/src/server/entry-server.tsx +154 -0
  1486. package/src/server/framework-request-handler.ts +848 -0
  1487. package/src/server/fusion-app.ts +447 -0
  1488. package/src/server/google-auth-mode.ts +34 -0
  1489. package/src/server/google-auth-plugin.ts +496 -0
  1490. package/src/server/google-oauth-credentials.ts +128 -0
  1491. package/src/server/google-oauth.ts +972 -0
  1492. package/src/server/google-realtime-session.ts +182 -0
  1493. package/src/server/h3-helpers.ts +174 -0
  1494. package/src/server/http-response-telemetry.ts +396 -0
  1495. package/src/server/identity-sso-store.ts +304 -0
  1496. package/src/server/identity-sso.ts +514 -0
  1497. package/src/server/index.ts +593 -0
  1498. package/src/server/missing-key.ts +36 -0
  1499. package/src/server/oauth-helpers.ts +44 -0
  1500. package/src/server/oauth-public-origin.ts +44 -0
  1501. package/src/server/oauth-return-url.ts +120 -0
  1502. package/src/server/og-fonts-data.ts +15 -0
  1503. package/src/server/og-fonts.ts +75 -0
  1504. package/src/server/onboarding-html.ts +4040 -0
  1505. package/src/server/open-route.ts +311 -0
  1506. package/src/server/org-admin.ts +28 -0
  1507. package/src/server/poll-events.ts +88 -0
  1508. package/src/server/poll.ts +1224 -0
  1509. package/src/server/prompts/framework-core-compact.ts +107 -0
  1510. package/src/server/prompts/framework-core.ts +143 -0
  1511. package/src/server/prompts/index.ts +16 -0
  1512. package/src/server/prompts/model-overlays.ts +54 -0
  1513. package/src/server/prompts/shared-rules.ts +104 -0
  1514. package/src/server/realtime-voice.ts +967 -0
  1515. package/src/server/recap-image-route.ts +242 -0
  1516. package/src/server/recap-image-store.ts +185 -0
  1517. package/src/server/request-context.ts +416 -0
  1518. package/src/server/request-origin.ts +67 -0
  1519. package/src/server/resources-plugin.ts +119 -0
  1520. package/src/server/schema-prompt.ts +429 -0
  1521. package/src/server/scoped-key-storage.ts +223 -0
  1522. package/src/server/security-headers.ts +170 -0
  1523. package/src/server/self-dispatch.ts +224 -0
  1524. package/src/server/sentry-config.ts +73 -0
  1525. package/src/server/sentry-plugin.ts +138 -0
  1526. package/src/server/sentry.ts +444 -0
  1527. package/src/server/short-lived-token.ts +170 -0
  1528. package/src/server/social-og-image.ts +454 -0
  1529. package/src/server/sse.ts +102 -0
  1530. package/src/server/ssr-handler.ts +466 -0
  1531. package/src/server/transcribe-voice.ts +1076 -0
  1532. package/src/server/voice-providers-status.ts +118 -0
  1533. package/src/server/workspace-oauth.ts +31 -0
  1534. package/src/server/workspace-provider-oauth.ts +970 -0
  1535. package/src/session-replay-iframe-protocol.ts +58 -0
  1536. package/src/settings/handlers.ts +44 -0
  1537. package/src/settings/index.ts +34 -0
  1538. package/src/settings/org-settings.ts +83 -0
  1539. package/src/settings/script-helpers.ts +24 -0
  1540. package/src/settings/store.ts +247 -0
  1541. package/src/settings/user-settings.ts +47 -0
  1542. package/src/setup-connections/index.ts +90 -0
  1543. package/src/setup-connections/onboarding.ts +47 -0
  1544. package/src/shared/agent-access.ts +112 -0
  1545. package/src/shared/agent-chat.ts +124 -0
  1546. package/src/shared/agent-env.ts +46 -0
  1547. package/src/shared/agent-readable-resource.ts +111 -0
  1548. package/src/shared/agent-sidebar-url.ts +39 -0
  1549. package/src/shared/cache-control.ts +25 -0
  1550. package/src/shared/cloudflare-globals.d.ts +34 -0
  1551. package/src/shared/context-xray.ts +172 -0
  1552. package/src/shared/embed-auth.ts +6 -0
  1553. package/src/shared/index.ts +83 -0
  1554. package/src/shared/llm-connection.ts +42 -0
  1555. package/src/shared/markdown-block-split.ts +129 -0
  1556. package/src/shared/mcp-connect-content.ts +130 -0
  1557. package/src/shared/mcp-embed-headers.ts +175 -0
  1558. package/src/shared/mcp-integration-config.ts +101 -0
  1559. package/src/shared/merge-by-id.ts +14 -0
  1560. package/src/shared/oauth-state.ts +39 -0
  1561. package/src/shared/reasoning-effort.ts +228 -0
  1562. package/src/shared/route-warmup-config.ts +128 -0
  1563. package/src/shared/runtime.ts +31 -0
  1564. package/src/shared/social-meta.ts +82 -0
  1565. package/src/shared/streaming-text-smoothing.ts +184 -0
  1566. package/src/shared/truncate.ts +13 -0
  1567. package/src/shared/workspace-app-audience.ts +177 -0
  1568. package/src/shared/workspace-app-id.ts +54 -0
  1569. package/src/sharing/access.ts +585 -0
  1570. package/src/sharing/actions/create-agent-resource-link.ts +91 -0
  1571. package/src/sharing/actions/extension-change.ts +22 -0
  1572. package/src/sharing/actions/list-resource-shares.ts +93 -0
  1573. package/src/sharing/actions/set-resource-visibility.ts +91 -0
  1574. package/src/sharing/actions/share-resource.ts +322 -0
  1575. package/src/sharing/actions/unshare-resource.ts +72 -0
  1576. package/src/sharing/index.ts +35 -0
  1577. package/src/sharing/registry.ts +201 -0
  1578. package/src/sharing/schema.ts +99 -0
  1579. package/src/styles/agent-conversation.css +766 -0
  1580. package/src/styles/agent-native.css +938 -0
  1581. package/src/styles/blocks.css +1717 -0
  1582. package/src/styles/chat-history-list.css +1 -0
  1583. package/src/styles/rich-markdown-editor.css +1 -0
  1584. package/src/tailwind.preset.ts +140 -0
  1585. package/src/templates/chat/.agents/skills/agent-native-docs/SKILL.md +15 -13
  1586. package/src/templates/chat/.agents/skills/customizing-agent-native/SKILL.md +63 -20
  1587. package/src/templates/chat/.agents/skills/self-modifying-code/SKILL.md +9 -6
  1588. package/src/templates/chat/AGENTS.md +3 -2
  1589. package/src/templates/default/.agents/skills/agent-native-docs/SKILL.md +15 -13
  1590. package/src/templates/default/.agents/skills/customizing-agent-native/SKILL.md +63 -20
  1591. package/src/templates/default/.agents/skills/self-modifying-code/SKILL.md +9 -6
  1592. package/src/templates/default/AGENTS.md +6 -5
  1593. package/src/templates/headless/.agents/skills/agent-native-docs/SKILL.md +15 -13
  1594. package/src/templates/headless/.agents/skills/customizing-agent-native/SKILL.md +63 -20
  1595. package/src/templates/headless/AGENTS.md +5 -4
  1596. package/src/templates/workspace-core/.agents/skills/agent-native-docs/SKILL.md +15 -13
  1597. package/src/templates/workspace-core/.agents/skills/agent-native-toolkit/SKILL.md +3 -2
  1598. package/src/templates/workspace-core/.agents/skills/customizing-agent-native/SKILL.md +63 -20
  1599. package/src/templates/workspace-core/.agents/skills/self-modifying-code/SKILL.md +9 -6
  1600. package/src/templates/workspace-core/AGENTS.md +4 -4
  1601. package/src/templates/workspace-root/AGENTS.md +4 -4
  1602. package/src/terminal/cli-registry.ts +57 -0
  1603. package/src/terminal/index.ts +22 -0
  1604. package/src/terminal/pty-server.ts +389 -0
  1605. package/src/terminal/terminal-plugin.ts +265 -0
  1606. package/src/testing.ts +23 -0
  1607. package/src/tracking/index.ts +10 -0
  1608. package/src/tracking/providers.ts +422 -0
  1609. package/src/tracking/registry.ts +102 -0
  1610. package/src/tracking/route.ts +108 -0
  1611. package/src/tracking/types.ts +16 -0
  1612. package/src/transcription/builder-transcription.ts +118 -0
  1613. package/src/triggers/actions/list-automations.ts +99 -0
  1614. package/src/triggers/actions/manage-automation.ts +78 -0
  1615. package/src/triggers/actions.ts +333 -0
  1616. package/src/triggers/condition-evaluator.ts +163 -0
  1617. package/src/triggers/dispatcher.ts +536 -0
  1618. package/src/triggers/index.ts +13 -0
  1619. package/src/triggers/routes.ts +322 -0
  1620. package/src/triggers/types.ts +42 -0
  1621. package/src/types/pglite.d.ts +26 -0
  1622. package/src/usage/store.ts +698 -0
  1623. package/src/vite/action-types-plugin.ts +481 -0
  1624. package/src/vite/agent-web-plugin.ts +78 -0
  1625. package/src/vite/agents-bundle-plugin.ts +185 -0
  1626. package/src/vite/client.ts +3056 -0
  1627. package/src/vite/index.ts +21 -0
  1628. package/src/voice/index.ts +18 -0
  1629. package/src/voice/voice-cleanup-prompt.ts +38 -0
  1630. package/src/voice/voice-context.ts +217 -0
  1631. package/src/voice/voice-replacements.ts +75 -0
  1632. package/src/workspace-connections/credentials.ts +603 -0
  1633. package/src/workspace-connections/index.ts +72 -0
  1634. package/src/workspace-connections/lifecycle.ts +43 -0
  1635. package/src/workspace-connections/store.ts +1943 -0
  1636. package/src/workspace-files/index.ts +18 -0
  1637. package/src/workspace-files/store.ts +339 -0
  1638. package/src/workspace-files/tool.ts +266 -0
@@ -0,0 +1,3417 @@
1
+ import {
2
+ SESSION_REPLAY_IFRAME_ATTRIBUTE,
3
+ SESSION_REPLAY_IFRAME_PROBE,
4
+ SESSION_REPLAY_IFRAME_START,
5
+ SESSION_REPLAY_IFRAME_STOP,
6
+ type SessionReplayIframePrivacyOptions,
7
+ type SessionReplayIframeStartMessage,
8
+ type SessionReplayIframeStopMessage,
9
+ } from "../session-replay-iframe-protocol.js";
10
+ import {
11
+ getOrCreateAnalyticsAnonymousId,
12
+ getOrCreateAnalyticsSessionId,
13
+ } from "./analytics-session.js";
14
+ import { scrubUrl } from "./url-scrub.js";
15
+
16
+ type ReplayEvent = Record<string, unknown>;
17
+ type QueuedReplayEvent = {
18
+ json: string;
19
+ byteLength: number;
20
+ timestampMs: number;
21
+ type: number | null;
22
+ };
23
+ type ReplayStopFn = () => void;
24
+ type ReplayResourceNode = {
25
+ tagName: string;
26
+ rel: string;
27
+ as: string;
28
+ type: string;
29
+ };
30
+ export type SessionReplayUrlMatcher =
31
+ | string
32
+ | RegExp
33
+ | ((url: string) => boolean);
34
+
35
+ interface RrwebRecordOptions {
36
+ emit: (event: ReplayEvent) => void;
37
+ checkoutEveryNth?: number;
38
+ checkoutEveryNms?: number;
39
+ inlineStylesheet?: boolean;
40
+ blockClass?: string | RegExp;
41
+ blockSelector?: string;
42
+ ignoreClass?: string | RegExp;
43
+ ignoreSelector?: string;
44
+ maskTextClass?: string | RegExp;
45
+ maskTextSelector?: string;
46
+ maskAllInputs?: boolean;
47
+ maskInputOptions?: Record<string, boolean>;
48
+ recordCanvas?: boolean;
49
+ recordCrossOriginIframes?: boolean;
50
+ collectFonts?: boolean;
51
+ inlineImages?: boolean;
52
+ sampling?: Record<string, unknown>;
53
+ }
54
+
55
+ type RrwebRecordFn = ((
56
+ options: RrwebRecordOptions,
57
+ ) => ReplayStopFn | undefined) & {
58
+ addCustomEvent?: (tag: string, payload: unknown) => void;
59
+ };
60
+
61
+ interface RrwebRecordModule {
62
+ record: RrwebRecordFn;
63
+ }
64
+
65
+ interface SessionReplayState {
66
+ active: boolean;
67
+ startPromise: Promise<SessionReplayStartResult> | null;
68
+ /** Invalidates deferred recorder startup when stop is requested mid-start. */
69
+ startGeneration: number;
70
+ replayId: string | null;
71
+ startedAtMs: number | null;
72
+ replayLinkBaseUrl: string | null;
73
+ sequence: number;
74
+ /** Pre-serialized + scrubbed event JSON strings, ready to splice at flush. */
75
+ queue: QueuedReplayEvent[];
76
+ queuedBytes: number;
77
+ retryBatches: QueuedReplayEvent[][];
78
+ /** Consecutive retryable 4xx responses for the current recording episode. */
79
+ transientClientErrorFailures: number;
80
+ flushTimer: number | null;
81
+ maxDurationTimer: number | null;
82
+ flushing: boolean;
83
+ /** Highest-priority flush requested while an upload was already in flight. */
84
+ pendingFlushReason: string | null;
85
+ /** Callers awaiting the coalesced flush tail. */
86
+ pendingFlushWaiters: Array<() => void>;
87
+ /** Drop incremental events until a new FullSnapshot can re-anchor the DOM. */
88
+ awaitingFullSnapshot: boolean;
89
+ stopRecorder: ReplayStopFn | null;
90
+ restoreUrlMonitor: (() => void) | null;
91
+ removeLifecycleListeners: (() => void) | null;
92
+ /** Stops the cooperative child-iframe bridge and notifies active children. */
93
+ restoreIframeBridge: (() => void) | null;
94
+ /** rrweb's `record.addCustomEvent`, captured at start (null when absent). */
95
+ addCustomEvent: ((tag: string, payload: unknown) => void) | null;
96
+ /** Uninstalls console/network interceptors and flushes pending duplicates. */
97
+ restoreCaptures: (() => void) | null;
98
+ options: NormalizedSessionReplayOptions | null;
99
+ lastAuthenticatedProperties: Record<string, unknown> | null;
100
+ /** Resource tag metadata used to classify later rrweb attribute mutations. */
101
+ resourceNodes: Map<number, ReplayResourceNode>;
102
+ /**
103
+ * Prevents a permanently misconfigured endpoint from causing an automatic
104
+ * 409 -> restart -> 409 loop. A successful upload resets the allowance, so
105
+ * a later, independent sequence conflict can still recover in-place.
106
+ */
107
+ automaticConflictRestartAttempted: boolean;
108
+ /**
109
+ * Cross-tab "who is recording this replayId" channel, open for the
110
+ * lifetime of an active recording. See `getOrCreateReplaySession` for why
111
+ * this exists (duplicated-tab guard against a shared `replayId`).
112
+ */
113
+ broadcastChannel: BroadcastChannel | null;
114
+ }
115
+
116
+ interface StoredReplaySession {
117
+ sessionId?: string;
118
+ replayId?: string;
119
+ startedAtMs?: number;
120
+ sequence?: number;
121
+ linkBaseUrl?: string;
122
+ }
123
+
124
+ /** Broadcast on `SESSION_REPLAY_BROADCAST_CHANNEL_NAME` by a tab resuming a
125
+ * stored replay session, to check whether another tab is already recording
126
+ * that same `replayId` (see the duplicated-tab guard in
127
+ * `getOrCreateReplaySession`'s doc comment). */
128
+ interface ReplayClaimMessage {
129
+ type: "an-replay-claim";
130
+ replayId: string;
131
+ instanceNonce: string;
132
+ }
133
+
134
+ /** Reply from a tab that is actively recording the claimed `replayId`. */
135
+ interface ReplayClaimTakenMessage {
136
+ type: "an-replay-claim-taken";
137
+ replayId: string;
138
+ /** Only the claimant with this nonce should yield the stored replay id.
139
+ * Optional while older recorder bundles can still be open during rollout. */
140
+ claimantNonce?: string;
141
+ }
142
+
143
+ type ReplayBroadcastMessage = ReplayClaimMessage | ReplayClaimTakenMessage;
144
+
145
+ /** rrweb `sampling` shape (mousemove/scroll/media throttles, input strategy). */
146
+ export type ReplayEventSampling = Record<string, unknown>;
147
+
148
+ /**
149
+ * Console capture cap overrides. `maxEvents` bounds the number of
150
+ * `agent-native.console` custom events emitted per recording session
151
+ * (default 1000); once hit, capture stops for the rest of the session and one
152
+ * final truncation-notice event is emitted.
153
+ */
154
+ export interface SessionReplayConsoleOptions {
155
+ maxEvents?: number;
156
+ }
157
+
158
+ /**
159
+ * Network capture cap overrides. `maxEvents` bounds the number of
160
+ * `agent-native.network` custom events emitted per recording session
161
+ * (default 2000); once hit, capture stops for the rest of the session and one
162
+ * final truncation-notice event is emitted.
163
+ *
164
+ * `captureErrorBodies` (default true) additionally captures a bounded,
165
+ * redacted response-body snippet for 5xx responses only -- request bodies
166
+ * and headers are never captured, and non-5xx/network-failure responses
167
+ * never carry a body. `maxErrorBodyLength` (default 2048) caps that snippet.
168
+ */
169
+ export interface SessionReplayNetworkOptions {
170
+ maxEvents?: number;
171
+ captureErrorBodies?: boolean;
172
+ maxErrorBodyLength?: number;
173
+ }
174
+
175
+ export interface SessionReplayUploadRejectedDetails {
176
+ status: number;
177
+ restartAttempted: boolean;
178
+ restartSucceeded: boolean;
179
+ restartReason?: SessionReplayStartResult["reason"];
180
+ }
181
+
182
+ export interface SessionReplayOptions {
183
+ enabled?: boolean;
184
+ /** Rechecked immediately before rrweb starts to cancel deferred startup. */
185
+ shouldStart?: () => boolean;
186
+ publicKey?: string;
187
+ endpoint?: string;
188
+ /** Analytics app origin used to build timestamped replay links. */
189
+ linkBaseUrl?: string;
190
+ requireSignedInUser?: boolean;
191
+ sampleRate?: number;
192
+ samplingSalt?: string;
193
+ allowUrls?: SessionReplayUrlMatcher[];
194
+ blockUrls?: SessionReplayUrlMatcher[];
195
+ flushIntervalMs?: number;
196
+ maxDurationMs?: number;
197
+ maxEventsPerBatch?: number;
198
+ maxBatchBytes?: number;
199
+ checkoutEveryNth?: number;
200
+ checkoutEveryNms?: number;
201
+ inlineStylesheet?: boolean;
202
+ blockSelector?: string;
203
+ ignoreSelector?: string;
204
+ maskTextClass?: string | RegExp;
205
+ maskTextSelector?: string;
206
+ maskAllInputs?: boolean;
207
+ recordCanvas?: boolean;
208
+ recordCrossOriginIframes?: boolean;
209
+ collectFonts?: boolean;
210
+ inlineImages?: boolean;
211
+ /**
212
+ * rrweb per-event throttling (distinct from `sampleRate`, which decides
213
+ * whether a whole session records). Throttles high-frequency event types
214
+ * (mousemove/scroll/input) to keep the recorded page responsive and the
215
+ * payloads small. Passed straight through to `rrweb.record({ sampling })`.
216
+ */
217
+ eventSampling?: ReplayEventSampling;
218
+ /**
219
+ * Capture console.log/info/warn/error/debug plus window `error` and
220
+ * `unhandledrejection` events as `agent-native.console` custom rrweb
221
+ * events. Defaults to on whenever session replay is enabled. Pass `false`
222
+ * to disable, or an options object to override caps.
223
+ */
224
+ console?: boolean | SessionReplayConsoleOptions;
225
+ /**
226
+ * Capture fetch/XHR requests as `agent-native.network` custom rrweb
227
+ * events (method, URL, status, timing). Request bodies and headers are
228
+ * never captured; response bodies are captured only as a bounded,
229
+ * redacted snippet for 5xx responses (see `captureErrorBodies`).
230
+ * Defaults to on whenever session replay is enabled. Pass `false` to
231
+ * disable, or an options object to override caps.
232
+ */
233
+ network?: boolean | SessionReplayNetworkOptions;
234
+ /** Rare recorder lifecycle signal; never includes replay content or URLs. */
235
+ onUploadRejected?: (details: SessionReplayUploadRejectedDetails) => void;
236
+ extraProperties?:
237
+ | Record<string, unknown>
238
+ | (() => Record<string, unknown> | undefined);
239
+ }
240
+
241
+ export interface SessionReplayContext {
242
+ replayId: string;
243
+ sessionId: string;
244
+ startedAtMs: number;
245
+ startedAt: string;
246
+ linkBaseUrl: string | null;
247
+ active: boolean;
248
+ }
249
+
250
+ export interface SessionReplayLinkOptions {
251
+ /** Event time to seek to when the replay link opens. */
252
+ at?: Date | number | string;
253
+ /** Overrides the configured Analytics app origin for this link. */
254
+ linkBaseUrl?: string;
255
+ }
256
+
257
+ export interface SessionReplayStartResult {
258
+ started: boolean;
259
+ reason?:
260
+ | "disabled"
261
+ | "not-browser"
262
+ | "missing-public-key"
263
+ | "missing-session-id"
264
+ | "missing-user-id"
265
+ | "sampled-out"
266
+ | "url-blocked"
267
+ | "already-active"
268
+ | "import-failed"
269
+ | "record-failed";
270
+ replayId?: string;
271
+ sessionId?: string;
272
+ sampled?: boolean;
273
+ }
274
+
275
+ interface NormalizedSessionReplayOptions {
276
+ publicKey: string;
277
+ endpoint: string;
278
+ linkBaseUrl: string | null;
279
+ requireSignedInUser: boolean;
280
+ sampleRate: number;
281
+ samplingSalt: string;
282
+ allowUrls: SessionReplayUrlMatcher[];
283
+ blockUrls: SessionReplayUrlMatcher[];
284
+ flushIntervalMs: number;
285
+ maxDurationMs: number;
286
+ maxEventsPerBatch: number;
287
+ maxBatchBytes: number;
288
+ checkoutEveryNth?: number;
289
+ checkoutEveryNms?: number;
290
+ inlineStylesheet: boolean;
291
+ blockSelector: string;
292
+ ignoreSelector: string;
293
+ maskTextClass: string | RegExp;
294
+ maskTextSelector: string;
295
+ maskAllInputs: boolean;
296
+ recordCanvas: boolean;
297
+ recordCrossOriginIframes: boolean;
298
+ collectFonts: boolean;
299
+ inlineImages: boolean;
300
+ eventSampling: ReplayEventSampling;
301
+ /** Null disables console capture entirely. */
302
+ console: NormalizedCaptureOptions | null;
303
+ /** Null disables network capture entirely. */
304
+ network: NormalizedCaptureOptions | null;
305
+ onUploadRejected?: SessionReplayOptions["onUploadRejected"];
306
+ extraProperties?: SessionReplayOptions["extraProperties"];
307
+ shouldStart?: SessionReplayOptions["shouldStart"];
308
+ }
309
+
310
+ interface NormalizedCaptureOptions {
311
+ maxEvents: number;
312
+ /** Network-only: capture a bounded 5xx response-body snippet. Unused by console. */
313
+ captureErrorBodies?: boolean;
314
+ /** Network-only: cap (chars) for the captured error-body snippet. */
315
+ maxErrorBodyLength?: number;
316
+ }
317
+
318
+ const DEFAULT_REPLAY_PATH = "/api/analytics/replay";
319
+ const DEFAULT_SAMPLING_SALT = "agent-native-session-replay";
320
+
321
+ /**
322
+ * Default rrweb event throttling. Without this, rrweb captures every
323
+ * mousemove/scroll which dominates event volume and main-thread cost on
324
+ * interactive pages. These caps keep replays faithful while staying light.
325
+ */
326
+ const DEFAULT_EVENT_SAMPLING: ReplayEventSampling = {
327
+ mousemove: 50,
328
+ mouseInteraction: true,
329
+ scroll: 100,
330
+ media: 800,
331
+ input: "last",
332
+ };
333
+ const SESSION_REPLAY_STATE_KEY = Symbol.for(
334
+ "agent-native.client.sessionReplay",
335
+ );
336
+ const SESSION_REPLAY_ID_STORAGE_KEY = "agent-native.session_replay_id";
337
+ const SESSION_REPLAY_IFRAME_BLOCK_SELECTOR = `iframe[${SESSION_REPLAY_IFRAME_ATTRIBUTE}]`;
338
+ const DEFAULT_BLOCK_SELECTOR = [
339
+ SESSION_REPLAY_IFRAME_BLOCK_SELECTOR,
340
+ "[data-sensitive]",
341
+ "[data-an-block]",
342
+ "[data-an-private]",
343
+ "[data-private]",
344
+ ".an-block",
345
+ ".an-replay-block",
346
+ ".an-private",
347
+ ".rr-block",
348
+ "[autocomplete='cc-number']",
349
+ "[autocomplete='cc-csc']",
350
+ "[autocomplete='cc-exp']",
351
+ "[name*='password' i]",
352
+ "[name*='credit' i]",
353
+ "[name*='card' i]",
354
+ "[name*='ssn' i]",
355
+ ].join(", ");
356
+ const DEFAULT_IGNORE_SELECTOR = ".an-ignore, [data-an-ignore]";
357
+ const DEFAULT_MASK_TEXT_CLASS = "an-mask";
358
+ const DEFAULT_MASK_TEXT_SELECTOR = "[data-an-mask]";
359
+ const DEFAULT_MASK_INPUT_OPTIONS: Record<string, boolean> = {
360
+ color: true,
361
+ date: true,
362
+ "datetime-local": true,
363
+ email: true,
364
+ month: true,
365
+ number: true,
366
+ password: true,
367
+ range: true,
368
+ search: true,
369
+ tel: true,
370
+ text: true,
371
+ time: true,
372
+ url: true,
373
+ week: true,
374
+ };
375
+ const DEFAULT_FLUSH_INTERVAL_MS = 5000;
376
+ const DEFAULT_MAX_DURATION_MS = 30 * 60 * 1000;
377
+ const DEFAULT_MAX_EVENTS_PER_BATCH = 50;
378
+ const DEFAULT_MAX_BATCH_BYTES = 256 * 1024;
379
+ const MAX_KEEPALIVE_REPLAY_UPLOAD_BYTES = 60 * 1024;
380
+ const REPLAY_TEXT_ENCODER =
381
+ typeof TextEncoder !== "undefined" ? new TextEncoder() : null;
382
+ const RRWEB_FULL_SNAPSHOT_EVENT_TYPE = 2;
383
+ /** Cross-tab channel name used by the duplicated-tab claim guard. */
384
+ const SESSION_REPLAY_BROADCAST_CHANNEL_NAME = "agent-native-session-replay";
385
+ /** How long a resuming tab waits for a "someone else already owns this
386
+ * replayId" reply before proceeding to record with the resumed id. */
387
+ const SESSION_REPLAY_CLAIM_TIMEOUT_MS = 150;
388
+
389
+ /** rrweb custom-event tag for captured console/window-error entries. */
390
+ export const SESSION_REPLAY_CONSOLE_EVENT_TAG = "agent-native.console";
391
+ /** rrweb custom-event tag for captured fetch/XHR request summaries. */
392
+ export const SESSION_REPLAY_NETWORK_EVENT_TAG = "agent-native.network";
393
+ /** Content-free agent-chat lifecycle markers for replay incident forensics. */
394
+ export const SESSION_REPLAY_AGENT_CHAT_EVENT_TAG = "agent-native.chat";
395
+
396
+ const DEFAULT_MAX_CONSOLE_EVENTS = 1000;
397
+ const DEFAULT_MAX_NETWORK_EVENTS = 2000;
398
+ const MAX_CONSOLE_MESSAGE_LENGTH = 500;
399
+ const MAX_CONSOLE_ARGS = 10;
400
+ const MAX_CONSOLE_STACK_LENGTH = 2000;
401
+ const MAX_CONSOLE_SERIALIZE_DEPTH = 4;
402
+ const MAX_CONSOLE_SERIALIZE_ENTRIES = 20;
403
+ /** Default cap (chars) for a captured 5xx response-body snippet. */
404
+ const DEFAULT_MAX_ERROR_BODY_LENGTH = 2048;
405
+ /** Hard timeout for the async response-body read; emit without body past this. */
406
+ const ERROR_BODY_READ_TIMEOUT_MS = 1500;
407
+
408
+ /**
409
+ * Re-entrancy guard: true while the recorder itself is emitting custom events
410
+ * or flushing (synchronously) so the console/fetch/XHR wrappers never capture
411
+ * the recorder's own work and feed it back into the replay stream.
412
+ */
413
+ let replayCaptureInternal = false;
414
+
415
+ // Credential-looking token redaction for captured console/network text,
416
+ // adapted from the clips template's browser-diagnostics redaction helper.
417
+ const CAPTURE_SECRET_KEY_FRAGMENT =
418
+ "(?:authorization|cookie|set[-_]?cookie|token|secret|password|passwd|pwd|api[-_]?key|apikey|session|credential)";
419
+ const CAPTURE_AUTHORIZATION_SCHEME_RE =
420
+ /\b(authorization)\b(\s*[:=]\s*)(?:bearer|basic)\s+[a-z0-9._~+/-]+=*/gi;
421
+ const CAPTURE_BEARER_RE = /\b(bearer|basic)\s+[a-z0-9._~+/-]+=*/gi;
422
+ const CAPTURE_DOUBLE_QUOTED_SECRET_RE = new RegExp(
423
+ `(["']?)([A-Za-z0-9_$.-]*${CAPTURE_SECRET_KEY_FRAGMENT}[A-Za-z0-9_$.-]*)\\1(\\s*[:=]\\s*)"(?:[^"\\\\]|\\\\.)*"`,
424
+ "gi",
425
+ );
426
+ const CAPTURE_SINGLE_QUOTED_SECRET_RE = new RegExp(
427
+ `(["']?)([A-Za-z0-9_$.-]*${CAPTURE_SECRET_KEY_FRAGMENT}[A-Za-z0-9_$.-]*)\\1(\\s*[:=]\\s*)'(?:[^'\\\\]|\\\\.)*'`,
428
+ "gi",
429
+ );
430
+ const CAPTURE_UNQUOTED_SECRET_RE = new RegExp(
431
+ `(["']?)([A-Za-z0-9_$.-]*${CAPTURE_SECRET_KEY_FRAGMENT}[A-Za-z0-9_$.-]*)\\1(\\s*[:=]\\s*)([^"',\\s;}\\]]+)`,
432
+ "gi",
433
+ );
434
+
435
+ function redactCaptureText(value: string): string {
436
+ return value
437
+ .replace(CAPTURE_AUTHORIZATION_SCHEME_RE, "$1$2<redacted>")
438
+ .replace(CAPTURE_BEARER_RE, "$1 <redacted>")
439
+ .replace(CAPTURE_DOUBLE_QUOTED_SECRET_RE, '$1$2$1$3"<redacted>"')
440
+ .replace(CAPTURE_SINGLE_QUOTED_SECRET_RE, "$1$2$1$3'<redacted>'")
441
+ .replace(CAPTURE_UNQUOTED_SECRET_RE, "$1$2$1$3<redacted>");
442
+ }
443
+ const URL_LIKE_KEYS = new Set([
444
+ "url",
445
+ "uri",
446
+ "href",
447
+ "src",
448
+ "currentUrl",
449
+ "referrer",
450
+ "from",
451
+ "to",
452
+ ]);
453
+
454
+ function getState(): SessionReplayState {
455
+ const g = globalThis as typeof globalThis & {
456
+ [SESSION_REPLAY_STATE_KEY]?: SessionReplayState;
457
+ };
458
+ if (!g[SESSION_REPLAY_STATE_KEY]) {
459
+ g[SESSION_REPLAY_STATE_KEY] = {
460
+ active: false,
461
+ startPromise: null,
462
+ startGeneration: 0,
463
+ replayId: null,
464
+ startedAtMs: null,
465
+ replayLinkBaseUrl: null,
466
+ sequence: 0,
467
+ queue: [],
468
+ queuedBytes: 0,
469
+ retryBatches: [],
470
+ transientClientErrorFailures: 0,
471
+ flushTimer: null,
472
+ maxDurationTimer: null,
473
+ flushing: false,
474
+ pendingFlushReason: null,
475
+ pendingFlushWaiters: [],
476
+ awaitingFullSnapshot: false,
477
+ stopRecorder: null,
478
+ restoreUrlMonitor: null,
479
+ removeLifecycleListeners: null,
480
+ restoreIframeBridge: null,
481
+ addCustomEvent: null,
482
+ restoreCaptures: null,
483
+ options: null,
484
+ lastAuthenticatedProperties: null,
485
+ resourceNodes: new Map(),
486
+ automaticConflictRestartAttempted: false,
487
+ broadcastChannel: null,
488
+ };
489
+ }
490
+ const state = g[SESSION_REPLAY_STATE_KEY]!;
491
+ // Keep Vite HMR safe when an older recorder state survives a module reload.
492
+ state.resourceNodes ??= new Map();
493
+ state.transientClientErrorFailures ??= 0;
494
+ state.restoreIframeBridge ??= null;
495
+ state.pendingFlushReason ??= null;
496
+ state.pendingFlushWaiters ??= [];
497
+ state.awaitingFullSnapshot ??= false;
498
+ state.startGeneration ??= 0;
499
+ state.replayLinkBaseUrl ??= null;
500
+ return state;
501
+ }
502
+
503
+ // The replay session record (replayId + sequence counter) lives in
504
+ // `sessionStorage`, not `localStorage`: `localStorage` is shared by every
505
+ // open tab of the origin, which would hand every tab the same `replayId` and
506
+ // the same sequence counter. See the guard comment above
507
+ // `getOrCreateReplaySession` for the corruption that causes.
508
+ function safeSessionStorageGet(key: string): string | null {
509
+ try {
510
+ return window.sessionStorage.getItem(key);
511
+ } catch {
512
+ return null;
513
+ }
514
+ }
515
+
516
+ function safeSessionStorageSet(key: string, value: string): void {
517
+ try {
518
+ window.sessionStorage.setItem(key, value);
519
+ } catch {
520
+ // private browsing / storage disabled -- replay still works for this page
521
+ }
522
+ }
523
+
524
+ function safeSessionStorageRemove(key: string): void {
525
+ try {
526
+ window.sessionStorage.removeItem(key);
527
+ } catch {
528
+ // private browsing / storage disabled -- replay still works for this page
529
+ }
530
+ }
531
+
532
+ let legacyLocalStorageReplaySessionCleared = false;
533
+
534
+ /**
535
+ * Best-effort, one-time removal of the pre-fix replay session record that
536
+ * used to live in `localStorage`. Never read from it -- adopting its
537
+ * `replayId` would recreate the exact shared-identity bug this file now
538
+ * avoids by using `sessionStorage` instead.
539
+ */
540
+ function clearLegacyLocalStorageReplaySession(): void {
541
+ if (legacyLocalStorageReplaySessionCleared) return;
542
+ legacyLocalStorageReplaySessionCleared = true;
543
+ try {
544
+ window.localStorage.removeItem(SESSION_REPLAY_ID_STORAGE_KEY);
545
+ } catch {
546
+ // best-effort only -- a stray legacy record is harmless once ignored
547
+ }
548
+ }
549
+
550
+ function generateReplayId(): string {
551
+ try {
552
+ if (
553
+ typeof crypto !== "undefined" &&
554
+ typeof crypto.randomUUID === "function"
555
+ ) {
556
+ return crypto.randomUUID();
557
+ }
558
+ } catch {
559
+ // fall through
560
+ }
561
+ return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`;
562
+ }
563
+
564
+ function readStoredReplaySession(): StoredReplaySession | null {
565
+ const raw = safeSessionStorageGet(SESSION_REPLAY_ID_STORAGE_KEY);
566
+ if (!raw) return null;
567
+ try {
568
+ const parsed = JSON.parse(raw) as StoredReplaySession;
569
+ if (!parsed || typeof parsed !== "object") return null;
570
+ return parsed;
571
+ } catch {
572
+ return null;
573
+ }
574
+ }
575
+
576
+ function writeStoredReplaySession(value: StoredReplaySession): void {
577
+ safeSessionStorageSet(SESSION_REPLAY_ID_STORAGE_KEY, JSON.stringify(value));
578
+ }
579
+
580
+ function removeStoredReplaySession(replayId: string): void {
581
+ if (readStoredReplaySession()?.replayId !== replayId) return;
582
+ safeSessionStorageRemove(SESSION_REPLAY_ID_STORAGE_KEY);
583
+ }
584
+
585
+ /**
586
+ * Per-tab replay identity is deliberate -- do not "fix" this by reading or
587
+ * writing the session record through `localStorage` again.
588
+ *
589
+ * `sessionStorage` is scoped to a single tab (and survives reloads/
590
+ * navigations within that tab, which is exactly the lifetime a recording
591
+ * needs). `localStorage` is shared by every open tab of the origin. If this
592
+ * record lived there, two tabs open to the same app would read/write the
593
+ * *same* `replayId` and the *same* sequence counter, so rrweb in each tab
594
+ * would record independently but upload chunks under one shared identity.
595
+ * The two interleaved DOM mutation streams get merged into a single
596
+ * recording server-side: mutations reference the other tab's node ids
597
+ * (broken CSS), the viewport/meta events reflect whichever tab resized last
598
+ * (wrong or ultra-wide viewport), and lost mousemove batches from the
599
+ * "other" tab's chunks read as a frozen cursor or a fake inactivity gap. A
600
+ * chunk-sequence collision with a different checksum gets rejected
601
+ * server-side (409) rather than merged, so one tab's batches are silently
602
+ * dropped -- there is no way to reconstruct or repair this at playback time.
603
+ * Keep this per-tab. A tab *duplicated* mid-session still shares a
604
+ * `sessionStorage` snapshot, which is what the `BroadcastChannel` claim
605
+ * check in `startSessionReplayRecorder` guards against.
606
+ */
607
+ function getOrCreateReplaySession(
608
+ sessionId: string,
609
+ linkBaseUrl?: string | null,
610
+ ): {
611
+ replayId: string;
612
+ startedAtMs: number;
613
+ sequence: number;
614
+ linkBaseUrl?: string;
615
+ /** True when resuming a session found in this tab's `sessionStorage`
616
+ * (e.g. a reload), as opposed to minting a brand-new id. Only the resumed
617
+ * case needs the duplicated-tab claim check -- a fresh id can never
618
+ * collide with anything already recording. */
619
+ resumed: boolean;
620
+ } {
621
+ clearLegacyLocalStorageReplaySession();
622
+ const parsed = readStoredReplaySession();
623
+ if (parsed?.sessionId === sessionId && parsed.replayId) {
624
+ const startedAtMs =
625
+ typeof parsed.startedAtMs === "number" &&
626
+ Number.isFinite(parsed.startedAtMs) &&
627
+ parsed.startedAtMs > 0
628
+ ? parsed.startedAtMs
629
+ : Date.now();
630
+ const sequence =
631
+ typeof parsed.sequence === "number" &&
632
+ Number.isFinite(parsed.sequence) &&
633
+ parsed.sequence >= 0
634
+ ? Math.floor(parsed.sequence)
635
+ : 0;
636
+ const resolvedLinkBaseUrl = linkBaseUrl ?? parsed.linkBaseUrl;
637
+ if (linkBaseUrl && parsed.linkBaseUrl !== linkBaseUrl) {
638
+ writeStoredReplaySession({ ...parsed, linkBaseUrl });
639
+ }
640
+ return {
641
+ replayId: parsed.replayId,
642
+ startedAtMs,
643
+ sequence,
644
+ ...(resolvedLinkBaseUrl ? { linkBaseUrl: resolvedLinkBaseUrl } : {}),
645
+ resumed: true,
646
+ };
647
+ }
648
+ const replayId = generateReplayId();
649
+ const startedAtMs = Date.now();
650
+ writeStoredReplaySession({
651
+ sessionId,
652
+ replayId,
653
+ startedAtMs,
654
+ sequence: 0,
655
+ ...(linkBaseUrl ? { linkBaseUrl } : {}),
656
+ });
657
+ return {
658
+ replayId,
659
+ startedAtMs,
660
+ sequence: 0,
661
+ ...(linkBaseUrl ? { linkBaseUrl } : {}),
662
+ resumed: false,
663
+ };
664
+ }
665
+
666
+ /**
667
+ * Open the cross-tab claim channel used to detect a *duplicated* tab (a
668
+ * browser "duplicate tab" or same-origin `window.open` copies
669
+ * `sessionStorage`, so two tabs can legitimately start with the same
670
+ * resumed `replayId`). Returns `null` when `BroadcastChannel` is
671
+ * unavailable -- callers must treat that as "skip the guard", never as an
672
+ * error.
673
+ */
674
+ function openReplayBroadcastChannel(): BroadcastChannel | null {
675
+ if (typeof BroadcastChannel === "undefined") return null;
676
+ try {
677
+ return new BroadcastChannel(SESSION_REPLAY_BROADCAST_CHANNEL_NAME);
678
+ } catch {
679
+ return null;
680
+ }
681
+ }
682
+
683
+ /**
684
+ * Wires up the duplicated-tab claim channel for one recorder lifetime.
685
+ * `respond` keeps listening for the whole life of the channel (any tab may
686
+ * later claim the `replayId` this tab is actively recording); `probeClaim`
687
+ * is used once, only when resuming a stored session, to ask "is anyone else
688
+ * already recording this id?" and wait up to
689
+ * `SESSION_REPLAY_CLAIM_TIMEOUT_MS` for a reply.
690
+ */
691
+ function createReplayClaimChannel(
692
+ state: SessionReplayState,
693
+ instanceNonce: string,
694
+ ): {
695
+ channel: BroadcastChannel | null;
696
+ probeClaim: (replayId: string) => Promise<boolean>;
697
+ } {
698
+ const channel = openReplayBroadcastChannel();
699
+ if (!channel) {
700
+ return { channel: null, probeClaim: async () => false };
701
+ }
702
+ let pending: {
703
+ replayId: string;
704
+ resolve: (taken: boolean) => void;
705
+ timer: number;
706
+ } | null = null;
707
+ channel.onmessage = (event: MessageEvent) => {
708
+ const data = event?.data as ReplayBroadcastMessage | undefined | null;
709
+ if (!data || typeof data !== "object") return;
710
+ if (data.type === "an-replay-claim") {
711
+ if (data.instanceNonce === instanceNonce) return;
712
+ const ownsReplayId = state.active && state.replayId === data.replayId;
713
+ const winsSimultaneousClaim =
714
+ pending?.replayId === data.replayId &&
715
+ instanceNonce.localeCompare(data.instanceNonce) < 0;
716
+ if (!ownsReplayId && !winsSimultaneousClaim) {
717
+ // If both duplicated tabs start simultaneously, deterministically
718
+ // yield to the lower nonce rather than letting both probes time out
719
+ // and record under the copied replay id.
720
+ if (
721
+ pending?.replayId === data.replayId &&
722
+ data.instanceNonce.localeCompare(instanceNonce) < 0
723
+ ) {
724
+ window.clearTimeout(pending.timer);
725
+ const resolve = pending.resolve;
726
+ pending = null;
727
+ resolve(true);
728
+ }
729
+ return;
730
+ }
731
+ try {
732
+ const reply: ReplayClaimTakenMessage = {
733
+ type: "an-replay-claim-taken",
734
+ replayId: data.replayId,
735
+ claimantNonce: data.instanceNonce,
736
+ };
737
+ channel.postMessage(reply);
738
+ } catch {
739
+ // best-effort -- a lost reply just means the duplicate tab resumes
740
+ // recording under the shared id; later 409s still protect the
741
+ // stream from getting corrupted merges.
742
+ }
743
+ return;
744
+ }
745
+ if (data.type === "an-replay-claim-taken") {
746
+ if (
747
+ pending &&
748
+ data.replayId === pending.replayId &&
749
+ (!data.claimantNonce || data.claimantNonce === instanceNonce)
750
+ ) {
751
+ window.clearTimeout(pending.timer);
752
+ const resolve = pending.resolve;
753
+ pending = null;
754
+ resolve(true);
755
+ }
756
+ }
757
+ };
758
+ const probeClaim = (replayId: string): Promise<boolean> =>
759
+ new Promise((resolve) => {
760
+ const timer = window.setTimeout(() => {
761
+ pending = null;
762
+ resolve(false);
763
+ }, SESSION_REPLAY_CLAIM_TIMEOUT_MS);
764
+ pending = { replayId, resolve, timer };
765
+ try {
766
+ const claim: ReplayClaimMessage = {
767
+ type: "an-replay-claim",
768
+ replayId,
769
+ instanceNonce,
770
+ };
771
+ channel.postMessage(claim);
772
+ } catch {
773
+ window.clearTimeout(timer);
774
+ pending = null;
775
+ resolve(false);
776
+ }
777
+ });
778
+ return { channel, probeClaim };
779
+ }
780
+
781
+ function persistReplaySequence(
782
+ sessionId: string,
783
+ replayId: string,
784
+ startedAtMs: number | null,
785
+ sequence: number,
786
+ linkBaseUrl?: string | null,
787
+ ): void {
788
+ const existing = readStoredReplaySession();
789
+ writeStoredReplaySession({
790
+ sessionId,
791
+ replayId,
792
+ startedAtMs: startedAtMs ?? Date.now(),
793
+ sequence,
794
+ ...(linkBaseUrl || existing?.linkBaseUrl
795
+ ? { linkBaseUrl: linkBaseUrl ?? existing?.linkBaseUrl }
796
+ : {}),
797
+ });
798
+ }
799
+
800
+ function clampSamplingRate(value: number | undefined): number {
801
+ if (typeof value !== "number" || Number.isNaN(value)) return 1;
802
+ return Math.min(1, Math.max(0, value));
803
+ }
804
+
805
+ function readEnvString(key: string): string | undefined {
806
+ return (import.meta.env as Record<string, string | undefined>)?.[key]?.trim();
807
+ }
808
+
809
+ function readEnvNumber(key: string): number | undefined {
810
+ const raw = readEnvString(key);
811
+ if (!raw) return undefined;
812
+ const value = Number(raw);
813
+ return Number.isFinite(value) ? value : undefined;
814
+ }
815
+
816
+ function readEnvBoolean(key: string): boolean | undefined {
817
+ const raw = readEnvString(key);
818
+ if (!raw) return undefined;
819
+ if (/^(1|true|yes|on)$/i.test(raw)) return true;
820
+ if (/^(0|false|no|off)$/i.test(raw)) return false;
821
+ return undefined;
822
+ }
823
+
824
+ function readFirstEnvString(keys: string[]): string | undefined {
825
+ for (const key of keys) {
826
+ const value = readEnvString(key);
827
+ if (value) return value;
828
+ }
829
+ return undefined;
830
+ }
831
+
832
+ function readFirstEnvNumber(keys: string[]): number | undefined {
833
+ for (const key of keys) {
834
+ const value = readEnvNumber(key);
835
+ if (value !== undefined) return value;
836
+ }
837
+ return undefined;
838
+ }
839
+
840
+ function replayEndpointFromAnalyticsEndpoint(value: string): string | null {
841
+ try {
842
+ const url = new URL(value);
843
+ if (url.pathname.endsWith("/api/analytics/track")) {
844
+ url.pathname = url.pathname.replace(
845
+ /\/api\/analytics\/track$/,
846
+ "/api/analytics/replay",
847
+ );
848
+ return url.toString();
849
+ }
850
+ if (url.pathname.endsWith("/track")) {
851
+ url.pathname = url.pathname.replace(/\/track$/, "/api/analytics/replay");
852
+ return url.toString();
853
+ }
854
+ } catch {
855
+ // Fall through to the relative path cases below.
856
+ }
857
+ if (value.endsWith("/api/analytics/track")) {
858
+ return value.replace(/\/api\/analytics\/track$/, "/api/analytics/replay");
859
+ }
860
+ if (value.endsWith("/track")) {
861
+ return value.replace(/\/track$/, "/api/analytics/replay");
862
+ }
863
+ return null;
864
+ }
865
+
866
+ function defaultReplayEndpoint(): string {
867
+ const analyticsEndpoint = readEnvString(
868
+ "VITE_AGENT_NATIVE_ANALYTICS_ENDPOINT",
869
+ );
870
+ const derived = analyticsEndpoint
871
+ ? replayEndpointFromAnalyticsEndpoint(analyticsEndpoint)
872
+ : null;
873
+ if (derived) return derived;
874
+ return typeof window !== "undefined"
875
+ ? `${window.location.origin}${DEFAULT_REPLAY_PATH}`
876
+ : DEFAULT_REPLAY_PATH;
877
+ }
878
+
879
+ function normalizeReplayLinkBaseUrl(value?: string): string | null {
880
+ const raw =
881
+ value?.trim() ||
882
+ readEnvString("VITE_AGENT_NATIVE_ANALYTICS_APP_URL") ||
883
+ (typeof window !== "undefined" ? window.location.origin : "");
884
+ if (!raw) return null;
885
+ try {
886
+ return new URL(
887
+ raw,
888
+ typeof window !== "undefined" ? window.location.href : undefined,
889
+ ).origin;
890
+ } catch {
891
+ return null;
892
+ }
893
+ }
894
+
895
+ export function getSessionReplaySamplingScore(
896
+ sessionId: string,
897
+ salt = DEFAULT_SAMPLING_SALT,
898
+ ): number {
899
+ let hash = 2166136261;
900
+ const input = `${salt}:${sessionId}`;
901
+ for (let i = 0; i < input.length; i += 1) {
902
+ hash ^= input.charCodeAt(i);
903
+ hash = Math.imul(hash, 16777619);
904
+ }
905
+ return (hash >>> 0) / 4294967296;
906
+ }
907
+
908
+ export function shouldSampleSessionReplay(
909
+ sessionId: string,
910
+ sampleRate = 1,
911
+ salt = DEFAULT_SAMPLING_SALT,
912
+ ): boolean {
913
+ const rate = clampSamplingRate(sampleRate);
914
+ if (rate <= 0) return false;
915
+ if (rate >= 1) return true;
916
+ return getSessionReplaySamplingScore(sessionId, salt) < rate;
917
+ }
918
+
919
+ function matcherAllows(matcher: SessionReplayUrlMatcher, url: string): boolean {
920
+ if (typeof matcher === "string") return url.includes(matcher);
921
+ if (matcher instanceof RegExp) return matcher.test(url);
922
+ return matcher(url);
923
+ }
924
+
925
+ function isUrlRecordable(
926
+ url: string,
927
+ options: Pick<NormalizedSessionReplayOptions, "allowUrls" | "blockUrls">,
928
+ ): boolean {
929
+ if (options.allowUrls.length > 0) {
930
+ const allowed = options.allowUrls.some((matcher) =>
931
+ matcherAllows(matcher, url),
932
+ );
933
+ if (!allowed) return false;
934
+ }
935
+ return !options.blockUrls.some((matcher) => matcherAllows(matcher, url));
936
+ }
937
+
938
+ function normalizeOptions(
939
+ options: SessionReplayOptions,
940
+ ): NormalizedSessionReplayOptions | null {
941
+ const publicKey =
942
+ options.publicKey ||
943
+ readFirstEnvString([
944
+ "VITE_AGENT_NATIVE_SESSION_REPLAY_PUBLIC_KEY",
945
+ "VITE_SESSION_REPLAY_PUBLIC_KEY",
946
+ ]) ||
947
+ (import.meta.env as Record<string, string | undefined>)
948
+ ?.VITE_AGENT_NATIVE_ANALYTICS_PUBLIC_KEY;
949
+ if (!publicKey) return null;
950
+ const endpoint =
951
+ options.endpoint ||
952
+ readFirstEnvString([
953
+ "VITE_AGENT_NATIVE_ANALYTICS_REPLAY_ENDPOINT",
954
+ "VITE_AGENT_NATIVE_SESSION_REPLAY_ENDPOINT",
955
+ "VITE_SESSION_REPLAY_INGEST_URL",
956
+ ]) ||
957
+ defaultReplayEndpoint();
958
+ return {
959
+ publicKey,
960
+ endpoint,
961
+ linkBaseUrl: normalizeReplayLinkBaseUrl(options.linkBaseUrl),
962
+ requireSignedInUser:
963
+ options.requireSignedInUser ??
964
+ readEnvBoolean("VITE_AGENT_NATIVE_SESSION_REPLAY_REQUIRE_AUTH") ??
965
+ readEnvBoolean("VITE_SESSION_REPLAY_REQUIRE_AUTH") ??
966
+ false,
967
+ sampleRate: clampSamplingRate(
968
+ options.sampleRate ??
969
+ readFirstEnvNumber([
970
+ "VITE_AGENT_NATIVE_SESSION_REPLAY_SAMPLE_RATE",
971
+ "VITE_SESSION_REPLAY_SAMPLE_RATE",
972
+ ]),
973
+ ),
974
+ samplingSalt: options.samplingSalt || DEFAULT_SAMPLING_SALT,
975
+ allowUrls: options.allowUrls ?? [],
976
+ blockUrls: options.blockUrls ?? [],
977
+ flushIntervalMs: Math.max(
978
+ 250,
979
+ options.flushIntervalMs ??
980
+ readFirstEnvNumber([
981
+ "VITE_AGENT_NATIVE_SESSION_REPLAY_CHUNK_INTERVAL_MS",
982
+ "VITE_SESSION_REPLAY_CHUNK_INTERVAL_MS",
983
+ ]) ??
984
+ DEFAULT_FLUSH_INTERVAL_MS,
985
+ ),
986
+ maxDurationMs: Math.max(
987
+ 1000,
988
+ options.maxDurationMs ??
989
+ readFirstEnvNumber([
990
+ "VITE_AGENT_NATIVE_SESSION_REPLAY_MAX_DURATION_MS",
991
+ "VITE_SESSION_REPLAY_MAX_DURATION_MS",
992
+ ]) ??
993
+ DEFAULT_MAX_DURATION_MS,
994
+ ),
995
+ maxEventsPerBatch: Math.max(
996
+ 1,
997
+ options.maxEventsPerBatch ?? DEFAULT_MAX_EVENTS_PER_BATCH,
998
+ ),
999
+ maxBatchBytes: Math.max(
1000
+ 1024,
1001
+ options.maxBatchBytes ??
1002
+ readFirstEnvNumber([
1003
+ "VITE_AGENT_NATIVE_SESSION_REPLAY_CHUNK_MAX_BYTES",
1004
+ "VITE_SESSION_REPLAY_CHUNK_MAX_BYTES",
1005
+ ]) ??
1006
+ DEFAULT_MAX_BATCH_BYTES,
1007
+ ),
1008
+ checkoutEveryNth: options.checkoutEveryNth,
1009
+ checkoutEveryNms: options.checkoutEveryNms,
1010
+ inlineStylesheet: options.inlineStylesheet ?? true,
1011
+ blockSelector: mergeReplayBlockSelector(
1012
+ options.blockSelector || DEFAULT_BLOCK_SELECTOR,
1013
+ ),
1014
+ ignoreSelector: options.ignoreSelector || DEFAULT_IGNORE_SELECTOR,
1015
+ maskTextClass: options.maskTextClass || DEFAULT_MASK_TEXT_CLASS,
1016
+ maskTextSelector: options.maskTextSelector || DEFAULT_MASK_TEXT_SELECTOR,
1017
+ maskAllInputs: options.maskAllInputs ?? true,
1018
+ recordCanvas: options.recordCanvas ?? false,
1019
+ // A top-level app can safely aggregate cooperative child recorders. An
1020
+ // app embedded by an unrelated cross-origin host must continue emitting
1021
+ // and uploading its own events, so it only forwards to a parent when the
1022
+ // caller explicitly opts in.
1023
+ recordCrossOriginIframes:
1024
+ options.recordCrossOriginIframes ?? window.parent === window,
1025
+ collectFonts: options.collectFonts ?? false,
1026
+ inlineImages: options.inlineImages ?? false,
1027
+ eventSampling: options.eventSampling ?? DEFAULT_EVENT_SAMPLING,
1028
+ console: normalizeCaptureToggle(
1029
+ options.console,
1030
+ DEFAULT_MAX_CONSOLE_EVENTS,
1031
+ ),
1032
+ network: normalizeCaptureToggle(
1033
+ options.network,
1034
+ DEFAULT_MAX_NETWORK_EVENTS,
1035
+ ),
1036
+ onUploadRejected: options.onUploadRejected,
1037
+ extraProperties: options.extraProperties,
1038
+ shouldStart: options.shouldStart,
1039
+ };
1040
+ }
1041
+
1042
+ function mergeReplayBlockSelector(blockSelector: string): string {
1043
+ return blockSelector.includes(SESSION_REPLAY_IFRAME_BLOCK_SELECTOR)
1044
+ ? blockSelector
1045
+ : `${blockSelector}, ${SESSION_REPLAY_IFRAME_BLOCK_SELECTOR}`;
1046
+ }
1047
+
1048
+ /**
1049
+ * Console/network capture default to ON whenever session replay records;
1050
+ * `false` disables a category, an object form overrides its caps. Network
1051
+ * options may additionally carry `captureErrorBodies`/`maxErrorBodyLength`;
1052
+ * console ignores those fields.
1053
+ */
1054
+ function normalizeCaptureToggle(
1055
+ value: boolean | SessionReplayNetworkOptions | undefined,
1056
+ defaultMaxEvents: number,
1057
+ ): NormalizedCaptureOptions | null {
1058
+ if (value === false) return null;
1059
+ const overrides = typeof value === "object" && value !== null ? value : {};
1060
+ const maxEvents =
1061
+ typeof overrides.maxEvents === "number" &&
1062
+ Number.isFinite(overrides.maxEvents)
1063
+ ? Math.max(1, Math.floor(overrides.maxEvents))
1064
+ : defaultMaxEvents;
1065
+ const maxErrorBodyLength =
1066
+ typeof overrides.maxErrorBodyLength === "number" &&
1067
+ Number.isFinite(overrides.maxErrorBodyLength)
1068
+ ? Math.max(0, Math.floor(overrides.maxErrorBodyLength))
1069
+ : DEFAULT_MAX_ERROR_BODY_LENGTH;
1070
+ return {
1071
+ maxEvents,
1072
+ captureErrorBodies: overrides.captureErrorBodies !== false,
1073
+ maxErrorBodyLength,
1074
+ };
1075
+ }
1076
+
1077
+ function scrubStringValue(key: string, value: string): string {
1078
+ const lowerKey = key.toLowerCase();
1079
+ const isUrlKey = URL_LIKE_KEYS.has(key) || URL_LIKE_KEYS.has(lowerKey);
1080
+ if (
1081
+ isUrlKey ||
1082
+ value.startsWith("http://") ||
1083
+ value.startsWith("https://") ||
1084
+ value.startsWith("/")
1085
+ ) {
1086
+ return scrubUrl(value) ?? value;
1087
+ }
1088
+ return value;
1089
+ }
1090
+
1091
+ function scrubReplayValue(
1092
+ value: unknown,
1093
+ key = "",
1094
+ depth = 0,
1095
+ seen = new WeakSet<object>(),
1096
+ ): unknown {
1097
+ if (typeof value === "string") return scrubStringValue(key, value);
1098
+ if (!value || typeof value !== "object") return value;
1099
+ if (depth > 12) return value;
1100
+ if (seen.has(value)) return value;
1101
+ seen.add(value);
1102
+ if (Array.isArray(value)) {
1103
+ return value.map((item) => scrubReplayValue(item, key, depth + 1, seen));
1104
+ }
1105
+ const out: Record<string, unknown> = {};
1106
+ for (const [childKey, childValue] of Object.entries(value)) {
1107
+ out[childKey] = scrubReplayValue(childValue, childKey, depth + 1, seen);
1108
+ }
1109
+ return out;
1110
+ }
1111
+
1112
+ /**
1113
+ * JSON.stringify replacer that scrubs URL-like string values inline. Folding
1114
+ * the scrub into the single serialization pass avoids a separate deep-clone of
1115
+ * every emitted event (FullSnapshots are large DOM trees) on the hot path.
1116
+ */
1117
+ const REPLAY_RESOURCE_LINK_RELS = new Set([
1118
+ "stylesheet",
1119
+ "icon",
1120
+ "apple-touch-icon",
1121
+ "mask-icon",
1122
+ ]);
1123
+ const REPLAY_RESOURCE_PRELOAD_TYPES = new Set([
1124
+ "style",
1125
+ "font",
1126
+ "image",
1127
+ "audio",
1128
+ "video",
1129
+ "track",
1130
+ ]);
1131
+ const REPLAY_RESOURCE_TAGS = new Set([
1132
+ "img",
1133
+ "source",
1134
+ "video",
1135
+ "audio",
1136
+ "track",
1137
+ "input",
1138
+ "link",
1139
+ ]);
1140
+ const NO_REPLAY_RESOURCE_ATTRIBUTES = new Set<string>();
1141
+ const REPLAY_SRC_ATTRIBUTES = new Set(["src"]);
1142
+ const REPLAY_SRCSET_ATTRIBUTES = new Set(["src", "srcset"]);
1143
+ const REPLAY_VIDEO_ATTRIBUTES = new Set(["src", "poster"]);
1144
+ const REPLAY_HREF_ATTRIBUTES = new Set(["href"]);
1145
+
1146
+ function replayAttributeString(
1147
+ attributes: Record<string, unknown>,
1148
+ key: string,
1149
+ ): string {
1150
+ return typeof attributes[key] === "string"
1151
+ ? attributes[key].toLowerCase()
1152
+ : "";
1153
+ }
1154
+
1155
+ function updateReplayResourceNode(
1156
+ current: ReplayResourceNode,
1157
+ attributes: Record<string, unknown>,
1158
+ ): ReplayResourceNode {
1159
+ return {
1160
+ tagName: current.tagName,
1161
+ rel: Object.hasOwn(attributes, "rel")
1162
+ ? replayAttributeString(attributes, "rel")
1163
+ : current.rel,
1164
+ as: Object.hasOwn(attributes, "as")
1165
+ ? replayAttributeString(attributes, "as")
1166
+ : current.as,
1167
+ type: Object.hasOwn(attributes, "type")
1168
+ ? replayAttributeString(attributes, "type")
1169
+ : current.type,
1170
+ };
1171
+ }
1172
+
1173
+ function replayPreservedResourceAttributes(
1174
+ node: ReplayResourceNode,
1175
+ ): ReadonlySet<string> {
1176
+ switch (node.tagName) {
1177
+ case "img":
1178
+ case "source":
1179
+ return REPLAY_SRCSET_ATTRIBUTES;
1180
+ case "video":
1181
+ return REPLAY_VIDEO_ATTRIBUTES;
1182
+ case "audio":
1183
+ case "track":
1184
+ return REPLAY_SRC_ATTRIBUTES;
1185
+ case "input":
1186
+ return node.type === "image"
1187
+ ? REPLAY_SRC_ATTRIBUTES
1188
+ : NO_REPLAY_RESOURCE_ATTRIBUTES;
1189
+ case "link": {
1190
+ const rels = node.rel.split(/\s+/);
1191
+ const isLoadBearingResource =
1192
+ rels.some((rel) => REPLAY_RESOURCE_LINK_RELS.has(rel)) ||
1193
+ (rels.includes("preload") &&
1194
+ REPLAY_RESOURCE_PRELOAD_TYPES.has(node.as));
1195
+ return isLoadBearingResource
1196
+ ? REPLAY_HREF_ATTRIBUTES
1197
+ : NO_REPLAY_RESOURCE_ATTRIBUTES;
1198
+ }
1199
+ default:
1200
+ return NO_REPLAY_RESOURCE_ATTRIBUTES;
1201
+ }
1202
+ }
1203
+
1204
+ /**
1205
+ * Build a path-aware replay serializer without cloning the rrweb event.
1206
+ *
1207
+ * Privacy still wins for Meta/navigation URLs, executable/embed URLs, anchor
1208
+ * hrefs, and custom console/network diagnostics. The narrow exception is
1209
+ * load-bearing stylesheet, font, image, and media attributes: changing those
1210
+ * signed URLs makes rrweb rebuild a page that never existed. JSON.stringify
1211
+ * calls a replacer for an `attributes` object before its children, so the
1212
+ * WeakMap lets the child callback recognize only that bag.
1213
+ */
1214
+ function createReplayScrubReplacer(
1215
+ resourceNodes: Map<number, ReplayResourceNode>,
1216
+ ): (this: unknown, key: string, value: unknown) => unknown {
1217
+ const preservedAttributes = new WeakMap<object, ReadonlySet<string>>();
1218
+
1219
+ return function replayScrubReplacer(
1220
+ this: unknown,
1221
+ key: string,
1222
+ value: unknown,
1223
+ ): unknown {
1224
+ if (key === "attributes" && value && typeof value === "object") {
1225
+ const attributes = value as Record<string, unknown>;
1226
+ const holder =
1227
+ this && typeof this === "object"
1228
+ ? (this as Record<string, unknown>)
1229
+ : undefined;
1230
+ const tagName =
1231
+ typeof holder?.tagName === "string" ? holder.tagName.toLowerCase() : "";
1232
+ const nodeId =
1233
+ typeof holder?.id === "number" && Number.isFinite(holder.id)
1234
+ ? holder.id
1235
+ : undefined;
1236
+ let resourceNode: ReplayResourceNode | undefined;
1237
+ if (tagName && REPLAY_RESOURCE_TAGS.has(tagName)) {
1238
+ resourceNode = updateReplayResourceNode(
1239
+ { tagName, rel: "", as: "", type: "" },
1240
+ attributes,
1241
+ );
1242
+ } else if (nodeId !== undefined && !tagName) {
1243
+ const current = resourceNodes.get(nodeId);
1244
+ if (current) {
1245
+ resourceNode = updateReplayResourceNode(current, attributes);
1246
+ }
1247
+ }
1248
+ if (nodeId !== undefined && resourceNode) {
1249
+ resourceNodes.set(nodeId, resourceNode);
1250
+ }
1251
+
1252
+ const resourceKeys = resourceNode
1253
+ ? replayPreservedResourceAttributes(resourceNode)
1254
+ : NO_REPLAY_RESOURCE_ATTRIBUTES;
1255
+
1256
+ if (resourceKeys.size > 0) preservedAttributes.set(value, resourceKeys);
1257
+ return value;
1258
+ }
1259
+
1260
+ if (
1261
+ typeof value === "string" &&
1262
+ this &&
1263
+ typeof this === "object" &&
1264
+ preservedAttributes.get(this)?.has(key.toLowerCase())
1265
+ ) {
1266
+ return value;
1267
+ }
1268
+ return typeof value === "string" ? scrubStringValue(key, value) : value;
1269
+ };
1270
+ }
1271
+
1272
+ /**
1273
+ * Serialize + scrub one event in a single pass. The resulting string is stored
1274
+ * directly on the queue and reused verbatim at flush, so each event is
1275
+ * stringified exactly once (was: deep-clone + size-stringify + flush-stringify).
1276
+ */
1277
+ function serializeReplayEvent(
1278
+ event: ReplayEvent,
1279
+ resourceNodes: Map<number, ReplayResourceNode>,
1280
+ ): string {
1281
+ try {
1282
+ if (event.type === 2) resourceNodes.clear();
1283
+ return JSON.stringify(event, createReplayScrubReplacer(resourceNodes));
1284
+ } catch {
1285
+ return "";
1286
+ }
1287
+ }
1288
+
1289
+ function replayEventTimestampMs(event: ReplayEvent): number {
1290
+ const timestamp = event.timestamp;
1291
+ if (typeof timestamp === "number" && Number.isFinite(timestamp)) {
1292
+ return timestamp;
1293
+ }
1294
+ if (typeof timestamp === "string" && timestamp.trim()) {
1295
+ const parsed = Date.parse(timestamp);
1296
+ if (Number.isFinite(parsed)) return parsed;
1297
+ }
1298
+ return Date.now();
1299
+ }
1300
+
1301
+ function enqueueReplayEvent(
1302
+ state: SessionReplayState,
1303
+ event: ReplayEvent,
1304
+ ): void {
1305
+ if (!state.options) return;
1306
+ const eventType = typeof event.type === "number" ? event.type : null;
1307
+ if (state.awaitingFullSnapshot) {
1308
+ if (eventType !== RRWEB_FULL_SNAPSHOT_EVENT_TYPE) return;
1309
+ state.awaitingFullSnapshot = false;
1310
+ }
1311
+ const serialized = serializeReplayEvent(event, state.resourceNodes);
1312
+ if (!serialized) return;
1313
+ const estimatedBytes = replaySerializedBytes(serialized);
1314
+ if (
1315
+ state.queue.length > 0 &&
1316
+ state.queuedBytes + estimatedBytes > state.options.maxBatchBytes
1317
+ ) {
1318
+ void flushSessionReplay("max-bytes");
1319
+ }
1320
+ state.queue.push({
1321
+ json: serialized,
1322
+ byteLength: estimatedBytes,
1323
+ timestampMs: replayEventTimestampMs(event),
1324
+ type: eventType,
1325
+ });
1326
+ state.queuedBytes += estimatedBytes;
1327
+ flushQueuedReplayIfNeeded(state);
1328
+ }
1329
+
1330
+ function replayExtraProperties(
1331
+ options: NormalizedSessionReplayOptions,
1332
+ ): Record<string, unknown> | undefined {
1333
+ const source = options.extraProperties;
1334
+ if (!source) return undefined;
1335
+ try {
1336
+ const props = typeof source === "function" ? source() : source;
1337
+ if (!props || typeof props !== "object") return undefined;
1338
+ return scrubReplayValue(props) as Record<string, unknown>;
1339
+ } catch {
1340
+ return undefined;
1341
+ }
1342
+ }
1343
+
1344
+ function replayString(value: unknown): string | undefined {
1345
+ return typeof value === "string" && value.trim() ? value : undefined;
1346
+ }
1347
+
1348
+ function replayEmail(value: unknown): string | undefined {
1349
+ const raw = replayString(value);
1350
+ return raw && raw.includes("@") ? raw : undefined;
1351
+ }
1352
+
1353
+ function replayUserEmail(
1354
+ properties: Record<string, unknown> | undefined,
1355
+ ): string | undefined {
1356
+ return (
1357
+ replayEmail(properties?.userEmail ?? properties?.user_email) ||
1358
+ replayEmail(properties?.email) ||
1359
+ replayEmail(properties?.userId ?? properties?.user_id)
1360
+ );
1361
+ }
1362
+
1363
+ function replayPropertiesForUpload(
1364
+ state: SessionReplayState,
1365
+ options: NormalizedSessionReplayOptions,
1366
+ ): Record<string, unknown> | undefined {
1367
+ const properties = replayExtraProperties(options);
1368
+ if (replayUserEmail(properties)) {
1369
+ state.lastAuthenticatedProperties = properties ? { ...properties } : null;
1370
+ return properties;
1371
+ }
1372
+ if (options.requireSignedInUser && state.lastAuthenticatedProperties) {
1373
+ return state.lastAuthenticatedProperties;
1374
+ }
1375
+ return properties;
1376
+ }
1377
+
1378
+ interface ReplayUploadPayload {
1379
+ body: string;
1380
+ replayId: string;
1381
+ sessionId: string;
1382
+ sequence: number;
1383
+ }
1384
+
1385
+ function buildReplayBody(
1386
+ state: SessionReplayState,
1387
+ reason: string,
1388
+ events: QueuedReplayEvent[],
1389
+ ): ReplayUploadPayload | null {
1390
+ const options = state.options;
1391
+ if (!options || !state.replayId) return null;
1392
+ const sessionId = getOrCreateAnalyticsSessionId();
1393
+ if (!sessionId) return null;
1394
+ const properties = replayPropertiesForUpload(state, options);
1395
+ const userEmail = replayUserEmail(properties);
1396
+ if (options.requireSignedInUser && !userEmail) return null;
1397
+ const userId =
1398
+ userEmail || replayString(properties?.userId ?? properties?.user_id);
1399
+ const eventTimestamps = events.map((event) => event.timestampMs);
1400
+ const nowMs = Date.now();
1401
+ const startedAtMs =
1402
+ state.startedAtMs ??
1403
+ (eventTimestamps.length ? Math.min(...eventTimestamps) : nowMs);
1404
+ const endedAtMs = eventTimestamps.length
1405
+ ? Math.max(...eventTimestamps)
1406
+ : nowMs;
1407
+ const envelope = {
1408
+ publicKey: options.publicKey,
1409
+ type: "session_replay",
1410
+ replayId: state.replayId,
1411
+ sessionId,
1412
+ ...(userId ? { userId } : {}),
1413
+ ...(userEmail ? { userEmail } : {}),
1414
+ anonymousId: getOrCreateAnalyticsAnonymousId(),
1415
+ sequence: state.sequence,
1416
+ reason,
1417
+ status: isFinalFlushReason(reason) ? "completed" : "active",
1418
+ eventCount: events.length,
1419
+ startedAt: new Date(startedAtMs).toISOString(),
1420
+ endedAt: new Date(endedAtMs).toISOString(),
1421
+ durationMs: Math.max(0, endedAtMs - startedAtMs),
1422
+ privacyMode: "mask-inputs-and-selected-text",
1423
+ url:
1424
+ typeof window !== "undefined"
1425
+ ? scrubUrl(window.location.href)
1426
+ : undefined,
1427
+ timestamp: new Date().toISOString(),
1428
+ properties,
1429
+ };
1430
+ // Events are already serialized+scrubbed JSON strings; splice them into the
1431
+ // envelope without re-serializing the (potentially large) events array.
1432
+ const envelopeJson = JSON.stringify(envelope);
1433
+ return {
1434
+ body: `${envelopeJson.slice(0, -1)},"events":[${events
1435
+ .map((event) => event.json)
1436
+ .join(",")}]}`,
1437
+ replayId: state.replayId,
1438
+ sessionId,
1439
+ sequence: state.sequence,
1440
+ };
1441
+ }
1442
+
1443
+ interface ReplayUploadBody {
1444
+ body: BodyInit;
1445
+ headers: Record<string, string>;
1446
+ compressed: boolean;
1447
+ }
1448
+
1449
+ function isCrossOriginReplayEndpoint(endpoint: string): boolean {
1450
+ if (typeof window === "undefined") return false;
1451
+ try {
1452
+ return (
1453
+ new URL(endpoint, window.location.href).origin !== window.location.origin
1454
+ );
1455
+ } catch {
1456
+ return false;
1457
+ }
1458
+ }
1459
+
1460
+ async function gzipReplayBody(body: string): Promise<Blob | null> {
1461
+ if (
1462
+ typeof CompressionStream === "undefined" ||
1463
+ typeof Blob === "undefined" ||
1464
+ typeof Response === "undefined"
1465
+ ) {
1466
+ return null;
1467
+ }
1468
+ try {
1469
+ const stream = new Blob([body], { type: "application/json" })
1470
+ .stream()
1471
+ .pipeThrough(new CompressionStream("gzip"));
1472
+ const compressed = await new Response(stream).arrayBuffer();
1473
+ return new Blob([compressed], { type: "application/octet-stream" });
1474
+ } catch {
1475
+ return null;
1476
+ }
1477
+ }
1478
+
1479
+ async function buildReplayUploadBody(body: string): Promise<ReplayUploadBody> {
1480
+ const compressed = await gzipReplayBody(body);
1481
+ if (compressed) {
1482
+ return {
1483
+ body: compressed,
1484
+ compressed: true,
1485
+ headers: {
1486
+ "Content-Type": "application/octet-stream",
1487
+ "Content-Encoding": "gzip",
1488
+ },
1489
+ };
1490
+ }
1491
+ return {
1492
+ body,
1493
+ compressed: false,
1494
+ headers: {
1495
+ "Content-Type": "text/plain;charset=UTF-8",
1496
+ },
1497
+ };
1498
+ }
1499
+
1500
+ function replayUploadBodyBytes(body: BodyInit): number {
1501
+ if (typeof body === "string") {
1502
+ if (typeof TextEncoder !== "undefined") {
1503
+ return new TextEncoder().encode(body).byteLength;
1504
+ }
1505
+ return body.length;
1506
+ }
1507
+ if (typeof Blob !== "undefined" && body instanceof Blob) {
1508
+ return body.size;
1509
+ }
1510
+ if (body instanceof ArrayBuffer) {
1511
+ return body.byteLength;
1512
+ }
1513
+ if (ArrayBuffer.isView(body)) {
1514
+ return body.byteLength;
1515
+ }
1516
+ return MAX_KEEPALIVE_REPLAY_UPLOAD_BYTES + 1;
1517
+ }
1518
+
1519
+ function canUseReplayKeepalive(body: BodyInit): boolean {
1520
+ return replayUploadBodyBytes(body) <= MAX_KEEPALIVE_REPLAY_UPLOAD_BYTES;
1521
+ }
1522
+
1523
+ /** Thrown by `sendReplayUpload` on a non-ok HTTP response, carrying the
1524
+ * status so `flushSessionReplay` can tell a permanent client rejection
1525
+ * (e.g. a 409 checksum conflict, which can never succeed on retry) apart
1526
+ * from a transient failure worth retrying. */
1527
+ class ReplayUploadHttpError extends Error {
1528
+ readonly status: number;
1529
+ constructor(status: number) {
1530
+ super(`Session replay upload failed with HTTP ${status}`);
1531
+ this.name = "ReplayUploadHttpError";
1532
+ this.status = status;
1533
+ }
1534
+ }
1535
+
1536
+ /** 4xx statuses where retrying the exact same batch can never succeed.
1537
+ * Keep this deliberately narrow: 401/403/404 can be temporary during auth or
1538
+ * deploy transitions, so they get a small retry budget before the rejected
1539
+ * episode is stopped. The budget prevents a persistent configuration failure
1540
+ * from pinning retryBatches while rrweb events grow without bound. */
1541
+ function isDefinitiveReplayUploadClientError(status: number): boolean {
1542
+ return status === 400 || status === 409 || status === 413 || status === 422;
1543
+ }
1544
+
1545
+ const MAX_TRANSIENT_REPLAY_CLIENT_FAILURES = 3;
1546
+
1547
+ function isTransientReplayUploadClientError(status: number): boolean {
1548
+ return status === 401 || status === 403 || status === 404;
1549
+ }
1550
+
1551
+ async function sendReplayUpload(
1552
+ options: NormalizedSessionReplayOptions,
1553
+ body: string,
1554
+ callbacks: { beforeKeepaliveUpload?: () => void } = {},
1555
+ ): Promise<void> {
1556
+ if (isCrossOriginReplayEndpoint(options.endpoint)) {
1557
+ const canUseKeepalive = canUseReplayKeepalive(body);
1558
+ if (canUseKeepalive) callbacks.beforeKeepaliveUpload?.();
1559
+ const response = await fetch(options.endpoint, {
1560
+ method: "POST",
1561
+ body,
1562
+ keepalive: canUseKeepalive,
1563
+ headers: { "Content-Type": "text/plain;charset=UTF-8" },
1564
+ });
1565
+ if (!response.ok) {
1566
+ throw new ReplayUploadHttpError(response.status);
1567
+ }
1568
+ return;
1569
+ }
1570
+
1571
+ const upload = await buildReplayUploadBody(body);
1572
+ const canUseKeepalive = canUseReplayKeepalive(upload.body);
1573
+ if (canUseKeepalive) callbacks.beforeKeepaliveUpload?.();
1574
+ const response = await fetch(options.endpoint, {
1575
+ method: "POST",
1576
+ body: upload.body,
1577
+ keepalive: canUseKeepalive,
1578
+ headers: {
1579
+ ...upload.headers,
1580
+ "X-Agent-Native-Analytics-Key": options.publicKey,
1581
+ },
1582
+ });
1583
+ if (!response.ok) {
1584
+ throw new ReplayUploadHttpError(response.status);
1585
+ }
1586
+ }
1587
+
1588
+ function isFinalFlushReason(reason: string): boolean {
1589
+ return [
1590
+ "auth-cleared",
1591
+ "manual",
1592
+ "pagehide",
1593
+ "beforeunload",
1594
+ "url-blocked",
1595
+ "max-duration",
1596
+ ].includes(reason);
1597
+ }
1598
+
1599
+ function flushReasonPriority(reason: string): number {
1600
+ // Unload reasons must retain their keepalive sequence reservation even when
1601
+ // another final request (for example, an explicit manual stop) is coalesced.
1602
+ if (reason === "pagehide" || reason === "beforeunload") return 3;
1603
+ if (isFinalFlushReason(reason)) return 2;
1604
+ if (reason === "visibility-hidden") return 1;
1605
+ return 0;
1606
+ }
1607
+
1608
+ function mergePendingFlushReason(
1609
+ current: string | null,
1610
+ requested: string,
1611
+ ): string {
1612
+ if (!current) return requested;
1613
+ return flushReasonPriority(requested) > flushReasonPriority(current)
1614
+ ? requested
1615
+ : current;
1616
+ }
1617
+
1618
+ function shouldReserveSequenceBeforeKeepalive(reason: string): boolean {
1619
+ return (
1620
+ reason === "pagehide" ||
1621
+ reason === "beforeunload" ||
1622
+ reason === "visibility-hidden"
1623
+ );
1624
+ }
1625
+
1626
+ function hasFullSnapshot(events: QueuedReplayEvent[]): boolean {
1627
+ return events.some((event) => event.type === RRWEB_FULL_SNAPSHOT_EVENT_TYPE);
1628
+ }
1629
+
1630
+ function hasPendingReplayBatch(state: SessionReplayState): boolean {
1631
+ return state.retryBatches.length > 0 || state.queue.length > 0;
1632
+ }
1633
+
1634
+ function shouldFlushQueuedReplay(state: SessionReplayState): boolean {
1635
+ if (!state.options || state.queue.length === 0) return false;
1636
+ return (
1637
+ hasFullSnapshot(state.queue) ||
1638
+ state.queue.length >= state.options.maxEventsPerBatch ||
1639
+ state.queuedBytes >= state.options.maxBatchBytes
1640
+ );
1641
+ }
1642
+
1643
+ function flushQueuedReplayIfNeeded(state: SessionReplayState): void {
1644
+ const options = state.options;
1645
+ if (!options) return;
1646
+ if (state.retryBatches.length > 0) return;
1647
+ if (!shouldFlushQueuedReplay(state)) return;
1648
+ const reason = hasFullSnapshot(state.queue)
1649
+ ? "full-snapshot"
1650
+ : state.queue.length >= options.maxEventsPerBatch
1651
+ ? "max-events"
1652
+ : "max-bytes";
1653
+ void flushSessionReplay(reason);
1654
+ }
1655
+
1656
+ function queuedReplayBytes(events: QueuedReplayEvent[]): number {
1657
+ return events.reduce(
1658
+ (total, event) => total + queuedReplayEventBytes(event),
1659
+ 0,
1660
+ );
1661
+ }
1662
+
1663
+ function replaySerializedBytes(value: string): number {
1664
+ if (REPLAY_TEXT_ENCODER) return REPLAY_TEXT_ENCODER.encode(value).byteLength;
1665
+ if (typeof Blob !== "undefined") return new Blob([value]).size;
1666
+ return value.length;
1667
+ }
1668
+
1669
+ function queuedReplayEventBytes(event: QueuedReplayEvent): number {
1670
+ return Number.isFinite(event.byteLength)
1671
+ ? event.byteLength
1672
+ : replaySerializedBytes(event.json);
1673
+ }
1674
+
1675
+ /**
1676
+ * Remove one bounded FIFO prefix from the live queue.
1677
+ *
1678
+ * Threshold-triggered flushes may arrive while another upload is active. The
1679
+ * old `queue.splice(0)` drained that entire accumulated backlog on the next
1680
+ * flush, bypassing both byte and event caps. Keep FullSnapshots isolated and
1681
+ * always take at least one event so an individually large snapshot can still
1682
+ * make progress.
1683
+ */
1684
+ function takeQueuedReplayBatch(state: SessionReplayState): QueuedReplayEvent[] {
1685
+ const options = state.options;
1686
+ if (!options || state.queue.length === 0) return [];
1687
+
1688
+ let count = 0;
1689
+ let bytes = 0;
1690
+ for (const event of state.queue) {
1691
+ if (count > 0 && event.type === RRWEB_FULL_SNAPSHOT_EVENT_TYPE) break;
1692
+ if (
1693
+ count > 0 &&
1694
+ (count >= options.maxEventsPerBatch ||
1695
+ bytes + queuedReplayEventBytes(event) > options.maxBatchBytes)
1696
+ ) {
1697
+ break;
1698
+ }
1699
+ count += 1;
1700
+ bytes += queuedReplayEventBytes(event);
1701
+ if (event.type === RRWEB_FULL_SNAPSHOT_EVENT_TYPE) break;
1702
+ }
1703
+
1704
+ const events = state.queue.splice(0, Math.max(1, count));
1705
+ state.queuedBytes = queuedReplayBytes(state.queue);
1706
+ return events;
1707
+ }
1708
+
1709
+ function splitReplayBatch(
1710
+ events: QueuedReplayEvent[],
1711
+ ): [QueuedReplayEvent[], QueuedReplayEvent[]] | null {
1712
+ if (events.length < 2) return null;
1713
+ const targetBytes = queuedReplayBytes(events) / 2;
1714
+ let splitAt = 1;
1715
+ let bytes = events[0] ? queuedReplayEventBytes(events[0]) : 0;
1716
+ while (splitAt < events.length - 1 && bytes < targetBytes) {
1717
+ const event = events[splitAt];
1718
+ if (event) bytes += queuedReplayEventBytes(event);
1719
+ splitAt += 1;
1720
+ }
1721
+ return [events.slice(0, splitAt), events.slice(splitAt)];
1722
+ }
1723
+
1724
+ function replayBatchNeedsDomReset(events: QueuedReplayEvent[]): boolean {
1725
+ return events.some((event) => {
1726
+ if (event.type === RRWEB_FULL_SNAPSHOT_EVENT_TYPE) return true;
1727
+ if (event.type !== 3) return false;
1728
+ try {
1729
+ const parsed = JSON.parse(event.json) as { data?: { source?: unknown } };
1730
+ // rrweb IncrementalSource.Mutation is zero. Dropping one may leave later
1731
+ // node references dangling until another FullSnapshot resets the mirror.
1732
+ return parsed.data?.source === 0;
1733
+ } catch {
1734
+ return true;
1735
+ }
1736
+ });
1737
+ }
1738
+
1739
+ function quarantinePendingReplayUntilFullSnapshot(
1740
+ state: SessionReplayState,
1741
+ ): void {
1742
+ const pending = [...state.retryBatches.flat(), ...state.queue];
1743
+ const resetAt = pending.findIndex(
1744
+ (event) => event.type === RRWEB_FULL_SNAPSHOT_EVENT_TYPE,
1745
+ );
1746
+ state.retryBatches = [];
1747
+ state.queue = resetAt >= 0 ? pending.slice(resetAt) : [];
1748
+ state.queuedBytes = queuedReplayBytes(state.queue);
1749
+ state.awaitingFullSnapshot = resetAt < 0;
1750
+ }
1751
+
1752
+ function restoreReplayEvents(
1753
+ state: SessionReplayState,
1754
+ events: QueuedReplayEvent[],
1755
+ ): void {
1756
+ state.retryBatches.unshift(events);
1757
+ }
1758
+
1759
+ function advanceReplaySequence(
1760
+ state: SessionReplayState,
1761
+ payload: ReplayUploadPayload,
1762
+ ): void {
1763
+ if (state.replayId !== payload.replayId) return;
1764
+ state.sequence = Math.max(state.sequence, payload.sequence + 1);
1765
+ persistReplaySequence(
1766
+ payload.sessionId,
1767
+ payload.replayId,
1768
+ state.startedAtMs,
1769
+ state.sequence,
1770
+ state.replayLinkBaseUrl,
1771
+ );
1772
+ }
1773
+
1774
+ function rollbackReplaySequenceReservation(
1775
+ state: SessionReplayState,
1776
+ payload: ReplayUploadPayload,
1777
+ ): void {
1778
+ if (state.replayId !== payload.replayId) return;
1779
+ if (state.sequence !== payload.sequence + 1) return;
1780
+ state.sequence = payload.sequence;
1781
+ persistReplaySequence(
1782
+ payload.sessionId,
1783
+ payload.replayId,
1784
+ state.startedAtMs,
1785
+ state.sequence,
1786
+ state.replayLinkBaseUrl,
1787
+ );
1788
+ }
1789
+
1790
+ export async function flushSessionReplay(reason = "manual"): Promise<void> {
1791
+ const state = getState();
1792
+ if (!state.options) return;
1793
+ if (state.flushing) {
1794
+ state.pendingFlushReason = mergePendingFlushReason(
1795
+ state.pendingFlushReason,
1796
+ reason,
1797
+ );
1798
+ return new Promise<void>((resolve) => {
1799
+ state.pendingFlushWaiters.push(resolve);
1800
+ });
1801
+ }
1802
+ if (!hasPendingReplayBatch(state)) return;
1803
+ const events = state.retryBatches.shift() ?? takeQueuedReplayBatch(state);
1804
+ const payload = buildReplayBody(state, reason, events);
1805
+ if (!payload || !state.options) {
1806
+ restoreReplayEvents(state, events);
1807
+ return;
1808
+ }
1809
+ state.flushing = true;
1810
+ let uploaded = false;
1811
+ let reservedSequence = false;
1812
+ let splitRejectedBatch = false;
1813
+ let droppedOversizedBatch = false;
1814
+ let definitiveClientErrorStatus: number | null = null;
1815
+ try {
1816
+ await sendReplayUpload(state.options, payload.body, {
1817
+ beforeKeepaliveUpload: shouldReserveSequenceBeforeKeepalive(reason)
1818
+ ? () => {
1819
+ advanceReplaySequence(state, payload);
1820
+ reservedSequence = true;
1821
+ }
1822
+ : undefined,
1823
+ });
1824
+ if (!reservedSequence) advanceReplaySequence(state, payload);
1825
+ state.automaticConflictRestartAttempted = false;
1826
+ state.transientClientErrorFailures = 0;
1827
+ uploaded = true;
1828
+ } catch (error) {
1829
+ if (reservedSequence) rollbackReplaySequenceReservation(state, payload);
1830
+ // A definitive 4xx (e.g. a 409 chunk-sequence/checksum conflict) can
1831
+ // never succeed by retrying the exact same batch -- requeuing it would
1832
+ // just spin forever, blocking every later batch behind it (flushes are
1833
+ // FIFO via `retryBatches`). Drop it and move on instead.
1834
+ const rejectedStatus =
1835
+ error instanceof ReplayUploadHttpError ? error.status : null;
1836
+ const splitBatch = rejectedStatus === 413 ? splitReplayBatch(events) : null;
1837
+ const isUnsplittableOversizedBatch =
1838
+ rejectedStatus === 413 && splitBatch === null;
1839
+ const isTransientClientError =
1840
+ rejectedStatus !== null &&
1841
+ isTransientReplayUploadClientError(rejectedStatus);
1842
+ if (isTransientClientError) {
1843
+ state.transientClientErrorFailures += 1;
1844
+ } else {
1845
+ state.transientClientErrorFailures = 0;
1846
+ }
1847
+ const exhaustedTransientClientRetries =
1848
+ isTransientClientError &&
1849
+ state.transientClientErrorFailures >=
1850
+ MAX_TRANSIENT_REPLAY_CLIENT_FAILURES;
1851
+ const isDefinitiveClientError =
1852
+ error instanceof ReplayUploadHttpError &&
1853
+ (isDefinitiveReplayUploadClientError(error.status) ||
1854
+ exhaustedTransientClientRetries) &&
1855
+ !splitBatch &&
1856
+ !isUnsplittableOversizedBatch;
1857
+ if (splitBatch) {
1858
+ // A server or platform can enforce a stricter decompressed-body limit
1859
+ // than the recorder's configured queue cap. Bisect in FIFO order and
1860
+ // retry both halves at the same sequence; only successful halves advance
1861
+ // it, so no event is duplicated or skipped.
1862
+ state.retryBatches.unshift(...splitBatch);
1863
+ splitRejectedBatch = true;
1864
+ } else if (isUnsplittableOversizedBatch) {
1865
+ // This one event cannot be made any smaller. Drop only the rejected
1866
+ // singleton and keep later retry halves/live batches in FIFO order. If
1867
+ // it carried DOM structure, quarantine dependent mutations until a new
1868
+ // FullSnapshot can safely re-anchor rrweb's node mirror.
1869
+ droppedOversizedBatch = true;
1870
+ if (replayBatchNeedsDomReset(events)) {
1871
+ quarantinePendingReplayUntilFullSnapshot(state);
1872
+ }
1873
+ } else if (isDefinitiveClientError) {
1874
+ // Continuing after a checksum/sequence conflict would reuse the same
1875
+ // rejected sequence forever. More importantly, advancing past it would
1876
+ // append mutations to a replay whose DOM stream may belong to another
1877
+ // tab. End this recorder and clear its persisted identity so the next
1878
+ // start creates a clean replay instead of producing corrupt playback.
1879
+ state.queue = [];
1880
+ state.queuedBytes = 0;
1881
+ state.retryBatches = [];
1882
+ removeStoredReplaySession(payload.replayId);
1883
+ definitiveClientErrorStatus = error.status;
1884
+ } else {
1885
+ restoreReplayEvents(state, events);
1886
+ }
1887
+ // Guard the recorder's own warning so console capture never records it
1888
+ // (a captured warning would enqueue an event and retrigger a flush).
1889
+ const previousInternal = replayCaptureInternal;
1890
+ replayCaptureInternal = true;
1891
+ try {
1892
+ if (splitRejectedBatch) {
1893
+ console.warn(
1894
+ "[session-replay] splitting oversized upload (HTTP 413)",
1895
+ error,
1896
+ );
1897
+ } else if (droppedOversizedBatch) {
1898
+ console.warn(
1899
+ "[session-replay] dropping oversized replay event (HTTP 413)",
1900
+ error,
1901
+ );
1902
+ } else if (isDefinitiveClientError) {
1903
+ console.warn(
1904
+ `[session-replay] dropping upload (HTTP ${(error as ReplayUploadHttpError).status})`,
1905
+ error,
1906
+ );
1907
+ } else {
1908
+ console.warn("[session-replay] upload failed", error);
1909
+ }
1910
+ } finally {
1911
+ replayCaptureInternal = previousInternal;
1912
+ }
1913
+ } finally {
1914
+ state.flushing = false;
1915
+ }
1916
+ const coalescedReason = state.pendingFlushReason;
1917
+ const coalescedWaiters = coalescedReason
1918
+ ? state.pendingFlushWaiters.splice(0)
1919
+ : [];
1920
+ if (coalescedReason) state.pendingFlushReason = null;
1921
+
1922
+ if (coalescedReason) {
1923
+ // A stop/unload request that arrived during this upload owns the tail.
1924
+ // Its higher-priority reason must replace threshold/internal reasons so a
1925
+ // small final batch is not stranded or mislabeled as active.
1926
+ await flushSessionReplay(coalescedReason);
1927
+ } else if (splitRejectedBatch || droppedOversizedBatch) {
1928
+ // Preserve final-status and unload semantics through every half. In
1929
+ // particular, pagehide/beforeunload retries must still reserve their
1930
+ // sequence before a keepalive fetch, and completed flushes must not be
1931
+ // rewritten to an internal recovery reason/status.
1932
+ await flushSessionReplay(reason);
1933
+ } else if (uploaded && hasPendingReplayBatch(state)) {
1934
+ const mustContinue =
1935
+ state.retryBatches.length > 0 ||
1936
+ isFinalFlushReason(reason) ||
1937
+ shouldFlushQueuedReplay(state);
1938
+ if (mustContinue) {
1939
+ if (isFinalFlushReason(reason)) {
1940
+ await flushSessionReplay(reason);
1941
+ } else void flushSessionReplay(reason);
1942
+ }
1943
+ }
1944
+ if (
1945
+ definitiveClientErrorStatus !== null &&
1946
+ state.replayId === payload.replayId
1947
+ ) {
1948
+ const rejectedOptions = state.options;
1949
+ const shouldRestartAfterConflict =
1950
+ definitiveClientErrorStatus === 409 &&
1951
+ state.active &&
1952
+ !isFinalFlushReason(reason) &&
1953
+ !state.automaticConflictRestartAttempted;
1954
+ if (shouldRestartAfterConflict) {
1955
+ state.automaticConflictRestartAttempted = true;
1956
+ }
1957
+
1958
+ await stopSessionReplay("upload-rejected");
1959
+
1960
+ // A 409 means this replay identity can no longer append safely (usually a
1961
+ // duplicated tab that inherited sessionStorage, or an old shared identity
1962
+ // still open during rollout). Do not leave a long-lived SPA tab silently
1963
+ // unrecorded until its next page load: restart rrweb under a fresh per-tab
1964
+ // id so it emits a new Meta + FullSnapshot stream. Limit this to one retry
1965
+ // until an upload succeeds; other definitive 4xx responses usually reflect
1966
+ // configuration/input errors and must not create a restart loop.
1967
+ let restartResult: SessionReplayStartResult | null = null;
1968
+ if (shouldRestartAfterConflict && rejectedOptions) {
1969
+ restartResult = await restartSessionReplayAfterConflict(
1970
+ state,
1971
+ rejectedOptions,
1972
+ payload.sessionId,
1973
+ );
1974
+ }
1975
+
1976
+ // Rare recovery-path telemetry lets Analytics owners quantify conflicts
1977
+ // without recording the rejected replay id, URL, or any captured content.
1978
+ try {
1979
+ rejectedOptions?.onUploadRejected?.({
1980
+ status: definitiveClientErrorStatus,
1981
+ restartAttempted: shouldRestartAfterConflict,
1982
+ restartSucceeded: restartResult?.started === true,
1983
+ ...(restartResult?.reason
1984
+ ? { restartReason: restartResult.reason }
1985
+ : {}),
1986
+ });
1987
+ } catch {
1988
+ // best-effort telemetry must never interfere with recording recovery
1989
+ }
1990
+ }
1991
+ for (const resolve of coalescedWaiters) resolve();
1992
+ }
1993
+
1994
+ /**
1995
+ * Restart a recorder whose replay identity was rejected without routing the
1996
+ * already-normalized options back through the public start API.
1997
+ *
1998
+ * The original recording already passed whole-session sampling. Re-entering
1999
+ * `startSessionReplay` here would ask `getOrCreateAnalyticsSessionId` again;
2000
+ * if the analytics session rotated while the upload was in flight, recovery
2001
+ * could be sampled out and leave a long-lived SPA tab silently unrecorded.
2002
+ * Keeping the original session id and accepted sampling decision also lets us
2003
+ * reuse the exact normalized console/network capture caps instead of relying
2004
+ * on internal option shapes continuing to round-trip through the public API.
2005
+ */
2006
+ async function restartSessionReplayAfterConflict(
2007
+ state: SessionReplayState,
2008
+ options: NormalizedSessionReplayOptions,
2009
+ sessionId: string,
2010
+ ): Promise<SessionReplayStartResult> {
2011
+ // Share the public-start mutex. A consumer may call start in the await gap
2012
+ // after the rejected recorder stops; both paths must converge on one rrweb
2013
+ // instance rather than racing two fresh identities.
2014
+ if (state.startPromise) return state.startPromise;
2015
+
2016
+ const startGeneration = ++state.startGeneration;
2017
+ let startPromise: Promise<SessionReplayStartResult>;
2018
+ startPromise = restartSessionReplayAfterConflictInternal(
2019
+ state,
2020
+ options,
2021
+ sessionId,
2022
+ startGeneration,
2023
+ ).finally(() => {
2024
+ if (state.startPromise === startPromise) state.startPromise = null;
2025
+ });
2026
+ state.startPromise = startPromise;
2027
+ return startPromise;
2028
+ }
2029
+
2030
+ async function restartSessionReplayAfterConflictInternal(
2031
+ state: SessionReplayState,
2032
+ options: NormalizedSessionReplayOptions,
2033
+ sessionId: string,
2034
+ startGeneration: number,
2035
+ ): Promise<SessionReplayStartResult> {
2036
+ if (options.shouldStart && !options.shouldStart()) {
2037
+ return { started: false, reason: "disabled", sessionId, sampled: true };
2038
+ }
2039
+
2040
+ const initialProperties = replayExtraProperties(options);
2041
+ if (options.requireSignedInUser && !replayUserEmail(initialProperties)) {
2042
+ return {
2043
+ started: false,
2044
+ reason: "missing-user-id",
2045
+ sessionId,
2046
+ sampled: true,
2047
+ };
2048
+ }
2049
+ if (!isUrlRecordable(window.location.href, options)) {
2050
+ return {
2051
+ started: false,
2052
+ reason: "url-blocked",
2053
+ sessionId,
2054
+ sampled: true,
2055
+ };
2056
+ }
2057
+
2058
+ return startSessionReplayRecorder(
2059
+ state,
2060
+ options,
2061
+ sessionId,
2062
+ true,
2063
+ initialProperties,
2064
+ startGeneration,
2065
+ );
2066
+ }
2067
+
2068
+ function installUrlMonitor(state: SessionReplayState): void {
2069
+ if (!state.options || state.restoreUrlMonitor) return;
2070
+ const options = state.options;
2071
+ const check = () => {
2072
+ if (!isUrlRecordable(window.location.href, options)) {
2073
+ stopSessionReplay("url-blocked");
2074
+ }
2075
+ };
2076
+ const originalPushState = window.history.pushState;
2077
+ const originalReplaceState = window.history.replaceState;
2078
+ window.history.pushState = function pushState(...args) {
2079
+ const result = originalPushState.apply(this, args);
2080
+ queueMicrotask(check);
2081
+ return result;
2082
+ };
2083
+ window.history.replaceState = function replaceState(...args) {
2084
+ const result = originalReplaceState.apply(this, args);
2085
+ queueMicrotask(check);
2086
+ return result;
2087
+ };
2088
+ window.addEventListener("popstate", check);
2089
+ state.restoreUrlMonitor = () => {
2090
+ window.history.pushState = originalPushState;
2091
+ window.history.replaceState = originalReplaceState;
2092
+ window.removeEventListener("popstate", check);
2093
+ state.restoreUrlMonitor = null;
2094
+ };
2095
+ }
2096
+
2097
+ function installLifecycleListeners(state: SessionReplayState): void {
2098
+ if (state.removeLifecycleListeners) return;
2099
+ const flushOnHidden = () => {
2100
+ if (document.visibilityState === "hidden") {
2101
+ void flushSessionReplay("visibility-hidden");
2102
+ }
2103
+ };
2104
+ const flushOnUnload = () => {
2105
+ void flushSessionReplay("pagehide");
2106
+ };
2107
+ document.addEventListener("visibilitychange", flushOnHidden);
2108
+ window.addEventListener("pagehide", flushOnUnload);
2109
+ state.removeLifecycleListeners = () => {
2110
+ document.removeEventListener("visibilitychange", flushOnHidden);
2111
+ window.removeEventListener("pagehide", flushOnUnload);
2112
+ state.removeLifecycleListeners = null;
2113
+ };
2114
+ }
2115
+
2116
+ function markedSessionReplayIframes(): HTMLIFrameElement[] {
2117
+ if (typeof document.querySelectorAll !== "function") return [];
2118
+ return Array.from(
2119
+ document.querySelectorAll<HTMLIFrameElement>(
2120
+ `iframe[${SESSION_REPLAY_IFRAME_ATTRIBUTE}]`,
2121
+ ),
2122
+ );
2123
+ }
2124
+
2125
+ function markedSessionReplayIframeForSource(
2126
+ source: MessageEventSource | null,
2127
+ ): HTMLIFrameElement | null {
2128
+ if (!source) return null;
2129
+ return (
2130
+ markedSessionReplayIframes().find(
2131
+ (iframe) => iframe.contentWindow === source,
2132
+ ) ?? null
2133
+ );
2134
+ }
2135
+
2136
+ function sessionReplayIframePrivacyOptions(
2137
+ options: NormalizedSessionReplayOptions,
2138
+ ): SessionReplayIframePrivacyOptions {
2139
+ return {
2140
+ blockSelector: options.blockSelector,
2141
+ ignoreSelector: options.ignoreSelector,
2142
+ maskTextClass: options.maskTextClass,
2143
+ maskTextSelector: options.maskTextSelector,
2144
+ maskAllInputs: options.maskAllInputs,
2145
+ maskInputOptions: DEFAULT_MASK_INPUT_OPTIONS,
2146
+ recordCanvas: options.recordCanvas,
2147
+ collectFonts: options.collectFonts,
2148
+ inlineImages: options.inlineImages,
2149
+ sampling: options.eventSampling,
2150
+ };
2151
+ }
2152
+
2153
+ function postSessionReplayIframeMessage(
2154
+ iframe: HTMLIFrameElement,
2155
+ message: SessionReplayIframeStartMessage | SessionReplayIframeStopMessage,
2156
+ ): void {
2157
+ try {
2158
+ iframe.contentWindow?.postMessage(message, "*");
2159
+ } catch {
2160
+ // The frame may have navigated or detached between discovery and send.
2161
+ }
2162
+ }
2163
+
2164
+ /**
2165
+ * Cooperative opaque/cross-origin iframe recording.
2166
+ *
2167
+ * rrweb cannot inspect these documents from the host. Framework-owned child
2168
+ * frames carry a marker and inject a tiny recorder that probes its direct
2169
+ * parent. Only an active host recorder responds, and only when the probe's
2170
+ * source is the contentWindow of a currently marked direct iframe.
2171
+ */
2172
+ function installSessionReplayIframeBridge(
2173
+ state: SessionReplayState,
2174
+ options: NormalizedSessionReplayOptions,
2175
+ ): void {
2176
+ if (!options.recordCrossOriginIframes || state.restoreIframeBridge) return;
2177
+
2178
+ const startMessage: SessionReplayIframeStartMessage = {
2179
+ type: SESSION_REPLAY_IFRAME_START,
2180
+ options: sessionReplayIframePrivacyOptions(options),
2181
+ };
2182
+ const stopMessage: SessionReplayIframeStopMessage = {
2183
+ type: SESSION_REPLAY_IFRAME_STOP,
2184
+ };
2185
+ const sendStart = (iframe: HTMLIFrameElement) =>
2186
+ postSessionReplayIframeMessage(iframe, startMessage);
2187
+ const onMessage = (event: MessageEvent) => {
2188
+ if (!state.active) return;
2189
+ if (
2190
+ !event.data ||
2191
+ typeof event.data !== "object" ||
2192
+ event.data.type !== SESSION_REPLAY_IFRAME_PROBE
2193
+ ) {
2194
+ return;
2195
+ }
2196
+ const iframe = markedSessionReplayIframeForSource(event.source);
2197
+ if (iframe) sendStart(iframe);
2198
+ };
2199
+
2200
+ window.addEventListener("message", onMessage);
2201
+ for (const iframe of markedSessionReplayIframes()) sendStart(iframe);
2202
+ state.restoreIframeBridge = () => {
2203
+ window.removeEventListener("message", onMessage);
2204
+ for (const iframe of markedSessionReplayIframes()) {
2205
+ postSessionReplayIframeMessage(iframe, stopMessage);
2206
+ }
2207
+ state.restoreIframeBridge = null;
2208
+ };
2209
+ }
2210
+
2211
+ function truncateCaptureText(value: string, maxLength: number): string {
2212
+ return value.length > maxLength ? value.slice(0, maxLength) : value;
2213
+ }
2214
+
2215
+ /** Circular-safe, depth/length-limited plain value for JSON.stringify. */
2216
+ function toCaptureSerializable(
2217
+ value: unknown,
2218
+ depth: number,
2219
+ seen: WeakSet<object>,
2220
+ ): unknown {
2221
+ if (value === null || value === undefined) return value;
2222
+ if (typeof value === "string") return value;
2223
+ if (typeof value === "number" || typeof value === "boolean") return value;
2224
+ if (typeof value === "bigint") return `${value}n`;
2225
+ if (typeof value === "function") return "[function]";
2226
+ if (typeof value === "symbol") return String(value);
2227
+ if (value instanceof Error) return `${value.name}: ${value.message}`;
2228
+ if (typeof value !== "object") return String(value);
2229
+ if (seen.has(value)) return "[circular]";
2230
+ if (depth >= MAX_CONSOLE_SERIALIZE_DEPTH) {
2231
+ return Array.isArray(value) ? "[array]" : "[object]";
2232
+ }
2233
+ seen.add(value);
2234
+ if (Array.isArray(value)) {
2235
+ const items = value
2236
+ .slice(0, MAX_CONSOLE_SERIALIZE_ENTRIES)
2237
+ .map((item) => toCaptureSerializable(item, depth + 1, seen));
2238
+ if (value.length > MAX_CONSOLE_SERIALIZE_ENTRIES) items.push("[truncated]");
2239
+ return items;
2240
+ }
2241
+ const out: Record<string, unknown> = {};
2242
+ let count = 0;
2243
+ for (const [key, child] of Object.entries(value)) {
2244
+ if (count >= MAX_CONSOLE_SERIALIZE_ENTRIES) {
2245
+ out["[truncated]"] = true;
2246
+ break;
2247
+ }
2248
+ out[key] = toCaptureSerializable(child, depth + 1, seen);
2249
+ count += 1;
2250
+ }
2251
+ return out;
2252
+ }
2253
+
2254
+ function serializeConsoleArg(value: unknown): string {
2255
+ try {
2256
+ if (typeof value === "string") return value;
2257
+ if (value instanceof Error) return `${value.name}: ${value.message}`;
2258
+ if (
2259
+ value === null ||
2260
+ value === undefined ||
2261
+ typeof value === "number" ||
2262
+ typeof value === "boolean" ||
2263
+ typeof value === "bigint"
2264
+ ) {
2265
+ return String(value);
2266
+ }
2267
+ return (
2268
+ JSON.stringify(toCaptureSerializable(value, 0, new WeakSet())) ??
2269
+ String(value)
2270
+ );
2271
+ } catch {
2272
+ try {
2273
+ return Object.prototype.toString.call(value);
2274
+ } catch {
2275
+ return "[unserializable]";
2276
+ }
2277
+ }
2278
+ }
2279
+
2280
+ /**
2281
+ * Emit an rrweb custom event while recording. Sets the re-entrancy guard so
2282
+ * synchronous work triggered by the emit (enqueue -> flush -> fetch of the
2283
+ * ingest endpoint) is never re-captured by the interceptors below.
2284
+ */
2285
+ function emitReplayCustomEvent(
2286
+ state: SessionReplayState,
2287
+ tag: string,
2288
+ payload: Record<string, unknown>,
2289
+ ): void {
2290
+ const addCustomEvent = state.addCustomEvent;
2291
+ if (!state.active || !addCustomEvent) return;
2292
+ const previous = replayCaptureInternal;
2293
+ replayCaptureInternal = true;
2294
+ try {
2295
+ addCustomEvent(tag, payload);
2296
+ } catch {
2297
+ // recorder already stopped -- drop the event
2298
+ } finally {
2299
+ replayCaptureInternal = previous;
2300
+ }
2301
+ }
2302
+
2303
+ function captureCurrentUrl(): string | undefined {
2304
+ try {
2305
+ return scrubUrl(window.location.href);
2306
+ } catch {
2307
+ return undefined;
2308
+ }
2309
+ }
2310
+
2311
+ type CaptureConsoleLevel = "log" | "info" | "warn" | "error" | "debug";
2312
+ type CaptureConsoleSource = "console" | "window-error" | "unhandledrejection";
2313
+
2314
+ const CAPTURE_CONSOLE_LEVELS: CaptureConsoleLevel[] = [
2315
+ "log",
2316
+ "info",
2317
+ "warn",
2318
+ "error",
2319
+ "debug",
2320
+ ];
2321
+
2322
+ function installConsoleCapture(
2323
+ state: SessionReplayState,
2324
+ captureOptions: NormalizedCaptureOptions,
2325
+ ): () => void {
2326
+ let emitted = 0;
2327
+ let stopped = false;
2328
+ let pending: {
2329
+ key: string;
2330
+ payload: Record<string, unknown>;
2331
+ repeat: number;
2332
+ } | null = null;
2333
+
2334
+ const emitPayload = (payload: Record<string, unknown>) => {
2335
+ if (stopped) return;
2336
+ if (emitted >= captureOptions.maxEvents) {
2337
+ stopped = true;
2338
+ emitReplayCustomEvent(state, SESSION_REPLAY_CONSOLE_EVENT_TAG, {
2339
+ level: "warn",
2340
+ source: "console",
2341
+ message: "session replay console capture truncated",
2342
+ truncated: true,
2343
+ });
2344
+ return;
2345
+ }
2346
+ emitted += 1;
2347
+ emitReplayCustomEvent(state, SESSION_REPLAY_CONSOLE_EVENT_TAG, payload);
2348
+ };
2349
+
2350
+ /**
2351
+ * The first occurrence of a message is emitted immediately; consecutive
2352
+ * identical messages accumulate here and are flushed as one event whose
2353
+ * `repeat` is the number of collapsed duplicates.
2354
+ */
2355
+ const flushPending = () => {
2356
+ const entry = pending;
2357
+ pending = null;
2358
+ if (!entry || entry.repeat <= 0) return;
2359
+ emitPayload({ ...entry.payload, repeat: entry.repeat });
2360
+ };
2361
+
2362
+ const capture = (
2363
+ level: CaptureConsoleLevel,
2364
+ source: CaptureConsoleSource,
2365
+ args: unknown[],
2366
+ stackOverride?: string,
2367
+ ) => {
2368
+ if (stopped || replayCaptureInternal) return;
2369
+ try {
2370
+ const message = truncateCaptureText(
2371
+ redactCaptureText(args.length ? serializeConsoleArg(args[0]) : ""),
2372
+ MAX_CONSOLE_MESSAGE_LENGTH,
2373
+ );
2374
+ const extraArgs = args
2375
+ .slice(1, 1 + MAX_CONSOLE_ARGS)
2376
+ .map((arg) =>
2377
+ truncateCaptureText(
2378
+ redactCaptureText(serializeConsoleArg(arg)),
2379
+ MAX_CONSOLE_MESSAGE_LENGTH,
2380
+ ),
2381
+ );
2382
+ const errorArg = args.find((arg): arg is Error => arg instanceof Error);
2383
+ const rawStack = stackOverride ?? errorArg?.stack;
2384
+ const stack =
2385
+ typeof rawStack === "string" && rawStack
2386
+ ? truncateCaptureText(
2387
+ redactCaptureText(rawStack),
2388
+ MAX_CONSOLE_STACK_LENGTH,
2389
+ )
2390
+ : undefined;
2391
+ const url = captureCurrentUrl();
2392
+ const payload: Record<string, unknown> = {
2393
+ level,
2394
+ source,
2395
+ message,
2396
+ ...(extraArgs.length ? { args: extraArgs } : {}),
2397
+ ...(stack ? { stack } : {}),
2398
+ ...(url ? { url } : {}),
2399
+ };
2400
+ const key = `${level}${source}${message}`;
2401
+ if (pending && pending.key === key) {
2402
+ pending.repeat += 1;
2403
+ return;
2404
+ }
2405
+ flushPending();
2406
+ emitPayload(payload);
2407
+ pending = { key, payload, repeat: 0 };
2408
+ } catch {
2409
+ // capture must never break the host page
2410
+ }
2411
+ };
2412
+
2413
+ const originals: Partial<
2414
+ Record<CaptureConsoleLevel, (...args: unknown[]) => void>
2415
+ > = {};
2416
+ const wrappers: Partial<
2417
+ Record<CaptureConsoleLevel, (...args: unknown[]) => void>
2418
+ > = {};
2419
+ for (const level of CAPTURE_CONSOLE_LEVELS) {
2420
+ const original = console[level] as (...args: unknown[]) => void;
2421
+ if (typeof original !== "function") continue;
2422
+ originals[level] = original;
2423
+ const wrapper = (...args: unknown[]) => {
2424
+ original.apply(console, args);
2425
+ try {
2426
+ capture(level, "console", args);
2427
+ } catch {
2428
+ // never throw from the wrapper
2429
+ }
2430
+ };
2431
+ wrappers[level] = wrapper;
2432
+ console[level] = wrapper;
2433
+ }
2434
+
2435
+ const onWindowError = (event: ErrorEvent) => {
2436
+ try {
2437
+ const error = event?.error;
2438
+ capture(
2439
+ "error",
2440
+ "window-error",
2441
+ [error instanceof Error ? error : (event?.message ?? "Error")],
2442
+ error instanceof Error ? error.stack : undefined,
2443
+ );
2444
+ } catch {
2445
+ // never throw from the listener
2446
+ }
2447
+ };
2448
+ const onUnhandledRejection = (event: PromiseRejectionEvent) => {
2449
+ try {
2450
+ const reason = event?.reason;
2451
+ capture(
2452
+ "error",
2453
+ "unhandledrejection",
2454
+ [reason instanceof Error ? reason : reason],
2455
+ reason instanceof Error ? reason.stack : undefined,
2456
+ );
2457
+ } catch {
2458
+ // never throw from the listener
2459
+ }
2460
+ };
2461
+ window.addEventListener("error", onWindowError as EventListener);
2462
+ window.addEventListener(
2463
+ "unhandledrejection",
2464
+ onUnhandledRejection as EventListener,
2465
+ );
2466
+
2467
+ return () => {
2468
+ try {
2469
+ flushPending();
2470
+ } catch {
2471
+ // best-effort duplicate flush
2472
+ }
2473
+ stopped = true;
2474
+ for (const level of CAPTURE_CONSOLE_LEVELS) {
2475
+ const original = originals[level];
2476
+ // Restore only what we installed: if another library patched on top of
2477
+ // our wrapper, leave the current function in place.
2478
+ if (original && console[level] === wrappers[level]) {
2479
+ console[level] = original;
2480
+ }
2481
+ }
2482
+ window.removeEventListener("error", onWindowError as EventListener);
2483
+ window.removeEventListener(
2484
+ "unhandledrejection",
2485
+ onUnhandledRejection as EventListener,
2486
+ );
2487
+ };
2488
+ }
2489
+
2490
+ function captureRequestUrl(input: unknown): string {
2491
+ if (typeof input === "string") return input;
2492
+ if (typeof URL !== "undefined" && input instanceof URL) {
2493
+ return input.toString();
2494
+ }
2495
+ if (input && typeof input === "object" && "url" in input) {
2496
+ const url = (input as { url?: unknown }).url;
2497
+ if (typeof url === "string") return url;
2498
+ }
2499
+ return "";
2500
+ }
2501
+
2502
+ function captureRequestMethod(
2503
+ input: unknown,
2504
+ init?: { method?: unknown },
2505
+ ): string {
2506
+ const initMethod = init?.method;
2507
+ if (typeof initMethod === "string" && initMethod) {
2508
+ return initMethod.toUpperCase();
2509
+ }
2510
+ if (input && typeof input === "object" && "method" in input) {
2511
+ const method = (input as { method?: unknown }).method;
2512
+ if (typeof method === "string" && method) return method.toUpperCase();
2513
+ }
2514
+ return "GET";
2515
+ }
2516
+
2517
+ /**
2518
+ * Requests the recorder must never record: its own ingest endpoint and the
2519
+ * core analytics tracking endpoint (either would create a flush -> event ->
2520
+ * flush feedback loop), plus non-network schemes.
2521
+ */
2522
+ function isCaptureExcludedUrl(rawUrl: string, ingestEndpoint: string): boolean {
2523
+ const trimmed = rawUrl.trim();
2524
+ if (!trimmed) return true;
2525
+ const lower = trimmed.toLowerCase();
2526
+ if (
2527
+ lower.startsWith("data:") ||
2528
+ lower.startsWith("blob:") ||
2529
+ lower.startsWith("about:")
2530
+ ) {
2531
+ return true;
2532
+ }
2533
+ try {
2534
+ const resolved = new URL(trimmed, window.location.href);
2535
+ const ingest = new URL(ingestEndpoint, window.location.href);
2536
+ if (
2537
+ resolved.origin === ingest.origin &&
2538
+ resolved.pathname === ingest.pathname
2539
+ ) {
2540
+ return true;
2541
+ }
2542
+ if (resolved.pathname.endsWith("/api/analytics/replay")) return true;
2543
+ if (resolved.pathname.endsWith("/api/analytics/track")) return true;
2544
+ } catch {
2545
+ // Unresolvable URL -- skip capture rather than risk recording junk.
2546
+ return true;
2547
+ }
2548
+ return false;
2549
+ }
2550
+
2551
+ /**
2552
+ * Best-effort, synchronous read of a 5xx XHR response body, sliced to `cap`
2553
+ * before redaction runs (caller redacts). Only reads `responseText` when
2554
+ * `responseType` is "" or "text" -- other response types are read via
2555
+ * `JSON.stringify` (guarded) for `"json"`, and skipped entirely otherwise
2556
+ * since arbitrary binary/blob/arraybuffer bodies are not useful error text.
2557
+ */
2558
+ function readXhrErrorBody(
2559
+ xhr: XMLHttpRequest,
2560
+ cap: number,
2561
+ ): string | undefined {
2562
+ try {
2563
+ const responseType = xhr.responseType;
2564
+ if (responseType === "" || responseType === "text") {
2565
+ const text = xhr.responseText;
2566
+ return typeof text === "string" ? text.slice(0, cap) : undefined;
2567
+ }
2568
+ if (responseType === "json") {
2569
+ try {
2570
+ return JSON.stringify(xhr.response)?.slice(0, cap);
2571
+ } catch {
2572
+ return undefined;
2573
+ }
2574
+ }
2575
+ return undefined;
2576
+ } catch {
2577
+ return undefined;
2578
+ }
2579
+ }
2580
+
2581
+ function installNetworkCapture(
2582
+ state: SessionReplayState,
2583
+ captureOptions: NormalizedCaptureOptions,
2584
+ ): () => void {
2585
+ const options = state.options;
2586
+ if (!options) return () => {};
2587
+ const ingestEndpoint = options.endpoint;
2588
+ let emitted = 0;
2589
+ let stopped = false;
2590
+
2591
+ const emitPayload = (payload: Record<string, unknown>) => {
2592
+ if (stopped) return;
2593
+ if (emitted >= captureOptions.maxEvents) {
2594
+ stopped = true;
2595
+ emitReplayCustomEvent(state, SESSION_REPLAY_NETWORK_EVENT_TAG, {
2596
+ message: "session replay network capture truncated",
2597
+ truncated: true,
2598
+ });
2599
+ return;
2600
+ }
2601
+ emitted += 1;
2602
+ emitReplayCustomEvent(state, SESSION_REPLAY_NETWORK_EVENT_TAG, payload);
2603
+ };
2604
+
2605
+ const errorBodyCap =
2606
+ captureOptions.captureErrorBodies !== false
2607
+ ? (captureOptions.maxErrorBodyLength ?? DEFAULT_MAX_ERROR_BODY_LENGTH)
2608
+ : null;
2609
+
2610
+ const recordRequest = (
2611
+ api: "fetch" | "xhr",
2612
+ method: string,
2613
+ rawUrl: string,
2614
+ status: number,
2615
+ ok: boolean,
2616
+ durationMs: number,
2617
+ error?: string,
2618
+ responseBody?: string,
2619
+ ) => {
2620
+ if (stopped) return;
2621
+ try {
2622
+ if (isCaptureExcludedUrl(rawUrl, ingestEndpoint)) return;
2623
+ const absolute = new URL(rawUrl, window.location.href).toString();
2624
+ const url = scrubUrl(absolute) ?? absolute;
2625
+ emitPayload({
2626
+ api,
2627
+ method: method.toUpperCase(),
2628
+ url,
2629
+ status,
2630
+ ok,
2631
+ durationMs: Math.max(0, Math.round(durationMs)),
2632
+ ...(error
2633
+ ? {
2634
+ error: truncateCaptureText(
2635
+ redactCaptureText(error),
2636
+ MAX_CONSOLE_MESSAGE_LENGTH,
2637
+ ),
2638
+ }
2639
+ : {}),
2640
+ ...(responseBody
2641
+ ? {
2642
+ responseBody: truncateCaptureText(
2643
+ redactCaptureText(responseBody),
2644
+ errorBodyCap ?? DEFAULT_MAX_ERROR_BODY_LENGTH,
2645
+ ),
2646
+ }
2647
+ : {}),
2648
+ });
2649
+ } catch {
2650
+ // capture must never break the host page
2651
+ }
2652
+ };
2653
+
2654
+ /**
2655
+ * Best-effort bounded read of a 5xx response body. Reads a decoded stream
2656
+ * up to `cap` chars and cancels the reader, or falls back to `.text()`
2657
+ * sliced to `cap` when no readable stream is exposed. Never throws --
2658
+ * opaque/no-body responses (or any read failure) resolve to undefined.
2659
+ */
2660
+ const readBoundedErrorBody = async (
2661
+ response: Response,
2662
+ cap: number,
2663
+ ): Promise<string | undefined> => {
2664
+ try {
2665
+ const reader = response.body?.getReader?.();
2666
+ if (reader) {
2667
+ const decoder = new TextDecoder();
2668
+ let text = "";
2669
+ while (text.length < cap) {
2670
+ const { done, value } = await reader.read();
2671
+ if (done) break;
2672
+ if (value) text += decoder.decode(value, { stream: true });
2673
+ }
2674
+ // Fire-and-forget: cancelling an already-exhausted (or still-open but
2675
+ // no-longer-wanted) reader can hang indefinitely on some stream
2676
+ // implementations. We never need the result, so don't await it --
2677
+ // that would defeat the whole point of bounding this read.
2678
+ try {
2679
+ reader.cancel().catch(() => {});
2680
+ } catch {
2681
+ // best-effort; some readers throw synchronously instead
2682
+ }
2683
+ return text.slice(0, cap);
2684
+ }
2685
+ const text = await response.text();
2686
+ return text.slice(0, cap);
2687
+ } catch {
2688
+ return undefined;
2689
+ }
2690
+ };
2691
+
2692
+ const restores: Array<() => void> = [];
2693
+
2694
+ if (typeof window.fetch === "function") {
2695
+ const originalFetch = window.fetch;
2696
+ const wrappedFetch = function (
2697
+ this: unknown,
2698
+ input?: RequestInfo | URL,
2699
+ init?: RequestInit,
2700
+ ): Promise<Response> {
2701
+ const self = this ?? window;
2702
+ if (stopped || replayCaptureInternal) {
2703
+ return originalFetch.call(self, input as RequestInfo | URL, init);
2704
+ }
2705
+ let method = "GET";
2706
+ let url = "";
2707
+ let skip = true;
2708
+ try {
2709
+ method = captureRequestMethod(input, init);
2710
+ url = captureRequestUrl(input);
2711
+ skip = isCaptureExcludedUrl(url, ingestEndpoint);
2712
+ } catch {
2713
+ skip = true;
2714
+ }
2715
+ if (skip) {
2716
+ return originalFetch.call(self, input as RequestInfo | URL, init);
2717
+ }
2718
+ const startedAt = performance.now();
2719
+ // Call the original exactly as the page did; never read the caller's
2720
+ // response before returning it, never replace the response, propagate
2721
+ // rejections untouched. A 5xx body snippet (if any) is read from a
2722
+ // clone and emitted from a detached promise chain afterward.
2723
+ const result = originalFetch.call(self, input as RequestInfo | URL, init);
2724
+ if (!result || typeof (result as Promise<Response>).then !== "function") {
2725
+ return result;
2726
+ }
2727
+ return result.then(
2728
+ (response) => {
2729
+ try {
2730
+ const durationMs = performance.now() - startedAt;
2731
+ if (errorBodyCap !== null && response.status >= 500) {
2732
+ // Clone immediately -- before any other code (including this
2733
+ // handler returning) can consume the original body -- so the
2734
+ // clone's stream is guaranteed unconsumed.
2735
+ let clone: Response | null = null;
2736
+ try {
2737
+ clone = response.clone();
2738
+ } catch {
2739
+ clone = null;
2740
+ }
2741
+ if (clone) {
2742
+ const bodyPromise = readBoundedErrorBody(clone, errorBodyCap);
2743
+ const timeoutPromise = new Promise<undefined>((resolve) => {
2744
+ setTimeout(
2745
+ () => resolve(undefined),
2746
+ ERROR_BODY_READ_TIMEOUT_MS,
2747
+ );
2748
+ });
2749
+ // Detached chain: never rejects, never awaited by the caller.
2750
+ Promise.race([bodyPromise, timeoutPromise])
2751
+ .then((responseBody) => {
2752
+ recordRequest(
2753
+ "fetch",
2754
+ method,
2755
+ url,
2756
+ response.status,
2757
+ response.ok,
2758
+ durationMs,
2759
+ undefined,
2760
+ responseBody,
2761
+ );
2762
+ })
2763
+ .catch(() => {
2764
+ // never affect the caller
2765
+ });
2766
+ } else {
2767
+ recordRequest(
2768
+ "fetch",
2769
+ method,
2770
+ url,
2771
+ response.status,
2772
+ response.ok,
2773
+ durationMs,
2774
+ );
2775
+ }
2776
+ } else {
2777
+ recordRequest(
2778
+ "fetch",
2779
+ method,
2780
+ url,
2781
+ response.status,
2782
+ response.ok,
2783
+ durationMs,
2784
+ );
2785
+ }
2786
+ } catch {
2787
+ // never affect the caller
2788
+ }
2789
+ return response;
2790
+ },
2791
+ (error) => {
2792
+ try {
2793
+ recordRequest(
2794
+ "fetch",
2795
+ method,
2796
+ url,
2797
+ 0,
2798
+ false,
2799
+ performance.now() - startedAt,
2800
+ error instanceof Error ? error.message : String(error),
2801
+ );
2802
+ } catch {
2803
+ // never affect the caller
2804
+ }
2805
+ throw error;
2806
+ },
2807
+ );
2808
+ };
2809
+ window.fetch = wrappedFetch as typeof window.fetch;
2810
+ restores.push(() => {
2811
+ // Restore only what we installed (another lib may have patched on top).
2812
+ if (window.fetch === (wrappedFetch as typeof window.fetch)) {
2813
+ window.fetch = originalFetch;
2814
+ }
2815
+ });
2816
+ }
2817
+
2818
+ if (typeof XMLHttpRequest !== "undefined" && XMLHttpRequest.prototype) {
2819
+ const originalOpen = XMLHttpRequest.prototype.open;
2820
+ const originalSend = XMLHttpRequest.prototype.send;
2821
+ const xhrInfo = new WeakMap<
2822
+ XMLHttpRequest,
2823
+ { method: string; url: string }
2824
+ >();
2825
+
2826
+ const wrappedOpen = function (
2827
+ this: XMLHttpRequest,
2828
+ method: string,
2829
+ url: string | URL,
2830
+ async?: boolean,
2831
+ username?: string | null,
2832
+ password?: string | null,
2833
+ ) {
2834
+ try {
2835
+ xhrInfo.set(this, { method: String(method), url: String(url) });
2836
+ } catch {
2837
+ // never throw from the wrapper
2838
+ }
2839
+ return originalOpen.call(
2840
+ this,
2841
+ method,
2842
+ url as string,
2843
+ async ?? true,
2844
+ username,
2845
+ password,
2846
+ );
2847
+ };
2848
+
2849
+ const wrappedSend = function (
2850
+ this: XMLHttpRequest,
2851
+ body?: Document | XMLHttpRequestBodyInit | null,
2852
+ ) {
2853
+ try {
2854
+ const info = xhrInfo.get(this);
2855
+ if (info && !stopped && !replayCaptureInternal) {
2856
+ const startedAt = performance.now();
2857
+ let errorMessage: string | undefined;
2858
+ const markError = (message: string) => () => {
2859
+ errorMessage = message;
2860
+ };
2861
+ this.addEventListener("error", markError("XMLHttpRequest failed"), {
2862
+ once: true,
2863
+ });
2864
+ this.addEventListener("abort", markError("XMLHttpRequest aborted"), {
2865
+ once: true,
2866
+ });
2867
+ this.addEventListener(
2868
+ "timeout",
2869
+ markError("XMLHttpRequest timed out"),
2870
+ { once: true },
2871
+ );
2872
+ this.addEventListener(
2873
+ "loadend",
2874
+ () => {
2875
+ const status = typeof this.status === "number" ? this.status : 0;
2876
+ const effectiveStatus = errorMessage ? 0 : status;
2877
+ let responseBody: string | undefined;
2878
+ if (errorBodyCap !== null && !errorMessage && status >= 500) {
2879
+ responseBody = readXhrErrorBody(this, errorBodyCap);
2880
+ }
2881
+ recordRequest(
2882
+ "xhr",
2883
+ info.method,
2884
+ info.url,
2885
+ effectiveStatus,
2886
+ !errorMessage && status >= 200 && status < 300,
2887
+ performance.now() - startedAt,
2888
+ errorMessage,
2889
+ responseBody,
2890
+ );
2891
+ xhrInfo.delete(this);
2892
+ },
2893
+ { once: true },
2894
+ );
2895
+ }
2896
+ } catch {
2897
+ // never throw from the wrapper
2898
+ }
2899
+ return originalSend.call(this, body ?? null);
2900
+ };
2901
+
2902
+ XMLHttpRequest.prototype.open =
2903
+ wrappedOpen as typeof XMLHttpRequest.prototype.open;
2904
+ XMLHttpRequest.prototype.send =
2905
+ wrappedSend as typeof XMLHttpRequest.prototype.send;
2906
+ restores.push(() => {
2907
+ if (
2908
+ XMLHttpRequest.prototype.open ===
2909
+ (wrappedOpen as typeof XMLHttpRequest.prototype.open)
2910
+ ) {
2911
+ XMLHttpRequest.prototype.open = originalOpen;
2912
+ }
2913
+ if (
2914
+ XMLHttpRequest.prototype.send ===
2915
+ (wrappedSend as typeof XMLHttpRequest.prototype.send)
2916
+ ) {
2917
+ XMLHttpRequest.prototype.send = originalSend;
2918
+ }
2919
+ });
2920
+ }
2921
+
2922
+ return () => {
2923
+ stopped = true;
2924
+ for (const restore of restores) {
2925
+ try {
2926
+ restore();
2927
+ } catch {
2928
+ // best-effort restore
2929
+ }
2930
+ }
2931
+ };
2932
+ }
2933
+
2934
+ function installCaptureInterceptors(state: SessionReplayState): void {
2935
+ const options = state.options;
2936
+ if (!options || state.restoreCaptures || !state.addCustomEvent) return;
2937
+ if (!options.console && !options.network) return;
2938
+ const restores: Array<() => void> = [];
2939
+ try {
2940
+ if (options.console) {
2941
+ restores.push(installConsoleCapture(state, options.console));
2942
+ }
2943
+ if (options.network) {
2944
+ restores.push(installNetworkCapture(state, options.network));
2945
+ }
2946
+ } catch {
2947
+ // keep whatever installed cleanly; restores below still uninstall it
2948
+ }
2949
+ if (restores.length === 0) return;
2950
+ state.restoreCaptures = () => {
2951
+ state.restoreCaptures = null;
2952
+ for (const restore of restores) {
2953
+ try {
2954
+ restore();
2955
+ } catch {
2956
+ // best-effort restore
2957
+ }
2958
+ }
2959
+ };
2960
+ }
2961
+
2962
+ export async function startSessionReplay(
2963
+ options: SessionReplayOptions = {},
2964
+ ): Promise<SessionReplayStartResult> {
2965
+ if (options.enabled === false) return { started: false, reason: "disabled" };
2966
+ if (options.shouldStart && !options.shouldStart()) {
2967
+ return { started: false, reason: "disabled" };
2968
+ }
2969
+ if (typeof window === "undefined" || typeof document === "undefined") {
2970
+ return { started: false, reason: "not-browser" };
2971
+ }
2972
+ const normalized = normalizeOptions(options);
2973
+ if (!normalized) return { started: false, reason: "missing-public-key" };
2974
+
2975
+ const sessionId = getOrCreateAnalyticsSessionId();
2976
+ if (!sessionId) return { started: false, reason: "missing-session-id" };
2977
+ const sampled = shouldSampleSessionReplay(
2978
+ sessionId,
2979
+ normalized.sampleRate,
2980
+ normalized.samplingSalt,
2981
+ );
2982
+ if (!sampled) {
2983
+ return { started: false, reason: "sampled-out", sessionId, sampled };
2984
+ }
2985
+ const initialProperties = replayExtraProperties(normalized);
2986
+ if (normalized.requireSignedInUser) {
2987
+ if (!replayUserEmail(initialProperties)) {
2988
+ return {
2989
+ started: false,
2990
+ reason: "missing-user-id",
2991
+ sessionId,
2992
+ sampled,
2993
+ };
2994
+ }
2995
+ }
2996
+ if (!isUrlRecordable(window.location.href, normalized)) {
2997
+ return { started: false, reason: "url-blocked", sessionId, sampled };
2998
+ }
2999
+
3000
+ const state = getState();
3001
+ if (state.active && state.replayId) {
3002
+ return {
3003
+ started: true,
3004
+ reason: "already-active",
3005
+ replayId: state.replayId,
3006
+ sessionId,
3007
+ sampled,
3008
+ };
3009
+ }
3010
+ if (state.startPromise) return state.startPromise;
3011
+
3012
+ // This is a new caller-initiated recording episode. A prior episode's
3013
+ // conflict-loop guard must not prevent this one from recovering once.
3014
+ state.automaticConflictRestartAttempted = false;
3015
+ state.transientClientErrorFailures = 0;
3016
+ const startGeneration = ++state.startGeneration;
3017
+
3018
+ let startPromise: Promise<SessionReplayStartResult>;
3019
+ startPromise = startSessionReplayRecorder(
3020
+ state,
3021
+ normalized,
3022
+ sessionId,
3023
+ sampled,
3024
+ initialProperties,
3025
+ startGeneration,
3026
+ ).finally(() => {
3027
+ if (state.startPromise === startPromise) {
3028
+ state.startPromise = null;
3029
+ }
3030
+ });
3031
+ state.startPromise = startPromise;
3032
+ return startPromise;
3033
+ }
3034
+
3035
+ async function startSessionReplayRecorder(
3036
+ state: SessionReplayState,
3037
+ normalized: NormalizedSessionReplayOptions,
3038
+ sessionId: string,
3039
+ sampled: boolean,
3040
+ initialProperties: Record<string, unknown> | undefined,
3041
+ startGeneration: number,
3042
+ ): Promise<SessionReplayStartResult> {
3043
+ if (state.active && state.replayId) {
3044
+ return {
3045
+ started: true,
3046
+ reason: "already-active",
3047
+ replayId: state.replayId,
3048
+ sessionId,
3049
+ sampled,
3050
+ };
3051
+ }
3052
+
3053
+ let rrweb: RrwebRecordModule;
3054
+ try {
3055
+ rrweb = (await import("@rrweb/record")) as RrwebRecordModule;
3056
+ } catch {
3057
+ return { started: false, reason: "import-failed", sessionId, sampled };
3058
+ }
3059
+ if (state.startGeneration !== startGeneration) {
3060
+ return { started: false, reason: "disabled", sessionId, sampled };
3061
+ }
3062
+ if (normalized.shouldStart && !normalized.shouldStart()) {
3063
+ return { started: false, reason: "disabled", sessionId, sampled };
3064
+ }
3065
+
3066
+ let replaySession = getOrCreateReplaySession(
3067
+ sessionId,
3068
+ normalized.linkBaseUrl,
3069
+ );
3070
+ const instanceNonce = generateReplayId();
3071
+ const { channel: replayChannel, probeClaim } = createReplayClaimChannel(
3072
+ state,
3073
+ instanceNonce,
3074
+ );
3075
+ // Only a *resumed* id needs the duplicated-tab check -- a freshly minted
3076
+ // id can never collide with a recorder that's already running, so this
3077
+ // never adds startup latency for the common "new tab" case.
3078
+ if (replaySession.resumed && replayChannel) {
3079
+ const taken = await probeClaim(replaySession.replayId);
3080
+ if (taken) {
3081
+ const freshReplayId = generateReplayId();
3082
+ const freshStartedAtMs = Date.now();
3083
+ writeStoredReplaySession({
3084
+ sessionId,
3085
+ replayId: freshReplayId,
3086
+ startedAtMs: freshStartedAtMs,
3087
+ sequence: 0,
3088
+ ...(normalized.linkBaseUrl
3089
+ ? { linkBaseUrl: normalized.linkBaseUrl }
3090
+ : {}),
3091
+ });
3092
+ replaySession = {
3093
+ replayId: freshReplayId,
3094
+ startedAtMs: freshStartedAtMs,
3095
+ sequence: 0,
3096
+ ...(normalized.linkBaseUrl
3097
+ ? { linkBaseUrl: normalized.linkBaseUrl }
3098
+ : {}),
3099
+ resumed: false,
3100
+ };
3101
+ }
3102
+ }
3103
+ // stopSessionReplay may be called while the duplicate-tab probe is waiting.
3104
+ // Recheck both cancellation and the caller's live eligibility before rrweb
3105
+ // is activated so a deferred start cannot escape a route/auth teardown.
3106
+ if (
3107
+ state.startGeneration !== startGeneration ||
3108
+ (normalized.shouldStart && !normalized.shouldStart())
3109
+ ) {
3110
+ try {
3111
+ replayChannel?.close();
3112
+ } catch {
3113
+ // best-effort cleanup
3114
+ }
3115
+ return { started: false, reason: "disabled", sessionId, sampled };
3116
+ }
3117
+ state.options = normalized;
3118
+ state.replayId = replaySession.replayId;
3119
+ state.startedAtMs = replaySession.startedAtMs;
3120
+ state.replayLinkBaseUrl =
3121
+ replaySession.linkBaseUrl ?? normalized.linkBaseUrl ?? null;
3122
+ state.sequence = replaySession.sequence;
3123
+ state.queue = [];
3124
+ state.queuedBytes = 0;
3125
+ state.retryBatches = [];
3126
+ state.pendingFlushReason = null;
3127
+ for (const resolve of state.pendingFlushWaiters.splice(0)) resolve();
3128
+ state.awaitingFullSnapshot = false;
3129
+ state.resourceNodes.clear();
3130
+ state.stopRecorder = null;
3131
+ state.broadcastChannel = replayChannel;
3132
+ state.lastAuthenticatedProperties = replayUserEmail(initialProperties)
3133
+ ? { ...initialProperties }
3134
+ : null;
3135
+ state.active = true;
3136
+
3137
+ try {
3138
+ const stopRecorder = rrweb.record({
3139
+ emit: (event) => enqueueReplayEvent(state, event),
3140
+ sampling: normalized.eventSampling,
3141
+ checkoutEveryNth: normalized.checkoutEveryNth,
3142
+ checkoutEveryNms: normalized.checkoutEveryNms,
3143
+ inlineStylesheet: normalized.inlineStylesheet,
3144
+ blockSelector: normalized.blockSelector,
3145
+ ignoreSelector: normalized.ignoreSelector,
3146
+ maskTextClass: normalized.maskTextClass,
3147
+ maskTextSelector: normalized.maskTextSelector,
3148
+ maskAllInputs: normalized.maskAllInputs,
3149
+ recordCanvas: normalized.recordCanvas,
3150
+ recordCrossOriginIframes: normalized.recordCrossOriginIframes,
3151
+ collectFonts: normalized.collectFonts,
3152
+ inlineImages: normalized.inlineImages,
3153
+ maskInputOptions: DEFAULT_MASK_INPUT_OPTIONS,
3154
+ });
3155
+ if (typeof stopRecorder !== "function") {
3156
+ state.active = false;
3157
+ state.options = null;
3158
+ state.replayId = null;
3159
+ state.startedAtMs = null;
3160
+ state.lastAuthenticatedProperties = null;
3161
+ try {
3162
+ state.broadcastChannel?.close();
3163
+ } catch {
3164
+ // best-effort cleanup
3165
+ }
3166
+ state.broadcastChannel = null;
3167
+ return { started: false, reason: "record-failed", sessionId, sampled };
3168
+ }
3169
+ state.stopRecorder = stopRecorder;
3170
+ state.flushTimer = window.setInterval(
3171
+ () => void flushSessionReplay("interval"),
3172
+ normalized.flushIntervalMs,
3173
+ );
3174
+ state.maxDurationTimer = window.setTimeout(
3175
+ () => stopSessionReplay("max-duration"),
3176
+ normalized.maxDurationMs,
3177
+ );
3178
+ installUrlMonitor(state);
3179
+ installLifecycleListeners(state);
3180
+ installSessionReplayIframeBridge(state, normalized);
3181
+ state.addCustomEvent =
3182
+ typeof rrweb.record.addCustomEvent === "function"
3183
+ ? rrweb.record.addCustomEvent
3184
+ : null;
3185
+ installCaptureInterceptors(state);
3186
+ return {
3187
+ started: true,
3188
+ replayId: state.replayId,
3189
+ sessionId,
3190
+ sampled,
3191
+ };
3192
+ } catch {
3193
+ try {
3194
+ state.restoreCaptures?.();
3195
+ } catch {
3196
+ // best-effort interceptor teardown
3197
+ }
3198
+ state.restoreCaptures = null;
3199
+ state.restoreIframeBridge?.();
3200
+ state.addCustomEvent = null;
3201
+ state.active = false;
3202
+ state.options = null;
3203
+ state.replayId = null;
3204
+ state.startedAtMs = null;
3205
+ state.lastAuthenticatedProperties = null;
3206
+ try {
3207
+ state.broadcastChannel?.close();
3208
+ } catch {
3209
+ // best-effort cleanup
3210
+ }
3211
+ state.broadcastChannel = null;
3212
+ return { started: false, reason: "record-failed", sessionId, sampled };
3213
+ }
3214
+ }
3215
+
3216
+ export async function stopSessionReplay(reason = "manual"): Promise<void> {
3217
+ const state = getState();
3218
+ // Invalidate an import/probe that has not set `active` yet. Without this,
3219
+ // stop during the duplicated-tab claim window was a no-op and rrweb started
3220
+ // after the caller believed recording had been disabled.
3221
+ state.startGeneration += 1;
3222
+ if (!state.active) return;
3223
+ // Restore console/fetch/XHR before tearing down the recorder: the restore
3224
+ // flushes any pending collapsed console duplicate, which must still be able
3225
+ // to emit through rrweb while it is recording.
3226
+ try {
3227
+ state.restoreCaptures?.();
3228
+ } catch {
3229
+ // best-effort interceptor teardown
3230
+ }
3231
+ state.restoreCaptures = null;
3232
+ state.restoreIframeBridge?.();
3233
+ state.active = false;
3234
+ state.addCustomEvent = null;
3235
+ try {
3236
+ state.stopRecorder?.();
3237
+ } catch {
3238
+ // best-effort recorder shutdown
3239
+ }
3240
+ state.stopRecorder = null;
3241
+ if (state.flushTimer) {
3242
+ window.clearInterval(state.flushTimer);
3243
+ state.flushTimer = null;
3244
+ }
3245
+ if (state.maxDurationTimer) {
3246
+ window.clearTimeout(state.maxDurationTimer);
3247
+ state.maxDurationTimer = null;
3248
+ }
3249
+ state.restoreUrlMonitor?.();
3250
+ state.removeLifecycleListeners?.();
3251
+ try {
3252
+ state.broadcastChannel?.close();
3253
+ } catch {
3254
+ // best-effort cleanup
3255
+ }
3256
+ state.broadcastChannel = null;
3257
+ await flushSessionReplay(reason);
3258
+ }
3259
+
3260
+ export function maybeStartSessionReplay(
3261
+ options: SessionReplayOptions = {},
3262
+ ): Promise<SessionReplayStartResult> {
3263
+ return startSessionReplay(options);
3264
+ }
3265
+
3266
+ export function isSessionReplayActive(): boolean {
3267
+ return getState().active;
3268
+ }
3269
+
3270
+ /**
3271
+ * The active session replay id when a recording is running, or the last one
3272
+ * persisted for this analytics session in this tab's `sessionStorage`.
3273
+ * First-party error capture uses this to tie each captured exception to the
3274
+ * replay it happened in, so triage can jump straight to
3275
+ * `/sessions/<recordingId>`.
3276
+ */
3277
+ export function getSessionReplayId(): string | null {
3278
+ const state = getState();
3279
+ if (state.active && state.replayId) return state.replayId;
3280
+ const stored = readStoredReplaySession();
3281
+ return stored?.replayId ?? null;
3282
+ }
3283
+
3284
+ /**
3285
+ * Return the replay identity associated with this browser tab. The context is
3286
+ * intentionally small so it can be attached to analytics events without
3287
+ * exposing replay content or credentials.
3288
+ */
3289
+ export function getSessionReplayContext(): SessionReplayContext | null {
3290
+ const state = getState();
3291
+ if (state.active && state.replayId && state.startedAtMs) {
3292
+ const sessionId = getOrCreateAnalyticsSessionId();
3293
+ if (!sessionId) return null;
3294
+ return {
3295
+ replayId: state.replayId,
3296
+ sessionId,
3297
+ startedAtMs: state.startedAtMs,
3298
+ startedAt: new Date(state.startedAtMs).toISOString(),
3299
+ linkBaseUrl: state.replayLinkBaseUrl,
3300
+ active: true,
3301
+ };
3302
+ }
3303
+
3304
+ const stored = readStoredReplaySession();
3305
+ if (!stored?.replayId || !stored.sessionId || !stored.startedAtMs) {
3306
+ return null;
3307
+ }
3308
+ return {
3309
+ replayId: stored.replayId,
3310
+ sessionId: stored.sessionId,
3311
+ startedAtMs: stored.startedAtMs,
3312
+ startedAt: new Date(stored.startedAtMs).toISOString(),
3313
+ linkBaseUrl: stored.linkBaseUrl ?? null,
3314
+ active: false,
3315
+ };
3316
+ }
3317
+
3318
+ /**
3319
+ * Build a scoped Analytics replay lookup link. The Analytics server resolves
3320
+ * the client replay id to its server recording id and converts the event time
3321
+ * into the replay player's offset before redirecting to the detail page.
3322
+ */
3323
+ export function getSessionReplayUrl(
3324
+ options: SessionReplayLinkOptions = {},
3325
+ ): string | null {
3326
+ const context = getSessionReplayContext();
3327
+ if (!context) return null;
3328
+ const base = normalizeReplayLinkBaseUrl(
3329
+ options.linkBaseUrl ?? context.linkBaseUrl ?? undefined,
3330
+ );
3331
+ if (!base) return null;
3332
+
3333
+ try {
3334
+ const url = new URL("/sessions/lookup", base);
3335
+ url.searchParams.set("sessionId", context.sessionId);
3336
+ url.searchParams.set("replayId", context.replayId);
3337
+ const at = options.at === undefined ? Date.now() : options.at;
3338
+ const timestamp =
3339
+ typeof at === "number"
3340
+ ? new Date(at)
3341
+ : typeof at === "string"
3342
+ ? new Date(at)
3343
+ : at;
3344
+ if (!Number.isNaN(timestamp.getTime())) {
3345
+ url.searchParams.set("at", timestamp.toISOString());
3346
+ }
3347
+ return url.toString();
3348
+ } catch {
3349
+ return null;
3350
+ }
3351
+ }
3352
+
3353
+ /**
3354
+ * Surface a manually captured exception on the active session replay timeline
3355
+ * as an `agent-native.console` custom event, reusing the diagnostics contract
3356
+ * (`level`, `source: "console"`, `message`, `stack`, `url`). No-op when no
3357
+ * recording is active. Auto-captured `window.onerror` / `unhandledrejection`
3358
+ * are intentionally NOT routed here — the recorder already logs those as
3359
+ * `window-error` / `unhandledrejection`, so re-emitting would double-count.
3360
+ */
3361
+ export function emitSessionReplayException(input: {
3362
+ type: string;
3363
+ message: string;
3364
+ level?: "fatal" | "error" | "warning" | "info" | "debug";
3365
+ stack?: string;
3366
+ url?: string;
3367
+ }): void {
3368
+ const state = getState();
3369
+ if (!state.active || !state.addCustomEvent) return;
3370
+ const level =
3371
+ input.level === "warning"
3372
+ ? "warn"
3373
+ : input.level === "info" || input.level === "debug"
3374
+ ? input.level
3375
+ : "error";
3376
+ emitReplayCustomEvent(state, SESSION_REPLAY_CONSOLE_EVENT_TAG, {
3377
+ level,
3378
+ source: "console",
3379
+ message: `${input.type}: ${input.message}`.slice(
3380
+ 0,
3381
+ MAX_CONSOLE_MESSAGE_LENGTH,
3382
+ ),
3383
+ ...(input.stack
3384
+ ? { stack: input.stack.slice(0, MAX_CONSOLE_STACK_LENGTH) }
3385
+ : {}),
3386
+ ...(input.url ? { url: input.url } : {}),
3387
+ });
3388
+ }
3389
+
3390
+ export type SessionReplayAgentChatEvent = {
3391
+ phase: "surface-mounted" | "run-observed" | "run-stopped";
3392
+ surface: string;
3393
+ threadId?: string;
3394
+ runId?: string;
3395
+ tabId?: string;
3396
+ };
3397
+
3398
+ /**
3399
+ * Add a content-free chat lifecycle marker to the active replay. The payload
3400
+ * deliberately accepts only ids and low-cardinality state; prompt/response
3401
+ * content must never enter replay diagnostics through this path.
3402
+ */
3403
+ export function emitSessionReplayAgentChatEvent(
3404
+ input: SessionReplayAgentChatEvent,
3405
+ ): void {
3406
+ const state = getState();
3407
+ if (!state.active || !state.addCustomEvent) return;
3408
+ const bounded = (value: string | undefined, max = 160) =>
3409
+ value?.trim().slice(0, max) || undefined;
3410
+ emitReplayCustomEvent(state, SESSION_REPLAY_AGENT_CHAT_EVENT_TAG, {
3411
+ phase: input.phase,
3412
+ surface: bounded(input.surface, 80) ?? "app",
3413
+ ...(bounded(input.threadId) ? { threadId: bounded(input.threadId) } : {}),
3414
+ ...(bounded(input.runId) ? { runId: bounded(input.runId) } : {}),
3415
+ ...(bounded(input.tabId) ? { tabId: bounded(input.tabId) } : {}),
3416
+ });
3417
+ }