@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,129 @@
1
+ import { describe, it, expect, vi, afterEach } from "vitest";
2
+ import { render, screen, fireEvent, cleanup } from "@testing-library/react";
3
+ import { WorkflowExecutionApprovalCard } from "../WorkflowExecutionApprovalCard";
4
+
5
+ // ApprovalAction enum: 1 = APPROVE, 3 = REJECT.
6
+ const APPROVE = 1;
7
+ const REJECT = 3;
8
+
9
+ const defaultProps = {
10
+ prompt: "The agent wants to delete /tmp/x. Approve?",
11
+ toolCallId: "tc-1",
12
+ approvers: [] as readonly string[],
13
+ timeoutSeconds: 0,
14
+ onSubmitApproval: vi.fn().mockResolvedValue(undefined),
15
+ isSubmitting: false,
16
+ };
17
+
18
+ afterEach(cleanup);
19
+
20
+ describe("WorkflowExecutionApprovalCard", () => {
21
+ describe("content", () => {
22
+ it("renders the prompt", () => {
23
+ render(<WorkflowExecutionApprovalCard {...defaultProps} />);
24
+ expect(screen.getByText(defaultProps.prompt)).toBeTruthy();
25
+ });
26
+
27
+ it("lists approvers and the timeout when provided", () => {
28
+ render(
29
+ <WorkflowExecutionApprovalCard
30
+ {...defaultProps}
31
+ approvers={["alice", "bob"]}
32
+ timeoutSeconds={120}
33
+ />,
34
+ );
35
+ expect(screen.getByText("Approvers: alice, bob")).toBeTruthy();
36
+ expect(screen.getByText("Expires in 2m")).toBeTruthy();
37
+ });
38
+ });
39
+
40
+ describe("submission", () => {
41
+ it("submits APPROVE with the comment", () => {
42
+ const onSubmitApproval = vi.fn().mockResolvedValue(undefined);
43
+ render(
44
+ <WorkflowExecutionApprovalCard {...defaultProps} onSubmitApproval={onSubmitApproval} />,
45
+ );
46
+ fireEvent.change(screen.getByPlaceholderText("Comment (optional)"), {
47
+ target: { value: "ok" },
48
+ });
49
+ fireEvent.click(screen.getByRole("button", { name: "Approve" }));
50
+ expect(onSubmitApproval).toHaveBeenCalledWith("tc-1", APPROVE, "ok");
51
+ });
52
+
53
+ it("submits REJECT with no comment as undefined", () => {
54
+ const onSubmitApproval = vi.fn().mockResolvedValue(undefined);
55
+ render(
56
+ <WorkflowExecutionApprovalCard {...defaultProps} onSubmitApproval={onSubmitApproval} />,
57
+ );
58
+ fireEvent.click(screen.getByRole("button", { name: "Reject" }));
59
+ expect(onSubmitApproval).toHaveBeenCalledWith("tc-1", REJECT, undefined);
60
+ });
61
+ });
62
+
63
+ describe("quiet chrome & buttons (shared DecisionButton)", () => {
64
+ it("uses neutral card chrome + warning accent, never the old amber fill", () => {
65
+ render(<WorkflowExecutionApprovalCard {...defaultProps} />);
66
+ const card = screen.getByRole("alert");
67
+ expect(card.className).toContain("border-border-prominent");
68
+ expect(card.className).toContain("border-l-warning");
69
+ expect(card.className).not.toContain("bg-warning");
70
+ });
71
+
72
+ it("Approve is the neutral chip (not loud green); Reject is a quiet danger ghost (not a red fill)", () => {
73
+ render(<WorkflowExecutionApprovalCard {...defaultProps} />);
74
+ const approve = screen.getByRole("button", { name: "Approve" });
75
+ expect(approve.className).toContain("bg-accent");
76
+ expect(approve.className).not.toContain("bg-success");
77
+
78
+ const reject = screen.getByRole("button", { name: "Reject" });
79
+ expect(reject.className).toContain("hover:text-destructive");
80
+ expect(reject.className).not.toContain("bg-destructive text-destructive-foreground");
81
+ // No opacity-modifier debt (e.g. the old hover:bg-destructive/10).
82
+ expect(reject.className).not.toMatch(/\/\d+(?:\s|$)/);
83
+ });
84
+ });
85
+
86
+ describe("loading state", () => {
87
+ it("disables both buttons while submitting", () => {
88
+ render(<WorkflowExecutionApprovalCard {...defaultProps} isSubmitting />);
89
+ expect(
90
+ (screen.getByRole("button", { name: "Approve" }) as HTMLButtonElement).disabled,
91
+ ).toBe(true);
92
+ expect(
93
+ (screen.getByRole("button", { name: "Reject" }) as HTMLButtonElement).disabled,
94
+ ).toBe(true);
95
+ });
96
+
97
+ it("shows the spinner on the clicked button during submission", () => {
98
+ const { rerender } = render(<WorkflowExecutionApprovalCard {...defaultProps} />);
99
+ fireEvent.click(screen.getByRole("button", { name: "Reject" }));
100
+ rerender(<WorkflowExecutionApprovalCard {...defaultProps} isSubmitting />);
101
+
102
+ expect(
103
+ screen.getByRole("button", { name: "Reject" }).querySelector(".animate-spin"),
104
+ ).toBeTruthy();
105
+ expect(
106
+ screen.getByRole("button", { name: "Approve" }).querySelector(".animate-spin"),
107
+ ).toBeNull();
108
+ });
109
+ });
110
+
111
+ describe("in-card decision error", () => {
112
+ it("renders the failure notice beside the actions when error is set", () => {
113
+ render(
114
+ <WorkflowExecutionApprovalCard
115
+ {...defaultProps}
116
+ error={new Error("gate already resolved")}
117
+ />,
118
+ );
119
+ expect(
120
+ screen.getByText(/Couldn.t submit decision — gate already resolved/),
121
+ ).toBeTruthy();
122
+ });
123
+
124
+ it("renders no failure notice when error is null", () => {
125
+ render(<WorkflowExecutionApprovalCard {...defaultProps} error={null} />);
126
+ expect(screen.queryByText(/Couldn.t submit decision/)).toBeNull();
127
+ });
128
+ });
129
+ });
@@ -0,0 +1,129 @@
1
+ import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
2
+ import { render, screen, fireEvent, cleanup } from "@testing-library/react";
3
+ import { FileChangeSetStatus } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
4
+
5
+ // Mock the child execution stream: each child id maps to a canned execution.
6
+ const mockExecutions: Record<string, any> = {};
7
+ vi.mock("../../execution/useExecutionStream", () => ({
8
+ useExecutionStream: (id: string | null) => ({
9
+ execution: id ? (mockExecutions[id] ?? null) : null,
10
+ }),
11
+ }));
12
+
13
+ // Stub FileReviewCard to a minimal control that surfaces its wiring, so the test
14
+ // exercises the container's mapping/forwarding — not the card's diff rendering.
15
+ vi.mock("../../execution/FileReviewCard", () => ({
16
+ FileReviewCard: (props: any) => (
17
+ <button
18
+ data-testid={`card-${props.fileChangeSet.id}`}
19
+ onClick={() => props.onSubmit(1 /* APPROVE */, { expectedDigest: "d" })}
20
+ >
21
+ review {props.fileChangeSet.id}
22
+ </button>
23
+ ),
24
+ }));
25
+
26
+ import { WorkflowFileReviewList } from "../WorkflowFileReviewList";
27
+
28
+ function awaitingSet(id: string) {
29
+ return { id, status: FileChangeSetStatus.AWAITING_REVIEW, changes: [{ id: "fc" }] };
30
+ }
31
+
32
+ beforeEach(() => {
33
+ for (const k of Object.keys(mockExecutions)) delete mockExecutions[k];
34
+ });
35
+
36
+ afterEach(() => {
37
+ cleanup();
38
+ });
39
+
40
+ describe("WorkflowFileReviewList", () => {
41
+ it("renders nothing when there are no references", () => {
42
+ const { container } = render(
43
+ <WorkflowFileReviewList pendingFileReviews={[]} onSubmitFileDecision={vi.fn()} />,
44
+ );
45
+ expect(container.firstChild).toBeNull();
46
+ });
47
+
48
+ it("renders a card for a referenced AWAITING_REVIEW set and forwards the decision with child routing", () => {
49
+ mockExecutions["aex-1"] = { status: { fileChangeSets: [awaitingSet("fcs-1")] } };
50
+ const onSubmit = vi.fn();
51
+
52
+ render(
53
+ <WorkflowFileReviewList
54
+ pendingFileReviews={[{ childAgentExecutionId: "aex-1", changeSetId: ["fcs-1"] } as any]}
55
+ onSubmitFileDecision={onSubmit}
56
+ />,
57
+ );
58
+
59
+ const card = screen.getByTestId("card-fcs-1");
60
+ fireEvent.click(card);
61
+ expect(onSubmit).toHaveBeenCalledWith("aex-1", "fcs-1", 1, { expectedDigest: "d" });
62
+ });
63
+
64
+ it("does not render a set that is not in the surfaced reference", () => {
65
+ // Child has an AWAITING set fcs-2, but the parent only surfaced fcs-1.
66
+ mockExecutions["aex-1"] = {
67
+ status: { fileChangeSets: [awaitingSet("fcs-1"), awaitingSet("fcs-2")] },
68
+ };
69
+
70
+ render(
71
+ <WorkflowFileReviewList
72
+ pendingFileReviews={[{ childAgentExecutionId: "aex-1", changeSetId: ["fcs-1"] } as any]}
73
+ onSubmitFileDecision={vi.fn()}
74
+ />,
75
+ );
76
+
77
+ expect(screen.getByTestId("card-fcs-1")).toBeTruthy();
78
+ expect(screen.queryByTestId("card-fcs-2")).toBeNull();
79
+ });
80
+
81
+ it("does not render a referenced set that is no longer AWAITING_REVIEW", () => {
82
+ mockExecutions["aex-1"] = {
83
+ status: { fileChangeSets: [{ id: "fcs-1", status: FileChangeSetStatus.DECIDED, changes: [{ id: "fc" }] }] },
84
+ };
85
+
86
+ render(
87
+ <WorkflowFileReviewList
88
+ pendingFileReviews={[{ childAgentExecutionId: "aex-1", changeSetId: ["fcs-1"] } as any]}
89
+ onSubmitFileDecision={vi.fn()}
90
+ />,
91
+ );
92
+
93
+ expect(screen.queryByTestId("card-fcs-1")).toBeNull();
94
+ });
95
+
96
+ it("renders reviews for multiple parallel children independently", () => {
97
+ mockExecutions["aex-1"] = { status: { fileChangeSets: [awaitingSet("fcs-1")] } };
98
+ mockExecutions["aex-2"] = { status: { fileChangeSets: [awaitingSet("fcs-2")] } };
99
+
100
+ render(
101
+ <WorkflowFileReviewList
102
+ pendingFileReviews={[
103
+ { childAgentExecutionId: "aex-1", changeSetId: ["fcs-1"] } as any,
104
+ { childAgentExecutionId: "aex-2", changeSetId: ["fcs-2"] } as any,
105
+ ]}
106
+ onSubmitFileDecision={vi.fn()}
107
+ />,
108
+ );
109
+
110
+ expect(screen.getByTestId("card-fcs-1")).toBeTruthy();
111
+ expect(screen.getByTestId("card-fcs-2")).toBeTruthy();
112
+ });
113
+
114
+ it("shows a deep-link affordance when onNavigateToAgentExecution is provided", () => {
115
+ mockExecutions["aex-1"] = { status: { fileChangeSets: [awaitingSet("fcs-1")] } };
116
+ const onNavigate = vi.fn();
117
+
118
+ render(
119
+ <WorkflowFileReviewList
120
+ pendingFileReviews={[{ childAgentExecutionId: "aex-1", changeSetId: ["fcs-1"] } as any]}
121
+ onSubmitFileDecision={vi.fn()}
122
+ onNavigateToAgentExecution={onNavigate}
123
+ />,
124
+ );
125
+
126
+ fireEvent.click(screen.getByText("View agent execution"));
127
+ expect(onNavigate).toHaveBeenCalledWith("aex-1");
128
+ });
129
+ });
@@ -37,14 +37,19 @@ describe("WorkflowTaskApprovalCard", () => {
37
37
  });
38
38
  });
39
39
 
40
- describe("button variants", () => {
41
- it("first outcome gets primary variant (success colors)", () => {
40
+ // The buttons now share the quiet, token-only DecisionButton primitive:
41
+ // first outcome = neutral chip (`primary`), reject/deny or the trailing of two
42
+ // = ghost-`danger`, middle outcomes = neutral `ghost`. No loud success/destructive
43
+ // fills, and no `bg-token/NN` opacity modifiers.
44
+ describe("button variants (quiet hierarchy)", () => {
45
+ it("first outcome is the neutral chip (primary), never the loud success green", () => {
42
46
  render(<WorkflowTaskApprovalCard {...defaultProps} />);
43
47
  const btn = screen.getByRole("button", { name: "Approve" });
44
- expect(btn.className).toContain("bg-success");
48
+ expect(btn.className).toContain("bg-accent");
49
+ expect(btn.className).not.toContain("bg-success");
45
50
  });
46
51
 
47
- it('outcome named "reject" gets destructive variant', () => {
52
+ it('outcome named "reject" is a quiet danger ghost (no red fill)', () => {
48
53
  const outcomes: TaskOutcome[] = [
49
54
  { name: "approve", label: "Approve" },
50
55
  { name: "reject", label: "Reject" },
@@ -52,10 +57,11 @@ describe("WorkflowTaskApprovalCard", () => {
52
57
  ];
53
58
  render(<WorkflowTaskApprovalCard {...defaultProps} outcomes={outcomes} />);
54
59
  const btn = screen.getByRole("button", { name: "Reject" });
55
- expect(btn.className).toContain("bg-destructive");
60
+ expect(btn.className).toContain("hover:text-destructive");
61
+ expect(btn.className).not.toContain("bg-destructive text-destructive-foreground");
56
62
  });
57
63
 
58
- it('outcome named "deny" gets destructive variant', () => {
64
+ it('outcome named "deny" is a quiet danger ghost (no red fill)', () => {
59
65
  const outcomes: TaskOutcome[] = [
60
66
  { name: "approve", label: "Approve" },
61
67
  { name: "deny", label: "Deny" },
@@ -63,20 +69,21 @@ describe("WorkflowTaskApprovalCard", () => {
63
69
  ];
64
70
  render(<WorkflowTaskApprovalCard {...defaultProps} outcomes={outcomes} />);
65
71
  const btn = screen.getByRole("button", { name: "Deny" });
66
- expect(btn.className).toContain("bg-destructive");
72
+ expect(btn.className).toContain("hover:text-destructive");
73
+ expect(btn.className).not.toContain("bg-destructive text-destructive-foreground");
67
74
  });
68
75
 
69
- it("second of two outcomes gets destructive variant (binary fallback)", () => {
76
+ it("second of two outcomes is the danger ghost (binary fallback)", () => {
70
77
  const outcomes: TaskOutcome[] = [
71
78
  { name: "accept", label: "Accept" },
72
79
  { name: "decline", label: "Decline" },
73
80
  ];
74
81
  render(<WorkflowTaskApprovalCard {...defaultProps} outcomes={outcomes} />);
75
82
  const btn = screen.getByRole("button", { name: "Decline" });
76
- expect(btn.className).toContain("bg-destructive");
83
+ expect(btn.className).toContain("hover:text-destructive");
77
84
  });
78
85
 
79
- it("middle outcomes in 3+ get secondary variant", () => {
86
+ it("middle outcomes in 3+ are neutral ghosts (no resting fill)", () => {
80
87
  const outcomes: TaskOutcome[] = [
81
88
  { name: "approve", label: "Approve" },
82
89
  { name: "defer", label: "Defer" },
@@ -84,7 +91,16 @@ describe("WorkflowTaskApprovalCard", () => {
84
91
  ];
85
92
  render(<WorkflowTaskApprovalCard {...defaultProps} outcomes={outcomes} />);
86
93
  const btn = screen.getByRole("button", { name: "Defer" });
87
- expect(btn.className).toContain("bg-background");
94
+ expect(btn.className).toContain("text-muted-foreground");
95
+ expect(btn.className).not.toMatch(/(?:^|\s)bg-/);
96
+ });
97
+
98
+ it("the card uses neutral chrome + warning accent, never the old amber fill", () => {
99
+ render(<WorkflowTaskApprovalCard {...defaultProps} />);
100
+ const card = screen.getByRole("form");
101
+ expect(card.className).toContain("border-border-prominent");
102
+ expect(card.className).toContain("border-l-warning");
103
+ expect(card.className).not.toContain("bg-warning");
88
104
  });
89
105
  });
90
106
 
@@ -232,6 +248,22 @@ describe("WorkflowTaskApprovalCard", () => {
232
248
  });
233
249
  });
234
250
 
251
+ describe("in-card decision error", () => {
252
+ it("renders the failure notice beside the outcomes when error is set", () => {
253
+ render(
254
+ <WorkflowTaskApprovalCard {...defaultProps} error={new Error("signal failed")} />,
255
+ );
256
+ expect(
257
+ screen.getByText(/Couldn.t submit decision — signal failed/),
258
+ ).toBeTruthy();
259
+ });
260
+
261
+ it("renders no failure notice when error is null", () => {
262
+ render(<WorkflowTaskApprovalCard {...defaultProps} error={null} />);
263
+ expect(screen.queryByText(/Couldn.t submit decision/)).toBeNull();
264
+ });
265
+ });
266
+
235
267
  describe("accessibility", () => {
236
268
  it('root element has role="form"', () => {
237
269
  render(<WorkflowTaskApprovalCard {...defaultProps} />);
@@ -23,6 +23,7 @@ const mockResume = vi.fn();
23
23
  const mockRecover = vi.fn();
24
24
  const mockSubmitApproval = vi.fn();
25
25
  const mockSubmitWorkflowTaskApproval = vi.fn();
26
+ const mockSubmitFileDecision = vi.fn();
26
27
 
27
28
  function makeMockClient(): Stigmer {
28
29
  return {
@@ -34,6 +35,7 @@ function makeMockClient(): Stigmer {
34
35
  recover: mockRecover,
35
36
  submitApproval: mockSubmitApproval,
36
37
  submitWorkflowTaskApproval: mockSubmitWorkflowTaskApproval,
38
+ submitFileDecision: mockSubmitFileDecision,
37
39
  },
38
40
  } as unknown as Stigmer;
39
41
  }
@@ -278,3 +280,277 @@ describe("onSuccess callback", () => {
278
280
  }
279
281
  });
280
282
  });
283
+
284
+ // ---------------------------------------------------------------------------
285
+ // Per-gate approval state (keyed; separate from the lifecycle scalar)
286
+ // ---------------------------------------------------------------------------
287
+
288
+ describe("per-gate approval state", () => {
289
+ it("tracks an in-flight tool approval keyed by toolCallId, not the lifecycle scalar", async () => {
290
+ let resolve!: (v: unknown) => void;
291
+ mockSubmitApproval.mockReturnValueOnce(new Promise((r) => (resolve = r)));
292
+
293
+ const { result } = renderHook(
294
+ () => useWorkflowExecutionActions("wex-001"),
295
+ { wrapper: createWrapper(makeMockClient()) },
296
+ );
297
+
298
+ let pending: Promise<unknown>;
299
+ act(() => {
300
+ pending = result.current.submitApproval("tc-1", 1 as any);
301
+ });
302
+
303
+ expect(result.current.approvalSubmittingToolCallIds.has("tc-1")).toBe(true);
304
+ // The lifecycle scalar is untouched by an approval.
305
+ expect(result.current.isSubmitting).toBe(false);
306
+
307
+ await act(async () => {
308
+ resolve(makeExecution());
309
+ await pending;
310
+ });
311
+
312
+ expect(result.current.approvalSubmittingToolCallIds.size).toBe(0);
313
+ });
314
+
315
+ it("records a failed tool approval in the keyed map, not the lifecycle scalar, and resolves null", async () => {
316
+ mockSubmitApproval.mockRejectedValueOnce(new Error("gate already resolved"));
317
+
318
+ const { result } = renderHook(
319
+ () => useWorkflowExecutionActions("wex-001"),
320
+ { wrapper: createWrapper(makeMockClient()) },
321
+ );
322
+
323
+ let returned: any;
324
+ await act(async () => {
325
+ returned = await result.current.submitApproval("tc-1", 1 as any);
326
+ });
327
+
328
+ expect(returned).toBeNull();
329
+ expect(result.current.approvalErrorsByToolCallId.get("tc-1")?.message).toBe(
330
+ "gate already resolved",
331
+ );
332
+ // The lifecycle scalar (header banner) must NOT pick up an approval failure.
333
+ expect(result.current.error).toBeNull();
334
+ expect(result.current.isSubmitting).toBe(false);
335
+ });
336
+
337
+ it("records a failed task approval keyed by taskName, not the lifecycle scalar", async () => {
338
+ mockSubmitWorkflowTaskApproval.mockRejectedValueOnce(new Error("signal failed"));
339
+
340
+ const { result } = renderHook(
341
+ () => useWorkflowExecutionActions("wex-001"),
342
+ { wrapper: createWrapper(makeMockClient()) },
343
+ );
344
+
345
+ let returned: any;
346
+ await act(async () => {
347
+ returned = await result.current.submitTaskApproval("review_task", "approved");
348
+ });
349
+
350
+ expect(returned).toBeNull();
351
+ expect(result.current.taskApprovalErrorsByTaskName.get("review_task")?.message).toBe(
352
+ "signal failed",
353
+ );
354
+ expect(result.current.error).toBeNull();
355
+ });
356
+
357
+ it("keeps tool and task approval errors in separate keyed spaces", async () => {
358
+ mockSubmitApproval.mockRejectedValueOnce(new Error("tool fail"));
359
+ mockSubmitWorkflowTaskApproval.mockRejectedValueOnce(new Error("task fail"));
360
+
361
+ const { result } = renderHook(
362
+ () => useWorkflowExecutionActions("wex-001"),
363
+ { wrapper: createWrapper(makeMockClient()) },
364
+ );
365
+
366
+ await act(async () => {
367
+ await result.current.submitApproval("tc-1", 1 as any);
368
+ });
369
+ await act(async () => {
370
+ await result.current.submitTaskApproval("review_task", "approved");
371
+ });
372
+
373
+ expect(result.current.approvalErrorsByToolCallId.get("tc-1")?.message).toBe("tool fail");
374
+ expect(result.current.taskApprovalErrorsByTaskName.get("review_task")?.message).toBe("task fail");
375
+ // Neither keyed failure crosses into the other space or the scalar.
376
+ expect(result.current.approvalErrorsByToolCallId.has("review_task")).toBe(false);
377
+ expect(result.current.taskApprovalErrorsByTaskName.has("tc-1")).toBe(false);
378
+ expect(result.current.error).toBeNull();
379
+ });
380
+
381
+ it("keeps a failed lifecycle action in the scalar, never in the keyed approval maps", async () => {
382
+ mockCancel.mockRejectedValueOnce(new Error("temporal down"));
383
+
384
+ const { result } = renderHook(
385
+ () => useWorkflowExecutionActions("wex-001"),
386
+ { wrapper: createWrapper(makeMockClient()) },
387
+ );
388
+
389
+ await act(async () => {
390
+ await result.current.cancel();
391
+ });
392
+
393
+ expect(result.current.error?.message).toBe("temporal down");
394
+ expect(result.current.approvalErrorsByToolCallId.size).toBe(0);
395
+ expect(result.current.taskApprovalErrorsByTaskName.size).toBe(0);
396
+ });
397
+
398
+ it("null executionId returns null without recording keyed state", async () => {
399
+ const { result } = renderHook(
400
+ () => useWorkflowExecutionActions(null),
401
+ { wrapper: createWrapper(makeMockClient()) },
402
+ );
403
+
404
+ let returned: any;
405
+ await act(async () => {
406
+ returned = await result.current.submitApproval("tc-1", 1 as any);
407
+ });
408
+
409
+ expect(returned).toBeNull();
410
+ expect(mockSubmitApproval).not.toHaveBeenCalled();
411
+ expect(result.current.approvalSubmittingToolCallIds.size).toBe(0);
412
+ expect(result.current.approvalErrorsByToolCallId.size).toBe(0);
413
+ });
414
+
415
+ it("keeps a stable return reference across renders when nothing changes", () => {
416
+ const { result, rerender } = renderHook(
417
+ () => useWorkflowExecutionActions("wex-001"),
418
+ { wrapper: createWrapper(makeMockClient()) },
419
+ );
420
+
421
+ const first = result.current;
422
+ rerender();
423
+ expect(result.current).toBe(first);
424
+ });
425
+ });
426
+
427
+ // ---------------------------------------------------------------------------
428
+ // Per-gate file-decision state (forwarding + keyed like FileReviewCard)
429
+ // ---------------------------------------------------------------------------
430
+
431
+ describe("submitFileDecision (workflow-parent file review)", () => {
432
+ it("forwards a CHANGE_SET decision with explicit child routing", async () => {
433
+ mockSubmitFileDecision.mockResolvedValueOnce(makeExecution());
434
+
435
+ const { result } = renderHook(
436
+ () => useWorkflowExecutionActions("wex-001"),
437
+ { wrapper: createWrapper(makeMockClient()) },
438
+ );
439
+
440
+ await act(async () => {
441
+ await result.current.submitFileDecision("aex-child", "fcs-1", 1 /* APPROVE */ as any, {
442
+ expectedDigest: "sha256:agg",
443
+ });
444
+ });
445
+
446
+ expect(mockSubmitFileDecision).toHaveBeenCalledTimes(1);
447
+ const input = mockSubmitFileDecision.mock.calls[0][0];
448
+ expect(input.executionId).toBe("wex-001");
449
+ expect(input.childAgentExecutionId).toBe("aex-child");
450
+ expect(input.changeSetId).toBe("fcs-1");
451
+ expect(input.scope).toBe(1 /* CHANGE_SET */);
452
+ expect(input.fileChangeId).toBe("");
453
+ expect(input.action).toBe(1 /* APPROVE */);
454
+ expect(input.expectedDigest).toBe("sha256:agg");
455
+ });
456
+
457
+ it("derives FILE scope when a fileChangeId is supplied", async () => {
458
+ mockSubmitFileDecision.mockResolvedValueOnce(makeExecution());
459
+
460
+ const { result } = renderHook(
461
+ () => useWorkflowExecutionActions("wex-001"),
462
+ { wrapper: createWrapper(makeMockClient()) },
463
+ );
464
+
465
+ await act(async () => {
466
+ await result.current.submitFileDecision("aex-child", "fcs-1", 2 /* REJECT */ as any, {
467
+ fileChangeId: "fc-9",
468
+ expectedDigest: "sha256:file",
469
+ });
470
+ });
471
+
472
+ const input = mockSubmitFileDecision.mock.calls[0][0];
473
+ expect(input.scope).toBe(2 /* FILE */);
474
+ expect(input.fileChangeId).toBe("fc-9");
475
+ expect(input.action).toBe(2 /* REJECT */);
476
+ });
477
+
478
+ it("tracks in-flight state keyed like FileReviewCard (changeSetId, then changeSetId:fileChangeId)", async () => {
479
+ let resolve!: (v: unknown) => void;
480
+ mockSubmitFileDecision.mockReturnValueOnce(new Promise((r) => (resolve = r)));
481
+
482
+ const { result } = renderHook(
483
+ () => useWorkflowExecutionActions("wex-001"),
484
+ { wrapper: createWrapper(makeMockClient()) },
485
+ );
486
+
487
+ let pending: Promise<unknown>;
488
+ act(() => {
489
+ pending = result.current.submitFileDecision("aex-child", "fcs-1", 1 as any);
490
+ });
491
+
492
+ expect(result.current.fileDecisionSubmittingKeys.has("fcs-1")).toBe(true);
493
+ expect(result.current.isSubmitting).toBe(false); // lifecycle scalar untouched
494
+
495
+ await act(async () => {
496
+ resolve(makeExecution());
497
+ await pending;
498
+ });
499
+
500
+ expect(result.current.fileDecisionSubmittingKeys.size).toBe(0);
501
+ });
502
+
503
+ it("records a failed decision in the keyed map (not the scalar) and resolves null", async () => {
504
+ mockSubmitFileDecision.mockRejectedValueOnce(new Error("expected_digest mismatch"));
505
+
506
+ const { result } = renderHook(
507
+ () => useWorkflowExecutionActions("wex-001"),
508
+ { wrapper: createWrapper(makeMockClient()) },
509
+ );
510
+
511
+ let returned: any;
512
+ await act(async () => {
513
+ returned = await result.current.submitFileDecision("aex-child", "fcs-1", 1 as any, {
514
+ fileChangeId: "fc-9",
515
+ });
516
+ });
517
+
518
+ expect(returned).toBeNull();
519
+ expect(result.current.fileDecisionErrorsByKey.get("fcs-1:fc-9")?.message).toBe(
520
+ "expected_digest mismatch",
521
+ );
522
+ expect(result.current.error).toBeNull(); // header banner unaffected
523
+ });
524
+
525
+ it("does not fire onSuccess", async () => {
526
+ mockSubmitFileDecision.mockResolvedValueOnce(makeExecution());
527
+ const onSuccess = vi.fn();
528
+
529
+ const { result } = renderHook(
530
+ () => useWorkflowExecutionActions("wex-001", { onSuccess }),
531
+ { wrapper: createWrapper(makeMockClient()) },
532
+ );
533
+
534
+ await act(async () => {
535
+ await result.current.submitFileDecision("aex-child", "fcs-1", 1 as any);
536
+ });
537
+
538
+ expect(onSuccess).not.toHaveBeenCalled();
539
+ });
540
+
541
+ it("null executionId returns null without calling the SDK", async () => {
542
+ const { result } = renderHook(
543
+ () => useWorkflowExecutionActions(null),
544
+ { wrapper: createWrapper(makeMockClient()) },
545
+ );
546
+
547
+ let returned: any;
548
+ await act(async () => {
549
+ returned = await result.current.submitFileDecision("aex-child", "fcs-1", 1 as any);
550
+ });
551
+
552
+ expect(returned).toBeNull();
553
+ expect(mockSubmitFileDecision).not.toHaveBeenCalled();
554
+ expect(result.current.fileDecisionSubmittingKeys.size).toBe(0);
555
+ });
556
+ });