@agent-native/core 0.134.0 → 0.135.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (493) hide show
  1. package/corpus/README.md +3 -3
  2. package/corpus/core/CHANGELOG.md +113 -0
  3. package/corpus/core/docs/content/template-content-developers.mdx +1 -1
  4. package/corpus/core/package.json +3 -2
  5. package/corpus/core/src/automations/service.ts +41 -4
  6. package/corpus/core/src/cli/mcp-config-writers.ts +27 -21
  7. package/corpus/core/src/client/agent-page/AgentJobsTab.tsx +187 -85
  8. package/corpus/core/src/client/agent-page/AutomationDetailsDialog.tsx +208 -0
  9. package/corpus/core/src/client/agent-page/AutomationScheduleDialog.tsx +180 -0
  10. package/corpus/core/src/client/agent-page/TimezoneSelect.tsx +241 -0
  11. package/corpus/core/src/client/agent-page/use-jobs.ts +50 -10
  12. package/corpus/core/src/client/org/OrgSwitcher.tsx +26 -0
  13. package/corpus/core/src/client/settings/AccountSettingsCard.tsx +35 -31
  14. package/corpus/core/src/client/settings/SchedulingTimezoneField.tsx +71 -0
  15. package/corpus/core/src/client/settings/SettingsRow.tsx +113 -0
  16. package/corpus/core/src/client/settings/agent-settings-search.ts +2 -1
  17. package/corpus/core/src/client/settings/index.ts +6 -0
  18. package/corpus/core/src/integrations/identity.ts +2 -12
  19. package/corpus/core/src/jobs/actions/list-automation-runs.ts +33 -0
  20. package/corpus/core/src/jobs/actions/list-recurring-jobs.ts +27 -6
  21. package/corpus/core/src/jobs/actions/manage-recurring-job.ts +41 -9
  22. package/corpus/core/src/jobs/background-automation-runner.ts +88 -0
  23. package/corpus/core/src/jobs/cron.ts +52 -6
  24. package/corpus/core/src/jobs/frontmatter.ts +20 -0
  25. package/corpus/core/src/jobs/run-history.ts +242 -0
  26. package/corpus/core/src/jobs/scheduler.ts +73 -18
  27. package/corpus/core/src/jobs/tools.ts +54 -6
  28. package/corpus/core/src/localization/actions/get-localization-preference.ts +2 -2
  29. package/corpus/core/src/localization/actions/set-localization-preference.ts +49 -11
  30. package/corpus/core/src/localization/default-messages.ts +27 -0
  31. package/corpus/core/src/localization/shared.ts +39 -6
  32. package/corpus/core/src/localization/user-timezone.ts +36 -0
  33. package/corpus/core/src/org/app-roles-handlers.ts +1 -9
  34. package/corpus/core/src/org/index.ts +2 -0
  35. package/corpus/core/src/org/membership.ts +18 -0
  36. package/corpus/core/src/review/actions/create-review-comment.ts +4 -1
  37. package/corpus/core/src/review/actions/reply-review-comment.ts +4 -1
  38. package/corpus/core/src/review/index.ts +5 -0
  39. package/corpus/core/src/review/notifications.ts +163 -0
  40. package/corpus/core/src/review/types.ts +7 -0
  41. package/corpus/core/src/server/action-discovery.ts +4 -0
  42. package/corpus/core/src/server/activity-notifications.ts +174 -0
  43. package/corpus/core/src/server/app-name.ts +31 -0
  44. package/corpus/core/src/server/better-auth-instance.ts +4 -4
  45. package/corpus/core/src/server/email-templates.ts +70 -14
  46. package/corpus/core/src/server/email.ts +66 -3
  47. package/corpus/core/src/server/index.ts +10 -0
  48. package/corpus/core/src/sharing/index.ts +5 -0
  49. package/corpus/core/src/sharing/recipients.ts +67 -0
  50. package/corpus/core/src/triggers/actions/list-automations.ts +27 -8
  51. package/corpus/core/src/triggers/actions/manage-automation.ts +17 -6
  52. package/corpus/core/src/triggers/actions.ts +16 -4
  53. package/corpus/core/src/triggers/dispatcher.ts +44 -30
  54. package/corpus/core/src/triggers/routes.ts +19 -6
  55. package/corpus/core/src/vitest-config.ts +68 -0
  56. package/corpus/core/vitest.config.ts +5 -0
  57. package/corpus/templates/analytics/app/pages/Settings.tsx +37 -53
  58. package/corpus/templates/analytics/changelog/2026-08-01-new-error-alerts-now-reach-you-by-email-and-a-scheduled-dash.md +6 -0
  59. package/corpus/templates/analytics/server/jobs/dashboard-report.ts +43 -0
  60. package/corpus/templates/analytics/server/lib/error-capture.ts +5 -1
  61. package/corpus/templates/analytics/vitest.config.ts +16 -12
  62. package/corpus/templates/assets/actions/refresh-generation-run.ts +3 -0
  63. package/corpus/templates/assets/app/components/ui/switch.tsx +1 -0
  64. package/corpus/templates/assets/app/hooks/use-assets-prefs.ts +55 -0
  65. package/corpus/templates/assets/app/i18n-data.ts +30 -0
  66. package/corpus/templates/assets/app/routes/settings.tsx +58 -55
  67. package/corpus/templates/assets/changelog/2026-08-01-get-an-email-when-a-generation-you-started-finishes-or-fails.md +6 -0
  68. package/corpus/templates/assets/server/lib/generation-run-notifications.ts +89 -0
  69. package/corpus/templates/assets/server/lib/video-runs.ts +8 -1
  70. package/corpus/templates/assets/server/routes/_agent-native/assets/user-prefs.get.ts +17 -0
  71. package/corpus/templates/assets/server/routes/_agent-native/assets/user-prefs.put.ts +36 -0
  72. package/corpus/templates/assets/shared/assets-user-prefs.ts +10 -0
  73. package/corpus/templates/assets/vitest.config.ts +16 -12
  74. package/corpus/templates/brain/app/routes/settings.tsx +15 -15
  75. package/corpus/templates/brain/vitest.config.ts +16 -12
  76. package/corpus/templates/calendar/app/pages/Settings.tsx +32 -40
  77. package/corpus/templates/calendar/changelog/2026-08-01-language-and-appearance-now-sit-together-in-one-preferences-.md +6 -0
  78. package/corpus/templates/calendar/vitest.config.ts +16 -12
  79. package/corpus/templates/chat/app/routes/settings.tsx +14 -22
  80. package/corpus/templates/chat/vitest.config.ts +9 -0
  81. package/corpus/templates/clips/.agents/skills/video-editing/SKILL.md +31 -0
  82. package/corpus/templates/clips/AGENTS.md +1 -0
  83. package/corpus/templates/clips/actions/add-comment.ts +12 -1
  84. package/corpus/templates/clips/actions/create-meeting.ts +2 -2
  85. package/corpus/templates/clips/actions/create-recording.ts +2 -2
  86. package/corpus/templates/clips/actions/finalize-recording.ts +42 -6
  87. package/corpus/templates/clips/actions/generate-filmstrip.ts +154 -0
  88. package/corpus/templates/clips/actions/get-recording-player-data.ts +6 -0
  89. package/corpus/templates/clips/actions/import-loom-recording.ts +2 -2
  90. package/corpus/templates/clips/actions/lib/ensure-recording-filmstrip.ts +258 -0
  91. package/corpus/templates/clips/actions/lib/ensure-seekable-video.ts +11 -3
  92. package/corpus/templates/clips/actions/react-to-comment.ts +13 -0
  93. package/corpus/templates/clips/actions/react-to-recording.ts +10 -1
  94. package/corpus/templates/clips/actions/reply-to-comment.ts +12 -1
  95. package/corpus/templates/clips/actions/stitch-recordings.ts +2 -2
  96. package/corpus/templates/clips/app/components/editor/editor-layout.tsx +152 -23
  97. package/corpus/templates/clips/app/components/editor/waveform.tsx +237 -75
  98. package/corpus/templates/clips/app/components/recorder/recorder-engine.ts +23 -1
  99. package/corpus/templates/clips/app/components/settings/ai-setup-section.tsx +441 -0
  100. package/corpus/templates/clips/app/components/settings/slack-section.tsx +329 -0
  101. package/corpus/templates/clips/app/components/settings/types.ts +14 -0
  102. package/corpus/templates/clips/app/components/settings/video-storage-section.tsx +443 -0
  103. package/corpus/templates/clips/app/hooks/use-secret-status.ts +69 -0
  104. package/corpus/templates/clips/app/i18n/en-US.ts +9 -1
  105. package/corpus/templates/clips/app/lib/video-filmstrip.ts +262 -0
  106. package/corpus/templates/clips/app/routes/_app.settings._index.tsx +165 -1351
  107. package/corpus/templates/clips/changelog/2026-07-27-saved-clips-now-resume-from-the-last-uploaded-byte-after-an-.md +6 -0
  108. package/corpus/templates/clips/changelog/2026-07-29-meeting-notes-no-longer-transcribe-the-other-side-twice-when.md +6 -0
  109. package/corpus/templates/clips/changelog/2026-07-31-editor-timeline-now-shows-video-frames-behind-the-waveform-g.md +6 -0
  110. package/corpus/templates/clips/changelog/2026-07-31-email-notifications-now-go-out-when-someone-comments-on-or-r.md +6 -0
  111. package/corpus/templates/clips/changelog/2026-07-31-you-can-now-set-a-default-visibility-for-new-recordings-in-s.md +6 -0
  112. package/corpus/templates/clips/changelog/2026-08-01-email-notifications-for-comments-and-reactions-now-actually-.md +6 -0
  113. package/corpus/templates/clips/changelog/2026-08-03-the-desktop-app-now-opens-directly-beneath-its-tray-icon-on-.md +6 -0
  114. package/corpus/templates/clips/chrome-extension/package.json +1 -0
  115. package/corpus/templates/clips/chrome-extension/vitest.config.ts +9 -0
  116. package/corpus/templates/clips/desktop/package.json +1 -0
  117. package/corpus/templates/clips/desktop/src/app.tsx +17 -1
  118. package/corpus/templates/clips/desktop/src/hooks/useMeetingTranscription.ts +37 -48
  119. package/corpus/templates/clips/desktop/src/lib/recorder.ts +331 -36
  120. package/corpus/templates/clips/desktop/src/lib/transcription-capture.ts +7 -5
  121. package/corpus/templates/clips/desktop/src/lib/transcription-engine.ts +182 -106
  122. package/corpus/templates/clips/desktop/src/lib/upload-recovery.ts +129 -0
  123. package/corpus/templates/clips/desktop/src/overlays/live-transcript.tsx +43 -14
  124. package/corpus/templates/clips/desktop/src/overlays/recording-pill.tsx +3 -3
  125. package/corpus/templates/clips/desktop/src-tauri/src/echo_guard.rs +443 -0
  126. package/corpus/templates/clips/desktop/src-tauri/src/lib.rs +1 -0
  127. package/corpus/templates/clips/desktop/src-tauri/src/native_screen.rs +815 -15
  128. package/corpus/templates/clips/desktop/src-tauri/src/tray.rs +61 -11
  129. package/corpus/templates/clips/desktop/src-tauri/src/whisper_speech.rs +125 -16
  130. package/corpus/templates/clips/desktop/vitest.config.ts +9 -0
  131. package/corpus/templates/clips/server/db/schema.ts +17 -0
  132. package/corpus/templates/clips/server/jobs/media-verification.ts +4 -0
  133. package/corpus/templates/clips/server/lib/activity-notifications.ts +185 -0
  134. package/corpus/templates/clips/server/lib/recording-media-cleanup.ts +2 -0
  135. package/corpus/templates/clips/server/lib/recording-upload-state.ts +46 -9
  136. package/corpus/templates/clips/server/lib/recordings.ts +24 -0
  137. package/corpus/templates/clips/server/lib/resumable-session.ts +10 -4
  138. package/corpus/templates/clips/server/lib/transactional-email-templates.ts +109 -0
  139. package/corpus/templates/clips/server/lib/upload-interruption.ts +8 -0
  140. package/corpus/templates/clips/server/lib/upload-lease.ts +50 -4
  141. package/corpus/templates/clips/server/lib/video-filmstrip-sprite.ts +207 -0
  142. package/corpus/templates/clips/server/lib/video-remux.ts +13 -4
  143. package/corpus/templates/clips/server/plugins/db.ts +16 -0
  144. package/corpus/templates/clips/server/routes/api/_agent-native-background/post-finalize-worker.post.ts +4 -0
  145. package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/abort.post.ts +123 -36
  146. package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/chunk.post.ts +159 -29
  147. package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/interrupt.post.ts +197 -0
  148. package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/reset-chunks.post.ts +193 -39
  149. package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/resume.get.ts +191 -12
  150. package/corpus/templates/clips/shared/clips-ai-prefs.ts +6 -0
  151. package/corpus/templates/clips/shared/feature-flags.ts +12 -0
  152. package/corpus/templates/clips/shared/recording-core.ts +4 -0
  153. package/corpus/templates/clips/vitest.config.ts +23 -19
  154. package/corpus/templates/content/.agents/skills/document-editing/references/databases.md +6 -4
  155. package/corpus/templates/content/actions/_database-membership-lock.ts +25 -0
  156. package/corpus/templates/content/actions/_database-row-batch.ts +6 -2
  157. package/corpus/templates/content/actions/_database-source-utils.ts +289 -129
  158. package/corpus/templates/content/actions/_database-utils.ts +12 -3
  159. package/corpus/templates/content/actions/add-comment.ts +15 -1
  160. package/corpus/templates/content/actions/add-content-database-source-field-property.ts +39 -21
  161. package/corpus/templates/content/actions/attach-content-database-source.ts +4 -1
  162. package/corpus/templates/content/actions/change-content-database-source-role.ts +3 -5
  163. package/corpus/templates/content/actions/duplicate-document-property.ts +59 -46
  164. package/corpus/templates/content/actions/execute-builder-source-execution.ts +77 -71
  165. package/corpus/templates/content/actions/list-documents.ts +1 -0
  166. package/corpus/templates/content/actions/materialize-builder-required-fields.ts +7 -0
  167. package/corpus/templates/content/actions/remove-database-items.ts +183 -0
  168. package/corpus/templates/content/actions/set-document-property.ts +57 -39
  169. package/corpus/templates/content/actions/sync-local-folder-source.ts +7 -0
  170. package/corpus/templates/content/app/components/editor/DocumentEditor.tsx +5 -2
  171. package/corpus/templates/content/app/components/editor/DocumentToolbar.tsx +50 -1
  172. package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +471 -329
  173. package/corpus/templates/content/app/components/editor/database/row-access.ts +45 -0
  174. package/corpus/templates/content/app/components/editor/database/shared.tsx +141 -38
  175. package/corpus/templates/content/app/components/ui/switch.tsx +1 -0
  176. package/corpus/templates/content/app/hooks/use-content-database.ts +2 -2
  177. package/corpus/templates/content/app/hooks/use-content-prefs.ts +55 -0
  178. package/corpus/templates/content/app/i18n-data.ts +170 -0
  179. package/corpus/templates/content/app/routes/_app.settings.tsx +47 -18
  180. package/corpus/templates/content/changelog/2026-07-30-breadcrumb-menus-now-hide-internal-system-pages.md +6 -0
  181. package/corpus/templates/content/changelog/2026-07-30-database-rows-can-now-be-removed-without-deleting-their-page.md +6 -0
  182. package/corpus/templates/content/changelog/2026-08-01-get-an-email-when-someone-comments-on-replies-in-or-mentions.md +6 -0
  183. package/corpus/templates/content/parity/eval-scenarios.ts +2 -2
  184. package/corpus/templates/content/parity/matrix.md +1 -1
  185. package/corpus/templates/content/parity/matrix.ts +4 -5
  186. package/corpus/templates/content/server/lib/comment-notifications.ts +146 -0
  187. package/corpus/templates/content/server/routes/_agent-native/content/user-prefs.get.ts +17 -0
  188. package/corpus/templates/content/server/routes/_agent-native/content/user-prefs.put.ts +36 -0
  189. package/corpus/templates/content/shared/api.ts +4 -0
  190. package/corpus/templates/content/shared/content-user-prefs.ts +11 -0
  191. package/corpus/templates/content/vitest.config.ts +20 -16
  192. package/corpus/templates/crm/app/routes/settings.tsx +14 -22
  193. package/corpus/templates/crm/vitest.config.ts +8 -4
  194. package/corpus/templates/design/.generated/bridge/editor-chrome.generated.ts +26 -11
  195. package/corpus/templates/design/actions/take-design-screenshot.ts +7 -0
  196. package/corpus/templates/design/app/components/design/DesignCanvas.tsx +11 -3
  197. package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +4 -1
  198. package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +54 -17
  199. package/corpus/templates/design/app/components/design/design-canvas/local-runtime.ts +126 -0
  200. package/corpus/templates/design/app/components/templates/TemplatePreview.tsx +2 -0
  201. package/corpus/templates/design/app/pages/Index.tsx +3 -1
  202. package/corpus/templates/design/app/pages/Present.tsx +2 -1
  203. package/corpus/templates/design/app/routes/settings.tsx +14 -22
  204. package/corpus/templates/design/changelog/2026-07-30-a-broken-inline-script-in-a-generated-screen-is-now-caught-o.md +6 -0
  205. package/corpus/templates/design/changelog/2026-07-30-design-html-is-now-validated-with-a-spec-grade-html-parser-i.md +6 -0
  206. package/corpus/templates/design/changelog/2026-07-30-designs-now-load-their-tailwind-and-alpine-runtimes-from-the.md +6 -0
  207. package/corpus/templates/design/changelog/2026-07-30-designs-with-a-broken-alpine-expression-are-now-caught-on-sa.md +6 -0
  208. package/corpus/templates/design/changelog/2026-07-30-editing-a-design-no-longer-strips-its-styling-the-canvas-kep.md +6 -0
  209. package/corpus/templates/design/changelog/2026-08-01-review-comments-replies-and-mentions-now-send-an-email-to-th.md +6 -0
  210. package/corpus/templates/design/package.json +4 -0
  211. package/corpus/templates/design/shared/html-integrity.ts +802 -556
  212. package/corpus/templates/design/vitest.config.ts +18 -12
  213. package/corpus/templates/dispatch/app/routes/settings.tsx +26 -40
  214. package/corpus/templates/dispatch/vitest.config.ts +11 -7
  215. package/corpus/templates/factory/app/routes/settings.tsx +14 -22
  216. package/corpus/templates/factory/vitest.config.ts +9 -0
  217. package/corpus/templates/forms/app/routes/_app.settings.tsx +14 -22
  218. package/corpus/templates/forms/vitest.config.ts +27 -23
  219. package/corpus/templates/macros/app/routes/settings.tsx +14 -22
  220. package/corpus/templates/macros/vitest.config.ts +16 -12
  221. package/corpus/templates/mail/app/pages/SettingsPage.tsx +14 -16
  222. package/corpus/templates/mail/vitest.config.ts +16 -12
  223. package/corpus/templates/plan/app/routes/settings.tsx +30 -44
  224. package/corpus/templates/plan/vitest.config.ts +24 -20
  225. package/corpus/templates/slides/actions/add-slide-comment.ts +12 -1
  226. package/corpus/templates/slides/app/components/ui/switch.tsx +1 -0
  227. package/corpus/templates/slides/app/hooks/use-slides-prefs.ts +55 -0
  228. package/corpus/templates/slides/app/i18n/en-US.ts +4 -0
  229. package/corpus/templates/slides/app/routes/settings.tsx +45 -22
  230. package/corpus/templates/slides/changelog/2026-08-01-get-an-email-when-someone-comments-on-or-replies-in-your-dec.md +6 -0
  231. package/corpus/templates/slides/server/lib/comment-notifications.ts +182 -0
  232. package/corpus/templates/slides/server/routes/_agent-native/slides/user-prefs.get.ts +16 -0
  233. package/corpus/templates/slides/server/routes/_agent-native/slides/user-prefs.put.ts +36 -0
  234. package/corpus/templates/slides/shared/slides-user-prefs.ts +11 -0
  235. package/corpus/templates/slides/vitest.config.ts +17 -13
  236. package/corpus/templates/tasks/app/components/layout/Sidebar.tsx +2 -0
  237. package/corpus/templates/tasks/app/routes/settings.tsx +14 -22
  238. package/corpus/templates/tasks/vitest.config.ts +17 -13
  239. package/corpus/toolkit/CHANGELOG.md +9 -0
  240. package/corpus/toolkit/package.json +1 -1
  241. package/corpus/toolkit/vitest.config.ts +3 -0
  242. package/dist/automations/service.d.ts +2 -0
  243. package/dist/automations/service.d.ts.map +1 -1
  244. package/dist/automations/service.js +29 -4
  245. package/dist/automations/service.js.map +1 -1
  246. package/dist/cli/mcp-config-writers.d.ts.map +1 -1
  247. package/dist/cli/mcp-config-writers.js +24 -23
  248. package/dist/cli/mcp-config-writers.js.map +1 -1
  249. package/dist/client/agent-page/AgentJobsTab.d.ts.map +1 -1
  250. package/dist/client/agent-page/AgentJobsTab.js +86 -29
  251. package/dist/client/agent-page/AgentJobsTab.js.map +1 -1
  252. package/dist/client/agent-page/AutomationDetailsDialog.d.ts +21 -0
  253. package/dist/client/agent-page/AutomationDetailsDialog.d.ts.map +1 -0
  254. package/dist/client/agent-page/AutomationDetailsDialog.js +33 -0
  255. package/dist/client/agent-page/AutomationDetailsDialog.js.map +1 -0
  256. package/dist/client/agent-page/AutomationScheduleDialog.d.ts +15 -0
  257. package/dist/client/agent-page/AutomationScheduleDialog.d.ts.map +1 -0
  258. package/dist/client/agent-page/AutomationScheduleDialog.js +46 -0
  259. package/dist/client/agent-page/AutomationScheduleDialog.js.map +1 -0
  260. package/dist/client/agent-page/TimezoneSelect.d.ts +17 -0
  261. package/dist/client/agent-page/TimezoneSelect.d.ts.map +1 -0
  262. package/dist/client/agent-page/TimezoneSelect.js +107 -0
  263. package/dist/client/agent-page/TimezoneSelect.js.map +1 -0
  264. package/dist/client/agent-page/use-jobs.d.ts +20 -7
  265. package/dist/client/agent-page/use-jobs.d.ts.map +1 -1
  266. package/dist/client/agent-page/use-jobs.js +24 -4
  267. package/dist/client/agent-page/use-jobs.js.map +1 -1
  268. package/dist/client/org/OrgSwitcher.d.ts +8 -1
  269. package/dist/client/org/OrgSwitcher.d.ts.map +1 -1
  270. package/dist/client/org/OrgSwitcher.js +8 -2
  271. package/dist/client/org/OrgSwitcher.js.map +1 -1
  272. package/dist/client/settings/AccountSettingsCard.d.ts.map +1 -1
  273. package/dist/client/settings/AccountSettingsCard.js +10 -9
  274. package/dist/client/settings/AccountSettingsCard.js.map +1 -1
  275. package/dist/client/settings/SchedulingTimezoneField.d.ts +10 -0
  276. package/dist/client/settings/SchedulingTimezoneField.d.ts.map +1 -0
  277. package/dist/client/settings/SchedulingTimezoneField.js +30 -0
  278. package/dist/client/settings/SchedulingTimezoneField.js.map +1 -0
  279. package/dist/client/settings/SettingsRow.d.ts +36 -0
  280. package/dist/client/settings/SettingsRow.d.ts.map +1 -0
  281. package/dist/client/settings/SettingsRow.js +15 -0
  282. package/dist/client/settings/SettingsRow.js.map +1 -0
  283. package/dist/client/settings/agent-settings-search.d.ts.map +1 -1
  284. package/dist/client/settings/agent-settings-search.js +1 -1
  285. package/dist/client/settings/agent-settings-search.js.map +1 -1
  286. package/dist/client/settings/index.d.ts +1 -0
  287. package/dist/client/settings/index.d.ts.map +1 -1
  288. package/dist/client/settings/index.js +1 -0
  289. package/dist/client/settings/index.js.map +1 -1
  290. package/dist/collab/struct-routes.d.ts +1 -1
  291. package/dist/file-upload/actions/upload-image.d.ts +1 -1
  292. package/dist/integrations/identity.d.ts.map +1 -1
  293. package/dist/integrations/identity.js +2 -11
  294. package/dist/integrations/identity.js.map +1 -1
  295. package/dist/jobs/actions/list-automation-runs.d.ts +8 -0
  296. package/dist/jobs/actions/list-automation-runs.d.ts.map +1 -0
  297. package/dist/jobs/actions/list-automation-runs.js +29 -0
  298. package/dist/jobs/actions/list-automation-runs.js.map +1 -0
  299. package/dist/jobs/actions/list-recurring-jobs.d.ts +2 -0
  300. package/dist/jobs/actions/list-recurring-jobs.d.ts.map +1 -1
  301. package/dist/jobs/actions/list-recurring-jobs.js +20 -7
  302. package/dist/jobs/actions/list-recurring-jobs.js.map +1 -1
  303. package/dist/jobs/actions/manage-recurring-job.d.ts +2 -0
  304. package/dist/jobs/actions/manage-recurring-job.js +30 -9
  305. package/dist/jobs/actions/manage-recurring-job.js.map +1 -1
  306. package/dist/jobs/background-automation-runner.d.ts.map +1 -1
  307. package/dist/jobs/background-automation-runner.js +60 -0
  308. package/dist/jobs/background-automation-runner.js.map +1 -1
  309. package/dist/jobs/cron.d.ts +16 -2
  310. package/dist/jobs/cron.d.ts.map +1 -1
  311. package/dist/jobs/cron.js +43 -6
  312. package/dist/jobs/cron.js.map +1 -1
  313. package/dist/jobs/frontmatter.d.ts +12 -0
  314. package/dist/jobs/frontmatter.d.ts.map +1 -1
  315. package/dist/jobs/frontmatter.js +8 -0
  316. package/dist/jobs/frontmatter.js.map +1 -1
  317. package/dist/jobs/run-history.d.ts +56 -0
  318. package/dist/jobs/run-history.d.ts.map +1 -0
  319. package/dist/jobs/run-history.js +169 -0
  320. package/dist/jobs/run-history.js.map +1 -0
  321. package/dist/jobs/scheduler.d.ts.map +1 -1
  322. package/dist/jobs/scheduler.js +51 -18
  323. package/dist/jobs/scheduler.js.map +1 -1
  324. package/dist/jobs/tools.d.ts.map +1 -1
  325. package/dist/jobs/tools.js +37 -6
  326. package/dist/jobs/tools.js.map +1 -1
  327. package/dist/localization/actions/get-localization-preference.d.ts +1 -2
  328. package/dist/localization/actions/get-localization-preference.d.ts.map +1 -1
  329. package/dist/localization/actions/get-localization-preference.js.map +1 -1
  330. package/dist/localization/actions/set-localization-preference.d.ts +3 -3
  331. package/dist/localization/actions/set-localization-preference.d.ts.map +1 -1
  332. package/dist/localization/actions/set-localization-preference.js +33 -8
  333. package/dist/localization/actions/set-localization-preference.js.map +1 -1
  334. package/dist/localization/default-messages.d.ts +25 -0
  335. package/dist/localization/default-messages.d.ts.map +1 -1
  336. package/dist/localization/default-messages.js +25 -0
  337. package/dist/localization/default-messages.js.map +1 -1
  338. package/dist/localization/shared.d.ts +16 -1
  339. package/dist/localization/shared.d.ts.map +1 -1
  340. package/dist/localization/shared.js +22 -4
  341. package/dist/localization/shared.js.map +1 -1
  342. package/dist/localization/user-timezone.d.ts +11 -0
  343. package/dist/localization/user-timezone.d.ts.map +1 -0
  344. package/dist/localization/user-timezone.js +31 -0
  345. package/dist/localization/user-timezone.js.map +1 -0
  346. package/dist/notifications/routes.d.ts +2 -2
  347. package/dist/observability/routes.d.ts +1 -1
  348. package/dist/org/app-roles-handlers.d.ts.map +1 -1
  349. package/dist/org/app-roles-handlers.js +1 -8
  350. package/dist/org/app-roles-handlers.js.map +1 -1
  351. package/dist/org/index.d.ts +1 -0
  352. package/dist/org/index.d.ts.map +1 -1
  353. package/dist/org/index.js +1 -0
  354. package/dist/org/index.js.map +1 -1
  355. package/dist/org/membership.d.ts +6 -0
  356. package/dist/org/membership.d.ts.map +1 -0
  357. package/dist/org/membership.js +16 -0
  358. package/dist/org/membership.js.map +1 -0
  359. package/dist/provider-api/actions/custom-provider-registration.d.ts +10 -10
  360. package/dist/provider-api/actions/provider-api.d.ts +8 -8
  361. package/dist/provider-api/corpus-jobs.d.ts +2 -2
  362. package/dist/resources/handlers.d.ts +1 -1
  363. package/dist/review/actions/create-review-comment.d.ts +32 -1
  364. package/dist/review/actions/create-review-comment.d.ts.map +1 -1
  365. package/dist/review/actions/create-review-comment.js +3 -1
  366. package/dist/review/actions/create-review-comment.js.map +1 -1
  367. package/dist/review/actions/reply-review-comment.d.ts +32 -1
  368. package/dist/review/actions/reply-review-comment.d.ts.map +1 -1
  369. package/dist/review/actions/reply-review-comment.js +3 -1
  370. package/dist/review/actions/reply-review-comment.js.map +1 -1
  371. package/dist/review/index.d.ts +1 -0
  372. package/dist/review/index.d.ts.map +1 -1
  373. package/dist/review/index.js +1 -0
  374. package/dist/review/index.js.map +1 -1
  375. package/dist/review/notifications.d.ts +24 -0
  376. package/dist/review/notifications.d.ts.map +1 -0
  377. package/dist/review/notifications.js +123 -0
  378. package/dist/review/notifications.js.map +1 -0
  379. package/dist/review/types.d.ts +5 -0
  380. package/dist/review/types.d.ts.map +1 -1
  381. package/dist/review/types.js.map +1 -1
  382. package/dist/server/action-discovery.d.ts.map +1 -1
  383. package/dist/server/action-discovery.js +4 -0
  384. package/dist/server/action-discovery.js.map +1 -1
  385. package/dist/server/activity-notifications.d.ts +61 -0
  386. package/dist/server/activity-notifications.d.ts.map +1 -0
  387. package/dist/server/activity-notifications.js +103 -0
  388. package/dist/server/activity-notifications.js.map +1 -0
  389. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  390. package/dist/server/app-name.d.ts +18 -0
  391. package/dist/server/app-name.d.ts.map +1 -1
  392. package/dist/server/app-name.js +31 -0
  393. package/dist/server/app-name.js.map +1 -1
  394. package/dist/server/better-auth-instance.js +4 -4
  395. package/dist/server/better-auth-instance.js.map +1 -1
  396. package/dist/server/email-templates.d.ts +12 -0
  397. package/dist/server/email-templates.d.ts.map +1 -1
  398. package/dist/server/email-templates.js +57 -14
  399. package/dist/server/email-templates.js.map +1 -1
  400. package/dist/server/email.d.ts +12 -0
  401. package/dist/server/email.d.ts.map +1 -1
  402. package/dist/server/email.js +52 -5
  403. package/dist/server/email.js.map +1 -1
  404. package/dist/server/index.d.ts +1 -0
  405. package/dist/server/index.d.ts.map +1 -1
  406. package/dist/server/index.js +1 -0
  407. package/dist/server/index.js.map +1 -1
  408. package/dist/sharing/index.d.ts +1 -0
  409. package/dist/sharing/index.d.ts.map +1 -1
  410. package/dist/sharing/index.js +1 -0
  411. package/dist/sharing/index.js.map +1 -1
  412. package/dist/sharing/recipients.d.ts +31 -0
  413. package/dist/sharing/recipients.d.ts.map +1 -0
  414. package/dist/sharing/recipients.js +40 -0
  415. package/dist/sharing/recipients.js.map +1 -0
  416. package/dist/templates/chat/app/routes/settings.tsx +14 -22
  417. package/dist/templates/chat/vitest.config.ts +9 -0
  418. package/dist/triggers/actions/list-automations.d.ts +2 -0
  419. package/dist/triggers/actions/list-automations.d.ts.map +1 -1
  420. package/dist/triggers/actions/list-automations.js +19 -8
  421. package/dist/triggers/actions/list-automations.js.map +1 -1
  422. package/dist/triggers/actions/manage-automation.d.ts +6 -0
  423. package/dist/triggers/actions/manage-automation.js +12 -6
  424. package/dist/triggers/actions/manage-automation.js.map +1 -1
  425. package/dist/triggers/actions.d.ts.map +1 -1
  426. package/dist/triggers/actions.js +15 -4
  427. package/dist/triggers/actions.js.map +1 -1
  428. package/dist/triggers/dispatcher.d.ts.map +1 -1
  429. package/dist/triggers/dispatcher.js +19 -17
  430. package/dist/triggers/dispatcher.js.map +1 -1
  431. package/dist/triggers/routes.d.ts.map +1 -1
  432. package/dist/triggers/routes.js +6 -6
  433. package/dist/triggers/routes.js.map +1 -1
  434. package/dist/vitest-config.d.ts +10 -0
  435. package/dist/vitest-config.d.ts.map +1 -0
  436. package/dist/vitest-config.js +55 -0
  437. package/dist/vitest-config.js.map +1 -0
  438. package/docs/content/template-content-developers.mdx +1 -1
  439. package/package.json +5 -4
  440. package/src/automations/service.ts +41 -4
  441. package/src/cli/mcp-config-writers.ts +27 -21
  442. package/src/client/agent-page/AgentJobsTab.tsx +187 -85
  443. package/src/client/agent-page/AutomationDetailsDialog.tsx +208 -0
  444. package/src/client/agent-page/AutomationScheduleDialog.tsx +180 -0
  445. package/src/client/agent-page/TimezoneSelect.tsx +241 -0
  446. package/src/client/agent-page/use-jobs.ts +50 -10
  447. package/src/client/org/OrgSwitcher.tsx +26 -0
  448. package/src/client/settings/AccountSettingsCard.tsx +35 -31
  449. package/src/client/settings/SchedulingTimezoneField.tsx +71 -0
  450. package/src/client/settings/SettingsRow.tsx +113 -0
  451. package/src/client/settings/agent-settings-search.ts +2 -1
  452. package/src/client/settings/index.ts +6 -0
  453. package/src/integrations/identity.ts +2 -12
  454. package/src/jobs/actions/list-automation-runs.ts +33 -0
  455. package/src/jobs/actions/list-recurring-jobs.ts +27 -6
  456. package/src/jobs/actions/manage-recurring-job.ts +41 -9
  457. package/src/jobs/background-automation-runner.ts +88 -0
  458. package/src/jobs/cron.ts +52 -6
  459. package/src/jobs/frontmatter.ts +20 -0
  460. package/src/jobs/run-history.ts +242 -0
  461. package/src/jobs/scheduler.ts +73 -18
  462. package/src/jobs/tools.ts +54 -6
  463. package/src/localization/actions/get-localization-preference.ts +2 -2
  464. package/src/localization/actions/set-localization-preference.ts +49 -11
  465. package/src/localization/default-messages.ts +27 -0
  466. package/src/localization/shared.ts +39 -6
  467. package/src/localization/user-timezone.ts +36 -0
  468. package/src/org/app-roles-handlers.ts +1 -9
  469. package/src/org/index.ts +2 -0
  470. package/src/org/membership.ts +18 -0
  471. package/src/review/actions/create-review-comment.ts +4 -1
  472. package/src/review/actions/reply-review-comment.ts +4 -1
  473. package/src/review/index.ts +5 -0
  474. package/src/review/notifications.ts +163 -0
  475. package/src/review/types.ts +7 -0
  476. package/src/server/action-discovery.ts +4 -0
  477. package/src/server/activity-notifications.ts +174 -0
  478. package/src/server/app-name.ts +31 -0
  479. package/src/server/better-auth-instance.ts +4 -4
  480. package/src/server/email-templates.ts +70 -14
  481. package/src/server/email.ts +66 -3
  482. package/src/server/index.ts +10 -0
  483. package/src/sharing/index.ts +5 -0
  484. package/src/sharing/recipients.ts +67 -0
  485. package/src/templates/chat/app/routes/settings.tsx +14 -22
  486. package/src/templates/chat/vitest.config.ts +9 -0
  487. package/src/triggers/actions/list-automations.ts +27 -8
  488. package/src/triggers/actions/manage-automation.ts +17 -6
  489. package/src/triggers/actions.ts +16 -4
  490. package/src/triggers/dispatcher.ts +44 -30
  491. package/src/triggers/routes.ts +19 -6
  492. package/src/vitest-config.ts +68 -0
  493. package/corpus/templates/content/actions/delete-database-items.ts +0 -89
@@ -11,13 +11,22 @@
11
11
  * site — keeps the transactional look-and-feel consistent.
12
12
  */
13
13
 
14
- import { getAppName } from "./app-name.js";
14
+ import { getAppName, getAppSlug, getAppDescription } from "./app-name.js";
15
15
  import { renderEmail, emailStrong } from "./email-template.js";
16
16
 
17
+ /** Shared reply-to for the framework's transactional emails. */
18
+ export const AGENT_NATIVE_REPLY_TO = "agent-native@builder.io";
19
+
17
20
  export interface RenderedEmailMessage {
18
21
  subject: string;
19
22
  html: string;
20
23
  text: string;
24
+ /**
25
+ * Per-app sender branding, applied by `sendEmail` only on first-party
26
+ * agent-native.com deployments. Self-hosted deployments keep the sender and
27
+ * reply-to they configured via EMAIL_FROM.
28
+ */
29
+ appSender?: { name: string; slug: string; replyTo?: string };
21
30
  }
22
31
 
23
32
  /**
@@ -33,6 +42,17 @@ function resolveAppName(): string {
33
42
  return stripCrlf(getAppName() || "Agent Native");
34
43
  }
35
44
 
45
+ /**
46
+ * Recipient-facing brand for auth emails. Only a recognized first-party
47
+ * template is presented as "Agent-Native <App>"; a custom deployment keeps its
48
+ * own name so its users aren't told they signed up for an Agent Native app.
49
+ */
50
+ function resolveBrand(slug: string | undefined): string {
51
+ const appName = getAppName();
52
+ if (!appName) return "Agent Native";
53
+ return slug ? `Agent-Native ${stripCrlf(appName)}` : stripCrlf(appName);
54
+ }
55
+
36
56
  // ---------------------------------------------------------------------------
37
57
  // Organization invitation
38
58
  // ---------------------------------------------------------------------------
@@ -89,28 +109,58 @@ export interface RenderVerifySignupEmailArgs {
89
109
  verifyUrl: string;
90
110
  }
91
111
 
112
+ /**
113
+ * Customer-facing description overrides for the verification email body. The
114
+ * default descriptions come from the app-picker hints, which name competitors
115
+ * for internal positioning; these rewrites frame them as "replacement" for a
116
+ * customer-facing email without changing the picker copy.
117
+ */
118
+ const VERIFY_EMAIL_DESCRIPTIONS: Record<string, string> = {
119
+ calendar:
120
+ "Agent-native Google Calendar replacement — manage events, sync, and public booking",
121
+ content:
122
+ "Open-source Obsidian/Notion replacement for MDX — edit local docs with agent assistance",
123
+ slides:
124
+ "Agent-native Google Slides replacement — generate and edit React presentations",
125
+ analytics:
126
+ "Agent-native Amplitude/Mixpanel replacement — connect data sources, prompt for charts",
127
+ mail: "Agent-native Superhuman replacement — email client with keyboard shortcuts and AI triage",
128
+ };
129
+
92
130
  export function renderVerifySignupEmail(
93
131
  args: RenderVerifySignupEmailArgs,
94
132
  ): RenderedEmailMessage {
95
133
  const email = stripCrlf(args.email);
96
- const appName = resolveAppName();
134
+ const slug = getAppSlug();
135
+ const brand = resolveBrand(slug);
136
+ const description = slug
137
+ ? (VERIFY_EMAIL_DESCRIPTIONS[slug] ?? getAppDescription())
138
+ : undefined;
139
+
140
+ const paragraphs = [
141
+ `Thanks for signing up for ${emailStrong(brand)}. To finish creating your account, confirm that ${emailStrong(email)} is your email address.`,
142
+ ];
143
+ if (description) {
144
+ paragraphs.push(`${stripCrlf(description).replace(/\.\s*$/, "")}.`);
145
+ }
146
+ paragraphs.push(`This link expires in 1 hour.`);
97
147
 
98
148
  const { html, text } = renderEmail({
99
- brandName: appName,
100
- preheader: `Confirm ${email} to finish setting up your ${appName} account.`,
101
- heading: `Verify your email for ${appName}`,
102
- paragraphs: [
103
- `Thanks for signing up for ${emailStrong(appName)}. To finish creating your account, confirm that ${emailStrong(email)} is your email address.`,
104
- `This link expires in 1 hour.`,
105
- ],
149
+ brandName: brand,
150
+ preheader: `Confirm ${email} to finish setting up your ${brand} account.`,
151
+ heading: `Verify your email for ${brand}`,
152
+ paragraphs,
106
153
  cta: { label: "Verify email", url: args.verifyUrl },
107
154
  footer: `If you didn't sign up, you can safely ignore this email.`,
108
155
  });
109
156
 
110
157
  return {
111
- subject: `Verify your email for ${appName}`,
158
+ subject: `Verify your email for ${brand}`,
112
159
  html,
113
160
  text,
161
+ appSender: slug
162
+ ? { name: brand, slug, replyTo: AGENT_NATIVE_REPLY_TO }
163
+ : undefined,
114
164
  };
115
165
  }
116
166
 
@@ -129,12 +179,15 @@ export function renderResetPasswordEmail(
129
179
  args: RenderResetPasswordEmailArgs,
130
180
  ): RenderedEmailMessage {
131
181
  const email = stripCrlf(args.email);
132
- const appName = resolveAppName();
182
+ // Match the verification email branding so password resets are clearly tied
183
+ // to the specific app. No value pitch here — it's a security email.
184
+ const slug = getAppSlug();
185
+ const brand = resolveBrand(slug);
133
186
 
134
187
  const { html, text } = renderEmail({
135
- brandName: appName,
188
+ brandName: brand,
136
189
  preheader: `Reset the password for ${email}. This link expires in 1 hour.`,
137
- heading: `Reset your ${appName} password`,
190
+ heading: `Reset your ${brand} password`,
138
191
  paragraphs: [
139
192
  `Someone requested a password reset for ${emailStrong(email)}. Click the button below to choose a new password.`,
140
193
  `This link expires in 1 hour.`,
@@ -144,8 +197,11 @@ export function renderResetPasswordEmail(
144
197
  });
145
198
 
146
199
  return {
147
- subject: `Reset your ${appName} password`,
200
+ subject: `Reset your ${brand} password`,
148
201
  html,
149
202
  text,
203
+ appSender: slug
204
+ ? { name: brand, slug, replyTo: AGENT_NATIVE_REPLY_TO }
205
+ : undefined,
150
206
  };
151
207
  }
@@ -39,6 +39,14 @@ export interface SendEmailArgs {
39
39
  fromName?: string;
40
40
  cc?: string | string[];
41
41
  replyTo?: string;
42
+ /**
43
+ * Per-app branding for first-party agent-native.com deployments. Applied
44
+ * only when the configured EMAIL_FROM is already on agent-native.com, so a
45
+ * self-hosted deployment keeps its own verified sender and support mailbox
46
+ * instead of sending as an unverified address a provider would reject.
47
+ * An explicit `from` / `replyTo` always wins.
48
+ */
49
+ appSender?: { name: string; slug: string; replyTo?: string };
42
50
  inReplyTo?: string;
43
51
  references?: string;
44
52
  attachments?: EmailAttachment[];
@@ -150,6 +158,56 @@ function withDisplayName(from: string, name: string): string {
150
158
  return `"${safe}" <${address}>`;
151
159
  }
152
160
 
161
+ const AGENT_NATIVE_SENDER_DOMAIN = "agent-native.com";
162
+
163
+ /**
164
+ * Resolve the per-app sender address, but only for deployments whose
165
+ * configured sender is already on agent-native.com. Any other (or missing)
166
+ * EMAIL_FROM means we cannot prove the branded address is a verified sender,
167
+ * so the deployment's own configuration is left untouched.
168
+ */
169
+ let warnedAppSenderSuppressed = false;
170
+
171
+ /**
172
+ * Suppressing the branding is correct for a sender we cannot prove we own,
173
+ * but it must not be invisible, or an operator sees generic senders with
174
+ * nothing pointing at why.
175
+ *
176
+ * EMAIL_FROM resolves per user/org/workspace through the scoped secret store,
177
+ * so the resolved address is tenant data and must never reach shared logs, and
178
+ * anything keyed by it would grow without bound in a warm worker. The message
179
+ * therefore carries no tenant values, which also makes it identical for every
180
+ * suppressed config — so emitting it once per process loses nothing.
181
+ */
182
+ function warnAppSenderSuppressed(): void {
183
+ if (warnedAppSenderSuppressed) return;
184
+ warnedAppSenderSuppressed = true;
185
+ console.warn(
186
+ `[agent-native:email] Per-app sender branding is off because the ` +
187
+ `configured EMAIL_FROM is not on ${AGENT_NATIVE_SENDER_DOMAIN}. ` +
188
+ `Transactional email keeps the configured sender. Expected when self-hosting.`,
189
+ );
190
+ }
191
+
192
+ function resolveAppSender(
193
+ configuredFrom: string | undefined,
194
+ appSender: SendEmailArgs["appSender"],
195
+ ): { address: string; name: string; replyTo?: string } | undefined {
196
+ if (!appSender) return undefined;
197
+ const address = configuredFrom
198
+ ? parseSendGridFrom(configuredFrom).email.toLowerCase()
199
+ : undefined;
200
+ if (!address?.endsWith(`@${AGENT_NATIVE_SENDER_DOMAIN}`)) {
201
+ warnAppSenderSuppressed();
202
+ return undefined;
203
+ }
204
+ return {
205
+ address: `${appSender.slug}@${AGENT_NATIVE_SENDER_DOMAIN}`,
206
+ name: appSender.name,
207
+ replyTo: appSender.replyTo,
208
+ };
209
+ }
210
+
153
211
  async function sendEmailWithSignal(
154
212
  args: SendEmailArgs,
155
213
  signal?: AbortSignal,
@@ -157,7 +215,12 @@ async function sendEmailWithSignal(
157
215
  const config = await resolveEmailTransport();
158
216
  signal?.throwIfAborted();
159
217
  const provider = config.provider;
160
- const from = getFromAddress(config, args.from, args.fromName);
218
+ const branded = resolveAppSender(config.from, args.appSender);
219
+ const from =
220
+ branded && !args.from
221
+ ? withDisplayName(branded.address, args.fromName ?? branded.name)
222
+ : getFromAddress(config, args.from, args.fromName);
223
+ const replyTo = args.replyTo ?? branded?.replyTo;
161
224
  const attachments = resolveAttachments(args);
162
225
 
163
226
  if (provider === "resend") {
@@ -169,7 +232,7 @@ async function sendEmailWithSignal(
169
232
  text: args.text,
170
233
  };
171
234
  if (args.cc) payload.cc = Array.isArray(args.cc) ? args.cc : [args.cc];
172
- if (args.replyTo) payload.reply_to = args.replyTo;
235
+ if (replyTo) payload.reply_to = replyTo;
173
236
  if (attachments?.length) {
174
237
  payload.attachments = attachments.map((a) => ({
175
238
  filename: a.filename,
@@ -220,7 +283,7 @@ async function sendEmailWithSignal(
220
283
  { type: "text/html", value: args.html },
221
284
  ],
222
285
  };
223
- if (args.replyTo) sgPayload.reply_to = parseSendGridFrom(args.replyTo);
286
+ if (replyTo) sgPayload.reply_to = parseSendGridFrom(replyTo);
224
287
  const sgHeaders: Record<string, string> = {};
225
288
  if (args.inReplyTo) sgHeaders["In-Reply-To"] = args.inReplyTo;
226
289
  if (args.references) sgHeaders["References"] = args.references;
@@ -547,6 +547,16 @@ export {
547
547
  type EmailProvider,
548
548
  type SendEmailArgs,
549
549
  } from "./email.js";
550
+ export {
551
+ notifyActivity,
552
+ runActivityNotification,
553
+ resolveActivityRecipients,
554
+ type ActivityDeliveryFailure,
555
+ type ActivityNotificationResult,
556
+ type ActivityNotificationStatus,
557
+ type NotifyActivityInput,
558
+ type ResolveActivityRecipientsInput,
559
+ } from "./activity-notifications.js";
550
560
  export {
551
561
  renderEmail,
552
562
  emailStrong,
@@ -34,3 +34,8 @@ export {
34
34
  type AccessContext,
35
35
  type ResolvedAccess,
36
36
  } from "./access.js";
37
+
38
+ export {
39
+ filterRecipientsByResourceAccess,
40
+ type FilterRecipientsInput,
41
+ } from "./recipients.js";
@@ -0,0 +1,67 @@
1
+ import { isOrgMember } from "../org/membership.js";
2
+ import { resolveAccess } from "./access.js";
3
+ import { roleSatisfies, type ShareRole } from "./schema.js";
4
+
5
+ export interface FilterRecipientsInput {
6
+ resourceType: string;
7
+ resourceId: string;
8
+ emails: Iterable<string>;
9
+ /** The resource's org, needed to honor `org` visibility. */
10
+ orgId?: string | null;
11
+ minimumRole?: ShareRole;
12
+ /**
13
+ * Override for resources that are not registered through `registerShareableResource`
14
+ * (the review registry resolves its own). Must honor `userEmail`/`orgId`
15
+ * rather than the ambient request, or the filter checks the wrong principal.
16
+ */
17
+ resolveRole?: (ctx: {
18
+ userEmail: string;
19
+ orgId?: string;
20
+ }) => Promise<{ role: string } | null>;
21
+ }
22
+
23
+ /**
24
+ * Keep only the addresses that can still open the resource *right now*.
25
+ *
26
+ * Notification recipient lists are built from historical rows — thread
27
+ * authors, stored mentions, caller-supplied addresses — none of which are an
28
+ * access grant. Without this, mentioning an arbitrary address mails it the
29
+ * comment body, and a collaborator whose share was revoked keeps receiving the
30
+ * thread. Access is re-resolved per address against the live ACL.
31
+ */
32
+ export async function filterRecipientsByResourceAccess({
33
+ resourceType,
34
+ resourceId,
35
+ emails,
36
+ orgId,
37
+ minimumRole = "viewer",
38
+ resolveRole,
39
+ }: FilterRecipientsInput): Promise<string[]> {
40
+ const resolve =
41
+ resolveRole ??
42
+ ((ctx: { userEmail: string; orgId?: string }) =>
43
+ resolveAccess(resourceType, resourceId, ctx));
44
+ const unique = new Set<string>();
45
+ for (const raw of emails) {
46
+ const email = raw.trim().toLowerCase();
47
+ if (email) unique.add(email);
48
+ }
49
+ if (unique.size === 0) return [];
50
+
51
+ const decisions = await Promise.all(
52
+ [...unique].map(async (email) => {
53
+ // Resolve without an org first: passing the resource's org up front
54
+ // would hand org-visibility access to anyone who is not in it.
55
+ const direct = await resolve({ userEmail: email });
56
+ if (direct && roleSatisfies(direct.role as ShareRole, minimumRole)) {
57
+ return email;
58
+ }
59
+ if (!orgId || !(await isOrgMember(orgId, email))) return null;
60
+ const viaOrg = await resolve({ userEmail: email, orgId });
61
+ return viaOrg && roleSatisfies(viaOrg.role as ShareRole, minimumRole)
62
+ ? email
63
+ : null;
64
+ }),
65
+ );
66
+ return decisions.filter((email): email is string => email !== null);
67
+ }
@@ -5,7 +5,12 @@ import {
5
5
  listAutomationDefinitions,
6
6
  type AutomationScope,
7
7
  } from "../../automations/service.js";
8
- import { describeCron, isValidCron, nextOccurrence } from "../../jobs/cron.js";
8
+ import {
9
+ describeCron,
10
+ effectiveTimezone,
11
+ isValidCron,
12
+ nextOccurrence,
13
+ } from "../../jobs/cron.js";
9
14
 
10
15
  const scopeSchema = z.enum(["personal", "organization"]);
11
16
 
@@ -13,15 +18,23 @@ function nextRun(
13
18
  meta: Awaited<ReturnType<typeof listAutomationDefinitions>>[number]["meta"],
14
19
  ): string | null {
15
20
  if (!meta.enabled) return null;
16
- if (meta.nextRun) return meta.nextRun;
17
- if (
21
+ const scheduled = Boolean(
18
22
  meta.triggerType === "schedule" &&
19
23
  meta.schedule &&
20
- isValidCron(meta.schedule)
21
- ) {
22
- return nextOccurrence(meta.schedule).toISOString();
24
+ isValidCron(meta.schedule),
25
+ );
26
+ // A stored `nextRun` in the past means the dispatcher kept declining to run
27
+ // this automation, not that it is overdue. Report the real next occurrence
28
+ // and let `lastError` carry the reason it keeps being passed over.
29
+ if (meta.nextRun) {
30
+ const stored = new Date(meta.nextRun).getTime();
31
+ if (!Number.isFinite(stored) || stored > Date.now() || !scheduled) {
32
+ return meta.nextRun;
33
+ }
23
34
  }
24
- return null;
35
+ return scheduled
36
+ ? nextOccurrence(meta.schedule!, undefined, meta.timezone).toISOString()
37
+ : null;
25
38
  }
26
39
 
27
40
  export interface AutomationActionItem {
@@ -32,11 +45,13 @@ export interface AutomationActionItem {
32
45
  triggerType: "event" | "schedule";
33
46
  event: string | null;
34
47
  schedule: string | null;
48
+ timezone: string | null;
35
49
  scheduleDescription: string | null;
36
50
  condition: string | null;
37
51
  body: string;
38
52
  enabled: boolean;
39
53
  lastRun: string | null;
54
+ lastCheck: string | null;
40
55
  lastStatus: string | null;
41
56
  lastError: string | null;
42
57
  nextRun: string | null;
@@ -76,11 +91,15 @@ export default defineAction({
76
91
  triggerType: meta.triggerType,
77
92
  event: meta.event ?? null,
78
93
  schedule: meta.schedule || null,
79
- scheduleDescription: meta.schedule ? describeCron(meta.schedule) : null,
94
+ timezone: meta.schedule ? effectiveTimezone(meta.timezone) : null,
95
+ scheduleDescription: meta.schedule
96
+ ? describeCron(meta.schedule, effectiveTimezone(meta.timezone))
97
+ : null,
80
98
  condition: meta.condition ?? null,
81
99
  body,
82
100
  enabled: meta.enabled,
83
101
  lastRun: meta.lastRun ?? null,
102
+ lastCheck: meta.lastCheck ?? null,
84
103
  lastStatus: meta.lastStatus ?? null,
85
104
  lastError: meta.lastError ?? null,
86
105
  nextRun: nextRun(meta),
@@ -16,8 +16,10 @@ export default defineAction({
16
16
  name: z.string().min(1),
17
17
  scope: z.enum(["personal", "organization"]).default("personal"),
18
18
  enabled: z.boolean().optional(),
19
+ schedule: z.string().min(1).optional(),
20
+ timezone: z.string().min(1).optional(),
19
21
  }),
20
- run: async ({ operation, name, scope, enabled }, ctx) => {
22
+ run: async ({ operation, name, scope, enabled, schedule, timezone }, ctx) => {
21
23
  const userEmail = ctx?.userEmail;
22
24
  if (!userEmail) throw new Error("Not authenticated.");
23
25
  const actor = { userEmail, orgId: ctx?.orgId };
@@ -27,20 +29,29 @@ export default defineAction({
27
29
  await refreshEventSubscriptions();
28
30
  return { deleted: true, name };
29
31
  }
30
- if (enabled === undefined) {
31
- throw Object.assign(new Error("enabled is required for update."), {
32
- statusCode: 400,
33
- });
32
+ if (
33
+ enabled === undefined &&
34
+ schedule === undefined &&
35
+ timezone === undefined
36
+ ) {
37
+ throw Object.assign(
38
+ new Error("enabled, schedule, or timezone is required for update."),
39
+ { statusCode: 400 },
40
+ );
34
41
  }
35
42
  const definition = await updateAutomation(actor, {
36
43
  name,
37
44
  scope,
38
- enabled,
45
+ ...(enabled === undefined ? {} : { enabled }),
46
+ ...(schedule === undefined ? {} : { schedule }),
47
+ ...(timezone === undefined ? {} : { timezone }),
39
48
  });
40
49
  await refreshEventSubscriptions();
41
50
  return {
42
51
  name,
43
52
  enabled: definition.meta.enabled,
53
+ schedule: definition.meta.schedule || null,
54
+ timezone: definition.meta.timezone ?? null,
44
55
  nextRun: definition.meta.nextRun ?? null,
45
56
  };
46
57
  },
@@ -18,7 +18,7 @@ import {
18
18
  type AutomationScope,
19
19
  } from "../automations/service.js";
20
20
  import { listEvents } from "../event-bus/index.js";
21
- import { describeCron } from "../jobs/cron.js";
21
+ import { describeCron, effectiveTimezone } from "../jobs/cron.js";
22
22
  import {
23
23
  getIntegrationRequestContext,
24
24
  getRequestOrgId,
@@ -71,7 +71,10 @@ async function handleList(
71
71
  triggerType: meta.triggerType,
72
72
  event: meta.event ?? null,
73
73
  schedule: meta.schedule || null,
74
- scheduleDescription: meta.schedule ? describeCron(meta.schedule) : null,
74
+ timezone: meta.timezone ? effectiveTimezone(meta.timezone) : null,
75
+ scheduleDescription: meta.schedule
76
+ ? describeCron(meta.schedule, effectiveTimezone(meta.timezone))
77
+ : null,
75
78
  condition: meta.condition ?? null,
76
79
  mode: meta.mode,
77
80
  domain: meta.domain ?? null,
@@ -127,6 +130,7 @@ async function handleDefine(
127
130
  triggerType: automationTriggerType(args.trigger_type),
128
131
  body: typeof args.body === "string" ? args.body : "",
129
132
  schedule: typeof args.schedule === "string" ? args.schedule : undefined,
133
+ timezone: typeof args.timezone === "string" ? args.timezone : undefined,
130
134
  event: typeof args.event === "string" ? args.event : undefined,
131
135
  condition:
132
136
  typeof args.condition === "string" ? args.condition : undefined,
@@ -164,6 +168,7 @@ async function handleDefine(
164
168
  triggerType: definition.meta.triggerType,
165
169
  event: definition.meta.event ?? null,
166
170
  schedule: definition.meta.schedule || null,
171
+ timezone: definition.meta.timezone ?? null,
167
172
  nextRun: definition.meta.nextRun ?? null,
168
173
  createdBy: definition.meta.createdBy,
169
174
  runAs: definition.meta.runAs,
@@ -208,6 +213,7 @@ async function handleUpdate(
208
213
  : null,
209
214
  body: typeof args.body === "string" ? args.body : undefined,
210
215
  schedule: typeof args.schedule === "string" ? args.schedule : undefined,
216
+ timezone: typeof args.timezone === "string" ? args.timezone : undefined,
211
217
  model:
212
218
  args.model === undefined
213
219
  ? undefined
@@ -225,6 +231,7 @@ async function handleUpdate(
225
231
  triggerType: definition.meta.triggerType,
226
232
  enabled: definition.meta.enabled,
227
233
  schedule: definition.meta.schedule || null,
234
+ timezone: definition.meta.timezone ?? null,
228
235
  nextRun: definition.meta.nextRun ?? null,
229
236
  createdBy: definition.meta.createdBy,
230
237
  runAs: definition.meta.runAs,
@@ -305,8 +312,8 @@ export function createAutomationToolEntries(
305
312
 
306
313
  - **list-events**: List all registered event types that automations can subscribe to. Returns event names, descriptions, and payload schemas. Call this BEFORE defining an automation to discover available events.
307
314
  - **list**: List all automations (triggers). Shows trigger, status, model, MCP allowlist, and delivery metadata. Optional params: scope, domain, enabled_only.
308
- - **define**: Create a new automation. IMPORTANT: Always confirm with the user before calling — show them a summary of what will be created. Required params: name, trigger_type, body. Optional: scope, event, schedule, condition, mode, domain, delegated_policy_id, model, mcpTools.
309
- - **update**: Update an existing automation's settings without changing its creator (enabled, schedule, condition, body, policy, model, MCP allowlist). Required param: name. Use the same scope it was created in.
315
+ - **define**: Create a new automation. IMPORTANT: Always confirm with the user before calling — show them a summary of what will be created. Required params: name, trigger_type, body. Optional: scope, event, schedule, timezone, condition, mode, domain, delegated_policy_id, model, mcpTools.
316
+ - **update**: Update an existing automation's settings without changing its creator (enabled, schedule, timezone, condition, body, policy, model, MCP allowlist). Required param: name. Use the same scope it was created in.
310
317
  - **delete**: Delete an automation. Always confirm with the user first. Required param: name.
311
318
  - **fire-test**: Fire a test event to validate automations. Emits a test.event.fired event. Optional param: data (JSON string).`,
312
319
  parameters: {
@@ -339,6 +346,11 @@ export function createAutomationToolEntries(
339
346
  description:
340
347
  "For event triggers: the event name to subscribe to. Call with action=list-events first to see available events.",
341
348
  },
349
+ timezone: {
350
+ type: "string",
351
+ description:
352
+ "IANA timezone the cron clock time is read in, e.g. 'America/New_York'. Optional; defaults to the user's saved scheduling timezone, then the caller's browser zone. Always pass this when the user names a time of day.",
353
+ },
342
354
  schedule: {
343
355
  type: "string",
344
356
  description:
@@ -82,6 +82,31 @@ const _eventSubscriptions = new Map<string, string>();
82
82
  const _dispatchingTriggers = new Set<string>();
83
83
  let _deps: TriggerDispatcherDeps | null = null;
84
84
 
85
+ /**
86
+ * Record that a tick evaluated this trigger and declined to dispatch it.
87
+ * `lastRun` stays untouched — nothing ran — and an unchanged outcome is not
88
+ * re-persisted, so a permanently blocked trigger neither reports phantom runs
89
+ * nor rewrites its resource on every matching event.
90
+ */
91
+ async function recordTriggerSkip(
92
+ resource: Resource,
93
+ meta: TriggerFrontmatter,
94
+ body: string,
95
+ status: "skipped" | "error",
96
+ reason: string | undefined,
97
+ ): Promise<void> {
98
+ const unchanged = meta.lastStatus === status && meta.lastError === reason;
99
+ meta.lastCheck = new Date().toISOString();
100
+ meta.lastStatus = status;
101
+ meta.lastError = reason;
102
+ if (unchanged) return;
103
+ await resourcePut(
104
+ resource.owner,
105
+ resource.path,
106
+ buildTriggerContent(meta, body),
107
+ );
108
+ }
109
+
85
110
  /**
86
111
  * Initialize the trigger dispatcher. Call once at server startup.
87
112
  * Loads all event-triggered jobs and subscribes to their events.
@@ -173,25 +198,22 @@ async function handleEvent(
173
198
  meta,
174
199
  );
175
200
  } catch {
176
- meta.lastRun = new Date().toISOString();
177
- meta.lastStatus = "skipped";
178
- meta.lastError =
179
- "Could not verify the automation execution identity.";
180
- await resourcePut(
181
- resource.owner,
182
- resource.path,
183
- buildTriggerContent(meta, body),
201
+ await recordTriggerSkip(
202
+ resource,
203
+ meta,
204
+ body,
205
+ "skipped",
206
+ "Could not verify the automation execution identity.",
184
207
  );
185
208
  continue;
186
209
  }
187
210
  if (!resolved.ok) {
188
- meta.lastRun = new Date().toISOString();
189
- meta.lastStatus = "skipped";
190
- meta.lastError = resolved.reason;
191
- await resourcePut(
192
- resource.owner,
193
- resource.path,
194
- buildTriggerContent(meta, body),
211
+ await recordTriggerSkip(
212
+ resource,
213
+ meta,
214
+ body,
215
+ "skipped",
216
+ resolved.reason,
195
217
  );
196
218
  continue;
197
219
  }
@@ -206,13 +228,12 @@ async function handleEvent(
206
228
  const userApiKey = await getOwnerActiveApiKey(owner);
207
229
  const apiKey = userApiKey || _deps.apiKey;
208
230
  if (!apiKey) {
209
- meta.lastRun = new Date().toISOString();
210
- meta.lastStatus = "error";
211
- meta.lastError = "No API key is available for this automation";
212
- await resourcePut(
213
- resource.owner,
214
- resource.path,
215
- buildTriggerContent(meta, body),
231
+ await recordTriggerSkip(
232
+ resource,
233
+ meta,
234
+ body,
235
+ "error",
236
+ "No API key is available for this automation",
216
237
  );
217
238
  console.warn(`[triggers] ${meta.lastError}: "${resource.path}"`);
218
239
  continue;
@@ -221,14 +242,7 @@ async function handleEvent(
221
242
  // Evaluate condition
222
243
  const matches = await evaluateCondition(meta.condition, payload, apiKey);
223
244
  if (!matches) {
224
- meta.lastRun = new Date().toISOString();
225
- meta.lastStatus = "skipped";
226
- meta.lastError = undefined;
227
- await resourcePut(
228
- resource.owner,
229
- resource.path,
230
- buildTriggerContent(meta, body),
231
- );
245
+ await recordTriggerSkip(resource, meta, body, "skipped", undefined);
232
246
  continue;
233
247
  }
234
248