@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.
- package/README.md +90 -2
- package/dist/artifacts-document.d.ts +3 -0
- package/dist/artifacts-document.js +17 -0
- package/dist/artifacts-document.js.map +1 -0
- package/dist/artifacts-presentation.d.ts +3 -0
- package/dist/artifacts-presentation.js +17 -0
- package/dist/artifacts-presentation.js.map +1 -0
- package/dist/artifacts-spreadsheet.d.ts +3 -0
- package/dist/artifacts-spreadsheet.js +17 -0
- package/dist/artifacts-spreadsheet.js.map +1 -0
- package/dist/artifacts.d.ts +2 -0
- package/dist/artifacts.js +170 -0
- package/dist/artifacts.js.map +1 -0
- package/dist/chunk-6BBWWOOU.js +82 -0
- package/dist/chunk-6BBWWOOU.js.map +1 -0
- package/dist/{chunk-FWXGSNXT.js → chunk-724BW4TW.js} +585 -34
- package/dist/chunk-724BW4TW.js.map +1 -0
- package/dist/chunk-7I2KB3OK.js +3895 -0
- package/dist/chunk-7I2KB3OK.js.map +1 -0
- package/dist/chunk-7WCLAEFV.js +2828 -0
- package/dist/chunk-7WCLAEFV.js.map +1 -0
- package/dist/{chunk-I4MPFDRP.js → chunk-FWS3KC3Z.js} +3 -3
- package/dist/{chunk-EKZH6IDG.js → chunk-HDBG7XGO.js} +62 -1
- package/dist/chunk-HDBG7XGO.js.map +1 -0
- package/dist/chunk-HUIKIDM5.js +2148 -0
- package/dist/chunk-HUIKIDM5.js.map +1 -0
- package/dist/{chunk-XCNTXWBB.js → chunk-JAGDUCZQ.js} +854 -340
- package/dist/chunk-JAGDUCZQ.js.map +1 -0
- package/dist/{chunk-LHLYWG5R.js → chunk-JR3QFY6B.js} +413 -37
- package/dist/chunk-JR3QFY6B.js.map +1 -0
- package/dist/chunk-JVYQRRC4.js +53 -0
- package/dist/chunk-JVYQRRC4.js.map +1 -0
- package/dist/chunk-KCGXPP5V.js +157 -0
- package/dist/chunk-KCGXPP5V.js.map +1 -0
- package/dist/chunk-PEI6RANI.js +3234 -0
- package/dist/chunk-PEI6RANI.js.map +1 -0
- package/dist/{chunk-NJ7M2B6Z.js → chunk-ROIRTM37.js} +18 -10
- package/dist/{chunk-NJ7M2B6Z.js.map → chunk-ROIRTM37.js.map} +1 -1
- package/dist/{chunk-O4VXOBGF.js → chunk-WSK7G5LE.js} +70 -2
- package/dist/chunk-WSK7G5LE.js.map +1 -0
- package/dist/{chunk-OL6QVXGF.js → chunk-X4S56Y2H.js} +27 -7
- package/dist/{chunk-OL6QVXGF.js.map → chunk-X4S56Y2H.js.map} +1 -1
- package/dist/client.d.ts +7 -1
- package/dist/clipboard.d.ts +1 -0
- package/dist/clipboard.js +7 -0
- package/dist/clipboard.js.map +1 -0
- package/dist/commands/index.d.ts +3 -3
- package/dist/commands/registry.d.ts +1 -1
- package/dist/commands/types.d.ts +1 -1
- package/dist/components/approval-surface.d.ts +2 -2
- package/dist/components/artifacts/artifact-surface.d.ts +18 -0
- package/dist/components/artifacts/document-editor.d.ts +172 -0
- package/dist/components/artifacts/editable-artifact-ui.d.ts +13 -0
- package/dist/components/artifacts/editable-artifact-workbench.d.ts +49 -0
- package/dist/components/artifacts/editable-document.d.ts +24 -0
- package/dist/components/artifacts/editable-presentation.d.ts +24 -0
- package/dist/components/artifacts/editable-spreadsheet.d.ts +32 -0
- package/dist/components/artifacts/index.d.ts +8 -0
- package/dist/components/artifacts/presentation-editor.d.ts +297 -0
- package/dist/components/artifacts/spreadsheet-canvas.d.ts +120 -0
- package/dist/components/artifacts/spreadsheet-grid.d.ts +113 -0
- package/dist/components/browser-viewer.d.ts +29 -0
- package/dist/components/chat-composer.d.ts +8 -8
- package/dist/components/code-editor.d.ts +25 -3
- package/dist/components/command-palette.d.ts +2 -2
- package/dist/components/composer-transcription-control.d.ts +2 -2
- package/dist/components/composer.d.ts +17 -17
- package/dist/components/computer-viewer.d.ts +23 -0
- package/dist/components/copy-button.d.ts +2 -2
- package/dist/components/desktop-viewer.d.ts +25 -21
- package/dist/components/diff-view.d.ts +1 -1
- package/dist/components/enrollment-consent.d.ts +1 -1
- package/dist/components/enrollment-device-flow.d.ts +1 -1
- package/dist/components/file-browser.d.ts +2 -2
- package/dist/components/fleet-tile.d.ts +1 -1
- package/dist/components/generated-video-player.d.ts +10 -0
- package/dist/components/human-input-form.d.ts +1 -1
- package/dist/components/human-input-surface.d.ts +2 -2
- package/dist/components/interactive-workbench-viewers.d.ts +2 -0
- package/dist/components/machine-card.d.ts +2 -2
- package/dist/components/machine-dock-bar.d.ts +3 -3
- package/dist/components/machine-health-pill.d.ts +2 -2
- package/dist/components/machine-input-display.d.ts +1 -1
- package/dist/components/machine-metrics.d.ts +2 -2
- package/dist/components/machine-status-pill.d.ts +4 -4
- package/dist/components/machines/health.d.ts +1 -1
- package/dist/components/machines/machine-detail.d.ts +3 -3
- package/dist/components/machines/metric-history-chart.d.ts +1 -1
- package/dist/components/machines/metric-sparkline.d.ts +2 -2
- package/dist/components/machines/series.d.ts +2 -2
- package/dist/components/machines-dashboard.d.ts +2 -2
- package/dist/components/markdown.d.ts +1 -1
- package/dist/components/message-timeline.d.ts +12 -5
- package/dist/components/model-picker.d.ts +2 -2
- package/dist/components/model-policy-picker.d.ts +7 -7
- package/dist/components/pierre-diff.d.ts +1 -1
- package/dist/components/pierre-file.d.ts +1 -1
- package/dist/components/queue-draft-policy.d.ts +1 -1
- package/dist/components/queue-surface-implementation.d.ts +3 -3
- package/dist/components/queue-surface-state.d.ts +4 -4
- package/dist/components/queue-surface.d.ts +5 -5
- package/dist/components/sandbox-files.d.ts +3 -3
- package/dist/components/sandbox-terminal.d.ts +21 -4
- package/dist/components/sandbox-workspace.d.ts +25 -9
- package/dist/components/session-chrome.d.ts +4 -4
- package/dist/components/session-status.d.ts +2 -2
- package/dist/components/timeline-annotation-selection.d.ts +9 -0
- package/dist/components/timeline-annotations-dialog.d.ts +16 -0
- package/dist/components/timeline-annotations.d.ts +13 -0
- package/dist/components/tooltip.d.ts +4 -4
- package/dist/components/user-message-body.d.ts +2 -2
- package/dist/components/workbench-changes.d.ts +2 -2
- package/dist/components/workspace-dock.d.ts +1 -1
- package/dist/composer.d.ts +11 -11
- package/dist/composer.js +5 -5
- package/dist/hooks/internal.d.ts +1 -1
- package/dist/hooks/use-attached-browsers.d.ts +21 -0
- package/dist/hooks/use-available-models.d.ts +2 -2
- package/dist/hooks/use-billing-usage.d.ts +1 -1
- package/dist/hooks/use-browser-frame-stream.d.ts +28 -0
- package/dist/hooks/use-browser-identities.d.ts +26 -0
- package/dist/hooks/use-browser-session.d.ts +28 -0
- package/dist/hooks/use-browser-sessions.d.ts +32 -0
- package/dist/hooks/use-codex-accounts.d.ts +2 -2
- package/dist/hooks/use-composer.d.ts +36 -9
- package/dist/hooks/use-computer-frame-stream.d.ts +24 -0
- package/dist/hooks/use-computer-session.d.ts +28 -0
- package/dist/hooks/use-computer-sessions.d.ts +28 -0
- package/dist/hooks/use-desktop-stream.d.ts +1 -1
- package/dist/hooks/use-environments.d.ts +1 -1
- package/dist/hooks/use-file-attachments.d.ts +1 -1
- package/dist/hooks/use-goal.d.ts +2 -2
- package/dist/hooks/use-human-input.d.ts +2 -2
- package/dist/hooks/use-last-started-turn-policy.d.ts +2 -2
- package/dist/hooks/use-machine-chip.d.ts +2 -2
- package/dist/hooks/use-machines.d.ts +4 -4
- package/dist/hooks/use-packs.d.ts +7 -2
- package/dist/hooks/use-rigs.d.ts +1 -1
- package/dist/hooks/use-sandbox-files.d.ts +6 -4
- package/dist/hooks/use-sandbox-git.d.ts +6 -5
- package/dist/hooks/use-sandbox-terminal.d.ts +1 -1
- package/dist/hooks/use-scheduled-tasks.d.ts +1 -1
- package/dist/hooks/use-session-capabilities.d.ts +5 -4
- package/dist/hooks/use-session-control.d.ts +1 -1
- package/dist/hooks/use-session-events.d.ts +2 -2
- package/dist/hooks/use-session-lineage.d.ts +1 -1
- package/dist/hooks/use-session-mcp-approval-policy.d.ts +2 -2
- package/dist/hooks/use-session.d.ts +2 -2
- package/dist/hooks/use-slash-commands.d.ts +1 -1
- package/dist/hooks/use-terminal-stream.d.ts +8 -2
- package/dist/hooks/use-turn-queue.d.ts +2 -2
- package/dist/hooks/use-video-artifact-playback.d.ts +9 -0
- package/dist/hooks/use-voice-input.d.ts +1 -1
- package/dist/hooks/use-workspace-capture.d.ts +1 -1
- package/dist/hooks/use-workspace-edit.d.ts +1 -1
- package/dist/hooks/use-workspace-sessions.d.ts +1 -1
- package/dist/hooks/use-workspaces.d.ts +1 -1
- package/dist/index.d.ts +158 -155
- package/dist/index.js +906 -462
- package/dist/index.js.map +1 -1
- package/dist/interaction.d.ts +23 -0
- package/dist/interaction.js +36 -0
- package/dist/interaction.js.map +1 -0
- package/dist/interactive-workbench-viewers-KHNOHWQE.js +13 -0
- package/dist/interactive-workbench-viewers-KHNOHWQE.js.map +1 -0
- package/dist/lib/relay-wire.d.ts +4 -0
- package/dist/lib/sandbox-liveness.d.ts +9 -0
- package/dist/lib/terminal-capability.d.ts +5 -0
- package/dist/lib/xterm-theme.d.ts +1 -1
- package/dist/machines.d.ts +30 -30
- package/dist/machines.js +3 -3
- package/dist/provider.d.ts +4 -4
- package/dist/{queue-surface-implementation-U5JFG7HT.js → queue-surface-implementation-2KVJTYXH.js} +100 -48
- package/dist/queue-surface-implementation-2KVJTYXH.js.map +1 -0
- package/dist/realtime/dropdown-menu.d.ts +8 -8
- package/dist/realtime/realtime-control.d.ts +6 -6
- package/dist/realtime.d.ts +4 -4
- package/dist/realtime.js +3 -3
- package/dist/session-context.d.ts +19 -1
- package/dist/session-ui.d.ts +14 -14
- package/dist/session-ui.js +5 -4
- package/dist/session.d.ts +28 -28
- package/dist/session.js +5 -5
- package/dist/timeline/activity-rail.d.ts +5 -3
- package/dist/timeline/disclosure-context.d.ts +1 -1
- package/dist/timeline/fleet-decision-projection.d.ts +1 -1
- package/dist/timeline/fleet-decision-row.d.ts +3 -3
- package/dist/timeline/index.d.ts +19 -19
- package/dist/timeline/parsers.d.ts +4 -1
- package/dist/timeline/projection.d.ts +3 -3
- package/dist/timeline/registry.d.ts +8 -2
- package/dist/timeline/screenshot-lightbox.d.ts +2 -2
- package/dist/timeline/shared.d.ts +21 -10
- package/dist/timeline/tool-diff.d.ts +2 -2
- package/dist/timeline/tool-renderers.d.ts +1 -1
- package/dist/timeline/turn-summary.d.ts +3 -3
- package/dist/timeline/types.d.ts +39 -3
- package/dist/timeline-annotation-selection-3L7LHGG2.js +187 -0
- package/dist/timeline-annotation-selection-3L7LHGG2.js.map +1 -0
- package/dist/timeline-annotations-dialog-U7EVLR75.js +196 -0
- package/dist/timeline-annotations-dialog-U7EVLR75.js.map +1 -0
- package/package.json +36 -7
- package/src/artifacts-document.ts +31 -0
- package/src/artifacts-presentation.ts +36 -0
- package/src/artifacts-spreadsheet.ts +21 -0
- package/src/artifacts.ts +86 -0
- package/src/client.ts +87 -1
- package/src/clipboard.ts +2 -0
- package/src/components/artifacts/artifact-surface.tsx +93 -0
- package/src/components/artifacts/document-editor.tsx +2255 -0
- package/src/components/artifacts/editable-artifact-ui.tsx +89 -0
- package/src/components/artifacts/editable-artifact-workbench.tsx +203 -0
- package/src/components/artifacts/editable-document.tsx +657 -0
- package/src/components/artifacts/editable-presentation.tsx +1020 -0
- package/src/components/artifacts/editable-spreadsheet.tsx +733 -0
- package/src/components/artifacts/index.ts +99 -0
- package/src/components/artifacts/presentation-editor.tsx +3148 -0
- package/src/components/artifacts/spreadsheet-canvas.ts +799 -0
- package/src/components/artifacts/spreadsheet-grid.tsx +2129 -0
- package/src/components/browser-viewer.tsx +1592 -0
- package/src/components/chat-composer.tsx +13 -0
- package/src/components/code-editor.tsx +89 -30
- package/src/components/composer-transcription-control.tsx +10 -6
- package/src/components/computer-viewer.tsx +1139 -0
- package/src/components/desktop-viewer.tsx +101 -73
- package/src/components/generated-video-player.tsx +89 -0
- package/src/components/interactive-workbench-viewers.ts +2 -0
- package/src/components/machine-input-display.ts +3 -0
- package/src/components/message-timeline.tsx +289 -32
- package/src/components/queue-surface-implementation.tsx +116 -60
- package/src/components/sandbox-files.tsx +2 -2
- package/src/components/sandbox-terminal.tsx +106 -41
- package/src/components/sandbox-workspace.tsx +215 -87
- package/src/components/session-chrome.tsx +53 -13
- package/src/components/timeline-annotation-selection.tsx +221 -0
- package/src/components/timeline-annotations-dialog.tsx +213 -0
- package/src/components/timeline-annotations.tsx +84 -0
- package/src/components/workbench-changes.tsx +188 -31
- package/src/hooks/use-attached-browsers.ts +152 -0
- package/src/hooks/use-browser-frame-stream.ts +316 -0
- package/src/hooks/use-browser-identities.ts +225 -0
- package/src/hooks/use-browser-session.ts +476 -0
- package/src/hooks/use-browser-sessions.ts +281 -0
- package/src/hooks/use-composer.ts +769 -24
- package/src/hooks/use-computer-frame-stream.ts +311 -0
- package/src/hooks/use-computer-session.ts +442 -0
- package/src/hooks/use-computer-sessions.ts +245 -0
- package/src/hooks/use-machine-chip.ts +5 -3
- package/src/hooks/use-machines.ts +33 -3
- package/src/hooks/use-packs.ts +51 -0
- package/src/hooks/use-sandbox-files.ts +98 -55
- package/src/hooks/use-sandbox-git.ts +93 -38
- package/src/hooks/use-sandbox-terminal.ts +3 -2
- package/src/hooks/use-session-capabilities.ts +59 -46
- package/src/hooks/use-terminal-stream.ts +102 -23
- package/src/hooks/use-video-artifact-playback.ts +28 -0
- package/src/hooks/use-workspace-edit.ts +2 -1
- package/src/index.ts +10 -0
- package/src/interaction.ts +71 -0
- package/src/lib/relay-wire.ts +4 -0
- package/src/lib/sandbox-liveness.ts +11 -0
- package/src/lib/terminal-capability.ts +10 -0
- package/src/session-context.ts +90 -0
- package/src/timeline/activity-rail.tsx +19 -3
- package/src/timeline/index.ts +3 -0
- package/src/timeline/parsers.ts +14 -1
- package/src/timeline/projection.ts +211 -14
- package/src/timeline/registry.ts +13 -1
- package/src/timeline/screenshot-lightbox.tsx +19 -9
- package/src/timeline/shared.tsx +56 -8
- package/src/timeline/tool-renderers.tsx +303 -50
- package/src/timeline/types.ts +55 -3
- package/styles/compiled.css +248 -15
- package/styles/index.css +15 -0
- package/styles/responsive.css +13 -1
- package/dist/chunk-EKZH6IDG.js.map +0 -1
- package/dist/chunk-FWXGSNXT.js.map +0 -1
- package/dist/chunk-LHLYWG5R.js.map +0 -1
- package/dist/chunk-O4VXOBGF.js.map +0 -1
- package/dist/chunk-XCNTXWBB.js.map +0 -1
- package/dist/fleet-decision-projection-SLNZMS42.js +0 -241
- package/dist/fleet-decision-projection-SLNZMS42.js.map +0 -1
- package/dist/queue-surface-implementation-U5JFG7HT.js.map +0 -1
- /package/dist/{chunk-I4MPFDRP.js.map → chunk-FWS3KC3Z.js.map} +0 -0
|
@@ -7,6 +7,7 @@ import type {
|
|
|
7
7
|
import { OpenGeniApiError } from "@opengeni/sdk";
|
|
8
8
|
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
9
9
|
import { useOpenGeni, type ClientOverride } from "../provider";
|
|
10
|
+
import { sandboxAcceptsLiveIo } from "../lib/sandbox-liveness";
|
|
10
11
|
|
|
11
12
|
export type TerminalChunk = {
|
|
12
13
|
/** Stable key (the source event id) so the xterm writer tracks a written-cursor. */
|
|
@@ -107,9 +108,9 @@ export function useSandboxTerminal(
|
|
|
107
108
|
// projection is the single source of truth for what's written). The open is
|
|
108
109
|
// gated on a WARM box: opening on a cold/warming lease races the box (the PTY
|
|
109
110
|
// exec-session is created on a box that the next op may not resume), which is
|
|
110
|
-
// exactly the "session not found" terminal failure; we wait for warm
|
|
111
|
+
// exactly the "session not found" terminal failure; we wait for warm.
|
|
111
112
|
const openInFlight = useRef(false);
|
|
112
|
-
const boxWarm = liveness === undefined || liveness
|
|
113
|
+
const boxWarm = liveness === undefined || sandboxAcceptsLiveIo(liveness);
|
|
113
114
|
useEffect(() => {
|
|
114
115
|
if (!interactive || !sessionId || ptyFilter || !boxWarm) {
|
|
115
116
|
setOpenError(null);
|
|
@@ -7,10 +7,13 @@ import {
|
|
|
7
7
|
} from "@opengeni/sdk";
|
|
8
8
|
import { useCallback, useEffect, useRef, useState } from "react";
|
|
9
9
|
import { useOpenGeni, type ClientOverride } from "../provider";
|
|
10
|
+
import { sandboxAcceptsLiveIo } from "../lib/sandbox-liveness";
|
|
11
|
+
import { terminalCanAcquirePty } from "../lib/terminal-capability";
|
|
10
12
|
import { usePageLiveActivity } from "./internal";
|
|
11
13
|
|
|
12
|
-
// "on-demand" is the boxless-benign resting state: the lease is
|
|
13
|
-
// asked to warm a box (no viewer/consent action, no
|
|
14
|
+
// "on-demand" is the boxless-benign resting state: the lease is not currently
|
|
15
|
+
// holder-backed and NOTHING asked to warm a box (no viewer/consent action, no
|
|
16
|
+
// files-tab warm). Under lazy
|
|
14
17
|
// provisioning a chat-only turn never creates a box, so this is the correct calm
|
|
15
18
|
// terminal state — NOT an error. It is distinct from "cold", which now means "a
|
|
16
19
|
// warm-up is genuinely in flight" (a viewer attach was requested) and therefore
|
|
@@ -84,7 +87,8 @@ export type UseSessionCapabilitiesResult = {
|
|
|
84
87
|
error: Error | null;
|
|
85
88
|
/**
|
|
86
89
|
* 409 from the desktop attach: the un-redacted (or shared) plane needs explicit
|
|
87
|
-
* acknowledgment before a viewer holder is granted.
|
|
90
|
+
* acknowledgment before a viewer holder is granted. The workbench uses this
|
|
91
|
+
* to complete acknowledgment automatically while Desktop is opening.
|
|
88
92
|
*/
|
|
89
93
|
acknowledgmentRequired: "unredacted" | "shared" | null;
|
|
90
94
|
/** 429 from the desktop attach: the per-session viewer cap is reached. */
|
|
@@ -112,24 +116,10 @@ function desktopAttachable(cell: SessionCapabilities["DesktopStream"]): boolean
|
|
|
112
116
|
|
|
113
117
|
/**
|
|
114
118
|
* Whether warming the box for the interactive terminal (pty-ws) is worth it.
|
|
115
|
-
*
|
|
116
|
-
*
|
|
117
|
-
*
|
|
118
|
-
* what flips `sse-events` → `pty-ws` by warming the box and minting the ttyd
|
|
119
|
-
* tunnel URL, so we attach whenever the terminal is not hard-unavailable.
|
|
119
|
+
* A real-PTY descriptor is attachable when already routed as `pty-ws`, or while
|
|
120
|
+
* transiently cold. A firehose-only backend and policy/OS/backend degradation
|
|
121
|
+
* cannot become interactive, so attaching there would only waste a holder.
|
|
120
122
|
*/
|
|
121
|
-
function terminalAttachable(cell: SessionCapabilities["Terminal"]): boolean {
|
|
122
|
-
if (cell.transport === null) {
|
|
123
|
-
// No terminal at all unless the only blocker is a transient cold state.
|
|
124
|
-
return (
|
|
125
|
-
cell.reason === "lease_cold" || cell.reason === "not_provisioned" || cell.reason === null
|
|
126
|
-
);
|
|
127
|
-
}
|
|
128
|
-
// Already pty-ws (warm) is fine to (re)attach; sse-events is the cold state the
|
|
129
|
-
// attach upgrades. Either way it's attachable.
|
|
130
|
-
return true;
|
|
131
|
-
}
|
|
132
|
-
|
|
133
123
|
/** Read `stream.url.rotated` payloads off the live event log, newest last. */
|
|
134
124
|
function rotationsFrom(events: SessionEvent[]): StreamUrlRotatedPayload[] {
|
|
135
125
|
const out: StreamUrlRotatedPayload[] = [];
|
|
@@ -149,8 +139,8 @@ function rotationsFrom(events: SessionEvent[]): StreamUrlRotatedPayload[] {
|
|
|
149
139
|
*
|
|
150
140
|
* Degradation is a value, never a crash: an unsupported surface comes back
|
|
151
141
|
* `available:false`/`transport:null` + a `reason`; the components render the
|
|
152
|
-
* reason-aware empty state. 409 (
|
|
153
|
-
* typed signals, not thrown.
|
|
142
|
+
* reason-aware empty state. 409 (acknowledgment) and 429 (viewer cap) are
|
|
143
|
+
* surfaced as typed signals, not thrown.
|
|
154
144
|
*/
|
|
155
145
|
export function useSessionCapabilities(
|
|
156
146
|
sessionId: string | null | undefined,
|
|
@@ -207,8 +197,15 @@ export function useSessionCapabilities(
|
|
|
207
197
|
if (!lifecycleEnabled || !sessionId) {
|
|
208
198
|
setState("idle");
|
|
209
199
|
setCapabilities(null);
|
|
200
|
+
viewerIdRef.current = null;
|
|
201
|
+
setViewerId(null);
|
|
210
202
|
return;
|
|
211
203
|
}
|
|
204
|
+
// The preceding effect cleanup released any old holder before this fresh
|
|
205
|
+
// lifecycle starts. Never project that stale id while re-negotiating or if
|
|
206
|
+
// the replacement grant degrades.
|
|
207
|
+
viewerIdRef.current = null;
|
|
208
|
+
setViewerId(null);
|
|
212
209
|
let cancelled = false;
|
|
213
210
|
let pollTimer: ReturnType<typeof setTimeout> | null = null;
|
|
214
211
|
let heartbeatTimer: ReturnType<typeof setTimeout> | null = null;
|
|
@@ -273,7 +270,7 @@ export function useSessionCapabilities(
|
|
|
273
270
|
.then((caps) => {
|
|
274
271
|
if (cancelled) return;
|
|
275
272
|
settle(caps);
|
|
276
|
-
if (caps.liveness
|
|
273
|
+
if (sandboxAcceptsLiveIo(caps.liveness)) {
|
|
277
274
|
setState("ready");
|
|
278
275
|
return;
|
|
279
276
|
}
|
|
@@ -308,8 +305,8 @@ export function useSessionCapabilities(
|
|
|
308
305
|
// attach serves BOTH live planes — the desktop pixel stream AND the
|
|
309
306
|
// interactive terminal (ttyd pty-ws) ride the same warm box and the same
|
|
310
307
|
// holder; the response folds the minted address for each requested plane.
|
|
311
|
-
// The
|
|
312
|
-
// rather than throwing — the tabs degrade gracefully.
|
|
308
|
+
// The acknowledgment requirement (409) and viewer cap (429) surface as
|
|
309
|
+
// typed signals rather than throwing — the tabs degrade gracefully.
|
|
313
310
|
//
|
|
314
311
|
// KEY: the handshake (getStreamCapabilities) NEVER spins up a cold box, so
|
|
315
312
|
// on a cold/warming lease the desktop cell comes back transport:null and
|
|
@@ -321,7 +318,7 @@ export function useSessionCapabilities(
|
|
|
321
318
|
// already live OR cold-but-feasible — and let POST /viewers warm the box and
|
|
322
319
|
// mint the URLs. Only a genuinely-unsupported reason suppresses the attach.
|
|
323
320
|
const wantDesktopAttach = attachDesktop && desktopAttachable(caps.DesktopStream);
|
|
324
|
-
const wantTerminalAttach = attachTerminal &&
|
|
321
|
+
const wantTerminalAttach = attachTerminal && terminalCanAcquirePty(caps.Terminal);
|
|
325
322
|
// The Files attach warms the box for a file WRITE (wake-on-edit intent). It
|
|
326
323
|
// folds no live URL (files are stateless HTTP) — the attach is purely a
|
|
327
324
|
// liveness refcount. Unlike the previous "keep the box warm while the Files
|
|
@@ -339,6 +336,8 @@ export function useSessionCapabilities(
|
|
|
339
336
|
// pty-ws terminal cell WITHOUT tripping the desktop consent 409.
|
|
340
337
|
const holder = await client.attachViewer(workspaceId, sessionId, {
|
|
341
338
|
desktop: wantDesktopAttach,
|
|
339
|
+
terminal: wantTerminalAttach,
|
|
340
|
+
files: wantFilesAttach,
|
|
342
341
|
});
|
|
343
342
|
if (cancelled) {
|
|
344
343
|
// The attach crossed an identity/unmount boundary after the server
|
|
@@ -348,6 +347,18 @@ export function useSessionCapabilities(
|
|
|
348
347
|
void client.detachViewer(workspaceId, sessionId, holder.viewerId).catch(() => {});
|
|
349
348
|
return;
|
|
350
349
|
}
|
|
350
|
+
if (
|
|
351
|
+
(wantDesktopAttach &&
|
|
352
|
+
(!holder.dataPlaneUrl || !holder.transport || !holder.client)) ||
|
|
353
|
+
(wantTerminalAttach && (!holder.terminalUrl || !holder.terminalTransport))
|
|
354
|
+
) {
|
|
355
|
+
// Nullable plane cells are valid graceful-degradation values at
|
|
356
|
+
// the raw API boundary, but an explicit UI grant must not become
|
|
357
|
+
// a successful holder that waits forever. Release it and surface
|
|
358
|
+
// a retryable error instead of retaining stale credentials.
|
|
359
|
+
await client.detachViewer(workspaceId, sessionId, holder.viewerId).catch(() => {});
|
|
360
|
+
throw new Error("live plane unavailable");
|
|
361
|
+
}
|
|
351
362
|
localViewerId = holder.viewerId;
|
|
352
363
|
viewerIdRef.current = holder.viewerId;
|
|
353
364
|
setViewerId(holder.viewerId);
|
|
@@ -367,24 +378,24 @@ export function useSessionCapabilities(
|
|
|
367
378
|
...prev.DesktopStream,
|
|
368
379
|
transport: holder.transport ?? prev.DesktopStream.transport,
|
|
369
380
|
client: holder.client ?? prev.DesktopStream.client,
|
|
370
|
-
url: holder.dataPlaneUrl
|
|
371
|
-
token: holder.streamToken
|
|
372
|
-
expiresAt: holder.streamExpiresAt
|
|
381
|
+
url: holder.dataPlaneUrl,
|
|
382
|
+
token: holder.streamToken,
|
|
383
|
+
expiresAt: holder.streamExpiresAt,
|
|
373
384
|
resolution: holder.resolution ?? prev.DesktopStream.resolution,
|
|
374
385
|
}
|
|
375
386
|
: prev.DesktopStream,
|
|
376
|
-
Terminal:
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
387
|
+
Terminal: wantTerminalAttach
|
|
388
|
+
? {
|
|
389
|
+
...prev.Terminal,
|
|
390
|
+
transport: holder.terminalTransport ?? prev.Terminal.transport,
|
|
391
|
+
url: holder.terminalUrl,
|
|
392
|
+
token: holder.terminalToken,
|
|
393
|
+
expiresAt: holder.terminalExpiresAt,
|
|
394
|
+
// A live pty-ws means the box is pty-capable for real.
|
|
395
|
+
ptyCapable: holder.terminalTransport ? true : prev.Terminal.ptyCapable,
|
|
396
|
+
reason: holder.terminalTransport ? null : prev.Terminal.reason,
|
|
397
|
+
}
|
|
398
|
+
: prev.Terminal,
|
|
388
399
|
}
|
|
389
400
|
: prev,
|
|
390
401
|
);
|
|
@@ -393,8 +404,8 @@ export function useSessionCapabilities(
|
|
|
393
404
|
if (cause instanceof OpenGeniApiError) {
|
|
394
405
|
if (cause.status === 409) {
|
|
395
406
|
// The un-redacted/shared consent gate is a DESKTOP requirement. A
|
|
396
|
-
// terminal-only warm attach needs no
|
|
397
|
-
// a
|
|
407
|
+
// terminal-only warm attach needs no acknowledgment, so a 409 there
|
|
408
|
+
// is not a desktop-open signal — the terminal stays on the read-only
|
|
398
409
|
// firehose. Only raise the consent requirement when the desktop was
|
|
399
410
|
// the (or a) reason we attached.
|
|
400
411
|
if (wantDesktopAttach) {
|
|
@@ -408,6 +419,8 @@ export function useSessionCapabilities(
|
|
|
408
419
|
setState("error");
|
|
409
420
|
setError(cause);
|
|
410
421
|
return;
|
|
422
|
+
} else {
|
|
423
|
+
throw cause;
|
|
411
424
|
}
|
|
412
425
|
// 409/429 are recoverable: structured surfaces still negotiated;
|
|
413
426
|
// keep going to set ready/cold below.
|
|
@@ -418,16 +431,16 @@ export function useSessionCapabilities(
|
|
|
418
431
|
}
|
|
419
432
|
|
|
420
433
|
// Whether ANYTHING asked to warm a box this negotiation. Only then is a
|
|
421
|
-
//
|
|
434
|
+
// non-warm lease "warming in flight" — worth polling, and worth surfacing a
|
|
422
435
|
// stall as an error. With no warm-up requested a cold lease is the benign
|
|
423
436
|
// boxless resting state (below), never an error.
|
|
424
437
|
const warmUpRequested = wantDesktopAttach || wantTerminalAttach || wantFilesAttach;
|
|
425
|
-
if (caps.liveness
|
|
438
|
+
if (sandboxAcceptsLiveIo(caps.liveness) || localViewerId) {
|
|
426
439
|
setState("ready");
|
|
427
440
|
startHeartbeat(caps);
|
|
428
441
|
} else if (warmUpRequested) {
|
|
429
442
|
// A warm-up is genuinely in flight (a viewer attach was requested) but the
|
|
430
|
-
// box isn't warm yet — poll until it
|
|
443
|
+
// box isn't holder-confirmed warm yet — poll until it is, with the deadline so a
|
|
431
444
|
// real stall surfaces as an error.
|
|
432
445
|
setState("cold");
|
|
433
446
|
pollUntilWarm();
|
|
@@ -17,7 +17,7 @@ export type UseTerminalStreamOptions = {
|
|
|
17
17
|
* The stream connects ONLY when `transport === "pty-ws"` and `url` is set; on a
|
|
18
18
|
* cold box (`transport === "sse-events"` / no url) it stays idle and the caller
|
|
19
19
|
* falls back to the Channel-A read-only firehose. */
|
|
20
|
-
capability: Pick<TerminalCapability, "transport" | "url" | "token"> | null;
|
|
20
|
+
capability: Pick<TerminalCapability, "transport" | "url" | "token" | "expiresAt"> | null;
|
|
21
21
|
/** Called for each OUTPUT payload from ttyd (write verbatim into xterm). */
|
|
22
22
|
onOutput?: ((data: string) => void) | undefined;
|
|
23
23
|
/** Called when ttyd sends a SET_WINDOW_TITLE frame. */
|
|
@@ -31,7 +31,8 @@ export type UseTerminalStreamResult = {
|
|
|
31
31
|
/** True once the ttyd socket is open (and the auth frame has been sent). */
|
|
32
32
|
connected: boolean;
|
|
33
33
|
status: TerminalStreamStatus;
|
|
34
|
-
/** Pipe a keystroke/paste to
|
|
34
|
+
/** Pipe a keystroke/paste to PTY stdin. Input during CONNECTING is bounded and
|
|
35
|
+
* replayed only after ttyd authentication succeeds. */
|
|
35
36
|
write: (data: string) => void;
|
|
36
37
|
/** Tell ttyd the PTY window changed size (on xterm fit/resize). */
|
|
37
38
|
resize: (cols: number, rows: number) => void;
|
|
@@ -39,6 +40,27 @@ export type UseTerminalStreamResult = {
|
|
|
39
40
|
disconnect: () => void;
|
|
40
41
|
};
|
|
41
42
|
|
|
43
|
+
// Keystrokes entered during the websocket handshake are held until ttyd has
|
|
44
|
+
// accepted its auth/resize frames. Bound the queue by UTF-16 code units (a hard
|
|
45
|
+
// <=128 KiB string payload in current JS engines) so a paste cannot turn a slow
|
|
46
|
+
// handshake into unbounded renderer memory. Overflow rejects the whole pending
|
|
47
|
+
// input rather than ever executing a truncated shell command.
|
|
48
|
+
export const MAX_PENDING_TERMINAL_INPUT_CODE_UNITS = 64 * 1024;
|
|
49
|
+
const TERMINAL_CONNECT_EXPIRY_SKEW_MS = 5_000;
|
|
50
|
+
|
|
51
|
+
/** A bearer must remain valid long enough to finish a new websocket handshake.
|
|
52
|
+
* Existing open sockets are unaffected: this is evaluated only when the
|
|
53
|
+
* credential identity changes and the connection effect attempts a new socket. */
|
|
54
|
+
export function terminalStreamCredentialUsable(
|
|
55
|
+
capability: Pick<TerminalCapability, "transport" | "url" | "expiresAt"> | null | undefined,
|
|
56
|
+
nowMs = Date.now(),
|
|
57
|
+
): boolean {
|
|
58
|
+
if (capability?.transport !== "pty-ws" || !capability.url) return false;
|
|
59
|
+
if (!capability.expiresAt) return true;
|
|
60
|
+
const expiresAt = Date.parse(capability.expiresAt);
|
|
61
|
+
return Number.isFinite(expiresAt) && expiresAt > nowMs + TERMINAL_CONNECT_EXPIRY_SKEW_MS;
|
|
62
|
+
}
|
|
63
|
+
|
|
42
64
|
/** Decode an inbound ttyd frame's payload (everything after the 1-char command).
|
|
43
65
|
* ttyd may send either a text frame (string) or a binary frame (ArrayBuffer);
|
|
44
66
|
* for binary we slice off the first byte (the command) and utf-8 decode the rest. */
|
|
@@ -52,6 +74,14 @@ function decodeFrame(data: string | ArrayBuffer): { command: string; payload: st
|
|
|
52
74
|
return { command, payload };
|
|
53
75
|
}
|
|
54
76
|
|
|
77
|
+
function closeSocket(socket: WebSocket | null): void {
|
|
78
|
+
try {
|
|
79
|
+
socket?.close();
|
|
80
|
+
} catch {
|
|
81
|
+
// Already closed or rejected by the host WebSocket implementation.
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
55
85
|
/**
|
|
56
86
|
* Drive a ttyd PTY-over-websocket connection from a `pty-ws` Terminal capability,
|
|
57
87
|
* symmetric with `use-desktop-stream` (the noVNC-over-tunnel hook). The scoped
|
|
@@ -73,6 +103,9 @@ export function useTerminalStream(options: UseTerminalStreamOptions): UseTermina
|
|
|
73
103
|
const { capability, onOutput, onTitle, initialCols, initialRows } = options;
|
|
74
104
|
const [status, setStatus] = useState<TerminalStreamStatus>("closed");
|
|
75
105
|
const wsRef = useRef<WebSocket | null>(null);
|
|
106
|
+
const pendingInputRef = useRef("");
|
|
107
|
+
const socketFailedRef = useRef(false);
|
|
108
|
+
const socketAuthenticatedRef = useRef(false);
|
|
76
109
|
// Latest size, so a resize() before the socket opens is replayed on open, and a
|
|
77
110
|
// reconnect seeds the right geometry.
|
|
78
111
|
const sizeRef = useRef<{ cols: number; rows: number }>({
|
|
@@ -89,12 +122,35 @@ export function useTerminalStream(options: UseTerminalStreamOptions): UseTermina
|
|
|
89
122
|
const transport = capability?.transport ?? null;
|
|
90
123
|
const url = capability?.url ?? null;
|
|
91
124
|
const token = capability?.token ?? null;
|
|
125
|
+
const expiresAt = capability?.expiresAt ?? null;
|
|
92
126
|
|
|
93
127
|
useEffect(() => {
|
|
94
128
|
// SSR / no WebSocket / not a live pty-ws cell: stay closed; the caller falls
|
|
95
129
|
// back to the Channel-A read-only firehose.
|
|
96
130
|
if (typeof window === "undefined" || typeof WebSocket === "undefined") return;
|
|
97
|
-
if (transport !== "pty-ws"
|
|
131
|
+
if (transport !== "pty-ws") {
|
|
132
|
+
// A transport downgrade is a real semantic boundary. Do not replay input
|
|
133
|
+
// captured for a PTY if this surface later changes back from firehose mode.
|
|
134
|
+
pendingInputRef.current = "";
|
|
135
|
+
socketAuthenticatedRef.current = false;
|
|
136
|
+
setStatus("closed");
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
// A changed/cleared credential is a new connection attempt boundary. It may
|
|
140
|
+
// receive input while its exact grant is pending even if the prior socket
|
|
141
|
+
// failed; the unchanged failed identity remains fenced because this effect
|
|
142
|
+
// does not re-run for a status-only render.
|
|
143
|
+
socketFailedRef.current = false;
|
|
144
|
+
socketAuthenticatedRef.current = false;
|
|
145
|
+
if (!url) {
|
|
146
|
+
// Exact grants arrive asynchronously. Preserve the bounded first input
|
|
147
|
+
// while the descriptor remains pty-ws, but never open without a credential.
|
|
148
|
+
setStatus("closed");
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
if (!terminalStreamCredentialUsable({ transport, url, expiresAt })) {
|
|
152
|
+
// A descriptor from an older rolling server may still carry a bearer that
|
|
153
|
+
// aged out before this surface mounted. Wait for the exact fresh grant.
|
|
98
154
|
setStatus("closed");
|
|
99
155
|
return;
|
|
100
156
|
}
|
|
@@ -107,6 +163,7 @@ export function useTerminalStream(options: UseTerminalStreamOptions): UseTermina
|
|
|
107
163
|
// it. The scoped token is already in the tunnel `url`.
|
|
108
164
|
socket = new WebSocket(terminalSocketUrl({ url }), TTYD_SUBPROTOCOL);
|
|
109
165
|
} catch {
|
|
166
|
+
socketFailedRef.current = true;
|
|
110
167
|
setStatus("error");
|
|
111
168
|
return;
|
|
112
169
|
}
|
|
@@ -121,8 +178,18 @@ export function useTerminalStream(options: UseTerminalStreamOptions): UseTermina
|
|
|
121
178
|
try {
|
|
122
179
|
socket.send(ttydAuthFrame({ columns: sizeRef.current.cols, rows: sizeRef.current.rows }));
|
|
123
180
|
socket.send(ttydResizeFrame(sizeRef.current.cols, sizeRef.current.rows));
|
|
181
|
+
if (pendingInputRef.current.length > 0) {
|
|
182
|
+
// One websocket frame makes the pre-open queue all-or-nothing from this
|
|
183
|
+
// client's perspective; never execute a prefix of a buffered command.
|
|
184
|
+
socket.send(ttydInputFrame(pendingInputRef.current));
|
|
185
|
+
pendingInputRef.current = "";
|
|
186
|
+
}
|
|
187
|
+
socketAuthenticatedRef.current = true;
|
|
124
188
|
} catch {
|
|
125
|
-
|
|
189
|
+
socketFailedRef.current = true;
|
|
190
|
+
setStatus("error");
|
|
191
|
+
closeSocket(socket);
|
|
192
|
+
return;
|
|
126
193
|
}
|
|
127
194
|
setStatus("open");
|
|
128
195
|
};
|
|
@@ -144,46 +211,60 @@ export function useTerminalStream(options: UseTerminalStreamOptions): UseTermina
|
|
|
144
211
|
};
|
|
145
212
|
|
|
146
213
|
socket.onerror = () => {
|
|
147
|
-
if (!disposed)
|
|
214
|
+
if (!disposed) {
|
|
215
|
+
socketFailedRef.current = true;
|
|
216
|
+
setStatus("error");
|
|
217
|
+
}
|
|
148
218
|
};
|
|
149
219
|
socket.onclose = () => {
|
|
150
|
-
if (
|
|
220
|
+
if (wsRef.current === socket) wsRef.current = null;
|
|
221
|
+
socketAuthenticatedRef.current = false;
|
|
222
|
+
if (!disposed) setStatus(socketFailedRef.current ? "error" : "closed");
|
|
151
223
|
};
|
|
152
224
|
|
|
153
225
|
return () => {
|
|
154
226
|
disposed = true;
|
|
155
227
|
wsRef.current = null;
|
|
228
|
+
socketAuthenticatedRef.current = false;
|
|
156
229
|
// Drop handlers so an in-flight close/error doesn't mutate state post-unmount.
|
|
157
230
|
socket.onopen = null;
|
|
158
231
|
socket.onmessage = null;
|
|
159
232
|
socket.onerror = null;
|
|
160
233
|
socket.onclose = null;
|
|
161
|
-
|
|
162
|
-
socket.close();
|
|
163
|
-
} catch {
|
|
164
|
-
// ignore teardown errors
|
|
165
|
-
}
|
|
234
|
+
closeSocket(socket);
|
|
166
235
|
};
|
|
167
236
|
// A url/token change (rotation) re-runs this effect → close old, open new.
|
|
168
237
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
169
|
-
}, [transport, url, token]);
|
|
238
|
+
}, [transport, url, token, expiresAt]);
|
|
170
239
|
|
|
171
240
|
return useMemo<UseTerminalStreamResult>(() => {
|
|
172
241
|
const write = (data: string) => {
|
|
173
242
|
const ws = wsRef.current;
|
|
174
|
-
if (ws && ws.readyState === WebSocket.OPEN) {
|
|
243
|
+
if (ws && ws.readyState === WebSocket.OPEN && socketAuthenticatedRef.current) {
|
|
175
244
|
try {
|
|
176
245
|
ws.send(ttydInputFrame(data));
|
|
177
246
|
} catch {
|
|
178
|
-
|
|
247
|
+
socketFailedRef.current = true;
|
|
248
|
+
setStatus("error");
|
|
249
|
+
closeSocket(ws);
|
|
250
|
+
}
|
|
251
|
+
} else if (transport === "pty-ws" && !socketFailedRef.current) {
|
|
252
|
+
const nextSize = pendingInputRef.current.length + data.length;
|
|
253
|
+
if (nextSize > MAX_PENDING_TERMINAL_INPUT_CODE_UNITS) {
|
|
254
|
+
pendingInputRef.current = "";
|
|
255
|
+
socketFailedRef.current = true;
|
|
256
|
+
setStatus("error");
|
|
257
|
+
closeSocket(ws);
|
|
258
|
+
return;
|
|
179
259
|
}
|
|
260
|
+
pendingInputRef.current += data;
|
|
180
261
|
}
|
|
181
262
|
};
|
|
182
263
|
const resize = (cols: number, rows: number) => {
|
|
183
264
|
if (cols <= 0 || rows <= 0) return;
|
|
184
265
|
sizeRef.current = { cols, rows };
|
|
185
266
|
const ws = wsRef.current;
|
|
186
|
-
if (ws && ws.readyState === WebSocket.OPEN) {
|
|
267
|
+
if (ws && ws.readyState === WebSocket.OPEN && socketAuthenticatedRef.current) {
|
|
187
268
|
try {
|
|
188
269
|
ws.send(ttydResizeFrame(cols, rows));
|
|
189
270
|
} catch {
|
|
@@ -194,14 +275,12 @@ export function useTerminalStream(options: UseTerminalStreamOptions): UseTermina
|
|
|
194
275
|
const disconnect = () => {
|
|
195
276
|
const ws = wsRef.current;
|
|
196
277
|
wsRef.current = null;
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
}
|
|
203
|
-
}
|
|
278
|
+
pendingInputRef.current = "";
|
|
279
|
+
socketFailedRef.current = false;
|
|
280
|
+
socketAuthenticatedRef.current = false;
|
|
281
|
+
setStatus("closed");
|
|
282
|
+
closeSocket(ws);
|
|
204
283
|
};
|
|
205
284
|
return { connected: status === "open", status, write, resize, disconnect };
|
|
206
|
-
}, [status]);
|
|
285
|
+
}, [status, transport]);
|
|
207
286
|
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { useMemo } from "react";
|
|
2
|
+
import type { VideoArtifactPlaybackLoader } from "../timeline";
|
|
3
|
+
import { useOpenGeni, type ClientOverride } from "../session-context";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Bind the current public SDK client/workspace to MessageTimeline's renewable
|
|
7
|
+
* video-source boundary. Proxy hosts may omit the method until they support
|
|
8
|
+
* retained video; the failure remains local to the player instead of loading
|
|
9
|
+
* any video bytes through React.
|
|
10
|
+
*/
|
|
11
|
+
export function useVideoArtifactPlaybackLoader(
|
|
12
|
+
options: ClientOverride = {},
|
|
13
|
+
): VideoArtifactPlaybackLoader {
|
|
14
|
+
const { client, workspaceId } = useOpenGeni(options);
|
|
15
|
+
return useMemo(
|
|
16
|
+
() => async (artifactId: string, signal?: AbortSignal) => {
|
|
17
|
+
if (typeof client.createVideoArtifactPlaybackSource !== "function") {
|
|
18
|
+
throw new Error("This OpenGeni host does not expose retained-video playback.");
|
|
19
|
+
}
|
|
20
|
+
return await client.createVideoArtifactPlaybackSource(
|
|
21
|
+
workspaceId,
|
|
22
|
+
artifactId,
|
|
23
|
+
signal ? { signal } : {},
|
|
24
|
+
);
|
|
25
|
+
},
|
|
26
|
+
[client, workspaceId],
|
|
27
|
+
);
|
|
28
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { useCallback, useEffect, useRef, useState } from "react";
|
|
2
2
|
import { useOpenGeni, type ClientOverride } from "../provider";
|
|
3
|
+
import { sandboxAcceptsLiveIo } from "../lib/sandbox-liveness";
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* The wake-on-edit state machine. This hook owns the logic and the UI renders
|
|
@@ -73,7 +74,7 @@ export type UseWorkspaceEditResult = {
|
|
|
73
74
|
};
|
|
74
75
|
|
|
75
76
|
function isLive(liveness: string | undefined): boolean {
|
|
76
|
-
return liveness
|
|
77
|
+
return sandboxAcceptsLiveIo(liveness);
|
|
77
78
|
}
|
|
78
79
|
|
|
79
80
|
/**
|
package/src/index.ts
CHANGED
|
@@ -9,9 +9,12 @@
|
|
|
9
9
|
// container-responsive composers additionally import responsive.css.
|
|
10
10
|
|
|
11
11
|
export type {
|
|
12
|
+
EmbeddedBrowserInteractionClientLike,
|
|
13
|
+
EmbeddedComputerInteractionClientLike,
|
|
12
14
|
EmbeddedFileAttachmentClientLike,
|
|
13
15
|
EmbeddedGoalClientLike,
|
|
14
16
|
EmbeddedHumanInputSessionClientLike,
|
|
17
|
+
EmbeddedInteractionClientLike,
|
|
15
18
|
EmbeddedSessionEventClientLike,
|
|
16
19
|
EmbeddedSessionLineageClientLike,
|
|
17
20
|
EmbeddedSessionMcpApprovalPolicyClientLike,
|
|
@@ -22,6 +25,7 @@ export type {
|
|
|
22
25
|
export { OpenGeniProvider } from "./provider";
|
|
23
26
|
export type { OpenGeniProviderProps } from "./provider";
|
|
24
27
|
export { useOpenGeni, useOpenGeniClient } from "./session-context";
|
|
28
|
+
export { useVideoArtifactPlaybackLoader } from "./hooks/use-video-artifact-playback";
|
|
25
29
|
export type { ClientOverride, OpenGeniContextValue } from "./session-context";
|
|
26
30
|
export { usePageLiveActivity } from "./hooks/internal";
|
|
27
31
|
|
|
@@ -332,6 +336,9 @@ export {
|
|
|
332
336
|
} from "./timeline";
|
|
333
337
|
export type {
|
|
334
338
|
CreateToolRegistryOptions,
|
|
339
|
+
RetainedArtifactLoader,
|
|
340
|
+
RetainedScreenshotLoader,
|
|
341
|
+
VideoArtifactPlaybackLoader,
|
|
335
342
|
ToolRegistry,
|
|
336
343
|
ToolRegistryEntry,
|
|
337
344
|
ToolRenderer,
|
|
@@ -464,6 +471,8 @@ export {
|
|
|
464
471
|
} from "./model-policy";
|
|
465
472
|
export type { LatencyModeId, PickerBillingClass, PickerModelRow } from "./model-policy";
|
|
466
473
|
export { MessageTimeline, TimelineRow } from "./components/message-timeline";
|
|
474
|
+
export { GeneratedVideoPlayer } from "./components/generated-video-player";
|
|
475
|
+
export type { GeneratedVideoPlayerProps } from "./components/generated-video-player";
|
|
467
476
|
export type { MessageTimelineProps } from "./components/message-timeline";
|
|
468
477
|
export { UserMessageBody, userMessageLikelyNeedsDisclosure } from "./components/user-message-body";
|
|
469
478
|
export type { UserMessageBodyProps } from "./components/user-message-body";
|
|
@@ -511,6 +520,7 @@ export {
|
|
|
511
520
|
WORKBENCH_TAB_CHANGES,
|
|
512
521
|
WORKBENCH_TAB_FILES,
|
|
513
522
|
WORKBENCH_TAB_TERMINAL,
|
|
523
|
+
WORKBENCH_TAB_BROWSER,
|
|
514
524
|
WORKBENCH_TAB_DESKTOP,
|
|
515
525
|
WORKBENCH_SURFACES,
|
|
516
526
|
} from "./components/sandbox-workspace";
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
// @opengeni/react/interaction — standalone Browser/Computer resource UI.
|
|
2
|
+
// Framework hosts may pass a narrow structural client instead of the full provider.
|
|
3
|
+
export type {
|
|
4
|
+
EmbeddedBrowserInteractionClientLike,
|
|
5
|
+
EmbeddedComputerInteractionClientLike,
|
|
6
|
+
EmbeddedInteractionClientLike,
|
|
7
|
+
} from "./client";
|
|
8
|
+
export {
|
|
9
|
+
useEmbeddedBrowserInteraction,
|
|
10
|
+
useEmbeddedComputerInteraction,
|
|
11
|
+
useEmbeddedInteraction,
|
|
12
|
+
} from "./session-context";
|
|
13
|
+
export type {
|
|
14
|
+
EmbeddedBrowserInteractionClientOverride,
|
|
15
|
+
EmbeddedComputerInteractionClientOverride,
|
|
16
|
+
EmbeddedInteractionClientOverride,
|
|
17
|
+
} from "./session-context";
|
|
18
|
+
export { useAttachedBrowsers } from "./hooks/use-attached-browsers";
|
|
19
|
+
export type {
|
|
20
|
+
AttachedBrowsersClient,
|
|
21
|
+
UseAttachedBrowsersOptions,
|
|
22
|
+
UseAttachedBrowsersResult,
|
|
23
|
+
} from "./hooks/use-attached-browsers";
|
|
24
|
+
export { useBrowserSessions } from "./hooks/use-browser-sessions";
|
|
25
|
+
export type {
|
|
26
|
+
BrowserSessionsClient,
|
|
27
|
+
UseBrowserSessionsOptions,
|
|
28
|
+
UseBrowserSessionsResult,
|
|
29
|
+
} from "./hooks/use-browser-sessions";
|
|
30
|
+
export { useBrowserIdentities } from "./hooks/use-browser-identities";
|
|
31
|
+
export type {
|
|
32
|
+
BrowserIdentitiesClient,
|
|
33
|
+
UseBrowserIdentitiesOptions,
|
|
34
|
+
UseBrowserIdentitiesResult,
|
|
35
|
+
} from "./hooks/use-browser-identities";
|
|
36
|
+
export { useBrowserSession } from "./hooks/use-browser-session";
|
|
37
|
+
export type {
|
|
38
|
+
UseBrowserSessionOptions,
|
|
39
|
+
UseBrowserSessionResult,
|
|
40
|
+
} from "./hooks/use-browser-session";
|
|
41
|
+
export { useBrowserFrameStream } from "./hooks/use-browser-frame-stream";
|
|
42
|
+
export type {
|
|
43
|
+
BrowserFrameConnectionState,
|
|
44
|
+
BrowserFrameWebSocket,
|
|
45
|
+
BrowserFrameWebSocketFactory,
|
|
46
|
+
UseBrowserFrameStreamOptions,
|
|
47
|
+
UseBrowserFrameStreamResult,
|
|
48
|
+
} from "./hooks/use-browser-frame-stream";
|
|
49
|
+
export { BrowserViewer } from "./components/browser-viewer";
|
|
50
|
+
export type { BrowserViewerNotification, BrowserViewerProps } from "./components/browser-viewer";
|
|
51
|
+
export { useComputerSessions } from "./hooks/use-computer-sessions";
|
|
52
|
+
export type {
|
|
53
|
+
ComputerSessionsClient,
|
|
54
|
+
UseComputerSessionsOptions,
|
|
55
|
+
UseComputerSessionsResult,
|
|
56
|
+
} from "./hooks/use-computer-sessions";
|
|
57
|
+
export { useComputerSession } from "./hooks/use-computer-session";
|
|
58
|
+
export type {
|
|
59
|
+
UseComputerSessionOptions,
|
|
60
|
+
UseComputerSessionResult,
|
|
61
|
+
} from "./hooks/use-computer-session";
|
|
62
|
+
export { useComputerFrameStream } from "./hooks/use-computer-frame-stream";
|
|
63
|
+
export type {
|
|
64
|
+
ComputerFrameConnectionState,
|
|
65
|
+
ComputerFrameWebSocket,
|
|
66
|
+
ComputerFrameWebSocketFactory,
|
|
67
|
+
UseComputerFrameStreamOptions,
|
|
68
|
+
UseComputerFrameStreamResult,
|
|
69
|
+
} from "./hooks/use-computer-frame-stream";
|
|
70
|
+
export { ComputerViewer } from "./components/computer-viewer";
|
|
71
|
+
export type { ComputerViewerNotification, ComputerViewerProps } from "./components/computer-viewer";
|
package/src/lib/relay-wire.ts
CHANGED
|
@@ -17,6 +17,10 @@ import { BinaryReader, BinaryWriter } from "@bufbuild/protobuf/wire";
|
|
|
17
17
|
|
|
18
18
|
/** `StreamKind.STREAM_KIND_DESKTOP` (agent-proto enum value). */
|
|
19
19
|
export const STREAM_KIND_DESKTOP = 2;
|
|
20
|
+
/** `StreamKind.STREAM_KIND_BROWSER` (agent-proto enum value). */
|
|
21
|
+
export const STREAM_KIND_BROWSER = 3;
|
|
22
|
+
/** `StreamKind.STREAM_KIND_COMPUTER` (agent-proto enum value). */
|
|
23
|
+
export const STREAM_KIND_COMPUTER = 4;
|
|
20
24
|
/** `StreamRole.STREAM_ROLE_CLIENT` (agent-proto enum value). */
|
|
21
25
|
export const STREAM_ROLE_CLIENT = 2;
|
|
22
26
|
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A sandbox is available for new I/O only after a holder has observed `warm`.
|
|
3
|
+
*
|
|
4
|
+
* `draining` means the holder count is zero and teardown owns the provider
|
|
5
|
+
* instance. The provider may disappear before the durable lease reaches `cold`,
|
|
6
|
+
* so treating `draining` as live races capture/termination. Explicit user intent
|
|
7
|
+
* reacquires a holder; only its subsequent `warm` state re-enables live I/O.
|
|
8
|
+
*/
|
|
9
|
+
export function sandboxAcceptsLiveIo(liveness: string | null | undefined): boolean {
|
|
10
|
+
return liveness === "warm";
|
|
11
|
+
}
|