@agent-native/core 0.134.0 → 0.135.0
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/docs/content/template-content-developers.mdx +1 -1
- 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/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-29-meeting-notes-no-longer-transcribe-the-other-side-twice-when.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/hooks/useMeetingTranscription.ts +37 -48
- package/corpus/templates/clips/desktop/src/lib/recorder.ts +331 -36
- package/corpus/templates/clips/desktop/src/lib/transcription-capture.ts +7 -5
- package/corpus/templates/clips/desktop/src/lib/transcription-engine.ts +182 -106
- package/corpus/templates/clips/desktop/src/lib/upload-recovery.ts +129 -0
- package/corpus/templates/clips/desktop/src/overlays/live-transcript.tsx +43 -14
- package/corpus/templates/clips/desktop/src/overlays/recording-pill.tsx +3 -3
- package/corpus/templates/clips/desktop/src-tauri/src/echo_guard.rs +443 -0
- package/corpus/templates/clips/desktop/src-tauri/src/lib.rs +1 -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/src-tauri/src/whisper_speech.rs +125 -16
- 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/.agents/skills/document-editing/references/databases.md +6 -4
- package/corpus/templates/content/actions/_database-membership-lock.ts +25 -0
- package/corpus/templates/content/actions/_database-row-batch.ts +6 -2
- package/corpus/templates/content/actions/_database-source-utils.ts +289 -129
- package/corpus/templates/content/actions/_database-utils.ts +12 -3
- package/corpus/templates/content/actions/add-comment.ts +15 -1
- package/corpus/templates/content/actions/add-content-database-source-field-property.ts +39 -21
- package/corpus/templates/content/actions/attach-content-database-source.ts +4 -1
- package/corpus/templates/content/actions/change-content-database-source-role.ts +3 -5
- package/corpus/templates/content/actions/duplicate-document-property.ts +59 -46
- package/corpus/templates/content/actions/execute-builder-source-execution.ts +77 -71
- package/corpus/templates/content/actions/list-documents.ts +1 -0
- package/corpus/templates/content/actions/materialize-builder-required-fields.ts +7 -0
- package/corpus/templates/content/actions/remove-database-items.ts +183 -0
- package/corpus/templates/content/actions/set-document-property.ts +57 -39
- package/corpus/templates/content/actions/sync-local-folder-source.ts +7 -0
- package/corpus/templates/content/app/components/editor/DocumentEditor.tsx +5 -2
- package/corpus/templates/content/app/components/editor/DocumentToolbar.tsx +50 -1
- package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +471 -329
- package/corpus/templates/content/app/components/editor/database/row-access.ts +45 -0
- package/corpus/templates/content/app/components/editor/database/shared.tsx +141 -38
- package/corpus/templates/content/app/components/ui/switch.tsx +1 -0
- package/corpus/templates/content/app/hooks/use-content-database.ts +2 -2
- package/corpus/templates/content/app/hooks/use-content-prefs.ts +55 -0
- package/corpus/templates/content/app/i18n-data.ts +170 -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-07-30-database-rows-can-now-be-removed-without-deleting-their-page.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/parity/eval-scenarios.ts +2 -2
- package/corpus/templates/content/parity/matrix.md +1 -1
- package/corpus/templates/content/parity/matrix.ts +4 -5
- package/corpus/templates/content/server/lib/comment-notifications.ts +146 -0
- 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 +4 -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/.generated/bridge/editor-chrome.generated.ts +26 -11
- package/corpus/templates/design/actions/take-design-screenshot.ts +7 -0
- package/corpus/templates/design/app/components/design/DesignCanvas.tsx +11 -3
- package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +4 -1
- package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +54 -17
- package/corpus/templates/design/app/components/design/design-canvas/local-runtime.ts +126 -0
- package/corpus/templates/design/app/components/templates/TemplatePreview.tsx +2 -0
- package/corpus/templates/design/app/pages/Index.tsx +3 -1
- package/corpus/templates/design/app/pages/Present.tsx +2 -1
- package/corpus/templates/design/app/routes/settings.tsx +14 -22
- package/corpus/templates/design/changelog/2026-07-30-a-broken-inline-script-in-a-generated-screen-is-now-caught-o.md +6 -0
- package/corpus/templates/design/changelog/2026-07-30-design-html-is-now-validated-with-a-spec-grade-html-parser-i.md +6 -0
- package/corpus/templates/design/changelog/2026-07-30-designs-now-load-their-tailwind-and-alpine-runtimes-from-the.md +6 -0
- package/corpus/templates/design/changelog/2026-07-30-designs-with-a-broken-alpine-expression-are-now-caught-on-sa.md +6 -0
- package/corpus/templates/design/changelog/2026-07-30-editing-a-design-no-longer-strips-its-styling-the-canvas-kep.md +6 -0
- 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/package.json +4 -0
- package/corpus/templates/design/shared/html-integrity.ts +802 -556
- 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/collab/struct-routes.d.ts +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 +2 -2
- package/dist/observability/routes.d.ts +1 -1
- 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/provider-api/actions/custom-provider-registration.d.ts +10 -10
- package/dist/provider-api/actions/provider-api.d.ts +8 -8
- package/dist/provider-api/corpus-jobs.d.ts +2 -2
- package/dist/resources/handlers.d.ts +1 -1
- 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/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/docs/content/template-content-developers.mdx +1 -1
- 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/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
- package/corpus/templates/content/actions/delete-database-items.ts +0 -89
|
@@ -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 {
|
|
@@ -146,6 +146,7 @@ mod macos {
|
|
|
146
146
|
use crate::capture_audio_bus::{
|
|
147
147
|
try_subscribe, AudioSources, AudioSubscription, SubscriptionAttempt,
|
|
148
148
|
};
|
|
149
|
+
use crate::echo_guard::EchoGuard;
|
|
149
150
|
use crate::native_speech::macos::{
|
|
150
151
|
start_raw_mic_capture, MicVoiceProcessingMode, RawMicCapture,
|
|
151
152
|
};
|
|
@@ -441,6 +442,11 @@ mod macos {
|
|
|
441
442
|
/// Recording capture only persists finals and disables this expensive
|
|
442
443
|
/// repeated inference; meeting capture keeps it enabled.
|
|
443
444
|
emit_partials: bool,
|
|
445
|
+
/// Shared speaker-bleed reference, present only while both streams are
|
|
446
|
+
/// capturing. The system stream records what the speakers played into
|
|
447
|
+
/// it; the mic stream reads it back to drop its own echo of that
|
|
448
|
+
/// playback before inference. See `echo_guard`.
|
|
449
|
+
echo_guard: Option<Arc<EchoGuard>>,
|
|
444
450
|
}
|
|
445
451
|
|
|
446
452
|
impl WhisperStream {
|
|
@@ -452,6 +458,7 @@ mod macos {
|
|
|
452
458
|
ctx: Arc<WhisperContext>,
|
|
453
459
|
stream_start: Instant,
|
|
454
460
|
emit_partials: bool,
|
|
461
|
+
echo_guard: Option<Arc<EchoGuard>>,
|
|
455
462
|
) -> Arc<Self> {
|
|
456
463
|
let done = Arc::new(AtomicBool::new(false));
|
|
457
464
|
let stream = Arc::new(WhisperStream {
|
|
@@ -468,6 +475,7 @@ mod macos {
|
|
|
468
475
|
}),
|
|
469
476
|
reset_generation: AtomicU32::new(0),
|
|
470
477
|
emit_partials,
|
|
478
|
+
echo_guard,
|
|
471
479
|
});
|
|
472
480
|
let worker_stream = stream.clone();
|
|
473
481
|
std::thread::spawn(move || {
|
|
@@ -492,6 +500,11 @@ mod macos {
|
|
|
492
500
|
if !self.running.load(Ordering::SeqCst) {
|
|
493
501
|
return;
|
|
494
502
|
}
|
|
503
|
+
if self.source == "system" {
|
|
504
|
+
if let Some(guard) = &self.echo_guard {
|
|
505
|
+
guard.note_playback(frames, self.src_rate.load(Ordering::SeqCst) as f64);
|
|
506
|
+
}
|
|
507
|
+
}
|
|
495
508
|
if let Ok(mut buf) = self.buf.lock() {
|
|
496
509
|
buf.extend_from_slice(frames);
|
|
497
510
|
}
|
|
@@ -514,15 +527,48 @@ mod macos {
|
|
|
514
527
|
.unwrap_or(0)
|
|
515
528
|
}
|
|
516
529
|
|
|
517
|
-
/// Mark the start of a fresh buffer (called when the buffer is
|
|
530
|
+
/// Mark the start of a fresh buffer (called when the buffer is drained
|
|
518
531
|
/// on finalize) so the next utterance's whisper timestamps offset
|
|
519
|
-
/// correctly onto the meeting timeline.
|
|
520
|
-
|
|
532
|
+
/// correctly onto the meeting timeline. `pending` is how much audio
|
|
533
|
+
/// survived the drain, which is how far before now the buffer begins.
|
|
534
|
+
fn reset_buffer_start(&self, pending: Duration) {
|
|
535
|
+
let now = Instant::now();
|
|
521
536
|
if let Ok(mut timeline) = self.timeline.lock() {
|
|
522
|
-
timeline.buffer_start =
|
|
537
|
+
timeline.buffer_start = buffer_start_after_drain(now, pending);
|
|
523
538
|
}
|
|
524
539
|
}
|
|
525
540
|
|
|
541
|
+
/// Wall-clock start of the audio currently sitting in `buf`, used to
|
|
542
|
+
/// line that audio up against the playback reference.
|
|
543
|
+
fn buffer_start(&self) -> Instant {
|
|
544
|
+
self.timeline
|
|
545
|
+
.lock()
|
|
546
|
+
.map(|timeline| timeline.buffer_start)
|
|
547
|
+
.unwrap_or_else(|_| Instant::now())
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
/// Whether `samples` (16 kHz mono) is the speakers bleeding into the
|
|
551
|
+
/// microphone rather than someone talking. Only the mic can be
|
|
552
|
+
/// contaminated: a call app never plays the local user back.
|
|
553
|
+
fn is_playback_echo(&self, samples: &[f32]) -> bool {
|
|
554
|
+
if self.source != "mic" {
|
|
555
|
+
return false;
|
|
556
|
+
}
|
|
557
|
+
self.echo_guard
|
|
558
|
+
.as_ref()
|
|
559
|
+
.is_some_and(|guard| guard.is_playback_echo(samples, self.buffer_start()))
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
/// Clear whichever live partial this stream last rendered. Suppressed
|
|
563
|
+
/// echo emits no final, so without this the overlay would keep showing
|
|
564
|
+
/// the partial that led up to it.
|
|
565
|
+
fn clear_partial(&self) {
|
|
566
|
+
let _ = self.app.emit(
|
|
567
|
+
"voice:partial-transcript",
|
|
568
|
+
serde_json::json!({ "text": "", "source": self.source }),
|
|
569
|
+
);
|
|
570
|
+
}
|
|
571
|
+
|
|
526
572
|
/// Rebase timestamps to "now" and discard any audio captured while the
|
|
527
573
|
/// recorder was warming up/counting down.
|
|
528
574
|
fn reset_timeline(&self) {
|
|
@@ -682,6 +728,10 @@ mod macos {
|
|
|
682
728
|
sum / count as f32
|
|
683
729
|
}
|
|
684
730
|
|
|
731
|
+
fn buffer_start_after_drain(now: Instant, pending: Duration) -> Instant {
|
|
732
|
+
now.checked_sub(pending).unwrap_or(now)
|
|
733
|
+
}
|
|
734
|
+
|
|
685
735
|
fn partial_inference_due(
|
|
686
736
|
emit_partials: bool,
|
|
687
737
|
had_voice: bool,
|
|
@@ -694,6 +744,18 @@ mod macos {
|
|
|
694
744
|
&& since_last_infer > Duration::from_millis(1200)
|
|
695
745
|
}
|
|
696
746
|
|
|
747
|
+
fn partial_inference_timestamp(
|
|
748
|
+
previous: Instant,
|
|
749
|
+
inference_ran: bool,
|
|
750
|
+
now: Instant,
|
|
751
|
+
) -> Instant {
|
|
752
|
+
if inference_ran {
|
|
753
|
+
now
|
|
754
|
+
} else {
|
|
755
|
+
previous
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
|
|
697
759
|
fn utterance_finalize_due(have_secs: f32, silence: Duration) -> bool {
|
|
698
760
|
(have_secs > 0.4 && silence > Duration::from_millis(800)) || have_secs > 25.0
|
|
699
761
|
}
|
|
@@ -790,20 +852,32 @@ mod macos {
|
|
|
790
852
|
if stream.reset_generation.load(Ordering::SeqCst) != seen_reset_generation {
|
|
791
853
|
continue;
|
|
792
854
|
}
|
|
793
|
-
|
|
794
|
-
|
|
855
|
+
if stream.is_playback_echo(resample_state.samples()) {
|
|
856
|
+
// A dropped utterance is indistinguishable from silence
|
|
857
|
+
// in the transcript, so say so here: this log is the
|
|
858
|
+
// only way a false positive is diagnosable afterwards.
|
|
859
|
+
eprintln!("[whisper-mic] suppressed {have_secs:.1}s of speaker bleed");
|
|
860
|
+
stream.clear_partial();
|
|
861
|
+
} else {
|
|
862
|
+
let segs = infer(&mut state, resample_state.samples(), lang);
|
|
863
|
+
stream.emit_transcript("voice:final-transcript", &segs, stream.offset_ms());
|
|
864
|
+
}
|
|
795
865
|
}
|
|
866
|
+
let mut pending = 0usize;
|
|
796
867
|
if let Ok(mut b) = stream.buf.lock() {
|
|
797
868
|
let to_drain = n_processed.min(b.len());
|
|
798
869
|
b.drain(..to_drain);
|
|
870
|
+
pending = b.len();
|
|
799
871
|
}
|
|
800
872
|
// Raw indices shift after the drain above (front-truncated),
|
|
801
873
|
// so the resample cache is invalid regardless of whether this
|
|
802
874
|
// utterance ran inference — rebuild fresh from whatever's left.
|
|
803
875
|
resample_state.drop_all();
|
|
804
|
-
//
|
|
805
|
-
//
|
|
806
|
-
|
|
876
|
+
// Advance the timeline offset so the next utterance's whisper
|
|
877
|
+
// timestamps map correctly. Inference can take seconds, and
|
|
878
|
+
// audio kept arriving throughout it, so the new buffer starts
|
|
879
|
+
// as far back as the audio it already holds — not at "now".
|
|
880
|
+
stream.reset_buffer_start(Duration::from_secs_f32(pending as f32 / src_rate));
|
|
807
881
|
last_raw_len = 0;
|
|
808
882
|
had_voice = false;
|
|
809
883
|
last_infer = Instant::now();
|
|
@@ -825,9 +899,15 @@ mod macos {
|
|
|
825
899
|
if stream.reset_generation.load(Ordering::SeqCst) != seen_reset_generation {
|
|
826
900
|
continue;
|
|
827
901
|
}
|
|
828
|
-
let
|
|
829
|
-
|
|
830
|
-
|
|
902
|
+
let inference_ran = if stream.is_playback_echo(resample_state.samples()) {
|
|
903
|
+
stream.clear_partial();
|
|
904
|
+
false
|
|
905
|
+
} else {
|
|
906
|
+
let segs = infer(&mut state, resample_state.samples(), lang);
|
|
907
|
+
stream.emit_transcript("voice:partial-transcript", &segs, stream.offset_ms());
|
|
908
|
+
true
|
|
909
|
+
};
|
|
910
|
+
last_infer = partial_inference_timestamp(last_infer, inference_ran, Instant::now());
|
|
831
911
|
}
|
|
832
912
|
}
|
|
833
913
|
|
|
@@ -835,7 +915,10 @@ mod macos {
|
|
|
835
915
|
let raw = stream.buf.lock().map(|b| b.clone()).unwrap_or_default();
|
|
836
916
|
let src_rate = stream.src_rate.load(Ordering::SeqCst) as f64;
|
|
837
917
|
let samples = resample_to_16k(&raw, src_rate);
|
|
838
|
-
if had_voice
|
|
918
|
+
if had_voice
|
|
919
|
+
&& samples.len() as f32 / SAMPLE_RATE_16K > 0.3
|
|
920
|
+
&& !stream.is_playback_echo(&samples)
|
|
921
|
+
{
|
|
839
922
|
let segs = infer(&mut state, &samples, lang);
|
|
840
923
|
stream.emit_transcript("voice:final-transcript", &segs, stream.offset_ms());
|
|
841
924
|
}
|
|
@@ -1028,6 +1111,9 @@ mod macos {
|
|
|
1028
1111
|
// competing VoiceProcessingIO mic input. Older macOS versions (and a
|
|
1029
1112
|
// failed SCK start) keep the existing split-capture fallback.
|
|
1030
1113
|
let session_start = Instant::now();
|
|
1114
|
+
// Only a session that captures both streams can tell speaker bleed
|
|
1115
|
+
// from speech, so a mic-only session leaves the guard unarmed.
|
|
1116
|
+
let echo_guard = capture_system.then(|| Arc::new(EchoGuard::new()));
|
|
1031
1117
|
let mic_stream = WhisperStream::new(
|
|
1032
1118
|
app.clone(),
|
|
1033
1119
|
"mic",
|
|
@@ -1036,6 +1122,7 @@ mod macos {
|
|
|
1036
1122
|
ctx.clone(),
|
|
1037
1123
|
session_start,
|
|
1038
1124
|
emit_partials,
|
|
1125
|
+
echo_guard.clone(),
|
|
1039
1126
|
);
|
|
1040
1127
|
let sys_stream = capture_system.then(|| {
|
|
1041
1128
|
WhisperStream::new(
|
|
@@ -1046,6 +1133,7 @@ mod macos {
|
|
|
1046
1133
|
ctx.clone(),
|
|
1047
1134
|
session_start,
|
|
1048
1135
|
emit_partials,
|
|
1136
|
+
echo_guard.clone(),
|
|
1049
1137
|
)
|
|
1050
1138
|
});
|
|
1051
1139
|
let mic_for_cb = mic_stream.clone();
|
|
@@ -1311,11 +1399,12 @@ mod macos {
|
|
|
1311
1399
|
|
|
1312
1400
|
#[cfg(test)]
|
|
1313
1401
|
mod tests {
|
|
1314
|
-
use std::time::Duration;
|
|
1402
|
+
use std::time::{Duration, Instant};
|
|
1315
1403
|
|
|
1316
1404
|
use super::{
|
|
1317
|
-
|
|
1318
|
-
|
|
1405
|
+
buffer_start_after_drain, partial_inference_due, partial_inference_timestamp,
|
|
1406
|
+
resample_to_16k, should_use_combined_sck_capture, split_mic_capture_options,
|
|
1407
|
+
utterance_finalize_due, IncrementalResample, SessionOwner,
|
|
1319
1408
|
};
|
|
1320
1409
|
use crate::native_speech::macos::MicVoiceProcessingMode;
|
|
1321
1410
|
|
|
@@ -1364,6 +1453,17 @@ mod macos {
|
|
|
1364
1453
|
);
|
|
1365
1454
|
}
|
|
1366
1455
|
|
|
1456
|
+
#[test]
|
|
1457
|
+
fn buffer_start_accounts_for_audio_captured_during_inference() {
|
|
1458
|
+
let now = Instant::now();
|
|
1459
|
+
let pending = Duration::from_millis(750);
|
|
1460
|
+
|
|
1461
|
+
assert_eq!(
|
|
1462
|
+
buffer_start_after_drain(now, pending),
|
|
1463
|
+
now.checked_sub(pending).unwrap()
|
|
1464
|
+
);
|
|
1465
|
+
}
|
|
1466
|
+
|
|
1367
1467
|
#[test]
|
|
1368
1468
|
fn recording_mode_never_runs_live_partial_inference() {
|
|
1369
1469
|
assert!(!partial_inference_due(
|
|
@@ -1390,6 +1490,15 @@ mod macos {
|
|
|
1390
1490
|
));
|
|
1391
1491
|
}
|
|
1392
1492
|
|
|
1493
|
+
#[test]
|
|
1494
|
+
fn echo_suppressed_partial_does_not_reset_retry_cadence() {
|
|
1495
|
+
let previous = Instant::now();
|
|
1496
|
+
let now = previous + Duration::from_secs(2);
|
|
1497
|
+
|
|
1498
|
+
assert_eq!(partial_inference_timestamp(previous, false, now), previous);
|
|
1499
|
+
assert_eq!(partial_inference_timestamp(previous, true, now), now);
|
|
1500
|
+
}
|
|
1501
|
+
|
|
1393
1502
|
#[test]
|
|
1394
1503
|
fn recording_mode_keeps_silence_and_long_utterance_finalization() {
|
|
1395
1504
|
assert!(utterance_finalize_due(1.0, Duration::from_millis(801)));
|
|
@@ -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
|
}
|