@opengeni/react 0.48.0 → 0.57.0

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 (325) hide show
  1. package/README.md +138 -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 +199 -0
  13. package/dist/artifacts.js.map +1 -0
  14. package/dist/{chunk-NJ7M2B6Z.js → chunk-5UQPNLIO.js} +16 -8
  15. package/dist/{chunk-NJ7M2B6Z.js.map → chunk-5UQPNLIO.js.map} +1 -1
  16. package/dist/{chunk-LHLYWG5R.js → chunk-6KLX2RH5.js} +461 -38
  17. package/dist/chunk-6KLX2RH5.js.map +1 -0
  18. package/dist/chunk-7WCLAEFV.js +2828 -0
  19. package/dist/chunk-7WCLAEFV.js.map +1 -0
  20. package/dist/{chunk-I4MPFDRP.js → chunk-B6WPZSTT.js} +104 -104
  21. package/dist/{chunk-I4MPFDRP.js.map → chunk-B6WPZSTT.js.map} +1 -1
  22. package/dist/{chunk-EKZH6IDG.js → chunk-C5ZPSSS5.js} +111 -1
  23. package/dist/chunk-C5ZPSSS5.js.map +1 -0
  24. package/dist/{chunk-OL6QVXGF.js → chunk-HA46CYQK.js} +152 -8
  25. package/dist/chunk-HA46CYQK.js.map +1 -0
  26. package/dist/chunk-HUIKIDM5.js +2148 -0
  27. package/dist/chunk-HUIKIDM5.js.map +1 -0
  28. package/dist/{chunk-FWXGSNXT.js → chunk-IGC6A2ZI.js} +585 -34
  29. package/dist/chunk-IGC6A2ZI.js.map +1 -0
  30. package/dist/chunk-JVYQRRC4.js +53 -0
  31. package/dist/chunk-JVYQRRC4.js.map +1 -0
  32. package/dist/chunk-KCGXPP5V.js +157 -0
  33. package/dist/chunk-KCGXPP5V.js.map +1 -0
  34. package/dist/chunk-LLOLUHVG.js +5937 -0
  35. package/dist/chunk-LLOLUHVG.js.map +1 -0
  36. package/dist/{chunk-WYA65Y3F.js → chunk-MKPIAGVL.js} +13 -3
  37. package/dist/chunk-MKPIAGVL.js.map +1 -0
  38. package/dist/chunk-PEI6RANI.js +3234 -0
  39. package/dist/chunk-PEI6RANI.js.map +1 -0
  40. package/dist/chunk-R3AZOAB5.js +933 -0
  41. package/dist/chunk-R3AZOAB5.js.map +1 -0
  42. package/dist/{chunk-O4VXOBGF.js → chunk-WSK7G5LE.js} +70 -2
  43. package/dist/chunk-WSK7G5LE.js.map +1 -0
  44. package/dist/{chunk-23EJ676W.js → chunk-XBZWOM2K.js} +9 -4
  45. package/dist/chunk-XBZWOM2K.js.map +1 -0
  46. package/dist/{chunk-XCNTXWBB.js → chunk-Y7OEDUJ5.js} +856 -338
  47. package/dist/chunk-Y7OEDUJ5.js.map +1 -0
  48. package/dist/client.d.ts +10 -2
  49. package/dist/clipboard.d.ts +1 -0
  50. package/dist/clipboard.js +7 -0
  51. package/dist/clipboard.js.map +1 -0
  52. package/dist/commands/index.d.ts +3 -3
  53. package/dist/commands/registry.d.ts +1 -1
  54. package/dist/commands/types.d.ts +1 -1
  55. package/dist/components/approval-surface.d.ts +2 -2
  56. package/dist/components/artifacts/artifact-surface.d.ts +18 -0
  57. package/dist/components/artifacts/document-editor.d.ts +172 -0
  58. package/dist/components/artifacts/editable-artifact-ui.d.ts +13 -0
  59. package/dist/components/artifacts/editable-artifact-workbench.d.ts +49 -0
  60. package/dist/components/artifacts/editable-document.d.ts +24 -0
  61. package/dist/components/artifacts/editable-presentation.d.ts +24 -0
  62. package/dist/components/artifacts/editable-spreadsheet.d.ts +32 -0
  63. package/dist/components/artifacts/index.d.ts +9 -0
  64. package/dist/components/artifacts/presentation-editor.d.ts +297 -0
  65. package/dist/components/artifacts/published-html-artifact-frame.d.ts +14 -0
  66. package/dist/components/artifacts/spreadsheet-canvas.d.ts +120 -0
  67. package/dist/components/artifacts/spreadsheet-grid.d.ts +113 -0
  68. package/dist/components/browser-input.d.ts +7 -0
  69. package/dist/components/browser-viewer.d.ts +30 -0
  70. package/dist/components/chat-composer.d.ts +8 -8
  71. package/dist/components/code-editor.d.ts +25 -3
  72. package/dist/components/command-palette.d.ts +2 -2
  73. package/dist/components/composer-transcription-control.d.ts +2 -2
  74. package/dist/components/composer.d.ts +17 -17
  75. package/dist/components/computer-viewer.d.ts +24 -0
  76. package/dist/components/copy-button.d.ts +2 -2
  77. package/dist/components/desktop-viewer.d.ts +50 -22
  78. package/dist/components/diff-view.d.ts +1 -1
  79. package/dist/components/enrollment-consent.d.ts +1 -1
  80. package/dist/components/enrollment-device-flow.d.ts +1 -1
  81. package/dist/components/file-browser.d.ts +2 -2
  82. package/dist/components/fleet-tile.d.ts +1 -1
  83. package/dist/components/generated-video-player.d.ts +10 -0
  84. package/dist/components/human-input-form.d.ts +1 -1
  85. package/dist/components/human-input-surface.d.ts +2 -2
  86. package/dist/components/interaction-intervention-banner.d.ts +11 -0
  87. package/dist/components/interactive-workbench-viewers.d.ts +2 -0
  88. package/dist/components/machine-card.d.ts +2 -2
  89. package/dist/components/machine-dock-bar.d.ts +3 -3
  90. package/dist/components/machine-health-pill.d.ts +2 -2
  91. package/dist/components/machine-input-display.d.ts +1 -1
  92. package/dist/components/machine-metrics.d.ts +2 -2
  93. package/dist/components/machine-status-pill.d.ts +4 -4
  94. package/dist/components/machines/health.d.ts +1 -1
  95. package/dist/components/machines/machine-detail.d.ts +3 -3
  96. package/dist/components/machines/metric-history-chart.d.ts +1 -1
  97. package/dist/components/machines/metric-sparkline.d.ts +2 -2
  98. package/dist/components/machines/series.d.ts +2 -2
  99. package/dist/components/machines-dashboard.d.ts +2 -2
  100. package/dist/components/markdown.d.ts +1 -1
  101. package/dist/components/message-timeline.d.ts +12 -5
  102. package/dist/components/model-picker.d.ts +2 -2
  103. package/dist/components/model-policy-picker.d.ts +7 -7
  104. package/dist/components/pierre-diff.d.ts +1 -1
  105. package/dist/components/pierre-file.d.ts +1 -1
  106. package/dist/components/queue-draft-policy.d.ts +1 -1
  107. package/dist/components/queue-surface-implementation.d.ts +3 -3
  108. package/dist/components/queue-surface-state.d.ts +4 -4
  109. package/dist/components/queue-surface.d.ts +5 -5
  110. package/dist/components/sandbox-files.d.ts +3 -3
  111. package/dist/components/sandbox-terminal.d.ts +24 -4
  112. package/dist/components/sandbox-workspace.d.ts +28 -9
  113. package/dist/components/session-chrome.d.ts +4 -4
  114. package/dist/components/session-status.d.ts +2 -2
  115. package/dist/components/timeline-annotation-selection.d.ts +9 -0
  116. package/dist/components/timeline-annotations-dialog.d.ts +16 -0
  117. package/dist/components/timeline-annotations.d.ts +13 -0
  118. package/dist/components/tooltip.d.ts +4 -4
  119. package/dist/components/user-message-body.d.ts +2 -2
  120. package/dist/components/workbench-changes.d.ts +2 -2
  121. package/dist/components/workspace-dock.d.ts +1 -1
  122. package/dist/composer.d.ts +11 -11
  123. package/dist/composer.js +7 -7
  124. package/dist/hooks/internal.d.ts +1 -1
  125. package/dist/hooks/use-attached-browsers.d.ts +21 -0
  126. package/dist/hooks/use-auth-runs.d.ts +32 -0
  127. package/dist/hooks/use-available-models.d.ts +2 -2
  128. package/dist/hooks/use-billing-usage.d.ts +1 -1
  129. package/dist/hooks/use-browser-downloads.d.ts +25 -0
  130. package/dist/hooks/use-browser-frame-stream.d.ts +28 -0
  131. package/dist/hooks/use-browser-identities.d.ts +30 -0
  132. package/dist/hooks/use-browser-session.d.ts +29 -0
  133. package/dist/hooks/use-browser-sessions.d.ts +32 -0
  134. package/dist/hooks/use-channels.d.ts +26 -0
  135. package/dist/hooks/use-codex-accounts.d.ts +2 -2
  136. package/dist/hooks/use-composer.d.ts +36 -9
  137. package/dist/hooks/use-computer-frame-stream.d.ts +24 -0
  138. package/dist/hooks/use-computer-session.d.ts +29 -0
  139. package/dist/hooks/use-computer-sessions.d.ts +28 -0
  140. package/dist/hooks/use-desktop-stream.d.ts +5 -1
  141. package/dist/hooks/use-environments.d.ts +1 -1
  142. package/dist/hooks/use-file-attachments.d.ts +1 -1
  143. package/dist/hooks/use-goal.d.ts +2 -2
  144. package/dist/hooks/use-human-input.d.ts +2 -2
  145. package/dist/hooks/use-interaction-interventions.d.ts +27 -0
  146. package/dist/hooks/use-interaction-invalidation.d.ts +19 -0
  147. package/dist/hooks/use-last-started-turn-policy.d.ts +2 -2
  148. package/dist/hooks/use-machine-chip.d.ts +2 -2
  149. package/dist/hooks/use-machines.d.ts +4 -4
  150. package/dist/hooks/use-network-routes.d.ts +25 -0
  151. package/dist/hooks/use-packs.d.ts +7 -2
  152. package/dist/hooks/use-rigs.d.ts +1 -1
  153. package/dist/hooks/use-sandbox-files.d.ts +6 -4
  154. package/dist/hooks/use-sandbox-git.d.ts +6 -5
  155. package/dist/hooks/use-sandbox-terminal.d.ts +1 -1
  156. package/dist/hooks/use-scheduled-tasks.d.ts +1 -1
  157. package/dist/hooks/use-session-capabilities.d.ts +5 -4
  158. package/dist/hooks/use-session-control.d.ts +1 -1
  159. package/dist/hooks/use-session-events.d.ts +2 -2
  160. package/dist/hooks/use-session-lineage.d.ts +1 -1
  161. package/dist/hooks/use-session-mcp-approval-policy.d.ts +2 -2
  162. package/dist/hooks/use-session.d.ts +2 -2
  163. package/dist/hooks/use-site-auth-connections.d.ts +25 -0
  164. package/dist/hooks/use-slash-commands.d.ts +1 -1
  165. package/dist/hooks/use-supergrok-accounts.d.ts +26 -0
  166. package/dist/hooks/use-terminal-stream.d.ts +11 -2
  167. package/dist/hooks/use-turn-queue.d.ts +2 -2
  168. package/dist/hooks/use-video-artifact-playback.d.ts +9 -0
  169. package/dist/hooks/use-voice-input.d.ts +1 -1
  170. package/dist/hooks/use-workspace-capture.d.ts +1 -1
  171. package/dist/hooks/use-workspace-edit.d.ts +1 -1
  172. package/dist/hooks/use-workspace-sessions.d.ts +1 -1
  173. package/dist/hooks/use-workspaces.d.ts +1 -1
  174. package/dist/index.d.ts +162 -155
  175. package/dist/index.js +1822 -1703
  176. package/dist/index.js.map +1 -1
  177. package/dist/interaction.d.ts +33 -0
  178. package/dist/interaction.js +550 -0
  179. package/dist/interaction.js.map +1 -0
  180. package/dist/interactive-workbench-viewers-TOHBXJEE.js +15 -0
  181. package/dist/interactive-workbench-viewers-TOHBXJEE.js.map +1 -0
  182. package/dist/lib/host-platform.d.ts +5 -0
  183. package/dist/lib/relay-wire.d.ts +24 -1
  184. package/dist/lib/sandbox-liveness.d.ts +9 -0
  185. package/dist/lib/terminal-capability.d.ts +5 -0
  186. package/dist/lib/xterm-theme.d.ts +1 -1
  187. package/dist/machines.d.ts +30 -30
  188. package/dist/machines.js +2 -2
  189. package/dist/model-policy.d.ts +1 -1
  190. package/dist/model-policy.js +1 -1
  191. package/dist/provider.d.ts +6 -5
  192. package/dist/{queue-surface-implementation-U5JFG7HT.js → queue-surface-implementation-2KVJTYXH.js} +100 -48
  193. package/dist/queue-surface-implementation-2KVJTYXH.js.map +1 -0
  194. package/dist/realtime/dropdown-menu.d.ts +8 -8
  195. package/dist/realtime/realtime-control.d.ts +8 -8
  196. package/dist/realtime.d.ts +4 -4
  197. package/dist/realtime.js +38 -11
  198. package/dist/realtime.js.map +1 -1
  199. package/dist/session-context.d.ts +28 -2
  200. package/dist/session-ui.d.ts +14 -14
  201. package/dist/session-ui.js +5 -4
  202. package/dist/session.d.ts +28 -28
  203. package/dist/session.js +10 -10
  204. package/dist/timeline/activity-rail.d.ts +5 -3
  205. package/dist/timeline/disclosure-context.d.ts +1 -1
  206. package/dist/timeline/fleet-decision-projection.d.ts +1 -1
  207. package/dist/timeline/fleet-decision-row.d.ts +3 -3
  208. package/dist/timeline/index.d.ts +19 -19
  209. package/dist/timeline/parsers.d.ts +4 -1
  210. package/dist/timeline/projection.d.ts +3 -3
  211. package/dist/timeline/registry.d.ts +8 -2
  212. package/dist/timeline/screenshot-lightbox.d.ts +2 -2
  213. package/dist/timeline/shared.d.ts +21 -10
  214. package/dist/timeline/tool-diff.d.ts +2 -2
  215. package/dist/timeline/tool-renderers.d.ts +1 -1
  216. package/dist/timeline/turn-summary.d.ts +3 -3
  217. package/dist/timeline/types.d.ts +44 -3
  218. package/dist/timeline-annotation-selection-3L7LHGG2.js +187 -0
  219. package/dist/timeline-annotation-selection-3L7LHGG2.js.map +1 -0
  220. package/dist/timeline-annotations-dialog-U7EVLR75.js +196 -0
  221. package/dist/timeline-annotations-dialog-U7EVLR75.js.map +1 -0
  222. package/package.json +36 -7
  223. package/src/artifacts-document.ts +31 -0
  224. package/src/artifacts-presentation.ts +36 -0
  225. package/src/artifacts-spreadsheet.ts +21 -0
  226. package/src/artifacts.ts +89 -0
  227. package/src/client.ts +157 -1
  228. package/src/clipboard.ts +2 -0
  229. package/src/components/artifacts/artifact-surface.tsx +93 -0
  230. package/src/components/artifacts/document-editor.tsx +2255 -0
  231. package/src/components/artifacts/editable-artifact-ui.tsx +89 -0
  232. package/src/components/artifacts/editable-artifact-workbench.tsx +203 -0
  233. package/src/components/artifacts/editable-document.tsx +657 -0
  234. package/src/components/artifacts/editable-presentation.tsx +1020 -0
  235. package/src/components/artifacts/editable-spreadsheet.tsx +733 -0
  236. package/src/components/artifacts/index.ts +104 -0
  237. package/src/components/artifacts/presentation-editor.tsx +3148 -0
  238. package/src/components/artifacts/published-html-artifact-frame.tsx +38 -0
  239. package/src/components/artifacts/spreadsheet-canvas.ts +799 -0
  240. package/src/components/artifacts/spreadsheet-grid.tsx +2129 -0
  241. package/src/components/browser-input.ts +65 -0
  242. package/src/components/browser-viewer.tsx +2702 -0
  243. package/src/components/chat-composer.tsx +13 -0
  244. package/src/components/code-editor.tsx +89 -30
  245. package/src/components/composer-transcription-control.tsx +10 -6
  246. package/src/components/computer-viewer.tsx +1636 -0
  247. package/src/components/desktop-viewer.tsx +215 -74
  248. package/src/components/generated-video-player.tsx +89 -0
  249. package/src/components/interaction-intervention-banner.tsx +99 -0
  250. package/src/components/interactive-workbench-viewers.ts +2 -0
  251. package/src/components/machine-input-display.ts +3 -0
  252. package/src/components/machines/machine-detail.tsx +1 -1
  253. package/src/components/message-timeline.tsx +289 -32
  254. package/src/components/model-policy-picker.tsx +15 -0
  255. package/src/components/queue-surface-implementation.tsx +116 -60
  256. package/src/components/sandbox-files.tsx +2 -2
  257. package/src/components/sandbox-terminal.tsx +128 -46
  258. package/src/components/sandbox-workspace.tsx +256 -90
  259. package/src/components/session-chrome.tsx +53 -13
  260. package/src/components/timeline-annotation-selection.tsx +221 -0
  261. package/src/components/timeline-annotations-dialog.tsx +213 -0
  262. package/src/components/timeline-annotations.tsx +84 -0
  263. package/src/components/workbench-changes.tsx +188 -31
  264. package/src/components/workspace-dock.tsx +59 -5
  265. package/src/hooks/use-attached-browsers.ts +154 -0
  266. package/src/hooks/use-auth-runs.ts +278 -0
  267. package/src/hooks/use-browser-downloads.ts +218 -0
  268. package/src/hooks/use-browser-frame-stream.ts +425 -0
  269. package/src/hooks/use-browser-identities.ts +266 -0
  270. package/src/hooks/use-browser-session.ts +543 -0
  271. package/src/hooks/use-browser-sessions.ts +286 -0
  272. package/src/hooks/use-channels.ts +99 -0
  273. package/src/hooks/use-composer.ts +769 -24
  274. package/src/hooks/use-computer-frame-stream.ts +434 -0
  275. package/src/hooks/use-computer-session.ts +462 -0
  276. package/src/hooks/use-computer-sessions.ts +251 -0
  277. package/src/hooks/use-desktop-stream.ts +39 -7
  278. package/src/hooks/use-interaction-interventions.ts +242 -0
  279. package/src/hooks/use-interaction-invalidation.ts +66 -0
  280. package/src/hooks/use-machine-chip.ts +15 -3
  281. package/src/hooks/use-machines.ts +33 -3
  282. package/src/hooks/use-network-routes.ts +214 -0
  283. package/src/hooks/use-packs.ts +51 -0
  284. package/src/hooks/use-sandbox-files.ts +98 -55
  285. package/src/hooks/use-sandbox-git.ts +93 -38
  286. package/src/hooks/use-sandbox-terminal.ts +3 -2
  287. package/src/hooks/use-session-capabilities.ts +59 -46
  288. package/src/hooks/use-site-auth-connections.ts +226 -0
  289. package/src/hooks/use-supergrok-accounts.ts +81 -0
  290. package/src/hooks/use-terminal-stream.ts +359 -47
  291. package/src/hooks/use-video-artifact-playback.ts +28 -0
  292. package/src/hooks/use-workspace-edit.ts +2 -1
  293. package/src/index.ts +18 -0
  294. package/src/interaction.ts +101 -0
  295. package/src/lib/host-platform.ts +10 -0
  296. package/src/lib/relay-wire.ts +47 -4
  297. package/src/lib/sandbox-liveness.ts +11 -0
  298. package/src/lib/terminal-capability.ts +10 -0
  299. package/src/model-policy.ts +19 -4
  300. package/src/provider.tsx +133 -1
  301. package/src/realtime/realtime-control.tsx +34 -6
  302. package/src/session-context.ts +156 -1
  303. package/src/timeline/activity-rail.tsx +25 -3
  304. package/src/timeline/index.ts +3 -0
  305. package/src/timeline/parsers.ts +14 -1
  306. package/src/timeline/projection.ts +269 -14
  307. package/src/timeline/registry.ts +13 -1
  308. package/src/timeline/screenshot-lightbox.tsx +19 -9
  309. package/src/timeline/shared.tsx +56 -8
  310. package/src/timeline/tool-renderers.tsx +303 -50
  311. package/src/timeline/types.ts +57 -3
  312. package/styles/compiled.css +312 -21
  313. package/styles/index.css +15 -0
  314. package/styles/responsive.css +13 -1
  315. package/dist/chunk-23EJ676W.js.map +0 -1
  316. package/dist/chunk-EKZH6IDG.js.map +0 -1
  317. package/dist/chunk-FWXGSNXT.js.map +0 -1
  318. package/dist/chunk-LHLYWG5R.js.map +0 -1
  319. package/dist/chunk-O4VXOBGF.js.map +0 -1
  320. package/dist/chunk-OL6QVXGF.js.map +0 -1
  321. package/dist/chunk-WYA65Y3F.js.map +0 -1
  322. package/dist/chunk-XCNTXWBB.js.map +0 -1
  323. package/dist/fleet-decision-projection-SLNZMS42.js +0 -241
  324. package/dist/fleet-decision-projection-SLNZMS42.js.map +0 -1
  325. package/dist/queue-surface-implementation-U5JFG7HT.js.map +0 -1
@@ -6,7 +6,7 @@ import {
6
6
  type DesktopRfbLike,
7
7
  type DesktopStreamCapability,
8
8
  } from "@opengeni/sdk";
9
- import { type RefObject, useEffect, useRef, useState } from "react";
9
+ import { type RefObject, useCallback, useEffect, useRef, useState } from "react";
10
10
  import { type DesktopWebSocketFactory, useRelayFrameStream } from "./use-relay-frame-stream";
11
11
 
12
12
  export type UseDesktopStreamOptions = {
@@ -19,6 +19,8 @@ export type UseDesktopStreamOptions = {
19
19
  scaleViewport?: boolean | undefined;
20
20
  /** Custom RFB factory (tests / a WebRTC swap). Defaults to a lazy @novnc/novnc. */
21
21
  rfbFactory?: DesktopRfbFactory | undefined;
22
+ /** Additional WebSocket protocols for an authenticated placement proxy. */
23
+ webSocketProtocols?: string[] | undefined;
22
24
  /** Custom socket factory for the `relay-frames` transport (tests). Defaults to
23
25
  * `new WebSocket(url)`. Mirrors `rfbFactory` for the frame renderer. */
24
26
  webSocketFactory?: DesktopWebSocketFactory | undefined;
@@ -29,6 +31,8 @@ export type UseDesktopStreamResult = {
29
31
  error: Error | null;
30
32
  /** Manual reconnect (e.g. after a securityfailure once a fresh URL arrives). */
31
33
  reconnect: () => void;
34
+ /** Synchronous input on the already-open RFB authority. */
35
+ sendKey?: ((keysym: number, code: string, down?: boolean) => boolean) | undefined;
32
36
  };
33
37
 
34
38
  /** Lazy-load @novnc/novnc's RFB as the default factory. Imported inside the
@@ -44,7 +48,10 @@ async function defaultRfbFactory(): Promise<DesktopRfbFactory> {
44
48
  default: new (
45
49
  t: HTMLElement,
46
50
  u: string,
47
- o: { credentials?: { password?: string | undefined } | undefined },
51
+ o: {
52
+ credentials?: { password?: string | undefined } | undefined;
53
+ wsProtocols?: string[] | undefined;
54
+ },
48
55
  ) => DesktopRfbLike;
49
56
  };
50
57
  const RFB = mod.default;
@@ -71,8 +78,15 @@ async function defaultRfbFactory(): Promise<DesktopRfbFactory> {
71
78
  * way, so `DesktopViewer` is transport-agnostic.
72
79
  */
73
80
  export function useDesktopStream(options: UseDesktopStreamOptions): UseDesktopStreamResult {
74
- const { capability, containerRef, interactive, scaleViewport, rfbFactory, webSocketFactory } =
75
- options;
81
+ const {
82
+ capability,
83
+ containerRef,
84
+ interactive,
85
+ scaleViewport,
86
+ rfbFactory,
87
+ webSocketProtocols,
88
+ webSocketFactory,
89
+ } = options;
76
90
 
77
91
  // The self-hosted PNG-frame path. Always invoked (rules of hooks); dormant
78
92
  // unless `transport === "relay-frames"`, in which case it owns the surface.
@@ -91,6 +105,12 @@ export function useDesktopStream(options: UseDesktopStreamOptions): UseDesktopSt
91
105
  };
92
106
 
93
107
  const reconnect = () => setNonce((n) => n + 1);
108
+ const sendKey = useCallback((keysym: number, code: string, down?: boolean): boolean => {
109
+ const rfb = rfbRef.current;
110
+ if (!rfb?.sendKey || rfb.viewOnly || stateRef.current !== "connected") return false;
111
+ rfb.sendKey(keysym, code, down);
112
+ return true;
113
+ }, []);
94
114
 
95
115
  const url = capability?.url ?? null;
96
116
  // The credential is keyed into the connect effect so a token rotation (new
@@ -99,6 +119,14 @@ export function useDesktopStream(options: UseDesktopStreamOptions): UseDesktopSt
99
119
  const token = capability?.token ?? null;
100
120
  const transport = capability?.transport ?? null;
101
121
  const mode = capability?.mode ?? "read-only";
122
+ // For direct sandbox RFB, authorization is carried in WebSocket
123
+ // subprotocols rather than `capability.token`. The view grant rotates before
124
+ // expiry while the URL stays stable, so the protocol credential is a real
125
+ // connection boundary: keeping the old socket leaves the viewer black once
126
+ // browserd expires it. Key the effect on the primitive value (not the caller's
127
+ // array identity) so credential rotation reconnects exactly once without
128
+ // churning on ordinary renders.
129
+ const webSocketProtocolsKey = webSocketProtocols?.join("\u0000") ?? "";
102
130
 
103
131
  // The live RFB handle. Holding it in a ref lets us flip view-only (take
104
132
  // control / return control) and re-scale on the OPEN connection instead of
@@ -113,10 +141,12 @@ export function useDesktopStream(options: UseDesktopStreamOptions): UseDesktopSt
113
141
  const scaleViewportRef = useRef(scaleViewport);
114
142
  const modeRef = useRef(mode);
115
143
  const rfbFactoryRef = useRef(rfbFactory);
144
+ const webSocketProtocolsRef = useRef(webSocketProtocols);
116
145
  interactiveRef.current = interactive;
117
146
  scaleViewportRef.current = scaleViewport;
118
147
  modeRef.current = mode;
119
148
  rfbFactoryRef.current = rfbFactory;
149
+ webSocketProtocolsRef.current = webSocketProtocols;
120
150
 
121
151
  // read-only is forced when the server says so OR the caller didn't opt in.
122
152
  const viewOnlyFor = (m: string, want: boolean | undefined) => m === "read-only" || !want;
@@ -164,6 +194,7 @@ export function useDesktopStream(options: UseDesktopStreamOptions): UseDesktopSt
164
194
  setBoth(nextDesktopState(stateRef.current, { type: "negotiated" }));
165
195
  rfb = factory(container, socketUrl, {
166
196
  credentials: token ? { password: token } : undefined,
197
+ ...(webSocketProtocolsRef.current ? { wsProtocols: webSocketProtocolsRef.current } : {}),
167
198
  });
168
199
  rfb.viewOnly = viewOnlyFor(modeRef.current, interactiveRef.current);
169
200
  // Fit-to-panel: SCALE the 1280x800 framebuffer down to the container
@@ -200,11 +231,12 @@ export function useDesktopStream(options: UseDesktopStreamOptions): UseDesktopSt
200
231
  }
201
232
  };
202
233
  // ONLY a real transport change reconnects: a fresh url (rotation), a new
203
- // credential, the transport flipping, or an explicit manual reconnect
234
+ // credential (RFB password OR bearer subprotocol), the transport flipping,
235
+ // or an explicit manual reconnect
204
236
  // (`nonce`). `interactive`/`scaleViewport`/`mode`/`rfbFactory` are read via
205
237
  // refs and applied live below — they must never re-open the socket.
206
238
  // eslint-disable-next-line react-hooks/exhaustive-deps
207
- }, [url, token, transport, nonce]);
239
+ }, [url, token, transport, webSocketProtocolsKey, nonce]);
208
240
 
209
241
  // Apply take-control / return-control to the OPEN connection in place. noVNC
210
242
  // honours `viewOnly` live, so flipping it neither blinks the surface nor drops
@@ -236,5 +268,5 @@ export function useDesktopStream(options: UseDesktopStreamOptions): UseDesktopSt
236
268
  // drives; for `vnc-ws` (and everything else) the noVNC path drives while the
237
269
  // frame renderer stays idle. Same public shape either way.
238
270
  if (transport === "relay-frames") return frameStream;
239
- return { state, error, reconnect };
271
+ return { state, error, reconnect, sendKey };
240
272
  }
@@ -0,0 +1,242 @@
1
+ import type {
2
+ CreateInteractionInterventionRequest,
3
+ InteractionIntervention,
4
+ InteractionInterventionMutationResponse,
5
+ ResolveInteractionInterventionRequest,
6
+ } from "@opengeni/sdk/interaction";
7
+ import { useCallback, useEffect, useRef, useState } from "react";
8
+ import type { EmbeddedInterventionClientLike } from "../client";
9
+ import {
10
+ type EmbeddedInterventionClientOverride,
11
+ useEmbeddedInterventions,
12
+ } from "../session-context";
13
+ import { useInteractionInvalidation } from "./use-interaction-invalidation";
14
+
15
+ export type UseInteractionInterventionsOptions = EmbeddedInterventionClientOverride & {
16
+ enabled?: boolean | undefined;
17
+ resourceKind?: "browser_session" | "computer_session" | undefined;
18
+ resourceId?: string | null | undefined;
19
+ includeSettled?: boolean | undefined;
20
+ pollIntervalMs?: number | undefined;
21
+ };
22
+
23
+ export type UseInteractionInterventionsResult = {
24
+ interventions: InteractionIntervention[];
25
+ loading: boolean;
26
+ refreshing: boolean;
27
+ mutating: boolean;
28
+ error: Error | null;
29
+ refresh: () => Promise<void>;
30
+ create: (
31
+ request: Omit<CreateInteractionInterventionRequest, "operationId"> & {
32
+ operationId?: string;
33
+ },
34
+ ) => Promise<InteractionInterventionMutationResponse>;
35
+ resolve: (
36
+ interventionId: string,
37
+ request: Omit<ResolveInteractionInterventionRequest, "operationId"> & {
38
+ operationId?: string;
39
+ },
40
+ ) => Promise<InteractionInterventionMutationResponse>;
41
+ };
42
+
43
+ /** Human-action bridge shared by Browser and Computer surfaces. */
44
+ export function useInteractionInterventions(
45
+ options: UseInteractionInterventionsOptions = {},
46
+ ): UseInteractionInterventionsResult {
47
+ const { client, workspaceId, workspaceInteractionEvent, workspaceInteractionConnectionState } =
48
+ useEmbeddedInterventions(options);
49
+ const enabled = (options.enabled ?? true) && options.resourceId !== null;
50
+ const resourceKind = options.resourceKind;
51
+ const resourceId = options.resourceId ?? undefined;
52
+ const includeSettled = options.includeSettled ?? false;
53
+ const key = `${workspaceId}:${resourceKind ?? "*"}:${resourceId ?? "*"}:${includeSettled}`;
54
+ const [state, setState] = useState(() => emptyState(key, enabled));
55
+ const [mutationCount, setMutationCount] = useState(0);
56
+ const requestRef = useRef<{ id: number; controller: AbortController | null }>({
57
+ id: 0,
58
+ controller: null,
59
+ });
60
+ const mountedRef = useRef(true);
61
+ const visible = state.key === key ? state : emptyState(key, enabled);
62
+
63
+ const cancelLoad = useCallback(() => {
64
+ requestRef.current.controller?.abort();
65
+ requestRef.current = { id: requestRef.current.id + 1, controller: null };
66
+ }, []);
67
+
68
+ const load = useCallback(
69
+ async (foreground: boolean, replace = true): Promise<void> => {
70
+ if (!enabled) return;
71
+ if (requestRef.current.controller && !replace) return;
72
+ const id = requestRef.current.id + 1;
73
+ requestRef.current.controller?.abort();
74
+ const controller = new AbortController();
75
+ requestRef.current = { id, controller };
76
+ if (foreground || replace) {
77
+ setState((current) => ({
78
+ ...(current.key === key ? current : emptyState(key, true)),
79
+ loading: foreground,
80
+ refreshing: !foreground,
81
+ error: foreground ? null : current.error,
82
+ }));
83
+ }
84
+ try {
85
+ const response = await client.listInteractionInterventions(workspaceId, {
86
+ ...(resourceKind ? { resourceKind } : {}),
87
+ ...(resourceId ? { resourceId } : {}),
88
+ includeSettled,
89
+ signal: controller.signal,
90
+ });
91
+ if (!mountedRef.current || requestRef.current.id !== id) return;
92
+ setState({
93
+ key,
94
+ interventions: sortInterventions(response.interventions),
95
+ loading: false,
96
+ refreshing: false,
97
+ error: null,
98
+ });
99
+ } catch (cause) {
100
+ if (controller.signal.aborted || !mountedRef.current || requestRef.current.id !== id)
101
+ return;
102
+ setState((current) => ({
103
+ ...(current.key === key ? current : emptyState(key, true)),
104
+ loading: false,
105
+ refreshing: false,
106
+ error: asError(cause),
107
+ }));
108
+ } finally {
109
+ if (requestRef.current.id === id) requestRef.current = { id, controller: null };
110
+ }
111
+ },
112
+ [client, enabled, includeSettled, key, resourceId, resourceKind, workspaceId],
113
+ );
114
+
115
+ const refresh = useCallback(async () => await load(false, true), [load]);
116
+
117
+ useEffect(() => {
118
+ mountedRef.current = true;
119
+ if (!enabled) {
120
+ cancelLoad();
121
+ setState(emptyState(key, false));
122
+ return;
123
+ }
124
+ void load(true);
125
+ return () => {
126
+ mountedRef.current = false;
127
+ cancelLoad();
128
+ };
129
+ }, [cancelLoad, enabled, key, load]);
130
+
131
+ useInteractionInvalidation({
132
+ workspaceId,
133
+ key,
134
+ enabled,
135
+ event: workspaceInteractionEvent,
136
+ connectionState: workspaceInteractionConnectionState,
137
+ refresh,
138
+ fallbackPollIntervalMs: options.pollIntervalMs ?? 2_000,
139
+ });
140
+
141
+ const mutate = useCallback(async <T>(operation: () => Promise<T>): Promise<T> => {
142
+ setMutationCount((count) => count + 1);
143
+ try {
144
+ return await operation();
145
+ } finally {
146
+ if (mountedRef.current) setMutationCount((count) => Math.max(0, count - 1));
147
+ }
148
+ }, []);
149
+
150
+ const merge = useCallback(
151
+ (intervention: InteractionIntervention) => {
152
+ setState((current) => {
153
+ if (current.key !== key) return current;
154
+ const interventions = current.interventions.filter(
155
+ (candidate) => candidate.id !== intervention.id,
156
+ );
157
+ const matches =
158
+ (!resourceKind || intervention.resourceKind === resourceKind) &&
159
+ (!resourceId || intervention.resourceId === resourceId);
160
+ if (matches && (includeSettled || intervention.status === "open")) {
161
+ interventions.push(intervention);
162
+ }
163
+ return {
164
+ ...current,
165
+ interventions: sortInterventions(interventions),
166
+ error: null,
167
+ };
168
+ });
169
+ },
170
+ [includeSettled, key, resourceId, resourceKind],
171
+ );
172
+
173
+ const create = useCallback(
174
+ async (
175
+ request: Omit<CreateInteractionInterventionRequest, "operationId"> & {
176
+ operationId?: string;
177
+ },
178
+ ): Promise<InteractionInterventionMutationResponse> =>
179
+ await mutate(async () => {
180
+ const response = await client.createInteractionIntervention(workspaceId, {
181
+ ...request,
182
+ operationId: request.operationId ?? crypto.randomUUID(),
183
+ });
184
+ merge(response.intervention);
185
+ return response;
186
+ }),
187
+ [client, merge, mutate, workspaceId],
188
+ );
189
+
190
+ const resolve = useCallback(
191
+ async (
192
+ interventionId: string,
193
+ request: Omit<ResolveInteractionInterventionRequest, "operationId"> & {
194
+ operationId?: string;
195
+ },
196
+ ): Promise<InteractionInterventionMutationResponse> =>
197
+ await mutate(async () => {
198
+ const response = await client.resolveInteractionIntervention(workspaceId, interventionId, {
199
+ ...request,
200
+ operationId: request.operationId ?? crypto.randomUUID(),
201
+ });
202
+ merge(response.intervention);
203
+ return response;
204
+ }),
205
+ [client, merge, mutate, workspaceId],
206
+ );
207
+
208
+ return {
209
+ interventions: visible.interventions,
210
+ loading: visible.loading,
211
+ refreshing: visible.refreshing,
212
+ mutating: mutationCount > 0,
213
+ error: visible.error,
214
+ refresh,
215
+ create,
216
+ resolve,
217
+ };
218
+ }
219
+
220
+ function emptyState(key: string, loading: boolean) {
221
+ return {
222
+ key,
223
+ interventions: [] as InteractionIntervention[],
224
+ loading,
225
+ refreshing: false,
226
+ error: null as Error | null,
227
+ };
228
+ }
229
+
230
+ function sortInterventions(
231
+ interventions: readonly InteractionIntervention[],
232
+ ): InteractionIntervention[] {
233
+ return [...interventions].sort(
234
+ (left, right) => Date.parse(right.updatedAt) - Date.parse(left.updatedAt),
235
+ );
236
+ }
237
+
238
+ function asError(cause: unknown): Error {
239
+ return cause instanceof Error ? cause : new Error(String(cause));
240
+ }
241
+
242
+ export type InteractionInterventionsClient = EmbeddedInterventionClientLike;
@@ -0,0 +1,66 @@
1
+ import type { StreamConnectionState, WorkspaceInteractionRevisionEvent } from "@opengeni/sdk";
2
+ import { useEffect, useRef } from "react";
3
+ import { usePageLiveActivity } from "./internal";
4
+
5
+ export type InteractionInvalidationOptions = {
6
+ workspaceId: string;
7
+ /** Distinguishes filtered projections of the same workspace catalog. */
8
+ key: string;
9
+ enabled: boolean;
10
+ event: WorkspaceInteractionRevisionEvent | null;
11
+ connectionState: StreamConnectionState | "idle" | "error";
12
+ /** Catalog responses carry the workspace revision; live-state lists do not. */
13
+ knownRevision?: number | undefined;
14
+ refresh: () => Promise<void>;
15
+ fallbackPollIntervalMs?: number | undefined;
16
+ };
17
+
18
+ /**
19
+ * Refetch authoritative lists on the shared latest-revision stream. Poll only
20
+ * while that stream is unavailable, preserving standalone-hook degradation
21
+ * without multiplying healthy-provider traffic.
22
+ */
23
+ export function useInteractionInvalidation(options: InteractionInvalidationOptions): void {
24
+ const {
25
+ workspaceId,
26
+ key,
27
+ enabled,
28
+ event,
29
+ connectionState,
30
+ knownRevision,
31
+ refresh,
32
+ fallbackPollIntervalMs,
33
+ } = options;
34
+ const pageLive = usePageLiveActivity();
35
+ const lastUnversionedEvent = useRef<{ key: string; sequence: number }>({
36
+ key,
37
+ sequence: 0,
38
+ });
39
+ const eventSequence = event?.workspaceId === workspaceId ? event.sequence : 0;
40
+
41
+ useEffect(() => {
42
+ if (!enabled || eventSequence <= 0) return;
43
+ if (knownRevision !== undefined) {
44
+ if (eventSequence > knownRevision) void refresh();
45
+ return;
46
+ }
47
+ if (lastUnversionedEvent.current.key !== key) {
48
+ lastUnversionedEvent.current = { key, sequence: 0 };
49
+ }
50
+ if (eventSequence <= lastUnversionedEvent.current.sequence) return;
51
+ lastUnversionedEvent.current = { key, sequence: eventSequence };
52
+ void refresh();
53
+ }, [enabled, eventSequence, key, knownRevision, refresh]);
54
+
55
+ useEffect(() => {
56
+ if (!enabled || !pageLive || connectionState === "live") return;
57
+ const interval = boundedPollInterval(fallbackPollIntervalMs ?? 3_000);
58
+ const timer = setInterval(() => void refresh(), interval);
59
+ return () => clearInterval(timer);
60
+ }, [connectionState, enabled, fallbackPollIntervalMs, pageLive, refresh]);
61
+ }
62
+
63
+ function boundedPollInterval(value: number): number {
64
+ if (!Number.isFinite(value)) return 3_000;
65
+ return Math.min(60_000, Math.max(1_000, Math.floor(value)));
66
+ }
@@ -1,6 +1,8 @@
1
1
  import { useMemo } from "react";
2
2
  import type { MachineState } from "@opengeni/sdk";
3
3
  import type { SessionCapabilitiesState } from "./use-session-capabilities";
4
+ import { sandboxAcceptsLiveIo } from "../lib/sandbox-liveness";
5
+ import { connectionStatusForState } from "../types/machines";
4
6
 
5
7
  /** The one truthful machine indicator. Honest staleness beats
6
8
  * fake liveness: a cold/asleep box reads "offline — as of <time>", never "live". */
@@ -53,7 +55,7 @@ export function formatAsOf(capturedAt: string, now: number): string {
53
55
  * it; the whole point is a deterministic, testable projection).
54
56
  *
55
57
  * Precedence:
56
- * 1. warm/draining lease → **live** (a warm box always wins).
58
+ * 1. warm lease → **live** (a holder-confirmed box always wins).
57
59
  * 2. actively warming (negotiating, wake-on-edit, or a reconnecting/enrolling
58
60
  * machine) → **waking**.
59
61
  * 3. everything else (a cold/asleep box, a self-hosted machine that's offline,
@@ -64,8 +66,18 @@ export function deriveMachineChip(input: DeriveMachineChipInput): MachineChip {
64
66
  const asOf = input.capturedAt ?? null;
65
67
  const offlineLabel = asOf ? `Offline — as of ${formatAsOf(asOf, now)}` : "Offline";
66
68
 
67
- // 1. A warm (or draining) lease is live regardless of anything else.
68
- if (input.liveness === "warm" || input.liveness === "draining") {
69
+ // A connected machine has no Modal group lease, so capability negotiation
70
+ // legitimately reports `cold` until a stream holder is minted. Its fleet
71
+ // probe is the authoritative reachability signal; do not label an online Mac
72
+ // "Offline" merely because there is no unrelated Modal lease.
73
+ const selfhostedOnline =
74
+ input.activeIsSelfhosted === true &&
75
+ input.activeMachineState != null &&
76
+ connectionStatusForState(input.activeMachineState) === "online";
77
+
78
+ // 1. A holder-confirmed warm provider or online connected machine is live.
79
+ // Draining remains teardown-owned and may already have lost its instance.
80
+ if (sandboxAcceptsLiveIo(input.liveness) || selfhostedOnline) {
69
81
  return { state: "live", label: "Live", asOf: null };
70
82
  }
71
83
 
@@ -1,5 +1,9 @@
1
1
  import { useCallback, useRef, useState } from "react";
2
- import type { RemoveEnrollmentRequest, RemoveEnrollmentResponse } from "@opengeni/sdk";
2
+ import type {
3
+ RemoveEnrollmentRequest,
4
+ RemoveEnrollmentResponse,
5
+ SwapActiveSandboxResponse,
6
+ } from "@opengeni/sdk";
3
7
  import { useOpenGeni, type ClientOverride } from "../provider";
4
8
  import { useMutationRunner, usePolledValue } from "./internal";
5
9
  import type { MachinesResponse, MachineView, MetricSample } from "../types/machines";
@@ -46,7 +50,7 @@ export type MachinesClientLike = {
46
50
  workspaceId: string,
47
51
  sessionId: string,
48
52
  request: { target: string },
49
- ) => Promise<unknown>;
53
+ ) => Promise<SwapActiveSandboxResponse>;
50
54
  /**
51
55
  * Host-supplied swap adapter (an escape hatch). When present it wins over the
52
56
  * default `swapActiveSandbox` path. Session-scoped, like the swap it backs.
@@ -99,6 +103,24 @@ export type UseMachinesResult = {
99
103
 
100
104
  const EMPTY: MachinesResponse = { activeSandboxId: null, activeEpoch: 0, machines: [] };
101
105
 
106
+ function swapFailureMessage(result: SwapActiveSandboxResponse): string {
107
+ if (result.reason?.trim()) return result.reason;
108
+ switch (result.code) {
109
+ case "recovery_in_progress":
110
+ return "The managed sandbox is still recovering. Try again shortly.";
111
+ case "recovery_degraded":
112
+ return "The managed sandbox recovery is degraded and needs attention.";
113
+ case "recovery_unrecoverable":
114
+ return "The managed sandbox could not be recovered.";
115
+ case "offline_enrollment":
116
+ return "The selected machine is offline.";
117
+ case "concurrent_swap":
118
+ return "The session route changed concurrently. Refresh and try again.";
119
+ default:
120
+ return "The session stayed on its current sandbox.";
121
+ }
122
+ }
123
+
102
124
  /**
103
125
  * The workspace Machines fleet: the selfhosted enrollments + the session's Modal
104
126
  * box, each with latest metrics + state, plus the active-sandbox pointer. Polls
@@ -171,7 +193,15 @@ export function useMachines(options: UseMachinesOptions = {}): UseMachinesResult
171
193
  const ownedIdentity = identityKey;
172
194
  setAttachState({ identity: ownedIdentity, sandboxId });
173
195
  const result = await run(async () => {
174
- await runSwap();
196
+ const response = await runSwap();
197
+ if (
198
+ response &&
199
+ typeof response === "object" &&
200
+ "swapped" in response &&
201
+ response.swapped === false
202
+ ) {
203
+ throw new Error(swapFailureMessage(response as SwapActiveSandboxResponse));
204
+ }
175
205
  return true;
176
206
  });
177
207
  if (identityRef.current === ownedIdentity) {