@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
@@ -0,0 +1,134 @@
1
+ /**
2
+ * FileReviewDock — the composer-docked decision surface for pending file
3
+ * reviews. These tests cover the dock's own responsibilities: emptiness,
4
+ * per-set submit binding, error threading, the defensive multi-set stack, and
5
+ * the height-capped scroll container. Decision-case behavior (per-file
6
+ * verdicts, binary acknowledgment, blocked files) lives in the FileReviewCard
7
+ * suites — the dock renders the same card in list mode.
8
+ */
9
+
10
+ import { describe, it, expect, vi, afterEach } from "vitest";
11
+ import { render, screen, fireEvent, cleanup } from "@testing-library/react";
12
+ import { create } from "@bufbuild/protobuf";
13
+ import {
14
+ CapturedFileChangeSchema,
15
+ FileChangeSetSchema,
16
+ type FileChangeSet,
17
+ } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/filereview_pb";
18
+ import { FileContentSchema } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/message_pb";
19
+ import {
20
+ DiffCompleteness,
21
+ FileChangeKind,
22
+ FileChangeSetStatus,
23
+ FileDecisionAction,
24
+ FileDecisionScope,
25
+ } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
26
+ import { FileReviewDock } from "../FileReviewDock";
27
+
28
+ function changeSet(id: string, paths: string[]): FileChangeSet {
29
+ return create(FileChangeSetSchema, {
30
+ id,
31
+ status: FileChangeSetStatus.AWAITING_REVIEW,
32
+ aggregateDigest: `agg-${id}`,
33
+ diffCompleteness: DiffCompleteness.COMPLETE,
34
+ changes: paths.map((path) =>
35
+ create(CapturedFileChangeSchema, {
36
+ id: `${id}:${path}`,
37
+ pathBefore: path,
38
+ pathAfter: path,
39
+ kind: FileChangeKind.MODIFY,
40
+ before: create(FileContentSchema, { body: { case: "inline", value: "old\n" } }),
41
+ after: create(FileContentSchema, { body: { case: "inline", value: "new\n" } }),
42
+ fileDigest: `d-${path}`,
43
+ diffComplete: true,
44
+ }),
45
+ ),
46
+ });
47
+ }
48
+
49
+ afterEach(() => {
50
+ cleanup();
51
+ });
52
+
53
+ describe("FileReviewDock", () => {
54
+ it("renders nothing when no sets are pending", () => {
55
+ const { container } = render(
56
+ <FileReviewDock changeSets={[]} onSubmit={() => {}} />,
57
+ );
58
+ expect(container.firstChild).toBeNull();
59
+ });
60
+
61
+ it("renders an interactive decision bar for a pending set, in list mode", () => {
62
+ render(
63
+ <FileReviewDock changeSets={[changeSet("cs-1", ["src/a.ts"])]} onSubmit={() => {}} />,
64
+ );
65
+ expect(screen.getByText("Review file changes")).toBeTruthy();
66
+ expect(
67
+ document.querySelector('[data-cursor-target="file-review-approve"]'),
68
+ ).toBeTruthy();
69
+ expect(
70
+ document.querySelector('[data-cursor-target="file-review-reject"]'),
71
+ ).toBeTruthy();
72
+ // List mode: the expander reveals the file inventory, never a diff — the
73
+ // transcript's stamped edit rows own the diffs.
74
+ fireEvent.click(screen.getByRole("button", { name: "Files" }));
75
+ expect(document.querySelector('[data-cursor-target="file-diff"]')).toBeNull();
76
+ expect(
77
+ document.querySelector('[data-cursor-target="file-review-list-row"]'),
78
+ ).toBeTruthy();
79
+ });
80
+
81
+ it("binds each set's id into the card-level submit", () => {
82
+ const onSubmit = vi.fn();
83
+ render(
84
+ <FileReviewDock changeSets={[changeSet("cs-1", ["src/a.ts"])]} onSubmit={onSubmit} />,
85
+ );
86
+ fireEvent.click(
87
+ document.querySelector<HTMLButtonElement>(
88
+ '[data-cursor-target="file-review-approve"]',
89
+ )!,
90
+ );
91
+ expect(onSubmit).toHaveBeenCalledTimes(1);
92
+ expect(onSubmit).toHaveBeenCalledWith("cs-1", FileDecisionAction.APPROVE, {
93
+ scope: FileDecisionScope.CHANGE_SET,
94
+ expectedDigest: "agg-cs-1",
95
+ acknowledgeUnreviewable: false,
96
+ });
97
+ });
98
+
99
+ it("threads a keyed decision error to the failed set's card", () => {
100
+ const decisionErrors = new Map([["cs-1", new Error("digest mismatch")]]);
101
+ render(
102
+ <FileReviewDock
103
+ changeSets={[changeSet("cs-1", ["src/a.ts"])]}
104
+ onSubmit={() => {}}
105
+ decisionErrors={decisionErrors}
106
+ />,
107
+ );
108
+ const err = screen.getByText(/Couldn.t submit decision — digest mismatch/);
109
+ expect(err.getAttribute("data-cursor-target")).toBe("file-review-error");
110
+ });
111
+
112
+ it("stacks multiple pending sets — none is ever dropped", () => {
113
+ render(
114
+ <FileReviewDock
115
+ changeSets={[changeSet("cs-1", ["a.ts"]), changeSet("cs-2", ["b.ts"])]}
116
+ onSubmit={() => {}}
117
+ />,
118
+ );
119
+ expect(screen.getAllByText("Review file changes")).toHaveLength(2);
120
+ });
121
+
122
+ it("caps the dock's height with an internal scroll container", () => {
123
+ // A set that starts expanded (many files) must scroll inside the dock
124
+ // rather than growing the fixed strip past the viewport.
125
+ const { container } = render(
126
+ <FileReviewDock changeSets={[changeSet("cs-1", ["a.ts", "b.ts"])]} onSubmit={() => {}} />,
127
+ );
128
+ const dock = container.querySelector('[data-cursor-target="file-review-dock"]');
129
+ expect(dock).toBeTruthy();
130
+ const scroller = dock!.firstElementChild!;
131
+ expect(scroller.className).toContain("overflow-y-auto");
132
+ expect(scroller.className).toContain("max-h-");
133
+ });
134
+ });
@@ -0,0 +1,79 @@
1
+ import { describe, it, expect, afterEach, vi } from "vitest";
2
+ import { render, cleanup, screen, fireEvent, waitFor } from "@testing-library/react";
3
+ import type { ReactNode } from "react";
4
+ import { create } from "@bufbuild/protobuf";
5
+ import type { Stigmer } from "@stigmer/sdk";
6
+ import {
7
+ ToolCallSchema,
8
+ ToolCallOutputRefSchema,
9
+ } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/message_pb";
10
+ import { StigmerContext } from "../../context";
11
+ import { McpToolDetail } from "../McpToolDetail";
12
+
13
+ afterEach(cleanup);
14
+
15
+ function withStigmer(children: ReactNode, stigmer: Stigmer) {
16
+ return <StigmerContext.Provider value={stigmer}>{children}</StigmerContext.Provider>;
17
+ }
18
+
19
+ function mcpToolCall(outputRef: ReturnType<typeof create<typeof ToolCallOutputRefSchema>>) {
20
+ return create(ToolCallSchema, {
21
+ id: "tc-1",
22
+ name: "computer_use",
23
+ mcpServerSlug: "computer-use",
24
+ outputRef,
25
+ });
26
+ }
27
+
28
+ describe("McpToolDetail — offloaded output (DD-016 parity)", () => {
29
+ it("renders an offloaded image from a freshly minted URL", async () => {
30
+ const getArtifactDownloadUrl = vi
31
+ .fn()
32
+ .mockResolvedValue({ downloadUrl: "https://fresh/shot.png" });
33
+ const stigmer = {
34
+ agentExecution: { getArtifactDownloadUrl, getArtifactContent: vi.fn() },
35
+ } as unknown as Stigmer;
36
+
37
+ const ref = create(ToolCallOutputRefSchema, {
38
+ storageKey: "artifacts/aex_1/toolcalls/tc-1.png",
39
+ isImage: true,
40
+ mimeType: "image/png",
41
+ });
42
+
43
+ render(withStigmer(<McpToolDetail toolCall={mcpToolCall(ref)} />, stigmer));
44
+
45
+ const img = await screen.findByRole("img");
46
+ expect(img.getAttribute("src")).toBe("https://fresh/shot.png");
47
+ expect(getArtifactDownloadUrl.mock.calls[0][0].storageKey).toBe(
48
+ "artifacts/aex_1/toolcalls/tc-1.png",
49
+ );
50
+ });
51
+
52
+ it("expands offloaded text on demand via getArtifactContent", async () => {
53
+ const getArtifactContent = vi.fn().mockResolvedValue({
54
+ content: new TextEncoder().encode("THE FULL MCP OUTPUT"),
55
+ contentType: "text/plain",
56
+ truncated: false,
57
+ });
58
+ const stigmer = {
59
+ agentExecution: { getArtifactContent, getArtifactDownloadUrl: vi.fn() },
60
+ } as unknown as Stigmer;
61
+
62
+ const ref = create(ToolCallOutputRefSchema, {
63
+ storageKey: "artifacts/aex_1/toolcalls/tc-1.txt",
64
+ isImage: false,
65
+ mimeType: "text/plain",
66
+ sizeBytes: 800_000n,
67
+ truncatedPreview: "preview head…",
68
+ });
69
+
70
+ render(withStigmer(<McpToolDetail toolCall={mcpToolCall(ref)} />, stigmer));
71
+
72
+ expect(screen.getByText("preview head…")).toBeTruthy();
73
+ expect(getArtifactContent).not.toHaveBeenCalled();
74
+
75
+ fireEvent.click(screen.getByText(/View full output/));
76
+
77
+ await waitFor(() => expect(screen.getByText("THE FULL MCP OUTPUT")).toBeTruthy());
78
+ });
79
+ });
@@ -16,12 +16,30 @@ import {
16
16
  ToolCallSchema,
17
17
  } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/message_pb";
18
18
  import { PendingApprovalSchema } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/approval_pb";
19
+ import { TodoItemSchema } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/todo_pb";
20
+ import { FileContentSchema } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/message_pb";
19
21
  import {
22
+ CapturedFileChangeSchema,
23
+ FileChangeSetSchema,
24
+ FileReviewBaselineCapturedSchema,
25
+ FileReviewCandidateCapturedSchema,
26
+ FileReviewEventSchema,
27
+ FileReviewEventStreamSchema,
28
+ FileReviewReconciledSchema,
29
+ } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/filereview_pb";
30
+ import {
31
+ DiffCompleteness,
20
32
  ExecutionPhase,
33
+ FileChangeKind,
34
+ FileChangeSetStatus,
35
+ FileDecisionAction,
36
+ FileReviewEventType,
21
37
  InteractionMode,
22
38
  MessageType,
39
+ TodoStatus,
23
40
  ToolCallStatus,
24
41
  } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
42
+
25
43
  import { MessageThread } from "../MessageThread";
26
44
 
27
45
  // ---------------------------------------------------------------------------
@@ -145,11 +163,266 @@ function makeExecutionWithApproval(
145
163
  return exec;
146
164
  }
147
165
 
166
+ /**
167
+ * An active execution whose AI turn carries a gated tool call, with a matching
168
+ * pending approval — the case that should render the gate INLINE on the tool
169
+ * row rather than as a detached bottom card.
170
+ */
171
+ function makeExecutionWithInlineApproval(
172
+ id: string,
173
+ toolCallId: string,
174
+ toolName: string,
175
+ ): AgentExecution {
176
+ const exec = create(AgentExecutionSchema);
177
+ exec.metadata = create(ApiResourceMetadataSchema, { id });
178
+ exec.spec = create(AgentExecutionSpecSchema, { message: "Do something" });
179
+
180
+ const status = create(AgentExecutionStatusSchema);
181
+ status.phase = ExecutionPhase.EXECUTION_WAITING_FOR_APPROVAL;
182
+
183
+ const aiMsg = create(AgentMessageSchema, {
184
+ type: MessageType.MESSAGE_AI,
185
+ content: "",
186
+ toolCalls: [
187
+ create(ToolCallSchema, {
188
+ id: toolCallId,
189
+ name: toolName,
190
+ status: ToolCallStatus.TOOL_CALL_WAITING_APPROVAL,
191
+ }),
192
+ ],
193
+ });
194
+ status.messages = [aiMsg];
195
+ status.pendingApprovals = [
196
+ create(PendingApprovalSchema, {
197
+ toolCallId,
198
+ toolName,
199
+ argsPreview: '{"path":"/tmp/x"}',
200
+ }),
201
+ ];
202
+ exec.status = status;
203
+ return exec;
204
+ }
205
+
206
+ /**
207
+ * An active execution carrying one single-file change set AWAITING_REVIEW — a
208
+ * PENDING set, which {@link MessageThread} must NOT render (the composer-docked
209
+ * FileReviewDock owns it). Flip the set's status to a settled value to exercise
210
+ * the read-only record rendering.
211
+ */
212
+ function makeExecutionWithFileReview(id: string, setId: string): AgentExecution {
213
+ const exec = create(AgentExecutionSchema);
214
+ exec.metadata = create(ApiResourceMetadataSchema, { id });
215
+ exec.spec = create(AgentExecutionSpecSchema, { message: "Edit a file" });
216
+
217
+ const status = create(AgentExecutionStatusSchema);
218
+ status.phase = ExecutionPhase.EXECUTION_IN_PROGRESS;
219
+ status.messages = [
220
+ create(AgentMessageSchema, { type: MessageType.MESSAGE_AI, content: "Done." }),
221
+ ];
222
+ status.fileChangeSets = [
223
+ create(FileChangeSetSchema, {
224
+ id: setId,
225
+ status: FileChangeSetStatus.AWAITING_REVIEW,
226
+ aggregateDigest: "agg-1",
227
+ diffCompleteness: DiffCompleteness.COMPLETE,
228
+ changes: [
229
+ create(CapturedFileChangeSchema, {
230
+ id: `${setId}:src/a.ts`,
231
+ pathBefore: "src/a.ts",
232
+ pathAfter: "src/a.ts",
233
+ kind: FileChangeKind.MODIFY,
234
+ before: create(FileContentSchema, { body: { case: "inline", value: "old\n" } }),
235
+ after: create(FileContentSchema, { body: { case: "inline", value: "new\n" } }),
236
+ fileDigest: "d-a",
237
+ diffComplete: true,
238
+ }),
239
+ ],
240
+ }),
241
+ ];
242
+ exec.status = status;
243
+ return exec;
244
+ }
245
+
148
246
  // ---------------------------------------------------------------------------
149
247
  // Tests
150
248
  // ---------------------------------------------------------------------------
151
249
 
152
250
  describe("MessageThread", () => {
251
+ it("never renders a PENDING set — the composer-docked FileReviewDock owns the decision surface", () => {
252
+ const exec = makeExecutionWithFileReview("exec-live", "cs-live:0");
253
+ render(
254
+ <MessageThread
255
+ executions={[]}
256
+ activeStreamExecution={exec}
257
+ showFileReviewRecords
258
+ />,
259
+ );
260
+ // Neither the interactive bar nor a record: the pending state lives on the
261
+ // stamped rows' badges, and the decision controls live in the dock.
262
+ expect(screen.queryByText("Review file changes")).toBeNull();
263
+ expect(screen.queryByText("File changes")).toBeNull();
264
+ expect(
265
+ document.querySelector('[data-cursor-target="file-review-approve"]'),
266
+ ).toBeNull();
267
+ });
268
+
269
+ it("renders a settled set as a read-only record (no decision controls)", () => {
270
+ const exec = makeExecutionWithFileReview("exec-settled", "cs-settled:0");
271
+ // A decided/reconciled set: history, not an action.
272
+ exec.status!.fileChangeSets[0].status = FileChangeSetStatus.RECONCILED;
273
+
274
+ render(<MessageThread executions={[exec]} showFileReviewRecords />);
275
+ expect(screen.getByText("File changes")).toBeTruthy();
276
+ expect(screen.queryByText("Review file changes")).toBeNull();
277
+ expect(
278
+ document.querySelector('[data-cursor-target="file-review-approve"]'),
279
+ ).toBeNull();
280
+ expect(
281
+ document.querySelector('[data-cursor-target="file-review-reject"]'),
282
+ ).toBeNull();
283
+ // Record mode is list mode: the expander reveals the file list, no diffs —
284
+ // the stamped edit rows own the diffs.
285
+ fireEvent.click(screen.getByRole("button", { name: "Show" }));
286
+ expect(document.querySelector('[data-cursor-target="file-diff"]')).toBeNull();
287
+ expect(
288
+ document.querySelector('[data-cursor-target="file-review-list-row"]'),
289
+ ).toBeTruthy();
290
+ });
291
+
292
+ it("renders no records at all without showFileReviewRecords (default)", () => {
293
+ const exec = makeExecutionWithFileReview("exec-off", "cs-off:0");
294
+ exec.status!.fileChangeSets[0].status = FileChangeSetStatus.RECONCILED;
295
+
296
+ render(<MessageThread executions={[exec]} />);
297
+ expect(screen.queryByText("File changes")).toBeNull();
298
+ });
299
+
300
+ it("folds the ledger and renders read-only for a terminal execution (empty projection)", () => {
301
+ // A terminal execution: the server projects no actionable file_change_sets,
302
+ // so MessageThread must fold the durable ledger to show what changed.
303
+ const exec = create(AgentExecutionSchema);
304
+ exec.metadata = create(ApiResourceMetadataSchema, { id: "exec-terminal" });
305
+ exec.spec = create(AgentExecutionSpecSchema, { message: "Edit a file" });
306
+ const status = create(AgentExecutionStatusSchema);
307
+ status.phase = ExecutionPhase.EXECUTION_COMPLETED;
308
+ status.messages = [
309
+ create(AgentMessageSchema, { type: MessageType.MESSAGE_AI, content: "Done." }),
310
+ ];
311
+ status.fileChangeSets = []; // terminal: server projects nil
312
+ const changeSetId = "cs-term:0";
313
+ status.fileReviewEventStream = create(FileReviewEventStreamSchema, {
314
+ executionId: "exec-terminal",
315
+ events: [
316
+ create(FileReviewEventSchema, {
317
+ changeSetId,
318
+ eventType: FileReviewEventType.BASELINE_CAPTURED,
319
+ payload: {
320
+ case: "baselineCaptured",
321
+ value: create(FileReviewBaselineCapturedSchema, {
322
+ changeSetId,
323
+ turnId: "t1",
324
+ harnessId: "deep-agent",
325
+ }),
326
+ },
327
+ }),
328
+ create(FileReviewEventSchema, {
329
+ changeSetId,
330
+ eventType: FileReviewEventType.CANDIDATE_CAPTURED,
331
+ payload: {
332
+ case: "candidateCaptured",
333
+ value: create(FileReviewCandidateCapturedSchema, {
334
+ changeSetId,
335
+ aggregateDigest: "agg-1",
336
+ changes: [
337
+ create(CapturedFileChangeSchema, {
338
+ id: `${changeSetId}:src/a.ts`,
339
+ pathBefore: "src/a.ts",
340
+ pathAfter: "src/a.ts",
341
+ kind: FileChangeKind.MODIFY,
342
+ before: create(FileContentSchema, { body: { case: "inline", value: "old\n" } }),
343
+ after: create(FileContentSchema, { body: { case: "inline", value: "new\n" } }),
344
+ fileDigest: "d-a",
345
+ diffComplete: true,
346
+ }),
347
+ ],
348
+ }),
349
+ },
350
+ }),
351
+ create(FileReviewEventSchema, {
352
+ changeSetId,
353
+ eventType: FileReviewEventType.RECONCILED,
354
+ payload: {
355
+ case: "reconciled",
356
+ value: create(FileReviewReconciledSchema, { changeSetId }),
357
+ },
358
+ }),
359
+ ],
360
+ });
361
+ exec.status = status;
362
+
363
+ render(<MessageThread executions={[exec]} showFileReviewRecords />);
364
+ // The read-only record renders with no decision controls; expanding it
365
+ // shows the folded set's changed file.
366
+ expect(screen.getByText("File changes")).toBeTruthy();
367
+ fireEvent.click(screen.getByRole("button", { name: "Show" }));
368
+ expect(screen.getByTitle("src/a.ts")).toBeTruthy();
369
+ expect(
370
+ document.querySelector('[data-cursor-target="file-review-approve"]'),
371
+ ).toBeNull();
372
+ });
373
+
374
+ it("threads an approvalErrors entry to an inline gate (the primary route)", () => {
375
+ const exec = makeExecutionWithInlineApproval("exec-ia", "tc-1", "delete_file");
376
+ const approvalErrors = new Map([["tc-1", new Error("gate already resolved")]]);
377
+
378
+ const { container } = render(
379
+ <MessageThread
380
+ executions={[]}
381
+ activeStreamExecution={exec}
382
+ onApprovalSubmit={() => {}}
383
+ approvalErrors={approvalErrors}
384
+ />,
385
+ );
386
+
387
+ const alert = container.querySelector('[data-cursor-target="approval-error"]');
388
+ expect(alert).toBeTruthy();
389
+ expect(alert!.textContent).toContain("gate already resolved");
390
+ });
391
+
392
+ it("threads an approvalErrors entry to a bottom backstop card (orphan approval)", () => {
393
+ // An approval whose tool call has no inline row renders as the bottom
394
+ // backstop ApprovalCard; the keyed error must reach it too.
395
+ const exec = create(AgentExecutionSchema);
396
+ exec.metadata = create(ApiResourceMetadataSchema, { id: "exec-orphan" });
397
+ exec.spec = create(AgentExecutionSpecSchema, { message: "Do something" });
398
+ const status = create(AgentExecutionStatusSchema);
399
+ status.phase = ExecutionPhase.EXECUTION_WAITING_FOR_APPROVAL;
400
+ status.messages = [];
401
+ status.pendingApprovals = [
402
+ create(PendingApprovalSchema, {
403
+ toolCallId: "tc-orphan",
404
+ toolName: "delete_file",
405
+ argsPreview: '{"path":"/tmp/x"}',
406
+ }),
407
+ ];
408
+ exec.status = status;
409
+
410
+ const approvalErrors = new Map([["tc-orphan", new Error("network down")]]);
411
+
412
+ const { container } = render(
413
+ <MessageThread
414
+ executions={[]}
415
+ activeStreamExecution={exec}
416
+ onApprovalSubmit={() => {}}
417
+ approvalErrors={approvalErrors}
418
+ />,
419
+ );
420
+
421
+ const alert = container.querySelector('[data-cursor-target="approval-error"]');
422
+ expect(alert).toBeTruthy();
423
+ expect(alert!.textContent).toContain("network down");
424
+ });
425
+
153
426
  it("renders role=log container when executions array is empty", () => {
154
427
  render(<MessageThread executions={[]} />);
155
428
 
@@ -176,6 +449,35 @@ describe("MessageThread", () => {
176
449
  ).toBeTruthy();
177
450
  });
178
451
 
452
+ it("renders the agent's todos as an inline card in the thread", () => {
453
+ const exec = makeExecution({
454
+ id: "exec-todos",
455
+ specMessage: "Build the feature",
456
+ aiContent: "Here is my plan",
457
+ });
458
+ // Attach a live plan to the execution status (as the runner would).
459
+ exec.status!.todos = {
460
+ t1: create(TodoItemSchema, {
461
+ id: "t1",
462
+ content: "Scaffold the component",
463
+ status: TodoStatus.TODO_IN_PROGRESS,
464
+ }),
465
+ t2: create(TodoItemSchema, {
466
+ id: "t2",
467
+ content: "Wire the thread",
468
+ status: TodoStatus.TODO_PENDING,
469
+ }),
470
+ };
471
+
472
+ render(<MessageThread executions={[exec]} />);
473
+
474
+ const region = screen.getByRole("region", { name: "Agent to-dos" });
475
+ expect(region).toBeTruthy();
476
+ // Active plan → expanded → tasks visible inline.
477
+ expect(region.textContent).toContain("Scaffold the component");
478
+ expect(region.textContent).toContain("0/2 completed");
479
+ });
480
+
179
481
  it("renders pending user message with opacity indicator", () => {
180
482
  render(
181
483
  <MessageThread
@@ -243,6 +545,28 @@ describe("MessageThread", () => {
243
545
  expect(onApproval.mock.calls[0][0]).toBe("tc-approve");
244
546
  });
245
547
 
548
+ it("renders a matching approval INLINE on its tool row, not as a bottom card, and routes the decision", () => {
549
+ const exec = makeExecutionWithInlineApproval("exec-inline", "tc-inline", "delete_file");
550
+ const onApproval = vi.fn();
551
+
552
+ render(
553
+ <MessageThread
554
+ executions={[]}
555
+ activeStreamExecution={exec}
556
+ onApprovalSubmit={onApproval}
557
+ />,
558
+ );
559
+
560
+ // No detached bottom card (role=alert is the standalone ApprovalCard only).
561
+ expect(screen.queryByRole("alert")).toBeNull();
562
+
563
+ // The gate's actions are present inline, and approving routes the decision
564
+ // with the gated tool's id.
565
+ fireEvent.click(screen.getByRole("button", { name: "Approve" }));
566
+ expect(onApproval).toHaveBeenCalledOnce();
567
+ expect(onApproval.mock.calls[0][0]).toBe("tc-inline");
568
+ });
569
+
246
570
  it("renders ExecutionPhaseBadge for non-completed terminal phase", () => {
247
571
  const exec = makeExecution({
248
572
  id: "exec-fail",