@opengeni/react 0.48.0 → 0.52.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (284) hide show
  1. package/README.md +90 -2
  2. package/dist/artifacts-document.d.ts +3 -0
  3. package/dist/artifacts-document.js +17 -0
  4. package/dist/artifacts-document.js.map +1 -0
  5. package/dist/artifacts-presentation.d.ts +3 -0
  6. package/dist/artifacts-presentation.js +17 -0
  7. package/dist/artifacts-presentation.js.map +1 -0
  8. package/dist/artifacts-spreadsheet.d.ts +3 -0
  9. package/dist/artifacts-spreadsheet.js +17 -0
  10. package/dist/artifacts-spreadsheet.js.map +1 -0
  11. package/dist/artifacts.d.ts +2 -0
  12. package/dist/artifacts.js +170 -0
  13. package/dist/artifacts.js.map +1 -0
  14. package/dist/chunk-6BBWWOOU.js +82 -0
  15. package/dist/chunk-6BBWWOOU.js.map +1 -0
  16. package/dist/{chunk-FWXGSNXT.js → chunk-724BW4TW.js} +585 -34
  17. package/dist/chunk-724BW4TW.js.map +1 -0
  18. package/dist/chunk-7I2KB3OK.js +3895 -0
  19. package/dist/chunk-7I2KB3OK.js.map +1 -0
  20. package/dist/chunk-7WCLAEFV.js +2828 -0
  21. package/dist/chunk-7WCLAEFV.js.map +1 -0
  22. package/dist/{chunk-I4MPFDRP.js → chunk-FWS3KC3Z.js} +3 -3
  23. package/dist/{chunk-EKZH6IDG.js → chunk-HDBG7XGO.js} +62 -1
  24. package/dist/chunk-HDBG7XGO.js.map +1 -0
  25. package/dist/chunk-HUIKIDM5.js +2148 -0
  26. package/dist/chunk-HUIKIDM5.js.map +1 -0
  27. package/dist/{chunk-XCNTXWBB.js → chunk-JAGDUCZQ.js} +854 -340
  28. package/dist/chunk-JAGDUCZQ.js.map +1 -0
  29. package/dist/{chunk-LHLYWG5R.js → chunk-JR3QFY6B.js} +413 -37
  30. package/dist/chunk-JR3QFY6B.js.map +1 -0
  31. package/dist/chunk-JVYQRRC4.js +53 -0
  32. package/dist/chunk-JVYQRRC4.js.map +1 -0
  33. package/dist/chunk-KCGXPP5V.js +157 -0
  34. package/dist/chunk-KCGXPP5V.js.map +1 -0
  35. package/dist/chunk-PEI6RANI.js +3234 -0
  36. package/dist/chunk-PEI6RANI.js.map +1 -0
  37. package/dist/{chunk-NJ7M2B6Z.js → chunk-ROIRTM37.js} +18 -10
  38. package/dist/{chunk-NJ7M2B6Z.js.map → chunk-ROIRTM37.js.map} +1 -1
  39. package/dist/{chunk-O4VXOBGF.js → chunk-WSK7G5LE.js} +70 -2
  40. package/dist/chunk-WSK7G5LE.js.map +1 -0
  41. package/dist/{chunk-OL6QVXGF.js → chunk-X4S56Y2H.js} +27 -7
  42. package/dist/{chunk-OL6QVXGF.js.map → chunk-X4S56Y2H.js.map} +1 -1
  43. package/dist/client.d.ts +7 -1
  44. package/dist/clipboard.d.ts +1 -0
  45. package/dist/clipboard.js +7 -0
  46. package/dist/clipboard.js.map +1 -0
  47. package/dist/commands/index.d.ts +3 -3
  48. package/dist/commands/registry.d.ts +1 -1
  49. package/dist/commands/types.d.ts +1 -1
  50. package/dist/components/approval-surface.d.ts +2 -2
  51. package/dist/components/artifacts/artifact-surface.d.ts +18 -0
  52. package/dist/components/artifacts/document-editor.d.ts +172 -0
  53. package/dist/components/artifacts/editable-artifact-ui.d.ts +13 -0
  54. package/dist/components/artifacts/editable-artifact-workbench.d.ts +49 -0
  55. package/dist/components/artifacts/editable-document.d.ts +24 -0
  56. package/dist/components/artifacts/editable-presentation.d.ts +24 -0
  57. package/dist/components/artifacts/editable-spreadsheet.d.ts +32 -0
  58. package/dist/components/artifacts/index.d.ts +8 -0
  59. package/dist/components/artifacts/presentation-editor.d.ts +297 -0
  60. package/dist/components/artifacts/spreadsheet-canvas.d.ts +120 -0
  61. package/dist/components/artifacts/spreadsheet-grid.d.ts +113 -0
  62. package/dist/components/browser-viewer.d.ts +29 -0
  63. package/dist/components/chat-composer.d.ts +8 -8
  64. package/dist/components/code-editor.d.ts +25 -3
  65. package/dist/components/command-palette.d.ts +2 -2
  66. package/dist/components/composer-transcription-control.d.ts +2 -2
  67. package/dist/components/composer.d.ts +17 -17
  68. package/dist/components/computer-viewer.d.ts +23 -0
  69. package/dist/components/copy-button.d.ts +2 -2
  70. package/dist/components/desktop-viewer.d.ts +25 -21
  71. package/dist/components/diff-view.d.ts +1 -1
  72. package/dist/components/enrollment-consent.d.ts +1 -1
  73. package/dist/components/enrollment-device-flow.d.ts +1 -1
  74. package/dist/components/file-browser.d.ts +2 -2
  75. package/dist/components/fleet-tile.d.ts +1 -1
  76. package/dist/components/generated-video-player.d.ts +10 -0
  77. package/dist/components/human-input-form.d.ts +1 -1
  78. package/dist/components/human-input-surface.d.ts +2 -2
  79. package/dist/components/interactive-workbench-viewers.d.ts +2 -0
  80. package/dist/components/machine-card.d.ts +2 -2
  81. package/dist/components/machine-dock-bar.d.ts +3 -3
  82. package/dist/components/machine-health-pill.d.ts +2 -2
  83. package/dist/components/machine-input-display.d.ts +1 -1
  84. package/dist/components/machine-metrics.d.ts +2 -2
  85. package/dist/components/machine-status-pill.d.ts +4 -4
  86. package/dist/components/machines/health.d.ts +1 -1
  87. package/dist/components/machines/machine-detail.d.ts +3 -3
  88. package/dist/components/machines/metric-history-chart.d.ts +1 -1
  89. package/dist/components/machines/metric-sparkline.d.ts +2 -2
  90. package/dist/components/machines/series.d.ts +2 -2
  91. package/dist/components/machines-dashboard.d.ts +2 -2
  92. package/dist/components/markdown.d.ts +1 -1
  93. package/dist/components/message-timeline.d.ts +12 -5
  94. package/dist/components/model-picker.d.ts +2 -2
  95. package/dist/components/model-policy-picker.d.ts +7 -7
  96. package/dist/components/pierre-diff.d.ts +1 -1
  97. package/dist/components/pierre-file.d.ts +1 -1
  98. package/dist/components/queue-draft-policy.d.ts +1 -1
  99. package/dist/components/queue-surface-implementation.d.ts +3 -3
  100. package/dist/components/queue-surface-state.d.ts +4 -4
  101. package/dist/components/queue-surface.d.ts +5 -5
  102. package/dist/components/sandbox-files.d.ts +3 -3
  103. package/dist/components/sandbox-terminal.d.ts +21 -4
  104. package/dist/components/sandbox-workspace.d.ts +25 -9
  105. package/dist/components/session-chrome.d.ts +4 -4
  106. package/dist/components/session-status.d.ts +2 -2
  107. package/dist/components/timeline-annotation-selection.d.ts +9 -0
  108. package/dist/components/timeline-annotations-dialog.d.ts +16 -0
  109. package/dist/components/timeline-annotations.d.ts +13 -0
  110. package/dist/components/tooltip.d.ts +4 -4
  111. package/dist/components/user-message-body.d.ts +2 -2
  112. package/dist/components/workbench-changes.d.ts +2 -2
  113. package/dist/components/workspace-dock.d.ts +1 -1
  114. package/dist/composer.d.ts +11 -11
  115. package/dist/composer.js +5 -5
  116. package/dist/hooks/internal.d.ts +1 -1
  117. package/dist/hooks/use-attached-browsers.d.ts +21 -0
  118. package/dist/hooks/use-available-models.d.ts +2 -2
  119. package/dist/hooks/use-billing-usage.d.ts +1 -1
  120. package/dist/hooks/use-browser-frame-stream.d.ts +28 -0
  121. package/dist/hooks/use-browser-identities.d.ts +26 -0
  122. package/dist/hooks/use-browser-session.d.ts +28 -0
  123. package/dist/hooks/use-browser-sessions.d.ts +32 -0
  124. package/dist/hooks/use-codex-accounts.d.ts +2 -2
  125. package/dist/hooks/use-composer.d.ts +36 -9
  126. package/dist/hooks/use-computer-frame-stream.d.ts +24 -0
  127. package/dist/hooks/use-computer-session.d.ts +28 -0
  128. package/dist/hooks/use-computer-sessions.d.ts +28 -0
  129. package/dist/hooks/use-desktop-stream.d.ts +1 -1
  130. package/dist/hooks/use-environments.d.ts +1 -1
  131. package/dist/hooks/use-file-attachments.d.ts +1 -1
  132. package/dist/hooks/use-goal.d.ts +2 -2
  133. package/dist/hooks/use-human-input.d.ts +2 -2
  134. package/dist/hooks/use-last-started-turn-policy.d.ts +2 -2
  135. package/dist/hooks/use-machine-chip.d.ts +2 -2
  136. package/dist/hooks/use-machines.d.ts +4 -4
  137. package/dist/hooks/use-packs.d.ts +7 -2
  138. package/dist/hooks/use-rigs.d.ts +1 -1
  139. package/dist/hooks/use-sandbox-files.d.ts +6 -4
  140. package/dist/hooks/use-sandbox-git.d.ts +6 -5
  141. package/dist/hooks/use-sandbox-terminal.d.ts +1 -1
  142. package/dist/hooks/use-scheduled-tasks.d.ts +1 -1
  143. package/dist/hooks/use-session-capabilities.d.ts +5 -4
  144. package/dist/hooks/use-session-control.d.ts +1 -1
  145. package/dist/hooks/use-session-events.d.ts +2 -2
  146. package/dist/hooks/use-session-lineage.d.ts +1 -1
  147. package/dist/hooks/use-session-mcp-approval-policy.d.ts +2 -2
  148. package/dist/hooks/use-session.d.ts +2 -2
  149. package/dist/hooks/use-slash-commands.d.ts +1 -1
  150. package/dist/hooks/use-terminal-stream.d.ts +8 -2
  151. package/dist/hooks/use-turn-queue.d.ts +2 -2
  152. package/dist/hooks/use-video-artifact-playback.d.ts +9 -0
  153. package/dist/hooks/use-voice-input.d.ts +1 -1
  154. package/dist/hooks/use-workspace-capture.d.ts +1 -1
  155. package/dist/hooks/use-workspace-edit.d.ts +1 -1
  156. package/dist/hooks/use-workspace-sessions.d.ts +1 -1
  157. package/dist/hooks/use-workspaces.d.ts +1 -1
  158. package/dist/index.d.ts +158 -155
  159. package/dist/index.js +906 -462
  160. package/dist/index.js.map +1 -1
  161. package/dist/interaction.d.ts +23 -0
  162. package/dist/interaction.js +36 -0
  163. package/dist/interaction.js.map +1 -0
  164. package/dist/interactive-workbench-viewers-KHNOHWQE.js +13 -0
  165. package/dist/interactive-workbench-viewers-KHNOHWQE.js.map +1 -0
  166. package/dist/lib/relay-wire.d.ts +4 -0
  167. package/dist/lib/sandbox-liveness.d.ts +9 -0
  168. package/dist/lib/terminal-capability.d.ts +5 -0
  169. package/dist/lib/xterm-theme.d.ts +1 -1
  170. package/dist/machines.d.ts +30 -30
  171. package/dist/machines.js +3 -3
  172. package/dist/provider.d.ts +4 -4
  173. package/dist/{queue-surface-implementation-U5JFG7HT.js → queue-surface-implementation-2KVJTYXH.js} +100 -48
  174. package/dist/queue-surface-implementation-2KVJTYXH.js.map +1 -0
  175. package/dist/realtime/dropdown-menu.d.ts +8 -8
  176. package/dist/realtime/realtime-control.d.ts +6 -6
  177. package/dist/realtime.d.ts +4 -4
  178. package/dist/realtime.js +3 -3
  179. package/dist/session-context.d.ts +19 -1
  180. package/dist/session-ui.d.ts +14 -14
  181. package/dist/session-ui.js +5 -4
  182. package/dist/session.d.ts +28 -28
  183. package/dist/session.js +5 -5
  184. package/dist/timeline/activity-rail.d.ts +5 -3
  185. package/dist/timeline/disclosure-context.d.ts +1 -1
  186. package/dist/timeline/fleet-decision-projection.d.ts +1 -1
  187. package/dist/timeline/fleet-decision-row.d.ts +3 -3
  188. package/dist/timeline/index.d.ts +19 -19
  189. package/dist/timeline/parsers.d.ts +4 -1
  190. package/dist/timeline/projection.d.ts +3 -3
  191. package/dist/timeline/registry.d.ts +8 -2
  192. package/dist/timeline/screenshot-lightbox.d.ts +2 -2
  193. package/dist/timeline/shared.d.ts +21 -10
  194. package/dist/timeline/tool-diff.d.ts +2 -2
  195. package/dist/timeline/tool-renderers.d.ts +1 -1
  196. package/dist/timeline/turn-summary.d.ts +3 -3
  197. package/dist/timeline/types.d.ts +39 -3
  198. package/dist/timeline-annotation-selection-3L7LHGG2.js +187 -0
  199. package/dist/timeline-annotation-selection-3L7LHGG2.js.map +1 -0
  200. package/dist/timeline-annotations-dialog-U7EVLR75.js +196 -0
  201. package/dist/timeline-annotations-dialog-U7EVLR75.js.map +1 -0
  202. package/package.json +36 -7
  203. package/src/artifacts-document.ts +31 -0
  204. package/src/artifacts-presentation.ts +36 -0
  205. package/src/artifacts-spreadsheet.ts +21 -0
  206. package/src/artifacts.ts +86 -0
  207. package/src/client.ts +87 -1
  208. package/src/clipboard.ts +2 -0
  209. package/src/components/artifacts/artifact-surface.tsx +93 -0
  210. package/src/components/artifacts/document-editor.tsx +2255 -0
  211. package/src/components/artifacts/editable-artifact-ui.tsx +89 -0
  212. package/src/components/artifacts/editable-artifact-workbench.tsx +203 -0
  213. package/src/components/artifacts/editable-document.tsx +657 -0
  214. package/src/components/artifacts/editable-presentation.tsx +1020 -0
  215. package/src/components/artifacts/editable-spreadsheet.tsx +733 -0
  216. package/src/components/artifacts/index.ts +99 -0
  217. package/src/components/artifacts/presentation-editor.tsx +3148 -0
  218. package/src/components/artifacts/spreadsheet-canvas.ts +799 -0
  219. package/src/components/artifacts/spreadsheet-grid.tsx +2129 -0
  220. package/src/components/browser-viewer.tsx +1592 -0
  221. package/src/components/chat-composer.tsx +13 -0
  222. package/src/components/code-editor.tsx +89 -30
  223. package/src/components/composer-transcription-control.tsx +10 -6
  224. package/src/components/computer-viewer.tsx +1139 -0
  225. package/src/components/desktop-viewer.tsx +101 -73
  226. package/src/components/generated-video-player.tsx +89 -0
  227. package/src/components/interactive-workbench-viewers.ts +2 -0
  228. package/src/components/machine-input-display.ts +3 -0
  229. package/src/components/message-timeline.tsx +289 -32
  230. package/src/components/queue-surface-implementation.tsx +116 -60
  231. package/src/components/sandbox-files.tsx +2 -2
  232. package/src/components/sandbox-terminal.tsx +106 -41
  233. package/src/components/sandbox-workspace.tsx +215 -87
  234. package/src/components/session-chrome.tsx +53 -13
  235. package/src/components/timeline-annotation-selection.tsx +221 -0
  236. package/src/components/timeline-annotations-dialog.tsx +213 -0
  237. package/src/components/timeline-annotations.tsx +84 -0
  238. package/src/components/workbench-changes.tsx +188 -31
  239. package/src/hooks/use-attached-browsers.ts +152 -0
  240. package/src/hooks/use-browser-frame-stream.ts +316 -0
  241. package/src/hooks/use-browser-identities.ts +225 -0
  242. package/src/hooks/use-browser-session.ts +476 -0
  243. package/src/hooks/use-browser-sessions.ts +281 -0
  244. package/src/hooks/use-composer.ts +769 -24
  245. package/src/hooks/use-computer-frame-stream.ts +311 -0
  246. package/src/hooks/use-computer-session.ts +442 -0
  247. package/src/hooks/use-computer-sessions.ts +245 -0
  248. package/src/hooks/use-machine-chip.ts +5 -3
  249. package/src/hooks/use-machines.ts +33 -3
  250. package/src/hooks/use-packs.ts +51 -0
  251. package/src/hooks/use-sandbox-files.ts +98 -55
  252. package/src/hooks/use-sandbox-git.ts +93 -38
  253. package/src/hooks/use-sandbox-terminal.ts +3 -2
  254. package/src/hooks/use-session-capabilities.ts +59 -46
  255. package/src/hooks/use-terminal-stream.ts +102 -23
  256. package/src/hooks/use-video-artifact-playback.ts +28 -0
  257. package/src/hooks/use-workspace-edit.ts +2 -1
  258. package/src/index.ts +10 -0
  259. package/src/interaction.ts +71 -0
  260. package/src/lib/relay-wire.ts +4 -0
  261. package/src/lib/sandbox-liveness.ts +11 -0
  262. package/src/lib/terminal-capability.ts +10 -0
  263. package/src/session-context.ts +90 -0
  264. package/src/timeline/activity-rail.tsx +19 -3
  265. package/src/timeline/index.ts +3 -0
  266. package/src/timeline/parsers.ts +14 -1
  267. package/src/timeline/projection.ts +211 -14
  268. package/src/timeline/registry.ts +13 -1
  269. package/src/timeline/screenshot-lightbox.tsx +19 -9
  270. package/src/timeline/shared.tsx +56 -8
  271. package/src/timeline/tool-renderers.tsx +303 -50
  272. package/src/timeline/types.ts +55 -3
  273. package/styles/compiled.css +248 -15
  274. package/styles/index.css +15 -0
  275. package/styles/responsive.css +13 -1
  276. package/dist/chunk-EKZH6IDG.js.map +0 -1
  277. package/dist/chunk-FWXGSNXT.js.map +0 -1
  278. package/dist/chunk-LHLYWG5R.js.map +0 -1
  279. package/dist/chunk-O4VXOBGF.js.map +0 -1
  280. package/dist/chunk-XCNTXWBB.js.map +0 -1
  281. package/dist/fleet-decision-projection-SLNZMS42.js +0 -241
  282. package/dist/fleet-decision-projection-SLNZMS42.js.map +0 -1
  283. package/dist/queue-surface-implementation-U5JFG7HT.js.map +0 -1
  284. /package/dist/{chunk-I4MPFDRP.js.map → chunk-FWS3KC3Z.js.map} +0 -0
@@ -16,7 +16,7 @@ import {
16
16
  verticalListSortingStrategy,
17
17
  } from "@dnd-kit/sortable";
18
18
  import { CSS } from "@dnd-kit/utilities";
19
- import type { SessionPendingInputPreview, SessionTurn } from "@opengeni/sdk";
19
+ import type { SessionPendingInputPreview, SessionTurn, TimelineAnnotation } from "@opengeni/sdk";
20
20
  import {
21
21
  ArrowDownToLineIcon,
22
22
  ArrowUpToLineIcon,
@@ -46,6 +46,7 @@ import {
46
46
  } from "../lib/use-portal-token-style";
47
47
  import { requestQueueDraftEdit } from "./queue-draft-policy";
48
48
  import { QueueErrorAlert, QueueStoppingStatus } from "./queue-surface-state";
49
+ import { TimelineAnnotationsChip } from "./timeline-annotations";
49
50
 
50
51
  /** The sole pending-input surface: compact above Goal, Agents, and composer. */
51
52
  type QueueSurfaceCommonProps = {
@@ -99,14 +100,15 @@ export function QueueSurface({
99
100
  (input) => !attachedInputIds.includes(input.id),
100
101
  );
101
102
  const canEditInComposer = queueComposerCheckoutEnabled(composer, readOnly);
102
- const collapsedPreview = useMemo(
103
- () =>
104
- queuePromptPreview(
105
- queue.queue[0]?.prompt ?? queue.pendingInputs[0]?.summary ?? "",
106
- QUEUE_COLLAPSED_PREVIEW_CHARACTERS,
107
- ),
108
- [queue.pendingInputs, queue.queue],
109
- );
103
+ const collapsedPreview = useMemo(() => {
104
+ const firstTurn = queue.queue[0];
105
+ return firstTurn
106
+ ? queueTurnPreview(firstTurn, QUEUE_COLLAPSED_PREVIEW_CHARACTERS)
107
+ : queuePromptPreview(
108
+ queue.pendingInputs[0]?.summary ?? "",
109
+ QUEUE_COLLAPSED_PREVIEW_CHARACTERS,
110
+ );
111
+ }, [queue.pendingInputs, queue.queue]);
110
112
  const sensors = useSensors(useSensor(PointerSensor, { activationConstraint: { distance: 6 } }));
111
113
 
112
114
  const displayedQueue = useMemo(() => {
@@ -409,8 +411,8 @@ export function QueueSurface({
409
411
  dir="auto"
410
412
  >
411
413
  {
412
- queuePromptPreview(
413
- queue.queue.find((turn) => turn.id === draggedTurnId)?.prompt ?? "",
414
+ queueTurnPreview(
415
+ queue.queue.find((turn) => turn.id === draggedTurnId),
414
416
  QUEUE_ROW_PREVIEW_CHARACTERS,
415
417
  ).summary
416
418
  }
@@ -490,6 +492,29 @@ type QueuePromptPreview = {
490
492
  isFallback: boolean;
491
493
  };
492
494
 
495
+ function annotationOnlyQueuePreview(count: number): QueuePromptPreview {
496
+ const summary = `${count} timeline ${count === 1 ? "annotation" : "annotations"}`;
497
+ return {
498
+ summary,
499
+ collapsedVisual: summary,
500
+ visibleStart: summary,
501
+ visibleIdentity: null,
502
+ visibleIdentityLabel: null,
503
+ isFallback: false,
504
+ };
505
+ }
506
+
507
+ function queueTurnPreview(
508
+ turn: SessionTurn | undefined,
509
+ maxCharacters: number,
510
+ ): QueuePromptPreview {
511
+ if (!turn) return queuePromptPreview("", maxCharacters);
512
+ const annotations = turn.annotations ?? [];
513
+ return turn.prompt.length === 0 && annotations.length > 0
514
+ ? annotationOnlyQueuePreview(annotations.length)
515
+ : queuePromptPreview(turn.prompt, maxCharacters);
516
+ }
517
+
493
518
  /**
494
519
  * Build a bounded head/tail summary of an arbitrary queued prompt. Sampling
495
520
  * both ends distinguishes prompts with equal long prefixes. Each sampled edge
@@ -752,73 +777,94 @@ function isLowSurrogate(codeUnit: number): boolean {
752
777
 
753
778
  function QueuePrompt({
754
779
  prompt,
780
+ annotations,
755
781
  index,
756
782
  onDisclosureChange,
757
783
  }: {
758
784
  prompt: string;
785
+ annotations: readonly TimelineAnnotation[];
759
786
  index: number;
760
787
  onDisclosureChange: (expanded: boolean) => void;
761
788
  }) {
762
789
  const [expanded, setExpanded] = useState(false);
763
790
  const fullContentId = useId();
764
- const preview = useMemo(() => queuePromptPreview(prompt, QUEUE_ROW_PREVIEW_CHARACTERS), [prompt]);
791
+ const preview = useMemo(
792
+ () =>
793
+ prompt.length === 0 && annotations.length > 0
794
+ ? annotationOnlyQueuePreview(annotations.length)
795
+ : queuePromptPreview(prompt, QUEUE_ROW_PREVIEW_CHARACTERS),
796
+ [annotations.length, prompt],
797
+ );
765
798
 
766
799
  return (
767
800
  <div className="w-full min-w-0 max-w-full">
768
- <div
769
- aria-label={`Queued prompt ${index + 1} summary: ${preview.summary}`}
770
- className="max-w-full overflow-hidden text-og-control leading-5 text-fg"
771
- data-testid={`queue-prompt-preview-${index + 1}`}
772
- dir="auto"
773
- role="note"
774
- >
775
- <span aria-hidden="true" className="flex min-w-0 max-w-full items-baseline gap-2 sm:block">
801
+ {prompt ? (
802
+ <div
803
+ aria-label={`Queued prompt ${index + 1} summary: ${preview.summary}`}
804
+ className="max-w-full overflow-hidden text-og-control leading-5 text-fg"
805
+ data-testid={`queue-prompt-preview-${index + 1}`}
806
+ dir="auto"
807
+ role="note"
808
+ >
776
809
  <span
777
- className={`${preview.isFallback ? "" : "line-clamp-1 sm:line-clamp-2"} min-w-0 flex-1 whitespace-pre-wrap break-all [unicode-bidi:plaintext]`}
778
- data-testid={`queue-prompt-start-${index + 1}`}
779
- dir="auto"
810
+ aria-hidden="true"
811
+ className="flex min-w-0 max-w-full items-baseline gap-2 sm:block"
780
812
  >
781
- {preview.visibleStart}
782
- </span>
783
- {preview.visibleIdentity && preview.visibleIdentityLabel ? (
784
813
  <span
785
- className="flex min-w-0 max-w-[70%] shrink-0 items-center gap-1 text-og-xs leading-4 text-fg-muted sm:mt-0.5 sm:max-w-full"
786
- data-testid={`queue-prompt-identity-row-${index + 1}`}
814
+ className={`${preview.isFallback ? "" : "line-clamp-1 sm:line-clamp-2"} min-w-0 flex-1 whitespace-pre-wrap break-all [unicode-bidi:plaintext]`}
815
+ data-testid={`queue-prompt-start-${index + 1}`}
816
+ dir="auto"
787
817
  >
788
- <span className="shrink-0 font-medium text-fg-subtle">
789
- {preview.visibleIdentityLabel}
790
- </span>
818
+ {preview.visibleStart}
819
+ </span>
820
+ {preview.visibleIdentity && preview.visibleIdentityLabel ? (
791
821
  <span
792
- className="min-w-0 overflow-hidden text-ellipsis whitespace-nowrap font-mono [unicode-bidi:plaintext]"
793
- data-testid={`queue-prompt-identity-${index + 1}`}
794
- dir="auto"
822
+ className="flex min-w-0 max-w-[70%] shrink-0 items-center gap-1 text-og-xs leading-4 text-fg-muted sm:mt-0.5 sm:max-w-full"
823
+ data-testid={`queue-prompt-identity-row-${index + 1}`}
795
824
  >
796
- {preview.visibleIdentity}
825
+ <span className="shrink-0 font-medium text-fg-subtle">
826
+ {preview.visibleIdentityLabel}
827
+ </span>
828
+ <span
829
+ className="min-w-0 overflow-hidden text-ellipsis whitespace-nowrap font-mono [unicode-bidi:plaintext]"
830
+ data-testid={`queue-prompt-identity-${index + 1}`}
831
+ dir="auto"
832
+ >
833
+ {preview.visibleIdentity}
834
+ </span>
797
835
  </span>
798
- </span>
799
- ) : null}
800
- </span>
801
- </div>
802
- <button
803
- type="button"
804
- aria-controls={fullContentId}
805
- aria-expanded={expanded}
806
- aria-label={`${expanded ? "Hide" : "Show"} full content for queued prompt ${index + 1}`}
807
- className="mt-1 inline-flex min-h-7 min-w-0 max-w-full items-center gap-1 whitespace-normal rounded-md text-left text-og-xs font-medium text-fg outline-hidden focus-visible:ring-2 focus-visible:ring-ring/40 pointer-coarse:min-h-[44px]"
808
- data-testid={`queue-prompt-disclosure-${index + 1}`}
809
- onClick={() => {
810
- const next = !expanded;
811
- setExpanded(next);
812
- onDisclosureChange(next);
813
- }}
814
- >
815
- <ChevronDownIcon
816
- aria-hidden="true"
817
- className={`size-3 shrink-0 transition-transform ${expanded ? "rotate-180" : ""}`}
836
+ ) : null}
837
+ </span>
838
+ </div>
839
+ ) : null}
840
+ {annotations.length > 0 ? (
841
+ <TimelineAnnotationsChip
842
+ annotations={annotations}
843
+ className={prompt ? "mt-1" : undefined}
818
844
  />
819
- {expanded ? "Hide full prompt" : "View full prompt"}
820
- </button>
821
- {expanded ? (
845
+ ) : null}
846
+ {prompt ? (
847
+ <button
848
+ type="button"
849
+ aria-controls={fullContentId}
850
+ aria-expanded={expanded}
851
+ aria-label={`${expanded ? "Hide" : "Show"} full content for queued prompt ${index + 1}`}
852
+ className="mt-1 inline-flex min-h-7 min-w-0 max-w-full items-center gap-1 whitespace-normal rounded-md text-left text-og-xs font-medium text-fg outline-hidden focus-visible:ring-2 focus-visible:ring-ring/40 pointer-coarse:min-h-[44px]"
853
+ data-testid={`queue-prompt-disclosure-${index + 1}`}
854
+ onClick={() => {
855
+ const next = !expanded;
856
+ setExpanded(next);
857
+ onDisclosureChange(next);
858
+ }}
859
+ >
860
+ <ChevronDownIcon
861
+ aria-hidden="true"
862
+ className={`size-3 shrink-0 transition-transform ${expanded ? "rotate-180" : ""}`}
863
+ />
864
+ {expanded ? "Hide full prompt" : "View full prompt"}
865
+ </button>
866
+ ) : null}
867
+ {expanded && prompt ? (
822
868
  <pre
823
869
  id={fullContentId}
824
870
  role="region"
@@ -850,7 +896,12 @@ function ReadOnlyQueueRow({
850
896
  <li className="flex min-w-0 items-start gap-2 bg-surface px-3 py-2">
851
897
  <span className="mt-1 shrink-0 font-mono text-og-xs text-fg-subtle">{index + 1}</span>
852
898
  <div className="min-w-0 flex-1">
853
- <QueuePrompt prompt={turn.prompt} index={index} onDisclosureChange={onDisclosureChange} />
899
+ <QueuePrompt
900
+ prompt={turn.prompt}
901
+ annotations={turn.annotations ?? []}
902
+ index={index}
903
+ onDisclosureChange={onDisclosureChange}
904
+ />
854
905
  {attachedInputs.length > 0 ? (
855
906
  <PendingMachineInputs inputs={attachedInputs} attached />
856
907
  ) : null}
@@ -941,7 +992,12 @@ function SortableQueueRow({
941
992
  {index + 1}
942
993
  </span>
943
994
  <div className="col-span-full row-start-2 min-w-0 sm:col-span-1 sm:col-start-3 sm:row-start-1">
944
- <QueuePrompt prompt={turn.prompt} index={index} onDisclosureChange={onDisclosureChange} />
995
+ <QueuePrompt
996
+ prompt={turn.prompt}
997
+ annotations={turn.annotations ?? []}
998
+ index={index}
999
+ onDisclosureChange={onDisclosureChange}
1000
+ />
945
1001
  {attachedInputs.length > 0 ? (
946
1002
  <PendingMachineInputs inputs={attachedInputs} attached />
947
1003
  ) : null}
@@ -319,8 +319,8 @@ export function SandboxFiles({
319
319
  path={viewPath}
320
320
  initialContents={fileView.content}
321
321
  themeType={resolvedTheme}
322
- onSave={(contents) =>
323
- files.writeFile(viewPath, contents, { expectedContent: fileView.content! })
322
+ onSave={(contents, expectedContents) =>
323
+ files.writeFile(viewPath, contents, { expectedContent: expectedContents })
324
324
  }
325
325
  onOverwrite={(contents) => files.writeFile(viewPath, contents, { force: true })}
326
326
  onReload={() => setViewReloadRevision((revision) => revision + 1)}
@@ -1,9 +1,11 @@
1
1
  import type { TerminalCapability } from "@opengeni/sdk";
2
2
  import { type ReactNode, useEffect, useRef, useState } from "react";
3
3
  import { cn } from "../lib/cn";
4
- import { useTerminalStream } from "../hooks/use-terminal-stream";
4
+ import { type TerminalStreamStatus, useTerminalStream } from "../hooks/use-terminal-stream";
5
5
  import type { UseSandboxTerminalResult } from "../hooks/use-sandbox-terminal";
6
6
  import { resolveTerminalFont, xtermThemeFromTokens } from "../lib/xterm-theme";
7
+ import { sandboxAcceptsLiveIo } from "../lib/sandbox-liveness";
8
+ import { terminalCanAcquirePty } from "../lib/terminal-capability";
7
9
  import { attachRenderer, type RendererLoaders, type RendererTier } from "../lib/xterm-renderer";
8
10
 
9
11
  /**
@@ -60,7 +62,7 @@ export type SandboxTerminalProps = {
60
62
  fontSize?: number | undefined;
61
63
  /**
62
64
  * Lease liveness (`cold` | `warming` | `warm` | `draining`). Drives the
63
- * boot-in-terminal status lines: once the user has focused the terminal and
65
+ * boot-in-terminal status lines: once the user has engaged the terminal and
64
66
  * the box is not yet warm, a styled "● waking machine — Ns…" line is rendered
65
67
  * INSIDE xterm (no overlay/spinner) until the live PTY connects.
66
68
  */
@@ -110,6 +112,44 @@ type XtermLike = {
110
112
  };
111
113
  type FitAddonLike = { fit: () => void };
112
114
 
115
+ export type TerminalSurfaceState =
116
+ | "loading"
117
+ | "connecting"
118
+ | "error"
119
+ | "waking"
120
+ | "output-only"
121
+ | "interactive";
122
+
123
+ /**
124
+ * Stable semantic state for hosts and browser acceptance. The xterm helper
125
+ * textarea exists before the live PTY socket is ready, so its presence alone
126
+ * must never be treated as proof that keystrokes can reach the sandbox.
127
+ */
128
+ export function terminalSurfaceState(input: {
129
+ ready: boolean;
130
+ acceptsInput: boolean;
131
+ inputPending: boolean;
132
+ ptyStatus: TerminalStreamStatus;
133
+ booting: boolean;
134
+ }): TerminalSurfaceState {
135
+ if (!input.ready) return "loading";
136
+ if (input.acceptsInput) return "interactive";
137
+ if (input.inputPending) return "connecting";
138
+ if (input.ptyStatus === "connecting") return "connecting";
139
+ if (input.ptyStatus === "error") return "error";
140
+ if (input.booting) return "waking";
141
+ return "output-only";
142
+ }
143
+
144
+ export function subscribeTerminalInput(
145
+ term: Pick<XtermLike, "onData"> | null,
146
+ sink: ((data: string) => void) | null,
147
+ enabled: boolean,
148
+ ): { dispose: () => void } | null {
149
+ if (!enabled || !term || !sink) return null;
150
+ return term.onData(sink);
151
+ }
152
+
113
153
  /** Clear the transient wake line and put the first live prompt at cell 1,1.
114
154
  * `Terminal.clear()` removes scrollback but deliberately preserves the cursor,
115
155
  * which lets the first PTY frame append to the old wake message. */
@@ -193,50 +233,61 @@ export function SandboxTerminal({
193
233
  const wroteFirehoseRef = useRef(false);
194
234
  const bootActiveRef = useRef(false);
195
235
  const [ready, setReady] = useState(false);
236
+ const [inputReady, setInputReady] = useState(false);
196
237
  const [activated, setActivated] = useState(false);
197
238
 
198
239
  // ── Interactive transport switch ─────────────────────────────────────────────
199
- const ptyWs = terminalCapability?.transport === "pty-ws" && Boolean(terminalCapability?.url);
200
- // Output frames buffer here until xterm has mounted; the write effect drains it.
201
- const ptyOutputQueueRef = useRef<string[]>([]);
240
+ const ptyDescriptor = terminalCapability?.transport === "pty-ws";
241
+ const ptyAttachable = terminalCanAcquirePty(terminalCapability);
202
242
  const {
203
243
  status: ptyStatus,
204
244
  write: ptyWrite,
205
245
  resize: resizePty,
206
246
  connected: ptyConnected,
207
247
  } = useTerminalStream({
208
- capability: ptyWs
209
- ? {
210
- transport: terminalCapability!.transport,
211
- url: terminalCapability!.url,
212
- token: terminalCapability!.token,
213
- }
214
- : null,
248
+ // Connect only after xterm exists. This makes the renderer the sole output
249
+ // owner from the first websocket frame and removes an otherwise unbounded
250
+ // pre-mount output queue during large command bursts.
251
+ capability:
252
+ ptyAttachable && ready
253
+ ? {
254
+ transport: "pty-ws",
255
+ url: ptyDescriptor ? terminalCapability!.url : null,
256
+ token: ptyDescriptor ? terminalCapability!.token : null,
257
+ expiresAt: ptyDescriptor ? terminalCapability!.expiresAt : null,
258
+ }
259
+ : null,
215
260
  onOutput: (data) => {
216
- const term = termRef.current;
217
- if (term) term.write(data);
218
- else ptyOutputQueueRef.current.push(data);
261
+ termRef.current?.write(data);
219
262
  },
220
263
  });
221
264
 
222
- // PTY mode = a live ttyd socket drives the screen. Firehose mode = the
223
- // read-only projection (or the legacy HTTP-write fallback).
224
- const ptyMode = ptyWs && ptyStatus !== "closed";
225
- const interactive = !readOnly && (ptyMode ? ptyConnected : result.write !== null);
226
- const terminalModeLabel = interactive
227
- ? null
228
- : readOnly
229
- ? "read-only"
230
- : ptyMode && ptyStatus === "connecting"
231
- ? "connecting"
232
- : ptyMode && ptyStatus === "error"
233
- ? "connection error"
234
- : "output only";
235
-
236
- // Boot-in-terminal: after the user focuses a not-yet-warm box, show styled
265
+ // A pty-ws capability exclusively owns the screen even while connecting or
266
+ // after a visible connection failure; never silently switch one terminal UI
267
+ // between two PTYs. Input is enabled during the handshake because the stream
268
+ // hook buffers it strictly and flushes only after ttyd auth succeeds.
269
+ const ptyMode = ptyDescriptor;
270
+ // Keep xterm stdin live for an attachable descriptor even before its URL has
271
+ // arrived. Focus/click triggers the grant; any immediately following input is
272
+ // bounded in the stream hook instead of disappearing in that HTTP interval.
273
+ const ptyCapturesInput = ptyAttachable && ptyStatus !== "error";
274
+ const interactive = !readOnly && (ptyAttachable ? ptyCapturesInput : result.write !== null);
275
+ // Public "interactive" means the mounted input subscription can reach a live
276
+ // PTY now. Before that point xterm still captures into the bounded queue, but
277
+ // acceptance and embedders see an explicit connecting state.
278
+ const acceptsInput = inputReady && (!ptyAttachable || ptyConnected);
279
+ const inputPending = inputReady && ptyAttachable && !ptyConnected && activated;
280
+ let terminalModeLabel: string | null;
281
+ if (readOnly) terminalModeLabel = "read-only";
282
+ else if (!ptyAttachable) terminalModeLabel = acceptsInput ? null : "output only";
283
+ else if (ptyStatus === "error") terminalModeLabel = "connection error";
284
+ else if (acceptsInput) terminalModeLabel = null;
285
+ else terminalModeLabel = activated ? "connecting" : "connect on input";
286
+
287
+ // Boot-in-terminal: after the user engages a not-yet-warm box, show styled
237
288
  // status lines INSIDE xterm instead of an overlay — but only when there is no
238
289
  // firehose transcript to show yet (otherwise the projected output is shown).
239
- const warm = liveness === "warm" || liveness === "draining";
290
+ const warm = sandboxAcceptsLiveIo(liveness);
240
291
  const booting =
241
292
  activated &&
242
293
  !ptyMode &&
@@ -244,6 +295,13 @@ export function SandboxTerminal({
244
295
  liveness !== null &&
245
296
  liveness !== undefined &&
246
297
  result.chunks.length === 0;
298
+ const surfaceState = terminalSurfaceState({
299
+ ready,
300
+ acceptsInput,
301
+ inputPending,
302
+ ptyStatus,
303
+ booting,
304
+ });
247
305
 
248
306
  function handleActivate() {
249
307
  if (!activated) setActivated(true);
@@ -361,10 +419,6 @@ export function SandboxTerminal({
361
419
  requestAnimationFrame(() => {
362
420
  if (disposed) return;
363
421
  settle();
364
- if (ptyOutputQueueRef.current.length > 0) {
365
- for (const data of ptyOutputQueueRef.current) term.write(data);
366
- ptyOutputQueueRef.current = [];
367
- }
368
422
  el.setAttribute("data-og-term-ready", "true");
369
423
  if (typeof globalThis.__OG_TERMINAL_DEBUG__ === "function") {
370
424
  globalThis.__OG_TERMINAL_DEBUG__({
@@ -459,12 +513,19 @@ export function SandboxTerminal({
459
513
  // socket; firehose mode uses the legacy ptyWrite-over-HTTP fn.
460
514
  useEffect(() => {
461
515
  const term = termRef.current;
462
- if (!ready || !term || !interactive) return;
463
- const sink = ptyMode ? ptyWrite : result.write;
464
- if (!sink) return;
465
- const sub = term.onData((data) => sink(data));
466
- return () => sub.dispose();
467
- }, [ready, interactive, ptyMode, ptyWrite, result.write]);
516
+ if (!ready || !term || !interactive) {
517
+ setInputReady(false);
518
+ return;
519
+ }
520
+ const sink = ptyAttachable ? ptyWrite : result.write;
521
+ if (!sink) {
522
+ setInputReady(false);
523
+ return;
524
+ }
525
+ const sub = subscribeTerminalInput(term, sink, true);
526
+ setInputReady(sub !== null);
527
+ return () => sub?.dispose();
528
+ }, [ready, interactive, ptyAttachable, ptyWrite, result.write]);
468
529
 
469
530
  // In PTY mode, tell ttyd the window size on the xterm resize event.
470
531
  useEffect(() => {
@@ -551,6 +612,10 @@ export function SandboxTerminal({
551
612
  className="h-full w-full"
552
613
  style={{ visibility: ready ? "visible" : "hidden" }}
553
614
  data-opengeni-terminal
615
+ data-opengeni-terminal-ready={ready ? "true" : "false"}
616
+ data-opengeni-terminal-interactive={surfaceState === "interactive" ? "true" : "false"}
617
+ data-opengeni-terminal-state={surfaceState}
618
+ data-opengeni-terminal-status={ptyAttachable ? ptyStatus : "firehose"}
554
619
  />
555
620
  </div>
556
621
  </div>