@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
@@ -10,6 +10,8 @@ import {
10
10
  import { TeamPage } from "@agent-native/core/client/org";
11
11
  import {
12
12
  AccountSettingsCard,
13
+ SettingsGroup,
14
+ SettingsRow,
13
15
  SettingsTabsPage,
14
16
  useAgentSettingsTabs,
15
17
  useBuilderConnectFlow,
@@ -36,6 +38,7 @@ import {
36
38
  type FormEvent,
37
39
  type ReactNode,
38
40
  } from "react";
41
+ import { toast } from "sonner";
39
42
 
40
43
  import { PageShell } from "@/components/layout/PageShell";
41
44
  import { Button } from "@/components/ui/button";
@@ -56,6 +59,8 @@ import {
56
59
  SelectTrigger,
57
60
  SelectValue,
58
61
  } from "@/components/ui/select";
62
+ import { Switch } from "@/components/ui/switch";
63
+ import { useAssetsPrefs } from "@/hooks/use-assets-prefs";
59
64
  import { cn } from "@/lib/utils";
60
65
 
61
66
  import changelog from "../../CHANGELOG.md?raw";
@@ -81,6 +86,7 @@ export default function SettingsPage() {
81
86
  const { data } = useActionQuery("list-libraries", { compact: true }) as {
82
87
  data?: { count?: number };
83
88
  };
89
+ const { prefs, loading: prefsLoading, save: savePrefs } = useAssetsPrefs();
84
90
 
85
91
  const generalSearchEntries = useMemo<SettingsSearchEntry[]>(
86
92
  () => [
@@ -90,6 +96,12 @@ export default function SettingsPage() {
90
96
  keywords: "language locale translation i18n",
91
97
  hash: "language",
92
98
  },
99
+ {
100
+ id: "assets-notifications",
101
+ label: t("settings.emailNotifications"),
102
+ keywords: "email notification generation finished failed alert",
103
+ hash: "notifications",
104
+ },
93
105
  {
94
106
  id: "assets-generation-setup",
95
107
  label: t("settings.setupTitle"),
@@ -125,20 +137,41 @@ export default function SettingsPage() {
125
137
  </p>
126
138
  </div>
127
139
 
128
- <Card id="language" className="scroll-mt-4">
129
- <CardHeader>
130
- <CardTitle className="text-base">
131
- {t("settings.languageTitle")}
132
- </CardTitle>
133
- <CardDescription>
134
- {t("settings.languageDescription")}
135
- </CardDescription>
136
- </CardHeader>
137
- <CardContent className="max-w-xs space-y-1.5">
138
- <Label>{t("settings.languageLabel")}</Label>
139
- <LanguagePicker label={t("settings.languageLabel")} />
140
- </CardContent>
141
- </Card>
140
+ <SettingsGroup className="scroll-mt-4">
141
+ <SettingsRow
142
+ id="language"
143
+ label={t("settings.languageTitle")}
144
+ description={t("settings.languageDescription")}
145
+ control={
146
+ <div className="w-56">
147
+ <LanguagePicker label={t("settings.languageLabel")} />
148
+ </div>
149
+ }
150
+ />
151
+ <SettingsRow
152
+ id="notifications"
153
+ label={t("settings.emailNotifications")}
154
+ description={t("settings.emailNotificationsDescription")}
155
+ control={
156
+ <Switch
157
+ aria-label={t("settings.emailNotifications")}
158
+ checked={prefs.emailNotifications !== false}
159
+ disabled={prefsLoading}
160
+ onCheckedChange={(checked) => {
161
+ savePrefs({ emailNotifications: checked }).catch(
162
+ (err) => {
163
+ toast.error(
164
+ err instanceof Error
165
+ ? err.message
166
+ : t("settings.saveFailed"),
167
+ );
168
+ },
169
+ );
170
+ }}
171
+ />
172
+ }
173
+ />
174
+ </SettingsGroup>
142
175
 
143
176
  <section id="asset-generation-setup" className="scroll-mt-4">
144
177
  <AssetsSetupCard libraryCount={data?.count ?? 0} />
@@ -243,8 +276,9 @@ function AssetsSetupCard({ libraryCount }: { libraryCount: number }) {
243
276
 
244
277
  <CardContent className="p-0">
245
278
  <SettingsRow
279
+ className="border-b border-border/70 last:border-b-0"
246
280
  icon={<IconKey className="size-4" />}
247
- title="Builder"
281
+ label="Builder"
248
282
  description={
249
283
  builderConnected
250
284
  ? orgName
@@ -261,7 +295,7 @@ function AssetsSetupCard({ libraryCount }: { libraryCount: number }) {
261
295
  : t("settings.optional")}
262
296
  </StatusPill>
263
297
  }
264
- action={
298
+ control={
265
299
  builderEnabled ? (
266
300
  <Button
267
301
  type="button"
@@ -295,8 +329,9 @@ function AssetsSetupCard({ libraryCount }: { libraryCount: number }) {
295
329
  {setupIssue ? <SetupIssueCallout message={setupIssue} /> : null}
296
330
 
297
331
  <SettingsRow
332
+ className="border-b border-border/70 last:border-b-0"
298
333
  icon={<IconPhoto className="size-4" />}
299
- title={t("settings.generation")}
334
+ label={t("settings.generation")}
300
335
  description={generationSummary(configData, builderConnected, t)}
301
336
  status={
302
337
  <StatusPill tone={generationReady ? "ready" : "attention"}>
@@ -305,7 +340,7 @@ function AssetsSetupCard({ libraryCount }: { libraryCount: number }) {
305
340
  : t("settings.generationNeedsSetup")}
306
341
  </StatusPill>
307
342
  }
308
- action={
343
+ control={
309
344
  generationStep ? (
310
345
  <DisclosureButton
311
346
  open={manualGenerationOpen}
@@ -326,8 +361,9 @@ function AssetsSetupCard({ libraryCount }: { libraryCount: number }) {
326
361
  ) : null}
327
362
 
328
363
  <SettingsRow
364
+ className="border-b border-border/70 last:border-b-0"
329
365
  icon={<IconCloudUpload className="size-4" />}
330
- title={t("settings.storage")}
366
+ label={t("settings.storage")}
331
367
  description={
332
368
  storageReady
333
369
  ? t("settings.storageReady")
@@ -340,7 +376,7 @@ function AssetsSetupCard({ libraryCount }: { libraryCount: number }) {
340
376
  : t("settings.generationNeedsSetup")}
341
377
  </StatusPill>
342
378
  }
343
- action={
379
+ control={
344
380
  storageStep ? (
345
381
  <DisclosureButton
346
382
  open={manualStorageOpen}
@@ -361,8 +397,9 @@ function AssetsSetupCard({ libraryCount }: { libraryCount: number }) {
361
397
  ) : null}
362
398
 
363
399
  <SettingsRow
400
+ className="border-b border-border/70 last:border-b-0"
364
401
  icon={<IconLibraryPhoto className="size-4" />}
365
- title={t("settings.brandKits")}
402
+ label={t("settings.brandKits")}
366
403
  description={`${libraryCount} accessible ${
367
404
  libraryCount === 1 ? "brand kit" : "brand kits"
368
405
  }.`}
@@ -375,40 +412,6 @@ function AssetsSetupCard({ libraryCount }: { libraryCount: number }) {
375
412
  );
376
413
  }
377
414
 
378
- function SettingsRow({
379
- icon,
380
- title,
381
- description,
382
- status,
383
- action,
384
- }: {
385
- icon: ReactNode;
386
- title: string;
387
- description: string;
388
- status: ReactNode;
389
- action?: ReactNode;
390
- }) {
391
- return (
392
- <div className="flex flex-col gap-3 border-b border-border/70 px-5 py-4 last:border-b-0 sm:flex-row sm:items-center sm:justify-between">
393
- <div className="flex min-w-0 gap-3">
394
- <div className="mt-0.5 flex size-8 shrink-0 items-center justify-center rounded-md border border-border bg-background text-muted-foreground">
395
- {icon}
396
- </div>
397
- <div className="min-w-0">
398
- <div className="flex flex-wrap items-center gap-2">
399
- <h3 className="text-sm font-medium">{title}</h3>
400
- {status}
401
- </div>
402
- <p className="mt-1 max-w-xl text-sm leading-6 text-muted-foreground">
403
- {description}
404
- </p>
405
- </div>
406
- </div>
407
- {action ? <div className="shrink-0 sm:ms-4">{action}</div> : null}
408
- </div>
409
- );
410
- }
411
-
412
415
  function StatusPill({
413
416
  children,
414
417
  tone,
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: added
3
+ date: 2026-08-01
4
+ ---
5
+
6
+ Get an email when a generation you started finishes or fails, with a new toggle in Settings
@@ -0,0 +1,89 @@
1
+ /**
2
+ * "Your generation finished" notifications.
3
+ *
4
+ * Only terminal transitions that happen *out of band* call this — a polled video
5
+ * run landing, or a stale image run being declared interrupted. Synchronous
6
+ * `generate-image` hands the result straight back to the caller, so emailing
7
+ * there would mail a user who is already looking at the image.
8
+ */
9
+ import { notifyWithDelivery } from "@agent-native/core/notifications";
10
+ import { getUserSetting } from "@agent-native/core/settings";
11
+
12
+ import {
13
+ ASSETS_USER_PREFS_KEY,
14
+ type AssetsUserPrefs,
15
+ } from "../../shared/assets-user-prefs.js";
16
+ import type { schema } from "../db/index.js";
17
+
18
+ type GenerationRun = typeof schema.assetGenerationRuns.$inferSelect;
19
+
20
+ export type GenerationRunNotificationStatus =
21
+ | "no-recipient"
22
+ | "notified"
23
+ | "notification-error";
24
+
25
+ export interface GenerationRunNotificationResult {
26
+ status: GenerationRunNotificationStatus;
27
+ emailed: boolean;
28
+ }
29
+
30
+ async function wantsEmail(email: string): Promise<boolean> {
31
+ const stored = await getUserSetting(email, ASSETS_USER_PREFS_KEY);
32
+ if (!stored || typeof stored !== "object" || Array.isArray(stored)) {
33
+ // No stored preference is a real state, and its meaning is "opted in".
34
+ return true;
35
+ }
36
+ return (stored as AssetsUserPrefs).emailNotifications !== false;
37
+ }
38
+
39
+ function summarize(run: GenerationRun): string {
40
+ const kind = run.mediaType === "video" ? "Video" : "Image";
41
+ const prompt = run.prompt.trim();
42
+ return prompt.length > 80
43
+ ? `${kind}: ${prompt.slice(0, 77)}…`
44
+ : `${kind}: ${prompt}`;
45
+ }
46
+
47
+ export async function notifyGenerationRunFinished(
48
+ run: GenerationRun,
49
+ outcome: "completed" | "failed",
50
+ ): Promise<GenerationRunNotificationResult> {
51
+ const owner = run.ownerEmail?.trim().toLowerCase();
52
+ if (!owner) return { status: "no-recipient", emailed: false };
53
+
54
+ try {
55
+ const emailed = await wantsEmail(owner);
56
+ const failed = outcome === "failed";
57
+ const title = failed ? "Generation failed" : "Generation finished";
58
+ const body = failed
59
+ ? `${summarize(run)}\n\n${run.error ?? "The run ended without a result."}`
60
+ : summarize(run);
61
+
62
+ await notifyWithDelivery(
63
+ {
64
+ severity: failed ? "warning" : "info",
65
+ title,
66
+ body,
67
+ channels: emailed ? ["inbox", "email"] : ["inbox"],
68
+ metadata: {
69
+ kind: "generation_run",
70
+ runId: run.id,
71
+ libraryId: run.libraryId,
72
+ outcome,
73
+ // The email channel is a no-op without explicit recipients.
74
+ ...(emailed ? { emailRecipients: [owner], emailSubject: title } : {}),
75
+ },
76
+ },
77
+ { owner },
78
+ );
79
+ return { status: "notified", emailed };
80
+ } catch (err) {
81
+ // A generation that produced an asset must not be reported as failed just
82
+ // because the mail server was down.
83
+ console.error(
84
+ `[assets] Could not notify ${owner} that run ${run.id} ${outcome}:`,
85
+ err instanceof Error ? err.message : String(err),
86
+ );
87
+ return { status: "notification-error", emailed: false };
88
+ }
89
+ }
@@ -2,6 +2,7 @@ import { eq } from "drizzle-orm";
2
2
 
3
3
  import { getDb, schema } from "../db/index.js";
4
4
  import { createAssetFromBuffer } from "./assets.js";
5
+ import { notifyGenerationRunFinished } from "./generation-run-notifications.js";
5
6
  import { nowIso, parseJson, stringifyJson } from "./json.js";
6
7
  import { pollGeminiVideoGeneration } from "./video-generation.js";
7
8
 
@@ -59,6 +60,7 @@ async function markRunCompletedWithAsset(
59
60
  metadata: nextRun.metadata,
60
61
  })
61
62
  .where(eq(schema.assetGenerationRuns.id, run.id));
63
+ await notifyGenerationRunFinished(nextRun, "completed");
62
64
  return nextRun;
63
65
  }
64
66
 
@@ -217,14 +219,19 @@ export async function completeVideoGenerationRun(
217
219
  } catch (err) {
218
220
  const message =
219
221
  err instanceof Error ? err.message : "Video generation failed.";
222
+ const completedAt = nowIso();
220
223
  await getDb()
221
224
  .update(schema.assetGenerationRuns)
222
225
  .set({
223
226
  status: "failed",
224
227
  error: message,
225
- completedAt: nowIso(),
228
+ completedAt,
226
229
  })
227
230
  .where(eq(schema.assetGenerationRuns.id, run.id));
231
+ await notifyGenerationRunFinished(
232
+ { ...run, status: "failed", error: message, completedAt },
233
+ "failed",
234
+ );
228
235
  throw err;
229
236
  }
230
237
  }
@@ -0,0 +1,17 @@
1
+ import { getSession } from "@agent-native/core/server";
2
+ import { getUserSetting } from "@agent-native/core/settings";
3
+ import { defineEventHandler, setResponseStatus } from "h3";
4
+
5
+ import { ASSETS_USER_PREFS_KEY } from "../../../../shared/assets-user-prefs.js";
6
+
7
+ export default defineEventHandler(async (event) => {
8
+ const session = await getSession(event);
9
+ if (!session?.email) {
10
+ setResponseStatus(event, 401);
11
+ return { error: "unauthorized" };
12
+ }
13
+
14
+ const stored = await getUserSetting(session.email, ASSETS_USER_PREFS_KEY);
15
+ // coercion-ok: a null read means this user has no preferences yet, which is a real state.
16
+ return stored ?? {};
17
+ });
@@ -0,0 +1,36 @@
1
+ import { getSession } from "@agent-native/core/server";
2
+ import { getUserSetting, putUserSetting } from "@agent-native/core/settings";
3
+ import { defineEventHandler, getHeader, readBody, setResponseStatus } from "h3";
4
+
5
+ import { ASSETS_USER_PREFS_KEY } from "../../../../shared/assets-user-prefs.js";
6
+
7
+ export default defineEventHandler(async (event) => {
8
+ const session = await getSession(event);
9
+ if (!session?.email) {
10
+ setResponseStatus(event, 401);
11
+ return { error: "unauthorized" };
12
+ }
13
+
14
+ // coercion-ok: an unparsable body is rejected with 400 immediately below.
15
+ const body = await readBody(event).catch(() => null);
16
+ if (!body || typeof body !== "object" || Array.isArray(body)) {
17
+ setResponseStatus(event, 400);
18
+ return { error: "Invalid settings payload" };
19
+ }
20
+
21
+ // Merge so a partial save never wipes preferences written elsewhere.
22
+ const stored = await getUserSetting(session.email, ASSETS_USER_PREFS_KEY);
23
+ // coercion-ok: a null read means this user has no preferences yet, which is a real state.
24
+ const existing = stored ?? {};
25
+ const next = {
26
+ ...(typeof existing === "object" && existing && !Array.isArray(existing)
27
+ ? existing
28
+ : {}),
29
+ ...(body as Record<string, unknown>),
30
+ };
31
+
32
+ await putUserSetting(session.email, ASSETS_USER_PREFS_KEY, next, {
33
+ requestSource: getHeader(event, "x-request-source") || undefined,
34
+ });
35
+ return next;
36
+ });
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Per-user Assets preferences, stored under one user-setting key so Settings
3
+ * and the generation pipeline agree on a single source of truth.
4
+ */
5
+ export const ASSETS_USER_PREFS_KEY = "assets-user-prefs";
6
+
7
+ export type AssetsUserPrefs = {
8
+ /** Email me when a generation run I started finishes or fails. */
9
+ emailNotifications?: boolean;
10
+ };
@@ -1,16 +1,20 @@
1
1
  import path from "node:path";
2
2
 
3
- import { defineConfig } from "vitest/config";
3
+ import baseConfig from "@agent-native/core/vitest-config";
4
+ import { defineConfig, mergeConfig } from "vitest/config";
4
5
 
5
- export default defineConfig({
6
- resolve: {
7
- alias: {
8
- "@": path.resolve(__dirname, "./app"),
9
- "@shared": path.resolve(__dirname, "./shared"),
6
+ export default mergeConfig(
7
+ baseConfig,
8
+ defineConfig({
9
+ resolve: {
10
+ alias: {
11
+ "@": path.resolve(__dirname, "./app"),
12
+ "@shared": path.resolve(__dirname, "./shared"),
13
+ },
10
14
  },
11
- },
12
- test: {
13
- include: ["**/*.{test,spec}.?(c|m)[jt]s?(x)"],
14
- exclude: ["**/node_modules/**", "**/.git/**", "**/dist/**"],
15
- },
16
- });
15
+ test: {
16
+ include: ["**/*.{test,spec}.?(c|m)[jt]s?(x)"],
17
+ exclude: ["**/node_modules/**", "**/.git/**", "**/dist/**"],
18
+ },
19
+ }),
20
+ );
@@ -7,6 +7,8 @@ import { LanguagePicker, useT } from "@agent-native/core/client/i18n";
7
7
  import { TeamPage } from "@agent-native/core/client/org";
8
8
  import {
9
9
  AccountSettingsCard,
10
+ SettingsGroup,
11
+ SettingsRow,
10
12
  SettingsTabsPage,
11
13
  useAgentSettingsTabs,
12
14
  type SettingsSearchEntry,
@@ -717,21 +719,19 @@ export default function SettingsRoute() {
717
719
  </main>
718
720
 
719
721
  <aside className="grid content-start gap-5">
720
- <Card id="language" className="scroll-mt-4">
721
- <CardHeader>
722
- <CardTitle className="flex items-center gap-2 text-base">
723
- <IconAdjustments className="size-4 text-primary" />
724
- {t("settings.languageTitle")}
725
- </CardTitle>
726
- <CardDescription>
727
- {t("settings.languageDescription")}
728
- </CardDescription>
729
- </CardHeader>
730
- <CardContent className="space-y-1.5">
731
- <Label>{t("settings.languageLabel")}</Label>
732
- <LanguagePicker label={t("settings.languageLabel")} />
733
- </CardContent>
734
- </Card>
722
+ <SettingsGroup className="scroll-mt-4">
723
+ <SettingsRow
724
+ id="language"
725
+ icon={<IconAdjustments className="text-primary" />}
726
+ label={t("settings.languageTitle")}
727
+ description={t("settings.languageDescription")}
728
+ control={
729
+ <div className="w-48">
730
+ <LanguagePicker label={t("settings.languageLabel")} />
731
+ </div>
732
+ }
733
+ />
734
+ </SettingsGroup>
735
735
 
736
736
  <Card>
737
737
  <CardHeader>
@@ -1,13 +1,17 @@
1
- import { defineConfig } from "vitest/config";
1
+ import baseConfig from "@agent-native/core/vitest-config";
2
+ import { defineConfig, mergeConfig } from "vitest/config";
2
3
 
3
- export default defineConfig({
4
- test: {
5
- passWithNoTests: true,
6
- exclude: [
7
- "**/node_modules/**",
8
- "**/dist/**",
9
- "**/.output/**",
10
- "**/.{idea,git,cache,output,temp}/**",
11
- ],
12
- },
13
- });
4
+ export default mergeConfig(
5
+ baseConfig,
6
+ defineConfig({
7
+ test: {
8
+ passWithNoTests: true,
9
+ exclude: [
10
+ "**/node_modules/**",
11
+ "**/dist/**",
12
+ "**/.output/**",
13
+ "**/.{idea,git,cache,output,temp}/**",
14
+ ],
15
+ },
16
+ }),
17
+ );
@@ -4,6 +4,8 @@ import { LanguagePicker, useT } from "@agent-native/core/client/i18n";
4
4
  import { TeamPage } from "@agent-native/core/client/org";
5
5
  import {
6
6
  AccountSettingsCard,
7
+ SettingsGroup,
8
+ SettingsRow,
7
9
  SettingsTabsPage,
8
10
  useAgentSettingsTabs,
9
11
  type SettingsSearchEntry,
@@ -209,20 +211,36 @@ export default function Settings() {
209
211
  {t("settings.description")}
210
212
  </p>
211
213
 
212
- <Card id="language" className="scroll-mt-16">
213
- <CardHeader>
214
- <CardTitle className="text-lg">
215
- {t("settings.languageTitle")}
216
- </CardTitle>
217
- <CardDescription>
218
- {t("settings.languageDescription")}
219
- </CardDescription>
220
- </CardHeader>
221
- <CardContent className="max-w-xs space-y-1.5">
222
- <Label>{t("settings.languageLabel")}</Label>
223
- <LanguagePicker label={t("settings.languageLabel")} />
224
- </CardContent>
225
- </Card>
214
+ <SettingsGroup>
215
+ <SettingsRow
216
+ id="language"
217
+ label={t("settings.languageTitle")}
218
+ description={t("settings.languageDescription")}
219
+ control={
220
+ <div className="w-56">
221
+ <LanguagePicker label={t("settings.languageLabel")} />
222
+ </div>
223
+ }
224
+ />
225
+ <SettingsRow
226
+ id="appearance"
227
+ label={t("settings.appearance")}
228
+ description={t("settings.appearanceDescription")}
229
+ >
230
+ <AppearancePicker
231
+ onChange={(preset: AppearancePresetId) => {
232
+ // Persist server-side so the choice survives reload and syncs
233
+ // across devices; the local UI has already updated optimistically.
234
+ callAction(
235
+ "change-appearance" as any,
236
+ { preset } as any,
237
+ ).catch(() => {
238
+ // Server write failed; the local DOM change still stands.
239
+ });
240
+ }}
241
+ />
242
+ </SettingsRow>
243
+ </SettingsGroup>
226
244
 
227
245
  {/* Google Calendar Connection */}
228
246
  <Card id="google-calendar" className="scroll-mt-16">
@@ -460,32 +478,6 @@ export default function Settings() {
460
478
  </div>
461
479
  </CardContent>
462
480
  </Card>
463
-
464
- {/* Appearance */}
465
- <Card id="appearance" className="scroll-mt-16">
466
- <CardHeader>
467
- <CardTitle className="text-lg">
468
- {t("settings.appearance")}
469
- </CardTitle>
470
- <CardDescription>
471
- {t("settings.appearanceDescription")}
472
- </CardDescription>
473
- </CardHeader>
474
- <CardContent>
475
- <AppearancePicker
476
- onChange={(preset: AppearancePresetId) => {
477
- // Persist server-side so the choice survives reload and syncs
478
- // across devices; the local UI has already updated optimistically.
479
- callAction(
480
- "change-appearance" as any,
481
- { preset } as any,
482
- ).catch(() => {
483
- // Server write failed; the local DOM change still stands.
484
- });
485
- }}
486
- />
487
- </CardContent>
488
- </Card>
489
481
  </div>
490
482
  }
491
483
  team={
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: improved
3
+ date: 2026-08-01
4
+ ---
5
+
6
+ Language and appearance now sit together in one Preferences card in Settings
@@ -1,16 +1,20 @@
1
1
  import path from "node:path";
2
2
 
3
- import { defineConfig } from "vitest/config";
3
+ import baseConfig from "@agent-native/core/vitest-config";
4
+ import { defineConfig, mergeConfig } from "vitest/config";
4
5
 
5
- export default defineConfig({
6
- resolve: {
7
- alias: {
8
- "@": path.resolve(__dirname, "./app"),
9
- "@shared": path.resolve(__dirname, "./shared"),
6
+ export default mergeConfig(
7
+ baseConfig,
8
+ defineConfig({
9
+ resolve: {
10
+ alias: {
11
+ "@": path.resolve(__dirname, "./app"),
12
+ "@shared": path.resolve(__dirname, "./shared"),
13
+ },
10
14
  },
11
- },
12
- test: {
13
- include: ["**/*.{test,spec}.?(c|m)[jt]s?(x)"],
14
- exclude: ["**/node_modules/**", "**/.git/**", "**/dist/**"],
15
- },
16
- });
15
+ test: {
16
+ include: ["**/*.{test,spec}.?(c|m)[jt]s?(x)"],
17
+ exclude: ["**/node_modules/**", "**/.git/**", "**/dist/**"],
18
+ },
19
+ }),
20
+ );