@stigmer/react 3.0.9-dev.20260615153829 → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (439) hide show
  1. package/execution/ApprovalCard.d.ts +47 -3
  2. package/execution/ApprovalCard.d.ts.map +1 -1
  3. package/execution/ApprovalCard.js +138 -70
  4. package/execution/ApprovalCard.js.map +1 -1
  5. package/execution/ApprovalContext.d.ts +72 -0
  6. package/execution/ApprovalContext.d.ts.map +1 -0
  7. package/execution/ApprovalContext.js +41 -0
  8. package/execution/ApprovalContext.js.map +1 -0
  9. package/execution/ArtifactCard.d.ts.map +1 -1
  10. package/execution/ArtifactCard.js +3 -1
  11. package/execution/ArtifactCard.js.map +1 -1
  12. package/execution/ArtifactPreviewModal.d.ts.map +1 -1
  13. package/execution/ArtifactPreviewModal.js +5 -3
  14. package/execution/ArtifactPreviewModal.js.map +1 -1
  15. package/execution/EmptyChangeNotice.d.ts +38 -0
  16. package/execution/EmptyChangeNotice.d.ts.map +1 -0
  17. package/execution/EmptyChangeNotice.js +27 -0
  18. package/execution/EmptyChangeNotice.js.map +1 -0
  19. package/execution/FileChangesView.d.ts +81 -0
  20. package/execution/FileChangesView.d.ts.map +1 -0
  21. package/execution/FileChangesView.js +164 -0
  22. package/execution/FileChangesView.js.map +1 -0
  23. package/execution/FilePathLink.d.ts +24 -4
  24. package/execution/FilePathLink.d.ts.map +1 -1
  25. package/execution/FilePathLink.js +31 -9
  26. package/execution/FilePathLink.js.map +1 -1
  27. package/execution/FileReviewCard.d.ts +111 -0
  28. package/execution/FileReviewCard.d.ts.map +1 -0
  29. package/execution/FileReviewCard.js +558 -0
  30. package/execution/FileReviewCard.js.map +1 -0
  31. package/execution/FileReviewContext.d.ts +35 -0
  32. package/execution/FileReviewContext.d.ts.map +1 -0
  33. package/execution/FileReviewContext.js +23 -0
  34. package/execution/FileReviewContext.js.map +1 -0
  35. package/execution/FileReviewDock.d.ts +73 -0
  36. package/execution/FileReviewDock.d.ts.map +1 -0
  37. package/execution/FileReviewDock.js +65 -0
  38. package/execution/FileReviewDock.js.map +1 -0
  39. package/execution/McpToolDetail.d.ts.map +1 -1
  40. package/execution/McpToolDetail.js +38 -5
  41. package/execution/McpToolDetail.js.map +1 -1
  42. package/execution/MessageThread.d.ts +42 -3
  43. package/execution/MessageThread.d.ts.map +1 -1
  44. package/execution/MessageThread.js +263 -18
  45. package/execution/MessageThread.js.map +1 -1
  46. package/execution/PlanArtifactCard.d.ts.map +1 -1
  47. package/execution/PlanArtifactCard.js +3 -1
  48. package/execution/PlanArtifactCard.js.map +1 -1
  49. package/execution/PlanCompletionCard.d.ts +1 -1
  50. package/execution/PlanCompletionCard.js +1 -1
  51. package/execution/ResultView.d.ts +19 -1
  52. package/execution/ResultView.d.ts.map +1 -1
  53. package/execution/ResultView.js +141 -40
  54. package/execution/ResultView.js.map +1 -1
  55. package/execution/SubAgentSection.d.ts.map +1 -1
  56. package/execution/SubAgentSection.js +45 -6
  57. package/execution/SubAgentSection.js.map +1 -1
  58. package/execution/TerminalSession.d.ts +34 -0
  59. package/execution/TerminalSession.d.ts.map +1 -0
  60. package/execution/TerminalSession.js +34 -0
  61. package/execution/TerminalSession.js.map +1 -0
  62. package/execution/TodoCard.d.ts +53 -0
  63. package/execution/TodoCard.d.ts.map +1 -0
  64. package/execution/TodoCard.js +77 -0
  65. package/execution/TodoCard.js.map +1 -0
  66. package/execution/ToolArgsView.d.ts +7 -1
  67. package/execution/ToolArgsView.d.ts.map +1 -1
  68. package/execution/ToolArgsView.js +10 -9
  69. package/execution/ToolArgsView.js.map +1 -1
  70. package/execution/ToolCallDetail.d.ts +10 -1
  71. package/execution/ToolCallDetail.d.ts.map +1 -1
  72. package/execution/ToolCallDetail.js +66 -19
  73. package/execution/ToolCallDetail.js.map +1 -1
  74. package/execution/ToolCallGroup.d.ts +28 -18
  75. package/execution/ToolCallGroup.d.ts.map +1 -1
  76. package/execution/ToolCallGroup.js +39 -148
  77. package/execution/ToolCallGroup.js.map +1 -1
  78. package/execution/ToolCallItem.d.ts +36 -8
  79. package/execution/ToolCallItem.d.ts.map +1 -1
  80. package/execution/ToolCallItem.js +140 -21
  81. package/execution/ToolCallItem.js.map +1 -1
  82. package/execution/ToolRunGroup.d.ts +48 -0
  83. package/execution/ToolRunGroup.d.ts.map +1 -0
  84. package/execution/ToolRunGroup.js +168 -0
  85. package/execution/ToolRunGroup.js.map +1 -0
  86. package/execution/approval-provenance.d.ts +2 -0
  87. package/execution/approval-provenance.d.ts.map +1 -0
  88. package/execution/approval-provenance.js +6 -0
  89. package/execution/approval-provenance.js.map +1 -0
  90. package/execution/artifact-utils.d.ts +0 -10
  91. package/execution/artifact-utils.d.ts.map +1 -1
  92. package/execution/artifact-utils.js +0 -17
  93. package/execution/artifact-utils.js.map +1 -1
  94. package/execution/execution-phases.d.ts +1 -6
  95. package/execution/execution-phases.d.ts.map +1 -1
  96. package/execution/execution-phases.js +4 -14
  97. package/execution/execution-phases.js.map +1 -1
  98. package/execution/file-path-resolver.d.ts +28 -0
  99. package/execution/file-path-resolver.d.ts.map +1 -1
  100. package/execution/file-path-resolver.js +24 -0
  101. package/execution/file-path-resolver.js.map +1 -1
  102. package/execution/file-review-status.d.ts +127 -0
  103. package/execution/file-review-status.d.ts.map +1 -0
  104. package/execution/file-review-status.js +135 -0
  105. package/execution/file-review-status.js.map +1 -0
  106. package/execution/index.d.ts +39 -5
  107. package/execution/index.d.ts.map +1 -1
  108. package/execution/index.js +23 -3
  109. package/execution/index.js.map +1 -1
  110. package/execution/segment-tool-calls.d.ts +46 -0
  111. package/execution/segment-tool-calls.d.ts.map +1 -0
  112. package/execution/segment-tool-calls.js +71 -0
  113. package/execution/segment-tool-calls.js.map +1 -0
  114. package/execution/tool-categories.d.ts +62 -1
  115. package/execution/tool-categories.d.ts.map +1 -1
  116. package/execution/tool-categories.js +96 -2
  117. package/execution/tool-categories.js.map +1 -1
  118. package/execution/tool-presenter.d.ts +38 -2
  119. package/execution/tool-presenter.d.ts.map +1 -1
  120. package/execution/tool-presenter.js +23 -1
  121. package/execution/tool-presenter.js.map +1 -1
  122. package/execution/tool-rendering-primitives.d.ts.map +1 -1
  123. package/execution/tool-rendering-primitives.js +3 -2
  124. package/execution/tool-rendering-primitives.js.map +1 -1
  125. package/execution/useArtifactDownload.d.ts +44 -0
  126. package/execution/useArtifactDownload.d.ts.map +1 -0
  127. package/execution/useArtifactDownload.js +80 -0
  128. package/execution/useArtifactDownload.js.map +1 -0
  129. package/execution/useArtifactDownloadUrl.d.ts +67 -0
  130. package/execution/useArtifactDownloadUrl.d.ts.map +1 -0
  131. package/execution/useArtifactDownloadUrl.js +59 -0
  132. package/execution/useArtifactDownloadUrl.js.map +1 -0
  133. package/execution/useFileChangeContent.d.ts +61 -0
  134. package/execution/useFileChangeContent.d.ts.map +1 -0
  135. package/execution/useFileChangeContent.js +89 -0
  136. package/execution/useFileChangeContent.js.map +1 -0
  137. package/execution/useFileReview.d.ts +89 -0
  138. package/execution/useFileReview.d.ts.map +1 -0
  139. package/execution/useFileReview.js +106 -0
  140. package/execution/useFileReview.js.map +1 -0
  141. package/execution/useSubmitApproval.d.ts +17 -2
  142. package/execution/useSubmitApproval.d.ts.map +1 -1
  143. package/execution/useSubmitApproval.js +27 -23
  144. package/execution/useSubmitApproval.js.map +1 -1
  145. package/execution/useToolOutputContent.d.ts +44 -0
  146. package/execution/useToolOutputContent.d.ts.map +1 -0
  147. package/execution/useToolOutputContent.js +31 -0
  148. package/execution/useToolOutputContent.js.map +1 -0
  149. package/index.d.ts +6 -6
  150. package/index.d.ts.map +1 -1
  151. package/index.js +3 -3
  152. package/index.js.map +1 -1
  153. package/internal/ApprovalPeekBar.d.ts +23 -0
  154. package/internal/ApprovalPeekBar.d.ts.map +1 -0
  155. package/internal/ApprovalPeekBar.js +25 -0
  156. package/internal/ApprovalPeekBar.js.map +1 -0
  157. package/internal/BoundedContent.d.ts +49 -0
  158. package/internal/BoundedContent.d.ts.map +1 -0
  159. package/internal/BoundedContent.js +50 -0
  160. package/internal/BoundedContent.js.map +1 -0
  161. package/internal/DecisionButton.d.ts +47 -0
  162. package/internal/DecisionButton.d.ts.map +1 -0
  163. package/internal/DecisionButton.js +42 -0
  164. package/internal/DecisionButton.js.map +1 -0
  165. package/internal/InCardDecisionError.d.ts +31 -0
  166. package/internal/InCardDecisionError.d.ts.map +1 -0
  167. package/internal/InCardDecisionError.js +23 -0
  168. package/internal/InCardDecisionError.js.map +1 -0
  169. package/internal/RevealToggle.d.ts +36 -0
  170. package/internal/RevealToggle.d.ts.map +1 -0
  171. package/internal/RevealToggle.js +32 -0
  172. package/internal/RevealToggle.js.map +1 -0
  173. package/internal/VirtualizedThread.d.ts +7 -1
  174. package/internal/VirtualizedThread.d.ts.map +1 -1
  175. package/internal/VirtualizedThread.js +7 -3
  176. package/internal/VirtualizedThread.js.map +1 -1
  177. package/internal/code-highlight.d.ts +24 -0
  178. package/internal/code-highlight.d.ts.map +1 -0
  179. package/internal/code-highlight.js +115 -0
  180. package/internal/code-highlight.js.map +1 -0
  181. package/internal/markdown-components.d.ts.map +1 -1
  182. package/internal/markdown-components.js +13 -2
  183. package/internal/markdown-components.js.map +1 -1
  184. package/internal/useAutoDisclosure.d.ts +34 -0
  185. package/internal/useAutoDisclosure.d.ts.map +1 -0
  186. package/internal/useAutoDisclosure.js +53 -0
  187. package/internal/useAutoDisclosure.js.map +1 -0
  188. package/internal/useElapsedSince.d.ts +30 -0
  189. package/internal/useElapsedSince.d.ts.map +1 -0
  190. package/internal/useElapsedSince.js +70 -0
  191. package/internal/useElapsedSince.js.map +1 -0
  192. package/internal/useIsOverflowing.d.ts +30 -0
  193. package/internal/useIsOverflowing.d.ts.map +1 -0
  194. package/internal/useIsOverflowing.js +47 -0
  195. package/internal/useIsOverflowing.js.map +1 -0
  196. package/internal/useIsTextTruncated.d.ts +24 -0
  197. package/internal/useIsTextTruncated.d.ts.map +1 -0
  198. package/internal/useIsTextTruncated.js +41 -0
  199. package/internal/useIsTextTruncated.js.map +1 -0
  200. package/internal/useKeyedSubmission.d.ts +49 -0
  201. package/internal/useKeyedSubmission.d.ts.map +1 -0
  202. package/internal/useKeyedSubmission.js +67 -0
  203. package/internal/useKeyedSubmission.js.map +1 -0
  204. package/package.json +7 -4
  205. package/session/SessionViewer.d.ts +2 -2
  206. package/session/SessionViewer.d.ts.map +1 -1
  207. package/session/SessionViewer.js +10 -4
  208. package/session/SessionViewer.js.map +1 -1
  209. package/session/index.d.ts +2 -0
  210. package/session/index.d.ts.map +1 -1
  211. package/session/index.js +1 -0
  212. package/session/index.js.map +1 -1
  213. package/session/inspector/ChangesTab.d.ts +8 -5
  214. package/session/inspector/ChangesTab.d.ts.map +1 -1
  215. package/session/inspector/ChangesTab.js +11 -8
  216. package/session/inspector/ChangesTab.js.map +1 -1
  217. package/session/inspector/SessionInspector.d.ts +4 -4
  218. package/session/inspector/SessionInspector.d.ts.map +1 -1
  219. package/session/inspector/SessionInspector.js +4 -4
  220. package/session/inspector/SessionInspector.js.map +1 -1
  221. package/session/inspector/index.d.ts +0 -2
  222. package/session/inspector/index.d.ts.map +1 -1
  223. package/session/inspector/index.js +0 -1
  224. package/session/inspector/index.js.map +1 -1
  225. package/session/inspector/useSessionInspector.d.ts +8 -4
  226. package/session/inspector/useSessionInspector.d.ts.map +1 -1
  227. package/session/inspector/useSessionInspector.js +27 -20
  228. package/session/inspector/useSessionInspector.js.map +1 -1
  229. package/session/useSessionConversation.d.ts +37 -3
  230. package/session/useSessionConversation.d.ts.map +1 -1
  231. package/session/useSessionConversation.js +18 -2
  232. package/session/useSessionConversation.js.map +1 -1
  233. package/session/useSessionFileChanges.d.ts +74 -0
  234. package/session/useSessionFileChanges.d.ts.map +1 -0
  235. package/session/useSessionFileChanges.js +178 -0
  236. package/session/useSessionFileChanges.js.map +1 -0
  237. package/src/__tests__/styles-border-layer-invariant.test.ts +105 -0
  238. package/src/execution/ApprovalCard.tsx +368 -230
  239. package/src/execution/ApprovalContext.ts +109 -0
  240. package/src/execution/ArtifactCard.tsx +10 -6
  241. package/src/execution/ArtifactPreviewModal.tsx +12 -6
  242. package/src/execution/EmptyChangeNotice.tsx +62 -0
  243. package/src/execution/FileChangesView.tsx +383 -0
  244. package/src/execution/FilePathLink.tsx +65 -12
  245. package/src/execution/FileReviewCard.tsx +1118 -0
  246. package/src/execution/FileReviewContext.ts +53 -0
  247. package/src/execution/FileReviewDock.tsx +157 -0
  248. package/src/execution/McpToolDetail.tsx +98 -32
  249. package/src/execution/MessageThread.tsx +374 -9
  250. package/src/execution/PlanArtifactCard.tsx +9 -6
  251. package/src/execution/PlanCompletionCard.tsx +1 -1
  252. package/src/execution/ResultView.tsx +333 -162
  253. package/src/execution/SubAgentSection.tsx +58 -11
  254. package/src/execution/TerminalSession.tsx +91 -0
  255. package/src/execution/TodoCard.tsx +186 -0
  256. package/src/execution/ToolArgsView.tsx +28 -17
  257. package/src/execution/ToolCallDetail.tsx +137 -26
  258. package/src/execution/ToolCallGroup.tsx +68 -296
  259. package/src/execution/ToolCallItem.tsx +265 -67
  260. package/src/execution/ToolRunGroup.tsx +302 -0
  261. package/src/execution/__tests__/ApprovalCard.test.tsx +395 -5
  262. package/src/execution/__tests__/ArtifactContentRenderer.test.tsx +15 -0
  263. package/src/execution/__tests__/ArtifactPreviewModal.test.tsx +0 -1
  264. package/src/execution/__tests__/EmptyChangeNotice.test.tsx +24 -0
  265. package/src/execution/__tests__/FileChangesView.test.tsx +296 -0
  266. package/src/execution/__tests__/FilePathLink.test.tsx +92 -0
  267. package/src/execution/__tests__/FileReviewCard.test.tsx +1044 -0
  268. package/src/execution/__tests__/FileReviewDock.test.tsx +134 -0
  269. package/src/execution/__tests__/McpToolDetail.test.tsx +79 -0
  270. package/src/execution/__tests__/MessageThread.test.tsx +324 -0
  271. package/src/execution/__tests__/PlanArtifactCard.test.tsx +37 -16
  272. package/src/execution/__tests__/ResultView.test.tsx +280 -2
  273. package/src/execution/__tests__/SubAgentSection.test.tsx +180 -0
  274. package/src/execution/__tests__/TerminalSession.test.tsx +72 -0
  275. package/src/execution/__tests__/TodoCard.test.tsx +102 -0
  276. package/src/execution/__tests__/ToolArgsView.test.tsx +48 -0
  277. package/src/execution/__tests__/ToolCallDetail.test.tsx +195 -0
  278. package/src/execution/__tests__/ToolCallGroup.test.tsx +132 -0
  279. package/src/execution/__tests__/ToolCallItem.test.tsx +521 -0
  280. package/src/execution/__tests__/ToolRunGroup.test.tsx +137 -0
  281. package/src/execution/__tests__/blanked-narration.test.tsx +149 -0
  282. package/src/execution/__tests__/buildThreadItems-approvals.test.ts +304 -0
  283. package/src/execution/__tests__/buildThreadItems-file-review.test.ts +331 -0
  284. package/src/execution/__tests__/file-path-resolver.test.ts +44 -0
  285. package/src/execution/__tests__/file-review-status.test.ts +343 -0
  286. package/src/execution/__tests__/message-entry.test.tsx +16 -0
  287. package/src/execution/__tests__/segment-tool-calls.test.ts +124 -0
  288. package/src/execution/__tests__/subagent-file-review-badge.test.tsx +273 -0
  289. package/src/execution/__tests__/thread-keys.test.ts +195 -0
  290. package/src/execution/__tests__/tool-presenter.test.tsx +95 -0
  291. package/src/execution/__tests__/useArtifactDownload.test.tsx +67 -0
  292. package/src/execution/__tests__/useArtifactDownloadUrl.test.tsx +51 -0
  293. package/src/execution/__tests__/useFileChangeContent.test.tsx +240 -0
  294. package/src/execution/__tests__/useFileReview.test.tsx +236 -0
  295. package/src/execution/__tests__/useSubmitApproval.test.tsx +165 -0
  296. package/src/execution/__tests__/useToolOutputContent.test.tsx +82 -0
  297. package/src/execution/approval-provenance.ts +8 -0
  298. package/src/execution/artifact-utils.ts +0 -18
  299. package/src/execution/execution-phases.ts +4 -16
  300. package/src/execution/file-path-resolver.ts +38 -0
  301. package/src/execution/file-review-status.ts +219 -0
  302. package/src/execution/index.ts +84 -4
  303. package/src/execution/segment-tool-calls.ts +101 -0
  304. package/src/execution/tool-categories.ts +128 -1
  305. package/src/execution/tool-presenter.ts +58 -3
  306. package/src/execution/tool-rendering-primitives.tsx +11 -14
  307. package/src/execution/useArtifactDownload.ts +109 -0
  308. package/src/execution/useArtifactDownloadUrl.ts +116 -0
  309. package/src/execution/useFileChangeContent.ts +149 -0
  310. package/src/execution/useFileReview.ts +197 -0
  311. package/src/execution/useSubmitApproval.ts +49 -27
  312. package/src/execution/useToolOutputContent.ts +70 -0
  313. package/src/index.ts +60 -1
  314. package/src/internal/ApprovalPeekBar.tsx +73 -0
  315. package/src/internal/BoundedContent.tsx +97 -0
  316. package/src/internal/DecisionButton.tsx +127 -0
  317. package/src/internal/InCardDecisionError.tsx +47 -0
  318. package/src/internal/RevealToggle.tsx +91 -0
  319. package/src/internal/VirtualizedThread.tsx +28 -2
  320. package/src/internal/__tests__/ApprovalPeekBar.test.tsx +34 -0
  321. package/src/internal/__tests__/BoundedContent.test.tsx +92 -0
  322. package/src/internal/__tests__/DecisionButton.test.tsx +107 -0
  323. package/src/internal/__tests__/RevealToggle.test.tsx +61 -0
  324. package/src/internal/__tests__/code-highlight.test.tsx +59 -0
  325. package/src/internal/__tests__/markdown-components.test.tsx +68 -2
  326. package/src/internal/__tests__/useAutoDisclosure.test.ts +74 -0
  327. package/src/internal/__tests__/useElapsedSince.test.tsx +68 -0
  328. package/src/internal/__tests__/useIsOverflowing.test.ts +36 -0
  329. package/src/internal/__tests__/useKeyedSubmission.test.tsx +126 -0
  330. package/src/internal/code-highlight.tsx +120 -0
  331. package/src/internal/markdown-components.tsx +20 -3
  332. package/src/internal/useAutoDisclosure.ts +58 -0
  333. package/src/internal/useElapsedSince.ts +72 -0
  334. package/src/internal/useIsOverflowing.ts +61 -0
  335. package/src/internal/useIsTextTruncated.ts +55 -0
  336. package/src/internal/useKeyedSubmission.ts +101 -0
  337. package/src/session/SessionViewer.tsx +21 -4
  338. package/src/session/__tests__/SessionViewer-fileReviewDock.test.tsx +198 -0
  339. package/src/session/__tests__/audienceWiring.test.tsx +4 -0
  340. package/src/session/__tests__/useSessionFileChanges.test.ts +303 -0
  341. package/src/session/index.ts +3 -0
  342. package/src/session/inspector/ChangesTab.tsx +20 -17
  343. package/src/session/inspector/SessionInspector.tsx +12 -10
  344. package/src/session/inspector/__tests__/ArtifactsTab.test.tsx +0 -1
  345. package/src/session/inspector/__tests__/ChangesTab.test.tsx +38 -0
  346. package/src/session/inspector/__tests__/useSessionInspector.test.ts +46 -61
  347. package/src/session/inspector/index.ts +0 -3
  348. package/src/session/inspector/useSessionInspector.ts +27 -20
  349. package/src/session/useSessionConversation.ts +80 -3
  350. package/src/session/useSessionFileChanges.ts +207 -0
  351. package/src/styles.css +123 -10
  352. package/src/version-history/DiffViewer.tsx +10 -7
  353. package/src/version-history/UnifiedDiffView.tsx +66 -0
  354. package/src/version-history/__tests__/DiffViewer.test.tsx +57 -0
  355. package/src/version-history/__tests__/UnifiedDiffView.test.tsx +50 -0
  356. package/src/version-history/__tests__/computeDiff.test.ts +35 -0
  357. package/src/version-history/__tests__/parseUnifiedDiff.test.ts +82 -0
  358. package/src/version-history/computeDiff.ts +6 -37
  359. package/src/version-history/diff-hunks.ts +63 -0
  360. package/src/version-history/index.ts +1 -0
  361. package/src/version-history/parseUnifiedDiff.ts +34 -0
  362. package/src/workflow/WorkflowExecutionApprovalCard.tsx +49 -24
  363. package/src/workflow/WorkflowExecutionTimeline.tsx +15 -4
  364. package/src/workflow/WorkflowExecutionTimelineEvent.tsx +8 -3
  365. package/src/workflow/WorkflowExecutionViewer.tsx +72 -28
  366. package/src/workflow/WorkflowFileReviewList.tsx +152 -0
  367. package/src/workflow/WorkflowTaskApprovalCard.tsx +37 -85
  368. package/src/workflow/__tests__/ExecutionInspector.test.tsx +30 -2
  369. package/src/workflow/__tests__/WorkflowExecutionApprovalCard.test.tsx +129 -0
  370. package/src/workflow/__tests__/WorkflowFileReviewList.test.tsx +129 -0
  371. package/src/workflow/__tests__/WorkflowTaskApprovalCard.test.tsx +43 -11
  372. package/src/workflow/__tests__/useWorkflowExecutionActions.test.tsx +276 -0
  373. package/src/workflow/execution-inspector/ExecutionInspector.tsx +42 -18
  374. package/src/workflow/index.ts +5 -0
  375. package/src/workflow/useWorkflowExecutionActions.ts +177 -29
  376. package/styles.css +1 -1
  377. package/version-history/DiffViewer.d.ts.map +1 -1
  378. package/version-history/DiffViewer.js +9 -3
  379. package/version-history/DiffViewer.js.map +1 -1
  380. package/version-history/UnifiedDiffView.d.ts +29 -0
  381. package/version-history/UnifiedDiffView.d.ts.map +1 -0
  382. package/version-history/UnifiedDiffView.js +36 -0
  383. package/version-history/UnifiedDiffView.js.map +1 -0
  384. package/version-history/computeDiff.d.ts +3 -1
  385. package/version-history/computeDiff.d.ts.map +1 -1
  386. package/version-history/computeDiff.js +5 -35
  387. package/version-history/computeDiff.js.map +1 -1
  388. package/version-history/diff-hunks.d.ts +21 -0
  389. package/version-history/diff-hunks.d.ts.map +1 -0
  390. package/version-history/diff-hunks.js +58 -0
  391. package/version-history/diff-hunks.js.map +1 -0
  392. package/version-history/index.d.ts +1 -0
  393. package/version-history/index.d.ts.map +1 -1
  394. package/version-history/index.js +1 -0
  395. package/version-history/index.js.map +1 -1
  396. package/version-history/parseUnifiedDiff.d.ts +22 -0
  397. package/version-history/parseUnifiedDiff.d.ts.map +1 -0
  398. package/version-history/parseUnifiedDiff.js +34 -0
  399. package/version-history/parseUnifiedDiff.js.map +1 -0
  400. package/workflow/WorkflowExecutionApprovalCard.d.ts +8 -1
  401. package/workflow/WorkflowExecutionApprovalCard.d.ts.map +1 -1
  402. package/workflow/WorkflowExecutionApprovalCard.js +17 -3
  403. package/workflow/WorkflowExecutionApprovalCard.js.map +1 -1
  404. package/workflow/WorkflowExecutionTimeline.d.ts +11 -2
  405. package/workflow/WorkflowExecutionTimeline.d.ts.map +1 -1
  406. package/workflow/WorkflowExecutionTimeline.js +2 -2
  407. package/workflow/WorkflowExecutionTimeline.js.map +1 -1
  408. package/workflow/WorkflowExecutionTimelineEvent.d.ts +4 -1
  409. package/workflow/WorkflowExecutionTimelineEvent.d.ts.map +1 -1
  410. package/workflow/WorkflowExecutionTimelineEvent.js +2 -2
  411. package/workflow/WorkflowExecutionTimelineEvent.js.map +1 -1
  412. package/workflow/WorkflowExecutionViewer.d.ts.map +1 -1
  413. package/workflow/WorkflowExecutionViewer.js +18 -10
  414. package/workflow/WorkflowExecutionViewer.js.map +1 -1
  415. package/workflow/WorkflowFileReviewList.d.ts +46 -0
  416. package/workflow/WorkflowFileReviewList.d.ts.map +1 -0
  417. package/workflow/WorkflowFileReviewList.js +48 -0
  418. package/workflow/WorkflowFileReviewList.js.map +1 -0
  419. package/workflow/WorkflowTaskApprovalCard.d.ts +8 -1
  420. package/workflow/WorkflowTaskApprovalCard.d.ts.map +1 -1
  421. package/workflow/WorkflowTaskApprovalCard.js +12 -19
  422. package/workflow/WorkflowTaskApprovalCard.js.map +1 -1
  423. package/workflow/execution-inspector/ExecutionInspector.d.ts +21 -4
  424. package/workflow/execution-inspector/ExecutionInspector.d.ts.map +1 -1
  425. package/workflow/execution-inspector/ExecutionInspector.js +6 -3
  426. package/workflow/execution-inspector/ExecutionInspector.js.map +1 -1
  427. package/workflow/index.d.ts +1 -0
  428. package/workflow/index.d.ts.map +1 -1
  429. package/workflow/index.js +1 -0
  430. package/workflow/index.js.map +1 -1
  431. package/workflow/useWorkflowExecutionActions.d.ts +62 -4
  432. package/workflow/useWorkflowExecutionActions.d.ts.map +1 -1
  433. package/workflow/useWorkflowExecutionActions.js +94 -18
  434. package/workflow/useWorkflowExecutionActions.js.map +1 -1
  435. package/session/inspector/PlanTab.d.ts +0 -15
  436. package/session/inspector/PlanTab.d.ts.map +0 -1
  437. package/session/inspector/PlanTab.js +0 -19
  438. package/session/inspector/PlanTab.js.map +0 -1
  439. package/src/session/inspector/PlanTab.tsx +0 -31
@@ -0,0 +1,58 @@
1
+ "use client";
2
+
3
+ import { useEffect, useRef, useState } from "react";
4
+
5
+ /**
6
+ * Disclosure state machine for progressively-revealed thread sections
7
+ * (tool-call groups, individual tool rows, sub-agent cards).
8
+ *
9
+ * The challenge it solves: a section should open and close *on its own* as
10
+ * the agent works (open while a tool runs, settle closed when it finishes),
11
+ * yet a user's explicit click must win and never be stomped by the next
12
+ * stream frame. Three components hand-rolled this same "auto unless the user
13
+ * intervened" logic; this hook is the single source of that shape.
14
+ *
15
+ * `autoOpen` is the component's live opinion of whether the section *should*
16
+ * be open right now (e.g. `isRunning || hasPendingApproval`). While the user
17
+ * has not toggled, `open` tracks `autoOpen`. Once the user toggles, their
18
+ * choice sticks for the lifetime of the component — the auto signal is
19
+ * ignored from then on, so the section never reopens or recollapses under
20
+ * them mid-stream.
21
+ *
22
+ * @param autoOpen Whether the section should auto-open given current state.
23
+ * @param options.initialOpen First-render open state. Defaults to `autoOpen`.
24
+ * Use it to honour a caller-provided `defaultExpanded` before the first
25
+ * effect runs.
26
+ * @returns A `[open, toggle]` tuple. `toggle` flips the state and marks it
27
+ * user-controlled.
28
+ *
29
+ * @example
30
+ * ```tsx
31
+ * const [open, toggle] = useAutoDisclosure(isRunning || hasPendingApproval);
32
+ * <button aria-expanded={open} onClick={toggle}>…</button>
33
+ * ```
34
+ */
35
+ export function useAutoDisclosure(
36
+ autoOpen: boolean,
37
+ options?: { readonly initialOpen?: boolean },
38
+ ): readonly [open: boolean, toggle: () => void] {
39
+ const [open, setOpen] = useState(options?.initialOpen ?? autoOpen);
40
+ const userToggledRef = useRef(false);
41
+ const prevAutoOpenRef = useRef(autoOpen);
42
+
43
+ useEffect(() => {
44
+ // Sync only on a genuine transition of the auto signal — never on mount,
45
+ // so `initialOpen` is honoured, and never against the user's choice.
46
+ if (prevAutoOpenRef.current === autoOpen) return;
47
+ prevAutoOpenRef.current = autoOpen;
48
+ if (userToggledRef.current) return;
49
+ setOpen(autoOpen);
50
+ }, [autoOpen]);
51
+
52
+ const toggle = useRef(() => {
53
+ userToggledRef.current = true;
54
+ setOpen((v) => !v);
55
+ }).current;
56
+
57
+ return [open, toggle];
58
+ }
@@ -0,0 +1,72 @@
1
+ "use client";
2
+
3
+ import { useEffect, useMemo, useState } from "react";
4
+
5
+ /**
6
+ * Live elapsed time since an ISO 8601 timestamp, re-rendering once per second.
7
+ *
8
+ * The shape behind every "how long has this been going" affordance in the
9
+ * thread: an approval gate waiting on a human, a sub-agent card whose nested
10
+ * activity cannot be streamed (the Cursor SDK returns sub-agent internals only
11
+ * at completion), a long-running tool. A ticking duration is the honest signal
12
+ * that work is progressing when there is nothing else to show — as opposed to
13
+ * fabricating intermediate state.
14
+ *
15
+ * Returns elapsed milliseconds (clamped to >= 0 against client/server clock
16
+ * skew), or `null` when the timestamp is absent or unparseable — callers
17
+ * render nothing rather than a bogus "0s". The interval is scoped to the
18
+ * component instance and torn down on unmount, so a thread full of settled
19
+ * cards costs zero timers.
20
+ *
21
+ * @param startedAt ISO 8601 timestamp the clock counts from (e.g.
22
+ * `ToolCall.approval_requested_at`, `SubAgentExecution.started_at`).
23
+ */
24
+ export function useElapsedSince(startedAt: string): number | null {
25
+ const startMs = useMemo(() => {
26
+ if (!startedAt) return null;
27
+ const t = new Date(startedAt).getTime();
28
+ return Number.isNaN(t) ? null : t;
29
+ }, [startedAt]);
30
+
31
+ const [elapsed, setElapsed] = useState<number | null>(null);
32
+
33
+ useEffect(() => {
34
+ if (startMs === null) {
35
+ setElapsed(null);
36
+ return;
37
+ }
38
+ setElapsed(Math.max(0, Date.now() - startMs));
39
+ const id = setInterval(() => {
40
+ setElapsed(Math.max(0, Date.now() - startMs));
41
+ }, 1000);
42
+ return () => clearInterval(id);
43
+ }, [startMs]);
44
+
45
+ return startMs === null ? null : elapsed;
46
+ }
47
+
48
+ /**
49
+ * Format an elapsed duration for the live tickers driven by
50
+ * {@link useElapsedSince}: `just now`, `42s`, `3m 12s`, `1h 5m`.
51
+ *
52
+ * Second-granularity (unlike ToolCallDetail's `formatDuration`, which keeps
53
+ * millisecond precision for completed calls) because a live counter that
54
+ * flickers sub-second digits reads as noise, not progress.
55
+ */
56
+ export function formatElapsed(ms: number): string {
57
+ if (ms < 1000) return "just now";
58
+ const seconds = Math.floor(ms / 1000);
59
+ if (seconds < 60) return `${seconds}s`;
60
+ const minutes = Math.floor(seconds / 60);
61
+ const remainingSeconds = seconds % 60;
62
+ if (minutes < 60) {
63
+ return remainingSeconds > 0
64
+ ? `${minutes}m ${remainingSeconds}s`
65
+ : `${minutes}m`;
66
+ }
67
+ const hours = Math.floor(minutes / 60);
68
+ const remainingMinutes = minutes % 60;
69
+ return remainingMinutes > 0
70
+ ? `${hours}h ${remainingMinutes}m`
71
+ : `${hours}h`;
72
+ }
@@ -0,0 +1,61 @@
1
+ import { useLayoutEffect, useRef, useState } from "react";
2
+
3
+ export interface UseIsOverflowingReturn<T extends HTMLElement> {
4
+ /** Attach to the element whose vertical overflow should be tracked. */
5
+ readonly ref: React.RefObject<T | null>;
6
+ /** `true` when the element's content is clipped by a vertical clamp (e.g. `overflow-hidden` + `max-h-*`). */
7
+ readonly isOverflowing: boolean;
8
+ }
9
+
10
+ /**
11
+ * Tracks whether a vertically-clamped element (e.g. one styled with
12
+ * `overflow-hidden max-h-*`) is actually clipping its content at the current
13
+ * height.
14
+ *
15
+ * The vertical analog of {@link useIsTextTruncated}: it measures
16
+ * `scrollHeight > clientHeight` synchronously via `useLayoutEffect` (so the
17
+ * first value is correct before paint — no flash) and keeps it in sync with
18
+ * content-size changes via a `ResizeObserver`. The state setter compares before
19
+ * writing, so a stable layout never triggers a re-render loop.
20
+ *
21
+ * Pass `enabled: false` to opt out entirely — the observer is not attached and
22
+ * `isOverflowing` stays `false`. Callers gate on this so only the elements that
23
+ * consume the signal pay for the measurement.
24
+ *
25
+ * Note: under a layout-free test environment (`happy-dom`) `scrollHeight` and
26
+ * `clientHeight` are both `0`, so this reports `false`; overflow-driven UI is
27
+ * therefore verified in the e2e (real-browser) layer, mirroring the split the
28
+ * rest of the SDK already documents.
29
+ *
30
+ * @internal
31
+ */
32
+ export function useIsOverflowing<T extends HTMLElement = HTMLElement>(
33
+ enabled: boolean,
34
+ ): UseIsOverflowingReturn<T> {
35
+ const ref = useRef<T | null>(null);
36
+ const [isOverflowing, setIsOverflowing] = useState(false);
37
+
38
+ useLayoutEffect(() => {
39
+ if (!enabled) {
40
+ // Reset so an element that stops consuming the signal doesn't keep a stale true.
41
+ setIsOverflowing((prev) => (prev ? false : prev));
42
+ return;
43
+ }
44
+
45
+ const el = ref.current;
46
+ if (!el) return;
47
+
48
+ const measure = () => {
49
+ const next = el.scrollHeight > el.clientHeight + 1;
50
+ setIsOverflowing((prev) => (prev === next ? prev : next));
51
+ };
52
+
53
+ measure();
54
+
55
+ const ro = new ResizeObserver(measure);
56
+ ro.observe(el);
57
+ return () => ro.disconnect();
58
+ }, [enabled]);
59
+
60
+ return { ref, isOverflowing };
61
+ }
@@ -0,0 +1,55 @@
1
+ import { useLayoutEffect, useRef, useState } from "react";
2
+
3
+ export interface UseIsTextTruncatedReturn<T extends HTMLElement> {
4
+ /** Attach to the element whose horizontal overflow should be tracked. */
5
+ readonly ref: React.RefObject<T | null>;
6
+ /** `true` when the element's content is clipped by `overflow` (e.g. `truncate`). */
7
+ readonly isTruncated: boolean;
8
+ }
9
+
10
+ /**
11
+ * Tracks whether a single-line, overflow-clipped element (e.g. one styled with
12
+ * Tailwind's `truncate`) is actually truncating its text at the current width.
13
+ *
14
+ * The horizontal analog of {@link useScrollShadows}: it measures
15
+ * `scrollWidth > clientWidth` synchronously via `useLayoutEffect` (so the first
16
+ * value is correct before paint — no flash) and keeps it in sync with width
17
+ * changes via a `ResizeObserver`. The state setter compares before writing, so a
18
+ * stable layout never triggers a re-render loop.
19
+ *
20
+ * Pass `enabled: false` to opt a row out entirely — the observer is not attached
21
+ * and `isTruncated` stays `false`. Callers gate on this so only the rows that
22
+ * consume the signal pay for the measurement.
23
+ *
24
+ * @internal
25
+ */
26
+ export function useIsTextTruncated<T extends HTMLElement = HTMLElement>(
27
+ enabled: boolean,
28
+ ): UseIsTextTruncatedReturn<T> {
29
+ const ref = useRef<T | null>(null);
30
+ const [isTruncated, setIsTruncated] = useState(false);
31
+
32
+ useLayoutEffect(() => {
33
+ if (!enabled) {
34
+ // Reset so a row that stops consuming the signal doesn't keep a stale true.
35
+ setIsTruncated((prev) => (prev ? false : prev));
36
+ return;
37
+ }
38
+
39
+ const el = ref.current;
40
+ if (!el) return;
41
+
42
+ const measure = () => {
43
+ const next = el.scrollWidth > el.clientWidth + 1;
44
+ setIsTruncated((prev) => (prev === next ? prev : next));
45
+ };
46
+
47
+ measure();
48
+
49
+ const ro = new ResizeObserver(measure);
50
+ ro.observe(el);
51
+ return () => ro.disconnect();
52
+ }, [enabled]);
53
+
54
+ return { ref, isTruncated };
55
+ }
@@ -0,0 +1,101 @@
1
+ "use client";
2
+
3
+ import { useCallback, useMemo, useState } from "react";
4
+ import { toError } from "./toError";
5
+
6
+ /** A stable empty set so an idle hook keeps a constant `submittingKeys` ref. */
7
+ const NO_KEYS: ReadonlySet<string> = new Set();
8
+ /** A stable empty map so an error-free hook keeps a constant `errorsByKey` ref. */
9
+ const NO_ERRORS: ReadonlyMap<string, Error> = new Map();
10
+
11
+ /** Return value of {@link useKeyedSubmission}. */
12
+ export interface KeyedSubmission<T> {
13
+ /**
14
+ * Keys whose operation is currently in flight. A consumer reads
15
+ * `submittingKeys.has(key)` to drive per-target loading state when several
16
+ * operations of the same kind can run at once.
17
+ */
18
+ readonly submittingKeys: ReadonlySet<string>;
19
+ /**
20
+ * Per-key failures, keyed exactly like {@link submittingKeys}. A consumer
21
+ * reads `errorsByKey.get(key)` to surface the failure beside the one control
22
+ * it belongs to — a single scalar cannot say which of several concurrent
23
+ * operations failed. Cleared for a key when that key is retried.
24
+ */
25
+ readonly errorsByKey: ReadonlyMap<string, Error>;
26
+ /**
27
+ * Run `fn` under the bookkeeping for `key`: clear the key's prior error,
28
+ * mark it in flight, await, and release it. On failure the error is recorded
29
+ * in {@link errorsByKey} and then **re-thrown** — the primitive never decides
30
+ * how the failure propagates, so each caller chooses (mirror to a scalar and
31
+ * rethrow, swallow to a sentinel, etc.).
32
+ */
33
+ readonly run: (key: string, fn: () => Promise<T>) => Promise<T>;
34
+ /** Reset every recorded error. In-flight tracking is untouched. */
35
+ readonly clearErrors: () => void;
36
+ }
37
+
38
+ /**
39
+ * Tracks per-key in-flight and error state for a family of concurrent async
40
+ * operations that share one identity space (e.g. approval decisions keyed by
41
+ * tool-call id, or by task name).
42
+ *
43
+ * This is the shared substrate behind every "many gates at once" surface: a
44
+ * thread or a workflow can hold several pending decisions simultaneously, so a
45
+ * failure (and the spinner) must be attributable to the *one* gate it belongs
46
+ * to. A single scalar `error`/`isSubmitting` pair cannot do that; a keyed
47
+ * `Set`/`Map` pair can. Singleton controls (a header's cancel/pause) do not
48
+ * need this — they correctly share one scalar.
49
+ *
50
+ * `run` re-throws after recording so the propagation policy stays with the
51
+ * caller: {@link useSubmitApproval} mirrors the failure to a scalar and
52
+ * rethrows; {@link useWorkflowExecutionActions} swallows it to `null`.
53
+ *
54
+ * The return is `useMemo`'d over stable parts (empty-collection sentinels keep
55
+ * the idle refs constant) so it is safe as a `React.memo` dependency (DD-010).
56
+ *
57
+ * @internal Not part of the public API.
58
+ */
59
+ export function useKeyedSubmission<T = void>(): KeyedSubmission<T> {
60
+ const [submittingKeys, setSubmittingKeys] =
61
+ useState<ReadonlySet<string>>(NO_KEYS);
62
+ const [errorsByKey, setErrorsByKey] =
63
+ useState<ReadonlyMap<string, Error>>(NO_ERRORS);
64
+
65
+ const clearErrors = useCallback(() => setErrorsByKey(NO_ERRORS), []);
66
+
67
+ const run = useCallback(async (key: string, fn: () => Promise<T>): Promise<T> => {
68
+ setSubmittingKeys((prev) => {
69
+ const next = new Set(prev);
70
+ next.add(key);
71
+ return next;
72
+ });
73
+ // Clear this key's prior failure so a retry starts clean.
74
+ setErrorsByKey((prev) => {
75
+ if (!prev.has(key)) return prev;
76
+ const next = new Map(prev);
77
+ next.delete(key);
78
+ return next;
79
+ });
80
+
81
+ try {
82
+ return await fn();
83
+ } catch (err) {
84
+ const e = toError(err);
85
+ setErrorsByKey((prev) => new Map(prev).set(key, e));
86
+ throw err;
87
+ } finally {
88
+ setSubmittingKeys((prev) => {
89
+ if (!prev.has(key)) return prev;
90
+ const next = new Set(prev);
91
+ next.delete(key);
92
+ return next;
93
+ });
94
+ }
95
+ }, []);
96
+
97
+ return useMemo(
98
+ () => ({ submittingKeys, errorsByKey, run, clearErrors }),
99
+ [submittingKeys, errorsByKey, run, clearErrors],
100
+ );
101
+ }
@@ -12,6 +12,7 @@ import { SelectionStore } from "../internal/store/selection-store";
12
12
  import { ThreadSelectionContext } from "../execution/ThreadSelectionContext";
13
13
  import { useSelectedThreadItem } from "../execution/useThreadSelection";
14
14
  import { MessageThread } from "../execution/MessageThread";
15
+ import { FileReviewDock } from "../execution/FileReviewDock";
15
16
  import { ThreadSkeleton } from "../execution/ThreadSkeleton";
16
17
  import { SessionComposer } from "../composer";
17
18
  import { SecretFlowErrorGuide, isSecretFlowError } from "../error";
@@ -109,8 +110,8 @@ export interface SessionViewerProps {
109
110
  * Owns `useSessionPageFlow` internally and composes:
110
111
  * - **Conversation column** (primary): `MessageThread` + error
111
112
  * banners + `SessionComposer`
112
- * - **SessionInspector** (secondary): tabbed panel with Plan,
113
- * Changes, Artifacts, Usage, and Inspect facets
113
+ * - **SessionInspector** (secondary): tabbed panel with Workspace,
114
+ * Config, Changes, Artifacts, Usage, and Inspect facets
114
115
  *
115
116
  * Connected via `ResizableSplit` with persisted width.
116
117
  *
@@ -288,8 +289,12 @@ function ConversationColumn({
288
289
  isEndUser,
289
290
  }: ConversationColumnProps) {
290
291
  const { conv } = flow;
291
- const sendError =
292
- flow.submitError ?? conv.sendError ?? conv.approvalError ?? conv.stopError;
292
+ // Approval failures are NOT folded into this banner: they now surface in-card,
293
+ // beside the exact gate that failed (see MessageThread's approvalErrors →
294
+ // ApprovalCard), matching the file-review card. A single banner cannot name
295
+ // which of several concurrent gates failed. The scalar conv.approvalError
296
+ // stays available for headless/`ink` consumers.
297
+ const sendError = flow.submitError ?? conv.sendError ?? conv.stopError;
293
298
 
294
299
  // Retry a terminal-failed execution by resending its originating message
295
300
  // through the full submit pipeline (agent override, runtime-env, workspace).
@@ -331,6 +336,8 @@ function ConversationColumn({
331
336
  onRetryExecution={onRetryExecution}
332
337
  onApprovalSubmit={flow.submitApproval}
333
338
  submittingApprovalIds={conv.submittingApprovalIds}
339
+ approvalErrors={conv.approvalErrors}
340
+ showFileReviewRecords
334
341
  onEditMessage={conv.isStoppable ? handleEditMessage : undefined}
335
342
  workspaceEntries={conv.workspaceEntries}
336
343
  sandboxWorkspaceRoot={flow.sandboxWorkspaceRoot}
@@ -356,6 +363,16 @@ function ConversationColumn({
356
363
  {flow.autoApproveAll && (
357
364
  <AutoApproveIndicator onTurnOff={() => flow.setAutoApproveAll(false)} />
358
365
  )}
366
+ {/* Pending file reviews dock here — pinned above the composer so the
367
+ decision the agent is blocked on can never scroll out of view. The
368
+ thread renders only observational rows (badges) and read-only
369
+ settled records; this is the one decision surface. */}
370
+ <FileReviewDock
371
+ changeSets={conv.fileChangeSets}
372
+ onSubmit={conv.submitFileDecision}
373
+ submittingDecisionKeys={conv.submittingFileDecisionKeys}
374
+ decisionErrors={conv.fileDecisionErrors}
375
+ />
359
376
  <SessionComposer
360
377
  ref={composerRef}
361
378
  onSubmit={flow.handleSubmit}
@@ -0,0 +1,198 @@
1
+ import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
2
+ import { render, screen, cleanup, fireEvent } from "@testing-library/react";
3
+ import { create } from "@bufbuild/protobuf";
4
+ import {
5
+ CapturedFileChangeSchema,
6
+ FileChangeSetSchema,
7
+ } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/filereview_pb";
8
+ import { FileContentSchema } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/message_pb";
9
+ import {
10
+ DiffCompleteness,
11
+ FileChangeKind,
12
+ FileChangeSetStatus,
13
+ FileDecisionAction,
14
+ } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
15
+
16
+ // ---------------------------------------------------------------------------
17
+ // Wiring-contract tests for the file-review surfaces on SessionViewer:
18
+ // the pending decision bar renders in the composer-docked FileReviewDock
19
+ // (fed from conv's seam), and the thread is records-only
20
+ // (`showFileReviewRecords`, no decision callbacks). The dock renders REAL —
21
+ // it is the subject; the thread/composer/inspector are prop-capturing probes.
22
+ // ---------------------------------------------------------------------------
23
+
24
+ type CapturedProps = Record<string, unknown>;
25
+
26
+ const threadProps: CapturedProps[] = [];
27
+ vi.mock("../../execution/MessageThread", () => ({
28
+ MessageThread: (props: CapturedProps) => {
29
+ threadProps.push(props);
30
+ return <div data-testid="thread-probe" />;
31
+ },
32
+ }));
33
+
34
+ vi.mock("../../composer", async (importOriginal) => {
35
+ const actual = await importOriginal<typeof import("../../composer")>();
36
+ return {
37
+ ...actual,
38
+ SessionComposer: () => <div data-testid="composer-probe" />,
39
+ };
40
+ });
41
+
42
+ vi.mock("../inspector/SessionInspector", () => ({
43
+ SessionInspector: () => <div data-testid="inspector-probe" />,
44
+ }));
45
+
46
+ const stubWorkspace = {
47
+ entries: [],
48
+ hasEntries: false,
49
+ toInput: vi.fn().mockReturnValue([]),
50
+ addGitRepo: vi.fn(),
51
+ addLocalPath: vi.fn(),
52
+ removeEntry: vi.fn(),
53
+ clear: vi.fn(),
54
+ };
55
+
56
+ function pendingChangeSet(id: string) {
57
+ return create(FileChangeSetSchema, {
58
+ id,
59
+ status: FileChangeSetStatus.AWAITING_REVIEW,
60
+ aggregateDigest: `agg-${id}`,
61
+ diffCompleteness: DiffCompleteness.COMPLETE,
62
+ changes: [
63
+ create(CapturedFileChangeSchema, {
64
+ id: `${id}:notes.md`,
65
+ pathBefore: "notes.md",
66
+ pathAfter: "notes.md",
67
+ kind: FileChangeKind.ADD,
68
+ before: create(FileContentSchema, { body: { case: "inline", value: "" } }),
69
+ after: create(FileContentSchema, { body: { case: "inline", value: "# Notes\n" } }),
70
+ fileDigest: "d-notes",
71
+ diffComplete: true,
72
+ }),
73
+ ],
74
+ });
75
+ }
76
+
77
+ const stubConv = {
78
+ session: { spec: {} },
79
+ isLoading: false,
80
+ loadError: null,
81
+ completedExecutions: [],
82
+ activeStreamExecution: null,
83
+ activePhase: null,
84
+ isStreaming: false,
85
+ isConnecting: false,
86
+ sendFollowUp: vi.fn(),
87
+ canSendFollowUp: true,
88
+ isSending: false,
89
+ sendError: null,
90
+ clearSendError: vi.fn(),
91
+ pendingUserMessage: null,
92
+ workspaceEntries: [],
93
+ mcpServerUsages: [],
94
+ skillRefs: [],
95
+ pendingApprovals: [],
96
+ submitApproval: vi.fn(),
97
+ submittingApprovalIds: new Set<string>(),
98
+ fileChangeSets: [] as ReturnType<typeof pendingChangeSet>[],
99
+ submitFileDecision: vi.fn(),
100
+ submittingFileDecisionKeys: new Set<string>(),
101
+ fileDecisionErrors: new Map<string, Error>(),
102
+ streamError: null,
103
+ reconnectStream: vi.fn(),
104
+ approvalError: null,
105
+ };
106
+
107
+ const stubSessionPageFlow = {
108
+ conv: stubConv,
109
+ harness: "native" as const,
110
+ executionTarget: undefined,
111
+ model: [undefined, vi.fn()] as const,
112
+ interactionMode: ["agent" as const, vi.fn()] as const,
113
+ agentRef: { org: "acme", slug: "support-bot" },
114
+ setAgentRef: vi.fn(),
115
+ resolution: null,
116
+ setResolution: vi.fn(),
117
+ isDefaultAgent: false,
118
+ mcpServerUsages: [],
119
+ setMcpServerUsages: vi.fn(),
120
+ skillRefs: [],
121
+ setSkillRefs: vi.fn(),
122
+ workspace: stubWorkspace,
123
+ sessionVariables: { entries: [], isEmpty: true, clear: vi.fn() },
124
+ autoApproveAll: false,
125
+ setAutoApproveAll: vi.fn(),
126
+ submitApproval: vi.fn(),
127
+ handleSubmit: vi.fn(),
128
+ submitError: null as Error | null,
129
+ displayExecution: null,
130
+ allExecutions: [],
131
+ sandboxWorkspaceRoot: undefined,
132
+ };
133
+ vi.mock("../useSessionPageFlow", () => ({
134
+ useSessionPageFlow: () => stubSessionPageFlow,
135
+ }));
136
+
137
+ import { SessionViewer } from "../SessionViewer";
138
+
139
+ beforeEach(() => {
140
+ threadProps.length = 0;
141
+ stubConv.fileChangeSets = [];
142
+ stubConv.submitFileDecision = vi.fn();
143
+ });
144
+
145
+ afterEach(() => {
146
+ cleanup();
147
+ vi.clearAllMocks();
148
+ });
149
+
150
+ describe("SessionViewer — file-review dock wiring", () => {
151
+ it("renders no dock when nothing is pending", () => {
152
+ render(<SessionViewer sessionId="ses_1" org="acme" />);
153
+ expect(
154
+ document.querySelector('[data-cursor-target="file-review-dock"]'),
155
+ ).toBeNull();
156
+ });
157
+
158
+ it("docks a pending set above the composer, wired to conv's decision seam", () => {
159
+ stubConv.fileChangeSets = [pendingChangeSet("cs-1")];
160
+ render(<SessionViewer sessionId="ses_1" org="acme" />);
161
+
162
+ const dock = document.querySelector('[data-cursor-target="file-review-dock"]');
163
+ expect(dock).toBeTruthy();
164
+ expect(screen.getByText("Review file changes")).toBeTruthy();
165
+
166
+ // The dock sits in the fixed strip: a sibling of the composer probe,
167
+ // ordered before it (pinned directly above the input).
168
+ const composer = screen.getByTestId("composer-probe");
169
+ expect(dock!.parentElement).toBe(composer.parentElement);
170
+ expect(
171
+ dock!.compareDocumentPosition(composer) & Node.DOCUMENT_POSITION_FOLLOWING,
172
+ ).toBeTruthy();
173
+
174
+ // A decision routes through conv.submitFileDecision with the set's id.
175
+ fireEvent.click(
176
+ document.querySelector<HTMLButtonElement>(
177
+ '[data-cursor-target="file-review-approve"]',
178
+ )!,
179
+ );
180
+ expect(stubConv.submitFileDecision).toHaveBeenCalledWith(
181
+ "cs-1",
182
+ FileDecisionAction.APPROVE,
183
+ expect.objectContaining({ expectedDigest: "agg-cs-1" }),
184
+ );
185
+ });
186
+
187
+ it("gives the thread records-only file-review props (no decision callbacks)", () => {
188
+ render(<SessionViewer sessionId="ses_1" org="acme" />);
189
+
190
+ expect(threadProps.length).toBeGreaterThan(0);
191
+ const props = threadProps.at(-1)!;
192
+ expect(props.showFileReviewRecords).toBe(true);
193
+ // The decision plumbing must not leak back into the thread.
194
+ expect(props.onFileDecisionSubmit).toBeUndefined();
195
+ expect(props.submittingFileDecisionKeys).toBeUndefined();
196
+ expect(props.fileDecisionErrors).toBeUndefined();
197
+ });
198
+ });
@@ -97,6 +97,10 @@ const stubConv = {
97
97
  pendingApprovals: [],
98
98
  submitApproval: vi.fn(),
99
99
  submittingApprovalIds: new Set<string>(),
100
+ fileChangeSets: [],
101
+ submitFileDecision: vi.fn(),
102
+ submittingFileDecisionKeys: new Set<string>(),
103
+ fileDecisionErrors: new Map<string, Error>(),
100
104
  streamError: null,
101
105
  reconnectStream: vi.fn(),
102
106
  approvalError: null,