@stigmer/react 3.0.9-dev.20260616060535 → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (426) hide show
  1. package/execution/ApprovalCard.d.ts +47 -3
  2. package/execution/ApprovalCard.d.ts.map +1 -1
  3. package/execution/ApprovalCard.js +138 -70
  4. package/execution/ApprovalCard.js.map +1 -1
  5. package/execution/ApprovalContext.d.ts +72 -0
  6. package/execution/ApprovalContext.d.ts.map +1 -0
  7. package/execution/ApprovalContext.js +41 -0
  8. package/execution/ApprovalContext.js.map +1 -0
  9. package/execution/ArtifactCard.d.ts.map +1 -1
  10. package/execution/ArtifactCard.js +3 -1
  11. package/execution/ArtifactCard.js.map +1 -1
  12. package/execution/ArtifactPreviewModal.d.ts.map +1 -1
  13. package/execution/ArtifactPreviewModal.js +5 -3
  14. package/execution/ArtifactPreviewModal.js.map +1 -1
  15. package/execution/EmptyChangeNotice.d.ts +38 -0
  16. package/execution/EmptyChangeNotice.d.ts.map +1 -0
  17. package/execution/EmptyChangeNotice.js +27 -0
  18. package/execution/EmptyChangeNotice.js.map +1 -0
  19. package/execution/FileChangesView.d.ts +81 -0
  20. package/execution/FileChangesView.d.ts.map +1 -0
  21. package/execution/FileChangesView.js +164 -0
  22. package/execution/FileChangesView.js.map +1 -0
  23. package/execution/FilePathLink.d.ts +24 -4
  24. package/execution/FilePathLink.d.ts.map +1 -1
  25. package/execution/FilePathLink.js +31 -9
  26. package/execution/FilePathLink.js.map +1 -1
  27. package/execution/FileReviewCard.d.ts +111 -0
  28. package/execution/FileReviewCard.d.ts.map +1 -0
  29. package/execution/FileReviewCard.js +558 -0
  30. package/execution/FileReviewCard.js.map +1 -0
  31. package/execution/FileReviewContext.d.ts +35 -0
  32. package/execution/FileReviewContext.d.ts.map +1 -0
  33. package/execution/FileReviewContext.js +23 -0
  34. package/execution/FileReviewContext.js.map +1 -0
  35. package/execution/FileReviewDock.d.ts +73 -0
  36. package/execution/FileReviewDock.d.ts.map +1 -0
  37. package/execution/FileReviewDock.js +65 -0
  38. package/execution/FileReviewDock.js.map +1 -0
  39. package/execution/McpToolDetail.d.ts.map +1 -1
  40. package/execution/McpToolDetail.js +38 -5
  41. package/execution/McpToolDetail.js.map +1 -1
  42. package/execution/MessageThread.d.ts +42 -3
  43. package/execution/MessageThread.d.ts.map +1 -1
  44. package/execution/MessageThread.js +263 -18
  45. package/execution/MessageThread.js.map +1 -1
  46. package/execution/PlanArtifactCard.d.ts.map +1 -1
  47. package/execution/PlanArtifactCard.js +3 -1
  48. package/execution/PlanArtifactCard.js.map +1 -1
  49. package/execution/PlanCompletionCard.d.ts +1 -1
  50. package/execution/PlanCompletionCard.js +1 -1
  51. package/execution/ResultView.d.ts +19 -1
  52. package/execution/ResultView.d.ts.map +1 -1
  53. package/execution/ResultView.js +141 -40
  54. package/execution/ResultView.js.map +1 -1
  55. package/execution/SubAgentSection.d.ts.map +1 -1
  56. package/execution/SubAgentSection.js +45 -6
  57. package/execution/SubAgentSection.js.map +1 -1
  58. package/execution/TerminalSession.d.ts +34 -0
  59. package/execution/TerminalSession.d.ts.map +1 -0
  60. package/execution/TerminalSession.js +34 -0
  61. package/execution/TerminalSession.js.map +1 -0
  62. package/execution/TodoCard.d.ts +53 -0
  63. package/execution/TodoCard.d.ts.map +1 -0
  64. package/execution/TodoCard.js +77 -0
  65. package/execution/TodoCard.js.map +1 -0
  66. package/execution/ToolArgsView.d.ts +7 -1
  67. package/execution/ToolArgsView.d.ts.map +1 -1
  68. package/execution/ToolArgsView.js +10 -9
  69. package/execution/ToolArgsView.js.map +1 -1
  70. package/execution/ToolCallDetail.d.ts +10 -1
  71. package/execution/ToolCallDetail.d.ts.map +1 -1
  72. package/execution/ToolCallDetail.js +66 -19
  73. package/execution/ToolCallDetail.js.map +1 -1
  74. package/execution/ToolCallGroup.d.ts +28 -18
  75. package/execution/ToolCallGroup.d.ts.map +1 -1
  76. package/execution/ToolCallGroup.js +39 -148
  77. package/execution/ToolCallGroup.js.map +1 -1
  78. package/execution/ToolCallItem.d.ts +36 -8
  79. package/execution/ToolCallItem.d.ts.map +1 -1
  80. package/execution/ToolCallItem.js +140 -21
  81. package/execution/ToolCallItem.js.map +1 -1
  82. package/execution/ToolRunGroup.d.ts +48 -0
  83. package/execution/ToolRunGroup.d.ts.map +1 -0
  84. package/execution/ToolRunGroup.js +168 -0
  85. package/execution/ToolRunGroup.js.map +1 -0
  86. package/execution/approval-provenance.d.ts +2 -0
  87. package/execution/approval-provenance.d.ts.map +1 -0
  88. package/execution/approval-provenance.js +6 -0
  89. package/execution/approval-provenance.js.map +1 -0
  90. package/execution/artifact-utils.d.ts +0 -10
  91. package/execution/artifact-utils.d.ts.map +1 -1
  92. package/execution/artifact-utils.js +0 -17
  93. package/execution/artifact-utils.js.map +1 -1
  94. package/execution/execution-phases.d.ts +1 -6
  95. package/execution/execution-phases.d.ts.map +1 -1
  96. package/execution/execution-phases.js +4 -14
  97. package/execution/execution-phases.js.map +1 -1
  98. package/execution/file-path-resolver.d.ts +28 -0
  99. package/execution/file-path-resolver.d.ts.map +1 -1
  100. package/execution/file-path-resolver.js +24 -0
  101. package/execution/file-path-resolver.js.map +1 -1
  102. package/execution/file-review-status.d.ts +127 -0
  103. package/execution/file-review-status.d.ts.map +1 -0
  104. package/execution/file-review-status.js +135 -0
  105. package/execution/file-review-status.js.map +1 -0
  106. package/execution/index.d.ts +39 -5
  107. package/execution/index.d.ts.map +1 -1
  108. package/execution/index.js +23 -3
  109. package/execution/index.js.map +1 -1
  110. package/execution/segment-tool-calls.d.ts +46 -0
  111. package/execution/segment-tool-calls.d.ts.map +1 -0
  112. package/execution/segment-tool-calls.js +71 -0
  113. package/execution/segment-tool-calls.js.map +1 -0
  114. package/execution/tool-categories.d.ts +62 -1
  115. package/execution/tool-categories.d.ts.map +1 -1
  116. package/execution/tool-categories.js +96 -2
  117. package/execution/tool-categories.js.map +1 -1
  118. package/execution/tool-presenter.d.ts +38 -2
  119. package/execution/tool-presenter.d.ts.map +1 -1
  120. package/execution/tool-presenter.js +23 -1
  121. package/execution/tool-presenter.js.map +1 -1
  122. package/execution/tool-rendering-primitives.d.ts.map +1 -1
  123. package/execution/tool-rendering-primitives.js +3 -2
  124. package/execution/tool-rendering-primitives.js.map +1 -1
  125. package/execution/useArtifactDownload.d.ts +44 -0
  126. package/execution/useArtifactDownload.d.ts.map +1 -0
  127. package/execution/useArtifactDownload.js +80 -0
  128. package/execution/useArtifactDownload.js.map +1 -0
  129. package/execution/useArtifactDownloadUrl.d.ts +67 -0
  130. package/execution/useArtifactDownloadUrl.d.ts.map +1 -0
  131. package/execution/useArtifactDownloadUrl.js +59 -0
  132. package/execution/useArtifactDownloadUrl.js.map +1 -0
  133. package/execution/useFileChangeContent.d.ts +61 -0
  134. package/execution/useFileChangeContent.d.ts.map +1 -0
  135. package/execution/useFileChangeContent.js +89 -0
  136. package/execution/useFileChangeContent.js.map +1 -0
  137. package/execution/useFileReview.d.ts +89 -0
  138. package/execution/useFileReview.d.ts.map +1 -0
  139. package/execution/useFileReview.js +106 -0
  140. package/execution/useFileReview.js.map +1 -0
  141. package/execution/useSubmitApproval.d.ts +17 -2
  142. package/execution/useSubmitApproval.d.ts.map +1 -1
  143. package/execution/useSubmitApproval.js +27 -23
  144. package/execution/useSubmitApproval.js.map +1 -1
  145. package/execution/useToolOutputContent.d.ts +44 -0
  146. package/execution/useToolOutputContent.d.ts.map +1 -0
  147. package/execution/useToolOutputContent.js +31 -0
  148. package/execution/useToolOutputContent.js.map +1 -0
  149. package/index.d.ts +6 -6
  150. package/index.d.ts.map +1 -1
  151. package/index.js +3 -3
  152. package/index.js.map +1 -1
  153. package/internal/ApprovalPeekBar.d.ts +23 -0
  154. package/internal/ApprovalPeekBar.d.ts.map +1 -0
  155. package/internal/ApprovalPeekBar.js +25 -0
  156. package/internal/ApprovalPeekBar.js.map +1 -0
  157. package/internal/BoundedContent.d.ts +49 -0
  158. package/internal/BoundedContent.d.ts.map +1 -0
  159. package/internal/BoundedContent.js +50 -0
  160. package/internal/BoundedContent.js.map +1 -0
  161. package/internal/DecisionButton.d.ts +47 -0
  162. package/internal/DecisionButton.d.ts.map +1 -0
  163. package/internal/DecisionButton.js +42 -0
  164. package/internal/DecisionButton.js.map +1 -0
  165. package/internal/InCardDecisionError.d.ts +31 -0
  166. package/internal/InCardDecisionError.d.ts.map +1 -0
  167. package/internal/InCardDecisionError.js +23 -0
  168. package/internal/InCardDecisionError.js.map +1 -0
  169. package/internal/RevealToggle.d.ts +36 -0
  170. package/internal/RevealToggle.d.ts.map +1 -0
  171. package/internal/RevealToggle.js +32 -0
  172. package/internal/RevealToggle.js.map +1 -0
  173. package/internal/VirtualizedThread.d.ts +7 -1
  174. package/internal/VirtualizedThread.d.ts.map +1 -1
  175. package/internal/VirtualizedThread.js +7 -3
  176. package/internal/VirtualizedThread.js.map +1 -1
  177. package/internal/useAutoDisclosure.d.ts +34 -0
  178. package/internal/useAutoDisclosure.d.ts.map +1 -0
  179. package/internal/useAutoDisclosure.js +53 -0
  180. package/internal/useAutoDisclosure.js.map +1 -0
  181. package/internal/useElapsedSince.d.ts +30 -0
  182. package/internal/useElapsedSince.d.ts.map +1 -0
  183. package/internal/useElapsedSince.js +70 -0
  184. package/internal/useElapsedSince.js.map +1 -0
  185. package/internal/useIsOverflowing.d.ts +30 -0
  186. package/internal/useIsOverflowing.d.ts.map +1 -0
  187. package/internal/useIsOverflowing.js +47 -0
  188. package/internal/useIsOverflowing.js.map +1 -0
  189. package/internal/useIsTextTruncated.d.ts +24 -0
  190. package/internal/useIsTextTruncated.d.ts.map +1 -0
  191. package/internal/useIsTextTruncated.js +41 -0
  192. package/internal/useIsTextTruncated.js.map +1 -0
  193. package/internal/useKeyedSubmission.d.ts +49 -0
  194. package/internal/useKeyedSubmission.d.ts.map +1 -0
  195. package/internal/useKeyedSubmission.js +67 -0
  196. package/internal/useKeyedSubmission.js.map +1 -0
  197. package/package.json +4 -4
  198. package/session/SessionViewer.d.ts +2 -2
  199. package/session/SessionViewer.d.ts.map +1 -1
  200. package/session/SessionViewer.js +10 -4
  201. package/session/SessionViewer.js.map +1 -1
  202. package/session/index.d.ts +2 -0
  203. package/session/index.d.ts.map +1 -1
  204. package/session/index.js +1 -0
  205. package/session/index.js.map +1 -1
  206. package/session/inspector/ChangesTab.d.ts +8 -5
  207. package/session/inspector/ChangesTab.d.ts.map +1 -1
  208. package/session/inspector/ChangesTab.js +11 -8
  209. package/session/inspector/ChangesTab.js.map +1 -1
  210. package/session/inspector/SessionInspector.d.ts +4 -4
  211. package/session/inspector/SessionInspector.d.ts.map +1 -1
  212. package/session/inspector/SessionInspector.js +4 -4
  213. package/session/inspector/SessionInspector.js.map +1 -1
  214. package/session/inspector/index.d.ts +0 -2
  215. package/session/inspector/index.d.ts.map +1 -1
  216. package/session/inspector/index.js +0 -1
  217. package/session/inspector/index.js.map +1 -1
  218. package/session/inspector/useSessionInspector.d.ts +8 -4
  219. package/session/inspector/useSessionInspector.d.ts.map +1 -1
  220. package/session/inspector/useSessionInspector.js +27 -20
  221. package/session/inspector/useSessionInspector.js.map +1 -1
  222. package/session/useSessionConversation.d.ts +37 -3
  223. package/session/useSessionConversation.d.ts.map +1 -1
  224. package/session/useSessionConversation.js +18 -2
  225. package/session/useSessionConversation.js.map +1 -1
  226. package/session/useSessionFileChanges.d.ts +74 -0
  227. package/session/useSessionFileChanges.d.ts.map +1 -0
  228. package/session/useSessionFileChanges.js +178 -0
  229. package/session/useSessionFileChanges.js.map +1 -0
  230. package/src/__tests__/styles-border-layer-invariant.test.ts +105 -0
  231. package/src/execution/ApprovalCard.tsx +368 -230
  232. package/src/execution/ApprovalContext.ts +109 -0
  233. package/src/execution/ArtifactCard.tsx +10 -6
  234. package/src/execution/ArtifactPreviewModal.tsx +12 -6
  235. package/src/execution/EmptyChangeNotice.tsx +62 -0
  236. package/src/execution/FileChangesView.tsx +383 -0
  237. package/src/execution/FilePathLink.tsx +65 -12
  238. package/src/execution/FileReviewCard.tsx +1118 -0
  239. package/src/execution/FileReviewContext.ts +53 -0
  240. package/src/execution/FileReviewDock.tsx +157 -0
  241. package/src/execution/McpToolDetail.tsx +98 -32
  242. package/src/execution/MessageThread.tsx +374 -9
  243. package/src/execution/PlanArtifactCard.tsx +9 -6
  244. package/src/execution/PlanCompletionCard.tsx +1 -1
  245. package/src/execution/ResultView.tsx +333 -162
  246. package/src/execution/SubAgentSection.tsx +58 -11
  247. package/src/execution/TerminalSession.tsx +91 -0
  248. package/src/execution/TodoCard.tsx +186 -0
  249. package/src/execution/ToolArgsView.tsx +28 -17
  250. package/src/execution/ToolCallDetail.tsx +137 -26
  251. package/src/execution/ToolCallGroup.tsx +68 -296
  252. package/src/execution/ToolCallItem.tsx +265 -67
  253. package/src/execution/ToolRunGroup.tsx +302 -0
  254. package/src/execution/__tests__/ApprovalCard.test.tsx +395 -5
  255. package/src/execution/__tests__/ArtifactPreviewModal.test.tsx +0 -1
  256. package/src/execution/__tests__/EmptyChangeNotice.test.tsx +24 -0
  257. package/src/execution/__tests__/FileChangesView.test.tsx +296 -0
  258. package/src/execution/__tests__/FilePathLink.test.tsx +92 -0
  259. package/src/execution/__tests__/FileReviewCard.test.tsx +1044 -0
  260. package/src/execution/__tests__/FileReviewDock.test.tsx +134 -0
  261. package/src/execution/__tests__/McpToolDetail.test.tsx +79 -0
  262. package/src/execution/__tests__/MessageThread.test.tsx +324 -0
  263. package/src/execution/__tests__/PlanArtifactCard.test.tsx +37 -16
  264. package/src/execution/__tests__/ResultView.test.tsx +280 -2
  265. package/src/execution/__tests__/SubAgentSection.test.tsx +180 -0
  266. package/src/execution/__tests__/TerminalSession.test.tsx +72 -0
  267. package/src/execution/__tests__/TodoCard.test.tsx +102 -0
  268. package/src/execution/__tests__/ToolArgsView.test.tsx +48 -0
  269. package/src/execution/__tests__/ToolCallDetail.test.tsx +195 -0
  270. package/src/execution/__tests__/ToolCallGroup.test.tsx +132 -0
  271. package/src/execution/__tests__/ToolCallItem.test.tsx +521 -0
  272. package/src/execution/__tests__/ToolRunGroup.test.tsx +137 -0
  273. package/src/execution/__tests__/blanked-narration.test.tsx +149 -0
  274. package/src/execution/__tests__/buildThreadItems-approvals.test.ts +304 -0
  275. package/src/execution/__tests__/buildThreadItems-file-review.test.ts +331 -0
  276. package/src/execution/__tests__/file-path-resolver.test.ts +44 -0
  277. package/src/execution/__tests__/file-review-status.test.ts +343 -0
  278. package/src/execution/__tests__/segment-tool-calls.test.ts +124 -0
  279. package/src/execution/__tests__/subagent-file-review-badge.test.tsx +273 -0
  280. package/src/execution/__tests__/thread-keys.test.ts +195 -0
  281. package/src/execution/__tests__/tool-presenter.test.tsx +95 -0
  282. package/src/execution/__tests__/useArtifactDownload.test.tsx +67 -0
  283. package/src/execution/__tests__/useArtifactDownloadUrl.test.tsx +51 -0
  284. package/src/execution/__tests__/useFileChangeContent.test.tsx +240 -0
  285. package/src/execution/__tests__/useFileReview.test.tsx +236 -0
  286. package/src/execution/__tests__/useSubmitApproval.test.tsx +165 -0
  287. package/src/execution/__tests__/useToolOutputContent.test.tsx +82 -0
  288. package/src/execution/approval-provenance.ts +8 -0
  289. package/src/execution/artifact-utils.ts +0 -18
  290. package/src/execution/execution-phases.ts +4 -16
  291. package/src/execution/file-path-resolver.ts +38 -0
  292. package/src/execution/file-review-status.ts +219 -0
  293. package/src/execution/index.ts +84 -4
  294. package/src/execution/segment-tool-calls.ts +101 -0
  295. package/src/execution/tool-categories.ts +128 -1
  296. package/src/execution/tool-presenter.ts +58 -3
  297. package/src/execution/tool-rendering-primitives.tsx +11 -14
  298. package/src/execution/useArtifactDownload.ts +109 -0
  299. package/src/execution/useArtifactDownloadUrl.ts +116 -0
  300. package/src/execution/useFileChangeContent.ts +149 -0
  301. package/src/execution/useFileReview.ts +197 -0
  302. package/src/execution/useSubmitApproval.ts +49 -27
  303. package/src/execution/useToolOutputContent.ts +70 -0
  304. package/src/index.ts +60 -1
  305. package/src/internal/ApprovalPeekBar.tsx +73 -0
  306. package/src/internal/BoundedContent.tsx +97 -0
  307. package/src/internal/DecisionButton.tsx +127 -0
  308. package/src/internal/InCardDecisionError.tsx +47 -0
  309. package/src/internal/RevealToggle.tsx +91 -0
  310. package/src/internal/VirtualizedThread.tsx +28 -2
  311. package/src/internal/__tests__/ApprovalPeekBar.test.tsx +34 -0
  312. package/src/internal/__tests__/BoundedContent.test.tsx +92 -0
  313. package/src/internal/__tests__/DecisionButton.test.tsx +107 -0
  314. package/src/internal/__tests__/RevealToggle.test.tsx +61 -0
  315. package/src/internal/__tests__/useAutoDisclosure.test.ts +74 -0
  316. package/src/internal/__tests__/useElapsedSince.test.tsx +68 -0
  317. package/src/internal/__tests__/useIsOverflowing.test.ts +36 -0
  318. package/src/internal/__tests__/useKeyedSubmission.test.tsx +126 -0
  319. package/src/internal/useAutoDisclosure.ts +58 -0
  320. package/src/internal/useElapsedSince.ts +72 -0
  321. package/src/internal/useIsOverflowing.ts +61 -0
  322. package/src/internal/useIsTextTruncated.ts +55 -0
  323. package/src/internal/useKeyedSubmission.ts +101 -0
  324. package/src/session/SessionViewer.tsx +21 -4
  325. package/src/session/__tests__/SessionViewer-fileReviewDock.test.tsx +198 -0
  326. package/src/session/__tests__/audienceWiring.test.tsx +4 -0
  327. package/src/session/__tests__/useSessionFileChanges.test.ts +303 -0
  328. package/src/session/index.ts +3 -0
  329. package/src/session/inspector/ChangesTab.tsx +20 -17
  330. package/src/session/inspector/SessionInspector.tsx +12 -10
  331. package/src/session/inspector/__tests__/ArtifactsTab.test.tsx +0 -1
  332. package/src/session/inspector/__tests__/ChangesTab.test.tsx +38 -0
  333. package/src/session/inspector/__tests__/useSessionInspector.test.ts +46 -61
  334. package/src/session/inspector/index.ts +0 -3
  335. package/src/session/inspector/useSessionInspector.ts +27 -20
  336. package/src/session/useSessionConversation.ts +80 -3
  337. package/src/session/useSessionFileChanges.ts +207 -0
  338. package/src/styles.css +35 -10
  339. package/src/version-history/DiffViewer.tsx +10 -7
  340. package/src/version-history/UnifiedDiffView.tsx +66 -0
  341. package/src/version-history/__tests__/DiffViewer.test.tsx +57 -0
  342. package/src/version-history/__tests__/UnifiedDiffView.test.tsx +50 -0
  343. package/src/version-history/__tests__/computeDiff.test.ts +35 -0
  344. package/src/version-history/__tests__/parseUnifiedDiff.test.ts +82 -0
  345. package/src/version-history/computeDiff.ts +6 -37
  346. package/src/version-history/diff-hunks.ts +63 -0
  347. package/src/version-history/index.ts +1 -0
  348. package/src/version-history/parseUnifiedDiff.ts +34 -0
  349. package/src/workflow/WorkflowExecutionApprovalCard.tsx +49 -24
  350. package/src/workflow/WorkflowExecutionTimeline.tsx +15 -4
  351. package/src/workflow/WorkflowExecutionTimelineEvent.tsx +8 -3
  352. package/src/workflow/WorkflowExecutionViewer.tsx +72 -28
  353. package/src/workflow/WorkflowFileReviewList.tsx +152 -0
  354. package/src/workflow/WorkflowTaskApprovalCard.tsx +37 -85
  355. package/src/workflow/__tests__/ExecutionInspector.test.tsx +30 -2
  356. package/src/workflow/__tests__/WorkflowExecutionApprovalCard.test.tsx +129 -0
  357. package/src/workflow/__tests__/WorkflowFileReviewList.test.tsx +129 -0
  358. package/src/workflow/__tests__/WorkflowTaskApprovalCard.test.tsx +43 -11
  359. package/src/workflow/__tests__/useWorkflowExecutionActions.test.tsx +276 -0
  360. package/src/workflow/execution-inspector/ExecutionInspector.tsx +42 -18
  361. package/src/workflow/index.ts +5 -0
  362. package/src/workflow/useWorkflowExecutionActions.ts +177 -29
  363. package/styles.css +1 -1
  364. package/version-history/DiffViewer.d.ts.map +1 -1
  365. package/version-history/DiffViewer.js +9 -3
  366. package/version-history/DiffViewer.js.map +1 -1
  367. package/version-history/UnifiedDiffView.d.ts +29 -0
  368. package/version-history/UnifiedDiffView.d.ts.map +1 -0
  369. package/version-history/UnifiedDiffView.js +36 -0
  370. package/version-history/UnifiedDiffView.js.map +1 -0
  371. package/version-history/computeDiff.d.ts +3 -1
  372. package/version-history/computeDiff.d.ts.map +1 -1
  373. package/version-history/computeDiff.js +5 -35
  374. package/version-history/computeDiff.js.map +1 -1
  375. package/version-history/diff-hunks.d.ts +21 -0
  376. package/version-history/diff-hunks.d.ts.map +1 -0
  377. package/version-history/diff-hunks.js +58 -0
  378. package/version-history/diff-hunks.js.map +1 -0
  379. package/version-history/index.d.ts +1 -0
  380. package/version-history/index.d.ts.map +1 -1
  381. package/version-history/index.js +1 -0
  382. package/version-history/index.js.map +1 -1
  383. package/version-history/parseUnifiedDiff.d.ts +22 -0
  384. package/version-history/parseUnifiedDiff.d.ts.map +1 -0
  385. package/version-history/parseUnifiedDiff.js +34 -0
  386. package/version-history/parseUnifiedDiff.js.map +1 -0
  387. package/workflow/WorkflowExecutionApprovalCard.d.ts +8 -1
  388. package/workflow/WorkflowExecutionApprovalCard.d.ts.map +1 -1
  389. package/workflow/WorkflowExecutionApprovalCard.js +17 -3
  390. package/workflow/WorkflowExecutionApprovalCard.js.map +1 -1
  391. package/workflow/WorkflowExecutionTimeline.d.ts +11 -2
  392. package/workflow/WorkflowExecutionTimeline.d.ts.map +1 -1
  393. package/workflow/WorkflowExecutionTimeline.js +2 -2
  394. package/workflow/WorkflowExecutionTimeline.js.map +1 -1
  395. package/workflow/WorkflowExecutionTimelineEvent.d.ts +4 -1
  396. package/workflow/WorkflowExecutionTimelineEvent.d.ts.map +1 -1
  397. package/workflow/WorkflowExecutionTimelineEvent.js +2 -2
  398. package/workflow/WorkflowExecutionTimelineEvent.js.map +1 -1
  399. package/workflow/WorkflowExecutionViewer.d.ts.map +1 -1
  400. package/workflow/WorkflowExecutionViewer.js +18 -10
  401. package/workflow/WorkflowExecutionViewer.js.map +1 -1
  402. package/workflow/WorkflowFileReviewList.d.ts +46 -0
  403. package/workflow/WorkflowFileReviewList.d.ts.map +1 -0
  404. package/workflow/WorkflowFileReviewList.js +48 -0
  405. package/workflow/WorkflowFileReviewList.js.map +1 -0
  406. package/workflow/WorkflowTaskApprovalCard.d.ts +8 -1
  407. package/workflow/WorkflowTaskApprovalCard.d.ts.map +1 -1
  408. package/workflow/WorkflowTaskApprovalCard.js +12 -19
  409. package/workflow/WorkflowTaskApprovalCard.js.map +1 -1
  410. package/workflow/execution-inspector/ExecutionInspector.d.ts +21 -4
  411. package/workflow/execution-inspector/ExecutionInspector.d.ts.map +1 -1
  412. package/workflow/execution-inspector/ExecutionInspector.js +6 -3
  413. package/workflow/execution-inspector/ExecutionInspector.js.map +1 -1
  414. package/workflow/index.d.ts +1 -0
  415. package/workflow/index.d.ts.map +1 -1
  416. package/workflow/index.js +1 -0
  417. package/workflow/index.js.map +1 -1
  418. package/workflow/useWorkflowExecutionActions.d.ts +62 -4
  419. package/workflow/useWorkflowExecutionActions.d.ts.map +1 -1
  420. package/workflow/useWorkflowExecutionActions.js +94 -18
  421. package/workflow/useWorkflowExecutionActions.js.map +1 -1
  422. package/session/inspector/PlanTab.d.ts +0 -15
  423. package/session/inspector/PlanTab.d.ts.map +0 -1
  424. package/session/inspector/PlanTab.js +0 -19
  425. package/session/inspector/PlanTab.js.map +0 -1
  426. package/src/session/inspector/PlanTab.tsx +0 -31
@@ -7,14 +7,18 @@ import type { AgentMessage, ToolCall } from "@stigmer/protos/ai/stigmer/agentic/
7
7
  import { AgentMessageSchema } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/message_pb";
8
8
  import type { SubAgentExecution } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/subagent_pb";
9
9
  import type { PendingApproval } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/approval_pb";
10
+ import type { FileChangeSet } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/filereview_pb";
10
11
  import type { ExecutionArtifact } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/artifact_pb";
12
+ import type { TodoItem } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/todo_pb";
11
13
  import {
12
14
  ApprovalAction,
13
15
  ExecutionPhase,
16
+ FileChangeSetStatus,
14
17
  InteractionMode,
15
18
  MessageType,
16
19
  } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
17
20
  import type { WorkspaceEntry } from "@stigmer/protos/ai/stigmer/agentic/session/v1/workspace_pb";
21
+ import { displayFileChangeSets } from "@stigmer/sdk";
18
22
  import { cn } from "@stigmer/theme";
19
23
  import { isTerminalPhase } from "./execution-phases";
20
24
  import { MessageEntry } from "./MessageEntry";
@@ -23,18 +27,23 @@ import { SubAgentSection } from "./SubAgentSection";
23
27
  import { ExecutionPhaseBadge } from "./ExecutionPhaseBadge";
24
28
  import { SetupProgress } from "./SetupProgress";
25
29
  import { ApprovalCard } from "./ApprovalCard";
30
+ import { FileReviewCard } from "./FileReviewCard";
26
31
  import { SummarizationCard } from "./SummarizationCard";
27
32
  import { PlanCompletionCard } from "./PlanCompletionCard";
28
33
  import { PlanArtifactCard } from "./PlanArtifactCard";
34
+ import { TodoCard } from "./TodoCard";
29
35
  import { findPlanArtifact } from "../library/detect-plan-artifact";
30
36
  import type { SummarizationEventView } from "./useContextWindow";
31
- import { isInternalTool } from "./tool-categories";
37
+ import { isInternalTool, isCollapsedToolCall } from "./tool-categories";
32
38
  import { FilePathContext, type FilePathContextValue } from "./FilePathContext";
33
39
  import type { ResolvedPathAction } from "./file-path-resolver";
34
40
  import { SandboxContext, type SandboxContextValue } from "./SandboxContext";
41
+ import { ApprovalContext, type ApprovalContextValue } from "./ApprovalContext";
42
+ import { FileReviewContext, type FileReviewContextValue } from "./FileReviewContext";
35
43
  import { useRenderTracer, useKeyStability, useDomNodeCount, DevProfiler } from "../internal/dev";
36
44
  import { useAutoScroll } from "../internal/useAutoScroll";
37
45
  import { JumpToLatestButton } from "../internal/JumpToLatestButton";
46
+ import { ApprovalPeekBar } from "../internal/ApprovalPeekBar";
38
47
  import { ThreadItemWrapper } from "../internal/ThreadItemWrapper";
39
48
 
40
49
  const LazyVirtualizedThread = lazy(() =>
@@ -43,6 +52,11 @@ const LazyVirtualizedThread = lazy(() =>
43
52
  })),
44
53
  );
45
54
 
55
+ /** Stable empty collections so an approval-free thread keeps referential identity. */
56
+ const EMPTY_APPROVALS: readonly PendingApproval[] = [];
57
+ const EMPTY_SUBMITTING_IDS: ReadonlySet<string> = new Set();
58
+ const EMPTY_APPROVAL_ERRORS: ReadonlyMap<string, Error> = new Map();
59
+
46
60
  /** Props for {@link MessageThread}. */
47
61
  export interface MessageThreadProps {
48
62
  /** Completed executions in chronological order. */
@@ -116,6 +130,31 @@ export interface MessageThreadProps {
116
130
  * `onApprovalSubmit` is provided.
117
131
  */
118
132
  readonly submittingApprovalIds?: ReadonlySet<string>;
133
+ /**
134
+ * Per-tool-call approval failures, keyed by `toolCallId` — surfaced in-card
135
+ * beside the gate that failed (an inline tool row or the bottom backstop
136
+ * card). Supply {@link useSubmitApproval}'s `errorsByToolCallId`. Only
137
+ * meaningful when `onApprovalSubmit` is provided.
138
+ */
139
+ readonly approvalErrors?: ReadonlyMap<string, Error>;
140
+ /**
141
+ * Render each *settled* captured change set as a read-only record card at
142
+ * its anchor in the thread (after the set's last stamped edit row) — the
143
+ * durable history of what changed and how it was decided. This is the only
144
+ * in-thread trace for a set with no stamped rows (e.g. changes made by shell
145
+ * commands), so the surface that owns the session history should enable it.
146
+ *
147
+ * The thread never renders *decision* controls: a pending (AWAITING_REVIEW)
148
+ * set on a live execution is deliberately NOT emitted here — its decision
149
+ * surface is the composer-docked {@link FileReviewDock}, which cannot scroll
150
+ * out of view. The stamped rows' badges carry the pending state in-thread.
151
+ *
152
+ * Opt-in with a backward-compatible default (DD-011); `SessionViewer`
153
+ * enables it.
154
+ *
155
+ * @default false
156
+ */
157
+ readonly showFileReviewRecords?: boolean;
119
158
  /**
120
159
  * Workspace entries from the session spec. When provided, file
121
160
  * paths in tool call rendering become interactive — git-sourced
@@ -216,8 +255,15 @@ export type ThreadItem =
216
255
  | { readonly kind: "phase-badge"; readonly phase: ExecutionPhase; readonly key: string }
217
256
  | { readonly kind: "execution-error"; readonly error: string; readonly retryMessage?: string; readonly key: string }
218
257
  | { readonly kind: "approval-request"; readonly pendingApproval: PendingApproval; readonly key: string }
258
+ | { readonly kind: "file-review-record"; readonly fileChangeSet: FileChangeSet; readonly key: string }
219
259
  | { readonly kind: "setup-progress"; readonly workspaceEntries: readonly WorkspaceEntry[]; readonly serverPhase?: string; readonly isAwaitingResponse?: boolean; readonly key: string }
220
260
  | { readonly kind: "context-compacted"; readonly event: SummarizationEventView; readonly key: string }
261
+ | {
262
+ readonly kind: "todos";
263
+ readonly key: string;
264
+ readonly executionId: string;
265
+ readonly todos: { readonly [id: string]: TodoItem };
266
+ }
221
267
  | {
222
268
  readonly kind: "plan-completion";
223
269
  readonly key: string;
@@ -249,6 +295,90 @@ function hasStartedResponding(execution: AgentExecution): boolean {
249
295
  });
250
296
  }
251
297
 
298
+ /**
299
+ * Adds the tool-call ids that a sub-agent renders as nested rows (the same
300
+ * non-internal AI tool calls {@link SubAgentSection} surfaces) to `target`,
301
+ * so their approvals are treated as inline and skip the bottom backstop.
302
+ */
303
+ function collectSubAgentInlineToolCallIds(
304
+ sub: SubAgentExecution,
305
+ target: Set<string>,
306
+ ): void {
307
+ for (const msg of sub.messages) {
308
+ if (msg.type !== MessageType.MESSAGE_AI) continue;
309
+ for (const tc of msg.toolCalls) {
310
+ if (tc.id && !isInternalTool(tc.name)) target.add(tc.id);
311
+ }
312
+ }
313
+ }
314
+
315
+ /**
316
+ * Records, for each change set referenced by a just-pushed tool-group item, the
317
+ * item's index — later groups overwrite, so the map converges on each set's
318
+ * LAST stamped row. That index is where the set's decision bar anchors: the
319
+ * review surface appears where the turn's editing activity ended, not at the
320
+ * thread tail.
321
+ */
322
+ function recordFileReviewAnchors(
323
+ toolCalls: readonly ToolCall[],
324
+ itemIndex: number,
325
+ anchorIndexBySetId: Map<string, number>,
326
+ ): void {
327
+ for (const tc of toolCalls) {
328
+ if (tc.fileChangeSetId) anchorIndexBySetId.set(tc.fileChangeSetId, itemIndex);
329
+ }
330
+ }
331
+
332
+ /**
333
+ * Inserts one execution's settled file-review records into its own thread
334
+ * segment: each change set's read-only `file-review-record` item is spliced
335
+ * immediately after the set's last stamped tool row
336
+ * ({@link recordFileReviewAnchors}); a set with no stamped row (its changes
337
+ * were made by shell commands, or the rows predate stamping) falls back to the
338
+ * segment's tail — this record is that set's ONLY in-thread trace, so it is
339
+ * never dropped.
340
+ *
341
+ * A *pending* set — AWAITING_REVIEW on a live (non-terminal) execution — is
342
+ * deliberately NOT emitted: its decision surface is the composer-docked
343
+ * `FileReviewDock`, which cannot scroll out of view, and the stamped rows'
344
+ * badges carry the pending state in place. The same set re-enters here as a
345
+ * record the moment it settles (or its execution terminates mid-review — an
346
+ * honest "not reviewed" record). A CAPTURING set (baseline only, no diff yet)
347
+ * has nothing to show and is skipped.
348
+ */
349
+ function insertFileReviewItems(
350
+ items: ThreadItem[],
351
+ changeSets: readonly FileChangeSet[],
352
+ anchorIndexBySetId: ReadonlyMap<string, number>,
353
+ execTerminal: boolean,
354
+ ): void {
355
+ const anchored: { index: number; item: ThreadItem }[] = [];
356
+ const tail: ThreadItem[] = [];
357
+ for (const changeSet of changeSets) {
358
+ if (changeSet.changes.length === 0) continue;
359
+ const pending =
360
+ !execTerminal && changeSet.status === FileChangeSetStatus.AWAITING_REVIEW;
361
+ if (pending) continue;
362
+ const item: ThreadItem = {
363
+ kind: "file-review-record",
364
+ fileChangeSet: changeSet,
365
+ key: `file-review-${changeSet.id}`,
366
+ };
367
+ const index = anchorIndexBySetId.get(changeSet.id);
368
+ if (index !== undefined) anchored.push({ index, item });
369
+ else tail.push(item);
370
+ }
371
+ // Splice in ascending anchor order with a running offset so earlier inserts
372
+ // do not displace later anchors.
373
+ anchored.sort((a, b) => a.index - b.index);
374
+ let offset = 0;
375
+ for (const { index, item } of anchored) {
376
+ items.splice(index + 1 + offset, 0, item);
377
+ offset++;
378
+ }
379
+ items.push(...tail);
380
+ }
381
+
252
382
  /**
253
383
  * Builds a flat list of renderable thread items from execution data.
254
384
  *
@@ -266,10 +396,32 @@ export function buildThreadItems(
266
396
  summarizationEvents?: readonly SummarizationEventView[],
267
397
  pendingMessageFailed = false,
268
398
  editableActiveTurn = false,
399
+ includeFileReviewRecords = false,
269
400
  ): ThreadItem[] {
270
401
  const items: ThreadItem[] = [];
402
+ // Tool-call ids that render as an inline-approval-capable ToolCallItem (a
403
+ // regular parent tool, or a sub-agent's nested tool). Their approvals show
404
+ // inline on the row, so they are excluded from the bottom backstop below —
405
+ // a task-spawn call (rendered as a SubAgentSection, not a tool row) is NOT
406
+ // collected, so its spawn-gate approval still surfaces at the bottom.
407
+ const inlineToolCallIds = new Set<string>();
408
+ // Dedupe by execution id, with activeStreamExecution authoritative for its own
409
+ // id. The hook passes `completedExecutions` (filtered on activeExecutionId),
410
+ // but that filter and activeStreamExecution (stream.execution ??
411
+ // fetchedActiveExecution) derive from different sources, so a transient skew
412
+ // can leave the active execution in BOTH lists. Concatenating blindly then
413
+ // emits the same execution twice → duplicate React keys → React silently drops
414
+ // one subtree, intermittently the one carrying a live ApprovalCard (the gate
415
+ // buttons vanish). Dropping any prior entry sharing the active id keeps the
416
+ // streamed copy as the single source of truth and the append-at-end invariant
417
+ // (activeStreamIndex === last) intact.
271
418
  const allExecutions = activeStreamExecution
272
- ? [...executions, activeStreamExecution]
419
+ ? [
420
+ ...executions.filter(
421
+ (e) => e.metadata?.id !== activeStreamExecution.metadata?.id,
422
+ ),
423
+ activeStreamExecution,
424
+ ]
273
425
  : executions;
274
426
  const activeStreamIndex = activeStreamExecution
275
427
  ? allExecutions.length - 1
@@ -306,6 +458,36 @@ export function buildThreadItems(
306
458
  const messages = exec.status?.messages ?? [];
307
459
  const subAgents = exec.status?.subAgentExecutions ?? [];
308
460
 
461
+ // This execution's settled change sets render as read-only records inside
462
+ // its own segment, anchored to the last stamped edit row of each set. The
463
+ // display seam reads the server's live projection when present and folds
464
+ // the durable ledger for a terminal execution (whose projection is nil),
465
+ // so records surface for EVERY execution, not just the last. Pending sets
466
+ // are excluded inside insertFileReviewItems — the composer dock owns them.
467
+ const execChangeSets = includeFileReviewRecords
468
+ ? displayFileChangeSets(exec.status)
469
+ : [];
470
+ const fileReviewAnchors = new Map<string, number>();
471
+
472
+ // The agent's plan renders as one inline card per turn, anchored to the
473
+ // first of: the opening AI/thinking message, the first unit of work
474
+ // (tool-group / sub-agent), or — as a backstop — the turn's tail. We carry
475
+ // the live `status.todos` reference (structural sharing keeps it stable),
476
+ // so the card updates in place and a settled card skips re-renders.
477
+ const execTodos = exec.status?.todos;
478
+ const hasTodos = execTodos != null && Object.keys(execTodos).length > 0;
479
+ let todosEmitted = false;
480
+ function emitTodos(): void {
481
+ if (todosEmitted || !hasTodos) return;
482
+ todosEmitted = true;
483
+ items.push({
484
+ kind: "todos",
485
+ executionId: execId,
486
+ todos: execTodos!,
487
+ key: `${execId}-todos`,
488
+ });
489
+ }
490
+
309
491
  const specMessage = exec.spec?.message;
310
492
  if (specMessage && specMessage !== "execute") {
311
493
  const syntheticHumanMsg = create(AgentMessageSchema);
@@ -348,20 +530,43 @@ export function buildThreadItems(
348
530
  message: msg,
349
531
  key: `${execId}-m${mi}`,
350
532
  });
533
+ // Anchor (1): the plan sits right under the agent's opening narration.
534
+ if (
535
+ msg.type === MessageType.MESSAGE_AI ||
536
+ msg.type === MessageType.MESSAGE_THINKING
537
+ ) {
538
+ emitTodos();
539
+ }
351
540
  }
352
541
 
542
+ // Runner-collapsed duplicates (a superseded same-turn denial twin of an
543
+ // approval gate) are not rendered — they are kept in the transcript only to
544
+ // satisfy the backend's append-only guard. Filtering them here keeps a
545
+ // message whose tool calls are ALL collapsed from emitting an empty group.
546
+ // Preserve the original array reference when nothing is collapsed (the
547
+ // common case) so structural sharing / memoization (T04) is not defeated by
548
+ // a fresh array on every build.
549
+ const renderableToolCalls =
550
+ msg.type === MessageType.MESSAGE_AI && msg.toolCalls.some(isCollapsedToolCall)
551
+ ? msg.toolCalls.filter((tc) => !isCollapsedToolCall(tc))
552
+ : msg.toolCalls;
553
+
353
554
  if (
354
555
  msg.type === MessageType.MESSAGE_AI &&
355
- msg.toolCalls.length > 0
556
+ renderableToolCalls.length > 0
356
557
  ) {
357
- const needsSplit = msg.toolCalls.some(
558
+ // Anchor (2): if work begins before any rendered narration, the plan
559
+ // still leads it.
560
+ emitTodos();
561
+
562
+ const needsSplit = renderableToolCalls.some(
358
563
  (tc) => tc.name === "task" || isInternalTool(tc.name),
359
564
  );
360
565
 
361
566
  if (needsSplit) {
362
567
  const regularTools: ToolCall[] = [];
363
568
  const matchedSubAgents: SubAgentExecution[] = [];
364
- for (const tc of msg.toolCalls) {
569
+ for (const tc of renderableToolCalls) {
365
570
  if (tc.name === "task") {
366
571
  const matched = subAgents.find((sa) => sa.id === tc.id);
367
572
  if (matched) matchedSubAgents.push(matched);
@@ -376,6 +581,10 @@ export function buildThreadItems(
376
581
  subAgentExecutions: subAgents,
377
582
  key: `${execId}-m${mi}-tc`,
378
583
  });
584
+ recordFileReviewAnchors(regularTools, items.length - 1, fileReviewAnchors);
585
+ for (const tc of regularTools) {
586
+ if (tc.id) inlineToolCallIds.add(tc.id);
587
+ }
379
588
  }
380
589
  for (const sa of matchedSubAgents) {
381
590
  items.push({
@@ -383,17 +592,36 @@ export function buildThreadItems(
383
592
  subAgentExecution: sa,
384
593
  key: `sa-${sa.id}`,
385
594
  });
595
+ collectSubAgentInlineToolCallIds(sa, inlineToolCallIds);
386
596
  }
387
597
  } else {
388
598
  items.push({
389
599
  kind: "tool-group",
390
- toolCalls: msg.toolCalls,
600
+ toolCalls: renderableToolCalls,
391
601
  subAgentExecutions: subAgents,
392
602
  key: `${execId}-m${mi}-tc`,
393
603
  });
604
+ recordFileReviewAnchors(renderableToolCalls, items.length - 1, fileReviewAnchors);
605
+ for (const tc of renderableToolCalls) {
606
+ if (tc.id) inlineToolCallIds.add(tc.id);
607
+ }
394
608
  }
395
609
  }
396
610
  }
611
+
612
+ // Anchor (3): a plan that produced no rendered narration or work item still
613
+ // surfaces — never silently dropped.
614
+ emitTodos();
615
+
616
+ // Settled records land inside this execution's segment, at their anchors.
617
+ if (execChangeSets.length > 0) {
618
+ insertFileReviewItems(
619
+ items,
620
+ execChangeSets,
621
+ fileReviewAnchors,
622
+ isTerminalPhase(exec.status?.phase ?? ExecutionPhase.EXECUTION_PHASE_UNSPECIFIED),
623
+ );
624
+ }
397
625
  }
398
626
 
399
627
  // Flush any remaining summarization events that occurred after all messages
@@ -477,9 +705,17 @@ export function buildThreadItems(
477
705
  }
478
706
 
479
707
  if (includeApprovals) {
708
+ // Backstop only: an approval whose tool call renders inline shows its gate
709
+ // on that row (see ApprovalContext / ToolCallItem). We emit a bottom card
710
+ // ONLY for an approval with no inline home — a true orphan, or a task-spawn
711
+ // approval that precedes its SubAgentExecution — so a pending gate is never
712
+ // invisible, and never duplicated.
480
713
  const allApprovals = lastExec?.status?.pendingApprovals ?? [];
481
714
  for (let ai = 0; ai < allApprovals.length; ai++) {
482
715
  const approval = allApprovals[ai];
716
+ if (approval.toolCallId && inlineToolCallIds.has(approval.toolCallId)) {
717
+ continue;
718
+ }
483
719
  items.push({
484
720
  kind: "approval-request",
485
721
  pendingApproval: approval,
@@ -488,6 +724,11 @@ export function buildThreadItems(
488
724
  }
489
725
  }
490
726
 
727
+ // File-review records are emitted inside each execution's segment (see
728
+ // insertFileReviewItems in the loop above) — anchored to the last stamped
729
+ // edit row, never appended at the thread tail. Pending decision bars are
730
+ // not thread items at all: they live in the composer-docked FileReviewDock.
731
+
491
732
  if (pendingUserMessage) {
492
733
  const alreadySynthesized =
493
734
  lastExec?.spec?.message === pendingUserMessage;
@@ -545,6 +786,8 @@ export function MessageThread({
545
786
  formatToolCallSummary,
546
787
  onApprovalSubmit,
547
788
  submittingApprovalIds,
789
+ approvalErrors,
790
+ showFileReviewRecords = false,
548
791
  workspaceEntries,
549
792
  onFilePathClick,
550
793
  sandboxWorkspaceRoot,
@@ -560,8 +803,8 @@ export function MessageThread({
560
803
  const includeApprovals = onApprovalSubmit != null;
561
804
  const editableActiveTurn = onEditMessage != null;
562
805
  const items = useMemo(
563
- () => buildThreadItems(executions, activeStreamExecution, pendingUserMessage, includeApprovals, workspaceEntries, summarizationEvents, pendingMessageFailed, editableActiveTurn),
564
- [executions, activeStreamExecution, pendingUserMessage, includeApprovals, workspaceEntries, summarizationEvents, pendingMessageFailed, editableActiveTurn],
806
+ () => buildThreadItems(executions, activeStreamExecution, pendingUserMessage, includeApprovals, workspaceEntries, summarizationEvents, pendingMessageFailed, editableActiveTurn, showFileReviewRecords),
807
+ [executions, activeStreamExecution, pendingUserMessage, includeApprovals, workspaceEntries, summarizationEvents, pendingMessageFailed, editableActiveTurn, showFileReviewRecords],
565
808
  );
566
809
 
567
810
  useKeyStability(items);
@@ -579,6 +822,57 @@ export function MessageThread({
579
822
  [sandboxWorkspaceRoot],
580
823
  );
581
824
 
825
+ // Pending approvals live on the latest execution. We project them into a
826
+ // tool-call-id-keyed map so each gated tool row can render its own approval
827
+ // inline (see ApprovalContext); buildThreadItems still emits a bottom card
828
+ // for any approval with no matching inline row (orphan backstop).
829
+ const lastExec = activeStreamExecution ?? executions[executions.length - 1];
830
+ const pendingApprovals = includeApprovals
831
+ ? lastExec?.status?.pendingApprovals ?? EMPTY_APPROVALS
832
+ : EMPTY_APPROVALS;
833
+
834
+ const approvalsByToolCallId = useMemo(() => {
835
+ const map = new Map<string, PendingApproval>();
836
+ for (const approval of pendingApprovals) {
837
+ if (approval.toolCallId) map.set(approval.toolCallId, approval);
838
+ }
839
+ return map;
840
+ }, [pendingApprovals]);
841
+
842
+ const approvalCtx = useMemo<ApprovalContextValue>(
843
+ () => ({
844
+ approvalsByToolCallId,
845
+ onSubmit: onApprovalSubmit,
846
+ submittingIds: submittingApprovalIds ?? EMPTY_SUBMITTING_IDS,
847
+ errorsByToolCallId: approvalErrors ?? EMPTY_APPROVAL_ERRORS,
848
+ }),
849
+ [approvalsByToolCallId, onApprovalSubmit, submittingApprovalIds, approvalErrors],
850
+ );
851
+
852
+ // Every displayable change set across the session, keyed by id, so a stamped
853
+ // edit row (ToolCall.file_change_set_id) can badge its set's live review
854
+ // state wherever it renders (parent thread or nested groups). The map is
855
+ // rebuilt only when the execution list reference changes; structural sharing
856
+ // keeps each FileChangeSet reference stable across streaming frames, so
857
+ // subscribing rows re-render on review events, not every snapshot.
858
+ const fileReviewCtx = useMemo<FileReviewContextValue>(() => {
859
+ const map = new Map<string, FileChangeSet>();
860
+ const all = activeStreamExecution
861
+ ? [...executions, activeStreamExecution]
862
+ : executions;
863
+ for (const exec of all) {
864
+ for (const changeSet of displayFileChangeSets(exec.status)) {
865
+ // Later wins: the active stream's copy supersedes a stale fetched one.
866
+ map.set(changeSet.id, changeSet);
867
+ }
868
+ }
869
+ return { changeSetsById: map };
870
+ }, [executions, activeStreamExecution]);
871
+
872
+ // Drives the global "approval needed" peek affordance — a count, not the
873
+ // cards, so the bar reuses the existing scroll machine without a new observer.
874
+ const unresolvedApprovalCount = pendingApprovals.length;
875
+
582
876
  if (virtualized) {
583
877
  return (
584
878
  <div className={cn("relative min-h-0", className)}>
@@ -588,8 +882,12 @@ export function MessageThread({
588
882
  formatToolCallSummary={formatToolCallSummary}
589
883
  onApprovalSubmit={onApprovalSubmit}
590
884
  submittingApprovalIds={submittingApprovalIds}
885
+ approvalErrors={approvalErrors}
591
886
  filePathCtx={filePathCtx}
592
887
  sandboxCtx={sandboxCtx}
888
+ approvalCtx={approvalCtx}
889
+ fileReviewCtx={fileReviewCtx}
890
+ unresolvedApprovalCount={unresolvedApprovalCount}
593
891
  onBuildFromPlan={onBuildFromPlan}
594
892
  org={org}
595
893
  planActionsDisabled={planActionsDisabled}
@@ -611,8 +909,12 @@ export function MessageThread({
611
909
  formatToolCallSummary={formatToolCallSummary}
612
910
  onApprovalSubmit={onApprovalSubmit}
613
911
  submittingApprovalIds={submittingApprovalIds}
912
+ approvalErrors={approvalErrors}
614
913
  filePathCtx={filePathCtx}
615
914
  sandboxCtx={sandboxCtx}
915
+ approvalCtx={approvalCtx}
916
+ fileReviewCtx={fileReviewCtx}
917
+ unresolvedApprovalCount={unresolvedApprovalCount}
616
918
  onBuildFromPlan={onBuildFromPlan}
617
919
  org={org}
618
920
  planActionsDisabled={planActionsDisabled}
@@ -638,8 +940,12 @@ interface NonVirtualizedThreadProps {
638
940
  comment?: string,
639
941
  ) => void;
640
942
  readonly submittingApprovalIds?: ReadonlySet<string>;
943
+ readonly approvalErrors?: ReadonlyMap<string, Error>;
641
944
  readonly filePathCtx: FilePathContextValue;
642
945
  readonly sandboxCtx: SandboxContextValue;
946
+ readonly approvalCtx: ApprovalContextValue;
947
+ readonly fileReviewCtx: FileReviewContextValue;
948
+ readonly unresolvedApprovalCount: number;
643
949
  readonly onBuildFromPlan?: () => void;
644
950
  readonly org?: string;
645
951
  readonly planActionsDisabled?: boolean;
@@ -655,8 +961,12 @@ function NonVirtualizedThread({
655
961
  formatToolCallSummary,
656
962
  onApprovalSubmit,
657
963
  submittingApprovalIds,
964
+ approvalErrors,
658
965
  filePathCtx,
659
966
  sandboxCtx,
967
+ approvalCtx,
968
+ fileReviewCtx,
969
+ unresolvedApprovalCount,
660
970
  onBuildFromPlan,
661
971
  org,
662
972
  planActionsDisabled,
@@ -686,6 +996,8 @@ function NonVirtualizedThread({
686
996
  >
687
997
  <SandboxContext.Provider value={sandboxCtx}>
688
998
  <FilePathContext.Provider value={filePathCtx}>
999
+ <ApprovalContext.Provider value={approvalCtx}>
1000
+ <FileReviewContext.Provider value={fileReviewCtx}>
689
1001
  <DevProfiler id="MessageThread">
690
1002
  <div ref={contentRef} className={cn("flex flex-col gap-4", centerContent && "mx-auto w-full max-w-3xl px-4")}>
691
1003
  {items.map((item) => (
@@ -695,6 +1007,7 @@ function NonVirtualizedThread({
695
1007
  formatToolCallSummary={formatToolCallSummary}
696
1008
  onApprovalSubmit={onApprovalSubmit}
697
1009
  submittingApprovalIds={submittingApprovalIds}
1010
+ approvalErrors={approvalErrors}
698
1011
  onBuildFromPlan={onBuildFromPlan}
699
1012
  org={org}
700
1013
  planActionsDisabled={planActionsDisabled}
@@ -706,11 +1019,23 @@ function NonVirtualizedThread({
706
1019
  ))}
707
1020
  </div>
708
1021
  </DevProfiler>
1022
+ </FileReviewContext.Provider>
1023
+ </ApprovalContext.Provider>
709
1024
  </FilePathContext.Provider>
710
1025
  </SandboxContext.Provider>
711
1026
  <div ref={sentinelRef} aria-hidden="true" />
712
1027
  </div>
713
- <JumpToLatestButton onClick={jumpToLatest} visible={!isFollowing} />
1028
+ {/* The peek bar takes the jump button's slot while approvals are pending,
1029
+ so the two never overlap — a gate is the louder of the two signals. */}
1030
+ <JumpToLatestButton
1031
+ onClick={jumpToLatest}
1032
+ visible={!isFollowing && unresolvedApprovalCount === 0}
1033
+ />
1034
+ <ApprovalPeekBar
1035
+ visible={!isFollowing && unresolvedApprovalCount > 0}
1036
+ count={unresolvedApprovalCount}
1037
+ onClick={jumpToLatest}
1038
+ />
714
1039
  </div>
715
1040
  );
716
1041
  }
@@ -734,6 +1059,7 @@ export interface ThreadItemRendererProps {
734
1059
  comment?: string,
735
1060
  ) => void;
736
1061
  readonly submittingApprovalIds?: ReadonlySet<string>;
1062
+ readonly approvalErrors?: ReadonlyMap<string, Error>;
737
1063
  readonly onBuildFromPlan?: () => void;
738
1064
  readonly org?: string;
739
1065
  readonly planActionsDisabled?: boolean;
@@ -758,6 +1084,7 @@ export function ThreadItemRenderer({
758
1084
  formatToolCallSummary,
759
1085
  onApprovalSubmit,
760
1086
  submittingApprovalIds,
1087
+ approvalErrors,
761
1088
  onBuildFromPlan,
762
1089
  org,
763
1090
  planActionsDisabled,
@@ -819,8 +1146,11 @@ export function ThreadItemRenderer({
819
1146
  pendingApproval={item.pendingApproval}
820
1147
  onApprovalSubmit={onApprovalSubmit!}
821
1148
  isSubmitting={submittingApprovalIds?.has(item.pendingApproval.toolCallId) ?? false}
1149
+ error={approvalErrors?.get(item.pendingApproval.toolCallId) ?? null}
822
1150
  />
823
1151
  );
1152
+ case "file-review-record":
1153
+ return <FileReviewRecordRow fileChangeSet={item.fileChangeSet} />;
824
1154
  case "setup-progress":
825
1155
  return (
826
1156
  <SetupProgress
@@ -831,6 +1161,8 @@ export function ThreadItemRenderer({
831
1161
  );
832
1162
  case "context-compacted":
833
1163
  return <SummarizationCard event={item.event} />;
1164
+ case "todos":
1165
+ return <TodoCard todos={item.todos} className="mx-4" />;
834
1166
  case "plan-completion":
835
1167
  // When the plan was published as an artifact, show the richer reviewable
836
1168
  // card (preview / copy / download / implement). Otherwise fall back to the
@@ -983,12 +1315,16 @@ interface ApprovalCardRowProps {
983
1315
  comment?: string,
984
1316
  ) => void;
985
1317
  readonly isSubmitting: boolean;
1318
+ // This gate's last failed decision, or null — a stable ref from the
1319
+ // approvalErrors map, so the row re-renders only when its error appears/clears.
1320
+ readonly error?: Error | null;
986
1321
  }
987
1322
 
988
1323
  const ApprovalCardRow = memo(function ApprovalCardRow({
989
1324
  pendingApproval,
990
1325
  onApprovalSubmit,
991
1326
  isSubmitting,
1327
+ error = null,
992
1328
  }: ApprovalCardRowProps) {
993
1329
  const handleSubmit = useCallback(
994
1330
  (action: ApprovalAction, comment?: string) => {
@@ -1002,6 +1338,35 @@ const ApprovalCardRow = memo(function ApprovalCardRow({
1002
1338
  pendingApproval={pendingApproval}
1003
1339
  onSubmit={handleSubmit}
1004
1340
  isSubmitting={isSubmitting}
1341
+ error={error}
1342
+ className="mx-4"
1343
+ />
1344
+ );
1345
+ });
1346
+
1347
+ // ---------------------------------------------------------------------------
1348
+ // FileReviewRecordRow — a settled change set's read-only in-thread record
1349
+ // ---------------------------------------------------------------------------
1350
+
1351
+ /**
1352
+ * Renders a settled change set as a read-only record at its position in the
1353
+ * transcript — what changed and how it was decided ("2 kept · 1 discarded").
1354
+ * Never interactive: the pending decision surface is the composer-docked
1355
+ * `FileReviewDock`, not a thread item (see {@link insertFileReviewItems}).
1356
+ */
1357
+ const FileReviewRecordRow = memo(function FileReviewRecordRow({
1358
+ fileChangeSet,
1359
+ }: {
1360
+ readonly fileChangeSet: FileChangeSet;
1361
+ }) {
1362
+ return (
1363
+ <FileReviewCard
1364
+ fileChangeSet={fileChangeSet}
1365
+ interactive={false}
1366
+ // The thread's stamped edit rows already show every diff in place, so
1367
+ // the record renders its compact file-list body — the history never
1368
+ // duplicates the transcript's diffs.
1369
+ showDiffs={false}
1005
1370
  className="mx-4"
1006
1371
  />
1007
1372
  );
@@ -5,6 +5,7 @@ import { cn } from "@stigmer/theme";
5
5
  import type { ExecutionArtifact } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/artifact_pb";
6
6
  import { ArtifactPreviewModal } from "./ArtifactPreviewModal";
7
7
  import { formatArtifactSize } from "./artifact-utils";
8
+ import { useArtifactDownload } from "./useArtifactDownload";
8
9
  import { useBuildFromPlanHotkey } from "./use-build-from-plan-hotkey";
9
10
 
10
11
  /** Props for {@link PlanArtifactCard}. */
@@ -63,6 +64,7 @@ export const PlanArtifactCard = memo(function PlanArtifactCard({
63
64
  }: PlanArtifactCardProps) {
64
65
  const [previewOpen, setPreviewOpen] = useState(false);
65
66
  const handleKeyDown = useBuildFromPlanHotkey(onImplement, disabled);
67
+ const { download, isDownloading } = useArtifactDownload(executionId);
66
68
 
67
69
  return (
68
70
  <div
@@ -97,17 +99,18 @@ export const PlanArtifactCard = memo(function PlanArtifactCard({
97
99
  Open full
98
100
  </button>
99
101
  )}
100
- <a
101
- href={artifact.downloadUrl}
102
- download={artifact.name}
102
+ <button
103
+ type="button"
104
+ onClick={() => download(artifact.storageKey, artifact.name)}
105
+ disabled={isDownloading}
103
106
  className={cn(
104
- "inline-flex items-center gap-1 text-xs font-medium text-muted-foreground transition-colors hover:text-foreground",
107
+ "inline-flex items-center gap-1 text-xs font-medium text-muted-foreground transition-colors hover:text-foreground disabled:opacity-50",
105
108
  FOCUS_RING,
106
109
  )}
107
110
  >
108
111
  <DownloadIcon />
109
- Download
110
- </a>
112
+ {isDownloading ? "Preparing…" : "Download"}
113
+ </button>
111
114
  {onImplement && (
112
115
  <button
113
116
  type="button"