@agent-native/core 0.98.0 → 0.98.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (243) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/core/CHANGELOG.md +24 -0
  3. package/corpus/core/package.json +2 -2
  4. package/corpus/core/src/action-change-marker.ts +8 -1
  5. package/corpus/core/src/changelog/parse.ts +8 -3
  6. package/corpus/core/src/client/AgentPanel.tsx +2 -3
  7. package/corpus/core/src/client/AssistantChat.tsx +83 -39
  8. package/corpus/core/src/client/analytics.ts +29 -0
  9. package/corpus/core/src/client/blocks/library/wireframe-kit.tsx +16 -2
  10. package/corpus/core/src/client/chat-model-groups.ts +28 -0
  11. package/corpus/core/src/client/composer/RealtimeVoiceMode.tsx +442 -85
  12. package/corpus/core/src/client/composer/TiptapComposer.tsx +46 -29
  13. package/corpus/core/src/client/composer/VoiceButton.tsx +2 -1
  14. package/corpus/core/src/client/composer/index.ts +5 -0
  15. package/corpus/core/src/client/composer/useRealtimeVoiceMode.tsx +1206 -67
  16. package/corpus/core/src/client/index.ts +5 -0
  17. package/corpus/core/src/client/progress/RunsTray.tsx +13 -1
  18. package/corpus/core/src/client/settings/SecretsSection.tsx +387 -266
  19. package/corpus/core/src/client/settings/SettingsPanel.tsx +117 -38
  20. package/corpus/core/src/client/use-action.ts +12 -0
  21. package/corpus/core/src/client/use-agent-chat-home-handoff.ts +8 -1
  22. package/corpus/core/src/client/use-agent-engine-configured.ts +17 -3
  23. package/corpus/core/src/client/use-db-sync.ts +84 -34
  24. package/corpus/core/src/client/use-pausing-interval.ts +14 -4
  25. package/corpus/core/src/integrations/scope-store.ts +1 -1
  26. package/corpus/core/src/integrations/usage-budget-store.ts +1 -1
  27. package/corpus/core/src/localization/default-messages.ts +44 -1
  28. package/corpus/core/src/mcp-client/workspace-servers.ts +4 -4
  29. package/corpus/core/src/secrets/crypto.ts +28 -8
  30. package/corpus/core/src/server/action-change.ts +5 -1
  31. package/corpus/core/src/server/action-routes.ts +8 -0
  32. package/corpus/core/src/server/poll.ts +1 -0
  33. package/corpus/core/src/server/realtime-voice.ts +299 -13
  34. package/corpus/core/src/styles/agent-native.css +2 -2
  35. package/corpus/core/src/templates/default/.agents/skills/real-time-sync/SKILL.md +2 -2
  36. package/corpus/core/src/templates/workspace-core/.agents/skills/real-time-sync/SKILL.md +2 -2
  37. package/corpus/core/src/templates/workspace-core/.agents/skills/secrets/SKILL.md +7 -3
  38. package/corpus/core/src/templates/workspace-core/.agents/skills/voice-transcription/SKILL.md +39 -9
  39. package/corpus/core/src/vite/client.ts +9 -1
  40. package/corpus/templates/analytics/.agents/skills/real-time-sync/SKILL.md +2 -2
  41. package/corpus/templates/analytics/.agents/skills/session-replay/SKILL.md +10 -5
  42. package/corpus/templates/analytics/AGENTS.md +9 -13
  43. package/corpus/templates/analytics/actions/navigate.ts +59 -5
  44. package/corpus/templates/analytics/actions/view-screen.ts +3 -6
  45. package/corpus/templates/analytics/app/components/ProviderCorpusJobNotifier.tsx +20 -3
  46. package/corpus/templates/analytics/app/components/dashboard/SqlChart.tsx +2 -1
  47. package/corpus/templates/analytics/app/components/layout/Header.tsx +1 -1
  48. package/corpus/templates/analytics/app/components/layout/Sidebar.tsx +21 -6
  49. package/corpus/templates/analytics/app/global.css +19 -20
  50. package/corpus/templates/analytics/app/lib/provider-corpus-job-sync.ts +26 -0
  51. package/corpus/templates/analytics/app/pages/Settings.tsx +35 -341
  52. package/corpus/templates/analytics/app/pages/monitoring/ErrorsPanel.tsx +38 -7
  53. package/corpus/templates/analytics/app/pages/monitoring/MonitoringPage.tsx +1 -1
  54. package/corpus/templates/analytics/app/pages/monitoring/errors/i18n.ts +3 -0
  55. package/corpus/templates/analytics/app/pages/sessions/SessionDetailPage.tsx +333 -272
  56. package/corpus/templates/analytics/app/pages/sessions/SessionDevToolsPanel.tsx +72 -17
  57. package/corpus/templates/analytics/app/pages/sessions/SessionsPage.tsx +297 -108
  58. package/corpus/templates/analytics/app/root.tsx +28 -8
  59. package/corpus/templates/analytics/changelog/2026-06-24-first-party-signup-and-pageview-panels-now-honor-dashboard-filters.md +2 -1
  60. package/corpus/templates/analytics/changelog/2026-06-25-referrer-dashboard-panels-show-external-domains.md +2 -1
  61. package/corpus/templates/analytics/changelog/2026-07-06-mobile-chat-header-is-more-compact-and-the-menu-button-opens-navigation.md +1 -0
  62. package/corpus/templates/analytics/changelog/2026-07-08-dashboard-history-and-chat-context.md +1 -0
  63. package/corpus/templates/analytics/changelog/2026-07-08-settings-are-cleaner-searchable-and-alerts-have-their-own-tab.md +1 -0
  64. package/corpus/templates/analytics/changelog/2026-07-08-sidebar-sections-stay-collapsed-until-opened.md +1 -0
  65. package/corpus/templates/analytics/changelog/2026-07-11-analytics-no-longer-repeatedly-restarts-background-requests-.md +6 -0
  66. package/corpus/templates/analytics/changelog/2026-07-11-analytics-only-asks-for-replay-storage-when-you-enable-sessi.md +6 -0
  67. package/corpus/templates/analytics/changelog/2026-07-11-charts-now-show-their-loading-skeleton-while-refreshing.md +6 -0
  68. package/corpus/templates/analytics/changelog/2026-07-11-realtime-voice-now-shows-a-clear-connection-indicator-comple.md +6 -0
  69. package/corpus/templates/analytics/changelog/2026-07-11-replay-event-counts-stay-hidden-until-recordings-finish-load.md +6 -0
  70. package/corpus/templates/analytics/changelog/2026-07-11-replay-storage-key-mismatches-show-actionable-setup-guidance.md +6 -0
  71. package/corpus/templates/analytics/changelog/2026-07-11-replay-timelines-group-continuous-scrolling-and-dev-tools-co.md +6 -0
  72. package/corpus/templates/analytics/changelog/2026-07-11-session-filters-now-stay-compact-in-a-settings-menu-at-mediu.md +6 -0
  73. package/corpus/templates/analytics/changelog/2026-07-11-session-replay-console-errors-now-link-to-matching-issues-or.md +6 -0
  74. package/corpus/templates/analytics/changelog/2026-07-11-session-replays-load-large-recordings-faster-and-surface-fai.md +6 -0
  75. package/corpus/templates/analytics/changelog/2026-07-11-session-replays-preserve-recorded-styles-and-viewport-layout.md +6 -0
  76. package/corpus/templates/analytics/changelog/2026-07-11-session-replays-stay-centered-instead-of-clipping-wide-recor.md +6 -0
  77. package/corpus/templates/analytics/package.json +1 -1
  78. package/corpus/templates/analytics/seeds/dashboards/agent-native-templates-first-party.json +1 -303
  79. package/corpus/templates/analytics/server/handlers/session-replay.ts +57 -0
  80. package/corpus/templates/analytics/server/lib/dashboard-catalog.ts +1 -1
  81. package/corpus/templates/analytics/server/lib/demo-dashboards.ts +16 -3
  82. package/corpus/templates/analytics/server/lib/session-replay-agent-context.ts +12 -0
  83. package/corpus/templates/analytics/server/lib/session-replay.ts +208 -2
  84. package/corpus/templates/analytics/server/plugins/onboarding.ts +7 -79
  85. package/corpus/templates/analytics/server/routes/api/session-replay/recordings/[recordingId]/chunks.get.ts +1 -0
  86. package/corpus/templates/assets/.agents/skills/real-time-sync/SKILL.md +2 -2
  87. package/corpus/templates/assets/app/global.css +4 -4
  88. package/corpus/templates/assets/app/routes/brand-kits.$id.tsx +0 -1
  89. package/corpus/templates/assets/app/routes/library.tsx +0 -1
  90. package/corpus/templates/brain/.agents/skills/real-time-sync/SKILL.md +2 -2
  91. package/corpus/templates/brain/app/global.css +5 -5
  92. package/corpus/templates/brain/app/root.tsx +0 -3
  93. package/corpus/templates/calendar/.agents/skills/real-time-sync/SKILL.md +2 -2
  94. package/corpus/templates/calendar/app/root.tsx +1 -0
  95. package/corpus/templates/chat/.agents/skills/real-time-sync/SKILL.md +2 -2
  96. package/corpus/templates/clips/app/hooks/use-auto-title.ts +74 -8
  97. package/corpus/templates/clips/app/hooks/use-library.ts +12 -25
  98. package/corpus/templates/clips/app/routes/_app.settings._index.tsx +20 -22
  99. package/corpus/templates/clips/changelog/2026-07-11-generated-titles-no-longer-cause-continuous-background-refreshes.md +6 -0
  100. package/corpus/templates/clips/changelog/2026-07-11-s3-storage-setup-stays-collapsed-until-you-choose-it-as-the-.md +6 -0
  101. package/corpus/templates/content/.agents/skills/real-time-sync/SKILL.md +2 -2
  102. package/corpus/templates/content/app/hooks/use-comments.ts +0 -1
  103. package/corpus/templates/content/changelog/2026-07-11-comments-now-update-from-live-collaboration-events-without-c.md +6 -0
  104. package/corpus/templates/design/app/components/layout/Header.tsx +1 -1
  105. package/corpus/templates/forms/.agents/skills/real-time-sync/SKILL.md +2 -2
  106. package/corpus/templates/macros/app/components/layout/AppLayout.tsx +1 -2
  107. package/corpus/templates/macros/app/root.tsx +0 -3
  108. package/corpus/templates/mail/.agents/skills/real-time-sync/SKILL.md +2 -2
  109. package/corpus/templates/mail/app/hooks/use-compose-state.ts +1 -0
  110. package/corpus/templates/mail/app/root.tsx +1 -12
  111. package/corpus/templates/plan/.agents/skills/real-time-sync/SKILL.md +2 -2
  112. package/corpus/templates/plan/app/hooks/use-navigation-state.ts +5 -2
  113. package/corpus/templates/plan/app/hooks/use-plans.ts +6 -13
  114. package/corpus/templates/plan/app/pages/PlansPage.tsx +5 -27
  115. package/corpus/templates/plan/changelog/2026-07-11-plan-refreshes-only-when-content-changes.md +6 -0
  116. package/corpus/templates/slides/app/components/comments/SlideCommentsPanel.tsx +4 -1
  117. package/corpus/templates/slides/app/components/editor/EditorToolbar.tsx +1 -1
  118. package/corpus/templates/slides/app/components/layout/Header.tsx +1 -1
  119. package/corpus/templates/slides/app/context/DeckContext.tsx +1 -0
  120. package/corpus/templates/slides/app/hooks/use-slide-comments.ts +7 -31
  121. package/corpus/templates/slides/changelog/2026-07-11-comments-now-update-from-live-collaboration-events-without-c.md +6 -0
  122. package/dist/action-change-marker.d.ts +1 -0
  123. package/dist/action-change-marker.d.ts.map +1 -1
  124. package/dist/action-change-marker.js +7 -1
  125. package/dist/action-change-marker.js.map +1 -1
  126. package/dist/changelog/parse.d.ts +4 -2
  127. package/dist/changelog/parse.d.ts.map +1 -1
  128. package/dist/changelog/parse.js +5 -3
  129. package/dist/changelog/parse.js.map +1 -1
  130. package/dist/client/AgentPanel.d.ts.map +1 -1
  131. package/dist/client/AgentPanel.js +2 -3
  132. package/dist/client/AgentPanel.js.map +1 -1
  133. package/dist/client/AssistantChat.d.ts +7 -0
  134. package/dist/client/AssistantChat.d.ts.map +1 -1
  135. package/dist/client/AssistantChat.js +62 -40
  136. package/dist/client/AssistantChat.js.map +1 -1
  137. package/dist/client/analytics.d.ts.map +1 -1
  138. package/dist/client/analytics.js +26 -0
  139. package/dist/client/analytics.js.map +1 -1
  140. package/dist/client/blocks/library/wireframe-kit.d.ts +2 -0
  141. package/dist/client/blocks/library/wireframe-kit.d.ts.map +1 -1
  142. package/dist/client/blocks/library/wireframe-kit.js +9 -2
  143. package/dist/client/blocks/library/wireframe-kit.js.map +1 -1
  144. package/dist/client/chat-model-groups.d.ts.map +1 -1
  145. package/dist/client/chat-model-groups.js +24 -0
  146. package/dist/client/chat-model-groups.js.map +1 -1
  147. package/dist/client/composer/RealtimeVoiceMode.d.ts +40 -4
  148. package/dist/client/composer/RealtimeVoiceMode.d.ts.map +1 -1
  149. package/dist/client/composer/RealtimeVoiceMode.js +172 -42
  150. package/dist/client/composer/RealtimeVoiceMode.js.map +1 -1
  151. package/dist/client/composer/TiptapComposer.d.ts +4 -0
  152. package/dist/client/composer/TiptapComposer.d.ts.map +1 -1
  153. package/dist/client/composer/TiptapComposer.js +47 -29
  154. package/dist/client/composer/TiptapComposer.js.map +1 -1
  155. package/dist/client/composer/VoiceButton.d.ts.map +1 -1
  156. package/dist/client/composer/VoiceButton.js +2 -2
  157. package/dist/client/composer/VoiceButton.js.map +1 -1
  158. package/dist/client/composer/index.d.ts +2 -2
  159. package/dist/client/composer/index.d.ts.map +1 -1
  160. package/dist/client/composer/index.js +1 -1
  161. package/dist/client/composer/index.js.map +1 -1
  162. package/dist/client/composer/useRealtimeVoiceMode.d.ts +100 -3
  163. package/dist/client/composer/useRealtimeVoiceMode.d.ts.map +1 -1
  164. package/dist/client/composer/useRealtimeVoiceMode.js +952 -51
  165. package/dist/client/composer/useRealtimeVoiceMode.js.map +1 -1
  166. package/dist/client/index.d.ts +1 -1
  167. package/dist/client/index.d.ts.map +1 -1
  168. package/dist/client/index.js +1 -1
  169. package/dist/client/index.js.map +1 -1
  170. package/dist/client/progress/RunsTray.d.ts.map +1 -1
  171. package/dist/client/progress/RunsTray.js +10 -1
  172. package/dist/client/progress/RunsTray.js.map +1 -1
  173. package/dist/client/settings/SecretsSection.d.ts +2 -3
  174. package/dist/client/settings/SecretsSection.d.ts.map +1 -1
  175. package/dist/client/settings/SecretsSection.js +46 -22
  176. package/dist/client/settings/SecretsSection.js.map +1 -1
  177. package/dist/client/settings/SettingsPanel.d.ts +9 -0
  178. package/dist/client/settings/SettingsPanel.d.ts.map +1 -1
  179. package/dist/client/settings/SettingsPanel.js +46 -12
  180. package/dist/client/settings/SettingsPanel.js.map +1 -1
  181. package/dist/client/use-action.d.ts +2 -0
  182. package/dist/client/use-action.d.ts.map +1 -1
  183. package/dist/client/use-action.js +10 -0
  184. package/dist/client/use-action.js.map +1 -1
  185. package/dist/client/use-agent-chat-home-handoff.d.ts +2 -0
  186. package/dist/client/use-agent-chat-home-handoff.d.ts.map +1 -1
  187. package/dist/client/use-agent-chat-home-handoff.js +7 -1
  188. package/dist/client/use-agent-chat-home-handoff.js.map +1 -1
  189. package/dist/client/use-agent-engine-configured.d.ts.map +1 -1
  190. package/dist/client/use-agent-engine-configured.js +17 -3
  191. package/dist/client/use-agent-engine-configured.js.map +1 -1
  192. package/dist/client/use-db-sync.d.ts +8 -2
  193. package/dist/client/use-db-sync.d.ts.map +1 -1
  194. package/dist/client/use-db-sync.js +57 -32
  195. package/dist/client/use-db-sync.js.map +1 -1
  196. package/dist/client/use-pausing-interval.d.ts.map +1 -1
  197. package/dist/client/use-pausing-interval.js +16 -4
  198. package/dist/client/use-pausing-interval.js.map +1 -1
  199. package/dist/integrations/scope-store.js +1 -1
  200. package/dist/integrations/scope-store.js.map +1 -1
  201. package/dist/integrations/usage-budget-store.js +1 -1
  202. package/dist/integrations/usage-budget-store.js.map +1 -1
  203. package/dist/localization/default-messages.d.ts +41 -1
  204. package/dist/localization/default-messages.d.ts.map +1 -1
  205. package/dist/localization/default-messages.js +41 -1
  206. package/dist/localization/default-messages.js.map +1 -1
  207. package/dist/mcp-client/workspace-servers.js +4 -4
  208. package/dist/mcp-client/workspace-servers.js.map +1 -1
  209. package/dist/observability/routes.d.ts +5 -5
  210. package/dist/provider-api/corpus-jobs.d.ts +2 -2
  211. package/dist/secrets/crypto.d.ts +7 -6
  212. package/dist/secrets/crypto.d.ts.map +1 -1
  213. package/dist/secrets/crypto.js +30 -11
  214. package/dist/secrets/crypto.js.map +1 -1
  215. package/dist/secrets/routes.d.ts +9 -9
  216. package/dist/server/action-change.d.ts +1 -0
  217. package/dist/server/action-change.d.ts.map +1 -1
  218. package/dist/server/action-change.js +4 -1
  219. package/dist/server/action-change.js.map +1 -1
  220. package/dist/server/action-routes.d.ts.map +1 -1
  221. package/dist/server/action-routes.js +5 -0
  222. package/dist/server/action-routes.js.map +1 -1
  223. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  224. package/dist/server/poll.d.ts.map +1 -1
  225. package/dist/server/poll.js +1 -0
  226. package/dist/server/poll.js.map +1 -1
  227. package/dist/server/realtime-voice.d.ts +7 -0
  228. package/dist/server/realtime-voice.d.ts.map +1 -1
  229. package/dist/server/realtime-voice.js +222 -12
  230. package/dist/server/realtime-voice.js.map +1 -1
  231. package/dist/styles/agent-native.css +2 -2
  232. package/dist/templates/default/.agents/skills/real-time-sync/SKILL.md +2 -2
  233. package/dist/templates/workspace-core/.agents/skills/real-time-sync/SKILL.md +2 -2
  234. package/dist/templates/workspace-core/.agents/skills/secrets/SKILL.md +7 -3
  235. package/dist/templates/workspace-core/.agents/skills/voice-transcription/SKILL.md +39 -9
  236. package/dist/vite/client.d.ts.map +1 -1
  237. package/dist/vite/client.js +6 -1
  238. package/dist/vite/client.js.map +1 -1
  239. package/package.json +2 -2
  240. package/src/templates/default/.agents/skills/real-time-sync/SKILL.md +2 -2
  241. package/src/templates/workspace-core/.agents/skills/real-time-sync/SKILL.md +2 -2
  242. package/src/templates/workspace-core/.agents/skills/secrets/SKILL.md +7 -3
  243. package/src/templates/workspace-core/.agents/skills/voice-transcription/SKILL.md +39 -9
@@ -164,12 +164,14 @@ function SettingsSelect({
164
164
  value,
165
165
  options,
166
166
  onValueChange,
167
+ disabled = false,
167
168
  }: {
168
169
  label: string;
169
170
  labelAdornment?: React.ReactNode;
170
171
  value: string;
171
172
  options: SettingsSelectOption[];
172
173
  onValueChange: (value: string) => void;
174
+ disabled?: boolean;
173
175
  }) {
174
176
  const isPage = useSettingsSurface() === "page";
175
177
  const controlStyle = isPage ? CONTROL_STYLE_PAGE : CONTROL_STYLE;
@@ -181,10 +183,14 @@ function SettingsSelect({
181
183
  <p className={fieldLabelClass(isPage)}>{label}</p>
182
184
  {labelAdornment}
183
185
  </div>
184
- <SelectPrimitive.Root value={value} onValueChange={onValueChange}>
186
+ <SelectPrimitive.Root
187
+ value={value}
188
+ onValueChange={onValueChange}
189
+ disabled={disabled}
190
+ >
185
191
  <SelectPrimitive.Trigger
186
192
  className={cn(
187
- "flex w-full items-center justify-between rounded-md border border-border bg-background px-3 text-start text-foreground outline-none transition-colors hover:bg-accent/40 data-[placeholder]:text-muted-foreground",
193
+ "flex w-full items-center justify-between rounded-md border border-border bg-background px-3 text-start text-foreground outline-none transition-colors hover:bg-accent/40 data-[placeholder]:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-60",
188
194
  isPage ? "h-10 text-sm" : "h-9 text-[12px]",
189
195
  )}
190
196
  aria-label={label}
@@ -624,8 +630,31 @@ function friendlyModelName(model: string): string {
624
630
  const tier = claude[1][0].toUpperCase() + claude[1].slice(1);
625
631
  return `${tier} ${claude[2]}${claude[3] ? `.${claude[3]}` : ""}`;
626
632
  }
627
- if (model.startsWith("gpt-")) return `GPT-${model.slice(4)}`;
633
+ if (model.startsWith("gpt-")) {
634
+ const rest = model.slice(4);
635
+ const gpt = rest.match(/^(\d+)[.-](\d+)(?:[.-](.+))?$/);
636
+ if (gpt) {
637
+ const suffix = gpt[3]
638
+ ? ` ${gpt[3]
639
+ .split("-")
640
+ .map((part) => part[0].toUpperCase() + part.slice(1))
641
+ .join(" ")}`
642
+ : "";
643
+ return `GPT-${gpt[1]}.${gpt[2]}${suffix}`;
644
+ }
645
+ return `GPT-${rest}`;
646
+ }
628
647
  if (/^o\d/.test(model)) return model;
648
+ const geminiVersioned = model.match(
649
+ /^gemini-(\d+)-(\d+)-(.+?)(?:-preview)?$/,
650
+ );
651
+ if (geminiVersioned) {
652
+ const variant = geminiVersioned[3]
653
+ .split("-")
654
+ .map((part) => part[0].toUpperCase() + part.slice(1))
655
+ .join(" ");
656
+ return `Gemini ${geminiVersioned[1]}.${geminiVersioned[2]} ${variant}`;
657
+ }
629
658
  const gemini = model.match(/^gemini-(.+?)(?:-preview)?$/);
630
659
  if (gemini) {
631
660
  const parts = gemini[1]
@@ -682,6 +711,77 @@ function latestModelsOnly(models: string[]): string[] {
682
711
  });
683
712
  }
684
713
 
714
+ export function AppDefaultModelField({
715
+ engine,
716
+ models,
717
+ value,
718
+ defaultModel,
719
+ disabled,
720
+ onValueChange,
721
+ onEnter,
722
+ }: {
723
+ engine: string;
724
+ models: string[];
725
+ value: string;
726
+ defaultModel?: string;
727
+ disabled?: boolean;
728
+ onValueChange: (value: string) => void;
729
+ onEnter?: () => void;
730
+ }) {
731
+ const isPage = useSettingsSurface() === "page";
732
+ const modelOptions: SettingsSelectOption[] = latestModelsOnly(models).map(
733
+ (model) => ({ value: model, label: friendlyModelName(model) }),
734
+ );
735
+
736
+ // Builder models are a closed catalog (and are validated server-side), so a
737
+ // real select keeps every available model visible even when one is already
738
+ // selected. Native datalists filter against the current input value, which
739
+ // made this field appear to contain only the active model.
740
+ if (engine === "builder" && modelOptions.length > 0) {
741
+ return (
742
+ <SettingsSelect
743
+ label="Model"
744
+ value={value}
745
+ options={modelOptions}
746
+ onValueChange={onValueChange}
747
+ disabled={disabled}
748
+ />
749
+ );
750
+ }
751
+
752
+ return (
753
+ <div className="space-y-1.5">
754
+ <p className={fieldLabelClass(isPage)}>Model</p>
755
+ <input
756
+ type="text"
757
+ list={`app-model-suggestions-${engine}`}
758
+ value={value}
759
+ disabled={disabled}
760
+ onChange={(event) => onValueChange(event.target.value)}
761
+ onKeyDown={(event) => {
762
+ if (event.key === "Enter") onEnter?.();
763
+ }}
764
+ placeholder={defaultModel ?? "model-id"}
765
+ spellCheck={false}
766
+ autoComplete="off"
767
+ className={cn(textInputClass(isPage), "disabled:opacity-60")}
768
+ style={isPage ? CONTROL_STYLE_PAGE : CONTROL_STYLE}
769
+ />
770
+ {modelOptions.length > 0 && (
771
+ <datalist id={`app-model-suggestions-${engine}`}>
772
+ {modelOptions.map((option) => (
773
+ <option
774
+ key={option.value}
775
+ value={option.value}
776
+ label={option.label}
777
+ />
778
+ ))}
779
+ </datalist>
780
+ )}
781
+ </div>
782
+ );
783
+ }
784
+
685
785
  // ─── LLM Section ────────────────────────────────────────────────────────────
686
786
 
687
787
  interface EngineInfo {
@@ -1388,9 +1488,6 @@ function AppModelDefaultsSectionInner({
1388
1488
  ? `Install ${engine.installPackage ?? "the provider packages"} to use this provider`
1389
1489
  : "Credentials not detected yet",
1390
1490
  }));
1391
- const modelOptions: SettingsSelectOption[] = latestModelsOnly(
1392
- selectedEngineInfo?.supportedModels ?? [],
1393
- ).map((model) => ({ value: model, label: friendlyModelName(model) }));
1394
1491
  const hasPendingChange =
1395
1492
  !!settings &&
1396
1493
  settings.canUpdate &&
@@ -1536,38 +1633,20 @@ function AppModelDefaultsSectionInner({
1536
1633
  }}
1537
1634
  />
1538
1635
 
1539
- <div className="space-y-1.5">
1540
- <p className={fieldLabelClass(isPage)}>Model</p>
1541
- <input
1542
- type="text"
1543
- list={`app-model-suggestions-${selectedEngine}`}
1544
- value={selectedModel}
1545
- disabled={!settings.canUpdate || saving}
1546
- onChange={(event) => {
1547
- setSelectedModel(event.target.value);
1548
- setError(null);
1549
- }}
1550
- onKeyDown={(event) => {
1551
- if (event.key === "Enter" && hasPendingChange) void save();
1552
- }}
1553
- placeholder={selectedEngineInfo?.defaultModel ?? "model-id"}
1554
- spellCheck={false}
1555
- autoComplete="off"
1556
- className={cn(textInputClass(isPage), "disabled:opacity-60")}
1557
- style={isPage ? CONTROL_STYLE_PAGE : CONTROL_STYLE}
1558
- />
1559
- {modelOptions.length > 0 && (
1560
- <datalist id={`app-model-suggestions-${selectedEngine}`}>
1561
- {modelOptions.map((option) => (
1562
- <option
1563
- key={option.value}
1564
- value={option.value}
1565
- label={option.label}
1566
- />
1567
- ))}
1568
- </datalist>
1569
- )}
1570
- </div>
1636
+ <AppDefaultModelField
1637
+ engine={selectedEngine}
1638
+ models={selectedEngineInfo?.supportedModels ?? []}
1639
+ value={selectedModel}
1640
+ defaultModel={selectedEngineInfo?.defaultModel}
1641
+ disabled={!settings.canUpdate || saving}
1642
+ onValueChange={(value) => {
1643
+ setSelectedModel(value);
1644
+ setError(null);
1645
+ }}
1646
+ onEnter={() => {
1647
+ if (hasPendingChange) void save();
1648
+ }}
1649
+ />
1571
1650
 
1572
1651
  <div className="flex items-center gap-1.5">
1573
1652
  <button
@@ -28,6 +28,7 @@ import type {
28
28
  } from "@tanstack/react-query";
29
29
 
30
30
  import { agentNativePath } from "./api-path.js";
31
+ import { getBrowserTabId } from "./browser-tab-id.js";
31
32
  import { ensureEmbedAuthFetchInterceptor } from "./embed-auth.js";
32
33
 
33
34
  const ACTION_PREFIX = agentNativePath("/_agent-native/actions");
@@ -216,6 +217,8 @@ export interface ActionFetchOptions {
216
217
  keepalive?: boolean;
217
218
  /** Pre-serialized mutation body used by the keepalive budget coordinator. */
218
219
  serializedBody?: string;
220
+ /** Omit the tab echo-suppression tag for imperative callers. */
221
+ includeRequestSource?: boolean;
219
222
  }
220
223
 
221
224
  /**
@@ -264,6 +267,14 @@ async function actionFetch<T>(
264
267
  // safe to expose: CORS allows it (see action-routes.ts) and it carries
265
268
  // no auth weight — it only narrows the caller tag.
266
269
  "X-Agent-Native-Frontend": "1",
270
+ ...(options?.includeRequestSource !== false
271
+ ? {
272
+ // The server copies this onto the emitted action sync event.
273
+ // useDbSync can then ignore the echo in this tab while other tabs
274
+ // still refresh.
275
+ "X-Request-Source": getBrowserTabId(),
276
+ }
277
+ : {}),
267
278
  };
268
279
  const tz = resolveUserTimezone();
269
280
  if (tz) headers["x-user-timezone"] = tz;
@@ -426,6 +437,7 @@ export function callAction<
426
437
  return actionFetch<R>(actionName, options.method ?? "POST", params, {
427
438
  signal: options.signal,
428
439
  timeoutMs: options.timeoutMs,
440
+ includeRequestSource: false,
429
441
  });
430
442
  }
431
443
 
@@ -79,6 +79,10 @@ function isStaticAssetPath(pathname: string): boolean {
79
79
  return /\.[A-Za-z0-9]{1,12}$/.test(lastSegment);
80
80
  }
81
81
 
82
+ function isSettingsPath(pathname: string): boolean {
83
+ return pathname === "/settings" || pathname.startsWith("/settings/");
84
+ }
85
+
82
86
  function localPathFromAnchor(anchor: HTMLAnchorElement): string | null {
83
87
  if (!anchor.href) return null;
84
88
  try {
@@ -135,6 +139,8 @@ export function useAgentChatHomeHandoff({
135
139
  /**
136
140
  * Intercepts ordinary in-app links clicked from a full-page chat route so the
137
141
  * page chat can morph into the destination AgentSidebar and keep its thread.
142
+ * Settings is intentionally excluded: a user opening it keeps chat full-page.
143
+ * Agent-driven navigation can still opt into the handoff by marking it first.
138
144
  */
139
145
  export function useAgentChatHomeHandoffLinks({
140
146
  storageKey,
@@ -170,7 +176,8 @@ export function useAgentChatHomeHandoffLinks({
170
176
  !path ||
171
177
  matchesChatPath(pathname) ||
172
178
  isFrameworkOrApiPath(pathname) ||
173
- isStaticAssetPath(pathname)
179
+ isStaticAssetPath(pathname) ||
180
+ isSettingsPath(pathname)
174
181
  ) {
175
182
  return;
176
183
  }
@@ -30,6 +30,7 @@ export interface UseAgentEngineConfiguredOptions {
30
30
  }
31
31
 
32
32
  const DEFAULT_STATUS_CHECK_TIMEOUT_MS = 2500;
33
+ const UNKNOWN_STATUS_RETRY_MS = 2000;
33
34
 
34
35
  async function fetchStatusJson(
35
36
  path: string,
@@ -129,9 +130,15 @@ export async function fetchAgentEngineConfiguredState(
129
130
  (builderStatusKnown && builderStatus.configured) ||
130
131
  (engineStatusKnown && engineStatus.configured);
131
132
  if (anyConfigured) return "configured";
132
- return envKeysKnown && builderStatusKnown && engineStatusKnown
133
- ? "missing"
134
- : "unknown";
133
+
134
+ // The engine status route is the canonical readiness check: it resolves
135
+ // Builder, scoped BYOK secrets, deployment credentials, and custom engines.
136
+ // Once it has answered `configured: false`, a slow legacy env/Builder status
137
+ // request must not leave the composer permissively stuck in `unknown`.
138
+ if (engineStatusKnown) return "missing";
139
+
140
+ // Compatibility fallback for older hosts without the canonical route.
141
+ return envKeysKnown && builderStatusKnown ? "missing" : "unknown";
135
142
  }
136
143
 
137
144
  /**
@@ -149,16 +156,22 @@ export function useAgentEngineConfigured(
149
156
 
150
157
  useEffect(() => {
151
158
  let cancelled = false;
159
+ let retryId: ReturnType<typeof setTimeout> | undefined;
152
160
  // Monotonic call counter: overlapping checks (mount + a
153
161
  // `agent-engine:configured-changed` fired right after a key is saved) can
154
162
  // resolve out of order; only the latest call may write state, or a slow
155
163
  // stale "missing" response would overwrite the fresh "configured" one.
156
164
  let requestSeq = 0;
157
165
  const check = async (options?: { missingFallback?: boolean }) => {
166
+ if (retryId !== undefined) {
167
+ clearTimeout(retryId);
168
+ retryId = undefined;
169
+ }
158
170
  const seq = ++requestSeq;
159
171
  const nextState = await fetchAgentEngineConfiguredState(enabled, options);
160
172
  if (cancelled || seq !== requestSeq) return;
161
173
  if (nextState === "unknown") {
174
+ retryId = setTimeout(() => void check(), UNKNOWN_STATUS_RETRY_MS);
162
175
  return;
163
176
  }
164
177
  setState(nextState);
@@ -185,6 +198,7 @@ export function useAgentEngineConfigured(
185
198
  window.addEventListener("agent-chat:missing-api-key", onMissing);
186
199
  return () => {
187
200
  cancelled = true;
201
+ if (retryId !== undefined) clearTimeout(retryId);
188
202
  window.removeEventListener(
189
203
  "agent-engine:configured-changed",
190
204
  onConfiguredChanged,
@@ -5,6 +5,7 @@ import {
5
5
  refreshDemoModeFetchInterceptor,
6
6
  } from "../demo/fetch-interceptor.js";
7
7
  import { agentNativePath } from "./api-path.js";
8
+ import { getBrowserTabId } from "./browser-tab-id.js";
8
9
  import {
9
10
  ensureEmbedAuthFetchInterceptor,
10
11
  isEmbedAuthActive,
@@ -16,15 +17,25 @@ interface Query {
16
17
  }
17
18
 
18
19
  interface QueryClient {
19
- invalidateQueries(opts?: {
20
+ invalidateQueries(
21
+ opts?: {
22
+ queryKey?: string[];
23
+ predicate?: (query: Query) => boolean;
24
+ },
25
+ options?: { cancelRefetch?: boolean },
26
+ ): unknown;
27
+ isFetching?(filters?: {
20
28
  queryKey?: string[];
21
29
  predicate?: (query: Query) => boolean;
22
- }): void;
30
+ }): number;
23
31
  }
24
32
 
25
33
  const POLL_ABORT_MIN_MS = 10_000;
26
- const SSE_FALLBACK_INTERVAL_MS = 15_000;
27
- const IDLE_POLL_INTERVAL_MS = 15_000;
34
+ // SSE delivers changes immediately in the normal path. The poll is a
35
+ // cross-process/serverless safety net, so an idle tab should not bill the host
36
+ // four times per minute forever. Focus and active agent work still poll now.
37
+ const SSE_FALLBACK_INTERVAL_MS = 60_000;
38
+ const IDLE_POLL_INTERVAL_MS = 60_000;
28
39
  const POLL_AUTH_FAILURE_COOLDOWN_MS = 60_000;
29
40
  /**
30
41
  * Max cadence for SSE/poll-driven query invalidation in `useDbSync`. Events
@@ -356,7 +367,7 @@ class SyncTransport {
356
367
  // full cadence. Resets on the first successful poll.
357
368
  const delay =
358
369
  this.consecutiveFailures > 0
359
- ? Math.min(base * 2 ** Math.min(this.consecutiveFailures, 5), 30_000)
370
+ ? Math.min(base * 2 ** Math.min(this.consecutiveFailures, 5), 300_000)
360
371
  : base;
361
372
  this.timer = setTimeout(() => {
362
373
  this.timer = null;
@@ -675,7 +686,7 @@ export function subscribeSyncEvents(
675
686
  * @param options.onEvent - Optional callback for each change event
676
687
  * @param options.interval - Poll interval in ms. Default: 2000
677
688
  * @param options.fallbackInterval - Poll interval while SSE is connected.
678
- * Default: 15000
689
+ * Default: 60000
679
690
  * @param options.pauseWhenHidden - Pause polling while the tab is hidden.
680
691
  * Default: true
681
692
  * @param options.ignoreSource - Skip events whose `requestSource` matches this
@@ -798,9 +809,15 @@ export function useDbSync(
798
809
 
799
810
  function invalidateForEvents(events: SyncEvent[]) {
800
811
  const ignore = ignoreSourceRef.current;
801
- const relevant = ignore
802
- ? events.filter((e) => e.requestSource !== ignore)
803
- : events;
812
+ const ownBrowserSource = getBrowserTabId();
813
+ const relevant = events.filter(
814
+ (event) =>
815
+ !(
816
+ event.source === "action" &&
817
+ event.requestSource === ownBrowserSource
818
+ ) &&
819
+ (!ignore || event.requestSource !== ignore),
820
+ );
804
821
  const suppressedActions = new Set(
805
822
  suppressActionInvalidationForRef.current ?? [],
806
823
  );
@@ -838,18 +855,43 @@ export function useDbSync(
838
855
  const invalidating = relevant.filter((e) => e.source !== "awareness");
839
856
 
840
857
  if (invalidating.length > 0 && queryClient) {
858
+ // Sync events describe completed writes. If a matching read is already
859
+ // in flight, let it finish instead of aborting and immediately
860
+ // launching the same request again. Repeated action events otherwise
861
+ // turn a slow endpoint into a cancel/restart loop that never settles.
862
+ const invalidateWithoutCancel = (filters?: {
863
+ queryKey?: string[];
864
+ predicate?: (query: Query) => boolean;
865
+ }) => {
866
+ const needsTrailingRefresh =
867
+ (queryClient.isFetching?.(filters) ?? 0) > 0;
868
+ const completion = queryClient.invalidateQueries(filters, {
869
+ cancelRefetch: false,
870
+ });
871
+ // TanStack Query deliberately leaves an in-flight fetch alone when
872
+ // cancelRefetch is false. Queue one post-settlement invalidation so
873
+ // a write that landed after that read began cannot be cleared as
874
+ // fresh by the older response.
875
+ if (needsTrailingRefresh && completion instanceof Promise) {
876
+ void completion.then(
877
+ () => queryClient.invalidateQueries(filters),
878
+ () => {},
879
+ );
880
+ }
881
+ };
841
882
  const hasActionEvent = invalidating.some(
842
883
  (evt) => evt.source === "action" && !isSuppressedActionEvent(evt),
843
884
  );
844
885
  if (hasActionEvent) {
845
- // Custom apps frequently start with raw `useQuery` calls before
846
- // graduating to `useActionQuery` or source-versioned query keys.
847
- // A successful mutating action is the core "agent changed app data"
848
- // signal, so refresh active queries broadly as a compatibility
849
- // safety net. Other event sources stay targeted to avoid request
850
- // storms from noisy domain-specific writes.
886
+ // Action-backed reads share the ["action"] prefix. Keep the default
887
+ // refresh targeted to those queries; invalidating every active query
888
+ // makes one agent write fan out across unrelated provider reads,
889
+ // dashboards, and background status checks. Older apps that still
890
+ // need broad compatibility can opt in with a predicate.
851
891
  const predicate = actionInvalidatePredicateRef.current;
852
- queryClient.invalidateQueries(predicate ? { predicate } : undefined);
892
+ invalidateWithoutCancel(
893
+ predicate ? { predicate } : { queryKey: ["action"] },
894
+ );
853
895
  }
854
896
 
855
897
  // Framework-level invalidate: a small, fixed list of query-key
@@ -880,32 +922,40 @@ export function useDbSync(
880
922
  const hasDataChangingEvent = invalidating.some(
881
923
  (evt) => evt.source !== "app-state",
882
924
  );
883
- // The broad action invalidate above already covers every fixed
884
- // framework prefix. Re-invalidating ["action"] immediately after it
885
- // cancels/restarts the same active reads and creates a duplicate
886
- // request wave. Keep the fixed-prefix pass for non-action sources;
887
- // action batches get exactly one compatibility invalidation.
888
- if (hasDataChangingEvent && !hasActionEvent) {
889
- queryClient.invalidateQueries({ queryKey: ["action"] });
890
- queryClient.invalidateQueries({ queryKey: ["extension"] });
891
- queryClient.invalidateQueries({ queryKey: ["extensions"] });
892
- queryClient.invalidateQueries({ queryKey: ["extension-slots"] });
893
- queryClient.invalidateQueries({ queryKey: ["slot-installs"] });
894
- queryClient.invalidateQueries({ queryKey: ["slot-available"] });
895
- queryClient.invalidateQueries({ queryKey: ["tool"] });
896
- queryClient.invalidateQueries({ queryKey: ["tools"] });
925
+ if (hasDataChangingEvent) {
926
+ const hasFrameworkPrefixEvent = invalidating.some((evt) =>
927
+ ["extensions", "extension", "tool", "tools", "slots"].includes(
928
+ evt.source ?? "",
929
+ ),
930
+ );
931
+ // The action-specific invalidation above already refreshed
932
+ // ["action"]. A mixed action + extension/tool batch still needs
933
+ // the independent framework prefixes, while pure action batches
934
+ // retain their narrow storm-resistant invalidation.
935
+ if (!hasActionEvent) {
936
+ invalidateWithoutCancel({ queryKey: ["action"] });
937
+ }
938
+ if (!hasActionEvent || hasFrameworkPrefixEvent) {
939
+ invalidateWithoutCancel({ queryKey: ["extension"] });
940
+ invalidateWithoutCancel({ queryKey: ["extensions"] });
941
+ invalidateWithoutCancel({ queryKey: ["extension-slots"] });
942
+ invalidateWithoutCancel({ queryKey: ["slot-installs"] });
943
+ invalidateWithoutCancel({ queryKey: ["slot-available"] });
944
+ invalidateWithoutCancel({ queryKey: ["tool"] });
945
+ invalidateWithoutCancel({ queryKey: ["tools"] });
946
+ }
897
947
  }
898
948
  if (invalidating.some((evt) => evt.source === "app-state")) {
899
- queryClient.invalidateQueries({ queryKey: ["app-state"] });
949
+ invalidateWithoutCancel({ queryKey: ["app-state"] });
900
950
  }
901
951
  if (hasAppStateEvent(invalidating, "navigate")) {
902
- queryClient.invalidateQueries({ queryKey: ["navigate-command"] });
952
+ invalidateWithoutCancel({ queryKey: ["navigate-command"] });
903
953
  }
904
954
  if (hasAppStateEvent(invalidating, "show-questions")) {
905
- queryClient.invalidateQueries({ queryKey: ["show-questions"] });
955
+ invalidateWithoutCancel({ queryKey: ["show-questions"] });
906
956
  }
907
957
  if (hasAppStateEvent(invalidating, "__set_url__")) {
908
- queryClient.invalidateQueries({ queryKey: ["__set_url__"] });
958
+ invalidateWithoutCancel({ queryKey: ["__set_url__"] });
909
959
  }
910
960
  }
911
961
  }
@@ -17,9 +17,19 @@ export function usePausingInterval(
17
17
  useEffect(() => {
18
18
  if (pollMs <= 0) return;
19
19
  let id: ReturnType<typeof setInterval> | null = null;
20
+ let running = false;
20
21
  const isHidden = () => typeof document !== "undefined" && document.hidden;
22
+ const run = async () => {
23
+ if (running) return;
24
+ running = true;
25
+ try {
26
+ await callback();
27
+ } finally {
28
+ running = false;
29
+ }
30
+ };
21
31
  const start = () => {
22
- if (id == null) id = setInterval(callback, pollMs);
32
+ if (id == null) id = setInterval(() => void run(), pollMs);
23
33
  };
24
34
  const stop = () => {
25
35
  if (id != null) {
@@ -29,7 +39,7 @@ export function usePausingInterval(
29
39
  };
30
40
 
31
41
  if (!pauseWhenHidden) {
32
- callback();
42
+ void run();
33
43
  start();
34
44
  return () => stop();
35
45
  }
@@ -38,12 +48,12 @@ export function usePausingInterval(
38
48
  if (document.hidden) {
39
49
  stop();
40
50
  } else {
41
- callback();
51
+ void run();
42
52
  start();
43
53
  }
44
54
  };
45
55
  if (!isHidden()) {
46
- callback();
56
+ void run();
47
57
  start();
48
58
  }
49
59
  document.addEventListener("visibilitychange", onVisibility);
@@ -216,7 +216,7 @@ function serviceOwnerForScope(
216
216
 
217
217
  function authorizationSql(alias = ""): string {
218
218
  const prefix = alias ? `${alias}.` : "";
219
- return `((${prefix}org_id = ? AND ? IS NOT NULL) OR (${prefix}org_id IS NULL AND ${prefix}owner_email = ?))`;
219
+ return `((${prefix}org_id = ? AND CAST(? AS TEXT) IS NOT NULL) OR (${prefix}org_id IS NULL AND ${prefix}owner_email = ?))`;
220
220
  }
221
221
 
222
222
  function authorizationArgs(
@@ -232,7 +232,7 @@ function requirePeriod(
232
232
 
233
233
  function authorizationSql(alias = ""): string {
234
234
  const prefix = alias ? `${alias}.` : "";
235
- return `((${prefix}subject_type = 'user' AND ${prefix}owner_email = ?) OR (${prefix}subject_type <> 'user' AND ((${prefix}org_id = ? AND ? IS NOT NULL) OR (${prefix}org_id IS NULL AND ${prefix}owner_email = ?))))`;
235
+ return `((${prefix}subject_type = 'user' AND ${prefix}owner_email = ?) OR (${prefix}subject_type <> 'user' AND ((${prefix}org_id = ? AND CAST(? AS TEXT) IS NOT NULL) OR (${prefix}org_id IS NULL AND ${prefix}owner_email = ?))))`;
236
236
  }
237
237
 
238
238
  function authorizationArgs(
@@ -211,6 +211,7 @@ const messages = {
211
211
  cliRequiresDevModeDescription:
212
212
  "Run this app locally with pnpm dev or use Builder.io to access the CLI terminal.",
213
213
  toggleAgent: "Toggle agent",
214
+ checkingAiConnection: "Checking AI connection...",
214
215
  voiceMode: {
215
216
  entryButtonLabel: "Use microphone",
216
217
  promptTitle: "Talk to your app",
@@ -227,7 +228,47 @@ const messages = {
227
228
  showChat: "Show chat",
228
229
  hideChat: "Hide chat",
229
230
  end: "End voice mode",
230
- microphoneSettings: "Microphone settings",
231
+ voiceSettings: "Voice settings",
232
+ settings: {
233
+ microphone: "Microphone",
234
+ defaultMicrophone: "System default",
235
+ microphoneSwitchFailed:
236
+ "Could not switch microphones. Your current microphone is still active.",
237
+ language: "Language",
238
+ autoLanguage: "Auto",
239
+ languages: {
240
+ en: "English",
241
+ es: "Spanish",
242
+ fr: "French",
243
+ de: "German",
244
+ it: "Italian",
245
+ pt: "Portuguese",
246
+ ja: "Japanese",
247
+ ko: "Korean",
248
+ zh: "Chinese",
249
+ },
250
+ intelligence: "Intelligence",
251
+ intelligenceLevels: {
252
+ instant: "Instant",
253
+ balanced: "Balanced",
254
+ deep: "Deep",
255
+ },
256
+ voiceStyle: "Voice style",
257
+ voiceChangePending:
258
+ "Your new voice will apply next time you start voice mode.",
259
+ voiceDescriptions: {
260
+ marin: "Warm and natural",
261
+ cedar: "Clear and grounded",
262
+ coral: "Friendly and bright",
263
+ sage: "Calm and thoughtful",
264
+ verse: "Expressive and versatile",
265
+ alloy: "Balanced and neutral",
266
+ ash: "Smooth and confident",
267
+ ballad: "Warm and expressive",
268
+ echo: "Clear and direct",
269
+ shimmer: "Light and upbeat",
270
+ },
271
+ },
231
272
  status: {
232
273
  connecting: "Connecting",
233
274
  listening: "Listening",
@@ -242,6 +283,8 @@ const messages = {
242
283
  responseFailed: "OpenAI could not complete the voice response.",
243
284
  sessionFailed: "The realtime voice session encountered an error.",
244
285
  channelDisconnected: "The realtime voice control channel disconnected.",
286
+ connectionTimedOut:
287
+ "The realtime voice connection timed out. Try again after the app finishes loading.",
245
288
  connectionFailed: "The realtime voice connection failed.",
246
289
  offerFailed: "The browser did not create an audio offer.",
247
290
  },
@@ -265,8 +265,8 @@ async function selectWorkspaceMcpResourceRows(options?: {
265
265
  AND wr.scope = ?
266
266
  AND wr.path LIKE ?
267
267
  AND (
268
- (? IS NOT NULL AND wr.org_id = ?)
269
- OR (? IS NOT NULL AND wr.org_id IS NULL AND lower(wr.owner_email) = lower(?))
268
+ (CAST(? AS TEXT) IS NOT NULL AND wr.org_id = ?)
269
+ OR (CAST(? AS TEXT) IS NOT NULL AND wr.org_id IS NULL AND lower(wr.owner_email) = lower(?))
270
270
  )
271
271
  `,
272
272
  args: [
@@ -304,8 +304,8 @@ async function selectWorkspaceMcpResourceRows(options?: {
304
304
  AND wg.status = ?
305
305
  AND wg.app_id = ?
306
306
  AND (
307
- (? IS NOT NULL AND wr.org_id = ?)
308
- OR (? IS NOT NULL AND wr.org_id IS NULL AND lower(wr.owner_email) = lower(?))
307
+ (CAST(? AS TEXT) IS NOT NULL AND wr.org_id = ?)
308
+ OR (CAST(? AS TEXT) IS NOT NULL AND wr.org_id IS NULL AND lower(wr.owner_email) = lower(?))
309
309
  )
310
310
  AND (
311
311
  (wr.org_id IS NOT NULL AND wg.org_id = wr.org_id)