@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
@@ -1,4 +1,5 @@
1
1
  import type { ToolCall } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/message_pb";
2
+ import { ToolCallStatus } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
2
3
  import type { JsonObject } from "@bufbuild/protobuf";
3
4
  import { ToolKind, resolveToolKind, resolveToolKindByName } from "@stigmer/sdk";
4
5
 
@@ -27,6 +28,96 @@ export type ToolCategory =
27
28
  | "mcp"
28
29
  | "unknown";
29
30
 
31
+ /**
32
+ * How much of a settled tool call to reveal inline by default in the thread.
33
+ *
34
+ * - `"summary"` — a compact one-line row. Used when the label + primary arg +
35
+ * result summary already tell the story (`Read foo.ts`, `Edit bar.ts +40 -0`,
36
+ * `Search … 2 matches`). The full detail is one click away.
37
+ * - `"preview"` — the row renders its result body **always** (no card chevron),
38
+ * because the one-liner cannot convey what happened so the content itself IS
39
+ * the information (a shell command's output, an MCP result such as a
40
+ * screenshot, a fetched page, a web-search answer, or an unrecognised tool's
41
+ * output). The body's own in-place reveal expands it beyond the height budget.
42
+ *
43
+ * This is the *disclosure* axis (how much of a result to show); it is
44
+ * orthogonal to {@link isRunGroupableCategory} (whether consecutive same-kind
45
+ * calls fold into one chip). A category can be result-rich but never folded
46
+ * (`shell`), or foldable but never previewed (`read`).
47
+ */
48
+ export type ToolDisclosure = "summary" | "preview";
49
+
50
+ /**
51
+ * Categories whose result is foregrounded as a persistent bounded preview by
52
+ * default — those for which the compact one-liner cannot convey what happened,
53
+ * so the output itself carries the meaning. All other categories default to
54
+ * `"summary"`.
55
+ *
56
+ * File edits and writes are previewed: a `+N -M` summary says how much changed
57
+ * but not *what* — and the change is the decision-relevant content. Surfacing a
58
+ * bounded diff inline (Cursor-style) is the point of a code agent. The body is
59
+ * height-clamped with a single in-place reveal for the rest, so density stays
60
+ * under control even in turns with many edits.
61
+ */
62
+ const PREVIEW_CATEGORIES: ReadonlySet<ToolCategory> = new Set<ToolCategory>([
63
+ "shell",
64
+ "edit",
65
+ "write",
66
+ "mcp",
67
+ "web-search",
68
+ "fetch",
69
+ "unknown",
70
+ ]);
71
+
72
+ /**
73
+ * Low-signal categories whose consecutive runs fold into a single collapsible
74
+ * "Read 5 files" chip in the thread. These are the read-only, repetitive
75
+ * operations whose individual rows are noise in bulk; everything else renders
76
+ * as a persistent row. This is the *run-grouping* axis, orthogonal to
77
+ * {@link ToolDisclosure} — see {@link isRunGroupableCategory}.
78
+ */
79
+ const RUN_GROUPABLE_CATEGORIES: ReadonlySet<ToolCategory> = new Set<ToolCategory>([
80
+ "read",
81
+ "list",
82
+ "search",
83
+ ]);
84
+
85
+ /**
86
+ * Returns the default inline {@link ToolDisclosure} for a presentation
87
+ * category. This is the headless policy behind the thread's "keep a live
88
+ * preview for shell/MCP/unknown, keep generic tools compact" behaviour;
89
+ * consumers can override per {@link ToolKind} via {@link registerToolPresenter}.
90
+ */
91
+ export function defaultDisclosureForCategory(
92
+ category: ToolCategory,
93
+ ): ToolDisclosure {
94
+ return PREVIEW_CATEGORIES.has(category) ? "preview" : "summary";
95
+ }
96
+
97
+ /**
98
+ * Returns whether a presentation category participates in run-grouping — i.e.
99
+ * whether a maximal run of consecutive calls of this category folds into one
100
+ * collapsible chip. See {@link RUN_GROUPABLE_CATEGORIES} and `segmentToolCalls`.
101
+ */
102
+ export function isRunGroupableCategory(category: ToolCategory): boolean {
103
+ return RUN_GROUPABLE_CATEGORIES.has(category);
104
+ }
105
+
106
+ /**
107
+ * Returns whether a category's primary argument is a file path — the file
108
+ * tools (read / write / edit / delete). These render their path filename-first
109
+ * via {@link FilePathLink} (full path on hover) in headers and previews, rather
110
+ * than as a raw, often-absolute string.
111
+ */
112
+ export function isFileCategory(category: ToolCategory): boolean {
113
+ return (
114
+ category === "read" ||
115
+ category === "write" ||
116
+ category === "edit" ||
117
+ category === "delete"
118
+ );
119
+ }
120
+
30
121
  /** Resolved display metadata for a tool call, returned by {@link resolveToolCategory}. */
31
122
  export interface ToolCategoryInfo {
32
123
  /** Semantic category of the tool. */
@@ -72,6 +163,31 @@ export function isInternalTool(toolName: string): boolean {
72
163
  return resolveToolKindByName(toolName) === ToolKind.TODO;
73
164
  }
74
165
 
166
+ /**
167
+ * Returns true for a tool call the runner collapsed in place — a superseded
168
+ * same-turn duplicate of an approval gate.
169
+ *
170
+ * When the agent emits one resource twice in a turn and both attempts are denied,
171
+ * the runner overlays the FIRST as the WAITING_APPROVAL gate and blanks the
172
+ * redundant twin to a content-less SKIPPED row (it cannot drop it — the backend's
173
+ * append-only-at-identity guard forbids removing a committed tool-call id). This
174
+ * predicate recognizes that blanked twin so the thread renders one card, not two.
175
+ *
176
+ * The signature is precise: a SKIPPED row with no approval flag, no result, no
177
+ * error, and no args preview. A genuinely user-skipped tool always retains the
178
+ * preview/args the user reviewed before skipping, so it is never blank and is
179
+ * never hidden by this.
180
+ */
181
+ export function isCollapsedToolCall(toolCall: ToolCall): boolean {
182
+ return (
183
+ toolCall.status === ToolCallStatus.TOOL_CALL_SKIPPED &&
184
+ !toolCall.requiresApproval &&
185
+ !toolCall.result &&
186
+ !toolCall.error &&
187
+ !toolCall.argsPreview
188
+ );
189
+ }
190
+
75
191
  /** Maps a {@link ToolKind} to its presentation metadata. */
76
192
  export function toolKindToCategoryInfo(
77
193
  kind: ToolKind,
@@ -226,11 +342,19 @@ export function extractPrimaryArg(toolCall: ToolCall): string | null {
226
342
  * Extracts the primary argument value from a JSON string
227
343
  * (typically `PendingApproval.argsPreview`). Returns `null` if
228
344
  * parsing fails or the expected field is not found.
345
+ *
346
+ * When the caller has the denormalized wire `tool_kind` (as `PendingApproval`
347
+ * does), pass it so resolution honors the same kind that drives the label/icon —
348
+ * a file tool whose NAME the classifier doesn't recognize still resolves to its
349
+ * `path` field. This mirrors {@link extractPrimaryArg}, which uses the wire kind
350
+ * via `resolveToolCategoryFromCall`. Falls back to name-based resolution when
351
+ * the kind is unset (legacy executions).
229
352
  */
230
353
  export function extractPrimaryArgFromPreview(
231
354
  toolName: string,
232
355
  argsPreview: string,
233
356
  mcpServerSlug?: string,
357
+ toolKind?: ToolKind,
234
358
  ): string | null {
235
359
  if (!argsPreview) return null;
236
360
 
@@ -238,7 +362,10 @@ export function extractPrimaryArgFromPreview(
238
362
  const parsed = JSON.parse(argsPreview);
239
363
  if (typeof parsed !== "object" || parsed === null) return null;
240
364
 
241
- const info = resolveToolCategory(toolName, mcpServerSlug);
365
+ const info =
366
+ toolKind !== undefined
367
+ ? resolveToolCategoryFromKind(toolKind, toolName, mcpServerSlug)
368
+ : resolveToolCategory(toolName, mcpServerSlug);
242
369
  return extractArgValue(
243
370
  parsed as JsonObject,
244
371
  info.primaryArgField,
@@ -16,16 +16,34 @@ import { useMemo } from "react";
16
16
  import type { ToolCall } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/message_pb";
17
17
  import { ToolKind, resolveToolKind, normalizeToolResult } from "@stigmer/sdk";
18
18
  import type { ToolResultView } from "@stigmer/sdk";
19
- import { resolveToolCategoryFromCall, extractPrimaryArg } from "./tool-categories";
20
- import type { ToolCategory } from "./tool-categories";
19
+ import {
20
+ resolveToolCategoryFromCall,
21
+ extractPrimaryArg,
22
+ defaultDisclosureForCategory,
23
+ isRunGroupableCategory,
24
+ } from "./tool-categories";
25
+ import type { ToolCategory, ToolDisclosure } from "./tool-categories";
21
26
  import { summarizeResultView } from "./ResultView";
22
27
 
23
- /** Optional per-kind overrides for label and summary. */
28
+ /** Optional per-kind overrides for label, summary, disclosure, and run-grouping. */
24
29
  export interface ToolPresenter {
25
30
  /** Overrides the display label (e.g. "Shell" -> "Run command"). */
26
31
  readonly label?: (toolCall: ToolCall) => string;
27
32
  /** Overrides the one-line result summary suffix. */
28
33
  readonly summary?: (toolCall: ToolCall, result: ToolResultView) => string | null;
34
+ /**
35
+ * Overrides the default inline disclosure. Use to keep a noisy MCP tool
36
+ * compact (`() => "summary"`) or to foreground a custom tool's output
37
+ * (`() => "preview"`) without forking the components.
38
+ */
39
+ readonly disclosure?: (toolCall: ToolCall, result: ToolResultView) => ToolDisclosure;
40
+ /**
41
+ * Overrides whether consecutive same-kind calls fold into one collapsible
42
+ * "Read 5 files" chip. Use to fold a chatty custom tool (`() => true`) or to
43
+ * keep a normally-folded category as standalone rows (`() => false`) without
44
+ * forking the thread layout. Defaults to {@link isRunGroupableCategory}.
45
+ */
46
+ readonly runGroupable?: (toolCall: ToolCall) => boolean;
29
47
  }
30
48
 
31
49
  const registry = new Map<ToolKind, ToolPresenter>();
@@ -72,6 +90,22 @@ export function getToolPresenter(kind: ToolKind): ToolPresenter | undefined {
72
90
  return registry.get(kind);
73
91
  }
74
92
 
93
+ /**
94
+ * Resolves whether a tool call participates in run-grouping, honouring a
95
+ * registered {@link ToolPresenter.runGroupable} override before the
96
+ * category default ({@link isRunGroupableCategory}).
97
+ *
98
+ * This is the non-hook twin of the `runGroupable` field in
99
+ * {@link useToolPresentation}, so the pure `segmentToolCalls` layout pass and
100
+ * the component layer share one source of truth (and honour the same override).
101
+ */
102
+ export function resolveRunGroupable(toolCall: ToolCall): boolean {
103
+ const kind = resolveToolKind(toolCall);
104
+ const override = registry.get(kind)?.runGroupable;
105
+ if (override) return override(toolCall);
106
+ return isRunGroupableCategory(resolveToolCategoryFromCall(toolCall).category);
107
+ }
108
+
75
109
  /** The fully-resolved presentation for a tool call, returned by {@link useToolPresentation}. */
76
110
  export interface ToolPresentation {
77
111
  /** Harness-agnostic kind (wire field, with name fallback). */
@@ -86,6 +120,19 @@ export interface ToolPresentation {
86
120
  readonly result: ToolResultView;
87
121
  /** One-line result summary suffix for the collapsed row (e.g. "+40 -0"). */
88
122
  readonly resultSummary: string | null;
123
+ /**
124
+ * Default inline disclosure for this tool — `"preview"` keeps a bounded,
125
+ * persistent preview of the result, `"summary"` keeps a compact row. The
126
+ * active/awaiting state can still force a row open; this is the *settled*
127
+ * default.
128
+ */
129
+ readonly disclosure: ToolDisclosure;
130
+ /**
131
+ * Whether consecutive same-kind calls fold into one collapsible chip — the
132
+ * run-grouping axis, orthogonal to {@link disclosure}. See
133
+ * {@link isRunGroupableCategory}.
134
+ */
135
+ readonly runGroupable: boolean;
89
136
  }
90
137
 
91
138
  /**
@@ -105,6 +152,12 @@ export function useToolPresentation(toolCall: ToolCall): ToolPresentation {
105
152
  const label = override?.label?.(toolCall) ?? categoryInfo.label;
106
153
  const resultSummary =
107
154
  override?.summary?.(toolCall, result) ?? summarizeResultView(result);
155
+ const disclosure =
156
+ override?.disclosure?.(toolCall, result) ??
157
+ defaultDisclosureForCategory(categoryInfo.category);
158
+ const runGroupable =
159
+ override?.runGroupable?.(toolCall) ??
160
+ isRunGroupableCategory(categoryInfo.category);
108
161
 
109
162
  return {
110
163
  kind,
@@ -113,6 +166,8 @@ export function useToolPresentation(toolCall: ToolCall): ToolPresentation {
113
166
  primaryArg: extractPrimaryArg(toolCall),
114
167
  result,
115
168
  resultSummary,
169
+ disclosure,
170
+ runGroupable,
116
171
  };
117
172
  }, [toolCall]);
118
173
  }
@@ -2,6 +2,7 @@
2
2
 
3
3
  import { useState } from "react";
4
4
  import { cn } from "@stigmer/theme";
5
+ import { RevealToggle } from "../internal/RevealToggle";
5
6
 
6
7
  /**
7
8
  * Shared truncation threshold for all collapsible tool rendering
@@ -48,13 +49,11 @@ export function CollapsibleCode({ label, content, className }: CollapsibleCodePr
48
49
  {displayContent}
49
50
  </pre>
50
51
  {needsTruncation && (
51
- <button
52
- type="button"
53
- onClick={() => setIsExpanded((v) => !v)}
54
- className="text-xs font-medium text-primary transition-colors hover:text-primary-muted"
55
- >
56
- {isExpanded ? "Show less" : `Show all ${lines.length} lines`}
57
- </button>
52
+ <RevealToggle
53
+ expanded={isExpanded}
54
+ onToggle={() => setIsExpanded((v) => !v)}
55
+ moreLabel={`Show all ${lines.length} lines`}
56
+ />
58
57
  )}
59
58
  </div>
60
59
  );
@@ -97,13 +96,11 @@ export function CollapsiblePre({ content, className }: CollapsiblePreProps) {
97
96
  {displayContent}
98
97
  </pre>
99
98
  {needsTruncation && (
100
- <button
101
- type="button"
102
- onClick={() => setIsExpanded((v) => !v)}
103
- className="mt-1 text-xs font-medium text-primary transition-colors hover:text-primary-muted"
104
- >
105
- {isExpanded ? "Show less" : `Show all ${lines.length} lines`}
106
- </button>
99
+ <RevealToggle
100
+ expanded={isExpanded}
101
+ onToggle={() => setIsExpanded((v) => !v)}
102
+ moreLabel={`Show all ${lines.length} lines`}
103
+ />
107
104
  )}
108
105
  </>
109
106
  );
@@ -0,0 +1,109 @@
1
+ "use client";
2
+
3
+ import { useCallback, useMemo, useState } from "react";
4
+ import { create } from "@bufbuild/protobuf";
5
+ import { GetArtifactDownloadUrlRequestSchema } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/io_pb";
6
+ import { useStigmer } from "../hooks";
7
+ import { toError } from "../internal/toError";
8
+
9
+ /** Return value of {@link useArtifactDownload}. */
10
+ export interface UseArtifactDownloadReturn {
11
+ /**
12
+ * Mint a fresh presigned URL for `storageKey` and trigger a browser
13
+ * download. No-op when the execution id is unknown or a download is already
14
+ * in flight. Resolves once the download has been initiated.
15
+ *
16
+ * @param storageKey - Storage key of the artifact to download.
17
+ * @param fileName - Optional suggested file name for the saved file.
18
+ */
19
+ readonly download: (storageKey: string, fileName?: string) => Promise<void>;
20
+
21
+ /** `true` while a URL is being minted / the download is being initiated. */
22
+ readonly isDownloading: boolean;
23
+
24
+ /** Error from the last failed download attempt, or `null` when healthy. */
25
+ readonly error: Error | null;
26
+ }
27
+
28
+ /**
29
+ * Behavior hook that downloads an execution artifact on demand.
30
+ *
31
+ * Mirrors the click → mint fresh URL → browser download flow established by
32
+ * `WorkflowExecutionArtifactPanel`, generalized so every artifact "Download"
33
+ * affordance shares one implementation. The URL is minted at click time from
34
+ * the stable `storageKey` via `getArtifactDownloadUrl`, so it is always valid —
35
+ * unlike the previously persisted, short-lived URL that expired after an hour.
36
+ *
37
+ * Returns an imperative `download(storageKey, fileName?)` action rather than a
38
+ * URL, because a download is a user action, not render data. For rendering an
39
+ * image or showing a link, use {@link useArtifactDownloadUrl} instead.
40
+ *
41
+ * @param executionId - Execution that produced the artifacts, or `null` to
42
+ * disable (the action becomes a no-op).
43
+ *
44
+ * @example
45
+ * ```tsx
46
+ * const { download, isDownloading } = useArtifactDownload(executionId);
47
+ * <button onClick={() => download(artifact.storageKey, artifact.name)} disabled={isDownloading}>
48
+ * Download
49
+ * </button>
50
+ * ```
51
+ *
52
+ * @see useArtifactDownloadUrl — declarative URL resolver (image src, links)
53
+ */
54
+ export function useArtifactDownload(
55
+ executionId: string | null,
56
+ ): UseArtifactDownloadReturn {
57
+ const stigmer = useStigmer();
58
+ const [isDownloading, setIsDownloading] = useState(false);
59
+ const [error, setError] = useState<Error | null>(null);
60
+
61
+ const download = useCallback(
62
+ async (storageKey: string, fileName?: string) => {
63
+ if (!executionId || !storageKey || isDownloading) return;
64
+ setIsDownloading(true);
65
+ setError(null);
66
+ try {
67
+ const result = await stigmer.agentExecution.getArtifactDownloadUrl(
68
+ create(GetArtifactDownloadUrlRequestSchema, { executionId, storageKey }),
69
+ );
70
+ if (result.downloadUrl) {
71
+ triggerBrowserDownload(result.downloadUrl, fileName);
72
+ }
73
+ } catch (err) {
74
+ setError(toError(err));
75
+ } finally {
76
+ setIsDownloading(false);
77
+ }
78
+ },
79
+ [executionId, stigmer, isDownloading],
80
+ );
81
+
82
+ return useMemo(
83
+ () => ({ download, isDownloading, error }),
84
+ [download, isDownloading, error],
85
+ );
86
+ }
87
+
88
+ /**
89
+ * Trigger a browser download for a URL via a transient anchor click. The
90
+ * `download` hint is best-effort: browsers ignore it for cross-origin URLs
91
+ * (e.g. R2), in which case the navigation still serves the file. Falls back to
92
+ * a new tab in non-DOM environments.
93
+ */
94
+ function triggerBrowserDownload(url: string, fileName?: string): void {
95
+ if (typeof document === "undefined") {
96
+ if (typeof window !== "undefined") {
97
+ window.open(url, "_blank", "noopener,noreferrer");
98
+ }
99
+ return;
100
+ }
101
+ const anchor = document.createElement("a");
102
+ anchor.href = url;
103
+ if (fileName) anchor.download = fileName;
104
+ anchor.rel = "noopener noreferrer";
105
+ anchor.target = "_blank";
106
+ document.body.appendChild(anchor);
107
+ anchor.click();
108
+ document.body.removeChild(anchor);
109
+ }
@@ -0,0 +1,116 @@
1
+ "use client";
2
+
3
+ import { useMemo } from "react";
4
+ import { create } from "@bufbuild/protobuf";
5
+ import { GetArtifactDownloadUrlRequestSchema } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/io_pb";
6
+ import { useStigmer } from "../hooks";
7
+ import { useFetch } from "../internal/useFetch";
8
+
9
+ /** Return value of {@link useArtifactDownloadUrl}. */
10
+ export interface UseArtifactDownloadUrlReturn {
11
+ /**
12
+ * A freshly minted presigned download URL for the artifact, or `null` when:
13
+ * - Fetching is skipped (`enabled` is `false`, or `executionId`/`storageKey` is `null`)
14
+ * - The request is in-flight
15
+ * - The request failed
16
+ */
17
+ readonly url: string | null;
18
+
19
+ /** `true` while the URL request is in-flight (first load). */
20
+ readonly isLoading: boolean;
21
+
22
+ /** `true` while a background refetch is in flight. */
23
+ readonly isRefetching: boolean;
24
+
25
+ /** Error from the last failed attempt, or `null` when healthy. */
26
+ readonly error: Error | null;
27
+
28
+ /** Re-fetch the URL (e.g. to mint a fresh one after the previous expired). */
29
+ readonly refetch: () => void;
30
+ }
31
+
32
+ interface UrlData {
33
+ readonly url: string | null;
34
+ }
35
+
36
+ const EMPTY: UrlData = { url: null };
37
+
38
+ /** Options for {@link useArtifactDownloadUrl}. */
39
+ export interface UseArtifactDownloadUrlOptions {
40
+ /**
41
+ * Gate the request. When `false`, the hook stays idle and returns `null` —
42
+ * use this to defer minting a URL until it is actually needed (e.g. only
43
+ * when an image becomes visible, or after a truncation is detected).
44
+ * Defaults to `true`.
45
+ */
46
+ readonly enabled?: boolean;
47
+ }
48
+
49
+ /**
50
+ * Data hook that resolves a **fresh** presigned download URL for an execution
51
+ * artifact on demand via `stigmer.agentExecution.getArtifactDownloadUrl()`.
52
+ *
53
+ * This exists because presigned URLs expire. The runner once baked a
54
+ * short-lived URL into the persisted execution status and the UI rendered it
55
+ * verbatim — so any link opened after the URL's lifetime failed with
56
+ * `ExpiredRequest`. Minting the URL at view time from the stable `storageKey`
57
+ * guarantees it is always valid, and works for every historical execution
58
+ * (the `storageKey` never expires).
59
+ *
60
+ * Use this for surfaces that genuinely need a URL — an `<img src>` for an
61
+ * offloaded screenshot, or a direct browser download of a large/binary
62
+ * artifact. For text that should render in-app, prefer {@link useArtifactContent}
63
+ * (CORS-safe, no presign).
64
+ *
65
+ * The URL is cached cross-mount by `storageKey` (DD-014), so repeated renders
66
+ * and remounts reuse the same minted URL instead of re-hitting the API.
67
+ *
68
+ * Pass `enabled: false` (or `null` for either id) to skip fetching — consistent
69
+ * with the `useArtifactContent(null, null)` convention.
70
+ *
71
+ * @param executionId - Execution that produced the artifact, or `null` to skip.
72
+ * @param storageKey - Storage key from the artifact/output ref, or `null` to skip.
73
+ * @param options - Optional gating (`enabled`).
74
+ *
75
+ * @example
76
+ * ```tsx
77
+ * // Render an offloaded screenshot with an always-fresh URL.
78
+ * const { url, isLoading } = useArtifactDownloadUrl(executionId, storageKey);
79
+ * if (isLoading) return <Skeleton />;
80
+ * return url ? <img src={url} alt="Tool output" /> : null;
81
+ * ```
82
+ *
83
+ * @see useArtifactContent — reads artifact bytes through the API (text preview)
84
+ * @see useArtifactDownload — imperative click-to-download counterpart
85
+ */
86
+ export function useArtifactDownloadUrl(
87
+ executionId: string | null,
88
+ storageKey: string | null,
89
+ options?: UseArtifactDownloadUrlOptions,
90
+ ): UseArtifactDownloadUrlReturn {
91
+ const stigmer = useStigmer();
92
+ const enabled = options?.enabled ?? true;
93
+ const active = enabled && !!executionId && !!storageKey;
94
+
95
+ const { data, isLoading, isRefetching, error, refetch } = useFetch(
96
+ active
97
+ ? () =>
98
+ stigmer.agentExecution
99
+ .getArtifactDownloadUrl(
100
+ create(GetArtifactDownloadUrlRequestSchema, {
101
+ executionId: executionId!,
102
+ storageKey: storageKey!,
103
+ }),
104
+ )
105
+ .then((result): UrlData => ({ url: result.downloadUrl || null }))
106
+ : null,
107
+ [executionId, storageKey, active, stigmer],
108
+ EMPTY,
109
+ { cacheKey: storageKey ? `artifact-url:${storageKey}` : undefined },
110
+ );
111
+
112
+ return useMemo(
113
+ () => ({ url: data.url, isLoading, isRefetching, error, refetch }),
114
+ [data.url, isLoading, isRefetching, error, refetch],
115
+ );
116
+ }
@@ -0,0 +1,149 @@
1
+ "use client";
2
+
3
+ import { useMemo } from "react";
4
+ import type {
5
+ FileChange,
6
+ FileContent,
7
+ } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/message_pb";
8
+ import { useArtifactContent } from "./useArtifactContent";
9
+ import { useArtifactDownloadUrl } from "./useArtifactDownloadUrl";
10
+
11
+ /** Return value of {@link useFileChangeContent}. */
12
+ export interface UseFileChangeContentReturn {
13
+ /**
14
+ * Before-side text for diffing. `""` for an absent side (e.g. CREATE has no
15
+ * before). `null` while an offloaded side is in-flight or failed to load.
16
+ */
17
+ readonly beforeText: string | null;
18
+ /**
19
+ * After-side text for diffing. `""` for an absent side (e.g. DELETE has no
20
+ * after). `null` while an offloaded side is in-flight or failed to load.
21
+ */
22
+ readonly afterText: string | null;
23
+ /** `true` when either side is binary — render "binary file changed", not a diff. */
24
+ readonly isBinary: boolean;
25
+ /** `true` while an offloaded side's content request is in flight. */
26
+ readonly isLoading: boolean;
27
+ /** Error from a failed offloaded-side fetch, or `null`. */
28
+ readonly error: Error | null;
29
+ /**
30
+ * `true` when an offloaded side exceeded the server's content-size limit and
31
+ * was truncated — the inline diff would be incomplete; offer {@link downloadUrl}.
32
+ */
33
+ readonly isTruncated: boolean;
34
+ /**
35
+ * Direct download URL for an offloaded side (the after side preferred), used
36
+ * as the fallback when content is truncated. `null` when neither side is offloaded.
37
+ */
38
+ readonly downloadUrl: string | null;
39
+ }
40
+
41
+ /**
42
+ * Behavior hook that resolves a {@link FileChange}'s before/after sides to the
43
+ * text the diff renderer needs.
44
+ *
45
+ * Small bodies are carried inline on the proto and returned with zero cost.
46
+ * Large bodies were offloaded to artifact storage by the runner
47
+ * (`FileContent.ref`); this hook lazily fetches them via
48
+ * {@link useArtifactContent} through the server proxy (CORS-safe). The fetch's
49
+ * `executionId` is derived from the ref's own `storageKey`
50
+ * (`artifacts/{executionId}/...`), so a net diff whose two sides originated in
51
+ * different executions still resolves each side correctly.
52
+ *
53
+ * This is the single place offload refs are dereferenced — the SDK's
54
+ * `tool-view` deliberately stays fetch-free, deferring all ref handling here.
55
+ *
56
+ * Rules-of-hooks safe: both sides always issue a (possibly skipped)
57
+ * `useArtifactContent` call; inline/absent sides pass `null` and never fetch.
58
+ *
59
+ * @param change - The file change whose content to resolve.
60
+ *
61
+ * @see useSessionFileChanges — produces the changes this resolves
62
+ * @see FileChangeDiff — renders the resolved content
63
+ */
64
+ export function useFileChangeContent(
65
+ change: FileChange,
66
+ ): UseFileChangeContentReturn {
67
+ const beforeRef = refOf(change.before);
68
+ const afterRef = refOf(change.after);
69
+
70
+ const beforeFetch = useArtifactContent(
71
+ beforeRef ? execIdFromStorageKey(beforeRef.storageKey) : null,
72
+ beforeRef ? beforeRef.storageKey : null,
73
+ null,
74
+ beforeRef?.contentHash,
75
+ );
76
+ const afterFetch = useArtifactContent(
77
+ afterRef ? execIdFromStorageKey(afterRef.storageKey) : null,
78
+ afterRef ? afterRef.storageKey : null,
79
+ null,
80
+ afterRef?.contentHash,
81
+ );
82
+
83
+ // The download fallback is needed only when an offloaded side was truncated.
84
+ // Resolve a fresh URL on demand from the stable storageKey (after side
85
+ // preferred), gated on truncation so we don't mint a URL we won't use.
86
+ const isTruncated = beforeFetch.isTruncated || afterFetch.isTruncated;
87
+ const downloadRef = afterRef ?? beforeRef;
88
+ const { url: downloadUrl } = useArtifactDownloadUrl(
89
+ downloadRef ? execIdFromStorageKey(downloadRef.storageKey) : null,
90
+ downloadRef ? downloadRef.storageKey : null,
91
+ { enabled: isTruncated },
92
+ );
93
+
94
+ return useMemo(
95
+ () => ({
96
+ beforeText: sideText(change.before, beforeFetch.content),
97
+ afterText: sideText(change.after, afterFetch.content),
98
+ isBinary: Boolean(change.before?.isBinary || change.after?.isBinary),
99
+ isLoading: beforeFetch.isLoading || afterFetch.isLoading,
100
+ error: beforeFetch.error ?? afterFetch.error,
101
+ isTruncated,
102
+ downloadUrl,
103
+ }),
104
+ [
105
+ change.before,
106
+ change.after,
107
+ beforeFetch.content,
108
+ afterFetch.content,
109
+ beforeFetch.isLoading,
110
+ afterFetch.isLoading,
111
+ beforeFetch.error,
112
+ afterFetch.error,
113
+ isTruncated,
114
+ downloadUrl,
115
+ ],
116
+ );
117
+ }
118
+
119
+ /** The offload pointer of a side, or null when the side is absent/inline/binary. */
120
+ function refOf(side: FileContent | undefined) {
121
+ if (!side || side.isBinary) return null;
122
+ return side.body.case === "ref" ? side.body.value : null;
123
+ }
124
+
125
+ /**
126
+ * Resolves one side to text: absent → "" (empty file), inline → its value,
127
+ * offloaded → the fetched content (`null` until it loads).
128
+ */
129
+ function sideText(
130
+ side: FileContent | undefined,
131
+ fetched: string | null,
132
+ ): string | null {
133
+ if (!side || side.isBinary) return "";
134
+ if (side.body.case === "inline") return side.body.value;
135
+ return fetched;
136
+ }
137
+
138
+ /**
139
+ * Extracts the execution id from an artifact storage key of the form
140
+ * `artifacts/{executionId}/...`. Returns `null` for an unexpected shape so the
141
+ * caller skips the fetch rather than issuing a request the server would reject.
142
+ */
143
+ export function execIdFromStorageKey(storageKey: string): string | null {
144
+ const parts = storageKey.split("/");
145
+ if (parts.length >= 3 && parts[0] === "artifacts" && parts[1]) {
146
+ return parts[1];
147
+ }
148
+ return null;
149
+ }