@opengeni/react 0.46.1 → 0.52.1

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 (341) hide show
  1. package/README.md +158 -14
  2. package/dist/artifacts-document.d.ts +3 -0
  3. package/dist/artifacts-document.js +17 -0
  4. package/dist/artifacts-document.js.map +1 -0
  5. package/dist/artifacts-presentation.d.ts +3 -0
  6. package/dist/artifacts-presentation.js +17 -0
  7. package/dist/artifacts-presentation.js.map +1 -0
  8. package/dist/artifacts-spreadsheet.d.ts +3 -0
  9. package/dist/artifacts-spreadsheet.js +17 -0
  10. package/dist/artifacts-spreadsheet.js.map +1 -0
  11. package/dist/artifacts.d.ts +2 -0
  12. package/dist/artifacts.js +170 -0
  13. package/dist/artifacts.js.map +1 -0
  14. package/dist/chunk-6BBWWOOU.js +82 -0
  15. package/dist/chunk-6BBWWOOU.js.map +1 -0
  16. package/dist/{chunk-HFO4ERGQ.js → chunk-724BW4TW.js} +586 -35
  17. package/dist/chunk-724BW4TW.js.map +1 -0
  18. package/dist/chunk-7I2KB3OK.js +3895 -0
  19. package/dist/chunk-7I2KB3OK.js.map +1 -0
  20. package/dist/chunk-7WCLAEFV.js +2828 -0
  21. package/dist/chunk-7WCLAEFV.js.map +1 -0
  22. package/dist/{chunk-U3CSCMWA.js → chunk-FK6VG4LL.js} +9 -13
  23. package/dist/chunk-FK6VG4LL.js.map +1 -0
  24. package/dist/{chunk-SRFUT2ZU.js → chunk-FWS3KC3Z.js} +120 -64
  25. package/dist/chunk-FWS3KC3Z.js.map +1 -0
  26. package/dist/{chunk-EKZH6IDG.js → chunk-HDBG7XGO.js} +62 -1
  27. package/dist/chunk-HDBG7XGO.js.map +1 -0
  28. package/dist/chunk-HUIKIDM5.js +2148 -0
  29. package/dist/chunk-HUIKIDM5.js.map +1 -0
  30. package/dist/{chunk-LGVMUIRV.js → chunk-JAGDUCZQ.js} +1053 -390
  31. package/dist/chunk-JAGDUCZQ.js.map +1 -0
  32. package/dist/{chunk-LWR4MXSS.js → chunk-JR3QFY6B.js} +420 -35
  33. package/dist/chunk-JR3QFY6B.js.map +1 -0
  34. package/dist/chunk-JVYQRRC4.js +53 -0
  35. package/dist/chunk-JVYQRRC4.js.map +1 -0
  36. package/dist/chunk-KCGXPP5V.js +157 -0
  37. package/dist/chunk-KCGXPP5V.js.map +1 -0
  38. package/dist/{chunk-EB67J347.js → chunk-KZ73RL76.js} +111 -31
  39. package/dist/chunk-KZ73RL76.js.map +1 -0
  40. package/dist/chunk-PEI6RANI.js +3234 -0
  41. package/dist/chunk-PEI6RANI.js.map +1 -0
  42. package/dist/{chunk-3PB7MIT6.js → chunk-ROIRTM37.js} +220 -72
  43. package/dist/chunk-ROIRTM37.js.map +1 -0
  44. package/dist/chunk-VZTQ73XT.js +86 -0
  45. package/dist/chunk-VZTQ73XT.js.map +1 -0
  46. package/dist/{chunk-VCBFUSUZ.js → chunk-WSK7G5LE.js} +72 -4
  47. package/dist/chunk-WSK7G5LE.js.map +1 -0
  48. package/dist/{chunk-FGZUCXZF.js → chunk-WYA65Y3F.js} +16 -16
  49. package/dist/chunk-WYA65Y3F.js.map +1 -0
  50. package/dist/{chunk-6EDV6YFE.js → chunk-X4S56Y2H.js} +147 -35
  51. package/dist/chunk-X4S56Y2H.js.map +1 -0
  52. package/dist/client.d.ts +7 -1
  53. package/dist/clipboard.d.ts +1 -0
  54. package/dist/clipboard.js +7 -0
  55. package/dist/clipboard.js.map +1 -0
  56. package/dist/commands/index.d.ts +3 -3
  57. package/dist/commands/registry.d.ts +1 -1
  58. package/dist/commands/types.d.ts +1 -1
  59. package/dist/components/approval-surface.d.ts +2 -2
  60. package/dist/components/artifacts/artifact-surface.d.ts +18 -0
  61. package/dist/components/artifacts/document-editor.d.ts +172 -0
  62. package/dist/components/artifacts/editable-artifact-ui.d.ts +13 -0
  63. package/dist/components/artifacts/editable-artifact-workbench.d.ts +49 -0
  64. package/dist/components/artifacts/editable-document.d.ts +24 -0
  65. package/dist/components/artifacts/editable-presentation.d.ts +24 -0
  66. package/dist/components/artifacts/editable-spreadsheet.d.ts +32 -0
  67. package/dist/components/artifacts/index.d.ts +8 -0
  68. package/dist/components/artifacts/presentation-editor.d.ts +297 -0
  69. package/dist/components/artifacts/spreadsheet-canvas.d.ts +120 -0
  70. package/dist/components/artifacts/spreadsheet-grid.d.ts +113 -0
  71. package/dist/components/browser-viewer.d.ts +29 -0
  72. package/dist/components/chat-composer.d.ts +13 -8
  73. package/dist/components/code-editor.d.ts +25 -3
  74. package/dist/components/command-palette.d.ts +2 -2
  75. package/dist/components/composer-transcription-control.d.ts +4 -2
  76. package/dist/components/composer.d.ts +29 -15
  77. package/dist/components/computer-viewer.d.ts +23 -0
  78. package/dist/components/copy-button.d.ts +2 -2
  79. package/dist/components/desktop-viewer.d.ts +25 -21
  80. package/dist/components/diff-view.d.ts +1 -1
  81. package/dist/components/enrollment-consent.d.ts +1 -1
  82. package/dist/components/enrollment-device-flow.d.ts +1 -1
  83. package/dist/components/file-browser.d.ts +2 -2
  84. package/dist/components/fleet-tile.d.ts +1 -1
  85. package/dist/components/generated-video-player.d.ts +10 -0
  86. package/dist/components/human-input-form.d.ts +1 -1
  87. package/dist/components/human-input-surface.d.ts +2 -2
  88. package/dist/components/interactive-workbench-viewers.d.ts +2 -0
  89. package/dist/components/machine-card.d.ts +2 -2
  90. package/dist/components/machine-dock-bar.d.ts +3 -3
  91. package/dist/components/machine-health-pill.d.ts +2 -2
  92. package/dist/components/machine-input-display.d.ts +1 -1
  93. package/dist/components/machine-metrics.d.ts +2 -2
  94. package/dist/components/machine-status-pill.d.ts +4 -4
  95. package/dist/components/machines/health.d.ts +1 -1
  96. package/dist/components/machines/machine-detail.d.ts +5 -3
  97. package/dist/components/machines/metric-history-chart.d.ts +1 -1
  98. package/dist/components/machines/metric-sparkline.d.ts +2 -2
  99. package/dist/components/machines/series.d.ts +2 -2
  100. package/dist/components/machines-dashboard.d.ts +5 -5
  101. package/dist/components/markdown.d.ts +1 -1
  102. package/dist/components/message-timeline.d.ts +22 -5
  103. package/dist/components/model-picker.d.ts +2 -2
  104. package/dist/components/model-policy-picker.d.ts +7 -7
  105. package/dist/components/pierre-diff.d.ts +1 -1
  106. package/dist/components/pierre-file.d.ts +1 -1
  107. package/dist/components/queue-draft-policy.d.ts +1 -1
  108. package/dist/components/queue-surface-implementation.d.ts +3 -3
  109. package/dist/components/queue-surface-state.d.ts +4 -4
  110. package/dist/components/queue-surface.d.ts +5 -5
  111. package/dist/components/sandbox-files.d.ts +5 -5
  112. package/dist/components/sandbox-terminal.d.ts +50 -4
  113. package/dist/components/sandbox-workspace.d.ts +30 -10
  114. package/dist/components/session-chrome.d.ts +4 -4
  115. package/dist/components/session-status.d.ts +2 -2
  116. package/dist/components/timeline-annotation-selection.d.ts +9 -0
  117. package/dist/components/timeline-annotations-dialog.d.ts +16 -0
  118. package/dist/components/timeline-annotations.d.ts +13 -0
  119. package/dist/components/tooltip.d.ts +4 -4
  120. package/dist/components/user-message-body.d.ts +2 -2
  121. package/dist/components/workbench-changes.d.ts +2 -2
  122. package/dist/components/workspace-dock.d.ts +3 -1
  123. package/dist/composer.d.ts +11 -11
  124. package/dist/composer.js +7 -7
  125. package/dist/hooks/internal.d.ts +7 -1
  126. package/dist/hooks/use-attached-browsers.d.ts +21 -0
  127. package/dist/hooks/use-available-models.d.ts +2 -2
  128. package/dist/hooks/use-billing-usage.d.ts +1 -1
  129. package/dist/hooks/use-browser-frame-stream.d.ts +28 -0
  130. package/dist/hooks/use-browser-identities.d.ts +26 -0
  131. package/dist/hooks/use-browser-session.d.ts +28 -0
  132. package/dist/hooks/use-browser-sessions.d.ts +32 -0
  133. package/dist/hooks/use-codex-accounts.d.ts +3 -3
  134. package/dist/hooks/use-composer.d.ts +36 -9
  135. package/dist/hooks/use-computer-frame-stream.d.ts +24 -0
  136. package/dist/hooks/use-computer-session.d.ts +28 -0
  137. package/dist/hooks/use-computer-sessions.d.ts +28 -0
  138. package/dist/hooks/use-desktop-stream.d.ts +1 -1
  139. package/dist/hooks/use-environments.d.ts +11 -5
  140. package/dist/hooks/use-file-attachments.d.ts +1 -1
  141. package/dist/hooks/use-goal.d.ts +2 -2
  142. package/dist/hooks/use-human-input.d.ts +2 -2
  143. package/dist/hooks/use-last-started-turn-policy.d.ts +2 -2
  144. package/dist/hooks/use-machine-chip.d.ts +2 -2
  145. package/dist/hooks/use-machines.d.ts +10 -3
  146. package/dist/hooks/use-packs.d.ts +7 -2
  147. package/dist/hooks/use-rigs.d.ts +1 -1
  148. package/dist/hooks/use-sandbox-files.d.ts +25 -4
  149. package/dist/hooks/use-sandbox-git.d.ts +13 -5
  150. package/dist/hooks/use-sandbox-terminal.d.ts +1 -1
  151. package/dist/hooks/use-scheduled-tasks.d.ts +1 -1
  152. package/dist/hooks/use-session-capabilities.d.ts +5 -4
  153. package/dist/hooks/use-session-control.d.ts +1 -1
  154. package/dist/hooks/use-session-events.d.ts +2 -2
  155. package/dist/hooks/use-session-lineage.d.ts +1 -1
  156. package/dist/hooks/use-session-mcp-approval-policy.d.ts +2 -2
  157. package/dist/hooks/use-session.d.ts +2 -2
  158. package/dist/hooks/use-slash-commands.d.ts +1 -1
  159. package/dist/hooks/use-terminal-stream.d.ts +8 -2
  160. package/dist/hooks/use-transcription.d.ts +2 -2
  161. package/dist/hooks/use-turn-queue.d.ts +2 -2
  162. package/dist/hooks/use-video-artifact-playback.d.ts +9 -0
  163. package/dist/hooks/use-voice-input.d.ts +6 -4
  164. package/dist/hooks/use-workspace-capture.d.ts +1 -1
  165. package/dist/hooks/use-workspace-edit.d.ts +1 -1
  166. package/dist/hooks/use-workspace-sessions.d.ts +1 -1
  167. package/dist/hooks/use-workspaces.d.ts +1 -1
  168. package/dist/index.d.ts +158 -154
  169. package/dist/index.js +1718 -908
  170. package/dist/index.js.map +1 -1
  171. package/dist/interaction.d.ts +23 -0
  172. package/dist/interaction.js +36 -0
  173. package/dist/interaction.js.map +1 -0
  174. package/dist/interactive-workbench-viewers-KHNOHWQE.js +13 -0
  175. package/dist/interactive-workbench-viewers-KHNOHWQE.js.map +1 -0
  176. package/dist/lib/composer-responsive-context.d.ts +9 -0
  177. package/dist/lib/relay-wire.d.ts +4 -0
  178. package/dist/lib/sandbox-liveness.d.ts +9 -0
  179. package/dist/lib/terminal-capability.d.ts +5 -0
  180. package/dist/lib/use-portal-token-style.d.ts +14 -3
  181. package/dist/lib/xterm-theme.d.ts +1 -1
  182. package/dist/machines.d.ts +30 -30
  183. package/dist/machines.js +3 -3
  184. package/dist/provider.d.ts +4 -4
  185. package/dist/{queue-surface-implementation-J4XJFKRW.js → queue-surface-implementation-2KVJTYXH.js} +117 -64
  186. package/dist/queue-surface-implementation-2KVJTYXH.js.map +1 -0
  187. package/dist/realtime/dropdown-menu.d.ts +8 -8
  188. package/dist/realtime/realtime-control.d.ts +15 -6
  189. package/dist/realtime.d.ts +4 -4
  190. package/dist/realtime.js +30 -33
  191. package/dist/realtime.js.map +1 -1
  192. package/dist/session-context.d.ts +19 -1
  193. package/dist/session-ui.d.ts +14 -14
  194. package/dist/session-ui.js +6 -5
  195. package/dist/session.d.ts +28 -28
  196. package/dist/session.js +5 -5
  197. package/dist/timeline/activity-rail.d.ts +6 -3
  198. package/dist/timeline/disclosure-context.d.ts +1 -1
  199. package/dist/timeline/fleet-decision-projection.d.ts +1 -1
  200. package/dist/timeline/fleet-decision-row.d.ts +3 -3
  201. package/dist/timeline/index.d.ts +19 -19
  202. package/dist/timeline/parsers.d.ts +6 -3
  203. package/dist/timeline/projection.d.ts +3 -3
  204. package/dist/timeline/registry.d.ts +10 -1
  205. package/dist/timeline/screenshot-lightbox.d.ts +2 -2
  206. package/dist/timeline/shared.d.ts +21 -10
  207. package/dist/timeline/tool-diff.d.ts +2 -2
  208. package/dist/timeline/tool-renderers.d.ts +1 -1
  209. package/dist/timeline/turn-summary.d.ts +3 -3
  210. package/dist/timeline/types.d.ts +42 -2
  211. package/dist/timeline-annotation-selection-3L7LHGG2.js +187 -0
  212. package/dist/timeline-annotation-selection-3L7LHGG2.js.map +1 -0
  213. package/dist/timeline-annotations-dialog-U7EVLR75.js +196 -0
  214. package/dist/timeline-annotations-dialog-U7EVLR75.js.map +1 -0
  215. package/dist/voice-recording-store.d.ts +8 -2
  216. package/package.json +52 -7
  217. package/src/artifacts-document.ts +31 -0
  218. package/src/artifacts-presentation.ts +36 -0
  219. package/src/artifacts-spreadsheet.ts +21 -0
  220. package/src/artifacts.ts +86 -0
  221. package/src/client.ts +90 -1
  222. package/src/clipboard.ts +2 -0
  223. package/src/components/artifacts/artifact-surface.tsx +93 -0
  224. package/src/components/artifacts/document-editor.tsx +2255 -0
  225. package/src/components/artifacts/editable-artifact-ui.tsx +89 -0
  226. package/src/components/artifacts/editable-artifact-workbench.tsx +203 -0
  227. package/src/components/artifacts/editable-document.tsx +657 -0
  228. package/src/components/artifacts/editable-presentation.tsx +1020 -0
  229. package/src/components/artifacts/editable-spreadsheet.tsx +733 -0
  230. package/src/components/artifacts/index.ts +99 -0
  231. package/src/components/artifacts/presentation-editor.tsx +3148 -0
  232. package/src/components/artifacts/spreadsheet-canvas.ts +799 -0
  233. package/src/components/artifacts/spreadsheet-grid.tsx +2129 -0
  234. package/src/components/browser-viewer.tsx +1592 -0
  235. package/src/components/chat-composer.tsx +25 -2
  236. package/src/components/code-editor.tsx +90 -31
  237. package/src/components/command-palette.tsx +1 -1
  238. package/src/components/composer-transcription-control.tsx +51 -22
  239. package/src/components/composer.tsx +49 -23
  240. package/src/components/computer-viewer.tsx +1139 -0
  241. package/src/components/copy-button.tsx +1 -1
  242. package/src/components/desktop-viewer.tsx +103 -75
  243. package/src/components/file-browser.tsx +39 -17
  244. package/src/components/generated-video-player.tsx +89 -0
  245. package/src/components/human-input-form.tsx +2 -2
  246. package/src/components/interactive-workbench-viewers.ts +2 -0
  247. package/src/components/machine-card.tsx +1 -1
  248. package/src/components/machine-input-display.ts +3 -0
  249. package/src/components/machines/machine-detail.tsx +39 -15
  250. package/src/components/machines-dashboard.tsx +7 -6
  251. package/src/components/message-timeline.tsx +327 -37
  252. package/src/components/model-picker.tsx +2 -2
  253. package/src/components/model-policy-picker.tsx +16 -15
  254. package/src/components/queue-surface-implementation.tsx +135 -75
  255. package/src/components/queue-surface-state.tsx +2 -2
  256. package/src/components/sandbox-files.tsx +142 -62
  257. package/src/components/sandbox-terminal.tsx +119 -35
  258. package/src/components/sandbox-workspace.tsx +394 -228
  259. package/src/components/session-chrome.tsx +67 -27
  260. package/src/components/timeline-annotation-selection.tsx +221 -0
  261. package/src/components/timeline-annotations-dialog.tsx +213 -0
  262. package/src/components/timeline-annotations.tsx +84 -0
  263. package/src/components/tooltip.tsx +13 -16
  264. package/src/components/user-message-body.tsx +1 -1
  265. package/src/components/workbench-changes.tsx +192 -35
  266. package/src/components/workspace-dock.tsx +13 -5
  267. package/src/composer.ts +3 -0
  268. package/src/hooks/internal.ts +132 -38
  269. package/src/hooks/use-attached-browsers.ts +152 -0
  270. package/src/hooks/use-browser-frame-stream.ts +316 -0
  271. package/src/hooks/use-browser-identities.ts +225 -0
  272. package/src/hooks/use-browser-session.ts +476 -0
  273. package/src/hooks/use-browser-sessions.ts +281 -0
  274. package/src/hooks/use-codex-accounts.ts +4 -4
  275. package/src/hooks/use-composer.ts +769 -24
  276. package/src/hooks/use-computer-frame-stream.ts +311 -0
  277. package/src/hooks/use-computer-session.ts +442 -0
  278. package/src/hooks/use-computer-sessions.ts +245 -0
  279. package/src/hooks/use-environments.ts +24 -5
  280. package/src/hooks/use-file-attachments.ts +70 -32
  281. package/src/hooks/use-goal.ts +17 -5
  282. package/src/hooks/use-machine-chip.ts +5 -3
  283. package/src/hooks/use-machines.ts +79 -3
  284. package/src/hooks/use-packs.ts +51 -0
  285. package/src/hooks/use-sandbox-files.ts +382 -88
  286. package/src/hooks/use-sandbox-git.ts +208 -46
  287. package/src/hooks/use-sandbox-terminal.ts +3 -2
  288. package/src/hooks/use-session-capabilities.ts +94 -70
  289. package/src/hooks/use-session-events.ts +18 -4
  290. package/src/hooks/use-terminal-stream.ts +102 -23
  291. package/src/hooks/use-transcription.ts +4 -14
  292. package/src/hooks/use-turn-queue.ts +17 -5
  293. package/src/hooks/use-video-artifact-playback.ts +28 -0
  294. package/src/hooks/use-voice-input.ts +163 -19
  295. package/src/hooks/use-windowed-sections.ts +9 -7
  296. package/src/hooks/use-workspace-edit.ts +2 -1
  297. package/src/index.ts +21 -6
  298. package/src/interaction.ts +71 -0
  299. package/src/lib/composer-responsive-context.ts +15 -0
  300. package/src/lib/relay-wire.ts +4 -0
  301. package/src/lib/sandbox-liveness.ts +11 -0
  302. package/src/lib/terminal-capability.ts +10 -0
  303. package/src/lib/use-portal-token-style.ts +60 -15
  304. package/src/provider.tsx +52 -7
  305. package/src/realtime/dropdown-menu.tsx +15 -19
  306. package/src/realtime/realtime-control.tsx +16 -5
  307. package/src/session-context.ts +90 -0
  308. package/src/timeline/activity-rail.tsx +31 -6
  309. package/src/timeline/index.ts +4 -1
  310. package/src/timeline/parsers.ts +46 -20
  311. package/src/timeline/projection.ts +233 -13
  312. package/src/timeline/registry.ts +19 -0
  313. package/src/timeline/screenshot-lightbox.tsx +30 -9
  314. package/src/timeline/shared.tsx +59 -11
  315. package/src/timeline/tool-renderers.tsx +421 -24
  316. package/src/timeline/turn-summary.tsx +2 -2
  317. package/src/timeline/types.ts +58 -2
  318. package/src/voice-recording-store.ts +15 -0
  319. package/styles/compiled.css +5298 -0
  320. package/styles/compiled.d.ts +2 -0
  321. package/styles/effective-tokens.css +27 -0
  322. package/styles/index.css +18 -0
  323. package/styles/responsive.css +208 -0
  324. package/styles/responsive.d.ts +2 -0
  325. package/styles/tokens.css +50 -17
  326. package/dist/chunk-3PB7MIT6.js.map +0 -1
  327. package/dist/chunk-6EDV6YFE.js.map +0 -1
  328. package/dist/chunk-EB67J347.js.map +0 -1
  329. package/dist/chunk-EKZH6IDG.js.map +0 -1
  330. package/dist/chunk-FGZUCXZF.js.map +0 -1
  331. package/dist/chunk-HFO4ERGQ.js.map +0 -1
  332. package/dist/chunk-JB5725BI.js +0 -48
  333. package/dist/chunk-JB5725BI.js.map +0 -1
  334. package/dist/chunk-LGVMUIRV.js.map +0 -1
  335. package/dist/chunk-LWR4MXSS.js.map +0 -1
  336. package/dist/chunk-SRFUT2ZU.js.map +0 -1
  337. package/dist/chunk-U3CSCMWA.js.map +0 -1
  338. package/dist/chunk-VCBFUSUZ.js.map +0 -1
  339. package/dist/fleet-decision-projection-SLNZMS42.js +0 -241
  340. package/dist/fleet-decision-projection-SLNZMS42.js.map +0 -1
  341. package/dist/queue-surface-implementation-J4XJFKRW.js.map +0 -1
package/dist/index.js CHANGED
@@ -1,58 +1,3 @@
1
- import {
2
- CONNECTION_STATUS_META,
3
- ConnectionDot,
4
- ConnectionStatusPill,
5
- EnrollmentConsent,
6
- EnrollmentDeviceFlow,
7
- HEALTH_TOKEN,
8
- MACHINE_STATE_BADGE_META,
9
- METRICS,
10
- METRIC_WINDOWS,
11
- MachineCard,
12
- MachineDetail,
13
- MachineDockBar,
14
- MachineHealthPill,
15
- MachineMetrics,
16
- MachineStatusPill,
17
- MachinesDashboard,
18
- MetricHistoryChart,
19
- MetricSparkline,
20
- OpenGeniProvider,
21
- SharedMachineDisclosure,
22
- WINDOW_LABEL,
23
- connectionStatusForState,
24
- deriveHealth,
25
- healthPulses,
26
- pointsFor,
27
- useMachines
28
- } from "./chunk-6EDV6YFE.js";
29
- import {
30
- SESSION_EVENT_BROWSER_MAX_BYTES,
31
- SESSION_EVENT_BROWSER_MAX_COUNT,
32
- SESSION_EVENT_BROWSER_PENDING_MAX_BYTES,
33
- SESSION_EVENT_BROWSER_PENDING_MAX_COUNT,
34
- SESSION_EVENT_BROWSER_SINGLE_EVENT_MAX_BYTES,
35
- approvalsFromRequiresAction,
36
- boundBrowserSessionEventWindow,
37
- humanInputRequestFromEvent,
38
- isGoalEvent,
39
- isHumanInputEvent,
40
- isLineageRefreshEvent,
41
- isSessionMcpApprovalPolicyEvent,
42
- isTitleEvent,
43
- isTurnQueueEvent,
44
- projectPendingApprovals,
45
- projectPendingHumanInputRequests,
46
- useFileAttachments,
47
- useGoal,
48
- useHumanInputRequests,
49
- useSession,
50
- useSessionControl,
51
- useSessionEvents,
52
- useSessionLineage,
53
- useSessionMcpApprovalPolicy,
54
- useTurnQueue
55
- } from "./chunk-SRFUT2ZU.js";
56
1
  import {
57
2
  ActivityDisclosure,
58
3
  ActivityRail,
@@ -61,6 +6,7 @@ import {
61
6
  CopyButton,
62
7
  CopyHoverFrame,
63
8
  DisclosureDefaultsProvider,
9
+ GeneratedVideoPlayer,
64
10
  HumanInputForm,
65
11
  HumanInputSurface,
66
12
  LightboxProvider,
@@ -85,7 +31,6 @@ import {
85
31
  applyPatchOps,
86
32
  applyPatchOpsFromToolItem,
87
33
  controlCaret,
88
- copyTextToClipboard,
89
34
  createDefaultToolRegistry,
90
35
  createToolRegistry,
91
36
  defaultHumanInputFormMessages,
@@ -99,11 +44,9 @@ import {
99
44
  parseFreeformApplyPatch,
100
45
  parseToolArgs,
101
46
  rawTypeOf,
102
- redactSecrets,
103
47
  sandboxCommandExitCode,
104
48
  sessionChromeGoalPillState,
105
49
  stripExecBanner,
106
- tableElementToTsv,
107
50
  tailPeek,
108
51
  unwrapMcpOutput,
109
52
  useLightbox,
@@ -111,16 +54,7 @@ import {
111
54
  useThemeType,
112
55
  userMessageLikelyNeedsDisclosure,
113
56
  v4aToGitFileDiff
114
- } from "./chunk-LGVMUIRV.js";
115
- import "./chunk-VCBFUSUZ.js";
116
- import {
117
- buildTimeline,
118
- creditExhaustedFromEvents,
119
- extractSessionRef,
120
- groupTimeline,
121
- sessionStatusFromEvents,
122
- toolDisplayName
123
- } from "./chunk-LWR4MXSS.js";
57
+ } from "./chunk-JAGDUCZQ.js";
124
58
  import {
125
59
  Actions,
126
60
  AttachButton,
@@ -173,19 +107,7 @@ import {
173
107
  useSlashCommands,
174
108
  useTranscription,
175
109
  useVoiceInput
176
- } from "./chunk-3PB7MIT6.js";
177
- import {
178
- FILE_ONLY_MESSAGE_TEXT,
179
- composeSendInput,
180
- shouldSteerOnKey,
181
- shouldSubmitOnKey,
182
- useComposer
183
- } from "./chunk-HFO4ERGQ.js";
184
- import {
185
- useMutationRunner,
186
- usePolledValue,
187
- useSessionEventTrigger
188
- } from "./chunk-EB67J347.js";
110
+ } from "./chunk-ROIRTM37.js";
189
111
  import {
190
112
  BillingClassMark,
191
113
  ModelPolicyPicker,
@@ -194,11 +116,7 @@ import {
194
116
  PickerBackHeader,
195
117
  PickerNavRow,
196
118
  defaultModelPolicyPickerMessages
197
- } from "./chunk-FGZUCXZF.js";
198
- import {
199
- useOpenGeni,
200
- useOpenGeniClient
201
- } from "./chunk-EKZH6IDG.js";
119
+ } from "./chunk-WYA65Y3F.js";
202
120
  import {
203
121
  advancedSourceSummary,
204
122
  availabilityReasonLabel,
@@ -222,7 +140,84 @@ import {
222
140
  TooltipContent,
223
141
  TooltipProvider,
224
142
  TooltipTrigger
225
- } from "./chunk-U3CSCMWA.js";
143
+ } from "./chunk-FK6VG4LL.js";
144
+ import {
145
+ TimelineAnnotationsChip
146
+ } from "./chunk-WSK7G5LE.js";
147
+ import {
148
+ usePortalTokenSource,
149
+ usePortalTokenStyle
150
+ } from "./chunk-VZTQ73XT.js";
151
+ import {
152
+ CONNECTION_STATUS_META,
153
+ ConnectionDot,
154
+ ConnectionStatusPill,
155
+ EnrollmentConsent,
156
+ EnrollmentDeviceFlow,
157
+ HEALTH_TOKEN,
158
+ MACHINE_STATE_BADGE_META,
159
+ METRICS,
160
+ METRIC_WINDOWS,
161
+ MachineCard,
162
+ MachineDetail,
163
+ MachineDockBar,
164
+ MachineHealthPill,
165
+ MachineMetrics,
166
+ MachineStatusPill,
167
+ MachinesDashboard,
168
+ MetricHistoryChart,
169
+ MetricSparkline,
170
+ OpenGeniProvider,
171
+ SharedMachineDisclosure,
172
+ WINDOW_LABEL,
173
+ connectionStatusForState,
174
+ deriveHealth,
175
+ healthPulses,
176
+ pointsFor,
177
+ useMachines
178
+ } from "./chunk-X4S56Y2H.js";
179
+ import {
180
+ SESSION_EVENT_BROWSER_MAX_BYTES,
181
+ SESSION_EVENT_BROWSER_MAX_COUNT,
182
+ SESSION_EVENT_BROWSER_PENDING_MAX_BYTES,
183
+ SESSION_EVENT_BROWSER_PENDING_MAX_COUNT,
184
+ SESSION_EVENT_BROWSER_SINGLE_EVENT_MAX_BYTES,
185
+ approvalsFromRequiresAction,
186
+ boundBrowserSessionEventWindow,
187
+ humanInputRequestFromEvent,
188
+ isGoalEvent,
189
+ isHumanInputEvent,
190
+ isLineageRefreshEvent,
191
+ isSessionMcpApprovalPolicyEvent,
192
+ isTitleEvent,
193
+ isTurnQueueEvent,
194
+ projectPendingApprovals,
195
+ projectPendingHumanInputRequests,
196
+ useFileAttachments,
197
+ useGoal,
198
+ useHumanInputRequests,
199
+ useSession,
200
+ useSessionControl,
201
+ useSessionEvents,
202
+ useSessionLineage,
203
+ useSessionMcpApprovalPolicy,
204
+ useTurnQueue
205
+ } from "./chunk-FWS3KC3Z.js";
206
+ import {
207
+ buildTimeline,
208
+ creditExhaustedFromEvents,
209
+ extractSessionRef,
210
+ groupTimeline,
211
+ sessionStatusFromEvents,
212
+ toolDisplayName
213
+ } from "./chunk-JR3QFY6B.js";
214
+ import {
215
+ FILE_ONLY_MESSAGE_TEXT,
216
+ composeSendInput,
217
+ shouldSteerOnKey,
218
+ shouldSubmitOnKey,
219
+ useComposer
220
+ } from "./chunk-724BW4TW.js";
226
221
  import {
227
222
  COMPOSER_PAYMENT_REQUIRED_MESSAGE,
228
223
  CREDIT_EXHAUSTION_MESSAGE,
@@ -237,11 +232,48 @@ import {
237
232
  tryParseJson
238
233
  } from "./chunk-LAKLF4PA.js";
239
234
  import {
240
- usePortalTokenStyle
241
- } from "./chunk-JB5725BI.js";
235
+ STREAM_KIND_DESKTOP,
236
+ STREAM_ROLE_CLIENT,
237
+ decodeStreamFrame,
238
+ decodeStreamOpenAck,
239
+ encodeStreamOpen
240
+ } from "./chunk-6BBWWOOU.js";
241
+ import {
242
+ useMutationRunner,
243
+ usePageLiveActivity,
244
+ usePolledValue,
245
+ useSessionEventTrigger
246
+ } from "./chunk-KZ73RL76.js";
247
+ import {
248
+ useOpenGeni,
249
+ useOpenGeniClient
250
+ } from "./chunk-HDBG7XGO.js";
242
251
  import {
243
252
  cn
244
253
  } from "./chunk-22KP6LR5.js";
254
+ import {
255
+ copyTextToClipboard,
256
+ tableElementToTsv
257
+ } from "./chunk-JVYQRRC4.js";
258
+
259
+ // src/hooks/use-video-artifact-playback.ts
260
+ import { useMemo } from "react";
261
+ function useVideoArtifactPlaybackLoader(options = {}) {
262
+ const { client, workspaceId } = useOpenGeni(options);
263
+ return useMemo(
264
+ () => async (artifactId, signal) => {
265
+ if (typeof client.createVideoArtifactPlaybackSource !== "function") {
266
+ throw new Error("This OpenGeni host does not expose retained-video playback.");
267
+ }
268
+ return await client.createVideoArtifactPlaybackSource(
269
+ workspaceId,
270
+ artifactId,
271
+ signal ? { signal } : {}
272
+ );
273
+ },
274
+ [client, workspaceId]
275
+ );
276
+ }
245
277
 
246
278
  // src/hooks/use-last-started-turn-policy.ts
247
279
  import { useCallback, useEffect } from "react";
@@ -378,7 +410,7 @@ function useWorkspaceSessions(options = {}) {
378
410
  }
379
411
 
380
412
  // src/hooks/use-environments.ts
381
- import { useCallback as useCallback4, useMemo } from "react";
413
+ import { useCallback as useCallback4, useMemo as useMemo2 } from "react";
382
414
  function useVariableSets(options = {}) {
383
415
  const { client, workspaceId } = useOpenGeni(options);
384
416
  const load = useCallback4(
@@ -423,6 +455,10 @@ function useVariableSets(options = {}) {
423
455
  },
424
456
  [client, workspaceId, run, refresh]
425
457
  );
458
+ const readVariable = useCallback4(
459
+ async (variableSetId, name) => await run(() => client.getVariableSetVariable(workspaceId, variableSetId, name)),
460
+ [client, workspaceId, run]
461
+ );
426
462
  const setVariable = useCallback4(
427
463
  async (variableSetId, name, value) => {
428
464
  const result = await run(
@@ -456,6 +492,7 @@ function useVariableSets(options = {}) {
456
492
  create,
457
493
  update,
458
494
  remove,
495
+ readVariable,
459
496
  setVariable,
460
497
  deleteVariable,
461
498
  mutating,
@@ -464,7 +501,7 @@ function useVariableSets(options = {}) {
464
501
  };
465
502
  }
466
503
  function useEnvironments(options = {}) {
467
- const legacyClient = useMemo(() => {
504
+ const legacyClient = useMemo2(() => {
468
505
  if (!options.client) {
469
506
  return void 0;
470
507
  }
@@ -478,7 +515,10 @@ function useEnvironments(options = {}) {
478
515
  deleteVariableSetVariable: options.client.deleteEnvironmentVariable ?? options.client.deleteVariableSetVariable
479
516
  };
480
517
  }, [options.client]);
481
- const result = useVariableSets({ ...options, ...legacyClient ? { client: legacyClient } : {} });
518
+ const result = useVariableSets({
519
+ ...options,
520
+ ...legacyClient ? { client: legacyClient } : {}
521
+ });
482
522
  return { ...result, environments: result.variableSets };
483
523
  }
484
524
 
@@ -728,6 +768,30 @@ function usePacks(options = {}) {
728
768
  },
729
769
  [client, workspaceId, run, refresh]
730
770
  );
771
+ const previewInstallation = useCallback6(
772
+ async (packId, request = {}) => await run(() => client.previewPackInstallation(workspaceId, packId, request)),
773
+ [client, workspaceId, run]
774
+ );
775
+ const install = useCallback6(
776
+ async (packId, request) => {
777
+ const result = await run(() => client.installPack(workspaceId, packId, request));
778
+ if (result) await refresh();
779
+ return result;
780
+ },
781
+ [client, workspaceId, run, refresh]
782
+ );
783
+ const previewUninstall = useCallback6(
784
+ async (packId) => await run(() => client.previewPackUninstall(workspaceId, packId)),
785
+ [client, workspaceId, run]
786
+ );
787
+ const uninstall = useCallback6(
788
+ async (packId, request) => {
789
+ const result = await run(() => client.uninstallPack(workspaceId, packId, request));
790
+ if (result) await refresh();
791
+ return result;
792
+ },
793
+ [client, workspaceId, run, refresh]
794
+ );
731
795
  const remove = useCallback6(
732
796
  async (packId) => {
733
797
  const result = await run(async () => {
@@ -755,6 +819,10 @@ function usePacks(options = {}) {
755
819
  refresh,
756
820
  register,
757
821
  enable,
822
+ previewInstallation,
823
+ install,
824
+ previewUninstall,
825
+ uninstall,
758
826
  remove,
759
827
  mutating,
760
828
  mutationError,
@@ -884,16 +952,24 @@ import {
884
952
  applyUrlRotation
885
953
  } from "@opengeni/sdk";
886
954
  import { useCallback as useCallback10, useEffect as useEffect3, useRef as useRef2, useState } from "react";
955
+
956
+ // src/lib/sandbox-liveness.ts
957
+ function sandboxAcceptsLiveIo(liveness) {
958
+ return liveness === "warm";
959
+ }
960
+
961
+ // src/lib/terminal-capability.ts
962
+ function terminalCanAcquirePty(cell) {
963
+ if (!cell?.ptyCapable) return false;
964
+ if (cell.transport === "pty-ws") return true;
965
+ return cell.reason === "lease_cold" || cell.reason === "not_provisioned";
966
+ }
967
+
968
+ // src/hooks/use-session-capabilities.ts
887
969
  function desktopAttachable(cell) {
888
970
  if (cell.transport !== null) return true;
889
971
  return cell.reason === "lease_cold" || cell.reason === "not_provisioned" || cell.reason === null;
890
972
  }
891
- function terminalAttachable(cell) {
892
- if (cell.transport === null) {
893
- return cell.reason === "lease_cold" || cell.reason === "not_provisioned" || cell.reason === null;
894
- }
895
- return true;
896
- }
897
973
  function rotationsFrom(events) {
898
974
  const out = [];
899
975
  for (const event of events) {
@@ -906,6 +982,8 @@ function rotationsFrom(events) {
906
982
  function useSessionCapabilities(sessionId, options = {}) {
907
983
  const { client, workspaceId } = useOpenGeni(options);
908
984
  const enabled = (options.enabled ?? true) && Boolean(sessionId);
985
+ const pageLive = usePageLiveActivity();
986
+ const lifecycleEnabled = enabled && pageLive;
909
987
  const attachDesktop = options.attachDesktop ?? false;
910
988
  const attachTerminal = options.attachTerminal ?? false;
911
989
  const attachFiles = options.attachFiles ?? false;
@@ -940,11 +1018,15 @@ function useSessionCapabilities(sessionId, options = {}) {
940
1018
  epochRef.current = 0;
941
1019
  viewerIdRef.current = null;
942
1020
  }
943
- if (!enabled || !sessionId) {
1021
+ if (!lifecycleEnabled || !sessionId) {
944
1022
  setState("idle");
945
1023
  setCapabilities(null);
1024
+ viewerIdRef.current = null;
1025
+ setViewerId(null);
946
1026
  return;
947
1027
  }
1028
+ viewerIdRef.current = null;
1029
+ setViewerId(null);
948
1030
  let cancelled = false;
949
1031
  let pollTimer = null;
950
1032
  let heartbeatTimer = null;
@@ -953,7 +1035,7 @@ function useSessionCapabilities(sessionId, options = {}) {
953
1035
  const startedAt = Date.now();
954
1036
  const clearTimers = () => {
955
1037
  if (pollTimer !== null) clearTimeout(pollTimer);
956
- if (heartbeatTimer !== null) clearInterval(heartbeatTimer);
1038
+ if (heartbeatTimer !== null) clearTimeout(heartbeatTimer);
957
1039
  pollTimer = null;
958
1040
  heartbeatTimer = null;
959
1041
  };
@@ -966,19 +1048,26 @@ function useSessionCapabilities(sessionId, options = {}) {
966
1048
  const startHeartbeat = (caps) => {
967
1049
  if (!localViewerId || heartbeatTimer !== null) return;
968
1050
  const intervalMs = caps.viewerHeartbeatIntervalMs > 0 ? caps.viewerHeartbeatIntervalMs : 3e4;
969
- heartbeatTimer = setInterval(() => {
1051
+ const schedule = () => {
970
1052
  if (cancelled || !localViewerId) return;
971
- void client.heartbeatViewer(workspaceId, sessionId, localViewerId, { leaseEpoch: epochRef.current }).then((res) => {
972
- if (!res.alive && !cancelled) {
973
- renegotiate();
974
- }
975
- }).catch((cause) => {
976
- if (cancelled) return;
977
- if (cause instanceof OpenGeniApiError && (cause.status === 409 || cause.status === 410)) {
978
- renegotiate();
979
- }
980
- });
981
- }, intervalMs);
1053
+ heartbeatTimer = setTimeout(() => {
1054
+ heartbeatTimer = null;
1055
+ if (cancelled || !localViewerId) return;
1056
+ void client.heartbeatViewer(workspaceId, sessionId, localViewerId, {
1057
+ leaseEpoch: epochRef.current
1058
+ }).then((res) => {
1059
+ if (!res.alive && !cancelled) {
1060
+ renegotiate();
1061
+ }
1062
+ }).catch((cause) => {
1063
+ if (cancelled) return;
1064
+ if (cause instanceof OpenGeniApiError && (cause.status === 409 || cause.status === 410)) {
1065
+ renegotiate();
1066
+ }
1067
+ }).finally(schedule);
1068
+ }, intervalMs);
1069
+ };
1070
+ schedule();
982
1071
  };
983
1072
  const pollUntilWarm = () => {
984
1073
  pollTimer = setTimeout(() => {
@@ -986,7 +1075,7 @@ function useSessionCapabilities(sessionId, options = {}) {
986
1075
  void client.getStreamCapabilities(workspaceId, sessionId, { signal: requestAbort.signal }).then((caps) => {
987
1076
  if (cancelled) return;
988
1077
  settle(caps);
989
- if (caps.liveness === "warm" || caps.liveness === "draining") {
1078
+ if (sandboxAcceptsLiveIo(caps.liveness)) {
990
1079
  setState("ready");
991
1080
  return;
992
1081
  }
@@ -1015,18 +1104,25 @@ function useSessionCapabilities(sessionId, options = {}) {
1015
1104
  if (cancelled) return;
1016
1105
  settle(caps);
1017
1106
  const wantDesktopAttach = attachDesktop && desktopAttachable(caps.DesktopStream);
1018
- const wantTerminalAttach = attachTerminal && terminalAttachable(caps.Terminal);
1107
+ const wantTerminalAttach = attachTerminal && terminalCanAcquirePty(caps.Terminal);
1019
1108
  const wantFilesAttach = attachFiles && caps.FileSystem.available;
1020
1109
  if (wantDesktopAttach || wantTerminalAttach || wantFilesAttach) {
1021
1110
  try {
1022
1111
  const holder = await client.attachViewer(workspaceId, sessionId, {
1023
- desktop: wantDesktopAttach
1112
+ desktop: wantDesktopAttach,
1113
+ terminal: wantTerminalAttach,
1114
+ files: wantFilesAttach
1024
1115
  });
1025
1116
  if (cancelled) {
1026
1117
  void client.detachViewer(workspaceId, sessionId, holder.viewerId).catch(() => {
1027
1118
  });
1028
1119
  return;
1029
1120
  }
1121
+ if (wantDesktopAttach && (!holder.dataPlaneUrl || !holder.transport || !holder.client) || wantTerminalAttach && (!holder.terminalUrl || !holder.terminalTransport)) {
1122
+ await client.detachViewer(workspaceId, sessionId, holder.viewerId).catch(() => {
1123
+ });
1124
+ throw new Error("live plane unavailable");
1125
+ }
1030
1126
  localViewerId = holder.viewerId;
1031
1127
  viewerIdRef.current = holder.viewerId;
1032
1128
  setViewerId(holder.viewerId);
@@ -1041,19 +1137,20 @@ function useSessionCapabilities(sessionId, options = {}) {
1041
1137
  ...prev.DesktopStream,
1042
1138
  transport: holder.transport ?? prev.DesktopStream.transport,
1043
1139
  client: holder.client ?? prev.DesktopStream.client,
1044
- url: holder.dataPlaneUrl ?? prev.DesktopStream.url,
1045
- token: holder.streamToken ?? prev.DesktopStream.token,
1046
- expiresAt: holder.streamExpiresAt ?? prev.DesktopStream.expiresAt,
1140
+ url: holder.dataPlaneUrl,
1141
+ token: holder.streamToken,
1142
+ expiresAt: holder.streamExpiresAt,
1047
1143
  resolution: holder.resolution ?? prev.DesktopStream.resolution
1048
1144
  } : prev.DesktopStream,
1049
- Terminal: holder.terminalUrl && holder.terminalTransport ? {
1145
+ Terminal: wantTerminalAttach ? {
1050
1146
  ...prev.Terminal,
1051
- transport: holder.terminalTransport,
1147
+ transport: holder.terminalTransport ?? prev.Terminal.transport,
1052
1148
  url: holder.terminalUrl,
1053
- token: holder.terminalToken ?? prev.Terminal.token,
1149
+ token: holder.terminalToken,
1150
+ expiresAt: holder.terminalExpiresAt,
1054
1151
  // A live pty-ws means the box is pty-capable for real.
1055
- ptyCapable: true,
1056
- reason: null
1152
+ ptyCapable: holder.terminalTransport ? true : prev.Terminal.ptyCapable,
1153
+ reason: holder.terminalTransport ? null : prev.Terminal.reason
1057
1154
  } : prev.Terminal
1058
1155
  } : prev
1059
1156
  );
@@ -1072,6 +1169,8 @@ function useSessionCapabilities(sessionId, options = {}) {
1072
1169
  setState("error");
1073
1170
  setError(cause);
1074
1171
  return;
1172
+ } else {
1173
+ throw cause;
1075
1174
  }
1076
1175
  } else {
1077
1176
  throw cause;
@@ -1079,7 +1178,7 @@ function useSessionCapabilities(sessionId, options = {}) {
1079
1178
  }
1080
1179
  }
1081
1180
  const warmUpRequested = wantDesktopAttach || wantTerminalAttach || wantFilesAttach;
1082
- if (caps.liveness === "warm" || caps.liveness === "draining" || localViewerId) {
1181
+ if (sandboxAcceptsLiveIo(caps.liveness) || localViewerId) {
1083
1182
  setState("ready");
1084
1183
  startHeartbeat(caps);
1085
1184
  } else if (warmUpRequested) {
@@ -1113,7 +1212,7 @@ function useSessionCapabilities(sessionId, options = {}) {
1113
1212
  client,
1114
1213
  workspaceId,
1115
1214
  sessionId,
1116
- enabled,
1215
+ lifecycleEnabled,
1117
1216
  attachDesktop,
1118
1217
  attachTerminal,
1119
1218
  attachFiles,
@@ -1162,76 +1261,6 @@ import {
1162
1261
  } from "@opengeni/sdk";
1163
1262
  import { useEffect as useEffect5, useRef as useRef4, useState as useState3 } from "react";
1164
1263
 
1165
- // src/lib/relay-wire.ts
1166
- import { BinaryReader, BinaryWriter } from "@bufbuild/protobuf/wire";
1167
- var STREAM_KIND_DESKTOP = 2;
1168
- var STREAM_ROLE_CLIENT = 2;
1169
- function encodeChannel(message, writer) {
1170
- if (message.channelId !== "") writer.uint32(10).string(message.channelId);
1171
- if (message.workspaceId !== "") writer.uint32(18).string(message.workspaceId);
1172
- if (message.agentId !== "") writer.uint32(26).string(message.agentId);
1173
- if (message.kind !== 0) writer.uint32(32).int32(message.kind);
1174
- if (message.port !== 0) writer.uint32(40).uint32(message.port);
1175
- return writer;
1176
- }
1177
- function encodeStreamOpen(message) {
1178
- const writer = new BinaryWriter();
1179
- encodeChannel(message.channel, writer.uint32(10).fork()).join();
1180
- if (message.token !== "") writer.uint32(18).string(message.token);
1181
- if (message.role !== 0) writer.uint32(24).int32(message.role);
1182
- if (message.resumeFromSeq !== "0") writer.uint32(32).uint64(message.resumeFromSeq);
1183
- return writer.finish();
1184
- }
1185
- function decodeAgentErrorMessage(reader, length) {
1186
- const end = reader.pos + length;
1187
- let message;
1188
- while (reader.pos < end) {
1189
- const tag = reader.uint32();
1190
- if (tag >>> 3 === 2 && tag === 18) {
1191
- message = reader.string();
1192
- continue;
1193
- }
1194
- if ((tag & 7) === 4 || tag === 0) break;
1195
- reader.skip(tag & 7);
1196
- }
1197
- return message !== void 0 ? { message } : {};
1198
- }
1199
- function decodeStreamOpenAck(bytes) {
1200
- const reader = new BinaryReader(bytes);
1201
- const end = reader.len;
1202
- let accepted = false;
1203
- let error;
1204
- while (reader.pos < end) {
1205
- const tag = reader.uint32();
1206
- if (tag >>> 3 === 1 && tag === 8) {
1207
- accepted = reader.bool();
1208
- continue;
1209
- }
1210
- if (tag >>> 3 === 2 && tag === 18) {
1211
- error = decodeAgentErrorMessage(reader, reader.uint32());
1212
- continue;
1213
- }
1214
- if ((tag & 7) === 4 || tag === 0) break;
1215
- reader.skip(tag & 7);
1216
- }
1217
- return error !== void 0 ? { accepted, error } : { accepted };
1218
- }
1219
- function decodeStreamFrame(bytes) {
1220
- const reader = new BinaryReader(bytes);
1221
- const end = reader.len;
1222
- let data = new Uint8Array(0);
1223
- while (reader.pos < end) {
1224
- const tag = reader.uint32();
1225
- if (tag >>> 3 === 3 && tag === 26) {
1226
- data = new Uint8Array(reader.bytes());
1227
- continue;
1228
- }
1229
- if ((tag & 7) === 4 || tag === 0) break;
1230
- reader.skip(tag & 7);
1231
- }
1232
- return { data };
1233
- }
1234
-
1235
1264
  // src/hooks/use-relay-frame-stream.ts
1236
1265
  import { useEffect as useEffect4, useRef as useRef3, useState as useState2 } from "react";
1237
1266
  var TAG_OPEN = 1;
@@ -1562,7 +1591,15 @@ import {
1562
1591
  ttydResizeFrame,
1563
1592
  TtydServerCommand
1564
1593
  } from "@opengeni/sdk";
1565
- import { useEffect as useEffect6, useMemo as useMemo2, useRef as useRef5, useState as useState4 } from "react";
1594
+ import { useEffect as useEffect6, useMemo as useMemo3, useRef as useRef5, useState as useState4 } from "react";
1595
+ var MAX_PENDING_TERMINAL_INPUT_CODE_UNITS = 64 * 1024;
1596
+ var TERMINAL_CONNECT_EXPIRY_SKEW_MS = 5e3;
1597
+ function terminalStreamCredentialUsable(capability, nowMs = Date.now()) {
1598
+ if (capability?.transport !== "pty-ws" || !capability.url) return false;
1599
+ if (!capability.expiresAt) return true;
1600
+ const expiresAt = Date.parse(capability.expiresAt);
1601
+ return Number.isFinite(expiresAt) && expiresAt > nowMs + TERMINAL_CONNECT_EXPIRY_SKEW_MS;
1602
+ }
1566
1603
  function decodeFrame(data) {
1567
1604
  if (typeof data === "string") {
1568
1605
  return { command: data.charAt(0), payload: data.slice(1) };
@@ -1572,10 +1609,19 @@ function decodeFrame(data) {
1572
1609
  const payload = bytes.length > 1 ? new TextDecoder().decode(bytes.subarray(1)) : "";
1573
1610
  return { command, payload };
1574
1611
  }
1612
+ function closeSocket(socket) {
1613
+ try {
1614
+ socket?.close();
1615
+ } catch {
1616
+ }
1617
+ }
1575
1618
  function useTerminalStream(options) {
1576
1619
  const { capability, onOutput, onTitle, initialCols, initialRows } = options;
1577
1620
  const [status, setStatus] = useState4("closed");
1578
1621
  const wsRef = useRef5(null);
1622
+ const pendingInputRef = useRef5("");
1623
+ const socketFailedRef = useRef5(false);
1624
+ const socketAuthenticatedRef = useRef5(false);
1579
1625
  const sizeRef = useRef5({
1580
1626
  cols: initialCols ?? 80,
1581
1627
  rows: initialRows ?? 24
@@ -1587,9 +1633,22 @@ function useTerminalStream(options) {
1587
1633
  const transport = capability?.transport ?? null;
1588
1634
  const url = capability?.url ?? null;
1589
1635
  const token = capability?.token ?? null;
1636
+ const expiresAt = capability?.expiresAt ?? null;
1590
1637
  useEffect6(() => {
1591
1638
  if (typeof window === "undefined" || typeof WebSocket === "undefined") return;
1592
- if (transport !== "pty-ws" || !url) {
1639
+ if (transport !== "pty-ws") {
1640
+ pendingInputRef.current = "";
1641
+ socketAuthenticatedRef.current = false;
1642
+ setStatus("closed");
1643
+ return;
1644
+ }
1645
+ socketFailedRef.current = false;
1646
+ socketAuthenticatedRef.current = false;
1647
+ if (!url) {
1648
+ setStatus("closed");
1649
+ return;
1650
+ }
1651
+ if (!terminalStreamCredentialUsable({ transport, url, expiresAt })) {
1593
1652
  setStatus("closed");
1594
1653
  return;
1595
1654
  }
@@ -1599,6 +1658,7 @@ function useTerminalStream(options) {
1599
1658
  try {
1600
1659
  socket = new WebSocket(terminalSocketUrl({ url }), TTYD_SUBPROTOCOL);
1601
1660
  } catch {
1661
+ socketFailedRef.current = true;
1602
1662
  setStatus("error");
1603
1663
  return;
1604
1664
  }
@@ -1609,7 +1669,16 @@ function useTerminalStream(options) {
1609
1669
  try {
1610
1670
  socket.send(ttydAuthFrame({ columns: sizeRef.current.cols, rows: sizeRef.current.rows }));
1611
1671
  socket.send(ttydResizeFrame(sizeRef.current.cols, sizeRef.current.rows));
1672
+ if (pendingInputRef.current.length > 0) {
1673
+ socket.send(ttydInputFrame(pendingInputRef.current));
1674
+ pendingInputRef.current = "";
1675
+ }
1676
+ socketAuthenticatedRef.current = true;
1612
1677
  } catch {
1678
+ socketFailedRef.current = true;
1679
+ setStatus("error");
1680
+ closeSocket(socket);
1681
+ return;
1613
1682
  }
1614
1683
  setStatus("open");
1615
1684
  };
@@ -1629,39 +1698,55 @@ function useTerminalStream(options) {
1629
1698
  }
1630
1699
  };
1631
1700
  socket.onerror = () => {
1632
- if (!disposed) setStatus("error");
1701
+ if (!disposed) {
1702
+ socketFailedRef.current = true;
1703
+ setStatus("error");
1704
+ }
1633
1705
  };
1634
1706
  socket.onclose = () => {
1635
- if (!disposed) setStatus("closed");
1707
+ if (wsRef.current === socket) wsRef.current = null;
1708
+ socketAuthenticatedRef.current = false;
1709
+ if (!disposed) setStatus(socketFailedRef.current ? "error" : "closed");
1636
1710
  };
1637
1711
  return () => {
1638
1712
  disposed = true;
1639
1713
  wsRef.current = null;
1714
+ socketAuthenticatedRef.current = false;
1640
1715
  socket.onopen = null;
1641
1716
  socket.onmessage = null;
1642
1717
  socket.onerror = null;
1643
1718
  socket.onclose = null;
1644
- try {
1645
- socket.close();
1646
- } catch {
1647
- }
1719
+ closeSocket(socket);
1648
1720
  };
1649
- }, [transport, url, token]);
1650
- return useMemo2(() => {
1721
+ }, [transport, url, token, expiresAt]);
1722
+ return useMemo3(() => {
1651
1723
  const write = (data) => {
1652
1724
  const ws = wsRef.current;
1653
- if (ws && ws.readyState === WebSocket.OPEN) {
1725
+ if (ws && ws.readyState === WebSocket.OPEN && socketAuthenticatedRef.current) {
1654
1726
  try {
1655
1727
  ws.send(ttydInputFrame(data));
1656
1728
  } catch {
1729
+ socketFailedRef.current = true;
1730
+ setStatus("error");
1731
+ closeSocket(ws);
1732
+ }
1733
+ } else if (transport === "pty-ws" && !socketFailedRef.current) {
1734
+ const nextSize = pendingInputRef.current.length + data.length;
1735
+ if (nextSize > MAX_PENDING_TERMINAL_INPUT_CODE_UNITS) {
1736
+ pendingInputRef.current = "";
1737
+ socketFailedRef.current = true;
1738
+ setStatus("error");
1739
+ closeSocket(ws);
1740
+ return;
1657
1741
  }
1742
+ pendingInputRef.current += data;
1658
1743
  }
1659
1744
  };
1660
1745
  const resize = (cols, rows) => {
1661
1746
  if (cols <= 0 || rows <= 0) return;
1662
1747
  sizeRef.current = { cols, rows };
1663
1748
  const ws = wsRef.current;
1664
- if (ws && ws.readyState === WebSocket.OPEN) {
1749
+ if (ws && ws.readyState === WebSocket.OPEN && socketAuthenticatedRef.current) {
1665
1750
  try {
1666
1751
  ws.send(ttydResizeFrame(cols, rows));
1667
1752
  } catch {
@@ -1671,20 +1756,19 @@ function useTerminalStream(options) {
1671
1756
  const disconnect = () => {
1672
1757
  const ws = wsRef.current;
1673
1758
  wsRef.current = null;
1674
- if (ws) {
1675
- try {
1676
- ws.close();
1677
- } catch {
1678
- }
1679
- }
1759
+ pendingInputRef.current = "";
1760
+ socketFailedRef.current = false;
1761
+ socketAuthenticatedRef.current = false;
1762
+ setStatus("closed");
1763
+ closeSocket(ws);
1680
1764
  };
1681
1765
  return { connected: status === "open", status, write, resize, disconnect };
1682
- }, [status]);
1766
+ }, [status, transport]);
1683
1767
  }
1684
1768
 
1685
1769
  // src/hooks/use-sandbox-terminal.ts
1686
1770
  import { OpenGeniApiError as OpenGeniApiError2 } from "@opengeni/sdk";
1687
- import { useCallback as useCallback11, useEffect as useEffect7, useMemo as useMemo3, useRef as useRef6, useState as useState5 } from "react";
1771
+ import { useCallback as useCallback11, useEffect as useEffect7, useMemo as useMemo4, useRef as useRef6, useState as useState5 } from "react";
1688
1772
  function useSandboxTerminal(sessionId, options) {
1689
1773
  const { client, workspaceId } = useOpenGeni(options);
1690
1774
  const includeAgentFirehose = options.includeAgentFirehose ?? true;
@@ -1704,7 +1788,7 @@ function useSandboxTerminal(sessionId, options) {
1704
1788
  setOpenError(null);
1705
1789
  }, [identityKey]);
1706
1790
  const openInFlight = useRef6(false);
1707
- const boxWarm = liveness === void 0 || liveness === "warm" || liveness === "draining";
1791
+ const boxWarm = liveness === void 0 || sandboxAcceptsLiveIo(liveness);
1708
1792
  useEffect7(() => {
1709
1793
  if (!interactive || !sessionId || ptyFilter || !boxWarm) {
1710
1794
  setOpenError(null);
@@ -1735,7 +1819,7 @@ function useSandboxTerminal(sessionId, options) {
1735
1819
  });
1736
1820
  };
1737
1821
  }, [interactive, client, workspaceId, sessionId, ptyFilter, boxWarm, reopenNonce]);
1738
- const { chunks, openPty, supportsInput } = useMemo3(() => {
1822
+ const { chunks, openPty, supportsInput } = useMemo4(() => {
1739
1823
  const out = [];
1740
1824
  const open = /* @__PURE__ */ new Map();
1741
1825
  let lastOpened = null;
@@ -1788,7 +1872,7 @@ function useSandboxTerminal(sessionId, options) {
1788
1872
  const identityMatches = stateIdentity === identityKey;
1789
1873
  const activePtyId = identityMatches ? openedPtyId ?? openPty : null;
1790
1874
  const canWrite = openedPtyId !== null || supportsInput;
1791
- const write = useMemo3(() => {
1875
+ const write = useMemo4(() => {
1792
1876
  if (!activePtyId || !canWrite || !sessionId) return null;
1793
1877
  const writeIdentity = identityKey;
1794
1878
  return (data) => {
@@ -1816,7 +1900,7 @@ function useSandboxTerminal(sessionId, options) {
1816
1900
  }
1817
1901
 
1818
1902
  // src/hooks/use-sandbox-files.ts
1819
- import { useCallback as useCallback12, useEffect as useEffect8, useRef as useRef7, useState as useState6 } from "react";
1903
+ import { useCallback as useCallback12, useEffect as useEffect8, useMemo as useMemo5, useRef as useRef7, useState as useState6 } from "react";
1820
1904
  var CapturedFileUnavailableError = class extends Error {
1821
1905
  constructor(path, reason) {
1822
1906
  super(
@@ -1838,6 +1922,25 @@ var FileWriteConflictError = class extends Error {
1838
1922
  }
1839
1923
  code = "file_write_conflict";
1840
1924
  };
1925
+ function normalizeRoot(root) {
1926
+ const normalized = root.replaceAll("\\", "/").replace(/^\.\/+/, "").replace(/\/+$/, "");
1927
+ return normalized === "." ? "" : normalized;
1928
+ }
1929
+ function qualifyStatusPath(root, path) {
1930
+ const normalized = normalizeRoot(root);
1931
+ return normalized ? `${normalized}/${path}` : path;
1932
+ }
1933
+ function summarizeStatuses(statuses) {
1934
+ const repositories = statuses.filter(({ status }) => status.isRepo);
1935
+ return {
1936
+ branch: repositories.length === 1 ? repositories[0]?.status.head ?? null : null,
1937
+ isRepo: repositories.length > 0,
1938
+ repoCount: repositories.length,
1939
+ ahead: repositories.reduce((sum, { status }) => sum + status.ahead, 0),
1940
+ behind: repositories.reduce((sum, { status }) => sum + status.behind, 0),
1941
+ dirtyCount: repositories.reduce((sum, { status }) => sum + status.files.length, 0)
1942
+ };
1943
+ }
1841
1944
  function parentOf(path) {
1842
1945
  const i = path.lastIndexOf("/");
1843
1946
  return i <= 0 ? "" : path.slice(0, i);
@@ -1881,7 +1984,10 @@ function replaceChildren(nodes, targetPath, children) {
1881
1984
  return { ...node, children };
1882
1985
  }
1883
1986
  if (node.kind === "dir" && node.children && targetPath.startsWith(`${node.path}/`)) {
1884
- return { ...node, children: replaceChildren(node.children, targetPath, children) };
1987
+ return {
1988
+ ...node,
1989
+ children: replaceChildren(node.children, targetPath, children)
1990
+ };
1885
1991
  }
1886
1992
  return node;
1887
1993
  });
@@ -1896,6 +2002,30 @@ function findNodeByPath(nodes, path) {
1896
2002
  }
1897
2003
  return void 0;
1898
2004
  }
2005
+ function loadedDirectoryPaths(nodes) {
2006
+ const paths = [""];
2007
+ const walk = (list) => {
2008
+ for (const node of list) {
2009
+ if (node.kind !== "dir" || node.children === void 0) continue;
2010
+ paths.push(node.path);
2011
+ walk(node.children);
2012
+ }
2013
+ };
2014
+ walk(nodes);
2015
+ return paths;
2016
+ }
2017
+ function initialDirectoryFrontier(rootPath, repoPaths) {
2018
+ const rootParts = normalizeRoot(rootPath).split("/").filter(Boolean);
2019
+ const paths = /* @__PURE__ */ new Set();
2020
+ for (const repoPath of repoPaths) {
2021
+ const parts = normalizeRoot(repoPath).split("/").filter(Boolean);
2022
+ if (rootParts.some((part, index) => parts[index] !== part)) continue;
2023
+ for (let depth = rootParts.length + 1; depth <= parts.length; depth += 1) {
2024
+ paths.add(parts.slice(0, depth).join("/"));
2025
+ }
2026
+ }
2027
+ return [...paths].sort((a, b) => a.split("/").length - b.split("/").length || a.localeCompare(b)).slice(0, 8);
2028
+ }
1899
2029
  function parentIsLoaded(nodes, parent) {
1900
2030
  if (parent === "") return true;
1901
2031
  const node = findNodeByPath(nodes, parent);
@@ -1927,7 +2057,10 @@ function removeNode(nodes, path) {
1927
2057
  return nodes.map((node) => {
1928
2058
  if (node.path === parent) {
1929
2059
  if (node.kind !== "dir" || node.children === void 0) return node;
1930
- return { ...node, children: node.children.filter((n) => n.path !== path) };
2060
+ return {
2061
+ ...node,
2062
+ children: node.children.filter((n) => n.path !== path)
2063
+ };
1931
2064
  }
1932
2065
  if (node.kind === "dir" && node.children && parent.startsWith(`${node.path}/`)) {
1933
2066
  return { ...node, children: removeNode(node.children, path) };
@@ -1996,15 +2129,27 @@ async function assertCapturedHash(bytes, expected) {
1996
2129
  function useSandboxFiles(sessionId, options = {}) {
1997
2130
  const { client, workspaceId } = useOpenGeni(options);
1998
2131
  const enabled = (options.enabled ?? true) && Boolean(sessionId);
2132
+ const active = options.active ?? true;
1999
2133
  const rootPath = options.rootPath ?? "";
2134
+ const repoPathsKey = [
2135
+ ...new Set((options.repoPaths?.length ? options.repoPaths : [rootPath]).map(normalizeRoot))
2136
+ ].join("\0");
2137
+ const repoPaths = useMemo5(() => repoPathsKey.split("\0"), [repoPathsKey]);
2000
2138
  const capture = options.capture ?? null;
2001
- const isLive2 = options.liveness === "warm" || options.liveness === "draining";
2002
- const identityKey = `${workspaceId}\0${sessionId ?? ""}\0${rootPath}`;
2139
+ const isLive2 = sandboxAcceptsLiveIo(options.liveness);
2140
+ const acceptsEventReads = options.liveness === void 0 || isLive2;
2141
+ const acceptsEventReadsRef = useRef7(acceptsEventReads);
2142
+ acceptsEventReadsRef.current = acceptsEventReads;
2143
+ const hasLiveIoFence = options.liveness !== void 0;
2144
+ const identityKey = `${workspaceId}\0${sessionId ?? ""}\0${rootPath}\0${repoPathsKey}`;
2003
2145
  const [tree, setTree] = useState6([]);
2004
2146
  const treeRef = useRef7([]);
2005
2147
  const [loading, setLoading] = useState6(false);
2006
2148
  const [expandingPaths, setExpandingPaths] = useState6(/* @__PURE__ */ new Set());
2007
2149
  const [error, setError] = useState6(null);
2150
+ const [gitSummary, setGitSummary] = useState6(null);
2151
+ const [gitLoading, setGitLoading] = useState6(false);
2152
+ const [contentRevision, setContentRevision] = useState6(0);
2008
2153
  const [stateIdentity, setStateIdentity] = useState6(identityKey);
2009
2154
  const [source, setSource] = useState6(null);
2010
2155
  const sourceRef = useRef7(null);
@@ -2012,11 +2157,14 @@ function useSandboxFiles(sessionId, options = {}) {
2012
2157
  const refreshGenerationRef = useRef7(0);
2013
2158
  const refreshAbortRef = useRef7(null);
2014
2159
  const identityAbortRef = useRef7(new AbortController());
2160
+ const eventReadAbortRef = useRef7(new AbortController());
2015
2161
  const identityGenerationRef = useRef7(0);
2016
2162
  const lastSeqRef = useRef7(0);
2017
2163
  const pendingParentsRef = useRef7(/* @__PURE__ */ new Set());
2018
2164
  const pendingGitRef = useRef7(false);
2165
+ const pendingAgentToolRef = useRef7(false);
2019
2166
  const debounceRef = useRef7(null);
2167
+ const lastCommandRefreshAtRef = useRef7(0);
2020
2168
  const ownRevisionsRef = useRef7(/* @__PURE__ */ new Set());
2021
2169
  const onMutationError = options.onMutationError;
2022
2170
  treeRef.current = tree;
@@ -2050,48 +2198,83 @@ function useSandboxFiles(sessionId, options = {}) {
2050
2198
  refreshAbortRef.current = refreshAbort;
2051
2199
  const generation = refreshGenerationRef.current += 1;
2052
2200
  setLoading(true);
2201
+ setGitLoading(true);
2053
2202
  setError(null);
2054
2203
  try {
2055
- try {
2056
- const status = await client.gitStatus(
2057
- workspaceId,
2058
- sessionId,
2059
- { path: rootPath },
2060
- { signal: refreshAbort.signal }
2061
- );
2062
- if (refreshGenerationRef.current !== generation) return;
2063
- const overlay = /* @__PURE__ */ new Map();
2064
- for (const file of status.files) {
2065
- const code = file.worktree ?? file.index;
2066
- const mapped = code ? GIT_STATUS_TO_TREE[code] : void 0;
2067
- if (mapped) overlay.set(file.path, mapped);
2068
- }
2069
- statusRef.current = overlay;
2070
- } catch {
2071
- if (refreshGenerationRef.current !== generation) return;
2072
- statusRef.current = /* @__PURE__ */ new Map();
2073
- }
2074
- const listed = await client.fsList(
2204
+ const frontierPaths = initialDirectoryFrontier(rootPath, repoPaths);
2205
+ const listRequests = [rootPath, ...frontierPaths].map((path) => ({
2206
+ path,
2207
+ depth: 1
2208
+ }));
2209
+ const statusPromise = repoPaths.length > 0 ? client.gitReadBatch(
2210
+ workspaceId,
2211
+ sessionId,
2212
+ {
2213
+ requests: repoPaths.map((root) => ({
2214
+ status: { path: root }
2215
+ }))
2216
+ },
2217
+ { signal: refreshAbort.signal }
2218
+ ).then(
2219
+ (batch) => batch.results.map((result, index) => ({
2220
+ root: repoPaths[index] ?? "",
2221
+ status: result.status
2222
+ }))
2223
+ ).catch(() => null) : Promise.resolve([]);
2224
+ const listedBatch = await client.fsListBatch(
2075
2225
  workspaceId,
2076
2226
  sessionId,
2077
- { path: rootPath, depth: 1 },
2227
+ { requests: listRequests },
2078
2228
  { signal: refreshAbort.signal }
2079
2229
  );
2080
2230
  if (refreshGenerationRef.current !== generation) return;
2231
+ const listed = listedBatch.results[0];
2232
+ if (!listed) throw new Error("Workspace list batch did not return the root result.");
2081
2233
  const children = (listed.root.children ?? []).map((node) => fsNodeToTree(node));
2082
- setTree(
2083
- (prev) => applyStatus(prev.length === 0 ? children : mergeRootChildren(prev, children))
2084
- );
2234
+ setTree((prev) => {
2235
+ let hydrated = prev.length === 0 ? children : mergeRootChildren(prev, children);
2236
+ for (const [index, path] of frontierPaths.entries()) {
2237
+ const prefetched = listedBatch.results[index + 1];
2238
+ if (!prefetched) continue;
2239
+ hydrated = replaceChildren(
2240
+ hydrated,
2241
+ path,
2242
+ (prefetched.root.children ?? []).map((node) => fsNodeToTree(node))
2243
+ );
2244
+ }
2245
+ return applyStatus(hydrated);
2246
+ });
2085
2247
  sourceRef.current = "live";
2086
2248
  setSource("live");
2249
+ setLoading(false);
2250
+ const statuses = await statusPromise;
2251
+ if (refreshGenerationRef.current !== generation) return;
2252
+ if (statuses) {
2253
+ const overlay = /* @__PURE__ */ new Map();
2254
+ for (const { root, status } of statuses) {
2255
+ for (const file of status.files) {
2256
+ const code = file.worktree ?? file.index;
2257
+ const mapped = code ? GIT_STATUS_TO_TREE[code] : void 0;
2258
+ if (mapped) overlay.set(qualifyStatusPath(root, file.path), mapped);
2259
+ }
2260
+ }
2261
+ statusRef.current = overlay;
2262
+ setGitSummary(summarizeStatuses(statuses));
2263
+ } else {
2264
+ statusRef.current = /* @__PURE__ */ new Map();
2265
+ setGitSummary(null);
2266
+ }
2267
+ setGitLoading(false);
2268
+ setTree((prev) => applyStatus(prev));
2087
2269
  } catch (cause) {
2088
2270
  if (refreshGenerationRef.current !== generation) return;
2089
2271
  setError(cause instanceof Error ? cause : new Error(String(cause)));
2090
2272
  } finally {
2091
2273
  if (refreshGenerationRef.current === generation) setLoading(false);
2274
+ if (refreshGenerationRef.current === generation) setGitLoading(false);
2092
2275
  if (refreshAbortRef.current === refreshAbort) refreshAbortRef.current = null;
2093
2276
  }
2094
- }, [client, workspaceId, sessionId, rootPath, applyStatus]);
2277
+ }, [client, workspaceId, sessionId, rootPath, repoPaths, applyStatus]);
2095
2278
  const seedFromCapture = useCallback12(
2096
2279
  (manifest) => {
2097
2280
  const overlay = /* @__PURE__ */ new Map();
@@ -2107,6 +2290,15 @@ function useSandboxFiles(sessionId, options = {}) {
2107
2290
  );
2108
2291
  sourceRef.current = "capture";
2109
2292
  setSource("capture");
2293
+ setGitSummary({
2294
+ branch: manifest.repos.length === 1 ? manifest.repos[0]?.head ?? null : null,
2295
+ isRepo: manifest.repos.length > 0,
2296
+ repoCount: manifest.repos.length,
2297
+ ahead: manifest.repos.reduce((sum, repo) => sum + repo.ahead, 0),
2298
+ behind: manifest.repos.reduce((sum, repo) => sum + repo.behind, 0),
2299
+ dirtyCount: manifest.files.length
2300
+ });
2301
+ setGitLoading(false);
2110
2302
  setError(null);
2111
2303
  },
2112
2304
  [applyStatus]
@@ -2224,18 +2416,14 @@ function useSandboxFiles(sessionId, options = {}) {
2224
2416
  [client, workspaceId, sessionId, capture, source]
2225
2417
  );
2226
2418
  const reconcilePath = useCallback12(
2227
- async (path) => {
2419
+ async (path, requestSignal) => {
2228
2420
  if (!sessionId) return;
2229
2421
  const identityGeneration = identityGenerationRef.current;
2230
2422
  const identitySignal = identityAbortRef.current.signal;
2423
+ const signal = requestSignal ? AbortSignal.any([identitySignal, requestSignal]) : identitySignal;
2231
2424
  if (!parentIsLoaded(treeRef.current, path)) return;
2232
2425
  try {
2233
- const listed = await client.fsList(
2234
- workspaceId,
2235
- sessionId,
2236
- { path, depth: 1 },
2237
- { signal: identitySignal }
2238
- );
2426
+ const listed = await client.fsList(workspaceId, sessionId, { path, depth: 1 }, { signal });
2239
2427
  if (identityGenerationRef.current !== identityGeneration) return;
2240
2428
  const children = (listed.root.children ?? []).map((node) => fsNodeToTree(node));
2241
2429
  if (path === "") setTree((prev) => applyStatus(mergeRootChildren(prev, children)));
@@ -2287,7 +2475,12 @@ function useSandboxFiles(sessionId, options = {}) {
2287
2475
  const identitySignal = identityAbortRef.current.signal;
2288
2476
  const parent = parentOf(path);
2289
2477
  const exists = Boolean(findNodeByPath(tree, path));
2290
- const node = { path, name: leafOf(path), kind: "file", size: content.length };
2478
+ const node = {
2479
+ path,
2480
+ name: leafOf(path),
2481
+ kind: "file",
2482
+ size: content.length
2483
+ };
2291
2484
  return await runOptimistic(
2292
2485
  "write",
2293
2486
  (nodes) => exists ? nodes : insertNode(nodes, parent, node),
@@ -2325,11 +2518,20 @@ function useSandboxFiles(sessionId, options = {}) {
2325
2518
  async (path) => {
2326
2519
  if (!sessionId) throw new Error("no session");
2327
2520
  const parent = parentOf(path);
2328
- const node = { path, name: leafOf(path), kind: "file", size: 0 };
2521
+ const node = {
2522
+ path,
2523
+ name: leafOf(path),
2524
+ kind: "file",
2525
+ size: 0
2526
+ };
2329
2527
  await runOptimistic(
2330
2528
  "create file",
2331
2529
  (nodes) => insertNode(nodes, parent, node),
2332
- () => client.fsWrite(workspaceId, sessionId, { path, content: "", overwrite: false }),
2530
+ () => client.fsWrite(workspaceId, sessionId, {
2531
+ path,
2532
+ content: "",
2533
+ overwrite: false
2534
+ }),
2333
2535
  [parent]
2334
2536
  );
2335
2537
  },
@@ -2339,7 +2541,12 @@ function useSandboxFiles(sessionId, options = {}) {
2339
2541
  async (path) => {
2340
2542
  if (!sessionId) throw new Error("no session");
2341
2543
  const parent = parentOf(path);
2342
- const node = { path, name: leafOf(path), kind: "dir", children: [] };
2544
+ const node = {
2545
+ path,
2546
+ name: leafOf(path),
2547
+ kind: "dir",
2548
+ children: []
2549
+ };
2343
2550
  await runOptimistic(
2344
2551
  "create folder",
2345
2552
  (nodes) => insertNode(nodes, parent, node),
@@ -2392,6 +2599,8 @@ function useSandboxFiles(sessionId, options = {}) {
2392
2599
  refreshAbortRef.current?.abort();
2393
2600
  refreshAbortRef.current = null;
2394
2601
  refreshGenerationRef.current += 1;
2602
+ eventReadAbortRef.current.abort();
2603
+ eventReadAbortRef.current = new AbortController();
2395
2604
  const identityChanged = previousIdentityRef.current !== identityKey;
2396
2605
  previousIdentityRef.current = identityKey;
2397
2606
  if (identityChanged || !enabled) {
@@ -2401,6 +2610,8 @@ function useSandboxFiles(sessionId, options = {}) {
2401
2610
  lastSeqRef.current = 0;
2402
2611
  pendingParentsRef.current = /* @__PURE__ */ new Set();
2403
2612
  pendingGitRef.current = false;
2613
+ pendingAgentToolRef.current = false;
2614
+ lastCommandRefreshAtRef.current = 0;
2404
2615
  if (debounceRef.current) {
2405
2616
  clearTimeout(debounceRef.current);
2406
2617
  debounceRef.current = null;
@@ -2415,6 +2626,9 @@ function useSandboxFiles(sessionId, options = {}) {
2415
2626
  setSource(null);
2416
2627
  setLoading(false);
2417
2628
  setError(null);
2629
+ setGitSummary(null);
2630
+ setGitLoading(false);
2631
+ setContentRevision(0);
2418
2632
  }
2419
2633
  if (!enabled) {
2420
2634
  return;
@@ -2423,42 +2637,88 @@ function useSandboxFiles(sessionId, options = {}) {
2423
2637
  if (currentCapture && (identityChanged || !isLive2 || sourceRef.current !== "live")) {
2424
2638
  seedFromCapture(currentCapture);
2425
2639
  }
2426
- if (isLive2 || !currentCapture) {
2640
+ if (!active) {
2641
+ if (!active) {
2642
+ setLoading(false);
2643
+ setGitLoading(false);
2644
+ }
2645
+ return;
2646
+ }
2647
+ if (isLive2 || !hasLiveIoFence && !currentCapture) {
2427
2648
  void refresh();
2428
2649
  }
2429
2650
  return () => {
2430
2651
  refreshAbortRef.current?.abort();
2431
2652
  refreshAbortRef.current = null;
2432
2653
  refreshGenerationRef.current += 1;
2654
+ eventReadAbortRef.current.abort();
2433
2655
  };
2434
- }, [enabled, isLive2, captureRevision, identityKey, refresh, seedFromCapture]);
2435
- const refreshGitOverlay = useCallback12(async () => {
2436
- if (!sessionId) return;
2437
- const identityGeneration = identityGenerationRef.current;
2438
- const identitySignal = identityAbortRef.current.signal;
2439
- try {
2440
- const status = await client.gitStatus(
2441
- workspaceId,
2442
- sessionId,
2443
- { path: rootPath },
2444
- { signal: identitySignal }
2445
- );
2446
- if (identityGenerationRef.current !== identityGeneration) return;
2447
- const overlay = /* @__PURE__ */ new Map();
2448
- for (const file of status.files) {
2449
- const code = file.worktree ?? file.index;
2450
- const mapped = code ? GIT_STATUS_TO_TREE[code] : void 0;
2451
- if (mapped) overlay.set(file.path, mapped);
2656
+ }, [
2657
+ enabled,
2658
+ active,
2659
+ isLive2,
2660
+ hasLiveIoFence,
2661
+ captureRevision,
2662
+ identityKey,
2663
+ refresh,
2664
+ seedFromCapture
2665
+ ]);
2666
+ const refreshGitOverlay = useCallback12(
2667
+ async (requestSignal) => {
2668
+ if (!sessionId) return;
2669
+ const identityGeneration = identityGenerationRef.current;
2670
+ const identitySignal = identityAbortRef.current.signal;
2671
+ const signal = requestSignal ? AbortSignal.any([identitySignal, requestSignal]) : identitySignal;
2672
+ setGitLoading(true);
2673
+ try {
2674
+ const batch = await client.gitReadBatch(
2675
+ workspaceId,
2676
+ sessionId,
2677
+ { requests: repoPaths.map((root) => ({ status: { path: root } })) },
2678
+ { signal }
2679
+ );
2680
+ const statuses = batch.results.map((result, index) => ({
2681
+ root: repoPaths[index] ?? "",
2682
+ status: result.status
2683
+ }));
2684
+ if (identityGenerationRef.current !== identityGeneration) return;
2685
+ const overlay = /* @__PURE__ */ new Map();
2686
+ for (const { root, status } of statuses) {
2687
+ for (const file of status.files) {
2688
+ const code = file.worktree ?? file.index;
2689
+ const mapped = code ? GIT_STATUS_TO_TREE[code] : void 0;
2690
+ if (mapped) overlay.set(qualifyStatusPath(root, file.path), mapped);
2691
+ }
2692
+ }
2693
+ statusRef.current = overlay;
2694
+ setGitSummary(summarizeStatuses(statuses));
2695
+ setTree((prev) => applyStatus(prev));
2696
+ } catch {
2697
+ } finally {
2698
+ if (identityGenerationRef.current === identityGeneration) setGitLoading(false);
2452
2699
  }
2453
- statusRef.current = overlay;
2454
- setTree((prev) => applyStatus(prev));
2455
- } catch {
2456
- }
2457
- }, [client, workspaceId, sessionId, rootPath, applyStatus]);
2700
+ },
2701
+ [client, workspaceId, sessionId, repoPaths, applyStatus]
2702
+ );
2458
2703
  const events = options.events;
2459
2704
  useEffect8(() => {
2460
2705
  if (!enabled || !events) return;
2706
+ if (!active || !acceptsEventReads) {
2707
+ for (const event of events) {
2708
+ if (event.sequence > lastSeqRef.current) lastSeqRef.current = event.sequence;
2709
+ }
2710
+ if (debounceRef.current) {
2711
+ clearTimeout(debounceRef.current);
2712
+ debounceRef.current = null;
2713
+ }
2714
+ pendingParentsRef.current = /* @__PURE__ */ new Set();
2715
+ pendingGitRef.current = false;
2716
+ pendingAgentToolRef.current = false;
2717
+ return;
2718
+ }
2461
2719
  let sawNew = false;
2720
+ let contentChanged = false;
2721
+ let sawCommandDelta = false;
2462
2722
  for (const event of events) {
2463
2723
  if (event.sequence <= lastSeqRef.current) continue;
2464
2724
  if (event.type === "fs.changed") {
@@ -2468,6 +2728,7 @@ function useSandboxFiles(sessionId, options = {}) {
2468
2728
  if (payload.source === "write") continue;
2469
2729
  if (typeof payload.revision === "number" && ownRevisionsRef.current.has(payload.revision))
2470
2730
  continue;
2731
+ contentChanged = true;
2471
2732
  for (const change of payload.changes ?? []) {
2472
2733
  pendingParentsRef.current.add(parentOf(change.path));
2473
2734
  if (change.oldPath) pendingParentsRef.current.add(parentOf(change.oldPath));
@@ -2478,34 +2739,70 @@ function useSandboxFiles(sessionId, options = {}) {
2478
2739
  if (payload && typeof payload === "object" && typeof payload.revision === "number" && ownRevisionsRef.current.has(payload.revision))
2479
2740
  continue;
2480
2741
  pendingGitRef.current = true;
2742
+ } else if (event.type === "agent.toolCall.output") {
2743
+ sawNew = true;
2744
+ contentChanged = true;
2745
+ pendingAgentToolRef.current = true;
2746
+ pendingGitRef.current = true;
2747
+ } else if (event.type === "sandbox.command.output.delta") {
2748
+ sawNew = true;
2749
+ contentChanged = true;
2750
+ sawCommandDelta = true;
2751
+ pendingAgentToolRef.current = true;
2752
+ pendingGitRef.current = true;
2481
2753
  }
2482
2754
  }
2483
2755
  for (const event of events)
2484
2756
  if (event.sequence > lastSeqRef.current) lastSeqRef.current = event.sequence;
2485
2757
  if (!sawNew) return;
2758
+ if (contentChanged && !sawCommandDelta) setContentRevision((revision) => revision + 1);
2486
2759
  if (debounceRef.current) clearTimeout(debounceRef.current);
2487
2760
  const identityGeneration = identityGenerationRef.current;
2761
+ const delay = sawCommandDelta ? Math.max(0, 1e3 - (Date.now() - lastCommandRefreshAtRef.current)) : 150;
2488
2762
  debounceRef.current = setTimeout(() => {
2489
2763
  debounceRef.current = null;
2490
2764
  if (identityGenerationRef.current !== identityGeneration) return;
2765
+ if (!acceptsEventReadsRef.current) {
2766
+ pendingParentsRef.current = /* @__PURE__ */ new Set();
2767
+ pendingGitRef.current = false;
2768
+ pendingAgentToolRef.current = false;
2769
+ return;
2770
+ }
2771
+ if (sawCommandDelta) {
2772
+ lastCommandRefreshAtRef.current = Date.now();
2773
+ setContentRevision((revision) => revision + 1);
2774
+ }
2491
2775
  const parents = pendingParentsRef.current;
2492
2776
  pendingParentsRef.current = /* @__PURE__ */ new Set();
2493
2777
  const wantGit = pendingGitRef.current;
2494
2778
  pendingGitRef.current = false;
2779
+ const wantAgentTool = pendingAgentToolRef.current;
2780
+ pendingAgentToolRef.current = false;
2781
+ const eventReadSignal = eventReadAbortRef.current.signal;
2495
2782
  void (async () => {
2496
- if (wantGit) await refreshGitOverlay();
2497
- if (identityGenerationRef.current !== identityGeneration) return;
2498
- for (const parent of parents) {
2499
- if (identityGenerationRef.current !== identityGeneration) return;
2500
- await reconcilePath(parent);
2501
- }
2783
+ const paths = wantAgentTool ? loadedDirectoryPaths(treeRef.current) : [...parents];
2784
+ await Promise.all([
2785
+ wantGit ? refreshGitOverlay(eventReadSignal) : Promise.resolve(),
2786
+ Promise.all(paths.map((path) => reconcilePath(path, eventReadSignal)))
2787
+ ]);
2502
2788
  })();
2503
- }, 150);
2504
- }, [enabled, events, reconcilePath, refreshGitOverlay]);
2789
+ }, delay);
2790
+ }, [enabled, active, acceptsEventReads, events, reconcilePath, refreshGitOverlay]);
2791
+ useEffect8(() => {
2792
+ if (active && acceptsEventReads) return;
2793
+ if (debounceRef.current) {
2794
+ clearTimeout(debounceRef.current);
2795
+ debounceRef.current = null;
2796
+ }
2797
+ pendingParentsRef.current = /* @__PURE__ */ new Set();
2798
+ pendingGitRef.current = false;
2799
+ pendingAgentToolRef.current = false;
2800
+ }, [active, acceptsEventReads]);
2505
2801
  useEffect8(
2506
2802
  () => () => {
2507
2803
  refreshAbortRef.current?.abort();
2508
2804
  identityAbortRef.current.abort();
2805
+ eventReadAbortRef.current.abort();
2509
2806
  identityGenerationRef.current += 1;
2510
2807
  if (debounceRef.current) clearTimeout(debounceRef.current);
2511
2808
  },
@@ -2523,25 +2820,28 @@ function useSandboxFiles(sessionId, options = {}) {
2523
2820
  deleteEntry,
2524
2821
  moveEntry,
2525
2822
  refresh,
2823
+ contentRevision: identityMatches ? contentRevision : 0,
2526
2824
  source: identityMatches ? source : null,
2527
2825
  capturedAt: identityMatches && source === "capture" ? capture?.capturedAt ?? null : null,
2826
+ gitSummary: identityMatches ? gitSummary : null,
2827
+ gitLoading: identityMatches && gitLoading,
2528
2828
  loading: identityMatches && loading,
2529
2829
  error: identityMatches ? error : null
2530
2830
  };
2531
2831
  }
2532
2832
 
2533
2833
  // src/hooks/use-sandbox-git.ts
2534
- import { useCallback as useCallback13, useEffect as useEffect9, useMemo as useMemo4, useRef as useRef8, useState as useState7 } from "react";
2535
- function normalizeRoot(root) {
2834
+ import { useCallback as useCallback13, useEffect as useEffect9, useMemo as useMemo6, useRef as useRef8, useState as useState7 } from "react";
2835
+ function normalizeRoot2(root) {
2536
2836
  const normalized = root.replaceAll("\\", "/").replace(/^\.\/+/, "").replace(/\/+$/, "");
2537
2837
  return normalized === "." ? "" : normalized;
2538
2838
  }
2539
2839
  function qualifyPath(repoRoot, path) {
2540
- const root = normalizeRoot(repoRoot);
2840
+ const root = normalizeRoot2(repoRoot);
2541
2841
  return root ? `${root}/${path}` : path;
2542
2842
  }
2543
2843
  function qualifyDiff(repoRoot, file) {
2544
- const root = normalizeRoot(repoRoot);
2844
+ const root = normalizeRoot2(repoRoot);
2545
2845
  return {
2546
2846
  ...file,
2547
2847
  path: qualifyPath(root, file.path),
@@ -2550,11 +2850,12 @@ function qualifyDiff(repoRoot, file) {
2550
2850
  };
2551
2851
  }
2552
2852
  function uniqueRoots(roots) {
2553
- return [...new Set(roots.map(normalizeRoot))].sort((a, b) => a.localeCompare(b));
2853
+ return [...new Set(roots.map(normalizeRoot2))].sort((a, b) => a.localeCompare(b));
2554
2854
  }
2855
+ var GIT_READ_BATCH_REQUEST_LIMIT = 32;
2555
2856
  function repoForPath(manifest, repoPath) {
2556
- const want = normalizeRoot(repoPath);
2557
- const exact = manifest.repos.find((r) => normalizeRoot(r.root) === want);
2857
+ const want = normalizeRoot2(repoPath);
2858
+ const exact = manifest.repos.find((r) => normalizeRoot2(r.root) === want);
2558
2859
  if (exact) return exact;
2559
2860
  if (want === "" && manifest.repos.length === 1) return manifest.repos[0] ?? null;
2560
2861
  return null;
@@ -2590,16 +2891,22 @@ function mergeDiffs(current, next) {
2590
2891
  function useSandboxGit(sessionId, options = {}) {
2591
2892
  const { client, workspaceId } = useOpenGeni(options);
2592
2893
  const enabled = (options.enabled ?? true) && Boolean(sessionId);
2894
+ const active = options.active ?? true;
2593
2895
  const repoPath = options.repoPath ?? "";
2594
2896
  const workspaceWide = options.repoPaths !== void 0;
2595
2897
  const repoPathsKey = uniqueRoots(
2596
2898
  options.repoPaths && options.repoPaths.length > 0 ? options.repoPaths : [repoPath]
2597
2899
  ).join("\0");
2598
- const repoPaths = useMemo4(() => repoPathsKey.split("\0"), [repoPathsKey]);
2599
- const staged = options.staged ?? false;
2600
- const capture = options.capture ?? null;
2601
- const isLive2 = options.liveness === "warm" || options.liveness === "draining";
2602
- const identityKey = `${workspaceId}\0${sessionId ?? ""}\0${repoPathsKey}\0${workspaceWide}\0${staged}`;
2900
+ const repoPaths = useMemo6(() => repoPathsKey.split("\0"), [repoPathsKey]);
2901
+ const comparison = options.comparison ?? (options.staged ? "staged" : "working");
2902
+ const hasAdvertisedRepoPaths = (options.repoPaths?.length ?? 0) > 0;
2903
+ const capture = comparison === "staged" ? null : options.capture ?? null;
2904
+ const isLive2 = sandboxAcceptsLiveIo(options.liveness);
2905
+ const acceptsEventReads = options.liveness === void 0 || isLive2;
2906
+ const acceptsEventReadsRef = useRef8(acceptsEventReads);
2907
+ acceptsEventReadsRef.current = acceptsEventReads;
2908
+ const hasLiveIoFence = options.liveness !== void 0;
2909
+ const identityKey = `${workspaceId}\0${sessionId ?? ""}\0${repoPathsKey}\0${workspaceWide}\0${comparison}`;
2603
2910
  const [diff, setDiff] = useState7([]);
2604
2911
  const [branch, setBranch] = useState7(null);
2605
2912
  const [isRepo, setIsRepo] = useState7(false);
@@ -2614,6 +2921,7 @@ function useSandboxGit(sessionId, options = {}) {
2614
2921
  const refreshGenerationRef = useRef8(0);
2615
2922
  const refreshAbortRef = useRef8(null);
2616
2923
  const lastChangeRef = useRef8(0);
2924
+ const commandRefreshTimerRef = useRef8(null);
2617
2925
  const refresh = useCallback13(async () => {
2618
2926
  if (!sessionId) return;
2619
2927
  refreshAbortRef.current?.abort();
@@ -2623,7 +2931,52 @@ function useSandboxGit(sessionId, options = {}) {
2623
2931
  setLoading(true);
2624
2932
  setError(null);
2625
2933
  try {
2626
- const statuses = await Promise.all(
2934
+ const diffRequest = (root) => client.gitDiff(
2935
+ workspaceId,
2936
+ sessionId,
2937
+ {
2938
+ path: root,
2939
+ ...comparison === "branch" ? { fromRef: "origin/HEAD", includeUntracked: true } : comparison === "working" ? { fromRef: "HEAD", includeUntracked: true } : { staged: true, includeUntracked: false }
2940
+ },
2941
+ { signal: refreshAbort.signal }
2942
+ );
2943
+ const prefetched = hasAdvertisedRepoPaths ? await (async () => {
2944
+ const prefetchedRepos = [];
2945
+ for (let offset = 0; offset < repoPaths.length; offset += GIT_READ_BATCH_REQUEST_LIMIT) {
2946
+ const roots2 = repoPaths.slice(offset, offset + GIT_READ_BATCH_REQUEST_LIMIT);
2947
+ const batch = await client.gitReadBatch(
2948
+ workspaceId,
2949
+ sessionId,
2950
+ {
2951
+ requests: roots2.map((root) => ({
2952
+ status: { path: root },
2953
+ diff: {
2954
+ path: root,
2955
+ ...comparison === "branch" ? { fromRef: "origin/HEAD", includeUntracked: true } : comparison === "working" ? { fromRef: "HEAD", includeUntracked: true } : { staged: true, includeUntracked: false }
2956
+ }
2957
+ }))
2958
+ },
2959
+ { signal: refreshAbort.signal }
2960
+ );
2961
+ if (batch.results.length !== roots2.length) {
2962
+ throw new Error(
2963
+ `Workspace Git batch returned ${batch.results.length} results for ${roots2.length} repositories.`
2964
+ );
2965
+ }
2966
+ batch.results.forEach((item, index) => {
2967
+ prefetchedRepos.push({
2968
+ root: roots2[index],
2969
+ status: item.status,
2970
+ result: item.diff ?? {
2971
+ files: [],
2972
+ revision: item.status.revision
2973
+ }
2974
+ });
2975
+ });
2976
+ }
2977
+ return prefetchedRepos;
2978
+ })() : null;
2979
+ const statuses = prefetched ?? await Promise.all(
2627
2980
  repoPaths.map(async (root) => ({
2628
2981
  root,
2629
2982
  status: await client.gitStatus(
@@ -2648,19 +3001,11 @@ function useSandboxGit(sessionId, options = {}) {
2648
3001
  setSource("live");
2649
3002
  return;
2650
3003
  }
3004
+ const prefetchedByRoot = new Map(prefetched?.map(({ root, result }) => [root, result]));
2651
3005
  const results = await Promise.all(
2652
3006
  repositories.map(async ({ root }) => ({
2653
3007
  root,
2654
- result: await client.gitDiff(
2655
- workspaceId,
2656
- sessionId,
2657
- {
2658
- path: root,
2659
- staged,
2660
- includeUntracked: !staged
2661
- },
2662
- { signal: refreshAbort.signal }
2663
- )
3008
+ result: prefetchedByRoot.get(root) ?? await diffRequest(root)
2664
3009
  }))
2665
3010
  );
2666
3011
  if (refreshGenerationRef.current !== generation) return;
@@ -2681,10 +3026,21 @@ function useSandboxGit(sessionId, options = {}) {
2681
3026
  if (refreshGenerationRef.current === generation) setLoading(false);
2682
3027
  if (refreshAbortRef.current === refreshAbort) refreshAbortRef.current = null;
2683
3028
  }
2684
- }, [client, workspaceId, sessionId, repoPaths, staged, workspaceWide]);
3029
+ }, [
3030
+ client,
3031
+ workspaceId,
3032
+ sessionId,
3033
+ repoPaths,
3034
+ hasAdvertisedRepoPaths,
3035
+ workspaceWide,
3036
+ comparison
3037
+ ]);
2685
3038
  const seedFromCapture = useCallback13(
2686
3039
  (manifest) => {
2687
3040
  const repositories = workspaceWide ? manifest.repos : [repoForPath(manifest, repoPath)].filter(Boolean);
3041
+ if (comparison === "branch" && repositories.some((repo) => repo.branchDiff === void 0)) {
3042
+ return;
3043
+ }
2688
3044
  if (repositories.length === 0) {
2689
3045
  setIsRepo(false);
2690
3046
  setBranch(null);
@@ -2700,20 +3056,21 @@ function useSandboxGit(sessionId, options = {}) {
2700
3056
  setBranch(repositories.length === 1 ? repositories[0]?.head ?? null : null);
2701
3057
  setAhead(repositories.reduce((sum, repo) => sum + repo.ahead, 0));
2702
3058
  setBehind(repositories.reduce((sum, repo) => sum + repo.behind, 0));
2703
- setRepoRoots(repositories.map((repo) => normalizeRoot(repo.root)));
3059
+ setRepoRoots(repositories.map((repo) => normalizeRoot2(repo.root)));
2704
3060
  setDiff(
2705
3061
  (prev) => mergeDiffs(
2706
3062
  prev,
2707
- repositories.flatMap(
2708
- (repo) => workspaceWide ? repo.diff.map((file) => qualifyDiff(repo.root, file)) : repo.diff
2709
- )
3063
+ repositories.flatMap((repo) => {
3064
+ const capturedDiff = comparison === "branch" ? repo.branchDiff ?? [] : repo.diff;
3065
+ return workspaceWide ? capturedDiff.map((file) => qualifyDiff(repo.root, file)) : capturedDiff;
3066
+ })
2710
3067
  )
2711
3068
  );
2712
3069
  sourceRef.current = "capture";
2713
3070
  setSource("capture");
2714
3071
  setError(null);
2715
3072
  },
2716
- [repoPath, workspaceWide]
3073
+ [comparison, repoPath, workspaceWide]
2717
3074
  );
2718
3075
  const captureRef = useRef8(capture);
2719
3076
  captureRef.current = capture;
@@ -2746,7 +3103,11 @@ function useSandboxGit(sessionId, options = {}) {
2746
3103
  if (currentCapture && (identityChanged || !isLive2 || sourceRef.current !== "live")) {
2747
3104
  seedFromCapture(currentCapture);
2748
3105
  }
2749
- if (isLive2 || !currentCapture) {
3106
+ if (!active) {
3107
+ if (!active) setLoading(false);
3108
+ return;
3109
+ }
3110
+ if (isLive2 || !hasLiveIoFence && !currentCapture) {
2750
3111
  void refresh();
2751
3112
  }
2752
3113
  return () => {
@@ -2754,21 +3115,68 @@ function useSandboxGit(sessionId, options = {}) {
2754
3115
  refreshAbortRef.current = null;
2755
3116
  refreshGenerationRef.current += 1;
2756
3117
  };
2757
- }, [enabled, isLive2, captureRevision, identityKey, refresh, seedFromCapture]);
3118
+ }, [
3119
+ enabled,
3120
+ active,
3121
+ isLive2,
3122
+ hasLiveIoFence,
3123
+ captureRevision,
3124
+ identityKey,
3125
+ refresh,
3126
+ seedFromCapture
3127
+ ]);
2758
3128
  const events = options.events;
2759
3129
  useEffect9(() => {
2760
- if (!enabled || !events) return;
3130
+ if (!enabled || !events) {
3131
+ if (commandRefreshTimerRef.current) {
3132
+ clearTimeout(commandRefreshTimerRef.current);
3133
+ commandRefreshTimerRef.current = null;
3134
+ }
3135
+ return;
3136
+ }
3137
+ if (!active || !acceptsEventReads) {
3138
+ for (const event of events) {
3139
+ if (event.sequence > lastChangeRef.current) lastChangeRef.current = event.sequence;
3140
+ }
3141
+ if (commandRefreshTimerRef.current) {
3142
+ clearTimeout(commandRefreshTimerRef.current);
3143
+ commandRefreshTimerRef.current = null;
3144
+ }
3145
+ return;
3146
+ }
2761
3147
  let latest = lastChangeRef.current;
3148
+ let immediate = false;
3149
+ let commandDelta = false;
2762
3150
  for (const event of events) {
2763
- if (event.type === "git.changed" && event.sequence > latest) {
3151
+ if (event.sequence <= latest) continue;
3152
+ if (event.type === "git.changed" || event.type === "agent.toolCall.output") {
3153
+ latest = event.sequence;
3154
+ immediate = true;
3155
+ } else if (event.type === "sandbox.command.output.delta") {
2764
3156
  latest = event.sequence;
3157
+ commandDelta = true;
2765
3158
  }
2766
3159
  }
2767
3160
  if (latest > lastChangeRef.current) {
2768
3161
  lastChangeRef.current = latest;
2769
- void refresh();
3162
+ if (immediate) {
3163
+ if (commandRefreshTimerRef.current) clearTimeout(commandRefreshTimerRef.current);
3164
+ commandRefreshTimerRef.current = null;
3165
+ if (acceptsEventReadsRef.current) void refresh();
3166
+ } else if (commandDelta && !commandRefreshTimerRef.current) {
3167
+ commandRefreshTimerRef.current = setTimeout(() => {
3168
+ commandRefreshTimerRef.current = null;
3169
+ if (acceptsEventReadsRef.current) void refresh();
3170
+ }, 1e3);
3171
+ }
2770
3172
  }
2771
- }, [enabled, events, refresh]);
3173
+ }, [enabled, active, acceptsEventReads, events, refresh]);
3174
+ useEffect9(
3175
+ () => () => {
3176
+ if (commandRefreshTimerRef.current) clearTimeout(commandRefreshTimerRef.current);
3177
+ },
3178
+ []
3179
+ );
2772
3180
  const identityMatches = enabled && stateIdentity === identityKey;
2773
3181
  const visibleRepoRoots = identityMatches ? repoRoots : [];
2774
3182
  return {
@@ -2985,7 +3393,7 @@ function useWorkspaceCapture(sessionId, options = {}) {
2985
3393
  // src/hooks/use-workspace-edit.ts
2986
3394
  import { useCallback as useCallback15, useEffect as useEffect11, useRef as useRef10, useState as useState9 } from "react";
2987
3395
  function isLive(liveness) {
2988
- return liveness === "warm" || liveness === "draining";
3396
+ return sandboxAcceptsLiveIo(liveness);
2989
3397
  }
2990
3398
  function useWorkspaceEdit(sessionId, options = {}) {
2991
3399
  const { client, workspaceId } = useOpenGeni(options);
@@ -3142,7 +3550,7 @@ function useWorkspaceEdit(sessionId, options = {}) {
3142
3550
  }
3143
3551
 
3144
3552
  // src/hooks/use-machine-chip.ts
3145
- import { useMemo as useMemo5 } from "react";
3553
+ import { useMemo as useMemo7 } from "react";
3146
3554
  function formatAsOf(capturedAt, now) {
3147
3555
  const then = Date.parse(capturedAt);
3148
3556
  if (Number.isNaN(then)) return "recently";
@@ -3159,7 +3567,7 @@ function deriveMachineChip(input) {
3159
3567
  const now = input.now ?? Date.now();
3160
3568
  const asOf = input.capturedAt ?? null;
3161
3569
  const offlineLabel = asOf ? `Offline \u2014 as of ${formatAsOf(asOf, now)}` : "Offline";
3162
- if (input.liveness === "warm" || input.liveness === "draining") {
3570
+ if (sandboxAcceptsLiveIo(input.liveness)) {
3163
3571
  return { state: "live", label: "Live", asOf: null };
3164
3572
  }
3165
3573
  const selfhostedOffline = input.activeIsSelfhosted === true && input.activeMachineState === "offline";
@@ -3179,7 +3587,7 @@ function useMachineChip(input) {
3179
3587
  capturedAt,
3180
3588
  now
3181
3589
  } = input;
3182
- return useMemo5(
3590
+ return useMemo7(
3183
3591
  () => deriveMachineChip({
3184
3592
  liveness,
3185
3593
  capabilitiesState,
@@ -3350,6 +3758,7 @@ import { LayoutGroup, motion } from "motion/react";
3350
3758
  import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
3351
3759
  function ChatComposer({
3352
3760
  composer,
3761
+ responsiveBasis,
3353
3762
  effectiveControl,
3354
3763
  queuedAheadCount,
3355
3764
  canControlWorkspace,
@@ -3397,7 +3806,7 @@ function ChatComposer({
3397
3806
  attachments || models || controlsLeading || controlsStart || transcription
3398
3807
  );
3399
3808
  const stackActions = hasControls && Boolean(actionsStart);
3400
- return /* @__PURE__ */ jsxs2(Root, { controller, className, children: [
3809
+ return /* @__PURE__ */ jsxs2(Root, { controller, responsiveBasis, className, children: [
3401
3810
  /* @__PURE__ */ jsxs2(Frame, { children: [
3402
3811
  /* @__PURE__ */ jsx2(CommandPalette2, {}),
3403
3812
  /* @__PURE__ */ jsxs2(Surface, { children: [
@@ -3405,63 +3814,81 @@ function ChatComposer({
3405
3814
  /* @__PURE__ */ jsx2(RestoredResources, {}),
3406
3815
  /* @__PURE__ */ jsx2(Attachments, {}),
3407
3816
  header,
3817
+ composer.annotations && composer.annotations.length > 0 ? /* @__PURE__ */ jsx2("div", { className: "px-3 pt-2 sm:px-4", children: /* @__PURE__ */ jsx2(
3818
+ TimelineAnnotationsChip,
3819
+ {
3820
+ annotations: composer.annotations,
3821
+ editable: true,
3822
+ focusAnnotationId: composer.annotationReviewTargetId,
3823
+ onFocusConsumed: composer.clearAnnotationReviewTarget,
3824
+ onUpdate: composer.updateAnnotation,
3825
+ onRemove: composer.removeAnnotation
3826
+ }
3827
+ ) }) : null,
3408
3828
  /* @__PURE__ */ jsx2(Input, { placeholder, autoFocus }),
3409
- controller.confirmState ? /* @__PURE__ */ jsx2(Confirmation, {}) : /* @__PURE__ */ jsx2(Footer, { className: stackActions ? "max-sm:flex-nowrap sm:flex-wrap" : void 0, children: /* @__PURE__ */ jsxs2(LayoutGroup, { id: "og-composer-footer", children: [
3410
- hasControls ? /* @__PURE__ */ jsxs2(
3411
- Controls,
3412
- {
3413
- className: stackActions ? "min-w-0 max-sm:flex-1 sm:w-auto" : void 0,
3414
- children: [
3415
- controlsLeading,
3416
- /* @__PURE__ */ jsx2(AttachButton, { className: attachButtonClassName }),
3417
- transcription ? /* @__PURE__ */ jsx2(
3418
- ComposerTranscriptionControl,
3419
- {
3420
- ...transcription,
3421
- suppressed: transcriptionSuppressed,
3422
- className: [transcription.className, transcriptionClassName].filter(Boolean).join(" ")
3423
- }
3424
- ) : null,
3425
- models ? /* @__PURE__ */ jsx2(
3426
- motion.span,
3427
- {
3428
- layout: true,
3429
- transition: { duration: 0.36, ease: [0.22, 1, 0.36, 1] },
3430
- className: "inline-flex min-w-0",
3431
- children: /* @__PURE__ */ jsx2(
3432
- ModelPicker2,
3829
+ controller.confirmState ? /* @__PURE__ */ jsx2(Confirmation, {}) : /* @__PURE__ */ jsx2(
3830
+ Footer,
3831
+ {
3832
+ "data-og-stack-actions": stackActions ? "" : void 0,
3833
+ className: stackActions ? "max-sm:flex-nowrap sm:flex-wrap" : void 0,
3834
+ children: /* @__PURE__ */ jsxs2(LayoutGroup, { id: "og-composer-footer", children: [
3835
+ hasControls ? /* @__PURE__ */ jsxs2(
3836
+ Controls,
3837
+ {
3838
+ className: stackActions ? "min-w-0 max-sm:flex-1 sm:w-auto" : void 0,
3839
+ children: [
3840
+ controlsLeading,
3841
+ /* @__PURE__ */ jsx2(AttachButton, { className: attachButtonClassName }),
3842
+ transcription ? /* @__PURE__ */ jsx2(
3843
+ ComposerTranscriptionControl,
3433
3844
  {
3434
- models,
3435
- value: selectedModel,
3436
- onChange: onSelectModel
3845
+ ...transcription,
3846
+ suppressed: transcriptionSuppressed,
3847
+ className: [transcription.className, transcriptionClassName].filter(Boolean).join(" ")
3437
3848
  }
3438
- )
3439
- }
3440
- ) : null,
3441
- controlsStart ? /* @__PURE__ */ jsx2(
3442
- motion.span,
3443
- {
3444
- layout: true,
3445
- transition: { duration: 0.36, ease: [0.22, 1, 0.36, 1] },
3446
- className: "inline-flex min-w-0 items-center gap-1.5",
3447
- children: controlsStart
3448
- }
3449
- ) : null
3450
- ]
3451
- }
3452
- ) : /* @__PURE__ */ jsx2(Hint, { children: hint }),
3453
- /* @__PURE__ */ jsxs2(
3454
- Actions,
3455
- {
3456
- className: stackActions ? "max-sm:shrink-0 sm:w-auto sm:justify-end" : void 0,
3457
- children: [
3458
- actionsStart,
3459
- /* @__PURE__ */ jsx2(PauseButton, {}),
3460
- /* @__PURE__ */ jsx2(SendButton, {})
3461
- ]
3462
- }
3463
- )
3464
- ] }) })
3849
+ ) : null,
3850
+ models ? /* @__PURE__ */ jsx2(
3851
+ motion.span,
3852
+ {
3853
+ layout: true,
3854
+ transition: { duration: 0.36, ease: [0.22, 1, 0.36, 1] },
3855
+ className: "inline-flex min-w-0",
3856
+ children: /* @__PURE__ */ jsx2(
3857
+ ModelPicker2,
3858
+ {
3859
+ models,
3860
+ value: selectedModel,
3861
+ onChange: onSelectModel
3862
+ }
3863
+ )
3864
+ }
3865
+ ) : null,
3866
+ controlsStart ? /* @__PURE__ */ jsx2(
3867
+ motion.span,
3868
+ {
3869
+ layout: true,
3870
+ transition: { duration: 0.36, ease: [0.22, 1, 0.36, 1] },
3871
+ className: "inline-flex min-w-0 items-center gap-1.5",
3872
+ children: controlsStart
3873
+ }
3874
+ ) : null
3875
+ ]
3876
+ }
3877
+ ) : /* @__PURE__ */ jsx2(Hint, { children: hint }),
3878
+ /* @__PURE__ */ jsxs2(
3879
+ Actions,
3880
+ {
3881
+ className: stackActions ? "max-sm:shrink-0 sm:w-auto sm:justify-end" : void 0,
3882
+ children: [
3883
+ actionsStart,
3884
+ /* @__PURE__ */ jsx2(PauseButton, {}),
3885
+ /* @__PURE__ */ jsx2(SendButton, {})
3886
+ ]
3887
+ }
3888
+ )
3889
+ ] })
3890
+ }
3891
+ )
3465
3892
  ] })
3466
3893
  ] }),
3467
3894
  /* @__PURE__ */ jsx2(Help, {}),
@@ -3683,6 +4110,22 @@ async function attachRenderer(prefer, loaders, onTier) {
3683
4110
 
3684
4111
  // src/components/sandbox-terminal.tsx
3685
4112
  import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
4113
+ function terminalSurfaceState(input) {
4114
+ if (!input.ready) return "loading";
4115
+ if (input.acceptsInput) return "interactive";
4116
+ if (input.inputPending) return "connecting";
4117
+ if (input.ptyStatus === "connecting") return "connecting";
4118
+ if (input.ptyStatus === "error") return "error";
4119
+ if (input.booting) return "waking";
4120
+ return "output-only";
4121
+ }
4122
+ function subscribeTerminalInput(term, sink, enabled) {
4123
+ if (!enabled || !term || !sink) return null;
4124
+ return term.onData(sink);
4125
+ }
4126
+ function clearTerminalBootStatus(term) {
4127
+ term.write("\r\x1B[2K\x1B[2J\x1B[H");
4128
+ }
3686
4129
  function forcedRendererFailures() {
3687
4130
  const raw = typeof globalThis !== "undefined" ? globalThis.__OG_FORCE_RENDERER_FAIL__ : void 0;
3688
4131
  const set = /* @__PURE__ */ new Set();
@@ -3715,30 +4158,49 @@ function SandboxTerminal({
3715
4158
  const wroteFirehoseRef = useRef12(false);
3716
4159
  const bootActiveRef = useRef12(false);
3717
4160
  const [ready, setReady] = useState11(false);
4161
+ const [inputReady, setInputReady] = useState11(false);
3718
4162
  const [activated, setActivated] = useState11(false);
3719
- const ptyWs = terminalCapability?.transport === "pty-ws" && Boolean(terminalCapability?.url);
3720
- const ptyOutputQueueRef = useRef12([]);
4163
+ const ptyDescriptor = terminalCapability?.transport === "pty-ws";
4164
+ const ptyAttachable = terminalCanAcquirePty(terminalCapability);
3721
4165
  const {
3722
4166
  status: ptyStatus,
3723
4167
  write: ptyWrite,
3724
4168
  resize: resizePty,
3725
4169
  connected: ptyConnected
3726
4170
  } = useTerminalStream({
3727
- capability: ptyWs ? {
3728
- transport: terminalCapability.transport,
3729
- url: terminalCapability.url,
3730
- token: terminalCapability.token
4171
+ // Connect only after xterm exists. This makes the renderer the sole output
4172
+ // owner from the first websocket frame and removes an otherwise unbounded
4173
+ // pre-mount output queue during large command bursts.
4174
+ capability: ptyAttachable && ready ? {
4175
+ transport: "pty-ws",
4176
+ url: ptyDescriptor ? terminalCapability.url : null,
4177
+ token: ptyDescriptor ? terminalCapability.token : null,
4178
+ expiresAt: ptyDescriptor ? terminalCapability.expiresAt : null
3731
4179
  } : null,
3732
4180
  onOutput: (data) => {
3733
- const term = termRef.current;
3734
- if (term) term.write(data);
3735
- else ptyOutputQueueRef.current.push(data);
4181
+ termRef.current?.write(data);
3736
4182
  }
3737
4183
  });
3738
- const ptyMode = ptyWs && ptyStatus !== "closed";
3739
- const interactive = !readOnly && (ptyMode || result.write !== null);
3740
- const warm = liveness === "warm" || liveness === "draining";
4184
+ const ptyMode = ptyDescriptor;
4185
+ const ptyCapturesInput = ptyAttachable && ptyStatus !== "error";
4186
+ const interactive = !readOnly && (ptyAttachable ? ptyCapturesInput : result.write !== null);
4187
+ const acceptsInput = inputReady && (!ptyAttachable || ptyConnected);
4188
+ const inputPending = inputReady && ptyAttachable && !ptyConnected && activated;
4189
+ let terminalModeLabel;
4190
+ if (readOnly) terminalModeLabel = "read-only";
4191
+ else if (!ptyAttachable) terminalModeLabel = acceptsInput ? null : "output only";
4192
+ else if (ptyStatus === "error") terminalModeLabel = "connection error";
4193
+ else if (acceptsInput) terminalModeLabel = null;
4194
+ else terminalModeLabel = activated ? "connecting" : "connect on input";
4195
+ const warm = sandboxAcceptsLiveIo(liveness);
3741
4196
  const booting = activated && !ptyMode && !warm && liveness !== null && liveness !== void 0 && result.chunks.length === 0;
4197
+ const surfaceState = terminalSurfaceState({
4198
+ ready,
4199
+ acceptsInput,
4200
+ inputPending,
4201
+ ptyStatus,
4202
+ booting
4203
+ });
3742
4204
  function handleActivate() {
3743
4205
  if (!activated) setActivated(true);
3744
4206
  onActivate?.();
@@ -3825,10 +4287,6 @@ function SandboxTerminal({
3825
4287
  requestAnimationFrame(() => {
3826
4288
  if (disposed) return;
3827
4289
  settle();
3828
- if (ptyOutputQueueRef.current.length > 0) {
3829
- for (const data of ptyOutputQueueRef.current) term.write(data);
3830
- ptyOutputQueueRef.current = [];
3831
- }
3832
4290
  el.setAttribute("data-og-term-ready", "true");
3833
4291
  if (typeof globalThis.__OG_TERMINAL_DEBUG__ === "function") {
3834
4292
  globalThis.__OG_TERMINAL_DEBUG__({
@@ -3873,7 +4331,7 @@ function SandboxTerminal({
3873
4331
  if (!booting) {
3874
4332
  if (bootActiveRef.current) {
3875
4333
  bootActiveRef.current = false;
3876
- if (!wroteFirehoseRef.current) term.clear();
4334
+ if (!wroteFirehoseRef.current) clearTerminalBootStatus(term);
3877
4335
  }
3878
4336
  return;
3879
4337
  }
@@ -3900,12 +4358,19 @@ function SandboxTerminal({
3900
4358
  }, [ready, result.chunks, ptyMode]);
3901
4359
  useEffect13(() => {
3902
4360
  const term = termRef.current;
3903
- if (!ready || !term || !interactive) return;
3904
- const sink = ptyMode ? ptyWrite : result.write;
3905
- if (!sink) return;
3906
- const sub = term.onData((data) => sink(data));
3907
- return () => sub.dispose();
3908
- }, [ready, interactive, ptyMode, ptyWrite, result.write]);
4361
+ if (!ready || !term || !interactive) {
4362
+ setInputReady(false);
4363
+ return;
4364
+ }
4365
+ const sink = ptyAttachable ? ptyWrite : result.write;
4366
+ if (!sink) {
4367
+ setInputReady(false);
4368
+ return;
4369
+ }
4370
+ const sub = subscribeTerminalInput(term, sink, true);
4371
+ setInputReady(sub !== null);
4372
+ return () => sub?.dispose();
4373
+ }, [ready, interactive, ptyAttachable, ptyWrite, result.write]);
3909
4374
  useEffect13(() => {
3910
4375
  const term = termRef.current;
3911
4376
  if (!ready || !term || !ptyMode) return;
@@ -3951,7 +4416,14 @@ function SandboxTerminal({
3951
4416
  ] })
3952
4417
  ] }),
3953
4418
  /* @__PURE__ */ jsxs4("span", { className: "flex shrink-0 items-center gap-2", children: [
3954
- !interactive && /* @__PURE__ */ jsx4("span", { className: "rounded-og-sm bg-og-surface-2 px-1.5 py-0.5 text-og-xs uppercase tracking-wide", children: "read-only" }),
4419
+ terminalModeLabel && /* @__PURE__ */ jsx4(
4420
+ "span",
4421
+ {
4422
+ className: "rounded-og-sm bg-og-surface-2 px-1.5 py-0.5 text-og-xs uppercase tracking-wide",
4423
+ role: "status",
4424
+ children: terminalModeLabel
4425
+ }
4426
+ ),
3955
4427
  /* @__PURE__ */ jsx4(
3956
4428
  "button",
3957
4429
  {
@@ -3981,7 +4453,11 @@ function SandboxTerminal({
3981
4453
  ref: containerRef,
3982
4454
  className: "h-full w-full",
3983
4455
  style: { visibility: ready ? "visible" : "hidden" },
3984
- "data-opengeni-terminal": true
4456
+ "data-opengeni-terminal": true,
4457
+ "data-opengeni-terminal-ready": ready ? "true" : "false",
4458
+ "data-opengeni-terminal-interactive": surfaceState === "interactive" ? "true" : "false",
4459
+ "data-opengeni-terminal-state": surfaceState,
4460
+ "data-opengeni-terminal-status": ptyAttachable ? ptyStatus : "firehose"
3985
4461
  }
3986
4462
  )
3987
4463
  ]
@@ -4051,7 +4527,7 @@ import {
4051
4527
  useEffect as useEffect15,
4052
4528
  useId as useId2,
4053
4529
  useLayoutEffect,
4054
- useMemo as useMemo7,
4530
+ useMemo as useMemo9,
4055
4531
  useRef as useRef13,
4056
4532
  useState as useState12
4057
4533
  } from "react";
@@ -4059,11 +4535,11 @@ import { AlertDialog } from "radix-ui";
4059
4535
  import { VList } from "virtua";
4060
4536
 
4061
4537
  // src/lib/use-unicode-fonts.ts
4062
- import { useEffect as useEffect14, useMemo as useMemo6 } from "react";
4538
+ import { useEffect as useEffect14, useMemo as useMemo8 } from "react";
4063
4539
  var japaneseScript = /[\u3040-\u30ff\u3400-\u4dbf\u4e00-\u9fff\uf900-\ufaff]/u;
4064
4540
  var arabicScript = /[\u0600-\u06ff\u0750-\u077f\u0870-\u08ff\ufb50-\ufdff\ufe70-\ufefc]/u;
4065
4541
  function useUnicodeFallbackFonts(values) {
4066
- const content = useMemo6(() => collectUnicodeContent(values), [values]);
4542
+ const content = useMemo8(() => collectUnicodeContent(values), [values]);
4067
4543
  useEffect14(() => {
4068
4544
  if (content.japanese) {
4069
4545
  void import("./noto-sans-jp-loader.generated-HFGLYBR3.js").then(({ loadNotoSansJp }) => loadNotoSansJp(content.japanese)).catch(() => void 0);
@@ -4146,10 +4622,11 @@ function FileBrowser({
4146
4622
  const [busy, setBusy] = useState12(false);
4147
4623
  const [coarsePointer, setCoarsePointer] = useState12(false);
4148
4624
  const treeId = useId2();
4149
- const containerRef = useRef13(null);
4625
+ const container = usePortalTokenSource();
4626
+ const containerRef = container.currentRef;
4150
4627
  const deleteReturnFocusRef = useRef13(null);
4151
4628
  const vlistRef = useRef13(null);
4152
- const portalTokenStyle = usePortalTokenStyle(containerRef);
4629
+ const portalTokenStyle = usePortalTokenStyle(container.source);
4153
4630
  const getTreeElement = useCallback16(
4154
4631
  () => typeof document === "undefined" ? null : document.getElementById(treeId),
4155
4632
  [treeId]
@@ -4209,7 +4686,7 @@ function FileBrowser({
4209
4686
  [expanded, expand]
4210
4687
  );
4211
4688
  const cold = result.source !== "live";
4212
- const renderItems = useMemo7(() => {
4689
+ const renderItems = useMemo9(() => {
4213
4690
  const items = [];
4214
4691
  if (draftCreate && draftCreate.parent === "") {
4215
4692
  items.push({ type: "create", parent: "", kind: draftCreate.kind, depth: 0 });
@@ -4240,22 +4717,22 @@ function FileBrowser({
4240
4717
  walk(result.tree, 0);
4241
4718
  return items;
4242
4719
  }, [result.tree, expanded, draftCreate, loadingPaths, result.expandingPaths, cold]);
4243
- const unicodeFontPaths = useMemo7(
4720
+ const unicodeFontPaths = useMemo9(
4244
4721
  () => renderItems.flatMap((item) => item.type === "node" ? [item.node.path] : []),
4245
4722
  [renderItems]
4246
4723
  );
4247
4724
  useUnicodeFallbackFonts(unicodeFontPaths);
4248
- const flatRows = useMemo7(
4725
+ const flatRows = useMemo9(
4249
4726
  () => renderItems.flatMap(
4250
4727
  (item) => item.type === "node" ? [{ node: item.node, depth: item.depth }] : []
4251
4728
  ),
4252
4729
  [renderItems]
4253
4730
  );
4254
- const rowIndexByPath = useMemo7(
4731
+ const rowIndexByPath = useMemo9(
4255
4732
  () => new Map(flatRows.map((row, index) => [row.node.path, index])),
4256
4733
  [flatRows]
4257
4734
  );
4258
- const treePositionByPath = useMemo7(() => {
4735
+ const treePositionByPath = useMemo9(() => {
4259
4736
  const positions = /* @__PURE__ */ new Map();
4260
4737
  const visit = (nodes) => {
4261
4738
  nodes.forEach((node, index) => {
@@ -4517,6 +4994,7 @@ function FileBrowser({
4517
4994
  runDelete,
4518
4995
  supportsMutation,
4519
4996
  coarsePointer,
4997
+ containerRef,
4520
4998
  rowIndexByPath,
4521
4999
  treeId
4522
5000
  ]
@@ -4537,7 +5015,7 @@ function FileBrowser({
4537
5015
  const showDegradedTree = result.error && result.tree.length > 0;
4538
5016
  const showEmpty = !result.loading && result.tree.length === 0 && !draftCreate;
4539
5017
  const showToolbar = supportsMutation || result.loading;
4540
- const usesVirtualTree = !showErrorEmpty && !showEmpty;
5018
+ const usesVirtualTree = !showErrorEmpty && !showEmpty && renderItems.length > 500;
4541
5019
  const renderNodeRow = (node, depth) => {
4542
5020
  const isOpen = expanded.has(node.path);
4543
5021
  if (renderNode) {
@@ -4772,7 +5250,7 @@ function FileBrowser({
4772
5250
  type: "button",
4773
5251
  onClick: () => void result.refresh(),
4774
5252
  disabled: result.loading,
4775
- className: "inline-flex min-h-7 shrink-0 items-center gap-1 rounded-og-sm border border-og-border bg-og-surface-1 px-2 font-medium text-og-fg transition-colors hover:border-og-border-strong focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-og-accent disabled:cursor-not-allowed disabled:opacity-50 pointer-coarse:min-h-11",
5253
+ className: "inline-flex min-h-7 shrink-0 items-center gap-1 rounded-og-sm border border-og-border bg-og-surface-1 px-2 font-medium text-og-fg transition-colors hover:border-og-border-strong focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-og-accent disabled:cursor-not-allowed disabled:opacity-50 pointer-coarse:min-h-11",
4776
5254
  children: [
4777
5255
  /* @__PURE__ */ jsx5(
4778
5256
  RefreshCwIcon,
@@ -4791,13 +5269,7 @@ function FileBrowser({
4791
5269
  /* @__PURE__ */ jsx5(
4792
5270
  "div",
4793
5271
  {
4794
- ref: containerRef,
4795
- id: usesVirtualTree ? void 0 : treeId,
4796
- role: usesVirtualTree ? void 0 : "tree",
4797
- "aria-label": usesVirtualTree ? void 0 : "Workspace files",
4798
- "aria-activedescendant": usesVirtualTree ? void 0 : activeDescendantId,
4799
- tabIndex: usesVirtualTree ? void 0 : 0,
4800
- "aria-multiselectable": usesVirtualTree ? void 0 : false,
5272
+ ref: container.ref,
4801
5273
  onFocus: (event) => {
4802
5274
  if (event.target.id === treeId && cursorIndex < 0) {
4803
5275
  const first = flatRows[0];
@@ -4816,7 +5288,7 @@ function FileBrowser({
4816
5288
  void onDropOnto("", src);
4817
5289
  } : void 0,
4818
5290
  className: cn(
4819
- "flex min-h-0 min-w-0 flex-1 flex-col p-1 outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-og-accent",
5291
+ "flex min-h-0 min-w-0 flex-1 flex-col p-1",
4820
5292
  dragOver === "" && "ring-1 ring-inset ring-og-accent"
4821
5293
  ),
4822
5294
  "data-opengeni-file-tree": true,
@@ -4841,7 +5313,19 @@ function FileBrowser({
4841
5313
  ]
4842
5314
  }
4843
5315
  )
4844
- ] }) : showEmpty ? /* @__PURE__ */ jsx5("div", { className: "p-2 text-og-sm text-og-fg-subtle", children: emptyState ?? "This directory is empty" }) : (
5316
+ ] }) : showEmpty ? /* @__PURE__ */ jsx5("div", { className: "p-2 text-og-sm text-og-fg-subtle", children: emptyState ?? "This directory is empty" }) : !usesVirtualTree ? /* @__PURE__ */ jsx5(
5317
+ "div",
5318
+ {
5319
+ id: treeId,
5320
+ role: "tree",
5321
+ "aria-label": "Workspace files",
5322
+ "aria-activedescendant": activeDescendantId,
5323
+ "aria-multiselectable": false,
5324
+ tabIndex: 0,
5325
+ className: "min-h-0 min-w-0 flex-1 overflow-auto outline-hidden focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-og-accent",
5326
+ children: renderItems.map((item) => /* @__PURE__ */ jsx5("div", { className: "min-w-0", children: renderItem(item) }, itemKey(item)))
5327
+ }
5328
+ ) : (
4845
5329
  // Virtualized: only the rows in/near the viewport mount, so a 3k-node
4846
5330
  // tree stays responsive. The flat item list already carries the
4847
5331
  // expanded subtree + synthetic (create/skeleton/residue) rows.
@@ -4855,7 +5339,7 @@ function FileBrowser({
4855
5339
  "aria-activedescendant": activeDescendantId,
4856
5340
  "aria-multiselectable": false,
4857
5341
  tabIndex: 0,
4858
- className: "min-h-0 flex-1 outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-og-accent",
5342
+ className: "min-h-0 flex-1 outline-hidden focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-og-accent",
4859
5343
  itemSize: coarsePointer ? 44 : 28,
4860
5344
  ssrCount: Math.min(32, renderItems.length),
4861
5345
  children: renderItems.map((item) => /* @__PURE__ */ jsx5("div", { className: "min-w-0", children: renderItem(item) }, itemKey(item)))
@@ -4941,11 +5425,11 @@ function DeleteDialog({
4941
5425
  }
4942
5426
  },
4943
5427
  children: /* @__PURE__ */ jsxs5(AlertDialog.Portal, { container: typeof document === "undefined" ? null : document.body, children: [
4944
- /* @__PURE__ */ jsx5(AlertDialog.Overlay, { className: "fixed inset-0 z-[100] bg-black/45 backdrop-blur-[2px]" }),
5428
+ /* @__PURE__ */ jsx5(AlertDialog.Overlay, { className: "og-root fixed inset-0 z-[100] bg-black/45 backdrop-blur-[2px]" }),
4945
5429
  /* @__PURE__ */ jsxs5(
4946
5430
  AlertDialog.Content,
4947
5431
  {
4948
- className: "og-root fixed left-1/2 top-1/2 z-[101] max-h-[calc(100dvh-2rem)] w-[calc(100%-2rem)] max-w-sm -translate-x-1/2 -translate-y-1/2 overflow-y-auto rounded-og-lg border border-og-border-strong bg-og-surface-1 p-4 text-og-fg shadow-2xl outline-none",
5432
+ className: "og-root fixed left-1/2 top-1/2 z-[101] max-h-[calc(100dvh-2rem)] w-[calc(100%-2rem)] max-w-sm -translate-x-1/2 -translate-y-1/2 overflow-y-auto rounded-og-lg border border-og-border-strong bg-og-surface-1 p-4 text-og-fg shadow-2xl outline-hidden",
4949
5433
  style: portalTokenStyle,
4950
5434
  onOpenAutoFocus: (event) => {
4951
5435
  event.preventDefault();
@@ -5090,7 +5574,7 @@ function InlineInput({
5090
5574
  className: cn(
5091
5575
  "min-w-0 flex-1 rounded-og-sm border bg-og-bg px-1 py-0 text-og-sm",
5092
5576
  "border-og-accent text-og-fg",
5093
- "outline-none"
5577
+ "outline-hidden"
5094
5578
  )
5095
5579
  }
5096
5580
  )
@@ -5189,18 +5673,19 @@ function ContextMenu({
5189
5673
  // src/components/workbench-changes.tsx
5190
5674
  import { ArrowUpRightIcon, FileWarningIcon, HistoryIcon } from "lucide-react";
5191
5675
  import {
5676
+ Component,
5192
5677
  useCallback as useCallback18,
5193
5678
  useEffect as useEffect17,
5194
5679
  useId as useId3,
5195
5680
  useLayoutEffect as useLayoutEffect2,
5196
- useMemo as useMemo9,
5681
+ useMemo as useMemo11,
5197
5682
  useRef as useRef15,
5198
5683
  useState as useState14
5199
5684
  } from "react";
5200
5685
  import { VList as VList2 } from "virtua";
5201
5686
 
5202
5687
  // src/hooks/use-windowed-sections.ts
5203
- import { useCallback as useCallback17, useEffect as useEffect16, useMemo as useMemo8, useRef as useRef14, useState as useState13 } from "react";
5688
+ import { useCallback as useCallback17, useEffect as useEffect16, useMemo as useMemo10, useRef as useRef14, useState as useState13 } from "react";
5204
5689
  function computeWindowRange(heights, scrollTop, viewport, overscan) {
5205
5690
  const n = heights.length;
5206
5691
  if (n === 0) return { start: 0, end: 0 };
@@ -5237,10 +5722,10 @@ function useWindowedSections(opts) {
5237
5722
  );
5238
5723
  useEffect16(() => {
5239
5724
  setHeights((prev) => {
5240
- if (prev.length === count) return prev;
5241
- return Array.from({ length: count }, (_, i) => prev[i] ?? estimateHeight(i));
5725
+ const next = Array.from({ length: count }, (_, i) => estimateHeight(i));
5726
+ return prev.length === next.length && prev.every((height, index) => height === next[index]) ? prev : next;
5242
5727
  });
5243
- }, [count]);
5728
+ }, [count, estimateHeight]);
5244
5729
  const heightsRef = useRef14(heights);
5245
5730
  heightsRef.current = heights;
5246
5731
  const [range, setRange] = useState13(() => ({
@@ -5283,7 +5768,7 @@ function useWindowedSections(opts) {
5283
5768
  return next;
5284
5769
  });
5285
5770
  }, []);
5286
- const offsets = useMemo8(() => {
5771
+ const offsets = useMemo10(() => {
5287
5772
  const out = new Array(count + 1);
5288
5773
  out[0] = 0;
5289
5774
  for (let i = 0; i < count; i++) out[i + 1] = (out[i] ?? 0) + Math.max(heights[i] ?? 0, 0);
@@ -5348,6 +5833,101 @@ function compactFileLabel(file) {
5348
5833
  const parent = slash >= 0 ? file.path.slice(0, slash + 1) : "";
5349
5834
  return `${STATUS_LETTER[file.status]} \xB7 ${basename}${parent ? ` \u2014 ${parent}` : ""}`;
5350
5835
  }
5836
+ var ChangesRailVList = class extends Component {
5837
+ state = {
5838
+ renderedRowCount: this.props.rowCount,
5839
+ resetVersion: 0
5840
+ };
5841
+ host = null;
5842
+ listHandle = null;
5843
+ pendingFocusedPath = null;
5844
+ restoreFrame = null;
5845
+ static getDerivedStateFromProps(props, state) {
5846
+ if (props.rowCount === state.renderedRowCount) return null;
5847
+ return {
5848
+ renderedRowCount: props.rowCount,
5849
+ resetVersion: props.rowCount < state.renderedRowCount ? state.resetVersion + 1 : state.resetVersion
5850
+ };
5851
+ }
5852
+ getSnapshotBeforeUpdate(_previousProps, previousState) {
5853
+ if (previousState.resetVersion === this.state.resetVersion) return null;
5854
+ const scroller = this.host?.firstElementChild;
5855
+ if (!(scroller instanceof HTMLElement)) return null;
5856
+ const active = document.activeElement;
5857
+ const focusedRow = active instanceof HTMLElement && scroller.contains(active) ? active.closest("[data-rail-file-path]") : null;
5858
+ return {
5859
+ focusedPath: focusedRow?.dataset.railFilePath ?? this.pendingFocusedPath,
5860
+ scrollOffset: scroller.scrollTop
5861
+ };
5862
+ }
5863
+ componentDidUpdate(_previousProps, _previousState, snapshot) {
5864
+ if (!snapshot || !this.listHandle) return;
5865
+ this.cancelRestoreFrame();
5866
+ const maxOffset = Math.max(0, this.listHandle.scrollSize - this.listHandle.viewportSize);
5867
+ this.listHandle.scrollTo(Math.min(Math.max(snapshot.scrollOffset, 0), maxOffset));
5868
+ const focusedPath = snapshot.focusedPath;
5869
+ if (!focusedPath) return;
5870
+ this.pendingFocusedPath = focusedPath;
5871
+ this.continueFocusRestore();
5872
+ }
5873
+ componentWillUnmount() {
5874
+ this.cancelRestoreFrame();
5875
+ this.pendingFocusedPath = null;
5876
+ }
5877
+ cancelRestoreFrame() {
5878
+ if (this.restoreFrame === null) return;
5879
+ if (typeof cancelAnimationFrame === "function") cancelAnimationFrame(this.restoreFrame);
5880
+ this.restoreFrame = null;
5881
+ }
5882
+ continueFocusRestore = () => {
5883
+ const path = this.pendingFocusedPath;
5884
+ if (!path) return;
5885
+ if (this.restoreFocusedPath(path)) {
5886
+ this.pendingFocusedPath = null;
5887
+ return;
5888
+ }
5889
+ if (this.restoreFrame === null && typeof requestAnimationFrame === "function") {
5890
+ this.restoreFrame = requestAnimationFrame(() => {
5891
+ this.restoreFrame = null;
5892
+ this.continueFocusRestore();
5893
+ });
5894
+ }
5895
+ };
5896
+ restoreFocusedPath(path) {
5897
+ const host = this.host;
5898
+ if (!host) return true;
5899
+ const active = document.activeElement;
5900
+ if (active instanceof HTMLElement && active.isConnected && active !== document.body) {
5901
+ return true;
5902
+ }
5903
+ if (!this.props.files.some((file) => file.path === path)) return true;
5904
+ const row = Array.from(host.querySelectorAll("[data-rail-file-path]")).find(
5905
+ (candidate) => candidate.dataset.railFilePath === path
5906
+ );
5907
+ if (!row) return false;
5908
+ row.focus({ preventScroll: true });
5909
+ return true;
5910
+ }
5911
+ setHost = (host) => {
5912
+ this.host = host;
5913
+ };
5914
+ setListHandle = (handle) => {
5915
+ this.listHandle = handle;
5916
+ };
5917
+ render() {
5918
+ return /* @__PURE__ */ jsx6("div", { ref: this.setHost, "data-opengeni-changes-rail": true, className: this.props.className, children: /* @__PURE__ */ jsx6(
5919
+ VList2,
5920
+ {
5921
+ ref: this.setListHandle,
5922
+ className: "h-full",
5923
+ itemSize: this.props.coarsePointer ? 44 : 28,
5924
+ ssrCount: Math.min(30, this.props.rowCount),
5925
+ children: this.props.children
5926
+ },
5927
+ this.state.resetVersion
5928
+ ) });
5929
+ }
5930
+ };
5351
5931
  function buildRail(files) {
5352
5932
  const roots = new Set(
5353
5933
  files.flatMap((file) => file.repoRoot === void 0 ? [] : [file.repoRoot])
@@ -5406,12 +5986,12 @@ function WorkbenchChanges({
5406
5986
  const [layout, setLayout] = useState14("unified");
5407
5987
  const [activePath, setActivePath] = useState14(null);
5408
5988
  const pickerId = useId3();
5409
- const unicodeFontPaths = useMemo9(
5989
+ const unicodeFontPaths = useMemo11(
5410
5990
  () => diff.flatMap((file) => file.oldPath ? [file.path, file.oldPath] : [file.path]),
5411
5991
  [diff]
5412
5992
  );
5413
5993
  useUnicodeFallbackFonts(unicodeFontPaths);
5414
- const { orderedFiles, rows, grouped } = useMemo9(() => buildRail(diff), [diff]);
5994
+ const { orderedFiles, rows, grouped } = useMemo11(() => buildRail(diff), [diff]);
5415
5995
  const selectedIndex = orderedFiles.findIndex((file) => file.path === activePath);
5416
5996
  const activeIndex = selectedIndex >= 0 ? selectedIndex : 0;
5417
5997
  useChangesLayoutEffect(() => {
@@ -5441,8 +6021,8 @@ function WorkbenchChanges({
5441
6021
  return () => query.removeListener(update);
5442
6022
  }
5443
6023
  }, []);
5444
- const additions = useMemo9(() => diff.reduce((sum, f) => sum + f.additions, 0), [diff]);
5445
- const deletions = useMemo9(() => diff.reduce((sum, f) => sum + f.deletions, 0), [diff]);
6024
+ const additions = useMemo11(() => diff.reduce((sum, f) => sum + f.additions, 0), [diff]);
6025
+ const deletions = useMemo11(() => diff.reduce((sum, f) => sum + f.deletions, 0), [diff]);
5446
6026
  const estimateHeight = useCallback18(
5447
6027
  (index) => {
5448
6028
  const file = orderedFiles[index];
@@ -5555,27 +6135,38 @@ function WorkbenchChanges({
5555
6135
  },
5556
6136
  title: activeFile?.path,
5557
6137
  "data-compact-file-picker": true,
5558
- className: "h-11 w-full rounded-og-md border border-og-border bg-og-bg px-3 font-og-mono text-og-sm text-og-fg outline-none transition-colors focus:border-og-accent focus:ring-2 focus:ring-og-accent-soft",
6138
+ className: "h-11 w-full rounded-og-md border border-og-border bg-og-bg px-3 font-og-mono text-og-sm text-og-fg outline-hidden transition-colors focus:border-og-accent focus:ring-2 focus:ring-og-accent-soft",
5559
6139
  children: orderedFiles.map((file, index) => /* @__PURE__ */ jsx6("option", { value: index, children: compactFileLabel(file) }, file.path))
5560
6140
  }
5561
6141
  )
5562
6142
  ] }),
5563
- /* @__PURE__ */ jsx6("div", { className: "min-h-0 min-w-0 flex-1 overflow-auto", "data-opengeni-changes-pane": true, children: activeFile ? /* @__PURE__ */ jsx6(
5564
- DiffSection,
6143
+ /* @__PURE__ */ jsx6(
6144
+ "div",
5565
6145
  {
5566
- file: activeFile,
5567
- layout: "unified",
5568
- themeType: resolvedTheme,
5569
- ...onOpenFile ? { onOpenFile } : {}
6146
+ "aria-label": "Changed file diff",
6147
+ className: "min-h-0 min-w-0 flex-1 overflow-auto outline-hidden focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-og-accent",
6148
+ "data-opengeni-changes-pane": true,
6149
+ role: "region",
6150
+ tabIndex: 0,
6151
+ children: activeFile ? /* @__PURE__ */ jsx6(
6152
+ DiffSection,
6153
+ {
6154
+ file: activeFile,
6155
+ layout: "unified",
6156
+ themeType: resolvedTheme,
6157
+ ...onOpenFile ? { onOpenFile } : {}
6158
+ }
6159
+ ) : null
5570
6160
  }
5571
- ) : null })
6161
+ )
5572
6162
  ] }) : /* @__PURE__ */ jsxs6("div", { className: "flex min-h-0 flex-1", children: [
5573
- /* @__PURE__ */ jsx6("div", { className: "w-[clamp(12rem,28%,15rem)] shrink-0 border-r border-og-border bg-og-surface-1/35", children: /* @__PURE__ */ jsx6(
5574
- VList2,
6163
+ /* @__PURE__ */ jsx6(
6164
+ ChangesRailVList,
5575
6165
  {
5576
- className: "h-full",
5577
- itemSize: coarsePointer ? 44 : 28,
5578
- ssrCount: Math.min(30, rows.length),
6166
+ rowCount: rows.length,
6167
+ coarsePointer,
6168
+ files: orderedFiles,
6169
+ className: "w-[clamp(12rem,28%,15rem)] shrink-0 border-r border-og-border bg-og-surface-1/35",
5579
6170
  children: rows.map(
5580
6171
  (row) => row.kind === "group" ? /* @__PURE__ */ jsxs6(
5581
6172
  "div",
@@ -5600,13 +6191,16 @@ function WorkbenchChanges({
5600
6191
  )
5601
6192
  )
5602
6193
  }
5603
- ) }),
6194
+ ),
5604
6195
  /* @__PURE__ */ jsx6(
5605
6196
  "div",
5606
6197
  {
5607
6198
  ref: windowed.scrollRef,
5608
- className: "min-h-0 min-w-0 flex-1 overflow-auto",
6199
+ "aria-label": "Changed files diff",
6200
+ className: "min-h-0 min-w-0 flex-1 overflow-auto outline-hidden focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-og-accent",
5609
6201
  "data-opengeni-changes-pane": true,
6202
+ role: "region",
6203
+ tabIndex: 0,
5610
6204
  children: /* @__PURE__ */ jsx6("div", { style: { position: "relative", height: windowed.totalHeight }, children: orderedFiles.map((file, index) => {
5611
6205
  if (index < windowed.range.start || index >= windowed.range.end) return null;
5612
6206
  return /* @__PURE__ */ jsx6(
@@ -5675,8 +6269,9 @@ function RailFileRow({
5675
6269
  onClick,
5676
6270
  title: file.path,
5677
6271
  "data-rail-file": true,
6272
+ "data-rail-file-path": file.path,
5678
6273
  className: cn(
5679
- "relative flex min-h-7 w-full items-center gap-1.5 truncate px-2 py-0.5 text-left text-og-sm transition-colors hover:bg-og-surface-2 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-og-accent pointer-coarse:min-h-11",
6274
+ "relative flex min-h-7 w-full items-center gap-1.5 truncate px-2 py-0.5 text-left text-og-sm transition-colors hover:bg-og-surface-2 focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-og-accent pointer-coarse:min-h-11",
5680
6275
  grouped && "pl-3",
5681
6276
  active && "bg-og-accent-soft text-og-fg before:absolute before:inset-y-1 before:left-0 before:w-0.5 before:rounded-r-full before:bg-og-accent"
5682
6277
  ),
@@ -5774,7 +6369,7 @@ function GuardBody({
5774
6369
  {
5775
6370
  type: "button",
5776
6371
  onClick: () => onOpenFile(file.path),
5777
- className: "inline-flex min-h-8 shrink-0 items-center gap-1.5 rounded-og-sm border border-og-border px-2 py-1 text-og-xs font-medium text-og-fg-muted transition-colors hover:border-og-border-strong hover:bg-og-surface-2 hover:text-og-fg focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-og-accent pointer-coarse:min-h-11",
6372
+ className: "inline-flex min-h-8 shrink-0 items-center gap-1.5 rounded-og-sm border border-og-border px-2 py-1 text-og-xs font-medium text-og-fg-muted transition-colors hover:border-og-border-strong hover:bg-og-surface-2 hover:text-og-fg focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-og-accent pointer-coarse:min-h-11",
5778
6373
  children: [
5779
6374
  "Open in Files",
5780
6375
  /* @__PURE__ */ jsx6(ArrowUpRightIcon, { className: "size-3.5", "aria-hidden": true })
@@ -5793,7 +6388,7 @@ function LayoutToggle({
5793
6388
  type: "button",
5794
6389
  onClick: () => onChange(value),
5795
6390
  className: cn(
5796
- "min-h-7 rounded-og-xs px-1.5 py-0.5 text-og-xs capitalize focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-og-accent max-[1023px]:min-w-11 pointer-coarse:min-h-11 pointer-coarse:min-w-11",
6391
+ "min-h-7 rounded-og-xs px-1.5 py-0.5 text-og-xs capitalize focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-og-accent max-[1023px]:min-w-11 pointer-coarse:min-h-11 pointer-coarse:min-w-11",
5797
6392
  layout === value ? "bg-og-accent-soft text-og-fg" : "text-og-fg-subtle hover:text-og-fg"
5798
6393
  ),
5799
6394
  children: value
@@ -5912,7 +6507,7 @@ import { Loader2Icon, SaveIcon } from "lucide-react";
5912
6507
  import {
5913
6508
  useCallback as useCallback19,
5914
6509
  useEffect as useEffect19,
5915
- useMemo as useMemo10,
6510
+ useMemo as useMemo12,
5916
6511
  useRef as useRef16,
5917
6512
  useState as useState16
5918
6513
  } from "react";
@@ -5960,6 +6555,19 @@ function languageForPath(path) {
5960
6555
  return null;
5961
6556
  }
5962
6557
  }
6558
+ function reconcileEditorBuffer(current, incoming) {
6559
+ if (current.path === incoming.path && current.value !== current.baseline) {
6560
+ return current;
6561
+ }
6562
+ if (current.path === incoming.path && current.value === incoming.contents && current.baseline === incoming.contents) {
6563
+ return current;
6564
+ }
6565
+ return {
6566
+ path: incoming.path,
6567
+ value: incoming.contents,
6568
+ baseline: incoming.contents
6569
+ };
6570
+ }
5963
6571
  function CodeEditor({
5964
6572
  path,
5965
6573
  initialContents,
@@ -5975,28 +6583,30 @@ function CodeEditor({
5975
6583
  }) {
5976
6584
  const [failed, setFailed] = useState16(false);
5977
6585
  const [bundle, setBundle] = useState16(null);
5978
- const [value, setValue] = useState16(initialContents);
5979
- const [baseline, setBaseline] = useState16(initialContents);
6586
+ const [buffer, setBuffer] = useState16(() => ({
6587
+ path,
6588
+ value: initialContents,
6589
+ baseline: initialContents
6590
+ }));
6591
+ const { value, baseline } = buffer;
5980
6592
  const [saving, setSaving] = useState16(false);
5981
6593
  const [saveError, setSaveError] = useState16(null);
5982
6594
  const [savedTick, setSavedTick] = useState16(false);
6595
+ const saveInFlightRef = useRef16(false);
5983
6596
  const dirty = value !== baseline;
5984
6597
  const saveConflict = saveError !== null && saveError.code === "file_write_conflict";
5985
6598
  const editIntentFiredRef = useRef16(false);
5986
- const lastSeed = useRef16({ path, contents: initialContents });
5987
6599
  useEffect19(() => {
5988
- const seedChanged = lastSeed.current.path !== path || lastSeed.current.contents !== initialContents;
5989
- if (!seedChanged) return;
5990
- lastSeed.current = { path, contents: initialContents };
6600
+ setBuffer((current) => reconcileEditorBuffer(current, { path, contents: initialContents }));
6601
+ }, [path, initialContents]);
6602
+ useEffect19(() => {
5991
6603
  editIntentFiredRef.current = false;
5992
- setValue(initialContents);
5993
- setBaseline(initialContents);
5994
6604
  setSaveError(null);
5995
6605
  setSavedTick(false);
5996
- }, [path, initialContents]);
6606
+ }, [path]);
5997
6607
  const noteEdit = useCallback19(
5998
6608
  (next) => {
5999
- setValue(next);
6609
+ setBuffer((current) => ({ ...current, value: next }));
6000
6610
  setSavedTick(false);
6001
6611
  if (!editIntentFiredRef.current && next !== baseline) {
6002
6612
  editIntentFiredRef.current = true;
@@ -6005,7 +6615,7 @@ function CodeEditor({
6005
6615
  },
6006
6616
  [baseline, onEditIntent]
6007
6617
  );
6008
- const langKey = useMemo10(() => languageForPath(path), [path]);
6618
+ const langKey = useMemo12(() => languageForPath(path), [path]);
6009
6619
  useEffect19(() => {
6010
6620
  let cancelled = false;
6011
6621
  void (async () => {
@@ -6049,38 +6659,44 @@ function CodeEditor({
6049
6659
  const saveRef = useRef16(() => {
6050
6660
  });
6051
6661
  const save = useCallback19(async () => {
6052
- if (readOnly) return;
6662
+ if (readOnly || saveInFlightRef.current) return;
6053
6663
  const snapshot = value;
6054
6664
  if (snapshot === baseline) return;
6665
+ saveInFlightRef.current = true;
6055
6666
  setSaving(true);
6056
6667
  setSaveError(null);
6057
6668
  setSavedTick(false);
6058
6669
  try {
6059
- await onSave(snapshot);
6060
- setBaseline(snapshot);
6061
- lastSeed.current = { path, contents: snapshot };
6670
+ await onSave(snapshot, baseline);
6671
+ setBuffer(
6672
+ (current) => current.path === path ? { ...current, baseline: snapshot } : current
6673
+ );
6062
6674
  setSavedTick(true);
6063
6675
  } catch (cause) {
6064
6676
  setSaveError(cause instanceof Error ? cause : new Error(String(cause)));
6065
6677
  } finally {
6678
+ saveInFlightRef.current = false;
6066
6679
  setSaving(false);
6067
6680
  }
6068
6681
  }, [readOnly, value, baseline, onSave, path]);
6069
6682
  const overwrite = useCallback19(async () => {
6070
- if (readOnly || !onOverwrite) return;
6683
+ if (readOnly || !onOverwrite || saveInFlightRef.current) return;
6071
6684
  const snapshot = value;
6072
6685
  if (snapshot === baseline) return;
6686
+ saveInFlightRef.current = true;
6073
6687
  setSaving(true);
6074
6688
  setSaveError(null);
6075
6689
  setSavedTick(false);
6076
6690
  try {
6077
6691
  await onOverwrite(snapshot);
6078
- setBaseline(snapshot);
6079
- lastSeed.current = { path, contents: snapshot };
6692
+ setBuffer(
6693
+ (current) => current.path === path ? { ...current, baseline: snapshot } : current
6694
+ );
6080
6695
  setSavedTick(true);
6081
6696
  } catch (cause) {
6082
6697
  setSaveError(cause instanceof Error ? cause : new Error(String(cause)));
6083
6698
  } finally {
6699
+ saveInFlightRef.current = false;
6084
6700
  setSaving(false);
6085
6701
  }
6086
6702
  }, [readOnly, onOverwrite, value, baseline, path]);
@@ -6093,18 +6709,26 @@ function CodeEditor({
6093
6709
  return () => clearTimeout(t);
6094
6710
  }, [savedTick]);
6095
6711
  const fileName = path.split("/").filter(Boolean).pop() ?? path;
6096
- const cmExtensions = useMemo10(() => {
6712
+ const cmExtensions = useMemo12(() => {
6097
6713
  if (!bundle) return [];
6098
6714
  const exts = [bundle.saveKeymapExtension(() => saveRef.current())];
6099
6715
  if (bundle.languageExtension) exts.push(bundle.languageExtension);
6100
6716
  return exts;
6101
6717
  }, [bundle]);
6102
6718
  const Editor = bundle?.Editor;
6719
+ const reload = useCallback19(() => {
6720
+ setBuffer({ path, value: initialContents, baseline: initialContents });
6721
+ editIntentFiredRef.current = false;
6722
+ setSaveError(null);
6723
+ setSavedTick(false);
6724
+ onReload?.();
6725
+ }, [initialContents, onReload, path]);
6103
6726
  return /* @__PURE__ */ jsxs7(
6104
6727
  "div",
6105
6728
  {
6106
6729
  className: cn("flex h-full min-h-0 flex-col", className),
6107
6730
  "data-opengeni-code-editor": true,
6731
+ "data-opengeni-editor-dirty": dirty ? "true" : "false",
6108
6732
  style: editorVars,
6109
6733
  children: [
6110
6734
  /* @__PURE__ */ jsxs7("div", { className: "flex min-h-9 shrink-0 items-center gap-2 border-b border-og-border bg-og-surface-1 px-2 py-1", children: [
@@ -6131,7 +6755,7 @@ function CodeEditor({
6131
6755
  children: saveError.message
6132
6756
  }
6133
6757
  ),
6134
- !saveError && savedTick && /* @__PURE__ */ jsx9("span", { className: "text-og-xs text-og-status-idle", children: "Saved" }),
6758
+ !saveError && savedTick && !dirty && /* @__PURE__ */ jsx9("span", { className: "text-og-xs text-og-status-idle", children: "Saved" }),
6135
6759
  readOnly ? /* @__PURE__ */ jsx9("span", { className: "text-og-xs uppercase tracking-wide text-og-fg-subtle", children: "Read-only" }) : !saveConflict ? /* @__PURE__ */ jsxs7(
6136
6760
  "button",
6137
6761
  {
@@ -6168,8 +6792,7 @@ function CodeEditor({
6168
6792
  {
6169
6793
  type: "button",
6170
6794
  onClick: () => {
6171
- setSaveError(null);
6172
- onReload();
6795
+ reload();
6173
6796
  },
6174
6797
  className: "min-h-8 rounded-og-sm border border-og-border bg-og-surface-1 px-2 text-og-xs font-medium text-og-fg hover:bg-og-surface-2 pointer-coarse:min-h-11",
6175
6798
  children: "Reload live"
@@ -6239,7 +6862,7 @@ function PlainTextarea({
6239
6862
  }
6240
6863
  },
6241
6864
  "aria-label": "Plain text file editor",
6242
- className: "h-full w-full resize-none bg-transparent p-2 font-og-mono text-og-sm text-og-fg outline-none"
6865
+ className: "h-full w-full resize-none bg-transparent p-2 font-og-mono text-og-sm text-og-fg outline-hidden"
6243
6866
  }
6244
6867
  );
6245
6868
  }
@@ -6250,6 +6873,7 @@ function EditorSkeleton() {
6250
6873
  // src/components/sandbox-files.tsx
6251
6874
  import { FileCode2Icon, FileWarningIcon as FileWarningIcon2, LoaderCircleIcon } from "lucide-react";
6252
6875
  import { useCallback as useCallback20, useEffect as useEffect20, useRef as useRef17, useState as useState17 } from "react";
6876
+ import { Group, Panel, Separator } from "react-resizable-panels";
6253
6877
  import { Fragment as Fragment3, jsx as jsx10, jsxs as jsxs8 } from "react/jsx-runtime";
6254
6878
  function SandboxFiles({
6255
6879
  files,
@@ -6297,17 +6921,30 @@ function SandboxFiles({
6297
6921
  const [wide, setWide] = useState17(false);
6298
6922
  useEffect20(() => {
6299
6923
  const el = rootRef.current;
6300
- if (!el || typeof ResizeObserver === "undefined") return;
6301
- const observer = new ResizeObserver((entries) => {
6302
- const w = entries[0]?.contentRect.width ?? 0;
6303
- setWide(w >= 720);
6304
- });
6924
+ if (!el) return;
6925
+ const update = () => setWide(el.getBoundingClientRect().width >= 720);
6926
+ update();
6927
+ if (typeof ResizeObserver === "undefined") return;
6928
+ const observer = new ResizeObserver(update);
6305
6929
  observer.observe(el);
6306
- return () => observer.disconnect();
6930
+ const surface = el.closest("[data-workspace-surface]");
6931
+ const surfaceObserver = surface && typeof MutationObserver !== "undefined" ? new MutationObserver(update) : null;
6932
+ surfaceObserver?.observe(surface, {
6933
+ attributes: true,
6934
+ attributeFilter: ["class", "style"]
6935
+ });
6936
+ return () => {
6937
+ observer.disconnect();
6938
+ surfaceObserver?.disconnect();
6939
+ };
6307
6940
  }, []);
6308
6941
  const resolvedTheme = useThemeType(themeType);
6309
6942
  const viewPath = selected;
6310
- const fileView = useFileView(viewPath, files.readFile, viewReloadRevision);
6943
+ const fileView = useFileView(
6944
+ viewPath,
6945
+ files.readFile,
6946
+ (files.contentRevision ?? 0) + viewReloadRevision
6947
+ );
6311
6948
  const selectFile = useCallback20((path) => {
6312
6949
  if (pendingRequestRef.current !== null) {
6313
6950
  handledRequestRef.current = pendingRequestRef.current;
@@ -6322,18 +6959,6 @@ function SandboxFiles({
6322
6959
  const showEditor = canEdit && editMode;
6323
6960
  const captureFileUnavailable = fileView.error instanceof CapturedFileUnavailableError ? fileView.error : null;
6324
6961
  const waitingForSelectedFile = liveRequestedPath === viewPath && (!liveWorkspaceReady || files.loading) && captureFileUnavailable !== null;
6325
- if (!fileSystemAvailable) {
6326
- return /* @__PURE__ */ jsx10(
6327
- Notice,
6328
- {
6329
- className,
6330
- icon: /* @__PURE__ */ jsx10(FileWarningIcon2, { className: "size-5", "aria-hidden": true }),
6331
- title: "Files unavailable",
6332
- announce: "alert",
6333
- children: "This sandbox does not expose a file system."
6334
- }
6335
- );
6336
- }
6337
6962
  if (workspaceResting || workspaceWaking) {
6338
6963
  return /* @__PURE__ */ jsx10("div", { className: cn("h-full", className), "data-opengeni-workspace-resting": true, children: /* @__PURE__ */ jsxs8(
6339
6964
  Notice,
@@ -6354,162 +6979,206 @@ function SandboxFiles({
6354
6979
  }
6355
6980
  ) });
6356
6981
  }
6982
+ if (!fileSystemAvailable) {
6983
+ return /* @__PURE__ */ jsx10(
6984
+ Notice,
6985
+ {
6986
+ className,
6987
+ icon: /* @__PURE__ */ jsx10(FileWarningIcon2, { className: "size-5", "aria-hidden": true }),
6988
+ title: "Files unavailable",
6989
+ announce: "alert",
6990
+ children: "This sandbox does not expose a file system."
6991
+ }
6992
+ );
6993
+ }
6357
6994
  return /* @__PURE__ */ jsxs8("div", { ref: rootRef, className: cn("flex h-full min-h-0 min-w-0 flex-col", className), children: [
6358
- /* @__PURE__ */ jsx10(GitHeader, { git, dirtyCount: git.diff.length }),
6359
- /* @__PURE__ */ jsxs8("div", { className: cn("flex min-h-0 flex-1", wide ? "flex-row" : "flex-col"), children: [
6360
- /* @__PURE__ */ jsx10(
6361
- "div",
6362
- {
6363
- className: cn(
6364
- "flex min-h-0 flex-col",
6365
- wide ? "w-[280px] shrink-0 border-r border-og-border" : "flex-1"
6366
- ),
6367
- children: /* @__PURE__ */ jsx10(
6368
- FileBrowser,
6369
- {
6370
- result: files,
6371
- selectedPath: selected ?? void 0,
6372
- onSelectFile: selectFile,
6373
- editable,
6374
- emptyState: "This directory is empty",
6375
- className: "min-w-0 flex-1"
6376
- }
6377
- )
6995
+ /* @__PURE__ */ jsx10(
6996
+ GitHeader,
6997
+ {
6998
+ loading: Boolean(files.loading || files.gitLoading || files.source === null || git?.loading) && files.gitSummary === null,
6999
+ git: files.gitSummary ?? {
7000
+ branch: git?.branch ?? null,
7001
+ isRepo: git?.isRepo ?? false,
7002
+ repoCount: git?.repoCount ?? 0,
7003
+ ahead: git?.ahead ?? 0,
7004
+ behind: git?.behind ?? 0,
7005
+ dirtyCount: git?.diff.length ?? 0
6378
7006
  }
6379
- ),
6380
- /* @__PURE__ */ jsxs8(
6381
- "div",
6382
- {
6383
- className: cn(
6384
- "flex min-h-0 min-w-0 flex-col",
6385
- wide ? "flex-1" : "flex-[1.4] border-t border-og-border"
6386
- ),
6387
- children: [
6388
- /* @__PURE__ */ jsxs8("div", { className: "flex shrink-0 items-center justify-between gap-2 border-b border-og-border bg-og-surface-1 px-2 py-1", children: [
6389
- /* @__PURE__ */ jsx10(
6390
- "span",
6391
- {
6392
- "data-opengeni-selected-file": true,
6393
- className: "min-w-0 truncate font-og-mono text-og-xs text-og-fg-muted",
6394
- children: selected ?? "No file selected"
6395
- }
6396
- ),
6397
- canEdit && /* @__PURE__ */ jsx10(
6398
- Segmented,
7007
+ }
7008
+ ),
7009
+ /* @__PURE__ */ jsxs8(
7010
+ Group,
7011
+ {
7012
+ orientation: wide ? "horizontal" : "vertical",
7013
+ className: cn("min-h-0 flex-1", wide ? "flex-row" : "flex-col"),
7014
+ children: [
7015
+ /* @__PURE__ */ jsx10(
7016
+ Panel,
7017
+ {
7018
+ id: "sandbox-files-tree",
7019
+ defaultSize: wide ? "280px" : "40%",
7020
+ minSize: wide ? "180px" : "120px",
7021
+ maxSize: wide ? "60%" : "70%",
7022
+ className: "flex min-h-0 min-w-0 flex-col",
7023
+ children: /* @__PURE__ */ jsx10(
7024
+ FileBrowser,
6399
7025
  {
6400
- options: [
6401
- { value: "view", label: "View" },
6402
- { value: "edit", label: "Edit" }
6403
- ],
6404
- value: editMode ? "edit" : "view",
6405
- onChange: (v) => setEditMode(v === "edit")
7026
+ result: files,
7027
+ selectedPath: selected ?? void 0,
7028
+ onSelectFile: selectFile,
7029
+ editable,
7030
+ emptyState: "This directory is empty",
7031
+ className: "min-w-0 flex-1"
6406
7032
  }
6407
7033
  )
6408
- ] }),
6409
- /* @__PURE__ */ jsx10("div", { className: "min-h-0 flex-1 overflow-auto", children: viewPath ? showEditor && fileView.content !== null ? /* @__PURE__ */ jsx10(
6410
- CodeEditor,
6411
- {
6412
- path: viewPath,
6413
- initialContents: fileView.content,
6414
- themeType: resolvedTheme,
6415
- onSave: (contents) => files.writeFile(viewPath, contents, { expectedContent: fileView.content }),
6416
- onOverwrite: (contents) => files.writeFile(viewPath, contents, { force: true }),
6417
- onReload: () => setViewReloadRevision((revision) => revision + 1),
6418
- ...onEditIntent ? { onEditIntent } : {},
6419
- className: "h-full"
6420
- },
6421
- viewPath
6422
- ) : waitingForSelectedFile ? /* @__PURE__ */ jsxs8(
6423
- Notice,
6424
- {
6425
- icon: /* @__PURE__ */ jsx10(
6426
- LoaderCircleIcon,
7034
+ }
7035
+ ),
7036
+ /* @__PURE__ */ jsx10(
7037
+ Separator,
7038
+ {
7039
+ "aria-label": "Resize file tree",
7040
+ className: cn(
7041
+ "group relative z-10 shrink-0 cursor-col-resize outline-hidden focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-og-accent",
7042
+ "w-px bg-og-border after:absolute after:inset-y-0 after:left-1/2 after:w-3 after:-translate-x-1/2",
7043
+ "hover:bg-og-accent focus-visible:bg-og-accent data-[separator-state=dragging]:bg-og-accent",
7044
+ !wide && "h-px w-full cursor-row-resize after:inset-x-0 after:inset-y-auto after:top-1/2 after:h-3 after:w-full after:-translate-x-0 after:-translate-y-1/2"
7045
+ )
7046
+ }
7047
+ ),
7048
+ /* @__PURE__ */ jsxs8(
7049
+ Panel,
7050
+ {
7051
+ id: "sandbox-files-viewer",
7052
+ minSize: wide ? "240px" : "160px",
7053
+ className: cn("flex min-h-0 min-w-0 flex-col", !wide && "border-t border-og-border"),
7054
+ children: [
7055
+ /* @__PURE__ */ jsxs8("div", { className: "flex shrink-0 items-center justify-between gap-2 border-b border-og-border bg-og-surface-1 px-2 py-1", children: [
7056
+ /* @__PURE__ */ jsx10(
7057
+ "span",
7058
+ {
7059
+ "data-opengeni-selected-file": true,
7060
+ className: "min-w-0 truncate font-og-mono text-og-xs text-og-fg-muted",
7061
+ children: selected ?? "No file selected"
7062
+ }
7063
+ ),
7064
+ canEdit && /* @__PURE__ */ jsx10(
7065
+ Segmented,
7066
+ {
7067
+ options: [
7068
+ { value: "view", label: "View" },
7069
+ { value: "edit", label: "Edit" }
7070
+ ],
7071
+ value: editMode ? "edit" : "view",
7072
+ onChange: (v) => setEditMode(v === "edit")
7073
+ }
7074
+ )
7075
+ ] }),
7076
+ /* @__PURE__ */ jsx10("div", { className: "min-h-0 flex-1 overflow-auto", children: viewPath ? showEditor && fileView.content !== null ? /* @__PURE__ */ jsx10(
7077
+ CodeEditor,
6427
7078
  {
6428
- className: "size-5 animate-spin motion-reduce:animate-none",
6429
- "aria-hidden": true
7079
+ path: viewPath,
7080
+ initialContents: fileView.content,
7081
+ themeType: resolvedTheme,
7082
+ onSave: (contents, expectedContents) => files.writeFile(viewPath, contents, { expectedContent: expectedContents }),
7083
+ onOverwrite: (contents) => files.writeFile(viewPath, contents, { force: true }),
7084
+ onReload: () => setViewReloadRevision((revision) => revision + 1),
7085
+ ...onEditIntent ? { onEditIntent } : {},
7086
+ className: "h-full"
7087
+ },
7088
+ viewPath
7089
+ ) : waitingForSelectedFile ? /* @__PURE__ */ jsxs8(
7090
+ Notice,
7091
+ {
7092
+ icon: /* @__PURE__ */ jsx10(
7093
+ LoaderCircleIcon,
7094
+ {
7095
+ className: "size-5 animate-spin motion-reduce:animate-none",
7096
+ "aria-hidden": true
7097
+ }
7098
+ ),
7099
+ title: "Waking workspace",
7100
+ announce: "status",
7101
+ children: [
7102
+ "Opening ",
7103
+ viewPath,
7104
+ " when the live file system is ready\u2026"
7105
+ ]
6430
7106
  }
6431
- ),
6432
- title: "Waking workspace",
6433
- announce: "status",
6434
- children: [
6435
- "Opening ",
7107
+ ) : captureFileUnavailable ? /* @__PURE__ */ jsxs8(Notice, { icon: /* @__PURE__ */ jsx10(FileCode2Icon, { className: "size-5", "aria-hidden": true }), title: "On machine", children: [
7108
+ /* @__PURE__ */ jsx10("p", { children: captureFileUnavailable.reason === "too-large" ? "This file is larger than the captured preview limit." : captureFileUnavailable.reason === "content-missing" ? "The captured copy is no longer available." : "This file was indexed, but it was not changed in the captured turn." }),
7109
+ onWakeWorkspace ? /* @__PURE__ */ jsx10(
7110
+ WakeButton,
7111
+ {
7112
+ onClick: () => {
7113
+ setLiveRequestedPath(viewPath);
7114
+ if (liveWorkspaceReady) void files.refresh();
7115
+ else onWakeWorkspace();
7116
+ },
7117
+ children: liveWorkspaceReady ? "Retry live file" : "Open live file"
7118
+ }
7119
+ ) : null
7120
+ ] }) : fileView.error ? /* @__PURE__ */ jsxs8(Notice, { announce: "alert", children: [
7121
+ "Could not open ",
6436
7122
  viewPath,
6437
- " when the live file system is ready\u2026"
6438
- ]
6439
- }
6440
- ) : captureFileUnavailable ? /* @__PURE__ */ jsxs8(Notice, { icon: /* @__PURE__ */ jsx10(FileCode2Icon, { className: "size-5", "aria-hidden": true }), title: "On machine", children: [
6441
- /* @__PURE__ */ jsx10("p", { children: captureFileUnavailable.reason === "too-large" ? "This file is larger than the captured preview limit." : captureFileUnavailable.reason === "content-missing" ? "The captured copy is no longer available." : "This file was indexed, but it was not changed in the captured turn." }),
6442
- onWakeWorkspace ? /* @__PURE__ */ jsx10(
6443
- WakeButton,
6444
- {
6445
- onClick: () => {
6446
- setLiveRequestedPath(viewPath);
6447
- if (liveWorkspaceReady) void files.refresh();
6448
- else onWakeWorkspace();
6449
- },
6450
- children: liveWorkspaceReady ? "Retry live file" : "Open live file"
6451
- }
6452
- ) : null
6453
- ] }) : fileView.error ? /* @__PURE__ */ jsxs8(Notice, { announce: "alert", children: [
6454
- "Could not open ",
6455
- viewPath,
6456
- ": ",
6457
- fileView.error.message
6458
- ] }) : fileView.loading ? /* @__PURE__ */ jsxs8(Notice, { announce: "status", children: [
6459
- "Loading ",
6460
- viewPath,
6461
- "\u2026"
6462
- ] }) : fileView.isBinary ? /* @__PURE__ */ jsxs8(Notice, { children: [
6463
- viewPath,
6464
- " is a binary file (",
6465
- fileView.sizeBytes ?? 0,
6466
- " bytes)."
6467
- ] }) : fileView.content !== null ? /* @__PURE__ */ jsxs8(Fragment3, { children: [
6468
- fileView.truncated && /* @__PURE__ */ jsxs8("div", { className: "border-b border-og-border bg-og-surface-1 px-2 py-1 text-og-xs text-og-status-running", children: [
6469
- "Large file \u2014 showing a truncated preview (",
6470
- fileView.sizeBytes ?? 0,
6471
- " bytes loaded). Editing is disabled to avoid corrupting the file."
6472
- ] }),
6473
- usePierre ? /* @__PURE__ */ jsx10(
6474
- PierreFile,
6475
- {
6476
- path: viewPath,
6477
- contents: fileView.content,
6478
- themeType: resolvedTheme,
6479
- fallback: /* @__PURE__ */ jsx10("pre", { className: "overflow-auto whitespace-pre p-2 font-og-mono text-og-sm text-og-fg", children: fileView.content }),
6480
- className: "p-1"
6481
- }
6482
- ) : /* @__PURE__ */ jsx10("pre", { className: "overflow-auto whitespace-pre p-2 font-og-mono text-og-sm text-og-fg", children: fileView.content })
6483
- ] }) : /* @__PURE__ */ jsxs8(Notice, { announce: "status", children: [
6484
- "Loading ",
6485
- viewPath,
6486
- "\u2026"
6487
- ] }) : (
6488
- // Nothing selected — the tree shows the whole workspace; pick a file.
6489
- requestedPath && !requestedPathReady ? /* @__PURE__ */ jsxs8(
6490
- Notice,
6491
- {
6492
- icon: /* @__PURE__ */ jsx10(
6493
- LoaderCircleIcon,
7123
+ ": ",
7124
+ fileView.error.message
7125
+ ] }) : fileView.loading ? /* @__PURE__ */ jsxs8(Notice, { announce: "status", children: [
7126
+ "Loading ",
7127
+ viewPath,
7128
+ "\u2026"
7129
+ ] }) : fileView.isBinary ? /* @__PURE__ */ jsxs8(Notice, { children: [
7130
+ viewPath,
7131
+ " is a binary file (",
7132
+ fileView.sizeBytes ?? 0,
7133
+ " bytes)."
7134
+ ] }) : fileView.content !== null ? /* @__PURE__ */ jsxs8(Fragment3, { children: [
7135
+ fileView.truncated && /* @__PURE__ */ jsxs8("div", { className: "border-b border-og-border bg-og-surface-1 px-2 py-1 text-og-xs text-og-status-running", children: [
7136
+ "Large file \u2014 showing a truncated preview (",
7137
+ fileView.sizeBytes ?? 0,
7138
+ " bytes loaded). Editing is disabled to avoid corrupting the file."
7139
+ ] }),
7140
+ usePierre ? /* @__PURE__ */ jsx10(
7141
+ PierreFile,
6494
7142
  {
6495
- className: "size-5 animate-spin motion-reduce:animate-none",
6496
- "aria-hidden": true
7143
+ path: viewPath,
7144
+ contents: fileView.content,
7145
+ themeType: resolvedTheme,
7146
+ fallback: /* @__PURE__ */ jsx10("pre", { className: "overflow-auto whitespace-pre p-2 font-og-mono text-og-sm text-og-fg", children: fileView.content }),
7147
+ className: "p-1"
6497
7148
  }
6498
- ),
6499
- title: "Waking sandbox",
6500
- announce: "status",
6501
- children: [
6502
- "Opening ",
6503
- requestedPath,
6504
- " when the live workspace is ready\u2026"
6505
- ]
6506
- }
6507
- ) : /* @__PURE__ */ jsx10(Notice, { icon: /* @__PURE__ */ jsx10(FileCode2Icon, { className: "size-5", "aria-hidden": true }), title: "Choose a file", children: "Select a file in the tree to preview it." })
6508
- ) })
6509
- ]
6510
- }
6511
- )
6512
- ] })
7149
+ ) : /* @__PURE__ */ jsx10("pre", { className: "overflow-auto whitespace-pre p-2 font-og-mono text-og-sm text-og-fg", children: fileView.content })
7150
+ ] }) : /* @__PURE__ */ jsxs8(Notice, { announce: "status", children: [
7151
+ "Loading ",
7152
+ viewPath,
7153
+ "\u2026"
7154
+ ] }) : (
7155
+ // Nothing selected — the tree shows the whole workspace; pick a file.
7156
+ requestedPath && !requestedPathReady ? /* @__PURE__ */ jsxs8(
7157
+ Notice,
7158
+ {
7159
+ icon: /* @__PURE__ */ jsx10(
7160
+ LoaderCircleIcon,
7161
+ {
7162
+ className: "size-5 animate-spin motion-reduce:animate-none",
7163
+ "aria-hidden": true
7164
+ }
7165
+ ),
7166
+ title: "Waking sandbox",
7167
+ announce: "status",
7168
+ children: [
7169
+ "Opening ",
7170
+ requestedPath,
7171
+ " when the live workspace is ready\u2026"
7172
+ ]
7173
+ }
7174
+ ) : /* @__PURE__ */ jsx10(Notice, { icon: /* @__PURE__ */ jsx10(FileCode2Icon, { className: "size-5", "aria-hidden": true }), title: "Choose a file", children: "Select a file in the tree to preview it." })
7175
+ ) })
7176
+ ]
7177
+ }
7178
+ )
7179
+ ]
7180
+ }
7181
+ )
6513
7182
  ] });
6514
7183
  }
6515
7184
  function WakeButton({ children, onClick }) {
@@ -6518,13 +7187,33 @@ function WakeButton({ children, onClick }) {
6518
7187
  {
6519
7188
  type: "button",
6520
7189
  onClick,
6521
- className: "mt-1 inline-flex min-h-11 items-center justify-center rounded-og-md bg-og-accent-deep px-3 py-2 text-og-sm font-medium text-og-accent-fg shadow-sm transition-opacity hover:opacity-90 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-og-accent focus-visible:ring-offset-2 focus-visible:ring-offset-og-bg",
7190
+ className: "mt-1 inline-flex min-h-11 items-center justify-center rounded-og-md bg-og-accent-deep px-3 py-2 text-og-sm font-medium text-og-accent-fg shadow-sm transition-opacity hover:opacity-90 focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-og-accent focus-visible:ring-offset-2 focus-visible:ring-offset-og-bg",
6522
7191
  children
6523
7192
  }
6524
7193
  );
6525
7194
  }
6526
- function GitHeader({ git, dirtyCount }) {
6527
- const dirty = dirtyCount > 0;
7195
+ function GitHeader({ git, loading }) {
7196
+ if (loading) {
7197
+ return /* @__PURE__ */ jsxs8(
7198
+ "div",
7199
+ {
7200
+ className: "flex shrink-0 items-center gap-2 border-b border-og-border bg-og-surface-1 px-2 py-1 text-og-sm text-og-fg-muted",
7201
+ role: "status",
7202
+ "aria-live": "polite",
7203
+ children: [
7204
+ /* @__PURE__ */ jsx10(
7205
+ LoaderCircleIcon,
7206
+ {
7207
+ className: "size-3.5 shrink-0 animate-spin motion-reduce:animate-none",
7208
+ "aria-hidden": true
7209
+ }
7210
+ ),
7211
+ /* @__PURE__ */ jsx10("span", { children: "Loading repository\u2026" })
7212
+ ]
7213
+ }
7214
+ );
7215
+ }
7216
+ const dirty = git.dirtyCount > 0;
6528
7217
  return /* @__PURE__ */ jsxs8("div", { className: "flex shrink-0 items-center gap-2 border-b border-og-border bg-og-surface-1 px-2 py-1 text-og-sm", children: [
6529
7218
  /* @__PURE__ */ jsx10(
6530
7219
  "span",
@@ -6536,7 +7225,7 @@ function GitHeader({ git, dirtyCount }) {
6536
7225
  )
6537
7226
  }
6538
7227
  ),
6539
- /* @__PURE__ */ jsx10("span", { className: "sr-only", children: dirty ? `Working tree has ${dirtyCount} changed files` : "Working tree clean" }),
7228
+ /* @__PURE__ */ jsx10("span", { className: "sr-only", children: dirty ? `Uncommitted changes in ${git.dirtyCount} files` : "No uncommitted changes" }),
6540
7229
  /* @__PURE__ */ jsx10("span", { className: "truncate font-og-mono text-og-fg", children: git.repoCount > 1 ? `${git.repoCount} repositories` : git.branch ?? (git.isRepo ? "(detached)" : "no repo") }),
6541
7230
  (git.ahead > 0 || git.behind > 0) && /* @__PURE__ */ jsxs8("span", { className: "flex shrink-0 items-center gap-1.5 text-og-xs text-og-fg-subtle", children: [
6542
7231
  git.ahead > 0 && /* @__PURE__ */ jsxs8("span", { children: [
@@ -6555,7 +7244,7 @@ function GitHeader({ git, dirtyCount }) {
6555
7244
  "data-contrast-audited": true,
6556
7245
  className: "ml-auto shrink-0 text-og-xs text-og-fg-subtle",
6557
7246
  children: [
6558
- dirtyCount,
7247
+ git.dirtyCount,
6559
7248
  " changed"
6560
7249
  ]
6561
7250
  }
@@ -6573,7 +7262,7 @@ function Segmented({
6573
7262
  type: "button",
6574
7263
  onClick: () => onChange(opt.value),
6575
7264
  className: cn(
6576
- "min-h-7 rounded-og-xs px-1.5 py-0.5 text-og-xs focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-og-accent max-[1023px]:min-h-11 max-[1023px]:min-w-11 pointer-coarse:min-h-11 pointer-coarse:min-w-11",
7265
+ "min-h-7 rounded-og-xs px-1.5 py-0.5 text-og-xs focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-og-accent max-[1023px]:min-h-11 max-[1023px]:min-w-11 pointer-coarse:min-h-11 pointer-coarse:min-w-11",
6577
7266
  opt.value === value ? "bg-og-accent-soft text-og-fg" : "text-og-fg-subtle hover:text-og-fg"
6578
7267
  ),
6579
7268
  children: opt.label
@@ -6582,6 +7271,7 @@ function Segmented({
6582
7271
  )) });
6583
7272
  }
6584
7273
  function useFileView(path, readFile, reloadRevision = 0) {
7274
+ const previousPathRef = useRef17(null);
6585
7275
  const [state, setState] = useState17({
6586
7276
  content: null,
6587
7277
  isBinary: false,
@@ -6592,6 +7282,7 @@ function useFileView(path, readFile, reloadRevision = 0) {
6592
7282
  });
6593
7283
  useEffect20(() => {
6594
7284
  if (!path) {
7285
+ previousPathRef.current = null;
6595
7286
  setState({
6596
7287
  content: null,
6597
7288
  isBinary: false,
@@ -6602,16 +7293,22 @@ function useFileView(path, readFile, reloadRevision = 0) {
6602
7293
  });
6603
7294
  return;
6604
7295
  }
7296
+ const pathChanged = previousPathRef.current !== path;
7297
+ previousPathRef.current = path;
6605
7298
  let cancelled = false;
6606
7299
  const abort = new AbortController();
6607
- setState({
6608
- content: null,
6609
- isBinary: false,
6610
- truncated: false,
6611
- sizeBytes: null,
6612
- loading: true,
6613
- error: null
6614
- });
7300
+ if (pathChanged) {
7301
+ setState({
7302
+ content: null,
7303
+ isBinary: false,
7304
+ truncated: false,
7305
+ sizeBytes: null,
7306
+ loading: true,
7307
+ error: null
7308
+ });
7309
+ } else {
7310
+ setState((previous) => ({ ...previous, error: null }));
7311
+ }
6615
7312
  void readFile(path, { signal: abort.signal }).then((res) => {
6616
7313
  if (cancelled) return;
6617
7314
  const content = res.isBinary ? null : res.encoding === "base64" ? decodeBase64Utf8(res.content) : res.content;
@@ -6625,14 +7322,17 @@ function useFileView(path, readFile, reloadRevision = 0) {
6625
7322
  });
6626
7323
  }).catch((cause) => {
6627
7324
  if (cancelled) return;
6628
- setState({
6629
- content: null,
6630
- isBinary: false,
6631
- truncated: false,
6632
- sizeBytes: null,
6633
- loading: false,
6634
- error: cause instanceof Error ? cause : new Error(String(cause))
6635
- });
7325
+ const error = cause instanceof Error ? cause : new Error(String(cause));
7326
+ setState(
7327
+ (previous) => pathChanged ? {
7328
+ content: null,
7329
+ isBinary: false,
7330
+ truncated: false,
7331
+ sizeBytes: null,
7332
+ loading: false,
7333
+ error
7334
+ } : { ...previous, loading: false, error }
7335
+ );
6636
7336
  });
6637
7337
  return () => {
6638
7338
  cancelled = true;
@@ -6703,7 +7403,7 @@ function DesktopViewer({
6703
7403
  scaleViewport,
6704
7404
  rfbFactory,
6705
7405
  webSocketFactory,
6706
- renderConsentGate,
7406
+ onActivate,
6707
7407
  onAcknowledge,
6708
7408
  watching,
6709
7409
  onWarm,
@@ -6715,22 +7415,55 @@ function DesktopViewer({
6715
7415
  className
6716
7416
  }) {
6717
7417
  const containerRef = useRef18(null);
6718
- const [consented, setConsented] = useState18(false);
7418
+ const onActivateRef = useRef18(onActivate);
7419
+ const onAcknowledgeRef = useRef18(onAcknowledge);
7420
+ onActivateRef.current = onActivate;
7421
+ onAcknowledgeRef.current = onAcknowledge;
7422
+ const acknowledgmentAttemptedRef = useRef18(false);
7423
+ const [acknowledgmentError, setAcknowledgmentError] = useState18(null);
7424
+ const [acknowledgmentRetryNonce, setAcknowledgmentRetryNonce] = useState18(0);
6719
7425
  const [takeControl, setTakeControl] = useState18(interactive ?? false);
6720
7426
  const externallyControlled = interactive !== void 0;
6721
7427
  const serverAllowsControl = capability?.mode !== "read-only";
6722
7428
  const wantControl = externallyControlled ? interactive : takeControl;
6723
7429
  const inControl = Boolean(wantControl) && serverAllowsControl;
6724
- const isWatching = watching ?? consented;
7430
+ const isWatching = watching ?? true;
6725
7431
  const transportNull = !capability || capability.transport === null;
6726
7432
  const reason = capability?.reason ?? null;
6727
- const needsAck = capability?.requiresAcknowledgment === true && capability.acknowledged !== true && !consented;
7433
+ const needsAcknowledgment = capability?.requiresAcknowledgment === true && capability.acknowledged !== true;
6728
7434
  const noLiveAddress = Boolean(capability) && !transportNull && !capability.url;
6729
7435
  const coldWarmable = transportNull && reason === "lease_cold" || !transportNull && noLiveAddress;
6730
7436
  const hardUnavailable = transportNull && isHardUnavailable(reason);
6731
- const accept = () => {
6732
- setConsented(true);
6733
- onAcknowledge?.();
7437
+ useEffect21(() => {
7438
+ onActivateRef.current?.();
7439
+ }, []);
7440
+ useEffect21(() => {
7441
+ if (!needsAcknowledgment) {
7442
+ acknowledgmentAttemptedRef.current = false;
7443
+ setAcknowledgmentError(null);
7444
+ return;
7445
+ }
7446
+ if (acknowledgmentAttemptedRef.current) return;
7447
+ acknowledgmentAttemptedRef.current = true;
7448
+ setAcknowledgmentError(null);
7449
+ const acknowledge = onAcknowledgeRef.current;
7450
+ if (!acknowledge) {
7451
+ setAcknowledgmentError(new Error("Desktop acknowledgment is not configured."));
7452
+ return;
7453
+ }
7454
+ let active = true;
7455
+ void Promise.resolve().then(() => acknowledge()).catch((cause) => {
7456
+ if (!active) return;
7457
+ setAcknowledgmentError(cause instanceof Error ? cause : new Error(String(cause)));
7458
+ });
7459
+ return () => {
7460
+ active = false;
7461
+ };
7462
+ }, [needsAcknowledgment, acknowledgmentRetryNonce]);
7463
+ const retryAcknowledgment = () => {
7464
+ acknowledgmentAttemptedRef.current = false;
7465
+ setAcknowledgmentError(null);
7466
+ setAcknowledgmentRetryNonce((nonce) => nonce + 1);
6734
7467
  };
6735
7468
  useEffect21(() => {
6736
7469
  if (!inControl || externallyControlled) return;
@@ -6745,7 +7478,7 @@ function DesktopViewer({
6745
7478
  window.removeEventListener("keydown", onKey);
6746
7479
  };
6747
7480
  }, [inControl, externallyControlled]);
6748
- const connectCapability = !transportNull && !needsAck && !viewerCapReached ? capability : null;
7481
+ const connectCapability = !transportNull && !needsAcknowledgment && !viewerCapReached ? capability : null;
6749
7482
  const stream = useDesktopStream({
6750
7483
  capability: connectCapability,
6751
7484
  containerRef,
@@ -6760,17 +7493,16 @@ function DesktopViewer({
6760
7493
  streamStateRef.current = stream.state;
6761
7494
  const warmKeyRef = useRef18(null);
6762
7495
  useEffect21(() => {
6763
- if (!isWatching || !coldWarmable || needsAck || viewerCapReached) {
7496
+ if (!isWatching || !coldWarmable || needsAcknowledgment || viewerCapReached) {
6764
7497
  if (!coldWarmable) warmKeyRef.current = null;
6765
7498
  return;
6766
7499
  }
6767
- const warm = onWarm ?? onAcknowledge;
6768
- if (!warm) return;
7500
+ if (!onWarm) return;
6769
7501
  const key = `${capability?.reason ?? ""}:${capability?.url ?? ""}:${capability?.expiresAt ?? ""}`;
6770
7502
  if (warmKeyRef.current === key) return;
6771
7503
  warmKeyRef.current = key;
6772
- warm();
6773
- }, [isWatching, coldWarmable, needsAck, viewerCapReached, capability, onWarm, onAcknowledge]);
7504
+ onWarm();
7505
+ }, [isWatching, coldWarmable, needsAcknowledgment, viewerCapReached, capability, onWarm]);
6774
7506
  useEffect21(() => {
6775
7507
  if (!hasLiveUrl || typeof document === "undefined") return;
6776
7508
  const maybeReattach = () => {
@@ -6803,13 +7535,14 @@ function DesktopViewer({
6803
7535
  let uiState;
6804
7536
  if (viewerCapReached) uiState = "viewer_cap";
6805
7537
  else if (hardUnavailable) uiState = "unavailable";
6806
- else if (needsAck && !isWatching) uiState = "consent";
7538
+ else if (acknowledgmentError) uiState = "acknowledgment_error";
7539
+ else if (needsAcknowledgment) uiState = "opening";
6807
7540
  else if (coldWarmable) uiState = "warming";
6808
7541
  else if (connected) uiState = "connected";
6809
7542
  else if (stream.error) uiState = "error";
6810
7543
  else if (connectTimedOut) uiState = "connect_failed";
6811
7544
  else uiState = "connecting";
6812
- const overlayShown = uiState !== "connected" && uiState !== "connecting";
7545
+ const overlayShown = uiState !== "connected" && uiState !== "connecting" && uiState !== "opening";
6813
7546
  const showToggle = showControlToggle && !overlayShown;
6814
7547
  let overlay = null;
6815
7548
  switch (uiState) {
@@ -6822,8 +7555,12 @@ function DesktopViewer({
6822
7555
  case "unavailable":
6823
7556
  overlay = renderUnavailable?.(reason) ?? defaultNotice("Desktop unavailable", unavailableCopy(reason));
6824
7557
  break;
6825
- case "consent":
6826
- overlay = renderConsentGate ? renderConsentGate(accept, capability?.shared ?? false) : /* @__PURE__ */ jsx11(DefaultConsentGate, { shared: capability?.shared ?? false, onAccept: accept });
7558
+ case "acknowledgment_error":
7559
+ overlay = defaultNotice(
7560
+ "Couldn\u2019t open desktop",
7561
+ acknowledgmentError?.message ?? "The desktop acknowledgment failed.",
7562
+ retryAcknowledgment
7563
+ );
6827
7564
  break;
6828
7565
  case "warming":
6829
7566
  overlay = renderWarming?.() ?? /* @__PURE__ */ jsx11(WarmingNotice, {});
@@ -6842,6 +7579,7 @@ function DesktopViewer({
6842
7579
  reconnect
6843
7580
  );
6844
7581
  break;
7582
+ case "opening":
6845
7583
  case "connecting":
6846
7584
  case "connected":
6847
7585
  overlay = null;
@@ -6870,7 +7608,7 @@ function DesktopViewer({
6870
7608
  "data-state": stream.state
6871
7609
  }
6872
7610
  ),
6873
- uiState === "connecting" && /* @__PURE__ */ jsxs9("div", { className: "pointer-events-none absolute inset-0 flex flex-col items-center justify-center gap-3 text-og-fg-subtle", children: [
7611
+ (uiState === "opening" || uiState === "connecting") && /* @__PURE__ */ jsxs9("div", { className: "pointer-events-none absolute inset-0 flex flex-col items-center justify-center gap-3 text-og-fg-subtle", children: [
6874
7612
  /* @__PURE__ */ jsxs9("span", { className: "relative flex items-center justify-center", children: [
6875
7613
  /* @__PURE__ */ jsx11(MonitorIcon, { className: "size-8 opacity-30", strokeWidth: 1.5 }),
6876
7614
  /* @__PURE__ */ jsx11(
@@ -6881,7 +7619,7 @@ function DesktopViewer({
6881
7619
  }
6882
7620
  )
6883
7621
  ] }),
6884
- /* @__PURE__ */ jsx11("span", { className: "text-og-sm", children: "Connecting to the desktop\u2026" })
7622
+ /* @__PURE__ */ jsx11("span", { className: "text-og-sm", children: uiState === "opening" ? "Opening the desktop\u2026" : "Connecting to the desktop\u2026" })
6885
7623
  ] }),
6886
7624
  showToggle && !externallyControlled && inControl && /* @__PURE__ */ jsx11(
6887
7625
  InControlBar,
@@ -6924,7 +7662,7 @@ function TakeControlCallToAction({
6924
7662
  "border-og-border",
6925
7663
  "bg-og-bg/85 backdrop-blur-md",
6926
7664
  "shadow-og-lg",
6927
- "outline-none transition-all duration-150 ease-out",
7665
+ "outline-hidden transition-all duration-150 ease-out",
6928
7666
  "focus-visible:ring-2 focus-visible:ring-og-accent focus-visible:ring-offset-2 focus-visible:ring-offset-og-bg",
6929
7667
  disabled ? "cursor-not-allowed opacity-60" : cn(
6930
7668
  "cursor-pointer opacity-90 hover:-translate-y-0.5 hover:opacity-100",
@@ -6972,7 +7710,7 @@ function InControlBar({ shared, onRelease }) {
6972
7710
  className: cn(
6973
7711
  "inline-flex items-center gap-1.5 rounded-og-sm border px-2.5 py-1 text-og-xs font-semibold shadow-og-md transition-colors",
6974
7712
  "border-og-accent bg-og-bg/90 text-og-fg backdrop-blur-sm",
6975
- "outline-none hover:bg-og-accent hover:text-og-accent-fg focus-visible:ring-2 focus-visible:ring-og-accent"
7713
+ "outline-hidden hover:bg-og-accent hover:text-og-accent-fg focus-visible:ring-2 focus-visible:ring-og-accent"
6976
7714
  ),
6977
7715
  children: [
6978
7716
  /* @__PURE__ */ jsx11(MonitorIcon, { className: "size-3.5", strokeWidth: 2, "aria-hidden": true }),
@@ -7019,26 +7757,6 @@ function WarmingNotice({ onRetry }) {
7019
7757
  )
7020
7758
  ] });
7021
7759
  }
7022
- function DefaultConsentGate({ shared, onAccept }) {
7023
- return /* @__PURE__ */ jsxs9("div", { className: "max-w-sm rounded-og-lg border border-og-border bg-og-bg p-4 text-center text-og-base text-og-fg", children: [
7024
- /* @__PURE__ */ jsx11("div", { className: "mb-1 font-medium", children: "Watch the live desktop?" }),
7025
- /* @__PURE__ */ jsxs9("p", { className: "mb-3 text-og-sm text-og-fg-subtle", children: [
7026
- "The desktop pixel stream is ",
7027
- /* @__PURE__ */ jsx11("strong", { children: "un-redacted" }),
7028
- " \u2014 it can show secrets the agent prints on screen.",
7029
- shared ? " This sandbox is shared: you'll also see other sessions' agents on the same screen." : ""
7030
- ] }),
7031
- /* @__PURE__ */ jsx11(
7032
- "button",
7033
- {
7034
- type: "button",
7035
- onClick: onAccept,
7036
- className: "rounded-og-sm bg-og-accent px-3 py-1.5 text-og-sm font-medium text-og-accent-fg pointer-coarse:min-h-11",
7037
- children: "I understand \u2014 show the desktop"
7038
- }
7039
- )
7040
- ] });
7041
- }
7042
7760
  function defaultNotice(title, body, onRetry) {
7043
7761
  return /* @__PURE__ */ jsxs9("div", { className: "max-w-sm rounded-og-lg border border-og-border bg-og-bg p-4 text-center text-og-base text-og-fg", children: [
7044
7762
  /* @__PURE__ */ jsxs9("div", { className: "mb-1 flex items-center justify-center gap-1.5 font-medium", children: [
@@ -7075,9 +7793,9 @@ import {
7075
7793
  XIcon as XIcon2
7076
7794
  } from "lucide-react";
7077
7795
  import {
7078
- Group,
7079
- Panel,
7080
- Separator,
7796
+ Group as Group2,
7797
+ Panel as Panel2,
7798
+ Separator as Separator2,
7081
7799
  useDefaultLayout,
7082
7800
  usePanelRef
7083
7801
  } from "react-resizable-panels";
@@ -7122,6 +7840,7 @@ function WorkspaceDock({
7122
7840
  onActiveTabChange,
7123
7841
  collapsed: collapsedProp,
7124
7842
  onCollapsedChange,
7843
+ showCollapseControl = false,
7125
7844
  headerAccessory,
7126
7845
  mobileLeadingControl,
7127
7846
  autoSaveId = "og.session.dock",
@@ -7298,6 +8017,11 @@ function WorkspaceDock({
7298
8017
  className: "fixed inset-0 z-40 flex flex-col bg-og-bg",
7299
8018
  hidden: collapsed,
7300
8019
  style: {
8020
+ // Author utility classes can override the user-agent `[hidden]`
8021
+ // rule (for example this surface's `flex` class). Keep an inline
8022
+ // display guard so a collapsed mobile workspace is actually gone
8023
+ // visually as well as from the accessibility tree.
8024
+ display: collapsed ? "none" : void 0,
7301
8025
  paddingTop: "env(safe-area-inset-top)",
7302
8026
  paddingBottom: "env(safe-area-inset-bottom)"
7303
8027
  },
@@ -7322,7 +8046,7 @@ function WorkspaceDock({
7322
8046
  onClick: expand,
7323
8047
  title: "Open workspace",
7324
8048
  "aria-label": "Open workspace",
7325
- className: "absolute right-3 top-3 z-30 inline-flex size-11 items-center justify-center rounded-og-md border border-og-border bg-og-surface-1 text-og-fg-muted shadow-lg transition-colors hover:border-og-border-strong hover:text-og-fg focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-og-accent",
8049
+ className: "absolute right-3 top-3 z-30 inline-flex size-11 items-center justify-center rounded-og-md border border-og-border bg-og-surface-1 text-og-fg-muted shadow-lg transition-colors hover:border-og-border-strong hover:text-og-fg focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-og-accent",
7326
8050
  style: { marginTop: "env(safe-area-inset-top)" },
7327
8051
  children: /* @__PURE__ */ jsx12(ChevronsLeftRightIcon, { className: "size-4", "aria-hidden": true })
7328
8052
  }
@@ -7346,20 +8070,20 @@ function WorkspaceDock({
7346
8070
  children: maximized ? /* @__PURE__ */ jsx12(Minimize2Icon, { className: "size-3.5" }) : /* @__PURE__ */ jsx12(Maximize2Icon, { className: "size-3.5" })
7347
8071
  }
7348
8072
  ),
7349
- hostControlled ? null : /* @__PURE__ */ jsx12(ChromeButton, { onClick: collapse, title: "Collapse", label: "Collapse dock", children: /* @__PURE__ */ jsx12(PanelRightCloseIcon, { className: "size-3.5" }) })
8073
+ hostControlled && !showCollapseControl ? null : /* @__PURE__ */ jsx12(ChromeButton, { onClick: collapse, title: "Collapse", label: "Collapse dock", children: /* @__PURE__ */ jsx12(PanelRightCloseIcon, { className: "size-3.5" }) })
7350
8074
  ] })
7351
8075
  }
7352
8076
  );
7353
8077
  return /* @__PURE__ */ jsxs10("div", { ref: rootRef, className: cn("relative flex h-full min-h-0 w-full min-w-0", className), children: [
7354
8078
  /* @__PURE__ */ jsxs10(
7355
- Group,
8079
+ Group2,
7356
8080
  {
7357
8081
  orientation: "horizontal",
7358
8082
  className: "min-h-0 flex-1",
7359
8083
  ...defaultLayout ? { defaultLayout } : {},
7360
8084
  onLayoutChanged,
7361
8085
  children: [
7362
- /* @__PURE__ */ jsx12(Panel, { id: "primary", minSize: "30%", className: "min-h-0 min-w-0", children: /* @__PURE__ */ jsx12(
8086
+ /* @__PURE__ */ jsx12(Panel2, { id: "primary", minSize: "30%", className: "min-h-0 min-w-0", children: /* @__PURE__ */ jsx12(
7363
8087
  "div",
7364
8088
  {
7365
8089
  "data-workspace-primary": true,
@@ -7369,9 +8093,9 @@ function WorkspaceDock({
7369
8093
  children: primary
7370
8094
  }
7371
8095
  ) }),
7372
- !collapsed && /* @__PURE__ */ jsx12(Separator, { className: "group relative z-10 w-1.5 shrink-0 outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-og-accent max-[1023px]:-mx-[19px] max-[1023px]:w-11 pointer-coarse:-mx-[19px] pointer-coarse:w-11", children: /* @__PURE__ */ jsx12("span", { className: "absolute inset-y-0 left-1/2 w-px -translate-x-1/2 bg-og-border transition-colors group-hover:bg-og-accent group-focus-visible:bg-og-accent group-data-[separator-state=dragging]:bg-og-accent" }) }),
8096
+ !collapsed && /* @__PURE__ */ jsx12(Separator2, { className: "group relative z-10 w-1.5 shrink-0 outline-hidden focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-og-accent max-[1023px]:-mx-[19px] max-[1023px]:w-11 pointer-coarse:-mx-[19px] pointer-coarse:w-11", children: /* @__PURE__ */ jsx12("span", { className: "absolute inset-y-0 left-1/2 w-px -translate-x-1/2 bg-og-border transition-colors group-hover:bg-og-accent group-focus-visible:bg-og-accent group-data-[separator-state=dragging]:bg-og-accent" }) }),
7373
8097
  /* @__PURE__ */ jsx12(
7374
- Panel,
8098
+ Panel2,
7375
8099
  {
7376
8100
  id: "dock",
7377
8101
  panelRef: dockPanelRef,
@@ -7435,7 +8159,7 @@ function ChromeButton({
7435
8159
  onClick,
7436
8160
  title,
7437
8161
  "aria-label": label,
7438
- className: "inline-flex size-7 items-center justify-center rounded-og-sm p-1 transition-colors hover:bg-og-surface-2 hover:text-og-fg focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-og-accent max-[1023px]:size-11 pointer-coarse:size-11",
8162
+ className: "inline-flex size-7 items-center justify-center rounded-og-sm p-1 transition-colors hover:bg-og-surface-2 hover:text-og-fg focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-og-accent max-[1023px]:size-11 pointer-coarse:size-11",
7439
8163
  children
7440
8164
  }
7441
8165
  );
@@ -7547,7 +8271,7 @@ function DockChrome({
7547
8271
  onClick: () => onTab(tab.id),
7548
8272
  onKeyDown: (event) => onTabKeyDown(event, index),
7549
8273
  className: cn(
7550
- "flex min-h-7 shrink-0 items-center justify-center gap-1 rounded-og-sm px-2 py-1 text-og-xs font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-og-accent max-[1023px]:min-h-11 max-[1023px]:min-w-11 pointer-coarse:min-h-11 pointer-coarse:min-w-11",
8274
+ "flex min-h-7 shrink-0 items-center justify-center gap-1 rounded-og-sm px-2 py-1 text-og-xs font-medium transition-colors focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-og-accent max-[1023px]:min-h-11 max-[1023px]:min-w-11 pointer-coarse:min-h-11 pointer-coarse:min-w-11",
7551
8275
  tab.id === current ? "bg-og-accent-soft text-og-fg" : "text-og-fg-subtle hover:text-og-fg"
7552
8276
  ),
7553
8277
  children: [
@@ -7582,25 +8306,46 @@ function DockChrome({
7582
8306
  }
7583
8307
 
7584
8308
  // src/components/sandbox-workspace.tsx
7585
- import { useCallback as useCallback22, useEffect as useEffect23, useMemo as useMemo11, useRef as useRef20, useState as useState20 } from "react";
7586
- import { Popover } from "radix-ui";
8309
+ import {
8310
+ lazy as lazy2,
8311
+ Suspense as Suspense2,
8312
+ useCallback as useCallback22,
8313
+ useEffect as useEffect23,
8314
+ useMemo as useMemo13,
8315
+ useRef as useRef20,
8316
+ useState as useState20
8317
+ } from "react";
7587
8318
  import {
7588
8319
  CircleCheckIcon,
7589
8320
  CpuIcon,
7590
- LaptopIcon,
8321
+ FileCode2Icon as FileCode2Icon2,
8322
+ GitCompareArrowsIcon,
8323
+ Globe2Icon,
7591
8324
  LoaderCircleIcon as LoaderCircleIcon3,
8325
+ MonitorIcon as MonitorIcon2,
7592
8326
  RefreshCwIcon as RefreshCwIcon2,
8327
+ SquareTerminalIcon,
7593
8328
  TriangleAlertIcon as TriangleAlertIcon2
7594
8329
  } from "lucide-react";
7595
8330
  import { jsx as jsx13, jsxs as jsxs11 } from "react/jsx-runtime";
8331
+ var LazyBrowserViewer = lazy2(async () => {
8332
+ const viewers = await import("./interactive-workbench-viewers-KHNOHWQE.js");
8333
+ return { default: viewers.BrowserViewer };
8334
+ });
8335
+ var LazyComputerViewer = lazy2(async () => {
8336
+ const viewers = await import("./interactive-workbench-viewers-KHNOHWQE.js");
8337
+ return { default: viewers.ComputerViewer };
8338
+ });
7596
8339
  var WORKBENCH_TAB_CHANGES = "changes";
7597
8340
  var WORKBENCH_TAB_FILES = "files";
7598
8341
  var WORKBENCH_TAB_TERMINAL = "terminal";
8342
+ var WORKBENCH_TAB_BROWSER = "browser";
7599
8343
  var WORKBENCH_TAB_DESKTOP = "desktop";
7600
8344
  var WORKBENCH_SURFACES = [
7601
8345
  WORKBENCH_TAB_CHANGES,
7602
8346
  WORKBENCH_TAB_FILES,
7603
8347
  WORKBENCH_TAB_TERMINAL,
8348
+ WORKBENCH_TAB_BROWSER,
7604
8349
  WORKBENCH_TAB_DESKTOP
7605
8350
  ];
7606
8351
  function isWorkbenchSurface(value) {
@@ -7624,6 +8369,24 @@ function captureDegradedMessage(reason) {
7624
8369
  return "Workspace capture is incomplete because repository discovery failed. Live files remain authoritative.";
7625
8370
  }
7626
8371
  }
8372
+ function WorkbenchTabLabel({ icon, children }) {
8373
+ return /* @__PURE__ */ jsxs11("span", { className: "inline-flex items-center gap-1.5", children: [
8374
+ /* @__PURE__ */ jsx13("span", { className: "[&>svg]:size-3.5", "aria-hidden": true, children: icon }),
8375
+ /* @__PURE__ */ jsx13("span", { children })
8376
+ ] });
8377
+ }
8378
+ function WorkbenchSurfaceLoading({ name }) {
8379
+ return /* @__PURE__ */ jsx13(
8380
+ CenteredState,
8381
+ {
8382
+ icon: /* @__PURE__ */ jsx13(LoaderCircleIcon3, { className: "size-5 animate-spin motion-reduce:animate-none", "aria-hidden": true }),
8383
+ children: /* @__PURE__ */ jsxs11("p", { className: "text-og-sm font-medium text-og-fg", children: [
8384
+ "Opening ",
8385
+ name
8386
+ ] })
8387
+ }
8388
+ );
8389
+ }
7627
8390
  function initialWorkspaceTab(events, override) {
7628
8391
  if (override) return override;
7629
8392
  let bestSeq = -1;
@@ -7635,7 +8398,17 @@ function initialWorkspaceTab(events, override) {
7635
8398
  bestSeq = event.sequence;
7636
8399
  bestFileCount = typeof stats?.fileCount === "number" ? stats.fileCount : 0;
7637
8400
  }
7638
- return bestFileCount > 0 ? WORKBENCH_TAB_CHANGES : WORKBENCH_TAB_FILES;
8401
+ if (bestFileCount > 0) return WORKBENCH_TAB_CHANGES;
8402
+ let latestGitSeq = -1;
8403
+ let committedOnly = false;
8404
+ for (const event of events ?? []) {
8405
+ if (event.type !== "git.changed" || event.sequence > bestSeq) continue;
8406
+ if (event.sequence <= latestGitSeq) continue;
8407
+ const payload = event.payload;
8408
+ latestGitSeq = event.sequence;
8409
+ committedOnly = typeof payload?.ahead === "number" && payload.ahead > 0 || payload?.reason === "commit";
8410
+ }
8411
+ return committedOnly ? WORKBENCH_TAB_CHANGES : WORKBENCH_TAB_FILES;
7639
8412
  }
7640
8413
  function sandboxProvisionInFlight(events) {
7641
8414
  let inFlight = false;
@@ -7657,29 +8430,59 @@ function sandboxProvisionInFlight(events) {
7657
8430
  function emptyWarmIntents(sessionId) {
7658
8431
  return {
7659
8432
  sessionId,
7660
- watchDesktop: false,
7661
8433
  warmTerminal: false,
7662
8434
  warmFiles: false
7663
8435
  };
7664
8436
  }
7665
8437
  function useSandboxWorkspaceTabs(options) {
7666
8438
  const { client, workspaceId } = useOpenGeni(options);
7667
- const { sessionId, events, onNotify, requestedFilePath, requestedFileRequestId, onOpenFile } = options;
8439
+ const {
8440
+ sessionId,
8441
+ events,
8442
+ onNotify,
8443
+ browserWebSocketFactory,
8444
+ computerWebSocketFactory,
8445
+ requestedFilePath,
8446
+ requestedFileRequestId,
8447
+ onOpenFile,
8448
+ onOpenComputerSession,
8449
+ requestedComputerSessionId,
8450
+ requestedComputerRequestId
8451
+ } = options;
7668
8452
  const initialTab = options.initialTab ?? null;
8453
+ const activeTab = options.activeTab ?? initialTab;
7669
8454
  const requestedSurfaces = options.surfaces ?? WORKBENCH_SURFACES;
7670
8455
  const surfaceSet = new Set(requestedSurfaces);
7671
8456
  const changesEnabled = surfaceSet.has(WORKBENCH_TAB_CHANGES);
7672
8457
  const filesEnabled = surfaceSet.has(WORKBENCH_TAB_FILES);
7673
8458
  const terminalEnabled = surfaceSet.has(WORKBENCH_TAB_TERMINAL);
8459
+ const browserEnabled = surfaceSet.has(WORKBENCH_TAB_BROWSER);
7674
8460
  const desktopEnabled = surfaceSet.has(WORKBENCH_TAB_DESKTOP);
8461
+ const createLinkedComputer = useCallback22(
8462
+ async (name) => {
8463
+ const response = await client.createComputerSession(workspaceId, {
8464
+ operationId: crypto.randomUUID(),
8465
+ sessionId,
8466
+ name
8467
+ });
8468
+ return {
8469
+ id: response.session.id,
8470
+ placement: response.session.placement
8471
+ };
8472
+ },
8473
+ [client, sessionId, workspaceId]
8474
+ );
7675
8475
  const workspaceDataEnabled = changesEnabled || filesEnabled;
7676
- const anySurfaceEnabled = workspaceDataEnabled || terminalEnabled || desktopEnabled;
8476
+ const machineSurfaceEnabled = workspaceDataEnabled || terminalEnabled || desktopEnabled;
8477
+ const resolvedActiveTab = activeTab ?? initialTab;
8478
+ const changesActive = resolvedActiveTab === WORKBENCH_TAB_CHANGES;
8479
+ const filesActive = resolvedActiveTab === WORKBENCH_TAB_FILES;
7677
8480
  const surfaceIdentity = WORKBENCH_SURFACES.filter((surface) => surfaceSet.has(surface)).join(",");
7678
8481
  const [storedWarmIntents, setStoredWarmIntents] = useState20(
7679
8482
  () => emptyWarmIntents(sessionId)
7680
8483
  );
7681
8484
  const warmIntents = storedWarmIntents.sessionId === sessionId ? storedWarmIntents : emptyWarmIntents(sessionId);
7682
- const { watchDesktop, warmTerminal, warmFiles } = warmIntents;
8485
+ const { warmTerminal, warmFiles } = warmIntents;
7683
8486
  const requestWarmIntent = useCallback22(
7684
8487
  (intent) => {
7685
8488
  setStoredWarmIntents((previous) => {
@@ -7693,13 +8496,13 @@ function useSandboxWorkspaceTabs(options) {
7693
8496
  workspaceId,
7694
8497
  sessionId,
7695
8498
  pollIntervalMs: 8e3,
7696
- enabled: anySurfaceEnabled
8499
+ enabled: machineSurfaceEnabled
7697
8500
  });
7698
8501
  const activeMachine = machines.machines.find((m) => m.sandboxId === machines.activeSandboxId) ?? machines.machines.find((m) => m.active) ?? null;
7699
8502
  const caps = useSessionCapabilities(sessionId, {
7700
8503
  events,
7701
- enabled: anySurfaceEnabled,
7702
- attachDesktop: desktopEnabled && watchDesktop,
8504
+ enabled: machineSurfaceEnabled,
8505
+ attachDesktop: false,
7703
8506
  attachTerminal: terminalEnabled && warmTerminal,
7704
8507
  // Explicit live-file intent only — NOT "the Files tab is open". A cold edit
7705
8508
  // or guarded-file open wakes the box; a glance at the tree/diff does not.
@@ -7707,19 +8510,18 @@ function useSandboxWorkspaceTabs(options) {
7707
8510
  });
7708
8511
  const capabilities = caps.capabilities;
7709
8512
  const liveness = capabilities?.liveness;
8513
+ const liveIoLiveness = liveness ?? null;
7710
8514
  const fileSystemOn = capabilities?.FileSystem.available ?? false;
7711
8515
  const fsReadOnly = capabilities?.FileSystem.readOnly ?? false;
7712
- const filesEditable = fileSystemOn && !fsReadOnly && (liveness === "warm" || liveness === "draining");
8516
+ const filesEditable = fileSystemOn && !fsReadOnly && sandboxAcceptsLiveIo(liveness);
7713
8517
  const gitOn = capabilities?.Git.available ?? false;
7714
8518
  const terminalOn = terminalEnabled && (capabilities?.Terminal.transport ?? null) !== null;
7715
- const ptyWsLive = capabilities?.Terminal.transport === "pty-ws" && Boolean(capabilities?.Terminal.url);
7716
- const ptyCapable = (capabilities?.Terminal.ptyCapable ?? false) && !ptyWsLive;
8519
+ const ptyCapable = warmTerminal && (capabilities?.Terminal.ptyCapable ?? false) && capabilities?.Terminal.transport === "sse-events" && capabilities?.Terminal.reason === null;
7717
8520
  const terminal = useSandboxTerminal(sessionId, {
7718
8521
  events: terminalEnabled ? events : [],
7719
8522
  interactive: terminalEnabled && ptyCapable,
7720
8523
  liveness
7721
8524
  });
7722
- const desktopAdvertised = desktopEnabled && ((capabilities?.DesktopStream.transport ?? null) !== null || capabilities?.DesktopStream.reason === "lease_cold");
7723
8525
  const provisioning = sandboxProvisionInFlight(events);
7724
8526
  const renegotiate = caps.renegotiate;
7725
8527
  const provisioningRef = useRef20({ sessionId, provisioning });
@@ -7735,9 +8537,9 @@ function useSandboxWorkspaceTabs(options) {
7735
8537
  enabled: workspaceDataEnabled
7736
8538
  });
7737
8539
  const captureAvailable = captureState.available;
7738
- const liveWorkspaceExpected = liveness === "warm" || liveness === "draining";
8540
+ const liveWorkspaceExpected = sandboxAcceptsLiveIo(liveness);
7739
8541
  const capturePending = !liveWorkspaceExpected && (captureState.fileCount === null || captureState.loading);
7740
- const repoPaths = useMemo11(() => {
8542
+ const repoPaths = useMemo13(() => {
7741
8543
  const advertised = capabilities?.Git.repos ?? [];
7742
8544
  if (advertised.length > 0) return advertised;
7743
8545
  return captureState.capture?.repos.map((repo) => repo.root) ?? [];
@@ -7760,7 +8562,9 @@ function useSandboxWorkspaceTabs(options) {
7760
8562
  // No passive Channel-A reads while cold, even after a conclusive capture
7761
8563
  // miss. A missing capture gets an explicit "Open live workspace" gate.
7762
8564
  enabled: filesEnabled && (captureAvailable || fileSystemOn && liveWorkspaceExpected),
7763
- liveness,
8565
+ active: filesActive,
8566
+ repoPaths,
8567
+ liveness: liveIoLiveness,
7764
8568
  capture: captureState.capture,
7765
8569
  // A reverted optimistic mutation (e.g. a 409 rename collision) surfaces as a
7766
8570
  // host notification — the tree silently rolls back, the user sees why.
@@ -7769,23 +8573,30 @@ function useSandboxWorkspaceTabs(options) {
7769
8573
  message: `Could not ${op}: ${error.message}`
7770
8574
  })
7771
8575
  });
8576
+ const [storedChangesComparison, setStoredChangesComparison] = useState20(null);
8577
+ const captureSupportsBranch = captureState.capture !== null && captureState.capture.repos.length > 0 && captureState.capture.repos.every((repo) => repo.branchDiff !== void 0);
8578
+ const changesComparison = storedChangesComparison?.sessionId === sessionId ? storedChangesComparison.value : liveWorkspaceExpected || captureSupportsBranch ? "branch" : "working";
8579
+ const setChangesComparison = useCallback22(
8580
+ (value) => setStoredChangesComparison({ sessionId, value }),
8581
+ [sessionId]
8582
+ );
7772
8583
  const git = useSandboxGit(sessionId, {
7773
8584
  events,
7774
- enabled: workspaceDataEnabled && (captureAvailable || gitOn && liveWorkspaceExpected),
7775
- repoPaths,
7776
- liveness,
7777
- capture: captureState.capture
7778
- });
7779
- const stagedGit = useSandboxGit(sessionId, {
7780
- events,
7781
- // Captures intentionally expose one combined review diff, not a staged
7782
- // index. Never probe the live index while the lease is cold.
7783
- enabled: filesEnabled && gitOn && liveWorkspaceExpected,
7784
- staged: true,
8585
+ enabled: changesComparison === "working" ? workspaceDataEnabled && (captureAvailable || gitOn && liveWorkspaceExpected) : changesComparison === "branch" ? workspaceDataEnabled && (captureSupportsBranch || gitOn && liveWorkspaceExpected) : workspaceDataEnabled && gitOn && liveWorkspaceExpected,
8586
+ // Before the dock chooses its source-driven default, Git is the sole probe
8587
+ // that may need to resolve that choice on a live workspace. Files remains
8588
+ // dormant, avoiding the old duplicate full-tree read.
8589
+ active: changesActive || resolvedActiveTab === null,
7785
8590
  repoPaths,
7786
- liveness,
7787
- capture: captureState.capture
8591
+ liveness: liveIoLiveness,
8592
+ comparison: changesComparison,
8593
+ capture: changesComparison === "staged" ? null : captureState.capture
7788
8594
  });
8595
+ useEffect23(() => {
8596
+ if (changesComparison === "branch" && git.error && storedChangesComparison?.sessionId !== sessionId) {
8597
+ setStoredChangesComparison({ sessionId, value: "working" });
8598
+ }
8599
+ }, [changesComparison, git.error, sessionId, storedChangesComparison?.sessionId]);
7789
8600
  const [xtermTheme, setXtermTheme] = useState20(void 0);
7790
8601
  useEffect23(() => {
7791
8602
  if (!terminalEnabled || typeof document === "undefined") return;
@@ -7798,33 +8609,13 @@ function useSandboxWorkspaceTabs(options) {
7798
8609
  });
7799
8610
  return () => observer.disconnect();
7800
8611
  }, [terminalEnabled]);
7801
- async function acknowledgeAndWatch() {
7802
- try {
7803
- const shared = capabilities?.DesktopStream.shared ?? false;
7804
- await client.acknowledgeStream(workspaceId, sessionId, {
7805
- acknowledgeUnredacted: true,
7806
- acknowledgeShared: shared
7807
- });
7808
- requestWarmIntent("watchDesktop");
7809
- caps.renegotiate();
7810
- } catch (error) {
7811
- onNotify?.({
7812
- kind: "error",
7813
- message: `Could not start the desktop stream: ${error instanceof Error ? error.message : String(error)}`
7814
- });
7815
- }
7816
- }
7817
- function rewarmDesktop() {
7818
- if (!watchDesktop) requestWarmIntent("watchDesktop");
7819
- caps.renegotiate();
7820
- }
7821
8612
  const dirtyCount = git.diff.length;
7822
8613
  const chip = useMachineChip({
7823
8614
  liveness,
7824
8615
  capabilitiesState: caps.state,
7825
8616
  activeMachineState: activeMachine?.state ?? null,
7826
8617
  activeIsSelfhosted: activeMachine?.kind === "selfhosted",
7827
- wantsWarm: terminalEnabled && warmTerminal || desktopEnabled && watchDesktop || workspaceDataEnabled && warmFiles,
8618
+ wantsWarm: terminalEnabled && warmTerminal || workspaceDataEnabled && warmFiles,
7828
8619
  capturedAt: captureState.capturedAt
7829
8620
  });
7830
8621
  const workspaceWaking = chip.state === "waking";
@@ -7837,7 +8628,9 @@ function useSandboxWorkspaceTabs(options) {
7837
8628
  defaultTabRef.current = { identity: defaultIdentity, value: null };
7838
8629
  }
7839
8630
  const captureIsAuthoritative = !liveWorkspaceExpected && (liveness !== void 0 || caps.error !== null);
7840
- const captureUnavailable = captureState.fileCount === 0 || captureState.error !== null;
8631
+ const captureHasChanges = (captureState.fileCount ?? 0) > 0 || (captureState.capture?.repos.some((repo) => (repo.branchDiff?.length ?? 0) > 0) ?? false);
8632
+ const captureUnavailable = captureState.fileCount === 0 && !captureHasChanges || captureState.error !== null;
8633
+ const implicitBranchFallbackPending = changesComparison === "branch" && git.error !== null && storedChangesComparison?.sessionId !== sessionId;
7841
8634
  if (defaultTabRef.current.value === null) {
7842
8635
  if (initialTab && (!isWorkbenchSurface(initialTab) || surfaceSet.has(initialTab))) {
7843
8636
  defaultTabRef.current.value = initialTab;
@@ -7849,7 +8642,7 @@ function useSandboxWorkspaceTabs(options) {
7849
8642
  );
7850
8643
  } else if (captureIsAuthoritative && captureState.fileCount !== null && captureState.available) {
7851
8644
  defaultTabRef.current.value = sourceDrivenDefaultTab(
7852
- captureState.fileCount > 0,
8645
+ captureHasChanges,
7853
8646
  changesEnabled,
7854
8647
  filesEnabled
7855
8648
  );
@@ -7861,26 +8654,28 @@ function useSandboxWorkspaceTabs(options) {
7861
8654
  defaultTabRef.current.value = sourceDrivenDefaultTab(false, changesEnabled, filesEnabled);
7862
8655
  } else if (captureState.available && git.error && captureState.fileCount !== null) {
7863
8656
  defaultTabRef.current.value = sourceDrivenDefaultTab(
7864
- captureState.fileCount > 0,
8657
+ captureHasChanges,
7865
8658
  changesEnabled,
7866
8659
  filesEnabled
7867
8660
  );
7868
- } else if (captureUnavailable && git.error) {
8661
+ } else if (captureUnavailable && git.error && !implicitBranchFallbackPending) {
7869
8662
  defaultTabRef.current.value = sourceDrivenDefaultTab(false, changesEnabled, filesEnabled);
7870
8663
  }
7871
8664
  }
7872
8665
  const defaultTab = defaultTabRef.current.value;
7873
- const tabs = useMemo11(() => {
8666
+ const tabs = useMemo13(() => {
7874
8667
  const list = [];
7875
8668
  if (changesEnabled)
7876
8669
  list.push({
7877
8670
  id: WORKBENCH_TAB_CHANGES,
7878
- label: "Changes",
8671
+ label: /* @__PURE__ */ jsx13(WorkbenchTabLabel, { icon: /* @__PURE__ */ jsx13(GitCompareArrowsIcon, {}), children: "Changes" }),
7879
8672
  badge: dirtyCount > 0 ? /* @__PURE__ */ jsx13(DirtyBadge, { count: dirtyCount }) : void 0,
7880
8673
  content: /* @__PURE__ */ jsx13(
7881
8674
  ChangesTabBody,
7882
8675
  {
7883
8676
  git,
8677
+ comparison: changesComparison,
8678
+ onComparisonChange: setChangesComparison,
7884
8679
  captureAvailable,
7885
8680
  captureRevision: captureState.revision,
7886
8681
  capturePending,
@@ -7902,13 +8697,12 @@ function useSandboxWorkspaceTabs(options) {
7902
8697
  if (filesEnabled)
7903
8698
  list.push({
7904
8699
  id: WORKBENCH_TAB_FILES,
7905
- label: "Files",
8700
+ label: /* @__PURE__ */ jsx13(WorkbenchTabLabel, { icon: /* @__PURE__ */ jsx13(FileCode2Icon2, {}), children: "Files" }),
7906
8701
  content: /* @__PURE__ */ jsx13(
7907
8702
  SandboxFiles,
7908
8703
  {
7909
8704
  files,
7910
8705
  git,
7911
- stagedGit,
7912
8706
  fileSystemAvailable: fileSystemOn || captureAvailable,
7913
8707
  editable: filesEditable && files.source === "live" && files.error === null && !files.loading,
7914
8708
  workspaceResting: !liveWorkspaceExpected && liveness !== void 0 && !capturePending && !captureAvailable && !workspaceWaking,
@@ -7918,7 +8712,7 @@ function useSandboxWorkspaceTabs(options) {
7918
8712
  ...requestedFilePath ? {
7919
8713
  requestedPath: requestedFilePath,
7920
8714
  ...requestedFileRequestId !== null && requestedFileRequestId !== void 0 ? { requestedPathRequestId: requestedFileRequestId } : {},
7921
- requestedPathReady: liveness === "warm" || liveness === "draining"
8715
+ requestedPathReady: sandboxAcceptsLiveIo(liveness)
7922
8716
  } : {},
7923
8717
  onEditIntent: () => requestWarmIntent("warmFiles"),
7924
8718
  className: "h-full"
@@ -7929,7 +8723,7 @@ function useSandboxWorkspaceTabs(options) {
7929
8723
  if (terminalOn) {
7930
8724
  list.push({
7931
8725
  id: WORKBENCH_TAB_TERMINAL,
7932
- label: "Terminal",
8726
+ label: /* @__PURE__ */ jsx13(WorkbenchTabLabel, { icon: /* @__PURE__ */ jsx13(SquareTerminalIcon, {}), children: "Terminal" }),
7933
8727
  content: /* @__PURE__ */ jsx13("div", { className: "h-full bg-og-bg p-1", children: /* @__PURE__ */ jsx13(
7934
8728
  SandboxTerminal,
7935
8729
  {
@@ -7940,26 +8734,49 @@ function useSandboxWorkspaceTabs(options) {
7940
8734
  shell: capabilities?.Terminal.shell ?? void 0,
7941
8735
  liveness,
7942
8736
  ...xtermTheme ? { theme: xtermTheme } : {}
7943
- }
8737
+ },
8738
+ sessionId
7944
8739
  ) })
7945
8740
  });
7946
8741
  }
7947
- if (desktopAdvertised) {
8742
+ if (browserEnabled) {
8743
+ list.push({
8744
+ id: WORKBENCH_TAB_BROWSER,
8745
+ label: /* @__PURE__ */ jsx13(WorkbenchTabLabel, { icon: /* @__PURE__ */ jsx13(Globe2Icon, {}), children: "Browser" }),
8746
+ content: /* @__PURE__ */ jsx13(Suspense2, { fallback: /* @__PURE__ */ jsx13(WorkbenchSurfaceLoading, { name: "Browser" }), children: /* @__PURE__ */ jsx13(
8747
+ LazyBrowserViewer,
8748
+ {
8749
+ client,
8750
+ workspaceId,
8751
+ sessionId,
8752
+ onNotify,
8753
+ ...desktopEnabled ? { createLinkedComputer } : {},
8754
+ ...onOpenComputerSession ? { onOpenComputer: onOpenComputerSession } : {},
8755
+ ...browserWebSocketFactory ? { webSocketFactory: browserWebSocketFactory } : {},
8756
+ className: "h-full"
8757
+ },
8758
+ sessionId
8759
+ ) })
8760
+ });
8761
+ }
8762
+ if (desktopEnabled) {
7948
8763
  list.push({
7949
8764
  id: WORKBENCH_TAB_DESKTOP,
7950
- label: "Desktop",
7951
- badge: watchDesktop ? /* @__PURE__ */ jsx13("span", { className: "rounded-og-xs bg-og-status-running/20 px-1 text-og-xs text-og-status-running", children: "Live" }) : void 0,
7952
- content: /* @__PURE__ */ jsx13(
7953
- DesktopViewer,
8765
+ label: /* @__PURE__ */ jsx13(WorkbenchTabLabel, { icon: /* @__PURE__ */ jsx13(MonitorIcon2, {}), children: "Computer" }),
8766
+ content: /* @__PURE__ */ jsx13(Suspense2, { fallback: /* @__PURE__ */ jsx13(WorkbenchSurfaceLoading, { name: "Computer" }), children: /* @__PURE__ */ jsx13(
8767
+ LazyComputerViewer,
7954
8768
  {
7955
- capability: capabilities?.DesktopStream ?? null,
7956
- viewerCapReached: caps.viewerCapReached,
7957
- watching: watchDesktop,
7958
- onAcknowledge: () => void acknowledgeAndWatch(),
7959
- onWarm: rewarmDesktop,
8769
+ client,
8770
+ workspaceId,
8771
+ sessionId,
8772
+ onNotify,
8773
+ requestedComputerSessionId,
8774
+ requestedComputerRequestId,
8775
+ ...computerWebSocketFactory ? { webSocketFactory: computerWebSocketFactory } : {},
7960
8776
  className: "h-full"
7961
- }
7962
- )
8777
+ },
8778
+ sessionId
8779
+ ) })
7963
8780
  });
7964
8781
  }
7965
8782
  return list;
@@ -7968,19 +8785,27 @@ function useSandboxWorkspaceTabs(options) {
7968
8785
  changesEnabled,
7969
8786
  filesEnabled,
7970
8787
  terminalOn,
7971
- desktopAdvertised,
8788
+ browserEnabled,
8789
+ desktopEnabled,
7972
8790
  captureAvailable,
7973
8791
  dirtyCount,
7974
- watchDesktop,
7975
8792
  warmTerminal,
7976
8793
  requestedFilePath,
7977
8794
  requestedFileRequestId,
7978
8795
  onOpenFile,
8796
+ onOpenComputerSession,
8797
+ requestedComputerSessionId,
8798
+ requestedComputerRequestId,
8799
+ onNotify,
8800
+ browserWebSocketFactory,
8801
+ computerWebSocketFactory,
8802
+ client,
8803
+ workspaceId,
7979
8804
  liveness,
7980
8805
  sessionId,
7981
8806
  files,
7982
8807
  git,
7983
- stagedGit,
8808
+ changesComparison,
7984
8809
  terminal,
7985
8810
  xtermTheme,
7986
8811
  capabilities,
@@ -7988,13 +8813,14 @@ function useSandboxWorkspaceTabs(options) {
7988
8813
  caps.state,
7989
8814
  caps.error,
7990
8815
  caps.viewerCapReached,
7991
- requestWarmIntent
8816
+ requestWarmIntent,
8817
+ createLinkedComputer
7992
8818
  ]);
7993
8819
  return {
7994
8820
  tabs,
7995
8821
  defaultTab,
7996
8822
  machine: {
7997
- enabled: anySurfaceEnabled,
8823
+ enabled: machineSurfaceEnabled,
7998
8824
  chip,
7999
8825
  activeMachine,
8000
8826
  loading: machines.loading,
@@ -8013,8 +8839,11 @@ function SandboxWorkspace(props) {
8013
8839
  trailingTabs,
8014
8840
  initialTab,
8015
8841
  onNotify,
8842
+ browserWebSocketFactory,
8843
+ computerWebSocketFactory,
8016
8844
  collapsed,
8017
8845
  onCollapsedChange,
8846
+ showCollapseControl,
8018
8847
  mobileLeadingControl,
8019
8848
  autoSaveId,
8020
8849
  defaultSize,
@@ -8024,7 +8853,11 @@ function SandboxWorkspace(props) {
8024
8853
  } = props;
8025
8854
  const [storedSelection, setStoredSelection] = useState20(null);
8026
8855
  const [requestedFile, setRequestedFile] = useState20(null);
8856
+ const [requestedComputer, setRequestedComputer] = useState20(null);
8027
8857
  const nextFileRequestId = useRef20(0);
8858
+ const nextComputerRequestId = useRef20(0);
8859
+ const selectedTab = storedSelection?.sessionId === sessionId ? storedSelection.tab : null;
8860
+ const activeTabHint = selectedTab ?? initialTab ?? leadingTabs?.[0]?.id ?? null;
8028
8861
  const openFile = useCallback22(
8029
8862
  (path) => {
8030
8863
  nextFileRequestId.current += 1;
@@ -8033,6 +8866,18 @@ function SandboxWorkspace(props) {
8033
8866
  },
8034
8867
  [sessionId]
8035
8868
  );
8869
+ const openComputer = useCallback22(
8870
+ (computerSessionId) => {
8871
+ nextComputerRequestId.current += 1;
8872
+ setRequestedComputer({
8873
+ sessionId,
8874
+ computerSessionId,
8875
+ requestId: nextComputerRequestId.current
8876
+ });
8877
+ setStoredSelection({ sessionId, tab: WORKBENCH_TAB_DESKTOP });
8878
+ },
8879
+ [sessionId]
8880
+ );
8036
8881
  const {
8037
8882
  tabs: workbenchTabs,
8038
8883
  machine,
@@ -8044,13 +8889,18 @@ function SandboxWorkspace(props) {
8044
8889
  events,
8045
8890
  ...surfaces ? { surfaces } : {},
8046
8891
  ...initialTab ? { initialTab } : {},
8892
+ activeTab: activeTabHint,
8047
8893
  ...onNotify ? { onNotify } : {},
8894
+ ...browserWebSocketFactory ? { browserWebSocketFactory } : {},
8895
+ ...computerWebSocketFactory ? { computerWebSocketFactory } : {},
8048
8896
  requestedFilePath: requestedFile?.sessionId === sessionId ? requestedFile.path : null,
8049
8897
  requestedFileRequestId: requestedFile?.sessionId === sessionId ? requestedFile.requestId : null,
8050
- onOpenFile: openFile
8898
+ onOpenFile: openFile,
8899
+ onOpenComputerSession: openComputer,
8900
+ requestedComputerSessionId: requestedComputer?.sessionId === sessionId ? requestedComputer.computerSessionId : null,
8901
+ requestedComputerRequestId: requestedComputer?.sessionId === sessionId ? requestedComputer.requestId : null
8051
8902
  });
8052
8903
  const tabs = [...leadingTabs ?? [], ...workbenchTabs, ...trailingTabs ?? []];
8053
- const selectedTab = storedSelection?.sessionId === sessionId ? storedSelection.tab : null;
8054
8904
  const preferredTab = selectedTab ?? defaultTab;
8055
8905
  const activeTab = preferredTab && tabs.some((tab) => tab.id === preferredTab) ? preferredTab : tabs[0]?.id;
8056
8906
  const selectTab = useCallback22(
@@ -8065,19 +8915,12 @@ function SandboxWorkspace(props) {
8065
8915
  ...activeTab !== void 0 ? { activeTab } : {},
8066
8916
  onActiveTabChange: selectTab,
8067
8917
  ...machine.enabled ? {
8068
- headerAccessory: /* @__PURE__ */ jsx13(
8069
- MachineStateChip,
8070
- {
8071
- chip: machine.chip,
8072
- activeMachine: machine.activeMachine,
8073
- error: machine.error,
8074
- onRetry: () => void machine.refresh()
8075
- }
8076
- )
8918
+ headerAccessory: /* @__PURE__ */ jsx13(MachineStateChip, { chip: machine.chip })
8077
8919
  } : {},
8078
8920
  ...mobileLeadingControl !== void 0 ? { mobileLeadingControl } : {},
8079
8921
  ...collapsed !== void 0 ? { collapsed } : {},
8080
8922
  ...onCollapsedChange ? { onCollapsedChange } : {},
8923
+ ...showCollapseControl !== void 0 ? { showCollapseControl } : {},
8081
8924
  ...autoSaveId !== void 0 ? { autoSaveId } : {},
8082
8925
  ...defaultSize !== void 0 ? { defaultSize } : {},
8083
8926
  ...minSize !== void 0 ? { minSize } : {},
@@ -8089,94 +8932,30 @@ function SandboxWorkspace(props) {
8089
8932
  function DirtyBadge({ count }) {
8090
8933
  return /* @__PURE__ */ jsx13("span", { className: "rounded-og-xs bg-og-accent-soft px-1 text-og-xs text-og-fg-muted", children: count });
8091
8934
  }
8092
- function MachineKindIcon({
8093
- kind,
8094
- className
8095
- }) {
8096
- const Icon = kind === "selfhosted" ? LaptopIcon : CpuIcon;
8097
- return /* @__PURE__ */ jsx13(Icon, { className: cn("size-3.5 shrink-0 text-og-fg-subtle", className), "aria-hidden": true });
8098
- }
8099
8935
  function chipDotClass(state) {
8100
8936
  if (state === "live") return "bg-og-status-running";
8101
8937
  if (state === "waking") return "bg-og-status-idle animate-pulse motion-reduce:animate-none";
8102
8938
  return "bg-og-fg-subtle";
8103
8939
  }
8104
- function MachineStateChip({
8105
- chip,
8106
- activeMachine,
8107
- error,
8108
- onRetry
8109
- }) {
8110
- const triggerRef = useRef20(null);
8111
- const portalStyle = usePortalTokenStyle(triggerRef);
8112
- return /* @__PURE__ */ jsxs11(Popover.Root, { children: [
8113
- /* @__PURE__ */ jsx13(Popover.Trigger, { asChild: true, children: /* @__PURE__ */ jsxs11(
8114
- "button",
8115
- {
8116
- ref: triggerRef,
8117
- type: "button",
8118
- "aria-label": `Machine: ${chip.label}`,
8119
- className: "inline-flex min-h-7 items-center gap-1.5 rounded-og-sm px-2 py-1 text-og-xs font-medium text-og-fg-muted transition-colors hover:bg-og-surface-2 hover:text-og-fg focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-og-accent max-[1023px]:min-h-11 pointer-coarse:min-h-11",
8120
- children: [
8121
- /* @__PURE__ */ jsx13(
8122
- "span",
8123
- {
8124
- className: cn("size-1.5 shrink-0 rounded-full", chipDotClass(chip.state)),
8125
- "aria-hidden": true
8126
- }
8127
- ),
8128
- /* @__PURE__ */ jsx13("span", { className: "max-w-[11rem] truncate", children: chip.label })
8129
- ]
8130
- }
8131
- ) }),
8132
- /* @__PURE__ */ jsx13(Popover.Portal, { forceMount: true, children: /* @__PURE__ */ jsxs11(
8133
- Popover.Content,
8134
- {
8135
- forceMount: true,
8136
- "data-machine-state-popover": true,
8137
- align: "end",
8138
- sideOffset: 6,
8139
- collisionPadding: 8,
8140
- className: "og-root z-50 w-64 max-w-[calc(100vw-1rem)] rounded-og-md border border-og-border bg-og-surface-1 p-3 text-og-sm text-og-fg shadow-lg outline-none",
8141
- style: portalStyle,
8142
- children: [
8143
- /* @__PURE__ */ jsxs11("div", { className: "flex min-w-0 items-center gap-1.5", children: [
8144
- /* @__PURE__ */ jsx13(MachineKindIcon, { kind: activeMachine?.kind }),
8145
- /* @__PURE__ */ jsx13("span", { className: "min-w-0 truncate font-medium", children: activeMachine?.name ?? "Sandbox" })
8146
- ] }),
8147
- /* @__PURE__ */ jsxs11("div", { className: "mt-2.5 flex items-center justify-between gap-2", children: [
8148
- /* @__PURE__ */ jsx13("span", { className: "text-og-xs text-og-fg-subtle", children: "Connection" }),
8149
- activeMachine ? /* @__PURE__ */ jsx13(
8150
- ConnectionStatusPill,
8151
- {
8152
- status: connectionStatusForState(activeMachine.state),
8153
- size: "sm"
8154
- }
8155
- ) : /* @__PURE__ */ jsx13("span", { className: "text-og-xs text-og-fg-muted", children: chip.label })
8156
- ] }),
8157
- chip.asOf ? /* @__PURE__ */ jsxs11("p", { className: "mt-2.5 text-og-xs leading-4 text-og-fg-subtle", children: [
8158
- "Workspace shown as of ",
8159
- formatAsOf(chip.asOf, Date.now()),
8160
- " \u2014 the machine is not live."
8161
- ] }) : null,
8162
- activeMachine && activeMachine.sharedSessionCount > 1 ? /* @__PURE__ */ jsx13("div", { className: "mt-2.5", children: /* @__PURE__ */ jsx13(
8163
- SharedMachineDisclosure,
8164
- {
8165
- sharedSessionCount: activeMachine.sharedSessionCount,
8166
- density: "full"
8167
- }
8168
- ) }) : null,
8169
- error ? /* @__PURE__ */ jsxs11("div", { className: "mt-2.5 space-y-1.5 border-t border-og-border pt-2.5", children: [
8170
- /* @__PURE__ */ jsx13("p", { className: "text-og-xs leading-4 text-og-danger", children: "Couldn't reach the sandbox for this session." }),
8171
- /* @__PURE__ */ jsxs11(DockActionButton, { onClick: onRetry, children: [
8172
- /* @__PURE__ */ jsx13(RefreshCwIcon2, { className: "size-3" }),
8173
- "Retry"
8174
- ] })
8175
- ] }) : null
8176
- ]
8177
- }
8178
- ) })
8179
- ] });
8940
+ function MachineStateChip({ chip }) {
8941
+ return /* @__PURE__ */ jsxs11(
8942
+ "div",
8943
+ {
8944
+ role: "status",
8945
+ "aria-label": `Machine: ${chip.label}`,
8946
+ className: "inline-flex min-h-7 items-center gap-1.5 px-2 py-1 text-og-xs font-medium text-og-fg-muted max-[1023px]:min-h-11 pointer-coarse:min-h-11",
8947
+ children: [
8948
+ /* @__PURE__ */ jsx13(
8949
+ "span",
8950
+ {
8951
+ className: cn("size-1.5 shrink-0 rounded-full", chipDotClass(chip.state)),
8952
+ "aria-hidden": true
8953
+ }
8954
+ ),
8955
+ /* @__PURE__ */ jsx13("span", { className: "max-w-[11rem] truncate", children: chip.label })
8956
+ ]
8957
+ }
8958
+ );
8180
8959
  }
8181
8960
  function DockActionButton({ onClick, children }) {
8182
8961
  return /* @__PURE__ */ jsx13(
@@ -8184,13 +8963,41 @@ function DockActionButton({ onClick, children }) {
8184
8963
  {
8185
8964
  type: "button",
8186
8965
  onClick,
8187
- className: "inline-flex items-center gap-1.5 rounded-og-sm border border-og-border px-2 py-1 text-og-xs font-medium text-og-fg-muted transition-colors hover:border-og-border-strong hover:text-og-fg focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-og-accent max-[1023px]:min-h-11 pointer-coarse:min-h-11",
8966
+ className: "inline-flex items-center gap-1.5 rounded-og-sm border border-og-border px-2 py-1 text-og-xs font-medium text-og-fg-muted transition-colors hover:border-og-border-strong hover:text-og-fg focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-og-accent max-[1023px]:min-h-11 pointer-coarse:min-h-11",
8188
8967
  children
8189
8968
  }
8190
8969
  );
8191
8970
  }
8192
8971
  function ChangesTabBody({
8972
+ comparison,
8973
+ onComparisonChange,
8974
+ ...props
8975
+ }) {
8976
+ return /* @__PURE__ */ jsxs11("div", { className: "flex h-full min-h-0 flex-col", children: [
8977
+ /* @__PURE__ */ jsx13("div", { className: "flex shrink-0 items-center gap-1 border-b border-og-border bg-og-surface-1 px-2 py-1.5", children: [
8978
+ ["branch", "Branch"],
8979
+ ["working", "Uncommitted"],
8980
+ ["staged", "Staged"]
8981
+ ].map(([value, label]) => /* @__PURE__ */ jsx13(
8982
+ "button",
8983
+ {
8984
+ type: "button",
8985
+ "aria-pressed": comparison === value,
8986
+ onClick: () => onComparisonChange(value),
8987
+ className: cn(
8988
+ "rounded-og-sm px-2 py-1 text-og-xs font-medium transition-colors focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-og-accent max-[1023px]:min-h-11 pointer-coarse:min-h-11",
8989
+ comparison === value ? "bg-og-surface-2 text-og-fg shadow-sm" : "text-og-fg-muted hover:bg-og-surface-2 hover:text-og-fg"
8990
+ ),
8991
+ children: label
8992
+ },
8993
+ value
8994
+ )) }),
8995
+ /* @__PURE__ */ jsx13("div", { className: "min-h-0 flex-1", children: /* @__PURE__ */ jsx13(ChangesTabContent, { comparison, ...props }) })
8996
+ ] });
8997
+ }
8998
+ function ChangesTabContent({
8193
8999
  git,
9000
+ comparison,
8194
9001
  captureAvailable,
8195
9002
  captureRevision,
8196
9003
  capturePending,
@@ -8221,7 +9028,7 @@ function ChangesTabBody({
8221
9028
  type: "button",
8222
9029
  onClick: () => void git.refresh(),
8223
9030
  disabled: git.loading,
8224
- className: "inline-flex min-h-7 shrink-0 items-center gap-1 rounded-og-sm border border-og-border bg-og-surface-1 px-2 font-medium text-og-fg transition-colors hover:border-og-border-strong focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-og-accent disabled:cursor-not-allowed disabled:opacity-50 pointer-coarse:min-h-11",
9031
+ className: "inline-flex min-h-7 shrink-0 items-center gap-1 rounded-og-sm border border-og-border bg-og-surface-1 px-2 font-medium text-og-fg transition-colors hover:border-og-border-strong focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-og-accent disabled:cursor-not-allowed disabled:opacity-50 pointer-coarse:min-h-11",
8225
9032
  children: [
8226
9033
  /* @__PURE__ */ jsx13(
8227
9034
  RefreshCwIcon2,
@@ -8277,7 +9084,7 @@ function ChangesTabBody({
8277
9084
  }
8278
9085
  );
8279
9086
  }
8280
- if (!liveWorkspaceExpected && !captureAvailable) {
9087
+ if (!liveWorkspaceExpected && (comparison !== "working" || !captureAvailable)) {
8281
9088
  return /* @__PURE__ */ jsxs11(
8282
9089
  CenteredState,
8283
9090
  {
@@ -8290,7 +9097,7 @@ function ChangesTabBody({
8290
9097
  ) : /* @__PURE__ */ jsx13(CpuIcon, { className: "size-5", "aria-hidden": true }),
8291
9098
  children: [
8292
9099
  /* @__PURE__ */ jsx13("p", { className: "text-og-sm font-medium text-og-fg", children: workspaceWaking ? "Waking workspace" : "Workspace is resting" }),
8293
- /* @__PURE__ */ jsx13("p", { className: "text-og-sm leading-5 text-og-fg-subtle", children: workspaceWaking ? "Connecting to the live working tree\u2026" : "No captured revision is available yet. Wake the sandbox to inspect current changes." }),
9100
+ /* @__PURE__ */ jsx13("p", { className: "text-og-sm leading-5 text-og-fg-subtle", children: workspaceWaking ? "Connecting to the live working tree\u2026" : comparison === "branch" ? "Wake the sandbox to compare this branch with the remote default branch." : comparison === "staged" ? "Wake the sandbox to inspect staged changes." : "No captured revision is available yet. Wake the sandbox to inspect uncommitted changes." }),
8294
9101
  !workspaceWaking ? /* @__PURE__ */ jsxs11(DockActionButton, { onClick: onWake, children: [
8295
9102
  /* @__PURE__ */ jsx13(CpuIcon, { className: "size-3" }),
8296
9103
  "Open live workspace"
@@ -8318,8 +9125,8 @@ function ChangesTabBody({
8318
9125
  );
8319
9126
  }
8320
9127
  return /* @__PURE__ */ jsxs11(CenteredState, { icon: /* @__PURE__ */ jsx13(CircleCheckIcon, { className: "size-5", "aria-hidden": true }), tone: "success", children: [
8321
- /* @__PURE__ */ jsx13("p", { className: "text-og-sm font-medium text-og-fg", children: "Working tree is clean" }),
8322
- /* @__PURE__ */ jsx13("p", { className: "text-og-sm leading-5 text-og-fg-subtle", children: "File edits from future turns will appear here." })
9128
+ /* @__PURE__ */ jsx13("p", { className: "text-og-sm font-medium text-og-fg", children: comparison === "branch" ? "No branch changes" : comparison === "staged" ? "No staged changes" : "No uncommitted changes" }),
9129
+ /* @__PURE__ */ jsx13("p", { className: "text-og-sm leading-5 text-og-fg-subtle", children: comparison === "branch" ? "This branch matches the remote default branch." : comparison === "staged" ? "Stage files to review the next commit here." : "Local edits not yet committed will appear here." })
8323
9130
  ] });
8324
9131
  }
8325
9132
  function CenteredState({
@@ -8356,7 +9163,7 @@ function CenteredState({
8356
9163
  // src/hooks/use-codex-accounts.ts
8357
9164
  import { useCallback as useCallback23, useState as useState21 } from "react";
8358
9165
  function isCodexAccountEvent(event) {
8359
- return event.type === "codex.account.switched" || event.type === "turn.started";
9166
+ return event.type === "codex.account.switched";
8360
9167
  }
8361
9168
  var EMPTY_SETTINGS = {
8362
9169
  rotationEnabled: false,
@@ -8483,6 +9290,7 @@ export {
8483
9290
  FileBrowser,
8484
9291
  FileWriteConflictError,
8485
9292
  FleetTile,
9293
+ GeneratedVideoPlayer,
8486
9294
  HEALTH_TOKEN,
8487
9295
  HumanInputForm,
8488
9296
  HumanInputSurface,
@@ -8551,6 +9359,7 @@ export {
8551
9359
  VoiceRecordingStorageUnavailableError,
8552
9360
  WINDOW_LABEL,
8553
9361
  WORKBENCH_SURFACES,
9362
+ WORKBENCH_TAB_BROWSER,
8554
9363
  WORKBENCH_TAB_CHANGES,
8555
9364
  WORKBENCH_TAB_DESKTOP,
8556
9365
  WORKBENCH_TAB_FILES,
@@ -8636,7 +9445,6 @@ export {
8636
9445
  projectPendingHumanInputRequests,
8637
9446
  projectPickerRows,
8638
9447
  rawTypeOf,
8639
- redactSecrets,
8640
9448
  runnableLatencyModesForModel,
8641
9449
  sandboxCommandExitCode,
8642
9450
  sessionChromeGoalPillState,
@@ -8671,6 +9479,7 @@ export {
8671
9479
  useOpenGeni,
8672
9480
  useOpenGeniClient,
8673
9481
  usePacks,
9482
+ usePageLiveActivity,
8674
9483
  useRelayFrameStream,
8675
9484
  useRig,
8676
9485
  useRigChanges,
@@ -8692,6 +9501,7 @@ export {
8692
9501
  useTranscription,
8693
9502
  useTurnQueue,
8694
9503
  useVariableSets,
9504
+ useVideoArtifactPlaybackLoader,
8695
9505
  useVoiceInput,
8696
9506
  useWorkspaceCapture,
8697
9507
  useWorkspaceEdit,