@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
|
@@ -11,13 +11,22 @@
|
|
|
11
11
|
* site — keeps the transactional look-and-feel consistent.
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
|
-
import { getAppName } from "./app-name.js";
|
|
14
|
+
import { getAppName, getAppSlug, getAppDescription } from "./app-name.js";
|
|
15
15
|
import { renderEmail, emailStrong } from "./email-template.js";
|
|
16
16
|
|
|
17
|
+
/** Shared reply-to for the framework's transactional emails. */
|
|
18
|
+
export const AGENT_NATIVE_REPLY_TO = "agent-native@builder.io";
|
|
19
|
+
|
|
17
20
|
export interface RenderedEmailMessage {
|
|
18
21
|
subject: string;
|
|
19
22
|
html: string;
|
|
20
23
|
text: string;
|
|
24
|
+
/**
|
|
25
|
+
* Per-app sender branding, applied by `sendEmail` only on first-party
|
|
26
|
+
* agent-native.com deployments. Self-hosted deployments keep the sender and
|
|
27
|
+
* reply-to they configured via EMAIL_FROM.
|
|
28
|
+
*/
|
|
29
|
+
appSender?: { name: string; slug: string; replyTo?: string };
|
|
21
30
|
}
|
|
22
31
|
|
|
23
32
|
/**
|
|
@@ -33,6 +42,17 @@ function resolveAppName(): string {
|
|
|
33
42
|
return stripCrlf(getAppName() || "Agent Native");
|
|
34
43
|
}
|
|
35
44
|
|
|
45
|
+
/**
|
|
46
|
+
* Recipient-facing brand for auth emails. Only a recognized first-party
|
|
47
|
+
* template is presented as "Agent-Native <App>"; a custom deployment keeps its
|
|
48
|
+
* own name so its users aren't told they signed up for an Agent Native app.
|
|
49
|
+
*/
|
|
50
|
+
function resolveBrand(slug: string | undefined): string {
|
|
51
|
+
const appName = getAppName();
|
|
52
|
+
if (!appName) return "Agent Native";
|
|
53
|
+
return slug ? `Agent-Native ${stripCrlf(appName)}` : stripCrlf(appName);
|
|
54
|
+
}
|
|
55
|
+
|
|
36
56
|
// ---------------------------------------------------------------------------
|
|
37
57
|
// Organization invitation
|
|
38
58
|
// ---------------------------------------------------------------------------
|
|
@@ -89,28 +109,58 @@ export interface RenderVerifySignupEmailArgs {
|
|
|
89
109
|
verifyUrl: string;
|
|
90
110
|
}
|
|
91
111
|
|
|
112
|
+
/**
|
|
113
|
+
* Customer-facing description overrides for the verification email body. The
|
|
114
|
+
* default descriptions come from the app-picker hints, which name competitors
|
|
115
|
+
* for internal positioning; these rewrites frame them as "replacement" for a
|
|
116
|
+
* customer-facing email without changing the picker copy.
|
|
117
|
+
*/
|
|
118
|
+
const VERIFY_EMAIL_DESCRIPTIONS: Record<string, string> = {
|
|
119
|
+
calendar:
|
|
120
|
+
"Agent-native Google Calendar replacement — manage events, sync, and public booking",
|
|
121
|
+
content:
|
|
122
|
+
"Open-source Obsidian/Notion replacement for MDX — edit local docs with agent assistance",
|
|
123
|
+
slides:
|
|
124
|
+
"Agent-native Google Slides replacement — generate and edit React presentations",
|
|
125
|
+
analytics:
|
|
126
|
+
"Agent-native Amplitude/Mixpanel replacement — connect data sources, prompt for charts",
|
|
127
|
+
mail: "Agent-native Superhuman replacement — email client with keyboard shortcuts and AI triage",
|
|
128
|
+
};
|
|
129
|
+
|
|
92
130
|
export function renderVerifySignupEmail(
|
|
93
131
|
args: RenderVerifySignupEmailArgs,
|
|
94
132
|
): RenderedEmailMessage {
|
|
95
133
|
const email = stripCrlf(args.email);
|
|
96
|
-
const
|
|
134
|
+
const slug = getAppSlug();
|
|
135
|
+
const brand = resolveBrand(slug);
|
|
136
|
+
const description = slug
|
|
137
|
+
? (VERIFY_EMAIL_DESCRIPTIONS[slug] ?? getAppDescription())
|
|
138
|
+
: undefined;
|
|
139
|
+
|
|
140
|
+
const paragraphs = [
|
|
141
|
+
`Thanks for signing up for ${emailStrong(brand)}. To finish creating your account, confirm that ${emailStrong(email)} is your email address.`,
|
|
142
|
+
];
|
|
143
|
+
if (description) {
|
|
144
|
+
paragraphs.push(`${stripCrlf(description).replace(/\.\s*$/, "")}.`);
|
|
145
|
+
}
|
|
146
|
+
paragraphs.push(`This link expires in 1 hour.`);
|
|
97
147
|
|
|
98
148
|
const { html, text } = renderEmail({
|
|
99
|
-
brandName:
|
|
100
|
-
preheader: `Confirm ${email} to finish setting up your ${
|
|
101
|
-
heading: `Verify your email for ${
|
|
102
|
-
paragraphs
|
|
103
|
-
`Thanks for signing up for ${emailStrong(appName)}. To finish creating your account, confirm that ${emailStrong(email)} is your email address.`,
|
|
104
|
-
`This link expires in 1 hour.`,
|
|
105
|
-
],
|
|
149
|
+
brandName: brand,
|
|
150
|
+
preheader: `Confirm ${email} to finish setting up your ${brand} account.`,
|
|
151
|
+
heading: `Verify your email for ${brand}`,
|
|
152
|
+
paragraphs,
|
|
106
153
|
cta: { label: "Verify email", url: args.verifyUrl },
|
|
107
154
|
footer: `If you didn't sign up, you can safely ignore this email.`,
|
|
108
155
|
});
|
|
109
156
|
|
|
110
157
|
return {
|
|
111
|
-
subject: `Verify your email for ${
|
|
158
|
+
subject: `Verify your email for ${brand}`,
|
|
112
159
|
html,
|
|
113
160
|
text,
|
|
161
|
+
appSender: slug
|
|
162
|
+
? { name: brand, slug, replyTo: AGENT_NATIVE_REPLY_TO }
|
|
163
|
+
: undefined,
|
|
114
164
|
};
|
|
115
165
|
}
|
|
116
166
|
|
|
@@ -129,12 +179,15 @@ export function renderResetPasswordEmail(
|
|
|
129
179
|
args: RenderResetPasswordEmailArgs,
|
|
130
180
|
): RenderedEmailMessage {
|
|
131
181
|
const email = stripCrlf(args.email);
|
|
132
|
-
|
|
182
|
+
// Match the verification email branding so password resets are clearly tied
|
|
183
|
+
// to the specific app. No value pitch here — it's a security email.
|
|
184
|
+
const slug = getAppSlug();
|
|
185
|
+
const brand = resolveBrand(slug);
|
|
133
186
|
|
|
134
187
|
const { html, text } = renderEmail({
|
|
135
|
-
brandName:
|
|
188
|
+
brandName: brand,
|
|
136
189
|
preheader: `Reset the password for ${email}. This link expires in 1 hour.`,
|
|
137
|
-
heading: `Reset your ${
|
|
190
|
+
heading: `Reset your ${brand} password`,
|
|
138
191
|
paragraphs: [
|
|
139
192
|
`Someone requested a password reset for ${emailStrong(email)}. Click the button below to choose a new password.`,
|
|
140
193
|
`This link expires in 1 hour.`,
|
|
@@ -144,8 +197,11 @@ export function renderResetPasswordEmail(
|
|
|
144
197
|
});
|
|
145
198
|
|
|
146
199
|
return {
|
|
147
|
-
subject: `Reset your ${
|
|
200
|
+
subject: `Reset your ${brand} password`,
|
|
148
201
|
html,
|
|
149
202
|
text,
|
|
203
|
+
appSender: slug
|
|
204
|
+
? { name: brand, slug, replyTo: AGENT_NATIVE_REPLY_TO }
|
|
205
|
+
: undefined,
|
|
150
206
|
};
|
|
151
207
|
}
|
|
@@ -39,6 +39,14 @@ export interface SendEmailArgs {
|
|
|
39
39
|
fromName?: string;
|
|
40
40
|
cc?: string | string[];
|
|
41
41
|
replyTo?: string;
|
|
42
|
+
/**
|
|
43
|
+
* Per-app branding for first-party agent-native.com deployments. Applied
|
|
44
|
+
* only when the configured EMAIL_FROM is already on agent-native.com, so a
|
|
45
|
+
* self-hosted deployment keeps its own verified sender and support mailbox
|
|
46
|
+
* instead of sending as an unverified address a provider would reject.
|
|
47
|
+
* An explicit `from` / `replyTo` always wins.
|
|
48
|
+
*/
|
|
49
|
+
appSender?: { name: string; slug: string; replyTo?: string };
|
|
42
50
|
inReplyTo?: string;
|
|
43
51
|
references?: string;
|
|
44
52
|
attachments?: EmailAttachment[];
|
|
@@ -150,6 +158,56 @@ function withDisplayName(from: string, name: string): string {
|
|
|
150
158
|
return `"${safe}" <${address}>`;
|
|
151
159
|
}
|
|
152
160
|
|
|
161
|
+
const AGENT_NATIVE_SENDER_DOMAIN = "agent-native.com";
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Resolve the per-app sender address, but only for deployments whose
|
|
165
|
+
* configured sender is already on agent-native.com. Any other (or missing)
|
|
166
|
+
* EMAIL_FROM means we cannot prove the branded address is a verified sender,
|
|
167
|
+
* so the deployment's own configuration is left untouched.
|
|
168
|
+
*/
|
|
169
|
+
let warnedAppSenderSuppressed = false;
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Suppressing the branding is correct for a sender we cannot prove we own,
|
|
173
|
+
* but it must not be invisible, or an operator sees generic senders with
|
|
174
|
+
* nothing pointing at why.
|
|
175
|
+
*
|
|
176
|
+
* EMAIL_FROM resolves per user/org/workspace through the scoped secret store,
|
|
177
|
+
* so the resolved address is tenant data and must never reach shared logs, and
|
|
178
|
+
* anything keyed by it would grow without bound in a warm worker. The message
|
|
179
|
+
* therefore carries no tenant values, which also makes it identical for every
|
|
180
|
+
* suppressed config — so emitting it once per process loses nothing.
|
|
181
|
+
*/
|
|
182
|
+
function warnAppSenderSuppressed(): void {
|
|
183
|
+
if (warnedAppSenderSuppressed) return;
|
|
184
|
+
warnedAppSenderSuppressed = true;
|
|
185
|
+
console.warn(
|
|
186
|
+
`[agent-native:email] Per-app sender branding is off because the ` +
|
|
187
|
+
`configured EMAIL_FROM is not on ${AGENT_NATIVE_SENDER_DOMAIN}. ` +
|
|
188
|
+
`Transactional email keeps the configured sender. Expected when self-hosting.`,
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function resolveAppSender(
|
|
193
|
+
configuredFrom: string | undefined,
|
|
194
|
+
appSender: SendEmailArgs["appSender"],
|
|
195
|
+
): { address: string; name: string; replyTo?: string } | undefined {
|
|
196
|
+
if (!appSender) return undefined;
|
|
197
|
+
const address = configuredFrom
|
|
198
|
+
? parseSendGridFrom(configuredFrom).email.toLowerCase()
|
|
199
|
+
: undefined;
|
|
200
|
+
if (!address?.endsWith(`@${AGENT_NATIVE_SENDER_DOMAIN}`)) {
|
|
201
|
+
warnAppSenderSuppressed();
|
|
202
|
+
return undefined;
|
|
203
|
+
}
|
|
204
|
+
return {
|
|
205
|
+
address: `${appSender.slug}@${AGENT_NATIVE_SENDER_DOMAIN}`,
|
|
206
|
+
name: appSender.name,
|
|
207
|
+
replyTo: appSender.replyTo,
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
|
|
153
211
|
async function sendEmailWithSignal(
|
|
154
212
|
args: SendEmailArgs,
|
|
155
213
|
signal?: AbortSignal,
|
|
@@ -157,7 +215,12 @@ async function sendEmailWithSignal(
|
|
|
157
215
|
const config = await resolveEmailTransport();
|
|
158
216
|
signal?.throwIfAborted();
|
|
159
217
|
const provider = config.provider;
|
|
160
|
-
const
|
|
218
|
+
const branded = resolveAppSender(config.from, args.appSender);
|
|
219
|
+
const from =
|
|
220
|
+
branded && !args.from
|
|
221
|
+
? withDisplayName(branded.address, args.fromName ?? branded.name)
|
|
222
|
+
: getFromAddress(config, args.from, args.fromName);
|
|
223
|
+
const replyTo = args.replyTo ?? branded?.replyTo;
|
|
161
224
|
const attachments = resolveAttachments(args);
|
|
162
225
|
|
|
163
226
|
if (provider === "resend") {
|
|
@@ -169,7 +232,7 @@ async function sendEmailWithSignal(
|
|
|
169
232
|
text: args.text,
|
|
170
233
|
};
|
|
171
234
|
if (args.cc) payload.cc = Array.isArray(args.cc) ? args.cc : [args.cc];
|
|
172
|
-
if (
|
|
235
|
+
if (replyTo) payload.reply_to = replyTo;
|
|
173
236
|
if (attachments?.length) {
|
|
174
237
|
payload.attachments = attachments.map((a) => ({
|
|
175
238
|
filename: a.filename,
|
|
@@ -220,7 +283,7 @@ async function sendEmailWithSignal(
|
|
|
220
283
|
{ type: "text/html", value: args.html },
|
|
221
284
|
],
|
|
222
285
|
};
|
|
223
|
-
if (
|
|
286
|
+
if (replyTo) sgPayload.reply_to = parseSendGridFrom(replyTo);
|
|
224
287
|
const sgHeaders: Record<string, string> = {};
|
|
225
288
|
if (args.inReplyTo) sgHeaders["In-Reply-To"] = args.inReplyTo;
|
|
226
289
|
if (args.references) sgHeaders["References"] = args.references;
|
|
@@ -547,6 +547,16 @@ export {
|
|
|
547
547
|
type EmailProvider,
|
|
548
548
|
type SendEmailArgs,
|
|
549
549
|
} from "./email.js";
|
|
550
|
+
export {
|
|
551
|
+
notifyActivity,
|
|
552
|
+
runActivityNotification,
|
|
553
|
+
resolveActivityRecipients,
|
|
554
|
+
type ActivityDeliveryFailure,
|
|
555
|
+
type ActivityNotificationResult,
|
|
556
|
+
type ActivityNotificationStatus,
|
|
557
|
+
type NotifyActivityInput,
|
|
558
|
+
type ResolveActivityRecipientsInput,
|
|
559
|
+
} from "./activity-notifications.js";
|
|
550
560
|
export {
|
|
551
561
|
renderEmail,
|
|
552
562
|
emailStrong,
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
|
|
16
16
|
import { and, eq, or, sql, type SQL } from "drizzle-orm";
|
|
17
17
|
|
|
18
|
+
import { orgMembers } from "../org/schema.js";
|
|
18
19
|
import {
|
|
19
20
|
getRequestAuthCapability,
|
|
20
21
|
getRequestUserEmail,
|
|
@@ -87,6 +88,35 @@ function emailColumnMatches(column: any, email: string): SQL {
|
|
|
87
88
|
return sql`lower(${column}) = ${email}`;
|
|
88
89
|
}
|
|
89
90
|
|
|
91
|
+
/**
|
|
92
|
+
* Real `org_members` membership, independent of the caller's currently
|
|
93
|
+
* active org (`ctx.orgId`). `org`-visibility access must key off actual
|
|
94
|
+
* membership — a user's active-org selection is a UI convenience, not a
|
|
95
|
+
* statement of which orgs they belong to.
|
|
96
|
+
*
|
|
97
|
+
* Queries through the resource's own `reg.getDb()` — the same connection
|
|
98
|
+
* every other lookup in this file uses — not the ambient `getDbExec()`,
|
|
99
|
+
* since `org_members` lives in that same app database.
|
|
100
|
+
*/
|
|
101
|
+
async function isOrgMember(
|
|
102
|
+
reg: ShareableResourceRegistration,
|
|
103
|
+
memberOrgId: string,
|
|
104
|
+
email: string,
|
|
105
|
+
): Promise<boolean> {
|
|
106
|
+
const db = reg.getDb() as any;
|
|
107
|
+
const rows = await db
|
|
108
|
+
.select({ id: orgMembers.id })
|
|
109
|
+
.from(orgMembers)
|
|
110
|
+
.where(
|
|
111
|
+
and(
|
|
112
|
+
eq(orgMembers.orgId, memberOrgId),
|
|
113
|
+
emailColumnMatches(orgMembers.email, email),
|
|
114
|
+
),
|
|
115
|
+
)
|
|
116
|
+
.limit(1);
|
|
117
|
+
return rows.length > 0;
|
|
118
|
+
}
|
|
119
|
+
|
|
90
120
|
/**
|
|
91
121
|
* Build a Drizzle `WHERE` clause that admits rows the current user can see.
|
|
92
122
|
* Pass the ownable resource table and its shares table; optional min role
|
|
@@ -470,7 +500,7 @@ async function resolveAccessImpl(
|
|
|
470
500
|
const resource = await loadResourceForAccess(reg, resourceId, options);
|
|
471
501
|
if (!resource) return null;
|
|
472
502
|
|
|
473
|
-
const { userEmail
|
|
503
|
+
const { userEmail } = ctx;
|
|
474
504
|
const normalizedUserEmail = normalizeEmailForAccess(userEmail);
|
|
475
505
|
|
|
476
506
|
if (
|
|
@@ -491,7 +521,19 @@ async function resolveAccessImpl(
|
|
|
491
521
|
// `visibility === "public"` on an `allowPublic: false` resource is treated
|
|
492
522
|
// as private: only owner + explicit shares grant access. Falls through to
|
|
493
523
|
// the explicit-share lookup below.
|
|
494
|
-
|
|
524
|
+
//
|
|
525
|
+
// Membership in the resource's own org, not equality with the caller's
|
|
526
|
+
// currently active org: a caller can be a genuine member of the
|
|
527
|
+
// resource's org while a *different* org is their active selection, and
|
|
528
|
+
// `org` visibility should still admit them. Still requires some active
|
|
529
|
+
// org to be set at all (`orgId`), matching the pre-existing behavior for
|
|
530
|
+
// a caller with no active org.
|
|
531
|
+
if (
|
|
532
|
+
resource.visibility === "org" &&
|
|
533
|
+
resource.orgId &&
|
|
534
|
+
normalizedUserEmail &&
|
|
535
|
+
(await isOrgMember(reg, resource.orgId, normalizedUserEmail))
|
|
536
|
+
) {
|
|
495
537
|
const role = await highestShareRole(reg, resourceId, ctx, resource);
|
|
496
538
|
return { role: role ?? "viewer", resource };
|
|
497
539
|
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { isOrgMember } from "../org/membership.js";
|
|
2
|
+
import { resolveAccess } from "./access.js";
|
|
3
|
+
import { roleSatisfies, type ShareRole } from "./schema.js";
|
|
4
|
+
|
|
5
|
+
export interface FilterRecipientsInput {
|
|
6
|
+
resourceType: string;
|
|
7
|
+
resourceId: string;
|
|
8
|
+
emails: Iterable<string>;
|
|
9
|
+
/** The resource's org, needed to honor `org` visibility. */
|
|
10
|
+
orgId?: string | null;
|
|
11
|
+
minimumRole?: ShareRole;
|
|
12
|
+
/**
|
|
13
|
+
* Override for resources that are not registered through `registerShareableResource`
|
|
14
|
+
* (the review registry resolves its own). Must honor `userEmail`/`orgId`
|
|
15
|
+
* rather than the ambient request, or the filter checks the wrong principal.
|
|
16
|
+
*/
|
|
17
|
+
resolveRole?: (ctx: {
|
|
18
|
+
userEmail: string;
|
|
19
|
+
orgId?: string;
|
|
20
|
+
}) => Promise<{ role: string } | null>;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Keep only the addresses that can still open the resource *right now*.
|
|
25
|
+
*
|
|
26
|
+
* Notification recipient lists are built from historical rows — thread
|
|
27
|
+
* authors, stored mentions, caller-supplied addresses — none of which are an
|
|
28
|
+
* access grant. Without this, mentioning an arbitrary address mails it the
|
|
29
|
+
* comment body, and a collaborator whose share was revoked keeps receiving the
|
|
30
|
+
* thread. Access is re-resolved per address against the live ACL.
|
|
31
|
+
*/
|
|
32
|
+
export async function filterRecipientsByResourceAccess({
|
|
33
|
+
resourceType,
|
|
34
|
+
resourceId,
|
|
35
|
+
emails,
|
|
36
|
+
orgId,
|
|
37
|
+
minimumRole = "viewer",
|
|
38
|
+
resolveRole,
|
|
39
|
+
}: FilterRecipientsInput): Promise<string[]> {
|
|
40
|
+
const resolve =
|
|
41
|
+
resolveRole ??
|
|
42
|
+
((ctx: { userEmail: string; orgId?: string }) =>
|
|
43
|
+
resolveAccess(resourceType, resourceId, ctx));
|
|
44
|
+
const unique = new Set<string>();
|
|
45
|
+
for (const raw of emails) {
|
|
46
|
+
const email = raw.trim().toLowerCase();
|
|
47
|
+
if (email) unique.add(email);
|
|
48
|
+
}
|
|
49
|
+
if (unique.size === 0) return [];
|
|
50
|
+
|
|
51
|
+
const decisions = await Promise.all(
|
|
52
|
+
[...unique].map(async (email) => {
|
|
53
|
+
// Resolve without an org first: passing the resource's org up front
|
|
54
|
+
// would hand org-visibility access to anyone who is not in it.
|
|
55
|
+
const direct = await resolve({ userEmail: email });
|
|
56
|
+
if (direct && roleSatisfies(direct.role as ShareRole, minimumRole)) {
|
|
57
|
+
return email;
|
|
58
|
+
}
|
|
59
|
+
if (!orgId || !(await isOrgMember(orgId, email))) return null;
|
|
60
|
+
const viaOrg = await resolve({ userEmail: email, orgId });
|
|
61
|
+
return viaOrg && roleSatisfies(viaOrg.role as ShareRole, minimumRole)
|
|
62
|
+
? email
|
|
63
|
+
: null;
|
|
64
|
+
}),
|
|
65
|
+
);
|
|
66
|
+
return decisions.filter((email): email is string => email !== null);
|
|
67
|
+
}
|
|
@@ -5,7 +5,12 @@ import {
|
|
|
5
5
|
listAutomationDefinitions,
|
|
6
6
|
type AutomationScope,
|
|
7
7
|
} from "../../automations/service.js";
|
|
8
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
describeCron,
|
|
10
|
+
effectiveTimezone,
|
|
11
|
+
isValidCron,
|
|
12
|
+
nextOccurrence,
|
|
13
|
+
} from "../../jobs/cron.js";
|
|
9
14
|
|
|
10
15
|
const scopeSchema = z.enum(["personal", "organization"]);
|
|
11
16
|
|
|
@@ -13,15 +18,23 @@ function nextRun(
|
|
|
13
18
|
meta: Awaited<ReturnType<typeof listAutomationDefinitions>>[number]["meta"],
|
|
14
19
|
): string | null {
|
|
15
20
|
if (!meta.enabled) return null;
|
|
16
|
-
|
|
17
|
-
if (
|
|
21
|
+
const scheduled = Boolean(
|
|
18
22
|
meta.triggerType === "schedule" &&
|
|
19
23
|
meta.schedule &&
|
|
20
|
-
isValidCron(meta.schedule)
|
|
21
|
-
)
|
|
22
|
-
|
|
24
|
+
isValidCron(meta.schedule),
|
|
25
|
+
);
|
|
26
|
+
// A stored `nextRun` in the past means the dispatcher kept declining to run
|
|
27
|
+
// this automation, not that it is overdue. Report the real next occurrence
|
|
28
|
+
// and let `lastError` carry the reason it keeps being passed over.
|
|
29
|
+
if (meta.nextRun) {
|
|
30
|
+
const stored = new Date(meta.nextRun).getTime();
|
|
31
|
+
if (!Number.isFinite(stored) || stored > Date.now() || !scheduled) {
|
|
32
|
+
return meta.nextRun;
|
|
33
|
+
}
|
|
23
34
|
}
|
|
24
|
-
return
|
|
35
|
+
return scheduled
|
|
36
|
+
? nextOccurrence(meta.schedule!, undefined, meta.timezone).toISOString()
|
|
37
|
+
: null;
|
|
25
38
|
}
|
|
26
39
|
|
|
27
40
|
export interface AutomationActionItem {
|
|
@@ -32,11 +45,13 @@ export interface AutomationActionItem {
|
|
|
32
45
|
triggerType: "event" | "schedule";
|
|
33
46
|
event: string | null;
|
|
34
47
|
schedule: string | null;
|
|
48
|
+
timezone: string | null;
|
|
35
49
|
scheduleDescription: string | null;
|
|
36
50
|
condition: string | null;
|
|
37
51
|
body: string;
|
|
38
52
|
enabled: boolean;
|
|
39
53
|
lastRun: string | null;
|
|
54
|
+
lastCheck: string | null;
|
|
40
55
|
lastStatus: string | null;
|
|
41
56
|
lastError: string | null;
|
|
42
57
|
nextRun: string | null;
|
|
@@ -76,11 +91,15 @@ export default defineAction({
|
|
|
76
91
|
triggerType: meta.triggerType,
|
|
77
92
|
event: meta.event ?? null,
|
|
78
93
|
schedule: meta.schedule || null,
|
|
79
|
-
|
|
94
|
+
timezone: meta.schedule ? effectiveTimezone(meta.timezone) : null,
|
|
95
|
+
scheduleDescription: meta.schedule
|
|
96
|
+
? describeCron(meta.schedule, effectiveTimezone(meta.timezone))
|
|
97
|
+
: null,
|
|
80
98
|
condition: meta.condition ?? null,
|
|
81
99
|
body,
|
|
82
100
|
enabled: meta.enabled,
|
|
83
101
|
lastRun: meta.lastRun ?? null,
|
|
102
|
+
lastCheck: meta.lastCheck ?? null,
|
|
84
103
|
lastStatus: meta.lastStatus ?? null,
|
|
85
104
|
lastError: meta.lastError ?? null,
|
|
86
105
|
nextRun: nextRun(meta),
|
|
@@ -16,8 +16,10 @@ export default defineAction({
|
|
|
16
16
|
name: z.string().min(1),
|
|
17
17
|
scope: z.enum(["personal", "organization"]).default("personal"),
|
|
18
18
|
enabled: z.boolean().optional(),
|
|
19
|
+
schedule: z.string().min(1).optional(),
|
|
20
|
+
timezone: z.string().min(1).optional(),
|
|
19
21
|
}),
|
|
20
|
-
run: async ({ operation, name, scope, enabled }, ctx) => {
|
|
22
|
+
run: async ({ operation, name, scope, enabled, schedule, timezone }, ctx) => {
|
|
21
23
|
const userEmail = ctx?.userEmail;
|
|
22
24
|
if (!userEmail) throw new Error("Not authenticated.");
|
|
23
25
|
const actor = { userEmail, orgId: ctx?.orgId };
|
|
@@ -27,20 +29,29 @@ export default defineAction({
|
|
|
27
29
|
await refreshEventSubscriptions();
|
|
28
30
|
return { deleted: true, name };
|
|
29
31
|
}
|
|
30
|
-
if (
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
32
|
+
if (
|
|
33
|
+
enabled === undefined &&
|
|
34
|
+
schedule === undefined &&
|
|
35
|
+
timezone === undefined
|
|
36
|
+
) {
|
|
37
|
+
throw Object.assign(
|
|
38
|
+
new Error("enabled, schedule, or timezone is required for update."),
|
|
39
|
+
{ statusCode: 400 },
|
|
40
|
+
);
|
|
34
41
|
}
|
|
35
42
|
const definition = await updateAutomation(actor, {
|
|
36
43
|
name,
|
|
37
44
|
scope,
|
|
38
|
-
enabled,
|
|
45
|
+
...(enabled === undefined ? {} : { enabled }),
|
|
46
|
+
...(schedule === undefined ? {} : { schedule }),
|
|
47
|
+
...(timezone === undefined ? {} : { timezone }),
|
|
39
48
|
});
|
|
40
49
|
await refreshEventSubscriptions();
|
|
41
50
|
return {
|
|
42
51
|
name,
|
|
43
52
|
enabled: definition.meta.enabled,
|
|
53
|
+
schedule: definition.meta.schedule || null,
|
|
54
|
+
timezone: definition.meta.timezone ?? null,
|
|
44
55
|
nextRun: definition.meta.nextRun ?? null,
|
|
45
56
|
};
|
|
46
57
|
},
|
|
@@ -18,7 +18,7 @@ import {
|
|
|
18
18
|
type AutomationScope,
|
|
19
19
|
} from "../automations/service.js";
|
|
20
20
|
import { listEvents } from "../event-bus/index.js";
|
|
21
|
-
import { describeCron } from "../jobs/cron.js";
|
|
21
|
+
import { describeCron, effectiveTimezone } from "../jobs/cron.js";
|
|
22
22
|
import {
|
|
23
23
|
getIntegrationRequestContext,
|
|
24
24
|
getRequestOrgId,
|
|
@@ -71,7 +71,10 @@ async function handleList(
|
|
|
71
71
|
triggerType: meta.triggerType,
|
|
72
72
|
event: meta.event ?? null,
|
|
73
73
|
schedule: meta.schedule || null,
|
|
74
|
-
|
|
74
|
+
timezone: meta.timezone ? effectiveTimezone(meta.timezone) : null,
|
|
75
|
+
scheduleDescription: meta.schedule
|
|
76
|
+
? describeCron(meta.schedule, effectiveTimezone(meta.timezone))
|
|
77
|
+
: null,
|
|
75
78
|
condition: meta.condition ?? null,
|
|
76
79
|
mode: meta.mode,
|
|
77
80
|
domain: meta.domain ?? null,
|
|
@@ -127,6 +130,7 @@ async function handleDefine(
|
|
|
127
130
|
triggerType: automationTriggerType(args.trigger_type),
|
|
128
131
|
body: typeof args.body === "string" ? args.body : "",
|
|
129
132
|
schedule: typeof args.schedule === "string" ? args.schedule : undefined,
|
|
133
|
+
timezone: typeof args.timezone === "string" ? args.timezone : undefined,
|
|
130
134
|
event: typeof args.event === "string" ? args.event : undefined,
|
|
131
135
|
condition:
|
|
132
136
|
typeof args.condition === "string" ? args.condition : undefined,
|
|
@@ -164,6 +168,7 @@ async function handleDefine(
|
|
|
164
168
|
triggerType: definition.meta.triggerType,
|
|
165
169
|
event: definition.meta.event ?? null,
|
|
166
170
|
schedule: definition.meta.schedule || null,
|
|
171
|
+
timezone: definition.meta.timezone ?? null,
|
|
167
172
|
nextRun: definition.meta.nextRun ?? null,
|
|
168
173
|
createdBy: definition.meta.createdBy,
|
|
169
174
|
runAs: definition.meta.runAs,
|
|
@@ -208,6 +213,7 @@ async function handleUpdate(
|
|
|
208
213
|
: null,
|
|
209
214
|
body: typeof args.body === "string" ? args.body : undefined,
|
|
210
215
|
schedule: typeof args.schedule === "string" ? args.schedule : undefined,
|
|
216
|
+
timezone: typeof args.timezone === "string" ? args.timezone : undefined,
|
|
211
217
|
model:
|
|
212
218
|
args.model === undefined
|
|
213
219
|
? undefined
|
|
@@ -225,6 +231,7 @@ async function handleUpdate(
|
|
|
225
231
|
triggerType: definition.meta.triggerType,
|
|
226
232
|
enabled: definition.meta.enabled,
|
|
227
233
|
schedule: definition.meta.schedule || null,
|
|
234
|
+
timezone: definition.meta.timezone ?? null,
|
|
228
235
|
nextRun: definition.meta.nextRun ?? null,
|
|
229
236
|
createdBy: definition.meta.createdBy,
|
|
230
237
|
runAs: definition.meta.runAs,
|
|
@@ -305,8 +312,8 @@ export function createAutomationToolEntries(
|
|
|
305
312
|
|
|
306
313
|
- **list-events**: List all registered event types that automations can subscribe to. Returns event names, descriptions, and payload schemas. Call this BEFORE defining an automation to discover available events.
|
|
307
314
|
- **list**: List all automations (triggers). Shows trigger, status, model, MCP allowlist, and delivery metadata. Optional params: scope, domain, enabled_only.
|
|
308
|
-
- **define**: Create a new automation. IMPORTANT: Always confirm with the user before calling — show them a summary of what will be created. Required params: name, trigger_type, body. Optional: scope, event, schedule, condition, mode, domain, delegated_policy_id, model, mcpTools.
|
|
309
|
-
- **update**: Update an existing automation's settings without changing its creator (enabled, schedule, condition, body, policy, model, MCP allowlist). Required param: name. Use the same scope it was created in.
|
|
315
|
+
- **define**: Create a new automation. IMPORTANT: Always confirm with the user before calling — show them a summary of what will be created. Required params: name, trigger_type, body. Optional: scope, event, schedule, timezone, condition, mode, domain, delegated_policy_id, model, mcpTools.
|
|
316
|
+
- **update**: Update an existing automation's settings without changing its creator (enabled, schedule, timezone, condition, body, policy, model, MCP allowlist). Required param: name. Use the same scope it was created in.
|
|
310
317
|
- **delete**: Delete an automation. Always confirm with the user first. Required param: name.
|
|
311
318
|
- **fire-test**: Fire a test event to validate automations. Emits a test.event.fired event. Optional param: data (JSON string).`,
|
|
312
319
|
parameters: {
|
|
@@ -339,6 +346,11 @@ export function createAutomationToolEntries(
|
|
|
339
346
|
description:
|
|
340
347
|
"For event triggers: the event name to subscribe to. Call with action=list-events first to see available events.",
|
|
341
348
|
},
|
|
349
|
+
timezone: {
|
|
350
|
+
type: "string",
|
|
351
|
+
description:
|
|
352
|
+
"IANA timezone the cron clock time is read in, e.g. 'America/New_York'. Optional; defaults to the user's saved scheduling timezone, then the caller's browser zone. Always pass this when the user names a time of day.",
|
|
353
|
+
},
|
|
342
354
|
schedule: {
|
|
343
355
|
type: "string",
|
|
344
356
|
description:
|