@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
@@ -1,11 +1,13 @@
1
1
  import type {
2
2
  GitFileDiff,
3
+ SessionCapabilities,
3
4
  SessionEvent,
4
5
  WorkspaceCaptureManifest,
5
6
  WorkspaceCaptureRepo,
6
7
  } from "@opengeni/sdk";
7
8
  import { useCallback, useEffect, useMemo, useRef, useState } from "react";
8
- import { useOpenGeni, type ClientOverride } from "../provider";
9
+ import { type ClientOverride, useOpenGeni } from "../provider";
10
+ import { sandboxAcceptsLiveIo } from "../lib/sandbox-liveness";
9
11
 
10
12
  export type UseSandboxGitOptions = ClientOverride & {
11
13
  /** Live event log (usually `useSessionEvents().events`) — drives auto-refresh
@@ -18,21 +20,32 @@ export type UseSandboxGitOptions = ClientOverride & {
18
20
  repoPaths?: readonly string[] | undefined;
19
21
  /** Diff the staged index vs HEAD (`--cached`) instead of the working tree. */
20
22
  staged?: boolean | undefined;
23
+ /** Review scope. `branch` compares the current branch with the remote default
24
+ * branch, `working` shows uncommitted edits, and `staged` shows the index.
25
+ * `staged` above remains as a compatibility alias. */
26
+ comparison?: SandboxGitComparison | undefined;
21
27
  /** Hold off the initial fetch. Default true. */
22
28
  enabled?: boolean | undefined;
23
- /** Lease liveness ("warm" | "draining" | "cold"). When NOT warm, the diff is
24
- * served from the capture (cold/offline) instead of a live `gitDiff` RPC. */
25
- liveness?: string | undefined;
29
+ /** Pause live reads/invalidation while preserving the last rendered result. */
30
+ active?: boolean | undefined;
31
+ /** Lease liveness. `null` means capability negotiation is still pending and
32
+ * blocks live I/O. When not warm, the diff is served from the capture instead
33
+ * of a live `gitDiff` RPC. Omission preserves legacy direct-hook behavior. */
34
+ liveness?: SessionCapabilities["liveness"] | null | undefined;
26
35
  /** The latest turn-end capture (from `useWorkspaceCapture`). Seeds the diff
27
36
  * immediately; a warm box reconciles live and leaves this durable review
28
37
  * surface in place if the live request is temporarily unavailable. */
29
38
  capture?: WorkspaceCaptureManifest | null | undefined;
30
39
  };
31
40
 
41
+ export type SandboxGitComparison = "branch" | "working" | "staged";
42
+
32
43
  /** A Git diff qualified into workspace scope. `repoRoot` is present for the
33
44
  * workspace-wide path and lets review UI group real repositories without
34
45
  * guessing from the first path segment. */
35
- export type SandboxGitFileDiff = GitFileDiff & { repoRoot?: string | undefined };
46
+ export type SandboxGitFileDiff = GitFileDiff & {
47
+ repoRoot?: string | undefined;
48
+ };
36
49
 
37
50
  export type UseSandboxGitResult = {
38
51
  /** Working-tree (or staged) diff vs HEAD — the structured hunks the Pierre
@@ -83,6 +96,11 @@ function uniqueRoots(roots: readonly string[]): string[] {
83
96
  return [...new Set(roots.map(normalizeRoot))].sort((a, b) => a.localeCompare(b));
84
97
  }
85
98
 
99
+ // GitReadBatchRequest is contract-bounded to 32 items. Keep workspace-wide
100
+ // reads bounded without changing the wire limit; sequential chunks preserve
101
+ // deterministic root/result mapping and cap Channel-A lease concurrency at one.
102
+ const GIT_READ_BATCH_REQUEST_LIMIT = 32;
103
+
86
104
  /** Find the capture repo matching `repoPath` (default workspace root). */
87
105
  function repoForPath(
88
106
  manifest: WorkspaceCaptureManifest,
@@ -175,6 +193,7 @@ export function useSandboxGit(
175
193
  ): UseSandboxGitResult {
176
194
  const { client, workspaceId } = useOpenGeni(options);
177
195
  const enabled = (options.enabled ?? true) && Boolean(sessionId);
196
+ const active = options.active ?? true;
178
197
  const repoPath = options.repoPath ?? "";
179
198
  const workspaceWide = options.repoPaths !== undefined;
180
199
  // Serialize the caller's array so inline values do not retrigger effects. An
@@ -183,10 +202,21 @@ export function useSandboxGit(
183
202
  options.repoPaths && options.repoPaths.length > 0 ? options.repoPaths : [repoPath],
184
203
  ).join("\u0000");
185
204
  const repoPaths = useMemo(() => repoPathsKey.split("\u0000"), [repoPathsKey]);
186
- const staged = options.staged ?? false;
187
- const capture = options.capture ?? null;
188
- const isLive = options.liveness === "warm" || options.liveness === "draining";
189
- const identityKey = `${workspaceId}\u0000${sessionId ?? ""}\u0000${repoPathsKey}\u0000${workspaceWide}\u0000${staged}`;
205
+ const comparison: SandboxGitComparison =
206
+ options.comparison ?? (options.staged ? "staged" : "working");
207
+ const hasAdvertisedRepoPaths = (options.repoPaths?.length ?? 0) > 0;
208
+ // New captures retain both working-tree and branch comparisons. Staged state
209
+ // remains live-only; legacy captures simply lack branchDiff and fall back via
210
+ // the workspace controller without inventing an empty branch comparison.
211
+ const capture = comparison === "staged" ? null : (options.capture ?? null);
212
+ const isLive = sandboxAcceptsLiveIo(options.liveness);
213
+ // `liveness` is optional for compatibility with direct hook consumers. Explicit
214
+ // `null` (negotiating) and lifecycle values require authoritative warm.
215
+ const acceptsEventReads = options.liveness === undefined || isLive;
216
+ const acceptsEventReadsRef = useRef(acceptsEventReads);
217
+ acceptsEventReadsRef.current = acceptsEventReads;
218
+ const hasLiveIoFence = options.liveness !== undefined;
219
+ const identityKey = `${workspaceId}\u0000${sessionId ?? ""}\u0000${repoPathsKey}\u0000${workspaceWide}\u0000${comparison}`;
190
220
 
191
221
  const [diff, setDiff] = useState<SandboxGitFileDiff[]>([]);
192
222
  const [branch, setBranch] = useState<string | null>(null);
@@ -204,6 +234,7 @@ export function useSandboxGit(
204
234
  const refreshGenerationRef = useRef(0);
205
235
  const refreshAbortRef = useRef<AbortController | null>(null);
206
236
  const lastChangeRef = useRef(0);
237
+ const commandRefreshTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
207
238
 
208
239
  const refresh = useCallback(async () => {
209
240
  if (!sessionId) return;
@@ -214,17 +245,88 @@ export function useSandboxGit(
214
245
  setLoading(true);
215
246
  setError(null);
216
247
  try {
217
- const statuses = await Promise.all(
218
- repoPaths.map(async (root) => ({
219
- root,
220
- status: await client.gitStatus(
221
- workspaceId,
222
- sessionId,
223
- { path: root },
224
- { signal: refreshAbort.signal },
225
- ),
226
- })),
227
- );
248
+ const diffRequest = (root: string) =>
249
+ client.gitDiff(
250
+ workspaceId,
251
+ sessionId,
252
+ {
253
+ path: root,
254
+ ...(comparison === "branch"
255
+ ? { fromRef: "origin/HEAD", includeUntracked: true }
256
+ : comparison === "working"
257
+ ? { fromRef: "HEAD", includeUntracked: true }
258
+ : { staged: true, includeUntracked: false }),
259
+ },
260
+ { signal: refreshAbort.signal },
261
+ );
262
+
263
+ // Capability-advertised roots are known repositories. Fetch status metadata
264
+ // and diff hunks together: both are independent Channel-A reads and each can
265
+ // take several seconds on a remote sandbox. The legacy root probe remains
266
+ // serial so a non-repository workspace never receives a failing git diff.
267
+ const prefetched = hasAdvertisedRepoPaths
268
+ ? await (async () => {
269
+ const prefetchedRepos: Array<{
270
+ root: string;
271
+ status: Awaited<ReturnType<typeof client.gitStatus>>;
272
+ result: Awaited<ReturnType<typeof client.gitDiff>>;
273
+ }> = [];
274
+ for (
275
+ let offset = 0;
276
+ offset < repoPaths.length;
277
+ offset += GIT_READ_BATCH_REQUEST_LIMIT
278
+ ) {
279
+ const roots = repoPaths.slice(offset, offset + GIT_READ_BATCH_REQUEST_LIMIT);
280
+ const batch = await client.gitReadBatch(
281
+ workspaceId,
282
+ sessionId,
283
+ {
284
+ requests: roots.map((root) => ({
285
+ status: { path: root },
286
+ diff: {
287
+ path: root,
288
+ ...(comparison === "branch"
289
+ ? { fromRef: "origin/HEAD", includeUntracked: true }
290
+ : comparison === "working"
291
+ ? { fromRef: "HEAD", includeUntracked: true }
292
+ : { staged: true, includeUntracked: false }),
293
+ },
294
+ })),
295
+ },
296
+ { signal: refreshAbort.signal },
297
+ );
298
+ if (batch.results.length !== roots.length) {
299
+ throw new Error(
300
+ `Workspace Git batch returned ${batch.results.length} results for ${roots.length} repositories.`,
301
+ );
302
+ }
303
+ batch.results.forEach((item, index) => {
304
+ prefetchedRepos.push({
305
+ root: roots[index]!,
306
+ status: item.status,
307
+ result: item.diff ?? {
308
+ files: [],
309
+ revision: item.status.revision,
310
+ },
311
+ });
312
+ });
313
+ }
314
+ return prefetchedRepos;
315
+ })()
316
+ : null;
317
+ const statuses =
318
+ prefetched ??
319
+ (await Promise.all(
320
+ repoPaths.map(async (root) => ({
321
+ root,
322
+ status: await client.gitStatus(
323
+ workspaceId,
324
+ sessionId,
325
+ { path: root },
326
+ { signal: refreshAbort.signal },
327
+ ),
328
+ })),
329
+ ));
228
330
  if (refreshGenerationRef.current !== generation) return;
229
331
  const repositories = statuses.filter(({ status }) => status.isRepo);
230
332
  const roots = repositories.map(({ root }) => root);
@@ -239,19 +341,11 @@ export function useSandboxGit(
239
341
  setSource("live");
240
342
  return;
241
343
  }
344
+ const prefetchedByRoot = new Map(prefetched?.map(({ root, result }) => [root, result]));
242
345
  const results = await Promise.all(
243
346
  repositories.map(async ({ root }) => ({
244
347
  root,
245
- result: await client.gitDiff(
246
- workspaceId,
247
- sessionId,
248
- {
249
- path: root,
250
- staged,
251
- includeUntracked: !staged,
252
- },
253
- { signal: refreshAbort.signal },
254
- ),
348
+ result: prefetchedByRoot.get(root) ?? (await diffRequest(root)),
255
349
  })),
256
350
  );
257
351
  if (refreshGenerationRef.current !== generation) return;
@@ -273,15 +367,25 @@ export function useSandboxGit(
273
367
  if (refreshGenerationRef.current === generation) setLoading(false);
274
368
  if (refreshAbortRef.current === refreshAbort) refreshAbortRef.current = null;
275
369
  }
276
- }, [client, workspaceId, sessionId, repoPaths, staged, workspaceWide]);
370
+ }, [
371
+ client,
372
+ workspaceId,
373
+ sessionId,
374
+ repoPaths,
375
+ hasAdvertisedRepoPaths,
376
+ workspaceWide,
377
+ comparison,
378
+ ]);
277
379
 
278
- // Serve the diff from a capture (the cold/offline source). `staged` has no
279
- // meaning here: the capture records the combined turn-end change surface vs HEAD.
380
+ // Serve the requested review scope from a capture when that scope exists.
280
381
  const seedFromCapture = useCallback(
281
382
  (manifest: WorkspaceCaptureManifest) => {
282
383
  const repositories = workspaceWide
283
384
  ? manifest.repos
284
385
  : ([repoForPath(manifest, repoPath)].filter(Boolean) as WorkspaceCaptureRepo[]);
386
+ if (comparison === "branch" && repositories.some((repo) => repo.branchDiff === undefined)) {
387
+ return;
388
+ }
285
389
  if (repositories.length === 0) {
286
390
  setIsRepo(false);
287
391
  setBranch(null);
@@ -301,16 +405,19 @@ export function useSandboxGit(
301
405
  setDiff((prev) =>
302
406
  mergeDiffs(
303
407
  prev,
304
- repositories.flatMap((repo) =>
305
- workspaceWide ? repo.diff.map((file) => qualifyDiff(repo.root, file)) : repo.diff,
306
- ),
408
+ repositories.flatMap((repo) => {
409
+ const capturedDiff = comparison === "branch" ? (repo.branchDiff ?? []) : repo.diff;
410
+ return workspaceWide
411
+ ? capturedDiff.map((file) => qualifyDiff(repo.root, file))
412
+ : capturedDiff;
413
+ }),
307
414
  ),
308
415
  );
309
416
  sourceRef.current = "capture";
310
417
  setSource("capture");
311
418
  setError(null);
312
419
  },
313
- [repoPath, workspaceWide],
420
+ [comparison, repoPath, workspaceWide],
314
421
  );
315
422
 
316
423
  // Source selection on mount / liveness / capture-revision change. Key on the
@@ -349,7 +456,11 @@ export function useSandboxGit(
349
456
  if (currentCapture && (identityChanged || !isLive || sourceRef.current !== "live")) {
350
457
  seedFromCapture(currentCapture);
351
458
  }
352
- if (isLive || !currentCapture) {
459
+ if (!active) {
460
+ if (!active) setLoading(false);
461
+ return;
462
+ }
463
+ if (isLive || (!hasLiveIoFence && !currentCapture)) {
353
464
  void refresh();
354
465
  }
355
466
  return () => {
@@ -357,25 +468,76 @@ export function useSandboxGit(
357
468
  refreshAbortRef.current = null;
358
469
  refreshGenerationRef.current += 1;
359
470
  };
360
- }, [enabled, isLive, captureRevision, identityKey, refresh, seedFromCapture]);
471
+ }, [
472
+ enabled,
473
+ active,
474
+ isLive,
475
+ hasLiveIoFence,
476
+ captureRevision,
477
+ identityKey,
478
+ refresh,
479
+ seedFromCapture,
480
+ ]);
361
481
 
362
- // git.changed → re-fetch the LIVE diff. A git.changed only originates from a
363
- // live box, so this both keeps warm sessions fresh and folds a cold box that
364
- // just came up (unchanged from the pre-capture behavior).
482
+ // git.changed → re-fetch the LIVE diff. Agent tool completion is also an
483
+ // invalidation point because shell/apply-patch writes can bypass Channel-A and
484
+ // therefore emit no git.changed event. This is event-driven, never polling.
365
485
  const events = options.events;
366
486
  useEffect(() => {
367
- if (!enabled || !events) return;
487
+ if (!enabled || !events) {
488
+ if (commandRefreshTimerRef.current) {
489
+ clearTimeout(commandRefreshTimerRef.current);
490
+ commandRefreshTimerRef.current = null;
491
+ }
492
+ return;
493
+ }
494
+ // Historical events describe the capture we already loaded. Inactive tabs
495
+ // and holderless draining/cold leases advance the cursor without issuing
496
+ // provider I/O; becoming warm performs one authoritative reconcile above.
497
+ if (!active || !acceptsEventReads) {
498
+ for (const event of events) {
499
+ if (event.sequence > lastChangeRef.current) lastChangeRef.current = event.sequence;
500
+ }
501
+ if (commandRefreshTimerRef.current) {
502
+ clearTimeout(commandRefreshTimerRef.current);
503
+ commandRefreshTimerRef.current = null;
504
+ }
505
+ return;
506
+ }
368
507
  let latest = lastChangeRef.current;
508
+ let immediate = false;
509
+ let commandDelta = false;
369
510
  for (const event of events) {
370
- if (event.type === "git.changed" && event.sequence > latest) {
511
+ if (event.sequence <= latest) continue;
512
+ if (event.type === "git.changed" || event.type === "agent.toolCall.output") {
513
+ latest = event.sequence;
514
+ immediate = true;
515
+ } else if (event.type === "sandbox.command.output.delta") {
371
516
  latest = event.sequence;
517
+ commandDelta = true;
372
518
  }
373
519
  }
374
520
  if (latest > lastChangeRef.current) {
375
521
  lastChangeRef.current = latest;
376
- void refresh();
522
+ if (immediate) {
523
+ if (commandRefreshTimerRef.current) clearTimeout(commandRefreshTimerRef.current);
524
+ commandRefreshTimerRef.current = null;
525
+ if (acceptsEventReadsRef.current) void refresh();
526
+ } else if (commandDelta && !commandRefreshTimerRef.current) {
527
+ commandRefreshTimerRef.current = setTimeout(() => {
528
+ commandRefreshTimerRef.current = null;
529
+ if (acceptsEventReadsRef.current) void refresh();
530
+ }, 1_000);
531
+ }
377
532
  }
378
- }, [enabled, events, refresh]);
533
+ }, [enabled, active, acceptsEventReads, events, refresh]);
534
+
535
+ useEffect(
536
+ () => () => {
537
+ if (commandRefreshTimerRef.current) clearTimeout(commandRefreshTimerRef.current);
538
+ },
539
+ [],
540
+ );
379
541
 
380
542
  const identityMatches = enabled && stateIdentity === identityKey;
381
543
  const visibleRepoRoots = identityMatches ? repoRoots : [];
@@ -7,6 +7,7 @@ import type {
7
7
  import { OpenGeniApiError } from "@opengeni/sdk";
8
8
  import { useCallback, useEffect, useMemo, useRef, useState } from "react";
9
9
  import { useOpenGeni, type ClientOverride } from "../provider";
10
+ import { sandboxAcceptsLiveIo } from "../lib/sandbox-liveness";
10
11
 
11
12
  export type TerminalChunk = {
12
13
  /** Stable key (the source event id) so the xterm writer tracks a written-cursor. */
@@ -107,9 +108,9 @@ export function useSandboxTerminal(
107
108
  // projection is the single source of truth for what's written). The open is
108
109
  // gated on a WARM box: opening on a cold/warming lease races the box (the PTY
109
110
  // exec-session is created on a box that the next op may not resume), which is
110
- // exactly the "session not found" terminal failure; we wait for warm/draining.
111
+ // exactly the "session not found" terminal failure; we wait for warm.
111
112
  const openInFlight = useRef(false);
112
- const boxWarm = liveness === undefined || liveness === "warm" || liveness === "draining";
113
+ const boxWarm = liveness === undefined || sandboxAcceptsLiveIo(liveness);
113
114
  useEffect(() => {
114
115
  if (!interactive || !sessionId || ptyFilter || !boxWarm) {
115
116
  setOpenError(null);