@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
@@ -6,13 +6,14 @@ import {
6
6
  type JSONSchema,
7
7
  type Plan,
8
8
  } from '@vertesia/common';
9
- import { cn } from '@vertesia/ui/core';
9
+ import { Badge, Button, cn, VTooltip } from '@vertesia/ui/core';
10
10
  import { i18nInstance, NAMESPACE, useUITranslation } from '@vertesia/ui/i18n';
11
11
  import { NavLink } from '@vertesia/ui/router';
12
12
  import { MarkdownRenderer, type MarkdownRendererProps } from '@vertesia/ui/widgets';
13
13
  import type { Element } from 'hast';
14
14
  import {
15
15
  AlertTriangle,
16
+ ArrowLeft,
16
17
  Bot,
17
18
  Brain,
18
19
  CheckCircle,
@@ -31,18 +32,29 @@ import { AskUserWidget } from '../AskUserWidget';
31
32
  import { ThinkingMessages } from '../WaitingMessages';
32
33
  import {
33
34
  formatWorkstreamName,
35
+ getWorkstreamActivityDetails,
34
36
  getWorkstreamDisplayName,
35
37
  getWorkstreamLaunchDetails,
36
38
  type WorkstreamLaunchDetails,
37
39
  } from '../workstreams.js';
38
40
  import { AttachmentPreviewList, parseUserMessageAttachments } from './AttachmentPreview';
39
41
  import BatchProgressPanel, { type BatchProgressPanelClassNames } from './BatchProgressPanel';
42
+ import { getMessageDeliveryStatus, MessageDeliveryStatus } from './MessageDeliveryStatus';
40
43
  import MessageItem, { type MessageItemClassNames, type MessageItemProps } from './MessageItem';
41
44
  import {
42
45
  getAnsweredRequestInputKeys,
46
+ getAnsweredToolApprovalRequestInputKeys,
47
+ getHiddenToolApprovalAnswerKeys,
43
48
  getPendingRequestInputMessage,
49
+ getRequestInputDisplayText,
44
50
  getRequestInputMessageKey,
51
+ getResolvedToolApprovalKeys,
52
+ hasRequestInputUx,
45
53
  isRequestInputAnswered,
54
+ isToolApprovalAnswerHidden,
55
+ isToolApprovalRequestInput,
56
+ isToolApprovalRequestInputHidden,
57
+ type RequestInputMessageWithUx,
46
58
  } from './requestInputMessages';
47
59
  import StreamingMessage, { type StreamingMessageClassNames } from './StreamingMessage';
48
60
  import {
@@ -57,10 +69,12 @@ import {
57
69
  import ToolCallGroup, { type ToolCallGroupClassNames } from './ToolCallGroup';
58
70
  import {
59
71
  DONE_STATES,
72
+ filterMessagesForActiveWorkstream,
60
73
  getWorkstreamId,
61
74
  groupMessagesWithStreaming,
62
75
  isInProgress,
63
76
  isToolPreambleMessage,
77
+ isUserStoppedMessage,
64
78
  mergeConsecutiveToolGroups,
65
79
  type RenderableGroup,
66
80
  type StreamingData,
@@ -108,6 +122,23 @@ function formatDuration(seconds: number): string {
108
122
  return remainingSeconds > 0 ? `${minutes}m ${remainingSeconds}s` : `${minutes}m`;
109
123
  }
110
124
 
125
+ function parseTimestampMs(timestamp: number | string | undefined): number | undefined {
126
+ if (timestamp === undefined || timestamp === null || timestamp === '') return undefined;
127
+ const value = typeof timestamp === 'number' ? timestamp : new Date(timestamp).getTime();
128
+ return Number.isFinite(value) ? value : undefined;
129
+ }
130
+
131
+ function formatToolDetailTimestamp(timestamp: number | string | undefined): string | undefined {
132
+ const value = parseTimestampMs(timestamp);
133
+ if (value === undefined) return undefined;
134
+
135
+ return new Intl.DateTimeFormat(undefined, {
136
+ hour: '2-digit',
137
+ minute: '2-digit',
138
+ second: '2-digit',
139
+ }).format(new Date(value));
140
+ }
141
+
111
142
  function getReadableToolLabel(message: AgentMessage): string {
112
143
  const details = message.details as
113
144
  | {
@@ -140,9 +171,7 @@ function getReadableToolLabel(message: AgentMessage): string {
140
171
  return 'Using tool';
141
172
  }
142
173
 
143
- function getSummaryWorkLabel(status: ToolExecutionStatus, isActive: boolean): string {
144
- if (status === 'error') return 'Work needs attention';
145
- if (status === 'warning') return 'Work had warnings';
174
+ function getSummaryWorkLabel(isActive: boolean): string {
146
175
  return isActive ? 'Working' : 'Worked';
147
176
  }
148
177
 
@@ -150,8 +179,8 @@ function isTransientThinkingWork(messages: AgentMessage[]): boolean {
150
179
  return messages.length > 0 && messages.every(isTransientWorkStatusMessage);
151
180
  }
152
181
 
153
- function getSummaryActivityLabel(status: ToolExecutionStatus, isActive: boolean): string {
154
- return getSummaryWorkLabel(status, isActive);
182
+ function getSummaryActivityLabel(isActive: boolean): string {
183
+ return getSummaryWorkLabel(isActive);
155
184
  }
156
185
 
157
186
  function hasOpenUserTurn(messages: AgentMessage[]): boolean {
@@ -160,6 +189,90 @@ function hasOpenUserTurn(messages: AgentMessage[]): boolean {
160
189
  return latestMessage?.type === AgentMessageType.QUESTION;
161
190
  }
162
191
 
192
+ function hasLatestToolApprovalAllowTurn(messages: AgentMessage[], hiddenToolApprovalAnswerKeys: Set<string>): boolean {
193
+ const mainMessages = messages.filter((message) => getWorkstreamId(message) === 'main');
194
+ const latestMessage = mainMessages[mainMessages.length - 1] ?? messages[messages.length - 1];
195
+ if (!latestMessage || !isToolApprovalAnswerHidden(latestMessage, hiddenToolApprovalAnswerKeys)) return false;
196
+
197
+ const response = getToolApprovalResponse(latestMessage);
198
+ return response === 'allow_once' || response === 'allow_for_run';
199
+ }
200
+
201
+ function getWriteArtifactCompletionPath(message: AgentMessage): string | undefined {
202
+ if (message.type !== AgentMessageType.UPDATE) return undefined;
203
+ const details = getDetailsRecord(message);
204
+ if (details.tool !== undefined || details.activity_group_id !== undefined) return undefined;
205
+ const path = typeof details.path === 'string' && details.path.trim() ? details.path.trim() : undefined;
206
+ if (!path) return undefined;
207
+
208
+ return getMessageText(message) === `Prepared and saved artifact to ${path}` ? path : undefined;
209
+ }
210
+
211
+ function findPreviousWriteArtifactToolMessage(messages: AgentMessage[], index: number): AgentMessage | undefined {
212
+ const workstreamId = getWorkstreamId(messages[index]);
213
+
214
+ for (let prevIndex = index - 1; prevIndex >= 0; prevIndex -= 1) {
215
+ const previous = messages[prevIndex];
216
+ if (getWorkstreamId(previous) !== workstreamId) continue;
217
+
218
+ const details = getDetailsRecord(previous);
219
+ if (details.tool === 'write_artifact' && typeof details.activity_group_id === 'string') return previous;
220
+
221
+ if (previous.type === AgentMessageType.REQUEST_INPUT) continue;
222
+ if (getToolApprovalResponse(previous)) continue;
223
+ if (details.display_role === 'thinking') continue;
224
+
225
+ if (typeof details.tool === 'string' && details.tool !== 'write_artifact') return undefined;
226
+ if (
227
+ previous.type === AgentMessageType.ANSWER ||
228
+ previous.type === AgentMessageType.COMPLETE ||
229
+ previous.type === AgentMessageType.IDLE ||
230
+ previous.type === AgentMessageType.TERMINATED
231
+ ) {
232
+ return undefined;
233
+ }
234
+ }
235
+
236
+ return undefined;
237
+ }
238
+
239
+ function attachWriteArtifactCompletionMessages(messages: AgentMessage[]): AgentMessage[] {
240
+ return messages.map((message, index) => {
241
+ const path = getWriteArtifactCompletionPath(message);
242
+ if (!path) return message;
243
+
244
+ const previousToolMessage = findPreviousWriteArtifactToolMessage(messages, index);
245
+ const previousDetails = previousToolMessage ? getDetailsRecord(previousToolMessage) : undefined;
246
+ if (!previousDetails) return message;
247
+
248
+ const activityGroupId =
249
+ typeof previousDetails.activity_group_id === 'string' ? previousDetails.activity_group_id : undefined;
250
+ const toolRunId = typeof previousDetails.tool_run_id === 'string' ? previousDetails.tool_run_id : undefined;
251
+ const toolUseId = typeof previousDetails.tool_use_id === 'string' ? previousDetails.tool_use_id : undefined;
252
+ const toolIteration =
253
+ typeof previousDetails.tool_iteration === 'number' ? previousDetails.tool_iteration : undefined;
254
+
255
+ if (!activityGroupId) return message;
256
+
257
+ return {
258
+ ...message,
259
+ details: {
260
+ ...message.details,
261
+ event_class: 'activity',
262
+ tool: 'write_artifact',
263
+ tool_run_id: toolRunId ?? 'write_artifact',
264
+ tool_use_id: toolUseId ?? toolRunId ?? 'write_artifact',
265
+ tool_iteration: toolIteration,
266
+ tool_status: 'completed',
267
+ tool_event: 'progress',
268
+ activity_group_id: activityGroupId,
269
+ output: getMessageText(message),
270
+ path,
271
+ },
272
+ };
273
+ });
274
+ }
275
+
163
276
  function getMessageText(message: AgentMessage): string {
164
277
  if (!message.message) return '';
165
278
  if (typeof message.message === 'object') return JSON.stringify(message.message, null, 2);
@@ -168,7 +281,8 @@ function getMessageText(message: AgentMessage): string {
168
281
 
169
282
  interface SummaryMessageProps {
170
283
  message: AgentMessage;
171
- onSendMessage?: (message: string) => void;
284
+ onSendMessage?: (message: string, metadata?: Record<string, unknown>) => void;
285
+ onSelectWorkstream?: (workstreamId: string) => void;
172
286
  requestInputAnswered?: boolean;
173
287
  StoreLinkComponent?: React.ComponentType<{ href: string; documentId: string; children: React.ReactNode }>;
174
288
  CollectionLinkComponent?: React.ComponentType<{ href: string; collectionId: string; children: React.ReactNode }>;
@@ -177,9 +291,11 @@ interface SummaryMessageProps {
177
291
  function SummaryWorkstreamLaunchMessage({
178
292
  message,
179
293
  details,
294
+ onSelectWorkstream,
180
295
  }: {
181
296
  message: AgentMessage;
182
297
  details: WorkstreamLaunchDetails;
298
+ onSelectWorkstream?: (workstreamId: string) => void;
183
299
  }) {
184
300
  const { t } = useUITranslation();
185
301
  const workstreamName = getWorkstreamDisplayName(details.workstreamId, details.interaction);
@@ -188,7 +304,15 @@ function SummaryWorkstreamLaunchMessage({
188
304
 
189
305
  return (
190
306
  <div className="mx-auto w-full max-w-3xl px-1" data-workstream-id={details.workstreamId}>
191
- <div className="flex items-start gap-3 border-b border-border/70 py-2 text-sm text-muted">
307
+ <button
308
+ type="button"
309
+ className={cn(
310
+ 'group flex w-full items-start gap-3 border-b border-border/70 py-2 text-start text-sm text-muted',
311
+ 'transition-colors hover:text-foreground focus-visible:outline-none focus-visible:ring-2',
312
+ 'focus-visible:ring-focus focus-visible:ring-offset-2 focus-visible:ring-offset-background',
313
+ )}
314
+ onClick={() => onSelectWorkstream?.(details.workstreamId)}
315
+ >
192
316
  <Bot className="mt-0.5 size-4 shrink-0 text-muted" aria-hidden="true" />
193
317
  <div className="min-w-0 flex-1">
194
318
  <div className="flex min-w-0 flex-wrap items-center gap-x-2 gap-y-1">
@@ -198,7 +322,9 @@ function SummaryWorkstreamLaunchMessage({
198
322
  {secondaryName && <div className="mt-0.5 truncate text-xs text-muted/75">{secondaryName}</div>}
199
323
  <span className="sr-only">{getMessageText(message)}</span>
200
324
  </div>
201
- </div>
325
+ {/* rtl-ok: chevron points and nudges toward inline-end in both directions. */}
326
+ <ChevronRight className="mt-0.5 size-4 shrink-0 text-muted transition-transform group-hover:translate-x-0.5 rtl:rotate-180 rtl:group-hover:-translate-x-0.5" />
327
+ </button>
202
328
  </div>
203
329
  );
204
330
  }
@@ -212,6 +338,8 @@ const SUMMARY_PROSE_CLASS = [
212
338
  ].join(' ');
213
339
 
214
340
  const USER_BUBBLE_COLLAPSE_THRESHOLD = 520;
341
+ const SUMMARY_THOUGHT_COLLAPSE_LINES = 6;
342
+ const SUMMARY_THOUGHT_COLLAPSE_THRESHOLD = 520;
215
343
  const STORE_LINK_MARKDOWN_RE =
216
344
  /\[[^\]]+\]\((?:\/store\/(?:objects|collections)\/|store:|document:|document:\/\/|collection:)[^)]+\)/;
217
345
  const DEFAULT_AGENT_MARKDOWN_COMPONENTS: MarkdownRendererProps['components'] = {
@@ -428,12 +556,14 @@ function useRotatingThinkingMessageIndex(enabled = true): number {
428
556
 
429
557
  function SummaryUserBubble({
430
558
  children,
559
+ message,
431
560
  workstreamId,
432
561
  className,
433
562
  artifactRunId,
434
563
  markdownComponents,
435
564
  }: {
436
565
  children: React.ReactNode;
566
+ message?: AgentMessage;
437
567
  workstreamId?: string;
438
568
  className?: string;
439
569
  artifactRunId?: string;
@@ -446,6 +576,7 @@ function SummaryUserBubble({
446
576
  const isPlainText = typeof children === 'string' || typeof children === 'number';
447
577
  const textContent = isPlainText ? String(children) : '';
448
578
  const shouldRenderMarkdown = typeof children === 'string' && STORE_LINK_MARKDOWN_RE.test(children);
579
+ const deliveryStatus = message ? getMessageDeliveryStatus(message) : undefined;
449
580
 
450
581
  useEffect(() => {
451
582
  if (!shouldCollapse && isExpanded) {
@@ -502,6 +633,11 @@ function SummaryUserBubble({
502
633
  />
503
634
  </button>
504
635
  )}
636
+ {message && deliveryStatus ? (
637
+ <div className="mt-1.5 flex justify-end">
638
+ <MessageDeliveryStatus message={message} className="h-4 w-4" />
639
+ </div>
640
+ ) : null}
505
641
  </div>
506
642
  </div>
507
643
  );
@@ -510,12 +646,14 @@ function SummaryUserBubble({
510
646
  function SummaryMessage({
511
647
  message,
512
648
  onSendMessage,
649
+ onSelectWorkstream,
513
650
  requestInputAnswered = false,
514
651
  StoreLinkComponent,
515
652
  CollectionLinkComponent,
516
653
  }: SummaryMessageProps) {
517
- const content = getMessageText(message);
518
- const workstreamLaunchDetails = getWorkstreamLaunchDetails(message);
654
+ const content =
655
+ message.type === AgentMessageType.REQUEST_INPUT ? getRequestInputDisplayText(message) : getMessageText(message);
656
+ const workstreamLaunchDetails = getWorkstreamLaunchDetails(message) ?? getWorkstreamActivityDetails(message);
519
657
  const workstreamId = getWorkstreamId(message);
520
658
  const runId = (message as { workflow_run_id?: string }).workflow_run_id;
521
659
  const parsedQuestion = useMemo(
@@ -574,7 +712,13 @@ function SummaryMessage({
574
712
  );
575
713
 
576
714
  if (workstreamLaunchDetails) {
577
- return <SummaryWorkstreamLaunchMessage message={message} details={workstreamLaunchDetails} />;
715
+ return (
716
+ <SummaryWorkstreamLaunchMessage
717
+ message={message}
718
+ details={workstreamLaunchDetails}
719
+ onSelectWorkstream={onSelectWorkstream}
720
+ />
721
+ );
578
722
  }
579
723
 
580
724
  if (message.type === AgentMessageType.QUESTION) {
@@ -597,6 +741,7 @@ function SummaryMessage({
597
741
  )}
598
742
  {questionBody && (
599
743
  <SummaryUserBubble
744
+ message={message}
600
745
  workstreamId={workstreamId}
601
746
  artifactRunId={runId}
602
747
  markdownComponents={markdownComponents}
@@ -620,6 +765,10 @@ function SummaryMessage({
620
765
  multiSelect={uxConfig.multiSelect}
621
766
  onSelect={(optionId) => onSendMessage?.(optionId)}
622
767
  onMultiSelect={(optionIds) => onSendMessage?.(optionIds.join(', '))}
768
+ allowFreeResponse={!uxConfig.options?.length || !!uxConfig.free_response}
769
+ placeholder={uxConfig.free_response?.placeholder}
770
+ submitLabel={uxConfig.free_response?.submit_label}
771
+ onSubmit={(value) => onSendMessage?.(value, uxConfig.free_response?.metadata)}
623
772
  hideBorder
624
773
  compact
625
774
  answered={requestInputAnswered}
@@ -868,12 +1017,21 @@ interface SummaryToolDetailItem {
868
1017
  kind: SummaryToolDetailKind;
869
1018
  label: string;
870
1019
  title: string;
1020
+ isPreamble?: boolean;
871
1021
  command?: string;
872
1022
  text?: string;
1023
+ toolName?: string;
1024
+ startedAt?: number | string;
1025
+ finishedAt?: number | string;
873
1026
  status?: ToolExecutionStatus;
1027
+ decisionText?: string;
874
1028
  sections: SummaryToolDetailSection[];
875
1029
  }
876
1030
 
1031
+ const SUMMARY_TOOL_STARTED_AT_DETAIL_KEY = '_summary_started_at';
1032
+ const SUMMARY_TOOL_FINISHED_AT_DETAIL_KEY = '_summary_finished_at';
1033
+ const SYNTHETIC_TOOL_APPROVAL_EVENT_SOURCE = 'synthetic_tool_approval_decision';
1034
+
877
1035
  const TOOL_DETAIL_SYSTEM_KEYS = new Set([
878
1036
  'account_id',
879
1037
  'activity_group_id',
@@ -899,6 +1057,15 @@ const TOOL_DETAIL_SYSTEM_KEYS = new Set([
899
1057
  'tools',
900
1058
  'message_to_human',
901
1059
  'progress_messages',
1060
+ 'source',
1061
+ 'token_usage',
1062
+ 'checkpoint_at',
1063
+ 'checkpoint_threshold',
1064
+ 'approval_decision',
1065
+ 'approval_request',
1066
+ 'cancellation_reason',
1067
+ SUMMARY_TOOL_STARTED_AT_DETAIL_KEY,
1068
+ SUMMARY_TOOL_FINISHED_AT_DETAIL_KEY,
902
1069
  'workflow_run_id',
903
1070
  ]);
904
1071
 
@@ -906,6 +1073,12 @@ function getDetailsRecord(message: AgentMessage): Record<string, unknown> {
906
1073
  return isRecordValue(message.details) ? message.details : {};
907
1074
  }
908
1075
 
1076
+ function isDocumentPanelEventMessage(message: AgentMessage): boolean {
1077
+ if (message.type !== AgentMessageType.UPDATE) return false;
1078
+ const eventClass = getDetailsRecord(message).event_class;
1079
+ return eventClass === 'document_created' || eventClass === 'document_updated';
1080
+ }
1081
+
909
1082
  function humanizeIdentifier(value: string): string {
910
1083
  return value
911
1084
  .replace(/[_-]+/g, ' ')
@@ -930,6 +1103,15 @@ function getToolNames(details: Record<string, unknown>): string[] {
930
1103
  return names;
931
1104
  }
932
1105
 
1106
+ function getUniqueToolName(details: Record<string, unknown>): string | undefined {
1107
+ const names = Array.from(new Set(getToolNames(details)));
1108
+ return names.length > 0 ? names.join(', ') : undefined;
1109
+ }
1110
+
1111
+ function getTimestampDetail(value: unknown): number | string | undefined {
1112
+ return typeof value === 'number' || typeof value === 'string' ? value : undefined;
1113
+ }
1114
+
933
1115
  const TOOL_TARGET_KEYS = [
934
1116
  'query',
935
1117
  'path',
@@ -958,15 +1140,13 @@ function getToolDetailKind(message: AgentMessage): SummaryToolDetailKind {
958
1140
  const details = getDetailsRecord(message);
959
1141
  const toolNames = getToolNames(details).join(' ').toLowerCase();
960
1142
  const concreteTool = typeof details.tool === 'string' ? details.tool.toLowerCase() : '';
961
- const text = getMessageText(message).toLowerCase();
962
- const haystack = `${toolNames} ${text}`;
1143
+ const classifierText = concreteTool || toolNames;
963
1144
 
964
1145
  if (isToolPreambleMessage(message)) return 'think';
965
- if (concreteTool.startsWith('learn_') || haystack.includes('learn_') || /\bskill\b/.test(text)) return 'skill';
1146
+ if (concreteTool.startsWith('learn_') || toolNames.includes('learn_')) return 'skill';
966
1147
  if (concreteTool === 'discover_tools') return 'discover';
967
1148
  if (message.type === AgentMessageType.THOUGHT && !concreteTool) return 'think';
968
1149
 
969
- const classifierText = concreteTool || haystack;
970
1150
  if (classifierText.includes('search') || classifierText.includes('web') || classifierText.includes('fetch'))
971
1151
  return 'search';
972
1152
  if (classifierText.includes('read') || classifierText.includes('document') || classifierText.includes('file'))
@@ -1022,6 +1202,135 @@ function getToolTarget(details: Record<string, unknown>): string | undefined {
1022
1202
  return undefined;
1023
1203
  }
1024
1204
 
1205
+ function getApprovalDecisionLabel(decision: unknown, toolLabel: string): string | undefined {
1206
+ switch (decision) {
1207
+ case 'denied':
1208
+ case 'denied_with_feedback':
1209
+ return `User declined to use ${toolLabel}.`;
1210
+ case 'timeout':
1211
+ return `Approval timed out for ${toolLabel}.`;
1212
+ case 'reviewer_denied':
1213
+ return `Approval reviewer denied ${toolLabel}.`;
1214
+ case 'cancelled_after_denial':
1215
+ return `Cancelled ${toolLabel} after another tool was denied.`;
1216
+ default:
1217
+ return undefined;
1218
+ }
1219
+ }
1220
+
1221
+ function getApprovalDecisionStatusText(decision: unknown): string | undefined {
1222
+ switch (decision) {
1223
+ case 'denied':
1224
+ case 'denied_with_feedback':
1225
+ return 'Declined by user';
1226
+ case 'timeout':
1227
+ return 'Approval timed out';
1228
+ case 'reviewer_denied':
1229
+ return 'Denied by reviewer';
1230
+ case 'cancelled_after_denial':
1231
+ return 'Cancelled after denial';
1232
+ default:
1233
+ return undefined;
1234
+ }
1235
+ }
1236
+
1237
+ function getToolApprovalRecord(message: AgentMessage): Record<string, unknown> | undefined {
1238
+ const details = getDetailsRecord(message);
1239
+ const toolApproval = details.tool_approval;
1240
+ return isRecordValue(toolApproval) ? toolApproval : undefined;
1241
+ }
1242
+
1243
+ function getApprovalKeyFromToolApproval(toolApproval: Record<string, unknown> | undefined): string | undefined {
1244
+ const approvalKey = toolApproval?.approval_key;
1245
+ return typeof approvalKey === 'string' && approvalKey.trim() ? approvalKey.trim() : undefined;
1246
+ }
1247
+
1248
+ function getToolApprovalResponse(message: AgentMessage): 'allow_once' | 'allow_for_run' | 'deny' | undefined {
1249
+ if (message.type !== AgentMessageType.QUESTION) return undefined;
1250
+ const normalized = getMessageText(message).trim().toLowerCase();
1251
+ if (normalized === 'allow_once' || normalized === 'allow_for_run' || normalized === 'deny') return normalized;
1252
+ return undefined;
1253
+ }
1254
+
1255
+ function getToolApprovalDisplayName(toolApproval: Record<string, unknown>): string {
1256
+ const title = toolApproval.tool_title;
1257
+ if (typeof title === 'string' && title.trim()) return title.trim();
1258
+ const name = toolApproval.tool_name;
1259
+ if (typeof name === 'string' && name.trim()) return humanizeIdentifier(name.trim());
1260
+ return 'tool action';
1261
+ }
1262
+
1263
+ function buildSyntheticToolApprovalDecisionMessages(
1264
+ messages: AgentMessage[],
1265
+ resolvedToolApprovalKeys: Set<string>,
1266
+ ): AgentMessage[] {
1267
+ const syntheticMessages: AgentMessage[] = [];
1268
+
1269
+ messages.forEach((message, index) => {
1270
+ if (message.type !== AgentMessageType.REQUEST_INPUT) return;
1271
+
1272
+ const toolApproval = getToolApprovalRecord(message);
1273
+ const approvalKey = getApprovalKeyFromToolApproval(toolApproval);
1274
+ if (!toolApproval || !approvalKey || resolvedToolApprovalKeys.has(approvalKey)) return;
1275
+
1276
+ const workstreamId = getWorkstreamId(message);
1277
+ let responseMessage: AgentMessage | undefined;
1278
+ for (let nextIndex = index + 1; nextIndex < messages.length; nextIndex += 1) {
1279
+ const nextMessage = messages[nextIndex];
1280
+ if (getWorkstreamId(nextMessage) !== workstreamId) continue;
1281
+ if (nextMessage.type === AgentMessageType.REQUEST_INPUT) break;
1282
+ if (nextMessage.type === AgentMessageType.QUESTION) {
1283
+ responseMessage = nextMessage;
1284
+ break;
1285
+ }
1286
+ }
1287
+
1288
+ if (!responseMessage || getToolApprovalResponse(responseMessage) !== 'deny') return;
1289
+
1290
+ const toolName =
1291
+ typeof toolApproval.tool_name === 'string' && toolApproval.tool_name.trim()
1292
+ ? toolApproval.tool_name.trim()
1293
+ : 'tool_action';
1294
+ const toolLabel = getToolApprovalDisplayName(toolApproval);
1295
+ const target = typeof toolApproval.target === 'string' && toolApproval.target.trim() ? toolApproval.target : '';
1296
+ const approvalRequest = {
1297
+ tool_name: toolName,
1298
+ tool_title: toolApproval.tool_title,
1299
+ action_summary: toolApproval.action_summary,
1300
+ target: toolApproval.target,
1301
+ approval_key: approvalKey,
1302
+ };
1303
+
1304
+ syntheticMessages.push({
1305
+ timestamp: responseMessage.timestamp ?? message.timestamp,
1306
+ workflow_run_id: responseMessage.workflow_run_id || message.workflow_run_id,
1307
+ type: AgentMessageType.THOUGHT,
1308
+ message: getApprovalDecisionLabel('denied', toolLabel) ?? `User declined to use ${toolLabel}.`,
1309
+ workstream_id: workstreamId,
1310
+ details: {
1311
+ event_class: 'activity',
1312
+ tool: toolName,
1313
+ tool_run_id: `approval:${approvalKey}`,
1314
+ tool_use_id: `approval:${approvalKey}`,
1315
+ tool_status: 'error',
1316
+ tool_event: 'failed',
1317
+ activity_group_id: `approval:${approvalKey}`,
1318
+ approval_decision: 'denied',
1319
+ approval_request: approvalRequest,
1320
+ input: toolApproval.input,
1321
+ target,
1322
+ observation: 'The user declined this tool action.',
1323
+ source: SYNTHETIC_TOOL_APPROVAL_EVENT_SOURCE,
1324
+ },
1325
+ });
1326
+ });
1327
+
1328
+ if (syntheticMessages.length === 0) return messages;
1329
+ return [...messages, ...syntheticMessages].sort(
1330
+ (a, b) => getTimestampMs(a.timestamp) - getTimestampMs(b.timestamp),
1331
+ );
1332
+ }
1333
+
1025
1334
  function compactInlineText(value: string, maxLength = 160): string {
1026
1335
  const normalized = value.replace(/\s+/g, ' ').trim();
1027
1336
  return normalized.length > maxLength ? `${normalized.slice(0, maxLength - 1)}...` : normalized;
@@ -1082,6 +1391,16 @@ function getRenderableGroupKey(group: RenderableGroup): string {
1082
1391
  return ['group', group.toolRunId, group.firstTimestamp, firstKey, lastKey].filter(Boolean).join(':');
1083
1392
  }
1084
1393
 
1394
+ function getPreviousRenderableGroupTimestamp(groups: RenderableGroup[], index: number): number | string | undefined {
1395
+ for (let previousIndex = index - 1; previousIndex >= 0; previousIndex--) {
1396
+ const group = groups[previousIndex];
1397
+ if (group.type === 'single') return group.message.timestamp;
1398
+ if (group.type === 'tool_group') return group.firstTimestamp;
1399
+ if (group.type === 'streaming') return group.startTimestamp;
1400
+ }
1401
+ return undefined;
1402
+ }
1403
+
1085
1404
  function formatToolSectionValue(value: unknown): string {
1086
1405
  const text = stringifyRequestValue(value).trim();
1087
1406
  return text.length > 2400 ? `${text.slice(0, 2400)}\n...` : text;
@@ -1132,16 +1451,11 @@ function getToolDetailSections(message: AgentMessage): SummaryToolDetailSection[
1132
1451
 
1133
1452
  addSection('Query', ['query']);
1134
1453
  addSection('Input', ['input', 'params', 'arguments', 'args']);
1135
- addSection('Output', [
1136
- 'output',
1137
- 'stdout',
1138
- 'result',
1139
- 'results',
1140
- 'content',
1141
- 'result_summary',
1142
- 'observation',
1143
- 'display_message',
1144
- ]);
1454
+ addSection(
1455
+ 'Output',
1456
+ ['output', 'stdout', 'result', 'results', 'content', 'result_summary', 'observation', 'display_message'],
1457
+ typeof details.approval_decision === 'string' ? 'error' : undefined,
1458
+ );
1145
1459
  addSection('Files', ['files', 'outputFiles']);
1146
1460
  addSection('Error', ['error', 'stderr'], 'error');
1147
1461
  if (targetEntry) consumedKeys.add(targetEntry.key);
@@ -1155,21 +1469,30 @@ function getToolDetailSections(message: AgentMessage): SummaryToolDetailSection[
1155
1469
  }
1156
1470
 
1157
1471
  function buildSummaryToolDetailItem(message: AgentMessage, index: number): SummaryToolDetailItem | undefined {
1472
+ if (message.type === AgentMessageType.REQUEST_INPUT) return undefined;
1473
+
1158
1474
  const text = getMessageText(message);
1159
1475
  const details = getDetailsRecord(message);
1476
+ const isPreamble = isToolPreambleMessage(message);
1160
1477
  const kind = getToolDetailKind(message);
1161
1478
  const toolNames = getToolNames(details);
1162
1479
  const target = getToolTarget(details);
1163
1480
  const command = typeof details.command === 'string' && details.command.trim() ? details.command.trim() : undefined;
1164
1481
  const fallbackTitle = toolNames[0] ? humanizeIdentifier(toolNames[0]) : getReadableToolLabel(message);
1482
+ const approvalDecisionTitle = getApprovalDecisionLabel(details.approval_decision, fallbackTitle);
1483
+ const decisionText = getApprovalDecisionStatusText(details.approval_decision);
1484
+ const messageToHuman =
1485
+ typeof details.message_to_human === 'string' && details.message_to_human.trim()
1486
+ ? details.message_to_human.trim()
1487
+ : undefined;
1165
1488
  const title =
1166
1489
  kind === 'think'
1167
1490
  ? text || fallbackTitle
1168
1491
  : kind === 'command'
1169
- ? compactInlineText(text || command || fallbackTitle)
1170
- : compactInlineText(target || text || fallbackTitle);
1492
+ ? compactInlineText(messageToHuman || (!approvalDecisionTitle ? text : '') || command || fallbackTitle)
1493
+ : compactInlineText(messageToHuman || target || (!approvalDecisionTitle ? text : '') || fallbackTitle);
1171
1494
  const normalizedText = text ? (kind === 'think' ? text : compactInlineText(text, 420)) : undefined;
1172
- const shouldShowText = normalizedText && normalizedText !== title;
1495
+ const shouldShowText = normalizedText && normalizedText !== title && !approvalDecisionTitle;
1173
1496
 
1174
1497
  if (!title && !shouldShowText) return undefined;
1175
1498
 
@@ -1178,41 +1501,130 @@ function buildSummaryToolDetailItem(message: AgentMessage, index: number): Summa
1178
1501
  kind,
1179
1502
  label: getToolDetailLabel(kind),
1180
1503
  title,
1504
+ isPreamble,
1181
1505
  command,
1182
1506
  text: shouldShowText ? normalizedText : undefined,
1183
- status: details.tool_status as ToolExecutionStatus | undefined,
1507
+ toolName: getUniqueToolName(details),
1508
+ startedAt: getTimestampDetail(details[SUMMARY_TOOL_STARTED_AT_DETAIL_KEY]) ?? message.timestamp,
1509
+ finishedAt: getTimestampDetail(details[SUMMARY_TOOL_FINISHED_AT_DETAIL_KEY]) ?? message.timestamp,
1510
+ status: approvalDecisionTitle ? 'error' : (details.tool_status as ToolExecutionStatus | undefined),
1511
+ decisionText,
1184
1512
  sections: getToolDetailSections(message),
1185
1513
  };
1186
1514
  }
1187
1515
 
1516
+ function isPlanToolMessage(message: AgentMessage): boolean {
1517
+ const details = getDetailsRecord(message);
1518
+ return details.tool === 'plan' || details.tool === 'update_plan';
1519
+ }
1520
+
1521
+ function isPlanLifecycleMessage(message: AgentMessage): boolean {
1522
+ const details = getDetailsRecord(message);
1523
+ if (message.type === AgentMessageType.UPDATE && Array.isArray(details.updates)) return true;
1524
+ return message.type === AgentMessageType.PLAN && Array.isArray(details.plan);
1525
+ }
1526
+
1527
+ function getActivityGroupIdentity(details: Record<string, unknown>): string | undefined {
1528
+ return typeof details.activity_group_id === 'string' && details.activity_group_id.trim()
1529
+ ? details.activity_group_id.trim()
1530
+ : undefined;
1531
+ }
1532
+
1533
+ function getToolIdentity(details: Record<string, unknown>): string | undefined {
1534
+ if (typeof details.tool_use_id === 'string' && details.tool_use_id.trim()) return details.tool_use_id.trim();
1535
+ if (typeof details.tool_run_id === 'string' && details.tool_run_id.trim()) return details.tool_run_id.trim();
1536
+ if (typeof details.tool === 'string' && details.tool.trim()) return details.tool.trim();
1537
+ return undefined;
1538
+ }
1539
+
1540
+ function getSplitActivityGroups(messages: AgentMessage[]): Set<string> {
1541
+ const startedToolIdentities = new Map<string, Set<string>>();
1542
+ const approvalDecisionToolIdentities = new Map<string, Set<string>>();
1543
+
1544
+ const addIdentity = (
1545
+ groups: Map<string, Set<string>>,
1546
+ activityGroupId: string | undefined,
1547
+ toolIdentity: string | undefined,
1548
+ ) => {
1549
+ if (!activityGroupId || !toolIdentity) return;
1550
+ const identities = groups.get(activityGroupId) ?? new Set<string>();
1551
+ identities.add(toolIdentity);
1552
+ groups.set(activityGroupId, identities);
1553
+ };
1554
+
1555
+ for (const message of messages) {
1556
+ const details = getDetailsRecord(message);
1557
+ const activityGroupId = getActivityGroupIdentity(details);
1558
+ const toolIdentity = getToolIdentity(details);
1559
+
1560
+ if (details.tool_event === 'started') {
1561
+ addIdentity(startedToolIdentities, activityGroupId, toolIdentity);
1562
+ }
1563
+ if (typeof details.approval_decision === 'string') {
1564
+ addIdentity(approvalDecisionToolIdentities, activityGroupId, toolIdentity);
1565
+ }
1566
+ }
1567
+
1568
+ return new Set(
1569
+ [...startedToolIdentities.entries(), ...approvalDecisionToolIdentities.entries()]
1570
+ .filter(([, identities]) => identities.size > 1)
1571
+ .map(([activityGroupId]) => activityGroupId),
1572
+ );
1573
+ }
1574
+
1575
+ function getSummaryToolGroupId(details: Record<string, unknown>, splitActivityGroups: Set<string>): string | undefined {
1576
+ const activityGroupId = getActivityGroupIdentity(details);
1577
+ const toolIdentity = getToolIdentity(details);
1578
+
1579
+ if (activityGroupId) {
1580
+ if (toolIdentity && splitActivityGroups.has(activityGroupId)) {
1581
+ return `activity:${activityGroupId}:tool:${toolIdentity}`;
1582
+ }
1583
+ return `activity:${activityGroupId}`;
1584
+ }
1585
+
1586
+ if (typeof details.tool_use_id === 'string' && details.tool_use_id.trim()) {
1587
+ return `tool-use:${details.tool_use_id.trim()}`;
1588
+ }
1589
+ if (typeof details.tool_run_id === 'string' && details.tool_run_id.trim())
1590
+ return `run:${details.tool_run_id.trim()}`;
1591
+ return undefined;
1592
+ }
1593
+
1188
1594
  function mergeSummaryToolMessages(messages: AgentMessage[]): AgentMessage[] {
1189
1595
  const byGroupId = new Map<string, { index: number; messages: AgentMessage[] }>();
1190
1596
  const ungrouped: Array<{ index: number; message: AgentMessage }> = [];
1597
+ let currentPlanGroupId: string | undefined;
1598
+ const splitActivityGroups = getSplitActivityGroups(messages);
1191
1599
 
1192
1600
  messages.forEach((message, index) => {
1193
1601
  const details = getDetailsRecord(message);
1194
1602
  if (isToolPreambleMessage(message)) {
1195
1603
  ungrouped.push({ index, message });
1604
+ currentPlanGroupId = undefined;
1196
1605
  return;
1197
1606
  }
1198
1607
 
1199
- const activityGroupId =
1200
- typeof details.activity_group_id === 'string' && details.activity_group_id.trim()
1201
- ? details.activity_group_id
1202
- : undefined;
1203
- const runId =
1204
- typeof details.tool_run_id === 'string' && details.tool_run_id.trim() ? details.tool_run_id : undefined;
1205
- const groupId = activityGroupId ? `activity:${activityGroupId}` : runId ? `run:${runId}` : undefined;
1206
- if (!groupId) {
1608
+ const groupId = getSummaryToolGroupId(details, splitActivityGroups);
1609
+ const legacyPlanGroupId = !groupId && isPlanLifecycleMessage(message) ? currentPlanGroupId : undefined;
1610
+ const effectiveGroupId = groupId ?? legacyPlanGroupId;
1611
+
1612
+ if (!effectiveGroupId) {
1207
1613
  ungrouped.push({ index, message });
1208
1614
  return;
1209
1615
  }
1210
1616
 
1211
- const group = byGroupId.get(groupId);
1617
+ const group = byGroupId.get(effectiveGroupId);
1212
1618
  if (group) {
1213
1619
  group.messages.push(message);
1214
1620
  } else {
1215
- byGroupId.set(groupId, { index, messages: [message] });
1621
+ byGroupId.set(effectiveGroupId, { index, messages: [message] });
1622
+ }
1623
+
1624
+ if (isPlanToolMessage(message)) {
1625
+ currentPlanGroupId = effectiveGroupId;
1626
+ } else if (message.type === AgentMessageType.PLAN) {
1627
+ currentPlanGroupId = undefined;
1216
1628
  }
1217
1629
  });
1218
1630
 
@@ -1223,6 +1635,10 @@ function mergeSummaryToolMessages(messages: AgentMessage[]): AgentMessage[] {
1223
1635
  const baseMessage = sortedMessages[sortedMessages.length - 1];
1224
1636
  const startMessage = sortedMessages.find((message) => getDetailsRecord(message).tool_event === 'started');
1225
1637
  const firstTextMessage = sortedMessages.find((message) => getMessageText(message));
1638
+ const latestApprovalDecisionMessage = sortedMessages.findLast((message) => {
1639
+ const details = getDetailsRecord(message);
1640
+ return typeof details.approval_decision === 'string' && Boolean(getMessageText(message));
1641
+ });
1226
1642
  const commandTextMessage = sortedMessages.findLast((message) => getMessageText(message).startsWith('$ '));
1227
1643
  const latestStatusMessage = sortedMessages.findLast(
1228
1644
  (message) =>
@@ -1236,9 +1652,19 @@ function mergeSummaryToolMessages(messages: AgentMessage[]): AgentMessage[] {
1236
1652
  if (latestStatusMessage && mergedDetails.error === undefined && mergedDetails.stderr === undefined) {
1237
1653
  mergedDetails.error = getMessageText(latestStatusMessage);
1238
1654
  }
1655
+ if (
1656
+ !latestStatusMessage &&
1657
+ sortedMessages.some(
1658
+ (message) => message.type === AgentMessageType.PLAN && Array.isArray(message.details?.plan),
1659
+ )
1660
+ ) {
1661
+ mergedDetails.tool_status = 'completed';
1662
+ }
1239
1663
  if (commandTextMessage && mergedDetails.command === undefined) {
1240
1664
  mergedDetails.command = getMessageText(commandTextMessage);
1241
1665
  }
1666
+ mergedDetails[SUMMARY_TOOL_STARTED_AT_DETAIL_KEY] = sortedMessages[0]?.timestamp;
1667
+ mergedDetails[SUMMARY_TOOL_FINISHED_AT_DETAIL_KEY] = sortedMessages[sortedMessages.length - 1]?.timestamp;
1242
1668
  const messageToHuman =
1243
1669
  typeof startMessage?.details?.message_to_human === 'string' ? startMessage.details.message_to_human : '';
1244
1670
 
@@ -1246,7 +1672,10 @@ function mergeSummaryToolMessages(messages: AgentMessage[]): AgentMessage[] {
1246
1672
  index,
1247
1673
  message: {
1248
1674
  ...baseMessage,
1249
- message: messageToHuman || (firstTextMessage ? firstTextMessage.message : baseMessage.message),
1675
+ message:
1676
+ latestApprovalDecisionMessage?.message ||
1677
+ messageToHuman ||
1678
+ (firstTextMessage ? firstTextMessage.message : baseMessage.message),
1250
1679
  details: mergedDetails,
1251
1680
  },
1252
1681
  };
@@ -1296,6 +1725,54 @@ function ToolDetailIcon({ kind, status }: { kind: SummaryToolDetailKind; status?
1296
1725
  }
1297
1726
  }
1298
1727
 
1728
+ interface ToolDetailMetadataEntry {
1729
+ label: string;
1730
+ value: string;
1731
+ }
1732
+
1733
+ function getToolDetailMetadata(item: SummaryToolDetailItem): ToolDetailMetadataEntry[] {
1734
+ const entries: ToolDetailMetadataEntry[] = [];
1735
+ const startedMs = parseTimestampMs(item.startedAt);
1736
+ const finishedMs = parseTimestampMs(item.finishedAt);
1737
+ const startedAt = formatToolDetailTimestamp(item.startedAt);
1738
+ const finishedAt = formatToolDetailTimestamp(item.finishedAt);
1739
+
1740
+ if (item.toolName) {
1741
+ entries.push({ label: 'Tool', value: item.toolName });
1742
+ }
1743
+
1744
+ if (startedAt && finishedAt && startedMs !== undefined && finishedMs !== undefined && startedMs !== finishedMs) {
1745
+ entries.push({ label: 'Started', value: startedAt });
1746
+ entries.push({ label: 'Ended', value: finishedAt });
1747
+ entries.push({ label: 'Duration', value: formatDuration(getDurationSeconds(item.startedAt, item.finishedAt)) });
1748
+ } else if (startedAt || finishedAt) {
1749
+ entries.push({ label: 'Time', value: startedAt ?? finishedAt ?? '' });
1750
+ }
1751
+
1752
+ return entries;
1753
+ }
1754
+
1755
+ function ToolDetailMetadata({ hasDetailContent, item }: { hasDetailContent: boolean; item: SummaryToolDetailItem }) {
1756
+ const entries = getToolDetailMetadata(item);
1757
+ if (entries.length === 0) return null;
1758
+
1759
+ return (
1760
+ <dl
1761
+ className={cn(
1762
+ 'flex flex-wrap gap-x-4 gap-y-1 text-xs',
1763
+ hasDetailContent && 'mb-3 border-b border-border/60 pb-2',
1764
+ )}
1765
+ >
1766
+ {entries.map((entry) => (
1767
+ <div key={entry.label} className="flex min-w-0 items-baseline gap-1.5">
1768
+ <dt className="shrink-0 text-muted">{entry.label}</dt>
1769
+ <dd className="min-w-0 wrap-break-word font-medium text-foreground/80">{entry.value}</dd>
1770
+ </div>
1771
+ ))}
1772
+ </dl>
1773
+ );
1774
+ }
1775
+
1299
1776
  function ToolDetailSection({ section }: { section: SummaryToolDetailSection }) {
1300
1777
  const value = section.value;
1301
1778
  const isPrimitive = typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean';
@@ -1363,6 +1840,8 @@ function formatToolPrimaryText(item: SummaryToolDetailItem): string {
1363
1840
 
1364
1841
  function formatToolDetailCopyText(item: SummaryToolDetailItem): string {
1365
1842
  const parts = [getToolPanelTitle(item), formatToolPrimaryText(item)];
1843
+ const metadata = getToolDetailMetadata(item).map((entry) => `${entry.label}: ${entry.value}`);
1844
+ if (metadata.length > 0) parts.push(metadata.join('\n'));
1366
1845
  if (item.text) parts.push(item.text);
1367
1846
 
1368
1847
  for (const section of item.sections) {
@@ -1404,9 +1883,10 @@ async function copyTextToClipboard(text: string): Promise<boolean> {
1404
1883
  }
1405
1884
 
1406
1885
  function SummaryToolDetailPanel({ item }: { item: SummaryToolDetailItem }) {
1407
- const copyText = formatToolDetailCopyText(item);
1408
1886
  const commandText = item.kind === 'command' && item.command ? formatToolPrimaryText(item) : undefined;
1409
1887
  const fallbackText = !commandText && item.sections.length === 0 ? item.text : undefined;
1888
+ const hasDetailContent = Boolean(commandText || fallbackText || item.sections.length);
1889
+ const copyText = formatToolDetailCopyText(item);
1410
1890
  const [copyState, setCopyState] = useState<'idle' | 'copied' | 'failed'>('idle');
1411
1891
  const copyResetRef = useRef<number | undefined>(undefined);
1412
1892
 
@@ -1426,23 +1906,26 @@ function SummaryToolDetailPanel({ item }: { item: SummaryToolDetailItem }) {
1426
1906
 
1427
1907
  return (
1428
1908
  <div className="relative mt-2 rounded-lg border border-border/70 bg-mixer-muted/10 p-3 shadow-sm">
1429
- <div className="absolute end-2 top-2">
1430
- <button
1431
- type="button"
1432
- className={cn(
1433
- 'inline-flex size-7 shrink-0 items-center justify-center rounded-md text-muted opacity-70 transition',
1434
- 'hover:bg-mixer-muted/20 hover:opacity-100 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring',
1435
- copyState === 'copied' && 'text-success opacity-100',
1436
- copyState === 'failed' && 'text-destructive opacity-100',
1437
- )}
1438
- onClick={copyDetails}
1439
- aria-label="Copy tool details"
1440
- title="Copy tool details"
1441
- >
1442
- {copyState === 'copied' ? <CheckCircle className="size-4" /> : <CopyIcon className="size-4" />}
1443
- </button>
1444
- </div>
1445
- <div className="pe-7">
1909
+ {hasDetailContent ? (
1910
+ <div className="absolute end-2 top-2">
1911
+ <button
1912
+ type="button"
1913
+ className={cn(
1914
+ 'inline-flex size-7 shrink-0 items-center justify-center rounded-md text-muted opacity-70 transition',
1915
+ 'hover:bg-mixer-muted/20 hover:opacity-100 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring',
1916
+ copyState === 'copied' && 'text-success opacity-100',
1917
+ copyState === 'failed' && 'text-destructive opacity-100',
1918
+ )}
1919
+ onClick={copyDetails}
1920
+ aria-label="Copy tool details"
1921
+ title="Copy tool details"
1922
+ >
1923
+ {copyState === 'copied' ? <CheckCircle className="size-4" /> : <CopyIcon className="size-4" />}
1924
+ </button>
1925
+ </div>
1926
+ ) : null}
1927
+ <div className={cn(hasDetailContent && 'pe-7')}>
1928
+ <ToolDetailMetadata item={item} hasDetailContent={hasDetailContent} />
1446
1929
  {commandText ? (
1447
1930
  <pre className="mb-3 whitespace-pre-wrap break-words font-mono text-sm leading-6 text-foreground/85">
1448
1931
  {commandText}
@@ -1459,15 +1942,20 @@ function SummaryToolDetailPanel({ item }: { item: SummaryToolDetailItem }) {
1459
1942
  </div>
1460
1943
  ) : null}
1461
1944
  </div>
1462
- {copyState === 'failed' ? <div className="mt-3 text-xs text-destructive">Copy failed</div> : null}
1945
+ {copyState === 'failed' && hasDetailContent ? (
1946
+ <div className="mt-3 text-xs text-destructive">Copy failed</div>
1947
+ ) : null}
1463
1948
  </div>
1464
1949
  );
1465
1950
  }
1466
1951
 
1467
1952
  function SummaryToolTimelineItem({ item }: { item: SummaryToolDetailItem }) {
1468
- const isAttention = item.status === 'error' || item.status === 'warning';
1469
- const hasDetails = Boolean(item.command || item.text || item.sections.length);
1953
+ const isDecision = Boolean(item.decisionText);
1954
+ const isAttention = !isDecision && (item.status === 'error' || item.status === 'warning');
1955
+ const hasMetadata = getToolDetailMetadata(item).length > 0;
1956
+ const hasDetails = Boolean(item.command || item.text || item.sections.length || hasMetadata);
1470
1957
  const [isExpanded, setIsExpanded] = useState(false);
1958
+ const iconStatus = isDecision ? undefined : item.status;
1471
1959
 
1472
1960
  return (
1473
1961
  <div className="min-w-0">
@@ -1488,11 +1976,15 @@ function SummaryToolTimelineItem({ item }: { item: SummaryToolDetailItem }) {
1488
1976
  isAttention ? 'text-attention' : 'text-muted',
1489
1977
  )}
1490
1978
  >
1491
- <ToolDetailIcon kind={item.kind} status={item.status} />
1979
+ <ToolDetailIcon kind={item.kind} status={iconStatus} />
1492
1980
  </span>
1493
- <span className="flex min-w-0 flex-wrap items-baseline gap-x-2 gap-y-0.5">
1494
- <span className="text-sm font-medium text-muted">{item.label}</span>
1495
- <span className="min-w-0 break-words text-sm text-muted">{item.title}</span>
1981
+ <span className="min-w-0 text-sm text-muted">
1982
+ <span className="break-words">{item.title}</span>
1983
+ {item.decisionText ? (
1984
+ <Badge variant="destructive" className="ms-2 rounded-full shadow-sm shadow-destructive/10">
1985
+ {item.decisionText}
1986
+ </Badge>
1987
+ ) : null}
1496
1988
  </span>
1497
1989
  {hasDetails ? (
1498
1990
  <ChevronDown
@@ -1513,27 +2005,87 @@ function SummaryToolTimelineItem({ item }: { item: SummaryToolDetailItem }) {
1513
2005
  );
1514
2006
  }
1515
2007
 
1516
- function SummaryThoughtTimelineItem({ item, artifactRunId }: { item: SummaryToolDetailItem; artifactRunId?: string }) {
2008
+ function SummaryThoughtProseItem({
2009
+ item,
2010
+ artifactRunId,
2011
+ disableCollapse,
2012
+ }: {
2013
+ item: SummaryToolDetailItem;
2014
+ artifactRunId?: string;
2015
+ disableCollapse?: boolean;
2016
+ }) {
2017
+ const { t } = useUITranslation();
1517
2018
  const text = item.text ?? item.title;
2019
+ const normalizedText = text.trim();
2020
+ const [isExpanded, setIsExpanded] = useState(false);
2021
+ const explicitLineCount = normalizedText ? normalizedText.split(/\r?\n/).length : 0;
2022
+ const isLong =
2023
+ normalizedText.length > SUMMARY_THOUGHT_COLLAPSE_THRESHOLD ||
2024
+ explicitLineCount > SUMMARY_THOUGHT_COLLAPSE_LINES;
2025
+ const toggleLabel = isExpanded ? t('agent.showLess') : t('agent.showMore');
1518
2026
 
1519
2027
  return (
1520
- <div className="min-w-0 py-1 text-sm leading-6 text-foreground/85">
1521
- <div className={SUMMARY_PROSE_CLASS} style={{ overflowWrap: 'anywhere' }}>
2028
+ <div className="min-w-0 py-1">
2029
+ <div
2030
+ data-testid="summary-thought-prose"
2031
+ className={cn(
2032
+ SUMMARY_PROSE_CLASS,
2033
+ isLong &&
2034
+ !disableCollapse &&
2035
+ !isExpanded &&
2036
+ '[display:-webkit-box] overflow-hidden [-webkit-box-orient:vertical] [-webkit-line-clamp:6]',
2037
+ )}
2038
+ style={{ overflowWrap: 'anywhere' }}
2039
+ >
1522
2040
  <MarkdownRenderer artifactRunId={artifactRunId} components={DEFAULT_AGENT_MARKDOWN_COMPONENTS}>
1523
2041
  {text}
1524
2042
  </MarkdownRenderer>
1525
2043
  </div>
2044
+ {isLong && !disableCollapse ? (
2045
+ <div className="mt-1.5 flex justify-end">
2046
+ <button
2047
+ type="button"
2048
+ aria-expanded={isExpanded}
2049
+ className={cn(
2050
+ 'inline-flex items-center gap-1 text-sm font-medium text-muted transition-colors',
2051
+ '[text-decoration:none] hover:text-foreground hover:[text-decoration:none]',
2052
+ 'focus-visible:text-foreground focus-visible:[text-decoration:none]',
2053
+ 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring',
2054
+ )}
2055
+ onClick={() => setIsExpanded((current) => !current)}
2056
+ >
2057
+ {toggleLabel}
2058
+ <ChevronDown
2059
+ className={cn('size-4 transition-transform', isExpanded && 'rotate-180')}
2060
+ aria-hidden="true"
2061
+ />
2062
+ </button>
2063
+ </div>
2064
+ ) : null}
1526
2065
  </div>
1527
2066
  );
1528
2067
  }
1529
2068
 
1530
- function SummaryToolTimeline({ items, artifactRunId }: { items: SummaryToolDetailItem[]; artifactRunId?: string }) {
2069
+ function SummaryToolTimeline({
2070
+ items,
2071
+ artifactRunId,
2072
+ disablePreambleCollapse,
2073
+ }: {
2074
+ items: SummaryToolDetailItem[];
2075
+ artifactRunId?: string;
2076
+ disablePreambleCollapse?: boolean;
2077
+ }) {
1531
2078
  return (
1532
2079
  <div className="mt-3">
1533
2080
  <div className="space-y-3">
1534
2081
  {items.map((item) =>
1535
- item.kind === 'think' ? (
1536
- <SummaryThoughtTimelineItem key={item.key} item={item} artifactRunId={artifactRunId} />
2082
+ item.isPreamble || item.kind === 'think' ? (
2083
+ <SummaryThoughtProseItem
2084
+ key={item.key}
2085
+ item={item}
2086
+ artifactRunId={artifactRunId}
2087
+ disableCollapse={disablePreambleCollapse}
2088
+ />
1537
2089
  ) : (
1538
2090
  <SummaryToolTimelineItem key={item.key} item={item} />
1539
2091
  ),
@@ -1563,17 +2115,47 @@ function SummaryStreamingMessage({
1563
2115
  );
1564
2116
  }
1565
2117
 
2118
+ function SummaryStoppedMessage({
2119
+ message,
2120
+ startTimestamp,
2121
+ endTimestamp,
2122
+ className,
2123
+ }: {
2124
+ message: AgentMessage;
2125
+ startTimestamp?: number | string;
2126
+ endTimestamp?: number | string;
2127
+ className?: string;
2128
+ }) {
2129
+ const { t } = useUITranslation();
2130
+ const duration = formatDuration(
2131
+ getDurationSeconds(startTimestamp ?? message.timestamp, endTimestamp ?? message.timestamp),
2132
+ );
2133
+
2134
+ return (
2135
+ <div className={cn('mx-auto w-full max-w-3xl px-1', className)} data-testid="summary-stopped-message">
2136
+ <div className="flex items-center justify-end gap-2 text-lg font-medium text-muted sm:text-xl">
2137
+ <span>{t('agent.youStoppedAfter', { duration })}</span>
2138
+ <MessageDeliveryStatus message={message} className="h-5 w-5" />
2139
+ </div>
2140
+ <div className="mt-5 border-b border-border/70" />
2141
+ </div>
2142
+ );
2143
+ }
2144
+
1566
2145
  function SummaryActivityRow({
1567
2146
  label,
1568
- status,
1569
2147
  timestamp,
1570
2148
  durationSeconds,
1571
2149
  showElapsed,
1572
2150
  details,
1573
2151
  emptyDetailsLabel,
1574
2152
  defaultExpanded = false,
2153
+ disablePreambleCollapse = false,
1575
2154
  className,
1576
2155
  artifactRunId,
2156
+ onSendMessage,
2157
+ answeredToolApprovalRequestInputKeys,
2158
+ resolvedToolApprovalKeys,
1577
2159
  }: {
1578
2160
  label: string;
1579
2161
  status?: ToolExecutionStatus;
@@ -1583,23 +2165,37 @@ function SummaryActivityRow({
1583
2165
  details?: AgentMessage[];
1584
2166
  emptyDetailsLabel?: string;
1585
2167
  defaultExpanded?: boolean;
2168
+ disablePreambleCollapse?: boolean;
1586
2169
  className?: string;
1587
2170
  artifactRunId?: string;
2171
+ onSendMessage?: (message: string, metadata?: Record<string, unknown>) => void;
2172
+ answeredToolApprovalRequestInputKeys?: Set<string>;
2173
+ resolvedToolApprovalKeys?: Set<string>;
1588
2174
  }) {
1589
2175
  const [isExpanded, setIsExpanded] = useState(defaultExpanded);
1590
2176
  const isLiveElapsed = showElapsed && durationSeconds === undefined;
1591
2177
  const liveElapsed = useLiveElapsedSeconds(timestamp, isLiveElapsed);
1592
2178
  const elapsed = durationSeconds ?? liveElapsed;
1593
2179
  const shouldShowElapsed = showElapsed && timestamp !== undefined;
1594
- const isAttention = status === 'error' || status === 'warning';
1595
2180
  const detailItems = useMemo(() => buildSummaryToolDetailItems(details ?? []), [details]);
1596
- const canExpand = detailItems.length > 0 || Boolean(emptyDetailsLabel);
2181
+ const requestInputMessages = useMemo(
2182
+ () =>
2183
+ (details ?? []).filter(
2184
+ (message): message is RequestInputMessageWithUx =>
2185
+ hasRequestInputUx(message) &&
2186
+ !isToolApprovalRequestInputHidden(
2187
+ message,
2188
+ answeredToolApprovalRequestInputKeys ?? new Set<string>(),
2189
+ resolvedToolApprovalKeys ?? new Set<string>(),
2190
+ ),
2191
+ ),
2192
+ [answeredToolApprovalRequestInputKeys, details, resolvedToolApprovalKeys],
2193
+ );
2194
+ const canExpand = detailItems.length > 0 || requestInputMessages.length > 0 || Boolean(emptyDetailsLabel);
1597
2195
 
1598
2196
  return (
1599
2197
  <div className={cn('mx-auto w-full max-w-3xl px-1', className)}>
1600
- <div
1601
- className={cn('border-b border-border/70 pb-3 text-sm', isAttention ? 'text-attention' : 'text-muted')}
1602
- >
2198
+ <div className="border-b border-border/70 pb-3 text-sm text-muted">
1603
2199
  <button
1604
2200
  type="button"
1605
2201
  className={cn(
@@ -1624,8 +2220,46 @@ function SummaryActivityRow({
1624
2220
  ) : null}
1625
2221
  </button>
1626
2222
  {canExpand && isExpanded ? (
1627
- detailItems.length > 0 ? (
1628
- <SummaryToolTimeline items={detailItems} artifactRunId={artifactRunId} />
2223
+ detailItems.length > 0 || requestInputMessages.length > 0 ? (
2224
+ <>
2225
+ {detailItems.length > 0 ? (
2226
+ <SummaryToolTimeline
2227
+ items={detailItems}
2228
+ artifactRunId={artifactRunId}
2229
+ disablePreambleCollapse={disablePreambleCollapse}
2230
+ />
2231
+ ) : null}
2232
+ {requestInputMessages.length > 0 ? (
2233
+ <div className="mt-3 space-y-3">
2234
+ {requestInputMessages.map((message) => {
2235
+ const uxConfig = message.details.ux;
2236
+ return (
2237
+ <AskUserWidget
2238
+ key={getAgentMessageRenderKey(message, 'work-request-input')}
2239
+ question={getRequestInputDisplayText(message)}
2240
+ options={uxConfig.options}
2241
+ variant={uxConfig.variant}
2242
+ multiSelect={uxConfig.multiSelect}
2243
+ onSelect={(optionId) => onSendMessage?.(optionId)}
2244
+ onMultiSelect={(optionIds) => onSendMessage?.(optionIds.join(', '))}
2245
+ allowFreeResponse={
2246
+ !uxConfig.options?.length || !!uxConfig.free_response
2247
+ }
2248
+ placeholder={uxConfig.free_response?.placeholder}
2249
+ submitLabel={uxConfig.free_response?.submit_label}
2250
+ onSubmit={(value) =>
2251
+ onSendMessage?.(value, uxConfig.free_response?.metadata)
2252
+ }
2253
+ hideBorder
2254
+ compact
2255
+ className="my-0"
2256
+ cardClassName="bg-background/60 shadow-none"
2257
+ />
2258
+ );
2259
+ })}
2260
+ </div>
2261
+ ) : null}
2262
+ </>
1629
2263
  ) : (
1630
2264
  <div className="mt-3 flex items-center gap-2 text-sm text-muted">
1631
2265
  <Terminal className="size-4 opacity-70" aria-hidden="true" />
@@ -1709,7 +2343,7 @@ interface AllMessagesMixedProps {
1709
2343
  taskLabels?: Map<string, string>; // Maps task IDs to more descriptive labels
1710
2344
  streamingMessages?: Map<string, StreamingData>; // Real-time streaming chunks
1711
2345
  /** Callback when user sends a message (e.g., from proposal selection) */
1712
- onSendMessage?: (message: string) => void;
2346
+ onSendMessage?: (message: string, metadata?: Record<string, unknown>) => void;
1713
2347
  /** Stable index for thinking messages (changes on 4s interval) */
1714
2348
  thinkingMessageIndex?: number;
1715
2349
  /** className overrides passed to every MessageItem */
@@ -1744,12 +2378,18 @@ interface AllMessagesMixedProps {
1744
2378
  initialRequestTitle?: string;
1745
2379
  /** Optional caller-provided renderer for agent-specific request shapes. */
1746
2380
  initialRequestTemplate?: AgentInitialRequestTemplate;
2381
+ /** Whether the synthetic initial request fallback should render when no persisted question is present. */
2382
+ showInitialRequest?: boolean;
1747
2383
  /** Message types to exclude from the conversation view */
1748
2384
  hiddenMessageTypes?: AgentMessageType[];
1749
2385
  /** Test/playback mode: keep the current scroll position while the rendered message slice changes. */
1750
2386
  disableAutoScroll?: boolean;
1751
2387
  /** Whether REQUEST_INPUT messages render their active controls in the transcript. */
1752
2388
  renderRequestInputControls?: boolean;
2389
+ /** Active workstream selected by the parent conversation shell. */
2390
+ activeWorkstream?: string;
2391
+ /** Called when the user selects a different workstream from the conversation. */
2392
+ onActiveWorkstreamChange?: (workstreamId: string) => void;
1753
2393
  }
1754
2394
 
1755
2395
  // PERFORMANCE: Throttle interval for auto-scroll (ms)
@@ -1779,9 +2419,12 @@ function AllMessagesMixedComponent({
1779
2419
  initialRequestSchema,
1780
2420
  initialRequestTitle,
1781
2421
  initialRequestTemplate,
2422
+ showInitialRequest,
1782
2423
  hiddenMessageTypes,
1783
2424
  disableAutoScroll = false,
1784
2425
  renderRequestInputControls = true,
2426
+ activeWorkstream: controlledActiveWorkstream,
2427
+ onActiveWorkstreamChange,
1785
2428
  }: AllMessagesMixedProps) {
1786
2429
  if (!artifactRunId) {
1787
2430
  console.warn('[AllMessagesMixed] artifactRunId prop is missing!');
@@ -1789,7 +2432,17 @@ function AllMessagesMixedComponent({
1789
2432
 
1790
2433
  const { t } = useUITranslation();
1791
2434
  const containerRef = useRef<HTMLDivElement | null>(null);
1792
- const [activeWorkstream, setActiveWorkstream] = useState<string>('all');
2435
+ const [internalActiveWorkstream, setInternalActiveWorkstream] = useState<string>('all');
2436
+ const activeWorkstream = controlledActiveWorkstream ?? internalActiveWorkstream;
2437
+ const setActiveWorkstream = useCallback(
2438
+ (workstreamId: string) => {
2439
+ if (controlledActiveWorkstream === undefined) {
2440
+ setInternalActiveWorkstream(workstreamId);
2441
+ }
2442
+ onActiveWorkstreamChange?.(workstreamId);
2443
+ },
2444
+ [controlledActiveWorkstream, onActiveWorkstreamChange],
2445
+ );
1793
2446
 
1794
2447
  // PERFORMANCE: Throttle auto-scroll to prevent layout thrashing
1795
2448
  // During streaming, scrollIntoView was being called 30+ times/sec
@@ -1884,9 +2537,9 @@ function AllMessagesMixedComponent({
1884
2537
  // Sort all messages chronologically and dedupe adjacent identical messages
1885
2538
  // Low-signal messages are suppressed at the source (server-side) via shouldSuppressLowSignalMessage
1886
2539
  const sortedMessages = React.useMemo(() => {
1887
- const filtered = hiddenMessageTypes?.length
1888
- ? messages.filter((m) => !hiddenMessageTypes.includes(m.type))
1889
- : messages;
2540
+ const filtered = messages.filter(
2541
+ (message) => !isDocumentPanelEventMessage(message) && !hiddenMessageTypes?.includes(message.type),
2542
+ );
1890
2543
 
1891
2544
  const sorted = [...filtered].sort((a, b) => {
1892
2545
  const timeA = typeof a.timestamp === 'number' ? a.timestamp : new Date(a.timestamp).getTime();
@@ -1909,17 +2562,60 @@ function AllMessagesMixedComponent({
1909
2562
  return deduped;
1910
2563
  }, [messages, hiddenMessageTypes]);
1911
2564
 
1912
- // Get workstreams from messages - only from message.workstream_id
1913
2565
  const workstreams = React.useMemo(() => {
1914
- // Just get the basic workstreams from the messages
1915
2566
  const extractedWorkstreams = extractWorkstreams(sortedMessages);
1916
2567
 
1917
- // We'll keep taskLabels - they might be used for display purposes elsewhere
1918
- // but we won't use them to create new workstream tabs
2568
+ sortedMessages.forEach((message) => {
2569
+ const details = getWorkstreamLaunchDetails(message) ?? getWorkstreamActivityDetails(message);
2570
+ if (!details) return;
2571
+ extractedWorkstreams.set(
2572
+ details.workstreamId,
2573
+ getWorkstreamDisplayName(details.workstreamId, details.interaction),
2574
+ );
2575
+ });
1919
2576
 
1920
2577
  return extractedWorkstreams;
1921
2578
  }, [sortedMessages]);
1922
2579
 
2580
+ const activeWorkstreamName = React.useMemo(() => {
2581
+ if (activeWorkstream === 'all') return undefined;
2582
+ return workstreams.get(activeWorkstream) ?? formatWorkstreamName(activeWorkstream);
2583
+ }, [activeWorkstream, workstreams]);
2584
+
2585
+ const scrollToTop = useCallback(() => {
2586
+ const container = containerRef.current;
2587
+ if (!container) return;
2588
+
2589
+ programmaticScrollRef.current = true;
2590
+ if (typeof container.scrollTo === 'function') {
2591
+ container.scrollTo({ top: 0, behavior: 'smooth' });
2592
+ } else {
2593
+ container.scrollTop = 0;
2594
+ }
2595
+ requestAnimationFrame(() => {
2596
+ programmaticScrollRef.current = false;
2597
+ });
2598
+ }, []);
2599
+
2600
+ const handleSelectWorkstream = useCallback(
2601
+ (workstreamId: string) => {
2602
+ setActiveWorkstream(workstreamId);
2603
+ requestAnimationFrame(scrollToTop);
2604
+ },
2605
+ [scrollToTop, setActiveWorkstream],
2606
+ );
2607
+
2608
+ const handleShowMainAgentChat = useCallback(() => {
2609
+ setActiveWorkstream('all');
2610
+ requestAnimationFrame(scrollToTop);
2611
+ }, [scrollToTop, setActiveWorkstream]);
2612
+
2613
+ useEffect(() => {
2614
+ if (activeWorkstream !== 'all' && !workstreams.has(activeWorkstream)) {
2615
+ setActiveWorkstream('all');
2616
+ }
2617
+ }, [activeWorkstream, workstreams, setActiveWorkstream]);
2618
+
1923
2619
  // Count messages per workstream
1924
2620
  const workstreamCounts = React.useMemo(() => {
1925
2621
  const counts = new Map<string, number>();
@@ -1942,16 +2638,27 @@ function AllMessagesMixedComponent({
1942
2638
 
1943
2639
  // Filter messages based on active workstream
1944
2640
  const displayMessages = React.useMemo(() => {
1945
- if (activeWorkstream === 'all') {
1946
- return sortedMessages;
1947
- }
1948
- return filterMessagesByWorkstream(sortedMessages, activeWorkstream);
2641
+ return attachWriteArtifactCompletionMessages(
2642
+ filterMessagesForActiveWorkstream(sortedMessages, activeWorkstream),
2643
+ );
1949
2644
  }, [sortedMessages, activeWorkstream]);
1950
2645
 
1951
2646
  const answeredRequestInputKeys = React.useMemo(
1952
2647
  () => getAnsweredRequestInputKeys(displayMessages),
1953
2648
  [displayMessages],
1954
2649
  );
2650
+ const resolvedToolApprovalKeys = React.useMemo(
2651
+ () => getResolvedToolApprovalKeys(displayMessages),
2652
+ [displayMessages],
2653
+ );
2654
+ const answeredToolApprovalRequestInputKeys = React.useMemo(
2655
+ () => getAnsweredToolApprovalRequestInputKeys(displayMessages),
2656
+ [displayMessages],
2657
+ );
2658
+ const hiddenToolApprovalAnswerKeys = React.useMemo(
2659
+ () => getHiddenToolApprovalAnswerKeys(displayMessages),
2660
+ [displayMessages],
2661
+ );
1955
2662
  const externallyRenderedRequestInputKey = React.useMemo(() => {
1956
2663
  if (renderRequestInputControls) return undefined;
1957
2664
  const pendingRequestInput = getPendingRequestInputMessage(displayMessages);
@@ -1960,8 +2667,21 @@ function AllMessagesMixedComponent({
1960
2667
  const shouldHideRequestInputMessage = React.useCallback(
1961
2668
  (message: AgentMessage) =>
1962
2669
  message.type === AgentMessageType.REQUEST_INPUT &&
1963
- externallyRenderedRequestInputKey === getRequestInputMessageKey(message),
1964
- [externallyRenderedRequestInputKey],
2670
+ (externallyRenderedRequestInputKey === getRequestInputMessageKey(message) ||
2671
+ isToolApprovalRequestInputHidden(
2672
+ message,
2673
+ answeredToolApprovalRequestInputKeys,
2674
+ resolvedToolApprovalKeys,
2675
+ )),
2676
+ [answeredToolApprovalRequestInputKeys, externallyRenderedRequestInputKey, resolvedToolApprovalKeys],
2677
+ );
2678
+ const shouldHideToolApprovalAnswerMessage = React.useCallback(
2679
+ (message: AgentMessage) => isToolApprovalAnswerHidden(message, hiddenToolApprovalAnswerKeys),
2680
+ [hiddenToolApprovalAnswerKeys],
2681
+ );
2682
+ const completionDisplayMessages = React.useMemo(
2683
+ () => displayMessages.filter((message) => !shouldHideToolApprovalAnswerMessage(message)),
2684
+ [displayMessages, shouldHideToolApprovalAnswerMessage],
1965
2685
  );
1966
2686
 
1967
2687
  const fallbackWorkingStartedAtRef = useRef(Date.now());
@@ -1969,11 +2689,14 @@ function AllMessagesMixedComponent({
1969
2689
  Boolean(prependFriendlyMessage?.trim()) ||
1970
2690
  hasInitialRequestValue(initialRequestData) ||
1971
2691
  initialRequestTemplate !== undefined;
2692
+ const canRenderInitialRequest = showInitialRequest ?? true;
2693
+ const hasRenderableInitialRequest = hasInitialRequest && canRenderInitialRequest;
1972
2694
  const hasPersistedUserQuestion = useMemo(
1973
2695
  () => displayMessages.some((message) => message.type === AgentMessageType.QUESTION),
1974
2696
  [displayMessages],
1975
2697
  );
1976
- const shouldRenderInitialRequest = hasInitialRequest && !hasPersistedUserQuestion;
2698
+ const shouldRenderInitialRequest =
2699
+ activeWorkstream === 'all' && hasRenderableInitialRequest && !hasPersistedUserQuestion;
1977
2700
 
1978
2701
  const latestDisplayMessageTimestamp = useMemo(() => {
1979
2702
  return displayMessages.reduce((latest, msg) => Math.max(latest, getTimestampMs(msg.timestamp)), -Infinity);
@@ -1985,11 +2708,30 @@ function AllMessagesMixedComponent({
1985
2708
  return Math.max(latest, getTimestampMs(msg.timestamp));
1986
2709
  }, -Infinity);
1987
2710
  }, [displayMessages]);
2711
+ const hasPendingToolApprovalRequest = useMemo(
2712
+ () =>
2713
+ displayMessages.some(
2714
+ (message) =>
2715
+ isToolApprovalRequestInput(message) &&
2716
+ !isToolApprovalRequestInputHidden(
2717
+ message,
2718
+ answeredToolApprovalRequestInputKeys,
2719
+ resolvedToolApprovalKeys,
2720
+ ),
2721
+ ),
2722
+ [answeredToolApprovalRequestInputKeys, displayMessages, resolvedToolApprovalKeys],
2723
+ );
2724
+ const hasLatestToolApprovalAllow = useMemo(
2725
+ () => hasLatestToolApprovalAllowTurn(displayMessages, hiddenToolApprovalAnswerKeys),
2726
+ [displayMessages, hiddenToolApprovalAnswerKeys],
2727
+ );
1988
2728
 
1989
2729
  const isDisplayCompleted = useMemo(() => {
1990
- if (hasOpenUserTurn(displayMessages)) return false;
1991
- return isCompleted || !isInProgress(displayMessages);
1992
- }, [displayMessages, isCompleted]);
2730
+ if (hasPendingToolApprovalRequest) return false;
2731
+ if (hasLatestToolApprovalAllow) return false;
2732
+ if (hasOpenUserTurn(completionDisplayMessages)) return false;
2733
+ return isCompleted || !isInProgress(completionDisplayMessages);
2734
+ }, [completionDisplayMessages, hasLatestToolApprovalAllow, hasPendingToolApprovalRequest, isCompleted]);
1993
2735
 
1994
2736
  // Split streaming messages:
1995
2737
  // - complete (or stale incomplete) ones are interleaved chronologically
@@ -2024,8 +2766,19 @@ function AllMessagesMixedComponent({
2024
2766
  }, [streamingMessages, activeWorkstream, latestNonTransientDisplayMessageTimestamp]);
2025
2767
 
2026
2768
  const summaryDisplayMessages = React.useMemo(
2027
- () => buildSummaryDisplayMessages(displayMessages, completeStreaming),
2028
- [displayMessages, completeStreaming],
2769
+ () =>
2770
+ buildSyntheticToolApprovalDecisionMessages(
2771
+ buildSummaryDisplayMessages(displayMessages, completeStreaming),
2772
+ resolvedToolApprovalKeys,
2773
+ ),
2774
+ [displayMessages, completeStreaming, resolvedToolApprovalKeys],
2775
+ );
2776
+ const visibleSummaryDisplayMessages = React.useMemo(
2777
+ () =>
2778
+ summaryDisplayMessages.filter(
2779
+ (message) => !shouldHideRequestInputMessage(message) && !shouldHideToolApprovalAnswerMessage(message),
2780
+ ),
2781
+ [summaryDisplayMessages, shouldHideRequestInputMessage, shouldHideToolApprovalAnswerMessage],
2029
2782
  );
2030
2783
 
2031
2784
  const latestSummaryObservedTimestamp = useMemo(() => {
@@ -2040,8 +2793,13 @@ function AllMessagesMixedComponent({
2040
2793
  }, [incompleteStreaming, latestDisplayMessageTimestamp]);
2041
2794
 
2042
2795
  const summaryConversationItems = React.useMemo(
2043
- () => buildSummaryConversationItems(summaryDisplayMessages, isDisplayCompleted, latestSummaryObservedTimestamp),
2044
- [summaryDisplayMessages, isDisplayCompleted, latestSummaryObservedTimestamp],
2796
+ () =>
2797
+ buildSummaryConversationItems(
2798
+ visibleSummaryDisplayMessages,
2799
+ isDisplayCompleted,
2800
+ latestSummaryObservedTimestamp,
2801
+ ),
2802
+ [visibleSummaryDisplayMessages, isDisplayCompleted, latestSummaryObservedTimestamp],
2045
2803
  );
2046
2804
 
2047
2805
  // Group messages with ONLY complete streaming interleaved for stacked view.
@@ -2058,8 +2816,12 @@ function AllMessagesMixedComponent({
2058
2816
  // Show an activity indicator when the latest visible conversation state is not terminal.
2059
2817
  // Older idle/complete messages from previous turns must not suppress the new turn.
2060
2818
  const isAgentWorking = useMemo(() => {
2061
- return !isDisplayCompleted;
2062
- }, [isDisplayCompleted]);
2819
+ // Also treat an open user turn (the latest message is the user's, awaiting the agent's
2820
+ // first output) as "working" — otherwise nothing animates in the gap between sending a
2821
+ // message and the first streamed token, especially after a stop where the run reads as
2822
+ // completed until the agent posts its next message.
2823
+ return !isDisplayCompleted || hasOpenUserTurn(completionDisplayMessages);
2824
+ }, [completionDisplayMessages, isDisplayCompleted]);
2063
2825
 
2064
2826
  const showActivityFallback = shouldShowSummaryActivityFallback(
2065
2827
  summaryConversationItems,
@@ -2070,15 +2832,18 @@ function AllMessagesMixedComponent({
2070
2832
  ? t('agent.preparing')
2071
2833
  : t('agent.working');
2072
2834
  const showInitialRequestWaitingCard =
2073
- displayMessages.length === 0 && hasInitialRequest && isAgentWorking && incompleteStreaming.length === 0;
2835
+ displayMessages.length === 0 &&
2836
+ hasRenderableInitialRequest &&
2837
+ isAgentWorking &&
2838
+ incompleteStreaming.length === 0;
2074
2839
  const activityAnchorCandidate = useMemo(
2075
2840
  () =>
2076
2841
  getSummaryActivityAnchorTimestamp(
2077
2842
  summaryConversationItems,
2078
- summaryDisplayMessages,
2843
+ visibleSummaryDisplayMessages,
2079
2844
  fallbackWorkingStartedAtRef.current,
2080
2845
  ),
2081
- [summaryConversationItems, summaryDisplayMessages],
2846
+ [summaryConversationItems, visibleSummaryDisplayMessages],
2082
2847
  );
2083
2848
  const activityStartedTimestampRef = useRef<number | string>(activityAnchorCandidate);
2084
2849
  const wasActivityFallbackVisibleRef = useRef(false);
@@ -2417,14 +3182,16 @@ function AllMessagesMixedComponent({
2417
3182
  <WorkstreamTabs
2418
3183
  workstreams={workstreams}
2419
3184
  activeWorkstream={activeWorkstream}
2420
- onSelectWorkstream={setActiveWorkstream}
3185
+ onSelectWorkstream={handleSelectWorkstream}
2421
3186
  count={workstreamCounts}
2422
3187
  completionStatus={workstreamCompletionStatus}
2423
3188
  />
2424
3189
  </div>
2425
3190
  )}
2426
3191
 
2427
- {displayMessages.length === 0 && !hasInitialRequest && !(isSummaryView && showActivityFallback) ? (
3192
+ {displayMessages.length === 0 &&
3193
+ !hasRenderableInitialRequest &&
3194
+ !(isSummaryView && showActivityFallback) ? (
2428
3195
  activeWorkstream === 'all' && isAgentWorking && incompleteStreaming.length === 0 ? (
2429
3196
  <div className="flex-1 px-2 py-6 sm:px-4">
2430
3197
  <InitialRequestWaitingCard
@@ -2450,6 +3217,28 @@ function AllMessagesMixedComponent({
2450
3217
  messageListClassName,
2451
3218
  )}
2452
3219
  >
3220
+ {isSummaryView && activeWorkstream !== 'all' && activeWorkstreamName && (
3221
+ <div className="sticky top-0 z-20 -mx-2 bg-background/95 px-2 pb-2 pt-1 backdrop-blur sm:-mx-4 sm:px-4">
3222
+ <div className="mx-auto flex w-full max-w-3xl items-center gap-2 border-b border-border/70 pb-3">
3223
+ <VTooltip description={t('agent.backToMainAgent')} asChild>
3224
+ <Button
3225
+ type="button"
3226
+ variant="ghost"
3227
+ size="icon"
3228
+ className="size-8 shrink-0 text-muted hover:text-foreground"
3229
+ aria-label={t('agent.backToMainAgent')}
3230
+ onClick={handleShowMainAgentChat}
3231
+ >
3232
+ <ArrowLeft className="size-4" aria-hidden="true" />
3233
+ </Button>
3234
+ </VTooltip>
3235
+ <div className="min-w-0 flex-1 truncate text-sm font-medium text-foreground">
3236
+ {activeWorkstreamName}
3237
+ </div>
3238
+ </div>
3239
+ </div>
3240
+ )}
3241
+
2453
3242
  {/* Friendly message — rendered outside the messages array to avoid memo issues/triggering autoscroll */}
2454
3243
  {shouldRenderInitialRequest && (
2455
3244
  <InitialRequestMessage
@@ -2544,6 +3333,23 @@ function AllMessagesMixedComponent({
2544
3333
  // Render single message
2545
3334
  const message = group.message;
2546
3335
  if (shouldHideRequestInputMessage(message)) return null;
3336
+ if (shouldHideToolApprovalAnswerMessage(message)) return null;
3337
+ if (isUserStoppedMessage(message)) {
3338
+ return (
3339
+ <TimelineEntry key={getAgentMessageRenderKey(message, 'stopped')}>
3340
+ <SummaryStoppedMessage
3341
+ message={message}
3342
+ startTimestamp={
3343
+ getPreviousRenderableGroupTimestamp(
3344
+ groupedMessages,
3345
+ groupIndex,
3346
+ ) ?? message.timestamp
3347
+ }
3348
+ endTimestamp={message.timestamp}
3349
+ />
3350
+ </TimelineEntry>
3351
+ );
3352
+ }
2547
3353
  const isLatestMessage =
2548
3354
  !isCompleted && isLastGroup && !DONE_STATES.includes(message.type);
2549
3355
 
@@ -2639,7 +3445,7 @@ function AllMessagesMixedComponent({
2639
3445
  return (
2640
3446
  <SummaryActivityRow
2641
3447
  key={`work-${item.id}-${item.isActive ? 'active' : 'done'}-${item.status}`}
2642
- label={getSummaryActivityLabel(item.status, item.isActive)}
3448
+ label={getSummaryActivityLabel(item.isActive)}
2643
3449
  status={item.status}
2644
3450
  timestamp={item.startTimestamp}
2645
3451
  durationSeconds={
@@ -2650,13 +3456,29 @@ function AllMessagesMixedComponent({
2650
3456
  showElapsed
2651
3457
  details={isThinkingOnlyWork ? undefined : item.messages}
2652
3458
  defaultExpanded={item.isActive && !isThinkingOnlyWork}
3459
+ disablePreambleCollapse={item.isActive}
2653
3460
  className={workingIndicatorClassName}
3461
+ onSendMessage={onSendMessage}
3462
+ answeredToolApprovalRequestInputKeys={answeredToolApprovalRequestInputKeys}
3463
+ resolvedToolApprovalKeys={resolvedToolApprovalKeys}
3464
+ />
3465
+ );
3466
+ }
3467
+
3468
+ if (item.type === 'stopped') {
3469
+ return (
3470
+ <SummaryStoppedMessage
3471
+ key={getAgentMessageRenderKey(item.message, 'stopped-summary')}
3472
+ message={item.message}
3473
+ startTimestamp={item.startTimestamp}
3474
+ endTimestamp={item.endTimestamp}
2654
3475
  />
2655
3476
  );
2656
3477
  }
2657
3478
 
2658
3479
  const message = item.message;
2659
3480
  if (shouldHideRequestInputMessage(message)) return null;
3481
+ if (shouldHideToolApprovalAnswerMessage(message)) return null;
2660
3482
  if (isBatchProgressMessage(message)) {
2661
3483
  return (
2662
3484
  <MessageErrorBoundary key={getAgentMessageRenderKey(message, 'batch')}>
@@ -2675,6 +3497,7 @@ function AllMessagesMixedComponent({
2675
3497
  <SummaryMessage
2676
3498
  message={message}
2677
3499
  onSendMessage={onSendMessage}
3500
+ onSelectWorkstream={handleSelectWorkstream}
2678
3501
  requestInputAnswered={isRequestInputAnswered(
2679
3502
  message,
2680
3503
  answeredRequestInputKeys,