@agent-native/core 0.91.2 → 0.92.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 (538) hide show
  1. package/corpus/README.md +2 -2
  2. package/corpus/core/CHANGELOG.md +31 -0
  3. package/corpus/core/docs/content/faq.mdx +17 -0
  4. package/corpus/core/docs/content/notifications.mdx +12 -6
  5. package/corpus/core/package.json +1 -1
  6. package/corpus/core/src/agent/production-agent.ts +140 -46
  7. package/corpus/core/src/agent/tool-search.ts +71 -4
  8. package/corpus/core/src/application-state/store.ts +12 -1
  9. package/corpus/core/src/cli/index.ts +20 -0
  10. package/corpus/core/src/cli/upgrade.ts +798 -0
  11. package/corpus/core/src/client/AssistantChat.tsx +28 -15
  12. package/corpus/core/src/client/HighlightedCodeBlock.tsx +20 -3
  13. package/corpus/core/src/client/PoweredByBadge.tsx +1 -8
  14. package/corpus/core/src/client/agent-chat-adapter.ts +68 -16
  15. package/corpus/core/src/client/chat/message-components.tsx +85 -8
  16. package/corpus/core/src/client/chat/tool-call-display.tsx +355 -144
  17. package/corpus/core/src/client/chat/widgets/DataTableWidget.tsx +52 -1
  18. package/corpus/core/src/client/code-agent-chat-adapter.ts +2 -9
  19. package/corpus/core/src/client/db-admin/TableEditor.tsx +49 -0
  20. package/corpus/core/src/client/db-admin/export-utils.ts +13 -4
  21. package/corpus/core/src/client/db-admin/useAgentSync.ts +72 -0
  22. package/corpus/core/src/client/error-capture.ts +69 -7
  23. package/corpus/core/src/client/extensions/EmbeddedExtension.tsx +5 -1
  24. package/corpus/core/src/client/extensions/ExtensionViewer.tsx +53 -8
  25. package/corpus/core/src/client/extensions/InlineExtensionFrame.tsx +5 -1
  26. package/corpus/core/src/client/extensions/portable-extension.ts +1 -0
  27. package/corpus/core/src/client/org/OrgSwitcher.tsx +12 -9
  28. package/corpus/core/src/client/org/TeamPage.tsx +77 -71
  29. package/corpus/core/src/client/session-replay.ts +3 -97
  30. package/corpus/core/src/client/settings/SettingsPanel.tsx +738 -441
  31. package/corpus/core/src/client/settings/SettingsSection.tsx +161 -17
  32. package/corpus/core/src/client/settings/SettingsTabsPage.tsx +25 -9
  33. package/corpus/core/src/client/sse-event-processor.ts +29 -2
  34. package/corpus/core/src/db-admin/agent-tools.ts +8 -2
  35. package/corpus/core/src/db-admin/operations.ts +116 -4
  36. package/corpus/core/src/db-admin/routes.ts +1 -0
  37. package/corpus/core/src/db-admin/types.ts +4 -0
  38. package/corpus/core/src/extensions/actions.ts +113 -5
  39. package/corpus/core/src/extensions/html-shell.ts +5 -4
  40. package/corpus/core/src/extensions/routes.ts +10 -0
  41. package/corpus/core/src/navigation/index.ts +1 -1
  42. package/corpus/core/src/notifications/channels.ts +88 -25
  43. package/corpus/core/src/notifications/registry.ts +17 -3
  44. package/corpus/core/src/notifications/types.ts +4 -1
  45. package/corpus/core/src/resources/handlers.ts +10 -8
  46. package/corpus/core/src/server/agent-chat-plugin.ts +7 -3
  47. package/corpus/core/src/server/poll.ts +293 -8
  48. package/corpus/core/src/server/request-context.ts +7 -0
  49. package/corpus/core/src/styles/agent-conversation.css +1 -1
  50. package/corpus/core/src/styles/agent-native.css +27 -0
  51. package/corpus/core/src/templates/default/.agents/skills/actions/SKILL.md +5 -0
  52. package/corpus/core/src/templates/default/.agents/skills/adding-a-feature/SKILL.md +5 -0
  53. package/corpus/core/src/templates/default/.agents/skills/real-time-sync/SKILL.md +1 -1
  54. package/corpus/core/src/templates/default/.agents/skills/security/SKILL.md +7 -0
  55. package/corpus/core/src/templates/default/.agents/skills/self-modifying-code/SKILL.md +20 -2
  56. package/corpus/core/src/templates/default/.agents/skills/storing-data/SKILL.md +5 -1
  57. package/corpus/core/src/templates/default/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
  58. package/corpus/core/src/templates/default/AGENTS.md +14 -5
  59. package/corpus/core/src/templates/default/package.json +2 -1
  60. package/corpus/core/src/templates/headless/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
  61. package/corpus/core/src/templates/headless/AGENTS.md +13 -3
  62. package/corpus/core/src/templates/headless/package.json +1 -0
  63. package/corpus/core/src/templates/workspace-core/.agents/skills/actions/SKILL.md +5 -0
  64. package/corpus/core/src/templates/workspace-core/.agents/skills/adding-a-feature/SKILL.md +5 -0
  65. package/corpus/core/src/templates/workspace-core/.agents/skills/context-awareness/SKILL.md +4 -0
  66. package/corpus/core/src/templates/workspace-core/.agents/skills/extensions/SKILL.md +6 -0
  67. package/corpus/core/src/templates/workspace-core/.agents/skills/performance/SKILL.md +4 -0
  68. package/corpus/core/src/templates/workspace-core/.agents/skills/real-time-sync/SKILL.md +1 -1
  69. package/corpus/core/src/templates/workspace-core/.agents/skills/security/SKILL.md +7 -0
  70. package/corpus/core/src/templates/workspace-core/.agents/skills/self-modifying-code/SKILL.md +20 -2
  71. package/corpus/core/src/templates/workspace-core/.agents/skills/storing-data/SKILL.md +5 -1
  72. package/corpus/core/src/templates/workspace-core/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
  73. package/corpus/core/src/templates/workspace-core/AGENTS.md +7 -1
  74. package/corpus/core/src/templates/workspace-root/AGENTS.md +17 -5
  75. package/corpus/core/src/templates/workspace-root/package.json +1 -0
  76. package/corpus/templates/analytics/.agents/skills/actions/SKILL.md +5 -0
  77. package/corpus/templates/analytics/.agents/skills/dashboard-management/SKILL.md +10 -0
  78. package/corpus/templates/analytics/.agents/skills/real-time-sync/SKILL.md +1 -1
  79. package/corpus/templates/analytics/.agents/skills/security/SKILL.md +7 -0
  80. package/corpus/templates/analytics/.agents/skills/self-modifying-code/SKILL.md +20 -2
  81. package/corpus/templates/analytics/.agents/skills/session-replay/SKILL.md +5 -6
  82. package/corpus/templates/analytics/.agents/skills/storing-data/SKILL.md +5 -1
  83. package/corpus/templates/analytics/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
  84. package/corpus/templates/analytics/AGENTS.md +39 -18
  85. package/corpus/templates/analytics/actions/capture-test-error.ts +4 -0
  86. package/corpus/templates/analytics/actions/get-analytics-alert-rule-defaults.ts +23 -0
  87. package/corpus/templates/analytics/actions/get-error-issue.ts +1 -1
  88. package/corpus/templates/analytics/actions/list-analysis-revisions.ts +40 -0
  89. package/corpus/templates/analytics/actions/list-dashboard-revisions.ts +40 -0
  90. package/corpus/templates/analytics/actions/list-dashboard-usage-stats.ts +299 -0
  91. package/corpus/templates/analytics/actions/navigate.ts +4 -2
  92. package/corpus/templates/analytics/actions/restore-analysis-revision.ts +43 -0
  93. package/corpus/templates/analytics/actions/restore-dashboard-revision.ts +68 -0
  94. package/corpus/templates/analytics/actions/save-analytics-alert-rule.ts +14 -0
  95. package/corpus/templates/analytics/actions/save-monitor.ts +15 -1
  96. package/corpus/templates/analytics/actions/view-screen.ts +24 -0
  97. package/corpus/templates/analytics/app/components/analysis/AnalysisHistoryPanel.tsx +181 -0
  98. package/corpus/templates/analytics/app/components/dashboard/DashboardHistoryPanel.tsx +184 -0
  99. package/corpus/templates/analytics/app/components/dashboard/DataTable.tsx +41 -6
  100. package/corpus/templates/analytics/app/components/dashboard/SqlChart.tsx +122 -11
  101. package/corpus/templates/analytics/app/components/layout/CommandPalette.tsx +22 -16
  102. package/corpus/templates/analytics/app/components/layout/Header.tsx +1 -1
  103. package/corpus/templates/analytics/app/components/layout/Sidebar.tsx +27 -21
  104. package/corpus/templates/analytics/app/hooks/use-analysis-revisions.ts +43 -0
  105. package/corpus/templates/analytics/app/hooks/use-dashboard-chat-context.ts +110 -0
  106. package/corpus/templates/analytics/app/hooks/use-dashboard-revisions.ts +43 -0
  107. package/corpus/templates/analytics/app/hooks/use-navigation-state.ts +8 -3
  108. package/corpus/templates/analytics/app/i18n/zh-TW.ts +64 -2
  109. package/corpus/templates/analytics/app/i18n-data.ts +783 -3
  110. package/corpus/templates/analytics/app/pages/Agents.tsx +359 -19
  111. package/corpus/templates/analytics/app/pages/Settings.tsx +3 -2
  112. package/corpus/templates/analytics/app/pages/adhoc/explorer-dashboard/index.tsx +34 -1
  113. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/index.tsx +28 -0
  114. package/corpus/templates/analytics/app/pages/analyses/AnalysisDetail.tsx +18 -1
  115. package/corpus/templates/analytics/app/pages/monitoring/ErrorsPanel.tsx +7 -10
  116. package/corpus/templates/analytics/app/pages/monitoring/MonitoringPage.tsx +8 -2
  117. package/corpus/templates/analytics/app/pages/monitoring/errors/IssueDetail.tsx +479 -23
  118. package/corpus/templates/analytics/app/pages/monitoring/errors/IssueList.tsx +0 -9
  119. package/corpus/templates/analytics/app/pages/monitoring/errors/i18n.ts +11 -0
  120. package/corpus/templates/analytics/app/pages/monitoring/errors/types.ts +9 -1
  121. package/corpus/templates/analytics/app/pages/monitoring/uptime/MonitorFormPage.tsx +157 -35
  122. package/corpus/templates/analytics/app/pages/monitoring/uptime/i18n.ts +17 -2
  123. package/corpus/templates/analytics/app/pages/monitoring/uptime/types.ts +4 -0
  124. package/corpus/templates/analytics/app/pages/sessions/SessionDetailPage.tsx +80 -220
  125. package/corpus/templates/analytics/app/pages/sessions/SessionsPage.tsx +9 -99
  126. package/corpus/templates/analytics/app/pages/settings/AlertRulesSettingsCard.tsx +262 -104
  127. package/corpus/templates/analytics/app/routes/team.tsx +1 -1
  128. package/corpus/templates/analytics/changelog/2026-07-08-admins-can-audit-dashboard-usage-ownership-and-cleanup-signa.md +6 -0
  129. package/corpus/templates/analytics/changelog/2026-07-08-agent-monitoring-stays-available-to-non-admins.md +6 -0
  130. package/corpus/templates/analytics/changelog/2026-07-08-alert-rules-are-easier-to-scan-expand-and-edit-from-settings.md +6 -0
  131. package/corpus/templates/analytics/changelog/2026-07-08-analytics-alert-forms-remember-last-email-recipients.md +6 -0
  132. package/corpus/templates/analytics/changelog/2026-07-08-analytics-alert-rules-can-now-use-per-rule-slack-and-webhook.md +6 -0
  133. package/corpus/templates/analytics/changelog/2026-07-08-dashboard-history-and-chat-context.md +5 -0
  134. package/corpus/templates/analytics/changelog/2026-07-08-dashboard-tables-keep-their-size-while-loading.md +6 -0
  135. package/corpus/templates/analytics/changelog/2026-07-08-dashboard-usage-now-counts-explorer-dashboard-traffic.md +6 -0
  136. package/corpus/templates/analytics/changelog/2026-07-08-demo-mode-session-emails-are-filtered-and-anonymized.md +6 -0
  137. package/corpus/templates/analytics/changelog/2026-07-08-error-issue-detail-adds-stack-traces-and-frequency-bars.md +6 -0
  138. package/corpus/templates/analytics/changelog/2026-07-08-error-issue-detail-now-shows-the-latest-occurrence-message-a.md +6 -0
  139. package/corpus/templates/analytics/changelog/2026-07-08-monitoring-errors-only-show-test-action-when-empty.md +6 -0
  140. package/corpus/templates/analytics/changelog/2026-07-08-monitoring-uptime-and-errors-tabs-now-clearly-show-which-view-is-active.md +6 -0
  141. package/corpus/templates/analytics/changelog/2026-07-08-production-session-replay-now-requires-private-blob-storage-.md +6 -0
  142. package/corpus/templates/analytics/changelog/2026-07-08-session-replay-uses-stock-rrweb-sizing-again-so-recorded.md +6 -0
  143. package/corpus/templates/analytics/changelog/2026-07-08-shared-extension-links-now-show-a-clear-message-when-you-do-.md +6 -0
  144. package/corpus/templates/analytics/changelog/2026-07-08-sidebar-dashboard-menus-now-open-above-the-dashboard-canvas.md +6 -0
  145. package/corpus/templates/analytics/changelog/2026-07-08-team-settings-show-access-controls-next-to-member-lists.md +6 -0
  146. package/corpus/templates/analytics/changelog/2026-07-08-uptime-alerting-clarifies-in-app-inbox-vs-email-and-lets-you-paste-slack-or-webhook-urls-per-monitor.md +6 -0
  147. package/corpus/templates/analytics/changelog/2026-07-08-uptime-monitors-now-use-a-10-second-request-timeout-by-defau.md +6 -0
  148. package/corpus/templates/analytics/docs/uptime-monitoring.md +13 -11
  149. package/corpus/templates/analytics/server/db/schema-monitoring.ts +5 -1
  150. package/corpus/templates/analytics/server/db/schema.ts +56 -0
  151. package/corpus/templates/analytics/server/lib/analytics-alerts.ts +92 -0
  152. package/corpus/templates/analytics/server/lib/dashboards-store.ts +334 -5
  153. package/corpus/templates/analytics/server/lib/error-capture.ts +231 -36
  154. package/corpus/templates/analytics/server/lib/session-replay.ts +114 -8
  155. package/corpus/templates/analytics/server/lib/uptime-monitors.ts +65 -16
  156. package/corpus/templates/analytics/server/plugins/db.ts +122 -2
  157. package/corpus/templates/analytics/server/routes/api/dashboard-agent-context.json.get.ts +1 -0
  158. package/corpus/templates/assets/.agents/skills/actions/SKILL.md +5 -0
  159. package/corpus/templates/assets/.agents/skills/real-time-sync/SKILL.md +1 -1
  160. package/corpus/templates/assets/.agents/skills/security/SKILL.md +7 -0
  161. package/corpus/templates/assets/.agents/skills/self-modifying-code/SKILL.md +20 -2
  162. package/corpus/templates/assets/.agents/skills/storing-data/SKILL.md +5 -1
  163. package/corpus/templates/assets/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
  164. package/corpus/templates/assets/AGENTS.md +4 -0
  165. package/corpus/templates/assets/app/routes/settings.tsx +1 -1
  166. package/corpus/templates/assets/app/routes/team.tsx +1 -1
  167. package/corpus/templates/brain/.agents/skills/actions/SKILL.md +5 -0
  168. package/corpus/templates/brain/.agents/skills/adding-a-feature/SKILL.md +5 -0
  169. package/corpus/templates/brain/.agents/skills/real-time-sync/SKILL.md +1 -1
  170. package/corpus/templates/brain/.agents/skills/security/SKILL.md +7 -0
  171. package/corpus/templates/brain/.agents/skills/self-modifying-code/SKILL.md +20 -2
  172. package/corpus/templates/brain/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
  173. package/corpus/templates/brain/AGENTS.md +4 -0
  174. package/corpus/templates/brain/app/routes/team.tsx +1 -1
  175. package/corpus/templates/calendar/.agents/skills/actions/SKILL.md +5 -0
  176. package/corpus/templates/calendar/.agents/skills/real-time-sync/SKILL.md +1 -1
  177. package/corpus/templates/calendar/.agents/skills/security/SKILL.md +7 -0
  178. package/corpus/templates/calendar/.agents/skills/self-modifying-code/SKILL.md +20 -2
  179. package/corpus/templates/calendar/.agents/skills/storing-data/SKILL.md +5 -1
  180. package/corpus/templates/calendar/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
  181. package/corpus/templates/calendar/AGENTS.md +4 -0
  182. package/corpus/templates/calendar/app/components/calendar/DayView.tsx +11 -2
  183. package/corpus/templates/calendar/app/components/calendar/EventDetailPopover.tsx +21 -8
  184. package/corpus/templates/calendar/app/components/calendar/MonthView.tsx +7 -1
  185. package/corpus/templates/calendar/app/components/calendar/WeekView.tsx +11 -2
  186. package/corpus/templates/calendar/app/lib/popover-click-guard.ts +48 -4
  187. package/corpus/templates/calendar/app/pages/Settings.tsx +1 -1
  188. package/corpus/templates/calendar/app/routes/_app.team.tsx +1 -1
  189. package/corpus/templates/calendar/changelog/2026-07-08-clicking-empty-calendar-space-closes-an-open-event-popover-b.md +6 -0
  190. package/corpus/templates/chat/.agents/skills/actions/SKILL.md +5 -0
  191. package/corpus/templates/chat/.agents/skills/adding-a-feature/SKILL.md +5 -0
  192. package/corpus/templates/chat/.agents/skills/real-time-sync/SKILL.md +1 -1
  193. package/corpus/templates/chat/.agents/skills/security/SKILL.md +7 -0
  194. package/corpus/templates/chat/.agents/skills/self-modifying-code/SKILL.md +20 -2
  195. package/corpus/templates/chat/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
  196. package/corpus/templates/chat/AGENTS.md +4 -0
  197. package/corpus/templates/chat/app/hooks/use-navigation-state.ts +1 -1
  198. package/corpus/templates/chat/app/routes/settings.tsx +2 -2
  199. package/corpus/templates/chat/app/routes/team.tsx +1 -1
  200. package/corpus/templates/clips/.agents/skills/actions/SKILL.md +5 -0
  201. package/corpus/templates/clips/.agents/skills/security/SKILL.md +7 -0
  202. package/corpus/templates/clips/.agents/skills/self-modifying-code/SKILL.md +20 -2
  203. package/corpus/templates/clips/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
  204. package/corpus/templates/clips/AGENTS.md +4 -0
  205. package/corpus/templates/clips/actions/finalize-recording.ts +9 -3
  206. package/corpus/templates/clips/actions/set-thumbnail.ts +11 -2
  207. package/corpus/templates/clips/app/components/meetings/canvas-editor.tsx +1 -7
  208. package/corpus/templates/clips/app/components/player/player-controls.tsx +21 -13
  209. package/corpus/templates/clips/app/components/player/video-player.tsx +30 -75
  210. package/corpus/templates/clips/app/i18n/ar-SA.ts +6 -5
  211. package/corpus/templates/clips/app/i18n/de-DE.ts +6 -6
  212. package/corpus/templates/clips/app/i18n/en-US.ts +6 -5
  213. package/corpus/templates/clips/app/i18n/es-ES.ts +6 -5
  214. package/corpus/templates/clips/app/i18n/fr-FR.ts +6 -6
  215. package/corpus/templates/clips/app/i18n/hi-IN.ts +6 -5
  216. package/corpus/templates/clips/app/i18n/ja-JP.ts +6 -5
  217. package/corpus/templates/clips/app/i18n/ko-KR.ts +6 -5
  218. package/corpus/templates/clips/app/i18n/pt-BR.ts +6 -5
  219. package/corpus/templates/clips/app/i18n/zh-CN.ts +6 -5
  220. package/corpus/templates/clips/app/i18n/zh-TW.ts +6 -5
  221. package/corpus/templates/clips/app/routes/_app.meetings.$meetingId.tsx +51 -118
  222. package/corpus/templates/clips/changelog/2026-07-08-desktop-meeting-notes-stay-active-from-tray-and-shortcut-controls.md +6 -0
  223. package/corpus/templates/clips/changelog/2026-07-08-desktop-meeting-popovers-can-start-notes-and-join-the-meetin.md +6 -0
  224. package/corpus/templates/clips/changelog/2026-07-08-hosted-clip-uploads-now-clear-failed-buffered-chunks.md +6 -0
  225. package/corpus/templates/clips/changelog/2026-07-08-hosted-recording-uploads-now-avoid-sql-chunk-storage.md +6 -0
  226. package/corpus/templates/clips/changelog/2026-07-08-meeting-notes-keep-their-recording-state-when-the-desktop.md +6 -0
  227. package/corpus/templates/clips/changelog/2026-07-08-meeting-recaps-now-keep-summaries-action-items-and-transcrip.md +6 -0
  228. package/corpus/templates/clips/changelog/2026-07-08-meeting-reminder-dropdowns-now-expand-fully-instead-of-being.md +6 -0
  229. package/corpus/templates/clips/changelog/2026-07-08-video-playback-controls-now-match-loom-style-5-second-skips-.md +6 -0
  230. package/corpus/templates/clips/desktop/src/app.tsx +123 -3
  231. package/corpus/templates/clips/desktop/src/hooks/useMeetingTranscription.ts +15 -1
  232. package/corpus/templates/clips/desktop/src/overlays/meeting-notification.tsx +23 -0
  233. package/corpus/templates/clips/desktop/src/styles.css +23 -1
  234. package/corpus/templates/clips/desktop/src-tauri/src/adhoc_meetings_watcher.rs +1 -3
  235. package/corpus/templates/clips/desktop/src-tauri/src/clips/mod.rs +40 -6
  236. package/corpus/templates/clips/desktop/src-tauri/src/lib.rs +4 -2
  237. package/corpus/templates/clips/desktop/src-tauri/src/meetings_watcher.rs +3 -5
  238. package/corpus/templates/clips/desktop/src-tauri/src/native_screen.rs +23 -19
  239. package/corpus/templates/clips/desktop/src-tauri/src/shortcuts.rs +3 -3
  240. package/corpus/templates/clips/desktop/src-tauri/src/silence_detector.rs +0 -1
  241. package/corpus/templates/clips/desktop/src-tauri/src/state.rs +5 -0
  242. package/corpus/templates/clips/desktop/src-tauri/src/tray.rs +18 -6
  243. package/corpus/templates/clips/server/lib/recording-upload-state.ts +63 -0
  244. package/corpus/templates/clips/server/lib/video-storage.ts +4 -0
  245. package/corpus/templates/clips/server/routes/api/recordings/[recordingId]/thumbnail.post.ts +19 -13
  246. package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/chunk.post.ts +40 -1
  247. package/corpus/templates/content/.agents/skills/actions/SKILL.md +5 -0
  248. package/corpus/templates/content/.agents/skills/real-time-sync/SKILL.md +1 -1
  249. package/corpus/templates/content/.agents/skills/security/SKILL.md +7 -0
  250. package/corpus/templates/content/.agents/skills/self-modifying-code/SKILL.md +20 -2
  251. package/corpus/templates/content/.agents/skills/storing-data/SKILL.md +5 -1
  252. package/corpus/templates/content/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
  253. package/corpus/templates/content/AGENTS.md +4 -0
  254. package/corpus/templates/content/actions/_batch-utils.ts +8 -0
  255. package/corpus/templates/content/actions/_builder-cms-read-client.ts +7 -5
  256. package/corpus/templates/content/actions/_database-source-utils.ts +776 -319
  257. package/corpus/templates/content/actions/_database-utils.ts +6 -1
  258. package/corpus/templates/content/actions/_property-utils.ts +168 -0
  259. package/corpus/templates/content/actions/add-content-database-source-field-property.ts +19 -12
  260. package/corpus/templates/content/actions/bind-content-database-source-field.ts +19 -12
  261. package/corpus/templates/content/actions/delete-document.ts +9 -13
  262. package/corpus/templates/content/actions/refresh-content-database-source.ts +5 -5
  263. package/corpus/templates/content/app/components/editor/body-hydration.ts +35 -11
  264. package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +47 -5
  265. package/corpus/templates/content/app/hooks/use-documents.ts +8 -2
  266. package/corpus/templates/content/app/routes/_app.settings.tsx +2 -2
  267. package/corpus/templates/content/app/routes/_app.team.tsx +1 -1
  268. package/corpus/templates/content/changelog/2026-07-08-builder-source-sync-now-completes-reliably-on-hosted-databases.md +6 -0
  269. package/corpus/templates/content/changelog/2026-07-08-fixed-builder-database-sync-so-large-sources-continue-past-t.md +6 -0
  270. package/corpus/templates/content/server/plugins/db.ts +19 -0
  271. package/corpus/templates/design/.agents/skills/actions/SKILL.md +5 -0
  272. package/corpus/templates/design/.agents/skills/security/SKILL.md +7 -0
  273. package/corpus/templates/design/.agents/skills/self-modifying-code/SKILL.md +20 -2
  274. package/corpus/templates/design/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
  275. package/corpus/templates/design/AGENTS.md +4 -0
  276. package/corpus/templates/design/actions/take-design-screenshot.ts +12 -9
  277. package/corpus/templates/design/app/components/design/inspector/ImageFillControls.tsx +57 -13
  278. package/corpus/templates/design/app/routes/team.tsx +1 -1
  279. package/corpus/templates/design/changelog/2026-07-08-local-images-added-to-designs-now-upload-to-file-storage-ins.md +6 -0
  280. package/corpus/templates/dispatch/.agents/skills/actions/SKILL.md +5 -0
  281. package/corpus/templates/dispatch/.agents/skills/security/SKILL.md +7 -0
  282. package/corpus/templates/dispatch/.agents/skills/self-modifying-code/SKILL.md +20 -2
  283. package/corpus/templates/dispatch/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
  284. package/corpus/templates/dispatch/AGENTS.md +4 -0
  285. package/corpus/templates/dispatch/app/hooks/use-navigation-state.ts +1 -1
  286. package/corpus/templates/dispatch/app/routes/settings.tsx +2 -2
  287. package/corpus/templates/dispatch/app/routes/team.tsx +1 -1
  288. package/corpus/templates/forms/.agents/skills/actions/SKILL.md +5 -0
  289. package/corpus/templates/forms/.agents/skills/real-time-sync/SKILL.md +1 -1
  290. package/corpus/templates/forms/.agents/skills/security/SKILL.md +7 -0
  291. package/corpus/templates/forms/.agents/skills/self-modifying-code/SKILL.md +20 -2
  292. package/corpus/templates/forms/.agents/skills/storing-data/SKILL.md +5 -1
  293. package/corpus/templates/forms/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
  294. package/corpus/templates/forms/AGENTS.md +4 -0
  295. package/corpus/templates/forms/app/routes/_app.settings.tsx +2 -2
  296. package/corpus/templates/forms/app/routes/_app.team.tsx +1 -1
  297. package/corpus/templates/forms/shared/navigation.ts +1 -1
  298. package/corpus/templates/macros/.agents/skills/security/SKILL.md +7 -0
  299. package/corpus/templates/macros/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
  300. package/corpus/templates/macros/AGENTS.md +4 -0
  301. package/corpus/templates/macros/app/routes/settings.tsx +2 -2
  302. package/corpus/templates/macros/app/routes/team.tsx +1 -1
  303. package/corpus/templates/mail/.agents/skills/actions/SKILL.md +5 -0
  304. package/corpus/templates/mail/.agents/skills/email-drafts/SKILL.md +1 -1
  305. package/corpus/templates/mail/.agents/skills/real-time-sync/SKILL.md +1 -1
  306. package/corpus/templates/mail/.agents/skills/security/SKILL.md +7 -0
  307. package/corpus/templates/mail/.agents/skills/self-modifying-code/SKILL.md +20 -2
  308. package/corpus/templates/mail/.agents/skills/storing-data/SKILL.md +5 -1
  309. package/corpus/templates/mail/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
  310. package/corpus/templates/mail/AGENTS.md +4 -0
  311. package/corpus/templates/mail/changelog/2026-07-08-mail-attachments-in-hosted-environments-now-require-file-sto.md +6 -0
  312. package/corpus/templates/mail/server/handlers/media.ts +26 -1
  313. package/corpus/templates/mail/server/lib/outgoing-email.ts +24 -1
  314. package/corpus/templates/mail/server/lib/upload-store.ts +2 -1
  315. package/corpus/templates/plan/.agents/skills/actions/SKILL.md +5 -0
  316. package/corpus/templates/plan/.agents/skills/adding-a-feature/SKILL.md +5 -0
  317. package/corpus/templates/plan/.agents/skills/real-time-sync/SKILL.md +1 -1
  318. package/corpus/templates/plan/.agents/skills/security/SKILL.md +7 -0
  319. package/corpus/templates/plan/.agents/skills/self-modifying-code/SKILL.md +20 -2
  320. package/corpus/templates/plan/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
  321. package/corpus/templates/plan/AGENTS.md +4 -0
  322. package/corpus/templates/plan/app/hooks/use-navigation-state.ts +1 -1
  323. package/corpus/templates/plan/app/routes/settings.tsx +2 -2
  324. package/corpus/templates/plan/app/routes/team.tsx +1 -1
  325. package/corpus/templates/plan/changelog/2026-07-08-hosted-plan-images-now-require-connected-storage.md +6 -0
  326. package/corpus/templates/plan/server/lib/plan-assets.ts +33 -4
  327. package/corpus/templates/slides/.agents/skills/actions/SKILL.md +5 -0
  328. package/corpus/templates/slides/.agents/skills/security/SKILL.md +7 -0
  329. package/corpus/templates/slides/.agents/skills/self-modifying-code/SKILL.md +20 -2
  330. package/corpus/templates/slides/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
  331. package/corpus/templates/slides/AGENTS.md +4 -0
  332. package/corpus/templates/slides/actions/generate-image-api.ts +15 -3
  333. package/corpus/templates/slides/app/routes/settings.tsx +2 -2
  334. package/corpus/templates/slides/app/routes/team.tsx +1 -1
  335. package/corpus/templates/slides/changelog/2026-07-08-generated-slide-images-now-return-hosted-file-storage-urls-i.md +6 -0
  336. package/corpus/templates/slides/shared/api.ts +1 -1
  337. package/corpus/templates/videos/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
  338. package/corpus/templates/voice/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
  339. package/dist/agent/production-agent.d.ts +5 -1
  340. package/dist/agent/production-agent.d.ts.map +1 -1
  341. package/dist/agent/production-agent.js +112 -42
  342. package/dist/agent/production-agent.js.map +1 -1
  343. package/dist/agent/tool-search.d.ts +2 -0
  344. package/dist/agent/tool-search.d.ts.map +1 -1
  345. package/dist/agent/tool-search.js +60 -4
  346. package/dist/agent/tool-search.js.map +1 -1
  347. package/dist/application-state/store.d.ts.map +1 -1
  348. package/dist/application-state/store.js +8 -2
  349. package/dist/application-state/store.js.map +1 -1
  350. package/dist/cli/index.js +19 -0
  351. package/dist/cli/index.js.map +1 -1
  352. package/dist/cli/upgrade.d.ts +100 -0
  353. package/dist/cli/upgrade.d.ts.map +1 -0
  354. package/dist/cli/upgrade.js +678 -0
  355. package/dist/cli/upgrade.js.map +1 -0
  356. package/dist/client/AssistantChat.d.ts.map +1 -1
  357. package/dist/client/AssistantChat.js +25 -14
  358. package/dist/client/AssistantChat.js.map +1 -1
  359. package/dist/client/HighlightedCodeBlock.d.ts +2 -0
  360. package/dist/client/HighlightedCodeBlock.d.ts.map +1 -1
  361. package/dist/client/HighlightedCodeBlock.js +10 -1
  362. package/dist/client/HighlightedCodeBlock.js.map +1 -1
  363. package/dist/client/PoweredByBadge.d.ts +1 -1
  364. package/dist/client/PoweredByBadge.d.ts.map +1 -1
  365. package/dist/client/PoweredByBadge.js +3 -9
  366. package/dist/client/PoweredByBadge.js.map +1 -1
  367. package/dist/client/agent-chat-adapter.d.ts.map +1 -1
  368. package/dist/client/agent-chat-adapter.js +60 -13
  369. package/dist/client/agent-chat-adapter.js.map +1 -1
  370. package/dist/client/chat/message-components.d.ts.map +1 -1
  371. package/dist/client/chat/message-components.js +61 -8
  372. package/dist/client/chat/message-components.js.map +1 -1
  373. package/dist/client/chat/tool-call-display.d.ts +12 -0
  374. package/dist/client/chat/tool-call-display.d.ts.map +1 -1
  375. package/dist/client/chat/tool-call-display.js +136 -42
  376. package/dist/client/chat/tool-call-display.js.map +1 -1
  377. package/dist/client/chat/widgets/DataTableWidget.d.ts.map +1 -1
  378. package/dist/client/chat/widgets/DataTableWidget.js +19 -2
  379. package/dist/client/chat/widgets/DataTableWidget.js.map +1 -1
  380. package/dist/client/code-agent-chat-adapter.js +2 -9
  381. package/dist/client/code-agent-chat-adapter.js.map +1 -1
  382. package/dist/client/db-admin/TableEditor.d.ts.map +1 -1
  383. package/dist/client/db-admin/TableEditor.js +19 -2
  384. package/dist/client/db-admin/TableEditor.js.map +1 -1
  385. package/dist/client/db-admin/export-utils.d.ts +2 -0
  386. package/dist/client/db-admin/export-utils.d.ts.map +1 -1
  387. package/dist/client/db-admin/export-utils.js +11 -3
  388. package/dist/client/db-admin/export-utils.js.map +1 -1
  389. package/dist/client/db-admin/useAgentSync.d.ts.map +1 -1
  390. package/dist/client/db-admin/useAgentSync.js +63 -0
  391. package/dist/client/db-admin/useAgentSync.js.map +1 -1
  392. package/dist/client/error-capture.d.ts.map +1 -1
  393. package/dist/client/error-capture.js +41 -7
  394. package/dist/client/error-capture.js.map +1 -1
  395. package/dist/client/extensions/EmbeddedExtension.d.ts.map +1 -1
  396. package/dist/client/extensions/EmbeddedExtension.js +3 -1
  397. package/dist/client/extensions/EmbeddedExtension.js.map +1 -1
  398. package/dist/client/extensions/ExtensionViewer.d.ts.map +1 -1
  399. package/dist/client/extensions/ExtensionViewer.js +32 -5
  400. package/dist/client/extensions/ExtensionViewer.js.map +1 -1
  401. package/dist/client/extensions/InlineExtensionFrame.d.ts.map +1 -1
  402. package/dist/client/extensions/InlineExtensionFrame.js +3 -1
  403. package/dist/client/extensions/InlineExtensionFrame.js.map +1 -1
  404. package/dist/client/extensions/portable-extension.d.ts.map +1 -1
  405. package/dist/client/extensions/portable-extension.js +1 -0
  406. package/dist/client/extensions/portable-extension.js.map +1 -1
  407. package/dist/client/org/OrgSwitcher.d.ts +1 -1
  408. package/dist/client/org/OrgSwitcher.d.ts.map +1 -1
  409. package/dist/client/org/OrgSwitcher.js +11 -6
  410. package/dist/client/org/OrgSwitcher.js.map +1 -1
  411. package/dist/client/org/TeamPage.d.ts.map +1 -1
  412. package/dist/client/org/TeamPage.js +5 -5
  413. package/dist/client/org/TeamPage.js.map +1 -1
  414. package/dist/client/session-replay.d.ts +0 -8
  415. package/dist/client/session-replay.d.ts.map +1 -1
  416. package/dist/client/session-replay.js +3 -80
  417. package/dist/client/session-replay.js.map +1 -1
  418. package/dist/client/settings/SettingsPanel.d.ts.map +1 -1
  419. package/dist/client/settings/SettingsPanel.js +89 -33
  420. package/dist/client/settings/SettingsPanel.js.map +1 -1
  421. package/dist/client/settings/SettingsSection.d.ts +19 -4
  422. package/dist/client/settings/SettingsSection.d.ts.map +1 -1
  423. package/dist/client/settings/SettingsSection.js +33 -4
  424. package/dist/client/settings/SettingsSection.js.map +1 -1
  425. package/dist/client/settings/SettingsTabsPage.d.ts.map +1 -1
  426. package/dist/client/settings/SettingsTabsPage.js +21 -10
  427. package/dist/client/settings/SettingsTabsPage.js.map +1 -1
  428. package/dist/client/sse-event-processor.d.ts +9 -0
  429. package/dist/client/sse-event-processor.d.ts.map +1 -1
  430. package/dist/client/sse-event-processor.js +20 -2
  431. package/dist/client/sse-event-processor.js.map +1 -1
  432. package/dist/db-admin/agent-tools.d.ts.map +1 -1
  433. package/dist/db-admin/agent-tools.js +7 -2
  434. package/dist/db-admin/agent-tools.js.map +1 -1
  435. package/dist/db-admin/operations.d.ts.map +1 -1
  436. package/dist/db-admin/operations.js +90 -4
  437. package/dist/db-admin/operations.js.map +1 -1
  438. package/dist/db-admin/routes.d.ts.map +1 -1
  439. package/dist/db-admin/routes.js +1 -0
  440. package/dist/db-admin/routes.js.map +1 -1
  441. package/dist/db-admin/types.d.ts +4 -0
  442. package/dist/db-admin/types.d.ts.map +1 -1
  443. package/dist/db-admin/types.js.map +1 -1
  444. package/dist/extensions/actions.d.ts.map +1 -1
  445. package/dist/extensions/actions.js +85 -5
  446. package/dist/extensions/actions.js.map +1 -1
  447. package/dist/extensions/html-shell.d.ts +5 -4
  448. package/dist/extensions/html-shell.d.ts.map +1 -1
  449. package/dist/extensions/html-shell.js.map +1 -1
  450. package/dist/extensions/routes.d.ts.map +1 -1
  451. package/dist/extensions/routes.js +8 -0
  452. package/dist/extensions/routes.js.map +1 -1
  453. package/dist/navigation/index.d.ts +1 -1
  454. package/dist/navigation/index.d.ts.map +1 -1
  455. package/dist/navigation/index.js +1 -1
  456. package/dist/navigation/index.js.map +1 -1
  457. package/dist/notifications/channels.d.ts +8 -3
  458. package/dist/notifications/channels.d.ts.map +1 -1
  459. package/dist/notifications/channels.js +61 -22
  460. package/dist/notifications/channels.js.map +1 -1
  461. package/dist/notifications/registry.d.ts.map +1 -1
  462. package/dist/notifications/registry.js +14 -3
  463. package/dist/notifications/registry.js.map +1 -1
  464. package/dist/notifications/types.d.ts +4 -1
  465. package/dist/notifications/types.d.ts.map +1 -1
  466. package/dist/notifications/types.js.map +1 -1
  467. package/dist/resources/handlers.d.ts +5 -0
  468. package/dist/resources/handlers.d.ts.map +1 -1
  469. package/dist/resources/handlers.js +9 -8
  470. package/dist/resources/handlers.js.map +1 -1
  471. package/dist/server/agent-chat-plugin.d.ts +5 -1
  472. package/dist/server/agent-chat-plugin.d.ts.map +1 -1
  473. package/dist/server/agent-chat-plugin.js +2 -2
  474. package/dist/server/agent-chat-plugin.js.map +1 -1
  475. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  476. package/dist/server/poll.d.ts +12 -4
  477. package/dist/server/poll.d.ts.map +1 -1
  478. package/dist/server/poll.js +236 -9
  479. package/dist/server/poll.js.map +1 -1
  480. package/dist/server/request-context.d.ts +7 -0
  481. package/dist/server/request-context.d.ts.map +1 -1
  482. package/dist/server/request-context.js.map +1 -1
  483. package/dist/styles/agent-conversation.css +1 -1
  484. package/dist/styles/agent-native.css +27 -0
  485. package/dist/templates/default/.agents/skills/actions/SKILL.md +5 -0
  486. package/dist/templates/default/.agents/skills/adding-a-feature/SKILL.md +5 -0
  487. package/dist/templates/default/.agents/skills/real-time-sync/SKILL.md +1 -1
  488. package/dist/templates/default/.agents/skills/security/SKILL.md +7 -0
  489. package/dist/templates/default/.agents/skills/self-modifying-code/SKILL.md +20 -2
  490. package/dist/templates/default/.agents/skills/storing-data/SKILL.md +5 -1
  491. package/dist/templates/default/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
  492. package/dist/templates/default/AGENTS.md +14 -5
  493. package/dist/templates/default/package.json +2 -1
  494. package/dist/templates/headless/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
  495. package/dist/templates/headless/AGENTS.md +13 -3
  496. package/dist/templates/headless/package.json +1 -0
  497. package/dist/templates/workspace-core/.agents/skills/actions/SKILL.md +5 -0
  498. package/dist/templates/workspace-core/.agents/skills/adding-a-feature/SKILL.md +5 -0
  499. package/dist/templates/workspace-core/.agents/skills/context-awareness/SKILL.md +4 -0
  500. package/dist/templates/workspace-core/.agents/skills/extensions/SKILL.md +6 -0
  501. package/dist/templates/workspace-core/.agents/skills/performance/SKILL.md +4 -0
  502. package/dist/templates/workspace-core/.agents/skills/real-time-sync/SKILL.md +1 -1
  503. package/dist/templates/workspace-core/.agents/skills/security/SKILL.md +7 -0
  504. package/dist/templates/workspace-core/.agents/skills/self-modifying-code/SKILL.md +20 -2
  505. package/dist/templates/workspace-core/.agents/skills/storing-data/SKILL.md +5 -1
  506. package/dist/templates/workspace-core/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
  507. package/dist/templates/workspace-core/AGENTS.md +7 -1
  508. package/dist/templates/workspace-root/AGENTS.md +17 -5
  509. package/dist/templates/workspace-root/package.json +1 -0
  510. package/docs/content/faq.mdx +17 -0
  511. package/docs/content/notifications.mdx +12 -6
  512. package/package.json +2 -2
  513. package/src/templates/default/.agents/skills/actions/SKILL.md +5 -0
  514. package/src/templates/default/.agents/skills/adding-a-feature/SKILL.md +5 -0
  515. package/src/templates/default/.agents/skills/real-time-sync/SKILL.md +1 -1
  516. package/src/templates/default/.agents/skills/security/SKILL.md +7 -0
  517. package/src/templates/default/.agents/skills/self-modifying-code/SKILL.md +20 -2
  518. package/src/templates/default/.agents/skills/storing-data/SKILL.md +5 -1
  519. package/src/templates/default/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
  520. package/src/templates/default/AGENTS.md +14 -5
  521. package/src/templates/default/package.json +2 -1
  522. package/src/templates/headless/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
  523. package/src/templates/headless/AGENTS.md +13 -3
  524. package/src/templates/headless/package.json +1 -0
  525. package/src/templates/workspace-core/.agents/skills/actions/SKILL.md +5 -0
  526. package/src/templates/workspace-core/.agents/skills/adding-a-feature/SKILL.md +5 -0
  527. package/src/templates/workspace-core/.agents/skills/context-awareness/SKILL.md +4 -0
  528. package/src/templates/workspace-core/.agents/skills/extensions/SKILL.md +6 -0
  529. package/src/templates/workspace-core/.agents/skills/performance/SKILL.md +4 -0
  530. package/src/templates/workspace-core/.agents/skills/real-time-sync/SKILL.md +1 -1
  531. package/src/templates/workspace-core/.agents/skills/security/SKILL.md +7 -0
  532. package/src/templates/workspace-core/.agents/skills/self-modifying-code/SKILL.md +20 -2
  533. package/src/templates/workspace-core/.agents/skills/storing-data/SKILL.md +5 -1
  534. package/src/templates/workspace-core/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
  535. package/src/templates/workspace-core/AGENTS.md +7 -1
  536. package/src/templates/workspace-root/AGENTS.md +17 -5
  537. package/src/templates/workspace-root/package.json +1 -0
  538. package/corpus/templates/analytics/changelog/2026-07-08-session-replay-clamps-ultra-wide-viewports-while-keeping-the.md +0 -6
@@ -1,3 +1,7 @@
1
+ import { stat, readFile } from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+
1
5
  /**
2
6
  * Server-side error capture — OWNED BY THE ERROR CAPTURE FEATURE.
3
7
  *
@@ -58,6 +62,10 @@ const DEFAULT_ISSUE_LIMIT = 50;
58
62
  const MAX_ISSUE_LIMIT = 100;
59
63
  const DEFAULT_EVENTS_PER_ISSUE_READ = 50;
60
64
  const SPARKLINE_DAYS = 14;
65
+ const SOURCE_CONTEXT_BEFORE = 4;
66
+ const SOURCE_CONTEXT_AFTER = 4;
67
+ const MAX_SOURCE_CONTEXT_FILE_BYTES = 2_000_000;
68
+ const MAX_SOURCE_CONTEXT_LINE_CHARS = 500;
61
69
 
62
70
  // ---------------------------------------------------------------------------
63
71
  // Pure helpers (unit tested)
@@ -70,6 +78,13 @@ export interface ParsedStackFrame {
70
78
  colno: number | null;
71
79
  inApp: boolean;
72
80
  raw: string;
81
+ sourceContext?: SourceContextLine[];
82
+ }
83
+
84
+ export interface SourceContextLine {
85
+ line: number;
86
+ text: string;
87
+ highlight: boolean;
73
88
  }
74
89
 
75
90
  const VENDOR_FILE_RE =
@@ -179,6 +194,172 @@ export function parseStack(
179
194
  return frames;
180
195
  }
181
196
 
197
+ export function sourceContextFromText(
198
+ source: string,
199
+ lineNumber: number | null | undefined,
200
+ options: { before?: number; after?: number } = {},
201
+ ): SourceContextLine[] | null {
202
+ if (!lineNumber || lineNumber < 1) return null;
203
+ const lines = source.split(/\r?\n/);
204
+ if (lineNumber > lines.length) return null;
205
+ const before = options.before ?? SOURCE_CONTEXT_BEFORE;
206
+ const after = options.after ?? SOURCE_CONTEXT_AFTER;
207
+ const start = Math.max(1, lineNumber - before);
208
+ const end = Math.min(lines.length, lineNumber + after);
209
+ const context: SourceContextLine[] = [];
210
+ for (let line = start; line <= end; line += 1) {
211
+ const text = lines[line - 1] ?? "";
212
+ context.push({
213
+ line,
214
+ text:
215
+ text.length > MAX_SOURCE_CONTEXT_LINE_CHARS
216
+ ? `${text.slice(0, MAX_SOURCE_CONTEXT_LINE_CHARS)}…`
217
+ : text,
218
+ highlight: line === lineNumber,
219
+ });
220
+ }
221
+ return context;
222
+ }
223
+
224
+ const SOURCE_EXT_RE = /\.(?:[cm]?[jt]sx?|vue|svelte|css|scss|json)$/i;
225
+ const SOURCE_CONTEXT_ALLOWED_PREFIXES = ["app/"] as const;
226
+
227
+ function templateRootFrom(value: string): string | null {
228
+ const marker = `${path.sep}templates${path.sep}analytics`;
229
+ const index = value.indexOf(marker);
230
+ if (index < 0) return null;
231
+ return value.slice(0, index + marker.length);
232
+ }
233
+
234
+ function sourceRoots(): string[] {
235
+ const roots = new Set<string>();
236
+ const cwdTemplateRoot = templateRootFrom(process.cwd());
237
+ roots.add(cwdTemplateRoot ?? process.cwd());
238
+ try {
239
+ const modulePath = fileURLToPath(import.meta.url);
240
+ const moduleTemplateRoot = templateRootFrom(modulePath);
241
+ if (moduleTemplateRoot) roots.add(moduleTemplateRoot);
242
+ } catch {
243
+ // import.meta.url is always file: in Node, but source context is best-effort.
244
+ }
245
+ return Array.from(roots).map((root) => path.resolve(root));
246
+ }
247
+
248
+ function isWithinRoot(candidate: string, root: string): boolean {
249
+ const relative = path.relative(root, candidate);
250
+ return (
251
+ relative === "" ||
252
+ (!relative.startsWith("..") && !path.isAbsolute(relative))
253
+ );
254
+ }
255
+
256
+ function cleanFrameFile(file: string | null): string | null {
257
+ if (!file) return null;
258
+ let cleaned = file.trim();
259
+ if (!cleaned || cleaned === "<anonymous>" || cleaned === "[native code]") {
260
+ return null;
261
+ }
262
+ if (/^https?:\/\//i.test(cleaned)) {
263
+ try {
264
+ cleaned = new URL(cleaned).pathname;
265
+ } catch {
266
+ return null;
267
+ }
268
+ } else if (cleaned.startsWith("file://")) {
269
+ try {
270
+ cleaned = fileURLToPath(cleaned);
271
+ } catch {
272
+ return null;
273
+ }
274
+ }
275
+ cleaned = cleaned.replace(/[?#].*$/, "");
276
+ try {
277
+ cleaned = decodeURIComponent(cleaned);
278
+ } catch {
279
+ // Keep the original path when a browser stack includes malformed escapes.
280
+ }
281
+ return cleaned;
282
+ }
283
+
284
+ export function trustedSourceRelativePath(cleaned: string): string | null {
285
+ const normalized = cleaned.replace(/\\/g, "/").replace(/^\/+/, "");
286
+ if (
287
+ !normalized ||
288
+ normalized.startsWith("../") ||
289
+ normalized.includes("/../")
290
+ ) {
291
+ return null;
292
+ }
293
+ if (
294
+ !SOURCE_CONTEXT_ALLOWED_PREFIXES.some((prefix) =>
295
+ normalized.startsWith(prefix),
296
+ )
297
+ ) {
298
+ return null;
299
+ }
300
+ return normalized;
301
+ }
302
+
303
+ async function resolveSourcePath(
304
+ frame: ParsedStackFrame,
305
+ ): Promise<string | null> {
306
+ if (!frame.inApp) return null;
307
+ const cleaned = cleanFrameFile(frame.file);
308
+ if (!cleaned || !SOURCE_EXT_RE.test(cleaned)) return null;
309
+ const relativePath = trustedSourceRelativePath(cleaned);
310
+ if (!relativePath) return null;
311
+ const roots = sourceRoots();
312
+ const candidates = new Set<string>();
313
+ for (const root of roots) candidates.add(path.resolve(root, relativePath));
314
+
315
+ for (const candidate of candidates) {
316
+ if (!roots.some((root) => isWithinRoot(candidate, root))) continue;
317
+ try {
318
+ const fileStat = await stat(candidate);
319
+ if (
320
+ fileStat.isFile() &&
321
+ fileStat.size > 0 &&
322
+ fileStat.size <= MAX_SOURCE_CONTEXT_FILE_BYTES
323
+ ) {
324
+ return candidate;
325
+ }
326
+ } catch {
327
+ // Try the next candidate.
328
+ }
329
+ }
330
+ return null;
331
+ }
332
+
333
+ async function sourceContextForFrame(
334
+ frame: ParsedStackFrame,
335
+ ): Promise<SourceContextLine[] | undefined> {
336
+ if (!frame.lineno) return undefined;
337
+ const sourcePath = await resolveSourcePath(frame);
338
+ if (!sourcePath) return undefined;
339
+ try {
340
+ const source = await readFile(sourcePath, "utf8");
341
+ return (
342
+ sourceContextFromText(source, frame.lineno, {
343
+ before: SOURCE_CONTEXT_BEFORE,
344
+ after: SOURCE_CONTEXT_AFTER,
345
+ }) ?? undefined
346
+ );
347
+ } catch {
348
+ return undefined;
349
+ }
350
+ }
351
+
352
+ async function addSourceContexts(
353
+ frames: ParsedStackFrame[],
354
+ ): Promise<ParsedStackFrame[]> {
355
+ return Promise.all(
356
+ frames.map(async (frame) => {
357
+ const sourceContext = await sourceContextForFrame(frame);
358
+ return sourceContext?.length ? { ...frame, sourceContext } : frame;
359
+ }),
360
+ );
361
+ }
362
+
182
363
  /** Strip content hashes + query/hash from a filename for stable grouping. */
183
364
  export function normalizeFrameFile(file: string | null): string {
184
365
  if (!file) return "";
@@ -1031,6 +1212,7 @@ export interface ErrorEventDetail {
1031
1212
  culprit: string | null;
1032
1213
  level: ExceptionLevel;
1033
1214
  stack: ParsedStackFrame[];
1215
+ rawStack: string | null;
1034
1216
  handled: boolean;
1035
1217
  url: string | null;
1036
1218
  userId: string | null;
@@ -1164,42 +1346,55 @@ export async function getErrorIssue(
1164
1346
  );
1165
1347
 
1166
1348
  const sessions = new Map<string, { recordingId: string; path: string }>();
1167
- const events: ErrorEventDetail[] = eventRows.map((row: any) => {
1168
- let recordingId: string | null = null;
1169
- if (row.sessionRecordingId && byId.has(row.sessionRecordingId)) {
1170
- recordingId = row.sessionRecordingId;
1171
- } else if (row.clientRecordingId && byClientId.has(row.clientRecordingId)) {
1172
- recordingId = byClientId.get(row.clientRecordingId) ?? null;
1173
- }
1174
- if (recordingId && !sessions.has(recordingId)) {
1175
- sessions.set(recordingId, {
1176
- recordingId,
1177
- path: `/sessions/${recordingId}`,
1178
- });
1179
- }
1180
- return {
1181
- id: row.id,
1182
- type: row.type,
1183
- message: row.message ?? "",
1184
- culprit: row.culprit ?? null,
1185
- level: coerceLevel(row.level),
1186
- stack: parseJson<ParsedStackFrame[]>(row.stack, []),
1187
- handled: Boolean(row.handled),
1188
- url: row.url ?? null,
1189
- userId: row.userId ?? null,
1190
- anonymousId: row.anonymousId ?? null,
1191
- userKey: row.userKey ?? null,
1192
- sessionId: row.sessionId ?? null,
1193
- sessionRecordingId: recordingId,
1194
- sessionRecordingPath: recordingPath(recordingId),
1195
- release: row.release ?? null,
1196
- environment: row.environment ?? null,
1197
- tags: parseJson<Record<string, unknown>>(row.tags, {}),
1198
- extra: parseJson<Record<string, unknown>>(row.extra, {}),
1199
- breadcrumbs: parseJson<unknown[]>(row.breadcrumbs, []),
1200
- occurredAt: row.occurredAt,
1201
- };
1202
- });
1349
+ const events = await Promise.all(
1350
+ eventRows.map(
1351
+ async (row: any, index: number): Promise<ErrorEventDetail> => {
1352
+ let recordingId: string | null = null;
1353
+ if (row.sessionRecordingId && byId.has(row.sessionRecordingId)) {
1354
+ recordingId = row.sessionRecordingId;
1355
+ } else if (
1356
+ row.clientRecordingId &&
1357
+ byClientId.has(row.clientRecordingId)
1358
+ ) {
1359
+ recordingId = byClientId.get(row.clientRecordingId) ?? null;
1360
+ }
1361
+ if (recordingId && !sessions.has(recordingId)) {
1362
+ sessions.set(recordingId, {
1363
+ recordingId,
1364
+ path: `/sessions/${recordingId}`,
1365
+ });
1366
+ }
1367
+ return {
1368
+ id: row.id,
1369
+ type: row.type,
1370
+ message: row.message ?? "",
1371
+ culprit: row.culprit ?? null,
1372
+ level: coerceLevel(row.level),
1373
+ stack:
1374
+ index === 0
1375
+ ? await addSourceContexts(
1376
+ parseJson<ParsedStackFrame[]>(row.stack, []),
1377
+ )
1378
+ : parseJson<ParsedStackFrame[]>(row.stack, []),
1379
+ rawStack: row.rawStack ?? null,
1380
+ handled: Boolean(row.handled),
1381
+ url: row.url ?? null,
1382
+ userId: row.userId ?? null,
1383
+ anonymousId: row.anonymousId ?? null,
1384
+ userKey: row.userKey ?? null,
1385
+ sessionId: row.sessionId ?? null,
1386
+ sessionRecordingId: recordingId,
1387
+ sessionRecordingPath: recordingPath(recordingId),
1388
+ release: row.release ?? null,
1389
+ environment: row.environment ?? null,
1390
+ tags: parseJson<Record<string, unknown>>(row.tags, {}),
1391
+ extra: parseJson<Record<string, unknown>>(row.extra, {}),
1392
+ breadcrumbs: parseJson<unknown[]>(row.breadcrumbs, []),
1393
+ occurredAt: row.occurredAt,
1394
+ };
1395
+ },
1396
+ ),
1397
+ );
1203
1398
 
1204
1399
  const sparklines = await sparklinesForIssues([issueId]);
1205
1400
  const issue: ErrorIssueSummary = {
@@ -2,6 +2,7 @@ import { Buffer } from "node:buffer";
2
2
  import { createHash, randomUUID } from "node:crypto";
3
3
  import { gzipSync, gunzipSync } from "node:zlib";
4
4
 
5
+ import { readAppState } from "@agent-native/core/application-state";
5
6
  import {
6
7
  deletePrivateBlob,
7
8
  putPrivateBlob,
@@ -237,6 +238,7 @@ const DEFAULT_REPLAY_MAX_REQUESTS_PER_MINUTE = 120;
237
238
  const RETENTION_DELETE_BATCH_SIZE = 500;
238
239
  const REPLAY_PRIVATE_BLOB_REF_KIND = "agent-native.session-replay.private-blob";
239
240
  const REPLAY_PRIVATE_BLOB_REF_VERSION = 1;
241
+ const SESSION_DEMO_EMAIL_PATTERN = /[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}/gi;
240
242
  let inlineReplayFallbackWarned = false;
241
243
 
242
244
  function replayError(message: string, statusCode: number): Error {
@@ -274,6 +276,92 @@ function replayEmail(value: unknown): string | null {
274
276
  return raw && raw.includes("@") ? raw : null;
275
277
  }
276
278
 
279
+ function normalizedEmail(value: unknown): string | null {
280
+ return replayEmail(value)?.toLowerCase() ?? null;
281
+ }
282
+
283
+ function isBuilderEmail(value: unknown): boolean {
284
+ return normalizedEmail(value)?.endsWith("@builder.io") === true;
285
+ }
286
+
287
+ async function isSessionDemoModeEnabled(): Promise<boolean> {
288
+ if (process.env.DEMO_MODE === "true") return true;
289
+ try {
290
+ const state = await readAppState("demo-mode");
291
+ return state?.enabled === true;
292
+ } catch {
293
+ return false;
294
+ }
295
+ }
296
+
297
+ function demoBuilderSessionCondition() {
298
+ return or(
299
+ sql`lower(${schema.sessionRecordings.userId}) like ${"%@builder.io"}`,
300
+ sql`lower(${schema.sessionRecordings.userKey}) like ${"%@builder.io"}`,
301
+ );
302
+ }
303
+
304
+ class SessionDemoAnonymizer {
305
+ private readonly aliases = new Map<string, string>();
306
+
307
+ aliasFor(value: unknown): string | null {
308
+ const raw = replayString(value);
309
+ if (!raw || raw.match(SESSION_DEMO_EMAIL_PATTERN)?.[0] !== raw) {
310
+ return null;
311
+ }
312
+ const email = raw.toLowerCase();
313
+ let alias = this.aliases.get(email);
314
+ if (!alias) {
315
+ alias = `anonymized-${this.aliases.size + 1}@builder.io`;
316
+ this.aliases.set(email, alias);
317
+ }
318
+ return alias;
319
+ }
320
+
321
+ summarize(row: any, role?: SessionReplayAccessRole): SessionRecordingSummary {
322
+ const summary = rowToSessionRecordingSummary(row, role);
323
+ return {
324
+ ...summary,
325
+ userId: this.aliasFor(row.userId) ?? summary.userId,
326
+ userKey: this.aliasFor(row.userKey) ?? summary.userKey,
327
+ ownerEmail: this.aliasFor(summary.ownerEmail) ?? summary.ownerEmail,
328
+ metadata: anonymizeEmailValue(summary.metadata, this) as Record<
329
+ string,
330
+ unknown
331
+ >,
332
+ };
333
+ }
334
+ }
335
+
336
+ function anonymizeEmailValue(
337
+ value: unknown,
338
+ anonymizer: SessionDemoAnonymizer,
339
+ ): unknown {
340
+ const alias = anonymizer.aliasFor(value);
341
+ if (alias) return alias;
342
+ if (Array.isArray(value)) {
343
+ return value.map((item) => anonymizeEmailValue(item, anonymizer));
344
+ }
345
+ if (typeof value === "string") {
346
+ return value.replace(SESSION_DEMO_EMAIL_PATTERN, (email) => {
347
+ return anonymizer.aliasFor(email) ?? email;
348
+ });
349
+ }
350
+ if (value && typeof value === "object") {
351
+ const out: Record<string, unknown> = {};
352
+ for (const [key, item] of Object.entries(value)) {
353
+ out[key] = anonymizeEmailValue(item, anonymizer);
354
+ }
355
+ return out;
356
+ }
357
+ return value;
358
+ }
359
+
360
+ function assertDemoVisibleSession(row: any): void {
361
+ if (isBuilderEmail(row.userId) || isBuilderEmail(row.userKey)) return;
362
+ throw replayError("Session recording not found", 404);
363
+ }
364
+
277
365
  function replayInteger(value: unknown): number | null {
278
366
  if (typeof value === "number" && Number.isInteger(value)) return value;
279
367
  if (typeof value === "string" && /^-?\d+$/.test(value.trim())) {
@@ -580,16 +668,16 @@ function abandonedReplayMinutes(): number {
580
668
  );
581
669
  }
582
670
 
583
- function productionInlineFallbackAllowed(): boolean {
671
+ function inlineReplayFallbackAllowed(): boolean {
584
672
  if (process.env.NODE_ENV !== "production") return true;
585
- return process.env.ANALYTICS_SESSION_REPLAY_SQL_FALLBACK === "1";
673
+ return false;
586
674
  }
587
675
 
588
676
  function warnInlineReplayFallback(): void {
589
677
  if (inlineReplayFallbackWarned) return;
590
678
  inlineReplayFallbackWarned = true;
591
679
  console.warn(
592
- "[session-replay] Private blob storage is not configured; storing capped replay chunks inline in SQL. This is intended only for local/dev use.",
680
+ "[session-replay] Private blob storage is not configured; storing capped replay chunks inline in SQL. This is for local/dev use only.",
593
681
  );
594
682
  }
595
683
 
@@ -619,9 +707,9 @@ async function storeReplayChunkBlob(
619
707
  },
620
708
  });
621
709
  if (!handle) {
622
- if (!productionInlineFallbackAllowed()) {
710
+ if (!inlineReplayFallbackAllowed()) {
623
711
  throw replayError(
624
- "Session replay blob storage is required in production. Configure a private blob provider or set ANALYTICS_SESSION_REPLAY_SQL_FALLBACK=1 for a capped temporary fallback.",
712
+ "Session replay blob storage is required in production. Configure a private blob provider before enabling full snapshot playback.",
625
713
  503,
626
714
  );
627
715
  }
@@ -1580,6 +1668,7 @@ export async function listSessionRecordings(
1580
1668
  filters: SessionReplayListFilters = {},
1581
1669
  ): Promise<SessionRecordingSummary[]> {
1582
1670
  const db = getDb() as any;
1671
+ const demoMode = await isSessionDemoModeEnabled();
1583
1672
  const limit = Math.min(
1584
1673
  MAX_SESSION_RECORDINGS_LIMIT,
1585
1674
  Math.max(1, filters.limit ?? DEFAULT_SESSION_RECORDINGS_LIMIT),
@@ -1592,6 +1681,7 @@ export async function listSessionRecordings(
1592
1681
  replayVisibleIdentityCondition(),
1593
1682
  replayPlayableEventsCondition(),
1594
1683
  ];
1684
+ if (demoMode) conditions.push(demoBuilderSessionCondition());
1595
1685
  if (filters.app)
1596
1686
  conditions.push(eq(schema.sessionRecordings.app, filters.app));
1597
1687
  if (filters.template) {
@@ -1645,13 +1735,22 @@ export async function listSessionRecordings(
1645
1735
  .where(and(...conditions))
1646
1736
  .orderBy(desc(schema.sessionRecordings.startedAt))
1647
1737
  .limit(limit);
1648
- return rows.map((row: any) => rowToSessionRecordingSummary(row));
1738
+ if (!demoMode) {
1739
+ return rows.map((row: any) => rowToSessionRecordingSummary(row));
1740
+ }
1741
+ const anonymizer = new SessionDemoAnonymizer();
1742
+ return rows
1743
+ .filter(
1744
+ (row: any) => isBuilderEmail(row.userId) || isBuilderEmail(row.userKey),
1745
+ )
1746
+ .map((row: any) => anonymizer.summarize(row));
1649
1747
  }
1650
1748
 
1651
1749
  export async function getSessionReplaySummary(
1652
1750
  recordingId: string,
1653
1751
  scope: SessionReplayScope,
1654
1752
  ): Promise<SessionRecordingSummary> {
1753
+ const demoMode = await isSessionDemoModeEnabled();
1655
1754
  const access = await resolveAccess("session-recording", recordingId, {
1656
1755
  userEmail: scope.userEmail,
1657
1756
  orgId: scope.orgId ?? undefined,
@@ -1660,12 +1759,17 @@ export async function getSessionReplaySummary(
1660
1759
  if (!isVisibleSessionRecording(access.resource)) {
1661
1760
  throw replayError("Session recording not found", 404);
1662
1761
  }
1663
- return rowToSessionRecordingSummary(access.resource, access.role);
1762
+ if (!demoMode) {
1763
+ return rowToSessionRecordingSummary(access.resource, access.role);
1764
+ }
1765
+ assertDemoVisibleSession(access.resource);
1766
+ return new SessionDemoAnonymizer().summarize(access.resource, access.role);
1664
1767
  }
1665
1768
 
1666
1769
  export async function getSessionReplayTokenizedSummary(
1667
1770
  recordingId: string,
1668
1771
  ): Promise<SessionRecordingSummary> {
1772
+ const demoMode = await isSessionDemoModeEnabled();
1669
1773
  const db = getDb() as any;
1670
1774
  // guard:allow-unscoped -- called only after verifySessionReplayAgentAccess(recordingId, token) verifies a signed, recording-scoped agent_access token.
1671
1775
  const [row] = await db
@@ -1676,7 +1780,9 @@ export async function getSessionReplayTokenizedSummary(
1676
1780
  if (!row || !isVisibleSessionRecording(row)) {
1677
1781
  throw replayError("Session recording not found", 404);
1678
1782
  }
1679
- return rowToSessionRecordingSummary(row, "viewer");
1783
+ if (!demoMode) return rowToSessionRecordingSummary(row, "viewer");
1784
+ assertDemoVisibleSession(row);
1785
+ return new SessionDemoAnonymizer().summarize(row, "viewer");
1680
1786
  }
1681
1787
 
1682
1788
  function parseInlineReplayEvents(inlineData: string): unknown[] {
@@ -101,6 +101,8 @@ export interface MonitorInput {
101
101
  severity?: MonitorSeverity;
102
102
  channels?: string[];
103
103
  emailRecipients?: string[];
104
+ slackWebhookUrl?: string | null;
105
+ webhookUrl?: string | null;
104
106
  cooldownMinutes?: number;
105
107
  enabled?: boolean;
106
108
  }
@@ -120,6 +122,8 @@ export interface Monitor {
120
122
  severity: MonitorSeverity;
121
123
  channels: string[];
122
124
  emailRecipients: string[];
125
+ slackWebhookUrl: string | null;
126
+ webhookUrl: string | null;
123
127
  cooldownMinutes: number;
124
128
  enabled: boolean;
125
129
  lastStatus: MonitorStatus | null;
@@ -199,6 +203,7 @@ const MAX_REDIRECT_HOPS = 5;
199
203
  const MONITOR_RUNNING_STALE_MS = 5 * 60 * 1000;
200
204
  const DEFAULT_RESULT_RETENTION_DAYS = 30;
201
205
  const DEFAULT_MONITOR_LIMIT_PER_OWNER = 100;
206
+ export const DEFAULT_MONITOR_TIMEOUT_MS = 10_000;
202
207
  const MAX_REQUEST_HEADER_COUNT = 20;
203
208
  const MAX_REQUEST_HEADER_NAME_LENGTH = 128;
204
209
  const MAX_REQUEST_HEADER_VALUE_BYTES = 2048;
@@ -462,11 +467,50 @@ function normalizeEmailRecipients(recipients: string[] | undefined): string[] {
462
467
  return normalized;
463
468
  }
464
469
 
470
+ function normalizeOptionalHttpUrl(
471
+ value: string | null | undefined,
472
+ label: string,
473
+ ): string | null {
474
+ const trimmed = typeof value === "string" ? value.trim() : "";
475
+ if (!trimmed) return null;
476
+ let parsed: URL;
477
+ try {
478
+ parsed = new URL(trimmed);
479
+ } catch {
480
+ throw badRequest(`${label} must be an absolute http(s) URL`);
481
+ }
482
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
483
+ throw badRequest(`${label} must be an absolute http(s) URL`);
484
+ }
485
+ return trimmed;
486
+ }
487
+
465
488
  function ensureInboxChannel(channels: string[]): string[] {
466
489
  const normalized = channels.map((c) => c.trim()).filter(Boolean);
467
490
  return normalized.includes("inbox") ? normalized : ["inbox", ...normalized];
468
491
  }
469
492
 
493
+ function monitorNotifyMetadata(monitor: Monitor): Record<string, unknown> {
494
+ return {
495
+ kind: "uptime_monitor",
496
+ monitorId: monitor.id,
497
+ monitorName: monitor.name,
498
+ url: monitor.url,
499
+ emailRecipients: monitor.emailRecipients,
500
+ requestedChannels: monitor.channels,
501
+ };
502
+ }
503
+
504
+ function monitorNotifyDeliveryMetadata(
505
+ monitor: Monitor,
506
+ ): Record<string, string> | undefined {
507
+ const delivery: Record<string, string> = {};
508
+ if (monitor.slackWebhookUrl)
509
+ delivery.slackWebhookUrl = monitor.slackWebhookUrl;
510
+ if (monitor.webhookUrl) delivery.webhookUrl = monitor.webhookUrl;
511
+ return Object.keys(delivery).length > 0 ? delivery : undefined;
512
+ }
513
+
470
514
  // ---------------------------------------------------------------------------
471
515
  // Pure evaluation core (unit-tested)
472
516
  // ---------------------------------------------------------------------------
@@ -779,7 +823,7 @@ export async function runMonitorCheck(
779
823
  monitor.timeoutMs,
780
824
  MIN_TIMEOUT_MS,
781
825
  MAX_TIMEOUT_MS,
782
- 15000,
826
+ DEFAULT_MONITOR_TIMEOUT_MS,
783
827
  );
784
828
  const timer = setTimeout(() => controller.abort(), timeoutMs);
785
829
  const start = Date.now();
@@ -882,7 +926,7 @@ function rowToMonitor(row: any): Monitor {
882
926
  ),
883
927
  requestBody: row.requestBody ?? null,
884
928
  intervalSeconds: Number(row.intervalSeconds ?? 300),
885
- timeoutMs: Number(row.timeoutMs ?? 15000),
929
+ timeoutMs: Number(row.timeoutMs ?? DEFAULT_MONITOR_TIMEOUT_MS),
886
930
  expectedStatus: normalizeStatusMatcher(
887
931
  safeJsonParse<unknown>(row.expectedStatus, null),
888
932
  ),
@@ -891,6 +935,8 @@ function rowToMonitor(row: any): Monitor {
891
935
  severity: row.severity === "warning" ? "warning" : "critical",
892
936
  channels: safeJsonParse<string[]>(row.channels, ["inbox"]),
893
937
  emailRecipients: safeJsonParse<string[]>(row.emailRecipients, []),
938
+ slackWebhookUrl: row.slackWebhookUrl?.trim() || null,
939
+ webhookUrl: row.webhookUrl?.trim() || null,
894
940
  cooldownMinutes: Number(row.cooldownMinutes ?? 15),
895
941
  enabled: row.enabled === true || row.enabled === 1,
896
942
  lastStatus: (row.lastStatus ?? null) as MonitorStatus | null,
@@ -1110,10 +1156,10 @@ export async function saveMonitor(
1110
1156
  300,
1111
1157
  );
1112
1158
  const timeoutMs = clampInt(
1113
- input.timeoutMs ?? 15000,
1159
+ input.timeoutMs ?? DEFAULT_MONITOR_TIMEOUT_MS,
1114
1160
  MIN_TIMEOUT_MS,
1115
1161
  MAX_TIMEOUT_MS,
1116
- 15000,
1162
+ DEFAULT_MONITOR_TIMEOUT_MS,
1117
1163
  );
1118
1164
  const expectedStatus = normalizeStatusMatcher(input.expectedStatus);
1119
1165
  const assertions = normalizeAssertions(input.assertions);
@@ -1121,6 +1167,11 @@ export async function saveMonitor(
1121
1167
  const severity = input.severity === "warning" ? "warning" : "critical";
1122
1168
  const emailRecipients = normalizeEmailRecipients(input.emailRecipients);
1123
1169
  const channels = normalizeChannels(input.channels, emailRecipients);
1170
+ const slackWebhookUrl = normalizeOptionalHttpUrl(
1171
+ input.slackWebhookUrl,
1172
+ "Slack webhook URL",
1173
+ );
1174
+ const webhookUrl = normalizeOptionalHttpUrl(input.webhookUrl, "Webhook URL");
1124
1175
  const cooldownMinutes = clampInt(input.cooldownMinutes ?? 15, 0, 24 * 60, 15);
1125
1176
  const enabled = input.enabled ?? true;
1126
1177
 
@@ -1138,6 +1189,8 @@ export async function saveMonitor(
1138
1189
  severity,
1139
1190
  channels: JSON.stringify(channels),
1140
1191
  emailRecipients: JSON.stringify(emailRecipients),
1192
+ slackWebhookUrl,
1193
+ webhookUrl,
1141
1194
  cooldownMinutes,
1142
1195
  enabled,
1143
1196
  };
@@ -1440,16 +1493,14 @@ async function notifyMonitorDown(monitor: Monitor, outcome: CheckOutcome) {
1440
1493
  body: `${host} is ${label}${detail}.${latency}`,
1441
1494
  channels: ensureInboxChannel(monitor.channels),
1442
1495
  metadata: {
1443
- kind: "uptime_monitor",
1444
- monitorId: monitor.id,
1445
- monitorName: monitor.name,
1446
- url: monitor.url,
1496
+ ...monitorNotifyMetadata(monitor),
1497
+ ...(monitorNotifyDeliveryMetadata(monitor)
1498
+ ? { delivery: monitorNotifyDeliveryMetadata(monitor) }
1499
+ : {}),
1447
1500
  status: outcome.status,
1448
1501
  statusCode: outcome.statusCode,
1449
1502
  latencyMs: outcome.latencyMs,
1450
1503
  failedAssertions: outcome.failedAssertions,
1451
- emailRecipients: monitor.emailRecipients,
1452
- requestedChannels: monitor.channels,
1453
1504
  },
1454
1505
  },
1455
1506
  { owner: monitor.ownerEmail },
@@ -1475,16 +1526,14 @@ async function notifyMonitorRecovered(
1475
1526
  body: `${host} is back up${downFor ? ` after ${downFor} of downtime` : ""}.${latency}`,
1476
1527
  channels: ensureInboxChannel(monitor.channels),
1477
1528
  metadata: {
1478
- kind: "uptime_monitor",
1479
- monitorId: monitor.id,
1480
- monitorName: monitor.name,
1481
- url: monitor.url,
1529
+ ...monitorNotifyMetadata(monitor),
1530
+ ...(monitorNotifyDeliveryMetadata(monitor)
1531
+ ? { delivery: monitorNotifyDeliveryMetadata(monitor) }
1532
+ : {}),
1482
1533
  status: "up",
1483
1534
  statusCode: outcome.statusCode,
1484
1535
  latencyMs: outcome.latencyMs,
1485
1536
  incidentId: incident.id,
1486
- emailRecipients: monitor.emailRecipients,
1487
- requestedChannels: monitor.channels,
1488
1537
  },
1489
1538
  },
1490
1539
  { owner: monitor.ownerEmail },