@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
@@ -7,6 +7,7 @@ import type {
7
7
  import { OpenGeniApiError } from "@opengeni/sdk";
8
8
  import { useCallback, useEffect, useMemo, useRef, useState } from "react";
9
9
  import { useOpenGeni, type ClientOverride } from "../provider";
10
+ import { sandboxAcceptsLiveIo } from "../lib/sandbox-liveness";
10
11
 
11
12
  export type TerminalChunk = {
12
13
  /** Stable key (the source event id) so the xterm writer tracks a written-cursor. */
@@ -107,9 +108,9 @@ export function useSandboxTerminal(
107
108
  // projection is the single source of truth for what's written). The open is
108
109
  // gated on a WARM box: opening on a cold/warming lease races the box (the PTY
109
110
  // exec-session is created on a box that the next op may not resume), which is
110
- // exactly the "session not found" terminal failure; we wait for warm/draining.
111
+ // exactly the "session not found" terminal failure; we wait for warm.
111
112
  const openInFlight = useRef(false);
112
- const boxWarm = liveness === undefined || liveness === "warm" || liveness === "draining";
113
+ const boxWarm = liveness === undefined || sandboxAcceptsLiveIo(liveness);
113
114
  useEffect(() => {
114
115
  if (!interactive || !sessionId || ptyFilter || !boxWarm) {
115
116
  setOpenError(null);
@@ -7,10 +7,13 @@ import {
7
7
  } from "@opengeni/sdk";
8
8
  import { useCallback, useEffect, useRef, useState } from "react";
9
9
  import { useOpenGeni, type ClientOverride } from "../provider";
10
+ import { sandboxAcceptsLiveIo } from "../lib/sandbox-liveness";
11
+ import { terminalCanAcquirePty } from "../lib/terminal-capability";
10
12
  import { usePageLiveActivity } from "./internal";
11
13
 
12
- // "on-demand" is the boxless-benign resting state: the lease is cold and NOTHING
13
- // asked to warm a box (no viewer/consent action, no files-tab warm). Under lazy
14
+ // "on-demand" is the boxless-benign resting state: the lease is not currently
15
+ // holder-backed and NOTHING asked to warm a box (no viewer/consent action, no
16
+ // files-tab warm). Under lazy
14
17
  // provisioning a chat-only turn never creates a box, so this is the correct calm
15
18
  // terminal state — NOT an error. It is distinct from "cold", which now means "a
16
19
  // warm-up is genuinely in flight" (a viewer attach was requested) and therefore
@@ -84,7 +87,8 @@ export type UseSessionCapabilitiesResult = {
84
87
  error: Error | null;
85
88
  /**
86
89
  * 409 from the desktop attach: the un-redacted (or shared) plane needs explicit
87
- * acknowledgment before a viewer holder is granted. Drives the consent prompt.
90
+ * acknowledgment before a viewer holder is granted. The workbench uses this
91
+ * to complete acknowledgment automatically while Desktop is opening.
88
92
  */
89
93
  acknowledgmentRequired: "unredacted" | "shared" | null;
90
94
  /** 429 from the desktop attach: the per-session viewer cap is reached. */
@@ -112,24 +116,10 @@ function desktopAttachable(cell: SessionCapabilities["DesktopStream"]): boolean
112
116
 
113
117
  /**
114
118
  * Whether warming the box for the interactive terminal (pty-ws) is worth it.
115
- * The Terminal cell is ALWAYS feasible when the backend advertises a transport at
116
- * all (`sse-events` on a cold box, `pty-ws` once warm) only a genuinely
117
- * terminal-less backend reports transport:null with a hard reason. The attach is
118
- * what flips `sse-events` → `pty-ws` by warming the box and minting the ttyd
119
- * tunnel URL, so we attach whenever the terminal is not hard-unavailable.
119
+ * A real-PTY descriptor is attachable when already routed as `pty-ws`, or while
120
+ * transiently cold. A firehose-only backend and policy/OS/backend degradation
121
+ * cannot become interactive, so attaching there would only waste a holder.
120
122
  */
121
- function terminalAttachable(cell: SessionCapabilities["Terminal"]): boolean {
122
- if (cell.transport === null) {
123
- // No terminal at all unless the only blocker is a transient cold state.
124
- return (
125
- cell.reason === "lease_cold" || cell.reason === "not_provisioned" || cell.reason === null
126
- );
127
- }
128
- // Already pty-ws (warm) is fine to (re)attach; sse-events is the cold state the
129
- // attach upgrades. Either way it's attachable.
130
- return true;
131
- }
132
-
133
123
  /** Read `stream.url.rotated` payloads off the live event log, newest last. */
134
124
  function rotationsFrom(events: SessionEvent[]): StreamUrlRotatedPayload[] {
135
125
  const out: StreamUrlRotatedPayload[] = [];
@@ -149,8 +139,8 @@ function rotationsFrom(events: SessionEvent[]): StreamUrlRotatedPayload[] {
149
139
  *
150
140
  * Degradation is a value, never a crash: an unsupported surface comes back
151
141
  * `available:false`/`transport:null` + a `reason`; the components render the
152
- * reason-aware empty state. 409 (consent) and 429 (viewer cap) are surfaced as
153
- * typed signals, not thrown.
142
+ * reason-aware empty state. 409 (acknowledgment) and 429 (viewer cap) are
143
+ * surfaced as typed signals, not thrown.
154
144
  */
155
145
  export function useSessionCapabilities(
156
146
  sessionId: string | null | undefined,
@@ -207,8 +197,15 @@ export function useSessionCapabilities(
207
197
  if (!lifecycleEnabled || !sessionId) {
208
198
  setState("idle");
209
199
  setCapabilities(null);
200
+ viewerIdRef.current = null;
201
+ setViewerId(null);
210
202
  return;
211
203
  }
204
+ // The preceding effect cleanup released any old holder before this fresh
205
+ // lifecycle starts. Never project that stale id while re-negotiating or if
206
+ // the replacement grant degrades.
207
+ viewerIdRef.current = null;
208
+ setViewerId(null);
212
209
  let cancelled = false;
213
210
  let pollTimer: ReturnType<typeof setTimeout> | null = null;
214
211
  let heartbeatTimer: ReturnType<typeof setTimeout> | null = null;
@@ -273,7 +270,7 @@ export function useSessionCapabilities(
273
270
  .then((caps) => {
274
271
  if (cancelled) return;
275
272
  settle(caps);
276
- if (caps.liveness === "warm" || caps.liveness === "draining") {
273
+ if (sandboxAcceptsLiveIo(caps.liveness)) {
277
274
  setState("ready");
278
275
  return;
279
276
  }
@@ -308,8 +305,8 @@ export function useSessionCapabilities(
308
305
  // attach serves BOTH live planes — the desktop pixel stream AND the
309
306
  // interactive terminal (ttyd pty-ws) ride the same warm box and the same
310
307
  // holder; the response folds the minted address for each requested plane.
311
- // The consent gate (409) and the viewer cap (429) surface as typed signals
312
- // rather than throwing — the tabs degrade gracefully.
308
+ // The acknowledgment requirement (409) and viewer cap (429) surface as
309
+ // typed signals rather than throwing — the tabs degrade gracefully.
313
310
  //
314
311
  // KEY: the handshake (getStreamCapabilities) NEVER spins up a cold box, so
315
312
  // on a cold/warming lease the desktop cell comes back transport:null and
@@ -321,7 +318,7 @@ export function useSessionCapabilities(
321
318
  // already live OR cold-but-feasible — and let POST /viewers warm the box and
322
319
  // mint the URLs. Only a genuinely-unsupported reason suppresses the attach.
323
320
  const wantDesktopAttach = attachDesktop && desktopAttachable(caps.DesktopStream);
324
- const wantTerminalAttach = attachTerminal && terminalAttachable(caps.Terminal);
321
+ const wantTerminalAttach = attachTerminal && terminalCanAcquirePty(caps.Terminal);
325
322
  // The Files attach warms the box for a file WRITE (wake-on-edit intent). It
326
323
  // folds no live URL (files are stateless HTTP) — the attach is purely a
327
324
  // liveness refcount. Unlike the previous "keep the box warm while the Files
@@ -339,6 +336,8 @@ export function useSessionCapabilities(
339
336
  // pty-ws terminal cell WITHOUT tripping the desktop consent 409.
340
337
  const holder = await client.attachViewer(workspaceId, sessionId, {
341
338
  desktop: wantDesktopAttach,
339
+ terminal: wantTerminalAttach,
340
+ files: wantFilesAttach,
342
341
  });
343
342
  if (cancelled) {
344
343
  // The attach crossed an identity/unmount boundary after the server
@@ -348,6 +347,18 @@ export function useSessionCapabilities(
348
347
  void client.detachViewer(workspaceId, sessionId, holder.viewerId).catch(() => {});
349
348
  return;
350
349
  }
350
+ if (
351
+ (wantDesktopAttach &&
352
+ (!holder.dataPlaneUrl || !holder.transport || !holder.client)) ||
353
+ (wantTerminalAttach && (!holder.terminalUrl || !holder.terminalTransport))
354
+ ) {
355
+ // Nullable plane cells are valid graceful-degradation values at
356
+ // the raw API boundary, but an explicit UI grant must not become
357
+ // a successful holder that waits forever. Release it and surface
358
+ // a retryable error instead of retaining stale credentials.
359
+ await client.detachViewer(workspaceId, sessionId, holder.viewerId).catch(() => {});
360
+ throw new Error("live plane unavailable");
361
+ }
351
362
  localViewerId = holder.viewerId;
352
363
  viewerIdRef.current = holder.viewerId;
353
364
  setViewerId(holder.viewerId);
@@ -367,24 +378,24 @@ export function useSessionCapabilities(
367
378
  ...prev.DesktopStream,
368
379
  transport: holder.transport ?? prev.DesktopStream.transport,
369
380
  client: holder.client ?? prev.DesktopStream.client,
370
- url: holder.dataPlaneUrl ?? prev.DesktopStream.url,
371
- token: holder.streamToken ?? prev.DesktopStream.token,
372
- expiresAt: holder.streamExpiresAt ?? prev.DesktopStream.expiresAt,
381
+ url: holder.dataPlaneUrl,
382
+ token: holder.streamToken,
383
+ expiresAt: holder.streamExpiresAt,
373
384
  resolution: holder.resolution ?? prev.DesktopStream.resolution,
374
385
  }
375
386
  : prev.DesktopStream,
376
- Terminal:
377
- holder.terminalUrl && holder.terminalTransport
378
- ? {
379
- ...prev.Terminal,
380
- transport: holder.terminalTransport,
381
- url: holder.terminalUrl,
382
- token: holder.terminalToken ?? prev.Terminal.token,
383
- // A live pty-ws means the box is pty-capable for real.
384
- ptyCapable: true,
385
- reason: null,
386
- }
387
- : prev.Terminal,
387
+ Terminal: wantTerminalAttach
388
+ ? {
389
+ ...prev.Terminal,
390
+ transport: holder.terminalTransport ?? prev.Terminal.transport,
391
+ url: holder.terminalUrl,
392
+ token: holder.terminalToken,
393
+ expiresAt: holder.terminalExpiresAt,
394
+ // A live pty-ws means the box is pty-capable for real.
395
+ ptyCapable: holder.terminalTransport ? true : prev.Terminal.ptyCapable,
396
+ reason: holder.terminalTransport ? null : prev.Terminal.reason,
397
+ }
398
+ : prev.Terminal,
388
399
  }
389
400
  : prev,
390
401
  );
@@ -393,8 +404,8 @@ export function useSessionCapabilities(
393
404
  if (cause instanceof OpenGeniApiError) {
394
405
  if (cause.status === 409) {
395
406
  // The un-redacted/shared consent gate is a DESKTOP requirement. A
396
- // terminal-only warm attach needs no consent, so a 409 there is not
397
- // a consent prompt — the terminal just stays on the read-only
407
+ // terminal-only warm attach needs no acknowledgment, so a 409 there
408
+ // is not a desktop-open signal — the terminal stays on the read-only
398
409
  // firehose. Only raise the consent requirement when the desktop was
399
410
  // the (or a) reason we attached.
400
411
  if (wantDesktopAttach) {
@@ -408,6 +419,8 @@ export function useSessionCapabilities(
408
419
  setState("error");
409
420
  setError(cause);
410
421
  return;
422
+ } else {
423
+ throw cause;
411
424
  }
412
425
  // 409/429 are recoverable: structured surfaces still negotiated;
413
426
  // keep going to set ready/cold below.
@@ -418,16 +431,16 @@ export function useSessionCapabilities(
418
431
  }
419
432
 
420
433
  // Whether ANYTHING asked to warm a box this negotiation. Only then is a
421
- // cold lease "warming in flight" — worth polling, and worth surfacing a
434
+ // non-warm lease "warming in flight" — worth polling, and worth surfacing a
422
435
  // stall as an error. With no warm-up requested a cold lease is the benign
423
436
  // boxless resting state (below), never an error.
424
437
  const warmUpRequested = wantDesktopAttach || wantTerminalAttach || wantFilesAttach;
425
- if (caps.liveness === "warm" || caps.liveness === "draining" || localViewerId) {
438
+ if (sandboxAcceptsLiveIo(caps.liveness) || localViewerId) {
426
439
  setState("ready");
427
440
  startHeartbeat(caps);
428
441
  } else if (warmUpRequested) {
429
442
  // A warm-up is genuinely in flight (a viewer attach was requested) but the
430
- // box isn't warm yet — poll until it warms, with the patience deadline so a
443
+ // box isn't holder-confirmed warm yet — poll until it is, with the deadline so a
431
444
  // real stall surfaces as an error.
432
445
  setState("cold");
433
446
  pollUntilWarm();
@@ -17,7 +17,7 @@ export type UseTerminalStreamOptions = {
17
17
  * The stream connects ONLY when `transport === "pty-ws"` and `url` is set; on a
18
18
  * cold box (`transport === "sse-events"` / no url) it stays idle and the caller
19
19
  * falls back to the Channel-A read-only firehose. */
20
- capability: Pick<TerminalCapability, "transport" | "url" | "token"> | null;
20
+ capability: Pick<TerminalCapability, "transport" | "url" | "token" | "expiresAt"> | null;
21
21
  /** Called for each OUTPUT payload from ttyd (write verbatim into xterm). */
22
22
  onOutput?: ((data: string) => void) | undefined;
23
23
  /** Called when ttyd sends a SET_WINDOW_TITLE frame. */
@@ -31,7 +31,8 @@ export type UseTerminalStreamResult = {
31
31
  /** True once the ttyd socket is open (and the auth frame has been sent). */
32
32
  connected: boolean;
33
33
  status: TerminalStreamStatus;
34
- /** Pipe a keystroke/paste to the PTY stdin. No-op until the socket is open. */
34
+ /** Pipe a keystroke/paste to PTY stdin. Input during CONNECTING is bounded and
35
+ * replayed only after ttyd authentication succeeds. */
35
36
  write: (data: string) => void;
36
37
  /** Tell ttyd the PTY window changed size (on xterm fit/resize). */
37
38
  resize: (cols: number, rows: number) => void;
@@ -39,6 +40,27 @@ export type UseTerminalStreamResult = {
39
40
  disconnect: () => void;
40
41
  };
41
42
 
43
+ // Keystrokes entered during the websocket handshake are held until ttyd has
44
+ // accepted its auth/resize frames. Bound the queue by UTF-16 code units (a hard
45
+ // <=128 KiB string payload in current JS engines) so a paste cannot turn a slow
46
+ // handshake into unbounded renderer memory. Overflow rejects the whole pending
47
+ // input rather than ever executing a truncated shell command.
48
+ export const MAX_PENDING_TERMINAL_INPUT_CODE_UNITS = 64 * 1024;
49
+ const TERMINAL_CONNECT_EXPIRY_SKEW_MS = 5_000;
50
+
51
+ /** A bearer must remain valid long enough to finish a new websocket handshake.
52
+ * Existing open sockets are unaffected: this is evaluated only when the
53
+ * credential identity changes and the connection effect attempts a new socket. */
54
+ export function terminalStreamCredentialUsable(
55
+ capability: Pick<TerminalCapability, "transport" | "url" | "expiresAt"> | null | undefined,
56
+ nowMs = Date.now(),
57
+ ): boolean {
58
+ if (capability?.transport !== "pty-ws" || !capability.url) return false;
59
+ if (!capability.expiresAt) return true;
60
+ const expiresAt = Date.parse(capability.expiresAt);
61
+ return Number.isFinite(expiresAt) && expiresAt > nowMs + TERMINAL_CONNECT_EXPIRY_SKEW_MS;
62
+ }
63
+
42
64
  /** Decode an inbound ttyd frame's payload (everything after the 1-char command).
43
65
  * ttyd may send either a text frame (string) or a binary frame (ArrayBuffer);
44
66
  * for binary we slice off the first byte (the command) and utf-8 decode the rest. */
@@ -52,6 +74,14 @@ function decodeFrame(data: string | ArrayBuffer): { command: string; payload: st
52
74
  return { command, payload };
53
75
  }
54
76
 
77
+ function closeSocket(socket: WebSocket | null): void {
78
+ try {
79
+ socket?.close();
80
+ } catch {
81
+ // Already closed or rejected by the host WebSocket implementation.
82
+ }
83
+ }
84
+
55
85
  /**
56
86
  * Drive a ttyd PTY-over-websocket connection from a `pty-ws` Terminal capability,
57
87
  * symmetric with `use-desktop-stream` (the noVNC-over-tunnel hook). The scoped
@@ -73,6 +103,9 @@ export function useTerminalStream(options: UseTerminalStreamOptions): UseTermina
73
103
  const { capability, onOutput, onTitle, initialCols, initialRows } = options;
74
104
  const [status, setStatus] = useState<TerminalStreamStatus>("closed");
75
105
  const wsRef = useRef<WebSocket | null>(null);
106
+ const pendingInputRef = useRef("");
107
+ const socketFailedRef = useRef(false);
108
+ const socketAuthenticatedRef = useRef(false);
76
109
  // Latest size, so a resize() before the socket opens is replayed on open, and a
77
110
  // reconnect seeds the right geometry.
78
111
  const sizeRef = useRef<{ cols: number; rows: number }>({
@@ -89,12 +122,35 @@ export function useTerminalStream(options: UseTerminalStreamOptions): UseTermina
89
122
  const transport = capability?.transport ?? null;
90
123
  const url = capability?.url ?? null;
91
124
  const token = capability?.token ?? null;
125
+ const expiresAt = capability?.expiresAt ?? null;
92
126
 
93
127
  useEffect(() => {
94
128
  // SSR / no WebSocket / not a live pty-ws cell: stay closed; the caller falls
95
129
  // back to the Channel-A read-only firehose.
96
130
  if (typeof window === "undefined" || typeof WebSocket === "undefined") return;
97
- if (transport !== "pty-ws" || !url) {
131
+ if (transport !== "pty-ws") {
132
+ // A transport downgrade is a real semantic boundary. Do not replay input
133
+ // captured for a PTY if this surface later changes back from firehose mode.
134
+ pendingInputRef.current = "";
135
+ socketAuthenticatedRef.current = false;
136
+ setStatus("closed");
137
+ return;
138
+ }
139
+ // A changed/cleared credential is a new connection attempt boundary. It may
140
+ // receive input while its exact grant is pending even if the prior socket
141
+ // failed; the unchanged failed identity remains fenced because this effect
142
+ // does not re-run for a status-only render.
143
+ socketFailedRef.current = false;
144
+ socketAuthenticatedRef.current = false;
145
+ if (!url) {
146
+ // Exact grants arrive asynchronously. Preserve the bounded first input
147
+ // while the descriptor remains pty-ws, but never open without a credential.
148
+ setStatus("closed");
149
+ return;
150
+ }
151
+ if (!terminalStreamCredentialUsable({ transport, url, expiresAt })) {
152
+ // A descriptor from an older rolling server may still carry a bearer that
153
+ // aged out before this surface mounted. Wait for the exact fresh grant.
98
154
  setStatus("closed");
99
155
  return;
100
156
  }
@@ -107,6 +163,7 @@ export function useTerminalStream(options: UseTerminalStreamOptions): UseTermina
107
163
  // it. The scoped token is already in the tunnel `url`.
108
164
  socket = new WebSocket(terminalSocketUrl({ url }), TTYD_SUBPROTOCOL);
109
165
  } catch {
166
+ socketFailedRef.current = true;
110
167
  setStatus("error");
111
168
  return;
112
169
  }
@@ -121,8 +178,18 @@ export function useTerminalStream(options: UseTerminalStreamOptions): UseTermina
121
178
  try {
122
179
  socket.send(ttydAuthFrame({ columns: sizeRef.current.cols, rows: sizeRef.current.rows }));
123
180
  socket.send(ttydResizeFrame(sizeRef.current.cols, sizeRef.current.rows));
181
+ if (pendingInputRef.current.length > 0) {
182
+ // One websocket frame makes the pre-open queue all-or-nothing from this
183
+ // client's perspective; never execute a prefix of a buffered command.
184
+ socket.send(ttydInputFrame(pendingInputRef.current));
185
+ pendingInputRef.current = "";
186
+ }
187
+ socketAuthenticatedRef.current = true;
124
188
  } catch {
125
- // a closed socket between open and send — onclose handles state.
189
+ socketFailedRef.current = true;
190
+ setStatus("error");
191
+ closeSocket(socket);
192
+ return;
126
193
  }
127
194
  setStatus("open");
128
195
  };
@@ -144,46 +211,60 @@ export function useTerminalStream(options: UseTerminalStreamOptions): UseTermina
144
211
  };
145
212
 
146
213
  socket.onerror = () => {
147
- if (!disposed) setStatus("error");
214
+ if (!disposed) {
215
+ socketFailedRef.current = true;
216
+ setStatus("error");
217
+ }
148
218
  };
149
219
  socket.onclose = () => {
150
- if (!disposed) setStatus("closed");
220
+ if (wsRef.current === socket) wsRef.current = null;
221
+ socketAuthenticatedRef.current = false;
222
+ if (!disposed) setStatus(socketFailedRef.current ? "error" : "closed");
151
223
  };
152
224
 
153
225
  return () => {
154
226
  disposed = true;
155
227
  wsRef.current = null;
228
+ socketAuthenticatedRef.current = false;
156
229
  // Drop handlers so an in-flight close/error doesn't mutate state post-unmount.
157
230
  socket.onopen = null;
158
231
  socket.onmessage = null;
159
232
  socket.onerror = null;
160
233
  socket.onclose = null;
161
- try {
162
- socket.close();
163
- } catch {
164
- // ignore teardown errors
165
- }
234
+ closeSocket(socket);
166
235
  };
167
236
  // A url/token change (rotation) re-runs this effect → close old, open new.
168
237
  // eslint-disable-next-line react-hooks/exhaustive-deps
169
- }, [transport, url, token]);
238
+ }, [transport, url, token, expiresAt]);
170
239
 
171
240
  return useMemo<UseTerminalStreamResult>(() => {
172
241
  const write = (data: string) => {
173
242
  const ws = wsRef.current;
174
- if (ws && ws.readyState === WebSocket.OPEN) {
243
+ if (ws && ws.readyState === WebSocket.OPEN && socketAuthenticatedRef.current) {
175
244
  try {
176
245
  ws.send(ttydInputFrame(data));
177
246
  } catch {
178
- // socket raced closed — the reconnect effect will re-establish.
247
+ socketFailedRef.current = true;
248
+ setStatus("error");
249
+ closeSocket(ws);
250
+ }
251
+ } else if (transport === "pty-ws" && !socketFailedRef.current) {
252
+ const nextSize = pendingInputRef.current.length + data.length;
253
+ if (nextSize > MAX_PENDING_TERMINAL_INPUT_CODE_UNITS) {
254
+ pendingInputRef.current = "";
255
+ socketFailedRef.current = true;
256
+ setStatus("error");
257
+ closeSocket(ws);
258
+ return;
179
259
  }
260
+ pendingInputRef.current += data;
180
261
  }
181
262
  };
182
263
  const resize = (cols: number, rows: number) => {
183
264
  if (cols <= 0 || rows <= 0) return;
184
265
  sizeRef.current = { cols, rows };
185
266
  const ws = wsRef.current;
186
- if (ws && ws.readyState === WebSocket.OPEN) {
267
+ if (ws && ws.readyState === WebSocket.OPEN && socketAuthenticatedRef.current) {
187
268
  try {
188
269
  ws.send(ttydResizeFrame(cols, rows));
189
270
  } catch {
@@ -194,14 +275,12 @@ export function useTerminalStream(options: UseTerminalStreamOptions): UseTermina
194
275
  const disconnect = () => {
195
276
  const ws = wsRef.current;
196
277
  wsRef.current = null;
197
- if (ws) {
198
- try {
199
- ws.close();
200
- } catch {
201
- // ignore
202
- }
203
- }
278
+ pendingInputRef.current = "";
279
+ socketFailedRef.current = false;
280
+ socketAuthenticatedRef.current = false;
281
+ setStatus("closed");
282
+ closeSocket(ws);
204
283
  };
205
284
  return { connected: status === "open", status, write, resize, disconnect };
206
- }, [status]);
285
+ }, [status, transport]);
207
286
  }
@@ -0,0 +1,28 @@
1
+ import { useMemo } from "react";
2
+ import type { VideoArtifactPlaybackLoader } from "../timeline";
3
+ import { useOpenGeni, type ClientOverride } from "../session-context";
4
+
5
+ /**
6
+ * Bind the current public SDK client/workspace to MessageTimeline's renewable
7
+ * video-source boundary. Proxy hosts may omit the method until they support
8
+ * retained video; the failure remains local to the player instead of loading
9
+ * any video bytes through React.
10
+ */
11
+ export function useVideoArtifactPlaybackLoader(
12
+ options: ClientOverride = {},
13
+ ): VideoArtifactPlaybackLoader {
14
+ const { client, workspaceId } = useOpenGeni(options);
15
+ return useMemo(
16
+ () => async (artifactId: string, signal?: AbortSignal) => {
17
+ if (typeof client.createVideoArtifactPlaybackSource !== "function") {
18
+ throw new Error("This OpenGeni host does not expose retained-video playback.");
19
+ }
20
+ return await client.createVideoArtifactPlaybackSource(
21
+ workspaceId,
22
+ artifactId,
23
+ signal ? { signal } : {},
24
+ );
25
+ },
26
+ [client, workspaceId],
27
+ );
28
+ }
@@ -1,5 +1,6 @@
1
1
  import { useCallback, useEffect, useRef, useState } from "react";
2
2
  import { useOpenGeni, type ClientOverride } from "../provider";
3
+ import { sandboxAcceptsLiveIo } from "../lib/sandbox-liveness";
3
4
 
4
5
  /**
5
6
  * The wake-on-edit state machine. This hook owns the logic and the UI renders
@@ -73,7 +74,7 @@ export type UseWorkspaceEditResult = {
73
74
  };
74
75
 
75
76
  function isLive(liveness: string | undefined): boolean {
76
- return liveness === "warm" || liveness === "draining";
77
+ return sandboxAcceptsLiveIo(liveness);
77
78
  }
78
79
 
79
80
  /**
package/src/index.ts CHANGED
@@ -9,9 +9,12 @@
9
9
  // container-responsive composers additionally import responsive.css.
10
10
 
11
11
  export type {
12
+ EmbeddedBrowserInteractionClientLike,
13
+ EmbeddedComputerInteractionClientLike,
12
14
  EmbeddedFileAttachmentClientLike,
13
15
  EmbeddedGoalClientLike,
14
16
  EmbeddedHumanInputSessionClientLike,
17
+ EmbeddedInteractionClientLike,
15
18
  EmbeddedSessionEventClientLike,
16
19
  EmbeddedSessionLineageClientLike,
17
20
  EmbeddedSessionMcpApprovalPolicyClientLike,
@@ -22,6 +25,7 @@ export type {
22
25
  export { OpenGeniProvider } from "./provider";
23
26
  export type { OpenGeniProviderProps } from "./provider";
24
27
  export { useOpenGeni, useOpenGeniClient } from "./session-context";
28
+ export { useVideoArtifactPlaybackLoader } from "./hooks/use-video-artifact-playback";
25
29
  export type { ClientOverride, OpenGeniContextValue } from "./session-context";
26
30
  export { usePageLiveActivity } from "./hooks/internal";
27
31
 
@@ -332,6 +336,9 @@ export {
332
336
  } from "./timeline";
333
337
  export type {
334
338
  CreateToolRegistryOptions,
339
+ RetainedArtifactLoader,
340
+ RetainedScreenshotLoader,
341
+ VideoArtifactPlaybackLoader,
335
342
  ToolRegistry,
336
343
  ToolRegistryEntry,
337
344
  ToolRenderer,
@@ -464,6 +471,8 @@ export {
464
471
  } from "./model-policy";
465
472
  export type { LatencyModeId, PickerBillingClass, PickerModelRow } from "./model-policy";
466
473
  export { MessageTimeline, TimelineRow } from "./components/message-timeline";
474
+ export { GeneratedVideoPlayer } from "./components/generated-video-player";
475
+ export type { GeneratedVideoPlayerProps } from "./components/generated-video-player";
467
476
  export type { MessageTimelineProps } from "./components/message-timeline";
468
477
  export { UserMessageBody, userMessageLikelyNeedsDisclosure } from "./components/user-message-body";
469
478
  export type { UserMessageBodyProps } from "./components/user-message-body";
@@ -511,6 +520,7 @@ export {
511
520
  WORKBENCH_TAB_CHANGES,
512
521
  WORKBENCH_TAB_FILES,
513
522
  WORKBENCH_TAB_TERMINAL,
523
+ WORKBENCH_TAB_BROWSER,
514
524
  WORKBENCH_TAB_DESKTOP,
515
525
  WORKBENCH_SURFACES,
516
526
  } from "./components/sandbox-workspace";
@@ -0,0 +1,71 @@
1
+ // @opengeni/react/interaction — standalone Browser/Computer resource UI.
2
+ // Framework hosts may pass a narrow structural client instead of the full provider.
3
+ export type {
4
+ EmbeddedBrowserInteractionClientLike,
5
+ EmbeddedComputerInteractionClientLike,
6
+ EmbeddedInteractionClientLike,
7
+ } from "./client";
8
+ export {
9
+ useEmbeddedBrowserInteraction,
10
+ useEmbeddedComputerInteraction,
11
+ useEmbeddedInteraction,
12
+ } from "./session-context";
13
+ export type {
14
+ EmbeddedBrowserInteractionClientOverride,
15
+ EmbeddedComputerInteractionClientOverride,
16
+ EmbeddedInteractionClientOverride,
17
+ } from "./session-context";
18
+ export { useAttachedBrowsers } from "./hooks/use-attached-browsers";
19
+ export type {
20
+ AttachedBrowsersClient,
21
+ UseAttachedBrowsersOptions,
22
+ UseAttachedBrowsersResult,
23
+ } from "./hooks/use-attached-browsers";
24
+ export { useBrowserSessions } from "./hooks/use-browser-sessions";
25
+ export type {
26
+ BrowserSessionsClient,
27
+ UseBrowserSessionsOptions,
28
+ UseBrowserSessionsResult,
29
+ } from "./hooks/use-browser-sessions";
30
+ export { useBrowserIdentities } from "./hooks/use-browser-identities";
31
+ export type {
32
+ BrowserIdentitiesClient,
33
+ UseBrowserIdentitiesOptions,
34
+ UseBrowserIdentitiesResult,
35
+ } from "./hooks/use-browser-identities";
36
+ export { useBrowserSession } from "./hooks/use-browser-session";
37
+ export type {
38
+ UseBrowserSessionOptions,
39
+ UseBrowserSessionResult,
40
+ } from "./hooks/use-browser-session";
41
+ export { useBrowserFrameStream } from "./hooks/use-browser-frame-stream";
42
+ export type {
43
+ BrowserFrameConnectionState,
44
+ BrowserFrameWebSocket,
45
+ BrowserFrameWebSocketFactory,
46
+ UseBrowserFrameStreamOptions,
47
+ UseBrowserFrameStreamResult,
48
+ } from "./hooks/use-browser-frame-stream";
49
+ export { BrowserViewer } from "./components/browser-viewer";
50
+ export type { BrowserViewerNotification, BrowserViewerProps } from "./components/browser-viewer";
51
+ export { useComputerSessions } from "./hooks/use-computer-sessions";
52
+ export type {
53
+ ComputerSessionsClient,
54
+ UseComputerSessionsOptions,
55
+ UseComputerSessionsResult,
56
+ } from "./hooks/use-computer-sessions";
57
+ export { useComputerSession } from "./hooks/use-computer-session";
58
+ export type {
59
+ UseComputerSessionOptions,
60
+ UseComputerSessionResult,
61
+ } from "./hooks/use-computer-session";
62
+ export { useComputerFrameStream } from "./hooks/use-computer-frame-stream";
63
+ export type {
64
+ ComputerFrameConnectionState,
65
+ ComputerFrameWebSocket,
66
+ ComputerFrameWebSocketFactory,
67
+ UseComputerFrameStreamOptions,
68
+ UseComputerFrameStreamResult,
69
+ } from "./hooks/use-computer-frame-stream";
70
+ export { ComputerViewer } from "./components/computer-viewer";
71
+ export type { ComputerViewerNotification, ComputerViewerProps } from "./components/computer-viewer";
@@ -17,6 +17,10 @@ import { BinaryReader, BinaryWriter } from "@bufbuild/protobuf/wire";
17
17
 
18
18
  /** `StreamKind.STREAM_KIND_DESKTOP` (agent-proto enum value). */
19
19
  export const STREAM_KIND_DESKTOP = 2;
20
+ /** `StreamKind.STREAM_KIND_BROWSER` (agent-proto enum value). */
21
+ export const STREAM_KIND_BROWSER = 3;
22
+ /** `StreamKind.STREAM_KIND_COMPUTER` (agent-proto enum value). */
23
+ export const STREAM_KIND_COMPUTER = 4;
20
24
  /** `StreamRole.STREAM_ROLE_CLIENT` (agent-proto enum value). */
21
25
  export const STREAM_ROLE_CLIENT = 2;
22
26
 
@@ -0,0 +1,11 @@
1
+ /**
2
+ * A sandbox is available for new I/O only after a holder has observed `warm`.
3
+ *
4
+ * `draining` means the holder count is zero and teardown owns the provider
5
+ * instance. The provider may disappear before the durable lease reaches `cold`,
6
+ * so treating `draining` as live races capture/termination. Explicit user intent
7
+ * reacquires a holder; only its subsequent `warm` state re-enables live I/O.
8
+ */
9
+ export function sandboxAcceptsLiveIo(liveness: string | null | undefined): boolean {
10
+ return liveness === "warm";
11
+ }