@agent-native/core 0.91.1 → 0.92.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/blueprints/action/crud.md +1 -1
- package/blueprints/channel/discord.md +1 -1
- package/blueprints/provider/stripe.md +1 -1
- package/blueprints/sandbox/docker.md +1 -1
- package/corpus/README.md +2 -2
- package/corpus/core/CHANGELOG.md +31 -0
- package/corpus/core/blueprints/action/crud.md +1 -1
- package/corpus/core/blueprints/channel/discord.md +1 -1
- package/corpus/core/blueprints/provider/stripe.md +1 -1
- package/corpus/core/blueprints/sandbox/docker.md +1 -1
- package/corpus/core/docs/content/faq.mdx +17 -0
- package/corpus/core/docs/content/notifications.mdx +12 -6
- package/corpus/core/package.json +6 -5
- package/corpus/core/src/agent/production-agent.ts +140 -46
- package/corpus/core/src/agent/run-manager.ts +23 -2
- package/corpus/core/src/agent/run-store.ts +2 -2
- package/corpus/core/src/agent/tool-search.ts +71 -4
- package/corpus/core/src/cli/add.ts +1 -1
- package/corpus/core/src/cli/create.ts +8 -3
- package/corpus/core/src/cli/index.ts +25 -4
- package/corpus/core/src/cli/upgrade.ts +798 -0
- package/corpus/core/src/client/AssistantChat.tsx +1 -1
- package/corpus/core/src/client/HighlightedCodeBlock.tsx +20 -3
- package/corpus/core/src/client/agent-chat-adapter.ts +31 -6
- package/corpus/core/src/client/chat/message-components.tsx +85 -8
- package/corpus/core/src/client/chat/tool-call-display.tsx +355 -144
- package/corpus/core/src/client/code-agent-chat-adapter.ts +2 -9
- package/corpus/core/src/client/extensions/ExtensionSlot.tsx +7 -0
- package/corpus/core/src/client/settings/SettingsPanel.tsx +533 -389
- package/corpus/core/src/client/settings/SettingsSection.tsx +154 -17
- package/corpus/core/src/client/sse-event-processor.ts +29 -2
- package/corpus/core/src/extensions/actions.ts +113 -5
- package/corpus/core/src/jobs/scheduler.ts +30 -1
- package/corpus/core/src/localization/default-messages.ts +21 -0
- package/corpus/core/src/notifications/channels.ts +88 -25
- package/corpus/core/src/notifications/registry.ts +17 -3
- package/corpus/core/src/notifications/types.ts +4 -1
- package/corpus/core/src/server/agent-chat-plugin.ts +7 -3
- package/corpus/core/src/server/request-context.ts +7 -0
- package/corpus/core/src/styles/agent-conversation.css +1 -1
- package/corpus/core/src/templates/default/.agents/skills/self-modifying-code/SKILL.md +20 -2
- package/corpus/core/src/templates/default/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
- package/corpus/core/src/templates/default/AGENTS.md +10 -3
- package/corpus/core/src/templates/default/app/i18n/ar-SA.ts +21 -0
- package/corpus/core/src/templates/default/app/i18n/de-DE.ts +21 -0
- package/corpus/core/src/templates/default/app/i18n/en-US.ts +21 -0
- package/corpus/core/src/templates/default/app/i18n/es-ES.ts +21 -0
- package/corpus/core/src/templates/default/app/i18n/fr-FR.ts +21 -0
- package/corpus/core/src/templates/default/app/i18n/hi-IN.ts +20 -0
- package/corpus/core/src/templates/default/app/i18n/ja-JP.ts +21 -0
- package/corpus/core/src/templates/default/app/i18n/ko-KR.ts +20 -0
- package/corpus/core/src/templates/default/app/i18n/pt-BR.ts +21 -0
- package/corpus/core/src/templates/default/app/i18n/zh-CN.ts +20 -0
- package/corpus/core/src/templates/default/app/i18n/zh-TW.ts +19 -0
- package/corpus/core/src/templates/default/package.json +4 -3
- package/corpus/core/src/templates/headless/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
- package/corpus/core/src/templates/headless/AGENTS.md +9 -3
- package/corpus/core/src/templates/headless/package.json +4 -3
- package/corpus/core/src/templates/workspace-core/.agents/skills/self-modifying-code/SKILL.md +20 -2
- package/corpus/core/src/templates/workspace-core/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
- package/corpus/core/src/templates/workspace-core/package.json +6 -6
- package/corpus/core/src/templates/workspace-root/AGENTS.md +10 -4
- package/corpus/core/src/templates/workspace-root/package.json +3 -2
- package/corpus/core/src/triggers/dispatcher.ts +30 -2
- package/corpus/core/src/vite/client.ts +96 -1
- package/corpus/templates/analytics/.agents/skills/self-modifying-code/SKILL.md +20 -2
- package/corpus/templates/analytics/.agents/skills/session-replay/SKILL.md +20 -2
- package/corpus/templates/analytics/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
- package/corpus/templates/analytics/AGENTS.md +7 -5
- package/corpus/templates/analytics/actions/save-monitor.ts +14 -0
- package/corpus/templates/analytics/app/components/dashboard/DataTable.tsx +41 -6
- package/corpus/templates/analytics/app/components/dashboard/SqlChart.tsx +122 -11
- package/corpus/templates/analytics/app/global.css +26 -10
- package/corpus/templates/analytics/app/i18n/zh-TW.ts +2 -0
- package/corpus/templates/analytics/app/i18n-data.ts +25 -0
- package/corpus/templates/analytics/app/pages/monitoring/MonitoringPage.tsx +8 -2
- package/corpus/templates/analytics/app/pages/monitoring/uptime/MonitorFormPage.tsx +151 -32
- package/corpus/templates/analytics/app/pages/monitoring/uptime/i18n.ts +17 -2
- package/corpus/templates/analytics/app/pages/monitoring/uptime/types.ts +4 -0
- package/corpus/templates/analytics/app/pages/sessions/SessionDetailPage.tsx +461 -286
- package/corpus/templates/analytics/app/pages/sessions/SessionDevToolsPanel.tsx +285 -294
- package/corpus/templates/analytics/changelog/2026-07-08-console-details-expand-inline-under-each-row-and-the-event-t.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-08-dashboard-tables-keep-their-size-while-loading.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-08-monitoring-uptime-and-errors-tabs-now-clearly-show-which-view-is-active.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-08-session-replay-keeps-a-usable-stage-when-dev-tools-is-open-a.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-08-session-replay-resumes-after-scrubbing-and-expands-console-r.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-08-session-replay-uses-stock-rrweb-sizing-again-so-recorded.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-08-session-replays-wait-for-full-load-keep-a-normal-viewport-an.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-08-sidebar-dashboard-menus-now-open-above-the-dashboard-canvas.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-08-uptime-alerting-clarifies-in-app-inbox-vs-email-and-lets-you-paste-slack-or-webhook-urls-per-monitor.md +6 -0
- package/corpus/templates/analytics/docs/uptime-monitoring.md +13 -11
- package/corpus/templates/analytics/package.json +2 -2
- package/corpus/templates/analytics/server/db/schema-monitoring.ts +4 -0
- package/corpus/templates/analytics/server/lib/uptime-monitors.ts +60 -12
- package/corpus/templates/analytics/server/plugins/db.ts +10 -0
- package/corpus/templates/assets/.agents/skills/self-modifying-code/SKILL.md +20 -2
- package/corpus/templates/assets/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
- package/corpus/templates/assets/package.json +2 -2
- package/corpus/templates/brain/.agents/skills/self-modifying-code/SKILL.md +20 -2
- package/corpus/templates/brain/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
- package/corpus/templates/brain/package.json +2 -2
- package/corpus/templates/calendar/.agents/skills/self-modifying-code/SKILL.md +20 -2
- package/corpus/templates/calendar/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
- package/corpus/templates/calendar/AGENTS.md +10 -0
- package/corpus/templates/calendar/actions/create-event.ts +5 -1
- package/corpus/templates/calendar/actions/event-action-helpers.ts +41 -0
- package/corpus/templates/calendar/actions/get-attendee-timezones.ts +28 -0
- package/corpus/templates/calendar/actions/set-attendee-timezone.ts +68 -0
- package/corpus/templates/calendar/app/components/calendar/EventAttendeesSection.tsx +150 -11
- package/corpus/templates/calendar/app/components/calendar/EventDetailPanel.tsx +36 -1
- package/corpus/templates/calendar/app/components/calendar/EventDetailPopover.tsx +32 -0
- package/corpus/templates/calendar/app/hooks/use-attendee-timezones.ts +24 -0
- package/corpus/templates/calendar/app/i18n/zh-TW.ts +2 -0
- package/corpus/templates/calendar/app/i18n-data.ts +20 -0
- package/corpus/templates/calendar/app/lib/attendee-local-time.ts +122 -0
- package/corpus/templates/calendar/changelog/2026-07-08-event-details-now-support-extension-widgets-and-you-can-see-.md +6 -0
- package/corpus/templates/calendar/changelog/2026-07-08-when-you-invite-guests-to-a-new-event-you-now-appear-in.md +6 -0
- package/corpus/templates/calendar/package.json +2 -2
- package/corpus/templates/calendar/server/handlers/events.ts +5 -1
- package/corpus/templates/calendar/shared/api.ts +6 -0
- package/corpus/templates/chat/.agents/skills/self-modifying-code/SKILL.md +20 -2
- package/corpus/templates/chat/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
- package/corpus/templates/chat/package.json +2 -2
- package/corpus/templates/clips/.agents/skills/meetings/SKILL.md +6 -2
- package/corpus/templates/clips/.agents/skills/self-modifying-code/SKILL.md +20 -2
- package/corpus/templates/clips/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
- package/corpus/templates/clips/actions/create-meeting.ts +8 -3
- package/corpus/templates/clips/actions/list-meetings.ts +31 -3
- package/corpus/templates/clips/changelog/2026-07-08-after-finishing-a-desktop-recording-reopening-clips-restor.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-08-clips-now-pops-up-to-take-notes-when-you-join-an-adhoc-zoom-.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-08-desktop-clip-recordings-are-louder-after-capture-mic-only.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-08-desktop-meeting-notes-stay-active-from-tray-and-shortcut-controls.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-08-desktop-meeting-popovers-can-start-notes-and-join-the-meetin.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-08-meeting-notes-keep-their-recording-state-when-the-desktop.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-08-meeting-reminder-dropdowns-now-expand-fully-instead-of-being.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-08-meeting-reminders-now-appear-1-minute-before-start-stay-unti.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-08-the-desktop-meetings-panel-has-room-for-its-drop-shadow-and-.md +6 -0
- package/corpus/templates/clips/chrome-extension/package.json +2 -2
- package/corpus/templates/clips/desktop/package.json +2 -2
- package/corpus/templates/clips/desktop/src/app.tsx +214 -37
- package/corpus/templates/clips/desktop/src/hooks/useMeetingTranscription.ts +30 -6
- package/corpus/templates/clips/desktop/src/lib/meeting-notification-timing.ts +83 -0
- package/corpus/templates/clips/desktop/src/lib/native-recording-warm.ts +21 -0
- package/corpus/templates/clips/desktop/src/lib/recorder.ts +98 -20
- package/corpus/templates/clips/desktop/src/overlays/meeting-notification.tsx +197 -50
- package/corpus/templates/clips/desktop/src/styles.css +150 -53
- package/corpus/templates/clips/desktop/src-tauri/src/adhoc_meetings_watcher.rs +362 -0
- package/corpus/templates/clips/desktop/src-tauri/src/clips/mod.rs +41 -7
- package/corpus/templates/clips/desktop/src-tauri/src/lib.rs +9 -2
- package/corpus/templates/clips/desktop/src-tauri/src/meetings_watcher.rs +89 -11
- package/corpus/templates/clips/desktop/src-tauri/src/native_screen.rs +71 -17
- package/corpus/templates/clips/desktop/src-tauri/src/native_speech.rs +36 -1
- package/corpus/templates/clips/desktop/src-tauri/src/notifications.rs +62 -10
- package/corpus/templates/clips/desktop/src-tauri/src/shortcuts.rs +3 -3
- package/corpus/templates/clips/desktop/src-tauri/src/silence_detector.rs +1 -25
- package/corpus/templates/clips/desktop/src-tauri/src/state.rs +5 -0
- package/corpus/templates/clips/desktop/src-tauri/src/tray.rs +18 -6
- package/corpus/templates/clips/desktop/src-tauri/src/util.rs +36 -1
- package/corpus/templates/clips/package.json +2 -2
- package/corpus/templates/content/.agents/skills/self-modifying-code/SKILL.md +20 -2
- package/corpus/templates/content/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
- package/corpus/templates/content/actions/_batch-utils.ts +8 -0
- package/corpus/templates/content/actions/_builder-cms-read-client.ts +7 -5
- package/corpus/templates/content/actions/_content-database-personal-view.ts +80 -0
- package/corpus/templates/content/actions/_database-source-utils.ts +776 -319
- package/corpus/templates/content/actions/_database-utils.ts +6 -1
- package/corpus/templates/content/actions/_property-utils.ts +168 -0
- package/corpus/templates/content/actions/add-content-database-source-field-property.ts +19 -12
- package/corpus/templates/content/actions/bind-content-database-source-field.ts +19 -12
- package/corpus/templates/content/actions/delete-document.ts +9 -13
- package/corpus/templates/content/actions/get-content-database-personal-view.ts +27 -0
- package/corpus/templates/content/actions/refresh-content-database-source.ts +5 -5
- package/corpus/templates/content/actions/update-content-database-personal-view.ts +31 -0
- package/corpus/templates/content/actions/update-content-database-view.ts +3 -0
- package/corpus/templates/content/app/components/editor/DocumentProperties.tsx +1 -1
- package/corpus/templates/content/app/components/editor/body-hydration.ts +35 -11
- package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +2631 -551
- package/corpus/templates/content/app/components/editor/database/filter-sort.ts +220 -18
- package/corpus/templates/content/app/hooks/use-content-database.ts +32 -0
- package/corpus/templates/content/app/hooks/use-documents.ts +8 -2
- package/corpus/templates/content/app/i18n/zh-TW.ts +16 -0
- package/corpus/templates/content/app/i18n-data.ts +154 -0
- package/corpus/templates/content/changelog/2026-07-08-builder-source-sync-now-completes-reliably-on-hosted-databases.md +6 -0
- package/corpus/templates/content/changelog/2026-07-08-database-filters-now-support-notion-style-reset-save-for-eve.md +6 -0
- package/corpus/templates/content/changelog/2026-07-08-date-filters-can-now-target-a-between-range.md +6 -0
- package/corpus/templates/content/changelog/2026-07-08-fixed-builder-database-sync-so-large-sources-continue-past-t.md +6 -0
- package/corpus/templates/content/package.json +2 -2
- package/corpus/templates/content/parity/matrix.md +1 -1
- package/corpus/templates/content/parity/matrix.ts +2 -0
- package/corpus/templates/content/server/plugins/db.ts +19 -0
- package/corpus/templates/content/shared/api.ts +24 -0
- package/corpus/templates/design/.agents/skills/self-modifying-code/SKILL.md +20 -2
- package/corpus/templates/design/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
- package/corpus/templates/design/package.json +2 -2
- package/corpus/templates/dispatch/.agents/skills/self-modifying-code/SKILL.md +20 -2
- package/corpus/templates/dispatch/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
- package/corpus/templates/dispatch/app/i18n/zh-TW.ts +34 -1
- package/corpus/templates/dispatch/app/i18n-data.ts +387 -10
- package/corpus/templates/dispatch/app/routes/automations.tsx +1 -0
- package/corpus/templates/dispatch/changelog/2026-07-08-added-an-automations-page-to-review-pause-and-create.md +6 -0
- package/corpus/templates/dispatch/changelog/2026-07-08-destinations-now-shows-outbound-delivery-queue-health.md +6 -0
- package/corpus/templates/dispatch/package.json +2 -2
- package/corpus/templates/forms/.agents/skills/self-modifying-code/SKILL.md +20 -2
- package/corpus/templates/forms/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
- package/corpus/templates/forms/package.json +2 -2
- package/corpus/templates/macros/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
- package/corpus/templates/macros/package.json +2 -2
- package/corpus/templates/mail/.agents/skills/self-modifying-code/SKILL.md +20 -2
- package/corpus/templates/mail/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
- package/corpus/templates/mail/app/hooks/use-emails.ts +56 -44
- package/corpus/templates/mail/app/lib/gmail-mutation-queue.ts +325 -0
- package/corpus/templates/mail/changelog/2026-07-08-rapid-archive-and-mark-read-now-batch-gmail-updates-so-the-i.md +6 -0
- package/corpus/templates/mail/package.json +2 -2
- package/corpus/templates/plan/.agents/skills/self-modifying-code/SKILL.md +20 -2
- package/corpus/templates/plan/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
- package/corpus/templates/plan/package.json +2 -2
- package/corpus/templates/slides/.agents/skills/self-modifying-code/SKILL.md +20 -2
- package/corpus/templates/slides/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
- package/corpus/templates/slides/app/components/editor/ImageDropPromptPopover.tsx +46 -29
- package/corpus/templates/slides/app/context/DeckContext.tsx +45 -18
- package/corpus/templates/slides/app/i18n/ar-SA.ts +4 -0
- package/corpus/templates/slides/app/i18n/de-DE.ts +4 -0
- package/corpus/templates/slides/app/i18n/en-US.ts +4 -0
- package/corpus/templates/slides/app/i18n/es-ES.ts +4 -0
- package/corpus/templates/slides/app/i18n/fr-FR.ts +4 -0
- package/corpus/templates/slides/app/i18n/hi-IN.ts +4 -0
- package/corpus/templates/slides/app/i18n/ja-JP.ts +4 -0
- package/corpus/templates/slides/app/i18n/ko-KR.ts +4 -0
- package/corpus/templates/slides/app/i18n/pt-BR.ts +4 -0
- package/corpus/templates/slides/app/i18n/zh-CN.ts +4 -0
- package/corpus/templates/slides/app/i18n/zh-TW.ts +4 -0
- package/corpus/templates/slides/app/lib/image-drop-to-agent.ts +115 -0
- package/corpus/templates/slides/app/pages/DeckEditor.tsx +22 -13
- package/corpus/templates/slides/changelog/2026-07-08-dropping-an-image-onto-a-slide-and-sending-it-to-the-agent-w.md +6 -0
- package/corpus/templates/slides/changelog/2026-07-08-undo-works-after-the-agent-edits-a-slide-so-you-can-reverse.md +6 -0
- package/corpus/templates/slides/package.json +2 -2
- package/corpus/templates/videos/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
- package/corpus/templates/voice/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
- package/dist/agent/production-agent.d.ts +5 -1
- package/dist/agent/production-agent.d.ts.map +1 -1
- package/dist/agent/production-agent.js +112 -42
- package/dist/agent/production-agent.js.map +1 -1
- package/dist/agent/run-manager.d.ts.map +1 -1
- package/dist/agent/run-manager.js +23 -2
- package/dist/agent/run-manager.js.map +1 -1
- package/dist/agent/run-store.js +2 -2
- package/dist/agent/run-store.js.map +1 -1
- package/dist/agent/tool-search.d.ts +2 -0
- package/dist/agent/tool-search.d.ts.map +1 -1
- package/dist/agent/tool-search.js +60 -4
- package/dist/agent/tool-search.js.map +1 -1
- package/dist/catalog.json +2 -2
- package/dist/cli/add.js +1 -1
- package/dist/cli/add.js.map +1 -1
- package/dist/cli/create.d.ts.map +1 -1
- package/dist/cli/create.js +8 -3
- package/dist/cli/create.js.map +1 -1
- package/dist/cli/index.js +24 -4
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/upgrade.d.ts +100 -0
- package/dist/cli/upgrade.d.ts.map +1 -0
- package/dist/cli/upgrade.js +678 -0
- package/dist/cli/upgrade.js.map +1 -0
- package/dist/client/AssistantChat.js +1 -1
- package/dist/client/AssistantChat.js.map +1 -1
- package/dist/client/HighlightedCodeBlock.d.ts +2 -0
- package/dist/client/HighlightedCodeBlock.d.ts.map +1 -1
- package/dist/client/HighlightedCodeBlock.js +10 -1
- package/dist/client/HighlightedCodeBlock.js.map +1 -1
- package/dist/client/agent-chat-adapter.d.ts.map +1 -1
- package/dist/client/agent-chat-adapter.js +32 -4
- package/dist/client/agent-chat-adapter.js.map +1 -1
- package/dist/client/chat/message-components.d.ts.map +1 -1
- package/dist/client/chat/message-components.js +61 -8
- package/dist/client/chat/message-components.js.map +1 -1
- package/dist/client/chat/tool-call-display.d.ts +12 -0
- package/dist/client/chat/tool-call-display.d.ts.map +1 -1
- package/dist/client/chat/tool-call-display.js +136 -42
- package/dist/client/chat/tool-call-display.js.map +1 -1
- package/dist/client/code-agent-chat-adapter.js +2 -9
- package/dist/client/code-agent-chat-adapter.js.map +1 -1
- package/dist/client/extensions/ExtensionSlot.js +6 -0
- package/dist/client/extensions/ExtensionSlot.js.map +1 -1
- package/dist/client/settings/SettingsPanel.d.ts.map +1 -1
- package/dist/client/settings/SettingsPanel.js +52 -19
- package/dist/client/settings/SettingsPanel.js.map +1 -1
- package/dist/client/settings/SettingsSection.d.ts +19 -4
- package/dist/client/settings/SettingsSection.d.ts.map +1 -1
- package/dist/client/settings/SettingsSection.js +33 -4
- package/dist/client/settings/SettingsSection.js.map +1 -1
- package/dist/client/sse-event-processor.d.ts +9 -0
- package/dist/client/sse-event-processor.d.ts.map +1 -1
- package/dist/client/sse-event-processor.js +20 -2
- package/dist/client/sse-event-processor.js.map +1 -1
- package/dist/collab/routes.d.ts +1 -1
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/extensions/actions.d.ts.map +1 -1
- package/dist/extensions/actions.js +85 -5
- package/dist/extensions/actions.js.map +1 -1
- package/dist/file-upload/actions/upload-image.d.ts +1 -1
- package/dist/jobs/scheduler.js +26 -1
- package/dist/jobs/scheduler.js.map +1 -1
- package/dist/localization/default-messages.d.ts +19 -0
- package/dist/localization/default-messages.d.ts.map +1 -1
- package/dist/localization/default-messages.js +19 -0
- package/dist/localization/default-messages.js.map +1 -1
- package/dist/notifications/channels.d.ts +8 -3
- package/dist/notifications/channels.d.ts.map +1 -1
- package/dist/notifications/channels.js +61 -22
- package/dist/notifications/channels.js.map +1 -1
- package/dist/notifications/registry.d.ts.map +1 -1
- package/dist/notifications/registry.js +14 -3
- package/dist/notifications/registry.js.map +1 -1
- package/dist/notifications/routes.d.ts +1 -1
- package/dist/notifications/types.d.ts +4 -1
- package/dist/notifications/types.d.ts.map +1 -1
- package/dist/notifications/types.js.map +1 -1
- package/dist/observability/routes.d.ts +1 -1
- package/dist/provider-api/corpus-jobs.d.ts +2 -2
- package/dist/resources/handlers.d.ts +3 -3
- package/dist/server/agent-chat-plugin.d.ts +5 -1
- package/dist/server/agent-chat-plugin.d.ts.map +1 -1
- package/dist/server/agent-chat-plugin.js +2 -2
- package/dist/server/agent-chat-plugin.js.map +1 -1
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/server/request-context.d.ts +7 -0
- package/dist/server/request-context.d.ts.map +1 -1
- package/dist/server/request-context.js.map +1 -1
- package/dist/styles/agent-conversation.css +1 -1
- package/dist/templates/default/.agents/skills/self-modifying-code/SKILL.md +20 -2
- package/dist/templates/default/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
- package/dist/templates/default/AGENTS.md +10 -3
- package/dist/templates/default/app/i18n/ar-SA.ts +21 -0
- package/dist/templates/default/app/i18n/de-DE.ts +21 -0
- package/dist/templates/default/app/i18n/en-US.ts +21 -0
- package/dist/templates/default/app/i18n/es-ES.ts +21 -0
- package/dist/templates/default/app/i18n/fr-FR.ts +21 -0
- package/dist/templates/default/app/i18n/hi-IN.ts +20 -0
- package/dist/templates/default/app/i18n/ja-JP.ts +21 -0
- package/dist/templates/default/app/i18n/ko-KR.ts +20 -0
- package/dist/templates/default/app/i18n/pt-BR.ts +21 -0
- package/dist/templates/default/app/i18n/zh-CN.ts +20 -0
- package/dist/templates/default/app/i18n/zh-TW.ts +19 -0
- package/dist/templates/default/package.json +4 -3
- package/dist/templates/headless/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
- package/dist/templates/headless/AGENTS.md +9 -3
- package/dist/templates/headless/package.json +4 -3
- package/dist/templates/workspace-core/.agents/skills/self-modifying-code/SKILL.md +20 -2
- package/dist/templates/workspace-core/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
- package/dist/templates/workspace-core/package.json +6 -6
- package/dist/templates/workspace-root/AGENTS.md +10 -4
- package/dist/templates/workspace-root/package.json +3 -2
- package/dist/triggers/dispatcher.js +27 -2
- package/dist/triggers/dispatcher.js.map +1 -1
- package/dist/vite/client.d.ts.map +1 -1
- package/dist/vite/client.js +70 -1
- package/dist/vite/client.js.map +1 -1
- package/docs/content/faq.mdx +17 -0
- package/docs/content/notifications.mdx +12 -6
- package/package.json +7 -6
- package/src/templates/default/.agents/skills/self-modifying-code/SKILL.md +20 -2
- package/src/templates/default/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
- package/src/templates/default/AGENTS.md +10 -3
- package/src/templates/default/app/i18n/ar-SA.ts +21 -0
- package/src/templates/default/app/i18n/de-DE.ts +21 -0
- package/src/templates/default/app/i18n/en-US.ts +21 -0
- package/src/templates/default/app/i18n/es-ES.ts +21 -0
- package/src/templates/default/app/i18n/fr-FR.ts +21 -0
- package/src/templates/default/app/i18n/hi-IN.ts +20 -0
- package/src/templates/default/app/i18n/ja-JP.ts +21 -0
- package/src/templates/default/app/i18n/ko-KR.ts +20 -0
- package/src/templates/default/app/i18n/pt-BR.ts +21 -0
- package/src/templates/default/app/i18n/zh-CN.ts +20 -0
- package/src/templates/default/app/i18n/zh-TW.ts +19 -0
- package/src/templates/default/package.json +4 -3
- package/src/templates/headless/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
- package/src/templates/headless/AGENTS.md +9 -3
- package/src/templates/headless/package.json +4 -3
- package/src/templates/workspace-core/.agents/skills/self-modifying-code/SKILL.md +20 -2
- package/src/templates/workspace-core/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
- package/src/templates/workspace-core/package.json +6 -6
- package/src/templates/workspace-root/AGENTS.md +10 -4
- package/src/templates/workspace-root/package.json +3 -2
|
@@ -15,8 +15,8 @@ use tauri::{
|
|
|
15
15
|
|
|
16
16
|
use crate::dlog;
|
|
17
17
|
use crate::state::{
|
|
18
|
-
DictationActive, LastTranscript, MeetingActive, RecordingActive, TrayAnchor,
|
|
19
|
-
VoiceWakePopover,
|
|
18
|
+
ActiveMeetingId, DictationActive, LastTranscript, MeetingActive, RecordingActive, TrayAnchor,
|
|
19
|
+
VoiceTargetBundle, VoiceWakePopover,
|
|
20
20
|
};
|
|
21
21
|
use crate::util::{
|
|
22
22
|
build_overlay_url, configure_overlay_behavior, hide_voice_wake_popover, is_recording_active,
|
|
@@ -56,7 +56,7 @@ const ONBOARDING_HEIGHT_LOGICAL: f64 = 640.0;
|
|
|
56
56
|
/// launch — this matches Loom's out-of-the-box behavior.
|
|
57
57
|
const BUBBLE_SIZE_SMALL: u32 = 360;
|
|
58
58
|
const BUBBLE_SIZE_MEDIUM: u32 = 504;
|
|
59
|
-
const POPOVER_SHADOW_GUTTER_LOGICAL: f64 =
|
|
59
|
+
const POPOVER_SHADOW_GUTTER_LOGICAL: f64 = 24.0;
|
|
60
60
|
const POPOVER_DEFAULT_WIDTH_LOGICAL: f64 = 360.0;
|
|
61
61
|
const POPOVER_DEFAULT_HEIGHT_LOGICAL: f64 = 520.0;
|
|
62
62
|
const OVERLAY_SHADOW_GUTTER_LOGICAL: f64 = 18.0;
|
|
@@ -969,8 +969,11 @@ pub async fn hide_overlays(app: AppHandle) -> Result<(), String> {
|
|
|
969
969
|
let _ = w.close();
|
|
970
970
|
}
|
|
971
971
|
}
|
|
972
|
-
//
|
|
973
|
-
|
|
972
|
+
// The meeting pill belongs to the live notes session, not the popover's
|
|
973
|
+
// camera/bubble lifecycle. Keep it visible when the popover closes.
|
|
974
|
+
if !crate::util::is_meeting_active(&app) {
|
|
975
|
+
let _ = crate::recording_indicator::recording_pill_hide(app).await;
|
|
976
|
+
}
|
|
974
977
|
Ok(())
|
|
975
978
|
}
|
|
976
979
|
|
|
@@ -1887,16 +1890,37 @@ pub async fn set_recording_state(app: AppHandle, active: bool) -> Result<(), Str
|
|
|
1887
1890
|
/// teardown in `lib.rs`: quit stays instant when no meeting is active, and
|
|
1888
1891
|
/// only waits for a graceful stop when one is.
|
|
1889
1892
|
#[tauri::command]
|
|
1890
|
-
pub async fn set_meeting_active(
|
|
1891
|
-
|
|
1893
|
+
pub async fn set_meeting_active(
|
|
1894
|
+
app: AppHandle,
|
|
1895
|
+
active: bool,
|
|
1896
|
+
meeting_id: Option<String>,
|
|
1897
|
+
) -> Result<(), String> {
|
|
1898
|
+
dlog!(
|
|
1899
|
+
"[clips-tray] set_meeting_active active={} meeting_id={:?}",
|
|
1900
|
+
active,
|
|
1901
|
+
meeting_id
|
|
1902
|
+
);
|
|
1892
1903
|
if let Some(state) = app.try_state::<MeetingActive>() {
|
|
1893
1904
|
if let Ok(mut g) = state.0.lock() {
|
|
1894
1905
|
*g = active;
|
|
1895
1906
|
}
|
|
1896
1907
|
}
|
|
1908
|
+
if let Some(state) = app.try_state::<ActiveMeetingId>() {
|
|
1909
|
+
if let Ok(mut g) = state.0.lock() {
|
|
1910
|
+
*g = if active { meeting_id } else { None };
|
|
1911
|
+
}
|
|
1912
|
+
}
|
|
1913
|
+
crate::tray::rebuild_tray_menu(&app);
|
|
1897
1914
|
Ok(())
|
|
1898
1915
|
}
|
|
1899
1916
|
|
|
1917
|
+
#[tauri::command]
|
|
1918
|
+
pub async fn get_active_meeting_id(app: AppHandle) -> Result<Option<String>, String> {
|
|
1919
|
+
Ok(app
|
|
1920
|
+
.try_state::<ActiveMeetingId>()
|
|
1921
|
+
.and_then(|s| s.0.lock().ok().and_then(|g| g.clone())))
|
|
1922
|
+
}
|
|
1923
|
+
|
|
1900
1924
|
/// Guards the quit-teardown handshake in `lib.rs`'s `ExitRequested` handler:
|
|
1901
1925
|
/// 0 = not requested, 1 = requested (waiting on JS), 2 = done (safe to let
|
|
1902
1926
|
/// the process exit — including the watchdog's own forced exit, which must
|
|
@@ -1937,6 +1961,16 @@ pub async fn reset_state(app: AppHandle) -> Result<(), String> {
|
|
|
1937
1961
|
*g = false;
|
|
1938
1962
|
}
|
|
1939
1963
|
}
|
|
1964
|
+
if let Some(state) = app.try_state::<MeetingActive>() {
|
|
1965
|
+
if let Ok(mut g) = state.0.lock() {
|
|
1966
|
+
*g = false;
|
|
1967
|
+
}
|
|
1968
|
+
}
|
|
1969
|
+
if let Some(state) = app.try_state::<ActiveMeetingId>() {
|
|
1970
|
+
if let Ok(mut g) = state.0.lock() {
|
|
1971
|
+
*g = None;
|
|
1972
|
+
}
|
|
1973
|
+
}
|
|
1940
1974
|
if let Some(w) = app.get_webview_window(REGION_GUIDES_LABEL) {
|
|
1941
1975
|
let _ = w.close();
|
|
1942
1976
|
}
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
//! is served by the Vite-built React UI (see `../dist`).
|
|
6
6
|
|
|
7
7
|
mod accessibility;
|
|
8
|
+
mod adhoc_meetings_watcher;
|
|
8
9
|
mod clips;
|
|
9
10
|
mod config;
|
|
10
11
|
mod debug;
|
|
@@ -32,8 +33,8 @@ use tauri::{Emitter, Manager};
|
|
|
32
33
|
|
|
33
34
|
use clips::{position_popover, toggle_popover};
|
|
34
35
|
use state::{
|
|
35
|
-
DictationActive, DictationEnabled, LastTranscript, MeetingActive,
|
|
36
|
-
RecordingActive, TrayAnchor, TrayMeetings, VoiceTargetBundle, VoiceWakePopover,
|
|
36
|
+
ActiveMeetingId, DictationActive, DictationEnabled, LastTranscript, MeetingActive,
|
|
37
|
+
PopoverShownAt, RecordingActive, TrayAnchor, TrayMeetings, VoiceTargetBundle, VoiceWakePopover,
|
|
37
38
|
};
|
|
38
39
|
use util::{
|
|
39
40
|
configure_overlay_behavior, is_recording_active, present_interactive_window,
|
|
@@ -94,6 +95,7 @@ pub fn run() {
|
|
|
94
95
|
clips::paste_last_dictation,
|
|
95
96
|
clips::set_recording_state,
|
|
96
97
|
clips::set_meeting_active,
|
|
98
|
+
clips::get_active_meeting_id,
|
|
97
99
|
clips::quit_teardown_done,
|
|
98
100
|
clips::reset_state,
|
|
99
101
|
clips::save_bubble_position,
|
|
@@ -203,6 +205,7 @@ pub fn run() {
|
|
|
203
205
|
.manage(PopoverShownAt::default())
|
|
204
206
|
.manage(RecordingActive::default())
|
|
205
207
|
.manage(MeetingActive::default())
|
|
208
|
+
.manage(ActiveMeetingId::default())
|
|
206
209
|
.manage(DictationEnabled::default())
|
|
207
210
|
.manage(DictationActive::default())
|
|
208
211
|
.manage(VoiceWakePopover::default())
|
|
@@ -211,6 +214,7 @@ pub fn run() {
|
|
|
211
214
|
.manage(native_screen::NativeFullscreenRecordingState::default())
|
|
212
215
|
.manage(screen_memory::ScreenMemoryState::default())
|
|
213
216
|
.manage(meetings_watcher::MeetingsWatcherState::default())
|
|
217
|
+
.manage(adhoc_meetings_watcher::AdhocMeetingsWatcherState::default())
|
|
214
218
|
.manage(notifications::MeetingNotificationState::default())
|
|
215
219
|
.manage(silence_detector::DetectorState::default())
|
|
216
220
|
.setup(|app| {
|
|
@@ -260,6 +264,9 @@ pub fn run() {
|
|
|
260
264
|
// server URL via `meetings_watcher_set_server_url` once the
|
|
261
265
|
// popover boots.
|
|
262
266
|
meetings_watcher::spawn_watcher(app.handle().clone());
|
|
267
|
+
// Granola-style adhoc Zoom/Teams detection — shares session
|
|
268
|
+
// credentials with the calendar watcher above.
|
|
269
|
+
adhoc_meetings_watcher::spawn_watcher(app.handle().clone());
|
|
263
270
|
|
|
264
271
|
// Pre-download the Whisper model in the background so the first
|
|
265
272
|
// meeting doesn't pay the ~142 MB download cost mid-call. Skipped
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
//!
|
|
3
3
|
//! Runs as a tokio task spawned from `lib.rs::run` setup. Every 30s it calls
|
|
4
4
|
//! the backend's `list-meetings` action for the next handful of live Google
|
|
5
|
-
//! Calendar meetings. For any meeting
|
|
6
|
-
//!
|
|
7
|
-
//! banner overlay.
|
|
5
|
+
//! Calendar meetings. For any meeting in the Granola-style reminder window
|
|
6
|
+
//! (1 minute before start through 5 minutes after) that we haven't already
|
|
7
|
+
//! alerted on, we fire the in-app banner overlay.
|
|
8
8
|
//!
|
|
9
9
|
//! ## Wire-up (from the popover renderer)
|
|
10
10
|
//!
|
|
@@ -39,8 +39,12 @@ use crate::tray_meetings::MeetingItem as TrayMeetingItem;
|
|
|
39
39
|
|
|
40
40
|
const MEETING_POLL_LIMIT: u8 = 10;
|
|
41
41
|
|
|
42
|
-
///
|
|
43
|
-
const NOTIFY_LEAD_SECS: i64 =
|
|
42
|
+
/// Show the reminder starting this many seconds before meeting start.
|
|
43
|
+
const NOTIFY_LEAD_SECS: i64 = 60;
|
|
44
|
+
|
|
45
|
+
/// Keep reminding eligible (until dismissed / acted on) this many seconds
|
|
46
|
+
/// after the scheduled start. Overlay auto-hide mirrors this hold window.
|
|
47
|
+
const NOTIFY_HOLD_AFTER_START_SECS: i64 = 5 * 60;
|
|
44
48
|
|
|
45
49
|
/// Forget de-dupe / snooze entries once a meeting's start is this far past, so
|
|
46
50
|
/// the maps don't grow unbounded across a long-running session.
|
|
@@ -80,6 +84,53 @@ struct MeetingsWatcherInner {
|
|
|
80
84
|
/// meetingId -> unix-seconds deadline. While now < deadline the meeting is
|
|
81
85
|
/// skipped; once it passes we re-fire the reminder exactly once.
|
|
82
86
|
snoozed_until: HashMap<String, i64>,
|
|
87
|
+
/// platform -> unix-seconds when a calendar reminder last fired. Soft
|
|
88
|
+
/// guard so adhoc Zoom/Teams detection doesn't double-prompt right after
|
|
89
|
+
/// a calendar banner for the same app.
|
|
90
|
+
last_calendar_notify_at: HashMap<String, i64>,
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/// Snapshot of auth fields the adhoc watcher needs to POST create-meeting.
|
|
94
|
+
#[derive(Clone, Default)]
|
|
95
|
+
pub struct MeetingsSessionSnapshot {
|
|
96
|
+
pub server_url: Option<String>,
|
|
97
|
+
pub session_cookie: Option<String>,
|
|
98
|
+
pub auth_token: Option<String>,
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
impl MeetingsWatcherState {
|
|
102
|
+
pub fn session_snapshot(&self) -> MeetingsSessionSnapshot {
|
|
103
|
+
let Ok(g) = self.inner.lock() else {
|
|
104
|
+
return MeetingsSessionSnapshot::default();
|
|
105
|
+
};
|
|
106
|
+
MeetingsSessionSnapshot {
|
|
107
|
+
server_url: g.server_url.clone(),
|
|
108
|
+
session_cookie: g.session_cookie.clone(),
|
|
109
|
+
auth_token: g.auth_token.clone(),
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
pub fn note_calendar_notify(&self, platform: Option<&str>) {
|
|
114
|
+
let Some(platform) = platform.map(str::trim).filter(|p| !p.is_empty()) else {
|
|
115
|
+
return;
|
|
116
|
+
};
|
|
117
|
+
if let Ok(mut g) = self.inner.lock() {
|
|
118
|
+
g.last_calendar_notify_at
|
|
119
|
+
.insert(platform.to_lowercase(), chrono::Utc::now().timestamp());
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/// True if a calendar reminder for `platform` fired within `within_secs`.
|
|
124
|
+
pub fn recent_calendar_notify(&self, platform: &str, within_secs: i64) -> bool {
|
|
125
|
+
let Ok(g) = self.inner.lock() else {
|
|
126
|
+
return false;
|
|
127
|
+
};
|
|
128
|
+
let key = platform.to_lowercase();
|
|
129
|
+
let Some(at) = g.last_calendar_notify_at.get(&key).copied() else {
|
|
130
|
+
return false;
|
|
131
|
+
};
|
|
132
|
+
chrono::Utc::now().timestamp() - at <= within_secs
|
|
133
|
+
}
|
|
83
134
|
}
|
|
84
135
|
|
|
85
136
|
#[derive(Debug, Clone, Deserialize, Serialize)]
|
|
@@ -88,8 +139,12 @@ struct MeetingItem {
|
|
|
88
139
|
title: Option<String>,
|
|
89
140
|
#[serde(default, alias = "scheduledStart")]
|
|
90
141
|
scheduled_start: Option<String>,
|
|
142
|
+
#[serde(default, alias = "scheduledEnd")]
|
|
143
|
+
scheduled_end: Option<String>,
|
|
91
144
|
#[serde(default, alias = "joinUrl")]
|
|
92
145
|
join_url: Option<String>,
|
|
146
|
+
#[serde(default)]
|
|
147
|
+
platform: Option<String>,
|
|
93
148
|
}
|
|
94
149
|
|
|
95
150
|
#[derive(Debug, Deserialize)]
|
|
@@ -232,9 +287,17 @@ async fn tick_once(app: &AppHandle, client: &reqwest::Client) -> Result<(), Stri
|
|
|
232
287
|
|
|
233
288
|
let url = format!("{}/_agent-native/actions/list-meetings", server_url);
|
|
234
289
|
let limit = MEETING_POLL_LIMIT.to_string();
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
290
|
+
// Include meetings that started within the hold window so a late-open
|
|
291
|
+
// desktop still surfaces the reminder until 5 minutes after start.
|
|
292
|
+
let within_min = ((NOTIFY_LEAD_SECS + NOTIFY_HOLD_AFTER_START_SECS) / 60 + 1).to_string();
|
|
293
|
+
let mut req = client.get(&url).query(&[
|
|
294
|
+
("view", "upcoming"),
|
|
295
|
+
("limit", limit.as_str()),
|
|
296
|
+
("upcomingWithinMin", within_min.as_str()),
|
|
297
|
+
// list-meetings also uses this for the lower bound when we widen the
|
|
298
|
+
// upcoming window to include recently-started events (see action).
|
|
299
|
+
("includeStartedWithinMin", "5"),
|
|
300
|
+
]);
|
|
238
301
|
req = req.header("X-Request-Source", "clips-desktop");
|
|
239
302
|
if let Some(c) = cookie.as_deref() {
|
|
240
303
|
req = req.header("Cookie", c);
|
|
@@ -301,14 +364,19 @@ async fn tick_once(app: &AppHandle, client: &reqwest::Client) -> Result<(), Stri
|
|
|
301
364
|
g.snoozed_until
|
|
302
365
|
.retain(|_, until| *until > now_ts - STALE_AFTER_SECS);
|
|
303
366
|
|
|
367
|
+
// Eligible from 1 min before start through 5 min after start.
|
|
368
|
+
// secs_until > 0 => still upcoming; negative => already started.
|
|
369
|
+
let in_window =
|
|
370
|
+
secs_until <= NOTIFY_LEAD_SECS && secs_until >= -NOTIFY_HOLD_AFTER_START_SECS;
|
|
371
|
+
|
|
304
372
|
let eligible = match g.snoozed_until.get(&m.id).copied() {
|
|
305
373
|
Some(until) if now_ts < until => false, // still snoozed
|
|
306
374
|
Some(_) => {
|
|
307
|
-
// Snooze elapsed — re-fire
|
|
375
|
+
// Snooze elapsed — re-fire if still inside the hold window.
|
|
308
376
|
g.snoozed_until.remove(&m.id);
|
|
309
|
-
|
|
377
|
+
in_window
|
|
310
378
|
}
|
|
311
|
-
None =>
|
|
379
|
+
None => in_window,
|
|
312
380
|
};
|
|
313
381
|
|
|
314
382
|
if !eligible {
|
|
@@ -333,10 +401,16 @@ async fn tick_once(app: &AppHandle, client: &reqwest::Client) -> Result<(), Stri
|
|
|
333
401
|
if config.show_meeting_widget_enabled
|
|
334
402
|
|| config.meeting_transcription_mode == MeetingTranscriptionMode::Auto
|
|
335
403
|
{
|
|
404
|
+
if let Some(state) = app.try_state::<MeetingsWatcherState>() {
|
|
405
|
+
state.note_calendar_notify(m.platform.as_deref());
|
|
406
|
+
}
|
|
336
407
|
let app_clone = app.clone();
|
|
337
408
|
let id_clone = m.id.clone();
|
|
338
409
|
let title_clone = title.clone();
|
|
339
410
|
let join_clone = join_url.clone();
|
|
411
|
+
let start_clone = m.scheduled_start.clone();
|
|
412
|
+
let end_clone = m.scheduled_end.clone();
|
|
413
|
+
let platform_clone = m.platform.clone();
|
|
340
414
|
let auto_start = config.meeting_transcription_mode == MeetingTranscriptionMode::Auto;
|
|
341
415
|
tauri::async_runtime::spawn(async move {
|
|
342
416
|
let _ = crate::notifications::notify_meeting_starting(
|
|
@@ -345,7 +419,11 @@ async fn tick_once(app: &AppHandle, client: &reqwest::Client) -> Result<(), Stri
|
|
|
345
419
|
title_clone,
|
|
346
420
|
secs_until,
|
|
347
421
|
join_clone,
|
|
422
|
+
start_clone,
|
|
423
|
+
end_clone,
|
|
424
|
+
platform_clone,
|
|
348
425
|
Some(auto_start),
|
|
426
|
+
None,
|
|
349
427
|
)
|
|
350
428
|
.await;
|
|
351
429
|
});
|
|
@@ -65,18 +65,36 @@ const AUDIO_DENOISE_FILTER: &str = "afftdn=nr=10:nf=-50:tn=1";
|
|
|
65
65
|
// explicit output rate the AAC track ends up at 192 kHz and plays back slow.
|
|
66
66
|
const AUDIO_OUTPUT_SAMPLE_RATE: u32 = 48000;
|
|
67
67
|
|
|
68
|
+
// Pre-gain for mic-only native captures. ScreenCaptureKit records without
|
|
69
|
+
// WebRTC AGC (unlike Loom / the browser path), so speech often lands well
|
|
70
|
+
// below -16 LUFS. 12 dB before loudnorm matches Loom-ish perceptual loudness
|
|
71
|
+
// for MacBook mics; loudnorm still clamps true peaks at TP=-1.5.
|
|
72
|
+
const AUDIO_MIC_PREGAIN_FILTER: &str = "volume=12dB";
|
|
73
|
+
// After the mic+system centered downmix (0.5*L+0.5*R) each source is ~
|
|
74
|
+
// 6 dB quieter. Restore energy before loudnorm so dual-capture clips are not
|
|
75
|
+
// systematically quieter than mic-only. Dual still mixes two sources so peaks
|
|
76
|
+
// may compress more under loudnorm than mic-only — by design when both sides
|
|
77
|
+
// compete for the same LUFS budget.
|
|
78
|
+
const AUDIO_DOWNMIX_MAKEUP_FILTER: &str = "volume=6dB";
|
|
79
|
+
|
|
68
80
|
// Loudness normalization, optionally preceded by the centered-stereo downmix
|
|
69
81
|
// that repairs the mic+system L/R split and denoise for native mic captures.
|
|
70
82
|
// Pair with `-ar AUDIO_OUTPUT_SAMPLE_RATE` so loudnorm's 192 kHz output is
|
|
71
83
|
// resampled back.
|
|
72
|
-
fn audio_filter_chain(downmix: bool, denoise: bool) -> String {
|
|
84
|
+
fn audio_filter_chain(downmix: bool, denoise: bool, mic_pregain: bool) -> String {
|
|
73
85
|
let mut filters = Vec::new();
|
|
74
86
|
if downmix {
|
|
75
87
|
filters.push(AUDIO_DOWNMIX_FILTER);
|
|
88
|
+
// Undo pan attenuation; do not also apply mic-only pregain here —
|
|
89
|
+
// system audio would get a double boost.
|
|
90
|
+
filters.push(AUDIO_DOWNMIX_MAKEUP_FILTER);
|
|
76
91
|
}
|
|
77
92
|
if denoise {
|
|
78
93
|
filters.push(AUDIO_DENOISE_FILTER);
|
|
79
94
|
}
|
|
95
|
+
if mic_pregain {
|
|
96
|
+
filters.push(AUDIO_MIC_PREGAIN_FILTER);
|
|
97
|
+
}
|
|
80
98
|
filters.push(AUDIO_LOUDNESS_FILTER);
|
|
81
99
|
filters.join(",")
|
|
82
100
|
}
|
|
@@ -522,11 +540,17 @@ struct SavedNativeRecording {
|
|
|
522
540
|
height: Option<u32>,
|
|
523
541
|
bytes: u64,
|
|
524
542
|
has_audio: bool,
|
|
525
|
-
// Whether the mic was captured. Drives
|
|
526
|
-
// for the mic+system L/R split. Defaults to
|
|
527
|
-
// before this field existed so their audio is
|
|
543
|
+
// Whether the mic was captured. Drives denoise + (with system audio) the
|
|
544
|
+
// centered-stereo downmix repair for the mic+system L/R split. Defaults to
|
|
545
|
+
// false for recordings queued before this field existed so their audio is
|
|
546
|
+
// left untouched.
|
|
528
547
|
#[serde(default)]
|
|
529
548
|
mic_captured: bool,
|
|
549
|
+
// Whether system audio was captured alongside the mic. Needed to decide
|
|
550
|
+
// downmix vs mic-only pregain on retry uploads. Defaults to false for
|
|
551
|
+
// older pending files (safe: skips downmix that would attenuate mic-only).
|
|
552
|
+
#[serde(default)]
|
|
553
|
+
system_audio_captured: bool,
|
|
530
554
|
has_camera: bool,
|
|
531
555
|
saved_at: String,
|
|
532
556
|
last_attempt_at: Option<String>,
|
|
@@ -2577,6 +2601,7 @@ fn saved_recording_from_path(
|
|
|
2577
2601
|
bytes,
|
|
2578
2602
|
has_audio,
|
|
2579
2603
|
mic_captured: session.restart.mic_captured_in_file,
|
|
2604
|
+
system_audio_captured: session.restart.capture_system_audio,
|
|
2580
2605
|
has_camera,
|
|
2581
2606
|
saved_at: now_iso(),
|
|
2582
2607
|
last_attempt_at: None,
|
|
@@ -3071,6 +3096,7 @@ async fn upload_recording_file(
|
|
|
3071
3096
|
Some(duration_ms),
|
|
3072
3097
|
has_audio,
|
|
3073
3098
|
session.restart.mic_captured_in_file,
|
|
3099
|
+
session.restart.capture_system_audio,
|
|
3074
3100
|
)?;
|
|
3075
3101
|
let upload_result = upload_prepared_recording_file(
|
|
3076
3102
|
app,
|
|
@@ -3152,6 +3178,7 @@ fn prepare_saved_recording_file(
|
|
|
3152
3178
|
Some(saved.duration_ms),
|
|
3153
3179
|
saved.has_audio,
|
|
3154
3180
|
saved.mic_captured,
|
|
3181
|
+
saved.system_audio_captured,
|
|
3155
3182
|
)?;
|
|
3156
3183
|
Ok((prepared, retry_combined_path))
|
|
3157
3184
|
}
|
|
@@ -3878,7 +3905,14 @@ fn prepare_recording_file(
|
|
|
3878
3905
|
duration_ms: Option<u128>,
|
|
3879
3906
|
has_audio: bool,
|
|
3880
3907
|
mic_captured_audio: bool,
|
|
3908
|
+
system_audio_captured: bool,
|
|
3881
3909
|
) -> Result<PreparedRecordingFile, String> {
|
|
3910
|
+
// Downmix only repairs mic+system L/R split. Applying it to mic-only
|
|
3911
|
+
// capture halves speech energy (~6 dB) when SCK puts the mic on one channel.
|
|
3912
|
+
let downmix_audio = mic_captured_audio && system_audio_captured;
|
|
3913
|
+
let denoise_audio = mic_captured_audio;
|
|
3914
|
+
// Pregain only for mic-only (no system audio compete) — SCK has no AGC.
|
|
3915
|
+
let mic_pregain = mic_captured_audio && !system_audio_captured;
|
|
3882
3916
|
let metadata = std::fs::metadata(path).map_err(|e| {
|
|
3883
3917
|
let diag = describe_recording_path(path);
|
|
3884
3918
|
eprintln!("[clips-tray] native recording file missing at prepare: {e}; {diag}");
|
|
@@ -3935,8 +3969,9 @@ fn prepare_recording_file(
|
|
|
3935
3969
|
ffmpeg_path,
|
|
3936
3970
|
path,
|
|
3937
3971
|
&normalized_path,
|
|
3938
|
-
|
|
3939
|
-
|
|
3972
|
+
downmix_audio,
|
|
3973
|
+
denoise_audio,
|
|
3974
|
+
mic_pregain,
|
|
3940
3975
|
) {
|
|
3941
3976
|
Ok(()) => {
|
|
3942
3977
|
let normalized_bytes = std::fs::metadata(&normalized_path)
|
|
@@ -4022,8 +4057,9 @@ fn prepare_recording_file(
|
|
|
4022
4057
|
height,
|
|
4023
4058
|
duration_ms,
|
|
4024
4059
|
has_audio,
|
|
4025
|
-
|
|
4026
|
-
|
|
4060
|
+
downmix_audio,
|
|
4061
|
+
denoise_audio,
|
|
4062
|
+
mic_pregain,
|
|
4027
4063
|
) {
|
|
4028
4064
|
Ok(()) => {
|
|
4029
4065
|
let compressed_bytes = std::fs::metadata(&compressed_path)
|
|
@@ -4421,6 +4457,7 @@ fn normalize_audio_with_ffmpeg(
|
|
|
4421
4457
|
output: &Path,
|
|
4422
4458
|
downmix_audio: bool,
|
|
4423
4459
|
denoise_audio: bool,
|
|
4460
|
+
mic_pregain: bool,
|
|
4424
4461
|
) -> Result<(), String> {
|
|
4425
4462
|
let audio_bitrate = format!("{NORMALIZED_AUDIO_BITRATE_KBPS}k");
|
|
4426
4463
|
let mut command = Command::new(ffmpeg_path);
|
|
@@ -4442,7 +4479,11 @@ fn normalize_audio_with_ffmpeg(
|
|
|
4442
4479
|
.arg("-b:a")
|
|
4443
4480
|
.arg(audio_bitrate)
|
|
4444
4481
|
.arg("-af")
|
|
4445
|
-
.arg(audio_filter_chain(
|
|
4482
|
+
.arg(audio_filter_chain(
|
|
4483
|
+
downmix_audio,
|
|
4484
|
+
denoise_audio,
|
|
4485
|
+
mic_pregain,
|
|
4486
|
+
))
|
|
4446
4487
|
.arg("-ac")
|
|
4447
4488
|
.arg("2")
|
|
4448
4489
|
.arg("-ar")
|
|
@@ -4507,6 +4548,7 @@ fn transcode_with_ffmpeg(
|
|
|
4507
4548
|
normalize_audio: bool,
|
|
4508
4549
|
downmix_audio: bool,
|
|
4509
4550
|
denoise_audio: bool,
|
|
4551
|
+
mic_pregain: bool,
|
|
4510
4552
|
) -> Result<(), String> {
|
|
4511
4553
|
let mut command = Command::new(ffmpeg_path);
|
|
4512
4554
|
command
|
|
@@ -4530,9 +4572,11 @@ fn transcode_with_ffmpeg(
|
|
|
4530
4572
|
}
|
|
4531
4573
|
|
|
4532
4574
|
if normalize_audio {
|
|
4533
|
-
command
|
|
4534
|
-
|
|
4535
|
-
|
|
4575
|
+
command.arg("-af").arg(audio_filter_chain(
|
|
4576
|
+
downmix_audio,
|
|
4577
|
+
denoise_audio,
|
|
4578
|
+
mic_pregain,
|
|
4579
|
+
));
|
|
4536
4580
|
}
|
|
4537
4581
|
|
|
4538
4582
|
let duration_rate_limit =
|
|
@@ -4965,7 +5009,8 @@ fn concat_mp4_segments(segments: &[PathBuf], output: &Path) -> Result<(), String
|
|
|
4965
5009
|
mod audio_track_probe_tests {
|
|
4966
5010
|
use super::{
|
|
4967
5011
|
audio_filter_chain, mp4_has_audio_track, parse_ffmpeg_volume_db, AudioSignalProbe,
|
|
4968
|
-
AUDIO_DENOISE_FILTER, AUDIO_DOWNMIX_FILTER,
|
|
5012
|
+
AUDIO_DENOISE_FILTER, AUDIO_DOWNMIX_FILTER, AUDIO_DOWNMIX_MAKEUP_FILTER,
|
|
5013
|
+
AUDIO_LOUDNESS_FILTER, AUDIO_MIC_PREGAIN_FILTER,
|
|
4969
5014
|
};
|
|
4970
5015
|
use std::io::Write;
|
|
4971
5016
|
|
|
@@ -5006,14 +5051,23 @@ mod audio_track_probe_tests {
|
|
|
5006
5051
|
|
|
5007
5052
|
#[test]
|
|
5008
5053
|
fn builds_native_audio_filter_chain_for_mic_noise_reduction() {
|
|
5009
|
-
assert_eq!(audio_filter_chain(false, false), AUDIO_LOUDNESS_FILTER);
|
|
5010
5054
|
assert_eq!(
|
|
5011
|
-
audio_filter_chain(false,
|
|
5055
|
+
audio_filter_chain(false, false, false),
|
|
5056
|
+
AUDIO_LOUDNESS_FILTER
|
|
5057
|
+
);
|
|
5058
|
+
assert_eq!(
|
|
5059
|
+
audio_filter_chain(false, true, false),
|
|
5012
5060
|
format!("{AUDIO_DENOISE_FILTER},{AUDIO_LOUDNESS_FILTER}")
|
|
5013
5061
|
);
|
|
5014
5062
|
assert_eq!(
|
|
5015
|
-
audio_filter_chain(true, true),
|
|
5016
|
-
format!("{
|
|
5063
|
+
audio_filter_chain(false, true, true),
|
|
5064
|
+
format!("{AUDIO_DENOISE_FILTER},{AUDIO_MIC_PREGAIN_FILTER},{AUDIO_LOUDNESS_FILTER}")
|
|
5065
|
+
);
|
|
5066
|
+
assert_eq!(
|
|
5067
|
+
audio_filter_chain(true, true, false),
|
|
5068
|
+
format!(
|
|
5069
|
+
"{AUDIO_DOWNMIX_FILTER},{AUDIO_DOWNMIX_MAKEUP_FILTER},{AUDIO_DENOISE_FILTER},{AUDIO_LOUDNESS_FILTER}"
|
|
5070
|
+
)
|
|
5017
5071
|
);
|
|
5018
5072
|
}
|
|
5019
5073
|
|
|
@@ -134,7 +134,8 @@ pub(crate) mod macos {
|
|
|
134
134
|
kAudioOutputUnitProperty_CurrentDevice, kAudioUnitScope_Global, AudioUnitSetProperty,
|
|
135
135
|
};
|
|
136
136
|
use objc2_avf_audio::{
|
|
137
|
-
AVAudioEngine, AVAudioInputNode, AVAudioPCMBuffer,
|
|
137
|
+
AVAudioEngine, AVAudioInputNode, AVAudioPCMBuffer, AVAudioSession,
|
|
138
|
+
AVAudioSessionCategoryOptions, AVAudioSessionCategoryPlayAndRecord, AVAudioTime,
|
|
138
139
|
AVAudioVoiceProcessingOtherAudioDuckingConfiguration,
|
|
139
140
|
AVAudioVoiceProcessingOtherAudioDuckingLevel,
|
|
140
141
|
};
|
|
@@ -652,6 +653,36 @@ pub(crate) mod macos {
|
|
|
652
653
|
}
|
|
653
654
|
}
|
|
654
655
|
|
|
656
|
+
/// Keep Zoom/Meet/Teams' own mic uplink alive while Clips opens a parallel
|
|
657
|
+
/// AVAudioEngine tap. Without MixWithOthers, PlayAndRecord takes exclusive
|
|
658
|
+
/// session ownership and other apps' live calls get ducked or starved even
|
|
659
|
+
/// when VoiceProcessingIO is off.
|
|
660
|
+
fn configure_shared_mic_audio_session() {
|
|
661
|
+
unsafe {
|
|
662
|
+
let session = AVAudioSession::sharedInstance();
|
|
663
|
+
let Some(category) = AVAudioSessionCategoryPlayAndRecord else {
|
|
664
|
+
eprintln!("[whisper-mic] PlayAndRecord category missing — skipping MixWithOthers");
|
|
665
|
+
return;
|
|
666
|
+
};
|
|
667
|
+
let options = AVAudioSessionCategoryOptions::MixWithOthers;
|
|
668
|
+
if let Err(err) = session.setCategory_withOptions_error(category, options) {
|
|
669
|
+
eprintln!(
|
|
670
|
+
"[whisper-mic] setCategory MixWithOthers failed: {} — continuing",
|
|
671
|
+
ns_error_message(&err)
|
|
672
|
+
);
|
|
673
|
+
return;
|
|
674
|
+
}
|
|
675
|
+
if let Err(err) = session.setActive_error(true) {
|
|
676
|
+
eprintln!(
|
|
677
|
+
"[whisper-mic] AVAudioSession setActive failed: {} — continuing",
|
|
678
|
+
ns_error_message(&err)
|
|
679
|
+
);
|
|
680
|
+
return;
|
|
681
|
+
}
|
|
682
|
+
eprintln!("[whisper-mic] AVAudioSession PlayAndRecord + MixWithOthers active");
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
|
|
655
686
|
fn take_warmed_raw_mic_engine(device_key: &RawMicDeviceKey) -> Option<WarmedRawMicEngine> {
|
|
656
687
|
let mut slot = warmed_raw_mic_engine_slot().lock().ok()?;
|
|
657
688
|
let should_reuse = slot
|
|
@@ -758,6 +789,7 @@ pub(crate) mod macos {
|
|
|
758
789
|
mic_device_label: Option<&str>,
|
|
759
790
|
device_key: RawMicDeviceKey,
|
|
760
791
|
) -> Result<WarmedRawMicEngine, String> {
|
|
792
|
+
configure_shared_mic_audio_session();
|
|
761
793
|
let engine: Retained<AVAudioEngine> = unsafe { AVAudioEngine::new() };
|
|
762
794
|
configure_engine_input_device(&engine, mic_device_id, mic_device_label)?;
|
|
763
795
|
let input_node: Retained<AVAudioInputNode> = unsafe { engine.inputNode() };
|
|
@@ -955,6 +987,7 @@ pub(crate) mod macos {
|
|
|
955
987
|
// Spin up the engine and grab its input node + native format.
|
|
956
988
|
// SAFETY: `AVAudioEngine::new()` returns a retained engine.
|
|
957
989
|
// `inputNode` is the engine's singleton input — also retained.
|
|
990
|
+
configure_shared_mic_audio_session();
|
|
958
991
|
let engine: Retained<AVAudioEngine> = unsafe { AVAudioEngine::new() };
|
|
959
992
|
configure_engine_input_device(
|
|
960
993
|
&engine,
|
|
@@ -1321,6 +1354,8 @@ pub(crate) mod macos {
|
|
|
1321
1354
|
}
|
|
1322
1355
|
clear_warmed_raw_mic_engine();
|
|
1323
1356
|
|
|
1357
|
+
configure_shared_mic_audio_session();
|
|
1358
|
+
|
|
1324
1359
|
let engine: Retained<AVAudioEngine> = unsafe { AVAudioEngine::new() };
|
|
1325
1360
|
configure_engine_input_device(
|
|
1326
1361
|
&engine,
|