@agent-native/core 0.134.1 → 0.135.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/corpus/README.md +3 -3
- package/corpus/core/CHANGELOG.md +113 -0
- package/corpus/core/package.json +3 -2
- package/corpus/core/src/automations/service.ts +41 -4
- package/corpus/core/src/cli/mcp-config-writers.ts +27 -21
- package/corpus/core/src/client/agent-page/AgentJobsTab.tsx +187 -85
- package/corpus/core/src/client/agent-page/AutomationDetailsDialog.tsx +208 -0
- package/corpus/core/src/client/agent-page/AutomationScheduleDialog.tsx +180 -0
- package/corpus/core/src/client/agent-page/TimezoneSelect.tsx +241 -0
- package/corpus/core/src/client/agent-page/use-jobs.ts +50 -10
- package/corpus/core/src/client/org/OrgSwitcher.tsx +26 -0
- package/corpus/core/src/client/settings/AccountSettingsCard.tsx +35 -31
- package/corpus/core/src/client/settings/SchedulingTimezoneField.tsx +71 -0
- package/corpus/core/src/client/settings/SettingsRow.tsx +113 -0
- package/corpus/core/src/client/settings/agent-settings-search.ts +2 -1
- package/corpus/core/src/client/settings/index.ts +6 -0
- package/corpus/core/src/integrations/identity.ts +2 -12
- package/corpus/core/src/jobs/actions/list-automation-runs.ts +33 -0
- package/corpus/core/src/jobs/actions/list-recurring-jobs.ts +27 -6
- package/corpus/core/src/jobs/actions/manage-recurring-job.ts +41 -9
- package/corpus/core/src/jobs/background-automation-runner.ts +88 -0
- package/corpus/core/src/jobs/cron.ts +52 -6
- package/corpus/core/src/jobs/frontmatter.ts +20 -0
- package/corpus/core/src/jobs/run-history.ts +242 -0
- package/corpus/core/src/jobs/scheduler.ts +73 -18
- package/corpus/core/src/jobs/tools.ts +54 -6
- package/corpus/core/src/localization/actions/get-localization-preference.ts +2 -2
- package/corpus/core/src/localization/actions/set-localization-preference.ts +49 -11
- package/corpus/core/src/localization/default-messages.ts +27 -0
- package/corpus/core/src/localization/shared.ts +39 -6
- package/corpus/core/src/localization/user-timezone.ts +36 -0
- package/corpus/core/src/org/app-roles-handlers.ts +1 -9
- package/corpus/core/src/org/index.ts +2 -0
- package/corpus/core/src/org/membership.ts +18 -0
- package/corpus/core/src/review/actions/create-review-comment.ts +4 -1
- package/corpus/core/src/review/actions/reply-review-comment.ts +4 -1
- package/corpus/core/src/review/index.ts +5 -0
- package/corpus/core/src/review/notifications.ts +163 -0
- package/corpus/core/src/review/types.ts +7 -0
- package/corpus/core/src/server/action-discovery.ts +4 -0
- package/corpus/core/src/server/activity-notifications.ts +174 -0
- package/corpus/core/src/server/app-name.ts +31 -0
- package/corpus/core/src/server/better-auth-instance.ts +4 -4
- package/corpus/core/src/server/email-templates.ts +70 -14
- package/corpus/core/src/server/email.ts +66 -3
- package/corpus/core/src/server/index.ts +10 -0
- package/corpus/core/src/sharing/access.ts +44 -2
- package/corpus/core/src/sharing/index.ts +5 -0
- package/corpus/core/src/sharing/recipients.ts +67 -0
- package/corpus/core/src/triggers/actions/list-automations.ts +27 -8
- package/corpus/core/src/triggers/actions/manage-automation.ts +17 -6
- package/corpus/core/src/triggers/actions.ts +16 -4
- package/corpus/core/src/triggers/dispatcher.ts +44 -30
- package/corpus/core/src/triggers/routes.ts +19 -6
- package/corpus/core/src/vitest-config.ts +68 -0
- package/corpus/core/vitest.config.ts +5 -0
- package/corpus/templates/analytics/app/pages/Settings.tsx +37 -53
- package/corpus/templates/analytics/changelog/2026-08-01-new-error-alerts-now-reach-you-by-email-and-a-scheduled-dash.md +6 -0
- package/corpus/templates/analytics/server/jobs/dashboard-report.ts +43 -0
- package/corpus/templates/analytics/server/lib/error-capture.ts +5 -1
- package/corpus/templates/analytics/vitest.config.ts +16 -12
- package/corpus/templates/assets/actions/refresh-generation-run.ts +3 -0
- package/corpus/templates/assets/app/components/ui/switch.tsx +1 -0
- package/corpus/templates/assets/app/hooks/use-assets-prefs.ts +55 -0
- package/corpus/templates/assets/app/i18n-data.ts +30 -0
- package/corpus/templates/assets/app/routes/settings.tsx +58 -55
- package/corpus/templates/assets/changelog/2026-08-01-get-an-email-when-a-generation-you-started-finishes-or-fails.md +6 -0
- package/corpus/templates/assets/server/lib/generation-run-notifications.ts +89 -0
- package/corpus/templates/assets/server/lib/video-runs.ts +8 -1
- package/corpus/templates/assets/server/routes/_agent-native/assets/user-prefs.get.ts +17 -0
- package/corpus/templates/assets/server/routes/_agent-native/assets/user-prefs.put.ts +36 -0
- package/corpus/templates/assets/shared/assets-user-prefs.ts +10 -0
- package/corpus/templates/assets/vitest.config.ts +16 -12
- package/corpus/templates/brain/app/routes/settings.tsx +15 -15
- package/corpus/templates/brain/vitest.config.ts +16 -12
- package/corpus/templates/calendar/app/pages/Settings.tsx +32 -40
- package/corpus/templates/calendar/changelog/2026-08-01-language-and-appearance-now-sit-together-in-one-preferences-.md +6 -0
- package/corpus/templates/calendar/vitest.config.ts +16 -12
- package/corpus/templates/chat/app/routes/settings.tsx +14 -22
- package/corpus/templates/chat/vitest.config.ts +9 -0
- package/corpus/templates/clips/.agents/skills/video-editing/SKILL.md +31 -0
- package/corpus/templates/clips/AGENTS.md +1 -0
- package/corpus/templates/clips/actions/add-comment.ts +12 -1
- package/corpus/templates/clips/actions/create-meeting.ts +2 -2
- package/corpus/templates/clips/actions/create-recording.ts +2 -2
- package/corpus/templates/clips/actions/finalize-recording.ts +42 -6
- package/corpus/templates/clips/actions/generate-filmstrip.ts +154 -0
- package/corpus/templates/clips/actions/get-recording-player-data.ts +6 -0
- package/corpus/templates/clips/actions/import-loom-recording.ts +2 -2
- package/corpus/templates/clips/actions/lib/ensure-recording-filmstrip.ts +258 -0
- package/corpus/templates/clips/actions/lib/ensure-seekable-video.ts +11 -3
- package/corpus/templates/clips/actions/react-to-comment.ts +13 -0
- package/corpus/templates/clips/actions/react-to-recording.ts +10 -1
- package/corpus/templates/clips/actions/reply-to-comment.ts +12 -1
- package/corpus/templates/clips/actions/stitch-recordings.ts +2 -2
- package/corpus/templates/clips/app/components/editor/editor-layout.tsx +152 -23
- package/corpus/templates/clips/app/components/editor/waveform.tsx +237 -75
- package/corpus/templates/clips/app/components/recorder/recorder-engine.ts +23 -1
- package/corpus/templates/clips/app/components/settings/ai-setup-section.tsx +441 -0
- package/corpus/templates/clips/app/components/settings/slack-section.tsx +329 -0
- package/corpus/templates/clips/app/components/settings/types.ts +14 -0
- package/corpus/templates/clips/app/components/settings/video-storage-section.tsx +443 -0
- package/corpus/templates/clips/app/hooks/use-secret-status.ts +69 -0
- package/corpus/templates/clips/app/i18n/en-US.ts +9 -1
- package/corpus/templates/clips/app/lib/video-filmstrip.ts +262 -0
- package/corpus/templates/clips/app/routes/_app.settings._index.tsx +165 -1351
- package/corpus/templates/clips/changelog/2026-07-27-saved-clips-now-resume-from-the-last-uploaded-byte-after-an-.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-31-editor-timeline-now-shows-video-frames-behind-the-waveform-g.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-31-email-notifications-now-go-out-when-someone-comments-on-or-r.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-31-you-can-now-set-a-default-visibility-for-new-recordings-in-s.md +6 -0
- package/corpus/templates/clips/changelog/2026-08-01-email-notifications-for-comments-and-reactions-now-actually-.md +6 -0
- package/corpus/templates/clips/changelog/2026-08-03-the-desktop-app-now-opens-directly-beneath-its-tray-icon-on-.md +6 -0
- package/corpus/templates/clips/chrome-extension/package.json +1 -0
- package/corpus/templates/clips/chrome-extension/vitest.config.ts +9 -0
- package/corpus/templates/clips/desktop/package.json +1 -0
- package/corpus/templates/clips/desktop/src/app.tsx +17 -1
- package/corpus/templates/clips/desktop/src/lib/recorder.ts +331 -36
- package/corpus/templates/clips/desktop/src/lib/upload-recovery.ts +129 -0
- package/corpus/templates/clips/desktop/src-tauri/src/native_screen.rs +815 -15
- package/corpus/templates/clips/desktop/src-tauri/src/tray.rs +61 -11
- package/corpus/templates/clips/desktop/vitest.config.ts +9 -0
- package/corpus/templates/clips/server/db/schema.ts +17 -0
- package/corpus/templates/clips/server/jobs/media-verification.ts +4 -0
- package/corpus/templates/clips/server/lib/activity-notifications.ts +185 -0
- package/corpus/templates/clips/server/lib/recording-media-cleanup.ts +2 -0
- package/corpus/templates/clips/server/lib/recording-upload-state.ts +46 -9
- package/corpus/templates/clips/server/lib/recordings.ts +24 -0
- package/corpus/templates/clips/server/lib/resumable-session.ts +10 -4
- package/corpus/templates/clips/server/lib/transactional-email-templates.ts +109 -0
- package/corpus/templates/clips/server/lib/upload-interruption.ts +8 -0
- package/corpus/templates/clips/server/lib/upload-lease.ts +50 -4
- package/corpus/templates/clips/server/lib/video-filmstrip-sprite.ts +207 -0
- package/corpus/templates/clips/server/lib/video-remux.ts +13 -4
- package/corpus/templates/clips/server/plugins/db.ts +16 -0
- package/corpus/templates/clips/server/routes/api/_agent-native-background/post-finalize-worker.post.ts +4 -0
- package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/abort.post.ts +123 -36
- package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/chunk.post.ts +159 -29
- package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/interrupt.post.ts +197 -0
- package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/reset-chunks.post.ts +193 -39
- package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/resume.get.ts +191 -12
- package/corpus/templates/clips/shared/clips-ai-prefs.ts +6 -0
- package/corpus/templates/clips/shared/feature-flags.ts +12 -0
- package/corpus/templates/clips/shared/recording-core.ts +4 -0
- package/corpus/templates/clips/vitest.config.ts +23 -19
- package/corpus/templates/content/actions/_database-utils.ts +113 -4
- package/corpus/templates/content/actions/add-comment.ts +15 -1
- package/corpus/templates/content/actions/get-document.ts +84 -3
- package/corpus/templates/content/actions/list-documents.ts +1 -0
- package/corpus/templates/content/app/components/editor/BuilderBodySyncingNotice.tsx +9 -2
- package/corpus/templates/content/app/components/editor/DocumentEditor.tsx +34 -15
- package/corpus/templates/content/app/components/editor/DocumentToolbar.tsx +55 -13
- package/corpus/templates/content/app/components/editor/body-hydration.ts +12 -6
- package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +2 -3
- package/corpus/templates/content/app/components/sidebar/DocumentSidebar.tsx +5 -12
- package/corpus/templates/content/app/components/ui/switch.tsx +1 -0
- package/corpus/templates/content/app/hooks/use-content-database.ts +16 -27
- package/corpus/templates/content/app/hooks/use-content-prefs.ts +55 -0
- package/corpus/templates/content/app/hooks/use-create-page.ts +3 -6
- package/corpus/templates/content/app/hooks/use-document-properties.ts +9 -16
- package/corpus/templates/content/app/hooks/use-document-versions.ts +3 -3
- package/corpus/templates/content/app/hooks/use-documents.ts +85 -29
- package/corpus/templates/content/app/hooks/use-notion.ts +15 -13
- package/corpus/templates/content/app/i18n-data.ts +72 -1
- package/corpus/templates/content/app/lib/document-query.ts +36 -0
- package/corpus/templates/content/app/routes/_app.settings.tsx +47 -18
- package/corpus/templates/content/changelog/2026-07-30-breadcrumb-menus-now-hide-internal-system-pages.md +6 -0
- package/corpus/templates/content/changelog/2026-08-01-get-an-email-when-someone-comments-on-replies-in-or-mentions.md +6 -0
- package/corpus/templates/content/changelog/2026-08-02-pages-opened-from-a-database-now-keep-that-database-s-fields.md +6 -0
- package/corpus/templates/content/server/lib/comment-notifications.ts +146 -0
- package/corpus/templates/content/server/lib/document-context.ts +11 -6
- package/corpus/templates/content/server/routes/_agent-native/content/user-prefs.get.ts +17 -0
- package/corpus/templates/content/server/routes/_agent-native/content/user-prefs.put.ts +36 -0
- package/corpus/templates/content/shared/api.ts +8 -0
- package/corpus/templates/content/shared/content-user-prefs.ts +11 -0
- package/corpus/templates/content/vitest.config.ts +20 -16
- package/corpus/templates/crm/app/routes/settings.tsx +14 -22
- package/corpus/templates/crm/vitest.config.ts +8 -4
- package/corpus/templates/design/app/routes/settings.tsx +14 -22
- package/corpus/templates/design/changelog/2026-08-01-review-comments-replies-and-mentions-now-send-an-email-to-th.md +6 -0
- package/corpus/templates/design/vitest.config.ts +18 -12
- package/corpus/templates/dispatch/app/routes/settings.tsx +26 -40
- package/corpus/templates/dispatch/vitest.config.ts +11 -7
- package/corpus/templates/factory/app/routes/settings.tsx +14 -22
- package/corpus/templates/factory/vitest.config.ts +9 -0
- package/corpus/templates/forms/app/routes/_app.settings.tsx +14 -22
- package/corpus/templates/forms/vitest.config.ts +27 -23
- package/corpus/templates/macros/app/routes/settings.tsx +14 -22
- package/corpus/templates/macros/vitest.config.ts +16 -12
- package/corpus/templates/mail/app/pages/SettingsPage.tsx +14 -16
- package/corpus/templates/mail/vitest.config.ts +16 -12
- package/corpus/templates/plan/app/routes/settings.tsx +30 -44
- package/corpus/templates/plan/vitest.config.ts +24 -20
- package/corpus/templates/slides/actions/add-slide-comment.ts +12 -1
- package/corpus/templates/slides/app/components/ui/switch.tsx +1 -0
- package/corpus/templates/slides/app/hooks/use-slides-prefs.ts +55 -0
- package/corpus/templates/slides/app/i18n/en-US.ts +4 -0
- package/corpus/templates/slides/app/routes/settings.tsx +45 -22
- package/corpus/templates/slides/changelog/2026-08-01-get-an-email-when-someone-comments-on-or-replies-in-your-dec.md +6 -0
- package/corpus/templates/slides/server/lib/comment-notifications.ts +182 -0
- package/corpus/templates/slides/server/routes/_agent-native/slides/user-prefs.get.ts +16 -0
- package/corpus/templates/slides/server/routes/_agent-native/slides/user-prefs.put.ts +36 -0
- package/corpus/templates/slides/shared/slides-user-prefs.ts +11 -0
- package/corpus/templates/slides/vitest.config.ts +17 -13
- package/corpus/templates/tasks/app/components/layout/Sidebar.tsx +2 -0
- package/corpus/templates/tasks/app/routes/settings.tsx +14 -22
- package/corpus/templates/tasks/vitest.config.ts +17 -13
- package/corpus/toolkit/CHANGELOG.md +9 -0
- package/corpus/toolkit/package.json +1 -1
- package/corpus/toolkit/vitest.config.ts +3 -0
- package/dist/automations/service.d.ts +2 -0
- package/dist/automations/service.d.ts.map +1 -1
- package/dist/automations/service.js +29 -4
- package/dist/automations/service.js.map +1 -1
- package/dist/cli/mcp-config-writers.d.ts.map +1 -1
- package/dist/cli/mcp-config-writers.js +24 -23
- package/dist/cli/mcp-config-writers.js.map +1 -1
- package/dist/client/agent-page/AgentJobsTab.d.ts.map +1 -1
- package/dist/client/agent-page/AgentJobsTab.js +86 -29
- package/dist/client/agent-page/AgentJobsTab.js.map +1 -1
- package/dist/client/agent-page/AutomationDetailsDialog.d.ts +21 -0
- package/dist/client/agent-page/AutomationDetailsDialog.d.ts.map +1 -0
- package/dist/client/agent-page/AutomationDetailsDialog.js +33 -0
- package/dist/client/agent-page/AutomationDetailsDialog.js.map +1 -0
- package/dist/client/agent-page/AutomationScheduleDialog.d.ts +15 -0
- package/dist/client/agent-page/AutomationScheduleDialog.d.ts.map +1 -0
- package/dist/client/agent-page/AutomationScheduleDialog.js +46 -0
- package/dist/client/agent-page/AutomationScheduleDialog.js.map +1 -0
- package/dist/client/agent-page/TimezoneSelect.d.ts +17 -0
- package/dist/client/agent-page/TimezoneSelect.d.ts.map +1 -0
- package/dist/client/agent-page/TimezoneSelect.js +107 -0
- package/dist/client/agent-page/TimezoneSelect.js.map +1 -0
- package/dist/client/agent-page/use-jobs.d.ts +20 -7
- package/dist/client/agent-page/use-jobs.d.ts.map +1 -1
- package/dist/client/agent-page/use-jobs.js +24 -4
- package/dist/client/agent-page/use-jobs.js.map +1 -1
- package/dist/client/org/OrgSwitcher.d.ts +8 -1
- package/dist/client/org/OrgSwitcher.d.ts.map +1 -1
- package/dist/client/org/OrgSwitcher.js +8 -2
- package/dist/client/org/OrgSwitcher.js.map +1 -1
- package/dist/client/settings/AccountSettingsCard.d.ts.map +1 -1
- package/dist/client/settings/AccountSettingsCard.js +10 -9
- package/dist/client/settings/AccountSettingsCard.js.map +1 -1
- package/dist/client/settings/SchedulingTimezoneField.d.ts +10 -0
- package/dist/client/settings/SchedulingTimezoneField.d.ts.map +1 -0
- package/dist/client/settings/SchedulingTimezoneField.js +30 -0
- package/dist/client/settings/SchedulingTimezoneField.js.map +1 -0
- package/dist/client/settings/SettingsRow.d.ts +36 -0
- package/dist/client/settings/SettingsRow.d.ts.map +1 -0
- package/dist/client/settings/SettingsRow.js +15 -0
- package/dist/client/settings/SettingsRow.js.map +1 -0
- package/dist/client/settings/agent-settings-search.d.ts.map +1 -1
- package/dist/client/settings/agent-settings-search.js +1 -1
- package/dist/client/settings/agent-settings-search.js.map +1 -1
- package/dist/client/settings/index.d.ts +1 -0
- package/dist/client/settings/index.d.ts.map +1 -1
- package/dist/client/settings/index.js +1 -0
- package/dist/client/settings/index.js.map +1 -1
- package/dist/file-upload/actions/upload-image.d.ts +1 -1
- package/dist/integrations/identity.d.ts.map +1 -1
- package/dist/integrations/identity.js +2 -11
- package/dist/integrations/identity.js.map +1 -1
- package/dist/jobs/actions/list-automation-runs.d.ts +8 -0
- package/dist/jobs/actions/list-automation-runs.d.ts.map +1 -0
- package/dist/jobs/actions/list-automation-runs.js +29 -0
- package/dist/jobs/actions/list-automation-runs.js.map +1 -0
- package/dist/jobs/actions/list-recurring-jobs.d.ts +2 -0
- package/dist/jobs/actions/list-recurring-jobs.d.ts.map +1 -1
- package/dist/jobs/actions/list-recurring-jobs.js +20 -7
- package/dist/jobs/actions/list-recurring-jobs.js.map +1 -1
- package/dist/jobs/actions/manage-recurring-job.d.ts +2 -0
- package/dist/jobs/actions/manage-recurring-job.js +30 -9
- package/dist/jobs/actions/manage-recurring-job.js.map +1 -1
- package/dist/jobs/background-automation-runner.d.ts.map +1 -1
- package/dist/jobs/background-automation-runner.js +60 -0
- package/dist/jobs/background-automation-runner.js.map +1 -1
- package/dist/jobs/cron.d.ts +16 -2
- package/dist/jobs/cron.d.ts.map +1 -1
- package/dist/jobs/cron.js +43 -6
- package/dist/jobs/cron.js.map +1 -1
- package/dist/jobs/frontmatter.d.ts +12 -0
- package/dist/jobs/frontmatter.d.ts.map +1 -1
- package/dist/jobs/frontmatter.js +8 -0
- package/dist/jobs/frontmatter.js.map +1 -1
- package/dist/jobs/run-history.d.ts +56 -0
- package/dist/jobs/run-history.d.ts.map +1 -0
- package/dist/jobs/run-history.js +169 -0
- package/dist/jobs/run-history.js.map +1 -0
- package/dist/jobs/scheduler.d.ts.map +1 -1
- package/dist/jobs/scheduler.js +51 -18
- package/dist/jobs/scheduler.js.map +1 -1
- package/dist/jobs/tools.d.ts.map +1 -1
- package/dist/jobs/tools.js +37 -6
- package/dist/jobs/tools.js.map +1 -1
- package/dist/localization/actions/get-localization-preference.d.ts +1 -2
- package/dist/localization/actions/get-localization-preference.d.ts.map +1 -1
- package/dist/localization/actions/get-localization-preference.js.map +1 -1
- package/dist/localization/actions/set-localization-preference.d.ts +3 -3
- package/dist/localization/actions/set-localization-preference.d.ts.map +1 -1
- package/dist/localization/actions/set-localization-preference.js +33 -8
- package/dist/localization/actions/set-localization-preference.js.map +1 -1
- package/dist/localization/default-messages.d.ts +25 -0
- package/dist/localization/default-messages.d.ts.map +1 -1
- package/dist/localization/default-messages.js +25 -0
- package/dist/localization/default-messages.js.map +1 -1
- package/dist/localization/shared.d.ts +16 -1
- package/dist/localization/shared.d.ts.map +1 -1
- package/dist/localization/shared.js +22 -4
- package/dist/localization/shared.js.map +1 -1
- package/dist/localization/user-timezone.d.ts +11 -0
- package/dist/localization/user-timezone.d.ts.map +1 -0
- package/dist/localization/user-timezone.js +31 -0
- package/dist/localization/user-timezone.js.map +1 -0
- package/dist/notifications/routes.d.ts +4 -4
- package/dist/observability/routes.d.ts +3 -3
- package/dist/org/app-roles-handlers.d.ts.map +1 -1
- package/dist/org/app-roles-handlers.js +1 -8
- package/dist/org/app-roles-handlers.js.map +1 -1
- package/dist/org/index.d.ts +1 -0
- package/dist/org/index.d.ts.map +1 -1
- package/dist/org/index.js +1 -0
- package/dist/org/index.js.map +1 -1
- package/dist/org/membership.d.ts +6 -0
- package/dist/org/membership.d.ts.map +1 -0
- package/dist/org/membership.js +16 -0
- package/dist/org/membership.js.map +1 -0
- package/dist/progress/routes.d.ts +1 -1
- package/dist/provider-api/actions/custom-provider-registration.d.ts +13 -13
- package/dist/provider-api/actions/provider-api.d.ts +8 -8
- package/dist/provider-api/corpus-jobs.d.ts +2 -2
- package/dist/review/actions/create-review-comment.d.ts +32 -1
- package/dist/review/actions/create-review-comment.d.ts.map +1 -1
- package/dist/review/actions/create-review-comment.js +3 -1
- package/dist/review/actions/create-review-comment.js.map +1 -1
- package/dist/review/actions/reply-review-comment.d.ts +32 -1
- package/dist/review/actions/reply-review-comment.d.ts.map +1 -1
- package/dist/review/actions/reply-review-comment.js +3 -1
- package/dist/review/actions/reply-review-comment.js.map +1 -1
- package/dist/review/index.d.ts +1 -0
- package/dist/review/index.d.ts.map +1 -1
- package/dist/review/index.js +1 -0
- package/dist/review/index.js.map +1 -1
- package/dist/review/notifications.d.ts +24 -0
- package/dist/review/notifications.d.ts.map +1 -0
- package/dist/review/notifications.js +123 -0
- package/dist/review/notifications.js.map +1 -0
- package/dist/review/types.d.ts +5 -0
- package/dist/review/types.d.ts.map +1 -1
- package/dist/review/types.js.map +1 -1
- package/dist/server/action-discovery.d.ts.map +1 -1
- package/dist/server/action-discovery.js +4 -0
- package/dist/server/action-discovery.js.map +1 -1
- package/dist/server/activity-notifications.d.ts +61 -0
- package/dist/server/activity-notifications.d.ts.map +1 -0
- package/dist/server/activity-notifications.js +103 -0
- package/dist/server/activity-notifications.js.map +1 -0
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/server/app-name.d.ts +18 -0
- package/dist/server/app-name.d.ts.map +1 -1
- package/dist/server/app-name.js +31 -0
- package/dist/server/app-name.js.map +1 -1
- package/dist/server/better-auth-instance.js +4 -4
- package/dist/server/better-auth-instance.js.map +1 -1
- package/dist/server/email-templates.d.ts +12 -0
- package/dist/server/email-templates.d.ts.map +1 -1
- package/dist/server/email-templates.js +57 -14
- package/dist/server/email-templates.js.map +1 -1
- package/dist/server/email.d.ts +12 -0
- package/dist/server/email.d.ts.map +1 -1
- package/dist/server/email.js +52 -5
- package/dist/server/email.js.map +1 -1
- package/dist/server/index.d.ts +1 -0
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +1 -0
- package/dist/server/index.js.map +1 -1
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/dist/sharing/access.d.ts.map +1 -1
- package/dist/sharing/access.js +32 -2
- package/dist/sharing/access.js.map +1 -1
- package/dist/sharing/index.d.ts +1 -0
- package/dist/sharing/index.d.ts.map +1 -1
- package/dist/sharing/index.js +1 -0
- package/dist/sharing/index.js.map +1 -1
- package/dist/sharing/recipients.d.ts +31 -0
- package/dist/sharing/recipients.d.ts.map +1 -0
- package/dist/sharing/recipients.js +40 -0
- package/dist/sharing/recipients.js.map +1 -0
- package/dist/templates/chat/app/routes/settings.tsx +14 -22
- package/dist/templates/chat/vitest.config.ts +9 -0
- package/dist/triggers/actions/list-automations.d.ts +2 -0
- package/dist/triggers/actions/list-automations.d.ts.map +1 -1
- package/dist/triggers/actions/list-automations.js +19 -8
- package/dist/triggers/actions/list-automations.js.map +1 -1
- package/dist/triggers/actions/manage-automation.d.ts +6 -0
- package/dist/triggers/actions/manage-automation.js +12 -6
- package/dist/triggers/actions/manage-automation.js.map +1 -1
- package/dist/triggers/actions.d.ts.map +1 -1
- package/dist/triggers/actions.js +15 -4
- package/dist/triggers/actions.js.map +1 -1
- package/dist/triggers/dispatcher.d.ts.map +1 -1
- package/dist/triggers/dispatcher.js +19 -17
- package/dist/triggers/dispatcher.js.map +1 -1
- package/dist/triggers/routes.d.ts.map +1 -1
- package/dist/triggers/routes.js +6 -6
- package/dist/triggers/routes.js.map +1 -1
- package/dist/vitest-config.d.ts +10 -0
- package/dist/vitest-config.d.ts.map +1 -0
- package/dist/vitest-config.js +55 -0
- package/dist/vitest-config.js.map +1 -0
- package/package.json +5 -4
- package/src/automations/service.ts +41 -4
- package/src/cli/mcp-config-writers.ts +27 -21
- package/src/client/agent-page/AgentJobsTab.tsx +187 -85
- package/src/client/agent-page/AutomationDetailsDialog.tsx +208 -0
- package/src/client/agent-page/AutomationScheduleDialog.tsx +180 -0
- package/src/client/agent-page/TimezoneSelect.tsx +241 -0
- package/src/client/agent-page/use-jobs.ts +50 -10
- package/src/client/org/OrgSwitcher.tsx +26 -0
- package/src/client/settings/AccountSettingsCard.tsx +35 -31
- package/src/client/settings/SchedulingTimezoneField.tsx +71 -0
- package/src/client/settings/SettingsRow.tsx +113 -0
- package/src/client/settings/agent-settings-search.ts +2 -1
- package/src/client/settings/index.ts +6 -0
- package/src/integrations/identity.ts +2 -12
- package/src/jobs/actions/list-automation-runs.ts +33 -0
- package/src/jobs/actions/list-recurring-jobs.ts +27 -6
- package/src/jobs/actions/manage-recurring-job.ts +41 -9
- package/src/jobs/background-automation-runner.ts +88 -0
- package/src/jobs/cron.ts +52 -6
- package/src/jobs/frontmatter.ts +20 -0
- package/src/jobs/run-history.ts +242 -0
- package/src/jobs/scheduler.ts +73 -18
- package/src/jobs/tools.ts +54 -6
- package/src/localization/actions/get-localization-preference.ts +2 -2
- package/src/localization/actions/set-localization-preference.ts +49 -11
- package/src/localization/default-messages.ts +27 -0
- package/src/localization/shared.ts +39 -6
- package/src/localization/user-timezone.ts +36 -0
- package/src/org/app-roles-handlers.ts +1 -9
- package/src/org/index.ts +2 -0
- package/src/org/membership.ts +18 -0
- package/src/review/actions/create-review-comment.ts +4 -1
- package/src/review/actions/reply-review-comment.ts +4 -1
- package/src/review/index.ts +5 -0
- package/src/review/notifications.ts +163 -0
- package/src/review/types.ts +7 -0
- package/src/server/action-discovery.ts +4 -0
- package/src/server/activity-notifications.ts +174 -0
- package/src/server/app-name.ts +31 -0
- package/src/server/better-auth-instance.ts +4 -4
- package/src/server/email-templates.ts +70 -14
- package/src/server/email.ts +66 -3
- package/src/server/index.ts +10 -0
- package/src/sharing/access.ts +44 -2
- package/src/sharing/index.ts +5 -0
- package/src/sharing/recipients.ts +67 -0
- package/src/templates/chat/app/routes/settings.tsx +14 -22
- package/src/templates/chat/vitest.config.ts +9 -0
- package/src/triggers/actions/list-automations.ts +27 -8
- package/src/triggers/actions/manage-automation.ts +17 -6
- package/src/triggers/actions.ts +16 -4
- package/src/triggers/dispatcher.ts +44 -30
- package/src/triggers/routes.ts +19 -6
- package/src/vitest-config.ts +68 -0
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generate and attach a timeline filmstrip sprite to a stored recording.
|
|
3
|
+
*
|
|
4
|
+
* This is the sibling of `ensure-seekable-video.ts` and follows the same shape:
|
|
5
|
+
* re-fetch the stored provider media, run one ffmpeg pass, upload the result,
|
|
6
|
+
* and point the recording row at it. Nothing here is destructive — a recording
|
|
7
|
+
* without a sprite still renders a filmstrip, because the editor falls back to
|
|
8
|
+
* extracting frames in the browser.
|
|
9
|
+
*
|
|
10
|
+
* Local/dev media (relative `/api/video/...` URLs backed by application_state)
|
|
11
|
+
* is skipped rather than special-cased: there is no absolute URL to fetch
|
|
12
|
+
* server-side, and the browser fallback already covers that environment.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import {
|
|
16
|
+
readAppState,
|
|
17
|
+
writeAppState,
|
|
18
|
+
} from "@agent-native/core/application-state";
|
|
19
|
+
import { uploadFile } from "@agent-native/core/file-upload";
|
|
20
|
+
import { and, eq, isNull } from "drizzle-orm";
|
|
21
|
+
|
|
22
|
+
import { getDb, schema } from "../../server/db/index.js";
|
|
23
|
+
import { deleteRecordingMediaObjects } from "../../server/lib/recording-media-cleanup.js";
|
|
24
|
+
import { ownerEmailMatches } from "../../server/lib/recordings.js";
|
|
25
|
+
import {
|
|
26
|
+
DEFAULT_FILMSTRIP_FRAME_COUNT,
|
|
27
|
+
generateFilmstripSprite,
|
|
28
|
+
type FilmstripSpriteGrid,
|
|
29
|
+
} from "../../server/lib/video-filmstrip-sprite.js";
|
|
30
|
+
import { isLoomRecordingSource } from "../../shared/loom.js";
|
|
31
|
+
import {
|
|
32
|
+
fetchProviderBytes,
|
|
33
|
+
isRemoteProviderUrl,
|
|
34
|
+
} from "./ensure-seekable-video.js";
|
|
35
|
+
|
|
36
|
+
export type EnsureFilmstripStatus =
|
|
37
|
+
| "generated"
|
|
38
|
+
| "already-generated"
|
|
39
|
+
| "not-found"
|
|
40
|
+
| "skipped-not-ready"
|
|
41
|
+
| "skipped-no-media"
|
|
42
|
+
| "skipped-local-media"
|
|
43
|
+
| "skipped-no-duration"
|
|
44
|
+
| "skipped-no-ffmpeg"
|
|
45
|
+
| "skipped-fetch-failed"
|
|
46
|
+
| "skipped-too-large"
|
|
47
|
+
| "skipped-upload-failed"
|
|
48
|
+
| "failed-ffmpeg"
|
|
49
|
+
| "failed-empty-output";
|
|
50
|
+
|
|
51
|
+
export interface EnsureFilmstripResult {
|
|
52
|
+
recordingId: string;
|
|
53
|
+
status: EnsureFilmstripStatus;
|
|
54
|
+
changed: boolean;
|
|
55
|
+
filmstripUrl?: string | null;
|
|
56
|
+
grid?: FilmstripSpriteGrid;
|
|
57
|
+
detail?: string;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** application_state key recording which media a sprite was generated from. */
|
|
61
|
+
export function filmstripMarkerKey(recordingId: string): string {
|
|
62
|
+
return `recording-filmstrip-${recordingId}`;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
async function isAlreadyGenerated(
|
|
66
|
+
recordingId: string,
|
|
67
|
+
videoUrl: string,
|
|
68
|
+
): Promise<boolean> {
|
|
69
|
+
const marker = await readAppState(filmstripMarkerKey(recordingId)).catch(
|
|
70
|
+
() => null,
|
|
71
|
+
);
|
|
72
|
+
return Boolean(
|
|
73
|
+
marker &&
|
|
74
|
+
typeof marker === "object" &&
|
|
75
|
+
(marker as { videoUrl?: unknown }).videoUrl === videoUrl,
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Ensure one recording has a filmstrip sprite. Owner-scoped: pass the resolved
|
|
81
|
+
* owner email so the lookup can only touch that owner's rows.
|
|
82
|
+
*/
|
|
83
|
+
export async function ensureRecordingFilmstrip(params: {
|
|
84
|
+
recordingId: string;
|
|
85
|
+
ownerEmail: string;
|
|
86
|
+
frameCount?: number;
|
|
87
|
+
force?: boolean;
|
|
88
|
+
}): Promise<EnsureFilmstripResult> {
|
|
89
|
+
const {
|
|
90
|
+
recordingId,
|
|
91
|
+
ownerEmail,
|
|
92
|
+
frameCount = DEFAULT_FILMSTRIP_FRAME_COUNT,
|
|
93
|
+
force = false,
|
|
94
|
+
} = params;
|
|
95
|
+
const db = getDb();
|
|
96
|
+
|
|
97
|
+
const [rec] = await db
|
|
98
|
+
.select({
|
|
99
|
+
id: schema.recordings.id,
|
|
100
|
+
status: schema.recordings.status,
|
|
101
|
+
videoUrl: schema.recordings.videoUrl,
|
|
102
|
+
durationMs: schema.recordings.durationMs,
|
|
103
|
+
filmstripUrl: schema.recordings.filmstripUrl,
|
|
104
|
+
sourceAppName: schema.recordings.sourceAppName,
|
|
105
|
+
})
|
|
106
|
+
.from(schema.recordings)
|
|
107
|
+
.where(
|
|
108
|
+
and(
|
|
109
|
+
eq(schema.recordings.id, recordingId),
|
|
110
|
+
ownerEmailMatches(schema.recordings.ownerEmail, ownerEmail),
|
|
111
|
+
),
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
if (!rec) return { recordingId, status: "not-found", changed: false };
|
|
115
|
+
if (rec.status !== "ready") {
|
|
116
|
+
return { recordingId, status: "skipped-not-ready", changed: false };
|
|
117
|
+
}
|
|
118
|
+
if (isLoomRecordingSource(rec)) {
|
|
119
|
+
return { recordingId, status: "skipped-local-media", changed: false };
|
|
120
|
+
}
|
|
121
|
+
if (!rec.videoUrl) {
|
|
122
|
+
return { recordingId, status: "skipped-no-media", changed: false };
|
|
123
|
+
}
|
|
124
|
+
if (!isRemoteProviderUrl(rec.videoUrl)) {
|
|
125
|
+
return {
|
|
126
|
+
recordingId,
|
|
127
|
+
status: "skipped-local-media",
|
|
128
|
+
changed: false,
|
|
129
|
+
filmstripUrl: rec.filmstripUrl,
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
if (rec.durationMs <= 0) {
|
|
133
|
+
return { recordingId, status: "skipped-no-duration", changed: false };
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (
|
|
137
|
+
!force &&
|
|
138
|
+
rec.filmstripUrl &&
|
|
139
|
+
(await isAlreadyGenerated(recordingId, rec.videoUrl))
|
|
140
|
+
) {
|
|
141
|
+
return {
|
|
142
|
+
recordingId,
|
|
143
|
+
status: "already-generated",
|
|
144
|
+
changed: false,
|
|
145
|
+
filmstripUrl: rec.filmstripUrl,
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const fetched = await fetchProviderBytes(rec.videoUrl);
|
|
150
|
+
if (!fetched.ok) {
|
|
151
|
+
return { recordingId, status: fetched.reason, changed: false };
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const sprite = await generateFilmstripSprite({
|
|
155
|
+
mediaBytes: fetched.bytes,
|
|
156
|
+
durationMs: rec.durationMs,
|
|
157
|
+
frameCount,
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
if (sprite.status !== "generated" || !sprite.sprite) {
|
|
161
|
+
return {
|
|
162
|
+
recordingId,
|
|
163
|
+
status: sprite.status,
|
|
164
|
+
changed: false,
|
|
165
|
+
filmstripUrl: rec.filmstripUrl,
|
|
166
|
+
detail: sprite.detail,
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const upload = await uploadFile({
|
|
171
|
+
data: sprite.sprite.bytes,
|
|
172
|
+
filename: `${recordingId}-filmstrip-${Date.now()}.jpg`,
|
|
173
|
+
mimeType: "image/jpeg",
|
|
174
|
+
ownerEmail,
|
|
175
|
+
stableUrl: true,
|
|
176
|
+
recordAsset: false,
|
|
177
|
+
}).catch((err) => {
|
|
178
|
+
console.warn("[ensure-recording-filmstrip] sprite upload failed", {
|
|
179
|
+
recordingId,
|
|
180
|
+
error: err instanceof Error ? err.message : String(err),
|
|
181
|
+
});
|
|
182
|
+
return null;
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
if (!upload?.url) {
|
|
186
|
+
return {
|
|
187
|
+
recordingId,
|
|
188
|
+
status: "skipped-upload-failed",
|
|
189
|
+
changed: false,
|
|
190
|
+
filmstripUrl: rec.filmstripUrl,
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const { grid } = sprite.sprite;
|
|
195
|
+
const updated = await db
|
|
196
|
+
.update(schema.recordings)
|
|
197
|
+
.set({
|
|
198
|
+
filmstripUrl: upload.url,
|
|
199
|
+
filmstripFrameCount: grid.frameCount,
|
|
200
|
+
filmstripColumns: grid.columns,
|
|
201
|
+
filmstripRows: grid.rows,
|
|
202
|
+
filmstripFrameWidth: grid.frameWidth,
|
|
203
|
+
filmstripFrameHeight: grid.frameHeight,
|
|
204
|
+
updatedAt: new Date().toISOString(),
|
|
205
|
+
})
|
|
206
|
+
.where(
|
|
207
|
+
and(
|
|
208
|
+
eq(schema.recordings.id, recordingId),
|
|
209
|
+
ownerEmailMatches(schema.recordings.ownerEmail, ownerEmail),
|
|
210
|
+
// Don't attach a sprite generated from media the row no longer points at.
|
|
211
|
+
eq(schema.recordings.videoUrl, rec.videoUrl),
|
|
212
|
+
// Ensure no concurrent job modified filmstripUrl in the interim.
|
|
213
|
+
rec.filmstripUrl
|
|
214
|
+
? eq(schema.recordings.filmstripUrl, rec.filmstripUrl)
|
|
215
|
+
: isNull(schema.recordings.filmstripUrl),
|
|
216
|
+
),
|
|
217
|
+
)
|
|
218
|
+
.returning({
|
|
219
|
+
id: schema.recordings.id,
|
|
220
|
+
filmstripUrl: schema.recordings.filmstripUrl,
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
if (updated.length !== 1 || updated[0]?.filmstripUrl !== upload.url) {
|
|
224
|
+
await deleteRecordingMediaObjects({
|
|
225
|
+
id: recordingId,
|
|
226
|
+
filmstripUrl: upload.url,
|
|
227
|
+
}).catch(() => {});
|
|
228
|
+
return {
|
|
229
|
+
recordingId,
|
|
230
|
+
status: "skipped-upload-failed",
|
|
231
|
+
changed: false,
|
|
232
|
+
filmstripUrl: rec.filmstripUrl,
|
|
233
|
+
detail: "Recording changed while the filmstrip sprite was uploading.",
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
if (rec.filmstripUrl && rec.filmstripUrl !== upload.url) {
|
|
238
|
+
await deleteRecordingMediaObjects({
|
|
239
|
+
id: recordingId,
|
|
240
|
+
filmstripUrl: rec.filmstripUrl,
|
|
241
|
+
}).catch(() => {});
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
await writeAppState(filmstripMarkerKey(recordingId), {
|
|
245
|
+
recordingId,
|
|
246
|
+
videoUrl: rec.videoUrl,
|
|
247
|
+
at: new Date().toISOString(),
|
|
248
|
+
});
|
|
249
|
+
await writeAppState("refresh-signal", { ts: Date.now() });
|
|
250
|
+
|
|
251
|
+
return {
|
|
252
|
+
recordingId,
|
|
253
|
+
status: "generated",
|
|
254
|
+
changed: true,
|
|
255
|
+
filmstripUrl: upload.url,
|
|
256
|
+
grid,
|
|
257
|
+
};
|
|
258
|
+
}
|
|
@@ -95,14 +95,22 @@ async function isAlreadyMarked(
|
|
|
95
95
|
);
|
|
96
96
|
}
|
|
97
97
|
|
|
98
|
-
function isRemoteProviderUrl(
|
|
98
|
+
export function isRemoteProviderUrl(
|
|
99
|
+
videoUrl: string | null | undefined,
|
|
100
|
+
): boolean {
|
|
99
101
|
return typeof videoUrl === "string" && /^https:\/\//i.test(videoUrl.trim());
|
|
100
102
|
}
|
|
101
103
|
|
|
102
|
-
|
|
104
|
+
/**
|
|
105
|
+
* Download stored provider media for server-side processing. Shared with the
|
|
106
|
+
* filmstrip sprite path — both need the same size cap and timeout, and both
|
|
107
|
+
* report the same two failure reasons rather than an empty buffer.
|
|
108
|
+
*/
|
|
109
|
+
export async function fetchProviderBytes(
|
|
103
110
|
videoUrl: string,
|
|
104
111
|
): Promise<
|
|
105
|
-
{ ok: true; bytes: Uint8Array }
|
|
112
|
+
| { ok: true; bytes: Uint8Array }
|
|
113
|
+
| { ok: false; reason: "skipped-fetch-failed" | "skipped-too-large" }
|
|
106
114
|
> {
|
|
107
115
|
const controller = new AbortController();
|
|
108
116
|
const timeout = setTimeout(
|
|
@@ -17,6 +17,7 @@ import { and, eq } from "drizzle-orm";
|
|
|
17
17
|
import { z } from "zod";
|
|
18
18
|
|
|
19
19
|
import { getDb, schema } from "../server/db/index.js";
|
|
20
|
+
import { notifyRecordingReaction } from "../server/lib/activity-notifications.js";
|
|
20
21
|
|
|
21
22
|
const MAX_CAS_ATTEMPTS = 3;
|
|
22
23
|
|
|
@@ -90,12 +91,24 @@ export default defineAction({
|
|
|
90
91
|
.returning({ id: schema.recordingComments.id });
|
|
91
92
|
|
|
92
93
|
if (updated.length > 0) {
|
|
94
|
+
// Removing a reaction is not an event worth emailing about.
|
|
95
|
+
const notified = had
|
|
96
|
+
? null
|
|
97
|
+
: await notifyRecordingReaction({
|
|
98
|
+
recordingId: existing.recordingId,
|
|
99
|
+
emoji: args.emoji,
|
|
100
|
+
viewerEmail,
|
|
101
|
+
videoTimestampMs: existing.videoTimestampMs,
|
|
102
|
+
extraRecipients: [existing.authorEmail],
|
|
103
|
+
});
|
|
104
|
+
|
|
93
105
|
await writeAppState("refresh-signal", { ts: Date.now() });
|
|
94
106
|
return {
|
|
95
107
|
id: args.commentId,
|
|
96
108
|
emoji: args.emoji,
|
|
97
109
|
reacted: !had,
|
|
98
110
|
reactions: next,
|
|
111
|
+
notified,
|
|
99
112
|
};
|
|
100
113
|
}
|
|
101
114
|
}
|
|
@@ -12,6 +12,7 @@ import { assertAccess } from "@agent-native/core/sharing";
|
|
|
12
12
|
import { z } from "zod";
|
|
13
13
|
|
|
14
14
|
import { getDb, schema } from "../server/db/index.js";
|
|
15
|
+
import { notifyRecordingReaction } from "../server/lib/activity-notifications.js";
|
|
15
16
|
import { nanoid } from "../server/lib/recordings.js";
|
|
16
17
|
|
|
17
18
|
export default defineAction({
|
|
@@ -50,8 +51,16 @@ export default defineAction({
|
|
|
50
51
|
createdAt: now,
|
|
51
52
|
});
|
|
52
53
|
|
|
54
|
+
const notified = await notifyRecordingReaction({
|
|
55
|
+
recordingId: args.recordingId,
|
|
56
|
+
emoji: args.emoji,
|
|
57
|
+
viewerEmail,
|
|
58
|
+
viewerName: args.viewerName,
|
|
59
|
+
videoTimestampMs: args.videoTimestampMs,
|
|
60
|
+
});
|
|
61
|
+
|
|
53
62
|
await writeAppState("refresh-signal", { ts: Date.now() });
|
|
54
63
|
|
|
55
|
-
return { id };
|
|
64
|
+
return { id, notified };
|
|
56
65
|
},
|
|
57
66
|
});
|
|
@@ -15,6 +15,7 @@ import { eq } from "drizzle-orm";
|
|
|
15
15
|
import { z } from "zod";
|
|
16
16
|
|
|
17
17
|
import { getDb, schema } from "../server/db/index.js";
|
|
18
|
+
import { notifyRecordingComment } from "../server/lib/activity-notifications.js";
|
|
18
19
|
import { nanoid } from "../server/lib/recordings.js";
|
|
19
20
|
|
|
20
21
|
export default defineAction({
|
|
@@ -58,12 +59,22 @@ export default defineAction({
|
|
|
58
59
|
updatedAt: now,
|
|
59
60
|
});
|
|
60
61
|
|
|
62
|
+
const notified = await notifyRecordingComment({
|
|
63
|
+
recordingId: parent.recordingId,
|
|
64
|
+
threadId: parent.threadId,
|
|
65
|
+
authorEmail,
|
|
66
|
+
authorName: args.authorName,
|
|
67
|
+
content: args.content,
|
|
68
|
+
videoTimestampMs: parent.videoTimestampMs,
|
|
69
|
+
isReply: true,
|
|
70
|
+
});
|
|
71
|
+
|
|
61
72
|
await writeAppState("refresh-signal", { ts: Date.now() });
|
|
62
73
|
|
|
63
74
|
console.log(
|
|
64
75
|
`Replied to comment ${args.commentId} (thread: ${parent.threadId})`,
|
|
65
76
|
);
|
|
66
77
|
|
|
67
|
-
return { id, threadId: parent.threadId, parentId: parent.id };
|
|
78
|
+
return { id, threadId: parent.threadId, parentId: parent.id, notified };
|
|
68
79
|
},
|
|
69
80
|
});
|
|
@@ -36,7 +36,7 @@ import { parseEdits, serializeEdits } from "../app/lib/timestamp-mapping.js";
|
|
|
36
36
|
import { getDb, schema } from "../server/db/index.js";
|
|
37
37
|
import {
|
|
38
38
|
getCurrentOwnerEmail,
|
|
39
|
-
|
|
39
|
+
getDefaultRecordingVisibility,
|
|
40
40
|
nanoid,
|
|
41
41
|
ownerEmailMatches,
|
|
42
42
|
} from "../server/lib/recordings.js";
|
|
@@ -121,7 +121,7 @@ export default defineAction({
|
|
|
121
121
|
}
|
|
122
122
|
const organizationId = ordered[0].organizationId;
|
|
123
123
|
const defaultVisibility =
|
|
124
|
-
await
|
|
124
|
+
await getDefaultRecordingVisibility(organizationId);
|
|
125
125
|
|
|
126
126
|
const totalDuration =
|
|
127
127
|
args.durationMs ??
|
|
@@ -58,6 +58,11 @@ import {
|
|
|
58
58
|
type EditsJson,
|
|
59
59
|
} from "@/lib/timestamp-mapping";
|
|
60
60
|
import { cn } from "@/lib/utils";
|
|
61
|
+
import {
|
|
62
|
+
extractFilmstripThumbnails,
|
|
63
|
+
type FilmstripFrame,
|
|
64
|
+
type FilmstripSprite,
|
|
65
|
+
} from "@/lib/video-filmstrip";
|
|
61
66
|
import { computePeaks, type WaveformPeaks } from "@/lib/waveform-peaks";
|
|
62
67
|
|
|
63
68
|
import { ChaptersEditor } from "./chapters-editor";
|
|
@@ -76,7 +81,7 @@ export interface EditorLayoutProps {
|
|
|
76
81
|
className?: string;
|
|
77
82
|
}
|
|
78
83
|
|
|
79
|
-
const WAVEFORM_HEIGHT =
|
|
84
|
+
const WAVEFORM_HEIGHT = 100;
|
|
80
85
|
const MIN_TIMELINE_ZOOM = 1;
|
|
81
86
|
const MAX_TIMELINE_ZOOM = 50;
|
|
82
87
|
|
|
@@ -244,10 +249,20 @@ export function EditorLayout({ recordingId, className }: EditorLayoutProps) {
|
|
|
244
249
|
return () => ro.disconnect();
|
|
245
250
|
}, []);
|
|
246
251
|
|
|
247
|
-
const
|
|
248
|
-
viewportWidth
|
|
249
|
-
|
|
250
|
-
|
|
252
|
+
const baseTrackWidth = useMemo(() => {
|
|
253
|
+
if (durationMs <= 0 || viewportWidth <= 0) return viewportWidth;
|
|
254
|
+
const durationSec = durationMs / 1000;
|
|
255
|
+
// Scale timeline track width proportionally with duration so long videos scale & scroll (bounded to safe max allocation)
|
|
256
|
+
const durationScaledPx = Math.min(8192, Math.round(durationSec * 14));
|
|
257
|
+
return Math.max(viewportWidth, durationScaledPx);
|
|
258
|
+
}, [durationMs, viewportWidth]);
|
|
259
|
+
|
|
260
|
+
const totalWidth = useMemo(() => {
|
|
261
|
+
return Math.max(
|
|
262
|
+
baseTrackWidth,
|
|
263
|
+
Math.floor(baseTrackWidth * Math.max(1, zoom)),
|
|
264
|
+
);
|
|
265
|
+
}, [baseTrackWidth, zoom]);
|
|
251
266
|
|
|
252
267
|
const calculateAnchoredScrollLeft = useCallback(
|
|
253
268
|
(
|
|
@@ -255,8 +270,8 @@ export function EditorLayout({ recordingId, className }: EditorLayoutProps) {
|
|
|
255
270
|
anchor?: { anchorRatio?: number; viewportX?: number },
|
|
256
271
|
) => {
|
|
257
272
|
const nextTotalWidth = Math.max(
|
|
258
|
-
|
|
259
|
-
Math.floor(
|
|
273
|
+
baseTrackWidth,
|
|
274
|
+
Math.floor(baseTrackWidth * Math.max(1, nextZoom)),
|
|
260
275
|
);
|
|
261
276
|
const maxScrollLeft = Math.max(0, nextTotalWidth - viewportWidth);
|
|
262
277
|
const anchorMs = selectionRange
|
|
@@ -277,7 +292,7 @@ export function EditorLayout({ recordingId, className }: EditorLayoutProps) {
|
|
|
277
292
|
const anchorX = anchorRatio * nextTotalWidth;
|
|
278
293
|
return Math.max(0, Math.min(maxScrollLeft, anchorX - viewportX));
|
|
279
294
|
},
|
|
280
|
-
[durationMs, playheadMs, selectionRange, viewportWidth],
|
|
295
|
+
[baseTrackWidth, durationMs, playheadMs, selectionRange, viewportWidth],
|
|
281
296
|
);
|
|
282
297
|
|
|
283
298
|
const setAnchoredZoom = useCallback(
|
|
@@ -319,8 +334,8 @@ export function EditorLayout({ recordingId, className }: EditorLayoutProps) {
|
|
|
319
334
|
viewportX: number,
|
|
320
335
|
) => {
|
|
321
336
|
const sourceTotalWidth = Math.max(
|
|
322
|
-
|
|
323
|
-
Math.floor(
|
|
337
|
+
baseTrackWidth,
|
|
338
|
+
Math.floor(baseTrackWidth * Math.max(1, sourceZoom)),
|
|
324
339
|
);
|
|
325
340
|
return Math.max(
|
|
326
341
|
0,
|
|
@@ -390,7 +405,7 @@ export function EditorLayout({ recordingId, className }: EditorLayoutProps) {
|
|
|
390
405
|
el.removeEventListener("gesturechange", handleGestureChange);
|
|
391
406
|
el.removeEventListener("gestureend", handleGestureEnd);
|
|
392
407
|
};
|
|
393
|
-
}, [scrollLeft, setAnchoredZoom, viewportWidth, zoom]);
|
|
408
|
+
}, [baseTrackWidth, scrollLeft, setAnchoredZoom, viewportWidth, zoom]);
|
|
394
409
|
|
|
395
410
|
// Sync the <video> to play state.
|
|
396
411
|
useEffect(() => {
|
|
@@ -498,6 +513,96 @@ export function EditorLayout({ recordingId, className }: EditorLayoutProps) {
|
|
|
498
513
|
};
|
|
499
514
|
}, [recordingId, waveformMediaUrl]);
|
|
500
515
|
|
|
516
|
+
// Filmstrip drawn behind the waveform. A server-generated sprite is one
|
|
517
|
+
// cached image request and is preferred; browser extraction is the fallback
|
|
518
|
+
// for hosts without ffmpeg and for local/dev media the server can't fetch.
|
|
519
|
+
const filmstripSprite = useMemo<FilmstripSprite | null>(() => {
|
|
520
|
+
const url = recording?.filmstripUrl;
|
|
521
|
+
const frameCount = Number(recording?.filmstripFrameCount ?? 0);
|
|
522
|
+
const columns = Number(recording?.filmstripColumns ?? 0);
|
|
523
|
+
if (!url || frameCount <= 0 || columns <= 0) return null;
|
|
524
|
+
return {
|
|
525
|
+
url,
|
|
526
|
+
frameCount,
|
|
527
|
+
columns,
|
|
528
|
+
rows: Number(recording?.filmstripRows ?? 1) || 1,
|
|
529
|
+
frameWidth: Number(recording?.filmstripFrameWidth ?? 0) || 160,
|
|
530
|
+
frameHeight: Number(recording?.filmstripFrameHeight ?? 0) || 90,
|
|
531
|
+
};
|
|
532
|
+
}, [
|
|
533
|
+
recording?.filmstripUrl,
|
|
534
|
+
recording?.filmstripFrameCount,
|
|
535
|
+
recording?.filmstripColumns,
|
|
536
|
+
recording?.filmstripRows,
|
|
537
|
+
recording?.filmstripFrameWidth,
|
|
538
|
+
recording?.filmstripFrameHeight,
|
|
539
|
+
]);
|
|
540
|
+
|
|
541
|
+
const [filmstripFrames, setFilmstripFrames] = useState<FilmstripFrame[]>([]);
|
|
542
|
+
|
|
543
|
+
useEffect(() => {
|
|
544
|
+
setFilmstripFrames([]);
|
|
545
|
+
}, [recordingId]);
|
|
546
|
+
|
|
547
|
+
// Cells should read as video frames, so aim for one per `height * aspect` of
|
|
548
|
+
// track. Bucketed so ordinary window resizing does not re-extract, and based
|
|
549
|
+
// on the unzoomed width — a zoomed fallback strip stretches, which is one of
|
|
550
|
+
// the reasons the server sprite is the preferred path.
|
|
551
|
+
const filmstripFrameCount = useMemo(() => {
|
|
552
|
+
const bucketedWidth = Math.max(240, Math.round(viewportWidth / 120) * 120);
|
|
553
|
+
const cellWidth = WAVEFORM_HEIGHT * (16 / 9);
|
|
554
|
+
return Math.min(48, Math.max(6, Math.round(bucketedWidth / cellWidth)));
|
|
555
|
+
}, [viewportWidth]);
|
|
556
|
+
|
|
557
|
+
useEffect(() => {
|
|
558
|
+
// A sprite already covers the whole clip — don't decode the video again.
|
|
559
|
+
if (filmstripSprite) {
|
|
560
|
+
setFilmstripFrames([]);
|
|
561
|
+
return;
|
|
562
|
+
}
|
|
563
|
+
if (!waveformMediaUrl || durationMs <= 0) {
|
|
564
|
+
setFilmstripFrames([]);
|
|
565
|
+
return;
|
|
566
|
+
}
|
|
567
|
+
setFilmstripFrames([]);
|
|
568
|
+
let cancelled = false;
|
|
569
|
+
// `waveformMediaUrl`, not `videoUrl`: reading frames back out of a
|
|
570
|
+
// cross-origin video taints the canvas, so provider media must come
|
|
571
|
+
// through the same-origin proxy first.
|
|
572
|
+
extractFilmstripThumbnails({
|
|
573
|
+
videoUrl: waveformMediaUrl,
|
|
574
|
+
durationMs,
|
|
575
|
+
frameCount: filmstripFrameCount,
|
|
576
|
+
})
|
|
577
|
+
.then((result) => {
|
|
578
|
+
if (cancelled) return;
|
|
579
|
+
if (result.status !== "ok") {
|
|
580
|
+
console.warn("[editor] filmstrip extraction failed", {
|
|
581
|
+
recordingId,
|
|
582
|
+
status: result.status,
|
|
583
|
+
detail: result.detail,
|
|
584
|
+
});
|
|
585
|
+
}
|
|
586
|
+
setFilmstripFrames(result.frames);
|
|
587
|
+
})
|
|
588
|
+
.catch((err) => {
|
|
589
|
+
if (cancelled) return;
|
|
590
|
+
console.warn("[editor] filmstrip extraction threw", {
|
|
591
|
+
recordingId,
|
|
592
|
+
error: err instanceof Error ? err.message : String(err),
|
|
593
|
+
});
|
|
594
|
+
});
|
|
595
|
+
return () => {
|
|
596
|
+
cancelled = true;
|
|
597
|
+
};
|
|
598
|
+
}, [
|
|
599
|
+
recordingId,
|
|
600
|
+
waveformMediaUrl,
|
|
601
|
+
durationMs,
|
|
602
|
+
filmstripFrameCount,
|
|
603
|
+
filmstripSprite,
|
|
604
|
+
]);
|
|
605
|
+
|
|
501
606
|
// --- actions ------------------------------------------------------------
|
|
502
607
|
const trim = useActionMutation("trim-recording");
|
|
503
608
|
const split = useActionMutation("split-recording");
|
|
@@ -550,12 +655,22 @@ export function EditorLayout({ recordingId, className }: EditorLayoutProps) {
|
|
|
550
655
|
) {
|
|
551
656
|
e.preventDefault();
|
|
552
657
|
undo.mutate({ recordingId });
|
|
553
|
-
} else if (
|
|
658
|
+
} else if (
|
|
659
|
+
!e.ctrlKey &&
|
|
660
|
+
!e.metaKey &&
|
|
661
|
+
!e.altKey &&
|
|
662
|
+
e.key.toLowerCase() === "i"
|
|
663
|
+
) {
|
|
554
664
|
setSelectionRange((r) => ({
|
|
555
665
|
startMs: playheadMs,
|
|
556
666
|
endMs: r?.endMs && r.endMs > playheadMs ? r.endMs : playheadMs + 1000,
|
|
557
667
|
}));
|
|
558
|
-
} else if (
|
|
668
|
+
} else if (
|
|
669
|
+
!e.ctrlKey &&
|
|
670
|
+
!e.metaKey &&
|
|
671
|
+
!e.altKey &&
|
|
672
|
+
e.key.toLowerCase() === "o"
|
|
673
|
+
) {
|
|
559
674
|
setSelectionRange((r) => ({
|
|
560
675
|
startMs:
|
|
561
676
|
r?.startMs && r.startMs < playheadMs
|
|
@@ -563,7 +678,12 @@ export function EditorLayout({ recordingId, className }: EditorLayoutProps) {
|
|
|
563
678
|
: Math.max(0, playheadMs - 1000),
|
|
564
679
|
endMs: playheadMs,
|
|
565
680
|
}));
|
|
566
|
-
} else if (
|
|
681
|
+
} else if (
|
|
682
|
+
!e.ctrlKey &&
|
|
683
|
+
!e.metaKey &&
|
|
684
|
+
!e.altKey &&
|
|
685
|
+
e.key.toLowerCase() === "x"
|
|
686
|
+
) {
|
|
567
687
|
// Cut: trim the current selection range
|
|
568
688
|
const range = selectionRange;
|
|
569
689
|
if (range) {
|
|
@@ -582,7 +702,12 @@ export function EditorLayout({ recordingId, className }: EditorLayoutProps) {
|
|
|
582
702
|
toast.error(err?.message ?? t("editorLayout.cutFailed")),
|
|
583
703
|
);
|
|
584
704
|
}
|
|
585
|
-
} else if (
|
|
705
|
+
} else if (
|
|
706
|
+
!e.ctrlKey &&
|
|
707
|
+
!e.metaKey &&
|
|
708
|
+
!e.altKey &&
|
|
709
|
+
e.key.toLowerCase() === "s"
|
|
710
|
+
) {
|
|
586
711
|
// Split at playhead
|
|
587
712
|
e.preventDefault();
|
|
588
713
|
split
|
|
@@ -693,6 +818,8 @@ export function EditorLayout({ recordingId, className }: EditorLayoutProps) {
|
|
|
693
818
|
<div className="relative min-w-0 overflow-hidden">
|
|
694
819
|
<Waveform
|
|
695
820
|
peaks={peaks}
|
|
821
|
+
sprite={filmstripSprite}
|
|
822
|
+
frames={filmstripFrames}
|
|
696
823
|
width={viewportWidth}
|
|
697
824
|
height={WAVEFORM_HEIGHT}
|
|
698
825
|
zoom={zoom}
|
|
@@ -717,14 +844,16 @@ export function EditorLayout({ recordingId, className }: EditorLayoutProps) {
|
|
|
717
844
|
transform: `translateX(${-scrollLeft}px)`,
|
|
718
845
|
}}
|
|
719
846
|
>
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
847
|
+
{durationMs > 0 && (
|
|
848
|
+
<TrimHandles
|
|
849
|
+
width={totalWidth}
|
|
850
|
+
height={WAVEFORM_HEIGHT}
|
|
851
|
+
value={effectiveSelection}
|
|
852
|
+
onChange={setSelectionRange}
|
|
853
|
+
durationMs={durationMs}
|
|
854
|
+
splitPoints={splitPoints}
|
|
855
|
+
/>
|
|
856
|
+
)}
|
|
728
857
|
</div>
|
|
729
858
|
</div>
|
|
730
859
|
</div>
|