@vertesia/ui 1.5.0-dev.20260722.120446Z → 1.5.0-dev.20260725.083715Z

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 (307) hide show
  1. package/lib/core/components/shadcn/filters/filterBar.d.ts.map +1 -1
  2. package/lib/core/components/shadcn/filters/filterBar.js +1 -1
  3. package/lib/core/components/shadcn/filters/filterBar.js.map +1 -1
  4. package/lib/core/components/shadcn/modal/dialog.d.ts +1 -1
  5. package/lib/core/components/shadcn/modal/dialog.d.ts.map +1 -1
  6. package/lib/core/components/shadcn/modal/dialog.js +2 -0
  7. package/lib/core/components/shadcn/modal/dialog.js.map +1 -1
  8. package/lib/core/components/shadcn/tabs.d.ts +12 -1
  9. package/lib/core/components/shadcn/tabs.d.ts.map +1 -1
  10. package/lib/core/components/shadcn/tabs.js +12 -4
  11. package/lib/core/components/shadcn/tabs.js.map +1 -1
  12. package/lib/features/agent/chat/AgentRightPanel.d.ts +5 -1
  13. package/lib/features/agent/chat/AgentRightPanel.d.ts.map +1 -1
  14. package/lib/features/agent/chat/AgentRightPanel.js +2 -2
  15. package/lib/features/agent/chat/AgentRightPanel.js.map +1 -1
  16. package/lib/features/agent/chat/ArtifactsTab.d.ts +5 -1
  17. package/lib/features/agent/chat/ArtifactsTab.d.ts.map +1 -1
  18. package/lib/features/agent/chat/ArtifactsTab.js +48 -11
  19. package/lib/features/agent/chat/ArtifactsTab.js.map +1 -1
  20. package/lib/features/agent/chat/DocumentEditingActionCard.d.ts +6 -0
  21. package/lib/features/agent/chat/DocumentEditingActionCard.d.ts.map +1 -0
  22. package/lib/features/agent/chat/DocumentEditingActionCard.js +129 -0
  23. package/lib/features/agent/chat/DocumentEditingActionCard.js.map +1 -0
  24. package/lib/features/agent/chat/ModernAgentConversation.d.ts +19 -1
  25. package/lib/features/agent/chat/ModernAgentConversation.d.ts.map +1 -1
  26. package/lib/features/agent/chat/ModernAgentConversation.js +125 -27
  27. package/lib/features/agent/chat/ModernAgentConversation.js.map +1 -1
  28. package/lib/features/agent/chat/ModernAgentOutput/AgentMarkdownAnchor.d.ts +45 -0
  29. package/lib/features/agent/chat/ModernAgentOutput/AgentMarkdownAnchor.d.ts.map +1 -0
  30. package/lib/features/agent/chat/ModernAgentOutput/AgentMarkdownAnchor.js +40 -0
  31. package/lib/features/agent/chat/ModernAgentOutput/AgentMarkdownAnchor.js.map +1 -0
  32. package/lib/features/agent/chat/ModernAgentOutput/AllMessagesMixed.d.ts +3 -1
  33. package/lib/features/agent/chat/ModernAgentOutput/AllMessagesMixed.d.ts.map +1 -1
  34. package/lib/features/agent/chat/ModernAgentOutput/AllMessagesMixed.js +94 -49
  35. package/lib/features/agent/chat/ModernAgentOutput/AllMessagesMixed.js.map +1 -1
  36. package/lib/features/agent/chat/ModernAgentOutput/AttachmentPreview.d.ts +2 -1
  37. package/lib/features/agent/chat/ModernAgentOutput/AttachmentPreview.d.ts.map +1 -1
  38. package/lib/features/agent/chat/ModernAgentOutput/AttachmentPreview.js +9 -5
  39. package/lib/features/agent/chat/ModernAgentOutput/AttachmentPreview.js.map +1 -1
  40. package/lib/features/agent/chat/ModernAgentOutput/CollapsibleAgentMarkdown.d.ts +25 -0
  41. package/lib/features/agent/chat/ModernAgentOutput/CollapsibleAgentMarkdown.d.ts.map +1 -0
  42. package/lib/features/agent/chat/ModernAgentOutput/CollapsibleAgentMarkdown.js +33 -0
  43. package/lib/features/agent/chat/ModernAgentOutput/CollapsibleAgentMarkdown.js.map +1 -0
  44. package/lib/features/agent/chat/ModernAgentOutput/MessageItem.d.ts +3 -1
  45. package/lib/features/agent/chat/ModernAgentOutput/MessageItem.d.ts.map +1 -1
  46. package/lib/features/agent/chat/ModernAgentOutput/MessageItem.js +22 -24
  47. package/lib/features/agent/chat/ModernAgentOutput/MessageItem.js.map +1 -1
  48. package/lib/features/agent/chat/ModernAgentOutput/ResourceChangeSummary.d.ts +14 -0
  49. package/lib/features/agent/chat/ModernAgentOutput/ResourceChangeSummary.d.ts.map +1 -0
  50. package/lib/features/agent/chat/ModernAgentOutput/ResourceChangeSummary.js +21 -0
  51. package/lib/features/agent/chat/ModernAgentOutput/ResourceChangeSummary.js.map +1 -0
  52. package/lib/features/agent/chat/ModernAgentOutput/ResourceChip.d.ts +30 -0
  53. package/lib/features/agent/chat/ModernAgentOutput/ResourceChip.d.ts.map +1 -0
  54. package/lib/features/agent/chat/ModernAgentOutput/ResourceChip.js +53 -0
  55. package/lib/features/agent/chat/ModernAgentOutput/ResourceChip.js.map +1 -0
  56. package/lib/features/agent/chat/ModernAgentOutput/ToolCallGroup.d.ts.map +1 -1
  57. package/lib/features/agent/chat/ModernAgentOutput/ToolCallGroup.js +21 -2
  58. package/lib/features/agent/chat/ModernAgentOutput/ToolCallGroup.js.map +1 -1
  59. package/lib/features/agent/chat/ModernAgentOutput/resourceLinks.d.ts +6 -0
  60. package/lib/features/agent/chat/ModernAgentOutput/resourceLinks.d.ts.map +1 -0
  61. package/lib/features/agent/chat/ModernAgentOutput/resourceLinks.js +29 -0
  62. package/lib/features/agent/chat/ModernAgentOutput/resourceLinks.js.map +1 -0
  63. package/lib/features/agent/chat/ModernAgentOutput/resourceSummary.d.ts +33 -0
  64. package/lib/features/agent/chat/ModernAgentOutput/resourceSummary.d.ts.map +1 -0
  65. package/lib/features/agent/chat/ModernAgentOutput/resourceSummary.js +129 -0
  66. package/lib/features/agent/chat/ModernAgentOutput/resourceSummary.js.map +1 -0
  67. package/lib/features/agent/chat/ModernAgentOutput/utils.d.ts +7 -0
  68. package/lib/features/agent/chat/ModernAgentOutput/utils.d.ts.map +1 -1
  69. package/lib/features/agent/chat/ModernAgentOutput/utils.js +11 -1
  70. package/lib/features/agent/chat/ModernAgentOutput/utils.js.map +1 -1
  71. package/lib/features/agent/chat/hooks/useAgentStream.d.ts +1 -1
  72. package/lib/features/agent/chat/hooks/useAgentStream.d.ts.map +1 -1
  73. package/lib/features/agent/chat/hooks/useAgentStream.js +24 -9
  74. package/lib/features/agent/chat/hooks/useAgentStream.js.map +1 -1
  75. package/lib/features/agent/chat/index.d.ts +2 -1
  76. package/lib/features/agent/chat/index.d.ts.map +1 -1
  77. package/lib/features/agent/chat/index.js +3 -0
  78. package/lib/features/agent/chat/index.js.map +1 -1
  79. package/lib/features/store/objects/DocumentSearchResults.js +2 -2
  80. package/lib/features/store/objects/DocumentSearchResults.js.map +1 -1
  81. package/lib/features/store/objects/components/ContentOverview.d.ts +2 -1
  82. package/lib/features/store/objects/components/ContentOverview.d.ts.map +1 -1
  83. package/lib/features/store/objects/components/ContentOverview.js +86 -15
  84. package/lib/features/store/objects/components/ContentOverview.js.map +1 -1
  85. package/lib/features/store/objects/components/DocumentEditingConfigurationSelector.d.ts +16 -0
  86. package/lib/features/store/objects/components/DocumentEditingConfigurationSelector.d.ts.map +1 -0
  87. package/lib/features/store/objects/components/DocumentEditingConfigurationSelector.js +116 -0
  88. package/lib/features/store/objects/components/DocumentEditingConfigurationSelector.js.map +1 -0
  89. package/lib/features/store/objects/components/DocumentEditingLockStatus.d.ts +7 -0
  90. package/lib/features/store/objects/components/DocumentEditingLockStatus.d.ts.map +1 -0
  91. package/lib/features/store/objects/components/DocumentEditingLockStatus.js +13 -0
  92. package/lib/features/store/objects/components/DocumentEditingLockStatus.js.map +1 -0
  93. package/lib/features/store/objects/components/DocumentEditingPanel.d.ts +48 -0
  94. package/lib/features/store/objects/components/DocumentEditingPanel.d.ts.map +1 -0
  95. package/lib/features/store/objects/components/DocumentEditingPanel.js +720 -0
  96. package/lib/features/store/objects/components/DocumentEditingPanel.js.map +1 -0
  97. package/lib/features/store/objects/components/TextEditorPanel.d.ts.map +1 -1
  98. package/lib/features/store/objects/components/TextEditorPanel.js +22 -9
  99. package/lib/features/store/objects/components/TextEditorPanel.js.map +1 -1
  100. package/lib/features/store/objects/components/documentArtifactRefs.d.ts +26 -0
  101. package/lib/features/store/objects/components/documentArtifactRefs.d.ts.map +1 -0
  102. package/lib/features/store/objects/components/documentArtifactRefs.js +52 -0
  103. package/lib/features/store/objects/components/documentArtifactRefs.js.map +1 -0
  104. package/lib/features/store/objects/components/documentEditingAgentConfig.d.ts +4 -0
  105. package/lib/features/store/objects/components/documentEditingAgentConfig.d.ts.map +1 -0
  106. package/lib/features/store/objects/components/documentEditingAgentConfig.js +40 -0
  107. package/lib/features/store/objects/components/documentEditingAgentConfig.js.map +1 -0
  108. package/lib/features/store/objects/components/documentEditingPrompts.d.ts +3 -0
  109. package/lib/features/store/objects/components/documentEditingPrompts.d.ts.map +1 -0
  110. package/lib/features/store/objects/components/documentEditingPrompts.js +13 -0
  111. package/lib/features/store/objects/components/documentEditingPrompts.js.map +1 -0
  112. package/lib/features/store/objects/components/documentEditingRun.d.ts +27 -0
  113. package/lib/features/store/objects/components/documentEditingRun.d.ts.map +1 -0
  114. package/lib/features/store/objects/components/documentEditingRun.js +126 -0
  115. package/lib/features/store/objects/components/documentEditingRun.js.map +1 -0
  116. package/lib/features/store/objects/components/documentEditingTarget.d.ts +20 -0
  117. package/lib/features/store/objects/components/documentEditingTarget.d.ts.map +1 -0
  118. package/lib/features/store/objects/components/documentEditingTarget.js +40 -0
  119. package/lib/features/store/objects/components/documentEditingTarget.js.map +1 -0
  120. package/lib/features/store/objects/components/index.d.ts +1 -0
  121. package/lib/features/store/objects/components/index.d.ts.map +1 -1
  122. package/lib/features/store/objects/components/index.js +1 -0
  123. package/lib/features/store/objects/components/index.js.map +1 -1
  124. package/lib/features/store/objects/components/useContentPanelHooks.d.ts +5 -1
  125. package/lib/features/store/objects/components/useContentPanelHooks.d.ts.map +1 -1
  126. package/lib/features/store/objects/components/useContentPanelHooks.js +53 -29
  127. package/lib/features/store/objects/components/useContentPanelHooks.js.map +1 -1
  128. package/lib/features/store/objects/components/useDownloadFile.d.ts +2 -1
  129. package/lib/features/store/objects/components/useDownloadFile.d.ts.map +1 -1
  130. package/lib/features/store/objects/components/useDownloadFile.js +3 -0
  131. package/lib/features/store/objects/components/useDownloadFile.js.map +1 -1
  132. package/lib/features/store/objects/selection/actions/StartWorkflowButton.js +1 -1
  133. package/lib/features/store/objects/selection/actions/StartWorkflowButton.js.map +1 -1
  134. package/lib/i18n/locales/ar.json +118 -1
  135. package/lib/i18n/locales/de.json +118 -1
  136. package/lib/i18n/locales/en.json +118 -0
  137. package/lib/i18n/locales/es.json +118 -1
  138. package/lib/i18n/locales/fr.json +118 -1
  139. package/lib/i18n/locales/it.json +118 -1
  140. package/lib/i18n/locales/ja.json +118 -1
  141. package/lib/i18n/locales/ko.json +118 -1
  142. package/lib/i18n/locales/pt.json +118 -1
  143. package/lib/i18n/locales/ru.json +118 -1
  144. package/lib/i18n/locales/tr.json +118 -1
  145. package/lib/i18n/locales/zh-TW.json +118 -1
  146. package/lib/i18n/locales/zh.json +118 -1
  147. package/lib/rich-text/EditorToolbar.d.ts +37 -0
  148. package/lib/rich-text/EditorToolbar.d.ts.map +1 -0
  149. package/lib/rich-text/EditorToolbar.js +215 -0
  150. package/lib/rich-text/EditorToolbar.js.map +1 -0
  151. package/lib/rich-text/VertesiaRichTextEditor.d.ts +26 -0
  152. package/lib/rich-text/VertesiaRichTextEditor.d.ts.map +1 -0
  153. package/lib/rich-text/VertesiaRichTextEditor.js +67 -0
  154. package/lib/rich-text/VertesiaRichTextEditor.js.map +1 -0
  155. package/lib/rich-text/index.d.ts +2 -0
  156. package/lib/rich-text/index.d.ts.map +1 -0
  157. package/lib/rich-text/index.js +2 -0
  158. package/lib/rich-text/index.js.map +1 -0
  159. package/lib/vertesia-ui-core.js +2 -2
  160. package/lib/vertesia-ui-core.js.map +1 -1
  161. package/lib/vertesia-ui-features.js +35 -30
  162. package/lib/vertesia-ui-features.js.map +1 -1
  163. package/lib/vertesia-ui-i18n.js +14 -14
  164. package/lib/vertesia-ui-i18n.js.map +1 -1
  165. package/lib/vertesia-ui-rich-text.js +5 -0
  166. package/lib/vertesia-ui-rich-text.js.map +1 -0
  167. package/lib/vertesia-ui-widgets.js +25 -7
  168. package/lib/vertesia-ui-widgets.js.map +1 -1
  169. package/lib/widgets/markdown/AgentResourceResolver.d.ts +31 -0
  170. package/lib/widgets/markdown/AgentResourceResolver.d.ts.map +1 -0
  171. package/lib/widgets/markdown/AgentResourceResolver.js +39 -0
  172. package/lib/widgets/markdown/AgentResourceResolver.js.map +1 -0
  173. package/lib/widgets/markdown/ArtifactEditingSurface.d.ts +63 -0
  174. package/lib/widgets/markdown/ArtifactEditingSurface.d.ts.map +1 -0
  175. package/lib/widgets/markdown/ArtifactEditingSurface.js +582 -0
  176. package/lib/widgets/markdown/ArtifactEditingSurface.js.map +1 -0
  177. package/lib/widgets/markdown/CollaborativeMarkdownRenderer.d.ts +69 -0
  178. package/lib/widgets/markdown/CollaborativeMarkdownRenderer.d.ts.map +1 -0
  179. package/lib/widgets/markdown/CollaborativeMarkdownRenderer.js +374 -0
  180. package/lib/widgets/markdown/CollaborativeMarkdownRenderer.js.map +1 -0
  181. package/lib/widgets/markdown/MarkdownLink.d.ts +3 -1
  182. package/lib/widgets/markdown/MarkdownLink.d.ts.map +1 -1
  183. package/lib/widgets/markdown/MarkdownLink.js +41 -14
  184. package/lib/widgets/markdown/MarkdownLink.js.map +1 -1
  185. package/lib/widgets/markdown/MarkdownRenderer.d.ts +8 -1
  186. package/lib/widgets/markdown/MarkdownRenderer.d.ts.map +1 -1
  187. package/lib/widgets/markdown/MarkdownRenderer.js +20 -4
  188. package/lib/widgets/markdown/MarkdownRenderer.js.map +1 -1
  189. package/lib/widgets/markdown/index.d.ts +5 -1
  190. package/lib/widgets/markdown/index.d.ts.map +1 -1
  191. package/lib/widgets/markdown/index.js +5 -1
  192. package/lib/widgets/markdown/index.js.map +1 -1
  193. package/lib/widgets/markdown/normalizeCustomSchemeLinks.d.ts.map +1 -1
  194. package/lib/widgets/markdown/normalizeCustomSchemeLinks.js +14 -2
  195. package/lib/widgets/markdown/normalizeCustomSchemeLinks.js.map +1 -1
  196. package/lib/widgets/markdown/textDiff.d.ts +52 -0
  197. package/lib/widgets/markdown/textDiff.d.ts.map +1 -0
  198. package/lib/widgets/markdown/textDiff.js +362 -0
  199. package/lib/widgets/markdown/textDiff.js.map +1 -0
  200. package/lib/widgets/markdown/useArtifactContent.d.ts +5 -3
  201. package/lib/widgets/markdown/useArtifactContent.d.ts.map +1 -1
  202. package/lib/widgets/markdown/useArtifactContent.js +6 -3
  203. package/lib/widgets/markdown/useArtifactContent.js.map +1 -1
  204. package/lib/widgets/markdown/useResolvedUrl.d.ts +1 -5
  205. package/lib/widgets/markdown/useResolvedUrl.d.ts.map +1 -1
  206. package/lib/widgets/markdown/useResolvedUrl.js +19 -31
  207. package/lib/widgets/markdown/useResolvedUrl.js.map +1 -1
  208. package/lib/widgets/xml-viewer/hooks/useXMLViewer.d.ts +9 -0
  209. package/lib/widgets/xml-viewer/hooks/useXMLViewer.d.ts.map +1 -1
  210. package/lib/widgets/xml-viewer/hooks/useXMLViewer.js +20 -17
  211. package/lib/widgets/xml-viewer/hooks/useXMLViewer.js.map +1 -1
  212. package/package.json +13 -9
  213. package/src/__tests__/a11y.test.tsx +4 -1
  214. package/src/__tests__/axe-helper.ts +17 -7
  215. package/src/__tests__/setup.ts +25 -0
  216. package/src/core/components/shadcn/filters/filterBar.tsx +1 -0
  217. package/src/core/components/shadcn/modal/dialog.tsx +3 -1
  218. package/src/core/components/shadcn/tabs.test.tsx +65 -0
  219. package/src/core/components/shadcn/tabs.tsx +25 -3
  220. package/src/features/agent/chat/AgentRightPanel.tsx +16 -1
  221. package/src/features/agent/chat/ArtifactsTab.test.tsx +55 -0
  222. package/src/features/agent/chat/ArtifactsTab.tsx +203 -52
  223. package/src/features/agent/chat/DocumentEditingActionCard.test.tsx +105 -0
  224. package/src/features/agent/chat/DocumentEditingActionCard.tsx +210 -0
  225. package/src/features/agent/chat/ModernAgentConversation.test.tsx +237 -40
  226. package/src/features/agent/chat/ModernAgentConversation.tsx +247 -75
  227. package/src/features/agent/chat/ModernAgentOutput/AgentMarkdownAnchor.test.tsx +79 -0
  228. package/src/features/agent/chat/ModernAgentOutput/AgentMarkdownAnchor.tsx +94 -0
  229. package/src/features/agent/chat/ModernAgentOutput/AllMessagesMixed.test.tsx +257 -34
  230. package/src/features/agent/chat/ModernAgentOutput/AllMessagesMixed.tsx +217 -105
  231. package/src/features/agent/chat/ModernAgentOutput/AttachmentPreview.tsx +17 -0
  232. package/src/features/agent/chat/ModernAgentOutput/CollapsibleAgentMarkdown.test.tsx +44 -0
  233. package/src/features/agent/chat/ModernAgentOutput/CollapsibleAgentMarkdown.tsx +88 -0
  234. package/src/features/agent/chat/ModernAgentOutput/MessageItem.tsx +30 -46
  235. package/src/features/agent/chat/ModernAgentOutput/ResourceChangeSummary.tsx +40 -0
  236. package/src/features/agent/chat/ModernAgentOutput/ResourceChip.test.tsx +148 -0
  237. package/src/features/agent/chat/ModernAgentOutput/ResourceChip.tsx +141 -0
  238. package/src/features/agent/chat/ModernAgentOutput/ToolCallGroup.tsx +33 -1
  239. package/src/features/agent/chat/ModernAgentOutput/resourceLinks.tsx +43 -0
  240. package/src/features/agent/chat/ModernAgentOutput/resourceSummary.test.ts +229 -0
  241. package/src/features/agent/chat/ModernAgentOutput/resourceSummary.ts +152 -0
  242. package/src/features/agent/chat/ModernAgentOutput/utils.test.ts +17 -0
  243. package/src/features/agent/chat/ModernAgentOutput/utils.ts +14 -1
  244. package/src/features/agent/chat/hooks/useAgentStream.test.tsx +188 -0
  245. package/src/features/agent/chat/hooks/useAgentStream.ts +28 -8
  246. package/src/features/agent/chat/index.ts +11 -0
  247. package/src/features/store/objects/DocumentSearchResults.tsx +2 -2
  248. package/src/features/store/objects/components/ContentOverview.tsx +216 -65
  249. package/src/features/store/objects/components/DocumentEditingConfigurationSelector.test.ts +34 -0
  250. package/src/features/store/objects/components/DocumentEditingConfigurationSelector.tsx +211 -0
  251. package/src/features/store/objects/components/DocumentEditingLockStatus.test.tsx +32 -0
  252. package/src/features/store/objects/components/DocumentEditingLockStatus.tsx +45 -0
  253. package/src/features/store/objects/components/DocumentEditingPanel.test.tsx +336 -0
  254. package/src/features/store/objects/components/DocumentEditingPanel.tsx +1236 -0
  255. package/src/features/store/objects/components/TextEditorPanel.test.tsx +48 -0
  256. package/src/features/store/objects/components/TextEditorPanel.tsx +41 -20
  257. package/src/features/store/objects/components/documentArtifactRefs.test.ts +65 -0
  258. package/src/features/store/objects/components/documentArtifactRefs.ts +72 -0
  259. package/src/features/store/objects/components/documentEditingAgentConfig.test.ts +28 -0
  260. package/src/features/store/objects/components/documentEditingAgentConfig.ts +41 -0
  261. package/src/features/store/objects/components/documentEditingPrompts.test.ts +26 -0
  262. package/src/features/store/objects/components/documentEditingPrompts.ts +12 -0
  263. package/src/features/store/objects/components/documentEditingRun.test.ts +176 -0
  264. package/src/features/store/objects/components/documentEditingRun.ts +184 -0
  265. package/src/features/store/objects/components/documentEditingTarget.test.ts +96 -0
  266. package/src/features/store/objects/components/documentEditingTarget.ts +63 -0
  267. package/src/features/store/objects/components/index.ts +1 -0
  268. package/src/features/store/objects/components/useContentPanelHooks.ts +52 -27
  269. package/src/features/store/objects/components/useDownloadFile.test.ts +20 -0
  270. package/src/features/store/objects/components/useDownloadFile.ts +5 -1
  271. package/src/features/store/objects/selection/actions/StartWorkflowButton.tsx +2 -2
  272. package/src/i18n/locales/ar.json +118 -1
  273. package/src/i18n/locales/de.json +118 -1
  274. package/src/i18n/locales/en.json +118 -0
  275. package/src/i18n/locales/es.json +118 -1
  276. package/src/i18n/locales/fr.json +118 -1
  277. package/src/i18n/locales/it.json +118 -1
  278. package/src/i18n/locales/ja.json +118 -1
  279. package/src/i18n/locales/ko.json +118 -1
  280. package/src/i18n/locales/pt.json +118 -1
  281. package/src/i18n/locales/ru.json +118 -1
  282. package/src/i18n/locales/tr.json +118 -1
  283. package/src/i18n/locales/zh-TW.json +118 -1
  284. package/src/i18n/locales/zh.json +118 -1
  285. package/src/rich-text/EditorToolbar.tsx +708 -0
  286. package/src/rich-text/VertesiaRichTextEditor.test.tsx +211 -0
  287. package/src/rich-text/VertesiaRichTextEditor.tsx +241 -0
  288. package/src/rich-text/index.ts +9 -0
  289. package/src/session/UserSession.test.ts +11 -11
  290. package/src/widgets/markdown/AgentResourceResolver.test.ts +24 -0
  291. package/src/widgets/markdown/AgentResourceResolver.tsx +70 -0
  292. package/src/widgets/markdown/ArtifactEditingSurface.test.tsx +537 -0
  293. package/src/widgets/markdown/ArtifactEditingSurface.tsx +878 -0
  294. package/src/widgets/markdown/CollaborativeMarkdownRenderer.test.tsx +484 -0
  295. package/src/widgets/markdown/CollaborativeMarkdownRenderer.tsx +825 -0
  296. package/src/widgets/markdown/MarkdownLink.test.tsx +122 -0
  297. package/src/widgets/markdown/MarkdownLink.tsx +102 -17
  298. package/src/widgets/markdown/MarkdownRenderer.tsx +30 -3
  299. package/src/widgets/markdown/index.ts +39 -1
  300. package/src/widgets/markdown/normalizeCustomSchemeLinks.ts +15 -2
  301. package/src/widgets/markdown/textDiff.test.ts +156 -0
  302. package/src/widgets/markdown/textDiff.ts +399 -0
  303. package/src/widgets/markdown/useArtifactContent.ts +9 -3
  304. package/src/widgets/markdown/useResolvedUrl.test.ts +29 -40
  305. package/src/widgets/markdown/useResolvedUrl.ts +33 -35
  306. package/src/widgets/xml-viewer/hooks/useXMLViewer.test.ts +26 -0
  307. package/src/widgets/xml-viewer/hooks/useXMLViewer.ts +23 -18
@@ -1,4 +1,9 @@
1
- import { type AgentMessage, AgentMessageType } from '@vertesia/common';
1
+ import {
2
+ type AgentMessage,
3
+ AgentMessageType,
4
+ type AgentResourceReference,
5
+ getResourcesFromMessage,
6
+ } from '@vertesia/common';
2
7
  import { Button, cn, onActivateKey, useToast } from '@vertesia/ui/core';
3
8
  import { useUITranslation } from '@vertesia/ui/i18n';
4
9
  import { useUserSession } from '@vertesia/ui/session';
@@ -9,6 +14,7 @@ import { memo, useEffect, useMemo, useRef, useState } from 'react';
9
14
  import { PulsatingCircle } from '../AnimatedThinkingDots';
10
15
  import { useImageLightbox } from '../ImageLightbox';
11
16
  import { getArtifactCacheKey, useArtifactUrlCache } from '../useArtifactUrlCache.js';
17
+ import { ResourceChipList } from './ResourceChip';
12
18
  import type { ToolExecutionStatus } from './utils';
13
19
 
14
20
  /** Keys that are internal metadata and not interesting to display */
@@ -27,8 +33,10 @@ const META_KEYS = new Set([
27
33
  'files',
28
34
  'outputFiles',
29
35
  'display_role',
36
+ 'duration_ms',
30
37
  'observation',
31
38
  'progress_messages',
39
+ 'resources',
32
40
  'browseruse',
33
41
  'browser_use',
34
42
  ]);
@@ -797,6 +805,23 @@ function ToolCallGroupComponent({
797
805
  // Extract workflow_run_id from messages (any message in the group should have it)
798
806
  const artifactRunId = messages.find((m) => m.workflow_run_id)?.workflow_run_id ?? messages[0]?.workflow_run_id;
799
807
 
808
+ // Aggregate resource references across the whole group so deep links stay visible even when the
809
+ // group is collapsed (mirrors how group images/files are surfaced at the group level).
810
+ const groupResources = useMemo(() => {
811
+ const seen = new Set<string>();
812
+ const out: AgentResourceReference[] = [];
813
+ for (const m of messages) {
814
+ for (const ref of getResourcesFromMessage(m)) {
815
+ const key = `${ref.type}:${ref.id}:${ref.action}`;
816
+ if (!seen.has(key)) {
817
+ seen.add(key);
818
+ out.push(ref);
819
+ }
820
+ }
821
+ }
822
+ return out;
823
+ }, [messages]);
824
+
800
825
  // Render status indicator based on tool execution status
801
826
  const renderStatusIndicator = () => {
802
827
  // Only pulse for the currently active/latest group
@@ -966,6 +991,13 @@ function ToolCallGroupComponent({
966
991
  {/* Show all images from the group prominently at the top */}
967
992
  <GroupImageDisplay messages={messages} artifactRunId={artifactRunId} />
968
993
 
994
+ {/* Deep links to resources created/updated/deleted in this group, independent of expansion state. */}
995
+ {groupResources.length > 0 && (
996
+ <div className="px-4 pt-1 pb-2">
997
+ <ResourceChipList resources={groupResources} workflowRunId={artifactRunId} />
998
+ </div>
999
+ )}
1000
+
969
1001
  {/* Collapsed summary - show tool calls as single-line rows with expand option */}
970
1002
  {isCollapsed && (
971
1003
  <div className="px-3 py-0.5 space-y-0">
@@ -0,0 +1,43 @@
1
+ import type { AgentResourceAction, AgentResourceType } from '@vertesia/common';
2
+ import type { LucideIcon } from 'lucide-react';
3
+ import {
4
+ Boxes,
5
+ BrainCircuit,
6
+ Database,
7
+ FileType,
8
+ MessagesSquare,
9
+ PanelsTopLeft,
10
+ PlayCircle,
11
+ SquareTerminal,
12
+ Workflow,
13
+ } from 'lucide-react';
14
+
15
+ const RESOURCE_ICONS: Record<AgentResourceType, LucideIcon> = {
16
+ document: Database,
17
+ collection: Boxes,
18
+ content_type: FileType,
19
+ interaction: MessagesSquare,
20
+ prompt: SquareTerminal,
21
+ agent: BrainCircuit,
22
+ workflow: Workflow,
23
+ process: Workflow,
24
+ process_run: PlayCircle,
25
+ interaction_run: PlayCircle,
26
+ view: PanelsTopLeft,
27
+ };
28
+
29
+ export function getResourceIcon(type: AgentResourceType): LucideIcon {
30
+ return RESOURCE_ICONS[type];
31
+ }
32
+
33
+ /** Badge variant for a resource action, using the semantic color system. */
34
+ export function getResourceActionVariant(action: AgentResourceAction): 'success' | 'info' | 'outline' {
35
+ switch (action) {
36
+ case 'created':
37
+ return 'success';
38
+ case 'deleted':
39
+ return 'outline';
40
+ default:
41
+ return 'info';
42
+ }
43
+ }
@@ -0,0 +1,229 @@
1
+ import { type AgentMessage, AgentMessageType, type AgentResourceReference } from '@vertesia/common';
2
+ import { describe, expect, it } from 'vitest';
3
+ import {
4
+ aggregateLatestTurnResources,
5
+ interleaveTurnSummaries,
6
+ isResourceSummaryReady,
7
+ segmentTurnResources,
8
+ } from './resourceSummary';
9
+
10
+ let seq = 0;
11
+ function msg(type: AgentMessageType, resources?: AgentResourceReference[], workstream_id = 'main'): AgentMessage {
12
+ seq += 1;
13
+ return {
14
+ timestamp: seq,
15
+ workflow_run_id: 'run-1',
16
+ type,
17
+ message: '',
18
+ workstream_id,
19
+ ...(resources ? { details: { event_class: 'activity', resources } } : {}),
20
+ };
21
+ }
22
+
23
+ function ref(
24
+ type: AgentResourceReference['type'],
25
+ id: string,
26
+ action: AgentResourceReference['action'],
27
+ label = id,
28
+ ): AgentResourceReference {
29
+ return { type, id, label, action };
30
+ }
31
+
32
+ describe('aggregateLatestTurnResources', () => {
33
+ it('returns resources from tool messages in the latest turn', () => {
34
+ const result = aggregateLatestTurnResources([
35
+ msg(AgentMessageType.QUESTION),
36
+ msg(AgentMessageType.THOUGHT, [ref('document', 'd1', 'created', 'Doc One')]),
37
+ msg(AgentMessageType.THOUGHT, [ref('collection', 'c1', 'created', 'Col One')]),
38
+ msg(AgentMessageType.COMPLETE),
39
+ ]);
40
+ expect(result).toEqual([
41
+ ref('document', 'd1', 'created', 'Doc One'),
42
+ ref('collection', 'c1', 'created', 'Col One'),
43
+ ]);
44
+ });
45
+
46
+ it('ignores resources from earlier turns', () => {
47
+ const result = aggregateLatestTurnResources([
48
+ msg(AgentMessageType.QUESTION),
49
+ msg(AgentMessageType.THOUGHT, [ref('document', 'old', 'created')]),
50
+ msg(AgentMessageType.COMPLETE),
51
+ msg(AgentMessageType.QUESTION),
52
+ msg(AgentMessageType.THOUGHT, [ref('document', 'new', 'created')]),
53
+ msg(AgentMessageType.COMPLETE),
54
+ ]);
55
+ expect(result).toEqual([ref('document', 'new', 'created')]);
56
+ });
57
+
58
+ it('dedupes by (type, id) and lets create win over later updates', () => {
59
+ const result = aggregateLatestTurnResources([
60
+ msg(AgentMessageType.QUESTION),
61
+ msg(AgentMessageType.THOUGHT, [ref('document', 'd1', 'created', 'First')]),
62
+ msg(AgentMessageType.THOUGHT, [ref('document', 'd1', 'updated', 'Renamed')]),
63
+ ]);
64
+ // create wins, but the most recent label is retained
65
+ expect(result).toEqual([ref('document', 'd1', 'created', 'Renamed')]);
66
+ });
67
+
68
+ it('drops a resource created then deleted in the same turn', () => {
69
+ const result = aggregateLatestTurnResources([
70
+ msg(AgentMessageType.QUESTION),
71
+ msg(AgentMessageType.THOUGHT, [ref('document', 'scratch', 'created')]),
72
+ msg(AgentMessageType.THOUGHT, [ref('document', 'scratch', 'deleted')]),
73
+ ]);
74
+ expect(result).toEqual([]);
75
+ });
76
+
77
+ it('keeps a resource deleted then re-created in the same turn (order matters)', () => {
78
+ const result = aggregateLatestTurnResources([
79
+ msg(AgentMessageType.QUESTION),
80
+ msg(AgentMessageType.THOUGHT, [ref('view', 'v1', 'deleted')]),
81
+ msg(AgentMessageType.THOUGHT, [ref('view', 'v1', 'created', 'Rebuilt View')]),
82
+ ]);
83
+ expect(result).toEqual([ref('view', 'v1', 'created', 'Rebuilt View')]);
84
+ });
85
+
86
+ it('reports a pre-existing resource deleted → recreated → deleted as deleted (not transient)', () => {
87
+ const result = aggregateLatestTurnResources([
88
+ msg(AgentMessageType.QUESTION),
89
+ msg(AgentMessageType.THOUGHT, [ref('view', 'v1', 'deleted')]),
90
+ msg(AgentMessageType.THOUGHT, [ref('view', 'v1', 'created')]),
91
+ msg(AgentMessageType.THOUGHT, [ref('view', 'v1', 'deleted', 'Old View')]),
92
+ ]);
93
+ expect(result).toEqual([ref('view', 'v1', 'deleted', 'Old View')]);
94
+ });
95
+
96
+ it('omits a resource created → deleted → created → deleted (never existed before the turn)', () => {
97
+ const result = aggregateLatestTurnResources([
98
+ msg(AgentMessageType.QUESTION),
99
+ msg(AgentMessageType.THOUGHT, [ref('view', 'v1', 'created')]),
100
+ msg(AgentMessageType.THOUGHT, [ref('view', 'v1', 'deleted')]),
101
+ msg(AgentMessageType.THOUGHT, [ref('view', 'v1', 'created')]),
102
+ msg(AgentMessageType.THOUGHT, [ref('view', 'v1', 'deleted')]),
103
+ ]);
104
+ expect(result).toEqual([]);
105
+ });
106
+
107
+ it('keeps a delete of a pre-existing resource', () => {
108
+ const result = aggregateLatestTurnResources([
109
+ msg(AgentMessageType.QUESTION),
110
+ msg(AgentMessageType.THOUGHT, [ref('interaction', 'i1', 'deleted', 'Old Interaction')]),
111
+ ]);
112
+ expect(result).toEqual([ref('interaction', 'i1', 'deleted', 'Old Interaction')]);
113
+ });
114
+
115
+ it('collapses repeated updates into a single entry', () => {
116
+ const result = aggregateLatestTurnResources([
117
+ msg(AgentMessageType.QUESTION),
118
+ msg(AgentMessageType.THOUGHT, [ref('collection', 'c1', 'updated')]),
119
+ msg(AgentMessageType.THOUGHT, [ref('collection', 'c1', 'updated')]),
120
+ ]);
121
+ expect(result).toEqual([ref('collection', 'c1', 'updated')]);
122
+ });
123
+
124
+ it('aggregates resources across child workstreams in the turn', () => {
125
+ const result = aggregateLatestTurnResources([
126
+ msg(AgentMessageType.QUESTION),
127
+ msg(AgentMessageType.THOUGHT, [ref('document', 'main-doc', 'created')], 'main'),
128
+ msg(AgentMessageType.THOUGHT, [ref('document', 'child-doc', 'created')], 'ws-2'),
129
+ ]);
130
+ expect(result).toEqual([ref('document', 'main-doc', 'created'), ref('document', 'child-doc', 'created')]);
131
+ });
132
+ });
133
+
134
+ describe('segmentTurnResources', () => {
135
+ it('returns one entry per turn with that turn’s resources', () => {
136
+ const turns = segmentTurnResources([
137
+ msg(AgentMessageType.QUESTION),
138
+ msg(AgentMessageType.THOUGHT, [ref('document', 'd1', 'created')]),
139
+ msg(AgentMessageType.COMPLETE),
140
+ msg(AgentMessageType.QUESTION),
141
+ msg(AgentMessageType.THOUGHT, [ref('collection', 'c1', 'created')]),
142
+ msg(AgentMessageType.COMPLETE),
143
+ ]);
144
+ expect(turns).toEqual([[ref('document', 'd1', 'created')], [ref('collection', 'c1', 'created')]]);
145
+ });
146
+
147
+ it('folds leading non-question messages into the first turn (index alignment)', () => {
148
+ const turns = segmentTurnResources([
149
+ msg(AgentMessageType.SYSTEM),
150
+ msg(AgentMessageType.QUESTION),
151
+ msg(AgentMessageType.THOUGHT, [ref('document', 'd1', 'created')]),
152
+ msg(AgentMessageType.QUESTION),
153
+ msg(AgentMessageType.THOUGHT, [ref('document', 'd2', 'created')]),
154
+ ]);
155
+ expect(turns).toHaveLength(2);
156
+ expect(turns[0]).toEqual([ref('document', 'd1', 'created')]);
157
+ expect(turns[1]).toEqual([ref('document', 'd2', 'created')]);
158
+ });
159
+ });
160
+
161
+ describe('isResourceSummaryReady', () => {
162
+ it('treats a final answer as the end of an interactive turn', () => {
163
+ expect(
164
+ isResourceSummaryReady([
165
+ msg(AgentMessageType.QUESTION),
166
+ msg(AgentMessageType.THOUGHT, [ref('document', 'd1', 'created')]),
167
+ msg(AgentMessageType.ANSWER),
168
+ ]),
169
+ ).toBe(true);
170
+ });
171
+
172
+ it('does not treat an answer as final when later main-workstream activity exists', () => {
173
+ expect(
174
+ isResourceSummaryReady([
175
+ msg(AgentMessageType.QUESTION),
176
+ msg(AgentMessageType.ANSWER),
177
+ msg(AgentMessageType.THOUGHT),
178
+ ]),
179
+ ).toBe(false);
180
+ });
181
+
182
+ it('does not let a child-workstream answer complete the main turn', () => {
183
+ expect(
184
+ isResourceSummaryReady([
185
+ msg(AgentMessageType.QUESTION),
186
+ msg(AgentMessageType.THOUGHT),
187
+ msg(AgentMessageType.ANSWER, undefined, 'child'),
188
+ ]),
189
+ ).toBe(false);
190
+ });
191
+ });
192
+
193
+ describe('interleaveTurnSummaries', () => {
194
+ type Item = { kind: 'q' | 'x'; id: string };
195
+ const q = (id: string): Item => ({ kind: 'q', id });
196
+ const x = (id: string): Item => ({ kind: 'x', id });
197
+ const isBoundary = (i: Item) => i.kind === 'q';
198
+ const turn = (id: string, action: AgentResourceReference['action'] = 'created'): AgentResourceReference[] => [
199
+ ref('document', id, action),
200
+ ];
201
+
202
+ it('inserts a completed turn’s summary before the next turn boundary and at the end', () => {
203
+ const items = [q('q1'), x('a'), q('q2'), x('b')];
204
+ const out = interleaveTurnSummaries(items, isBoundary, [turn('d1'), turn('d2')], true);
205
+ const kinds = out.map((i) => ('type' in i ? i.type : i.kind));
206
+ // summary for turn 0 appears before q2; summary for turn 1 at the end
207
+ expect(kinds).toEqual(['q', 'x', 'resource_summary', 'q', 'x', 'resource_summary']);
208
+ });
209
+
210
+ it('does not render the last (in-progress) turn summary until the stream is complete', () => {
211
+ const items = [q('q1'), x('a')];
212
+ const out = interleaveTurnSummaries(items, isBoundary, [turn('d1')], false);
213
+ expect(out.some((i) => 'type' in i && i.type === 'resource_summary')).toBe(false);
214
+ });
215
+
216
+ it('skips turns with no resources', () => {
217
+ const items = [q('q1'), x('a'), q('q2')];
218
+ const empty: AgentResourceReference[] = [];
219
+ const out = interleaveTurnSummaries(items, isBoundary, [empty, turn('d2')], true);
220
+ // turn 0 empty → no summary before q2; turn 1 has resources → summary at end
221
+ const kinds = out.map((i) => ('type' in i ? i.type : i.kind));
222
+ expect(kinds).toEqual(['q', 'x', 'q', 'resource_summary']);
223
+ });
224
+
225
+ it('returns items unchanged when there are no turn summaries', () => {
226
+ const items = [q('q1'), x('a')];
227
+ expect(interleaveTurnSummaries(items, isBoundary, [], true)).toBe(items);
228
+ });
229
+ });
@@ -0,0 +1,152 @@
1
+ import {
2
+ type AgentMessage,
3
+ AgentMessageType,
4
+ type AgentResourceReference,
5
+ getResourcesFromMessage,
6
+ } from '@vertesia/common';
7
+ import { getWorkstreamId, isInProgress } from './utils';
8
+
9
+ interface ResourceAccumulator {
10
+ ref: AgentResourceReference;
11
+ /** The first action observed for this resource in the turn — indicates whether it pre-existed. */
12
+ firstAction: AgentResourceReference['action'];
13
+ /** The most recent action observed, in message order. */
14
+ lastAction: AgentResourceReference['action'];
15
+ /** True once a `created` action was seen for this resource within the turn. */
16
+ createdInTurn: boolean;
17
+ }
18
+
19
+ /**
20
+ * Dedupe the resources referenced across a set of messages by (type, id), modeling operations
21
+ * sequentially (in message order):
22
+ * - first created then finally deleted (create → … → delete) → transient scratch work that never
23
+ * existed before the turn, omitted;
24
+ * - a resource that pre-existed (first action was delete/update) and ends deleted → kept as `deleted`
25
+ * (e.g. delete → create → delete reports `deleted`, not transient);
26
+ * - any sequence ending in create, or containing a create when not ending deleted → kept as `created`;
27
+ * - otherwise `updated`.
28
+ * The most recently seen label/revision is retained, and first-mention order is preserved.
29
+ */
30
+ function dedupeResources(messages: AgentMessage[]): AgentResourceReference[] {
31
+ const order: string[] = [];
32
+ const byKey = new Map<string, ResourceAccumulator>();
33
+
34
+ for (const message of messages) {
35
+ for (const ref of getResourcesFromMessage(message)) {
36
+ const key = `${ref.type}:${ref.id}`;
37
+ let entry = byKey.get(key);
38
+ if (!entry) {
39
+ entry = { ref, firstAction: ref.action, lastAction: ref.action, createdInTurn: false };
40
+ byKey.set(key, entry);
41
+ order.push(key);
42
+ }
43
+ entry.ref = ref;
44
+ entry.lastAction = ref.action;
45
+ if (ref.action === 'created') entry.createdInTurn = true;
46
+ }
47
+ }
48
+
49
+ const result: AgentResourceReference[] = [];
50
+ for (const key of order) {
51
+ const entry = byKey.get(key);
52
+ if (!entry) continue;
53
+ // Created first and ultimately deleted → never existed before the turn, transient: omit.
54
+ if (entry.firstAction === 'created' && entry.lastAction === 'deleted') continue;
55
+ const action: AgentResourceReference['action'] =
56
+ entry.lastAction === 'deleted'
57
+ ? 'deleted'
58
+ : entry.lastAction === 'created' || entry.createdInTurn
59
+ ? 'created'
60
+ : 'updated';
61
+ result.push({ ...entry.ref, action });
62
+ }
63
+ return result;
64
+ }
65
+
66
+ /**
67
+ * Split the conversation into turns at user QUESTION boundaries and return the deduped resources for
68
+ * each turn, in order. A turn spans from one user message up to (but excluding) the next. Turns with
69
+ * no resources still produce an entry (empty list) so callers can align positions with turn indices.
70
+ */
71
+ export function segmentTurnResources(messages: AgentMessage[]): AgentResourceReference[][] {
72
+ if (messages.length === 0) return [];
73
+ const turns: AgentMessage[][] = [];
74
+ let current: AgentMessage[] = [];
75
+ let currentHasQuestion = false;
76
+ for (const message of messages) {
77
+ // Start a new turn at each user QUESTION, but fold any leading non-question messages (e.g. an
78
+ // initial system prompt) into the first turn so turn indices align with QUESTION order.
79
+ if (message.type === AgentMessageType.QUESTION && currentHasQuestion) {
80
+ turns.push(current);
81
+ current = [];
82
+ currentHasQuestion = false;
83
+ }
84
+ if (message.type === AgentMessageType.QUESTION) currentHasQuestion = true;
85
+ current.push(message);
86
+ }
87
+ if (current.length > 0) turns.push(current);
88
+
89
+ return turns.map(dedupeResources);
90
+ }
91
+
92
+ /**
93
+ * Aggregate the resources created/updated/deleted during the most recent turn. Convenience wrapper
94
+ * over {@link segmentTurnResources} for callers that only need the latest turn.
95
+ */
96
+ export function aggregateLatestTurnResources(messages: AgentMessage[]): AgentResourceReference[] {
97
+ const turns = segmentTurnResources(messages);
98
+ return turns.length > 0 ? turns[turns.length - 1] : [];
99
+ }
100
+
101
+ /**
102
+ * Whether the latest main-workstream turn has produced its final response and its resource summary
103
+ * can be shown. Interactive conversations commonly remain alive after an ANSWER so they can accept
104
+ * another user message; requiring the entire workflow to become terminal would hide the summary for
105
+ * those otherwise-complete turns.
106
+ */
107
+ export function isResourceSummaryReady(messages: AgentMessage[]): boolean {
108
+ if (!isInProgress(messages)) return true;
109
+
110
+ const mainMessages = messages.filter((message) => getWorkstreamId(message) === 'main');
111
+ const lastMainMessage = mainMessages[mainMessages.length - 1];
112
+ return lastMainMessage?.type === AgentMessageType.ANSWER;
113
+ }
114
+
115
+ export interface ResourceSummaryRenderItem {
116
+ type: 'resource_summary';
117
+ key: string;
118
+ resources: AgentResourceReference[];
119
+ }
120
+
121
+ /**
122
+ * Interleave per-turn resource summaries into a rendered timeline of `items`. A completed turn's
123
+ * summary is emitted right before the next turn begins, and the most recent completed turn's summary
124
+ * at the end. `isTurnBoundary` identifies the user-message item that starts a new turn; the i-th
125
+ * boundary maps to `turnResources[i - 1]`. The final (most recent) turn is only summarized when
126
+ * `isLatestTurnComplete` is true, so an in-progress turn shows no summary. Empty turns are skipped.
127
+ */
128
+ export function interleaveTurnSummaries<T>(
129
+ items: T[],
130
+ isTurnBoundary: (item: T) => boolean,
131
+ turnResources: AgentResourceReference[][],
132
+ isLatestTurnComplete: boolean,
133
+ ): Array<T | ResourceSummaryRenderItem> {
134
+ if (turnResources.length === 0) return items;
135
+ const out: Array<T | ResourceSummaryRenderItem> = [];
136
+ const pushSummary = (turnIndex: number) => {
137
+ const resources = turnResources[turnIndex];
138
+ if (!resources || resources.length === 0) return;
139
+ const isLastTurn = turnIndex >= turnResources.length - 1;
140
+ if (isLastTurn && !isLatestTurnComplete) return;
141
+ out.push({ type: 'resource_summary', key: `resource-summary-${turnIndex}`, resources });
142
+ };
143
+ let boundariesSeen = 0;
144
+ for (const item of items) {
145
+ const boundary = isTurnBoundary(item);
146
+ if (boundary && boundariesSeen > 0) pushSummary(boundariesSeen - 1);
147
+ if (boundary) boundariesSeen += 1;
148
+ out.push(item);
149
+ }
150
+ pushSummary(boundariesSeen > 0 ? boundariesSeen - 1 : turnResources.length - 1);
151
+ return out;
152
+ }
@@ -154,6 +154,23 @@ describe('ModernAgentOutput utils - progress state', () => {
154
154
 
155
155
  expect(isInProgress([idle, question])).toBe(true);
156
156
  });
157
+
158
+ it('ignores passive artifact working-copy saves after the agent goes idle', () => {
159
+ const idle = makeMessage({
160
+ timestamp: 1000,
161
+ type: AgentMessageType.IDLE,
162
+ message: 'Waiting for your command...',
163
+ });
164
+ const userAutosave = makeMessage({
165
+ timestamp: 2000,
166
+ type: AgentMessageType.UPDATE,
167
+ message: 'Updated working copy files/blog_post_draft.md',
168
+ details: { event_class: 'artifact_updated', source: 'user_edit', generation: '1784609448394436' },
169
+ });
170
+
171
+ // The user editing their artifact while the agent is idle must not resurrect "Working".
172
+ expect(isInProgress([idle, userAutosave])).toBe(false);
173
+ });
157
174
  });
158
175
 
159
176
  describe('ModernAgentOutput utils - sliding view thinking', () => {
@@ -55,6 +55,19 @@ export const DONE_STATES = [
55
55
  AgentMessageType.TERMINATED,
56
56
  ];
57
57
 
58
+ /**
59
+ * Passive artifact working-copy saves (e.g. the user autosaving their edits in the collaborative
60
+ * editor) arrive as UPDATE messages with an `artifact_updated` event class. They are not agent
61
+ * activity: they must not flip the conversation back to "Working" and must not render as tool-call
62
+ * cards in the timeline.
63
+ */
64
+ export function isPassiveArtifactUpdate(message: AgentMessage): boolean {
65
+ return (
66
+ message.type === AgentMessageType.UPDATE &&
67
+ (message.details as { event_class?: string } | undefined)?.event_class === 'artifact_updated'
68
+ );
69
+ }
70
+
58
71
  export function isInProgress(messages: AgentMessage[]) {
59
72
  if (!messages.length) return true;
60
73
 
@@ -63,7 +76,7 @@ export function isInProgress(messages: AgentMessage[]) {
63
76
  message.details?._deliveryStatus === 'failed' &&
64
77
  DONE_STATES.includes(message.type);
65
78
 
66
- const progressMessages = messages.filter((m) => !isTerminalOptimisticFailure(m));
79
+ const progressMessages = messages.filter((m) => !isTerminalOptimisticFailure(m) && !isPassiveArtifactUpdate(m));
67
80
 
68
81
  // Only the main workstream determines whether the conversation is in progress.
69
82
  // Child workstream COMPLETE/IDLE messages must not flip this flag.