@agent-native/core 0.115.4 → 0.117.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (487) hide show
  1. package/agent-native.eject.json +1 -0
  2. package/corpus/README.md +3 -3
  3. package/corpus/core/CHANGELOG.md +43 -0
  4. package/corpus/core/agent-native.eject.json +1 -0
  5. package/corpus/core/docs/AGENTS.md +1 -1
  6. package/corpus/core/docs/content/agent-mentions.mdx +2 -2
  7. package/corpus/core/docs/content/{workspace.mdx → agent-resources.mdx} +1 -7
  8. package/corpus/core/docs/content/agent-teams.mdx +4 -4
  9. package/corpus/core/docs/content/dispatch.mdx +10 -10
  10. package/corpus/core/docs/content/drop-in-agent.mdx +4 -4
  11. package/corpus/core/docs/content/external-agents.mdx +1 -1
  12. package/corpus/core/docs/content/frames.mdx +1 -1
  13. package/corpus/core/docs/content/integrations.mdx +13 -2
  14. package/corpus/core/docs/content/key-concepts.mdx +2 -2
  15. package/corpus/core/docs/content/mcp-clients.mdx +7 -1
  16. package/corpus/core/docs/content/multi-app-workspace.mdx +3 -3
  17. package/corpus/core/docs/content/real-time-collaboration.mdx +33 -15
  18. package/corpus/core/docs/content/recurring-jobs.mdx +7 -7
  19. package/corpus/core/docs/content/skills-guide.mdx +4 -4
  20. package/corpus/core/docs/content/template-brain.mdx +1 -1
  21. package/corpus/core/docs/content/toolkit-collaboration.mdx +1 -1
  22. package/corpus/core/docs/content/using-your-agent.mdx +1 -1
  23. package/corpus/core/docs/content/what-is-agent-native.mdx +4 -4
  24. package/corpus/core/docs/content/workspace-management.mdx +4 -4
  25. package/corpus/core/package.json +4 -1
  26. package/corpus/core/src/agent/production-agent.ts +13 -3
  27. package/corpus/core/src/cli/doctor.ts +6 -1
  28. package/corpus/core/src/client/AgentPanel.tsx +79 -41
  29. package/corpus/core/src/client/AssistantChat.tsx +71 -25
  30. package/corpus/core/src/client/FeedbackButton.tsx +33 -7
  31. package/corpus/core/src/client/MultiTabAssistantChat.tsx +6 -8
  32. package/corpus/core/src/client/agent-chat-adapter.ts +11 -2
  33. package/corpus/core/src/client/agent-page/AgentTabFrame.tsx +22 -3
  34. package/corpus/core/src/client/agent-page/AgentTabsPage.tsx +34 -19
  35. package/corpus/core/src/client/analytics.ts +7 -0
  36. package/corpus/core/src/client/chat/message-components.tsx +43 -16
  37. package/corpus/core/src/client/chat/tool-call-display.tsx +30 -18
  38. package/corpus/core/src/client/chat/tool-render-registry.tsx +2 -0
  39. package/corpus/core/src/client/resources/McpConnectionSuggestion.tsx +19 -2
  40. package/corpus/core/src/client/resources/ResourceTree.tsx +7 -10
  41. package/corpus/core/src/client/resources/ResourcesPanel.tsx +33 -70
  42. package/corpus/core/src/client/resources/mcp-connection-resume.ts +107 -0
  43. package/corpus/core/src/client/resources/mcp-integration-catalog.ts +65 -12
  44. package/corpus/core/src/client/resources/mcp-integration-logos.ts +4 -0
  45. package/corpus/core/src/client/sse-event-processor.ts +50 -14
  46. package/corpus/core/src/client/use-db-sync.ts +370 -21
  47. package/corpus/core/src/collab/client.ts +26 -2
  48. package/corpus/core/src/connections/catalog.ts +7 -1
  49. package/corpus/core/src/db/ddl-guard.ts +49 -10
  50. package/corpus/core/src/deploy/build.ts +25 -2
  51. package/corpus/core/src/eject/remote-mcp-presets.ts +37 -5
  52. package/corpus/core/src/guards/explicit-collab-access.ts +229 -0
  53. package/corpus/core/src/guards/index.ts +1 -0
  54. package/corpus/core/src/localization/default-messages.ts +54 -5
  55. package/corpus/core/src/mcp/connect-route.ts +34 -51
  56. package/corpus/core/src/provider-api/index.ts +1 -1
  57. package/corpus/core/src/realtime-protocol.ts +92 -0
  58. package/corpus/core/src/server/collab-plugin.ts +137 -18
  59. package/corpus/core/src/server/core-routes-plugin.ts +7 -0
  60. package/corpus/core/src/server/index.ts +2 -0
  61. package/corpus/core/src/server/poll-events.ts +29 -8
  62. package/corpus/core/src/server/poll.ts +1123 -865
  63. package/corpus/core/src/server/realtime-token.ts +106 -0
  64. package/corpus/core/src/server/sentry-config.ts +40 -0
  65. package/corpus/core/src/server/short-lived-token.ts +156 -0
  66. package/corpus/core/src/server/sse.ts +15 -0
  67. package/corpus/core/src/server/ssr-handler.ts +9 -3
  68. package/corpus/core/src/styles/agent-native.css +40 -0
  69. package/corpus/core/src/templates/default/.agents/skills/agent-native-toolkit/SKILL.md +4 -1
  70. package/corpus/core/src/templates/default/.agents/skills/real-time-collab/SKILL.md +35 -13
  71. package/corpus/core/src/templates/default/.agents/skills/real-time-sync/SKILL.md +1 -1
  72. package/corpus/core/src/templates/headless/.agents/skills/agent-native-toolkit/SKILL.md +4 -1
  73. package/corpus/core/src/templates/workspace-core/.agents/skills/agent-native-toolkit/SKILL.md +4 -1
  74. package/corpus/core/src/templates/workspace-core/.agents/skills/real-time-collab/SKILL.md +35 -13
  75. package/corpus/core/src/templates/workspace-core/.agents/skills/real-time-sync/SKILL.md +1 -1
  76. package/corpus/core/src/templates/workspace-core/.agents/skills/voice-transcription/SKILL.md +12 -8
  77. package/corpus/templates/analytics/.agents/skills/agent-native-toolkit/SKILL.md +4 -1
  78. package/corpus/templates/analytics/.agents/skills/real-time-sync/SKILL.md +1 -1
  79. package/corpus/templates/analytics/AGENTS.md +7 -2
  80. package/corpus/templates/analytics/actions/export-dashboard-panel-to-google-sheet.ts +144 -0
  81. package/corpus/templates/analytics/app/components/layout/Layout.tsx +7 -88
  82. package/corpus/templates/analytics/app/components/layout/Sidebar.tsx +34 -37
  83. package/corpus/templates/analytics/app/global.css +0 -4
  84. package/corpus/templates/analytics/app/hooks/use-navigation-state.ts +2 -7
  85. package/corpus/templates/analytics/app/i18n-data.ts +107 -0
  86. package/corpus/templates/analytics/app/lib/data-source-status.ts +9 -0
  87. package/corpus/templates/analytics/app/lib/sql-query.ts +1 -2
  88. package/corpus/templates/analytics/app/pages/Ask.tsx +2 -7
  89. package/corpus/templates/analytics/app/pages/DataSources.tsx +48 -0
  90. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/SqlChartCard.tsx +59 -0
  91. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/index.tsx +2 -0
  92. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/report-panel-window.ts +3 -1
  93. package/corpus/templates/analytics/changelog/2026-07-22-clear-background-chat-progress.md +6 -0
  94. package/corpus/templates/analytics/changelog/2026-07-22-daily-dashboard-email-query-batches.md +6 -0
  95. package/corpus/templates/analytics/changelog/2026-07-22-export-table-panels-directly-to-google-sheets.md +6 -0
  96. package/corpus/templates/analytics/changelog/2026-07-22-full-page-chat-keeps-the-active-conversation-when-moving-to-.md +6 -0
  97. package/corpus/templates/analytics/changelog/2026-07-22-recent-chats-can-expand-in-the-sidebar.md +6 -0
  98. package/corpus/templates/analytics/seeds/dashboards/agent-native-templates-first-party.json +8 -8
  99. package/corpus/templates/analytics/server/lib/canonical-first-party-dashboard-repair.ts +31 -0
  100. package/corpus/templates/analytics/server/lib/dashboard-report-subscriptions.ts +3 -5
  101. package/corpus/templates/analytics/server/lib/dashboard-report.ts +12 -0
  102. package/corpus/templates/analytics/server/lib/first-party-dashboard-repair.ts +3 -5
  103. package/corpus/templates/analytics/server/lib/first-party-metric-catalog.ts +150 -21
  104. package/corpus/templates/analytics/server/lib/google-sheets-export.ts +188 -0
  105. package/corpus/templates/analytics/server/lib/provider-api.ts +7 -2
  106. package/corpus/templates/analytics/server/plugins/collab.ts +6 -3
  107. package/corpus/templates/analytics/shared/sql-query-limits.ts +1 -0
  108. package/corpus/templates/assets/.agents/skills/agent-native-toolkit/SKILL.md +4 -1
  109. package/corpus/templates/assets/.agents/skills/real-time-collab/SKILL.md +35 -13
  110. package/corpus/templates/assets/.agents/skills/real-time-sync/SKILL.md +1 -1
  111. package/corpus/templates/assets/app/components/layout/Layout.tsx +1 -1
  112. package/corpus/templates/assets/app/components/layout/Sidebar.tsx +54 -62
  113. package/corpus/templates/assets/app/hooks/use-navigation-state.ts +2 -6
  114. package/corpus/templates/assets/app/routes/_index.tsx +8 -5
  115. package/corpus/templates/assets/changelog/2026-07-22-full-page-chat-keeps-the-active-conversation-when-moving-to-.md +6 -0
  116. package/corpus/templates/assets/changelog/2026-07-22-recent-chats-expand-from-a-compact-list.md +6 -0
  117. package/corpus/templates/brain/.agents/skills/agent-native-toolkit/SKILL.md +4 -1
  118. package/corpus/templates/brain/.agents/skills/real-time-sync/SKILL.md +1 -1
  119. package/corpus/templates/brain/actions/rotate-source-ingest-token.ts +77 -0
  120. package/corpus/templates/brain/app/components/layout/Layout.tsx +5 -1
  121. package/corpus/templates/brain/app/components/layout/Sidebar.tsx +38 -51
  122. package/corpus/templates/brain/app/hooks/use-navigation-state.ts +2 -9
  123. package/corpus/templates/brain/app/i18n-data.ts +120 -0
  124. package/corpus/templates/brain/app/routes/sources.tsx +156 -5
  125. package/corpus/templates/brain/changelog/2026-07-22-chat-runs-always-end-with-an-answer.md +6 -0
  126. package/corpus/templates/brain/changelog/2026-07-22-clear-background-chat-progress.md +6 -0
  127. package/corpus/templates/brain/changelog/2026-07-22-compact-sidebar-footer.md +6 -0
  128. package/corpus/templates/brain/changelog/2026-07-22-full-page-chat-keeps-the-active-conversation-when-moving-to-.md +6 -0
  129. package/corpus/templates/brain/changelog/2026-07-22-long-chat-titles-stay-inside-the-sidebar.md +6 -0
  130. package/corpus/templates/brain/changelog/2026-07-22-new-clips-and-webhook-sources-now-show-their-secure-connecti.md +6 -0
  131. package/corpus/templates/brain/jobs/emit-netlify-brain-queue-cron.ts +125 -0
  132. package/corpus/templates/brain/netlify.toml +5 -1
  133. package/corpus/templates/brain/scripts/configure-prod-clips-source.ts +50 -0
  134. package/corpus/templates/brain/server/lib/capture-sanitization.ts +64 -6
  135. package/corpus/templates/brain/server/plugins/agent-chat.ts +4 -0
  136. package/corpus/templates/brain/server/plugins/brain-jobs.ts +1 -0
  137. package/corpus/templates/brain/server/routes/api/brain/queue/run.post.ts +55 -0
  138. package/corpus/templates/calendar/.agents/skills/agent-native-toolkit/SKILL.md +4 -1
  139. package/corpus/templates/calendar/.agents/skills/real-time-sync/SKILL.md +1 -1
  140. package/corpus/templates/chat/.agents/skills/agent-native-toolkit/SKILL.md +4 -1
  141. package/corpus/templates/chat/.agents/skills/real-time-sync/SKILL.md +1 -1
  142. package/corpus/templates/chat/app/components/layout/Layout.tsx +1 -0
  143. package/corpus/templates/chat/app/components/layout/Sidebar.tsx +43 -67
  144. package/corpus/templates/chat/app/hooks/use-navigation-state.ts +2 -7
  145. package/corpus/templates/chat/app/routes/_index.tsx +8 -5
  146. package/corpus/templates/chat/changelog/2026-07-22-compact-sidebar-footer.md +6 -0
  147. package/corpus/templates/chat/changelog/2026-07-22-full-page-chat-keeps-the-active-conversation-when-moving-to-.md +6 -0
  148. package/corpus/templates/chat/changelog/2026-07-22-recent-chats-are-easier-to-scan-and-expand.md +6 -0
  149. package/corpus/templates/clips/.agents/skills/agent-native-toolkit/SKILL.md +4 -1
  150. package/corpus/templates/clips/AGENTS.md +9 -1
  151. package/corpus/templates/clips/actions/export-to-brain.ts +82 -6
  152. package/corpus/templates/clips/actions/request-transcript.ts +25 -14
  153. package/corpus/templates/clips/app/components/sharing/share-ui.tsx +12 -0
  154. package/corpus/templates/clips/app/i18n/en-US.ts +1 -0
  155. package/corpus/templates/clips/changelog/2026-07-22-clips-transcripts-now-retry-brain-export.md +5 -0
  156. package/corpus/templates/clips/changelog/2026-07-22-german-umlauts-and-other-non-ascii-characters-now-paste-corr.md +6 -0
  157. package/corpus/templates/clips/desktop/src-tauri/src/clips/mod.rs +26 -3
  158. package/corpus/templates/clips/jobs/emit-netlify-brain-export-cron.ts +118 -0
  159. package/corpus/templates/clips/netlify.toml +4 -1
  160. package/corpus/templates/clips/scripts/configure-prod-brain-export.ts +92 -0
  161. package/corpus/templates/clips/server/jobs/brain-export.ts +182 -0
  162. package/corpus/templates/clips/server/lib/brain-export-state.ts +42 -0
  163. package/corpus/templates/clips/server/lib/post-finalize-dispatch.ts +5 -1
  164. package/corpus/templates/clips/server/plugins/calendar-jobs.ts +2 -0
  165. package/corpus/templates/clips/server/register-secrets.ts +42 -0
  166. package/corpus/templates/clips/server/routes/api/_agent-native-background/post-finalize-worker.post.ts +10 -1
  167. package/corpus/templates/clips/server/routes/api/clips/brain-export/run.post.ts +45 -0
  168. package/corpus/templates/content/.agents/skills/agent-native-toolkit/SKILL.md +4 -1
  169. package/corpus/templates/content/.agents/skills/real-time-sync/SKILL.md +1 -1
  170. package/corpus/templates/content/app/components/editor/DocumentProperties.tsx +566 -299
  171. package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +14 -0
  172. package/corpus/templates/content/app/components/sidebar/DocumentSidebar.tsx +1 -1
  173. package/corpus/templates/content/app/i18n-data.ts +90 -0
  174. package/corpus/templates/content/server/plugins/collab.ts +1 -1
  175. package/corpus/templates/crm/.agents/skills/agent-native-toolkit/SKILL.md +4 -1
  176. package/corpus/templates/crm/actions/_crm-data-program-actions.ts +15 -0
  177. package/corpus/templates/crm/actions/get-crm-dashboard-panel.ts +2 -0
  178. package/corpus/templates/crm/actions/run.ts +2 -6
  179. package/corpus/templates/crm/actions/update-crm-record.ts +4 -2
  180. package/corpus/templates/crm/app/components/layout/CrmLayout.tsx +6 -0
  181. package/corpus/templates/crm/changelog/2026-07-22-crm-field-validation-now-explains-which-fields-cannot-be-edi.md +6 -0
  182. package/corpus/templates/crm/changelog/2026-07-22-fixed-hubspot-contact-syncs-missing-records-updated-after-th.md +6 -0
  183. package/corpus/templates/crm/changelog/2026-07-22-fixed-pipeline-dashboard-panels-so-their-opportunity-data-lo.md +6 -0
  184. package/corpus/templates/crm/changelog/2026-07-22-full-page-chat-keeps-the-active-conversation-when-moving-to-.md +6 -0
  185. package/corpus/templates/crm/server/crm/hubspot-adapter.ts +4 -1
  186. package/corpus/templates/design/.agents/skills/agent-native-toolkit/SKILL.md +4 -1
  187. package/corpus/templates/design/app/components/layout/Sidebar.tsx +1 -1
  188. package/corpus/templates/design/server/plugins/collab.ts +11 -8
  189. package/corpus/templates/dispatch/.agents/skills/agent-native-toolkit/SKILL.md +4 -1
  190. package/corpus/templates/forms/.agents/skills/agent-native-toolkit/SKILL.md +4 -1
  191. package/corpus/templates/forms/.agents/skills/real-time-sync/SKILL.md +1 -1
  192. package/corpus/templates/forms/app/components/layout/Layout.tsx +1 -0
  193. package/corpus/templates/forms/app/components/layout/Sidebar.tsx +1 -1
  194. package/corpus/templates/forms/app/hooks/use-navigation-state.ts +2 -9
  195. package/corpus/templates/forms/app/root.tsx +1 -6
  196. package/corpus/templates/forms/changelog/2026-07-22-full-page-chat-keeps-the-active-conversation-when-moving-to-.md +6 -0
  197. package/corpus/templates/macros/.agents/skills/agent-native-toolkit/SKILL.md +4 -1
  198. package/corpus/templates/mail/.agents/skills/agent-native-toolkit/SKILL.md +4 -1
  199. package/corpus/templates/mail/.agents/skills/real-time-sync/SKILL.md +1 -1
  200. package/corpus/templates/mail/app/components/layout/AppLayout.tsx +2 -2
  201. package/corpus/templates/plan/.agents/skills/agent-native-toolkit/SKILL.md +4 -1
  202. package/corpus/templates/plan/.agents/skills/real-time-sync/SKILL.md +1 -1
  203. package/corpus/templates/plan/app/components/layout/Layout.tsx +5 -1
  204. package/corpus/templates/plan/app/components/layout/Sidebar.tsx +57 -72
  205. package/corpus/templates/plan/app/hooks/use-navigation-state.ts +2 -9
  206. package/corpus/templates/plan/app/lib/create-plan-routing.ts +2 -2
  207. package/corpus/templates/plan/app/pages/PlanChatPage.tsx +0 -1
  208. package/corpus/templates/plan/changelog/2026-07-22-compact-sidebar-footer.md +6 -0
  209. package/corpus/templates/plan/changelog/2026-07-22-full-page-chat-keeps-the-active-conversation-when-moving-to-.md +6 -0
  210. package/corpus/templates/plan/changelog/2026-07-22-recent-chats-and-sidebar-controls-are-more-compact.md +6 -0
  211. package/corpus/templates/plan/server/plugins/collab.ts +5 -2
  212. package/corpus/templates/slides/.agents/skills/agent-native-toolkit/SKILL.md +4 -1
  213. package/corpus/templates/slides/app/components/layout/Sidebar.tsx +1 -1
  214. package/corpus/templates/slides/server/plugins/collab.ts +11 -8
  215. package/corpus/templates/tasks/.agents/skills/agent-native-toolkit/SKILL.md +4 -1
  216. package/corpus/templates/tasks/.agents/skills/real-time-sync/SKILL.md +1 -1
  217. package/corpus/templates/tasks/app/components/layout/Sidebar.tsx +5 -22
  218. package/corpus/templates/tasks/changelog/2026-07-22-compact-sidebar-footer.md +6 -0
  219. package/corpus/toolkit/CHANGELOG.md +7 -0
  220. package/corpus/toolkit/README.md +12 -3
  221. package/corpus/toolkit/package.json +1 -1
  222. package/corpus/toolkit/src/chat-history/ChatHistoryRail.tsx +89 -0
  223. package/corpus/toolkit/src/chat-history/index.ts +5 -0
  224. package/corpus/toolkit/src/chat-history.css +59 -0
  225. package/corpus/toolkit/src/composer/RealtimeVoiceMode.tsx +106 -36
  226. package/corpus/toolkit/src/composer/VoiceButton.tsx +49 -0
  227. package/corpus/toolkit/src/composer/realtime-voice-audio-level.ts +2 -1
  228. package/corpus/toolkit/src/composer/useRealtimeVoiceMode.tsx +5 -3
  229. package/corpus/toolkit/src/styles.css +35 -0
  230. package/dist/agent/production-agent.d.ts.map +1 -1
  231. package/dist/agent/production-agent.js +13 -3
  232. package/dist/agent/production-agent.js.map +1 -1
  233. package/dist/cli/doctor.d.ts +1 -1
  234. package/dist/cli/doctor.d.ts.map +1 -1
  235. package/dist/cli/doctor.js +5 -2
  236. package/dist/cli/doctor.js.map +1 -1
  237. package/dist/client/AgentPanel.d.ts +1 -0
  238. package/dist/client/AgentPanel.d.ts.map +1 -1
  239. package/dist/client/AgentPanel.js +38 -18
  240. package/dist/client/AgentPanel.js.map +1 -1
  241. package/dist/client/AssistantChat.d.ts.map +1 -1
  242. package/dist/client/AssistantChat.js +55 -20
  243. package/dist/client/AssistantChat.js.map +1 -1
  244. package/dist/client/FeedbackButton.d.ts +4 -2
  245. package/dist/client/FeedbackButton.d.ts.map +1 -1
  246. package/dist/client/FeedbackButton.js +18 -2
  247. package/dist/client/FeedbackButton.js.map +1 -1
  248. package/dist/client/MultiTabAssistantChat.d.ts +1 -1
  249. package/dist/client/MultiTabAssistantChat.d.ts.map +1 -1
  250. package/dist/client/MultiTabAssistantChat.js +2 -3
  251. package/dist/client/MultiTabAssistantChat.js.map +1 -1
  252. package/dist/client/agent-chat-adapter.d.ts.map +1 -1
  253. package/dist/client/agent-chat-adapter.js +11 -3
  254. package/dist/client/agent-chat-adapter.js.map +1 -1
  255. package/dist/client/agent-page/AgentTabFrame.d.ts +3 -1
  256. package/dist/client/agent-page/AgentTabFrame.d.ts.map +1 -1
  257. package/dist/client/agent-page/AgentTabFrame.js +3 -2
  258. package/dist/client/agent-page/AgentTabFrame.js.map +1 -1
  259. package/dist/client/agent-page/AgentTabsPage.d.ts +2 -0
  260. package/dist/client/agent-page/AgentTabsPage.d.ts.map +1 -1
  261. package/dist/client/agent-page/AgentTabsPage.js +26 -15
  262. package/dist/client/agent-page/AgentTabsPage.js.map +1 -1
  263. package/dist/client/analytics.d.ts +10 -0
  264. package/dist/client/analytics.d.ts.map +1 -1
  265. package/dist/client/analytics.js.map +1 -1
  266. package/dist/client/chat/message-components.d.ts +6 -1
  267. package/dist/client/chat/message-components.d.ts.map +1 -1
  268. package/dist/client/chat/message-components.js +17 -11
  269. package/dist/client/chat/message-components.js.map +1 -1
  270. package/dist/client/chat/tool-call-display.d.ts +11 -1
  271. package/dist/client/chat/tool-call-display.d.ts.map +1 -1
  272. package/dist/client/chat/tool-call-display.js +16 -17
  273. package/dist/client/chat/tool-call-display.js.map +1 -1
  274. package/dist/client/chat/tool-render-registry.d.ts +2 -0
  275. package/dist/client/chat/tool-render-registry.d.ts.map +1 -1
  276. package/dist/client/chat/tool-render-registry.js.map +1 -1
  277. package/dist/client/resources/McpConnectionSuggestion.d.ts.map +1 -1
  278. package/dist/client/resources/McpConnectionSuggestion.js +15 -1
  279. package/dist/client/resources/McpConnectionSuggestion.js.map +1 -1
  280. package/dist/client/resources/ResourceTree.d.ts.map +1 -1
  281. package/dist/client/resources/ResourceTree.js +3 -3
  282. package/dist/client/resources/ResourceTree.js.map +1 -1
  283. package/dist/client/resources/ResourcesPanel.d.ts +1 -0
  284. package/dist/client/resources/ResourcesPanel.d.ts.map +1 -1
  285. package/dist/client/resources/ResourcesPanel.js +34 -20
  286. package/dist/client/resources/ResourcesPanel.js.map +1 -1
  287. package/dist/client/resources/mcp-connection-resume.d.ts +11 -0
  288. package/dist/client/resources/mcp-connection-resume.d.ts.map +1 -0
  289. package/dist/client/resources/mcp-connection-resume.js +95 -0
  290. package/dist/client/resources/mcp-connection-resume.js.map +1 -0
  291. package/dist/client/resources/mcp-integration-catalog.d.ts +1 -0
  292. package/dist/client/resources/mcp-integration-catalog.d.ts.map +1 -1
  293. package/dist/client/resources/mcp-integration-catalog.js +63 -8
  294. package/dist/client/resources/mcp-integration-catalog.js.map +1 -1
  295. package/dist/client/resources/mcp-integration-logos.d.ts.map +1 -1
  296. package/dist/client/resources/mcp-integration-logos.js +4 -0
  297. package/dist/client/resources/mcp-integration-logos.js.map +1 -1
  298. package/dist/client/sse-event-processor.d.ts +5 -0
  299. package/dist/client/sse-event-processor.d.ts.map +1 -1
  300. package/dist/client/sse-event-processor.js +39 -13
  301. package/dist/client/sse-event-processor.js.map +1 -1
  302. package/dist/client/use-db-sync.d.ts +4 -3
  303. package/dist/client/use-db-sync.d.ts.map +1 -1
  304. package/dist/client/use-db-sync.js +332 -21
  305. package/dist/client/use-db-sync.js.map +1 -1
  306. package/dist/collab/client.d.ts.map +1 -1
  307. package/dist/collab/client.js +26 -2
  308. package/dist/collab/client.js.map +1 -1
  309. package/dist/collab/routes.d.ts +1 -1
  310. package/dist/connections/catalog.d.ts +1 -1
  311. package/dist/connections/catalog.d.ts.map +1 -1
  312. package/dist/connections/catalog.js +7 -1
  313. package/dist/connections/catalog.js.map +1 -1
  314. package/dist/db/ddl-guard.d.ts +7 -2
  315. package/dist/db/ddl-guard.d.ts.map +1 -1
  316. package/dist/db/ddl-guard.js +21 -9
  317. package/dist/db/ddl-guard.js.map +1 -1
  318. package/dist/deploy/build.d.ts.map +1 -1
  319. package/dist/deploy/build.js +25 -2
  320. package/dist/deploy/build.js.map +1 -1
  321. package/dist/eject/remote-mcp-presets.d.ts.map +1 -1
  322. package/dist/eject/remote-mcp-presets.js +33 -3
  323. package/dist/eject/remote-mcp-presets.js.map +1 -1
  324. package/dist/guards/explicit-collab-access.d.ts +8 -0
  325. package/dist/guards/explicit-collab-access.d.ts.map +1 -0
  326. package/dist/guards/explicit-collab-access.js +214 -0
  327. package/dist/guards/explicit-collab-access.js.map +1 -0
  328. package/dist/guards/index.d.ts +1 -0
  329. package/dist/guards/index.d.ts.map +1 -1
  330. package/dist/guards/index.js +1 -0
  331. package/dist/guards/index.js.map +1 -1
  332. package/dist/localization/default-messages.d.ts +43 -0
  333. package/dist/localization/default-messages.d.ts.map +1 -1
  334. package/dist/localization/default-messages.js +48 -5
  335. package/dist/localization/default-messages.js.map +1 -1
  336. package/dist/mcp/connect-route.d.ts.map +1 -1
  337. package/dist/mcp/connect-route.js +34 -48
  338. package/dist/mcp/connect-route.js.map +1 -1
  339. package/dist/observability/routes.d.ts +5 -5
  340. package/dist/provider-api/actions/custom-provider-registration.d.ts +7 -7
  341. package/dist/provider-api/actions/provider-api.d.ts +7 -7
  342. package/dist/provider-api/corpus-jobs.d.ts +2 -2
  343. package/dist/provider-api/index.js +1 -1
  344. package/dist/provider-api/index.js.map +1 -1
  345. package/dist/realtime-protocol.d.ts +40 -0
  346. package/dist/realtime-protocol.d.ts.map +1 -0
  347. package/dist/realtime-protocol.js +69 -0
  348. package/dist/realtime-protocol.js.map +1 -0
  349. package/dist/resources/handlers.d.ts +1 -1
  350. package/dist/secrets/routes.d.ts +3 -3
  351. package/dist/server/collab-plugin.d.ts +34 -3
  352. package/dist/server/collab-plugin.d.ts.map +1 -1
  353. package/dist/server/collab-plugin.js +65 -12
  354. package/dist/server/collab-plugin.js.map +1 -1
  355. package/dist/server/core-routes-plugin.d.ts.map +1 -1
  356. package/dist/server/core-routes-plugin.js +3 -0
  357. package/dist/server/core-routes-plugin.js.map +1 -1
  358. package/dist/server/index.d.ts +1 -1
  359. package/dist/server/index.d.ts.map +1 -1
  360. package/dist/server/index.js.map +1 -1
  361. package/dist/server/poll-events.d.ts +7 -1
  362. package/dist/server/poll-events.d.ts.map +1 -1
  363. package/dist/server/poll-events.js +24 -7
  364. package/dist/server/poll-events.js.map +1 -1
  365. package/dist/server/poll.d.ts +223 -42
  366. package/dist/server/poll.d.ts.map +1 -1
  367. package/dist/server/poll.js +849 -737
  368. package/dist/server/poll.js.map +1 -1
  369. package/dist/server/realtime-token.d.ts +34 -0
  370. package/dist/server/realtime-token.d.ts.map +1 -0
  371. package/dist/server/realtime-token.js +85 -0
  372. package/dist/server/realtime-token.js.map +1 -0
  373. package/dist/server/sentry-config.d.ts +12 -0
  374. package/dist/server/sentry-config.d.ts.map +1 -1
  375. package/dist/server/sentry-config.js +34 -0
  376. package/dist/server/sentry-config.js.map +1 -1
  377. package/dist/server/short-lived-token.d.ts +46 -0
  378. package/dist/server/short-lived-token.d.ts.map +1 -1
  379. package/dist/server/short-lived-token.js +104 -0
  380. package/dist/server/short-lived-token.js.map +1 -1
  381. package/dist/server/sse.d.ts +1 -1
  382. package/dist/server/sse.d.ts.map +1 -1
  383. package/dist/server/sse.js +4 -0
  384. package/dist/server/sse.js.map +1 -1
  385. package/dist/server/ssr-handler.d.ts.map +1 -1
  386. package/dist/server/ssr-handler.js +5 -3
  387. package/dist/server/ssr-handler.js.map +1 -1
  388. package/dist/server/transcribe-voice.d.ts +1 -1
  389. package/dist/styles/agent-native.css +40 -0
  390. package/dist/templates/chat/.agents/skills/agent-native-toolkit/SKILL.md +4 -1
  391. package/dist/templates/chat/.agents/skills/real-time-sync/SKILL.md +1 -1
  392. package/dist/templates/chat/app/components/layout/Layout.tsx +1 -0
  393. package/dist/templates/chat/app/components/layout/Sidebar.tsx +43 -67
  394. package/dist/templates/chat/app/hooks/use-navigation-state.ts +2 -7
  395. package/dist/templates/chat/app/routes/_index.tsx +8 -5
  396. package/dist/templates/chat/changelog/2026-07-22-compact-sidebar-footer.md +6 -0
  397. package/dist/templates/chat/changelog/2026-07-22-full-page-chat-keeps-the-active-conversation-when-moving-to-.md +6 -0
  398. package/dist/templates/chat/changelog/2026-07-22-recent-chats-are-easier-to-scan-and-expand.md +6 -0
  399. package/dist/templates/default/.agents/skills/agent-native-toolkit/SKILL.md +4 -1
  400. package/dist/templates/default/.agents/skills/real-time-collab/SKILL.md +35 -13
  401. package/dist/templates/default/.agents/skills/real-time-sync/SKILL.md +1 -1
  402. package/dist/templates/headless/.agents/skills/agent-native-toolkit/SKILL.md +4 -1
  403. package/dist/templates/workspace-core/.agents/skills/agent-native-toolkit/SKILL.md +4 -1
  404. package/dist/templates/workspace-core/.agents/skills/real-time-collab/SKILL.md +35 -13
  405. package/dist/templates/workspace-core/.agents/skills/real-time-sync/SKILL.md +1 -1
  406. package/dist/templates/workspace-core/.agents/skills/voice-transcription/SKILL.md +12 -8
  407. package/docs/AGENTS.md +1 -1
  408. package/docs/content/agent-mentions.mdx +2 -2
  409. package/docs/content/{workspace.mdx → agent-resources.mdx} +1 -7
  410. package/docs/content/agent-teams.mdx +4 -4
  411. package/docs/content/dispatch.mdx +10 -10
  412. package/docs/content/drop-in-agent.mdx +4 -4
  413. package/docs/content/external-agents.mdx +1 -1
  414. package/docs/content/frames.mdx +1 -1
  415. package/docs/content/integrations.mdx +13 -2
  416. package/docs/content/key-concepts.mdx +2 -2
  417. package/docs/content/mcp-clients.mdx +7 -1
  418. package/docs/content/multi-app-workspace.mdx +3 -3
  419. package/docs/content/real-time-collaboration.mdx +33 -15
  420. package/docs/content/recurring-jobs.mdx +7 -7
  421. package/docs/content/skills-guide.mdx +4 -4
  422. package/docs/content/template-brain.mdx +1 -1
  423. package/docs/content/toolkit-collaboration.mdx +1 -1
  424. package/docs/content/using-your-agent.mdx +1 -1
  425. package/docs/content/what-is-agent-native.mdx +4 -4
  426. package/docs/content/workspace-management.mdx +4 -4
  427. package/package.json +6 -3
  428. package/src/agent/production-agent.ts +13 -3
  429. package/src/cli/doctor.ts +6 -1
  430. package/src/client/AgentPanel.tsx +79 -41
  431. package/src/client/AssistantChat.tsx +71 -25
  432. package/src/client/FeedbackButton.tsx +33 -7
  433. package/src/client/MultiTabAssistantChat.tsx +6 -8
  434. package/src/client/agent-chat-adapter.ts +11 -2
  435. package/src/client/agent-page/AgentTabFrame.tsx +22 -3
  436. package/src/client/agent-page/AgentTabsPage.tsx +34 -19
  437. package/src/client/analytics.ts +7 -0
  438. package/src/client/chat/message-components.tsx +43 -16
  439. package/src/client/chat/tool-call-display.tsx +30 -18
  440. package/src/client/chat/tool-render-registry.tsx +2 -0
  441. package/src/client/resources/McpConnectionSuggestion.tsx +19 -2
  442. package/src/client/resources/ResourceTree.tsx +7 -10
  443. package/src/client/resources/ResourcesPanel.tsx +33 -70
  444. package/src/client/resources/mcp-connection-resume.ts +107 -0
  445. package/src/client/resources/mcp-integration-catalog.ts +65 -12
  446. package/src/client/resources/mcp-integration-logos.ts +4 -0
  447. package/src/client/sse-event-processor.ts +50 -14
  448. package/src/client/use-db-sync.ts +370 -21
  449. package/src/collab/client.ts +26 -2
  450. package/src/connections/catalog.ts +7 -1
  451. package/src/db/ddl-guard.ts +49 -10
  452. package/src/deploy/build.ts +25 -2
  453. package/src/eject/remote-mcp-presets.ts +37 -5
  454. package/src/guards/explicit-collab-access.ts +229 -0
  455. package/src/guards/index.ts +1 -0
  456. package/src/localization/default-messages.ts +54 -5
  457. package/src/mcp/connect-route.ts +34 -51
  458. package/src/provider-api/index.ts +1 -1
  459. package/src/realtime-protocol.ts +92 -0
  460. package/src/server/collab-plugin.ts +137 -18
  461. package/src/server/core-routes-plugin.ts +7 -0
  462. package/src/server/index.ts +2 -0
  463. package/src/server/poll-events.ts +29 -8
  464. package/src/server/poll.ts +1123 -865
  465. package/src/server/realtime-token.ts +106 -0
  466. package/src/server/sentry-config.ts +40 -0
  467. package/src/server/short-lived-token.ts +156 -0
  468. package/src/server/sse.ts +15 -0
  469. package/src/server/ssr-handler.ts +9 -3
  470. package/src/styles/agent-native.css +40 -0
  471. package/src/templates/chat/.agents/skills/agent-native-toolkit/SKILL.md +4 -1
  472. package/src/templates/chat/.agents/skills/real-time-sync/SKILL.md +1 -1
  473. package/src/templates/chat/app/components/layout/Layout.tsx +1 -0
  474. package/src/templates/chat/app/components/layout/Sidebar.tsx +43 -67
  475. package/src/templates/chat/app/hooks/use-navigation-state.ts +2 -7
  476. package/src/templates/chat/app/routes/_index.tsx +8 -5
  477. package/src/templates/chat/changelog/2026-07-22-compact-sidebar-footer.md +6 -0
  478. package/src/templates/chat/changelog/2026-07-22-full-page-chat-keeps-the-active-conversation-when-moving-to-.md +6 -0
  479. package/src/templates/chat/changelog/2026-07-22-recent-chats-are-easier-to-scan-and-expand.md +6 -0
  480. package/src/templates/default/.agents/skills/agent-native-toolkit/SKILL.md +4 -1
  481. package/src/templates/default/.agents/skills/real-time-collab/SKILL.md +35 -13
  482. package/src/templates/default/.agents/skills/real-time-sync/SKILL.md +1 -1
  483. package/src/templates/headless/.agents/skills/agent-native-toolkit/SKILL.md +4 -1
  484. package/src/templates/workspace-core/.agents/skills/agent-native-toolkit/SKILL.md +4 -1
  485. package/src/templates/workspace-core/.agents/skills/real-time-collab/SKILL.md +35 -13
  486. package/src/templates/workspace-core/.agents/skills/real-time-sync/SKILL.md +1 -1
  487. package/src/templates/workspace-core/.agents/skills/voice-transcription/SKILL.md +12 -8
@@ -1,6 +1,13 @@
1
1
  import { useEffect, useRef, useState } from "react";
2
2
 
3
3
  import { ensureDemoModeFetchInterceptor } from "../demo/fetch-interceptor.js";
4
+ import {
5
+ parseHandshakeFrame,
6
+ parseTokenFrame,
7
+ REALTIME_PROTOCOL_VERSION,
8
+ REALTIME_SSE_HANDSHAKE_EVENT,
9
+ REALTIME_SSE_TOKEN_EVENT,
10
+ } from "../realtime-protocol.js";
4
11
  import { agentNativePath } from "./api-path.js";
5
12
  import { getBrowserTabId } from "./browser-tab-id.js";
6
13
  import {
@@ -33,6 +40,7 @@ const POLL_ABORT_MIN_MS = 10_000;
33
40
  // four times per minute forever. Focus and active agent work still poll now.
34
41
  const SSE_FALLBACK_INTERVAL_MS = 60_000;
35
42
  const IDLE_POLL_INTERVAL_MS = 60_000;
43
+ const HIDDEN_POLL_INTERVAL_MS = 10_000;
36
44
  const POLL_AUTH_FAILURE_COOLDOWN_MS = 60_000;
37
45
  /**
38
46
  * Max cadence for SSE/poll-driven query invalidation in `useDbSync`. Events
@@ -87,6 +95,64 @@ function resolveSseUrl(sseUrl: string | false | undefined): string | false {
87
95
  return agentNativePath(sseUrl ?? "/_agent-native/events");
88
96
  }
89
97
 
98
+ // --- Hosted Realtime Gateway binding ----------------------------------------
99
+ //
100
+ // When the app is configured for the hosted gateway, the transport connects to
101
+ // the gateway (cross-origin) instead of the Netlify app, carrying a short-lived
102
+ // subscribe token minted from the app's own same-origin endpoint. All of this
103
+ // is gated on a non-null binding — apps without hosted config keep the exact
104
+ // local behavior below.
105
+
106
+ const REALTIME_GATEWAY_SSE_PATH = "/stream";
107
+ const REALTIME_GATEWAY_POLL_PATH = "/poll";
108
+ const REALTIME_TOKEN_MINT_PATH = "/_agent-native/realtime-token";
109
+ /** Consecutive gateway failures before health-gating back to the local app. */
110
+ const HOSTED_UNHEALTHY_THRESHOLD = 3;
111
+
112
+ interface RealtimeGatewayBinding {
113
+ /** Gateway SSE URL (token appended per connect). */
114
+ sseUrl: string;
115
+ /** Gateway poll URL (token appended per request). */
116
+ pollUrl: string;
117
+ /** Same-origin app endpoint that mints the subscribe token. */
118
+ tokenMintUrl: string;
119
+ }
120
+
121
+ function getRealtimeConfig():
122
+ | { transport?: string; gatewayBaseUrl?: string }
123
+ | undefined {
124
+ if (typeof window === "undefined") return undefined;
125
+ return window.__AGENT_NATIVE_CONFIG__?.realtime;
126
+ }
127
+
128
+ /**
129
+ * Resolve the hosted-gateway binding, or null to stay on the local app. Gated
130
+ * on: SSE enabled (the gateway is push-first), not embed auth (needs the
131
+ * same-origin session to mint), and `transport: "hosted"` with a base URL in
132
+ * the impersonal SSR config.
133
+ */
134
+ function resolveGatewayBinding(
135
+ localSseUrl: string | false,
136
+ ): RealtimeGatewayBinding | null {
137
+ if (localSseUrl === false) return null;
138
+ if (isEmbedAuthActive()) return null;
139
+ const config = getRealtimeConfig();
140
+ if (config?.transport !== "hosted") return null;
141
+ const base = config.gatewayBaseUrl?.replace(/\/+$/, "");
142
+ if (!base) return null;
143
+ return {
144
+ sseUrl: `${base}${REALTIME_GATEWAY_SSE_PATH}`,
145
+ pollUrl: `${base}${REALTIME_GATEWAY_POLL_PATH}`,
146
+ tokenMintUrl: agentNativePath(REALTIME_TOKEN_MINT_PATH),
147
+ };
148
+ }
149
+
150
+ /** ±20% jitter so gateway timeout/deploy-driven reconnects don't stampede. */
151
+ function applyReconnectJitter(delay: number): number {
152
+ const jitter = delay * 0.2 * (Math.random() * 2 - 1);
153
+ return Math.max(0, Math.round(delay + jitter));
154
+ }
155
+
90
156
  function normalizeEventPayload(payload: unknown): SyncEvent[] {
91
157
  if (!payload || typeof payload !== "object") return [];
92
158
  const record = payload as { type?: unknown; events?: unknown };
@@ -157,6 +223,7 @@ async function fetchPollJson<T>(
157
223
  pollUrl: string,
158
224
  since: number,
159
225
  interval: number,
226
+ token?: string,
160
227
  ): Promise<T> {
161
228
  const controller =
162
229
  typeof AbortController === "undefined" ? null : new AbortController();
@@ -164,9 +231,16 @@ async function fetchPollJson<T>(
164
231
  ? setTimeout(() => controller.abort(), getPollAbortMs(interval))
165
232
  : null;
166
233
 
234
+ // Local path stays exactly `?since=N`; the hosted gateway also carries the
235
+ // subscribe token on the query string (a cross-origin fetch can't set the
236
+ // Authorization header for the SSE sibling either, so both use the query).
237
+ const url = token
238
+ ? `${pollUrl}${pollUrl.includes("?") ? "&" : "?"}since=${since}&token=${encodeURIComponent(token)}`
239
+ : `${pollUrl}?since=${since}`;
240
+
167
241
  try {
168
242
  const res = await fetch(
169
- `${pollUrl}?since=${since}`,
243
+ url,
170
244
  controller ? { signal: controller.signal } : undefined,
171
245
  );
172
246
  if (!res.ok) throw new HttpStatusError(res.status);
@@ -214,7 +288,10 @@ interface TransportSubscription {
214
288
  * subscribers with their own fallback loops (e.g. the collab doc poll)
215
289
  * relax their cadence while the push path is healthy.
216
290
  */
217
- onSseStateChange?: (connected: boolean) => void;
291
+ onSseStateChange?: (
292
+ connected: boolean,
293
+ capabilities?: readonly string[],
294
+ ) => void;
218
295
  }
219
296
 
220
297
  class SyncTransport {
@@ -228,11 +305,148 @@ class SyncTransport {
228
305
  private authFailureUntil = 0;
229
306
  private consecutiveFailures = 0;
230
307
  private activeChatIds = new Set<string>();
308
+ // Hosted-gateway state. `mode` starts "hosted" when a binding is present and
309
+ // flips to "local" on health-gate revert; `token` is the current subscribe
310
+ // token (minted from the app, rotated over the stream), never part of any
311
+ // registry key.
312
+ private mode: "hosted" | "local";
313
+ private token: string | null = null;
314
+ private tokenMintInFlight: Promise<boolean> | null = null;
315
+ private gatewayReconnectTimer: ReturnType<typeof setTimeout> | null = null;
316
+ private capabilities: string[] = [];
231
317
 
232
318
  constructor(
233
319
  private readonly pollUrl: string,
234
320
  private readonly sseUrl: string | false,
235
- ) {}
321
+ private readonly gateway: RealtimeGatewayBinding | null = null,
322
+ ) {
323
+ this.mode = gateway ? "hosted" : "local";
324
+ }
325
+
326
+ /** Capabilities advertised by the gateway handshake (e.g. `no-awareness`). */
327
+ getCapabilities(): readonly string[] {
328
+ return this.capabilities;
329
+ }
330
+
331
+ private get activeSseUrl(): string | false {
332
+ if (this.mode === "hosted" && this.gateway) {
333
+ return this.token
334
+ ? `${this.gateway.sseUrl}?token=${encodeURIComponent(this.token)}`
335
+ : this.gateway.sseUrl;
336
+ }
337
+ return this.sseUrl;
338
+ }
339
+
340
+ private get activePollUrl(): string {
341
+ return this.mode === "hosted" && this.gateway
342
+ ? this.gateway.pollUrl
343
+ : this.pollUrl;
344
+ }
345
+
346
+ /**
347
+ * Mint a subscribe token from the app's same-origin endpoint.
348
+ *
349
+ * Only TERMINAL outcomes health-gate to local: 404 (gateway not provisioned)
350
+ * and 401/403 (not authorized) — retrying those for this tab is pointless.
351
+ * TRANSIENT failures (5xx/429 from a cold Netlify function, network errors)
352
+ * keep the hosted intent and ride the jittered reconnect + unhealthy-threshold
353
+ * path, so a deploy / scale-to-zero blip doesn't permanently abandon the
354
+ * gateway for the tab.
355
+ */
356
+ private mintToken(): Promise<boolean> {
357
+ if (!this.gateway || this.mode !== "hosted") return Promise.resolve(false);
358
+ if (this.tokenMintInFlight) return this.tokenMintInFlight;
359
+ const mintUrl = this.gateway.tokenMintUrl;
360
+ this.tokenMintInFlight = (async () => {
361
+ // Bound the mint like fetchPollJson bounds polls: a black-holed request
362
+ // must resolve as a transient failure, not hang tokenMintInFlight forever
363
+ // (poll() awaits this while holding inFlight, so a hung mint would stall
364
+ // the whole transport with no timer pending).
365
+ const controller =
366
+ typeof AbortController === "undefined" ? null : new AbortController();
367
+ const timeout = controller
368
+ ? setTimeout(() => controller.abort(), POLL_ABORT_MIN_MS)
369
+ : null;
370
+ try {
371
+ const res = await fetch(mintUrl, {
372
+ credentials: "same-origin",
373
+ ...(controller ? { signal: controller.signal } : {}),
374
+ });
375
+ if (res.ok) {
376
+ const data = (await res.json()) as { token?: unknown };
377
+ if (typeof data?.token === "string" && data.token) {
378
+ this.token = data.token;
379
+ // Deliberately NOT resetting consecutiveFailures here: minting
380
+ // succeeds via the app origin even when the GATEWAY is down, so a
381
+ // reset would let a mint-ok -> stream-fail loop run forever below
382
+ // the unhealthy threshold. Only real gateway connectivity (stream
383
+ // onopen / poll success) clears the count.
384
+ return true;
385
+ }
386
+ // 2xx without a token is a terminal misconfiguration.
387
+ this.revertToLocal();
388
+ return false;
389
+ }
390
+ if (res.status === 404 || res.status === 401 || res.status === 403) {
391
+ this.revertToLocal();
392
+ return false;
393
+ }
394
+ this.onGatewayTransientFailure();
395
+ return false;
396
+ } catch {
397
+ this.onGatewayTransientFailure();
398
+ return false;
399
+ } finally {
400
+ if (timeout) clearTimeout(timeout);
401
+ this.tokenMintInFlight = null;
402
+ }
403
+ })();
404
+ return this.tokenMintInFlight;
405
+ }
406
+
407
+ /**
408
+ * A transient gateway failure (mint 5xx/429, network). Keep hosted intent but
409
+ * count toward the unhealthy threshold; revert to local only once it trips.
410
+ */
411
+ private onGatewayTransientFailure(): void {
412
+ this.consecutiveFailures++;
413
+ if (this.consecutiveFailures >= HOSTED_UNHEALTHY_THRESHOLD) {
414
+ this.revertToLocal();
415
+ }
416
+ }
417
+
418
+ /**
419
+ * Health-gate back to the app's own /poll + /events with the cursor intact
420
+ * (versionRef is untouched), so delivery stays poll-equivalent — never a
421
+ * silent stall.
422
+ */
423
+ private revertToLocal(): void {
424
+ if (this.mode === "local") return;
425
+ this.mode = "local";
426
+ this.token = null;
427
+ // The local in-process SSE path sends no handshake, so hosted capabilities
428
+ // (e.g. no-awareness) must not survive the fallback — stale caps would keep
429
+ // collab on its fast presence cadence against the local stream. Subscribers
430
+ // are re-notified via the close/connect cycle below.
431
+ this.capabilities = [];
432
+ if (this.gatewayReconnectTimer) {
433
+ clearTimeout(this.gatewayReconnectTimer);
434
+ this.gatewayReconnectTimer = null;
435
+ }
436
+ this.closeEvents();
437
+ if (!this.stopped) {
438
+ this.connectEvents();
439
+ this.schedulePoll();
440
+ }
441
+ }
442
+
443
+ private scheduleGatewayReconnect(): void {
444
+ if (this.stopped || this.gatewayReconnectTimer) return;
445
+ this.gatewayReconnectTimer = setTimeout(() => {
446
+ this.gatewayReconnectTimer = null;
447
+ if (!this.stopped && !this.eventSource) this.connectEvents();
448
+ }, applyReconnectJitter(1000));
449
+ }
236
450
 
237
451
  // -------------------------------------------------------------------------
238
452
  // Subscriber management
@@ -252,7 +466,7 @@ class SyncTransport {
252
466
  } else {
253
467
  this.reschedule();
254
468
  }
255
- sub.onSseStateChange?.(this.sseConnected);
469
+ sub.onSseStateChange?.(this.sseConnected, this.capabilities);
256
470
  }
257
471
 
258
472
  remove(id: symbol): void {
@@ -319,8 +533,18 @@ class SyncTransport {
319
533
  private setSseConnected(connected: boolean): void {
320
534
  if (this.sseConnected === connected) return;
321
535
  this.sseConnected = connected;
536
+ this.notifySseState();
537
+ }
538
+
539
+ /**
540
+ * Notify subscribers of the current SSE state AND the negotiated gateway
541
+ * capabilities. Called on connect/disconnect and again once the handshake
542
+ * arrives, so a consumer (e.g. collab) can decide — for instance — not to
543
+ * relax its presence cadence on a `no-awareness` hosted stream.
544
+ */
545
+ private notifySseState(): void {
322
546
  for (const sub of this.subscribers.values()) {
323
- sub.onSseStateChange?.(connected);
547
+ sub.onSseStateChange?.(this.sseConnected, this.capabilities);
324
548
  }
325
549
  }
326
550
 
@@ -344,19 +568,26 @@ class SyncTransport {
344
568
  }, authDelay);
345
569
  return;
346
570
  }
347
- const base = this.isActive
571
+ const visibleBase = this.isActive
348
572
  ? this.effectiveInterval
349
573
  : this.sseConnected
350
574
  ? this.effectiveFallbackInterval
351
575
  : this.effectiveIdleInterval;
576
+ const base = isDocumentHidden()
577
+ ? Math.max(visibleBase, HIDDEN_POLL_INTERVAL_MS)
578
+ : visibleBase;
352
579
  // Exponential backoff while polls keep failing (500s during a deploy,
353
580
  // DNS blips, a struggling DB). Auth failures have their own cooldown
354
581
  // above; this covers everything else so a down server isn't hammered at
355
582
  // full cadence. Resets on the first successful poll.
356
- const delay =
583
+ const backoff =
357
584
  this.consecutiveFailures > 0
358
585
  ? Math.min(base * 2 ** Math.min(this.consecutiveFailures, 5), 300_000)
359
586
  : base;
587
+ // Jitter only for gateway-capable transports so reconnect/poll retries
588
+ // don't stampede a gateway deploy; apps with no gateway config keep the
589
+ // exact deterministic cadence.
590
+ const delay = this.gateway ? applyReconnectJitter(backoff) : backoff;
360
591
  this.timer = setTimeout(() => {
361
592
  this.timer = null;
362
593
  void this.poll();
@@ -383,7 +614,6 @@ class SyncTransport {
383
614
  private connectEvents(): void {
384
615
  if (
385
616
  this.stopped ||
386
- !this.sseUrl ||
387
617
  this.eventSource ||
388
618
  typeof EventSource === "undefined" ||
389
619
  (this.effectivePauseWhenHidden && isDocumentHidden())
@@ -391,10 +621,38 @@ class SyncTransport {
391
621
  return;
392
622
  }
393
623
 
394
- const source = new EventSource(this.sseUrl);
624
+ // Hosted gateway needs a subscribe token before the stream can open.
625
+ // EventSource can't set headers, so the token rides the connect query
626
+ // string (see activeSseUrl). Mint first, then connect.
627
+ if (this.mode === "hosted" && this.gateway && !this.token) {
628
+ void this.mintToken().then((ok) => {
629
+ if (this.stopped) return;
630
+ if (ok && !this.eventSource) {
631
+ this.connectEvents();
632
+ } else if (!ok && this.mode === "hosted") {
633
+ // Transient mint failure (terminal ones already reverted to local,
634
+ // flipping mode). Without a retry timer nothing would ever reopen
635
+ // SSE — connectEvents is only reachable from focus/visibility/run
636
+ // events — leaving the tab poll-only at the idle cadence.
637
+ this.scheduleGatewayReconnect();
638
+ }
639
+ });
640
+ return;
641
+ }
642
+
643
+ const url = this.activeSseUrl;
644
+ if (!url) return;
645
+
646
+ const source = new EventSource(url);
395
647
  this.eventSource = source;
396
648
  source.onopen = () => {
397
649
  this.setSseConnected(true);
650
+ if (this.mode === "hosted") {
651
+ // A live gateway stream is the real health signal: clear failure
652
+ // counts accumulated by mint/stream retries. Local mode keeps main's
653
+ // semantics (only a successful poll resets the poll backoff).
654
+ this.consecutiveFailures = 0;
655
+ }
398
656
  this.schedulePoll();
399
657
  };
400
658
  source.onerror = () => {
@@ -406,6 +664,19 @@ class SyncTransport {
406
664
  // we'd be stuck on polling-only forever.
407
665
  if (source.readyState === EventSource.CLOSED) {
408
666
  this.eventSource = null;
667
+ if (this.mode === "hosted" && this.gateway) {
668
+ // A closed gateway stream is most likely an expired token or a
669
+ // request-timeout/deploy cycle. Re-mint and reconnect with jitter;
670
+ // this is NOT the poll-401 cooldown path. Each closed stream counts
671
+ // toward the unhealthy threshold so a hard-down gateway (or one
672
+ // rejecting our tokens) health-gates to local instead of looping
673
+ // mint+connect forever; a successful reconnect resets the count in
674
+ // onopen above.
675
+ this.token = null;
676
+ this.onGatewayTransientFailure();
677
+ if (this.mode === "hosted") this.scheduleGatewayReconnect();
678
+ return;
679
+ }
409
680
  }
410
681
  this.schedulePoll();
411
682
  };
@@ -421,6 +692,38 @@ class SyncTransport {
421
692
  // Ignore malformed SSE frames; polling is the safety net.
422
693
  }
423
694
  };
695
+
696
+ if (this.mode === "hosted" && this.gateway) {
697
+ // Control frames ride NAMED SSE events so they never reach onmessage /
698
+ // normalizeEventPayload as spurious data events.
699
+ source.addEventListener(REALTIME_SSE_HANDSHAKE_EVENT, (e) => {
700
+ const hs = parseHandshakeFrame((e as MessageEvent).data);
701
+ if (!hs) return;
702
+ if (hs.protocol !== REALTIME_PROTOCOL_VERSION) {
703
+ // Surface an unexpected protocol rather than silently adopting its
704
+ // capabilities; keep the conservative (no advertised capabilities)
705
+ // stance so downstream (collab) does not relax on assumptions.
706
+ console.warn(
707
+ `[agent-native] unsupported realtime protocol ${hs.protocol} (expected ${REALTIME_PROTOCOL_VERSION})`,
708
+ );
709
+ return;
710
+ }
711
+ this.capabilities = hs.capabilities;
712
+ // Re-notify subscribers now that capabilities are known — the initial
713
+ // connected notification fired before the handshake arrived.
714
+ this.notifySseState();
715
+ });
716
+ source.addEventListener(REALTIME_SSE_TOKEN_EVENT, (e) => {
717
+ const frame = parseTokenFrame((e as MessageEvent).data);
718
+ if (!frame?.token) return;
719
+ this.token = frame.token;
720
+ // EventSource can't change a live stream's URL, and its auto-reconnect
721
+ // reuses the original (old-token) URL. Close and reconnect (jittered) so
722
+ // the rotated token is actually used on the next connect.
723
+ this.closeEvents();
724
+ this.scheduleGatewayReconnect();
725
+ });
726
+ }
424
727
  }
425
728
 
426
729
  /**
@@ -442,10 +745,17 @@ class SyncTransport {
442
745
  if (this.stopped || this.inFlight) return;
443
746
  this.inFlight = true;
444
747
  try {
748
+ if (this.mode === "hosted" && this.gateway && !this.token) {
749
+ // No token yet — mint before polling the gateway. A failed mint has
750
+ // already reverted us to local; a scheduled poll will pick it up.
751
+ const ok = await this.mintToken();
752
+ if (!ok || this.stopped) return;
753
+ }
445
754
  const data = await fetchPollJson<PollResponse>(
446
- this.pollUrl,
755
+ this.activePollUrl,
447
756
  this.versionRef,
448
757
  this.effectiveInterval,
758
+ this.mode === "hosted" ? (this.token ?? undefined) : undefined,
449
759
  );
450
760
  if (this.stopped) return;
451
761
  this.consecutiveFailures = 0;
@@ -455,7 +765,18 @@ class SyncTransport {
455
765
  } catch (err) {
456
766
  if (this.stopped) return;
457
767
  this.consecutiveFailures++;
458
- if (isAuthFailure(err)) {
768
+ if (this.mode === "hosted" && this.gateway) {
769
+ // Gateway auth failure → re-mint (expired/rotated token), WITHOUT
770
+ // tripping the poll-401 cooldown. Persistent failures of any kind
771
+ // health-gate back to the local app.
772
+ if (isAuthFailure(err)) {
773
+ this.token = null;
774
+ void this.mintToken();
775
+ }
776
+ if (this.consecutiveFailures >= HOSTED_UNHEALTHY_THRESHOLD) {
777
+ this.revertToLocal();
778
+ }
779
+ } else if (isAuthFailure(err)) {
459
780
  this.authFailureUntil = Date.now() + POLL_AUTH_FAILURE_COOLDOWN_MS;
460
781
  this.closeEvents();
461
782
  }
@@ -490,6 +811,11 @@ class SyncTransport {
490
811
  clearTimeout(this.timer);
491
812
  this.timer = null;
492
813
  }
814
+ } else {
815
+ // Keep push connected and the polling safety net alive in backgrounded
816
+ // tabs, but relax an active chat's cadence so hidden work stays current
817
+ // without polling as aggressively as the visible surface.
818
+ this.reschedule();
493
819
  }
494
820
  };
495
821
 
@@ -554,6 +880,10 @@ class SyncTransport {
554
880
  clearTimeout(this.timer);
555
881
  this.timer = null;
556
882
  }
883
+ if (this.gatewayReconnectTimer) {
884
+ clearTimeout(this.gatewayReconnectTimer);
885
+ this.gatewayReconnectTimer = null;
886
+ }
557
887
  window.removeEventListener("focus", this.handleFocus);
558
888
  window.removeEventListener(
559
889
  "agentNative.chatRunning",
@@ -576,11 +906,14 @@ const transportRegistry = new Map<string, SyncTransport>();
576
906
  function getOrCreateTransport(
577
907
  pollUrl: string,
578
908
  sseUrl: string | false,
909
+ gateway: RealtimeGatewayBinding | null = null,
579
910
  ): SyncTransport {
911
+ // Key on the LOCAL urls only — a transport may flip hosted→local at runtime,
912
+ // and the token must never fragment the registry, so neither is in the key.
580
913
  const key = `${pollUrl}\0${String(sseUrl)}`;
581
914
  let transport = transportRegistry.get(key);
582
915
  if (!transport) {
583
- transport = new SyncTransport(pollUrl, sseUrl);
916
+ transport = new SyncTransport(pollUrl, sseUrl, gateway);
584
917
  transportRegistry.set(key, transport);
585
918
  }
586
919
  return transport;
@@ -607,7 +940,10 @@ export interface SubscribeSyncEventsOptions {
607
940
  /** Receives every batch of change events (SSE push or poll). */
608
941
  onEvents: (events: SyncEvent[], version: number | undefined) => void;
609
942
  /** Notified when the shared SSE connection opens/closes (and once on join). */
610
- onSseStateChange?: (connected: boolean) => void;
943
+ onSseStateChange?: (
944
+ connected: boolean,
945
+ capabilities?: readonly string[],
946
+ ) => void;
611
947
  pollUrl?: string;
612
948
  sseUrl?: string | false;
613
949
  pauseWhenHidden?: boolean;
@@ -636,12 +972,16 @@ export function subscribeSyncEvents(
636
972
  ): () => void {
637
973
  const pollUrl = agentNativePath(options.pollUrl ?? "/_agent-native/poll");
638
974
  const sseUrl = resolveSseUrl(options.sseUrl);
639
- const transport = getOrCreateTransport(pollUrl, sseUrl);
975
+ const transport = getOrCreateTransport(
976
+ pollUrl,
977
+ sseUrl,
978
+ resolveGatewayBinding(sseUrl),
979
+ );
640
980
  const id = Symbol("subscribeSyncEvents");
641
981
  transport.add(id, {
642
982
  onEvents: options.onEvents,
643
983
  onSseStateChange: options.onSseStateChange,
644
- pauseWhenHidden: options.pauseWhenHidden ?? true,
984
+ pauseWhenHidden: options.pauseWhenHidden ?? false,
645
985
  interval: options.interval ?? 60_000,
646
986
  idleInterval: options.interval ?? 60_000,
647
987
  fallbackInterval: options.fallbackInterval ?? 60_000,
@@ -675,8 +1015,9 @@ export function subscribeSyncEvents(
675
1015
  * @param options.interval - Poll interval in ms. Default: 2000
676
1016
  * @param options.fallbackInterval - Poll interval while SSE is connected.
677
1017
  * Default: 60000
678
- * @param options.pauseWhenHidden - Pause polling while the tab is hidden.
679
- * Default: true
1018
+ * @param options.pauseWhenHidden - Pause sync while the tab is hidden.
1019
+ * Default: false. Hidden tabs keep SSE connected and poll no faster than
1020
+ * every 10 seconds while active; idle polling remains at 60 seconds.
680
1021
  * @param options.ignoreSource - Skip events whose `requestSource` matches this
681
1022
  * value. Use a per-tab ID so the UI ignores its own writes while still
682
1023
  * picking up changes from other tabs, agents, and scripts.
@@ -714,7 +1055,7 @@ export function useDbSync(
714
1055
  options.fallbackInterval ?? SSE_FALLBACK_INTERVAL_MS,
715
1056
  interval,
716
1057
  ),
717
- pauseWhenHidden = true,
1058
+ pauseWhenHidden = false,
718
1059
  } = options;
719
1060
  const idleInterval =
720
1061
  options.interval === undefined ? IDLE_POLL_INTERVAL_MS : interval;
@@ -978,7 +1319,11 @@ export function useDbSync(
978
1319
  );
979
1320
  }
980
1321
 
981
- const transport = getOrCreateTransport(pollUrl, sseUrl);
1322
+ const transport = getOrCreateTransport(
1323
+ pollUrl,
1324
+ sseUrl,
1325
+ resolveGatewayBinding(sseUrl),
1326
+ );
982
1327
  transport.add(id, {
983
1328
  onEvents,
984
1329
  pauseWhenHidden,
@@ -1053,7 +1398,7 @@ export function useScreenRefreshKey(
1053
1398
  options.fallbackInterval ?? SSE_FALLBACK_INTERVAL_MS,
1054
1399
  interval,
1055
1400
  ),
1056
- pauseWhenHidden = true,
1401
+ pauseWhenHidden = false,
1057
1402
  } = options;
1058
1403
  const idleInterval =
1059
1404
  options.interval === undefined ? IDLE_POLL_INTERVAL_MS : interval;
@@ -1084,7 +1429,11 @@ export function useScreenRefreshKey(
1084
1429
  );
1085
1430
  }
1086
1431
 
1087
- const transport = getOrCreateTransport(pollUrl, sseUrl);
1432
+ const transport = getOrCreateTransport(
1433
+ pollUrl,
1434
+ sseUrl,
1435
+ resolveGatewayBinding(sseUrl),
1436
+ );
1088
1437
  transport.add(id, {
1089
1438
  onEvents,
1090
1439
  pauseWhenHidden,
@@ -45,6 +45,7 @@ import * as Y from "yjs";
45
45
 
46
46
  import { agentNativePath } from "../client/api-path.js";
47
47
  import { subscribeSyncEvents, type SyncEvent } from "../client/use-db-sync.js";
48
+ import { REALTIME_CAP_NO_AWARENESS } from "../realtime-protocol.js";
48
49
  export {
49
50
  dedupeCollabUsersByEmail,
50
51
  emailToColor,
@@ -326,6 +327,12 @@ class CollabDocConnection {
326
327
  private pollVersion = 0;
327
328
  private lastPolledVersion = 0;
328
329
  private sseActive = false;
330
+ // Whether the active SSE stream actually forwards awareness. The hosted
331
+ // Realtime Gateway advertises `no-awareness` (it can't see the in-process
332
+ // awareness emitter), so on that transport we must NOT relax the presence
333
+ // poll cadence — otherwise remote cursors go stale. In-process SSE forwards
334
+ // awareness and sends no handshake, so this stays true there.
335
+ private sseAwarenessCovered = false;
329
336
  private sseSubscribedWithPause: boolean | null = null;
330
337
  private unsubscribeCollabEvents: (() => void) | null = null;
331
338
  private unsubscribeAwarenessEvents: (() => void) | null = null;
@@ -760,8 +767,22 @@ class CollabDocConnection {
760
767
  if (this.disposed || !this.syncActive) return;
761
768
  for (const change of events) this.handleSharedEvent(change);
762
769
  },
763
- onSseStateChange: (connected) => {
770
+ onSseStateChange: (connected, capabilities) => {
771
+ const wasActive = this.sseActive;
764
772
  this.sseActive = connected;
773
+ // Only treat SSE as covering awareness when it's connected AND does not
774
+ // advertise `no-awareness` (the hosted gateway does). Drives whether we
775
+ // relax the presence poll — see getActivePollInterval.
776
+ const awarenessCovered =
777
+ connected && !capabilities?.includes(REALTIME_CAP_NO_AWARENESS);
778
+ const coverageFlipped = awarenessCovered !== this.sseAwarenessCovered;
779
+ this.sseAwarenessCovered = awarenessCovered;
780
+ // The gateway handshake lands AFTER onopen, so a relaxed poll timer
781
+ // scheduled at connect can already be pending when `no-awareness`
782
+ // arrives. Reschedule only for that mid-connection capability flip so
783
+ // the fast presence cadence applies immediately; connect/disconnect
784
+ // transitions keep the pre-existing next-natural-tick behavior.
785
+ if (coverageFlipped && connected === wasActive) this.reschedulePoll();
765
786
  if (connected) this.consecutiveErrors = 0;
766
787
  },
767
788
  pauseWhenHidden,
@@ -811,7 +832,10 @@ class CollabDocConnection {
811
832
  }
812
833
 
813
834
  private getActivePollInterval(): number {
814
- return this.sseActive
835
+ // Relax to the slow cadence only when SSE is genuinely carrying awareness;
836
+ // on a `no-awareness` hosted stream keep the fast cadence so presence/
837
+ // cursor state doesn't go stale (the gateway doesn't forward awareness).
838
+ return this.sseActive && this.sseAwarenessCovered
815
839
  ? this.effectivePollIntervalWithSse
816
840
  : this.effectivePollInterval;
817
841
  }
@@ -194,7 +194,13 @@ export const WORKSPACE_CONNECTION_PROVIDERS = [
194
194
  },
195
195
  ],
196
196
  capabilities: ["search", "import", "docs"],
197
- recommendedTemplateUses: ["brain", "content", "slides", "dispatch"],
197
+ recommendedTemplateUses: [
198
+ "brain",
199
+ "content",
200
+ "slides",
201
+ "dispatch",
202
+ "analytics",
203
+ ],
198
204
  oauth: {
199
205
  provider: "google",
200
206
  authorizationUrl: "https://accounts.google.com/o/oauth2/v2/auth",