@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
@@ -18,7 +18,7 @@ export interface PlanCompletionCardProps {
18
18
  * Fallback CTA shown in the {@link MessageThread} after a completed Plan-mode
19
19
  * execution that did **not** publish a `plan.md` artifact (older executions, or
20
20
  * a plan whose upload failed). When a plan artifact exists, the richer
21
- * {@link PlanArtifactCard} is shown instead.
21
+ * `PlanArtifactCard` is shown instead.
22
22
  *
23
23
  * Mirrors `PlanArtifactCard`'s hierarchy — one prominent, themeable "Build from
24
24
  * plan" primary action with the same card-scoped `Cmd/Ctrl+Enter` accelerator —
@@ -5,7 +5,8 @@
5
5
  // blocks — instead of a raw JSON dump. The json/text variants are the graceful
6
6
  // fallbacks, so unknown tools are still readable.
7
7
 
8
- import { useMemo } from "react";
8
+ import { useMemo, useState } from "react";
9
+ import type { ReactNode } from "react";
9
10
  import type {
10
11
  ToolResultView,
11
12
  ToolSearchMatch,
@@ -13,15 +14,40 @@ import type {
13
14
  } from "@stigmer/sdk";
14
15
  import { cn } from "@stigmer/theme";
15
16
  import { computeDiff } from "../version-history/computeDiff";
17
+ import { DiffViewer } from "../version-history/DiffViewer";
18
+ import { UnifiedDiffView } from "../version-history/UnifiedDiffView";
16
19
  import type { DiffHunk } from "../version-history/types";
17
20
  import { CollapsibleCode, CollapsiblePre, formatJson } from "./tool-rendering-primitives";
18
21
  import { FilePathLink } from "./FilePathLink";
19
- import { useSandboxNormalize } from "./SandboxContext";
22
+ import { EmptyChangeNotice } from "./EmptyChangeNotice";
23
+ import { TerminalSession } from "./TerminalSession";
24
+ import { execIdFromStorageKey } from "./useFileChangeContent";
25
+ import { useArtifactDownloadUrl } from "./useArtifactDownloadUrl";
26
+ import { useArtifactDownload } from "./useArtifactDownload";
27
+ import { useToolOutputContent } from "./useToolOutputContent";
20
28
 
21
29
  /** Props for {@link ResultView}. */
22
30
  export interface ResultViewProps {
23
31
  /** The normalized result view to render. */
24
32
  readonly view: ToolResultView;
33
+ /**
34
+ * Whether the file/diff variants render the filename header. Defaults to
35
+ * `true`. Set to `false` where an ancestor already names the file (a tool-call
36
+ * row whose header shows the path) so the body is not captioned with a path
37
+ * the user just read; the `+N -M` stats still render.
38
+ */
39
+ readonly showFileName?: boolean;
40
+ /**
41
+ * Whether the result body renders its headline summary stat — the diff's
42
+ * `+N -M`, or a search/list's `N files` / `N matches` / `N items` count.
43
+ * Defaults to `true`. Set to `false` where an ancestor row already shows the
44
+ * summary (a tool-call row that renders `summarizeResultView`) so the body does
45
+ * not repeat the count the user just read. A search/list still surfaces a
46
+ * truncation note when suppressed, since that is information the row header does
47
+ * not carry. Orthogonal to {@link showFileName}: the approval gate suppresses
48
+ * the name but keeps the stats.
49
+ */
50
+ readonly showStats?: boolean;
25
51
  /** Additional CSS class names for the root container. */
26
52
  readonly className?: string;
27
53
  }
@@ -34,18 +60,18 @@ export interface ResultViewProps {
34
60
  * this component renders it. Importable on its own by platform builders who
35
61
  * compose custom tool UIs.
36
62
  */
37
- export function ResultView({ view, className }: ResultViewProps) {
63
+ export function ResultView({ view, showFileName = true, showStats = true, className }: ResultViewProps) {
38
64
  switch (view.type) {
39
65
  case "diff":
40
- return <DiffResultView view={view} className={className} />;
66
+ return <DiffResultView view={view} showFileName={showFileName} showStats={showStats} className={className} />;
41
67
  case "terminal":
42
68
  return <TerminalResultView view={view} className={className} />;
43
69
  case "search":
44
- return <SearchResultView matches={view.matches} count={view.count} className={className} />;
70
+ return <SearchResultView view={view} showCount={showStats} className={className} />;
45
71
  case "list":
46
- return <ListResultView entries={view.entries} count={view.count} className={className} />;
72
+ return <ListResultView entries={view.entries} count={view.count} showCount={showStats} className={className} />;
47
73
  case "file":
48
- return <FileResultView view={view} className={className} />;
74
+ return <FileResultView view={view} showFileName={showFileName} className={className} />;
49
75
  case "contentBlocks":
50
76
  return <ContentBlocksResultView blocks={view.blocks} className={className} />;
51
77
  case "outputRef":
@@ -75,7 +101,7 @@ export function summarizeResultView(view: ToolResultView): string | null {
75
101
  case "terminal":
76
102
  return view.exitCode !== undefined && view.exitCode !== 0 ? `exit ${view.exitCode}` : null;
77
103
  case "search":
78
- return `${view.count} ${view.count === 1 ? "match" : "matches"}`;
104
+ return searchCountLabel(view.count, view.kind);
79
105
  case "list":
80
106
  return `${view.count} ${view.count === 1 ? "item" : "items"}`;
81
107
  default:
@@ -119,7 +145,17 @@ function diffStats(view: DiffView): DiffStats | null {
119
145
  return null;
120
146
  }
121
147
 
122
- function DiffResultView({ view, className }: { view: DiffView; className?: string }) {
148
+ function DiffResultView({
149
+ view,
150
+ showFileName = true,
151
+ showStats = true,
152
+ className,
153
+ }: {
154
+ view: DiffView;
155
+ showFileName?: boolean;
156
+ showStats?: boolean;
157
+ className?: string;
158
+ }) {
123
159
  // The native engine returns no diff, so we compute hunks from args (old/new).
124
160
  // The Cursor envelope provides a ready unified-diff string we render directly.
125
161
  const hunks = useMemo<readonly DiffHunk[]>(() => {
@@ -137,122 +173,60 @@ function DiffResultView({ view, className }: { view: DiffView; className?: strin
137
173
  return hunks.length > 0 ? countHunks(hunks) : null;
138
174
  }, [view.linesAdded, view.linesRemoved, hunks]);
139
175
 
176
+ const showPath = showFileName && Boolean(view.path);
177
+ const showStatsRow = showStats && stats != null;
178
+
179
+ // One diff family across every surface: computed hunks render through the
180
+ // canonical, accessible DiffViewer (line numbers + --stgm-diff-* tokens); a
181
+ // ready hunk-only patch (Cursor envelope) is parsed into the same DiffViewer
182
+ // table by UnifiedDiffView (raw fallback only if unparseable). The header
183
+ // (path + ± stats) is this view's own, so the DiffViewer is used without its
184
+ // filePath header to avoid a duplicate path.
140
185
  return (
141
- <div className={cn("space-y-1", className)}>
142
- <div className="flex items-center gap-2 text-muted-foreground">
143
- {view.path && <FilePathLink path={view.path} className="text-xs" />}
144
- {stats && (
145
- <span className="shrink-0 tabular-nums text-xs">
146
- <span className="text-success">+{stats.added}</span>{" "}
147
- <span className="text-destructive">-{stats.removed}</span>
148
- </span>
149
- )}
150
- </div>
186
+ <div className={cn("space-y-1", className)} data-cursor-target="file-diff">
187
+ {(showPath || showStatsRow) && (
188
+ <div className="flex items-center gap-2 text-muted-foreground">
189
+ {showPath && (
190
+ <FilePathLink path={view.path} dirDisplay="dim" className="text-xs" />
191
+ )}
192
+ {showStatsRow && stats && (
193
+ <span className="shrink-0 tabular-nums text-xs">
194
+ <span className="text-diff-added-fg">+{stats.added}</span>{" "}
195
+ <span className="text-diff-removed-fg">-{stats.removed}</span>
196
+ </span>
197
+ )}
198
+ </div>
199
+ )}
151
200
 
152
201
  {hunks.length > 0 ? (
153
- <DiffHunks hunks={hunks} />
202
+ <DiffViewer hunks={hunks} />
154
203
  ) : view.unifiedDiff ? (
155
- <UnifiedDiffText patch={view.unifiedDiff} />
156
- ) : null}
157
- </div>
158
- );
159
- }
160
-
161
- function DiffHunks({ hunks }: { hunks: readonly DiffHunk[] }) {
162
- return (
163
- <div className="overflow-auto rounded-md border border-border bg-muted-subtle font-mono text-xs">
164
- {hunks.map((hunk, hi) => (
165
- <div key={hi} className="border-b border-border-muted last:border-b-0">
166
- {hunk.lines.map((line, li) => (
167
- <div
168
- key={li}
169
- className={cn(
170
- "whitespace-pre-wrap break-words px-2 py-0.5",
171
- line.type === "added" && "bg-success-subtle text-success",
172
- line.type === "removed" && "bg-destructive-subtle text-destructive",
173
- line.type === "context" && "text-muted-foreground",
174
- )}
175
- >
176
- {/* Screen readers get the change type as words; sighted users get
177
- the +/- sign and color. Never color-only. */}
178
- {line.type !== "context" && (
179
- <span className="sr-only">
180
- {line.type === "added" ? "Added: " : "Removed: "}
181
- </span>
182
- )}
183
- <span className="select-none" aria-hidden="true">
184
- {line.type === "added" ? "+" : line.type === "removed" ? "-" : " "}
185
- </span>
186
- {line.content}
187
- </div>
188
- ))}
189
- </div>
190
- ))}
204
+ <UnifiedDiffView patch={view.unifiedDiff} />
205
+ ) : (
206
+ <EmptyChangeNotice kind="no-preview" />
207
+ )}
191
208
  </div>
192
209
  );
193
210
  }
194
211
 
195
- // Renders a raw unified-diff patch string (Cursor envelope) with +/- coloring.
196
- function UnifiedDiffText({ patch }: { patch: string }) {
197
- const lines = patch.split("\n");
198
- return (
199
- <pre className="max-h-80 overflow-auto whitespace-pre-wrap break-words rounded-md border border-border bg-muted-subtle p-2 font-mono text-xs">
200
- {lines.map((line, i) => (
201
- <div
202
- key={i}
203
- className={cn(
204
- line.startsWith("+") && !line.startsWith("+++") && "text-success",
205
- line.startsWith("-") && !line.startsWith("---") && "text-destructive",
206
- line.startsWith("@@") && "text-primary",
207
- )}
208
- >
209
- {line || " "}
210
- </div>
211
- ))}
212
- </pre>
213
- );
214
- }
215
-
216
212
  // ---------------------------------------------------------------------------
217
213
  // Terminal
218
214
  // ---------------------------------------------------------------------------
219
215
 
220
216
  type TerminalView = Extract<ToolResultView, { type: "terminal" }>;
221
217
 
218
+ // A shell result IS a terminal session: the command prompt line plus its
219
+ // output, rendered as one block by the shared TerminalSession (no separate
220
+ // "Output" caption or command box).
222
221
  function TerminalResultView({ view, className }: { view: TerminalView; className?: string }) {
223
- const normalize = useSandboxNormalize();
224
- const failed = view.exitCode !== undefined && view.exitCode !== 0;
225
-
226
222
  return (
227
- <div className={cn("space-y-1", className)}>
228
- <div className="flex items-center gap-2">
229
- <span className="font-medium text-muted-foreground">Output</span>
230
- {view.exitCode !== undefined && (
231
- <span
232
- className={cn(
233
- "rounded px-1 py-0.5 text-[10px] font-medium leading-none tabular-nums",
234
- failed ? "bg-destructive-subtle text-destructive" : "bg-success-subtle text-success",
235
- )}
236
- >
237
- exit {view.exitCode}
238
- </span>
239
- )}
240
- </div>
241
- <div className="rounded-md border border-border bg-[var(--stgm-terminal-bg,#1a1a2e)] p-2.5">
242
- {view.stdout && (
243
- <CollapsiblePre
244
- content={normalize(view.stdout)}
245
- className="text-[var(--stgm-terminal-fg,#e0e0e0)]"
246
- />
247
- )}
248
- {view.stderr && (
249
- <CollapsiblePre
250
- content={normalize(view.stderr)}
251
- className="text-destructive"
252
- />
253
- )}
254
- </div>
255
- </div>
223
+ <TerminalSession
224
+ command={view.command}
225
+ stdout={view.stdout}
226
+ stderr={view.stderr}
227
+ exitCode={view.exitCode}
228
+ className={className}
229
+ />
256
230
  );
257
231
  }
258
232
 
@@ -260,42 +234,158 @@ function TerminalResultView({ view, className }: { view: TerminalView; className
260
234
  // Search / List
261
235
  // ---------------------------------------------------------------------------
262
236
 
237
+ type SearchView = Extract<ToolResultView, { type: "search" }>;
238
+
239
+ /** "N files" / "N matches" — the noun follows the search subtype. */
240
+ function searchCountLabel(count: number, kind: SearchView["kind"]): string {
241
+ if (kind === "files") {
242
+ return `${count} ${count === 1 ? "file" : "files"}`;
243
+ }
244
+ return `${count} ${count === 1 ? "match" : "matches"}`;
245
+ }
246
+
247
+ /**
248
+ * Renders a search result with a subtype-aware treatment.
249
+ *
250
+ * - **File-name search** (`kind: "files"` — glob / file_search): the matches are
251
+ * paths, rendered as a clickable {@link FilePathLink} list (filename-first,
252
+ * full path on hover, opens on GitHub or copies locally).
253
+ * - **Content search** (`kind: "content"` — grep): the matches are lines,
254
+ * grouped under their file with `line: text` rows; matches with no file
255
+ * association (the native grep shape) render as a flat monospace list.
256
+ *
257
+ * The count header uses the engine's authoritative `count` (which can exceed the
258
+ * returned matches when `truncated`), and a "showing first N" note makes a
259
+ * capped result honest instead of silently partial. Empty results name the
260
+ * subtype ("No files found" vs "No matches") — the query lives in the owning row
261
+ * header, so it is not restated here.
262
+ */
263
263
  function SearchResultView({
264
- matches,
265
- count,
264
+ view,
265
+ showCount = true,
266
266
  className,
267
267
  }: {
268
- matches: readonly ToolSearchMatch[];
269
- count: number;
268
+ view: SearchView;
269
+ showCount?: boolean;
270
270
  className?: string;
271
271
  }) {
272
- if (count === 0) {
273
- return <p className={cn("text-xs text-muted-foreground", className)}>No matches</p>;
272
+ const { matches, count, kind, truncated } = view;
273
+
274
+ if (count === 0 && matches.length === 0) {
275
+ return (
276
+ <p className={cn("text-xs text-muted-foreground", className)}>
277
+ {kind === "files" ? "No files found" : "No matches"}
278
+ </p>
279
+ );
274
280
  }
281
+
282
+ // The owning row header already shows the count (summarizeResultView), so when
283
+ // suppressed the body shows only the truncation note — the one fact the header
284
+ // does not carry. When standalone (showCount), it leads with the full count.
285
+ const header = showCount
286
+ ? `${searchCountLabel(count, kind)}${truncated ? ` \u00b7 showing first ${matches.length}` : ""}`
287
+ : truncated
288
+ ? `Showing first ${matches.length} of ${count}`
289
+ : null;
290
+
275
291
  return (
276
292
  <div className={cn("space-y-1", className)}>
277
- <span className="font-medium text-muted-foreground">
278
- {count} {count === 1 ? "match" : "matches"}
279
- </span>
280
- <div className="max-h-80 overflow-auto rounded-md border border-border bg-muted-subtle font-mono text-xs">
281
- {matches.map((m, i) => (
282
- <div key={i} className="whitespace-pre-wrap break-words border-b border-border-muted px-2 py-0.5 text-foreground last:border-b-0">
283
- {m.file && <span className="text-primary">{m.file}{m.line !== undefined ? `:${m.line}` : ""} </span>}
284
- <span className={m.file ? "text-muted-foreground" : "text-foreground"}>{m.text}</span>
285
- </div>
286
- ))}
287
- </div>
293
+ {header && <span className="font-medium text-muted-foreground">{header}</span>}
294
+ {kind === "files" ? (
295
+ <SearchFileList matches={matches} />
296
+ ) : (
297
+ <SearchContentMatches matches={matches} />
298
+ )}
299
+ </div>
300
+ );
301
+ }
302
+
303
+ /** Bounded, scrollable shell shared by both search result treatments. */
304
+ function SearchResultBox({ children }: { children: ReactNode }) {
305
+ return (
306
+ <div className="max-h-80 overflow-auto rounded-md border border-border bg-muted-subtle text-xs">
307
+ {children}
288
308
  </div>
289
309
  );
290
310
  }
291
311
 
312
+ /** A clickable list of file-name search hits. */
313
+ function SearchFileList({ matches }: { matches: readonly ToolSearchMatch[] }) {
314
+ return (
315
+ <SearchResultBox>
316
+ {matches.map((m, i) => (
317
+ <div
318
+ key={i}
319
+ className="border-b border-border-muted px-2 py-0.5 last:border-b-0"
320
+ >
321
+ <FilePathLink path={m.file ?? m.text} dirDisplay="dim" className="text-xs" />
322
+ </div>
323
+ ))}
324
+ </SearchResultBox>
325
+ );
326
+ }
327
+
328
+ /** Content (grep) matches, grouped under their file when one is provided. */
329
+ function SearchContentMatches({ matches }: { matches: readonly ToolSearchMatch[] }) {
330
+ const groups = useMemo(() => groupMatchesByFile(matches), [matches]);
331
+ return (
332
+ <SearchResultBox>
333
+ {groups.map((group, gi) => (
334
+ <div key={gi} className="border-b border-border-muted last:border-b-0">
335
+ {group.file && (
336
+ <div className="bg-muted-faint px-2 py-0.5">
337
+ <FilePathLink path={group.file} dirDisplay="dim" className="text-xs" />
338
+ </div>
339
+ )}
340
+ {group.matches.map((m, i) => (
341
+ <div
342
+ key={i}
343
+ className="whitespace-pre-wrap break-words px-2 py-0.5 font-mono text-foreground"
344
+ >
345
+ {m.line !== undefined && (
346
+ <span className="select-none text-muted-foreground-subtle">{m.line}: </span>
347
+ )}
348
+ {m.text}
349
+ </div>
350
+ ))}
351
+ </div>
352
+ ))}
353
+ </SearchResultBox>
354
+ );
355
+ }
356
+
357
+ interface MatchGroup {
358
+ readonly file?: string;
359
+ readonly matches: ToolSearchMatch[];
360
+ }
361
+
362
+ /**
363
+ * Groups consecutive matches that share a file into one group, preserving order.
364
+ * Matches with no file association fall into their own file-less group so the
365
+ * native grep shape (line text only) still renders as a flat list.
366
+ */
367
+ function groupMatchesByFile(matches: readonly ToolSearchMatch[]): MatchGroup[] {
368
+ const groups: MatchGroup[] = [];
369
+ for (const m of matches) {
370
+ const last = groups[groups.length - 1];
371
+ if (last && last.file === m.file) {
372
+ last.matches.push(m);
373
+ } else {
374
+ groups.push({ file: m.file, matches: [m] });
375
+ }
376
+ }
377
+ return groups;
378
+ }
379
+
292
380
  function ListResultView({
293
381
  entries,
294
382
  count,
383
+ showCount = true,
295
384
  className,
296
385
  }: {
297
386
  entries: readonly string[];
298
387
  count: number;
388
+ showCount?: boolean;
299
389
  className?: string;
300
390
  }) {
301
391
  if (count === 0) {
@@ -303,9 +393,11 @@ function ListResultView({
303
393
  }
304
394
  return (
305
395
  <div className={cn("space-y-1", className)}>
306
- <span className="font-medium text-muted-foreground">
307
- {count} {count === 1 ? "item" : "items"}
308
- </span>
396
+ {showCount && (
397
+ <span className="font-medium text-muted-foreground">
398
+ {count} {count === 1 ? "item" : "items"}
399
+ </span>
400
+ )}
309
401
  <div className="max-h-80 overflow-auto rounded-md border border-border bg-muted-subtle font-mono text-xs">
310
402
  {entries.map((e, i) => (
311
403
  <div key={i} className="truncate px-2 py-0.5 text-foreground">
@@ -323,17 +415,35 @@ function ListResultView({
323
415
 
324
416
  type FileView = Extract<ToolResultView, { type: "file" }>;
325
417
 
326
- function FileResultView({ view, className }: { view: FileView; className?: string }) {
418
+ function FileResultView({
419
+ view,
420
+ showFileName = true,
421
+ className,
422
+ }: {
423
+ view: FileView;
424
+ showFileName?: boolean;
425
+ className?: string;
426
+ }) {
327
427
  if (!view.content) {
328
- return view.path ? (
329
- <div className={cn("flex items-center gap-1.5 text-xs", className)}>
330
- <FilePathLink path={view.path} className="text-xs" />
331
- </div>
332
- ) : null;
428
+ // No body. When the filename is shown elsewhere (showFileName=false), an
429
+ // empty write fallback degrades honestly to a neutral notice rather than a
430
+ // bare, redundant path; otherwise the path itself is the information (a read).
431
+ if (showFileName && view.path) {
432
+ return (
433
+ <div className={cn("flex items-center gap-1.5 text-xs", className)}>
434
+ <FilePathLink path={view.path} className="text-xs" />
435
+ </div>
436
+ );
437
+ }
438
+ return showFileName ? null : (
439
+ <EmptyChangeNotice kind="no-preview" className={className} />
440
+ );
333
441
  }
334
442
  return (
335
443
  <div className={cn("space-y-1", className)}>
336
- {view.path && <FilePathLink path={view.path} className="text-xs" />}
444
+ {showFileName && view.path && (
445
+ <FilePathLink path={view.path} className="text-xs" />
446
+ )}
337
447
  <CollapsibleCode label={view.truncated ? "Content (truncated)" : "Content"} content={view.content} />
338
448
  </div>
339
449
  );
@@ -369,49 +479,110 @@ function formatBytes(n: number): string {
369
479
  }
370
480
 
371
481
  /**
372
- * Renders a tool result whose bytes were offloaded to artifact storage. Images
373
- * (e.g. computer-use screenshots) render inline as a thumbnail that opens full
374
- * size; other large output shows its inline preview head plus a link to fetch
375
- * the full content. Replaces the old `[image]` placeholder.
482
+ * Renders a tool result whose bytes were offloaded to artifact storage. The
483
+ * bytes are resolved on demand from the stable `storageKey` at view time — the
484
+ * URL once baked into the persisted status expired after an hour, so it is no
485
+ * longer trusted. Images (e.g. computer-use screenshots) render inline via a
486
+ * freshly minted URL; other large output expands its full text in-app, with a
487
+ * download fallback when the server truncates it.
376
488
  */
377
489
  function OutputRefResultView({ view, className }: { view: OutputRefView; className?: string }) {
378
490
  if (view.isImage) {
491
+ return <OutputRefImage storageKey={view.storageKey} className={className} />;
492
+ }
493
+ return <OutputRefText view={view} className={className} />;
494
+ }
495
+
496
+ /** Offloaded image output, rendered from an always-fresh presigned URL. */
497
+ function OutputRefImage({ storageKey, className }: { storageKey: string; className?: string }) {
498
+ const executionId = useMemo(() => execIdFromStorageKey(storageKey), [storageKey]);
499
+ const { url, error } = useArtifactDownloadUrl(executionId, storageKey);
500
+
501
+ if (error) {
502
+ return <p className={cn("text-xs text-destructive", className)}>Couldn&apos;t load image output.</p>;
503
+ }
504
+ if (!url) {
379
505
  return (
380
- <div className={cn("space-y-1", className)}>
381
- <a
382
- href={view.downloadUrl}
383
- target="_blank"
384
- rel="noreferrer"
385
- className="inline-block focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
386
- >
387
- <img
388
- src={view.downloadUrl}
389
- alt="Tool output screenshot"
390
- loading="lazy"
391
- className="max-h-96 w-auto rounded-md border border-border"
392
- />
393
- </a>
394
- </div>
506
+ <div
507
+ className={cn("h-40 w-64 animate-pulse rounded-md border border-border bg-muted", className)}
508
+ aria-busy="true"
509
+ aria-label="Loading image output"
510
+ />
395
511
  );
396
512
  }
397
-
398
513
  return (
399
514
  <div className={cn("space-y-1", className)}>
400
- {view.preview && (
401
- <CollapsiblePre content={view.preview} className="text-foreground" />
402
- )}
403
515
  <a
404
- href={view.downloadUrl}
516
+ href={url}
405
517
  target="_blank"
406
518
  rel="noreferrer"
407
- className="inline-block text-xs font-medium text-primary underline-offset-2 hover:underline focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
519
+ className="inline-block focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
408
520
  >
409
- View full output{view.sizeBytes ? ` (${formatBytes(view.sizeBytes)})` : ""}
521
+ <img
522
+ src={url}
523
+ alt="Tool output screenshot"
524
+ loading="lazy"
525
+ className="max-h-96 w-auto rounded-md border border-border"
526
+ />
410
527
  </a>
411
528
  </div>
412
529
  );
413
530
  }
414
531
 
532
+ /**
533
+ * Offloaded text output. Shows a preview head with a "View full output" toggle
534
+ * that lazily fetches the full content in-app (CORS-safe). If the server
535
+ * truncates it, offers a full download via a freshly minted URL.
536
+ */
537
+ function OutputRefText({ view, className }: { view: OutputRefView; className?: string }) {
538
+ const [expanded, setExpanded] = useState(false);
539
+ const executionId = useMemo(() => execIdFromStorageKey(view.storageKey), [view.storageKey]);
540
+ const { content, isLoading, isTruncated, error } = useToolOutputContent(
541
+ { storageKey: view.storageKey, contentHash: view.contentHash },
542
+ expanded,
543
+ );
544
+ const { download, isDownloading } = useArtifactDownload(executionId);
545
+
546
+ const sizeSuffix = view.sizeBytes ? ` (${formatBytes(view.sizeBytes)})` : "";
547
+
548
+ return (
549
+ <div className={cn("space-y-1", className)}>
550
+ {!expanded ? (
551
+ <>
552
+ {view.preview && (
553
+ <CollapsiblePre content={view.preview} className="text-foreground" />
554
+ )}
555
+ <button
556
+ type="button"
557
+ onClick={() => setExpanded(true)}
558
+ className="inline-block text-xs font-medium text-primary underline-offset-2 hover:underline focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
559
+ >
560
+ View full output{sizeSuffix}
561
+ </button>
562
+ </>
563
+ ) : isLoading ? (
564
+ <p className="text-xs text-muted-foreground">Loading full output…</p>
565
+ ) : error ? (
566
+ <p className="text-xs text-destructive">Couldn&apos;t load full output. Try again.</p>
567
+ ) : content !== null ? (
568
+ <div className="space-y-1">
569
+ <CollapsiblePre content={content} className="text-foreground" />
570
+ {isTruncated && (
571
+ <button
572
+ type="button"
573
+ onClick={() => download(view.storageKey)}
574
+ disabled={isDownloading}
575
+ className="inline-block text-xs font-medium text-primary underline-offset-2 hover:underline focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:opacity-50"
576
+ >
577
+ {isDownloading ? "Preparing download…" : `Output truncated — download full file${sizeSuffix}`}
578
+ </button>
579
+ )}
580
+ </div>
581
+ ) : null}
582
+ </div>
583
+ );
584
+ }
585
+
415
586
  function ErrorResultView({ message, className }: { message: string; className?: string }) {
416
587
  return (
417
588
  <div className={cn("space-y-1", className)}>