@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,14 +11,59 @@ use crate::tray_meetings::{build_meetings_section, handle_meeting_menu_click, Me
|
|
|
11
11
|
use crate::util::{is_meeting_active, is_recording_active};
|
|
12
12
|
use crate::TRAY_PNG;
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
let
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
14
|
+
fn physical_tray_rect(rect: tauri::Rect) -> (i32, i32, i32, i32) {
|
|
15
|
+
let (x, y) = match rect.position {
|
|
16
|
+
tauri::Position::Physical(position) => (position.x, position.y),
|
|
17
|
+
tauri::Position::Logical(position) => (position.x as i32, position.y as i32),
|
|
18
|
+
};
|
|
19
|
+
let (width, height) = match rect.size {
|
|
20
|
+
tauri::Size::Physical(size) => (size.width as i32, size.height as i32),
|
|
21
|
+
tauri::Size::Logical(size) => (size.width as i32, size.height as i32),
|
|
22
|
+
};
|
|
23
|
+
(x, y, width, height)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/// A status item that has not been laid out yet reports a frame at the screen
|
|
27
|
+
/// origin, which macOS coordinate flipping turns into a bottom-left rect. Only
|
|
28
|
+
/// a rect sitting in a monitor's menu-bar row can anchor the popover.
|
|
29
|
+
fn tray_rect_is_laid_out(app: &tauri::AppHandle, rect: tauri::Rect) -> bool {
|
|
30
|
+
let (x, y, width, height) = physical_tray_rect(rect);
|
|
31
|
+
if width <= 0 || height <= 0 {
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
let center_x = x + width / 2;
|
|
36
|
+
let center_y = y + height / 2;
|
|
37
|
+
let Some(window) = app.get_webview_window("popover") else {
|
|
38
|
+
return false;
|
|
39
|
+
};
|
|
40
|
+
let Ok(monitors) = window.available_monitors() else {
|
|
19
41
|
return false;
|
|
20
42
|
};
|
|
21
43
|
|
|
44
|
+
monitors.into_iter().any(|monitor| {
|
|
45
|
+
let position = monitor.position();
|
|
46
|
+
let size = monitor.size();
|
|
47
|
+
if center_x < position.x || center_x >= position.x + size.width as i32 {
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
#[cfg(target_os = "macos")]
|
|
51
|
+
{
|
|
52
|
+
let menu_bar_band = (64.0 * monitor.scale_factor()).round() as i32;
|
|
53
|
+
center_y >= position.y && center_y < position.y + menu_bar_band
|
|
54
|
+
}
|
|
55
|
+
#[cfg(not(target_os = "macos"))]
|
|
56
|
+
{
|
|
57
|
+
center_y >= position.y && center_y < position.y + size.height as i32
|
|
58
|
+
}
|
|
59
|
+
})
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
fn store_tray_anchor(app: &tauri::AppHandle, rect: tauri::Rect) -> bool {
|
|
63
|
+
if !tray_rect_is_laid_out(app, rect) {
|
|
64
|
+
dlog!("[clips-tray] ignoring tray rect that is not laid out yet");
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
22
67
|
let Some(anchor) = app.try_state::<TrayAnchor>() else {
|
|
23
68
|
return false;
|
|
24
69
|
};
|
|
@@ -29,6 +74,16 @@ pub fn refresh_tray_anchor(app: &tauri::AppHandle) -> bool {
|
|
|
29
74
|
true
|
|
30
75
|
}
|
|
31
76
|
|
|
77
|
+
pub fn refresh_tray_anchor(app: &tauri::AppHandle) -> bool {
|
|
78
|
+
let Some(rect) = app
|
|
79
|
+
.tray_by_id("main")
|
|
80
|
+
.and_then(|tray| tray.rect().ok().flatten())
|
|
81
|
+
else {
|
|
82
|
+
return false;
|
|
83
|
+
};
|
|
84
|
+
store_tray_anchor(app, rect)
|
|
85
|
+
}
|
|
86
|
+
|
|
32
87
|
/// Build the full tray menu with the given upcoming-meetings list. Used both
|
|
33
88
|
/// at startup (with `Vec::new()`) and at refresh time when the meetings
|
|
34
89
|
/// watcher pushes a new snapshot — `TrayIcon::set_menu(Some(...))` swaps the
|
|
@@ -222,12 +277,7 @@ pub fn build_tray(app: &tauri::App) -> Result<(), Box<dyn std::error::Error>> {
|
|
|
222
277
|
_ => None,
|
|
223
278
|
};
|
|
224
279
|
if let Some(rect) = rect {
|
|
225
|
-
|
|
226
|
-
if let Some(anchor) = app.try_state::<TrayAnchor>() {
|
|
227
|
-
if let Ok(mut g) = anchor.0.lock() {
|
|
228
|
-
*g = Some(rect);
|
|
229
|
-
}
|
|
230
|
-
}
|
|
280
|
+
store_tray_anchor(tray.app_handle(), rect);
|
|
231
281
|
}
|
|
232
282
|
|
|
233
283
|
if let TrayIconEvent::Click {
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import baseConfig from "@agent-native/core/vitest-config";
|
|
2
|
+
import { mergeConfig } from "vitest/config";
|
|
3
|
+
|
|
4
|
+
import viteConfig from "./vite.config";
|
|
5
|
+
|
|
6
|
+
// Kept separate from vite.config.ts so the production config never imports
|
|
7
|
+
// vitest: `agent-native build` loads vite.config.ts in installs where vitest
|
|
8
|
+
// is absent.
|
|
9
|
+
export default mergeConfig(viteConfig, baseConfig);
|
|
@@ -146,6 +146,16 @@ export const recordings = table("recordings", {
|
|
|
146
146
|
thumbnailUrl: text("thumbnail_url"),
|
|
147
147
|
animatedThumbnailUrl: text("animated_thumbnail_url"),
|
|
148
148
|
|
|
149
|
+
// Editor timeline filmstrip: one sprite image plus the grid geometry needed
|
|
150
|
+
// to address a cell. Null means "not generated yet", which is why the editor
|
|
151
|
+
// still has a browser-side extraction fallback.
|
|
152
|
+
filmstripUrl: text("filmstrip_url"),
|
|
153
|
+
filmstripFrameCount: integer("filmstrip_frame_count").notNull().default(0),
|
|
154
|
+
filmstripColumns: integer("filmstrip_columns").notNull().default(0),
|
|
155
|
+
filmstripRows: integer("filmstrip_rows").notNull().default(0),
|
|
156
|
+
filmstripFrameWidth: integer("filmstrip_frame_width").notNull().default(0),
|
|
157
|
+
filmstripFrameHeight: integer("filmstrip_frame_height").notNull().default(0),
|
|
158
|
+
|
|
149
159
|
durationMs: integer("duration_ms").notNull().default(0),
|
|
150
160
|
videoUrl: text("video_url"),
|
|
151
161
|
videoFormat: text("video_format", { enum: ["webm", "mp4"] })
|
|
@@ -168,6 +178,13 @@ export const recordings = table("recordings", {
|
|
|
168
178
|
// Authoritative liveness for an in-flight upload: renewed by every chunk
|
|
169
179
|
// POST, and the only thing the upload reaper is allowed to consult.
|
|
170
180
|
uploadLeaseExpiresAt: text("upload_lease_expires_at"),
|
|
181
|
+
// Fences resumed writers: every recovery claim rotates this token so stale
|
|
182
|
+
// chunks and delayed interruption callbacks cannot mutate the new attempt.
|
|
183
|
+
uploadAttemptId: text("upload_attempt_id"),
|
|
184
|
+
// Every destructive restart receives a new generation. Unlike the attempt
|
|
185
|
+
// id (which is deliberately stable across a lost response), this fences the
|
|
186
|
+
// provider handle and buffered scratch that the restart replaces.
|
|
187
|
+
uploadGenerationId: text("upload_generation_id"),
|
|
171
188
|
failureReason: text("failure_reason"),
|
|
172
189
|
loomImportClaimId: text("loom_import_claim_id"),
|
|
173
190
|
loomImportClaimedAt: text("loom_import_claimed_at"),
|
|
@@ -67,6 +67,7 @@ export async function runMediaVerificationSweepOnce(): Promise<void> {
|
|
|
67
67
|
.select({
|
|
68
68
|
ownerEmail: schema.recordings.ownerEmail,
|
|
69
69
|
orgId: schema.recordings.orgId,
|
|
70
|
+
uploadGenerationId: schema.recordings.uploadGenerationId,
|
|
70
71
|
})
|
|
71
72
|
.from(schema.recordings)
|
|
72
73
|
.where(
|
|
@@ -92,6 +93,9 @@ export async function runMediaVerificationSweepOnce(): Promise<void> {
|
|
|
92
93
|
MAX_ATTEMPTS,
|
|
93
94
|
marker.completedAttempts + 1,
|
|
94
95
|
),
|
|
96
|
+
...(recording.uploadGenerationId
|
|
97
|
+
? { uploadGenerationId: recording.uploadGenerationId }
|
|
98
|
+
: {}),
|
|
95
99
|
});
|
|
96
100
|
},
|
|
97
101
|
);
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Email notifications for Activity events (comments, replies, reactions).
|
|
3
|
+
*
|
|
4
|
+
* Recipient resolution and delivery reporting live in
|
|
5
|
+
* `@agent-native/core/server`; this module only knows which Clips rows are
|
|
6
|
+
* involved and which template to render. Share invites are deliberately NOT
|
|
7
|
+
* routed through the `emailNotifications` preference — they have their own
|
|
8
|
+
* delivery path.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import {
|
|
12
|
+
notifyActivity,
|
|
13
|
+
runActivityNotification,
|
|
14
|
+
type ActivityNotificationResult,
|
|
15
|
+
} from "@agent-native/core/server";
|
|
16
|
+
import { filterRecipientsByResourceAccess } from "@agent-native/core/sharing";
|
|
17
|
+
import { and, eq } from "drizzle-orm";
|
|
18
|
+
|
|
19
|
+
import { CLIPS_USER_PREFS_KEY } from "../../shared/clips-ai-prefs.js";
|
|
20
|
+
import { getDb, schema } from "../db/index.js";
|
|
21
|
+
import { sendClipsTransactionalEmail } from "./transactional-email-templates.js";
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* `recording-missing` is kept distinct from `no-recipients`: one means the row
|
|
25
|
+
* we were asked to notify about could not be read, the other means nobody
|
|
26
|
+
* wanted the email.
|
|
27
|
+
*/
|
|
28
|
+
export type ClipsActivityNotificationResult =
|
|
29
|
+
| ActivityNotificationResult
|
|
30
|
+
| { status: "recording-missing"; sent: []; failed: [] };
|
|
31
|
+
|
|
32
|
+
const RECORDING_MISSING: ClipsActivityNotificationResult = {
|
|
33
|
+
status: "recording-missing",
|
|
34
|
+
sent: [],
|
|
35
|
+
failed: [],
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const LOG_LABEL = "[clips] activity notification";
|
|
39
|
+
|
|
40
|
+
async function getRecording(recordingId: string) {
|
|
41
|
+
const [row] = await getDb()
|
|
42
|
+
.select({
|
|
43
|
+
id: schema.recordings.id,
|
|
44
|
+
title: schema.recordings.title,
|
|
45
|
+
ownerEmail: schema.recordings.ownerEmail,
|
|
46
|
+
orgId: schema.recordings.orgId,
|
|
47
|
+
})
|
|
48
|
+
.from(schema.recordings)
|
|
49
|
+
.where(eq(schema.recordings.id, recordingId))
|
|
50
|
+
.limit(1);
|
|
51
|
+
return row ?? null;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async function threadParticipants(
|
|
55
|
+
recordingId: string,
|
|
56
|
+
threadId: string,
|
|
57
|
+
): Promise<string[]> {
|
|
58
|
+
const rows = await getDb()
|
|
59
|
+
.select({ authorEmail: schema.recordingComments.authorEmail })
|
|
60
|
+
.from(schema.recordingComments)
|
|
61
|
+
.where(
|
|
62
|
+
and(
|
|
63
|
+
eq(schema.recordingComments.recordingId, recordingId),
|
|
64
|
+
eq(schema.recordingComments.threadId, threadId),
|
|
65
|
+
),
|
|
66
|
+
);
|
|
67
|
+
return rows.map((row) => row.authorEmail);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export interface RecordingCommentNotificationInput {
|
|
71
|
+
recordingId: string;
|
|
72
|
+
threadId: string;
|
|
73
|
+
authorEmail: string;
|
|
74
|
+
authorName?: string | null;
|
|
75
|
+
content: string;
|
|
76
|
+
videoTimestampMs?: number | null;
|
|
77
|
+
isReply?: boolean;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export async function notifyRecordingComment(
|
|
81
|
+
input: RecordingCommentNotificationInput,
|
|
82
|
+
): Promise<ClipsActivityNotificationResult> {
|
|
83
|
+
return runActivityNotification(LOG_LABEL, () =>
|
|
84
|
+
deliverRecordingCommentEmails(input),
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async function deliverRecordingCommentEmails(
|
|
89
|
+
input: RecordingCommentNotificationInput,
|
|
90
|
+
): Promise<ClipsActivityNotificationResult> {
|
|
91
|
+
const recording = await getRecording(input.recordingId);
|
|
92
|
+
if (!recording) {
|
|
93
|
+
console.error(`${LOG_LABEL}: recording ${input.recordingId} not found`);
|
|
94
|
+
return RECORDING_MISSING;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const candidates = [recording.ownerEmail];
|
|
98
|
+
if (input.isReply) {
|
|
99
|
+
candidates.push(
|
|
100
|
+
...(await threadParticipants(input.recordingId, input.threadId)),
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Thread rows are history, not an access grant: a viewer whose share was
|
|
105
|
+
// revoked must stop receiving the recording's comment bodies.
|
|
106
|
+
const allowed = await filterRecipientsByResourceAccess({
|
|
107
|
+
resourceType: "recording",
|
|
108
|
+
resourceId: recording.id,
|
|
109
|
+
emails: candidates,
|
|
110
|
+
orgId: recording.orgId,
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
return notifyActivity({
|
|
114
|
+
candidates: allowed,
|
|
115
|
+
actorEmail: input.authorEmail,
|
|
116
|
+
preferenceKey: CLIPS_USER_PREFS_KEY,
|
|
117
|
+
logLabel: LOG_LABEL,
|
|
118
|
+
send: (to) =>
|
|
119
|
+
sendClipsTransactionalEmail({
|
|
120
|
+
kind: "activity-comment",
|
|
121
|
+
to,
|
|
122
|
+
recordingId: recording.id,
|
|
123
|
+
title: recording.title,
|
|
124
|
+
authorEmail: input.authorEmail,
|
|
125
|
+
authorName: input.authorName ?? null,
|
|
126
|
+
content: input.content,
|
|
127
|
+
videoTimestampMs: input.videoTimestampMs ?? null,
|
|
128
|
+
isReply: input.isReply ?? false,
|
|
129
|
+
}),
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export interface RecordingReactionNotificationInput {
|
|
134
|
+
recordingId: string;
|
|
135
|
+
emoji: string;
|
|
136
|
+
viewerEmail: string;
|
|
137
|
+
viewerName?: string | null;
|
|
138
|
+
videoTimestampMs?: number | null;
|
|
139
|
+
extraRecipients?: (string | null | undefined)[];
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export async function notifyRecordingReaction(
|
|
143
|
+
input: RecordingReactionNotificationInput,
|
|
144
|
+
): Promise<ClipsActivityNotificationResult> {
|
|
145
|
+
return runActivityNotification(LOG_LABEL, () =>
|
|
146
|
+
deliverRecordingReactionEmails(input),
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
async function deliverRecordingReactionEmails(
|
|
151
|
+
input: RecordingReactionNotificationInput,
|
|
152
|
+
): Promise<ClipsActivityNotificationResult> {
|
|
153
|
+
const recording = await getRecording(input.recordingId);
|
|
154
|
+
if (!recording) {
|
|
155
|
+
console.error(`${LOG_LABEL}: recording ${input.recordingId} not found`);
|
|
156
|
+
return RECORDING_MISSING;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const allowed = await filterRecipientsByResourceAccess({
|
|
160
|
+
resourceType: "recording",
|
|
161
|
+
resourceId: recording.id,
|
|
162
|
+
emails: [recording.ownerEmail, ...(input.extraRecipients ?? [])].filter(
|
|
163
|
+
(email): email is string => Boolean(email),
|
|
164
|
+
),
|
|
165
|
+
orgId: recording.orgId,
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
return notifyActivity({
|
|
169
|
+
candidates: allowed,
|
|
170
|
+
actorEmail: input.viewerEmail,
|
|
171
|
+
preferenceKey: CLIPS_USER_PREFS_KEY,
|
|
172
|
+
logLabel: LOG_LABEL,
|
|
173
|
+
send: (to) =>
|
|
174
|
+
sendClipsTransactionalEmail({
|
|
175
|
+
kind: "activity-reaction",
|
|
176
|
+
to,
|
|
177
|
+
recordingId: recording.id,
|
|
178
|
+
title: recording.title,
|
|
179
|
+
emoji: input.emoji,
|
|
180
|
+
authorEmail: input.viewerEmail,
|
|
181
|
+
authorName: input.viewerName ?? null,
|
|
182
|
+
videoTimestampMs: input.videoTimestampMs ?? null,
|
|
183
|
+
}),
|
|
184
|
+
});
|
|
185
|
+
}
|
|
@@ -7,6 +7,7 @@ interface RecordingMediaUrls {
|
|
|
7
7
|
videoUrl?: string | null;
|
|
8
8
|
thumbnailUrl?: string | null;
|
|
9
9
|
animatedThumbnailUrl?: string | null;
|
|
10
|
+
filmstripUrl?: string | null;
|
|
10
11
|
}
|
|
11
12
|
|
|
12
13
|
export interface RecordingMediaCleanupResult {
|
|
@@ -25,6 +26,7 @@ export function recordingMediaUrls(recording: RecordingMediaUrls): string[] {
|
|
|
25
26
|
recording.videoUrl,
|
|
26
27
|
recording.thumbnailUrl,
|
|
27
28
|
recording.animatedThumbnailUrl,
|
|
29
|
+
recording.filmstripUrl,
|
|
28
30
|
];
|
|
29
31
|
return [...new Set(urls.filter((url): url is string => Boolean(url)))];
|
|
30
32
|
}
|
|
@@ -4,14 +4,46 @@ function escapeLike(value: string): string {
|
|
|
4
4
|
return value.replace(/[!%_]/g, (match) => `!${match}`);
|
|
5
5
|
}
|
|
6
6
|
|
|
7
|
-
function chunkPrefix(
|
|
8
|
-
|
|
7
|
+
function chunkPrefix(
|
|
8
|
+
recordingId: string,
|
|
9
|
+
generationId?: string | null,
|
|
10
|
+
): string {
|
|
11
|
+
return generationId
|
|
12
|
+
? `recording-chunks-${recordingId}-${generationId}-`
|
|
13
|
+
: `recording-chunks-${recordingId}-`;
|
|
9
14
|
}
|
|
10
15
|
|
|
11
16
|
function likePrefix(prefix: string): string {
|
|
12
17
|
return `${escapeLike(prefix)}%`;
|
|
13
18
|
}
|
|
14
19
|
|
|
20
|
+
function exactChunkKeyArgs(
|
|
21
|
+
ownerEmail: string,
|
|
22
|
+
recordingId: string,
|
|
23
|
+
generationId?: string | null,
|
|
24
|
+
): [string, string, number, string, string] {
|
|
25
|
+
const prefix = chunkPrefix(recordingId, generationId);
|
|
26
|
+
return [
|
|
27
|
+
ownerEmail,
|
|
28
|
+
likePrefix(prefix),
|
|
29
|
+
prefix.length + 6,
|
|
30
|
+
`${prefix}000000`,
|
|
31
|
+
`${prefix}999999`,
|
|
32
|
+
];
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const exactChunkKeyWhere = `session_id = ? AND key LIKE ? ESCAPE '!' AND length(key) = ? AND key >= ? AND key <= ?`;
|
|
36
|
+
|
|
37
|
+
function isChunkKeyForGeneration(
|
|
38
|
+
key: string,
|
|
39
|
+
recordingId: string,
|
|
40
|
+
generationId?: string | null,
|
|
41
|
+
): boolean {
|
|
42
|
+
const prefix = chunkPrefix(recordingId, generationId);
|
|
43
|
+
if (!key.startsWith(prefix)) return false;
|
|
44
|
+
return /^\d+$/.test(key.slice(prefix.length));
|
|
45
|
+
}
|
|
46
|
+
|
|
15
47
|
function numberFromRowValue(value: unknown): number {
|
|
16
48
|
if (typeof value === "number" && Number.isFinite(value)) return value;
|
|
17
49
|
if (typeof value === "bigint") return Number(value);
|
|
@@ -81,21 +113,25 @@ export function validateRecordingChunkKeys(
|
|
|
81
113
|
export async function listRecordingChunkKeys(
|
|
82
114
|
ownerEmail: string,
|
|
83
115
|
recordingId: string,
|
|
116
|
+
generationId?: string | null,
|
|
84
117
|
): Promise<string[]> {
|
|
85
118
|
const { rows } = await getDbExec().execute({
|
|
86
|
-
sql: `SELECT key FROM application_state WHERE
|
|
87
|
-
args:
|
|
119
|
+
sql: `SELECT key FROM application_state WHERE ${exactChunkKeyWhere}`,
|
|
120
|
+
args: exactChunkKeyArgs(ownerEmail, recordingId, generationId),
|
|
88
121
|
});
|
|
89
|
-
return rows
|
|
122
|
+
return rows
|
|
123
|
+
.map((row) => String(row.key))
|
|
124
|
+
.filter((key) => isChunkKeyForGeneration(key, recordingId, generationId));
|
|
90
125
|
}
|
|
91
126
|
|
|
92
127
|
export async function deleteRecordingChunks(
|
|
93
128
|
ownerEmail: string,
|
|
94
129
|
recordingId: string,
|
|
130
|
+
generationId?: string | null,
|
|
95
131
|
): Promise<number> {
|
|
96
132
|
const result = await getDbExec().execute({
|
|
97
|
-
sql: `DELETE FROM application_state WHERE
|
|
98
|
-
args:
|
|
133
|
+
sql: `DELETE FROM application_state WHERE ${exactChunkKeyWhere}`,
|
|
134
|
+
args: exactChunkKeyArgs(ownerEmail, recordingId, generationId),
|
|
99
135
|
});
|
|
100
136
|
return result.rowsAffected ?? 0;
|
|
101
137
|
}
|
|
@@ -103,13 +139,14 @@ export async function deleteRecordingChunks(
|
|
|
103
139
|
export async function sumRecordingChunkBytes(
|
|
104
140
|
ownerEmail: string,
|
|
105
141
|
recordingId: string,
|
|
142
|
+
generationId?: string | null,
|
|
106
143
|
): Promise<number> {
|
|
107
144
|
const bytesExpression = isPostgres()
|
|
108
145
|
? `COALESCE(SUM((value::jsonb ->> 'bytes')::bigint), 0)`
|
|
109
146
|
: `COALESCE(SUM(json_extract(value, '$.bytes')), 0)`;
|
|
110
147
|
const { rows } = await getDbExec().execute({
|
|
111
|
-
sql: `SELECT ${bytesExpression} AS bytes FROM application_state WHERE
|
|
112
|
-
args:
|
|
148
|
+
sql: `SELECT ${bytesExpression} AS bytes FROM application_state WHERE ${exactChunkKeyWhere}`,
|
|
149
|
+
args: exactChunkKeyArgs(ownerEmail, recordingId, generationId),
|
|
113
150
|
});
|
|
114
151
|
return numberFromRowValue(rows[0]?.bytes);
|
|
115
152
|
}
|
|
@@ -5,9 +5,14 @@ import {
|
|
|
5
5
|
getRequestUserEmail,
|
|
6
6
|
getRequestOrgId,
|
|
7
7
|
} from "@agent-native/core/server/request-context";
|
|
8
|
+
import { getUserSetting } from "@agent-native/core/settings";
|
|
8
9
|
import { and, count, desc, eq, sql } from "drizzle-orm";
|
|
9
10
|
import { HTTPError, type H3Event } from "h3";
|
|
10
11
|
|
|
12
|
+
import {
|
|
13
|
+
CLIPS_USER_PREFS_KEY,
|
|
14
|
+
type ClipsUserPrefs,
|
|
15
|
+
} from "../../shared/clips-ai-prefs.js";
|
|
11
16
|
import { getDb, schema } from "../db/index.js";
|
|
12
17
|
|
|
13
18
|
export function getCurrentOwnerEmail(): string {
|
|
@@ -99,6 +104,25 @@ export async function getOrganizationDefaultVisibility(
|
|
|
99
104
|
}
|
|
100
105
|
}
|
|
101
106
|
|
|
107
|
+
/**
|
|
108
|
+
* Visibility for a new recording: the personal preference of the creator
|
|
109
|
+
* wins, then the organization default, then the built-in default.
|
|
110
|
+
*/
|
|
111
|
+
export async function getDefaultRecordingVisibility(
|
|
112
|
+
organizationId: string | null | undefined,
|
|
113
|
+
): Promise<RecordingVisibility> {
|
|
114
|
+
const email = getRequestUserEmail();
|
|
115
|
+
if (email) {
|
|
116
|
+
const prefs = (await getUserSetting(
|
|
117
|
+
normalizeOwnerEmail(email),
|
|
118
|
+
CLIPS_USER_PREFS_KEY,
|
|
119
|
+
)) as ClipsUserPrefs | null;
|
|
120
|
+
const preferred = prefs?.defaultRecordingVisibility;
|
|
121
|
+
if (isRecordingVisibility(preferred)) return preferred;
|
|
122
|
+
}
|
|
123
|
+
return getOrganizationDefaultVisibility(organizationId);
|
|
124
|
+
}
|
|
125
|
+
|
|
102
126
|
const ORG_ROLE_RANK: Record<OrganizationAccessRole, number> = {
|
|
103
127
|
member: 1,
|
|
104
128
|
admin: 2,
|
|
@@ -12,12 +12,16 @@ export interface StoredResumableSession {
|
|
|
12
12
|
lastCommittedIndex?: number;
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
const key = (recordingId: string) =>
|
|
15
|
+
const key = (recordingId: string, generationId?: string | null) =>
|
|
16
|
+
generationId
|
|
17
|
+
? `resumable-session-${recordingId}-${generationId}`
|
|
18
|
+
: `resumable-session-${recordingId}`;
|
|
16
19
|
|
|
17
20
|
export async function getResumableSession(
|
|
18
21
|
recordingId: string,
|
|
22
|
+
generationId?: string | null,
|
|
19
23
|
): Promise<StoredResumableSession | null> {
|
|
20
|
-
const raw = await readAppState(key(recordingId));
|
|
24
|
+
const raw = await readAppState(key(recordingId, generationId));
|
|
21
25
|
if (!raw || typeof raw !== "object") return null;
|
|
22
26
|
return raw as unknown as StoredResumableSession;
|
|
23
27
|
}
|
|
@@ -25,15 +29,17 @@ export async function getResumableSession(
|
|
|
25
29
|
export async function setResumableSession(
|
|
26
30
|
recordingId: string,
|
|
27
31
|
session: StoredResumableSession,
|
|
32
|
+
generationId?: string | null,
|
|
28
33
|
): Promise<void> {
|
|
29
34
|
await writeAppState(
|
|
30
|
-
key(recordingId),
|
|
35
|
+
key(recordingId, generationId),
|
|
31
36
|
session as unknown as Record<string, unknown>,
|
|
32
37
|
);
|
|
33
38
|
}
|
|
34
39
|
|
|
35
40
|
export async function deleteResumableSession(
|
|
36
41
|
recordingId: string,
|
|
42
|
+
generationId?: string | null,
|
|
37
43
|
): Promise<void> {
|
|
38
|
-
await deleteAppState(key(recordingId));
|
|
44
|
+
await deleteAppState(key(recordingId, generationId));
|
|
39
45
|
}
|
|
@@ -9,6 +9,8 @@ const CLIPS_BRAND_NAME = "Clips";
|
|
|
9
9
|
const EMAIL_SEND_TIMEOUT_MS = 60_000;
|
|
10
10
|
const FRIENDLY_REPLY_TO = "hello@agent-native.com";
|
|
11
11
|
const UNTITLED_CLIP = "Untitled Clip";
|
|
12
|
+
const ACTIVITY_EMAIL_FOOTER =
|
|
13
|
+
"You received this because email notifications are on in your Clips settings.";
|
|
12
14
|
|
|
13
15
|
interface TransactionalEmailBase {
|
|
14
16
|
to: string;
|
|
@@ -37,6 +39,25 @@ export type ClipsTransactionalEmailInput =
|
|
|
37
39
|
| (TransactionalEmailBase & {
|
|
38
40
|
kind: "two-clips";
|
|
39
41
|
generatedSummary?: string | null;
|
|
42
|
+
})
|
|
43
|
+
| (TransactionalEmailBase & {
|
|
44
|
+
kind: "activity-comment";
|
|
45
|
+
recordingId: string;
|
|
46
|
+
title?: string | null;
|
|
47
|
+
authorEmail?: string | null;
|
|
48
|
+
authorName?: string | null;
|
|
49
|
+
content: string;
|
|
50
|
+
videoTimestampMs?: number | null;
|
|
51
|
+
isReply?: boolean;
|
|
52
|
+
})
|
|
53
|
+
| (TransactionalEmailBase & {
|
|
54
|
+
kind: "activity-reaction";
|
|
55
|
+
recordingId: string;
|
|
56
|
+
title?: string | null;
|
|
57
|
+
emoji: string;
|
|
58
|
+
authorEmail?: string | null;
|
|
59
|
+
authorName?: string | null;
|
|
60
|
+
videoTimestampMs?: number | null;
|
|
40
61
|
});
|
|
41
62
|
|
|
42
63
|
export interface ClipsTransactionalEmailRenderOptions {
|
|
@@ -103,6 +124,31 @@ function clipUrl(
|
|
|
103
124
|
return appUrlForPath(`/r/${encodeURIComponent(recordingId)}`, options);
|
|
104
125
|
}
|
|
105
126
|
|
|
127
|
+
function clipCommentsUrl(
|
|
128
|
+
recordingId: string,
|
|
129
|
+
videoTimestampMs: number | null | undefined,
|
|
130
|
+
options: ClipsTransactionalEmailRenderOptions,
|
|
131
|
+
): string {
|
|
132
|
+
const url = new URL(clipUrl(recordingId, options));
|
|
133
|
+
url.searchParams.set("panel", "comments");
|
|
134
|
+
if (typeof videoTimestampMs === "number" && videoTimestampMs > 0) {
|
|
135
|
+
url.searchParams.set("t", String(Math.floor(videoTimestampMs / 1000)));
|
|
136
|
+
}
|
|
137
|
+
return url.toString();
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function formatTimestamp(ms: number): string {
|
|
141
|
+
const total = Math.floor(ms / 1000);
|
|
142
|
+
const minutes = Math.floor(total / 60);
|
|
143
|
+
const seconds = total % 60;
|
|
144
|
+
return `${minutes}:${String(seconds).padStart(2, "0")}`;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function quotedExcerpt(value: string): string {
|
|
148
|
+
const text = singleLine(value);
|
|
149
|
+
return text.length > 280 ? `${text.slice(0, 277)}…` : text;
|
|
150
|
+
}
|
|
151
|
+
|
|
106
152
|
function recordUrl(options: ClipsTransactionalEmailRenderOptions): string {
|
|
107
153
|
return appUrlForPath("/record", options);
|
|
108
154
|
}
|
|
@@ -232,6 +278,69 @@ export function renderClipsTransactionalEmail(
|
|
|
232
278
|
});
|
|
233
279
|
return { subject, ...rendered };
|
|
234
280
|
}
|
|
281
|
+
|
|
282
|
+
case "activity-comment": {
|
|
283
|
+
const author =
|
|
284
|
+
singleLine(input.authorName) ||
|
|
285
|
+
normalizeEmailDisplayName(input.authorEmail, "Someone");
|
|
286
|
+
const at =
|
|
287
|
+
typeof input.videoTimestampMs === "number" && input.videoTimestampMs > 0
|
|
288
|
+
? ` at ${formatTimestamp(input.videoTimestampMs)}`
|
|
289
|
+
: "";
|
|
290
|
+
const subject = input.isReply
|
|
291
|
+
? `${author} replied on “${title}”`
|
|
292
|
+
: `${author} commented on “${title}”`;
|
|
293
|
+
const rendered = renderEmail({
|
|
294
|
+
brandName: CLIPS_BRAND_NAME,
|
|
295
|
+
preheader: subject,
|
|
296
|
+
heading: input.isReply
|
|
297
|
+
? `${author} replied on your Clip`
|
|
298
|
+
: `${author} commented on your Clip`,
|
|
299
|
+
paragraphs: [
|
|
300
|
+
`${emailStrong(author)} left a ${input.isReply ? "reply" : "comment"} on ${emailStrong(title!)}${at}.`,
|
|
301
|
+
`“${quotedExcerpt(input.content)}”`,
|
|
302
|
+
],
|
|
303
|
+
cta: {
|
|
304
|
+
label: "Read and reply",
|
|
305
|
+
url: clipCommentsUrl(
|
|
306
|
+
input.recordingId,
|
|
307
|
+
input.videoTimestampMs,
|
|
308
|
+
options,
|
|
309
|
+
),
|
|
310
|
+
},
|
|
311
|
+
footer: ACTIVITY_EMAIL_FOOTER,
|
|
312
|
+
});
|
|
313
|
+
return { subject, ...rendered };
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
case "activity-reaction": {
|
|
317
|
+
const author =
|
|
318
|
+
singleLine(input.authorName) ||
|
|
319
|
+
normalizeEmailDisplayName(input.authorEmail, "Someone");
|
|
320
|
+
const at =
|
|
321
|
+
typeof input.videoTimestampMs === "number" && input.videoTimestampMs > 0
|
|
322
|
+
? ` at ${formatTimestamp(input.videoTimestampMs)}`
|
|
323
|
+
: "";
|
|
324
|
+
const subject = `${author} reacted ${input.emoji} on “${title}”`;
|
|
325
|
+
const rendered = renderEmail({
|
|
326
|
+
brandName: CLIPS_BRAND_NAME,
|
|
327
|
+
preheader: subject,
|
|
328
|
+
heading: "Someone reacted to your Clip",
|
|
329
|
+
paragraphs: [
|
|
330
|
+
`${emailStrong(author)} reacted ${emailStrong(input.emoji)} on ${emailStrong(title!)}${at}.`,
|
|
331
|
+
],
|
|
332
|
+
cta: {
|
|
333
|
+
label: "See Clip activity",
|
|
334
|
+
url: clipCommentsUrl(
|
|
335
|
+
input.recordingId,
|
|
336
|
+
input.videoTimestampMs,
|
|
337
|
+
options,
|
|
338
|
+
),
|
|
339
|
+
},
|
|
340
|
+
footer: ACTIVITY_EMAIL_FOOTER,
|
|
341
|
+
});
|
|
342
|
+
return { subject, ...rendered };
|
|
343
|
+
}
|
|
235
344
|
}
|
|
236
345
|
}
|
|
237
346
|
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export const RETRYABLE_UPLOAD_INTERRUPTION_REASON =
|
|
2
|
+
"Upload was interrupted. The local recording is safe; retry from the Clips desktop app.";
|
|
3
|
+
|
|
4
|
+
export function isRetryableUploadInterruption(
|
|
5
|
+
failureReason: string | null | undefined,
|
|
6
|
+
): boolean {
|
|
7
|
+
return failureReason === RETRYABLE_UPLOAD_INTERRUPTION_REASON;
|
|
8
|
+
}
|