@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
@@ -13,17 +13,24 @@ const planArtifact = create(ExecutionArtifactSchema, {
13
13
  kind: ExecutionArtifactKind.FILE,
14
14
  sizeBytes: 4500n,
15
15
  storageKey: "artifacts/aex_1/plan.md",
16
- downloadUrl: "https://example.test/plan.md",
17
16
  });
18
17
 
19
18
  /** Modal content fetches artifact text — keep it pending so nothing rejects. */
20
- function createStigmerMock(): { stigmer: Stigmer; getArtifactContent: ReturnType<typeof vi.fn> } {
19
+ function createStigmerMock(): {
20
+ stigmer: Stigmer;
21
+ getArtifactContent: ReturnType<typeof vi.fn>;
22
+ getArtifactDownloadUrl: ReturnType<typeof vi.fn>;
23
+ } {
21
24
  const getArtifactContent = vi.fn().mockReturnValue(new Promise(() => {}));
25
+ const getArtifactDownloadUrl = vi
26
+ .fn()
27
+ .mockResolvedValue({ downloadUrl: "https://example.test/plan.md" });
22
28
  return {
23
29
  stigmer: {
24
- agentExecution: { getArtifactContent },
30
+ agentExecution: { getArtifactContent, getArtifactDownloadUrl },
25
31
  } as unknown as Stigmer,
26
32
  getArtifactContent,
33
+ getArtifactDownloadUrl,
27
34
  };
28
35
  }
29
36
 
@@ -33,11 +40,16 @@ function withStigmer(children: ReactNode, stigmer: Stigmer) {
33
40
  );
34
41
  }
35
42
 
43
+ /** PlanArtifactCard now resolves downloads on demand, so it needs a provider. */
44
+ function renderCard(ui: ReactNode) {
45
+ return render(withStigmer(ui, createStigmerMock().stigmer));
46
+ }
47
+
36
48
  afterEach(cleanup);
37
49
 
38
50
  describe("PlanArtifactCard — action surface", () => {
39
51
  it("is an accessible region labelled as the plan's actions, with name + size", () => {
40
- render(
52
+ renderCard(
41
53
  <PlanArtifactCard executionId="aex_1" artifact={planArtifact} org="acme" />,
42
54
  );
43
55
 
@@ -61,7 +73,7 @@ describe("PlanArtifactCard — action surface", () => {
61
73
 
62
74
  it("offers exactly one primary 'Build from plan' action that calls onImplement", () => {
63
75
  const onImplement = vi.fn();
64
- render(
76
+ renderCard(
65
77
  <PlanArtifactCard
66
78
  executionId="aex_1"
67
79
  artifact={planArtifact}
@@ -75,7 +87,7 @@ describe("PlanArtifactCard — action surface", () => {
75
87
  });
76
88
 
77
89
  it("hides 'Build from plan' when onImplement is not provided", () => {
78
- render(
90
+ renderCard(
79
91
  <PlanArtifactCard executionId="aex_1" artifact={planArtifact} org="acme" />,
80
92
  );
81
93
  expect(screen.queryByText("Build from plan")).toBeNull();
@@ -83,7 +95,7 @@ describe("PlanArtifactCard — action surface", () => {
83
95
 
84
96
  it("disables 'Build from plan' when disabled", () => {
85
97
  const onImplement = vi.fn();
86
- render(
98
+ renderCard(
87
99
  <PlanArtifactCard
88
100
  executionId="aex_1"
89
101
  artifact={planArtifact}
@@ -99,19 +111,28 @@ describe("PlanArtifactCard — action surface", () => {
99
111
  expect(onImplement).not.toHaveBeenCalled();
100
112
  });
101
113
 
102
- it("exposes a Download link to the artifact", () => {
114
+ it("downloads the artifact on demand via a freshly minted URL", async () => {
115
+ const { stigmer, getArtifactDownloadUrl } = createStigmerMock();
103
116
  render(
104
- <PlanArtifactCard executionId="aex_1" artifact={planArtifact} org="acme" />,
117
+ withStigmer(
118
+ <PlanArtifactCard executionId="aex_1" artifact={planArtifact} org="acme" />,
119
+ stigmer,
120
+ ),
105
121
  );
106
- const link = screen.getByText("Download").closest("a")!;
107
- expect(link.getAttribute("href")).toBe("https://example.test/plan.md");
122
+ fireEvent.click(screen.getByText("Download"));
123
+ // The URL is resolved at click time from the stable storage key — never a
124
+ // baked, expirable URL.
125
+ expect(getArtifactDownloadUrl).toHaveBeenCalledTimes(1);
126
+ const req = getArtifactDownloadUrl.mock.calls[0][0];
127
+ expect(req.executionId).toBe("aex_1");
128
+ expect(req.storageKey).toBe("artifacts/aex_1/plan.md");
108
129
  });
109
130
  });
110
131
 
111
132
  describe("PlanArtifactCard — Cmd/Ctrl+Enter accelerator (card-scoped)", () => {
112
133
  it("fires onImplement on Cmd+Enter from within the card", () => {
113
134
  const onImplement = vi.fn();
114
- render(
135
+ renderCard(
115
136
  <PlanArtifactCard
116
137
  executionId="aex_1"
117
138
  artifact={planArtifact}
@@ -127,7 +148,7 @@ describe("PlanArtifactCard — Cmd/Ctrl+Enter accelerator (card-scoped)", () =>
127
148
 
128
149
  it("fires onImplement on Ctrl+Enter", () => {
129
150
  const onImplement = vi.fn();
130
- render(
151
+ renderCard(
131
152
  <PlanArtifactCard
132
153
  executionId="aex_1"
133
154
  artifact={planArtifact}
@@ -143,7 +164,7 @@ describe("PlanArtifactCard — Cmd/Ctrl+Enter accelerator (card-scoped)", () =>
143
164
 
144
165
  it("ignores a plain Enter (no modifier) so it never hijacks typing", () => {
145
166
  const onImplement = vi.fn();
146
- render(
167
+ renderCard(
147
168
  <PlanArtifactCard
148
169
  executionId="aex_1"
149
170
  artifact={planArtifact}
@@ -159,7 +180,7 @@ describe("PlanArtifactCard — Cmd/Ctrl+Enter accelerator (card-scoped)", () =>
159
180
 
160
181
  it("does not fire when disabled", () => {
161
182
  const onImplement = vi.fn();
162
- render(
183
+ renderCard(
163
184
  <PlanArtifactCard
164
185
  executionId="aex_1"
165
186
  artifact={planArtifact}
@@ -197,7 +218,7 @@ describe("PlanArtifactCard — Open full (org-gated preview)", () => {
197
218
 
198
219
  it("hides 'Open full' when org is absent (modal needs org), keeping Build + Download", () => {
199
220
  const onImplement = vi.fn();
200
- render(
221
+ renderCard(
201
222
  <PlanArtifactCard
202
223
  executionId="aex_1"
203
224
  artifact={planArtifact}
@@ -1,10 +1,24 @@
1
- import { describe, it, expect, afterEach } from "vitest";
2
- import { render, cleanup } from "@testing-library/react";
1
+ import { describe, it, expect, afterEach, vi } from "vitest";
2
+ import { render, cleanup, screen, fireEvent, waitFor } from "@testing-library/react";
3
+ import type { ReactNode } from "react";
4
+ import { create, type JsonObject } from "@bufbuild/protobuf";
5
+ import { ToolCallSchema } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/message_pb";
6
+ import {
7
+ ToolCallStatus,
8
+ ToolKind,
9
+ } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
10
+ import { normalizeToolResult } from "@stigmer/sdk";
3
11
  import type { ToolResultView } from "@stigmer/sdk";
12
+ import type { Stigmer } from "@stigmer/sdk";
13
+ import { StigmerContext } from "../../context";
4
14
  import { ResultView, summarizeResultView } from "../ResultView";
5
15
 
6
16
  afterEach(cleanup);
7
17
 
18
+ function withStigmer(children: ReactNode, stigmer: Stigmer) {
19
+ return <StigmerContext.Provider value={stigmer}>{children}</StigmerContext.Provider>;
20
+ }
21
+
8
22
  describe("ResultView", () => {
9
23
  it("renders an edit diff with add/remove counts from the envelope", () => {
10
24
  const view: ToolResultView = {
@@ -30,6 +44,39 @@ describe("ResultView", () => {
30
44
  expect(container.textContent).toContain("hello world");
31
45
  });
32
46
 
47
+ it("renders a Cursor unifiedDiff through the DiffViewer table, not a raw patch dump", () => {
48
+ const view: ToolResultView = {
49
+ type: "diff",
50
+ path: "/workspace/notes.md",
51
+ unifiedDiff: "--- /dev/null\n+++ b/notes.md\n@@ -0,0 +1,2 @@\n+# Title\n+body\n",
52
+ linesAdded: 2,
53
+ linesRemoved: 0,
54
+ };
55
+ const { container } = render(<ResultView view={view} />);
56
+ // The accessible table renders the content...
57
+ expect(container.querySelector("table")).not.toBeNull();
58
+ expect(container.textContent).toContain("# Title");
59
+ // ...and the raw patch preamble never reaches the screen.
60
+ expect(container.textContent).not.toContain("/dev/null");
61
+ expect(container.textContent).not.toContain("@@");
62
+ });
63
+
64
+ it("suppresses the +N -M stats when showStats is false (owning row shows them)", () => {
65
+ const view: ToolResultView = {
66
+ type: "diff",
67
+ path: "/workspace/x.md",
68
+ oldText: "a",
69
+ newText: "a b",
70
+ };
71
+ const { container } = render(
72
+ <ResultView view={view} showFileName={false} showStats={false} />,
73
+ );
74
+ // The diff content still renders, but the duplicate counts do not.
75
+ expect(container.textContent).toContain("a b");
76
+ expect(container.textContent).not.toContain("+1");
77
+ expect(container.textContent).not.toContain("-0");
78
+ });
79
+
33
80
  it("renders a terminal with a non-zero exit badge", () => {
34
81
  const view: ToolResultView = {
35
82
  type: "terminal",
@@ -42,6 +89,26 @@ describe("ResultView", () => {
42
89
  expect(container.textContent).toContain("boom");
43
90
  });
44
91
 
92
+ it("renders a terminal as one session: $ command then output, no badge on success", () => {
93
+ const view: ToolResultView = {
94
+ type: "terminal",
95
+ command: "echo hello",
96
+ stdout: "hello",
97
+ stderr: "",
98
+ exitCode: 0,
99
+ };
100
+ const { container } = render(<ResultView view={view} />);
101
+ // The command leads the session as a prompt line, with its output below.
102
+ expect(container.textContent).toContain("$ echo hello");
103
+ expect(container.textContent).toContain("hello");
104
+ // Success carries no exit badge — the row's status icon confirms the run.
105
+ expect(container.textContent).not.toContain("exit 0");
106
+ // One block, not a command box + a separate output box.
107
+ expect(
108
+ container.querySelectorAll('[data-cursor-target="terminal-session"]'),
109
+ ).toHaveLength(1);
110
+ });
111
+
45
112
  it("renders a search match list", () => {
46
113
  const view: ToolResultView = {
47
114
  type: "search",
@@ -52,6 +119,95 @@ describe("ResultView", () => {
52
119
  expect(container.textContent).toContain("2 matches");
53
120
  });
54
121
 
122
+ it("renders a file-name search as a clickable file list with a 'files' count", () => {
123
+ const view: ToolResultView = {
124
+ type: "search",
125
+ kind: "files",
126
+ matches: [{ file: "src/a.ts", text: "src/a.ts" }],
127
+ count: 1,
128
+ };
129
+ const { container } = render(<ResultView view={view} />);
130
+ expect(container.textContent).toContain("1 file");
131
+ // The path renders filename-first through FilePathLink (an interactive control).
132
+ expect(container.textContent).toContain("a.ts");
133
+ expect(container.querySelector("button, a")).not.toBeNull();
134
+ });
135
+
136
+ it("renders content matches grouped under their file with line numbers", () => {
137
+ const view: ToolResultView = {
138
+ type: "search",
139
+ kind: "content",
140
+ matches: [
141
+ { file: "a.ts", line: 12, text: "// TODO: fix" },
142
+ { file: "a.ts", line: 30, text: "// TODO: later" },
143
+ ],
144
+ count: 2,
145
+ };
146
+ const { container } = render(<ResultView view={view} />);
147
+ expect(container.textContent).toContain("2 matches");
148
+ expect(container.textContent).toContain("12:");
149
+ expect(container.textContent).toContain("// TODO: fix");
150
+ });
151
+
152
+ it("names the subtype in the empty state (files vs matches), query-free", () => {
153
+ const files = render(
154
+ <ResultView view={{ type: "search", kind: "files", matches: [], count: 0 }} />,
155
+ );
156
+ expect(files.container.textContent).toContain("No files found");
157
+
158
+ const content = render(
159
+ <ResultView view={{ type: "search", kind: "content", matches: [], count: 0 }} />,
160
+ );
161
+ expect(content.container.textContent).toContain("No matches");
162
+ });
163
+
164
+ it("shows a 'showing first N' note when the engine truncated the results", () => {
165
+ const view: ToolResultView = {
166
+ type: "search",
167
+ kind: "files",
168
+ matches: [
169
+ { file: "a.ts", text: "a.ts" },
170
+ { file: "b.ts", text: "b.ts" },
171
+ ],
172
+ count: 200,
173
+ truncated: true,
174
+ };
175
+ const { container } = render(<ResultView view={view} />);
176
+ expect(container.textContent).toContain("200 files");
177
+ expect(container.textContent).toContain("showing first 2");
178
+ });
179
+
180
+ it("suppresses the search count when showStats is false (owning row shows it)", () => {
181
+ const view: ToolResultView = {
182
+ type: "search",
183
+ kind: "files",
184
+ matches: [{ file: "Dockerfile", text: "Dockerfile" }],
185
+ count: 1,
186
+ };
187
+ const { container } = render(<ResultView view={view} showStats={false} />);
188
+ // The file still renders, but the redundant "1 file" count does not.
189
+ expect(container.textContent).toContain("Dockerfile");
190
+ expect(container.textContent).not.toContain("1 file");
191
+ });
192
+
193
+ it("keeps the truncation note even when the count is suppressed", () => {
194
+ const view: ToolResultView = {
195
+ type: "search",
196
+ kind: "files",
197
+ matches: [
198
+ { file: "a.ts", text: "a.ts" },
199
+ { file: "b.ts", text: "b.ts" },
200
+ ],
201
+ count: 200,
202
+ truncated: true,
203
+ };
204
+ const { container } = render(<ResultView view={view} showStats={false} />);
205
+ // The count header is gone, but the "showing first N of M" note remains —
206
+ // it is information the row header does not carry.
207
+ expect(container.textContent).not.toContain("200 files");
208
+ expect(container.textContent).toContain("Showing first 2 of 200");
209
+ });
210
+
55
211
  it("renders unknown JSON results as a labeled code block, not a raw dump", () => {
56
212
  const view: ToolResultView = { type: "json", value: { a: 1 } };
57
213
  const { container } = render(<ResultView view={view} />);
@@ -70,6 +226,119 @@ describe("ResultView", () => {
70
226
  const { container } = render(<ResultView view={{ type: "empty" }} />);
71
227
  expect(container.textContent).toBe("");
72
228
  });
229
+
230
+ it("suppresses the diff filename when showFileName is false (owning row names it)", () => {
231
+ const view: ToolResultView = {
232
+ type: "diff",
233
+ path: "/workspace/secret.md",
234
+ oldText: "a",
235
+ newText: "a b",
236
+ };
237
+ const { container } = render(<ResultView view={view} showFileName={false} />);
238
+ // The path is not restated in the body; the diff content still renders.
239
+ expect(container.textContent).not.toContain("secret.md");
240
+ expect(container.textContent).toContain("a b");
241
+ });
242
+
243
+ it("renders a neutral 'no preview' notice for a contentless diff", () => {
244
+ const view: ToolResultView = { type: "diff", path: "/workspace/x.md" };
245
+ const { container } = render(<ResultView view={view} showFileName={false} />);
246
+ expect(container.textContent).toContain("No preview available for this change");
247
+ expect(container.textContent).not.toContain("x.md");
248
+ });
249
+
250
+ // Integration lock through the full normalizeToolResult -> ResultView pipeline:
251
+ // a whole-file write's proposed content is reconstructed from the tool args
252
+ // (Phase 5 Slice 4 removed the ToolCall.file_changes capture) and renders as
253
+ // the file body — never the "No preview available" notice.
254
+ it("renders a whole-file write's proposed content from args, not 'No preview available'", () => {
255
+ const CONTENT = "# Notes\n\n- first bullet\n- second bullet\n";
256
+ const toolCall = create(ToolCallSchema, {
257
+ id: "tool_create",
258
+ name: "write_file",
259
+ toolKind: ToolKind.FILE_WRITE,
260
+ status: ToolCallStatus.TOOL_CALL_COMPLETED,
261
+ args: { path: "notes.md", contents: CONTENT } as JsonObject,
262
+ });
263
+
264
+ const { container } = render(<ResultView view={normalizeToolResult(toolCall)} />);
265
+ expect(container.textContent).toContain("first bullet");
266
+ expect(container.textContent).not.toContain("No preview available");
267
+ });
268
+
269
+ it("suppresses the file path in a write fallback when showFileName is false", () => {
270
+ const view: ToolResultView = {
271
+ type: "file",
272
+ path: "/workspace/created.md",
273
+ content: "the body",
274
+ truncated: false,
275
+ };
276
+ const { container } = render(<ResultView view={view} showFileName={false} />);
277
+ expect(container.textContent).toContain("the body");
278
+ expect(container.textContent).not.toContain("created.md");
279
+ });
280
+ });
281
+
282
+ describe("ResultView — offloaded outputRef (on-demand resolution)", () => {
283
+ const imageView: ToolResultView = {
284
+ type: "outputRef",
285
+ storageKey: "artifacts/aex_1/toolcalls/tc.png",
286
+ contentHash: "h",
287
+ isImage: true,
288
+ mimeType: "image/png",
289
+ sizeBytes: 1234,
290
+ preview: "",
291
+ };
292
+
293
+ const textView: ToolResultView = {
294
+ type: "outputRef",
295
+ storageKey: "artifacts/aex_1/toolcalls/tc.txt",
296
+ contentHash: "h",
297
+ isImage: false,
298
+ mimeType: "text/plain",
299
+ sizeBytes: 900_000,
300
+ preview: "short head…",
301
+ };
302
+
303
+ it("renders an offloaded image from a freshly minted URL (never a baked one)", async () => {
304
+ const getArtifactDownloadUrl = vi
305
+ .fn()
306
+ .mockResolvedValue({ downloadUrl: "https://fresh/url.png" });
307
+ const stigmer = {
308
+ agentExecution: { getArtifactDownloadUrl, getArtifactContent: vi.fn() },
309
+ } as unknown as Stigmer;
310
+
311
+ render(withStigmer(<ResultView view={imageView} />, stigmer));
312
+
313
+ const img = await screen.findByRole("img");
314
+ expect(img.getAttribute("src")).toBe("https://fresh/url.png");
315
+ // URL was resolved on demand from the stable storage key.
316
+ const req = getArtifactDownloadUrl.mock.calls[0][0];
317
+ expect(req.storageKey).toBe("artifacts/aex_1/toolcalls/tc.png");
318
+ expect(req.executionId).toBe("aex_1");
319
+ });
320
+
321
+ it("shows the preview head and defers the full fetch until 'View full output'", async () => {
322
+ const getArtifactContent = vi.fn().mockResolvedValue({
323
+ content: new TextEncoder().encode("THE FULL OUTPUT"),
324
+ contentType: "text/plain",
325
+ truncated: false,
326
+ });
327
+ const stigmer = {
328
+ agentExecution: { getArtifactContent, getArtifactDownloadUrl: vi.fn() },
329
+ } as unknown as Stigmer;
330
+
331
+ render(withStigmer(<ResultView view={textView} />, stigmer));
332
+
333
+ // Lazy: nothing fetched until the user expands.
334
+ expect(screen.getByText("short head…")).toBeTruthy();
335
+ expect(getArtifactContent).not.toHaveBeenCalled();
336
+
337
+ fireEvent.click(screen.getByText(/View full output/));
338
+
339
+ await waitFor(() => expect(screen.getByText("THE FULL OUTPUT")).toBeTruthy());
340
+ expect(getArtifactContent).toHaveBeenCalledTimes(1);
341
+ });
73
342
  });
74
343
 
75
344
  describe("summarizeResultView", () => {
@@ -89,6 +358,15 @@ describe("summarizeResultView", () => {
89
358
  expect(summarizeResultView({ type: "list", entries: [], count: 1 })).toBe("1 item");
90
359
  });
91
360
 
361
+ it("uses the file noun for a file-name search summary", () => {
362
+ expect(
363
+ summarizeResultView({ type: "search", kind: "files", matches: [], count: 3 }),
364
+ ).toBe("3 files");
365
+ expect(
366
+ summarizeResultView({ type: "search", kind: "files", matches: [], count: 1 }),
367
+ ).toBe("1 file");
368
+ });
369
+
92
370
  it("returns null when there is nothing to summarize", () => {
93
371
  expect(summarizeResultView({ type: "text", text: "hi" })).toBeNull();
94
372
  });
@@ -0,0 +1,180 @@
1
+ import { describe, it, expect, afterEach, vi } from "vitest";
2
+ import { render, screen, cleanup, act } from "@testing-library/react";
3
+ import { create } from "@bufbuild/protobuf";
4
+ import {
5
+ AgentMessageSchema,
6
+ ToolCallSchema,
7
+ type AgentMessage,
8
+ } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/message_pb";
9
+ import {
10
+ SubAgentExecutionSchema,
11
+ type SubAgentExecution,
12
+ } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/subagent_pb";
13
+ import {
14
+ PendingApprovalSchema,
15
+ type PendingApproval,
16
+ } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/approval_pb";
17
+ import {
18
+ MessageType,
19
+ SubAgentStatus,
20
+ ToolCallStatus,
21
+ } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
22
+ import { SubAgentSection } from "../SubAgentSection";
23
+ import { ApprovalContext, type ApprovalContextValue } from "../ApprovalContext";
24
+
25
+ afterEach(cleanup);
26
+
27
+ function gatedToolMessage(toolCallId: string): AgentMessage {
28
+ return create(AgentMessageSchema, {
29
+ type: MessageType.MESSAGE_AI,
30
+ content: "",
31
+ toolCalls: [
32
+ create(ToolCallSchema, {
33
+ id: toolCallId,
34
+ name: "delete_file",
35
+ status: ToolCallStatus.TOOL_CALL_WAITING_APPROVAL,
36
+ }),
37
+ ],
38
+ });
39
+ }
40
+
41
+ function subAgent(opts: {
42
+ status: SubAgentStatus;
43
+ messages?: AgentMessage[];
44
+ startedAt?: string;
45
+ completedAt?: string;
46
+ }): SubAgentExecution {
47
+ return create(SubAgentExecutionSchema, {
48
+ id: "sa-1",
49
+ name: "researcher",
50
+ status: opts.status,
51
+ messages: opts.messages ?? [],
52
+ startedAt: opts.startedAt ?? "",
53
+ completedAt: opts.completedAt ?? "",
54
+ });
55
+ }
56
+
57
+ /** The CollapsibleCard's own toggle is the first button under its role=group. */
58
+ function sectionExpanded(container: HTMLElement): boolean {
59
+ const toggle = container.querySelector('[role="group"] > button');
60
+ return toggle?.getAttribute("aria-expanded") === "true";
61
+ }
62
+
63
+ const emptyCtx: ApprovalContextValue = {
64
+ approvalsByToolCallId: new Map(),
65
+ submittingIds: new Set(),
66
+ errorsByToolCallId: new Map(),
67
+ };
68
+
69
+ describe("SubAgentSection auto-disclosure", () => {
70
+ it("auto-opens while the sub-agent is running", () => {
71
+ const { container } = render(
72
+ <ApprovalContext.Provider value={emptyCtx}>
73
+ <SubAgentSection
74
+ subAgentExecution={subAgent({ status: SubAgentStatus.SUB_AGENT_IN_PROGRESS })}
75
+ />
76
+ </ApprovalContext.Provider>,
77
+ );
78
+ expect(sectionExpanded(container)).toBe(true);
79
+ });
80
+
81
+ it("stays collapsed when the sub-agent has settled", () => {
82
+ const { container } = render(
83
+ <ApprovalContext.Provider value={emptyCtx}>
84
+ <SubAgentSection
85
+ subAgentExecution={subAgent({ status: SubAgentStatus.SUB_AGENT_COMPLETED })}
86
+ />
87
+ </ApprovalContext.Provider>,
88
+ );
89
+ expect(sectionExpanded(container)).toBe(false);
90
+ });
91
+
92
+ it("auto-opens a settled sub-agent when a nested tool awaits approval, and shows the gate inline", () => {
93
+ const approval: PendingApproval = create(PendingApprovalSchema, {
94
+ toolCallId: "tc-nested",
95
+ toolName: "delete_file",
96
+ argsPreview: '{"path":"/tmp/x"}',
97
+ });
98
+ const ctx: ApprovalContextValue = {
99
+ approvalsByToolCallId: new Map([["tc-nested", approval]]),
100
+ onSubmit: () => {},
101
+ submittingIds: new Set(),
102
+ errorsByToolCallId: new Map(),
103
+ };
104
+
105
+ const { container } = render(
106
+ <ApprovalContext.Provider value={ctx}>
107
+ <SubAgentSection
108
+ subAgentExecution={subAgent({
109
+ status: SubAgentStatus.SUB_AGENT_COMPLETED,
110
+ messages: [gatedToolMessage("tc-nested")],
111
+ })}
112
+ />
113
+ </ApprovalContext.Provider>,
114
+ );
115
+
116
+ expect(sectionExpanded(container)).toBe(true);
117
+ // The nested gate's actions surface inside the sub-agent section.
118
+ expect(screen.getByLabelText("Approve")).toBeTruthy();
119
+ });
120
+ });
121
+
122
+ // The Cursor SDK delivers sub-agent internals only at completion (nothing to
123
+ // stream mid-run), so the running card's honest live signal is a ticking
124
+ // elapsed counter from started_at. Completed cards keep the precise static
125
+ // duration (start -> completion), not a counter.
126
+ describe("SubAgentSection running duration", () => {
127
+ afterEach(() => {
128
+ vi.useRealTimers();
129
+ });
130
+
131
+ it("shows a ticking elapsed time while running", () => {
132
+ vi.useFakeTimers();
133
+ vi.setSystemTime(new Date("2026-07-02T12:00:42Z"));
134
+
135
+ render(
136
+ <ApprovalContext.Provider value={emptyCtx}>
137
+ <SubAgentSection
138
+ subAgentExecution={subAgent({
139
+ status: SubAgentStatus.SUB_AGENT_IN_PROGRESS,
140
+ startedAt: "2026-07-02T12:00:00Z",
141
+ })}
142
+ />
143
+ </ApprovalContext.Provider>,
144
+ );
145
+
146
+ expect(screen.getByText("42s")).toBeTruthy();
147
+
148
+ act(() => {
149
+ vi.advanceTimersByTime(20_000);
150
+ });
151
+ expect(screen.getByText("1m 2s")).toBeTruthy();
152
+ });
153
+
154
+ it("shows no counter while running when started_at is absent", () => {
155
+ render(
156
+ <ApprovalContext.Provider value={emptyCtx}>
157
+ <SubAgentSection
158
+ subAgentExecution={subAgent({ status: SubAgentStatus.SUB_AGENT_IN_PROGRESS })}
159
+ />
160
+ </ApprovalContext.Provider>,
161
+ );
162
+ expect(screen.queryByText(/^\d+s$/)).toBeNull();
163
+ });
164
+
165
+ it("shows the static duration (not a counter) once completed", () => {
166
+ render(
167
+ <ApprovalContext.Provider value={emptyCtx}>
168
+ <SubAgentSection
169
+ subAgentExecution={subAgent({
170
+ status: SubAgentStatus.SUB_AGENT_COMPLETED,
171
+ startedAt: "2026-07-02T12:00:00Z",
172
+ completedAt: "2026-07-02T12:01:14.500Z",
173
+ })}
174
+ />
175
+ </ApprovalContext.Provider>,
176
+ );
177
+ // formatDuration's m/s rendering for a completed 74.5s run.
178
+ expect(screen.getByText("1m 15s")).toBeTruthy();
179
+ });
180
+ });