@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
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Base vitest config that caps a suite's worker pool at a share of the machine.
3
+ *
4
+ * Vitest sizes its pool at `cores - 1` with no awareness of peer processes, so
5
+ * several suites running at once — concurrent agent sessions, or a workspace
6
+ * running packages in parallel — each try to claim the whole box. Set
7
+ * `VITEST_CONCURRENCY` to a percentage or a worker count to change the lid; a
8
+ * package that needs its own value sets `test.maxWorkers` in its own config,
9
+ * which wins the merge.
10
+ *
11
+ * This ships from core rather than living at the monorepo root because template
12
+ * directories are scaffolded out verbatim as standalone apps — a config that
13
+ * reaches above the template would resolve to nothing in the generated app.
14
+ *
15
+ * Keep this module free of runtime imports so loading it costs a config file
16
+ * nothing beyond reading the environment.
17
+ */
18
+ const DEFAULT_MAX_WORKERS = "25%";
19
+ const ENV_KEYS = ["VITEST_CONCURRENCY", "AGENT_NATIVE_VITEST_CONCURRENCY"];
20
+ /**
21
+ * Resolve the worker lid from the environment, throwing on anything that is
22
+ * neither a percentage nor a worker count. A bad value must not quietly become
23
+ * the default — that reads as "configured" while running at some other size.
24
+ */
25
+ export function resolveMaxWorkers(env = process.env) {
26
+ const rawMaxWorkers = env.VITEST_MAX_WORKERS;
27
+ if (rawMaxWorkers?.includes("%")) {
28
+ throw new Error(`VITEST_MAX_WORKERS=${rawMaxWorkers} is not a percentage to vitest — it parses as ` +
29
+ `${Number.parseInt(rawMaxWorkers)} workers. Use VITEST_CONCURRENCY for percentages, ` +
30
+ `or an integer for VITEST_MAX_WORKERS.`);
31
+ }
32
+ const key = ENV_KEYS.find((name) => env[name]);
33
+ if (!key)
34
+ return DEFAULT_MAX_WORKERS;
35
+ const value = env[key].trim();
36
+ if (/^\d+%$/.test(value)) {
37
+ const percent = Number.parseInt(value);
38
+ if (percent < 1 || percent > 100) {
39
+ throw new Error(`${key}=${value} is out of range — use 1% to 100%.`);
40
+ }
41
+ return value;
42
+ }
43
+ const count = Number(value);
44
+ if (!Number.isInteger(count) || count < 1) {
45
+ throw new Error(`${key}=${value} is not a percentage like "25%" or a worker count like "2".`);
46
+ }
47
+ return count;
48
+ }
49
+ const vitestBaseConfig = {
50
+ test: {
51
+ maxWorkers: resolveMaxWorkers(),
52
+ },
53
+ };
54
+ export default vitestBaseConfig;
55
+ //# sourceMappingURL=vitest-config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vitest-config.js","sourceRoot":"","sources":["../src/vitest-config.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;GAgBG;AAEH,MAAM,mBAAmB,GAAG,KAAK,CAAC;AAClC,MAAM,QAAQ,GAAG,CAAC,oBAAoB,EAAE,iCAAiC,CAAC,CAAC;AAE3E;;;;GAIG;AACH,MAAM,UAAU,iBAAiB,CAC/B,GAAG,GAAsB,OAAO,CAAC,GAAG;IAEpC,MAAM,aAAa,GAAG,GAAG,CAAC,kBAAkB,CAAC;IAC7C,IAAI,aAAa,EAAE,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;QACjC,MAAM,IAAI,KAAK,CACb,sBAAsB,aAAa,gDAAgD;YACjF,GAAG,MAAM,CAAC,QAAQ,CAAC,aAAa,CAAC,oDAAoD;YACrF,uCAAuC,CAC1C,CAAC;IACJ,CAAC;IAED,MAAM,GAAG,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC;IAC/C,IAAI,CAAC,GAAG;QAAE,OAAO,mBAAmB,CAAC;IAErC,MAAM,KAAK,GAAG,GAAG,CAAC,GAAG,CAAE,CAAC,IAAI,EAAE,CAAC;IAC/B,IAAI,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;QACzB,MAAM,OAAO,GAAG,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;QACvC,IAAI,OAAO,GAAG,CAAC,IAAI,OAAO,GAAG,GAAG,EAAE,CAAC;YACjC,MAAM,IAAI,KAAK,CAAC,GAAG,GAAG,IAAI,KAAK,oCAAoC,CAAC,CAAC;QACvE,CAAC;QACD,OAAO,KAAK,CAAC;IACf,CAAC;IAED,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;IAC5B,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC,IAAI,KAAK,GAAG,CAAC,EAAE,CAAC;QAC1C,MAAM,IAAI,KAAK,CACb,GAAG,GAAG,IAAI,KAAK,6DAA6D,CAC7E,CAAC;IACJ,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,MAAM,gBAAgB,GAAmB;IACvC,IAAI,EAAE;QACJ,UAAU,EAAE,iBAAiB,EAAE;KAChC;CACF,CAAC;AAEF,eAAe,gBAAgB,CAAC","sourcesContent":["import type { ViteUserConfig } from \"vitest/config\";\n\n/**\n * Base vitest config that caps a suite's worker pool at a share of the machine.\n *\n * Vitest sizes its pool at `cores - 1` with no awareness of peer processes, so\n * several suites running at once — concurrent agent sessions, or a workspace\n * running packages in parallel — each try to claim the whole box. Set\n * `VITEST_CONCURRENCY` to a percentage or a worker count to change the lid; a\n * package that needs its own value sets `test.maxWorkers` in its own config,\n * which wins the merge.\n *\n * This ships from core rather than living at the monorepo root because template\n * directories are scaffolded out verbatim as standalone apps — a config that\n * reaches above the template would resolve to nothing in the generated app.\n *\n * Keep this module free of runtime imports so loading it costs a config file\n * nothing beyond reading the environment.\n */\n\nconst DEFAULT_MAX_WORKERS = \"25%\";\nconst ENV_KEYS = [\"VITEST_CONCURRENCY\", \"AGENT_NATIVE_VITEST_CONCURRENCY\"];\n\n/**\n * Resolve the worker lid from the environment, throwing on anything that is\n * neither a percentage nor a worker count. A bad value must not quietly become\n * the default — that reads as \"configured\" while running at some other size.\n */\nexport function resolveMaxWorkers(\n env: NodeJS.ProcessEnv = process.env,\n): string | number {\n const rawMaxWorkers = env.VITEST_MAX_WORKERS;\n if (rawMaxWorkers?.includes(\"%\")) {\n throw new Error(\n `VITEST_MAX_WORKERS=${rawMaxWorkers} is not a percentage to vitest — it parses as ` +\n `${Number.parseInt(rawMaxWorkers)} workers. Use VITEST_CONCURRENCY for percentages, ` +\n `or an integer for VITEST_MAX_WORKERS.`,\n );\n }\n\n const key = ENV_KEYS.find((name) => env[name]);\n if (!key) return DEFAULT_MAX_WORKERS;\n\n const value = env[key]!.trim();\n if (/^\\d+%$/.test(value)) {\n const percent = Number.parseInt(value);\n if (percent < 1 || percent > 100) {\n throw new Error(`${key}=${value} is out of range — use 1% to 100%.`);\n }\n return value;\n }\n\n const count = Number(value);\n if (!Number.isInteger(count) || count < 1) {\n throw new Error(\n `${key}=${value} is not a percentage like \"25%\" or a worker count like \"2\".`,\n );\n }\n return count;\n}\n\nconst vitestBaseConfig: ViteUserConfig = {\n test: {\n maxWorkers: resolveMaxWorkers(),\n },\n};\n\nexport default vitestBaseConfig;\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agent-native/core",
3
- "version": "0.134.1",
3
+ "version": "0.135.1",
4
4
  "description": "Framework for agent-native application development — where AI agents and UI share SQL state, actions, and context",
5
5
  "homepage": "https://github.com/BuilderIO/agent-native#readme",
6
6
  "bugs": {
@@ -61,6 +61,7 @@
61
61
  "default": "./dist/index.js"
62
62
  },
63
63
  "./vite": "./dist/vite/index.js",
64
+ "./vitest-config": "./dist/vitest-config.js",
64
65
  "./server": "./dist/server/index.js",
65
66
  "./server/edge": "./dist/server/edge.js",
66
67
  "./server/entry-server": "./dist/server/entry-server.js",
@@ -395,8 +396,8 @@
395
396
  "y-protocols": "^1.0.7",
396
397
  "yjs": "^13.6.31",
397
398
  "zod": "^4.3.6",
398
- "@agent-native/recap-cli": "0.5.1",
399
- "@agent-native/toolkit": "^0.12.1"
399
+ "@agent-native/recap-cli": "0.5.2",
400
+ "@agent-native/toolkit": "^0.12.2"
400
401
  },
401
402
  "devDependencies": {
402
403
  "@ai-sdk/anthropic": "^3.0.71",
@@ -586,6 +587,6 @@
586
587
  "build": "tsc && tsc -p tsconfig.cli.json && node scripts/finalize-build.mjs && node scripts/check-dist-imports.mjs",
587
588
  "dev": "tsc --watch",
588
589
  "typecheck": "tsc --noEmit",
589
- "test": "vitest --run --dir src --maxWorkers=25%"
590
+ "test": "vitest --run --dir src"
590
591
  }
591
592
  }
@@ -1,11 +1,13 @@
1
1
  import { getDbExec } from "../db/client.js";
2
- import { isValidCron, nextOccurrence } from "../jobs/cron.js";
2
+ import { isValidCron, isValidTimezone, nextOccurrence } from "../jobs/cron.js";
3
3
  import {
4
4
  buildJobResourceContent,
5
5
  normalizeJobMcpTools,
6
6
  parseJobResource,
7
7
  type JobFrontmatter,
8
8
  } from "../jobs/frontmatter.js";
9
+ import { deleteAutomationRuns } from "../jobs/run-history.js";
10
+ import { resolveUserSchedulingTimezone } from "../localization/user-timezone.js";
9
11
  import {
10
12
  organizationIdFromResourceOwner,
11
13
  organizationResourceOwner,
@@ -49,6 +51,7 @@ export interface DefineAutomationInput {
49
51
  triggerType: "schedule" | "event";
50
52
  body: string;
51
53
  schedule?: string;
54
+ timezone?: string;
52
55
  event?: string;
53
56
  condition?: string;
54
57
  domain?: string;
@@ -68,6 +71,7 @@ export interface UpdateAutomationInput {
68
71
  condition?: string | null;
69
72
  delegatedPolicyId?: string | null;
70
73
  schedule?: string;
74
+ timezone?: string;
71
75
  model?: string | null;
72
76
  mcpTools?: unknown;
73
77
  }
@@ -309,9 +313,20 @@ export async function defineAutomation(
309
313
  throw httpError("event is required for event-triggered automations.", 400);
310
314
  }
311
315
 
316
+ if (input.timezone && !isValidTimezone(input.timezone)) {
317
+ throw httpError(`Unknown timezone "${input.timezone}".`, 400);
318
+ }
319
+ // Resolve now and persist it: a schedule whose zone is implicit means
320
+ // something different the moment it is read on a differently-zoned host.
321
+ const timezone =
322
+ input.triggerType === "schedule"
323
+ ? input.timezone || (await resolveUserSchedulingTimezone(actor.userEmail))
324
+ : undefined;
325
+
312
326
  const mcpTools = normalizeJobMcpTools(input.mcpTools);
313
327
  const meta: JobFrontmatter = {
314
328
  schedule: input.triggerType === "schedule" ? schedule : "",
329
+ timezone,
315
330
  enabled: true,
316
331
  triggerType: input.triggerType,
317
332
  event: input.triggerType === "event" ? event : undefined,
@@ -324,7 +339,7 @@ export async function defineAutomation(
324
339
  runAs: "creator",
325
340
  nextRun:
326
341
  input.triggerType === "schedule"
327
- ? nextOccurrence(schedule).toISOString()
342
+ ? nextOccurrence(schedule, undefined, timezone).toISOString()
328
343
  : undefined,
329
344
  model: input.model?.trim() || undefined,
330
345
  mcpTools: mcpTools?.length ? mcpTools : undefined,
@@ -365,7 +380,22 @@ export async function updateAutomation(
365
380
  throw httpError(`Invalid cron expression "${input.schedule}".`, 400);
366
381
  }
367
382
  meta.schedule = input.schedule;
368
- meta.nextRun = nextOccurrence(input.schedule).toISOString();
383
+ }
384
+ if (input.timezone !== undefined) {
385
+ if (!isValidTimezone(input.timezone)) {
386
+ throw httpError(`Unknown timezone "${input.timezone}".`, 400);
387
+ }
388
+ if (meta.triggerType !== "schedule") {
389
+ throw httpError("Event automations do not have a timezone.", 400);
390
+ }
391
+ meta.timezone = input.timezone;
392
+ }
393
+ if (input.schedule !== undefined || input.timezone !== undefined) {
394
+ meta.nextRun = nextOccurrence(
395
+ meta.schedule,
396
+ undefined,
397
+ meta.timezone,
398
+ ).toISOString();
369
399
  }
370
400
  if (input.enabled !== undefined) {
371
401
  meta.enabled = input.enabled;
@@ -374,7 +404,11 @@ export async function updateAutomation(
374
404
  meta.triggerType === "schedule" &&
375
405
  isValidCron(meta.schedule)
376
406
  ) {
377
- meta.nextRun = nextOccurrence(meta.schedule).toISOString();
407
+ meta.nextRun = nextOccurrence(
408
+ meta.schedule,
409
+ undefined,
410
+ meta.timezone,
411
+ ).toISOString();
378
412
  }
379
413
  }
380
414
  if (input.condition !== undefined) {
@@ -417,6 +451,9 @@ export async function deleteAutomation(
417
451
  );
418
452
  }
419
453
  await resourceDelete(definition.resource.id);
454
+ // Names are reusable, so leaving history behind would attach these runs to
455
+ // whatever automation is created under the same name next.
456
+ await deleteAutomationRuns(definition.resource.owner, name);
420
457
  }
421
458
 
422
459
  export interface AutomationExecutionIdentity {
@@ -259,6 +259,24 @@ export function configPathFor(
259
259
  // JSON client configs (Claude Code, Claude Code CLI, Cowork)
260
260
  // ---------------------------------------------------------------------------
261
261
 
262
+ /**
263
+ * Read an existing MCP config without conflating missing and unreadable files.
264
+ * Only ENOENT means the config is absent; any other read failure must abort
265
+ * before a caller can overwrite existing user configuration.
266
+ */
267
+ function readExistingConfigFile(file: string): string | undefined {
268
+ try {
269
+ return fs.readFileSync(file, "utf-8");
270
+ } catch (error) {
271
+ if ((error as NodeJS.ErrnoException).code === "ENOENT") return undefined;
272
+ const detail = error instanceof Error ? ` (${error.message})` : "";
273
+ throw new Error(
274
+ `Cannot read MCP config file: ${file}\n` +
275
+ `Check that the file is readable and re-run. The file has not been modified.${detail}`,
276
+ );
277
+ }
278
+ }
279
+
262
280
  /**
263
281
  * Read and parse a JSON config file.
264
282
  *
@@ -269,13 +287,8 @@ export function configPathFor(
269
287
  * file with only the new MCP entry (data-loss hazard).
270
288
  */
271
289
  function readJsonFile(file: string): Record<string, any> {
272
- let raw: string;
273
- try {
274
- raw = fs.readFileSync(file, "utf-8");
275
- } catch {
276
- // Missing (ENOENT) or unreadable file — treat as empty.
277
- return {};
278
- }
290
+ const raw = readExistingConfigFile(file);
291
+ if (raw === undefined) return {};
279
292
  if (!raw.trim()) return {};
280
293
  try {
281
294
  const parsed = JSON.parse(raw);
@@ -576,12 +589,7 @@ export function writeCodexBlock(
576
589
  name: string,
577
590
  block: string | null,
578
591
  ): void {
579
- let content = "";
580
- try {
581
- content = fs.readFileSync(file, "utf-8");
582
- } catch {
583
- content = "";
584
- }
592
+ const content = readExistingConfigFile(file) ?? "";
585
593
 
586
594
  const lines = content.split(/\r?\n/);
587
595
  const out: string[] = [];
@@ -616,14 +624,12 @@ export function writeCodexBlock(
616
624
  }
617
625
 
618
626
  export function codexHasBlock(file: string, name: string): boolean {
619
- try {
620
- const content = fs.readFileSync(file, "utf-8");
621
- return content
622
- .split(/\r?\n/)
623
- .some((line) => codexServerNameOfHeader(line) === name);
624
- } catch {
625
- return false;
626
- }
627
+ const content = readExistingConfigFile(file);
628
+ return (
629
+ content
630
+ ?.split(/\r?\n/)
631
+ .some((line) => codexServerNameOfHeader(line) === name) ?? false
632
+ );
627
633
  }
628
634
 
629
635
  // ---------------------------------------------------------------------------
@@ -3,8 +3,10 @@ import {
3
3
  IconBolt,
4
4
  IconCalendarEvent,
5
5
  IconClock,
6
+ IconAlertTriangle,
6
7
  IconEye,
7
8
  IconLoader2,
9
+ IconPencil,
8
10
  IconPlayerPause,
9
11
  IconPlayerPlay,
10
12
  IconTrash,
@@ -24,6 +26,11 @@ import { useFormatters, useT } from "../i18n.js";
24
26
  import { automationCreationContext } from "../settings/AutomationsSection.js";
25
27
  import { AgentEmptyState } from "./AgentEmptyState.js";
26
28
  import { AgentTabFrame } from "./AgentTabFrame.js";
29
+ import {
30
+ AutomationDetailsDialog,
31
+ type AutomationDetailsField,
32
+ } from "./AutomationDetailsDialog.js";
33
+ import { AutomationScheduleDialog } from "./AutomationScheduleDialog.js";
27
34
  import type { AgentPageTabProps } from "./types.js";
28
35
  import {
29
36
  useAutomations,
@@ -62,6 +69,101 @@ function listAutomations(automations: Automation[]): ListedAutomation[] {
62
69
  }));
63
70
  }
64
71
 
72
+ type Translate = ReturnType<typeof useT>;
73
+
74
+ function describeTrigger(entry: ListedAutomation, t: Translate): string {
75
+ if (entry.kind === "automation" && entry.triggerType === "event") {
76
+ return t("jobs.automationEventDetails", {
77
+ defaultValue: "Runs when {{event}}.",
78
+ event: entry.resource.event ?? "an event fires",
79
+ });
80
+ }
81
+ return (
82
+ entry.resource.scheduleDescription ||
83
+ entry.resource.schedule ||
84
+ t("jobs.scheduledTrigger", { defaultValue: "Scheduled" })
85
+ );
86
+ }
87
+
88
+ function detailsFields(
89
+ entry: ListedAutomation,
90
+ t: Translate,
91
+ formatDateTime: (value: string | null) => string | null,
92
+ ): AutomationDetailsField[] {
93
+ const resource = entry.resource;
94
+ const unset = t("jobs.notSet", { defaultValue: "—" });
95
+ const fields: AutomationDetailsField[] = [
96
+ {
97
+ label: t("jobs.status", { defaultValue: "Status" }),
98
+ value: resource.enabled
99
+ ? t("jobs.enabled", { defaultValue: "Enabled" })
100
+ : t("jobs.paused", { defaultValue: "Paused" }),
101
+ },
102
+ {
103
+ label: t("jobs.trigger", { defaultValue: "Trigger" }),
104
+ value:
105
+ entry.triggerType === "event"
106
+ ? t("jobs.eventTrigger", { defaultValue: "Event-triggered" })
107
+ : t("jobs.scheduledTrigger", { defaultValue: "Scheduled" }),
108
+ },
109
+ ];
110
+
111
+ if (entry.triggerType === "schedule") {
112
+ fields.push(
113
+ {
114
+ label: t("jobs.cronExpression", { defaultValue: "Cron expression" }),
115
+ value: resource.schedule || unset,
116
+ mono: true,
117
+ },
118
+ {
119
+ label: t("jobs.timezone", { defaultValue: "Timezone" }),
120
+ value: resource.timezone || unset,
121
+ },
122
+ );
123
+ }
124
+
125
+ fields.push(
126
+ {
127
+ label: t("jobs.nextRun", { defaultValue: "Next run" }),
128
+ value: formatDateTime(resource.nextRun) ?? unset,
129
+ },
130
+ {
131
+ label: t("jobs.lastRun", { defaultValue: "Last run" }),
132
+ value:
133
+ formatDateTime(resource.lastRun) ??
134
+ t("jobs.neverRan", { defaultValue: "Never" }),
135
+ },
136
+ {
137
+ label: t("jobs.lastChecked", { defaultValue: "Last checked" }),
138
+ value: formatDateTime(resource.lastCheck) ?? unset,
139
+ },
140
+ {
141
+ label: t("jobs.lastStatus", { defaultValue: "Last status" }),
142
+ value: resource.lastStatus || unset,
143
+ },
144
+ {
145
+ label: t("jobs.scope", { defaultValue: "Scope" }),
146
+ value:
147
+ resource.scope === "organization"
148
+ ? t("jobs.organization", { defaultValue: "Organization" })
149
+ : t("jobs.personal", { defaultValue: "Personal" }),
150
+ },
151
+ {
152
+ label: t("jobs.createdBy", { defaultValue: "Created by" }),
153
+ value: resource.createdBy || unset,
154
+ },
155
+ );
156
+
157
+ if (entry.kind === "automation") {
158
+ fields.push({
159
+ label: t("jobs.model", { defaultValue: "Model" }),
160
+ value: entry.resource.model || unset,
161
+ });
162
+ }
163
+
164
+ return fields;
165
+ }
166
+
65
167
  export function organizationAutomationCreationContext(): string {
66
168
  return "The user wants to create a new organization automation. Use manage-automations with action=define and scope=organization to create it. Ask clarifying questions if needed about whether it runs on a schedule or event, any conditions, and what actions to take.";
67
169
  }
@@ -83,6 +185,9 @@ export function AgentJobsTab({ canManageOrg = false }: AgentPageTabProps) {
83
185
  const [detailsTarget, setDetailsTarget] = useState<ListedAutomation | null>(
84
186
  null,
85
187
  );
188
+ const [scheduleTarget, setScheduleTarget] = useState<ListedAutomation | null>(
189
+ null,
190
+ );
86
191
 
87
192
  const formatDateTime = (value: string | null) => {
88
193
  if (!value || Number.isNaN(new Date(value).getTime())) return null;
@@ -111,14 +216,14 @@ export function AgentJobsTab({ canManageOrg = false }: AgentPageTabProps) {
111
216
  const mutateEntry = (
112
217
  entry: ListedAutomation,
113
218
  operation: "update" | "delete",
114
- enabled?: boolean,
219
+ patch?: { enabled?: boolean; schedule?: string },
115
220
  onSuccess?: () => void,
116
221
  ) => {
117
222
  const input = {
118
223
  operation,
119
224
  name: entry.resource.name,
120
225
  scope: entry.resource.scope,
121
- ...(enabled === undefined ? {} : { enabled }),
226
+ ...patch,
122
227
  };
123
228
  const options = onSuccess ? { onSuccess } : undefined;
124
229
 
@@ -243,6 +348,7 @@ export function AgentJobsTab({ canManageOrg = false }: AgentPageTabProps) {
243
348
  {entries.map((entry) => {
244
349
  const resource = entry.resource;
245
350
  const lastRun = formatDateTime(resource.lastRun);
351
+ const lastCheck = formatDateTime(resource.lastCheck);
246
352
  const nextRun = formatDateTime(resource.nextRun);
247
353
  const triggerDescription =
248
354
  entry.kind === "automation" && entry.triggerType === "event"
@@ -310,7 +416,7 @@ export function AgentJobsTab({ canManageOrg = false }: AgentPageTabProps) {
310
416
  <p className="mt-1 line-clamp-2 text-xs text-muted-foreground/80">
311
417
  {instructions}
312
418
  </p>
313
- {lastRun || nextRun ? (
419
+ {lastRun || nextRun || lastCheck ? (
314
420
  <div className="mt-2 flex flex-wrap gap-x-4 gap-y-1 text-[11px] text-muted-foreground">
315
421
  {nextRun ? (
316
422
  <span>
@@ -318,14 +424,29 @@ export function AgentJobsTab({ canManageOrg = false }: AgentPageTabProps) {
318
424
  {nextRun}
319
425
  </span>
320
426
  ) : null}
321
- {lastRun ? (
427
+ <span>
428
+ {t("jobs.lastRun", { defaultValue: "Last run" })}:{" "}
429
+ {lastRun ??
430
+ t("jobs.neverRan", { defaultValue: "Never" })}
431
+ </span>
432
+ {!lastRun && lastCheck ? (
322
433
  <span>
323
- {t("jobs.lastRun", { defaultValue: "Last run" })}:{" "}
324
- {lastRun}
434
+ {t("jobs.lastChecked", {
435
+ defaultValue: "Last checked",
436
+ })}
437
+ : {lastCheck}
325
438
  </span>
326
439
  ) : null}
327
440
  </div>
328
441
  ) : null}
442
+ {resource.lastError ? (
443
+ <p className="mt-2 flex items-start gap-1.5 text-[11px] text-destructive">
444
+ <IconAlertTriangle className="mt-px size-3 shrink-0" />
445
+ <span className="min-w-0 break-words">
446
+ {resource.lastError}
447
+ </span>
448
+ </p>
449
+ ) : null}
329
450
  </div>
330
451
  <div className="flex shrink-0 items-center gap-1">
331
452
  <Button
@@ -340,6 +461,19 @@ export function AgentJobsTab({ canManageOrg = false }: AgentPageTabProps) {
340
461
  </Button>
341
462
  {resource.canUpdate ? (
342
463
  <>
464
+ {entry.triggerType === "schedule" ? (
465
+ <Button
466
+ type="button"
467
+ variant="ghost"
468
+ size="sm"
469
+ className="cursor-pointer px-2 text-xs"
470
+ disabled={mutationPending}
471
+ onClick={() => setScheduleTarget(entry)}
472
+ >
473
+ <IconPencil className="size-3.5" />
474
+ {t("jobs.edit", { defaultValue: "Edit" })}
475
+ </Button>
476
+ ) : null}
343
477
  <Button
344
478
  type="button"
345
479
  variant="ghost"
@@ -347,7 +481,9 @@ export function AgentJobsTab({ canManageOrg = false }: AgentPageTabProps) {
347
481
  className="cursor-pointer px-2 text-xs"
348
482
  disabled={mutationPending}
349
483
  onClick={() =>
350
- mutateEntry(entry, "update", !resource.enabled)
484
+ mutateEntry(entry, "update", {
485
+ enabled: !resource.enabled,
486
+ })
351
487
  }
352
488
  >
353
489
  {resource.enabled ? (
@@ -505,84 +641,50 @@ export function AgentJobsTab({ canManageOrg = false }: AgentPageTabProps) {
505
641
  </DialogContent>
506
642
  </Dialog>
507
643
 
508
- <Dialog
509
- open={detailsTarget !== null}
510
- onOpenChange={(open) => {
511
- if (!open) setDetailsTarget(null);
512
- }}
513
- >
514
- <DialogContent className="max-w-2xl">
515
- <DialogHeader>
516
- <DialogTitle>
517
- {detailsTarget?.resource.name.replace(/-/g, " ") ??
518
- t("jobs.automationDetails", {
519
- defaultValue: "Automation details",
520
- })}
521
- </DialogTitle>
522
- <DialogDescription>
523
- {detailsTarget
524
- ? detailsTarget.triggerType === "event"
525
- ? t("jobs.automationEventDetails", {
526
- defaultValue: "Runs when {{event}}.",
527
- event:
528
- detailsTarget.kind === "automation"
529
- ? (detailsTarget.resource.event ?? "an event fires")
530
- : "an event fires",
531
- })
532
- : detailsTarget.resource.scheduleDescription ||
533
- detailsTarget.resource.schedule ||
534
- t("jobs.scheduledTrigger", {
535
- defaultValue: "Scheduled",
536
- })
537
- : null}
538
- </DialogDescription>
539
- </DialogHeader>
540
- {detailsTarget ? (
541
- <div className="space-y-3">
542
- {detailsTarget.kind === "automation" &&
543
- detailsTarget.resource.condition ? (
544
- <div>
545
- <p className="text-xs font-medium text-foreground">
546
- {t("jobs.condition", { defaultValue: "Condition" })}
547
- </p>
548
- <p className="mt-1 text-sm text-muted-foreground">
549
- {detailsTarget.resource.condition}
550
- </p>
551
- </div>
552
- ) : null}
553
- <div>
554
- <p className="text-xs font-medium text-foreground">
555
- {t("jobs.instructions", { defaultValue: "Instructions" })}
556
- </p>
557
- <p className="mt-1 whitespace-pre-wrap text-sm text-muted-foreground">
558
- {detailsTarget.kind === "automation"
559
- ? detailsTarget.resource.body
560
- : detailsTarget.resource.instructions}
561
- </p>
562
- </div>
563
- {(detailsTarget.resource.mcpTools ?? []).length > 0 ? (
564
- <div>
565
- <p className="text-xs font-medium text-foreground">
566
- {t("jobs.mcpTools", {
567
- defaultValue: "Connected MCP tools",
568
- })}
569
- </p>
570
- <div className="mt-1 flex flex-wrap gap-1.5">
571
- {(detailsTarget.resource.mcpTools ?? []).map((toolName) => (
572
- <code
573
- key={toolName}
574
- className="rounded bg-muted px-1.5 py-0.5 text-[11px] text-muted-foreground"
575
- >
576
- {toolName}
577
- </code>
578
- ))}
579
- </div>
580
- </div>
581
- ) : null}
582
- </div>
583
- ) : null}
584
- </DialogContent>
585
- </Dialog>
644
+ {detailsTarget ? (
645
+ <AutomationDetailsDialog
646
+ open
647
+ name={detailsTarget.resource.name}
648
+ scope={
649
+ detailsTarget.resource.scope === "organization" ? "org" : "user"
650
+ }
651
+ triggerSummary={describeTrigger(detailsTarget, t)}
652
+ fields={detailsFields(detailsTarget, t, formatDateTime)}
653
+ condition={
654
+ detailsTarget.kind === "automation"
655
+ ? detailsTarget.resource.condition
656
+ : null
657
+ }
658
+ instructions={
659
+ detailsTarget.kind === "automation"
660
+ ? detailsTarget.resource.body
661
+ : detailsTarget.resource.instructions
662
+ }
663
+ mcpTools={detailsTarget.resource.mcpTools ?? []}
664
+ lastError={detailsTarget.resource.lastError}
665
+ formatTimestamp={(value) =>
666
+ formatDateTime(new Date(value).toISOString()) ?? String(value)
667
+ }
668
+ onClose={() => setDetailsTarget(null)}
669
+ />
670
+ ) : null}
671
+
672
+ {scheduleTarget ? (
673
+ <AutomationScheduleDialog
674
+ open
675
+ name={scheduleTarget.resource.name}
676
+ schedule={scheduleTarget.resource.schedule ?? ""}
677
+ timezone={scheduleTarget.resource.timezone ?? null}
678
+ saving={mutationPending}
679
+ error={mutationError ? mutationError.message : null}
680
+ onCancel={() => setScheduleTarget(null)}
681
+ onSave={(next) =>
682
+ mutateEntry(scheduleTarget, "update", next, () =>
683
+ setScheduleTarget(null),
684
+ )
685
+ }
686
+ />
687
+ ) : null}
586
688
  </AgentTabFrame>
587
689
  );
588
690
  }