@stigmer/react 3.0.9-dev.20260615153829 → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (439) hide show
  1. package/execution/ApprovalCard.d.ts +47 -3
  2. package/execution/ApprovalCard.d.ts.map +1 -1
  3. package/execution/ApprovalCard.js +138 -70
  4. package/execution/ApprovalCard.js.map +1 -1
  5. package/execution/ApprovalContext.d.ts +72 -0
  6. package/execution/ApprovalContext.d.ts.map +1 -0
  7. package/execution/ApprovalContext.js +41 -0
  8. package/execution/ApprovalContext.js.map +1 -0
  9. package/execution/ArtifactCard.d.ts.map +1 -1
  10. package/execution/ArtifactCard.js +3 -1
  11. package/execution/ArtifactCard.js.map +1 -1
  12. package/execution/ArtifactPreviewModal.d.ts.map +1 -1
  13. package/execution/ArtifactPreviewModal.js +5 -3
  14. package/execution/ArtifactPreviewModal.js.map +1 -1
  15. package/execution/EmptyChangeNotice.d.ts +38 -0
  16. package/execution/EmptyChangeNotice.d.ts.map +1 -0
  17. package/execution/EmptyChangeNotice.js +27 -0
  18. package/execution/EmptyChangeNotice.js.map +1 -0
  19. package/execution/FileChangesView.d.ts +81 -0
  20. package/execution/FileChangesView.d.ts.map +1 -0
  21. package/execution/FileChangesView.js +164 -0
  22. package/execution/FileChangesView.js.map +1 -0
  23. package/execution/FilePathLink.d.ts +24 -4
  24. package/execution/FilePathLink.d.ts.map +1 -1
  25. package/execution/FilePathLink.js +31 -9
  26. package/execution/FilePathLink.js.map +1 -1
  27. package/execution/FileReviewCard.d.ts +111 -0
  28. package/execution/FileReviewCard.d.ts.map +1 -0
  29. package/execution/FileReviewCard.js +558 -0
  30. package/execution/FileReviewCard.js.map +1 -0
  31. package/execution/FileReviewContext.d.ts +35 -0
  32. package/execution/FileReviewContext.d.ts.map +1 -0
  33. package/execution/FileReviewContext.js +23 -0
  34. package/execution/FileReviewContext.js.map +1 -0
  35. package/execution/FileReviewDock.d.ts +73 -0
  36. package/execution/FileReviewDock.d.ts.map +1 -0
  37. package/execution/FileReviewDock.js +65 -0
  38. package/execution/FileReviewDock.js.map +1 -0
  39. package/execution/McpToolDetail.d.ts.map +1 -1
  40. package/execution/McpToolDetail.js +38 -5
  41. package/execution/McpToolDetail.js.map +1 -1
  42. package/execution/MessageThread.d.ts +42 -3
  43. package/execution/MessageThread.d.ts.map +1 -1
  44. package/execution/MessageThread.js +263 -18
  45. package/execution/MessageThread.js.map +1 -1
  46. package/execution/PlanArtifactCard.d.ts.map +1 -1
  47. package/execution/PlanArtifactCard.js +3 -1
  48. package/execution/PlanArtifactCard.js.map +1 -1
  49. package/execution/PlanCompletionCard.d.ts +1 -1
  50. package/execution/PlanCompletionCard.js +1 -1
  51. package/execution/ResultView.d.ts +19 -1
  52. package/execution/ResultView.d.ts.map +1 -1
  53. package/execution/ResultView.js +141 -40
  54. package/execution/ResultView.js.map +1 -1
  55. package/execution/SubAgentSection.d.ts.map +1 -1
  56. package/execution/SubAgentSection.js +45 -6
  57. package/execution/SubAgentSection.js.map +1 -1
  58. package/execution/TerminalSession.d.ts +34 -0
  59. package/execution/TerminalSession.d.ts.map +1 -0
  60. package/execution/TerminalSession.js +34 -0
  61. package/execution/TerminalSession.js.map +1 -0
  62. package/execution/TodoCard.d.ts +53 -0
  63. package/execution/TodoCard.d.ts.map +1 -0
  64. package/execution/TodoCard.js +77 -0
  65. package/execution/TodoCard.js.map +1 -0
  66. package/execution/ToolArgsView.d.ts +7 -1
  67. package/execution/ToolArgsView.d.ts.map +1 -1
  68. package/execution/ToolArgsView.js +10 -9
  69. package/execution/ToolArgsView.js.map +1 -1
  70. package/execution/ToolCallDetail.d.ts +10 -1
  71. package/execution/ToolCallDetail.d.ts.map +1 -1
  72. package/execution/ToolCallDetail.js +66 -19
  73. package/execution/ToolCallDetail.js.map +1 -1
  74. package/execution/ToolCallGroup.d.ts +28 -18
  75. package/execution/ToolCallGroup.d.ts.map +1 -1
  76. package/execution/ToolCallGroup.js +39 -148
  77. package/execution/ToolCallGroup.js.map +1 -1
  78. package/execution/ToolCallItem.d.ts +36 -8
  79. package/execution/ToolCallItem.d.ts.map +1 -1
  80. package/execution/ToolCallItem.js +140 -21
  81. package/execution/ToolCallItem.js.map +1 -1
  82. package/execution/ToolRunGroup.d.ts +48 -0
  83. package/execution/ToolRunGroup.d.ts.map +1 -0
  84. package/execution/ToolRunGroup.js +168 -0
  85. package/execution/ToolRunGroup.js.map +1 -0
  86. package/execution/approval-provenance.d.ts +2 -0
  87. package/execution/approval-provenance.d.ts.map +1 -0
  88. package/execution/approval-provenance.js +6 -0
  89. package/execution/approval-provenance.js.map +1 -0
  90. package/execution/artifact-utils.d.ts +0 -10
  91. package/execution/artifact-utils.d.ts.map +1 -1
  92. package/execution/artifact-utils.js +0 -17
  93. package/execution/artifact-utils.js.map +1 -1
  94. package/execution/execution-phases.d.ts +1 -6
  95. package/execution/execution-phases.d.ts.map +1 -1
  96. package/execution/execution-phases.js +4 -14
  97. package/execution/execution-phases.js.map +1 -1
  98. package/execution/file-path-resolver.d.ts +28 -0
  99. package/execution/file-path-resolver.d.ts.map +1 -1
  100. package/execution/file-path-resolver.js +24 -0
  101. package/execution/file-path-resolver.js.map +1 -1
  102. package/execution/file-review-status.d.ts +127 -0
  103. package/execution/file-review-status.d.ts.map +1 -0
  104. package/execution/file-review-status.js +135 -0
  105. package/execution/file-review-status.js.map +1 -0
  106. package/execution/index.d.ts +39 -5
  107. package/execution/index.d.ts.map +1 -1
  108. package/execution/index.js +23 -3
  109. package/execution/index.js.map +1 -1
  110. package/execution/segment-tool-calls.d.ts +46 -0
  111. package/execution/segment-tool-calls.d.ts.map +1 -0
  112. package/execution/segment-tool-calls.js +71 -0
  113. package/execution/segment-tool-calls.js.map +1 -0
  114. package/execution/tool-categories.d.ts +62 -1
  115. package/execution/tool-categories.d.ts.map +1 -1
  116. package/execution/tool-categories.js +96 -2
  117. package/execution/tool-categories.js.map +1 -1
  118. package/execution/tool-presenter.d.ts +38 -2
  119. package/execution/tool-presenter.d.ts.map +1 -1
  120. package/execution/tool-presenter.js +23 -1
  121. package/execution/tool-presenter.js.map +1 -1
  122. package/execution/tool-rendering-primitives.d.ts.map +1 -1
  123. package/execution/tool-rendering-primitives.js +3 -2
  124. package/execution/tool-rendering-primitives.js.map +1 -1
  125. package/execution/useArtifactDownload.d.ts +44 -0
  126. package/execution/useArtifactDownload.d.ts.map +1 -0
  127. package/execution/useArtifactDownload.js +80 -0
  128. package/execution/useArtifactDownload.js.map +1 -0
  129. package/execution/useArtifactDownloadUrl.d.ts +67 -0
  130. package/execution/useArtifactDownloadUrl.d.ts.map +1 -0
  131. package/execution/useArtifactDownloadUrl.js +59 -0
  132. package/execution/useArtifactDownloadUrl.js.map +1 -0
  133. package/execution/useFileChangeContent.d.ts +61 -0
  134. package/execution/useFileChangeContent.d.ts.map +1 -0
  135. package/execution/useFileChangeContent.js +89 -0
  136. package/execution/useFileChangeContent.js.map +1 -0
  137. package/execution/useFileReview.d.ts +89 -0
  138. package/execution/useFileReview.d.ts.map +1 -0
  139. package/execution/useFileReview.js +106 -0
  140. package/execution/useFileReview.js.map +1 -0
  141. package/execution/useSubmitApproval.d.ts +17 -2
  142. package/execution/useSubmitApproval.d.ts.map +1 -1
  143. package/execution/useSubmitApproval.js +27 -23
  144. package/execution/useSubmitApproval.js.map +1 -1
  145. package/execution/useToolOutputContent.d.ts +44 -0
  146. package/execution/useToolOutputContent.d.ts.map +1 -0
  147. package/execution/useToolOutputContent.js +31 -0
  148. package/execution/useToolOutputContent.js.map +1 -0
  149. package/index.d.ts +6 -6
  150. package/index.d.ts.map +1 -1
  151. package/index.js +3 -3
  152. package/index.js.map +1 -1
  153. package/internal/ApprovalPeekBar.d.ts +23 -0
  154. package/internal/ApprovalPeekBar.d.ts.map +1 -0
  155. package/internal/ApprovalPeekBar.js +25 -0
  156. package/internal/ApprovalPeekBar.js.map +1 -0
  157. package/internal/BoundedContent.d.ts +49 -0
  158. package/internal/BoundedContent.d.ts.map +1 -0
  159. package/internal/BoundedContent.js +50 -0
  160. package/internal/BoundedContent.js.map +1 -0
  161. package/internal/DecisionButton.d.ts +47 -0
  162. package/internal/DecisionButton.d.ts.map +1 -0
  163. package/internal/DecisionButton.js +42 -0
  164. package/internal/DecisionButton.js.map +1 -0
  165. package/internal/InCardDecisionError.d.ts +31 -0
  166. package/internal/InCardDecisionError.d.ts.map +1 -0
  167. package/internal/InCardDecisionError.js +23 -0
  168. package/internal/InCardDecisionError.js.map +1 -0
  169. package/internal/RevealToggle.d.ts +36 -0
  170. package/internal/RevealToggle.d.ts.map +1 -0
  171. package/internal/RevealToggle.js +32 -0
  172. package/internal/RevealToggle.js.map +1 -0
  173. package/internal/VirtualizedThread.d.ts +7 -1
  174. package/internal/VirtualizedThread.d.ts.map +1 -1
  175. package/internal/VirtualizedThread.js +7 -3
  176. package/internal/VirtualizedThread.js.map +1 -1
  177. package/internal/code-highlight.d.ts +24 -0
  178. package/internal/code-highlight.d.ts.map +1 -0
  179. package/internal/code-highlight.js +115 -0
  180. package/internal/code-highlight.js.map +1 -0
  181. package/internal/markdown-components.d.ts.map +1 -1
  182. package/internal/markdown-components.js +13 -2
  183. package/internal/markdown-components.js.map +1 -1
  184. package/internal/useAutoDisclosure.d.ts +34 -0
  185. package/internal/useAutoDisclosure.d.ts.map +1 -0
  186. package/internal/useAutoDisclosure.js +53 -0
  187. package/internal/useAutoDisclosure.js.map +1 -0
  188. package/internal/useElapsedSince.d.ts +30 -0
  189. package/internal/useElapsedSince.d.ts.map +1 -0
  190. package/internal/useElapsedSince.js +70 -0
  191. package/internal/useElapsedSince.js.map +1 -0
  192. package/internal/useIsOverflowing.d.ts +30 -0
  193. package/internal/useIsOverflowing.d.ts.map +1 -0
  194. package/internal/useIsOverflowing.js +47 -0
  195. package/internal/useIsOverflowing.js.map +1 -0
  196. package/internal/useIsTextTruncated.d.ts +24 -0
  197. package/internal/useIsTextTruncated.d.ts.map +1 -0
  198. package/internal/useIsTextTruncated.js +41 -0
  199. package/internal/useIsTextTruncated.js.map +1 -0
  200. package/internal/useKeyedSubmission.d.ts +49 -0
  201. package/internal/useKeyedSubmission.d.ts.map +1 -0
  202. package/internal/useKeyedSubmission.js +67 -0
  203. package/internal/useKeyedSubmission.js.map +1 -0
  204. package/package.json +7 -4
  205. package/session/SessionViewer.d.ts +2 -2
  206. package/session/SessionViewer.d.ts.map +1 -1
  207. package/session/SessionViewer.js +10 -4
  208. package/session/SessionViewer.js.map +1 -1
  209. package/session/index.d.ts +2 -0
  210. package/session/index.d.ts.map +1 -1
  211. package/session/index.js +1 -0
  212. package/session/index.js.map +1 -1
  213. package/session/inspector/ChangesTab.d.ts +8 -5
  214. package/session/inspector/ChangesTab.d.ts.map +1 -1
  215. package/session/inspector/ChangesTab.js +11 -8
  216. package/session/inspector/ChangesTab.js.map +1 -1
  217. package/session/inspector/SessionInspector.d.ts +4 -4
  218. package/session/inspector/SessionInspector.d.ts.map +1 -1
  219. package/session/inspector/SessionInspector.js +4 -4
  220. package/session/inspector/SessionInspector.js.map +1 -1
  221. package/session/inspector/index.d.ts +0 -2
  222. package/session/inspector/index.d.ts.map +1 -1
  223. package/session/inspector/index.js +0 -1
  224. package/session/inspector/index.js.map +1 -1
  225. package/session/inspector/useSessionInspector.d.ts +8 -4
  226. package/session/inspector/useSessionInspector.d.ts.map +1 -1
  227. package/session/inspector/useSessionInspector.js +27 -20
  228. package/session/inspector/useSessionInspector.js.map +1 -1
  229. package/session/useSessionConversation.d.ts +37 -3
  230. package/session/useSessionConversation.d.ts.map +1 -1
  231. package/session/useSessionConversation.js +18 -2
  232. package/session/useSessionConversation.js.map +1 -1
  233. package/session/useSessionFileChanges.d.ts +74 -0
  234. package/session/useSessionFileChanges.d.ts.map +1 -0
  235. package/session/useSessionFileChanges.js +178 -0
  236. package/session/useSessionFileChanges.js.map +1 -0
  237. package/src/__tests__/styles-border-layer-invariant.test.ts +105 -0
  238. package/src/execution/ApprovalCard.tsx +368 -230
  239. package/src/execution/ApprovalContext.ts +109 -0
  240. package/src/execution/ArtifactCard.tsx +10 -6
  241. package/src/execution/ArtifactPreviewModal.tsx +12 -6
  242. package/src/execution/EmptyChangeNotice.tsx +62 -0
  243. package/src/execution/FileChangesView.tsx +383 -0
  244. package/src/execution/FilePathLink.tsx +65 -12
  245. package/src/execution/FileReviewCard.tsx +1118 -0
  246. package/src/execution/FileReviewContext.ts +53 -0
  247. package/src/execution/FileReviewDock.tsx +157 -0
  248. package/src/execution/McpToolDetail.tsx +98 -32
  249. package/src/execution/MessageThread.tsx +374 -9
  250. package/src/execution/PlanArtifactCard.tsx +9 -6
  251. package/src/execution/PlanCompletionCard.tsx +1 -1
  252. package/src/execution/ResultView.tsx +333 -162
  253. package/src/execution/SubAgentSection.tsx +58 -11
  254. package/src/execution/TerminalSession.tsx +91 -0
  255. package/src/execution/TodoCard.tsx +186 -0
  256. package/src/execution/ToolArgsView.tsx +28 -17
  257. package/src/execution/ToolCallDetail.tsx +137 -26
  258. package/src/execution/ToolCallGroup.tsx +68 -296
  259. package/src/execution/ToolCallItem.tsx +265 -67
  260. package/src/execution/ToolRunGroup.tsx +302 -0
  261. package/src/execution/__tests__/ApprovalCard.test.tsx +395 -5
  262. package/src/execution/__tests__/ArtifactContentRenderer.test.tsx +15 -0
  263. package/src/execution/__tests__/ArtifactPreviewModal.test.tsx +0 -1
  264. package/src/execution/__tests__/EmptyChangeNotice.test.tsx +24 -0
  265. package/src/execution/__tests__/FileChangesView.test.tsx +296 -0
  266. package/src/execution/__tests__/FilePathLink.test.tsx +92 -0
  267. package/src/execution/__tests__/FileReviewCard.test.tsx +1044 -0
  268. package/src/execution/__tests__/FileReviewDock.test.tsx +134 -0
  269. package/src/execution/__tests__/McpToolDetail.test.tsx +79 -0
  270. package/src/execution/__tests__/MessageThread.test.tsx +324 -0
  271. package/src/execution/__tests__/PlanArtifactCard.test.tsx +37 -16
  272. package/src/execution/__tests__/ResultView.test.tsx +280 -2
  273. package/src/execution/__tests__/SubAgentSection.test.tsx +180 -0
  274. package/src/execution/__tests__/TerminalSession.test.tsx +72 -0
  275. package/src/execution/__tests__/TodoCard.test.tsx +102 -0
  276. package/src/execution/__tests__/ToolArgsView.test.tsx +48 -0
  277. package/src/execution/__tests__/ToolCallDetail.test.tsx +195 -0
  278. package/src/execution/__tests__/ToolCallGroup.test.tsx +132 -0
  279. package/src/execution/__tests__/ToolCallItem.test.tsx +521 -0
  280. package/src/execution/__tests__/ToolRunGroup.test.tsx +137 -0
  281. package/src/execution/__tests__/blanked-narration.test.tsx +149 -0
  282. package/src/execution/__tests__/buildThreadItems-approvals.test.ts +304 -0
  283. package/src/execution/__tests__/buildThreadItems-file-review.test.ts +331 -0
  284. package/src/execution/__tests__/file-path-resolver.test.ts +44 -0
  285. package/src/execution/__tests__/file-review-status.test.ts +343 -0
  286. package/src/execution/__tests__/message-entry.test.tsx +16 -0
  287. package/src/execution/__tests__/segment-tool-calls.test.ts +124 -0
  288. package/src/execution/__tests__/subagent-file-review-badge.test.tsx +273 -0
  289. package/src/execution/__tests__/thread-keys.test.ts +195 -0
  290. package/src/execution/__tests__/tool-presenter.test.tsx +95 -0
  291. package/src/execution/__tests__/useArtifactDownload.test.tsx +67 -0
  292. package/src/execution/__tests__/useArtifactDownloadUrl.test.tsx +51 -0
  293. package/src/execution/__tests__/useFileChangeContent.test.tsx +240 -0
  294. package/src/execution/__tests__/useFileReview.test.tsx +236 -0
  295. package/src/execution/__tests__/useSubmitApproval.test.tsx +165 -0
  296. package/src/execution/__tests__/useToolOutputContent.test.tsx +82 -0
  297. package/src/execution/approval-provenance.ts +8 -0
  298. package/src/execution/artifact-utils.ts +0 -18
  299. package/src/execution/execution-phases.ts +4 -16
  300. package/src/execution/file-path-resolver.ts +38 -0
  301. package/src/execution/file-review-status.ts +219 -0
  302. package/src/execution/index.ts +84 -4
  303. package/src/execution/segment-tool-calls.ts +101 -0
  304. package/src/execution/tool-categories.ts +128 -1
  305. package/src/execution/tool-presenter.ts +58 -3
  306. package/src/execution/tool-rendering-primitives.tsx +11 -14
  307. package/src/execution/useArtifactDownload.ts +109 -0
  308. package/src/execution/useArtifactDownloadUrl.ts +116 -0
  309. package/src/execution/useFileChangeContent.ts +149 -0
  310. package/src/execution/useFileReview.ts +197 -0
  311. package/src/execution/useSubmitApproval.ts +49 -27
  312. package/src/execution/useToolOutputContent.ts +70 -0
  313. package/src/index.ts +60 -1
  314. package/src/internal/ApprovalPeekBar.tsx +73 -0
  315. package/src/internal/BoundedContent.tsx +97 -0
  316. package/src/internal/DecisionButton.tsx +127 -0
  317. package/src/internal/InCardDecisionError.tsx +47 -0
  318. package/src/internal/RevealToggle.tsx +91 -0
  319. package/src/internal/VirtualizedThread.tsx +28 -2
  320. package/src/internal/__tests__/ApprovalPeekBar.test.tsx +34 -0
  321. package/src/internal/__tests__/BoundedContent.test.tsx +92 -0
  322. package/src/internal/__tests__/DecisionButton.test.tsx +107 -0
  323. package/src/internal/__tests__/RevealToggle.test.tsx +61 -0
  324. package/src/internal/__tests__/code-highlight.test.tsx +59 -0
  325. package/src/internal/__tests__/markdown-components.test.tsx +68 -2
  326. package/src/internal/__tests__/useAutoDisclosure.test.ts +74 -0
  327. package/src/internal/__tests__/useElapsedSince.test.tsx +68 -0
  328. package/src/internal/__tests__/useIsOverflowing.test.ts +36 -0
  329. package/src/internal/__tests__/useKeyedSubmission.test.tsx +126 -0
  330. package/src/internal/code-highlight.tsx +120 -0
  331. package/src/internal/markdown-components.tsx +20 -3
  332. package/src/internal/useAutoDisclosure.ts +58 -0
  333. package/src/internal/useElapsedSince.ts +72 -0
  334. package/src/internal/useIsOverflowing.ts +61 -0
  335. package/src/internal/useIsTextTruncated.ts +55 -0
  336. package/src/internal/useKeyedSubmission.ts +101 -0
  337. package/src/session/SessionViewer.tsx +21 -4
  338. package/src/session/__tests__/SessionViewer-fileReviewDock.test.tsx +198 -0
  339. package/src/session/__tests__/audienceWiring.test.tsx +4 -0
  340. package/src/session/__tests__/useSessionFileChanges.test.ts +303 -0
  341. package/src/session/index.ts +3 -0
  342. package/src/session/inspector/ChangesTab.tsx +20 -17
  343. package/src/session/inspector/SessionInspector.tsx +12 -10
  344. package/src/session/inspector/__tests__/ArtifactsTab.test.tsx +0 -1
  345. package/src/session/inspector/__tests__/ChangesTab.test.tsx +38 -0
  346. package/src/session/inspector/__tests__/useSessionInspector.test.ts +46 -61
  347. package/src/session/inspector/index.ts +0 -3
  348. package/src/session/inspector/useSessionInspector.ts +27 -20
  349. package/src/session/useSessionConversation.ts +80 -3
  350. package/src/session/useSessionFileChanges.ts +207 -0
  351. package/src/styles.css +123 -10
  352. package/src/version-history/DiffViewer.tsx +10 -7
  353. package/src/version-history/UnifiedDiffView.tsx +66 -0
  354. package/src/version-history/__tests__/DiffViewer.test.tsx +57 -0
  355. package/src/version-history/__tests__/UnifiedDiffView.test.tsx +50 -0
  356. package/src/version-history/__tests__/computeDiff.test.ts +35 -0
  357. package/src/version-history/__tests__/parseUnifiedDiff.test.ts +82 -0
  358. package/src/version-history/computeDiff.ts +6 -37
  359. package/src/version-history/diff-hunks.ts +63 -0
  360. package/src/version-history/index.ts +1 -0
  361. package/src/version-history/parseUnifiedDiff.ts +34 -0
  362. package/src/workflow/WorkflowExecutionApprovalCard.tsx +49 -24
  363. package/src/workflow/WorkflowExecutionTimeline.tsx +15 -4
  364. package/src/workflow/WorkflowExecutionTimelineEvent.tsx +8 -3
  365. package/src/workflow/WorkflowExecutionViewer.tsx +72 -28
  366. package/src/workflow/WorkflowFileReviewList.tsx +152 -0
  367. package/src/workflow/WorkflowTaskApprovalCard.tsx +37 -85
  368. package/src/workflow/__tests__/ExecutionInspector.test.tsx +30 -2
  369. package/src/workflow/__tests__/WorkflowExecutionApprovalCard.test.tsx +129 -0
  370. package/src/workflow/__tests__/WorkflowFileReviewList.test.tsx +129 -0
  371. package/src/workflow/__tests__/WorkflowTaskApprovalCard.test.tsx +43 -11
  372. package/src/workflow/__tests__/useWorkflowExecutionActions.test.tsx +276 -0
  373. package/src/workflow/execution-inspector/ExecutionInspector.tsx +42 -18
  374. package/src/workflow/index.ts +5 -0
  375. package/src/workflow/useWorkflowExecutionActions.ts +177 -29
  376. package/styles.css +1 -1
  377. package/version-history/DiffViewer.d.ts.map +1 -1
  378. package/version-history/DiffViewer.js +9 -3
  379. package/version-history/DiffViewer.js.map +1 -1
  380. package/version-history/UnifiedDiffView.d.ts +29 -0
  381. package/version-history/UnifiedDiffView.d.ts.map +1 -0
  382. package/version-history/UnifiedDiffView.js +36 -0
  383. package/version-history/UnifiedDiffView.js.map +1 -0
  384. package/version-history/computeDiff.d.ts +3 -1
  385. package/version-history/computeDiff.d.ts.map +1 -1
  386. package/version-history/computeDiff.js +5 -35
  387. package/version-history/computeDiff.js.map +1 -1
  388. package/version-history/diff-hunks.d.ts +21 -0
  389. package/version-history/diff-hunks.d.ts.map +1 -0
  390. package/version-history/diff-hunks.js +58 -0
  391. package/version-history/diff-hunks.js.map +1 -0
  392. package/version-history/index.d.ts +1 -0
  393. package/version-history/index.d.ts.map +1 -1
  394. package/version-history/index.js +1 -0
  395. package/version-history/index.js.map +1 -1
  396. package/version-history/parseUnifiedDiff.d.ts +22 -0
  397. package/version-history/parseUnifiedDiff.d.ts.map +1 -0
  398. package/version-history/parseUnifiedDiff.js +34 -0
  399. package/version-history/parseUnifiedDiff.js.map +1 -0
  400. package/workflow/WorkflowExecutionApprovalCard.d.ts +8 -1
  401. package/workflow/WorkflowExecutionApprovalCard.d.ts.map +1 -1
  402. package/workflow/WorkflowExecutionApprovalCard.js +17 -3
  403. package/workflow/WorkflowExecutionApprovalCard.js.map +1 -1
  404. package/workflow/WorkflowExecutionTimeline.d.ts +11 -2
  405. package/workflow/WorkflowExecutionTimeline.d.ts.map +1 -1
  406. package/workflow/WorkflowExecutionTimeline.js +2 -2
  407. package/workflow/WorkflowExecutionTimeline.js.map +1 -1
  408. package/workflow/WorkflowExecutionTimelineEvent.d.ts +4 -1
  409. package/workflow/WorkflowExecutionTimelineEvent.d.ts.map +1 -1
  410. package/workflow/WorkflowExecutionTimelineEvent.js +2 -2
  411. package/workflow/WorkflowExecutionTimelineEvent.js.map +1 -1
  412. package/workflow/WorkflowExecutionViewer.d.ts.map +1 -1
  413. package/workflow/WorkflowExecutionViewer.js +18 -10
  414. package/workflow/WorkflowExecutionViewer.js.map +1 -1
  415. package/workflow/WorkflowFileReviewList.d.ts +46 -0
  416. package/workflow/WorkflowFileReviewList.d.ts.map +1 -0
  417. package/workflow/WorkflowFileReviewList.js +48 -0
  418. package/workflow/WorkflowFileReviewList.js.map +1 -0
  419. package/workflow/WorkflowTaskApprovalCard.d.ts +8 -1
  420. package/workflow/WorkflowTaskApprovalCard.d.ts.map +1 -1
  421. package/workflow/WorkflowTaskApprovalCard.js +12 -19
  422. package/workflow/WorkflowTaskApprovalCard.js.map +1 -1
  423. package/workflow/execution-inspector/ExecutionInspector.d.ts +21 -4
  424. package/workflow/execution-inspector/ExecutionInspector.d.ts.map +1 -1
  425. package/workflow/execution-inspector/ExecutionInspector.js +6 -3
  426. package/workflow/execution-inspector/ExecutionInspector.js.map +1 -1
  427. package/workflow/index.d.ts +1 -0
  428. package/workflow/index.d.ts.map +1 -1
  429. package/workflow/index.js +1 -0
  430. package/workflow/index.js.map +1 -1
  431. package/workflow/useWorkflowExecutionActions.d.ts +62 -4
  432. package/workflow/useWorkflowExecutionActions.d.ts.map +1 -1
  433. package/workflow/useWorkflowExecutionActions.js +94 -18
  434. package/workflow/useWorkflowExecutionActions.js.map +1 -1
  435. package/session/inspector/PlanTab.d.ts +0 -15
  436. package/session/inspector/PlanTab.d.ts.map +0 -1
  437. package/session/inspector/PlanTab.js +0 -19
  438. package/session/inspector/PlanTab.js.map +0 -1
  439. package/src/session/inspector/PlanTab.tsx +0 -31
@@ -0,0 +1,72 @@
1
+ import { describe, it, expect, afterEach } from "vitest";
2
+ import { render, screen, cleanup } from "@testing-library/react";
3
+ import { TerminalSession } from "../TerminalSession";
4
+ import { SandboxContext } from "../SandboxContext";
5
+
6
+ afterEach(cleanup);
7
+
8
+ describe("TerminalSession", () => {
9
+ it("renders one session: the $ command prompt line then its output", () => {
10
+ const { container } = render(
11
+ <TerminalSession command="echo hello" stdout="hello world" exitCode={0} />,
12
+ );
13
+ const session = container.querySelector(
14
+ '[data-cursor-target="terminal-session"]',
15
+ );
16
+ expect(session).toBeTruthy();
17
+ expect(session!.textContent).toContain("$ echo hello");
18
+ expect(session!.textContent).toContain("hello world");
19
+ });
20
+
21
+ it("shows no exit badge on success", () => {
22
+ const { container } = render(
23
+ <TerminalSession command="true" stdout="" exitCode={0} />,
24
+ );
25
+ expect(container.textContent).not.toContain("exit 0");
26
+ });
27
+
28
+ it("shows an exit badge and an sr-only status on failure", () => {
29
+ render(<TerminalSession command="false" stdout="" exitCode={2} />);
30
+ // The visible badge.
31
+ expect(screen.getByText("exit 2")).toBeTruthy();
32
+ // The accessible status (screen-reader text).
33
+ expect(screen.getByText("Command exited with code 2")).toBeTruthy();
34
+ });
35
+
36
+ it("renders stderr in the destructive color", () => {
37
+ render(<TerminalSession command="oops" stderr="bad things" exitCode={1} />);
38
+ const stderr = screen.getByText("bad things");
39
+ expect(stderr.className).toContain("text-destructive");
40
+ });
41
+
42
+ it("preserves a multi-line (heredoc) command in full", () => {
43
+ const command = "cat > f << EOF\nline one\nline two\nEOF";
44
+ const { container } = render(<TerminalSession command={command} />);
45
+ expect(container.textContent).toContain("line one");
46
+ expect(container.textContent).toContain("line two");
47
+ // A long command must NOT grow its own truncation toggle (only output does).
48
+ expect(screen.queryByText(/Show all \d+ lines/)).toBeNull();
49
+ });
50
+
51
+ it("supports a command-only render (the pre-execution gate)", () => {
52
+ const { container } = render(<TerminalSession command="npm test" />);
53
+ expect(container.textContent).toContain("$ npm test");
54
+ // No output, no failure badge.
55
+ expect(container.textContent).not.toMatch(/exit \d+/);
56
+ });
57
+
58
+ it("normalizes absolute sandbox paths to workspace-relative for display", () => {
59
+ const { container } = render(
60
+ <SandboxContext.Provider value={{ sandboxWorkspaceRoot: "/home/daytona/workspace" }}>
61
+ <TerminalSession
62
+ command="ls /home/daytona/workspace/src"
63
+ stdout="/home/daytona/workspace/src/a.ts"
64
+ />
65
+ </SandboxContext.Provider>,
66
+ );
67
+ // The absolute root is stripped; the relative remainder is shown.
68
+ expect(container.textContent).not.toContain("/home/daytona/workspace/");
69
+ expect(container.textContent).toContain("$ ls src");
70
+ expect(container.textContent).toContain("src/a.ts");
71
+ });
72
+ });
@@ -0,0 +1,102 @@
1
+ import { describe, it, expect, afterEach } from "vitest";
2
+ import { render, fireEvent, cleanup } from "@testing-library/react";
3
+ import { create } from "@bufbuild/protobuf";
4
+ import {
5
+ TodoItemSchema,
6
+ type TodoItem,
7
+ } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/todo_pb";
8
+ import { TodoStatus } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
9
+ import { TodoCard, todoCardPropsEqual } from "../TodoCard";
10
+
11
+ afterEach(cleanup);
12
+
13
+ function todo(id: string, content: string, status: TodoStatus): TodoItem {
14
+ return create(TodoItemSchema, { id, content, status });
15
+ }
16
+
17
+ function map(items: TodoItem[]): { [id: string]: TodoItem } {
18
+ const m: { [id: string]: TodoItem } = {};
19
+ for (const t of items) m[t.id] = t;
20
+ return m;
21
+ }
22
+
23
+ const ACTIVE = map([
24
+ todo("t1", "Design the card", TodoStatus.TODO_COMPLETED),
25
+ todo("t2", "Wire the thread", TodoStatus.TODO_IN_PROGRESS),
26
+ todo("t3", "Write tests", TodoStatus.TODO_PENDING),
27
+ ]);
28
+
29
+ const DONE = map([
30
+ todo("t1", "Design the card", TodoStatus.TODO_COMPLETED),
31
+ todo("t2", "Wire the thread", TodoStatus.TODO_COMPLETED),
32
+ ]);
33
+
34
+ describe("TodoCard", () => {
35
+ it("renders an 'Agent to-dos' region with the To-dos header and progress", () => {
36
+ const { container } = render(<TodoCard todos={ACTIVE} />);
37
+
38
+ const region = container.querySelector("[role='region']") as HTMLElement;
39
+ expect(region).toBeTruthy();
40
+ expect(region.getAttribute("aria-label")).toBe("Agent to-dos");
41
+ expect(region.textContent).toContain("To-dos");
42
+ // 1 of 3 completed.
43
+ expect(region.textContent).toContain("1/3 completed");
44
+ });
45
+
46
+ it("renders nothing when there are no todos", () => {
47
+ const { container } = render(<TodoCard todos={{}} />);
48
+ expect(container.firstChild).toBeNull();
49
+ });
50
+
51
+ it("is expanded while the plan is active (an item is in progress)", () => {
52
+ const { container } = render(<TodoCard todos={ACTIVE} />);
53
+
54
+ const button = container.querySelector("button")!;
55
+ expect(button.getAttribute("aria-expanded")).toBe("true");
56
+ // The task list is visible.
57
+ expect(container.querySelector("[role='list']")).toBeTruthy();
58
+ expect(container.textContent).toContain("Wire the thread");
59
+ });
60
+
61
+ it("is collapsed by default once the plan is fully resolved (history)", () => {
62
+ const { container } = render(<TodoCard todos={DONE} />);
63
+
64
+ const button = container.querySelector("button")!;
65
+ expect(button.getAttribute("aria-expanded")).toBe("false");
66
+ // The list is not rendered while collapsed.
67
+ expect(container.querySelector("[role='list']")).toBeNull();
68
+ });
69
+
70
+ it("toggles the list open/closed on header click", () => {
71
+ const { container } = render(<TodoCard todos={DONE} />);
72
+ const button = container.querySelector("button")!;
73
+
74
+ expect(container.querySelector("[role='list']")).toBeNull();
75
+ fireEvent.click(button);
76
+ expect(button.getAttribute("aria-expanded")).toBe("true");
77
+ expect(container.querySelector("[role='list']")).toBeTruthy();
78
+
79
+ fireEvent.click(button);
80
+ expect(button.getAttribute("aria-expanded")).toBe("false");
81
+ expect(container.querySelector("[role='list']")).toBeNull();
82
+ });
83
+ });
84
+
85
+ describe("todoCardPropsEqual (React.memo comparator)", () => {
86
+ it("treats the same todos reference as equal (skips re-render)", () => {
87
+ expect(todoCardPropsEqual({ todos: ACTIVE }, { todos: ACTIVE })).toBe(true);
88
+ });
89
+
90
+ it("treats a different todos reference as not equal (re-renders)", () => {
91
+ expect(todoCardPropsEqual({ todos: ACTIVE }, { todos: DONE })).toBe(false);
92
+ });
93
+
94
+ it("treats a className change as not equal", () => {
95
+ expect(
96
+ todoCardPropsEqual(
97
+ { todos: ACTIVE, className: "a" },
98
+ { todos: ACTIVE, className: "b" },
99
+ ),
100
+ ).toBe(false);
101
+ });
102
+ });
@@ -0,0 +1,48 @@
1
+ import { describe, it, expect, afterEach } from "vitest";
2
+ import { render, screen, cleanup } from "@testing-library/react";
3
+ import { ToolArgsView } from "../ToolArgsView";
4
+
5
+ afterEach(cleanup);
6
+
7
+ // The file-tool args view captions write/edit content with a path. When an
8
+ // ancestor already names the file (the approval gate header), showFileName=false
9
+ // drops that redundant path while keeping the decision-relevant content.
10
+ describe("ToolArgsView file tools", () => {
11
+ it("shows the path and content by default for a write", () => {
12
+ render(
13
+ <ToolArgsView
14
+ toolName="write_file"
15
+ args={{ path: "src/a.ts", contents: "hi" }}
16
+ />,
17
+ );
18
+ expect(screen.getByText("a.ts")).toBeTruthy();
19
+ expect(screen.getByText("Content")).toBeTruthy();
20
+ });
21
+
22
+ it("suppresses the redundant path but keeps the content when showFileName is false", () => {
23
+ render(
24
+ <ToolArgsView
25
+ toolName="write_file"
26
+ args={{ path: "src/a.ts", contents: "hi" }}
27
+ showFileName={false}
28
+ />,
29
+ );
30
+ expect(screen.queryByText("a.ts")).toBeNull();
31
+ expect(screen.getByText("Content")).toBeTruthy();
32
+ });
33
+ });
34
+
35
+ // The pre-execution (approval gate) shell view shows the command in the same
36
+ // terminal-session chrome the completed call uses — there is no output yet.
37
+ describe("ToolArgsView shell", () => {
38
+ it("renders the command as a terminal session prompt line", () => {
39
+ const { container } = render(
40
+ <ToolArgsView toolName="shell" args={{ command: "ls -la" }} />,
41
+ );
42
+ const session = container.querySelector(
43
+ '[data-cursor-target="terminal-session"]',
44
+ );
45
+ expect(session).toBeTruthy();
46
+ expect(session!.textContent).toContain("$ ls -la");
47
+ });
48
+ });
@@ -0,0 +1,195 @@
1
+ import { describe, it, expect, afterEach } from "vitest";
2
+ import { render, cleanup } from "@testing-library/react";
3
+ import { create, type JsonObject } from "@bufbuild/protobuf";
4
+ import {
5
+ ToolCallSchema,
6
+ type ToolCall,
7
+ } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/message_pb";
8
+ import {
9
+ ApprovalPolicySource,
10
+ ToolKind,
11
+ } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
12
+ import { ToolCallDetail } from "../ToolCallDetail";
13
+
14
+ afterEach(cleanup);
15
+
16
+ function makeToolCall(opts: {
17
+ name: string;
18
+ toolKind?: ToolKind;
19
+ args?: Record<string, unknown>;
20
+ result?: string;
21
+ approvalPolicySource?: ApprovalPolicySource;
22
+ startedAt?: string;
23
+ completedAt?: string;
24
+ }): ToolCall {
25
+ return create(ToolCallSchema, {
26
+ id: opts.name,
27
+ name: opts.name,
28
+ toolKind: opts.toolKind ?? ToolKind.UNSPECIFIED,
29
+ args: (opts.args ?? {}) as JsonObject,
30
+ result: opts.result ?? "",
31
+ approvalPolicySource:
32
+ opts.approvalPolicySource ?? ApprovalPolicySource.UNSPECIFIED,
33
+ startedAt: opts.startedAt ?? "",
34
+ completedAt: opts.completedAt ?? "",
35
+ });
36
+ }
37
+
38
+ describe("ToolCallDetail — metadata invariant (header owns it, body shows content)", () => {
39
+ it("does not render the duration in the body (the owning row header shows it)", () => {
40
+ // A 5s run would format as "5.0s" if the body still printed duration.
41
+ const { container } = render(
42
+ <ToolCallDetail
43
+ toolCall={makeToolCall({
44
+ name: "delete_file",
45
+ toolKind: ToolKind.FILE_DELETE,
46
+ args: { path: "/tmp/x.ts" },
47
+ startedAt: "2026-01-01T00:00:00.000Z",
48
+ completedAt: "2026-01-01T00:00:05.000Z",
49
+ })}
50
+ />,
51
+ );
52
+ expect(container.textContent).not.toContain("5.0s");
53
+ });
54
+
55
+ it("suppresses the everyday default provenance (built-in tool policy is noise)", () => {
56
+ const { queryByText } = render(
57
+ <ToolCallDetail
58
+ toolCall={makeToolCall({
59
+ name: "delete_file",
60
+ toolKind: ToolKind.FILE_DELETE,
61
+ args: { path: "/tmp/x.ts" },
62
+ approvalPolicySource: ApprovalPolicySource.BUILTIN_CATEGORY,
63
+ })}
64
+ />,
65
+ );
66
+ expect(queryByText(/required by/)).toBeNull();
67
+ });
68
+
69
+ it("suppresses provenance entirely for a legacy/ungated UNSPECIFIED call", () => {
70
+ const { queryByText } = render(
71
+ <ToolCallDetail
72
+ toolCall={makeToolCall({
73
+ name: "delete_file",
74
+ toolKind: ToolKind.FILE_DELETE,
75
+ args: { path: "/tmp/x.ts" },
76
+ approvalPolicySource: ApprovalPolicySource.UNSPECIFIED,
77
+ })}
78
+ />,
79
+ );
80
+ expect(queryByText(/required by/)).toBeNull();
81
+ expect(queryByText(/auto-approved/)).toBeNull();
82
+ });
83
+
84
+ it("surfaces a genuinely informative provenance (a run lease that cleared the call)", () => {
85
+ const { getByText } = render(
86
+ <ToolCallDetail
87
+ toolCall={makeToolCall({
88
+ name: "delete_file",
89
+ toolKind: ToolKind.FILE_DELETE,
90
+ args: { path: "/tmp/x.ts" },
91
+ approvalPolicySource: ApprovalPolicySource.APPROVAL_LEASE,
92
+ })}
93
+ />,
94
+ );
95
+ expect(getByText("auto-approved by a run lease")).toBeTruthy();
96
+ });
97
+ });
98
+
99
+ describe("ToolCallDetail — search body de-duplicates the query", () => {
100
+ it("does not restate the query (no 'Pattern:' row) when the header shows it in full", () => {
101
+ const { container } = render(
102
+ <ToolCallDetail
103
+ toolCall={makeToolCall({
104
+ name: "Grep",
105
+ toolKind: ToolKind.SEARCH,
106
+ args: { pattern: "pipeline" },
107
+ result:
108
+ '{"workspaceResults":{"/work/demo":{"type":"files","output":{"files":[],"count":0}}}}',
109
+ })}
110
+ // Header did not truncate the short query.
111
+ primaryArgTruncated={false}
112
+ />,
113
+ );
114
+ // The legacy duplicate label is gone, and the short query is not repeated.
115
+ expect(container.textContent).not.toContain("Pattern:");
116
+ expect(container.textContent).not.toContain("pipeline");
117
+ // The result still renders its (empty) state honestly.
118
+ expect(container.textContent).toContain("No files found");
119
+ });
120
+
121
+ it("unwraps the real {status,value:{workspaceResults}} grep result instead of dumping JSON", () => {
122
+ const { container } = render(
123
+ <ToolCallDetail
124
+ toolCall={makeToolCall({
125
+ name: "Grep",
126
+ toolKind: ToolKind.SEARCH,
127
+ args: { pattern: "pipeline" },
128
+ result:
129
+ '{"status":"success","value":{"workspaceResults":{"/work/demo":{"type":"files","output":{"files":[],"count":0}}}}}',
130
+ })}
131
+ />,
132
+ );
133
+ // The body renders the empty state, never the raw envelope keys.
134
+ expect(container.textContent).toContain("No files found");
135
+ expect(container.textContent).not.toContain("workspaceResults");
136
+ expect(container.textContent).not.toContain("status");
137
+ });
138
+
139
+ it("does not restate the result count in the body (the row header shows it)", () => {
140
+ const { container } = render(
141
+ <ToolCallDetail
142
+ toolCall={makeToolCall({
143
+ name: "Glob",
144
+ toolKind: ToolKind.SEARCH,
145
+ args: { pattern: "Dockerfile" },
146
+ result:
147
+ '{"status":"success","value":{"files":["Dockerfile"],"totalFiles":1,"clientTruncated":false,"ripgrepTruncated":false}}',
148
+ })}
149
+ />,
150
+ );
151
+ // The matched file renders, but the redundant "1 file" count does not.
152
+ expect(container.textContent).toContain("Dockerfile");
153
+ expect(container.textContent).not.toContain("1 file");
154
+ });
155
+
156
+ it("restates the full query (labeled 'Query') only when the header truncated it", () => {
157
+ const longQuery =
158
+ "a-very-long-search-query-that-would-not-fit-in-the-header-subtitle";
159
+ const { container } = render(
160
+ <ToolCallDetail
161
+ toolCall={makeToolCall({
162
+ name: "Grep",
163
+ toolKind: ToolKind.SEARCH,
164
+ args: { pattern: longQuery },
165
+ result:
166
+ '{"workspaceResults":{"/work/demo":{"type":"files","output":{"files":[],"count":0}}}}',
167
+ })}
168
+ primaryArgTruncated
169
+ />,
170
+ );
171
+ expect(container.textContent).toContain("Query");
172
+ expect(container.textContent).toContain(longQuery);
173
+ });
174
+ });
175
+
176
+ describe("ToolCallDetail — edit body has no duplicate stats", () => {
177
+ it("renders the edit diff as a table without restating the +N -M counts", () => {
178
+ const { container } = render(
179
+ <ToolCallDetail
180
+ toolCall={makeToolCall({
181
+ name: "str_replace",
182
+ toolKind: ToolKind.FILE_EDIT,
183
+ args: { path: "src/x.ts" },
184
+ result:
185
+ '{"status":"success","value":{"linesAdded":1,"linesRemoved":0,"diffString":"@@ -0,0 +1,1 @@\\n+new line"}}',
186
+ })}
187
+ />,
188
+ );
189
+ // The diff content renders through the table...
190
+ expect(container.querySelector("table")).not.toBeNull();
191
+ expect(container.textContent).toContain("new line");
192
+ // ...but the body does not repeat the +N -M the row header already shows.
193
+ expect(container.textContent).not.toContain("+1");
194
+ });
195
+ });
@@ -0,0 +1,132 @@
1
+ import { describe, it, expect, afterEach } from "vitest";
2
+ import { render, screen, cleanup } from "@testing-library/react";
3
+ import { create, type JsonObject } from "@bufbuild/protobuf";
4
+ import {
5
+ ToolCallSchema,
6
+ type ToolCall,
7
+ } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/message_pb";
8
+ import { ToolCallStatus } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
9
+ import { ToolCallGroup } from "../ToolCallGroup";
10
+
11
+ afterEach(cleanup);
12
+
13
+ function makeToolCall(name: string, id: string, args: Record<string, unknown>, result = ""): ToolCall {
14
+ return create(ToolCallSchema, {
15
+ id,
16
+ name,
17
+ args: args as JsonObject,
18
+ result,
19
+ status: ToolCallStatus.TOOL_CALL_COMPLETED,
20
+ });
21
+ }
22
+
23
+ describe("ToolCallGroup timeline", () => {
24
+ it("keeps a completed turn's high-signal rows visible (no per-turn collapse)", () => {
25
+ render(
26
+ <ToolCallGroup
27
+ toolCalls={[
28
+ makeToolCall("Shell", "s1", { command: "echo hi" }, "hi"),
29
+ makeToolCall("StrReplace", "e1", { path: "/a.ts", old_string: "a", new_string: "b" }),
30
+ ]}
31
+ />,
32
+ );
33
+
34
+ // Both rows persist after the turn settles — nothing hidden behind a pill.
35
+ expect(screen.getByText("echo hi")).toBeTruthy();
36
+ // The edit row shows its file (filename-first) in the row header and its
37
+ // bounded diff preview below.
38
+ expect(screen.getAllByText("a.ts").length).toBeGreaterThanOrEqual(1);
39
+ // There is no aggregate "Ran N tools" trigger any more.
40
+ expect(screen.queryByText(/Ran \d+ tool/)).toBeNull();
41
+ });
42
+
43
+ it("folds a run of consecutive reads into one chip while leaving other rows", () => {
44
+ render(
45
+ <ToolCallGroup
46
+ toolCalls={[
47
+ makeToolCall("Shell", "s1", { command: "echo hi" }, "hi"),
48
+ makeToolCall("Read", "r1", { path: "/a.ts" }),
49
+ makeToolCall("Read", "r2", { path: "/b.ts" }),
50
+ makeToolCall("Read", "r3", { path: "/c.ts" }),
51
+ ]}
52
+ />,
53
+ );
54
+
55
+ // The shell stays a row; the three reads collapse into a single chip.
56
+ expect(screen.getByText("echo hi")).toBeTruthy();
57
+ expect(screen.getByText("Read 3 files")).toBeTruthy();
58
+ // Folded read paths are hidden until the chip is expanded.
59
+ expect(screen.queryByText("/a.ts")).toBeNull();
60
+ });
61
+
62
+ it("renders a lone read as its own row, not a chip", () => {
63
+ render(
64
+ <ToolCallGroup toolCalls={[makeToolCall("Read", "r1", { path: "/only.ts" })]} />,
65
+ );
66
+
67
+ expect(screen.getByText("only.ts")).toBeTruthy();
68
+ expect(screen.queryByText(/Read \d+ files/)).toBeNull();
69
+ });
70
+
71
+ it("hides a runner-collapsed duplicate twin (blanked SKIPPED) so one resource renders one card", () => {
72
+ // The runner overlays the first same-resource denial as the gate and collapses
73
+ // the redundant twin in place to a content-less SKIPPED row (it cannot drop the
74
+ // committed id). That blanked twin must not render a second card.
75
+ const gate = makeToolCall("edit", "stream-1", { path: "/notes.md", old_string: "a", new_string: "b" });
76
+ gate.status = ToolCallStatus.TOOL_CALL_WAITING_APPROVAL;
77
+ gate.requiresApproval = true;
78
+
79
+ const collapsedTwin = create(ToolCallSchema, {
80
+ id: "stream-2",
81
+ name: "edit",
82
+ // Blanked exactly as collapseDenialTwin leaves it: SKIPPED, no approval, no
83
+ // result/error/argsPreview/file_changes.
84
+ status: ToolCallStatus.TOOL_CALL_SKIPPED,
85
+ requiresApproval: false,
86
+ });
87
+
88
+ const { container } = render(<ToolCallGroup toolCalls={[gate, collapsedTwin]} />);
89
+
90
+ // Exactly one row — the gate; the collapsed twin is not drawn.
91
+ const rows = container.querySelectorAll('[data-cursor-target="tool-call-row"]');
92
+ expect(rows.length).toBe(1);
93
+ expect(container.querySelector('[data-cursor-target="tool-call-group"]')).toBeTruthy();
94
+ });
95
+
96
+ it("renders nothing when every tool call in the group is a collapsed twin", () => {
97
+ const collapsedTwin = create(ToolCallSchema, {
98
+ id: "stream-2",
99
+ name: "edit",
100
+ status: ToolCallStatus.TOOL_CALL_SKIPPED,
101
+ requiresApproval: false,
102
+ });
103
+
104
+ const { container } = render(<ToolCallGroup toolCalls={[collapsedTwin]} />);
105
+ expect(container.querySelector('[data-cursor-target="tool-call-group"]')).toBeNull();
106
+ });
107
+
108
+ it("stacks each tool call as its own bordered card with gaps (no left rail)", () => {
109
+ const { container } = render(
110
+ <ToolCallGroup
111
+ toolCalls={[
112
+ makeToolCall("Shell", "s1", { command: "echo hi" }, "hi"),
113
+ makeToolCall("StrReplace", "e1", { path: "/a.ts", old_string: "a", new_string: "b" }),
114
+ ]}
115
+ />,
116
+ );
117
+
118
+ const group = container.querySelector('[data-cursor-target="tool-call-group"]')!;
119
+ // Cards stacked with a gap — the old left rail is gone.
120
+ expect(group.className).toContain("gap-2");
121
+ expect(group.className).not.toContain("border-l-2");
122
+
123
+ const rows = container.querySelectorAll('[data-cursor-target="tool-call-row"]');
124
+ expect(rows.length).toBe(2);
125
+ for (const row of rows) {
126
+ expect(row.className).toContain("rounded-lg");
127
+ // Class presence only — actual rendering is guarded by the layer-invariant
128
+ // + e2e computed-style tests (happy-dom does not resolve `@layer`).
129
+ expect(row.className).toContain("border-border-prominent");
130
+ }
131
+ });
132
+ });