@agent-native/core 0.97.0 → 0.98.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (1019) hide show
  1. package/corpus/README.md +2 -2
  2. package/corpus/core/CHANGELOG.md +56 -0
  3. package/corpus/core/docs/content/authentication.mdx +3 -3
  4. package/corpus/core/docs/content/doctor.mdx +170 -0
  5. package/corpus/core/docs/content/external-agents.mdx +8 -7
  6. package/corpus/core/docs/content/locales/ar-SA/authentication.mdx +2 -2
  7. package/corpus/core/docs/content/locales/ar-SA/external-agents.mdx +8 -7
  8. package/corpus/core/docs/content/locales/ar-SA/mcp-protocol.mdx +1 -1
  9. package/corpus/core/docs/content/locales/ar-SA/security.mdx +2 -0
  10. package/corpus/core/docs/content/locales/de-DE/authentication.mdx +1 -1
  11. package/corpus/core/docs/content/locales/de-DE/external-agents.mdx +8 -7
  12. package/corpus/core/docs/content/locales/de-DE/mcp-protocol.mdx +1 -1
  13. package/corpus/core/docs/content/locales/de-DE/security.mdx +2 -0
  14. package/corpus/core/docs/content/locales/es-ES/authentication.mdx +2 -2
  15. package/corpus/core/docs/content/locales/es-ES/external-agents.mdx +8 -7
  16. package/corpus/core/docs/content/locales/es-ES/mcp-protocol.mdx +1 -1
  17. package/corpus/core/docs/content/locales/es-ES/security.mdx +2 -0
  18. package/corpus/core/docs/content/locales/fr-FR/authentication.mdx +1 -1
  19. package/corpus/core/docs/content/locales/fr-FR/external-agents.mdx +8 -7
  20. package/corpus/core/docs/content/locales/fr-FR/mcp-protocol.mdx +1 -1
  21. package/corpus/core/docs/content/locales/fr-FR/security.mdx +2 -0
  22. package/corpus/core/docs/content/locales/hi-IN/authentication.mdx +2 -2
  23. package/corpus/core/docs/content/locales/hi-IN/external-agents.mdx +8 -7
  24. package/corpus/core/docs/content/locales/hi-IN/mcp-protocol.mdx +1 -1
  25. package/corpus/core/docs/content/locales/hi-IN/security.mdx +2 -0
  26. package/corpus/core/docs/content/locales/ja-JP/authentication.mdx +2 -2
  27. package/corpus/core/docs/content/locales/ja-JP/external-agents.mdx +8 -7
  28. package/corpus/core/docs/content/locales/ja-JP/mcp-protocol.mdx +1 -1
  29. package/corpus/core/docs/content/locales/ja-JP/security.mdx +2 -0
  30. package/corpus/core/docs/content/locales/ko-KR/authentication.mdx +2 -2
  31. package/corpus/core/docs/content/locales/ko-KR/external-agents.mdx +8 -7
  32. package/corpus/core/docs/content/locales/ko-KR/mcp-protocol.mdx +1 -1
  33. package/corpus/core/docs/content/locales/ko-KR/security.mdx +2 -0
  34. package/corpus/core/docs/content/locales/pt-BR/authentication.mdx +2 -2
  35. package/corpus/core/docs/content/locales/pt-BR/external-agents.mdx +8 -7
  36. package/corpus/core/docs/content/locales/pt-BR/mcp-protocol.mdx +1 -1
  37. package/corpus/core/docs/content/locales/pt-BR/security.mdx +2 -0
  38. package/corpus/core/docs/content/locales/zh-CN/authentication.mdx +2 -2
  39. package/corpus/core/docs/content/locales/zh-CN/external-agents.mdx +8 -7
  40. package/corpus/core/docs/content/locales/zh-CN/mcp-protocol.mdx +1 -1
  41. package/corpus/core/docs/content/locales/zh-CN/security.mdx +2 -0
  42. package/corpus/core/docs/content/locales/zh-TW/authentication.mdx +2 -2
  43. package/corpus/core/docs/content/locales/zh-TW/external-agents.mdx +8 -7
  44. package/corpus/core/docs/content/locales/zh-TW/mcp-protocol.mdx +1 -1
  45. package/corpus/core/docs/content/locales/zh-TW/security.mdx +2 -0
  46. package/corpus/core/docs/content/mcp-protocol.mdx +1 -1
  47. package/corpus/core/docs/content/security.mdx +2 -0
  48. package/corpus/core/package.json +6 -2
  49. package/corpus/core/scripts/finalize-build.mjs +63 -3
  50. package/corpus/core/scripts/materialize-source-corpus.mjs +101 -15
  51. package/corpus/core/src/action-change-marker.ts +8 -1
  52. package/corpus/core/src/action.ts +42 -0
  53. package/corpus/core/src/agent/engine/builder-engine.ts +3 -0
  54. package/corpus/core/src/agent/engine/context-directives-transform.ts +69 -0
  55. package/corpus/core/src/agent/engine/continuation-dispatch-retry.ts +168 -0
  56. package/corpus/core/src/agent/engine/tool-call-journal-seed.ts +85 -0
  57. package/corpus/core/src/agent/production-agent.ts +129 -244
  58. package/corpus/core/src/changelog/parse.ts +8 -3
  59. package/corpus/core/src/cli/doctor.ts +9 -3
  60. package/corpus/core/src/cli/plan-local.ts +10 -2
  61. package/corpus/core/src/cli/skills-content/assets-skill.ts +83 -0
  62. package/corpus/core/src/cli/skills-content/canvas.ts +153 -0
  63. package/corpus/core/src/cli/skills-content/connection.ts +52 -0
  64. package/corpus/core/src/cli/skills-content/content-skill.ts +109 -0
  65. package/corpus/core/src/cli/skills-content/design-exploration-skill.ts +118 -0
  66. package/corpus/core/src/cli/skills-content/design-visual-edit-skill.ts +228 -0
  67. package/corpus/core/src/cli/skills-content/document-quality.ts +196 -0
  68. package/corpus/core/src/cli/skills-content/exemplar.ts +69 -0
  69. package/corpus/core/src/cli/skills-content/help.ts +77 -0
  70. package/corpus/core/src/cli/skills-content/index.ts +22 -0
  71. package/corpus/core/src/cli/skills-content/local-files.ts +95 -0
  72. package/corpus/core/src/cli/skills-content/plan-setup-auth.ts +80 -0
  73. package/corpus/core/src/cli/skills-content/visual-plan-skill.ts +470 -0
  74. package/corpus/core/src/cli/skills-content/visual-recap-skill.ts +547 -0
  75. package/corpus/core/src/cli/skills-content/visualize-repo-skill.ts +93 -0
  76. package/corpus/core/src/cli/skills-content/wireframe.ts +349 -0
  77. package/corpus/core/src/cli/skills.ts +27 -2733
  78. package/corpus/core/src/client/AgentPanel.tsx +8 -8
  79. package/corpus/core/src/client/AssistantChat.tsx +83 -39
  80. package/corpus/core/src/client/CommandMenu.tsx +16 -5
  81. package/corpus/core/src/client/FeedbackButton.tsx +2 -2
  82. package/corpus/core/src/client/analytics.ts +29 -0
  83. package/corpus/core/src/client/app-providers.tsx +18 -5
  84. package/corpus/core/src/client/blocks/library/wireframe-kit.tsx +16 -2
  85. package/corpus/core/src/client/chat/tool-call-display.tsx +3 -3
  86. package/corpus/core/src/client/chat-model-groups.ts +28 -0
  87. package/corpus/core/src/client/composer/PastedTextChip.tsx +1 -1
  88. package/corpus/core/src/client/composer/RealtimeVoiceMode.tsx +523 -68
  89. package/corpus/core/src/client/composer/TiptapComposer.tsx +47 -30
  90. package/corpus/core/src/client/composer/VoiceButton.tsx +22 -5
  91. package/corpus/core/src/client/composer/index.ts +5 -0
  92. package/corpus/core/src/client/composer/useRealtimeVoiceMode.tsx +1271 -35
  93. package/corpus/core/src/client/context-xray/ContextXRayPanel.tsx +5 -2
  94. package/corpus/core/src/client/extensions/ExtensionEditor.tsx +37 -4
  95. package/corpus/core/src/client/extensions/ExtensionQueryErrorState.tsx +47 -0
  96. package/corpus/core/src/client/extensions/ExtensionSlot.tsx +39 -10
  97. package/corpus/core/src/client/extensions/ExtensionViewer.tsx +44 -6
  98. package/corpus/core/src/client/extensions/ExtensionsListPage.tsx +12 -3
  99. package/corpus/core/src/client/extensions/ExtensionsSidebarSection.tsx +49 -11
  100. package/corpus/core/src/client/index.ts +5 -13
  101. package/corpus/core/src/client/progress/RunsTray.tsx +23 -4
  102. package/corpus/core/src/client/settings/SecretsSection.tsx +387 -266
  103. package/corpus/core/src/client/settings/SettingsPanel.tsx +117 -38
  104. package/corpus/core/src/client/settings/SettingsTabsPage.tsx +7 -0
  105. package/corpus/core/src/client/settings/useBuilderStatus.ts +6 -0
  106. package/corpus/core/src/client/tool-cells/EditCell.tsx +19 -16
  107. package/corpus/core/src/client/tool-cells/FilesChangedSummary.tsx +26 -23
  108. package/corpus/core/src/client/tool-cells/WriteCell.tsx +19 -16
  109. package/corpus/core/src/client/use-action.ts +12 -0
  110. package/corpus/core/src/client/use-agent-chat-home-handoff.ts +8 -1
  111. package/corpus/core/src/client/use-agent-engine-configured.ts +17 -3
  112. package/corpus/core/src/client/use-db-sync.ts +84 -34
  113. package/corpus/core/src/client/use-pausing-interval.ts +14 -4
  114. package/corpus/core/src/client/visual-style-controls.tsx +1 -1
  115. package/corpus/core/src/deploy/build.ts +36 -14
  116. package/corpus/core/src/integrations/google-docs-poller.ts +23 -2
  117. package/corpus/core/src/integrations/plugin.ts +10 -0
  118. package/corpus/core/src/integrations/scope-store.ts +1 -1
  119. package/corpus/core/src/integrations/usage-budget-store.ts +1 -1
  120. package/corpus/core/src/integrations/webhook-handler.ts +27 -2
  121. package/corpus/core/src/jobs/scheduler.ts +28 -2
  122. package/corpus/core/src/localization/default-messages.ts +51 -1
  123. package/corpus/core/src/mcp/oauth-token.ts +6 -1
  124. package/corpus/core/src/mcp-client/workspace-servers.ts +4 -4
  125. package/corpus/core/src/observability/routes.ts +36 -0
  126. package/corpus/core/src/observability/sentiment.ts +261 -0
  127. package/corpus/core/src/observability/store.ts +23 -0
  128. package/corpus/core/src/observability/traces.ts +49 -63
  129. package/corpus/core/src/observability/tracking-identity.ts +55 -0
  130. package/corpus/core/src/observability/types.ts +13 -0
  131. package/corpus/core/src/secrets/crypto.ts +28 -8
  132. package/corpus/core/src/secrets/storage.ts +67 -3
  133. package/corpus/core/src/server/action-change.ts +5 -1
  134. package/corpus/core/src/server/action-routes.ts +8 -0
  135. package/corpus/core/src/server/agent-chat/action-filters-a2a.ts +199 -0
  136. package/corpus/core/src/server/agent-chat/browser-team-tools.ts +510 -0
  137. package/corpus/core/src/server/agent-chat/context-tools.ts +595 -0
  138. package/corpus/core/src/server/agent-chat/framework-prompts.ts +470 -0
  139. package/corpus/core/src/server/agent-chat/lazy-fs.ts +11 -0
  140. package/corpus/core/src/server/agent-chat/mcp-glue.ts +91 -0
  141. package/corpus/core/src/server/agent-chat/plugin-options.ts +350 -0
  142. package/corpus/core/src/server/agent-chat/process-run-failure.ts +62 -0
  143. package/corpus/core/src/server/agent-chat/prompt-resources.ts +688 -0
  144. package/corpus/core/src/server/agent-chat/recurring-jobs-runtime.ts +80 -0
  145. package/corpus/core/src/server/agent-chat/request-surface.ts +71 -0
  146. package/corpus/core/src/server/agent-chat/run-code-tools.ts +67 -0
  147. package/corpus/core/src/server/agent-chat/script-entries.ts +779 -0
  148. package/corpus/core/src/server/agent-chat/shared-thread.ts +301 -0
  149. package/corpus/core/src/server/agent-chat/skill-frontmatter.ts +41 -0
  150. package/corpus/core/src/server/agent-chat-plugin.ts +364 -4182
  151. package/corpus/core/src/server/agent-teams.ts +26 -3
  152. package/corpus/core/src/server/auth.ts +55 -57
  153. package/corpus/core/src/server/credential-provider.ts +22 -25
  154. package/corpus/core/src/server/edge.ts +1 -0
  155. package/corpus/core/src/server/onboarding-html.ts +1 -16
  156. package/corpus/core/src/server/poll.ts +1 -0
  157. package/corpus/core/src/server/realtime-voice.ts +335 -14
  158. package/corpus/core/src/server/request-origin.ts +6 -2
  159. package/corpus/core/src/server/ssr-handler.ts +55 -2
  160. package/corpus/core/src/sharing/access.ts +155 -11
  161. package/corpus/core/src/sharing/registry.ts +7 -0
  162. package/corpus/core/src/styles/agent-native.css +17 -16
  163. package/corpus/core/src/templates/default/.agents/skills/frontend-design/SKILL.md +1 -1
  164. package/corpus/core/src/templates/default/.agents/skills/real-time-sync/SKILL.md +2 -2
  165. package/corpus/core/src/templates/default/app/i18n/ar-SA.ts +6 -0
  166. package/corpus/core/src/templates/default/app/i18n/de-DE.ts +6 -0
  167. package/corpus/core/src/templates/default/app/i18n/en-US.ts +6 -0
  168. package/corpus/core/src/templates/default/app/i18n/es-ES.ts +6 -0
  169. package/corpus/core/src/templates/default/app/i18n/fr-FR.ts +6 -0
  170. package/corpus/core/src/templates/default/app/i18n/hi-IN.ts +6 -0
  171. package/corpus/core/src/templates/default/app/i18n/ja-JP.ts +6 -0
  172. package/corpus/core/src/templates/default/app/i18n/ko-KR.ts +6 -0
  173. package/corpus/core/src/templates/default/app/i18n/pt-BR.ts +6 -0
  174. package/corpus/core/src/templates/default/app/i18n/zh-CN.ts +6 -0
  175. package/corpus/core/src/templates/default/app/i18n/zh-TW.ts +6 -0
  176. package/corpus/core/src/templates/workspace-core/.agents/skills/authentication/SKILL.md +27 -29
  177. package/corpus/core/src/templates/workspace-core/.agents/skills/external-agents/SKILL.md +2 -1
  178. package/corpus/core/src/templates/workspace-core/.agents/skills/frontend-design/SKILL.md +1 -1
  179. package/corpus/core/src/templates/workspace-core/.agents/skills/observability/SKILL.md +32 -0
  180. package/corpus/core/src/templates/workspace-core/.agents/skills/real-time-sync/SKILL.md +2 -2
  181. package/corpus/core/src/templates/workspace-core/.agents/skills/secrets/SKILL.md +7 -3
  182. package/corpus/core/src/templates/workspace-core/.agents/skills/voice-transcription/SKILL.md +39 -9
  183. package/corpus/core/src/templates/workspace-core/.agents/skills/writing-agent-instructions/SKILL.md +25 -0
  184. package/corpus/core/src/templates/workspace-core/src/server/index.ts +4 -2
  185. package/corpus/core/src/testing.ts +26 -0
  186. package/corpus/core/src/triggers/dispatcher.ts +23 -2
  187. package/corpus/core/src/vite/client.ts +135 -3
  188. package/corpus/core/tsconfig.cli.json +1 -1
  189. package/corpus/core/tsconfig.json +5 -1
  190. package/corpus/templates/analytics/.agents/skills/bigquery/SKILL.md +2 -2
  191. package/corpus/templates/analytics/.agents/skills/frontend-design/SKILL.md +1 -1
  192. package/corpus/templates/analytics/.agents/skills/real-time-sync/SKILL.md +2 -2
  193. package/corpus/templates/analytics/.agents/skills/session-replay/SKILL.md +10 -5
  194. package/corpus/templates/analytics/AGENTS.md +15 -6
  195. package/corpus/templates/analytics/actions/install-dashboard-template.ts +79 -43
  196. package/corpus/templates/analytics/actions/list-explorer-dashboards.ts +3 -3
  197. package/corpus/templates/analytics/actions/list-sql-dashboards.ts +8 -3
  198. package/corpus/templates/analytics/actions/navigate.ts +59 -5
  199. package/corpus/templates/analytics/actions/rename-analysis.ts +8 -2
  200. package/corpus/templates/analytics/actions/rename-dashboard.ts +8 -7
  201. package/corpus/templates/analytics/actions/view-screen.ts +7 -29
  202. package/corpus/templates/analytics/app/components/ProviderCorpusJobNotifier.tsx +20 -3
  203. package/corpus/templates/analytics/app/components/ResourceLoadError.tsx +42 -0
  204. package/corpus/templates/analytics/app/components/dashboard/SqlChart.tsx +2 -1
  205. package/corpus/templates/analytics/app/components/layout/CommandPalette.tsx +85 -70
  206. package/corpus/templates/analytics/app/components/layout/Header.tsx +1 -1
  207. package/corpus/templates/analytics/app/components/layout/Sidebar.tsx +128 -95
  208. package/corpus/templates/analytics/app/components/layout/SidebarLoadError.tsx +24 -0
  209. package/corpus/templates/analytics/app/global.css +19 -20
  210. package/corpus/templates/analytics/app/hooks/use-dashboard-views.ts +15 -5
  211. package/corpus/templates/analytics/app/i18n/zh-TW.ts +4 -0
  212. package/corpus/templates/analytics/app/i18n-data.ts +40 -0
  213. package/corpus/templates/analytics/app/lib/provider-corpus-job-sync.ts +26 -0
  214. package/corpus/templates/analytics/app/pages/Settings.tsx +41 -343
  215. package/corpus/templates/analytics/app/pages/adhoc/explorer/components/SqlPreview.tsx +36 -20
  216. package/corpus/templates/analytics/app/pages/adhoc/explorer/index.tsx +34 -20
  217. package/corpus/templates/analytics/app/pages/adhoc/explorer/use-explorer-config.ts +42 -41
  218. package/corpus/templates/analytics/app/pages/adhoc/explorer-dashboard/index.tsx +43 -36
  219. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/ViewsMenu.tsx +11 -2
  220. package/corpus/templates/analytics/app/pages/monitoring/ErrorsPanel.tsx +38 -7
  221. package/corpus/templates/analytics/app/pages/monitoring/MonitoringPage.tsx +1 -1
  222. package/corpus/templates/analytics/app/pages/monitoring/errors/i18n.ts +3 -0
  223. package/corpus/templates/analytics/app/pages/sessions/SessionDetailPage.tsx +333 -272
  224. package/corpus/templates/analytics/app/pages/sessions/SessionDevToolsPanel.tsx +72 -17
  225. package/corpus/templates/analytics/app/pages/sessions/SessionsPage.tsx +297 -108
  226. package/corpus/templates/analytics/app/root.tsx +40 -8
  227. package/corpus/templates/analytics/app/routes/_index.tsx +14 -2
  228. package/corpus/templates/analytics/changelog/2026-06-24-first-party-signup-and-pageview-panels-now-honor-dashboard-filters.md +2 -1
  229. package/corpus/templates/analytics/changelog/2026-06-25-referrer-dashboard-panels-show-external-domains.md +2 -1
  230. package/corpus/templates/analytics/changelog/2026-07-03-agent-llm-observability-dashboard.md +1 -1
  231. package/corpus/templates/analytics/changelog/2026-07-06-mobile-chat-header-is-more-compact-and-the-menu-button-opens-navigation.md +1 -0
  232. package/corpus/templates/analytics/changelog/2026-07-08-dashboard-history-and-chat-context.md +1 -0
  233. package/corpus/templates/analytics/changelog/2026-07-08-settings-are-cleaner-searchable-and-alerts-have-their-own-tab.md +1 -0
  234. package/corpus/templates/analytics/changelog/2026-07-08-sidebar-sections-stay-collapsed-until-opened.md +1 -0
  235. package/corpus/templates/analytics/changelog/2026-07-11-agent-feedback-sentiment-dashboard.md +6 -0
  236. package/corpus/templates/analytics/changelog/2026-07-11-analytics-chats-now-start-faster-by-loading-metric-definitio.md +6 -0
  237. package/corpus/templates/analytics/changelog/2026-07-11-analytics-no-longer-repeatedly-restarts-background-requests-.md +6 -0
  238. package/corpus/templates/analytics/changelog/2026-07-11-analytics-only-asks-for-replay-storage-when-you-enable-sessi.md +6 -0
  239. package/corpus/templates/analytics/changelog/2026-07-11-charts-now-show-their-loading-skeleton-while-refreshing.md +6 -0
  240. package/corpus/templates/analytics/changelog/2026-07-11-command-and-sql-previews-now-respond-without-lag.md +6 -0
  241. package/corpus/templates/analytics/changelog/2026-07-11-command-search-now-reports-loading-failures-and-lets-you-ret.md +6 -0
  242. package/corpus/templates/analytics/changelog/2026-07-11-dashboard-navigation-now-loads-faster-and-reports-request-fa.md +6 -0
  243. package/corpus/templates/analytics/changelog/2026-07-11-inferred-message-sentiment-dashboard.md +6 -0
  244. package/corpus/templates/analytics/changelog/2026-07-11-microphone-settings-from-realtime-voice-mode-now-open-the-vo.md +6 -0
  245. package/corpus/templates/analytics/changelog/2026-07-11-more-dashboard-edit-paths-are-now-safe-against-simultaneous-.md +6 -0
  246. package/corpus/templates/analytics/changelog/2026-07-11-realtime-voice-now-shows-a-clear-connection-indicator-comple.md +6 -0
  247. package/corpus/templates/analytics/changelog/2026-07-11-renaming-a-dashboard-is-now-safe-against-simultaneous-agent-.md +6 -0
  248. package/corpus/templates/analytics/changelog/2026-07-11-renaming-an-analysis-is-now-safe-against-simultaneous-edits.md +6 -0
  249. package/corpus/templates/analytics/changelog/2026-07-11-replay-event-counts-stay-hidden-until-recordings-finish-load.md +6 -0
  250. package/corpus/templates/analytics/changelog/2026-07-11-replay-storage-key-mismatches-show-actionable-setup-guidance.md +6 -0
  251. package/corpus/templates/analytics/changelog/2026-07-11-replay-timelines-group-continuous-scrolling-and-dev-tools-co.md +6 -0
  252. package/corpus/templates/analytics/changelog/2026-07-11-session-filters-now-stay-compact-in-a-settings-menu-at-mediu.md +6 -0
  253. package/corpus/templates/analytics/changelog/2026-07-11-session-replay-console-errors-now-link-to-matching-issues-or.md +6 -0
  254. package/corpus/templates/analytics/changelog/2026-07-11-session-replays-load-large-recordings-faster-and-surface-fai.md +6 -0
  255. package/corpus/templates/analytics/changelog/2026-07-11-session-replays-preserve-recorded-styles-and-viewport-layout.md +6 -0
  256. package/corpus/templates/analytics/changelog/2026-07-11-session-replays-stay-centered-instead-of-clipping-wide-recor.md +6 -0
  257. package/corpus/templates/analytics/changelog/2026-07-11-workspace-mounted-analytics-now-opens-ask-reliably-and-route.md +6 -0
  258. package/corpus/templates/analytics/docs/schemas/first-party-analytics.md +37 -3
  259. package/corpus/templates/analytics/package.json +1 -1
  260. package/corpus/templates/analytics/seeds/dashboards/agent-native-templates-first-party.json +37 -1
  261. package/corpus/templates/analytics/server/handlers/session-replay.ts +57 -0
  262. package/corpus/templates/analytics/server/lib/dashboard-catalog.ts +1 -24
  263. package/corpus/templates/analytics/server/lib/dashboard-seeds.ts +0 -5
  264. package/corpus/templates/analytics/server/lib/dashboards-store.ts +289 -20
  265. package/corpus/templates/analytics/server/lib/data-dictionary-context.ts +106 -34
  266. package/corpus/templates/analytics/server/lib/demo-dashboards.ts +20 -4
  267. package/corpus/templates/analytics/server/lib/session-replay-agent-context.ts +12 -0
  268. package/corpus/templates/analytics/server/lib/session-replay.ts +208 -2
  269. package/corpus/templates/analytics/server/plugins/agent-chat.ts +11 -39
  270. package/corpus/templates/analytics/server/plugins/onboarding.ts +7 -79
  271. package/corpus/templates/analytics/server/routes/api/session-replay/recordings/[recordingId]/chunks.get.ts +1 -0
  272. package/corpus/templates/assets/.agents/skills/frontend-design/SKILL.md +1 -1
  273. package/corpus/templates/assets/.agents/skills/real-time-sync/SKILL.md +2 -2
  274. package/corpus/templates/assets/app/components/create/RecentDraftsSection.tsx +30 -4
  275. package/corpus/templates/assets/app/global.css +4 -4
  276. package/corpus/templates/assets/app/routes/brand-kits.$id.tsx +0 -1
  277. package/corpus/templates/assets/app/routes/library.tsx +49 -6
  278. package/corpus/templates/assets/changelog/2026-07-11-library-load-failures-now-show-a-retry-action.md +6 -0
  279. package/corpus/templates/brain/.agents/skills/frontend-design/SKILL.md +1 -1
  280. package/corpus/templates/brain/.agents/skills/real-time-sync/SKILL.md +2 -2
  281. package/corpus/templates/brain/app/global.css +5 -5
  282. package/corpus/templates/brain/app/root.tsx +0 -3
  283. package/corpus/templates/calendar/.agents/skills/frontend-design/SKILL.md +1 -1
  284. package/corpus/templates/calendar/.agents/skills/real-time-sync/SKILL.md +2 -2
  285. package/corpus/templates/calendar/.env.example +5 -0
  286. package/corpus/templates/calendar/app/components/calendar/EventCard.tsx +2 -2
  287. package/corpus/templates/calendar/app/i18n/zh-TW.ts +2 -0
  288. package/corpus/templates/calendar/app/i18n-data.ts +20 -0
  289. package/corpus/templates/calendar/app/pages/BookingLinksPage.tsx +37 -1
  290. package/corpus/templates/calendar/app/pages/BookingsList.tsx +20 -3
  291. package/corpus/templates/calendar/app/root.tsx +1 -0
  292. package/corpus/templates/calendar/changelog/2026-07-11-booking-load-failures-now-show-a-retry-action.md +6 -0
  293. package/corpus/templates/calendar/changelog/2026-07-11-event-cards-give-subtle-press-feedback-and-smoother-hover.md +6 -0
  294. package/corpus/templates/chat/.agents/skills/frontend-design/SKILL.md +1 -1
  295. package/corpus/templates/chat/.agents/skills/real-time-sync/SKILL.md +2 -2
  296. package/corpus/templates/clips/.agents/skills/frontend-design/SKILL.md +1 -1
  297. package/corpus/templates/clips/actions/decline-invite.ts +8 -0
  298. package/corpus/templates/clips/app/components/library/folder-tree.tsx +209 -187
  299. package/corpus/templates/clips/app/components/meetings/meeting-card.tsx +2 -2
  300. package/corpus/templates/clips/app/components/player/reactions-tray.tsx +17 -5
  301. package/corpus/templates/clips/app/components/player/scrubber.tsx +5 -3
  302. package/corpus/templates/clips/app/components/recorder/pre-record-panel.tsx +2 -2
  303. package/corpus/templates/clips/app/components/workspace/insights-hub.tsx +119 -95
  304. package/corpus/templates/clips/app/global.css +45 -0
  305. package/corpus/templates/clips/app/hooks/use-auto-title.ts +74 -8
  306. package/corpus/templates/clips/app/hooks/use-library.ts +12 -25
  307. package/corpus/templates/clips/app/root.tsx +4 -0
  308. package/corpus/templates/clips/app/routes/_app.notifications.tsx +23 -2
  309. package/corpus/templates/clips/app/routes/_app.settings._index.tsx +20 -22
  310. package/corpus/templates/clips/app/routes/_app.trash.tsx +24 -2
  311. package/corpus/templates/clips/changelog/2026-07-11-controls-and-folders-now-move-more-smoothly.md +6 -0
  312. package/corpus/templates/clips/changelog/2026-07-11-declining-an-invitation-now-requires-being-its-recipient.md +6 -0
  313. package/corpus/templates/clips/changelog/2026-07-11-generated-titles-no-longer-cause-continuous-background-refreshes.md +6 -0
  314. package/corpus/templates/clips/changelog/2026-07-11-recording-overlays-are-lighter-weight-and-no-longer-cause-pa.md +6 -0
  315. package/corpus/templates/clips/changelog/2026-07-11-s3-storage-setup-stays-collapsed-until-you-choose-it-as-the-.md +6 -0
  316. package/corpus/templates/clips/changelog/2026-07-11-workspace-load-failures-now-show-a-retry-action.md +6 -0
  317. package/corpus/templates/clips/chrome-extension/src/overlay.css +34 -5
  318. package/corpus/templates/clips/chrome-extension/src/styles.css +2 -2
  319. package/corpus/templates/clips/desktop/src/styles.css +71 -26
  320. package/corpus/templates/content/.agents/skills/frontend-design/SKILL.md +1 -1
  321. package/corpus/templates/content/.agents/skills/real-time-sync/SKILL.md +2 -2
  322. package/corpus/templates/content/actions/move-document.ts +34 -18
  323. package/corpus/templates/content/app/components/QueryErrorState.tsx +39 -0
  324. package/corpus/templates/content/app/components/editor/LinkHoverPreview.tsx +1 -1
  325. package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +225 -1435
  326. package/corpus/templates/content/app/components/editor/database/GalleryView.tsx +368 -0
  327. package/corpus/templates/content/app/components/editor/database/ListView.tsx +353 -0
  328. package/corpus/templates/content/app/components/editor/database/TimelineView.tsx +580 -0
  329. package/corpus/templates/content/app/components/sidebar/DocumentSidebar.tsx +11 -2
  330. package/corpus/templates/content/app/global.css +19 -19
  331. package/corpus/templates/content/app/hooks/use-comments.ts +0 -1
  332. package/corpus/templates/content/app/root.tsx +2 -2
  333. package/corpus/templates/content/app/routes/_app._index.tsx +11 -1
  334. package/corpus/templates/content/changelog/2026-07-11-comments-now-update-from-live-collaboration-events-without-c.md +6 -0
  335. package/corpus/templates/content/changelog/2026-07-11-document-load-failures-now-show-a-retry-action.md +6 -0
  336. package/corpus/templates/content/changelog/2026-07-11-editor-toggle-feedback-now-feels-more-responsive.md +6 -0
  337. package/corpus/templates/content/changelog/2026-07-11-moving-documents-at-the-same-time-no-longer-scrambles-their-.md +6 -0
  338. package/corpus/templates/content/changelog/2026-07-11-public-document-chats-now-start-faster-while-keeping-full-do.md +6 -0
  339. package/corpus/templates/content/changelog/2026-07-11-reopening-a-comment-now-uses-consistent-permissions-everywhe.md +6 -0
  340. package/corpus/templates/content/changelog/2026-07-11-sidebar-resizing-now-tracks-your-cursor-instantly-instead-of.md +6 -0
  341. package/corpus/templates/content/server/lib/public-documents.ts +47 -10
  342. package/corpus/templates/content/server/routes/api/comments/[id].patch.ts +24 -24
  343. package/corpus/templates/design/.agents/skills/frontend-design/SKILL.md +1 -1
  344. package/corpus/templates/design/app/components/QueryErrorState.tsx +24 -0
  345. package/corpus/templates/design/app/components/design/CanvasContextMenu.tsx +1 -1
  346. package/corpus/templates/design/app/components/design/DesignImportPanel.tsx +5 -2
  347. package/corpus/templates/design/app/components/design/EditPanel.tsx +326 -319
  348. package/corpus/templates/design/app/components/design/LayersPanel.tsx +198 -193
  349. package/corpus/templates/design/app/components/design/edit-panel/panel-primitives.tsx +11 -4
  350. package/corpus/templates/design/app/components/design/inspector/DesignColorPicker.tsx +1 -1
  351. package/corpus/templates/design/app/components/layout/Header.tsx +1 -1
  352. package/corpus/templates/design/app/components/layout/Layout.tsx +1 -1
  353. package/corpus/templates/design/app/pages/DesignSystems.tsx +7 -1
  354. package/corpus/templates/design/app/pages/Index.tsx +14 -2
  355. package/corpus/templates/design/app/pages/Present.tsx +16 -3
  356. package/corpus/templates/design/app/pages/Templates.tsx +10 -4
  357. package/corpus/templates/design/app/pages/design-editor/canvas-primitive-insert.ts +484 -0
  358. package/corpus/templates/design/app/pages/design-editor/clone-and-pen-edit.ts +378 -0
  359. package/corpus/templates/design/app/pages/design-editor/design-data-geometry-utils.ts +138 -0
  360. package/corpus/templates/design/app/pages/design-editor/dom-utils.ts +15 -0
  361. package/corpus/templates/design/app/pages/design-editor/generation-prompt-directives.ts +180 -0
  362. package/corpus/templates/design/app/pages/design-editor/html-layer-positioning.ts +226 -0
  363. package/corpus/templates/design/app/pages/design-editor/png-export-render.ts +348 -0
  364. package/corpus/templates/design/app/pages/design-editor/screen-command-utils.ts +160 -0
  365. package/corpus/templates/design/app/pages/design-editor/text-edit-utils.ts +238 -0
  366. package/corpus/templates/design/app/pages/design-editor/tool-state.ts +23 -1
  367. package/corpus/templates/design/app/pages/design-editor/types.ts +25 -0
  368. package/corpus/templates/design/app/root.tsx +10 -1
  369. package/corpus/templates/design/changelog/2026-07-11-canvas-context-menus-now-open-without-motion-jitter.md +6 -0
  370. package/corpus/templates/design/changelog/2026-07-11-design-load-failures-now-show-a-retry-action.md +6 -0
  371. package/corpus/templates/design/changelog/2026-07-11-inspector-tooltips-appear-instantly-as-you-sweep-across-icon.md +6 -0
  372. package/corpus/templates/dispatch/.agents/skills/frontend-design/SKILL.md +1 -1
  373. package/corpus/templates/dispatch/app/i18n/zh-TW.ts +10 -0
  374. package/corpus/templates/dispatch/app/i18n-data.ts +123 -0
  375. package/corpus/templates/dispatch/app/routes/integrations.tsx +163 -144
  376. package/corpus/templates/dispatch/changelog/2026-07-11-dispatch-mcp-connections-now-start-with-safer-app-access-and.md +6 -0
  377. package/corpus/templates/dispatch/changelog/2026-07-11-overview-and-chat-now-share-a-cleaner-more-stable-flow.md +6 -0
  378. package/corpus/templates/dispatch/changelog/2026-07-11-query-errors-now-show-a-retry-option.md +6 -0
  379. package/corpus/templates/forms/.agents/skills/frontend-design/SKILL.md +1 -1
  380. package/corpus/templates/forms/.agents/skills/real-time-sync/SKILL.md +2 -2
  381. package/corpus/templates/forms/app/components/layout/Sidebar.tsx +3 -3
  382. package/corpus/templates/forms/app/root.tsx +34 -8
  383. package/corpus/templates/macros/.agents/skills/frontend-design/SKILL.md +1 -1
  384. package/corpus/templates/macros/app/components/DailyProgress.tsx +25 -5
  385. package/corpus/templates/macros/app/components/QueryErrorState.tsx +31 -0
  386. package/corpus/templates/macros/app/components/WeightTracker.tsx +5 -1
  387. package/corpus/templates/macros/app/components/layout/AppLayout.tsx +1 -2
  388. package/corpus/templates/macros/app/components/ui/button.tsx +1 -1
  389. package/corpus/templates/macros/app/components/ui/progress.tsx +1 -1
  390. package/corpus/templates/macros/app/components/ui/tabs.tsx +1 -1
  391. package/corpus/templates/macros/app/i18n/ar-SA.ts +2 -0
  392. package/corpus/templates/macros/app/i18n/de-DE.ts +2 -0
  393. package/corpus/templates/macros/app/i18n/en-US.ts +2 -0
  394. package/corpus/templates/macros/app/i18n/es-ES.ts +2 -0
  395. package/corpus/templates/macros/app/i18n/fr-FR.ts +2 -0
  396. package/corpus/templates/macros/app/i18n/hi-IN.ts +2 -0
  397. package/corpus/templates/macros/app/i18n/ja-JP.ts +2 -0
  398. package/corpus/templates/macros/app/i18n/ko-KR.ts +2 -0
  399. package/corpus/templates/macros/app/i18n/pt-BR.ts +2 -0
  400. package/corpus/templates/macros/app/i18n/zh-CN.ts +2 -0
  401. package/corpus/templates/macros/app/i18n/zh-TW.ts +2 -0
  402. package/corpus/templates/macros/app/root.tsx +0 -3
  403. package/corpus/templates/macros/app/routes/_index.tsx +22 -12
  404. package/corpus/templates/macros/app/routes/analytics.tsx +31 -10
  405. package/corpus/templates/macros/changelog/2026-07-11-daily-tracking-now-loads-and-updates-more-quickly.md +6 -0
  406. package/corpus/templates/macros/changelog/2026-07-11-data-load-failures-now-show-a-clear-error-with-a-retry-actio.md +6 -0
  407. package/corpus/templates/mail/.agents/skills/frontend-design/SKILL.md +1 -1
  408. package/corpus/templates/mail/.agents/skills/real-time-sync/SKILL.md +2 -2
  409. package/corpus/templates/mail/.env.example +16 -11
  410. package/corpus/templates/mail/actions/list-emails.ts +39 -35
  411. package/corpus/templates/mail/app/components/email/ComposeModal.tsx +58 -6
  412. package/corpus/templates/mail/app/components/email/EmailListItem.tsx +35 -2
  413. package/corpus/templates/mail/app/components/layout/AppLayout.tsx +113 -104
  414. package/corpus/templates/mail/app/hooks/use-compose-state.ts +1 -0
  415. package/corpus/templates/mail/app/root.tsx +9 -22
  416. package/corpus/templates/mail/changelog/2026-07-11-compose-and-navigation-motion-now-feel-faster-and-smoother.md +6 -0
  417. package/corpus/templates/mail/changelog/2026-07-11-swipe-to-archive-or-snooze-now-responds-to-quick-flicks-not-.md +6 -0
  418. package/corpus/templates/mail/changelog/2026-07-11-the-assistant-now-sees-the-same-inbox-you-do-snoozed-mail-st.md +6 -0
  419. package/corpus/templates/mail/server/handlers/emails.ts +25 -69
  420. package/corpus/templates/mail/server/lib/list-inbox-emails.ts +169 -0
  421. package/corpus/templates/plan/.agents/skills/frontend-design/SKILL.md +1 -1
  422. package/corpus/templates/plan/.agents/skills/real-time-sync/SKILL.md +2 -2
  423. package/corpus/templates/plan/.env.example +4 -0
  424. package/corpus/templates/plan/actions/create-plan-design.ts +89 -81
  425. package/corpus/templates/plan/actions/create-prototype-plan.ts +72 -59
  426. package/corpus/templates/plan/actions/create-ui-plan.ts +70 -55
  427. package/corpus/templates/plan/actions/create-visual-plan.ts +65 -51
  428. package/corpus/templates/plan/actions/update-local-plan-folder.ts +43 -30
  429. package/corpus/templates/plan/actions/update-visual-plan.ts +81 -64
  430. package/corpus/templates/plan/app/components/layout/Sidebar.tsx +17 -0
  431. package/corpus/templates/plan/app/components/ui/tabs.tsx +1 -1
  432. package/corpus/templates/plan/app/hooks/use-navigation-state.ts +5 -2
  433. package/corpus/templates/plan/app/hooks/use-plans.ts +6 -13
  434. package/corpus/templates/plan/app/i18n/ar-SA.ts +10 -0
  435. package/corpus/templates/plan/app/i18n/de-DE.ts +10 -0
  436. package/corpus/templates/plan/app/i18n/en-US.ts +10 -0
  437. package/corpus/templates/plan/app/i18n/es-ES.ts +10 -0
  438. package/corpus/templates/plan/app/i18n/fr-FR.ts +10 -0
  439. package/corpus/templates/plan/app/i18n/hi-IN.ts +10 -0
  440. package/corpus/templates/plan/app/i18n/ja-JP.ts +10 -0
  441. package/corpus/templates/plan/app/i18n/ko-KR.ts +10 -0
  442. package/corpus/templates/plan/app/i18n/pt-BR.ts +10 -0
  443. package/corpus/templates/plan/app/i18n/zh-CN.ts +10 -0
  444. package/corpus/templates/plan/app/i18n/zh-TW.ts +10 -0
  445. package/corpus/templates/plan/app/lib/plan-local-bridge.ts +80 -1
  446. package/corpus/templates/plan/app/pages/PlansPage.tsx +157 -31
  447. package/corpus/templates/plan/app/root.tsx +11 -0
  448. package/corpus/templates/plan/changelog/2026-07-11-local-plans-now-explain-and-recover-from-browser-permission-.md +6 -0
  449. package/corpus/templates/plan/changelog/2026-07-11-plan-edits-now-save-atomically-and-can-no-longer-partially-a.md +6 -0
  450. package/corpus/templates/plan/changelog/2026-07-11-plan-lists-now-show-a-clear-error-with-a-retry-action-when-t.md +6 -0
  451. package/corpus/templates/plan/changelog/2026-07-11-plan-refreshes-only-when-content-changes.md +6 -0
  452. package/corpus/templates/plan/shared/plan-content.ts +546 -260
  453. package/corpus/templates/slides/.agents/skills/frontend-design/SKILL.md +1 -1
  454. package/corpus/templates/slides/actions/delete-slide-comment.ts +49 -0
  455. package/corpus/templates/slides/actions/update-slide-comment.ts +93 -0
  456. package/corpus/templates/slides/actions/view-screen.ts +14 -6
  457. package/corpus/templates/slides/app/components/comments/SlideCommentsPanel.tsx +37 -11
  458. package/corpus/templates/slides/app/components/editor/EditorSidebar.tsx +1 -1
  459. package/corpus/templates/slides/app/components/editor/EditorToolbar.tsx +2 -2
  460. package/corpus/templates/slides/app/components/editor/GenerateSlidesDialog.tsx +2 -2
  461. package/corpus/templates/slides/app/components/layout/Header.tsx +1 -1
  462. package/corpus/templates/slides/app/components/presentation/PresentationView.tsx +4 -2
  463. package/corpus/templates/slides/app/components/visual-editor/CanvasCommentPins.tsx +4 -3
  464. package/corpus/templates/slides/app/context/DeckContext.tsx +10 -0
  465. package/corpus/templates/slides/app/hooks/use-slide-comments.ts +7 -31
  466. package/corpus/templates/slides/app/i18n/ar-SA.ts +6 -0
  467. package/corpus/templates/slides/app/i18n/de-DE.ts +6 -0
  468. package/corpus/templates/slides/app/i18n/en-US.ts +6 -0
  469. package/corpus/templates/slides/app/i18n/es-ES.ts +6 -0
  470. package/corpus/templates/slides/app/i18n/fr-FR.ts +6 -0
  471. package/corpus/templates/slides/app/i18n/hi-IN.ts +6 -0
  472. package/corpus/templates/slides/app/i18n/ja-JP.ts +6 -0
  473. package/corpus/templates/slides/app/i18n/ko-KR.ts +6 -0
  474. package/corpus/templates/slides/app/i18n/pt-BR.ts +6 -0
  475. package/corpus/templates/slides/app/i18n/zh-CN.ts +5 -0
  476. package/corpus/templates/slides/app/i18n/zh-TW.ts +5 -0
  477. package/corpus/templates/slides/app/pages/DesignSystems.tsx +23 -2
  478. package/corpus/templates/slides/app/pages/Index.tsx +29 -1
  479. package/corpus/templates/slides/changelog/2026-07-11-comment-load-failures-now-show-a-retry-action-instead-of-say.md +6 -0
  480. package/corpus/templates/slides/changelog/2026-07-11-comment-pins-now-clear-with-a-faster-smoother-confirmation.md +6 -0
  481. package/corpus/templates/slides/changelog/2026-07-11-comments-now-update-from-live-collaboration-events-without-c.md +6 -0
  482. package/corpus/templates/slides/changelog/2026-07-11-deck-and-design-system-load-failures-now-show-a-retry-action.md +6 -0
  483. package/corpus/templates/slides/changelog/2026-07-11-editor-toggles-and-toolbar-menus-now-animate-smoothly.md +6 -0
  484. package/corpus/templates/slides/changelog/2026-07-11-the-assistant-can-now-resolve-reopen-and-delete-slide-commen.md +6 -0
  485. package/corpus/templates/slides/changelog/2026-07-11-viewing-slides-no-longer-loads-every-deck-s-full-contents-in.md +6 -0
  486. package/corpus/templates/slides/server/routes/api/comments/[id].delete.ts +29 -48
  487. package/corpus/templates/slides/server/routes/api/comments/[id].patch.ts +29 -65
  488. package/dist/action-change-marker.d.ts +1 -0
  489. package/dist/action-change-marker.d.ts.map +1 -1
  490. package/dist/action-change-marker.js +7 -1
  491. package/dist/action-change-marker.js.map +1 -1
  492. package/dist/action.d.ts +23 -0
  493. package/dist/action.d.ts.map +1 -1
  494. package/dist/action.js +13 -0
  495. package/dist/action.js.map +1 -1
  496. package/dist/agent/engine/builder-engine.d.ts.map +1 -1
  497. package/dist/agent/engine/builder-engine.js +3 -0
  498. package/dist/agent/engine/builder-engine.js.map +1 -1
  499. package/dist/agent/engine/context-directives-transform.d.ts +25 -0
  500. package/dist/agent/engine/context-directives-transform.d.ts.map +1 -0
  501. package/dist/agent/engine/context-directives-transform.js +53 -0
  502. package/dist/agent/engine/context-directives-transform.js.map +1 -0
  503. package/dist/agent/engine/continuation-dispatch-retry.d.ts +40 -0
  504. package/dist/agent/engine/continuation-dispatch-retry.d.ts.map +1 -0
  505. package/dist/agent/engine/continuation-dispatch-retry.js +109 -0
  506. package/dist/agent/engine/continuation-dispatch-retry.js.map +1 -0
  507. package/dist/agent/engine/tool-call-journal-seed.d.ts +46 -0
  508. package/dist/agent/engine/tool-call-journal-seed.d.ts.map +1 -0
  509. package/dist/agent/engine/tool-call-journal-seed.js +60 -0
  510. package/dist/agent/engine/tool-call-journal-seed.js.map +1 -0
  511. package/dist/agent/production-agent.d.ts +7 -0
  512. package/dist/agent/production-agent.d.ts.map +1 -1
  513. package/dist/agent/production-agent.js +102 -222
  514. package/dist/agent/production-agent.js.map +1 -1
  515. package/dist/changelog/parse.d.ts +4 -2
  516. package/dist/changelog/parse.d.ts.map +1 -1
  517. package/dist/changelog/parse.js +5 -3
  518. package/dist/changelog/parse.js.map +1 -1
  519. package/dist/cli/doctor.d.ts.map +1 -1
  520. package/dist/cli/doctor.js +7 -3
  521. package/dist/cli/doctor.js.map +1 -1
  522. package/dist/cli/plan-local.d.ts.map +1 -1
  523. package/dist/cli/plan-local.js +8 -2
  524. package/dist/cli/plan-local.js.map +1 -1
  525. package/dist/cli/skills-content/assets-skill.d.ts +2 -0
  526. package/dist/cli/skills-content/assets-skill.d.ts.map +1 -0
  527. package/dist/cli/skills-content/assets-skill.js +84 -0
  528. package/dist/cli/skills-content/assets-skill.js.map +1 -0
  529. package/dist/cli/skills-content/canvas.d.ts +4 -0
  530. package/dist/cli/skills-content/canvas.d.ts.map +1 -0
  531. package/dist/cli/skills-content/canvas.js +152 -0
  532. package/dist/cli/skills-content/canvas.js.map +1 -0
  533. package/dist/cli/skills-content/connection.d.ts +2 -0
  534. package/dist/cli/skills-content/connection.d.ts.map +1 -0
  535. package/dist/cli/skills-content/connection.js +53 -0
  536. package/dist/cli/skills-content/connection.js.map +1 -0
  537. package/dist/cli/skills-content/content-skill.d.ts +2 -0
  538. package/dist/cli/skills-content/content-skill.d.ts.map +1 -0
  539. package/dist/cli/skills-content/content-skill.js +110 -0
  540. package/dist/cli/skills-content/content-skill.js.map +1 -0
  541. package/dist/cli/skills-content/design-exploration-skill.d.ts +2 -0
  542. package/dist/cli/skills-content/design-exploration-skill.d.ts.map +1 -0
  543. package/dist/cli/skills-content/design-exploration-skill.js +119 -0
  544. package/dist/cli/skills-content/design-exploration-skill.js.map +1 -0
  545. package/dist/cli/skills-content/design-visual-edit-skill.d.ts +2 -0
  546. package/dist/cli/skills-content/design-visual-edit-skill.d.ts.map +1 -0
  547. package/dist/cli/skills-content/design-visual-edit-skill.js +229 -0
  548. package/dist/cli/skills-content/design-visual-edit-skill.js.map +1 -0
  549. package/dist/cli/skills-content/document-quality.d.ts +4 -0
  550. package/dist/cli/skills-content/document-quality.d.ts.map +1 -0
  551. package/dist/cli/skills-content/document-quality.js +195 -0
  552. package/dist/cli/skills-content/document-quality.js.map +1 -0
  553. package/dist/cli/skills-content/exemplar.d.ts +4 -0
  554. package/dist/cli/skills-content/exemplar.d.ts.map +1 -0
  555. package/dist/cli/skills-content/exemplar.js +68 -0
  556. package/dist/cli/skills-content/exemplar.js.map +1 -0
  557. package/dist/cli/skills-content/help.d.ts +2 -0
  558. package/dist/cli/skills-content/help.d.ts.map +1 -0
  559. package/dist/cli/skills-content/help.js +78 -0
  560. package/dist/cli/skills-content/help.js.map +1 -0
  561. package/dist/cli/skills-content/index.d.ts +23 -0
  562. package/dist/cli/skills-content/index.d.ts.map +1 -0
  563. package/dist/cli/skills-content/index.js +23 -0
  564. package/dist/cli/skills-content/index.js.map +1 -0
  565. package/dist/cli/skills-content/local-files.d.ts +2 -0
  566. package/dist/cli/skills-content/local-files.d.ts.map +1 -0
  567. package/dist/cli/skills-content/local-files.js +96 -0
  568. package/dist/cli/skills-content/local-files.js.map +1 -0
  569. package/dist/cli/skills-content/plan-setup-auth.d.ts +9 -0
  570. package/dist/cli/skills-content/plan-setup-auth.d.ts.map +1 -0
  571. package/dist/cli/skills-content/plan-setup-auth.js +81 -0
  572. package/dist/cli/skills-content/plan-setup-auth.js.map +1 -0
  573. package/dist/cli/skills-content/visual-plan-skill.d.ts +2 -0
  574. package/dist/cli/skills-content/visual-plan-skill.d.ts.map +1 -0
  575. package/dist/cli/skills-content/visual-plan-skill.js +471 -0
  576. package/dist/cli/skills-content/visual-plan-skill.js.map +1 -0
  577. package/dist/cli/skills-content/visual-recap-skill.d.ts +2 -0
  578. package/dist/cli/skills-content/visual-recap-skill.d.ts.map +1 -0
  579. package/dist/cli/skills-content/visual-recap-skill.js +548 -0
  580. package/dist/cli/skills-content/visual-recap-skill.js.map +1 -0
  581. package/dist/cli/skills-content/visualize-repo-skill.d.ts +2 -0
  582. package/dist/cli/skills-content/visualize-repo-skill.d.ts.map +1 -0
  583. package/dist/cli/skills-content/visualize-repo-skill.js +94 -0
  584. package/dist/cli/skills-content/visualize-repo-skill.js.map +1 -0
  585. package/dist/cli/skills-content/wireframe.d.ts +4 -0
  586. package/dist/cli/skills-content/wireframe.d.ts.map +1 -0
  587. package/dist/cli/skills-content/wireframe.js +347 -0
  588. package/dist/cli/skills-content/wireframe.js.map +1 -0
  589. package/dist/cli/skills.d.ts +2 -10
  590. package/dist/cli/skills.d.ts.map +1 -1
  591. package/dist/cli/skills.js +2 -2710
  592. package/dist/cli/skills.js.map +1 -1
  593. package/dist/client/AgentPanel.d.ts.map +1 -1
  594. package/dist/client/AgentPanel.js +6 -5
  595. package/dist/client/AgentPanel.js.map +1 -1
  596. package/dist/client/AssistantChat.d.ts +7 -0
  597. package/dist/client/AssistantChat.d.ts.map +1 -1
  598. package/dist/client/AssistantChat.js +62 -40
  599. package/dist/client/AssistantChat.js.map +1 -1
  600. package/dist/client/CommandMenu.d.ts.map +1 -1
  601. package/dist/client/CommandMenu.js +15 -3
  602. package/dist/client/CommandMenu.js.map +1 -1
  603. package/dist/client/FeedbackButton.js +2 -2
  604. package/dist/client/FeedbackButton.js.map +1 -1
  605. package/dist/client/analytics.d.ts.map +1 -1
  606. package/dist/client/analytics.js +26 -0
  607. package/dist/client/analytics.js.map +1 -1
  608. package/dist/client/app-providers.d.ts +13 -5
  609. package/dist/client/app-providers.d.ts.map +1 -1
  610. package/dist/client/app-providers.js +9 -6
  611. package/dist/client/app-providers.js.map +1 -1
  612. package/dist/client/blocks/library/wireframe-kit.d.ts +2 -0
  613. package/dist/client/blocks/library/wireframe-kit.d.ts.map +1 -1
  614. package/dist/client/blocks/library/wireframe-kit.js +9 -2
  615. package/dist/client/blocks/library/wireframe-kit.js.map +1 -1
  616. package/dist/client/chat/tool-call-display.d.ts +4 -0
  617. package/dist/client/chat/tool-call-display.d.ts.map +1 -1
  618. package/dist/client/chat/tool-call-display.js +3 -3
  619. package/dist/client/chat/tool-call-display.js.map +1 -1
  620. package/dist/client/chat-model-groups.d.ts.map +1 -1
  621. package/dist/client/chat-model-groups.js +24 -0
  622. package/dist/client/chat-model-groups.js.map +1 -1
  623. package/dist/client/composer/PastedTextChip.js +1 -1
  624. package/dist/client/composer/PastedTextChip.js.map +1 -1
  625. package/dist/client/composer/RealtimeVoiceMode.d.ts +40 -2
  626. package/dist/client/composer/RealtimeVoiceMode.d.ts.map +1 -1
  627. package/dist/client/composer/RealtimeVoiceMode.js +186 -35
  628. package/dist/client/composer/RealtimeVoiceMode.js.map +1 -1
  629. package/dist/client/composer/TiptapComposer.d.ts +4 -0
  630. package/dist/client/composer/TiptapComposer.d.ts.map +1 -1
  631. package/dist/client/composer/TiptapComposer.js +48 -30
  632. package/dist/client/composer/TiptapComposer.js.map +1 -1
  633. package/dist/client/composer/VoiceButton.d.ts +2 -0
  634. package/dist/client/composer/VoiceButton.d.ts.map +1 -1
  635. package/dist/client/composer/VoiceButton.js +9 -3
  636. package/dist/client/composer/VoiceButton.js.map +1 -1
  637. package/dist/client/composer/index.d.ts +2 -2
  638. package/dist/client/composer/index.d.ts.map +1 -1
  639. package/dist/client/composer/index.js +1 -1
  640. package/dist/client/composer/index.js.map +1 -1
  641. package/dist/client/composer/useRealtimeVoiceMode.d.ts +104 -2
  642. package/dist/client/composer/useRealtimeVoiceMode.d.ts.map +1 -1
  643. package/dist/client/composer/useRealtimeVoiceMode.js +1015 -35
  644. package/dist/client/composer/useRealtimeVoiceMode.js.map +1 -1
  645. package/dist/client/context-xray/ContextXRayPanel.d.ts.map +1 -1
  646. package/dist/client/context-xray/ContextXRayPanel.js +4 -1
  647. package/dist/client/context-xray/ContextXRayPanel.js.map +1 -1
  648. package/dist/client/extensions/ExtensionEditor.d.ts.map +1 -1
  649. package/dist/client/extensions/ExtensionEditor.js +15 -5
  650. package/dist/client/extensions/ExtensionEditor.js.map +1 -1
  651. package/dist/client/extensions/ExtensionQueryErrorState.d.ts +8 -0
  652. package/dist/client/extensions/ExtensionQueryErrorState.d.ts.map +1 -0
  653. package/dist/client/extensions/ExtensionQueryErrorState.js +11 -0
  654. package/dist/client/extensions/ExtensionQueryErrorState.js.map +1 -0
  655. package/dist/client/extensions/ExtensionSlot.d.ts.map +1 -1
  656. package/dist/client/extensions/ExtensionSlot.js +17 -7
  657. package/dist/client/extensions/ExtensionSlot.js.map +1 -1
  658. package/dist/client/extensions/ExtensionViewer.d.ts.map +1 -1
  659. package/dist/client/extensions/ExtensionViewer.js +17 -7
  660. package/dist/client/extensions/ExtensionViewer.js.map +1 -1
  661. package/dist/client/extensions/ExtensionsListPage.d.ts.map +1 -1
  662. package/dist/client/extensions/ExtensionsListPage.js +5 -3
  663. package/dist/client/extensions/ExtensionsListPage.js.map +1 -1
  664. package/dist/client/extensions/ExtensionsSidebarSection.d.ts.map +1 -1
  665. package/dist/client/extensions/ExtensionsSidebarSection.js +21 -8
  666. package/dist/client/extensions/ExtensionsSidebarSection.js.map +1 -1
  667. package/dist/client/index.d.ts +1 -3
  668. package/dist/client/index.d.ts.map +1 -1
  669. package/dist/client/index.js +1 -6
  670. package/dist/client/index.js.map +1 -1
  671. package/dist/client/progress/RunsTray.d.ts.map +1 -1
  672. package/dist/client/progress/RunsTray.js +17 -4
  673. package/dist/client/progress/RunsTray.js.map +1 -1
  674. package/dist/client/settings/SecretsSection.d.ts +2 -3
  675. package/dist/client/settings/SecretsSection.d.ts.map +1 -1
  676. package/dist/client/settings/SecretsSection.js +46 -22
  677. package/dist/client/settings/SecretsSection.js.map +1 -1
  678. package/dist/client/settings/SettingsPanel.d.ts +9 -0
  679. package/dist/client/settings/SettingsPanel.d.ts.map +1 -1
  680. package/dist/client/settings/SettingsPanel.js +46 -12
  681. package/dist/client/settings/SettingsPanel.js.map +1 -1
  682. package/dist/client/settings/SettingsTabsPage.d.ts.map +1 -1
  683. package/dist/client/settings/SettingsTabsPage.js +4 -0
  684. package/dist/client/settings/SettingsTabsPage.js.map +1 -1
  685. package/dist/client/settings/useBuilderStatus.d.ts +2 -0
  686. package/dist/client/settings/useBuilderStatus.d.ts.map +1 -1
  687. package/dist/client/settings/useBuilderStatus.js +4 -0
  688. package/dist/client/settings/useBuilderStatus.js.map +1 -1
  689. package/dist/client/tool-cells/EditCell.d.ts.map +1 -1
  690. package/dist/client/tool-cells/EditCell.js +2 -1
  691. package/dist/client/tool-cells/EditCell.js.map +1 -1
  692. package/dist/client/tool-cells/FilesChangedSummary.d.ts.map +1 -1
  693. package/dist/client/tool-cells/FilesChangedSummary.js +2 -1
  694. package/dist/client/tool-cells/FilesChangedSummary.js.map +1 -1
  695. package/dist/client/tool-cells/WriteCell.d.ts.map +1 -1
  696. package/dist/client/tool-cells/WriteCell.js +2 -1
  697. package/dist/client/tool-cells/WriteCell.js.map +1 -1
  698. package/dist/client/use-action.d.ts +2 -0
  699. package/dist/client/use-action.d.ts.map +1 -1
  700. package/dist/client/use-action.js +10 -0
  701. package/dist/client/use-action.js.map +1 -1
  702. package/dist/client/use-agent-chat-home-handoff.d.ts +2 -0
  703. package/dist/client/use-agent-chat-home-handoff.d.ts.map +1 -1
  704. package/dist/client/use-agent-chat-home-handoff.js +7 -1
  705. package/dist/client/use-agent-chat-home-handoff.js.map +1 -1
  706. package/dist/client/use-agent-engine-configured.d.ts.map +1 -1
  707. package/dist/client/use-agent-engine-configured.js +17 -3
  708. package/dist/client/use-agent-engine-configured.js.map +1 -1
  709. package/dist/client/use-db-sync.d.ts +8 -2
  710. package/dist/client/use-db-sync.d.ts.map +1 -1
  711. package/dist/client/use-db-sync.js +57 -32
  712. package/dist/client/use-db-sync.js.map +1 -1
  713. package/dist/client/use-pausing-interval.d.ts.map +1 -1
  714. package/dist/client/use-pausing-interval.js +16 -4
  715. package/dist/client/use-pausing-interval.js.map +1 -1
  716. package/dist/client/visual-style-controls.js +1 -1
  717. package/dist/client/visual-style-controls.js.map +1 -1
  718. package/dist/collab/routes.d.ts +1 -1
  719. package/dist/deploy/build.d.ts.map +1 -1
  720. package/dist/deploy/build.js +36 -14
  721. package/dist/deploy/build.js.map +1 -1
  722. package/dist/integrations/google-docs-poller.d.ts +7 -0
  723. package/dist/integrations/google-docs-poller.d.ts.map +1 -1
  724. package/dist/integrations/google-docs-poller.js +13 -3
  725. package/dist/integrations/google-docs-poller.js.map +1 -1
  726. package/dist/integrations/plugin.d.ts.map +1 -1
  727. package/dist/integrations/plugin.js +10 -0
  728. package/dist/integrations/plugin.js.map +1 -1
  729. package/dist/integrations/scope-store.js +1 -1
  730. package/dist/integrations/scope-store.js.map +1 -1
  731. package/dist/integrations/usage-budget-store.js +1 -1
  732. package/dist/integrations/usage-budget-store.js.map +1 -1
  733. package/dist/integrations/webhook-handler.d.ts +11 -0
  734. package/dist/integrations/webhook-handler.d.ts.map +1 -1
  735. package/dist/integrations/webhook-handler.js +16 -4
  736. package/dist/integrations/webhook-handler.js.map +1 -1
  737. package/dist/jobs/scheduler.d.ts +12 -0
  738. package/dist/jobs/scheduler.d.ts.map +1 -1
  739. package/dist/jobs/scheduler.js +13 -3
  740. package/dist/jobs/scheduler.js.map +1 -1
  741. package/dist/localization/default-messages.d.ts +47 -0
  742. package/dist/localization/default-messages.d.ts.map +1 -1
  743. package/dist/localization/default-messages.js +48 -1
  744. package/dist/localization/default-messages.js.map +1 -1
  745. package/dist/mcp/oauth-token.d.ts +1 -1
  746. package/dist/mcp/oauth-token.d.ts.map +1 -1
  747. package/dist/mcp/oauth-token.js +6 -1
  748. package/dist/mcp/oauth-token.js.map +1 -1
  749. package/dist/mcp-client/workspace-servers.js +4 -4
  750. package/dist/mcp-client/workspace-servers.js.map +1 -1
  751. package/dist/notifications/routes.d.ts +2 -2
  752. package/dist/observability/routes.d.ts +5 -5
  753. package/dist/observability/routes.d.ts.map +1 -1
  754. package/dist/observability/routes.js +32 -0
  755. package/dist/observability/routes.js.map +1 -1
  756. package/dist/observability/sentiment.d.ts +42 -0
  757. package/dist/observability/sentiment.d.ts.map +1 -0
  758. package/dist/observability/sentiment.js +196 -0
  759. package/dist/observability/sentiment.js.map +1 -0
  760. package/dist/observability/store.d.ts +5 -0
  761. package/dist/observability/store.d.ts.map +1 -1
  762. package/dist/observability/store.js +17 -0
  763. package/dist/observability/store.js.map +1 -1
  764. package/dist/observability/traces.d.ts +2 -0
  765. package/dist/observability/traces.d.ts.map +1 -1
  766. package/dist/observability/traces.js +42 -57
  767. package/dist/observability/traces.js.map +1 -1
  768. package/dist/observability/tracking-identity.d.ts +3 -0
  769. package/dist/observability/tracking-identity.d.ts.map +1 -0
  770. package/dist/observability/tracking-identity.js +51 -0
  771. package/dist/observability/tracking-identity.js.map +1 -0
  772. package/dist/observability/types.d.ts +10 -0
  773. package/dist/observability/types.d.ts.map +1 -1
  774. package/dist/observability/types.js +3 -0
  775. package/dist/observability/types.js.map +1 -1
  776. package/dist/progress/routes.d.ts +1 -1
  777. package/dist/resources/handlers.d.ts +1 -1
  778. package/dist/secrets/crypto.d.ts +7 -6
  779. package/dist/secrets/crypto.d.ts.map +1 -1
  780. package/dist/secrets/crypto.js +30 -11
  781. package/dist/secrets/crypto.js.map +1 -1
  782. package/dist/secrets/routes.d.ts +9 -9
  783. package/dist/secrets/storage.d.ts +6 -0
  784. package/dist/secrets/storage.d.ts.map +1 -1
  785. package/dist/secrets/storage.js +61 -3
  786. package/dist/secrets/storage.js.map +1 -1
  787. package/dist/server/action-change.d.ts +1 -0
  788. package/dist/server/action-change.d.ts.map +1 -1
  789. package/dist/server/action-change.js +4 -1
  790. package/dist/server/action-change.js.map +1 -1
  791. package/dist/server/action-routes.d.ts.map +1 -1
  792. package/dist/server/action-routes.js +5 -0
  793. package/dist/server/action-routes.js.map +1 -1
  794. package/dist/server/agent-chat/action-filters-a2a.d.ts +50 -0
  795. package/dist/server/agent-chat/action-filters-a2a.d.ts.map +1 -0
  796. package/dist/server/agent-chat/action-filters-a2a.js +127 -0
  797. package/dist/server/agent-chat/action-filters-a2a.js.map +1 -0
  798. package/dist/server/agent-chat/browser-team-tools.d.ts +21 -0
  799. package/dist/server/agent-chat/browser-team-tools.d.ts.map +1 -0
  800. package/dist/server/agent-chat/browser-team-tools.js +417 -0
  801. package/dist/server/agent-chat/browser-team-tools.js.map +1 -0
  802. package/dist/server/agent-chat/context-tools.d.ts +33 -0
  803. package/dist/server/agent-chat/context-tools.d.ts.map +1 -0
  804. package/dist/server/agent-chat/context-tools.js +520 -0
  805. package/dist/server/agent-chat/context-tools.js.map +1 -0
  806. package/dist/server/agent-chat/framework-prompts.d.ts +65 -0
  807. package/dist/server/agent-chat/framework-prompts.d.ts.map +1 -0
  808. package/dist/server/agent-chat/framework-prompts.js +403 -0
  809. package/dist/server/agent-chat/framework-prompts.js.map +1 -0
  810. package/dist/server/agent-chat/lazy-fs.d.ts +2 -0
  811. package/dist/server/agent-chat/lazy-fs.d.ts.map +1 -0
  812. package/dist/server/agent-chat/lazy-fs.js +11 -0
  813. package/dist/server/agent-chat/lazy-fs.js.map +1 -0
  814. package/dist/server/agent-chat/mcp-glue.d.ts +9 -0
  815. package/dist/server/agent-chat/mcp-glue.d.ts.map +1 -0
  816. package/dist/server/agent-chat/mcp-glue.js +63 -0
  817. package/dist/server/agent-chat/mcp-glue.js.map +1 -0
  818. package/dist/server/agent-chat/plugin-options.d.ts +322 -0
  819. package/dist/server/agent-chat/plugin-options.d.ts.map +1 -0
  820. package/dist/server/agent-chat/plugin-options.js +2 -0
  821. package/dist/server/agent-chat/plugin-options.js.map +1 -0
  822. package/dist/server/agent-chat/process-run-failure.d.ts +12 -0
  823. package/dist/server/agent-chat/process-run-failure.d.ts.map +1 -0
  824. package/dist/server/agent-chat/process-run-failure.js +24 -0
  825. package/dist/server/agent-chat/process-run-failure.js.map +1 -0
  826. package/dist/server/agent-chat/prompt-resources.d.ts +28 -0
  827. package/dist/server/agent-chat/prompt-resources.d.ts.map +1 -0
  828. package/dist/server/agent-chat/prompt-resources.js +514 -0
  829. package/dist/server/agent-chat/prompt-resources.js.map +1 -0
  830. package/dist/server/agent-chat/recurring-jobs-runtime.d.ts +5 -0
  831. package/dist/server/agent-chat/recurring-jobs-runtime.d.ts.map +1 -0
  832. package/dist/server/agent-chat/recurring-jobs-runtime.js +53 -0
  833. package/dist/server/agent-chat/recurring-jobs-runtime.js.map +1 -0
  834. package/dist/server/agent-chat/request-surface.d.ts +8 -0
  835. package/dist/server/agent-chat/request-surface.d.ts.map +1 -0
  836. package/dist/server/agent-chat/request-surface.js +56 -0
  837. package/dist/server/agent-chat/request-surface.js.map +1 -0
  838. package/dist/server/agent-chat/run-code-tools.d.ts +17 -0
  839. package/dist/server/agent-chat/run-code-tools.d.ts.map +1 -0
  840. package/dist/server/agent-chat/run-code-tools.js +55 -0
  841. package/dist/server/agent-chat/run-code-tools.js.map +1 -0
  842. package/dist/server/agent-chat/script-entries.d.ts +47 -0
  843. package/dist/server/agent-chat/script-entries.d.ts.map +1 -0
  844. package/dist/server/agent-chat/script-entries.js +633 -0
  845. package/dist/server/agent-chat/script-entries.js.map +1 -0
  846. package/dist/server/agent-chat/shared-thread.d.ts +33 -0
  847. package/dist/server/agent-chat/shared-thread.d.ts.map +1 -0
  848. package/dist/server/agent-chat/shared-thread.js +242 -0
  849. package/dist/server/agent-chat/shared-thread.js.map +1 -0
  850. package/dist/server/agent-chat/skill-frontmatter.d.ts +7 -0
  851. package/dist/server/agent-chat/skill-frontmatter.d.ts.map +1 -0
  852. package/dist/server/agent-chat/skill-frontmatter.js +26 -0
  853. package/dist/server/agent-chat/skill-frontmatter.js.map +1 -0
  854. package/dist/server/agent-chat-plugin.d.ts +45 -486
  855. package/dist/server/agent-chat-plugin.d.ts.map +1 -1
  856. package/dist/server/agent-chat-plugin.js +173 -3005
  857. package/dist/server/agent-chat-plugin.js.map +1 -1
  858. package/dist/server/agent-teams.d.ts +10 -0
  859. package/dist/server/agent-teams.d.ts.map +1 -1
  860. package/dist/server/agent-teams.js +13 -3
  861. package/dist/server/agent-teams.js.map +1 -1
  862. package/dist/server/auth.d.ts.map +1 -1
  863. package/dist/server/auth.js +50 -52
  864. package/dist/server/auth.js.map +1 -1
  865. package/dist/server/credential-provider.d.ts.map +1 -1
  866. package/dist/server/credential-provider.js +22 -21
  867. package/dist/server/credential-provider.js.map +1 -1
  868. package/dist/server/edge.d.ts +1 -0
  869. package/dist/server/edge.d.ts.map +1 -1
  870. package/dist/server/edge.js +1 -0
  871. package/dist/server/edge.js.map +1 -1
  872. package/dist/server/onboarding-html.d.ts.map +1 -1
  873. package/dist/server/onboarding-html.js +1 -16
  874. package/dist/server/onboarding-html.js.map +1 -1
  875. package/dist/server/poll.d.ts.map +1 -1
  876. package/dist/server/poll.js +1 -0
  877. package/dist/server/poll.js.map +1 -1
  878. package/dist/server/realtime-voice.d.ts +10 -0
  879. package/dist/server/realtime-voice.d.ts.map +1 -1
  880. package/dist/server/realtime-voice.js +249 -15
  881. package/dist/server/realtime-voice.js.map +1 -1
  882. package/dist/server/request-origin.d.ts.map +1 -1
  883. package/dist/server/request-origin.js +6 -3
  884. package/dist/server/request-origin.js.map +1 -1
  885. package/dist/server/ssr-handler.d.ts.map +1 -1
  886. package/dist/server/ssr-handler.js +47 -2
  887. package/dist/server/ssr-handler.js.map +1 -1
  888. package/dist/sharing/access.d.ts +63 -3
  889. package/dist/sharing/access.d.ts.map +1 -1
  890. package/dist/sharing/access.js +53 -15
  891. package/dist/sharing/access.js.map +1 -1
  892. package/dist/sharing/registry.d.ts +7 -0
  893. package/dist/sharing/registry.d.ts.map +1 -1
  894. package/dist/sharing/registry.js.map +1 -1
  895. package/dist/styles/agent-native.css +17 -16
  896. package/dist/templates/default/.agents/skills/frontend-design/SKILL.md +1 -1
  897. package/dist/templates/default/.agents/skills/real-time-sync/SKILL.md +2 -2
  898. package/dist/templates/default/app/i18n/ar-SA.ts +6 -0
  899. package/dist/templates/default/app/i18n/de-DE.ts +6 -0
  900. package/dist/templates/default/app/i18n/en-US.ts +6 -0
  901. package/dist/templates/default/app/i18n/es-ES.ts +6 -0
  902. package/dist/templates/default/app/i18n/fr-FR.ts +6 -0
  903. package/dist/templates/default/app/i18n/hi-IN.ts +6 -0
  904. package/dist/templates/default/app/i18n/ja-JP.ts +6 -0
  905. package/dist/templates/default/app/i18n/ko-KR.ts +6 -0
  906. package/dist/templates/default/app/i18n/pt-BR.ts +6 -0
  907. package/dist/templates/default/app/i18n/zh-CN.ts +6 -0
  908. package/dist/templates/default/app/i18n/zh-TW.ts +6 -0
  909. package/dist/templates/workspace-core/.agents/skills/authentication/SKILL.md +27 -29
  910. package/dist/templates/workspace-core/.agents/skills/external-agents/SKILL.md +2 -1
  911. package/dist/templates/workspace-core/.agents/skills/frontend-design/SKILL.md +1 -1
  912. package/dist/templates/workspace-core/.agents/skills/observability/SKILL.md +32 -0
  913. package/dist/templates/workspace-core/.agents/skills/real-time-sync/SKILL.md +2 -2
  914. package/dist/templates/workspace-core/.agents/skills/secrets/SKILL.md +7 -3
  915. package/dist/templates/workspace-core/.agents/skills/voice-transcription/SKILL.md +39 -9
  916. package/dist/templates/workspace-core/.agents/skills/writing-agent-instructions/SKILL.md +25 -0
  917. package/dist/templates/workspace-core/src/server/index.ts +4 -2
  918. package/dist/testing.d.ts +16 -0
  919. package/dist/testing.d.ts.map +1 -0
  920. package/dist/testing.js +16 -0
  921. package/dist/testing.js.map +1 -0
  922. package/dist/triggers/dispatcher.d.ts +7 -0
  923. package/dist/triggers/dispatcher.d.ts.map +1 -1
  924. package/dist/triggers/dispatcher.js +13 -3
  925. package/dist/triggers/dispatcher.js.map +1 -1
  926. package/dist/vite/client.d.ts +16 -1
  927. package/dist/vite/client.d.ts.map +1 -1
  928. package/dist/vite/client.js +111 -3
  929. package/dist/vite/client.js.map +1 -1
  930. package/docs/content/authentication.mdx +3 -3
  931. package/docs/content/doctor.mdx +170 -0
  932. package/docs/content/external-agents.mdx +8 -7
  933. package/docs/content/locales/ar-SA/authentication.mdx +2 -2
  934. package/docs/content/locales/ar-SA/external-agents.mdx +8 -7
  935. package/docs/content/locales/ar-SA/mcp-protocol.mdx +1 -1
  936. package/docs/content/locales/ar-SA/security.mdx +2 -0
  937. package/docs/content/locales/de-DE/authentication.mdx +1 -1
  938. package/docs/content/locales/de-DE/external-agents.mdx +8 -7
  939. package/docs/content/locales/de-DE/mcp-protocol.mdx +1 -1
  940. package/docs/content/locales/de-DE/security.mdx +2 -0
  941. package/docs/content/locales/es-ES/authentication.mdx +2 -2
  942. package/docs/content/locales/es-ES/external-agents.mdx +8 -7
  943. package/docs/content/locales/es-ES/mcp-protocol.mdx +1 -1
  944. package/docs/content/locales/es-ES/security.mdx +2 -0
  945. package/docs/content/locales/fr-FR/authentication.mdx +1 -1
  946. package/docs/content/locales/fr-FR/external-agents.mdx +8 -7
  947. package/docs/content/locales/fr-FR/mcp-protocol.mdx +1 -1
  948. package/docs/content/locales/fr-FR/security.mdx +2 -0
  949. package/docs/content/locales/hi-IN/authentication.mdx +2 -2
  950. package/docs/content/locales/hi-IN/external-agents.mdx +8 -7
  951. package/docs/content/locales/hi-IN/mcp-protocol.mdx +1 -1
  952. package/docs/content/locales/hi-IN/security.mdx +2 -0
  953. package/docs/content/locales/ja-JP/authentication.mdx +2 -2
  954. package/docs/content/locales/ja-JP/external-agents.mdx +8 -7
  955. package/docs/content/locales/ja-JP/mcp-protocol.mdx +1 -1
  956. package/docs/content/locales/ja-JP/security.mdx +2 -0
  957. package/docs/content/locales/ko-KR/authentication.mdx +2 -2
  958. package/docs/content/locales/ko-KR/external-agents.mdx +8 -7
  959. package/docs/content/locales/ko-KR/mcp-protocol.mdx +1 -1
  960. package/docs/content/locales/ko-KR/security.mdx +2 -0
  961. package/docs/content/locales/pt-BR/authentication.mdx +2 -2
  962. package/docs/content/locales/pt-BR/external-agents.mdx +8 -7
  963. package/docs/content/locales/pt-BR/mcp-protocol.mdx +1 -1
  964. package/docs/content/locales/pt-BR/security.mdx +2 -0
  965. package/docs/content/locales/zh-CN/authentication.mdx +2 -2
  966. package/docs/content/locales/zh-CN/external-agents.mdx +8 -7
  967. package/docs/content/locales/zh-CN/mcp-protocol.mdx +1 -1
  968. package/docs/content/locales/zh-CN/security.mdx +2 -0
  969. package/docs/content/locales/zh-TW/authentication.mdx +2 -2
  970. package/docs/content/locales/zh-TW/external-agents.mdx +8 -7
  971. package/docs/content/locales/zh-TW/mcp-protocol.mdx +1 -1
  972. package/docs/content/locales/zh-TW/security.mdx +2 -0
  973. package/docs/content/mcp-protocol.mdx +1 -1
  974. package/docs/content/security.mdx +2 -0
  975. package/package.json +7 -3
  976. package/src/templates/default/.agents/skills/frontend-design/SKILL.md +1 -1
  977. package/src/templates/default/.agents/skills/real-time-sync/SKILL.md +2 -2
  978. package/src/templates/default/app/i18n/ar-SA.ts +6 -0
  979. package/src/templates/default/app/i18n/de-DE.ts +6 -0
  980. package/src/templates/default/app/i18n/en-US.ts +6 -0
  981. package/src/templates/default/app/i18n/es-ES.ts +6 -0
  982. package/src/templates/default/app/i18n/fr-FR.ts +6 -0
  983. package/src/templates/default/app/i18n/hi-IN.ts +6 -0
  984. package/src/templates/default/app/i18n/ja-JP.ts +6 -0
  985. package/src/templates/default/app/i18n/ko-KR.ts +6 -0
  986. package/src/templates/default/app/i18n/pt-BR.ts +6 -0
  987. package/src/templates/default/app/i18n/zh-CN.ts +6 -0
  988. package/src/templates/default/app/i18n/zh-TW.ts +6 -0
  989. package/src/templates/workspace-core/.agents/skills/authentication/SKILL.md +27 -29
  990. package/src/templates/workspace-core/.agents/skills/external-agents/SKILL.md +2 -1
  991. package/src/templates/workspace-core/.agents/skills/frontend-design/SKILL.md +1 -1
  992. package/src/templates/workspace-core/.agents/skills/observability/SKILL.md +32 -0
  993. package/src/templates/workspace-core/.agents/skills/real-time-sync/SKILL.md +2 -2
  994. package/src/templates/workspace-core/.agents/skills/secrets/SKILL.md +7 -3
  995. package/src/templates/workspace-core/.agents/skills/voice-transcription/SKILL.md +39 -9
  996. package/src/templates/workspace-core/.agents/skills/writing-agent-instructions/SKILL.md +25 -0
  997. package/src/templates/workspace-core/src/server/index.ts +4 -2
  998. package/corpus/core/src/client/ProductionAgentPanel.tsx +0 -9
  999. package/corpus/core/src/client/auth-redirect-url.ts +0 -23
  1000. package/corpus/core/src/client/useProductionAgent.ts +0 -253
  1001. package/corpus/core/src/shared/auth-redirect-url.ts +0 -1
  1002. package/corpus/templates/analytics/app/pages/Index.tsx +0 -16
  1003. package/corpus/templates/analytics/seeds/dashboards/agent-observability-llm.json +0 -173
  1004. package/dist/client/ProductionAgentPanel.d.ts +0 -6
  1005. package/dist/client/ProductionAgentPanel.d.ts.map +0 -1
  1006. package/dist/client/ProductionAgentPanel.js +0 -6
  1007. package/dist/client/ProductionAgentPanel.js.map +0 -1
  1008. package/dist/client/auth-redirect-url.d.ts +0 -2
  1009. package/dist/client/auth-redirect-url.d.ts.map +0 -1
  1010. package/dist/client/auth-redirect-url.js +0 -19
  1011. package/dist/client/auth-redirect-url.js.map +0 -1
  1012. package/dist/client/useProductionAgent.d.ts +0 -24
  1013. package/dist/client/useProductionAgent.d.ts.map +0 -1
  1014. package/dist/client/useProductionAgent.js +0 -179
  1015. package/dist/client/useProductionAgent.js.map +0 -1
  1016. package/dist/shared/auth-redirect-url.d.ts +0 -2
  1017. package/dist/shared/auth-redirect-url.d.ts.map +0 -1
  1018. package/dist/shared/auth-redirect-url.js +0 -2
  1019. package/dist/shared/auth-redirect-url.js.map +0 -1
@@ -33,9 +33,83 @@ import {
33
33
  } from "./RealtimeVoiceMode.js";
34
34
 
35
35
  const REALTIME_VOICE_STATE_KEY = "realtime-voice-session";
36
+ const REALTIME_VOICE_PREFERENCES_KEY = "realtime-voice-prefs";
36
37
  const REALTIME_VOICE_REQUEST_SOURCE = "realtime-voice";
37
38
  const REALTIME_VOICE_SESSION_PATH = "/_agent-native/realtime-voice/session";
38
39
  const REALTIME_VOICE_TOOL_PATH = "/_agent-native/realtime-voice/tool";
40
+ const REALTIME_VOICE_CAPABILITY_HEADER = "X-Agent-Native-Realtime-Capability";
41
+ const REALTIME_VOICE_CONNECTION_TIMEOUT_MS = 15_000;
42
+ const REALTIME_VOICE_MAX_TOOLS = 32;
43
+ const REALTIME_VOICE_MAX_TOOL_SCHEMA_BYTES = 32_000;
44
+ const REALTIME_VOICE_MAX_SESSION_BYTES = 64_000;
45
+ const REALTIME_VOICE_TOOL_UPDATE_TIMEOUT_MS = 5_000;
46
+ const REALTIME_VOICE_PRIORITY_TOOL_NAMES = [
47
+ "navigate",
48
+ "set-url-path",
49
+ "set-search-params",
50
+ "view-screen",
51
+ "tool-search",
52
+ ] as const;
53
+ const REALTIME_VOICE_TOOL_NAME = /^[A-Za-z0-9_-]{1,64}$/;
54
+ const REALTIME_VOICE_MICROPHONE_STORAGE_KEY =
55
+ "agent-native:realtime-voice-microphone";
56
+
57
+ export const REALTIME_VOICE_LANGUAGES = [
58
+ "auto",
59
+ "en",
60
+ "es",
61
+ "fr",
62
+ "de",
63
+ "it",
64
+ "pt",
65
+ "ja",
66
+ "ko",
67
+ "zh",
68
+ ] as const;
69
+ export const REALTIME_VOICE_INTELLIGENCE_LEVELS = [
70
+ "instant",
71
+ "balanced",
72
+ "deep",
73
+ ] as const;
74
+ export const REALTIME_VOICES = [
75
+ "marin",
76
+ "cedar",
77
+ "coral",
78
+ "sage",
79
+ "verse",
80
+ "alloy",
81
+ "ash",
82
+ "ballad",
83
+ "echo",
84
+ "shimmer",
85
+ ] as const;
86
+
87
+ export type RealtimeVoiceLanguage = (typeof REALTIME_VOICE_LANGUAGES)[number];
88
+ export type RealtimeVoiceIntelligence =
89
+ (typeof REALTIME_VOICE_INTELLIGENCE_LEVELS)[number];
90
+ export type RealtimeVoice = (typeof REALTIME_VOICES)[number];
91
+
92
+ export interface RealtimeVoicePreferences {
93
+ language: RealtimeVoiceLanguage;
94
+ intelligence: RealtimeVoiceIntelligence;
95
+ voice: RealtimeVoice;
96
+ }
97
+
98
+ export const DEFAULT_REALTIME_VOICE_PREFERENCES: RealtimeVoicePreferences = {
99
+ language: "auto",
100
+ intelligence: "instant",
101
+ voice: "marin",
102
+ };
103
+
104
+ export const REALTIME_VOICE_AUDIO_CONSTRAINTS: MediaTrackConstraints = {
105
+ // Prefer the browser/OS-selected input instead of whichever physical device
106
+ // happens to be enumerated first. `ideal` keeps browsers without the
107
+ // synthetic `default` device from failing with OverconstrainedError.
108
+ deviceId: { ideal: "default" },
109
+ echoCancellation: true,
110
+ noiseSuppression: true,
111
+ autoGainControl: true,
112
+ };
39
113
 
40
114
  type RealtimeServerEvent = Record<string, unknown> & { type?: string };
41
115
 
@@ -44,6 +118,19 @@ export interface RealtimeVoiceToolResult {
44
118
  status: "completed" | "failed" | "approval_required";
45
119
  output: string;
46
120
  approvalKey?: string;
121
+ expandedTools?: RealtimeVoiceFunctionTool[];
122
+ }
123
+
124
+ export interface RealtimeVoiceSessionAnswer {
125
+ sdp: string;
126
+ capability?: string;
127
+ }
128
+
129
+ export interface RealtimeVoiceFunctionTool {
130
+ type: "function";
131
+ name: string;
132
+ description?: string;
133
+ parameters?: Record<string, unknown>;
47
134
  }
48
135
 
49
136
  export interface RealtimeVoiceModeApi {
@@ -52,17 +139,359 @@ export interface RealtimeVoiceModeApi {
52
139
  errorMessage: string | null;
53
140
  chatVisible: boolean;
54
141
  audioLevels: RealtimeVoiceAudioLevelStore;
142
+ preferences: RealtimeVoicePreferences;
143
+ microphones: readonly RealtimeVoiceMicrophone[];
144
+ microphoneDeviceId: string;
145
+ microphoneSwitching: boolean;
146
+ microphoneError: string | null;
147
+ voiceChangePending: boolean;
148
+ setLanguage: (language: RealtimeVoiceLanguage) => void;
149
+ setIntelligence: (intelligence: RealtimeVoiceIntelligence) => void;
150
+ setVoice: (voice: RealtimeVoice) => void;
151
+ setMicrophone: (deviceId: string) => Promise<void>;
55
152
  start: () => Promise<void>;
56
153
  end: () => void;
57
154
  toggleChat: () => void;
58
155
  }
59
156
 
157
+ export interface RealtimeVoiceMicrophone {
158
+ deviceId: string;
159
+ label: string;
160
+ }
161
+
162
+ export function createRealtimeVoiceAudioConstraints(
163
+ deviceId = "default",
164
+ exact = false,
165
+ ): MediaTrackConstraints {
166
+ return {
167
+ ...REALTIME_VOICE_AUDIO_CONSTRAINTS,
168
+ deviceId:
169
+ deviceId === "default"
170
+ ? { ideal: "default" }
171
+ : exact
172
+ ? { exact: deviceId }
173
+ : { ideal: deviceId },
174
+ };
175
+ }
176
+
177
+ export async function replaceRealtimeVoiceMicrophone(options: {
178
+ mediaDevices: Pick<MediaDevices, "getUserMedia">;
179
+ peer: Pick<RTCPeerConnection, "getSenders">;
180
+ currentStream: MediaStream | null;
181
+ deviceId: string;
182
+ }): Promise<MediaStream> {
183
+ const replacementStream = await options.mediaDevices.getUserMedia({
184
+ audio: createRealtimeVoiceAudioConstraints(options.deviceId, true),
185
+ });
186
+ try {
187
+ const replacementTrack = replacementStream.getAudioTracks()[0];
188
+ const sender = options.peer
189
+ .getSenders()
190
+ .find((candidate) => candidate.track?.kind === "audio");
191
+ if (!replacementTrack || !sender) {
192
+ throw new Error("No active microphone track is available.");
193
+ }
194
+
195
+ await sender.replaceTrack(replacementTrack);
196
+ for (const track of options.currentStream?.getTracks() ?? []) {
197
+ track.onended = null;
198
+ track.stop();
199
+ }
200
+ return replacementStream;
201
+ } catch (error) {
202
+ for (const track of replacementStream.getTracks()) track.stop();
203
+ throw error;
204
+ }
205
+ }
206
+
207
+ function readRealtimeVoiceMicrophoneId(): string {
208
+ if (typeof window === "undefined") return "default";
209
+ try {
210
+ return (
211
+ window.localStorage.getItem(REALTIME_VOICE_MICROPHONE_STORAGE_KEY) ||
212
+ "default"
213
+ );
214
+ } catch {
215
+ return "default";
216
+ }
217
+ }
218
+
219
+ function writeRealtimeVoiceMicrophoneId(deviceId: string): void {
220
+ try {
221
+ window.localStorage.setItem(
222
+ REALTIME_VOICE_MICROPHONE_STORAGE_KEY,
223
+ deviceId,
224
+ );
225
+ } catch {
226
+ // A remembered local device is optional and must never block voice mode.
227
+ }
228
+ }
229
+
230
+ function isOneOf<T extends readonly string[]>(
231
+ values: T,
232
+ value: unknown,
233
+ ): value is T[number] {
234
+ return typeof value === "string" && values.includes(value);
235
+ }
236
+
237
+ export function normalizeRealtimeVoicePreferences(
238
+ value: unknown,
239
+ ): RealtimeVoicePreferences {
240
+ const record =
241
+ value && typeof value === "object" && !Array.isArray(value)
242
+ ? (value as Record<string, unknown>)
243
+ : {};
244
+ return {
245
+ language: isOneOf(REALTIME_VOICE_LANGUAGES, record.language)
246
+ ? record.language
247
+ : DEFAULT_REALTIME_VOICE_PREFERENCES.language,
248
+ intelligence: isOneOf(
249
+ REALTIME_VOICE_INTELLIGENCE_LEVELS,
250
+ record.intelligence,
251
+ )
252
+ ? record.intelligence
253
+ : DEFAULT_REALTIME_VOICE_PREFERENCES.intelligence,
254
+ voice: isOneOf(REALTIME_VOICES, record.voice)
255
+ ? record.voice
256
+ : DEFAULT_REALTIME_VOICE_PREFERENCES.voice,
257
+ };
258
+ }
259
+
260
+ export function resolveRealtimeVoiceLanguage(
261
+ language: RealtimeVoiceLanguage,
262
+ browserLanguages: readonly string[] = [],
263
+ ): Exclude<RealtimeVoiceLanguage, "auto"> {
264
+ if (language !== "auto") return language;
265
+ for (const locale of browserLanguages) {
266
+ const primary = locale.trim().split("-")[0]?.toLowerCase();
267
+ if (isOneOf(REALTIME_VOICE_LANGUAGES, primary) && primary !== "auto") {
268
+ return primary;
269
+ }
270
+ }
271
+ return "en";
272
+ }
273
+
274
+ export function realtimeVoiceReasoningEffort(
275
+ intelligence: RealtimeVoiceIntelligence,
276
+ ): "minimal" | "low" | "medium" {
277
+ if (intelligence === "balanced") return "low";
278
+ if (intelligence === "deep") return "medium";
279
+ return "minimal";
280
+ }
281
+
282
+ export function createRealtimeVoicePreferenceUpdate(
283
+ preferences: RealtimeVoicePreferences,
284
+ options: {
285
+ browserLanguages?: readonly string[];
286
+ includeVoice?: boolean;
287
+ } = {},
288
+ ): Record<string, unknown> {
289
+ return {
290
+ type: "session.update",
291
+ session: {
292
+ type: "realtime",
293
+ reasoning: {
294
+ effort: realtimeVoiceReasoningEffort(preferences.intelligence),
295
+ },
296
+ audio: {
297
+ input: {
298
+ transcription: {
299
+ model: "gpt-4o-mini-transcribe",
300
+ language: resolveRealtimeVoiceLanguage(
301
+ preferences.language,
302
+ options.browserLanguages,
303
+ ),
304
+ },
305
+ },
306
+ ...(options.includeVoice
307
+ ? { output: { voice: preferences.voice } }
308
+ : {}),
309
+ },
310
+ },
311
+ };
312
+ }
313
+
60
314
  export interface CompletedRealtimeVoiceTranscript {
61
315
  role: "user" | "assistant";
62
316
  text: string;
63
317
  providerId?: string;
64
318
  }
65
319
 
320
+ export interface RealtimeVoiceTranscriptSequencer {
321
+ handle: (event: RealtimeServerEvent) => void;
322
+ reset: () => void;
323
+ }
324
+
325
+ interface SequencedRealtimeVoiceTranscript {
326
+ status: "pending" | "completed" | "skipped";
327
+ role?: CompletedRealtimeVoiceTranscript["role"];
328
+ transcript?: CompletedRealtimeVoiceTranscript;
329
+ }
330
+
331
+ /**
332
+ * Input transcription is produced by a separate ASR model and can finish after
333
+ * the assistant response. Reserve each message's position when OpenAI adds it
334
+ * to the conversation, then publish only the contiguous completed prefix.
335
+ */
336
+ export function createRealtimeVoiceTranscriptSequencer(
337
+ publish: (transcript: CompletedRealtimeVoiceTranscript) => void,
338
+ ): RealtimeVoiceTranscriptSequencer {
339
+ const order: string[] = [];
340
+ const items = new Map<string, SequencedRealtimeVoiceTranscript>();
341
+ const settledItemIds = new Set<string>();
342
+ const receivedTranscriptIds = new Set<string>();
343
+
344
+ const reserve = (
345
+ id: string,
346
+ role?: CompletedRealtimeVoiceTranscript["role"],
347
+ ) => {
348
+ if (settledItemIds.has(id)) return;
349
+ const existing = items.get(id);
350
+ if (existing) {
351
+ existing.role ??= role;
352
+ return;
353
+ }
354
+ order.push(id);
355
+ items.set(id, { status: "pending", role });
356
+ };
357
+
358
+ const drain = () => {
359
+ while (order.length > 0) {
360
+ const id = order[0]!;
361
+ const entry = items.get(id);
362
+ if (!entry || entry.status === "pending") return;
363
+ order.shift();
364
+ items.delete(id);
365
+ settledItemIds.add(id);
366
+ if (entry.status === "completed" && entry.transcript) {
367
+ publish(entry.transcript);
368
+ }
369
+ }
370
+ };
371
+
372
+ const skip = (id: unknown) => {
373
+ if (typeof id !== "string" || !id) return;
374
+ reserve(id);
375
+ const entry = items.get(id)!;
376
+ if (entry.status === "pending") entry.status = "skipped";
377
+ };
378
+
379
+ return {
380
+ handle(event) {
381
+ if (
382
+ event.type === "conversation.item.added" ||
383
+ event.type === "conversation.item.created"
384
+ ) {
385
+ const item = event.item;
386
+ if (item && typeof item === "object") {
387
+ const record = item as Record<string, unknown>;
388
+ if (
389
+ record.type === "message" &&
390
+ (record.role === "user" || record.role === "assistant") &&
391
+ typeof record.id === "string"
392
+ ) {
393
+ reserve(record.id, record.role);
394
+ }
395
+ }
396
+ }
397
+
398
+ const transcript = extractCompletedRealtimeVoiceTranscript(event);
399
+ if (transcript) {
400
+ const transcriptId = transcript.providerId
401
+ ? `${transcript.role}:${transcript.providerId}`
402
+ : undefined;
403
+ if (transcriptId && receivedTranscriptIds.has(transcriptId)) return;
404
+ if (transcriptId) receivedTranscriptIds.add(transcriptId);
405
+ const itemId =
406
+ typeof event.item_id === "string" ? event.item_id : undefined;
407
+ if (!itemId) {
408
+ // Older providers may omit item_id. Match the first reserved slot for
409
+ // that role so one incomplete legacy event cannot strand the queue.
410
+ const reservedId = order.find((id) => {
411
+ const entry = items.get(id);
412
+ return (
413
+ entry?.status === "pending" && entry.role === transcript.role
414
+ );
415
+ });
416
+ if (reservedId) {
417
+ items.set(reservedId, {
418
+ status: "completed",
419
+ role: transcript.role,
420
+ transcript,
421
+ });
422
+ } else {
423
+ publish(transcript);
424
+ }
425
+ } else {
426
+ if (settledItemIds.has(itemId)) return;
427
+ reserve(itemId, transcript.role);
428
+ items.set(itemId, {
429
+ status: "completed",
430
+ role: transcript.role,
431
+ transcript,
432
+ });
433
+ }
434
+ } else if (
435
+ event.type === "conversation.item.input_audio_transcription.failed" ||
436
+ event.type === "conversation.item.deleted"
437
+ ) {
438
+ skip(event.item_id);
439
+ } else if (event.type === "response.done") {
440
+ const response = event.response;
441
+ const output =
442
+ response && typeof response === "object"
443
+ ? (response as { output?: unknown }).output
444
+ : undefined;
445
+ if (Array.isArray(output)) {
446
+ for (const item of output) {
447
+ if (!item || typeof item !== "object") continue;
448
+ const record = item as Record<string, unknown>;
449
+ if (record.type === "message") skip(record.id);
450
+ }
451
+ }
452
+ }
453
+ drain();
454
+ },
455
+ reset() {
456
+ order.length = 0;
457
+ items.clear();
458
+ settledItemIds.clear();
459
+ receivedTranscriptIds.clear();
460
+ },
461
+ };
462
+ }
463
+
464
+ export function createRealtimeVoiceGreetingEvent(): Record<string, unknown> {
465
+ return {
466
+ type: "response.create",
467
+ response: {
468
+ output_modalities: ["audio"],
469
+ instructions:
470
+ 'Say exactly: "How can I help you?" Do not add anything else.',
471
+ // Do not set max_output_tokens here. Realtime counts assistant audio at
472
+ // roughly one token per 50 ms, so a text-sized cap can cut speech off.
473
+ // The exact-response instruction already keeps this greeting bounded.
474
+ },
475
+ };
476
+ }
477
+
478
+ export function createRealtimeVoiceGreetingStarter(
479
+ send: (event: Record<string, unknown>) => void,
480
+ ): { start: () => boolean; reset: () => void } {
481
+ let started = false;
482
+ return {
483
+ start() {
484
+ if (started) return false;
485
+ started = true;
486
+ send(createRealtimeVoiceGreetingEvent());
487
+ return true;
488
+ },
489
+ reset() {
490
+ started = false;
491
+ },
492
+ };
493
+ }
494
+
66
495
  /**
67
496
  * Voice mode owns the chat only temporarily. Restore the captured transcript
68
497
  * when it is still the user's active thread (or the chat has no active thread)
@@ -108,6 +537,48 @@ function errorMessage(error: unknown): string {
108
537
  return error instanceof Error ? error.message : String(error);
109
538
  }
110
539
 
540
+ export function isRealtimeVoiceAbortError(error: unknown): boolean {
541
+ return Boolean(
542
+ error &&
543
+ typeof error === "object" &&
544
+ (error as { name?: unknown }).name === "AbortError",
545
+ );
546
+ }
547
+
548
+ export function isRealtimeVoiceSetupRequiredError(error: unknown): boolean {
549
+ return Boolean(
550
+ error &&
551
+ typeof error === "object" &&
552
+ (error as { status?: unknown }).status === 409,
553
+ );
554
+ }
555
+
556
+ export function createRealtimeVoiceConnectionTimeout(
557
+ onTimeout: () => void,
558
+ timeoutMs = REALTIME_VOICE_CONNECTION_TIMEOUT_MS,
559
+ ): () => void {
560
+ const timeout = window.setTimeout(onTimeout, timeoutMs);
561
+ return () => window.clearTimeout(timeout);
562
+ }
563
+
564
+ export function createRealtimeVoiceConnectionGate(
565
+ onTimeout: () => void,
566
+ timeoutMs = REALTIME_VOICE_CONNECTION_TIMEOUT_MS,
567
+ ): {
568
+ markTransportReady: () => void;
569
+ markSessionCreated: () => void;
570
+ cancel: () => void;
571
+ } {
572
+ const cancel = createRealtimeVoiceConnectionTimeout(onTimeout, timeoutMs);
573
+ return {
574
+ // RTC connectivity alone is not enough: the session may never finish its
575
+ // Realtime handshake. Keep the deadline armed until session.created.
576
+ markTransportReady() {},
577
+ markSessionCreated: cancel,
578
+ cancel,
579
+ };
580
+ }
581
+
111
582
  async function readErrorResponse(response: Response): Promise<string> {
112
583
  const raw = await response.text().catch(() => "");
113
584
  if (!raw) return response.statusText || `HTTP ${response.status}`;
@@ -119,10 +590,18 @@ async function readErrorResponse(response: Response): Promise<string> {
119
590
  }
120
591
  }
121
592
 
122
- export async function createRealtimeVoiceSession(
593
+ interface RealtimeVoiceSessionOptions {
594
+ browserTabId?: string;
595
+ signal?: AbortSignal;
596
+ preferences?: RealtimeVoicePreferences;
597
+ browserLanguages?: readonly string[];
598
+ }
599
+
600
+ export async function createRealtimeVoiceSessionWithCapability(
123
601
  offerSdp: string,
124
- options: { browserTabId?: string; signal?: AbortSignal } = {},
125
- ): Promise<string> {
602
+ options: RealtimeVoiceSessionOptions = {},
603
+ ): Promise<RealtimeVoiceSessionAnswer> {
604
+ const preferences = options.preferences;
126
605
  const response = await fetch(agentNativePath(REALTIME_VOICE_SESSION_PATH), {
127
606
  method: "POST",
128
607
  credentials: "same-origin",
@@ -131,6 +610,16 @@ export async function createRealtimeVoiceSession(
131
610
  ...(options.browserTabId
132
611
  ? { "X-Agent-Native-Browser-Tab": options.browserTabId }
133
612
  : {}),
613
+ ...(preferences
614
+ ? {
615
+ "X-Agent-Native-Realtime-Language": resolveRealtimeVoiceLanguage(
616
+ preferences.language,
617
+ options.browserLanguages,
618
+ ),
619
+ "X-Agent-Native-Realtime-Intelligence": preferences.intelligence,
620
+ "X-Agent-Native-Realtime-Voice": preferences.voice,
621
+ }
622
+ : {}),
134
623
  },
135
624
  body: offerSdp,
136
625
  signal: options.signal,
@@ -141,7 +630,21 @@ export async function createRealtimeVoiceSession(
141
630
  (error as { status?: number }).status = response.status;
142
631
  throw error;
143
632
  }
144
- return response.text();
633
+ const sdp = await response.text();
634
+ const capability = response.headers.get(REALTIME_VOICE_CAPABILITY_HEADER);
635
+ return {
636
+ sdp,
637
+ ...(capability ? { capability } : {}),
638
+ };
639
+ }
640
+
641
+ /** Backwards-compatible SDP-only session helper for existing client callers. */
642
+ export async function createRealtimeVoiceSession(
643
+ offerSdp: string,
644
+ options: RealtimeVoiceSessionOptions = {},
645
+ ): Promise<string> {
646
+ return (await createRealtimeVoiceSessionWithCapability(offerSdp, options))
647
+ .sdp;
145
648
  }
146
649
 
147
650
  export async function executeRealtimeVoiceTool(input: {
@@ -150,6 +653,7 @@ export async function executeRealtimeVoiceTool(input: {
150
653
  callId: string;
151
654
  sessionId?: string;
152
655
  browserTabId?: string;
656
+ capability?: string;
153
657
  signal?: AbortSignal;
154
658
  }): Promise<RealtimeVoiceToolResult> {
155
659
  const response = await fetch(agentNativePath(REALTIME_VOICE_TOOL_PATH), {
@@ -160,6 +664,9 @@ export async function executeRealtimeVoiceTool(input: {
160
664
  ...(input.browserTabId
161
665
  ? { "X-Agent-Native-Browser-Tab": input.browserTabId }
162
666
  : {}),
667
+ ...(input.capability
668
+ ? { [REALTIME_VOICE_CAPABILITY_HEADER]: input.capability }
669
+ : {}),
163
670
  },
164
671
  body: JSON.stringify({
165
672
  name: input.name,
@@ -176,6 +683,264 @@ export async function executeRealtimeVoiceTool(input: {
176
683
  return (await response.json()) as RealtimeVoiceToolResult;
177
684
  }
178
685
 
686
+ function jsonByteLength(value: unknown): number {
687
+ return new TextEncoder().encode(JSON.stringify(value)).byteLength;
688
+ }
689
+
690
+ function normalizeRealtimeVoiceFunctionTool(
691
+ value: unknown,
692
+ ): RealtimeVoiceFunctionTool | null {
693
+ if (!value || typeof value !== "object" || Array.isArray(value)) return null;
694
+ const record = value as Record<string, unknown>;
695
+ if (
696
+ record.type !== "function" ||
697
+ typeof record.name !== "string" ||
698
+ !REALTIME_VOICE_TOOL_NAME.test(record.name)
699
+ ) {
700
+ return null;
701
+ }
702
+ if (
703
+ record.description !== undefined &&
704
+ typeof record.description !== "string"
705
+ ) {
706
+ return null;
707
+ }
708
+ if (
709
+ record.parameters !== undefined &&
710
+ (!record.parameters ||
711
+ typeof record.parameters !== "object" ||
712
+ Array.isArray(record.parameters))
713
+ ) {
714
+ return null;
715
+ }
716
+ try {
717
+ if (jsonByteLength(record) > REALTIME_VOICE_MAX_TOOL_SCHEMA_BYTES) {
718
+ return null;
719
+ }
720
+ return JSON.parse(JSON.stringify(record)) as RealtimeVoiceFunctionTool;
721
+ } catch {
722
+ return null;
723
+ }
724
+ }
725
+
726
+ export function extractRealtimeVoiceSessionTools(
727
+ event: RealtimeServerEvent,
728
+ ): RealtimeVoiceFunctionTool[] | null {
729
+ if (event.type !== "session.created" && event.type !== "session.updated") {
730
+ return null;
731
+ }
732
+ const session = event.session;
733
+ if (!session || typeof session !== "object") return null;
734
+ const tools = (session as { tools?: unknown }).tools;
735
+ if (!Array.isArray(tools)) return null;
736
+ return tools
737
+ .map(normalizeRealtimeVoiceFunctionTool)
738
+ .filter((tool): tool is RealtimeVoiceFunctionTool => Boolean(tool));
739
+ }
740
+
741
+ function createRealtimeVoiceToolManifestUpdate(
742
+ tools: RealtimeVoiceFunctionTool[],
743
+ eventId?: string,
744
+ ): Record<string, unknown> {
745
+ return {
746
+ type: "session.update",
747
+ ...(eventId ? { event_id: eventId } : {}),
748
+ session: {
749
+ type: "realtime",
750
+ tools,
751
+ tool_choice: "auto",
752
+ },
753
+ };
754
+ }
755
+
756
+ /**
757
+ * Merge search-discovered schemas into the live manifest. Pinned navigation
758
+ * and discovery tools stay available, then the newest discoveries take the
759
+ * remaining slots ahead of lower-priority tools from the original manifest.
760
+ */
761
+ export function mergeRealtimeVoiceToolManifest(
762
+ currentTools: readonly RealtimeVoiceFunctionTool[],
763
+ expandedTools: readonly RealtimeVoiceFunctionTool[],
764
+ ): RealtimeVoiceFunctionTool[] {
765
+ const current = new Map<string, RealtimeVoiceFunctionTool>();
766
+ for (const value of currentTools) {
767
+ const tool = normalizeRealtimeVoiceFunctionTool(value);
768
+ if (tool) current.set(tool.name, tool);
769
+ }
770
+ const expanded = new Map<string, RealtimeVoiceFunctionTool>();
771
+ for (const value of expandedTools) {
772
+ const tool = normalizeRealtimeVoiceFunctionTool(value);
773
+ if (tool) expanded.set(tool.name, tool);
774
+ }
775
+
776
+ const candidates: RealtimeVoiceFunctionTool[] = [];
777
+ const seen = new Set<string>();
778
+ const add = (tool: RealtimeVoiceFunctionTool | undefined) => {
779
+ if (!tool || seen.has(tool.name)) return;
780
+ seen.add(tool.name);
781
+ candidates.push(tool);
782
+ };
783
+ for (const name of REALTIME_VOICE_PRIORITY_TOOL_NAMES) {
784
+ add(expanded.get(name) ?? current.get(name));
785
+ }
786
+ for (const tool of expanded.values()) add(tool);
787
+ for (const tool of current.values()) add(tool);
788
+
789
+ const packed: RealtimeVoiceFunctionTool[] = [];
790
+ for (const tool of candidates) {
791
+ if (packed.length >= REALTIME_VOICE_MAX_TOOLS) break;
792
+ const candidate = [...packed, tool];
793
+ if (
794
+ jsonByteLength(
795
+ createRealtimeVoiceToolManifestUpdate(
796
+ candidate,
797
+ "realtime_tool_manifest_999999999",
798
+ ),
799
+ ) <= REALTIME_VOICE_MAX_SESSION_BYTES
800
+ ) {
801
+ packed.push(tool);
802
+ }
803
+ }
804
+ return packed;
805
+ }
806
+
807
+ function createRealtimeVoiceToolResultEvent(
808
+ result: RealtimeVoiceToolResult,
809
+ ): Record<string, unknown> {
810
+ const modelResult = {
811
+ callId: result.callId,
812
+ status: result.status,
813
+ output: result.output,
814
+ ...(result.approvalKey ? { approvalKey: result.approvalKey } : {}),
815
+ };
816
+ return {
817
+ type: "conversation.item.create",
818
+ item: {
819
+ type: "function_call_output",
820
+ call_id: result.callId,
821
+ output: JSON.stringify(modelResult),
822
+ },
823
+ };
824
+ }
825
+
826
+ export interface RealtimeVoiceToolManifestCoordinator {
827
+ enqueue: (result: RealtimeVoiceToolResult) => void;
828
+ setSessionTools: (tools: readonly RealtimeVoiceFunctionTool[]) => void;
829
+ handleError: (eventId: string | undefined, message?: string) => boolean;
830
+ reset: () => void;
831
+ getTools: () => readonly RealtimeVoiceFunctionTool[];
832
+ }
833
+
834
+ /**
835
+ * Realtime treats `session.update.tools` as a full replacement. Serialize
836
+ * discovery updates and wait for a confirming `session.updated` manifest
837
+ * before returning the search result and allowing the next model response.
838
+ */
839
+ export function createRealtimeVoiceToolManifestCoordinator(
840
+ send: (event: Record<string, unknown>) => void,
841
+ timeoutMs = REALTIME_VOICE_TOOL_UPDATE_TIMEOUT_MS,
842
+ ): RealtimeVoiceToolManifestCoordinator {
843
+ let currentTools: RealtimeVoiceFunctionTool[] = [];
844
+ const queue: RealtimeVoiceToolResult[] = [];
845
+ let updateSequence = 0;
846
+ let active:
847
+ | {
848
+ result: RealtimeVoiceToolResult;
849
+ expectedNames: string[];
850
+ eventId: string;
851
+ timer: ReturnType<typeof setTimeout>;
852
+ }
853
+ | undefined;
854
+
855
+ const finish = (failureMessage?: string) => {
856
+ if (!active) return;
857
+ const { result, timer } = active;
858
+ clearTimeout(timer);
859
+ active = undefined;
860
+ send(
861
+ createRealtimeVoiceToolResultEvent(
862
+ failureMessage
863
+ ? {
864
+ callId: result.callId,
865
+ status: "failed",
866
+ output: `${failureMessage} The discovered tools were not added to this voice session. Original tool result: ${result.output}`,
867
+ }
868
+ : result,
869
+ ),
870
+ );
871
+ send({ type: "response.create" });
872
+ drain();
873
+ };
874
+
875
+ const drain = () => {
876
+ if (active) return;
877
+ const result = queue.shift();
878
+ if (!result) return;
879
+ const expanded = Array.isArray(result.expandedTools)
880
+ ? result.expandedTools
881
+ .map(normalizeRealtimeVoiceFunctionTool)
882
+ .filter((tool): tool is RealtimeVoiceFunctionTool => Boolean(tool))
883
+ : [];
884
+ if (result.status !== "completed" || expanded.length === 0) {
885
+ send(createRealtimeVoiceToolResultEvent(result));
886
+ send({ type: "response.create" });
887
+ drain();
888
+ return;
889
+ }
890
+
891
+ const merged = mergeRealtimeVoiceToolManifest(currentTools, expanded);
892
+ const mergedNames = new Set(merged.map((tool) => tool.name));
893
+ const expectedNames = expanded
894
+ .map((tool) => tool.name)
895
+ .filter((name) => mergedNames.has(name));
896
+ if (expectedNames.length === 0) {
897
+ send(createRealtimeVoiceToolResultEvent(result));
898
+ send({ type: "response.create" });
899
+ drain();
900
+ return;
901
+ }
902
+
903
+ const eventId = `realtime_tool_manifest_${++updateSequence}`;
904
+ active = {
905
+ result,
906
+ expectedNames,
907
+ eventId,
908
+ timer: setTimeout(
909
+ () => finish("Timed out while installing the discovered tools."),
910
+ timeoutMs,
911
+ ),
912
+ };
913
+ send(createRealtimeVoiceToolManifestUpdate(merged, eventId));
914
+ };
915
+
916
+ return {
917
+ enqueue(result) {
918
+ queue.push(result);
919
+ drain();
920
+ },
921
+ setSessionTools(tools) {
922
+ currentTools = mergeRealtimeVoiceToolManifest([], tools);
923
+ if (!active) return;
924
+ const names = new Set(currentTools.map((tool) => tool.name));
925
+ if (active.expectedNames.every((name) => names.has(name))) finish();
926
+ },
927
+ handleError(eventId, message) {
928
+ if (!active || !eventId || active.eventId !== eventId) return false;
929
+ finish(message || "The provider rejected the discovered tool update.");
930
+ return true;
931
+ },
932
+ reset() {
933
+ if (active) clearTimeout(active.timer);
934
+ active = undefined;
935
+ queue.length = 0;
936
+ currentTools = [];
937
+ },
938
+ getTools() {
939
+ return currentTools;
940
+ },
941
+ };
942
+ }
943
+
179
944
  export function extractRealtimeVoiceFunctionCalls(
180
945
  event: RealtimeServerEvent,
181
946
  ): Array<{ name: string; callId: string; argumentsText: string }> {
@@ -242,6 +1007,13 @@ function openOpenAiKeySettings(): void {
242
1007
  );
243
1008
  }
244
1009
 
1010
+ export function listenForRealtimeVoicePageHide(
1011
+ cleanup: () => void,
1012
+ ): () => void {
1013
+ window.addEventListener("pagehide", cleanup);
1014
+ return () => window.removeEventListener("pagehide", cleanup);
1015
+ }
1016
+
245
1017
  function voiceCopy(t: ReturnType<typeof useT>): RealtimeVoiceModeCopy {
246
1018
  return {
247
1019
  entryButtonLabel: t("agentPanel.voiceMode.entryButtonLabel"),
@@ -257,6 +1029,49 @@ function voiceCopy(t: ReturnType<typeof useT>): RealtimeVoiceModeCopy {
257
1029
  showChat: t("agentPanel.voiceMode.showChat"),
258
1030
  hideChat: t("agentPanel.voiceMode.hideChat"),
259
1031
  endVoiceMode: t("agentPanel.voiceMode.end"),
1032
+ voiceSettings: t("agentPanel.voiceMode.voiceSettings"),
1033
+ settings: {
1034
+ language: t("agentPanel.voiceMode.settings.language"),
1035
+ autoLanguage: t("agentPanel.voiceMode.settings.autoLanguage"),
1036
+ languages: {
1037
+ en: t("agentPanel.voiceMode.settings.languages.en"),
1038
+ es: t("agentPanel.voiceMode.settings.languages.es"),
1039
+ fr: t("agentPanel.voiceMode.settings.languages.fr"),
1040
+ de: t("agentPanel.voiceMode.settings.languages.de"),
1041
+ it: t("agentPanel.voiceMode.settings.languages.it"),
1042
+ pt: t("agentPanel.voiceMode.settings.languages.pt"),
1043
+ ja: t("agentPanel.voiceMode.settings.languages.ja"),
1044
+ ko: t("agentPanel.voiceMode.settings.languages.ko"),
1045
+ zh: t("agentPanel.voiceMode.settings.languages.zh"),
1046
+ },
1047
+ intelligence: t("agentPanel.voiceMode.settings.intelligence"),
1048
+ intelligenceLevels: {
1049
+ instant: t("agentPanel.voiceMode.settings.intelligenceLevels.instant"),
1050
+ balanced: t(
1051
+ "agentPanel.voiceMode.settings.intelligenceLevels.balanced",
1052
+ ),
1053
+ deep: t("agentPanel.voiceMode.settings.intelligenceLevels.deep"),
1054
+ },
1055
+ voiceStyle: t("agentPanel.voiceMode.settings.voiceStyle"),
1056
+ microphone: t("agentPanel.voiceMode.settings.microphone"),
1057
+ defaultMicrophone: t("agentPanel.voiceMode.settings.defaultMicrophone"),
1058
+ microphoneSwitchFailed: t(
1059
+ "agentPanel.voiceMode.settings.microphoneSwitchFailed",
1060
+ ),
1061
+ voiceChangePending: t("agentPanel.voiceMode.settings.voiceChangePending"),
1062
+ voiceDescriptions: {
1063
+ marin: t("agentPanel.voiceMode.settings.voiceDescriptions.marin"),
1064
+ cedar: t("agentPanel.voiceMode.settings.voiceDescriptions.cedar"),
1065
+ coral: t("agentPanel.voiceMode.settings.voiceDescriptions.coral"),
1066
+ sage: t("agentPanel.voiceMode.settings.voiceDescriptions.sage"),
1067
+ verse: t("agentPanel.voiceMode.settings.voiceDescriptions.verse"),
1068
+ alloy: t("agentPanel.voiceMode.settings.voiceDescriptions.alloy"),
1069
+ ash: t("agentPanel.voiceMode.settings.voiceDescriptions.ash"),
1070
+ ballad: t("agentPanel.voiceMode.settings.voiceDescriptions.ballad"),
1071
+ echo: t("agentPanel.voiceMode.settings.voiceDescriptions.echo"),
1072
+ shimmer: t("agentPanel.voiceMode.settings.voiceDescriptions.shimmer"),
1073
+ },
1074
+ },
260
1075
  status: {
261
1076
  connecting: t("agentPanel.voiceMode.status.connecting"),
262
1077
  listening: t("agentPanel.voiceMode.status.listening"),
@@ -270,6 +1085,7 @@ function voiceCopy(t: ReturnType<typeof useT>): RealtimeVoiceModeCopy {
270
1085
  responseFailed: t("agentPanel.voiceMode.errors.responseFailed"),
271
1086
  sessionFailed: t("agentPanel.voiceMode.errors.sessionFailed"),
272
1087
  channelDisconnected: t("agentPanel.voiceMode.errors.channelDisconnected"),
1088
+ connectionTimedOut: t("agentPanel.voiceMode.errors.connectionTimedOut"),
273
1089
  connectionFailed: t("agentPanel.voiceMode.errors.connectionFailed"),
274
1090
  offerFailed: t("agentPanel.voiceMode.errors.offerFailed"),
275
1091
  },
@@ -288,8 +1104,20 @@ function useRealtimeVoiceModeController(
288
1104
  const [state, setState] = useState<"idle" | RealtimeVoiceModeState>("idle");
289
1105
  const [error, setError] = useState<string | null>(null);
290
1106
  const [chatVisible, setChatVisible] = useState(false);
1107
+ const [preferences, setPreferences] = useState(
1108
+ DEFAULT_REALTIME_VOICE_PREFERENCES,
1109
+ );
1110
+ const [voiceChangePending, setVoiceChangePending] = useState(false);
1111
+ const [microphones, setMicrophones] = useState<RealtimeVoiceMicrophone[]>([]);
1112
+ const [microphoneDeviceId, setMicrophoneDeviceId] = useState(
1113
+ readRealtimeVoiceMicrophoneId,
1114
+ );
1115
+ const [microphoneSwitching, setMicrophoneSwitching] = useState(false);
1116
+ const [microphoneError, setMicrophoneError] = useState<string | null>(null);
291
1117
  const [audioLevels] = useState(createRealtimeVoiceAudioLevelStore);
292
1118
  const stateRef = useRef(state);
1119
+ const preferencesRef = useRef(preferences);
1120
+ const hasOutputAudioRef = useRef(false);
293
1121
  const peerRef = useRef<RTCPeerConnection | null>(null);
294
1122
  const channelRef = useRef<RTCDataChannel | null>(null);
295
1123
  const streamRef = useRef<MediaStream | null>(null);
@@ -304,18 +1132,56 @@ function useRealtimeVoiceModeController(
304
1132
  const meterFrameRef = useRef<number | null>(null);
305
1133
  const lastMeterSampleRef = useRef(0);
306
1134
  const abortRef = useRef<AbortController | null>(null);
1135
+ const connectionGateRef = useRef<ReturnType<
1136
+ typeof createRealtimeVoiceConnectionGate
1137
+ > | null>(null);
307
1138
  const handledCallsRef = useRef(new Set<string>());
308
1139
  const sessionIdRef = useRef<string | undefined>(undefined);
1140
+ const capabilityRef = useRef<string | undefined>(undefined);
1141
+ const transportGenerationRef = useRef(0);
309
1142
  const startedAtRef = useRef<string | undefined>(undefined);
310
1143
  const lastUserTextRef = useRef("");
311
1144
  const lastAssistantTextRef = useRef("");
312
1145
  const transcriptThreadIdRef = useRef<string | undefined>(undefined);
313
1146
  const transcriptSequenceRef = useRef(0);
1147
+ const preferencesHydratedRef = useRef(false);
1148
+ const preferencesEditedRef = useRef(false);
1149
+ const preferenceWriteChainRef = useRef<Promise<void>>(Promise.resolve());
1150
+ const toolManifestCoordinator = useMemo(
1151
+ () =>
1152
+ createRealtimeVoiceToolManifestCoordinator((event) => {
1153
+ sendDataChannelEvent(channelRef.current, event);
1154
+ }),
1155
+ [],
1156
+ );
1157
+ const switchMicrophoneRef = useRef<
1158
+ (deviceId: string, force?: boolean) => Promise<void>
1159
+ >(async () => undefined);
314
1160
 
315
1161
  useEffect(() => {
316
1162
  stateRef.current = state;
317
1163
  }, [state]);
318
1164
 
1165
+ useEffect(() => {
1166
+ preferencesRef.current = preferences;
1167
+ }, [preferences]);
1168
+
1169
+ const hydratePreferences = useCallback(async () => {
1170
+ if (preferencesHydratedRef.current) return;
1171
+ try {
1172
+ const stored = await readClientAppState(REALTIME_VOICE_PREFERENCES_KEY);
1173
+ if (!preferencesEditedRef.current && stored != null) {
1174
+ const next = normalizeRealtimeVoicePreferences(stored);
1175
+ preferencesRef.current = next;
1176
+ setPreferences(next);
1177
+ }
1178
+ } catch {
1179
+ // Preferences are optional; keep safe defaults when storage is unavailable.
1180
+ } finally {
1181
+ preferencesHydratedRef.current = true;
1182
+ }
1183
+ }, []);
1184
+
319
1185
  const syncAppState = useCallback(
320
1186
  (nextState: "idle" | RealtimeVoiceModeState) => {
321
1187
  const value =
@@ -328,6 +1194,7 @@ function useRealtimeVoiceModeController(
328
1194
  startedAt: startedAtRef.current,
329
1195
  sessionId: sessionIdRef.current,
330
1196
  browserTabId,
1197
+ preferences: preferencesRef.current,
331
1198
  lastUserText: lastUserTextRef.current || undefined,
332
1199
  lastAssistantText: lastAssistantTextRef.current || undefined,
333
1200
  };
@@ -347,6 +1214,95 @@ function useRealtimeVoiceModeController(
347
1214
  [syncAppState],
348
1215
  );
349
1216
 
1217
+ const savePreferences = useCallback((next: RealtimeVoicePreferences) => {
1218
+ preferencesEditedRef.current = true;
1219
+ preferencesHydratedRef.current = true;
1220
+ preferencesRef.current = next;
1221
+ setPreferences(next);
1222
+ preferenceWriteChainRef.current = preferenceWriteChainRef.current
1223
+ .catch(() => undefined)
1224
+ .then(async () => {
1225
+ await setClientAppState(REALTIME_VOICE_PREFERENCES_KEY, next, {
1226
+ requestSource: REALTIME_VOICE_REQUEST_SOURCE,
1227
+ });
1228
+ })
1229
+ .catch(() => undefined);
1230
+ }, []);
1231
+
1232
+ const updateLivePreferences = useCallback(
1233
+ (next: RealtimeVoicePreferences, includeVoice = false) => {
1234
+ sendDataChannelEvent(
1235
+ channelRef.current,
1236
+ createRealtimeVoicePreferenceUpdate(next, {
1237
+ browserLanguages:
1238
+ typeof navigator === "undefined" ? [] : navigator.languages,
1239
+ includeVoice,
1240
+ }),
1241
+ );
1242
+ },
1243
+ [],
1244
+ );
1245
+
1246
+ const setLanguage = useCallback(
1247
+ (language: RealtimeVoiceLanguage) => {
1248
+ const next = { ...preferencesRef.current, language };
1249
+ savePreferences(next);
1250
+ updateLivePreferences(next);
1251
+ },
1252
+ [savePreferences, updateLivePreferences],
1253
+ );
1254
+
1255
+ const setIntelligence = useCallback(
1256
+ (intelligence: RealtimeVoiceIntelligence) => {
1257
+ const next = { ...preferencesRef.current, intelligence };
1258
+ savePreferences(next);
1259
+ updateLivePreferences(next);
1260
+ },
1261
+ [savePreferences, updateLivePreferences],
1262
+ );
1263
+
1264
+ const setVoice = useCallback(
1265
+ (voice: RealtimeVoice) => {
1266
+ const next = { ...preferencesRef.current, voice };
1267
+ savePreferences(next);
1268
+ if (hasOutputAudioRef.current) {
1269
+ setVoiceChangePending(true);
1270
+ updateLivePreferences(next);
1271
+ return;
1272
+ }
1273
+ setVoiceChangePending(false);
1274
+ updateLivePreferences(next, true);
1275
+ },
1276
+ [savePreferences, updateLivePreferences],
1277
+ );
1278
+
1279
+ const refreshMicrophones = useCallback(async (): Promise<
1280
+ RealtimeVoiceMicrophone[] | null
1281
+ > => {
1282
+ const mediaDevices = navigator.mediaDevices;
1283
+ if (!mediaDevices?.enumerateDevices) return null;
1284
+ try {
1285
+ const devices = await mediaDevices.enumerateDevices();
1286
+ const inputs = devices
1287
+ .filter(
1288
+ (device) =>
1289
+ device.kind === "audioinput" &&
1290
+ device.deviceId &&
1291
+ device.deviceId !== "default" &&
1292
+ device.deviceId !== "communications",
1293
+ )
1294
+ .map((device, index) => ({
1295
+ deviceId: device.deviceId,
1296
+ label: device.label || `Microphone ${index + 1}`,
1297
+ }));
1298
+ setMicrophones(inputs);
1299
+ return inputs;
1300
+ } catch {
1301
+ // Enumeration is progressive enhancement; system default remains usable.
1302
+ return null;
1303
+ }
1304
+ }, []);
1305
+
350
1306
  const startMeterLoop = useCallback(() => {
351
1307
  if (meterFrameRef.current !== null) return;
352
1308
  const sample = (timestamp: number) => {
@@ -418,14 +1374,69 @@ function useRealtimeVoiceModeController(
418
1374
  [startMeterLoop],
419
1375
  );
420
1376
 
1377
+ const setMicrophone = useCallback(
1378
+ async (deviceId: string, force = false) => {
1379
+ if ((!force && deviceId === microphoneDeviceId) || microphoneSwitching) {
1380
+ return;
1381
+ }
1382
+ const mediaDevices = navigator.mediaDevices;
1383
+ const peer = peerRef.current;
1384
+ if (!mediaDevices?.getUserMedia || !peer) return;
1385
+
1386
+ setMicrophoneSwitching(true);
1387
+ setMicrophoneError(null);
1388
+ try {
1389
+ const replacementStream = await replaceRealtimeVoiceMicrophone({
1390
+ mediaDevices,
1391
+ peer,
1392
+ currentStream: streamRef.current,
1393
+ deviceId,
1394
+ });
1395
+ const replacementTrack = replacementStream.getAudioTracks()[0];
1396
+ streamRef.current = replacementStream;
1397
+ if (replacementTrack) {
1398
+ replacementTrack.onended = () => {
1399
+ void switchMicrophoneRef.current("default", true);
1400
+ };
1401
+ }
1402
+ attachAudioMeter(streamRef.current, "input");
1403
+ writeRealtimeVoiceMicrophoneId(deviceId);
1404
+ setMicrophoneDeviceId(deviceId);
1405
+ await refreshMicrophones();
1406
+ } catch {
1407
+ setMicrophoneError(
1408
+ copy?.settings.microphoneSwitchFailed ??
1409
+ "Could not switch microphones. Your current microphone is still active.",
1410
+ );
1411
+ } finally {
1412
+ setMicrophoneSwitching(false);
1413
+ }
1414
+ },
1415
+ [
1416
+ attachAudioMeter,
1417
+ copy,
1418
+ microphoneDeviceId,
1419
+ microphoneSwitching,
1420
+ refreshMicrophones,
1421
+ ],
1422
+ );
1423
+ switchMicrophoneRef.current = setMicrophone;
1424
+
421
1425
  const cleanupTransport = useCallback(() => {
1426
+ connectionGateRef.current?.cancel();
1427
+ connectionGateRef.current = null;
1428
+ transportGenerationRef.current += 1;
1429
+ capabilityRef.current = undefined;
422
1430
  abortRef.current?.abort();
423
1431
  abortRef.current = null;
424
1432
  channelRef.current?.close();
425
1433
  channelRef.current = null;
426
1434
  peerRef.current?.close();
427
1435
  peerRef.current = null;
428
- for (const track of streamRef.current?.getTracks() ?? []) track.stop();
1436
+ for (const track of streamRef.current?.getTracks() ?? []) {
1437
+ track.onended = null;
1438
+ track.stop();
1439
+ }
429
1440
  streamRef.current = null;
430
1441
  if (audioRef.current) {
431
1442
  audioRef.current.pause();
@@ -450,7 +1461,8 @@ function useRealtimeVoiceModeController(
450
1461
  if (audioContext) void audioContext.close().catch(() => undefined);
451
1462
  audioLevels.reset();
452
1463
  handledCallsRef.current.clear();
453
- }, [audioLevels]);
1464
+ toolManifestCoordinator.reset();
1465
+ }, [audioLevels, toolManifestCoordinator]);
454
1466
 
455
1467
  const fail = useCallback(
456
1468
  (message: string, options?: { openKeySettings?: boolean }) => {
@@ -466,6 +1478,8 @@ function useRealtimeVoiceModeController(
466
1478
  async (call: { name: string; callId: string; argumentsText: string }) => {
467
1479
  if (handledCallsRef.current.has(call.callId)) return;
468
1480
  handledCallsRef.current.add(call.callId);
1481
+ const transportGeneration = transportGenerationRef.current;
1482
+ const transportChannel = channelRef.current;
469
1483
  transition("working");
470
1484
  let result: RealtimeVoiceToolResult;
471
1485
  try {
@@ -476,6 +1490,7 @@ function useRealtimeVoiceModeController(
476
1490
  callId: call.callId,
477
1491
  sessionId: sessionIdRef.current,
478
1492
  browserTabId,
1493
+ capability: capabilityRef.current,
479
1494
  signal: abortRef.current?.signal,
480
1495
  });
481
1496
  } catch (toolError) {
@@ -485,24 +1500,21 @@ function useRealtimeVoiceModeController(
485
1500
  output: errorMessage(toolError),
486
1501
  };
487
1502
  }
488
- sendDataChannelEvent(channelRef.current, {
489
- type: "conversation.item.create",
490
- item: {
491
- type: "function_call_output",
492
- call_id: call.callId,
493
- output: JSON.stringify(result),
494
- },
495
- });
496
- sendDataChannelEvent(channelRef.current, { type: "response.create" });
1503
+ if (
1504
+ transportGeneration !== transportGenerationRef.current ||
1505
+ channelRef.current !== transportChannel
1506
+ ) {
1507
+ return;
1508
+ }
1509
+ toolManifestCoordinator.enqueue(result);
497
1510
  },
498
- [browserTabId, transition],
1511
+ [browserTabId, toolManifestCoordinator, transition],
499
1512
  );
500
1513
 
501
- const persistCompletedTranscript = useCallback(
502
- (event: RealtimeServerEvent) => {
503
- const transcript = extractCompletedRealtimeVoiceTranscript(event);
1514
+ const publishCompletedTranscript = useCallback(
1515
+ (transcript: CompletedRealtimeVoiceTranscript) => {
504
1516
  const threadId = transcriptThreadIdRef.current;
505
- if (!transcript || !threadId) return;
1517
+ if (!threadId) return;
506
1518
  const sessionIdentity =
507
1519
  sessionIdRef.current ?? startedAtRef.current ?? "pending";
508
1520
  const providerIdentity =
@@ -518,23 +1530,46 @@ function useRealtimeVoiceModeController(
518
1530
  [],
519
1531
  );
520
1532
 
1533
+ const transcriptSequencer = useMemo(
1534
+ () => createRealtimeVoiceTranscriptSequencer(publishCompletedTranscript),
1535
+ [publishCompletedTranscript],
1536
+ );
1537
+ const greetingStarter = useMemo(
1538
+ () =>
1539
+ createRealtimeVoiceGreetingStarter((event) => {
1540
+ sendDataChannelEvent(channelRef.current, event);
1541
+ }),
1542
+ [],
1543
+ );
1544
+
521
1545
  const handleServerEvent = useCallback(
522
1546
  (event: RealtimeServerEvent) => {
1547
+ transcriptSequencer.handle(event);
1548
+ const sessionTools = extractRealtimeVoiceSessionTools(event);
1549
+ if (sessionTools) toolManifestCoordinator.setSessionTools(sessionTools);
523
1550
  if (event.type === "session.created") {
1551
+ connectionGateRef.current?.markSessionCreated();
1552
+ connectionGateRef.current = null;
524
1553
  const session = event.session;
525
1554
  if (session && typeof session === "object") {
526
1555
  const id = (session as { id?: unknown }).id;
527
1556
  if (typeof id === "string") sessionIdRef.current = id;
528
1557
  }
529
- transition("listening");
1558
+ updateLivePreferences(preferencesRef.current, true);
1559
+ greetingStarter.start();
1560
+ transition("working");
530
1561
  } else if (event.type === "input_audio_buffer.speech_started") {
531
1562
  transition("listening");
532
1563
  } else if (event.type === "input_audio_buffer.speech_stopped") {
533
1564
  transition("working");
534
1565
  } else if (event.type === "response.created") {
1566
+ // From this point onward the response is committed to audio output, so
1567
+ // changing voices risks violating Realtime's per-session voice lock.
1568
+ hasOutputAudioRef.current = true;
535
1569
  lastAssistantTextRef.current = "";
536
1570
  transition("working");
537
1571
  } else if (event.type === "response.output_audio_transcript.delta") {
1572
+ hasOutputAudioRef.current = true;
538
1573
  if (typeof event.delta === "string") {
539
1574
  lastAssistantTextRef.current += event.delta;
540
1575
  }
@@ -543,7 +1578,6 @@ function useRealtimeVoiceModeController(
543
1578
  if (typeof event.transcript === "string") {
544
1579
  lastAssistantTextRef.current = event.transcript;
545
1580
  }
546
- persistCompletedTranscript(event);
547
1581
  syncAppState("speaking");
548
1582
  } else if (
549
1583
  event.type === "conversation.item.input_audio_transcription.completed"
@@ -551,7 +1585,6 @@ function useRealtimeVoiceModeController(
551
1585
  if (typeof event.transcript === "string") {
552
1586
  lastUserTextRef.current = event.transcript;
553
1587
  }
554
- persistCompletedTranscript(event);
555
1588
  syncAppState("working");
556
1589
  } else if (event.type === "response.done") {
557
1590
  const response = event.response;
@@ -574,6 +1607,18 @@ function useRealtimeVoiceModeController(
574
1607
  : typeof detail === "string"
575
1608
  ? detail
576
1609
  : "";
1610
+ const expansionEventId =
1611
+ detail && typeof detail === "object"
1612
+ ? ((detail as { event_id?: unknown }).event_id ?? event.event_id)
1613
+ : event.event_id;
1614
+ if (
1615
+ toolManifestCoordinator.handleError(
1616
+ typeof expansionEventId === "string" ? expansionEventId : undefined,
1617
+ message,
1618
+ )
1619
+ ) {
1620
+ return;
1621
+ }
577
1622
  fail(
578
1623
  message ||
579
1624
  copy?.errors.sessionFailed ||
@@ -591,10 +1636,13 @@ function useRealtimeVoiceModeController(
591
1636
  [
592
1637
  copy,
593
1638
  fail,
1639
+ greetingStarter,
594
1640
  handleFunctionCall,
595
- persistCompletedTranscript,
596
1641
  syncAppState,
1642
+ transcriptSequencer,
597
1643
  transition,
1644
+ toolManifestCoordinator,
1645
+ updateLivePreferences,
598
1646
  ],
599
1647
  );
600
1648
 
@@ -615,25 +1663,62 @@ function useRealtimeVoiceModeController(
615
1663
  lastUserTextRef.current = "";
616
1664
  lastAssistantTextRef.current = "";
617
1665
  sessionIdRef.current = undefined;
1666
+ hasOutputAudioRef.current = false;
1667
+ greetingStarter.reset();
1668
+ setVoiceChangePending(false);
618
1669
  transcriptThreadIdRef.current =
619
1670
  realtimeVoiceTranscriptRegistry.activeThreadId();
620
1671
  transcriptSequenceRef.current = 0;
1672
+ transcriptSequencer.reset();
621
1673
  transition("connecting");
622
1674
  setChatVisible(false);
623
1675
  window.dispatchEvent(new Event("agent-panel:close"));
624
1676
 
1677
+ await hydratePreferences();
1678
+ if ((stateRef.current as string) !== "connecting") return;
1679
+
625
1680
  const abortController = new AbortController();
626
1681
  abortRef.current = abortController;
1682
+ const isCurrentAttempt = () =>
1683
+ abortRef.current === abortController && !abortController.signal.aborted;
1684
+ const connectionGate = createRealtimeVoiceConnectionGate(() => {
1685
+ if (!isCurrentAttempt()) return;
1686
+ fail(
1687
+ copy?.errors.connectionTimedOut ??
1688
+ "The realtime voice connection timed out.",
1689
+ );
1690
+ });
1691
+ connectionGateRef.current = connectionGate;
627
1692
  try {
628
1693
  const stream = await navigator.mediaDevices.getUserMedia({
629
- audio: {
630
- echoCancellation: true,
631
- noiseSuppression: true,
632
- autoGainControl: true,
633
- },
1694
+ audio: createRealtimeVoiceAudioConstraints(
1695
+ readRealtimeVoiceMicrophoneId(),
1696
+ ),
634
1697
  });
1698
+ if (!isCurrentAttempt()) {
1699
+ for (const track of stream.getTracks()) track.stop();
1700
+ return;
1701
+ }
635
1702
  streamRef.current = stream;
1703
+ for (const track of stream.getAudioTracks()) {
1704
+ track.onended = () => {
1705
+ void switchMicrophoneRef.current("default", true);
1706
+ };
1707
+ }
636
1708
  attachAudioMeter(stream, "input");
1709
+ void refreshMicrophones().then((inputs) => {
1710
+ const selected = readRealtimeVoiceMicrophoneId();
1711
+ if (
1712
+ inputs &&
1713
+ selected !== "default" &&
1714
+ !inputs.some((device) => device.deviceId === selected)
1715
+ ) {
1716
+ // The persisted device was only an ideal preference, so getUserMedia
1717
+ // already selected a usable fallback for this new session.
1718
+ writeRealtimeVoiceMicrophoneId("default");
1719
+ setMicrophoneDeviceId("default");
1720
+ }
1721
+ });
637
1722
 
638
1723
  const peer = new RTCPeerConnection();
639
1724
  peerRef.current = peer;
@@ -652,21 +1737,27 @@ function useRealtimeVoiceModeController(
652
1737
 
653
1738
  const channel = peer.createDataChannel("oai-events");
654
1739
  channelRef.current = channel;
655
- channel.onopen = () => transition("listening");
1740
+ channel.onopen = connectionGate.markTransportReady;
656
1741
  channel.onmessage = (messageEvent) => {
1742
+ if (!isCurrentAttempt()) return;
657
1743
  try {
658
1744
  handleServerEvent(JSON.parse(String(messageEvent.data)));
659
1745
  } catch {
660
1746
  // Ignore malformed provider events without ending a healthy call.
661
1747
  }
662
1748
  };
663
- channel.onerror = () =>
1749
+ channel.onerror = () => {
1750
+ if (!isCurrentAttempt()) return;
664
1751
  fail(
665
1752
  copy?.errors.channelDisconnected ??
666
1753
  "The realtime voice control channel disconnected.",
667
1754
  );
1755
+ };
668
1756
  peer.onconnectionstatechange = () => {
669
- if (peer.connectionState === "connected") transition("listening");
1757
+ if (!isCurrentAttempt()) return;
1758
+ if (peer.connectionState === "connected") {
1759
+ connectionGate.markTransportReady();
1760
+ }
670
1761
  if (peer.connectionState === "failed") {
671
1762
  fail(
672
1763
  copy?.errors.connectionFailed ??
@@ -676,28 +1767,65 @@ function useRealtimeVoiceModeController(
676
1767
  };
677
1768
 
678
1769
  const offer = await peer.createOffer();
1770
+ if (!isCurrentAttempt()) return;
679
1771
  await peer.setLocalDescription(offer);
1772
+ if (!isCurrentAttempt()) return;
680
1773
  if (!offer.sdp) {
681
1774
  throw new Error(
682
1775
  copy?.errors.offerFailed ??
683
1776
  "The browser did not create an audio offer.",
684
1777
  );
685
1778
  }
686
- const answerSdp = await createRealtimeVoiceSession(offer.sdp, {
1779
+ const answer = await createRealtimeVoiceSessionWithCapability(offer.sdp, {
687
1780
  browserTabId,
688
1781
  signal: abortController.signal,
1782
+ preferences: preferencesRef.current,
1783
+ browserLanguages: navigator.languages,
689
1784
  });
690
- await peer.setRemoteDescription({ type: "answer", sdp: answerSdp });
1785
+ if (!isCurrentAttempt()) return;
1786
+ capabilityRef.current = answer.capability;
1787
+ await peer.setRemoteDescription({ type: "answer", sdp: answer.sdp });
691
1788
  } catch (startError) {
1789
+ // Ending a connection aborts the SDP request by design. A superseded
1790
+ // attempt must never resurrect the dock or surface that cancellation as
1791
+ // an error after cleanup.
1792
+ if (
1793
+ isRealtimeVoiceAbortError(startError) ||
1794
+ abortController.signal.aborted ||
1795
+ abortRef.current !== abortController
1796
+ ) {
1797
+ return;
1798
+ }
1799
+ if (isRealtimeVoiceSetupRequiredError(startError)) {
1800
+ // Credentials can disappear after the entry-point status check. Treat
1801
+ // the authoritative setup response as a gate, not as an active voice
1802
+ // session failure: clean up the mic/RTC attempt, reopen chat, and
1803
+ // refresh both the chat and voice provider setup surfaces.
1804
+ cleanupTransport();
1805
+ setError(null);
1806
+ startedAtRef.current = undefined;
1807
+ sessionIdRef.current = undefined;
1808
+ transcriptThreadIdRef.current = undefined;
1809
+ transition("idle");
1810
+ setChatVisible(true);
1811
+ window.dispatchEvent(new Event("agent-panel:open"));
1812
+ window.dispatchEvent(new Event("agent-engine:configured-changed"));
1813
+ return;
1814
+ }
692
1815
  const status = (startError as { status?: unknown })?.status;
693
1816
  fail(errorMessage(startError), { openKeySettings: status === 400 });
694
1817
  }
695
1818
  }, [
696
1819
  attachAudioMeter,
697
1820
  browserTabId,
1821
+ cleanupTransport,
698
1822
  copy,
699
1823
  fail,
1824
+ greetingStarter,
700
1825
  handleServerEvent,
1826
+ hydratePreferences,
1827
+ refreshMicrophones,
1828
+ transcriptSequencer,
701
1829
  transition,
702
1830
  ]);
703
1831
 
@@ -736,6 +1864,26 @@ function useRealtimeVoiceModeController(
736
1864
  window.dispatchEvent(new Event("agent-panel:toggle"));
737
1865
  }, []);
738
1866
 
1867
+ useEffect(() => {
1868
+ const mediaDevices = navigator.mediaDevices;
1869
+ if (!mediaDevices?.addEventListener) return;
1870
+ const handleDeviceChange = () => {
1871
+ const selected = readRealtimeVoiceMicrophoneId();
1872
+ void refreshMicrophones().then((inputs) => {
1873
+ if (
1874
+ inputs &&
1875
+ selected !== "default" &&
1876
+ !inputs.some((device) => device.deviceId === selected)
1877
+ ) {
1878
+ void setMicrophone("default");
1879
+ }
1880
+ });
1881
+ };
1882
+ mediaDevices.addEventListener("devicechange", handleDeviceChange);
1883
+ return () =>
1884
+ mediaDevices.removeEventListener("devicechange", handleDeviceChange);
1885
+ }, [refreshMicrophones, setMicrophone]);
1886
+
739
1887
  useEffect(() => {
740
1888
  const onSidebarState = (event: Event) => {
741
1889
  const detail = (event as CustomEvent<AgentSidebarStateChangeDetail>)
@@ -749,7 +1897,13 @@ function useRealtimeVoiceModeController(
749
1897
  window.removeEventListener(SIDEBAR_STATE_CHANGE_EVENT, onSidebarState);
750
1898
  }, []);
751
1899
 
752
- useEffect(() => cleanupTransport, [cleanupTransport]);
1900
+ useEffect(() => {
1901
+ const stopListening = listenForRealtimeVoicePageHide(cleanupTransport);
1902
+ return () => {
1903
+ stopListening();
1904
+ cleanupTransport();
1905
+ };
1906
+ }, [cleanupTransport]);
753
1907
 
754
1908
  return {
755
1909
  state,
@@ -757,6 +1911,16 @@ function useRealtimeVoiceModeController(
757
1911
  errorMessage: error,
758
1912
  chatVisible,
759
1913
  audioLevels,
1914
+ preferences,
1915
+ microphones,
1916
+ microphoneDeviceId,
1917
+ microphoneSwitching,
1918
+ microphoneError,
1919
+ voiceChangePending,
1920
+ setLanguage,
1921
+ setIntelligence,
1922
+ setVoice,
1923
+ setMicrophone,
760
1924
  start,
761
1925
  end,
762
1926
  toggleChat,
@@ -777,6 +1941,77 @@ export function RealtimeVoiceModeProvider({
777
1941
  );
778
1942
  const copy = useRealtimeVoiceModeCopy();
779
1943
  const voice = useRealtimeVoiceModeController(resolvedBrowserTabId, copy);
1944
+ const inlineSettings = useMemo(
1945
+ () => ({
1946
+ dialogLabel: copy.voiceSettings,
1947
+ ...(voice.voiceChangePending
1948
+ ? { appliesNextConversationNote: copy.settings.voiceChangePending }
1949
+ : {}),
1950
+ ...(voice.microphoneError
1951
+ ? { microphoneError: voice.microphoneError }
1952
+ : {}),
1953
+ microphone: {
1954
+ label: copy.settings.microphone,
1955
+ value: voice.microphoneDeviceId,
1956
+ disabled: voice.microphoneSwitching,
1957
+ options: [
1958
+ {
1959
+ value: "default",
1960
+ label: copy.settings.defaultMicrophone,
1961
+ },
1962
+ ...voice.microphones.map((microphone) => ({
1963
+ value: microphone.deviceId,
1964
+ label: microphone.label,
1965
+ })),
1966
+ ],
1967
+ onValueChange: (value: string) => {
1968
+ void voice.setMicrophone(value);
1969
+ },
1970
+ },
1971
+ language: {
1972
+ label: copy.settings.language,
1973
+ value: voice.preferences.language,
1974
+ options: REALTIME_VOICE_LANGUAGES.map((language) => ({
1975
+ value: language,
1976
+ label:
1977
+ language === "auto"
1978
+ ? copy.settings.autoLanguage
1979
+ : copy.settings.languages[language],
1980
+ })),
1981
+ onValueChange: (value: string) => {
1982
+ if (isOneOf(REALTIME_VOICE_LANGUAGES, value)) {
1983
+ voice.setLanguage(value);
1984
+ }
1985
+ },
1986
+ },
1987
+ intelligence: {
1988
+ label: copy.settings.intelligence,
1989
+ value: voice.preferences.intelligence,
1990
+ options: REALTIME_VOICE_INTELLIGENCE_LEVELS.map((intelligence) => ({
1991
+ value: intelligence,
1992
+ label: copy.settings.intelligenceLevels[intelligence],
1993
+ })),
1994
+ onValueChange: (value: string) => {
1995
+ if (isOneOf(REALTIME_VOICE_INTELLIGENCE_LEVELS, value)) {
1996
+ voice.setIntelligence(value);
1997
+ }
1998
+ },
1999
+ },
2000
+ voiceStyle: {
2001
+ label: copy.settings.voiceStyle,
2002
+ value: voice.preferences.voice,
2003
+ options: REALTIME_VOICES.map((voiceName) => ({
2004
+ value: voiceName,
2005
+ label: `${voiceName[0]!.toUpperCase()}${voiceName.slice(1)}`,
2006
+ description: copy.settings.voiceDescriptions[voiceName],
2007
+ })),
2008
+ onValueChange: (value: string) => {
2009
+ if (isOneOf(REALTIME_VOICES, value)) voice.setVoice(value);
2010
+ },
2011
+ },
2012
+ }),
2013
+ [copy, voice],
2014
+ );
780
2015
 
781
2016
  return (
782
2017
  <RealtimeVoiceModeContext.Provider value={voice}>
@@ -790,6 +2025,7 @@ export function RealtimeVoiceModeProvider({
790
2025
  audioLevels={voice.audioLevels}
791
2026
  onToggleChat={voice.toggleChat}
792
2027
  onEndVoiceMode={voice.end}
2028
+ settings={inlineSettings}
793
2029
  errorMessage={voice.errorMessage}
794
2030
  />,
795
2031
  document.body,