@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
@@ -0,0 +1,5 @@
1
+ ---
2
+ type: improved
3
+ ---
4
+
5
+ Ready clip transcripts now keep an export receipt and retry Brain delivery after temporary failures.
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: fixed
3
+ date: 2026-07-22
4
+ ---
5
+
6
+ German umlauts and other non-ASCII characters now paste correctly from macOS dictation
@@ -1854,7 +1854,21 @@ mod tests {
1854
1854
 
1855
1855
  #[cfg(target_os = "macos")]
1856
1856
  mod macos_only {
1857
- use super::super::chunk_graphemes_by_utf16_units;
1857
+ use super::super::{chunk_graphemes_by_utf16_units, utf8_pasteboard_command};
1858
+ use std::ffi::OsStr;
1859
+
1860
+ #[test]
1861
+ fn pasteboard_commands_force_utf8_for_gui_launches() {
1862
+ let command = utf8_pasteboard_command("pbcopy");
1863
+ let env_value = |key: &str| {
1864
+ command
1865
+ .get_envs()
1866
+ .find(|(name, _)| *name == OsStr::new(key))
1867
+ .and_then(|(_, value)| value)
1868
+ };
1869
+ assert_eq!(env_value("LANG"), Some(OsStr::new("en_US.UTF-8")));
1870
+ assert_eq!(env_value("LC_ALL"), Some(OsStr::new("en_US.UTF-8")));
1871
+ }
1858
1872
 
1859
1873
  #[test]
1860
1874
  fn never_splits_a_zwj_family_emoji_across_chunks() {
@@ -1892,9 +1906,18 @@ mod tests {
1892
1906
  }
1893
1907
  }
1894
1908
 
1909
+ #[cfg(target_os = "macos")]
1910
+ fn utf8_pasteboard_command(program: &str) -> Command {
1911
+ let mut command = Command::new(program);
1912
+ command
1913
+ .env("LANG", "en_US.UTF-8")
1914
+ .env("LC_ALL", "en_US.UTF-8");
1915
+ command
1916
+ }
1917
+
1895
1918
  #[cfg(target_os = "macos")]
1896
1919
  fn write_clipboard(text: &str) -> Result<(), String> {
1897
- let mut child = Command::new("pbcopy")
1920
+ let mut child = utf8_pasteboard_command("pbcopy")
1898
1921
  .stdin(Stdio::piped())
1899
1922
  .spawn()
1900
1923
  .map_err(|e| format!("pbcopy spawn: {e}"))?;
@@ -1926,7 +1949,7 @@ fn write_clipboard(_text: &str) -> Result<(), String> {
1926
1949
  /// content on the clipboard are not preserved by the later restore.
1927
1950
  #[cfg(target_os = "macos")]
1928
1951
  fn read_clipboard() -> Option<String> {
1929
- let out = Command::new("pbpaste").output().ok()?;
1952
+ let out = utf8_pasteboard_command("pbpaste").output().ok()?;
1930
1953
  if !out.status.success() {
1931
1954
  return None;
1932
1955
  }
@@ -0,0 +1,118 @@
1
+ #!/usr/bin/env tsx
2
+
3
+ import { randomBytes } from "node:crypto";
4
+ import { cpSync, existsSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
5
+ import path from "node:path";
6
+ import { fileURLToPath } from "node:url";
7
+
8
+ const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
9
+ const FUNCTIONS_DIR = path.join(ROOT, ".netlify", "functions-internal");
10
+ const SERVER_DIR = path.join(FUNCTIONS_DIR, "server");
11
+ export const SCHEDULED_NAME = "clips-brain-export-cron";
12
+ export const WORKER_NAME = "clips-brain-export-sweep-background";
13
+ export const ROUTE_PATH = "/api/clips/brain-export/run";
14
+ export const SCHEDULE = "* * * * *";
15
+
16
+ function ensureDir(dir: string) {
17
+ mkdirSync(dir, { recursive: true });
18
+ }
19
+
20
+ export function scheduledTriggerSource(token: string) {
21
+ return `const WORKER_PATH = "/.netlify/functions/${WORKER_NAME}";
22
+ const CRON_TOKEN = ${JSON.stringify(token)};
23
+
24
+ export default async function handler(request) {
25
+ const response = await fetch(new URL(WORKER_PATH, new URL(request.url).origin), {
26
+ method: "POST",
27
+ headers: { "content-type": "application/json", "x-agent-native-clips-brain-export-cron": CRON_TOKEN },
28
+ body: JSON.stringify({ scheduled: true }),
29
+ });
30
+ if (!response.ok && response.status !== 202) console.error("[clips-brain-export-cron] Background sweep trigger failed:", response.status);
31
+ }
32
+
33
+ export const config = {
34
+ name: "Clips Brain export cron trigger",
35
+ generator: "agent-native clips build",
36
+ schedule: ${JSON.stringify(SCHEDULE)},
37
+ };
38
+ `;
39
+ }
40
+
41
+ export function backgroundWorkerSource(token: string) {
42
+ return `globalThis.__AGENT_NATIVE_CLIPS_BRAIN_EXPORT_SCHEDULED_RUNTIME__ = true;
43
+ const CRON_TOKEN = ${JSON.stringify(token)};
44
+ const ROUTE_PATH = ${JSON.stringify(ROUTE_PATH)};
45
+ let cachedHandler;
46
+
47
+ function timingSafeEquals(a, b) {
48
+ if (a.length !== b.length) return false;
49
+ let diff = 0;
50
+ for (let i = 0; i < a.length; i += 1) diff |= a.charCodeAt(i) ^ b.charCodeAt(i);
51
+ return diff === 0;
52
+ }
53
+
54
+ export default async function handler(request, context) {
55
+ const token = request.headers.get("x-agent-native-clips-brain-export-cron") || "";
56
+ if (!timingSafeEquals(token, CRON_TOKEN)) return new Response("Unauthorized", { status: 401 });
57
+ cachedHandler ??= (await import("./main.mjs")).default;
58
+ const url = new URL(request.url);
59
+ url.pathname = ROUTE_PATH;
60
+ url.search = "";
61
+ return cachedHandler(new Request(url, {
62
+ method: "POST",
63
+ headers: { "content-type": "application/json" },
64
+ body: JSON.stringify({ scheduled: true }),
65
+ }), context);
66
+ }
67
+
68
+ export const config = {
69
+ name: "Clips Brain export background sweep",
70
+ generator: "agent-native clips build",
71
+ background: true,
72
+ nodeBundler: "none",
73
+ includedFiles: ["**"],
74
+ preferStatic: false,
75
+ };
76
+ `;
77
+ }
78
+
79
+ export function emitNetlifyBrainExportCron(
80
+ options: {
81
+ functionsDir?: string;
82
+ serverDir?: string;
83
+ token?: string;
84
+ } = {},
85
+ ) {
86
+ const functionsDir = options.functionsDir ?? FUNCTIONS_DIR;
87
+ const serverDir = options.serverDir ?? SERVER_DIR;
88
+ if (!existsSync(serverDir))
89
+ throw new Error(
90
+ "Expected Nitro Netlify server output before emitting Clips Brain export functions.",
91
+ );
92
+ const token = options.token ?? randomBytes(32).toString("hex");
93
+ const scheduledDir = path.join(functionsDir, SCHEDULED_NAME);
94
+ const workerDir = path.join(functionsDir, WORKER_NAME);
95
+ rmSync(scheduledDir, { recursive: true, force: true });
96
+ rmSync(workerDir, { recursive: true, force: true });
97
+ ensureDir(scheduledDir);
98
+ cpSync(serverDir, workerDir, { recursive: true });
99
+ rmSync(path.join(workerDir, "server.mjs"), { force: true });
100
+ writeFileSync(
101
+ path.join(scheduledDir, `${SCHEDULED_NAME}.mjs`),
102
+ scheduledTriggerSource(token),
103
+ );
104
+ writeFileSync(
105
+ path.join(workerDir, `${WORKER_NAME}.mjs`),
106
+ backgroundWorkerSource(token),
107
+ );
108
+ }
109
+
110
+ if (
111
+ process.argv[1] &&
112
+ path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)
113
+ ) {
114
+ emitNetlifyBrainExportCron();
115
+ console.log(
116
+ "[clips-brain-export-cron] Emitted scheduled export sweep and background worker.",
117
+ );
118
+ }
@@ -1,6 +1,6 @@
1
1
  [build]
2
2
  ignore = "node ./scripts/netlify-ignore-build.mjs clips"
3
- command = "export DATABASE_URL=${NETLIFY_DATABASE_URL:-$DATABASE_URL} && pnpm install && NITRO_PRESET=netlify pnpm --filter clips build"
3
+ command = "export DATABASE_URL=${NETLIFY_DATABASE_URL:-$DATABASE_URL} && pnpm install && NITRO_PRESET=netlify pnpm --filter clips build && pnpm exec tsx templates/clips/jobs/emit-netlify-brain-export-cron.ts"
4
4
  publish = "templates/clips/dist"
5
5
  functions = "templates/clips/.netlify/functions-internal"
6
6
 
@@ -25,3 +25,6 @@ AGENT_NATIVE_SERVERLESS_FFMPEG_ARCH = "x64"
25
25
  # auto-continuation path can hand off before Netlify kills the function.
26
26
  [functions."*"]
27
27
  timeout = 75
28
+
29
+ [functions.clips-brain-export-sweep-background]
30
+ timeout = 120
@@ -0,0 +1,92 @@
1
+ import { readFile } from "node:fs/promises";
2
+
3
+ import { writeAppState } from "@agent-native/core/application-state";
4
+ import { saveCredential } from "@agent-native/core/credentials";
5
+ import { getDbExec } from "@agent-native/core/db";
6
+ import { runWithRequestContext } from "@agent-native/core/server";
7
+
8
+ const tokenFile = process.env.BRAIN_CLIPS_TOKEN_FILE;
9
+ if (!tokenFile) throw new Error("BRAIN_CLIPS_TOKEN_FILE is required");
10
+ const token = (await readFile(tokenFile, "utf8")).trim();
11
+ if (!token.startsWith("brain_") || token.length < 32) {
12
+ throw new Error("The Brain ingest token is invalid");
13
+ }
14
+
15
+ const db = getDbExec();
16
+ const { rows } = await db.execute({
17
+ sql: `SELECT id, owner_email, org_id, created_at
18
+ FROM recordings
19
+ WHERE status = ? AND trashed_at IS NULL AND created_at >= ?
20
+ AND EXISTS (
21
+ SELECT 1 FROM recording_transcripts
22
+ WHERE recording_transcripts.recording_id = recordings.id
23
+ AND recording_transcripts.status = ?
24
+ AND LENGTH(TRIM(COALESCE(recording_transcripts.full_text, ''))) > 0
25
+ )
26
+ ORDER BY created_at DESC
27
+ LIMIT ?`,
28
+ args: [
29
+ "ready",
30
+ new Date(Date.now() - 3 * 24 * 60 * 60 * 1000).toISOString(),
31
+ "ready",
32
+ 20,
33
+ ],
34
+ });
35
+ if (rows.length === 0)
36
+ throw new Error("No recent ready Clips recordings found");
37
+
38
+ const recordings = rows as Array<{
39
+ id: string;
40
+ owner_email: string;
41
+ org_id?: string | null;
42
+ }>;
43
+ const ownerEmail = recordings[0]!.owner_email;
44
+ const orgId = recordings[0]!.org_id?.trim();
45
+ if (!ownerEmail || !orgId) {
46
+ throw new Error(
47
+ "Recent Clips recordings are missing owner or organization scope",
48
+ );
49
+ }
50
+ if (
51
+ recordings.some(
52
+ (recording) =>
53
+ recording.owner_email !== ownerEmail ||
54
+ recording.org_id?.trim() !== orgId,
55
+ )
56
+ ) {
57
+ throw new Error(
58
+ "Recent Clips recordings span multiple owners or organizations",
59
+ );
60
+ }
61
+
62
+ await saveCredential(
63
+ "BRAIN_INGEST_URL",
64
+ "https://brain.agent-native.com/api/_agent-native/brain/ingest",
65
+ { userEmail: ownerEmail, orgId, scope: "org" },
66
+ );
67
+ await saveCredential("BRAIN_INGEST_TOKEN", token, {
68
+ userEmail: ownerEmail,
69
+ orgId,
70
+ scope: "org",
71
+ });
72
+
73
+ await runWithRequestContext({ userEmail: ownerEmail, orgId }, async () => {
74
+ const now = new Date().toISOString();
75
+ for (const recording of recordings) {
76
+ await writeAppState(`clips-brain-export-${recording.id}`, {
77
+ recordingId: recording.id,
78
+ status: "pending",
79
+ attempts: 0,
80
+ updatedAt: now,
81
+ nextAttemptAt: now,
82
+ });
83
+ }
84
+ });
85
+
86
+ console.log(
87
+ JSON.stringify({
88
+ configured: true,
89
+ queued: recordings.length,
90
+ organizationScoped: true,
91
+ }),
92
+ );
@@ -0,0 +1,182 @@
1
+ import { getDbExec } from "@agent-native/core/db";
2
+ import { runWithRequestContext } from "@agent-native/core/server";
3
+ import { and, eq, isNull } from "drizzle-orm";
4
+
5
+ import exportToBrain from "../../actions/export-to-brain.js";
6
+ import { getDb, schema } from "../db/index.js";
7
+ import {
8
+ BRAIN_EXPORT_STATE_PREFIX,
9
+ parseBrainExportState,
10
+ writeBrainExportState,
11
+ } from "../lib/brain-export-state.js";
12
+ import { ownerEmailMatches } from "../lib/recordings.js";
13
+
14
+ const SWEEP_INTERVAL_MS = 60_000;
15
+ const MAX_ATTEMPTS = 8;
16
+ const SWEEP_LIMIT = 4;
17
+ const DISCOVERY_LIMIT = 20;
18
+ const BACKFILL_DAYS = 7;
19
+ let skippingLogged = false;
20
+
21
+ async function seedRecentBrainExports(): Promise<void> {
22
+ const now = new Date();
23
+ const { rows } = await getDbExec().execute({
24
+ sql: `SELECT recordings.id, recordings.owner_email, recordings.org_id
25
+ FROM recordings
26
+ INNER JOIN recording_transcripts
27
+ ON recording_transcripts.recording_id = recordings.id
28
+ WHERE recordings.status = ?
29
+ AND recordings.trashed_at IS NULL
30
+ AND recordings.org_id IS NOT NULL
31
+ AND recordings.created_at >= ?
32
+ AND recording_transcripts.status = ?
33
+ AND LENGTH(TRIM(COALESCE(recording_transcripts.full_text, ''))) > 0
34
+ AND NOT EXISTS (
35
+ SELECT 1 FROM application_state
36
+ WHERE application_state.session_id = recordings.owner_email
37
+ AND application_state.key = ? || recordings.id
38
+ )
39
+ ORDER BY recordings.created_at DESC
40
+ LIMIT ?`,
41
+ args: [
42
+ "ready",
43
+ new Date(
44
+ now.getTime() - BACKFILL_DAYS * 24 * 60 * 60 * 1000,
45
+ ).toISOString(),
46
+ "ready",
47
+ BRAIN_EXPORT_STATE_PREFIX,
48
+ DISCOVERY_LIMIT,
49
+ ],
50
+ });
51
+ for (const row of rows as Array<{
52
+ id?: unknown;
53
+ owner_email?: unknown;
54
+ org_id?: unknown;
55
+ }>) {
56
+ const recordingId = typeof row.id === "string" ? row.id.trim() : "";
57
+ const ownerEmail =
58
+ typeof row.owner_email === "string" ? row.owner_email.trim() : "";
59
+ const orgId = typeof row.org_id === "string" ? row.org_id.trim() : "";
60
+ if (!recordingId || !ownerEmail || !orgId) continue;
61
+ await runWithRequestContext({ userEmail: ownerEmail, orgId }, () =>
62
+ writeBrainExportState({
63
+ recordingId,
64
+ status: "pending",
65
+ attempts: 0,
66
+ updatedAt: now.toISOString(),
67
+ nextAttemptAt: now.toISOString(),
68
+ }),
69
+ );
70
+ }
71
+ }
72
+
73
+ export async function runBrainExportSweepOnce(): Promise<void> {
74
+ await seedRecentBrainExports();
75
+ const { rows } = await getDbExec().execute({
76
+ sql: "SELECT session_id, key, value FROM application_state WHERE key LIKE ? AND (value LIKE ? OR value LIKE ?) ORDER BY updated_at ASC LIMIT ?",
77
+ args: [
78
+ `${BRAIN_EXPORT_STATE_PREFIX}%`,
79
+ '%"status":"pending"%',
80
+ '%"status":"failed"%',
81
+ SWEEP_LIMIT,
82
+ ],
83
+ });
84
+ const now = Date.now();
85
+ for (const row of rows as Array<{
86
+ session_id?: unknown;
87
+ key?: unknown;
88
+ value?: unknown;
89
+ }>) {
90
+ const ownerEmail =
91
+ typeof row.session_id === "string" ? row.session_id.trim() : "";
92
+ const key = typeof row.key === "string" ? row.key : "";
93
+ const recordingId = key.slice(BRAIN_EXPORT_STATE_PREFIX.length);
94
+ let raw: unknown;
95
+ try {
96
+ raw = JSON.parse(typeof row.value === "string" ? row.value : "");
97
+ } catch {
98
+ continue;
99
+ }
100
+ const state = parseBrainExportState(raw);
101
+ if (
102
+ !ownerEmail ||
103
+ !recordingId ||
104
+ !state ||
105
+ state.recordingId !== recordingId ||
106
+ !["pending", "failed"].includes(state.status) ||
107
+ state.attempts >= MAX_ATTEMPTS ||
108
+ Date.parse(state.nextAttemptAt ?? "") > now
109
+ )
110
+ continue;
111
+ try {
112
+ const [recording] = await getDb()
113
+ .select({
114
+ ownerEmail: schema.recordings.ownerEmail,
115
+ orgId: schema.recordings.orgId,
116
+ })
117
+ .from(schema.recordings)
118
+ .where(
119
+ and(
120
+ eq(schema.recordings.id, recordingId),
121
+ ownerEmailMatches(schema.recordings.ownerEmail, ownerEmail),
122
+ eq(schema.recordings.status, "ready"),
123
+ isNull(schema.recordings.trashedAt),
124
+ ),
125
+ )
126
+ .limit(1);
127
+ if (!recording) continue;
128
+ await runWithRequestContext(
129
+ {
130
+ userEmail: recording.ownerEmail,
131
+ orgId: recording.orgId ?? undefined,
132
+ },
133
+ () =>
134
+ exportToBrain.run({ recordingId, retryAttempt: state.attempts + 1 }),
135
+ );
136
+ } catch (error) {
137
+ const attempts = state.attempts + 1;
138
+ await runWithRequestContext({ userEmail: ownerEmail }, () =>
139
+ writeBrainExportState({
140
+ recordingId,
141
+ status: "failed",
142
+ attempts,
143
+ reason: "brain-export-worker-failed",
144
+ updatedAt: new Date().toISOString(),
145
+ nextAttemptAt: new Date(
146
+ Date.now() +
147
+ Math.min(15 * 60_000, 30_000 * 2 ** Math.max(0, attempts - 1)),
148
+ ).toISOString(),
149
+ }),
150
+ );
151
+ console.warn("[brain-export] sweep item failed", {
152
+ recordingId,
153
+ error: error instanceof Error ? error.message : String(error),
154
+ });
155
+ }
156
+ }
157
+ }
158
+
159
+ export default function registerBrainExportJob(): void {
160
+ if (process.env.NETLIFY === "true") return;
161
+ const enabled =
162
+ process.env.RUN_BACKGROUND_JOBS === "1" ||
163
+ (process.env.NODE_ENV === "production" &&
164
+ process.env.RUN_BACKGROUND_JOBS !== "0");
165
+ if (!enabled) {
166
+ if (process.env.DEBUG && !skippingLogged) {
167
+ console.log(
168
+ "[brain-export] Skipping background sweep (set RUN_BACKGROUND_JOBS=1 to enable in dev).",
169
+ );
170
+ skippingLogged = true;
171
+ }
172
+ return;
173
+ }
174
+ setInterval(() => {
175
+ runBrainExportSweepOnce().catch((error) =>
176
+ console.error("[brain-export] interval failed:", error),
177
+ );
178
+ }, SWEEP_INTERVAL_MS);
179
+ console.log(
180
+ `[brain-export] Recurring recovery sweep every ${SWEEP_INTERVAL_MS / 1000}s.`,
181
+ );
182
+ }
@@ -0,0 +1,42 @@
1
+ import { writeAppState } from "@agent-native/core/application-state";
2
+
3
+ export const BRAIN_EXPORT_STATE_PREFIX = "clips-brain-export-";
4
+
5
+ export type BrainExportState = {
6
+ recordingId: string;
7
+ status: "pending" | "exported" | "quarantined" | "failed" | "skipped";
8
+ attempts: number;
9
+ updatedAt: string;
10
+ nextAttemptAt?: string;
11
+ captureId?: string;
12
+ sensitivityReceiptId?: string;
13
+ sensitivityDisposition?: string;
14
+ reason?: string;
15
+ };
16
+
17
+ export function brainExportStateKey(recordingId: string): string {
18
+ return `${BRAIN_EXPORT_STATE_PREFIX}${recordingId}`;
19
+ }
20
+
21
+ export async function writeBrainExportState(state: BrainExportState) {
22
+ await writeAppState(brainExportStateKey(state.recordingId), state);
23
+ }
24
+
25
+ export function parseBrainExportState(value: unknown): BrainExportState | null {
26
+ if (!value || typeof value !== "object") return null;
27
+ const state = value as Partial<BrainExportState>;
28
+ if (
29
+ typeof state.recordingId !== "string" ||
30
+ !state.recordingId ||
31
+ !["pending", "exported", "quarantined", "failed", "skipped"].includes(
32
+ String(state.status),
33
+ ) ||
34
+ typeof state.attempts !== "number" ||
35
+ !Number.isInteger(state.attempts) ||
36
+ state.attempts < 0 ||
37
+ typeof state.updatedAt !== "string"
38
+ ) {
39
+ return null;
40
+ }
41
+ return state as BrainExportState;
42
+ }
@@ -7,7 +7,11 @@ import {
7
7
  signScopedAgentAccessToken,
8
8
  } from "@agent-native/core/server";
9
9
 
10
- export type PostFinalizeJobKind = "media-ready" | "seekable" | "transcript";
10
+ export type PostFinalizeJobKind =
11
+ | "media-ready"
12
+ | "seekable"
13
+ | "transcript"
14
+ | "brain-export";
11
15
 
12
16
  export const POST_FINALIZE_JOB_TOKEN_KIND = "clips-post-finalize-job";
13
17
 
@@ -5,6 +5,7 @@
5
5
  * pattern).
6
6
  */
7
7
 
8
+ import registerBrainExportJob from "../jobs/brain-export.js";
8
9
  import registerBuilderMediaCompressionJob from "../jobs/builder-media-compression.js";
9
10
  import registerMediaVerificationJob from "../jobs/media-verification.js";
10
11
  import registerMeetingRemindersJob from "../jobs/meeting-reminders.js";
@@ -17,6 +18,7 @@ export default () => {
17
18
  // background loop is off.
18
19
  registerMeetingRemindersJob();
19
20
  registerBuilderMediaCompressionJob();
21
+ registerBrainExportJob();
20
22
  registerMediaVerificationJob();
21
23
  registerPollCalendarsJob();
22
24
  registerStaleMeetingSweeperJob();
@@ -219,6 +219,48 @@ registerRequiredSecret({
219
219
  required: false,
220
220
  });
221
221
 
222
+ // ── Brain transcript ingest ──────────────────────────────────────────
223
+ // Both values are workspace-scoped: every clip in the workspace must reach
224
+ // the same Brain source, while the encrypted secret store keeps the token out
225
+ // of action responses, application state, and client bundles.
226
+
227
+ registerRequiredSecret({
228
+ key: "BRAIN_INGEST_URL",
229
+ label: "Brain ingest URL",
230
+ description:
231
+ "Signed Brain generic-ingest endpoint for ready Clips transcripts. Pair with BRAIN_INGEST_TOKEN.",
232
+ scope: "workspace",
233
+ kind: "api-key",
234
+ required: false,
235
+ validator: (value) => {
236
+ if (!value) return true;
237
+ try {
238
+ const url = new URL(value);
239
+ return url.protocol === "http:" || url.protocol === "https:"
240
+ ? true
241
+ : { ok: false, error: "Use an HTTP or HTTPS URL." };
242
+ } catch {
243
+ return { ok: false, error: "Enter a valid ingest URL." };
244
+ }
245
+ },
246
+ });
247
+
248
+ registerRequiredSecret({
249
+ key: "BRAIN_INGEST_TOKEN",
250
+ label: "Brain ingest token",
251
+ description:
252
+ "Bearer token for the configured Brain ingest URL. Stored encrypted and never returned to Clips clients or export receipts.",
253
+ scope: "workspace",
254
+ kind: "api-key",
255
+ required: false,
256
+ validator: (value) => {
257
+ if (!value) return true;
258
+ return typeof value === "string" && value.trim().length >= 8
259
+ ? true
260
+ : { ok: false, error: "Token looks too short." };
261
+ },
262
+ });
263
+
222
264
  // ── Dark-launched media worker plumbing ──────────────────────────────
223
265
  // These are optional until the ai-services worker is deployed. When enabled,
224
266
  // Clips enqueues background video compression jobs there instead of using
@@ -11,6 +11,7 @@ import {
11
11
  } from "h3";
12
12
  import { z } from "zod";
13
13
 
14
+ import exportToBrain from "../../../../actions/export-to-brain.js";
14
15
  import finalizeRecording from "../../../../actions/finalize-recording.js";
15
16
  import { ensureRecordingSeekable } from "../../../../actions/lib/ensure-seekable-video.js";
16
17
  import requestTranscript from "../../../../actions/request-transcript.js";
@@ -23,7 +24,7 @@ import {
23
24
 
24
25
  const bodySchema = z.object({
25
26
  recordingId: z.string().min(1).max(200),
26
- kind: z.enum(["media-ready", "seekable", "transcript"]),
27
+ kind: z.enum(["media-ready", "seekable", "transcript", "brain-export"]),
27
28
  token: z.string().min(1),
28
29
  delayMs: z.number().int().min(0).max(30_000).optional(),
29
30
  retryAttempt: z.number().int().min(1).max(10).optional(),
@@ -111,6 +112,14 @@ export default defineEventHandler(async (event: H3Event) => {
111
112
  return { ok: true, kind, result };
112
113
  }
113
114
 
115
+ if (kind === "brain-export") {
116
+ const result = await exportToBrain.run({
117
+ recordingId,
118
+ retryAttempt,
119
+ });
120
+ return { ok: true, kind, result };
121
+ }
122
+
114
123
  const result = await requestTranscript.run({
115
124
  recordingId,
116
125
  force: true,
@@ -0,0 +1,45 @@
1
+ import { timingSafeEqual } from "node:crypto";
2
+
3
+ import { createError, defineEventHandler, getHeader } from "h3";
4
+
5
+ import { runBrainExportSweepOnce } from "../../../../jobs/brain-export.js";
6
+
7
+ declare global {
8
+ var __AGENT_NATIVE_CLIPS_BRAIN_EXPORT_SCHEDULED_RUNTIME__:
9
+ | boolean
10
+ | undefined;
11
+ }
12
+
13
+ function productionLike() {
14
+ return (
15
+ process.env.NODE_ENV === "production" || process.env.NETLIFY === "true"
16
+ );
17
+ }
18
+
19
+ function headerMatchesSecret(header: string | undefined, secret: string) {
20
+ const expected = `Bearer ${secret}`;
21
+ const value = header?.trim() ?? "";
22
+ return (
23
+ value.length === expected.length &&
24
+ timingSafeEqual(Buffer.from(value), Buffer.from(expected))
25
+ );
26
+ }
27
+
28
+ export default defineEventHandler(async (event) => {
29
+ const scheduled =
30
+ globalThis.__AGENT_NATIVE_CLIPS_BRAIN_EXPORT_SCHEDULED_RUNTIME__ === true;
31
+ const secret = process.env.CLIPS_BRAIN_EXPORT_JOBS_SECRET?.trim(); // guard:allow-env-credential — deployment scheduler route secret
32
+ if (!secret && productionLike() && !scheduled)
33
+ throw createError({
34
+ statusCode: 503,
35
+ statusMessage: "CLIPS_BRAIN_EXPORT_JOBS_SECRET is required",
36
+ });
37
+ if (
38
+ secret &&
39
+ !scheduled &&
40
+ !headerMatchesSecret(getHeader(event, "authorization"), secret)
41
+ )
42
+ throw createError({ statusCode: 401, statusMessage: "Unauthorized" });
43
+ await runBrainExportSweepOnce();
44
+ return { ok: true };
45
+ });
@@ -85,7 +85,10 @@ in the moment of agent use.
85
85
  approval, generative UI, progress, and screen-state exposure.
86
86
  - **Chat history kit**: presentational chat lists and recent-chat rails belong
87
87
  in Toolkit; Core keeps thread persistence, agent execution, transport, and
88
- page-to-sidebar handoff. Apps inject routing, labels, and domain actions.
88
+ page-to-sidebar handoff. Use Toolkit's `ChatHistoryRail` for the standard
89
+ five-item sidebar preview and a footer row with New chat followed by an
90
+ ellipsis disclosure up to fifteen. Apps inject routing, labels, and domain
91
+ actions.
89
92
  - **Agent page kit**: the full-page `/agent` surface (`AgentTabsPage` from
90
93
  `@agent-native/core/client`) with Context, Files, Connections, Jobs, and
91
94
  Access tabs plus a Personal/Organization scope toggle. The canonical home