@agent-native/core 0.134.1 → 0.135.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (463) hide show
  1. package/corpus/README.md +3 -3
  2. package/corpus/core/CHANGELOG.md +113 -0
  3. package/corpus/core/package.json +3 -2
  4. package/corpus/core/src/automations/service.ts +41 -4
  5. package/corpus/core/src/cli/mcp-config-writers.ts +27 -21
  6. package/corpus/core/src/client/agent-page/AgentJobsTab.tsx +187 -85
  7. package/corpus/core/src/client/agent-page/AutomationDetailsDialog.tsx +208 -0
  8. package/corpus/core/src/client/agent-page/AutomationScheduleDialog.tsx +180 -0
  9. package/corpus/core/src/client/agent-page/TimezoneSelect.tsx +241 -0
  10. package/corpus/core/src/client/agent-page/use-jobs.ts +50 -10
  11. package/corpus/core/src/client/org/OrgSwitcher.tsx +26 -0
  12. package/corpus/core/src/client/settings/AccountSettingsCard.tsx +35 -31
  13. package/corpus/core/src/client/settings/SchedulingTimezoneField.tsx +71 -0
  14. package/corpus/core/src/client/settings/SettingsRow.tsx +113 -0
  15. package/corpus/core/src/client/settings/agent-settings-search.ts +2 -1
  16. package/corpus/core/src/client/settings/index.ts +6 -0
  17. package/corpus/core/src/integrations/identity.ts +2 -12
  18. package/corpus/core/src/jobs/actions/list-automation-runs.ts +33 -0
  19. package/corpus/core/src/jobs/actions/list-recurring-jobs.ts +27 -6
  20. package/corpus/core/src/jobs/actions/manage-recurring-job.ts +41 -9
  21. package/corpus/core/src/jobs/background-automation-runner.ts +88 -0
  22. package/corpus/core/src/jobs/cron.ts +52 -6
  23. package/corpus/core/src/jobs/frontmatter.ts +20 -0
  24. package/corpus/core/src/jobs/run-history.ts +242 -0
  25. package/corpus/core/src/jobs/scheduler.ts +73 -18
  26. package/corpus/core/src/jobs/tools.ts +54 -6
  27. package/corpus/core/src/localization/actions/get-localization-preference.ts +2 -2
  28. package/corpus/core/src/localization/actions/set-localization-preference.ts +49 -11
  29. package/corpus/core/src/localization/default-messages.ts +27 -0
  30. package/corpus/core/src/localization/shared.ts +39 -6
  31. package/corpus/core/src/localization/user-timezone.ts +36 -0
  32. package/corpus/core/src/org/app-roles-handlers.ts +1 -9
  33. package/corpus/core/src/org/index.ts +2 -0
  34. package/corpus/core/src/org/membership.ts +18 -0
  35. package/corpus/core/src/review/actions/create-review-comment.ts +4 -1
  36. package/corpus/core/src/review/actions/reply-review-comment.ts +4 -1
  37. package/corpus/core/src/review/index.ts +5 -0
  38. package/corpus/core/src/review/notifications.ts +163 -0
  39. package/corpus/core/src/review/types.ts +7 -0
  40. package/corpus/core/src/server/action-discovery.ts +4 -0
  41. package/corpus/core/src/server/activity-notifications.ts +174 -0
  42. package/corpus/core/src/server/app-name.ts +31 -0
  43. package/corpus/core/src/server/better-auth-instance.ts +4 -4
  44. package/corpus/core/src/server/email-templates.ts +70 -14
  45. package/corpus/core/src/server/email.ts +66 -3
  46. package/corpus/core/src/server/index.ts +10 -0
  47. package/corpus/core/src/sharing/access.ts +44 -2
  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-31-editor-timeline-now-shows-video-frames-behind-the-waveform-g.md +6 -0
  109. package/corpus/templates/clips/changelog/2026-07-31-email-notifications-now-go-out-when-someone-comments-on-or-r.md +6 -0
  110. package/corpus/templates/clips/changelog/2026-07-31-you-can-now-set-a-default-visibility-for-new-recordings-in-s.md +6 -0
  111. package/corpus/templates/clips/changelog/2026-08-01-email-notifications-for-comments-and-reactions-now-actually-.md +6 -0
  112. package/corpus/templates/clips/changelog/2026-08-03-the-desktop-app-now-opens-directly-beneath-its-tray-icon-on-.md +6 -0
  113. package/corpus/templates/clips/chrome-extension/package.json +1 -0
  114. package/corpus/templates/clips/chrome-extension/vitest.config.ts +9 -0
  115. package/corpus/templates/clips/desktop/package.json +1 -0
  116. package/corpus/templates/clips/desktop/src/app.tsx +17 -1
  117. package/corpus/templates/clips/desktop/src/lib/recorder.ts +331 -36
  118. package/corpus/templates/clips/desktop/src/lib/upload-recovery.ts +129 -0
  119. package/corpus/templates/clips/desktop/src-tauri/src/native_screen.rs +815 -15
  120. package/corpus/templates/clips/desktop/src-tauri/src/tray.rs +61 -11
  121. package/corpus/templates/clips/desktop/vitest.config.ts +9 -0
  122. package/corpus/templates/clips/server/db/schema.ts +17 -0
  123. package/corpus/templates/clips/server/jobs/media-verification.ts +4 -0
  124. package/corpus/templates/clips/server/lib/activity-notifications.ts +185 -0
  125. package/corpus/templates/clips/server/lib/recording-media-cleanup.ts +2 -0
  126. package/corpus/templates/clips/server/lib/recording-upload-state.ts +46 -9
  127. package/corpus/templates/clips/server/lib/recordings.ts +24 -0
  128. package/corpus/templates/clips/server/lib/resumable-session.ts +10 -4
  129. package/corpus/templates/clips/server/lib/transactional-email-templates.ts +109 -0
  130. package/corpus/templates/clips/server/lib/upload-interruption.ts +8 -0
  131. package/corpus/templates/clips/server/lib/upload-lease.ts +50 -4
  132. package/corpus/templates/clips/server/lib/video-filmstrip-sprite.ts +207 -0
  133. package/corpus/templates/clips/server/lib/video-remux.ts +13 -4
  134. package/corpus/templates/clips/server/plugins/db.ts +16 -0
  135. package/corpus/templates/clips/server/routes/api/_agent-native-background/post-finalize-worker.post.ts +4 -0
  136. package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/abort.post.ts +123 -36
  137. package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/chunk.post.ts +159 -29
  138. package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/interrupt.post.ts +197 -0
  139. package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/reset-chunks.post.ts +193 -39
  140. package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/resume.get.ts +191 -12
  141. package/corpus/templates/clips/shared/clips-ai-prefs.ts +6 -0
  142. package/corpus/templates/clips/shared/feature-flags.ts +12 -0
  143. package/corpus/templates/clips/shared/recording-core.ts +4 -0
  144. package/corpus/templates/clips/vitest.config.ts +23 -19
  145. package/corpus/templates/content/actions/_database-utils.ts +113 -4
  146. package/corpus/templates/content/actions/add-comment.ts +15 -1
  147. package/corpus/templates/content/actions/get-document.ts +84 -3
  148. package/corpus/templates/content/actions/list-documents.ts +1 -0
  149. package/corpus/templates/content/app/components/editor/BuilderBodySyncingNotice.tsx +9 -2
  150. package/corpus/templates/content/app/components/editor/DocumentEditor.tsx +34 -15
  151. package/corpus/templates/content/app/components/editor/DocumentToolbar.tsx +55 -13
  152. package/corpus/templates/content/app/components/editor/body-hydration.ts +12 -6
  153. package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +2 -3
  154. package/corpus/templates/content/app/components/sidebar/DocumentSidebar.tsx +5 -12
  155. package/corpus/templates/content/app/components/ui/switch.tsx +1 -0
  156. package/corpus/templates/content/app/hooks/use-content-database.ts +16 -27
  157. package/corpus/templates/content/app/hooks/use-content-prefs.ts +55 -0
  158. package/corpus/templates/content/app/hooks/use-create-page.ts +3 -6
  159. package/corpus/templates/content/app/hooks/use-document-properties.ts +9 -16
  160. package/corpus/templates/content/app/hooks/use-document-versions.ts +3 -3
  161. package/corpus/templates/content/app/hooks/use-documents.ts +85 -29
  162. package/corpus/templates/content/app/hooks/use-notion.ts +15 -13
  163. package/corpus/templates/content/app/i18n-data.ts +72 -1
  164. package/corpus/templates/content/app/lib/document-query.ts +36 -0
  165. package/corpus/templates/content/app/routes/_app.settings.tsx +47 -18
  166. package/corpus/templates/content/changelog/2026-07-30-breadcrumb-menus-now-hide-internal-system-pages.md +6 -0
  167. package/corpus/templates/content/changelog/2026-08-01-get-an-email-when-someone-comments-on-replies-in-or-mentions.md +6 -0
  168. package/corpus/templates/content/changelog/2026-08-02-pages-opened-from-a-database-now-keep-that-database-s-fields.md +6 -0
  169. package/corpus/templates/content/server/lib/comment-notifications.ts +146 -0
  170. package/corpus/templates/content/server/lib/document-context.ts +11 -6
  171. package/corpus/templates/content/server/routes/_agent-native/content/user-prefs.get.ts +17 -0
  172. package/corpus/templates/content/server/routes/_agent-native/content/user-prefs.put.ts +36 -0
  173. package/corpus/templates/content/shared/api.ts +8 -0
  174. package/corpus/templates/content/shared/content-user-prefs.ts +11 -0
  175. package/corpus/templates/content/vitest.config.ts +20 -16
  176. package/corpus/templates/crm/app/routes/settings.tsx +14 -22
  177. package/corpus/templates/crm/vitest.config.ts +8 -4
  178. package/corpus/templates/design/app/routes/settings.tsx +14 -22
  179. package/corpus/templates/design/changelog/2026-08-01-review-comments-replies-and-mentions-now-send-an-email-to-th.md +6 -0
  180. package/corpus/templates/design/vitest.config.ts +18 -12
  181. package/corpus/templates/dispatch/app/routes/settings.tsx +26 -40
  182. package/corpus/templates/dispatch/vitest.config.ts +11 -7
  183. package/corpus/templates/factory/app/routes/settings.tsx +14 -22
  184. package/corpus/templates/factory/vitest.config.ts +9 -0
  185. package/corpus/templates/forms/app/routes/_app.settings.tsx +14 -22
  186. package/corpus/templates/forms/vitest.config.ts +27 -23
  187. package/corpus/templates/macros/app/routes/settings.tsx +14 -22
  188. package/corpus/templates/macros/vitest.config.ts +16 -12
  189. package/corpus/templates/mail/app/pages/SettingsPage.tsx +14 -16
  190. package/corpus/templates/mail/vitest.config.ts +16 -12
  191. package/corpus/templates/plan/app/routes/settings.tsx +30 -44
  192. package/corpus/templates/plan/vitest.config.ts +24 -20
  193. package/corpus/templates/slides/actions/add-slide-comment.ts +12 -1
  194. package/corpus/templates/slides/app/components/ui/switch.tsx +1 -0
  195. package/corpus/templates/slides/app/hooks/use-slides-prefs.ts +55 -0
  196. package/corpus/templates/slides/app/i18n/en-US.ts +4 -0
  197. package/corpus/templates/slides/app/routes/settings.tsx +45 -22
  198. package/corpus/templates/slides/changelog/2026-08-01-get-an-email-when-someone-comments-on-or-replies-in-your-dec.md +6 -0
  199. package/corpus/templates/slides/server/lib/comment-notifications.ts +182 -0
  200. package/corpus/templates/slides/server/routes/_agent-native/slides/user-prefs.get.ts +16 -0
  201. package/corpus/templates/slides/server/routes/_agent-native/slides/user-prefs.put.ts +36 -0
  202. package/corpus/templates/slides/shared/slides-user-prefs.ts +11 -0
  203. package/corpus/templates/slides/vitest.config.ts +17 -13
  204. package/corpus/templates/tasks/app/components/layout/Sidebar.tsx +2 -0
  205. package/corpus/templates/tasks/app/routes/settings.tsx +14 -22
  206. package/corpus/templates/tasks/vitest.config.ts +17 -13
  207. package/corpus/toolkit/CHANGELOG.md +9 -0
  208. package/corpus/toolkit/package.json +1 -1
  209. package/corpus/toolkit/vitest.config.ts +3 -0
  210. package/dist/automations/service.d.ts +2 -0
  211. package/dist/automations/service.d.ts.map +1 -1
  212. package/dist/automations/service.js +29 -4
  213. package/dist/automations/service.js.map +1 -1
  214. package/dist/cli/mcp-config-writers.d.ts.map +1 -1
  215. package/dist/cli/mcp-config-writers.js +24 -23
  216. package/dist/cli/mcp-config-writers.js.map +1 -1
  217. package/dist/client/agent-page/AgentJobsTab.d.ts.map +1 -1
  218. package/dist/client/agent-page/AgentJobsTab.js +86 -29
  219. package/dist/client/agent-page/AgentJobsTab.js.map +1 -1
  220. package/dist/client/agent-page/AutomationDetailsDialog.d.ts +21 -0
  221. package/dist/client/agent-page/AutomationDetailsDialog.d.ts.map +1 -0
  222. package/dist/client/agent-page/AutomationDetailsDialog.js +33 -0
  223. package/dist/client/agent-page/AutomationDetailsDialog.js.map +1 -0
  224. package/dist/client/agent-page/AutomationScheduleDialog.d.ts +15 -0
  225. package/dist/client/agent-page/AutomationScheduleDialog.d.ts.map +1 -0
  226. package/dist/client/agent-page/AutomationScheduleDialog.js +46 -0
  227. package/dist/client/agent-page/AutomationScheduleDialog.js.map +1 -0
  228. package/dist/client/agent-page/TimezoneSelect.d.ts +17 -0
  229. package/dist/client/agent-page/TimezoneSelect.d.ts.map +1 -0
  230. package/dist/client/agent-page/TimezoneSelect.js +107 -0
  231. package/dist/client/agent-page/TimezoneSelect.js.map +1 -0
  232. package/dist/client/agent-page/use-jobs.d.ts +20 -7
  233. package/dist/client/agent-page/use-jobs.d.ts.map +1 -1
  234. package/dist/client/agent-page/use-jobs.js +24 -4
  235. package/dist/client/agent-page/use-jobs.js.map +1 -1
  236. package/dist/client/org/OrgSwitcher.d.ts +8 -1
  237. package/dist/client/org/OrgSwitcher.d.ts.map +1 -1
  238. package/dist/client/org/OrgSwitcher.js +8 -2
  239. package/dist/client/org/OrgSwitcher.js.map +1 -1
  240. package/dist/client/settings/AccountSettingsCard.d.ts.map +1 -1
  241. package/dist/client/settings/AccountSettingsCard.js +10 -9
  242. package/dist/client/settings/AccountSettingsCard.js.map +1 -1
  243. package/dist/client/settings/SchedulingTimezoneField.d.ts +10 -0
  244. package/dist/client/settings/SchedulingTimezoneField.d.ts.map +1 -0
  245. package/dist/client/settings/SchedulingTimezoneField.js +30 -0
  246. package/dist/client/settings/SchedulingTimezoneField.js.map +1 -0
  247. package/dist/client/settings/SettingsRow.d.ts +36 -0
  248. package/dist/client/settings/SettingsRow.d.ts.map +1 -0
  249. package/dist/client/settings/SettingsRow.js +15 -0
  250. package/dist/client/settings/SettingsRow.js.map +1 -0
  251. package/dist/client/settings/agent-settings-search.d.ts.map +1 -1
  252. package/dist/client/settings/agent-settings-search.js +1 -1
  253. package/dist/client/settings/agent-settings-search.js.map +1 -1
  254. package/dist/client/settings/index.d.ts +1 -0
  255. package/dist/client/settings/index.d.ts.map +1 -1
  256. package/dist/client/settings/index.js +1 -0
  257. package/dist/client/settings/index.js.map +1 -1
  258. package/dist/file-upload/actions/upload-image.d.ts +1 -1
  259. package/dist/integrations/identity.d.ts.map +1 -1
  260. package/dist/integrations/identity.js +2 -11
  261. package/dist/integrations/identity.js.map +1 -1
  262. package/dist/jobs/actions/list-automation-runs.d.ts +8 -0
  263. package/dist/jobs/actions/list-automation-runs.d.ts.map +1 -0
  264. package/dist/jobs/actions/list-automation-runs.js +29 -0
  265. package/dist/jobs/actions/list-automation-runs.js.map +1 -0
  266. package/dist/jobs/actions/list-recurring-jobs.d.ts +2 -0
  267. package/dist/jobs/actions/list-recurring-jobs.d.ts.map +1 -1
  268. package/dist/jobs/actions/list-recurring-jobs.js +20 -7
  269. package/dist/jobs/actions/list-recurring-jobs.js.map +1 -1
  270. package/dist/jobs/actions/manage-recurring-job.d.ts +2 -0
  271. package/dist/jobs/actions/manage-recurring-job.js +30 -9
  272. package/dist/jobs/actions/manage-recurring-job.js.map +1 -1
  273. package/dist/jobs/background-automation-runner.d.ts.map +1 -1
  274. package/dist/jobs/background-automation-runner.js +60 -0
  275. package/dist/jobs/background-automation-runner.js.map +1 -1
  276. package/dist/jobs/cron.d.ts +16 -2
  277. package/dist/jobs/cron.d.ts.map +1 -1
  278. package/dist/jobs/cron.js +43 -6
  279. package/dist/jobs/cron.js.map +1 -1
  280. package/dist/jobs/frontmatter.d.ts +12 -0
  281. package/dist/jobs/frontmatter.d.ts.map +1 -1
  282. package/dist/jobs/frontmatter.js +8 -0
  283. package/dist/jobs/frontmatter.js.map +1 -1
  284. package/dist/jobs/run-history.d.ts +56 -0
  285. package/dist/jobs/run-history.d.ts.map +1 -0
  286. package/dist/jobs/run-history.js +169 -0
  287. package/dist/jobs/run-history.js.map +1 -0
  288. package/dist/jobs/scheduler.d.ts.map +1 -1
  289. package/dist/jobs/scheduler.js +51 -18
  290. package/dist/jobs/scheduler.js.map +1 -1
  291. package/dist/jobs/tools.d.ts.map +1 -1
  292. package/dist/jobs/tools.js +37 -6
  293. package/dist/jobs/tools.js.map +1 -1
  294. package/dist/localization/actions/get-localization-preference.d.ts +1 -2
  295. package/dist/localization/actions/get-localization-preference.d.ts.map +1 -1
  296. package/dist/localization/actions/get-localization-preference.js.map +1 -1
  297. package/dist/localization/actions/set-localization-preference.d.ts +3 -3
  298. package/dist/localization/actions/set-localization-preference.d.ts.map +1 -1
  299. package/dist/localization/actions/set-localization-preference.js +33 -8
  300. package/dist/localization/actions/set-localization-preference.js.map +1 -1
  301. package/dist/localization/default-messages.d.ts +25 -0
  302. package/dist/localization/default-messages.d.ts.map +1 -1
  303. package/dist/localization/default-messages.js +25 -0
  304. package/dist/localization/default-messages.js.map +1 -1
  305. package/dist/localization/shared.d.ts +16 -1
  306. package/dist/localization/shared.d.ts.map +1 -1
  307. package/dist/localization/shared.js +22 -4
  308. package/dist/localization/shared.js.map +1 -1
  309. package/dist/localization/user-timezone.d.ts +11 -0
  310. package/dist/localization/user-timezone.d.ts.map +1 -0
  311. package/dist/localization/user-timezone.js +31 -0
  312. package/dist/localization/user-timezone.js.map +1 -0
  313. package/dist/notifications/routes.d.ts +4 -4
  314. package/dist/observability/routes.d.ts +3 -3
  315. package/dist/org/app-roles-handlers.d.ts.map +1 -1
  316. package/dist/org/app-roles-handlers.js +1 -8
  317. package/dist/org/app-roles-handlers.js.map +1 -1
  318. package/dist/org/index.d.ts +1 -0
  319. package/dist/org/index.d.ts.map +1 -1
  320. package/dist/org/index.js +1 -0
  321. package/dist/org/index.js.map +1 -1
  322. package/dist/org/membership.d.ts +6 -0
  323. package/dist/org/membership.d.ts.map +1 -0
  324. package/dist/org/membership.js +16 -0
  325. package/dist/org/membership.js.map +1 -0
  326. package/dist/progress/routes.d.ts +1 -1
  327. package/dist/provider-api/actions/custom-provider-registration.d.ts +13 -13
  328. package/dist/provider-api/actions/provider-api.d.ts +8 -8
  329. package/dist/provider-api/corpus-jobs.d.ts +2 -2
  330. package/dist/review/actions/create-review-comment.d.ts +32 -1
  331. package/dist/review/actions/create-review-comment.d.ts.map +1 -1
  332. package/dist/review/actions/create-review-comment.js +3 -1
  333. package/dist/review/actions/create-review-comment.js.map +1 -1
  334. package/dist/review/actions/reply-review-comment.d.ts +32 -1
  335. package/dist/review/actions/reply-review-comment.d.ts.map +1 -1
  336. package/dist/review/actions/reply-review-comment.js +3 -1
  337. package/dist/review/actions/reply-review-comment.js.map +1 -1
  338. package/dist/review/index.d.ts +1 -0
  339. package/dist/review/index.d.ts.map +1 -1
  340. package/dist/review/index.js +1 -0
  341. package/dist/review/index.js.map +1 -1
  342. package/dist/review/notifications.d.ts +24 -0
  343. package/dist/review/notifications.d.ts.map +1 -0
  344. package/dist/review/notifications.js +123 -0
  345. package/dist/review/notifications.js.map +1 -0
  346. package/dist/review/types.d.ts +5 -0
  347. package/dist/review/types.d.ts.map +1 -1
  348. package/dist/review/types.js.map +1 -1
  349. package/dist/server/action-discovery.d.ts.map +1 -1
  350. package/dist/server/action-discovery.js +4 -0
  351. package/dist/server/action-discovery.js.map +1 -1
  352. package/dist/server/activity-notifications.d.ts +61 -0
  353. package/dist/server/activity-notifications.d.ts.map +1 -0
  354. package/dist/server/activity-notifications.js +103 -0
  355. package/dist/server/activity-notifications.js.map +1 -0
  356. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  357. package/dist/server/app-name.d.ts +18 -0
  358. package/dist/server/app-name.d.ts.map +1 -1
  359. package/dist/server/app-name.js +31 -0
  360. package/dist/server/app-name.js.map +1 -1
  361. package/dist/server/better-auth-instance.js +4 -4
  362. package/dist/server/better-auth-instance.js.map +1 -1
  363. package/dist/server/email-templates.d.ts +12 -0
  364. package/dist/server/email-templates.d.ts.map +1 -1
  365. package/dist/server/email-templates.js +57 -14
  366. package/dist/server/email-templates.js.map +1 -1
  367. package/dist/server/email.d.ts +12 -0
  368. package/dist/server/email.d.ts.map +1 -1
  369. package/dist/server/email.js +52 -5
  370. package/dist/server/email.js.map +1 -1
  371. package/dist/server/index.d.ts +1 -0
  372. package/dist/server/index.d.ts.map +1 -1
  373. package/dist/server/index.js +1 -0
  374. package/dist/server/index.js.map +1 -1
  375. package/dist/server/transcribe-voice.d.ts +1 -1
  376. package/dist/sharing/access.d.ts.map +1 -1
  377. package/dist/sharing/access.js +32 -2
  378. package/dist/sharing/access.js.map +1 -1
  379. package/dist/sharing/index.d.ts +1 -0
  380. package/dist/sharing/index.d.ts.map +1 -1
  381. package/dist/sharing/index.js +1 -0
  382. package/dist/sharing/index.js.map +1 -1
  383. package/dist/sharing/recipients.d.ts +31 -0
  384. package/dist/sharing/recipients.d.ts.map +1 -0
  385. package/dist/sharing/recipients.js +40 -0
  386. package/dist/sharing/recipients.js.map +1 -0
  387. package/dist/templates/chat/app/routes/settings.tsx +14 -22
  388. package/dist/templates/chat/vitest.config.ts +9 -0
  389. package/dist/triggers/actions/list-automations.d.ts +2 -0
  390. package/dist/triggers/actions/list-automations.d.ts.map +1 -1
  391. package/dist/triggers/actions/list-automations.js +19 -8
  392. package/dist/triggers/actions/list-automations.js.map +1 -1
  393. package/dist/triggers/actions/manage-automation.d.ts +6 -0
  394. package/dist/triggers/actions/manage-automation.js +12 -6
  395. package/dist/triggers/actions/manage-automation.js.map +1 -1
  396. package/dist/triggers/actions.d.ts.map +1 -1
  397. package/dist/triggers/actions.js +15 -4
  398. package/dist/triggers/actions.js.map +1 -1
  399. package/dist/triggers/dispatcher.d.ts.map +1 -1
  400. package/dist/triggers/dispatcher.js +19 -17
  401. package/dist/triggers/dispatcher.js.map +1 -1
  402. package/dist/triggers/routes.d.ts.map +1 -1
  403. package/dist/triggers/routes.js +6 -6
  404. package/dist/triggers/routes.js.map +1 -1
  405. package/dist/vitest-config.d.ts +10 -0
  406. package/dist/vitest-config.d.ts.map +1 -0
  407. package/dist/vitest-config.js +55 -0
  408. package/dist/vitest-config.js.map +1 -0
  409. package/package.json +5 -4
  410. package/src/automations/service.ts +41 -4
  411. package/src/cli/mcp-config-writers.ts +27 -21
  412. package/src/client/agent-page/AgentJobsTab.tsx +187 -85
  413. package/src/client/agent-page/AutomationDetailsDialog.tsx +208 -0
  414. package/src/client/agent-page/AutomationScheduleDialog.tsx +180 -0
  415. package/src/client/agent-page/TimezoneSelect.tsx +241 -0
  416. package/src/client/agent-page/use-jobs.ts +50 -10
  417. package/src/client/org/OrgSwitcher.tsx +26 -0
  418. package/src/client/settings/AccountSettingsCard.tsx +35 -31
  419. package/src/client/settings/SchedulingTimezoneField.tsx +71 -0
  420. package/src/client/settings/SettingsRow.tsx +113 -0
  421. package/src/client/settings/agent-settings-search.ts +2 -1
  422. package/src/client/settings/index.ts +6 -0
  423. package/src/integrations/identity.ts +2 -12
  424. package/src/jobs/actions/list-automation-runs.ts +33 -0
  425. package/src/jobs/actions/list-recurring-jobs.ts +27 -6
  426. package/src/jobs/actions/manage-recurring-job.ts +41 -9
  427. package/src/jobs/background-automation-runner.ts +88 -0
  428. package/src/jobs/cron.ts +52 -6
  429. package/src/jobs/frontmatter.ts +20 -0
  430. package/src/jobs/run-history.ts +242 -0
  431. package/src/jobs/scheduler.ts +73 -18
  432. package/src/jobs/tools.ts +54 -6
  433. package/src/localization/actions/get-localization-preference.ts +2 -2
  434. package/src/localization/actions/set-localization-preference.ts +49 -11
  435. package/src/localization/default-messages.ts +27 -0
  436. package/src/localization/shared.ts +39 -6
  437. package/src/localization/user-timezone.ts +36 -0
  438. package/src/org/app-roles-handlers.ts +1 -9
  439. package/src/org/index.ts +2 -0
  440. package/src/org/membership.ts +18 -0
  441. package/src/review/actions/create-review-comment.ts +4 -1
  442. package/src/review/actions/reply-review-comment.ts +4 -1
  443. package/src/review/index.ts +5 -0
  444. package/src/review/notifications.ts +163 -0
  445. package/src/review/types.ts +7 -0
  446. package/src/server/action-discovery.ts +4 -0
  447. package/src/server/activity-notifications.ts +174 -0
  448. package/src/server/app-name.ts +31 -0
  449. package/src/server/better-auth-instance.ts +4 -4
  450. package/src/server/email-templates.ts +70 -14
  451. package/src/server/email.ts +66 -3
  452. package/src/server/index.ts +10 -0
  453. package/src/sharing/access.ts +44 -2
  454. package/src/sharing/index.ts +5 -0
  455. package/src/sharing/recipients.ts +67 -0
  456. package/src/templates/chat/app/routes/settings.tsx +14 -22
  457. package/src/templates/chat/vitest.config.ts +9 -0
  458. package/src/triggers/actions/list-automations.ts +27 -8
  459. package/src/triggers/actions/manage-automation.ts +17 -6
  460. package/src/triggers/actions.ts +16 -4
  461. package/src/triggers/dispatcher.ts +44 -30
  462. package/src/triggers/routes.ts +19 -6
  463. package/src/vitest-config.ts +68 -0
@@ -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,
@@ -15,6 +15,7 @@
15
15
 
16
16
  import { and, eq, or, sql, type SQL } from "drizzle-orm";
17
17
 
18
+ import { orgMembers } from "../org/schema.js";
18
19
  import {
19
20
  getRequestAuthCapability,
20
21
  getRequestUserEmail,
@@ -87,6 +88,35 @@ function emailColumnMatches(column: any, email: string): SQL {
87
88
  return sql`lower(${column}) = ${email}`;
88
89
  }
89
90
 
91
+ /**
92
+ * Real `org_members` membership, independent of the caller's currently
93
+ * active org (`ctx.orgId`). `org`-visibility access must key off actual
94
+ * membership — a user's active-org selection is a UI convenience, not a
95
+ * statement of which orgs they belong to.
96
+ *
97
+ * Queries through the resource's own `reg.getDb()` — the same connection
98
+ * every other lookup in this file uses — not the ambient `getDbExec()`,
99
+ * since `org_members` lives in that same app database.
100
+ */
101
+ async function isOrgMember(
102
+ reg: ShareableResourceRegistration,
103
+ memberOrgId: string,
104
+ email: string,
105
+ ): Promise<boolean> {
106
+ const db = reg.getDb() as any;
107
+ const rows = await db
108
+ .select({ id: orgMembers.id })
109
+ .from(orgMembers)
110
+ .where(
111
+ and(
112
+ eq(orgMembers.orgId, memberOrgId),
113
+ emailColumnMatches(orgMembers.email, email),
114
+ ),
115
+ )
116
+ .limit(1);
117
+ return rows.length > 0;
118
+ }
119
+
90
120
  /**
91
121
  * Build a Drizzle `WHERE` clause that admits rows the current user can see.
92
122
  * Pass the ownable resource table and its shares table; optional min role
@@ -470,7 +500,7 @@ async function resolveAccessImpl(
470
500
  const resource = await loadResourceForAccess(reg, resourceId, options);
471
501
  if (!resource) return null;
472
502
 
473
- const { userEmail, orgId } = ctx;
503
+ const { userEmail } = ctx;
474
504
  const normalizedUserEmail = normalizeEmailForAccess(userEmail);
475
505
 
476
506
  if (
@@ -491,7 +521,19 @@ async function resolveAccessImpl(
491
521
  // `visibility === "public"` on an `allowPublic: false` resource is treated
492
522
  // as private: only owner + explicit shares grant access. Falls through to
493
523
  // the explicit-share lookup below.
494
- if (resource.visibility === "org" && orgId && resource.orgId === orgId) {
524
+ //
525
+ // Membership in the resource's own org, not equality with the caller's
526
+ // currently active org: a caller can be a genuine member of the
527
+ // resource's org while a *different* org is their active selection, and
528
+ // `org` visibility should still admit them. Still requires some active
529
+ // org to be set at all (`orgId`), matching the pre-existing behavior for
530
+ // a caller with no active org.
531
+ if (
532
+ resource.visibility === "org" &&
533
+ resource.orgId &&
534
+ normalizedUserEmail &&
535
+ (await isOrgMember(reg, resource.orgId, normalizedUserEmail))
536
+ ) {
495
537
  const role = await highestShareRole(reg, resourceId, ctx, resource);
496
538
  return { role: role ?? "viewer", resource };
497
539
  }
@@ -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: