@opengeni/react 1.4.0 → 1.4.4

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 (218) hide show
  1. package/README.md +8 -0
  2. package/dist/accounts.js +2 -0
  3. package/dist/accounts.js.map +1 -1
  4. package/dist/artifacts-document.js +5 -4
  5. package/dist/artifacts-presentation.js +5 -4
  6. package/dist/artifacts-spreadsheet.js +4 -3
  7. package/dist/artifacts.js +14 -13
  8. package/dist/artifacts.js.map +1 -1
  9. package/dist/browser-viewer-5EEAS3IU.js +17 -0
  10. package/dist/chat-interactive-block-ZFHOKTMS.js +12 -0
  11. package/dist/{chunk-WEHMPOXK.js → chunk-2DDJ6YH6.js} +3 -1
  12. package/dist/{chunk-WEHMPOXK.js.map → chunk-2DDJ6YH6.js.map} +1 -1
  13. package/dist/{chunk-C5X5AJXK.js → chunk-2HVLQ26H.js} +5 -4
  14. package/dist/{chunk-C5X5AJXK.js.map → chunk-2HVLQ26H.js.map} +1 -1
  15. package/dist/{chunk-HDJWDK7W.js → chunk-2OQA3CPB.js} +300 -166
  16. package/dist/{chunk-HDJWDK7W.js.map → chunk-2OQA3CPB.js.map} +1 -1
  17. package/dist/{chunk-A4UJB2MM.js → chunk-32CGLXW2.js} +4 -3
  18. package/dist/{chunk-A4UJB2MM.js.map → chunk-32CGLXW2.js.map} +1 -1
  19. package/dist/{chunk-5CVXZA3Y.js → chunk-45NUOSXQ.js} +3 -2
  20. package/dist/{chunk-5CVXZA3Y.js.map → chunk-45NUOSXQ.js.map} +1 -1
  21. package/dist/{chunk-WUISZ3WU.js → chunk-53KJBGPZ.js} +5 -4
  22. package/dist/{chunk-WUISZ3WU.js.map → chunk-53KJBGPZ.js.map} +1 -1
  23. package/dist/{chunk-MBMPHOMA.js → chunk-55DXD22O.js} +3 -2
  24. package/dist/{chunk-MBMPHOMA.js.map → chunk-55DXD22O.js.map} +1 -1
  25. package/dist/{chunk-JT4JW7BC.js → chunk-5I4VRWBB.js} +3 -1
  26. package/dist/{chunk-JT4JW7BC.js.map → chunk-5I4VRWBB.js.map} +1 -1
  27. package/dist/{chunk-UK4TINKT.js → chunk-B27E3V37.js} +3 -1
  28. package/dist/{chunk-UK4TINKT.js.map → chunk-B27E3V37.js.map} +1 -1
  29. package/dist/{chunk-J6DBS4T3.js → chunk-BNKV5ZLF.js} +5 -4
  30. package/dist/{chunk-J6DBS4T3.js.map → chunk-BNKV5ZLF.js.map} +1 -1
  31. package/dist/{chunk-YM2DE7BQ.js → chunk-C5BF4L26.js} +13 -12
  32. package/dist/{chunk-YM2DE7BQ.js.map → chunk-C5BF4L26.js.map} +1 -1
  33. package/dist/{chunk-YBXQDQFX.js → chunk-C7CMD56T.js} +6 -5
  34. package/dist/{chunk-YBXQDQFX.js.map → chunk-C7CMD56T.js.map} +1 -1
  35. package/dist/{chunk-UGLEYEMP.js → chunk-CM7LVUKC.js} +3 -1
  36. package/dist/{chunk-UGLEYEMP.js.map → chunk-CM7LVUKC.js.map} +1 -1
  37. package/dist/{chunk-JVYQRRC4.js → chunk-CVYCLFMH.js} +3 -1
  38. package/dist/{chunk-JVYQRRC4.js.map → chunk-CVYCLFMH.js.map} +1 -1
  39. package/dist/{chunk-LJ2UTJJE.js → chunk-EAOSEIXB.js} +182 -97
  40. package/dist/chunk-EAOSEIXB.js.map +1 -0
  41. package/dist/{chunk-Y2PRMYLC.js → chunk-G3U2A6GS.js} +3 -2
  42. package/dist/{chunk-Y2PRMYLC.js.map → chunk-G3U2A6GS.js.map} +1 -1
  43. package/dist/{chunk-WZMYFJD3.js → chunk-G6YRCIYJ.js} +16 -7
  44. package/dist/{chunk-WZMYFJD3.js.map → chunk-G6YRCIYJ.js.map} +1 -1
  45. package/dist/{chunk-B3V6TG77.js → chunk-GC3PH6N7.js} +3 -1
  46. package/dist/{chunk-B3V6TG77.js.map → chunk-GC3PH6N7.js.map} +1 -1
  47. package/dist/{chunk-CJUO7J5Z.js → chunk-HXYOSSQO.js} +3 -2
  48. package/dist/{chunk-CJUO7J5Z.js.map → chunk-HXYOSSQO.js.map} +1 -1
  49. package/dist/{chunk-GYVRFZDY.js → chunk-I6OY3MWC.js} +310 -115
  50. package/dist/chunk-I6OY3MWC.js.map +1 -0
  51. package/dist/{chunk-BRM6FVZA.js → chunk-IEE7WCLN.js} +4 -3
  52. package/dist/{chunk-BRM6FVZA.js.map → chunk-IEE7WCLN.js.map} +1 -1
  53. package/dist/{chunk-DYWBUX5Q.js → chunk-IMSUVYQB.js} +6 -5
  54. package/dist/{chunk-DYWBUX5Q.js.map → chunk-IMSUVYQB.js.map} +1 -1
  55. package/dist/{chunk-LN6TCXD3.js → chunk-IUHHPMAX.js} +184 -48
  56. package/dist/chunk-IUHHPMAX.js.map +1 -0
  57. package/dist/{chunk-SGKD2OCN.js → chunk-IYQCWGU5.js} +6 -5
  58. package/dist/{chunk-SGKD2OCN.js.map → chunk-IYQCWGU5.js.map} +1 -1
  59. package/dist/{chunk-J2QWVSQY.js → chunk-JH7V627O.js} +8 -7
  60. package/dist/{chunk-J2QWVSQY.js.map → chunk-JH7V627O.js.map} +1 -1
  61. package/dist/{chunk-EESD3PY3.js → chunk-JHTIMBOL.js} +6 -5
  62. package/dist/{chunk-EESD3PY3.js.map → chunk-JHTIMBOL.js.map} +1 -1
  63. package/dist/{chunk-LNIRAOA3.js → chunk-L2J3PU7A.js} +7 -6
  64. package/dist/{chunk-LNIRAOA3.js.map → chunk-L2J3PU7A.js.map} +1 -1
  65. package/dist/{chunk-ZYRGEKWS.js → chunk-LJUMYUBO.js} +3 -1
  66. package/dist/{chunk-ZYRGEKWS.js.map → chunk-LJUMYUBO.js.map} +1 -1
  67. package/dist/{chunk-4RVYOY4O.js → chunk-LSW2OJVV.js} +4 -3
  68. package/dist/{chunk-4RVYOY4O.js.map → chunk-LSW2OJVV.js.map} +1 -1
  69. package/dist/{chunk-UTRTG6LH.js → chunk-LVS3MH5T.js} +3 -1
  70. package/dist/{chunk-UTRTG6LH.js.map → chunk-LVS3MH5T.js.map} +1 -1
  71. package/dist/chunk-M357MHH3.js +2 -0
  72. package/dist/{chunk-WRHIZYZF.js → chunk-MEQM4LI3.js} +6 -5
  73. package/dist/{chunk-WRHIZYZF.js.map → chunk-MEQM4LI3.js.map} +1 -1
  74. package/dist/chunk-NX4ADH73.js +14 -0
  75. package/dist/chunk-NX4ADH73.js.map +1 -0
  76. package/dist/{chunk-XCHWDJ22.js → chunk-OGTTUYNT.js} +3 -1
  77. package/dist/{chunk-XCHWDJ22.js.map → chunk-OGTTUYNT.js.map} +1 -1
  78. package/dist/{chunk-GIMRELBW.js → chunk-OYQQJL3J.js} +3 -1
  79. package/dist/{chunk-GIMRELBW.js.map → chunk-OYQQJL3J.js.map} +1 -1
  80. package/dist/{chunk-RSI7XYXL.js → chunk-PBCZL4JP.js} +6 -5
  81. package/dist/{chunk-RSI7XYXL.js.map → chunk-PBCZL4JP.js.map} +1 -1
  82. package/dist/{chunk-I7IWRQLN.js → chunk-PHBFJCEU.js} +17 -11
  83. package/dist/chunk-PHBFJCEU.js.map +1 -0
  84. package/dist/{chunk-3CTRFMAK.js → chunk-PKCAYHDC.js} +5 -4
  85. package/dist/{chunk-3CTRFMAK.js.map → chunk-PKCAYHDC.js.map} +1 -1
  86. package/dist/{chunk-K3HQAKQX.js → chunk-PMV53IMM.js} +3 -1
  87. package/dist/{chunk-K3HQAKQX.js.map → chunk-PMV53IMM.js.map} +1 -1
  88. package/dist/{chunk-YQEAA6LT.js → chunk-QHUE7E4N.js} +3 -2
  89. package/dist/{chunk-YQEAA6LT.js.map → chunk-QHUE7E4N.js.map} +1 -1
  90. package/dist/{chunk-OA6HFRQP.js → chunk-QMMCJGFI.js} +4 -3
  91. package/dist/{chunk-OA6HFRQP.js.map → chunk-QMMCJGFI.js.map} +1 -1
  92. package/dist/{chunk-YV4L3ILF.js → chunk-QV3DJVRZ.js} +3 -1
  93. package/dist/{chunk-YV4L3ILF.js.map → chunk-QV3DJVRZ.js.map} +1 -1
  94. package/dist/{chunk-447JLCBU.js → chunk-S2QH6EZ5.js} +3 -1
  95. package/dist/{chunk-447JLCBU.js.map → chunk-S2QH6EZ5.js.map} +1 -1
  96. package/dist/{chunk-PNY3JKBJ.js → chunk-SFH43JT2.js} +5 -4
  97. package/dist/{chunk-PNY3JKBJ.js.map → chunk-SFH43JT2.js.map} +1 -1
  98. package/dist/{chunk-AWZLIMKX.js → chunk-T46QZH3E.js} +3 -1
  99. package/dist/{chunk-AWZLIMKX.js.map → chunk-T46QZH3E.js.map} +1 -1
  100. package/dist/{chunk-5J4FOW6Q.js → chunk-TNEZNMSN.js} +3 -1
  101. package/dist/{chunk-5J4FOW6Q.js.map → chunk-TNEZNMSN.js.map} +1 -1
  102. package/dist/{chunk-22KP6LR5.js → chunk-TXS65BB2.js} +3 -1
  103. package/dist/{chunk-22KP6LR5.js.map → chunk-TXS65BB2.js.map} +1 -1
  104. package/dist/{chunk-H5X55XH7.js → chunk-UDPH5SLV.js} +4 -3
  105. package/dist/{chunk-H5X55XH7.js.map → chunk-UDPH5SLV.js.map} +1 -1
  106. package/dist/{chunk-VZTQ73XT.js → chunk-XEA5BCMA.js} +3 -1
  107. package/dist/{chunk-VZTQ73XT.js.map → chunk-XEA5BCMA.js.map} +1 -1
  108. package/dist/{chunk-5F4WR7X7.js → chunk-XS67YG5R.js} +3 -1
  109. package/dist/{chunk-5F4WR7X7.js.map → chunk-XS67YG5R.js.map} +1 -1
  110. package/dist/{chunk-TKOTKRWY.js → chunk-XTD7TZHW.js} +3 -1
  111. package/dist/{chunk-TKOTKRWY.js.map → chunk-XTD7TZHW.js.map} +1 -1
  112. package/dist/{chunk-A2PI6UMY.js → chunk-YSRBFJMQ.js} +3 -1
  113. package/dist/{chunk-A2PI6UMY.js.map → chunk-YSRBFJMQ.js.map} +1 -1
  114. package/dist/chunk-YVFRBXST.js +1420 -0
  115. package/dist/chunk-YVFRBXST.js.map +1 -0
  116. package/dist/{chunk-SCOTPE2I.js → chunk-YYLBXXTE.js} +3 -1
  117. package/dist/{chunk-SCOTPE2I.js.map → chunk-YYLBXXTE.js.map} +1 -1
  118. package/dist/{chunk-RHREZ7BO.js → chunk-ZUWF4NF7.js} +3 -1
  119. package/dist/{chunk-RHREZ7BO.js.map → chunk-ZUWF4NF7.js.map} +1 -1
  120. package/dist/clipboard.js +3 -2
  121. package/dist/components/composer-transcription-control.d.ts +4 -0
  122. package/dist/components/session-conversation.d.ts +6 -0
  123. package/dist/composer-voice-events.d.ts +8 -0
  124. package/dist/composer.js +15 -13
  125. package/dist/computer-viewer-ELNLZGHI.js +20 -0
  126. package/dist/connect.js +15 -12
  127. package/dist/connect.js.map +1 -1
  128. package/dist/desktop.js +6 -5
  129. package/dist/desktop.js.map +1 -1
  130. package/dist/diffs.js +2 -1
  131. package/dist/diffs.js.map +1 -1
  132. package/dist/editor.js +4 -3
  133. package/dist/editor.js.map +1 -1
  134. package/dist/embedded-voice-NIL4RDX3.js +69 -0
  135. package/dist/embedded-voice-NIL4RDX3.js.map +1 -0
  136. package/dist/{fleet-decision-row-YZ3ZKWMM.js → fleet-decision-row-SI63O5V4.js} +3 -1
  137. package/dist/{fleet-decision-row-YZ3ZKWMM.js.map → fleet-decision-row-SI63O5V4.js.map} +1 -1
  138. package/dist/hooks/use-client-config-flags.d.ts +6 -0
  139. package/dist/hooks/use-computer-session.d.ts +2 -0
  140. package/dist/hooks/use-realtime-voice-models.d.ts +9 -0
  141. package/dist/hooks/use-voice-input.d.ts +8 -1
  142. package/dist/index.js +47 -45
  143. package/dist/index.js.map +1 -1
  144. package/dist/interaction.js +14 -13
  145. package/dist/interaction.js.map +1 -1
  146. package/dist/{latest-question-GPYM2JNU.js → latest-question-2POFPYNB.js} +5 -4
  147. package/dist/{latest-question-GPYM2JNU.js.map → latest-question-2POFPYNB.js.map} +1 -1
  148. package/dist/machines.js +8 -7
  149. package/dist/{markdown-table-layout-QXSLK4BY.js → markdown-table-layout-DQ4DRNVT.js} +3 -1
  150. package/dist/{markdown-table-layout-QXSLK4BY.js.map → markdown-table-layout-DQ4DRNVT.js.map} +1 -1
  151. package/dist/model-policy-picker-menu-WRNSRFQC.js +15 -0
  152. package/dist/model-policy.js +2 -1
  153. package/dist/{noto-sans-arabic-loader-IA2T4X2A.js → noto-sans-arabic-loader-XSH2KYWJ.js} +3 -1
  154. package/dist/{noto-sans-arabic-loader-IA2T4X2A.js.map → noto-sans-arabic-loader-XSH2KYWJ.js.map} +1 -1
  155. package/dist/{noto-sans-jp-loader.generated-HFGLYBR3.js → noto-sans-jp-loader.generated-MR5L6V7K.js} +3 -1
  156. package/dist/{noto-sans-jp-loader.generated-HFGLYBR3.js.map → noto-sans-jp-loader.generated-MR5L6V7K.js.map} +1 -1
  157. package/dist/{platform-activity-row-627BFZ46.js → platform-activity-row-PJJVATTZ.js} +3 -1
  158. package/dist/{platform-activity-row-627BFZ46.js.map → platform-activity-row-PJJVATTZ.js.map} +1 -1
  159. package/dist/{queue-surface-implementation-ZHFI5JBP.js → queue-surface-implementation-67FS7FUI.js} +10 -9
  160. package/dist/{queue-surface-implementation-ZHFI5JBP.js.map → queue-surface-implementation-67FS7FUI.js.map} +1 -1
  161. package/dist/realtime/embedded-voice.d.ts +20 -0
  162. package/dist/realtime.js +22 -1353
  163. package/dist/realtime.js.map +1 -1
  164. package/dist/sandbox-files-34CJJNOY.js +21 -0
  165. package/dist/session-ui.js +32 -30
  166. package/dist/session-ui.js.map +1 -1
  167. package/dist/session.js +10 -9
  168. package/dist/sites.js +2 -1
  169. package/dist/terminal.js +6 -5
  170. package/dist/terminal.js.map +1 -1
  171. package/dist/{timeline-annotation-markers-GNDHTGBD.js → timeline-annotation-markers-U3G66KUD.js} +5 -4
  172. package/dist/{timeline-annotation-markers-GNDHTGBD.js.map → timeline-annotation-markers-U3G66KUD.js.map} +1 -1
  173. package/dist/{timeline-annotation-selection-DMLMM2TX.js → timeline-annotation-selection-BI7QTPTZ.js} +4 -3
  174. package/dist/{timeline-annotation-selection-DMLMM2TX.js.map → timeline-annotation-selection-BI7QTPTZ.js.map} +1 -1
  175. package/dist/{timeline-annotations-dialog-ELRDBHTS.js → timeline-annotations-dialog-FOMHSDTE.js} +7 -6
  176. package/dist/{timeline-annotations-dialog-ELRDBHTS.js.map → timeline-annotations-dialog-FOMHSDTE.js.map} +1 -1
  177. package/dist/usage.js +6 -5
  178. package/dist/usage.js.map +1 -1
  179. package/dist/voice-recording-owner.d.ts +7 -0
  180. package/package.json +3 -3
  181. package/src/components/composer-transcription-control.tsx +109 -27
  182. package/src/components/computer-viewer.tsx +139 -30
  183. package/src/components/markdown.tsx +7 -2
  184. package/src/components/message-timeline.tsx +10 -1
  185. package/src/components/session-conversation.tsx +44 -0
  186. package/src/components/session-list.tsx +1 -1
  187. package/src/components/session-proxy-scope.tsx +26 -5
  188. package/src/composer-voice-events.ts +12 -0
  189. package/src/connection-discovery.tsx +4 -1
  190. package/src/hooks/use-browser-session.ts +445 -168
  191. package/src/hooks/use-client-config-flags.ts +8 -0
  192. package/src/hooks/use-computer-session.ts +334 -98
  193. package/src/hooks/use-goal.ts +12 -1
  194. package/src/hooks/use-realtime-voice-models.ts +67 -0
  195. package/src/hooks/use-voice-input.ts +109 -18
  196. package/src/realtime/embedded-voice.tsx +81 -0
  197. package/src/realtime/realtime-control.tsx +82 -17
  198. package/src/voice-recording-owner.ts +22 -0
  199. package/styles/compiled.css +77 -18
  200. package/styles/index.css +49 -0
  201. package/styles/responsive.css +61 -4
  202. package/styles/session-capability.css +1 -1
  203. package/dist/browser-viewer-SHYKQRNX.js +0 -16
  204. package/dist/chat-interactive-block-UGBTNB3L.js +0 -11
  205. package/dist/chunk-GYVRFZDY.js.map +0 -1
  206. package/dist/chunk-I7IWRQLN.js.map +0 -1
  207. package/dist/chunk-IWIDV7SB.js +0 -1
  208. package/dist/chunk-LJ2UTJJE.js.map +0 -1
  209. package/dist/chunk-LN6TCXD3.js.map +0 -1
  210. package/dist/computer-viewer-6SRGWRUX.js +0 -19
  211. package/dist/model-policy-picker-menu-DXHUL73H.js +0 -14
  212. package/dist/sandbox-files-GORZOUUJ.js +0 -20
  213. /package/dist/{browser-viewer-SHYKQRNX.js.map → browser-viewer-5EEAS3IU.js.map} +0 -0
  214. /package/dist/{chat-interactive-block-UGBTNB3L.js.map → chat-interactive-block-ZFHOKTMS.js.map} +0 -0
  215. /package/dist/{chunk-IWIDV7SB.js.map → chunk-M357MHH3.js.map} +0 -0
  216. /package/dist/{computer-viewer-6SRGWRUX.js.map → computer-viewer-ELNLZGHI.js.map} +0 -0
  217. /package/dist/{model-policy-picker-menu-DXHUL73H.js.map → model-policy-picker-menu-WRNSRFQC.js.map} +0 -0
  218. /package/dist/{sandbox-files-GORZOUUJ.js.map → sandbox-files-34CJJNOY.js.map} +0 -0
@@ -1,3 +1,4 @@
1
+ "use client";
1
2
  import {
2
3
  InteractionInterventionBanner,
3
4
  isInteractionControlUnavailable,
@@ -6,13 +7,13 @@ import {
6
7
  useInteractionInterventions,
7
8
  useInteractionInvalidation,
8
9
  useViewerMenuDismiss
9
- } from "./chunk-J6DBS4T3.js";
10
+ } from "./chunk-BNKV5ZLF.js";
10
11
  import {
11
12
  copyTextToClipboard
12
- } from "./chunk-JVYQRRC4.js";
13
+ } from "./chunk-CVYCLFMH.js";
13
14
  import {
14
15
  DesktopViewer
15
- } from "./chunk-DYWBUX5Q.js";
16
+ } from "./chunk-IMSUVYQB.js";
16
17
  import {
17
18
  STREAM_CLOSE_REASON_NORMAL,
18
19
  STREAM_KIND_COMPUTER,
@@ -21,16 +22,16 @@ import {
21
22
  decodeStreamOpenAck,
22
23
  encodeStreamClose,
23
24
  encodeStreamOpen
24
- } from "./chunk-5J4FOW6Q.js";
25
+ } from "./chunk-TNEZNMSN.js";
25
26
  import {
26
27
  usePageLiveActivity
27
- } from "./chunk-YQEAA6LT.js";
28
+ } from "./chunk-QHUE7E4N.js";
28
29
  import {
29
30
  useEmbeddedComputerInteraction
30
- } from "./chunk-TKOTKRWY.js";
31
+ } from "./chunk-XTD7TZHW.js";
31
32
  import {
32
33
  cn
33
- } from "./chunk-22KP6LR5.js";
34
+ } from "./chunk-TXS65BB2.js";
34
35
 
35
36
  // src/components/computer-viewer.tsx
36
37
  import { interactionControlFailureFromError } from "@opengeni/sdk/interaction";
@@ -407,27 +408,49 @@ function isPlacementGenerationLossError(cause) {
407
408
 
408
409
  // src/hooks/use-computer-session.ts
409
410
  import { useCallback as useCallback2, useEffect as useEffect2, useRef as useRef2, useState as useState2 } from "react";
411
+ var actionResultFences = /* @__PURE__ */ new WeakMap();
412
+ function isStaleComputerActionResult(value) {
413
+ return typeof value === "object" && value !== null && actionResultFences.get(value)?.() === false;
414
+ }
415
+ function computerActionReceiptError(receipt) {
416
+ const error = new Error(receipt.error?.message ?? "Desktop input did not complete.");
417
+ const fence = actionResultFences.get(receipt);
418
+ if (fence) actionResultFences.set(error, fence);
419
+ return error;
420
+ }
410
421
  function useComputerSession(options) {
411
422
  const { client, workspaceId } = useEmbeddedComputerInteraction(options);
412
423
  const computerSessionId = options.computerSessionId;
413
424
  const enabled = (options.enabled ?? true) && computerSessionId !== null;
425
+ const sourceRef = useRef2({
426
+ client,
427
+ workspaceId,
428
+ computerSessionId,
429
+ enabled
430
+ });
431
+ if (sourceRef.current.client !== client || sourceRef.current.workspaceId !== workspaceId || sourceRef.current.computerSessionId !== computerSessionId || sourceRef.current.enabled !== enabled) {
432
+ sourceRef.current = { client, workspaceId, computerSessionId, enabled };
433
+ }
434
+ const source = sourceRef.current;
414
435
  const pageLive = usePageLiveActivity();
415
436
  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);
437
+ const [state, setState] = useState2(() => emptyState(source, enabled));
438
+ const visible = state.source === source ? state : emptyState(source, enabled);
420
439
  const refreshBlocked = isNonRetryableInteractionError(visible.error);
421
440
  const selectedTargetIdRef = useRef2(visible.selectedTargetId);
422
441
  selectedTargetIdRef.current = visible.selectedTargetId;
442
+ const selectionRevisionRef = useRef2(0);
443
+ const actionResultOrderRef = useRef2({ next: 0, settled: 0 });
423
444
  const targetsRef = useRef2({
445
+ source,
424
446
  computerSessionId,
425
447
  targets: visible.targets
426
448
  });
427
- const observationRef = useRef2({ computerSessionId, observation: visible.observation });
428
- if (targetsRef.current.computerSessionId !== computerSessionId) {
429
- targetsRef.current = { computerSessionId, targets: visible.targets };
449
+ const observationRef = useRef2({ source, computerSessionId, observation: visible.observation });
450
+ if (targetsRef.current.source !== source) {
451
+ targetsRef.current = { source, computerSessionId, targets: visible.targets };
430
452
  observationRef.current = {
453
+ source,
431
454
  computerSessionId,
432
455
  observation: visible.observation
433
456
  };
@@ -441,16 +464,21 @@ function useComputerSession(options) {
441
464
  });
442
465
  const mutationRef = useRef2({
443
466
  computerSessionId,
467
+ source,
444
468
  count: 0
445
469
  });
446
470
  const mountedRef = useRef2(true);
471
+ const isCurrentSource = useCallback2(
472
+ () => mountedRef.current && sourceRef.current === source,
473
+ [source]
474
+ );
447
475
  const invalidateRefresh = useCallback2(() => {
448
476
  const id = requestRef.current.id + 1;
449
477
  requestRef.current.controller?.abort();
450
478
  requestRef.current = { id, controller: null };
451
479
  }, []);
452
480
  const refresh = useCallback2(async () => {
453
- if (!enabled || !computerSessionId) return;
481
+ if (!enabled || !computerSessionId || !isCurrentSource()) return;
454
482
  const id = requestRef.current.id + 1;
455
483
  requestRef.current.controller?.abort();
456
484
  const controller = new AbortController();
@@ -466,16 +494,16 @@ function useComputerSession(options) {
466
494
  })
467
495
  ]);
468
496
  discoveryCompleted = true;
469
- if (!mountedRef.current || requestRef.current.id !== id) return;
497
+ if (!isCurrentSource() || requestRef.current.id !== id) return;
470
498
  const targets = sortComputerTargets(targetResponse.targets);
471
499
  const selected = chooseTarget(targets, selectedTargetIdRef.current);
472
- const previous = observationRef.current.observation;
500
+ const previous = observationRef.current.source === source ? observationRef.current.observation : null;
473
501
  const retainedObservation = selected && previous?.target.id === selected.id && previous.target.controllerGeneration === selected.controllerGeneration && previous.target.targetGeneration === selected.targetGeneration ? previous : null;
474
502
  selectedTargetIdRef.current = selected?.id ?? null;
475
- targetsRef.current = { computerSessionId, targets };
476
- observationRef.current = { computerSessionId, observation: retainedObservation };
503
+ targetsRef.current = { source, computerSessionId, targets };
504
+ observationRef.current = { source, computerSessionId, observation: retainedObservation };
477
505
  setState(
478
- (current) => current.computerSessionId === computerSessionId ? {
506
+ (current) => isCurrentSource() && current.source === source && current.computerSessionId === computerSessionId ? {
479
507
  ...current,
480
508
  session,
481
509
  targets,
@@ -489,12 +517,12 @@ function useComputerSession(options) {
489
517
  const observation = selected ? await client.observeComputerTarget(workspaceId, computerSessionId, selected.id, {
490
518
  signal: controller.signal
491
519
  }) : null;
492
- if (!mountedRef.current || requestRef.current.id !== id) return;
520
+ if (!isCurrentSource() || requestRef.current.id !== id) return;
493
521
  selectedTargetIdRef.current = selected?.id ?? null;
494
- targetsRef.current = { computerSessionId, targets };
495
- observationRef.current = { computerSessionId, observation };
522
+ targetsRef.current = { source, computerSessionId, targets };
523
+ observationRef.current = { source, computerSessionId, observation };
496
524
  setState(
497
- (current) => current.computerSessionId === computerSessionId ? {
525
+ (current) => isCurrentSource() && current.source === source && current.computerSessionId === computerSessionId ? {
498
526
  ...current,
499
527
  session,
500
528
  targets,
@@ -505,10 +533,10 @@ function useComputerSession(options) {
505
533
  } : current
506
534
  );
507
535
  } catch (cause) {
508
- if (controller.signal.aborted || !mountedRef.current || requestRef.current.id !== id) return;
536
+ if (controller.signal.aborted || !isCurrentSource() || requestRef.current.id !== id) return;
509
537
  const error = cause instanceof Error ? cause : new Error(String(cause));
510
538
  setState(
511
- (current) => current.computerSessionId === computerSessionId ? {
539
+ (current) => isCurrentSource() && current.source === source && current.computerSessionId === computerSessionId ? {
512
540
  ...current,
513
541
  loading: false,
514
542
  error,
@@ -521,23 +549,27 @@ function useComputerSession(options) {
521
549
  } finally {
522
550
  if (requestRef.current.id === id) requestRef.current = { id, controller: null };
523
551
  }
524
- }, [client, computerSessionId, enabled, workspaceId]);
552
+ }, [client, computerSessionId, enabled, isCurrentSource, source, workspaceId]);
525
553
  useEffect2(() => {
526
554
  mountedRef.current = true;
527
555
  if (!enabled) {
528
556
  invalidateRefresh();
529
- setState(emptyState(computerSessionId, false));
557
+ setState(emptyState(source, false));
530
558
  return;
531
559
  }
532
560
  setState(
533
- (current) => current.computerSessionId === computerSessionId ? { ...current, loading: true } : emptyState(computerSessionId, true)
561
+ (current) => current.source === source ? {
562
+ ...current,
563
+ loading: true,
564
+ mutating: mutationRef.current.source === source && mutationRef.current.count > 0
565
+ } : emptyState(source, true)
534
566
  );
535
567
  void refresh();
536
568
  return () => {
537
569
  mountedRef.current = false;
538
570
  invalidateRefresh();
539
571
  };
540
- }, [computerSessionId, enabled, invalidateRefresh, refresh]);
572
+ }, [computerSessionId, enabled, invalidateRefresh, refresh, source]);
541
573
  useEffect2(() => {
542
574
  if (!enabled || !pageLive || visible.mutating || refreshBlocked) return;
543
575
  const timer = setInterval(() => {
@@ -560,55 +592,48 @@ function useComputerSession(options) {
560
592
  }, [client, computerSessionId, enabled, pageLive, refresh, refreshBlocked, workspaceId]);
561
593
  const runMutation = useCallback2(
562
594
  async (scopeComputerSessionId, operation) => {
595
+ if (!isCurrentSource()) throw new Error("The desktop source is no longer selected.");
563
596
  invalidateRefresh();
564
- if (mutationRef.current.computerSessionId !== scopeComputerSessionId) {
597
+ if (mutationRef.current.computerSessionId !== scopeComputerSessionId || mutationRef.current.source !== source) {
565
598
  mutationRef.current = {
566
599
  computerSessionId: scopeComputerSessionId,
600
+ source,
567
601
  count: 0
568
602
  };
569
603
  }
570
604
  mutationRef.current.count += 1;
571
605
  setState(
572
- (current) => current.computerSessionId === scopeComputerSessionId ? { ...current, mutating: true, error: null } : current
606
+ (current) => isCurrentSource() && current.source === source && current.computerSessionId === scopeComputerSessionId ? { ...current, mutating: true, error: null } : current
573
607
  );
574
608
  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;
609
+ return await operation();
590
610
  } finally {
591
- if (mutationRef.current.computerSessionId === scopeComputerSessionId) {
611
+ if (mutationRef.current.computerSessionId === scopeComputerSessionId && mutationRef.current.source === source) {
592
612
  mutationRef.current.count = Math.max(0, mutationRef.current.count - 1);
593
613
  const mutating = mutationRef.current.count > 0;
594
614
  setState(
595
- (current) => current.computerSessionId === scopeComputerSessionId ? { ...current, mutating } : current
615
+ (current) => isCurrentSource() && current.source === source && current.computerSessionId === scopeComputerSessionId ? { ...current, mutating } : current
596
616
  );
597
617
  }
598
618
  }
599
619
  },
600
- [invalidateRefresh]
620
+ [invalidateRefresh, isCurrentSource, source]
601
621
  );
602
622
  const selectTarget = useCallback2(
603
623
  async (targetId) => {
604
624
  if (!computerSessionId) throw new Error("No desktop is selected.");
605
- const target = targetsRef.current.targets.find((candidate) => candidate.id === targetId);
625
+ if (!isCurrentSource()) throw new Error("The desktop source is no longer selected.");
626
+ const target = targetsRef.current.source === source ? targetsRef.current.targets.find((candidate) => candidate.id === targetId) : null;
606
627
  if (!target) throw new Error("The selected desktop target is no longer available.");
607
628
  invalidateRefresh();
629
+ const readRequestId = requestRef.current.id;
630
+ const selectionRevision = ++selectionRevisionRef.current;
631
+ const isCurrentSelection = () => isCurrentSource() && selectionRevisionRef.current === selectionRevision;
632
+ const isCurrentSelectionRead = () => isCurrentSelection() && requestRef.current.id === readRequestId;
608
633
  selectedTargetIdRef.current = target.id;
609
- observationRef.current = { computerSessionId, observation: null };
634
+ observationRef.current = { source, computerSessionId, observation: null };
610
635
  setState(
611
- (current) => current.computerSessionId === computerSessionId ? {
636
+ (current) => isCurrentSelection() && current.source === source && current.computerSessionId === computerSessionId ? {
612
637
  ...current,
613
638
  selectedTargetId: target.id,
614
639
  observation: null,
@@ -622,37 +647,40 @@ function useComputerSession(options) {
622
647
  computerSessionId,
623
648
  target.id
624
649
  );
625
- if (selectedTargetIdRef.current !== target.id) return target;
626
- observationRef.current = { computerSessionId, observation };
650
+ if (!isCurrentSelectionRead() || selectedTargetIdRef.current !== target.id) return target;
651
+ observationRef.current = { source, computerSessionId, observation };
627
652
  setState(
628
- (current) => current.computerSessionId === computerSessionId && current.selectedTargetId === target.id ? { ...current, observation, loading: false, error: null } : current
653
+ (current) => isCurrentSelection() && current.source === source && current.computerSessionId === computerSessionId && current.selectedTargetId === target.id ? { ...current, observation, loading: false, error: null } : current
629
654
  );
630
655
  return observation.target;
631
656
  } catch (cause) {
632
657
  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
- );
658
+ if (isCurrentSelectionRead()) {
659
+ setState(
660
+ (current) => isCurrentSelection() && current.source === source && current.computerSessionId === computerSessionId && current.selectedTargetId === target.id ? {
661
+ ...current,
662
+ loading: false,
663
+ error,
664
+ controlError: isInteractionControlUnavailable(error, "observation") ? error : current.controlError
665
+ } : current
666
+ );
667
+ }
641
668
  throw error;
642
669
  }
643
670
  },
644
- [client, computerSessionId, invalidateRefresh, workspaceId]
671
+ [client, computerSessionId, invalidateRefresh, isCurrentSource, source, workspaceId]
645
672
  );
646
673
  const dispatchAction = useCallback2(
647
674
  async (action, operationId, frame) => {
648
675
  if (!computerSessionId) throw new Error("No desktop is selected.");
676
+ if (!isCurrentSource()) throw new Error("The desktop source is no longer selected.");
649
677
  if (frame && frame.computerSessionId !== computerSessionId) {
650
678
  throw new Error("The displayed desktop frame belongs to another desktop session.");
651
679
  }
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(
680
+ const currentObservation = observationRef.current.source === source && observationRef.current.computerSessionId === computerSessionId ? observationRef.current.observation : null;
681
+ const focusTarget = action.type === "focus" && targetsRef.current.source === source ? targetsRef.current.targets.find((candidate) => candidate.id === action.targetId) ?? null : null;
682
+ const frameTarget = frame && targetsRef.current.source === source ? currentObservation?.target.id === frame.targetId ? currentObservation.target : targetsRef.current.targets.find((candidate) => candidate.id === frame.targetId) ?? null : null;
683
+ const inputTarget = (action.type === "keyboard" || action.type === "clipboard") && targetsRef.current.source === source && targetsRef.current.computerSessionId === computerSessionId ? targetsRef.current.targets.find(
656
684
  (candidate) => candidate.id === selectedTargetIdRef.current
657
685
  ) : null;
658
686
  const target = frameTarget ?? focusTarget ?? currentObservation?.target ?? inputTarget ?? null;
@@ -660,6 +688,9 @@ function useComputerSession(options) {
660
688
  if (frame && frame.targetId !== selectedTargetIdRef.current) {
661
689
  throw new Error("The displayed desktop frame is no longer selected.");
662
690
  }
691
+ if (frame && (frame.controllerGeneration !== target.controllerGeneration || frame.targetGeneration !== target.targetGeneration)) {
692
+ throw new Error("The displayed desktop frame belongs to an earlier target generation.");
693
+ }
663
694
  if (action.type === "pointer") {
664
695
  const expectedFrameId = frame?.frameId ?? currentObservation?.frameId ?? null;
665
696
  if (!expectedFrameId || action.frameId !== expectedFrameId) {
@@ -669,34 +700,114 @@ function useComputerSession(options) {
669
700
  if (action.type === "semantic" && !currentObservation) {
670
701
  throw new Error("The desktop accessibility tree is not ready for input.");
671
702
  }
703
+ const selectedTargetId = selectedTargetIdRef.current;
704
+ const selectedTarget = targetsRef.current.targets.find(
705
+ (candidate) => candidate.id === selectedTargetId
706
+ );
707
+ const selectedFence = selectedTarget && {
708
+ id: selectedTarget.id,
709
+ computerSessionId: selectedTarget.computerSessionId,
710
+ controllerGeneration: selectedTarget.controllerGeneration,
711
+ targetGeneration: selectedTarget.targetGeneration
712
+ };
713
+ const selectionRevision = selectionRevisionRef.current;
714
+ const isCurrentView = () => isCurrentSource() && selectionRevisionRef.current === selectionRevision && selectedTargetIdRef.current === selectedTargetId && targetsRef.current.source === source && targetsRef.current.computerSessionId === computerSessionId && sameTargetFence(
715
+ selectedFence,
716
+ targetsRef.current.targets.find((candidate) => candidate.id === selectedTargetId)
717
+ );
718
+ const resultOrder = ++actionResultOrderRef.current.next;
719
+ let admitted = false;
720
+ let resultTargetId = selectedTargetId;
721
+ let resultFence = selectedFence;
722
+ const isCurrentResultView = () => isCurrentSource() && selectionRevisionRef.current === selectionRevision && selectedTargetIdRef.current === resultTargetId && targetsRef.current.source === source && sameTargetFence(
723
+ resultFence,
724
+ targetsRef.current.targets.find((candidate) => candidate.id === resultTargetId)
725
+ );
726
+ const canProjectResult = () => {
727
+ if (!admitted) {
728
+ if (!isCurrentView() || resultOrder < actionResultOrderRef.current.settled) return false;
729
+ actionResultOrderRef.current.settled = resultOrder;
730
+ admitted = true;
731
+ invalidateRefresh();
732
+ }
733
+ return isCurrentResultView() && resultOrder === actionResultOrderRef.current.settled;
734
+ };
735
+ const projectState = (update) => {
736
+ setState(
737
+ (current) => admitted && isCurrentResultView() && current.source === source && current.computerSessionId === computerSessionId && current.actionResultOrder <= resultOrder ? { ...update(current), actionResultOrder: resultOrder } : current
738
+ );
739
+ };
672
740
  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 ? {
741
+ try {
742
+ const receipt = await client.actInComputer(workspaceId, computerSessionId, {
743
+ operationId,
744
+ targetId: target.id,
745
+ expectedTargetGeneration: frame?.targetGeneration ?? target.targetGeneration,
746
+ expectedObservationId: action.type === "pointer" ? null : currentObservation?.observationId ?? null,
747
+ expectedFrameId: action.type === "pointer" ? action.frameId : null,
748
+ action
749
+ });
750
+ const receiptSettled = receipt.state === "completed" || receipt.state === "failed" || receipt.state === "outcome_unknown";
751
+ actionResultFences.set(
752
+ receipt,
753
+ () => admitted ? isCurrentResultView() && resultOrder === actionResultOrderRef.current.settled : isCurrentView() && resultOrder >= actionResultOrderRef.current.settled
754
+ );
755
+ if (!receiptSettled || !canProjectResult()) return receipt;
756
+ if (receipt.observation) {
757
+ const observation = receipt.observation;
758
+ observationRef.current = { source, computerSessionId, observation };
759
+ selectedTargetIdRef.current = observation.target.id;
760
+ targetsRef.current.targets = replaceTarget(
761
+ targetsRef.current.targets,
762
+ observation.target
763
+ );
764
+ resultTargetId = observation.target.id;
765
+ resultFence = {
766
+ id: observation.target.id,
767
+ computerSessionId: observation.target.computerSessionId,
768
+ controllerGeneration: observation.target.controllerGeneration,
769
+ targetGeneration: observation.target.targetGeneration
770
+ };
771
+ projectState((current) => ({
687
772
  ...current,
773
+ error: null,
774
+ controlError: null,
688
775
  observation,
689
776
  selectedTargetId: observation.target.id,
690
777
  targets: replaceTarget(current.targets, observation.target)
691
- } : current
778
+ }));
779
+ } else {
780
+ projectState((current) => ({ ...current, error: null, controlError: null }));
781
+ void refresh();
782
+ }
783
+ return receipt;
784
+ } catch (cause) {
785
+ const error = cause instanceof Error ? cause : new Error(String(cause));
786
+ actionResultFences.set(
787
+ error,
788
+ () => admitted && isCurrentResultView() && resultOrder === actionResultOrderRef.current.settled
692
789
  );
693
- } else {
694
- void refresh();
790
+ if (canProjectResult()) {
791
+ projectState((current) => ({
792
+ ...current,
793
+ error,
794
+ controlError: isInteractionControlUnavailable(error) ? error : current.controlError
795
+ }));
796
+ }
797
+ throw error;
695
798
  }
696
- return receipt;
697
799
  });
698
800
  },
699
- [client, computerSessionId, refresh, runMutation, workspaceId]
801
+ [
802
+ client,
803
+ computerSessionId,
804
+ invalidateRefresh,
805
+ isCurrentSource,
806
+ refresh,
807
+ runMutation,
808
+ source,
809
+ workspaceId
810
+ ]
700
811
  );
701
812
  const act = useCallback2(
702
813
  async (action, operationId = crypto.randomUUID()) => await dispatchAction(action, operationId, null),
@@ -708,12 +819,31 @@ function useComputerSession(options) {
708
819
  );
709
820
  const readClipboard = useCallback2(async () => {
710
821
  if (!computerSessionId) throw new Error("No desktop is selected.");
822
+ const selectionRevision = selectionRevisionRef.current;
823
+ const settledOrder = actionResultOrderRef.current.settled;
824
+ const selectedTargetId = selectedTargetIdRef.current;
825
+ const selectedTarget = targetsRef.current.targets.find(
826
+ (candidate) => candidate.id === selectedTargetId
827
+ );
828
+ const selectedFence = selectedTarget && {
829
+ id: selectedTarget.id,
830
+ computerSessionId: selectedTarget.computerSessionId,
831
+ controllerGeneration: selectedTarget.controllerGeneration,
832
+ targetGeneration: selectedTarget.targetGeneration
833
+ };
834
+ const isCurrentRead = () => isCurrentSource() && selectionRevisionRef.current === selectionRevision && actionResultOrderRef.current.settled === settledOrder && selectedTargetIdRef.current === selectedTargetId && targetsRef.current.source === source && sameTargetFence(
835
+ selectedFence,
836
+ targetsRef.current.targets.find((candidate) => candidate.id === selectedTargetId)
837
+ );
711
838
  try {
712
- return await client.readComputerClipboard(workspaceId, computerSessionId);
839
+ const clipboard = await client.readComputerClipboard(workspaceId, computerSessionId);
840
+ actionResultFences.set(clipboard, isCurrentRead);
841
+ return clipboard;
713
842
  } catch (cause) {
714
843
  const error = cause instanceof Error ? cause : new Error(String(cause));
844
+ actionResultFences.set(error, isCurrentRead);
715
845
  setState(
716
- (current) => current.computerSessionId === computerSessionId ? {
846
+ (current) => isCurrentRead() && current.source === source && current.computerSessionId === computerSessionId ? {
717
847
  ...current,
718
848
  error,
719
849
  controlError: isInteractionControlUnavailable(error) ? error : current.controlError
@@ -721,7 +851,7 @@ function useComputerSession(options) {
721
851
  );
722
852
  throw error;
723
853
  }
724
- }, [client, computerSessionId, workspaceId]);
854
+ }, [client, computerSessionId, isCurrentSource, source, workspaceId]);
725
855
  return {
726
856
  session: visible.session,
727
857
  targets: visible.targets,
@@ -738,9 +868,10 @@ function useComputerSession(options) {
738
868
  actFromFrame
739
869
  };
740
870
  }
741
- function emptyState(computerSessionId, loading) {
871
+ function emptyState(source, loading) {
742
872
  return {
743
- computerSessionId,
873
+ source,
874
+ computerSessionId: source.computerSessionId,
744
875
  session: null,
745
876
  targets: [],
746
877
  selectedTargetId: null,
@@ -748,7 +879,8 @@ function emptyState(computerSessionId, loading) {
748
879
  loading,
749
880
  mutating: false,
750
881
  error: null,
751
- controlError: null
882
+ controlError: null,
883
+ actionResultOrder: 0
752
884
  };
753
885
  }
754
886
  function chooseTarget(targets, preferredId) {
@@ -768,6 +900,9 @@ function replaceTarget(targets, target) {
768
900
  target
769
901
  ]);
770
902
  }
903
+ function sameTargetFence(left, right) {
904
+ return left?.id === right?.id && left?.computerSessionId === right?.computerSessionId && left?.controllerGeneration === right?.controllerGeneration && left?.targetGeneration === right?.targetGeneration;
905
+ }
771
906
 
772
907
  // src/hooks/use-computer-input-posture.ts
773
908
  import { useCallback as useCallback3, useEffect as useEffect3, useRef as useRef3, useState as useState3 } from "react";
@@ -1088,6 +1223,7 @@ function ComputerViewer({
1088
1223
  }, [enabled, endStaleComputer, registry.sessions]);
1089
1224
  const notifyError = useCallback5(
1090
1225
  (cause, fallback) => {
1226
+ if (isStaleComputerActionResult(cause)) return;
1091
1227
  onNotify?.({
1092
1228
  kind: "error",
1093
1229
  message: cause instanceof Error ? cause.message : fallback
@@ -1322,19 +1458,20 @@ function ComputerViewer({
1322
1458
  suppressGenericCreate
1323
1459
  ]);
1324
1460
  const perform = useCallback5(
1325
- async (action, frame) => {
1461
+ async (action, frame, operationId) => {
1326
1462
  if (computer.controlError) throw computer.controlError;
1327
1463
  if (inputDenied) throw new Error("Desktop is view only. Refresh to check desktop controls.");
1328
1464
  let receipt;
1329
1465
  if (action.type === "pointer") {
1330
1466
  if (!frame) throw new Error("Desktop view is not ready for pointer input.");
1331
- receipt = await actFromFrame(action, frame);
1467
+ receipt = await actFromFrame(action, frame, operationId);
1332
1468
  } else {
1333
- receipt = await act(action);
1469
+ receipt = await act(action, operationId);
1334
1470
  }
1335
- if (receipt.state !== "completed") {
1336
- throw new Error(receipt.error?.message ?? "Desktop input did not complete.");
1471
+ if (!isStaleComputerActionResult(receipt) && receipt.state !== "completed") {
1472
+ throw computerActionReceiptError(receipt);
1337
1473
  }
1474
+ return receipt;
1338
1475
  },
1339
1476
  [act, actFromFrame, computer.controlError, inputDenied]
1340
1477
  );
@@ -1349,11 +1486,15 @@ function ComputerViewer({
1349
1486
  return;
1350
1487
  event.preventDefault();
1351
1488
  event.stopPropagation();
1352
- void perform({ type: "clipboard", operation: "copy" }, null).then(() => computer.readClipboard()).then(async (clipboard) => {
1489
+ void (async () => {
1490
+ const receipt = await perform({ type: "clipboard", operation: "copy" }, null);
1491
+ if (isStaleComputerActionResult(receipt)) return;
1492
+ const clipboard = await computer.readClipboard();
1493
+ if (isStaleComputerActionResult(receipt) || isStaleComputerActionResult(clipboard)) return;
1353
1494
  if (clipboard.text && !await copyTextToClipboard(clipboard.text)) {
1354
1495
  throw new Error("Desktop text could not be copied to the local clipboard");
1355
1496
  }
1356
- }).catch((cause) => notifyError(cause, "Could not copy from the desktop."));
1497
+ })().catch((cause) => notifyError(cause, "Could not copy from the desktop."));
1357
1498
  },
1358
1499
  [computer, notifyError, perform, rfbInputEnabled, rfbStream]
1359
1500
  );
@@ -1362,8 +1503,11 @@ function ComputerViewer({
1362
1503
  if (!rfbInputEnabled || !rfbStream || computer.session?.capabilities?.clipboard !== true)
1363
1504
  return false;
1364
1505
  void (async () => {
1365
- await perform({ type: "clipboard", operation: "write", text }, null);
1366
- assertExactComputerClipboard(await computer.readClipboard(), text);
1506
+ const receipt = await perform({ type: "clipboard", operation: "write", text }, null);
1507
+ if (isStaleComputerActionResult(receipt)) return;
1508
+ const clipboard = await computer.readClipboard();
1509
+ if (isStaleComputerActionResult(receipt) || isStaleComputerActionResult(clipboard)) return;
1510
+ assertExactComputerClipboard(clipboard, text);
1367
1511
  await perform({ type: "clipboard", operation: "paste" }, null);
1368
1512
  })().catch((cause) => notifyError(cause, "Could not paste into the desktop."));
1369
1513
  return true;
@@ -1522,6 +1666,7 @@ function ComputerViewer({
1522
1666
  backgroundInput: computer.session?.capabilities?.backgroundInput === true,
1523
1667
  clipboardEnabled: attachmentInputAllowed && computer.session?.capabilities?.clipboard === true,
1524
1668
  pointerInput: attachmentInputAllowed && computer.session?.capabilities?.pointerInput === true,
1669
+ pointerClickContinuation: attachmentInputAllowed && computer.session?.capabilities?.pointerClickContinuation === true,
1525
1670
  keyboardInput: attachmentInputAllowed && computer.session?.capabilities?.keyboardInput === true,
1526
1671
  onAction: perform,
1527
1672
  onReadClipboard: computer.readClipboard,
@@ -1996,21 +2141,38 @@ function ComputerViewport(props) {
1996
2141
  }
1997
2142
  }, [clearBufferedInput, props.keyboardInput, props.pointerInput, rawInputEnabled]);
1998
2143
  const enqueue = useCallback5(
1999
- (action, frame, after) => {
2144
+ (action, frame, after, continuation) => {
2000
2145
  const epoch = actionQueueEpochRef.current;
2001
2146
  const dispatch = actionRef.current;
2002
2147
  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);
2148
+ const operationId = crypto.randomUUID();
2149
+ let markStarted;
2150
+ const started = new Promise((resolve) => {
2151
+ markStarted = resolve;
2152
+ });
2153
+ const previous = actionTailRef.current;
2154
+ const ready = continuation && previous === continuation.completion ? continuation.started : previous.then(() => true);
2155
+ const delivery = ready.then(async (firstStarted) => {
2156
+ if (!firstStarted || !isCurrent()) {
2157
+ markStarted(false);
2158
+ return;
2159
+ }
2160
+ const pending = dispatch(action, frame, operationId);
2161
+ markStarted(true);
2162
+ const receipt = await pending;
2163
+ const isCurrentResult = () => isCurrent() && !isStaleComputerActionResult(receipt);
2164
+ if (!isCurrentResult()) return;
2165
+ await after?.(isCurrentResult);
2008
2166
  }).catch((cause) => {
2009
- if (!isCurrent()) return;
2167
+ markStarted(false);
2168
+ if (!isCurrent() || isStaleComputerActionResult(cause)) return;
2010
2169
  actionQueueEpochRef.current += 1;
2011
2170
  clearBufferedInput();
2012
2171
  errorRef.current(cause);
2013
2172
  });
2173
+ const completion = continuation ? Promise.all([previous, delivery]).then(() => void 0) : delivery;
2174
+ actionTailRef.current = completion;
2175
+ return { operationId, started, completion };
2014
2176
  },
2015
2177
  [clearBufferedInput]
2016
2178
  );
@@ -2031,6 +2193,7 @@ function ComputerViewport(props) {
2031
2193
  if (clickTimerRef.current) clearTimeout(clickTimerRef.current);
2032
2194
  clickTimerRef.current = null;
2033
2195
  lastClickRef.current = null;
2196
+ if (!pending.pending) return;
2034
2197
  enqueue(
2035
2198
  {
2036
2199
  type: "pointer",
@@ -2062,7 +2225,8 @@ function ComputerViewport(props) {
2062
2225
  }, [enqueue]);
2063
2226
  const pointerDown = (event) => {
2064
2227
  const frame = paintedFrameRef.current;
2065
- if (!frame || props.mutating || !rawInputEnabled || event.button !== 0) return;
2228
+ if (!frame || !props.pointerClickContinuation && props.mutating || !rawInputEnabled || event.button !== 0)
2229
+ return;
2066
2230
  event.preventDefault();
2067
2231
  if (keyboardInputEnabled) inputRef.current?.focus({ preventScroll: true });
2068
2232
  if (!pointerInputEnabled) return;
@@ -2081,6 +2245,7 @@ function ComputerViewport(props) {
2081
2245
  const start = pointerStartRef.current;
2082
2246
  pointerStartRef.current = null;
2083
2247
  if (!start || start.pointerId !== event.pointerId) return;
2248
+ const canvasRect = event.currentTarget.getBoundingClientRect();
2084
2249
  const from = point(start.frame, start.x, start.y);
2085
2250
  const to = point(start.frame, event.clientX, event.clientY);
2086
2251
  if (!from || !to) return;
@@ -2102,7 +2267,7 @@ function ComputerViewport(props) {
2102
2267
  }
2103
2268
  const now = Date.now();
2104
2269
  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)) {
2270
+ if (previous && now - previous.at < 280 && previous.pending === !props.pointerClickContinuation && (props.pointerClickContinuation ? Math.hypot(previous.clientX - event.clientX, previous.clientY - event.clientY) < 6 && previous.canvasRect.left === canvasRect.left && previous.canvasRect.top === canvasRect.top && previous.canvasRect.width === canvasRect.width && previous.canvasRect.height === canvasRect.height && sameComputerTarget(previous.frame, start.frame) && previous.frame.width === start.frame.width && previous.frame.height === start.frame.height : Math.hypot(previous.x - to.x, previous.y - to.y) < 6 && sameFrameFence(previous.frame, start.frame))) {
2106
2271
  if (clickTimerRef.current) clearTimeout(clickTimerRef.current);
2107
2272
  clickTimerRef.current = null;
2108
2273
  lastClickRef.current = null;
@@ -2110,15 +2275,45 @@ function ComputerViewport(props) {
2110
2275
  {
2111
2276
  type: "pointer",
2112
2277
  frameId: start.frame.frameId,
2113
- action: "double_click",
2278
+ action: props.pointerClickContinuation ? "click" : "double_click",
2279
+ ...props.pointerClickContinuation ? { clickCount: 2 } : {},
2280
+ ...previous.delivery ? { continuationOfOperationId: previous.delivery.operationId } : {},
2281
+ x: to.x,
2282
+ y: to.y
2283
+ },
2284
+ start.frame,
2285
+ void 0,
2286
+ previous.delivery ?? void 0
2287
+ );
2288
+ return;
2289
+ }
2290
+ flushPendingClick();
2291
+ lastClickRef.current = {
2292
+ at: now,
2293
+ x: to.x,
2294
+ y: to.y,
2295
+ frame: start.frame,
2296
+ clientX: event.clientX,
2297
+ clientY: event.clientY,
2298
+ canvasRect,
2299
+ pending: !props.pointerClickContinuation,
2300
+ delivery: null
2301
+ };
2302
+ if (props.pointerClickContinuation) {
2303
+ const delivery = enqueue(
2304
+ {
2305
+ type: "pointer",
2306
+ frameId: start.frame.frameId,
2307
+ action: "click",
2308
+ clickCount: 1,
2114
2309
  x: to.x,
2115
2310
  y: to.y
2116
2311
  },
2117
2312
  start.frame
2118
2313
  );
2314
+ if (lastClickRef.current) lastClickRef.current.delivery = delivery;
2119
2315
  return;
2120
2316
  }
2121
- lastClickRef.current = { at: now, x: to.x, y: to.y, frame: start.frame };
2122
2317
  clickTimerRef.current = setTimeout(() => {
2123
2318
  clickTimerRef.current = null;
2124
2319
  lastClickRef.current = null;
@@ -2730,4 +2925,4 @@ export {
2730
2925
  ComputerViewer,
2731
2926
  computerKey
2732
2927
  };
2733
- //# sourceMappingURL=chunk-GYVRFZDY.js.map
2928
+ //# sourceMappingURL=chunk-I6OY3MWC.js.map