@agent-native/core 0.92.0 → 0.92.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (375) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/core/CHANGELOG.md +20 -0
  3. package/corpus/core/package.json +1 -1
  4. package/corpus/core/src/application-state/store.ts +12 -1
  5. package/corpus/core/src/cli/sync-builder-starter-manifest.ts +29 -1
  6. package/corpus/core/src/client/AssistantChat.tsx +27 -14
  7. package/corpus/core/src/client/PoweredByBadge.tsx +1 -8
  8. package/corpus/core/src/client/agent-chat-adapter.ts +37 -10
  9. package/corpus/core/src/client/chat/widgets/DataTableWidget.tsx +52 -1
  10. package/corpus/core/src/client/db-admin/TableEditor.tsx +49 -0
  11. package/corpus/core/src/client/db-admin/export-utils.ts +13 -4
  12. package/corpus/core/src/client/db-admin/useAgentSync.ts +72 -0
  13. package/corpus/core/src/client/error-capture.ts +69 -7
  14. package/corpus/core/src/client/extensions/EmbeddedExtension.tsx +5 -1
  15. package/corpus/core/src/client/extensions/ExtensionViewer.tsx +53 -8
  16. package/corpus/core/src/client/extensions/InlineExtensionFrame.tsx +5 -1
  17. package/corpus/core/src/client/extensions/portable-extension.ts +1 -0
  18. package/corpus/core/src/client/org/OrgSwitcher.tsx +12 -9
  19. package/corpus/core/src/client/org/TeamPage.tsx +77 -71
  20. package/corpus/core/src/client/settings/SettingsPanel.tsx +205 -52
  21. package/corpus/core/src/client/settings/SettingsSection.tsx +8 -1
  22. package/corpus/core/src/client/settings/SettingsTabsPage.tsx +25 -9
  23. package/corpus/core/src/db-admin/agent-tools.ts +8 -2
  24. package/corpus/core/src/db-admin/operations.ts +116 -4
  25. package/corpus/core/src/db-admin/routes.ts +1 -0
  26. package/corpus/core/src/db-admin/types.ts +4 -0
  27. package/corpus/core/src/extensions/html-shell.ts +5 -4
  28. package/corpus/core/src/extensions/routes.ts +10 -0
  29. package/corpus/core/src/navigation/index.ts +1 -1
  30. package/corpus/core/src/notifications/channels.ts +1 -22
  31. package/corpus/core/src/resources/handlers.ts +10 -8
  32. package/corpus/core/src/server/poll.ts +293 -8
  33. package/corpus/core/src/styles/agent-native.css +27 -0
  34. package/corpus/core/src/templates/default/.agents/skills/actions/SKILL.md +5 -0
  35. package/corpus/core/src/templates/default/.agents/skills/adding-a-feature/SKILL.md +5 -0
  36. package/corpus/core/src/templates/default/.agents/skills/real-time-sync/SKILL.md +1 -1
  37. package/corpus/core/src/templates/default/.agents/skills/security/SKILL.md +7 -0
  38. package/corpus/core/src/templates/default/.agents/skills/storing-data/SKILL.md +5 -1
  39. package/corpus/core/src/templates/default/AGENTS.md +4 -2
  40. package/corpus/core/src/templates/headless/AGENTS.md +4 -0
  41. package/corpus/core/src/templates/workspace-core/.agents/skills/actions/SKILL.md +5 -0
  42. package/corpus/core/src/templates/workspace-core/.agents/skills/adding-a-feature/SKILL.md +5 -0
  43. package/corpus/core/src/templates/workspace-core/.agents/skills/context-awareness/SKILL.md +4 -0
  44. package/corpus/core/src/templates/workspace-core/.agents/skills/extensions/SKILL.md +6 -0
  45. package/corpus/core/src/templates/workspace-core/.agents/skills/performance/SKILL.md +4 -0
  46. package/corpus/core/src/templates/workspace-core/.agents/skills/real-time-sync/SKILL.md +1 -1
  47. package/corpus/core/src/templates/workspace-core/.agents/skills/security/SKILL.md +7 -0
  48. package/corpus/core/src/templates/workspace-core/.agents/skills/storing-data/SKILL.md +5 -1
  49. package/corpus/core/src/templates/workspace-core/AGENTS.md +7 -1
  50. package/corpus/core/src/templates/workspace-root/AGENTS.md +7 -1
  51. package/corpus/templates/analytics/.agents/skills/actions/SKILL.md +5 -0
  52. package/corpus/templates/analytics/.agents/skills/dashboard-management/SKILL.md +10 -0
  53. package/corpus/templates/analytics/.agents/skills/real-time-sync/SKILL.md +1 -1
  54. package/corpus/templates/analytics/.agents/skills/security/SKILL.md +7 -0
  55. package/corpus/templates/analytics/.agents/skills/storing-data/SKILL.md +5 -1
  56. package/corpus/templates/analytics/AGENTS.md +33 -14
  57. package/corpus/templates/analytics/actions/capture-test-error.ts +4 -0
  58. package/corpus/templates/analytics/actions/get-analytics-alert-rule-defaults.ts +23 -0
  59. package/corpus/templates/analytics/actions/get-error-issue.ts +1 -1
  60. package/corpus/templates/analytics/actions/list-analysis-revisions.ts +40 -0
  61. package/corpus/templates/analytics/actions/list-dashboard-revisions.ts +40 -0
  62. package/corpus/templates/analytics/actions/list-dashboard-usage-stats.ts +299 -0
  63. package/corpus/templates/analytics/actions/navigate.ts +4 -2
  64. package/corpus/templates/analytics/actions/restore-analysis-revision.ts +43 -0
  65. package/corpus/templates/analytics/actions/restore-dashboard-revision.ts +68 -0
  66. package/corpus/templates/analytics/actions/save-analytics-alert-rule.ts +14 -0
  67. package/corpus/templates/analytics/actions/save-monitor.ts +1 -1
  68. package/corpus/templates/analytics/actions/view-screen.ts +24 -0
  69. package/corpus/templates/analytics/app/components/analysis/AnalysisHistoryPanel.tsx +181 -0
  70. package/corpus/templates/analytics/app/components/dashboard/DashboardHistoryPanel.tsx +184 -0
  71. package/corpus/templates/analytics/app/components/layout/CommandPalette.tsx +22 -16
  72. package/corpus/templates/analytics/app/components/layout/Header.tsx +1 -1
  73. package/corpus/templates/analytics/app/components/layout/Sidebar.tsx +27 -21
  74. package/corpus/templates/analytics/app/hooks/use-analysis-revisions.ts +43 -0
  75. package/corpus/templates/analytics/app/hooks/use-dashboard-chat-context.ts +110 -0
  76. package/corpus/templates/analytics/app/hooks/use-dashboard-revisions.ts +43 -0
  77. package/corpus/templates/analytics/app/hooks/use-navigation-state.ts +8 -3
  78. package/corpus/templates/analytics/app/i18n/zh-TW.ts +64 -2
  79. package/corpus/templates/analytics/app/i18n-data.ts +783 -3
  80. package/corpus/templates/analytics/app/pages/Agents.tsx +359 -19
  81. package/corpus/templates/analytics/app/pages/Settings.tsx +3 -2
  82. package/corpus/templates/analytics/app/pages/adhoc/explorer-dashboard/index.tsx +34 -1
  83. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/index.tsx +28 -0
  84. package/corpus/templates/analytics/app/pages/analyses/AnalysisDetail.tsx +18 -1
  85. package/corpus/templates/analytics/app/pages/monitoring/ErrorsPanel.tsx +7 -10
  86. package/corpus/templates/analytics/app/pages/monitoring/errors/IssueDetail.tsx +479 -23
  87. package/corpus/templates/analytics/app/pages/monitoring/errors/IssueList.tsx +0 -9
  88. package/corpus/templates/analytics/app/pages/monitoring/errors/i18n.ts +11 -0
  89. package/corpus/templates/analytics/app/pages/monitoring/errors/types.ts +9 -1
  90. package/corpus/templates/analytics/app/pages/monitoring/uptime/MonitorFormPage.tsx +6 -3
  91. package/corpus/templates/analytics/app/pages/sessions/SessionDetailPage.tsx +26 -30
  92. package/corpus/templates/analytics/app/pages/sessions/SessionsPage.tsx +9 -99
  93. package/corpus/templates/analytics/app/pages/settings/AlertRulesSettingsCard.tsx +262 -104
  94. package/corpus/templates/analytics/app/routes/team.tsx +1 -1
  95. package/corpus/templates/analytics/changelog/2026-07-08-admins-can-audit-dashboard-usage-ownership-and-cleanup-signa.md +6 -0
  96. package/corpus/templates/analytics/changelog/2026-07-08-agent-monitoring-stays-available-to-non-admins.md +6 -0
  97. package/corpus/templates/analytics/changelog/2026-07-08-alert-emails-now-show-the-message-without-raw-metadata-json.md +6 -0
  98. package/corpus/templates/analytics/changelog/2026-07-08-alert-rules-are-easier-to-scan-expand-and-edit-from-settings.md +6 -0
  99. package/corpus/templates/analytics/changelog/2026-07-08-analytics-alert-forms-remember-last-email-recipients.md +6 -0
  100. package/corpus/templates/analytics/changelog/2026-07-08-analytics-alert-rules-can-now-use-per-rule-slack-and-webhook.md +6 -0
  101. package/corpus/templates/analytics/changelog/2026-07-08-dashboard-history-and-chat-context.md +5 -0
  102. package/corpus/templates/analytics/changelog/2026-07-08-dashboard-usage-now-counts-explorer-dashboard-traffic.md +6 -0
  103. package/corpus/templates/analytics/changelog/2026-07-08-demo-mode-session-emails-are-filtered-and-anonymized.md +6 -0
  104. package/corpus/templates/analytics/changelog/2026-07-08-error-issue-detail-adds-stack-traces-and-frequency-bars.md +6 -0
  105. package/corpus/templates/analytics/changelog/2026-07-08-error-issue-detail-now-shows-the-latest-occurrence-message-a.md +6 -0
  106. package/corpus/templates/analytics/changelog/2026-07-08-monitoring-errors-only-show-test-action-when-empty.md +6 -0
  107. package/corpus/templates/analytics/changelog/2026-07-08-production-session-replay-now-requires-private-blob-storage-.md +6 -0
  108. package/corpus/templates/analytics/changelog/2026-07-08-shared-extension-links-now-show-a-clear-message-when-you-do-.md +6 -0
  109. package/corpus/templates/analytics/changelog/2026-07-08-team-settings-show-access-controls-next-to-member-lists.md +6 -0
  110. package/corpus/templates/analytics/changelog/2026-07-08-uptime-checks-no-longer-count-ssrf-setup-time-against-the-re.md +6 -0
  111. package/corpus/templates/analytics/changelog/2026-07-08-uptime-checks-reuse-one-keep-alive-connection-so-recorded-la.md +6 -0
  112. package/corpus/templates/analytics/changelog/2026-07-08-uptime-monitors-now-confirm-transient-timeout-failures-befor.md +6 -0
  113. package/corpus/templates/analytics/changelog/2026-07-08-uptime-monitors-now-use-a-10-second-request-timeout-by-defau.md +6 -0
  114. package/corpus/templates/analytics/docs/uptime-monitoring.md +30 -18
  115. package/corpus/templates/analytics/server/db/schema-monitoring.ts +1 -1
  116. package/corpus/templates/analytics/server/db/schema.ts +56 -0
  117. package/corpus/templates/analytics/server/lib/analytics-alerts.ts +92 -0
  118. package/corpus/templates/analytics/server/lib/dashboards-store.ts +334 -5
  119. package/corpus/templates/analytics/server/lib/error-capture.ts +231 -36
  120. package/corpus/templates/analytics/server/lib/session-replay.ts +114 -8
  121. package/corpus/templates/analytics/server/lib/uptime-monitors.ts +244 -33
  122. package/corpus/templates/analytics/server/plugins/db.ts +112 -2
  123. package/corpus/templates/analytics/server/routes/api/dashboard-agent-context.json.get.ts +1 -0
  124. package/corpus/templates/assets/.agents/skills/actions/SKILL.md +5 -0
  125. package/corpus/templates/assets/.agents/skills/real-time-sync/SKILL.md +1 -1
  126. package/corpus/templates/assets/.agents/skills/security/SKILL.md +7 -0
  127. package/corpus/templates/assets/.agents/skills/storing-data/SKILL.md +5 -1
  128. package/corpus/templates/assets/AGENTS.md +4 -0
  129. package/corpus/templates/assets/app/routes/settings.tsx +1 -1
  130. package/corpus/templates/assets/app/routes/team.tsx +1 -1
  131. package/corpus/templates/brain/.agents/skills/actions/SKILL.md +5 -0
  132. package/corpus/templates/brain/.agents/skills/adding-a-feature/SKILL.md +5 -0
  133. package/corpus/templates/brain/.agents/skills/real-time-sync/SKILL.md +1 -1
  134. package/corpus/templates/brain/.agents/skills/security/SKILL.md +7 -0
  135. package/corpus/templates/brain/AGENTS.md +4 -0
  136. package/corpus/templates/brain/app/routes/team.tsx +1 -1
  137. package/corpus/templates/calendar/.agents/skills/actions/SKILL.md +5 -0
  138. package/corpus/templates/calendar/.agents/skills/real-time-sync/SKILL.md +1 -1
  139. package/corpus/templates/calendar/.agents/skills/security/SKILL.md +7 -0
  140. package/corpus/templates/calendar/.agents/skills/storing-data/SKILL.md +5 -1
  141. package/corpus/templates/calendar/AGENTS.md +4 -0
  142. package/corpus/templates/calendar/app/components/calendar/DayView.tsx +11 -2
  143. package/corpus/templates/calendar/app/components/calendar/EventDetailPopover.tsx +21 -8
  144. package/corpus/templates/calendar/app/components/calendar/MonthView.tsx +7 -1
  145. package/corpus/templates/calendar/app/components/calendar/WeekView.tsx +11 -2
  146. package/corpus/templates/calendar/app/lib/popover-click-guard.ts +48 -4
  147. package/corpus/templates/calendar/app/pages/Settings.tsx +1 -1
  148. package/corpus/templates/calendar/app/routes/_app.team.tsx +1 -1
  149. package/corpus/templates/calendar/changelog/2026-07-08-clicking-empty-calendar-space-closes-an-open-event-popover-b.md +6 -0
  150. package/corpus/templates/chat/.agents/skills/actions/SKILL.md +5 -0
  151. package/corpus/templates/chat/.agents/skills/adding-a-feature/SKILL.md +5 -0
  152. package/corpus/templates/chat/.agents/skills/real-time-sync/SKILL.md +1 -1
  153. package/corpus/templates/chat/.agents/skills/security/SKILL.md +7 -0
  154. package/corpus/templates/chat/AGENTS.md +4 -0
  155. package/corpus/templates/chat/app/hooks/use-navigation-state.ts +1 -1
  156. package/corpus/templates/chat/app/routes/settings.tsx +2 -2
  157. package/corpus/templates/chat/app/routes/team.tsx +1 -1
  158. package/corpus/templates/clips/.agents/skills/actions/SKILL.md +5 -0
  159. package/corpus/templates/clips/.agents/skills/security/SKILL.md +7 -0
  160. package/corpus/templates/clips/AGENTS.md +4 -0
  161. package/corpus/templates/clips/actions/finalize-recording.ts +9 -3
  162. package/corpus/templates/clips/actions/set-thumbnail.ts +11 -2
  163. package/corpus/templates/clips/app/components/meetings/canvas-editor.tsx +1 -7
  164. package/corpus/templates/clips/app/components/player/player-controls.tsx +21 -13
  165. package/corpus/templates/clips/app/components/player/video-player.tsx +30 -75
  166. package/corpus/templates/clips/app/i18n/ar-SA.ts +6 -5
  167. package/corpus/templates/clips/app/i18n/de-DE.ts +6 -6
  168. package/corpus/templates/clips/app/i18n/en-US.ts +6 -5
  169. package/corpus/templates/clips/app/i18n/es-ES.ts +6 -5
  170. package/corpus/templates/clips/app/i18n/fr-FR.ts +6 -6
  171. package/corpus/templates/clips/app/i18n/hi-IN.ts +6 -5
  172. package/corpus/templates/clips/app/i18n/ja-JP.ts +6 -5
  173. package/corpus/templates/clips/app/i18n/ko-KR.ts +6 -5
  174. package/corpus/templates/clips/app/i18n/pt-BR.ts +6 -5
  175. package/corpus/templates/clips/app/i18n/zh-CN.ts +6 -5
  176. package/corpus/templates/clips/app/i18n/zh-TW.ts +6 -5
  177. package/corpus/templates/clips/app/routes/_app.meetings.$meetingId.tsx +51 -118
  178. package/corpus/templates/clips/changelog/2026-07-08-hosted-clip-uploads-now-clear-failed-buffered-chunks.md +6 -0
  179. package/corpus/templates/clips/changelog/2026-07-08-hosted-recording-uploads-now-avoid-sql-chunk-storage.md +6 -0
  180. package/corpus/templates/clips/changelog/2026-07-08-meeting-recaps-now-keep-summaries-action-items-and-transcrip.md +6 -0
  181. package/corpus/templates/clips/changelog/2026-07-08-video-playback-controls-now-match-loom-style-5-second-skips-.md +6 -0
  182. package/corpus/templates/clips/server/lib/recording-upload-state.ts +63 -0
  183. package/corpus/templates/clips/server/lib/video-storage.ts +4 -0
  184. package/corpus/templates/clips/server/routes/api/recordings/[recordingId]/thumbnail.post.ts +19 -13
  185. package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/chunk.post.ts +40 -1
  186. package/corpus/templates/content/.agents/skills/actions/SKILL.md +5 -0
  187. package/corpus/templates/content/.agents/skills/real-time-sync/SKILL.md +1 -1
  188. package/corpus/templates/content/.agents/skills/security/SKILL.md +7 -0
  189. package/corpus/templates/content/.agents/skills/storing-data/SKILL.md +5 -1
  190. package/corpus/templates/content/AGENTS.md +4 -0
  191. package/corpus/templates/content/app/routes/_app.settings.tsx +2 -2
  192. package/corpus/templates/content/app/routes/_app.team.tsx +1 -1
  193. package/corpus/templates/design/.agents/skills/actions/SKILL.md +5 -0
  194. package/corpus/templates/design/.agents/skills/security/SKILL.md +7 -0
  195. package/corpus/templates/design/AGENTS.md +4 -0
  196. package/corpus/templates/design/actions/take-design-screenshot.ts +12 -9
  197. package/corpus/templates/design/app/components/design/inspector/ImageFillControls.tsx +57 -13
  198. package/corpus/templates/design/app/routes/team.tsx +1 -1
  199. package/corpus/templates/design/changelog/2026-07-08-local-images-added-to-designs-now-upload-to-file-storage-ins.md +6 -0
  200. package/corpus/templates/dispatch/.agents/skills/actions/SKILL.md +5 -0
  201. package/corpus/templates/dispatch/.agents/skills/security/SKILL.md +7 -0
  202. package/corpus/templates/dispatch/AGENTS.md +4 -0
  203. package/corpus/templates/dispatch/app/hooks/use-navigation-state.ts +1 -1
  204. package/corpus/templates/dispatch/app/routes/settings.tsx +2 -2
  205. package/corpus/templates/dispatch/app/routes/team.tsx +1 -1
  206. package/corpus/templates/forms/.agents/skills/actions/SKILL.md +5 -0
  207. package/corpus/templates/forms/.agents/skills/real-time-sync/SKILL.md +1 -1
  208. package/corpus/templates/forms/.agents/skills/security/SKILL.md +7 -0
  209. package/corpus/templates/forms/.agents/skills/storing-data/SKILL.md +5 -1
  210. package/corpus/templates/forms/AGENTS.md +4 -0
  211. package/corpus/templates/forms/app/routes/_app.settings.tsx +2 -2
  212. package/corpus/templates/forms/app/routes/_app.team.tsx +1 -1
  213. package/corpus/templates/forms/shared/navigation.ts +1 -1
  214. package/corpus/templates/macros/.agents/skills/security/SKILL.md +7 -0
  215. package/corpus/templates/macros/AGENTS.md +4 -0
  216. package/corpus/templates/macros/app/routes/settings.tsx +2 -2
  217. package/corpus/templates/macros/app/routes/team.tsx +1 -1
  218. package/corpus/templates/mail/.agents/skills/actions/SKILL.md +5 -0
  219. package/corpus/templates/mail/.agents/skills/email-drafts/SKILL.md +1 -1
  220. package/corpus/templates/mail/.agents/skills/real-time-sync/SKILL.md +1 -1
  221. package/corpus/templates/mail/.agents/skills/security/SKILL.md +7 -0
  222. package/corpus/templates/mail/.agents/skills/storing-data/SKILL.md +5 -1
  223. package/corpus/templates/mail/AGENTS.md +4 -0
  224. package/corpus/templates/mail/changelog/2026-07-08-mail-attachments-in-hosted-environments-now-require-file-sto.md +6 -0
  225. package/corpus/templates/mail/server/handlers/media.ts +26 -1
  226. package/corpus/templates/mail/server/lib/outgoing-email.ts +24 -1
  227. package/corpus/templates/mail/server/lib/upload-store.ts +2 -1
  228. package/corpus/templates/plan/.agents/skills/actions/SKILL.md +5 -0
  229. package/corpus/templates/plan/.agents/skills/adding-a-feature/SKILL.md +5 -0
  230. package/corpus/templates/plan/.agents/skills/real-time-sync/SKILL.md +1 -1
  231. package/corpus/templates/plan/.agents/skills/security/SKILL.md +7 -0
  232. package/corpus/templates/plan/AGENTS.md +4 -0
  233. package/corpus/templates/plan/app/hooks/use-navigation-state.ts +1 -1
  234. package/corpus/templates/plan/app/routes/settings.tsx +2 -2
  235. package/corpus/templates/plan/app/routes/team.tsx +1 -1
  236. package/corpus/templates/plan/changelog/2026-07-08-hosted-plan-images-now-require-connected-storage.md +6 -0
  237. package/corpus/templates/plan/server/lib/plan-assets.ts +33 -4
  238. package/corpus/templates/slides/.agents/skills/actions/SKILL.md +5 -0
  239. package/corpus/templates/slides/.agents/skills/security/SKILL.md +7 -0
  240. package/corpus/templates/slides/AGENTS.md +4 -0
  241. package/corpus/templates/slides/actions/generate-image-api.ts +15 -3
  242. package/corpus/templates/slides/app/routes/settings.tsx +2 -2
  243. package/corpus/templates/slides/app/routes/team.tsx +1 -1
  244. package/corpus/templates/slides/changelog/2026-07-08-generated-slide-images-now-return-hosted-file-storage-urls-i.md +6 -0
  245. package/corpus/templates/slides/shared/api.ts +1 -1
  246. package/dist/application-state/store.d.ts.map +1 -1
  247. package/dist/application-state/store.js +8 -2
  248. package/dist/application-state/store.js.map +1 -1
  249. package/dist/cli/sync-builder-starter-manifest.d.ts.map +1 -1
  250. package/dist/cli/sync-builder-starter-manifest.js +24 -1
  251. package/dist/cli/sync-builder-starter-manifest.js.map +1 -1
  252. package/dist/client/AssistantChat.d.ts.map +1 -1
  253. package/dist/client/AssistantChat.js +24 -13
  254. package/dist/client/AssistantChat.js.map +1 -1
  255. package/dist/client/PoweredByBadge.d.ts +1 -1
  256. package/dist/client/PoweredByBadge.d.ts.map +1 -1
  257. package/dist/client/PoweredByBadge.js +3 -9
  258. package/dist/client/PoweredByBadge.js.map +1 -1
  259. package/dist/client/agent-chat-adapter.d.ts.map +1 -1
  260. package/dist/client/agent-chat-adapter.js +28 -9
  261. package/dist/client/agent-chat-adapter.js.map +1 -1
  262. package/dist/client/chat/widgets/DataTableWidget.d.ts.map +1 -1
  263. package/dist/client/chat/widgets/DataTableWidget.js +19 -2
  264. package/dist/client/chat/widgets/DataTableWidget.js.map +1 -1
  265. package/dist/client/db-admin/TableEditor.d.ts.map +1 -1
  266. package/dist/client/db-admin/TableEditor.js +19 -2
  267. package/dist/client/db-admin/TableEditor.js.map +1 -1
  268. package/dist/client/db-admin/export-utils.d.ts +2 -0
  269. package/dist/client/db-admin/export-utils.d.ts.map +1 -1
  270. package/dist/client/db-admin/export-utils.js +11 -3
  271. package/dist/client/db-admin/export-utils.js.map +1 -1
  272. package/dist/client/db-admin/useAgentSync.d.ts.map +1 -1
  273. package/dist/client/db-admin/useAgentSync.js +63 -0
  274. package/dist/client/db-admin/useAgentSync.js.map +1 -1
  275. package/dist/client/error-capture.d.ts.map +1 -1
  276. package/dist/client/error-capture.js +41 -7
  277. package/dist/client/error-capture.js.map +1 -1
  278. package/dist/client/extensions/EmbeddedExtension.d.ts.map +1 -1
  279. package/dist/client/extensions/EmbeddedExtension.js +3 -1
  280. package/dist/client/extensions/EmbeddedExtension.js.map +1 -1
  281. package/dist/client/extensions/ExtensionViewer.d.ts.map +1 -1
  282. package/dist/client/extensions/ExtensionViewer.js +32 -5
  283. package/dist/client/extensions/ExtensionViewer.js.map +1 -1
  284. package/dist/client/extensions/InlineExtensionFrame.d.ts.map +1 -1
  285. package/dist/client/extensions/InlineExtensionFrame.js +3 -1
  286. package/dist/client/extensions/InlineExtensionFrame.js.map +1 -1
  287. package/dist/client/extensions/portable-extension.d.ts.map +1 -1
  288. package/dist/client/extensions/portable-extension.js +1 -0
  289. package/dist/client/extensions/portable-extension.js.map +1 -1
  290. package/dist/client/org/OrgSwitcher.d.ts +1 -1
  291. package/dist/client/org/OrgSwitcher.d.ts.map +1 -1
  292. package/dist/client/org/OrgSwitcher.js +11 -6
  293. package/dist/client/org/OrgSwitcher.js.map +1 -1
  294. package/dist/client/org/TeamPage.d.ts.map +1 -1
  295. package/dist/client/org/TeamPage.js +5 -5
  296. package/dist/client/org/TeamPage.js.map +1 -1
  297. package/dist/client/settings/SettingsPanel.d.ts.map +1 -1
  298. package/dist/client/settings/SettingsPanel.js +37 -14
  299. package/dist/client/settings/SettingsPanel.js.map +1 -1
  300. package/dist/client/settings/SettingsSection.js +1 -1
  301. package/dist/client/settings/SettingsSection.js.map +1 -1
  302. package/dist/client/settings/SettingsTabsPage.d.ts.map +1 -1
  303. package/dist/client/settings/SettingsTabsPage.js +21 -10
  304. package/dist/client/settings/SettingsTabsPage.js.map +1 -1
  305. package/dist/collab/routes.d.ts +1 -1
  306. package/dist/db-admin/agent-tools.d.ts.map +1 -1
  307. package/dist/db-admin/agent-tools.js +7 -2
  308. package/dist/db-admin/agent-tools.js.map +1 -1
  309. package/dist/db-admin/operations.d.ts.map +1 -1
  310. package/dist/db-admin/operations.js +90 -4
  311. package/dist/db-admin/operations.js.map +1 -1
  312. package/dist/db-admin/routes.d.ts.map +1 -1
  313. package/dist/db-admin/routes.js +1 -0
  314. package/dist/db-admin/routes.js.map +1 -1
  315. package/dist/db-admin/types.d.ts +4 -0
  316. package/dist/db-admin/types.d.ts.map +1 -1
  317. package/dist/db-admin/types.js.map +1 -1
  318. package/dist/extensions/html-shell.d.ts +5 -4
  319. package/dist/extensions/html-shell.d.ts.map +1 -1
  320. package/dist/extensions/html-shell.js.map +1 -1
  321. package/dist/extensions/routes.d.ts.map +1 -1
  322. package/dist/extensions/routes.js +8 -0
  323. package/dist/extensions/routes.js.map +1 -1
  324. package/dist/navigation/index.d.ts +1 -1
  325. package/dist/navigation/index.d.ts.map +1 -1
  326. package/dist/navigation/index.js +1 -1
  327. package/dist/navigation/index.js.map +1 -1
  328. package/dist/notifications/channels.js +1 -17
  329. package/dist/notifications/channels.js.map +1 -1
  330. package/dist/notifications/routes.d.ts +1 -1
  331. package/dist/resources/handlers.d.ts +5 -0
  332. package/dist/resources/handlers.d.ts.map +1 -1
  333. package/dist/resources/handlers.js +9 -8
  334. package/dist/resources/handlers.js.map +1 -1
  335. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  336. package/dist/server/poll.d.ts +12 -4
  337. package/dist/server/poll.d.ts.map +1 -1
  338. package/dist/server/poll.js +236 -9
  339. package/dist/server/poll.js.map +1 -1
  340. package/dist/styles/agent-native.css +27 -0
  341. package/dist/templates/default/.agents/skills/actions/SKILL.md +5 -0
  342. package/dist/templates/default/.agents/skills/adding-a-feature/SKILL.md +5 -0
  343. package/dist/templates/default/.agents/skills/real-time-sync/SKILL.md +1 -1
  344. package/dist/templates/default/.agents/skills/security/SKILL.md +7 -0
  345. package/dist/templates/default/.agents/skills/storing-data/SKILL.md +5 -1
  346. package/dist/templates/default/AGENTS.md +4 -2
  347. package/dist/templates/headless/AGENTS.md +4 -0
  348. package/dist/templates/workspace-core/.agents/skills/actions/SKILL.md +5 -0
  349. package/dist/templates/workspace-core/.agents/skills/adding-a-feature/SKILL.md +5 -0
  350. package/dist/templates/workspace-core/.agents/skills/context-awareness/SKILL.md +4 -0
  351. package/dist/templates/workspace-core/.agents/skills/extensions/SKILL.md +6 -0
  352. package/dist/templates/workspace-core/.agents/skills/performance/SKILL.md +4 -0
  353. package/dist/templates/workspace-core/.agents/skills/real-time-sync/SKILL.md +1 -1
  354. package/dist/templates/workspace-core/.agents/skills/security/SKILL.md +7 -0
  355. package/dist/templates/workspace-core/.agents/skills/storing-data/SKILL.md +5 -1
  356. package/dist/templates/workspace-core/AGENTS.md +7 -1
  357. package/dist/templates/workspace-root/AGENTS.md +7 -1
  358. package/package.json +1 -1
  359. package/src/templates/default/.agents/skills/actions/SKILL.md +5 -0
  360. package/src/templates/default/.agents/skills/adding-a-feature/SKILL.md +5 -0
  361. package/src/templates/default/.agents/skills/real-time-sync/SKILL.md +1 -1
  362. package/src/templates/default/.agents/skills/security/SKILL.md +7 -0
  363. package/src/templates/default/.agents/skills/storing-data/SKILL.md +5 -1
  364. package/src/templates/default/AGENTS.md +4 -2
  365. package/src/templates/headless/AGENTS.md +4 -0
  366. package/src/templates/workspace-core/.agents/skills/actions/SKILL.md +5 -0
  367. package/src/templates/workspace-core/.agents/skills/adding-a-feature/SKILL.md +5 -0
  368. package/src/templates/workspace-core/.agents/skills/context-awareness/SKILL.md +4 -0
  369. package/src/templates/workspace-core/.agents/skills/extensions/SKILL.md +6 -0
  370. package/src/templates/workspace-core/.agents/skills/performance/SKILL.md +4 -0
  371. package/src/templates/workspace-core/.agents/skills/real-time-sync/SKILL.md +1 -1
  372. package/src/templates/workspace-core/.agents/skills/security/SKILL.md +7 -0
  373. package/src/templates/workspace-core/.agents/skills/storing-data/SKILL.md +5 -1
  374. package/src/templates/workspace-core/AGENTS.md +7 -1
  375. package/src/templates/workspace-root/AGENTS.md +7 -1
@@ -34,6 +34,7 @@ import {
34
34
  count,
35
35
  desc,
36
36
  eq,
37
+ gt,
37
38
  gte,
38
39
  isNull,
39
40
  lte,
@@ -203,6 +204,7 @@ const MAX_REDIRECT_HOPS = 5;
203
204
  const MONITOR_RUNNING_STALE_MS = 5 * 60 * 1000;
204
205
  const DEFAULT_RESULT_RETENTION_DAYS = 30;
205
206
  const DEFAULT_MONITOR_LIMIT_PER_OWNER = 100;
207
+ export const DEFAULT_MONITOR_TIMEOUT_MS = 10_000;
206
208
  const MAX_REQUEST_HEADER_COUNT = 20;
207
209
  const MAX_REQUEST_HEADER_NAME_LENGTH = 128;
208
210
  const MAX_REQUEST_HEADER_VALUE_BYTES = 2048;
@@ -215,6 +217,7 @@ const MIN_INTERVAL_SECONDS = 30;
215
217
  const MAX_INTERVAL_SECONDS = 24 * 60 * 60;
216
218
  const MIN_TIMEOUT_MS = 1000;
217
219
  const MAX_TIMEOUT_MS = 120_000;
220
+ const DEFAULT_TRANSIENT_FAILURE_CONFIRMATION_CHECKS = 2;
218
221
 
219
222
  const ASSERTION_TYPES: AssertionType[] = [
220
223
  "body_contains",
@@ -280,6 +283,14 @@ function monitorLimitPerOwner(): number {
280
283
  : DEFAULT_MONITOR_LIMIT_PER_OWNER;
281
284
  }
282
285
 
286
+ function transientFailureConfirmationChecks(): number {
287
+ const raw = process.env.UPTIME_MONITOR_TRANSIENT_FAILURE_CONFIRMATION_CHECKS;
288
+ const parsed = Number.parseInt(raw ?? "", 10);
289
+ return Number.isFinite(parsed) && parsed > 0
290
+ ? Math.min(parsed, 10)
291
+ : DEFAULT_TRANSIENT_FAILURE_CONFIRMATION_CHECKS;
292
+ }
293
+
283
294
  export function hostFromUrl(url: string): string {
284
295
  try {
285
296
  return new URL(url).host;
@@ -674,6 +685,40 @@ export function evaluateCheck(params: EvaluateCheckParams): {
674
685
  // SSRF-safe fetch + probe
675
686
  // ---------------------------------------------------------------------------
676
687
 
688
+ // A single SSRF-safe dispatcher (undici Agent) is reused across every probe.
689
+ // Building a fresh Agent per check disabled HTTP keep-alive — each check paid a
690
+ // cold DNS + TCP + TLS handshake, inflating the recorded latency well above the
691
+ // site's real response time (and occasionally spiking near the timeout) — and
692
+ // leaked Agents, which are never closed. The connect-time private-IP guard runs
693
+ // on every new socket, so reuse keeps the exact same SSRF protection.
694
+ // `undefined` = not built yet; the resolved value may be `null` on runtimes
695
+ // without undici / node:dns, in which case callers fall back to plain `fetch`.
696
+ let sharedSsrfDispatcherPromise: Promise<unknown | null> | undefined;
697
+
698
+ function getSharedSsrfDispatcher(): Promise<unknown | null> {
699
+ if (!sharedSsrfDispatcherPromise) {
700
+ sharedSsrfDispatcherPromise = createSsrfSafeDispatcher().catch(() => null);
701
+ }
702
+ return sharedSsrfDispatcherPromise;
703
+ }
704
+
705
+ async function prepareMonitorFetch(
706
+ url: string,
707
+ opts: { allowPrivateHosts: boolean },
708
+ ): Promise<{ dispatcher: unknown | undefined }> {
709
+ const dispatcher = opts.allowPrivateHosts
710
+ ? undefined
711
+ : ((await getSharedSsrfDispatcher()) ?? undefined);
712
+
713
+ if (!opts.allowPrivateHosts && (await isBlockedExtensionUrlWithDns(url))) {
714
+ throw new Error(
715
+ `SSRF blocked: refusing to fetch private/internal address (${url})`,
716
+ );
717
+ }
718
+
719
+ return { dispatcher };
720
+ }
721
+
677
722
  async function safeMonitorFetch(
678
723
  url: string,
679
724
  init: RequestInit,
@@ -681,17 +726,26 @@ async function safeMonitorFetch(
681
726
  followRedirects: boolean;
682
727
  maxRedirects: number;
683
728
  allowPrivateHosts: boolean;
729
+ /** Prebuilt outside the abort window so SSRF setup is not billed as site latency. */
730
+ dispatcher?: unknown;
731
+ /** When true, the caller already DNS-checked `url` before starting the timer. */
732
+ initialDnsChecked?: boolean;
684
733
  },
685
734
  ): Promise<Response> {
686
- const dispatcher = opts.allowPrivateHosts
687
- ? undefined
688
- : ((await createSsrfSafeDispatcher()) ?? undefined);
735
+ const dispatcher =
736
+ opts.dispatcher !== undefined
737
+ ? opts.dispatcher
738
+ : opts.allowPrivateHosts
739
+ ? undefined
740
+ : ((await getSharedSsrfDispatcher()) ?? undefined);
689
741
 
690
742
  let currentUrl = url;
691
743
  const maxHops = opts.followRedirects ? opts.maxRedirects : 0;
692
744
 
693
745
  for (let hop = 0; hop <= maxHops; hop++) {
746
+ const skipDns = hop === 0 && opts.initialDnsChecked;
694
747
  if (
748
+ !skipDns &&
695
749
  !opts.allowPrivateHosts &&
696
750
  (await isBlockedExtensionUrlWithDns(currentUrl))
697
751
  ) {
@@ -710,6 +764,7 @@ async function safeMonitorFetch(
710
764
  if (opts.followRedirects && isRedirect) {
711
765
  const location = response.headers.get("location");
712
766
  if (!location) return response;
767
+ await cancelResponseBody(response);
713
768
  currentUrl = new URL(location, currentUrl).href;
714
769
  continue;
715
770
  }
@@ -720,17 +775,50 @@ async function safeMonitorFetch(
720
775
  );
721
776
  }
722
777
 
723
- async function readCappedText(res: Response, cap: number): Promise<string> {
778
+ async function cancelResponseBody(res: Response): Promise<void> {
779
+ try {
780
+ await res.body?.cancel();
781
+ } catch {
782
+ // Best-effort cleanup only.
783
+ }
784
+ }
785
+
786
+ async function readCappedText(
787
+ res: Response,
788
+ cap: number,
789
+ timeoutMs?: number,
790
+ ): Promise<{ text: string; timedOut: boolean }> {
791
+ let timedOut = false;
792
+ let timer: ReturnType<typeof setTimeout> | null = null;
724
793
  const body = res.body;
725
794
  if (!body) {
726
795
  try {
727
- const text = await res.text();
728
- return text.length > cap ? text.slice(0, cap) : text;
796
+ const text =
797
+ timeoutMs && timeoutMs > 0
798
+ ? await Promise.race([
799
+ res.text(),
800
+ new Promise<string>((resolve) => {
801
+ timer = setTimeout(() => {
802
+ timedOut = true;
803
+ resolve("");
804
+ }, timeoutMs);
805
+ }),
806
+ ])
807
+ : await res.text();
808
+ return { text: text.length > cap ? text.slice(0, cap) : text, timedOut };
729
809
  } catch {
730
- return "";
810
+ return { text: "", timedOut };
811
+ } finally {
812
+ if (timer) clearTimeout(timer);
731
813
  }
732
814
  }
733
815
  const reader = body.getReader();
816
+ if (timeoutMs && timeoutMs > 0) {
817
+ timer = setTimeout(() => {
818
+ timedOut = true;
819
+ void reader.cancel().catch(() => {});
820
+ }, timeoutMs);
821
+ }
734
822
  const chunks: Uint8Array[] = [];
735
823
  let total = 0;
736
824
  try {
@@ -745,6 +833,7 @@ async function readCappedText(res: Response, cap: number): Promise<string> {
745
833
  } catch {
746
834
  // Partial body is fine for text assertions.
747
835
  } finally {
836
+ if (timer) clearTimeout(timer);
748
837
  try {
749
838
  await reader.cancel();
750
839
  } catch {
@@ -761,7 +850,10 @@ async function readCappedText(res: Response, cap: number): Promise<string> {
761
850
  merged.set(slice, offset);
762
851
  offset += slice.length;
763
852
  }
764
- return new TextDecoder("utf-8", { fatal: false }).decode(merged);
853
+ return {
854
+ text: new TextDecoder("utf-8", { fatal: false }).decode(merged),
855
+ timedOut,
856
+ };
765
857
  }
766
858
 
767
859
  function headersToObject(headers: Headers): Record<string, string> {
@@ -772,10 +864,22 @@ function headersToObject(headers: Headers): Record<string, string> {
772
864
  return out;
773
865
  }
774
866
 
867
+ function needsResponseBody(assertions: Assertion[]): boolean {
868
+ return assertions.some(
869
+ (assertion) =>
870
+ assertion.type === "body_contains" || assertion.type === "body_absent",
871
+ );
872
+ }
873
+
775
874
  /**
776
875
  * Execute one probe for `monitor`. Performs an SSRF-safe fetch with an
777
876
  * AbortController timeout, measures latency, caps the response body, and
778
877
  * classifies the result. Never throws — failures are captured in the outcome.
878
+ *
879
+ * The abort budget covers only the HTTP fetch (headers / redirect chain), not
880
+ * SSRF dispatcher/DNS setup or optional body reads. Billing setup time against
881
+ * `timeoutMs` produced false "Timed out after Nms" alerts when the site itself
882
+ * was fine.
779
883
  */
780
884
  export async function runMonitorCheck(
781
885
  monitor: Pick<
@@ -817,14 +921,50 @@ export async function runMonitorCheck(
817
921
  };
818
922
  }
819
923
 
820
- const controller = new AbortController();
821
924
  const timeoutMs = clampInt(
822
925
  monitor.timeoutMs,
823
926
  MIN_TIMEOUT_MS,
824
927
  MAX_TIMEOUT_MS,
825
- 15000,
928
+ DEFAULT_MONITOR_TIMEOUT_MS,
826
929
  );
827
- const timer = setTimeout(() => controller.abort(), timeoutMs);
930
+
931
+ let dispatcher: unknown | undefined;
932
+ try {
933
+ ({ dispatcher } = await prepareMonitorFetch(monitor.url, {
934
+ allowPrivateHosts,
935
+ }));
936
+ } catch (err) {
937
+ const message =
938
+ err instanceof Error ? err.message : String(err ?? "check failed");
939
+ const isConfig = message.startsWith("SSRF blocked");
940
+ const errorText = message.slice(0, 500);
941
+ const outcome = evaluateCheck({
942
+ statusCode: null,
943
+ latencyMs: null,
944
+ bodyText: "",
945
+ headers: {},
946
+ matcher,
947
+ assertions,
948
+ fetchError: errorText,
949
+ errorKind: isConfig ? "config" : "network",
950
+ });
951
+ return {
952
+ checkedAt,
953
+ statusCode: null,
954
+ latencyMs: null,
955
+ status: outcome.status,
956
+ ok: outcome.ok,
957
+ failedAssertions: outcome.failedAssertions,
958
+ error: errorText,
959
+ };
960
+ }
961
+
962
+ const controller = new AbortController();
963
+ let timedOut = false;
964
+ const timer = setTimeout(() => {
965
+ timedOut = true;
966
+ controller.abort();
967
+ }, timeoutMs);
828
968
  const start = Date.now();
829
969
 
830
970
  try {
@@ -845,15 +985,32 @@ export async function runMonitorCheck(
845
985
  followRedirects: monitor.followRedirects,
846
986
  maxRedirects: MAX_REDIRECT_HOPS,
847
987
  allowPrivateHosts,
988
+ dispatcher,
989
+ initialDnsChecked: Boolean(dispatcher),
848
990
  });
849
991
 
850
- const bodyText =
851
- method === "HEAD"
852
- ? ""
853
- : await readCappedText(response, MAX_RESPONSE_BODY_BYTES);
992
+ // Stop the abort timer as soon as headers arrive so a slow/optional body
993
+ // read cannot be mislabeled as a request timeout with a null status code.
994
+ clearTimeout(timer);
995
+
854
996
  const latencyMs = Date.now() - start;
855
997
  const statusCode = response.status;
856
998
  const headers = headersToObject(response.headers);
999
+ let bodyText = "";
1000
+ let bodyReadError: string | null = null;
1001
+ if (method !== "HEAD" && needsResponseBody(assertions)) {
1002
+ const body = await readCappedText(
1003
+ response,
1004
+ MAX_RESPONSE_BODY_BYTES,
1005
+ timeoutMs,
1006
+ );
1007
+ bodyText = body.text;
1008
+ if (body.timedOut) {
1009
+ bodyReadError = `Response body read timed out after ${timeoutMs}ms`;
1010
+ }
1011
+ } else {
1012
+ await cancelResponseBody(response);
1013
+ }
857
1014
 
858
1015
  const outcome = evaluateCheck({
859
1016
  statusCode,
@@ -863,31 +1020,33 @@ export async function runMonitorCheck(
863
1020
  matcher,
864
1021
  assertions,
865
1022
  });
1023
+ const failedAssertions = bodyReadError
1024
+ ? [...outcome.failedAssertions, bodyReadError]
1025
+ : outcome.failedAssertions;
1026
+ const status = bodyReadError ? "down" : outcome.status;
866
1027
 
867
1028
  return {
868
1029
  checkedAt,
869
1030
  statusCode,
870
1031
  latencyMs,
871
- status: outcome.status,
872
- ok: outcome.ok,
873
- failedAssertions: outcome.failedAssertions,
874
- error: outcome.failedAssertions.length
875
- ? outcome.failedAssertions.join("; ")
876
- : null,
1032
+ status,
1033
+ ok: status === "up" && outcome.ok,
1034
+ failedAssertions,
1035
+ error: failedAssertions.length ? failedAssertions.join("; ") : null,
877
1036
  };
878
1037
  } catch (err) {
879
- const isTimeout =
880
- (err as { name?: string })?.name === "AbortError" ||
881
- controller.signal.aborted;
882
1038
  const message =
883
1039
  err instanceof Error ? err.message : String(err ?? "check failed");
884
1040
  const isConfig = message.startsWith("SSRF blocked");
1041
+ // Only our timer sets `timedOut`. Prefer the real SSRF/config message when
1042
+ // both happened (e.g. abort fired while a redirect DNS check was in flight).
1043
+ const isTimeout = timedOut && !isConfig;
885
1044
  const errorText = isTimeout
886
1045
  ? `Timed out after ${timeoutMs}ms`
887
1046
  : message.slice(0, 500);
888
1047
  const outcome = evaluateCheck({
889
1048
  statusCode: null,
890
- latencyMs: null,
1049
+ latencyMs: isTimeout ? timeoutMs : null,
891
1050
  bodyText: "",
892
1051
  headers: {},
893
1052
  matcher,
@@ -898,7 +1057,7 @@ export async function runMonitorCheck(
898
1057
  return {
899
1058
  checkedAt,
900
1059
  statusCode: null,
901
- latencyMs: null,
1060
+ latencyMs: isTimeout ? timeoutMs : null,
902
1061
  status: outcome.status,
903
1062
  ok: outcome.ok,
904
1063
  failedAssertions: outcome.failedAssertions,
@@ -925,7 +1084,7 @@ function rowToMonitor(row: any): Monitor {
925
1084
  ),
926
1085
  requestBody: row.requestBody ?? null,
927
1086
  intervalSeconds: Number(row.intervalSeconds ?? 300),
928
- timeoutMs: Number(row.timeoutMs ?? 15000),
1087
+ timeoutMs: Number(row.timeoutMs ?? DEFAULT_MONITOR_TIMEOUT_MS),
929
1088
  expectedStatus: normalizeStatusMatcher(
930
1089
  safeJsonParse<unknown>(row.expectedStatus, null),
931
1090
  ),
@@ -1155,10 +1314,10 @@ export async function saveMonitor(
1155
1314
  300,
1156
1315
  );
1157
1316
  const timeoutMs = clampInt(
1158
- input.timeoutMs ?? 15000,
1317
+ input.timeoutMs ?? DEFAULT_MONITOR_TIMEOUT_MS,
1159
1318
  MIN_TIMEOUT_MS,
1160
1319
  MAX_TIMEOUT_MS,
1161
- 15000,
1320
+ DEFAULT_MONITOR_TIMEOUT_MS,
1162
1321
  );
1163
1322
  const expectedStatus = normalizeStatusMatcher(input.expectedStatus);
1164
1323
  const assertions = normalizeAssertions(input.assertions);
@@ -1433,6 +1592,25 @@ function describeCause(outcome: CheckOutcome): string {
1433
1592
  return "Check failed";
1434
1593
  }
1435
1594
 
1595
+ function isTransientNoResponseFailure(outcome: CheckOutcome): boolean {
1596
+ return (
1597
+ outcome.status === "down" &&
1598
+ outcome.statusCode == null &&
1599
+ Boolean(outcome.error)
1600
+ );
1601
+ }
1602
+
1603
+ export function shouldOpenMonitorIncident(
1604
+ outcome: CheckOutcome,
1605
+ priorConsecutiveFailures: number,
1606
+ confirmationChecks = transientFailureConfirmationChecks(),
1607
+ ): boolean {
1608
+ const needed = isTransientNoResponseFailure(outcome)
1609
+ ? Math.max(1, Math.min(10, Math.floor(confirmationChecks)))
1610
+ : 1;
1611
+ return Math.max(0, Math.floor(priorConsecutiveFailures)) + 1 >= needed;
1612
+ }
1613
+
1436
1614
  async function getOpenIncident(
1437
1615
  monitorId: string,
1438
1616
  ctx: AccessCtx,
@@ -1477,6 +1655,29 @@ async function recentlyResolvedWithinCooldown(
1477
1655
  return now.getTime() - resolved < monitor.cooldownMinutes * 60 * 1000;
1478
1656
  }
1479
1657
 
1658
+ async function getFailureStreakStartedAt(
1659
+ monitor: Monitor,
1660
+ ctx: AccessCtx,
1661
+ fallback: string,
1662
+ ): Promise<string> {
1663
+ const db = getDb() as any;
1664
+ const table = schema.monitorCheckResults;
1665
+ const clauses: any[] = [
1666
+ resultsOwnerWhere(ctx),
1667
+ eq(table.monitorId, monitor.id),
1668
+ eq(table.ok, false),
1669
+ ];
1670
+ if (monitor.lastSuccessAt)
1671
+ clauses.push(gt(table.checkedAt, monitor.lastSuccessAt));
1672
+ const [row] = await db
1673
+ .select({ checkedAt: table.checkedAt })
1674
+ .from(table)
1675
+ .where(and(...clauses))
1676
+ .orderBy(asc(table.checkedAt))
1677
+ .limit(1);
1678
+ return row?.checkedAt ?? fallback;
1679
+ }
1680
+
1480
1681
  async function notifyMonitorDown(monitor: Monitor, outcome: CheckOutcome) {
1481
1682
  const host = hostFromUrl(monitor.url);
1482
1683
  const label = outcome.status === "degraded" ? "degraded" : "down";
@@ -1560,8 +1761,8 @@ export interface EvaluateMonitorResult {
1560
1761
 
1561
1762
  /**
1562
1763
  * Open / update / resolve incidents based on the latest outcome and send
1563
- * notifications. On a transition into failure it opens an incident and
1564
- * notifies (respecting an anti-flap cooldown); on recovery it resolves the
1764
+ * notifications. On a transition into confirmed failure it opens an incident
1765
+ * and notifies (respecting an anti-flap cooldown); on recovery it resolves the
1565
1766
  * open incident and sends a recovery notice.
1566
1767
  */
1567
1768
  export async function evaluateAndNotifyMonitor(
@@ -1575,6 +1776,8 @@ export async function evaluateAndNotifyMonitor(
1575
1776
 
1576
1777
  if (!outcome.ok) {
1577
1778
  const cause = describeCause(outcome);
1779
+ const priorConsecutiveFailures = monitor.consecutiveFailures ?? 0;
1780
+ const nextChecksFailed = priorConsecutiveFailures + 1;
1578
1781
  if (open) {
1579
1782
  const nextStatus =
1580
1783
  open.status === "down" ? "down" : (outcome.status as MonitorStatus);
@@ -1590,6 +1793,10 @@ export async function evaluateAndNotifyMonitor(
1590
1793
  return { status: outcome.status, incidentId: open.id, notified: false };
1591
1794
  }
1592
1795
 
1796
+ if (!shouldOpenMonitorIncident(outcome, priorConsecutiveFailures)) {
1797
+ return { status: outcome.status, notified: false };
1798
+ }
1799
+
1593
1800
  const suppressed = await recentlyResolvedWithinCooldown(monitor, ctx, now);
1594
1801
  let notificationId: string | undefined;
1595
1802
  if (!suppressed) {
@@ -1604,17 +1811,21 @@ export async function evaluateAndNotifyMonitor(
1604
1811
  }
1605
1812
  }
1606
1813
  const incidentId = randomUUID();
1814
+ const startedAt =
1815
+ nextChecksFailed > 1
1816
+ ? await getFailureStreakStartedAt(monitor, ctx, outcome.checkedAt)
1817
+ : outcome.checkedAt;
1607
1818
  await db.insert(schema.monitorIncidents).values({
1608
1819
  id: incidentId,
1609
1820
  monitorId: monitor.id,
1610
- startedAt: outcome.checkedAt,
1821
+ startedAt,
1611
1822
  resolvedAt: null,
1612
1823
  status: outcome.status === "degraded" ? "degraded" : "down",
1613
1824
  severity: monitor.severity,
1614
1825
  cause,
1615
1826
  lastError: outcome.error,
1616
1827
  notificationId: notificationId ?? null,
1617
- checksFailed: 1,
1828
+ checksFailed: nextChecksFailed,
1618
1829
  createdAt: outcome.checkedAt,
1619
1830
  ownerEmail: monitor.ownerEmail,
1620
1831
  orgId: monitor.orgId,
@@ -568,6 +568,8 @@ const runAnalyticsMigrations = runMigrations(
568
568
  severity TEXT NOT NULL DEFAULT 'warning',
569
569
  channels TEXT NOT NULL DEFAULT '["inbox"]',
570
570
  email_recipients TEXT NOT NULL DEFAULT '[]',
571
+ slack_webhook_url TEXT,
572
+ webhook_url TEXT,
571
573
  enabled BOOLEAN NOT NULL DEFAULT true,
572
574
  last_evaluated_at TEXT,
573
575
  last_triggered_at TEXT,
@@ -592,6 +594,8 @@ const runAnalyticsMigrations = runMigrations(
592
594
  severity TEXT NOT NULL DEFAULT 'warning',
593
595
  channels TEXT NOT NULL DEFAULT '["inbox"]',
594
596
  email_recipients TEXT NOT NULL DEFAULT '[]',
597
+ slack_webhook_url TEXT,
598
+ webhook_url TEXT,
595
599
  enabled INTEGER NOT NULL DEFAULT 1,
596
600
  last_evaluated_at TEXT,
597
601
  last_triggered_at TEXT,
@@ -891,7 +895,7 @@ const runAnalyticsMigrations = runMigrations(
891
895
  request_headers TEXT NOT NULL DEFAULT '{}',
892
896
  request_body TEXT,
893
897
  interval_seconds INTEGER NOT NULL DEFAULT 300,
894
- timeout_ms INTEGER NOT NULL DEFAULT 15000,
898
+ timeout_ms INTEGER NOT NULL DEFAULT 10000,
895
899
  expected_status TEXT NOT NULL DEFAULT '{"mode":"class","classes":["2xx"]}',
896
900
  assertions TEXT NOT NULL DEFAULT '[]',
897
901
  follow_redirects BOOLEAN NOT NULL DEFAULT true,
@@ -921,7 +925,7 @@ const runAnalyticsMigrations = runMigrations(
921
925
  request_headers TEXT NOT NULL DEFAULT '{}',
922
926
  request_body TEXT,
923
927
  interval_seconds INTEGER NOT NULL DEFAULT 300,
924
- timeout_ms INTEGER NOT NULL DEFAULT 15000,
928
+ timeout_ms INTEGER NOT NULL DEFAULT 10000,
925
929
  expected_status TEXT NOT NULL DEFAULT '{"mode":"class","classes":["2xx"]}',
926
930
  assertions TEXT NOT NULL DEFAULT '[]',
927
931
  follow_redirects INTEGER NOT NULL DEFAULT 1,
@@ -1124,6 +1128,112 @@ const runAnalyticsMigrations = runMigrations(
1124
1128
  name: "uptime-monitors-webhook-url",
1125
1129
  sql: `ALTER TABLE monitors ADD COLUMN IF NOT EXISTS webhook_url TEXT`,
1126
1130
  },
1131
+ {
1132
+ version: 108,
1133
+ name: "analytics-alert-rules-slack-webhook-url",
1134
+ sql: `ALTER TABLE analytics_alert_rules ADD COLUMN IF NOT EXISTS slack_webhook_url TEXT`,
1135
+ },
1136
+ {
1137
+ version: 109,
1138
+ name: "analytics-alert-rules-webhook-url",
1139
+ sql: `ALTER TABLE analytics_alert_rules ADD COLUMN IF NOT EXISTS webhook_url TEXT`,
1140
+ },
1141
+ {
1142
+ version: 110,
1143
+ name: "dashboards-updated-by",
1144
+ sql: `ALTER TABLE dashboards ADD COLUMN IF NOT EXISTS updated_by TEXT`,
1145
+ },
1146
+ {
1147
+ version: 111,
1148
+ name: "dashboard-revisions-table",
1149
+ sql: {
1150
+ postgres: `CREATE TABLE IF NOT EXISTS dashboard_revisions (
1151
+ id TEXT PRIMARY KEY,
1152
+ dashboard_id TEXT NOT NULL,
1153
+ kind TEXT NOT NULL,
1154
+ title TEXT NOT NULL,
1155
+ config TEXT NOT NULL,
1156
+ created_at TEXT NOT NULL DEFAULT (now()::text),
1157
+ created_by TEXT,
1158
+ owner_email TEXT NOT NULL DEFAULT 'local@localhost',
1159
+ org_id TEXT
1160
+ )`,
1161
+ sqlite: `CREATE TABLE IF NOT EXISTS dashboard_revisions (
1162
+ id TEXT PRIMARY KEY,
1163
+ dashboard_id TEXT NOT NULL,
1164
+ kind TEXT NOT NULL,
1165
+ title TEXT NOT NULL,
1166
+ config TEXT NOT NULL,
1167
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
1168
+ created_by TEXT,
1169
+ owner_email TEXT NOT NULL DEFAULT 'local@localhost',
1170
+ org_id TEXT
1171
+ )`,
1172
+ },
1173
+ },
1174
+ {
1175
+ version: 112,
1176
+ name: "dashboard-revisions-dashboard-created-idx",
1177
+ sql: `CREATE INDEX IF NOT EXISTS dashboard_revisions_dashboard_created_idx ON dashboard_revisions (dashboard_id, created_at)`,
1178
+ },
1179
+ {
1180
+ version: 113,
1181
+ name: "analysis-revisions-table",
1182
+ sql: {
1183
+ postgres: `CREATE TABLE IF NOT EXISTS analysis_revisions (
1184
+ id TEXT PRIMARY KEY,
1185
+ analysis_id TEXT NOT NULL,
1186
+ name TEXT NOT NULL,
1187
+ description TEXT NOT NULL DEFAULT '',
1188
+ question TEXT NOT NULL DEFAULT '',
1189
+ instructions TEXT NOT NULL DEFAULT '',
1190
+ data_sources TEXT NOT NULL DEFAULT '[]',
1191
+ result_markdown TEXT NOT NULL DEFAULT '',
1192
+ result_data TEXT,
1193
+ created_at TEXT NOT NULL DEFAULT (now()::text),
1194
+ created_by TEXT,
1195
+ owner_email TEXT NOT NULL DEFAULT 'local@localhost',
1196
+ org_id TEXT
1197
+ )`,
1198
+ sqlite: `CREATE TABLE IF NOT EXISTS analysis_revisions (
1199
+ id TEXT PRIMARY KEY,
1200
+ analysis_id TEXT NOT NULL,
1201
+ name TEXT NOT NULL,
1202
+ description TEXT NOT NULL DEFAULT '',
1203
+ question TEXT NOT NULL DEFAULT '',
1204
+ instructions TEXT NOT NULL DEFAULT '',
1205
+ data_sources TEXT NOT NULL DEFAULT '[]',
1206
+ result_markdown TEXT NOT NULL DEFAULT '',
1207
+ result_data TEXT,
1208
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
1209
+ created_by TEXT,
1210
+ owner_email TEXT NOT NULL DEFAULT 'local@localhost',
1211
+ org_id TEXT
1212
+ )`,
1213
+ },
1214
+ },
1215
+ {
1216
+ version: 114,
1217
+ name: "analysis-revisions-analysis-created-idx",
1218
+ sql: `CREATE INDEX IF NOT EXISTS analysis_revisions_analysis_created_idx ON analysis_revisions (analysis_id, created_at)`,
1219
+ },
1220
+ {
1221
+ version: 115,
1222
+ name: "uptime-monitors-timeout-10s",
1223
+ sql: {
1224
+ postgres: `
1225
+ ALTER TABLE monitors ALTER COLUMN timeout_ms SET DEFAULT 10000;
1226
+ UPDATE monitors
1227
+ SET timeout_ms = 10000, updated_at = COALESCE(NULLIF(updated_at, ''), now()::text)
1228
+ WHERE timeout_ms IS NULL OR timeout_ms < 10000 OR timeout_ms = 15000
1229
+ `,
1230
+ sqlite: `
1231
+ UPDATE monitors
1232
+ SET timeout_ms = 10000, updated_at = COALESCE(NULLIF(updated_at, ''), datetime('now'))
1233
+ WHERE timeout_ms IS NULL OR timeout_ms < 10000 OR timeout_ms = 15000
1234
+ `,
1235
+ },
1236
+ },
1127
1237
  ],
1128
1238
  { table: "analytics_migrations" },
1129
1239
  );
@@ -51,6 +51,7 @@ function rowToDashboard(row: any): DashboardRecord {
51
51
  visibility: row.visibility,
52
52
  createdAt: row.createdAt,
53
53
  updatedAt: row.updatedAt,
54
+ updatedBy: row.updatedBy ?? null,
54
55
  archivedAt: row.archivedAt ?? null,
55
56
  hiddenAt: row.hiddenAt ?? null,
56
57
  hiddenBy: row.hiddenBy ?? null,
@@ -377,6 +377,11 @@ Most operations should be actions. You only need custom routes in `server/routes
377
377
  - **Webhooks** — external services POST to a specific URL
378
378
  - **OAuth callbacks** — redirect-based flows that need specific URL patterns
379
379
 
380
+ When the agent needs a durable image or file URL, call the core `upload-image`
381
+ action or use `uploadFile()` in server code. Do not write base64 into SQL,
382
+ markdown, deck/design JSON, or action results. `_agentImages` on action results
383
+ is for ephemeral vision previews only, not persistence.
384
+
380
385
  If it's a standard CRUD operation, data query, or a wrapper around an action, use the action instead.
381
386
 
382
387
  ## Legacy Pattern (bare export)
@@ -50,7 +50,7 @@ The agent modifies data in SQL, but the UI runs in the browser. SSE bridges same
50
50
 
51
51
  For list/sidebar queries, use the same pattern — pass the counter into the queryKey of every list query you want to keep fresh.
52
52
 
53
- 4. **Fallback** polling calls `/_agent-native/poll?since=N`. It runs every 2 seconds until SSE is connected, then relaxes to 15 seconds (`SSE_FALLBACK_INTERVAL_MS`). If SSE is disabled or unavailable (e.g., edge/serverless deployments), polling continues at the 2 s cadence. Polling is the universal serverless fallback it detects DB timestamp changes even when the write happened in a different process or invocation.
53
+ 4. **Fallback** polling calls `/_agent-native/poll?since=N`. It runs every 2 seconds until SSE is connected, then relaxes to 15 seconds (`SSE_FALLBACK_INTERVAL_MS`). If SSE is disabled or unavailable (e.g., edge/serverless deployments), polling continues at the 2 s cadence. Polling is the universal serverless fallback: new framework writes are read from the durable `sync_events` log, while the older DB timestamp scan remains as a slower safety net for direct SQL writes and older processes.
54
54
 
55
55
  5. When the agent writes to the database, the version increments, SSE/polling detects it, and React Query refetches the affected queries.
56
56
 
@@ -51,6 +51,13 @@ export default defineAction({
51
51
 
52
52
  The legacy `parameters:` field (plain JSON Schema) has no runtime validation — do not use it for new code.
53
53
 
54
+ ## Large Payloads
55
+
56
+ Do not accept or persist unbounded base64/file blobs through actions, SQL writes,
57
+ or `application_state`. Route uploads through the file-upload provider and store
58
+ references. This prevents database bloat, slow hot paths, and accidental secret
59
+ or customer-data embedding inside binary payloads.
60
+
54
61
  ## SQL Injection
55
62
 
56
63
  Never concatenate user input into SQL strings. Use Drizzle ORM's query builder (always safe) or parameterized queries: