@stigmer/react 3.1.3 → 3.1.5

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 (248) hide show
  1. package/composer/SessionComposer.d.ts +31 -0
  2. package/composer/SessionComposer.d.ts.map +1 -1
  3. package/composer/SessionComposer.js +12 -4
  4. package/composer/SessionComposer.js.map +1 -1
  5. package/execution/ArtifactApplyButton.d.ts +25 -0
  6. package/execution/ArtifactApplyButton.d.ts.map +1 -0
  7. package/execution/ArtifactApplyButton.js +21 -0
  8. package/execution/ArtifactApplyButton.js.map +1 -0
  9. package/execution/ArtifactContentBody.d.ts +36 -0
  10. package/execution/ArtifactContentBody.d.ts.map +1 -0
  11. package/execution/ArtifactContentBody.js +62 -0
  12. package/execution/ArtifactContentBody.js.map +1 -0
  13. package/execution/ArtifactDocument.d.ts +48 -0
  14. package/execution/ArtifactDocument.d.ts.map +1 -0
  15. package/execution/ArtifactDocument.js +89 -0
  16. package/execution/ArtifactDocument.js.map +1 -0
  17. package/execution/ArtifactPreviewModal.d.ts +4 -4
  18. package/execution/ArtifactPreviewModal.d.ts.map +1 -1
  19. package/execution/ArtifactPreviewModal.js +15 -142
  20. package/execution/ArtifactPreviewModal.js.map +1 -1
  21. package/execution/ArtifactRow.d.ts +49 -0
  22. package/execution/ArtifactRow.d.ts.map +1 -0
  23. package/execution/ArtifactRow.js +56 -0
  24. package/execution/ArtifactRow.js.map +1 -0
  25. package/execution/ArtifactsWidget.d.ts +6 -9
  26. package/execution/ArtifactsWidget.d.ts.map +1 -1
  27. package/execution/ArtifactsWidget.js +15 -19
  28. package/execution/ArtifactsWidget.js.map +1 -1
  29. package/execution/FileReviewContext.d.ts +15 -1
  30. package/execution/FileReviewContext.d.ts.map +1 -1
  31. package/execution/FileReviewContext.js +20 -1
  32. package/execution/FileReviewContext.js.map +1 -1
  33. package/execution/MessageThread.d.ts +65 -2
  34. package/execution/MessageThread.d.ts.map +1 -1
  35. package/execution/MessageThread.js +42 -23
  36. package/execution/MessageThread.js.map +1 -1
  37. package/execution/TodoCard.d.ts +8 -0
  38. package/execution/TodoCard.d.ts.map +1 -1
  39. package/execution/TodoCard.js +6 -4
  40. package/execution/TodoCard.js.map +1 -1
  41. package/execution/TodoList.d.ts +20 -1
  42. package/execution/TodoList.d.ts.map +1 -1
  43. package/execution/TodoList.js +9 -6
  44. package/execution/TodoList.js.map +1 -1
  45. package/execution/ToolCallDetail.d.ts +3 -2
  46. package/execution/ToolCallDetail.d.ts.map +1 -1
  47. package/execution/ToolCallDetail.js +49 -16
  48. package/execution/ToolCallDetail.js.map +1 -1
  49. package/execution/file-review-status.d.ts +15 -0
  50. package/execution/file-review-status.d.ts.map +1 -1
  51. package/execution/file-review-status.js +22 -0
  52. package/execution/file-review-status.js.map +1 -1
  53. package/execution/index.d.ts +13 -7
  54. package/execution/index.d.ts.map +1 -1
  55. package/execution/index.js +7 -4
  56. package/execution/index.js.map +1 -1
  57. package/execution/useArtifactContent.d.ts +6 -1
  58. package/execution/useArtifactContent.d.ts.map +1 -1
  59. package/execution/useArtifactContent.js +7 -2
  60. package/execution/useArtifactContent.js.map +1 -1
  61. package/execution/useArtifactInspection.d.ts +85 -0
  62. package/execution/useArtifactInspection.d.ts.map +1 -0
  63. package/execution/useArtifactInspection.js +143 -0
  64. package/execution/useArtifactInspection.js.map +1 -0
  65. package/execution/useCreateAgentExecution.d.ts +11 -0
  66. package/execution/useCreateAgentExecution.d.ts.map +1 -1
  67. package/execution/useCreateAgentExecution.js +1 -0
  68. package/execution/useCreateAgentExecution.js.map +1 -1
  69. package/github/useGitHubFileReader.d.ts +4 -2
  70. package/github/useGitHubFileReader.d.ts.map +1 -1
  71. package/github/useGitHubFileReader.js +16 -3
  72. package/github/useGitHubFileReader.js.map +1 -1
  73. package/github/useGitHubTreeLister.d.ts +2 -1
  74. package/github/useGitHubTreeLister.d.ts.map +1 -1
  75. package/github/useGitHubTreeLister.js +7 -3
  76. package/github/useGitHubTreeLister.js.map +1 -1
  77. package/index.d.ts +4 -4
  78. package/index.d.ts.map +1 -1
  79. package/index.js +3 -3
  80. package/index.js.map +1 -1
  81. package/internal/MermaidDiagram.d.ts +47 -0
  82. package/internal/MermaidDiagram.d.ts.map +1 -0
  83. package/internal/MermaidDiagram.js +118 -0
  84. package/internal/MermaidDiagram.js.map +1 -0
  85. package/internal/VirtualizedThread.d.ts +3 -2
  86. package/internal/VirtualizedThread.d.ts.map +1 -1
  87. package/internal/VirtualizedThread.js +3 -2
  88. package/internal/VirtualizedThread.js.map +1 -1
  89. package/internal/markdown-components.d.ts.map +1 -1
  90. package/internal/markdown-components.js +34 -0
  91. package/internal/markdown-components.js.map +1 -1
  92. package/internal/mermaid-loader.d.ts +26 -0
  93. package/internal/mermaid-loader.d.ts.map +1 -0
  94. package/internal/mermaid-loader.js +34 -0
  95. package/internal/mermaid-loader.js.map +1 -0
  96. package/internal/mermaid-theme-css.d.ts +41 -0
  97. package/internal/mermaid-theme-css.d.ts.map +1 -0
  98. package/internal/mermaid-theme-css.js +179 -0
  99. package/internal/mermaid-theme-css.js.map +1 -0
  100. package/internal/slug.d.ts +9 -5
  101. package/internal/slug.d.ts.map +1 -1
  102. package/internal/slug.js +10 -6
  103. package/internal/slug.js.map +1 -1
  104. package/package.json +5 -4
  105. package/session/SessionViewer.d.ts +11 -1
  106. package/session/SessionViewer.d.ts.map +1 -1
  107. package/session/SessionViewer.js +101 -21
  108. package/session/SessionViewer.js.map +1 -1
  109. package/session/artifact-document.d.ts +13 -0
  110. package/session/artifact-document.d.ts.map +1 -0
  111. package/session/artifact-document.js +22 -0
  112. package/session/artifact-document.js.map +1 -0
  113. package/session/facets/ArtifactsTab.d.ts +30 -4
  114. package/session/facets/ArtifactsTab.d.ts.map +1 -1
  115. package/session/facets/ArtifactsTab.js +32 -10
  116. package/session/facets/ArtifactsTab.js.map +1 -1
  117. package/session/index.d.ts +3 -1
  118. package/session/index.d.ts.map +1 -1
  119. package/session/index.js +5 -1
  120. package/session/index.js.map +1 -1
  121. package/session/useSessionArtifacts.d.ts +17 -0
  122. package/session/useSessionArtifacts.d.ts.map +1 -1
  123. package/session/useSessionArtifacts.js +16 -5
  124. package/session/useSessionArtifacts.js.map +1 -1
  125. package/session/useSessionConversation.d.ts +17 -1
  126. package/session/useSessionConversation.d.ts.map +1 -1
  127. package/session/useSessionConversation.js +32 -4
  128. package/session/useSessionConversation.js.map +1 -1
  129. package/session/useSessionPageFlow.d.ts.map +1 -1
  130. package/session/useSessionPageFlow.js +1 -0
  131. package/session/useSessionPageFlow.js.map +1 -1
  132. package/session/useSessionPanel.d.ts +20 -0
  133. package/session/useSessionPanel.d.ts.map +1 -1
  134. package/session/useSessionPanel.js +13 -0
  135. package/session/useSessionPanel.js.map +1 -1
  136. package/session/useSessionRailViews.d.ts +13 -1
  137. package/session/useSessionRailViews.d.ts.map +1 -1
  138. package/session/useSessionRailViews.js +5 -3
  139. package/session/useSessionRailViews.js.map +1 -1
  140. package/session/useWorkspaceReadRefs.d.ts +45 -0
  141. package/session/useWorkspaceReadRefs.d.ts.map +1 -0
  142. package/session/useWorkspaceReadRefs.js +104 -0
  143. package/session/useWorkspaceReadRefs.js.map +1 -0
  144. package/src/composer/SessionComposer.tsx +69 -1
  145. package/src/composer/__tests__/SessionComposer-editing.test.tsx +128 -0
  146. package/src/execution/ArtifactApplyButton.tsx +79 -0
  147. package/src/execution/ArtifactContentBody.tsx +255 -0
  148. package/src/execution/ArtifactDocument.tsx +344 -0
  149. package/src/execution/ArtifactPreviewModal.tsx +39 -405
  150. package/src/execution/ArtifactRow.tsx +165 -0
  151. package/src/execution/ArtifactsWidget.tsx +24 -33
  152. package/src/execution/FileReviewContext.ts +27 -1
  153. package/src/execution/MessageThread.tsx +110 -22
  154. package/src/execution/TodoCard.tsx +20 -4
  155. package/src/execution/TodoList.tsx +26 -4
  156. package/src/execution/ToolCallDetail.tsx +69 -16
  157. package/src/execution/__tests__/ArtifactContentBody.test.tsx +131 -0
  158. package/src/execution/__tests__/ArtifactDocument.test.tsx +100 -0
  159. package/src/execution/__tests__/ArtifactRow.test.tsx +139 -0
  160. package/src/execution/__tests__/ArtifactsWidget.test.tsx +78 -0
  161. package/src/execution/__tests__/ToolCallDetail.test.tsx +188 -2
  162. package/src/execution/__tests__/file-review-status.test.ts +47 -0
  163. package/src/execution/__tests__/message-entry.test.tsx +50 -2
  164. package/src/execution/__tests__/message-thread-slots.test.tsx +463 -0
  165. package/src/execution/__tests__/useArtifactInspection.test.tsx +151 -0
  166. package/src/execution/__tests__/useCreateAgentExecution.test.tsx +35 -0
  167. package/src/execution/file-review-status.ts +24 -0
  168. package/src/execution/index.ts +27 -5
  169. package/src/execution/useArtifactContent.ts +7 -0
  170. package/src/execution/useArtifactInspection.ts +252 -0
  171. package/src/execution/useCreateAgentExecution.ts +12 -0
  172. package/src/github/__tests__/useGitHubFileReader.test.ts +35 -2
  173. package/src/github/__tests__/useGitHubTreeLister.test.ts +14 -0
  174. package/src/github/useGitHubFileReader.ts +19 -6
  175. package/src/github/useGitHubTreeLister.ts +7 -3
  176. package/src/index.ts +15 -2
  177. package/src/internal/MermaidDiagram.tsx +164 -0
  178. package/src/internal/VirtualizedThread.tsx +5 -1
  179. package/src/internal/__tests__/a11y/mermaid-theme.a11y.test.tsx +122 -0
  180. package/src/internal/__tests__/markdown-components.test.tsx +93 -1
  181. package/src/internal/__tests__/mermaid-diagram.test.tsx +282 -0
  182. package/src/internal/markdown-components.tsx +36 -1
  183. package/src/internal/mermaid-loader.ts +44 -0
  184. package/src/internal/mermaid-theme-css.ts +178 -0
  185. package/src/internal/slug.ts +10 -6
  186. package/src/session/SessionViewer.tsx +204 -35
  187. package/src/session/__tests__/SessionViewer-artifactTabs.test.tsx +210 -0
  188. package/src/session/__tests__/SessionViewer-editResubmit.test.tsx +233 -0
  189. package/src/session/__tests__/useSessionArtifacts.test.ts +57 -0
  190. package/src/session/__tests__/useSessionConversation.test.tsx +172 -1
  191. package/src/session/__tests__/useSessionPanel.test.ts +70 -1
  192. package/src/session/__tests__/useWorkspaceReadRefs.test.ts +245 -0
  193. package/src/session/artifact-document.ts +23 -0
  194. package/src/session/facets/ArtifactsTab.tsx +66 -18
  195. package/src/session/facets/__tests__/ArtifactsTab.test.tsx +111 -47
  196. package/src/session/index.ts +7 -1
  197. package/src/session/useSessionArtifacts.ts +16 -5
  198. package/src/session/useSessionConversation.ts +48 -6
  199. package/src/session/useSessionPageFlow.ts +1 -0
  200. package/src/session/useSessionPanel.ts +41 -0
  201. package/src/session/useSessionRailViews.tsx +21 -1
  202. package/src/session/useWorkspaceReadRefs.ts +118 -0
  203. package/src/workspace/FileViewer.tsx +144 -38
  204. package/src/workspace/WorkspaceFileReader.ts +21 -0
  205. package/src/workspace/__tests__/FileViewer.test.tsx +138 -3
  206. package/src/workspace/__tests__/useWorkspaceFileContent.test.tsx +20 -0
  207. package/src/workspace/__tests__/workspaceListingCache.test.ts +114 -0
  208. package/src/workspace/index.ts +4 -1
  209. package/src/workspace/useWorkspaceEntries.ts +11 -0
  210. package/src/workspace/useWorkspaceFileContent.ts +4 -1
  211. package/src/workspace/useWorkspaceFileSearch.ts +8 -3
  212. package/src/workspace/useWorkspaceFiles.ts +3 -2
  213. package/src/workspace/workspaceListingCache.ts +21 -7
  214. package/styles.css +1 -1
  215. package/workspace/FileViewer.d.ts +1 -1
  216. package/workspace/FileViewer.d.ts.map +1 -1
  217. package/workspace/FileViewer.js +55 -5
  218. package/workspace/FileViewer.js.map +1 -1
  219. package/workspace/WorkspaceFileReader.d.ts +17 -0
  220. package/workspace/WorkspaceFileReader.d.ts.map +1 -1
  221. package/workspace/WorkspaceFileReader.js +18 -0
  222. package/workspace/WorkspaceFileReader.js.map +1 -1
  223. package/workspace/index.d.ts +1 -1
  224. package/workspace/index.d.ts.map +1 -1
  225. package/workspace/index.js +1 -1
  226. package/workspace/index.js.map +1 -1
  227. package/workspace/useWorkspaceEntries.d.ts +11 -0
  228. package/workspace/useWorkspaceEntries.d.ts.map +1 -1
  229. package/workspace/useWorkspaceEntries.js.map +1 -1
  230. package/workspace/useWorkspaceFileContent.d.ts.map +1 -1
  231. package/workspace/useWorkspaceFileContent.js +4 -1
  232. package/workspace/useWorkspaceFileContent.js.map +1 -1
  233. package/workspace/useWorkspaceFileSearch.d.ts.map +1 -1
  234. package/workspace/useWorkspaceFileSearch.js +8 -3
  235. package/workspace/useWorkspaceFileSearch.js.map +1 -1
  236. package/workspace/useWorkspaceFiles.d.ts +2 -1
  237. package/workspace/useWorkspaceFiles.d.ts.map +1 -1
  238. package/workspace/useWorkspaceFiles.js +3 -2
  239. package/workspace/useWorkspaceFiles.js.map +1 -1
  240. package/workspace/workspaceListingCache.d.ts +1 -1
  241. package/workspace/workspaceListingCache.d.ts.map +1 -1
  242. package/workspace/workspaceListingCache.js +20 -7
  243. package/workspace/workspaceListingCache.js.map +1 -1
  244. package/execution/ArtifactCard.d.ts +0 -81
  245. package/execution/ArtifactCard.d.ts.map +0 -1
  246. package/execution/ArtifactCard.js +0 -130
  247. package/execution/ArtifactCard.js.map +0 -1
  248. package/src/execution/ArtifactCard.tsx +0 -320
@@ -169,7 +169,11 @@ export { MessageEntry } from "./MessageEntry.js";
169
169
  export type { MessageEntryProps } from "./MessageEntry.js";
170
170
 
171
171
  export { MessageThread } from "./MessageThread.js";
172
- export type { MessageThreadProps, ThreadContentColumn } from "./MessageThread.js";
172
+ export type {
173
+ MessageThreadProps,
174
+ MessageThreadSlots,
175
+ ThreadContentColumn,
176
+ } from "./MessageThread.js";
173
177
 
174
178
  export { ThreadSkeleton } from "./ThreadSkeleton.js";
175
179
  export type { ThreadSkeletonProps } from "./ThreadSkeleton.js";
@@ -182,11 +186,12 @@ export type { ExecutionProgressProps } from "./ExecutionProgress.js";
182
186
 
183
187
  export {
184
188
  TodoList,
189
+ TodoRow,
185
190
  TodoInProgressIcon,
186
191
  findActiveTodo,
187
192
  todoCompletionSummary,
188
193
  } from "./TodoList.js";
189
- export type { TodoListProps } from "./TodoList.js";
194
+ export type { TodoListProps, TodoRowProps } from "./TodoList.js";
190
195
 
191
196
  export { TodoCard } from "./TodoCard.js";
192
197
  export type { TodoCardProps } from "./TodoCard.js";
@@ -204,6 +209,7 @@ export {
204
209
  deriveEffectiveVerdicts,
205
210
  changeForRowPath,
206
211
  fileReviewRowState,
212
+ fileReviewRowChange,
207
213
  } from "./file-review-status.js";
208
214
  export type {
209
215
  FileReviewability,
@@ -239,15 +245,31 @@ export type { FileChangeProgressBarProps } from "./FileChangeProgressBar.js";
239
245
  export { ApprovalContext, useApproval } from "./ApprovalContext.js";
240
246
  export type { ApprovalContextValue, UseApprovalResult } from "./ApprovalContext.js";
241
247
 
242
- export { FileReviewContext, useFileReviewRowState } from "./FileReviewContext.js";
248
+ export {
249
+ FileReviewContext,
250
+ useFileReviewRowState,
251
+ useFileReviewRowChange,
252
+ } from "./FileReviewContext.js";
243
253
  export type { FileReviewContextValue } from "./FileReviewContext.js";
244
254
 
245
- export { ArtifactCard } from "./ArtifactCard.js";
246
- export type { ArtifactCardProps } from "./ArtifactCard.js";
255
+ export { ArtifactRow } from "./ArtifactRow.js";
256
+ export type { ArtifactRowProps } from "./ArtifactRow.js";
247
257
 
248
258
  export { ArtifactContentRenderer } from "./ArtifactContentRenderer.js";
249
259
  export type { ArtifactContentRendererProps } from "./ArtifactContentRenderer.js";
250
260
 
261
+ export { ArtifactContentBody } from "./ArtifactContentBody.js";
262
+ export type { ArtifactContentBodyProps } from "./ArtifactContentBody.js";
263
+
264
+ export { ArtifactDocument } from "./ArtifactDocument.js";
265
+ export type { ArtifactDocumentProps } from "./ArtifactDocument.js";
266
+
267
+ export { useArtifactInspection } from "./useArtifactInspection.js";
268
+ export type {
269
+ ArtifactInspection,
270
+ UseArtifactInspectionOptions,
271
+ } from "./useArtifactInspection.js";
272
+
251
273
  export { ArtifactPreviewContent, ArtifactPreviewModal } from "./ArtifactPreviewModal.js";
252
274
  export type { ArtifactPreviewContentProps, ArtifactPreviewModalProps } from "./ArtifactPreviewModal.js";
253
275
 
@@ -100,6 +100,11 @@ export interface UseArtifactContentReturn {
100
100
  * When the same file is overwritten during execution, the `storageKey` stays
101
101
  * stable but `contentHash` changes, triggering a re-fetch so the UI never
102
102
  * shows stale content. Pass `undefined` or omit for backwards compatibility.
103
+ * @param cacheKey - Optional cross-mount cache key (DD-014). When set, a remount
104
+ * with the same key renders the previously-fetched content instantly and
105
+ * refetches in the background (no skeleton flash) — used by the artifact
106
+ * document so switching back to a recently-viewed tab is instant. Omit for the
107
+ * default fetch-on-mount behavior (card / modal / plan callers are unchanged).
103
108
  *
104
109
  * @example
105
110
  * ```tsx
@@ -142,6 +147,7 @@ export function useArtifactContent(
142
147
  storageKey: string | null,
143
148
  entryPath?: string | null,
144
149
  contentHash?: string,
150
+ cacheKey?: string,
145
151
  ): UseArtifactContentReturn {
146
152
  const stigmer = useStigmer();
147
153
 
@@ -164,6 +170,7 @@ export function useArtifactContent(
164
170
  : null,
165
171
  [executionId, storageKey, entryPath, contentHash, stigmer],
166
172
  EMPTY_ARTIFACT,
173
+ cacheKey ? { cacheKey } : undefined,
167
174
  );
168
175
 
169
176
  return {
@@ -0,0 +1,252 @@
1
+ "use client";
2
+
3
+ import { useCallback, useMemo, useState } from "react";
4
+ import type { ExecutionArtifact } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/artifact_pb";
5
+ import { ExecutionArtifactKind } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
6
+ import { useArtifactContent } from "./useArtifactContent.js";
7
+ import { isTextArtifact } from "./artifact-utils.js";
8
+ import { useDetectStigmerResource } from "../library/useDetectStigmerResource.js";
9
+ import { useDetectSkillPackage } from "../library/useDetectSkillPackage.js";
10
+ import type { SkillPackageDetection } from "../library/detect-skill-package.js";
11
+ import {
12
+ useApplyResource,
13
+ type ApplyResourceResult,
14
+ } from "../library/useApplyResource.js";
15
+
16
+ const COPIED_FEEDBACK_MS = 2000;
17
+
18
+ /** Options for {@link useArtifactInspection}. */
19
+ export interface UseArtifactInspectionOptions {
20
+ /**
21
+ * Cross-mount cache key for the content fetch (DD-014). Set it so a remount
22
+ * with the same key renders the previously-fetched content instantly and
23
+ * refetches in the background — the artifact document passes it so switching
24
+ * back to a recently-viewed tab is instant. Omit for fetch-on-mount (the
25
+ * modal, which resets on every open).
26
+ */
27
+ readonly cacheKey?: string;
28
+ /**
29
+ * Called after a resource is successfully applied or a skill package pushed.
30
+ * The consumer uses it for post-apply behavior (toast, navigate to Library).
31
+ */
32
+ readonly onApplied?: (result: ApplyResourceResult) => void;
33
+ }
34
+
35
+ /**
36
+ * Everything a chrome (the modal action bar, the document toolbar, the content
37
+ * body) needs to inspect and act on one artifact: fetched content, resource
38
+ * detection, the Apply/Push action, and clipboard copy.
39
+ */
40
+ export interface ArtifactInspection {
41
+ /** Decoded text content, or `null` (directory / binary / loading / error). */
42
+ readonly content: string | null;
43
+ /** Server-detected content type, or `null`. */
44
+ readonly contentType: string | null;
45
+ /** Whether the fetched content was truncated by the server's size cap. */
46
+ readonly isTruncated: boolean;
47
+ /** `true` while the content request is in-flight (first load). */
48
+ readonly isLoading: boolean;
49
+ /** Content-fetch error, or `null`. */
50
+ readonly error: Error | null;
51
+ /** Re-fetch the artifact content. */
52
+ readonly refetch: () => void;
53
+
54
+ /** Whether the artifact is a directory (skill package candidate). */
55
+ readonly isDirectory: boolean;
56
+ /** Skill-package detection for directory artifacts. */
57
+ readonly skillDetection: SkillPackageDetection;
58
+
59
+ /** `true` when the artifact is a recognized Stigmer resource or skill. */
60
+ readonly isDetected: boolean;
61
+ /** `true` while detection is still resolving (content / skill fetch). */
62
+ readonly isDetecting: boolean;
63
+ /** Detection badge label (`"Agent detected"`, `"Skill · N files"`), or `null`. */
64
+ readonly detectionLabel: string | null;
65
+
66
+ /** Apply/Push CTA label (`"Apply to org"`, `"Push Skill to org"`), or `null`. */
67
+ readonly ctaLabel: string | null;
68
+ /** Apply the detected resource / push the detected skill. */
69
+ readonly apply: () => Promise<void>;
70
+ /** `true` while an apply/push is in-flight. */
71
+ readonly isApplying: boolean;
72
+ /** The successful apply result, or `null`. */
73
+ readonly applyResult: ApplyResourceResult | null;
74
+ /** Apply/push error, or `null`. */
75
+ readonly applyError: Error | null;
76
+
77
+ /** Copy the in-memory content to the clipboard. No-op without content. */
78
+ readonly copy: () => void;
79
+ /** `true` for a short window after a successful copy. */
80
+ readonly copied: boolean;
81
+ }
82
+
83
+ /**
84
+ * Headless behavior hook consolidating the artifact inspect-and-act pipeline
85
+ * that was previously inlined per artifact surface: content fetch,
86
+ * Agent/McpServer YAML detection, skill-package detection, the Apply/Push
87
+ * mutation, and in-memory clipboard copy.
88
+ *
89
+ * It is the single writer of this pipeline's derived state, consumed by every
90
+ * artifact chrome — the modal action bar and the editor-area
91
+ * `ArtifactDocument` toolbar (DD-003 headless-first). Because there is exactly
92
+ * one place computing `detectionLabel` / `ctaLabel` / the apply flow, the two
93
+ * surfaces cannot drift.
94
+ *
95
+ * All four underlying hooks are called unconditionally with null-gated
96
+ * arguments (their documented skip contract), so this composition is
97
+ * React-rules-clean regardless of the artifact kind.
98
+ *
99
+ * @param artifact - The artifact to inspect.
100
+ * @param executionId - Execution that produced it (content + skill fetching).
101
+ * @param org - Organization for the "Apply to [org]" / "Push Skill to [org]" CTA.
102
+ * @param options - {@link UseArtifactInspectionOptions}.
103
+ */
104
+ export function useArtifactInspection(
105
+ artifact: ExecutionArtifact,
106
+ executionId: string,
107
+ org: string,
108
+ options?: UseArtifactInspectionOptions,
109
+ ): ArtifactInspection {
110
+ const isDirectory = artifact.kind === ExecutionArtifactKind.DIRECTORY;
111
+ const canFetchContent = !isDirectory && isTextArtifact(artifact);
112
+
113
+ const {
114
+ content,
115
+ contentType,
116
+ isTruncated,
117
+ isLoading,
118
+ error,
119
+ refetch,
120
+ } = useArtifactContent(
121
+ canFetchContent ? executionId : null,
122
+ canFetchContent ? artifact.storageKey : null,
123
+ undefined,
124
+ artifact.contentHash || undefined,
125
+ options?.cacheKey,
126
+ );
127
+
128
+ const yamlDetection = useDetectStigmerResource(
129
+ canFetchContent ? content : null,
130
+ );
131
+
132
+ const { detection: skillDetection, isLoading: isSkillLoading } =
133
+ useDetectSkillPackage(
134
+ isDirectory ? artifact : null,
135
+ isDirectory ? executionId : null,
136
+ );
137
+
138
+ const isDetected = yamlDetection.detected || skillDetection.detected;
139
+ const isDetecting =
140
+ (canFetchContent && isLoading) || (isDirectory && isSkillLoading);
141
+
142
+ let detectionLabel: string | null = null;
143
+ if (yamlDetection.detected) {
144
+ detectionLabel = `${yamlDetection.displayName} detected`;
145
+ } else if (skillDetection.detected) {
146
+ const count = skillDetection.fileCount;
147
+ detectionLabel = `Skill \u00B7 ${count} ${count === 1 ? "file" : "files"}`;
148
+ }
149
+
150
+ let ctaLabel: string | null = null;
151
+ if (yamlDetection.detected) {
152
+ ctaLabel = `Apply to ${org}`;
153
+ } else if (skillDetection.detected) {
154
+ ctaLabel = `Push Skill to ${org}`;
155
+ }
156
+
157
+ // --- Apply / Push ---------------------------------------------------------
158
+
159
+ const { applyYamlResource, pushSkillPackage, isApplying, error: applyError, clearError } =
160
+ useApplyResource();
161
+ const [applyResult, setApplyResult] = useState<ApplyResourceResult | null>(
162
+ null,
163
+ );
164
+ const onApplied = options?.onApplied;
165
+
166
+ const apply = useCallback(async () => {
167
+ clearError();
168
+ try {
169
+ let result: ApplyResourceResult;
170
+ if (yamlDetection.detected && content) {
171
+ result = await applyYamlResource(content, org);
172
+ } else if (skillDetection.detected) {
173
+ result = await pushSkillPackage({
174
+ org,
175
+ executionId,
176
+ storageKey: artifact.storageKey,
177
+ });
178
+ } else {
179
+ return;
180
+ }
181
+ setApplyResult(result);
182
+ onApplied?.(result);
183
+ } catch {
184
+ // error state is owned by useApplyResource
185
+ }
186
+ }, [
187
+ yamlDetection.detected,
188
+ skillDetection.detected,
189
+ content,
190
+ org,
191
+ executionId,
192
+ artifact.storageKey,
193
+ applyYamlResource,
194
+ pushSkillPackage,
195
+ clearError,
196
+ onApplied,
197
+ ]);
198
+
199
+ // --- Copy -----------------------------------------------------------------
200
+
201
+ const [copied, setCopied] = useState(false);
202
+ const copy = useCallback(() => {
203
+ if (!content) return;
204
+ void navigator.clipboard.writeText(content).then(() => {
205
+ setCopied(true);
206
+ setTimeout(() => setCopied(false), COPIED_FEEDBACK_MS);
207
+ });
208
+ }, [content]);
209
+
210
+ return useMemo(
211
+ () => ({
212
+ content,
213
+ contentType,
214
+ isTruncated,
215
+ isLoading,
216
+ error,
217
+ refetch,
218
+ isDirectory,
219
+ skillDetection,
220
+ isDetected,
221
+ isDetecting,
222
+ detectionLabel,
223
+ ctaLabel,
224
+ apply,
225
+ isApplying,
226
+ applyResult,
227
+ applyError,
228
+ copy,
229
+ copied,
230
+ }),
231
+ [
232
+ content,
233
+ contentType,
234
+ isTruncated,
235
+ isLoading,
236
+ error,
237
+ refetch,
238
+ isDirectory,
239
+ skillDetection,
240
+ isDetected,
241
+ isDetecting,
242
+ detectionLabel,
243
+ ctaLabel,
244
+ apply,
245
+ isApplying,
246
+ applyResult,
247
+ applyError,
248
+ copy,
249
+ copied,
250
+ ],
251
+ );
252
+ }
@@ -93,6 +93,17 @@ export interface CreateAgentExecutionInput {
93
93
  * Populated by the drag-to-reference feature in SessionComposer.
94
94
  */
95
95
  readonly workspaceFileRefs?: string[];
96
+ /**
97
+ * ID of the execution this one supersedes via edit-and-resubmit.
98
+ *
99
+ * Set when the user stopped an in-flight turn, edited its message, and
100
+ * resubmitted. Chat threads hide the superseded execution so the edited
101
+ * message replaces the original in place; history surfaces keep the full
102
+ * record. Maps to `AgentExecutionSpec.supersedes_execution_id`.
103
+ *
104
+ * Display-level only — the runner does not rewind model context.
105
+ */
106
+ readonly supersedesExecutionId?: string;
96
107
  }
97
108
 
98
109
  /** Resolved output of {@link UseCreateAgentExecutionReturn.create}. */
@@ -199,6 +210,7 @@ export function useCreateAgentExecution(): UseCreateAgentExecutionReturn {
199
210
  runtimeEnv: input.runtimeEnv,
200
211
  attachments: input.attachments,
201
212
  workspaceFileRefs: input.workspaceFileRefs,
213
+ supersedesExecutionId: input.supersedesExecutionId,
202
214
  });
203
215
 
204
216
  return {
@@ -1,6 +1,7 @@
1
1
  import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
2
2
  import { renderHook } from "@testing-library/react";
3
3
  import { useGitHubFileReader } from "../useGitHubFileReader";
4
+ import { WorkspaceFileNotFoundError } from "../../workspace/WorkspaceFileReader";
4
5
  import type { WorkspaceEntry } from "../../workspace/useWorkspaceEntries";
5
6
 
6
7
  function gitEntry(overrides?: Partial<WorkspaceEntry>): WorkspaceEntry {
@@ -120,11 +121,43 @@ describe("useGitHubFileReader", () => {
120
121
  );
121
122
  });
122
123
 
123
- it("throws on a non-OK response (404 = failed to load, not unsupported)", async () => {
124
+ it("throws the typed WorkspaceFileNotFoundError on a 404", async () => {
124
125
  vi.mocked(globalThis.fetch).mockResolvedValue(new Response("nope", { status: 404 }));
125
126
 
126
127
  const { result } = renderHook(() => useGitHubFileReader("ghp_abc"));
127
- await expect(result.current!(gitEntry(), "missing.ts")).rejects.toThrow("HTTP 404");
128
+ await expect(result.current!(gitEntry(), "missing.ts")).rejects.toThrow(
129
+ WorkspaceFileNotFoundError,
130
+ );
131
+ });
132
+
133
+ it("throws a generic error (not the typed not-found) on other non-OK responses", async () => {
134
+ vi.mocked(globalThis.fetch).mockResolvedValue(new Response("boom", { status: 500 }));
135
+
136
+ const { result } = renderHook(() => useGitHubFileReader("ghp_abc"));
137
+ const error = await result.current!(gitEntry(), "src/index.ts").then(
138
+ () => null,
139
+ (e: unknown) => e,
140
+ );
141
+ expect(error).toBeInstanceOf(Error);
142
+ expect((error as Error).message).toContain("HTTP 500");
143
+ expect(error).not.toBeInstanceOf(WorkspaceFileNotFoundError);
144
+ });
145
+
146
+ it("reads at readRef in preference to gitBranch when both are set", async () => {
147
+ vi.mocked(globalThis.fetch).mockResolvedValue(
148
+ jsonResponse({ encoding: "base64", size: 1, content: b64("x"), sha: "s1" }),
149
+ );
150
+
151
+ const { result } = renderHook(() => useGitHubFileReader("ghp_abc"));
152
+ await result.current!(
153
+ gitEntry({ gitBranch: "main", readRef: "abc123def456" }),
154
+ "notes.md",
155
+ );
156
+
157
+ expect(globalThis.fetch).toHaveBeenCalledWith(
158
+ expect.stringContaining("?ref=abc123def456"),
159
+ expect.anything(),
160
+ );
128
161
  });
129
162
 
130
163
  it("throws when the path is a directory (Contents API returns an array)", async () => {
@@ -148,6 +148,20 @@ describe("useGitHubTreeLister", () => {
148
148
  );
149
149
  });
150
150
 
151
+ it("lists at readRef in preference to gitBranch when both are set", async () => {
152
+ vi.mocked(globalThis.fetch).mockResolvedValue(
153
+ new Response(JSON.stringify(SAMPLE_TREE_RESPONSE), { status: 200 }),
154
+ );
155
+
156
+ const { result } = renderHook(() => useGitHubTreeLister("ghp_abc"));
157
+ await result.current!(gitEntry({ gitBranch: "main", readRef: "abc123def456" }));
158
+
159
+ expect(vi.mocked(globalThis.fetch)).toHaveBeenCalledWith(
160
+ expect.stringContaining("/git/trees/abc123def456?"),
161
+ expect.anything(),
162
+ );
163
+ });
164
+
151
165
  it("encodes branch names with special characters", async () => {
152
166
  vi.mocked(globalThis.fetch).mockResolvedValue(
153
167
  new Response(JSON.stringify(SAMPLE_TREE_RESPONSE), { status: 200 }),
@@ -2,9 +2,10 @@
2
2
 
3
3
  import { useCallback } from "react";
4
4
  import type { WorkspaceEntry } from "../workspace/useWorkspaceEntries.js";
5
- import type {
6
- WorkspaceFileContent,
7
- WorkspaceFileReader,
5
+ import {
6
+ WorkspaceFileNotFoundError,
7
+ type WorkspaceFileContent,
8
+ type WorkspaceFileReader,
8
9
  } from "../workspace/WorkspaceFileReader.js";
9
10
  import { base64ToBytes, normalizeGitHubContent } from "./decodeGitHubContent.js";
10
11
  import { parseGitUrl } from "./parseGitUrl.js";
@@ -58,7 +59,9 @@ function encodePath(path: string): string {
58
59
  * (non-git entry, missing/unparseable URL) — the "unsupported here" state.
59
60
  * - **Throws** on a real failure: a non-OK response, or a directory path
60
61
  * (which the Contents API returns as a JSON array). Consumers surface these
61
- * as an error, distinct from the null "unsupported" state.
62
+ * as an error, distinct from the null "unsupported" state. A 404 throws the
63
+ * typed {@link WorkspaceFileNotFoundError} so consumers can fall back to
64
+ * session-captured content for files not yet pushed to the ref.
62
65
  *
63
66
  * @example
64
67
  * ```tsx
@@ -80,14 +83,24 @@ export function useGitHubFileReader(
80
83
  const parsed = parseGitUrl(entry.gitUrl);
81
84
  if (!parsed) return null;
82
85
 
83
- const branch = entry.gitBranch || "main";
86
+ // readRef (the session's write-back commit SHA, when one exists) wins
87
+ // over the configured branch: agent-created files live on the pushed
88
+ // write-back commit, not the base branch. The Contents API accepts a
89
+ // branch name or a commit SHA interchangeably as `ref`.
90
+ const ref = entry.readRef || entry.gitBranch || "main";
84
91
  const url =
85
92
  `${GITHUB_API}/${parsed.owner}/${parsed.repo}/contents/${encodePath(path)}` +
86
- `?ref=${encodeURIComponent(branch)}`;
93
+ `?ref=${encodeURIComponent(ref)}`;
87
94
 
88
95
  const resp = await fetch(url, {
89
96
  headers: { Authorization: `Bearer ${token}` },
90
97
  });
98
+ // 404 is typed: a file that isn't at this ref (yet) is an expected,
99
+ // recoverable state — e.g. an agent-created file whose write-back
100
+ // hasn't pushed — that consumers handle differently from a failure.
101
+ if (resp.status === 404) {
102
+ throw new WorkspaceFileNotFoundError(path);
103
+ }
91
104
  if (!resp.ok) {
92
105
  throw new Error(
93
106
  `GitHub content fetch failed for ${path} (HTTP ${resp.status})`,
@@ -38,7 +38,8 @@ const TRUNCATION_MARKER: WorkspaceFileEntry = {
38
38
  * Creates a {@link WorkspaceFileLister} backed by the GitHub Trees API.
39
39
  *
40
40
  * Uses the existing client-side OAuth token (same one that powers
41
- * `GitHubRepoPicker`) to call `GET /repos/{owner}/{repo}/git/trees/{branch}?recursive=1`.
41
+ * `GitHubRepoPicker`) to call `GET /repos/{owner}/{repo}/git/trees/{ref}?recursive=1`,
42
+ * where the ref is the entry's `readRef` (write-back commit) or `gitBranch`.
42
43
  *
43
44
  * Returns `null` for:
44
45
  * - Non-git workspace entries
@@ -64,8 +65,11 @@ export function useGitHubTreeLister(
64
65
  const parsed = parseGitUrl(entry.gitUrl);
65
66
  if (!parsed) return null;
66
67
 
67
- const branch = entry.gitBranch || "main";
68
- const url = `${GITHUB_TREES_API}/${parsed.owner}/${parsed.repo}/git/trees/${encodeURIComponent(branch)}?recursive=1`;
68
+ // readRef (the session's write-back commit SHA, when one exists) wins
69
+ // over the configured branch, so the tree includes agent-created files.
70
+ // The Trees API accepts a branch name or a commit SHA interchangeably.
71
+ const ref = entry.readRef || entry.gitBranch || "main";
72
+ const url = `${GITHUB_TREES_API}/${parsed.owner}/${parsed.repo}/git/trees/${encodeURIComponent(ref)}?recursive=1`;
69
73
 
70
74
  const resp = await fetch(url, {
71
75
  headers: { Authorization: `Bearer ${token}` },
package/src/index.ts CHANGED
@@ -71,6 +71,7 @@ export {
71
71
  useWorkspaceContentSearch,
72
72
  useWorkspaceSources,
73
73
  MAX_WORKSPACE_FILE_READ_BYTES,
74
+ WorkspaceFileNotFoundError,
74
75
  WorkspaceEditor,
75
76
  WorkspaceFileSearch,
76
77
  WorkspaceContentSearch,
@@ -129,7 +130,9 @@ export {
129
130
  useSessionExecutions,
130
131
  useSessionConversation,
131
132
  useSessionArtifacts,
133
+ artifactKey,
132
134
  useSessionWriteBacks,
135
+ useWorkspaceReadRefs,
133
136
  useSessionFileChanges,
134
137
  useSessionUsage,
135
138
  useAgentRefFromSession,
@@ -154,6 +157,7 @@ export {
154
157
  PlanStreamingDocument,
155
158
  PLAN_DOCUMENT_ENTRY_ID,
156
159
  PLAN_DOCUMENT_PATH,
160
+ ARTIFACT_DOCUMENT_ENTRY_ID,
157
161
  SetupTab,
158
162
  } from "./session/index.js";
159
163
  export type {
@@ -231,6 +235,7 @@ export {
231
235
  deriveEffectiveVerdicts,
232
236
  changeForRowPath,
233
237
  fileReviewRowState,
238
+ fileReviewRowChange,
234
239
  foldFileReviewEventStream,
235
240
  displayFileChangeSets,
236
241
  toDisplayFileChange,
@@ -288,8 +293,12 @@ export {
288
293
  useApproval,
289
294
  FileReviewContext,
290
295
  useFileReviewRowState,
291
- ArtifactCard,
296
+ useFileReviewRowChange,
297
+ ArtifactRow,
292
298
  ArtifactContentRenderer,
299
+ ArtifactContentBody,
300
+ ArtifactDocument,
301
+ useArtifactInspection,
293
302
  ArtifactPreviewContent,
294
303
  ArtifactPreviewModal,
295
304
  ArtifactsWidget,
@@ -381,8 +390,12 @@ export type {
381
390
  ApprovalContextValue,
382
391
  UseApprovalResult,
383
392
  FileReviewContextValue,
384
- ArtifactCardProps,
393
+ ArtifactRowProps,
385
394
  ArtifactContentRendererProps,
395
+ ArtifactContentBodyProps,
396
+ ArtifactDocumentProps,
397
+ ArtifactInspection,
398
+ UseArtifactInspectionOptions,
386
399
  ArtifactRenderMode,
387
400
  ArtifactPreviewContentProps,
388
401
  ArtifactPreviewModalProps,