@stigmer/react 3.1.1 → 3.1.3

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 (483) hide show
  1. package/composer/SessionComposer.d.ts +25 -1
  2. package/composer/SessionComposer.d.ts.map +1 -1
  3. package/composer/SessionComposer.js +19 -8
  4. package/composer/SessionComposer.js.map +1 -1
  5. package/execution/ArtifactContentRenderer.d.ts +11 -1
  6. package/execution/ArtifactContentRenderer.d.ts.map +1 -1
  7. package/execution/ArtifactContentRenderer.js +39 -15
  8. package/execution/ArtifactContentRenderer.js.map +1 -1
  9. package/execution/ArtifactPreviewModal.d.ts +5 -5
  10. package/execution/ArtifactPreviewModal.js +4 -4
  11. package/execution/ArtifactPreviewModal.js.map +1 -1
  12. package/execution/FileChangeProgressBar.d.ts +47 -0
  13. package/execution/FileChangeProgressBar.d.ts.map +1 -0
  14. package/execution/FileChangeProgressBar.js +49 -0
  15. package/execution/FileChangeProgressBar.js.map +1 -0
  16. package/execution/FileChangesView.d.ts.map +1 -1
  17. package/execution/FileChangesView.js +19 -4
  18. package/execution/FileChangesView.js.map +1 -1
  19. package/execution/FilePathContext.d.ts +8 -1
  20. package/execution/FilePathContext.d.ts.map +1 -1
  21. package/execution/FilePathContext.js.map +1 -1
  22. package/execution/FilePathLink.d.ts.map +1 -1
  23. package/execution/FilePathLink.js +21 -6
  24. package/execution/FilePathLink.js.map +1 -1
  25. package/execution/FileReviewAtoms.d.ts +27 -0
  26. package/execution/FileReviewAtoms.d.ts.map +1 -0
  27. package/execution/FileReviewAtoms.js +64 -0
  28. package/execution/FileReviewAtoms.js.map +1 -0
  29. package/execution/FileReviewCard.d.ts +2 -2
  30. package/execution/FileReviewCard.d.ts.map +1 -1
  31. package/execution/FileReviewCard.js +32 -62
  32. package/execution/FileReviewCard.js.map +1 -1
  33. package/execution/MessageEntry.d.ts +20 -0
  34. package/execution/MessageEntry.d.ts.map +1 -1
  35. package/execution/MessageEntry.js +19 -5
  36. package/execution/MessageEntry.js.map +1 -1
  37. package/execution/MessageThread.d.ts +75 -13
  38. package/execution/MessageThread.d.ts.map +1 -1
  39. package/execution/MessageThread.js +195 -27
  40. package/execution/MessageThread.js.map +1 -1
  41. package/execution/PlanArtifactCard.d.ts +36 -21
  42. package/execution/PlanArtifactCard.d.ts.map +1 -1
  43. package/execution/PlanArtifactCard.js +39 -25
  44. package/execution/PlanArtifactCard.js.map +1 -1
  45. package/execution/PlanCompletionCard.d.ts +4 -4
  46. package/execution/PlanCompletionCard.d.ts.map +1 -1
  47. package/execution/PlanCompletionCard.js +4 -4
  48. package/execution/PlanCompletionCard.js.map +1 -1
  49. package/execution/PlanDocumentMessage.d.ts +32 -0
  50. package/execution/PlanDocumentMessage.d.ts.map +1 -0
  51. package/execution/PlanDocumentMessage.js +37 -0
  52. package/execution/PlanDocumentMessage.js.map +1 -0
  53. package/execution/PlanStreamingCard.d.ts +50 -0
  54. package/execution/PlanStreamingCard.d.ts.map +1 -0
  55. package/execution/PlanStreamingCard.js +40 -0
  56. package/execution/PlanStreamingCard.js.map +1 -0
  57. package/execution/index.d.ts +9 -1
  58. package/execution/index.d.ts.map +1 -1
  59. package/execution/index.js +4 -0
  60. package/execution/index.js.map +1 -1
  61. package/execution/use-build-from-plan-hotkey.d.ts +1 -1
  62. package/execution/use-build-from-plan-hotkey.js +1 -1
  63. package/execution/useArtifactCopy.d.ts +51 -0
  64. package/execution/useArtifactCopy.d.ts.map +1 -0
  65. package/execution/useArtifactCopy.js +75 -0
  66. package/execution/useArtifactCopy.js.map +1 -0
  67. package/execution/useArtifactDownload.d.ts.map +1 -1
  68. package/execution/useArtifactDownload.js +17 -5
  69. package/execution/useArtifactDownload.js.map +1 -1
  70. package/execution/useCreateAgentExecution.d.ts +12 -0
  71. package/execution/useCreateAgentExecution.d.ts.map +1 -1
  72. package/execution/useCreateAgentExecution.js +5 -1
  73. package/execution/useCreateAgentExecution.js.map +1 -1
  74. package/github/decodeGitHubContent.d.ts +29 -0
  75. package/github/decodeGitHubContent.d.ts.map +1 -0
  76. package/github/decodeGitHubContent.js +72 -0
  77. package/github/decodeGitHubContent.js.map +1 -0
  78. package/github/index.d.ts +1 -0
  79. package/github/index.d.ts.map +1 -1
  80. package/github/index.js +1 -0
  81. package/github/index.js.map +1 -1
  82. package/github/useGitHubFileReader.d.ts +26 -0
  83. package/github/useGitHubFileReader.d.ts.map +1 -0
  84. package/github/useGitHubFileReader.js +106 -0
  85. package/github/useGitHubFileReader.js.map +1 -0
  86. package/github/useGitHubTreeLister.d.ts.map +1 -1
  87. package/github/useGitHubTreeLister.js +6 -2
  88. package/github/useGitHubTreeLister.js.map +1 -1
  89. package/index.d.ts +9 -9
  90. package/index.d.ts.map +1 -1
  91. package/index.js +5 -5
  92. package/index.js.map +1 -1
  93. package/internal/ResizableSplit.d.ts +72 -22
  94. package/internal/ResizableSplit.d.ts.map +1 -1
  95. package/internal/ResizableSplit.js +96 -38
  96. package/internal/ResizableSplit.js.map +1 -1
  97. package/internal/VirtualizedThread.d.ts +5 -3
  98. package/internal/VirtualizedThread.d.ts.map +1 -1
  99. package/internal/VirtualizedThread.js +6 -4
  100. package/internal/VirtualizedThread.js.map +1 -1
  101. package/internal/file-icons/index.d.ts +17 -0
  102. package/internal/file-icons/index.d.ts.map +1 -0
  103. package/internal/file-icons/index.js +60 -0
  104. package/internal/file-icons/index.js.map +1 -0
  105. package/internal/file-tree/FileTreeNode.d.ts +18 -1
  106. package/internal/file-tree/FileTreeNode.d.ts.map +1 -1
  107. package/internal/file-tree/FileTreeNode.js +11 -2
  108. package/internal/file-tree/FileTreeNode.js.map +1 -1
  109. package/internal/markdown-components.d.ts +38 -1
  110. package/internal/markdown-components.d.ts.map +1 -1
  111. package/internal/markdown-components.js +86 -11
  112. package/internal/markdown-components.js.map +1 -1
  113. package/{workflow → internal}/motion-preference.d.ts +7 -4
  114. package/internal/motion-preference.d.ts.map +1 -0
  115. package/{workflow → internal}/motion-preference.js +7 -4
  116. package/internal/motion-preference.js.map +1 -0
  117. package/internal/store/index.d.ts +33 -0
  118. package/internal/store/index.d.ts.map +1 -1
  119. package/internal/store/index.js +59 -0
  120. package/internal/store/index.js.map +1 -1
  121. package/internal/store/workspace-editors-store.d.ts +145 -0
  122. package/internal/store/workspace-editors-store.d.ts.map +1 -0
  123. package/internal/store/workspace-editors-store.js +208 -0
  124. package/internal/store/workspace-editors-store.js.map +1 -0
  125. package/internal/store/workspace-file-selection-store.d.ts +38 -0
  126. package/internal/store/workspace-file-selection-store.d.ts.map +1 -0
  127. package/internal/store/workspace-file-selection-store.js +62 -0
  128. package/internal/store/workspace-file-selection-store.js.map +1 -0
  129. package/internal/useRevealLine.d.ts +49 -0
  130. package/internal/useRevealLine.d.ts.map +1 -0
  131. package/internal/useRevealLine.js +43 -0
  132. package/internal/useRevealLine.js.map +1 -0
  133. package/library/detect-plan-artifact.d.ts +43 -4
  134. package/library/detect-plan-artifact.d.ts.map +1 -1
  135. package/library/detect-plan-artifact.js +45 -5
  136. package/library/detect-plan-artifact.js.map +1 -1
  137. package/library/detect-streaming-plan.d.ts +45 -0
  138. package/library/detect-streaming-plan.d.ts.map +1 -0
  139. package/library/detect-streaming-plan.js +83 -0
  140. package/library/detect-streaming-plan.js.map +1 -0
  141. package/library/index.d.ts +4 -1
  142. package/library/index.d.ts.map +1 -1
  143. package/library/index.js +2 -1
  144. package/library/index.js.map +1 -1
  145. package/package.json +4 -4
  146. package/session/NewSessionViewer.d.ts +19 -4
  147. package/session/NewSessionViewer.d.ts.map +1 -1
  148. package/session/NewSessionViewer.js +52 -25
  149. package/session/NewSessionViewer.js.map +1 -1
  150. package/session/PlanEditor.d.ts +55 -0
  151. package/session/PlanEditor.d.ts.map +1 -0
  152. package/session/PlanEditor.js +88 -0
  153. package/session/PlanEditor.js.map +1 -0
  154. package/session/PlanStreamingDocument.d.ts +30 -0
  155. package/session/PlanStreamingDocument.d.ts.map +1 -0
  156. package/session/PlanStreamingDocument.js +37 -0
  157. package/session/PlanStreamingDocument.js.map +1 -0
  158. package/session/SessionPanelChip.d.ts +26 -0
  159. package/session/SessionPanelChip.d.ts.map +1 -0
  160. package/session/SessionPanelChip.js +40 -0
  161. package/session/SessionPanelChip.js.map +1 -0
  162. package/session/SessionViewer.d.ts +34 -7
  163. package/session/SessionViewer.d.ts.map +1 -1
  164. package/session/SessionViewer.js +334 -33
  165. package/session/SessionViewer.js.map +1 -1
  166. package/session/{inspector → facets}/ArtifactsTab.d.ts +10 -2
  167. package/session/facets/ArtifactsTab.d.ts.map +1 -0
  168. package/session/{inspector → facets}/ArtifactsTab.js +8 -3
  169. package/session/facets/ArtifactsTab.js.map +1 -0
  170. package/session/{inspector → facets}/ChangesTab.d.ts +3 -3
  171. package/session/facets/ChangesTab.d.ts.map +1 -0
  172. package/session/{inspector → facets}/ChangesTab.js +3 -3
  173. package/session/facets/ChangesTab.js.map +1 -0
  174. package/session/facets/InspectTab.d.ts.map +1 -0
  175. package/session/facets/InspectTab.js.map +1 -0
  176. package/session/{inspector → facets}/SetupTab.d.ts +11 -2
  177. package/session/facets/SetupTab.d.ts.map +1 -0
  178. package/session/{inspector → facets}/SetupTab.js +4 -3
  179. package/session/facets/SetupTab.js.map +1 -0
  180. package/session/{inspector → facets}/UsageTab.d.ts +1 -1
  181. package/session/facets/UsageTab.d.ts.map +1 -0
  182. package/session/{inspector → facets}/UsageTab.js +1 -1
  183. package/session/facets/UsageTab.js.map +1 -0
  184. package/session/{inspector → facets}/index.d.ts +0 -6
  185. package/session/facets/index.d.ts.map +1 -0
  186. package/session/{inspector → facets}/index.js +0 -3
  187. package/session/facets/index.js.map +1 -0
  188. package/session/index.d.ts +11 -2
  189. package/session/index.d.ts.map +1 -1
  190. package/session/index.js +15 -2
  191. package/session/index.js.map +1 -1
  192. package/session/plan-document.d.ts +21 -0
  193. package/session/plan-document.d.ts.map +1 -0
  194. package/session/plan-document.js +28 -0
  195. package/session/plan-document.js.map +1 -0
  196. package/session/useOpenFileChange.d.ts +20 -0
  197. package/session/useOpenFileChange.d.ts.map +1 -0
  198. package/session/useOpenFileChange.js +31 -0
  199. package/session/useOpenFileChange.js.map +1 -0
  200. package/session/usePlanDraft.d.ts +52 -0
  201. package/session/usePlanDraft.d.ts.map +1 -0
  202. package/session/usePlanDraft.js +63 -0
  203. package/session/usePlanDraft.js.map +1 -0
  204. package/session/useSessionConversation.d.ts +14 -1
  205. package/session/useSessionConversation.d.ts.map +1 -1
  206. package/session/useSessionConversation.js +20 -2
  207. package/session/useSessionConversation.js.map +1 -1
  208. package/session/useSessionPageFlow.d.ts.map +1 -1
  209. package/session/useSessionPageFlow.js +1 -0
  210. package/session/useSessionPageFlow.js.map +1 -1
  211. package/session/useSessionPanel.d.ts +120 -0
  212. package/session/useSessionPanel.d.ts.map +1 -0
  213. package/session/useSessionPanel.js +130 -0
  214. package/session/useSessionPanel.js.map +1 -0
  215. package/session/useSessionRailViews.d.ts +45 -0
  216. package/session/useSessionRailViews.d.ts.map +1 -0
  217. package/session/useSessionRailViews.js +105 -0
  218. package/session/useSessionRailViews.js.map +1 -0
  219. package/src/composer/SessionComposer.tsx +53 -10
  220. package/src/composer/__tests__/SessionComposer-contract.test.tsx +58 -4
  221. package/src/execution/ArtifactContentRenderer.tsx +101 -24
  222. package/src/execution/ArtifactPreviewModal.tsx +9 -9
  223. package/src/execution/FileChangeProgressBar.tsx +121 -0
  224. package/src/execution/FileChangesView.tsx +18 -4
  225. package/src/execution/FilePathContext.tsx +8 -1
  226. package/src/execution/FilePathLink.tsx +22 -6
  227. package/src/execution/FileReviewAtoms.tsx +89 -0
  228. package/src/execution/FileReviewCard.tsx +57 -90
  229. package/src/execution/MessageEntry.tsx +49 -1
  230. package/src/execution/MessageThread.tsx +308 -39
  231. package/src/execution/PlanArtifactCard.tsx +174 -64
  232. package/src/execution/PlanCompletionCard.tsx +5 -5
  233. package/src/execution/PlanDocumentMessage.tsx +96 -0
  234. package/src/execution/PlanStreamingCard.tsx +141 -0
  235. package/src/execution/__tests__/ArtifactContentRenderer.test.tsx +80 -1
  236. package/src/execution/__tests__/ArtifactPreviewModal.test.tsx +6 -6
  237. package/src/execution/__tests__/FileChangeProgressBar.test.tsx +125 -0
  238. package/src/execution/__tests__/FilePathLink.test.tsx +87 -0
  239. package/src/execution/__tests__/FileReviewCard.test.tsx +66 -7
  240. package/src/execution/__tests__/MessageThread.test.tsx +102 -1
  241. package/src/execution/__tests__/PlanArtifactCard.test.tsx +114 -26
  242. package/src/execution/__tests__/PlanCompletionCard.test.tsx +2 -2
  243. package/src/execution/__tests__/PlanDocumentMessage.test.tsx +55 -0
  244. package/src/execution/__tests__/PlanStreamingCard.test.tsx +56 -0
  245. package/src/execution/__tests__/buildThreadItems-plan-completion.test.ts +600 -22
  246. package/src/execution/__tests__/message-entry.test.tsx +17 -0
  247. package/src/execution/__tests__/useArtifactCopy.test.tsx +90 -0
  248. package/src/execution/__tests__/useArtifactDownload.test.tsx +2 -0
  249. package/src/execution/__tests__/useCreateAgentExecution.test.tsx +85 -0
  250. package/src/execution/index.ts +13 -1
  251. package/src/execution/use-build-from-plan-hotkey.ts +1 -1
  252. package/src/execution/useArtifactCopy.ts +109 -0
  253. package/src/execution/useArtifactDownload.ts +17 -5
  254. package/src/execution/useCreateAgentExecution.ts +18 -1
  255. package/src/github/__tests__/decodeGitHubContent.test.ts +102 -0
  256. package/src/github/__tests__/useGitHubFileReader.test.ts +183 -0
  257. package/src/github/decodeGitHubContent.ts +83 -0
  258. package/src/github/index.ts +1 -0
  259. package/src/github/useGitHubFileReader.ts +152 -0
  260. package/src/github/useGitHubTreeLister.ts +6 -2
  261. package/src/index.ts +69 -9
  262. package/src/internal/ResizableSplit.tsx +184 -51
  263. package/src/internal/VirtualizedThread.tsx +12 -4
  264. package/src/internal/__tests__/ResizableSplit.test.tsx +125 -3
  265. package/src/internal/__tests__/markdown-components.test.tsx +68 -0
  266. package/src/internal/__tests__/useRevealLine.test.tsx +97 -0
  267. package/src/internal/file-icons/__tests__/file-icons.test.tsx +35 -0
  268. package/src/internal/file-icons/index.tsx +137 -0
  269. package/src/internal/file-tree/FileTreeNode.tsx +56 -5
  270. package/src/internal/file-tree/__tests__/FileTreeNode-explorer.test.tsx +86 -0
  271. package/src/internal/markdown-components.tsx +116 -11
  272. package/src/{workflow → internal}/motion-preference.ts +7 -4
  273. package/src/internal/store/__tests__/workspace-editors-store.test.ts +216 -0
  274. package/src/internal/store/__tests__/workspace-file-selection-store.test.ts +118 -0
  275. package/src/internal/store/index.ts +94 -0
  276. package/src/internal/store/workspace-editors-store.ts +315 -0
  277. package/src/internal/store/workspace-file-selection-store.ts +88 -0
  278. package/src/internal/useRevealLine.ts +81 -0
  279. package/src/library/__tests__/detect-plan-artifact.test.ts +69 -0
  280. package/src/library/__tests__/detect-streaming-plan.test.ts +234 -0
  281. package/src/library/detect-plan-artifact.ts +62 -5
  282. package/src/library/detect-streaming-plan.ts +108 -0
  283. package/src/library/index.ts +7 -0
  284. package/src/session/NewSessionViewer.tsx +123 -55
  285. package/src/session/PlanEditor.tsx +361 -0
  286. package/src/session/PlanStreamingDocument.tsx +97 -0
  287. package/src/session/SessionPanelChip.tsx +103 -0
  288. package/src/session/SessionViewer.tsx +655 -96
  289. package/src/session/__tests__/NewSessionViewer.test.tsx +148 -0
  290. package/src/session/__tests__/PlanEditor.test.tsx +217 -0
  291. package/src/session/__tests__/PlanStreamingDocument.test.tsx +50 -0
  292. package/src/session/__tests__/SessionPanelChip.test.tsx +42 -0
  293. package/src/session/__tests__/SessionViewer-fileReviewDock.test.tsx +66 -4
  294. package/src/session/__tests__/SessionViewer-transcriptFileOpen.test.tsx +205 -0
  295. package/src/session/__tests__/audienceWiring.test.tsx +55 -19
  296. package/src/session/__tests__/useOpenFileChange.test.tsx +82 -0
  297. package/src/session/__tests__/usePlanDraft.test.ts +102 -0
  298. package/src/session/__tests__/useSessionConversation.test.tsx +44 -0
  299. package/src/session/__tests__/useSessionPanel.test.ts +298 -0
  300. package/src/session/__tests__/useSessionRailViews.test.tsx +124 -0
  301. package/src/session/{inspector → facets}/ArtifactsTab.tsx +27 -5
  302. package/src/session/{inspector → facets}/ChangesTab.tsx +3 -3
  303. package/src/session/{inspector → facets}/SetupTab.tsx +13 -1
  304. package/src/session/{inspector → facets}/UsageTab.tsx +1 -1
  305. package/src/session/{inspector → facets}/__tests__/ArtifactsTab.test.tsx +42 -8
  306. package/src/session/facets/__tests__/SetupTab.test.tsx +44 -0
  307. package/src/session/{inspector → facets}/index.ts +0 -16
  308. package/src/session/index.ts +30 -13
  309. package/src/session/plan-document.ts +30 -0
  310. package/src/session/useOpenFileChange.ts +53 -0
  311. package/src/session/usePlanDraft.ts +109 -0
  312. package/src/session/useSessionConversation.ts +38 -3
  313. package/src/session/useSessionPageFlow.ts +1 -0
  314. package/src/session/useSessionPanel.ts +288 -0
  315. package/src/session/useSessionRailViews.tsx +200 -0
  316. package/src/workflow/WorkflowExecutionGraph.tsx +1 -1
  317. package/src/workflow/index.ts +1 -1
  318. package/src/workflow/useFollowExecution.ts +1 -1
  319. package/src/workspace/EditorTabs.tsx +174 -0
  320. package/src/workspace/ExplorerTree.tsx +253 -0
  321. package/src/workspace/FileViewer.tsx +537 -0
  322. package/src/workspace/WorkspaceContentSearch.tsx +502 -0
  323. package/src/workspace/WorkspaceContentSearcher.ts +70 -0
  324. package/src/workspace/WorkspaceEditor.tsx +19 -16
  325. package/src/workspace/WorkspaceFileLister.ts +11 -0
  326. package/src/workspace/WorkspaceFileReader.ts +57 -0
  327. package/src/workspace/WorkspaceFileSearch.tsx +452 -0
  328. package/src/workspace/WorkspaceSurface.tsx +839 -0
  329. package/src/workspace/__tests__/EditorTabs.test.tsx +102 -0
  330. package/src/workspace/__tests__/ExplorerTree.test.tsx +204 -0
  331. package/src/workspace/__tests__/FileViewer.test.tsx +439 -0
  332. package/src/workspace/__tests__/WorkspaceContentSearch.test.tsx +229 -0
  333. package/src/workspace/__tests__/WorkspaceEditor-initialPanel.test.tsx +19 -0
  334. package/src/workspace/__tests__/WorkspaceFileSearch.test.tsx +227 -0
  335. package/src/workspace/__tests__/WorkspaceSurface.test.tsx +418 -0
  336. package/src/workspace/__tests__/a11y/harness.tsx +140 -0
  337. package/src/workspace/__tests__/a11y/mock-capabilities.ts +138 -0
  338. package/src/workspace/__tests__/a11y/workspace-editor.a11y.test.tsx +65 -0
  339. package/src/workspace/__tests__/a11y/workspace-explorer-search.a11y.test.tsx +112 -0
  340. package/src/workspace/__tests__/a11y/workspace-viewer-tabs.a11y.test.tsx +69 -0
  341. package/src/workspace/__tests__/findChangeForSelection.test.ts +132 -0
  342. package/src/workspace/__tests__/matchContentPreview.test.ts +55 -0
  343. package/src/workspace/__tests__/matchWorkspaceFiles.test.ts +104 -0
  344. package/src/workspace/__tests__/resolveWorkspaceFileSelection.test.ts +135 -0
  345. package/src/workspace/__tests__/useWorkspaceContentSearch.test.tsx +196 -0
  346. package/src/workspace/__tests__/useWorkspaceFileContent.test.tsx +190 -0
  347. package/src/workspace/__tests__/useWorkspaceFileSearch.test.tsx +162 -0
  348. package/src/workspace/findChangeForSelection.ts +61 -0
  349. package/src/workspace/index.ts +56 -2
  350. package/src/workspace/matchContentPreview.ts +51 -0
  351. package/src/workspace/matchWorkspaceFiles.ts +86 -0
  352. package/src/workspace/resolveWorkspaceFileSelection.ts +119 -0
  353. package/src/workspace/useWorkspaceContentSearch.ts +228 -0
  354. package/src/workspace/useWorkspaceFileContent.ts +129 -0
  355. package/src/workspace/useWorkspaceFileSearch.ts +184 -0
  356. package/src/workspace/useWorkspaceFiles.ts +27 -27
  357. package/src/workspace/workspaceListingCache.ts +93 -0
  358. package/styles.css +1 -1
  359. package/workflow/WorkflowExecutionGraph.js +1 -1
  360. package/workflow/WorkflowExecutionGraph.js.map +1 -1
  361. package/workflow/index.d.ts +1 -1
  362. package/workflow/index.d.ts.map +1 -1
  363. package/workflow/index.js +1 -1
  364. package/workflow/index.js.map +1 -1
  365. package/workflow/useFollowExecution.js +1 -1
  366. package/workflow/useFollowExecution.js.map +1 -1
  367. package/workspace/EditorTabs.d.ts +47 -0
  368. package/workspace/EditorTabs.d.ts.map +1 -0
  369. package/workspace/EditorTabs.js +78 -0
  370. package/workspace/EditorTabs.js.map +1 -0
  371. package/workspace/ExplorerTree.d.ts +32 -0
  372. package/workspace/ExplorerTree.d.ts.map +1 -0
  373. package/workspace/ExplorerTree.js +49 -0
  374. package/workspace/ExplorerTree.js.map +1 -0
  375. package/workspace/FileViewer.d.ts +93 -0
  376. package/workspace/FileViewer.d.ts.map +1 -0
  377. package/workspace/FileViewer.js +193 -0
  378. package/workspace/FileViewer.js.map +1 -0
  379. package/workspace/WorkspaceContentSearch.d.ts +41 -0
  380. package/workspace/WorkspaceContentSearch.d.ts.map +1 -0
  381. package/workspace/WorkspaceContentSearch.js +216 -0
  382. package/workspace/WorkspaceContentSearch.js.map +1 -0
  383. package/workspace/WorkspaceContentSearcher.d.ts +59 -0
  384. package/workspace/WorkspaceContentSearcher.d.ts.map +1 -0
  385. package/workspace/WorkspaceContentSearcher.js +7 -0
  386. package/workspace/WorkspaceContentSearcher.js.map +1 -0
  387. package/workspace/WorkspaceEditor.d.ts.map +1 -1
  388. package/workspace/WorkspaceEditor.js +8 -10
  389. package/workspace/WorkspaceEditor.js.map +1 -1
  390. package/workspace/WorkspaceFileLister.d.ts +11 -0
  391. package/workspace/WorkspaceFileLister.d.ts.map +1 -1
  392. package/workspace/WorkspaceFileReader.d.ts +48 -0
  393. package/workspace/WorkspaceFileReader.d.ts.map +1 -0
  394. package/workspace/WorkspaceFileReader.js +11 -0
  395. package/workspace/WorkspaceFileReader.js.map +1 -0
  396. package/workspace/WorkspaceFileSearch.d.ts +32 -0
  397. package/workspace/WorkspaceFileSearch.d.ts.map +1 -0
  398. package/workspace/WorkspaceFileSearch.js +175 -0
  399. package/workspace/WorkspaceFileSearch.js.map +1 -0
  400. package/workspace/WorkspaceSurface.d.ts +151 -0
  401. package/workspace/WorkspaceSurface.d.ts.map +1 -0
  402. package/workspace/WorkspaceSurface.js +220 -0
  403. package/workspace/WorkspaceSurface.js.map +1 -0
  404. package/workspace/findChangeForSelection.d.ts +19 -0
  405. package/workspace/findChangeForSelection.d.ts.map +1 -0
  406. package/workspace/findChangeForSelection.js +46 -0
  407. package/workspace/findChangeForSelection.js.map +1 -0
  408. package/workspace/index.d.ts +24 -2
  409. package/workspace/index.d.ts.map +1 -1
  410. package/workspace/index.js +13 -1
  411. package/workspace/index.js.map +1 -1
  412. package/workspace/matchContentPreview.d.ts +21 -0
  413. package/workspace/matchContentPreview.d.ts.map +1 -0
  414. package/workspace/matchContentPreview.js +36 -0
  415. package/workspace/matchContentPreview.js.map +1 -0
  416. package/workspace/matchWorkspaceFiles.d.ts +30 -0
  417. package/workspace/matchWorkspaceFiles.d.ts.map +1 -0
  418. package/workspace/matchWorkspaceFiles.js +58 -0
  419. package/workspace/matchWorkspaceFiles.js.map +1 -0
  420. package/workspace/resolveWorkspaceFileSelection.d.ts +19 -0
  421. package/workspace/resolveWorkspaceFileSelection.d.ts.map +1 -0
  422. package/workspace/resolveWorkspaceFileSelection.js +115 -0
  423. package/workspace/resolveWorkspaceFileSelection.js.map +1 -0
  424. package/workspace/useWorkspaceContentSearch.d.ts +69 -0
  425. package/workspace/useWorkspaceContentSearch.d.ts.map +1 -0
  426. package/workspace/useWorkspaceContentSearch.js +154 -0
  427. package/workspace/useWorkspaceContentSearch.js.map +1 -0
  428. package/workspace/useWorkspaceFileContent.d.ts +73 -0
  429. package/workspace/useWorkspaceFileContent.d.ts.map +1 -0
  430. package/workspace/useWorkspaceFileContent.js +57 -0
  431. package/workspace/useWorkspaceFileContent.js.map +1 -0
  432. package/workspace/useWorkspaceFileSearch.d.ts +61 -0
  433. package/workspace/useWorkspaceFileSearch.d.ts.map +1 -0
  434. package/workspace/useWorkspaceFileSearch.js +119 -0
  435. package/workspace/useWorkspaceFileSearch.js.map +1 -0
  436. package/workspace/useWorkspaceFiles.d.ts +13 -5
  437. package/workspace/useWorkspaceFiles.d.ts.map +1 -1
  438. package/workspace/useWorkspaceFiles.js +18 -19
  439. package/workspace/useWorkspaceFiles.js.map +1 -1
  440. package/workspace/workspaceListingCache.d.ts +47 -0
  441. package/workspace/workspaceListingCache.d.ts.map +1 -0
  442. package/workspace/workspaceListingCache.js +55 -0
  443. package/workspace/workspaceListingCache.js.map +1 -0
  444. package/session/inspector/ArtifactsTab.d.ts.map +0 -1
  445. package/session/inspector/ArtifactsTab.js.map +0 -1
  446. package/session/inspector/ChangesTab.d.ts.map +0 -1
  447. package/session/inspector/ChangesTab.js.map +0 -1
  448. package/session/inspector/InspectTab.d.ts.map +0 -1
  449. package/session/inspector/InspectTab.js.map +0 -1
  450. package/session/inspector/SessionInspector.d.ts +0 -64
  451. package/session/inspector/SessionInspector.d.ts.map +0 -1
  452. package/session/inspector/SessionInspector.js +0 -53
  453. package/session/inspector/SessionInspector.js.map +0 -1
  454. package/session/inspector/SetupTab.d.ts.map +0 -1
  455. package/session/inspector/SetupTab.js.map +0 -1
  456. package/session/inspector/UsageTab.d.ts.map +0 -1
  457. package/session/inspector/UsageTab.js.map +0 -1
  458. package/session/inspector/WorkspaceTab.d.ts +0 -35
  459. package/session/inspector/WorkspaceTab.d.ts.map +0 -1
  460. package/session/inspector/WorkspaceTab.js +0 -75
  461. package/session/inspector/WorkspaceTab.js.map +0 -1
  462. package/session/inspector/index.d.ts.map +0 -1
  463. package/session/inspector/index.js.map +0 -1
  464. package/session/inspector/useSessionInspector.d.ts +0 -49
  465. package/session/inspector/useSessionInspector.d.ts.map +0 -1
  466. package/session/inspector/useSessionInspector.js +0 -122
  467. package/session/inspector/useSessionInspector.js.map +0 -1
  468. package/src/session/inspector/SessionInspector.tsx +0 -162
  469. package/src/session/inspector/WorkspaceTab.tsx +0 -245
  470. package/src/session/inspector/__tests__/useSessionInspector.test.ts +0 -251
  471. package/src/session/inspector/useSessionInspector.ts +0 -178
  472. package/src/workspace/WorkspaceEntryFiles.tsx +0 -196
  473. package/workflow/motion-preference.d.ts.map +0 -1
  474. package/workflow/motion-preference.js.map +0 -1
  475. package/workspace/WorkspaceEntryFiles.d.ts +0 -22
  476. package/workspace/WorkspaceEntryFiles.d.ts.map +0 -1
  477. package/workspace/WorkspaceEntryFiles.js +0 -58
  478. package/workspace/WorkspaceEntryFiles.js.map +0 -1
  479. /package/session/{inspector → facets}/InspectTab.d.ts +0 -0
  480. /package/session/{inspector → facets}/InspectTab.js +0 -0
  481. /package/src/{workflow → internal}/__tests__/motion-preference.test.ts +0 -0
  482. /package/src/session/{inspector → facets}/InspectTab.tsx +0 -0
  483. /package/src/session/{inspector → facets}/__tests__/ChangesTab.test.tsx +0 -0
@@ -1,33 +1,134 @@
1
1
  "use client";
2
2
 
3
- import { useCallback, useMemo, useRef, type ReactNode } from "react";
3
+ import { memo, useCallback, useEffect, useMemo, useRef, useState, type ReactNode } from "react";
4
+ import { create } from "@bufbuild/protobuf";
4
5
  import { cn } from "@stigmer/theme";
5
- import { getUserMessage, type ResourceRef } from "@stigmer/sdk";
6
+ import { getUserMessage, type AttachmentInput, type ResourceRef } from "@stigmer/sdk";
7
+ import {
8
+ GetArtifactContentRequestSchema,
9
+ UploadAttachmentRequestSchema,
10
+ } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/io_pb";
6
11
  import type { UseGitHubConnectionReturn } from "../github/useGitHubConnection.js";
7
12
  import type { WorkspaceFileLister } from "../workspace/WorkspaceFileLister.js";
13
+ import type { WorkspaceFileReader } from "../workspace/WorkspaceFileReader.js";
14
+ import type { WorkspaceContentSearcher } from "../workspace/WorkspaceContentSearcher.js";
15
+ import { resolveWorkspaceFileSelection } from "../workspace/resolveWorkspaceFileSelection.js";
16
+ import {
17
+ WorkspaceSurface,
18
+ type SurfaceVirtualDocument,
19
+ } from "../workspace/WorkspaceSurface.js";
8
20
  import type { InteractionModeOption, SessionComposerHandle } from "../composer/index.js";
9
21
  import type { ApplyResourceResult } from "../library/useApplyResource.js";
10
22
  import { ResizableSplit } from "../internal/ResizableSplit.js";
11
23
  import { SelectionStore } from "../internal/store/selection-store.js";
24
+ import { useWorkspaceEditors, isVirtualEntryId } from "../internal/store/index.js";
12
25
  import { ThreadSelectionContext } from "../execution/ThreadSelectionContext.js";
13
26
  import { useSelectedThreadItem } from "../execution/useThreadSelection.js";
14
27
  import { MessageThread } from "../execution/MessageThread.js";
28
+ import { FileChangeProgressBar } from "../execution/FileChangeProgressBar.js";
15
29
  import { FileReviewDock } from "../execution/FileReviewDock.js";
30
+ import {
31
+ FilePathContext,
32
+ type FilePathContextValue,
33
+ } from "../execution/FilePathContext.js";
16
34
  import { ThreadSkeleton } from "../execution/ThreadSkeleton.js";
17
35
  import { SessionComposer } from "../composer/index.js";
18
36
  import { SecretFlowErrorGuide, isSecretFlowError } from "../error/index.js";
37
+ import { useStigmer } from "../hooks.js";
38
+ import {
39
+ findLatestSessionPlan,
40
+ findPlanArtifact,
41
+ type SessionPlan,
42
+ } from "../library/detect-plan-artifact.js";
43
+ import { findStreamingPlan } from "../library/detect-streaming-plan.js";
19
44
  import { useSessionPageFlow } from "./useSessionPageFlow.js";
20
- import { SessionInspector } from "./inspector/SessionInspector.js";
45
+ import { useOpenFileChange } from "./useOpenFileChange.js";
46
+ import { usePlanDraft, planDraftKey, type PlanDraftController } from "./usePlanDraft.js";
47
+ import { PlanEditor } from "./PlanEditor.js";
48
+ import { PlanStreamingDocument } from "./PlanStreamingDocument.js";
49
+ import { PLAN_DOCUMENT_ENTRY_ID, PLAN_DOCUMENT_PATH } from "./plan-document.js";
50
+ import { useSessionRailViews } from "./useSessionRailViews.js";
51
+ import { useSessionPanel, type SessionPanelController } from "./useSessionPanel.js";
52
+ import { SessionPanelChip } from "./SessionPanelChip.js";
53
+ import { useSessionWriteBacks } from "./useSessionWriteBacks.js";
54
+ import { useSessionArtifacts } from "./useSessionArtifacts.js";
55
+ import type { SetupTabProps } from "./facets/SetupTab.js";
56
+ import { ExecutionPhase } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
21
57
  import type { RuntimeEnvProvider } from "./runtime-env.js";
22
58
  import type { SessionAudience } from "./audience.js";
23
59
 
24
60
  /**
25
- * Message submitted when the user implements a plan. References the published
26
- * `plan.md` explicitly so the agent acts on the durable artifact rather than
27
- * relying on re-reading its own prior chat message.
61
+ * Where the approved plan mounts in the implement execution's workspace
62
+ * the harnesses' standard attachment inputs directory, where the runner's
63
+ * implement-plan directive points the agent (shared/implement-plan-prompt.ts).
64
+ * Attaching under the plan's own filename (e.g. `feature-x_a1b2c3d4.plan.md`) is what
65
+ * makes the build implement the document the user approved (including in-place
66
+ * edits), not a paraphrase from memory; the runner detects it by the plan
67
+ * filename convention (`isPlanArtifactName`), so the exact name is free to vary.
28
68
  */
29
- const IMPLEMENT_PLAN_MESSAGE =
30
- "Implement the plan above (saved as plan.md). Follow it step by step and make the changes it describes.";
69
+ function approvedPlanMountPath(planFileName: string): string {
70
+ return `.stigmer/inputs/${planFileName}`;
71
+ }
72
+
73
+ /**
74
+ * The build turn's user message — a short human-readable label, NOT the
75
+ * implement instruction. The agent-facing instruction is runner-injected
76
+ * (keyed off `executionConfig.buildFromPlan`). The thread hides the turn
77
+ * entirely from the same flag (the plan card above it is the visible cause);
78
+ * this label exists for surfaces without that treatment (the CLI, execution
79
+ * history, older clients), which show it as-is.
80
+ */
81
+ const BUILD_FROM_PLAN_MESSAGE = "Build from plan";
82
+
83
+ /**
84
+ * A plan the active execution is writing right now, resolved at the viewer
85
+ * level (from `findStreamingPlan`) so the panel's plan tab can render it
86
+ * live. The streaming sibling of {@link SessionPlan}: `executionId` keys the
87
+ * document (a new turn's stream resets the tab's view state) and forms the
88
+ * `<executionId>:streaming` identity for the plan-tab auto-open trigger.
89
+ */
90
+ interface SessionStreamingPlan {
91
+ /** ID of the execution writing the plan. */
92
+ readonly executionId: string;
93
+ /** The live plan text (fence-stripped display projection). */
94
+ readonly displayText: string;
95
+ }
96
+
97
+ /**
98
+ * Reads the published plan's full text for the build handoff. Refuses a
99
+ * truncated read (content RPC caps at 512 KB): attaching a partial plan would
100
+ * silently implement half a document — the caller falls back to
101
+ * conversation-history implement instead.
102
+ */
103
+ async function fetchPlanText(
104
+ stigmer: ReturnType<typeof useStigmer>,
105
+ plan: SessionPlan,
106
+ ): Promise<string> {
107
+ const result = await stigmer.agentExecution.getArtifactContent(
108
+ create(GetArtifactContentRequestSchema, {
109
+ executionId: plan.executionId,
110
+ storageKey: plan.artifact.storageKey,
111
+ }),
112
+ );
113
+ if (result.truncated) {
114
+ throw new Error("plan content truncated — not attaching a partial plan");
115
+ }
116
+ return new TextDecoder().decode(result.content);
117
+ }
118
+
119
+ /**
120
+ * Width and anchoring of the conversation reading column, shared by the
121
+ * thread and the composer/banners block so they can never drift apart.
122
+ *
123
+ * Centered (the classic chat reading view, matching Cursor's agent panel):
124
+ * with the session panel collapsed the column sits in the middle of the
125
+ * row; opening the panel narrows the chat pane below the column max, so
126
+ * the column simply fills it. The thread applies the same geometry via
127
+ * `contentColumn="center"`, which adds the item gutter; the composer/banner
128
+ * block's children carry their own edge padding, so only width and
129
+ * anchoring live here.
130
+ */
131
+ const CONVERSATION_COLUMN_CLASS = "mx-auto w-full max-w-3xl";
31
132
 
32
133
  /** Props for {@link SessionViewer}. */
33
134
  export interface SessionViewerProps {
@@ -70,6 +171,21 @@ export interface SessionViewerProps {
70
171
  * expandable file tree. (DD-004 capability injection, DD-011 opt-in.)
71
172
  */
72
173
  readonly workspaceFileLister?: WorkspaceFileLister;
174
+ /**
175
+ * Platform-injected content reader for the read-only file viewer. When
176
+ * provided, clicking a file in the Workspace tree opens it in a contextual
177
+ * "Viewer" tab. GitHub Contents/blob on web, Tauri fs on desktop; `undefined`
178
+ * degrades the viewer to an "unavailable here" state (DD-004, DD-011 opt-in).
179
+ */
180
+ readonly workspaceFileReader?: WorkspaceFileReader;
181
+ /**
182
+ * Platform-injected content (text) searcher. When provided, the session
183
+ * panel's Search pane gains a `Name | Text` toggle for full-text search
184
+ * across the workspace. Desktop injects a native ripgrep-backed searcher;
185
+ * web leaves it undefined (git content search needs a branch-accurate
186
+ * backend — DD-09), keeping Search filename-only there (DD-004, DD-011 opt-in).
187
+ */
188
+ readonly workspaceContentSearcher?: WorkspaceContentSearcher;
73
189
  /**
74
190
  * Supplies host-app environment variables for every follow-up
75
191
  * execution (e.g. short-lived credentials for MCP tools, minted as
@@ -92,11 +208,18 @@ export interface SessionViewerProps {
92
208
  */
93
209
  readonly audience?: SessionAudience;
94
210
  /**
95
- * Slot for host-injected header actions (e.g., Share button with
96
- * PermissionGate). Rendered in the top-right corner of the viewer.
97
- * Keeps the SDK organism unopinionated about Console auth (DD-004).
211
+ * Slot for host-injected header actions. Rendered in the top-right corner
212
+ * of the viewer, beside the panel chip. Keeps the SDK organism
213
+ * unopinionated about Console auth (DD-004).
98
214
  */
99
215
  readonly headerActions?: ReactNode;
216
+ /**
217
+ * Host-injected access management control (e.g. the Console's
218
+ * `ManageAccessButton` with its own permission gating). Rendered inside
219
+ * the panel's Config facet rather than the header — access is session
220
+ * configuration, not a moment-to-moment action (DD-004 slot injection).
221
+ */
222
+ readonly accessSlot?: ReactNode;
100
223
  /** Called after a resource is applied from the Artifacts tab. */
101
224
  readonly onApplied?: (result: ApplyResourceResult) => void;
102
225
  /** Additional CSS classes for the root container. */
@@ -110,10 +233,13 @@ export interface SessionViewerProps {
110
233
  * Owns `useSessionPageFlow` internally and composes:
111
234
  * - **Conversation column** (primary): `MessageThread` + error
112
235
  * banners + `SessionComposer`
113
- * - **SessionInspector** (secondary): tabbed panel with Workspace,
114
- * Config, Changes, Artifacts, Usage, and Inspect facets
236
+ * - **Session panel** (secondary): the unified `WorkspaceSurface` — explorer,
237
+ * search, read-only editor, and the session facets (Config, Changes,
238
+ * Artifacts, Usage, Inspect) as injected rail views. Collapsed by default
239
+ * to a top-right chip; opening a file (tree, search, or a transcript path)
240
+ * expands it.
115
241
  *
116
- * Connected via `ResizableSplit` with persisted width.
242
+ * Connected via `ResizableSplit` with persisted chat width.
117
243
  *
118
244
  * Thread selection is provided via `ThreadSelectionContext` (Phase 2
119
245
  * opt-in) for render-isolated per-item selection.
@@ -150,9 +276,12 @@ export function SessionViewer({
150
276
  enableLocal = false,
151
277
  onBrowseLocalFolder,
152
278
  workspaceFileLister,
279
+ workspaceFileReader,
280
+ workspaceContentSearcher,
153
281
  getRuntimeEnv,
154
282
  audience = "integrator",
155
283
  headerActions,
284
+ accessSlot,
156
285
  onApplied,
157
286
  className,
158
287
  }: SessionViewerProps) {
@@ -170,16 +299,182 @@ export function SessionViewer({
170
299
  }
171
300
  const selectionStore = selectionStoreRef.current;
172
301
 
302
+ const phase =
303
+ flow.displayExecution?.status?.phase ??
304
+ ExecutionPhase.EXECUTION_PHASE_UNSPECIFIED;
305
+ const hasPhase = phase !== ExecutionPhase.EXECUTION_PHASE_UNSPECIFIED;
306
+
307
+ // Facet arrival counts: rail badges inside the panel, the chip's dot-count
308
+ // while collapsed. Arrivals never auto-open the panel (badge-only signal).
309
+ const { hasWriteBacks, writeBackCount } = useSessionWriteBacks(flow.allExecutions);
310
+ const { artifactCount } = useSessionArtifacts(flow.allExecutions);
311
+
312
+ // The session's current plan (latest published plan.md across executions)
313
+ // and its viewer-owned draft. Ownership at this level is deliberate: the
314
+ // panel unmounts facet content on every view switch, so a facet-local
315
+ // draft would silently lose the user's edits (see usePlanDraft).
316
+ const stigmer = useStigmer();
317
+ const sessionPlan = useMemo(
318
+ () => findLatestSessionPlan(flow.allExecutions),
319
+ [flow.allExecutions],
320
+ );
321
+ const planDraft = usePlanDraft(sessionPlan);
322
+
323
+ // The plan the ACTIVE turn is writing right now (the H1 convention — see
324
+ // findStreamingPlan), resolved at this level for the same reason
325
+ // sessionPlan is: the panel's plan tab renders it. Recomputed per stream
326
+ // commit by design — this component already re-renders on every commit
327
+ // (it reads `flow`), and the scan touches only the last AI message.
328
+ const streamingPlan = useMemo<SessionStreamingPlan | undefined>(() => {
329
+ const activeExec = conv.activeStreamExecution;
330
+ const live = findStreamingPlan(activeExec);
331
+ const executionId = activeExec?.metadata?.id;
332
+ return live && executionId
333
+ ? { executionId, displayText: live.displayText }
334
+ : undefined;
335
+ }, [conv.activeStreamExecution]);
336
+ const [isBuildingFromPlan, setIsBuildingFromPlan] = useState(false);
337
+ const [planAttachFailed, setPlanAttachFailed] = useState(false);
338
+
339
+ // WHICH plan the panel's plan document tab shows. `null` means the session's
340
+ // current (latest) plan — the editable, buildable one; an execution id
341
+ // selects that turn's historical plan, rendered read-only. One tab, host-
342
+ // controlled content (Decision 3 in DD-16).
343
+ const [openPlanExecutionId, setOpenPlanExecutionId] = useState<string | null>(null);
344
+
345
+ // The plan tab's identity across both plan lifecycles: while a plan
346
+ // streams, the in-flight turn owns the tab (`<executionId>:streaming`);
347
+ // once published, the artifact identity (planDraftKey) takes over. Each
348
+ // transition is a NEW identity, so useSessionPanel's planKey trigger
349
+ // auto-opens the tab the moment a plan starts streaming AND (idempotently)
350
+ // when it settles into the published plan.
351
+ const currentPlanKey = sessionPlan ? planDraftKey(sessionPlan) : null;
352
+ const panelPlanKey = streamingPlan
353
+ ? `${streamingPlan.executionId}:streaming`
354
+ : currentPlanKey;
355
+
356
+ // A new plan identity (a plan starting to stream, the first published
357
+ // plan, or a refinement) supersedes any historical plan the user had
358
+ // opened: the tab snaps back to "latest" so the auto-open always surfaces
359
+ // the incoming plan. Adjust-state-during-render — own state only, the
360
+ // established idiom.
361
+ const [prevPlanKey, setPrevPlanKey] = useState(panelPlanKey);
362
+ if (panelPlanKey !== prevPlanKey) {
363
+ setPrevPlanKey(panelPlanKey);
364
+ setOpenPlanExecutionId(null);
365
+ }
366
+
367
+ // The unified-panel controller: owns the open-editor group store, the
368
+ // open/collapsed state, and the rail-view FSM. Shared with the launcher
369
+ // (DD-016). The editor store is owned here (never subscribed at this level)
370
+ // so opening/switching files re-renders only the panel subtree — the
371
+ // SessionPanelRegion subscribes, not the conversation column — preserving
372
+ // streaming render isolation (DD-009/DD-010, invariant 2).
373
+ const panel = useSessionPanel({
374
+ phase: hasPhase ? phase : null,
375
+ hasChanges: hasWriteBacks,
376
+ planKey: panelPlanKey,
377
+ });
378
+
173
379
  const handleBuildFromPlan = useCallback(() => {
174
380
  // Switch the picker to Agent (so subsequent turns stay in Agent) and submit
175
- // the implement message immediately through the composer's full pipeline.
381
+ // the implement message through the composer's full pipeline.
176
382
  // `interactionMode: "agent"` is passed explicitly to win the same-tick race
177
383
  // where the composer prop has not yet re-rendered from "plan".
178
384
  setInteractionMode("agent");
179
- composerRef.current?.submit(IMPLEMENT_PLAN_MESSAGE, {
180
- interactionMode: "agent",
181
- });
182
- }, [setInteractionMode]);
385
+ setPlanAttachFailed(false);
386
+
387
+ // `buildFromPlan` (not message text) is what tells the runner to inject
388
+ // the implement directive and the thread to hide the turn's message —
389
+ // with or without the attachment (the runner picks the directive variant).
390
+ const submitImplementTurn = (attachments?: AttachmentInput[]) => {
391
+ composerRef.current?.submit(BUILD_FROM_PLAN_MESSAGE, {
392
+ interactionMode: "agent",
393
+ buildFromPlan: true,
394
+ attachments,
395
+ });
396
+ };
397
+
398
+ // No published artifact (the bare-CTA fallback card) — nothing to attach.
399
+ if (!sessionPlan) {
400
+ submitImplementTurn();
401
+ return;
402
+ }
403
+
404
+ // Deterministic handoff: upload the APPROVED plan (draft if edited, else
405
+ // the artifact text — one uniform path) and attach it to the implement
406
+ // execution. The published artifact itself stays immutable; the approved
407
+ // copy is a new input on the build turn (edit-as-input provenance).
408
+ setIsBuildingFromPlan(true);
409
+ // The approved copy keeps the published plan's filename, so the mounted
410
+ // input, the download, and the plan card all agree on one name.
411
+ const planFileName = sessionPlan.artifact.name;
412
+ void (async () => {
413
+ try {
414
+ const approvedText =
415
+ planDraft.readDraft() ?? (await fetchPlanText(stigmer, sessionPlan));
416
+ const response = await stigmer.agentExecution.uploadAttachment(
417
+ create(UploadAttachmentRequestSchema, {
418
+ filename: planFileName,
419
+ content: new TextEncoder().encode(approvedText),
420
+ contentType: "text/markdown",
421
+ }),
422
+ );
423
+ submitImplementTurn([
424
+ {
425
+ filename: planFileName,
426
+ storageKey: response.storageKey,
427
+ mountPath: approvedPlanMountPath(planFileName),
428
+ contentType: "text/markdown",
429
+ },
430
+ ]);
431
+ } catch {
432
+ // Attachment plumbing must never block the build: implement from
433
+ // conversation history alone, and say so (non-blocking notice).
434
+ setPlanAttachFailed(true);
435
+ submitImplementTurn();
436
+ } finally {
437
+ setIsBuildingFromPlan(false);
438
+ }
439
+ })();
440
+ }, [setInteractionMode, sessionPlan, planDraft.readDraft, stigmer]);
441
+
442
+ // "Open plan" (a thread plan card, or the Artifacts facet's plan.md) → the
443
+ // panel's plan document tab. Opening the LATEST plan clears the historical
444
+ // selection (the tab shows the editable current plan); any other execution
445
+ // id opens that turn's plan read-only in the same tab.
446
+ const handleOpenPlan = useCallback(
447
+ (executionId: string) => {
448
+ setOpenPlanExecutionId(
449
+ sessionPlan && executionId === sessionPlan.executionId
450
+ ? null
451
+ : executionId,
452
+ );
453
+ panel.openPlanDocument();
454
+ },
455
+ [sessionPlan, panel.openPlanDocument],
456
+ );
457
+
458
+ // Open a transcript tool-call file path in the panel's read-only editor.
459
+ // Resolves the (possibly absolute / subdir-prefixed) path to a
460
+ // repo/root-relative selection the editor can fetch; on a definite hit it
461
+ // opens the file (expanding the panel) and returns `true` (handled),
462
+ // otherwise returns `false` so the path keeps its default copy / GitHub-link
463
+ // behavior. Writing the editor store re-renders only the panel subtree
464
+ // (which subscribes), never this streaming column.
465
+ const handleTranscriptFilePathClick = useCallback(
466
+ (path: string): boolean => {
467
+ const selection = resolveWorkspaceFileSelection(
468
+ path,
469
+ flow.workspace.entries,
470
+ flow.sandboxWorkspaceRoot,
471
+ );
472
+ if (!selection) return false;
473
+ panel.openFile(selection.entryId, selection.path);
474
+ return true;
475
+ },
476
+ [flow.workspace.entries, flow.sandboxWorkspaceRoot, panel.openFile],
477
+ );
183
478
 
184
479
  if (conv.isLoading) {
185
480
  return (
@@ -199,22 +494,36 @@ export function SessionViewer({
199
494
 
200
495
  return (
201
496
  <div className={cn("relative flex h-full w-full flex-col", className)}>
202
- {headerActions && (
203
- <div className="absolute top-2 right-6 z-10">
204
- {headerActions}
205
- </div>
206
- )}
497
+ {/* Top-right controls: host actions + the panel chip. The chip is the
498
+ panel's always-mounted toggle; while collapsed it carries only the
499
+ pending-item count. Execution status is never surfaced as header
500
+ chrome — the thread itself communicates run state. */}
501
+ <div className="absolute top-2 right-6 z-10 flex items-center gap-2">
502
+ {headerActions}
503
+ <SessionPanelChip
504
+ isOpen={panel.isOpen}
505
+ onToggle={panel.isOpen ? panel.closePanel : panel.openPanel}
506
+ badgeCount={writeBackCount + artifactCount}
507
+ />
508
+ </div>
207
509
 
208
510
  <ThreadSelectionContext.Provider value={selectionStore}>
511
+ {/* One layout, collapsed by default: chat fills the row until the panel
512
+ opens, then becomes the fixed narrow pane on the left while the
513
+ panel takes the flexible region. Collapsing goes through the
514
+ split's `collapsedPane` (CSS, not conditional structure), so the
515
+ conversation is always the same first child and an open/close never
516
+ remounts it (invariant 1). */}
209
517
  <ResizableSplit
210
- defaultSize={384}
211
- minSize={280}
212
- maxSize={600}
213
- storageKey="stgm-session-inspector-width"
214
- className={cn(
215
- "min-h-0 flex-1",
216
- "[&>*:nth-child(2)]:max-lg:hidden [&>*:nth-child(3)]:max-lg:hidden",
217
- )}
518
+ resizablePane="primary"
519
+ collapsedPane={panel.isOpen ? "none" : "secondary"}
520
+ defaultSize={420}
521
+ minSize={320}
522
+ maxSize={640}
523
+ storageKey="stgm-session-chat-width"
524
+ responsiveCollapse={panel.isOpen ? "primary" : "none"}
525
+ ariaLabel="Resize chat panel"
526
+ className="min-h-0 flex-1"
218
527
  primary={
219
528
  <ConversationColumn
220
529
  flow={flow}
@@ -229,23 +538,37 @@ export function SessionViewer({
229
538
  enableLocal={enableLocal}
230
539
  onBrowseLocalFolder={onBrowseLocalFolder}
231
540
  onBuildFromPlan={handleBuildFromPlan}
541
+ onOpenPlan={handleOpenPlan}
542
+ isBuildingFromPlan={isBuildingFromPlan}
543
+ planAttachFailed={planAttachFailed}
544
+ onDismissPlanAttachFailed={() => setPlanAttachFailed(false)}
545
+ onFilePathClick={handleTranscriptFilePathClick}
232
546
  isEndUser={isEndUser}
233
547
  />
234
548
  }
235
549
  secondary={
236
- <InspectorPanel
237
- flow={flow}
238
- org={org}
239
- selectionStore={selectionStore}
240
- onApplied={onApplied}
241
- onImplementPlan={handleBuildFromPlan}
242
- enableGitHub={enableGitHub}
243
- enableLocal={enableLocal}
244
- gitHubConnection={gitHubConnection}
245
- onBrowseLocalFolder={onBrowseLocalFolder}
246
- workspaceFileLister={workspaceFileLister}
247
- isEndUser={isEndUser}
248
- />
550
+ panel.isOpen ? (
551
+ <SessionPanelRegion
552
+ flow={flow}
553
+ org={org}
554
+ panel={panel}
555
+ accessSlot={accessSlot}
556
+ onApplied={onApplied}
557
+ onImplementPlan={handleBuildFromPlan}
558
+ implementPlanDisabled={!conv.canSendFollowUp || isBuildingFromPlan}
559
+ sessionPlan={sessionPlan}
560
+ streamingPlan={streamingPlan}
561
+ planDraft={planDraft}
562
+ openPlanExecutionId={openPlanExecutionId}
563
+ onOpenPlan={handleOpenPlan}
564
+ enableLocal={enableLocal}
565
+ onBrowseLocalFolder={onBrowseLocalFolder}
566
+ workspaceFileLister={workspaceFileLister}
567
+ workspaceFileReader={workspaceFileReader}
568
+ workspaceContentSearcher={workspaceContentSearcher}
569
+ isEndUser={isEndUser}
570
+ />
571
+ ) : null
249
572
  }
250
573
  />
251
574
  </ThreadSelectionContext.Provider>
@@ -270,10 +593,23 @@ interface ConversationColumnProps {
270
593
  readonly enableLocal: boolean;
271
594
  readonly onBrowseLocalFolder?: () => Promise<string | null>;
272
595
  readonly onBuildFromPlan: () => void;
596
+ /** Opens a plan in the panel's plan document tab (thread card "Open plan"). */
597
+ readonly onOpenPlan: (executionId: string) => void;
598
+ /** True while the approved plan is being uploaded ahead of the build turn. */
599
+ readonly isBuildingFromPlan: boolean;
600
+ /** True when the last build fell back to a message-only implement. */
601
+ readonly planAttachFailed: boolean;
602
+ readonly onDismissPlanAttachFailed: () => void;
603
+ /**
604
+ * Opens a transcript tool-call file path in the Viewer. Returns `true` when it
605
+ * resolved and opened the file (suppressing the link's default), `false` to
606
+ * let the path keep its copy / GitHub-link behavior.
607
+ */
608
+ readonly onFilePathClick: (path: string) => boolean;
273
609
  readonly isEndUser: boolean;
274
610
  }
275
611
 
276
- function ConversationColumn({
612
+ const ConversationColumn = memo(function ConversationColumn({
277
613
  flow,
278
614
  modelId,
279
615
  setModelId,
@@ -286,6 +622,11 @@ function ConversationColumn({
286
622
  enableLocal,
287
623
  onBrowseLocalFolder,
288
624
  onBuildFromPlan,
625
+ onOpenPlan,
626
+ isBuildingFromPlan,
627
+ planAttachFailed,
628
+ onDismissPlanAttachFailed,
629
+ onFilePathClick,
289
630
  isEndUser,
290
631
  }: ConversationColumnProps) {
291
632
  const { conv } = flow;
@@ -325,6 +666,20 @@ function ConversationColumn({
325
666
  [conv.stop, composerRef],
326
667
  );
327
668
 
669
+ // The composer-docked FileReviewDock sits OUTSIDE MessageThread, so the
670
+ // thread's own FilePathContext provider does not reach it. This provider
671
+ // gives the dock's file list the same path resolution and click routing
672
+ // (open in the panel's viewer; GitHub/copy fallback) as the transcript —
673
+ // one click behavior for a path everywhere in the session. Memoized so the
674
+ // dock's memoized cards are not invalidated by unrelated renders (DD-010).
675
+ const dockFilePathCtx = useMemo<FilePathContextValue>(
676
+ () => ({
677
+ workspaceEntries: conv.workspaceEntries ?? [],
678
+ onFilePathClick,
679
+ }),
680
+ [conv.workspaceEntries, onFilePathClick],
681
+ );
682
+
328
683
  return (
329
684
  <div className="flex h-full min-w-0 flex-col">
330
685
  <MessageThread
@@ -340,14 +695,20 @@ function ConversationColumn({
340
695
  showFileReviewRecords
341
696
  onEditMessage={conv.isStoppable ? handleEditMessage : undefined}
342
697
  workspaceEntries={conv.workspaceEntries}
698
+ onFilePathClick={onFilePathClick}
343
699
  sandboxWorkspaceRoot={flow.sandboxWorkspaceRoot}
344
700
  onBuildFromPlan={onBuildFromPlan}
701
+ onOpenPlan={onOpenPlan}
345
702
  org={org}
346
- planActionsDisabled={!conv.canSendFollowUp}
347
- centerContent
703
+ planActionsDisabled={!conv.canSendFollowUp || isBuildingFromPlan}
704
+ planBuildPending={isBuildingFromPlan}
705
+ contentColumn="center"
348
706
  className="flex-1"
349
707
  />
350
- <div className="mx-auto w-full max-w-3xl">
708
+ <div className={CONVERSATION_COLUMN_CLASS}>
709
+ {planAttachFailed && (
710
+ <PlanAttachFailedNotice onDismiss={onDismissPlanAttachFailed} />
711
+ )}
351
712
  {conv.isReconnecting && <ReconnectingIndicator />}
352
713
  {conv.connectTimedOut && (
353
714
  <ConnectTimedOutBanner onRetry={conv.reconnectStream} />
@@ -367,12 +728,19 @@ function ConversationColumn({
367
728
  decision the agent is blocked on can never scroll out of view. The
368
729
  thread renders only observational rows (badges) and read-only
369
730
  settled records; this is the one decision surface. */}
370
- <FileReviewDock
371
- changeSets={conv.fileChangeSets}
372
- onSubmit={conv.submitFileDecision}
373
- submittingDecisionKeys={conv.submittingFileDecisionKeys}
374
- decisionErrors={conv.fileDecisionErrors}
375
- />
731
+ <FilePathContext.Provider value={dockFilePathCtx}>
732
+ {/* Mid-run live capture (DD-32): the "N files changed so far" strip for
733
+ a still-running turn. Mutually exclusive with the dock below —
734
+ progress shows while CAPTURING, the dock once AWAITING_REVIEW — so it
735
+ hands off cleanly when review opens. Non-interactive. */}
736
+ <FileChangeProgressBar progress={conv.fileChangeProgress} />
737
+ <FileReviewDock
738
+ changeSets={conv.fileChangeSets}
739
+ onSubmit={conv.submitFileDecision}
740
+ submittingDecisionKeys={conv.submittingFileDecisionKeys}
741
+ decisionErrors={conv.fileDecisionErrors}
742
+ />
743
+ </FilePathContext.Provider>
376
744
  <SessionComposer
377
745
  ref={composerRef}
378
746
  onSubmit={flow.handleSubmit}
@@ -407,41 +775,165 @@ function ConversationColumn({
407
775
  </div>
408
776
  </div>
409
777
  );
410
- }
778
+ });
411
779
 
412
780
  // ---------------------------------------------------------------------------
413
- // Inspector panel (right/secondary) — reads selection from store
781
+ // Session panel region (right/secondary) — the unified panel's subtree
414
782
  // ---------------------------------------------------------------------------
415
783
 
416
- interface InspectorPanelProps {
784
+ interface SessionPanelRegionProps {
417
785
  readonly flow: ReturnType<typeof useSessionPageFlow>;
418
786
  readonly org: string;
419
- readonly selectionStore: SelectionStore;
787
+ /**
788
+ * The panel controller owned by `SessionViewer`. Its editor store is
789
+ * subscribed here (not at `SessionViewer`) so opening/switching files
790
+ * re-renders only this region, never the conversation column.
791
+ */
792
+ readonly panel: SessionPanelController;
793
+ /** Host access management control, surfaced in the Config facet. */
794
+ readonly accessSlot?: ReactNode;
420
795
  readonly onApplied?: (result: ApplyResourceResult) => void;
421
- /** Implement a plan from the Artifacts tab (same action as the thread card). */
796
+ /** Implement a plan (plan tab primary + Artifacts preview action). */
422
797
  readonly onImplementPlan?: () => void;
423
- readonly enableGitHub: boolean;
798
+ readonly implementPlanDisabled?: boolean;
799
+ /** The session's latest plan — the plan tab's editable content. */
800
+ readonly sessionPlan?: SessionPlan;
801
+ /**
802
+ * The plan the active turn is writing right now. While present it OWNS the
803
+ * plan tab: the tab renders the live document (`PlanStreamingDocument`)
804
+ * instead of a published plan — the in-flight plan is the newest
805
+ * deliverable, and the snap-back in `SessionViewer` has already cleared any
806
+ * historical selection when this identity appeared.
807
+ */
808
+ readonly streamingPlan?: SessionStreamingPlan;
809
+ /** Viewer-owned plan draft (survives panel collapse and view switches). */
810
+ readonly planDraft: PlanDraftController;
811
+ /**
812
+ * Which plan the plan document tab shows: `null` for the latest (editable),
813
+ * or an execution id whose historical plan renders read-only.
814
+ */
815
+ readonly openPlanExecutionId: string | null;
816
+ /** Opens a plan in the plan tab (routed to the Artifacts facet's plan.md). */
817
+ readonly onOpenPlan: (executionId: string) => void;
424
818
  readonly enableLocal: boolean;
425
- readonly gitHubConnection?: UseGitHubConnectionReturn;
426
819
  readonly onBrowseLocalFolder?: () => Promise<string | null>;
427
820
  readonly workspaceFileLister?: WorkspaceFileLister;
821
+ readonly workspaceFileReader?: WorkspaceFileReader;
822
+ readonly workspaceContentSearcher?: WorkspaceContentSearcher;
428
823
  readonly isEndUser: boolean;
429
824
  }
430
825
 
431
- function InspectorPanel({
826
+ function SessionPanelRegion({
432
827
  flow,
433
828
  org,
434
- selectionStore,
829
+ panel,
830
+ accessSlot,
435
831
  onApplied,
436
832
  onImplementPlan,
437
- enableGitHub,
833
+ implementPlanDisabled,
834
+ sessionPlan,
835
+ streamingPlan,
836
+ planDraft,
837
+ openPlanExecutionId,
838
+ onOpenPlan,
438
839
  enableLocal,
439
- gitHubConnection,
440
840
  onBrowseLocalFolder,
441
841
  workspaceFileLister,
842
+ workspaceFileReader,
843
+ workspaceContentSearcher,
442
844
  isEndUser,
443
- }: InspectorPanelProps) {
845
+ }: SessionPanelRegionProps) {
444
846
  const selectedItem = useSelectedThreadItem();
847
+ const { editors, activeKey, activeFile, reveal } = useWorkspaceEditors(
848
+ panel.editorsStore,
849
+ );
850
+
851
+ // Honor a pending jump-to-line reveal only while it targets the active editor
852
+ // (an EditorReveal is structurally a RevealTarget). Switching tabs changes
853
+ // activeKey, so a stale reveal for another tab is naturally ignored.
854
+ const activeReveal =
855
+ reveal && reveal.key === activeKey ? reveal : undefined;
856
+
857
+ // This region is the level that subscribes to thread selection; report it to
858
+ // the controller so an open panel can auto-surface the Inspect view. (A
859
+ // collapsed panel never sees — and deliberately never reacts to — selection.)
860
+ useEffect(() => {
861
+ panel.notifySelection(selectedItem);
862
+ }, [selectedItem, panel.notifySelection]);
863
+
864
+ // Correlate the active file with its session change for diff-as-default
865
+ // (DD-06 parity with the transcript's rendering of the same change). A
866
+ // virtual document (the plan tab) is not a workspace file — pass `null` so
867
+ // the hook's no-file guard skips the net-change fold entirely, rather than
868
+ // relying on the correlation harmlessly missing the sentinel id.
869
+ const openFileChange = useOpenFileChange(
870
+ activeFile && !isVirtualEntryId(activeFile.entryId) ? activeFile : null,
871
+ flow.allExecutions,
872
+ flow.workspace.entries,
873
+ flow.sandboxWorkspaceRoot,
874
+ );
875
+
876
+ // Resolve WHICH plan the plan document tab shows: the latest (editable,
877
+ // buildable, draft-backed) unless a historical execution id was opened —
878
+ // that plan renders read-only. A stale id (execution gone, or its plan
879
+ // artifact missing) degrades to the latest rather than an empty tab.
880
+ const openPlan = useMemo<SessionPlan | undefined>(() => {
881
+ if (
882
+ openPlanExecutionId === null ||
883
+ openPlanExecutionId === sessionPlan?.executionId
884
+ ) {
885
+ return sessionPlan;
886
+ }
887
+ const execution = flow.allExecutions.find(
888
+ (e) => e.metadata?.id === openPlanExecutionId,
889
+ );
890
+ const artifact = findPlanArtifact(execution);
891
+ return artifact
892
+ ? { executionId: openPlanExecutionId, artifact }
893
+ : sessionPlan;
894
+ }, [openPlanExecutionId, sessionPlan, flow.allExecutions]);
895
+
896
+ // The plan document tab (SurfaceVirtualDocument): the panel's editor-area
897
+ // rendering of the session's plan. Three states, in precedence order:
898
+ // a STREAMING plan owns the tab (live document, no actions); otherwise the
899
+ // resolved `openPlan` renders in the editor (keyed by plan identity so
900
+ // switching plans resets view state cleanly); otherwise an honest empty
901
+ // notice (DD-006) — reachable only when a streaming plan auto-opened the
902
+ // tab and its turn then ended without publishing, with no earlier plan to
903
+ // fall back to.
904
+ const openPlanIsLatest = openPlan?.executionId === sessionPlan?.executionId;
905
+ const virtualDocuments = useMemo<readonly SurfaceVirtualDocument[]>(() => {
906
+ return [
907
+ {
908
+ entryId: PLAN_DOCUMENT_ENTRY_ID,
909
+ path: PLAN_DOCUMENT_PATH,
910
+ content: streamingPlan ? (
911
+ <PlanStreamingDocument
912
+ key={`streaming-${streamingPlan.executionId}`}
913
+ displayText={streamingPlan.displayText}
914
+ />
915
+ ) : openPlan ? (
916
+ <PlanEditor
917
+ key={planDraftKey(openPlan)}
918
+ plan={openPlan}
919
+ draft={openPlanIsLatest ? planDraft : undefined}
920
+ onBuildFromPlan={openPlanIsLatest ? onImplementPlan : undefined}
921
+ buildDisabled={implementPlanDisabled}
922
+ readOnly={!openPlanIsLatest}
923
+ />
924
+ ) : (
925
+ <PlanUnavailableNotice />
926
+ ),
927
+ },
928
+ ];
929
+ }, [
930
+ streamingPlan,
931
+ openPlan,
932
+ openPlanIsLatest,
933
+ planDraft,
934
+ onImplementPlan,
935
+ implementPlanDisabled,
936
+ ]);
445
937
 
446
938
  const handleRemoveAgent = useCallback(() => {
447
939
  flow.setAgentRef(null);
@@ -470,7 +962,7 @@ function InspectorPanel({
470
962
  [flow.skillRefs, flow.setSkillRefs],
471
963
  );
472
964
 
473
- const sessionConfig = useMemo(
965
+ const sessionConfig = useMemo<SetupTabProps>(
474
966
  () => ({
475
967
  agentRef: flow.agentRef,
476
968
  isDefaultAgent: flow.isDefaultAgent,
@@ -480,8 +972,8 @@ function InspectorPanel({
480
972
  harness: flow.harness,
481
973
  executionTarget: flow.executionTarget,
482
974
  modelId: flow.model[0],
483
- // End users see the configuration but cannot strip it — the Setup
484
- // tab renders read-only without mutation callbacks (DD-011).
975
+ // End users see the configuration but cannot strip it — the Config
976
+ // facet renders read-only without mutation callbacks (DD-011).
485
977
  mutations: isEndUser
486
978
  ? undefined
487
979
  : {
@@ -489,42 +981,59 @@ function InspectorPanel({
489
981
  onRemoveMcp: handleRemoveMcp,
490
982
  onRemoveSkill: handleRemoveSkill,
491
983
  },
984
+ accessSlot,
492
985
  }),
493
986
  [
494
987
  flow.agentRef, flow.isDefaultAgent, flow.mcpServerUsages, flow.skillRefs,
495
988
  flow.sessionVariables, flow.harness, flow.executionTarget, flow.model,
496
989
  isEndUser, handleRemoveAgent, handleRemoveMcp, handleRemoveSkill,
990
+ accessSlot,
497
991
  ],
498
992
  );
499
993
 
500
- const workspaceConfig = useMemo(
501
- () => ({
502
- actions: {
503
- workspace: flow.workspace,
504
- enableGitHub,
505
- enableLocal,
506
- gitHubConnection,
507
- onBrowseLocalFolder,
508
- workspaceFileLister,
509
- },
510
- }),
511
- [flow.workspace, enableGitHub, enableLocal, gitHubConnection, onBrowseLocalFolder, workspaceFileLister],
512
- );
994
+ // The session facets (Config / Changes / Artifacts / Usage / Inspect) as
995
+ // injected rail views — the full inspector feature set inside one panel.
996
+ const railViews = useSessionRailViews({
997
+ allExecutions: flow.allExecutions,
998
+ org,
999
+ sessionConfig,
1000
+ selectedItem,
1001
+ onApplied,
1002
+ onImplementPlan,
1003
+ onOpenPlan,
1004
+ });
1005
+
1006
+ // Explorer-footer folder attach (desktop only — needs the native picker).
1007
+ // Same wiring the retired Workspace tab used.
1008
+ const canAddLocalFolder = enableLocal && !!onBrowseLocalFolder;
1009
+ const handleAddLocalFolder = useCallback(async () => {
1010
+ const path = await onBrowseLocalFolder?.();
1011
+ if (path) flow.workspace.addLocalPath(path);
1012
+ }, [onBrowseLocalFolder, flow.workspace.addLocalPath]);
513
1013
 
514
1014
  return (
515
- <aside className="flex h-full flex-col overflow-hidden">
516
- <SessionInspector
517
- displayExecution={flow.displayExecution}
518
- allExecutions={flow.allExecutions}
519
- org={org}
520
- selectedItem={selectedItem}
521
- onApplied={onApplied}
522
- onImplementPlan={onImplementPlan}
523
- sessionConfig={sessionConfig}
524
- workspaceConfig={workspaceConfig}
525
- className="min-h-0 flex-1"
526
- />
527
- </aside>
1015
+ <WorkspaceSurface
1016
+ entries={flow.workspace.entries}
1017
+ lister={workspaceFileLister}
1018
+ reader={workspaceFileReader}
1019
+ searcher={workspaceContentSearcher}
1020
+ view={panel.view}
1021
+ onViewChange={panel.setView}
1022
+ extraViews={railViews}
1023
+ virtualDocuments={virtualDocuments}
1024
+ onRemoveEntry={flow.workspace.remove}
1025
+ onAddLocalFolder={canAddLocalFolder ? handleAddLocalFolder : undefined}
1026
+ editors={editors}
1027
+ selectedFile={activeFile}
1028
+ onOpenFile={panel.openFile}
1029
+ onActivateEditor={panel.activateEditor}
1030
+ onPinEditor={panel.pinEditor}
1031
+ onCloseEditor={panel.closeEditor}
1032
+ onCollapse={panel.closePanel}
1033
+ change={openFileChange ?? undefined}
1034
+ reveal={activeReveal}
1035
+ className="h-full"
1036
+ />
528
1037
  );
529
1038
  }
530
1039
 
@@ -593,6 +1102,56 @@ function AutoApproveIndicator({ onTurnOff }: { onTurnOff: () => void }) {
593
1102
  );
594
1103
  }
595
1104
 
1105
+ /**
1106
+ * The plan document tab's empty state — reachable only when a streaming plan
1107
+ * auto-opened the tab and its turn then ended without publishing (stopped or
1108
+ * failed) while the session has no earlier published plan to fall back to.
1109
+ * The partial plan reverts to the conversation (the thread un-collapses it),
1110
+ * so the notice points there. Never a blank pane (DD-006).
1111
+ */
1112
+ function PlanUnavailableNotice() {
1113
+ return (
1114
+ <div
1115
+ role="status"
1116
+ className="mx-auto flex w-full max-w-3xl flex-col items-center gap-1 px-4 py-8 text-center"
1117
+ >
1118
+ <p className="text-xs font-medium text-foreground">
1119
+ This turn ended before a plan was completed.
1120
+ </p>
1121
+ <p className="text-xs text-muted-foreground">
1122
+ Any partial plan text remains in the conversation.
1123
+ </p>
1124
+ </div>
1125
+ );
1126
+ }
1127
+
1128
+ /**
1129
+ * Non-blocking notice for a build that fell back to message-only implement
1130
+ * (the approved plan.md could not be uploaded/attached). The build itself
1131
+ * proceeded — the agent follows the plan from conversation history — so this
1132
+ * is a status, not an alert, and it is dismissible.
1133
+ */
1134
+ function PlanAttachFailedNotice({ onDismiss }: { onDismiss: () => void }) {
1135
+ return (
1136
+ <div
1137
+ role="status"
1138
+ className="flex items-center gap-2 border-t border-border-muted px-4 py-1.5 text-xs text-muted-foreground"
1139
+ >
1140
+ <span className="min-w-0 flex-1 truncate">
1141
+ Couldn&rsquo;t attach plan.md — the agent will follow the plan from the
1142
+ conversation instead.
1143
+ </span>
1144
+ <button
1145
+ type="button"
1146
+ onClick={onDismiss}
1147
+ className="shrink-0 rounded font-medium text-foreground underline-offset-2 hover:underline focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
1148
+ >
1149
+ Dismiss
1150
+ </button>
1151
+ </div>
1152
+ );
1153
+ }
1154
+
596
1155
  function SendErrorBanner({ error }: { error: Error }) {
597
1156
  if (isSecretFlowError(error)) {
598
1157
  return <SecretFlowErrorGuide error={error} className="mx-4 my-2" />;