@opengeni/react 1.0.1 → 1.0.2-canary.37202341988001

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 (632) hide show
  1. package/README.md +660 -21
  2. package/dist/accounts.d.ts +64 -0
  3. package/dist/accounts.js +822 -0
  4. package/dist/accounts.js.map +1 -0
  5. package/dist/approvals.d.ts +2 -1
  6. package/dist/artifacts-document.js +3 -2
  7. package/dist/artifacts-presentation.js +3 -2
  8. package/dist/artifacts-spreadsheet.js +2 -2
  9. package/dist/artifacts.d.ts +4 -1
  10. package/dist/artifacts.js +518 -42
  11. package/dist/artifacts.js.map +1 -1
  12. package/dist/browser-viewer-SHYKQRNX.js +16 -0
  13. package/dist/capability-catalog-row.d.ts +20 -0
  14. package/dist/capability-logo-fallback.d.ts +3 -0
  15. package/dist/chat-interactive-block-UGBTNB3L.js +11 -0
  16. package/dist/chat-interactive-block-UGBTNB3L.js.map +1 -0
  17. package/dist/chunk-26ZNDDLZ.js +453 -0
  18. package/dist/chunk-26ZNDDLZ.js.map +1 -0
  19. package/dist/chunk-3CTRFMAK.js +359 -0
  20. package/dist/chunk-3CTRFMAK.js.map +1 -0
  21. package/dist/chunk-447JLCBU.js +209 -0
  22. package/dist/chunk-447JLCBU.js.map +1 -0
  23. package/dist/chunk-4RVYOY4O.js +379 -0
  24. package/dist/chunk-4RVYOY4O.js.map +1 -0
  25. package/dist/chunk-5CVXZA3Y.js +292 -0
  26. package/dist/chunk-5CVXZA3Y.js.map +1 -0
  27. package/dist/chunk-5J4FOW6Q.js +108 -0
  28. package/dist/chunk-5J4FOW6Q.js.map +1 -0
  29. package/dist/chunk-742CZ3L3.js +481 -0
  30. package/dist/chunk-742CZ3L3.js.map +1 -0
  31. package/dist/chunk-A2PI6UMY.js +41 -0
  32. package/dist/chunk-A2PI6UMY.js.map +1 -0
  33. package/dist/{chunk-7WCLAEFV.js → chunk-A4UJB2MM.js} +37 -27
  34. package/dist/chunk-A4UJB2MM.js.map +1 -0
  35. package/dist/chunk-B3V6TG77.js +41 -0
  36. package/dist/chunk-B3V6TG77.js.map +1 -0
  37. package/dist/chunk-BRM6FVZA.js +302 -0
  38. package/dist/chunk-BRM6FVZA.js.map +1 -0
  39. package/dist/chunk-C5X5AJXK.js +182 -0
  40. package/dist/chunk-C5X5AJXK.js.map +1 -0
  41. package/dist/{chunk-6KLX2RH5.js → chunk-DO544OSV.js} +1075 -79
  42. package/dist/chunk-DO544OSV.js.map +1 -0
  43. package/dist/{chunk-R3AZOAB5.js → chunk-DYWBUX5Q.js} +29 -123
  44. package/dist/chunk-DYWBUX5Q.js.map +1 -0
  45. package/dist/{chunk-HA46CYQK.js → chunk-EESD3PY3.js} +792 -537
  46. package/dist/chunk-EESD3PY3.js.map +1 -0
  47. package/dist/{chunk-5UQPNLIO.js → chunk-EN3YTHKR.js} +4117 -3896
  48. package/dist/chunk-EN3YTHKR.js.map +1 -0
  49. package/dist/chunk-ERGYJIBB.js +21 -0
  50. package/dist/chunk-ERGYJIBB.js.map +1 -0
  51. package/dist/chunk-FZCP527Y.js +116 -0
  52. package/dist/chunk-FZCP527Y.js.map +1 -0
  53. package/dist/chunk-GIMRELBW.js +14 -0
  54. package/dist/chunk-GIMRELBW.js.map +1 -0
  55. package/dist/chunk-GYVRFZDY.js +2733 -0
  56. package/dist/chunk-GYVRFZDY.js.map +1 -0
  57. package/dist/{chunk-LLOLUHVG.js → chunk-HDJWDK7W.js} +1358 -2746
  58. package/dist/chunk-HDJWDK7W.js.map +1 -0
  59. package/dist/chunk-I7IWRQLN.js +1608 -0
  60. package/dist/chunk-I7IWRQLN.js.map +1 -0
  61. package/dist/chunk-IT4BJUGF.js +13055 -0
  62. package/dist/chunk-IT4BJUGF.js.map +1 -0
  63. package/dist/chunk-IWIDV7SB.js +1 -0
  64. package/dist/chunk-IWIDV7SB.js.map +1 -0
  65. package/dist/chunk-J2QWVSQY.js +312 -0
  66. package/dist/chunk-J2QWVSQY.js.map +1 -0
  67. package/dist/chunk-J6DBS4T3.js +371 -0
  68. package/dist/chunk-J6DBS4T3.js.map +1 -0
  69. package/dist/{chunk-B6WPZSTT.js → chunk-JIQYUGVL.js} +1331 -1163
  70. package/dist/chunk-JIQYUGVL.js.map +1 -0
  71. package/dist/chunk-JT4JW7BC.js +54 -0
  72. package/dist/chunk-JT4JW7BC.js.map +1 -0
  73. package/dist/chunk-K3HQAKQX.js +24 -0
  74. package/dist/chunk-K3HQAKQX.js.map +1 -0
  75. package/dist/{chunk-PEI6RANI.js → chunk-PNY3JKBJ.js} +309 -74
  76. package/dist/chunk-PNY3JKBJ.js.map +1 -0
  77. package/dist/chunk-RHREZ7BO.js +11 -0
  78. package/dist/chunk-RHREZ7BO.js.map +1 -0
  79. package/dist/chunk-RLUSC2WA.js +299 -0
  80. package/dist/chunk-RLUSC2WA.js.map +1 -0
  81. package/dist/{chunk-IGC6A2ZI.js → chunk-RSI7XYXL.js} +777 -246
  82. package/dist/chunk-RSI7XYXL.js.map +1 -0
  83. package/dist/chunk-SCOTPE2I.js +589 -0
  84. package/dist/chunk-SCOTPE2I.js.map +1 -0
  85. package/dist/{chunk-C5ZPSSS5.js → chunk-TKOTKRWY.js} +5 -4
  86. package/dist/{chunk-C5ZPSSS5.js.map → chunk-TKOTKRWY.js.map} +1 -1
  87. package/dist/chunk-TMYGL6X5.js +742 -0
  88. package/dist/chunk-TMYGL6X5.js.map +1 -0
  89. package/dist/chunk-UGLEYEMP.js +33 -0
  90. package/dist/chunk-UGLEYEMP.js.map +1 -0
  91. package/dist/chunk-UK4TINKT.js +27 -0
  92. package/dist/chunk-UK4TINKT.js.map +1 -0
  93. package/dist/chunk-UTRTG6LH.js +220 -0
  94. package/dist/chunk-UTRTG6LH.js.map +1 -0
  95. package/dist/chunk-WEHMPOXK.js +39 -0
  96. package/dist/chunk-WEHMPOXK.js.map +1 -0
  97. package/dist/chunk-WGXVEJO5.js +319 -0
  98. package/dist/chunk-WGXVEJO5.js.map +1 -0
  99. package/dist/chunk-WRHIZYZF.js +988 -0
  100. package/dist/chunk-WRHIZYZF.js.map +1 -0
  101. package/dist/{chunk-HUIKIDM5.js → chunk-WUISZ3WU.js} +223 -115
  102. package/dist/chunk-WUISZ3WU.js.map +1 -0
  103. package/dist/chunk-XCHWDJ22.js +107 -0
  104. package/dist/chunk-XCHWDJ22.js.map +1 -0
  105. package/dist/{chunk-KCGXPP5V.js → chunk-Y2PRMYLC.js} +11 -6
  106. package/dist/chunk-Y2PRMYLC.js.map +1 -0
  107. package/dist/chunk-YBXQDQFX.js +1772 -0
  108. package/dist/chunk-YBXQDQFX.js.map +1 -0
  109. package/dist/chunk-YM2DE7BQ.js +3124 -0
  110. package/dist/chunk-YM2DE7BQ.js.map +1 -0
  111. package/dist/{chunk-KZ73RL76.js → chunk-YQEAA6LT.js} +7 -3
  112. package/dist/chunk-YQEAA6LT.js.map +1 -0
  113. package/dist/chunk-YV4L3ILF.js +9 -0
  114. package/dist/chunk-YV4L3ILF.js.map +1 -0
  115. package/dist/chunk-ZYRGEKWS.js +216 -0
  116. package/dist/chunk-ZYRGEKWS.js.map +1 -0
  117. package/dist/client.d.ts +7 -4
  118. package/dist/clipboard.js +1 -0
  119. package/dist/commands/types.d.ts +2 -0
  120. package/dist/components/anthropic-mark.d.ts +3 -0
  121. package/dist/components/approval-surface.d.ts +15 -0
  122. package/dist/components/artifacts/artifact-chrome.d.ts +120 -0
  123. package/dist/components/artifacts/artifact-sandbox.d.ts +27 -0
  124. package/dist/components/artifacts/artifact-surface.d.ts +3 -1
  125. package/dist/components/artifacts/chat-interactive-block.d.ts +29 -0
  126. package/dist/components/artifacts/deferred-chat-media.d.ts +15 -0
  127. package/dist/components/artifacts/document-editor.d.ts +4 -2
  128. package/dist/components/artifacts/editable-artifact-ui.d.ts +2 -1
  129. package/dist/components/artifacts/editable-artifact-view.d.ts +46 -0
  130. package/dist/components/artifacts/editable-document.d.ts +1 -1
  131. package/dist/components/artifacts/editable-presentation.d.ts +2 -2
  132. package/dist/components/artifacts/editable-spreadsheet.d.ts +2 -1
  133. package/dist/components/artifacts/index.d.ts +8 -1
  134. package/dist/components/artifacts/inline-html-document.d.ts +2 -0
  135. package/dist/components/artifacts/latest-task-runner.d.ts +15 -0
  136. package/dist/components/artifacts/presentation-editor.d.ts +6 -2
  137. package/dist/components/artifacts/published-html-artifact-frame.d.ts +48 -3
  138. package/dist/components/artifacts/session-artifact-viewer.d.ts +39 -0
  139. package/dist/components/artifacts/site-view.d.ts +23 -0
  140. package/dist/components/artifacts/spreadsheet-general-display.d.ts +24 -0
  141. package/dist/components/artifacts/visualization-assets.gen.d.ts +2 -0
  142. package/dist/components/browser-select-control.d.ts +16 -0
  143. package/dist/components/browser-viewer.d.ts +10 -1
  144. package/dist/components/chat-composer.d.ts +21 -3
  145. package/dist/components/child-session-link.d.ts +5 -0
  146. package/dist/components/claude-mark.d.ts +3 -0
  147. package/dist/components/code-editor.d.ts +1 -4
  148. package/dist/components/composer.d.ts +45 -4
  149. package/dist/components/computer-viewer.d.ts +4 -1
  150. package/dist/components/copy-button.d.ts +3 -1
  151. package/dist/components/desktop-viewer.d.ts +1 -1
  152. package/dist/components/enrollment-consent.d.ts +6 -3
  153. package/dist/components/file-browser.d.ts +10 -1
  154. package/dist/components/fleet-tile.d.ts +2 -2
  155. package/dist/components/grok-mark.d.ts +26 -0
  156. package/dist/components/human-input-form.d.ts +21 -2
  157. package/dist/components/human-input-surface.d.ts +6 -3
  158. package/dist/components/machine-card.d.ts +4 -1
  159. package/dist/components/machines/machine-detail.d.ts +4 -1
  160. package/dist/components/machines/machine-operation-policy.d.ts +11 -0
  161. package/dist/components/machines-dashboard.d.ts +3 -1
  162. package/dist/components/markdown-table-layout.d.ts +12 -0
  163. package/dist/components/markdown.d.ts +47 -3
  164. package/dist/components/message-timeline.d.ts +72 -9
  165. package/dist/components/model-mark.d.ts +29 -0
  166. package/dist/components/model-policy-picker-menu.d.ts +9 -0
  167. package/dist/components/model-policy-picker.d.ts +62 -13
  168. package/dist/components/open-geni-chat.d.ts +58 -0
  169. package/dist/components/open-geni-links.d.ts +68 -0
  170. package/dist/components/openrouter-mark.d.ts +6 -0
  171. package/dist/components/pierre-file.d.ts +1 -1
  172. package/dist/components/preview-loading.d.ts +2 -0
  173. package/dist/components/queue-item-content.d.ts +9 -0
  174. package/dist/components/sandbox-files.d.ts +13 -2
  175. package/dist/components/sandbox-workspace.d.ts +43 -3
  176. package/dist/components/session-capability-frame.d.ts +35 -0
  177. package/dist/components/session-chrome.d.ts +66 -8
  178. package/dist/components/session-commands-panel.d.ts +6 -0
  179. package/dist/components/session-commands.d.ts +6 -0
  180. package/dist/components/session-connection-request.d.ts +8 -0
  181. package/dist/components/session-conversation.d.ts +89 -0
  182. package/dist/components/session-list.d.ts +36 -0
  183. package/dist/components/session-mcp-capability-card.d.ts +41 -0
  184. package/dist/components/session-proxy-scope.d.ts +21 -0
  185. package/dist/components/timeline-anchor.d.ts +24 -0
  186. package/dist/components/timeline-annotation-chrome.d.ts +25 -0
  187. package/dist/components/timeline-annotation-layout.d.ts +42 -0
  188. package/dist/components/timeline-annotation-markers.d.ts +8 -0
  189. package/dist/components/timeline-annotation-reveal-context.d.ts +4 -0
  190. package/dist/components/timeline-annotation-shared.d.ts +31 -0
  191. package/dist/components/timeline-annotations-dialog.d.ts +3 -2
  192. package/dist/components/timeline-annotations.d.ts +13 -3
  193. package/dist/components/timeline-reading-progress.d.ts +11 -0
  194. package/dist/components/timeline-search.d.ts +34 -0
  195. package/dist/components/timeline-settlement.d.ts +16 -0
  196. package/dist/components/tip-follow.d.ts +16 -0
  197. package/dist/components/usage-limit-notice.d.ts +49 -0
  198. package/dist/components/usage-member-list.d.ts +91 -0
  199. package/dist/components/usage-meter.d.ts +73 -0
  200. package/dist/components/use-viewer-menu-dismiss.d.ts +2 -0
  201. package/dist/components/user-message-body.d.ts +10 -1
  202. package/dist/components/workspace-dock.d.ts +6 -2
  203. package/dist/composer.d.ts +5 -4
  204. package/dist/composer.js +15 -9
  205. package/dist/computer-viewer-6SRGWRUX.js +19 -0
  206. package/dist/computer-viewer-6SRGWRUX.js.map +1 -0
  207. package/dist/connect-account-identity.d.ts +8 -0
  208. package/dist/connect-accounts.d.ts +15 -0
  209. package/dist/connect-chooser.d.ts +18 -0
  210. package/dist/connect-panel.d.ts +18 -0
  211. package/dist/connect-setup.d.ts +16 -0
  212. package/dist/connect.d.ts +21 -0
  213. package/dist/connect.js +2129 -0
  214. package/dist/connect.js.map +1 -0
  215. package/dist/connection-catalog.d.ts +38 -0
  216. package/dist/connection-discovery.d.ts +12 -0
  217. package/dist/connection-installed.d.ts +17 -0
  218. package/dist/connection-logo.d.ts +9 -0
  219. package/dist/connection-service-logo.d.ts +8 -0
  220. package/dist/connection-service-presentation.d.ts +13 -0
  221. package/dist/connection-skeleton.d.ts +5 -0
  222. package/dist/connection-type-picker.d.ts +6 -0
  223. package/dist/conversation-timeline.d.ts +5 -0
  224. package/dist/desktop.d.ts +7 -0
  225. package/dist/desktop.js +26 -0
  226. package/dist/desktop.js.map +1 -0
  227. package/dist/device-authorization.d.ts +23 -0
  228. package/dist/diffs.d.ts +8 -0
  229. package/dist/diffs.js +14 -0
  230. package/dist/diffs.js.map +1 -0
  231. package/dist/discovery-cache.d.ts +5 -0
  232. package/dist/editor.d.ts +7 -0
  233. package/dist/editor.js +21 -0
  234. package/dist/editor.js.map +1 -0
  235. package/dist/embedded-session-client.d.ts +23 -0
  236. package/dist/file-node-visibility.d.ts +15 -0
  237. package/dist/{fleet-decision-row-GGCAT4E4.js → fleet-decision-row-YZ3ZKWMM.js} +2 -1
  238. package/dist/fleet-decision-row-YZ3ZKWMM.js.map +1 -0
  239. package/dist/hooks/channel-cache.d.ts +23 -0
  240. package/dist/hooks/latest-question.d.ts +25 -0
  241. package/dist/hooks/shared-machines-list.d.ts +14 -0
  242. package/dist/hooks/use-attached-browsers.d.ts +2 -1
  243. package/dist/hooks/use-browser-frame-stream.d.ts +1 -1
  244. package/dist/hooks/use-browser-session.d.ts +12 -1
  245. package/dist/hooks/use-channels.d.ts +1 -0
  246. package/dist/hooks/use-codex-accounts.d.ts +25 -7
  247. package/dist/hooks/use-composer.d.ts +45 -9
  248. package/dist/hooks/use-computer-frame-stream.d.ts +1 -1
  249. package/dist/hooks/use-computer-input-posture.d.ts +13 -0
  250. package/dist/hooks/use-computer-session.d.ts +2 -0
  251. package/dist/hooks/use-connect.d.ts +22 -0
  252. package/dist/hooks/use-desktop-stream.d.ts +1 -1
  253. package/dist/hooks/use-file-attachments.d.ts +21 -2
  254. package/dist/hooks/use-machine-chip.d.ts +3 -3
  255. package/dist/hooks/use-machines.d.ts +19 -1
  256. package/dist/hooks/use-sandbox-files.d.ts +5 -1
  257. package/dist/hooks/use-session-background-commands.d.ts +14 -0
  258. package/dist/hooks/use-session-events.d.ts +30 -8
  259. package/dist/hooks/use-session-lineage.d.ts +4 -0
  260. package/dist/hooks/use-session-mcp-approval-policy.d.ts +2 -2
  261. package/dist/hooks/use-session.d.ts +6 -0
  262. package/dist/hooks/use-turn-queue.d.ts +12 -0
  263. package/dist/hooks/use-usage.d.ts +40 -0
  264. package/dist/hooks/use-workspace-edit.d.ts +4 -0
  265. package/dist/hooks/use-workspace-sessions.d.ts +27 -5
  266. package/dist/human-input.d.ts +3 -1
  267. package/dist/identity-link-accounts.d.ts +11 -0
  268. package/dist/identity-link-consent.d.ts +18 -0
  269. package/dist/index.d.ts +60 -17
  270. package/dist/index.js +2238 -6201
  271. package/dist/index.js.map +1 -1
  272. package/dist/interaction.d.ts +1 -1
  273. package/dist/interaction.js +35 -184
  274. package/dist/interaction.js.map +1 -1
  275. package/dist/latest-question-HTRIS4KJ.js +117 -0
  276. package/dist/latest-question-HTRIS4KJ.js.map +1 -0
  277. package/dist/lib/error-message.d.ts +7 -0
  278. package/dist/lib/format.d.ts +36 -2
  279. package/dist/lib/host-theme.d.ts +70 -0
  280. package/dist/lib/interaction-errors.d.ts +6 -0
  281. package/dist/lib/menu-styles.d.ts +22 -0
  282. package/dist/lib/pierre-diffs-loader.d.ts +20 -0
  283. package/dist/lib/workbench-peers.d.ts +36 -0
  284. package/dist/lib/xterm-theme.d.ts +7 -5
  285. package/dist/machines.d.ts +4 -2
  286. package/dist/machines.js +14 -4
  287. package/dist/markdown-table-layout-QXSLK4BY.js +106 -0
  288. package/dist/markdown-table-layout-QXSLK4BY.js.map +1 -0
  289. package/dist/mcp-connection-status.d.ts +14 -0
  290. package/dist/model-policy-picker-menu-M54ISBMN.js +14 -0
  291. package/dist/model-policy-picker-menu-M54ISBMN.js.map +1 -0
  292. package/dist/model-policy.d.ts +19 -3
  293. package/dist/model-policy.js +9 -1
  294. package/dist/older-history.d.ts +30 -0
  295. package/dist/platform-activity-row-627BFZ46.js +182 -0
  296. package/dist/platform-activity-row-627BFZ46.js.map +1 -0
  297. package/dist/plugin-details.d.ts +14 -0
  298. package/dist/plugin-discovery.d.ts +25 -0
  299. package/dist/provider.d.ts +13 -1
  300. package/dist/{queue-surface-implementation-2KVJTYXH.js → queue-surface-implementation-ZHFI5JBP.js} +131 -44
  301. package/dist/queue-surface-implementation-ZHFI5JBP.js.map +1 -0
  302. package/dist/realtime/realtime-control.d.ts +6 -3
  303. package/dist/realtime.js +133 -74
  304. package/dist/realtime.js.map +1 -1
  305. package/dist/sandbox-files-GORZOUUJ.js +20 -0
  306. package/dist/sandbox-files-GORZOUUJ.js.map +1 -0
  307. package/dist/session-auth-recommendation.d.ts +5 -0
  308. package/dist/session-capability-policy.d.ts +12 -0
  309. package/dist/session-ui.d.ts +30 -2
  310. package/dist/session-ui.js +85 -7
  311. package/dist/session-ui.js.map +1 -1
  312. package/dist/session.d.ts +8 -2
  313. package/dist/session.js +30 -19
  314. package/dist/sites-ui.d.ts +34 -0
  315. package/dist/sites.d.ts +3 -0
  316. package/dist/sites.js +11 -0
  317. package/dist/sites.js.map +1 -0
  318. package/dist/skill-discovery.d.ts +47 -0
  319. package/dist/terminal.d.ts +8 -0
  320. package/dist/terminal.js +31 -0
  321. package/dist/terminal.js.map +1 -0
  322. package/dist/timeline/activity-rail.d.ts +11 -2
  323. package/dist/timeline/allowance-exhausted-row.d.ts +24 -0
  324. package/dist/timeline/compaction-copy.d.ts +10 -0
  325. package/dist/timeline/entrance.d.ts +13 -1
  326. package/dist/timeline/genie-loading.d.ts +34 -0
  327. package/dist/timeline/index.d.ts +6 -2
  328. package/dist/timeline/knowledge-receipt.d.ts +17 -0
  329. package/dist/timeline/platform-activity-row.d.ts +14 -0
  330. package/dist/timeline/presented-image.d.ts +6 -0
  331. package/dist/timeline/projection.d.ts +31 -2
  332. package/dist/timeline/registry.d.ts +2 -0
  333. package/dist/timeline/retained-image.d.ts +18 -0
  334. package/dist/timeline/rolling-activity.d.ts +11 -0
  335. package/dist/timeline/screenshot-lightbox.d.ts +16 -1
  336. package/dist/timeline/shared.d.ts +8 -3
  337. package/dist/timeline/startup-preference.d.ts +3 -0
  338. package/dist/timeline/startup-timings.d.ts +4 -0
  339. package/dist/timeline/tool-display-name.d.ts +2 -1
  340. package/dist/timeline/turn-summary.d.ts +34 -3
  341. package/dist/timeline/types.d.ts +147 -7
  342. package/dist/timeline-annotation-markers-GNDHTGBD.js +165 -0
  343. package/dist/timeline-annotation-markers-GNDHTGBD.js.map +1 -0
  344. package/dist/timeline-annotation-selection-DMLMM2TX.js +241 -0
  345. package/dist/timeline-annotation-selection-DMLMM2TX.js.map +1 -0
  346. package/dist/timeline-annotations-dialog-ELRDBHTS.js +273 -0
  347. package/dist/timeline-annotations-dialog-ELRDBHTS.js.map +1 -0
  348. package/dist/types/machines.d.ts +2 -2
  349. package/dist/usage/allowance-copy.d.ts +25 -0
  350. package/dist/usage/summary.d.ts +35 -0
  351. package/dist/usage.d.ts +13 -0
  352. package/dist/usage.js +1074 -0
  353. package/dist/usage.js.map +1 -0
  354. package/package.json +44 -4
  355. package/src/accounts.tsx +1072 -0
  356. package/src/approvals.ts +8 -1
  357. package/src/artifacts.ts +42 -0
  358. package/src/assets/README.md +8 -0
  359. package/src/assets/gmail.ico +0 -0
  360. package/src/assets/outlook.ico +0 -0
  361. package/src/capability-catalog-row.tsx +94 -0
  362. package/src/capability-logo-fallback.ts +22 -0
  363. package/src/client.ts +22 -13
  364. package/src/commands/registry.ts +18 -15
  365. package/src/commands/types.ts +2 -0
  366. package/src/components/anthropic-mark.tsx +10 -0
  367. package/src/components/approval-surface.tsx +171 -37
  368. package/src/components/artifacts/artifact-chrome.tsx +413 -0
  369. package/src/components/artifacts/artifact-sandbox.tsx +185 -0
  370. package/src/components/artifacts/artifact-surface.tsx +39 -19
  371. package/src/components/artifacts/chat-interactive-block.tsx +260 -0
  372. package/src/components/artifacts/deferred-chat-media.tsx +67 -0
  373. package/src/components/artifacts/document-editor.tsx +141 -16
  374. package/src/components/artifacts/editable-artifact-ui.tsx +4 -2
  375. package/src/components/artifacts/editable-artifact-view.tsx +187 -0
  376. package/src/components/artifacts/editable-artifact-workbench.tsx +5 -2
  377. package/src/components/artifacts/editable-document.tsx +37 -36
  378. package/src/components/artifacts/editable-presentation.tsx +36 -18
  379. package/src/components/artifacts/editable-spreadsheet.tsx +5 -2
  380. package/src/components/artifacts/index.ts +44 -0
  381. package/src/components/artifacts/inline-html-document.ts +24 -0
  382. package/src/components/artifacts/latest-task-runner.ts +49 -0
  383. package/src/components/artifacts/presentation-editor.tsx +323 -67
  384. package/src/components/artifacts/published-html-artifact-frame.tsx +338 -5
  385. package/src/components/artifacts/session-artifact-viewer.tsx +376 -0
  386. package/src/components/artifacts/site-view.tsx +116 -0
  387. package/src/components/artifacts/spreadsheet-canvas.ts +5 -10
  388. package/src/components/artifacts/spreadsheet-general-display.ts +43 -0
  389. package/src/components/artifacts/spreadsheet-grid.tsx +12 -13
  390. package/src/components/artifacts/visualization-assets/base.css +1015 -0
  391. package/src/components/artifacts/visualization-assets/helpers.html +349 -0
  392. package/src/components/artifacts/visualization-assets.gen.ts +3 -0
  393. package/src/components/browser-select-control.tsx +312 -0
  394. package/src/components/browser-viewer.tsx +1054 -314
  395. package/src/components/chat-composer.tsx +131 -85
  396. package/src/components/child-session-link.tsx +27 -0
  397. package/src/components/claude-mark.tsx +10 -0
  398. package/src/components/code-editor.tsx +13 -16
  399. package/src/components/composer-transcription-control.tsx +7 -5
  400. package/src/components/composer.tsx +376 -49
  401. package/src/components/computer-viewer.tsx +1041 -329
  402. package/src/components/copy-button.tsx +13 -5
  403. package/src/components/desktop-viewer.tsx +1 -1
  404. package/src/components/enrollment-consent.tsx +65 -7
  405. package/src/components/enrollment-device-flow.tsx +1 -1
  406. package/src/components/file-browser.tsx +158 -34
  407. package/src/components/fleet-tile.tsx +8 -16
  408. package/src/components/generated-video-player.tsx +4 -2
  409. package/src/components/grok-mark.tsx +33 -0
  410. package/src/components/human-input-form.tsx +455 -114
  411. package/src/components/human-input-surface.tsx +18 -7
  412. package/src/components/interaction-intervention-banner.tsx +2 -2
  413. package/src/components/machine-card.tsx +119 -4
  414. package/src/components/machine-dock-bar.tsx +1 -1
  415. package/src/components/machine-input-display.ts +23 -2
  416. package/src/components/machines/machine-detail.tsx +52 -2
  417. package/src/components/machines/machine-operation-policy.tsx +179 -0
  418. package/src/components/machines-dashboard.tsx +8 -17
  419. package/src/components/markdown-table-layout.ts +132 -0
  420. package/src/components/markdown.tsx +473 -44
  421. package/src/components/message-timeline.tsx +2754 -665
  422. package/src/components/model-mark.tsx +90 -0
  423. package/src/components/model-picker.tsx +15 -4
  424. package/src/components/model-policy-picker-menu.tsx +424 -0
  425. package/src/components/model-policy-picker.tsx +337 -381
  426. package/src/components/open-geni-chat.tsx +338 -0
  427. package/src/components/open-geni-links.tsx +179 -0
  428. package/src/components/openrouter-mark.tsx +13 -0
  429. package/src/components/pierre-diff.tsx +41 -16
  430. package/src/components/pierre-file.tsx +36 -11
  431. package/src/components/preview-loading.tsx +154 -0
  432. package/src/components/queue-item-content.ts +14 -0
  433. package/src/components/queue-surface-implementation.tsx +132 -42
  434. package/src/components/queue-surface-state.tsx +3 -1
  435. package/src/components/sandbox-files.tsx +267 -35
  436. package/src/components/sandbox-terminal.tsx +48 -11
  437. package/src/components/sandbox-workspace.tsx +337 -135
  438. package/src/components/session-capability-frame.tsx +192 -0
  439. package/src/components/session-chrome.tsx +1027 -328
  440. package/src/components/session-commands-panel.tsx +126 -0
  441. package/src/components/session-commands.tsx +18 -0
  442. package/src/components/session-connection-request.tsx +97 -0
  443. package/src/components/session-conversation.tsx +526 -0
  444. package/src/components/session-list.tsx +250 -0
  445. package/src/components/session-mcp-capability-card.tsx +680 -0
  446. package/src/components/session-proxy-scope.tsx +92 -0
  447. package/src/components/session-status.tsx +1 -1
  448. package/src/components/timeline-anchor.tsx +119 -0
  449. package/src/components/timeline-annotation-chrome.tsx +196 -0
  450. package/src/components/timeline-annotation-layout.ts +268 -0
  451. package/src/components/timeline-annotation-markers.tsx +185 -0
  452. package/src/components/timeline-annotation-reveal-context.ts +9 -0
  453. package/src/components/timeline-annotation-selection.tsx +106 -54
  454. package/src/components/timeline-annotation-shared.ts +245 -0
  455. package/src/components/timeline-annotations-dialog.tsx +185 -128
  456. package/src/components/timeline-annotations.tsx +189 -45
  457. package/src/components/timeline-reading-progress.ts +80 -0
  458. package/src/components/timeline-search.tsx +168 -0
  459. package/src/components/timeline-settlement.ts +60 -0
  460. package/src/components/tip-follow.ts +70 -7
  461. package/src/components/tooltip.tsx +38 -5
  462. package/src/components/usage-limit-notice.tsx +179 -0
  463. package/src/components/usage-member-list.tsx +792 -0
  464. package/src/components/usage-meter.tsx +382 -0
  465. package/src/components/use-viewer-menu-dismiss.ts +33 -0
  466. package/src/components/user-message-body.tsx +194 -49
  467. package/src/components/workspace-dock.tsx +141 -175
  468. package/src/composer.ts +6 -1
  469. package/src/connect-account-identity.tsx +34 -0
  470. package/src/connect-accounts.tsx +233 -0
  471. package/src/connect-chooser.tsx +336 -0
  472. package/src/connect-panel.tsx +99 -0
  473. package/src/connect-setup.tsx +307 -0
  474. package/src/connect.ts +60 -0
  475. package/src/connection-assets.d.ts +4 -0
  476. package/src/connection-catalog.tsx +162 -0
  477. package/src/connection-discovery.tsx +235 -0
  478. package/src/connection-installed.tsx +51 -0
  479. package/src/connection-logo.tsx +37 -0
  480. package/src/connection-service-logo.tsx +55 -0
  481. package/src/connection-service-presentation.ts +46 -0
  482. package/src/connection-skeleton.tsx +16 -0
  483. package/src/connection-type-picker.tsx +36 -0
  484. package/src/conversation-timeline.ts +92 -0
  485. package/src/desktop.ts +24 -0
  486. package/src/device-authorization.tsx +98 -0
  487. package/src/diffs.ts +16 -0
  488. package/src/discovery-cache.ts +42 -0
  489. package/src/editor.ts +13 -0
  490. package/src/embedded-session-client.ts +104 -0
  491. package/src/file-node-visibility.ts +66 -0
  492. package/src/hooks/channel-cache.ts +104 -0
  493. package/src/hooks/internal.ts +3 -2
  494. package/src/hooks/latest-question.ts +164 -0
  495. package/src/hooks/shared-machines-list.ts +239 -0
  496. package/src/hooks/use-attached-browsers.ts +5 -1
  497. package/src/hooks/use-available-models.ts +20 -14
  498. package/src/hooks/use-browser-frame-stream.ts +25 -3
  499. package/src/hooks/use-browser-session.ts +271 -71
  500. package/src/hooks/use-browser-sessions.ts +7 -1
  501. package/src/hooks/use-channels.ts +32 -10
  502. package/src/hooks/use-codex-accounts.ts +78 -21
  503. package/src/hooks/use-composer.ts +1034 -320
  504. package/src/hooks/use-computer-frame-stream.ts +39 -16
  505. package/src/hooks/use-computer-input-posture.ts +94 -0
  506. package/src/hooks/use-computer-session.ts +110 -32
  507. package/src/hooks/use-computer-sessions.ts +8 -1
  508. package/src/hooks/use-connect.ts +23 -0
  509. package/src/hooks/use-desktop-stream.ts +19 -28
  510. package/src/hooks/use-file-attachments.ts +118 -10
  511. package/src/hooks/use-goal.ts +36 -9
  512. package/src/hooks/use-human-input.ts +31 -4
  513. package/src/hooks/use-last-started-turn-policy.ts +15 -13
  514. package/src/hooks/use-machine-chip.ts +23 -6
  515. package/src/hooks/use-machines.ts +104 -6
  516. package/src/hooks/use-sandbox-files.ts +253 -150
  517. package/src/hooks/use-sandbox-git.ts +58 -29
  518. package/src/hooks/use-session-background-commands.ts +67 -0
  519. package/src/hooks/use-session-capabilities.ts +21 -2
  520. package/src/hooks/use-session-events.ts +759 -433
  521. package/src/hooks/use-session-lineage.ts +28 -6
  522. package/src/hooks/use-session-mcp-approval-policy.ts +13 -5
  523. package/src/hooks/use-session.ts +75 -16
  524. package/src/hooks/use-slash-commands.ts +6 -11
  525. package/src/hooks/use-turn-queue.ts +113 -13
  526. package/src/hooks/use-usage.ts +100 -0
  527. package/src/hooks/use-video-artifact-playback.ts +1 -1
  528. package/src/hooks/use-voice-input.ts +12 -1
  529. package/src/hooks/use-workspace-capture.ts +3 -2
  530. package/src/hooks/use-workspace-edit.ts +23 -5
  531. package/src/hooks/use-workspace-sessions.ts +99 -11
  532. package/src/human-input.ts +12 -1
  533. package/src/identity-link-accounts.tsx +148 -0
  534. package/src/identity-link-consent.tsx +192 -0
  535. package/src/index.ts +157 -12
  536. package/src/interaction.ts +1 -0
  537. package/src/lib/error-message.ts +37 -0
  538. package/src/lib/format.ts +150 -14
  539. package/src/lib/host-theme.ts +511 -0
  540. package/src/lib/interaction-errors.ts +33 -0
  541. package/src/lib/menu-styles.ts +44 -0
  542. package/src/lib/pierre-diffs-loader.ts +53 -0
  543. package/src/lib/workbench-peers.ts +62 -0
  544. package/src/lib/xterm-theme.ts +23 -21
  545. package/src/machines.ts +9 -1
  546. package/src/mcp-connection-status.ts +35 -0
  547. package/src/model-policy.ts +223 -69
  548. package/src/older-history.ts +87 -0
  549. package/src/plugin-details.tsx +185 -0
  550. package/src/plugin-discovery.tsx +196 -0
  551. package/src/provider.tsx +55 -14
  552. package/src/realtime/dropdown-menu.tsx +19 -12
  553. package/src/realtime/realtime-control.tsx +133 -46
  554. package/src/session-auth-recommendation.ts +48 -0
  555. package/src/session-capability-policy.ts +83 -0
  556. package/src/session-context.ts +4 -3
  557. package/src/session-ui.ts +66 -2
  558. package/src/session.ts +18 -1
  559. package/src/sites-ui.tsx +402 -0
  560. package/src/sites.ts +3 -0
  561. package/src/skill-discovery.tsx +208 -0
  562. package/src/terminal.ts +26 -0
  563. package/src/timeline/activity-rail.tsx +171 -80
  564. package/src/timeline/allowance-exhausted-row.tsx +86 -0
  565. package/src/timeline/compaction-copy.ts +42 -0
  566. package/src/timeline/entrance.tsx +28 -3
  567. package/src/timeline/fleet-decision-projection.ts +2 -1
  568. package/src/timeline/fleet-decision-row.tsx +1 -0
  569. package/src/timeline/genie-loading.tsx +136 -0
  570. package/src/timeline/index.ts +11 -0
  571. package/src/timeline/knowledge-receipt.tsx +100 -0
  572. package/src/timeline/parsers.ts +2 -2
  573. package/src/timeline/platform-activity-row.tsx +216 -0
  574. package/src/timeline/presented-image.ts +49 -0
  575. package/src/timeline/projection.ts +1609 -84
  576. package/src/timeline/registry.ts +15 -3
  577. package/src/timeline/retained-image.ts +97 -0
  578. package/src/timeline/rolling-activity.tsx +92 -0
  579. package/src/timeline/screenshot-lightbox.tsx +115 -17
  580. package/src/timeline/shared.tsx +47 -15
  581. package/src/timeline/startup-preference.ts +33 -0
  582. package/src/timeline/startup-timings.tsx +145 -0
  583. package/src/timeline/tool-display-name.ts +8 -6
  584. package/src/timeline/tool-renderers.tsx +497 -128
  585. package/src/timeline/turn-summary.tsx +218 -44
  586. package/src/timeline/types.ts +192 -7
  587. package/src/types/machines.ts +10 -0
  588. package/src/usage/allowance-copy.ts +64 -0
  589. package/src/usage/summary.ts +93 -0
  590. package/src/usage.ts +50 -0
  591. package/styles/compiled.css +2098 -757
  592. package/styles/connect.css +284 -0
  593. package/styles/connect.d.ts +1 -0
  594. package/styles/effective-tokens.css +1 -0
  595. package/styles/index.css +201 -9
  596. package/styles/responsive.css +27 -1
  597. package/styles/session-capability.css +44 -0
  598. package/styles/tokens.css +135 -65
  599. package/dist/chunk-5UQPNLIO.js.map +0 -1
  600. package/dist/chunk-6KLX2RH5.js.map +0 -1
  601. package/dist/chunk-7WCLAEFV.js.map +0 -1
  602. package/dist/chunk-B6WPZSTT.js.map +0 -1
  603. package/dist/chunk-FK6VG4LL.js +0 -90
  604. package/dist/chunk-FK6VG4LL.js.map +0 -1
  605. package/dist/chunk-HA46CYQK.js.map +0 -1
  606. package/dist/chunk-HUIKIDM5.js.map +0 -1
  607. package/dist/chunk-IGC6A2ZI.js.map +0 -1
  608. package/dist/chunk-KCGXPP5V.js.map +0 -1
  609. package/dist/chunk-KZ73RL76.js.map +0 -1
  610. package/dist/chunk-LAKLF4PA.js +0 -141
  611. package/dist/chunk-LAKLF4PA.js.map +0 -1
  612. package/dist/chunk-LLOLUHVG.js.map +0 -1
  613. package/dist/chunk-MKPIAGVL.js +0 -465
  614. package/dist/chunk-MKPIAGVL.js.map +0 -1
  615. package/dist/chunk-PEI6RANI.js.map +0 -1
  616. package/dist/chunk-R3AZOAB5.js.map +0 -1
  617. package/dist/chunk-WSK7G5LE.js +0 -170
  618. package/dist/chunk-WSK7G5LE.js.map +0 -1
  619. package/dist/chunk-XBZWOM2K.js +0 -218
  620. package/dist/chunk-XBZWOM2K.js.map +0 -1
  621. package/dist/chunk-Y7OEDUJ5.js +0 -8810
  622. package/dist/chunk-Y7OEDUJ5.js.map +0 -1
  623. package/dist/fleet-decision-row-GGCAT4E4.js.map +0 -1
  624. package/dist/hooks/use-packs.d.ts +0 -32
  625. package/dist/interactive-workbench-viewers-TOHBXJEE.js +0 -15
  626. package/dist/queue-surface-implementation-2KVJTYXH.js.map +0 -1
  627. package/dist/timeline-annotation-selection-3L7LHGG2.js +0 -187
  628. package/dist/timeline-annotation-selection-3L7LHGG2.js.map +0 -1
  629. package/dist/timeline-annotations-dialog-U7EVLR75.js +0 -196
  630. package/dist/timeline-annotations-dialog-U7EVLR75.js.map +0 -1
  631. package/src/hooks/use-packs.ts +0 -157
  632. /package/dist/{interactive-workbench-viewers-TOHBXJEE.js.map → browser-viewer-SHYKQRNX.js.map} +0 -0
@@ -0,0 +1,2733 @@
1
+ import {
2
+ InteractionInterventionBanner,
3
+ isInteractionControlUnavailable,
4
+ isNonRetryableInteractionError,
5
+ isSourcePlacementChangedError,
6
+ useInteractionInterventions,
7
+ useInteractionInvalidation,
8
+ useViewerMenuDismiss
9
+ } from "./chunk-J6DBS4T3.js";
10
+ import {
11
+ copyTextToClipboard
12
+ } from "./chunk-JVYQRRC4.js";
13
+ import {
14
+ DesktopViewer
15
+ } from "./chunk-DYWBUX5Q.js";
16
+ import {
17
+ STREAM_CLOSE_REASON_NORMAL,
18
+ STREAM_KIND_COMPUTER,
19
+ STREAM_ROLE_CLIENT,
20
+ decodeStreamFrame,
21
+ decodeStreamOpenAck,
22
+ encodeStreamClose,
23
+ encodeStreamOpen
24
+ } from "./chunk-5J4FOW6Q.js";
25
+ import {
26
+ usePageLiveActivity
27
+ } from "./chunk-YQEAA6LT.js";
28
+ import {
29
+ useEmbeddedComputerInteraction
30
+ } from "./chunk-TKOTKRWY.js";
31
+ import {
32
+ cn
33
+ } from "./chunk-22KP6LR5.js";
34
+
35
+ // src/components/computer-viewer.tsx
36
+ import { interactionControlFailureFromError } from "@opengeni/sdk/interaction";
37
+ import {
38
+ ChevronDownIcon,
39
+ CircleAlertIcon,
40
+ KeyboardIcon,
41
+ LoaderCircleIcon,
42
+ LockKeyholeIcon,
43
+ MonitorIcon,
44
+ MousePointer2Icon,
45
+ PanelsTopLeftIcon,
46
+ PlusIcon,
47
+ RefreshCwIcon,
48
+ RotateCcwIcon
49
+ } from "lucide-react";
50
+ import {
51
+ useCallback as useCallback5,
52
+ useEffect as useEffect5,
53
+ useLayoutEffect,
54
+ useMemo as useMemo2,
55
+ useRef as useRef5,
56
+ useState as useState5
57
+ } from "react";
58
+
59
+ // src/hooks/use-computer-frame-stream.ts
60
+ import {
61
+ computerFrameSocketUrl,
62
+ decodeComputerFrameMessage
63
+ } from "@opengeni/sdk/interaction";
64
+ import { useCallback, useEffect, useRef, useState } from "react";
65
+ var RELAY_TAG_OPEN = 1;
66
+ var RELAY_TAG_OPEN_ACK = 2;
67
+ var RELAY_TAG_FRAME = 3;
68
+ var RELAY_TAG_CLOSE = 4;
69
+ var MAX_AUTOMATIC_RECONNECTS_WITHOUT_A_FRAME = 2;
70
+ function useComputerFrameStream(options) {
71
+ const { client, workspaceId } = useEmbeddedComputerInteraction(options);
72
+ const enabled = options.enabled ?? true;
73
+ const [nonce, setNonce] = useState(0);
74
+ const [result, setResult] = useState({
75
+ state: "idle",
76
+ frame: null,
77
+ attachment: null,
78
+ error: null
79
+ });
80
+ const latestRef = useRef({
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
+ const reconnect = useCallback(() => setNonce((value) => value + 1), []);
89
+ useEffect(() => {
90
+ const computerSessionId = options.computerSessionId;
91
+ const targetId = options.targetId;
92
+ if (!enabled || !computerSessionId || !targetId || typeof window === "undefined") {
93
+ setResult({ state: "idle", frame: null, attachment: null, error: null });
94
+ return;
95
+ }
96
+ latestRef.current = { key: "", sequence: -1 };
97
+ setResult({
98
+ state: "attaching",
99
+ frame: null,
100
+ attachment: null,
101
+ error: null
102
+ });
103
+ let disposed = false;
104
+ let socket = null;
105
+ let socketHandlers = null;
106
+ let reconnectTimer = null;
107
+ let expiryTimer = null;
108
+ let failures = 0;
109
+ let controllerGeneration = null;
110
+ let activeAttachment = null;
111
+ let activeStream = null;
112
+ let attachmentExpiresAt = 0;
113
+ let lastRelaySequence = null;
114
+ let relayAccepted = false;
115
+ let pendingFrame = null;
116
+ let decodingFrame = false;
117
+ const attachmentAbort = new AbortController();
118
+ const clearSocket = (terminateProducer = false) => {
119
+ if (!socket) return;
120
+ const current = socket;
121
+ const handlers = socketHandlers;
122
+ if (terminateProducer && activeStream?.kind === "relay" && socket.readyState === 1) {
123
+ try {
124
+ socket.send(
125
+ relayDatagram(
126
+ RELAY_TAG_CLOSE,
127
+ encodeStreamClose({
128
+ channelId: activeStream.channel.channelId,
129
+ reason: STREAM_CLOSE_REASON_NORMAL,
130
+ message: "viewer detached"
131
+ })
132
+ )
133
+ );
134
+ } catch {
135
+ }
136
+ }
137
+ if (handlers) {
138
+ current.removeEventListener("open", handlers.open);
139
+ current.removeEventListener("message", handlers.message);
140
+ current.removeEventListener("error", handlers.error);
141
+ current.removeEventListener("close", handlers.close);
142
+ }
143
+ if (current.readyState === 0 || current.readyState === 1) {
144
+ current.close(1e3, "viewer detached");
145
+ }
146
+ socket = null;
147
+ socketHandlers = null;
148
+ pendingFrame = null;
149
+ };
150
+ const clearRecoveryTimers = () => {
151
+ if (reconnectTimer) clearTimeout(reconnectTimer);
152
+ if (expiryTimer) clearTimeout(expiryTimer);
153
+ reconnectTimer = null;
154
+ expiryTimer = null;
155
+ };
156
+ const scheduleReconnect = () => {
157
+ if (disposed || reconnectTimer) return;
158
+ if (failures >= MAX_AUTOMATIC_RECONNECTS_WITHOUT_A_FRAME) {
159
+ clearRecoveryTimers();
160
+ return;
161
+ }
162
+ const delay = Math.min(5e3, 250 * 2 ** Math.min(failures, 5));
163
+ failures += 1;
164
+ setResult((current) => ({ ...current, state: "reconnecting" }));
165
+ reconnectTimer = setTimeout(() => {
166
+ reconnectTimer = null;
167
+ connectSocket();
168
+ }, delay);
169
+ };
170
+ const fail = (cause, reconnectAutomatically = true) => {
171
+ if (disposed) return;
172
+ const error = cause instanceof Error ? cause : new Error(String(cause));
173
+ setResult((current) => ({ ...current, state: "error", error }));
174
+ clearSocket();
175
+ if (reconnectAutomatically) scheduleReconnect();
176
+ else clearRecoveryTimers();
177
+ };
178
+ const onOpen = (source) => {
179
+ if (disposed || source !== socket) return;
180
+ if (activeStream?.kind === "relay") {
181
+ const body = encodeStreamOpen({
182
+ channel: { ...activeStream.channel, kind: STREAM_KIND_COMPUTER },
183
+ token: activeStream.token,
184
+ role: STREAM_ROLE_CLIENT,
185
+ resumeFromSeq: lastRelaySequence === null ? "0" : (BigInt(lastRelaySequence) + 1n).toString()
186
+ });
187
+ try {
188
+ source.send(relayDatagram(RELAY_TAG_OPEN, body));
189
+ } catch (error) {
190
+ fail(error);
191
+ }
192
+ return;
193
+ }
194
+ setResult((current) => ({ ...current, state: "live", error: null }));
195
+ };
196
+ const drainLatestFrame = async () => {
197
+ if (decodingFrame) return;
198
+ decodingFrame = true;
199
+ try {
200
+ while (pendingFrame) {
201
+ if (disposed) break;
202
+ const pending = pendingFrame;
203
+ pendingFrame = null;
204
+ const { bytes, source } = pending;
205
+ const frame = await decodeComputerFrameMessage(bytes);
206
+ if (disposed || source !== socket) continue;
207
+ if (frame.computerSessionId !== computerSessionId || frame.targetId !== targetId) {
208
+ throw new Error("desktop frame belongs to another resource");
209
+ }
210
+ if (!controllerGeneration || frame.controllerGeneration !== controllerGeneration) {
211
+ throw new Error("desktop frame belongs to a stale controller");
212
+ }
213
+ const key = `${computerSessionId}:${targetId}:${frame.controllerGeneration}:${frame.targetGeneration}`;
214
+ if (latestRef.current.key === key && frame.sequence <= latestRef.current.sequence)
215
+ continue;
216
+ latestRef.current = { key, sequence: frame.sequence };
217
+ failures = 0;
218
+ setResult((current) => ({
219
+ ...current,
220
+ state: "live",
221
+ frame,
222
+ error: null
223
+ }));
224
+ }
225
+ } catch (cause) {
226
+ fail(cause);
227
+ } finally {
228
+ decodingFrame = false;
229
+ if (!disposed && pendingFrame) void drainLatestFrame();
230
+ }
231
+ };
232
+ const onMessage = (source, event) => {
233
+ void (async () => {
234
+ try {
235
+ const bytes = await messageBytes(event.data);
236
+ if (disposed || source !== socket) return;
237
+ let frameBytes = bytes;
238
+ if (activeStream?.kind === "relay") {
239
+ if (bytes.length < 1) throw new Error("desktop relay returned an empty message");
240
+ const tag = bytes[0];
241
+ const body = bytes.subarray(1);
242
+ if (tag === RELAY_TAG_OPEN_ACK) {
243
+ const ack = decodeStreamOpenAck(body);
244
+ if (!ack.accepted) {
245
+ activeAttachment = null;
246
+ activeStream = null;
247
+ attachmentExpiresAt = 0;
248
+ throw new Error(ack.error?.message ?? "desktop stream was rejected by the relay");
249
+ }
250
+ relayAccepted = true;
251
+ return;
252
+ }
253
+ if (tag === RELAY_TAG_CLOSE) {
254
+ activeAttachment = null;
255
+ activeStream = null;
256
+ attachmentExpiresAt = 0;
257
+ lastRelaySequence = null;
258
+ relayAccepted = false;
259
+ fail(new Error("Desktop frame source ended."), false);
260
+ return;
261
+ }
262
+ if (tag !== RELAY_TAG_FRAME || !relayAccepted) return;
263
+ const relayFrame = decodeStreamFrame(body);
264
+ lastRelaySequence = relayFrame.seq;
265
+ frameBytes = relayFrame.data;
266
+ }
267
+ pendingFrame = { bytes: frameBytes, source };
268
+ void drainLatestFrame();
269
+ } catch (cause) {
270
+ fail(cause);
271
+ }
272
+ })();
273
+ };
274
+ const onError = (source) => {
275
+ if (source !== socket) return;
276
+ fail(new Error("Desktop view lost connection."));
277
+ };
278
+ const onClose = (source) => {
279
+ if (disposed || source !== socket) return;
280
+ fail(new Error("Desktop view lost connection."));
281
+ };
282
+ const connectSocket = () => {
283
+ if (disposed) return;
284
+ clearSocket();
285
+ if (!activeAttachment || !activeStream || attachmentExpiresAt <= Date.now() + 1e3) {
286
+ void attach();
287
+ return;
288
+ }
289
+ if (activeStream.kind === "direct_rfb") {
290
+ setResult((current) => ({ ...current, state: "live", error: null }));
291
+ return;
292
+ }
293
+ relayAccepted = false;
294
+ setResult((current) => ({
295
+ ...current,
296
+ state: "connecting",
297
+ error: null
298
+ }));
299
+ const createSocket = factoryRef.current ?? ((url, protocols) => new WebSocket(url, protocols));
300
+ const openedSocket = createSocket(
301
+ activeStream.kind === "relay" ? activeStream.url : computerFrameSocketUrl(activeAttachment, streamRef.current),
302
+ activeStream.kind === "direct_websocket" ? [...activeStream.protocols] : []
303
+ );
304
+ socket = openedSocket;
305
+ socketHandlers = {
306
+ open: () => onOpen(openedSocket),
307
+ message: (event) => onMessage(openedSocket, event),
308
+ error: () => onError(openedSocket),
309
+ close: () => onClose(openedSocket)
310
+ };
311
+ openedSocket.binaryType = "arraybuffer";
312
+ openedSocket.addEventListener("open", socketHandlers.open);
313
+ openedSocket.addEventListener("message", socketHandlers.message);
314
+ openedSocket.addEventListener("error", socketHandlers.error);
315
+ openedSocket.addEventListener("close", socketHandlers.close);
316
+ };
317
+ const attach = async () => {
318
+ if (disposed) return;
319
+ clearSocket();
320
+ if (expiryTimer) clearTimeout(expiryTimer);
321
+ setResult((current) => ({
322
+ ...current,
323
+ state: current.attachment ? "reconnecting" : "attaching",
324
+ error: null
325
+ }));
326
+ try {
327
+ const attachment = await client.attachComputerSession(
328
+ workspaceId,
329
+ computerSessionId,
330
+ {
331
+ targetId,
332
+ expiresInSeconds: 120,
333
+ ...streamRef.current ? { stream: streamRef.current } : {}
334
+ },
335
+ { signal: attachmentAbort.signal, timeoutMs: 15e3 }
336
+ );
337
+ if (disposed) return;
338
+ if (attachment.computerSessionId !== computerSessionId || attachment.targetId !== targetId) {
339
+ throw new Error("desktop attachment does not match the requested resource");
340
+ }
341
+ controllerGeneration = attachment.controllerGeneration;
342
+ latestRef.current = { key: "", sequence: -1 };
343
+ activeAttachment = attachment;
344
+ activeStream = attachment.stream;
345
+ attachmentExpiresAt = Date.parse(attachment.expiresAt);
346
+ lastRelaySequence = null;
347
+ relayAccepted = false;
348
+ setResult((current) => ({
349
+ ...current,
350
+ state: "connecting",
351
+ attachment: {
352
+ computerSessionId: attachment.computerSessionId,
353
+ controllerGeneration: attachment.controllerGeneration,
354
+ targetId: attachment.targetId,
355
+ inputAllowed: attachment.inputAllowed,
356
+ stream: attachment.stream,
357
+ expiresAt: attachment.expiresAt
358
+ },
359
+ error: null
360
+ }));
361
+ const refreshIn = Number.isFinite(attachmentExpiresAt) ? Math.max(1e3, attachmentExpiresAt - Date.now() - 5e3) : 6e4;
362
+ expiryTimer = setTimeout(() => {
363
+ if (disposed) return;
364
+ clearSocket(true);
365
+ activeAttachment = null;
366
+ activeStream = null;
367
+ attachmentExpiresAt = 0;
368
+ void attach();
369
+ }, refreshIn);
370
+ connectSocket();
371
+ } catch (cause) {
372
+ if (attachmentAbort.signal.aborted || disposed) return;
373
+ fail(cause, !isPlacementGenerationLossError(cause));
374
+ }
375
+ };
376
+ void attach();
377
+ return () => {
378
+ disposed = true;
379
+ pendingFrame = null;
380
+ attachmentAbort.abort();
381
+ clearRecoveryTimers();
382
+ clearSocket(true);
383
+ };
384
+ }, [client, enabled, nonce, options.computerSessionId, options.targetId, workspaceId]);
385
+ return { ...result, reconnect };
386
+ }
387
+ function relayDatagram(tag, body) {
388
+ const message = new Uint8Array(body.length + 1);
389
+ message[0] = tag;
390
+ message.set(body, 1);
391
+ return message.buffer;
392
+ }
393
+ async function messageBytes(value) {
394
+ if (value instanceof ArrayBuffer) return new Uint8Array(value);
395
+ if (ArrayBuffer.isView(value)) {
396
+ return new Uint8Array(value.buffer, value.byteOffset, value.byteLength).slice();
397
+ }
398
+ if (typeof Blob !== "undefined" && value instanceof Blob) {
399
+ return new Uint8Array(await value.arrayBuffer());
400
+ }
401
+ throw new Error("desktop frame stream returned a non-binary message");
402
+ }
403
+ function isPlacementGenerationLossError(cause) {
404
+ const message = cause instanceof Error ? cause.message : String(cause);
405
+ return /placement instance changed|controller authority changed/i.test(message);
406
+ }
407
+
408
+ // src/hooks/use-computer-session.ts
409
+ import { useCallback as useCallback2, useEffect as useEffect2, useRef as useRef2, useState as useState2 } from "react";
410
+ function useComputerSession(options) {
411
+ const { client, workspaceId } = useEmbeddedComputerInteraction(options);
412
+ const computerSessionId = options.computerSessionId;
413
+ const enabled = (options.enabled ?? true) && computerSessionId !== null;
414
+ const pageLive = usePageLiveActivity();
415
+ const pollIntervalMs = Math.max(750, options.pollIntervalMs ?? 2e3);
416
+ const [state, setState] = useState2(
417
+ () => emptyState(computerSessionId, enabled)
418
+ );
419
+ const visible = state.computerSessionId === computerSessionId ? state : emptyState(computerSessionId, enabled);
420
+ const refreshBlocked = isNonRetryableInteractionError(visible.error);
421
+ const selectedTargetIdRef = useRef2(visible.selectedTargetId);
422
+ selectedTargetIdRef.current = visible.selectedTargetId;
423
+ const targetsRef = useRef2({
424
+ computerSessionId,
425
+ targets: visible.targets
426
+ });
427
+ const observationRef = useRef2({ computerSessionId, observation: visible.observation });
428
+ if (targetsRef.current.computerSessionId !== computerSessionId) {
429
+ targetsRef.current = { computerSessionId, targets: visible.targets };
430
+ observationRef.current = {
431
+ computerSessionId,
432
+ observation: visible.observation
433
+ };
434
+ } else {
435
+ targetsRef.current.targets = visible.targets;
436
+ observationRef.current.observation = visible.observation;
437
+ }
438
+ const requestRef = useRef2({
439
+ id: 0,
440
+ controller: null
441
+ });
442
+ const mutationRef = useRef2({
443
+ computerSessionId,
444
+ count: 0
445
+ });
446
+ const mountedRef = useRef2(true);
447
+ const invalidateRefresh = useCallback2(() => {
448
+ const id = requestRef.current.id + 1;
449
+ requestRef.current.controller?.abort();
450
+ requestRef.current = { id, controller: null };
451
+ }, []);
452
+ const refresh = useCallback2(async () => {
453
+ if (!enabled || !computerSessionId) return;
454
+ const id = requestRef.current.id + 1;
455
+ requestRef.current.controller?.abort();
456
+ const controller = new AbortController();
457
+ requestRef.current = { id, controller };
458
+ let discoveryCompleted = false;
459
+ try {
460
+ const [session, targetResponse] = await Promise.all([
461
+ client.getComputerSession(workspaceId, computerSessionId, {
462
+ signal: controller.signal
463
+ }),
464
+ client.listComputerTargets(workspaceId, computerSessionId, {
465
+ signal: controller.signal
466
+ })
467
+ ]);
468
+ discoveryCompleted = true;
469
+ if (!mountedRef.current || requestRef.current.id !== id) return;
470
+ const targets = sortComputerTargets(targetResponse.targets);
471
+ const selected = chooseTarget(targets, selectedTargetIdRef.current);
472
+ const previous = observationRef.current.observation;
473
+ const retainedObservation = selected && previous?.target.id === selected.id && previous.target.controllerGeneration === selected.controllerGeneration && previous.target.targetGeneration === selected.targetGeneration ? previous : null;
474
+ selectedTargetIdRef.current = selected?.id ?? null;
475
+ targetsRef.current = { computerSessionId, targets };
476
+ observationRef.current = { computerSessionId, observation: retainedObservation };
477
+ setState(
478
+ (current) => current.computerSessionId === computerSessionId ? {
479
+ ...current,
480
+ session,
481
+ targets,
482
+ selectedTargetId: selected?.id ?? null,
483
+ observation: retainedObservation,
484
+ loading: false,
485
+ error: null,
486
+ controlError: null
487
+ } : current
488
+ );
489
+ const observation = selected ? await client.observeComputerTarget(workspaceId, computerSessionId, selected.id, {
490
+ signal: controller.signal
491
+ }) : null;
492
+ if (!mountedRef.current || requestRef.current.id !== id) return;
493
+ selectedTargetIdRef.current = selected?.id ?? null;
494
+ targetsRef.current = { computerSessionId, targets };
495
+ observationRef.current = { computerSessionId, observation };
496
+ setState(
497
+ (current) => current.computerSessionId === computerSessionId ? {
498
+ ...current,
499
+ session,
500
+ targets,
501
+ selectedTargetId: selected?.id ?? null,
502
+ observation,
503
+ loading: false,
504
+ error: null
505
+ } : current
506
+ );
507
+ } catch (cause) {
508
+ if (controller.signal.aborted || !mountedRef.current || requestRef.current.id !== id) return;
509
+ const error = cause instanceof Error ? cause : new Error(String(cause));
510
+ setState(
511
+ (current) => current.computerSessionId === computerSessionId ? {
512
+ ...current,
513
+ loading: false,
514
+ error,
515
+ controlError: isInteractionControlUnavailable(
516
+ error,
517
+ discoveryCompleted ? "observation" : "control"
518
+ ) ? error : current.controlError
519
+ } : current
520
+ );
521
+ } finally {
522
+ if (requestRef.current.id === id) requestRef.current = { id, controller: null };
523
+ }
524
+ }, [client, computerSessionId, enabled, workspaceId]);
525
+ useEffect2(() => {
526
+ mountedRef.current = true;
527
+ if (!enabled) {
528
+ invalidateRefresh();
529
+ setState(emptyState(computerSessionId, false));
530
+ return;
531
+ }
532
+ setState(
533
+ (current) => current.computerSessionId === computerSessionId ? { ...current, loading: true } : emptyState(computerSessionId, true)
534
+ );
535
+ void refresh();
536
+ return () => {
537
+ mountedRef.current = false;
538
+ invalidateRefresh();
539
+ };
540
+ }, [computerSessionId, enabled, invalidateRefresh, refresh]);
541
+ useEffect2(() => {
542
+ if (!enabled || !pageLive || visible.mutating || refreshBlocked) return;
543
+ const timer = setInterval(() => {
544
+ if (!requestRef.current.controller) void refresh();
545
+ }, pollIntervalMs);
546
+ return () => clearInterval(timer);
547
+ }, [enabled, pageLive, pollIntervalMs, refresh, refreshBlocked, visible.mutating]);
548
+ useEffect2(() => {
549
+ if (!enabled || !computerSessionId || !pageLive || refreshBlocked) return;
550
+ let disposed = false;
551
+ const timer = setInterval(() => {
552
+ void client.heartbeatComputerSession(workspaceId, computerSessionId).catch(() => {
553
+ if (!disposed) void refresh();
554
+ });
555
+ }, 3e4);
556
+ return () => {
557
+ disposed = true;
558
+ clearInterval(timer);
559
+ };
560
+ }, [client, computerSessionId, enabled, pageLive, refresh, refreshBlocked, workspaceId]);
561
+ const runMutation = useCallback2(
562
+ async (scopeComputerSessionId, operation) => {
563
+ invalidateRefresh();
564
+ if (mutationRef.current.computerSessionId !== scopeComputerSessionId) {
565
+ mutationRef.current = {
566
+ computerSessionId: scopeComputerSessionId,
567
+ count: 0
568
+ };
569
+ }
570
+ mutationRef.current.count += 1;
571
+ setState(
572
+ (current) => current.computerSessionId === scopeComputerSessionId ? { ...current, mutating: true, error: null } : current
573
+ );
574
+ try {
575
+ const result = await operation();
576
+ setState(
577
+ (current) => current.computerSessionId === scopeComputerSessionId ? { ...current, controlError: null } : current
578
+ );
579
+ return result;
580
+ } catch (cause) {
581
+ const error = cause instanceof Error ? cause : new Error(String(cause));
582
+ setState(
583
+ (current) => current.computerSessionId === scopeComputerSessionId ? {
584
+ ...current,
585
+ error,
586
+ controlError: isInteractionControlUnavailable(error) ? error : current.controlError
587
+ } : current
588
+ );
589
+ throw error;
590
+ } finally {
591
+ if (mutationRef.current.computerSessionId === scopeComputerSessionId) {
592
+ mutationRef.current.count = Math.max(0, mutationRef.current.count - 1);
593
+ const mutating = mutationRef.current.count > 0;
594
+ setState(
595
+ (current) => current.computerSessionId === scopeComputerSessionId ? { ...current, mutating } : current
596
+ );
597
+ }
598
+ }
599
+ },
600
+ [invalidateRefresh]
601
+ );
602
+ const selectTarget = useCallback2(
603
+ async (targetId) => {
604
+ if (!computerSessionId) throw new Error("No desktop is selected.");
605
+ const target = targetsRef.current.targets.find((candidate) => candidate.id === targetId);
606
+ if (!target) throw new Error("The selected desktop target is no longer available.");
607
+ invalidateRefresh();
608
+ selectedTargetIdRef.current = target.id;
609
+ observationRef.current = { computerSessionId, observation: null };
610
+ setState(
611
+ (current) => current.computerSessionId === computerSessionId ? {
612
+ ...current,
613
+ selectedTargetId: target.id,
614
+ observation: null,
615
+ loading: true,
616
+ error: null
617
+ } : current
618
+ );
619
+ try {
620
+ const observation = await client.observeComputerTarget(
621
+ workspaceId,
622
+ computerSessionId,
623
+ target.id
624
+ );
625
+ if (selectedTargetIdRef.current !== target.id) return target;
626
+ observationRef.current = { computerSessionId, observation };
627
+ setState(
628
+ (current) => current.computerSessionId === computerSessionId && current.selectedTargetId === target.id ? { ...current, observation, loading: false, error: null } : current
629
+ );
630
+ return observation.target;
631
+ } catch (cause) {
632
+ const error = cause instanceof Error ? cause : new Error(String(cause));
633
+ setState(
634
+ (current) => current.computerSessionId === computerSessionId && current.selectedTargetId === target.id ? {
635
+ ...current,
636
+ loading: false,
637
+ error,
638
+ controlError: isInteractionControlUnavailable(error, "observation") ? error : current.controlError
639
+ } : current
640
+ );
641
+ throw error;
642
+ }
643
+ },
644
+ [client, computerSessionId, invalidateRefresh, workspaceId]
645
+ );
646
+ const dispatchAction = useCallback2(
647
+ async (action, operationId, frame) => {
648
+ if (!computerSessionId) throw new Error("No desktop is selected.");
649
+ if (frame && frame.computerSessionId !== computerSessionId) {
650
+ throw new Error("The displayed desktop frame belongs to another desktop session.");
651
+ }
652
+ const currentObservation = observationRef.current.computerSessionId === computerSessionId ? observationRef.current.observation : null;
653
+ const focusTarget = action.type === "focus" ? targetsRef.current.targets.find((candidate) => candidate.id === action.targetId) ?? null : null;
654
+ const frameTarget = frame ? targetsRef.current.targets.find((candidate) => candidate.id === frame.targetId) ?? (currentObservation?.target.id === frame.targetId ? currentObservation.target : null) : null;
655
+ const inputTarget = (action.type === "keyboard" || action.type === "clipboard") && targetsRef.current.computerSessionId === computerSessionId ? targetsRef.current.targets.find(
656
+ (candidate) => candidate.id === selectedTargetIdRef.current
657
+ ) : null;
658
+ const target = frameTarget ?? focusTarget ?? currentObservation?.target ?? inputTarget ?? null;
659
+ if (!target) throw new Error("The desktop target is not ready for input.");
660
+ if (frame && frame.targetId !== selectedTargetIdRef.current) {
661
+ throw new Error("The displayed desktop frame is no longer selected.");
662
+ }
663
+ if (action.type === "pointer") {
664
+ const expectedFrameId = frame?.frameId ?? currentObservation?.frameId ?? null;
665
+ if (!expectedFrameId || action.frameId !== expectedFrameId) {
666
+ throw new Error("Pointer input must reference the exact displayed desktop frame.");
667
+ }
668
+ }
669
+ if (action.type === "semantic" && !currentObservation) {
670
+ throw new Error("The desktop accessibility tree is not ready for input.");
671
+ }
672
+ return await runMutation(computerSessionId, async () => {
673
+ const receipt = await client.actInComputer(workspaceId, computerSessionId, {
674
+ operationId,
675
+ targetId: target.id,
676
+ expectedTargetGeneration: frame?.targetGeneration ?? target.targetGeneration,
677
+ expectedObservationId: action.type === "pointer" ? null : currentObservation?.observationId ?? null,
678
+ expectedFrameId: action.type === "pointer" ? action.frameId : null,
679
+ action
680
+ });
681
+ if (receipt.observation) {
682
+ const observation = receipt.observation;
683
+ observationRef.current = { computerSessionId, observation };
684
+ selectedTargetIdRef.current = observation.target.id;
685
+ setState(
686
+ (current) => current.computerSessionId === computerSessionId ? {
687
+ ...current,
688
+ observation,
689
+ selectedTargetId: observation.target.id,
690
+ targets: replaceTarget(current.targets, observation.target)
691
+ } : current
692
+ );
693
+ } else {
694
+ void refresh();
695
+ }
696
+ return receipt;
697
+ });
698
+ },
699
+ [client, computerSessionId, refresh, runMutation, workspaceId]
700
+ );
701
+ const act = useCallback2(
702
+ async (action, operationId = crypto.randomUUID()) => await dispatchAction(action, operationId, null),
703
+ [dispatchAction]
704
+ );
705
+ const actFromFrame = useCallback2(
706
+ async (action, frame, operationId = crypto.randomUUID()) => await dispatchAction(action, operationId, frame),
707
+ [dispatchAction]
708
+ );
709
+ const readClipboard = useCallback2(async () => {
710
+ if (!computerSessionId) throw new Error("No desktop is selected.");
711
+ try {
712
+ return await client.readComputerClipboard(workspaceId, computerSessionId);
713
+ } catch (cause) {
714
+ const error = cause instanceof Error ? cause : new Error(String(cause));
715
+ setState(
716
+ (current) => current.computerSessionId === computerSessionId ? {
717
+ ...current,
718
+ error,
719
+ controlError: isInteractionControlUnavailable(error) ? error : current.controlError
720
+ } : current
721
+ );
722
+ throw error;
723
+ }
724
+ }, [client, computerSessionId, workspaceId]);
725
+ return {
726
+ session: visible.session,
727
+ targets: visible.targets,
728
+ selectedTarget: visible.targets.find((target) => target.id === visible.selectedTargetId) ?? null,
729
+ observation: visible.observation,
730
+ loading: visible.loading,
731
+ mutating: visible.mutating,
732
+ error: visible.error,
733
+ controlError: visible.controlError,
734
+ refresh,
735
+ readClipboard,
736
+ selectTarget,
737
+ act,
738
+ actFromFrame
739
+ };
740
+ }
741
+ function emptyState(computerSessionId, loading) {
742
+ return {
743
+ computerSessionId,
744
+ session: null,
745
+ targets: [],
746
+ selectedTargetId: null,
747
+ observation: null,
748
+ loading,
749
+ mutating: false,
750
+ error: null,
751
+ controlError: null
752
+ };
753
+ }
754
+ function chooseTarget(targets, preferredId) {
755
+ const preferred = targets.find((target) => target.id === preferredId);
756
+ if (preferred) return preferred;
757
+ return targets.find((target) => target.focused && target.kind === "screen") ?? targets.find((target) => target.kind === "screen") ?? targets.find((target) => target.focused && target.kind === "window") ?? targets.find((target) => target.kind === "window") ?? targets.find((target) => target.focused && target.kind === "app") ?? targets.find((target) => target.kind === "app") ?? targets[0] ?? null;
758
+ }
759
+ function sortComputerTargets(targets) {
760
+ const rank = { window: 0, app: 1, screen: 2 };
761
+ return [...targets].sort(
762
+ (left, right) => Number(right.focused) - Number(left.focused) || rank[left.kind] - rank[right.kind] || left.title.localeCompare(right.title) || left.id.localeCompare(right.id)
763
+ );
764
+ }
765
+ function replaceTarget(targets, target) {
766
+ return sortComputerTargets([
767
+ ...targets.filter((candidate) => candidate.id !== target.id),
768
+ target
769
+ ]);
770
+ }
771
+
772
+ // src/hooks/use-computer-input-posture.ts
773
+ import { useCallback as useCallback3, useEffect as useEffect3, useRef as useRef3, useState as useState3 } from "react";
774
+ function useComputerInputPosture(options) {
775
+ const { client, workspaceId } = useEmbeddedComputerInteraction(options);
776
+ const { computerSessionId, controllerGeneration, enabled } = options;
777
+ const [nonce, setNonce] = useState3(0);
778
+ const [state, setState] = useState3(null);
779
+ const key = JSON.stringify([workspaceId, computerSessionId, controllerGeneration, enabled]);
780
+ const sourceRef = useRef3({ client, key });
781
+ if (sourceRef.current.client !== client || sourceRef.current.key !== key) {
782
+ sourceRef.current = { client, key };
783
+ }
784
+ const source = sourceRef.current;
785
+ const visible = state?.source === source && state.nonce === nonce ? state : null;
786
+ const refresh = useCallback3(() => setNonce((value) => value + 1), []);
787
+ useEffect3(() => {
788
+ if (!enabled || !computerSessionId || !controllerGeneration) return;
789
+ const controller = new AbortController();
790
+ let disposed = false;
791
+ setState({ source, nonce, inputAllowed: null, pending: true, error: null });
792
+ void (async () => {
793
+ try {
794
+ if (!client.getComputerInputPosture) {
795
+ throw new Error("Desktop input availability could not be verified.");
796
+ }
797
+ const posture = await client.getComputerInputPosture(workspaceId, computerSessionId, {
798
+ signal: controller.signal,
799
+ timeoutMs: 15e3
800
+ });
801
+ if (disposed || sourceRef.current !== source) return;
802
+ if (posture.computerSessionId !== computerSessionId || posture.controllerGeneration !== controllerGeneration || typeof posture.inputAllowed !== "boolean") {
803
+ throw new Error("Desktop input availability belongs to a different controller.");
804
+ }
805
+ setState({
806
+ source,
807
+ nonce,
808
+ inputAllowed: posture.inputAllowed,
809
+ pending: false,
810
+ error: null
811
+ });
812
+ } catch (cause) {
813
+ if (disposed || controller.signal.aborted || sourceRef.current !== source) return;
814
+ setState({
815
+ source,
816
+ nonce,
817
+ inputAllowed: null,
818
+ pending: false,
819
+ error: cause instanceof Error ? cause : new Error(String(cause))
820
+ });
821
+ }
822
+ })();
823
+ return () => {
824
+ disposed = true;
825
+ controller.abort();
826
+ };
827
+ }, [client, computerSessionId, controllerGeneration, enabled, nonce, source, workspaceId]);
828
+ return {
829
+ inputAllowed: visible?.inputAllowed ?? null,
830
+ pending: enabled && (visible?.pending ?? true),
831
+ error: visible?.error ?? null,
832
+ refresh
833
+ };
834
+ }
835
+
836
+ // src/hooks/use-computer-sessions.ts
837
+ import { useCallback as useCallback4, useEffect as useEffect4, useMemo, useRef as useRef4, useState as useState4 } from "react";
838
+ function useComputerSessions(options = {}) {
839
+ const {
840
+ client,
841
+ workspaceId,
842
+ registerSessionReconciler,
843
+ workspaceInteractionEvent,
844
+ workspaceInteractionConnectionState
845
+ } = useEmbeddedComputerInteraction(options);
846
+ const enabled = options.enabled ?? true;
847
+ const [state, setState] = useState4(() => emptyState2(workspaceId, enabled));
848
+ const requestRef = useRef4({
849
+ id: 0,
850
+ controller: null
851
+ });
852
+ const mountedRef = useRef4(true);
853
+ const visible = state.workspaceId === workspaceId ? state : emptyState2(workspaceId, enabled);
854
+ const cancelLoad = useCallback4(() => {
855
+ const id = requestRef.current.id + 1;
856
+ requestRef.current.controller?.abort();
857
+ requestRef.current = { id, controller: null };
858
+ }, []);
859
+ const load = useCallback4(
860
+ async (foreground) => {
861
+ if (!enabled) return;
862
+ const id = requestRef.current.id + 1;
863
+ requestRef.current.controller?.abort();
864
+ const controller = new AbortController();
865
+ requestRef.current = { id, controller };
866
+ setState((current) => ({
867
+ ...current.workspaceId === workspaceId ? current : emptyState2(workspaceId, true),
868
+ active: true,
869
+ loading: foreground,
870
+ refreshing: !foreground,
871
+ error: foreground || current.workspaceId !== workspaceId ? null : current.error
872
+ }));
873
+ try {
874
+ const response = await client.listComputerSessions(workspaceId, {
875
+ signal: controller.signal
876
+ });
877
+ if (!mountedRef.current || requestRef.current.id !== id) return;
878
+ setState({
879
+ workspaceId,
880
+ active: true,
881
+ revision: response.revision,
882
+ sessions: sortComputerSessions(response.sessions),
883
+ loading: false,
884
+ refreshing: false,
885
+ error: null
886
+ });
887
+ } catch (cause) {
888
+ if (controller.signal.aborted || !mountedRef.current || requestRef.current.id !== id)
889
+ return;
890
+ setState((current) => ({
891
+ ...current.workspaceId === workspaceId ? current : emptyState2(workspaceId, true),
892
+ loading: false,
893
+ refreshing: false,
894
+ error: cause instanceof Error ? cause : new Error(String(cause))
895
+ }));
896
+ } finally {
897
+ if (requestRef.current.id === id) requestRef.current = { id, controller: null };
898
+ }
899
+ },
900
+ [client, enabled, workspaceId]
901
+ );
902
+ const refresh = useCallback4(async () => await load(false), [load]);
903
+ useEffect4(() => {
904
+ mountedRef.current = true;
905
+ if (!enabled) {
906
+ cancelLoad();
907
+ setState(emptyState2(workspaceId, false));
908
+ return;
909
+ }
910
+ void load(true);
911
+ return () => {
912
+ mountedRef.current = false;
913
+ cancelLoad();
914
+ };
915
+ }, [cancelLoad, enabled, load, workspaceId]);
916
+ useInteractionInvalidation({
917
+ workspaceId,
918
+ key: workspaceId,
919
+ enabled,
920
+ event: workspaceInteractionEvent,
921
+ connectionState: workspaceInteractionConnectionState,
922
+ knownRevision: visible.revision,
923
+ refresh,
924
+ fallbackPollIntervalMs: options.pollIntervalMs
925
+ });
926
+ useEffect4(() => {
927
+ if (!enabled || !options.sessionId) return;
928
+ return registerSessionReconciler(
929
+ options.sessionId,
930
+ `computer-sessions:${workspaceId}`,
931
+ refresh
932
+ );
933
+ }, [enabled, options.sessionId, refresh, registerSessionReconciler, workspaceId]);
934
+ const create = useCallback4(
935
+ async (request) => {
936
+ const response = await client.createComputerSession(workspaceId, {
937
+ ...request,
938
+ operationId: request.operationId ?? crypto.randomUUID()
939
+ });
940
+ setState((current) => mergeSession(current, workspaceId, response.session));
941
+ return response;
942
+ },
943
+ [client, workspaceId]
944
+ );
945
+ const end = useCallback4(
946
+ async (computerSessionId, operationId = crypto.randomUUID()) => {
947
+ const response = await client.endComputerSession(workspaceId, computerSessionId, {
948
+ operationId
949
+ });
950
+ setState((current) => mergeSession(current, workspaceId, response.session));
951
+ return response;
952
+ },
953
+ [client, workspaceId]
954
+ );
955
+ const relevantSessions = useMemo(
956
+ () => options.sessionId ? visible.sessions.filter(
957
+ (session) => session.associations.some((association) => association.sessionId === options.sessionId)
958
+ ) : [],
959
+ [options.sessionId, visible.sessions]
960
+ );
961
+ return {
962
+ revision: visible.revision,
963
+ sessions: visible.sessions,
964
+ relevantSessions,
965
+ // A disabled registry is deliberately empty. When its surface becomes
966
+ // visible again, expose that transition as loading in the same render so
967
+ // consumers cannot mistake the stale empty snapshot for a completed list.
968
+ loading: enabled ? !visible.active || visible.loading : false,
969
+ refreshing: visible.refreshing,
970
+ error: visible.error,
971
+ refresh,
972
+ create,
973
+ end
974
+ };
975
+ }
976
+ function emptyState2(workspaceId, loading) {
977
+ return {
978
+ workspaceId,
979
+ active: loading,
980
+ revision: 0,
981
+ sessions: [],
982
+ loading,
983
+ refreshing: false,
984
+ error: null
985
+ };
986
+ }
987
+ function sortComputerSessions(sessions) {
988
+ return [...sessions].sort((left, right) => {
989
+ const live = Number(isLiveComputer(right)) - Number(isLiveComputer(left));
990
+ return live || timestamp(right.lastUsedAt) - timestamp(left.lastUsedAt) || left.id.localeCompare(right.id);
991
+ });
992
+ }
993
+ function isLiveComputer(session) {
994
+ return !["ending", "ended", "failed", "lost"].includes(session.lifecycle);
995
+ }
996
+ function timestamp(value) {
997
+ const parsed = Date.parse(value);
998
+ return Number.isFinite(parsed) ? parsed : 0;
999
+ }
1000
+ function mergeSession(current, workspaceId, session) {
1001
+ if (current.workspaceId !== workspaceId) return current;
1002
+ const sessions = current.sessions.filter((candidate) => candidate.id !== session.id);
1003
+ sessions.push(session);
1004
+ return { ...current, sessions: sortComputerSessions(sessions), error: null };
1005
+ }
1006
+
1007
+ // src/components/computer-viewer.tsx
1008
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
1009
+ function ComputerViewer({
1010
+ sessionId,
1011
+ enabled = true,
1012
+ className,
1013
+ onNotify,
1014
+ webSocketFactory,
1015
+ renderEmpty,
1016
+ requestedComputerSessionId,
1017
+ requestedComputerRequestId,
1018
+ initialComputerSessionId,
1019
+ onComputerSessionIdChange,
1020
+ ...override
1021
+ }) {
1022
+ const registry = useComputerSessions({ ...override, sessionId, enabled });
1023
+ const createSession = registry.create;
1024
+ const refreshRegistry = registry.refresh;
1025
+ const liveRelevant = useMemo2(
1026
+ () => registry.relevantSessions.filter((session) => isLiveComputer2(session)),
1027
+ [registry.relevantSessions]
1028
+ );
1029
+ const connectedPlacementLoss = useMemo2(
1030
+ () => registry.relevantSessions.find(
1031
+ (session) => session.lifecycle === "lost" && session.failureCode === "source_placement_changed" && session.placement.kind === "connected_machine"
1032
+ ) ?? null,
1033
+ [registry.relevantSessions]
1034
+ );
1035
+ const recentRelevantFailure = useMemo2(
1036
+ () => liveRelevant.length === 0 ? registry.relevantSessions.find(
1037
+ (session) => ["failed", "lost"].includes(session.lifecycle) && !(session.failureCode === "source_placement_changed" && session.placement.kind === "connected_machine")
1038
+ ) ?? null : null,
1039
+ [liveRelevant.length, registry.relevantSessions]
1040
+ );
1041
+ const liveSessions = useMemo2(() => {
1042
+ const sessions = registry.sessions.filter((session) => isLiveComputer2(session));
1043
+ return recentRelevantFailure ? [...sessions, recentRelevantFailure] : sessions;
1044
+ }, [recentRelevantFailure, registry.sessions]);
1045
+ const relevant = useMemo2(
1046
+ () => liveRelevant.length > 0 ? liveRelevant : recentRelevantFailure ? [recentRelevantFailure] : [],
1047
+ [liveRelevant, recentRelevantFailure]
1048
+ );
1049
+ const [selection, setSelection] = useState5(
1050
+ () => initialComputerSessionId ? { sessionId: initialComputerSessionId, pinned: true } : null
1051
+ );
1052
+ const selectComputerSession = useCallback5(
1053
+ (next) => {
1054
+ setSelection(next);
1055
+ onComputerSessionIdChange?.(next?.sessionId ?? null);
1056
+ },
1057
+ [onComputerSessionIdChange]
1058
+ );
1059
+ const interventions = useInteractionInterventions({
1060
+ ...override,
1061
+ enabled,
1062
+ resourceKind: "computer_session"
1063
+ });
1064
+ const [creating, setCreating] = useState5(false);
1065
+ const [createError, setCreateError] = useState5(null);
1066
+ const [showControls, setShowControls] = useState5(false);
1067
+ const previousSessionIdRef = useRef5(sessionId);
1068
+ const handledRequestRef = useRef5(null);
1069
+ const seenInterventionIdsRef = useRef5(/* @__PURE__ */ new Set());
1070
+ const createInFlightRef = useRef5(false);
1071
+ const autoCreateSessionRef = useRef5(null);
1072
+ const emptyCatalogConfirmationRef = useRef5(null);
1073
+ const endedGenerationLossRef = useRef5(/* @__PURE__ */ new Set());
1074
+ const recreatedPlacementLossRef = useRef5(/* @__PURE__ */ new Set());
1075
+ const [suppressGenericCreate, setSuppressGenericCreate] = useState5(false);
1076
+ const endStaleComputer = registry.end;
1077
+ useEffect5(() => {
1078
+ if (!enabled) return;
1079
+ for (const session of registry.sessions) {
1080
+ if (!isAttachedChromeGenerationLoss(session)) continue;
1081
+ setSuppressGenericCreate(true);
1082
+ if (endedGenerationLossRef.current.has(session.id)) continue;
1083
+ endedGenerationLossRef.current.add(session.id);
1084
+ void endStaleComputer(session.id).catch(() => {
1085
+ endedGenerationLossRef.current.delete(session.id);
1086
+ });
1087
+ }
1088
+ }, [enabled, endStaleComputer, registry.sessions]);
1089
+ const notifyError = useCallback5(
1090
+ (cause, fallback) => {
1091
+ onNotify?.({
1092
+ kind: "error",
1093
+ message: cause instanceof Error ? cause.message : fallback
1094
+ });
1095
+ },
1096
+ [onNotify]
1097
+ );
1098
+ useEffect5(() => {
1099
+ if (previousSessionIdRef.current === sessionId) return;
1100
+ previousSessionIdRef.current = sessionId;
1101
+ handledRequestRef.current = null;
1102
+ autoCreateSessionRef.current = null;
1103
+ emptyCatalogConfirmationRef.current = null;
1104
+ recreatedPlacementLossRef.current.clear();
1105
+ setSuppressGenericCreate(false);
1106
+ setCreateError(null);
1107
+ setSelection(
1108
+ initialComputerSessionId ? { sessionId: initialComputerSessionId, pinned: true } : null
1109
+ );
1110
+ }, [initialComputerSessionId, sessionId]);
1111
+ useEffect5(() => {
1112
+ if (!enabled || registry.loading) return;
1113
+ const requested = requestedComputerSessionId ? liveSessions.find((session) => session.id === requestedComputerSessionId) ?? null : null;
1114
+ const requestKey = requested ? `${requestedComputerRequestId ?? "default"}:${requested.id}` : null;
1115
+ if (requested && requestKey && handledRequestRef.current !== requestKey) {
1116
+ handledRequestRef.current = requestKey;
1117
+ selectComputerSession({ sessionId: requested.id, pinned: true });
1118
+ return;
1119
+ }
1120
+ const selectedStillLive = liveSessions.some((session) => session.id === selection?.sessionId);
1121
+ if (selection?.pinned && selectedStillLive) return;
1122
+ const preferred = relevant[0] ?? null;
1123
+ if (!preferred) {
1124
+ if (selection) selectComputerSession(null);
1125
+ return;
1126
+ }
1127
+ if (!selectedStillLive || !selection?.pinned) {
1128
+ if (selection?.sessionId !== preferred.id || selection.pinned) {
1129
+ selectComputerSession({ sessionId: preferred.id, pinned: false });
1130
+ }
1131
+ }
1132
+ }, [
1133
+ liveSessions,
1134
+ enabled,
1135
+ registry.loading,
1136
+ relevant,
1137
+ requestedComputerRequestId,
1138
+ requestedComputerSessionId,
1139
+ selectComputerSession,
1140
+ selection
1141
+ ]);
1142
+ const selectedRegistrySession = useMemo2(
1143
+ () => liveSessions.find((session) => session.id === selection?.sessionId) ?? null,
1144
+ [liveSessions, selection?.sessionId]
1145
+ );
1146
+ const interventionCounts = useMemo2(
1147
+ () => countInterventions(interventions.interventions),
1148
+ [interventions.interventions]
1149
+ );
1150
+ const selectedInterventions = useMemo2(
1151
+ () => interventions.interventions.filter(
1152
+ (intervention) => intervention.resourceId === selection?.sessionId
1153
+ ),
1154
+ [interventions.interventions, selection?.sessionId]
1155
+ );
1156
+ const controllerReady = selectedRegistrySession?.lifecycle === "active";
1157
+ const computer = useComputerSession({
1158
+ ...override,
1159
+ computerSessionId: selection?.sessionId ?? null,
1160
+ enabled: enabled && selection !== null && controllerReady
1161
+ });
1162
+ useEffect5(() => {
1163
+ setShowControls(computer.selectedTarget?.kind === "app");
1164
+ }, [sessionId, selection?.sessionId, computer.selectedTarget?.id, computer.selectedTarget?.kind]);
1165
+ useEffect5(() => {
1166
+ if (!isSourcePlacementChangedError(computer.error, "computer_session")) return;
1167
+ void refreshRegistry();
1168
+ }, [computer.error, refreshRegistry]);
1169
+ const act = computer.act;
1170
+ const actFromFrame = computer.actFromFrame;
1171
+ const refreshComputer = computer.refresh;
1172
+ const frames = useComputerFrameStream({
1173
+ ...override,
1174
+ computerSessionId: selection?.sessionId ?? null,
1175
+ targetId: computer.selectedTarget?.id ?? null,
1176
+ enabled: enabled && selection !== null && controllerReady && computer.selectedTarget !== null && isComputerFrameTarget(computer.selectedTarget),
1177
+ stream: {
1178
+ format: "jpeg",
1179
+ quality: 78,
1180
+ maxWidth: 1920,
1181
+ maxHeight: 1200,
1182
+ everyNthFrame: 1
1183
+ },
1184
+ ...webSocketFactory ? { webSocketFactory } : {}
1185
+ });
1186
+ const displayedFrame = frames.frame && frames.frame.computerSessionId === selection?.sessionId && frames.frame.targetId === computer.selectedTarget?.id && frames.frame.targetGeneration === computer.selectedTarget?.targetGeneration ? frames.frame : null;
1187
+ const rfbStream = frames.attachment?.stream.kind === "direct_rfb" ? frames.attachment.stream : null;
1188
+ const appTarget = computer.selectedTarget?.kind === "app";
1189
+ const appInputPosture = useComputerInputPosture({
1190
+ ...override,
1191
+ computerSessionId: selection?.sessionId ?? null,
1192
+ controllerGeneration: computer.session?.controller?.controllerGeneration ?? null,
1193
+ enabled: enabled && controllerReady && appTarget
1194
+ });
1195
+ const inputPostureRef = useRef5({
1196
+ sessionId: selection?.sessionId ?? null,
1197
+ controllerGeneration: computer.session?.controller?.controllerGeneration ?? null,
1198
+ denied: false
1199
+ });
1200
+ const postureSessionId = selection?.sessionId ?? null;
1201
+ const postureController = computer.session?.controller?.controllerGeneration ?? null;
1202
+ if (inputPostureRef.current.sessionId !== postureSessionId || inputPostureRef.current.controllerGeneration !== postureController) {
1203
+ inputPostureRef.current = {
1204
+ sessionId: postureSessionId,
1205
+ controllerGeneration: postureController,
1206
+ denied: false
1207
+ };
1208
+ }
1209
+ if (frames.attachment?.computerSessionId === postureSessionId && frames.attachment.controllerGeneration === postureController) {
1210
+ inputPostureRef.current.denied = frames.attachment.inputAllowed === false;
1211
+ }
1212
+ const inputDenied = appTarget ? appInputPosture.inputAllowed !== true : inputPostureRef.current.denied;
1213
+ const attachmentInputAllowed = frames.attachment !== null && !inputDenied;
1214
+ const inputPosturePending = appTarget ? appInputPosture.pending : inputDenied && (frames.state === "attaching" || frames.state === "reconnecting");
1215
+ const machineLocked = selectedRegistrySession?.failureCode === "machine_locked";
1216
+ const controlUnavailable = computer.controlError !== null;
1217
+ const connectionState = controlUnavailable ? "error" : frames.state;
1218
+ const rfbInputEnabled = rfbStream?.inputAllowed === true && attachmentInputAllowed && !machineLocked && !controlUnavailable && computer.session?.capabilities?.pointerInput === true && computer.session?.capabilities?.keyboardInput === true;
1219
+ const rfbCapability = useMemo2(() => {
1220
+ if (!rfbStream || !frames.attachment) return null;
1221
+ const bounds = computer.selectedTarget?.bounds;
1222
+ return {
1223
+ transport: "vnc-ws",
1224
+ client: "novnc",
1225
+ mode: rfbInputEnabled ? "interactive" : "read-only",
1226
+ url: rfbStream.url,
1227
+ token: null,
1228
+ expiresAt: frames.attachment.expiresAt,
1229
+ resolution: [bounds?.width ?? 1440, bounds?.height ?? 900],
1230
+ unredacted: true,
1231
+ requiresAcknowledgment: false,
1232
+ acknowledged: true,
1233
+ shared: false,
1234
+ sharedSessionIds: [],
1235
+ reason: null
1236
+ };
1237
+ }, [computer.selectedTarget?.bounds, frames.attachment, rfbInputEnabled, rfbStream]);
1238
+ const currentIds = useMemo2(() => new Set(relevant.map((session) => session.id)), [relevant]);
1239
+ useEffect5(() => {
1240
+ for (const intervention of interventions.interventions) {
1241
+ if (seenInterventionIdsRef.current.has(intervention.id)) continue;
1242
+ seenInterventionIdsRef.current.add(intervention.id);
1243
+ onNotify?.({
1244
+ kind: "info",
1245
+ message: `${interventionTitle(intervention)}: ${intervention.reason}`
1246
+ });
1247
+ }
1248
+ }, [interventions.interventions, onNotify]);
1249
+ const resolveIntervention = useCallback5(
1250
+ (intervention, outcome) => {
1251
+ void interventions.resolve(intervention.id, {
1252
+ expectedVersion: intervention.version,
1253
+ outcome
1254
+ }).then(() => {
1255
+ onNotify?.({
1256
+ kind: "info",
1257
+ message: outcome === "completed" ? "Agent notified. Continuing work." : "Request cancelled."
1258
+ });
1259
+ }).catch((cause) => notifyError(cause, "Could not update the desktop request."));
1260
+ },
1261
+ [interventions, notifyError, onNotify]
1262
+ );
1263
+ const createComputer = useCallback5(() => {
1264
+ if (createInFlightRef.current) return;
1265
+ createInFlightRef.current = true;
1266
+ setCreateError(null);
1267
+ setCreating(true);
1268
+ void createSession({ sessionId, name: "Desktop" }).then((response) => {
1269
+ selectComputerSession({ sessionId: response.session.id, pinned: false });
1270
+ }).catch((cause) => {
1271
+ const error = cause instanceof Error ? cause : new Error("Could not open a desktop.");
1272
+ setCreateError(error);
1273
+ notifyError(error, "Could not open a desktop.");
1274
+ }).finally(() => {
1275
+ createInFlightRef.current = false;
1276
+ setCreating(false);
1277
+ });
1278
+ }, [createSession, notifyError, selectComputerSession, sessionId]);
1279
+ useEffect5(() => {
1280
+ if (!enabled || registry.loading || registry.refreshing || liveRelevant.length > 0 || !connectedPlacementLoss || recreatedPlacementLossRef.current.has(connectedPlacementLoss.id)) {
1281
+ return;
1282
+ }
1283
+ recreatedPlacementLossRef.current.add(connectedPlacementLoss.id);
1284
+ autoCreateSessionRef.current = sessionId;
1285
+ emptyCatalogConfirmationRef.current = null;
1286
+ createComputer();
1287
+ }, [
1288
+ connectedPlacementLoss,
1289
+ createComputer,
1290
+ enabled,
1291
+ liveRelevant.length,
1292
+ registry.loading,
1293
+ registry.refreshing,
1294
+ sessionId
1295
+ ]);
1296
+ useEffect5(() => {
1297
+ if (relevant.length > 0) {
1298
+ autoCreateSessionRef.current = sessionId;
1299
+ emptyCatalogConfirmationRef.current = null;
1300
+ }
1301
+ }, [relevant.length, sessionId]);
1302
+ useEffect5(() => {
1303
+ if (!enabled || registry.loading || registry.refreshing || registry.error || relevant.length > 0 || suppressGenericCreate || autoCreateSessionRef.current === sessionId) {
1304
+ return;
1305
+ }
1306
+ if (emptyCatalogConfirmationRef.current !== sessionId) {
1307
+ emptyCatalogConfirmationRef.current = sessionId;
1308
+ void refreshRegistry();
1309
+ return;
1310
+ }
1311
+ autoCreateSessionRef.current = sessionId;
1312
+ createComputer();
1313
+ }, [
1314
+ createComputer,
1315
+ enabled,
1316
+ refreshRegistry,
1317
+ registry.error,
1318
+ registry.loading,
1319
+ registry.refreshing,
1320
+ relevant.length,
1321
+ sessionId,
1322
+ suppressGenericCreate
1323
+ ]);
1324
+ const perform = useCallback5(
1325
+ async (action, frame) => {
1326
+ if (computer.controlError) throw computer.controlError;
1327
+ if (inputDenied) throw new Error("Desktop is view only. Refresh to check desktop controls.");
1328
+ let receipt;
1329
+ if (action.type === "pointer") {
1330
+ if (!frame) throw new Error("Desktop view is not ready for pointer input.");
1331
+ receipt = await actFromFrame(action, frame);
1332
+ } else {
1333
+ receipt = await act(action);
1334
+ }
1335
+ if (receipt.state !== "completed") {
1336
+ throw new Error(receipt.error?.message ?? "Desktop input did not complete.");
1337
+ }
1338
+ },
1339
+ [act, actFromFrame, computer.controlError, inputDenied]
1340
+ );
1341
+ const reconnect = () => {
1342
+ void refreshComputer();
1343
+ if (appTarget) appInputPosture.refresh();
1344
+ else frames.reconnect();
1345
+ };
1346
+ const copyFromRfb = useCallback5(
1347
+ (event) => {
1348
+ if (!rfbInputEnabled || !rfbStream || computer.session?.capabilities?.clipboard !== true)
1349
+ return;
1350
+ event.preventDefault();
1351
+ event.stopPropagation();
1352
+ void perform({ type: "clipboard", operation: "copy" }, null).then(() => computer.readClipboard()).then(async (clipboard) => {
1353
+ if (clipboard.text && !await copyTextToClipboard(clipboard.text)) {
1354
+ throw new Error("Desktop text could not be copied to the local clipboard");
1355
+ }
1356
+ }).catch((cause) => notifyError(cause, "Could not copy from the desktop."));
1357
+ },
1358
+ [computer, notifyError, perform, rfbInputEnabled, rfbStream]
1359
+ );
1360
+ const pasteIntoRfb = useCallback5(
1361
+ (text) => {
1362
+ if (!rfbInputEnabled || !rfbStream || computer.session?.capabilities?.clipboard !== true)
1363
+ return false;
1364
+ void (async () => {
1365
+ await perform({ type: "clipboard", operation: "write", text }, null);
1366
+ assertExactComputerClipboard(await computer.readClipboard(), text);
1367
+ await perform({ type: "clipboard", operation: "paste" }, null);
1368
+ })().catch((cause) => notifyError(cause, "Could not paste into the desktop."));
1369
+ return true;
1370
+ },
1371
+ [computer, notifyError, perform, rfbInputEnabled, rfbStream]
1372
+ );
1373
+ const hideGenericCreate = suppressGenericCreate && liveRelevant.length === 0;
1374
+ const generationLossSelected = selectedRegistrySession !== null && isAttachedChromeGenerationLoss(selectedRegistrySession);
1375
+ const generationLossFrames = isAttachedChromeGenerationLossError(frames.error);
1376
+ if (!enabled) return null;
1377
+ if (registry.loading && liveSessions.length === 0) {
1378
+ return /* @__PURE__ */ jsx(
1379
+ ComputerNotice,
1380
+ {
1381
+ icon: /* @__PURE__ */ jsx(LoaderCircleIcon, { className: "size-4 animate-spin" }),
1382
+ text: "Finding workspace desktops\u2026",
1383
+ ...className ? { className } : {}
1384
+ }
1385
+ );
1386
+ }
1387
+ if (liveSessions.length === 0) {
1388
+ if (hideGenericCreate) {
1389
+ return /* @__PURE__ */ jsx("div", { className: cn("grid h-full place-items-center bg-og-bg p-6", className), children: /* @__PURE__ */ jsxs("div", { className: "max-w-sm text-center", children: [
1390
+ /* @__PURE__ */ jsx(CircleAlertIcon, { className: "mx-auto size-5 text-og-danger" }),
1391
+ /* @__PURE__ */ jsx("p", { className: "mt-3 text-og-menu font-medium text-og-fg", children: "Chrome reconnected\u2014open a fresh browser/desktop." }),
1392
+ /* @__PURE__ */ jsx("p", { className: "mt-1 text-og-control leading-5 text-og-fg-muted", children: "This Chrome profile is live again, but the previous desktop cannot move to the new connection. Use Browser \u2192 New browser \u2192 Connected Chrome." })
1393
+ ] }) });
1394
+ }
1395
+ if (renderEmpty) return renderEmpty(createComputer, creating);
1396
+ const error = createError ?? registry.error;
1397
+ return /* @__PURE__ */ jsx("div", { className: cn("grid h-full place-items-center bg-og-bg p-6", className), children: /* @__PURE__ */ jsxs("div", { className: "max-w-sm text-center", children: [
1398
+ /* @__PURE__ */ jsx("span", { className: "mx-auto grid size-10 place-items-center rounded-og-lg border border-og-border bg-og-surface-1 text-og-fg-muted", children: error ? /* @__PURE__ */ jsx(CircleAlertIcon, { className: "size-5 text-og-danger" }) : /* @__PURE__ */ jsx(LoaderCircleIcon, { className: "size-5 animate-spin" }) }),
1399
+ /* @__PURE__ */ jsx("p", { className: "mt-3 text-og-menu font-medium text-og-fg", children: error ? "Desktop didn\u2019t open" : "Opening desktop\u2026" }),
1400
+ /* @__PURE__ */ jsx("p", { className: "mt-1 text-og-control leading-5 text-og-fg-muted", children: error?.message ?? "Preparing this agent\u2019s desktop. It will appear here when ready." }),
1401
+ error ? /* @__PURE__ */ jsxs(
1402
+ "button",
1403
+ {
1404
+ type: "button",
1405
+ onClick: createError ? createComputer : () => void refreshRegistry(),
1406
+ disabled: creating || registry.refreshing,
1407
+ className: "mt-4 inline-flex h-8 items-center gap-1.5 rounded-og-sm border border-og-primary-border bg-og-primary text-og-primary-fg px-3 text-og-control font-medium transition hover:bg-og-primary-hover disabled:opacity-50",
1408
+ children: [
1409
+ creating || registry.refreshing ? /* @__PURE__ */ jsx(LoaderCircleIcon, { className: "size-3.5 animate-spin" }) : /* @__PURE__ */ jsx(RotateCcwIcon, { className: "size-3.5" }),
1410
+ "Try again"
1411
+ ]
1412
+ }
1413
+ ) : null
1414
+ ] }) });
1415
+ }
1416
+ return /* @__PURE__ */ jsxs(
1417
+ "div",
1418
+ {
1419
+ className: cn(
1420
+ "@container/computer-viewer relative flex h-full min-h-0 min-w-0 flex-col overflow-hidden bg-og-bg",
1421
+ className
1422
+ ),
1423
+ children: [
1424
+ /* @__PURE__ */ jsx(
1425
+ ComputerToolbar,
1426
+ {
1427
+ sessions: liveSessions,
1428
+ relevantSessionIds: currentIds,
1429
+ selectedSessionId: selection?.sessionId ?? null,
1430
+ creating,
1431
+ refreshing: registry.refreshing || inputPosturePending,
1432
+ interventionCounts,
1433
+ targets: controllerReady ? computer.targets : [],
1434
+ selectedTargetId: computer.selectedTarget?.id ?? null,
1435
+ targetsLoading: controllerReady && computer.loading,
1436
+ onSelectTarget: (targetId) => {
1437
+ void computer.selectTarget(targetId).catch((cause) => notifyError(cause, "Could not switch desktop views."));
1438
+ },
1439
+ onSelect: (computerSessionId) => selectComputerSession({ sessionId: computerSessionId, pinned: true }),
1440
+ onFollow: () => {
1441
+ if (relevant[0]) selectComputerSession({ sessionId: relevant[0].id, pinned: false });
1442
+ },
1443
+ onCreate: hideGenericCreate ? void 0 : createComputer,
1444
+ onRefresh: () => {
1445
+ if (appTarget) {
1446
+ appInputPosture.refresh();
1447
+ } else if ((connectionState === "error" || inputDenied) && !generationLossSelected && !generationLossFrames) {
1448
+ frames.reconnect();
1449
+ }
1450
+ void Promise.all([refreshRegistry(), refreshComputer()]);
1451
+ }
1452
+ }
1453
+ ),
1454
+ /* @__PURE__ */ jsx(
1455
+ InteractionInterventionBanner,
1456
+ {
1457
+ interventions: selectedInterventions,
1458
+ activeTargetId: computer.selectedTarget?.id ?? null,
1459
+ mutating: interventions.mutating,
1460
+ onOpen: (intervention) => void computer.selectTarget(intervention.targetId).catch((cause) => notifyError(cause, "Could not open the requested desktop view.")),
1461
+ onResolve: resolveIntervention
1462
+ }
1463
+ ),
1464
+ !selectedRegistrySession ? /* @__PURE__ */ jsx(
1465
+ ComputerUnselectedPanel,
1466
+ {
1467
+ peerCount: liveSessions.length,
1468
+ creating,
1469
+ error: createError,
1470
+ generationLoss: suppressGenericCreate,
1471
+ onCreate: createComputer
1472
+ }
1473
+ ) : !controllerReady ? /* @__PURE__ */ jsx(
1474
+ ComputerLifecyclePanel,
1475
+ {
1476
+ session: selectedRegistrySession,
1477
+ creating,
1478
+ onRefresh: registry.refresh,
1479
+ onRetry: createComputer
1480
+ }
1481
+ ) : /* @__PURE__ */ jsxs(Fragment, { children: [
1482
+ /* @__PURE__ */ jsxs("div", { className: "flex min-h-0 flex-1 flex-col @xl/computer-viewer:flex-row", children: [
1483
+ rfbStream ? /* @__PURE__ */ jsxs("div", { className: "relative min-h-0 flex-1 bg-black", onCopyCapture: copyFromRfb, children: [
1484
+ /* @__PURE__ */ jsx(
1485
+ DesktopViewer,
1486
+ {
1487
+ capability: rfbCapability,
1488
+ interactive: rfbInputEnabled,
1489
+ showControlToggle: false,
1490
+ webSocketProtocols: rfbStream.protocols,
1491
+ onPasteText: pasteIntoRfb,
1492
+ targetPlatform: computer.session?.platform ?? null,
1493
+ className: "h-full"
1494
+ }
1495
+ ),
1496
+ !rfbInputEnabled ? /* @__PURE__ */ jsx("div", { className: "pointer-events-none absolute bottom-3 left-1/2 -translate-x-1/2 rounded-full border border-white/10 bg-black/70 px-3 py-1 text-[11px] text-white/80 backdrop-blur", children: controlUnavailable ? /* @__PURE__ */ jsxs(Fragment, { children: [
1497
+ "Desktop controls unavailable",
1498
+ " ",
1499
+ /* @__PURE__ */ jsx(
1500
+ "button",
1501
+ {
1502
+ type: "button",
1503
+ onClick: reconnect,
1504
+ className: "pointer-events-auto ml-2 font-medium underline",
1505
+ children: "Reconnect"
1506
+ }
1507
+ )
1508
+ ] }) : "View only \xB7 direct input unavailable" }) : null
1509
+ ] }) : /* @__PURE__ */ jsx(
1510
+ ComputerViewport,
1511
+ {
1512
+ frame: machineLocked ? null : displayedFrame,
1513
+ observation: computer.observation,
1514
+ target: computer.selectedTarget,
1515
+ machineLocked,
1516
+ controlUnavailable,
1517
+ inputAllowed: !inputDenied,
1518
+ connectionState,
1519
+ connectionError: computer.controlError ?? appInputPosture.error ?? frames.error ?? computer.error,
1520
+ mutating: computer.mutating,
1521
+ backgroundActions: computer.session?.capabilities?.backgroundActions === true,
1522
+ backgroundInput: computer.session?.capabilities?.backgroundInput === true,
1523
+ clipboardEnabled: attachmentInputAllowed && computer.session?.capabilities?.clipboard === true,
1524
+ pointerInput: attachmentInputAllowed && computer.session?.capabilities?.pointerInput === true,
1525
+ keyboardInput: attachmentInputAllowed && computer.session?.capabilities?.keyboardInput === true,
1526
+ onAction: perform,
1527
+ onReadClipboard: computer.readClipboard,
1528
+ onReconnect: generationLossSelected || generationLossFrames ? () => void 0 : reconnect,
1529
+ onError: (cause) => notifyError(cause, "Desktop input failed.")
1530
+ },
1531
+ JSON.stringify([
1532
+ selection?.sessionId,
1533
+ computer.selectedTarget?.id,
1534
+ computer.selectedTarget?.targetGeneration,
1535
+ frames.attachment?.controllerGeneration
1536
+ ])
1537
+ ),
1538
+ showControls ? /* @__PURE__ */ jsx(
1539
+ ComputerSemanticPanel,
1540
+ {
1541
+ observation: computer.observation,
1542
+ mutating: computer.mutating || controlUnavailable || inputDenied,
1543
+ controlUnavailable,
1544
+ inputAllowed: !inputDenied,
1545
+ onAction: (action) => void perform(action, null).catch(
1546
+ (cause) => notifyError(cause, "Desktop action failed.")
1547
+ )
1548
+ }
1549
+ ) : null
1550
+ ] }),
1551
+ /* @__PURE__ */ jsx(
1552
+ ComputerStatusBar,
1553
+ {
1554
+ session: computer.session,
1555
+ target: computer.selectedTarget,
1556
+ connectionState,
1557
+ controlUnavailable,
1558
+ inputAllowed: !inputDenied,
1559
+ refreshing: registry.refreshing,
1560
+ showControls,
1561
+ controlCount: semanticControls(computer.observation).length,
1562
+ onToggleControls: () => setShowControls((current) => !current)
1563
+ }
1564
+ )
1565
+ ] })
1566
+ ]
1567
+ }
1568
+ );
1569
+ }
1570
+ function ComputerUnselectedPanel(props) {
1571
+ return /* @__PURE__ */ jsx("div", { className: "grid min-h-0 flex-1 place-items-center bg-og-bg p-6 text-center", children: /* @__PURE__ */ jsxs("div", { className: "max-w-sm", children: [
1572
+ /* @__PURE__ */ jsx("span", { className: "mx-auto grid size-10 place-items-center rounded-og-md border border-og-border bg-og-surface-1 text-og-fg-muted", children: props.error || props.generationLoss ? /* @__PURE__ */ jsx(CircleAlertIcon, { className: "size-4.5 text-og-danger" }) : /* @__PURE__ */ jsx(LoaderCircleIcon, { className: "size-4.5 animate-spin" }) }),
1573
+ /* @__PURE__ */ jsx("p", { className: "mt-3 text-og-menu font-medium text-og-fg", children: props.generationLoss ? "Chrome reconnected\u2014open a fresh browser/desktop." : props.error ? "Desktop didn\u2019t open" : "Opening desktop\u2026" }),
1574
+ /* @__PURE__ */ jsx("p", { className: "mt-1 text-og-control leading-5 text-og-fg-muted", children: props.generationLoss ? "This Chrome profile is live again, but the previous desktop cannot move to the new connection. Use Browser \u2192 New browser \u2192 Connected Chrome." : props.error?.message ?? (props.peerCount === 1 ? "Preparing this agent\u2019s desktop. One peer desktop remains available from the menu." : `Preparing this agent\u2019s desktop. ${props.peerCount} peer desktops remain available from the menu.`) }),
1575
+ props.error && !props.generationLoss ? /* @__PURE__ */ jsxs(
1576
+ "button",
1577
+ {
1578
+ type: "button",
1579
+ disabled: props.creating,
1580
+ onClick: props.onCreate,
1581
+ className: "mt-4 inline-flex h-8 items-center gap-1.5 rounded-og-sm border border-og-border bg-og-surface-1 px-3 text-og-control font-medium text-og-fg transition hover:bg-og-surface-2 disabled:cursor-not-allowed disabled:opacity-50",
1582
+ children: [
1583
+ props.creating ? /* @__PURE__ */ jsx(LoaderCircleIcon, { className: "size-3.5 animate-spin" }) : /* @__PURE__ */ jsx(RotateCcwIcon, { className: "size-3.5" }),
1584
+ "Try again"
1585
+ ]
1586
+ }
1587
+ ) : null
1588
+ ] }) });
1589
+ }
1590
+ function ComputerToolbar(props) {
1591
+ const detailsRef = useViewerMenuDismiss();
1592
+ const selected = props.sessions.find((session) => session.id === props.selectedSessionId);
1593
+ const current = props.sessions.filter((session) => props.relevantSessionIds.has(session.id));
1594
+ const others = props.sessions.filter((session) => !props.relevantSessionIds.has(session.id));
1595
+ const closeMenu = (restoreFocus) => {
1596
+ detailsRef.current?.removeAttribute("open");
1597
+ if (restoreFocus) detailsRef.current?.querySelector("summary")?.focus();
1598
+ };
1599
+ const choose = (id, restoreFocus) => {
1600
+ props.onSelect(id);
1601
+ closeMenu(restoreFocus);
1602
+ };
1603
+ return /* @__PURE__ */ jsxs("div", { className: "flex h-10 shrink-0 items-center gap-1.5 border-b border-og-border bg-og-surface-1 px-2", children: [
1604
+ /* @__PURE__ */ jsxs("details", { ref: detailsRef, className: "min-w-0", children: [
1605
+ /* @__PURE__ */ jsxs("summary", { className: "flex h-7 max-w-32 cursor-pointer list-none items-center gap-2 rounded-og-sm px-2 text-og-control text-og-fg transition hover:bg-og-surface-2 @sm/computer-viewer:max-w-52 [&::-webkit-details-marker]:hidden", children: [
1606
+ /* @__PURE__ */ jsx(MonitorIcon, { className: "size-3.5 shrink-0 text-og-fg-muted" }),
1607
+ /* @__PURE__ */ jsx("span", { className: "truncate font-medium", children: selected?.name === "Computer" ? "Desktop" : selected?.name ?? "Desktop" }),
1608
+ (props.interventionCounts.get(selected?.id ?? "") ?? 0) > 0 ? /* @__PURE__ */ jsx("span", { className: "size-1.5 shrink-0 rounded-full bg-og-status-waiting" }) : null,
1609
+ /* @__PURE__ */ jsx(ChevronDownIcon, { className: "size-3 shrink-0 text-og-fg-subtle" })
1610
+ ] }),
1611
+ /* @__PURE__ */ jsxs("div", { className: "absolute left-2 top-10 z-30 flex max-h-[calc(100%-3rem)] w-72 max-w-[calc(100%-1rem)] flex-col overflow-hidden rounded-og-md border border-og-border bg-og-surface-1 p-1 shadow-xl", children: [
1612
+ /* @__PURE__ */ jsxs("div", { className: "min-h-0 max-h-96 overflow-y-auto overscroll-contain", children: [
1613
+ /* @__PURE__ */ jsx(
1614
+ ComputerSessionGroup,
1615
+ {
1616
+ label: "Current agent",
1617
+ sessions: current,
1618
+ selectedId: props.selectedSessionId,
1619
+ interventionCounts: props.interventionCounts,
1620
+ onSelect: choose
1621
+ }
1622
+ ),
1623
+ /* @__PURE__ */ jsx(
1624
+ ComputerSessionGroup,
1625
+ {
1626
+ label: "Other agents",
1627
+ sessions: others,
1628
+ selectedId: props.selectedSessionId,
1629
+ interventionCounts: props.interventionCounts,
1630
+ onSelect: choose
1631
+ }
1632
+ )
1633
+ ] }),
1634
+ /* @__PURE__ */ jsxs("div", { className: "mt-1 flex shrink-0 gap-1 border-t border-og-border pt-1", children: [
1635
+ current.length > 0 ? /* @__PURE__ */ jsx(
1636
+ MenuButton,
1637
+ {
1638
+ onClick: (event) => {
1639
+ props.onFollow();
1640
+ closeMenu(event.detail === 0);
1641
+ },
1642
+ children: "Follow agent"
1643
+ }
1644
+ ) : null,
1645
+ props.onCreate ? /* @__PURE__ */ jsxs(
1646
+ MenuButton,
1647
+ {
1648
+ onClick: (event) => {
1649
+ props.onCreate?.();
1650
+ closeMenu(event.detail === 0);
1651
+ },
1652
+ disabled: props.creating,
1653
+ children: [
1654
+ /* @__PURE__ */ jsx(PlusIcon, { className: "size-3.5" }),
1655
+ " New desktop"
1656
+ ]
1657
+ }
1658
+ ) : null
1659
+ ] })
1660
+ ] })
1661
+ ] }),
1662
+ /* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1 truncate text-og-xs text-og-fg-subtle", children: selected ? `${platformLabel(selected)} \xB7 ${placementLabel(selected)}` : "" }),
1663
+ /* @__PURE__ */ jsx(
1664
+ ComputerTargetPicker,
1665
+ {
1666
+ targets: props.targets,
1667
+ selectedTargetId: props.selectedTargetId,
1668
+ loading: props.targetsLoading,
1669
+ onSelect: props.onSelectTarget
1670
+ }
1671
+ ),
1672
+ /* @__PURE__ */ jsx(
1673
+ "button",
1674
+ {
1675
+ type: "button",
1676
+ onClick: props.onRefresh,
1677
+ disabled: props.refreshing,
1678
+ "aria-busy": props.refreshing,
1679
+ className: "grid size-7 place-items-center rounded-og-sm text-og-fg-muted transition hover:bg-og-surface-2 hover:text-og-fg",
1680
+ "aria-label": "Refresh desktops",
1681
+ children: /* @__PURE__ */ jsx(RefreshCwIcon, { className: cn("size-3.5", props.refreshing && "animate-spin") })
1682
+ }
1683
+ ),
1684
+ props.onCreate ? /* @__PURE__ */ jsx(
1685
+ "button",
1686
+ {
1687
+ type: "button",
1688
+ onClick: props.onCreate,
1689
+ disabled: props.creating,
1690
+ className: "hidden size-7 place-items-center rounded-og-sm text-og-fg-muted transition hover:bg-og-surface-2 hover:text-og-fg disabled:opacity-40 @sm/computer-viewer:grid",
1691
+ "aria-label": "Open a new desktop",
1692
+ children: props.creating ? /* @__PURE__ */ jsx(LoaderCircleIcon, { className: "size-3.5 animate-spin" }) : /* @__PURE__ */ jsx(PlusIcon, { className: "size-3.5" })
1693
+ }
1694
+ ) : null
1695
+ ] });
1696
+ }
1697
+ function ComputerSessionGroup(props) {
1698
+ if (props.sessions.length === 0) return null;
1699
+ return /* @__PURE__ */ jsxs("div", { className: "py-1", children: [
1700
+ /* @__PURE__ */ jsx("p", { className: "px-2 pb-1 text-[10px] font-medium uppercase tracking-[0.12em] text-og-fg-subtle", children: props.label }),
1701
+ props.sessions.map((session) => {
1702
+ const interventionCount = props.interventionCounts.get(session.id) ?? 0;
1703
+ return /* @__PURE__ */ jsxs(
1704
+ "button",
1705
+ {
1706
+ type: "button",
1707
+ onClick: (event) => props.onSelect(session.id, event.detail === 0),
1708
+ "aria-pressed": session.id === props.selectedId,
1709
+ className: cn(
1710
+ "flex w-full items-center gap-2 rounded-og-sm px-2 py-1.5 text-left transition hover:bg-og-surface-2",
1711
+ session.id === props.selectedId && "bg-og-surface-2"
1712
+ ),
1713
+ children: [
1714
+ /* @__PURE__ */ jsx(
1715
+ "span",
1716
+ {
1717
+ className: cn(
1718
+ "size-1.5 rounded-full",
1719
+ session.lifecycle === "active" ? "bg-og-status-running" : "bg-og-muted"
1720
+ )
1721
+ }
1722
+ ),
1723
+ /* @__PURE__ */ jsxs("span", { className: "min-w-0 flex-1", children: [
1724
+ /* @__PURE__ */ jsx("span", { className: "block truncate text-og-control text-og-fg", children: session.name }),
1725
+ /* @__PURE__ */ jsxs("span", { className: "block truncate text-og-xs text-og-fg-subtle", children: [
1726
+ platformLabel(session),
1727
+ " \xB7 ",
1728
+ placementLabel(session)
1729
+ ] })
1730
+ ] }),
1731
+ interventionCount > 0 ? /* @__PURE__ */ jsx("span", { className: "rounded-full bg-og-status-waiting/10 px-1.5 py-0.5 text-[10px] font-medium text-og-status-waiting", children: interventionCount }) : null
1732
+ ]
1733
+ },
1734
+ session.id
1735
+ );
1736
+ })
1737
+ ] });
1738
+ }
1739
+ function MenuButton(props) {
1740
+ return /* @__PURE__ */ jsx(
1741
+ "button",
1742
+ {
1743
+ type: "button",
1744
+ onClick: props.onClick,
1745
+ disabled: props.disabled,
1746
+ className: "flex h-7 flex-1 items-center justify-center gap-1 rounded-og-sm px-2 text-og-control text-og-fg-muted transition hover:bg-og-surface-2 hover:text-og-fg disabled:opacity-50",
1747
+ children: props.children
1748
+ }
1749
+ );
1750
+ }
1751
+ function ComputerTargetPicker(props) {
1752
+ const screens = props.targets.filter((target) => target.kind === "screen");
1753
+ const advancedTargets = props.targets.filter(
1754
+ (target) => target.kind === "app" || target.kind === "window" && isRenderableComputerView(target)
1755
+ );
1756
+ const advancedRef = useViewerMenuDismiss(advancedTargets.length > 0);
1757
+ const selected = props.targets.find((target) => target.id === props.selectedTargetId);
1758
+ const choose = (id, restoreFocus = false) => {
1759
+ props.onSelect(id);
1760
+ if (advancedRef.current?.open) {
1761
+ advancedRef.current.removeAttribute("open");
1762
+ if (restoreFocus) advancedRef.current.querySelector("summary")?.focus();
1763
+ }
1764
+ };
1765
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
1766
+ screens.length > 1 ? /* @__PURE__ */ jsxs(
1767
+ "select",
1768
+ {
1769
+ "aria-label": "Screen",
1770
+ value: selected?.kind === "screen" ? selected.id : "",
1771
+ onChange: (event) => choose(event.currentTarget.value),
1772
+ className: "h-7 min-w-0 max-w-28 rounded-og-sm border border-og-border bg-og-surface-1 px-1.5 text-og-control text-og-fg outline-hidden focus:border-og-accent @sm/computer-viewer:max-w-40",
1773
+ children: [
1774
+ /* @__PURE__ */ jsx("option", { value: "", disabled: true, hidden: true, children: "Screen" }),
1775
+ screens.map((screen, index) => /* @__PURE__ */ jsx("option", { value: screen.id, children: screen.title || `Screen ${index + 1}` }, screen.id))
1776
+ ]
1777
+ }
1778
+ ) : screens[0] && selected?.kind !== "screen" ? /* @__PURE__ */ jsxs(
1779
+ "button",
1780
+ {
1781
+ type: "button",
1782
+ onClick: () => choose(screens[0].id),
1783
+ className: "flex h-7 shrink-0 items-center gap-1.5 rounded-og-sm px-2 text-og-control text-og-fg-muted transition hover:bg-og-surface-2 hover:text-og-fg",
1784
+ children: [
1785
+ /* @__PURE__ */ jsx(MonitorIcon, { className: "size-3.5" }),
1786
+ " Full desktop"
1787
+ ]
1788
+ }
1789
+ ) : null,
1790
+ advancedTargets.length > 0 ? /* @__PURE__ */ jsxs("details", { ref: advancedRef, className: "shrink-0", children: [
1791
+ /* @__PURE__ */ jsxs(
1792
+ "summary",
1793
+ {
1794
+ "aria-label": "Advanced desktop views",
1795
+ title: "Advanced desktop views",
1796
+ className: "flex h-7 cursor-pointer list-none items-center gap-1.5 rounded-og-sm px-2 text-og-control text-og-fg-muted transition hover:bg-og-surface-2 hover:text-og-fg [&::-webkit-details-marker]:hidden",
1797
+ children: [
1798
+ /* @__PURE__ */ jsx(PanelsTopLeftIcon, { className: "size-3.5" }),
1799
+ /* @__PURE__ */ jsx("span", { className: "hidden @sm/computer-viewer:inline", children: "Advanced" }),
1800
+ /* @__PURE__ */ jsx(ChevronDownIcon, { className: "size-3" })
1801
+ ]
1802
+ }
1803
+ ),
1804
+ /* @__PURE__ */ jsxs("div", { className: "absolute right-2 top-10 z-30 flex max-h-[calc(100%-3rem)] w-64 max-w-[calc(100%-1rem)] flex-col overflow-hidden rounded-og-md border border-og-border bg-og-surface-1 p-1 shadow-xl", children: [
1805
+ /* @__PURE__ */ jsx("p", { className: "shrink-0 px-2 py-1 text-og-xs text-og-fg-subtle", children: "Apps and windows" }),
1806
+ /* @__PURE__ */ jsx("div", { className: "min-h-0 max-h-80 overflow-y-auto overscroll-contain", children: advancedTargets.map((target) => /* @__PURE__ */ jsxs(
1807
+ "button",
1808
+ {
1809
+ type: "button",
1810
+ onClick: (event) => choose(target.id, event.detail === 0),
1811
+ "aria-pressed": target.id === props.selectedTargetId,
1812
+ className: cn(
1813
+ "flex w-full min-w-0 items-center gap-2 rounded-og-sm px-2 py-1.5 text-left transition hover:bg-og-surface-2",
1814
+ target.id === props.selectedTargetId && "bg-og-surface-2"
1815
+ ),
1816
+ children: [
1817
+ /* @__PURE__ */ jsxs("span", { className: "min-w-0 flex-1", children: [
1818
+ /* @__PURE__ */ jsx("span", { className: "block truncate text-og-control text-og-fg", children: target.title || target.applicationId || target.kind }),
1819
+ /* @__PURE__ */ jsx("span", { className: "block text-og-xs text-og-fg-subtle", children: target.kind === "app" ? "App controls" : "Window view" })
1820
+ ] }),
1821
+ target.focused ? /* @__PURE__ */ jsx(
1822
+ "span",
1823
+ {
1824
+ className: "size-1.5 shrink-0 rounded-full bg-og-status-running",
1825
+ "aria-label": "Focused"
1826
+ }
1827
+ ) : null
1828
+ ]
1829
+ },
1830
+ target.id
1831
+ )) })
1832
+ ] })
1833
+ ] }) : null,
1834
+ props.loading ? /* @__PURE__ */ jsx(LoaderCircleIcon, { className: "size-3.5 shrink-0 animate-spin text-og-fg-muted" }) : null
1835
+ ] });
1836
+ }
1837
+ function isRenderableComputerView(target) {
1838
+ if (target.kind === "screen") return true;
1839
+ if (target.kind !== "window" || !target.bounds) return false;
1840
+ return target.bounds.width >= 160 && target.bounds.height >= 90;
1841
+ }
1842
+ function isComputerFrameTarget(target) {
1843
+ return target.kind === "window" || target.kind === "screen";
1844
+ }
1845
+ function ComputerLifecyclePanel(props) {
1846
+ const generationLoss = isAttachedChromeGenerationLoss(props.session);
1847
+ const failed = ["failed", "lost", "repair_required"].includes(props.session.lifecycle);
1848
+ const retryCreatesSession = ["failed", "lost"].includes(props.session.lifecycle);
1849
+ return /* @__PURE__ */ jsx("div", { className: "grid min-h-0 flex-1 place-items-center bg-og-bg p-6", children: /* @__PURE__ */ jsxs("div", { className: "max-w-sm text-center", children: [
1850
+ failed ? /* @__PURE__ */ jsx(CircleAlertIcon, { className: "mx-auto size-5 text-og-danger" }) : /* @__PURE__ */ jsx(LoaderCircleIcon, { className: "mx-auto size-5 animate-spin text-og-fg-muted" }),
1851
+ /* @__PURE__ */ jsx("p", { className: "mt-3 text-og-menu font-medium text-og-fg", children: generationLoss ? "Chrome reconnected\u2014open a fresh browser/desktop." : failed ? "Desktop needs attention" : lifecycleLabel(props.session.lifecycle) }),
1852
+ /* @__PURE__ */ jsx("p", { className: "mt-1 text-og-control leading-5 text-og-fg-muted", children: generationLoss ? "This Chrome profile is live again, but the previous desktop cannot move to the new connection. Use Browser \u2192 New browser \u2192 Connected Chrome." : computerFailureMessage(props.session) ?? "The desktop is being prepared on its placement. It will appear here when ready." }),
1853
+ failed && !generationLoss ? /* @__PURE__ */ jsxs(
1854
+ "button",
1855
+ {
1856
+ type: "button",
1857
+ onClick: () => retryCreatesSession ? props.onRetry() : void props.onRefresh(),
1858
+ disabled: props.creating,
1859
+ className: "mt-4 inline-flex h-8 items-center gap-1.5 rounded-og-sm border border-og-border bg-og-surface-1 px-3 text-og-control font-medium text-og-fg transition hover:bg-og-surface-2",
1860
+ children: [
1861
+ props.creating ? /* @__PURE__ */ jsx(LoaderCircleIcon, { className: "size-3.5 animate-spin" }) : /* @__PURE__ */ jsx(RotateCcwIcon, { className: "size-3.5" }),
1862
+ retryCreatesSession ? "Try again" : "Check again"
1863
+ ]
1864
+ }
1865
+ ) : null
1866
+ ] }) });
1867
+ }
1868
+ function ComputerViewport(props) {
1869
+ const canvasRef = useRef5(null);
1870
+ const inputRef = useRef5(null);
1871
+ const composingRef = useRef5(false);
1872
+ const pointerStartRef = useRef5(null);
1873
+ const clickTimerRef = useRef5(null);
1874
+ const lastClickRef = useRef5(null);
1875
+ const wheelRef = useRef5(null);
1876
+ const pendingTextRef = useRef5(null);
1877
+ const actionRef = useRef5(props.onAction);
1878
+ const readClipboardRef = useRef5(props.onReadClipboard);
1879
+ const errorRef = useRef5(props.onError);
1880
+ const actionTailRef = useRef5(Promise.resolve());
1881
+ const actionQueueEpochRef = useRef5(0);
1882
+ const queuedFrameRef = useRef5(null);
1883
+ const currentFrameRef = useRef5(props.frame);
1884
+ const paintedFrameRef = useRef5(null);
1885
+ const [paintedFrame, setPaintedFrame] = useState5(null);
1886
+ const decodingFrameRef = useRef5(false);
1887
+ const mountedRef = useRef5(true);
1888
+ actionRef.current = props.onAction;
1889
+ readClipboardRef.current = props.onReadClipboard;
1890
+ errorRef.current = props.onError;
1891
+ const streamFailed = props.connectionState === "error";
1892
+ const rawInputEnabled = props.target?.kind !== "app" && !streamFailed && !props.controlUnavailable && !props.machineLocked && (props.backgroundInput || !props.backgroundActions || props.target?.kind === "screen" || props.target?.focused === true);
1893
+ const clearBufferedInput = useCallback5(() => {
1894
+ if (clickTimerRef.current) clearTimeout(clickTimerRef.current);
1895
+ if (wheelRef.current?.timer) clearTimeout(wheelRef.current.timer);
1896
+ if (pendingTextRef.current?.timer) clearTimeout(pendingTextRef.current.timer);
1897
+ clickTimerRef.current = null;
1898
+ wheelRef.current = null;
1899
+ pendingTextRef.current = null;
1900
+ pointerStartRef.current = null;
1901
+ lastClickRef.current = null;
1902
+ composingRef.current = false;
1903
+ if (inputRef.current) inputRef.current.value = "";
1904
+ }, []);
1905
+ const pointerInputEnabled = rawInputEnabled && props.pointerInput;
1906
+ const keyboardInputEnabled = rawInputEnabled && props.keyboardInput;
1907
+ useLayoutEffect(() => {
1908
+ const canvas = canvasRef.current;
1909
+ const viewport = canvas?.parentElement;
1910
+ if (!canvas || !viewport) return;
1911
+ const fit = () => fitComputerCanvas(canvas);
1912
+ fit();
1913
+ if (typeof ResizeObserver === "undefined") {
1914
+ window.addEventListener("resize", fit);
1915
+ return () => window.removeEventListener("resize", fit);
1916
+ }
1917
+ const observer = new ResizeObserver(fit);
1918
+ observer.observe(viewport);
1919
+ return () => observer.disconnect();
1920
+ }, []);
1921
+ const paintQueuedFrames = useCallback5(() => {
1922
+ if (decodingFrameRef.current) return;
1923
+ decodingFrameRef.current = true;
1924
+ void (async () => {
1925
+ try {
1926
+ while (mountedRef.current) {
1927
+ const frame = queuedFrameRef.current;
1928
+ queuedFrameRef.current = null;
1929
+ if (!frame) break;
1930
+ let objectUrl = null;
1931
+ try {
1932
+ const blob = new Blob([frame.data.slice().buffer], { type: frame.mediaType });
1933
+ if (typeof createImageBitmap === "function") {
1934
+ const bitmap = await createImageBitmap(blob);
1935
+ try {
1936
+ const canvas2 = canvasRef.current;
1937
+ if (mountedRef.current && canvas2 && currentFrameRef.current && sameComputerTarget(frame, currentFrameRef.current)) {
1938
+ paintCanvas(canvas2, bitmap, frame.width, frame.height);
1939
+ paintedFrameRef.current = frame;
1940
+ setPaintedFrame(frame);
1941
+ }
1942
+ } finally {
1943
+ bitmap.close();
1944
+ }
1945
+ continue;
1946
+ }
1947
+ objectUrl = URL.createObjectURL(blob);
1948
+ const image = await loadImage(objectUrl);
1949
+ const canvas = canvasRef.current;
1950
+ if (mountedRef.current && canvas && currentFrameRef.current && sameComputerTarget(frame, currentFrameRef.current)) {
1951
+ paintCanvas(canvas, image, frame.width, frame.height);
1952
+ paintedFrameRef.current = frame;
1953
+ setPaintedFrame(frame);
1954
+ }
1955
+ } catch (cause) {
1956
+ if (mountedRef.current && currentFrameRef.current && sameComputerTarget(frame, currentFrameRef.current))
1957
+ errorRef.current(cause);
1958
+ } finally {
1959
+ if (objectUrl) URL.revokeObjectURL(objectUrl);
1960
+ }
1961
+ }
1962
+ } finally {
1963
+ decodingFrameRef.current = false;
1964
+ if (mountedRef.current && queuedFrameRef.current) paintQueuedFrames();
1965
+ }
1966
+ })();
1967
+ }, []);
1968
+ useLayoutEffect(() => {
1969
+ mountedRef.current = true;
1970
+ return () => {
1971
+ mountedRef.current = false;
1972
+ actionQueueEpochRef.current += 1;
1973
+ queuedFrameRef.current = null;
1974
+ clearBufferedInput();
1975
+ };
1976
+ }, [clearBufferedInput]);
1977
+ useLayoutEffect(() => {
1978
+ if (currentFrameRef.current && !props.frame) {
1979
+ actionQueueEpochRef.current += 1;
1980
+ clearBufferedInput();
1981
+ }
1982
+ currentFrameRef.current = props.frame;
1983
+ if (!props.frame) {
1984
+ queuedFrameRef.current = null;
1985
+ paintedFrameRef.current = null;
1986
+ setPaintedFrame(null);
1987
+ return;
1988
+ }
1989
+ queuedFrameRef.current = props.frame;
1990
+ paintQueuedFrames();
1991
+ }, [clearBufferedInput, paintQueuedFrames, props.frame]);
1992
+ useLayoutEffect(() => {
1993
+ if (!rawInputEnabled || !props.pointerInput || !props.keyboardInput) {
1994
+ actionQueueEpochRef.current += 1;
1995
+ clearBufferedInput();
1996
+ }
1997
+ }, [clearBufferedInput, props.keyboardInput, props.pointerInput, rawInputEnabled]);
1998
+ const enqueue = useCallback5(
1999
+ (action, frame, after) => {
2000
+ const epoch = actionQueueEpochRef.current;
2001
+ const dispatch = actionRef.current;
2002
+ const isCurrent = () => mountedRef.current && epoch === actionQueueEpochRef.current;
2003
+ actionTailRef.current = actionTailRef.current.catch(() => void 0).then(async () => {
2004
+ if (!isCurrent()) return;
2005
+ await dispatch(action, frame);
2006
+ if (!isCurrent()) return;
2007
+ await after?.(isCurrent);
2008
+ }).catch((cause) => {
2009
+ if (!isCurrent()) return;
2010
+ actionQueueEpochRef.current += 1;
2011
+ clearBufferedInput();
2012
+ errorRef.current(cause);
2013
+ });
2014
+ },
2015
+ [clearBufferedInput]
2016
+ );
2017
+ const point = useCallback5(
2018
+ (frame, clientX, clientY) => computerPoint(canvasRef.current, frame, clientX, clientY),
2019
+ []
2020
+ );
2021
+ const flushPendingText = useCallback5(() => {
2022
+ const pending = pendingTextRef.current;
2023
+ if (!pending) return;
2024
+ clearTimeout(pending.timer);
2025
+ pendingTextRef.current = null;
2026
+ enqueue({ type: "keyboard", action: "type", value: pending.text }, null);
2027
+ }, [enqueue]);
2028
+ const flushPendingClick = useCallback5(() => {
2029
+ const pending = lastClickRef.current;
2030
+ if (!pending) return;
2031
+ if (clickTimerRef.current) clearTimeout(clickTimerRef.current);
2032
+ clickTimerRef.current = null;
2033
+ lastClickRef.current = null;
2034
+ enqueue(
2035
+ {
2036
+ type: "pointer",
2037
+ frameId: pending.frame.frameId,
2038
+ action: "click",
2039
+ x: pending.x,
2040
+ y: pending.y
2041
+ },
2042
+ pending.frame
2043
+ );
2044
+ }, [enqueue]);
2045
+ const flushPendingWheel = useCallback5(() => {
2046
+ const batch = wheelRef.current;
2047
+ if (!batch) return;
2048
+ if (batch.timer) clearTimeout(batch.timer);
2049
+ wheelRef.current = null;
2050
+ enqueue(
2051
+ {
2052
+ type: "pointer",
2053
+ frameId: batch.frame.frameId,
2054
+ action: "scroll",
2055
+ x: batch.x,
2056
+ y: batch.y,
2057
+ deltaX: batch.deltaX,
2058
+ deltaY: batch.deltaY
2059
+ },
2060
+ batch.frame
2061
+ );
2062
+ }, [enqueue]);
2063
+ const pointerDown = (event) => {
2064
+ const frame = paintedFrameRef.current;
2065
+ if (!frame || props.mutating || !rawInputEnabled || event.button !== 0) return;
2066
+ event.preventDefault();
2067
+ if (keyboardInputEnabled) inputRef.current?.focus({ preventScroll: true });
2068
+ if (!pointerInputEnabled) return;
2069
+ flushPendingWheel();
2070
+ flushPendingText();
2071
+ pointerStartRef.current = {
2072
+ x: event.clientX,
2073
+ y: event.clientY,
2074
+ pointerId: event.pointerId,
2075
+ frame
2076
+ };
2077
+ event.currentTarget.setPointerCapture?.(event.pointerId);
2078
+ };
2079
+ const pointerUp = (event) => {
2080
+ if (!pointerInputEnabled) return;
2081
+ const start = pointerStartRef.current;
2082
+ pointerStartRef.current = null;
2083
+ if (!start || start.pointerId !== event.pointerId) return;
2084
+ const from = point(start.frame, start.x, start.y);
2085
+ const to = point(start.frame, event.clientX, event.clientY);
2086
+ if (!from || !to) return;
2087
+ if (Math.hypot(event.clientX - start.x, event.clientY - start.y) > 5) {
2088
+ flushPendingClick();
2089
+ enqueue(
2090
+ {
2091
+ type: "pointer",
2092
+ frameId: start.frame.frameId,
2093
+ action: "drag",
2094
+ x: from.x,
2095
+ y: from.y,
2096
+ endX: to.x,
2097
+ endY: to.y
2098
+ },
2099
+ start.frame
2100
+ );
2101
+ return;
2102
+ }
2103
+ const now = Date.now();
2104
+ const previous = lastClickRef.current;
2105
+ if (previous && now - previous.at < 280 && Math.hypot(previous.x - to.x, previous.y - to.y) < 6 && sameFrameFence(previous.frame, start.frame)) {
2106
+ if (clickTimerRef.current) clearTimeout(clickTimerRef.current);
2107
+ clickTimerRef.current = null;
2108
+ lastClickRef.current = null;
2109
+ enqueue(
2110
+ {
2111
+ type: "pointer",
2112
+ frameId: start.frame.frameId,
2113
+ action: "double_click",
2114
+ x: to.x,
2115
+ y: to.y
2116
+ },
2117
+ start.frame
2118
+ );
2119
+ return;
2120
+ }
2121
+ lastClickRef.current = { at: now, x: to.x, y: to.y, frame: start.frame };
2122
+ clickTimerRef.current = setTimeout(() => {
2123
+ clickTimerRef.current = null;
2124
+ lastClickRef.current = null;
2125
+ enqueue(
2126
+ {
2127
+ type: "pointer",
2128
+ frameId: start.frame.frameId,
2129
+ action: "click",
2130
+ x: to.x,
2131
+ y: to.y
2132
+ },
2133
+ start.frame
2134
+ );
2135
+ }, 280);
2136
+ };
2137
+ const contextMenu = (event) => {
2138
+ event.preventDefault();
2139
+ if (!pointerInputEnabled) return;
2140
+ const frame = paintedFrameRef.current;
2141
+ if (!frame) return;
2142
+ flushPendingWheel();
2143
+ flushPendingText();
2144
+ flushPendingClick();
2145
+ const at = point(frame, event.clientX, event.clientY);
2146
+ if (at) {
2147
+ enqueue(
2148
+ {
2149
+ type: "pointer",
2150
+ frameId: frame.frameId,
2151
+ action: "click",
2152
+ x: at.x,
2153
+ y: at.y,
2154
+ button: "right"
2155
+ },
2156
+ frame
2157
+ );
2158
+ }
2159
+ };
2160
+ const wheel = (event) => {
2161
+ if (!pointerInputEnabled) return;
2162
+ const frame = paintedFrameRef.current;
2163
+ if (!frame) return;
2164
+ const at = point(frame, event.clientX, event.clientY);
2165
+ if (!at) return;
2166
+ flushPendingText();
2167
+ flushPendingClick();
2168
+ event.preventDefault();
2169
+ let pending = wheelRef.current;
2170
+ if (pending && (!sameComputerTarget(pending.frame, frame) || Math.hypot(pending.x - at.x, pending.y - at.y) > 6)) {
2171
+ flushPendingWheel();
2172
+ pending = null;
2173
+ }
2174
+ wheelRef.current = {
2175
+ x: at.x,
2176
+ y: at.y,
2177
+ deltaX: (pending?.deltaX ?? 0) + event.deltaX,
2178
+ deltaY: (pending?.deltaY ?? 0) + event.deltaY,
2179
+ frame,
2180
+ timer: pending?.timer ?? setTimeout(flushPendingWheel, 45)
2181
+ };
2182
+ };
2183
+ const keyDown = (event) => {
2184
+ if (!keyboardInputEnabled) return;
2185
+ if (composingRef.current || event.nativeEvent.isComposing || event.nativeEvent.keyCode === 229) {
2186
+ return;
2187
+ }
2188
+ flushPendingWheel();
2189
+ const command = event.metaKey || event.ctrlKey;
2190
+ if (props.clipboardEnabled && command && !event.altKey && ["c", "v"].includes(event.key.toLowerCase())) {
2191
+ flushPendingText();
2192
+ return;
2193
+ }
2194
+ const key = computerKey(event);
2195
+ if (!key) return;
2196
+ event.preventDefault();
2197
+ flushPendingText();
2198
+ flushPendingClick();
2199
+ enqueue({ type: "keyboard", action: "press", value: key }, null);
2200
+ };
2201
+ const copy = (event) => {
2202
+ if (!props.clipboardEnabled || !keyboardInputEnabled) return;
2203
+ event.preventDefault();
2204
+ flushPendingWheel();
2205
+ flushPendingText();
2206
+ flushPendingClick();
2207
+ const readClipboard = readClipboardRef.current;
2208
+ enqueue({ type: "clipboard", operation: "copy" }, null, async (isCurrent) => {
2209
+ const clipboard = await readClipboard();
2210
+ if (!isCurrent()) return;
2211
+ if (!clipboard.text) return;
2212
+ if (!await copyTextToClipboard(clipboard.text)) {
2213
+ throw new Error("Desktop text could not be copied to the local clipboard");
2214
+ }
2215
+ });
2216
+ };
2217
+ const paste = (event) => {
2218
+ if (!props.clipboardEnabled || !keyboardInputEnabled) return;
2219
+ event.preventDefault();
2220
+ flushPendingWheel();
2221
+ flushPendingText();
2222
+ flushPendingClick();
2223
+ const text = event.clipboardData.getData("text/plain");
2224
+ const readClipboard = readClipboardRef.current;
2225
+ const dispatch = actionRef.current;
2226
+ enqueue({ type: "clipboard", operation: "write", text }, null, async (isCurrent) => {
2227
+ const clipboard = await readClipboard();
2228
+ if (!isCurrent()) return;
2229
+ assertExactComputerClipboard(clipboard, text);
2230
+ await dispatch({ type: "clipboard", operation: "paste" }, null);
2231
+ });
2232
+ };
2233
+ const input = (value, nativeComposing = false) => {
2234
+ if (composingRef.current || nativeComposing) return;
2235
+ if (!value || !keyboardInputEnabled) return;
2236
+ flushPendingWheel();
2237
+ flushPendingClick();
2238
+ const pending = pendingTextRef.current;
2239
+ if (pending) {
2240
+ clearTimeout(pending.timer);
2241
+ pending.text += value;
2242
+ pending.timer = setTimeout(flushPendingText, 16);
2243
+ } else {
2244
+ pendingTextRef.current = {
2245
+ text: value,
2246
+ timer: setTimeout(flushPendingText, 16)
2247
+ };
2248
+ }
2249
+ if (inputRef.current) inputRef.current.value = "";
2250
+ };
2251
+ const compositionEnd = (event) => {
2252
+ composingRef.current = false;
2253
+ input(event.currentTarget.value || event.data);
2254
+ };
2255
+ const showCanvas = props.frame !== null && paintedFrame !== null && sameComputerTarget(props.frame, paintedFrame) && !props.machineLocked && !streamFailed;
2256
+ return /* @__PURE__ */ jsxs("div", { className: "relative min-h-0 flex-1 overflow-hidden bg-black", children: [
2257
+ /* @__PURE__ */ jsx(
2258
+ "canvas",
2259
+ {
2260
+ ref: canvasRef,
2261
+ className: cn(
2262
+ "absolute inset-0 m-auto max-h-full max-w-full touch-none focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-og-accent",
2263
+ !showCanvas && "invisible",
2264
+ !pointerInputEnabled && "cursor-default"
2265
+ ),
2266
+ onPointerDown: pointerDown,
2267
+ onPointerUp: pointerUp,
2268
+ onPointerCancel: () => {
2269
+ pointerStartRef.current = null;
2270
+ },
2271
+ onContextMenu: contextMenu,
2272
+ onWheel: wheel,
2273
+ "aria-label": `Interactive ${props.target?.kind ?? "desktop"} view`
2274
+ }
2275
+ ),
2276
+ /* @__PURE__ */ jsx(
2277
+ "textarea",
2278
+ {
2279
+ ref: inputRef,
2280
+ defaultValue: "",
2281
+ onInput: (event) => input(event.currentTarget.value, event.nativeEvent.isComposing),
2282
+ onCompositionStart: () => {
2283
+ composingRef.current = true;
2284
+ },
2285
+ onCompositionEnd: compositionEnd,
2286
+ onKeyDown: keyDown,
2287
+ onCopy: copy,
2288
+ onPaste: paste,
2289
+ className: "pointer-events-none absolute left-1/2 top-1/2 size-px resize-none overflow-hidden opacity-0",
2290
+ "aria-label": "Desktop keyboard input",
2291
+ autoCapitalize: "off",
2292
+ autoCorrect: "off",
2293
+ spellCheck: false,
2294
+ disabled: !keyboardInputEnabled
2295
+ }
2296
+ ),
2297
+ !showCanvas ? /* @__PURE__ */ jsx(
2298
+ ComputerViewportFallback,
2299
+ {
2300
+ observation: props.observation,
2301
+ machineLocked: props.machineLocked,
2302
+ controlUnavailable: props.controlUnavailable,
2303
+ inputAllowed: props.inputAllowed,
2304
+ connectionState: props.connectionState,
2305
+ error: props.connectionError,
2306
+ onAction: (action) => enqueue(action, null),
2307
+ onReconnect: props.onReconnect
2308
+ }
2309
+ ) : null,
2310
+ props.mutating ? /* @__PURE__ */ jsxs("div", { className: "pointer-events-none absolute right-3 top-3 flex items-center gap-1.5 rounded-full border border-white/10 bg-black/65 px-2.5 py-1 text-[11px] text-white/80 backdrop-blur", children: [
2311
+ /* @__PURE__ */ jsx(LoaderCircleIcon, { className: "size-3 animate-spin" }),
2312
+ " Acting"
2313
+ ] }) : null,
2314
+ showCanvas && (!rawInputEnabled || !props.pointerInput || !props.keyboardInput) ? /* @__PURE__ */ jsxs("div", { className: "absolute bottom-3 left-1/2 flex w-max max-w-[calc(100%-1.5rem)] -translate-x-1/2 items-center gap-2 rounded-full border border-white/10 bg-black/70 py-1 pl-3 pr-1 text-[11px] text-white/80 backdrop-blur", children: [
2315
+ /* @__PURE__ */ jsx("span", { children: !rawInputEnabled && (props.pointerInput || props.keyboardInput) ? "Background view \xB7 use app controls" : !props.pointerInput && !props.keyboardInput ? "View only \xB7 mouse and keyboard unavailable" : props.keyboardInput ? "Keyboard only \xB7 mouse unavailable" : "Mouse only \xB7 keyboard unavailable" }),
2316
+ !rawInputEnabled && props.target && (props.pointerInput || props.keyboardInput) ? /* @__PURE__ */ jsx(
2317
+ "button",
2318
+ {
2319
+ type: "button",
2320
+ disabled: props.mutating,
2321
+ onClick: () => enqueue({ type: "focus", targetId: props.target.id }, null),
2322
+ className: "shrink-0 whitespace-nowrap rounded-full bg-white/10 px-2 py-0.5 font-medium text-white transition hover:bg-white/20 disabled:opacity-50",
2323
+ children: "Bring to front"
2324
+ }
2325
+ ) : null
2326
+ ] }) : null
2327
+ ] });
2328
+ }
2329
+ function assertExactComputerClipboard(clipboard, expected) {
2330
+ if (clipboard.truncated || clipboard.text !== expected) {
2331
+ throw new Error("Desktop clipboard did not accept the exact pasted text");
2332
+ }
2333
+ }
2334
+ function ComputerViewportFallback(props) {
2335
+ if (props.machineLocked) {
2336
+ return /* @__PURE__ */ jsx("div", { className: "absolute inset-0 grid place-items-center bg-og-bg p-6", children: /* @__PURE__ */ jsxs("div", { className: "max-w-sm text-center", children: [
2337
+ /* @__PURE__ */ jsx(LockKeyholeIcon, { className: "mx-auto size-6 text-og-fg-muted" }),
2338
+ /* @__PURE__ */ jsx("p", { className: "mt-3 text-og-menu font-medium text-og-fg", children: "Machine locked" }),
2339
+ /* @__PURE__ */ jsx("p", { className: "mt-1 text-og-control leading-5 text-og-fg-muted", children: "Live frames and input stay private until someone unlocks the machine." })
2340
+ ] }) });
2341
+ }
2342
+ const appControls = props.observation?.target.kind === "app";
2343
+ const controlFailure = interactionControlFailureFromError(props.error);
2344
+ const interactive = semanticControls(props.observation).filter((node) => semanticAction(node) !== null).slice(0, 10);
2345
+ return /* @__PURE__ */ jsx("div", { className: "absolute inset-0 grid place-items-center bg-og-bg p-6", children: /* @__PURE__ */ jsxs("div", { className: "w-full max-w-md rounded-og-lg border border-og-border bg-og-surface-1 p-4 shadow-lg", children: [
2346
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
2347
+ props.error ? /* @__PURE__ */ jsx(CircleAlertIcon, { className: "size-4 text-og-danger" }) : appControls ? /* @__PURE__ */ jsx(PanelsTopLeftIcon, { className: "size-4 text-og-fg-muted" }) : /* @__PURE__ */ jsx(LoaderCircleIcon, { className: "size-4 animate-spin text-og-fg-muted" }),
2348
+ /* @__PURE__ */ jsx("p", { className: "text-og-menu font-medium text-og-fg", children: isAttachedChromeGenerationLossError(props.error) ? "Chrome reconnected\u2014open a fresh browser/desktop." : !props.inputAllowed ? "View only" : props.controlUnavailable ? "Desktop controls unavailable" : props.error ? "Live view disconnected" : appControls ? "App controls" : computerConnectionLabel(props.connectionState) })
2349
+ ] }),
2350
+ props.error ? /* @__PURE__ */ jsx("p", { className: "mt-2 text-og-control leading-5 text-og-fg-muted", children: isAttachedChromeGenerationLossError(props.error) ? "Chrome reconnected\u2014open a fresh browser/desktop. Use Browser \u2192 New browser \u2192 Connected Chrome." : controlFailure?.message ?? props.error.message }) : null,
2351
+ !props.inputAllowed ? /* @__PURE__ */ jsx("p", { className: "mt-2 text-og-control leading-5 text-og-fg-muted", children: "Refresh to check desktop controls." }) : null,
2352
+ interactive.length > 0 && !props.controlUnavailable && props.inputAllowed ? /* @__PURE__ */ jsxs("div", { className: "mt-3 border-t border-og-border pt-3", children: [
2353
+ /* @__PURE__ */ jsx("p", { className: "mb-2 text-og-xs text-og-fg-subtle", children: "App controls remain available" }),
2354
+ /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-1.5", children: interactive.map((node) => /* @__PURE__ */ jsx(
2355
+ "button",
2356
+ {
2357
+ type: "button",
2358
+ onClick: () => {
2359
+ const action = semanticAction(node);
2360
+ if (action) props.onAction(action);
2361
+ },
2362
+ className: "rounded-og-sm border border-og-border bg-og-surface-1 px-2 py-1 text-og-control text-og-fg transition hover:bg-og-surface-2",
2363
+ children: node.name || node.role
2364
+ },
2365
+ node.ref
2366
+ )) })
2367
+ ] }) : null,
2368
+ props.error && !isAttachedChromeGenerationLossError(props.error) ? /* @__PURE__ */ jsx(
2369
+ "button",
2370
+ {
2371
+ type: "button",
2372
+ onClick: props.onReconnect,
2373
+ className: "mt-3 text-og-control font-medium text-og-accent hover:underline",
2374
+ children: controlFailure?.retryable === false ? "Try again" : "Reconnect"
2375
+ }
2376
+ ) : null
2377
+ ] }) });
2378
+ }
2379
+ function ComputerSemanticPanel(props) {
2380
+ const nodes = semanticControls(props.observation).slice(0, 100);
2381
+ return /* @__PURE__ */ jsxs("aside", { className: "box-border max-h-[40%] w-full shrink-0 overflow-y-auto border-t border-og-border bg-og-surface-1 p-2 @xl/computer-viewer:max-h-none @xl/computer-viewer:w-64 @xl/computer-viewer:border-t-0 @xl/computer-viewer:border-l", children: [
2382
+ /* @__PURE__ */ jsxs("div", { className: "mb-2 flex items-center gap-1.5 px-1 text-og-xs font-medium uppercase tracking-[0.1em] text-og-fg-subtle", children: [
2383
+ /* @__PURE__ */ jsx(KeyboardIcon, { className: "size-3" }),
2384
+ " App controls"
2385
+ ] }),
2386
+ !props.inputAllowed ? /* @__PURE__ */ jsx("p", { className: "px-1 py-2 text-og-control leading-5 text-og-fg-muted", children: "View only \xB7 refresh to check desktop controls." }) : props.controlUnavailable ? /* @__PURE__ */ jsx("p", { className: "px-1 py-2 text-og-control leading-5 text-og-fg-muted", children: "Reconnect to use app controls." }) : null,
2387
+ nodes.length === 0 ? props.controlUnavailable ? null : /* @__PURE__ */ jsx("p", { className: "px-1 py-2 text-og-control leading-5 text-og-fg-muted", children: "This view has no accessibility controls. Use the live image instead." }) : /* @__PURE__ */ jsx("div", { className: "space-y-0.5", children: nodes.map((node) => /* @__PURE__ */ jsx(
2388
+ ComputerSemanticControl,
2389
+ {
2390
+ node,
2391
+ mutating: props.mutating,
2392
+ onAction: props.onAction
2393
+ },
2394
+ node.ref
2395
+ )) })
2396
+ ] });
2397
+ }
2398
+ function ComputerSemanticControl(props) {
2399
+ const { node } = props;
2400
+ const editable = node.actions.includes("set_value");
2401
+ const action = semanticAction(node);
2402
+ const observedValue = semanticValue(node) ?? "";
2403
+ const [value, setValue] = useState5(() => observedValue);
2404
+ useEffect5(() => {
2405
+ setValue(observedValue);
2406
+ }, [node.ref, observedValue]);
2407
+ if (editable) {
2408
+ return /* @__PURE__ */ jsxs(
2409
+ "form",
2410
+ {
2411
+ className: "rounded-og-sm px-2 py-1.5 hover:bg-og-surface-2",
2412
+ onSubmit: (event) => {
2413
+ event.preventDefault();
2414
+ if (props.mutating) return;
2415
+ props.onAction({
2416
+ type: "semantic",
2417
+ locator: { kind: "ref", ref: node.ref },
2418
+ action: "set_value",
2419
+ value
2420
+ });
2421
+ },
2422
+ children: [
2423
+ /* @__PURE__ */ jsx("label", { className: "block truncate text-[10px] uppercase text-og-fg-subtle", children: node.name || node.identifier || node.role }),
2424
+ /* @__PURE__ */ jsxs("div", { className: "mt-1 flex gap-1", children: [
2425
+ /* @__PURE__ */ jsx(
2426
+ "input",
2427
+ {
2428
+ value,
2429
+ disabled: props.mutating,
2430
+ onChange: (event) => setValue(event.currentTarget.value),
2431
+ className: "min-w-0 flex-1 rounded-og-sm border border-og-border bg-og-surface-1 px-1.5 py-1 text-og-control text-og-fg outline-hidden focus:border-og-accent",
2432
+ "aria-label": `Set ${node.name || node.identifier || node.role}`
2433
+ }
2434
+ ),
2435
+ /* @__PURE__ */ jsx(
2436
+ "button",
2437
+ {
2438
+ type: "submit",
2439
+ disabled: props.mutating,
2440
+ className: "rounded-og-sm border border-og-border bg-og-surface-1 px-2 text-og-control font-medium text-og-fg transition hover:bg-og-surface-2 disabled:opacity-50",
2441
+ children: "Set"
2442
+ }
2443
+ )
2444
+ ] })
2445
+ ]
2446
+ }
2447
+ );
2448
+ }
2449
+ return /* @__PURE__ */ jsxs(
2450
+ "button",
2451
+ {
2452
+ type: "button",
2453
+ disabled: !action || props.mutating,
2454
+ onClick: () => {
2455
+ if (action) props.onAction(action);
2456
+ },
2457
+ className: "flex w-full items-start gap-2 rounded-og-sm px-2 py-1.5 text-left transition hover:bg-og-surface-2 disabled:cursor-default disabled:hover:bg-transparent",
2458
+ children: [
2459
+ /* @__PURE__ */ jsx("span", { className: "mt-0.5 text-[10px] uppercase text-og-fg-subtle", children: node.role }),
2460
+ /* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1 truncate text-og-control text-og-fg", children: node.name || semanticValue(node) || node.identifier || "Unnamed control" })
2461
+ ]
2462
+ }
2463
+ );
2464
+ }
2465
+ function ComputerStatusBar(props) {
2466
+ const screen = props.target?.kind === "screen";
2467
+ return /* @__PURE__ */ jsxs("div", { className: "flex h-8 shrink-0 items-center gap-2 border-t border-og-border bg-og-surface-1 px-2 text-og-xs text-og-fg-subtle", children: [
2468
+ /* @__PURE__ */ jsx(
2469
+ "span",
2470
+ {
2471
+ className: cn(
2472
+ "size-1.5 rounded-full",
2473
+ props.connectionState === "live" ? "bg-og-status-running" : "bg-og-muted"
2474
+ )
2475
+ }
2476
+ ),
2477
+ /* @__PURE__ */ jsx("span", { children: !props.inputAllowed ? "View only" : props.controlUnavailable ? "Controls unavailable" : props.target?.kind === "app" ? "App controls" : props.connectionState === "live" ? "Live" : computerConnectionLabel(props.connectionState) }),
2478
+ /* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1 truncate", children: !props.inputAllowed ? "Refresh to check desktop controls" : props.controlUnavailable ? "Reconnect to use desktop input" : screen ? "Full screen \xB7 input may move pointer and focus" : props.target?.kind === "app" ? props.session?.capabilities?.backgroundActions ? "App \xB7 controls work in the background" : "App controls" : props.session?.capabilities?.backgroundInput ? "Window \xB7 clicks and typing stay in the background" : props.session?.capabilities?.backgroundActions ? "Window \xB7 app controls work in the background" : props.target?.kind ?? "Desktop" }),
2479
+ screen ? /* @__PURE__ */ jsx(MousePointer2Icon, { className: "size-3", "aria-hidden": true }) : null,
2480
+ props.refreshing ? /* @__PURE__ */ jsx(LoaderCircleIcon, { className: "size-3 animate-spin" }) : null,
2481
+ props.controlCount > 0 || props.showControls ? /* @__PURE__ */ jsxs(
2482
+ "button",
2483
+ {
2484
+ type: "button",
2485
+ onClick: props.onToggleControls,
2486
+ "aria-pressed": props.showControls,
2487
+ className: cn(
2488
+ "rounded px-1.5 py-0.5 transition hover:bg-og-surface-2 hover:text-og-fg",
2489
+ props.showControls && "bg-og-surface-2 text-og-fg"
2490
+ ),
2491
+ children: [
2492
+ "Controls ",
2493
+ props.controlCount
2494
+ ]
2495
+ }
2496
+ ) : null
2497
+ ] });
2498
+ }
2499
+ function ComputerNotice(props) {
2500
+ return /* @__PURE__ */ jsx(
2501
+ "div",
2502
+ {
2503
+ className: cn(
2504
+ "grid h-full place-items-center bg-og-bg text-og-control text-og-fg-muted",
2505
+ props.className
2506
+ ),
2507
+ children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
2508
+ props.icon,
2509
+ props.text
2510
+ ] })
2511
+ }
2512
+ );
2513
+ }
2514
+ var structuralRoles = /* @__PURE__ */ new Set([
2515
+ "frame",
2516
+ "panel",
2517
+ "section",
2518
+ "document",
2519
+ "document web",
2520
+ "static",
2521
+ "static text",
2522
+ "heading",
2523
+ "paragraph",
2524
+ "notification",
2525
+ "separator",
2526
+ "tool bar",
2527
+ "page tab list"
2528
+ ]);
2529
+ function semanticControls(observation) {
2530
+ if (observation?.semantic?.kind !== "snapshot") return [];
2531
+ const result = [];
2532
+ const visit = (node) => {
2533
+ const role = node.role.trim().toLowerCase();
2534
+ const label = node.name?.trim() || node.identifier?.trim();
2535
+ if (node.actions.includes("set_value") || label && label.toLowerCase() !== role && !structuralRoles.has(role) && semanticAction(node) !== null) {
2536
+ result.push(node);
2537
+ }
2538
+ node.children?.forEach(visit);
2539
+ };
2540
+ observation.semantic.roots.forEach(visit);
2541
+ return result;
2542
+ }
2543
+ function semanticAction(node) {
2544
+ const invoke = ["invoke", "press", "click"].some((action) => node.actions.includes(action));
2545
+ if (invoke) {
2546
+ return {
2547
+ type: "semantic",
2548
+ locator: { kind: "ref", ref: node.ref },
2549
+ action: "invoke"
2550
+ };
2551
+ }
2552
+ return null;
2553
+ }
2554
+ function semanticValue(node) {
2555
+ return typeof node.value === "string" ? node.value : null;
2556
+ }
2557
+ function paintCanvas(canvas, image, width, height) {
2558
+ const dimensionsChanged = canvas.width !== width || canvas.height !== height;
2559
+ canvas.width = width;
2560
+ canvas.height = height;
2561
+ const context = canvas.getContext("2d", { alpha: false });
2562
+ if (!context) throw new Error("Desktop canvas is unavailable.");
2563
+ context.drawImage(image, 0, 0, width, height);
2564
+ if (dimensionsChanged) fitComputerCanvas(canvas);
2565
+ }
2566
+ function fitComputerCanvas(canvas) {
2567
+ const viewport = canvas.parentElement;
2568
+ if (!viewport || canvas.width <= 0 || canvas.height <= 0) return;
2569
+ const scale = Math.min(
2570
+ viewport.clientWidth / canvas.width,
2571
+ viewport.clientHeight / canvas.height
2572
+ );
2573
+ if (!Number.isFinite(scale) || scale <= 0) return;
2574
+ canvas.style.width = `${canvas.width * scale}px`;
2575
+ canvas.style.height = `${canvas.height * scale}px`;
2576
+ }
2577
+ function loadImage(url) {
2578
+ return new Promise((resolve, reject) => {
2579
+ const image = new Image();
2580
+ image.onload = () => resolve(image);
2581
+ image.onerror = () => reject(new Error("Desktop frame image could not be decoded."));
2582
+ image.src = url;
2583
+ });
2584
+ }
2585
+ function computerPoint(canvas, frame, clientX, clientY) {
2586
+ if (!canvas) return null;
2587
+ const rect = canvas.getBoundingClientRect();
2588
+ if (rect.width <= 0 || rect.height <= 0) return null;
2589
+ return {
2590
+ x: Math.max(0, Math.min(frame.width - 1, (clientX - rect.left) / rect.width * frame.width)),
2591
+ y: Math.max(0, Math.min(frame.height - 1, (clientY - rect.top) / rect.height * frame.height))
2592
+ };
2593
+ }
2594
+ function computerKey(event) {
2595
+ if (["Alt", "AltGraph", "Control", "Meta", "Shift"].includes(event.key)) return null;
2596
+ const special = /* @__PURE__ */ new Set([
2597
+ "Enter",
2598
+ "Tab",
2599
+ "Escape",
2600
+ "Backspace",
2601
+ "Delete",
2602
+ "ArrowUp",
2603
+ "ArrowDown",
2604
+ "ArrowLeft",
2605
+ "ArrowRight",
2606
+ "Home",
2607
+ "End",
2608
+ "PageUp",
2609
+ "PageDown"
2610
+ ]);
2611
+ const modified = event.altKey || event.ctrlKey || event.metaKey;
2612
+ if (!modified && !special.has(event.key)) return null;
2613
+ const parts = [];
2614
+ if (event.ctrlKey) parts.push("Control");
2615
+ if (event.altKey) parts.push("Alt");
2616
+ if (event.metaKey) parts.push("Meta");
2617
+ if (event.shiftKey) parts.push("Shift");
2618
+ parts.push(event.key === " " ? "Space" : event.key);
2619
+ return parts.join("+");
2620
+ }
2621
+ function sameFrameFence(left, right) {
2622
+ return left.frameId === right.frameId && sameComputerTarget(left, right);
2623
+ }
2624
+ function sameComputerTarget(left, right) {
2625
+ return left.computerSessionId === right.computerSessionId && left.controllerGeneration === right.controllerGeneration && left.targetId === right.targetId && left.targetGeneration === right.targetGeneration;
2626
+ }
2627
+ function isLiveComputer2(session) {
2628
+ return !["ending", "ended", "failed", "lost"].includes(session.lifecycle);
2629
+ }
2630
+ function computerFailureMessage(session) {
2631
+ switch (session.failureCode) {
2632
+ case "machine_locked":
2633
+ return "Unlock the connected Mac, then try again.";
2634
+ case "controller_heartbeat_expired":
2635
+ return "The desktop connection was lost. Try opening it again.";
2636
+ case "controller_transition_expired":
2637
+ return session.placement.kind === "attached_device" ? "Chrome reconnected\u2014open a fresh browser/desktop. Use Browser \u2192 New browser \u2192 Connected Chrome." : "The desktop connection expired. Open a new desktop.";
2638
+ case null:
2639
+ return null;
2640
+ default:
2641
+ return "The desktop could not be opened. Try again.";
2642
+ }
2643
+ }
2644
+ function isAttachedChromeGenerationLoss(session) {
2645
+ return session.lifecycle === "lost" && session.failureCode === "controller_transition_expired" && session.placement.kind === "attached_device";
2646
+ }
2647
+ function isAttachedChromeGenerationLossError(error) {
2648
+ if (!error) return false;
2649
+ return /placement instance changed|controller authority changed/i.test(error.message);
2650
+ }
2651
+ function countInterventions(interventions) {
2652
+ const counts = /* @__PURE__ */ new Map();
2653
+ for (const intervention of interventions) {
2654
+ counts.set(intervention.resourceId, (counts.get(intervention.resourceId) ?? 0) + 1);
2655
+ }
2656
+ return counts;
2657
+ }
2658
+ function interventionTitle(intervention) {
2659
+ switch (intervention.kind) {
2660
+ case "manual_login":
2661
+ return "Sign in needed";
2662
+ case "mfa":
2663
+ return "Verification needed";
2664
+ case "external_action":
2665
+ case "confirmation":
2666
+ return "Action needed";
2667
+ case "other":
2668
+ return "Desktop needs your help";
2669
+ }
2670
+ }
2671
+ function platformLabel(session) {
2672
+ switch (session.platform) {
2673
+ case "macos":
2674
+ return "Mac";
2675
+ case "linux":
2676
+ return "Linux";
2677
+ case "windows":
2678
+ return "Windows";
2679
+ default:
2680
+ return "Desktop";
2681
+ }
2682
+ }
2683
+ function placementLabel(session) {
2684
+ switch (session.placement.kind) {
2685
+ case "sandbox_group":
2686
+ return "Agent sandbox";
2687
+ case "connected_machine":
2688
+ return "Connected machine";
2689
+ case "attached_device":
2690
+ return "Your machine";
2691
+ case "external_provider":
2692
+ return "Cloud desktop";
2693
+ }
2694
+ }
2695
+ function lifecycleLabel(lifecycle) {
2696
+ switch (lifecycle) {
2697
+ case "starting":
2698
+ return "Opening desktop\u2026";
2699
+ case "suspending":
2700
+ return "Pausing desktop\u2026";
2701
+ case "suspended":
2702
+ return "Desktop paused";
2703
+ case "restoring":
2704
+ return "Restoring desktop\u2026";
2705
+ case "ending":
2706
+ return "Closing desktop\u2026";
2707
+ default:
2708
+ return "Connecting to desktop\u2026";
2709
+ }
2710
+ }
2711
+ function computerConnectionLabel(state) {
2712
+ switch (state) {
2713
+ case "attaching":
2714
+ return "Opening desktop view\u2026";
2715
+ case "connecting":
2716
+ return "Connecting\u2026";
2717
+ case "reconnecting":
2718
+ return "Reconnecting\u2026";
2719
+ case "error":
2720
+ return "Disconnected";
2721
+ default:
2722
+ return "Waiting for desktop\u2026";
2723
+ }
2724
+ }
2725
+
2726
+ export {
2727
+ useComputerSessions,
2728
+ useComputerSession,
2729
+ useComputerFrameStream,
2730
+ ComputerViewer,
2731
+ computerKey
2732
+ };
2733
+ //# sourceMappingURL=chunk-GYVRFZDY.js.map