@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.
Files changed (383) hide show
  1. package/blueprints/action/crud.md +1 -1
  2. package/blueprints/channel/discord.md +1 -1
  3. package/blueprints/provider/stripe.md +1 -1
  4. package/blueprints/sandbox/docker.md +1 -1
  5. package/corpus/README.md +2 -2
  6. package/corpus/core/CHANGELOG.md +31 -0
  7. package/corpus/core/blueprints/action/crud.md +1 -1
  8. package/corpus/core/blueprints/channel/discord.md +1 -1
  9. package/corpus/core/blueprints/provider/stripe.md +1 -1
  10. package/corpus/core/blueprints/sandbox/docker.md +1 -1
  11. package/corpus/core/docs/content/faq.mdx +17 -0
  12. package/corpus/core/docs/content/notifications.mdx +12 -6
  13. package/corpus/core/package.json +6 -5
  14. package/corpus/core/src/agent/production-agent.ts +140 -46
  15. package/corpus/core/src/agent/run-manager.ts +23 -2
  16. package/corpus/core/src/agent/run-store.ts +2 -2
  17. package/corpus/core/src/agent/tool-search.ts +71 -4
  18. package/corpus/core/src/cli/add.ts +1 -1
  19. package/corpus/core/src/cli/create.ts +8 -3
  20. package/corpus/core/src/cli/index.ts +25 -4
  21. package/corpus/core/src/cli/upgrade.ts +798 -0
  22. package/corpus/core/src/client/AssistantChat.tsx +1 -1
  23. package/corpus/core/src/client/HighlightedCodeBlock.tsx +20 -3
  24. package/corpus/core/src/client/agent-chat-adapter.ts +31 -6
  25. package/corpus/core/src/client/chat/message-components.tsx +85 -8
  26. package/corpus/core/src/client/chat/tool-call-display.tsx +355 -144
  27. package/corpus/core/src/client/code-agent-chat-adapter.ts +2 -9
  28. package/corpus/core/src/client/extensions/ExtensionSlot.tsx +7 -0
  29. package/corpus/core/src/client/settings/SettingsPanel.tsx +533 -389
  30. package/corpus/core/src/client/settings/SettingsSection.tsx +154 -17
  31. package/corpus/core/src/client/sse-event-processor.ts +29 -2
  32. package/corpus/core/src/extensions/actions.ts +113 -5
  33. package/corpus/core/src/jobs/scheduler.ts +30 -1
  34. package/corpus/core/src/localization/default-messages.ts +21 -0
  35. package/corpus/core/src/notifications/channels.ts +88 -25
  36. package/corpus/core/src/notifications/registry.ts +17 -3
  37. package/corpus/core/src/notifications/types.ts +4 -1
  38. package/corpus/core/src/server/agent-chat-plugin.ts +7 -3
  39. package/corpus/core/src/server/request-context.ts +7 -0
  40. package/corpus/core/src/styles/agent-conversation.css +1 -1
  41. package/corpus/core/src/templates/default/.agents/skills/self-modifying-code/SKILL.md +20 -2
  42. package/corpus/core/src/templates/default/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
  43. package/corpus/core/src/templates/default/AGENTS.md +10 -3
  44. package/corpus/core/src/templates/default/app/i18n/ar-SA.ts +21 -0
  45. package/corpus/core/src/templates/default/app/i18n/de-DE.ts +21 -0
  46. package/corpus/core/src/templates/default/app/i18n/en-US.ts +21 -0
  47. package/corpus/core/src/templates/default/app/i18n/es-ES.ts +21 -0
  48. package/corpus/core/src/templates/default/app/i18n/fr-FR.ts +21 -0
  49. package/corpus/core/src/templates/default/app/i18n/hi-IN.ts +20 -0
  50. package/corpus/core/src/templates/default/app/i18n/ja-JP.ts +21 -0
  51. package/corpus/core/src/templates/default/app/i18n/ko-KR.ts +20 -0
  52. package/corpus/core/src/templates/default/app/i18n/pt-BR.ts +21 -0
  53. package/corpus/core/src/templates/default/app/i18n/zh-CN.ts +20 -0
  54. package/corpus/core/src/templates/default/app/i18n/zh-TW.ts +19 -0
  55. package/corpus/core/src/templates/default/package.json +4 -3
  56. package/corpus/core/src/templates/headless/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
  57. package/corpus/core/src/templates/headless/AGENTS.md +9 -3
  58. package/corpus/core/src/templates/headless/package.json +4 -3
  59. package/corpus/core/src/templates/workspace-core/.agents/skills/self-modifying-code/SKILL.md +20 -2
  60. package/corpus/core/src/templates/workspace-core/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
  61. package/corpus/core/src/templates/workspace-core/package.json +6 -6
  62. package/corpus/core/src/templates/workspace-root/AGENTS.md +10 -4
  63. package/corpus/core/src/templates/workspace-root/package.json +3 -2
  64. package/corpus/core/src/triggers/dispatcher.ts +30 -2
  65. package/corpus/core/src/vite/client.ts +96 -1
  66. package/corpus/templates/analytics/.agents/skills/self-modifying-code/SKILL.md +20 -2
  67. package/corpus/templates/analytics/.agents/skills/session-replay/SKILL.md +20 -2
  68. package/corpus/templates/analytics/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
  69. package/corpus/templates/analytics/AGENTS.md +7 -5
  70. package/corpus/templates/analytics/actions/save-monitor.ts +14 -0
  71. package/corpus/templates/analytics/app/components/dashboard/DataTable.tsx +41 -6
  72. package/corpus/templates/analytics/app/components/dashboard/SqlChart.tsx +122 -11
  73. package/corpus/templates/analytics/app/global.css +26 -10
  74. package/corpus/templates/analytics/app/i18n/zh-TW.ts +2 -0
  75. package/corpus/templates/analytics/app/i18n-data.ts +25 -0
  76. package/corpus/templates/analytics/app/pages/monitoring/MonitoringPage.tsx +8 -2
  77. package/corpus/templates/analytics/app/pages/monitoring/uptime/MonitorFormPage.tsx +151 -32
  78. package/corpus/templates/analytics/app/pages/monitoring/uptime/i18n.ts +17 -2
  79. package/corpus/templates/analytics/app/pages/monitoring/uptime/types.ts +4 -0
  80. package/corpus/templates/analytics/app/pages/sessions/SessionDetailPage.tsx +461 -286
  81. package/corpus/templates/analytics/app/pages/sessions/SessionDevToolsPanel.tsx +285 -294
  82. package/corpus/templates/analytics/changelog/2026-07-08-console-details-expand-inline-under-each-row-and-the-event-t.md +6 -0
  83. package/corpus/templates/analytics/changelog/2026-07-08-dashboard-tables-keep-their-size-while-loading.md +6 -0
  84. package/corpus/templates/analytics/changelog/2026-07-08-monitoring-uptime-and-errors-tabs-now-clearly-show-which-view-is-active.md +6 -0
  85. package/corpus/templates/analytics/changelog/2026-07-08-session-replay-keeps-a-usable-stage-when-dev-tools-is-open-a.md +6 -0
  86. package/corpus/templates/analytics/changelog/2026-07-08-session-replay-resumes-after-scrubbing-and-expands-console-r.md +6 -0
  87. package/corpus/templates/analytics/changelog/2026-07-08-session-replay-uses-stock-rrweb-sizing-again-so-recorded.md +6 -0
  88. package/corpus/templates/analytics/changelog/2026-07-08-session-replays-wait-for-full-load-keep-a-normal-viewport-an.md +6 -0
  89. package/corpus/templates/analytics/changelog/2026-07-08-sidebar-dashboard-menus-now-open-above-the-dashboard-canvas.md +6 -0
  90. 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
  91. package/corpus/templates/analytics/docs/uptime-monitoring.md +13 -11
  92. package/corpus/templates/analytics/package.json +2 -2
  93. package/corpus/templates/analytics/server/db/schema-monitoring.ts +4 -0
  94. package/corpus/templates/analytics/server/lib/uptime-monitors.ts +60 -12
  95. package/corpus/templates/analytics/server/plugins/db.ts +10 -0
  96. package/corpus/templates/assets/.agents/skills/self-modifying-code/SKILL.md +20 -2
  97. package/corpus/templates/assets/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
  98. package/corpus/templates/assets/package.json +2 -2
  99. package/corpus/templates/brain/.agents/skills/self-modifying-code/SKILL.md +20 -2
  100. package/corpus/templates/brain/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
  101. package/corpus/templates/brain/package.json +2 -2
  102. package/corpus/templates/calendar/.agents/skills/self-modifying-code/SKILL.md +20 -2
  103. package/corpus/templates/calendar/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
  104. package/corpus/templates/calendar/AGENTS.md +10 -0
  105. package/corpus/templates/calendar/actions/create-event.ts +5 -1
  106. package/corpus/templates/calendar/actions/event-action-helpers.ts +41 -0
  107. package/corpus/templates/calendar/actions/get-attendee-timezones.ts +28 -0
  108. package/corpus/templates/calendar/actions/set-attendee-timezone.ts +68 -0
  109. package/corpus/templates/calendar/app/components/calendar/EventAttendeesSection.tsx +150 -11
  110. package/corpus/templates/calendar/app/components/calendar/EventDetailPanel.tsx +36 -1
  111. package/corpus/templates/calendar/app/components/calendar/EventDetailPopover.tsx +32 -0
  112. package/corpus/templates/calendar/app/hooks/use-attendee-timezones.ts +24 -0
  113. package/corpus/templates/calendar/app/i18n/zh-TW.ts +2 -0
  114. package/corpus/templates/calendar/app/i18n-data.ts +20 -0
  115. package/corpus/templates/calendar/app/lib/attendee-local-time.ts +122 -0
  116. package/corpus/templates/calendar/changelog/2026-07-08-event-details-now-support-extension-widgets-and-you-can-see-.md +6 -0
  117. package/corpus/templates/calendar/changelog/2026-07-08-when-you-invite-guests-to-a-new-event-you-now-appear-in.md +6 -0
  118. package/corpus/templates/calendar/package.json +2 -2
  119. package/corpus/templates/calendar/server/handlers/events.ts +5 -1
  120. package/corpus/templates/calendar/shared/api.ts +6 -0
  121. package/corpus/templates/chat/.agents/skills/self-modifying-code/SKILL.md +20 -2
  122. package/corpus/templates/chat/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
  123. package/corpus/templates/chat/package.json +2 -2
  124. package/corpus/templates/clips/.agents/skills/meetings/SKILL.md +6 -2
  125. package/corpus/templates/clips/.agents/skills/self-modifying-code/SKILL.md +20 -2
  126. package/corpus/templates/clips/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
  127. package/corpus/templates/clips/actions/create-meeting.ts +8 -3
  128. package/corpus/templates/clips/actions/list-meetings.ts +31 -3
  129. package/corpus/templates/clips/changelog/2026-07-08-after-finishing-a-desktop-recording-reopening-clips-restor.md +6 -0
  130. package/corpus/templates/clips/changelog/2026-07-08-clips-now-pops-up-to-take-notes-when-you-join-an-adhoc-zoom-.md +6 -0
  131. package/corpus/templates/clips/changelog/2026-07-08-desktop-clip-recordings-are-louder-after-capture-mic-only.md +6 -0
  132. package/corpus/templates/clips/changelog/2026-07-08-desktop-meeting-notes-stay-active-from-tray-and-shortcut-controls.md +6 -0
  133. package/corpus/templates/clips/changelog/2026-07-08-desktop-meeting-popovers-can-start-notes-and-join-the-meetin.md +6 -0
  134. package/corpus/templates/clips/changelog/2026-07-08-meeting-notes-keep-their-recording-state-when-the-desktop.md +6 -0
  135. package/corpus/templates/clips/changelog/2026-07-08-meeting-reminder-dropdowns-now-expand-fully-instead-of-being.md +6 -0
  136. package/corpus/templates/clips/changelog/2026-07-08-meeting-reminders-now-appear-1-minute-before-start-stay-unti.md +6 -0
  137. package/corpus/templates/clips/changelog/2026-07-08-the-desktop-meetings-panel-has-room-for-its-drop-shadow-and-.md +6 -0
  138. package/corpus/templates/clips/chrome-extension/package.json +2 -2
  139. package/corpus/templates/clips/desktop/package.json +2 -2
  140. package/corpus/templates/clips/desktop/src/app.tsx +214 -37
  141. package/corpus/templates/clips/desktop/src/hooks/useMeetingTranscription.ts +30 -6
  142. package/corpus/templates/clips/desktop/src/lib/meeting-notification-timing.ts +83 -0
  143. package/corpus/templates/clips/desktop/src/lib/native-recording-warm.ts +21 -0
  144. package/corpus/templates/clips/desktop/src/lib/recorder.ts +98 -20
  145. package/corpus/templates/clips/desktop/src/overlays/meeting-notification.tsx +197 -50
  146. package/corpus/templates/clips/desktop/src/styles.css +150 -53
  147. package/corpus/templates/clips/desktop/src-tauri/src/adhoc_meetings_watcher.rs +362 -0
  148. package/corpus/templates/clips/desktop/src-tauri/src/clips/mod.rs +41 -7
  149. package/corpus/templates/clips/desktop/src-tauri/src/lib.rs +9 -2
  150. package/corpus/templates/clips/desktop/src-tauri/src/meetings_watcher.rs +89 -11
  151. package/corpus/templates/clips/desktop/src-tauri/src/native_screen.rs +71 -17
  152. package/corpus/templates/clips/desktop/src-tauri/src/native_speech.rs +36 -1
  153. package/corpus/templates/clips/desktop/src-tauri/src/notifications.rs +62 -10
  154. package/corpus/templates/clips/desktop/src-tauri/src/shortcuts.rs +3 -3
  155. package/corpus/templates/clips/desktop/src-tauri/src/silence_detector.rs +1 -25
  156. package/corpus/templates/clips/desktop/src-tauri/src/state.rs +5 -0
  157. package/corpus/templates/clips/desktop/src-tauri/src/tray.rs +18 -6
  158. package/corpus/templates/clips/desktop/src-tauri/src/util.rs +36 -1
  159. package/corpus/templates/clips/package.json +2 -2
  160. package/corpus/templates/content/.agents/skills/self-modifying-code/SKILL.md +20 -2
  161. package/corpus/templates/content/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
  162. package/corpus/templates/content/actions/_batch-utils.ts +8 -0
  163. package/corpus/templates/content/actions/_builder-cms-read-client.ts +7 -5
  164. package/corpus/templates/content/actions/_content-database-personal-view.ts +80 -0
  165. package/corpus/templates/content/actions/_database-source-utils.ts +776 -319
  166. package/corpus/templates/content/actions/_database-utils.ts +6 -1
  167. package/corpus/templates/content/actions/_property-utils.ts +168 -0
  168. package/corpus/templates/content/actions/add-content-database-source-field-property.ts +19 -12
  169. package/corpus/templates/content/actions/bind-content-database-source-field.ts +19 -12
  170. package/corpus/templates/content/actions/delete-document.ts +9 -13
  171. package/corpus/templates/content/actions/get-content-database-personal-view.ts +27 -0
  172. package/corpus/templates/content/actions/refresh-content-database-source.ts +5 -5
  173. package/corpus/templates/content/actions/update-content-database-personal-view.ts +31 -0
  174. package/corpus/templates/content/actions/update-content-database-view.ts +3 -0
  175. package/corpus/templates/content/app/components/editor/DocumentProperties.tsx +1 -1
  176. package/corpus/templates/content/app/components/editor/body-hydration.ts +35 -11
  177. package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +2631 -551
  178. package/corpus/templates/content/app/components/editor/database/filter-sort.ts +220 -18
  179. package/corpus/templates/content/app/hooks/use-content-database.ts +32 -0
  180. package/corpus/templates/content/app/hooks/use-documents.ts +8 -2
  181. package/corpus/templates/content/app/i18n/zh-TW.ts +16 -0
  182. package/corpus/templates/content/app/i18n-data.ts +154 -0
  183. package/corpus/templates/content/changelog/2026-07-08-builder-source-sync-now-completes-reliably-on-hosted-databases.md +6 -0
  184. package/corpus/templates/content/changelog/2026-07-08-database-filters-now-support-notion-style-reset-save-for-eve.md +6 -0
  185. package/corpus/templates/content/changelog/2026-07-08-date-filters-can-now-target-a-between-range.md +6 -0
  186. package/corpus/templates/content/changelog/2026-07-08-fixed-builder-database-sync-so-large-sources-continue-past-t.md +6 -0
  187. package/corpus/templates/content/package.json +2 -2
  188. package/corpus/templates/content/parity/matrix.md +1 -1
  189. package/corpus/templates/content/parity/matrix.ts +2 -0
  190. package/corpus/templates/content/server/plugins/db.ts +19 -0
  191. package/corpus/templates/content/shared/api.ts +24 -0
  192. package/corpus/templates/design/.agents/skills/self-modifying-code/SKILL.md +20 -2
  193. package/corpus/templates/design/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
  194. package/corpus/templates/design/package.json +2 -2
  195. package/corpus/templates/dispatch/.agents/skills/self-modifying-code/SKILL.md +20 -2
  196. package/corpus/templates/dispatch/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
  197. package/corpus/templates/dispatch/app/i18n/zh-TW.ts +34 -1
  198. package/corpus/templates/dispatch/app/i18n-data.ts +387 -10
  199. package/corpus/templates/dispatch/app/routes/automations.tsx +1 -0
  200. package/corpus/templates/dispatch/changelog/2026-07-08-added-an-automations-page-to-review-pause-and-create.md +6 -0
  201. package/corpus/templates/dispatch/changelog/2026-07-08-destinations-now-shows-outbound-delivery-queue-health.md +6 -0
  202. package/corpus/templates/dispatch/package.json +2 -2
  203. package/corpus/templates/forms/.agents/skills/self-modifying-code/SKILL.md +20 -2
  204. package/corpus/templates/forms/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
  205. package/corpus/templates/forms/package.json +2 -2
  206. package/corpus/templates/macros/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
  207. package/corpus/templates/macros/package.json +2 -2
  208. package/corpus/templates/mail/.agents/skills/self-modifying-code/SKILL.md +20 -2
  209. package/corpus/templates/mail/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
  210. package/corpus/templates/mail/app/hooks/use-emails.ts +56 -44
  211. package/corpus/templates/mail/app/lib/gmail-mutation-queue.ts +325 -0
  212. package/corpus/templates/mail/changelog/2026-07-08-rapid-archive-and-mark-read-now-batch-gmail-updates-so-the-i.md +6 -0
  213. package/corpus/templates/mail/package.json +2 -2
  214. package/corpus/templates/plan/.agents/skills/self-modifying-code/SKILL.md +20 -2
  215. package/corpus/templates/plan/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
  216. package/corpus/templates/plan/package.json +2 -2
  217. package/corpus/templates/slides/.agents/skills/self-modifying-code/SKILL.md +20 -2
  218. package/corpus/templates/slides/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
  219. package/corpus/templates/slides/app/components/editor/ImageDropPromptPopover.tsx +46 -29
  220. package/corpus/templates/slides/app/context/DeckContext.tsx +45 -18
  221. package/corpus/templates/slides/app/i18n/ar-SA.ts +4 -0
  222. package/corpus/templates/slides/app/i18n/de-DE.ts +4 -0
  223. package/corpus/templates/slides/app/i18n/en-US.ts +4 -0
  224. package/corpus/templates/slides/app/i18n/es-ES.ts +4 -0
  225. package/corpus/templates/slides/app/i18n/fr-FR.ts +4 -0
  226. package/corpus/templates/slides/app/i18n/hi-IN.ts +4 -0
  227. package/corpus/templates/slides/app/i18n/ja-JP.ts +4 -0
  228. package/corpus/templates/slides/app/i18n/ko-KR.ts +4 -0
  229. package/corpus/templates/slides/app/i18n/pt-BR.ts +4 -0
  230. package/corpus/templates/slides/app/i18n/zh-CN.ts +4 -0
  231. package/corpus/templates/slides/app/i18n/zh-TW.ts +4 -0
  232. package/corpus/templates/slides/app/lib/image-drop-to-agent.ts +115 -0
  233. package/corpus/templates/slides/app/pages/DeckEditor.tsx +22 -13
  234. package/corpus/templates/slides/changelog/2026-07-08-dropping-an-image-onto-a-slide-and-sending-it-to-the-agent-w.md +6 -0
  235. package/corpus/templates/slides/changelog/2026-07-08-undo-works-after-the-agent-edits-a-slide-so-you-can-reverse.md +6 -0
  236. package/corpus/templates/slides/package.json +2 -2
  237. package/corpus/templates/videos/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
  238. package/corpus/templates/voice/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
  239. package/dist/agent/production-agent.d.ts +5 -1
  240. package/dist/agent/production-agent.d.ts.map +1 -1
  241. package/dist/agent/production-agent.js +112 -42
  242. package/dist/agent/production-agent.js.map +1 -1
  243. package/dist/agent/run-manager.d.ts.map +1 -1
  244. package/dist/agent/run-manager.js +23 -2
  245. package/dist/agent/run-manager.js.map +1 -1
  246. package/dist/agent/run-store.js +2 -2
  247. package/dist/agent/run-store.js.map +1 -1
  248. package/dist/agent/tool-search.d.ts +2 -0
  249. package/dist/agent/tool-search.d.ts.map +1 -1
  250. package/dist/agent/tool-search.js +60 -4
  251. package/dist/agent/tool-search.js.map +1 -1
  252. package/dist/catalog.json +2 -2
  253. package/dist/cli/add.js +1 -1
  254. package/dist/cli/add.js.map +1 -1
  255. package/dist/cli/create.d.ts.map +1 -1
  256. package/dist/cli/create.js +8 -3
  257. package/dist/cli/create.js.map +1 -1
  258. package/dist/cli/index.js +24 -4
  259. package/dist/cli/index.js.map +1 -1
  260. package/dist/cli/upgrade.d.ts +100 -0
  261. package/dist/cli/upgrade.d.ts.map +1 -0
  262. package/dist/cli/upgrade.js +678 -0
  263. package/dist/cli/upgrade.js.map +1 -0
  264. package/dist/client/AssistantChat.js +1 -1
  265. package/dist/client/AssistantChat.js.map +1 -1
  266. package/dist/client/HighlightedCodeBlock.d.ts +2 -0
  267. package/dist/client/HighlightedCodeBlock.d.ts.map +1 -1
  268. package/dist/client/HighlightedCodeBlock.js +10 -1
  269. package/dist/client/HighlightedCodeBlock.js.map +1 -1
  270. package/dist/client/agent-chat-adapter.d.ts.map +1 -1
  271. package/dist/client/agent-chat-adapter.js +32 -4
  272. package/dist/client/agent-chat-adapter.js.map +1 -1
  273. package/dist/client/chat/message-components.d.ts.map +1 -1
  274. package/dist/client/chat/message-components.js +61 -8
  275. package/dist/client/chat/message-components.js.map +1 -1
  276. package/dist/client/chat/tool-call-display.d.ts +12 -0
  277. package/dist/client/chat/tool-call-display.d.ts.map +1 -1
  278. package/dist/client/chat/tool-call-display.js +136 -42
  279. package/dist/client/chat/tool-call-display.js.map +1 -1
  280. package/dist/client/code-agent-chat-adapter.js +2 -9
  281. package/dist/client/code-agent-chat-adapter.js.map +1 -1
  282. package/dist/client/extensions/ExtensionSlot.js +6 -0
  283. package/dist/client/extensions/ExtensionSlot.js.map +1 -1
  284. package/dist/client/settings/SettingsPanel.d.ts.map +1 -1
  285. package/dist/client/settings/SettingsPanel.js +52 -19
  286. package/dist/client/settings/SettingsPanel.js.map +1 -1
  287. package/dist/client/settings/SettingsSection.d.ts +19 -4
  288. package/dist/client/settings/SettingsSection.d.ts.map +1 -1
  289. package/dist/client/settings/SettingsSection.js +33 -4
  290. package/dist/client/settings/SettingsSection.js.map +1 -1
  291. package/dist/client/sse-event-processor.d.ts +9 -0
  292. package/dist/client/sse-event-processor.d.ts.map +1 -1
  293. package/dist/client/sse-event-processor.js +20 -2
  294. package/dist/client/sse-event-processor.js.map +1 -1
  295. package/dist/collab/routes.d.ts +1 -1
  296. package/dist/collab/struct-routes.d.ts +1 -1
  297. package/dist/extensions/actions.d.ts.map +1 -1
  298. package/dist/extensions/actions.js +85 -5
  299. package/dist/extensions/actions.js.map +1 -1
  300. package/dist/file-upload/actions/upload-image.d.ts +1 -1
  301. package/dist/jobs/scheduler.js +26 -1
  302. package/dist/jobs/scheduler.js.map +1 -1
  303. package/dist/localization/default-messages.d.ts +19 -0
  304. package/dist/localization/default-messages.d.ts.map +1 -1
  305. package/dist/localization/default-messages.js +19 -0
  306. package/dist/localization/default-messages.js.map +1 -1
  307. package/dist/notifications/channels.d.ts +8 -3
  308. package/dist/notifications/channels.d.ts.map +1 -1
  309. package/dist/notifications/channels.js +61 -22
  310. package/dist/notifications/channels.js.map +1 -1
  311. package/dist/notifications/registry.d.ts.map +1 -1
  312. package/dist/notifications/registry.js +14 -3
  313. package/dist/notifications/registry.js.map +1 -1
  314. package/dist/notifications/routes.d.ts +1 -1
  315. package/dist/notifications/types.d.ts +4 -1
  316. package/dist/notifications/types.d.ts.map +1 -1
  317. package/dist/notifications/types.js.map +1 -1
  318. package/dist/observability/routes.d.ts +1 -1
  319. package/dist/provider-api/corpus-jobs.d.ts +2 -2
  320. package/dist/resources/handlers.d.ts +3 -3
  321. package/dist/server/agent-chat-plugin.d.ts +5 -1
  322. package/dist/server/agent-chat-plugin.d.ts.map +1 -1
  323. package/dist/server/agent-chat-plugin.js +2 -2
  324. package/dist/server/agent-chat-plugin.js.map +1 -1
  325. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  326. package/dist/server/request-context.d.ts +7 -0
  327. package/dist/server/request-context.d.ts.map +1 -1
  328. package/dist/server/request-context.js.map +1 -1
  329. package/dist/styles/agent-conversation.css +1 -1
  330. package/dist/templates/default/.agents/skills/self-modifying-code/SKILL.md +20 -2
  331. package/dist/templates/default/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
  332. package/dist/templates/default/AGENTS.md +10 -3
  333. package/dist/templates/default/app/i18n/ar-SA.ts +21 -0
  334. package/dist/templates/default/app/i18n/de-DE.ts +21 -0
  335. package/dist/templates/default/app/i18n/en-US.ts +21 -0
  336. package/dist/templates/default/app/i18n/es-ES.ts +21 -0
  337. package/dist/templates/default/app/i18n/fr-FR.ts +21 -0
  338. package/dist/templates/default/app/i18n/hi-IN.ts +20 -0
  339. package/dist/templates/default/app/i18n/ja-JP.ts +21 -0
  340. package/dist/templates/default/app/i18n/ko-KR.ts +20 -0
  341. package/dist/templates/default/app/i18n/pt-BR.ts +21 -0
  342. package/dist/templates/default/app/i18n/zh-CN.ts +20 -0
  343. package/dist/templates/default/app/i18n/zh-TW.ts +19 -0
  344. package/dist/templates/default/package.json +4 -3
  345. package/dist/templates/headless/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
  346. package/dist/templates/headless/AGENTS.md +9 -3
  347. package/dist/templates/headless/package.json +4 -3
  348. package/dist/templates/workspace-core/.agents/skills/self-modifying-code/SKILL.md +20 -2
  349. package/dist/templates/workspace-core/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
  350. package/dist/templates/workspace-core/package.json +6 -6
  351. package/dist/templates/workspace-root/AGENTS.md +10 -4
  352. package/dist/templates/workspace-root/package.json +3 -2
  353. package/dist/triggers/dispatcher.js +27 -2
  354. package/dist/triggers/dispatcher.js.map +1 -1
  355. package/dist/vite/client.d.ts.map +1 -1
  356. package/dist/vite/client.js +70 -1
  357. package/dist/vite/client.js.map +1 -1
  358. package/docs/content/faq.mdx +17 -0
  359. package/docs/content/notifications.mdx +12 -6
  360. package/package.json +7 -6
  361. package/src/templates/default/.agents/skills/self-modifying-code/SKILL.md +20 -2
  362. package/src/templates/default/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
  363. package/src/templates/default/AGENTS.md +10 -3
  364. package/src/templates/default/app/i18n/ar-SA.ts +21 -0
  365. package/src/templates/default/app/i18n/de-DE.ts +21 -0
  366. package/src/templates/default/app/i18n/en-US.ts +21 -0
  367. package/src/templates/default/app/i18n/es-ES.ts +21 -0
  368. package/src/templates/default/app/i18n/fr-FR.ts +21 -0
  369. package/src/templates/default/app/i18n/hi-IN.ts +20 -0
  370. package/src/templates/default/app/i18n/ja-JP.ts +21 -0
  371. package/src/templates/default/app/i18n/ko-KR.ts +20 -0
  372. package/src/templates/default/app/i18n/pt-BR.ts +21 -0
  373. package/src/templates/default/app/i18n/zh-CN.ts +20 -0
  374. package/src/templates/default/app/i18n/zh-TW.ts +19 -0
  375. package/src/templates/default/package.json +4 -3
  376. package/src/templates/headless/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
  377. package/src/templates/headless/AGENTS.md +9 -3
  378. package/src/templates/headless/package.json +4 -3
  379. package/src/templates/workspace-core/.agents/skills/self-modifying-code/SKILL.md +20 -2
  380. package/src/templates/workspace-core/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
  381. package/src/templates/workspace-core/package.json +6 -6
  382. package/src/templates/workspace-root/AGENTS.md +10 -4
  383. 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, VoiceTargetBundle,
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 = 12.0;
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
- // A recording pill may be owned by meeting or voice flows; tear it down too.
973
- let _ = crate::recording_indicator::recording_pill_hide(app).await;
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(app: AppHandle, active: bool) -> Result<(), String> {
1891
- dlog!("[clips-tray] set_meeting_active active={}", active);
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, PopoverShownAt,
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 starting in the next 5 minutes we
6
- //! haven't already alerted on, we fire a native notification + the in-app
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
- /// Notify when a meeting starts within this many seconds (the reminder lead).
43
- const NOTIFY_LEAD_SECS: i64 = 300;
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
- let mut req = client
236
- .get(&url)
237
- .query(&[("view", "upcoming"), ("limit", limit.as_str())]);
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 now regardless of the lead window.
375
+ // Snooze elapsed — re-fire if still inside the hold window.
308
376
  g.snoozed_until.remove(&m.id);
309
- true
377
+ in_window
310
378
  }
311
- None => (0..=NOTIFY_LEAD_SECS).contains(&secs_until),
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 the centered-stereo downmix repair
526
- // for the mic+system L/R split. Defaults to false for recordings queued
527
- // before this field existed so their audio is left untouched.
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
- mic_captured_audio,
3939
- mic_captured_audio,
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
- mic_captured_audio,
4026
- mic_captured_audio,
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(downmix_audio, denoise_audio))
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
- .arg("-af")
4535
- .arg(audio_filter_chain(downmix_audio, denoise_audio));
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, AUDIO_LOUDNESS_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, true),
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!("{AUDIO_DOWNMIX_FILTER},{AUDIO_DENOISE_FILTER},{AUDIO_LOUDNESS_FILTER}")
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, AVAudioTime,
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,