@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.
- package/corpus/README.md +2 -2
- package/corpus/core/CHANGELOG.md +56 -0
- package/corpus/core/docs/content/authentication.mdx +3 -3
- package/corpus/core/docs/content/doctor.mdx +170 -0
- package/corpus/core/docs/content/external-agents.mdx +8 -7
- package/corpus/core/docs/content/locales/ar-SA/authentication.mdx +2 -2
- package/corpus/core/docs/content/locales/ar-SA/external-agents.mdx +8 -7
- package/corpus/core/docs/content/locales/ar-SA/mcp-protocol.mdx +1 -1
- package/corpus/core/docs/content/locales/ar-SA/security.mdx +2 -0
- package/corpus/core/docs/content/locales/de-DE/authentication.mdx +1 -1
- package/corpus/core/docs/content/locales/de-DE/external-agents.mdx +8 -7
- package/corpus/core/docs/content/locales/de-DE/mcp-protocol.mdx +1 -1
- package/corpus/core/docs/content/locales/de-DE/security.mdx +2 -0
- package/corpus/core/docs/content/locales/es-ES/authentication.mdx +2 -2
- package/corpus/core/docs/content/locales/es-ES/external-agents.mdx +8 -7
- package/corpus/core/docs/content/locales/es-ES/mcp-protocol.mdx +1 -1
- package/corpus/core/docs/content/locales/es-ES/security.mdx +2 -0
- package/corpus/core/docs/content/locales/fr-FR/authentication.mdx +1 -1
- package/corpus/core/docs/content/locales/fr-FR/external-agents.mdx +8 -7
- package/corpus/core/docs/content/locales/fr-FR/mcp-protocol.mdx +1 -1
- package/corpus/core/docs/content/locales/fr-FR/security.mdx +2 -0
- package/corpus/core/docs/content/locales/hi-IN/authentication.mdx +2 -2
- package/corpus/core/docs/content/locales/hi-IN/external-agents.mdx +8 -7
- package/corpus/core/docs/content/locales/hi-IN/mcp-protocol.mdx +1 -1
- package/corpus/core/docs/content/locales/hi-IN/security.mdx +2 -0
- package/corpus/core/docs/content/locales/ja-JP/authentication.mdx +2 -2
- package/corpus/core/docs/content/locales/ja-JP/external-agents.mdx +8 -7
- package/corpus/core/docs/content/locales/ja-JP/mcp-protocol.mdx +1 -1
- package/corpus/core/docs/content/locales/ja-JP/security.mdx +2 -0
- package/corpus/core/docs/content/locales/ko-KR/authentication.mdx +2 -2
- package/corpus/core/docs/content/locales/ko-KR/external-agents.mdx +8 -7
- package/corpus/core/docs/content/locales/ko-KR/mcp-protocol.mdx +1 -1
- package/corpus/core/docs/content/locales/ko-KR/security.mdx +2 -0
- package/corpus/core/docs/content/locales/pt-BR/authentication.mdx +2 -2
- package/corpus/core/docs/content/locales/pt-BR/external-agents.mdx +8 -7
- package/corpus/core/docs/content/locales/pt-BR/mcp-protocol.mdx +1 -1
- package/corpus/core/docs/content/locales/pt-BR/security.mdx +2 -0
- package/corpus/core/docs/content/locales/zh-CN/authentication.mdx +2 -2
- package/corpus/core/docs/content/locales/zh-CN/external-agents.mdx +8 -7
- package/corpus/core/docs/content/locales/zh-CN/mcp-protocol.mdx +1 -1
- package/corpus/core/docs/content/locales/zh-CN/security.mdx +2 -0
- package/corpus/core/docs/content/locales/zh-TW/authentication.mdx +2 -2
- package/corpus/core/docs/content/locales/zh-TW/external-agents.mdx +8 -7
- package/corpus/core/docs/content/locales/zh-TW/mcp-protocol.mdx +1 -1
- package/corpus/core/docs/content/locales/zh-TW/security.mdx +2 -0
- package/corpus/core/docs/content/mcp-protocol.mdx +1 -1
- package/corpus/core/docs/content/security.mdx +2 -0
- package/corpus/core/package.json +6 -2
- package/corpus/core/scripts/finalize-build.mjs +63 -3
- package/corpus/core/scripts/materialize-source-corpus.mjs +101 -15
- package/corpus/core/src/action-change-marker.ts +8 -1
- package/corpus/core/src/action.ts +42 -0
- package/corpus/core/src/agent/engine/builder-engine.ts +3 -0
- package/corpus/core/src/agent/engine/context-directives-transform.ts +69 -0
- package/corpus/core/src/agent/engine/continuation-dispatch-retry.ts +168 -0
- package/corpus/core/src/agent/engine/tool-call-journal-seed.ts +85 -0
- package/corpus/core/src/agent/production-agent.ts +129 -244
- package/corpus/core/src/changelog/parse.ts +8 -3
- package/corpus/core/src/cli/doctor.ts +9 -3
- package/corpus/core/src/cli/plan-local.ts +10 -2
- package/corpus/core/src/cli/skills-content/assets-skill.ts +83 -0
- package/corpus/core/src/cli/skills-content/canvas.ts +153 -0
- package/corpus/core/src/cli/skills-content/connection.ts +52 -0
- package/corpus/core/src/cli/skills-content/content-skill.ts +109 -0
- package/corpus/core/src/cli/skills-content/design-exploration-skill.ts +118 -0
- package/corpus/core/src/cli/skills-content/design-visual-edit-skill.ts +228 -0
- package/corpus/core/src/cli/skills-content/document-quality.ts +196 -0
- package/corpus/core/src/cli/skills-content/exemplar.ts +69 -0
- package/corpus/core/src/cli/skills-content/help.ts +77 -0
- package/corpus/core/src/cli/skills-content/index.ts +22 -0
- package/corpus/core/src/cli/skills-content/local-files.ts +95 -0
- package/corpus/core/src/cli/skills-content/plan-setup-auth.ts +80 -0
- package/corpus/core/src/cli/skills-content/visual-plan-skill.ts +470 -0
- package/corpus/core/src/cli/skills-content/visual-recap-skill.ts +547 -0
- package/corpus/core/src/cli/skills-content/visualize-repo-skill.ts +93 -0
- package/corpus/core/src/cli/skills-content/wireframe.ts +349 -0
- package/corpus/core/src/cli/skills.ts +27 -2733
- package/corpus/core/src/client/AgentPanel.tsx +8 -8
- package/corpus/core/src/client/AssistantChat.tsx +83 -39
- package/corpus/core/src/client/CommandMenu.tsx +16 -5
- package/corpus/core/src/client/FeedbackButton.tsx +2 -2
- package/corpus/core/src/client/analytics.ts +29 -0
- package/corpus/core/src/client/app-providers.tsx +18 -5
- package/corpus/core/src/client/blocks/library/wireframe-kit.tsx +16 -2
- package/corpus/core/src/client/chat/tool-call-display.tsx +3 -3
- package/corpus/core/src/client/chat-model-groups.ts +28 -0
- package/corpus/core/src/client/composer/PastedTextChip.tsx +1 -1
- package/corpus/core/src/client/composer/RealtimeVoiceMode.tsx +523 -68
- package/corpus/core/src/client/composer/TiptapComposer.tsx +47 -30
- package/corpus/core/src/client/composer/VoiceButton.tsx +22 -5
- package/corpus/core/src/client/composer/index.ts +5 -0
- package/corpus/core/src/client/composer/useRealtimeVoiceMode.tsx +1271 -35
- package/corpus/core/src/client/context-xray/ContextXRayPanel.tsx +5 -2
- package/corpus/core/src/client/extensions/ExtensionEditor.tsx +37 -4
- package/corpus/core/src/client/extensions/ExtensionQueryErrorState.tsx +47 -0
- package/corpus/core/src/client/extensions/ExtensionSlot.tsx +39 -10
- package/corpus/core/src/client/extensions/ExtensionViewer.tsx +44 -6
- package/corpus/core/src/client/extensions/ExtensionsListPage.tsx +12 -3
- package/corpus/core/src/client/extensions/ExtensionsSidebarSection.tsx +49 -11
- package/corpus/core/src/client/index.ts +5 -13
- package/corpus/core/src/client/progress/RunsTray.tsx +23 -4
- package/corpus/core/src/client/settings/SecretsSection.tsx +387 -266
- package/corpus/core/src/client/settings/SettingsPanel.tsx +117 -38
- package/corpus/core/src/client/settings/SettingsTabsPage.tsx +7 -0
- package/corpus/core/src/client/settings/useBuilderStatus.ts +6 -0
- package/corpus/core/src/client/tool-cells/EditCell.tsx +19 -16
- package/corpus/core/src/client/tool-cells/FilesChangedSummary.tsx +26 -23
- package/corpus/core/src/client/tool-cells/WriteCell.tsx +19 -16
- package/corpus/core/src/client/use-action.ts +12 -0
- package/corpus/core/src/client/use-agent-chat-home-handoff.ts +8 -1
- package/corpus/core/src/client/use-agent-engine-configured.ts +17 -3
- package/corpus/core/src/client/use-db-sync.ts +84 -34
- package/corpus/core/src/client/use-pausing-interval.ts +14 -4
- package/corpus/core/src/client/visual-style-controls.tsx +1 -1
- package/corpus/core/src/deploy/build.ts +36 -14
- package/corpus/core/src/integrations/google-docs-poller.ts +23 -2
- package/corpus/core/src/integrations/plugin.ts +10 -0
- package/corpus/core/src/integrations/scope-store.ts +1 -1
- package/corpus/core/src/integrations/usage-budget-store.ts +1 -1
- package/corpus/core/src/integrations/webhook-handler.ts +27 -2
- package/corpus/core/src/jobs/scheduler.ts +28 -2
- package/corpus/core/src/localization/default-messages.ts +51 -1
- package/corpus/core/src/mcp/oauth-token.ts +6 -1
- package/corpus/core/src/mcp-client/workspace-servers.ts +4 -4
- package/corpus/core/src/observability/routes.ts +36 -0
- package/corpus/core/src/observability/sentiment.ts +261 -0
- package/corpus/core/src/observability/store.ts +23 -0
- package/corpus/core/src/observability/traces.ts +49 -63
- package/corpus/core/src/observability/tracking-identity.ts +55 -0
- package/corpus/core/src/observability/types.ts +13 -0
- package/corpus/core/src/secrets/crypto.ts +28 -8
- package/corpus/core/src/secrets/storage.ts +67 -3
- package/corpus/core/src/server/action-change.ts +5 -1
- package/corpus/core/src/server/action-routes.ts +8 -0
- package/corpus/core/src/server/agent-chat/action-filters-a2a.ts +199 -0
- package/corpus/core/src/server/agent-chat/browser-team-tools.ts +510 -0
- package/corpus/core/src/server/agent-chat/context-tools.ts +595 -0
- package/corpus/core/src/server/agent-chat/framework-prompts.ts +470 -0
- package/corpus/core/src/server/agent-chat/lazy-fs.ts +11 -0
- package/corpus/core/src/server/agent-chat/mcp-glue.ts +91 -0
- package/corpus/core/src/server/agent-chat/plugin-options.ts +350 -0
- package/corpus/core/src/server/agent-chat/process-run-failure.ts +62 -0
- package/corpus/core/src/server/agent-chat/prompt-resources.ts +688 -0
- package/corpus/core/src/server/agent-chat/recurring-jobs-runtime.ts +80 -0
- package/corpus/core/src/server/agent-chat/request-surface.ts +71 -0
- package/corpus/core/src/server/agent-chat/run-code-tools.ts +67 -0
- package/corpus/core/src/server/agent-chat/script-entries.ts +779 -0
- package/corpus/core/src/server/agent-chat/shared-thread.ts +301 -0
- package/corpus/core/src/server/agent-chat/skill-frontmatter.ts +41 -0
- package/corpus/core/src/server/agent-chat-plugin.ts +364 -4182
- package/corpus/core/src/server/agent-teams.ts +26 -3
- package/corpus/core/src/server/auth.ts +55 -57
- package/corpus/core/src/server/credential-provider.ts +22 -25
- package/corpus/core/src/server/edge.ts +1 -0
- package/corpus/core/src/server/onboarding-html.ts +1 -16
- package/corpus/core/src/server/poll.ts +1 -0
- package/corpus/core/src/server/realtime-voice.ts +335 -14
- package/corpus/core/src/server/request-origin.ts +6 -2
- package/corpus/core/src/server/ssr-handler.ts +55 -2
- package/corpus/core/src/sharing/access.ts +155 -11
- package/corpus/core/src/sharing/registry.ts +7 -0
- package/corpus/core/src/styles/agent-native.css +17 -16
- package/corpus/core/src/templates/default/.agents/skills/frontend-design/SKILL.md +1 -1
- package/corpus/core/src/templates/default/.agents/skills/real-time-sync/SKILL.md +2 -2
- package/corpus/core/src/templates/default/app/i18n/ar-SA.ts +6 -0
- package/corpus/core/src/templates/default/app/i18n/de-DE.ts +6 -0
- package/corpus/core/src/templates/default/app/i18n/en-US.ts +6 -0
- package/corpus/core/src/templates/default/app/i18n/es-ES.ts +6 -0
- package/corpus/core/src/templates/default/app/i18n/fr-FR.ts +6 -0
- package/corpus/core/src/templates/default/app/i18n/hi-IN.ts +6 -0
- package/corpus/core/src/templates/default/app/i18n/ja-JP.ts +6 -0
- package/corpus/core/src/templates/default/app/i18n/ko-KR.ts +6 -0
- package/corpus/core/src/templates/default/app/i18n/pt-BR.ts +6 -0
- package/corpus/core/src/templates/default/app/i18n/zh-CN.ts +6 -0
- package/corpus/core/src/templates/default/app/i18n/zh-TW.ts +6 -0
- package/corpus/core/src/templates/workspace-core/.agents/skills/authentication/SKILL.md +27 -29
- package/corpus/core/src/templates/workspace-core/.agents/skills/external-agents/SKILL.md +2 -1
- package/corpus/core/src/templates/workspace-core/.agents/skills/frontend-design/SKILL.md +1 -1
- package/corpus/core/src/templates/workspace-core/.agents/skills/observability/SKILL.md +32 -0
- package/corpus/core/src/templates/workspace-core/.agents/skills/real-time-sync/SKILL.md +2 -2
- package/corpus/core/src/templates/workspace-core/.agents/skills/secrets/SKILL.md +7 -3
- package/corpus/core/src/templates/workspace-core/.agents/skills/voice-transcription/SKILL.md +39 -9
- package/corpus/core/src/templates/workspace-core/.agents/skills/writing-agent-instructions/SKILL.md +25 -0
- package/corpus/core/src/templates/workspace-core/src/server/index.ts +4 -2
- package/corpus/core/src/testing.ts +26 -0
- package/corpus/core/src/triggers/dispatcher.ts +23 -2
- package/corpus/core/src/vite/client.ts +135 -3
- package/corpus/core/tsconfig.cli.json +1 -1
- package/corpus/core/tsconfig.json +5 -1
- package/corpus/templates/analytics/.agents/skills/bigquery/SKILL.md +2 -2
- package/corpus/templates/analytics/.agents/skills/frontend-design/SKILL.md +1 -1
- package/corpus/templates/analytics/.agents/skills/real-time-sync/SKILL.md +2 -2
- package/corpus/templates/analytics/.agents/skills/session-replay/SKILL.md +10 -5
- package/corpus/templates/analytics/AGENTS.md +15 -6
- package/corpus/templates/analytics/actions/install-dashboard-template.ts +79 -43
- package/corpus/templates/analytics/actions/list-explorer-dashboards.ts +3 -3
- package/corpus/templates/analytics/actions/list-sql-dashboards.ts +8 -3
- package/corpus/templates/analytics/actions/navigate.ts +59 -5
- package/corpus/templates/analytics/actions/rename-analysis.ts +8 -2
- package/corpus/templates/analytics/actions/rename-dashboard.ts +8 -7
- package/corpus/templates/analytics/actions/view-screen.ts +7 -29
- package/corpus/templates/analytics/app/components/ProviderCorpusJobNotifier.tsx +20 -3
- package/corpus/templates/analytics/app/components/ResourceLoadError.tsx +42 -0
- package/corpus/templates/analytics/app/components/dashboard/SqlChart.tsx +2 -1
- package/corpus/templates/analytics/app/components/layout/CommandPalette.tsx +85 -70
- package/corpus/templates/analytics/app/components/layout/Header.tsx +1 -1
- package/corpus/templates/analytics/app/components/layout/Sidebar.tsx +128 -95
- package/corpus/templates/analytics/app/components/layout/SidebarLoadError.tsx +24 -0
- package/corpus/templates/analytics/app/global.css +19 -20
- package/corpus/templates/analytics/app/hooks/use-dashboard-views.ts +15 -5
- package/corpus/templates/analytics/app/i18n/zh-TW.ts +4 -0
- package/corpus/templates/analytics/app/i18n-data.ts +40 -0
- package/corpus/templates/analytics/app/lib/provider-corpus-job-sync.ts +26 -0
- package/corpus/templates/analytics/app/pages/Settings.tsx +41 -343
- package/corpus/templates/analytics/app/pages/adhoc/explorer/components/SqlPreview.tsx +36 -20
- package/corpus/templates/analytics/app/pages/adhoc/explorer/index.tsx +34 -20
- package/corpus/templates/analytics/app/pages/adhoc/explorer/use-explorer-config.ts +42 -41
- package/corpus/templates/analytics/app/pages/adhoc/explorer-dashboard/index.tsx +43 -36
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/ViewsMenu.tsx +11 -2
- package/corpus/templates/analytics/app/pages/monitoring/ErrorsPanel.tsx +38 -7
- package/corpus/templates/analytics/app/pages/monitoring/MonitoringPage.tsx +1 -1
- package/corpus/templates/analytics/app/pages/monitoring/errors/i18n.ts +3 -0
- package/corpus/templates/analytics/app/pages/sessions/SessionDetailPage.tsx +333 -272
- package/corpus/templates/analytics/app/pages/sessions/SessionDevToolsPanel.tsx +72 -17
- package/corpus/templates/analytics/app/pages/sessions/SessionsPage.tsx +297 -108
- package/corpus/templates/analytics/app/root.tsx +40 -8
- package/corpus/templates/analytics/app/routes/_index.tsx +14 -2
- package/corpus/templates/analytics/changelog/2026-06-24-first-party-signup-and-pageview-panels-now-honor-dashboard-filters.md +2 -1
- package/corpus/templates/analytics/changelog/2026-06-25-referrer-dashboard-panels-show-external-domains.md +2 -1
- package/corpus/templates/analytics/changelog/2026-07-03-agent-llm-observability-dashboard.md +1 -1
- package/corpus/templates/analytics/changelog/2026-07-06-mobile-chat-header-is-more-compact-and-the-menu-button-opens-navigation.md +1 -0
- package/corpus/templates/analytics/changelog/2026-07-08-dashboard-history-and-chat-context.md +1 -0
- package/corpus/templates/analytics/changelog/2026-07-08-settings-are-cleaner-searchable-and-alerts-have-their-own-tab.md +1 -0
- package/corpus/templates/analytics/changelog/2026-07-08-sidebar-sections-stay-collapsed-until-opened.md +1 -0
- package/corpus/templates/analytics/changelog/2026-07-11-agent-feedback-sentiment-dashboard.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-11-analytics-chats-now-start-faster-by-loading-metric-definitio.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-11-analytics-no-longer-repeatedly-restarts-background-requests-.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-11-analytics-only-asks-for-replay-storage-when-you-enable-sessi.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-11-charts-now-show-their-loading-skeleton-while-refreshing.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-11-command-and-sql-previews-now-respond-without-lag.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-11-command-search-now-reports-loading-failures-and-lets-you-ret.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-11-dashboard-navigation-now-loads-faster-and-reports-request-fa.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-11-inferred-message-sentiment-dashboard.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-11-microphone-settings-from-realtime-voice-mode-now-open-the-vo.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-11-more-dashboard-edit-paths-are-now-safe-against-simultaneous-.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-11-realtime-voice-now-shows-a-clear-connection-indicator-comple.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-11-renaming-a-dashboard-is-now-safe-against-simultaneous-agent-.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-11-renaming-an-analysis-is-now-safe-against-simultaneous-edits.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-11-replay-event-counts-stay-hidden-until-recordings-finish-load.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-11-replay-storage-key-mismatches-show-actionable-setup-guidance.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-11-replay-timelines-group-continuous-scrolling-and-dev-tools-co.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-11-session-filters-now-stay-compact-in-a-settings-menu-at-mediu.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-11-session-replay-console-errors-now-link-to-matching-issues-or.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-11-session-replays-load-large-recordings-faster-and-surface-fai.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-11-session-replays-preserve-recorded-styles-and-viewport-layout.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-11-session-replays-stay-centered-instead-of-clipping-wide-recor.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-11-workspace-mounted-analytics-now-opens-ask-reliably-and-route.md +6 -0
- package/corpus/templates/analytics/docs/schemas/first-party-analytics.md +37 -3
- package/corpus/templates/analytics/package.json +1 -1
- package/corpus/templates/analytics/seeds/dashboards/agent-native-templates-first-party.json +37 -1
- package/corpus/templates/analytics/server/handlers/session-replay.ts +57 -0
- package/corpus/templates/analytics/server/lib/dashboard-catalog.ts +1 -24
- package/corpus/templates/analytics/server/lib/dashboard-seeds.ts +0 -5
- package/corpus/templates/analytics/server/lib/dashboards-store.ts +289 -20
- package/corpus/templates/analytics/server/lib/data-dictionary-context.ts +106 -34
- package/corpus/templates/analytics/server/lib/demo-dashboards.ts +20 -4
- package/corpus/templates/analytics/server/lib/session-replay-agent-context.ts +12 -0
- package/corpus/templates/analytics/server/lib/session-replay.ts +208 -2
- package/corpus/templates/analytics/server/plugins/agent-chat.ts +11 -39
- package/corpus/templates/analytics/server/plugins/onboarding.ts +7 -79
- package/corpus/templates/analytics/server/routes/api/session-replay/recordings/[recordingId]/chunks.get.ts +1 -0
- package/corpus/templates/assets/.agents/skills/frontend-design/SKILL.md +1 -1
- package/corpus/templates/assets/.agents/skills/real-time-sync/SKILL.md +2 -2
- package/corpus/templates/assets/app/components/create/RecentDraftsSection.tsx +30 -4
- package/corpus/templates/assets/app/global.css +4 -4
- package/corpus/templates/assets/app/routes/brand-kits.$id.tsx +0 -1
- package/corpus/templates/assets/app/routes/library.tsx +49 -6
- package/corpus/templates/assets/changelog/2026-07-11-library-load-failures-now-show-a-retry-action.md +6 -0
- package/corpus/templates/brain/.agents/skills/frontend-design/SKILL.md +1 -1
- package/corpus/templates/brain/.agents/skills/real-time-sync/SKILL.md +2 -2
- package/corpus/templates/brain/app/global.css +5 -5
- package/corpus/templates/brain/app/root.tsx +0 -3
- package/corpus/templates/calendar/.agents/skills/frontend-design/SKILL.md +1 -1
- package/corpus/templates/calendar/.agents/skills/real-time-sync/SKILL.md +2 -2
- package/corpus/templates/calendar/.env.example +5 -0
- package/corpus/templates/calendar/app/components/calendar/EventCard.tsx +2 -2
- package/corpus/templates/calendar/app/i18n/zh-TW.ts +2 -0
- package/corpus/templates/calendar/app/i18n-data.ts +20 -0
- package/corpus/templates/calendar/app/pages/BookingLinksPage.tsx +37 -1
- package/corpus/templates/calendar/app/pages/BookingsList.tsx +20 -3
- package/corpus/templates/calendar/app/root.tsx +1 -0
- package/corpus/templates/calendar/changelog/2026-07-11-booking-load-failures-now-show-a-retry-action.md +6 -0
- package/corpus/templates/calendar/changelog/2026-07-11-event-cards-give-subtle-press-feedback-and-smoother-hover.md +6 -0
- package/corpus/templates/chat/.agents/skills/frontend-design/SKILL.md +1 -1
- package/corpus/templates/chat/.agents/skills/real-time-sync/SKILL.md +2 -2
- package/corpus/templates/clips/.agents/skills/frontend-design/SKILL.md +1 -1
- package/corpus/templates/clips/actions/decline-invite.ts +8 -0
- package/corpus/templates/clips/app/components/library/folder-tree.tsx +209 -187
- package/corpus/templates/clips/app/components/meetings/meeting-card.tsx +2 -2
- package/corpus/templates/clips/app/components/player/reactions-tray.tsx +17 -5
- package/corpus/templates/clips/app/components/player/scrubber.tsx +5 -3
- package/corpus/templates/clips/app/components/recorder/pre-record-panel.tsx +2 -2
- package/corpus/templates/clips/app/components/workspace/insights-hub.tsx +119 -95
- package/corpus/templates/clips/app/global.css +45 -0
- package/corpus/templates/clips/app/hooks/use-auto-title.ts +74 -8
- package/corpus/templates/clips/app/hooks/use-library.ts +12 -25
- package/corpus/templates/clips/app/root.tsx +4 -0
- package/corpus/templates/clips/app/routes/_app.notifications.tsx +23 -2
- package/corpus/templates/clips/app/routes/_app.settings._index.tsx +20 -22
- package/corpus/templates/clips/app/routes/_app.trash.tsx +24 -2
- package/corpus/templates/clips/changelog/2026-07-11-controls-and-folders-now-move-more-smoothly.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-11-declining-an-invitation-now-requires-being-its-recipient.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-11-generated-titles-no-longer-cause-continuous-background-refreshes.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-11-recording-overlays-are-lighter-weight-and-no-longer-cause-pa.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-11-s3-storage-setup-stays-collapsed-until-you-choose-it-as-the-.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-11-workspace-load-failures-now-show-a-retry-action.md +6 -0
- package/corpus/templates/clips/chrome-extension/src/overlay.css +34 -5
- package/corpus/templates/clips/chrome-extension/src/styles.css +2 -2
- package/corpus/templates/clips/desktop/src/styles.css +71 -26
- package/corpus/templates/content/.agents/skills/frontend-design/SKILL.md +1 -1
- package/corpus/templates/content/.agents/skills/real-time-sync/SKILL.md +2 -2
- package/corpus/templates/content/actions/move-document.ts +34 -18
- package/corpus/templates/content/app/components/QueryErrorState.tsx +39 -0
- package/corpus/templates/content/app/components/editor/LinkHoverPreview.tsx +1 -1
- package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +225 -1435
- package/corpus/templates/content/app/components/editor/database/GalleryView.tsx +368 -0
- package/corpus/templates/content/app/components/editor/database/ListView.tsx +353 -0
- package/corpus/templates/content/app/components/editor/database/TimelineView.tsx +580 -0
- package/corpus/templates/content/app/components/sidebar/DocumentSidebar.tsx +11 -2
- package/corpus/templates/content/app/global.css +19 -19
- package/corpus/templates/content/app/hooks/use-comments.ts +0 -1
- package/corpus/templates/content/app/root.tsx +2 -2
- package/corpus/templates/content/app/routes/_app._index.tsx +11 -1
- package/corpus/templates/content/changelog/2026-07-11-comments-now-update-from-live-collaboration-events-without-c.md +6 -0
- package/corpus/templates/content/changelog/2026-07-11-document-load-failures-now-show-a-retry-action.md +6 -0
- package/corpus/templates/content/changelog/2026-07-11-editor-toggle-feedback-now-feels-more-responsive.md +6 -0
- package/corpus/templates/content/changelog/2026-07-11-moving-documents-at-the-same-time-no-longer-scrambles-their-.md +6 -0
- package/corpus/templates/content/changelog/2026-07-11-public-document-chats-now-start-faster-while-keeping-full-do.md +6 -0
- package/corpus/templates/content/changelog/2026-07-11-reopening-a-comment-now-uses-consistent-permissions-everywhe.md +6 -0
- package/corpus/templates/content/changelog/2026-07-11-sidebar-resizing-now-tracks-your-cursor-instantly-instead-of.md +6 -0
- package/corpus/templates/content/server/lib/public-documents.ts +47 -10
- package/corpus/templates/content/server/routes/api/comments/[id].patch.ts +24 -24
- package/corpus/templates/design/.agents/skills/frontend-design/SKILL.md +1 -1
- package/corpus/templates/design/app/components/QueryErrorState.tsx +24 -0
- package/corpus/templates/design/app/components/design/CanvasContextMenu.tsx +1 -1
- package/corpus/templates/design/app/components/design/DesignImportPanel.tsx +5 -2
- package/corpus/templates/design/app/components/design/EditPanel.tsx +326 -319
- package/corpus/templates/design/app/components/design/LayersPanel.tsx +198 -193
- package/corpus/templates/design/app/components/design/edit-panel/panel-primitives.tsx +11 -4
- package/corpus/templates/design/app/components/design/inspector/DesignColorPicker.tsx +1 -1
- package/corpus/templates/design/app/components/layout/Header.tsx +1 -1
- package/corpus/templates/design/app/components/layout/Layout.tsx +1 -1
- package/corpus/templates/design/app/pages/DesignSystems.tsx +7 -1
- package/corpus/templates/design/app/pages/Index.tsx +14 -2
- package/corpus/templates/design/app/pages/Present.tsx +16 -3
- package/corpus/templates/design/app/pages/Templates.tsx +10 -4
- package/corpus/templates/design/app/pages/design-editor/canvas-primitive-insert.ts +484 -0
- package/corpus/templates/design/app/pages/design-editor/clone-and-pen-edit.ts +378 -0
- package/corpus/templates/design/app/pages/design-editor/design-data-geometry-utils.ts +138 -0
- package/corpus/templates/design/app/pages/design-editor/dom-utils.ts +15 -0
- package/corpus/templates/design/app/pages/design-editor/generation-prompt-directives.ts +180 -0
- package/corpus/templates/design/app/pages/design-editor/html-layer-positioning.ts +226 -0
- package/corpus/templates/design/app/pages/design-editor/png-export-render.ts +348 -0
- package/corpus/templates/design/app/pages/design-editor/screen-command-utils.ts +160 -0
- package/corpus/templates/design/app/pages/design-editor/text-edit-utils.ts +238 -0
- package/corpus/templates/design/app/pages/design-editor/tool-state.ts +23 -1
- package/corpus/templates/design/app/pages/design-editor/types.ts +25 -0
- package/corpus/templates/design/app/root.tsx +10 -1
- package/corpus/templates/design/changelog/2026-07-11-canvas-context-menus-now-open-without-motion-jitter.md +6 -0
- package/corpus/templates/design/changelog/2026-07-11-design-load-failures-now-show-a-retry-action.md +6 -0
- package/corpus/templates/design/changelog/2026-07-11-inspector-tooltips-appear-instantly-as-you-sweep-across-icon.md +6 -0
- package/corpus/templates/dispatch/.agents/skills/frontend-design/SKILL.md +1 -1
- package/corpus/templates/dispatch/app/i18n/zh-TW.ts +10 -0
- package/corpus/templates/dispatch/app/i18n-data.ts +123 -0
- package/corpus/templates/dispatch/app/routes/integrations.tsx +163 -144
- package/corpus/templates/dispatch/changelog/2026-07-11-dispatch-mcp-connections-now-start-with-safer-app-access-and.md +6 -0
- package/corpus/templates/dispatch/changelog/2026-07-11-overview-and-chat-now-share-a-cleaner-more-stable-flow.md +6 -0
- package/corpus/templates/dispatch/changelog/2026-07-11-query-errors-now-show-a-retry-option.md +6 -0
- package/corpus/templates/forms/.agents/skills/frontend-design/SKILL.md +1 -1
- package/corpus/templates/forms/.agents/skills/real-time-sync/SKILL.md +2 -2
- package/corpus/templates/forms/app/components/layout/Sidebar.tsx +3 -3
- package/corpus/templates/forms/app/root.tsx +34 -8
- package/corpus/templates/macros/.agents/skills/frontend-design/SKILL.md +1 -1
- package/corpus/templates/macros/app/components/DailyProgress.tsx +25 -5
- package/corpus/templates/macros/app/components/QueryErrorState.tsx +31 -0
- package/corpus/templates/macros/app/components/WeightTracker.tsx +5 -1
- package/corpus/templates/macros/app/components/layout/AppLayout.tsx +1 -2
- package/corpus/templates/macros/app/components/ui/button.tsx +1 -1
- package/corpus/templates/macros/app/components/ui/progress.tsx +1 -1
- package/corpus/templates/macros/app/components/ui/tabs.tsx +1 -1
- package/corpus/templates/macros/app/i18n/ar-SA.ts +2 -0
- package/corpus/templates/macros/app/i18n/de-DE.ts +2 -0
- package/corpus/templates/macros/app/i18n/en-US.ts +2 -0
- package/corpus/templates/macros/app/i18n/es-ES.ts +2 -0
- package/corpus/templates/macros/app/i18n/fr-FR.ts +2 -0
- package/corpus/templates/macros/app/i18n/hi-IN.ts +2 -0
- package/corpus/templates/macros/app/i18n/ja-JP.ts +2 -0
- package/corpus/templates/macros/app/i18n/ko-KR.ts +2 -0
- package/corpus/templates/macros/app/i18n/pt-BR.ts +2 -0
- package/corpus/templates/macros/app/i18n/zh-CN.ts +2 -0
- package/corpus/templates/macros/app/i18n/zh-TW.ts +2 -0
- package/corpus/templates/macros/app/root.tsx +0 -3
- package/corpus/templates/macros/app/routes/_index.tsx +22 -12
- package/corpus/templates/macros/app/routes/analytics.tsx +31 -10
- package/corpus/templates/macros/changelog/2026-07-11-daily-tracking-now-loads-and-updates-more-quickly.md +6 -0
- package/corpus/templates/macros/changelog/2026-07-11-data-load-failures-now-show-a-clear-error-with-a-retry-actio.md +6 -0
- package/corpus/templates/mail/.agents/skills/frontend-design/SKILL.md +1 -1
- package/corpus/templates/mail/.agents/skills/real-time-sync/SKILL.md +2 -2
- package/corpus/templates/mail/.env.example +16 -11
- package/corpus/templates/mail/actions/list-emails.ts +39 -35
- package/corpus/templates/mail/app/components/email/ComposeModal.tsx +58 -6
- package/corpus/templates/mail/app/components/email/EmailListItem.tsx +35 -2
- package/corpus/templates/mail/app/components/layout/AppLayout.tsx +113 -104
- package/corpus/templates/mail/app/hooks/use-compose-state.ts +1 -0
- package/corpus/templates/mail/app/root.tsx +9 -22
- package/corpus/templates/mail/changelog/2026-07-11-compose-and-navigation-motion-now-feel-faster-and-smoother.md +6 -0
- package/corpus/templates/mail/changelog/2026-07-11-swipe-to-archive-or-snooze-now-responds-to-quick-flicks-not-.md +6 -0
- package/corpus/templates/mail/changelog/2026-07-11-the-assistant-now-sees-the-same-inbox-you-do-snoozed-mail-st.md +6 -0
- package/corpus/templates/mail/server/handlers/emails.ts +25 -69
- package/corpus/templates/mail/server/lib/list-inbox-emails.ts +169 -0
- package/corpus/templates/plan/.agents/skills/frontend-design/SKILL.md +1 -1
- package/corpus/templates/plan/.agents/skills/real-time-sync/SKILL.md +2 -2
- package/corpus/templates/plan/.env.example +4 -0
- package/corpus/templates/plan/actions/create-plan-design.ts +89 -81
- package/corpus/templates/plan/actions/create-prototype-plan.ts +72 -59
- package/corpus/templates/plan/actions/create-ui-plan.ts +70 -55
- package/corpus/templates/plan/actions/create-visual-plan.ts +65 -51
- package/corpus/templates/plan/actions/update-local-plan-folder.ts +43 -30
- package/corpus/templates/plan/actions/update-visual-plan.ts +81 -64
- package/corpus/templates/plan/app/components/layout/Sidebar.tsx +17 -0
- package/corpus/templates/plan/app/components/ui/tabs.tsx +1 -1
- package/corpus/templates/plan/app/hooks/use-navigation-state.ts +5 -2
- package/corpus/templates/plan/app/hooks/use-plans.ts +6 -13
- package/corpus/templates/plan/app/i18n/ar-SA.ts +10 -0
- package/corpus/templates/plan/app/i18n/de-DE.ts +10 -0
- package/corpus/templates/plan/app/i18n/en-US.ts +10 -0
- package/corpus/templates/plan/app/i18n/es-ES.ts +10 -0
- package/corpus/templates/plan/app/i18n/fr-FR.ts +10 -0
- package/corpus/templates/plan/app/i18n/hi-IN.ts +10 -0
- package/corpus/templates/plan/app/i18n/ja-JP.ts +10 -0
- package/corpus/templates/plan/app/i18n/ko-KR.ts +10 -0
- package/corpus/templates/plan/app/i18n/pt-BR.ts +10 -0
- package/corpus/templates/plan/app/i18n/zh-CN.ts +10 -0
- package/corpus/templates/plan/app/i18n/zh-TW.ts +10 -0
- package/corpus/templates/plan/app/lib/plan-local-bridge.ts +80 -1
- package/corpus/templates/plan/app/pages/PlansPage.tsx +157 -31
- package/corpus/templates/plan/app/root.tsx +11 -0
- package/corpus/templates/plan/changelog/2026-07-11-local-plans-now-explain-and-recover-from-browser-permission-.md +6 -0
- package/corpus/templates/plan/changelog/2026-07-11-plan-edits-now-save-atomically-and-can-no-longer-partially-a.md +6 -0
- package/corpus/templates/plan/changelog/2026-07-11-plan-lists-now-show-a-clear-error-with-a-retry-action-when-t.md +6 -0
- package/corpus/templates/plan/changelog/2026-07-11-plan-refreshes-only-when-content-changes.md +6 -0
- package/corpus/templates/plan/shared/plan-content.ts +546 -260
- package/corpus/templates/slides/.agents/skills/frontend-design/SKILL.md +1 -1
- package/corpus/templates/slides/actions/delete-slide-comment.ts +49 -0
- package/corpus/templates/slides/actions/update-slide-comment.ts +93 -0
- package/corpus/templates/slides/actions/view-screen.ts +14 -6
- package/corpus/templates/slides/app/components/comments/SlideCommentsPanel.tsx +37 -11
- package/corpus/templates/slides/app/components/editor/EditorSidebar.tsx +1 -1
- package/corpus/templates/slides/app/components/editor/EditorToolbar.tsx +2 -2
- package/corpus/templates/slides/app/components/editor/GenerateSlidesDialog.tsx +2 -2
- package/corpus/templates/slides/app/components/layout/Header.tsx +1 -1
- package/corpus/templates/slides/app/components/presentation/PresentationView.tsx +4 -2
- package/corpus/templates/slides/app/components/visual-editor/CanvasCommentPins.tsx +4 -3
- package/corpus/templates/slides/app/context/DeckContext.tsx +10 -0
- package/corpus/templates/slides/app/hooks/use-slide-comments.ts +7 -31
- package/corpus/templates/slides/app/i18n/ar-SA.ts +6 -0
- package/corpus/templates/slides/app/i18n/de-DE.ts +6 -0
- package/corpus/templates/slides/app/i18n/en-US.ts +6 -0
- package/corpus/templates/slides/app/i18n/es-ES.ts +6 -0
- package/corpus/templates/slides/app/i18n/fr-FR.ts +6 -0
- package/corpus/templates/slides/app/i18n/hi-IN.ts +6 -0
- package/corpus/templates/slides/app/i18n/ja-JP.ts +6 -0
- package/corpus/templates/slides/app/i18n/ko-KR.ts +6 -0
- package/corpus/templates/slides/app/i18n/pt-BR.ts +6 -0
- package/corpus/templates/slides/app/i18n/zh-CN.ts +5 -0
- package/corpus/templates/slides/app/i18n/zh-TW.ts +5 -0
- package/corpus/templates/slides/app/pages/DesignSystems.tsx +23 -2
- package/corpus/templates/slides/app/pages/Index.tsx +29 -1
- package/corpus/templates/slides/changelog/2026-07-11-comment-load-failures-now-show-a-retry-action-instead-of-say.md +6 -0
- package/corpus/templates/slides/changelog/2026-07-11-comment-pins-now-clear-with-a-faster-smoother-confirmation.md +6 -0
- package/corpus/templates/slides/changelog/2026-07-11-comments-now-update-from-live-collaboration-events-without-c.md +6 -0
- package/corpus/templates/slides/changelog/2026-07-11-deck-and-design-system-load-failures-now-show-a-retry-action.md +6 -0
- package/corpus/templates/slides/changelog/2026-07-11-editor-toggles-and-toolbar-menus-now-animate-smoothly.md +6 -0
- package/corpus/templates/slides/changelog/2026-07-11-the-assistant-can-now-resolve-reopen-and-delete-slide-commen.md +6 -0
- package/corpus/templates/slides/changelog/2026-07-11-viewing-slides-no-longer-loads-every-deck-s-full-contents-in.md +6 -0
- package/corpus/templates/slides/server/routes/api/comments/[id].delete.ts +29 -48
- package/corpus/templates/slides/server/routes/api/comments/[id].patch.ts +29 -65
- package/dist/action-change-marker.d.ts +1 -0
- package/dist/action-change-marker.d.ts.map +1 -1
- package/dist/action-change-marker.js +7 -1
- package/dist/action-change-marker.js.map +1 -1
- package/dist/action.d.ts +23 -0
- package/dist/action.d.ts.map +1 -1
- package/dist/action.js +13 -0
- package/dist/action.js.map +1 -1
- package/dist/agent/engine/builder-engine.d.ts.map +1 -1
- package/dist/agent/engine/builder-engine.js +3 -0
- package/dist/agent/engine/builder-engine.js.map +1 -1
- package/dist/agent/engine/context-directives-transform.d.ts +25 -0
- package/dist/agent/engine/context-directives-transform.d.ts.map +1 -0
- package/dist/agent/engine/context-directives-transform.js +53 -0
- package/dist/agent/engine/context-directives-transform.js.map +1 -0
- package/dist/agent/engine/continuation-dispatch-retry.d.ts +40 -0
- package/dist/agent/engine/continuation-dispatch-retry.d.ts.map +1 -0
- package/dist/agent/engine/continuation-dispatch-retry.js +109 -0
- package/dist/agent/engine/continuation-dispatch-retry.js.map +1 -0
- package/dist/agent/engine/tool-call-journal-seed.d.ts +46 -0
- package/dist/agent/engine/tool-call-journal-seed.d.ts.map +1 -0
- package/dist/agent/engine/tool-call-journal-seed.js +60 -0
- package/dist/agent/engine/tool-call-journal-seed.js.map +1 -0
- package/dist/agent/production-agent.d.ts +7 -0
- package/dist/agent/production-agent.d.ts.map +1 -1
- package/dist/agent/production-agent.js +102 -222
- package/dist/agent/production-agent.js.map +1 -1
- package/dist/changelog/parse.d.ts +4 -2
- package/dist/changelog/parse.d.ts.map +1 -1
- package/dist/changelog/parse.js +5 -3
- package/dist/changelog/parse.js.map +1 -1
- package/dist/cli/doctor.d.ts.map +1 -1
- package/dist/cli/doctor.js +7 -3
- package/dist/cli/doctor.js.map +1 -1
- package/dist/cli/plan-local.d.ts.map +1 -1
- package/dist/cli/plan-local.js +8 -2
- package/dist/cli/plan-local.js.map +1 -1
- package/dist/cli/skills-content/assets-skill.d.ts +2 -0
- package/dist/cli/skills-content/assets-skill.d.ts.map +1 -0
- package/dist/cli/skills-content/assets-skill.js +84 -0
- package/dist/cli/skills-content/assets-skill.js.map +1 -0
- package/dist/cli/skills-content/canvas.d.ts +4 -0
- package/dist/cli/skills-content/canvas.d.ts.map +1 -0
- package/dist/cli/skills-content/canvas.js +152 -0
- package/dist/cli/skills-content/canvas.js.map +1 -0
- package/dist/cli/skills-content/connection.d.ts +2 -0
- package/dist/cli/skills-content/connection.d.ts.map +1 -0
- package/dist/cli/skills-content/connection.js +53 -0
- package/dist/cli/skills-content/connection.js.map +1 -0
- package/dist/cli/skills-content/content-skill.d.ts +2 -0
- package/dist/cli/skills-content/content-skill.d.ts.map +1 -0
- package/dist/cli/skills-content/content-skill.js +110 -0
- package/dist/cli/skills-content/content-skill.js.map +1 -0
- package/dist/cli/skills-content/design-exploration-skill.d.ts +2 -0
- package/dist/cli/skills-content/design-exploration-skill.d.ts.map +1 -0
- package/dist/cli/skills-content/design-exploration-skill.js +119 -0
- package/dist/cli/skills-content/design-exploration-skill.js.map +1 -0
- package/dist/cli/skills-content/design-visual-edit-skill.d.ts +2 -0
- package/dist/cli/skills-content/design-visual-edit-skill.d.ts.map +1 -0
- package/dist/cli/skills-content/design-visual-edit-skill.js +229 -0
- package/dist/cli/skills-content/design-visual-edit-skill.js.map +1 -0
- package/dist/cli/skills-content/document-quality.d.ts +4 -0
- package/dist/cli/skills-content/document-quality.d.ts.map +1 -0
- package/dist/cli/skills-content/document-quality.js +195 -0
- package/dist/cli/skills-content/document-quality.js.map +1 -0
- package/dist/cli/skills-content/exemplar.d.ts +4 -0
- package/dist/cli/skills-content/exemplar.d.ts.map +1 -0
- package/dist/cli/skills-content/exemplar.js +68 -0
- package/dist/cli/skills-content/exemplar.js.map +1 -0
- package/dist/cli/skills-content/help.d.ts +2 -0
- package/dist/cli/skills-content/help.d.ts.map +1 -0
- package/dist/cli/skills-content/help.js +78 -0
- package/dist/cli/skills-content/help.js.map +1 -0
- package/dist/cli/skills-content/index.d.ts +23 -0
- package/dist/cli/skills-content/index.d.ts.map +1 -0
- package/dist/cli/skills-content/index.js +23 -0
- package/dist/cli/skills-content/index.js.map +1 -0
- package/dist/cli/skills-content/local-files.d.ts +2 -0
- package/dist/cli/skills-content/local-files.d.ts.map +1 -0
- package/dist/cli/skills-content/local-files.js +96 -0
- package/dist/cli/skills-content/local-files.js.map +1 -0
- package/dist/cli/skills-content/plan-setup-auth.d.ts +9 -0
- package/dist/cli/skills-content/plan-setup-auth.d.ts.map +1 -0
- package/dist/cli/skills-content/plan-setup-auth.js +81 -0
- package/dist/cli/skills-content/plan-setup-auth.js.map +1 -0
- package/dist/cli/skills-content/visual-plan-skill.d.ts +2 -0
- package/dist/cli/skills-content/visual-plan-skill.d.ts.map +1 -0
- package/dist/cli/skills-content/visual-plan-skill.js +471 -0
- package/dist/cli/skills-content/visual-plan-skill.js.map +1 -0
- package/dist/cli/skills-content/visual-recap-skill.d.ts +2 -0
- package/dist/cli/skills-content/visual-recap-skill.d.ts.map +1 -0
- package/dist/cli/skills-content/visual-recap-skill.js +548 -0
- package/dist/cli/skills-content/visual-recap-skill.js.map +1 -0
- package/dist/cli/skills-content/visualize-repo-skill.d.ts +2 -0
- package/dist/cli/skills-content/visualize-repo-skill.d.ts.map +1 -0
- package/dist/cli/skills-content/visualize-repo-skill.js +94 -0
- package/dist/cli/skills-content/visualize-repo-skill.js.map +1 -0
- package/dist/cli/skills-content/wireframe.d.ts +4 -0
- package/dist/cli/skills-content/wireframe.d.ts.map +1 -0
- package/dist/cli/skills-content/wireframe.js +347 -0
- package/dist/cli/skills-content/wireframe.js.map +1 -0
- package/dist/cli/skills.d.ts +2 -10
- package/dist/cli/skills.d.ts.map +1 -1
- package/dist/cli/skills.js +2 -2710
- package/dist/cli/skills.js.map +1 -1
- package/dist/client/AgentPanel.d.ts.map +1 -1
- package/dist/client/AgentPanel.js +6 -5
- package/dist/client/AgentPanel.js.map +1 -1
- package/dist/client/AssistantChat.d.ts +7 -0
- package/dist/client/AssistantChat.d.ts.map +1 -1
- package/dist/client/AssistantChat.js +62 -40
- package/dist/client/AssistantChat.js.map +1 -1
- package/dist/client/CommandMenu.d.ts.map +1 -1
- package/dist/client/CommandMenu.js +15 -3
- package/dist/client/CommandMenu.js.map +1 -1
- package/dist/client/FeedbackButton.js +2 -2
- package/dist/client/FeedbackButton.js.map +1 -1
- package/dist/client/analytics.d.ts.map +1 -1
- package/dist/client/analytics.js +26 -0
- package/dist/client/analytics.js.map +1 -1
- package/dist/client/app-providers.d.ts +13 -5
- package/dist/client/app-providers.d.ts.map +1 -1
- package/dist/client/app-providers.js +9 -6
- package/dist/client/app-providers.js.map +1 -1
- package/dist/client/blocks/library/wireframe-kit.d.ts +2 -0
- package/dist/client/blocks/library/wireframe-kit.d.ts.map +1 -1
- package/dist/client/blocks/library/wireframe-kit.js +9 -2
- package/dist/client/blocks/library/wireframe-kit.js.map +1 -1
- package/dist/client/chat/tool-call-display.d.ts +4 -0
- package/dist/client/chat/tool-call-display.d.ts.map +1 -1
- package/dist/client/chat/tool-call-display.js +3 -3
- package/dist/client/chat/tool-call-display.js.map +1 -1
- package/dist/client/chat-model-groups.d.ts.map +1 -1
- package/dist/client/chat-model-groups.js +24 -0
- package/dist/client/chat-model-groups.js.map +1 -1
- package/dist/client/composer/PastedTextChip.js +1 -1
- package/dist/client/composer/PastedTextChip.js.map +1 -1
- package/dist/client/composer/RealtimeVoiceMode.d.ts +40 -2
- package/dist/client/composer/RealtimeVoiceMode.d.ts.map +1 -1
- package/dist/client/composer/RealtimeVoiceMode.js +186 -35
- package/dist/client/composer/RealtimeVoiceMode.js.map +1 -1
- package/dist/client/composer/TiptapComposer.d.ts +4 -0
- package/dist/client/composer/TiptapComposer.d.ts.map +1 -1
- package/dist/client/composer/TiptapComposer.js +48 -30
- package/dist/client/composer/TiptapComposer.js.map +1 -1
- package/dist/client/composer/VoiceButton.d.ts +2 -0
- package/dist/client/composer/VoiceButton.d.ts.map +1 -1
- package/dist/client/composer/VoiceButton.js +9 -3
- package/dist/client/composer/VoiceButton.js.map +1 -1
- package/dist/client/composer/index.d.ts +2 -2
- package/dist/client/composer/index.d.ts.map +1 -1
- package/dist/client/composer/index.js +1 -1
- package/dist/client/composer/index.js.map +1 -1
- package/dist/client/composer/useRealtimeVoiceMode.d.ts +104 -2
- package/dist/client/composer/useRealtimeVoiceMode.d.ts.map +1 -1
- package/dist/client/composer/useRealtimeVoiceMode.js +1015 -35
- package/dist/client/composer/useRealtimeVoiceMode.js.map +1 -1
- package/dist/client/context-xray/ContextXRayPanel.d.ts.map +1 -1
- package/dist/client/context-xray/ContextXRayPanel.js +4 -1
- package/dist/client/context-xray/ContextXRayPanel.js.map +1 -1
- package/dist/client/extensions/ExtensionEditor.d.ts.map +1 -1
- package/dist/client/extensions/ExtensionEditor.js +15 -5
- package/dist/client/extensions/ExtensionEditor.js.map +1 -1
- package/dist/client/extensions/ExtensionQueryErrorState.d.ts +8 -0
- package/dist/client/extensions/ExtensionQueryErrorState.d.ts.map +1 -0
- package/dist/client/extensions/ExtensionQueryErrorState.js +11 -0
- package/dist/client/extensions/ExtensionQueryErrorState.js.map +1 -0
- package/dist/client/extensions/ExtensionSlot.d.ts.map +1 -1
- package/dist/client/extensions/ExtensionSlot.js +17 -7
- package/dist/client/extensions/ExtensionSlot.js.map +1 -1
- package/dist/client/extensions/ExtensionViewer.d.ts.map +1 -1
- package/dist/client/extensions/ExtensionViewer.js +17 -7
- package/dist/client/extensions/ExtensionViewer.js.map +1 -1
- package/dist/client/extensions/ExtensionsListPage.d.ts.map +1 -1
- package/dist/client/extensions/ExtensionsListPage.js +5 -3
- package/dist/client/extensions/ExtensionsListPage.js.map +1 -1
- package/dist/client/extensions/ExtensionsSidebarSection.d.ts.map +1 -1
- package/dist/client/extensions/ExtensionsSidebarSection.js +21 -8
- package/dist/client/extensions/ExtensionsSidebarSection.js.map +1 -1
- package/dist/client/index.d.ts +1 -3
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +1 -6
- package/dist/client/index.js.map +1 -1
- package/dist/client/progress/RunsTray.d.ts.map +1 -1
- package/dist/client/progress/RunsTray.js +17 -4
- package/dist/client/progress/RunsTray.js.map +1 -1
- package/dist/client/settings/SecretsSection.d.ts +2 -3
- package/dist/client/settings/SecretsSection.d.ts.map +1 -1
- package/dist/client/settings/SecretsSection.js +46 -22
- package/dist/client/settings/SecretsSection.js.map +1 -1
- package/dist/client/settings/SettingsPanel.d.ts +9 -0
- package/dist/client/settings/SettingsPanel.d.ts.map +1 -1
- package/dist/client/settings/SettingsPanel.js +46 -12
- package/dist/client/settings/SettingsPanel.js.map +1 -1
- package/dist/client/settings/SettingsTabsPage.d.ts.map +1 -1
- package/dist/client/settings/SettingsTabsPage.js +4 -0
- package/dist/client/settings/SettingsTabsPage.js.map +1 -1
- package/dist/client/settings/useBuilderStatus.d.ts +2 -0
- package/dist/client/settings/useBuilderStatus.d.ts.map +1 -1
- package/dist/client/settings/useBuilderStatus.js +4 -0
- package/dist/client/settings/useBuilderStatus.js.map +1 -1
- package/dist/client/tool-cells/EditCell.d.ts.map +1 -1
- package/dist/client/tool-cells/EditCell.js +2 -1
- package/dist/client/tool-cells/EditCell.js.map +1 -1
- package/dist/client/tool-cells/FilesChangedSummary.d.ts.map +1 -1
- package/dist/client/tool-cells/FilesChangedSummary.js +2 -1
- package/dist/client/tool-cells/FilesChangedSummary.js.map +1 -1
- package/dist/client/tool-cells/WriteCell.d.ts.map +1 -1
- package/dist/client/tool-cells/WriteCell.js +2 -1
- package/dist/client/tool-cells/WriteCell.js.map +1 -1
- package/dist/client/use-action.d.ts +2 -0
- package/dist/client/use-action.d.ts.map +1 -1
- package/dist/client/use-action.js +10 -0
- package/dist/client/use-action.js.map +1 -1
- package/dist/client/use-agent-chat-home-handoff.d.ts +2 -0
- package/dist/client/use-agent-chat-home-handoff.d.ts.map +1 -1
- package/dist/client/use-agent-chat-home-handoff.js +7 -1
- package/dist/client/use-agent-chat-home-handoff.js.map +1 -1
- package/dist/client/use-agent-engine-configured.d.ts.map +1 -1
- package/dist/client/use-agent-engine-configured.js +17 -3
- package/dist/client/use-agent-engine-configured.js.map +1 -1
- package/dist/client/use-db-sync.d.ts +8 -2
- package/dist/client/use-db-sync.d.ts.map +1 -1
- package/dist/client/use-db-sync.js +57 -32
- package/dist/client/use-db-sync.js.map +1 -1
- package/dist/client/use-pausing-interval.d.ts.map +1 -1
- package/dist/client/use-pausing-interval.js +16 -4
- package/dist/client/use-pausing-interval.js.map +1 -1
- package/dist/client/visual-style-controls.js +1 -1
- package/dist/client/visual-style-controls.js.map +1 -1
- package/dist/collab/routes.d.ts +1 -1
- package/dist/deploy/build.d.ts.map +1 -1
- package/dist/deploy/build.js +36 -14
- package/dist/deploy/build.js.map +1 -1
- package/dist/integrations/google-docs-poller.d.ts +7 -0
- package/dist/integrations/google-docs-poller.d.ts.map +1 -1
- package/dist/integrations/google-docs-poller.js +13 -3
- package/dist/integrations/google-docs-poller.js.map +1 -1
- package/dist/integrations/plugin.d.ts.map +1 -1
- package/dist/integrations/plugin.js +10 -0
- package/dist/integrations/plugin.js.map +1 -1
- package/dist/integrations/scope-store.js +1 -1
- package/dist/integrations/scope-store.js.map +1 -1
- package/dist/integrations/usage-budget-store.js +1 -1
- package/dist/integrations/usage-budget-store.js.map +1 -1
- package/dist/integrations/webhook-handler.d.ts +11 -0
- package/dist/integrations/webhook-handler.d.ts.map +1 -1
- package/dist/integrations/webhook-handler.js +16 -4
- package/dist/integrations/webhook-handler.js.map +1 -1
- package/dist/jobs/scheduler.d.ts +12 -0
- package/dist/jobs/scheduler.d.ts.map +1 -1
- package/dist/jobs/scheduler.js +13 -3
- package/dist/jobs/scheduler.js.map +1 -1
- package/dist/localization/default-messages.d.ts +47 -0
- package/dist/localization/default-messages.d.ts.map +1 -1
- package/dist/localization/default-messages.js +48 -1
- package/dist/localization/default-messages.js.map +1 -1
- package/dist/mcp/oauth-token.d.ts +1 -1
- package/dist/mcp/oauth-token.d.ts.map +1 -1
- package/dist/mcp/oauth-token.js +6 -1
- package/dist/mcp/oauth-token.js.map +1 -1
- package/dist/mcp-client/workspace-servers.js +4 -4
- package/dist/mcp-client/workspace-servers.js.map +1 -1
- package/dist/notifications/routes.d.ts +2 -2
- package/dist/observability/routes.d.ts +5 -5
- package/dist/observability/routes.d.ts.map +1 -1
- package/dist/observability/routes.js +32 -0
- package/dist/observability/routes.js.map +1 -1
- package/dist/observability/sentiment.d.ts +42 -0
- package/dist/observability/sentiment.d.ts.map +1 -0
- package/dist/observability/sentiment.js +196 -0
- package/dist/observability/sentiment.js.map +1 -0
- package/dist/observability/store.d.ts +5 -0
- package/dist/observability/store.d.ts.map +1 -1
- package/dist/observability/store.js +17 -0
- package/dist/observability/store.js.map +1 -1
- package/dist/observability/traces.d.ts +2 -0
- package/dist/observability/traces.d.ts.map +1 -1
- package/dist/observability/traces.js +42 -57
- package/dist/observability/traces.js.map +1 -1
- package/dist/observability/tracking-identity.d.ts +3 -0
- package/dist/observability/tracking-identity.d.ts.map +1 -0
- package/dist/observability/tracking-identity.js +51 -0
- package/dist/observability/tracking-identity.js.map +1 -0
- package/dist/observability/types.d.ts +10 -0
- package/dist/observability/types.d.ts.map +1 -1
- package/dist/observability/types.js +3 -0
- package/dist/observability/types.js.map +1 -1
- package/dist/progress/routes.d.ts +1 -1
- package/dist/resources/handlers.d.ts +1 -1
- package/dist/secrets/crypto.d.ts +7 -6
- package/dist/secrets/crypto.d.ts.map +1 -1
- package/dist/secrets/crypto.js +30 -11
- package/dist/secrets/crypto.js.map +1 -1
- package/dist/secrets/routes.d.ts +9 -9
- package/dist/secrets/storage.d.ts +6 -0
- package/dist/secrets/storage.d.ts.map +1 -1
- package/dist/secrets/storage.js +61 -3
- package/dist/secrets/storage.js.map +1 -1
- package/dist/server/action-change.d.ts +1 -0
- package/dist/server/action-change.d.ts.map +1 -1
- package/dist/server/action-change.js +4 -1
- package/dist/server/action-change.js.map +1 -1
- package/dist/server/action-routes.d.ts.map +1 -1
- package/dist/server/action-routes.js +5 -0
- package/dist/server/action-routes.js.map +1 -1
- package/dist/server/agent-chat/action-filters-a2a.d.ts +50 -0
- package/dist/server/agent-chat/action-filters-a2a.d.ts.map +1 -0
- package/dist/server/agent-chat/action-filters-a2a.js +127 -0
- package/dist/server/agent-chat/action-filters-a2a.js.map +1 -0
- package/dist/server/agent-chat/browser-team-tools.d.ts +21 -0
- package/dist/server/agent-chat/browser-team-tools.d.ts.map +1 -0
- package/dist/server/agent-chat/browser-team-tools.js +417 -0
- package/dist/server/agent-chat/browser-team-tools.js.map +1 -0
- package/dist/server/agent-chat/context-tools.d.ts +33 -0
- package/dist/server/agent-chat/context-tools.d.ts.map +1 -0
- package/dist/server/agent-chat/context-tools.js +520 -0
- package/dist/server/agent-chat/context-tools.js.map +1 -0
- package/dist/server/agent-chat/framework-prompts.d.ts +65 -0
- package/dist/server/agent-chat/framework-prompts.d.ts.map +1 -0
- package/dist/server/agent-chat/framework-prompts.js +403 -0
- package/dist/server/agent-chat/framework-prompts.js.map +1 -0
- package/dist/server/agent-chat/lazy-fs.d.ts +2 -0
- package/dist/server/agent-chat/lazy-fs.d.ts.map +1 -0
- package/dist/server/agent-chat/lazy-fs.js +11 -0
- package/dist/server/agent-chat/lazy-fs.js.map +1 -0
- package/dist/server/agent-chat/mcp-glue.d.ts +9 -0
- package/dist/server/agent-chat/mcp-glue.d.ts.map +1 -0
- package/dist/server/agent-chat/mcp-glue.js +63 -0
- package/dist/server/agent-chat/mcp-glue.js.map +1 -0
- package/dist/server/agent-chat/plugin-options.d.ts +322 -0
- package/dist/server/agent-chat/plugin-options.d.ts.map +1 -0
- package/dist/server/agent-chat/plugin-options.js +2 -0
- package/dist/server/agent-chat/plugin-options.js.map +1 -0
- package/dist/server/agent-chat/process-run-failure.d.ts +12 -0
- package/dist/server/agent-chat/process-run-failure.d.ts.map +1 -0
- package/dist/server/agent-chat/process-run-failure.js +24 -0
- package/dist/server/agent-chat/process-run-failure.js.map +1 -0
- package/dist/server/agent-chat/prompt-resources.d.ts +28 -0
- package/dist/server/agent-chat/prompt-resources.d.ts.map +1 -0
- package/dist/server/agent-chat/prompt-resources.js +514 -0
- package/dist/server/agent-chat/prompt-resources.js.map +1 -0
- package/dist/server/agent-chat/recurring-jobs-runtime.d.ts +5 -0
- package/dist/server/agent-chat/recurring-jobs-runtime.d.ts.map +1 -0
- package/dist/server/agent-chat/recurring-jobs-runtime.js +53 -0
- package/dist/server/agent-chat/recurring-jobs-runtime.js.map +1 -0
- package/dist/server/agent-chat/request-surface.d.ts +8 -0
- package/dist/server/agent-chat/request-surface.d.ts.map +1 -0
- package/dist/server/agent-chat/request-surface.js +56 -0
- package/dist/server/agent-chat/request-surface.js.map +1 -0
- package/dist/server/agent-chat/run-code-tools.d.ts +17 -0
- package/dist/server/agent-chat/run-code-tools.d.ts.map +1 -0
- package/dist/server/agent-chat/run-code-tools.js +55 -0
- package/dist/server/agent-chat/run-code-tools.js.map +1 -0
- package/dist/server/agent-chat/script-entries.d.ts +47 -0
- package/dist/server/agent-chat/script-entries.d.ts.map +1 -0
- package/dist/server/agent-chat/script-entries.js +633 -0
- package/dist/server/agent-chat/script-entries.js.map +1 -0
- package/dist/server/agent-chat/shared-thread.d.ts +33 -0
- package/dist/server/agent-chat/shared-thread.d.ts.map +1 -0
- package/dist/server/agent-chat/shared-thread.js +242 -0
- package/dist/server/agent-chat/shared-thread.js.map +1 -0
- package/dist/server/agent-chat/skill-frontmatter.d.ts +7 -0
- package/dist/server/agent-chat/skill-frontmatter.d.ts.map +1 -0
- package/dist/server/agent-chat/skill-frontmatter.js +26 -0
- package/dist/server/agent-chat/skill-frontmatter.js.map +1 -0
- package/dist/server/agent-chat-plugin.d.ts +45 -486
- package/dist/server/agent-chat-plugin.d.ts.map +1 -1
- package/dist/server/agent-chat-plugin.js +173 -3005
- package/dist/server/agent-chat-plugin.js.map +1 -1
- package/dist/server/agent-teams.d.ts +10 -0
- package/dist/server/agent-teams.d.ts.map +1 -1
- package/dist/server/agent-teams.js +13 -3
- package/dist/server/agent-teams.js.map +1 -1
- package/dist/server/auth.d.ts.map +1 -1
- package/dist/server/auth.js +50 -52
- package/dist/server/auth.js.map +1 -1
- package/dist/server/credential-provider.d.ts.map +1 -1
- package/dist/server/credential-provider.js +22 -21
- package/dist/server/credential-provider.js.map +1 -1
- package/dist/server/edge.d.ts +1 -0
- package/dist/server/edge.d.ts.map +1 -1
- package/dist/server/edge.js +1 -0
- package/dist/server/edge.js.map +1 -1
- package/dist/server/onboarding-html.d.ts.map +1 -1
- package/dist/server/onboarding-html.js +1 -16
- package/dist/server/onboarding-html.js.map +1 -1
- package/dist/server/poll.d.ts.map +1 -1
- package/dist/server/poll.js +1 -0
- package/dist/server/poll.js.map +1 -1
- package/dist/server/realtime-voice.d.ts +10 -0
- package/dist/server/realtime-voice.d.ts.map +1 -1
- package/dist/server/realtime-voice.js +249 -15
- package/dist/server/realtime-voice.js.map +1 -1
- package/dist/server/request-origin.d.ts.map +1 -1
- package/dist/server/request-origin.js +6 -3
- package/dist/server/request-origin.js.map +1 -1
- package/dist/server/ssr-handler.d.ts.map +1 -1
- package/dist/server/ssr-handler.js +47 -2
- package/dist/server/ssr-handler.js.map +1 -1
- package/dist/sharing/access.d.ts +63 -3
- package/dist/sharing/access.d.ts.map +1 -1
- package/dist/sharing/access.js +53 -15
- package/dist/sharing/access.js.map +1 -1
- package/dist/sharing/registry.d.ts +7 -0
- package/dist/sharing/registry.d.ts.map +1 -1
- package/dist/sharing/registry.js.map +1 -1
- package/dist/styles/agent-native.css +17 -16
- package/dist/templates/default/.agents/skills/frontend-design/SKILL.md +1 -1
- package/dist/templates/default/.agents/skills/real-time-sync/SKILL.md +2 -2
- package/dist/templates/default/app/i18n/ar-SA.ts +6 -0
- package/dist/templates/default/app/i18n/de-DE.ts +6 -0
- package/dist/templates/default/app/i18n/en-US.ts +6 -0
- package/dist/templates/default/app/i18n/es-ES.ts +6 -0
- package/dist/templates/default/app/i18n/fr-FR.ts +6 -0
- package/dist/templates/default/app/i18n/hi-IN.ts +6 -0
- package/dist/templates/default/app/i18n/ja-JP.ts +6 -0
- package/dist/templates/default/app/i18n/ko-KR.ts +6 -0
- package/dist/templates/default/app/i18n/pt-BR.ts +6 -0
- package/dist/templates/default/app/i18n/zh-CN.ts +6 -0
- package/dist/templates/default/app/i18n/zh-TW.ts +6 -0
- package/dist/templates/workspace-core/.agents/skills/authentication/SKILL.md +27 -29
- package/dist/templates/workspace-core/.agents/skills/external-agents/SKILL.md +2 -1
- package/dist/templates/workspace-core/.agents/skills/frontend-design/SKILL.md +1 -1
- package/dist/templates/workspace-core/.agents/skills/observability/SKILL.md +32 -0
- package/dist/templates/workspace-core/.agents/skills/real-time-sync/SKILL.md +2 -2
- package/dist/templates/workspace-core/.agents/skills/secrets/SKILL.md +7 -3
- package/dist/templates/workspace-core/.agents/skills/voice-transcription/SKILL.md +39 -9
- package/dist/templates/workspace-core/.agents/skills/writing-agent-instructions/SKILL.md +25 -0
- package/dist/templates/workspace-core/src/server/index.ts +4 -2
- package/dist/testing.d.ts +16 -0
- package/dist/testing.d.ts.map +1 -0
- package/dist/testing.js +16 -0
- package/dist/testing.js.map +1 -0
- package/dist/triggers/dispatcher.d.ts +7 -0
- package/dist/triggers/dispatcher.d.ts.map +1 -1
- package/dist/triggers/dispatcher.js +13 -3
- package/dist/triggers/dispatcher.js.map +1 -1
- package/dist/vite/client.d.ts +16 -1
- package/dist/vite/client.d.ts.map +1 -1
- package/dist/vite/client.js +111 -3
- package/dist/vite/client.js.map +1 -1
- package/docs/content/authentication.mdx +3 -3
- package/docs/content/doctor.mdx +170 -0
- package/docs/content/external-agents.mdx +8 -7
- package/docs/content/locales/ar-SA/authentication.mdx +2 -2
- package/docs/content/locales/ar-SA/external-agents.mdx +8 -7
- package/docs/content/locales/ar-SA/mcp-protocol.mdx +1 -1
- package/docs/content/locales/ar-SA/security.mdx +2 -0
- package/docs/content/locales/de-DE/authentication.mdx +1 -1
- package/docs/content/locales/de-DE/external-agents.mdx +8 -7
- package/docs/content/locales/de-DE/mcp-protocol.mdx +1 -1
- package/docs/content/locales/de-DE/security.mdx +2 -0
- package/docs/content/locales/es-ES/authentication.mdx +2 -2
- package/docs/content/locales/es-ES/external-agents.mdx +8 -7
- package/docs/content/locales/es-ES/mcp-protocol.mdx +1 -1
- package/docs/content/locales/es-ES/security.mdx +2 -0
- package/docs/content/locales/fr-FR/authentication.mdx +1 -1
- package/docs/content/locales/fr-FR/external-agents.mdx +8 -7
- package/docs/content/locales/fr-FR/mcp-protocol.mdx +1 -1
- package/docs/content/locales/fr-FR/security.mdx +2 -0
- package/docs/content/locales/hi-IN/authentication.mdx +2 -2
- package/docs/content/locales/hi-IN/external-agents.mdx +8 -7
- package/docs/content/locales/hi-IN/mcp-protocol.mdx +1 -1
- package/docs/content/locales/hi-IN/security.mdx +2 -0
- package/docs/content/locales/ja-JP/authentication.mdx +2 -2
- package/docs/content/locales/ja-JP/external-agents.mdx +8 -7
- package/docs/content/locales/ja-JP/mcp-protocol.mdx +1 -1
- package/docs/content/locales/ja-JP/security.mdx +2 -0
- package/docs/content/locales/ko-KR/authentication.mdx +2 -2
- package/docs/content/locales/ko-KR/external-agents.mdx +8 -7
- package/docs/content/locales/ko-KR/mcp-protocol.mdx +1 -1
- package/docs/content/locales/ko-KR/security.mdx +2 -0
- package/docs/content/locales/pt-BR/authentication.mdx +2 -2
- package/docs/content/locales/pt-BR/external-agents.mdx +8 -7
- package/docs/content/locales/pt-BR/mcp-protocol.mdx +1 -1
- package/docs/content/locales/pt-BR/security.mdx +2 -0
- package/docs/content/locales/zh-CN/authentication.mdx +2 -2
- package/docs/content/locales/zh-CN/external-agents.mdx +8 -7
- package/docs/content/locales/zh-CN/mcp-protocol.mdx +1 -1
- package/docs/content/locales/zh-CN/security.mdx +2 -0
- package/docs/content/locales/zh-TW/authentication.mdx +2 -2
- package/docs/content/locales/zh-TW/external-agents.mdx +8 -7
- package/docs/content/locales/zh-TW/mcp-protocol.mdx +1 -1
- package/docs/content/locales/zh-TW/security.mdx +2 -0
- package/docs/content/mcp-protocol.mdx +1 -1
- package/docs/content/security.mdx +2 -0
- package/package.json +7 -3
- package/src/templates/default/.agents/skills/frontend-design/SKILL.md +1 -1
- package/src/templates/default/.agents/skills/real-time-sync/SKILL.md +2 -2
- package/src/templates/default/app/i18n/ar-SA.ts +6 -0
- package/src/templates/default/app/i18n/de-DE.ts +6 -0
- package/src/templates/default/app/i18n/en-US.ts +6 -0
- package/src/templates/default/app/i18n/es-ES.ts +6 -0
- package/src/templates/default/app/i18n/fr-FR.ts +6 -0
- package/src/templates/default/app/i18n/hi-IN.ts +6 -0
- package/src/templates/default/app/i18n/ja-JP.ts +6 -0
- package/src/templates/default/app/i18n/ko-KR.ts +6 -0
- package/src/templates/default/app/i18n/pt-BR.ts +6 -0
- package/src/templates/default/app/i18n/zh-CN.ts +6 -0
- package/src/templates/default/app/i18n/zh-TW.ts +6 -0
- package/src/templates/workspace-core/.agents/skills/authentication/SKILL.md +27 -29
- package/src/templates/workspace-core/.agents/skills/external-agents/SKILL.md +2 -1
- package/src/templates/workspace-core/.agents/skills/frontend-design/SKILL.md +1 -1
- package/src/templates/workspace-core/.agents/skills/observability/SKILL.md +32 -0
- package/src/templates/workspace-core/.agents/skills/real-time-sync/SKILL.md +2 -2
- package/src/templates/workspace-core/.agents/skills/secrets/SKILL.md +7 -3
- package/src/templates/workspace-core/.agents/skills/voice-transcription/SKILL.md +39 -9
- package/src/templates/workspace-core/.agents/skills/writing-agent-instructions/SKILL.md +25 -0
- package/src/templates/workspace-core/src/server/index.ts +4 -2
- package/corpus/core/src/client/ProductionAgentPanel.tsx +0 -9
- package/corpus/core/src/client/auth-redirect-url.ts +0 -23
- package/corpus/core/src/client/useProductionAgent.ts +0 -253
- package/corpus/core/src/shared/auth-redirect-url.ts +0 -1
- package/corpus/templates/analytics/app/pages/Index.tsx +0 -16
- package/corpus/templates/analytics/seeds/dashboards/agent-observability-llm.json +0 -173
- package/dist/client/ProductionAgentPanel.d.ts +0 -6
- package/dist/client/ProductionAgentPanel.d.ts.map +0 -1
- package/dist/client/ProductionAgentPanel.js +0 -6
- package/dist/client/ProductionAgentPanel.js.map +0 -1
- package/dist/client/auth-redirect-url.d.ts +0 -2
- package/dist/client/auth-redirect-url.d.ts.map +0 -1
- package/dist/client/auth-redirect-url.js +0 -19
- package/dist/client/auth-redirect-url.js.map +0 -1
- package/dist/client/useProductionAgent.d.ts +0 -24
- package/dist/client/useProductionAgent.d.ts.map +0 -1
- package/dist/client/useProductionAgent.js +0 -179
- package/dist/client/useProductionAgent.js.map +0 -1
- package/dist/shared/auth-redirect-url.d.ts +0 -2
- package/dist/shared/auth-redirect-url.d.ts.map +0 -1
- package/dist/shared/auth-redirect-url.js +0 -2
- 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
|
-
|
|
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:
|
|
125
|
-
): Promise<
|
|
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
|
-
|
|
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() ?? [])
|
|
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
|
-
|
|
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
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
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
|
|
502
|
-
(
|
|
503
|
-
const transcript = extractCompletedRealtimeVoiceTranscript(event);
|
|
1514
|
+
const publishCompletedTranscript = useCallback(
|
|
1515
|
+
(transcript: CompletedRealtimeVoiceTranscript) => {
|
|
504
1516
|
const threadId = transcriptThreadIdRef.current;
|
|
505
|
-
if (!
|
|
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
|
-
|
|
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
|
-
|
|
631
|
-
|
|
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 =
|
|
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 (
|
|
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
|
|
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
|
-
|
|
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(() =>
|
|
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,
|