@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
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"frontmatter.js","sourceRoot":"","sources":["../../src/jobs/frontmatter.ts"],"names":[],"mappings":"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","sourcesContent":["export type JobLastStatus = \"success\" | \"error\" | \"running\" | \"skipped\";\nexport type JobTriggerType = \"schedule\" | \"event\";\nexport type JobExecutionMode = \"agentic\" | \"deterministic\";\n\n/**\n * Every persisted field understood by either recurring jobs or automations.\n *\n * Automation-only fields stay optional so legacy recurring jobs can remain\n * distinguishable from explicitly defined schedule automations.\n */\nexport interface JobFrontmatter {\n schedule: string;\n enabled: boolean;\n createdBy?: string;\n orgId?: string;\n runAs?: \"creator\" | \"shared\";\n lastRun?: string;\n lastStatus?: JobLastStatus;\n lastError?: string;\n nextRun?: string;\n originScopeId?: string;\n deliveryPlatform?: string;\n deliveryDestination?: string;\n deliveryThreadRef?: string;\n deliveryTenantId?: string;\n model?: string;\n /** Explicit MCP tool capabilities available to this background run. */\n mcpTools?: string[];\n /** Present only for resources explicitly defined as automations. */\n triggerType?: JobTriggerType;\n /** For event automations: the event name to subscribe to. */\n event?: string;\n /** Natural-language condition evaluated before dispatch. */\n condition?: string;\n mode?: JobExecutionMode;\n /** Domain tag for filtering in per-template UIs. */\n domain?: string;\n /**\n * Optional application-owned policy id carried into actions by the trusted\n * trigger dispatcher. It is not model-supplied action input.\n */\n delegatedPolicyId?: string;\n}\n\nexport interface JobResourceClassification {\n kind: \"job\" | \"automation\";\n hasExplicitTriggerType: boolean;\n triggerType: JobTriggerType;\n}\n\nexport interface ParsedJobResource {\n meta: JobFrontmatter;\n body: string;\n classification: JobResourceClassification;\n}\n\nconst MAX_JOB_MCP_TOOLS = 64;\nconst JOB_MCP_TOOL_NAME_RE = /^mcp__[^\\s]+__[^\\s]+$/;\nconst FRONTMATTER_RE = /^---\\r?\\n([\\s\\S]*?)\\r?\\n---(?:\\r?\\n)?([\\s\\S]*)$/;\nconst DELEGATED_POLICY_ID_RE = /^[a-z0-9][a-z0-9._:-]{0,127}$/i;\n\n/**\n * Normalize the non-secret MCP capability references persisted with a job.\n * Tool names are opaque framework identifiers; URLs and credentials never\n * belong in job frontmatter.\n */\nexport function normalizeJobMcpTools(value: unknown): string[] | undefined {\n if (value === undefined || value === null) return undefined;\n const parsed =\n typeof value === \"string\"\n ? (() => {\n try {\n return JSON.parse(value);\n } catch {\n throw new Error(\"mcpTools must be a JSON array of tool names.\");\n }\n })()\n : value;\n if (!Array.isArray(parsed)) {\n throw new Error(\"mcpTools must be an array of MCP tool names.\");\n }\n if (parsed.length > MAX_JOB_MCP_TOOLS) {\n throw new Error(\n `mcpTools may contain at most ${MAX_JOB_MCP_TOOLS} tool names.`,\n );\n }\n const normalized = [...new Set(parsed)];\n if (\n normalized.some(\n (toolName) =>\n typeof toolName !== \"string\" || !JOB_MCP_TOOL_NAME_RE.test(toolName),\n )\n ) {\n throw new Error(\n \"mcpTools must contain only framework MCP tool names such as mcp__server__tool.\",\n );\n }\n return normalized;\n}\n\nfunction parseScalar(rawValue: string): string {\n const value = rawValue.trim();\n if (value.startsWith('\"') && value.endsWith('\"')) {\n try {\n const parsed = JSON.parse(value);\n return typeof parsed === \"string\" ? parsed : value.slice(1, -1);\n } catch {\n return value\n .slice(1, -1)\n .replace(/\\\\n/g, \"\\n\")\n .replace(/\\\\r/g, \"\\r\")\n .replace(/\\\\\"/g, '\"')\n .replace(/\\\\\\\\/g, \"\\\\\");\n }\n }\n if (value.startsWith(\"'\") && value.endsWith(\"'\")) {\n return value.slice(1, -1).replace(/''/g, \"'\");\n }\n return value;\n}\n\nfunction parseKnownField(\n meta: JobFrontmatter,\n key: string,\n rawValue: string,\n): void {\n const value = parseScalar(rawValue);\n switch (key) {\n case \"schedule\":\n meta.schedule = value;\n break;\n case \"enabled\":\n meta.enabled = value !== \"false\";\n break;\n case \"createdBy\":\n meta.createdBy = value;\n break;\n case \"orgId\":\n meta.orgId = value;\n break;\n case \"runAs\":\n meta.runAs =\n value === \"shared\" || value === \"creator\" ? value : undefined;\n break;\n case \"lastRun\":\n meta.lastRun = value;\n break;\n case \"lastStatus\":\n meta.lastStatus = value as JobLastStatus;\n break;\n case \"lastError\":\n meta.lastError = value;\n break;\n case \"nextRun\":\n meta.nextRun = value;\n break;\n case \"originScopeId\":\n meta.originScopeId = value;\n break;\n case \"deliveryPlatform\":\n meta.deliveryPlatform = value;\n break;\n case \"deliveryDestination\":\n meta.deliveryDestination = value;\n break;\n case \"deliveryThreadRef\":\n meta.deliveryThreadRef = value;\n break;\n case \"deliveryTenantId\":\n meta.deliveryTenantId = value;\n break;\n case \"model\":\n meta.model = value;\n break;\n case \"mcpTools\":\n meta.mcpTools = normalizeJobMcpTools(value);\n break;\n case \"triggerType\":\n // The field's presence is the durable legacy-job/automation boundary.\n // Preserve that marker even if an old writer stored an invalid value.\n meta.triggerType = value === \"event\" ? \"event\" : \"schedule\";\n break;\n case \"event\":\n meta.event = value;\n break;\n case \"condition\":\n meta.condition = value;\n break;\n case \"mode\":\n if (value === \"agentic\" || value === \"deterministic\") {\n meta.mode = value;\n }\n break;\n case \"domain\":\n meta.domain = value;\n break;\n case \"delegatedPolicyId\":\n meta.delegatedPolicyId = value || undefined;\n break;\n }\n}\n\nexport function classifyJobFrontmatter(\n meta: JobFrontmatter,\n): JobResourceClassification {\n const hasExplicitTriggerType = meta.triggerType !== undefined;\n return {\n kind: hasExplicitTriggerType ? \"automation\" : \"job\",\n hasExplicitTriggerType,\n triggerType: meta.triggerType ?? \"schedule\",\n };\n}\n\nexport function parseJobResource(content: string): ParsedJobResource {\n const match = content.match(FRONTMATTER_RE);\n if (!match) {\n const meta: JobFrontmatter = { schedule: \"\", enabled: false };\n return {\n meta,\n body: content,\n classification: classifyJobFrontmatter(meta),\n };\n }\n\n const meta: JobFrontmatter = { schedule: \"\", enabled: true };\n for (const line of match[1].split(/\\r?\\n/)) {\n const colonIdx = line.indexOf(\":\");\n if (colonIdx === -1) continue;\n parseKnownField(\n meta,\n line.slice(0, colonIdx).trim(),\n line.slice(colonIdx + 1),\n );\n }\n\n return {\n meta,\n body: match[2].trim(),\n classification: classifyJobFrontmatter(meta),\n };\n}\n\nexport function classifyJobResource(\n content: string,\n): JobResourceClassification {\n return parseJobResource(content).classification;\n}\n\nfunction pushString(\n lines: string[],\n key: string,\n value: string | undefined,\n alwaysQuote = true,\n): void {\n if (!value) return;\n const serialized =\n alwaysQuote || value.includes(\"\\n\") || value.includes(\"\\r\")\n ? JSON.stringify(value)\n : value;\n lines.push(`${key}: ${serialized}`);\n}\n\nexport function buildJobResourceContent(\n meta: JobFrontmatter,\n body: string,\n): string {\n if (\n meta.delegatedPolicyId &&\n !DELEGATED_POLICY_ID_RE.test(meta.delegatedPolicyId)\n ) {\n throw new Error(\n \"Delegated automation policy IDs must be 1-128 letters, numbers, dots, underscores, colons, or hyphens.\",\n );\n }\n\n const lines = [\n \"---\",\n `schedule: ${JSON.stringify(meta.schedule)}`,\n `enabled: ${meta.enabled}`,\n ];\n if (meta.triggerType) lines.push(`triggerType: ${meta.triggerType}`);\n pushString(lines, \"event\", meta.event);\n pushString(lines, \"condition\", meta.condition);\n if (meta.mode) lines.push(`mode: ${meta.mode}`);\n pushString(lines, \"domain\", meta.domain);\n pushString(lines, \"delegatedPolicyId\", meta.delegatedPolicyId);\n // Keep the long-standing human-readable owner shape used by existing\n // resources and diagnostics; values that can contain free-form text use\n // JSON quoting below.\n pushString(lines, \"createdBy\", meta.createdBy, false);\n pushString(lines, \"orgId\", meta.orgId);\n if (meta.runAs) lines.push(`runAs: ${meta.runAs}`);\n pushString(lines, \"lastRun\", meta.lastRun);\n if (meta.lastStatus) lines.push(`lastStatus: ${meta.lastStatus}`);\n pushString(lines, \"lastError\", meta.lastError);\n pushString(lines, \"nextRun\", meta.nextRun);\n pushString(lines, \"originScopeId\", meta.originScopeId);\n pushString(lines, \"deliveryPlatform\", meta.deliveryPlatform);\n pushString(lines, \"deliveryDestination\", meta.deliveryDestination);\n pushString(lines, \"deliveryThreadRef\", meta.deliveryThreadRef);\n pushString(lines, \"deliveryTenantId\", meta.deliveryTenantId);\n pushString(lines, \"model\", meta.model);\n if (meta.mcpTools?.length) {\n lines.push(`mcpTools: ${JSON.stringify(meta.mcpTools)}`);\n }\n lines.push(\"---\", \"\", body);\n return lines.join(\"\\n\");\n}\n"]}
|
|
1
|
+
{"version":3,"file":"frontmatter.js","sourceRoot":"","sources":["../../src/jobs/frontmatter.ts"],"names":[],"mappings":"AAoEA,MAAM,iBAAiB,GAAG,EAAE,CAAC;AAC7B,MAAM,oBAAoB,GAAG,uBAAuB,CAAC;AACrD,MAAM,cAAc,GAAG,iDAAiD,CAAC;AACzE,MAAM,sBAAsB,GAAG,gCAAgC,CAAC;AAEhE;;;;GAIG;AACH,MAAM,UAAU,oBAAoB,CAAC,KAAc;IACjD,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,IAAI;QAAE,OAAO,SAAS,CAAC;IAC5D,MAAM,MAAM,GACV,OAAO,KAAK,KAAK,QAAQ;QACvB,CAAC,CAAC,CAAC,GAAG,EAAE;YACJ,IAAI,CAAC;gBACH,OAAO,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;YAC3B,CAAC;YAAC,MAAM,CAAC;gBACP,MAAM,IAAI,KAAK,CAAC,8CAA8C,CAAC,CAAC;YAClE,CAAC;QACH,CAAC,CAAC,EAAE;QACN,CAAC,CAAC,KAAK,CAAC;IACZ,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;QAC3B,MAAM,IAAI,KAAK,CAAC,8CAA8C,CAAC,CAAC;IAClE,CAAC;IACD,IAAI,MAAM,CAAC,MAAM,GAAG,iBAAiB,EAAE,CAAC;QACtC,MAAM,IAAI,KAAK,CACb,gCAAgC,iBAAiB,cAAc,CAChE,CAAC;IACJ,CAAC;IACD,MAAM,UAAU,GAAG,CAAC,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC;IACxC,IACE,UAAU,CAAC,IAAI,CACb,CAAC,QAAQ,EAAE,EAAE,CACX,OAAO,QAAQ,KAAK,QAAQ,IAAI,CAAC,oBAAoB,CAAC,IAAI,CAAC,QAAQ,CAAC,CACvE,EACD,CAAC;QACD,MAAM,IAAI,KAAK,CACb,gFAAgF,CACjF,CAAC;IACJ,CAAC;IACD,OAAO,UAAU,CAAC;AACpB,CAAC;AAED,SAAS,WAAW,CAAC,QAAgB;IACnC,MAAM,KAAK,GAAG,QAAQ,CAAC,IAAI,EAAE,CAAC;IAC9B,IAAI,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;QACjD,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;YACjC,OAAO,OAAO,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;QAClE,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,KAAK;iBACT,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;iBACZ,OAAO,CAAC,MAAM,EAAE,IAAI,CAAC;iBACrB,OAAO,CAAC,MAAM,EAAE,IAAI,CAAC;iBACrB,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC;iBACpB,OAAO,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;QAC5B,CAAC;IACH,CAAC;IACD,IAAI,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;QACjD,OAAO,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;IAChD,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,SAAS,eAAe,CACtB,IAAoB,EACpB,GAAW,EACX,QAAgB;IAEhB,MAAM,KAAK,GAAG,WAAW,CAAC,QAAQ,CAAC,CAAC;IACpC,QAAQ,GAAG,EAAE,CAAC;QACZ,KAAK,UAAU;YACb,IAAI,CAAC,QAAQ,GAAG,KAAK,CAAC;YACtB,MAAM;QACR,KAAK,SAAS;YACZ,IAAI,CAAC,OAAO,GAAG,KAAK,KAAK,OAAO,CAAC;YACjC,MAAM;QACR,KAAK,UAAU;YACb,IAAI,CAAC,QAAQ,GAAG,KAAK,IAAI,SAAS,CAAC;YACnC,MAAM;QACR,KAAK,WAAW;YACd,IAAI,CAAC,SAAS,GAAG,KAAK,CAAC;YACvB,MAAM;QACR,KAAK,OAAO;YACV,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;YACnB,MAAM;QACR,KAAK,OAAO;YACV,IAAI,CAAC,KAAK;gBACR,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC;YAChE,MAAM;QACR,KAAK,SAAS;YACZ,IAAI,CAAC,OAAO,GAAG,KAAK,CAAC;YACrB,MAAM;QACR,KAAK,WAAW;YACd,IAAI,CAAC,SAAS,GAAG,KAAK,CAAC;YACvB,MAAM;QACR,KAAK,YAAY;YACf,IAAI,CAAC,UAAU,GAAG,KAAsB,CAAC;YACzC,MAAM;QACR,KAAK,WAAW;YACd,IAAI,CAAC,SAAS,GAAG,KAAK,CAAC;YACvB,MAAM;QACR,KAAK,SAAS;YACZ,IAAI,CAAC,OAAO,GAAG,KAAK,CAAC;YACrB,MAAM;QACR,KAAK,eAAe;YAClB,IAAI,CAAC,aAAa,GAAG,KAAK,CAAC;YAC3B,MAAM;QACR,KAAK,kBAAkB;YACrB,IAAI,CAAC,gBAAgB,GAAG,KAAK,CAAC;YAC9B,MAAM;QACR,KAAK,qBAAqB;YACxB,IAAI,CAAC,mBAAmB,GAAG,KAAK,CAAC;YACjC,MAAM;QACR,KAAK,mBAAmB;YACtB,IAAI,CAAC,iBAAiB,GAAG,KAAK,CAAC;YAC/B,MAAM;QACR,KAAK,kBAAkB;YACrB,IAAI,CAAC,gBAAgB,GAAG,KAAK,CAAC;YAC9B,MAAM;QACR,KAAK,OAAO;YACV,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;YACnB,MAAM;QACR,KAAK,UAAU;YACb,IAAI,CAAC,QAAQ,GAAG,oBAAoB,CAAC,KAAK,CAAC,CAAC;YAC5C,MAAM;QACR,KAAK,aAAa;YAChB,sEAAsE;YACtE,sEAAsE;YACtE,IAAI,CAAC,WAAW,GAAG,KAAK,KAAK,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,UAAU,CAAC;YAC5D,MAAM;QACR,KAAK,OAAO;YACV,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;YACnB,MAAM;QACR,KAAK,WAAW;YACd,IAAI,CAAC,SAAS,GAAG,KAAK,CAAC;YACvB,MAAM;QACR,KAAK,MAAM;YACT,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,eAAe,EAAE,CAAC;gBACrD,IAAI,CAAC,IAAI,GAAG,KAAK,CAAC;YACpB,CAAC;YACD,MAAM;QACR,KAAK,QAAQ;YACX,IAAI,CAAC,MAAM,GAAG,KAAK,CAAC;YACpB,MAAM;QACR,KAAK,mBAAmB;YACtB,IAAI,CAAC,iBAAiB,GAAG,KAAK,IAAI,SAAS,CAAC;YAC5C,MAAM;IACV,CAAC;AACH,CAAC;AAED,MAAM,UAAU,sBAAsB,CACpC,IAAoB;IAEpB,MAAM,sBAAsB,GAAG,IAAI,CAAC,WAAW,KAAK,SAAS,CAAC;IAC9D,OAAO;QACL,IAAI,EAAE,sBAAsB,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,KAAK;QACnD,sBAAsB;QACtB,WAAW,EAAE,IAAI,CAAC,WAAW,IAAI,UAAU;KAC5C,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,OAAe;IAC9C,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,cAAc,CAAC,CAAC;IAC5C,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,MAAM,IAAI,GAAmB,EAAE,QAAQ,EAAE,EAAE,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;QAC9D,OAAO;YACL,IAAI;YACJ,IAAI,EAAE,OAAO;YACb,cAAc,EAAE,sBAAsB,CAAC,IAAI,CAAC;SAC7C,CAAC;IACJ,CAAC;IAED,MAAM,IAAI,GAAmB,EAAE,QAAQ,EAAE,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;IAC7D,KAAK,MAAM,IAAI,IAAI,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,EAAE,CAAC;QAC3C,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QACnC,IAAI,QAAQ,KAAK,CAAC,CAAC;YAAE,SAAS;QAC9B,eAAe,CACb,IAAI,EACJ,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC,IAAI,EAAE,EAC9B,IAAI,CAAC,KAAK,CAAC,QAAQ,GAAG,CAAC,CAAC,CACzB,CAAC;IACJ,CAAC;IAED,OAAO;QACL,IAAI;QACJ,IAAI,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE;QACrB,cAAc,EAAE,sBAAsB,CAAC,IAAI,CAAC;KAC7C,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,mBAAmB,CACjC,OAAe;IAEf,OAAO,gBAAgB,CAAC,OAAO,CAAC,CAAC,cAAc,CAAC;AAClD,CAAC;AAED,SAAS,UAAU,CACjB,KAAe,EACf,GAAW,EACX,KAAyB,EACzB,WAAW,GAAG,IAAI;IAElB,IAAI,CAAC,KAAK;QAAE,OAAO;IACnB,MAAM,UAAU,GACd,WAAW,IAAI,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC;QACzD,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC;QACvB,CAAC,CAAC,KAAK,CAAC;IACZ,KAAK,CAAC,IAAI,CAAC,GAAG,GAAG,KAAK,UAAU,EAAE,CAAC,CAAC;AACtC,CAAC;AAED,MAAM,UAAU,uBAAuB,CACrC,IAAoB,EACpB,IAAY;IAEZ,IACE,IAAI,CAAC,iBAAiB;QACtB,CAAC,sBAAsB,CAAC,IAAI,CAAC,IAAI,CAAC,iBAAiB,CAAC,EACpD,CAAC;QACD,MAAM,IAAI,KAAK,CACb,wGAAwG,CACzG,CAAC;IACJ,CAAC;IAED,MAAM,KAAK,GAAG;QACZ,KAAK;QACL,aAAa,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE;QAC5C,YAAY,IAAI,CAAC,OAAO,EAAE;KAC3B,CAAC;IACF,IAAI,IAAI,CAAC,WAAW;QAAE,KAAK,CAAC,IAAI,CAAC,gBAAgB,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC;IACrE,UAAU,CAAC,KAAK,EAAE,OAAO,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC;IACvC,UAAU,CAAC,KAAK,EAAE,WAAW,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC;IAC/C,IAAI,IAAI,CAAC,IAAI;QAAE,KAAK,CAAC,IAAI,CAAC,SAAS,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC;IAChD,UAAU,CAAC,KAAK,EAAE,QAAQ,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC;IACzC,UAAU,CAAC,KAAK,EAAE,mBAAmB,EAAE,IAAI,CAAC,iBAAiB,CAAC,CAAC;IAC/D,qEAAqE;IACrE,wEAAwE;IACxE,sBAAsB;IACtB,UAAU,CAAC,KAAK,EAAE,WAAW,EAAE,IAAI,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC;IACtD,UAAU,CAAC,KAAK,EAAE,OAAO,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC;IACvC,IAAI,IAAI,CAAC,KAAK;QAAE,KAAK,CAAC,IAAI,CAAC,UAAU,IAAI,CAAC,KAAK,EAAE,CAAC,CAAC;IACnD,UAAU,CAAC,KAAK,EAAE,UAAU,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC;IAC7C,UAAU,CAAC,KAAK,EAAE,SAAS,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC;IAC3C,UAAU,CAAC,KAAK,EAAE,WAAW,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC;IAC/C,IAAI,IAAI,CAAC,UAAU;QAAE,KAAK,CAAC,IAAI,CAAC,eAAe,IAAI,CAAC,UAAU,EAAE,CAAC,CAAC;IAClE,UAAU,CAAC,KAAK,EAAE,WAAW,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC;IAC/C,UAAU,CAAC,KAAK,EAAE,SAAS,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC;IAC3C,UAAU,CAAC,KAAK,EAAE,eAAe,EAAE,IAAI,CAAC,aAAa,CAAC,CAAC;IACvD,UAAU,CAAC,KAAK,EAAE,kBAAkB,EAAE,IAAI,CAAC,gBAAgB,CAAC,CAAC;IAC7D,UAAU,CAAC,KAAK,EAAE,qBAAqB,EAAE,IAAI,CAAC,mBAAmB,CAAC,CAAC;IACnE,UAAU,CAAC,KAAK,EAAE,mBAAmB,EAAE,IAAI,CAAC,iBAAiB,CAAC,CAAC;IAC/D,UAAU,CAAC,KAAK,EAAE,kBAAkB,EAAE,IAAI,CAAC,gBAAgB,CAAC,CAAC;IAC7D,UAAU,CAAC,KAAK,EAAE,OAAO,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC;IACvC,IAAI,IAAI,CAAC,QAAQ,EAAE,MAAM,EAAE,CAAC;QAC1B,KAAK,CAAC,IAAI,CAAC,aAAa,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;IAC3D,CAAC;IACD,KAAK,CAAC,IAAI,CAAC,KAAK,EAAE,EAAE,EAAE,IAAI,CAAC,CAAC;IAC5B,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC1B,CAAC","sourcesContent":["export type JobLastStatus = \"success\" | \"error\" | \"running\" | \"skipped\";\nexport type JobTriggerType = \"schedule\" | \"event\";\nexport type JobExecutionMode = \"agentic\" | \"deterministic\";\n\n/**\n * Every persisted field understood by either recurring jobs or automations.\n *\n * Automation-only fields stay optional so legacy recurring jobs can remain\n * distinguishable from explicitly defined schedule automations.\n */\nexport interface JobFrontmatter {\n schedule: string;\n enabled: boolean;\n /**\n * IANA zone the cron fields are read in. Absent means the schedule predates\n * timezone support and keeps its original host-relative meaning.\n */\n timezone?: string;\n createdBy?: string;\n orgId?: string;\n runAs?: \"creator\" | \"shared\";\n /** Last time the automation actually started executing. */\n lastRun?: string;\n /**\n * Last time a tick evaluated this automation and declined to run it. Kept\n * distinct from `lastRun` so a blocked automation cannot report a run it\n * never performed.\n */\n lastCheck?: string;\n lastStatus?: JobLastStatus;\n lastError?: string;\n nextRun?: string;\n originScopeId?: string;\n deliveryPlatform?: string;\n deliveryDestination?: string;\n deliveryThreadRef?: string;\n deliveryTenantId?: string;\n model?: string;\n /** Explicit MCP tool capabilities available to this background run. */\n mcpTools?: string[];\n /** Present only for resources explicitly defined as automations. */\n triggerType?: JobTriggerType;\n /** For event automations: the event name to subscribe to. */\n event?: string;\n /** Natural-language condition evaluated before dispatch. */\n condition?: string;\n mode?: JobExecutionMode;\n /** Domain tag for filtering in per-template UIs. */\n domain?: string;\n /**\n * Optional application-owned policy id carried into actions by the trusted\n * trigger dispatcher. It is not model-supplied action input.\n */\n delegatedPolicyId?: string;\n}\n\nexport interface JobResourceClassification {\n kind: \"job\" | \"automation\";\n hasExplicitTriggerType: boolean;\n triggerType: JobTriggerType;\n}\n\nexport interface ParsedJobResource {\n meta: JobFrontmatter;\n body: string;\n classification: JobResourceClassification;\n}\n\nconst MAX_JOB_MCP_TOOLS = 64;\nconst JOB_MCP_TOOL_NAME_RE = /^mcp__[^\\s]+__[^\\s]+$/;\nconst FRONTMATTER_RE = /^---\\r?\\n([\\s\\S]*?)\\r?\\n---(?:\\r?\\n)?([\\s\\S]*)$/;\nconst DELEGATED_POLICY_ID_RE = /^[a-z0-9][a-z0-9._:-]{0,127}$/i;\n\n/**\n * Normalize the non-secret MCP capability references persisted with a job.\n * Tool names are opaque framework identifiers; URLs and credentials never\n * belong in job frontmatter.\n */\nexport function normalizeJobMcpTools(value: unknown): string[] | undefined {\n if (value === undefined || value === null) return undefined;\n const parsed =\n typeof value === \"string\"\n ? (() => {\n try {\n return JSON.parse(value);\n } catch {\n throw new Error(\"mcpTools must be a JSON array of tool names.\");\n }\n })()\n : value;\n if (!Array.isArray(parsed)) {\n throw new Error(\"mcpTools must be an array of MCP tool names.\");\n }\n if (parsed.length > MAX_JOB_MCP_TOOLS) {\n throw new Error(\n `mcpTools may contain at most ${MAX_JOB_MCP_TOOLS} tool names.`,\n );\n }\n const normalized = [...new Set(parsed)];\n if (\n normalized.some(\n (toolName) =>\n typeof toolName !== \"string\" || !JOB_MCP_TOOL_NAME_RE.test(toolName),\n )\n ) {\n throw new Error(\n \"mcpTools must contain only framework MCP tool names such as mcp__server__tool.\",\n );\n }\n return normalized;\n}\n\nfunction parseScalar(rawValue: string): string {\n const value = rawValue.trim();\n if (value.startsWith('\"') && value.endsWith('\"')) {\n try {\n const parsed = JSON.parse(value);\n return typeof parsed === \"string\" ? parsed : value.slice(1, -1);\n } catch {\n return value\n .slice(1, -1)\n .replace(/\\\\n/g, \"\\n\")\n .replace(/\\\\r/g, \"\\r\")\n .replace(/\\\\\"/g, '\"')\n .replace(/\\\\\\\\/g, \"\\\\\");\n }\n }\n if (value.startsWith(\"'\") && value.endsWith(\"'\")) {\n return value.slice(1, -1).replace(/''/g, \"'\");\n }\n return value;\n}\n\nfunction parseKnownField(\n meta: JobFrontmatter,\n key: string,\n rawValue: string,\n): void {\n const value = parseScalar(rawValue);\n switch (key) {\n case \"schedule\":\n meta.schedule = value;\n break;\n case \"enabled\":\n meta.enabled = value !== \"false\";\n break;\n case \"timezone\":\n meta.timezone = value || undefined;\n break;\n case \"createdBy\":\n meta.createdBy = value;\n break;\n case \"orgId\":\n meta.orgId = value;\n break;\n case \"runAs\":\n meta.runAs =\n value === \"shared\" || value === \"creator\" ? value : undefined;\n break;\n case \"lastRun\":\n meta.lastRun = value;\n break;\n case \"lastCheck\":\n meta.lastCheck = value;\n break;\n case \"lastStatus\":\n meta.lastStatus = value as JobLastStatus;\n break;\n case \"lastError\":\n meta.lastError = value;\n break;\n case \"nextRun\":\n meta.nextRun = value;\n break;\n case \"originScopeId\":\n meta.originScopeId = value;\n break;\n case \"deliveryPlatform\":\n meta.deliveryPlatform = value;\n break;\n case \"deliveryDestination\":\n meta.deliveryDestination = value;\n break;\n case \"deliveryThreadRef\":\n meta.deliveryThreadRef = value;\n break;\n case \"deliveryTenantId\":\n meta.deliveryTenantId = value;\n break;\n case \"model\":\n meta.model = value;\n break;\n case \"mcpTools\":\n meta.mcpTools = normalizeJobMcpTools(value);\n break;\n case \"triggerType\":\n // The field's presence is the durable legacy-job/automation boundary.\n // Preserve that marker even if an old writer stored an invalid value.\n meta.triggerType = value === \"event\" ? \"event\" : \"schedule\";\n break;\n case \"event\":\n meta.event = value;\n break;\n case \"condition\":\n meta.condition = value;\n break;\n case \"mode\":\n if (value === \"agentic\" || value === \"deterministic\") {\n meta.mode = value;\n }\n break;\n case \"domain\":\n meta.domain = value;\n break;\n case \"delegatedPolicyId\":\n meta.delegatedPolicyId = value || undefined;\n break;\n }\n}\n\nexport function classifyJobFrontmatter(\n meta: JobFrontmatter,\n): JobResourceClassification {\n const hasExplicitTriggerType = meta.triggerType !== undefined;\n return {\n kind: hasExplicitTriggerType ? \"automation\" : \"job\",\n hasExplicitTriggerType,\n triggerType: meta.triggerType ?? \"schedule\",\n };\n}\n\nexport function parseJobResource(content: string): ParsedJobResource {\n const match = content.match(FRONTMATTER_RE);\n if (!match) {\n const meta: JobFrontmatter = { schedule: \"\", enabled: false };\n return {\n meta,\n body: content,\n classification: classifyJobFrontmatter(meta),\n };\n }\n\n const meta: JobFrontmatter = { schedule: \"\", enabled: true };\n for (const line of match[1].split(/\\r?\\n/)) {\n const colonIdx = line.indexOf(\":\");\n if (colonIdx === -1) continue;\n parseKnownField(\n meta,\n line.slice(0, colonIdx).trim(),\n line.slice(colonIdx + 1),\n );\n }\n\n return {\n meta,\n body: match[2].trim(),\n classification: classifyJobFrontmatter(meta),\n };\n}\n\nexport function classifyJobResource(\n content: string,\n): JobResourceClassification {\n return parseJobResource(content).classification;\n}\n\nfunction pushString(\n lines: string[],\n key: string,\n value: string | undefined,\n alwaysQuote = true,\n): void {\n if (!value) return;\n const serialized =\n alwaysQuote || value.includes(\"\\n\") || value.includes(\"\\r\")\n ? JSON.stringify(value)\n : value;\n lines.push(`${key}: ${serialized}`);\n}\n\nexport function buildJobResourceContent(\n meta: JobFrontmatter,\n body: string,\n): string {\n if (\n meta.delegatedPolicyId &&\n !DELEGATED_POLICY_ID_RE.test(meta.delegatedPolicyId)\n ) {\n throw new Error(\n \"Delegated automation policy IDs must be 1-128 letters, numbers, dots, underscores, colons, or hyphens.\",\n );\n }\n\n const lines = [\n \"---\",\n `schedule: ${JSON.stringify(meta.schedule)}`,\n `enabled: ${meta.enabled}`,\n ];\n if (meta.triggerType) lines.push(`triggerType: ${meta.triggerType}`);\n pushString(lines, \"event\", meta.event);\n pushString(lines, \"condition\", meta.condition);\n if (meta.mode) lines.push(`mode: ${meta.mode}`);\n pushString(lines, \"domain\", meta.domain);\n pushString(lines, \"delegatedPolicyId\", meta.delegatedPolicyId);\n // Keep the long-standing human-readable owner shape used by existing\n // resources and diagnostics; values that can contain free-form text use\n // JSON quoting below.\n pushString(lines, \"createdBy\", meta.createdBy, false);\n pushString(lines, \"orgId\", meta.orgId);\n if (meta.runAs) lines.push(`runAs: ${meta.runAs}`);\n pushString(lines, \"timezone\", meta.timezone);\n pushString(lines, \"lastRun\", meta.lastRun);\n pushString(lines, \"lastCheck\", meta.lastCheck);\n if (meta.lastStatus) lines.push(`lastStatus: ${meta.lastStatus}`);\n pushString(lines, \"lastError\", meta.lastError);\n pushString(lines, \"nextRun\", meta.nextRun);\n pushString(lines, \"originScopeId\", meta.originScopeId);\n pushString(lines, \"deliveryPlatform\", meta.deliveryPlatform);\n pushString(lines, \"deliveryDestination\", meta.deliveryDestination);\n pushString(lines, \"deliveryThreadRef\", meta.deliveryThreadRef);\n pushString(lines, \"deliveryTenantId\", meta.deliveryTenantId);\n pushString(lines, \"model\", meta.model);\n if (meta.mcpTools?.length) {\n lines.push(`mcpTools: ${JSON.stringify(meta.mcpTools)}`);\n }\n lines.push(\"---\", \"\", body);\n return lines.join(\"\\n\");\n}\n"]}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* "interrupted" is derived at read time, never stored: a process killed
|
|
3
|
+
* mid-run cannot write its own outcome, so a row left running long past the
|
|
4
|
+
* point a run could still be alive is reported as interrupted rather than
|
|
5
|
+
* shown as permanently in-flight.
|
|
6
|
+
*/
|
|
7
|
+
export type AutomationRunStatus = "running" | "success" | "error" | "interrupted";
|
|
8
|
+
export interface AutomationRun {
|
|
9
|
+
id: string;
|
|
10
|
+
owner: string;
|
|
11
|
+
automation: string;
|
|
12
|
+
path: string;
|
|
13
|
+
scope: string | null;
|
|
14
|
+
orgId: string | null;
|
|
15
|
+
runId: string | null;
|
|
16
|
+
threadId: string | null;
|
|
17
|
+
status: AutomationRunStatus;
|
|
18
|
+
startedAt: number;
|
|
19
|
+
finishedAt: number | null;
|
|
20
|
+
error: string | null;
|
|
21
|
+
}
|
|
22
|
+
export interface StartAutomationRunInput {
|
|
23
|
+
owner: string;
|
|
24
|
+
automation: string;
|
|
25
|
+
path: string;
|
|
26
|
+
scope?: string | null;
|
|
27
|
+
orgId?: string | null;
|
|
28
|
+
runId?: string | null;
|
|
29
|
+
threadId?: string | null;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Record that an automation actually began executing. Returns the run id used
|
|
33
|
+
* to close the record out. Only real executions get a row — a tick that
|
|
34
|
+
* declined to run the automation must not appear in its history.
|
|
35
|
+
*/
|
|
36
|
+
export declare function startAutomationRun(input: StartAutomationRunInput): Promise<string>;
|
|
37
|
+
export declare function finishAutomationRun(id: string, status: Exclude<AutomationRunStatus, "running">, error?: string): Promise<void>;
|
|
38
|
+
/**
|
|
39
|
+
* Attach the agent thread once it exists. The thread is created after the run
|
|
40
|
+
* row so the history survives a crash between the two.
|
|
41
|
+
*/
|
|
42
|
+
export declare function attachAutomationRunThread(id: string, threadId: string, runId: string): Promise<void>;
|
|
43
|
+
/**
|
|
44
|
+
* Forget an automation's executions.
|
|
45
|
+
*
|
|
46
|
+
* History is keyed by the automation's name, which is reusable: deleting
|
|
47
|
+
* "digest" and creating a new "digest" would otherwise show the old
|
|
48
|
+
* definition's runs as the new one's history.
|
|
49
|
+
*/
|
|
50
|
+
export declare function deleteAutomationRuns(owner: string, automation: string): Promise<void>;
|
|
51
|
+
export declare function listAutomationRuns(options: {
|
|
52
|
+
owners: string[];
|
|
53
|
+
automation: string;
|
|
54
|
+
limit?: number;
|
|
55
|
+
}): Promise<AutomationRun[]>;
|
|
56
|
+
//# sourceMappingURL=run-history.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"run-history.d.ts","sourceRoot":"","sources":["../../src/jobs/run-history.ts"],"names":[],"mappings":"AAKA;;;;;GAKG;AACH,MAAM,MAAM,mBAAmB,GAC3B,SAAS,GACT,SAAS,GACT,OAAO,GACP,aAAa,CAAC;AAElB,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,MAAM,EAAE,mBAAmB,CAAC;IAC5B,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CACtB;AAED,MAAM,WAAW,uBAAuB;IACtC,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B;AA6ED;;;;GAIG;AACH,wBAAsB,kBAAkB,CACtC,KAAK,EAAE,uBAAuB,GAC7B,OAAO,CAAC,MAAM,CAAC,CAoBjB;AA2BD,wBAAsB,mBAAmB,CACvC,EAAE,EAAE,MAAM,EACV,MAAM,EAAE,OAAO,CAAC,mBAAmB,EAAE,SAAS,CAAC,EAC/C,KAAK,CAAC,EAAE,MAAM,GACb,OAAO,CAAC,IAAI,CAAC,CAMf;AAED;;;GAGG;AACH,wBAAsB,yBAAyB,CAC7C,EAAE,EAAE,MAAM,EACV,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC,IAAI,CAAC,CAMf;AAED;;;;;;GAMG;AACH,wBAAsB,oBAAoB,CACxC,KAAK,EAAE,MAAM,EACb,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,IAAI,CAAC,CAWf;AAED,wBAAsB,kBAAkB,CAAC,OAAO,EAAE;IAChD,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,GAAG,OAAO,CAAC,aAAa,EAAE,CAAC,CAe3B"}
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { getDbExec, intType, isPostgres } from "../db/client.js";
|
|
3
|
+
import { ensureIndexExists, ensureTableExists } from "../db/ddl-guard.js";
|
|
4
|
+
const TABLE = "automation_runs";
|
|
5
|
+
const MAX_ERROR_LENGTH = 500;
|
|
6
|
+
/**
|
|
7
|
+
* Generous multiple of the runner's own 5 minute hard abort
|
|
8
|
+
* (BACKGROUND_RUN_HARD_TIMEOUT_MS). Past this, no run is still alive.
|
|
9
|
+
*/
|
|
10
|
+
const RUN_LIVENESS_CEILING_MS = 15 * 60_000;
|
|
11
|
+
/** Rows kept per automation, so a per-minute schedule cannot grow forever. */
|
|
12
|
+
const RUNS_RETAINED_PER_AUTOMATION = 50;
|
|
13
|
+
let _initPromise;
|
|
14
|
+
async function ensureTable() {
|
|
15
|
+
if (!_initPromise) {
|
|
16
|
+
_initPromise = (async () => {
|
|
17
|
+
const client = getDbExec();
|
|
18
|
+
const createSql = `
|
|
19
|
+
CREATE TABLE IF NOT EXISTS ${TABLE} (
|
|
20
|
+
id TEXT PRIMARY KEY,
|
|
21
|
+
owner TEXT NOT NULL,
|
|
22
|
+
automation TEXT NOT NULL,
|
|
23
|
+
path TEXT NOT NULL,
|
|
24
|
+
scope TEXT,
|
|
25
|
+
org_id TEXT,
|
|
26
|
+
run_id TEXT,
|
|
27
|
+
thread_id TEXT,
|
|
28
|
+
status TEXT NOT NULL DEFAULT 'running',
|
|
29
|
+
started_at ${intType()} NOT NULL,
|
|
30
|
+
finished_at ${intType()},
|
|
31
|
+
error TEXT
|
|
32
|
+
)
|
|
33
|
+
`;
|
|
34
|
+
const indexSql = `CREATE INDEX IF NOT EXISTS idx_${TABLE}_owner_automation ON ${TABLE} (owner, automation, started_at)`;
|
|
35
|
+
if (isPostgres()) {
|
|
36
|
+
await ensureTableExists(TABLE, createSql);
|
|
37
|
+
await ensureIndexExists(`idx_${TABLE}_owner_automation`, indexSql);
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
await client.execute(createSql);
|
|
41
|
+
await client.execute(indexSql);
|
|
42
|
+
})().catch((err) => {
|
|
43
|
+
_initPromise = undefined;
|
|
44
|
+
throw err;
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
return _initPromise;
|
|
48
|
+
}
|
|
49
|
+
function toRun(row, now) {
|
|
50
|
+
const stored = String(row.status);
|
|
51
|
+
const startedAt = Number(row.started_at);
|
|
52
|
+
const status = stored === "running" && now - startedAt > RUN_LIVENESS_CEILING_MS
|
|
53
|
+
? "interrupted"
|
|
54
|
+
: stored;
|
|
55
|
+
return {
|
|
56
|
+
id: String(row.id),
|
|
57
|
+
owner: String(row.owner),
|
|
58
|
+
automation: String(row.automation),
|
|
59
|
+
path: String(row.path),
|
|
60
|
+
scope: row.scope == null ? null : String(row.scope),
|
|
61
|
+
orgId: row.org_id == null ? null : String(row.org_id),
|
|
62
|
+
runId: row.run_id == null ? null : String(row.run_id),
|
|
63
|
+
threadId: row.thread_id == null ? null : String(row.thread_id),
|
|
64
|
+
status,
|
|
65
|
+
startedAt,
|
|
66
|
+
finishedAt: row.finished_at == null ? null : Number(row.finished_at),
|
|
67
|
+
error: row.error == null ? null : String(row.error),
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Record that an automation actually began executing. Returns the run id used
|
|
72
|
+
* to close the record out. Only real executions get a row — a tick that
|
|
73
|
+
* declined to run the automation must not appear in its history.
|
|
74
|
+
*/
|
|
75
|
+
export async function startAutomationRun(input) {
|
|
76
|
+
await ensureTable();
|
|
77
|
+
const id = randomUUID();
|
|
78
|
+
await getDbExec().execute({
|
|
79
|
+
sql: `INSERT INTO ${TABLE} (id, owner, automation, path, scope, org_id, run_id, thread_id, status, started_at)
|
|
80
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, 'running', ?)`,
|
|
81
|
+
args: [
|
|
82
|
+
id,
|
|
83
|
+
input.owner,
|
|
84
|
+
input.automation,
|
|
85
|
+
input.path,
|
|
86
|
+
input.scope ?? null,
|
|
87
|
+
input.orgId ?? null,
|
|
88
|
+
input.runId ?? null,
|
|
89
|
+
input.threadId ?? null,
|
|
90
|
+
Date.now(),
|
|
91
|
+
],
|
|
92
|
+
});
|
|
93
|
+
await pruneAutomationRuns(input.owner, input.automation);
|
|
94
|
+
return id;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Drop the oldest rows for one automation once it exceeds the retention cap.
|
|
98
|
+
*
|
|
99
|
+
* Pruning on insert keeps the table bounded by the number of automations
|
|
100
|
+
* rather than by how often they run, and avoids a separate sweeper. The
|
|
101
|
+
* derived table is aliased because Postgres requires it.
|
|
102
|
+
*/
|
|
103
|
+
async function pruneAutomationRuns(owner, automation) {
|
|
104
|
+
await getDbExec().execute({
|
|
105
|
+
sql: `DELETE FROM ${TABLE}
|
|
106
|
+
WHERE owner = ? AND automation = ? AND started_at < (
|
|
107
|
+
SELECT MIN(started_at) FROM (
|
|
108
|
+
SELECT started_at FROM ${TABLE}
|
|
109
|
+
WHERE owner = ? AND automation = ?
|
|
110
|
+
ORDER BY started_at DESC
|
|
111
|
+
LIMIT ${RUNS_RETAINED_PER_AUTOMATION}
|
|
112
|
+
) recent
|
|
113
|
+
)`,
|
|
114
|
+
args: [owner, automation, owner, automation],
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
export async function finishAutomationRun(id, status, error) {
|
|
118
|
+
await ensureTable();
|
|
119
|
+
await getDbExec().execute({
|
|
120
|
+
sql: `UPDATE ${TABLE} SET status = ?, finished_at = ?, error = ? WHERE id = ?`,
|
|
121
|
+
args: [status, Date.now(), error?.slice(0, MAX_ERROR_LENGTH) ?? null, id],
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Attach the agent thread once it exists. The thread is created after the run
|
|
126
|
+
* row so the history survives a crash between the two.
|
|
127
|
+
*/
|
|
128
|
+
export async function attachAutomationRunThread(id, threadId, runId) {
|
|
129
|
+
await ensureTable();
|
|
130
|
+
await getDbExec().execute({
|
|
131
|
+
sql: `UPDATE ${TABLE} SET thread_id = ?, run_id = ? WHERE id = ?`,
|
|
132
|
+
args: [threadId, runId, id],
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Forget an automation's executions.
|
|
137
|
+
*
|
|
138
|
+
* History is keyed by the automation's name, which is reusable: deleting
|
|
139
|
+
* "digest" and creating a new "digest" would otherwise show the old
|
|
140
|
+
* definition's runs as the new one's history.
|
|
141
|
+
*/
|
|
142
|
+
export async function deleteAutomationRuns(owner, automation) {
|
|
143
|
+
await ensureTable();
|
|
144
|
+
// Bounded to runs that had already started. Names are reusable, so if a new
|
|
145
|
+
// automation takes this name and starts running before the cleanup lands,
|
|
146
|
+
// the cutoff keeps that run's history from being swept up with the old
|
|
147
|
+
// definition's.
|
|
148
|
+
const cutoff = Date.now();
|
|
149
|
+
await getDbExec().execute({
|
|
150
|
+
sql: `DELETE FROM ${TABLE} WHERE owner = ? AND automation = ? AND started_at <= ?`,
|
|
151
|
+
args: [owner, automation, cutoff],
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
export async function listAutomationRuns(options) {
|
|
155
|
+
await ensureTable();
|
|
156
|
+
const owners = options.owners.filter(Boolean);
|
|
157
|
+
if (!owners.length)
|
|
158
|
+
return [];
|
|
159
|
+
const limit = Math.min(Math.max(options.limit ?? 20, 1), 100);
|
|
160
|
+
const placeholders = owners.map(() => "?").join(", ");
|
|
161
|
+
const result = await getDbExec().execute({
|
|
162
|
+
sql: `SELECT * FROM ${TABLE} WHERE owner IN (${placeholders}) AND automation = ?
|
|
163
|
+
ORDER BY started_at DESC LIMIT ${limit}`,
|
|
164
|
+
args: [...owners, options.automation],
|
|
165
|
+
});
|
|
166
|
+
const now = Date.now();
|
|
167
|
+
return (result.rows ?? []).map((row) => toRun(row, now));
|
|
168
|
+
}
|
|
169
|
+
//# sourceMappingURL=run-history.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"run-history.js","sourceRoot":"","sources":["../../src/jobs/run-history.ts"],"names":[],"mappings":"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","sourcesContent":["import { randomUUID } from \"node:crypto\";\n\nimport { getDbExec, intType, isPostgres } from \"../db/client.js\";\nimport { ensureIndexExists, ensureTableExists } from \"../db/ddl-guard.js\";\n\n/**\n * \"interrupted\" is derived at read time, never stored: a process killed\n * mid-run cannot write its own outcome, so a row left running long past the\n * point a run could still be alive is reported as interrupted rather than\n * shown as permanently in-flight.\n */\nexport type AutomationRunStatus =\n | \"running\"\n | \"success\"\n | \"error\"\n | \"interrupted\";\n\nexport interface AutomationRun {\n id: string;\n owner: string;\n automation: string;\n path: string;\n scope: string | null;\n orgId: string | null;\n runId: string | null;\n threadId: string | null;\n status: AutomationRunStatus;\n startedAt: number;\n finishedAt: number | null;\n error: string | null;\n}\n\nexport interface StartAutomationRunInput {\n owner: string;\n automation: string;\n path: string;\n scope?: string | null;\n orgId?: string | null;\n runId?: string | null;\n threadId?: string | null;\n}\n\nconst TABLE = \"automation_runs\";\nconst MAX_ERROR_LENGTH = 500;\n\n/**\n * Generous multiple of the runner's own 5 minute hard abort\n * (BACKGROUND_RUN_HARD_TIMEOUT_MS). Past this, no run is still alive.\n */\nconst RUN_LIVENESS_CEILING_MS = 15 * 60_000;\n\n/** Rows kept per automation, so a per-minute schedule cannot grow forever. */\nconst RUNS_RETAINED_PER_AUTOMATION = 50;\n\nlet _initPromise: Promise<void> | undefined;\n\nasync function ensureTable(): Promise<void> {\n if (!_initPromise) {\n _initPromise = (async () => {\n const client = getDbExec();\n const createSql = `\n CREATE TABLE IF NOT EXISTS ${TABLE} (\n id TEXT PRIMARY KEY,\n owner TEXT NOT NULL,\n automation TEXT NOT NULL,\n path TEXT NOT NULL,\n scope TEXT,\n org_id TEXT,\n run_id TEXT,\n thread_id TEXT,\n status TEXT NOT NULL DEFAULT 'running',\n started_at ${intType()} NOT NULL,\n finished_at ${intType()},\n error TEXT\n )\n `;\n const indexSql = `CREATE INDEX IF NOT EXISTS idx_${TABLE}_owner_automation ON ${TABLE} (owner, automation, started_at)`;\n\n if (isPostgres()) {\n await ensureTableExists(TABLE, createSql);\n await ensureIndexExists(`idx_${TABLE}_owner_automation`, indexSql);\n return;\n }\n\n await client.execute(createSql);\n await client.execute(indexSql);\n })().catch((err) => {\n _initPromise = undefined;\n throw err;\n });\n }\n return _initPromise;\n}\n\nfunction toRun(row: Record<string, unknown>, now: number): AutomationRun {\n const stored = String(row.status) as AutomationRunStatus;\n const startedAt = Number(row.started_at);\n const status: AutomationRunStatus =\n stored === \"running\" && now - startedAt > RUN_LIVENESS_CEILING_MS\n ? \"interrupted\"\n : stored;\n return {\n id: String(row.id),\n owner: String(row.owner),\n automation: String(row.automation),\n path: String(row.path),\n scope: row.scope == null ? null : String(row.scope),\n orgId: row.org_id == null ? null : String(row.org_id),\n runId: row.run_id == null ? null : String(row.run_id),\n threadId: row.thread_id == null ? null : String(row.thread_id),\n status,\n startedAt,\n finishedAt: row.finished_at == null ? null : Number(row.finished_at),\n error: row.error == null ? null : String(row.error),\n };\n}\n\n/**\n * Record that an automation actually began executing. Returns the run id used\n * to close the record out. Only real executions get a row — a tick that\n * declined to run the automation must not appear in its history.\n */\nexport async function startAutomationRun(\n input: StartAutomationRunInput,\n): Promise<string> {\n await ensureTable();\n const id = randomUUID();\n await getDbExec().execute({\n sql: `INSERT INTO ${TABLE} (id, owner, automation, path, scope, org_id, run_id, thread_id, status, started_at)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, 'running', ?)`,\n args: [\n id,\n input.owner,\n input.automation,\n input.path,\n input.scope ?? null,\n input.orgId ?? null,\n input.runId ?? null,\n input.threadId ?? null,\n Date.now(),\n ],\n });\n await pruneAutomationRuns(input.owner, input.automation);\n return id;\n}\n\n/**\n * Drop the oldest rows for one automation once it exceeds the retention cap.\n *\n * Pruning on insert keeps the table bounded by the number of automations\n * rather than by how often they run, and avoids a separate sweeper. The\n * derived table is aliased because Postgres requires it.\n */\nasync function pruneAutomationRuns(\n owner: string,\n automation: string,\n): Promise<void> {\n await getDbExec().execute({\n sql: `DELETE FROM ${TABLE}\n WHERE owner = ? AND automation = ? AND started_at < (\n SELECT MIN(started_at) FROM (\n SELECT started_at FROM ${TABLE}\n WHERE owner = ? AND automation = ?\n ORDER BY started_at DESC\n LIMIT ${RUNS_RETAINED_PER_AUTOMATION}\n ) recent\n )`,\n args: [owner, automation, owner, automation],\n });\n}\n\nexport async function finishAutomationRun(\n id: string,\n status: Exclude<AutomationRunStatus, \"running\">,\n error?: string,\n): Promise<void> {\n await ensureTable();\n await getDbExec().execute({\n sql: `UPDATE ${TABLE} SET status = ?, finished_at = ?, error = ? WHERE id = ?`,\n args: [status, Date.now(), error?.slice(0, MAX_ERROR_LENGTH) ?? null, id],\n });\n}\n\n/**\n * Attach the agent thread once it exists. The thread is created after the run\n * row so the history survives a crash between the two.\n */\nexport async function attachAutomationRunThread(\n id: string,\n threadId: string,\n runId: string,\n): Promise<void> {\n await ensureTable();\n await getDbExec().execute({\n sql: `UPDATE ${TABLE} SET thread_id = ?, run_id = ? WHERE id = ?`,\n args: [threadId, runId, id],\n });\n}\n\n/**\n * Forget an automation's executions.\n *\n * History is keyed by the automation's name, which is reusable: deleting\n * \"digest\" and creating a new \"digest\" would otherwise show the old\n * definition's runs as the new one's history.\n */\nexport async function deleteAutomationRuns(\n owner: string,\n automation: string,\n): Promise<void> {\n await ensureTable();\n // Bounded to runs that had already started. Names are reusable, so if a new\n // automation takes this name and starts running before the cleanup lands,\n // the cutoff keeps that run's history from being swept up with the old\n // definition's.\n const cutoff = Date.now();\n await getDbExec().execute({\n sql: `DELETE FROM ${TABLE} WHERE owner = ? AND automation = ? AND started_at <= ?`,\n args: [owner, automation, cutoff],\n });\n}\n\nexport async function listAutomationRuns(options: {\n owners: string[];\n automation: string;\n limit?: number;\n}): Promise<AutomationRun[]> {\n await ensureTable();\n const owners = options.owners.filter(Boolean);\n if (!owners.length) return [];\n const limit = Math.min(Math.max(options.limit ?? 20, 1), 100);\n const placeholders = owners.map(() => \"?\").join(\", \");\n const result = await getDbExec().execute({\n sql: `SELECT * FROM ${TABLE} WHERE owner IN (${placeholders}) AND automation = ?\n ORDER BY started_at DESC LIMIT ${limit}`,\n args: [...owners, options.automation],\n });\n const now = Date.now();\n return (result.rows ?? []).map((row) =>\n toRun(row as Record<string, unknown>, now),\n );\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scheduler.d.ts","sourceRoot":"","sources":["../../src/jobs/scheduler.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"scheduler.d.ts","sourceRoot":"","sources":["../../src/jobs/scheduler.ts"],"names":[],"mappings":"AAMA,OAAO,EACL,yBAAyB,EAIzB,KAAK,2BAA2B,EAChC,KAAK,wBAAwB,EAC9B,MAAM,mCAAmC,CAAC;AAO3C,OAAO,EAGL,KAAK,cAAc,EACpB,MAAM,kBAAkB,CAAC;AAI1B,OAAO,EACL,sBAAsB,EACtB,mBAAmB,EACnB,oBAAoB,EACpB,gBAAgB,EAChB,KAAK,cAAc,EACnB,KAAK,yBAAyB,GAC/B,MAAM,kBAAkB,CAAC;AAE1B,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,MAAM,GAAG;IACpD,IAAI,EAAE,cAAc,CAAC;IACrB,IAAI,EAAE,MAAM,CAAC;CACd,CAGA;AAED,wBAAgB,eAAe,CAAC,IAAI,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAE1E;AAID,MAAM,MAAM,mBAAmB,GAAG,2BAA2B,CAAC;AAE9D,MAAM,WAAW,aAAc,SAAQ,wBAAwB;IAC7D;;;;;;;;;;OAUG;IACH,mBAAmB,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,mBAAmB,KAAK,MAAM,EAAE,GAAG,SAAS,CAAC;CAC3E;AAgCD;;;GAGG;AACH,wBAAsB,oBAAoB,CAAC,IAAI,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CA0F7E;AAED,eAAO,MAAM,kBAAkB,kCAA4B,CAAC"}
|
package/dist/jobs/scheduler.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { resourceListAllOwners, resourcePut, } from "../resources/store.js";
|
|
1
|
+
import { resourceGetByPath, resourceListAllOwners, resourcePut, } from "../resources/store.js";
|
|
2
2
|
import { backgroundRunCutOffReason, isBackgroundAutomationRunActive, resolveBackgroundAutomationIdentity, runBackgroundAutomation, } from "./background-automation-runner.js";
|
|
3
|
-
import { nextOccurrence, isValidCron, describeCron } from "./cron.js";
|
|
3
|
+
import { nextOccurrence, isValidCron, describeCron, effectiveTimezone, } from "./cron.js";
|
|
4
4
|
import { buildJobResourceContent, parseJobResource, } from "./frontmatter.js";
|
|
5
5
|
// ─── Frontmatter parsing ────────────────────────────────────────────────────
|
|
6
6
|
export { classifyJobFrontmatter, classifyJobResource, normalizeJobMcpTools, parseJobResource, } from "./frontmatter.js";
|
|
@@ -79,7 +79,7 @@ export async function processRecurringJobs(deps) {
|
|
|
79
79
|
// Stuck — reset so the next check can re-run it
|
|
80
80
|
meta.lastStatus = "error";
|
|
81
81
|
meta.lastError = "Job timed out or server crashed mid-run";
|
|
82
|
-
const next = nextOccurrence(meta.schedule, now);
|
|
82
|
+
const next = nextOccurrence(meta.schedule, now, meta.timezone);
|
|
83
83
|
meta.nextRun = next.toISOString();
|
|
84
84
|
await updateResource(resource, meta, body);
|
|
85
85
|
continue;
|
|
@@ -95,7 +95,7 @@ export async function processRecurringJobs(deps) {
|
|
|
95
95
|
// real next occurrence. Computing from new Date(0) (the epoch) always
|
|
96
96
|
// returns a 1970 date, which is < now, so the job would fire
|
|
97
97
|
// immediately on first sight regardless of its schedule.
|
|
98
|
-
const next = nextOccurrence(meta.schedule, now);
|
|
98
|
+
const next = nextOccurrence(meta.schedule, now, meta.timezone);
|
|
99
99
|
meta.nextRun = next.toISOString();
|
|
100
100
|
await updateResource(resource, meta, body);
|
|
101
101
|
continue;
|
|
@@ -146,10 +146,16 @@ async function executeJob(resource, meta, body, deps, now) {
|
|
|
146
146
|
console.warn(`[recurring-jobs] Skipping job "${jobName}": ${identity.reason}. ` +
|
|
147
147
|
`User/membership no longer valid — leaving cron entry for admin review.`);
|
|
148
148
|
// Mark as skipped without resetting nextRun so an admin can find it.
|
|
149
|
-
|
|
149
|
+
// `lastRun` is deliberately untouched: the job did not run, and stamping
|
|
150
|
+
// it here made a permanently blocked job look like it ran every minute.
|
|
151
|
+
// Re-writing an unchanged resource on every tick also churns the poll
|
|
152
|
+
// stream, so only persist when the failure state actually changed.
|
|
153
|
+
const alreadyRecorded = meta.lastStatus === "skipped" && meta.lastError === identity.reason;
|
|
154
|
+
meta.lastCheck = now.toISOString();
|
|
150
155
|
meta.lastStatus = "skipped";
|
|
151
156
|
meta.lastError = identity.reason;
|
|
152
|
-
|
|
157
|
+
if (!alreadyRecorded)
|
|
158
|
+
await updateResource(resource, meta, body);
|
|
153
159
|
return;
|
|
154
160
|
}
|
|
155
161
|
const jobUserEmail = identity.identity.userEmail;
|
|
@@ -188,29 +194,56 @@ async function executeJob(resource, meta, body, deps, now) {
|
|
|
188
194
|
automation: jobContext,
|
|
189
195
|
ownerEmail: jobUserEmail,
|
|
190
196
|
orgId: jobOrgId,
|
|
191
|
-
prompt: `[Recurring Job: ${jobName}]\nSchedule: ${describeCron(meta.schedule)}\n\nExecute the following job instructions:\n\n${body}`,
|
|
197
|
+
prompt: `[Recurring Job: ${jobName}]\nSchedule: ${describeCron(meta.schedule, effectiveTimezone(meta.timezone))}\n\nExecute the following job instructions:\n\n${body}`,
|
|
192
198
|
threadTitle: `Job: ${jobName} — ${now.toLocaleDateString()}`,
|
|
193
199
|
runIdPrefix: `job-${jobName}`,
|
|
194
200
|
usageLabel: `recurring-job:${jobName}`,
|
|
195
201
|
requestContext,
|
|
196
202
|
}, deps);
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
203
|
+
await recordExecutionOutcome(resource, {
|
|
204
|
+
lastRun: meta.lastRun,
|
|
205
|
+
lastStatus: "success",
|
|
206
|
+
lastError: undefined,
|
|
207
|
+
});
|
|
208
|
+
console.log(`[recurring-jobs] Job "${jobName}" completed.`);
|
|
202
209
|
}
|
|
203
210
|
catch (err) {
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
211
|
+
const lastError = err instanceof Error ? err.message.slice(0, 200) : "Unknown error";
|
|
212
|
+
await recordExecutionOutcome(resource, {
|
|
213
|
+
lastRun: meta.lastRun,
|
|
214
|
+
lastStatus: "error",
|
|
215
|
+
lastError,
|
|
216
|
+
});
|
|
217
|
+
console.error(`[recurring-jobs] Job "${jobName}" failed:`, lastError);
|
|
210
218
|
}
|
|
211
219
|
}
|
|
212
220
|
async function updateResource(resource, meta, body) {
|
|
213
221
|
const content = buildJobContent(meta, body);
|
|
214
222
|
await resourcePut(resource.owner, resource.path, content);
|
|
215
223
|
}
|
|
224
|
+
/**
|
|
225
|
+
* Persist the result of a run without clobbering a concurrent edit.
|
|
226
|
+
*
|
|
227
|
+
* A run holds its `meta` for as long as the job takes, so writing that whole
|
|
228
|
+
* snapshot back on completion would silently revert a schedule, timezone or
|
|
229
|
+
* instruction change made while it was running. Only the execution fields are
|
|
230
|
+
* ours to write, and `nextRun` is recomputed from whatever schedule is stored
|
|
231
|
+
* now, so an edit mid-run takes effect on the next tick.
|
|
232
|
+
*/
|
|
233
|
+
async function recordExecutionOutcome(resource, outcome) {
|
|
234
|
+
const latest = await resourceGetByPath(resource.owner, resource.path);
|
|
235
|
+
if (!latest) {
|
|
236
|
+
// Deleted while it was running. Writing the pre-run snapshot back would
|
|
237
|
+
// resurrect the automation and schedule it again.
|
|
238
|
+
console.log(`[recurring-jobs] "${resource.path}" was deleted mid-run; dropping its outcome.`);
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
const current = parseJobResource(latest.content);
|
|
242
|
+
const meta = { ...current.meta, ...outcome };
|
|
243
|
+
if (meta.schedule && isValidCron(meta.schedule)) {
|
|
244
|
+
// Measured from completion so a long run cannot immediately re-fire.
|
|
245
|
+
meta.nextRun = nextOccurrence(meta.schedule, new Date(), meta.timezone).toISOString();
|
|
246
|
+
}
|
|
247
|
+
await updateResource(resource, meta, current.body);
|
|
248
|
+
}
|
|
216
249
|
//# sourceMappingURL=scheduler.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scheduler.js","sourceRoot":"","sources":["../../src/jobs/scheduler.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n resourceListAllOwners,\n resourcePut,\n type Resource,\n} from \"../resources/store.js\";\nimport {\n backgroundRunCutOffReason,\n isBackgroundAutomationRunActive,\n resolveBackgroundAutomationIdentity,\n runBackgroundAutomation,\n type BackgroundAutomationContext,\n type BackgroundAutomationDeps,\n} from \"./background-automation-runner.js\";\nimport { nextOccurrence, isValidCron, describeCron } from \"./cron.js\";\nimport {\n buildJobResourceContent,\n parseJobResource,\n type JobFrontmatter,\n} from \"./frontmatter.js\";\n\n// ─── Frontmatter parsing ────────────────────────────────────────────────────\n\nexport {\n classifyJobFrontmatter,\n classifyJobResource,\n normalizeJobMcpTools,\n parseJobResource,\n type JobFrontmatter,\n type JobResourceClassification,\n} from \"./frontmatter.js\";\n\nexport function parseJobFrontmatter(content: string): {\n meta: JobFrontmatter;\n body: string;\n} {\n const { meta, body } = parseJobResource(content);\n return { meta, body };\n}\n\nexport function buildJobContent(meta: JobFrontmatter, body: string): string {\n return buildJobResourceContent(meta, body);\n}\n\n// ─── Job execution ──────────────────────────────────────────────────────────\n\nexport type RecurringJobContext = BackgroundAutomationContext;\n\nexport interface SchedulerDeps extends BackgroundAutomationDeps {\n /**\n * Tool names to expose on the FIRST engine request for a job run. When\n * provided, every other action returned by `getActions()` is deferred\n * behind an attached `tool-search` entry instead of being serialized on\n * every scheduled tick — `runAgentLoop`'s mid-run tool expansion\n * (`expandActiveTools`) still lets the model discover and call them after\n * a search. Omit to keep the full `getActions()` set visible up front\n * (current behavior). The caller (not this module) knows which of the\n * merged actions are the app's own vs. framework additions, so this must\n * be supplied explicitly rather than inferred here.\n */\n getInitialToolNames?: (job?: RecurringJobContext) => string[] | undefined;\n}\n\nlet _isRunning = false;\n\n// Skip the DB query on every tick if we recently confirmed no jobs exist.\n// `_hasJobsCache` is invalidated whenever a `jobs/*` resource is written or\n// deleted (subscribed below), and refreshed at most every 5 minutes.\nlet _hasJobsCache: boolean | undefined;\nlet _lastJobsCheck = 0;\nconst JOBS_CHECK_INTERVAL_MS = 5 * 60_000;\nlet _emitterSubscribed = false;\n\nfunction subscribeToJobsResourceEvents(): void {\n if (_emitterSubscribed) return;\n _emitterSubscribed = true;\n // Lazy import to avoid circular deps at module load\n import(\"../resources/emitter.js\")\n .then(({ getResourcesEmitter }) => {\n getResourcesEmitter().on(\"resources\", (event: any) => {\n if (typeof event?.path === \"string\" && event.path.startsWith(\"jobs/\")) {\n _hasJobsCache = undefined;\n }\n });\n })\n .catch((err) => {\n console.warn(\n \"[jobs] resource-event subscription failed:\",\n err instanceof Error ? err.message : err,\n );\n });\n}\n\n/**\n * Process all due recurring jobs. Called every 60 seconds.\n * Sequential execution with 5-minute timeout per job.\n */\nexport async function processRecurringJobs(deps: SchedulerDeps): Promise<void> {\n // Prevent concurrent runs\n if (_isRunning) return;\n\n subscribeToJobsResourceEvents();\n\n // Skip if we recently confirmed there are no job resources to run.\n const nowMs = Date.now();\n if (\n _hasJobsCache === false &&\n nowMs - _lastJobsCheck < JOBS_CHECK_INTERVAL_MS\n ) {\n return;\n }\n\n _isRunning = true;\n\n try {\n const jobResources = await resourceListAllOwners(\"jobs/\");\n _hasJobsCache = jobResources.some(\n (r) => r.path.endsWith(\".md\") && !r.path.endsWith(\".keep\"),\n );\n _lastJobsCheck = nowMs;\n if (!_hasJobsCache) return;\n const now = new Date();\n\n for (const resource of jobResources) {\n // Skip non-markdown or .keep files\n if (!resource.path.endsWith(\".md\")) continue;\n if (resource.path.endsWith(\".keep\")) continue;\n\n const { meta, body } = parseJobFrontmatter(resource.content);\n\n // Skip disabled or missing schedule\n if (!meta.enabled || !meta.schedule) continue;\n if (!isValidCron(meta.schedule)) continue;\n\n // Skip if currently running, unless it has been stuck for more than 10 minutes\n // (server crash mid-job leaves lastStatus=running forever without this guard)\n if (meta.lastStatus === \"running\") {\n if (isBackgroundAutomationRunActive(meta, now)) continue;\n // Stuck — reset so the next check can re-run it\n meta.lastStatus = \"error\";\n meta.lastError = \"Job timed out or server crashed mid-run\";\n const next = nextOccurrence(meta.schedule, now);\n meta.nextRun = next.toISOString();\n await updateResource(resource, meta, body);\n continue;\n }\n\n // Check if due\n if (meta.nextRun) {\n const nextRunDate = new Date(meta.nextRun);\n if (nextRunDate > now) continue;\n } else {\n // No nextRun computed yet — seed it from `now` so the job waits for its\n // real next occurrence. Computing from new Date(0) (the epoch) always\n // returns a 1970 date, which is < now, so the job would fire\n // immediately on first sight regardless of its schedule.\n const next = nextOccurrence(meta.schedule, now);\n meta.nextRun = next.toISOString();\n await updateResource(resource, meta, body);\n continue;\n }\n\n // Skip if body is empty\n if (!body.trim()) continue;\n\n // Execute the job\n await executeJob(resource, meta, body, deps, now);\n }\n } catch (err) {\n // Transient WS / connection drops (Neon serverless): silently retry next\n // tick instead of spamming stderr — `retryOnConnectionError` already did\n // its retry budget at the driver level.\n const { isConnectionError } = await import(\"../db/client.js\");\n if (isConnectionError(err)) {\n _hasJobsCache = undefined; // force re-check on next successful tick\n _lastJobsCheck = 0;\n return;\n }\n // Unwrap ErrorEvent (Neon WS driver emits these on network failure) so logs show the real cause\n const detail =\n err instanceof Error\n ? err\n : ((err as any)?.error ?? (err as any)?.message ?? err);\n console.error(\"[recurring-jobs] Error processing jobs:\", detail);\n } finally {\n _isRunning = false;\n }\n}\n\nexport const jobRunCutOffReason = backgroundRunCutOffReason;\n\nasync function executeJob(\n resource: Resource,\n meta: JobFrontmatter,\n body: string,\n deps: SchedulerDeps,\n now: Date,\n): Promise<void> {\n const jobName = resource.path.replace(/^jobs\\//, \"\").replace(/\\.md$/, \"\");\n\n const jobContext: RecurringJobContext = {\n name: jobName,\n meta,\n body,\n resource,\n };\n const identity = await resolveBackgroundAutomationIdentity(jobContext);\n\n // SECURITY (audit 12 #10): re-validate the run-as user/membership on\n // every tick. Sharing revocation, user deletion, and org-member removal\n // must take effect for already-scheduled jobs. Skip the tick on\n // failure; leave the cron entry alone so an admin can purge after\n // investigation.\n if (!identity.ok) {\n console.warn(\n `[recurring-jobs] Skipping job \"${jobName}\": ${identity.reason}. ` +\n `User/membership no longer valid — leaving cron entry for admin review.`,\n );\n // Mark as skipped without resetting nextRun so an admin can find it.\n meta.lastRun = now.toISOString();\n meta.lastStatus = \"skipped\";\n meta.lastError = identity.reason;\n await updateResource(resource, meta, body);\n return;\n }\n const jobUserEmail = identity.identity.userEmail;\n const jobOrgId = identity.identity.orgId;\n\n // Mark as running\n meta.lastRun = now.toISOString();\n meta.lastStatus = \"running\";\n meta.lastError = undefined;\n await updateResource(resource, meta, body);\n\n const requestContext =\n meta.originScopeId && meta.deliveryPlatform && meta.deliveryDestination\n ? {\n isIntegrationCaller: true as const,\n integration: {\n taskId: `job:${jobName}:${now.getTime()}`,\n scopeId: meta.originScopeId,\n principalType: \"service\" as const,\n incoming: {\n platform: meta.deliveryPlatform,\n externalThreadId: `${meta.deliveryTenantId || \"unknown\"}:${meta.deliveryDestination}:${meta.deliveryThreadRef || \"root\"}`,\n text: \"\",\n tenantId: meta.deliveryTenantId,\n integrationScopeId: meta.originScopeId,\n platformContext: {\n channelId: meta.deliveryDestination,\n threadTs: meta.deliveryThreadRef,\n teamId: meta.deliveryTenantId,\n },\n threadRef: meta.deliveryThreadRef,\n timestamp: now.getTime(),\n },\n },\n }\n : undefined;\n\n try {\n await runBackgroundAutomation(\n {\n automation: jobContext,\n ownerEmail: jobUserEmail,\n orgId: jobOrgId,\n prompt: `[Recurring Job: ${jobName}]\\nSchedule: ${describeCron(meta.schedule)}\\n\\nExecute the following job instructions:\\n\\n${body}`,\n threadTitle: `Job: ${jobName} — ${now.toLocaleDateString()}`,\n runIdPrefix: `job-${jobName}`,\n usageLabel: `recurring-job:${jobName}`,\n requestContext,\n },\n deps,\n );\n\n // Compute from completion time so long runs cannot immediately re-fire.\n meta.lastStatus = \"success\";\n meta.nextRun = nextOccurrence(meta.schedule, new Date()).toISOString();\n await updateResource(resource, meta, body);\n console.log(\n `[recurring-jobs] Job \"${jobName}\" completed. Next run: ${meta.nextRun}`,\n );\n } catch (err) {\n meta.lastStatus = \"error\";\n meta.lastError =\n err instanceof Error ? err.message.slice(0, 200) : \"Unknown error\";\n meta.nextRun = nextOccurrence(meta.schedule, new Date()).toISOString();\n await updateResource(resource, meta, body);\n console.error(`[recurring-jobs] Job \"${jobName}\" failed:`, meta.lastError);\n }\n}\n\nasync function updateResource(\n resource: Resource,\n meta: JobFrontmatter,\n body: string,\n): Promise<void> {\n const content = buildJobContent(meta, body);\n await resourcePut(resource.owner, resource.path, content);\n}\n"]}
|
|
1
|
+
{"version":3,"file":"scheduler.js","sourceRoot":"","sources":["../../src/jobs/scheduler.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n resourceGetByPath,\n resourceListAllOwners,\n resourcePut,\n type Resource,\n} from \"../resources/store.js\";\nimport {\n backgroundRunCutOffReason,\n isBackgroundAutomationRunActive,\n resolveBackgroundAutomationIdentity,\n runBackgroundAutomation,\n type BackgroundAutomationContext,\n type BackgroundAutomationDeps,\n} from \"./background-automation-runner.js\";\nimport {\n nextOccurrence,\n isValidCron,\n describeCron,\n effectiveTimezone,\n} from \"./cron.js\";\nimport {\n buildJobResourceContent,\n parseJobResource,\n type JobFrontmatter,\n} from \"./frontmatter.js\";\n\n// ─── Frontmatter parsing ────────────────────────────────────────────────────\n\nexport {\n classifyJobFrontmatter,\n classifyJobResource,\n normalizeJobMcpTools,\n parseJobResource,\n type JobFrontmatter,\n type JobResourceClassification,\n} from \"./frontmatter.js\";\n\nexport function parseJobFrontmatter(content: string): {\n meta: JobFrontmatter;\n body: string;\n} {\n const { meta, body } = parseJobResource(content);\n return { meta, body };\n}\n\nexport function buildJobContent(meta: JobFrontmatter, body: string): string {\n return buildJobResourceContent(meta, body);\n}\n\n// ─── Job execution ──────────────────────────────────────────────────────────\n\nexport type RecurringJobContext = BackgroundAutomationContext;\n\nexport interface SchedulerDeps extends BackgroundAutomationDeps {\n /**\n * Tool names to expose on the FIRST engine request for a job run. When\n * provided, every other action returned by `getActions()` is deferred\n * behind an attached `tool-search` entry instead of being serialized on\n * every scheduled tick — `runAgentLoop`'s mid-run tool expansion\n * (`expandActiveTools`) still lets the model discover and call them after\n * a search. Omit to keep the full `getActions()` set visible up front\n * (current behavior). The caller (not this module) knows which of the\n * merged actions are the app's own vs. framework additions, so this must\n * be supplied explicitly rather than inferred here.\n */\n getInitialToolNames?: (job?: RecurringJobContext) => string[] | undefined;\n}\n\nlet _isRunning = false;\n\n// Skip the DB query on every tick if we recently confirmed no jobs exist.\n// `_hasJobsCache` is invalidated whenever a `jobs/*` resource is written or\n// deleted (subscribed below), and refreshed at most every 5 minutes.\nlet _hasJobsCache: boolean | undefined;\nlet _lastJobsCheck = 0;\nconst JOBS_CHECK_INTERVAL_MS = 5 * 60_000;\nlet _emitterSubscribed = false;\n\nfunction subscribeToJobsResourceEvents(): void {\n if (_emitterSubscribed) return;\n _emitterSubscribed = true;\n // Lazy import to avoid circular deps at module load\n import(\"../resources/emitter.js\")\n .then(({ getResourcesEmitter }) => {\n getResourcesEmitter().on(\"resources\", (event: any) => {\n if (typeof event?.path === \"string\" && event.path.startsWith(\"jobs/\")) {\n _hasJobsCache = undefined;\n }\n });\n })\n .catch((err) => {\n console.warn(\n \"[jobs] resource-event subscription failed:\",\n err instanceof Error ? err.message : err,\n );\n });\n}\n\n/**\n * Process all due recurring jobs. Called every 60 seconds.\n * Sequential execution with 5-minute timeout per job.\n */\nexport async function processRecurringJobs(deps: SchedulerDeps): Promise<void> {\n // Prevent concurrent runs\n if (_isRunning) return;\n\n subscribeToJobsResourceEvents();\n\n // Skip if we recently confirmed there are no job resources to run.\n const nowMs = Date.now();\n if (\n _hasJobsCache === false &&\n nowMs - _lastJobsCheck < JOBS_CHECK_INTERVAL_MS\n ) {\n return;\n }\n\n _isRunning = true;\n\n try {\n const jobResources = await resourceListAllOwners(\"jobs/\");\n _hasJobsCache = jobResources.some(\n (r) => r.path.endsWith(\".md\") && !r.path.endsWith(\".keep\"),\n );\n _lastJobsCheck = nowMs;\n if (!_hasJobsCache) return;\n const now = new Date();\n\n for (const resource of jobResources) {\n // Skip non-markdown or .keep files\n if (!resource.path.endsWith(\".md\")) continue;\n if (resource.path.endsWith(\".keep\")) continue;\n\n const { meta, body } = parseJobFrontmatter(resource.content);\n\n // Skip disabled or missing schedule\n if (!meta.enabled || !meta.schedule) continue;\n if (!isValidCron(meta.schedule)) continue;\n\n // Skip if currently running, unless it has been stuck for more than 10 minutes\n // (server crash mid-job leaves lastStatus=running forever without this guard)\n if (meta.lastStatus === \"running\") {\n if (isBackgroundAutomationRunActive(meta, now)) continue;\n // Stuck — reset so the next check can re-run it\n meta.lastStatus = \"error\";\n meta.lastError = \"Job timed out or server crashed mid-run\";\n const next = nextOccurrence(meta.schedule, now, meta.timezone);\n meta.nextRun = next.toISOString();\n await updateResource(resource, meta, body);\n continue;\n }\n\n // Check if due\n if (meta.nextRun) {\n const nextRunDate = new Date(meta.nextRun);\n if (nextRunDate > now) continue;\n } else {\n // No nextRun computed yet — seed it from `now` so the job waits for its\n // real next occurrence. Computing from new Date(0) (the epoch) always\n // returns a 1970 date, which is < now, so the job would fire\n // immediately on first sight regardless of its schedule.\n const next = nextOccurrence(meta.schedule, now, meta.timezone);\n meta.nextRun = next.toISOString();\n await updateResource(resource, meta, body);\n continue;\n }\n\n // Skip if body is empty\n if (!body.trim()) continue;\n\n // Execute the job\n await executeJob(resource, meta, body, deps, now);\n }\n } catch (err) {\n // Transient WS / connection drops (Neon serverless): silently retry next\n // tick instead of spamming stderr — `retryOnConnectionError` already did\n // its retry budget at the driver level.\n const { isConnectionError } = await import(\"../db/client.js\");\n if (isConnectionError(err)) {\n _hasJobsCache = undefined; // force re-check on next successful tick\n _lastJobsCheck = 0;\n return;\n }\n // Unwrap ErrorEvent (Neon WS driver emits these on network failure) so logs show the real cause\n const detail =\n err instanceof Error\n ? err\n : ((err as any)?.error ?? (err as any)?.message ?? err);\n console.error(\"[recurring-jobs] Error processing jobs:\", detail);\n } finally {\n _isRunning = false;\n }\n}\n\nexport const jobRunCutOffReason = backgroundRunCutOffReason;\n\nasync function executeJob(\n resource: Resource,\n meta: JobFrontmatter,\n body: string,\n deps: SchedulerDeps,\n now: Date,\n): Promise<void> {\n const jobName = resource.path.replace(/^jobs\\//, \"\").replace(/\\.md$/, \"\");\n\n const jobContext: RecurringJobContext = {\n name: jobName,\n meta,\n body,\n resource,\n };\n const identity = await resolveBackgroundAutomationIdentity(jobContext);\n\n // SECURITY (audit 12 #10): re-validate the run-as user/membership on\n // every tick. Sharing revocation, user deletion, and org-member removal\n // must take effect for already-scheduled jobs. Skip the tick on\n // failure; leave the cron entry alone so an admin can purge after\n // investigation.\n if (!identity.ok) {\n console.warn(\n `[recurring-jobs] Skipping job \"${jobName}\": ${identity.reason}. ` +\n `User/membership no longer valid — leaving cron entry for admin review.`,\n );\n // Mark as skipped without resetting nextRun so an admin can find it.\n // `lastRun` is deliberately untouched: the job did not run, and stamping\n // it here made a permanently blocked job look like it ran every minute.\n // Re-writing an unchanged resource on every tick also churns the poll\n // stream, so only persist when the failure state actually changed.\n const alreadyRecorded =\n meta.lastStatus === \"skipped\" && meta.lastError === identity.reason;\n meta.lastCheck = now.toISOString();\n meta.lastStatus = \"skipped\";\n meta.lastError = identity.reason;\n if (!alreadyRecorded) await updateResource(resource, meta, body);\n return;\n }\n const jobUserEmail = identity.identity.userEmail;\n const jobOrgId = identity.identity.orgId;\n\n // Mark as running\n meta.lastRun = now.toISOString();\n meta.lastStatus = \"running\";\n meta.lastError = undefined;\n await updateResource(resource, meta, body);\n\n const requestContext =\n meta.originScopeId && meta.deliveryPlatform && meta.deliveryDestination\n ? {\n isIntegrationCaller: true as const,\n integration: {\n taskId: `job:${jobName}:${now.getTime()}`,\n scopeId: meta.originScopeId,\n principalType: \"service\" as const,\n incoming: {\n platform: meta.deliveryPlatform,\n externalThreadId: `${meta.deliveryTenantId || \"unknown\"}:${meta.deliveryDestination}:${meta.deliveryThreadRef || \"root\"}`,\n text: \"\",\n tenantId: meta.deliveryTenantId,\n integrationScopeId: meta.originScopeId,\n platformContext: {\n channelId: meta.deliveryDestination,\n threadTs: meta.deliveryThreadRef,\n teamId: meta.deliveryTenantId,\n },\n threadRef: meta.deliveryThreadRef,\n timestamp: now.getTime(),\n },\n },\n }\n : undefined;\n\n try {\n await runBackgroundAutomation(\n {\n automation: jobContext,\n ownerEmail: jobUserEmail,\n orgId: jobOrgId,\n prompt: `[Recurring Job: ${jobName}]\\nSchedule: ${describeCron(meta.schedule, effectiveTimezone(meta.timezone))}\\n\\nExecute the following job instructions:\\n\\n${body}`,\n threadTitle: `Job: ${jobName} — ${now.toLocaleDateString()}`,\n runIdPrefix: `job-${jobName}`,\n usageLabel: `recurring-job:${jobName}`,\n requestContext,\n },\n deps,\n );\n\n await recordExecutionOutcome(resource, {\n lastRun: meta.lastRun,\n lastStatus: \"success\",\n lastError: undefined,\n });\n console.log(`[recurring-jobs] Job \"${jobName}\" completed.`);\n } catch (err) {\n const lastError =\n err instanceof Error ? err.message.slice(0, 200) : \"Unknown error\";\n await recordExecutionOutcome(resource, {\n lastRun: meta.lastRun,\n lastStatus: \"error\",\n lastError,\n });\n console.error(`[recurring-jobs] Job \"${jobName}\" failed:`, lastError);\n }\n}\n\nasync function updateResource(\n resource: Resource,\n meta: JobFrontmatter,\n body: string,\n): Promise<void> {\n const content = buildJobContent(meta, body);\n await resourcePut(resource.owner, resource.path, content);\n}\n\n/** Execution bookkeeping the scheduler owns; the rest belongs to the editor. */\ntype ExecutionOutcome = Pick<\n JobFrontmatter,\n \"lastRun\" | \"lastCheck\" | \"lastStatus\" | \"lastError\"\n>;\n\n/**\n * Persist the result of a run without clobbering a concurrent edit.\n *\n * A run holds its `meta` for as long as the job takes, so writing that whole\n * snapshot back on completion would silently revert a schedule, timezone or\n * instruction change made while it was running. Only the execution fields are\n * ours to write, and `nextRun` is recomputed from whatever schedule is stored\n * now, so an edit mid-run takes effect on the next tick.\n */\nasync function recordExecutionOutcome(\n resource: Resource,\n outcome: ExecutionOutcome,\n): Promise<void> {\n const latest = await resourceGetByPath(resource.owner, resource.path);\n if (!latest) {\n // Deleted while it was running. Writing the pre-run snapshot back would\n // resurrect the automation and schedule it again.\n console.log(\n `[recurring-jobs] \"${resource.path}\" was deleted mid-run; dropping its outcome.`,\n );\n return;\n }\n const current = parseJobResource(latest.content);\n\n const meta: JobFrontmatter = { ...current.meta, ...outcome };\n if (meta.schedule && isValidCron(meta.schedule)) {\n // Measured from completion so a long run cannot immediately re-fire.\n meta.nextRun = nextOccurrence(\n meta.schedule,\n new Date(),\n meta.timezone,\n ).toISOString();\n }\n await updateResource(resource, meta, current.body);\n}\n"]}
|
package/dist/jobs/tools.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../../src/jobs/tools.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,8BAA8B,CAAC;
|
|
1
|
+
{"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../../src/jobs/tools.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,8BAA8B,CAAC;AAyBhE,OAAO,EAIL,KAAK,cAAc,EACpB,MAAM,gBAAgB,CAAC;AAsCxB;;;;;;;;;;GAUG;AACH,wBAAsB,oBAAoB,CACxC,aAAa,EAAE,MAAM,EACrB,IAAI,EAAE,cAAc,GACnB,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAkBxB;AAwQD,wBAAgB,cAAc,IAAI,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAmG5D"}
|