@agent-native/core 0.128.3 → 0.129.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 (350) hide show
  1. package/corpus/README.md +2 -2
  2. package/corpus/core/CHANGELOG.md +91 -0
  3. package/corpus/core/docs/content/template-design-collaboration-and-full-apps.mdx +3 -1
  4. package/corpus/core/package.json +6 -3
  5. package/corpus/core/src/a2a/client.ts +18 -4
  6. package/corpus/core/src/a2a/server.ts +19 -3
  7. package/corpus/core/src/a2a/types.ts +7 -0
  8. package/corpus/core/src/action.ts +43 -16
  9. package/corpus/core/src/agent/engine/ai-sdk-engine.ts +14 -10
  10. package/corpus/core/src/agent/production-agent.ts +41 -3
  11. package/corpus/core/src/agent/thread-data-builder.ts +5 -0
  12. package/corpus/core/src/chat-threads/migrations.ts +53 -0
  13. package/corpus/core/src/cli/create.ts +4 -0
  14. package/corpus/core/src/cli/skills-content/design-visual-edit-skill.ts +36 -16
  15. package/corpus/core/src/cli/skills.ts +16 -0
  16. package/corpus/core/src/client/AgentPanel.tsx +9 -5
  17. package/corpus/core/src/client/AssistantChat.tsx +138 -46
  18. package/corpus/core/src/client/MultiTabAssistantChat.tsx +16 -10
  19. package/corpus/core/src/client/agent-chat-adapter.ts +21 -3
  20. package/corpus/core/src/client/analytics.ts +159 -102
  21. package/corpus/core/src/client/chat/legacy-chart-shorthand.tsx +479 -0
  22. package/corpus/core/src/client/chat/markdown-renderer.tsx +23 -3
  23. package/corpus/core/src/client/chat/message-components.tsx +12 -5
  24. package/corpus/core/src/client/chat/runtime.ts +2 -1
  25. package/corpus/core/src/client/chat/tool-call-display.tsx +25 -2
  26. package/corpus/core/src/client/chat-model-groups.ts +3 -0
  27. package/corpus/core/src/client/client-status-requests.ts +161 -0
  28. package/corpus/core/src/client/settings/VoiceTranscriptionSection.tsx +2 -2
  29. package/corpus/core/src/client/sse-event-processor.ts +15 -3
  30. package/corpus/core/src/client/use-agent-engine-configured.ts +54 -50
  31. package/corpus/core/src/client/use-chat-models.ts +16 -11
  32. package/corpus/core/src/client/use-chat-threads.ts +32 -9
  33. package/corpus/core/src/client/use-session.ts +4 -5
  34. package/corpus/core/src/db/client.ts +83 -8
  35. package/corpus/core/src/db/ensure-additive-columns.ts +31 -20
  36. package/corpus/core/src/db/migrations.ts +22 -2
  37. package/corpus/core/src/extensions/actions.ts +1 -1
  38. package/corpus/core/src/mcp/approval-store.ts +99 -0
  39. package/corpus/core/src/mcp/build-server.ts +579 -303
  40. package/corpus/core/src/mcp/embed-app.ts +1 -1
  41. package/corpus/core/src/mcp/oauth-client-metadata.ts +380 -0
  42. package/corpus/core/src/mcp/oauth-route.ts +57 -52
  43. package/corpus/core/src/mcp/screen-memory-stdio.ts +400 -378
  44. package/corpus/core/src/mcp/server.ts +29 -145
  45. package/corpus/core/src/mcp/stdio.ts +72 -80
  46. package/corpus/core/src/mcp-client/manager.ts +6 -10
  47. package/corpus/core/src/mcp-client/oauth-client.ts +127 -36
  48. package/corpus/core/src/mcp-client/oauth-routes.ts +16 -6
  49. package/corpus/core/src/mcp-client/routes.ts +6 -5
  50. package/corpus/core/src/server/agent-capabilities.ts +20 -0
  51. package/corpus/core/src/server/agent-chat/action-filters-a2a.ts +27 -0
  52. package/corpus/core/src/server/agent-chat-plugin.ts +18 -0
  53. package/corpus/core/src/server/transcribe-voice.ts +14 -5
  54. package/corpus/core/src/templates/default/.agents/skills/security/SKILL.md +13 -2
  55. package/corpus/core/src/templates/workspace-core/.agents/skills/authentication/SKILL.md +5 -2
  56. package/corpus/core/src/templates/workspace-core/.agents/skills/external-agents/SKILL.md +18 -0
  57. package/corpus/core/src/templates/workspace-core/.agents/skills/security/SKILL.md +13 -2
  58. package/corpus/templates/analytics/.agents/skills/data-querying/SKILL.md +12 -0
  59. package/corpus/templates/analytics/.agents/skills/security/SKILL.md +13 -2
  60. package/corpus/templates/analytics/actions/generate-chart.ts +2 -2
  61. package/corpus/templates/analytics/app/components/dashboard/DashboardPanelSkeleton.tsx +1 -10
  62. package/corpus/templates/analytics/app/global.css +0 -31
  63. package/corpus/templates/analytics/app/lib/sql-query.ts +46 -20
  64. package/corpus/templates/analytics/changelog/2026-07-28-analytics-dashboard-loading-states-now-use-a-simpler-standar.md +6 -0
  65. package/corpus/templates/analytics/changelog/2026-07-28-analytics-dashboards-now-load-first-party-charts-reliably-in.md +6 -0
  66. package/corpus/templates/analytics/server/lib/first-party-analytics-cache.ts +42 -16
  67. package/corpus/templates/analytics/server/lib/first-party-analytics.ts +4 -2
  68. package/corpus/templates/analytics/shared/sql-query-limits.ts +1 -0
  69. package/corpus/templates/assets/.agents/skills/security/SKILL.md +13 -2
  70. package/corpus/templates/brain/.agents/skills/security/SKILL.md +13 -2
  71. package/corpus/templates/calendar/.agents/skills/security/SKILL.md +13 -2
  72. package/corpus/templates/chat/.agents/skills/security/SKILL.md +13 -2
  73. package/corpus/templates/clips/.agents/skills/security/SKILL.md +13 -2
  74. package/corpus/templates/clips/app/components/player/player-controls.tsx +3 -6
  75. package/corpus/templates/clips/app/routes/r.$recordingId.tsx +6 -2
  76. package/corpus/templates/clips/app/routes/share.$shareId.tsx +3 -1
  77. package/corpus/templates/clips/changelog/2026-07-28-copied-share-links-no-longer-leave-an-unnecessary-confirmati.md +6 -0
  78. package/corpus/templates/clips/changelog/2026-07-28-playback-skip-controls-no-longer-show-overlapping-numbers.md +6 -0
  79. package/corpus/templates/clips/changelog/2026-07-28-public-clips-share-buttons-now-use-a-clean-text-only-label.md +6 -0
  80. package/corpus/templates/clips/desktop/src/app.tsx +14 -17
  81. package/corpus/templates/clips/desktop/src/overlays/live-audio-bars.tsx +3 -1
  82. package/corpus/templates/clips/desktop/src/styles.css +13 -10
  83. package/corpus/templates/clips/desktop/src-tauri/src/recording_indicator.rs +2 -2
  84. package/corpus/templates/clips/desktop/src-tauri/src/whisper_speech.rs +36 -0
  85. package/corpus/templates/content/.agents/skills/security/SKILL.md +13 -2
  86. package/corpus/templates/content/actions/_builder-cms-read-client.ts +155 -34
  87. package/corpus/templates/content/actions/_builder-docs-client.ts +151 -30
  88. package/corpus/templates/content/actions/stage-builder-source-bulk-update.ts +5 -2
  89. package/corpus/templates/content/app/root.tsx +35 -14
  90. package/corpus/templates/content/changelog/2026-07-28-builder-backed-files-media-fields-now-accept-direct-file-url.md +6 -0
  91. package/corpus/templates/content/changelog/2026-07-28-builder-content-reads-now-negotiate-the-newest-mcp-protocol-.md +6 -0
  92. package/corpus/templates/content/changelog/2026-07-28-public-content-pages-load-faster-before-the-optional-ask-sid.md +6 -0
  93. package/corpus/templates/design/.agents/skills/security/SKILL.md +13 -2
  94. package/corpus/templates/design/.agents/skills/visual-edit/SKILL.md +36 -16
  95. package/corpus/templates/design/.generated/bridge/editor-chrome.generated.ts +42 -3
  96. package/corpus/templates/design/actions/open-visual-edit.ts +2 -1
  97. package/corpus/templates/design/app/components/design/CanvasContextMenu.tsx +2 -2
  98. package/corpus/templates/design/app/components/design/DesignCanvas.tsx +14 -15
  99. package/corpus/templates/design/app/components/design/ResponsiveInteractBar.tsx +314 -0
  100. package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +88 -3
  101. package/corpus/templates/design/app/components/design/keyboard-shortcuts.ts +1 -1
  102. package/corpus/templates/design/app/hooks/useDesignHotkeys.ts +7 -6
  103. package/corpus/templates/design/app/i18n-data.ts +56 -11
  104. package/corpus/templates/design/app/i18n-responsive-interact.ts +183 -0
  105. package/corpus/templates/design/app/lib/agent-chat.ts +60 -0
  106. package/corpus/templates/design/app/pages/design-editor/responsive-interact.ts +89 -0
  107. package/corpus/templates/design/changelog/2026-07-28-design-edits-can-be-applied-through-the-host-coding-agent-fr.md +6 -0
  108. package/corpus/templates/design/changelog/2026-07-28-interact-mode-can-preview-designs-in-responsive-device-sizes.md +6 -0
  109. package/corpus/templates/design/changelog/2026-07-28-interact-mode-now-opens-a-full-screen-responsive-preview-wit.md +6 -0
  110. package/corpus/templates/design/changelog/2026-07-28-show-or-hide-design-editing-chrome-with-cmd-on-apple-devices.md +2 -2
  111. package/corpus/templates/design/changelog/2026-07-28-visual-edit-now-always-shows-the-live-running-app-in-the-can.md +6 -0
  112. package/corpus/templates/dispatch/.agents/skills/security/SKILL.md +13 -2
  113. package/corpus/templates/forms/.agents/skills/security/SKILL.md +13 -2
  114. package/corpus/templates/macros/.agents/skills/security/SKILL.md +13 -2
  115. package/corpus/templates/mail/.agents/skills/security/SKILL.md +13 -2
  116. package/corpus/templates/plan/.agents/skills/security/SKILL.md +13 -2
  117. package/corpus/templates/slides/.agents/skills/security/SKILL.md +13 -2
  118. package/corpus/templates/tasks/.agents/skills/security/SKILL.md +13 -2
  119. package/corpus/toolkit/CHANGELOG.md +13 -0
  120. package/corpus/toolkit/package.json +1 -1
  121. package/corpus/toolkit/src/chat-history.css +1 -2
  122. package/corpus/toolkit/src/composer/TiptapComposer.tsx +45 -0
  123. package/dist/a2a/client.d.ts +7 -0
  124. package/dist/a2a/client.d.ts.map +1 -1
  125. package/dist/a2a/client.js +8 -3
  126. package/dist/a2a/client.js.map +1 -1
  127. package/dist/a2a/server.d.ts.map +1 -1
  128. package/dist/a2a/server.js +18 -3
  129. package/dist/a2a/server.js.map +1 -1
  130. package/dist/a2a/types.d.ts +7 -0
  131. package/dist/a2a/types.d.ts.map +1 -1
  132. package/dist/a2a/types.js.map +1 -1
  133. package/dist/action.d.ts +15 -1
  134. package/dist/action.d.ts.map +1 -1
  135. package/dist/action.js +39 -16
  136. package/dist/action.js.map +1 -1
  137. package/dist/agent/engine/ai-sdk-engine.d.ts.map +1 -1
  138. package/dist/agent/engine/ai-sdk-engine.js +14 -10
  139. package/dist/agent/engine/ai-sdk-engine.js.map +1 -1
  140. package/dist/agent/production-agent.d.ts.map +1 -1
  141. package/dist/agent/production-agent.js +35 -7
  142. package/dist/agent/production-agent.js.map +1 -1
  143. package/dist/agent/thread-data-builder.d.ts +2 -0
  144. package/dist/agent/thread-data-builder.d.ts.map +1 -1
  145. package/dist/agent/thread-data-builder.js +3 -0
  146. package/dist/agent/thread-data-builder.js.map +1 -1
  147. package/dist/chat-threads/migrations.d.ts +21 -0
  148. package/dist/chat-threads/migrations.d.ts.map +1 -0
  149. package/dist/chat-threads/migrations.js +45 -0
  150. package/dist/chat-threads/migrations.js.map +1 -0
  151. package/dist/cli/create.d.ts.map +1 -1
  152. package/dist/cli/create.js +4 -0
  153. package/dist/cli/create.js.map +1 -1
  154. package/dist/cli/skills-content/design-visual-edit-skill.d.ts +1 -1
  155. package/dist/cli/skills-content/design-visual-edit-skill.d.ts.map +1 -1
  156. package/dist/cli/skills-content/design-visual-edit-skill.js +36 -16
  157. package/dist/cli/skills-content/design-visual-edit-skill.js.map +1 -1
  158. package/dist/cli/skills.d.ts.map +1 -1
  159. package/dist/cli/skills.js +15 -0
  160. package/dist/cli/skills.js.map +1 -1
  161. package/dist/client/AgentPanel.d.ts.map +1 -1
  162. package/dist/client/AgentPanel.js +6 -5
  163. package/dist/client/AgentPanel.js.map +1 -1
  164. package/dist/client/AssistantChat.d.ts +37 -0
  165. package/dist/client/AssistantChat.d.ts.map +1 -1
  166. package/dist/client/AssistantChat.js +41 -8
  167. package/dist/client/AssistantChat.js.map +1 -1
  168. package/dist/client/MultiTabAssistantChat.d.ts.map +1 -1
  169. package/dist/client/MultiTabAssistantChat.js +11 -10
  170. package/dist/client/MultiTabAssistantChat.js.map +1 -1
  171. package/dist/client/agent-chat-adapter.d.ts.map +1 -1
  172. package/dist/client/agent-chat-adapter.js +19 -3
  173. package/dist/client/agent-chat-adapter.js.map +1 -1
  174. package/dist/client/analytics.d.ts.map +1 -1
  175. package/dist/client/analytics.js +150 -101
  176. package/dist/client/analytics.js.map +1 -1
  177. package/dist/client/chat/legacy-chart-shorthand.d.ts +29 -0
  178. package/dist/client/chat/legacy-chart-shorthand.d.ts.map +1 -0
  179. package/dist/client/chat/legacy-chart-shorthand.js +330 -0
  180. package/dist/client/chat/legacy-chart-shorthand.js.map +1 -0
  181. package/dist/client/chat/markdown-renderer.d.ts.map +1 -1
  182. package/dist/client/chat/markdown-renderer.js +8 -2
  183. package/dist/client/chat/markdown-renderer.js.map +1 -1
  184. package/dist/client/chat/message-components.d.ts +2 -1
  185. package/dist/client/chat/message-components.d.ts.map +1 -1
  186. package/dist/client/chat/message-components.js +11 -4
  187. package/dist/client/chat/message-components.js.map +1 -1
  188. package/dist/client/chat/runtime.d.ts.map +1 -1
  189. package/dist/client/chat/runtime.js +2 -1
  190. package/dist/client/chat/runtime.js.map +1 -1
  191. package/dist/client/chat/tool-call-display.d.ts +4 -1
  192. package/dist/client/chat/tool-call-display.d.ts.map +1 -1
  193. package/dist/client/chat/tool-call-display.js +14 -8
  194. package/dist/client/chat/tool-call-display.js.map +1 -1
  195. package/dist/client/chat-model-groups.d.ts.map +1 -1
  196. package/dist/client/chat-model-groups.js +3 -0
  197. package/dist/client/chat-model-groups.js.map +1 -1
  198. package/dist/client/client-status-requests.d.ts +14 -0
  199. package/dist/client/client-status-requests.d.ts.map +1 -0
  200. package/dist/client/client-status-requests.js +126 -0
  201. package/dist/client/client-status-requests.js.map +1 -0
  202. package/dist/client/settings/VoiceTranscriptionSection.js +1 -1
  203. package/dist/client/settings/VoiceTranscriptionSection.js.map +1 -1
  204. package/dist/client/sse-event-processor.d.ts +7 -0
  205. package/dist/client/sse-event-processor.d.ts.map +1 -1
  206. package/dist/client/sse-event-processor.js +8 -3
  207. package/dist/client/sse-event-processor.js.map +1 -1
  208. package/dist/client/use-agent-engine-configured.d.ts.map +1 -1
  209. package/dist/client/use-agent-engine-configured.js +29 -43
  210. package/dist/client/use-agent-engine-configured.js.map +1 -1
  211. package/dist/client/use-chat-models.d.ts.map +1 -1
  212. package/dist/client/use-chat-models.js +11 -11
  213. package/dist/client/use-chat-models.js.map +1 -1
  214. package/dist/client/use-chat-threads.d.ts.map +1 -1
  215. package/dist/client/use-chat-threads.js +23 -7
  216. package/dist/client/use-chat-threads.js.map +1 -1
  217. package/dist/client/use-session.d.ts.map +1 -1
  218. package/dist/client/use-session.js +4 -4
  219. package/dist/client/use-session.js.map +1 -1
  220. package/dist/collab/awareness.d.ts +2 -2
  221. package/dist/collab/awareness.d.ts.map +1 -1
  222. package/dist/db/client.d.ts.map +1 -1
  223. package/dist/db/client.js +48 -4
  224. package/dist/db/client.js.map +1 -1
  225. package/dist/db/ensure-additive-columns.d.ts.map +1 -1
  226. package/dist/db/ensure-additive-columns.js +32 -21
  227. package/dist/db/ensure-additive-columns.js.map +1 -1
  228. package/dist/db/migrations.d.ts +9 -0
  229. package/dist/db/migrations.d.ts.map +1 -1
  230. package/dist/db/migrations.js +14 -3
  231. package/dist/db/migrations.js.map +1 -1
  232. package/dist/extensions/actions.js +1 -1
  233. package/dist/extensions/actions.js.map +1 -1
  234. package/dist/mcp/approval-store.d.ts +16 -0
  235. package/dist/mcp/approval-store.d.ts.map +1 -0
  236. package/dist/mcp/approval-store.js +85 -0
  237. package/dist/mcp/approval-store.js.map +1 -0
  238. package/dist/mcp/build-server.d.ts +1 -34
  239. package/dist/mcp/build-server.d.ts.map +1 -1
  240. package/dist/mcp/build-server.js +220 -23
  241. package/dist/mcp/build-server.js.map +1 -1
  242. package/dist/mcp/embed-app.js +1 -1
  243. package/dist/mcp/embed-app.js.map +1 -1
  244. package/dist/mcp/oauth-client-metadata.d.ts +15 -0
  245. package/dist/mcp/oauth-client-metadata.d.ts.map +1 -0
  246. package/dist/mcp/oauth-client-metadata.js +297 -0
  247. package/dist/mcp/oauth-client-metadata.js.map +1 -0
  248. package/dist/mcp/oauth-route.d.ts.map +1 -1
  249. package/dist/mcp/oauth-route.js +39 -53
  250. package/dist/mcp/oauth-route.js.map +1 -1
  251. package/dist/mcp/screen-memory-stdio.d.ts.map +1 -1
  252. package/dist/mcp/screen-memory-stdio.js +304 -296
  253. package/dist/mcp/screen-memory-stdio.js.map +1 -1
  254. package/dist/mcp/server.d.ts +12 -25
  255. package/dist/mcp/server.d.ts.map +1 -1
  256. package/dist/mcp/server.js +24 -133
  257. package/dist/mcp/server.js.map +1 -1
  258. package/dist/mcp/stdio.d.ts.map +1 -1
  259. package/dist/mcp/stdio.js +36 -35
  260. package/dist/mcp/stdio.js.map +1 -1
  261. package/dist/mcp-client/manager.d.ts.map +1 -1
  262. package/dist/mcp-client/manager.js +6 -6
  263. package/dist/mcp-client/manager.js.map +1 -1
  264. package/dist/mcp-client/oauth-client.d.ts +13 -17
  265. package/dist/mcp-client/oauth-client.d.ts.map +1 -1
  266. package/dist/mcp-client/oauth-client.js +74 -16
  267. package/dist/mcp-client/oauth-client.js.map +1 -1
  268. package/dist/mcp-client/oauth-routes.d.ts +2 -2
  269. package/dist/mcp-client/oauth-routes.d.ts.map +1 -1
  270. package/dist/mcp-client/oauth-routes.js +16 -6
  271. package/dist/mcp-client/oauth-routes.js.map +1 -1
  272. package/dist/mcp-client/routes.js +5 -5
  273. package/dist/mcp-client/routes.js.map +1 -1
  274. package/dist/notifications/routes.d.ts +1 -1
  275. package/dist/progress/routes.d.ts +1 -1
  276. package/dist/resources/handlers.d.ts +1 -1
  277. package/dist/secrets/routes.d.ts +9 -9
  278. package/dist/server/agent-capabilities.d.ts.map +1 -1
  279. package/dist/server/agent-capabilities.js +21 -0
  280. package/dist/server/agent-capabilities.js.map +1 -1
  281. package/dist/server/agent-chat/action-filters-a2a.d.ts +14 -0
  282. package/dist/server/agent-chat/action-filters-a2a.d.ts.map +1 -1
  283. package/dist/server/agent-chat/action-filters-a2a.js +16 -0
  284. package/dist/server/agent-chat/action-filters-a2a.js.map +1 -1
  285. package/dist/server/agent-chat-plugin.d.ts.map +1 -1
  286. package/dist/server/agent-chat-plugin.js +11 -1
  287. package/dist/server/agent-chat-plugin.js.map +1 -1
  288. package/dist/server/transcribe-voice.js +12 -6
  289. package/dist/server/transcribe-voice.js.map +1 -1
  290. package/dist/templates/chat/.agents/skills/security/SKILL.md +13 -2
  291. package/dist/templates/default/.agents/skills/security/SKILL.md +13 -2
  292. package/dist/templates/workspace-core/.agents/skills/authentication/SKILL.md +5 -2
  293. package/dist/templates/workspace-core/.agents/skills/external-agents/SKILL.md +18 -0
  294. package/dist/templates/workspace-core/.agents/skills/security/SKILL.md +13 -2
  295. package/docs/content/template-design-collaboration-and-full-apps.mdx +3 -1
  296. package/package.json +7 -4
  297. package/src/a2a/client.ts +18 -4
  298. package/src/a2a/server.ts +19 -3
  299. package/src/a2a/types.ts +7 -0
  300. package/src/action.ts +43 -16
  301. package/src/agent/engine/ai-sdk-engine.ts +14 -10
  302. package/src/agent/production-agent.ts +41 -3
  303. package/src/agent/thread-data-builder.ts +5 -0
  304. package/src/chat-threads/migrations.ts +53 -0
  305. package/src/cli/create.ts +4 -0
  306. package/src/cli/skills-content/design-visual-edit-skill.ts +36 -16
  307. package/src/cli/skills.ts +16 -0
  308. package/src/client/AgentPanel.tsx +9 -5
  309. package/src/client/AssistantChat.tsx +138 -46
  310. package/src/client/MultiTabAssistantChat.tsx +16 -10
  311. package/src/client/agent-chat-adapter.ts +21 -3
  312. package/src/client/analytics.ts +159 -102
  313. package/src/client/chat/legacy-chart-shorthand.tsx +479 -0
  314. package/src/client/chat/markdown-renderer.tsx +23 -3
  315. package/src/client/chat/message-components.tsx +12 -5
  316. package/src/client/chat/runtime.ts +2 -1
  317. package/src/client/chat/tool-call-display.tsx +25 -2
  318. package/src/client/chat-model-groups.ts +3 -0
  319. package/src/client/client-status-requests.ts +161 -0
  320. package/src/client/settings/VoiceTranscriptionSection.tsx +2 -2
  321. package/src/client/sse-event-processor.ts +15 -3
  322. package/src/client/use-agent-engine-configured.ts +54 -50
  323. package/src/client/use-chat-models.ts +16 -11
  324. package/src/client/use-chat-threads.ts +32 -9
  325. package/src/client/use-session.ts +4 -5
  326. package/src/db/client.ts +83 -8
  327. package/src/db/ensure-additive-columns.ts +31 -20
  328. package/src/db/migrations.ts +22 -2
  329. package/src/extensions/actions.ts +1 -1
  330. package/src/mcp/approval-store.ts +99 -0
  331. package/src/mcp/build-server.ts +579 -303
  332. package/src/mcp/embed-app.ts +1 -1
  333. package/src/mcp/oauth-client-metadata.ts +380 -0
  334. package/src/mcp/oauth-route.ts +57 -52
  335. package/src/mcp/screen-memory-stdio.ts +400 -378
  336. package/src/mcp/server.ts +29 -145
  337. package/src/mcp/stdio.ts +72 -80
  338. package/src/mcp-client/manager.ts +6 -10
  339. package/src/mcp-client/oauth-client.ts +127 -36
  340. package/src/mcp-client/oauth-routes.ts +16 -6
  341. package/src/mcp-client/routes.ts +6 -5
  342. package/src/server/agent-capabilities.ts +20 -0
  343. package/src/server/agent-chat/action-filters-a2a.ts +27 -0
  344. package/src/server/agent-chat-plugin.ts +18 -0
  345. package/src/server/transcribe-voice.ts +14 -5
  346. package/src/templates/chat/.agents/skills/security/SKILL.md +13 -2
  347. package/src/templates/default/.agents/skills/security/SKILL.md +13 -2
  348. package/src/templates/workspace-core/.agents/skills/authentication/SKILL.md +5 -2
  349. package/src/templates/workspace-core/.agents/skills/external-agents/SKILL.md +18 -0
  350. package/src/templates/workspace-core/.agents/skills/security/SKILL.md +13 -2
@@ -4,6 +4,7 @@
4
4
  import type { ToolCallMessagePartProps } from "@assistant-ui/react";
5
5
  import {
6
6
  IconLoader2,
7
+ IconAlertTriangle,
7
8
  IconCircleX,
8
9
  IconCheck,
9
10
  IconChevronRight,
@@ -583,6 +584,7 @@ export function ToolCallDisplay({
583
584
  mcpApp,
584
585
  chatUI,
585
586
  isRunning,
587
+ outcome,
586
588
  structuredMeta,
587
589
  approval,
588
590
  repeatCount,
@@ -596,6 +598,8 @@ export function ToolCallDisplay({
596
598
  mcpApp?: AgentMcpAppPayload;
597
599
  chatUI?: ActionChatUIConfig;
598
600
  isRunning: boolean;
601
+ /** "unknown": the stream ended mid-flight, so the side effect may have landed. */
602
+ outcome?: "unknown";
599
603
  structuredMeta?: Record<string, unknown>;
600
604
  approval?: { approvalKey: string; dismissed?: boolean };
601
605
  repeatCount?: number;
@@ -661,6 +665,7 @@ export function ToolCallDisplay({
661
665
  mcpApp={mcpApp}
662
666
  chatUI={chatUI}
663
667
  isRunning={isRunning}
668
+ outcome={outcome}
664
669
  isActiveTail={showActiveTail}
665
670
  structuredMeta={structuredMeta}
666
671
  approval={approval}
@@ -677,6 +682,7 @@ function ToolCallDisplayGeneric({
677
682
  mcpApp,
678
683
  chatUI,
679
684
  isRunning,
685
+ outcome,
680
686
  isActiveTail,
681
687
  structuredMeta,
682
688
  approval,
@@ -689,6 +695,7 @@ function ToolCallDisplayGeneric({
689
695
  mcpApp?: AgentMcpAppPayload;
690
696
  chatUI?: ActionChatUIConfig;
691
697
  isRunning: boolean;
698
+ outcome?: "unknown";
692
699
  isActiveTail: boolean;
693
700
  structuredMeta?: Record<string, unknown>;
694
701
  approval?: { approvalKey: string; dismissed?: boolean };
@@ -704,6 +711,7 @@ function ToolCallDisplayGeneric({
704
711
  ? args.agent
705
712
  : null;
706
713
  const isAgentError = isAgentCall && result === "Error calling agent";
714
+ const isUnknownOutcome = !isRunning && outcome === "unknown";
707
715
  const agentStreamText = isRawCallAgent
708
716
  ? (result ?? "")
709
717
  : isAgentCall
@@ -865,6 +873,8 @@ function ToolCallDisplayGeneric({
865
873
  <IconLoader2 className="size-3.5 animate-spin" />
866
874
  ) : isAgentError ? (
867
875
  <IconCircleX className="size-3.5 text-destructive" />
876
+ ) : isUnknownOutcome ? (
877
+ <IconAlertTriangle className="size-3.5 text-muted-foreground" />
868
878
  ) : (
869
879
  <>
870
880
  <ToolIcon
@@ -929,6 +939,12 @@ function ToolCallDisplayGeneric({
929
939
  )}
930
940
  </div>
931
941
  </AnimatedCollapse>
942
+ {isUnknownOutcome && (
943
+ <p role="status" className="ps-5 text-xs text-muted-foreground">
944
+ Interrupted before this finished reporting — it may or may not have
945
+ completed. Check before retrying.
946
+ </p>
947
+ )}
932
948
  {approval && (
933
949
  <ApprovalAffordance toolName={toolName} approval={approval} />
934
950
  )}
@@ -1149,14 +1165,19 @@ export function ToolCallFallback({
1149
1165
  chatUI?: ActionChatUIConfig;
1150
1166
  structuredMeta?: Record<string, unknown>;
1151
1167
  activity?: boolean;
1168
+ outcome?: "unknown";
1152
1169
  approval?: { approvalKey: string; dismissed?: boolean };
1153
1170
  repeatCount?: number;
1154
1171
  isLatestRunning?: boolean;
1155
1172
  isActiveTail?: boolean;
1156
1173
  }) {
1157
1174
  const chatRunning = React.useContext(ChatRunningContext);
1158
- const isRunning =
1159
- result === undefined && (chatRunning || rest.activity === true);
1175
+ // A spinner is a claim that something is running right now, so it needs an
1176
+ // actually-running chat. `chatRunning` already stays true across
1177
+ // auto-continuation gaps and server-active runs (resolveAssistantChatRunningState),
1178
+ // so an activity placeholder alone must never resurrect one on rehydrated
1179
+ // history.
1180
+ const isRunning = result === undefined && chatRunning;
1160
1181
  return (
1161
1182
  <ToolCallDisplay
1162
1183
  toolName={toolName}
@@ -1173,6 +1194,7 @@ export function ToolCallFallback({
1173
1194
  chatUI={rest.chatUI}
1174
1195
  structuredMeta={rest.structuredMeta}
1175
1196
  isRunning={isRunning}
1197
+ outcome={rest.outcome}
1176
1198
  isActiveTail={rest.isActiveTail}
1177
1199
  isLatestRunning={rest.isLatestRunning}
1178
1200
  approval={rest.approval}
@@ -1247,6 +1269,7 @@ export function ReconnectStreamMessage({
1247
1269
  mcpApp={part.mcpApp}
1248
1270
  chatUI={part.chatUI}
1249
1271
  structuredMeta={part.structuredMeta}
1272
+ outcome={part.outcome}
1250
1273
  isRunning={
1251
1274
  part.result === undefined && (chatRunning || part.activity === true)
1252
1275
  }
@@ -40,6 +40,9 @@ function addCurrentModel(
40
40
  return next;
41
41
  }
42
42
 
43
+ // Cheapest → most expensive. The composer mirrors these tokens for the `$`
44
+ // cost labels on picker rows (packages/toolkit/src/composer/TiptapComposer.tsx);
45
+ // the toolkit cannot import core, so a new family must be added in both lists.
43
46
  const MODEL_COST_ORDER = [
44
47
  "luna",
45
48
  "terra",
@@ -0,0 +1,161 @@
1
+ import { agentNativePath } from "./api-path.js";
2
+
3
+ export type ClientStatusResult<T> =
4
+ | { state: "available"; value: T }
5
+ | { state: "unavailable"; status?: number };
6
+
7
+ type CacheEntry = {
8
+ expiresAt: number;
9
+ result: ClientStatusResult<unknown>;
10
+ };
11
+
12
+ const RESULT_TTL_MS = 500;
13
+ const REQUEST_TIMEOUT_MS = 15_000;
14
+ const cache = new Map<string, CacheEntry>();
15
+ const requests = new Map<string, Promise<ClientStatusResult<unknown>>>();
16
+ const requestControllers = new Map<string, AbortController>();
17
+ const requestGenerations = new Map<string, number>();
18
+ let invalidationListenersInstalled = false;
19
+ let generation = 0;
20
+
21
+ function expireClientStatusCache(): void {
22
+ generation += 1;
23
+ cache.clear();
24
+ }
25
+
26
+ function installInvalidationListeners(): void {
27
+ if (
28
+ invalidationListenersInstalled ||
29
+ typeof window === "undefined" ||
30
+ typeof document === "undefined"
31
+ ) {
32
+ return;
33
+ }
34
+ invalidationListenersInstalled = true;
35
+
36
+ if (typeof window.addEventListener === "function") {
37
+ window.addEventListener("focus", expireClientStatusCache);
38
+ window.addEventListener(
39
+ "agent-engine:configured-changed",
40
+ invalidateClientStatusRequests,
41
+ );
42
+ }
43
+ if (typeof document.addEventListener === "function") {
44
+ document.addEventListener("visibilitychange", () => {
45
+ if (document.visibilityState === "visible") {
46
+ expireClientStatusCache();
47
+ }
48
+ });
49
+ }
50
+ }
51
+
52
+ async function fetchClientStatus<T>(
53
+ path: string,
54
+ ): Promise<ClientStatusResult<T>> {
55
+ installInvalidationListeners();
56
+ const url = agentNativePath(path);
57
+ const cached = cache.get(url);
58
+ if (cached && cached.expiresAt > Date.now()) {
59
+ return cached.result as ClientStatusResult<T>;
60
+ }
61
+ cache.delete(url);
62
+
63
+ const pending = requests.get(url);
64
+ if (pending) return pending as Promise<ClientStatusResult<T>>;
65
+
66
+ const requestGeneration = generation;
67
+ const requestUrlGeneration = requestGenerations.get(url) ?? 0;
68
+ const controller =
69
+ typeof AbortController === "undefined" ? null : new AbortController();
70
+ let timeoutId: ReturnType<typeof setTimeout> | undefined;
71
+ const timeout = new Promise<ClientStatusResult<unknown>>((resolve) => {
72
+ timeoutId = setTimeout(() => {
73
+ controller?.abort();
74
+ resolve({ state: "unavailable" });
75
+ }, REQUEST_TIMEOUT_MS);
76
+ });
77
+ const transport = fetch(url, {
78
+ cache: "no-store",
79
+ credentials: "same-origin",
80
+ ...(controller ? { signal: controller.signal } : {}),
81
+ })
82
+ .then(async (response): Promise<ClientStatusResult<unknown>> => {
83
+ if (!response.ok) {
84
+ return { state: "unavailable", status: response.status };
85
+ }
86
+ try {
87
+ return { state: "available", value: await response.json() };
88
+ } catch {
89
+ return { state: "unavailable", status: response.status };
90
+ }
91
+ })
92
+ .catch((): ClientStatusResult<unknown> => ({ state: "unavailable" }));
93
+ const request = Promise.race([transport, timeout])
94
+ .then((result) => {
95
+ if (
96
+ generation === requestGeneration &&
97
+ (requestGenerations.get(url) ?? 0) === requestUrlGeneration &&
98
+ result.state === "available"
99
+ ) {
100
+ cache.set(url, {
101
+ expiresAt: Date.now() + RESULT_TTL_MS,
102
+ result,
103
+ });
104
+ }
105
+ return result;
106
+ })
107
+ .finally(() => {
108
+ if (timeoutId !== undefined) clearTimeout(timeoutId);
109
+ if (requests.get(url) === request) requests.delete(url);
110
+ if (requestControllers.get(url) === controller) {
111
+ requestControllers.delete(url);
112
+ }
113
+ });
114
+
115
+ requests.set(url, request);
116
+ if (controller) requestControllers.set(url, controller);
117
+ return request as Promise<ClientStatusResult<T>>;
118
+ }
119
+
120
+ export function invalidateClientStatusRequest(path: string): void {
121
+ const url = agentNativePath(path);
122
+ requestGenerations.set(url, (requestGenerations.get(url) ?? 0) + 1);
123
+ cache.delete(url);
124
+ requestControllers.get(url)?.abort();
125
+ requestControllers.delete(url);
126
+ requests.delete(url);
127
+ }
128
+
129
+ export function invalidateClientStatusRequests(): void {
130
+ generation += 1;
131
+ cache.clear();
132
+ for (const controller of requestControllers.values()) {
133
+ controller.abort();
134
+ }
135
+ requestControllers.clear();
136
+ requests.clear();
137
+ }
138
+
139
+ export function fetchAgentEngineStatus<T = unknown>(): Promise<
140
+ ClientStatusResult<T>
141
+ > {
142
+ return fetchClientStatus<T>("/_agent-native/agent-engine/status");
143
+ }
144
+
145
+ export function fetchEnvironmentStatus<T = unknown>(): Promise<
146
+ ClientStatusResult<T>
147
+ > {
148
+ return fetchClientStatus<T>("/_agent-native/env-status");
149
+ }
150
+
151
+ export function fetchBuilderStatus<T = unknown>(): Promise<
152
+ ClientStatusResult<T>
153
+ > {
154
+ return fetchClientStatus<T>("/_agent-native/builder/status");
155
+ }
156
+
157
+ export function fetchAuthSessionStatus<T = unknown>(): Promise<
158
+ ClientStatusResult<T>
159
+ > {
160
+ return fetchClientStatus<T>("/_agent-native/auth/session");
161
+ }
@@ -627,8 +627,8 @@ export function VoiceTranscriptionSection() {
627
627
  id="openai"
628
628
  selected={transcriptionMode === "batch" && provider === "openai"}
629
629
  onSelect={() => chooseBatchProvider("openai")}
630
- title="OpenAI Whisper"
631
- subtitle="Batch Whisper provider. Requires an OpenAI API key."
630
+ title="OpenAI Transcribe"
631
+ subtitle="Batch transcription provider. Requires an OpenAI API key."
632
632
  rightSlot={
633
633
  openAiConfigured === null ? null : openAiConfigured ? (
634
634
  <span className="flex items-center gap-1 text-[10px] text-green-500">
@@ -34,6 +34,13 @@ export type ContentPart =
34
34
  args: Record<string, string>;
35
35
  result?: string;
36
36
  isError?: boolean;
37
+ /**
38
+ * Set when the stream ended while this tool was still in flight. We know
39
+ * it started and NOT whether its side effect landed, so it is deliberately
40
+ * separate from `isError` — an email that WAS delivered must never render
41
+ * as a failure just because the transport dropped before `tool_done`.
42
+ */
43
+ outcome?: "unknown";
37
44
  completedSideEffect?: boolean;
38
45
  mcpApp?: AgentMcpAppPayload;
39
46
  chatUI?: ActionChatUIConfig;
@@ -155,7 +162,10 @@ export function settleInterruptedToolCalls(
155
162
  part.activity === true
156
163
  ? (options?.activityResult ?? INTERRUPTED_ACTIVITY_RESULT)
157
164
  : result;
158
- part.isError = true;
165
+ // Interrupted is not failed: the side effect may well have landed. Never
166
+ // set `isError` here — that is reserved for a result the server told us
167
+ // failed.
168
+ part.outcome = "unknown";
159
169
  changed = true;
160
170
  }
161
171
  }
@@ -1029,7 +1039,8 @@ function completedToolNamesAfterLastAssistantText(
1029
1039
  part.type === "tool-call" &&
1030
1040
  part.activity !== true &&
1031
1041
  part.result !== undefined &&
1032
- part.isError !== true
1042
+ part.isError !== true &&
1043
+ part.outcome !== "unknown"
1033
1044
  ) {
1034
1045
  names.add(part.toolName);
1035
1046
  }
@@ -1053,7 +1064,8 @@ function hasCompletedCustomUi(content: ContentPart[]): boolean {
1053
1064
  part?.type !== "tool-call" ||
1054
1065
  part.activity === true ||
1055
1066
  part.result === undefined ||
1056
- part.isError === true
1067
+ part.isError === true ||
1068
+ part.outcome === "unknown"
1057
1069
  ) {
1058
1070
  continue;
1059
1071
  }
@@ -1,7 +1,13 @@
1
1
  import { useEffect, useState } from "react";
2
2
 
3
3
  import { PROVIDER_ENV_VARS } from "../agent/engine/provider-env-vars.js";
4
- import { agentNativePath } from "./api-path.js";
4
+ import {
5
+ fetchAgentEngineStatus,
6
+ fetchBuilderStatus,
7
+ fetchEnvironmentStatus,
8
+ invalidateClientStatusRequest,
9
+ type ClientStatusResult,
10
+ } from "./client-status-requests.js";
5
11
 
6
12
  const PROVIDER_ENV_VAR_SET = new Set(PROVIDER_ENV_VARS);
7
13
 
@@ -39,43 +45,32 @@ export interface UseAgentEngineConfiguredOptions {
39
45
  threadId?: string | null;
40
46
  }
41
47
 
42
- // Abort ceiling for a hung request, NOT a deadline the probes race against.
43
- // `/agent-engine/status` resolves session, org context, a scoped secret and a
44
- // settings row — several DB round-trips that measured ~4.7s on a warm local
45
- // dev server, so any budget short enough to "feel fast" just manufactures a
46
- // fake answer. Losing this ceiling is treated as "don't know", never "missing".
47
- const DEFAULT_STATUS_CHECK_TIMEOUT_MS = 15000;
48
48
  const RETRY_BASE_MS = 2000;
49
49
  const RETRY_MAX_MS = 30000;
50
50
 
51
- async function fetchStatusJson(
51
+ async function waitForStatus<T>(
52
+ request: Promise<ClientStatusResult<T>>,
52
53
  path: string,
53
- timeoutMs: number,
54
- ): Promise<unknown | null> {
55
- const controller =
56
- typeof AbortController !== "undefined" ? new AbortController() : null;
54
+ timeoutMs: number | undefined,
55
+ ): Promise<ClientStatusResult<T>> {
56
+ if (timeoutMs === undefined) return request;
57
+
57
58
  let timeoutId: ReturnType<typeof setTimeout> | undefined;
58
- const timeout = new Promise<null>((resolve) => {
59
+ const timeout = new Promise<ClientStatusResult<T>>((resolve) => {
59
60
  timeoutId = setTimeout(() => {
60
- controller?.abort();
61
- resolve(null);
61
+ // A request that loses this race may never settle. Evict and abort the
62
+ // shared probe so the scheduled retry starts a genuinely new request.
63
+ invalidateClientStatusRequest(path);
64
+ resolve({ state: "unavailable" });
62
65
  }, timeoutMs);
63
66
  });
64
-
65
- // Never serve a stale status from the HTTP cache: this is re-fetched right
66
- // after a provider connects, and a cached "missing" would keep the composer
67
- // gate and error banner pinned even though a provider is now configured.
68
- const request = fetch(agentNativePath(path), {
69
- cache: "no-store",
70
- ...(controller ? { signal: controller.signal } : {}),
71
- })
72
- .then((r) => (r.ok ? r.json() : null))
73
- .catch(() => null)
74
- .finally(() => {
75
- if (timeoutId !== undefined) clearTimeout(timeoutId);
76
- });
77
-
78
- return Promise.race([request, timeout]);
67
+ try {
68
+ return await Promise.race([request, timeout]);
69
+ } finally {
70
+ if (timeoutId !== undefined) {
71
+ clearTimeout(timeoutId);
72
+ }
73
+ }
79
74
  }
80
75
 
81
76
  function hasConfiguredFlag(value: unknown): value is { configured: boolean } {
@@ -117,22 +112,38 @@ export async function fetchAgentEngineConfiguredState(
117
112
  const timeoutMs =
118
113
  typeof options?.timeoutMs === "number" && options.timeoutMs > 0
119
114
  ? options.timeoutMs
120
- : DEFAULT_STATUS_CHECK_TIMEOUT_MS;
121
- const [envKeys, builderStatus, engineStatus] = await Promise.all([
122
- fetchStatusJson("/_agent-native/env-status", timeoutMs),
123
- fetchStatusJson("/_agent-native/builder/status", timeoutMs),
124
- fetchStatusJson("/_agent-native/agent-engine/status", timeoutMs),
125
- ]);
126
-
127
- // All three failed — surface a retryable unavailable state instead of
128
- // leaving the composer in an infinite checking loop.
129
- if (envKeys == null && builderStatus == null && engineStatus == null) {
130
- return "unavailable";
115
+ : undefined;
116
+ const engineResult = await waitForStatus(
117
+ fetchAgentEngineStatus(),
118
+ "/_agent-native/agent-engine/status",
119
+ timeoutMs,
120
+ );
121
+ if (
122
+ engineResult.state === "available" &&
123
+ hasConfiguredFlag(engineResult.value)
124
+ ) {
125
+ return engineResult.value.configured ? "configured" : "missing";
131
126
  }
132
127
 
128
+ // Older hosts may not expose the canonical route. Only then pay for the two
129
+ // legacy probes; current hosts answer readiness with one request.
130
+ const [envResult, builderResult] = await Promise.all([
131
+ waitForStatus(
132
+ fetchEnvironmentStatus(),
133
+ "/_agent-native/env-status",
134
+ timeoutMs,
135
+ ),
136
+ waitForStatus(
137
+ fetchBuilderStatus(),
138
+ "/_agent-native/builder/status",
139
+ timeoutMs,
140
+ ),
141
+ ]);
142
+ const envKeys = envResult.state === "available" ? envResult.value : undefined;
143
+ const builderStatus =
144
+ builderResult.state === "available" ? builderResult.value : undefined;
133
145
  const envKeysKnown = Array.isArray(envKeys);
134
146
  const builderStatusKnown = hasConfiguredFlag(builderStatus);
135
- const engineStatusKnown = hasConfiguredFlag(engineStatus);
136
147
  const keys = envKeysKnown
137
148
  ? (envKeys as Array<{
138
149
  key: string;
@@ -142,16 +153,9 @@ export async function fetchAgentEngineConfiguredState(
142
153
  const llmKeys = keys.filter((k) => PROVIDER_ENV_VAR_SET.has(k.key));
143
154
  const anyConfigured =
144
155
  llmKeys.some((k) => k.configured) ||
145
- (builderStatusKnown && builderStatus.configured) ||
146
- (engineStatusKnown && engineStatus.configured);
156
+ (builderStatusKnown && builderStatus.configured);
147
157
  if (anyConfigured) return "configured";
148
158
 
149
- // The engine status route is the canonical readiness check: it resolves
150
- // Builder, scoped BYOK secrets, deployment credentials, and custom engines.
151
- // Once it has answered `configured: false`, a slow legacy env/Builder status
152
- // request must not leave the composer permissively stuck in `unknown`.
153
- if (engineStatusKnown) return "missing";
154
-
155
159
  // Compatibility fallback for older hosts without the canonical route.
156
160
  return envKeysKnown && builderStatusKnown ? "missing" : "unavailable";
157
161
  }
@@ -7,11 +7,14 @@ import {
7
7
  resolveReasoningEffortSelection,
8
8
  type ReasoningEffort,
9
9
  } from "../shared/reasoning-effort.js";
10
- import { agentNativePath } from "./api-path.js";
11
10
  import {
12
11
  buildChatModelGroups,
13
12
  type EngineModelGroup,
14
13
  } from "./chat-model-groups.js";
14
+ import {
15
+ fetchBuilderStatus,
16
+ fetchEnvironmentStatus,
17
+ } from "./client-status-requests.js";
15
18
  import { callAction } from "./use-action.js";
16
19
 
17
20
  export type { EngineModelGroup } from "./chat-model-groups.js";
@@ -192,14 +195,10 @@ export function useChatModels({
192
195
  callAction("manage-agent-engine" as any, { action: "list" } as any).catch(
193
196
  () => null,
194
197
  ),
195
- fetch(agentNativePath("/_agent-native/env-status"))
196
- .then((r) => (r.ok ? r.json() : []))
197
- .catch(() => []),
198
- fetch(agentNativePath("/_agent-native/builder/status"))
199
- .then((r) => (r.ok ? r.json() : null))
200
- .catch(() => null),
198
+ fetchEnvironmentStatus<Array<{ key: string; configured: boolean }>>(),
199
+ fetchBuilderStatus<{ configured?: boolean }>(),
201
200
  ])
202
- .then(([enginesData, envKeys, builderStatus]) => {
201
+ .then(([enginesData, envResult, builderResult]) => {
203
202
  if (!enginesData?.engines) {
204
203
  // Without a catalog the picker keeps an unvalidated DEFAULT_MODEL,
205
204
  // which is indistinguishable from a real selection unless we say so.
@@ -208,10 +207,16 @@ export function useChatModels({
208
207
  );
209
208
  return;
210
209
  }
210
+ if (
211
+ envResult.state !== "available" ||
212
+ builderResult.state !== "available"
213
+ ) {
214
+ return;
215
+ }
216
+ const envKeys = envResult.value;
217
+ const builderStatus = builderResult.value;
211
218
  const configuredKeys = new Set(
212
- (envKeys as Array<{ key: string; configured: boolean }>)
213
- .filter((k) => k.configured)
214
- .map((k) => k.key),
219
+ envKeys.filter((k) => k.configured).map((k) => k.key),
215
220
  );
216
221
  const builderConnected = builderStatus?.configured === true;
217
222
  const currentEngineName: string | undefined =
@@ -78,6 +78,35 @@ const ACTIVE_THREAD_KEY = "agent-chat-active-thread";
78
78
  const THREADS_UPDATED_EVENT = "agent-chat:threads-updated";
79
79
  const THREADS_PAGE_SIZE = 50;
80
80
 
81
+ const sharedThreadListRequests = new Map<
82
+ string,
83
+ Promise<ChatThreadSummary[] | undefined>
84
+ >();
85
+
86
+ async function fetchSharedThreadListPage(
87
+ apiUrl: string,
88
+ offset: number,
89
+ ): Promise<ChatThreadSummary[] | undefined> {
90
+ const requestKey = `${apiUrl}\n${offset}`;
91
+ const pending = sharedThreadListRequests.get(requestKey);
92
+ if (pending) return pending;
93
+
94
+ const request = fetch(
95
+ offset > 0 ? `${apiUrl}/threads?offset=${offset}` : `${apiUrl}/threads`,
96
+ )
97
+ .then(async (res) => {
98
+ if (!res.ok) return undefined;
99
+ const data = await res.json();
100
+ const threads = (data.threads ?? []) as ChatThreadSummary[];
101
+ return threads;
102
+ })
103
+ .finally(() => {
104
+ sharedThreadListRequests.delete(requestKey);
105
+ });
106
+ sharedThreadListRequests.set(requestKey, request);
107
+ return request;
108
+ }
109
+
81
110
  function emitThreadsUpdated() {
82
111
  if (typeof window === "undefined") return;
83
112
  window.dispatchEvent(new CustomEvent(THREADS_UPDATED_EVENT));
@@ -441,20 +470,14 @@ export function useChatThreads(
441
470
  async (options?: { append?: boolean }) => {
442
471
  try {
443
472
  const offset = options?.append ? nextThreadsOffsetRef.current : 0;
444
- const res = await fetch(
445
- offset > 0
446
- ? `${apiUrl}/threads?offset=${offset}`
447
- : `${apiUrl}/threads`,
448
- );
449
- if (!res.ok) {
473
+ const loaded = await fetchSharedThreadListPage(apiUrl, offset);
474
+ if (!loaded) {
450
475
  if (!options?.append) {
451
476
  setThreadsLoadError("Could not load chat history.");
452
477
  }
453
478
  return;
454
479
  }
455
- const data = await res.json();
456
480
  setThreadsLoadError(null);
457
- const loaded = (data.threads ?? []) as ChatThreadSummary[];
458
481
  if (!options?.append) {
459
482
  nextThreadsOffsetRef.current = loaded.length;
460
483
  } else {
@@ -526,7 +549,7 @@ export function useChatThreads(
526
549
  }
527
550
  return [...optimisticOnly, ...merged];
528
551
  });
529
- return data.threads as ChatThreadSummary[];
552
+ return loaded;
530
553
  } catch {
531
554
  if (!options?.append) {
532
555
  setThreadsLoadError("Could not load chat history.");
@@ -2,7 +2,7 @@ import { useEffect, useState } from "react";
2
2
 
3
3
  import type { AuthSession } from "../server/auth.js";
4
4
  import { setSentryUser, trackSessionStatus } from "./analytics.js";
5
- import { agentNativePath } from "./api-path.js";
5
+ import { fetchAuthSessionStatus } from "./client-status-requests.js";
6
6
 
7
7
  export type { AuthSession };
8
8
 
@@ -51,10 +51,9 @@ function fetchSharedSession(): Promise<AuthSession | null | undefined> {
51
51
 
52
52
  sessionRequest = (async () => {
53
53
  try {
54
- const res = await fetch(agentNativePath("/_agent-native/auth/session"));
55
- if (!res.ok) return undefined;
56
-
57
- const data = await res.json();
54
+ const result = await fetchAuthSessionStatus();
55
+ if (result.state === "unavailable") return undefined;
56
+ const data = result.value as AuthSession & { error?: unknown };
58
57
  const session = data.error ? null : (data as AuthSession);
59
58
  cachedSession = session;
60
59
  cachedSessionAt = Date.now();