@opengeni/react 7.6.1-canary.37073905159001 → 7.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (136) hide show
  1. package/README.md +2 -19
  2. package/dist/{browser-viewer-63TXMINB.js → browser-viewer-5X47I6T2.js} +5 -6
  3. package/dist/{chunk-TKB4SB44.js → chunk-45GE35O7.js} +2 -10
  4. package/dist/chunk-45GE35O7.js.map +1 -0
  5. package/dist/{chunk-DOJWJQ3F.js → chunk-5EXGU7UF.js} +3 -3
  6. package/dist/{chunk-YN7I3STW.js → chunk-6NEZPF53.js} +32 -153
  7. package/dist/chunk-6NEZPF53.js.map +1 -0
  8. package/dist/{chunk-I7IWRQLN.js → chunk-BI57CQUV.js} +2 -2
  9. package/dist/{chunk-6ZSWSH4I.js → chunk-CHPKSLPN.js} +1 -3
  10. package/dist/chunk-CHPKSLPN.js.map +1 -0
  11. package/dist/{chunk-Q5QBEZIQ.js → chunk-DV2DBNPQ.js} +29 -48
  12. package/dist/chunk-DV2DBNPQ.js.map +1 -0
  13. package/dist/{chunk-RDKBXMWX.js → chunk-FGBCYQOT.js} +74 -120
  14. package/dist/chunk-FGBCYQOT.js.map +1 -0
  15. package/dist/{chunk-AAG4R3QN.js → chunk-KGENSZKS.js} +3 -4
  16. package/dist/chunk-KGENSZKS.js.map +1 -0
  17. package/dist/{chunk-YQEAA6LT.js → chunk-KZ73RL76.js} +3 -7
  18. package/dist/chunk-KZ73RL76.js.map +1 -0
  19. package/dist/{chunk-LFXYGZWK.js → chunk-LZ3KHA55.js} +60 -169
  20. package/dist/chunk-LZ3KHA55.js.map +1 -0
  21. package/dist/{chunk-2QUSQVF2.js → chunk-UINDK6C3.js} +3 -3
  22. package/dist/{chunk-UTRTG6LH.js → chunk-UJRYEWZI.js} +8 -48
  23. package/dist/chunk-UJRYEWZI.js.map +1 -0
  24. package/dist/{chunk-XOAMRDDK.js → chunk-VGIKFIO2.js} +3 -6
  25. package/dist/chunk-VGIKFIO2.js.map +1 -0
  26. package/dist/{chunk-ELCE5SXV.js → chunk-WZ55DVGK.js} +4 -8
  27. package/dist/chunk-WZ55DVGK.js.map +1 -0
  28. package/dist/{chunk-IQUCRAT7.js → chunk-XA6R7DJQ.js} +3 -3
  29. package/dist/{chunk-TOAH2BKA.js → chunk-YCGBREUV.js} +2 -6
  30. package/dist/{chunk-TOAH2BKA.js.map → chunk-YCGBREUV.js.map} +1 -1
  31. package/dist/{chunk-HNFCAQ5R.js → chunk-YRYF2GLG.js} +31 -39
  32. package/dist/chunk-YRYF2GLG.js.map +1 -0
  33. package/dist/{chunk-GISEVGS6.js → chunk-YSMMDOOE.js} +2 -10
  34. package/dist/chunk-YSMMDOOE.js.map +1 -0
  35. package/dist/{chunk-CWWKTNKK.js → chunk-Z3DISEQN.js} +18 -35
  36. package/dist/chunk-Z3DISEQN.js.map +1 -0
  37. package/dist/client.d.ts +1 -4
  38. package/dist/commands/types.d.ts +0 -2
  39. package/dist/components/approval-surface.d.ts +0 -15
  40. package/dist/composer.js +7 -8
  41. package/dist/{computer-viewer-X2MBRZY4.js → computer-viewer-7GF35SNY.js} +4 -5
  42. package/dist/connect.js +3 -3
  43. package/dist/hooks/use-browser-session.d.ts +0 -4
  44. package/dist/hooks/use-composer.d.ts +0 -2
  45. package/dist/hooks/use-computer-session.d.ts +0 -2
  46. package/dist/hooks/use-file-attachments.d.ts +0 -2
  47. package/dist/hooks/use-session-events.d.ts +1 -1
  48. package/dist/hooks/use-workspace-sessions.d.ts +5 -15
  49. package/dist/index.d.ts +2 -2
  50. package/dist/index.js +21 -28
  51. package/dist/index.js.map +1 -1
  52. package/dist/interaction.d.ts +1 -1
  53. package/dist/interaction.js +5 -6
  54. package/dist/interaction.js.map +1 -1
  55. package/dist/{latest-question-CNEZ5KAN.js → latest-question-3BJGKQQ6.js} +3 -3
  56. package/dist/lib/format.d.ts +3 -27
  57. package/dist/lib/interaction-errors.d.ts +0 -3
  58. package/dist/machines.js +4 -5
  59. package/dist/{model-policy-picker-menu-JSA64TNN.js → model-policy-picker-menu-PUTA5JOB.js} +4 -4
  60. package/dist/model-policy.js +1 -1
  61. package/dist/provider.d.ts +1 -5
  62. package/dist/{queue-surface-implementation-HENWYFWA.js → queue-surface-implementation-2CAQAIHM.js} +2 -3
  63. package/dist/{queue-surface-implementation-HENWYFWA.js.map → queue-surface-implementation-2CAQAIHM.js.map} +1 -1
  64. package/dist/realtime.js +2 -2
  65. package/dist/{sandbox-files-BBC47SLD.js → sandbox-files-AEJTWGT4.js} +3 -4
  66. package/dist/session-ui.d.ts +1 -1
  67. package/dist/session-ui.js +12 -13
  68. package/dist/session-ui.js.map +1 -1
  69. package/dist/session.js +6 -7
  70. package/dist/usage.js +1 -2
  71. package/dist/usage.js.map +1 -1
  72. package/package.json +3 -3
  73. package/src/client.ts +1 -5
  74. package/src/commands/registry.ts +15 -18
  75. package/src/commands/types.ts +0 -2
  76. package/src/components/approval-surface.tsx +42 -166
  77. package/src/components/browser-viewer.tsx +9 -38
  78. package/src/components/composer.tsx +2 -9
  79. package/src/components/computer-viewer.tsx +30 -160
  80. package/src/components/generated-video-player.tsx +2 -4
  81. package/src/components/human-input-form.tsx +5 -18
  82. package/src/components/message-timeline.tsx +2 -4
  83. package/src/components/open-geni-chat.tsx +4 -11
  84. package/src/components/queue-surface-state.tsx +1 -3
  85. package/src/components/session-chrome.tsx +2 -5
  86. package/src/components/session-commands-panel.tsx +1 -3
  87. package/src/components/session-conversation.tsx +2 -4
  88. package/src/components/session-list.tsx +5 -7
  89. package/src/components/session-mcp-capability-card.tsx +1 -11
  90. package/src/hooks/internal.ts +2 -3
  91. package/src/hooks/use-browser-session.ts +79 -115
  92. package/src/hooks/use-composer.ts +31 -70
  93. package/src/hooks/use-computer-session.ts +6 -57
  94. package/src/hooks/use-file-attachments.ts +3 -16
  95. package/src/hooks/use-goal.ts +1 -2
  96. package/src/hooks/use-session-capabilities.ts +2 -3
  97. package/src/hooks/use-session-events.ts +8 -15
  98. package/src/hooks/use-slash-commands.ts +11 -6
  99. package/src/hooks/use-turn-queue.ts +1 -2
  100. package/src/hooks/use-workspace-sessions.ts +7 -40
  101. package/src/index.ts +2 -7
  102. package/src/interaction.ts +0 -1
  103. package/src/lib/format.ts +9 -73
  104. package/src/lib/interaction-errors.ts +0 -15
  105. package/src/model-policy.ts +0 -5
  106. package/src/provider.tsx +4 -12
  107. package/src/session-ui.ts +1 -1
  108. package/src/timeline/projection.ts +1 -12
  109. package/styles/compiled.css +0 -14
  110. package/dist/chunk-6ZSWSH4I.js.map +0 -1
  111. package/dist/chunk-AAG4R3QN.js.map +0 -1
  112. package/dist/chunk-CWWKTNKK.js.map +0 -1
  113. package/dist/chunk-ELCE5SXV.js.map +0 -1
  114. package/dist/chunk-GISEVGS6.js.map +0 -1
  115. package/dist/chunk-HNFCAQ5R.js.map +0 -1
  116. package/dist/chunk-LFXYGZWK.js.map +0 -1
  117. package/dist/chunk-Q5QBEZIQ.js.map +0 -1
  118. package/dist/chunk-RDKBXMWX.js.map +0 -1
  119. package/dist/chunk-TKB4SB44.js.map +0 -1
  120. package/dist/chunk-UGLEYEMP.js +0 -33
  121. package/dist/chunk-UGLEYEMP.js.map +0 -1
  122. package/dist/chunk-UTRTG6LH.js.map +0 -1
  123. package/dist/chunk-XOAMRDDK.js.map +0 -1
  124. package/dist/chunk-YN7I3STW.js.map +0 -1
  125. package/dist/chunk-YQEAA6LT.js.map +0 -1
  126. package/dist/lib/error-message.d.ts +0 -7
  127. package/src/lib/error-message.ts +0 -37
  128. /package/dist/{browser-viewer-63TXMINB.js.map → browser-viewer-5X47I6T2.js.map} +0 -0
  129. /package/dist/{chunk-DOJWJQ3F.js.map → chunk-5EXGU7UF.js.map} +0 -0
  130. /package/dist/{chunk-I7IWRQLN.js.map → chunk-BI57CQUV.js.map} +0 -0
  131. /package/dist/{chunk-2QUSQVF2.js.map → chunk-UINDK6C3.js.map} +0 -0
  132. /package/dist/{chunk-IQUCRAT7.js.map → chunk-XA6R7DJQ.js.map} +0 -0
  133. /package/dist/{computer-viewer-X2MBRZY4.js.map → computer-viewer-7GF35SNY.js.map} +0 -0
  134. /package/dist/{latest-question-CNEZ5KAN.js.map → latest-question-3BJGKQQ6.js.map} +0 -0
  135. /package/dist/{model-policy-picker-menu-JSA64TNN.js.map → model-policy-picker-menu-PUTA5JOB.js.map} +0 -0
  136. /package/dist/{sandbox-files-BBC47SLD.js.map → sandbox-files-AEJTWGT4.js.map} +0 -0
@@ -25,7 +25,6 @@ import {
25
25
  } from "lucide-react";
26
26
  import {
27
27
  type ClipboardEvent,
28
- type CompositionEvent,
29
28
  type KeyboardEvent,
30
29
  type MouseEvent,
31
30
  type PointerEvent,
@@ -291,12 +290,9 @@ export function ComputerViewer({
291
290
  const rfbStream =
292
291
  frames.attachment?.stream.kind === "direct_rfb" ? frames.attachment.stream : null;
293
292
  const machineLocked = selectedRegistrySession?.failureCode === "machine_locked";
294
- const controlUnavailable = computer.controlError !== null;
295
- const connectionState = controlUnavailable ? "error" : frames.state;
296
293
  // RFB has one combined input switch; partial native input stays view-only.
297
294
  const rfbInputEnabled =
298
295
  !machineLocked &&
299
- !controlUnavailable &&
300
296
  computer.session?.capabilities?.pointerInput === true &&
301
297
  computer.session?.capabilities?.keyboardInput === true;
302
298
  const rfbCapability = useMemo<DesktopStreamCapability | null>(() => {
@@ -445,7 +441,6 @@ export function ComputerViewer({
445
441
 
446
442
  const perform = useCallback(
447
443
  async (action: ComputerAction, frame: ComputerFrame | null): Promise<void> => {
448
- if (computer.controlError) throw computer.controlError;
449
444
  let receipt;
450
445
  if (action.type === "pointer") {
451
446
  if (!frame) throw new Error("Desktop view is not ready for pointer input.");
@@ -457,14 +452,9 @@ export function ComputerViewer({
457
452
  throw new Error(receipt.error?.message ?? "Desktop input did not complete.");
458
453
  }
459
454
  },
460
- [act, actFromFrame, computer.controlError],
455
+ [act, actFromFrame],
461
456
  );
462
457
 
463
- const reconnect = () => {
464
- void refreshComputer();
465
- frames.reconnect();
466
- };
467
-
468
458
  const copyFromRfb = useCallback(
469
459
  (event: ClipboardEvent<HTMLDivElement>) => {
470
460
  if (!rfbInputEnabled || !rfbStream || computer.session?.capabilities?.clipboard !== true)
@@ -596,7 +586,7 @@ export function ComputerViewer({
596
586
  }}
597
587
  onCreate={hideGenericCreate ? undefined : createComputer}
598
588
  onRefresh={() => {
599
- if (connectionState === "error" && !generationLossSelected && !generationLossFrames) {
589
+ if (frames.state === "error" && !generationLossSelected && !generationLossFrames) {
600
590
  frames.reconnect();
601
591
  }
602
592
  void Promise.all([refreshRegistry(), refreshComputer()]);
@@ -654,20 +644,7 @@ export function ComputerViewer({
654
644
  />
655
645
  {!rfbInputEnabled ? (
656
646
  <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">
657
- {controlUnavailable ? (
658
- <>
659
- Desktop controls unavailable{" "}
660
- <button
661
- type="button"
662
- onClick={reconnect}
663
- className="pointer-events-auto ml-2 font-medium underline"
664
- >
665
- Reconnect
666
- </button>
667
- </>
668
- ) : (
669
- "View only · direct input unavailable"
670
- )}
647
+ View only · direct input unavailable
671
648
  </div>
672
649
  ) : null}
673
650
  </div>
@@ -683,19 +660,19 @@ export function ComputerViewer({
683
660
  observation={computer.observation}
684
661
  target={computer.selectedTarget}
685
662
  machineLocked={machineLocked}
686
- controlUnavailable={controlUnavailable}
687
- connectionState={connectionState}
688
- connectionError={computer.controlError ?? frames.error ?? computer.error}
663
+ connectionState={frames.state}
664
+ connectionError={frames.error ?? computer.error}
689
665
  mutating={computer.mutating}
690
666
  backgroundActions={computer.session?.capabilities?.backgroundActions === true}
691
- backgroundInput={computer.session?.capabilities?.backgroundInput === true}
692
667
  clipboardEnabled={computer.session?.capabilities?.clipboard === true}
693
668
  pointerInput={computer.session?.capabilities?.pointerInput === true}
694
669
  keyboardInput={computer.session?.capabilities?.keyboardInput === true}
695
670
  onAction={perform}
696
671
  onReadClipboard={computer.readClipboard}
697
672
  onReconnect={
698
- generationLossSelected || generationLossFrames ? () => undefined : reconnect
673
+ generationLossSelected || generationLossFrames
674
+ ? () => undefined
675
+ : frames.reconnect
699
676
  }
700
677
  onError={(cause) => notifyError(cause, "Desktop input failed.")}
701
678
  />
@@ -703,8 +680,7 @@ export function ComputerViewer({
703
680
  {showControls ? (
704
681
  <ComputerSemanticPanel
705
682
  observation={computer.observation}
706
- mutating={computer.mutating || controlUnavailable}
707
- controlUnavailable={controlUnavailable}
683
+ mutating={computer.mutating}
708
684
  onAction={(action) =>
709
685
  void perform(action, null).catch((cause) =>
710
686
  notifyError(cause, "Desktop action failed."),
@@ -716,11 +692,10 @@ export function ComputerViewer({
716
692
  <ComputerStatusBar
717
693
  session={computer.session}
718
694
  target={computer.selectedTarget}
719
- connectionState={connectionState}
720
- controlUnavailable={controlUnavailable}
695
+ connectionState={frames.state}
721
696
  refreshing={registry.refreshing}
722
697
  showControls={showControls}
723
- controlCount={semanticControls(computer.observation).length}
698
+ controlCount={semanticNodes(computer.observation).length}
724
699
  onToggleControls={() => setShowControls((current) => !current)}
725
700
  />
726
701
  </>
@@ -1050,12 +1025,10 @@ function ComputerViewport(props: {
1050
1025
  observation: ComputerObservation | null;
1051
1026
  target: ComputerTarget | null;
1052
1027
  machineLocked: boolean;
1053
- controlUnavailable: boolean;
1054
1028
  connectionState: string;
1055
1029
  connectionError: Error | null;
1056
1030
  mutating: boolean;
1057
1031
  backgroundActions: boolean;
1058
- backgroundInput: boolean;
1059
1032
  clipboardEnabled: boolean;
1060
1033
  pointerInput: boolean;
1061
1034
  keyboardInput: boolean;
@@ -1066,7 +1039,6 @@ function ComputerViewport(props: {
1066
1039
  }) {
1067
1040
  const canvasRef = useRef<HTMLCanvasElement | null>(null);
1068
1041
  const inputRef = useRef<HTMLTextAreaElement | null>(null);
1069
- const composingRef = useRef(false);
1070
1042
  const pointerStartRef = useRef<PointerStart | null>(null);
1071
1043
  const clickTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
1072
1044
  const lastClickRef = useRef<{
@@ -1104,12 +1076,8 @@ function ComputerViewport(props: {
1104
1076
  const streamFailed = props.connectionState === "error";
1105
1077
  const rawInputEnabled =
1106
1078
  !streamFailed &&
1107
- !props.controlUnavailable &&
1108
1079
  !props.machineLocked &&
1109
- (props.backgroundInput ||
1110
- !props.backgroundActions ||
1111
- props.target?.kind === "screen" ||
1112
- props.target?.focused === true);
1080
+ (!props.backgroundActions || props.target?.kind === "screen" || props.target?.focused === true);
1113
1081
 
1114
1082
  const clearBufferedInput = useCallback(() => {
1115
1083
  if (clickTimerRef.current) clearTimeout(clickTimerRef.current);
@@ -1120,29 +1088,12 @@ function ComputerViewport(props: {
1120
1088
  pendingTextRef.current = null;
1121
1089
  pointerStartRef.current = null;
1122
1090
  lastClickRef.current = null;
1123
- composingRef.current = false;
1124
1091
  if (inputRef.current) inputRef.current.value = "";
1125
1092
  }, []);
1126
1093
 
1127
1094
  const pointerInputEnabled = rawInputEnabled && props.pointerInput;
1128
1095
  const keyboardInputEnabled = rawInputEnabled && props.keyboardInput;
1129
1096
 
1130
- useLayoutEffect(() => {
1131
- const canvas = canvasRef.current;
1132
- const viewport = canvas?.parentElement;
1133
- if (!canvas || !viewport) return;
1134
- const fit = () => fitComputerCanvas(canvas);
1135
- fit();
1136
- if (typeof ResizeObserver === "undefined") {
1137
- window.addEventListener("resize", fit);
1138
- return () => window.removeEventListener("resize", fit);
1139
- }
1140
- // The canvas is absolute, so fitting it cannot resize the observed dock.
1141
- const observer = new ResizeObserver(fit);
1142
- observer.observe(viewport);
1143
- return () => observer.disconnect();
1144
- }, []);
1145
-
1146
1097
  const paintQueuedFrames = useCallback(() => {
1147
1098
  if (decodingFrameRef.current) return;
1148
1099
  decodingFrameRef.current = true;
@@ -1452,13 +1403,6 @@ function ComputerViewport(props: {
1452
1403
 
1453
1404
  const keyDown = (event: KeyboardEvent<HTMLTextAreaElement>) => {
1454
1405
  if (!keyboardInputEnabled) return;
1455
- if (
1456
- composingRef.current ||
1457
- event.nativeEvent.isComposing ||
1458
- event.nativeEvent.keyCode === 229
1459
- ) {
1460
- return;
1461
- }
1462
1406
  flushPendingWheel();
1463
1407
  const command = event.metaKey || event.ctrlKey;
1464
1408
  if (
@@ -1512,8 +1456,7 @@ function ComputerViewport(props: {
1512
1456
  });
1513
1457
  };
1514
1458
 
1515
- const input = (value: string, nativeComposing = false) => {
1516
- if (composingRef.current || nativeComposing) return;
1459
+ const input = (value: string) => {
1517
1460
  if (!value || !keyboardInputEnabled) return;
1518
1461
  flushPendingWheel();
1519
1462
  flushPendingClick();
@@ -1531,11 +1474,6 @@ function ComputerViewport(props: {
1531
1474
  if (inputRef.current) inputRef.current.value = "";
1532
1475
  };
1533
1476
 
1534
- const compositionEnd = (event: CompositionEvent<HTMLTextAreaElement>) => {
1535
- composingRef.current = false;
1536
- input(event.currentTarget.value || event.data);
1537
- };
1538
-
1539
1477
  const showCanvas =
1540
1478
  props.frame !== null &&
1541
1479
  paintedFrame !== null &&
@@ -1563,13 +1501,7 @@ function ComputerViewport(props: {
1563
1501
  <textarea
1564
1502
  ref={inputRef}
1565
1503
  defaultValue=""
1566
- onInput={(event) =>
1567
- input(event.currentTarget.value, (event.nativeEvent as InputEvent).isComposing)
1568
- }
1569
- onCompositionStart={() => {
1570
- composingRef.current = true;
1571
- }}
1572
- onCompositionEnd={compositionEnd}
1504
+ onInput={(event) => input(event.currentTarget.value)}
1573
1505
  onKeyDown={keyDown}
1574
1506
  onCopy={copy}
1575
1507
  onPaste={paste}
@@ -1584,7 +1516,6 @@ function ComputerViewport(props: {
1584
1516
  <ComputerViewportFallback
1585
1517
  observation={props.observation}
1586
1518
  machineLocked={props.machineLocked}
1587
- controlUnavailable={props.controlUnavailable}
1588
1519
  connectionState={props.connectionState}
1589
1520
  error={props.connectionError}
1590
1521
  onAction={(action) => enqueue(action, null)}
@@ -1632,7 +1563,6 @@ function assertExactComputerClipboard(clipboard: ComputerClipboard, expected: st
1632
1563
  function ComputerViewportFallback(props: {
1633
1564
  observation: ComputerObservation | null;
1634
1565
  machineLocked: boolean;
1635
- controlUnavailable: boolean;
1636
1566
  connectionState: string;
1637
1567
  error: Error | null;
1638
1568
  onAction: (action: ComputerAction) => void;
@@ -1652,7 +1582,7 @@ function ComputerViewportFallback(props: {
1652
1582
  );
1653
1583
  }
1654
1584
  const controlFailure = interactionControlFailureFromError(props.error);
1655
- const interactive = semanticControls(props.observation)
1585
+ const interactive = semanticNodes(props.observation)
1656
1586
  .filter((node) => semanticAction(node) !== null)
1657
1587
  .slice(0, 10);
1658
1588
  return (
@@ -1667,11 +1597,9 @@ function ComputerViewportFallback(props: {
1667
1597
  <p className="text-og-menu font-medium text-og-fg">
1668
1598
  {isAttachedChromeGenerationLossError(props.error)
1669
1599
  ? "Chrome reconnected—open a fresh browser/desktop."
1670
- : props.controlUnavailable
1671
- ? "Desktop controls unavailable"
1672
- : props.error
1673
- ? "Live view disconnected"
1674
- : computerConnectionLabel(props.connectionState)}
1600
+ : props.error
1601
+ ? "Live view disconnected"
1602
+ : computerConnectionLabel(props.connectionState)}
1675
1603
  </p>
1676
1604
  </div>
1677
1605
  {props.error ? (
@@ -1681,7 +1609,7 @@ function ComputerViewportFallback(props: {
1681
1609
  : (controlFailure?.message ?? props.error.message)}
1682
1610
  </p>
1683
1611
  ) : null}
1684
- {interactive.length > 0 && !props.controlUnavailable ? (
1612
+ {interactive.length > 0 ? (
1685
1613
  <div className="mt-3 border-t border-og-border pt-3">
1686
1614
  <p className="mb-2 text-og-xs text-og-fg-subtle">App controls remain available</p>
1687
1615
  <div className="flex flex-wrap gap-1.5">
@@ -1718,26 +1646,18 @@ function ComputerViewportFallback(props: {
1718
1646
  function ComputerSemanticPanel(props: {
1719
1647
  observation: ComputerObservation | null;
1720
1648
  mutating: boolean;
1721
- controlUnavailable: boolean;
1722
1649
  onAction: (action: ComputerAction) => void;
1723
1650
  }) {
1724
- const nodes = semanticControls(props.observation).slice(0, 100);
1651
+ const nodes = semanticNodes(props.observation).slice(0, 100);
1725
1652
  return (
1726
1653
  <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">
1727
1654
  <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">
1728
1655
  <KeyboardIcon className="size-3" /> App controls
1729
1656
  </div>
1730
- {props.controlUnavailable ? (
1657
+ {nodes.length === 0 ? (
1731
1658
  <p className="px-1 py-2 text-og-control leading-5 text-og-fg-muted">
1732
- Reconnect to use app controls.
1659
+ This view has no accessibility controls. Use the live image instead.
1733
1660
  </p>
1734
- ) : null}
1735
- {nodes.length === 0 ? (
1736
- props.controlUnavailable ? null : (
1737
- <p className="px-1 py-2 text-og-control leading-5 text-og-fg-muted">
1738
- This view has no accessibility controls. Use the live image instead.
1739
- </p>
1740
- )
1741
1661
  ) : (
1742
1662
  <div className="space-y-0.5">
1743
1663
  {nodes.map((node) => (
@@ -1827,7 +1747,6 @@ function ComputerStatusBar(props: {
1827
1747
  session: ComputerSession | null;
1828
1748
  target: ComputerTarget | null;
1829
1749
  connectionState: string;
1830
- controlUnavailable: boolean;
1831
1750
  refreshing: boolean;
1832
1751
  showControls: boolean;
1833
1752
  controlCount: number;
@@ -1843,22 +1762,14 @@ function ComputerStatusBar(props: {
1843
1762
  )}
1844
1763
  />
1845
1764
  <span>
1846
- {props.controlUnavailable
1847
- ? "Controls unavailable"
1848
- : props.connectionState === "live"
1849
- ? "Live"
1850
- : computerConnectionLabel(props.connectionState)}
1765
+ {props.connectionState === "live" ? "Live" : computerConnectionLabel(props.connectionState)}
1851
1766
  </span>
1852
1767
  <span className="min-w-0 flex-1 truncate">
1853
- {props.controlUnavailable
1854
- ? "Reconnect to use desktop input"
1855
- : screen
1856
- ? "Full screen · input may move pointer and focus"
1857
- : props.session?.capabilities?.backgroundInput
1858
- ? "Window · clicks and typing stay in the background"
1859
- : props.session?.capabilities?.backgroundActions
1860
- ? "Window · app controls work in the background"
1861
- : (props.target?.kind ?? "Desktop")}
1768
+ {screen
1769
+ ? "Full screen · input may move pointer and focus"
1770
+ : props.session?.capabilities?.backgroundActions
1771
+ ? "Window · app controls work in the background"
1772
+ : (props.target?.kind ?? "Desktop")}
1862
1773
  </span>
1863
1774
  {screen ? <MousePointer2Icon className="size-3" aria-hidden /> : null}
1864
1775
  {props.refreshing ? <LoaderCircleIcon className="size-3 animate-spin" /> : null}
@@ -1893,37 +1804,11 @@ function ComputerNotice(props: { icon: ReactNode; text: string; className?: stri
1893
1804
  );
1894
1805
  }
1895
1806
 
1896
- const structuralRoles = new Set([
1897
- "frame",
1898
- "panel",
1899
- "section",
1900
- "document",
1901
- "document web",
1902
- "static",
1903
- "static text",
1904
- "heading",
1905
- "paragraph",
1906
- "notification",
1907
- "separator",
1908
- "tool bar",
1909
- "page tab list",
1910
- ]);
1911
-
1912
- function semanticControls(observation: ComputerObservation | null): InteractionSemanticNode[] {
1807
+ function semanticNodes(observation: ComputerObservation | null): InteractionSemanticNode[] {
1913
1808
  if (observation?.semantic?.kind !== "snapshot") return [];
1914
1809
  const result: InteractionSemanticNode[] = [];
1915
1810
  const visit = (node: InteractionSemanticNode) => {
1916
- const role = node.role.trim().toLowerCase();
1917
- const label = node.name?.trim() || node.identifier?.trim();
1918
- if (
1919
- node.actions.includes("set_value") ||
1920
- (label &&
1921
- label.toLowerCase() !== role &&
1922
- !structuralRoles.has(role) &&
1923
- semanticAction(node) !== null)
1924
- ) {
1925
- result.push(node);
1926
- }
1811
+ result.push(node);
1927
1812
  node.children?.forEach(visit);
1928
1813
  };
1929
1814
  observation.semantic.roots.forEach(visit);
@@ -1955,26 +1840,11 @@ function paintCanvas(
1955
1840
  width: number,
1956
1841
  height: number,
1957
1842
  ): void {
1958
- const dimensionsChanged = canvas.width !== width || canvas.height !== height;
1959
1843
  canvas.width = width;
1960
1844
  canvas.height = height;
1961
1845
  const context = canvas.getContext("2d", { alpha: false });
1962
1846
  if (!context) throw new Error("Desktop canvas is unavailable.");
1963
1847
  context.drawImage(image, 0, 0, width, height);
1964
- if (dimensionsChanged) fitComputerCanvas(canvas);
1965
- }
1966
-
1967
- function fitComputerCanvas(canvas: HTMLCanvasElement): void {
1968
- const viewport = canvas.parentElement;
1969
- if (!viewport || canvas.width <= 0 || canvas.height <= 0) return;
1970
- const scale = Math.min(
1971
- viewport.clientWidth / canvas.width,
1972
- viewport.clientHeight / canvas.height,
1973
- );
1974
- // Hidden docks retain their previous fit until their content box is visible.
1975
- if (!Number.isFinite(scale) || scale <= 0) return;
1976
- canvas.style.width = `${canvas.width * scale}px`;
1977
- canvas.style.height = `${canvas.height * scale}px`;
1978
1848
  }
1979
1849
 
1980
1850
  function loadImage(url: string): Promise<HTMLImageElement> {
@@ -1,7 +1,6 @@
1
1
  import type { GeneratedVideoReceipt, VideoArtifactPlaybackSource } from "@opengeni/sdk";
2
2
  import { useEffect, useState } from "react";
3
3
  import { cn } from "../lib/cn";
4
- import { useErrorMessage } from "../lib/error-message";
5
4
  import type { VideoArtifactPlaybackLoader } from "../timeline";
6
5
 
7
6
  export type GeneratedVideoPlayerProps = {
@@ -23,7 +22,6 @@ export function GeneratedVideoPlayer({
23
22
  className,
24
23
  label = "Generated video",
25
24
  }: GeneratedVideoPlayerProps) {
26
- const formatError = useErrorMessage();
27
25
  const [retry, setRetry] = useState(0);
28
26
  const [state, setState] = useState<SourceState>({ kind: "loading" });
29
27
 
@@ -38,12 +36,12 @@ export function GeneratedVideoPlayer({
38
36
  if (controller.signal.aborted) return;
39
37
  setState({
40
38
  kind: "error",
41
- message: formatError(error),
39
+ message: error instanceof Error ? error.message : "Playback is unavailable.",
42
40
  });
43
41
  },
44
42
  );
45
43
  return () => controller.abort();
46
- }, [loadPlaybackSource, receipt.artifact.artifactId, retry, formatError]);
44
+ }, [loadPlaybackSource, receipt.artifact.artifactId, retry]);
47
45
 
48
46
  if (state.kind === "loading") {
49
47
  return (
@@ -8,7 +8,6 @@ import type {
8
8
  import { ChevronDownIcon, ChevronUpIcon, MessageCircleQuestionIcon } from "lucide-react";
9
9
  import { useEffect, useId, useRef, useState, type FormEvent, type ReactNode } from "react";
10
10
  import { cn } from "../lib/cn";
11
- import { useErrorMessage } from "../lib/error-message";
12
11
 
13
12
  export type HumanInputAnswerDraft = {
14
13
  values: string[];
@@ -16,8 +15,6 @@ export type HumanInputAnswerDraft = {
16
15
  otherSelected: boolean;
17
16
  };
18
17
 
19
- class SkillReviewVerificationError extends Error {}
20
-
21
18
  export type HumanInputFormMessages = {
22
19
  title: string;
23
20
  description: string;
@@ -119,7 +116,6 @@ function HumanInputRequestForm({
119
116
  defaultCollapsed = false,
120
117
  className,
121
118
  }: HumanInputFormProps) {
122
- const formatError = useErrorMessage();
123
119
  const messages = { ...defaultHumanInputFormMessages, ...messageOverrides };
124
120
  const singleQuestion = request.questions.length === 1 ? request.questions[0]! : null;
125
121
  const resolvedTitle =
@@ -188,7 +184,7 @@ function HumanInputRequestForm({
188
184
  questions.map(async (question) => {
189
185
  if (!loadSkillReview)
190
186
  throw new Error(
191
- "This client cannot preview Skill files. Ask your administrator for a review-capable client.",
187
+ "This client cannot preview Skill files. Open this request in Opengeni to review it.",
192
188
  );
193
189
  const reference = question.reference;
194
190
  const record = await loadSkillReview(reference);
@@ -198,9 +194,7 @@ function HumanInputRequestForm({
198
194
  (record.removalOperationId ?? undefined) !== reference.removalOperationId ||
199
195
  !record.files.some((file) => file.path === "SKILL.md")
200
196
  ) {
201
- throw new SkillReviewVerificationError(
202
- "The requested Skill revision could not be verified.",
203
- );
197
+ throw new Error("The requested Skill revision could not be verified.");
204
198
  }
205
199
  return [question.id, record] as const;
206
200
  }),
@@ -220,20 +214,13 @@ function HumanInputRequestForm({
220
214
  loader: loadSkillReview,
221
215
  identity: reviewIdentity,
222
216
  records: {},
223
- error: formatError(
224
- cause,
225
- cause instanceof SkillReviewVerificationError
226
- ? cause.message
227
- : !loadSkillReview
228
- ? "This client cannot preview Skill files. Ask your administrator for a review-capable client."
229
- : undefined,
230
- ),
217
+ error: cause instanceof Error ? cause.message : "Could not load the Skill files.",
231
218
  });
232
219
  });
233
220
  return () => {
234
221
  current = false;
235
222
  };
236
- }, [loadSkillReview, reviewIdentity, reviewReload, formatError]);
223
+ }, [loadSkillReview, reviewIdentity, reviewReload]);
237
224
  const visibleReviews =
238
225
  reviews?.loader === loadSkillReview && reviews?.identity === reviewIdentity ? reviews : null;
239
226
  const preview = (question: HumanInputQuestion) => {
@@ -332,7 +319,7 @@ function HumanInputRequestForm({
332
319
  await onSubmit(response);
333
320
  } catch (cause) {
334
321
  if (generation === submissionGeneration.current) {
335
- setSubmissionError(formatError(cause));
322
+ setSubmissionError(cause instanceof Error ? cause.message : String(cause));
336
323
  }
337
324
  } finally {
338
325
  if (generation === submissionGeneration.current) {
@@ -1,5 +1,4 @@
1
1
  import { RollingActivity } from "../timeline/rolling-activity";
2
- import { useErrorMessage } from "../lib/error-message";
3
2
  import { formatElapsed } from "../timeline/turn-summary";
4
3
  import { ActivityNoteTextContext } from "../timeline/activity-rail";
5
4
  import { timelineGroupContainsPresentedImage } from "../timeline/presented-image";
@@ -563,7 +562,6 @@ export function MessageTimeline({
563
562
  emptyState,
564
563
  className,
565
564
  }: MessageTimelineProps) {
566
- const formatError = useErrorMessage();
567
565
  const resolvedItems = useMemo(() => {
568
566
  const projectedItems = items ?? buildTimeline(events ?? []);
569
567
  if (!shouldRenderAuthNeeded) {
@@ -674,12 +672,12 @@ export function MessageTimeline({
674
672
  setNewerRetryPending(false);
675
673
  setNewerFailure({
676
674
  key: newerBoundaryKey,
677
- message: formatError(reason),
675
+ message: reason instanceof Error ? reason.message : String(reason),
678
676
  });
679
677
  },
680
678
  );
681
679
  },
682
- [onLoadNewer, loadingNewer, newerBoundaryKey, formatError],
680
+ [onLoadNewer, loadingNewer, newerBoundaryKey],
683
681
  );
684
682
  const previousSourceIdsRef = useRef(new Set<string>());
685
683
  const previousSourceBoundaryRef = useRef<string | undefined>(undefined);
@@ -2,7 +2,6 @@ import type { OpenGeniClient } from "@opengeni/sdk";
2
2
  import { MenuIcon, SendIcon, XIcon } from "lucide-react";
3
3
  import { useCallback, useRef, useState, type CSSProperties, type FormEvent } from "react";
4
4
  import { cn } from "../lib/cn";
5
- import { useErrorMessage } from "../lib/error-message";
6
5
  import { useOpenGeni, type ClientOverride } from "../session-context";
7
6
  import { SessionConversation, type SessionConversationProps } from "./session-conversation";
8
7
  import { SessionList, type SessionListLabels, type SessionListProps } from "./session-list";
@@ -199,8 +198,7 @@ function NewChat({
199
198
  }) {
200
199
  const [text, setText] = useState("");
201
200
  const [sending, setSending] = useState(false);
202
- const [error, setError] = useState<{ cause: unknown } | null>(null);
203
- const formatError = useErrorMessage();
201
+ const [error, setError] = useState<string | null>(null);
204
202
  // One key per draft, so a retried send returns the same chat.
205
203
  const idempotencyKey = useRef(crypto.randomUUID());
206
204
 
@@ -216,7 +214,7 @@ function NewChat({
216
214
  setText("");
217
215
  onCreated(id);
218
216
  } catch (cause) {
219
- setError({ cause });
217
+ setError(cause instanceof Error ? cause.message : String(cause));
220
218
  } finally {
221
219
  setSending(false);
222
220
  }
@@ -225,17 +223,12 @@ function NewChat({
225
223
  return (
226
224
  <form
227
225
  onSubmit={(event) => void submit(event)}
228
- className="box-border flex h-full flex-col justify-end gap-2 p-3"
226
+ className="flex h-full flex-col justify-end gap-2 p-3"
229
227
  data-og-new-chat-composer=""
230
228
  >
231
229
  {error ? (
232
230
  <p role="alert" className="text-og-xs text-og-status-failed">
233
- {formatError(
234
- error.cause,
235
- error.cause instanceof Error && error.cause.message === labels.newChatUnavailable
236
- ? labels.newChatUnavailable
237
- : undefined,
238
- )}
231
+ {error}
239
232
  </p>
240
233
  ) : null}
241
234
  <div className="flex items-end gap-2 rounded-lg border border-og-border bg-og-surface-1 p-2">
@@ -1,7 +1,6 @@
1
1
  import { Loader2Icon, RotateCwIcon } from "lucide-react";
2
2
 
3
3
  import type { UseTurnQueueResult } from "../hooks/use-turn-queue";
4
- import { useErrorMessage } from "../lib/error-message";
5
4
 
6
5
  type QueueStateProps = {
7
6
  queue: UseTurnQueueResult;
@@ -61,7 +60,6 @@ export function QueueErrorAlert({
61
60
  queue,
62
61
  dividerBefore = false,
63
62
  }: QueueStateProps & { dividerBefore?: boolean }) {
64
- const formatError = useErrorMessage();
65
63
  return (
66
64
  <div className={`${dividerBefore ? "border-t border-border" : ""} p-2`}>
67
65
  <div
@@ -76,7 +74,7 @@ export function QueueErrorAlert({
76
74
  data-testid="queue-error-message"
77
75
  dir="auto"
78
76
  >
79
- {formatError(queue.mutationError ?? queue.error)}
77
+ {(queue.mutationError ?? queue.error)?.message}
80
78
  </span>
81
79
  <button
82
80
  type="button"
@@ -73,7 +73,6 @@ import type { ComposerOptimisticMessage, ComposerState } from "../hooks/use-comp
73
73
  import type { UseGoalResult } from "../hooks/use-goal";
74
74
  import type { UseTurnQueueResult } from "../hooks/use-turn-queue";
75
75
  import { cn } from "../lib/cn";
76
- import { useErrorMessage } from "../lib/error-message";
77
76
  import { formatClockTime } from "../lib/format";
78
77
  import { requestQueueDraftEdit } from "./queue-draft-policy";
79
78
  import { QUEUE_ITEM_CONTENT_UNAVAILABLE, queueItemContent } from "./queue-item-content";
@@ -454,7 +453,6 @@ export function SessionChrome({
454
453
  defaultActive = null,
455
454
  onActiveChange,
456
455
  }: SessionChromeProps) {
457
- const formatError = useErrorMessage();
458
456
  const [activityRequested, setActivityOpen] = useState(
459
457
  Boolean(defaultActive && ["incoming", "agents", "commands"].includes(defaultActive)),
460
458
  );
@@ -1194,7 +1192,7 @@ export function SessionChrome({
1194
1192
  </AnimatePresence>
1195
1193
  {goal?.mutationError ? (
1196
1194
  <p role="alert" className="px-3 pb-2 text-og-xs text-og-danger">
1197
- Goal action not confirmed. {formatError(goal.mutationError)}
1195
+ Goal action not confirmed. {goal.mutationError.message}
1198
1196
  </p>
1199
1197
  ) : null}
1200
1198
  <div
@@ -1669,7 +1667,6 @@ function GoalPanel({
1669
1667
  elapsed: string | null;
1670
1668
  readOnly: boolean;
1671
1669
  }) {
1672
- const formatError = useErrorMessage();
1673
1670
  const record = goal.goal;
1674
1671
  if (!record) return null;
1675
1672
  const canToggle = !readOnly && (record.status === "active" || record.status === "paused");
@@ -1702,7 +1699,7 @@ function GoalPanel({
1702
1699
  ) : null}
1703
1700
  {record.continuation?.lastError ? (
1704
1701
  <p className="rounded-og-sm bg-og-status-waiting/10 px-1.5 py-1 text-og-xs leading-4 text-og-status-waiting">
1705
- {formatError(record.continuation.lastError)}
1702
+ {record.continuation.lastError}
1706
1703
  </p>
1707
1704
  ) : null}
1708
1705
  <div className="flex flex-wrap items-center justify-between gap-1.5 pt-0.5">