@stigmer/react 3.1.2 → 3.1.4

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 (285) hide show
  1. package/composer/SessionComposer.d.ts +37 -5
  2. package/composer/SessionComposer.d.ts.map +1 -1
  3. package/composer/SessionComposer.js +13 -5
  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 +9 -9
  18. package/execution/ArtifactPreviewModal.d.ts.map +1 -1
  19. package/execution/ArtifactPreviewModal.js +18 -145
  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/FileChangeProgressBar.d.ts +47 -0
  30. package/execution/FileChangeProgressBar.d.ts.map +1 -0
  31. package/execution/FileChangeProgressBar.js +49 -0
  32. package/execution/FileChangeProgressBar.js.map +1 -0
  33. package/execution/FileChangesView.d.ts.map +1 -1
  34. package/execution/FileChangesView.js +19 -4
  35. package/execution/FileChangesView.js.map +1 -1
  36. package/execution/FileReviewAtoms.d.ts +27 -0
  37. package/execution/FileReviewAtoms.d.ts.map +1 -0
  38. package/execution/FileReviewAtoms.js +64 -0
  39. package/execution/FileReviewAtoms.js.map +1 -0
  40. package/execution/FileReviewCard.d.ts +2 -2
  41. package/execution/FileReviewCard.d.ts.map +1 -1
  42. package/execution/FileReviewCard.js +32 -62
  43. package/execution/FileReviewCard.js.map +1 -1
  44. package/execution/MessageEntry.d.ts +0 -9
  45. package/execution/MessageEntry.d.ts.map +1 -1
  46. package/execution/MessageEntry.js +1 -11
  47. package/execution/MessageEntry.js.map +1 -1
  48. package/execution/MessageThread.d.ts +3 -4
  49. package/execution/MessageThread.d.ts.map +1 -1
  50. package/execution/MessageThread.js +25 -11
  51. package/execution/MessageThread.js.map +1 -1
  52. package/execution/PlanArtifactCard.d.ts +16 -10
  53. package/execution/PlanArtifactCard.d.ts.map +1 -1
  54. package/execution/PlanArtifactCard.js +31 -7
  55. package/execution/PlanArtifactCard.js.map +1 -1
  56. package/execution/PlanCompletionCard.d.ts +4 -4
  57. package/execution/PlanCompletionCard.d.ts.map +1 -1
  58. package/execution/PlanCompletionCard.js +4 -4
  59. package/execution/PlanCompletionCard.js.map +1 -1
  60. package/execution/PlanStreamingCard.d.ts +7 -2
  61. package/execution/PlanStreamingCard.d.ts.map +1 -1
  62. package/execution/PlanStreamingCard.js +11 -6
  63. package/execution/PlanStreamingCard.js.map +1 -1
  64. package/execution/index.d.ts +12 -2
  65. package/execution/index.d.ts.map +1 -1
  66. package/execution/index.js +6 -1
  67. package/execution/index.js.map +1 -1
  68. package/execution/use-build-from-plan-hotkey.d.ts +1 -1
  69. package/execution/use-build-from-plan-hotkey.js +1 -1
  70. package/execution/useArtifactContent.d.ts +6 -1
  71. package/execution/useArtifactContent.d.ts.map +1 -1
  72. package/execution/useArtifactContent.js +7 -2
  73. package/execution/useArtifactContent.js.map +1 -1
  74. package/execution/useArtifactCopy.d.ts +51 -0
  75. package/execution/useArtifactCopy.d.ts.map +1 -0
  76. package/execution/useArtifactCopy.js +75 -0
  77. package/execution/useArtifactCopy.js.map +1 -0
  78. package/execution/useArtifactDownload.d.ts.map +1 -1
  79. package/execution/useArtifactDownload.js +17 -5
  80. package/execution/useArtifactDownload.js.map +1 -1
  81. package/execution/useArtifactInspection.d.ts +85 -0
  82. package/execution/useArtifactInspection.d.ts.map +1 -0
  83. package/execution/useArtifactInspection.js +143 -0
  84. package/execution/useArtifactInspection.js.map +1 -0
  85. package/execution/useCreateAgentExecution.d.ts +16 -3
  86. package/execution/useCreateAgentExecution.d.ts.map +1 -1
  87. package/execution/useCreateAgentExecution.js +1 -0
  88. package/execution/useCreateAgentExecution.js.map +1 -1
  89. package/index.d.ts +4 -4
  90. package/index.d.ts.map +1 -1
  91. package/index.js +3 -3
  92. package/index.js.map +1 -1
  93. package/internal/MermaidDiagram.d.ts +47 -0
  94. package/internal/MermaidDiagram.d.ts.map +1 -0
  95. package/internal/MermaidDiagram.js +118 -0
  96. package/internal/MermaidDiagram.js.map +1 -0
  97. package/internal/file-tree/FileTreeNode.d.ts.map +1 -1
  98. package/internal/file-tree/FileTreeNode.js +2 -2
  99. package/internal/file-tree/FileTreeNode.js.map +1 -1
  100. package/internal/markdown-components.d.ts.map +1 -1
  101. package/internal/markdown-components.js +34 -0
  102. package/internal/markdown-components.js.map +1 -1
  103. package/internal/mermaid-loader.d.ts +26 -0
  104. package/internal/mermaid-loader.d.ts.map +1 -0
  105. package/internal/mermaid-loader.js +34 -0
  106. package/internal/mermaid-loader.js.map +1 -0
  107. package/internal/mermaid-theme-css.d.ts +41 -0
  108. package/internal/mermaid-theme-css.d.ts.map +1 -0
  109. package/internal/mermaid-theme-css.js +179 -0
  110. package/internal/mermaid-theme-css.js.map +1 -0
  111. package/internal/slug.d.ts +9 -5
  112. package/internal/slug.d.ts.map +1 -1
  113. package/internal/slug.js +10 -6
  114. package/internal/slug.js.map +1 -1
  115. package/library/detect-plan-artifact.d.ts +27 -9
  116. package/library/detect-plan-artifact.d.ts.map +1 -1
  117. package/library/detect-plan-artifact.js +26 -6
  118. package/library/detect-plan-artifact.js.map +1 -1
  119. package/library/index.d.ts +1 -1
  120. package/library/index.d.ts.map +1 -1
  121. package/library/index.js +1 -1
  122. package/library/index.js.map +1 -1
  123. package/package.json +5 -4
  124. package/session/PlanEditor.js +1 -1
  125. package/session/PlanEditor.js.map +1 -1
  126. package/session/SessionViewer.d.ts.map +1 -1
  127. package/session/SessionViewer.js +122 -30
  128. package/session/SessionViewer.js.map +1 -1
  129. package/session/artifact-document.d.ts +13 -0
  130. package/session/artifact-document.d.ts.map +1 -0
  131. package/session/artifact-document.js +22 -0
  132. package/session/artifact-document.js.map +1 -0
  133. package/session/facets/ArtifactsTab.d.ts +30 -4
  134. package/session/facets/ArtifactsTab.d.ts.map +1 -1
  135. package/session/facets/ArtifactsTab.js +32 -10
  136. package/session/facets/ArtifactsTab.js.map +1 -1
  137. package/session/index.d.ts +2 -1
  138. package/session/index.d.ts.map +1 -1
  139. package/session/index.js +4 -1
  140. package/session/index.js.map +1 -1
  141. package/session/plan-document.d.ts +14 -2
  142. package/session/plan-document.d.ts.map +1 -1
  143. package/session/plan-document.js +14 -3
  144. package/session/plan-document.js.map +1 -1
  145. package/session/usePlanDraft.d.ts +1 -1
  146. package/session/useSessionArtifacts.d.ts +17 -0
  147. package/session/useSessionArtifacts.d.ts.map +1 -1
  148. package/session/useSessionArtifacts.js +16 -5
  149. package/session/useSessionArtifacts.js.map +1 -1
  150. package/session/useSessionConversation.d.ts +26 -3
  151. package/session/useSessionConversation.d.ts.map +1 -1
  152. package/session/useSessionConversation.js +51 -6
  153. package/session/useSessionConversation.js.map +1 -1
  154. package/session/useSessionPageFlow.d.ts.map +1 -1
  155. package/session/useSessionPageFlow.js +1 -0
  156. package/session/useSessionPageFlow.js.map +1 -1
  157. package/session/useSessionPanel.d.ts +20 -0
  158. package/session/useSessionPanel.d.ts.map +1 -1
  159. package/session/useSessionPanel.js +13 -0
  160. package/session/useSessionPanel.js.map +1 -1
  161. package/session/useSessionRailViews.d.ts +13 -1
  162. package/session/useSessionRailViews.d.ts.map +1 -1
  163. package/session/useSessionRailViews.js +5 -3
  164. package/session/useSessionRailViews.js.map +1 -1
  165. package/src/composer/SessionComposer.tsx +76 -7
  166. package/src/composer/__tests__/SessionComposer-editing.test.tsx +128 -0
  167. package/src/execution/ArtifactApplyButton.tsx +79 -0
  168. package/src/execution/ArtifactContentBody.tsx +255 -0
  169. package/src/execution/ArtifactDocument.tsx +344 -0
  170. package/src/execution/ArtifactPreviewModal.tsx +49 -415
  171. package/src/execution/ArtifactRow.tsx +165 -0
  172. package/src/execution/ArtifactsWidget.tsx +24 -33
  173. package/src/execution/FileChangeProgressBar.tsx +121 -0
  174. package/src/execution/FileChangesView.tsx +18 -4
  175. package/src/execution/FileReviewAtoms.tsx +89 -0
  176. package/src/execution/FileReviewCard.tsx +57 -90
  177. package/src/execution/MessageEntry.tsx +0 -62
  178. package/src/execution/MessageThread.tsx +29 -15
  179. package/src/execution/PlanArtifactCard.tsx +130 -52
  180. package/src/execution/PlanCompletionCard.tsx +5 -5
  181. package/src/execution/PlanStreamingCard.tsx +15 -11
  182. package/src/execution/__tests__/ArtifactContentBody.test.tsx +131 -0
  183. package/src/execution/__tests__/ArtifactDocument.test.tsx +100 -0
  184. package/src/execution/__tests__/ArtifactPreviewModal.test.tsx +6 -6
  185. package/src/execution/__tests__/ArtifactRow.test.tsx +139 -0
  186. package/src/execution/__tests__/ArtifactsWidget.test.tsx +78 -0
  187. package/src/execution/__tests__/FileChangeProgressBar.test.tsx +125 -0
  188. package/src/execution/__tests__/FileReviewCard.test.tsx +66 -7
  189. package/src/execution/__tests__/MessageThread.test.tsx +1 -1
  190. package/src/execution/__tests__/PlanArtifactCard.test.tsx +52 -18
  191. package/src/execution/__tests__/PlanCompletionCard.test.tsx +2 -2
  192. package/src/execution/__tests__/PlanStreamingCard.test.tsx +11 -4
  193. package/src/execution/__tests__/buildThreadItems-plan-completion.test.ts +104 -8
  194. package/src/execution/__tests__/message-entry.test.tsx +56 -39
  195. package/src/execution/__tests__/useArtifactCopy.test.tsx +90 -0
  196. package/src/execution/__tests__/useArtifactDownload.test.tsx +2 -0
  197. package/src/execution/__tests__/useArtifactInspection.test.tsx +151 -0
  198. package/src/execution/__tests__/useCreateAgentExecution.test.tsx +35 -0
  199. package/src/execution/index.ts +20 -2
  200. package/src/execution/use-build-from-plan-hotkey.ts +1 -1
  201. package/src/execution/useArtifactContent.ts +7 -0
  202. package/src/execution/useArtifactCopy.ts +109 -0
  203. package/src/execution/useArtifactDownload.ts +17 -5
  204. package/src/execution/useArtifactInspection.ts +252 -0
  205. package/src/execution/useCreateAgentExecution.ts +17 -3
  206. package/src/index.ts +17 -2
  207. package/src/internal/MermaidDiagram.tsx +164 -0
  208. package/src/internal/__tests__/a11y/mermaid-theme.a11y.test.tsx +122 -0
  209. package/src/internal/__tests__/markdown-components.test.tsx +93 -1
  210. package/src/internal/__tests__/mermaid-diagram.test.tsx +282 -0
  211. package/src/internal/file-tree/FileTreeNode.tsx +13 -2
  212. package/src/internal/markdown-components.tsx +36 -1
  213. package/src/internal/mermaid-loader.ts +44 -0
  214. package/src/internal/mermaid-theme-css.ts +178 -0
  215. package/src/internal/slug.ts +10 -6
  216. package/src/library/__tests__/detect-plan-artifact.test.ts +21 -0
  217. package/src/library/detect-plan-artifact.ts +32 -10
  218. package/src/library/index.ts +2 -0
  219. package/src/session/PlanEditor.tsx +1 -1
  220. package/src/session/SessionViewer.tsx +227 -51
  221. package/src/session/__tests__/PlanEditor.test.tsx +4 -4
  222. package/src/session/__tests__/PlanStreamingDocument.test.tsx +1 -1
  223. package/src/session/__tests__/SessionViewer-artifactTabs.test.tsx +210 -0
  224. package/src/session/__tests__/SessionViewer-editResubmit.test.tsx +233 -0
  225. package/src/session/__tests__/SessionViewer-fileReviewDock.test.tsx +57 -0
  226. package/src/session/__tests__/useSessionArtifacts.test.ts +57 -0
  227. package/src/session/__tests__/useSessionConversation.test.tsx +216 -1
  228. package/src/session/__tests__/useSessionPanel.test.ts +70 -1
  229. package/src/session/artifact-document.ts +23 -0
  230. package/src/session/facets/ArtifactsTab.tsx +66 -18
  231. package/src/session/facets/__tests__/ArtifactsTab.test.tsx +111 -47
  232. package/src/session/index.ts +5 -1
  233. package/src/session/plan-document.ts +14 -3
  234. package/src/session/usePlanDraft.ts +1 -1
  235. package/src/session/useSessionArtifacts.ts +16 -5
  236. package/src/session/useSessionConversation.ts +80 -10
  237. package/src/session/useSessionPageFlow.ts +1 -0
  238. package/src/session/useSessionPanel.ts +41 -0
  239. package/src/session/useSessionRailViews.tsx +21 -1
  240. package/src/workspace/EditorTabs.tsx +38 -9
  241. package/src/workspace/ExplorerTree.tsx +10 -4
  242. package/src/workspace/FileViewer.tsx +27 -6
  243. package/src/workspace/WorkspaceContentSearch.tsx +44 -20
  244. package/src/workspace/WorkspaceEditor.tsx +19 -16
  245. package/src/workspace/WorkspaceFileSearch.tsx +51 -28
  246. package/src/workspace/WorkspaceSurface.tsx +106 -48
  247. package/src/workspace/__tests__/EditorTabs.test.tsx +4 -2
  248. package/src/workspace/__tests__/ExplorerTree.test.tsx +52 -0
  249. package/src/workspace/__tests__/FileViewer.test.tsx +18 -0
  250. package/src/workspace/__tests__/WorkspaceContentSearch.test.tsx +38 -5
  251. package/src/workspace/__tests__/WorkspaceEditor-initialPanel.test.tsx +19 -0
  252. package/src/workspace/__tests__/WorkspaceFileSearch.test.tsx +27 -6
  253. package/src/workspace/__tests__/WorkspaceSurface.test.tsx +70 -0
  254. package/src/workspace/__tests__/a11y/harness.tsx +140 -0
  255. package/src/workspace/__tests__/a11y/mock-capabilities.ts +138 -0
  256. package/src/workspace/__tests__/a11y/workspace-editor.a11y.test.tsx +65 -0
  257. package/src/workspace/__tests__/a11y/workspace-explorer-search.a11y.test.tsx +112 -0
  258. package/src/workspace/__tests__/a11y/workspace-viewer-tabs.a11y.test.tsx +69 -0
  259. package/styles.css +1 -1
  260. package/workspace/EditorTabs.d.ts +21 -3
  261. package/workspace/EditorTabs.d.ts.map +1 -1
  262. package/workspace/EditorTabs.js +18 -6
  263. package/workspace/EditorTabs.js.map +1 -1
  264. package/workspace/ExplorerTree.js +8 -4
  265. package/workspace/ExplorerTree.js.map +1 -1
  266. package/workspace/FileViewer.d.ts.map +1 -1
  267. package/workspace/FileViewer.js +19 -8
  268. package/workspace/FileViewer.js.map +1 -1
  269. package/workspace/WorkspaceContentSearch.d.ts.map +1 -1
  270. package/workspace/WorkspaceContentSearch.js +23 -2
  271. package/workspace/WorkspaceContentSearch.js.map +1 -1
  272. package/workspace/WorkspaceEditor.d.ts.map +1 -1
  273. package/workspace/WorkspaceEditor.js +8 -10
  274. package/workspace/WorkspaceEditor.js.map +1 -1
  275. package/workspace/WorkspaceFileSearch.d.ts.map +1 -1
  276. package/workspace/WorkspaceFileSearch.js +23 -3
  277. package/workspace/WorkspaceFileSearch.js.map +1 -1
  278. package/workspace/WorkspaceSurface.d.ts.map +1 -1
  279. package/workspace/WorkspaceSurface.js +54 -18
  280. package/workspace/WorkspaceSurface.js.map +1 -1
  281. package/execution/ArtifactCard.d.ts +0 -81
  282. package/execution/ArtifactCard.d.ts.map +0 -1
  283. package/execution/ArtifactCard.js +0 -130
  284. package/execution/ArtifactCard.js.map +0 -1
  285. package/src/execution/ArtifactCard.tsx +0 -320
@@ -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,109 @@
1
+ "use client";
2
+
3
+ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
4
+ import { create } from "@bufbuild/protobuf";
5
+ import { GetArtifactContentRequestSchema } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/io_pb";
6
+ import { useStigmer } from "../hooks.js";
7
+ import { toError } from "../internal/toError.js";
8
+
9
+ /** How long the transient `copied` flag stays true after a successful copy. */
10
+ const COPIED_FEEDBACK_MS = 2000;
11
+
12
+ /** Return value of {@link useArtifactCopy}. */
13
+ export interface UseArtifactCopyReturn {
14
+ /**
15
+ * Fetch the artifact's text at click time and write it to the clipboard.
16
+ * No-op when the execution id is unknown or a copy is already in flight.
17
+ * Resolves once the copy has been attempted.
18
+ *
19
+ * @param storageKey - Storage key of the artifact to copy.
20
+ */
21
+ readonly copy: (storageKey: string) => Promise<void>;
22
+
23
+ /** `true` while the content is being fetched / written to the clipboard. */
24
+ readonly isCopying: boolean;
25
+
26
+ /**
27
+ * `true` for a brief window after a successful copy (drives "Copied"
28
+ * affordance feedback), then resets automatically.
29
+ */
30
+ readonly copied: boolean;
31
+
32
+ /** Error from the last failed copy attempt, or `null` when healthy. */
33
+ readonly error: Error | null;
34
+ }
35
+
36
+ /**
37
+ * Behavior hook that copies an execution artifact's text to the clipboard on
38
+ * demand.
39
+ *
40
+ * The imperative sibling of {@link useArtifactDownload}: it fetches content at
41
+ * click time via `getArtifactContent` (CORS-safe, through the Stigmer API)
42
+ * rather than requiring the caller to have loaded it. This suits content-free
43
+ * surfaces — a plan card that detects the artifact by convention and never
44
+ * fetches until the user acts — where a mount-time {@link useArtifactContent}
45
+ * would be wasteful.
46
+ *
47
+ * Returns an imperative `copy(storageKey)` action plus a transient `copied`
48
+ * flag for feedback, mirroring the modal's copy affordance so every surface
49
+ * shares one implementation.
50
+ *
51
+ * @param executionId - Execution that produced the artifacts, or `null` to
52
+ * disable (the action becomes a no-op).
53
+ *
54
+ * @example
55
+ * ```tsx
56
+ * const { copy, isCopying, copied } = useArtifactCopy(executionId);
57
+ * <button onClick={() => copy(artifact.storageKey)} disabled={isCopying}>
58
+ * {copied ? "Copied" : "Copy"}
59
+ * </button>
60
+ * ```
61
+ *
62
+ * @see useArtifactDownload — imperative save-to-disk counterpart
63
+ * @see useArtifactContent — declarative content reader (preview rendering)
64
+ */
65
+ export function useArtifactCopy(
66
+ executionId: string | null,
67
+ ): UseArtifactCopyReturn {
68
+ const stigmer = useStigmer();
69
+ const [isCopying, setIsCopying] = useState(false);
70
+ const [copied, setCopied] = useState(false);
71
+ const [error, setError] = useState<Error | null>(null);
72
+
73
+ // Clear the pending "copied" timer on unmount so a resolved copy never sets
74
+ // state on an unmounted component.
75
+ const copiedTimer = useRef<ReturnType<typeof setTimeout> | null>(null);
76
+ useEffect(() => {
77
+ return () => {
78
+ if (copiedTimer.current !== null) clearTimeout(copiedTimer.current);
79
+ };
80
+ }, []);
81
+
82
+ const copy = useCallback(
83
+ async (storageKey: string) => {
84
+ if (!executionId || !storageKey || isCopying) return;
85
+ setIsCopying(true);
86
+ setError(null);
87
+ try {
88
+ const result = await stigmer.agentExecution.getArtifactContent(
89
+ create(GetArtifactContentRequestSchema, { executionId, storageKey }),
90
+ );
91
+ const text = new TextDecoder().decode(result.content);
92
+ await navigator.clipboard.writeText(text);
93
+ setCopied(true);
94
+ if (copiedTimer.current !== null) clearTimeout(copiedTimer.current);
95
+ copiedTimer.current = setTimeout(() => setCopied(false), COPIED_FEEDBACK_MS);
96
+ } catch (err) {
97
+ setError(toError(err));
98
+ } finally {
99
+ setIsCopying(false);
100
+ }
101
+ },
102
+ [executionId, stigmer, isCopying],
103
+ );
104
+
105
+ return useMemo(
106
+ () => ({ copy, isCopying, copied, error }),
107
+ [copy, isCopying, copied, error],
108
+ );
109
+ }
@@ -65,7 +65,16 @@ export function useArtifactDownload(
65
65
  setError(null);
66
66
  try {
67
67
  const result = await stigmer.agentExecution.getArtifactDownloadUrl(
68
- create(GetArtifactDownloadUrlRequestSchema, { executionId, storageKey }),
68
+ create(GetArtifactDownloadUrlRequestSchema, {
69
+ executionId,
70
+ storageKey,
71
+ // This is a save-to-disk action, so mint a URL that forces a
72
+ // browser download (Content-Disposition: attachment). Browsers
73
+ // ignore the anchor's `download` attribute cross-origin, so the
74
+ // disposition must ride on the URL itself. Contrast
75
+ // useArtifactDownloadUrl, which stays inline for `<img src>`.
76
+ asAttachment: true,
77
+ }),
69
78
  );
70
79
  if (result.downloadUrl) {
71
80
  triggerBrowserDownload(result.downloadUrl, fileName);
@@ -86,10 +95,13 @@ export function useArtifactDownload(
86
95
  }
87
96
 
88
97
  /**
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.
98
+ * Trigger a browser download for a URL via a transient anchor click.
99
+ *
100
+ * The URL minted for this flow carries `Content-Disposition: attachment`
101
+ * (requested via `asAttachment`), so the browser saves the file even
102
+ * cross-origin — where it would ignore the anchor's `download` attribute. The
103
+ * `download` hint is still set as a same-origin nicety. Falls back to a new tab
104
+ * in non-DOM environments.
93
105
  */
94
106
  function triggerBrowserDownload(url: string, fileName?: string): void {
95
107
  if (typeof document === "undefined") {
@@ -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
+ }
@@ -55,11 +55,13 @@ export interface CreateAgentExecutionInput {
55
55
  */
56
56
  readonly interactionMode?: "agent" | "plan";
57
57
  /**
58
- * Marks this execution as a "Build from plan" turn: the user approved a
58
+ * Marks this execution as a Build-from-plan turn: the user approved a
59
59
  * plan from a prior Plan-mode execution and asked the agent to implement
60
60
  * it. The runner injects the implement-plan directive (pointing at the
61
- * attached approved plan when present); the thread renders the turn as a
62
- * compact chip instead of the raw message text.
61
+ * attached approved plan when present); the thread hides the turn's
62
+ * machine-written message entirely the plan card above it is the
63
+ * visible cause. Surfaces without that treatment (the CLI, history)
64
+ * show the message text as-is.
63
65
  *
64
66
  * Maps to `ExecutionConfig.build_from_plan` in the proto.
65
67
  */
@@ -91,6 +93,17 @@ export interface CreateAgentExecutionInput {
91
93
  * Populated by the drag-to-reference feature in SessionComposer.
92
94
  */
93
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;
94
107
  }
95
108
 
96
109
  /** Resolved output of {@link UseCreateAgentExecutionReturn.create}. */
@@ -197,6 +210,7 @@ export function useCreateAgentExecution(): UseCreateAgentExecutionReturn {
197
210
  runtimeEnv: input.runtimeEnv,
198
211
  attachments: input.attachments,
199
212
  workspaceFileRefs: input.workspaceFileRefs,
213
+ supersedesExecutionId: input.supersedesExecutionId,
200
214
  });
201
215
 
202
216
  return {
package/src/index.ts CHANGED
@@ -129,6 +129,7 @@ export {
129
129
  useSessionExecutions,
130
130
  useSessionConversation,
131
131
  useSessionArtifacts,
132
+ artifactKey,
132
133
  useSessionWriteBacks,
133
134
  useSessionFileChanges,
134
135
  useSessionUsage,
@@ -154,6 +155,7 @@ export {
154
155
  PlanStreamingDocument,
155
156
  PLAN_DOCUMENT_ENTRY_ID,
156
157
  PLAN_DOCUMENT_PATH,
158
+ ARTIFACT_DOCUMENT_ENTRY_ID,
157
159
  SetupTab,
158
160
  } from "./session/index.js";
159
161
  export type {
@@ -283,12 +285,16 @@ export {
283
285
  ApprovalCardBody,
284
286
  FileReviewCard,
285
287
  FileReviewDock,
288
+ FileChangeProgressBar,
286
289
  ApprovalContext,
287
290
  useApproval,
288
291
  FileReviewContext,
289
292
  useFileReviewRowState,
290
- ArtifactCard,
293
+ ArtifactRow,
291
294
  ArtifactContentRenderer,
295
+ ArtifactContentBody,
296
+ ArtifactDocument,
297
+ useArtifactInspection,
292
298
  ArtifactPreviewContent,
293
299
  ArtifactPreviewModal,
294
300
  ArtifactsWidget,
@@ -307,6 +313,7 @@ export {
307
313
  useArtifactContent,
308
314
  useArtifactDownloadUrl,
309
315
  useArtifactDownload,
316
+ useArtifactCopy,
310
317
  useToolOutputContent,
311
318
  useFileChangeContent,
312
319
  execIdFromStorageKey,
@@ -375,11 +382,16 @@ export type {
375
382
  ApprovalCardBodyProps,
376
383
  FileReviewCardProps,
377
384
  FileReviewDockProps,
385
+ FileChangeProgressBarProps,
378
386
  ApprovalContextValue,
379
387
  UseApprovalResult,
380
388
  FileReviewContextValue,
381
- ArtifactCardProps,
389
+ ArtifactRowProps,
382
390
  ArtifactContentRendererProps,
391
+ ArtifactContentBodyProps,
392
+ ArtifactDocumentProps,
393
+ ArtifactInspection,
394
+ UseArtifactInspectionOptions,
383
395
  ArtifactRenderMode,
384
396
  ArtifactPreviewContentProps,
385
397
  ArtifactPreviewModalProps,
@@ -397,6 +409,7 @@ export type {
397
409
  UseArtifactDownloadUrlReturn,
398
410
  UseArtifactDownloadUrlOptions,
399
411
  UseArtifactDownloadReturn,
412
+ UseArtifactCopyReturn,
400
413
  UseToolOutputContentReturn,
401
414
  ToolOutputRefLike,
402
415
  UseFileChangeContentReturn,
@@ -980,10 +993,12 @@ export {
980
993
  detectSkillPackage,
981
994
  useDetectSkillPackage,
982
995
  isPlanArtifact,
996
+ isPlanArtifactName,
983
997
  findPlanArtifact,
984
998
  findLatestSessionPlan,
985
999
  findStreamingPlan,
986
1000
  PLAN_ARTIFACT_NAME,
1001
+ PLAN_ARTIFACT_SUFFIX,
987
1002
  parseResourceYaml,
988
1003
  serializeAgentYaml,
989
1004
  serializeMcpServerYaml,