@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
@@ -1,15 +1,28 @@
1
1
  "use client";
2
2
 
3
3
  import { memo, useCallback, useEffect, useMemo, useState } from "react";
4
+ import type { ReactNode } from "react";
4
5
  import type { PendingApproval } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/approval_pb";
5
- import { ApprovalAction } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
6
+ import { ApprovalAction, ToolKind } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
6
7
  import { cn } from "@stigmer/theme";
8
+ import {
9
+ describeApprovalPolicySource,
10
+ isInformativePolicySource,
11
+ } from "./approval-provenance";
7
12
  import {
8
13
  resolveToolCategoryFromKind,
9
14
  extractPrimaryArgFromPreview,
15
+ extractWriteContentFromPreview,
16
+ isFileCategory,
17
+ type ToolCategory,
10
18
  } from "./tool-categories";
11
19
  import { CATEGORY_ICON } from "./ToolCallItem";
12
20
  import { ToolArgsView } from "./ToolArgsView";
21
+ import { EmptyChangeNotice } from "./EmptyChangeNotice";
22
+ import { FilePathLink } from "./FilePathLink";
23
+ import { DecisionButton } from "../internal/DecisionButton";
24
+ import { InCardDecisionError } from "../internal/InCardDecisionError";
25
+ import { useElapsedSince, formatElapsed } from "../internal/useElapsedSince";
13
26
 
14
27
  /** Props for {@link ApprovalCard}. */
15
28
  export interface ApprovalCardProps {
@@ -24,6 +37,14 @@ export interface ApprovalCardProps {
24
37
  readonly onSubmit: (action: ApprovalAction, comment?: string) => void;
25
38
  /** True while the RPC for this specific tool call is in flight. */
26
39
  readonly isSubmitting?: boolean;
40
+ /**
41
+ * This gate's last failed decision, or `null` — surfaced in-card, beside the
42
+ * action buttons (the optimistic spinner reverts on failure, so this explains
43
+ * the snap-back). A thread can hold many gates; the error must name the one it
44
+ * belongs to, which a single global banner cannot do. Supply the entry from
45
+ * {@link useSubmitApproval}'s `errorsByToolCallId` for this `toolCallId`.
46
+ */
47
+ readonly error?: Error | null;
27
48
  /** Additional CSS class names for the root container. */
28
49
  readonly className?: string;
29
50
  }
@@ -35,9 +56,11 @@ export interface ApprovalCardProps {
35
56
  * The compact header row matches the ToolCallItem layout:
36
57
  * [CategoryIcon] Label primaryArg [⏳ waiting Xm]
37
58
  *
38
- * Arguments are rendered through the shared {@link ToolArgsView}
39
- * dispatch, ensuring pixel-level parity between the approval
40
- * preview and the post-execution detail view.
59
+ * The body shows the proposed change from the tool args (the
60
+ * pre-execution deny-gate has no captured `FileChangeSet` yet): the
61
+ * write/edit content when present, an {@link EmptyChangeNotice} when
62
+ * only a path is known, otherwise the shared {@link ToolArgsView}
63
+ * dispatch, keeping pixel-level parity with the detail view.
41
64
  *
42
65
  * Wrapped in `React.memo` — structural sharing (T04) preserves the
43
66
  * `PendingApproval` reference when unchanged, so approval cards
@@ -56,32 +79,69 @@ export const ApprovalCard = memo(function ApprovalCard({
56
79
  pendingApproval,
57
80
  onSubmit,
58
81
  isSubmitting = false,
82
+ error = null,
59
83
  className,
60
84
  }: ApprovalCardProps) {
61
- const [activeAction, setActiveAction] = useState<ApprovalAction | null>(null);
85
+ // Prefer the denormalized wire tool_kind (populated by the server-side
86
+ // PendingApproval projection); fall back to the name for legacy executions.
87
+ const categoryInfo = resolveToolCategoryFromKind(
88
+ pendingApproval.toolKind,
89
+ pendingApproval.toolName,
90
+ pendingApproval.mcpServerSlug,
91
+ );
62
92
 
63
- const handleAction = useCallback(
64
- (action: ApprovalAction) => {
65
- setActiveAction(action);
66
- onSubmit(action);
67
- },
68
- [onSubmit],
93
+ // Cursor-grade chrome: a thin neutral card (no amber "brown" fill), with the
94
+ // gate's "needs your decision" signal carried by one restrained cue — a 2px
95
+ // left accent bar — plus the warning-colored clock/elapsed in the header. A
96
+ // destructive (delete) gate keeps a red accent as a hard safety signal.
97
+ const accentClass =
98
+ categoryInfo.category === "delete"
99
+ ? "border-l-2 border-l-destructive"
100
+ : "border-l-2 border-l-warning";
101
+
102
+ return (
103
+ <div
104
+ role="alert"
105
+ aria-label={`Approval required for ${pendingApproval.toolName}`}
106
+ className={cn(
107
+ "rounded-lg border border-border-prominent",
108
+ accentClass,
109
+ className,
110
+ )}
111
+ >
112
+ <ApprovalCardHeader pendingApproval={pendingApproval} bordered />
113
+ <ApprovalCardBody
114
+ pendingApproval={pendingApproval}
115
+ onSubmit={onSubmit}
116
+ isSubmitting={isSubmitting}
117
+ error={error}
118
+ />
119
+ </div>
69
120
  );
121
+ });
70
122
 
71
- useEffect(() => {
72
- if (!isSubmitting) {
73
- setActiveAction(null);
74
- }
75
- }, [isSubmitting]);
123
+ /** Props for {@link ApprovalCardHeader}. */
124
+ export interface ApprovalCardHeaderProps {
125
+ /** The pending approval to summarize. */
126
+ readonly pendingApproval: PendingApproval;
127
+ /** Adds a bottom divider, for use above {@link ApprovalCardBody}. */
128
+ readonly bordered?: boolean;
129
+ }
76
130
 
77
- // Prefer the denormalized wire tool_kind (populated by the server-side
78
- // PendingApproval projection); fall back to the name for legacy executions.
131
+ /**
132
+ * The compact summary row of an approval `[icon] Label primaryArg [via sub]
133
+ * [⏳ Xm]`. Used as the standalone {@link ApprovalCard}'s header. Not rendered
134
+ * inline on a {@link ToolCallItem}, whose own row already serves as the header.
135
+ */
136
+ export function ApprovalCardHeader({
137
+ pendingApproval,
138
+ bordered = false,
139
+ }: ApprovalCardHeaderProps) {
79
140
  const categoryInfo = resolveToolCategoryFromKind(
80
141
  pendingApproval.toolKind,
81
142
  pendingApproval.toolName,
82
143
  pendingApproval.mcpServerSlug,
83
144
  );
84
-
85
145
  const CategoryIcon = CATEGORY_ICON[categoryInfo.category];
86
146
 
87
147
  const primaryArg = useMemo(
@@ -90,14 +150,114 @@ export const ApprovalCard = memo(function ApprovalCard({
90
150
  pendingApproval.toolName,
91
151
  pendingApproval.argsPreview,
92
152
  pendingApproval.mcpServerSlug,
153
+ pendingApproval.toolKind,
93
154
  ),
94
155
  [
95
156
  pendingApproval.toolName,
96
157
  pendingApproval.argsPreview,
97
158
  pendingApproval.mcpServerSlug,
159
+ pendingApproval.toolKind,
98
160
  ],
99
161
  );
100
162
 
163
+ return (
164
+ <div
165
+ className={cn(
166
+ "flex items-center gap-2 px-2.5 py-1.5 text-xs",
167
+ bordered && "border-b border-border-muted",
168
+ )}
169
+ >
170
+ <span className="shrink-0 text-warning" aria-hidden="true">
171
+ <CategoryIcon />
172
+ </span>
173
+
174
+ <span className="min-w-0 flex-1 flex items-center gap-1.5 overflow-hidden">
175
+ <span className="shrink-0 font-medium text-foreground">
176
+ {categoryInfo.label}
177
+ </span>
178
+ {primaryArg &&
179
+ categoryInfo.category !== "shell" &&
180
+ (isFileCategory(categoryInfo.category) ? (
181
+ // A file tool's primary arg is a path — render it filename-first
182
+ // with the full path on hover, matching the ToolCallItem header.
183
+ <FilePathLink
184
+ path={primaryArg}
185
+ className="min-w-0 text-xs text-muted-foreground"
186
+ />
187
+ ) : (
188
+ // Shell is the exception: its command is shown in the body's
189
+ // terminal session, so the header stays minimal (icon + label).
190
+ <span className="min-w-0 truncate font-mono text-muted-foreground">
191
+ {primaryArg}
192
+ </span>
193
+ ))}
194
+ </span>
195
+
196
+ {pendingApproval.fromSubAgent && pendingApproval.subAgentName && (
197
+ <span className="shrink-0 rounded bg-muted px-1 py-0.5 font-mono text-muted-foreground">
198
+ via {pendingApproval.subAgentSubject || pendingApproval.subAgentName}
199
+ </span>
200
+ )}
201
+
202
+ <WaitingDuration requestedAt={pendingApproval.requestedAt} />
203
+
204
+ <span className="shrink-0 text-warning" aria-hidden="true">
205
+ <ClockIcon />
206
+ </span>
207
+ </div>
208
+ );
209
+ }
210
+
211
+ /** Props for {@link ApprovalCardBody}. */
212
+ export interface ApprovalCardBodyProps {
213
+ /** The pending approval to render a decision panel for. */
214
+ readonly pendingApproval: PendingApproval;
215
+ /** Called when the user picks Approve / Approve-all / Skip / Reject. */
216
+ readonly onSubmit: (action: ApprovalAction, comment?: string) => void;
217
+ /** True while the RPC for this tool call is in flight. */
218
+ readonly isSubmitting?: boolean;
219
+ /** This gate's last failed decision, or `null` — surfaced in-card below the actions. */
220
+ readonly error?: Error | null;
221
+ /** Additional CSS class names for the body container. */
222
+ readonly className?: string;
223
+ }
224
+
225
+ /**
226
+ * The decision panel of an approval: the optional message and gate reason, the
227
+ * file-changes / args preview, and the action buttons. Reused in two places —
228
+ * the standalone {@link ApprovalCard} (with {@link ApprovalCardHeader} above
229
+ * it) and inline inside a gated {@link ToolCallItem}'s expanded panel, where
230
+ * the item's own row is the header. One body, identical preview in both.
231
+ */
232
+ export function ApprovalCardBody({
233
+ pendingApproval,
234
+ onSubmit,
235
+ isSubmitting = false,
236
+ error = null,
237
+ className,
238
+ }: ApprovalCardBodyProps) {
239
+ const [activeAction, setActiveAction] = useState<ApprovalAction | null>(null);
240
+
241
+ const handleAction = useCallback(
242
+ (action: ApprovalAction) => {
243
+ setActiveAction(action);
244
+ onSubmit(action);
245
+ },
246
+ [onSubmit],
247
+ );
248
+
249
+ useEffect(() => {
250
+ if (!isSubmitting) {
251
+ setActiveAction(null);
252
+ }
253
+ }, [isSubmitting]);
254
+
255
+ const categoryInfo = resolveToolCategoryFromKind(
256
+ pendingApproval.toolKind,
257
+ pendingApproval.toolName,
258
+ pendingApproval.mcpServerSlug,
259
+ );
260
+
101
261
  const parsedArgs = useMemo<Record<string, unknown> | null>(() => {
102
262
  if (!pendingApproval.argsPreview) return null;
103
263
  try {
@@ -111,198 +271,211 @@ export const ApprovalCard = memo(function ApprovalCard({
111
271
  }
112
272
  }, [pendingApproval.argsPreview]);
113
273
 
114
- const borderClass =
115
- categoryInfo.category === "delete"
116
- ? "border-destructive/30 bg-destructive-subtle"
117
- : "border-warning/30 bg-warning/5";
274
+ // APPROVE_ALL now grants a run-lifetime lease scoped to the clicked tool's
275
+ // CLASS — its MCP server for an MCP tool, else its approval category (where
276
+ // write and edit collapse to one "file edits" class, mirroring the runner's
277
+ // toolApprovalCategory). The label must name that exact class so the button
278
+ // never over-promises: it does NOT silence other classes.
279
+ const approveAllLabel = useMemo(
280
+ () => buildApproveAllLabel(categoryInfo.category, pendingApproval.mcpServerSlug),
281
+ [categoryInfo.category, pendingApproval.mcpServerSlug],
282
+ );
118
283
 
119
- return (
120
- <div
121
- role="alert"
122
- aria-label={`Approval required for ${pendingApproval.toolName}`}
123
- className={cn("rounded-lg border", borderClass, className)}
124
- >
125
- {/* Compact header row — matches ToolCallItem layout */}
126
- <div
127
- className={cn(
128
- "flex items-center gap-2 px-2.5 py-1.5 text-xs",
129
- "border-b border-border-muted",
130
- )}
131
- >
132
- <span className="shrink-0 text-warning" aria-hidden="true">
133
- <CategoryIcon />
134
- </span>
284
+ // Why-gated: the authorization provenance the server projected onto the
285
+ // PendingApproval (approval_policy_source). Smart-suppressed — the everyday
286
+ // "this category needs approval" default is noise next to the action it gates,
287
+ // so only a genuinely informative provenance (an explicit override, a
288
+ // server-marked destructive tighten) earns a line; the full phrase is on hover.
289
+ const gateReason = useMemo(
290
+ () => describeApprovalPolicySource(pendingApproval.approvalPolicySource),
291
+ [pendingApproval.approvalPolicySource],
292
+ );
293
+ const showGateReason =
294
+ gateReason != null &&
295
+ isInformativePolicySource(pendingApproval.approvalPolicySource);
135
296
 
136
- <span className="min-w-0 flex-1 flex items-baseline gap-1.5 overflow-hidden">
137
- <span className="shrink-0 font-medium text-foreground">
138
- {categoryInfo.label}
139
- </span>
140
- {primaryArg && (
141
- <span className="min-w-0 truncate font-mono text-muted-foreground">
142
- {primaryArg}
143
- </span>
144
- )}
145
- </span>
297
+ // The runner's message for a file tool ("Write file: <path>") only restates
298
+ // the header + diff, so it is suppressed; a message is shown only when it adds
299
+ // information (e.g. an MCP tool's human-authored prompt). Shell shows its
300
+ // command in the args view, never a message.
301
+ const showMessage =
302
+ Boolean(pendingApproval.message) &&
303
+ categoryInfo.category !== "shell" &&
304
+ !isFileCategory(categoryInfo.category);
146
305
 
147
- {pendingApproval.fromSubAgent && pendingApproval.subAgentName && (
148
- <span className="shrink-0 rounded bg-muted px-1 py-0.5 font-mono text-muted-foreground">
149
- via {pendingApproval.subAgentSubject || pendingApproval.subAgentName}
150
- </span>
151
- )}
306
+ const isWriteEdit =
307
+ categoryInfo.category === "write" || categoryInfo.category === "edit";
152
308
 
153
- <WaitingDuration requestedAt={pendingApproval.requestedAt} />
309
+ // The proposed write/edit content from the args preview, when the gate carries
310
+ // no authoritative file_changes capture. Drives the three-way fallback below.
311
+ const writeContent = useMemo(
312
+ () => extractWriteContentFromPreview(pendingApproval.argsPreview),
313
+ [pendingApproval.argsPreview],
314
+ );
154
315
 
155
- <span className="shrink-0 text-warning" aria-hidden="true">
156
- <ClockIcon />
157
- </span>
158
- </div>
316
+ // The decision-relevant preview. The pre-execution deny-gate is an
317
+ // apply-then-review exception: the change has not been captured yet (the tool
318
+ // is DENIED, awaiting approval), so there is no `FileChangeSet` to render.
319
+ // Instead the gate shows the PROPOSED change from the tool args: the write/edit
320
+ // content when the args carry it (path suppressed — the header has it); a
321
+ // neutral "no preview" notice when only a path is known (the resume placeholder
322
+ // case); otherwise the shared args view for non-file tools. (Captured
323
+ // file-review — the apply-then-review path — renders via FileReviewCard, not
324
+ // here.)
325
+ let preview: ReactNode = null;
326
+ if (isWriteEdit && writeContent === null) {
327
+ // No proposed content in the args. A whole-file write is modeled as a create
328
+ // throughout the runner (FILE_WRITE -> CREATE), so the authoritative toolKind
329
+ // lets the gate say plainly that a new file is being written rather than the
330
+ // misleading non-committal "no preview". An edit (modify) cannot be proven a
331
+ // create, so it keeps the non-committal notice.
332
+ const kind =
333
+ pendingApproval.toolKind === ToolKind.FILE_WRITE ? "create" : "no-preview";
334
+ preview = <EmptyChangeNotice kind={kind} />;
335
+ } else if (parsedArgs) {
336
+ preview = (
337
+ <ToolArgsView
338
+ toolName={pendingApproval.toolName}
339
+ args={parsedArgs}
340
+ mcpServerSlug={pendingApproval.mcpServerSlug}
341
+ showFileName={!isWriteEdit}
342
+ />
343
+ );
344
+ }
159
345
 
160
- {/* Body */}
161
- <div className="px-3 py-2.5 space-y-2">
162
- {pendingApproval.message && categoryInfo.category !== "shell" && (
163
- <p className="text-xs text-foreground">
164
- {pendingApproval.message}
165
- </p>
166
- )}
167
-
168
- {/* Category-specific args preview — shared with ToolCallDetail */}
169
- {parsedArgs && (
170
- <ToolArgsView
171
- toolName={pendingApproval.toolName}
172
- args={parsedArgs}
173
- mcpServerSlug={pendingApproval.mcpServerSlug}
174
- />
175
- )}
176
-
177
- {/* Action buttons */}
178
- <div className="flex items-center gap-2 pt-1">
179
- <ActionButton
180
- label="Approve"
181
- action={ApprovalAction.APPROVE}
182
- activeAction={activeAction}
183
- isSubmitting={isSubmitting}
184
- onClick={handleAction}
185
- variant="approve"
186
- cursorTarget="approve-button"
187
- />
188
- {/* Subordinate escalation: approve this call and stop asking for the
189
- rest of the run. Kept visually quieter than the primary Approve so
190
- it never competes with the per-call decision the user is making. */}
191
- <ActionButton
192
- label="Approve & don't ask again"
193
- action={ApprovalAction.APPROVE_ALL}
194
- activeAction={activeAction}
195
- isSubmitting={isSubmitting}
196
- onClick={handleAction}
197
- variant="approveAll"
198
- cursorTarget="approve-all-button"
199
- />
200
- <ActionButton
201
- label="Skip"
202
- action={ApprovalAction.SKIP}
203
- activeAction={activeAction}
204
- isSubmitting={isSubmitting}
205
- onClick={handleAction}
206
- variant="skip"
207
- />
208
- <ActionButton
209
- label="Reject"
210
- action={ApprovalAction.REJECT}
211
- activeAction={activeAction}
212
- isSubmitting={isSubmitting}
213
- onClick={handleAction}
214
- variant="reject"
215
- />
216
- </div>
217
- </div>
218
- </div>
219
- );
220
- });
346
+ return (
347
+ <div className={cn("px-3 py-2.5 space-y-2", className)}>
348
+ {showMessage && (
349
+ <p className="text-xs text-foreground">{pendingApproval.message}</p>
350
+ )}
221
351
 
222
- // ---------------------------------------------------------------------------
223
- // Internal sub-components
224
- // ---------------------------------------------------------------------------
352
+ {preview}
225
353
 
226
- function ActionButton({
227
- label,
228
- action,
229
- activeAction,
230
- isSubmitting,
231
- onClick,
232
- variant,
233
- cursorTarget,
234
- }: {
235
- label: string;
236
- action: ApprovalAction;
237
- activeAction: ApprovalAction | null;
238
- isSubmitting: boolean;
239
- onClick: (action: ApprovalAction) => void;
240
- variant: "approve" | "approveAll" | "skip" | "reject";
241
- cursorTarget?: string;
242
- }) {
243
- const isActive = activeAction === action;
244
- const disabled = isSubmitting;
245
-
246
- const variantClasses: Record<typeof variant, string> = {
247
- approve: cn(
248
- "bg-success text-success-foreground hover:bg-success/90",
249
- "disabled:bg-success/50 disabled:text-success-foreground/70",
250
- ),
251
- approveAll: cn(
252
- "text-success hover:bg-success/10",
253
- "disabled:text-success/50",
254
- ),
255
- skip: cn(
256
- "border border-border bg-background text-muted-foreground hover:bg-muted hover:text-foreground",
257
- "disabled:bg-muted-faint disabled:text-muted-foreground-faint",
258
- ),
259
- reject: cn(
260
- "bg-destructive text-destructive-foreground hover:bg-destructive-hover",
261
- "disabled:bg-destructive-subtle0 disabled:text-destructive-foreground/70",
262
- ),
263
- };
354
+ {/* Consent stated at grant time (DD-28): approving a shell command covers
355
+ the files it creates or changes — when the turn's mutations all come
356
+ from approved commands, the file-change set is kept automatically and
357
+ never re-gates. A mixed turn (commands + file-tool edits) still
358
+ reviews everything, so this line under-promises rather than over-. */}
359
+ {categoryInfo.category === "shell" && (
360
+ <p className="text-[11px] italic text-muted-foreground">
361
+ Files this command creates or changes are covered by this approval —
362
+ they are kept automatically, with no second review.
363
+ </p>
364
+ )}
264
365
 
265
- return (
266
- <button
267
- type="button"
268
- disabled={disabled}
269
- onClick={() => onClick(action)}
270
- aria-label={label}
271
- data-cursor-target={cursorTarget}
272
- className={cn(
273
- "inline-flex items-center justify-center gap-1.5 rounded-md px-3 py-1.5 text-xs font-medium transition-colors",
274
- "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
275
- "disabled:cursor-not-allowed",
276
- variantClasses[variant],
366
+ {/* Decision actions — quiet, Cursor-grade hierarchy: one neutral-chip
367
+ primary (Approve), ghost Skip, ghost-danger Reject. The broad
368
+ run-lifetime lease (Approve all) is demoted to the far right via
369
+ `ml-auto` so it never competes with — or is mis-clicked for — the
370
+ per-call Approve (Fitts/Hick). */}
371
+ <div className="flex items-center gap-2 pt-1">
372
+ <DecisionButton
373
+ label="Approve"
374
+ variant="primary"
375
+ onClick={() => handleAction(ApprovalAction.APPROVE)}
376
+ isActive={activeAction === ApprovalAction.APPROVE}
377
+ isSubmitting={isSubmitting}
378
+ cursorTarget="approve-button"
379
+ />
380
+ <DecisionButton
381
+ label="Skip"
382
+ variant="ghost"
383
+ onClick={() => handleAction(ApprovalAction.SKIP)}
384
+ isActive={activeAction === ApprovalAction.SKIP}
385
+ isSubmitting={isSubmitting}
386
+ />
387
+ <DecisionButton
388
+ label="Reject"
389
+ variant="danger"
390
+ onClick={() => handleAction(ApprovalAction.REJECT)}
391
+ isActive={activeAction === ApprovalAction.REJECT}
392
+ isSubmitting={isSubmitting}
393
+ />
394
+ {/* Subordinate escalation: approve this call AND stop asking for THIS
395
+ CLASS of tool for the rest of the run (its MCP server, or its file
396
+ edit / delete / shell category) — other classes keep prompting. The
397
+ label names the leased class so the scope is never a surprise. */}
398
+ <DecisionButton
399
+ label={approveAllLabel}
400
+ variant="ghost"
401
+ onClick={() => handleAction(ApprovalAction.APPROVE_ALL)}
402
+ isActive={activeAction === ApprovalAction.APPROVE_ALL}
403
+ isSubmitting={isSubmitting}
404
+ className="ml-auto"
405
+ cursorTarget="approve-all-button"
406
+ />
407
+ </div>
408
+
409
+ {/* De-emphasized provenance, trailing the decision it explains. Only the
410
+ informative reasons reach here (see showGateReason); the full phrase is
411
+ on hover. */}
412
+ {showGateReason && (
413
+ <p
414
+ className="text-[11px] italic text-muted-foreground"
415
+ title={gateReason ?? undefined}
416
+ data-cursor-target="approval-gate-reason"
417
+ >
418
+ {gateReason}
419
+ </p>
277
420
  )}
278
- >
279
- {isActive && isSubmitting ? <SpinnerIcon /> : null}
280
- {label}
281
- </button>
421
+
422
+ {/* A failed decision is surfaced HERE, in-card, beside the actions — not
423
+ via the session's global banner. A thread can hold many gates (each
424
+ inline tool row, plus the bottom backstop), so a failure must name the
425
+ one it belongs to; the optimistic spinner has already reverted, and
426
+ this explains the snap-back. Shared with FileReviewCard. */}
427
+ {error && (
428
+ <InCardDecisionError
429
+ error={error}
430
+ leadIn="submit decision"
431
+ cursorTarget="approval-error"
432
+ />
433
+ )}
434
+ </div>
282
435
  );
283
436
  }
284
437
 
285
- function WaitingDuration({ requestedAt }: { requestedAt: string }) {
286
- const startMs = useMemo(() => {
287
- if (!requestedAt) return null;
288
- const t = new Date(requestedAt).getTime();
289
- return Number.isNaN(t) ? null : t;
290
- }, [requestedAt]);
438
+ // ---------------------------------------------------------------------------
439
+ // Internal helpers
440
+ // ---------------------------------------------------------------------------
291
441
 
292
- const [elapsed, setElapsed] = useState<number | null>(null);
442
+ /**
443
+ * Builds the truthful APPROVE_ALL button label for a tool's lease class.
444
+ *
445
+ * The lease an APPROVE_ALL grants is scoped to ONE class: the MCP server for an
446
+ * MCP tool, otherwise the approval category (write/delete/shell) — where the
447
+ * presentation "write" and "edit" categories collapse to a single "file edits"
448
+ * class, exactly as the runner's `toolApprovalCategory` collapses FILE_WRITE and
449
+ * FILE_EDIT to "write". The label names that class so the button never implies a
450
+ * broader effect than the lease actually has. The generic fallback only applies
451
+ * to a tool with no leasable class, which is not normally gated for approval.
452
+ */
453
+ function buildApproveAllLabel(
454
+ category: ToolCategory,
455
+ mcpServerSlug: string,
456
+ ): string {
457
+ if (mcpServerSlug) {
458
+ return `Approve all ${mcpServerSlug} tools`;
459
+ }
460
+ switch (category) {
461
+ case "shell":
462
+ return "Approve all shell commands";
463
+ case "delete":
464
+ return "Approve all file deletions";
465
+ case "write":
466
+ case "edit":
467
+ return "Approve all file edits";
468
+ default:
469
+ return "Approve all of this kind";
470
+ }
471
+ }
293
472
 
294
- useEffect(() => {
295
- if (startMs === null) {
296
- setElapsed(null);
297
- return;
298
- }
299
- setElapsed(Math.max(0, Date.now() - startMs));
300
- const id = setInterval(() => {
301
- setElapsed(Math.max(0, Date.now() - startMs));
302
- }, 1000);
303
- return () => clearInterval(id);
304
- }, [startMs]);
473
+ // ---------------------------------------------------------------------------
474
+ // Internal sub-components
475
+ // ---------------------------------------------------------------------------
305
476
 
477
+ function WaitingDuration({ requestedAt }: { requestedAt: string }) {
478
+ const elapsed = useElapsedSince(requestedAt);
306
479
  if (elapsed === null) return null;
307
480
 
308
481
  return (
@@ -312,24 +485,6 @@ function WaitingDuration({ requestedAt }: { requestedAt: string }) {
312
485
  );
313
486
  }
314
487
 
315
- function formatElapsed(ms: number): string {
316
- if (ms < 1000) return "just now";
317
- const seconds = Math.floor(ms / 1000);
318
- if (seconds < 60) return `${seconds}s`;
319
- const minutes = Math.floor(seconds / 60);
320
- const remainingSeconds = seconds % 60;
321
- if (minutes < 60) {
322
- return remainingSeconds > 0
323
- ? `${minutes}m ${remainingSeconds}s`
324
- : `${minutes}m`;
325
- }
326
- const hours = Math.floor(minutes / 60);
327
- const remainingMinutes = minutes % 60;
328
- return remainingMinutes > 0
329
- ? `${hours}h ${remainingMinutes}m`
330
- : `${hours}h`;
331
- }
332
-
333
488
  // ---------------------------------------------------------------------------
334
489
  // Inline SVG icons
335
490
  // ---------------------------------------------------------------------------
@@ -351,20 +506,3 @@ function ClockIcon() {
351
506
  </svg>
352
507
  );
353
508
  }
354
-
355
- function SpinnerIcon() {
356
- return (
357
- <svg
358
- width="10"
359
- height="10"
360
- viewBox="0 0 12 12"
361
- fill="none"
362
- stroke="currentColor"
363
- strokeWidth="1.5"
364
- className="animate-spin"
365
- aria-hidden="true"
366
- >
367
- <path d="M6 1.5A4.5 4.5 0 1 1 1.5 6" strokeLinecap="round" />
368
- </svg>
369
- );
370
- }