@agent-native/core 0.95.0 → 0.96.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 (316) hide show
  1. package/corpus/README.md +2 -2
  2. package/corpus/core/CHANGELOG.md +35 -0
  3. package/corpus/core/docs/content/locales/ar-SA/template-design.mdx +13 -0
  4. package/corpus/core/docs/content/locales/de-DE/template-design.mdx +18 -0
  5. package/corpus/core/docs/content/locales/es-ES/template-design.mdx +16 -0
  6. package/corpus/core/docs/content/locales/fr-FR/template-design.mdx +17 -0
  7. package/corpus/core/docs/content/locales/hi-IN/template-design.mdx +16 -0
  8. package/corpus/core/docs/content/locales/ja-JP/template-design.mdx +17 -0
  9. package/corpus/core/docs/content/locales/ko-KR/template-design.mdx +15 -0
  10. package/corpus/core/docs/content/locales/pt-BR/template-design.mdx +16 -0
  11. package/corpus/core/docs/content/locales/zh-CN/template-design.mdx +11 -0
  12. package/corpus/core/docs/content/locales/zh-TW/template-design.mdx +12 -0
  13. package/corpus/core/docs/content/template-design.mdx +16 -0
  14. package/corpus/core/package.json +4 -2
  15. package/corpus/core/src/a2a/client.ts +11 -1
  16. package/corpus/core/src/agent/production-agent.ts +107 -27
  17. package/corpus/core/src/agent/thread-data-builder.ts +48 -22
  18. package/corpus/core/src/chat-threads/store.ts +20 -1
  19. package/corpus/core/src/cli/code-agent-executor.ts +1 -0
  20. package/corpus/core/src/cli/code-agent-runs.ts +14 -0
  21. package/corpus/core/src/client/AgentPanel.tsx +32 -25
  22. package/corpus/core/src/client/AssistantChat.tsx +286 -45
  23. package/corpus/core/src/client/MultiTabAssistantChat.tsx +141 -90
  24. package/corpus/core/src/client/chat/ChatHistoryList.tsx +404 -0
  25. package/corpus/core/src/client/chat/index.ts +6 -0
  26. package/corpus/core/src/client/chat/tool-call-display.tsx +38 -4
  27. package/corpus/core/src/client/chat/use-reconnect-reader-owner.ts +28 -0
  28. package/corpus/core/src/client/code-agent-chat-adapter.ts +72 -4
  29. package/corpus/core/src/client/composer/RealtimeVoiceMode.tsx +52 -11
  30. package/corpus/core/src/client/composer/TiptapComposer.tsx +157 -20
  31. package/corpus/core/src/client/composer/VoiceButton.tsx +28 -0
  32. package/corpus/core/src/client/composer/useRealtimeVoiceMode.tsx +5 -0
  33. package/corpus/core/src/client/conversation/code-agent-transcript.ts +4 -5
  34. package/corpus/core/src/client/guided-questions.tsx +11 -2
  35. package/corpus/core/src/client/index.ts +11 -0
  36. package/corpus/core/src/client/integrations/IntegrationsPanel.tsx +6 -7
  37. package/corpus/core/src/client/sse-event-processor.ts +90 -21
  38. package/corpus/core/src/client/use-chat-threads.ts +10 -1
  39. package/corpus/core/src/client/voice-provider-status.ts +44 -0
  40. package/corpus/core/src/code-agents/transcript-normalizer.ts +76 -0
  41. package/corpus/core/src/connections/catalog.ts +2 -2
  42. package/corpus/core/src/deploy/build.ts +12 -8
  43. package/corpus/core/src/integrations/a2a-continuation-processor.ts +35 -5
  44. package/corpus/core/src/integrations/adapters/slack.ts +74 -21
  45. package/corpus/core/src/integrations/catalog.ts +2 -2
  46. package/corpus/core/src/integrations/webhook-handler.ts +14 -0
  47. package/corpus/core/src/localization/default-messages.ts +6 -0
  48. package/corpus/core/src/observability/hosted-model-experiment.ts +118 -0
  49. package/corpus/core/src/observability/routes.ts +17 -0
  50. package/corpus/core/src/observability/store.ts +4 -1
  51. package/corpus/core/src/observability/traces.ts +41 -2
  52. package/corpus/core/src/scripts/agent-engines/list-agent-engines.ts +19 -1
  53. package/corpus/core/src/scripts/call-agent.ts +53 -3
  54. package/corpus/core/src/scripts/chat/search-chats.ts +11 -8
  55. package/corpus/core/src/secrets/register-framework-secrets.ts +1 -1
  56. package/corpus/core/src/server/agent-chat-plugin.ts +41 -6
  57. package/corpus/core/src/server/auth.ts +1 -1
  58. package/corpus/core/src/server/core-routes-plugin.ts +15 -0
  59. package/corpus/core/src/server/credential-provider.ts +7 -3
  60. package/corpus/core/src/server/realtime-voice.ts +71 -24
  61. package/corpus/core/src/server/voice-providers-status.ts +4 -3
  62. package/corpus/core/src/shared/cache-control.ts +4 -4
  63. package/corpus/core/src/styles/agent-native.css +1 -0
  64. package/corpus/core/src/styles/chat-history-list.css +313 -0
  65. package/corpus/core/src/templates/workspace-core/.agents/skills/observability/SKILL.md +20 -0
  66. package/corpus/core/src/templates/workspace-core/.agents/skills/voice-transcription/SKILL.md +8 -4
  67. package/corpus/core/src/vite/client.ts +1 -0
  68. package/corpus/templates/analytics/AGENTS.md +7 -0
  69. package/corpus/templates/analytics/actions/bigquery.ts +26 -2
  70. package/corpus/templates/analytics/actions/data-source-status.ts +18 -0
  71. package/corpus/templates/analytics/app/components/layout/Sidebar.tsx +5 -4
  72. package/corpus/templates/analytics/app/global.css +10 -0
  73. package/corpus/templates/analytics/app/lib/data-sources.ts +5 -4
  74. package/corpus/templates/analytics/app/pages/Ask.tsx +25 -2
  75. package/corpus/templates/analytics/changelog/2026-07-10-analytics-answers-simple-time-bounded-metrics-without-unnece.md +6 -0
  76. package/corpus/templates/analytics/changelog/2026-07-10-analytics-chat-reliably-uses-connected-data-sources-across-s.md +6 -0
  77. package/corpus/templates/analytics/changelog/2026-07-10-chat-threads-now-stay-clear-of-top-actions-and-fade-smoothly.md +6 -0
  78. package/corpus/templates/analytics/changelog/2026-07-10-context-chips-can-now-be-selected-and-removed-one-at-a-time-.md +6 -0
  79. package/corpus/templates/analytics/changelog/2026-07-10-slack-token-setup-marked-legacy.md +6 -0
  80. package/corpus/templates/analytics/changelog/2026-07-10-the-ask-composer-no-longer-carries-dashboard-or-panel-contex.md +6 -0
  81. package/corpus/templates/analytics/changelog/2026-07-10-the-ask-section-stays-highlighted-while-its-chat-history-is-.md +6 -0
  82. package/corpus/templates/analytics/server/lib/bigquery.ts +90 -14
  83. package/corpus/templates/analytics/server/lib/credential-keys.ts +6 -2
  84. package/corpus/templates/analytics/server/plugins/agent-chat.ts +82 -7
  85. package/corpus/templates/brain/changelog/2026-07-10-slack-backfill-token-marked-legacy.md +6 -0
  86. package/corpus/templates/brain/server/register-secrets.ts +2 -2
  87. package/corpus/templates/clips/app/components/library/library-layout.tsx +7 -1
  88. package/corpus/templates/clips/app/components/library/page-header.tsx +19 -3
  89. package/corpus/templates/clips/app/routes/_app.library._index.tsx +16 -12
  90. package/corpus/templates/clips/changelog/2026-07-10-new-recording-appears-once-when-the-library-sidebar-is-open.md +6 -0
  91. package/corpus/templates/clips/changelog/2026-07-10-slack-token-fallback-guidance.md +6 -0
  92. package/corpus/templates/clips/server/register-secrets.ts +1 -1
  93. package/corpus/templates/content/app/components/editor/DocumentEditor.tsx +0 -66
  94. package/corpus/templates/content/app/routes/_app.local-files.tsx +7 -47
  95. package/corpus/templates/content/changelog/2026-07-10-local-folders-now-wait-for-an-explicit-pull-before-content-r.md +6 -0
  96. package/corpus/templates/content/package.json +1 -1
  97. package/corpus/templates/design/actions/generate-design.ts +128 -31
  98. package/corpus/templates/design/actions/present-design-variants.ts +37 -9
  99. package/corpus/templates/design/app/components/design/DesignCanvas.tsx +9 -3
  100. package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +32 -10
  101. package/corpus/templates/design/app/components/design/QuestionFlow.tsx +2 -2
  102. package/corpus/templates/design/app/components/design/TweaksPanel.tsx +9 -1
  103. package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +7 -5
  104. package/corpus/templates/design/app/components/design/multi-screen/types.ts +3 -0
  105. package/corpus/templates/design/app/global.css +10 -0
  106. package/corpus/templates/design/app/hooks/use-question-flow.ts +9 -2
  107. package/corpus/templates/design/app/i18n/zh-TW.ts +1 -0
  108. package/corpus/templates/design/app/i18n-data.ts +21 -11
  109. package/corpus/templates/design/changelog/2026-07-10-editor-panels-are-easier-to-distinguish-and-design-intake-qu.md +6 -0
  110. package/corpus/templates/design/changelog/2026-07-10-interact-mode-now-stays-on-the-full-canvas-matches-edit-visu.md +6 -0
  111. package/corpus/templates/design/changelog/2026-07-10-new-web-designs-open-at-desktop-size-and-adapt-across-mobile.md +6 -0
  112. package/corpus/templates/design/changelog/2026-07-10-tweaks-now-link-to-full-documentation.md +6 -0
  113. package/corpus/templates/design/server/plugins/agent-chat.ts +2 -0
  114. package/corpus/templates/mail/app/i18n/ar-SA.ts +3 -3
  115. package/corpus/templates/mail/app/i18n/de-DE.ts +3 -3
  116. package/corpus/templates/mail/app/i18n/en-US.ts +4 -3
  117. package/corpus/templates/mail/app/i18n/es-ES.ts +3 -3
  118. package/corpus/templates/mail/app/i18n/fr-FR.ts +3 -3
  119. package/corpus/templates/mail/app/i18n/hi-IN.ts +3 -3
  120. package/corpus/templates/mail/app/i18n/ja-JP.ts +3 -3
  121. package/corpus/templates/mail/app/i18n/ko-KR.ts +3 -3
  122. package/corpus/templates/mail/app/i18n/pt-BR.ts +3 -3
  123. package/corpus/templates/mail/app/i18n/zh-CN.ts +3 -3
  124. package/corpus/templates/mail/app/i18n/zh-TW.ts +3 -3
  125. package/corpus/templates/mail/changelog/2026-07-10-slack-intake-marked-legacy.md +6 -0
  126. package/corpus/templates/mail/server/lib/env-config.ts +5 -4
  127. package/corpus/templates/plan/package.json +1 -1
  128. package/dist/a2a/client.d.ts +7 -0
  129. package/dist/a2a/client.d.ts.map +1 -1
  130. package/dist/a2a/client.js +2 -1
  131. package/dist/a2a/client.js.map +1 -1
  132. package/dist/agent/production-agent.d.ts +6 -0
  133. package/dist/agent/production-agent.d.ts.map +1 -1
  134. package/dist/agent/production-agent.js +83 -26
  135. package/dist/agent/production-agent.js.map +1 -1
  136. package/dist/agent/thread-data-builder.d.ts +1 -0
  137. package/dist/agent/thread-data-builder.d.ts.map +1 -1
  138. package/dist/agent/thread-data-builder.js +42 -23
  139. package/dist/agent/thread-data-builder.js.map +1 -1
  140. package/dist/chat-threads/store.d.ts +10 -0
  141. package/dist/chat-threads/store.d.ts.map +1 -1
  142. package/dist/chat-threads/store.js +6 -0
  143. package/dist/chat-threads/store.js.map +1 -1
  144. package/dist/cli/code-agent-executor.d.ts.map +1 -1
  145. package/dist/cli/code-agent-executor.js +1 -0
  146. package/dist/cli/code-agent-executor.js.map +1 -1
  147. package/dist/cli/code-agent-runs.d.ts +12 -0
  148. package/dist/cli/code-agent-runs.d.ts.map +1 -1
  149. package/dist/cli/code-agent-runs.js +1 -0
  150. package/dist/cli/code-agent-runs.js.map +1 -1
  151. package/dist/client/AgentPanel.d.ts.map +1 -1
  152. package/dist/client/AgentPanel.js +3 -3
  153. package/dist/client/AgentPanel.js.map +1 -1
  154. package/dist/client/AssistantChat.d.ts +14 -1
  155. package/dist/client/AssistantChat.d.ts.map +1 -1
  156. package/dist/client/AssistantChat.js +207 -32
  157. package/dist/client/AssistantChat.js.map +1 -1
  158. package/dist/client/MultiTabAssistantChat.d.ts.map +1 -1
  159. package/dist/client/MultiTabAssistantChat.js +83 -10
  160. package/dist/client/MultiTabAssistantChat.js.map +1 -1
  161. package/dist/client/chat/ChatHistoryList.d.ts +88 -0
  162. package/dist/client/chat/ChatHistoryList.d.ts.map +1 -0
  163. package/dist/client/chat/ChatHistoryList.js +83 -0
  164. package/dist/client/chat/ChatHistoryList.js.map +1 -0
  165. package/dist/client/chat/index.d.ts +1 -0
  166. package/dist/client/chat/index.d.ts.map +1 -1
  167. package/dist/client/chat/index.js +1 -0
  168. package/dist/client/chat/index.js.map +1 -1
  169. package/dist/client/chat/tool-call-display.d.ts +13 -1
  170. package/dist/client/chat/tool-call-display.d.ts.map +1 -1
  171. package/dist/client/chat/tool-call-display.js +11 -3
  172. package/dist/client/chat/tool-call-display.js.map +1 -1
  173. package/dist/client/chat/use-reconnect-reader-owner.d.ts +11 -0
  174. package/dist/client/chat/use-reconnect-reader-owner.d.ts.map +1 -0
  175. package/dist/client/chat/use-reconnect-reader-owner.js +21 -0
  176. package/dist/client/chat/use-reconnect-reader-owner.js.map +1 -0
  177. package/dist/client/code-agent-chat-adapter.d.ts +17 -1
  178. package/dist/client/code-agent-chat-adapter.d.ts.map +1 -1
  179. package/dist/client/code-agent-chat-adapter.js +47 -3
  180. package/dist/client/code-agent-chat-adapter.js.map +1 -1
  181. package/dist/client/composer/RealtimeVoiceMode.d.ts +11 -1
  182. package/dist/client/composer/RealtimeVoiceMode.d.ts.map +1 -1
  183. package/dist/client/composer/RealtimeVoiceMode.js +5 -3
  184. package/dist/client/composer/RealtimeVoiceMode.js.map +1 -1
  185. package/dist/client/composer/TiptapComposer.d.ts +25 -2
  186. package/dist/client/composer/TiptapComposer.d.ts.map +1 -1
  187. package/dist/client/composer/TiptapComposer.js +96 -5
  188. package/dist/client/composer/TiptapComposer.js.map +1 -1
  189. package/dist/client/composer/VoiceButton.d.ts.map +1 -1
  190. package/dist/client/composer/VoiceButton.js +21 -1
  191. package/dist/client/composer/VoiceButton.js.map +1 -1
  192. package/dist/client/composer/useRealtimeVoiceMode.d.ts.map +1 -1
  193. package/dist/client/composer/useRealtimeVoiceMode.js +5 -0
  194. package/dist/client/composer/useRealtimeVoiceMode.js.map +1 -1
  195. package/dist/client/conversation/code-agent-transcript.d.ts +2 -0
  196. package/dist/client/conversation/code-agent-transcript.d.ts.map +1 -1
  197. package/dist/client/conversation/code-agent-transcript.js +3 -5
  198. package/dist/client/conversation/code-agent-transcript.js.map +1 -1
  199. package/dist/client/guided-questions.d.ts.map +1 -1
  200. package/dist/client/guided-questions.js +7 -2
  201. package/dist/client/guided-questions.js.map +1 -1
  202. package/dist/client/index.d.ts +3 -1
  203. package/dist/client/index.d.ts.map +1 -1
  204. package/dist/client/index.js +3 -1
  205. package/dist/client/index.js.map +1 -1
  206. package/dist/client/integrations/IntegrationsPanel.d.ts.map +1 -1
  207. package/dist/client/integrations/IntegrationsPanel.js +5 -7
  208. package/dist/client/integrations/IntegrationsPanel.js.map +1 -1
  209. package/dist/client/sse-event-processor.d.ts.map +1 -1
  210. package/dist/client/sse-event-processor.js +67 -20
  211. package/dist/client/sse-event-processor.js.map +1 -1
  212. package/dist/client/use-chat-threads.d.ts.map +1 -1
  213. package/dist/client/use-chat-threads.js +9 -1
  214. package/dist/client/use-chat-threads.js.map +1 -1
  215. package/dist/client/voice-provider-status.d.ts +10 -0
  216. package/dist/client/voice-provider-status.d.ts.map +1 -0
  217. package/dist/client/voice-provider-status.js +32 -0
  218. package/dist/client/voice-provider-status.js.map +1 -0
  219. package/dist/code-agents/transcript-normalizer.d.ts +29 -0
  220. package/dist/code-agents/transcript-normalizer.d.ts.map +1 -1
  221. package/dist/code-agents/transcript-normalizer.js +55 -0
  222. package/dist/code-agents/transcript-normalizer.js.map +1 -1
  223. package/dist/collab/struct-routes.d.ts +1 -1
  224. package/dist/connections/catalog.d.ts +2 -2
  225. package/dist/connections/catalog.js +2 -2
  226. package/dist/connections/catalog.js.map +1 -1
  227. package/dist/deploy/build.d.ts.map +1 -1
  228. package/dist/deploy/build.js +12 -8
  229. package/dist/deploy/build.js.map +1 -1
  230. package/dist/integrations/a2a-continuation-processor.d.ts.map +1 -1
  231. package/dist/integrations/a2a-continuation-processor.js +25 -5
  232. package/dist/integrations/a2a-continuation-processor.js.map +1 -1
  233. package/dist/integrations/adapters/slack.d.ts.map +1 -1
  234. package/dist/integrations/adapters/slack.js +55 -21
  235. package/dist/integrations/adapters/slack.js.map +1 -1
  236. package/dist/integrations/catalog.js +2 -2
  237. package/dist/integrations/catalog.js.map +1 -1
  238. package/dist/integrations/webhook-handler.d.ts.map +1 -1
  239. package/dist/integrations/webhook-handler.js +11 -0
  240. package/dist/integrations/webhook-handler.js.map +1 -1
  241. package/dist/localization/default-messages.d.ts +5 -0
  242. package/dist/localization/default-messages.d.ts.map +1 -1
  243. package/dist/localization/default-messages.js +5 -0
  244. package/dist/localization/default-messages.js.map +1 -1
  245. package/dist/observability/hosted-model-experiment.d.ts +39 -0
  246. package/dist/observability/hosted-model-experiment.d.ts.map +1 -0
  247. package/dist/observability/hosted-model-experiment.js +90 -0
  248. package/dist/observability/hosted-model-experiment.js.map +1 -0
  249. package/dist/observability/routes.d.ts.map +1 -1
  250. package/dist/observability/routes.js +16 -0
  251. package/dist/observability/routes.js.map +1 -1
  252. package/dist/observability/store.d.ts.map +1 -1
  253. package/dist/observability/store.js +4 -1
  254. package/dist/observability/store.js.map +1 -1
  255. package/dist/observability/traces.d.ts +6 -0
  256. package/dist/observability/traces.d.ts.map +1 -1
  257. package/dist/observability/traces.js +28 -2
  258. package/dist/observability/traces.js.map +1 -1
  259. package/dist/provider-api/corpus-jobs.d.ts +2 -2
  260. package/dist/scripts/agent-engines/list-agent-engines.d.ts.map +1 -1
  261. package/dist/scripts/agent-engines/list-agent-engines.js +19 -1
  262. package/dist/scripts/agent-engines/list-agent-engines.js.map +1 -1
  263. package/dist/scripts/call-agent.d.ts.map +1 -1
  264. package/dist/scripts/call-agent.js +43 -3
  265. package/dist/scripts/call-agent.js.map +1 -1
  266. package/dist/scripts/chat/search-chats.d.ts +1 -1
  267. package/dist/scripts/chat/search-chats.d.ts.map +1 -1
  268. package/dist/scripts/chat/search-chats.js +11 -8
  269. package/dist/scripts/chat/search-chats.js.map +1 -1
  270. package/dist/secrets/register-framework-secrets.js +1 -1
  271. package/dist/secrets/register-framework-secrets.js.map +1 -1
  272. package/dist/server/agent-chat-plugin.d.ts +11 -0
  273. package/dist/server/agent-chat-plugin.d.ts.map +1 -1
  274. package/dist/server/agent-chat-plugin.js +27 -6
  275. package/dist/server/agent-chat-plugin.js.map +1 -1
  276. package/dist/server/auth.js +1 -1
  277. package/dist/server/auth.js.map +1 -1
  278. package/dist/server/core-routes-plugin.d.ts +3 -0
  279. package/dist/server/core-routes-plugin.d.ts.map +1 -1
  280. package/dist/server/core-routes-plugin.js +13 -0
  281. package/dist/server/core-routes-plugin.js.map +1 -1
  282. package/dist/server/credential-provider.d.ts +5 -3
  283. package/dist/server/credential-provider.d.ts.map +1 -1
  284. package/dist/server/credential-provider.js +7 -3
  285. package/dist/server/credential-provider.js.map +1 -1
  286. package/dist/server/realtime-voice.d.ts.map +1 -1
  287. package/dist/server/realtime-voice.js +55 -21
  288. package/dist/server/realtime-voice.js.map +1 -1
  289. package/dist/server/voice-providers-status.d.ts.map +1 -1
  290. package/dist/server/voice-providers-status.js +2 -3
  291. package/dist/server/voice-providers-status.js.map +1 -1
  292. package/dist/shared/cache-control.d.ts +6 -6
  293. package/dist/shared/cache-control.d.ts.map +1 -1
  294. package/dist/shared/cache-control.js +3 -3
  295. package/dist/shared/cache-control.js.map +1 -1
  296. package/dist/styles/agent-native.css +1 -0
  297. package/dist/styles/chat-history-list.css +313 -0
  298. package/dist/templates/workspace-core/.agents/skills/observability/SKILL.md +20 -0
  299. package/dist/templates/workspace-core/.agents/skills/voice-transcription/SKILL.md +8 -4
  300. package/dist/vite/client.d.ts.map +1 -1
  301. package/dist/vite/client.js +1 -0
  302. package/dist/vite/client.js.map +1 -1
  303. package/docs/content/locales/ar-SA/template-design.mdx +13 -0
  304. package/docs/content/locales/de-DE/template-design.mdx +18 -0
  305. package/docs/content/locales/es-ES/template-design.mdx +16 -0
  306. package/docs/content/locales/fr-FR/template-design.mdx +17 -0
  307. package/docs/content/locales/hi-IN/template-design.mdx +16 -0
  308. package/docs/content/locales/ja-JP/template-design.mdx +17 -0
  309. package/docs/content/locales/ko-KR/template-design.mdx +15 -0
  310. package/docs/content/locales/pt-BR/template-design.mdx +16 -0
  311. package/docs/content/locales/zh-CN/template-design.mdx +11 -0
  312. package/docs/content/locales/zh-TW/template-design.mdx +12 -0
  313. package/docs/content/template-design.mdx +16 -0
  314. package/package.json +4 -2
  315. package/src/templates/workspace-core/.agents/skills/observability/SKILL.md +20 -0
  316. package/src/templates/workspace-core/.agents/skills/voice-transcription/SKILL.md +8 -4
@@ -123,6 +123,7 @@ import {
123
123
  type ApprovalContextValue,
124
124
  ReconnectStreamMessage,
125
125
  } from "./chat/tool-call-display.js";
126
+ import { useReconnectReaderOwner } from "./chat/use-reconnect-reader-owner.js";
126
127
  import {
127
128
  MessageScroller,
128
129
  MessageScrollerButton,
@@ -764,6 +765,20 @@ function toolCallIdsRepresentSameLocalCall(
764
765
  return renderedId === reconnectId || renderedId.endsWith(`:${reconnectId}`);
765
766
  }
766
767
 
768
+ function toolCallIdIsReaderLocal(id: string): boolean {
769
+ return (
770
+ /^tc_\d+$/.test(id) ||
771
+ /:tc_\d+$/.test(id) ||
772
+ id.startsWith("reconnect-activity:")
773
+ );
774
+ }
775
+
776
+ function toolCallReaderLocalKey(id: string): string | null {
777
+ const counterMatch = id.match(/(?:^|:)(tc_\d+)$/);
778
+ if (counterMatch?.[1]) return counterMatch[1];
779
+ return id.startsWith("reconnect-activity:") ? id : null;
780
+ }
781
+
767
782
  function collectRenderedToolCallStates(messages: readonly unknown[]): {
768
783
  byId: Map<string, { rank: number }>;
769
784
  latestAssistantByFingerprint: Map<string, { rank: number }>;
@@ -862,7 +877,14 @@ function latestRenderedAssistantText(messages: readonly unknown[]): string {
862
877
  function dedupePendingToolCallReplaysWithinContent(
863
878
  content: ContentPart[],
864
879
  ): ContentPart[] {
865
- const seenLaterFingerprints = new Set<string>();
880
+ const laterStatesByFingerprint = new Map<
881
+ string,
882
+ {
883
+ ids: Set<string>;
884
+ readerLocalKeys: Set<string>;
885
+ hasStableId: boolean;
886
+ }
887
+ >();
866
888
  let changed = false;
867
889
  const nextReversed: ContentPart[] = [];
868
890
 
@@ -870,53 +892,153 @@ function dedupePendingToolCallReplaysWithinContent(
870
892
  const part = content[i];
871
893
  if (!part) continue;
872
894
  const fingerprint = toolCallFingerprintFromContentPart(part);
873
- if (
874
- fingerprint &&
875
- !toolCallPartHasResult(part) &&
876
- seenLaterFingerprints.has(fingerprint)
877
- ) {
895
+ const laterState = fingerprint
896
+ ? laterStatesByFingerprint.get(fingerprint)
897
+ : undefined;
898
+ const id = toolCallIdFromContentPart(part);
899
+ const readerLocalKey = id ? toolCallReaderLocalKey(id) : null;
900
+ const isReaderReplay = Boolean(
901
+ id &&
902
+ laterState &&
903
+ (laterState.ids.has(id) ||
904
+ (readerLocalKey && laterState.readerLocalKeys.has(readerLocalKey)) ||
905
+ (toolCallIdIsReaderLocal(id) && laterState.hasStableId)),
906
+ );
907
+ if (fingerprint && !toolCallPartHasResult(part) && isReaderReplay) {
878
908
  changed = true;
879
909
  continue;
880
910
  }
881
- if (fingerprint) seenLaterFingerprints.add(fingerprint);
911
+ if (fingerprint) {
912
+ const state = laterState ?? {
913
+ ids: new Set(),
914
+ readerLocalKeys: new Set(),
915
+ hasStableId: false,
916
+ };
917
+ if (id) {
918
+ state.ids.add(id);
919
+ const key = toolCallReaderLocalKey(id);
920
+ if (key) state.readerLocalKeys.add(key);
921
+ else state.hasStableId = true;
922
+ }
923
+ laterStatesByFingerprint.set(fingerprint, state);
924
+ }
882
925
  nextReversed.push(part);
883
926
  }
884
927
 
885
928
  return changed ? nextReversed.reverse() : content;
886
929
  }
887
930
 
931
+ function pendingToolCallCountsByName(
932
+ content: readonly ContentPart[],
933
+ ): Map<string, number> {
934
+ const counts = new Map<string, number>();
935
+ for (const part of content) {
936
+ const name = toolCallNameFromContentPart(part);
937
+ if (!name || toolCallProgressRank(part) >= 4) continue;
938
+ counts.set(name, (counts.get(name) ?? 0) + 1);
939
+ }
940
+ return counts;
941
+ }
942
+
943
+ /**
944
+ * Linear-time longest overlap where a suffix of `renderedText` equals a prefix
945
+ * of `reconnectText`. The reconnect overlay is recalculated during render on
946
+ * every stream tick, so a descending slice/endsWith scan becomes quadratic on
947
+ * large repeated output.
948
+ */
949
+ function longestSuffixPrefixOverlap(
950
+ renderedText: string,
951
+ reconnectText: string,
952
+ ): number {
953
+ const maxOverlap = Math.min(renderedText.length, reconnectText.length);
954
+ if (maxOverlap === 0) return 0;
955
+
956
+ const pattern = reconnectText.slice(0, maxOverlap);
957
+ const prefixLengths = new Array<number>(pattern.length).fill(0);
958
+ for (let index = 1; index < pattern.length; index += 1) {
959
+ let matched = prefixLengths[index - 1] ?? 0;
960
+ while (matched > 0 && pattern[index] !== pattern[matched]) {
961
+ matched = prefixLengths[matched - 1] ?? 0;
962
+ }
963
+ if (pattern[index] === pattern[matched]) matched += 1;
964
+ prefixLengths[index] = matched;
965
+ }
966
+
967
+ let matched = 0;
968
+ const renderedStart = renderedText.length - maxOverlap;
969
+ for (let index = renderedStart; index < renderedText.length; index += 1) {
970
+ const character = renderedText[index];
971
+ while (matched > 0 && character !== pattern[matched]) {
972
+ matched = prefixLengths[matched - 1] ?? 0;
973
+ }
974
+ if (character === pattern[matched]) matched += 1;
975
+ if (matched === pattern.length && index < renderedText.length - 1) {
976
+ matched = prefixLengths[matched - 1] ?? 0;
977
+ }
978
+ }
979
+ return matched;
980
+ }
981
+
888
982
  function trimReconnectTextAlreadyRendered(
889
983
  content: ContentPart[],
890
984
  renderedAssistantText: string,
985
+ options?: { trimTailOverlap?: boolean },
891
986
  ): ContentPart[] {
892
987
  if (!renderedAssistantText) return content;
893
988
 
894
- let remainingOverlap = renderedAssistantText;
989
+ const reconnectText = assistantTextFromContent(content);
990
+ if (!reconnectText) return content;
991
+
992
+ let overlapLength = 0;
993
+ if (reconnectText.startsWith(renderedAssistantText)) {
994
+ overlapLength = renderedAssistantText.length;
995
+ } else if (renderedAssistantText.startsWith(reconnectText)) {
996
+ overlapLength = reconnectText.length;
997
+ } else if (options?.trimTailOverlap) {
998
+ const tailOverlap = longestSuffixPrefixOverlap(
999
+ renderedAssistantText,
1000
+ reconnectText,
1001
+ );
1002
+ if (tailOverlap > 0) {
1003
+ // Tail reconnects begin after the last remembered event, so their first
1004
+ // text can be the final suffix of a thread snapshot imported in parallel.
1005
+ // For a partial overlap, require a whole word/phrase boundary so a
1006
+ // coincidental shared character at the join cannot eat legitimate text.
1007
+ const renderedBeforeOverlap =
1008
+ renderedAssistantText[renderedAssistantText.length - tailOverlap - 1];
1009
+ const reconnectAfterOverlap = reconnectText[tailOverlap];
1010
+ const isWordCharacter = (value: string | undefined) =>
1011
+ value ? /[\p{L}\p{N}_]/u.test(value) : false;
1012
+ const isWholeBoundary =
1013
+ !isWordCharacter(renderedBeforeOverlap) &&
1014
+ !isWordCharacter(reconnectAfterOverlap);
1015
+ if (tailOverlap === reconnectText.length || isWholeBoundary) {
1016
+ overlapLength = tailOverlap;
1017
+ }
1018
+ }
1019
+ }
1020
+
1021
+ if (overlapLength === 0) return content;
1022
+
1023
+ let remainingOverlap = overlapLength;
895
1024
  let changed = false;
896
1025
  const next: ContentPart[] = [];
897
1026
 
898
1027
  for (const part of content) {
899
- if (part.type !== "text" || remainingOverlap.length === 0) {
1028
+ if (part.type !== "text" || remainingOverlap === 0) {
900
1029
  next.push(part);
901
1030
  continue;
902
1031
  }
903
1032
 
904
- if (remainingOverlap.startsWith(part.text)) {
905
- remainingOverlap = remainingOverlap.slice(part.text.length);
1033
+ if (part.text.length <= remainingOverlap) {
1034
+ remainingOverlap -= part.text.length;
906
1035
  changed = true;
907
1036
  continue;
908
1037
  }
909
1038
 
910
- if (part.text.startsWith(remainingOverlap)) {
911
- const text = part.text.slice(remainingOverlap.length);
912
- remainingOverlap = "";
913
- changed = true;
914
- if (text) next.push({ ...part, text });
915
- continue;
916
- }
917
-
918
- remainingOverlap = "";
919
- next.push(part);
1039
+ next.push({ ...part, text: part.text.slice(remainingOverlap) });
1040
+ remainingOverlap = 0;
1041
+ changed = true;
920
1042
  }
921
1043
 
922
1044
  return changed ? next : content;
@@ -925,10 +1047,14 @@ function trimReconnectTextAlreadyRendered(
925
1047
  export function dedupeReconnectContentAgainstMessages(
926
1048
  content: ContentPart[],
927
1049
  messages: readonly unknown[],
928
- options?: { suppressToolRepeats?: boolean },
1050
+ options?: {
1051
+ suppressToolRepeats?: boolean;
1052
+ trimTailTextOverlap?: boolean;
1053
+ },
929
1054
  ): ContentPart[] {
930
1055
  if (content.length === 0) return content;
931
1056
  const snapshotDeduped = dedupePendingToolCallReplaysWithinContent(content);
1057
+ const reconnectPendingCounts = pendingToolCallCountsByName(snapshotDeduped);
932
1058
  let changed = snapshotDeduped !== content;
933
1059
  if (messages.length === 0) return changed ? snapshotDeduped : content;
934
1060
  const { byId, latestAssistantByFingerprint, latestAssistantByName } =
@@ -1009,6 +1135,31 @@ export function dedupeReconnectContentAgainstMessages(
1009
1135
  const latestByName = name
1010
1136
  ? latestAssistantByName.get(name)
1011
1137
  : undefined;
1138
+ const reconnectPendingCount = name
1139
+ ? (reconnectPendingCounts.get(name) ?? 0)
1140
+ : 0;
1141
+ const hasReaderLocalIdentity = Boolean(
1142
+ (id && toolCallIdIsReaderLocal(id)) ||
1143
+ (latestByName &&
1144
+ Array.from(latestByName.pendingIds).some(
1145
+ toolCallIdIsReaderLocal,
1146
+ )),
1147
+ );
1148
+ if (
1149
+ options?.suppressToolRepeats &&
1150
+ latestByName &&
1151
+ latestByName.pendingIds.size === 1 &&
1152
+ reconnectPendingCount === 1 &&
1153
+ hasReaderLocalIdentity &&
1154
+ latestByName.pendingRank >= reconnectRank
1155
+ ) {
1156
+ // During reconnect -> live-adapter handoff both accumulators own
1157
+ // the same run. Their pre-tool-start activity cards can have
1158
+ // unrelated reader-local ids and no args fingerprint, so name +
1159
+ // pending progress is the only stable identity available.
1160
+ changed = true;
1161
+ return false;
1162
+ }
1012
1163
  const matchesRenderedLocalId =
1013
1164
  id && latestByName
1014
1165
  ? Array.from(latestByName.ids).some((renderedId) =>
@@ -1037,6 +1188,7 @@ export function dedupeReconnectContentAgainstMessages(
1037
1188
  const textDeduped = trimReconnectTextAlreadyRendered(
1038
1189
  filtered,
1039
1190
  renderedAssistantText,
1191
+ { trimTailOverlap: options?.trimTailTextOverlap },
1040
1192
  );
1041
1193
  if (textDeduped !== filtered) changed = true;
1042
1194
  return changed ? textDeduped : content;
@@ -1151,19 +1303,40 @@ function contentHasVisibleReasoning(content: unknown): boolean {
1151
1303
  });
1152
1304
  }
1153
1305
 
1306
+ function contentHasUnresolvedToolCall(
1307
+ content: unknown,
1308
+ toolName?: string | null,
1309
+ ): boolean {
1310
+ if (!Array.isArray(content)) return false;
1311
+ return content.some((part) => {
1312
+ if (!part || typeof part !== "object") return false;
1313
+ const candidate = part as {
1314
+ type?: unknown;
1315
+ toolName?: unknown;
1316
+ result?: unknown;
1317
+ };
1318
+ return (
1319
+ candidate.type === "tool-call" &&
1320
+ candidate.result === undefined &&
1321
+ (!toolName || candidate.toolName === toolName)
1322
+ );
1323
+ });
1324
+ }
1325
+
1154
1326
  export function shouldShowGlobalRunningStatus({
1155
1327
  showRunningInUI,
1156
1328
  runningActivityLabel,
1329
+ runningActivityTool,
1157
1330
  latestMessage,
1158
1331
  reconnectContent,
1159
1332
  }: {
1160
1333
  showRunningInUI: boolean;
1161
1334
  runningActivityLabel: string | null | undefined;
1335
+ runningActivityTool?: string | null;
1162
1336
  latestMessage: unknown;
1163
1337
  reconnectContent: readonly ContentPart[];
1164
1338
  }): boolean {
1165
1339
  if (!showRunningInUI) return false;
1166
- if (runningActivityLabel) return true;
1167
1340
 
1168
1341
  const message =
1169
1342
  latestMessage && typeof latestMessage === "object"
@@ -1172,6 +1345,30 @@ export function shouldShowGlobalRunningStatus({
1172
1345
  const latestMessageHasReasoning =
1173
1346
  message?.role === "assistant" &&
1174
1347
  contentHasVisibleReasoning(message.content);
1348
+ const latestMessageHasUnresolvedTool =
1349
+ message?.role === "assistant" &&
1350
+ contentHasUnresolvedToolCall(message.content);
1351
+ const matchingActivityToolIsVisible = Boolean(
1352
+ runningActivityTool &&
1353
+ ((message?.role === "assistant" &&
1354
+ contentHasUnresolvedToolCall(message.content, runningActivityTool)) ||
1355
+ contentHasUnresolvedToolCall(reconnectContent, runningActivityTool)),
1356
+ );
1357
+
1358
+ // A pending card for the same tool is already the running indicator.
1359
+ // Rendering its global activity label as well shows the logical call twice
1360
+ // (for example, `generate design` plus `Writing generate design...`).
1361
+ if (runningActivityLabel && matchingActivityToolIsVisible) {
1362
+ return false;
1363
+ }
1364
+ if (
1365
+ !runningActivityLabel &&
1366
+ (latestMessageHasUnresolvedTool ||
1367
+ contentHasUnresolvedToolCall(reconnectContent))
1368
+ ) {
1369
+ return false;
1370
+ }
1371
+ if (runningActivityLabel) return true;
1175
1372
 
1176
1373
  return (
1177
1374
  !latestMessageHasReasoning && !contentHasVisibleReasoning(reconnectContent)
@@ -1432,6 +1629,17 @@ export interface AssistantChatProps {
1432
1629
  historyReloadKey?: string | number | null;
1433
1630
  /** Smooth the last assistant message while an external transcript is updating. */
1434
1631
  externalStreaming?: boolean;
1632
+ /**
1633
+ * Optional host hooks for the inline `needsApproval` affordance beyond the
1634
+ * built-in Approve. Additive: omit entirely to keep today's default
1635
+ * behavior (Deny is local-only, no "Always allow" button). Code sessions
1636
+ * pass these through to the same `host.controlRun` commands their
1637
+ * standalone approval banner already uses (see CodeAgentsApp).
1638
+ */
1639
+ approvalActions?: {
1640
+ onDeny?: (approvalKey: string) => void;
1641
+ onAlwaysAllow?: (approvalKey: string) => void;
1642
+ };
1435
1643
  }
1436
1644
 
1437
1645
  export const CHAT_STORAGE_PREFIX = "agent-chat:";
@@ -1655,6 +1863,7 @@ const AssistantChatInner = forwardRef<
1655
1863
  onSlashCommand,
1656
1864
  execMode,
1657
1865
  onExecModeChange,
1866
+ approvalActions,
1658
1867
  planModeDisabled,
1659
1868
  planModeDisabledReason,
1660
1869
  selectedModel,
@@ -2041,6 +2250,10 @@ const AssistantChatInner = forwardRef<
2041
2250
  const reconnectCanMaterializeRef = useRef(false);
2042
2251
  const reconnectAbortRef = useRef<AbortController | null>(null);
2043
2252
  const reconnectAutoRecoveryCountRef = useRef(0);
2253
+ const reconnectOwnerMountedRef = useReconnectReaderOwner(
2254
+ reconnectRunIdRef,
2255
+ reconnectAbortRef,
2256
+ );
2044
2257
  const [pendingReconnectRecovery, setPendingReconnectRecovery] =
2045
2258
  useState<PendingReconnectRecovery | null>(null);
2046
2259
  // Nuclear stop: user clicked stop. Clears the stop button/indicator AND
@@ -2386,6 +2599,7 @@ const AssistantChatInner = forwardRef<
2386
2599
  const startReconnectToRun = useCallback(
2387
2600
  (runInfo: ActiveRunLookup): boolean => {
2388
2601
  if (
2602
+ !reconnectOwnerMountedRef.current ||
2389
2603
  !threadId ||
2390
2604
  !runInfo.runId ||
2391
2605
  (runInfo.status !== "running" && !isReplayableTerminalRun(runInfo))
@@ -2596,6 +2810,12 @@ const AssistantChatInner = forwardRef<
2596
2810
  rafPending = true;
2597
2811
  requestAnimationFrame(() => {
2598
2812
  rafPending = false;
2813
+ if (
2814
+ !reconnectOwnerMountedRef.current ||
2815
+ reconnectRunIdRef.current !== runId
2816
+ ) {
2817
+ return;
2818
+ }
2599
2819
  setReconnectContent(latestSnapshot);
2600
2820
  });
2601
2821
  };
@@ -2665,6 +2885,11 @@ const AssistantChatInner = forwardRef<
2665
2885
  clearInterval(idleCheck);
2666
2886
  }
2667
2887
 
2888
+ // A newer reader, live adapter, stop action, or component unmount took
2889
+ // ownership while this async loop was unwinding. Do not let the stale
2890
+ // generation refresh/import or update reconnect state afterwards.
2891
+ if (reconnectRunIdRef.current !== runId) return;
2892
+
2668
2893
  if (noProgressDuringReconnect && reconnectRunIdRef.current === runId) {
2669
2894
  const reconnectErrorCode =
2670
2895
  reconnectTerminalReason === "run_timeout"
@@ -3630,6 +3855,7 @@ const AssistantChatInner = forwardRef<
3630
3855
  const stillNeeded =
3631
3856
  dedupeReconnectContentAgainstMessages(reconnectContent, messages, {
3632
3857
  suppressToolRepeats: true,
3858
+ trimTailTextOverlap: true,
3633
3859
  }).length > 0;
3634
3860
  if (!stillNeeded) {
3635
3861
  setReconnectContent([]);
@@ -4236,7 +4462,11 @@ const AssistantChatInner = forwardRef<
4236
4462
  const visibleReconnectContent = dedupeReconnectContentAgainstMessages(
4237
4463
  reconnectContent,
4238
4464
  messages,
4239
- { suppressToolRepeats: adapterHandoffPending },
4465
+ {
4466
+ suppressToolRepeats: adapterHandoffPending,
4467
+ trimTailTextOverlap:
4468
+ adapterHandoffPending || reconnectTailOnlyRef.current,
4469
+ },
4240
4470
  );
4241
4471
  const chatScrollResetKey = `${tabId ?? ""}:${threadId ?? ""}`;
4242
4472
 
@@ -4262,11 +4492,18 @@ const AssistantChatInner = forwardRef<
4262
4492
  );
4263
4493
  const latestMessage = messages[messages.length - 1];
4264
4494
  const latestMessageRole = latestMessage?.role;
4495
+ const reconnectStatusContent =
4496
+ visibleReconnectContent.length > 0
4497
+ ? visibleReconnectContent
4498
+ : reconnectContent.length === 0
4499
+ ? reconnectActivityContent
4500
+ : [];
4265
4501
  const showGlobalRunningStatus = shouldShowGlobalRunningStatus({
4266
4502
  showRunningInUI,
4267
4503
  runningActivityLabel,
4504
+ runningActivityTool,
4268
4505
  latestMessage,
4269
- reconnectContent: visibleReconnectContent,
4506
+ reconnectContent: reconnectStatusContent,
4270
4507
  });
4271
4508
  const latestAssistantWasPlan =
4272
4509
  latestMessageRole === "assistant" &&
@@ -4415,8 +4652,12 @@ const AssistantChatInner = forwardRef<
4415
4652
  ),
4416
4653
  } as Parameters<typeof threadRuntime.append>[0]);
4417
4654
  },
4655
+ ...(approvalActions?.onDeny ? { onDeny: approvalActions.onDeny } : {}),
4656
+ ...(approvalActions?.onAlwaysAllow
4657
+ ? { onAlwaysAllow: approvalActions.onAlwaysAllow }
4658
+ : {}),
4418
4659
  }),
4419
- [appendThreadMessage, execMode, markOptimisticRunning],
4660
+ [appendThreadMessage, execMode, markOptimisticRunning, approvalActions],
4420
4661
  );
4421
4662
 
4422
4663
  return (
@@ -4964,33 +5205,33 @@ const AssistantChatInner = forwardRef<
4964
5205
  onAttachmentError={setComposerError}
4965
5206
  extraActionButton={
4966
5207
  contextXRayEnabled ||
4967
- composerExtraActionButton ||
4968
- showRunningInUI ? (
5208
+ composerExtraActionButton ? (
4969
5209
  <>
4970
5210
  {contextXRayEnabled && (
4971
5211
  <ContextMeter threadId={threadId} />
4972
5212
  )}
4973
5213
  {composerExtraActionButton}
4974
- {showRunningInUI && (
4975
- <Tooltip>
4976
- <TooltipTrigger asChild>
4977
- <button
4978
- type="button"
4979
- onClick={() => stopActiveRun()}
4980
- aria-label="Stop response"
4981
- className="shrink-0 flex h-7 w-7 cursor-pointer items-center justify-center rounded-full bg-primary text-primary-foreground shadow-sm transition-opacity hover:opacity-90 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2"
4982
- >
4983
- <IconPlayerStopFilled className="h-3 w-3" />
4984
- </button>
4985
- </TooltipTrigger>
4986
- <TooltipContent>
4987
- Stop response
4988
- </TooltipContent>
4989
- </Tooltip>
4990
- )}
4991
5214
  </>
4992
5215
  ) : undefined
4993
5216
  }
5217
+ stopButton={
5218
+ showRunningInUI ? (
5219
+ <Tooltip>
5220
+ <TooltipTrigger asChild>
5221
+ <button
5222
+ type="button"
5223
+ onClick={() => stopActiveRun()}
5224
+ aria-label="Stop response"
5225
+ data-agent-composer-slot="stop-button"
5226
+ className="shrink-0 flex h-7 w-7 cursor-pointer items-center justify-center rounded-full bg-primary text-primary-foreground shadow-sm transition-opacity hover:opacity-90 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2"
5227
+ >
5228
+ <IconPlayerStopFilled className="h-3 w-3" />
5229
+ </button>
5230
+ </TooltipTrigger>
5231
+ <TooltipContent>Stop response</TooltipContent>
5232
+ </Tooltip>
5233
+ ) : undefined
5234
+ }
4994
5235
  />
4995
5236
  </>
4996
5237
  )}