@opengeni/react 0.48.0 → 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 (284) hide show
  1. package/README.md +90 -2
  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-FWXGSNXT.js → chunk-724BW4TW.js} +585 -34
  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-I4MPFDRP.js → chunk-FWS3KC3Z.js} +3 -3
  23. package/dist/{chunk-EKZH6IDG.js → chunk-HDBG7XGO.js} +62 -1
  24. package/dist/chunk-HDBG7XGO.js.map +1 -0
  25. package/dist/chunk-HUIKIDM5.js +2148 -0
  26. package/dist/chunk-HUIKIDM5.js.map +1 -0
  27. package/dist/{chunk-XCNTXWBB.js → chunk-JAGDUCZQ.js} +854 -340
  28. package/dist/chunk-JAGDUCZQ.js.map +1 -0
  29. package/dist/{chunk-LHLYWG5R.js → chunk-JR3QFY6B.js} +413 -37
  30. package/dist/chunk-JR3QFY6B.js.map +1 -0
  31. package/dist/chunk-JVYQRRC4.js +53 -0
  32. package/dist/chunk-JVYQRRC4.js.map +1 -0
  33. package/dist/chunk-KCGXPP5V.js +157 -0
  34. package/dist/chunk-KCGXPP5V.js.map +1 -0
  35. package/dist/chunk-PEI6RANI.js +3234 -0
  36. package/dist/chunk-PEI6RANI.js.map +1 -0
  37. package/dist/{chunk-NJ7M2B6Z.js → chunk-ROIRTM37.js} +18 -10
  38. package/dist/{chunk-NJ7M2B6Z.js.map → chunk-ROIRTM37.js.map} +1 -1
  39. package/dist/{chunk-O4VXOBGF.js → chunk-WSK7G5LE.js} +70 -2
  40. package/dist/chunk-WSK7G5LE.js.map +1 -0
  41. package/dist/{chunk-OL6QVXGF.js → chunk-X4S56Y2H.js} +27 -7
  42. package/dist/{chunk-OL6QVXGF.js.map → chunk-X4S56Y2H.js.map} +1 -1
  43. package/dist/client.d.ts +7 -1
  44. package/dist/clipboard.d.ts +1 -0
  45. package/dist/clipboard.js +7 -0
  46. package/dist/clipboard.js.map +1 -0
  47. package/dist/commands/index.d.ts +3 -3
  48. package/dist/commands/registry.d.ts +1 -1
  49. package/dist/commands/types.d.ts +1 -1
  50. package/dist/components/approval-surface.d.ts +2 -2
  51. package/dist/components/artifacts/artifact-surface.d.ts +18 -0
  52. package/dist/components/artifacts/document-editor.d.ts +172 -0
  53. package/dist/components/artifacts/editable-artifact-ui.d.ts +13 -0
  54. package/dist/components/artifacts/editable-artifact-workbench.d.ts +49 -0
  55. package/dist/components/artifacts/editable-document.d.ts +24 -0
  56. package/dist/components/artifacts/editable-presentation.d.ts +24 -0
  57. package/dist/components/artifacts/editable-spreadsheet.d.ts +32 -0
  58. package/dist/components/artifacts/index.d.ts +8 -0
  59. package/dist/components/artifacts/presentation-editor.d.ts +297 -0
  60. package/dist/components/artifacts/spreadsheet-canvas.d.ts +120 -0
  61. package/dist/components/artifacts/spreadsheet-grid.d.ts +113 -0
  62. package/dist/components/browser-viewer.d.ts +29 -0
  63. package/dist/components/chat-composer.d.ts +8 -8
  64. package/dist/components/code-editor.d.ts +25 -3
  65. package/dist/components/command-palette.d.ts +2 -2
  66. package/dist/components/composer-transcription-control.d.ts +2 -2
  67. package/dist/components/composer.d.ts +17 -17
  68. package/dist/components/computer-viewer.d.ts +23 -0
  69. package/dist/components/copy-button.d.ts +2 -2
  70. package/dist/components/desktop-viewer.d.ts +25 -21
  71. package/dist/components/diff-view.d.ts +1 -1
  72. package/dist/components/enrollment-consent.d.ts +1 -1
  73. package/dist/components/enrollment-device-flow.d.ts +1 -1
  74. package/dist/components/file-browser.d.ts +2 -2
  75. package/dist/components/fleet-tile.d.ts +1 -1
  76. package/dist/components/generated-video-player.d.ts +10 -0
  77. package/dist/components/human-input-form.d.ts +1 -1
  78. package/dist/components/human-input-surface.d.ts +2 -2
  79. package/dist/components/interactive-workbench-viewers.d.ts +2 -0
  80. package/dist/components/machine-card.d.ts +2 -2
  81. package/dist/components/machine-dock-bar.d.ts +3 -3
  82. package/dist/components/machine-health-pill.d.ts +2 -2
  83. package/dist/components/machine-input-display.d.ts +1 -1
  84. package/dist/components/machine-metrics.d.ts +2 -2
  85. package/dist/components/machine-status-pill.d.ts +4 -4
  86. package/dist/components/machines/health.d.ts +1 -1
  87. package/dist/components/machines/machine-detail.d.ts +3 -3
  88. package/dist/components/machines/metric-history-chart.d.ts +1 -1
  89. package/dist/components/machines/metric-sparkline.d.ts +2 -2
  90. package/dist/components/machines/series.d.ts +2 -2
  91. package/dist/components/machines-dashboard.d.ts +2 -2
  92. package/dist/components/markdown.d.ts +1 -1
  93. package/dist/components/message-timeline.d.ts +12 -5
  94. package/dist/components/model-picker.d.ts +2 -2
  95. package/dist/components/model-policy-picker.d.ts +7 -7
  96. package/dist/components/pierre-diff.d.ts +1 -1
  97. package/dist/components/pierre-file.d.ts +1 -1
  98. package/dist/components/queue-draft-policy.d.ts +1 -1
  99. package/dist/components/queue-surface-implementation.d.ts +3 -3
  100. package/dist/components/queue-surface-state.d.ts +4 -4
  101. package/dist/components/queue-surface.d.ts +5 -5
  102. package/dist/components/sandbox-files.d.ts +3 -3
  103. package/dist/components/sandbox-terminal.d.ts +21 -4
  104. package/dist/components/sandbox-workspace.d.ts +25 -9
  105. package/dist/components/session-chrome.d.ts +4 -4
  106. package/dist/components/session-status.d.ts +2 -2
  107. package/dist/components/timeline-annotation-selection.d.ts +9 -0
  108. package/dist/components/timeline-annotations-dialog.d.ts +16 -0
  109. package/dist/components/timeline-annotations.d.ts +13 -0
  110. package/dist/components/tooltip.d.ts +4 -4
  111. package/dist/components/user-message-body.d.ts +2 -2
  112. package/dist/components/workbench-changes.d.ts +2 -2
  113. package/dist/components/workspace-dock.d.ts +1 -1
  114. package/dist/composer.d.ts +11 -11
  115. package/dist/composer.js +5 -5
  116. package/dist/hooks/internal.d.ts +1 -1
  117. package/dist/hooks/use-attached-browsers.d.ts +21 -0
  118. package/dist/hooks/use-available-models.d.ts +2 -2
  119. package/dist/hooks/use-billing-usage.d.ts +1 -1
  120. package/dist/hooks/use-browser-frame-stream.d.ts +28 -0
  121. package/dist/hooks/use-browser-identities.d.ts +26 -0
  122. package/dist/hooks/use-browser-session.d.ts +28 -0
  123. package/dist/hooks/use-browser-sessions.d.ts +32 -0
  124. package/dist/hooks/use-codex-accounts.d.ts +2 -2
  125. package/dist/hooks/use-composer.d.ts +36 -9
  126. package/dist/hooks/use-computer-frame-stream.d.ts +24 -0
  127. package/dist/hooks/use-computer-session.d.ts +28 -0
  128. package/dist/hooks/use-computer-sessions.d.ts +28 -0
  129. package/dist/hooks/use-desktop-stream.d.ts +1 -1
  130. package/dist/hooks/use-environments.d.ts +1 -1
  131. package/dist/hooks/use-file-attachments.d.ts +1 -1
  132. package/dist/hooks/use-goal.d.ts +2 -2
  133. package/dist/hooks/use-human-input.d.ts +2 -2
  134. package/dist/hooks/use-last-started-turn-policy.d.ts +2 -2
  135. package/dist/hooks/use-machine-chip.d.ts +2 -2
  136. package/dist/hooks/use-machines.d.ts +4 -4
  137. package/dist/hooks/use-packs.d.ts +7 -2
  138. package/dist/hooks/use-rigs.d.ts +1 -1
  139. package/dist/hooks/use-sandbox-files.d.ts +6 -4
  140. package/dist/hooks/use-sandbox-git.d.ts +6 -5
  141. package/dist/hooks/use-sandbox-terminal.d.ts +1 -1
  142. package/dist/hooks/use-scheduled-tasks.d.ts +1 -1
  143. package/dist/hooks/use-session-capabilities.d.ts +5 -4
  144. package/dist/hooks/use-session-control.d.ts +1 -1
  145. package/dist/hooks/use-session-events.d.ts +2 -2
  146. package/dist/hooks/use-session-lineage.d.ts +1 -1
  147. package/dist/hooks/use-session-mcp-approval-policy.d.ts +2 -2
  148. package/dist/hooks/use-session.d.ts +2 -2
  149. package/dist/hooks/use-slash-commands.d.ts +1 -1
  150. package/dist/hooks/use-terminal-stream.d.ts +8 -2
  151. package/dist/hooks/use-turn-queue.d.ts +2 -2
  152. package/dist/hooks/use-video-artifact-playback.d.ts +9 -0
  153. package/dist/hooks/use-voice-input.d.ts +1 -1
  154. package/dist/hooks/use-workspace-capture.d.ts +1 -1
  155. package/dist/hooks/use-workspace-edit.d.ts +1 -1
  156. package/dist/hooks/use-workspace-sessions.d.ts +1 -1
  157. package/dist/hooks/use-workspaces.d.ts +1 -1
  158. package/dist/index.d.ts +158 -155
  159. package/dist/index.js +906 -462
  160. package/dist/index.js.map +1 -1
  161. package/dist/interaction.d.ts +23 -0
  162. package/dist/interaction.js +36 -0
  163. package/dist/interaction.js.map +1 -0
  164. package/dist/interactive-workbench-viewers-KHNOHWQE.js +13 -0
  165. package/dist/interactive-workbench-viewers-KHNOHWQE.js.map +1 -0
  166. package/dist/lib/relay-wire.d.ts +4 -0
  167. package/dist/lib/sandbox-liveness.d.ts +9 -0
  168. package/dist/lib/terminal-capability.d.ts +5 -0
  169. package/dist/lib/xterm-theme.d.ts +1 -1
  170. package/dist/machines.d.ts +30 -30
  171. package/dist/machines.js +3 -3
  172. package/dist/provider.d.ts +4 -4
  173. package/dist/{queue-surface-implementation-U5JFG7HT.js → queue-surface-implementation-2KVJTYXH.js} +100 -48
  174. package/dist/queue-surface-implementation-2KVJTYXH.js.map +1 -0
  175. package/dist/realtime/dropdown-menu.d.ts +8 -8
  176. package/dist/realtime/realtime-control.d.ts +6 -6
  177. package/dist/realtime.d.ts +4 -4
  178. package/dist/realtime.js +3 -3
  179. package/dist/session-context.d.ts +19 -1
  180. package/dist/session-ui.d.ts +14 -14
  181. package/dist/session-ui.js +5 -4
  182. package/dist/session.d.ts +28 -28
  183. package/dist/session.js +5 -5
  184. package/dist/timeline/activity-rail.d.ts +5 -3
  185. package/dist/timeline/disclosure-context.d.ts +1 -1
  186. package/dist/timeline/fleet-decision-projection.d.ts +1 -1
  187. package/dist/timeline/fleet-decision-row.d.ts +3 -3
  188. package/dist/timeline/index.d.ts +19 -19
  189. package/dist/timeline/parsers.d.ts +4 -1
  190. package/dist/timeline/projection.d.ts +3 -3
  191. package/dist/timeline/registry.d.ts +8 -2
  192. package/dist/timeline/screenshot-lightbox.d.ts +2 -2
  193. package/dist/timeline/shared.d.ts +21 -10
  194. package/dist/timeline/tool-diff.d.ts +2 -2
  195. package/dist/timeline/tool-renderers.d.ts +1 -1
  196. package/dist/timeline/turn-summary.d.ts +3 -3
  197. package/dist/timeline/types.d.ts +39 -3
  198. package/dist/timeline-annotation-selection-3L7LHGG2.js +187 -0
  199. package/dist/timeline-annotation-selection-3L7LHGG2.js.map +1 -0
  200. package/dist/timeline-annotations-dialog-U7EVLR75.js +196 -0
  201. package/dist/timeline-annotations-dialog-U7EVLR75.js.map +1 -0
  202. package/package.json +36 -7
  203. package/src/artifacts-document.ts +31 -0
  204. package/src/artifacts-presentation.ts +36 -0
  205. package/src/artifacts-spreadsheet.ts +21 -0
  206. package/src/artifacts.ts +86 -0
  207. package/src/client.ts +87 -1
  208. package/src/clipboard.ts +2 -0
  209. package/src/components/artifacts/artifact-surface.tsx +93 -0
  210. package/src/components/artifacts/document-editor.tsx +2255 -0
  211. package/src/components/artifacts/editable-artifact-ui.tsx +89 -0
  212. package/src/components/artifacts/editable-artifact-workbench.tsx +203 -0
  213. package/src/components/artifacts/editable-document.tsx +657 -0
  214. package/src/components/artifacts/editable-presentation.tsx +1020 -0
  215. package/src/components/artifacts/editable-spreadsheet.tsx +733 -0
  216. package/src/components/artifacts/index.ts +99 -0
  217. package/src/components/artifacts/presentation-editor.tsx +3148 -0
  218. package/src/components/artifacts/spreadsheet-canvas.ts +799 -0
  219. package/src/components/artifacts/spreadsheet-grid.tsx +2129 -0
  220. package/src/components/browser-viewer.tsx +1592 -0
  221. package/src/components/chat-composer.tsx +13 -0
  222. package/src/components/code-editor.tsx +89 -30
  223. package/src/components/composer-transcription-control.tsx +10 -6
  224. package/src/components/computer-viewer.tsx +1139 -0
  225. package/src/components/desktop-viewer.tsx +101 -73
  226. package/src/components/generated-video-player.tsx +89 -0
  227. package/src/components/interactive-workbench-viewers.ts +2 -0
  228. package/src/components/machine-input-display.ts +3 -0
  229. package/src/components/message-timeline.tsx +289 -32
  230. package/src/components/queue-surface-implementation.tsx +116 -60
  231. package/src/components/sandbox-files.tsx +2 -2
  232. package/src/components/sandbox-terminal.tsx +106 -41
  233. package/src/components/sandbox-workspace.tsx +215 -87
  234. package/src/components/session-chrome.tsx +53 -13
  235. package/src/components/timeline-annotation-selection.tsx +221 -0
  236. package/src/components/timeline-annotations-dialog.tsx +213 -0
  237. package/src/components/timeline-annotations.tsx +84 -0
  238. package/src/components/workbench-changes.tsx +188 -31
  239. package/src/hooks/use-attached-browsers.ts +152 -0
  240. package/src/hooks/use-browser-frame-stream.ts +316 -0
  241. package/src/hooks/use-browser-identities.ts +225 -0
  242. package/src/hooks/use-browser-session.ts +476 -0
  243. package/src/hooks/use-browser-sessions.ts +281 -0
  244. package/src/hooks/use-composer.ts +769 -24
  245. package/src/hooks/use-computer-frame-stream.ts +311 -0
  246. package/src/hooks/use-computer-session.ts +442 -0
  247. package/src/hooks/use-computer-sessions.ts +245 -0
  248. package/src/hooks/use-machine-chip.ts +5 -3
  249. package/src/hooks/use-machines.ts +33 -3
  250. package/src/hooks/use-packs.ts +51 -0
  251. package/src/hooks/use-sandbox-files.ts +98 -55
  252. package/src/hooks/use-sandbox-git.ts +93 -38
  253. package/src/hooks/use-sandbox-terminal.ts +3 -2
  254. package/src/hooks/use-session-capabilities.ts +59 -46
  255. package/src/hooks/use-terminal-stream.ts +102 -23
  256. package/src/hooks/use-video-artifact-playback.ts +28 -0
  257. package/src/hooks/use-workspace-edit.ts +2 -1
  258. package/src/index.ts +10 -0
  259. package/src/interaction.ts +71 -0
  260. package/src/lib/relay-wire.ts +4 -0
  261. package/src/lib/sandbox-liveness.ts +11 -0
  262. package/src/lib/terminal-capability.ts +10 -0
  263. package/src/session-context.ts +90 -0
  264. package/src/timeline/activity-rail.tsx +19 -3
  265. package/src/timeline/index.ts +3 -0
  266. package/src/timeline/parsers.ts +14 -1
  267. package/src/timeline/projection.ts +211 -14
  268. package/src/timeline/registry.ts +13 -1
  269. package/src/timeline/screenshot-lightbox.tsx +19 -9
  270. package/src/timeline/shared.tsx +56 -8
  271. package/src/timeline/tool-renderers.tsx +303 -50
  272. package/src/timeline/types.ts +55 -3
  273. package/styles/compiled.css +248 -15
  274. package/styles/index.css +15 -0
  275. package/styles/responsive.css +13 -1
  276. package/dist/chunk-EKZH6IDG.js.map +0 -1
  277. package/dist/chunk-FWXGSNXT.js.map +0 -1
  278. package/dist/chunk-LHLYWG5R.js.map +0 -1
  279. package/dist/chunk-O4VXOBGF.js.map +0 -1
  280. package/dist/chunk-XCNTXWBB.js.map +0 -1
  281. package/dist/fleet-decision-projection-SLNZMS42.js +0 -241
  282. package/dist/fleet-decision-projection-SLNZMS42.js.map +0 -1
  283. package/dist/queue-surface-implementation-U5JFG7HT.js.map +0 -1
  284. /package/dist/{chunk-I4MPFDRP.js.map → chunk-FWS3KC3Z.js.map} +0 -0
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-OL6QVXGF.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-I4MPFDRP.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,
@@ -102,7 +47,6 @@ import {
102
47
  sandboxCommandExitCode,
103
48
  sessionChromeGoalPillState,
104
49
  stripExecBanner,
105
- tableElementToTsv,
106
50
  tailPeek,
107
51
  unwrapMcpOutput,
108
52
  useLightbox,
@@ -110,16 +54,7 @@ import {
110
54
  useThemeType,
111
55
  userMessageLikelyNeedsDisclosure,
112
56
  v4aToGitFileDiff
113
- } from "./chunk-XCNTXWBB.js";
114
- import "./chunk-O4VXOBGF.js";
115
- import {
116
- buildTimeline,
117
- creditExhaustedFromEvents,
118
- extractSessionRef,
119
- groupTimeline,
120
- sessionStatusFromEvents,
121
- toolDisplayName
122
- } from "./chunk-LHLYWG5R.js";
57
+ } from "./chunk-JAGDUCZQ.js";
123
58
  import {
124
59
  Actions,
125
60
  AttachButton,
@@ -172,20 +107,7 @@ import {
172
107
  useSlashCommands,
173
108
  useTranscription,
174
109
  useVoiceInput
175
- } from "./chunk-NJ7M2B6Z.js";
176
- import {
177
- FILE_ONLY_MESSAGE_TEXT,
178
- composeSendInput,
179
- shouldSteerOnKey,
180
- shouldSubmitOnKey,
181
- useComposer
182
- } from "./chunk-FWXGSNXT.js";
183
- import {
184
- useMutationRunner,
185
- usePageLiveActivity,
186
- usePolledValue,
187
- useSessionEventTrigger
188
- } from "./chunk-KZ73RL76.js";
110
+ } from "./chunk-ROIRTM37.js";
189
111
  import {
190
112
  BillingClassMark,
191
113
  ModelPolicyPicker,
@@ -195,10 +117,6 @@ import {
195
117
  PickerNavRow,
196
118
  defaultModelPolicyPickerMessages
197
119
  } from "./chunk-WYA65Y3F.js";
198
- import {
199
- useOpenGeni,
200
- useOpenGeniClient
201
- } from "./chunk-EKZH6IDG.js";
202
120
  import {
203
121
  advancedSourceSummary,
204
122
  availabilityReasonLabel,
@@ -223,6 +141,83 @@ import {
223
141
  TooltipProvider,
224
142
  TooltipTrigger
225
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,12 +232,48 @@ import {
237
232
  tryParseJson
238
233
  } from "./chunk-LAKLF4PA.js";
239
234
  import {
240
- usePortalTokenSource,
241
- usePortalTokenStyle
242
- } from "./chunk-VZTQ73XT.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";
243
251
  import {
244
252
  cn
245
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
+ }
246
277
 
247
278
  // src/hooks/use-last-started-turn-policy.ts
248
279
  import { useCallback, useEffect } from "react";
@@ -379,7 +410,7 @@ function useWorkspaceSessions(options = {}) {
379
410
  }
380
411
 
381
412
  // src/hooks/use-environments.ts
382
- import { useCallback as useCallback4, useMemo } from "react";
413
+ import { useCallback as useCallback4, useMemo as useMemo2 } from "react";
383
414
  function useVariableSets(options = {}) {
384
415
  const { client, workspaceId } = useOpenGeni(options);
385
416
  const load = useCallback4(
@@ -470,7 +501,7 @@ function useVariableSets(options = {}) {
470
501
  };
471
502
  }
472
503
  function useEnvironments(options = {}) {
473
- const legacyClient = useMemo(() => {
504
+ const legacyClient = useMemo2(() => {
474
505
  if (!options.client) {
475
506
  return void 0;
476
507
  }
@@ -737,6 +768,30 @@ function usePacks(options = {}) {
737
768
  },
738
769
  [client, workspaceId, run, refresh]
739
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
+ );
740
795
  const remove = useCallback6(
741
796
  async (packId) => {
742
797
  const result = await run(async () => {
@@ -764,6 +819,10 @@ function usePacks(options = {}) {
764
819
  refresh,
765
820
  register,
766
821
  enable,
822
+ previewInstallation,
823
+ install,
824
+ previewUninstall,
825
+ uninstall,
767
826
  remove,
768
827
  mutating,
769
828
  mutationError,
@@ -893,16 +952,24 @@ import {
893
952
  applyUrlRotation
894
953
  } from "@opengeni/sdk";
895
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
896
969
  function desktopAttachable(cell) {
897
970
  if (cell.transport !== null) return true;
898
971
  return cell.reason === "lease_cold" || cell.reason === "not_provisioned" || cell.reason === null;
899
972
  }
900
- function terminalAttachable(cell) {
901
- if (cell.transport === null) {
902
- return cell.reason === "lease_cold" || cell.reason === "not_provisioned" || cell.reason === null;
903
- }
904
- return true;
905
- }
906
973
  function rotationsFrom(events) {
907
974
  const out = [];
908
975
  for (const event of events) {
@@ -954,8 +1021,12 @@ function useSessionCapabilities(sessionId, options = {}) {
954
1021
  if (!lifecycleEnabled || !sessionId) {
955
1022
  setState("idle");
956
1023
  setCapabilities(null);
1024
+ viewerIdRef.current = null;
1025
+ setViewerId(null);
957
1026
  return;
958
1027
  }
1028
+ viewerIdRef.current = null;
1029
+ setViewerId(null);
959
1030
  let cancelled = false;
960
1031
  let pollTimer = null;
961
1032
  let heartbeatTimer = null;
@@ -1004,7 +1075,7 @@ function useSessionCapabilities(sessionId, options = {}) {
1004
1075
  void client.getStreamCapabilities(workspaceId, sessionId, { signal: requestAbort.signal }).then((caps) => {
1005
1076
  if (cancelled) return;
1006
1077
  settle(caps);
1007
- if (caps.liveness === "warm" || caps.liveness === "draining") {
1078
+ if (sandboxAcceptsLiveIo(caps.liveness)) {
1008
1079
  setState("ready");
1009
1080
  return;
1010
1081
  }
@@ -1033,18 +1104,25 @@ function useSessionCapabilities(sessionId, options = {}) {
1033
1104
  if (cancelled) return;
1034
1105
  settle(caps);
1035
1106
  const wantDesktopAttach = attachDesktop && desktopAttachable(caps.DesktopStream);
1036
- const wantTerminalAttach = attachTerminal && terminalAttachable(caps.Terminal);
1107
+ const wantTerminalAttach = attachTerminal && terminalCanAcquirePty(caps.Terminal);
1037
1108
  const wantFilesAttach = attachFiles && caps.FileSystem.available;
1038
1109
  if (wantDesktopAttach || wantTerminalAttach || wantFilesAttach) {
1039
1110
  try {
1040
1111
  const holder = await client.attachViewer(workspaceId, sessionId, {
1041
- desktop: wantDesktopAttach
1112
+ desktop: wantDesktopAttach,
1113
+ terminal: wantTerminalAttach,
1114
+ files: wantFilesAttach
1042
1115
  });
1043
1116
  if (cancelled) {
1044
1117
  void client.detachViewer(workspaceId, sessionId, holder.viewerId).catch(() => {
1045
1118
  });
1046
1119
  return;
1047
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
+ }
1048
1126
  localViewerId = holder.viewerId;
1049
1127
  viewerIdRef.current = holder.viewerId;
1050
1128
  setViewerId(holder.viewerId);
@@ -1059,19 +1137,20 @@ function useSessionCapabilities(sessionId, options = {}) {
1059
1137
  ...prev.DesktopStream,
1060
1138
  transport: holder.transport ?? prev.DesktopStream.transport,
1061
1139
  client: holder.client ?? prev.DesktopStream.client,
1062
- url: holder.dataPlaneUrl ?? prev.DesktopStream.url,
1063
- token: holder.streamToken ?? prev.DesktopStream.token,
1064
- expiresAt: holder.streamExpiresAt ?? prev.DesktopStream.expiresAt,
1140
+ url: holder.dataPlaneUrl,
1141
+ token: holder.streamToken,
1142
+ expiresAt: holder.streamExpiresAt,
1065
1143
  resolution: holder.resolution ?? prev.DesktopStream.resolution
1066
1144
  } : prev.DesktopStream,
1067
- Terminal: holder.terminalUrl && holder.terminalTransport ? {
1145
+ Terminal: wantTerminalAttach ? {
1068
1146
  ...prev.Terminal,
1069
- transport: holder.terminalTransport,
1147
+ transport: holder.terminalTransport ?? prev.Terminal.transport,
1070
1148
  url: holder.terminalUrl,
1071
- token: holder.terminalToken ?? prev.Terminal.token,
1149
+ token: holder.terminalToken,
1150
+ expiresAt: holder.terminalExpiresAt,
1072
1151
  // A live pty-ws means the box is pty-capable for real.
1073
- ptyCapable: true,
1074
- reason: null
1152
+ ptyCapable: holder.terminalTransport ? true : prev.Terminal.ptyCapable,
1153
+ reason: holder.terminalTransport ? null : prev.Terminal.reason
1075
1154
  } : prev.Terminal
1076
1155
  } : prev
1077
1156
  );
@@ -1090,6 +1169,8 @@ function useSessionCapabilities(sessionId, options = {}) {
1090
1169
  setState("error");
1091
1170
  setError(cause);
1092
1171
  return;
1172
+ } else {
1173
+ throw cause;
1093
1174
  }
1094
1175
  } else {
1095
1176
  throw cause;
@@ -1097,7 +1178,7 @@ function useSessionCapabilities(sessionId, options = {}) {
1097
1178
  }
1098
1179
  }
1099
1180
  const warmUpRequested = wantDesktopAttach || wantTerminalAttach || wantFilesAttach;
1100
- if (caps.liveness === "warm" || caps.liveness === "draining" || localViewerId) {
1181
+ if (sandboxAcceptsLiveIo(caps.liveness) || localViewerId) {
1101
1182
  setState("ready");
1102
1183
  startHeartbeat(caps);
1103
1184
  } else if (warmUpRequested) {
@@ -1180,76 +1261,6 @@ import {
1180
1261
  } from "@opengeni/sdk";
1181
1262
  import { useEffect as useEffect5, useRef as useRef4, useState as useState3 } from "react";
1182
1263
 
1183
- // src/lib/relay-wire.ts
1184
- import { BinaryReader, BinaryWriter } from "@bufbuild/protobuf/wire";
1185
- var STREAM_KIND_DESKTOP = 2;
1186
- var STREAM_ROLE_CLIENT = 2;
1187
- function encodeChannel(message, writer) {
1188
- if (message.channelId !== "") writer.uint32(10).string(message.channelId);
1189
- if (message.workspaceId !== "") writer.uint32(18).string(message.workspaceId);
1190
- if (message.agentId !== "") writer.uint32(26).string(message.agentId);
1191
- if (message.kind !== 0) writer.uint32(32).int32(message.kind);
1192
- if (message.port !== 0) writer.uint32(40).uint32(message.port);
1193
- return writer;
1194
- }
1195
- function encodeStreamOpen(message) {
1196
- const writer = new BinaryWriter();
1197
- encodeChannel(message.channel, writer.uint32(10).fork()).join();
1198
- if (message.token !== "") writer.uint32(18).string(message.token);
1199
- if (message.role !== 0) writer.uint32(24).int32(message.role);
1200
- if (message.resumeFromSeq !== "0") writer.uint32(32).uint64(message.resumeFromSeq);
1201
- return writer.finish();
1202
- }
1203
- function decodeAgentErrorMessage(reader, length) {
1204
- const end = reader.pos + length;
1205
- let message;
1206
- while (reader.pos < end) {
1207
- const tag = reader.uint32();
1208
- if (tag >>> 3 === 2 && tag === 18) {
1209
- message = reader.string();
1210
- continue;
1211
- }
1212
- if ((tag & 7) === 4 || tag === 0) break;
1213
- reader.skip(tag & 7);
1214
- }
1215
- return message !== void 0 ? { message } : {};
1216
- }
1217
- function decodeStreamOpenAck(bytes) {
1218
- const reader = new BinaryReader(bytes);
1219
- const end = reader.len;
1220
- let accepted = false;
1221
- let error;
1222
- while (reader.pos < end) {
1223
- const tag = reader.uint32();
1224
- if (tag >>> 3 === 1 && tag === 8) {
1225
- accepted = reader.bool();
1226
- continue;
1227
- }
1228
- if (tag >>> 3 === 2 && tag === 18) {
1229
- error = decodeAgentErrorMessage(reader, reader.uint32());
1230
- continue;
1231
- }
1232
- if ((tag & 7) === 4 || tag === 0) break;
1233
- reader.skip(tag & 7);
1234
- }
1235
- return error !== void 0 ? { accepted, error } : { accepted };
1236
- }
1237
- function decodeStreamFrame(bytes) {
1238
- const reader = new BinaryReader(bytes);
1239
- const end = reader.len;
1240
- let data = new Uint8Array(0);
1241
- while (reader.pos < end) {
1242
- const tag = reader.uint32();
1243
- if (tag >>> 3 === 3 && tag === 26) {
1244
- data = new Uint8Array(reader.bytes());
1245
- continue;
1246
- }
1247
- if ((tag & 7) === 4 || tag === 0) break;
1248
- reader.skip(tag & 7);
1249
- }
1250
- return { data };
1251
- }
1252
-
1253
1264
  // src/hooks/use-relay-frame-stream.ts
1254
1265
  import { useEffect as useEffect4, useRef as useRef3, useState as useState2 } from "react";
1255
1266
  var TAG_OPEN = 1;
@@ -1580,7 +1591,15 @@ import {
1580
1591
  ttydResizeFrame,
1581
1592
  TtydServerCommand
1582
1593
  } from "@opengeni/sdk";
1583
- 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
+ }
1584
1603
  function decodeFrame(data) {
1585
1604
  if (typeof data === "string") {
1586
1605
  return { command: data.charAt(0), payload: data.slice(1) };
@@ -1590,10 +1609,19 @@ function decodeFrame(data) {
1590
1609
  const payload = bytes.length > 1 ? new TextDecoder().decode(bytes.subarray(1)) : "";
1591
1610
  return { command, payload };
1592
1611
  }
1612
+ function closeSocket(socket) {
1613
+ try {
1614
+ socket?.close();
1615
+ } catch {
1616
+ }
1617
+ }
1593
1618
  function useTerminalStream(options) {
1594
1619
  const { capability, onOutput, onTitle, initialCols, initialRows } = options;
1595
1620
  const [status, setStatus] = useState4("closed");
1596
1621
  const wsRef = useRef5(null);
1622
+ const pendingInputRef = useRef5("");
1623
+ const socketFailedRef = useRef5(false);
1624
+ const socketAuthenticatedRef = useRef5(false);
1597
1625
  const sizeRef = useRef5({
1598
1626
  cols: initialCols ?? 80,
1599
1627
  rows: initialRows ?? 24
@@ -1605,9 +1633,22 @@ function useTerminalStream(options) {
1605
1633
  const transport = capability?.transport ?? null;
1606
1634
  const url = capability?.url ?? null;
1607
1635
  const token = capability?.token ?? null;
1636
+ const expiresAt = capability?.expiresAt ?? null;
1608
1637
  useEffect6(() => {
1609
1638
  if (typeof window === "undefined" || typeof WebSocket === "undefined") return;
1610
- 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 })) {
1611
1652
  setStatus("closed");
1612
1653
  return;
1613
1654
  }
@@ -1617,6 +1658,7 @@ function useTerminalStream(options) {
1617
1658
  try {
1618
1659
  socket = new WebSocket(terminalSocketUrl({ url }), TTYD_SUBPROTOCOL);
1619
1660
  } catch {
1661
+ socketFailedRef.current = true;
1620
1662
  setStatus("error");
1621
1663
  return;
1622
1664
  }
@@ -1627,7 +1669,16 @@ function useTerminalStream(options) {
1627
1669
  try {
1628
1670
  socket.send(ttydAuthFrame({ columns: sizeRef.current.cols, rows: sizeRef.current.rows }));
1629
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;
1630
1677
  } catch {
1678
+ socketFailedRef.current = true;
1679
+ setStatus("error");
1680
+ closeSocket(socket);
1681
+ return;
1631
1682
  }
1632
1683
  setStatus("open");
1633
1684
  };
@@ -1647,39 +1698,55 @@ function useTerminalStream(options) {
1647
1698
  }
1648
1699
  };
1649
1700
  socket.onerror = () => {
1650
- if (!disposed) setStatus("error");
1701
+ if (!disposed) {
1702
+ socketFailedRef.current = true;
1703
+ setStatus("error");
1704
+ }
1651
1705
  };
1652
1706
  socket.onclose = () => {
1653
- if (!disposed) setStatus("closed");
1707
+ if (wsRef.current === socket) wsRef.current = null;
1708
+ socketAuthenticatedRef.current = false;
1709
+ if (!disposed) setStatus(socketFailedRef.current ? "error" : "closed");
1654
1710
  };
1655
1711
  return () => {
1656
1712
  disposed = true;
1657
1713
  wsRef.current = null;
1714
+ socketAuthenticatedRef.current = false;
1658
1715
  socket.onopen = null;
1659
1716
  socket.onmessage = null;
1660
1717
  socket.onerror = null;
1661
1718
  socket.onclose = null;
1662
- try {
1663
- socket.close();
1664
- } catch {
1665
- }
1719
+ closeSocket(socket);
1666
1720
  };
1667
- }, [transport, url, token]);
1668
- return useMemo2(() => {
1721
+ }, [transport, url, token, expiresAt]);
1722
+ return useMemo3(() => {
1669
1723
  const write = (data) => {
1670
1724
  const ws = wsRef.current;
1671
- if (ws && ws.readyState === WebSocket.OPEN) {
1725
+ if (ws && ws.readyState === WebSocket.OPEN && socketAuthenticatedRef.current) {
1672
1726
  try {
1673
1727
  ws.send(ttydInputFrame(data));
1674
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;
1675
1741
  }
1742
+ pendingInputRef.current += data;
1676
1743
  }
1677
1744
  };
1678
1745
  const resize = (cols, rows) => {
1679
1746
  if (cols <= 0 || rows <= 0) return;
1680
1747
  sizeRef.current = { cols, rows };
1681
1748
  const ws = wsRef.current;
1682
- if (ws && ws.readyState === WebSocket.OPEN) {
1749
+ if (ws && ws.readyState === WebSocket.OPEN && socketAuthenticatedRef.current) {
1683
1750
  try {
1684
1751
  ws.send(ttydResizeFrame(cols, rows));
1685
1752
  } catch {
@@ -1689,20 +1756,19 @@ function useTerminalStream(options) {
1689
1756
  const disconnect = () => {
1690
1757
  const ws = wsRef.current;
1691
1758
  wsRef.current = null;
1692
- if (ws) {
1693
- try {
1694
- ws.close();
1695
- } catch {
1696
- }
1697
- }
1759
+ pendingInputRef.current = "";
1760
+ socketFailedRef.current = false;
1761
+ socketAuthenticatedRef.current = false;
1762
+ setStatus("closed");
1763
+ closeSocket(ws);
1698
1764
  };
1699
1765
  return { connected: status === "open", status, write, resize, disconnect };
1700
- }, [status]);
1766
+ }, [status, transport]);
1701
1767
  }
1702
1768
 
1703
1769
  // src/hooks/use-sandbox-terminal.ts
1704
1770
  import { OpenGeniApiError as OpenGeniApiError2 } from "@opengeni/sdk";
1705
- 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";
1706
1772
  function useSandboxTerminal(sessionId, options) {
1707
1773
  const { client, workspaceId } = useOpenGeni(options);
1708
1774
  const includeAgentFirehose = options.includeAgentFirehose ?? true;
@@ -1722,7 +1788,7 @@ function useSandboxTerminal(sessionId, options) {
1722
1788
  setOpenError(null);
1723
1789
  }, [identityKey]);
1724
1790
  const openInFlight = useRef6(false);
1725
- const boxWarm = liveness === void 0 || liveness === "warm" || liveness === "draining";
1791
+ const boxWarm = liveness === void 0 || sandboxAcceptsLiveIo(liveness);
1726
1792
  useEffect7(() => {
1727
1793
  if (!interactive || !sessionId || ptyFilter || !boxWarm) {
1728
1794
  setOpenError(null);
@@ -1753,7 +1819,7 @@ function useSandboxTerminal(sessionId, options) {
1753
1819
  });
1754
1820
  };
1755
1821
  }, [interactive, client, workspaceId, sessionId, ptyFilter, boxWarm, reopenNonce]);
1756
- const { chunks, openPty, supportsInput } = useMemo3(() => {
1822
+ const { chunks, openPty, supportsInput } = useMemo4(() => {
1757
1823
  const out = [];
1758
1824
  const open = /* @__PURE__ */ new Map();
1759
1825
  let lastOpened = null;
@@ -1806,7 +1872,7 @@ function useSandboxTerminal(sessionId, options) {
1806
1872
  const identityMatches = stateIdentity === identityKey;
1807
1873
  const activePtyId = identityMatches ? openedPtyId ?? openPty : null;
1808
1874
  const canWrite = openedPtyId !== null || supportsInput;
1809
- const write = useMemo3(() => {
1875
+ const write = useMemo4(() => {
1810
1876
  if (!activePtyId || !canWrite || !sessionId) return null;
1811
1877
  const writeIdentity = identityKey;
1812
1878
  return (data) => {
@@ -1834,7 +1900,7 @@ function useSandboxTerminal(sessionId, options) {
1834
1900
  }
1835
1901
 
1836
1902
  // src/hooks/use-sandbox-files.ts
1837
- import { useCallback as useCallback12, useEffect as useEffect8, useMemo as useMemo4, 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";
1838
1904
  var CapturedFileUnavailableError = class extends Error {
1839
1905
  constructor(path, reason) {
1840
1906
  super(
@@ -2068,9 +2134,13 @@ function useSandboxFiles(sessionId, options = {}) {
2068
2134
  const repoPathsKey = [
2069
2135
  ...new Set((options.repoPaths?.length ? options.repoPaths : [rootPath]).map(normalizeRoot))
2070
2136
  ].join("\0");
2071
- const repoPaths = useMemo4(() => repoPathsKey.split("\0"), [repoPathsKey]);
2137
+ const repoPaths = useMemo5(() => repoPathsKey.split("\0"), [repoPathsKey]);
2072
2138
  const capture = options.capture ?? null;
2073
- const isLive2 = options.liveness === "warm" || options.liveness === "draining";
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;
2074
2144
  const identityKey = `${workspaceId}\0${sessionId ?? ""}\0${rootPath}\0${repoPathsKey}`;
2075
2145
  const [tree, setTree] = useState6([]);
2076
2146
  const treeRef = useRef7([]);
@@ -2087,6 +2157,7 @@ function useSandboxFiles(sessionId, options = {}) {
2087
2157
  const refreshGenerationRef = useRef7(0);
2088
2158
  const refreshAbortRef = useRef7(null);
2089
2159
  const identityAbortRef = useRef7(new AbortController());
2160
+ const eventReadAbortRef = useRef7(new AbortController());
2090
2161
  const identityGenerationRef = useRef7(0);
2091
2162
  const lastSeqRef = useRef7(0);
2092
2163
  const pendingParentsRef = useRef7(/* @__PURE__ */ new Set());
@@ -2345,18 +2416,14 @@ function useSandboxFiles(sessionId, options = {}) {
2345
2416
  [client, workspaceId, sessionId, capture, source]
2346
2417
  );
2347
2418
  const reconcilePath = useCallback12(
2348
- async (path) => {
2419
+ async (path, requestSignal) => {
2349
2420
  if (!sessionId) return;
2350
2421
  const identityGeneration = identityGenerationRef.current;
2351
2422
  const identitySignal = identityAbortRef.current.signal;
2423
+ const signal = requestSignal ? AbortSignal.any([identitySignal, requestSignal]) : identitySignal;
2352
2424
  if (!parentIsLoaded(treeRef.current, path)) return;
2353
2425
  try {
2354
- const listed = await client.fsList(
2355
- workspaceId,
2356
- sessionId,
2357
- { path, depth: 1 },
2358
- { signal: identitySignal }
2359
- );
2426
+ const listed = await client.fsList(workspaceId, sessionId, { path, depth: 1 }, { signal });
2360
2427
  if (identityGenerationRef.current !== identityGeneration) return;
2361
2428
  const children = (listed.root.children ?? []).map((node) => fsNodeToTree(node));
2362
2429
  if (path === "") setTree((prev) => applyStatus(mergeRootChildren(prev, children)));
@@ -2532,6 +2599,8 @@ function useSandboxFiles(sessionId, options = {}) {
2532
2599
  refreshAbortRef.current?.abort();
2533
2600
  refreshAbortRef.current = null;
2534
2601
  refreshGenerationRef.current += 1;
2602
+ eventReadAbortRef.current.abort();
2603
+ eventReadAbortRef.current = new AbortController();
2535
2604
  const identityChanged = previousIdentityRef.current !== identityKey;
2536
2605
  previousIdentityRef.current = identityKey;
2537
2606
  if (identityChanged || !enabled) {
@@ -2575,55 +2644,76 @@ function useSandboxFiles(sessionId, options = {}) {
2575
2644
  }
2576
2645
  return;
2577
2646
  }
2578
- if (isLive2 || !currentCapture) {
2647
+ if (isLive2 || !hasLiveIoFence && !currentCapture) {
2579
2648
  void refresh();
2580
2649
  }
2581
2650
  return () => {
2582
2651
  refreshAbortRef.current?.abort();
2583
2652
  refreshAbortRef.current = null;
2584
2653
  refreshGenerationRef.current += 1;
2654
+ eventReadAbortRef.current.abort();
2585
2655
  };
2586
- }, [enabled, active, isLive2, captureRevision, identityKey, refresh, seedFromCapture]);
2587
- const refreshGitOverlay = useCallback12(async () => {
2588
- if (!sessionId) return;
2589
- const identityGeneration = identityGenerationRef.current;
2590
- const identitySignal = identityAbortRef.current.signal;
2591
- setGitLoading(true);
2592
- try {
2593
- const batch = await client.gitReadBatch(
2594
- workspaceId,
2595
- sessionId,
2596
- { requests: repoPaths.map((root) => ({ status: { path: root } })) },
2597
- { signal: identitySignal }
2598
- );
2599
- const statuses = batch.results.map((result, index) => ({
2600
- root: repoPaths[index] ?? "",
2601
- status: result.status
2602
- }));
2603
- if (identityGenerationRef.current !== identityGeneration) return;
2604
- const overlay = /* @__PURE__ */ new Map();
2605
- for (const { root, status } of statuses) {
2606
- for (const file of status.files) {
2607
- const code = file.worktree ?? file.index;
2608
- const mapped = code ? GIT_STATUS_TO_TREE[code] : void 0;
2609
- if (mapped) overlay.set(qualifyStatusPath(root, 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
+ }
2610
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);
2611
2699
  }
2612
- statusRef.current = overlay;
2613
- setGitSummary(summarizeStatuses(statuses));
2614
- setTree((prev) => applyStatus(prev));
2615
- } catch {
2616
- } finally {
2617
- if (identityGenerationRef.current === identityGeneration) setGitLoading(false);
2618
- }
2619
- }, [client, workspaceId, sessionId, repoPaths, applyStatus]);
2700
+ },
2701
+ [client, workspaceId, sessionId, repoPaths, applyStatus]
2702
+ );
2620
2703
  const events = options.events;
2621
2704
  useEffect8(() => {
2622
2705
  if (!enabled || !events) return;
2623
- if (!active) {
2706
+ if (!active || !acceptsEventReads) {
2624
2707
  for (const event of events) {
2625
2708
  if (event.sequence > lastSeqRef.current) lastSeqRef.current = event.sequence;
2626
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;
2627
2717
  return;
2628
2718
  }
2629
2719
  let sawNew = false;
@@ -2672,6 +2762,12 @@ function useSandboxFiles(sessionId, options = {}) {
2672
2762
  debounceRef.current = setTimeout(() => {
2673
2763
  debounceRef.current = null;
2674
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
+ }
2675
2771
  if (sawCommandDelta) {
2676
2772
  lastCommandRefreshAtRef.current = Date.now();
2677
2773
  setContentRevision((revision) => revision + 1);
@@ -2682,17 +2778,18 @@ function useSandboxFiles(sessionId, options = {}) {
2682
2778
  pendingGitRef.current = false;
2683
2779
  const wantAgentTool = pendingAgentToolRef.current;
2684
2780
  pendingAgentToolRef.current = false;
2781
+ const eventReadSignal = eventReadAbortRef.current.signal;
2685
2782
  void (async () => {
2686
2783
  const paths = wantAgentTool ? loadedDirectoryPaths(treeRef.current) : [...parents];
2687
2784
  await Promise.all([
2688
- wantGit ? refreshGitOverlay() : Promise.resolve(),
2689
- Promise.all(paths.map((path) => reconcilePath(path)))
2785
+ wantGit ? refreshGitOverlay(eventReadSignal) : Promise.resolve(),
2786
+ Promise.all(paths.map((path) => reconcilePath(path, eventReadSignal)))
2690
2787
  ]);
2691
2788
  })();
2692
2789
  }, delay);
2693
- }, [enabled, active, events, reconcilePath, refreshGitOverlay]);
2790
+ }, [enabled, active, acceptsEventReads, events, reconcilePath, refreshGitOverlay]);
2694
2791
  useEffect8(() => {
2695
- if (active) return;
2792
+ if (active && acceptsEventReads) return;
2696
2793
  if (debounceRef.current) {
2697
2794
  clearTimeout(debounceRef.current);
2698
2795
  debounceRef.current = null;
@@ -2700,11 +2797,12 @@ function useSandboxFiles(sessionId, options = {}) {
2700
2797
  pendingParentsRef.current = /* @__PURE__ */ new Set();
2701
2798
  pendingGitRef.current = false;
2702
2799
  pendingAgentToolRef.current = false;
2703
- }, [active]);
2800
+ }, [active, acceptsEventReads]);
2704
2801
  useEffect8(
2705
2802
  () => () => {
2706
2803
  refreshAbortRef.current?.abort();
2707
2804
  identityAbortRef.current.abort();
2805
+ eventReadAbortRef.current.abort();
2708
2806
  identityGenerationRef.current += 1;
2709
2807
  if (debounceRef.current) clearTimeout(debounceRef.current);
2710
2808
  },
@@ -2733,7 +2831,7 @@ function useSandboxFiles(sessionId, options = {}) {
2733
2831
  }
2734
2832
 
2735
2833
  // src/hooks/use-sandbox-git.ts
2736
- import { useCallback as useCallback13, useEffect as useEffect9, useMemo as useMemo5, useRef as useRef8, useState as useState7 } from "react";
2834
+ import { useCallback as useCallback13, useEffect as useEffect9, useMemo as useMemo6, useRef as useRef8, useState as useState7 } from "react";
2737
2835
  function normalizeRoot2(root) {
2738
2836
  const normalized = root.replaceAll("\\", "/").replace(/^\.\/+/, "").replace(/\/+$/, "");
2739
2837
  return normalized === "." ? "" : normalized;
@@ -2754,6 +2852,7 @@ function qualifyDiff(repoRoot, file) {
2754
2852
  function uniqueRoots(roots) {
2755
2853
  return [...new Set(roots.map(normalizeRoot2))].sort((a, b) => a.localeCompare(b));
2756
2854
  }
2855
+ var GIT_READ_BATCH_REQUEST_LIMIT = 32;
2757
2856
  function repoForPath(manifest, repoPath) {
2758
2857
  const want = normalizeRoot2(repoPath);
2759
2858
  const exact = manifest.repos.find((r) => normalizeRoot2(r.root) === want);
@@ -2798,11 +2897,15 @@ function useSandboxGit(sessionId, options = {}) {
2798
2897
  const repoPathsKey = uniqueRoots(
2799
2898
  options.repoPaths && options.repoPaths.length > 0 ? options.repoPaths : [repoPath]
2800
2899
  ).join("\0");
2801
- const repoPaths = useMemo5(() => repoPathsKey.split("\0"), [repoPathsKey]);
2900
+ const repoPaths = useMemo6(() => repoPathsKey.split("\0"), [repoPathsKey]);
2802
2901
  const comparison = options.comparison ?? (options.staged ? "staged" : "working");
2803
2902
  const hasAdvertisedRepoPaths = (options.repoPaths?.length ?? 0) > 0;
2804
2903
  const capture = comparison === "staged" ? null : options.capture ?? null;
2805
- const isLive2 = options.liveness === "warm" || options.liveness === "draining";
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;
2806
2909
  const identityKey = `${workspaceId}\0${sessionId ?? ""}\0${repoPathsKey}\0${workspaceWide}\0${comparison}`;
2807
2910
  const [diff, setDiff] = useState7([]);
2808
2911
  const [branch, setBranch] = useState7(null);
@@ -2837,29 +2940,42 @@ function useSandboxGit(sessionId, options = {}) {
2837
2940
  },
2838
2941
  { signal: refreshAbort.signal }
2839
2942
  );
2840
- const prefetched = hasAdvertisedRepoPaths ? await client.gitReadBatch(
2841
- workspaceId,
2842
- sessionId,
2843
- {
2844
- requests: repoPaths.map((root) => ({
2845
- status: { path: root },
2846
- diff: {
2847
- path: root,
2848
- ...comparison === "branch" ? { fromRef: "origin/HEAD", includeUntracked: true } : comparison === "working" ? { fromRef: "HEAD", includeUntracked: true } : { staged: true, includeUntracked: false }
2849
- }
2850
- }))
2851
- },
2852
- { signal: refreshAbort.signal }
2853
- ).then(
2854
- (batch) => batch.results.map((item, index) => ({
2855
- root: repoPaths[index] ?? "",
2856
- status: item.status,
2857
- result: item.diff ?? {
2858
- files: [],
2859
- revision: item.status.revision
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
+ );
2860
2965
  }
2861
- }))
2862
- ) : null;
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;
2863
2979
  const statuses = prefetched ?? await Promise.all(
2864
2980
  repoPaths.map(async (root) => ({
2865
2981
  root,
@@ -2991,7 +3107,7 @@ function useSandboxGit(sessionId, options = {}) {
2991
3107
  if (!active) setLoading(false);
2992
3108
  return;
2993
3109
  }
2994
- if (isLive2 || !currentCapture) {
3110
+ if (isLive2 || !hasLiveIoFence && !currentCapture) {
2995
3111
  void refresh();
2996
3112
  }
2997
3113
  return () => {
@@ -2999,10 +3115,29 @@ function useSandboxGit(sessionId, options = {}) {
2999
3115
  refreshAbortRef.current = null;
3000
3116
  refreshGenerationRef.current += 1;
3001
3117
  };
3002
- }, [enabled, active, isLive2, captureRevision, identityKey, refresh, seedFromCapture]);
3118
+ }, [
3119
+ enabled,
3120
+ active,
3121
+ isLive2,
3122
+ hasLiveIoFence,
3123
+ captureRevision,
3124
+ identityKey,
3125
+ refresh,
3126
+ seedFromCapture
3127
+ ]);
3003
3128
  const events = options.events;
3004
3129
  useEffect9(() => {
3005
- if (!enabled || !active || !events) {
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
+ }
3006
3141
  if (commandRefreshTimerRef.current) {
3007
3142
  clearTimeout(commandRefreshTimerRef.current);
3008
3143
  commandRefreshTimerRef.current = null;
@@ -3027,15 +3162,15 @@ function useSandboxGit(sessionId, options = {}) {
3027
3162
  if (immediate) {
3028
3163
  if (commandRefreshTimerRef.current) clearTimeout(commandRefreshTimerRef.current);
3029
3164
  commandRefreshTimerRef.current = null;
3030
- void refresh();
3165
+ if (acceptsEventReadsRef.current) void refresh();
3031
3166
  } else if (commandDelta && !commandRefreshTimerRef.current) {
3032
3167
  commandRefreshTimerRef.current = setTimeout(() => {
3033
3168
  commandRefreshTimerRef.current = null;
3034
- void refresh();
3169
+ if (acceptsEventReadsRef.current) void refresh();
3035
3170
  }, 1e3);
3036
3171
  }
3037
3172
  }
3038
- }, [enabled, active, events, refresh]);
3173
+ }, [enabled, active, acceptsEventReads, events, refresh]);
3039
3174
  useEffect9(
3040
3175
  () => () => {
3041
3176
  if (commandRefreshTimerRef.current) clearTimeout(commandRefreshTimerRef.current);
@@ -3258,7 +3393,7 @@ function useWorkspaceCapture(sessionId, options = {}) {
3258
3393
  // src/hooks/use-workspace-edit.ts
3259
3394
  import { useCallback as useCallback15, useEffect as useEffect11, useRef as useRef10, useState as useState9 } from "react";
3260
3395
  function isLive(liveness) {
3261
- return liveness === "warm" || liveness === "draining";
3396
+ return sandboxAcceptsLiveIo(liveness);
3262
3397
  }
3263
3398
  function useWorkspaceEdit(sessionId, options = {}) {
3264
3399
  const { client, workspaceId } = useOpenGeni(options);
@@ -3415,7 +3550,7 @@ function useWorkspaceEdit(sessionId, options = {}) {
3415
3550
  }
3416
3551
 
3417
3552
  // src/hooks/use-machine-chip.ts
3418
- import { useMemo as useMemo6 } from "react";
3553
+ import { useMemo as useMemo7 } from "react";
3419
3554
  function formatAsOf(capturedAt, now) {
3420
3555
  const then = Date.parse(capturedAt);
3421
3556
  if (Number.isNaN(then)) return "recently";
@@ -3432,7 +3567,7 @@ function deriveMachineChip(input) {
3432
3567
  const now = input.now ?? Date.now();
3433
3568
  const asOf = input.capturedAt ?? null;
3434
3569
  const offlineLabel = asOf ? `Offline \u2014 as of ${formatAsOf(asOf, now)}` : "Offline";
3435
- if (input.liveness === "warm" || input.liveness === "draining") {
3570
+ if (sandboxAcceptsLiveIo(input.liveness)) {
3436
3571
  return { state: "live", label: "Live", asOf: null };
3437
3572
  }
3438
3573
  const selfhostedOffline = input.activeIsSelfhosted === true && input.activeMachineState === "offline";
@@ -3452,7 +3587,7 @@ function useMachineChip(input) {
3452
3587
  capturedAt,
3453
3588
  now
3454
3589
  } = input;
3455
- return useMemo6(
3590
+ return useMemo7(
3456
3591
  () => deriveMachineChip({
3457
3592
  liveness,
3458
3593
  capabilitiesState,
@@ -3679,6 +3814,17 @@ function ChatComposer({
3679
3814
  /* @__PURE__ */ jsx2(RestoredResources, {}),
3680
3815
  /* @__PURE__ */ jsx2(Attachments, {}),
3681
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,
3682
3828
  /* @__PURE__ */ jsx2(Input, { placeholder, autoFocus }),
3683
3829
  controller.confirmState ? /* @__PURE__ */ jsx2(Confirmation, {}) : /* @__PURE__ */ jsx2(
3684
3830
  Footer,
@@ -3964,6 +4110,19 @@ async function attachRenderer(prefer, loaders, onTier) {
3964
4110
 
3965
4111
  // src/components/sandbox-terminal.tsx
3966
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
+ }
3967
4126
  function clearTerminalBootStatus(term) {
3968
4127
  term.write("\r\x1B[2K\x1B[2J\x1B[H");
3969
4128
  }
@@ -3999,31 +4158,49 @@ function SandboxTerminal({
3999
4158
  const wroteFirehoseRef = useRef12(false);
4000
4159
  const bootActiveRef = useRef12(false);
4001
4160
  const [ready, setReady] = useState11(false);
4161
+ const [inputReady, setInputReady] = useState11(false);
4002
4162
  const [activated, setActivated] = useState11(false);
4003
- const ptyWs = terminalCapability?.transport === "pty-ws" && Boolean(terminalCapability?.url);
4004
- const ptyOutputQueueRef = useRef12([]);
4163
+ const ptyDescriptor = terminalCapability?.transport === "pty-ws";
4164
+ const ptyAttachable = terminalCanAcquirePty(terminalCapability);
4005
4165
  const {
4006
4166
  status: ptyStatus,
4007
4167
  write: ptyWrite,
4008
4168
  resize: resizePty,
4009
4169
  connected: ptyConnected
4010
4170
  } = useTerminalStream({
4011
- capability: ptyWs ? {
4012
- transport: terminalCapability.transport,
4013
- url: terminalCapability.url,
4014
- 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
4015
4179
  } : null,
4016
4180
  onOutput: (data) => {
4017
- const term = termRef.current;
4018
- if (term) term.write(data);
4019
- else ptyOutputQueueRef.current.push(data);
4181
+ termRef.current?.write(data);
4020
4182
  }
4021
4183
  });
4022
- const ptyMode = ptyWs && ptyStatus !== "closed";
4023
- const interactive = !readOnly && (ptyMode ? ptyConnected : result.write !== null);
4024
- const terminalModeLabel = interactive ? null : readOnly ? "read-only" : ptyMode && ptyStatus === "connecting" ? "connecting" : ptyMode && ptyStatus === "error" ? "connection error" : "output only";
4025
- 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);
4026
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
+ });
4027
4204
  function handleActivate() {
4028
4205
  if (!activated) setActivated(true);
4029
4206
  onActivate?.();
@@ -4110,10 +4287,6 @@ function SandboxTerminal({
4110
4287
  requestAnimationFrame(() => {
4111
4288
  if (disposed) return;
4112
4289
  settle();
4113
- if (ptyOutputQueueRef.current.length > 0) {
4114
- for (const data of ptyOutputQueueRef.current) term.write(data);
4115
- ptyOutputQueueRef.current = [];
4116
- }
4117
4290
  el.setAttribute("data-og-term-ready", "true");
4118
4291
  if (typeof globalThis.__OG_TERMINAL_DEBUG__ === "function") {
4119
4292
  globalThis.__OG_TERMINAL_DEBUG__({
@@ -4185,12 +4358,19 @@ function SandboxTerminal({
4185
4358
  }, [ready, result.chunks, ptyMode]);
4186
4359
  useEffect13(() => {
4187
4360
  const term = termRef.current;
4188
- if (!ready || !term || !interactive) return;
4189
- const sink = ptyMode ? ptyWrite : result.write;
4190
- if (!sink) return;
4191
- const sub = term.onData((data) => sink(data));
4192
- return () => sub.dispose();
4193
- }, [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]);
4194
4374
  useEffect13(() => {
4195
4375
  const term = termRef.current;
4196
4376
  if (!ready || !term || !ptyMode) return;
@@ -4273,7 +4453,11 @@ function SandboxTerminal({
4273
4453
  ref: containerRef,
4274
4454
  className: "h-full w-full",
4275
4455
  style: { visibility: ready ? "visible" : "hidden" },
4276
- "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"
4277
4461
  }
4278
4462
  )
4279
4463
  ]
@@ -4343,7 +4527,7 @@ import {
4343
4527
  useEffect as useEffect15,
4344
4528
  useId as useId2,
4345
4529
  useLayoutEffect,
4346
- useMemo as useMemo8,
4530
+ useMemo as useMemo9,
4347
4531
  useRef as useRef13,
4348
4532
  useState as useState12
4349
4533
  } from "react";
@@ -4351,11 +4535,11 @@ import { AlertDialog } from "radix-ui";
4351
4535
  import { VList } from "virtua";
4352
4536
 
4353
4537
  // src/lib/use-unicode-fonts.ts
4354
- import { useEffect as useEffect14, useMemo as useMemo7 } from "react";
4538
+ import { useEffect as useEffect14, useMemo as useMemo8 } from "react";
4355
4539
  var japaneseScript = /[\u3040-\u30ff\u3400-\u4dbf\u4e00-\u9fff\uf900-\ufaff]/u;
4356
4540
  var arabicScript = /[\u0600-\u06ff\u0750-\u077f\u0870-\u08ff\ufb50-\ufdff\ufe70-\ufefc]/u;
4357
4541
  function useUnicodeFallbackFonts(values) {
4358
- const content = useMemo7(() => collectUnicodeContent(values), [values]);
4542
+ const content = useMemo8(() => collectUnicodeContent(values), [values]);
4359
4543
  useEffect14(() => {
4360
4544
  if (content.japanese) {
4361
4545
  void import("./noto-sans-jp-loader.generated-HFGLYBR3.js").then(({ loadNotoSansJp }) => loadNotoSansJp(content.japanese)).catch(() => void 0);
@@ -4502,7 +4686,7 @@ function FileBrowser({
4502
4686
  [expanded, expand]
4503
4687
  );
4504
4688
  const cold = result.source !== "live";
4505
- const renderItems = useMemo8(() => {
4689
+ const renderItems = useMemo9(() => {
4506
4690
  const items = [];
4507
4691
  if (draftCreate && draftCreate.parent === "") {
4508
4692
  items.push({ type: "create", parent: "", kind: draftCreate.kind, depth: 0 });
@@ -4533,22 +4717,22 @@ function FileBrowser({
4533
4717
  walk(result.tree, 0);
4534
4718
  return items;
4535
4719
  }, [result.tree, expanded, draftCreate, loadingPaths, result.expandingPaths, cold]);
4536
- const unicodeFontPaths = useMemo8(
4720
+ const unicodeFontPaths = useMemo9(
4537
4721
  () => renderItems.flatMap((item) => item.type === "node" ? [item.node.path] : []),
4538
4722
  [renderItems]
4539
4723
  );
4540
4724
  useUnicodeFallbackFonts(unicodeFontPaths);
4541
- const flatRows = useMemo8(
4725
+ const flatRows = useMemo9(
4542
4726
  () => renderItems.flatMap(
4543
4727
  (item) => item.type === "node" ? [{ node: item.node, depth: item.depth }] : []
4544
4728
  ),
4545
4729
  [renderItems]
4546
4730
  );
4547
- const rowIndexByPath = useMemo8(
4731
+ const rowIndexByPath = useMemo9(
4548
4732
  () => new Map(flatRows.map((row, index) => [row.node.path, index])),
4549
4733
  [flatRows]
4550
4734
  );
4551
- const treePositionByPath = useMemo8(() => {
4735
+ const treePositionByPath = useMemo9(() => {
4552
4736
  const positions = /* @__PURE__ */ new Map();
4553
4737
  const visit = (nodes) => {
4554
4738
  nodes.forEach((node, index) => {
@@ -5489,18 +5673,19 @@ function ContextMenu({
5489
5673
  // src/components/workbench-changes.tsx
5490
5674
  import { ArrowUpRightIcon, FileWarningIcon, HistoryIcon } from "lucide-react";
5491
5675
  import {
5676
+ Component,
5492
5677
  useCallback as useCallback18,
5493
5678
  useEffect as useEffect17,
5494
5679
  useId as useId3,
5495
5680
  useLayoutEffect as useLayoutEffect2,
5496
- useMemo as useMemo10,
5681
+ useMemo as useMemo11,
5497
5682
  useRef as useRef15,
5498
5683
  useState as useState14
5499
5684
  } from "react";
5500
5685
  import { VList as VList2 } from "virtua";
5501
5686
 
5502
5687
  // src/hooks/use-windowed-sections.ts
5503
- import { useCallback as useCallback17, useEffect as useEffect16, useMemo as useMemo9, 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";
5504
5689
  function computeWindowRange(heights, scrollTop, viewport, overscan) {
5505
5690
  const n = heights.length;
5506
5691
  if (n === 0) return { start: 0, end: 0 };
@@ -5583,7 +5768,7 @@ function useWindowedSections(opts) {
5583
5768
  return next;
5584
5769
  });
5585
5770
  }, []);
5586
- const offsets = useMemo9(() => {
5771
+ const offsets = useMemo10(() => {
5587
5772
  const out = new Array(count + 1);
5588
5773
  out[0] = 0;
5589
5774
  for (let i = 0; i < count; i++) out[i + 1] = (out[i] ?? 0) + Math.max(heights[i] ?? 0, 0);
@@ -5648,6 +5833,101 @@ function compactFileLabel(file) {
5648
5833
  const parent = slash >= 0 ? file.path.slice(0, slash + 1) : "";
5649
5834
  return `${STATUS_LETTER[file.status]} \xB7 ${basename}${parent ? ` \u2014 ${parent}` : ""}`;
5650
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
+ };
5651
5931
  function buildRail(files) {
5652
5932
  const roots = new Set(
5653
5933
  files.flatMap((file) => file.repoRoot === void 0 ? [] : [file.repoRoot])
@@ -5706,12 +5986,12 @@ function WorkbenchChanges({
5706
5986
  const [layout, setLayout] = useState14("unified");
5707
5987
  const [activePath, setActivePath] = useState14(null);
5708
5988
  const pickerId = useId3();
5709
- const unicodeFontPaths = useMemo10(
5989
+ const unicodeFontPaths = useMemo11(
5710
5990
  () => diff.flatMap((file) => file.oldPath ? [file.path, file.oldPath] : [file.path]),
5711
5991
  [diff]
5712
5992
  );
5713
5993
  useUnicodeFallbackFonts(unicodeFontPaths);
5714
- const { orderedFiles, rows, grouped } = useMemo10(() => buildRail(diff), [diff]);
5994
+ const { orderedFiles, rows, grouped } = useMemo11(() => buildRail(diff), [diff]);
5715
5995
  const selectedIndex = orderedFiles.findIndex((file) => file.path === activePath);
5716
5996
  const activeIndex = selectedIndex >= 0 ? selectedIndex : 0;
5717
5997
  useChangesLayoutEffect(() => {
@@ -5741,8 +6021,8 @@ function WorkbenchChanges({
5741
6021
  return () => query.removeListener(update);
5742
6022
  }
5743
6023
  }, []);
5744
- const additions = useMemo10(() => diff.reduce((sum, f) => sum + f.additions, 0), [diff]);
5745
- const deletions = useMemo10(() => 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]);
5746
6026
  const estimateHeight = useCallback18(
5747
6027
  (index) => {
5748
6028
  const file = orderedFiles[index];
@@ -5860,22 +6140,33 @@ function WorkbenchChanges({
5860
6140
  }
5861
6141
  )
5862
6142
  ] }),
5863
- /* @__PURE__ */ jsx6("div", { className: "min-h-0 min-w-0 flex-1 overflow-auto", "data-opengeni-changes-pane": true, children: activeFile ? /* @__PURE__ */ jsx6(
5864
- DiffSection,
6143
+ /* @__PURE__ */ jsx6(
6144
+ "div",
5865
6145
  {
5866
- file: activeFile,
5867
- layout: "unified",
5868
- themeType: resolvedTheme,
5869
- ...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
5870
6160
  }
5871
- ) : null })
6161
+ )
5872
6162
  ] }) : /* @__PURE__ */ jsxs6("div", { className: "flex min-h-0 flex-1", children: [
5873
- /* @__PURE__ */ jsx6("div", { className: "w-[clamp(12rem,28%,15rem)] shrink-0 border-r border-og-border bg-og-surface-1/35", children: /* @__PURE__ */ jsx6(
5874
- VList2,
6163
+ /* @__PURE__ */ jsx6(
6164
+ ChangesRailVList,
5875
6165
  {
5876
- className: "h-full",
5877
- itemSize: coarsePointer ? 44 : 28,
5878
- 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",
5879
6170
  children: rows.map(
5880
6171
  (row) => row.kind === "group" ? /* @__PURE__ */ jsxs6(
5881
6172
  "div",
@@ -5900,13 +6191,16 @@ function WorkbenchChanges({
5900
6191
  )
5901
6192
  )
5902
6193
  }
5903
- ) }),
6194
+ ),
5904
6195
  /* @__PURE__ */ jsx6(
5905
6196
  "div",
5906
6197
  {
5907
6198
  ref: windowed.scrollRef,
5908
- 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",
5909
6201
  "data-opengeni-changes-pane": true,
6202
+ role: "region",
6203
+ tabIndex: 0,
5910
6204
  children: /* @__PURE__ */ jsx6("div", { style: { position: "relative", height: windowed.totalHeight }, children: orderedFiles.map((file, index) => {
5911
6205
  if (index < windowed.range.start || index >= windowed.range.end) return null;
5912
6206
  return /* @__PURE__ */ jsx6(
@@ -5975,6 +6269,7 @@ function RailFileRow({
5975
6269
  onClick,
5976
6270
  title: file.path,
5977
6271
  "data-rail-file": true,
6272
+ "data-rail-file-path": file.path,
5978
6273
  className: cn(
5979
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",
5980
6275
  grouped && "pl-3",
@@ -6212,7 +6507,7 @@ import { Loader2Icon, SaveIcon } from "lucide-react";
6212
6507
  import {
6213
6508
  useCallback as useCallback19,
6214
6509
  useEffect as useEffect19,
6215
- useMemo as useMemo11,
6510
+ useMemo as useMemo12,
6216
6511
  useRef as useRef16,
6217
6512
  useState as useState16
6218
6513
  } from "react";
@@ -6260,6 +6555,19 @@ function languageForPath(path) {
6260
6555
  return null;
6261
6556
  }
6262
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
+ }
6263
6571
  function CodeEditor({
6264
6572
  path,
6265
6573
  initialContents,
@@ -6275,28 +6583,30 @@ function CodeEditor({
6275
6583
  }) {
6276
6584
  const [failed, setFailed] = useState16(false);
6277
6585
  const [bundle, setBundle] = useState16(null);
6278
- const [value, setValue] = useState16(initialContents);
6279
- const [baseline, setBaseline] = useState16(initialContents);
6586
+ const [buffer, setBuffer] = useState16(() => ({
6587
+ path,
6588
+ value: initialContents,
6589
+ baseline: initialContents
6590
+ }));
6591
+ const { value, baseline } = buffer;
6280
6592
  const [saving, setSaving] = useState16(false);
6281
6593
  const [saveError, setSaveError] = useState16(null);
6282
6594
  const [savedTick, setSavedTick] = useState16(false);
6595
+ const saveInFlightRef = useRef16(false);
6283
6596
  const dirty = value !== baseline;
6284
6597
  const saveConflict = saveError !== null && saveError.code === "file_write_conflict";
6285
6598
  const editIntentFiredRef = useRef16(false);
6286
- const lastSeed = useRef16({ path, contents: initialContents });
6287
6599
  useEffect19(() => {
6288
- const seedChanged = lastSeed.current.path !== path || lastSeed.current.contents !== initialContents;
6289
- if (!seedChanged) return;
6290
- lastSeed.current = { path, contents: initialContents };
6600
+ setBuffer((current) => reconcileEditorBuffer(current, { path, contents: initialContents }));
6601
+ }, [path, initialContents]);
6602
+ useEffect19(() => {
6291
6603
  editIntentFiredRef.current = false;
6292
- setValue(initialContents);
6293
- setBaseline(initialContents);
6294
6604
  setSaveError(null);
6295
6605
  setSavedTick(false);
6296
- }, [path, initialContents]);
6606
+ }, [path]);
6297
6607
  const noteEdit = useCallback19(
6298
6608
  (next) => {
6299
- setValue(next);
6609
+ setBuffer((current) => ({ ...current, value: next }));
6300
6610
  setSavedTick(false);
6301
6611
  if (!editIntentFiredRef.current && next !== baseline) {
6302
6612
  editIntentFiredRef.current = true;
@@ -6305,7 +6615,7 @@ function CodeEditor({
6305
6615
  },
6306
6616
  [baseline, onEditIntent]
6307
6617
  );
6308
- const langKey = useMemo11(() => languageForPath(path), [path]);
6618
+ const langKey = useMemo12(() => languageForPath(path), [path]);
6309
6619
  useEffect19(() => {
6310
6620
  let cancelled = false;
6311
6621
  void (async () => {
@@ -6349,38 +6659,44 @@ function CodeEditor({
6349
6659
  const saveRef = useRef16(() => {
6350
6660
  });
6351
6661
  const save = useCallback19(async () => {
6352
- if (readOnly) return;
6662
+ if (readOnly || saveInFlightRef.current) return;
6353
6663
  const snapshot = value;
6354
6664
  if (snapshot === baseline) return;
6665
+ saveInFlightRef.current = true;
6355
6666
  setSaving(true);
6356
6667
  setSaveError(null);
6357
6668
  setSavedTick(false);
6358
6669
  try {
6359
- await onSave(snapshot);
6360
- setBaseline(snapshot);
6361
- lastSeed.current = { path, contents: snapshot };
6670
+ await onSave(snapshot, baseline);
6671
+ setBuffer(
6672
+ (current) => current.path === path ? { ...current, baseline: snapshot } : current
6673
+ );
6362
6674
  setSavedTick(true);
6363
6675
  } catch (cause) {
6364
6676
  setSaveError(cause instanceof Error ? cause : new Error(String(cause)));
6365
6677
  } finally {
6678
+ saveInFlightRef.current = false;
6366
6679
  setSaving(false);
6367
6680
  }
6368
6681
  }, [readOnly, value, baseline, onSave, path]);
6369
6682
  const overwrite = useCallback19(async () => {
6370
- if (readOnly || !onOverwrite) return;
6683
+ if (readOnly || !onOverwrite || saveInFlightRef.current) return;
6371
6684
  const snapshot = value;
6372
6685
  if (snapshot === baseline) return;
6686
+ saveInFlightRef.current = true;
6373
6687
  setSaving(true);
6374
6688
  setSaveError(null);
6375
6689
  setSavedTick(false);
6376
6690
  try {
6377
6691
  await onOverwrite(snapshot);
6378
- setBaseline(snapshot);
6379
- lastSeed.current = { path, contents: snapshot };
6692
+ setBuffer(
6693
+ (current) => current.path === path ? { ...current, baseline: snapshot } : current
6694
+ );
6380
6695
  setSavedTick(true);
6381
6696
  } catch (cause) {
6382
6697
  setSaveError(cause instanceof Error ? cause : new Error(String(cause)));
6383
6698
  } finally {
6699
+ saveInFlightRef.current = false;
6384
6700
  setSaving(false);
6385
6701
  }
6386
6702
  }, [readOnly, onOverwrite, value, baseline, path]);
@@ -6393,18 +6709,26 @@ function CodeEditor({
6393
6709
  return () => clearTimeout(t);
6394
6710
  }, [savedTick]);
6395
6711
  const fileName = path.split("/").filter(Boolean).pop() ?? path;
6396
- const cmExtensions = useMemo11(() => {
6712
+ const cmExtensions = useMemo12(() => {
6397
6713
  if (!bundle) return [];
6398
6714
  const exts = [bundle.saveKeymapExtension(() => saveRef.current())];
6399
6715
  if (bundle.languageExtension) exts.push(bundle.languageExtension);
6400
6716
  return exts;
6401
6717
  }, [bundle]);
6402
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]);
6403
6726
  return /* @__PURE__ */ jsxs7(
6404
6727
  "div",
6405
6728
  {
6406
6729
  className: cn("flex h-full min-h-0 flex-col", className),
6407
6730
  "data-opengeni-code-editor": true,
6731
+ "data-opengeni-editor-dirty": dirty ? "true" : "false",
6408
6732
  style: editorVars,
6409
6733
  children: [
6410
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: [
@@ -6431,7 +6755,7 @@ function CodeEditor({
6431
6755
  children: saveError.message
6432
6756
  }
6433
6757
  ),
6434
- !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" }),
6435
6759
  readOnly ? /* @__PURE__ */ jsx9("span", { className: "text-og-xs uppercase tracking-wide text-og-fg-subtle", children: "Read-only" }) : !saveConflict ? /* @__PURE__ */ jsxs7(
6436
6760
  "button",
6437
6761
  {
@@ -6468,8 +6792,7 @@ function CodeEditor({
6468
6792
  {
6469
6793
  type: "button",
6470
6794
  onClick: () => {
6471
- setSaveError(null);
6472
- onReload();
6795
+ reload();
6473
6796
  },
6474
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",
6475
6798
  children: "Reload live"
@@ -6756,7 +7079,7 @@ function SandboxFiles({
6756
7079
  path: viewPath,
6757
7080
  initialContents: fileView.content,
6758
7081
  themeType: resolvedTheme,
6759
- onSave: (contents) => files.writeFile(viewPath, contents, { expectedContent: fileView.content }),
7082
+ onSave: (contents, expectedContents) => files.writeFile(viewPath, contents, { expectedContent: expectedContents }),
6760
7083
  onOverwrite: (contents) => files.writeFile(viewPath, contents, { force: true }),
6761
7084
  onReload: () => setViewReloadRevision((revision) => revision + 1),
6762
7085
  ...onEditIntent ? { onEditIntent } : {},
@@ -7080,7 +7403,7 @@ function DesktopViewer({
7080
7403
  scaleViewport,
7081
7404
  rfbFactory,
7082
7405
  webSocketFactory,
7083
- renderConsentGate,
7406
+ onActivate,
7084
7407
  onAcknowledge,
7085
7408
  watching,
7086
7409
  onWarm,
@@ -7092,22 +7415,55 @@ function DesktopViewer({
7092
7415
  className
7093
7416
  }) {
7094
7417
  const containerRef = useRef18(null);
7095
- 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);
7096
7425
  const [takeControl, setTakeControl] = useState18(interactive ?? false);
7097
7426
  const externallyControlled = interactive !== void 0;
7098
7427
  const serverAllowsControl = capability?.mode !== "read-only";
7099
7428
  const wantControl = externallyControlled ? interactive : takeControl;
7100
7429
  const inControl = Boolean(wantControl) && serverAllowsControl;
7101
- const isWatching = watching ?? consented;
7430
+ const isWatching = watching ?? true;
7102
7431
  const transportNull = !capability || capability.transport === null;
7103
7432
  const reason = capability?.reason ?? null;
7104
- const needsAck = capability?.requiresAcknowledgment === true && capability.acknowledged !== true && !consented;
7433
+ const needsAcknowledgment = capability?.requiresAcknowledgment === true && capability.acknowledged !== true;
7105
7434
  const noLiveAddress = Boolean(capability) && !transportNull && !capability.url;
7106
7435
  const coldWarmable = transportNull && reason === "lease_cold" || !transportNull && noLiveAddress;
7107
7436
  const hardUnavailable = transportNull && isHardUnavailable(reason);
7108
- const accept = () => {
7109
- setConsented(true);
7110
- 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);
7111
7467
  };
7112
7468
  useEffect21(() => {
7113
7469
  if (!inControl || externallyControlled) return;
@@ -7122,7 +7478,7 @@ function DesktopViewer({
7122
7478
  window.removeEventListener("keydown", onKey);
7123
7479
  };
7124
7480
  }, [inControl, externallyControlled]);
7125
- const connectCapability = !transportNull && !needsAck && !viewerCapReached ? capability : null;
7481
+ const connectCapability = !transportNull && !needsAcknowledgment && !viewerCapReached ? capability : null;
7126
7482
  const stream = useDesktopStream({
7127
7483
  capability: connectCapability,
7128
7484
  containerRef,
@@ -7137,17 +7493,16 @@ function DesktopViewer({
7137
7493
  streamStateRef.current = stream.state;
7138
7494
  const warmKeyRef = useRef18(null);
7139
7495
  useEffect21(() => {
7140
- if (!isWatching || !coldWarmable || needsAck || viewerCapReached) {
7496
+ if (!isWatching || !coldWarmable || needsAcknowledgment || viewerCapReached) {
7141
7497
  if (!coldWarmable) warmKeyRef.current = null;
7142
7498
  return;
7143
7499
  }
7144
- const warm = onWarm ?? onAcknowledge;
7145
- if (!warm) return;
7500
+ if (!onWarm) return;
7146
7501
  const key = `${capability?.reason ?? ""}:${capability?.url ?? ""}:${capability?.expiresAt ?? ""}`;
7147
7502
  if (warmKeyRef.current === key) return;
7148
7503
  warmKeyRef.current = key;
7149
- warm();
7150
- }, [isWatching, coldWarmable, needsAck, viewerCapReached, capability, onWarm, onAcknowledge]);
7504
+ onWarm();
7505
+ }, [isWatching, coldWarmable, needsAcknowledgment, viewerCapReached, capability, onWarm]);
7151
7506
  useEffect21(() => {
7152
7507
  if (!hasLiveUrl || typeof document === "undefined") return;
7153
7508
  const maybeReattach = () => {
@@ -7180,13 +7535,14 @@ function DesktopViewer({
7180
7535
  let uiState;
7181
7536
  if (viewerCapReached) uiState = "viewer_cap";
7182
7537
  else if (hardUnavailable) uiState = "unavailable";
7183
- else if (needsAck && !isWatching) uiState = "consent";
7538
+ else if (acknowledgmentError) uiState = "acknowledgment_error";
7539
+ else if (needsAcknowledgment) uiState = "opening";
7184
7540
  else if (coldWarmable) uiState = "warming";
7185
7541
  else if (connected) uiState = "connected";
7186
7542
  else if (stream.error) uiState = "error";
7187
7543
  else if (connectTimedOut) uiState = "connect_failed";
7188
7544
  else uiState = "connecting";
7189
- const overlayShown = uiState !== "connected" && uiState !== "connecting";
7545
+ const overlayShown = uiState !== "connected" && uiState !== "connecting" && uiState !== "opening";
7190
7546
  const showToggle = showControlToggle && !overlayShown;
7191
7547
  let overlay = null;
7192
7548
  switch (uiState) {
@@ -7199,8 +7555,12 @@ function DesktopViewer({
7199
7555
  case "unavailable":
7200
7556
  overlay = renderUnavailable?.(reason) ?? defaultNotice("Desktop unavailable", unavailableCopy(reason));
7201
7557
  break;
7202
- case "consent":
7203
- 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
+ );
7204
7564
  break;
7205
7565
  case "warming":
7206
7566
  overlay = renderWarming?.() ?? /* @__PURE__ */ jsx11(WarmingNotice, {});
@@ -7219,6 +7579,7 @@ function DesktopViewer({
7219
7579
  reconnect
7220
7580
  );
7221
7581
  break;
7582
+ case "opening":
7222
7583
  case "connecting":
7223
7584
  case "connected":
7224
7585
  overlay = null;
@@ -7247,7 +7608,7 @@ function DesktopViewer({
7247
7608
  "data-state": stream.state
7248
7609
  }
7249
7610
  ),
7250
- 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: [
7251
7612
  /* @__PURE__ */ jsxs9("span", { className: "relative flex items-center justify-center", children: [
7252
7613
  /* @__PURE__ */ jsx11(MonitorIcon, { className: "size-8 opacity-30", strokeWidth: 1.5 }),
7253
7614
  /* @__PURE__ */ jsx11(
@@ -7258,7 +7619,7 @@ function DesktopViewer({
7258
7619
  }
7259
7620
  )
7260
7621
  ] }),
7261
- /* @__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" })
7262
7623
  ] }),
7263
7624
  showToggle && !externallyControlled && inControl && /* @__PURE__ */ jsx11(
7264
7625
  InControlBar,
@@ -7396,26 +7757,6 @@ function WarmingNotice({ onRetry }) {
7396
7757
  )
7397
7758
  ] });
7398
7759
  }
7399
- function DefaultConsentGate({ shared, onAccept }) {
7400
- 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: [
7401
- /* @__PURE__ */ jsx11("div", { className: "mb-1 font-medium", children: "Watch the live desktop?" }),
7402
- /* @__PURE__ */ jsxs9("p", { className: "mb-3 text-og-sm text-og-fg-subtle", children: [
7403
- "The desktop pixel stream is ",
7404
- /* @__PURE__ */ jsx11("strong", { children: "un-redacted" }),
7405
- " \u2014 it can show secrets the agent prints on screen.",
7406
- shared ? " This sandbox is shared: you'll also see other sessions' agents on the same screen." : ""
7407
- ] }),
7408
- /* @__PURE__ */ jsx11(
7409
- "button",
7410
- {
7411
- type: "button",
7412
- onClick: onAccept,
7413
- 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",
7414
- children: "I understand \u2014 show the desktop"
7415
- }
7416
- )
7417
- ] });
7418
- }
7419
7760
  function defaultNotice(title, body, onRetry) {
7420
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: [
7421
7762
  /* @__PURE__ */ jsxs9("div", { className: "mb-1 flex items-center justify-center gap-1.5 font-medium", children: [
@@ -7965,12 +8306,21 @@ function DockChrome({
7965
8306
  }
7966
8307
 
7967
8308
  // src/components/sandbox-workspace.tsx
7968
- import { useCallback as useCallback22, useEffect as useEffect23, useMemo as useMemo12, useRef as useRef20, useState as useState20 } from "react";
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";
7969
8318
  import {
7970
8319
  CircleCheckIcon,
7971
8320
  CpuIcon,
7972
8321
  FileCode2Icon as FileCode2Icon2,
7973
8322
  GitCompareArrowsIcon,
8323
+ Globe2Icon,
7974
8324
  LoaderCircleIcon as LoaderCircleIcon3,
7975
8325
  MonitorIcon as MonitorIcon2,
7976
8326
  RefreshCwIcon as RefreshCwIcon2,
@@ -7978,14 +8328,24 @@ import {
7978
8328
  TriangleAlertIcon as TriangleAlertIcon2
7979
8329
  } from "lucide-react";
7980
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
+ });
7981
8339
  var WORKBENCH_TAB_CHANGES = "changes";
7982
8340
  var WORKBENCH_TAB_FILES = "files";
7983
8341
  var WORKBENCH_TAB_TERMINAL = "terminal";
8342
+ var WORKBENCH_TAB_BROWSER = "browser";
7984
8343
  var WORKBENCH_TAB_DESKTOP = "desktop";
7985
8344
  var WORKBENCH_SURFACES = [
7986
8345
  WORKBENCH_TAB_CHANGES,
7987
8346
  WORKBENCH_TAB_FILES,
7988
8347
  WORKBENCH_TAB_TERMINAL,
8348
+ WORKBENCH_TAB_BROWSER,
7989
8349
  WORKBENCH_TAB_DESKTOP
7990
8350
  ];
7991
8351
  function isWorkbenchSurface(value) {
@@ -8015,6 +8375,18 @@ function WorkbenchTabLabel({ icon, children }) {
8015
8375
  /* @__PURE__ */ jsx13("span", { children })
8016
8376
  ] });
8017
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
+ }
8018
8390
  function initialWorkspaceTab(events, override) {
8019
8391
  if (override) return override;
8020
8392
  let bestSeq = -1;
@@ -8026,7 +8398,17 @@ function initialWorkspaceTab(events, override) {
8026
8398
  bestSeq = event.sequence;
8027
8399
  bestFileCount = typeof stats?.fileCount === "number" ? stats.fileCount : 0;
8028
8400
  }
8029
- 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;
8030
8412
  }
8031
8413
  function sandboxProvisionInFlight(events) {
8032
8414
  let inFlight = false;
@@ -8048,14 +8430,25 @@ function sandboxProvisionInFlight(events) {
8048
8430
  function emptyWarmIntents(sessionId) {
8049
8431
  return {
8050
8432
  sessionId,
8051
- watchDesktop: false,
8052
8433
  warmTerminal: false,
8053
8434
  warmFiles: false
8054
8435
  };
8055
8436
  }
8056
8437
  function useSandboxWorkspaceTabs(options) {
8057
8438
  const { client, workspaceId } = useOpenGeni(options);
8058
- 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;
8059
8452
  const initialTab = options.initialTab ?? null;
8060
8453
  const activeTab = options.activeTab ?? initialTab;
8061
8454
  const requestedSurfaces = options.surfaces ?? WORKBENCH_SURFACES;
@@ -8063,9 +8456,24 @@ function useSandboxWorkspaceTabs(options) {
8063
8456
  const changesEnabled = surfaceSet.has(WORKBENCH_TAB_CHANGES);
8064
8457
  const filesEnabled = surfaceSet.has(WORKBENCH_TAB_FILES);
8065
8458
  const terminalEnabled = surfaceSet.has(WORKBENCH_TAB_TERMINAL);
8459
+ const browserEnabled = surfaceSet.has(WORKBENCH_TAB_BROWSER);
8066
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
+ );
8067
8475
  const workspaceDataEnabled = changesEnabled || filesEnabled;
8068
- const anySurfaceEnabled = workspaceDataEnabled || terminalEnabled || desktopEnabled;
8476
+ const machineSurfaceEnabled = workspaceDataEnabled || terminalEnabled || desktopEnabled;
8069
8477
  const resolvedActiveTab = activeTab ?? initialTab;
8070
8478
  const changesActive = resolvedActiveTab === WORKBENCH_TAB_CHANGES;
8071
8479
  const filesActive = resolvedActiveTab === WORKBENCH_TAB_FILES;
@@ -8074,7 +8482,7 @@ function useSandboxWorkspaceTabs(options) {
8074
8482
  () => emptyWarmIntents(sessionId)
8075
8483
  );
8076
8484
  const warmIntents = storedWarmIntents.sessionId === sessionId ? storedWarmIntents : emptyWarmIntents(sessionId);
8077
- const { watchDesktop, warmTerminal, warmFiles } = warmIntents;
8485
+ const { warmTerminal, warmFiles } = warmIntents;
8078
8486
  const requestWarmIntent = useCallback22(
8079
8487
  (intent) => {
8080
8488
  setStoredWarmIntents((previous) => {
@@ -8088,13 +8496,13 @@ function useSandboxWorkspaceTabs(options) {
8088
8496
  workspaceId,
8089
8497
  sessionId,
8090
8498
  pollIntervalMs: 8e3,
8091
- enabled: anySurfaceEnabled
8499
+ enabled: machineSurfaceEnabled
8092
8500
  });
8093
8501
  const activeMachine = machines.machines.find((m) => m.sandboxId === machines.activeSandboxId) ?? machines.machines.find((m) => m.active) ?? null;
8094
8502
  const caps = useSessionCapabilities(sessionId, {
8095
8503
  events,
8096
- enabled: anySurfaceEnabled,
8097
- attachDesktop: desktopEnabled && watchDesktop,
8504
+ enabled: machineSurfaceEnabled,
8505
+ attachDesktop: false,
8098
8506
  attachTerminal: terminalEnabled && warmTerminal,
8099
8507
  // Explicit live-file intent only — NOT "the Files tab is open". A cold edit
8100
8508
  // or guarded-file open wakes the box; a glance at the tree/diff does not.
@@ -8102,19 +8510,18 @@ function useSandboxWorkspaceTabs(options) {
8102
8510
  });
8103
8511
  const capabilities = caps.capabilities;
8104
8512
  const liveness = capabilities?.liveness;
8513
+ const liveIoLiveness = liveness ?? null;
8105
8514
  const fileSystemOn = capabilities?.FileSystem.available ?? false;
8106
8515
  const fsReadOnly = capabilities?.FileSystem.readOnly ?? false;
8107
- const filesEditable = fileSystemOn && !fsReadOnly && (liveness === "warm" || liveness === "draining");
8516
+ const filesEditable = fileSystemOn && !fsReadOnly && sandboxAcceptsLiveIo(liveness);
8108
8517
  const gitOn = capabilities?.Git.available ?? false;
8109
8518
  const terminalOn = terminalEnabled && (capabilities?.Terminal.transport ?? null) !== null;
8110
- const ptyWsLive = capabilities?.Terminal.transport === "pty-ws" && Boolean(capabilities?.Terminal.url);
8111
- const ptyCapable = (capabilities?.Terminal.ptyCapable ?? false) && !ptyWsLive;
8519
+ const ptyCapable = warmTerminal && (capabilities?.Terminal.ptyCapable ?? false) && capabilities?.Terminal.transport === "sse-events" && capabilities?.Terminal.reason === null;
8112
8520
  const terminal = useSandboxTerminal(sessionId, {
8113
8521
  events: terminalEnabled ? events : [],
8114
8522
  interactive: terminalEnabled && ptyCapable,
8115
8523
  liveness
8116
8524
  });
8117
- const desktopAdvertised = desktopEnabled && ((capabilities?.DesktopStream.transport ?? null) !== null || capabilities?.DesktopStream.reason === "lease_cold");
8118
8525
  const provisioning = sandboxProvisionInFlight(events);
8119
8526
  const renegotiate = caps.renegotiate;
8120
8527
  const provisioningRef = useRef20({ sessionId, provisioning });
@@ -8130,9 +8537,9 @@ function useSandboxWorkspaceTabs(options) {
8130
8537
  enabled: workspaceDataEnabled
8131
8538
  });
8132
8539
  const captureAvailable = captureState.available;
8133
- const liveWorkspaceExpected = liveness === "warm" || liveness === "draining";
8540
+ const liveWorkspaceExpected = sandboxAcceptsLiveIo(liveness);
8134
8541
  const capturePending = !liveWorkspaceExpected && (captureState.fileCount === null || captureState.loading);
8135
- const repoPaths = useMemo12(() => {
8542
+ const repoPaths = useMemo13(() => {
8136
8543
  const advertised = capabilities?.Git.repos ?? [];
8137
8544
  if (advertised.length > 0) return advertised;
8138
8545
  return captureState.capture?.repos.map((repo) => repo.root) ?? [];
@@ -8157,7 +8564,7 @@ function useSandboxWorkspaceTabs(options) {
8157
8564
  enabled: filesEnabled && (captureAvailable || fileSystemOn && liveWorkspaceExpected),
8158
8565
  active: filesActive,
8159
8566
  repoPaths,
8160
- liveness,
8567
+ liveness: liveIoLiveness,
8161
8568
  capture: captureState.capture,
8162
8569
  // A reverted optimistic mutation (e.g. a 409 rename collision) surfaces as a
8163
8570
  // host notification — the tree silently rolls back, the user sees why.
@@ -8181,7 +8588,7 @@ function useSandboxWorkspaceTabs(options) {
8181
8588
  // dormant, avoiding the old duplicate full-tree read.
8182
8589
  active: changesActive || resolvedActiveTab === null,
8183
8590
  repoPaths,
8184
- liveness,
8591
+ liveness: liveIoLiveness,
8185
8592
  comparison: changesComparison,
8186
8593
  capture: changesComparison === "staged" ? null : captureState.capture
8187
8594
  });
@@ -8202,33 +8609,13 @@ function useSandboxWorkspaceTabs(options) {
8202
8609
  });
8203
8610
  return () => observer.disconnect();
8204
8611
  }, [terminalEnabled]);
8205
- async function acknowledgeAndWatch() {
8206
- try {
8207
- const shared = capabilities?.DesktopStream.shared ?? false;
8208
- await client.acknowledgeStream(workspaceId, sessionId, {
8209
- acknowledgeUnredacted: true,
8210
- acknowledgeShared: shared
8211
- });
8212
- requestWarmIntent("watchDesktop");
8213
- caps.renegotiate();
8214
- } catch (error) {
8215
- onNotify?.({
8216
- kind: "error",
8217
- message: `Could not start the desktop stream: ${error instanceof Error ? error.message : String(error)}`
8218
- });
8219
- }
8220
- }
8221
- function rewarmDesktop() {
8222
- if (!watchDesktop) requestWarmIntent("watchDesktop");
8223
- caps.renegotiate();
8224
- }
8225
8612
  const dirtyCount = git.diff.length;
8226
8613
  const chip = useMachineChip({
8227
8614
  liveness,
8228
8615
  capabilitiesState: caps.state,
8229
8616
  activeMachineState: activeMachine?.state ?? null,
8230
8617
  activeIsSelfhosted: activeMachine?.kind === "selfhosted",
8231
- wantsWarm: terminalEnabled && warmTerminal || desktopEnabled && watchDesktop || workspaceDataEnabled && warmFiles,
8618
+ wantsWarm: terminalEnabled && warmTerminal || workspaceDataEnabled && warmFiles,
8232
8619
  capturedAt: captureState.capturedAt
8233
8620
  });
8234
8621
  const workspaceWaking = chip.state === "waking";
@@ -8241,7 +8628,8 @@ function useSandboxWorkspaceTabs(options) {
8241
8628
  defaultTabRef.current = { identity: defaultIdentity, value: null };
8242
8629
  }
8243
8630
  const captureIsAuthoritative = !liveWorkspaceExpected && (liveness !== void 0 || caps.error !== null);
8244
- 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;
8245
8633
  const implicitBranchFallbackPending = changesComparison === "branch" && git.error !== null && storedChangesComparison?.sessionId !== sessionId;
8246
8634
  if (defaultTabRef.current.value === null) {
8247
8635
  if (initialTab && (!isWorkbenchSurface(initialTab) || surfaceSet.has(initialTab))) {
@@ -8254,7 +8642,7 @@ function useSandboxWorkspaceTabs(options) {
8254
8642
  );
8255
8643
  } else if (captureIsAuthoritative && captureState.fileCount !== null && captureState.available) {
8256
8644
  defaultTabRef.current.value = sourceDrivenDefaultTab(
8257
- captureState.fileCount > 0,
8645
+ captureHasChanges,
8258
8646
  changesEnabled,
8259
8647
  filesEnabled
8260
8648
  );
@@ -8266,7 +8654,7 @@ function useSandboxWorkspaceTabs(options) {
8266
8654
  defaultTabRef.current.value = sourceDrivenDefaultTab(false, changesEnabled, filesEnabled);
8267
8655
  } else if (captureState.available && git.error && captureState.fileCount !== null) {
8268
8656
  defaultTabRef.current.value = sourceDrivenDefaultTab(
8269
- captureState.fileCount > 0,
8657
+ captureHasChanges,
8270
8658
  changesEnabled,
8271
8659
  filesEnabled
8272
8660
  );
@@ -8275,7 +8663,7 @@ function useSandboxWorkspaceTabs(options) {
8275
8663
  }
8276
8664
  }
8277
8665
  const defaultTab = defaultTabRef.current.value;
8278
- const tabs = useMemo12(() => {
8666
+ const tabs = useMemo13(() => {
8279
8667
  const list = [];
8280
8668
  if (changesEnabled)
8281
8669
  list.push({
@@ -8324,7 +8712,7 @@ function useSandboxWorkspaceTabs(options) {
8324
8712
  ...requestedFilePath ? {
8325
8713
  requestedPath: requestedFilePath,
8326
8714
  ...requestedFileRequestId !== null && requestedFileRequestId !== void 0 ? { requestedPathRequestId: requestedFileRequestId } : {},
8327
- requestedPathReady: liveness === "warm" || liveness === "draining"
8715
+ requestedPathReady: sandboxAcceptsLiveIo(liveness)
8328
8716
  } : {},
8329
8717
  onEditIntent: () => requestWarmIntent("warmFiles"),
8330
8718
  className: "h-full"
@@ -8346,26 +8734,49 @@ function useSandboxWorkspaceTabs(options) {
8346
8734
  shell: capabilities?.Terminal.shell ?? void 0,
8347
8735
  liveness,
8348
8736
  ...xtermTheme ? { theme: xtermTheme } : {}
8349
- }
8737
+ },
8738
+ sessionId
8350
8739
  ) })
8351
8740
  });
8352
8741
  }
8353
- 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) {
8354
8763
  list.push({
8355
8764
  id: WORKBENCH_TAB_DESKTOP,
8356
- label: /* @__PURE__ */ jsx13(WorkbenchTabLabel, { icon: /* @__PURE__ */ jsx13(MonitorIcon2, {}), children: "Desktop" }),
8357
- 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,
8358
- content: /* @__PURE__ */ jsx13(
8359
- 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,
8360
8768
  {
8361
- capability: capabilities?.DesktopStream ?? null,
8362
- viewerCapReached: caps.viewerCapReached,
8363
- watching: watchDesktop,
8364
- onAcknowledge: () => void acknowledgeAndWatch(),
8365
- onWarm: rewarmDesktop,
8769
+ client,
8770
+ workspaceId,
8771
+ sessionId,
8772
+ onNotify,
8773
+ requestedComputerSessionId,
8774
+ requestedComputerRequestId,
8775
+ ...computerWebSocketFactory ? { webSocketFactory: computerWebSocketFactory } : {},
8366
8776
  className: "h-full"
8367
- }
8368
- )
8777
+ },
8778
+ sessionId
8779
+ ) })
8369
8780
  });
8370
8781
  }
8371
8782
  return list;
@@ -8374,14 +8785,22 @@ function useSandboxWorkspaceTabs(options) {
8374
8785
  changesEnabled,
8375
8786
  filesEnabled,
8376
8787
  terminalOn,
8377
- desktopAdvertised,
8788
+ browserEnabled,
8789
+ desktopEnabled,
8378
8790
  captureAvailable,
8379
8791
  dirtyCount,
8380
- watchDesktop,
8381
8792
  warmTerminal,
8382
8793
  requestedFilePath,
8383
8794
  requestedFileRequestId,
8384
8795
  onOpenFile,
8796
+ onOpenComputerSession,
8797
+ requestedComputerSessionId,
8798
+ requestedComputerRequestId,
8799
+ onNotify,
8800
+ browserWebSocketFactory,
8801
+ computerWebSocketFactory,
8802
+ client,
8803
+ workspaceId,
8385
8804
  liveness,
8386
8805
  sessionId,
8387
8806
  files,
@@ -8394,13 +8813,14 @@ function useSandboxWorkspaceTabs(options) {
8394
8813
  caps.state,
8395
8814
  caps.error,
8396
8815
  caps.viewerCapReached,
8397
- requestWarmIntent
8816
+ requestWarmIntent,
8817
+ createLinkedComputer
8398
8818
  ]);
8399
8819
  return {
8400
8820
  tabs,
8401
8821
  defaultTab,
8402
8822
  machine: {
8403
- enabled: anySurfaceEnabled,
8823
+ enabled: machineSurfaceEnabled,
8404
8824
  chip,
8405
8825
  activeMachine,
8406
8826
  loading: machines.loading,
@@ -8419,6 +8839,8 @@ function SandboxWorkspace(props) {
8419
8839
  trailingTabs,
8420
8840
  initialTab,
8421
8841
  onNotify,
8842
+ browserWebSocketFactory,
8843
+ computerWebSocketFactory,
8422
8844
  collapsed,
8423
8845
  onCollapsedChange,
8424
8846
  showCollapseControl,
@@ -8431,7 +8853,9 @@ function SandboxWorkspace(props) {
8431
8853
  } = props;
8432
8854
  const [storedSelection, setStoredSelection] = useState20(null);
8433
8855
  const [requestedFile, setRequestedFile] = useState20(null);
8856
+ const [requestedComputer, setRequestedComputer] = useState20(null);
8434
8857
  const nextFileRequestId = useRef20(0);
8858
+ const nextComputerRequestId = useRef20(0);
8435
8859
  const selectedTab = storedSelection?.sessionId === sessionId ? storedSelection.tab : null;
8436
8860
  const activeTabHint = selectedTab ?? initialTab ?? leadingTabs?.[0]?.id ?? null;
8437
8861
  const openFile = useCallback22(
@@ -8442,6 +8866,18 @@ function SandboxWorkspace(props) {
8442
8866
  },
8443
8867
  [sessionId]
8444
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
+ );
8445
8881
  const {
8446
8882
  tabs: workbenchTabs,
8447
8883
  machine,
@@ -8455,9 +8891,14 @@ function SandboxWorkspace(props) {
8455
8891
  ...initialTab ? { initialTab } : {},
8456
8892
  activeTab: activeTabHint,
8457
8893
  ...onNotify ? { onNotify } : {},
8894
+ ...browserWebSocketFactory ? { browserWebSocketFactory } : {},
8895
+ ...computerWebSocketFactory ? { computerWebSocketFactory } : {},
8458
8896
  requestedFilePath: requestedFile?.sessionId === sessionId ? requestedFile.path : null,
8459
8897
  requestedFileRequestId: requestedFile?.sessionId === sessionId ? requestedFile.requestId : null,
8460
- onOpenFile: openFile
8898
+ onOpenFile: openFile,
8899
+ onOpenComputerSession: openComputer,
8900
+ requestedComputerSessionId: requestedComputer?.sessionId === sessionId ? requestedComputer.computerSessionId : null,
8901
+ requestedComputerRequestId: requestedComputer?.sessionId === sessionId ? requestedComputer.requestId : null
8461
8902
  });
8462
8903
  const tabs = [...leadingTabs ?? [], ...workbenchTabs, ...trailingTabs ?? []];
8463
8904
  const preferredTab = selectedTab ?? defaultTab;
@@ -8849,6 +9290,7 @@ export {
8849
9290
  FileBrowser,
8850
9291
  FileWriteConflictError,
8851
9292
  FleetTile,
9293
+ GeneratedVideoPlayer,
8852
9294
  HEALTH_TOKEN,
8853
9295
  HumanInputForm,
8854
9296
  HumanInputSurface,
@@ -8917,6 +9359,7 @@ export {
8917
9359
  VoiceRecordingStorageUnavailableError,
8918
9360
  WINDOW_LABEL,
8919
9361
  WORKBENCH_SURFACES,
9362
+ WORKBENCH_TAB_BROWSER,
8920
9363
  WORKBENCH_TAB_CHANGES,
8921
9364
  WORKBENCH_TAB_DESKTOP,
8922
9365
  WORKBENCH_TAB_FILES,
@@ -9058,6 +9501,7 @@ export {
9058
9501
  useTranscription,
9059
9502
  useTurnQueue,
9060
9503
  useVariableSets,
9504
+ useVideoArtifactPlaybackLoader,
9061
9505
  useVoiceInput,
9062
9506
  useWorkspaceCapture,
9063
9507
  useWorkspaceEdit,