@vertesia/ui 1.4.0-dev.20260615.042549Z → 1.4.0-dev.20260629.130134Z

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 (337) hide show
  1. package/lib/core/components/shadcn/dropdown.d.ts.map +1 -1
  2. package/lib/core/components/shadcn/dropdown.js +6 -2
  3. package/lib/core/components/shadcn/dropdown.js.map +1 -1
  4. package/lib/core/components/shadcn/filters/filterBar.d.ts.map +1 -1
  5. package/lib/core/components/shadcn/filters/filterBar.js +9 -2
  6. package/lib/core/components/shadcn/filters/filterBar.js.map +1 -1
  7. package/lib/core/components/shadcn/modal/ConfirmModal.d.ts +16 -1
  8. package/lib/core/components/shadcn/modal/ConfirmModal.d.ts.map +1 -1
  9. package/lib/core/components/shadcn/modal/ConfirmModal.js +25 -3
  10. package/lib/core/components/shadcn/modal/ConfirmModal.js.map +1 -1
  11. package/lib/core/components/shadcn/modal/dialog.js +1 -1
  12. package/lib/core/components/shadcn/modal/dialog.js.map +1 -1
  13. package/lib/core/components/shadcn/popover.d.ts.map +1 -1
  14. package/lib/core/components/shadcn/popover.js +3 -1
  15. package/lib/core/components/shadcn/popover.js.map +1 -1
  16. package/lib/core/components/shadcn/switch.js +3 -3
  17. package/lib/core/components/shadcn/switch.js.map +1 -1
  18. package/lib/core/components/shadcn/tabs.js +1 -1
  19. package/lib/core/components/shadcn/tabs.js.map +1 -1
  20. package/lib/core/components/shadcn/tooltip.d.ts.map +1 -1
  21. package/lib/core/components/shadcn/tooltip.js +3 -1
  22. package/lib/core/components/shadcn/tooltip.js.map +1 -1
  23. package/lib/env/index.d.ts +0 -19
  24. package/lib/env/index.d.ts.map +1 -1
  25. package/lib/env/index.js +0 -6
  26. package/lib/env/index.js.map +1 -1
  27. package/lib/features/agent/PayloadBuilder.d.ts +4 -0
  28. package/lib/features/agent/PayloadBuilder.d.ts.map +1 -1
  29. package/lib/features/agent/PayloadBuilder.js +16 -0
  30. package/lib/features/agent/PayloadBuilder.js.map +1 -1
  31. package/lib/features/agent/chat/AgentApprovalModeSelector.d.ts +10 -0
  32. package/lib/features/agent/chat/AgentApprovalModeSelector.d.ts.map +1 -0
  33. package/lib/features/agent/chat/AgentApprovalModeSelector.js +50 -0
  34. package/lib/features/agent/chat/AgentApprovalModeSelector.js.map +1 -0
  35. package/lib/features/agent/chat/AgentChatFixtureReplay.d.ts +2 -1
  36. package/lib/features/agent/chat/AgentChatFixtureReplay.d.ts.map +1 -1
  37. package/lib/features/agent/chat/AgentChatFixtureReplay.js +3 -3
  38. package/lib/features/agent/chat/AgentChatFixtureReplay.js.map +1 -1
  39. package/lib/features/agent/chat/AgentRequestInputOverlay.d.ts +5 -2
  40. package/lib/features/agent/chat/AgentRequestInputOverlay.d.ts.map +1 -1
  41. package/lib/features/agent/chat/AgentRequestInputOverlay.js +41 -7
  42. package/lib/features/agent/chat/AgentRequestInputOverlay.js.map +1 -1
  43. package/lib/features/agent/chat/AgentRightPanel.d.ts.map +1 -1
  44. package/lib/features/agent/chat/AgentRightPanel.js +133 -12
  45. package/lib/features/agent/chat/AgentRightPanel.js.map +1 -1
  46. package/lib/features/agent/chat/AnimatedThinkingDots.js +1 -1
  47. package/lib/features/agent/chat/AnimatedThinkingDots.js.map +1 -1
  48. package/lib/features/agent/chat/ArtifactsTab.d.ts.map +1 -1
  49. package/lib/features/agent/chat/ArtifactsTab.js +11 -65
  50. package/lib/features/agent/chat/ArtifactsTab.js.map +1 -1
  51. package/lib/features/agent/chat/AskUserWidget.d.ts +3 -1
  52. package/lib/features/agent/chat/AskUserWidget.d.ts.map +1 -1
  53. package/lib/features/agent/chat/AskUserWidget.js +4 -3
  54. package/lib/features/agent/chat/AskUserWidget.js.map +1 -1
  55. package/lib/features/agent/chat/ModernAgentConversation.d.ts +10 -2
  56. package/lib/features/agent/chat/ModernAgentConversation.d.ts.map +1 -1
  57. package/lib/features/agent/chat/ModernAgentConversation.js +526 -117
  58. package/lib/features/agent/chat/ModernAgentConversation.js.map +1 -1
  59. package/lib/features/agent/chat/ModernAgentOutput/ActiveWorkstreamsSummary.d.ts +8 -0
  60. package/lib/features/agent/chat/ModernAgentOutput/ActiveWorkstreamsSummary.d.ts.map +1 -0
  61. package/lib/features/agent/chat/ModernAgentOutput/ActiveWorkstreamsSummary.js +19 -0
  62. package/lib/features/agent/chat/ModernAgentOutput/ActiveWorkstreamsSummary.js.map +1 -0
  63. package/lib/features/agent/chat/ModernAgentOutput/AllMessagesMixed.d.ts +8 -2
  64. package/lib/features/agent/chat/ModernAgentOutput/AllMessagesMixed.d.ts.map +1 -1
  65. package/lib/features/agent/chat/ModernAgentOutput/AllMessagesMixed.js +551 -92
  66. package/lib/features/agent/chat/ModernAgentOutput/AllMessagesMixed.js.map +1 -1
  67. package/lib/features/agent/chat/ModernAgentOutput/AttachmentPreview.d.ts.map +1 -1
  68. package/lib/features/agent/chat/ModernAgentOutput/AttachmentPreview.js +7 -6
  69. package/lib/features/agent/chat/ModernAgentOutput/AttachmentPreview.js.map +1 -1
  70. package/lib/features/agent/chat/ModernAgentOutput/Header.js +2 -2
  71. package/lib/features/agent/chat/ModernAgentOutput/Header.js.map +1 -1
  72. package/lib/features/agent/chat/ModernAgentOutput/MessageDeliveryStatus.d.ts +8 -0
  73. package/lib/features/agent/chat/ModernAgentOutput/MessageDeliveryStatus.d.ts.map +1 -0
  74. package/lib/features/agent/chat/ModernAgentOutput/MessageDeliveryStatus.js +61 -0
  75. package/lib/features/agent/chat/ModernAgentOutput/MessageDeliveryStatus.js.map +1 -0
  76. package/lib/features/agent/chat/ModernAgentOutput/MessageInput.d.ts +15 -2
  77. package/lib/features/agent/chat/ModernAgentOutput/MessageInput.d.ts.map +1 -1
  78. package/lib/features/agent/chat/ModernAgentOutput/MessageInput.js +28 -38
  79. package/lib/features/agent/chat/ModernAgentOutput/MessageInput.js.map +1 -1
  80. package/lib/features/agent/chat/ModernAgentOutput/MessageItem.d.ts +1 -1
  81. package/lib/features/agent/chat/ModernAgentOutput/MessageItem.d.ts.map +1 -1
  82. package/lib/features/agent/chat/ModernAgentOutput/MessageItem.js +3 -2
  83. package/lib/features/agent/chat/ModernAgentOutput/MessageItem.js.map +1 -1
  84. package/lib/features/agent/chat/ModernAgentOutput/SlideInPanel.js +2 -1
  85. package/lib/features/agent/chat/ModernAgentOutput/SlideInPanel.js.map +1 -1
  86. package/lib/features/agent/chat/ModernAgentOutput/SlidingMessages.js +1 -1
  87. package/lib/features/agent/chat/ModernAgentOutput/SlidingMessages.js.map +1 -1
  88. package/lib/features/agent/chat/ModernAgentOutput/SummaryConversation.d.ts +5 -0
  89. package/lib/features/agent/chat/ModernAgentOutput/SummaryConversation.d.ts.map +1 -1
  90. package/lib/features/agent/chat/ModernAgentOutput/SummaryConversation.js +59 -4
  91. package/lib/features/agent/chat/ModernAgentOutput/SummaryConversation.js.map +1 -1
  92. package/lib/features/agent/chat/ModernAgentOutput/WorkstreamTabs.js +2 -2
  93. package/lib/features/agent/chat/ModernAgentOutput/WorkstreamTabs.js.map +1 -1
  94. package/lib/features/agent/chat/ModernAgentOutput/requestInputMessages.d.ts +9 -0
  95. package/lib/features/agent/chat/ModernAgentOutput/requestInputMessages.d.ts.map +1 -1
  96. package/lib/features/agent/chat/ModernAgentOutput/requestInputMessages.js +180 -0
  97. package/lib/features/agent/chat/ModernAgentOutput/requestInputMessages.js.map +1 -1
  98. package/lib/features/agent/chat/ModernAgentOutput/utils.d.ts +6 -0
  99. package/lib/features/agent/chat/ModernAgentOutput/utils.d.ts.map +1 -1
  100. package/lib/features/agent/chat/ModernAgentOutput/utils.js +92 -2
  101. package/lib/features/agent/chat/ModernAgentOutput/utils.js.map +1 -1
  102. package/lib/features/agent/chat/clipboardFiles.d.ts +15 -0
  103. package/lib/features/agent/chat/clipboardFiles.d.ts.map +1 -0
  104. package/lib/features/agent/chat/clipboardFiles.js +35 -0
  105. package/lib/features/agent/chat/clipboardFiles.js.map +1 -0
  106. package/lib/features/agent/chat/hooks/useAgentStream.d.ts +6 -0
  107. package/lib/features/agent/chat/hooks/useAgentStream.d.ts.map +1 -1
  108. package/lib/features/agent/chat/hooks/useAgentStream.js +218 -37
  109. package/lib/features/agent/chat/hooks/useAgentStream.js.map +1 -1
  110. package/lib/features/agent/chat/hooks/useArtifacts.d.ts +1 -8
  111. package/lib/features/agent/chat/hooks/useArtifacts.d.ts.map +1 -1
  112. package/lib/features/agent/chat/hooks/useArtifacts.js +10 -28
  113. package/lib/features/agent/chat/hooks/useArtifacts.js.map +1 -1
  114. package/lib/features/agent/chat/index.d.ts +2 -1
  115. package/lib/features/agent/chat/index.d.ts.map +1 -1
  116. package/lib/features/agent/chat/index.js +2 -1
  117. package/lib/features/agent/chat/index.js.map +1 -1
  118. package/lib/features/agent/chat/workstreams.d.ts +1 -0
  119. package/lib/features/agent/chat/workstreams.d.ts.map +1 -1
  120. package/lib/features/agent/chat/workstreams.js +28 -0
  121. package/lib/features/agent/chat/workstreams.js.map +1 -1
  122. package/lib/features/facets/AgentRunnerFacetsNav.d.ts +10 -1
  123. package/lib/features/facets/AgentRunnerFacetsNav.d.ts.map +1 -1
  124. package/lib/features/facets/AgentRunnerFacetsNav.js +6 -3
  125. package/lib/features/facets/AgentRunnerFacetsNav.js.map +1 -1
  126. package/lib/features/facets/CollectionsFacetsNav.d.ts +10 -1
  127. package/lib/features/facets/CollectionsFacetsNav.d.ts.map +1 -1
  128. package/lib/features/facets/CollectionsFacetsNav.js +14 -5
  129. package/lib/features/facets/CollectionsFacetsNav.js.map +1 -1
  130. package/lib/features/facets/PromptsFacetsNav.d.ts +10 -1
  131. package/lib/features/facets/PromptsFacetsNav.d.ts.map +1 -1
  132. package/lib/features/facets/PromptsFacetsNav.js +14 -5
  133. package/lib/features/facets/PromptsFacetsNav.js.map +1 -1
  134. package/lib/features/facets/RunsFacetsNav.d.ts +10 -1
  135. package/lib/features/facets/RunsFacetsNav.d.ts.map +1 -1
  136. package/lib/features/facets/RunsFacetsNav.js +14 -5
  137. package/lib/features/facets/RunsFacetsNav.js.map +1 -1
  138. package/lib/features/index.d.ts +0 -1
  139. package/lib/features/index.d.ts.map +1 -1
  140. package/lib/features/index.js +0 -1
  141. package/lib/features/index.js.map +1 -1
  142. package/lib/features/oauth/McpConnectionsButton.d.ts +30 -0
  143. package/lib/features/oauth/McpConnectionsButton.d.ts.map +1 -0
  144. package/lib/features/oauth/McpConnectionsButton.js +70 -0
  145. package/lib/features/oauth/McpConnectionsButton.js.map +1 -0
  146. package/lib/features/oauth/McpConnectionsDialog.d.ts +25 -0
  147. package/lib/features/oauth/McpConnectionsDialog.d.ts.map +1 -0
  148. package/lib/features/oauth/McpConnectionsDialog.js +26 -0
  149. package/lib/features/oauth/McpConnectionsDialog.js.map +1 -0
  150. package/lib/features/oauth/RemoteMcpConnectionButton.d.ts.map +1 -1
  151. package/lib/features/oauth/RemoteMcpConnectionButton.js +14 -6
  152. package/lib/features/oauth/RemoteMcpConnectionButton.js.map +1 -1
  153. package/lib/features/oauth/index.d.ts +3 -0
  154. package/lib/features/oauth/index.d.ts.map +1 -1
  155. package/lib/features/oauth/index.js +3 -0
  156. package/lib/features/oauth/index.js.map +1 -1
  157. package/lib/features/oauth/useMcpConnections.d.ts +44 -0
  158. package/lib/features/oauth/useMcpConnections.d.ts.map +1 -0
  159. package/lib/features/oauth/useMcpConnections.js +168 -0
  160. package/lib/features/oauth/useMcpConnections.js.map +1 -0
  161. package/lib/features/permissions/UserPermissionsProvider.d.ts +4 -6
  162. package/lib/features/permissions/UserPermissionsProvider.d.ts.map +1 -1
  163. package/lib/features/permissions/UserPermissionsProvider.js +40 -42
  164. package/lib/features/permissions/UserPermissionsProvider.js.map +1 -1
  165. package/lib/features/store/objects/DocumentPreviewPanel.d.ts.map +1 -1
  166. package/lib/features/store/objects/DocumentPreviewPanel.js +2 -1
  167. package/lib/features/store/objects/DocumentPreviewPanel.js.map +1 -1
  168. package/lib/features/store/objects/DocumentQuickFilter.d.ts +23 -0
  169. package/lib/features/store/objects/DocumentQuickFilter.d.ts.map +1 -0
  170. package/lib/features/store/objects/DocumentQuickFilter.js +30 -0
  171. package/lib/features/store/objects/DocumentQuickFilter.js.map +1 -0
  172. package/lib/features/store/objects/DocumentSearchResults.d.ts.map +1 -1
  173. package/lib/features/store/objects/DocumentSearchResults.js +17 -1
  174. package/lib/features/store/objects/DocumentSearchResults.js.map +1 -1
  175. package/lib/features/store/objects/components/ContentOverview.d.ts.map +1 -1
  176. package/lib/features/store/objects/components/ContentOverview.js +17 -74
  177. package/lib/features/store/objects/components/ContentOverview.js.map +1 -1
  178. package/lib/features/store/objects/layout/DocumentTableColumn.d.ts +7 -1
  179. package/lib/features/store/objects/layout/DocumentTableColumn.d.ts.map +1 -1
  180. package/lib/features/store/objects/layout/DocumentTableColumn.js +27 -3
  181. package/lib/features/store/objects/layout/DocumentTableColumn.js.map +1 -1
  182. package/lib/features/store/objects/layout/documentLayout.js +1 -1
  183. package/lib/features/store/objects/layout/renderers.d.ts +2 -1
  184. package/lib/features/store/objects/layout/renderers.d.ts.map +1 -1
  185. package/lib/features/store/objects/layout/renderers.js +27 -20
  186. package/lib/features/store/objects/layout/renderers.js.map +1 -1
  187. package/lib/features/store/types/ContentObjectTypesSearch.d.ts.map +1 -1
  188. package/lib/features/store/types/ContentObjectTypesSearch.js +4 -1
  189. package/lib/features/store/types/ContentObjectTypesSearch.js.map +1 -1
  190. package/lib/features/store/types/ContentObjectTypesTable.d.ts +2 -1
  191. package/lib/features/store/types/ContentObjectTypesTable.d.ts.map +1 -1
  192. package/lib/features/store/types/ContentObjectTypesTable.js +8 -3
  193. package/lib/features/store/types/ContentObjectTypesTable.js.map +1 -1
  194. package/lib/i18n/instance.d.ts.map +1 -1
  195. package/lib/i18n/instance.js +1 -0
  196. package/lib/i18n/instance.js.map +1 -1
  197. package/lib/i18n/locales/ar.json +72 -45
  198. package/lib/i18n/locales/de.json +65 -34
  199. package/lib/i18n/locales/en.json +63 -47
  200. package/lib/i18n/locales/es.json +65 -35
  201. package/lib/i18n/locales/fr.json +65 -35
  202. package/lib/i18n/locales/it.json +72 -42
  203. package/lib/i18n/locales/ja.json +72 -41
  204. package/lib/i18n/locales/ko.json +72 -41
  205. package/lib/i18n/locales/pt.json +72 -42
  206. package/lib/i18n/locales/ru.json +72 -43
  207. package/lib/i18n/locales/tr.json +72 -41
  208. package/lib/i18n/locales/zh-TW.json +72 -41
  209. package/lib/i18n/locales/zh.json +72 -41
  210. package/lib/session/UserSession.d.ts +0 -6
  211. package/lib/session/UserSession.d.ts.map +1 -1
  212. package/lib/session/UserSession.js +0 -15
  213. package/lib/session/UserSession.js.map +1 -1
  214. package/lib/session/UserSessionProvider.d.ts.map +1 -1
  215. package/lib/session/UserSessionProvider.js +39 -98
  216. package/lib/session/UserSessionProvider.js.map +1 -1
  217. package/lib/session/auth/composable.d.ts.map +1 -1
  218. package/lib/session/auth/composable.js +3 -33
  219. package/lib/session/auth/composable.js.map +1 -1
  220. package/lib/shell/login/SignInPrimitives.d.ts.map +1 -1
  221. package/lib/shell/login/SignInPrimitives.js +3 -2
  222. package/lib/shell/login/SignInPrimitives.js.map +1 -1
  223. package/lib/vertesia-ui-core.js +1 -1
  224. package/lib/vertesia-ui-core.js.map +1 -1
  225. package/lib/vertesia-ui-env.js +1 -1
  226. package/lib/vertesia-ui-env.js.map +1 -1
  227. package/lib/vertesia-ui-features.js +1 -1
  228. package/lib/vertesia-ui-features.js.map +1 -1
  229. package/lib/vertesia-ui-i18n.js +1 -1
  230. package/lib/vertesia-ui-i18n.js.map +1 -1
  231. package/lib/vertesia-ui-session.js +1 -1
  232. package/lib/vertesia-ui-session.js.map +1 -1
  233. package/lib/vertesia-ui-shell.js +1 -1
  234. package/lib/vertesia-ui-shell.js.map +1 -1
  235. package/lib/vertesia-ui-widgets.js +1 -1
  236. package/lib/vertesia-ui-widgets.js.map +1 -1
  237. package/lib/widgets/schema-editor/editor/Editable.js +1 -1
  238. package/lib/widgets/schema-editor/editor/Editable.js.map +1 -1
  239. package/llms.txt +70 -70
  240. package/package.json +35 -32
  241. package/src/core/components/shadcn/dropdown.tsx +2 -0
  242. package/src/core/components/shadcn/filters/filterBar.tsx +9 -2
  243. package/src/core/components/shadcn/modal/ConfirmModal.tsx +65 -3
  244. package/src/core/components/shadcn/modal/dialog.tsx +1 -1
  245. package/src/core/components/shadcn/popover.tsx +1 -0
  246. package/src/core/components/shadcn/switch.tsx +4 -4
  247. package/src/core/components/shadcn/tabs.tsx +1 -1
  248. package/src/core/components/shadcn/tooltip.tsx +1 -0
  249. package/src/env/index.ts +0 -24
  250. package/src/features/agent/PayloadBuilder.tsx +18 -0
  251. package/src/features/agent/chat/AgentApprovalModeSelector.tsx +130 -0
  252. package/src/features/agent/chat/AgentChatFixtureReplay.test.tsx +22 -2
  253. package/src/features/agent/chat/AgentChatFixtureReplay.tsx +33 -27
  254. package/src/features/agent/chat/AgentRequestInputOverlay.test.tsx +122 -0
  255. package/src/features/agent/chat/AgentRequestInputOverlay.tsx +91 -17
  256. package/src/features/agent/chat/AgentRightPanel.tsx +238 -50
  257. package/src/features/agent/chat/AnimatedThinkingDots.tsx +1 -1
  258. package/src/features/agent/chat/ArtifactsTab.tsx +21 -155
  259. package/src/features/agent/chat/AskUserWidget.tsx +6 -2
  260. package/src/features/agent/chat/ModernAgentConversation.test.tsx +1157 -47
  261. package/src/features/agent/chat/ModernAgentConversation.tsx +703 -136
  262. package/src/features/agent/chat/ModernAgentOutput/ActiveWorkstreamsSummary.tsx +74 -0
  263. package/src/features/agent/chat/ModernAgentOutput/AllMessagesMixed.test.tsx +1420 -126
  264. package/src/features/agent/chat/ModernAgentOutput/AllMessagesMixed.tsx +934 -111
  265. package/src/features/agent/chat/ModernAgentOutput/AttachmentPreview.tsx +31 -40
  266. package/src/features/agent/chat/ModernAgentOutput/Header.tsx +28 -28
  267. package/src/features/agent/chat/ModernAgentOutput/MessageDeliveryStatus.tsx +83 -0
  268. package/src/features/agent/chat/ModernAgentOutput/MessageInput.test.tsx +38 -2
  269. package/src/features/agent/chat/ModernAgentOutput/MessageInput.tsx +121 -98
  270. package/src/features/agent/chat/ModernAgentOutput/MessageItem.tsx +7 -1
  271. package/src/features/agent/chat/ModernAgentOutput/SlideInPanel.tsx +3 -3
  272. package/src/features/agent/chat/ModernAgentOutput/SlidingMessages.tsx +1 -1
  273. package/src/features/agent/chat/ModernAgentOutput/SummaryConversation.ts +82 -2
  274. package/src/features/agent/chat/ModernAgentOutput/WorkstreamTabs.tsx +2 -2
  275. package/src/features/agent/chat/ModernAgentOutput/requestInputMessages.ts +216 -0
  276. package/src/features/agent/chat/ModernAgentOutput/utils.test.ts +149 -0
  277. package/src/features/agent/chat/ModernAgentOutput/utils.ts +103 -2
  278. package/src/features/agent/chat/clipboardFiles.ts +33 -0
  279. package/src/features/agent/chat/hooks/useAgentStream.test.tsx +355 -4
  280. package/src/features/agent/chat/hooks/useAgentStream.ts +270 -43
  281. package/src/features/agent/chat/hooks/useArtifacts.ts +11 -44
  282. package/src/features/agent/chat/index.ts +10 -1
  283. package/src/features/agent/chat/workstreams.ts +42 -0
  284. package/src/features/facets/AgentRunnerFacetsNav.tsx +23 -3
  285. package/src/features/facets/CollectionsFacetsNav.tsx +30 -5
  286. package/src/features/facets/PromptsFacetsNav.tsx +30 -5
  287. package/src/features/facets/RunsFacetsNav.tsx +32 -5
  288. package/src/features/index.ts +0 -1
  289. package/src/features/oauth/McpConnectionsButton.test.tsx +248 -0
  290. package/src/features/oauth/McpConnectionsButton.tsx +269 -0
  291. package/src/features/oauth/McpConnectionsDialog.tsx +142 -0
  292. package/src/features/oauth/RemoteMcpConnectionButton.tsx +37 -14
  293. package/src/features/oauth/index.ts +3 -0
  294. package/src/features/oauth/useMcpConnections.test.ts +94 -0
  295. package/src/features/oauth/useMcpConnections.ts +210 -0
  296. package/src/features/permissions/UserPermissionsProvider.tsx +41 -59
  297. package/src/features/store/objects/DocumentPreviewPanel.tsx +4 -2
  298. package/src/features/store/objects/DocumentQuickFilter.tsx +72 -0
  299. package/src/features/store/objects/DocumentSearchResults.tsx +30 -11
  300. package/src/features/store/objects/components/ContentOverview.tsx +30 -99
  301. package/src/features/store/objects/layout/DocumentTableColumn.tsx +29 -4
  302. package/src/features/store/objects/layout/documentLayout.tsx +1 -1
  303. package/src/features/store/objects/layout/renderers.tsx +80 -20
  304. package/src/features/store/types/ContentObjectTypesSearch.tsx +7 -1
  305. package/src/features/store/types/ContentObjectTypesTable.tsx +34 -4
  306. package/src/i18n/instance.ts +1 -0
  307. package/src/i18n/locales/ar.json +72 -45
  308. package/src/i18n/locales/de.json +65 -34
  309. package/src/i18n/locales/en.json +63 -47
  310. package/src/i18n/locales/es.json +65 -35
  311. package/src/i18n/locales/fr.json +65 -35
  312. package/src/i18n/locales/it.json +72 -42
  313. package/src/i18n/locales/ja.json +72 -41
  314. package/src/i18n/locales/ko.json +72 -41
  315. package/src/i18n/locales/pt.json +72 -42
  316. package/src/i18n/locales/ru.json +72 -43
  317. package/src/i18n/locales/tr.json +72 -41
  318. package/src/i18n/locales/zh-TW.json +72 -41
  319. package/src/i18n/locales/zh.json +72 -41
  320. package/src/i18n/rtl.test.ts +57 -1
  321. package/src/session/UserSession.ts +0 -16
  322. package/src/session/UserSessionProvider.tsx +51 -123
  323. package/src/session/auth/composable.ts +3 -34
  324. package/src/shell/login/SignInPrimitives.tsx +3 -2
  325. package/src/widgets/schema-editor/editor/Editable.tsx +1 -1
  326. package/lib/features/document-viewer/UniversalDocumentViewer.d.ts +0 -73
  327. package/lib/features/document-viewer/UniversalDocumentViewer.d.ts.map +0 -1
  328. package/lib/features/document-viewer/UniversalDocumentViewer.js +0 -415
  329. package/lib/features/document-viewer/UniversalDocumentViewer.js.map +0 -1
  330. package/lib/features/document-viewer/index.d.ts +0 -2
  331. package/lib/features/document-viewer/index.d.ts.map +0 -1
  332. package/lib/features/document-viewer/index.js +0 -2
  333. package/lib/features/document-viewer/index.js.map +0 -1
  334. package/src/features/document-viewer/UniversalDocumentViewer.test.tsx +0 -123
  335. package/src/features/document-viewer/UniversalDocumentViewer.tsx +0 -746
  336. package/src/features/document-viewer/index.ts +0 -1
  337. package/src/session/auth/composable.test.ts +0 -79
@@ -7,6 +7,7 @@ import { ModernAgentConversation } from './ModernAgentConversation';
7
7
 
8
8
  const mocks = vi.hoisted(() => ({
9
9
  addOptimisticMessage: vi.fn(),
10
+ updateOptimisticMessageStatus: vi.fn(),
10
11
  removeOptimisticMessages: vi.fn(),
11
12
  reconnect: vi.fn(),
12
13
  restart: vi.fn(),
@@ -19,6 +20,8 @@ const mocks = vi.hoisted(() => ({
19
20
  useAgentPlans: vi.fn(),
20
21
  useDocumentPanel: vi.fn(),
21
22
  useFileProcessing: vi.fn(),
23
+ getActiveWorkstreams: vi.fn(),
24
+ retrieve: vi.fn(),
22
25
  }));
23
26
 
24
27
  vi.mock('@vertesia/ui/session', () => ({
@@ -27,7 +30,8 @@ vi.mock('@vertesia/ui/session', () => ({
27
30
  agents: {
28
31
  restart: mocks.restart,
29
32
  sendSignal: mocks.sendSignal,
30
- getActiveWorkstreams: vi.fn().mockResolvedValue({ running: [] }),
33
+ getActiveWorkstreams: mocks.getActiveWorkstreams,
34
+ retrieve: mocks.retrieve,
31
35
  },
32
36
  },
33
37
  project: undefined,
@@ -58,12 +62,25 @@ vi.mock('./ModernAgentOutput/MessageInput', () => ({
58
62
  activeWorkstreams?: Array<{ workstream_id: string; status: string }>;
59
63
  isCompleted?: boolean;
60
64
  isStreaming?: boolean;
65
+ placeholder?: string;
66
+ contextWindowUsage?: {
67
+ usedTokens: number;
68
+ checkpointTokens: number;
69
+ usedPercent: number;
70
+ remainingPercent: number;
71
+ };
72
+ onCompactContext?: () => void;
73
+ isCompactingContext?: boolean;
74
+ approvalModeSlot?: React.ReactNode;
61
75
  }) => {
62
76
  mocks.messageInputProps(props);
63
77
  return (
64
- <button type="button" onClick={() => props.onSend('follow up')}>
65
- composer send
66
- </button>
78
+ <div>
79
+ {props.approvalModeSlot}
80
+ <button type="button" onClick={() => props.onSend('follow up')}>
81
+ composer send
82
+ </button>
83
+ </div>
67
84
  );
68
85
  },
69
86
  }));
@@ -72,17 +89,34 @@ vi.mock('./ModernAgentOutput/AllMessagesMixed', () => ({
72
89
  default: ({
73
90
  messages,
74
91
  streamingMessages,
92
+ isCompleted,
75
93
  bottomRef,
76
94
  onSendMessage,
95
+ showInitialRequest,
77
96
  renderRequestInputControls,
97
+ activeWorkstream,
98
+ onActiveWorkstreamChange,
78
99
  }: {
79
100
  messages: AgentMessage[];
80
101
  streamingMessages: Map<string, unknown>;
102
+ isCompleted?: boolean;
81
103
  bottomRef: React.RefObject<HTMLDivElement>;
82
- onSendMessage?: (message: string) => void;
104
+ onSendMessage?: (message: string, metadata?: Record<string, unknown>) => void;
105
+ showInitialRequest?: boolean;
83
106
  renderRequestInputControls?: boolean;
107
+ activeWorkstream?: string;
108
+ onActiveWorkstreamChange?: (workstreamId: string) => void;
84
109
  }) => {
85
- mocks.allMessagesMixedProps({ messages, streamingMessages, onSendMessage, renderRequestInputControls });
110
+ mocks.allMessagesMixedProps({
111
+ messages,
112
+ streamingMessages,
113
+ isCompleted,
114
+ onSendMessage,
115
+ showInitialRequest,
116
+ renderRequestInputControls,
117
+ activeWorkstream,
118
+ onActiveWorkstreamChange,
119
+ });
86
120
  return (
87
121
  <div>
88
122
  <div data-testid="rendered-message-count">{messages.length}</div>
@@ -132,6 +166,7 @@ function createMessage(type: AgentMessageType, message: string): AgentMessage {
132
166
  function mockStreamState(options: {
133
167
  messages: AgentMessage[];
134
168
  isCompleted?: boolean;
169
+ initialHistoryStatus?: 'loading' | 'empty' | 'has_messages' | 'error';
135
170
  agentRunStatus?: string | null;
136
171
  streamingMessages?: Map<string, unknown>;
137
172
  }) {
@@ -139,8 +174,10 @@ function mockStreamState(options: {
139
174
  messages: options.messages,
140
175
  streamingMessages: options.streamingMessages ?? new Map(),
141
176
  isCompleted: options.isCompleted ?? true,
177
+ initialHistoryStatus: options.initialHistoryStatus ?? 'has_messages',
142
178
  debugChunkFlash: false,
143
179
  addOptimisticMessage: mocks.addOptimisticMessage,
180
+ updateOptimisticMessageStatus: mocks.updateOptimisticMessageStatus,
144
181
  removeOptimisticMessages: mocks.removeOptimisticMessages,
145
182
  reconnect: mocks.reconnect,
146
183
  agentRunStatus: options.agentRunStatus ?? null,
@@ -162,11 +199,27 @@ function renderConversation(props: Partial<React.ComponentProps<typeof ModernAge
162
199
  );
163
200
  }
164
201
 
202
+ function latestAllMessagesMixedProps() {
203
+ const calls = mocks.allMessagesMixedProps.mock.calls;
204
+ return calls[calls.length - 1]?.[0] as
205
+ | {
206
+ messages: AgentMessage[];
207
+ streamingMessages: Map<string, unknown>;
208
+ isCompleted?: boolean;
209
+ showInitialRequest?: boolean;
210
+ activeWorkstream?: string;
211
+ onActiveWorkstreamChange?: (workstreamId: string) => void;
212
+ }
213
+ | undefined;
214
+ }
215
+
165
216
  describe('ModernAgentConversation send handling', () => {
166
217
  beforeEach(() => {
167
218
  vi.clearAllMocks();
168
219
  mocks.restart.mockResolvedValue({ id: 'agent-run-1' });
169
220
  mocks.sendSignal.mockResolvedValue({});
221
+ mocks.getActiveWorkstreams.mockResolvedValue({ running: [] });
222
+ mocks.retrieve.mockResolvedValue({ disabled_mcp_collections: undefined });
170
223
  mocks.useAgentPlans.mockReturnValue({
171
224
  plans: [],
172
225
  activePlanIndex: 0,
@@ -220,13 +273,16 @@ describe('ModernAgentConversation send handling', () => {
220
273
  'UserInput',
221
274
  expect.objectContaining({
222
275
  message: 'follow up',
276
+ client_message_id: expect.any(String),
223
277
  metadata: expect.objectContaining({
278
+ id: expect.any(String),
224
279
  _messageId: expect.any(String),
225
280
  }),
226
281
  }),
227
282
  );
228
283
  expect(mocks.restart.mock.invocationCallOrder[0]).toBeLessThan(mocks.sendSignal.mock.invocationCallOrder[0]);
229
284
  expect(mocks.reconnect.mock.invocationCallOrder[0]).toBeLessThan(mocks.sendSignal.mock.invocationCallOrder[0]);
285
+ expect(mocks.updateOptimisticMessageStatus).toHaveBeenCalledWith(expect.any(String), 'received');
230
286
  });
231
287
 
232
288
  it('does not restart or send from a terminal run that cannot continue', () => {
@@ -284,6 +340,128 @@ describe('ModernAgentConversation send handling', () => {
284
340
  expect(screen.queryByRole('button', { name: 'composer send' })).toBeNull();
285
341
  });
286
342
 
343
+ it('passes the selected approval mode when starting a new interactive run', async () => {
344
+ const startWorkflow = vi.fn().mockResolvedValue(undefined);
345
+ renderWithProviders(
346
+ <ModernAgentConversation startWorkflow={startWorkflow} hideHeader hideFileUpload initialMessage="" />,
347
+ );
348
+
349
+ const selector = screen.getByRole('button', { name: 'Agent approval mode' });
350
+ expect(selector.getAttribute('title')).toBeNull();
351
+
352
+ fireEvent.pointerDown(selector, {
353
+ button: 0,
354
+ ctrlKey: false,
355
+ });
356
+ fireEvent.click(await screen.findByRole('menuitemradio', { name: /Approve for me/ }));
357
+ fireEvent.change(screen.getByRole('textbox'), { target: { value: 'Draft the release notes' } });
358
+ fireEvent.click(screen.getByRole('button', { name: 'Start Agent' }));
359
+
360
+ await waitFor(() => {
361
+ expect(startWorkflow).toHaveBeenCalledWith('Draft the release notes', {
362
+ tool_approval_mode: 'auto_review',
363
+ });
364
+ });
365
+ });
366
+
367
+ it('signals approval mode changes for an active interactive run', async () => {
368
+ mocks.retrieve.mockResolvedValue({
369
+ tool_approval_mode: 'ask',
370
+ interactive: true,
371
+ disabled_mcp_collections: undefined,
372
+ });
373
+ mockStreamState({
374
+ messages: [createMessage(AgentMessageType.ANSWER, 'still running')],
375
+ isCompleted: false,
376
+ agentRunStatus: 'RUNNING',
377
+ });
378
+
379
+ renderConversation({ hideMessageInput: false, interactive: true, allowWorkflowControl: true });
380
+
381
+ await waitFor(() => {
382
+ expect(screen.getByText('Ask for approval')).not.toBeNull();
383
+ });
384
+ fireEvent.pointerDown(screen.getByRole('button', { name: 'Agent approval mode' }), {
385
+ button: 0,
386
+ ctrlKey: false,
387
+ });
388
+ fireEvent.click(await screen.findByRole('menuitemradio', { name: /Approve for me/ }));
389
+
390
+ await waitFor(() => {
391
+ expect(mocks.sendSignal).toHaveBeenCalledWith('agent-run-1', 'ToolApprovalModeChanged', {
392
+ mode: 'auto_review',
393
+ });
394
+ });
395
+ });
396
+
397
+ it('switches to full control without a confirmation modal', async () => {
398
+ mocks.retrieve.mockResolvedValue({
399
+ tool_approval_mode: 'ask',
400
+ interactive: true,
401
+ disabled_mcp_collections: undefined,
402
+ });
403
+ mockStreamState({
404
+ messages: [createMessage(AgentMessageType.ANSWER, 'still running')],
405
+ isCompleted: false,
406
+ agentRunStatus: 'RUNNING',
407
+ });
408
+
409
+ renderConversation({ hideMessageInput: false, interactive: true, allowWorkflowControl: true });
410
+
411
+ await waitFor(() => {
412
+ expect(screen.getByText('Ask for approval')).not.toBeNull();
413
+ });
414
+ fireEvent.pointerDown(screen.getByRole('button', { name: 'Agent approval mode' }), {
415
+ button: 0,
416
+ ctrlKey: false,
417
+ });
418
+ fireEvent.click(await screen.findByRole('menuitemradio', { name: /Full control/ }));
419
+
420
+ await waitFor(() => {
421
+ expect(mocks.sendSignal).toHaveBeenCalledWith('agent-run-1', 'ToolApprovalModeChanged', {
422
+ mode: 'full_control',
423
+ });
424
+ });
425
+ expect(screen.queryByText('Switch to full control?')).toBeNull();
426
+ });
427
+
428
+ it('does not show a full-control fallback before active run mode metadata loads', () => {
429
+ mocks.retrieve.mockReturnValue(new Promise(() => {}));
430
+ mockStreamState({
431
+ messages: [createMessage(AgentMessageType.ANSWER, 'still running')],
432
+ isCompleted: false,
433
+ agentRunStatus: 'RUNNING',
434
+ });
435
+
436
+ renderConversation({ hideMessageInput: false, interactive: true, allowWorkflowControl: true });
437
+
438
+ expect(screen.queryByRole('button', { name: 'Agent approval mode' })).toBeNull();
439
+ expect(screen.queryByText('Full control')).toBeNull();
440
+ });
441
+
442
+ it('disables the active-run approval selector when a run is completed', async () => {
443
+ mocks.retrieve.mockResolvedValue({
444
+ tool_approval_mode: 'ask',
445
+ interactive: true,
446
+ disabled_mcp_collections: undefined,
447
+ });
448
+ mockStreamState({
449
+ messages: [createMessage(AgentMessageType.COMPLETE, 'done')],
450
+ isCompleted: true,
451
+ agentRunStatus: 'COMPLETED',
452
+ });
453
+
454
+ renderConversation({
455
+ hideMessageInput: false,
456
+ interactive: true,
457
+ allowWorkflowControl: true,
458
+ onRestart: vi.fn(),
459
+ });
460
+
461
+ const selector = await screen.findByRole('button', { name: 'Agent approval mode' });
462
+ expect((selector as HTMLButtonElement).disabled).toBe(true);
463
+ });
464
+
287
465
  it('unlocks the composer when an idle marker arrives before the stream completion flag updates', () => {
288
466
  mockStreamState({
289
467
  messages: [
@@ -295,6 +473,7 @@ describe('ModernAgentConversation send handling', () => {
295
473
  isCompleted: false,
296
474
  agentRunStatus: 'RUNNING',
297
475
  });
476
+ mocks.sendSignal.mockReturnValue(new Promise(() => {}));
298
477
 
299
478
  renderConversation({ hideMessageInput: false });
300
479
 
@@ -307,7 +486,204 @@ describe('ModernAgentConversation send handling', () => {
307
486
  expect(latestMessageInputProps.isStreaming).toBe(false);
308
487
  });
309
488
 
310
- it('uses message-derived active workstreams for the composer count and right panel fallback', async () => {
489
+ it('keeps live run controls enabled while the agent is idle waiting for user input', async () => {
490
+ mocks.retrieve.mockResolvedValue({
491
+ tool_approval_mode: 'ask',
492
+ interactive: true,
493
+ disabled_mcp_collections: undefined,
494
+ });
495
+ mockStreamState({
496
+ messages: [
497
+ {
498
+ ...createMessage(AgentMessageType.THOUGHT, 'Planning the next tool call'),
499
+ details: {
500
+ token_usage: {
501
+ total: 50_000,
502
+ },
503
+ checkpoint_at: 100_000,
504
+ },
505
+ },
506
+ createMessage(AgentMessageType.IDLE, 'Waiting for your command...'),
507
+ ],
508
+ isCompleted: false,
509
+ agentRunStatus: 'RUNNING',
510
+ });
511
+
512
+ renderConversation({
513
+ hideMessageInput: false,
514
+ interactive: true,
515
+ allowWorkflowControl: true,
516
+ });
517
+
518
+ const selector = await screen.findByRole('button', { name: 'Agent approval mode' });
519
+ expect((selector as HTMLButtonElement).disabled).toBe(false);
520
+
521
+ const latestMessageInputProps = mocks.messageInputProps.mock.lastCall?.[0] as {
522
+ contextWindowUsage?: {
523
+ usedTokens: number;
524
+ checkpointTokens: number;
525
+ usedPercent: number;
526
+ remainingPercent: number;
527
+ };
528
+ onCompactContext?: () => void | Promise<void>;
529
+ };
530
+
531
+ expect(latestMessageInputProps.contextWindowUsage).toEqual({
532
+ usedTokens: 50_000,
533
+ checkpointTokens: 100_000,
534
+ usedPercent: 50,
535
+ remainingPercent: 50,
536
+ });
537
+ expect(latestMessageInputProps.onCompactContext).toBeTypeOf('function');
538
+ });
539
+
540
+ it('derives context usage from persisted messages and sends manual compact signal', async () => {
541
+ mockStreamState({
542
+ messages: [
543
+ {
544
+ ...createMessage(AgentMessageType.THOUGHT, 'Planning the next tool call'),
545
+ details: {
546
+ token_usage: {
547
+ prompt: 40_000,
548
+ result: 10_000,
549
+ total: 50_000,
550
+ },
551
+ checkpoint_at: 100_000,
552
+ },
553
+ },
554
+ ],
555
+ isCompleted: false,
556
+ agentRunStatus: 'RUNNING',
557
+ });
558
+
559
+ renderConversation({ hideMessageInput: false });
560
+
561
+ expect(mocks.messageInputProps).toHaveBeenCalled();
562
+
563
+ const latestMessageInputProps = mocks.messageInputProps.mock.lastCall?.[0] as {
564
+ contextWindowUsage?: {
565
+ usedTokens: number;
566
+ checkpointTokens: number;
567
+ usedPercent: number;
568
+ remainingPercent: number;
569
+ };
570
+ onCompactContext?: () => void | Promise<void>;
571
+ };
572
+
573
+ expect(latestMessageInputProps.contextWindowUsage).toEqual({
574
+ usedTokens: 50_000,
575
+ checkpointTokens: 100_000,
576
+ usedPercent: 50,
577
+ remainingPercent: 50,
578
+ });
579
+
580
+ act(() => {
581
+ void latestMessageInputProps.onCompactContext?.();
582
+ });
583
+
584
+ expect(mocks.sendSignal).toHaveBeenCalledWith('agent-run-1', 'TriggerCheckpoint', {
585
+ reason: 'manual user request',
586
+ });
587
+ });
588
+
589
+ it('derives context usage from approval request messages when no preamble was posted', () => {
590
+ mockStreamState({
591
+ messages: [
592
+ {
593
+ ...createMessage(AgentMessageType.REQUEST_INPUT, 'Approve Create Document?'),
594
+ details: {
595
+ tool_approval: {
596
+ tool_name: 'create_document',
597
+ tool_title: 'Create Document',
598
+ target: 'name:Budget plan',
599
+ },
600
+ token_usage: {
601
+ total: 82_000,
602
+ },
603
+ checkpoint_threshold: 100_000,
604
+ },
605
+ },
606
+ ],
607
+ isCompleted: false,
608
+ agentRunStatus: 'RUNNING',
609
+ });
610
+
611
+ renderConversation({ hideMessageInput: false, interactive: true, allowWorkflowControl: true });
612
+
613
+ const latestMessageInputProps = mocks.messageInputProps.mock.lastCall?.[0] as {
614
+ contextWindowUsage?: {
615
+ usedTokens: number;
616
+ checkpointTokens: number;
617
+ usedPercent: number;
618
+ remainingPercent: number;
619
+ };
620
+ onCompactContext?: () => void | Promise<void>;
621
+ };
622
+
623
+ expect(latestMessageInputProps.contextWindowUsage).toEqual({
624
+ usedTokens: 82_000,
625
+ checkpointTokens: 100_000,
626
+ usedPercent: 82,
627
+ remainingPercent: 18,
628
+ });
629
+ expect(latestMessageInputProps.onCompactContext).toBeTypeOf('function');
630
+ });
631
+
632
+ it('makes it explicit that the composer still messages the main agent while viewing a workstream', async () => {
633
+ mockStreamState({
634
+ messages: [
635
+ createMessage(AgentMessageType.QUESTION, 'main request'),
636
+ {
637
+ ...createMessage(AgentMessageType.UPDATE, 'France workstream launched'),
638
+ timestamp: 2_000,
639
+ workstream_id: 'france_news_agent',
640
+ details: {
641
+ event_class: 'activity',
642
+ workstream_event: 'launched',
643
+ workstream_id: 'france_news_agent',
644
+ interaction: 'sys:AdhocTaskAgent',
645
+ },
646
+ },
647
+ {
648
+ ...createMessage(AgentMessageType.THOUGHT, 'France internal search'),
649
+ timestamp: 3_000,
650
+ workstream_id: 'france_news_agent',
651
+ },
652
+ ],
653
+ isCompleted: false,
654
+ agentRunStatus: 'RUNNING',
655
+ });
656
+
657
+ renderConversation({ hideMessageInput: false });
658
+
659
+ act(() => {
660
+ latestAllMessagesMixedProps()?.onActiveWorkstreamChange?.('france_news_agent');
661
+ });
662
+
663
+ const latestMessageInputProps = mocks.messageInputProps.mock.lastCall?.[0] as {
664
+ placeholder?: string;
665
+ };
666
+
667
+ expect(latestMessageInputProps.placeholder).toBe('Message @Main Agent (viewing France News Agent)...');
668
+
669
+ fireEvent.click(screen.getByRole('button', { name: 'composer send' }));
670
+
671
+ await waitFor(() => {
672
+ expect(mocks.sendSignal).toHaveBeenCalledWith(
673
+ 'agent-run-1',
674
+ 'UserInput',
675
+ expect.objectContaining({ message: 'follow up' }),
676
+ );
677
+ });
678
+ expect(mocks.addOptimisticMessage).toHaveBeenCalledWith(
679
+ expect.objectContaining({
680
+ message: 'follow up',
681
+ workstream_id: 'main',
682
+ }),
683
+ );
684
+ });
685
+
686
+ it('uses message-derived workstreams for panel history while the composer only counts active workstreams', async () => {
311
687
  mockStreamState({
312
688
  messages: [
313
689
  createMessage(AgentMessageType.QUESTION, 'main request'),
@@ -322,6 +698,7 @@ describe('ModernAgentConversation send handling', () => {
322
698
  renderConversation({
323
699
  hideMessageInput: false,
324
700
  hideWorkstreamTabs: false,
701
+ onRestart: vi.fn(),
325
702
  showRightPanel: true,
326
703
  });
327
704
 
@@ -341,6 +718,7 @@ describe('ModernAgentConversation send handling', () => {
341
718
  expect(latestRightPanelProps.activeWorkstreams).toEqual([
342
719
  expect.objectContaining({ workstream_id: 'alpha', status: 'running' }),
343
720
  expect.objectContaining({ workstream_id: 'beta', status: 'running' }),
721
+ expect.objectContaining({ workstream_id: 'gamma', status: 'completed' }),
344
722
  ]);
345
723
  expect(latestRightPanelProps.activeTab).toBe('plan');
346
724
  expect(latestMessageInputProps.activeTaskCount).toBe(2);
@@ -350,7 +728,7 @@ describe('ModernAgentConversation send handling', () => {
350
728
  ]);
351
729
  });
352
730
 
353
- it('does not count completed workstream lifecycle messages as active in the composer fallback', async () => {
731
+ it('shows completed workstream lifecycle messages in the panel without counting them as active', async () => {
354
732
  mockStreamState({
355
733
  messages: [
356
734
  createMessage(AgentMessageType.QUESTION, 'main request'),
@@ -407,6 +785,7 @@ describe('ModernAgentConversation send handling', () => {
407
785
 
408
786
  expect(latestRightPanelProps.activeWorkstreams).toEqual([
409
787
  expect.objectContaining({ workstream_id: 'qa_assignee', status: 'running' }),
788
+ expect.objectContaining({ workstream_id: 'qa_tasks', status: 'completed' }),
410
789
  ]);
411
790
  expect(latestMessageInputProps.activeTaskCount).toBe(1);
412
791
  expect(latestMessageInputProps.activeWorkstreams).toEqual([
@@ -414,32 +793,85 @@ describe('ModernAgentConversation send handling', () => {
414
793
  ]);
415
794
  });
416
795
 
417
- it('keeps launched workstreams active when their child transcript emits JSON results', async () => {
796
+ it('keeps message-derived workstream history when the live workstream query fails', async () => {
797
+ const warn = vi.spyOn(console, 'warn').mockImplementation(() => {});
798
+ mocks.getActiveWorkstreams.mockRejectedValueOnce(new Error('workflow no longer queryable'));
418
799
  mockStreamState({
419
800
  messages: [
420
801
  createMessage(AgentMessageType.QUESTION, 'main request'),
421
802
  {
422
- ...createMessage(
423
- AgentMessageType.UPDATE,
424
- 'Workstream "ImageGeneratorAgent-16b7f73a-8e8e-40b7-b891-cb47a78c38c6" launched',
425
- ),
426
- workstream_id: 'ImageGeneratorAgent-16b7f73a-8e8e-40b7-b891-cb47a78c38c6',
803
+ ...createMessage(AgentMessageType.UPDATE, 'Workstream "qa_tasks" launched'),
804
+ workstream_id: 'qa_tasks',
427
805
  details: {
428
806
  workstream_event: 'launched',
429
- workstream_id: 'ImageGeneratorAgent-16b7f73a-8e8e-40b7-b891-cb47a78c38c6',
430
- launch_id: 'launch-image',
431
- interaction: 'ImageGeneratorAgent',
807
+ workstream_id: 'qa_tasks',
808
+ launch_id: 'launch-qa-tasks',
809
+ },
810
+ },
811
+ {
812
+ ...createMessage(AgentMessageType.UPDATE, 'Workstream "qa_tasks" completed'),
813
+ workstream_id: 'qa_tasks',
814
+ details: {
815
+ workstream_event: 'completed',
816
+ workstream_id: 'qa_tasks',
817
+ launch_id: 'launch-qa-tasks',
818
+ status: 'completed',
819
+ },
820
+ },
821
+ ],
822
+ isCompleted: false,
823
+ agentRunStatus: 'RUNNING',
824
+ });
825
+
826
+ try {
827
+ renderConversation({
828
+ hideMessageInput: false,
829
+ hideWorkstreamTabs: false,
830
+ showRightPanel: true,
831
+ });
832
+
833
+ await waitFor(() => {
834
+ expect(mocks.getActiveWorkstreams).toHaveBeenCalled();
835
+ const latestRightPanelProps = mocks.rightPanelProps.mock.lastCall?.[0] as {
836
+ activeWorkstreams?: Array<{ workstream_id: string; status: string }>;
837
+ };
838
+ expect(latestRightPanelProps.activeWorkstreams).toEqual([
839
+ expect.objectContaining({ workstream_id: 'qa_tasks', status: 'completed' }),
840
+ ]);
841
+ });
842
+
843
+ const latestMessageInputProps = mocks.messageInputProps.mock.lastCall?.[0] as {
844
+ activeTaskCount?: number;
845
+ activeWorkstreams?: Array<{ workstream_id: string; status: string }>;
846
+ };
847
+ expect(latestMessageInputProps.activeTaskCount).toBe(0);
848
+ expect(latestMessageInputProps.activeWorkstreams).toEqual([]);
849
+ } finally {
850
+ warn.mockRestore();
851
+ }
852
+ });
853
+
854
+ it('marks failed pre-launch workstream activities as failed instead of active', async () => {
855
+ mockStreamState({
856
+ messages: [
857
+ createMessage(AgentMessageType.QUESTION, 'main request'),
858
+ {
859
+ ...createMessage(AgentMessageType.UPDATE, 'Provisioning browser sandbox for "Create Bookmark"...'),
860
+ workstream_id: 'create_bookmark',
861
+ details: {
862
+ event_class: 'activity',
863
+ workstream_id: 'create_bookmark',
432
864
  },
433
865
  },
434
866
  {
435
867
  ...createMessage(
436
- AgentMessageType.COMPLETE,
437
- '{\n "generated_images": ["store:65dbb885-d76f-431a-a01e-0511d70730a5"]\n}',
868
+ AgentMessageType.ERROR,
869
+ 'Failed to provision browser sandbox: Request failed with status code 502',
438
870
  ),
439
- workstream_id: 'ImageGeneratorAgent-16b7f73a-8e8e-40b7-b891-cb47a78c38c6',
871
+ workstream_id: 'create_bookmark',
440
872
  details: {
441
- event_class: 'user_content',
442
- streamed: true,
873
+ event_class: 'activity',
874
+ workstream_id: 'create_bookmark',
443
875
  },
444
876
  },
445
877
  ],
@@ -454,43 +886,234 @@ describe('ModernAgentConversation send handling', () => {
454
886
  });
455
887
 
456
888
  await waitFor(() => {
457
- expect(mocks.rightPanelProps).toHaveBeenCalled();
889
+ const latestRightPanelProps = mocks.rightPanelProps.mock.lastCall?.[0] as {
890
+ activeWorkstreams?: Array<{ workstream_id: string; status: string }>;
891
+ };
892
+ expect(latestRightPanelProps.activeWorkstreams).toEqual([
893
+ expect.objectContaining({ workstream_id: 'create_bookmark', status: 'failed' }),
894
+ ]);
458
895
  });
459
896
 
460
897
  const latestMessageInputProps = mocks.messageInputProps.mock.lastCall?.[0] as {
461
898
  activeTaskCount?: number;
462
- activeWorkstreams?: Array<{ workstream_id: string; status: string; interaction?: string }>;
899
+ activeWorkstreams?: Array<{ workstream_id: string; status: string }>;
463
900
  };
464
-
465
- expect(latestMessageInputProps.activeTaskCount).toBe(1);
466
- expect(latestMessageInputProps.activeWorkstreams).toEqual([
467
- expect.objectContaining({
468
- workstream_id: 'ImageGeneratorAgent-16b7f73a-8e8e-40b7-b891-cb47a78c38c6',
469
- interaction: 'ImageGeneratorAgent',
470
- status: 'running',
471
- }),
472
- ]);
901
+ expect(latestMessageInputProps.activeTaskCount).toBe(0);
902
+ expect(latestMessageInputProps.activeWorkstreams).toEqual([]);
473
903
  });
474
904
 
475
- it('shows a bottom request overlay for pending ask input even when normal input is hidden', async () => {
905
+ it('does not let stale active workstream queries override terminal message-derived state', async () => {
906
+ mocks.getActiveWorkstreams.mockResolvedValue({
907
+ running: [
908
+ {
909
+ launch_id: 'launch-create-bookmark',
910
+ workstream_id: 'create_bookmark',
911
+ interaction: 'sys:BrowserAgent',
912
+ started_at: Date.now() - 5000,
913
+ elapsed_ms: 5000,
914
+ deadline_ms: 30000,
915
+ status: 'running',
916
+ },
917
+ ],
918
+ });
476
919
  mockStreamState({
477
920
  messages: [
921
+ createMessage(AgentMessageType.QUESTION, 'main request'),
478
922
  {
479
- ...createMessage(AgentMessageType.REQUEST_INPUT, 'What is your favorite color?'),
923
+ ...createMessage(AgentMessageType.UPDATE, 'Provisioning browser sandbox for "Create Bookmark"...'),
924
+ workstream_id: 'create_bookmark',
480
925
  details: {
481
- ux: {
482
- options: [
483
- { id: 'red', label: 'Red' },
484
- { id: 'blue', label: 'Blue' },
485
- ],
486
- },
926
+ event_class: 'activity',
927
+ workstream_id: 'create_bookmark',
487
928
  },
488
929
  },
489
- ],
490
- isCompleted: false,
491
- agentRunStatus: 'RUNNING',
492
- });
493
-
930
+ {
931
+ ...createMessage(
932
+ AgentMessageType.ERROR,
933
+ 'Failed to provision browser sandbox: Request failed with status code 502',
934
+ ),
935
+ workstream_id: 'create_bookmark',
936
+ details: {
937
+ event_class: 'activity',
938
+ workstream_id: 'create_bookmark',
939
+ },
940
+ },
941
+ ],
942
+ isCompleted: false,
943
+ agentRunStatus: 'RUNNING',
944
+ });
945
+
946
+ renderConversation({
947
+ hideMessageInput: false,
948
+ hideWorkstreamTabs: false,
949
+ showRightPanel: true,
950
+ });
951
+
952
+ await waitFor(() => {
953
+ const latestRightPanelProps = mocks.rightPanelProps.mock.lastCall?.[0] as {
954
+ activeWorkstreams?: Array<{ workstream_id: string; status: string; interaction?: string }>;
955
+ };
956
+ expect(latestRightPanelProps.activeWorkstreams).toEqual([
957
+ expect.objectContaining({
958
+ workstream_id: 'create_bookmark',
959
+ status: 'failed',
960
+ interaction: 'sys:BrowserAgent',
961
+ }),
962
+ ]);
963
+ });
964
+
965
+ const latestMessageInputProps = mocks.messageInputProps.mock.lastCall?.[0] as {
966
+ activeTaskCount?: number;
967
+ activeWorkstreams?: Array<{ workstream_id: string; status: string }>;
968
+ };
969
+ expect(latestMessageInputProps.activeTaskCount).toBe(0);
970
+ expect(latestMessageInputProps.activeWorkstreams).toEqual([]);
971
+ });
972
+
973
+ it('uses the live workstream query to enrich message-derived running workstreams', async () => {
974
+ mocks.getActiveWorkstreams.mockResolvedValue({
975
+ running: [
976
+ {
977
+ launch_id: 'launch-alpha',
978
+ workstream_id: 'alpha',
979
+ interaction: 'sys:AlphaAgent',
980
+ started_at: Date.now() - 5000,
981
+ elapsed_ms: 5000,
982
+ deadline_ms: 30000,
983
+ status: 'running',
984
+ latest_progress: {
985
+ launch_id: 'launch-alpha',
986
+ workstream_id: 'alpha',
987
+ phase: 'executing_tool',
988
+ updated_at: Date.now(),
989
+ },
990
+ child_workflow_id: 'child-alpha',
991
+ child_workflow_run_id: 'child-run-alpha',
992
+ },
993
+ ],
994
+ });
995
+ mockStreamState({
996
+ messages: [
997
+ createMessage(AgentMessageType.QUESTION, 'main request'),
998
+ {
999
+ ...createMessage(AgentMessageType.UPDATE, 'Workstream "alpha" launched'),
1000
+ workstream_id: 'alpha',
1001
+ details: {
1002
+ workstream_event: 'launched',
1003
+ workstream_id: 'alpha',
1004
+ launch_id: 'launch-alpha',
1005
+ },
1006
+ },
1007
+ ],
1008
+ isCompleted: false,
1009
+ agentRunStatus: 'RUNNING',
1010
+ });
1011
+
1012
+ renderConversation({
1013
+ hideMessageInput: false,
1014
+ hideWorkstreamTabs: false,
1015
+ showRightPanel: true,
1016
+ });
1017
+
1018
+ await waitFor(() => {
1019
+ const latestRightPanelProps = mocks.rightPanelProps.mock.lastCall?.[0] as {
1020
+ activeWorkstreams?: Array<{
1021
+ workstream_id: string;
1022
+ status: string;
1023
+ elapsed_ms?: number;
1024
+ phase?: string;
1025
+ child_workflow_run_id?: string;
1026
+ }>;
1027
+ };
1028
+ expect(latestRightPanelProps.activeWorkstreams).toEqual([
1029
+ expect.objectContaining({
1030
+ workstream_id: 'alpha',
1031
+ status: 'running',
1032
+ elapsed_ms: 5000,
1033
+ phase: 'executing_tool',
1034
+ child_workflow_run_id: 'child-run-alpha',
1035
+ }),
1036
+ ]);
1037
+ });
1038
+ });
1039
+
1040
+ it('keeps launched workstreams active when their child transcript emits JSON results', async () => {
1041
+ mockStreamState({
1042
+ messages: [
1043
+ createMessage(AgentMessageType.QUESTION, 'main request'),
1044
+ {
1045
+ ...createMessage(
1046
+ AgentMessageType.UPDATE,
1047
+ 'Workstream "ImageGeneratorAgent-16b7f73a-8e8e-40b7-b891-cb47a78c38c6" launched',
1048
+ ),
1049
+ workstream_id: 'ImageGeneratorAgent-16b7f73a-8e8e-40b7-b891-cb47a78c38c6',
1050
+ details: {
1051
+ workstream_event: 'launched',
1052
+ workstream_id: 'ImageGeneratorAgent-16b7f73a-8e8e-40b7-b891-cb47a78c38c6',
1053
+ launch_id: 'launch-image',
1054
+ interaction: 'ImageGeneratorAgent',
1055
+ },
1056
+ },
1057
+ {
1058
+ ...createMessage(
1059
+ AgentMessageType.COMPLETE,
1060
+ '{\n "generated_images": ["store:65dbb885-d76f-431a-a01e-0511d70730a5"]\n}',
1061
+ ),
1062
+ workstream_id: 'ImageGeneratorAgent-16b7f73a-8e8e-40b7-b891-cb47a78c38c6',
1063
+ details: {
1064
+ event_class: 'user_content',
1065
+ streamed: true,
1066
+ },
1067
+ },
1068
+ ],
1069
+ isCompleted: false,
1070
+ agentRunStatus: 'RUNNING',
1071
+ });
1072
+
1073
+ renderConversation({
1074
+ hideMessageInput: false,
1075
+ hideWorkstreamTabs: false,
1076
+ showRightPanel: true,
1077
+ });
1078
+
1079
+ await waitFor(() => {
1080
+ expect(mocks.rightPanelProps).toHaveBeenCalled();
1081
+ });
1082
+
1083
+ const latestMessageInputProps = mocks.messageInputProps.mock.lastCall?.[0] as {
1084
+ activeTaskCount?: number;
1085
+ activeWorkstreams?: Array<{ workstream_id: string; status: string; interaction?: string }>;
1086
+ };
1087
+
1088
+ expect(latestMessageInputProps.activeTaskCount).toBe(1);
1089
+ expect(latestMessageInputProps.activeWorkstreams).toEqual([
1090
+ expect.objectContaining({
1091
+ workstream_id: 'ImageGeneratorAgent-16b7f73a-8e8e-40b7-b891-cb47a78c38c6',
1092
+ interaction: 'ImageGeneratorAgent',
1093
+ status: 'running',
1094
+ }),
1095
+ ]);
1096
+ });
1097
+
1098
+ it('shows a bottom request overlay for pending ask input even when normal input is hidden', async () => {
1099
+ mockStreamState({
1100
+ messages: [
1101
+ {
1102
+ ...createMessage(AgentMessageType.REQUEST_INPUT, 'What is your favorite color?'),
1103
+ details: {
1104
+ ux: {
1105
+ options: [
1106
+ { id: 'red', label: 'Red' },
1107
+ { id: 'blue', label: 'Blue' },
1108
+ ],
1109
+ },
1110
+ },
1111
+ },
1112
+ ],
1113
+ isCompleted: false,
1114
+ agentRunStatus: 'RUNNING',
1115
+ });
1116
+
494
1117
  renderConversation();
495
1118
 
496
1119
  expect(screen.queryByRole('button', { name: 'composer send' })).toBeNull();
@@ -508,7 +1131,75 @@ describe('ModernAgentConversation send handling', () => {
508
1131
  expect(mocks.sendSignal).toHaveBeenCalledWith(
509
1132
  'agent-run-1',
510
1133
  'UserInput',
511
- expect.objectContaining({ message: 'blue' }),
1134
+ expect.objectContaining({
1135
+ message: 'blue',
1136
+ client_message_id: expect.any(String),
1137
+ metadata: expect.objectContaining({
1138
+ id: expect.any(String),
1139
+ _messageId: expect.any(String),
1140
+ }),
1141
+ }),
1142
+ );
1143
+ });
1144
+
1145
+ it('passes commented approval denial metadata through the request overlay signal', async () => {
1146
+ mockStreamState({
1147
+ messages: [
1148
+ {
1149
+ ...createMessage(AgentMessageType.REQUEST_INPUT, 'Approve Write Artifact: quotes.md?'),
1150
+ details: {
1151
+ tool_approval: {
1152
+ tool_name: 'write_artifact',
1153
+ approval_key: 'write_artifact:name:quotes.md',
1154
+ },
1155
+ ux: {
1156
+ options: [
1157
+ { id: 'allow_once', label: 'Allow once' },
1158
+ { id: 'allow_for_run', label: 'Allow this action for this run' },
1159
+ { id: 'deny', label: 'Deny' },
1160
+ ],
1161
+ free_response: {
1162
+ placeholder: 'No, and tell the agent what to do differently',
1163
+ submit_label: 'Submit',
1164
+ metadata: {
1165
+ tool_approval_response: {
1166
+ decision: 'deny_with_feedback',
1167
+ approval_key: 'write_artifact:name:quotes.md',
1168
+ },
1169
+ },
1170
+ },
1171
+ },
1172
+ },
1173
+ },
1174
+ ],
1175
+ isCompleted: false,
1176
+ agentRunStatus: 'RUNNING',
1177
+ });
1178
+
1179
+ renderConversation();
1180
+
1181
+ fireEvent.change(screen.getByPlaceholderText('No, and tell the agent what to do differently'), {
1182
+ target: { value: 'Do not write a file. Put the summary in chat.' },
1183
+ });
1184
+ fireEvent.click(screen.getByRole('button', { name: 'Submit' }));
1185
+
1186
+ await waitFor(() => {
1187
+ expect(mocks.sendSignal).toHaveBeenCalledTimes(1);
1188
+ });
1189
+ expect(mocks.sendSignal).toHaveBeenCalledWith(
1190
+ 'agent-run-1',
1191
+ 'UserInput',
1192
+ expect.objectContaining({
1193
+ message: 'Do not write a file. Put the summary in chat.',
1194
+ metadata: expect.objectContaining({
1195
+ tool_approval_response: {
1196
+ decision: 'deny_with_feedback',
1197
+ approval_key: 'write_artifact:name:quotes.md',
1198
+ },
1199
+ id: expect.any(String),
1200
+ _messageId: expect.any(String),
1201
+ }),
1202
+ }),
512
1203
  );
513
1204
  });
514
1205
 
@@ -535,6 +1226,137 @@ describe('ModernAgentConversation send handling', () => {
535
1226
  );
536
1227
  });
537
1228
 
1229
+ it('marks a sent message as sending and then received', async () => {
1230
+ mockStreamState({
1231
+ messages: [createMessage(AgentMessageType.ANSWER, 'still running')],
1232
+ agentRunStatus: 'RUNNING',
1233
+ });
1234
+
1235
+ renderConversation({ onRestart: vi.fn() });
1236
+
1237
+ fireEvent.click(screen.getByRole('button', { name: 'inline send' }));
1238
+
1239
+ const optimisticMessage = mocks.addOptimisticMessage.mock.calls[0]?.[0] as AgentMessage | undefined;
1240
+ expect(optimisticMessage).toEqual(
1241
+ expect.objectContaining({
1242
+ type: AgentMessageType.QUESTION,
1243
+ message: 'follow up',
1244
+ details: expect.objectContaining({
1245
+ _optimistic: true,
1246
+ _messageId: expect.any(String),
1247
+ _deliveryStatus: 'sending',
1248
+ }),
1249
+ }),
1250
+ );
1251
+
1252
+ await waitFor(() => {
1253
+ expect(mocks.updateOptimisticMessageStatus).toHaveBeenCalledWith(
1254
+ optimisticMessage?.details?._messageId,
1255
+ 'received',
1256
+ );
1257
+ });
1258
+ });
1259
+
1260
+ it('marks a failed send without removing the optimistic message', async () => {
1261
+ mocks.sendSignal.mockRejectedValueOnce(new Error('signal failed'));
1262
+ mockStreamState({
1263
+ messages: [createMessage(AgentMessageType.ANSWER, 'still running')],
1264
+ agentRunStatus: 'RUNNING',
1265
+ });
1266
+
1267
+ renderConversation({ onRestart: vi.fn() });
1268
+
1269
+ fireEvent.click(screen.getByRole('button', { name: 'inline send' }));
1270
+
1271
+ const optimisticMessage = mocks.addOptimisticMessage.mock.calls[0]?.[0] as AgentMessage | undefined;
1272
+
1273
+ await waitFor(() => {
1274
+ expect(mocks.updateOptimisticMessageStatus).toHaveBeenCalledWith(
1275
+ optimisticMessage?.details?._messageId,
1276
+ 'failed',
1277
+ );
1278
+ });
1279
+ expect(mocks.removeOptimisticMessages).not.toHaveBeenCalled();
1280
+ });
1281
+
1282
+ it('adds an optimistic stop marker and marks it received when Stop is signaled', async () => {
1283
+ const stopRef = { current: null } as React.MutableRefObject<(() => void) | null>;
1284
+ mockStreamState({
1285
+ messages: [createMessage(AgentMessageType.THOUGHT, 'working')],
1286
+ isCompleted: false,
1287
+ agentRunStatus: 'RUNNING',
1288
+ });
1289
+
1290
+ renderConversation({ stopRef });
1291
+
1292
+ await waitFor(() => {
1293
+ expect(stopRef.current).toBeTypeOf('function');
1294
+ });
1295
+
1296
+ act(() => {
1297
+ stopRef.current?.();
1298
+ });
1299
+
1300
+ const optimisticMessage = mocks.addOptimisticMessage.mock.calls[0]?.[0] as AgentMessage | undefined;
1301
+ expect(optimisticMessage).toEqual(
1302
+ expect.objectContaining({
1303
+ type: AgentMessageType.IDLE,
1304
+ message: 'Stopped. Waiting for your command...',
1305
+ details: expect.objectContaining({
1306
+ _optimistic: true,
1307
+ _messageId: expect.any(String),
1308
+ _deliveryStatus: 'sending',
1309
+ status_reason: 'user_stopped',
1310
+ }),
1311
+ }),
1312
+ );
1313
+ await waitFor(() => {
1314
+ expect(mocks.sendSignal).toHaveBeenCalledWith(
1315
+ 'agent-run-1',
1316
+ 'Stop',
1317
+ expect.objectContaining({
1318
+ client_message_id: optimisticMessage?.details?._messageId,
1319
+ metadata: expect.objectContaining({
1320
+ id: optimisticMessage?.details?._messageId,
1321
+ _messageId: optimisticMessage?.details?._messageId,
1322
+ }),
1323
+ }),
1324
+ );
1325
+ });
1326
+ expect(mocks.updateOptimisticMessageStatus).toHaveBeenCalledWith(
1327
+ optimisticMessage?.details?._messageId,
1328
+ 'received',
1329
+ );
1330
+ });
1331
+
1332
+ it('marks an optimistic stop marker failed when Stop signaling fails', async () => {
1333
+ const stopRef = { current: null } as React.MutableRefObject<(() => void) | null>;
1334
+ mocks.sendSignal.mockRejectedValueOnce(new Error('stop failed'));
1335
+ mockStreamState({
1336
+ messages: [createMessage(AgentMessageType.THOUGHT, 'working')],
1337
+ isCompleted: false,
1338
+ agentRunStatus: 'RUNNING',
1339
+ });
1340
+
1341
+ renderConversation({ stopRef });
1342
+
1343
+ await waitFor(() => {
1344
+ expect(stopRef.current).toBeTypeOf('function');
1345
+ });
1346
+
1347
+ act(() => {
1348
+ stopRef.current?.();
1349
+ });
1350
+
1351
+ const optimisticMessage = mocks.addOptimisticMessage.mock.calls[0]?.[0] as AgentMessage | undefined;
1352
+ await waitFor(() => {
1353
+ expect(mocks.updateOptimisticMessageStatus).toHaveBeenCalledWith(
1354
+ optimisticMessage?.details?._messageId,
1355
+ 'failed',
1356
+ );
1357
+ });
1358
+ });
1359
+
538
1360
  it('playback controls slice rendered messages and scroll forward without mutating the live stream', async () => {
539
1361
  const originalScrollIntoView = Element.prototype.scrollIntoView;
540
1362
  const scrollIntoView = vi.fn();
@@ -569,6 +1391,7 @@ describe('ModernAgentConversation send handling', () => {
569
1391
 
570
1392
  expect(screen.getByTestId('rendered-message-count').textContent).toBe('1');
571
1393
  expect(screen.getByTestId('rendered-streaming-count').textContent).toBe('0');
1394
+ expect(latestAllMessagesMixedProps()?.isCompleted).toBe(false);
572
1395
  expect(scrollIntoView).not.toHaveBeenCalled();
573
1396
 
574
1397
  const playbackPositionInput = screen.getByRole('textbox', { name: 'Playback position' });
@@ -592,6 +1415,7 @@ describe('ModernAgentConversation send handling', () => {
592
1415
 
593
1416
  expect(screen.getByTestId('rendered-message-count').textContent).toBe('5');
594
1417
  expect(screen.getByTestId('rendered-streaming-count').textContent).toBe('0');
1418
+ expect(latestAllMessagesMixedProps()?.isCompleted).toBe(true);
595
1419
  await waitFor(() => expect(scrollIntoView).toHaveBeenCalled());
596
1420
  scrollIntoView.mockClear();
597
1421
 
@@ -605,6 +1429,197 @@ describe('ModernAgentConversation send handling', () => {
605
1429
  }
606
1430
  });
607
1431
 
1432
+ it('scopes playback controls to the active workstream', () => {
1433
+ mockStreamState({
1434
+ messages: [
1435
+ createMessage(AgentMessageType.QUESTION, 'Compare France and Japan news.'),
1436
+ {
1437
+ ...createMessage(AgentMessageType.UPDATE, 'Japan workstream launched.'),
1438
+ timestamp: 2_000,
1439
+ workstream_id: 'japan_news',
1440
+ details: {
1441
+ event_class: 'activity',
1442
+ workstream_event: 'launched',
1443
+ workstream_id: 'japan_news',
1444
+ interaction: 'sys:AdhocTaskAgent',
1445
+ },
1446
+ },
1447
+ {
1448
+ ...createMessage(AgentMessageType.THOUGHT, 'Japan internal search.'),
1449
+ timestamp: 3_000,
1450
+ workstream_id: 'japan_news',
1451
+ },
1452
+ {
1453
+ ...createMessage(AgentMessageType.ANSWER, 'Japan internal answer.'),
1454
+ timestamp: 4_000,
1455
+ workstream_id: 'japan_news',
1456
+ },
1457
+ {
1458
+ ...createMessage(AgentMessageType.UPDATE, 'France workstream launched.'),
1459
+ timestamp: 5_000,
1460
+ workstream_id: 'france_news',
1461
+ details: {
1462
+ event_class: 'activity',
1463
+ workstream_event: 'launched',
1464
+ workstream_id: 'france_news',
1465
+ interaction: 'sys:AdhocTaskAgent',
1466
+ },
1467
+ },
1468
+ {
1469
+ ...createMessage(AgentMessageType.THOUGHT, 'France internal search.'),
1470
+ timestamp: 6_000,
1471
+ workstream_id: 'france_news',
1472
+ },
1473
+ createMessage(AgentMessageType.ANSWER, 'Main monitoring update.'),
1474
+ ],
1475
+ });
1476
+
1477
+ renderConversation({ enablePlayback: true });
1478
+
1479
+ const controls = screen.getByTestId('agent-test-playback-controls');
1480
+ expect(controls.getAttribute('data-live-message-count')).toBe('4');
1481
+ expect(screen.getByTestId('rendered-message-count').textContent).toBe('4');
1482
+
1483
+ act(() => {
1484
+ latestAllMessagesMixedProps()?.onActiveWorkstreamChange?.('japan_news');
1485
+ });
1486
+
1487
+ expect(screen.getByTestId('agent-test-playback-controls').getAttribute('data-live-message-count')).toBe('2');
1488
+ expect(screen.getByTestId('rendered-message-count').textContent).toBe('2');
1489
+ expect(latestAllMessagesMixedProps()?.activeWorkstream).toBe('japan_news');
1490
+
1491
+ fireEvent.click(screen.getByRole('button', { name: 'Jump to first message' }));
1492
+
1493
+ expect(screen.getByTestId('rendered-message-count').textContent).toBe('1');
1494
+ });
1495
+
1496
+ it('only enables the synthetic initial request after empty history while no real messages exist', () => {
1497
+ mockStreamState({
1498
+ messages: [],
1499
+ isCompleted: false,
1500
+ initialHistoryStatus: 'empty',
1501
+ agentRunStatus: 'RUNNING',
1502
+ });
1503
+
1504
+ renderConversation({
1505
+ initialRequestData: { task: 'What are the news headlines in France today?' },
1506
+ });
1507
+
1508
+ expect(latestAllMessagesMixedProps()?.showInitialRequest).toBe(true);
1509
+ });
1510
+
1511
+ it('disables the synthetic initial request when real messages arrive after an empty history race', () => {
1512
+ mockStreamState({
1513
+ messages: [createMessage(AgentMessageType.ANSWER, 'Current French headlines...')],
1514
+ isCompleted: true,
1515
+ initialHistoryStatus: 'empty',
1516
+ agentRunStatus: 'COMPLETED',
1517
+ });
1518
+
1519
+ renderConversation({
1520
+ initialRequestData: { task: 'What are the news headlines in France today?' },
1521
+ });
1522
+
1523
+ expect(latestAllMessagesMixedProps()?.showInitialRequest).toBe(false);
1524
+ });
1525
+
1526
+ it('does not enable the synthetic initial request while history is still loading', () => {
1527
+ mockStreamState({
1528
+ messages: [],
1529
+ isCompleted: false,
1530
+ initialHistoryStatus: 'loading',
1531
+ agentRunStatus: 'RUNNING',
1532
+ });
1533
+
1534
+ renderConversation({
1535
+ initialRequestData: { task: 'What are the news headlines in France today?' },
1536
+ });
1537
+
1538
+ expect(latestAllMessagesMixedProps()?.showInitialRequest).toBe(false);
1539
+ });
1540
+
1541
+ it('does not poll live workstreams while initial history is loading', () => {
1542
+ mockStreamState({
1543
+ messages: [],
1544
+ isCompleted: false,
1545
+ initialHistoryStatus: 'loading',
1546
+ agentRunStatus: 'RUNNING',
1547
+ });
1548
+
1549
+ renderConversation();
1550
+
1551
+ expect(mocks.getActiveWorkstreams).not.toHaveBeenCalled();
1552
+ });
1553
+
1554
+ it('stops polling live workstreams when the server marks the query unavailable', async () => {
1555
+ vi.useFakeTimers();
1556
+ mocks.getActiveWorkstreams.mockResolvedValue({ running: [], completed: [], unavailable: true });
1557
+ mockStreamState({
1558
+ messages: [createMessage(AgentMessageType.QUESTION, 'main request')],
1559
+ isCompleted: false,
1560
+ initialHistoryStatus: 'has_messages',
1561
+ agentRunStatus: 'RUNNING',
1562
+ });
1563
+
1564
+ try {
1565
+ renderConversation();
1566
+
1567
+ await act(async () => {
1568
+ await Promise.resolve();
1569
+ });
1570
+ const callCountAfterUnavailable = mocks.getActiveWorkstreams.mock.calls.length;
1571
+ expect(callCountAfterUnavailable).toBeGreaterThan(0);
1572
+
1573
+ await act(async () => {
1574
+ vi.advanceTimersByTime(10_000);
1575
+ });
1576
+ expect(mocks.getActiveWorkstreams).toHaveBeenCalledTimes(callCountAfterUnavailable);
1577
+ } finally {
1578
+ vi.useRealTimers();
1579
+ }
1580
+ });
1581
+
1582
+ it('does not overlap live workstream polls while a previous request is pending', async () => {
1583
+ vi.useFakeTimers();
1584
+ const resolveQueries: Array<(value: { running: [] }) => void> = [];
1585
+ mocks.getActiveWorkstreams.mockImplementation(
1586
+ () =>
1587
+ new Promise<{ running: [] }>((resolve) => {
1588
+ resolveQueries.push(resolve);
1589
+ }),
1590
+ );
1591
+ mockStreamState({
1592
+ messages: [createMessage(AgentMessageType.QUESTION, 'main request')],
1593
+ isCompleted: false,
1594
+ initialHistoryStatus: 'has_messages',
1595
+ agentRunStatus: 'RUNNING',
1596
+ });
1597
+
1598
+ try {
1599
+ renderConversation();
1600
+
1601
+ const initialCallCount = mocks.getActiveWorkstreams.mock.calls.length;
1602
+ expect(initialCallCount).toBeGreaterThan(0);
1603
+
1604
+ await act(async () => {
1605
+ vi.advanceTimersByTime(10_000);
1606
+ });
1607
+ expect(mocks.getActiveWorkstreams).toHaveBeenCalledTimes(initialCallCount);
1608
+
1609
+ await act(async () => {
1610
+ for (const resolveQuery of resolveQueries) {
1611
+ resolveQuery({ running: [] });
1612
+ }
1613
+ });
1614
+ await act(async () => {
1615
+ vi.advanceTimersByTime(10_000);
1616
+ });
1617
+ expect(mocks.getActiveWorkstreams.mock.calls.length).toBeGreaterThan(initialCallCount);
1618
+ } finally {
1619
+ vi.useRealTimers();
1620
+ }
1621
+ });
1622
+
608
1623
  it('hides the composer while viewing rewound playback history', () => {
609
1624
  mockStreamState({
610
1625
  messages: [
@@ -628,6 +1643,101 @@ describe('ModernAgentConversation send handling', () => {
628
1643
  expect(screen.getByRole('button', { name: 'composer send' })).not.toBeNull();
629
1644
  });
630
1645
 
1646
+ it('shows a disabled MCP request overlay while viewing rewound playback history', async () => {
1647
+ mockStreamState({
1648
+ messages: [
1649
+ {
1650
+ ...createMessage(AgentMessageType.REQUEST_INPUT, 'Connect to Jira to continue.'),
1651
+ timestamp: 1_000,
1652
+ details: {
1653
+ ux: {
1654
+ mcp_connect: {
1655
+ app_install_id: 'app1',
1656
+ collection_id: 'jira',
1657
+ name: 'Jira',
1658
+ },
1659
+ },
1660
+ },
1661
+ },
1662
+ {
1663
+ ...createMessage(AgentMessageType.QUESTION, "I've connected to Jira."),
1664
+ timestamp: 2_000,
1665
+ },
1666
+ ],
1667
+ isCompleted: false,
1668
+ agentRunStatus: 'RUNNING',
1669
+ });
1670
+
1671
+ renderConversation({ enablePlayback: true, hideMessageInput: false });
1672
+
1673
+ expect(screen.getByRole('button', { name: 'composer send' })).not.toBeNull();
1674
+
1675
+ fireEvent.click(screen.getByRole('button', { name: 'Jump to first message' }));
1676
+
1677
+ await waitFor(() => {
1678
+ expect(screen.getByText('Connect to Jira to continue.')).not.toBeNull();
1679
+ });
1680
+ expect(screen.queryByRole('button', { name: 'composer send' })).toBeNull();
1681
+ expect((screen.getByRole('button', { name: /connect/i }) as HTMLButtonElement).disabled).toBe(true);
1682
+ expect((screen.getByRole('button', { name: /decline/i }) as HTMLButtonElement).disabled).toBe(true);
1683
+ expect(mocks.allMessagesMixedProps.mock.lastCall?.[0]).toEqual(
1684
+ expect.objectContaining({ renderRequestInputControls: false }),
1685
+ );
1686
+ });
1687
+
1688
+ it('shows active workstreams for the rewound playback cursor', () => {
1689
+ mockStreamState({
1690
+ messages: [
1691
+ createMessage(AgentMessageType.QUESTION, 'Compare France and Japan news.'),
1692
+ {
1693
+ ...createMessage(AgentMessageType.UPDATE, 'Japan workstream launched.'),
1694
+ timestamp: 2_000,
1695
+ workstream_id: 'japan_news',
1696
+ details: {
1697
+ event_class: 'activity',
1698
+ workstream_event: 'launched',
1699
+ workstream_id: 'japan_news',
1700
+ interaction: 'sys:AdhocTaskAgent',
1701
+ },
1702
+ },
1703
+ {
1704
+ ...createMessage(AgentMessageType.THOUGHT, 'Japan internal search.'),
1705
+ timestamp: 3_000,
1706
+ workstream_id: 'japan_news',
1707
+ },
1708
+ {
1709
+ ...createMessage(AgentMessageType.UPDATE, 'France workstream launched.'),
1710
+ timestamp: 4_000,
1711
+ workstream_id: 'france_news',
1712
+ details: {
1713
+ event_class: 'activity',
1714
+ workstream_event: 'launched',
1715
+ workstream_id: 'france_news',
1716
+ interaction: 'sys:AdhocTaskAgent',
1717
+ },
1718
+ },
1719
+ ],
1720
+ isCompleted: false,
1721
+ agentRunStatus: 'RUNNING',
1722
+ });
1723
+
1724
+ renderConversation({ enablePlayback: true, hideMessageInput: false });
1725
+
1726
+ expect(document.querySelector('[data-agent-active-workstreams]')).toBeNull();
1727
+
1728
+ const playbackPositionInput = screen.getByRole('textbox', { name: 'Playback position' });
1729
+ fireEvent.change(playbackPositionInput, {
1730
+ target: { value: '2' },
1731
+ });
1732
+ fireEvent.blur(playbackPositionInput, {
1733
+ target: { value: '2' },
1734
+ });
1735
+
1736
+ expect(document.querySelector('[data-agent-active-workstreams]')).not.toBeNull();
1737
+ expect(screen.getByText('Japan News')).not.toBeNull();
1738
+ expect(screen.queryByRole('button', { name: 'composer send' })).toBeNull();
1739
+ });
1740
+
631
1741
  it('exposes a local header toggle for playback controls', () => {
632
1742
  mockStreamState({
633
1743
  messages: [