@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
@@ -1 +1 @@
1
- {"version":3,"file":"access.js","sourceRoot":"","sources":["../../src/sharing/access.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Access-control helpers for shareable resources.\n *\n * The access model combines:\n * 1. Direct ownership — `owner_email = currentUser`.\n * 2. Visibility — `'private' | 'org' | 'public'`. `org` grants read to anyone\n * in the same org; `public` grants read to any authenticated user.\n * 3. Share rows — per-user or per-org grants in the `{type}_shares` table\n * with a role (`viewer | editor | admin`).\n *\n * Use `applyAccessFilter()` on list/read queries to filter rows the current\n * user can see. Use `assertAccess()` at the top of write actions to reject\n * callers who lack the required role.\n */\n\nimport { and, eq, or, sql, type SQL } from \"drizzle-orm\";\n\nimport {\n getRequestAuthCapability,\n getRequestUserEmail,\n getRequestOrgId,\n} from \"../server/request-context.js\";\nimport {\n listShareableResources,\n requireShareableResource,\n type ShareableResourceRegistration,\n} from \"./registry.js\";\nimport { ROLE_RANK, type ShareRole, type Visibility } from \"./schema.js\";\n\n/**\n * Find a registration by its drizzle table identity. Used to look up\n * per-resource policy flags (e.g. `allowPublic`) inside `accessFilter`,\n * which receives only the table — not the resource-type name.\n *\n * Identity is stable within a single bundle (Vite dedupes module instances);\n * the SSR/server side is the only caller, so per-bundle identity is fine.\n */\nfunction findRegistrationByTable(\n resourceTable: any,\n): ShareableResourceRegistration | undefined {\n for (const reg of listShareableResources()) {\n if (reg.resourceTable === resourceTable) return reg;\n }\n return undefined;\n}\n\nexport class ForbiddenError extends Error {\n statusCode = 403;\n constructor(message = \"Forbidden\") {\n super(message);\n this.name = \"ForbiddenError\";\n }\n}\n\nexport interface AccessContext {\n userEmail?: string;\n orgId?: string;\n authCapability?: string;\n}\n\n/** Current request's access context. Pulls from request-context ALS. */\nexport function currentAccess(): AccessContext {\n return {\n userEmail: getRequestUserEmail(),\n orgId: getRequestOrgId(),\n authCapability: getRequestAuthCapability(),\n };\n}\n\nexport function resolveRegisteredAccessContext(\n reg: ShareableResourceRegistration | undefined,\n ctx: AccessContext,\n): AccessContext {\n if (!reg?.resolveAccessContext) return ctx;\n const resolved = reg.resolveAccessContext(ctx);\n return ctx.authCapability\n ? { ...resolved, authCapability: ctx.authCapability }\n : resolved;\n}\n\nfunction normalizeEmailForAccess(email: string | undefined): string | null {\n const normalized = email?.trim().toLowerCase();\n return normalized || null;\n}\n\nfunction emailColumnMatches(column: any, email: string): SQL {\n return sql`lower(${column}) = ${email}`;\n}\n\n/**\n * Build a Drizzle `WHERE` clause that admits rows the current user can see.\n * Pass the ownable resource table and its shares table; optional min role\n * (defaults to 'viewer') gates which share rows count.\n *\n * `visibility = 'public'` is intentionally NOT admitted by default. Public\n * means \"anyone with the link can view\" (still honoured by `resolveAccess`\n * for read-by-id), not \"appears in every signed-in user's list/sidebar.\"\n * Pass `{ includePublic: true }` for the rare list endpoint that wants\n * cross-user public discovery (a public template gallery, for example).\n *\n * Example:\n *\n * const rows = await db\n * .select()\n * .from(schema.documents)\n * .where(accessFilter(schema.documents, schema.documentShares));\n */\nexport function accessFilter(\n resourceTable: any,\n sharesTable: any,\n rawCtx: AccessContext = currentAccess(),\n minRole: ShareRole = \"viewer\",\n options: { includePublic?: boolean } = {},\n): SQL {\n // Defense in depth — resources registered with `allowPublic: false` must\n // never participate in cross-user \"public\" discovery, even if a caller\n // accidentally passes `includePublic: true` or if a stale public row sits\n // in the DB.\n const reg = findRegistrationByTable(resourceTable);\n const ctx = resolveRegisteredAccessContext(reg, rawCtx);\n const { userEmail, orgId } = ctx;\n const normalizedUserEmail = normalizeEmailForAccess(userEmail);\n const publicAllowed = reg?.allowPublic !== false;\n const includePublic = (options.includePublic ?? false) && publicAllowed;\n const clauses: SQL[] = [];\n\n if (normalizedUserEmail) {\n clauses.push(\n and(\n emailColumnMatches(resourceTable.ownerEmail, normalizedUserEmail),\n ownerScopeFilter(reg, resourceTable, ctx),\n )!,\n );\n }\n if (minRole === \"viewer\") {\n if (includePublic) {\n clauses.push(eq(resourceTable.visibility, \"public\"));\n }\n if (orgId) {\n clauses.push(\n and(\n eq(resourceTable.visibility, \"org\"),\n eq(resourceTable.orgId, orgId),\n )!,\n );\n }\n }\n if (normalizedUserEmail) {\n const shareScope = restrictedShareScopeSql(reg, resourceTable, ctx);\n clauses.push(\n sql`exists (select 1 from ${sharesTable}\n where ${sharesTable.resourceId} = ${resourceTable.id}\n and ${sharesTable.principalType} = 'user'\n and lower(${sharesTable.principalId}) = ${normalizedUserEmail}\n and ${shareScope}\n and ${minRoleSql(minRole)})`,\n );\n }\n if (orgId) {\n const shareScope = restrictedShareScopeSql(reg, resourceTable, ctx);\n clauses.push(\n sql`exists (select 1 from ${sharesTable}\n where ${sharesTable.resourceId} = ${resourceTable.id}\n and ${sharesTable.principalType} = 'org'\n and ${sharesTable.principalId} = ${orgId}\n and ${shareScope}\n and ${minRoleSql(minRole)})`,\n );\n }\n\n return or(...clauses) ?? sql`1=0`;\n}\n\nfunction ownerScopeFilter(\n reg: ShareableResourceRegistration | undefined,\n resourceTable: any,\n ctx: AccessContext,\n): SQL {\n if (reg?.ownerAccessIgnoresOrg === true) return sql`1=1`;\n if (ctx.orgId) {\n // Rows created before org-scoping, or in solo mode, have no org_id. Keep\n // them manageable by their owner after the owner joins or switches into an\n // organization, while still keeping rows from other orgs out of scope.\n return or(\n eq(resourceTable.orgId, ctx.orgId),\n sql`${resourceTable.orgId} IS NULL`,\n )!;\n }\n return sql`${resourceTable.orgId} IS NULL`;\n}\n\nfunction ownerMatchesActiveScope(\n reg: ShareableResourceRegistration | undefined,\n resource: any,\n ctx: AccessContext,\n): boolean {\n if (reg?.ownerAccessIgnoresOrg === true) return true;\n const resourceOrgId = resource?.orgId ?? null;\n if (!resourceOrgId) return true;\n return ctx.orgId === resourceOrgId;\n}\n\nfunction minRoleSql(minRole: ShareRole): SQL {\n if (minRole === \"viewer\") {\n // any role satisfies viewer\n return sql`1=1`;\n }\n if (minRole === \"editor\") {\n return sql`role in ('editor','admin')`;\n }\n return sql`role = 'admin'`;\n}\n\nfunction restrictedShareScopeSql(\n reg: ShareableResourceRegistration | undefined,\n resourceTable: any,\n ctx: AccessContext,\n): SQL {\n // Restricted resources (extensions) must stay inside their resource org even\n // if stale cross-org share rows already exist from older code or bad data.\n if (reg?.requireOrgMemberForUserShares !== true) return sql`1=1`;\n if (!ctx.orgId) return sql`1=0`;\n return eq(resourceTable.orgId, ctx.orgId);\n}\n\nfunction explicitSharesAllowedForResource(\n reg: ShareableResourceRegistration,\n resource: any,\n ctx: AccessContext,\n): boolean {\n if (reg.requireOrgMemberForUserShares !== true) return true;\n const resourceOrgId = resource?.orgId ?? null;\n return !!resourceOrgId && !!ctx.orgId && resourceOrgId === ctx.orgId;\n}\n\nexport interface ResolvedAccess {\n /** Effective role: 'owner' for the resource owner, or the share role. */\n role: \"owner\" | ShareRole;\n /** The resource row (already loaded). */\n resource: any;\n}\n\n/**\n * Minimal resource shape returned when a caller opts into a projected access\n * load via `{ skipResourceBody: true }`. Contains exactly the columns the\n * access-decision logic itself reads — identity, ownership, org scope, and\n * visibility — never a resource type's heavy body columns (`data`,\n * `content`, and similar blobs).\n */\nexport interface AccessProjectedResource {\n id: string;\n ownerEmail: string;\n orgId: string | null;\n visibility: Visibility;\n}\n\nexport interface ResolvedAccessProjected {\n /** Effective role: 'owner' for the resource owner, or the share role. */\n role: \"owner\" | ShareRole;\n /** Only the access-decision columns — not the full resource row. */\n resource: AccessProjectedResource;\n}\n\nexport interface ResolveAccessOptions {\n /**\n * When true, load only the columns the access decision itself needs\n * (`id`, `ownerEmail`, `orgId`, `visibility`) instead of the full resource\n * row. Use this when the caller only needs the access decision — not the\n * resource body — and wants to skip fetching heavy type-specific columns\n * (e.g. `data`/`content` blobs) on every `assertAccess`/`resolveAccess`\n * call.\n *\n * Silently ignored (falls back to a full row load) for any resource type\n * registered with a `publicAccessRole` *function* resolver, since that\n * callback can read arbitrary resource fields the projection would have\n * omitted — see `hasDynamicPublicAccessRoleResolver` below.\n *\n * Default: `false` — the full row is loaded, matching historical behavior.\n * Callers that need resource body fields (most call sites today — for\n * many resource types `resolveAccess().resource` doubles as the action's\n * primary data read) must NOT pass this.\n */\n skipResourceBody?: boolean;\n}\n\nasync function publicAccessRoleForResource(\n reg: ShareableResourceRegistration,\n resource: any,\n ctx: AccessContext,\n): Promise<ShareRole> {\n const roleResolver = reg.publicAccessRole;\n if (!roleResolver) return \"viewer\";\n return typeof roleResolver === \"function\"\n ? await roleResolver(resource, ctx)\n : roleResolver;\n}\n\nfunction higherShareRole(a: ShareRole, b: ShareRole | null): ShareRole {\n if (!b) return a;\n return ROLE_RANK[b] > ROLE_RANK[a] ? b : a;\n}\n\nfunction columnName(column: unknown): string | null {\n const candidate = column as\n | {\n name?: unknown;\n config?: { name?: unknown };\n _: { name?: unknown };\n }\n | undefined;\n const name = candidate?.name ?? candidate?.config?.name ?? candidate?._?.name;\n return typeof name === \"string\" && name ? name : null;\n}\n\nfunction missingColumnName(err: unknown): string | null {\n const error = err as { code?: string; message?: string } | undefined;\n const message = error?.message ?? \"\";\n if (\n error?.code !== \"42703\" &&\n !/no such column|does not exist/i.test(message)\n ) {\n return null;\n }\n const quoted = message.match(/column\\s+\"([^\"]+)\"\\s+does not exist/i)?.[1];\n if (quoted) return quoted;\n const sqlite = message.match(/no such column:\\s+[\"`]?([\\w.]+)[\"`]?/i)?.[1];\n if (sqlite) return sqlite.split(\".\").pop() ?? sqlite;\n return null;\n}\n\nfunction selectExistingColumns(\n columns: Record<string, unknown>,\n omittedColumnNames: Set<string>,\n): Record<string, unknown> {\n const selection: Record<string, unknown> = {};\n for (const [key, column] of Object.entries(columns)) {\n const name = columnName(column);\n if (!name || omittedColumnNames.has(name)) continue;\n selection[key] = column;\n }\n return selection;\n}\n\n/**\n * The fixed column set the access-decision logic in `resolveAccess` itself\n * reads: identity (`id`), ownership (`ownerEmail`, `orgId`), and the coarse\n * `visibility` flag. This is intentionally NOT caller-configurable — an\n * arbitrary caller-supplied column list could omit a column the access\n * checks below depend on and silently break authorization. Every ownable\n * resource table has these columns (`ownableColumns()` + a primary key\n * named `id`), so this projection is safe for any registration that\n * doesn't also read the row through a dynamic resolver (see below).\n */\nfunction projectedAccessColumns(resourceTable: any): Record<string, unknown> {\n return {\n id: resourceTable.id,\n ownerEmail: resourceTable.ownerEmail,\n orgId: resourceTable.orgId,\n visibility: resourceTable.visibility,\n };\n}\n\n/**\n * True when this registration's `publicAccessRole` is a callback rather than\n * a fixed role string. Callbacks receive the loaded `resource` and may read\n * arbitrary fields on it (see e.g. `templates/design`'s\n * `publicDesignAccessRole`, which inspects design-specific data). The\n * projected access load must never be used for these registrations — always\n * fall back to a full row load so the resolver sees the complete resource.\n */\nfunction hasDynamicPublicAccessRoleResolver(\n reg: ShareableResourceRegistration,\n): boolean {\n return typeof reg.publicAccessRole === \"function\";\n}\n\nasync function loadResourceForAccess(\n reg: ShareableResourceRegistration,\n resourceId: string,\n options: ResolveAccessOptions = {},\n): Promise<any | null> {\n const db = reg.getDb() as any;\n const useProjection =\n options.skipResourceBody === true &&\n !hasDynamicPublicAccessRoleResolver(reg);\n const projectedColumns = useProjection\n ? projectedAccessColumns(reg.resourceTable)\n : null;\n const omittedColumnNames = new Set<string>();\n\n for (let attempt = 0; attempt < 12; attempt++) {\n try {\n const query =\n !projectedColumns && omittedColumnNames.size === 0\n ? db.select()\n : db.select(\n selectExistingColumns(\n projectedColumns ?? reg.resourceTable,\n omittedColumnNames,\n ),\n );\n const [resource] = await query\n .from(reg.resourceTable)\n .where(eq(reg.resourceTable.id, resourceId));\n return resource ?? null;\n } catch (err) {\n const missing = missingColumnName(err);\n if (!missing || omittedColumnNames.has(missing)) throw err;\n omittedColumnNames.add(missing);\n console.warn(\n `[sharing] ${reg.type} access lookup omitted missing column ${missing}`,\n );\n }\n }\n\n throw new Error(\n `Could not load ${reg.type} ${resourceId}: too many missing resource columns`,\n );\n}\n\n/**\n * Return the effective role the current user has on a specific resource, or\n * null if they have no access. Loads the resource and relevant share rows.\n *\n * By default the full resource row is loaded (unchanged historical\n * behavior — for most resource types `.resource` here doubles as the\n * action's primary data read). Pass `{ skipResourceBody: true }` when the\n * caller only needs the access decision to load just the access-decision\n * columns instead — see `ResolveAccessOptions`.\n */\nexport async function resolveAccess(\n resourceType: string,\n resourceId: string,\n rawCtx?: AccessContext,\n options?: { skipResourceBody?: false },\n): Promise<ResolvedAccess | null>;\nexport async function resolveAccess(\n resourceType: string,\n resourceId: string,\n rawCtx: AccessContext | undefined,\n options: { skipResourceBody: true },\n): Promise<ResolvedAccessProjected | null>;\nexport async function resolveAccess(\n resourceType: string,\n resourceId: string,\n rawCtx: AccessContext = currentAccess(),\n options: ResolveAccessOptions = {},\n): Promise<ResolvedAccess | ResolvedAccessProjected | null> {\n return resolveAccessImpl(resourceType, resourceId, rawCtx, options);\n}\n\n/**\n * Un-overloaded implementation shared by the `resolveAccess` overloads and\n * called directly by `assertAccess` below. Kept separate from the exported\n * `resolveAccess` so internal callers passing a widened\n * `ResolveAccessOptions` (rather than a `{ skipResourceBody: true }` /\n * `{ skipResourceBody?: false }` literal) don't have to satisfy TypeScript's\n * overload resolution, which only matches against the declared overload\n * signatures, not the implementation signature.\n */\nasync function resolveAccessImpl(\n resourceType: string,\n resourceId: string,\n rawCtx: AccessContext = currentAccess(),\n options: ResolveAccessOptions = {},\n): Promise<ResolvedAccess | ResolvedAccessProjected | null> {\n const reg = requireShareableResource(resourceType);\n const ctx = resolveRegisteredAccessContext(reg, rawCtx);\n\n const resource = await loadResourceForAccess(reg, resourceId, options);\n if (!resource) return null;\n\n const { userEmail, orgId } = ctx;\n const normalizedUserEmail = normalizeEmailForAccess(userEmail);\n\n if (\n normalizedUserEmail &&\n normalizeEmailForAccess(resource.ownerEmail) === normalizedUserEmail &&\n ownerMatchesActiveScope(reg, resource, ctx)\n ) {\n return { role: \"owner\", resource };\n }\n if (resource.visibility === \"public\" && reg.allowPublic !== false) {\n // No share row needed; default viewer unless the resource registration\n // deliberately grants a stronger public-by-link role or explicit shares\n // upgrade the current principal.\n const publicRole = await publicAccessRoleForResource(reg, resource, ctx);\n const role = await highestShareRole(reg, resourceId, ctx, resource);\n return { role: higherShareRole(publicRole, role), resource };\n }\n // `visibility === \"public\"` on an `allowPublic: false` resource is treated\n // as private: only owner + explicit shares grant access. Falls through to\n // the explicit-share lookup below.\n if (resource.visibility === \"org\" && orgId && resource.orgId === orgId) {\n const role = await highestShareRole(reg, resourceId, ctx, resource);\n return { role: role ?? \"viewer\", resource };\n }\n const role = await highestShareRole(reg, resourceId, ctx, resource);\n if (role) return { role, resource };\n return null;\n}\n\nasync function highestShareRole(\n reg: ShareableResourceRegistration,\n resourceId: string,\n ctx: AccessContext,\n resource: any,\n): Promise<ShareRole | null> {\n const { userEmail, orgId } = ctx;\n const normalizedUserEmail = normalizeEmailForAccess(userEmail);\n if (!normalizedUserEmail && !orgId) return null;\n if (!explicitSharesAllowedForResource(reg, resource, ctx)) return null;\n const db = reg.getDb() as any;\n\n const principalClauses: ReturnType<typeof and>[] = [];\n if (normalizedUserEmail) {\n principalClauses.push(\n and(\n eq(reg.sharesTable.principalType, \"user\"),\n emailColumnMatches(reg.sharesTable.principalId, normalizedUserEmail),\n ),\n );\n }\n if (orgId) {\n principalClauses.push(\n and(\n eq(reg.sharesTable.principalType, \"org\"),\n eq(reg.sharesTable.principalId, orgId),\n ),\n );\n }\n\n const rows = await db\n .select({ role: reg.sharesTable.role })\n .from(reg.sharesTable)\n .where(\n and(eq(reg.sharesTable.resourceId, resourceId), or(...principalClauses)),\n )\n .limit(10);\n\n let best: ShareRole | null = null;\n for (const r of rows as Array<{ role: ShareRole }>) {\n if (!best || ROLE_RANK[r.role] > ROLE_RANK[best]) best = r.role;\n }\n return best;\n}\n\n/**\n * Throw ForbiddenError if the current user can't act on this resource with at\n * least the given role. Used at the top of update/delete actions.\n *\n * By default the full resource row is loaded (unchanged historical\n * behavior). Pass `{ skipResourceBody: true }` as the fifth argument when\n * the caller only needs the access decision — see `ResolveAccessOptions`.\n */\nexport async function assertAccess(\n resourceType: string,\n resourceId: string,\n minRole?: ShareRole | \"owner\",\n ctx?: AccessContext,\n options?: { skipResourceBody?: false },\n): Promise<ResolvedAccess>;\nexport async function assertAccess(\n resourceType: string,\n resourceId: string,\n minRole: ShareRole | \"owner\" | undefined,\n ctx: AccessContext | undefined,\n options: { skipResourceBody: true },\n): Promise<ResolvedAccessProjected>;\nexport async function assertAccess(\n resourceType: string,\n resourceId: string,\n minRole: ShareRole | \"owner\" = \"viewer\",\n ctx: AccessContext = currentAccess(),\n options: ResolveAccessOptions = {},\n): Promise<ResolvedAccess | ResolvedAccessProjected> {\n const access = await resolveAccessImpl(\n resourceType,\n resourceId,\n ctx,\n options,\n );\n if (!access) {\n throw new ForbiddenError(`No access to ${resourceType} ${resourceId}`);\n }\n if (ROLE_RANK[access.role] < ROLE_RANK[minRole]) {\n throw new ForbiddenError(\n `Requires ${minRole} role on ${resourceType} ${resourceId} (have ${access.role})`,\n );\n }\n return access;\n}\n"]}
1
+ {"version":3,"file":"access.js","sourceRoot":"","sources":["../../src/sharing/access.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Access-control helpers for shareable resources.\n *\n * The access model combines:\n * 1. Direct ownership — `owner_email = currentUser`.\n * 2. Visibility — `'private' | 'org' | 'public'`. `org` grants read to anyone\n * in the same org; `public` grants read to any authenticated user.\n * 3. Share rows — per-user or per-org grants in the `{type}_shares` table\n * with a role (`viewer | editor | admin`).\n *\n * Use `applyAccessFilter()` on list/read queries to filter rows the current\n * user can see. Use `assertAccess()` at the top of write actions to reject\n * callers who lack the required role.\n */\n\nimport { and, eq, or, sql, type SQL } from \"drizzle-orm\";\n\nimport { orgMembers } from \"../org/schema.js\";\nimport {\n getRequestAuthCapability,\n getRequestUserEmail,\n getRequestOrgId,\n} from \"../server/request-context.js\";\nimport {\n listShareableResources,\n requireShareableResource,\n type ShareableResourceRegistration,\n} from \"./registry.js\";\nimport { ROLE_RANK, type ShareRole, type Visibility } from \"./schema.js\";\n\n/**\n * Find a registration by its drizzle table identity. Used to look up\n * per-resource policy flags (e.g. `allowPublic`) inside `accessFilter`,\n * which receives only the table — not the resource-type name.\n *\n * Identity is stable within a single bundle (Vite dedupes module instances);\n * the SSR/server side is the only caller, so per-bundle identity is fine.\n */\nfunction findRegistrationByTable(\n resourceTable: any,\n): ShareableResourceRegistration | undefined {\n for (const reg of listShareableResources()) {\n if (reg.resourceTable === resourceTable) return reg;\n }\n return undefined;\n}\n\nexport class ForbiddenError extends Error {\n statusCode = 403;\n constructor(message = \"Forbidden\") {\n super(message);\n this.name = \"ForbiddenError\";\n }\n}\n\nexport interface AccessContext {\n userEmail?: string;\n orgId?: string;\n authCapability?: string;\n}\n\n/** Current request's access context. Pulls from request-context ALS. */\nexport function currentAccess(): AccessContext {\n return {\n userEmail: getRequestUserEmail(),\n orgId: getRequestOrgId(),\n authCapability: getRequestAuthCapability(),\n };\n}\n\nexport function resolveRegisteredAccessContext(\n reg: ShareableResourceRegistration | undefined,\n ctx: AccessContext,\n): AccessContext {\n if (!reg?.resolveAccessContext) return ctx;\n const resolved = reg.resolveAccessContext(ctx);\n return ctx.authCapability\n ? { ...resolved, authCapability: ctx.authCapability }\n : resolved;\n}\n\nfunction normalizeEmailForAccess(email: string | undefined): string | null {\n const normalized = email?.trim().toLowerCase();\n return normalized || null;\n}\n\nfunction emailColumnMatches(column: any, email: string): SQL {\n return sql`lower(${column}) = ${email}`;\n}\n\n/**\n * Real `org_members` membership, independent of the caller's currently\n * active org (`ctx.orgId`). `org`-visibility access must key off actual\n * membership — a user's active-org selection is a UI convenience, not a\n * statement of which orgs they belong to.\n *\n * Queries through the resource's own `reg.getDb()` — the same connection\n * every other lookup in this file uses — not the ambient `getDbExec()`,\n * since `org_members` lives in that same app database.\n */\nasync function isOrgMember(\n reg: ShareableResourceRegistration,\n memberOrgId: string,\n email: string,\n): Promise<boolean> {\n const db = reg.getDb() as any;\n const rows = await db\n .select({ id: orgMembers.id })\n .from(orgMembers)\n .where(\n and(\n eq(orgMembers.orgId, memberOrgId),\n emailColumnMatches(orgMembers.email, email),\n ),\n )\n .limit(1);\n return rows.length > 0;\n}\n\n/**\n * Build a Drizzle `WHERE` clause that admits rows the current user can see.\n * Pass the ownable resource table and its shares table; optional min role\n * (defaults to 'viewer') gates which share rows count.\n *\n * `visibility = 'public'` is intentionally NOT admitted by default. Public\n * means \"anyone with the link can view\" (still honoured by `resolveAccess`\n * for read-by-id), not \"appears in every signed-in user's list/sidebar.\"\n * Pass `{ includePublic: true }` for the rare list endpoint that wants\n * cross-user public discovery (a public template gallery, for example).\n *\n * Example:\n *\n * const rows = await db\n * .select()\n * .from(schema.documents)\n * .where(accessFilter(schema.documents, schema.documentShares));\n */\nexport function accessFilter(\n resourceTable: any,\n sharesTable: any,\n rawCtx: AccessContext = currentAccess(),\n minRole: ShareRole = \"viewer\",\n options: { includePublic?: boolean } = {},\n): SQL {\n // Defense in depth — resources registered with `allowPublic: false` must\n // never participate in cross-user \"public\" discovery, even if a caller\n // accidentally passes `includePublic: true` or if a stale public row sits\n // in the DB.\n const reg = findRegistrationByTable(resourceTable);\n const ctx = resolveRegisteredAccessContext(reg, rawCtx);\n const { userEmail, orgId } = ctx;\n const normalizedUserEmail = normalizeEmailForAccess(userEmail);\n const publicAllowed = reg?.allowPublic !== false;\n const includePublic = (options.includePublic ?? false) && publicAllowed;\n const clauses: SQL[] = [];\n\n if (normalizedUserEmail) {\n clauses.push(\n and(\n emailColumnMatches(resourceTable.ownerEmail, normalizedUserEmail),\n ownerScopeFilter(reg, resourceTable, ctx),\n )!,\n );\n }\n if (minRole === \"viewer\") {\n if (includePublic) {\n clauses.push(eq(resourceTable.visibility, \"public\"));\n }\n if (orgId) {\n clauses.push(\n and(\n eq(resourceTable.visibility, \"org\"),\n eq(resourceTable.orgId, orgId),\n )!,\n );\n }\n }\n if (normalizedUserEmail) {\n const shareScope = restrictedShareScopeSql(reg, resourceTable, ctx);\n clauses.push(\n sql`exists (select 1 from ${sharesTable}\n where ${sharesTable.resourceId} = ${resourceTable.id}\n and ${sharesTable.principalType} = 'user'\n and lower(${sharesTable.principalId}) = ${normalizedUserEmail}\n and ${shareScope}\n and ${minRoleSql(minRole)})`,\n );\n }\n if (orgId) {\n const shareScope = restrictedShareScopeSql(reg, resourceTable, ctx);\n clauses.push(\n sql`exists (select 1 from ${sharesTable}\n where ${sharesTable.resourceId} = ${resourceTable.id}\n and ${sharesTable.principalType} = 'org'\n and ${sharesTable.principalId} = ${orgId}\n and ${shareScope}\n and ${minRoleSql(minRole)})`,\n );\n }\n\n return or(...clauses) ?? sql`1=0`;\n}\n\nfunction ownerScopeFilter(\n reg: ShareableResourceRegistration | undefined,\n resourceTable: any,\n ctx: AccessContext,\n): SQL {\n if (reg?.ownerAccessIgnoresOrg === true) return sql`1=1`;\n if (ctx.orgId) {\n // Rows created before org-scoping, or in solo mode, have no org_id. Keep\n // them manageable by their owner after the owner joins or switches into an\n // organization, while still keeping rows from other orgs out of scope.\n return or(\n eq(resourceTable.orgId, ctx.orgId),\n sql`${resourceTable.orgId} IS NULL`,\n )!;\n }\n return sql`${resourceTable.orgId} IS NULL`;\n}\n\nfunction ownerMatchesActiveScope(\n reg: ShareableResourceRegistration | undefined,\n resource: any,\n ctx: AccessContext,\n): boolean {\n if (reg?.ownerAccessIgnoresOrg === true) return true;\n const resourceOrgId = resource?.orgId ?? null;\n if (!resourceOrgId) return true;\n return ctx.orgId === resourceOrgId;\n}\n\nfunction minRoleSql(minRole: ShareRole): SQL {\n if (minRole === \"viewer\") {\n // any role satisfies viewer\n return sql`1=1`;\n }\n if (minRole === \"editor\") {\n return sql`role in ('editor','admin')`;\n }\n return sql`role = 'admin'`;\n}\n\nfunction restrictedShareScopeSql(\n reg: ShareableResourceRegistration | undefined,\n resourceTable: any,\n ctx: AccessContext,\n): SQL {\n // Restricted resources (extensions) must stay inside their resource org even\n // if stale cross-org share rows already exist from older code or bad data.\n if (reg?.requireOrgMemberForUserShares !== true) return sql`1=1`;\n if (!ctx.orgId) return sql`1=0`;\n return eq(resourceTable.orgId, ctx.orgId);\n}\n\nfunction explicitSharesAllowedForResource(\n reg: ShareableResourceRegistration,\n resource: any,\n ctx: AccessContext,\n): boolean {\n if (reg.requireOrgMemberForUserShares !== true) return true;\n const resourceOrgId = resource?.orgId ?? null;\n return !!resourceOrgId && !!ctx.orgId && resourceOrgId === ctx.orgId;\n}\n\nexport interface ResolvedAccess {\n /** Effective role: 'owner' for the resource owner, or the share role. */\n role: \"owner\" | ShareRole;\n /** The resource row (already loaded). */\n resource: any;\n}\n\n/**\n * Minimal resource shape returned when a caller opts into a projected access\n * load via `{ skipResourceBody: true }`. Contains exactly the columns the\n * access-decision logic itself reads — identity, ownership, org scope, and\n * visibility — never a resource type's heavy body columns (`data`,\n * `content`, and similar blobs).\n */\nexport interface AccessProjectedResource {\n id: string;\n ownerEmail: string;\n orgId: string | null;\n visibility: Visibility;\n}\n\nexport interface ResolvedAccessProjected {\n /** Effective role: 'owner' for the resource owner, or the share role. */\n role: \"owner\" | ShareRole;\n /** Only the access-decision columns — not the full resource row. */\n resource: AccessProjectedResource;\n}\n\nexport interface ResolveAccessOptions {\n /**\n * When true, load only the columns the access decision itself needs\n * (`id`, `ownerEmail`, `orgId`, `visibility`) instead of the full resource\n * row. Use this when the caller only needs the access decision — not the\n * resource body — and wants to skip fetching heavy type-specific columns\n * (e.g. `data`/`content` blobs) on every `assertAccess`/`resolveAccess`\n * call.\n *\n * Silently ignored (falls back to a full row load) for any resource type\n * registered with a `publicAccessRole` *function* resolver, since that\n * callback can read arbitrary resource fields the projection would have\n * omitted — see `hasDynamicPublicAccessRoleResolver` below.\n *\n * Default: `false` — the full row is loaded, matching historical behavior.\n * Callers that need resource body fields (most call sites today — for\n * many resource types `resolveAccess().resource` doubles as the action's\n * primary data read) must NOT pass this.\n */\n skipResourceBody?: boolean;\n}\n\nasync function publicAccessRoleForResource(\n reg: ShareableResourceRegistration,\n resource: any,\n ctx: AccessContext,\n): Promise<ShareRole> {\n const roleResolver = reg.publicAccessRole;\n if (!roleResolver) return \"viewer\";\n return typeof roleResolver === \"function\"\n ? await roleResolver(resource, ctx)\n : roleResolver;\n}\n\nfunction higherShareRole(a: ShareRole, b: ShareRole | null): ShareRole {\n if (!b) return a;\n return ROLE_RANK[b] > ROLE_RANK[a] ? b : a;\n}\n\nfunction columnName(column: unknown): string | null {\n const candidate = column as\n | {\n name?: unknown;\n config?: { name?: unknown };\n _: { name?: unknown };\n }\n | undefined;\n const name = candidate?.name ?? candidate?.config?.name ?? candidate?._?.name;\n return typeof name === \"string\" && name ? name : null;\n}\n\nfunction missingColumnName(err: unknown): string | null {\n const error = err as { code?: string; message?: string } | undefined;\n const message = error?.message ?? \"\";\n if (\n error?.code !== \"42703\" &&\n !/no such column|does not exist/i.test(message)\n ) {\n return null;\n }\n const quoted = message.match(/column\\s+\"([^\"]+)\"\\s+does not exist/i)?.[1];\n if (quoted) return quoted;\n const sqlite = message.match(/no such column:\\s+[\"`]?([\\w.]+)[\"`]?/i)?.[1];\n if (sqlite) return sqlite.split(\".\").pop() ?? sqlite;\n return null;\n}\n\nfunction selectExistingColumns(\n columns: Record<string, unknown>,\n omittedColumnNames: Set<string>,\n): Record<string, unknown> {\n const selection: Record<string, unknown> = {};\n for (const [key, column] of Object.entries(columns)) {\n const name = columnName(column);\n if (!name || omittedColumnNames.has(name)) continue;\n selection[key] = column;\n }\n return selection;\n}\n\n/**\n * The fixed column set the access-decision logic in `resolveAccess` itself\n * reads: identity (`id`), ownership (`ownerEmail`, `orgId`), and the coarse\n * `visibility` flag. This is intentionally NOT caller-configurable — an\n * arbitrary caller-supplied column list could omit a column the access\n * checks below depend on and silently break authorization. Every ownable\n * resource table has these columns (`ownableColumns()` + a primary key\n * named `id`), so this projection is safe for any registration that\n * doesn't also read the row through a dynamic resolver (see below).\n */\nfunction projectedAccessColumns(resourceTable: any): Record<string, unknown> {\n return {\n id: resourceTable.id,\n ownerEmail: resourceTable.ownerEmail,\n orgId: resourceTable.orgId,\n visibility: resourceTable.visibility,\n };\n}\n\n/**\n * True when this registration's `publicAccessRole` is a callback rather than\n * a fixed role string. Callbacks receive the loaded `resource` and may read\n * arbitrary fields on it (see e.g. `templates/design`'s\n * `publicDesignAccessRole`, which inspects design-specific data). The\n * projected access load must never be used for these registrations — always\n * fall back to a full row load so the resolver sees the complete resource.\n */\nfunction hasDynamicPublicAccessRoleResolver(\n reg: ShareableResourceRegistration,\n): boolean {\n return typeof reg.publicAccessRole === \"function\";\n}\n\nasync function loadResourceForAccess(\n reg: ShareableResourceRegistration,\n resourceId: string,\n options: ResolveAccessOptions = {},\n): Promise<any | null> {\n const db = reg.getDb() as any;\n const useProjection =\n options.skipResourceBody === true &&\n !hasDynamicPublicAccessRoleResolver(reg);\n const projectedColumns = useProjection\n ? projectedAccessColumns(reg.resourceTable)\n : null;\n const omittedColumnNames = new Set<string>();\n\n for (let attempt = 0; attempt < 12; attempt++) {\n try {\n const query =\n !projectedColumns && omittedColumnNames.size === 0\n ? db.select()\n : db.select(\n selectExistingColumns(\n projectedColumns ?? reg.resourceTable,\n omittedColumnNames,\n ),\n );\n const [resource] = await query\n .from(reg.resourceTable)\n .where(eq(reg.resourceTable.id, resourceId));\n return resource ?? null;\n } catch (err) {\n const missing = missingColumnName(err);\n if (!missing || omittedColumnNames.has(missing)) throw err;\n omittedColumnNames.add(missing);\n console.warn(\n `[sharing] ${reg.type} access lookup omitted missing column ${missing}`,\n );\n }\n }\n\n throw new Error(\n `Could not load ${reg.type} ${resourceId}: too many missing resource columns`,\n );\n}\n\n/**\n * Return the effective role the current user has on a specific resource, or\n * null if they have no access. Loads the resource and relevant share rows.\n *\n * By default the full resource row is loaded (unchanged historical\n * behavior — for most resource types `.resource` here doubles as the\n * action's primary data read). Pass `{ skipResourceBody: true }` when the\n * caller only needs the access decision to load just the access-decision\n * columns instead — see `ResolveAccessOptions`.\n */\nexport async function resolveAccess(\n resourceType: string,\n resourceId: string,\n rawCtx?: AccessContext,\n options?: { skipResourceBody?: false },\n): Promise<ResolvedAccess | null>;\nexport async function resolveAccess(\n resourceType: string,\n resourceId: string,\n rawCtx: AccessContext | undefined,\n options: { skipResourceBody: true },\n): Promise<ResolvedAccessProjected | null>;\nexport async function resolveAccess(\n resourceType: string,\n resourceId: string,\n rawCtx: AccessContext = currentAccess(),\n options: ResolveAccessOptions = {},\n): Promise<ResolvedAccess | ResolvedAccessProjected | null> {\n return resolveAccessImpl(resourceType, resourceId, rawCtx, options);\n}\n\n/**\n * Un-overloaded implementation shared by the `resolveAccess` overloads and\n * called directly by `assertAccess` below. Kept separate from the exported\n * `resolveAccess` so internal callers passing a widened\n * `ResolveAccessOptions` (rather than a `{ skipResourceBody: true }` /\n * `{ skipResourceBody?: false }` literal) don't have to satisfy TypeScript's\n * overload resolution, which only matches against the declared overload\n * signatures, not the implementation signature.\n */\nasync function resolveAccessImpl(\n resourceType: string,\n resourceId: string,\n rawCtx: AccessContext = currentAccess(),\n options: ResolveAccessOptions = {},\n): Promise<ResolvedAccess | ResolvedAccessProjected | null> {\n const reg = requireShareableResource(resourceType);\n const ctx = resolveRegisteredAccessContext(reg, rawCtx);\n\n const resource = await loadResourceForAccess(reg, resourceId, options);\n if (!resource) return null;\n\n const { userEmail } = ctx;\n const normalizedUserEmail = normalizeEmailForAccess(userEmail);\n\n if (\n normalizedUserEmail &&\n normalizeEmailForAccess(resource.ownerEmail) === normalizedUserEmail &&\n ownerMatchesActiveScope(reg, resource, ctx)\n ) {\n return { role: \"owner\", resource };\n }\n if (resource.visibility === \"public\" && reg.allowPublic !== false) {\n // No share row needed; default viewer unless the resource registration\n // deliberately grants a stronger public-by-link role or explicit shares\n // upgrade the current principal.\n const publicRole = await publicAccessRoleForResource(reg, resource, ctx);\n const role = await highestShareRole(reg, resourceId, ctx, resource);\n return { role: higherShareRole(publicRole, role), resource };\n }\n // `visibility === \"public\"` on an `allowPublic: false` resource is treated\n // as private: only owner + explicit shares grant access. Falls through to\n // the explicit-share lookup below.\n //\n // Membership in the resource's own org, not equality with the caller's\n // currently active org: a caller can be a genuine member of the\n // resource's org while a *different* org is their active selection, and\n // `org` visibility should still admit them. Still requires some active\n // org to be set at all (`orgId`), matching the pre-existing behavior for\n // a caller with no active org.\n if (\n resource.visibility === \"org\" &&\n resource.orgId &&\n normalizedUserEmail &&\n (await isOrgMember(reg, resource.orgId, normalizedUserEmail))\n ) {\n const role = await highestShareRole(reg, resourceId, ctx, resource);\n return { role: role ?? \"viewer\", resource };\n }\n const role = await highestShareRole(reg, resourceId, ctx, resource);\n if (role) return { role, resource };\n return null;\n}\n\nasync function highestShareRole(\n reg: ShareableResourceRegistration,\n resourceId: string,\n ctx: AccessContext,\n resource: any,\n): Promise<ShareRole | null> {\n const { userEmail, orgId } = ctx;\n const normalizedUserEmail = normalizeEmailForAccess(userEmail);\n if (!normalizedUserEmail && !orgId) return null;\n if (!explicitSharesAllowedForResource(reg, resource, ctx)) return null;\n const db = reg.getDb() as any;\n\n const principalClauses: ReturnType<typeof and>[] = [];\n if (normalizedUserEmail) {\n principalClauses.push(\n and(\n eq(reg.sharesTable.principalType, \"user\"),\n emailColumnMatches(reg.sharesTable.principalId, normalizedUserEmail),\n ),\n );\n }\n if (orgId) {\n principalClauses.push(\n and(\n eq(reg.sharesTable.principalType, \"org\"),\n eq(reg.sharesTable.principalId, orgId),\n ),\n );\n }\n\n const rows = await db\n .select({ role: reg.sharesTable.role })\n .from(reg.sharesTable)\n .where(\n and(eq(reg.sharesTable.resourceId, resourceId), or(...principalClauses)),\n )\n .limit(10);\n\n let best: ShareRole | null = null;\n for (const r of rows as Array<{ role: ShareRole }>) {\n if (!best || ROLE_RANK[r.role] > ROLE_RANK[best]) best = r.role;\n }\n return best;\n}\n\n/**\n * Throw ForbiddenError if the current user can't act on this resource with at\n * least the given role. Used at the top of update/delete actions.\n *\n * By default the full resource row is loaded (unchanged historical\n * behavior). Pass `{ skipResourceBody: true }` as the fifth argument when\n * the caller only needs the access decision — see `ResolveAccessOptions`.\n */\nexport async function assertAccess(\n resourceType: string,\n resourceId: string,\n minRole?: ShareRole | \"owner\",\n ctx?: AccessContext,\n options?: { skipResourceBody?: false },\n): Promise<ResolvedAccess>;\nexport async function assertAccess(\n resourceType: string,\n resourceId: string,\n minRole: ShareRole | \"owner\" | undefined,\n ctx: AccessContext | undefined,\n options: { skipResourceBody: true },\n): Promise<ResolvedAccessProjected>;\nexport async function assertAccess(\n resourceType: string,\n resourceId: string,\n minRole: ShareRole | \"owner\" = \"viewer\",\n ctx: AccessContext = currentAccess(),\n options: ResolveAccessOptions = {},\n): Promise<ResolvedAccess | ResolvedAccessProjected> {\n const access = await resolveAccessImpl(\n resourceType,\n resourceId,\n ctx,\n options,\n );\n if (!access) {\n throw new ForbiddenError(`No access to ${resourceType} ${resourceId}`);\n }\n if (ROLE_RANK[access.role] < ROLE_RANK[minRole]) {\n throw new ForbiddenError(\n `Requires ${minRole} role on ${resourceType} ${resourceId} (have ${access.role})`,\n );\n }\n return access;\n}\n"]}
@@ -8,4 +8,5 @@
8
8
  export { ownableColumns, createSharesTable, roleSatisfies, ROLE_RANK, type Visibility, type ShareRole, type PrincipalType, } from "./schema.js";
9
9
  export { registerShareableResource, getShareableResource, requireShareableResource, listShareableResources, type ShareableResourceRegistration, type ShareEmailExtras, } from "./registry.js";
10
10
  export { accessFilter, resolveAccess, assertAccess, currentAccess, ForbiddenError, type AccessContext, type ResolvedAccess, } from "./access.js";
11
+ export { filterRecipientsByResourceAccess, type FilterRecipientsInput, } from "./recipients.js";
11
12
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/sharing/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EACL,cAAc,EACd,iBAAiB,EACjB,aAAa,EACb,SAAS,EACT,KAAK,UAAU,EACf,KAAK,SAAS,EACd,KAAK,aAAa,GACnB,MAAM,aAAa,CAAC;AAErB,OAAO,EACL,yBAAyB,EACzB,oBAAoB,EACpB,wBAAwB,EACxB,sBAAsB,EACtB,KAAK,6BAA6B,EAClC,KAAK,gBAAgB,GACtB,MAAM,eAAe,CAAC;AAEvB,OAAO,EACL,YAAY,EACZ,aAAa,EACb,YAAY,EACZ,aAAa,EACb,cAAc,EACd,KAAK,aAAa,EAClB,KAAK,cAAc,GACpB,MAAM,aAAa,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/sharing/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EACL,cAAc,EACd,iBAAiB,EACjB,aAAa,EACb,SAAS,EACT,KAAK,UAAU,EACf,KAAK,SAAS,EACd,KAAK,aAAa,GACnB,MAAM,aAAa,CAAC;AAErB,OAAO,EACL,yBAAyB,EACzB,oBAAoB,EACpB,wBAAwB,EACxB,sBAAsB,EACtB,KAAK,6BAA6B,EAClC,KAAK,gBAAgB,GACtB,MAAM,eAAe,CAAC;AAEvB,OAAO,EACL,YAAY,EACZ,aAAa,EACb,YAAY,EACZ,aAAa,EACb,cAAc,EACd,KAAK,aAAa,EAClB,KAAK,cAAc,GACpB,MAAM,aAAa,CAAC;AAErB,OAAO,EACL,gCAAgC,EAChC,KAAK,qBAAqB,GAC3B,MAAM,iBAAiB,CAAC"}
@@ -8,4 +8,5 @@
8
8
  export { ownableColumns, createSharesTable, roleSatisfies, ROLE_RANK, } from "./schema.js";
9
9
  export { registerShareableResource, getShareableResource, requireShareableResource, listShareableResources, } from "./registry.js";
10
10
  export { accessFilter, resolveAccess, assertAccess, currentAccess, ForbiddenError, } from "./access.js";
11
+ export { filterRecipientsByResourceAccess, } from "./recipients.js";
11
12
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/sharing/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EACL,cAAc,EACd,iBAAiB,EACjB,aAAa,EACb,SAAS,GAIV,MAAM,aAAa,CAAC;AAErB,OAAO,EACL,yBAAyB,EACzB,oBAAoB,EACpB,wBAAwB,EACxB,sBAAsB,GAGvB,MAAM,eAAe,CAAC;AAEvB,OAAO,EACL,YAAY,EACZ,aAAa,EACb,YAAY,EACZ,aAAa,EACb,cAAc,GAGf,MAAM,aAAa,CAAC","sourcesContent":["/**\n * Framework-level sharing / privacy primitive.\n *\n * Templates make their resource tables ownable and register them here so the\n * shared share actions and UI work end-to-end. See\n * `.agents/skills/sharing/SKILL.md` for the full pattern.\n */\n\nexport {\n ownableColumns,\n createSharesTable,\n roleSatisfies,\n ROLE_RANK,\n type Visibility,\n type ShareRole,\n type PrincipalType,\n} from \"./schema.js\";\n\nexport {\n registerShareableResource,\n getShareableResource,\n requireShareableResource,\n listShareableResources,\n type ShareableResourceRegistration,\n type ShareEmailExtras,\n} from \"./registry.js\";\n\nexport {\n accessFilter,\n resolveAccess,\n assertAccess,\n currentAccess,\n ForbiddenError,\n type AccessContext,\n type ResolvedAccess,\n} from \"./access.js\";\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/sharing/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EACL,cAAc,EACd,iBAAiB,EACjB,aAAa,EACb,SAAS,GAIV,MAAM,aAAa,CAAC;AAErB,OAAO,EACL,yBAAyB,EACzB,oBAAoB,EACpB,wBAAwB,EACxB,sBAAsB,GAGvB,MAAM,eAAe,CAAC;AAEvB,OAAO,EACL,YAAY,EACZ,aAAa,EACb,YAAY,EACZ,aAAa,EACb,cAAc,GAGf,MAAM,aAAa,CAAC;AAErB,OAAO,EACL,gCAAgC,GAEjC,MAAM,iBAAiB,CAAC","sourcesContent":["/**\n * Framework-level sharing / privacy primitive.\n *\n * Templates make their resource tables ownable and register them here so the\n * shared share actions and UI work end-to-end. See\n * `.agents/skills/sharing/SKILL.md` for the full pattern.\n */\n\nexport {\n ownableColumns,\n createSharesTable,\n roleSatisfies,\n ROLE_RANK,\n type Visibility,\n type ShareRole,\n type PrincipalType,\n} from \"./schema.js\";\n\nexport {\n registerShareableResource,\n getShareableResource,\n requireShareableResource,\n listShareableResources,\n type ShareableResourceRegistration,\n type ShareEmailExtras,\n} from \"./registry.js\";\n\nexport {\n accessFilter,\n resolveAccess,\n assertAccess,\n currentAccess,\n ForbiddenError,\n type AccessContext,\n type ResolvedAccess,\n} from \"./access.js\";\n\nexport {\n filterRecipientsByResourceAccess,\n type FilterRecipientsInput,\n} from \"./recipients.js\";\n"]}
@@ -0,0 +1,31 @@
1
+ import { type ShareRole } from "./schema.js";
2
+ export interface FilterRecipientsInput {
3
+ resourceType: string;
4
+ resourceId: string;
5
+ emails: Iterable<string>;
6
+ /** The resource's org, needed to honor `org` visibility. */
7
+ orgId?: string | null;
8
+ minimumRole?: ShareRole;
9
+ /**
10
+ * Override for resources that are not registered through `registerShareableResource`
11
+ * (the review registry resolves its own). Must honor `userEmail`/`orgId`
12
+ * rather than the ambient request, or the filter checks the wrong principal.
13
+ */
14
+ resolveRole?: (ctx: {
15
+ userEmail: string;
16
+ orgId?: string;
17
+ }) => Promise<{
18
+ role: string;
19
+ } | null>;
20
+ }
21
+ /**
22
+ * Keep only the addresses that can still open the resource *right now*.
23
+ *
24
+ * Notification recipient lists are built from historical rows — thread
25
+ * authors, stored mentions, caller-supplied addresses — none of which are an
26
+ * access grant. Without this, mentioning an arbitrary address mails it the
27
+ * comment body, and a collaborator whose share was revoked keeps receiving the
28
+ * thread. Access is re-resolved per address against the live ACL.
29
+ */
30
+ export declare function filterRecipientsByResourceAccess({ resourceType, resourceId, emails, orgId, minimumRole, resolveRole, }: FilterRecipientsInput): Promise<string[]>;
31
+ //# sourceMappingURL=recipients.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"recipients.d.ts","sourceRoot":"","sources":["../../src/sharing/recipients.ts"],"names":[],"mappings":"AAEA,OAAO,EAAiB,KAAK,SAAS,EAAE,MAAM,aAAa,CAAC;AAE5D,MAAM,WAAW,qBAAqB;IACpC,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;IACzB,4DAA4D;IAC5D,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,WAAW,CAAC,EAAE,SAAS,CAAC;IACxB;;;;OAIG;IACH,WAAW,CAAC,EAAE,CAAC,GAAG,EAAE;QAClB,SAAS,EAAE,MAAM,CAAC;QAClB,KAAK,CAAC,EAAE,MAAM,CAAC;KAChB,KAAK,OAAO,CAAC;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC,CAAC;CACxC;AAED;;;;;;;;GAQG;AACH,wBAAsB,gCAAgC,CAAC,EACrD,YAAY,EACZ,UAAU,EACV,MAAM,EACN,KAAK,EACL,WAAsB,EACtB,WAAW,GACZ,EAAE,qBAAqB,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CA4B3C"}
@@ -0,0 +1,40 @@
1
+ import { isOrgMember } from "../org/membership.js";
2
+ import { resolveAccess } from "./access.js";
3
+ import { roleSatisfies } from "./schema.js";
4
+ /**
5
+ * Keep only the addresses that can still open the resource *right now*.
6
+ *
7
+ * Notification recipient lists are built from historical rows — thread
8
+ * authors, stored mentions, caller-supplied addresses — none of which are an
9
+ * access grant. Without this, mentioning an arbitrary address mails it the
10
+ * comment body, and a collaborator whose share was revoked keeps receiving the
11
+ * thread. Access is re-resolved per address against the live ACL.
12
+ */
13
+ export async function filterRecipientsByResourceAccess({ resourceType, resourceId, emails, orgId, minimumRole = "viewer", resolveRole, }) {
14
+ const resolve = resolveRole ??
15
+ ((ctx) => resolveAccess(resourceType, resourceId, ctx));
16
+ const unique = new Set();
17
+ for (const raw of emails) {
18
+ const email = raw.trim().toLowerCase();
19
+ if (email)
20
+ unique.add(email);
21
+ }
22
+ if (unique.size === 0)
23
+ return [];
24
+ const decisions = await Promise.all([...unique].map(async (email) => {
25
+ // Resolve without an org first: passing the resource's org up front
26
+ // would hand org-visibility access to anyone who is not in it.
27
+ const direct = await resolve({ userEmail: email });
28
+ if (direct && roleSatisfies(direct.role, minimumRole)) {
29
+ return email;
30
+ }
31
+ if (!orgId || !(await isOrgMember(orgId, email)))
32
+ return null;
33
+ const viaOrg = await resolve({ userEmail: email, orgId });
34
+ return viaOrg && roleSatisfies(viaOrg.role, minimumRole)
35
+ ? email
36
+ : null;
37
+ }));
38
+ return decisions.filter((email) => email !== null);
39
+ }
40
+ //# sourceMappingURL=recipients.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"recipients.js","sourceRoot":"","sources":["../../src/sharing/recipients.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AACnD,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,aAAa,EAAkB,MAAM,aAAa,CAAC;AAoB5D;;;;;;;;GAQG;AACH,MAAM,CAAC,KAAK,UAAU,gCAAgC,CAAC,EACrD,YAAY,EACZ,UAAU,EACV,MAAM,EACN,KAAK,EACL,WAAW,GAAG,QAAQ,EACtB,WAAW,GACW;IACtB,MAAM,OAAO,GACX,WAAW;QACX,CAAC,CAAC,GAA0C,EAAE,EAAE,CAC9C,aAAa,CAAC,YAAY,EAAE,UAAU,EAAE,GAAG,CAAC,CAAC,CAAC;IAClD,MAAM,MAAM,GAAG,IAAI,GAAG,EAAU,CAAC;IACjC,KAAK,MAAM,GAAG,IAAI,MAAM,EAAE,CAAC;QACzB,MAAM,KAAK,GAAG,GAAG,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;QACvC,IAAI,KAAK;YAAE,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;IAC/B,CAAC;IACD,IAAI,MAAM,CAAC,IAAI,KAAK,CAAC;QAAE,OAAO,EAAE,CAAC;IAEjC,MAAM,SAAS,GAAG,MAAM,OAAO,CAAC,GAAG,CACjC,CAAC,GAAG,MAAM,CAAC,CAAC,GAAG,CAAC,KAAK,EAAE,KAAK,EAAE,EAAE;QAC9B,oEAAoE;QACpE,+DAA+D;QAC/D,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC,CAAC;QACnD,IAAI,MAAM,IAAI,aAAa,CAAC,MAAM,CAAC,IAAiB,EAAE,WAAW,CAAC,EAAE,CAAC;YACnE,OAAO,KAAK,CAAC;QACf,CAAC;QACD,IAAI,CAAC,KAAK,IAAI,CAAC,CAAC,MAAM,WAAW,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;YAAE,OAAO,IAAI,CAAC;QAC9D,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,EAAE,SAAS,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC;QAC1D,OAAO,MAAM,IAAI,aAAa,CAAC,MAAM,CAAC,IAAiB,EAAE,WAAW,CAAC;YACnE,CAAC,CAAC,KAAK;YACP,CAAC,CAAC,IAAI,CAAC;IACX,CAAC,CAAC,CACH,CAAC;IACF,OAAO,SAAS,CAAC,MAAM,CAAC,CAAC,KAAK,EAAmB,EAAE,CAAC,KAAK,KAAK,IAAI,CAAC,CAAC;AACtE,CAAC","sourcesContent":["import { isOrgMember } from \"../org/membership.js\";\nimport { resolveAccess } from \"./access.js\";\nimport { roleSatisfies, type ShareRole } from \"./schema.js\";\n\nexport interface FilterRecipientsInput {\n resourceType: string;\n resourceId: string;\n emails: Iterable<string>;\n /** The resource's org, needed to honor `org` visibility. */\n orgId?: string | null;\n minimumRole?: ShareRole;\n /**\n * Override for resources that are not registered through `registerShareableResource`\n * (the review registry resolves its own). Must honor `userEmail`/`orgId`\n * rather than the ambient request, or the filter checks the wrong principal.\n */\n resolveRole?: (ctx: {\n userEmail: string;\n orgId?: string;\n }) => Promise<{ role: string } | null>;\n}\n\n/**\n * Keep only the addresses that can still open the resource *right now*.\n *\n * Notification recipient lists are built from historical rows — thread\n * authors, stored mentions, caller-supplied addresses — none of which are an\n * access grant. Without this, mentioning an arbitrary address mails it the\n * comment body, and a collaborator whose share was revoked keeps receiving the\n * thread. Access is re-resolved per address against the live ACL.\n */\nexport async function filterRecipientsByResourceAccess({\n resourceType,\n resourceId,\n emails,\n orgId,\n minimumRole = \"viewer\",\n resolveRole,\n}: FilterRecipientsInput): Promise<string[]> {\n const resolve =\n resolveRole ??\n ((ctx: { userEmail: string; orgId?: string }) =>\n resolveAccess(resourceType, resourceId, ctx));\n const unique = new Set<string>();\n for (const raw of emails) {\n const email = raw.trim().toLowerCase();\n if (email) unique.add(email);\n }\n if (unique.size === 0) return [];\n\n const decisions = await Promise.all(\n [...unique].map(async (email) => {\n // Resolve without an org first: passing the resource's org up front\n // would hand org-visibility access to anyone who is not in it.\n const direct = await resolve({ userEmail: email });\n if (direct && roleSatisfies(direct.role as ShareRole, minimumRole)) {\n return email;\n }\n if (!orgId || !(await isOrgMember(orgId, email))) return null;\n const viaOrg = await resolve({ userEmail: email, orgId });\n return viaOrg && roleSatisfies(viaOrg.role as ShareRole, minimumRole)\n ? email\n : null;\n }),\n );\n return decisions.filter((email): email is string => email !== null);\n}\n"]}
@@ -3,6 +3,8 @@ import { LanguagePicker, useT } from "@agent-native/core/client/i18n";
3
3
  import { TeamPage } from "@agent-native/core/client/org";
4
4
  import {
5
5
  AccountSettingsCard,
6
+ SettingsGroup,
7
+ SettingsRow,
6
8
  SettingsTabsPage,
7
9
  useAgentSettingsTabs,
8
10
  type SettingsSearchEntry,
@@ -10,14 +12,6 @@ import {
10
12
  import { useSetPageTitle } from "@agent-native/toolkit/app-shell";
11
13
  import { useMemo } from "react";
12
14
 
13
- import {
14
- Card,
15
- CardContent,
16
- CardDescription,
17
- CardHeader,
18
- CardTitle,
19
- } from "@/components/ui/card";
20
- import { Label } from "@/components/ui/label";
21
15
  import { APP_TITLE } from "@/lib/app-config";
22
16
 
23
17
  import changelog from "../../CHANGELOG.md?raw";
@@ -55,20 +49,18 @@ export default function SettingsRoute() {
55
49
  {t("settings.description")}
56
50
  </p>
57
51
 
58
- <Card id="language" className="scroll-mt-16">
59
- <CardHeader>
60
- <CardTitle className="text-base">
61
- {t("settings.languageTitle")}
62
- </CardTitle>
63
- <CardDescription>
64
- {t("settings.languageDescription")}
65
- </CardDescription>
66
- </CardHeader>
67
- <CardContent className="max-w-xs space-y-1.5">
68
- <Label>{t("settings.languageLabel")}</Label>
69
- <LanguagePicker label={t("settings.languageLabel")} />
70
- </CardContent>
71
- </Card>
52
+ <SettingsGroup>
53
+ <SettingsRow
54
+ id="language"
55
+ label={t("settings.languageTitle")}
56
+ description={t("settings.languageDescription")}
57
+ control={
58
+ <div className="w-56">
59
+ <LanguagePicker label={t("settings.languageLabel")} />
60
+ </div>
61
+ }
62
+ />
63
+ </SettingsGroup>
72
64
  </div>
73
65
  }
74
66
  team={
@@ -0,0 +1,9 @@
1
+ import baseConfig from "@agent-native/core/vitest-config";
2
+ import { mergeConfig } from "vitest/config";
3
+
4
+ import viteConfig from "./vite.config";
5
+
6
+ // Kept separate from vite.config.ts so the production config never imports
7
+ // vitest: `agent-native build` loads vite.config.ts in installs where vitest
8
+ // is absent.
9
+ export default mergeConfig(viteConfig, baseConfig);
@@ -6,11 +6,13 @@ export interface AutomationActionItem {
6
6
  triggerType: "event" | "schedule";
7
7
  event: string | null;
8
8
  schedule: string | null;
9
+ timezone: string | null;
9
10
  scheduleDescription: string | null;
10
11
  condition: string | null;
11
12
  body: string;
12
13
  enabled: boolean;
13
14
  lastRun: string | null;
15
+ lastCheck: string | null;
14
16
  lastStatus: string | null;
15
17
  lastError: string | null;
16
18
  nextRun: string | null;
@@ -1 +1 @@
1
- {"version":3,"file":"list-automations.d.ts","sourceRoot":"","sources":["../../../src/triggers/actions/list-automations.ts"],"names":[],"mappings":"AA0BA,MAAM,WAAW,oBAAoB;IACnC,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,UAAU,GAAG,cAAc,CAAC;IACnC,WAAW,EAAE,OAAO,GAAG,UAAU,CAAC;IAClC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,mBAAmB,EAAE,MAAM,GAAG,IAAI,CAAC;IACnC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,mBAAmB,EAAE,MAAM,GAAG,IAAI,CAAC;IACnC,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,SAAS,EAAE,OAAO,CAAC;CACpB"}
1
+ {"version":3,"file":"list-automations.d.ts","sourceRoot":"","sources":["../../../src/triggers/actions/list-automations.ts"],"names":[],"mappings":"AAuCA,MAAM,WAAW,oBAAoB;IACnC,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,UAAU,GAAG,cAAc,CAAC;IACnC,WAAW,EAAE,OAAO,GAAG,UAAU,CAAC;IAClC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,mBAAmB,EAAE,MAAM,GAAG,IAAI,CAAC;IACnC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,mBAAmB,EAAE,MAAM,GAAG,IAAI,CAAC;IACnC,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,SAAS,EAAE,OAAO,CAAC;CACpB"}
@@ -1,19 +1,26 @@
1
1
  import { z } from "zod";
2
2
  import { defineAction } from "../../action.js";
3
3
  import { listAutomationDefinitions, } from "../../automations/service.js";
4
- import { describeCron, isValidCron, nextOccurrence } from "../../jobs/cron.js";
4
+ import { describeCron, effectiveTimezone, isValidCron, nextOccurrence, } from "../../jobs/cron.js";
5
5
  const scopeSchema = z.enum(["personal", "organization"]);
6
6
  function nextRun(meta) {
7
7
  if (!meta.enabled)
8
8
  return null;
9
- if (meta.nextRun)
10
- return meta.nextRun;
11
- if (meta.triggerType === "schedule" &&
9
+ const scheduled = Boolean(meta.triggerType === "schedule" &&
12
10
  meta.schedule &&
13
- isValidCron(meta.schedule)) {
14
- return nextOccurrence(meta.schedule).toISOString();
11
+ isValidCron(meta.schedule));
12
+ // A stored `nextRun` in the past means the dispatcher kept declining to run
13
+ // this automation, not that it is overdue. Report the real next occurrence
14
+ // and let `lastError` carry the reason it keeps being passed over.
15
+ if (meta.nextRun) {
16
+ const stored = new Date(meta.nextRun).getTime();
17
+ if (!Number.isFinite(stored) || stored > Date.now() || !scheduled) {
18
+ return meta.nextRun;
19
+ }
15
20
  }
16
- return null;
21
+ return scheduled
22
+ ? nextOccurrence(meta.schedule, undefined, meta.timezone).toISOString()
23
+ : null;
17
24
  }
18
25
  export default defineAction({
19
26
  description: "List event-triggered and schedule-triggered automations in the selected personal or organization scope.",
@@ -37,11 +44,15 @@ export default defineAction({
37
44
  triggerType: meta.triggerType,
38
45
  event: meta.event ?? null,
39
46
  schedule: meta.schedule || null,
40
- scheduleDescription: meta.schedule ? describeCron(meta.schedule) : null,
47
+ timezone: meta.schedule ? effectiveTimezone(meta.timezone) : null,
48
+ scheduleDescription: meta.schedule
49
+ ? describeCron(meta.schedule, effectiveTimezone(meta.timezone))
50
+ : null,
41
51
  condition: meta.condition ?? null,
42
52
  body,
43
53
  enabled: meta.enabled,
44
54
  lastRun: meta.lastRun ?? null,
55
+ lastCheck: meta.lastCheck ?? null,
45
56
  lastStatus: meta.lastStatus ?? null,
46
57
  lastError: meta.lastError ?? null,
47
58
  nextRun: nextRun(meta),
@@ -1 +1 @@
1
- {"version":3,"file":"list-automations.js","sourceRoot":"","sources":["../../../src/triggers/actions/list-automations.ts"],"names":[],"mappings":"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","sourcesContent":["import { z } from \"zod\";\n\nimport { defineAction } from \"../../action.js\";\nimport {\n listAutomationDefinitions,\n type AutomationScope,\n} from \"../../automations/service.js\";\nimport { describeCron, isValidCron, nextOccurrence } from \"../../jobs/cron.js\";\n\nconst scopeSchema = z.enum([\"personal\", \"organization\"]);\n\nfunction nextRun(\n meta: Awaited<ReturnType<typeof listAutomationDefinitions>>[number][\"meta\"],\n): string | null {\n if (!meta.enabled) return null;\n if (meta.nextRun) return meta.nextRun;\n if (\n meta.triggerType === \"schedule\" &&\n meta.schedule &&\n isValidCron(meta.schedule)\n ) {\n return nextOccurrence(meta.schedule).toISOString();\n }\n return null;\n}\n\nexport interface AutomationActionItem {\n id: string;\n name: string;\n path: string;\n scope: \"personal\" | \"organization\";\n triggerType: \"event\" | \"schedule\";\n event: string | null;\n schedule: string | null;\n scheduleDescription: string | null;\n condition: string | null;\n body: string;\n enabled: boolean;\n lastRun: string | null;\n lastStatus: string | null;\n lastError: string | null;\n nextRun: string | null;\n createdBy: string | null;\n model: string | null;\n mcpTools: string[];\n originScopeId: string | null;\n deliveryPlatform: string | null;\n deliveryDestination: string | null;\n deliveryThreadRef: string | null;\n deliveryTenantId: string | null;\n canUpdate: boolean;\n}\n\nexport default defineAction({\n description:\n \"List event-triggered and schedule-triggered automations in the selected personal or organization scope.\",\n agentTool: false,\n schema: z.object({\n scope: scopeSchema.default(\"personal\"),\n }),\n http: { method: \"GET\" },\n readOnly: true,\n parallelSafe: true,\n run: async ({ scope }, ctx): Promise<AutomationActionItem[]> => {\n const userEmail = ctx?.userEmail;\n if (!userEmail) throw new Error(\"Not authenticated.\");\n const definitions = await listAutomationDefinitions(\n { userEmail, orgId: ctx?.orgId },\n scope as AutomationScope,\n );\n return definitions.map(({ resource, name, meta, body, canUpdate }) => ({\n id: resource.id,\n name,\n path: resource.path,\n scope: scope as AutomationScope,\n triggerType: meta.triggerType,\n event: meta.event ?? null,\n schedule: meta.schedule || null,\n scheduleDescription: meta.schedule ? describeCron(meta.schedule) : null,\n condition: meta.condition ?? null,\n body,\n enabled: meta.enabled,\n lastRun: meta.lastRun ?? null,\n lastStatus: meta.lastStatus ?? null,\n lastError: meta.lastError ?? null,\n nextRun: nextRun(meta),\n createdBy: meta.createdBy ?? null,\n model: meta.model ?? null,\n mcpTools: meta.mcpTools ?? [],\n originScopeId: meta.originScopeId ?? null,\n deliveryPlatform: meta.deliveryPlatform ?? null,\n deliveryDestination: meta.deliveryDestination ?? null,\n deliveryThreadRef: meta.deliveryThreadRef ?? null,\n deliveryTenantId: meta.deliveryTenantId ?? null,\n canUpdate,\n }));\n },\n});\n"]}
1
+ {"version":3,"file":"list-automations.js","sourceRoot":"","sources":["../../../src/triggers/actions/list-automations.ts"],"names":[],"mappings":"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","sourcesContent":["import { z } from \"zod\";\n\nimport { defineAction } from \"../../action.js\";\nimport {\n listAutomationDefinitions,\n type AutomationScope,\n} from \"../../automations/service.js\";\nimport {\n describeCron,\n effectiveTimezone,\n isValidCron,\n nextOccurrence,\n} from \"../../jobs/cron.js\";\n\nconst scopeSchema = z.enum([\"personal\", \"organization\"]);\n\nfunction nextRun(\n meta: Awaited<ReturnType<typeof listAutomationDefinitions>>[number][\"meta\"],\n): string | null {\n if (!meta.enabled) return null;\n const scheduled = Boolean(\n meta.triggerType === \"schedule\" &&\n meta.schedule &&\n isValidCron(meta.schedule),\n );\n // A stored `nextRun` in the past means the dispatcher kept declining to run\n // this automation, not that it is overdue. Report the real next occurrence\n // and let `lastError` carry the reason it keeps being passed over.\n if (meta.nextRun) {\n const stored = new Date(meta.nextRun).getTime();\n if (!Number.isFinite(stored) || stored > Date.now() || !scheduled) {\n return meta.nextRun;\n }\n }\n return scheduled\n ? nextOccurrence(meta.schedule!, undefined, meta.timezone).toISOString()\n : null;\n}\n\nexport interface AutomationActionItem {\n id: string;\n name: string;\n path: string;\n scope: \"personal\" | \"organization\";\n triggerType: \"event\" | \"schedule\";\n event: string | null;\n schedule: string | null;\n timezone: string | null;\n scheduleDescription: string | null;\n condition: string | null;\n body: string;\n enabled: boolean;\n lastRun: string | null;\n lastCheck: string | null;\n lastStatus: string | null;\n lastError: string | null;\n nextRun: string | null;\n createdBy: string | null;\n model: string | null;\n mcpTools: string[];\n originScopeId: string | null;\n deliveryPlatform: string | null;\n deliveryDestination: string | null;\n deliveryThreadRef: string | null;\n deliveryTenantId: string | null;\n canUpdate: boolean;\n}\n\nexport default defineAction({\n description:\n \"List event-triggered and schedule-triggered automations in the selected personal or organization scope.\",\n agentTool: false,\n schema: z.object({\n scope: scopeSchema.default(\"personal\"),\n }),\n http: { method: \"GET\" },\n readOnly: true,\n parallelSafe: true,\n run: async ({ scope }, ctx): Promise<AutomationActionItem[]> => {\n const userEmail = ctx?.userEmail;\n if (!userEmail) throw new Error(\"Not authenticated.\");\n const definitions = await listAutomationDefinitions(\n { userEmail, orgId: ctx?.orgId },\n scope as AutomationScope,\n );\n return definitions.map(({ resource, name, meta, body, canUpdate }) => ({\n id: resource.id,\n name,\n path: resource.path,\n scope: scope as AutomationScope,\n triggerType: meta.triggerType,\n event: meta.event ?? null,\n schedule: meta.schedule || null,\n timezone: meta.schedule ? effectiveTimezone(meta.timezone) : null,\n scheduleDescription: meta.schedule\n ? describeCron(meta.schedule, effectiveTimezone(meta.timezone))\n : null,\n condition: meta.condition ?? null,\n body,\n enabled: meta.enabled,\n lastRun: meta.lastRun ?? null,\n lastCheck: meta.lastCheck ?? null,\n lastStatus: meta.lastStatus ?? null,\n lastError: meta.lastError ?? null,\n nextRun: nextRun(meta),\n createdBy: meta.createdBy ?? null,\n model: meta.model ?? null,\n mcpTools: meta.mcpTools ?? [],\n originScopeId: meta.originScopeId ?? null,\n deliveryPlatform: meta.deliveryPlatform ?? null,\n deliveryDestination: meta.deliveryDestination ?? null,\n deliveryThreadRef: meta.deliveryThreadRef ?? null,\n deliveryTenantId: meta.deliveryTenantId ?? null,\n canUpdate,\n }));\n },\n});\n"]}
@@ -3,15 +3,21 @@ declare const _default: import("../../action.js").ActionDefinition<{
3
3
  name: string;
4
4
  scope?: "organization" | "personal";
5
5
  enabled?: boolean;
6
+ schedule?: string;
7
+ timezone?: string;
6
8
  }, {
7
9
  deleted: boolean;
8
10
  name: string;
9
11
  enabled?: undefined;
12
+ schedule?: undefined;
13
+ timezone?: undefined;
10
14
  nextRun?: undefined;
11
15
  } | {
12
16
  deleted?: undefined;
13
17
  name: string;
14
18
  enabled: boolean;
19
+ schedule: string;
20
+ timezone: string;
15
21
  nextRun: string;
16
22
  }>;
17
23
  export default _default;
@@ -10,8 +10,10 @@ export default defineAction({
10
10
  name: z.string().min(1),
11
11
  scope: z.enum(["personal", "organization"]).default("personal"),
12
12
  enabled: z.boolean().optional(),
13
+ schedule: z.string().min(1).optional(),
14
+ timezone: z.string().min(1).optional(),
13
15
  }),
14
- run: async ({ operation, name, scope, enabled }, ctx) => {
16
+ run: async ({ operation, name, scope, enabled, schedule, timezone }, ctx) => {
15
17
  const userEmail = ctx?.userEmail;
16
18
  if (!userEmail)
17
19
  throw new Error("Not authenticated.");
@@ -21,20 +23,24 @@ export default defineAction({
21
23
  await refreshEventSubscriptions();
22
24
  return { deleted: true, name };
23
25
  }
24
- if (enabled === undefined) {
25
- throw Object.assign(new Error("enabled is required for update."), {
26
- statusCode: 400,
27
- });
26
+ if (enabled === undefined &&
27
+ schedule === undefined &&
28
+ timezone === undefined) {
29
+ throw Object.assign(new Error("enabled, schedule, or timezone is required for update."), { statusCode: 400 });
28
30
  }
29
31
  const definition = await updateAutomation(actor, {
30
32
  name,
31
33
  scope,
32
- enabled,
34
+ ...(enabled === undefined ? {} : { enabled }),
35
+ ...(schedule === undefined ? {} : { schedule }),
36
+ ...(timezone === undefined ? {} : { timezone }),
33
37
  });
34
38
  await refreshEventSubscriptions();
35
39
  return {
36
40
  name,
37
41
  enabled: definition.meta.enabled,
42
+ schedule: definition.meta.schedule || null,
43
+ timezone: definition.meta.timezone ?? null,
38
44
  nextRun: definition.meta.nextRun ?? null,
39
45
  };
40
46
  },
@@ -1 +1 @@
1
- {"version":3,"file":"manage-automation.js","sourceRoot":"","sources":["../../../src/triggers/actions/manage-automation.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAC/C,OAAO,EACL,gBAAgB,EAChB,gBAAgB,GACjB,MAAM,8BAA8B,CAAC;AACtC,OAAO,EAAE,yBAAyB,EAAE,MAAM,kBAAkB,CAAC;AAE7D,eAAe,YAAY,CAAC;IAC1B,WAAW,EACT,mGAAmG;IACrG,SAAS,EAAE,KAAK;IAChB,MAAM,EAAE,CAAC,CAAC,MAAM,CAAC;QACf,SAAS,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;QACvC,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;QACvB,KAAK,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,UAAU,EAAE,cAAc,CAAC,CAAC,CAAC,OAAO,CAAC,UAAU,CAAC;QAC/D,OAAO,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE;KAChC,CAAC;IACF,GAAG,EAAE,KAAK,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE,GAAG,EAAE,EAAE;QACtD,MAAM,SAAS,GAAG,GAAG,EAAE,SAAS,CAAC;QACjC,IAAI,CAAC,SAAS;YAAE,MAAM,IAAI,KAAK,CAAC,oBAAoB,CAAC,CAAC;QACtD,MAAM,KAAK,GAAG,EAAE,SAAS,EAAE,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,CAAC;QAE/C,IAAI,SAAS,KAAK,QAAQ,EAAE,CAAC;YAC3B,MAAM,gBAAgB,CAAC,KAAK,EAAE,KAAK,EAAE,IAAI,CAAC,CAAC;YAC3C,MAAM,yBAAyB,EAAE,CAAC;YAClC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;QACjC,CAAC;QACD,IAAI,OAAO,KAAK,SAAS,EAAE,CAAC;YAC1B,MAAM,MAAM,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,iCAAiC,CAAC,EAAE;gBAChE,UAAU,EAAE,GAAG;aAChB,CAAC,CAAC;QACL,CAAC;QACD,MAAM,UAAU,GAAG,MAAM,gBAAgB,CAAC,KAAK,EAAE;YAC/C,IAAI;YACJ,KAAK;YACL,OAAO;SACR,CAAC,CAAC;QACH,MAAM,yBAAyB,EAAE,CAAC;QAClC,OAAO;YACL,IAAI;YACJ,OAAO,EAAE,UAAU,CAAC,IAAI,CAAC,OAAO;YAChC,OAAO,EAAE,UAAU,CAAC,IAAI,CAAC,OAAO,IAAI,IAAI;SACzC,CAAC;IACJ,CAAC;CACF,CAAC,CAAC","sourcesContent":["import { z } from \"zod\";\n\nimport { defineAction } from \"../../action.js\";\nimport {\n deleteAutomation,\n updateAutomation,\n} from \"../../automations/service.js\";\nimport { refreshEventSubscriptions } from \"../dispatcher.js\";\n\nexport default defineAction({\n description:\n \"Enable, disable, or delete a personal or organization automation from the Agent Automations page.\",\n agentTool: false,\n schema: z.object({\n operation: z.enum([\"update\", \"delete\"]),\n name: z.string().min(1),\n scope: z.enum([\"personal\", \"organization\"]).default(\"personal\"),\n enabled: z.boolean().optional(),\n }),\n run: async ({ operation, name, scope, enabled }, ctx) => {\n const userEmail = ctx?.userEmail;\n if (!userEmail) throw new Error(\"Not authenticated.\");\n const actor = { userEmail, orgId: ctx?.orgId };\n\n if (operation === \"delete\") {\n await deleteAutomation(actor, scope, name);\n await refreshEventSubscriptions();\n return { deleted: true, name };\n }\n if (enabled === undefined) {\n throw Object.assign(new Error(\"enabled is required for update.\"), {\n statusCode: 400,\n });\n }\n const definition = await updateAutomation(actor, {\n name,\n scope,\n enabled,\n });\n await refreshEventSubscriptions();\n return {\n name,\n enabled: definition.meta.enabled,\n nextRun: definition.meta.nextRun ?? null,\n };\n },\n});\n"]}
1
+ {"version":3,"file":"manage-automation.js","sourceRoot":"","sources":["../../../src/triggers/actions/manage-automation.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAC/C,OAAO,EACL,gBAAgB,EAChB,gBAAgB,GACjB,MAAM,8BAA8B,CAAC;AACtC,OAAO,EAAE,yBAAyB,EAAE,MAAM,kBAAkB,CAAC;AAE7D,eAAe,YAAY,CAAC;IAC1B,WAAW,EACT,mGAAmG;IACrG,SAAS,EAAE,KAAK;IAChB,MAAM,EAAE,CAAC,CAAC,MAAM,CAAC;QACf,SAAS,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;QACvC,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;QACvB,KAAK,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,UAAU,EAAE,cAAc,CAAC,CAAC,CAAC,OAAO,CAAC,UAAU,CAAC;QAC/D,OAAO,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE;QAC/B,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,EAAE;QACtC,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,EAAE;KACvC,CAAC;IACF,GAAG,EAAE,KAAK,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,EAAE,GAAG,EAAE,EAAE;QAC1E,MAAM,SAAS,GAAG,GAAG,EAAE,SAAS,CAAC;QACjC,IAAI,CAAC,SAAS;YAAE,MAAM,IAAI,KAAK,CAAC,oBAAoB,CAAC,CAAC;QACtD,MAAM,KAAK,GAAG,EAAE,SAAS,EAAE,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,CAAC;QAE/C,IAAI,SAAS,KAAK,QAAQ,EAAE,CAAC;YAC3B,MAAM,gBAAgB,CAAC,KAAK,EAAE,KAAK,EAAE,IAAI,CAAC,CAAC;YAC3C,MAAM,yBAAyB,EAAE,CAAC;YAClC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;QACjC,CAAC;QACD,IACE,OAAO,KAAK,SAAS;YACrB,QAAQ,KAAK,SAAS;YACtB,QAAQ,KAAK,SAAS,EACtB,CAAC;YACD,MAAM,MAAM,CAAC,MAAM,CACjB,IAAI,KAAK,CAAC,wDAAwD,CAAC,EACnE,EAAE,UAAU,EAAE,GAAG,EAAE,CACpB,CAAC;QACJ,CAAC;QACD,MAAM,UAAU,GAAG,MAAM,gBAAgB,CAAC,KAAK,EAAE;YAC/C,IAAI;YACJ,KAAK;YACL,GAAG,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC;YAC7C,GAAG,CAAC,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC;YAC/C,GAAG,CAAC,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC;SAChD,CAAC,CAAC;QACH,MAAM,yBAAyB,EAAE,CAAC;QAClC,OAAO;YACL,IAAI;YACJ,OAAO,EAAE,UAAU,CAAC,IAAI,CAAC,OAAO;YAChC,QAAQ,EAAE,UAAU,CAAC,IAAI,CAAC,QAAQ,IAAI,IAAI;YAC1C,QAAQ,EAAE,UAAU,CAAC,IAAI,CAAC,QAAQ,IAAI,IAAI;YAC1C,OAAO,EAAE,UAAU,CAAC,IAAI,CAAC,OAAO,IAAI,IAAI;SACzC,CAAC;IACJ,CAAC;CACF,CAAC,CAAC","sourcesContent":["import { z } from \"zod\";\n\nimport { defineAction } from \"../../action.js\";\nimport {\n deleteAutomation,\n updateAutomation,\n} from \"../../automations/service.js\";\nimport { refreshEventSubscriptions } from \"../dispatcher.js\";\n\nexport default defineAction({\n description:\n \"Enable, disable, or delete a personal or organization automation from the Agent Automations page.\",\n agentTool: false,\n schema: z.object({\n operation: z.enum([\"update\", \"delete\"]),\n name: z.string().min(1),\n scope: z.enum([\"personal\", \"organization\"]).default(\"personal\"),\n enabled: z.boolean().optional(),\n schedule: z.string().min(1).optional(),\n timezone: z.string().min(1).optional(),\n }),\n run: async ({ operation, name, scope, enabled, schedule, timezone }, ctx) => {\n const userEmail = ctx?.userEmail;\n if (!userEmail) throw new Error(\"Not authenticated.\");\n const actor = { userEmail, orgId: ctx?.orgId };\n\n if (operation === \"delete\") {\n await deleteAutomation(actor, scope, name);\n await refreshEventSubscriptions();\n return { deleted: true, name };\n }\n if (\n enabled === undefined &&\n schedule === undefined &&\n timezone === undefined\n ) {\n throw Object.assign(\n new Error(\"enabled, schedule, or timezone is required for update.\"),\n { statusCode: 400 },\n );\n }\n const definition = await updateAutomation(actor, {\n name,\n scope,\n ...(enabled === undefined ? {} : { enabled }),\n ...(schedule === undefined ? {} : { schedule }),\n ...(timezone === undefined ? {} : { timezone }),\n });\n await refreshEventSubscriptions();\n return {\n name,\n enabled: definition.meta.enabled,\n schedule: definition.meta.schedule || null,\n timezone: definition.meta.timezone ?? null,\n nextRun: definition.meta.nextRun ?? null,\n };\n },\n});\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"actions.d.ts","sourceRoot":"","sources":["../../src/triggers/actions.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,8BAA8B,CAAC;AA8RhE,wBAAgB,2BAA2B,CACzC,cAAc,EAAE,MAAM,MAAM,GAC3B,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAqI7B"}
1
+ {"version":3,"file":"actions.d.ts","sourceRoot":"","sources":["../../src/triggers/actions.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,8BAA8B,CAAC;AAqShE,wBAAgB,2BAA2B,CACzC,cAAc,EAAE,MAAM,MAAM,GAC3B,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CA0I7B"}