@opengeni/react 0.46.1 → 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 (341) hide show
  1. package/README.md +158 -14
  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-HFO4ERGQ.js → chunk-724BW4TW.js} +586 -35
  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-U3CSCMWA.js → chunk-FK6VG4LL.js} +9 -13
  23. package/dist/chunk-FK6VG4LL.js.map +1 -0
  24. package/dist/{chunk-SRFUT2ZU.js → chunk-FWS3KC3Z.js} +120 -64
  25. package/dist/chunk-FWS3KC3Z.js.map +1 -0
  26. package/dist/{chunk-EKZH6IDG.js → chunk-HDBG7XGO.js} +62 -1
  27. package/dist/chunk-HDBG7XGO.js.map +1 -0
  28. package/dist/chunk-HUIKIDM5.js +2148 -0
  29. package/dist/chunk-HUIKIDM5.js.map +1 -0
  30. package/dist/{chunk-LGVMUIRV.js → chunk-JAGDUCZQ.js} +1053 -390
  31. package/dist/chunk-JAGDUCZQ.js.map +1 -0
  32. package/dist/{chunk-LWR4MXSS.js → chunk-JR3QFY6B.js} +420 -35
  33. package/dist/chunk-JR3QFY6B.js.map +1 -0
  34. package/dist/chunk-JVYQRRC4.js +53 -0
  35. package/dist/chunk-JVYQRRC4.js.map +1 -0
  36. package/dist/chunk-KCGXPP5V.js +157 -0
  37. package/dist/chunk-KCGXPP5V.js.map +1 -0
  38. package/dist/{chunk-EB67J347.js → chunk-KZ73RL76.js} +111 -31
  39. package/dist/chunk-KZ73RL76.js.map +1 -0
  40. package/dist/chunk-PEI6RANI.js +3234 -0
  41. package/dist/chunk-PEI6RANI.js.map +1 -0
  42. package/dist/{chunk-3PB7MIT6.js → chunk-ROIRTM37.js} +220 -72
  43. package/dist/chunk-ROIRTM37.js.map +1 -0
  44. package/dist/chunk-VZTQ73XT.js +86 -0
  45. package/dist/chunk-VZTQ73XT.js.map +1 -0
  46. package/dist/{chunk-VCBFUSUZ.js → chunk-WSK7G5LE.js} +72 -4
  47. package/dist/chunk-WSK7G5LE.js.map +1 -0
  48. package/dist/{chunk-FGZUCXZF.js → chunk-WYA65Y3F.js} +16 -16
  49. package/dist/chunk-WYA65Y3F.js.map +1 -0
  50. package/dist/{chunk-6EDV6YFE.js → chunk-X4S56Y2H.js} +147 -35
  51. package/dist/chunk-X4S56Y2H.js.map +1 -0
  52. package/dist/client.d.ts +7 -1
  53. package/dist/clipboard.d.ts +1 -0
  54. package/dist/clipboard.js +7 -0
  55. package/dist/clipboard.js.map +1 -0
  56. package/dist/commands/index.d.ts +3 -3
  57. package/dist/commands/registry.d.ts +1 -1
  58. package/dist/commands/types.d.ts +1 -1
  59. package/dist/components/approval-surface.d.ts +2 -2
  60. package/dist/components/artifacts/artifact-surface.d.ts +18 -0
  61. package/dist/components/artifacts/document-editor.d.ts +172 -0
  62. package/dist/components/artifacts/editable-artifact-ui.d.ts +13 -0
  63. package/dist/components/artifacts/editable-artifact-workbench.d.ts +49 -0
  64. package/dist/components/artifacts/editable-document.d.ts +24 -0
  65. package/dist/components/artifacts/editable-presentation.d.ts +24 -0
  66. package/dist/components/artifacts/editable-spreadsheet.d.ts +32 -0
  67. package/dist/components/artifacts/index.d.ts +8 -0
  68. package/dist/components/artifacts/presentation-editor.d.ts +297 -0
  69. package/dist/components/artifacts/spreadsheet-canvas.d.ts +120 -0
  70. package/dist/components/artifacts/spreadsheet-grid.d.ts +113 -0
  71. package/dist/components/browser-viewer.d.ts +29 -0
  72. package/dist/components/chat-composer.d.ts +13 -8
  73. package/dist/components/code-editor.d.ts +25 -3
  74. package/dist/components/command-palette.d.ts +2 -2
  75. package/dist/components/composer-transcription-control.d.ts +4 -2
  76. package/dist/components/composer.d.ts +29 -15
  77. package/dist/components/computer-viewer.d.ts +23 -0
  78. package/dist/components/copy-button.d.ts +2 -2
  79. package/dist/components/desktop-viewer.d.ts +25 -21
  80. package/dist/components/diff-view.d.ts +1 -1
  81. package/dist/components/enrollment-consent.d.ts +1 -1
  82. package/dist/components/enrollment-device-flow.d.ts +1 -1
  83. package/dist/components/file-browser.d.ts +2 -2
  84. package/dist/components/fleet-tile.d.ts +1 -1
  85. package/dist/components/generated-video-player.d.ts +10 -0
  86. package/dist/components/human-input-form.d.ts +1 -1
  87. package/dist/components/human-input-surface.d.ts +2 -2
  88. package/dist/components/interactive-workbench-viewers.d.ts +2 -0
  89. package/dist/components/machine-card.d.ts +2 -2
  90. package/dist/components/machine-dock-bar.d.ts +3 -3
  91. package/dist/components/machine-health-pill.d.ts +2 -2
  92. package/dist/components/machine-input-display.d.ts +1 -1
  93. package/dist/components/machine-metrics.d.ts +2 -2
  94. package/dist/components/machine-status-pill.d.ts +4 -4
  95. package/dist/components/machines/health.d.ts +1 -1
  96. package/dist/components/machines/machine-detail.d.ts +5 -3
  97. package/dist/components/machines/metric-history-chart.d.ts +1 -1
  98. package/dist/components/machines/metric-sparkline.d.ts +2 -2
  99. package/dist/components/machines/series.d.ts +2 -2
  100. package/dist/components/machines-dashboard.d.ts +5 -5
  101. package/dist/components/markdown.d.ts +1 -1
  102. package/dist/components/message-timeline.d.ts +22 -5
  103. package/dist/components/model-picker.d.ts +2 -2
  104. package/dist/components/model-policy-picker.d.ts +7 -7
  105. package/dist/components/pierre-diff.d.ts +1 -1
  106. package/dist/components/pierre-file.d.ts +1 -1
  107. package/dist/components/queue-draft-policy.d.ts +1 -1
  108. package/dist/components/queue-surface-implementation.d.ts +3 -3
  109. package/dist/components/queue-surface-state.d.ts +4 -4
  110. package/dist/components/queue-surface.d.ts +5 -5
  111. package/dist/components/sandbox-files.d.ts +5 -5
  112. package/dist/components/sandbox-terminal.d.ts +50 -4
  113. package/dist/components/sandbox-workspace.d.ts +30 -10
  114. package/dist/components/session-chrome.d.ts +4 -4
  115. package/dist/components/session-status.d.ts +2 -2
  116. package/dist/components/timeline-annotation-selection.d.ts +9 -0
  117. package/dist/components/timeline-annotations-dialog.d.ts +16 -0
  118. package/dist/components/timeline-annotations.d.ts +13 -0
  119. package/dist/components/tooltip.d.ts +4 -4
  120. package/dist/components/user-message-body.d.ts +2 -2
  121. package/dist/components/workbench-changes.d.ts +2 -2
  122. package/dist/components/workspace-dock.d.ts +3 -1
  123. package/dist/composer.d.ts +11 -11
  124. package/dist/composer.js +7 -7
  125. package/dist/hooks/internal.d.ts +7 -1
  126. package/dist/hooks/use-attached-browsers.d.ts +21 -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-frame-stream.d.ts +28 -0
  130. package/dist/hooks/use-browser-identities.d.ts +26 -0
  131. package/dist/hooks/use-browser-session.d.ts +28 -0
  132. package/dist/hooks/use-browser-sessions.d.ts +32 -0
  133. package/dist/hooks/use-codex-accounts.d.ts +3 -3
  134. package/dist/hooks/use-composer.d.ts +36 -9
  135. package/dist/hooks/use-computer-frame-stream.d.ts +24 -0
  136. package/dist/hooks/use-computer-session.d.ts +28 -0
  137. package/dist/hooks/use-computer-sessions.d.ts +28 -0
  138. package/dist/hooks/use-desktop-stream.d.ts +1 -1
  139. package/dist/hooks/use-environments.d.ts +11 -5
  140. package/dist/hooks/use-file-attachments.d.ts +1 -1
  141. package/dist/hooks/use-goal.d.ts +2 -2
  142. package/dist/hooks/use-human-input.d.ts +2 -2
  143. package/dist/hooks/use-last-started-turn-policy.d.ts +2 -2
  144. package/dist/hooks/use-machine-chip.d.ts +2 -2
  145. package/dist/hooks/use-machines.d.ts +10 -3
  146. package/dist/hooks/use-packs.d.ts +7 -2
  147. package/dist/hooks/use-rigs.d.ts +1 -1
  148. package/dist/hooks/use-sandbox-files.d.ts +25 -4
  149. package/dist/hooks/use-sandbox-git.d.ts +13 -5
  150. package/dist/hooks/use-sandbox-terminal.d.ts +1 -1
  151. package/dist/hooks/use-scheduled-tasks.d.ts +1 -1
  152. package/dist/hooks/use-session-capabilities.d.ts +5 -4
  153. package/dist/hooks/use-session-control.d.ts +1 -1
  154. package/dist/hooks/use-session-events.d.ts +2 -2
  155. package/dist/hooks/use-session-lineage.d.ts +1 -1
  156. package/dist/hooks/use-session-mcp-approval-policy.d.ts +2 -2
  157. package/dist/hooks/use-session.d.ts +2 -2
  158. package/dist/hooks/use-slash-commands.d.ts +1 -1
  159. package/dist/hooks/use-terminal-stream.d.ts +8 -2
  160. package/dist/hooks/use-transcription.d.ts +2 -2
  161. package/dist/hooks/use-turn-queue.d.ts +2 -2
  162. package/dist/hooks/use-video-artifact-playback.d.ts +9 -0
  163. package/dist/hooks/use-voice-input.d.ts +6 -4
  164. package/dist/hooks/use-workspace-capture.d.ts +1 -1
  165. package/dist/hooks/use-workspace-edit.d.ts +1 -1
  166. package/dist/hooks/use-workspace-sessions.d.ts +1 -1
  167. package/dist/hooks/use-workspaces.d.ts +1 -1
  168. package/dist/index.d.ts +158 -154
  169. package/dist/index.js +1718 -908
  170. package/dist/index.js.map +1 -1
  171. package/dist/interaction.d.ts +23 -0
  172. package/dist/interaction.js +36 -0
  173. package/dist/interaction.js.map +1 -0
  174. package/dist/interactive-workbench-viewers-KHNOHWQE.js +13 -0
  175. package/dist/interactive-workbench-viewers-KHNOHWQE.js.map +1 -0
  176. package/dist/lib/composer-responsive-context.d.ts +9 -0
  177. package/dist/lib/relay-wire.d.ts +4 -0
  178. package/dist/lib/sandbox-liveness.d.ts +9 -0
  179. package/dist/lib/terminal-capability.d.ts +5 -0
  180. package/dist/lib/use-portal-token-style.d.ts +14 -3
  181. package/dist/lib/xterm-theme.d.ts +1 -1
  182. package/dist/machines.d.ts +30 -30
  183. package/dist/machines.js +3 -3
  184. package/dist/provider.d.ts +4 -4
  185. package/dist/{queue-surface-implementation-J4XJFKRW.js → queue-surface-implementation-2KVJTYXH.js} +117 -64
  186. package/dist/queue-surface-implementation-2KVJTYXH.js.map +1 -0
  187. package/dist/realtime/dropdown-menu.d.ts +8 -8
  188. package/dist/realtime/realtime-control.d.ts +15 -6
  189. package/dist/realtime.d.ts +4 -4
  190. package/dist/realtime.js +30 -33
  191. package/dist/realtime.js.map +1 -1
  192. package/dist/session-context.d.ts +19 -1
  193. package/dist/session-ui.d.ts +14 -14
  194. package/dist/session-ui.js +6 -5
  195. package/dist/session.d.ts +28 -28
  196. package/dist/session.js +5 -5
  197. package/dist/timeline/activity-rail.d.ts +6 -3
  198. package/dist/timeline/disclosure-context.d.ts +1 -1
  199. package/dist/timeline/fleet-decision-projection.d.ts +1 -1
  200. package/dist/timeline/fleet-decision-row.d.ts +3 -3
  201. package/dist/timeline/index.d.ts +19 -19
  202. package/dist/timeline/parsers.d.ts +6 -3
  203. package/dist/timeline/projection.d.ts +3 -3
  204. package/dist/timeline/registry.d.ts +10 -1
  205. package/dist/timeline/screenshot-lightbox.d.ts +2 -2
  206. package/dist/timeline/shared.d.ts +21 -10
  207. package/dist/timeline/tool-diff.d.ts +2 -2
  208. package/dist/timeline/tool-renderers.d.ts +1 -1
  209. package/dist/timeline/turn-summary.d.ts +3 -3
  210. package/dist/timeline/types.d.ts +42 -2
  211. package/dist/timeline-annotation-selection-3L7LHGG2.js +187 -0
  212. package/dist/timeline-annotation-selection-3L7LHGG2.js.map +1 -0
  213. package/dist/timeline-annotations-dialog-U7EVLR75.js +196 -0
  214. package/dist/timeline-annotations-dialog-U7EVLR75.js.map +1 -0
  215. package/dist/voice-recording-store.d.ts +8 -2
  216. package/package.json +52 -7
  217. package/src/artifacts-document.ts +31 -0
  218. package/src/artifacts-presentation.ts +36 -0
  219. package/src/artifacts-spreadsheet.ts +21 -0
  220. package/src/artifacts.ts +86 -0
  221. package/src/client.ts +90 -1
  222. package/src/clipboard.ts +2 -0
  223. package/src/components/artifacts/artifact-surface.tsx +93 -0
  224. package/src/components/artifacts/document-editor.tsx +2255 -0
  225. package/src/components/artifacts/editable-artifact-ui.tsx +89 -0
  226. package/src/components/artifacts/editable-artifact-workbench.tsx +203 -0
  227. package/src/components/artifacts/editable-document.tsx +657 -0
  228. package/src/components/artifacts/editable-presentation.tsx +1020 -0
  229. package/src/components/artifacts/editable-spreadsheet.tsx +733 -0
  230. package/src/components/artifacts/index.ts +99 -0
  231. package/src/components/artifacts/presentation-editor.tsx +3148 -0
  232. package/src/components/artifacts/spreadsheet-canvas.ts +799 -0
  233. package/src/components/artifacts/spreadsheet-grid.tsx +2129 -0
  234. package/src/components/browser-viewer.tsx +1592 -0
  235. package/src/components/chat-composer.tsx +25 -2
  236. package/src/components/code-editor.tsx +90 -31
  237. package/src/components/command-palette.tsx +1 -1
  238. package/src/components/composer-transcription-control.tsx +51 -22
  239. package/src/components/composer.tsx +49 -23
  240. package/src/components/computer-viewer.tsx +1139 -0
  241. package/src/components/copy-button.tsx +1 -1
  242. package/src/components/desktop-viewer.tsx +103 -75
  243. package/src/components/file-browser.tsx +39 -17
  244. package/src/components/generated-video-player.tsx +89 -0
  245. package/src/components/human-input-form.tsx +2 -2
  246. package/src/components/interactive-workbench-viewers.ts +2 -0
  247. package/src/components/machine-card.tsx +1 -1
  248. package/src/components/machine-input-display.ts +3 -0
  249. package/src/components/machines/machine-detail.tsx +39 -15
  250. package/src/components/machines-dashboard.tsx +7 -6
  251. package/src/components/message-timeline.tsx +327 -37
  252. package/src/components/model-picker.tsx +2 -2
  253. package/src/components/model-policy-picker.tsx +16 -15
  254. package/src/components/queue-surface-implementation.tsx +135 -75
  255. package/src/components/queue-surface-state.tsx +2 -2
  256. package/src/components/sandbox-files.tsx +142 -62
  257. package/src/components/sandbox-terminal.tsx +119 -35
  258. package/src/components/sandbox-workspace.tsx +394 -228
  259. package/src/components/session-chrome.tsx +67 -27
  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/tooltip.tsx +13 -16
  264. package/src/components/user-message-body.tsx +1 -1
  265. package/src/components/workbench-changes.tsx +192 -35
  266. package/src/components/workspace-dock.tsx +13 -5
  267. package/src/composer.ts +3 -0
  268. package/src/hooks/internal.ts +132 -38
  269. package/src/hooks/use-attached-browsers.ts +152 -0
  270. package/src/hooks/use-browser-frame-stream.ts +316 -0
  271. package/src/hooks/use-browser-identities.ts +225 -0
  272. package/src/hooks/use-browser-session.ts +476 -0
  273. package/src/hooks/use-browser-sessions.ts +281 -0
  274. package/src/hooks/use-codex-accounts.ts +4 -4
  275. package/src/hooks/use-composer.ts +769 -24
  276. package/src/hooks/use-computer-frame-stream.ts +311 -0
  277. package/src/hooks/use-computer-session.ts +442 -0
  278. package/src/hooks/use-computer-sessions.ts +245 -0
  279. package/src/hooks/use-environments.ts +24 -5
  280. package/src/hooks/use-file-attachments.ts +70 -32
  281. package/src/hooks/use-goal.ts +17 -5
  282. package/src/hooks/use-machine-chip.ts +5 -3
  283. package/src/hooks/use-machines.ts +79 -3
  284. package/src/hooks/use-packs.ts +51 -0
  285. package/src/hooks/use-sandbox-files.ts +382 -88
  286. package/src/hooks/use-sandbox-git.ts +208 -46
  287. package/src/hooks/use-sandbox-terminal.ts +3 -2
  288. package/src/hooks/use-session-capabilities.ts +94 -70
  289. package/src/hooks/use-session-events.ts +18 -4
  290. package/src/hooks/use-terminal-stream.ts +102 -23
  291. package/src/hooks/use-transcription.ts +4 -14
  292. package/src/hooks/use-turn-queue.ts +17 -5
  293. package/src/hooks/use-video-artifact-playback.ts +28 -0
  294. package/src/hooks/use-voice-input.ts +163 -19
  295. package/src/hooks/use-windowed-sections.ts +9 -7
  296. package/src/hooks/use-workspace-edit.ts +2 -1
  297. package/src/index.ts +21 -6
  298. package/src/interaction.ts +71 -0
  299. package/src/lib/composer-responsive-context.ts +15 -0
  300. package/src/lib/relay-wire.ts +4 -0
  301. package/src/lib/sandbox-liveness.ts +11 -0
  302. package/src/lib/terminal-capability.ts +10 -0
  303. package/src/lib/use-portal-token-style.ts +60 -15
  304. package/src/provider.tsx +52 -7
  305. package/src/realtime/dropdown-menu.tsx +15 -19
  306. package/src/realtime/realtime-control.tsx +16 -5
  307. package/src/session-context.ts +90 -0
  308. package/src/timeline/activity-rail.tsx +31 -6
  309. package/src/timeline/index.ts +4 -1
  310. package/src/timeline/parsers.ts +46 -20
  311. package/src/timeline/projection.ts +233 -13
  312. package/src/timeline/registry.ts +19 -0
  313. package/src/timeline/screenshot-lightbox.tsx +30 -9
  314. package/src/timeline/shared.tsx +59 -11
  315. package/src/timeline/tool-renderers.tsx +421 -24
  316. package/src/timeline/turn-summary.tsx +2 -2
  317. package/src/timeline/types.ts +58 -2
  318. package/src/voice-recording-store.ts +15 -0
  319. package/styles/compiled.css +5298 -0
  320. package/styles/compiled.d.ts +2 -0
  321. package/styles/effective-tokens.css +27 -0
  322. package/styles/index.css +18 -0
  323. package/styles/responsive.css +208 -0
  324. package/styles/responsive.d.ts +2 -0
  325. package/styles/tokens.css +50 -17
  326. package/dist/chunk-3PB7MIT6.js.map +0 -1
  327. package/dist/chunk-6EDV6YFE.js.map +0 -1
  328. package/dist/chunk-EB67J347.js.map +0 -1
  329. package/dist/chunk-EKZH6IDG.js.map +0 -1
  330. package/dist/chunk-FGZUCXZF.js.map +0 -1
  331. package/dist/chunk-HFO4ERGQ.js.map +0 -1
  332. package/dist/chunk-JB5725BI.js +0 -48
  333. package/dist/chunk-JB5725BI.js.map +0 -1
  334. package/dist/chunk-LGVMUIRV.js.map +0 -1
  335. package/dist/chunk-LWR4MXSS.js.map +0 -1
  336. package/dist/chunk-SRFUT2ZU.js.map +0 -1
  337. package/dist/chunk-U3CSCMWA.js.map +0 -1
  338. package/dist/chunk-VCBFUSUZ.js.map +0 -1
  339. package/dist/fleet-decision-projection-SLNZMS42.js +0 -241
  340. package/dist/fleet-decision-projection-SLNZMS42.js.map +0 -1
  341. package/dist/queue-surface-implementation-J4XJFKRW.js.map +0 -1
@@ -2,6 +2,51 @@ import type { SessionEvent } from "@opengeni/sdk";
2
2
  import { useCallback, useEffect, useLayoutEffect, useRef, useState } from "react";
3
3
  import type { EmbeddedSessionEventClientLike } from "../client";
4
4
 
5
+ const HIDDEN_STREAM_GRACE_MS = 2_000;
6
+
7
+ /**
8
+ * Keep live browser work active while the page is visible, then suspend it
9
+ * after a short hidden-tab grace period. Returning to the page re-enables the
10
+ * caller, which must reconcile durable state before resuming live delivery.
11
+ */
12
+ export function usePageLiveActivity(hiddenGraceMs = HIDDEN_STREAM_GRACE_MS): boolean {
13
+ // Keep the server and initial browser render identical; the effect applies
14
+ // visibility after mount and preserves the intentional hidden-tab grace.
15
+ const [active, setActive] = useState(true);
16
+
17
+ useEffect(() => {
18
+ if (typeof document === "undefined") return;
19
+ let hiddenTimer: ReturnType<typeof setTimeout> | null = null;
20
+ const cancelHiddenTimer = () => {
21
+ if (hiddenTimer !== null) {
22
+ clearTimeout(hiddenTimer);
23
+ hiddenTimer = null;
24
+ }
25
+ };
26
+ const sync = () => {
27
+ cancelHiddenTimer();
28
+ if (document.visibilityState !== "hidden") {
29
+ setActive(true);
30
+ return;
31
+ }
32
+ hiddenTimer = setTimeout(() => {
33
+ hiddenTimer = null;
34
+ if (document.visibilityState === "hidden") setActive(false);
35
+ }, hiddenGraceMs);
36
+ };
37
+ document.addEventListener("visibilitychange", sync);
38
+ window.addEventListener("pageshow", sync);
39
+ sync();
40
+ return () => {
41
+ cancelHiddenTimer();
42
+ document.removeEventListener("visibilitychange", sync);
43
+ window.removeEventListener("pageshow", sync);
44
+ };
45
+ }, [hiddenGraceMs]);
46
+
47
+ return active;
48
+ }
49
+
5
50
  export type AsyncListState<T> = {
6
51
  data: T | null;
7
52
  loading: boolean;
@@ -19,6 +64,7 @@ export function usePolledValue<T>(
19
64
  ): AsyncListState<T> {
20
65
  const enabled = options.enabled ?? true;
21
66
  const pollIntervalMs = options.pollIntervalMs;
67
+ const pageLive = usePageLiveActivity();
22
68
  const [data, setData] = useState<T | null>(null);
23
69
  const [loading, setLoading] = useState(enabled);
24
70
  const [error, setError] = useState<Error | null>(null);
@@ -28,6 +74,8 @@ export function usePolledValue<T>(
28
74
  activeLoadRef.current = load;
29
75
  }, [load]);
30
76
  const requestAbortRef = useRef<AbortController | null>(null);
77
+ const inFlightRef = useRef<{ load: typeof load; promise: Promise<void> } | null>(null);
78
+ const trailingRefreshRef = useRef<{ load: typeof load; promise: Promise<void> } | null>(null);
31
79
  const [stateIdentity, setStateIdentity] = useState<{ load: typeof load }>(() => ({ load }));
32
80
 
33
81
  // A new loader identity means a new query (different session/workspace/...):
@@ -40,68 +88,107 @@ export function usePolledValue<T>(
40
88
  }
41
89
  }, [load, stateIdentity.load]);
42
90
 
43
- const run = useCallback(async () => {
91
+ const run = useCallback((): Promise<void> => {
44
92
  // A callback retained by a completed mutation from the previous query must
45
93
  // not supersede or settle the current query's request.
46
- if (activeLoadRef.current !== load) return;
94
+ if (activeLoadRef.current !== load) return Promise.resolve();
95
+ const existing = inFlightRef.current;
96
+ if (existing?.load === load) return existing.promise;
47
97
  const ticket = ++generation.current;
48
- requestAbortRef.current?.abort();
49
98
  const requestAbort = new AbortController();
50
99
  requestAbortRef.current = requestAbort;
51
- try {
52
- const result = await load(requestAbort.signal);
53
- if (
54
- ticket === generation.current &&
55
- activeLoadRef.current === load &&
56
- !requestAbort.signal.aborted
57
- ) {
58
- setData(result);
59
- setError(null);
60
- setLoading(false);
61
- }
62
- } catch (cause) {
63
- if (
64
- ticket === generation.current &&
65
- activeLoadRef.current === load &&
66
- !requestAbort.signal.aborted
67
- ) {
68
- setError(cause instanceof Error ? cause : new Error(String(cause)));
69
- setLoading(false);
70
- }
71
- } finally {
72
- if (requestAbortRef.current === requestAbort) {
73
- requestAbortRef.current = null;
100
+ let promise!: Promise<void>;
101
+ promise = (async () => {
102
+ try {
103
+ const result = await load(requestAbort.signal);
104
+ if (
105
+ ticket === generation.current &&
106
+ activeLoadRef.current === load &&
107
+ !requestAbort.signal.aborted
108
+ ) {
109
+ setData(result);
110
+ setError(null);
111
+ setLoading(false);
112
+ }
113
+ } catch (cause) {
114
+ if (
115
+ ticket === generation.current &&
116
+ activeLoadRef.current === load &&
117
+ !requestAbort.signal.aborted
118
+ ) {
119
+ setError(cause instanceof Error ? cause : new Error(String(cause)));
120
+ setLoading(false);
121
+ }
122
+ } finally {
123
+ if (requestAbortRef.current === requestAbort) requestAbortRef.current = null;
124
+ if (inFlightRef.current?.promise === promise) inFlightRef.current = null;
74
125
  }
75
- }
126
+ })();
127
+ inFlightRef.current = { load, promise };
128
+ return promise;
76
129
  }, [load]);
77
130
 
131
+ const refresh = useCallback((): Promise<void> => {
132
+ const existing = inFlightRef.current;
133
+ if (existing?.load !== load) return run();
134
+ const trailing = trailingRefreshRef.current;
135
+ if (trailing?.load === load) return trailing.promise;
136
+ let promise!: Promise<void>;
137
+ promise = existing.promise.then(async () => {
138
+ if (trailingRefreshRef.current?.promise === promise) {
139
+ trailingRefreshRef.current = null;
140
+ }
141
+ if (activeLoadRef.current === load) await run();
142
+ });
143
+ trailingRefreshRef.current = { load, promise };
144
+ return promise;
145
+ }, [load, run]);
146
+
78
147
  useEffect(() => {
79
- if (!enabled) {
148
+ if (!enabled || !pageLive) {
80
149
  setLoading(false);
150
+ generation.current += 1;
151
+ requestAbortRef.current?.abort();
152
+ inFlightRef.current = null;
153
+ trailingRefreshRef.current = null;
81
154
  return;
82
155
  }
83
156
  setLoading(true);
84
- void run();
157
+ let cancelled = false;
158
+ let timer: ReturnType<typeof setTimeout> | null = null;
159
+ const schedule = () => {
160
+ if (cancelled || pollIntervalMs === undefined || pollIntervalMs <= 0) return;
161
+ timer = setTimeout(() => {
162
+ timer = null;
163
+ void run().finally(schedule);
164
+ }, pollIntervalMs);
165
+ };
166
+ void run().finally(schedule);
85
167
  if (pollIntervalMs === undefined || pollIntervalMs <= 0) {
86
168
  return () => {
169
+ cancelled = true;
87
170
  generation.current += 1;
88
171
  requestAbortRef.current?.abort();
172
+ inFlightRef.current = null;
173
+ trailingRefreshRef.current = null;
89
174
  };
90
175
  }
91
- const timer = setInterval(() => void run(), pollIntervalMs);
92
176
  return () => {
93
- clearInterval(timer);
177
+ cancelled = true;
178
+ if (timer !== null) clearTimeout(timer);
94
179
  generation.current += 1;
95
180
  requestAbortRef.current?.abort();
181
+ inFlightRef.current = null;
182
+ trailingRefreshRef.current = null;
96
183
  };
97
- }, [run, enabled, pollIntervalMs]);
184
+ }, [run, enabled, pageLive, pollIntervalMs]);
98
185
 
99
186
  const identityMatches = stateIdentity.load === load;
100
187
  return {
101
188
  data: identityMatches ? data : null,
102
189
  loading: identityMatches ? loading : enabled,
103
190
  error: identityMatches ? error : null,
104
- refresh: run,
191
+ refresh,
105
192
  };
106
193
  }
107
194
 
@@ -224,6 +311,8 @@ export function useSessionEventTrigger(
224
311
  reconcileBeforeLive?: (() => void | Promise<void>) | undefined,
225
312
  ): void {
226
313
  const enabled = options.enabled ?? true;
314
+ const pageLive = usePageLiveActivity();
315
+ const liveEnabled = enabled && pageLive;
227
316
  const events = options.events;
228
317
  const sharedFeed = events !== undefined;
229
318
  const matchRef = useRef(match);
@@ -240,7 +329,7 @@ export function useSessionEventTrigger(
240
329
 
241
330
  // Shared-log mode: scan only the unseen tail on every append.
242
331
  useEffect(() => {
243
- if (!sharedFeed || !enabled || !sessionId) {
332
+ if (!sharedFeed || !liveEnabled || !sessionId) {
244
333
  return;
245
334
  }
246
335
  const feedKey = `${workspaceId}\u0000${sessionId}`;
@@ -285,11 +374,11 @@ export function useSessionEventTrigger(
285
374
  onEventRef.current(event);
286
375
  }
287
376
  }
288
- }, [sharedFeed, enabled, events, workspaceId, sessionId]);
377
+ }, [sharedFeed, liveEnabled, events, workspaceId, sessionId]);
289
378
 
290
379
  // Self-stream mode: tail from the session's current lastSequence.
291
380
  useEffect(() => {
292
- if (sharedFeed || !enabled || !sessionId) {
381
+ if (sharedFeed || !liveEnabled || !sessionId) {
293
382
  return;
294
383
  }
295
384
  const controller = new AbortController();
@@ -302,7 +391,12 @@ export function useSessionEventTrigger(
302
391
  const stream = client.streamEvents(workspaceId, sessionId, {
303
392
  after: session.lastSequence,
304
393
  signal: controller.signal,
305
- beforeLive: async () => await reconcileBeforeLiveRef.current?.(),
394
+ onOpen: () => {
395
+ // Start reconciliation without delaying consumption of live SSE.
396
+ void Promise.resolve()
397
+ .then(() => reconcileBeforeLiveRef.current?.())
398
+ .catch(() => undefined);
399
+ },
306
400
  });
307
401
  for await (const event of stream) {
308
402
  if (matchRef.current(event)) {
@@ -317,7 +411,7 @@ export function useSessionEventTrigger(
317
411
  return () => {
318
412
  controller.abort();
319
413
  };
320
- }, [sharedFeed, enabled, client, workspaceId, sessionId]);
414
+ }, [sharedFeed, liveEnabled, client, workspaceId, sessionId]);
321
415
  }
322
416
 
323
417
  /** Debounce rapid event bursts into one trailing call (default 150ms). */
@@ -0,0 +1,152 @@
1
+ import type { AttachedBrowserDevice } from "@opengeni/sdk/interaction";
2
+ import { useCallback, useEffect, useRef, useState } from "react";
3
+ import type { EmbeddedBrowserInteractionClientLike } from "../client";
4
+ import {
5
+ type EmbeddedBrowserInteractionClientOverride,
6
+ useEmbeddedBrowserInteraction,
7
+ } from "../session-context";
8
+ import { usePageLiveActivity } from "./internal";
9
+
10
+ export type UseAttachedBrowsersOptions = EmbeddedBrowserInteractionClientOverride & {
11
+ enabled?: boolean | undefined;
12
+ includeDisconnected?: boolean | undefined;
13
+ pollIntervalMs?: number | undefined;
14
+ };
15
+
16
+ export type UseAttachedBrowsersResult = {
17
+ revision: number;
18
+ devices: AttachedBrowserDevice[];
19
+ loading: boolean;
20
+ refreshing: boolean;
21
+ error: Error | null;
22
+ refresh: () => Promise<void>;
23
+ };
24
+
25
+ /** Workspace-wide inventory of Chrome profiles currently connected through an
26
+ * enrolled OpenGeni machine. BrowserSessions remain the execution authority. */
27
+ export function useAttachedBrowsers(
28
+ options: UseAttachedBrowsersOptions = {},
29
+ ): UseAttachedBrowsersResult {
30
+ const { client, workspaceId } = useEmbeddedBrowserInteraction(options);
31
+ const enabled = options.enabled ?? true;
32
+ const includeDisconnected = options.includeDisconnected ?? false;
33
+ const pageLive = usePageLiveActivity();
34
+ const pollIntervalMs = Math.max(1_000, options.pollIntervalMs ?? 3_000);
35
+ const [state, setState] = useState(() => emptyState(workspaceId, enabled));
36
+ const requestRef = useRef<{ id: number; controller: AbortController | null }>({
37
+ id: 0,
38
+ controller: null,
39
+ });
40
+ const mountedRef = useRef(true);
41
+ const visible = state.workspaceId === workspaceId ? state : emptyState(workspaceId, enabled);
42
+
43
+ const cancelLoad = useCallback(() => {
44
+ const id = requestRef.current.id + 1;
45
+ requestRef.current.controller?.abort();
46
+ requestRef.current = { id, controller: null };
47
+ }, []);
48
+
49
+ const load = useCallback(
50
+ async (foreground: boolean): Promise<void> => {
51
+ if (!enabled) return;
52
+ const id = requestRef.current.id + 1;
53
+ requestRef.current.controller?.abort();
54
+ const controller = new AbortController();
55
+ requestRef.current = { id, controller };
56
+ setState((current) => ({
57
+ ...(current.workspaceId === workspaceId ? current : emptyState(workspaceId, true)),
58
+ loading: foreground,
59
+ refreshing: !foreground,
60
+ error: foreground || current.workspaceId !== workspaceId ? null : current.error,
61
+ }));
62
+ try {
63
+ const response = await client.listAttachedBrowsers(workspaceId, {
64
+ includeDisconnected,
65
+ signal: controller.signal,
66
+ });
67
+ if (!mountedRef.current || requestRef.current.id !== id) return;
68
+ setState({
69
+ workspaceId,
70
+ revision: response.revision,
71
+ devices: sortDevices(response.devices),
72
+ loading: false,
73
+ refreshing: false,
74
+ error: null,
75
+ });
76
+ } catch (cause) {
77
+ if (controller.signal.aborted || !mountedRef.current || requestRef.current.id !== id)
78
+ return;
79
+ setState((current) => ({
80
+ ...(current.workspaceId === workspaceId ? current : emptyState(workspaceId, true)),
81
+ loading: false,
82
+ refreshing: false,
83
+ error: cause instanceof Error ? cause : new Error(String(cause)),
84
+ }));
85
+ } finally {
86
+ if (requestRef.current.id === id) requestRef.current = { id, controller: null };
87
+ }
88
+ },
89
+ [client, enabled, includeDisconnected, workspaceId],
90
+ );
91
+
92
+ const refresh = useCallback(async () => await load(false), [load]);
93
+
94
+ useEffect(() => {
95
+ mountedRef.current = true;
96
+ if (!enabled) {
97
+ cancelLoad();
98
+ setState(emptyState(workspaceId, false));
99
+ return;
100
+ }
101
+ void load(true);
102
+ return () => {
103
+ mountedRef.current = false;
104
+ cancelLoad();
105
+ };
106
+ }, [cancelLoad, enabled, load, workspaceId]);
107
+
108
+ useEffect(() => {
109
+ if (!enabled || !pageLive) return;
110
+ const timer = setInterval(() => {
111
+ if (!requestRef.current.controller) void load(false);
112
+ }, pollIntervalMs);
113
+ return () => clearInterval(timer);
114
+ }, [enabled, load, pageLive, pollIntervalMs]);
115
+
116
+ return {
117
+ revision: visible.revision,
118
+ devices: visible.devices,
119
+ loading: visible.loading,
120
+ refreshing: visible.refreshing,
121
+ error: visible.error,
122
+ refresh,
123
+ };
124
+ }
125
+
126
+ function emptyState(workspaceId: string, loading: boolean) {
127
+ return {
128
+ workspaceId,
129
+ revision: 0,
130
+ devices: [] as AttachedBrowserDevice[],
131
+ loading,
132
+ refreshing: false,
133
+ error: null as Error | null,
134
+ };
135
+ }
136
+
137
+ function sortDevices(devices: readonly AttachedBrowserDevice[]): AttachedBrowserDevice[] {
138
+ return [...devices].sort(
139
+ (left, right) =>
140
+ Number(right.state === "connected") - Number(left.state === "connected") ||
141
+ timestamp(right.lastSeenAt) - timestamp(left.lastSeenAt) ||
142
+ left.name.localeCompare(right.name),
143
+ );
144
+ }
145
+
146
+ function timestamp(value: string): number {
147
+ const parsed = Date.parse(value);
148
+ return Number.isFinite(parsed) ? parsed : 0;
149
+ }
150
+
151
+ /** Exported for narrow proxy implementations and tests. */
152
+ export type AttachedBrowsersClient = EmbeddedBrowserInteractionClientLike;