@stigmer/react 3.0.9-dev.20260616060535 → 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 (426) 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/useAutoDisclosure.d.ts +34 -0
  178. package/internal/useAutoDisclosure.d.ts.map +1 -0
  179. package/internal/useAutoDisclosure.js +53 -0
  180. package/internal/useAutoDisclosure.js.map +1 -0
  181. package/internal/useElapsedSince.d.ts +30 -0
  182. package/internal/useElapsedSince.d.ts.map +1 -0
  183. package/internal/useElapsedSince.js +70 -0
  184. package/internal/useElapsedSince.js.map +1 -0
  185. package/internal/useIsOverflowing.d.ts +30 -0
  186. package/internal/useIsOverflowing.d.ts.map +1 -0
  187. package/internal/useIsOverflowing.js +47 -0
  188. package/internal/useIsOverflowing.js.map +1 -0
  189. package/internal/useIsTextTruncated.d.ts +24 -0
  190. package/internal/useIsTextTruncated.d.ts.map +1 -0
  191. package/internal/useIsTextTruncated.js +41 -0
  192. package/internal/useIsTextTruncated.js.map +1 -0
  193. package/internal/useKeyedSubmission.d.ts +49 -0
  194. package/internal/useKeyedSubmission.d.ts.map +1 -0
  195. package/internal/useKeyedSubmission.js +67 -0
  196. package/internal/useKeyedSubmission.js.map +1 -0
  197. package/package.json +4 -4
  198. package/session/SessionViewer.d.ts +2 -2
  199. package/session/SessionViewer.d.ts.map +1 -1
  200. package/session/SessionViewer.js +10 -4
  201. package/session/SessionViewer.js.map +1 -1
  202. package/session/index.d.ts +2 -0
  203. package/session/index.d.ts.map +1 -1
  204. package/session/index.js +1 -0
  205. package/session/index.js.map +1 -1
  206. package/session/inspector/ChangesTab.d.ts +8 -5
  207. package/session/inspector/ChangesTab.d.ts.map +1 -1
  208. package/session/inspector/ChangesTab.js +11 -8
  209. package/session/inspector/ChangesTab.js.map +1 -1
  210. package/session/inspector/SessionInspector.d.ts +4 -4
  211. package/session/inspector/SessionInspector.d.ts.map +1 -1
  212. package/session/inspector/SessionInspector.js +4 -4
  213. package/session/inspector/SessionInspector.js.map +1 -1
  214. package/session/inspector/index.d.ts +0 -2
  215. package/session/inspector/index.d.ts.map +1 -1
  216. package/session/inspector/index.js +0 -1
  217. package/session/inspector/index.js.map +1 -1
  218. package/session/inspector/useSessionInspector.d.ts +8 -4
  219. package/session/inspector/useSessionInspector.d.ts.map +1 -1
  220. package/session/inspector/useSessionInspector.js +27 -20
  221. package/session/inspector/useSessionInspector.js.map +1 -1
  222. package/session/useSessionConversation.d.ts +37 -3
  223. package/session/useSessionConversation.d.ts.map +1 -1
  224. package/session/useSessionConversation.js +18 -2
  225. package/session/useSessionConversation.js.map +1 -1
  226. package/session/useSessionFileChanges.d.ts +74 -0
  227. package/session/useSessionFileChanges.d.ts.map +1 -0
  228. package/session/useSessionFileChanges.js +178 -0
  229. package/session/useSessionFileChanges.js.map +1 -0
  230. package/src/__tests__/styles-border-layer-invariant.test.ts +105 -0
  231. package/src/execution/ApprovalCard.tsx +368 -230
  232. package/src/execution/ApprovalContext.ts +109 -0
  233. package/src/execution/ArtifactCard.tsx +10 -6
  234. package/src/execution/ArtifactPreviewModal.tsx +12 -6
  235. package/src/execution/EmptyChangeNotice.tsx +62 -0
  236. package/src/execution/FileChangesView.tsx +383 -0
  237. package/src/execution/FilePathLink.tsx +65 -12
  238. package/src/execution/FileReviewCard.tsx +1118 -0
  239. package/src/execution/FileReviewContext.ts +53 -0
  240. package/src/execution/FileReviewDock.tsx +157 -0
  241. package/src/execution/McpToolDetail.tsx +98 -32
  242. package/src/execution/MessageThread.tsx +374 -9
  243. package/src/execution/PlanArtifactCard.tsx +9 -6
  244. package/src/execution/PlanCompletionCard.tsx +1 -1
  245. package/src/execution/ResultView.tsx +333 -162
  246. package/src/execution/SubAgentSection.tsx +58 -11
  247. package/src/execution/TerminalSession.tsx +91 -0
  248. package/src/execution/TodoCard.tsx +186 -0
  249. package/src/execution/ToolArgsView.tsx +28 -17
  250. package/src/execution/ToolCallDetail.tsx +137 -26
  251. package/src/execution/ToolCallGroup.tsx +68 -296
  252. package/src/execution/ToolCallItem.tsx +265 -67
  253. package/src/execution/ToolRunGroup.tsx +302 -0
  254. package/src/execution/__tests__/ApprovalCard.test.tsx +395 -5
  255. package/src/execution/__tests__/ArtifactPreviewModal.test.tsx +0 -1
  256. package/src/execution/__tests__/EmptyChangeNotice.test.tsx +24 -0
  257. package/src/execution/__tests__/FileChangesView.test.tsx +296 -0
  258. package/src/execution/__tests__/FilePathLink.test.tsx +92 -0
  259. package/src/execution/__tests__/FileReviewCard.test.tsx +1044 -0
  260. package/src/execution/__tests__/FileReviewDock.test.tsx +134 -0
  261. package/src/execution/__tests__/McpToolDetail.test.tsx +79 -0
  262. package/src/execution/__tests__/MessageThread.test.tsx +324 -0
  263. package/src/execution/__tests__/PlanArtifactCard.test.tsx +37 -16
  264. package/src/execution/__tests__/ResultView.test.tsx +280 -2
  265. package/src/execution/__tests__/SubAgentSection.test.tsx +180 -0
  266. package/src/execution/__tests__/TerminalSession.test.tsx +72 -0
  267. package/src/execution/__tests__/TodoCard.test.tsx +102 -0
  268. package/src/execution/__tests__/ToolArgsView.test.tsx +48 -0
  269. package/src/execution/__tests__/ToolCallDetail.test.tsx +195 -0
  270. package/src/execution/__tests__/ToolCallGroup.test.tsx +132 -0
  271. package/src/execution/__tests__/ToolCallItem.test.tsx +521 -0
  272. package/src/execution/__tests__/ToolRunGroup.test.tsx +137 -0
  273. package/src/execution/__tests__/blanked-narration.test.tsx +149 -0
  274. package/src/execution/__tests__/buildThreadItems-approvals.test.ts +304 -0
  275. package/src/execution/__tests__/buildThreadItems-file-review.test.ts +331 -0
  276. package/src/execution/__tests__/file-path-resolver.test.ts +44 -0
  277. package/src/execution/__tests__/file-review-status.test.ts +343 -0
  278. package/src/execution/__tests__/segment-tool-calls.test.ts +124 -0
  279. package/src/execution/__tests__/subagent-file-review-badge.test.tsx +273 -0
  280. package/src/execution/__tests__/thread-keys.test.ts +195 -0
  281. package/src/execution/__tests__/tool-presenter.test.tsx +95 -0
  282. package/src/execution/__tests__/useArtifactDownload.test.tsx +67 -0
  283. package/src/execution/__tests__/useArtifactDownloadUrl.test.tsx +51 -0
  284. package/src/execution/__tests__/useFileChangeContent.test.tsx +240 -0
  285. package/src/execution/__tests__/useFileReview.test.tsx +236 -0
  286. package/src/execution/__tests__/useSubmitApproval.test.tsx +165 -0
  287. package/src/execution/__tests__/useToolOutputContent.test.tsx +82 -0
  288. package/src/execution/approval-provenance.ts +8 -0
  289. package/src/execution/artifact-utils.ts +0 -18
  290. package/src/execution/execution-phases.ts +4 -16
  291. package/src/execution/file-path-resolver.ts +38 -0
  292. package/src/execution/file-review-status.ts +219 -0
  293. package/src/execution/index.ts +84 -4
  294. package/src/execution/segment-tool-calls.ts +101 -0
  295. package/src/execution/tool-categories.ts +128 -1
  296. package/src/execution/tool-presenter.ts +58 -3
  297. package/src/execution/tool-rendering-primitives.tsx +11 -14
  298. package/src/execution/useArtifactDownload.ts +109 -0
  299. package/src/execution/useArtifactDownloadUrl.ts +116 -0
  300. package/src/execution/useFileChangeContent.ts +149 -0
  301. package/src/execution/useFileReview.ts +197 -0
  302. package/src/execution/useSubmitApproval.ts +49 -27
  303. package/src/execution/useToolOutputContent.ts +70 -0
  304. package/src/index.ts +60 -1
  305. package/src/internal/ApprovalPeekBar.tsx +73 -0
  306. package/src/internal/BoundedContent.tsx +97 -0
  307. package/src/internal/DecisionButton.tsx +127 -0
  308. package/src/internal/InCardDecisionError.tsx +47 -0
  309. package/src/internal/RevealToggle.tsx +91 -0
  310. package/src/internal/VirtualizedThread.tsx +28 -2
  311. package/src/internal/__tests__/ApprovalPeekBar.test.tsx +34 -0
  312. package/src/internal/__tests__/BoundedContent.test.tsx +92 -0
  313. package/src/internal/__tests__/DecisionButton.test.tsx +107 -0
  314. package/src/internal/__tests__/RevealToggle.test.tsx +61 -0
  315. package/src/internal/__tests__/useAutoDisclosure.test.ts +74 -0
  316. package/src/internal/__tests__/useElapsedSince.test.tsx +68 -0
  317. package/src/internal/__tests__/useIsOverflowing.test.ts +36 -0
  318. package/src/internal/__tests__/useKeyedSubmission.test.tsx +126 -0
  319. package/src/internal/useAutoDisclosure.ts +58 -0
  320. package/src/internal/useElapsedSince.ts +72 -0
  321. package/src/internal/useIsOverflowing.ts +61 -0
  322. package/src/internal/useIsTextTruncated.ts +55 -0
  323. package/src/internal/useKeyedSubmission.ts +101 -0
  324. package/src/session/SessionViewer.tsx +21 -4
  325. package/src/session/__tests__/SessionViewer-fileReviewDock.test.tsx +198 -0
  326. package/src/session/__tests__/audienceWiring.test.tsx +4 -0
  327. package/src/session/__tests__/useSessionFileChanges.test.ts +303 -0
  328. package/src/session/index.ts +3 -0
  329. package/src/session/inspector/ChangesTab.tsx +20 -17
  330. package/src/session/inspector/SessionInspector.tsx +12 -10
  331. package/src/session/inspector/__tests__/ArtifactsTab.test.tsx +0 -1
  332. package/src/session/inspector/__tests__/ChangesTab.test.tsx +38 -0
  333. package/src/session/inspector/__tests__/useSessionInspector.test.ts +46 -61
  334. package/src/session/inspector/index.ts +0 -3
  335. package/src/session/inspector/useSessionInspector.ts +27 -20
  336. package/src/session/useSessionConversation.ts +80 -3
  337. package/src/session/useSessionFileChanges.ts +207 -0
  338. package/src/styles.css +35 -10
  339. package/src/version-history/DiffViewer.tsx +10 -7
  340. package/src/version-history/UnifiedDiffView.tsx +66 -0
  341. package/src/version-history/__tests__/DiffViewer.test.tsx +57 -0
  342. package/src/version-history/__tests__/UnifiedDiffView.test.tsx +50 -0
  343. package/src/version-history/__tests__/computeDiff.test.ts +35 -0
  344. package/src/version-history/__tests__/parseUnifiedDiff.test.ts +82 -0
  345. package/src/version-history/computeDiff.ts +6 -37
  346. package/src/version-history/diff-hunks.ts +63 -0
  347. package/src/version-history/index.ts +1 -0
  348. package/src/version-history/parseUnifiedDiff.ts +34 -0
  349. package/src/workflow/WorkflowExecutionApprovalCard.tsx +49 -24
  350. package/src/workflow/WorkflowExecutionTimeline.tsx +15 -4
  351. package/src/workflow/WorkflowExecutionTimelineEvent.tsx +8 -3
  352. package/src/workflow/WorkflowExecutionViewer.tsx +72 -28
  353. package/src/workflow/WorkflowFileReviewList.tsx +152 -0
  354. package/src/workflow/WorkflowTaskApprovalCard.tsx +37 -85
  355. package/src/workflow/__tests__/ExecutionInspector.test.tsx +30 -2
  356. package/src/workflow/__tests__/WorkflowExecutionApprovalCard.test.tsx +129 -0
  357. package/src/workflow/__tests__/WorkflowFileReviewList.test.tsx +129 -0
  358. package/src/workflow/__tests__/WorkflowTaskApprovalCard.test.tsx +43 -11
  359. package/src/workflow/__tests__/useWorkflowExecutionActions.test.tsx +276 -0
  360. package/src/workflow/execution-inspector/ExecutionInspector.tsx +42 -18
  361. package/src/workflow/index.ts +5 -0
  362. package/src/workflow/useWorkflowExecutionActions.ts +177 -29
  363. package/styles.css +1 -1
  364. package/version-history/DiffViewer.d.ts.map +1 -1
  365. package/version-history/DiffViewer.js +9 -3
  366. package/version-history/DiffViewer.js.map +1 -1
  367. package/version-history/UnifiedDiffView.d.ts +29 -0
  368. package/version-history/UnifiedDiffView.d.ts.map +1 -0
  369. package/version-history/UnifiedDiffView.js +36 -0
  370. package/version-history/UnifiedDiffView.js.map +1 -0
  371. package/version-history/computeDiff.d.ts +3 -1
  372. package/version-history/computeDiff.d.ts.map +1 -1
  373. package/version-history/computeDiff.js +5 -35
  374. package/version-history/computeDiff.js.map +1 -1
  375. package/version-history/diff-hunks.d.ts +21 -0
  376. package/version-history/diff-hunks.d.ts.map +1 -0
  377. package/version-history/diff-hunks.js +58 -0
  378. package/version-history/diff-hunks.js.map +1 -0
  379. package/version-history/index.d.ts +1 -0
  380. package/version-history/index.d.ts.map +1 -1
  381. package/version-history/index.js +1 -0
  382. package/version-history/index.js.map +1 -1
  383. package/version-history/parseUnifiedDiff.d.ts +22 -0
  384. package/version-history/parseUnifiedDiff.d.ts.map +1 -0
  385. package/version-history/parseUnifiedDiff.js +34 -0
  386. package/version-history/parseUnifiedDiff.js.map +1 -0
  387. package/workflow/WorkflowExecutionApprovalCard.d.ts +8 -1
  388. package/workflow/WorkflowExecutionApprovalCard.d.ts.map +1 -1
  389. package/workflow/WorkflowExecutionApprovalCard.js +17 -3
  390. package/workflow/WorkflowExecutionApprovalCard.js.map +1 -1
  391. package/workflow/WorkflowExecutionTimeline.d.ts +11 -2
  392. package/workflow/WorkflowExecutionTimeline.d.ts.map +1 -1
  393. package/workflow/WorkflowExecutionTimeline.js +2 -2
  394. package/workflow/WorkflowExecutionTimeline.js.map +1 -1
  395. package/workflow/WorkflowExecutionTimelineEvent.d.ts +4 -1
  396. package/workflow/WorkflowExecutionTimelineEvent.d.ts.map +1 -1
  397. package/workflow/WorkflowExecutionTimelineEvent.js +2 -2
  398. package/workflow/WorkflowExecutionTimelineEvent.js.map +1 -1
  399. package/workflow/WorkflowExecutionViewer.d.ts.map +1 -1
  400. package/workflow/WorkflowExecutionViewer.js +18 -10
  401. package/workflow/WorkflowExecutionViewer.js.map +1 -1
  402. package/workflow/WorkflowFileReviewList.d.ts +46 -0
  403. package/workflow/WorkflowFileReviewList.d.ts.map +1 -0
  404. package/workflow/WorkflowFileReviewList.js +48 -0
  405. package/workflow/WorkflowFileReviewList.js.map +1 -0
  406. package/workflow/WorkflowTaskApprovalCard.d.ts +8 -1
  407. package/workflow/WorkflowTaskApprovalCard.d.ts.map +1 -1
  408. package/workflow/WorkflowTaskApprovalCard.js +12 -19
  409. package/workflow/WorkflowTaskApprovalCard.js.map +1 -1
  410. package/workflow/execution-inspector/ExecutionInspector.d.ts +21 -4
  411. package/workflow/execution-inspector/ExecutionInspector.d.ts.map +1 -1
  412. package/workflow/execution-inspector/ExecutionInspector.js +6 -3
  413. package/workflow/execution-inspector/ExecutionInspector.js.map +1 -1
  414. package/workflow/index.d.ts +1 -0
  415. package/workflow/index.d.ts.map +1 -1
  416. package/workflow/index.js +1 -0
  417. package/workflow/index.js.map +1 -1
  418. package/workflow/useWorkflowExecutionActions.d.ts +62 -4
  419. package/workflow/useWorkflowExecutionActions.d.ts.map +1 -1
  420. package/workflow/useWorkflowExecutionActions.js +94 -18
  421. package/workflow/useWorkflowExecutionActions.js.map +1 -1
  422. package/session/inspector/PlanTab.d.ts +0 -15
  423. package/session/inspector/PlanTab.d.ts.map +0 -1
  424. package/session/inspector/PlanTab.js +0 -19
  425. package/session/inspector/PlanTab.js.map +0 -1
  426. package/src/session/inspector/PlanTab.tsx +0 -31
@@ -0,0 +1,521 @@
1
+ import { describe, it, expect, afterEach, vi } from "vitest";
2
+ import { render, screen, cleanup, fireEvent } 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
+ PendingApprovalSchema,
10
+ type PendingApproval,
11
+ } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/approval_pb";
12
+ import {
13
+ CapturedFileChangeSchema,
14
+ FileChangeSetSchema,
15
+ FileDecisionSchema,
16
+ type FileChangeSet,
17
+ } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/filereview_pb";
18
+ import {
19
+ ApprovalAction,
20
+ FileChangeKind,
21
+ FileChangeSetStatus,
22
+ FileDecisionAction,
23
+ FileDecisionScope,
24
+ ToolCallStatus,
25
+ } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
26
+ import { ToolCallItem } from "../ToolCallItem";
27
+ import { ApprovalContext, type ApprovalContextValue } from "../ApprovalContext";
28
+ import { FileReviewContext } from "../FileReviewContext";
29
+
30
+ afterEach(cleanup);
31
+
32
+ function makeToolCall(opts: {
33
+ id?: string;
34
+ name: string;
35
+ args?: Record<string, unknown>;
36
+ result?: string;
37
+ mcpServerSlug?: string;
38
+ status?: ToolCallStatus;
39
+ fileChangeSetId?: string;
40
+ }): ToolCall {
41
+ return create(ToolCallSchema, {
42
+ id: opts.id ?? opts.name,
43
+ name: opts.name,
44
+ args: (opts.args ?? {}) as JsonObject,
45
+ result: opts.result ?? "",
46
+ mcpServerSlug: opts.mcpServerSlug ?? "",
47
+ status: opts.status ?? ToolCallStatus.TOOL_CALL_COMPLETED,
48
+ fileChangeSetId: opts.fileChangeSetId ?? "",
49
+ });
50
+ }
51
+
52
+ // The card-level disclosure (header chevron) lives ONLY on summary / sub-agent
53
+ // rows: a `div[role="button"]` header inside the row. Preview (content-bearing)
54
+ // rows have no such control — their body is always visible — so these helpers
55
+ // target that header specifically rather than "any [aria-expanded]" (a block's
56
+ // own RevealToggle also carries aria-expanded, on a native <button>, which has
57
+ // no role attribute and so is excluded here).
58
+ function rowHeaderToggle(container: HTMLElement): Element | null {
59
+ return container.querySelector(
60
+ '[data-cursor-target="tool-call-row"] [role="button"]',
61
+ );
62
+ }
63
+ function hasHeaderChevron(container: HTMLElement): boolean {
64
+ return rowHeaderToggle(container) != null;
65
+ }
66
+ function isExpanded(container: HTMLElement): boolean {
67
+ return rowHeaderToggle(container)?.getAttribute("aria-expanded") === "true";
68
+ }
69
+ function previewBody(container: HTMLElement): Element | null {
70
+ return container.querySelector('[data-cursor-target="tool-preview"]');
71
+ }
72
+
73
+ describe("ToolCallItem disclosure", () => {
74
+ // --- Summary categories keep the chevron ----------------------------------
75
+
76
+ it("keeps a settled summary tool collapsed behind a chevron", () => {
77
+ // Delete is a `summary` category — its one-line row (the file) tells the
78
+ // story, so its body stays hidden behind the header chevron.
79
+ const { container } = render(
80
+ <ToolCallItem
81
+ toolCall={makeToolCall({ name: "delete_file", args: { path: "/x.ts" } })}
82
+ />,
83
+ );
84
+ expect(hasHeaderChevron(container)).toBe(true);
85
+ expect(isExpanded(container)).toBe(false);
86
+ expect(previewBody(container)).toBeNull();
87
+ });
88
+
89
+ it("settles a summary tool closed once it finishes running", () => {
90
+ // A running summary tool foregrounds while live, then collapses to its
91
+ // compact summary the moment it completes (the user never touched it). The
92
+ // chevron is present throughout. Proves the React.memo + useAutoDisclosure
93
+ // composition recomputes autoOpen from the new ToolCall on re-render.
94
+ const { container, rerender } = render(
95
+ <ToolCallItem
96
+ toolCall={makeToolCall({
97
+ id: "tc-del",
98
+ name: "delete_file",
99
+ args: { path: "/x.ts" },
100
+ status: ToolCallStatus.TOOL_CALL_RUNNING,
101
+ })}
102
+ />,
103
+ );
104
+ expect(isExpanded(container)).toBe(true);
105
+
106
+ rerender(
107
+ <ToolCallItem
108
+ toolCall={makeToolCall({
109
+ id: "tc-del",
110
+ name: "delete_file",
111
+ args: { path: "/x.ts" },
112
+ status: ToolCallStatus.TOOL_CALL_COMPLETED,
113
+ })}
114
+ />,
115
+ );
116
+ expect(hasHeaderChevron(container)).toBe(true);
117
+ expect(isExpanded(container)).toBe(false);
118
+ });
119
+
120
+ // --- Preview categories: no chevron, always-visible body ------------------
121
+
122
+ it("renders a settled edit as an always-visible diff with no header chevron", () => {
123
+ // Edit is a `preview` category: the diff IS the information, so it renders
124
+ // inline with no competing card chevron. The diff "Show more" is layout-
125
+ // gated (jsdom computes no layout), so it is asserted in e2e, not here.
126
+ const { container } = render(
127
+ <ToolCallItem
128
+ toolCall={makeToolCall({
129
+ name: "StrReplace",
130
+ args: { path: "/x.ts" },
131
+ result:
132
+ '{"status":"success","value":{"linesAdded":1,"linesRemoved":1,"diffString":"@@ -1 +1 @@\\n-a\\n+b"}}',
133
+ })}
134
+ />,
135
+ );
136
+ expect(hasHeaderChevron(container)).toBe(false);
137
+ expect(previewBody(container)).not.toBeNull();
138
+ // The diff renders as the accessible table.
139
+ expect(container.querySelector("table")).not.toBeNull();
140
+ });
141
+
142
+ it("renders a settled shell as an always-visible terminal session, no chevron", () => {
143
+ // Shell is a `preview` category: its output IS the information. The body
144
+ // reads as one terminal session — the `$ command` prompt line leads, with
145
+ // its output below — visible without any disclosure click.
146
+ const { container } = render(
147
+ <ToolCallItem
148
+ toolCall={makeToolCall({
149
+ name: "Shell",
150
+ args: { command: "echo hello" },
151
+ result: "hello",
152
+ })}
153
+ />,
154
+ );
155
+ expect(hasHeaderChevron(container)).toBe(false);
156
+ expect(previewBody(container)).not.toBeNull();
157
+ expect(container.textContent).toContain("$ echo hello");
158
+ expect(screen.getByText("hello")).toBeTruthy();
159
+ expect(
160
+ container.querySelectorAll('[data-cursor-target="terminal-session"]'),
161
+ ).toHaveLength(1);
162
+ });
163
+
164
+ it("renders a settled MCP tool's result inline with no chevron", () => {
165
+ const { container } = render(
166
+ <ToolCallItem
167
+ toolCall={makeToolCall({
168
+ name: "send_message",
169
+ mcpServerSlug: "acme/slack",
170
+ result: '{"ok":true}',
171
+ })}
172
+ />,
173
+ );
174
+ expect(hasHeaderChevron(container)).toBe(false);
175
+ expect(previewBody(container)).not.toBeNull();
176
+ });
177
+
178
+ it("keeps the shell header minimal — no command subtitle, no exit summary", () => {
179
+ // The command and exit code live in the terminal session body, so the plain
180
+ // header is just icon + label + status + duration. A failed command must not
181
+ // leak its command or "exit N" into the header row.
182
+ const { container } = render(
183
+ <ToolCallItem
184
+ toolCall={makeToolCall({
185
+ name: "Shell",
186
+ args: { command: "ls /secret/abs/path" },
187
+ result: "denied\n[Command failed with exit code 2]",
188
+ })}
189
+ />,
190
+ );
191
+ const row = container.querySelector('[data-cursor-target="tool-call-row"]')!;
192
+ const header = row.firstElementChild!;
193
+ expect(header.textContent).toContain("Shell");
194
+ expect(header.textContent).not.toContain("/secret/abs/path");
195
+ expect(header.textContent).not.toContain("exit 2");
196
+ // The exit DOES surface — in the body's terminal session.
197
+ expect(container.textContent).toContain("exit 2");
198
+ });
199
+
200
+ it("renders a running shell as a single terminal session block, no chevron", () => {
201
+ const { container } = render(
202
+ <ToolCallItem
203
+ toolCall={makeToolCall({
204
+ name: "Shell",
205
+ args: { command: "echo hi" },
206
+ result: "hi",
207
+ status: ToolCallStatus.TOOL_CALL_RUNNING,
208
+ })}
209
+ />,
210
+ );
211
+ expect(hasHeaderChevron(container)).toBe(false);
212
+ expect(
213
+ container.querySelectorAll('[data-cursor-target="terminal-session"]'),
214
+ ).toHaveLength(1);
215
+ });
216
+
217
+ it("suppresses the body for a content-bearing tool that produced nothing", () => {
218
+ // The regression guard: a preview-category tool whose result normalizes to
219
+ // `empty` (no output) must stay a clean one-line row, never an empty padded
220
+ // box. (Shell never hits this — it always carries its command line.)
221
+ const { container } = render(
222
+ <ToolCallItem toolCall={makeToolCall({ name: "some_unknown_tool", result: "" })} />,
223
+ );
224
+ expect(hasHeaderChevron(container)).toBe(false);
225
+ expect(previewBody(container)).toBeNull();
226
+ });
227
+
228
+ it("shows the +N -M summary once for an edit — header only, not the diff body", () => {
229
+ // The row header carries the result summary; the always-visible diff body
230
+ // must NOT repeat it. With added != removed the header's combined "+3 -0"
231
+ // node and a body "+3" stat span would be distinguishable — and the body has
232
+ // none (suppressed via showStats={false}).
233
+ const { container } = render(
234
+ <ToolCallItem
235
+ toolCall={makeToolCall({
236
+ name: "StrReplace",
237
+ args: { path: "/x.ts" },
238
+ result:
239
+ '{"status":"success","value":{"linesAdded":3,"linesRemoved":0,"diffString":"@@ -0,0 +1,3 @@\\n+a\\n+b\\n+c"}}',
240
+ })}
241
+ />,
242
+ );
243
+ expect(hasHeaderChevron(container)).toBe(false);
244
+ expect(screen.getByText("+3 -0")).toBeTruthy();
245
+ expect(screen.queryAllByText("+3")).toHaveLength(0);
246
+ expect(container.querySelector("table")).not.toBeNull();
247
+ });
248
+
249
+ // --- Approvals -------------------------------------------------------------
250
+
251
+ it("foregrounds a gated summary tool behind its chevron with inline actions", () => {
252
+ const tc = makeToolCall({
253
+ id: "tc-gated",
254
+ name: "delete_file",
255
+ status: ToolCallStatus.TOOL_CALL_WAITING_APPROVAL,
256
+ });
257
+ const approval: PendingApproval = create(PendingApprovalSchema, {
258
+ toolCallId: "tc-gated",
259
+ toolName: "delete_file",
260
+ argsPreview: '{"path":"/tmp/x"}',
261
+ });
262
+ const ctx: ApprovalContextValue = {
263
+ approvalsByToolCallId: new Map([["tc-gated", approval]]),
264
+ onSubmit: () => {},
265
+ submittingIds: new Set(),
266
+ errorsByToolCallId: new Map(),
267
+ };
268
+
269
+ const { container } = render(
270
+ <ApprovalContext.Provider value={ctx}>
271
+ <ToolCallItem toolCall={tc} />
272
+ </ApprovalContext.Provider>,
273
+ );
274
+
275
+ expect(hasHeaderChevron(container)).toBe(true);
276
+ expect(isExpanded(container)).toBe(true);
277
+ expect(screen.getByLabelText("Approve")).toBeTruthy();
278
+ expect(screen.getByLabelText("Approve all file deletions")).toBeTruthy();
279
+ });
280
+
281
+ it("renders a gated preview tool's actions inline with no chevron", () => {
282
+ // A gated shell (a preview category) shows its decision actions in the
283
+ // always-visible body — no header chevron, since there is nothing to reveal.
284
+ const tc = makeToolCall({
285
+ id: "tc-shell-gated",
286
+ name: "Shell",
287
+ args: { command: "rm -rf /tmp/x" },
288
+ status: ToolCallStatus.TOOL_CALL_WAITING_APPROVAL,
289
+ });
290
+ const approval: PendingApproval = create(PendingApprovalSchema, {
291
+ toolCallId: "tc-shell-gated",
292
+ toolName: "Shell",
293
+ argsPreview: '{"command":"rm -rf /tmp/x"}',
294
+ });
295
+ const ctx: ApprovalContextValue = {
296
+ approvalsByToolCallId: new Map([["tc-shell-gated", approval]]),
297
+ onSubmit: () => {},
298
+ submittingIds: new Set(),
299
+ errorsByToolCallId: new Map(),
300
+ };
301
+
302
+ const { container } = render(
303
+ <ApprovalContext.Provider value={ctx}>
304
+ <ToolCallItem toolCall={tc} />
305
+ </ApprovalContext.Provider>,
306
+ );
307
+
308
+ expect(hasHeaderChevron(container)).toBe(false);
309
+ expect(previewBody(container)).not.toBeNull();
310
+ expect(screen.getByLabelText("Approve")).toBeTruthy();
311
+ });
312
+
313
+ it("gives a pending gate card a restrained destructive accent (no amber fill)", () => {
314
+ const tc = makeToolCall({
315
+ id: "tc-gated",
316
+ name: "delete_file",
317
+ status: ToolCallStatus.TOOL_CALL_WAITING_APPROVAL,
318
+ });
319
+ const approval: PendingApproval = create(PendingApprovalSchema, {
320
+ toolCallId: "tc-gated",
321
+ toolName: "delete_file",
322
+ argsPreview: '{"path":"/tmp/x"}',
323
+ });
324
+ const ctx: ApprovalContextValue = {
325
+ approvalsByToolCallId: new Map([["tc-gated", approval]]),
326
+ onSubmit: () => {},
327
+ submittingIds: new Set(),
328
+ errorsByToolCallId: new Map(),
329
+ };
330
+
331
+ const { container } = render(
332
+ <ApprovalContext.Provider value={ctx}>
333
+ <ToolCallItem toolCall={tc} />
334
+ </ApprovalContext.Provider>,
335
+ );
336
+
337
+ const row = container.querySelector('[data-cursor-target="tool-call-row"]')!;
338
+ expect(row.className).toContain("border-l-destructive");
339
+ expect(row.className).not.toContain("bg-warning");
340
+ });
341
+
342
+ it("routes an inline APPROVE_ALL decision with the gated tool's id", () => {
343
+ const tc = makeToolCall({
344
+ id: "tc-gated",
345
+ name: "delete_file",
346
+ status: ToolCallStatus.TOOL_CALL_WAITING_APPROVAL,
347
+ });
348
+ const approval: PendingApproval = create(PendingApprovalSchema, {
349
+ toolCallId: "tc-gated",
350
+ toolName: "delete_file",
351
+ argsPreview: '{"path":"/tmp/x"}',
352
+ });
353
+ const onSubmit = vi.fn();
354
+ const ctx: ApprovalContextValue = {
355
+ approvalsByToolCallId: new Map([["tc-gated", approval]]),
356
+ onSubmit,
357
+ submittingIds: new Set(),
358
+ errorsByToolCallId: new Map(),
359
+ };
360
+
361
+ render(
362
+ <ApprovalContext.Provider value={ctx}>
363
+ <ToolCallItem toolCall={tc} />
364
+ </ApprovalContext.Provider>,
365
+ );
366
+
367
+ fireEvent.click(screen.getByLabelText("Approve all file deletions"));
368
+ expect(onSubmit).toHaveBeenCalledTimes(1);
369
+ expect(onSubmit).toHaveBeenCalledWith(
370
+ "tc-gated",
371
+ ApprovalAction.APPROVE_ALL,
372
+ undefined,
373
+ );
374
+ });
375
+
376
+ it("surfaces a keyed approval error in-card on the gated row (the primary route)", () => {
377
+ const tc = makeToolCall({
378
+ id: "tc-gated",
379
+ name: "delete_file",
380
+ status: ToolCallStatus.TOOL_CALL_WAITING_APPROVAL,
381
+ });
382
+ const approval: PendingApproval = create(PendingApprovalSchema, {
383
+ toolCallId: "tc-gated",
384
+ toolName: "delete_file",
385
+ argsPreview: '{"path":"/tmp/x"}',
386
+ });
387
+ const ctx: ApprovalContextValue = {
388
+ approvalsByToolCallId: new Map([["tc-gated", approval]]),
389
+ onSubmit: () => {},
390
+ submittingIds: new Set(),
391
+ // The error for THIS gate, keyed by its tool call id.
392
+ errorsByToolCallId: new Map([["tc-gated", new Error("gate already resolved")]]),
393
+ };
394
+
395
+ const { container } = render(
396
+ <ApprovalContext.Provider value={ctx}>
397
+ <ToolCallItem toolCall={tc} />
398
+ </ApprovalContext.Provider>,
399
+ );
400
+
401
+ const alert = container.querySelector(
402
+ '[data-cursor-target="approval-error"]',
403
+ );
404
+ expect(alert).toBeTruthy();
405
+ expect(alert!.textContent).toContain("gate already resolved");
406
+ });
407
+
408
+ // --- Card chrome -----------------------------------------------------------
409
+
410
+ it("renders as its own bordered card by default, with a divider-row fallback when nested", () => {
411
+ const tc = makeToolCall({ name: "Shell", args: { command: "echo hi" }, result: "hi" });
412
+
413
+ const { container, rerender } = render(<ToolCallItem toolCall={tc} />);
414
+ let row = container.querySelector('[data-cursor-target="tool-call-row"]')!;
415
+ expect(row.className).toContain("rounded-lg");
416
+ expect(row.className).toContain("border-border-prominent");
417
+
418
+ rerender(<ToolCallItem toolCall={tc} bordered={false} />);
419
+ row = container.querySelector('[data-cursor-target="tool-call-row"]')!;
420
+ expect(row.className).not.toContain("rounded-lg");
421
+ expect(row.className).toContain("border-b");
422
+ });
423
+ });
424
+
425
+ // ---------------------------------------------------------------------------
426
+ // File-review badge — a stamped edit row's live review state
427
+ // ---------------------------------------------------------------------------
428
+
429
+ describe("ToolCallItem file-review badge", () => {
430
+ const SET_ID = "aex-1:0";
431
+
432
+ function reviewSet(
433
+ status: FileChangeSetStatus,
434
+ decisions: { action: FileDecisionAction }[] = [],
435
+ ): FileChangeSet {
436
+ return create(FileChangeSetSchema, {
437
+ id: SET_ID,
438
+ status,
439
+ changes: [
440
+ create(CapturedFileChangeSchema, {
441
+ id: `${SET_ID}:notes.md`,
442
+ pathBefore: "notes.md",
443
+ pathAfter: "notes.md",
444
+ kind: FileChangeKind.MODIFY,
445
+ diffComplete: true,
446
+ }),
447
+ ],
448
+ decisions: decisions.map((d) =>
449
+ create(FileDecisionSchema, {
450
+ changeSetId: SET_ID,
451
+ scope: FileDecisionScope.CHANGE_SET,
452
+ action: d.action,
453
+ }),
454
+ ),
455
+ });
456
+ }
457
+
458
+ function renderStampedRow(set: FileChangeSet) {
459
+ const tc = makeToolCall({
460
+ id: "tc-edit",
461
+ name: "edit",
462
+ args: { path: "notes.md", old_string: "a", new_string: "b" },
463
+ fileChangeSetId: SET_ID,
464
+ });
465
+ return render(
466
+ <FileReviewContext.Provider value={{ changeSetsById: new Map([[SET_ID, set]]) }}>
467
+ <ToolCallItem toolCall={tc} />
468
+ </FileReviewContext.Provider>,
469
+ );
470
+ }
471
+
472
+ function badge(container: HTMLElement): Element | null {
473
+ return container.querySelector('[data-cursor-target="file-review-row-badge"]');
474
+ }
475
+
476
+ it("badges a stamped row 'Pending review' while its set awaits review", () => {
477
+ const { container } = renderStampedRow(reviewSet(FileChangeSetStatus.AWAITING_REVIEW));
478
+ expect(badge(container)?.textContent).toBe("Pending review");
479
+ });
480
+
481
+ it("flips the badge to the file's verdict once the set is decided", () => {
482
+ const kept = renderStampedRow(
483
+ reviewSet(FileChangeSetStatus.RECONCILED, [{ action: FileDecisionAction.APPROVE }]),
484
+ );
485
+ expect(badge(kept.container)?.textContent).toBe("Kept");
486
+ cleanup();
487
+
488
+ const discarded = renderStampedRow(
489
+ reviewSet(FileChangeSetStatus.RECONCILED, [{ action: FileDecisionAction.REJECT }]),
490
+ );
491
+ expect(badge(discarded.container)?.textContent).toBe("Discarded");
492
+ });
493
+
494
+ it("shows no badge for an unstamped row or without a provider", () => {
495
+ // Unstamped (a plain edit outside capture mode) — provider present.
496
+ const unstamped = makeToolCall({
497
+ id: "tc-plain",
498
+ name: "edit",
499
+ args: { path: "notes.md", old_string: "a", new_string: "b" },
500
+ });
501
+ const withProvider = render(
502
+ <FileReviewContext.Provider
503
+ value={{ changeSetsById: new Map([[SET_ID, reviewSet(FileChangeSetStatus.AWAITING_REVIEW)]]) }}
504
+ >
505
+ <ToolCallItem toolCall={unstamped} />
506
+ </FileReviewContext.Provider>,
507
+ );
508
+ expect(badge(withProvider.container)).toBeNull();
509
+ cleanup();
510
+
511
+ // Stamped but no provider (a consumer without file review) — no badge.
512
+ const stamped = makeToolCall({
513
+ id: "tc-edit",
514
+ name: "edit",
515
+ args: { path: "notes.md", old_string: "a", new_string: "b" },
516
+ fileChangeSetId: SET_ID,
517
+ });
518
+ const bare = render(<ToolCallItem toolCall={stamped} />);
519
+ expect(badge(bare.container)).toBeNull();
520
+ });
521
+ });
@@ -0,0 +1,137 @@
1
+ import { describe, it, expect, afterEach } from "vitest";
2
+ import { render, screen, cleanup, fireEvent } 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
+ PendingApprovalSchema,
10
+ type PendingApproval,
11
+ } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/approval_pb";
12
+ import { ToolCallStatus } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
13
+ import { ToolRunGroup } from "../ToolRunGroup";
14
+ import { ApprovalContext, type ApprovalContextValue } from "../ApprovalContext";
15
+
16
+ afterEach(cleanup);
17
+
18
+ function makeRead(id: string, path: string, status?: ToolCallStatus): ToolCall {
19
+ return create(ToolCallSchema, {
20
+ id,
21
+ name: "Read",
22
+ args: { path } as JsonObject,
23
+ status: status ?? ToolCallStatus.TOOL_CALL_COMPLETED,
24
+ });
25
+ }
26
+
27
+ function chipExpanded(container: HTMLElement): boolean {
28
+ return (
29
+ container.querySelector("[aria-expanded]")?.getAttribute("aria-expanded") ===
30
+ "true"
31
+ );
32
+ }
33
+
34
+ describe("ToolRunGroup", () => {
35
+ it("renders a category-aware collapsed label and hides the rows", () => {
36
+ const { container } = render(
37
+ <ToolRunGroup
38
+ category="read"
39
+ toolCalls={[
40
+ makeRead("r1", "/a.ts"),
41
+ makeRead("r2", "/b.ts"),
42
+ makeRead("r3", "/c.ts"),
43
+ ]}
44
+ />,
45
+ );
46
+
47
+ expect(screen.getByText("Read 3 files")).toBeTruthy();
48
+ expect(chipExpanded(container)).toBe(false);
49
+ // Rows are not rendered while folded.
50
+ expect(screen.queryByText("/a.ts")).toBeNull();
51
+ });
52
+
53
+ it("reveals the folded rows on expand", () => {
54
+ render(
55
+ <ToolRunGroup
56
+ category="read"
57
+ toolCalls={[makeRead("r1", "/a.ts"), makeRead("r2", "/b.ts")]}
58
+ />,
59
+ );
60
+
61
+ fireEvent.click(screen.getByText("Read 2 files"));
62
+ // Read rows render filename-first (the leading "/" is a dimmed-or-hidden dir).
63
+ expect(screen.getByText("a.ts")).toBeTruthy();
64
+ expect(screen.getByText("b.ts")).toBeTruthy();
65
+ });
66
+
67
+ it("settles closed when the run has completed", () => {
68
+ const { container } = render(
69
+ <ToolRunGroup
70
+ category="read"
71
+ toolCalls={[makeRead("r1", "/a.ts"), makeRead("r2", "/b.ts")]}
72
+ />,
73
+ );
74
+ expect(chipExpanded(container)).toBe(false);
75
+ });
76
+
77
+ it("auto-opens when a folded call is awaiting approval, so the gate is reachable", () => {
78
+ const gated = makeRead("r2", "/b.ts", ToolCallStatus.TOOL_CALL_WAITING_APPROVAL);
79
+ const approval: PendingApproval = create(PendingApprovalSchema, {
80
+ toolCallId: "r2",
81
+ toolName: "Read",
82
+ argsPreview: '{"path":"/b.ts"}',
83
+ });
84
+ const ctx: ApprovalContextValue = {
85
+ approvalsByToolCallId: new Map([["r2", approval]]),
86
+ onSubmit: () => {},
87
+ submittingIds: new Set(),
88
+ errorsByToolCallId: new Map(),
89
+ };
90
+
91
+ const { container } = render(
92
+ <ApprovalContext.Provider value={ctx}>
93
+ <ToolRunGroup category="read" toolCalls={[makeRead("r1", "/a.ts"), gated]} />
94
+ </ApprovalContext.Provider>,
95
+ );
96
+
97
+ expect(chipExpanded(container)).toBe(true);
98
+ // The gate renders inline on its row inside the opened chip.
99
+ expect(screen.getByLabelText("Approve")).toBeTruthy();
100
+ });
101
+
102
+ it("is a bordered card whose expanded children are borderless divider rows", () => {
103
+ const { container } = render(
104
+ <ToolRunGroup
105
+ category="read"
106
+ toolCalls={[makeRead("r1", "/a.ts"), makeRead("r2", "/b.ts")]}
107
+ />,
108
+ );
109
+
110
+ const chip = container.querySelector('[data-cursor-target="tool-run-group"]')!;
111
+ expect(chip.className).toContain("rounded-lg");
112
+ // Class presence only — actual rendering is guarded by the layer-invariant +
113
+ // e2e computed-style tests (happy-dom does not resolve `@layer`).
114
+ expect(chip.className).toContain("border-border-prominent");
115
+
116
+ fireEvent.click(screen.getByText("Read 2 files"));
117
+
118
+ // The chip is the card; its child rows are dividers, never nested cards.
119
+ const rows = container.querySelectorAll('[data-cursor-target="tool-call-row"]');
120
+ expect(rows.length).toBe(2);
121
+ for (const row of rows) {
122
+ expect(row.className).not.toContain("rounded-lg");
123
+ expect(row.className).toContain("border-b");
124
+ }
125
+ });
126
+
127
+ it("honours a custom label formatter", () => {
128
+ render(
129
+ <ToolRunGroup
130
+ category="read"
131
+ toolCalls={[makeRead("r1", "/a.ts"), makeRead("r2", "/b.ts")]}
132
+ formatLabel={(calls) => `Inspected ${calls.length} sources`}
133
+ />,
134
+ );
135
+ expect(screen.getByText("Inspected 2 sources")).toBeTruthy();
136
+ });
137
+ });