@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.
- package/execution/ApprovalCard.d.ts +47 -3
- package/execution/ApprovalCard.d.ts.map +1 -1
- package/execution/ApprovalCard.js +138 -70
- package/execution/ApprovalCard.js.map +1 -1
- package/execution/ApprovalContext.d.ts +72 -0
- package/execution/ApprovalContext.d.ts.map +1 -0
- package/execution/ApprovalContext.js +41 -0
- package/execution/ApprovalContext.js.map +1 -0
- package/execution/ArtifactCard.d.ts.map +1 -1
- package/execution/ArtifactCard.js +3 -1
- package/execution/ArtifactCard.js.map +1 -1
- package/execution/ArtifactPreviewModal.d.ts.map +1 -1
- package/execution/ArtifactPreviewModal.js +5 -3
- package/execution/ArtifactPreviewModal.js.map +1 -1
- package/execution/EmptyChangeNotice.d.ts +38 -0
- package/execution/EmptyChangeNotice.d.ts.map +1 -0
- package/execution/EmptyChangeNotice.js +27 -0
- package/execution/EmptyChangeNotice.js.map +1 -0
- package/execution/FileChangesView.d.ts +81 -0
- package/execution/FileChangesView.d.ts.map +1 -0
- package/execution/FileChangesView.js +164 -0
- package/execution/FileChangesView.js.map +1 -0
- package/execution/FilePathLink.d.ts +24 -4
- package/execution/FilePathLink.d.ts.map +1 -1
- package/execution/FilePathLink.js +31 -9
- package/execution/FilePathLink.js.map +1 -1
- package/execution/FileReviewCard.d.ts +111 -0
- package/execution/FileReviewCard.d.ts.map +1 -0
- package/execution/FileReviewCard.js +558 -0
- package/execution/FileReviewCard.js.map +1 -0
- package/execution/FileReviewContext.d.ts +35 -0
- package/execution/FileReviewContext.d.ts.map +1 -0
- package/execution/FileReviewContext.js +23 -0
- package/execution/FileReviewContext.js.map +1 -0
- package/execution/FileReviewDock.d.ts +73 -0
- package/execution/FileReviewDock.d.ts.map +1 -0
- package/execution/FileReviewDock.js +65 -0
- package/execution/FileReviewDock.js.map +1 -0
- package/execution/McpToolDetail.d.ts.map +1 -1
- package/execution/McpToolDetail.js +38 -5
- package/execution/McpToolDetail.js.map +1 -1
- package/execution/MessageThread.d.ts +42 -3
- package/execution/MessageThread.d.ts.map +1 -1
- package/execution/MessageThread.js +263 -18
- package/execution/MessageThread.js.map +1 -1
- package/execution/PlanArtifactCard.d.ts.map +1 -1
- package/execution/PlanArtifactCard.js +3 -1
- package/execution/PlanArtifactCard.js.map +1 -1
- package/execution/PlanCompletionCard.d.ts +1 -1
- package/execution/PlanCompletionCard.js +1 -1
- package/execution/ResultView.d.ts +19 -1
- package/execution/ResultView.d.ts.map +1 -1
- package/execution/ResultView.js +141 -40
- package/execution/ResultView.js.map +1 -1
- package/execution/SubAgentSection.d.ts.map +1 -1
- package/execution/SubAgentSection.js +45 -6
- package/execution/SubAgentSection.js.map +1 -1
- package/execution/TerminalSession.d.ts +34 -0
- package/execution/TerminalSession.d.ts.map +1 -0
- package/execution/TerminalSession.js +34 -0
- package/execution/TerminalSession.js.map +1 -0
- package/execution/TodoCard.d.ts +53 -0
- package/execution/TodoCard.d.ts.map +1 -0
- package/execution/TodoCard.js +77 -0
- package/execution/TodoCard.js.map +1 -0
- package/execution/ToolArgsView.d.ts +7 -1
- package/execution/ToolArgsView.d.ts.map +1 -1
- package/execution/ToolArgsView.js +10 -9
- package/execution/ToolArgsView.js.map +1 -1
- package/execution/ToolCallDetail.d.ts +10 -1
- package/execution/ToolCallDetail.d.ts.map +1 -1
- package/execution/ToolCallDetail.js +66 -19
- package/execution/ToolCallDetail.js.map +1 -1
- package/execution/ToolCallGroup.d.ts +28 -18
- package/execution/ToolCallGroup.d.ts.map +1 -1
- package/execution/ToolCallGroup.js +39 -148
- package/execution/ToolCallGroup.js.map +1 -1
- package/execution/ToolCallItem.d.ts +36 -8
- package/execution/ToolCallItem.d.ts.map +1 -1
- package/execution/ToolCallItem.js +140 -21
- package/execution/ToolCallItem.js.map +1 -1
- package/execution/ToolRunGroup.d.ts +48 -0
- package/execution/ToolRunGroup.d.ts.map +1 -0
- package/execution/ToolRunGroup.js +168 -0
- package/execution/ToolRunGroup.js.map +1 -0
- package/execution/approval-provenance.d.ts +2 -0
- package/execution/approval-provenance.d.ts.map +1 -0
- package/execution/approval-provenance.js +6 -0
- package/execution/approval-provenance.js.map +1 -0
- package/execution/artifact-utils.d.ts +0 -10
- package/execution/artifact-utils.d.ts.map +1 -1
- package/execution/artifact-utils.js +0 -17
- package/execution/artifact-utils.js.map +1 -1
- package/execution/execution-phases.d.ts +1 -6
- package/execution/execution-phases.d.ts.map +1 -1
- package/execution/execution-phases.js +4 -14
- package/execution/execution-phases.js.map +1 -1
- package/execution/file-path-resolver.d.ts +28 -0
- package/execution/file-path-resolver.d.ts.map +1 -1
- package/execution/file-path-resolver.js +24 -0
- package/execution/file-path-resolver.js.map +1 -1
- package/execution/file-review-status.d.ts +127 -0
- package/execution/file-review-status.d.ts.map +1 -0
- package/execution/file-review-status.js +135 -0
- package/execution/file-review-status.js.map +1 -0
- package/execution/index.d.ts +39 -5
- package/execution/index.d.ts.map +1 -1
- package/execution/index.js +23 -3
- package/execution/index.js.map +1 -1
- package/execution/segment-tool-calls.d.ts +46 -0
- package/execution/segment-tool-calls.d.ts.map +1 -0
- package/execution/segment-tool-calls.js +71 -0
- package/execution/segment-tool-calls.js.map +1 -0
- package/execution/tool-categories.d.ts +62 -1
- package/execution/tool-categories.d.ts.map +1 -1
- package/execution/tool-categories.js +96 -2
- package/execution/tool-categories.js.map +1 -1
- package/execution/tool-presenter.d.ts +38 -2
- package/execution/tool-presenter.d.ts.map +1 -1
- package/execution/tool-presenter.js +23 -1
- package/execution/tool-presenter.js.map +1 -1
- package/execution/tool-rendering-primitives.d.ts.map +1 -1
- package/execution/tool-rendering-primitives.js +3 -2
- package/execution/tool-rendering-primitives.js.map +1 -1
- package/execution/useArtifactDownload.d.ts +44 -0
- package/execution/useArtifactDownload.d.ts.map +1 -0
- package/execution/useArtifactDownload.js +80 -0
- package/execution/useArtifactDownload.js.map +1 -0
- package/execution/useArtifactDownloadUrl.d.ts +67 -0
- package/execution/useArtifactDownloadUrl.d.ts.map +1 -0
- package/execution/useArtifactDownloadUrl.js +59 -0
- package/execution/useArtifactDownloadUrl.js.map +1 -0
- package/execution/useFileChangeContent.d.ts +61 -0
- package/execution/useFileChangeContent.d.ts.map +1 -0
- package/execution/useFileChangeContent.js +89 -0
- package/execution/useFileChangeContent.js.map +1 -0
- package/execution/useFileReview.d.ts +89 -0
- package/execution/useFileReview.d.ts.map +1 -0
- package/execution/useFileReview.js +106 -0
- package/execution/useFileReview.js.map +1 -0
- package/execution/useSubmitApproval.d.ts +17 -2
- package/execution/useSubmitApproval.d.ts.map +1 -1
- package/execution/useSubmitApproval.js +27 -23
- package/execution/useSubmitApproval.js.map +1 -1
- package/execution/useToolOutputContent.d.ts +44 -0
- package/execution/useToolOutputContent.d.ts.map +1 -0
- package/execution/useToolOutputContent.js +31 -0
- package/execution/useToolOutputContent.js.map +1 -0
- package/index.d.ts +6 -6
- package/index.d.ts.map +1 -1
- package/index.js +3 -3
- package/index.js.map +1 -1
- package/internal/ApprovalPeekBar.d.ts +23 -0
- package/internal/ApprovalPeekBar.d.ts.map +1 -0
- package/internal/ApprovalPeekBar.js +25 -0
- package/internal/ApprovalPeekBar.js.map +1 -0
- package/internal/BoundedContent.d.ts +49 -0
- package/internal/BoundedContent.d.ts.map +1 -0
- package/internal/BoundedContent.js +50 -0
- package/internal/BoundedContent.js.map +1 -0
- package/internal/DecisionButton.d.ts +47 -0
- package/internal/DecisionButton.d.ts.map +1 -0
- package/internal/DecisionButton.js +42 -0
- package/internal/DecisionButton.js.map +1 -0
- package/internal/InCardDecisionError.d.ts +31 -0
- package/internal/InCardDecisionError.d.ts.map +1 -0
- package/internal/InCardDecisionError.js +23 -0
- package/internal/InCardDecisionError.js.map +1 -0
- package/internal/RevealToggle.d.ts +36 -0
- package/internal/RevealToggle.d.ts.map +1 -0
- package/internal/RevealToggle.js +32 -0
- package/internal/RevealToggle.js.map +1 -0
- package/internal/VirtualizedThread.d.ts +7 -1
- package/internal/VirtualizedThread.d.ts.map +1 -1
- package/internal/VirtualizedThread.js +7 -3
- package/internal/VirtualizedThread.js.map +1 -1
- package/internal/useAutoDisclosure.d.ts +34 -0
- package/internal/useAutoDisclosure.d.ts.map +1 -0
- package/internal/useAutoDisclosure.js +53 -0
- package/internal/useAutoDisclosure.js.map +1 -0
- package/internal/useElapsedSince.d.ts +30 -0
- package/internal/useElapsedSince.d.ts.map +1 -0
- package/internal/useElapsedSince.js +70 -0
- package/internal/useElapsedSince.js.map +1 -0
- package/internal/useIsOverflowing.d.ts +30 -0
- package/internal/useIsOverflowing.d.ts.map +1 -0
- package/internal/useIsOverflowing.js +47 -0
- package/internal/useIsOverflowing.js.map +1 -0
- package/internal/useIsTextTruncated.d.ts +24 -0
- package/internal/useIsTextTruncated.d.ts.map +1 -0
- package/internal/useIsTextTruncated.js +41 -0
- package/internal/useIsTextTruncated.js.map +1 -0
- package/internal/useKeyedSubmission.d.ts +49 -0
- package/internal/useKeyedSubmission.d.ts.map +1 -0
- package/internal/useKeyedSubmission.js +67 -0
- package/internal/useKeyedSubmission.js.map +1 -0
- package/package.json +4 -4
- package/session/SessionViewer.d.ts +2 -2
- package/session/SessionViewer.d.ts.map +1 -1
- package/session/SessionViewer.js +10 -4
- package/session/SessionViewer.js.map +1 -1
- package/session/index.d.ts +2 -0
- package/session/index.d.ts.map +1 -1
- package/session/index.js +1 -0
- package/session/index.js.map +1 -1
- package/session/inspector/ChangesTab.d.ts +8 -5
- package/session/inspector/ChangesTab.d.ts.map +1 -1
- package/session/inspector/ChangesTab.js +11 -8
- package/session/inspector/ChangesTab.js.map +1 -1
- package/session/inspector/SessionInspector.d.ts +4 -4
- package/session/inspector/SessionInspector.d.ts.map +1 -1
- package/session/inspector/SessionInspector.js +4 -4
- package/session/inspector/SessionInspector.js.map +1 -1
- package/session/inspector/index.d.ts +0 -2
- package/session/inspector/index.d.ts.map +1 -1
- package/session/inspector/index.js +0 -1
- package/session/inspector/index.js.map +1 -1
- package/session/inspector/useSessionInspector.d.ts +8 -4
- package/session/inspector/useSessionInspector.d.ts.map +1 -1
- package/session/inspector/useSessionInspector.js +27 -20
- package/session/inspector/useSessionInspector.js.map +1 -1
- package/session/useSessionConversation.d.ts +37 -3
- package/session/useSessionConversation.d.ts.map +1 -1
- package/session/useSessionConversation.js +18 -2
- package/session/useSessionConversation.js.map +1 -1
- package/session/useSessionFileChanges.d.ts +74 -0
- package/session/useSessionFileChanges.d.ts.map +1 -0
- package/session/useSessionFileChanges.js +178 -0
- package/session/useSessionFileChanges.js.map +1 -0
- package/src/__tests__/styles-border-layer-invariant.test.ts +105 -0
- package/src/execution/ApprovalCard.tsx +368 -230
- package/src/execution/ApprovalContext.ts +109 -0
- package/src/execution/ArtifactCard.tsx +10 -6
- package/src/execution/ArtifactPreviewModal.tsx +12 -6
- package/src/execution/EmptyChangeNotice.tsx +62 -0
- package/src/execution/FileChangesView.tsx +383 -0
- package/src/execution/FilePathLink.tsx +65 -12
- package/src/execution/FileReviewCard.tsx +1118 -0
- package/src/execution/FileReviewContext.ts +53 -0
- package/src/execution/FileReviewDock.tsx +157 -0
- package/src/execution/McpToolDetail.tsx +98 -32
- package/src/execution/MessageThread.tsx +374 -9
- package/src/execution/PlanArtifactCard.tsx +9 -6
- package/src/execution/PlanCompletionCard.tsx +1 -1
- package/src/execution/ResultView.tsx +333 -162
- package/src/execution/SubAgentSection.tsx +58 -11
- package/src/execution/TerminalSession.tsx +91 -0
- package/src/execution/TodoCard.tsx +186 -0
- package/src/execution/ToolArgsView.tsx +28 -17
- package/src/execution/ToolCallDetail.tsx +137 -26
- package/src/execution/ToolCallGroup.tsx +68 -296
- package/src/execution/ToolCallItem.tsx +265 -67
- package/src/execution/ToolRunGroup.tsx +302 -0
- package/src/execution/__tests__/ApprovalCard.test.tsx +395 -5
- package/src/execution/__tests__/ArtifactPreviewModal.test.tsx +0 -1
- package/src/execution/__tests__/EmptyChangeNotice.test.tsx +24 -0
- package/src/execution/__tests__/FileChangesView.test.tsx +296 -0
- package/src/execution/__tests__/FilePathLink.test.tsx +92 -0
- package/src/execution/__tests__/FileReviewCard.test.tsx +1044 -0
- package/src/execution/__tests__/FileReviewDock.test.tsx +134 -0
- package/src/execution/__tests__/McpToolDetail.test.tsx +79 -0
- package/src/execution/__tests__/MessageThread.test.tsx +324 -0
- package/src/execution/__tests__/PlanArtifactCard.test.tsx +37 -16
- package/src/execution/__tests__/ResultView.test.tsx +280 -2
- package/src/execution/__tests__/SubAgentSection.test.tsx +180 -0
- package/src/execution/__tests__/TerminalSession.test.tsx +72 -0
- package/src/execution/__tests__/TodoCard.test.tsx +102 -0
- package/src/execution/__tests__/ToolArgsView.test.tsx +48 -0
- package/src/execution/__tests__/ToolCallDetail.test.tsx +195 -0
- package/src/execution/__tests__/ToolCallGroup.test.tsx +132 -0
- package/src/execution/__tests__/ToolCallItem.test.tsx +521 -0
- package/src/execution/__tests__/ToolRunGroup.test.tsx +137 -0
- package/src/execution/__tests__/blanked-narration.test.tsx +149 -0
- package/src/execution/__tests__/buildThreadItems-approvals.test.ts +304 -0
- package/src/execution/__tests__/buildThreadItems-file-review.test.ts +331 -0
- package/src/execution/__tests__/file-path-resolver.test.ts +44 -0
- package/src/execution/__tests__/file-review-status.test.ts +343 -0
- package/src/execution/__tests__/segment-tool-calls.test.ts +124 -0
- package/src/execution/__tests__/subagent-file-review-badge.test.tsx +273 -0
- package/src/execution/__tests__/thread-keys.test.ts +195 -0
- package/src/execution/__tests__/tool-presenter.test.tsx +95 -0
- package/src/execution/__tests__/useArtifactDownload.test.tsx +67 -0
- package/src/execution/__tests__/useArtifactDownloadUrl.test.tsx +51 -0
- package/src/execution/__tests__/useFileChangeContent.test.tsx +240 -0
- package/src/execution/__tests__/useFileReview.test.tsx +236 -0
- package/src/execution/__tests__/useSubmitApproval.test.tsx +165 -0
- package/src/execution/__tests__/useToolOutputContent.test.tsx +82 -0
- package/src/execution/approval-provenance.ts +8 -0
- package/src/execution/artifact-utils.ts +0 -18
- package/src/execution/execution-phases.ts +4 -16
- package/src/execution/file-path-resolver.ts +38 -0
- package/src/execution/file-review-status.ts +219 -0
- package/src/execution/index.ts +84 -4
- package/src/execution/segment-tool-calls.ts +101 -0
- package/src/execution/tool-categories.ts +128 -1
- package/src/execution/tool-presenter.ts +58 -3
- package/src/execution/tool-rendering-primitives.tsx +11 -14
- package/src/execution/useArtifactDownload.ts +109 -0
- package/src/execution/useArtifactDownloadUrl.ts +116 -0
- package/src/execution/useFileChangeContent.ts +149 -0
- package/src/execution/useFileReview.ts +197 -0
- package/src/execution/useSubmitApproval.ts +49 -27
- package/src/execution/useToolOutputContent.ts +70 -0
- package/src/index.ts +60 -1
- package/src/internal/ApprovalPeekBar.tsx +73 -0
- package/src/internal/BoundedContent.tsx +97 -0
- package/src/internal/DecisionButton.tsx +127 -0
- package/src/internal/InCardDecisionError.tsx +47 -0
- package/src/internal/RevealToggle.tsx +91 -0
- package/src/internal/VirtualizedThread.tsx +28 -2
- package/src/internal/__tests__/ApprovalPeekBar.test.tsx +34 -0
- package/src/internal/__tests__/BoundedContent.test.tsx +92 -0
- package/src/internal/__tests__/DecisionButton.test.tsx +107 -0
- package/src/internal/__tests__/RevealToggle.test.tsx +61 -0
- package/src/internal/__tests__/useAutoDisclosure.test.ts +74 -0
- package/src/internal/__tests__/useElapsedSince.test.tsx +68 -0
- package/src/internal/__tests__/useIsOverflowing.test.ts +36 -0
- package/src/internal/__tests__/useKeyedSubmission.test.tsx +126 -0
- package/src/internal/useAutoDisclosure.ts +58 -0
- package/src/internal/useElapsedSince.ts +72 -0
- package/src/internal/useIsOverflowing.ts +61 -0
- package/src/internal/useIsTextTruncated.ts +55 -0
- package/src/internal/useKeyedSubmission.ts +101 -0
- package/src/session/SessionViewer.tsx +21 -4
- package/src/session/__tests__/SessionViewer-fileReviewDock.test.tsx +198 -0
- package/src/session/__tests__/audienceWiring.test.tsx +4 -0
- package/src/session/__tests__/useSessionFileChanges.test.ts +303 -0
- package/src/session/index.ts +3 -0
- package/src/session/inspector/ChangesTab.tsx +20 -17
- package/src/session/inspector/SessionInspector.tsx +12 -10
- package/src/session/inspector/__tests__/ArtifactsTab.test.tsx +0 -1
- package/src/session/inspector/__tests__/ChangesTab.test.tsx +38 -0
- package/src/session/inspector/__tests__/useSessionInspector.test.ts +46 -61
- package/src/session/inspector/index.ts +0 -3
- package/src/session/inspector/useSessionInspector.ts +27 -20
- package/src/session/useSessionConversation.ts +80 -3
- package/src/session/useSessionFileChanges.ts +207 -0
- package/src/styles.css +35 -10
- package/src/version-history/DiffViewer.tsx +10 -7
- package/src/version-history/UnifiedDiffView.tsx +66 -0
- package/src/version-history/__tests__/DiffViewer.test.tsx +57 -0
- package/src/version-history/__tests__/UnifiedDiffView.test.tsx +50 -0
- package/src/version-history/__tests__/computeDiff.test.ts +35 -0
- package/src/version-history/__tests__/parseUnifiedDiff.test.ts +82 -0
- package/src/version-history/computeDiff.ts +6 -37
- package/src/version-history/diff-hunks.ts +63 -0
- package/src/version-history/index.ts +1 -0
- package/src/version-history/parseUnifiedDiff.ts +34 -0
- package/src/workflow/WorkflowExecutionApprovalCard.tsx +49 -24
- package/src/workflow/WorkflowExecutionTimeline.tsx +15 -4
- package/src/workflow/WorkflowExecutionTimelineEvent.tsx +8 -3
- package/src/workflow/WorkflowExecutionViewer.tsx +72 -28
- package/src/workflow/WorkflowFileReviewList.tsx +152 -0
- package/src/workflow/WorkflowTaskApprovalCard.tsx +37 -85
- package/src/workflow/__tests__/ExecutionInspector.test.tsx +30 -2
- package/src/workflow/__tests__/WorkflowExecutionApprovalCard.test.tsx +129 -0
- package/src/workflow/__tests__/WorkflowFileReviewList.test.tsx +129 -0
- package/src/workflow/__tests__/WorkflowTaskApprovalCard.test.tsx +43 -11
- package/src/workflow/__tests__/useWorkflowExecutionActions.test.tsx +276 -0
- package/src/workflow/execution-inspector/ExecutionInspector.tsx +42 -18
- package/src/workflow/index.ts +5 -0
- package/src/workflow/useWorkflowExecutionActions.ts +177 -29
- package/styles.css +1 -1
- package/version-history/DiffViewer.d.ts.map +1 -1
- package/version-history/DiffViewer.js +9 -3
- package/version-history/DiffViewer.js.map +1 -1
- package/version-history/UnifiedDiffView.d.ts +29 -0
- package/version-history/UnifiedDiffView.d.ts.map +1 -0
- package/version-history/UnifiedDiffView.js +36 -0
- package/version-history/UnifiedDiffView.js.map +1 -0
- package/version-history/computeDiff.d.ts +3 -1
- package/version-history/computeDiff.d.ts.map +1 -1
- package/version-history/computeDiff.js +5 -35
- package/version-history/computeDiff.js.map +1 -1
- package/version-history/diff-hunks.d.ts +21 -0
- package/version-history/diff-hunks.d.ts.map +1 -0
- package/version-history/diff-hunks.js +58 -0
- package/version-history/diff-hunks.js.map +1 -0
- package/version-history/index.d.ts +1 -0
- package/version-history/index.d.ts.map +1 -1
- package/version-history/index.js +1 -0
- package/version-history/index.js.map +1 -1
- package/version-history/parseUnifiedDiff.d.ts +22 -0
- package/version-history/parseUnifiedDiff.d.ts.map +1 -0
- package/version-history/parseUnifiedDiff.js +34 -0
- package/version-history/parseUnifiedDiff.js.map +1 -0
- package/workflow/WorkflowExecutionApprovalCard.d.ts +8 -1
- package/workflow/WorkflowExecutionApprovalCard.d.ts.map +1 -1
- package/workflow/WorkflowExecutionApprovalCard.js +17 -3
- package/workflow/WorkflowExecutionApprovalCard.js.map +1 -1
- package/workflow/WorkflowExecutionTimeline.d.ts +11 -2
- package/workflow/WorkflowExecutionTimeline.d.ts.map +1 -1
- package/workflow/WorkflowExecutionTimeline.js +2 -2
- package/workflow/WorkflowExecutionTimeline.js.map +1 -1
- package/workflow/WorkflowExecutionTimelineEvent.d.ts +4 -1
- package/workflow/WorkflowExecutionTimelineEvent.d.ts.map +1 -1
- package/workflow/WorkflowExecutionTimelineEvent.js +2 -2
- package/workflow/WorkflowExecutionTimelineEvent.js.map +1 -1
- package/workflow/WorkflowExecutionViewer.d.ts.map +1 -1
- package/workflow/WorkflowExecutionViewer.js +18 -10
- package/workflow/WorkflowExecutionViewer.js.map +1 -1
- package/workflow/WorkflowFileReviewList.d.ts +46 -0
- package/workflow/WorkflowFileReviewList.d.ts.map +1 -0
- package/workflow/WorkflowFileReviewList.js +48 -0
- package/workflow/WorkflowFileReviewList.js.map +1 -0
- package/workflow/WorkflowTaskApprovalCard.d.ts +8 -1
- package/workflow/WorkflowTaskApprovalCard.d.ts.map +1 -1
- package/workflow/WorkflowTaskApprovalCard.js +12 -19
- package/workflow/WorkflowTaskApprovalCard.js.map +1 -1
- package/workflow/execution-inspector/ExecutionInspector.d.ts +21 -4
- package/workflow/execution-inspector/ExecutionInspector.d.ts.map +1 -1
- package/workflow/execution-inspector/ExecutionInspector.js +6 -3
- package/workflow/execution-inspector/ExecutionInspector.js.map +1 -1
- package/workflow/index.d.ts +1 -0
- package/workflow/index.d.ts.map +1 -1
- package/workflow/index.js +1 -0
- package/workflow/index.js.map +1 -1
- package/workflow/useWorkflowExecutionActions.d.ts +62 -4
- package/workflow/useWorkflowExecutionActions.d.ts.map +1 -1
- package/workflow/useWorkflowExecutionActions.js +94 -18
- package/workflow/useWorkflowExecutionActions.js.map +1 -1
- package/session/inspector/PlanTab.d.ts +0 -15
- package/session/inspector/PlanTab.d.ts.map +0 -1
- package/session/inspector/PlanTab.js +0 -19
- package/session/inspector/PlanTab.js.map +0 -1
- package/src/session/inspector/PlanTab.tsx +0 -31
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { createContext, useCallback, useContext } from "react";
|
|
2
|
+
import type { PendingApproval } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/approval_pb";
|
|
3
|
+
import type { ApprovalAction } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Context value that carries the run's pending approvals to the tool rows
|
|
7
|
+
* that own them, so an approval gate can render *inline* on the exact tool
|
|
8
|
+
* call it blocks (parent thread and nested sub-agents alike) instead of as a
|
|
9
|
+
* detached card at the bottom of the thread.
|
|
10
|
+
*
|
|
11
|
+
* Provided by {@link MessageThread} (or a platform builder's custom wrapper),
|
|
12
|
+
* beside {@link FilePathContext} / `SandboxContext`. When no provider is
|
|
13
|
+
* present — or the consumer did not supply an `onApprovalSubmit` handler — the
|
|
14
|
+
* map is empty and {@link useApproval} returns `null`, so the inline UI is a
|
|
15
|
+
* no-op (backward compatible).
|
|
16
|
+
*
|
|
17
|
+
* The map is keyed by `tool_call_id` and rebuilt only when the underlying
|
|
18
|
+
* `pendingApprovals` list changes (not per streaming frame), so subscribing
|
|
19
|
+
* tool rows re-render on approval events, not on every snapshot — preserving
|
|
20
|
+
* the thread's streaming re-render isolation (DD-009 / DD-010).
|
|
21
|
+
*/
|
|
22
|
+
export interface ApprovalContextValue {
|
|
23
|
+
/** Unresolved approvals, keyed by the `tool_call_id` they gate. */
|
|
24
|
+
readonly approvalsByToolCallId: ReadonlyMap<string, PendingApproval>;
|
|
25
|
+
/**
|
|
26
|
+
* Submits a decision for a gated tool call. Wired to the same handler the
|
|
27
|
+
* bottom {@link ApprovalCard} backstop uses; when absent, no inline approval
|
|
28
|
+
* UI is shown.
|
|
29
|
+
*/
|
|
30
|
+
readonly onSubmit?: (
|
|
31
|
+
toolCallId: string,
|
|
32
|
+
action: ApprovalAction,
|
|
33
|
+
comment?: string,
|
|
34
|
+
) => void;
|
|
35
|
+
/** Tool-call ids whose decision RPC is in flight, for per-card spinners. */
|
|
36
|
+
readonly submittingIds: ReadonlySet<string>;
|
|
37
|
+
/**
|
|
38
|
+
* Per-tool-call failures, keyed by `tool_call_id` (the parallel of
|
|
39
|
+
* {@link submittingIds}). A failed decision is surfaced in-card, beside the
|
|
40
|
+
* exact gate that failed, so the inline row can name *which* action did not
|
|
41
|
+
* take and why. Empty when no provider supplies it.
|
|
42
|
+
*/
|
|
43
|
+
readonly errorsByToolCallId: ReadonlyMap<string, Error>;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const DEFAULT_VALUE: ApprovalContextValue = {
|
|
47
|
+
approvalsByToolCallId: new Map(),
|
|
48
|
+
submittingIds: new Set(),
|
|
49
|
+
errorsByToolCallId: new Map(),
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
/** Context that supplies a run's pending approvals to the tool rows they gate. */
|
|
53
|
+
export const ApprovalContext =
|
|
54
|
+
createContext<ApprovalContextValue>(DEFAULT_VALUE);
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Resolved inline-approval state for a single tool call. Its shape is a superset
|
|
58
|
+
* of the props {@link ApprovalCardBody} reads, so a consumer can spread it
|
|
59
|
+
* directly: `<ApprovalCardBody {...approval} />`.
|
|
60
|
+
*/
|
|
61
|
+
export interface UseApprovalResult {
|
|
62
|
+
/** The pending approval gating this tool call. */
|
|
63
|
+
readonly pendingApproval: PendingApproval;
|
|
64
|
+
/** Submits a decision for this tool call (id already bound). */
|
|
65
|
+
readonly onSubmit: (action: ApprovalAction, comment?: string) => void;
|
|
66
|
+
/** True while this tool call's decision RPC is in flight. */
|
|
67
|
+
readonly isSubmitting: boolean;
|
|
68
|
+
/** This gate's last failed decision, or `null` — surfaced in-card beside it. */
|
|
69
|
+
readonly error: Error | null;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Returns the inline-approval state for a tool call, or `null` when the call
|
|
74
|
+
* is not currently gated (no matching approval, or no submit handler wired).
|
|
75
|
+
*
|
|
76
|
+
* The returned `onSubmit` is pre-bound to `toolCallId` and stable across
|
|
77
|
+
* renders, so it can flow straight into a memoized approval body.
|
|
78
|
+
*
|
|
79
|
+
* @example
|
|
80
|
+
* ```tsx
|
|
81
|
+
* const approval = useApproval(toolCall.id);
|
|
82
|
+
* if (approval) {
|
|
83
|
+
* return <ApprovalCardBody {...approval} />;
|
|
84
|
+
* }
|
|
85
|
+
* ```
|
|
86
|
+
*/
|
|
87
|
+
export function useApproval(toolCallId: string): UseApprovalResult | null {
|
|
88
|
+
const { approvalsByToolCallId, onSubmit, submittingIds, errorsByToolCallId } =
|
|
89
|
+
useContext(ApprovalContext);
|
|
90
|
+
|
|
91
|
+
const handleSubmit = useCallback(
|
|
92
|
+
(action: ApprovalAction, comment?: string) => {
|
|
93
|
+
onSubmit?.(toolCallId, action, comment);
|
|
94
|
+
},
|
|
95
|
+
[onSubmit, toolCallId],
|
|
96
|
+
);
|
|
97
|
+
|
|
98
|
+
const pendingApproval = toolCallId
|
|
99
|
+
? approvalsByToolCallId.get(toolCallId)
|
|
100
|
+
: undefined;
|
|
101
|
+
if (!pendingApproval || !onSubmit) return null;
|
|
102
|
+
|
|
103
|
+
return {
|
|
104
|
+
pendingApproval,
|
|
105
|
+
onSubmit: handleSubmit,
|
|
106
|
+
isSubmitting: submittingIds.has(toolCallId),
|
|
107
|
+
error: errorsByToolCallId.get(toolCallId) ?? null,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
@@ -4,6 +4,7 @@ import type { ExecutionArtifact } from "@stigmer/protos/ai/stigmer/agentic/agent
|
|
|
4
4
|
import { ExecutionArtifactKind } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
|
|
5
5
|
import { cn } from "@stigmer/theme";
|
|
6
6
|
import { useArtifactContent } from "./useArtifactContent";
|
|
7
|
+
import { useArtifactDownload } from "./useArtifactDownload";
|
|
7
8
|
import { isTextArtifact, formatArtifactSize } from "./artifact-utils";
|
|
8
9
|
import { useDetectStigmerResource } from "../library/useDetectStigmerResource";
|
|
9
10
|
import { useDetectSkillPackage } from "../library/useDetectSkillPackage";
|
|
@@ -148,6 +149,8 @@ export function ArtifactCard({
|
|
|
148
149
|
|
|
149
150
|
const canPreview = !!onPreview && (isTextArtifact(artifact) || isDirectory);
|
|
150
151
|
|
|
152
|
+
const { download, isDownloading } = useArtifactDownload(executionId);
|
|
153
|
+
|
|
151
154
|
const parentDir =
|
|
152
155
|
hasNameCollision && artifact.sandboxPath
|
|
153
156
|
? parentDirectory(artifact.sandboxPath)
|
|
@@ -211,17 +214,18 @@ export function ArtifactCard({
|
|
|
211
214
|
Preview
|
|
212
215
|
</button>
|
|
213
216
|
)}
|
|
214
|
-
<
|
|
215
|
-
|
|
216
|
-
download
|
|
217
|
+
<button
|
|
218
|
+
type="button"
|
|
219
|
+
onClick={() => download(artifact.storageKey, artifact.name)}
|
|
220
|
+
disabled={isDownloading}
|
|
217
221
|
className={cn(
|
|
218
|
-
"inline-flex items-center gap-1 text-xs font-medium text-muted-foreground transition-colors hover:text-foreground",
|
|
222
|
+
"inline-flex items-center gap-1 text-xs font-medium text-muted-foreground transition-colors hover:text-foreground disabled:opacity-50",
|
|
219
223
|
FOCUS_RING_CLASSES,
|
|
220
224
|
)}
|
|
221
225
|
>
|
|
222
226
|
<DownloadIcon />
|
|
223
|
-
{isDirectory ? "Download ZIP" : "Download"}
|
|
224
|
-
</
|
|
227
|
+
{isDownloading ? "Preparing…" : isDirectory ? "Download ZIP" : "Download"}
|
|
228
|
+
</button>
|
|
225
229
|
</div>
|
|
226
230
|
|
|
227
231
|
</div>
|
|
@@ -5,6 +5,7 @@ import type { ExecutionArtifact } from "@stigmer/protos/ai/stigmer/agentic/agent
|
|
|
5
5
|
import { ExecutionArtifactKind } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
|
|
6
6
|
import { cn } from "@stigmer/theme";
|
|
7
7
|
import { useArtifactContent } from "./useArtifactContent";
|
|
8
|
+
import { useArtifactDownload } from "./useArtifactDownload";
|
|
8
9
|
import { isTextArtifact, formatArtifactSize } from "./artifact-utils";
|
|
9
10
|
import { ArtifactContentRenderer } from "./ArtifactContentRenderer";
|
|
10
11
|
import { useDetectStigmerResource } from "../library/useDetectStigmerResource";
|
|
@@ -269,6 +270,7 @@ export function ArtifactPreviewContent({
|
|
|
269
270
|
|
|
270
271
|
<ActionBar
|
|
271
272
|
artifact={artifact}
|
|
273
|
+
executionId={executionId}
|
|
272
274
|
isDirectory={isDirectory}
|
|
273
275
|
hasContent={content !== null}
|
|
274
276
|
copied={copied}
|
|
@@ -622,6 +624,7 @@ function EntryIcon({ name }: { readonly name: string }) {
|
|
|
622
624
|
|
|
623
625
|
function ActionBar({
|
|
624
626
|
artifact,
|
|
627
|
+
executionId,
|
|
625
628
|
isDirectory,
|
|
626
629
|
hasContent,
|
|
627
630
|
copied,
|
|
@@ -636,6 +639,7 @@ function ActionBar({
|
|
|
636
639
|
onImplement,
|
|
637
640
|
}: {
|
|
638
641
|
readonly artifact: ExecutionArtifact;
|
|
642
|
+
readonly executionId: string;
|
|
639
643
|
readonly isDirectory: boolean;
|
|
640
644
|
readonly hasContent: boolean;
|
|
641
645
|
readonly copied: boolean;
|
|
@@ -649,6 +653,7 @@ function ActionBar({
|
|
|
649
653
|
readonly onApply: () => void;
|
|
650
654
|
readonly onImplement?: () => void;
|
|
651
655
|
}) {
|
|
656
|
+
const { download, isDownloading } = useArtifactDownload(executionId);
|
|
652
657
|
return (
|
|
653
658
|
<div className="flex items-center justify-between gap-3 border-t border-border px-4 py-3">
|
|
654
659
|
<div className="flex items-center gap-3">
|
|
@@ -670,17 +675,18 @@ function ActionBar({
|
|
|
670
675
|
</button>
|
|
671
676
|
)}
|
|
672
677
|
|
|
673
|
-
<
|
|
674
|
-
|
|
675
|
-
download
|
|
678
|
+
<button
|
|
679
|
+
type="button"
|
|
680
|
+
onClick={() => download(artifact.storageKey, artifact.name)}
|
|
681
|
+
disabled={isDownloading}
|
|
676
682
|
className={cn(
|
|
677
|
-
"inline-flex items-center gap-1.5 text-xs font-medium text-muted-foreground transition-colors hover:text-foreground",
|
|
683
|
+
"inline-flex items-center gap-1.5 text-xs font-medium text-muted-foreground transition-colors hover:text-foreground disabled:opacity-50",
|
|
678
684
|
FOCUS_RING_CLASSES,
|
|
679
685
|
)}
|
|
680
686
|
>
|
|
681
687
|
<DownloadIcon />
|
|
682
|
-
{isDirectory ? "Download ZIP" : "Download"}
|
|
683
|
-
</
|
|
688
|
+
{isDownloading ? "Preparing…" : isDirectory ? "Download ZIP" : "Download"}
|
|
689
|
+
</button>
|
|
684
690
|
</div>
|
|
685
691
|
|
|
686
692
|
<div className="flex shrink-0 items-center gap-3">
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { cn } from "@stigmer/theme";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Which empty file-change state to describe.
|
|
7
|
+
*
|
|
8
|
+
* - `"empty-create"` — a new file proven to have no content (the approval gate's
|
|
9
|
+
* proto `FileChange` carries `changeType === CREATE` with an empty body).
|
|
10
|
+
* - `"create"` — a new file IS being written, but its proposed content could not
|
|
11
|
+
* be captured for preview (the rare content-less FILE_WRITE gate, e.g. a
|
|
12
|
+
* no-stream denial synthesis). Distinguished from `empty-create` because the
|
|
13
|
+
* file is not proven empty — only proven to be a write/create.
|
|
14
|
+
* - `"no-preview"` — the change carries no renderable diff/content AND cannot be
|
|
15
|
+
* proven to be a create (e.g. a path-only resume placeholder for an edit).
|
|
16
|
+
* Never claims emptiness or creation it cannot prove.
|
|
17
|
+
*/
|
|
18
|
+
export type EmptyChangeKind = "empty-create" | "create" | "no-preview";
|
|
19
|
+
|
|
20
|
+
const MESSAGE: Record<EmptyChangeKind, string> = {
|
|
21
|
+
"empty-create": "New empty file",
|
|
22
|
+
create: "New file — preview unavailable",
|
|
23
|
+
"no-preview": "No preview available for this change",
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
/** Props for {@link EmptyChangeNotice}. */
|
|
27
|
+
export interface EmptyChangeNoticeProps {
|
|
28
|
+
/** The empty-change state to describe. */
|
|
29
|
+
readonly kind: EmptyChangeKind;
|
|
30
|
+
/** Additional CSS class names for the root container. */
|
|
31
|
+
readonly className?: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* A quiet, neutral notice rendered in place of a diff when a file change has
|
|
36
|
+
* nothing to show — a newly created empty file, or a change whose content is
|
|
37
|
+
* genuinely unavailable.
|
|
38
|
+
*
|
|
39
|
+
* It is the single owner of both messages so the approval gate and the
|
|
40
|
+
* post-execution detail can never drift apart in wording. The two cases are
|
|
41
|
+
* deliberately distinct: an honest "new empty file" requires proof (the gate's
|
|
42
|
+
* proto change type), while everything else degrades to the non-committal
|
|
43
|
+
* "no preview available" rather than misrepresenting an uncaptured change as
|
|
44
|
+
* empty.
|
|
45
|
+
*
|
|
46
|
+
* Styled to match the `Notice` used elsewhere in the change views (neutral
|
|
47
|
+
* border + muted surface, tokens only — DD-005).
|
|
48
|
+
*/
|
|
49
|
+
export function EmptyChangeNotice({ kind, className }: EmptyChangeNoticeProps) {
|
|
50
|
+
return (
|
|
51
|
+
<div
|
|
52
|
+
role="status"
|
|
53
|
+
data-cursor-target="empty-change-notice"
|
|
54
|
+
className={cn(
|
|
55
|
+
"rounded-md border border-border bg-muted-subtle px-3 py-2 text-xs text-muted-foreground",
|
|
56
|
+
className,
|
|
57
|
+
)}
|
|
58
|
+
>
|
|
59
|
+
{MESSAGE[kind]}
|
|
60
|
+
</div>
|
|
61
|
+
);
|
|
62
|
+
}
|
|
@@ -0,0 +1,383 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useMemo, useState } from "react";
|
|
4
|
+
import type { FileChange } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/message_pb";
|
|
5
|
+
import {
|
|
6
|
+
FileChangeCaptureLevel,
|
|
7
|
+
FileChangeType,
|
|
8
|
+
} from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
|
|
9
|
+
import { cn } from "@stigmer/theme";
|
|
10
|
+
import { BoundedContent } from "../internal/BoundedContent";
|
|
11
|
+
import { computeDiff } from "../version-history/computeDiff";
|
|
12
|
+
import { DiffViewer } from "../version-history/DiffViewer";
|
|
13
|
+
import { DiffFileList } from "../version-history/DiffFileList";
|
|
14
|
+
import { DiffSummary } from "../version-history/DiffSummary";
|
|
15
|
+
import { UnifiedDiffView } from "../version-history/UnifiedDiffView";
|
|
16
|
+
import type { DiffHunk, FileDiffEntry } from "../version-history/types";
|
|
17
|
+
import { FilePathLink } from "./FilePathLink";
|
|
18
|
+
import { EmptyChangeNotice } from "./EmptyChangeNotice";
|
|
19
|
+
import { useFileChangeContent } from "./useFileChangeContent";
|
|
20
|
+
|
|
21
|
+
/** Props for {@link FileChangesView}. */
|
|
22
|
+
export interface FileChangesViewProps {
|
|
23
|
+
/**
|
|
24
|
+
* Net file changes to render, typically from {@link useSessionFileChanges}.
|
|
25
|
+
* One entry per changed file.
|
|
26
|
+
*/
|
|
27
|
+
readonly changes: readonly FileChange[];
|
|
28
|
+
/** Additional CSS classes for the root container. */
|
|
29
|
+
readonly className?: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Consolidated, GitHub-style view of the files an agent changed: a summary
|
|
34
|
+
* bar, a file selector (when more than one file changed), and the unified diff
|
|
35
|
+
* for the selected file.
|
|
36
|
+
*
|
|
37
|
+
* Renders the runner's authoritative {@link FileChange} captures directly
|
|
38
|
+
* (proto-first). Whole-file captures (native) become a true before/after diff;
|
|
39
|
+
* hunk-only captures (Cursor today) render their unified diff; offloaded bodies
|
|
40
|
+
* are fetched lazily only when their file is opened (see {@link FileChangeDiff}).
|
|
41
|
+
*
|
|
42
|
+
* Zero Console dependencies — safe for platform builder embedding.
|
|
43
|
+
*
|
|
44
|
+
* @example
|
|
45
|
+
* ```tsx
|
|
46
|
+
* const { fileChanges } = useSessionFileChanges(allExecutions);
|
|
47
|
+
* <FileChangesView changes={fileChanges} />
|
|
48
|
+
* ```
|
|
49
|
+
*/
|
|
50
|
+
export function FileChangesView({ changes, className }: FileChangesViewProps) {
|
|
51
|
+
const entries = useMemo<readonly FileDiffEntry[]>(
|
|
52
|
+
() => changes.map(toFileDiffEntry),
|
|
53
|
+
[changes],
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
const [selectedPath, setSelectedPath] = useState<string | null>(
|
|
57
|
+
changes[0]?.path ?? null,
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
const selected =
|
|
61
|
+
changes.find((c) => c.path === selectedPath) ?? changes[0] ?? null;
|
|
62
|
+
|
|
63
|
+
const totals = useMemo(
|
|
64
|
+
() =>
|
|
65
|
+
entries.reduce(
|
|
66
|
+
(acc, e) => ({
|
|
67
|
+
additions: acc.additions + e.additions,
|
|
68
|
+
deletions: acc.deletions + e.deletions,
|
|
69
|
+
}),
|
|
70
|
+
{ additions: 0, deletions: 0 },
|
|
71
|
+
),
|
|
72
|
+
[entries],
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
if (changes.length === 0 || !selected) return null;
|
|
76
|
+
|
|
77
|
+
return (
|
|
78
|
+
<div className={cn("flex flex-col gap-3", className)}>
|
|
79
|
+
<DiffSummary
|
|
80
|
+
fileCount={changes.length}
|
|
81
|
+
additions={totals.additions}
|
|
82
|
+
deletions={totals.deletions}
|
|
83
|
+
/>
|
|
84
|
+
|
|
85
|
+
{changes.length > 1 && (
|
|
86
|
+
<div className="overflow-hidden rounded-lg border border-border">
|
|
87
|
+
<DiffFileList
|
|
88
|
+
files={entries}
|
|
89
|
+
selectedPath={selected.path}
|
|
90
|
+
onSelect={setSelectedPath}
|
|
91
|
+
/>
|
|
92
|
+
</div>
|
|
93
|
+
)}
|
|
94
|
+
|
|
95
|
+
<FileChangeDiff key={selected.path} change={selected} />
|
|
96
|
+
</div>
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** Props for {@link FileChangeDiff}. */
|
|
101
|
+
export interface FileChangeDiffProps {
|
|
102
|
+
/** The single file change to render. */
|
|
103
|
+
readonly change: FileChange;
|
|
104
|
+
/** Additional CSS classes for the root container. */
|
|
105
|
+
readonly className?: string;
|
|
106
|
+
/**
|
|
107
|
+
* Optional class applied to the scrolling diff body (the `DiffViewer` table or
|
|
108
|
+
* unified-diff `<pre>`). A raw height cap with no reveal control; prefer
|
|
109
|
+
* {@link bounded} for the consistent clamp-and-reveal used across tool cards.
|
|
110
|
+
*/
|
|
111
|
+
readonly bodyClassName?: string;
|
|
112
|
+
/**
|
|
113
|
+
* When `true`, the diff body is wrapped in the shared {@link BoundedContent}
|
|
114
|
+
* primitive: clamped to the standard preview height with a bottom fade and an
|
|
115
|
+
* in-place "Show more" / "Show less", identical to the always-visible
|
|
116
|
+
* edit/write diff body in the timeline. Defaults to `false` so the dedicated
|
|
117
|
+
* Changes view ({@link FileChangesView}) renders the full diff; the approval
|
|
118
|
+
* gate opts in so a large change cannot push the decision buttons off-screen
|
|
119
|
+
* while still letting the reviewer expand the whole diff in place.
|
|
120
|
+
*/
|
|
121
|
+
readonly bounded?: boolean;
|
|
122
|
+
/**
|
|
123
|
+
* Whether to render the filename header (the `FilePathLink` + any rename
|
|
124
|
+
* prefix). Defaults to `true`. Set to `false` where an ancestor already names
|
|
125
|
+
* the file — the approval gate header, or a tool-call row — so the diff is not
|
|
126
|
+
* captioned with a path the user just read; the `+N -M` stats still render.
|
|
127
|
+
*/
|
|
128
|
+
readonly showFileName?: boolean;
|
|
129
|
+
/**
|
|
130
|
+
* Whether to render the `+N -M` summary. Defaults to `true`. Set to `false`
|
|
131
|
+
* where an ancestor row already shows the result summary (a tool-call row) so
|
|
132
|
+
* the body does not repeat the counts. Orthogonal to {@link showFileName}: the
|
|
133
|
+
* approval gate suppresses the name but keeps the stats.
|
|
134
|
+
*/
|
|
135
|
+
readonly showStats?: boolean;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Renders the diff for a single {@link FileChange}, adapting to how completely
|
|
140
|
+
* the runner captured it:
|
|
141
|
+
*
|
|
142
|
+
* - **Whole-file** (native): a true before/after diff via {@link DiffViewer}.
|
|
143
|
+
* Offloaded bodies are fetched lazily; loading, error, and too-large
|
|
144
|
+
* (server-truncated) states are surfaced honestly.
|
|
145
|
+
* - **Hunk-only** (Cursor today): the captured unified diff, labeled as such.
|
|
146
|
+
* - **Binary**: a "binary file changed" notice instead of a text diff.
|
|
147
|
+
*
|
|
148
|
+
* Importable on its own by platform builders composing custom change UIs.
|
|
149
|
+
*/
|
|
150
|
+
export function FileChangeDiff({
|
|
151
|
+
change,
|
|
152
|
+
className,
|
|
153
|
+
bodyClassName,
|
|
154
|
+
bounded = false,
|
|
155
|
+
showFileName = true,
|
|
156
|
+
showStats = true,
|
|
157
|
+
}: FileChangeDiffProps) {
|
|
158
|
+
const { beforeText, afterText, isBinary, isLoading, error, isTruncated, downloadUrl } =
|
|
159
|
+
useFileChangeContent(change);
|
|
160
|
+
|
|
161
|
+
const hunks = useMemo<readonly DiffHunk[]>(() => {
|
|
162
|
+
if (beforeText === null || afterText === null) return [];
|
|
163
|
+
return computeDiff(beforeText, afterText);
|
|
164
|
+
}, [beforeText, afterText]);
|
|
165
|
+
|
|
166
|
+
const stats = useMemo(() => {
|
|
167
|
+
if (change.captureLevel === FileChangeCaptureLevel.HUNK_ONLY) {
|
|
168
|
+
return { additions: change.linesAdded, deletions: change.linesRemoved };
|
|
169
|
+
}
|
|
170
|
+
return countHunks(hunks);
|
|
171
|
+
}, [change.captureLevel, change.linesAdded, change.linesRemoved, hunks]);
|
|
172
|
+
|
|
173
|
+
const showStatsRow = showStats && (stats.additions > 0 || stats.deletions > 0);
|
|
174
|
+
|
|
175
|
+
return (
|
|
176
|
+
<div
|
|
177
|
+
className={cn("flex flex-col gap-1.5", className)}
|
|
178
|
+
data-cursor-target="file-diff"
|
|
179
|
+
>
|
|
180
|
+
{(showFileName || showStatsRow) && (
|
|
181
|
+
<div className="flex items-center gap-2 text-xs text-muted-foreground">
|
|
182
|
+
{showFileName &&
|
|
183
|
+
change.changeType === FileChangeType.RENAME &&
|
|
184
|
+
change.renameFrom && (
|
|
185
|
+
<span className="min-w-0 truncate font-mono text-muted-foreground-faint">
|
|
186
|
+
{change.renameFrom} →
|
|
187
|
+
</span>
|
|
188
|
+
)}
|
|
189
|
+
{showFileName && (
|
|
190
|
+
<FilePathLink path={change.path} dirDisplay="dim" className="text-xs" />
|
|
191
|
+
)}
|
|
192
|
+
{showStatsRow && (
|
|
193
|
+
<span className="shrink-0 tabular-nums">
|
|
194
|
+
<span className="text-diff-added-fg">+{stats.additions}</span>{" "}
|
|
195
|
+
<span className="text-diff-removed-fg">-{stats.deletions}</span>
|
|
196
|
+
</span>
|
|
197
|
+
)}
|
|
198
|
+
</div>
|
|
199
|
+
)}
|
|
200
|
+
|
|
201
|
+
<FileChangeBody
|
|
202
|
+
change={change}
|
|
203
|
+
hunks={hunks}
|
|
204
|
+
isBinary={isBinary}
|
|
205
|
+
isLoading={isLoading}
|
|
206
|
+
error={error}
|
|
207
|
+
isTruncated={isTruncated}
|
|
208
|
+
downloadUrl={downloadUrl}
|
|
209
|
+
bodyClassName={bodyClassName}
|
|
210
|
+
bounded={bounded}
|
|
211
|
+
/>
|
|
212
|
+
</div>
|
|
213
|
+
);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// ---------------------------------------------------------------------------
|
|
217
|
+
// Body — the capture-level / fetch-state switch
|
|
218
|
+
// ---------------------------------------------------------------------------
|
|
219
|
+
|
|
220
|
+
function FileChangeBody({
|
|
221
|
+
change,
|
|
222
|
+
hunks,
|
|
223
|
+
isBinary,
|
|
224
|
+
isLoading,
|
|
225
|
+
error,
|
|
226
|
+
isTruncated,
|
|
227
|
+
downloadUrl,
|
|
228
|
+
bodyClassName,
|
|
229
|
+
bounded,
|
|
230
|
+
}: {
|
|
231
|
+
change: FileChange;
|
|
232
|
+
hunks: readonly DiffHunk[];
|
|
233
|
+
isBinary: boolean;
|
|
234
|
+
isLoading: boolean;
|
|
235
|
+
error: Error | null;
|
|
236
|
+
isTruncated: boolean;
|
|
237
|
+
downloadUrl: string | null;
|
|
238
|
+
bodyClassName?: string;
|
|
239
|
+
bounded: boolean;
|
|
240
|
+
}) {
|
|
241
|
+
// A CREATE with no renderable diff is a genuinely empty new file; anything
|
|
242
|
+
// else with no content degrades to the non-committal "no preview" (we never
|
|
243
|
+
// claim emptiness we cannot prove from the capture).
|
|
244
|
+
const emptyKind =
|
|
245
|
+
change.changeType === FileChangeType.CREATE ? "empty-create" : "no-preview";
|
|
246
|
+
|
|
247
|
+
// When bounded, the actual diff renderers (and only those — short notices need
|
|
248
|
+
// no clamp) are wrapped in the shared BoundedContent budget, so the gate's
|
|
249
|
+
// diff matches the timeline edit/write diff and never crowds out the buttons.
|
|
250
|
+
const bound = (node: React.ReactNode) =>
|
|
251
|
+
bounded ? (
|
|
252
|
+
<BoundedContent cursorTarget="file-diff-expand">{node}</BoundedContent>
|
|
253
|
+
) : (
|
|
254
|
+
node
|
|
255
|
+
);
|
|
256
|
+
|
|
257
|
+
if (isBinary) {
|
|
258
|
+
return <Notice>Binary file changed.</Notice>;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
if (change.captureLevel === FileChangeCaptureLevel.HUNK_ONLY) {
|
|
262
|
+
return change.unifiedDiff ? (
|
|
263
|
+
bound(<UnifiedDiffView patch={change.unifiedDiff} className={bodyClassName} />)
|
|
264
|
+
) : (
|
|
265
|
+
<EmptyChangeNotice kind={emptyKind} className={bodyClassName} />
|
|
266
|
+
);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// Whole-file: content may be inline (ready) or offloaded (fetched lazily).
|
|
270
|
+
if (isTruncated) {
|
|
271
|
+
return (
|
|
272
|
+
<Notice>
|
|
273
|
+
This file is too large to diff inline.{" "}
|
|
274
|
+
{downloadUrl && (
|
|
275
|
+
<a
|
|
276
|
+
href={downloadUrl}
|
|
277
|
+
target="_blank"
|
|
278
|
+
rel="noreferrer"
|
|
279
|
+
className="font-medium text-primary underline-offset-2 hover:underline focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
|
280
|
+
>
|
|
281
|
+
Download the full file
|
|
282
|
+
</a>
|
|
283
|
+
)}
|
|
284
|
+
</Notice>
|
|
285
|
+
);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
if (isLoading) {
|
|
289
|
+
return <Notice>Loading diff…</Notice>;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
if (error) {
|
|
293
|
+
return <Notice variant="error">Could not load this file's contents.</Notice>;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
if (hunks.length === 0) {
|
|
297
|
+
return <EmptyChangeNotice kind={emptyKind} className={bodyClassName} />;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
return bound(<DiffViewer hunks={hunks} className={bodyClassName} />);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
function Notice({
|
|
304
|
+
children,
|
|
305
|
+
variant = "muted",
|
|
306
|
+
}: {
|
|
307
|
+
children: React.ReactNode;
|
|
308
|
+
variant?: "muted" | "error";
|
|
309
|
+
}) {
|
|
310
|
+
return (
|
|
311
|
+
<div
|
|
312
|
+
role="status"
|
|
313
|
+
className={cn(
|
|
314
|
+
"rounded-md border border-border bg-muted-subtle px-3 py-2 text-xs",
|
|
315
|
+
variant === "error" ? "text-destructive" : "text-muted-foreground",
|
|
316
|
+
)}
|
|
317
|
+
>
|
|
318
|
+
{children}
|
|
319
|
+
</div>
|
|
320
|
+
);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// ---------------------------------------------------------------------------
|
|
324
|
+
// Helpers
|
|
325
|
+
// ---------------------------------------------------------------------------
|
|
326
|
+
|
|
327
|
+
function countHunks(hunks: readonly DiffHunk[]): {
|
|
328
|
+
additions: number;
|
|
329
|
+
deletions: number;
|
|
330
|
+
} {
|
|
331
|
+
let additions = 0;
|
|
332
|
+
let deletions = 0;
|
|
333
|
+
for (const hunk of hunks) {
|
|
334
|
+
for (const line of hunk.lines) {
|
|
335
|
+
if (line.type === "added") additions++;
|
|
336
|
+
else if (line.type === "removed") deletions++;
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
return { additions, deletions };
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
/**
|
|
343
|
+
* Projects a {@link FileChange} into a {@link FileDiffEntry} for the file list.
|
|
344
|
+
*
|
|
345
|
+
* Counts: hunk-only uses the runner's authoritative numbers; whole-file inline
|
|
346
|
+
* is derived from the content; an offloaded whole-file side has no synchronous
|
|
347
|
+
* content, so its count stays 0 until the file is opened and fetched.
|
|
348
|
+
*/
|
|
349
|
+
function toFileDiffEntry(change: FileChange): FileDiffEntry {
|
|
350
|
+
const changeType =
|
|
351
|
+
change.changeType === FileChangeType.CREATE
|
|
352
|
+
? "added"
|
|
353
|
+
: change.changeType === FileChangeType.DELETE
|
|
354
|
+
? "removed"
|
|
355
|
+
: "modified";
|
|
356
|
+
|
|
357
|
+
if (change.captureLevel === FileChangeCaptureLevel.HUNK_ONLY) {
|
|
358
|
+
return {
|
|
359
|
+
path: change.path,
|
|
360
|
+
changeType,
|
|
361
|
+
additions: change.linesAdded,
|
|
362
|
+
deletions: change.linesRemoved,
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
const before = inlineSide(change.before);
|
|
367
|
+
const after = inlineSide(change.after);
|
|
368
|
+
if (before !== null && after !== null) {
|
|
369
|
+
const { additions, deletions } = countHunks(computeDiff(before, after));
|
|
370
|
+
return { path: change.path, changeType, additions, deletions };
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
return { path: change.path, changeType, additions: 0, deletions: 0 };
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
/** Inline text of a side, or null when absent/offloaded/binary. */
|
|
377
|
+
function inlineSide(
|
|
378
|
+
side: FileChange["before"],
|
|
379
|
+
): string | null {
|
|
380
|
+
if (!side) return "";
|
|
381
|
+
if (side.isBinary) return null;
|
|
382
|
+
return side.body.case === "inline" ? side.body.value : null;
|
|
383
|
+
}
|