@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
@@ -0,0 +1,434 @@
1
+ import {
2
+ computerFrameSocketUrl,
3
+ decodeComputerFrameMessage,
4
+ type ComputerFrame,
5
+ type ComputerFrameStreamOptions,
6
+ type ComputerSessionAttachment,
7
+ } from "@opengeni/sdk/interaction";
8
+ import { useCallback, useEffect, useRef, useState } from "react";
9
+ import {
10
+ decodeStreamFrame,
11
+ decodeStreamOpenAck,
12
+ encodeStreamClose,
13
+ encodeStreamOpen,
14
+ STREAM_CLOSE_REASON_NORMAL,
15
+ STREAM_KIND_COMPUTER,
16
+ STREAM_ROLE_CLIENT,
17
+ } from "../lib/relay-wire";
18
+ import {
19
+ type EmbeddedComputerInteractionClientOverride,
20
+ useEmbeddedComputerInteraction,
21
+ } from "../session-context";
22
+
23
+ export type ComputerFrameConnectionState =
24
+ | "idle"
25
+ | "attaching"
26
+ | "connecting"
27
+ | "live"
28
+ | "reconnecting"
29
+ | "error";
30
+
31
+ export type ComputerFrameWebSocket = Pick<
32
+ WebSocket,
33
+ "binaryType" | "readyState" | "close" | "addEventListener" | "removeEventListener"
34
+ > & { send(data: ArrayBuffer): void };
35
+
36
+ export type ComputerFrameWebSocketFactory = (
37
+ url: string,
38
+ protocols: string[],
39
+ ) => ComputerFrameWebSocket;
40
+
41
+ const RELAY_TAG_OPEN = 1;
42
+ const RELAY_TAG_OPEN_ACK = 2;
43
+ const RELAY_TAG_FRAME = 3;
44
+ const RELAY_TAG_CLOSE = 4;
45
+ const MAX_AUTOMATIC_RECONNECTS_WITHOUT_A_FRAME = 2;
46
+
47
+ export type UseComputerFrameStreamOptions = EmbeddedComputerInteractionClientOverride & {
48
+ computerSessionId: string | null;
49
+ targetId: string | null;
50
+ enabled?: boolean | undefined;
51
+ stream?: ComputerFrameStreamOptions | undefined;
52
+ webSocketFactory?: ComputerFrameWebSocketFactory | undefined;
53
+ };
54
+
55
+ export type UseComputerFrameStreamResult = {
56
+ state: ComputerFrameConnectionState;
57
+ frame: ComputerFrame | null;
58
+ attachment: Pick<
59
+ ComputerSessionAttachment,
60
+ "computerSessionId" | "controllerGeneration" | "targetId" | "stream" | "expiresAt"
61
+ > | null;
62
+ error: Error | null;
63
+ reconnect: () => void;
64
+ };
65
+
66
+ /** Reconnecting, latest-only ComputerSession media attachment. Grants remain in
67
+ * WebSocket subprotocols or the relay's authenticated first binary message. */
68
+ export function useComputerFrameStream(
69
+ options: UseComputerFrameStreamOptions,
70
+ ): UseComputerFrameStreamResult {
71
+ const { client, workspaceId } = useEmbeddedComputerInteraction(options);
72
+ const enabled = options.enabled ?? true;
73
+ const [nonce, setNonce] = useState(0);
74
+ const [result, setResult] = useState<Omit<UseComputerFrameStreamResult, "reconnect">>({
75
+ state: "idle",
76
+ frame: null,
77
+ attachment: null,
78
+ error: null,
79
+ });
80
+ const latestRef = useRef<{ key: string; sequence: number }>({
81
+ key: "",
82
+ sequence: -1,
83
+ });
84
+ const factoryRef = useRef(options.webSocketFactory);
85
+ const streamRef = useRef(options.stream);
86
+ factoryRef.current = options.webSocketFactory;
87
+ streamRef.current = options.stream;
88
+
89
+ const reconnect = useCallback(() => setNonce((value) => value + 1), []);
90
+
91
+ useEffect(() => {
92
+ const computerSessionId = options.computerSessionId;
93
+ const targetId = options.targetId;
94
+ if (!enabled || !computerSessionId || !targetId || typeof window === "undefined") {
95
+ setResult({ state: "idle", frame: null, attachment: null, error: null });
96
+ return;
97
+ }
98
+
99
+ latestRef.current = { key: "", sequence: -1 };
100
+ setResult({
101
+ state: "attaching",
102
+ frame: null,
103
+ attachment: null,
104
+ error: null,
105
+ });
106
+
107
+ let disposed = false;
108
+ let socket: ComputerFrameWebSocket | null = null;
109
+ let socketHandlers: {
110
+ open: () => void;
111
+ message: (event: MessageEvent) => void;
112
+ error: () => void;
113
+ close: () => void;
114
+ } | null = null;
115
+ let reconnectTimer: ReturnType<typeof setTimeout> | null = null;
116
+ let expiryTimer: ReturnType<typeof setTimeout> | null = null;
117
+ let failures = 0;
118
+ let controllerGeneration: string | null = null;
119
+ let activeAttachment: ComputerSessionAttachment | null = null;
120
+ let activeStream: ComputerSessionAttachment["stream"] | null = null;
121
+ let attachmentExpiresAt = 0;
122
+ let lastRelaySequence: string | null = null;
123
+ let relayAccepted = false;
124
+ let pendingFrame: { bytes: Uint8Array; source: ComputerFrameWebSocket } | null = null;
125
+ let decodingFrame = false;
126
+ const attachmentAbort = new AbortController();
127
+
128
+ const clearSocket = (terminateProducer = false) => {
129
+ if (!socket) return;
130
+ const current = socket;
131
+ const handlers = socketHandlers;
132
+ if (terminateProducer && activeStream?.kind === "relay" && socket.readyState === 1) {
133
+ try {
134
+ socket.send(
135
+ relayDatagram(
136
+ RELAY_TAG_CLOSE,
137
+ encodeStreamClose({
138
+ channelId: activeStream.channel.channelId,
139
+ reason: STREAM_CLOSE_REASON_NORMAL,
140
+ message: "viewer detached",
141
+ }),
142
+ ),
143
+ );
144
+ } catch {
145
+ // OPEN can race a transport close. Local teardown must still finish.
146
+ }
147
+ }
148
+ if (handlers) {
149
+ current.removeEventListener("open", handlers.open);
150
+ current.removeEventListener("message", handlers.message);
151
+ current.removeEventListener("error", handlers.error);
152
+ current.removeEventListener("close", handlers.close);
153
+ }
154
+ if (current.readyState === 0 || current.readyState === 1) {
155
+ current.close(1000, "viewer detached");
156
+ }
157
+ socket = null;
158
+ socketHandlers = null;
159
+ pendingFrame = null;
160
+ };
161
+
162
+ const scheduleReconnect = () => {
163
+ if (disposed || reconnectTimer) return;
164
+ if (failures >= MAX_AUTOMATIC_RECONNECTS_WITHOUT_A_FRAME) return;
165
+ const delay = Math.min(5_000, 250 * 2 ** Math.min(failures, 5));
166
+ failures += 1;
167
+ setResult((current) => ({ ...current, state: "reconnecting" }));
168
+ reconnectTimer = setTimeout(() => {
169
+ reconnectTimer = null;
170
+ connectSocket();
171
+ }, delay);
172
+ };
173
+
174
+ const fail = (cause: unknown, reconnectAutomatically = true) => {
175
+ if (disposed) return;
176
+ const error = cause instanceof Error ? cause : new Error(String(cause));
177
+ setResult((current) => ({ ...current, state: "error", error }));
178
+ clearSocket();
179
+ if (reconnectAutomatically) scheduleReconnect();
180
+ };
181
+
182
+ const onOpen = (source: ComputerFrameWebSocket) => {
183
+ if (disposed || source !== socket) return;
184
+ if (activeStream?.kind === "relay") {
185
+ const body = encodeStreamOpen({
186
+ channel: { ...activeStream.channel, kind: STREAM_KIND_COMPUTER },
187
+ token: activeStream.token,
188
+ role: STREAM_ROLE_CLIENT,
189
+ resumeFromSeq:
190
+ lastRelaySequence === null ? "0" : (BigInt(lastRelaySequence) + 1n).toString(),
191
+ });
192
+ try {
193
+ source.send(relayDatagram(RELAY_TAG_OPEN, body));
194
+ } catch (error) {
195
+ fail(error);
196
+ }
197
+ return;
198
+ }
199
+ setResult((current) => ({ ...current, state: "live", error: null }));
200
+ };
201
+
202
+ const drainLatestFrame = async () => {
203
+ if (decodingFrame) return;
204
+ decodingFrame = true;
205
+ try {
206
+ while (pendingFrame) {
207
+ if (disposed) break;
208
+ const pending = pendingFrame;
209
+ pendingFrame = null;
210
+ const { bytes, source } = pending;
211
+ const frame = await decodeComputerFrameMessage(bytes);
212
+ if (disposed || source !== socket) continue;
213
+ if (frame.computerSessionId !== computerSessionId || frame.targetId !== targetId) {
214
+ throw new Error("computer frame belongs to another resource");
215
+ }
216
+ if (!controllerGeneration || frame.controllerGeneration !== controllerGeneration) {
217
+ throw new Error("computer frame belongs to a stale controller");
218
+ }
219
+ const key = `${computerSessionId}:${targetId}:${frame.controllerGeneration}:${frame.targetGeneration}`;
220
+ if (latestRef.current.key === key && frame.sequence <= latestRef.current.sequence)
221
+ continue;
222
+ latestRef.current = { key, sequence: frame.sequence };
223
+ // A real decoded frame—not merely a socket handshake—is the recovery
224
+ // boundary. Resetting on `open` caused a dead producer to reconnect
225
+ // forever and continuously spawn fresh relay/SCK work.
226
+ failures = 0;
227
+ setResult((current) => ({
228
+ ...current,
229
+ state: "live",
230
+ frame,
231
+ error: null,
232
+ }));
233
+ }
234
+ } catch (cause) {
235
+ fail(cause);
236
+ } finally {
237
+ decodingFrame = false;
238
+ if (!disposed && pendingFrame) void drainLatestFrame();
239
+ }
240
+ };
241
+
242
+ const onMessage = (source: ComputerFrameWebSocket, event: MessageEvent) => {
243
+ void (async () => {
244
+ try {
245
+ const bytes = await messageBytes(event.data);
246
+ if (disposed || source !== socket) return;
247
+ let frameBytes = bytes;
248
+ if (activeStream?.kind === "relay") {
249
+ if (bytes.length < 1) throw new Error("computer relay returned an empty message");
250
+ const tag = bytes[0];
251
+ const body = bytes.subarray(1);
252
+ if (tag === RELAY_TAG_OPEN_ACK) {
253
+ const ack = decodeStreamOpenAck(body);
254
+ if (!ack.accepted) {
255
+ activeAttachment = null;
256
+ activeStream = null;
257
+ attachmentExpiresAt = 0;
258
+ throw new Error(ack.error?.message ?? "computer stream was rejected by the relay");
259
+ }
260
+ relayAccepted = true;
261
+ return;
262
+ }
263
+ if (tag === RELAY_TAG_CLOSE) {
264
+ // A relay close is terminal for this producer/channel. Reusing
265
+ // the same attachment only reconnects to a dead channel forever;
266
+ // invalidate it so the retry mints a fresh producer.
267
+ activeAttachment = null;
268
+ activeStream = null;
269
+ attachmentExpiresAt = 0;
270
+ lastRelaySequence = null;
271
+ relayAccepted = false;
272
+ fail(new Error("Computer frame source ended."), false);
273
+ return;
274
+ }
275
+ if (tag !== RELAY_TAG_FRAME || !relayAccepted) return;
276
+ const relayFrame = decodeStreamFrame(body);
277
+ lastRelaySequence = relayFrame.seq;
278
+ frameBytes = relayFrame.data;
279
+ }
280
+ // Digest verification can take longer than an inter-frame interval on
281
+ // low-power clients. Keep exactly one pending encoded frame and replace
282
+ // it with newer input while a decode is in flight; never build latency.
283
+ pendingFrame = { bytes: frameBytes, source };
284
+ void drainLatestFrame();
285
+ } catch (cause) {
286
+ fail(cause);
287
+ }
288
+ })();
289
+ };
290
+
291
+ const onError = (source: ComputerFrameWebSocket) => {
292
+ if (source !== socket) return;
293
+ fail(new Error("Computer view lost connection."));
294
+ };
295
+
296
+ const onClose = (source: ComputerFrameWebSocket) => {
297
+ if (disposed || source !== socket) return;
298
+ clearSocket();
299
+ scheduleReconnect();
300
+ };
301
+
302
+ const connectSocket = () => {
303
+ if (disposed) return;
304
+ clearSocket();
305
+ if (!activeAttachment || !activeStream || attachmentExpiresAt <= Date.now() + 1_000) {
306
+ void attach();
307
+ return;
308
+ }
309
+ if (activeStream.kind === "direct_rfb") {
310
+ setResult((current) => ({ ...current, state: "live", error: null }));
311
+ return;
312
+ }
313
+ relayAccepted = false;
314
+ setResult((current) => ({
315
+ ...current,
316
+ state: "connecting",
317
+ error: null,
318
+ }));
319
+ const createSocket =
320
+ factoryRef.current ?? ((url: string, protocols: string[]) => new WebSocket(url, protocols));
321
+ const openedSocket = createSocket(
322
+ activeStream.kind === "relay"
323
+ ? activeStream.url
324
+ : computerFrameSocketUrl(activeAttachment, streamRef.current),
325
+ activeStream.kind === "direct_websocket" ? [...activeStream.protocols] : [],
326
+ );
327
+ socket = openedSocket;
328
+ socketHandlers = {
329
+ open: () => onOpen(openedSocket),
330
+ message: (event) => onMessage(openedSocket, event),
331
+ error: () => onError(openedSocket),
332
+ close: () => onClose(openedSocket),
333
+ };
334
+ openedSocket.binaryType = "arraybuffer";
335
+ openedSocket.addEventListener("open", socketHandlers.open);
336
+ openedSocket.addEventListener("message", socketHandlers.message);
337
+ openedSocket.addEventListener("error", socketHandlers.error);
338
+ openedSocket.addEventListener("close", socketHandlers.close);
339
+ };
340
+
341
+ const attach = async () => {
342
+ if (disposed) return;
343
+ clearSocket();
344
+ if (expiryTimer) clearTimeout(expiryTimer);
345
+ setResult((current) => ({
346
+ ...current,
347
+ state: current.attachment ? "reconnecting" : "attaching",
348
+ error: null,
349
+ }));
350
+ try {
351
+ const attachment = await client.attachComputerSession(
352
+ workspaceId,
353
+ computerSessionId,
354
+ {
355
+ targetId,
356
+ expiresInSeconds: 120,
357
+ ...(streamRef.current ? { stream: streamRef.current } : {}),
358
+ },
359
+ { signal: attachmentAbort.signal },
360
+ );
361
+ if (disposed) return;
362
+ if (
363
+ attachment.computerSessionId !== computerSessionId ||
364
+ attachment.targetId !== targetId
365
+ ) {
366
+ throw new Error("computer attachment does not match the requested resource");
367
+ }
368
+ controllerGeneration = attachment.controllerGeneration;
369
+ activeAttachment = attachment;
370
+ activeStream = attachment.stream;
371
+ attachmentExpiresAt = Date.parse(attachment.expiresAt);
372
+ lastRelaySequence = null;
373
+ relayAccepted = false;
374
+ setResult((current) => ({
375
+ ...current,
376
+ state: "connecting",
377
+ attachment: {
378
+ computerSessionId: attachment.computerSessionId,
379
+ controllerGeneration: attachment.controllerGeneration,
380
+ targetId: attachment.targetId,
381
+ stream: attachment.stream,
382
+ expiresAt: attachment.expiresAt,
383
+ },
384
+ error: null,
385
+ }));
386
+ const refreshIn = Number.isFinite(attachmentExpiresAt)
387
+ ? Math.max(1_000, attachmentExpiresAt - Date.now() - 5_000)
388
+ : 60_000;
389
+ expiryTimer = setTimeout(() => {
390
+ if (disposed) return;
391
+ clearSocket(true);
392
+ activeAttachment = null;
393
+ activeStream = null;
394
+ attachmentExpiresAt = 0;
395
+ void attach();
396
+ }, refreshIn);
397
+ connectSocket();
398
+ } catch (cause) {
399
+ if (attachmentAbort.signal.aborted || disposed) return;
400
+ fail(cause);
401
+ }
402
+ };
403
+
404
+ void attach();
405
+ return () => {
406
+ disposed = true;
407
+ pendingFrame = null;
408
+ attachmentAbort.abort();
409
+ if (reconnectTimer) clearTimeout(reconnectTimer);
410
+ if (expiryTimer) clearTimeout(expiryTimer);
411
+ clearSocket(true);
412
+ };
413
+ }, [client, enabled, nonce, options.computerSessionId, options.targetId, workspaceId]);
414
+
415
+ return { ...result, reconnect };
416
+ }
417
+
418
+ function relayDatagram(tag: number, body: Uint8Array): ArrayBuffer {
419
+ const message = new Uint8Array(body.length + 1);
420
+ message[0] = tag;
421
+ message.set(body, 1);
422
+ return message.buffer as ArrayBuffer;
423
+ }
424
+
425
+ async function messageBytes(value: unknown): Promise<Uint8Array> {
426
+ if (value instanceof ArrayBuffer) return new Uint8Array(value);
427
+ if (ArrayBuffer.isView(value)) {
428
+ return new Uint8Array(value.buffer, value.byteOffset, value.byteLength).slice();
429
+ }
430
+ if (typeof Blob !== "undefined" && value instanceof Blob) {
431
+ return new Uint8Array(await value.arrayBuffer());
432
+ }
433
+ throw new Error("computer frame stream returned a non-binary message");
434
+ }