@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.
- package/corpus/README.md +3 -3
- package/corpus/core/CHANGELOG.md +113 -0
- package/corpus/core/package.json +3 -2
- package/corpus/core/src/automations/service.ts +41 -4
- package/corpus/core/src/cli/mcp-config-writers.ts +27 -21
- package/corpus/core/src/client/agent-page/AgentJobsTab.tsx +187 -85
- package/corpus/core/src/client/agent-page/AutomationDetailsDialog.tsx +208 -0
- package/corpus/core/src/client/agent-page/AutomationScheduleDialog.tsx +180 -0
- package/corpus/core/src/client/agent-page/TimezoneSelect.tsx +241 -0
- package/corpus/core/src/client/agent-page/use-jobs.ts +50 -10
- package/corpus/core/src/client/org/OrgSwitcher.tsx +26 -0
- package/corpus/core/src/client/settings/AccountSettingsCard.tsx +35 -31
- package/corpus/core/src/client/settings/SchedulingTimezoneField.tsx +71 -0
- package/corpus/core/src/client/settings/SettingsRow.tsx +113 -0
- package/corpus/core/src/client/settings/agent-settings-search.ts +2 -1
- package/corpus/core/src/client/settings/index.ts +6 -0
- package/corpus/core/src/integrations/identity.ts +2 -12
- package/corpus/core/src/jobs/actions/list-automation-runs.ts +33 -0
- package/corpus/core/src/jobs/actions/list-recurring-jobs.ts +27 -6
- package/corpus/core/src/jobs/actions/manage-recurring-job.ts +41 -9
- package/corpus/core/src/jobs/background-automation-runner.ts +88 -0
- package/corpus/core/src/jobs/cron.ts +52 -6
- package/corpus/core/src/jobs/frontmatter.ts +20 -0
- package/corpus/core/src/jobs/run-history.ts +242 -0
- package/corpus/core/src/jobs/scheduler.ts +73 -18
- package/corpus/core/src/jobs/tools.ts +54 -6
- package/corpus/core/src/localization/actions/get-localization-preference.ts +2 -2
- package/corpus/core/src/localization/actions/set-localization-preference.ts +49 -11
- package/corpus/core/src/localization/default-messages.ts +27 -0
- package/corpus/core/src/localization/shared.ts +39 -6
- package/corpus/core/src/localization/user-timezone.ts +36 -0
- package/corpus/core/src/org/app-roles-handlers.ts +1 -9
- package/corpus/core/src/org/index.ts +2 -0
- package/corpus/core/src/org/membership.ts +18 -0
- package/corpus/core/src/review/actions/create-review-comment.ts +4 -1
- package/corpus/core/src/review/actions/reply-review-comment.ts +4 -1
- package/corpus/core/src/review/index.ts +5 -0
- package/corpus/core/src/review/notifications.ts +163 -0
- package/corpus/core/src/review/types.ts +7 -0
- package/corpus/core/src/server/action-discovery.ts +4 -0
- package/corpus/core/src/server/activity-notifications.ts +174 -0
- package/corpus/core/src/server/app-name.ts +31 -0
- package/corpus/core/src/server/better-auth-instance.ts +4 -4
- package/corpus/core/src/server/email-templates.ts +70 -14
- package/corpus/core/src/server/email.ts +66 -3
- package/corpus/core/src/server/index.ts +10 -0
- package/corpus/core/src/sharing/access.ts +44 -2
- package/corpus/core/src/sharing/index.ts +5 -0
- package/corpus/core/src/sharing/recipients.ts +67 -0
- package/corpus/core/src/triggers/actions/list-automations.ts +27 -8
- package/corpus/core/src/triggers/actions/manage-automation.ts +17 -6
- package/corpus/core/src/triggers/actions.ts +16 -4
- package/corpus/core/src/triggers/dispatcher.ts +44 -30
- package/corpus/core/src/triggers/routes.ts +19 -6
- package/corpus/core/src/vitest-config.ts +68 -0
- package/corpus/core/vitest.config.ts +5 -0
- package/corpus/templates/analytics/app/pages/Settings.tsx +37 -53
- package/corpus/templates/analytics/changelog/2026-08-01-new-error-alerts-now-reach-you-by-email-and-a-scheduled-dash.md +6 -0
- package/corpus/templates/analytics/server/jobs/dashboard-report.ts +43 -0
- package/corpus/templates/analytics/server/lib/error-capture.ts +5 -1
- package/corpus/templates/analytics/vitest.config.ts +16 -12
- package/corpus/templates/assets/actions/refresh-generation-run.ts +3 -0
- package/corpus/templates/assets/app/components/ui/switch.tsx +1 -0
- package/corpus/templates/assets/app/hooks/use-assets-prefs.ts +55 -0
- package/corpus/templates/assets/app/i18n-data.ts +30 -0
- package/corpus/templates/assets/app/routes/settings.tsx +58 -55
- package/corpus/templates/assets/changelog/2026-08-01-get-an-email-when-a-generation-you-started-finishes-or-fails.md +6 -0
- package/corpus/templates/assets/server/lib/generation-run-notifications.ts +89 -0
- package/corpus/templates/assets/server/lib/video-runs.ts +8 -1
- package/corpus/templates/assets/server/routes/_agent-native/assets/user-prefs.get.ts +17 -0
- package/corpus/templates/assets/server/routes/_agent-native/assets/user-prefs.put.ts +36 -0
- package/corpus/templates/assets/shared/assets-user-prefs.ts +10 -0
- package/corpus/templates/assets/vitest.config.ts +16 -12
- package/corpus/templates/brain/app/routes/settings.tsx +15 -15
- package/corpus/templates/brain/vitest.config.ts +16 -12
- package/corpus/templates/calendar/app/pages/Settings.tsx +32 -40
- package/corpus/templates/calendar/changelog/2026-08-01-language-and-appearance-now-sit-together-in-one-preferences-.md +6 -0
- package/corpus/templates/calendar/vitest.config.ts +16 -12
- package/corpus/templates/chat/app/routes/settings.tsx +14 -22
- package/corpus/templates/chat/vitest.config.ts +9 -0
- package/corpus/templates/clips/.agents/skills/video-editing/SKILL.md +31 -0
- package/corpus/templates/clips/AGENTS.md +1 -0
- package/corpus/templates/clips/actions/add-comment.ts +12 -1
- package/corpus/templates/clips/actions/create-meeting.ts +2 -2
- package/corpus/templates/clips/actions/create-recording.ts +2 -2
- package/corpus/templates/clips/actions/finalize-recording.ts +42 -6
- package/corpus/templates/clips/actions/generate-filmstrip.ts +154 -0
- package/corpus/templates/clips/actions/get-recording-player-data.ts +6 -0
- package/corpus/templates/clips/actions/import-loom-recording.ts +2 -2
- package/corpus/templates/clips/actions/lib/ensure-recording-filmstrip.ts +258 -0
- package/corpus/templates/clips/actions/lib/ensure-seekable-video.ts +11 -3
- package/corpus/templates/clips/actions/react-to-comment.ts +13 -0
- package/corpus/templates/clips/actions/react-to-recording.ts +10 -1
- package/corpus/templates/clips/actions/reply-to-comment.ts +12 -1
- package/corpus/templates/clips/actions/stitch-recordings.ts +2 -2
- package/corpus/templates/clips/app/components/editor/editor-layout.tsx +152 -23
- package/corpus/templates/clips/app/components/editor/waveform.tsx +237 -75
- package/corpus/templates/clips/app/components/recorder/recorder-engine.ts +23 -1
- package/corpus/templates/clips/app/components/settings/ai-setup-section.tsx +441 -0
- package/corpus/templates/clips/app/components/settings/slack-section.tsx +329 -0
- package/corpus/templates/clips/app/components/settings/types.ts +14 -0
- package/corpus/templates/clips/app/components/settings/video-storage-section.tsx +443 -0
- package/corpus/templates/clips/app/hooks/use-secret-status.ts +69 -0
- package/corpus/templates/clips/app/i18n/en-US.ts +9 -1
- package/corpus/templates/clips/app/lib/video-filmstrip.ts +262 -0
- package/corpus/templates/clips/app/routes/_app.settings._index.tsx +165 -1351
- package/corpus/templates/clips/changelog/2026-07-27-saved-clips-now-resume-from-the-last-uploaded-byte-after-an-.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-31-editor-timeline-now-shows-video-frames-behind-the-waveform-g.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-31-email-notifications-now-go-out-when-someone-comments-on-or-r.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-31-you-can-now-set-a-default-visibility-for-new-recordings-in-s.md +6 -0
- package/corpus/templates/clips/changelog/2026-08-01-email-notifications-for-comments-and-reactions-now-actually-.md +6 -0
- package/corpus/templates/clips/changelog/2026-08-03-the-desktop-app-now-opens-directly-beneath-its-tray-icon-on-.md +6 -0
- package/corpus/templates/clips/chrome-extension/package.json +1 -0
- package/corpus/templates/clips/chrome-extension/vitest.config.ts +9 -0
- package/corpus/templates/clips/desktop/package.json +1 -0
- package/corpus/templates/clips/desktop/src/app.tsx +17 -1
- package/corpus/templates/clips/desktop/src/lib/recorder.ts +331 -36
- package/corpus/templates/clips/desktop/src/lib/upload-recovery.ts +129 -0
- package/corpus/templates/clips/desktop/src-tauri/src/native_screen.rs +815 -15
- package/corpus/templates/clips/desktop/src-tauri/src/tray.rs +61 -11
- package/corpus/templates/clips/desktop/vitest.config.ts +9 -0
- package/corpus/templates/clips/server/db/schema.ts +17 -0
- package/corpus/templates/clips/server/jobs/media-verification.ts +4 -0
- package/corpus/templates/clips/server/lib/activity-notifications.ts +185 -0
- package/corpus/templates/clips/server/lib/recording-media-cleanup.ts +2 -0
- package/corpus/templates/clips/server/lib/recording-upload-state.ts +46 -9
- package/corpus/templates/clips/server/lib/recordings.ts +24 -0
- package/corpus/templates/clips/server/lib/resumable-session.ts +10 -4
- package/corpus/templates/clips/server/lib/transactional-email-templates.ts +109 -0
- package/corpus/templates/clips/server/lib/upload-interruption.ts +8 -0
- package/corpus/templates/clips/server/lib/upload-lease.ts +50 -4
- package/corpus/templates/clips/server/lib/video-filmstrip-sprite.ts +207 -0
- package/corpus/templates/clips/server/lib/video-remux.ts +13 -4
- package/corpus/templates/clips/server/plugins/db.ts +16 -0
- package/corpus/templates/clips/server/routes/api/_agent-native-background/post-finalize-worker.post.ts +4 -0
- package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/abort.post.ts +123 -36
- package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/chunk.post.ts +159 -29
- package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/interrupt.post.ts +197 -0
- package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/reset-chunks.post.ts +193 -39
- package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/resume.get.ts +191 -12
- package/corpus/templates/clips/shared/clips-ai-prefs.ts +6 -0
- package/corpus/templates/clips/shared/feature-flags.ts +12 -0
- package/corpus/templates/clips/shared/recording-core.ts +4 -0
- package/corpus/templates/clips/vitest.config.ts +23 -19
- package/corpus/templates/content/actions/_database-utils.ts +113 -4
- package/corpus/templates/content/actions/add-comment.ts +15 -1
- package/corpus/templates/content/actions/get-document.ts +84 -3
- package/corpus/templates/content/actions/list-documents.ts +1 -0
- package/corpus/templates/content/app/components/editor/BuilderBodySyncingNotice.tsx +9 -2
- package/corpus/templates/content/app/components/editor/DocumentEditor.tsx +34 -15
- package/corpus/templates/content/app/components/editor/DocumentToolbar.tsx +55 -13
- package/corpus/templates/content/app/components/editor/body-hydration.ts +12 -6
- package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +2 -3
- package/corpus/templates/content/app/components/sidebar/DocumentSidebar.tsx +5 -12
- package/corpus/templates/content/app/components/ui/switch.tsx +1 -0
- package/corpus/templates/content/app/hooks/use-content-database.ts +16 -27
- package/corpus/templates/content/app/hooks/use-content-prefs.ts +55 -0
- package/corpus/templates/content/app/hooks/use-create-page.ts +3 -6
- package/corpus/templates/content/app/hooks/use-document-properties.ts +9 -16
- package/corpus/templates/content/app/hooks/use-document-versions.ts +3 -3
- package/corpus/templates/content/app/hooks/use-documents.ts +85 -29
- package/corpus/templates/content/app/hooks/use-notion.ts +15 -13
- package/corpus/templates/content/app/i18n-data.ts +72 -1
- package/corpus/templates/content/app/lib/document-query.ts +36 -0
- package/corpus/templates/content/app/routes/_app.settings.tsx +47 -18
- package/corpus/templates/content/changelog/2026-07-30-breadcrumb-menus-now-hide-internal-system-pages.md +6 -0
- package/corpus/templates/content/changelog/2026-08-01-get-an-email-when-someone-comments-on-replies-in-or-mentions.md +6 -0
- package/corpus/templates/content/changelog/2026-08-02-pages-opened-from-a-database-now-keep-that-database-s-fields.md +6 -0
- package/corpus/templates/content/server/lib/comment-notifications.ts +146 -0
- package/corpus/templates/content/server/lib/document-context.ts +11 -6
- package/corpus/templates/content/server/routes/_agent-native/content/user-prefs.get.ts +17 -0
- package/corpus/templates/content/server/routes/_agent-native/content/user-prefs.put.ts +36 -0
- package/corpus/templates/content/shared/api.ts +8 -0
- package/corpus/templates/content/shared/content-user-prefs.ts +11 -0
- package/corpus/templates/content/vitest.config.ts +20 -16
- package/corpus/templates/crm/app/routes/settings.tsx +14 -22
- package/corpus/templates/crm/vitest.config.ts +8 -4
- package/corpus/templates/design/app/routes/settings.tsx +14 -22
- package/corpus/templates/design/changelog/2026-08-01-review-comments-replies-and-mentions-now-send-an-email-to-th.md +6 -0
- package/corpus/templates/design/vitest.config.ts +18 -12
- package/corpus/templates/dispatch/app/routes/settings.tsx +26 -40
- package/corpus/templates/dispatch/vitest.config.ts +11 -7
- package/corpus/templates/factory/app/routes/settings.tsx +14 -22
- package/corpus/templates/factory/vitest.config.ts +9 -0
- package/corpus/templates/forms/app/routes/_app.settings.tsx +14 -22
- package/corpus/templates/forms/vitest.config.ts +27 -23
- package/corpus/templates/macros/app/routes/settings.tsx +14 -22
- package/corpus/templates/macros/vitest.config.ts +16 -12
- package/corpus/templates/mail/app/pages/SettingsPage.tsx +14 -16
- package/corpus/templates/mail/vitest.config.ts +16 -12
- package/corpus/templates/plan/app/routes/settings.tsx +30 -44
- package/corpus/templates/plan/vitest.config.ts +24 -20
- package/corpus/templates/slides/actions/add-slide-comment.ts +12 -1
- package/corpus/templates/slides/app/components/ui/switch.tsx +1 -0
- package/corpus/templates/slides/app/hooks/use-slides-prefs.ts +55 -0
- package/corpus/templates/slides/app/i18n/en-US.ts +4 -0
- package/corpus/templates/slides/app/routes/settings.tsx +45 -22
- package/corpus/templates/slides/changelog/2026-08-01-get-an-email-when-someone-comments-on-or-replies-in-your-dec.md +6 -0
- package/corpus/templates/slides/server/lib/comment-notifications.ts +182 -0
- package/corpus/templates/slides/server/routes/_agent-native/slides/user-prefs.get.ts +16 -0
- package/corpus/templates/slides/server/routes/_agent-native/slides/user-prefs.put.ts +36 -0
- package/corpus/templates/slides/shared/slides-user-prefs.ts +11 -0
- package/corpus/templates/slides/vitest.config.ts +17 -13
- package/corpus/templates/tasks/app/components/layout/Sidebar.tsx +2 -0
- package/corpus/templates/tasks/app/routes/settings.tsx +14 -22
- package/corpus/templates/tasks/vitest.config.ts +17 -13
- package/corpus/toolkit/CHANGELOG.md +9 -0
- package/corpus/toolkit/package.json +1 -1
- package/corpus/toolkit/vitest.config.ts +3 -0
- package/dist/automations/service.d.ts +2 -0
- package/dist/automations/service.d.ts.map +1 -1
- package/dist/automations/service.js +29 -4
- package/dist/automations/service.js.map +1 -1
- package/dist/cli/mcp-config-writers.d.ts.map +1 -1
- package/dist/cli/mcp-config-writers.js +24 -23
- package/dist/cli/mcp-config-writers.js.map +1 -1
- package/dist/client/agent-page/AgentJobsTab.d.ts.map +1 -1
- package/dist/client/agent-page/AgentJobsTab.js +86 -29
- package/dist/client/agent-page/AgentJobsTab.js.map +1 -1
- package/dist/client/agent-page/AutomationDetailsDialog.d.ts +21 -0
- package/dist/client/agent-page/AutomationDetailsDialog.d.ts.map +1 -0
- package/dist/client/agent-page/AutomationDetailsDialog.js +33 -0
- package/dist/client/agent-page/AutomationDetailsDialog.js.map +1 -0
- package/dist/client/agent-page/AutomationScheduleDialog.d.ts +15 -0
- package/dist/client/agent-page/AutomationScheduleDialog.d.ts.map +1 -0
- package/dist/client/agent-page/AutomationScheduleDialog.js +46 -0
- package/dist/client/agent-page/AutomationScheduleDialog.js.map +1 -0
- package/dist/client/agent-page/TimezoneSelect.d.ts +17 -0
- package/dist/client/agent-page/TimezoneSelect.d.ts.map +1 -0
- package/dist/client/agent-page/TimezoneSelect.js +107 -0
- package/dist/client/agent-page/TimezoneSelect.js.map +1 -0
- package/dist/client/agent-page/use-jobs.d.ts +20 -7
- package/dist/client/agent-page/use-jobs.d.ts.map +1 -1
- package/dist/client/agent-page/use-jobs.js +24 -4
- package/dist/client/agent-page/use-jobs.js.map +1 -1
- package/dist/client/org/OrgSwitcher.d.ts +8 -1
- package/dist/client/org/OrgSwitcher.d.ts.map +1 -1
- package/dist/client/org/OrgSwitcher.js +8 -2
- package/dist/client/org/OrgSwitcher.js.map +1 -1
- package/dist/client/settings/AccountSettingsCard.d.ts.map +1 -1
- package/dist/client/settings/AccountSettingsCard.js +10 -9
- package/dist/client/settings/AccountSettingsCard.js.map +1 -1
- package/dist/client/settings/SchedulingTimezoneField.d.ts +10 -0
- package/dist/client/settings/SchedulingTimezoneField.d.ts.map +1 -0
- package/dist/client/settings/SchedulingTimezoneField.js +30 -0
- package/dist/client/settings/SchedulingTimezoneField.js.map +1 -0
- package/dist/client/settings/SettingsRow.d.ts +36 -0
- package/dist/client/settings/SettingsRow.d.ts.map +1 -0
- package/dist/client/settings/SettingsRow.js +15 -0
- package/dist/client/settings/SettingsRow.js.map +1 -0
- package/dist/client/settings/agent-settings-search.d.ts.map +1 -1
- package/dist/client/settings/agent-settings-search.js +1 -1
- package/dist/client/settings/agent-settings-search.js.map +1 -1
- package/dist/client/settings/index.d.ts +1 -0
- package/dist/client/settings/index.d.ts.map +1 -1
- package/dist/client/settings/index.js +1 -0
- package/dist/client/settings/index.js.map +1 -1
- package/dist/file-upload/actions/upload-image.d.ts +1 -1
- package/dist/integrations/identity.d.ts.map +1 -1
- package/dist/integrations/identity.js +2 -11
- package/dist/integrations/identity.js.map +1 -1
- package/dist/jobs/actions/list-automation-runs.d.ts +8 -0
- package/dist/jobs/actions/list-automation-runs.d.ts.map +1 -0
- package/dist/jobs/actions/list-automation-runs.js +29 -0
- package/dist/jobs/actions/list-automation-runs.js.map +1 -0
- package/dist/jobs/actions/list-recurring-jobs.d.ts +2 -0
- package/dist/jobs/actions/list-recurring-jobs.d.ts.map +1 -1
- package/dist/jobs/actions/list-recurring-jobs.js +20 -7
- package/dist/jobs/actions/list-recurring-jobs.js.map +1 -1
- package/dist/jobs/actions/manage-recurring-job.d.ts +2 -0
- package/dist/jobs/actions/manage-recurring-job.js +30 -9
- package/dist/jobs/actions/manage-recurring-job.js.map +1 -1
- package/dist/jobs/background-automation-runner.d.ts.map +1 -1
- package/dist/jobs/background-automation-runner.js +60 -0
- package/dist/jobs/background-automation-runner.js.map +1 -1
- package/dist/jobs/cron.d.ts +16 -2
- package/dist/jobs/cron.d.ts.map +1 -1
- package/dist/jobs/cron.js +43 -6
- package/dist/jobs/cron.js.map +1 -1
- package/dist/jobs/frontmatter.d.ts +12 -0
- package/dist/jobs/frontmatter.d.ts.map +1 -1
- package/dist/jobs/frontmatter.js +8 -0
- package/dist/jobs/frontmatter.js.map +1 -1
- package/dist/jobs/run-history.d.ts +56 -0
- package/dist/jobs/run-history.d.ts.map +1 -0
- package/dist/jobs/run-history.js +169 -0
- package/dist/jobs/run-history.js.map +1 -0
- package/dist/jobs/scheduler.d.ts.map +1 -1
- package/dist/jobs/scheduler.js +51 -18
- package/dist/jobs/scheduler.js.map +1 -1
- package/dist/jobs/tools.d.ts.map +1 -1
- package/dist/jobs/tools.js +37 -6
- package/dist/jobs/tools.js.map +1 -1
- package/dist/localization/actions/get-localization-preference.d.ts +1 -2
- package/dist/localization/actions/get-localization-preference.d.ts.map +1 -1
- package/dist/localization/actions/get-localization-preference.js.map +1 -1
- package/dist/localization/actions/set-localization-preference.d.ts +3 -3
- package/dist/localization/actions/set-localization-preference.d.ts.map +1 -1
- package/dist/localization/actions/set-localization-preference.js +33 -8
- package/dist/localization/actions/set-localization-preference.js.map +1 -1
- package/dist/localization/default-messages.d.ts +25 -0
- package/dist/localization/default-messages.d.ts.map +1 -1
- package/dist/localization/default-messages.js +25 -0
- package/dist/localization/default-messages.js.map +1 -1
- package/dist/localization/shared.d.ts +16 -1
- package/dist/localization/shared.d.ts.map +1 -1
- package/dist/localization/shared.js +22 -4
- package/dist/localization/shared.js.map +1 -1
- package/dist/localization/user-timezone.d.ts +11 -0
- package/dist/localization/user-timezone.d.ts.map +1 -0
- package/dist/localization/user-timezone.js +31 -0
- package/dist/localization/user-timezone.js.map +1 -0
- package/dist/notifications/routes.d.ts +4 -4
- package/dist/observability/routes.d.ts +3 -3
- package/dist/org/app-roles-handlers.d.ts.map +1 -1
- package/dist/org/app-roles-handlers.js +1 -8
- package/dist/org/app-roles-handlers.js.map +1 -1
- package/dist/org/index.d.ts +1 -0
- package/dist/org/index.d.ts.map +1 -1
- package/dist/org/index.js +1 -0
- package/dist/org/index.js.map +1 -1
- package/dist/org/membership.d.ts +6 -0
- package/dist/org/membership.d.ts.map +1 -0
- package/dist/org/membership.js +16 -0
- package/dist/org/membership.js.map +1 -0
- package/dist/progress/routes.d.ts +1 -1
- package/dist/provider-api/actions/custom-provider-registration.d.ts +13 -13
- package/dist/provider-api/actions/provider-api.d.ts +8 -8
- package/dist/provider-api/corpus-jobs.d.ts +2 -2
- package/dist/review/actions/create-review-comment.d.ts +32 -1
- package/dist/review/actions/create-review-comment.d.ts.map +1 -1
- package/dist/review/actions/create-review-comment.js +3 -1
- package/dist/review/actions/create-review-comment.js.map +1 -1
- package/dist/review/actions/reply-review-comment.d.ts +32 -1
- package/dist/review/actions/reply-review-comment.d.ts.map +1 -1
- package/dist/review/actions/reply-review-comment.js +3 -1
- package/dist/review/actions/reply-review-comment.js.map +1 -1
- package/dist/review/index.d.ts +1 -0
- package/dist/review/index.d.ts.map +1 -1
- package/dist/review/index.js +1 -0
- package/dist/review/index.js.map +1 -1
- package/dist/review/notifications.d.ts +24 -0
- package/dist/review/notifications.d.ts.map +1 -0
- package/dist/review/notifications.js +123 -0
- package/dist/review/notifications.js.map +1 -0
- package/dist/review/types.d.ts +5 -0
- package/dist/review/types.d.ts.map +1 -1
- package/dist/review/types.js.map +1 -1
- package/dist/server/action-discovery.d.ts.map +1 -1
- package/dist/server/action-discovery.js +4 -0
- package/dist/server/action-discovery.js.map +1 -1
- package/dist/server/activity-notifications.d.ts +61 -0
- package/dist/server/activity-notifications.d.ts.map +1 -0
- package/dist/server/activity-notifications.js +103 -0
- package/dist/server/activity-notifications.js.map +1 -0
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/server/app-name.d.ts +18 -0
- package/dist/server/app-name.d.ts.map +1 -1
- package/dist/server/app-name.js +31 -0
- package/dist/server/app-name.js.map +1 -1
- package/dist/server/better-auth-instance.js +4 -4
- package/dist/server/better-auth-instance.js.map +1 -1
- package/dist/server/email-templates.d.ts +12 -0
- package/dist/server/email-templates.d.ts.map +1 -1
- package/dist/server/email-templates.js +57 -14
- package/dist/server/email-templates.js.map +1 -1
- package/dist/server/email.d.ts +12 -0
- package/dist/server/email.d.ts.map +1 -1
- package/dist/server/email.js +52 -5
- package/dist/server/email.js.map +1 -1
- package/dist/server/index.d.ts +1 -0
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +1 -0
- package/dist/server/index.js.map +1 -1
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/dist/sharing/access.d.ts.map +1 -1
- package/dist/sharing/access.js +32 -2
- package/dist/sharing/access.js.map +1 -1
- package/dist/sharing/index.d.ts +1 -0
- package/dist/sharing/index.d.ts.map +1 -1
- package/dist/sharing/index.js +1 -0
- package/dist/sharing/index.js.map +1 -1
- package/dist/sharing/recipients.d.ts +31 -0
- package/dist/sharing/recipients.d.ts.map +1 -0
- package/dist/sharing/recipients.js +40 -0
- package/dist/sharing/recipients.js.map +1 -0
- package/dist/templates/chat/app/routes/settings.tsx +14 -22
- package/dist/templates/chat/vitest.config.ts +9 -0
- package/dist/triggers/actions/list-automations.d.ts +2 -0
- package/dist/triggers/actions/list-automations.d.ts.map +1 -1
- package/dist/triggers/actions/list-automations.js +19 -8
- package/dist/triggers/actions/list-automations.js.map +1 -1
- package/dist/triggers/actions/manage-automation.d.ts +6 -0
- package/dist/triggers/actions/manage-automation.js +12 -6
- package/dist/triggers/actions/manage-automation.js.map +1 -1
- package/dist/triggers/actions.d.ts.map +1 -1
- package/dist/triggers/actions.js +15 -4
- package/dist/triggers/actions.js.map +1 -1
- package/dist/triggers/dispatcher.d.ts.map +1 -1
- package/dist/triggers/dispatcher.js +19 -17
- package/dist/triggers/dispatcher.js.map +1 -1
- package/dist/triggers/routes.d.ts.map +1 -1
- package/dist/triggers/routes.js +6 -6
- package/dist/triggers/routes.js.map +1 -1
- package/dist/vitest-config.d.ts +10 -0
- package/dist/vitest-config.d.ts.map +1 -0
- package/dist/vitest-config.js +55 -0
- package/dist/vitest-config.js.map +1 -0
- package/package.json +5 -4
- package/src/automations/service.ts +41 -4
- package/src/cli/mcp-config-writers.ts +27 -21
- package/src/client/agent-page/AgentJobsTab.tsx +187 -85
- package/src/client/agent-page/AutomationDetailsDialog.tsx +208 -0
- package/src/client/agent-page/AutomationScheduleDialog.tsx +180 -0
- package/src/client/agent-page/TimezoneSelect.tsx +241 -0
- package/src/client/agent-page/use-jobs.ts +50 -10
- package/src/client/org/OrgSwitcher.tsx +26 -0
- package/src/client/settings/AccountSettingsCard.tsx +35 -31
- package/src/client/settings/SchedulingTimezoneField.tsx +71 -0
- package/src/client/settings/SettingsRow.tsx +113 -0
- package/src/client/settings/agent-settings-search.ts +2 -1
- package/src/client/settings/index.ts +6 -0
- package/src/integrations/identity.ts +2 -12
- package/src/jobs/actions/list-automation-runs.ts +33 -0
- package/src/jobs/actions/list-recurring-jobs.ts +27 -6
- package/src/jobs/actions/manage-recurring-job.ts +41 -9
- package/src/jobs/background-automation-runner.ts +88 -0
- package/src/jobs/cron.ts +52 -6
- package/src/jobs/frontmatter.ts +20 -0
- package/src/jobs/run-history.ts +242 -0
- package/src/jobs/scheduler.ts +73 -18
- package/src/jobs/tools.ts +54 -6
- package/src/localization/actions/get-localization-preference.ts +2 -2
- package/src/localization/actions/set-localization-preference.ts +49 -11
- package/src/localization/default-messages.ts +27 -0
- package/src/localization/shared.ts +39 -6
- package/src/localization/user-timezone.ts +36 -0
- package/src/org/app-roles-handlers.ts +1 -9
- package/src/org/index.ts +2 -0
- package/src/org/membership.ts +18 -0
- package/src/review/actions/create-review-comment.ts +4 -1
- package/src/review/actions/reply-review-comment.ts +4 -1
- package/src/review/index.ts +5 -0
- package/src/review/notifications.ts +163 -0
- package/src/review/types.ts +7 -0
- package/src/server/action-discovery.ts +4 -0
- package/src/server/activity-notifications.ts +174 -0
- package/src/server/app-name.ts +31 -0
- package/src/server/better-auth-instance.ts +4 -4
- package/src/server/email-templates.ts +70 -14
- package/src/server/email.ts +66 -3
- package/src/server/index.ts +10 -0
- package/src/sharing/access.ts +44 -2
- package/src/sharing/index.ts +5 -0
- package/src/sharing/recipients.ts +67 -0
- package/src/templates/chat/app/routes/settings.tsx +14 -22
- package/src/templates/chat/vitest.config.ts +9 -0
- package/src/triggers/actions/list-automations.ts +27 -8
- package/src/triggers/actions/manage-automation.ts +17 -6
- package/src/triggers/actions.ts +16 -4
- package/src/triggers/dispatcher.ts +44 -30
- package/src/triggers/routes.ts +19 -6
- package/src/vitest-config.ts +68 -0
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import {
|
|
2
|
+
TimezoneSelect,
|
|
3
|
+
browserTimezone,
|
|
4
|
+
} from "../agent-page/TimezoneSelect.js";
|
|
5
|
+
import { useT } from "../i18n.js";
|
|
6
|
+
import { useActionMutation, useActionQuery } from "../use-action.js";
|
|
7
|
+
|
|
8
|
+
interface LocalizationPreferenceResult {
|
|
9
|
+
locale: string;
|
|
10
|
+
timezone: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const SYSTEM = "system";
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Timezone used when the agent schedules work for this user.
|
|
17
|
+
*
|
|
18
|
+
* This is deliberately a stored preference rather than a per-request read of
|
|
19
|
+
* the browser zone: automations are created and run by callers that have no
|
|
20
|
+
* browser at all (cron ticks, chat integrations, A2A), and those callers would
|
|
21
|
+
* otherwise fall back to the host zone and schedule the user's 8am job in UTC.
|
|
22
|
+
*/
|
|
23
|
+
export function SchedulingTimezoneField() {
|
|
24
|
+
const t = useT();
|
|
25
|
+
const detected = browserTimezone();
|
|
26
|
+
const preference = useActionQuery<LocalizationPreferenceResult>(
|
|
27
|
+
"get-localization-preference",
|
|
28
|
+
);
|
|
29
|
+
const save = useActionMutation<
|
|
30
|
+
LocalizationPreferenceResult,
|
|
31
|
+
{ timezone: string }
|
|
32
|
+
>("set-localization-preference");
|
|
33
|
+
|
|
34
|
+
// Selecting a zone commits it, matching the language picker beside it.
|
|
35
|
+
const stored = preference.data?.timezone ?? SYSTEM;
|
|
36
|
+
const pending = save.isPending ? save.variables?.timezone : undefined;
|
|
37
|
+
const value = pending ?? stored;
|
|
38
|
+
|
|
39
|
+
return (
|
|
40
|
+
<div className="space-y-2">
|
|
41
|
+
<label
|
|
42
|
+
className="text-sm font-medium"
|
|
43
|
+
htmlFor="agent-native-scheduling-timezone"
|
|
44
|
+
>
|
|
45
|
+
{t("settings.timezoneLabel", { defaultValue: "Timezone" })}
|
|
46
|
+
</label>
|
|
47
|
+
|
|
48
|
+
<TimezoneSelect
|
|
49
|
+
id="agent-native-scheduling-timezone"
|
|
50
|
+
value={value}
|
|
51
|
+
disabled={preference.isLoading || save.isPending}
|
|
52
|
+
suggested={[detected]}
|
|
53
|
+
systemLabel={t("settings.timezoneSystem", {
|
|
54
|
+
defaultValue: "Follow this browser ({{zone}})",
|
|
55
|
+
zone: detected,
|
|
56
|
+
})}
|
|
57
|
+
onChange={(timezone) => save.mutate({ timezone })}
|
|
58
|
+
/>
|
|
59
|
+
|
|
60
|
+
<p className="min-h-4 text-xs text-muted-foreground">
|
|
61
|
+
{save.error ? (
|
|
62
|
+
<span className="text-destructive">{save.error.message}</span>
|
|
63
|
+
) : (
|
|
64
|
+
t("settings.timezoneHint", {
|
|
65
|
+
defaultValue: "Used for timestamps and scheduled automations.",
|
|
66
|
+
})
|
|
67
|
+
)}
|
|
68
|
+
</p>
|
|
69
|
+
</div>
|
|
70
|
+
);
|
|
71
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
|
|
3
|
+
import { cn } from "../utils.js";
|
|
4
|
+
|
|
5
|
+
export interface SettingsGroupProps {
|
|
6
|
+
/** Anchor target so a settings search hit can scroll the whole group. */
|
|
7
|
+
id?: string;
|
|
8
|
+
title?: string;
|
|
9
|
+
description?: string;
|
|
10
|
+
className?: string;
|
|
11
|
+
children: ReactNode;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* A bordered card holding several one-line settings instead of one card per
|
|
16
|
+
* control. Copy is passed in already translated — core components do not
|
|
17
|
+
* resolve an app's i18n keys.
|
|
18
|
+
*/
|
|
19
|
+
export function SettingsGroup({
|
|
20
|
+
id,
|
|
21
|
+
title,
|
|
22
|
+
description,
|
|
23
|
+
className,
|
|
24
|
+
children,
|
|
25
|
+
}: SettingsGroupProps) {
|
|
26
|
+
return (
|
|
27
|
+
<section
|
|
28
|
+
id={id}
|
|
29
|
+
className={cn(
|
|
30
|
+
"scroll-mt-16 overflow-hidden rounded-lg border border-border bg-card text-card-foreground shadow-sm",
|
|
31
|
+
className,
|
|
32
|
+
)}
|
|
33
|
+
>
|
|
34
|
+
{(title || description) && (
|
|
35
|
+
<header className="border-b border-border/60 px-5 py-4 sm:px-6">
|
|
36
|
+
{title && (
|
|
37
|
+
<h2 className="text-base font-semibold leading-none tracking-tight">
|
|
38
|
+
{title}
|
|
39
|
+
</h2>
|
|
40
|
+
)}
|
|
41
|
+
{description && (
|
|
42
|
+
<p className="mt-1.5 text-sm leading-snug text-muted-foreground">
|
|
43
|
+
{description}
|
|
44
|
+
</p>
|
|
45
|
+
)}
|
|
46
|
+
</header>
|
|
47
|
+
)}
|
|
48
|
+
<div className="divide-y divide-border/60">{children}</div>
|
|
49
|
+
</section>
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export interface SettingsRowProps {
|
|
54
|
+
/**
|
|
55
|
+
* Anchor target. Rows keep the ids their former standalone cards used, so
|
|
56
|
+
* existing settings-search hashes still resolve after a card collapses into
|
|
57
|
+
* a row.
|
|
58
|
+
*/
|
|
59
|
+
id?: string;
|
|
60
|
+
label: ReactNode;
|
|
61
|
+
description?: ReactNode;
|
|
62
|
+
icon?: ReactNode;
|
|
63
|
+
/** Badge or pill rendered beside the label. */
|
|
64
|
+
status?: ReactNode;
|
|
65
|
+
/** The input itself: switch, select, button, link. */
|
|
66
|
+
control?: ReactNode;
|
|
67
|
+
/** Extra content below the row, e.g. a disclosure panel. */
|
|
68
|
+
children?: ReactNode;
|
|
69
|
+
className?: string;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** One setting: label and description on the start side, control on the end. */
|
|
73
|
+
export function SettingsRow({
|
|
74
|
+
id,
|
|
75
|
+
label,
|
|
76
|
+
description,
|
|
77
|
+
icon,
|
|
78
|
+
status,
|
|
79
|
+
control,
|
|
80
|
+
children,
|
|
81
|
+
className,
|
|
82
|
+
}: SettingsRowProps) {
|
|
83
|
+
return (
|
|
84
|
+
<div id={id} className={cn("scroll-mt-16 px-5 py-4 sm:px-6", className)}>
|
|
85
|
+
<div className="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
|
|
86
|
+
<div className="flex min-w-0 gap-3">
|
|
87
|
+
{icon && (
|
|
88
|
+
<span className="mt-0.5 flex size-8 shrink-0 items-center justify-center rounded-md border border-border bg-background text-muted-foreground [&>svg]:size-[18px]">
|
|
89
|
+
{icon}
|
|
90
|
+
</span>
|
|
91
|
+
)}
|
|
92
|
+
<div className="min-w-0">
|
|
93
|
+
<div className="flex flex-wrap items-center gap-2">
|
|
94
|
+
<span className="text-sm font-medium text-foreground">
|
|
95
|
+
{label}
|
|
96
|
+
</span>
|
|
97
|
+
{status}
|
|
98
|
+
</div>
|
|
99
|
+
{description && (
|
|
100
|
+
<p className="mt-1 max-w-xl text-sm leading-6 text-muted-foreground">
|
|
101
|
+
{description}
|
|
102
|
+
</p>
|
|
103
|
+
)}
|
|
104
|
+
</div>
|
|
105
|
+
</div>
|
|
106
|
+
{control && (
|
|
107
|
+
<div className="shrink-0 sm:ms-4 sm:text-end">{control}</div>
|
|
108
|
+
)}
|
|
109
|
+
</div>
|
|
110
|
+
{children && <div className="mt-4">{children}</div>}
|
|
111
|
+
</div>
|
|
112
|
+
);
|
|
113
|
+
}
|
|
@@ -93,7 +93,8 @@ const SETTINGS_SECTION_SEARCH_META: Record<
|
|
|
93
93
|
> = {
|
|
94
94
|
account: {
|
|
95
95
|
label: "Account",
|
|
96
|
-
keywords:
|
|
96
|
+
keywords:
|
|
97
|
+
"profile photo avatar identity signed in email name timezone time zone schedule scheduling clock",
|
|
97
98
|
},
|
|
98
99
|
llm: {
|
|
99
100
|
label: "LLM",
|
|
@@ -34,6 +34,12 @@ export {
|
|
|
34
34
|
type OpenBuilderConnectPopupOptions,
|
|
35
35
|
} from "./useBuilderStatus.js";
|
|
36
36
|
export { SecretsSection, type SecretsSectionProps } from "./SecretsSection.js";
|
|
37
|
+
export {
|
|
38
|
+
SettingsGroup,
|
|
39
|
+
SettingsRow,
|
|
40
|
+
type SettingsGroupProps,
|
|
41
|
+
type SettingsRowProps,
|
|
42
|
+
} from "./SettingsRow.js";
|
|
37
43
|
export {
|
|
38
44
|
normalizeSettingsSection,
|
|
39
45
|
settingsSectionDomId,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { isOrgMember as isMemberOfOrg } from "../org/membership.js";
|
|
2
2
|
import { upsertVerifiedIntegrationIdentity } from "./identity-links-store.js";
|
|
3
3
|
import {
|
|
4
4
|
getActiveIntegrationInstallationByKey,
|
|
@@ -74,16 +74,6 @@ async function resolveSlackInstallation(incoming: IncomingMessage) {
|
|
|
74
74
|
: null;
|
|
75
75
|
}
|
|
76
76
|
|
|
77
|
-
async function isMemberOfOrg(email: string, orgId: string): Promise<boolean> {
|
|
78
|
-
const { rows } = await getDbExec().execute({
|
|
79
|
-
sql: `SELECT 1 FROM org_members
|
|
80
|
-
WHERE org_id = ? AND LOWER(email) = ?
|
|
81
|
-
LIMIT 1`,
|
|
82
|
-
args: [orgId, email],
|
|
83
|
-
});
|
|
84
|
-
return rows.length > 0;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
77
|
/**
|
|
88
78
|
* Resolve the default integration principal.
|
|
89
79
|
*
|
|
@@ -150,7 +140,7 @@ export async function resolveDefaultIntegrationExecutionContext(
|
|
|
150
140
|
incoming.tenantId
|
|
151
141
|
) {
|
|
152
142
|
try {
|
|
153
|
-
isOrgMember = await isMemberOfOrg(
|
|
143
|
+
isOrgMember = await isMemberOfOrg(installation.orgId, email);
|
|
154
144
|
} catch {
|
|
155
145
|
// A membership-store outage is not evidence that the sender is merely
|
|
156
146
|
// unlinked. Fail closed instead of widening them to org-wide access.
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
import { defineAction } from "../../action.js";
|
|
4
|
+
import { organizationResourceOwner } from "../../resources/store.js";
|
|
5
|
+
import { listAutomationRuns, type AutomationRun } from "../run-history.js";
|
|
6
|
+
|
|
7
|
+
const scopeSchema = z.enum(["personal", "organization"]);
|
|
8
|
+
|
|
9
|
+
export default defineAction({
|
|
10
|
+
description:
|
|
11
|
+
"List past execution records for one automation or recurring job in the selected scope.",
|
|
12
|
+
agentTool: false,
|
|
13
|
+
schema: z.object({
|
|
14
|
+
name: z.string().min(1),
|
|
15
|
+
scope: scopeSchema.default("personal"),
|
|
16
|
+
limit: z.number().int().min(1).max(100).optional(),
|
|
17
|
+
}),
|
|
18
|
+
http: { method: "GET" },
|
|
19
|
+
readOnly: true,
|
|
20
|
+
parallelSafe: true,
|
|
21
|
+
run: async ({ name, scope, limit }, ctx): Promise<AutomationRun[]> => {
|
|
22
|
+
const userEmail = ctx?.userEmail;
|
|
23
|
+
if (!userEmail) throw new Error("Not authenticated.");
|
|
24
|
+
if (scope === "organization" && !ctx?.orgId) return [];
|
|
25
|
+
|
|
26
|
+
const owner =
|
|
27
|
+
scope === "organization"
|
|
28
|
+
? organizationResourceOwner(ctx.orgId as string)
|
|
29
|
+
: userEmail;
|
|
30
|
+
|
|
31
|
+
return listAutomationRuns({ owners: [owner], automation: name, limit });
|
|
32
|
+
},
|
|
33
|
+
});
|
|
@@ -6,7 +6,12 @@ import {
|
|
|
6
6
|
resourceGetByPath,
|
|
7
7
|
resourceList,
|
|
8
8
|
} from "../../resources/store.js";
|
|
9
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
describeCron,
|
|
11
|
+
effectiveTimezone,
|
|
12
|
+
isValidCron,
|
|
13
|
+
nextOccurrence,
|
|
14
|
+
} from "../cron.js";
|
|
10
15
|
import { classifyJobResource } from "../frontmatter.js";
|
|
11
16
|
import { parseJobFrontmatter } from "../scheduler.js";
|
|
12
17
|
import { authorizeJobMutation } from "../tools.js";
|
|
@@ -17,15 +22,25 @@ function jobName(path: string): string {
|
|
|
17
22
|
return path.replace(/^jobs\//, "").replace(/\.md$/, "");
|
|
18
23
|
}
|
|
19
24
|
|
|
25
|
+
/**
|
|
26
|
+
* A stored `nextRun` in the past means the scheduler kept declining to run the
|
|
27
|
+
* job, not that it is due two days ago. Report the real next occurrence and
|
|
28
|
+
* let `lastError` carry the reason it keeps being passed over.
|
|
29
|
+
*/
|
|
20
30
|
function nextRun(
|
|
21
31
|
meta: ReturnType<typeof parseJobFrontmatter>["meta"],
|
|
22
32
|
): string | null {
|
|
23
33
|
if (!meta.enabled) return null;
|
|
24
|
-
|
|
25
|
-
if (meta.
|
|
26
|
-
|
|
34
|
+
const scheduled = Boolean(meta.schedule && isValidCron(meta.schedule));
|
|
35
|
+
if (meta.nextRun) {
|
|
36
|
+
const stored = new Date(meta.nextRun).getTime();
|
|
37
|
+
if (!Number.isFinite(stored) || stored > Date.now() || !scheduled) {
|
|
38
|
+
return meta.nextRun;
|
|
39
|
+
}
|
|
27
40
|
}
|
|
28
|
-
return
|
|
41
|
+
return scheduled
|
|
42
|
+
? nextOccurrence(meta.schedule, undefined, meta.timezone).toISOString()
|
|
43
|
+
: null;
|
|
29
44
|
}
|
|
30
45
|
|
|
31
46
|
export interface RecurringJobActionItem {
|
|
@@ -34,10 +49,12 @@ export interface RecurringJobActionItem {
|
|
|
34
49
|
path: string;
|
|
35
50
|
scope: "personal" | "organization";
|
|
36
51
|
schedule: string;
|
|
52
|
+
timezone: string;
|
|
37
53
|
scheduleDescription: string;
|
|
38
54
|
instructions: string;
|
|
39
55
|
enabled: boolean;
|
|
40
56
|
lastRun: string | null;
|
|
57
|
+
lastCheck: string | null;
|
|
41
58
|
lastStatus: string | null;
|
|
42
59
|
lastError: string | null;
|
|
43
60
|
nextRun: string | null;
|
|
@@ -87,10 +104,14 @@ export default defineAction({
|
|
|
87
104
|
path: full.path,
|
|
88
105
|
scope,
|
|
89
106
|
schedule: meta.schedule,
|
|
90
|
-
|
|
107
|
+
timezone: effectiveTimezone(meta.timezone),
|
|
108
|
+
scheduleDescription: meta.schedule
|
|
109
|
+
? describeCron(meta.schedule, effectiveTimezone(meta.timezone))
|
|
110
|
+
: "",
|
|
91
111
|
instructions: body,
|
|
92
112
|
enabled: meta.enabled,
|
|
93
113
|
lastRun: meta.lastRun ?? null,
|
|
114
|
+
lastCheck: meta.lastCheck ?? null,
|
|
94
115
|
lastStatus: meta.lastStatus ?? null,
|
|
95
116
|
lastError: meta.lastError ?? null,
|
|
96
117
|
nextRun: nextRun(meta),
|
|
@@ -7,8 +7,9 @@ import {
|
|
|
7
7
|
resourceGetByPath,
|
|
8
8
|
resourcePut,
|
|
9
9
|
} from "../../resources/store.js";
|
|
10
|
-
import { isValidCron, nextOccurrence } from "../cron.js";
|
|
10
|
+
import { isValidCron, isValidTimezone, nextOccurrence } from "../cron.js";
|
|
11
11
|
import { classifyJobResource } from "../frontmatter.js";
|
|
12
|
+
import { deleteAutomationRuns } from "../run-history.js";
|
|
12
13
|
import { buildJobContent, parseJobFrontmatter } from "../scheduler.js";
|
|
13
14
|
import { authorizeJobMutation } from "../tools.js";
|
|
14
15
|
|
|
@@ -23,8 +24,10 @@ export default defineAction({
|
|
|
23
24
|
name: z.string().min(1),
|
|
24
25
|
scope: scopeSchema.default("personal"),
|
|
25
26
|
enabled: z.boolean().optional(),
|
|
27
|
+
schedule: z.string().min(1).optional(),
|
|
28
|
+
timezone: z.string().min(1).optional(),
|
|
26
29
|
}),
|
|
27
|
-
run: async ({ operation, name, scope, enabled }, ctx) => {
|
|
30
|
+
run: async ({ operation, name, scope, enabled, schedule, timezone }, ctx) => {
|
|
28
31
|
const userEmail = ctx?.userEmail;
|
|
29
32
|
if (!userEmail) throw new Error("Not authenticated.");
|
|
30
33
|
if (scope === "organization" && !ctx?.orgId) {
|
|
@@ -54,17 +57,46 @@ export default defineAction({
|
|
|
54
57
|
|
|
55
58
|
if (operation === "delete") {
|
|
56
59
|
await resourceDelete(resource.id);
|
|
60
|
+
// Names are reusable; history left behind would surface as the run
|
|
61
|
+
// history of whatever job is next created under this name.
|
|
62
|
+
await deleteAutomationRuns(resource.owner, name);
|
|
57
63
|
return { deleted: true, name };
|
|
58
64
|
}
|
|
59
65
|
|
|
60
|
-
if (
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
66
|
+
if (timezone !== undefined) {
|
|
67
|
+
if (!isValidTimezone(timezone)) {
|
|
68
|
+
throw Object.assign(new Error(`Unknown timezone "${timezone}".`), {
|
|
69
|
+
statusCode: 400,
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
meta.timezone = timezone;
|
|
73
|
+
}
|
|
74
|
+
if (
|
|
75
|
+
enabled === undefined &&
|
|
76
|
+
schedule === undefined &&
|
|
77
|
+
timezone === undefined
|
|
78
|
+
) {
|
|
79
|
+
throw Object.assign(
|
|
80
|
+
new Error("enabled, schedule, or timezone is required for update."),
|
|
81
|
+
{ statusCode: 400 },
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
if (schedule !== undefined) {
|
|
85
|
+
if (!isValidCron(schedule)) {
|
|
86
|
+
throw Object.assign(
|
|
87
|
+
new Error(`Invalid cron expression "${schedule}".`),
|
|
88
|
+
{ statusCode: 400 },
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
meta.schedule = schedule;
|
|
64
92
|
}
|
|
65
|
-
meta.enabled = enabled;
|
|
66
|
-
if (enabled && meta.schedule && isValidCron(meta.schedule)) {
|
|
67
|
-
meta.nextRun = nextOccurrence(
|
|
93
|
+
if (enabled !== undefined) meta.enabled = enabled;
|
|
94
|
+
if (meta.enabled && meta.schedule && isValidCron(meta.schedule)) {
|
|
95
|
+
meta.nextRun = nextOccurrence(
|
|
96
|
+
meta.schedule,
|
|
97
|
+
undefined,
|
|
98
|
+
meta.timezone,
|
|
99
|
+
).toISOString();
|
|
68
100
|
}
|
|
69
101
|
await resourcePut(
|
|
70
102
|
resource.owner,
|
|
@@ -32,6 +32,11 @@ import {
|
|
|
32
32
|
type RequestContext,
|
|
33
33
|
} from "../server/request-context.js";
|
|
34
34
|
import type { JobFrontmatter } from "./frontmatter.js";
|
|
35
|
+
import {
|
|
36
|
+
attachAutomationRunThread,
|
|
37
|
+
finishAutomationRun,
|
|
38
|
+
startAutomationRun,
|
|
39
|
+
} from "./run-history.js";
|
|
35
40
|
|
|
36
41
|
const BACKGROUND_RUN_STUCK_MS = 10 * 60_000;
|
|
37
42
|
const BACKGROUND_RUN_HARD_TIMEOUT_MS = 5 * 60_000;
|
|
@@ -248,6 +253,87 @@ function createRunId(prefix: string): string {
|
|
|
248
253
|
export async function runBackgroundAutomation(
|
|
249
254
|
options: BackgroundAutomationRunOptions,
|
|
250
255
|
deps: BackgroundAutomationDeps,
|
|
256
|
+
): Promise<BackgroundAutomationRunResult> {
|
|
257
|
+
const { automation } = options;
|
|
258
|
+
// Bookkeeping, so it must not gate the work it describes: a history table
|
|
259
|
+
// that cannot be written should cost us the record, not the automation.
|
|
260
|
+
// Everything downstream tolerates a null id by skipping its own write.
|
|
261
|
+
let historyId: string | null = null;
|
|
262
|
+
try {
|
|
263
|
+
historyId = await startAutomationRun({
|
|
264
|
+
owner: automation.resource.owner,
|
|
265
|
+
automation: automation.name,
|
|
266
|
+
path: automation.resource.path,
|
|
267
|
+
scope: organizationIdFromResourceOwner(automation.resource.owner)
|
|
268
|
+
? "organization"
|
|
269
|
+
: "personal",
|
|
270
|
+
orgId: options.orgId ?? null,
|
|
271
|
+
});
|
|
272
|
+
} catch (err) {
|
|
273
|
+
console.error(
|
|
274
|
+
`[automations] Could not open a history record for "${automation.name}"; running anyway:`,
|
|
275
|
+
err,
|
|
276
|
+
);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
let result: BackgroundAutomationRunResult;
|
|
280
|
+
try {
|
|
281
|
+
result = await executeBackgroundAutomation(options, deps, historyId);
|
|
282
|
+
} catch (err) {
|
|
283
|
+
await recordRunOutcome(
|
|
284
|
+
historyId,
|
|
285
|
+
"error",
|
|
286
|
+
err instanceof Error ? err.message : String(err),
|
|
287
|
+
);
|
|
288
|
+
throw err;
|
|
289
|
+
}
|
|
290
|
+
// Outside the try: history is bookkeeping about the run, so a failure to
|
|
291
|
+
// write it must not turn a completed automation into a reported failure.
|
|
292
|
+
await recordRunOutcome(historyId, "success");
|
|
293
|
+
return result;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Link the run to its agent thread. Bookkeeping again: the automation is
|
|
298
|
+
* already executing by this point, so a failed write costs the cross-reference
|
|
299
|
+
* in the history view, not the run.
|
|
300
|
+
*/
|
|
301
|
+
async function recordRunThread(
|
|
302
|
+
historyId: string | null,
|
|
303
|
+
threadId: string,
|
|
304
|
+
runId: string,
|
|
305
|
+
): Promise<void> {
|
|
306
|
+
if (!historyId) return;
|
|
307
|
+
try {
|
|
308
|
+
await attachAutomationRunThread(historyId, threadId, runId);
|
|
309
|
+
} catch (err) {
|
|
310
|
+
console.error(
|
|
311
|
+
`[automations] Could not attach thread ${threadId} to run ${historyId}:`,
|
|
312
|
+
err,
|
|
313
|
+
);
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
async function recordRunOutcome(
|
|
318
|
+
historyId: string | null,
|
|
319
|
+
status: "success" | "error",
|
|
320
|
+
error?: string,
|
|
321
|
+
): Promise<void> {
|
|
322
|
+
if (!historyId) return;
|
|
323
|
+
try {
|
|
324
|
+
await finishAutomationRun(historyId, status, error);
|
|
325
|
+
} catch (err) {
|
|
326
|
+
console.error(
|
|
327
|
+
`[automations] Could not record run ${historyId} as ${status}:`,
|
|
328
|
+
err,
|
|
329
|
+
);
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
async function executeBackgroundAutomation(
|
|
334
|
+
options: BackgroundAutomationRunOptions,
|
|
335
|
+
deps: BackgroundAutomationDeps,
|
|
336
|
+
historyId: string | null,
|
|
251
337
|
): Promise<BackgroundAutomationRunResult> {
|
|
252
338
|
const { automation, ownerEmail, orgId, prompt, threadTitle, usageLabel } =
|
|
253
339
|
options;
|
|
@@ -291,6 +377,8 @@ export async function runBackgroundAutomation(
|
|
|
291
377
|
const systemPrompt = await deps.getSystemPrompt(ownerEmail);
|
|
292
378
|
const thread = await createThread(ownerEmail, { title: threadTitle });
|
|
293
379
|
const runId = createRunId(options.runIdPrefix);
|
|
380
|
+
await recordRunThread(historyId, thread.id, runId);
|
|
381
|
+
|
|
294
382
|
// Scheduled work is background work: it has no synchronous serverless
|
|
295
383
|
// caller waiting on it, so it must not inherit the interactive clamp
|
|
296
384
|
// (40s soft timeout, a 30s no-progress backstop at 0.75x that, and 6
|
|
@@ -9,12 +9,50 @@ function normalize(cronExpr: string): string {
|
|
|
9
9
|
return ALIAS_MAP[cronExpr.trim().toLowerCase()] ?? cronExpr;
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
+
/**
|
|
13
|
+
* IANA zone the host process evaluates wall-clock times in. Jobs stored before
|
|
14
|
+
* schedules carried a `timezone` are interpreted here, so their meaning does
|
|
15
|
+
* not silently shift when timezone support is added.
|
|
16
|
+
*/
|
|
17
|
+
export function serverTimezone(): string {
|
|
18
|
+
try {
|
|
19
|
+
return Intl.DateTimeFormat().resolvedOptions().timeZone || "UTC";
|
|
20
|
+
} catch {
|
|
21
|
+
return "UTC";
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function isValidTimezone(timezone: string): boolean {
|
|
26
|
+
if (!timezone.trim()) return false;
|
|
27
|
+
try {
|
|
28
|
+
new Intl.DateTimeFormat("en-US", { timeZone: timezone });
|
|
29
|
+
return true;
|
|
30
|
+
} catch {
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Resolve the zone a schedule's wall-clock fields are read in. An absent
|
|
37
|
+
* `timezone` means the schedule predates timezone support, so it keeps its
|
|
38
|
+
* original host-relative meaning rather than being reinterpreted as UTC.
|
|
39
|
+
*/
|
|
40
|
+
export function effectiveTimezone(timezone?: string | null): string {
|
|
41
|
+
return timezone && isValidTimezone(timezone) ? timezone : serverTimezone();
|
|
42
|
+
}
|
|
43
|
+
|
|
12
44
|
/**
|
|
13
45
|
* Compute the next occurrence of a cron expression after the given date.
|
|
46
|
+
* `timezone` is the IANA zone the expression's wall-clock fields refer to.
|
|
14
47
|
*/
|
|
15
|
-
export function nextOccurrence(
|
|
48
|
+
export function nextOccurrence(
|
|
49
|
+
cronExpr: string,
|
|
50
|
+
after?: Date,
|
|
51
|
+
timezone?: string | null,
|
|
52
|
+
): Date {
|
|
16
53
|
const expr = CronExpressionParser.parse(normalize(cronExpr), {
|
|
17
54
|
currentDate: after ?? new Date(),
|
|
55
|
+
tz: effectiveTimezone(timezone),
|
|
18
56
|
});
|
|
19
57
|
const next = expr.next();
|
|
20
58
|
return next.toDate();
|
|
@@ -36,13 +74,21 @@ export function isValidCron(cronExpr: string): boolean {
|
|
|
36
74
|
* Convert a cron expression to a human-readable description.
|
|
37
75
|
* Handles common patterns; falls back to the raw expression for unusual ones.
|
|
38
76
|
*/
|
|
39
|
-
export function describeCron(
|
|
77
|
+
export function describeCron(
|
|
78
|
+
cronExpr: string,
|
|
79
|
+
timezone?: string | null,
|
|
80
|
+
): string {
|
|
40
81
|
const normalized = normalize(cronExpr);
|
|
41
82
|
const parts = normalized.trim().split(/\s+/);
|
|
42
83
|
if (parts.length !== 5) return cronExpr;
|
|
43
84
|
|
|
44
85
|
const [minute, hour, dayOfMonth, month, dayOfWeek] = parts;
|
|
45
86
|
|
|
87
|
+
// A bare wall-clock time is ambiguous the moment the host and the reader sit
|
|
88
|
+
// in different zones, so every description carrying a time names its zone.
|
|
89
|
+
const zone = (text: string) =>
|
|
90
|
+
timezone ? text + " (" + timezone + ")" : text;
|
|
91
|
+
|
|
46
92
|
// Every minute
|
|
47
93
|
if (normalized === "* * * * *") return "Every minute";
|
|
48
94
|
|
|
@@ -98,7 +144,7 @@ export function describeCron(cronExpr: string): string {
|
|
|
98
144
|
|
|
99
145
|
// Every day at specific time
|
|
100
146
|
if (dayOfMonth === "*" && month === "*" && dayOfWeek === "*" && time) {
|
|
101
|
-
return `Every day at ${time}
|
|
147
|
+
return zone(`Every day at ${time}`);
|
|
102
148
|
}
|
|
103
149
|
|
|
104
150
|
// Weekdays
|
|
@@ -108,18 +154,18 @@ export function describeCron(cronExpr: string): string {
|
|
|
108
154
|
(dayOfWeek === "1-5" || dayOfWeek === "MON-FRI") &&
|
|
109
155
|
time
|
|
110
156
|
) {
|
|
111
|
-
return `Every weekday at ${time}
|
|
157
|
+
return zone(`Every weekday at ${time}`);
|
|
112
158
|
}
|
|
113
159
|
|
|
114
160
|
// Specific day of week
|
|
115
161
|
if (dayOfMonth === "*" && month === "*" && dayOfWeek !== "*" && time) {
|
|
116
162
|
const days = dayOfWeek.split(",").map((d) => dayNames[d] || d);
|
|
117
|
-
return `Every ${days.join(", ")} at ${time}
|
|
163
|
+
return zone(`Every ${days.join(", ")} at ${time}`);
|
|
118
164
|
}
|
|
119
165
|
|
|
120
166
|
// Specific day of month
|
|
121
167
|
if (dayOfMonth !== "*" && month === "*" && dayOfWeek === "*" && time) {
|
|
122
|
-
return `On day ${dayOfMonth} of every month at ${time}
|
|
168
|
+
return zone(`On day ${dayOfMonth} of every month at ${time}`);
|
|
123
169
|
}
|
|
124
170
|
|
|
125
171
|
return cronExpr;
|
|
@@ -11,10 +11,22 @@ export type JobExecutionMode = "agentic" | "deterministic";
|
|
|
11
11
|
export interface JobFrontmatter {
|
|
12
12
|
schedule: string;
|
|
13
13
|
enabled: boolean;
|
|
14
|
+
/**
|
|
15
|
+
* IANA zone the cron fields are read in. Absent means the schedule predates
|
|
16
|
+
* timezone support and keeps its original host-relative meaning.
|
|
17
|
+
*/
|
|
18
|
+
timezone?: string;
|
|
14
19
|
createdBy?: string;
|
|
15
20
|
orgId?: string;
|
|
16
21
|
runAs?: "creator" | "shared";
|
|
22
|
+
/** Last time the automation actually started executing. */
|
|
17
23
|
lastRun?: string;
|
|
24
|
+
/**
|
|
25
|
+
* Last time a tick evaluated this automation and declined to run it. Kept
|
|
26
|
+
* distinct from `lastRun` so a blocked automation cannot report a run it
|
|
27
|
+
* never performed.
|
|
28
|
+
*/
|
|
29
|
+
lastCheck?: string;
|
|
18
30
|
lastStatus?: JobLastStatus;
|
|
19
31
|
lastError?: string;
|
|
20
32
|
nextRun?: string;
|
|
@@ -132,6 +144,9 @@ function parseKnownField(
|
|
|
132
144
|
case "enabled":
|
|
133
145
|
meta.enabled = value !== "false";
|
|
134
146
|
break;
|
|
147
|
+
case "timezone":
|
|
148
|
+
meta.timezone = value || undefined;
|
|
149
|
+
break;
|
|
135
150
|
case "createdBy":
|
|
136
151
|
meta.createdBy = value;
|
|
137
152
|
break;
|
|
@@ -145,6 +160,9 @@ function parseKnownField(
|
|
|
145
160
|
case "lastRun":
|
|
146
161
|
meta.lastRun = value;
|
|
147
162
|
break;
|
|
163
|
+
case "lastCheck":
|
|
164
|
+
meta.lastCheck = value;
|
|
165
|
+
break;
|
|
148
166
|
case "lastStatus":
|
|
149
167
|
meta.lastStatus = value as JobLastStatus;
|
|
150
168
|
break;
|
|
@@ -290,7 +308,9 @@ export function buildJobResourceContent(
|
|
|
290
308
|
pushString(lines, "createdBy", meta.createdBy, false);
|
|
291
309
|
pushString(lines, "orgId", meta.orgId);
|
|
292
310
|
if (meta.runAs) lines.push(`runAs: ${meta.runAs}`);
|
|
311
|
+
pushString(lines, "timezone", meta.timezone);
|
|
293
312
|
pushString(lines, "lastRun", meta.lastRun);
|
|
313
|
+
pushString(lines, "lastCheck", meta.lastCheck);
|
|
294
314
|
if (meta.lastStatus) lines.push(`lastStatus: ${meta.lastStatus}`);
|
|
295
315
|
pushString(lines, "lastError", meta.lastError);
|
|
296
316
|
pushString(lines, "nextRun", meta.nextRun);
|