@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
@@ -28,22 +28,26 @@ export type DesktopViewerProps = {
28
28
  * Defaults to `new WebSocket(url)`. Mirrors `rfbFactory`. */
29
29
  webSocketFactory?: DesktopWebSocketFactory | undefined;
30
30
  /**
31
- * Consent gate for the un-redacted (and possibly shared) pixel plane. Rendered
32
- * BEFORE connecting whenever the desktop requires acknowledgment that hasn't
33
- * been given. Call `onAccept` to record consent (the host wires it to
34
- * `client.acknowledgeStream` + a re-negotiate). When omitted, a default
35
- * banner is shown.
31
+ * @deprecated Desktop acknowledgment is automatic. Retained as an ignored
32
+ * compatibility prop so existing embedders keep compiling.
36
33
  */
37
34
  renderConsentGate?: ((onAccept: () => void, shared: boolean) => ReactNode) | undefined;
38
- /** Called when the default consent gate's accept button is pressed. */
39
- onAcknowledge?: (() => void) | undefined;
40
35
  /**
41
- * Whether the host has the viewer attach engaged (i.e. the user has opted into
42
- * watching the parent's `watchDesktop`/`attachDesktop`). Drives the
43
- * cold-state behaviour: when watching, a cold-but-warmable lease AUTO-WARMS
44
- * (and re-warms when the box drains) instead of dead-ending. When omitted we
45
- * infer it from a recorded consent (the default gate's accept), so the
46
- * component still self-heals after the first acknowledgment.
36
+ * Called once when the viewer mounts. Hosts use this to engage the desktop
37
+ * holder immediately when the Desktop tab opens.
38
+ */
39
+ onActivate?: (() => void) | undefined;
40
+ /**
41
+ * Called automatically when the server requires acknowledgment of the
42
+ * un-redacted (and possibly shared) pixel plane. The viewer shows its ordinary
43
+ * opening state while this completes; no click-through gate is rendered.
44
+ */
45
+ onAcknowledge?: (() => void | Promise<void>) | undefined;
46
+ /**
47
+ * Whether the host has the viewer attach engaged. Drives the cold-state
48
+ * behaviour: when watching, a cold-but-warmable lease AUTO-WARMS (and re-warms
49
+ * when the box drains) instead of dead-ending. Mounting the viewer counts as
50
+ * watching when this is omitted.
47
51
  */
48
52
  watching?: boolean | undefined;
49
53
  /**
@@ -51,8 +55,7 @@ export type DesktopViewerProps = {
51
55
  * already been recorded — only the box drained). The host wires this to
52
56
  * "engage the viewer attach + re-negotiate". Called automatically when a
53
57
  * watched desktop is found cold-but-warmable, and behind the manual retry on
54
- * the warming notice. Distinct from `onAcknowledge`, which is the FIRST,
55
- * consent-bearing warm.
58
+ * the warming notice. Distinct from the automatic acknowledgment request.
56
59
  */
57
60
  onWarm?: (() => void) | undefined;
58
61
  /** Shown when transport is null (headless backend / degraded / disabled). */
@@ -75,13 +78,14 @@ export type DesktopViewerProps = {
75
78
  /**
76
79
  * The derived desktop surface state. A single source of truth so the overlay,
77
80
  * the scrim, the auto-warm effect, and the watchdog all agree. Priority order
78
- * (highest first): viewer-cap → unavailable → consent → warming → connect-failed
79
- * → error → connecting → connected.
81
+ * (highest first): viewer-cap → unavailable → acknowledgment-error → warming →
82
+ * connect-failed → error → opening/connecting → connected.
80
83
  */
81
84
  type DesktopUiState =
82
85
  | "viewer_cap" // 429 — the per-session live-viewer limit is reached.
83
86
  | "unavailable" // genuinely unsupported (headless/policy/os/backend/not-provisioned).
84
- | "consent" // un-redacted/shared plane needs acknowledgment first.
87
+ | "acknowledgment_error" // automatic un-redacted/shared acknowledgment failed.
88
+ | "opening" // acknowledgment or viewer attach is in flight.
85
89
  | "warming" // cold-but-warmable: box not running yet (we auto-warm + spin).
86
90
  | "connecting" // url is live, RFB negotiating/handshaking (we spin, with a watchdog).
87
91
  | "connect_failed" // watchdog fired: url present but never connected.
@@ -122,10 +126,11 @@ function isHardUnavailable(reason: CapabilityUnavailableReason | null): boolean
122
126
  * reason).
123
127
  *
124
128
  * Warming: a cold-but-warmable lease (`reason: "lease_cold"`) is NOT a dead end.
125
- * When the user is watching (consented), the viewer asks the host to (re)warm
126
- * the box (`onWarm`) and shows a "Warming…" spinner; if the box later drains to
127
- * cold it re-warms. Genuinely-unavailable surfaces (headless/policy/os/backend)
128
- * keep a clear, static unavailable notice.
129
+ * When the viewer is mounted, it asks the host to engage the desktop and records
130
+ * any required acknowledgment automatically. A cold-but-warmable box then
131
+ * (re)warms through `onWarm`; if it later drains to cold it re-warms.
132
+ * Genuinely-unavailable surfaces (headless/policy/os/backend) keep a clear,
133
+ * static unavailable notice.
129
134
  */
130
135
  export function DesktopViewer({
131
136
  capability,
@@ -134,7 +139,7 @@ export function DesktopViewer({
134
139
  scaleViewport,
135
140
  rfbFactory,
136
141
  webSocketFactory,
137
- renderConsentGate,
142
+ onActivate,
138
143
  onAcknowledge,
139
144
  watching,
140
145
  onWarm,
@@ -146,7 +151,13 @@ export function DesktopViewer({
146
151
  className,
147
152
  }: DesktopViewerProps) {
148
153
  const containerRef = useRef<HTMLDivElement | null>(null);
149
- const [consented, setConsented] = useState(false);
154
+ const onActivateRef = useRef(onActivate);
155
+ const onAcknowledgeRef = useRef(onAcknowledge);
156
+ onActivateRef.current = onActivate;
157
+ onAcknowledgeRef.current = onAcknowledge;
158
+ const acknowledgmentAttemptedRef = useRef(false);
159
+ const [acknowledgmentError, setAcknowledgmentError] = useState<Error | null>(null);
160
+ const [acknowledgmentRetryNonce, setAcknowledgmentRetryNonce] = useState(0);
150
161
  // Local control state when not externally controlled. The server gate
151
162
  // (`capability.mode`) is the hard ceiling — a read-only deployment can never
152
163
  // be flipped to interactive regardless of this toggle.
@@ -156,16 +167,16 @@ export function DesktopViewer({
156
167
  const wantControl = externallyControlled ? interactive : takeControl;
157
168
  const inControl = Boolean(wantControl) && serverAllowsControl;
158
169
 
159
- // Whether the host has the viewer attach engaged. Prefer the explicit prop;
160
- // fall back to a locally-recorded consent so the component still self-heals
161
- // (auto-warms / re-warms) once the user has accepted the gate at least once.
162
- const isWatching = watching ?? consented;
170
+ // Mounting this component means the Desktop surface is being viewed. A host
171
+ // may still pass its holder state explicitly while the activation callback
172
+ // flips that state on the first mount.
173
+ const isWatching = watching ?? true;
163
174
 
164
175
  // ── Decide the rendered state (before touching the stream hook) ─────────────
165
176
  const transportNull = !capability || capability.transport === null;
166
177
  const reason = capability?.reason ?? null;
167
- const needsAck =
168
- capability?.requiresAcknowledgment === true && capability.acknowledged !== true && !consented;
178
+ const needsAcknowledgment =
179
+ capability?.requiresAcknowledgment === true && capability.acknowledged !== true;
169
180
  // No live address yet on an otherwise-live transport (post-ack, mid-warm).
170
181
  const noLiveAddress = Boolean(capability) && !transportNull && !capability!.url;
171
182
 
@@ -176,9 +187,45 @@ export function DesktopViewer({
176
187
  // Genuinely-unavailable: transport null for a HARD reason (not lease_cold).
177
188
  const hardUnavailable = transportNull && isHardUnavailable(reason);
178
189
 
179
- const accept = () => {
180
- setConsented(true);
181
- onAcknowledge?.();
190
+ // Selecting Desktop mounts the viewer, which is itself the user's intent to
191
+ // open it. Engage the holder immediately — no separate "watch" click.
192
+ useEffect(() => {
193
+ onActivateRef.current?.();
194
+ }, []);
195
+
196
+ // Preserve the server-side acknowledgment/audit contract without making the
197
+ // user click through a disclosure card. One automatic attempt runs for each
198
+ // unacknowledged episode; a genuine request failure gets an explicit retry.
199
+ useEffect(() => {
200
+ if (!needsAcknowledgment) {
201
+ acknowledgmentAttemptedRef.current = false;
202
+ setAcknowledgmentError(null);
203
+ return;
204
+ }
205
+ if (acknowledgmentAttemptedRef.current) return;
206
+ acknowledgmentAttemptedRef.current = true;
207
+ setAcknowledgmentError(null);
208
+ const acknowledge = onAcknowledgeRef.current;
209
+ if (!acknowledge) {
210
+ setAcknowledgmentError(new Error("Desktop acknowledgment is not configured."));
211
+ return;
212
+ }
213
+ let active = true;
214
+ void Promise.resolve()
215
+ .then(() => acknowledge())
216
+ .catch((cause) => {
217
+ if (!active) return;
218
+ setAcknowledgmentError(cause instanceof Error ? cause : new Error(String(cause)));
219
+ });
220
+ return () => {
221
+ active = false;
222
+ };
223
+ }, [needsAcknowledgment, acknowledgmentRetryNonce]);
224
+
225
+ const retryAcknowledgment = () => {
226
+ acknowledgmentAttemptedRef.current = false;
227
+ setAcknowledgmentError(null);
228
+ setAcknowledgmentRetryNonce((nonce) => nonce + 1);
182
229
  };
183
230
 
184
231
  // Release control WITHOUT ever swallowing a key the desktop needs. Esc, and
@@ -218,7 +265,8 @@ export function DesktopViewer({
218
265
  // Do NOT open a socket while the viewer-cap (429) notice is showing — the slot
219
266
  // is already exhausted, so connecting would only burn a doomed attempt (and in
220
267
  // tests leak an unhandled ws error from the never-resolving tunnel URL).
221
- const connectCapability = !transportNull && !needsAck && !viewerCapReached ? capability : null;
268
+ const connectCapability =
269
+ !transportNull && !needsAcknowledgment && !viewerCapReached ? capability : null;
222
270
  const stream = useDesktopStream({
223
271
  capability: connectCapability,
224
272
  containerRef,
@@ -238,21 +286,19 @@ export function DesktopViewer({
238
286
 
239
287
  // ── AUTO-WARM ───────────────────────────────────────────────────────────────
240
288
  // When the user is watching and the desktop is cold-but-warmable, ask the host
241
- // to (re)warm the box. This covers BOTH (a) the user just accepted consent and
289
+ // to (re)warm the box. This covers BOTH (a) the desktop was just opened and
242
290
  // (b) a previously-warm box that drained back to cold under a live viewer. We
243
291
  // fire once per distinct cold episode (keyed on the lease epoch + url) so we
244
292
  // don't spam the attach while a single warm is in flight. `onWarm` is the
245
- // no-re-ack path; if the host only wired `onAcknowledge` (no `onWarm`), the
246
- // first warm still rides consent and subsequent drains fall back to it.
293
+ // no-re-ack path; acknowledgment remains a separate automatic request.
247
294
  const warmKeyRef = useRef<string | null>(null);
248
295
  useEffect(() => {
249
- if (!isWatching || !coldWarmable || needsAck || viewerCapReached) {
296
+ if (!isWatching || !coldWarmable || needsAcknowledgment || viewerCapReached) {
250
297
  // Reset the de-dupe once we leave the cold episode so a future drain warms.
251
298
  if (!coldWarmable) warmKeyRef.current = null;
252
299
  return;
253
300
  }
254
- const warm = onWarm ?? onAcknowledge;
255
- if (!warm) return;
301
+ if (!onWarm) return;
256
302
  // Key the de-dupe on the cell fields available to the viewer: while cold,
257
303
  // `reason`/`url`/`expiresAt` are stable, so we warm exactly once; once the box
258
304
  // warms the cell changes (url+expiresAt minted) and `coldWarmable` flips false,
@@ -260,10 +306,10 @@ export function DesktopViewer({
260
306
  const key = `${capability?.reason ?? ""}:${capability?.url ?? ""}:${capability?.expiresAt ?? ""}`;
261
307
  if (warmKeyRef.current === key) return; // already kicked this episode.
262
308
  warmKeyRef.current = key;
263
- warm();
309
+ onWarm();
264
310
  // capability identity is the trigger; leaseEpoch/expiresAt key the episode.
265
311
  // eslint-disable-next-line react-hooks/exhaustive-deps
266
- }, [isWatching, coldWarmable, needsAck, viewerCapReached, capability, onWarm, onAcknowledge]);
312
+ }, [isWatching, coldWarmable, needsAcknowledgment, viewerCapReached, capability, onWarm]);
267
313
 
268
314
  // ── TAB RE-ATTACH ───────────────────────────────────────────────────────────
269
315
  // The "stuck Watching, never connects" bug: the RFB socket never (re)fires when
@@ -331,7 +377,8 @@ export function DesktopViewer({
331
377
  let uiState: DesktopUiState;
332
378
  if (viewerCapReached) uiState = "viewer_cap";
333
379
  else if (hardUnavailable) uiState = "unavailable";
334
- else if (needsAck && !isWatching) uiState = "consent";
380
+ else if (acknowledgmentError) uiState = "acknowledgment_error";
381
+ else if (needsAcknowledgment) uiState = "opening";
335
382
  else if (coldWarmable) uiState = "warming";
336
383
  else if (connected) uiState = "connected";
337
384
  else if (stream.error) uiState = "error";
@@ -340,7 +387,7 @@ export function DesktopViewer({
340
387
 
341
388
  // An overlay blocks the surface for every state except connected; the toggle
342
389
  // and the live scrim are suppressed under an overlay.
343
- const overlayShown = uiState !== "connected" && uiState !== "connecting";
390
+ const overlayShown = uiState !== "connected" && uiState !== "connecting" && uiState !== "opening";
344
391
  const showToggle = showControlToggle && !overlayShown;
345
392
 
346
393
  let overlay: ReactNode = null;
@@ -358,11 +405,11 @@ export function DesktopViewer({
358
405
  renderUnavailable?.(reason) ??
359
406
  defaultNotice("Desktop unavailable", unavailableCopy(reason));
360
407
  break;
361
- case "consent":
362
- overlay = renderConsentGate ? (
363
- renderConsentGate(accept, capability?.shared ?? false)
364
- ) : (
365
- <DefaultConsentGate shared={capability?.shared ?? false} onAccept={accept} />
408
+ case "acknowledgment_error":
409
+ overlay = defaultNotice(
410
+ "Couldn’t open desktop",
411
+ acknowledgmentError?.message ?? "The desktop acknowledgment failed.",
412
+ retryAcknowledgment,
366
413
  );
367
414
  break;
368
415
  case "warming":
@@ -382,6 +429,7 @@ export function DesktopViewer({
382
429
  reconnect,
383
430
  );
384
431
  break;
432
+ case "opening":
385
433
  case "connecting":
386
434
  case "connected":
387
435
  overlay = null;
@@ -420,7 +468,7 @@ export function DesktopViewer({
420
468
  rectangle before the first framebuffer paints. Only shown in the
421
469
  `connecting` UI-state (every other non-connected state has an explicit
422
470
  overlay). A spinner + transitional copy makes it feel live. */}
423
- {uiState === "connecting" && (
471
+ {(uiState === "opening" || uiState === "connecting") && (
424
472
  <div className="pointer-events-none absolute inset-0 flex flex-col items-center justify-center gap-3 text-og-fg-subtle">
425
473
  <span className="relative flex items-center justify-center">
426
474
  <MonitorIcon className="size-8 opacity-30" strokeWidth={1.5} />
@@ -429,7 +477,9 @@ export function DesktopViewer({
429
477
  strokeWidth={1.25}
430
478
  />
431
479
  </span>
432
- <span className="text-og-sm">Connecting to the desktop…</span>
480
+ <span className="text-og-sm">
481
+ {uiState === "opening" ? "Opening the desktop…" : "Connecting to the desktop…"}
482
+ </span>
433
483
  </div>
434
484
  )}
435
485
 
@@ -630,28 +680,6 @@ function WarmingNotice({ onRetry }: { onRetry?: (() => void) | undefined }) {
630
680
  );
631
681
  }
632
682
 
633
- function DefaultConsentGate({ shared, onAccept }: { shared: boolean; onAccept: () => void }) {
634
- return (
635
- <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">
636
- <div className="mb-1 font-medium">Watch the live desktop?</div>
637
- <p className="mb-3 text-og-sm text-og-fg-subtle">
638
- The desktop pixel stream is <strong>un-redacted</strong> — it can show secrets the agent
639
- prints on screen.
640
- {shared
641
- ? " This sandbox is shared: you'll also see other sessions' agents on the same screen."
642
- : ""}
643
- </p>
644
- <button
645
- type="button"
646
- onClick={onAccept}
647
- 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"
648
- >
649
- I understand — show the desktop
650
- </button>
651
- </div>
652
- );
653
- }
654
-
655
683
  function defaultNotice(title: string, body: string, onRetry?: () => void): ReactNode {
656
684
  return (
657
685
  <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">
@@ -0,0 +1,89 @@
1
+ import type { GeneratedVideoReceipt, VideoArtifactPlaybackSource } from "@opengeni/sdk";
2
+ import { useEffect, useState } from "react";
3
+ import { cn } from "../lib/cn";
4
+ import type { VideoArtifactPlaybackLoader } from "../timeline";
5
+
6
+ export type GeneratedVideoPlayerProps = {
7
+ receipt: GeneratedVideoReceipt;
8
+ loadPlaybackSource: VideoArtifactPlaybackLoader;
9
+ className?: string | undefined;
10
+ label?: string | undefined;
11
+ };
12
+
13
+ type SourceState =
14
+ | { kind: "loading" }
15
+ | { kind: "ready"; source: VideoArtifactPlaybackSource }
16
+ | { kind: "error"; message: string };
17
+
18
+ /** Native, Range-based playback. Video bytes never pass through React or the SDK heap. */
19
+ export function GeneratedVideoPlayer({
20
+ receipt,
21
+ loadPlaybackSource,
22
+ className,
23
+ label = "Generated video",
24
+ }: GeneratedVideoPlayerProps) {
25
+ const [retry, setRetry] = useState(0);
26
+ const [state, setState] = useState<SourceState>({ kind: "loading" });
27
+
28
+ useEffect(() => {
29
+ const controller = new AbortController();
30
+ setState({ kind: "loading" });
31
+ void loadPlaybackSource(receipt.artifact.artifactId, controller.signal).then(
32
+ (source) => {
33
+ if (!controller.signal.aborted) setState({ kind: "ready", source });
34
+ },
35
+ (error: unknown) => {
36
+ if (controller.signal.aborted) return;
37
+ setState({
38
+ kind: "error",
39
+ message: error instanceof Error ? error.message : "Playback is unavailable.",
40
+ });
41
+ },
42
+ );
43
+ return () => controller.abort();
44
+ }, [loadPlaybackSource, receipt.artifact.artifactId, retry]);
45
+
46
+ if (state.kind === "loading") {
47
+ return (
48
+ <div
49
+ aria-label={`Loading ${label.toLowerCase()}`}
50
+ className={cn(
51
+ "aspect-video w-full animate-pulse rounded-og-md bg-og-surface-2 motion-reduce:animate-none",
52
+ className,
53
+ )}
54
+ />
55
+ );
56
+ }
57
+ if (state.kind === "error") {
58
+ return (
59
+ <div
60
+ role="status"
61
+ className={cn(
62
+ "rounded-og-md border border-og-status-failed/30 bg-og-status-failed/5 px-3 py-2 text-og-sm text-og-status-failed",
63
+ className,
64
+ )}
65
+ >
66
+ {state.message}
67
+ </div>
68
+ );
69
+ }
70
+
71
+ return (
72
+ <video
73
+ key={state.source.url}
74
+ aria-label={label}
75
+ className={cn(
76
+ "max-h-[32rem] w-full rounded-og-md bg-black object-contain shadow-sm",
77
+ className,
78
+ )}
79
+ controls
80
+ playsInline
81
+ preload="metadata"
82
+ src={state.source.url}
83
+ onError={() => {
84
+ if (retry === 0) setRetry(1);
85
+ else setState({ kind: "error", message: "Video playback failed." });
86
+ }}
87
+ />
88
+ );
89
+ }
@@ -0,0 +1,2 @@
1
+ export { BrowserViewer } from "./browser-viewer";
2
+ export { ComputerViewer } from "./computer-viewer";
@@ -6,6 +6,7 @@ export const MACHINE_INPUT_META: Record<MachineInputMember["kind"], string> = {
6
6
  agent_message: "Agent update",
7
7
  agent_steer_instruction: "Agent direction",
8
8
  child_terminal_result: "Agent finished",
9
+ media_generation_result: "Video update",
9
10
  };
10
11
 
11
12
  /**
@@ -32,6 +33,8 @@ export function machineInputBatchLabel(members: readonly MachineInputMember[]):
32
33
  return n === 1 ? "Agent update" : `${n} agent updates`;
33
34
  case "agent_steer_instruction":
34
35
  return n === 1 ? "Agent direction" : `${n} agent directions`;
36
+ case "media_generation_result":
37
+ return n === 1 ? "Video ready" : `${n} video updates`;
35
38
  }
36
39
  }
37
40
  const parts = [...counts.entries()].map(([kind, count]) => {