@opengeni/react 0.48.0 → 0.57.0
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 +138 -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 +199 -0
- package/dist/artifacts.js.map +1 -0
- package/dist/{chunk-NJ7M2B6Z.js → chunk-5UQPNLIO.js} +16 -8
- package/dist/{chunk-NJ7M2B6Z.js.map → chunk-5UQPNLIO.js.map} +1 -1
- package/dist/{chunk-LHLYWG5R.js → chunk-6KLX2RH5.js} +461 -38
- package/dist/chunk-6KLX2RH5.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-B6WPZSTT.js} +104 -104
- package/dist/{chunk-I4MPFDRP.js.map → chunk-B6WPZSTT.js.map} +1 -1
- package/dist/{chunk-EKZH6IDG.js → chunk-C5ZPSSS5.js} +111 -1
- package/dist/chunk-C5ZPSSS5.js.map +1 -0
- package/dist/{chunk-OL6QVXGF.js → chunk-HA46CYQK.js} +152 -8
- package/dist/chunk-HA46CYQK.js.map +1 -0
- package/dist/chunk-HUIKIDM5.js +2148 -0
- package/dist/chunk-HUIKIDM5.js.map +1 -0
- package/dist/{chunk-FWXGSNXT.js → chunk-IGC6A2ZI.js} +585 -34
- package/dist/chunk-IGC6A2ZI.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-LLOLUHVG.js +5937 -0
- package/dist/chunk-LLOLUHVG.js.map +1 -0
- package/dist/{chunk-WYA65Y3F.js → chunk-MKPIAGVL.js} +13 -3
- package/dist/chunk-MKPIAGVL.js.map +1 -0
- package/dist/chunk-PEI6RANI.js +3234 -0
- package/dist/chunk-PEI6RANI.js.map +1 -0
- package/dist/chunk-R3AZOAB5.js +933 -0
- package/dist/chunk-R3AZOAB5.js.map +1 -0
- package/dist/{chunk-O4VXOBGF.js → chunk-WSK7G5LE.js} +70 -2
- package/dist/chunk-WSK7G5LE.js.map +1 -0
- package/dist/{chunk-23EJ676W.js → chunk-XBZWOM2K.js} +9 -4
- package/dist/chunk-XBZWOM2K.js.map +1 -0
- package/dist/{chunk-XCNTXWBB.js → chunk-Y7OEDUJ5.js} +856 -338
- package/dist/chunk-Y7OEDUJ5.js.map +1 -0
- package/dist/client.d.ts +10 -2
- 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 +9 -0
- package/dist/components/artifacts/presentation-editor.d.ts +297 -0
- package/dist/components/artifacts/published-html-artifact-frame.d.ts +14 -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-input.d.ts +7 -0
- package/dist/components/browser-viewer.d.ts +30 -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 +24 -0
- package/dist/components/copy-button.d.ts +2 -2
- package/dist/components/desktop-viewer.d.ts +50 -22
- 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/interaction-intervention-banner.d.ts +11 -0
- 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 +24 -4
- package/dist/components/sandbox-workspace.d.ts +28 -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 +7 -7
- package/dist/hooks/internal.d.ts +1 -1
- package/dist/hooks/use-attached-browsers.d.ts +21 -0
- package/dist/hooks/use-auth-runs.d.ts +32 -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-downloads.d.ts +25 -0
- package/dist/hooks/use-browser-frame-stream.d.ts +28 -0
- package/dist/hooks/use-browser-identities.d.ts +30 -0
- package/dist/hooks/use-browser-session.d.ts +29 -0
- package/dist/hooks/use-browser-sessions.d.ts +32 -0
- package/dist/hooks/use-channels.d.ts +26 -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 +29 -0
- package/dist/hooks/use-computer-sessions.d.ts +28 -0
- package/dist/hooks/use-desktop-stream.d.ts +5 -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-interaction-interventions.d.ts +27 -0
- package/dist/hooks/use-interaction-invalidation.d.ts +19 -0
- 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-network-routes.d.ts +25 -0
- 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-site-auth-connections.d.ts +25 -0
- package/dist/hooks/use-slash-commands.d.ts +1 -1
- package/dist/hooks/use-supergrok-accounts.d.ts +26 -0
- package/dist/hooks/use-terminal-stream.d.ts +11 -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 +162 -155
- package/dist/index.js +1822 -1703
- package/dist/index.js.map +1 -1
- package/dist/interaction.d.ts +33 -0
- package/dist/interaction.js +550 -0
- package/dist/interaction.js.map +1 -0
- package/dist/interactive-workbench-viewers-TOHBXJEE.js +15 -0
- package/dist/interactive-workbench-viewers-TOHBXJEE.js.map +1 -0
- package/dist/lib/host-platform.d.ts +5 -0
- package/dist/lib/relay-wire.d.ts +24 -1
- 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 +2 -2
- package/dist/model-policy.d.ts +1 -1
- package/dist/model-policy.js +1 -1
- package/dist/provider.d.ts +6 -5
- 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 +8 -8
- package/dist/realtime.d.ts +4 -4
- package/dist/realtime.js +38 -11
- package/dist/realtime.js.map +1 -1
- package/dist/session-context.d.ts +28 -2
- 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 +10 -10
- 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 +44 -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 +89 -0
- package/src/client.ts +157 -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 +104 -0
- package/src/components/artifacts/presentation-editor.tsx +3148 -0
- package/src/components/artifacts/published-html-artifact-frame.tsx +38 -0
- package/src/components/artifacts/spreadsheet-canvas.ts +799 -0
- package/src/components/artifacts/spreadsheet-grid.tsx +2129 -0
- package/src/components/browser-input.ts +65 -0
- package/src/components/browser-viewer.tsx +2702 -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 +1636 -0
- package/src/components/desktop-viewer.tsx +215 -74
- package/src/components/generated-video-player.tsx +89 -0
- package/src/components/interaction-intervention-banner.tsx +99 -0
- package/src/components/interactive-workbench-viewers.ts +2 -0
- package/src/components/machine-input-display.ts +3 -0
- package/src/components/machines/machine-detail.tsx +1 -1
- package/src/components/message-timeline.tsx +289 -32
- package/src/components/model-policy-picker.tsx +15 -0
- package/src/components/queue-surface-implementation.tsx +116 -60
- package/src/components/sandbox-files.tsx +2 -2
- package/src/components/sandbox-terminal.tsx +128 -46
- package/src/components/sandbox-workspace.tsx +256 -90
- 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/components/workspace-dock.tsx +59 -5
- package/src/hooks/use-attached-browsers.ts +154 -0
- package/src/hooks/use-auth-runs.ts +278 -0
- package/src/hooks/use-browser-downloads.ts +218 -0
- package/src/hooks/use-browser-frame-stream.ts +425 -0
- package/src/hooks/use-browser-identities.ts +266 -0
- package/src/hooks/use-browser-session.ts +543 -0
- package/src/hooks/use-browser-sessions.ts +286 -0
- package/src/hooks/use-channels.ts +99 -0
- package/src/hooks/use-composer.ts +769 -24
- package/src/hooks/use-computer-frame-stream.ts +434 -0
- package/src/hooks/use-computer-session.ts +462 -0
- package/src/hooks/use-computer-sessions.ts +251 -0
- package/src/hooks/use-desktop-stream.ts +39 -7
- package/src/hooks/use-interaction-interventions.ts +242 -0
- package/src/hooks/use-interaction-invalidation.ts +66 -0
- package/src/hooks/use-machine-chip.ts +15 -3
- package/src/hooks/use-machines.ts +33 -3
- package/src/hooks/use-network-routes.ts +214 -0
- 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-site-auth-connections.ts +226 -0
- package/src/hooks/use-supergrok-accounts.ts +81 -0
- package/src/hooks/use-terminal-stream.ts +359 -47
- package/src/hooks/use-video-artifact-playback.ts +28 -0
- package/src/hooks/use-workspace-edit.ts +2 -1
- package/src/index.ts +18 -0
- package/src/interaction.ts +101 -0
- package/src/lib/host-platform.ts +10 -0
- package/src/lib/relay-wire.ts +47 -4
- package/src/lib/sandbox-liveness.ts +11 -0
- package/src/lib/terminal-capability.ts +10 -0
- package/src/model-policy.ts +19 -4
- package/src/provider.tsx +133 -1
- package/src/realtime/realtime-control.tsx +34 -6
- package/src/session-context.ts +156 -1
- package/src/timeline/activity-rail.tsx +25 -3
- package/src/timeline/index.ts +3 -0
- package/src/timeline/parsers.ts +14 -1
- package/src/timeline/projection.ts +269 -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 +57 -3
- package/styles/compiled.css +312 -21
- package/styles/index.css +15 -0
- package/styles/responsive.css +13 -1
- package/dist/chunk-23EJ676W.js.map +0 -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-OL6QVXGF.js.map +0 -1
- package/dist/chunk-WYA65Y3F.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
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
// Changes — review-first git: turn-end capture (cold) or live diff (warm)
|
|
5
5
|
// Files — tree + inline editor (capture-backed cold, live warm)
|
|
6
6
|
// Terminal — interactive xterm wired to the box PTY (Channel-A projection)
|
|
7
|
-
//
|
|
7
|
+
// Browser — workspace-wide BrowserSessions, tabs, live frames, and input
|
|
8
|
+
// Computer — workspace ComputerSessions: native targets, frames, and input
|
|
8
9
|
// plus a machine-state chip in the dock header (the one truthful live/waking/
|
|
9
10
|
// offline indicator) and any host-injected extra tabs (Run/Debug in apps/web).
|
|
10
11
|
//
|
|
@@ -13,13 +14,24 @@
|
|
|
13
14
|
// (no `sonner` import), and every surface renders with package primitives + og
|
|
14
15
|
// tokens only. `apps/web` consumes this through the exact public surface an
|
|
15
16
|
// external embedder uses — that is criterion F1.
|
|
16
|
-
import {
|
|
17
|
+
import {
|
|
18
|
+
lazy,
|
|
19
|
+
type ReactNode,
|
|
20
|
+
Suspense,
|
|
21
|
+
useCallback,
|
|
22
|
+
useEffect,
|
|
23
|
+
useMemo,
|
|
24
|
+
useRef,
|
|
25
|
+
useState,
|
|
26
|
+
} from "react";
|
|
17
27
|
import type { SessionEvent } from "@opengeni/sdk";
|
|
28
|
+
import type { InteractionPlacement } from "@opengeni/sdk/interaction";
|
|
18
29
|
import {
|
|
19
30
|
CircleCheckIcon,
|
|
20
31
|
CpuIcon,
|
|
21
32
|
FileCode2Icon,
|
|
22
33
|
GitCompareArrowsIcon,
|
|
34
|
+
Globe2Icon,
|
|
23
35
|
LoaderCircleIcon,
|
|
24
36
|
MonitorIcon,
|
|
25
37
|
RefreshCwIcon,
|
|
@@ -30,6 +42,9 @@ import {
|
|
|
30
42
|
import { type ClientOverride, useOpenGeni } from "../provider";
|
|
31
43
|
import { cn } from "../lib/cn";
|
|
32
44
|
import { xtermThemeFromTokens } from "../lib/xterm-theme";
|
|
45
|
+
import { sandboxAcceptsLiveIo } from "../lib/sandbox-liveness";
|
|
46
|
+
import type { BrowserFrameWebSocketFactory } from "../hooks/use-browser-frame-stream";
|
|
47
|
+
import type { ComputerFrameWebSocketFactory } from "../hooks/use-computer-frame-stream";
|
|
33
48
|
import { useSessionCapabilities } from "../hooks/use-session-capabilities";
|
|
34
49
|
import { useSandboxFiles } from "../hooks/use-sandbox-files";
|
|
35
50
|
import {
|
|
@@ -45,9 +60,18 @@ import type { MachineView } from "../types/machines";
|
|
|
45
60
|
import { SandboxFiles } from "./sandbox-files";
|
|
46
61
|
import { WorkbenchChanges } from "./workbench-changes";
|
|
47
62
|
import { SandboxTerminal, type XtermTheme } from "./sandbox-terminal";
|
|
48
|
-
import { DesktopViewer } from "./desktop-viewer";
|
|
49
63
|
import { WorkspaceDock, type WorkspaceDockProps, type WorkspaceTab } from "./workspace-dock";
|
|
50
64
|
|
|
65
|
+
const LazyBrowserViewer = lazy(async () => {
|
|
66
|
+
const viewers = await import("./interactive-workbench-viewers");
|
|
67
|
+
return { default: viewers.BrowserViewer };
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
const LazyComputerViewer = lazy(async () => {
|
|
71
|
+
const viewers = await import("./interactive-workbench-viewers");
|
|
72
|
+
return { default: viewers.ComputerViewer };
|
|
73
|
+
});
|
|
74
|
+
|
|
51
75
|
/** A host-routed notification (replaces the app-only `sonner` toast coupling). */
|
|
52
76
|
export type WorkspaceNotification = { kind: "error" | "info"; message: string };
|
|
53
77
|
|
|
@@ -55,6 +79,7 @@ export type WorkspaceNotification = { kind: "error" | "info"; message: string };
|
|
|
55
79
|
export const WORKBENCH_TAB_CHANGES = "changes";
|
|
56
80
|
export const WORKBENCH_TAB_FILES = "files";
|
|
57
81
|
export const WORKBENCH_TAB_TERMINAL = "terminal";
|
|
82
|
+
export const WORKBENCH_TAB_BROWSER = "browser";
|
|
58
83
|
export const WORKBENCH_TAB_DESKTOP = "desktop";
|
|
59
84
|
|
|
60
85
|
/**
|
|
@@ -66,6 +91,7 @@ export const WORKBENCH_SURFACES = [
|
|
|
66
91
|
WORKBENCH_TAB_CHANGES,
|
|
67
92
|
WORKBENCH_TAB_FILES,
|
|
68
93
|
WORKBENCH_TAB_TERMINAL,
|
|
94
|
+
WORKBENCH_TAB_BROWSER,
|
|
69
95
|
WORKBENCH_TAB_DESKTOP,
|
|
70
96
|
] as const;
|
|
71
97
|
|
|
@@ -110,6 +136,18 @@ function WorkbenchTabLabel({ icon, children }: { icon: ReactNode; children: Reac
|
|
|
110
136
|
);
|
|
111
137
|
}
|
|
112
138
|
|
|
139
|
+
function WorkbenchSurfaceLoading({ name }: { name: "Browser" | "Computer" }) {
|
|
140
|
+
return (
|
|
141
|
+
<CenteredState
|
|
142
|
+
icon={
|
|
143
|
+
<LoaderCircleIcon className="size-5 animate-spin motion-reduce:animate-none" aria-hidden />
|
|
144
|
+
}
|
|
145
|
+
>
|
|
146
|
+
<p className="text-og-sm font-medium text-og-fg">Opening {name}</p>
|
|
147
|
+
</CenteredState>
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
|
|
113
151
|
/**
|
|
114
152
|
* Decide a workspace tab from an already-local event log: the newest
|
|
115
153
|
* `workspace.revision.captured` announce carries the change surface stats, so
|
|
@@ -134,7 +172,19 @@ export function initialWorkspaceTab(
|
|
|
134
172
|
bestSeq = event.sequence;
|
|
135
173
|
bestFileCount = typeof stats?.fileCount === "number" ? stats.fileCount : 0;
|
|
136
174
|
}
|
|
137
|
-
|
|
175
|
+
if (bestFileCount > 0) return WORKBENCH_TAB_CHANGES;
|
|
176
|
+
|
|
177
|
+
let latestGitSeq = -1;
|
|
178
|
+
let committedOnly = false;
|
|
179
|
+
for (const event of events ?? []) {
|
|
180
|
+
if (event.type !== "git.changed" || event.sequence > bestSeq) continue;
|
|
181
|
+
if (event.sequence <= latestGitSeq) continue;
|
|
182
|
+
const payload = event.payload as { ahead?: number; reason?: string } | null;
|
|
183
|
+
latestGitSeq = event.sequence;
|
|
184
|
+
committedOnly =
|
|
185
|
+
(typeof payload?.ahead === "number" && payload.ahead > 0) || payload?.reason === "commit";
|
|
186
|
+
}
|
|
187
|
+
return committedOnly ? WORKBENCH_TAB_CHANGES : WORKBENCH_TAB_FILES;
|
|
138
188
|
}
|
|
139
189
|
|
|
140
190
|
/**
|
|
@@ -186,7 +236,7 @@ export type UseSandboxWorkspaceTabsOptions = ClientOverride & {
|
|
|
186
236
|
/** Live event log (usually `useSessionEvents().events`). */
|
|
187
237
|
events: SessionEvent[];
|
|
188
238
|
/**
|
|
189
|
-
* Built-in surfaces this host wants to expose. Omit for all
|
|
239
|
+
* Built-in surfaces this host wants to expose. Omit for all five. A disabled
|
|
190
240
|
* surface is behaviorally dormant: its data hooks, warm intents, stream
|
|
191
241
|
* attachment, and navigation callbacks are not activated.
|
|
192
242
|
*/
|
|
@@ -196,9 +246,16 @@ export type UseSandboxWorkspaceTabsOptions = ClientOverride & {
|
|
|
196
246
|
initialTab?: string | null | undefined;
|
|
197
247
|
/** Actually selected dock tab. Inactive data surfaces retain cache but issue no live reads. */
|
|
198
248
|
activeTab?: string | null | undefined;
|
|
249
|
+
/** Whether the dock is visible. Hidden docks retain UI state but must not keep
|
|
250
|
+
* expensive browser/computer media streams alive. */
|
|
251
|
+
workspaceVisible?: boolean | undefined;
|
|
199
252
|
/** Host-routed notifications (mutation errors, desktop-consent failures). The
|
|
200
253
|
* package never imports a toast library — the host decides how to surface. */
|
|
201
254
|
onNotify?: ((notification: WorkspaceNotification) => void) | undefined;
|
|
255
|
+
/** Alternate frame transport for non-browser runtimes and deterministic tests. */
|
|
256
|
+
browserWebSocketFactory?: BrowserFrameWebSocketFactory | undefined;
|
|
257
|
+
/** Alternate Computer frame transport for non-browser runtimes and deterministic tests. */
|
|
258
|
+
computerWebSocketFactory?: ComputerFrameWebSocketFactory | undefined;
|
|
202
259
|
/** File requested by a Changes guard. The Files surface defers the read until
|
|
203
260
|
* the sandbox is live, then reveals this path. */
|
|
204
261
|
requestedFilePath?: string | null | undefined;
|
|
@@ -207,10 +264,14 @@ export type UseSandboxWorkspaceTabsOptions = ClientOverride & {
|
|
|
207
264
|
requestedFileRequestId?: string | number | null | undefined;
|
|
208
265
|
/** Route a guarded diff into the host's Files tab. */
|
|
209
266
|
onOpenFile?: ((path: string) => void) | undefined;
|
|
267
|
+
/** Navigate the host dock to one exact linked ComputerSession. */
|
|
268
|
+
onOpenComputerSession?: ((computerSessionId: string) => void) | undefined;
|
|
269
|
+
requestedComputerSessionId?: string | null | undefined;
|
|
270
|
+
requestedComputerRequestId?: string | number | null | undefined;
|
|
210
271
|
};
|
|
211
272
|
|
|
212
273
|
export type UseSandboxWorkspaceTabsResult = {
|
|
213
|
-
/** Changes | Files | Terminal | Desktop (capability-gated where noted). */
|
|
274
|
+
/** Changes | Files | Terminal | Browser | Desktop (capability-gated where noted). */
|
|
214
275
|
tabs: WorkspaceTab[];
|
|
215
276
|
/** The source-driven default tab: Changes when the first authoritative capture
|
|
216
277
|
* or live Git result has changes, else Files (a host `initialTab` overrides).
|
|
@@ -223,7 +284,6 @@ export type UseSandboxWorkspaceTabsResult = {
|
|
|
223
284
|
|
|
224
285
|
type SessionWarmIntents = {
|
|
225
286
|
sessionId: string;
|
|
226
|
-
watchDesktop: boolean;
|
|
227
287
|
warmTerminal: boolean;
|
|
228
288
|
warmFiles: boolean;
|
|
229
289
|
};
|
|
@@ -231,7 +291,6 @@ type SessionWarmIntents = {
|
|
|
231
291
|
function emptyWarmIntents(sessionId: string): SessionWarmIntents {
|
|
232
292
|
return {
|
|
233
293
|
sessionId,
|
|
234
|
-
watchDesktop: false,
|
|
235
294
|
warmTerminal: false,
|
|
236
295
|
warmFiles: false,
|
|
237
296
|
};
|
|
@@ -240,24 +299,56 @@ function emptyWarmIntents(sessionId: string): SessionWarmIntents {
|
|
|
240
299
|
/**
|
|
241
300
|
* Build the workbench tabs + the machine-chip model for one session. This is the
|
|
242
301
|
* dock "brain": capability negotiation, capture-backed cold reads, prewarm
|
|
243
|
-
* flags, desktop
|
|
302
|
+
* flags, automatic desktop acknowledgment, and the xterm theme observer — all
|
|
303
|
+
* package-local.
|
|
244
304
|
*/
|
|
245
305
|
export function useSandboxWorkspaceTabs(
|
|
246
306
|
options: UseSandboxWorkspaceTabsOptions,
|
|
247
307
|
): UseSandboxWorkspaceTabsResult {
|
|
248
|
-
const { client, workspaceId } = useOpenGeni(options);
|
|
249
|
-
const {
|
|
250
|
-
|
|
308
|
+
const { client, workspaceId, workspaceInteractionEvent } = useOpenGeni(options);
|
|
309
|
+
const {
|
|
310
|
+
sessionId,
|
|
311
|
+
events,
|
|
312
|
+
onNotify,
|
|
313
|
+
browserWebSocketFactory,
|
|
314
|
+
computerWebSocketFactory,
|
|
315
|
+
requestedFilePath,
|
|
316
|
+
requestedFileRequestId,
|
|
317
|
+
onOpenFile,
|
|
318
|
+
onOpenComputerSession,
|
|
319
|
+
requestedComputerSessionId,
|
|
320
|
+
requestedComputerRequestId,
|
|
321
|
+
} = options;
|
|
251
322
|
const initialTab = options.initialTab ?? null;
|
|
252
323
|
const activeTab = options.activeTab ?? initialTab;
|
|
324
|
+
const workspaceVisible = options.workspaceVisible ?? true;
|
|
253
325
|
const requestedSurfaces = options.surfaces ?? WORKBENCH_SURFACES;
|
|
254
326
|
const surfaceSet = new Set<SandboxWorkspaceSurface>(requestedSurfaces);
|
|
255
327
|
const changesEnabled = surfaceSet.has(WORKBENCH_TAB_CHANGES);
|
|
256
328
|
const filesEnabled = surfaceSet.has(WORKBENCH_TAB_FILES);
|
|
257
329
|
const terminalEnabled = surfaceSet.has(WORKBENCH_TAB_TERMINAL);
|
|
330
|
+
const browserEnabled = surfaceSet.has(WORKBENCH_TAB_BROWSER);
|
|
258
331
|
const desktopEnabled = surfaceSet.has(WORKBENCH_TAB_DESKTOP);
|
|
332
|
+
const createLinkedComputer = useCallback(
|
|
333
|
+
async (name: string, placement?: InteractionPlacement) => {
|
|
334
|
+
const response = await client.createComputerSession(workspaceId, {
|
|
335
|
+
operationId: crypto.randomUUID(),
|
|
336
|
+
sessionId,
|
|
337
|
+
name,
|
|
338
|
+
...(placement ? { placement } : {}),
|
|
339
|
+
});
|
|
340
|
+
if (response.operation.state !== "completed" || response.session.lifecycle !== "active") {
|
|
341
|
+
throw new Error(response.operation.error?.message ?? "The computer could not be opened.");
|
|
342
|
+
}
|
|
343
|
+
return {
|
|
344
|
+
id: response.session.id,
|
|
345
|
+
placement: response.session.placement,
|
|
346
|
+
};
|
|
347
|
+
},
|
|
348
|
+
[client, sessionId, workspaceId],
|
|
349
|
+
);
|
|
259
350
|
const workspaceDataEnabled = changesEnabled || filesEnabled;
|
|
260
|
-
const
|
|
351
|
+
const machineSurfaceEnabled = workspaceDataEnabled || terminalEnabled || desktopEnabled;
|
|
261
352
|
// Do not speculatively activate both remote workspace surfaces while the dock
|
|
262
353
|
// is still resolving its selected tab. Modal calls cannot be cancelled after
|
|
263
354
|
// dispatch, so that transient state otherwise creates an aborted duplicate
|
|
@@ -267,17 +358,17 @@ export function useSandboxWorkspaceTabs(
|
|
|
267
358
|
const filesActive = resolvedActiveTab === WORKBENCH_TAB_FILES;
|
|
268
359
|
const surfaceIdentity = WORKBENCH_SURFACES.filter((surface) => surfaceSet.has(surface)).join(",");
|
|
269
360
|
|
|
270
|
-
// The
|
|
361
|
+
// The two box-warming INTENTS, each off by default and each
|
|
271
362
|
// flipped true by a genuine user action (never on mount, never on a passive
|
|
272
|
-
// capture glance):
|
|
273
|
-
//
|
|
363
|
+
// capture glance): terminal engagement (`onActivate`) and a deliberate
|
|
364
|
+
// live-file open/edit in Files. Browsing capture-served
|
|
274
365
|
// Changes/Files warms nothing — that is the whole point of Refinement 1.
|
|
275
366
|
const [storedWarmIntents, setStoredWarmIntents] = useState<SessionWarmIntents>(() =>
|
|
276
367
|
emptyWarmIntents(sessionId),
|
|
277
368
|
);
|
|
278
369
|
const warmIntents =
|
|
279
370
|
storedWarmIntents.sessionId === sessionId ? storedWarmIntents : emptyWarmIntents(sessionId);
|
|
280
|
-
const {
|
|
371
|
+
const { warmTerminal, warmFiles } = warmIntents;
|
|
281
372
|
const requestWarmIntent = useCallback(
|
|
282
373
|
(intent: Exclude<keyof SessionWarmIntents, "sessionId">) => {
|
|
283
374
|
setStoredWarmIntents((previous) => {
|
|
@@ -294,7 +385,7 @@ export function useSandboxWorkspaceTabs(
|
|
|
294
385
|
workspaceId,
|
|
295
386
|
sessionId,
|
|
296
387
|
pollIntervalMs: 8000,
|
|
297
|
-
enabled:
|
|
388
|
+
enabled: machineSurfaceEnabled,
|
|
298
389
|
});
|
|
299
390
|
const activeMachine: MachineView | null =
|
|
300
391
|
machines.machines.find((m) => m.sandboxId === machines.activeSandboxId) ??
|
|
@@ -303,8 +394,8 @@ export function useSandboxWorkspaceTabs(
|
|
|
303
394
|
|
|
304
395
|
const caps = useSessionCapabilities(sessionId, {
|
|
305
396
|
events,
|
|
306
|
-
enabled:
|
|
307
|
-
attachDesktop:
|
|
397
|
+
enabled: machineSurfaceEnabled,
|
|
398
|
+
attachDesktop: false,
|
|
308
399
|
attachTerminal: terminalEnabled && warmTerminal,
|
|
309
400
|
// Explicit live-file intent only — NOT "the Files tab is open". A cold edit
|
|
310
401
|
// or guarded-file open wakes the box; a glance at the tree/diff does not.
|
|
@@ -312,6 +403,11 @@ export function useSandboxWorkspaceTabs(
|
|
|
312
403
|
});
|
|
313
404
|
const capabilities = caps.capabilities;
|
|
314
405
|
const liveness = capabilities?.liveness;
|
|
406
|
+
// A missing capability document is not evidence that the sandbox is live.
|
|
407
|
+
// Pass explicit null to live-I/O hooks so fast capture/event hydration cannot
|
|
408
|
+
// exploit their legacy "liveness omitted" compatibility path while negotiation
|
|
409
|
+
// is still pending.
|
|
410
|
+
const liveIoLiveness = liveness ?? null;
|
|
315
411
|
const fileSystemOn = capabilities?.FileSystem.available ?? false;
|
|
316
412
|
// The FS is writable only when it's live AND not read-only. A self-hosted box
|
|
317
413
|
// that's offline (or any read-only advertisement) or a capture-served cold tree
|
|
@@ -323,26 +419,23 @@ export function useSandboxWorkspaceTabs(
|
|
|
323
419
|
// Mutations are live-only. A capture is a review artifact, never a writable
|
|
324
420
|
// surrogate for a sleeping machine; the user must deliberately open the live
|
|
325
421
|
// workspace before create/rename/delete/edit controls appear.
|
|
326
|
-
const filesEditable =
|
|
327
|
-
fileSystemOn && !fsReadOnly && (liveness === "warm" || liveness === "draining");
|
|
422
|
+
const filesEditable = fileSystemOn && !fsReadOnly && sandboxAcceptsLiveIo(liveness);
|
|
328
423
|
const gitOn = capabilities?.Git.available ?? false;
|
|
329
424
|
const terminalOn = terminalEnabled && (capabilities?.Terminal.transport ?? null) !== null;
|
|
330
|
-
//
|
|
331
|
-
//
|
|
332
|
-
//
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
425
|
+
// A descriptor-only pty-ws cell intentionally has no short-lived URL until
|
|
426
|
+
// terminal intent acquires one. Do not mistake that grant boundary for a
|
|
427
|
+
// legacy HTTP PTY and eagerly open a second terminal. The fallback exists only
|
|
428
|
+
// for a genuinely firehose-transport backend with no typed degradation.
|
|
429
|
+
const ptyCapable =
|
|
430
|
+
warmTerminal &&
|
|
431
|
+
(capabilities?.Terminal.ptyCapable ?? false) &&
|
|
432
|
+
capabilities?.Terminal.transport === "sse-events" &&
|
|
433
|
+
capabilities?.Terminal.reason === null;
|
|
336
434
|
const terminal = useSandboxTerminal(sessionId, {
|
|
337
435
|
events: terminalEnabled ? events : [],
|
|
338
436
|
interactive: terminalEnabled && ptyCapable,
|
|
339
437
|
liveness,
|
|
340
438
|
});
|
|
341
|
-
const desktopAdvertised =
|
|
342
|
-
desktopEnabled &&
|
|
343
|
-
((capabilities?.DesktopStream.transport ?? null) !== null ||
|
|
344
|
-
capabilities?.DesktopStream.reason === "lease_cold");
|
|
345
|
-
|
|
346
439
|
// Lazy provisioning (#315) creates the box mid-turn on the first sandbox tool
|
|
347
440
|
// call, emitting sandbox.provision started→completed/failed on the live stream.
|
|
348
441
|
// The on-demand resting hook rests without polling, so when the box warms the
|
|
@@ -360,6 +453,17 @@ export function useSandboxWorkspaceTabs(
|
|
|
360
453
|
provisioningRef.current = { sessionId, provisioning };
|
|
361
454
|
}, [sessionId, provisioning, renegotiate]);
|
|
362
455
|
|
|
456
|
+
// BrowserSession and ComputerSession lifecycles use the same placement lease
|
|
457
|
+
// as files/terminal/desktop, but are workspace resources rather than session
|
|
458
|
+
// events. Re-read the capability document on their canonical workspace
|
|
459
|
+
// invalidation so the one machine chip cannot remain "Offline" beside a live
|
|
460
|
+
// browser/computer (or remain "Live" after the final interaction holder ends).
|
|
461
|
+
// This is a read-only negotiation: it never acquires a holder or wakes a box.
|
|
462
|
+
useEffect(() => {
|
|
463
|
+
if (workspaceInteractionEvent?.workspaceId !== workspaceId) return;
|
|
464
|
+
renegotiate();
|
|
465
|
+
}, [workspaceId, workspaceInteractionEvent, renegotiate]);
|
|
466
|
+
|
|
363
467
|
// The cold-paint data source: the latest turn-end capture, fetched with a single
|
|
364
468
|
// api round-trip on mount (no machine). Feeds the Files tree + the Changes/Git
|
|
365
469
|
// diff immediately; a warm box reconciles live without discarding the capture
|
|
@@ -369,7 +473,7 @@ export function useSandboxWorkspaceTabs(
|
|
|
369
473
|
enabled: workspaceDataEnabled,
|
|
370
474
|
});
|
|
371
475
|
const captureAvailable = captureState.available;
|
|
372
|
-
const liveWorkspaceExpected = liveness
|
|
476
|
+
const liveWorkspaceExpected = sandboxAcceptsLiveIo(liveness);
|
|
373
477
|
// Capability negotiation and capture resolution are independent requests. A
|
|
374
478
|
// cold capability document can win that race by a few milliseconds; enabling
|
|
375
479
|
// the leaf hooks in that window would make them interpret `capture: null` as a
|
|
@@ -407,7 +511,7 @@ export function useSandboxWorkspaceTabs(
|
|
|
407
511
|
enabled: filesEnabled && (captureAvailable || (fileSystemOn && liveWorkspaceExpected)),
|
|
408
512
|
active: filesActive,
|
|
409
513
|
repoPaths,
|
|
410
|
-
liveness,
|
|
514
|
+
liveness: liveIoLiveness,
|
|
411
515
|
capture: captureState.capture,
|
|
412
516
|
// A reverted optimistic mutation (e.g. a 409 rename collision) surfaces as a
|
|
413
517
|
// host notification — the tree silently rolls back, the user sees why.
|
|
@@ -451,7 +555,7 @@ export function useSandboxWorkspaceTabs(
|
|
|
451
555
|
// dormant, avoiding the old duplicate full-tree read.
|
|
452
556
|
active: changesActive || resolvedActiveTab === null,
|
|
453
557
|
repoPaths,
|
|
454
|
-
liveness,
|
|
558
|
+
liveness: liveIoLiveness,
|
|
455
559
|
comparison: changesComparison,
|
|
456
560
|
capture: changesComparison === "staged" ? null : captureState.capture,
|
|
457
561
|
});
|
|
@@ -484,30 +588,6 @@ export function useSandboxWorkspaceTabs(
|
|
|
484
588
|
return () => observer.disconnect();
|
|
485
589
|
}, [terminalEnabled]);
|
|
486
590
|
|
|
487
|
-
async function acknowledgeAndWatch() {
|
|
488
|
-
try {
|
|
489
|
-
const shared = capabilities?.DesktopStream.shared ?? false;
|
|
490
|
-
await client.acknowledgeStream(workspaceId, sessionId, {
|
|
491
|
-
acknowledgeUnredacted: true,
|
|
492
|
-
acknowledgeShared: shared,
|
|
493
|
-
});
|
|
494
|
-
requestWarmIntent("watchDesktop");
|
|
495
|
-
caps.renegotiate();
|
|
496
|
-
} catch (error) {
|
|
497
|
-
onNotify?.({
|
|
498
|
-
kind: "error",
|
|
499
|
-
message: `Could not start the desktop stream: ${error instanceof Error ? error.message : String(error)}`,
|
|
500
|
-
});
|
|
501
|
-
}
|
|
502
|
-
}
|
|
503
|
-
|
|
504
|
-
// Re-warm WITHOUT re-acknowledging: the consent was already recorded, only the
|
|
505
|
-
// box drained back to cold. Idempotent — the viewer auto-warm de-dupes.
|
|
506
|
-
function rewarmDesktop() {
|
|
507
|
-
if (!watchDesktop) requestWarmIntent("watchDesktop");
|
|
508
|
-
caps.renegotiate();
|
|
509
|
-
}
|
|
510
|
-
|
|
511
591
|
const dirtyCount = git.diff.length;
|
|
512
592
|
|
|
513
593
|
// The one truthful machine indicator, derived from the live capability/liveness
|
|
@@ -517,10 +597,7 @@ export function useSandboxWorkspaceTabs(
|
|
|
517
597
|
capabilitiesState: caps.state,
|
|
518
598
|
activeMachineState: activeMachine?.state ?? null,
|
|
519
599
|
activeIsSelfhosted: activeMachine?.kind === "selfhosted",
|
|
520
|
-
wantsWarm:
|
|
521
|
-
(terminalEnabled && warmTerminal) ||
|
|
522
|
-
(desktopEnabled && watchDesktop) ||
|
|
523
|
-
(workspaceDataEnabled && warmFiles),
|
|
600
|
+
wantsWarm: (terminalEnabled && warmTerminal) || (workspaceDataEnabled && warmFiles),
|
|
524
601
|
capturedAt: captureState.capturedAt,
|
|
525
602
|
});
|
|
526
603
|
const workspaceWaking = chip.state === "waking";
|
|
@@ -543,7 +620,11 @@ export function useSandboxWorkspaceTabs(
|
|
|
543
620
|
}
|
|
544
621
|
const captureIsAuthoritative =
|
|
545
622
|
!liveWorkspaceExpected && (liveness !== undefined || caps.error !== null);
|
|
546
|
-
const
|
|
623
|
+
const captureHasChanges =
|
|
624
|
+
(captureState.fileCount ?? 0) > 0 ||
|
|
625
|
+
(captureState.capture?.repos.some((repo) => (repo.branchDiff?.length ?? 0) > 0) ?? false);
|
|
626
|
+
const captureUnavailable =
|
|
627
|
+
(captureState.fileCount === 0 && !captureHasChanges) || captureState.error !== null;
|
|
547
628
|
const implicitBranchFallbackPending =
|
|
548
629
|
changesComparison === "branch" &&
|
|
549
630
|
git.error !== null &&
|
|
@@ -563,7 +644,7 @@ export function useSandboxWorkspaceTabs(
|
|
|
563
644
|
captureState.available
|
|
564
645
|
) {
|
|
565
646
|
defaultTabRef.current.value = sourceDrivenDefaultTab(
|
|
566
|
-
|
|
647
|
+
captureHasChanges,
|
|
567
648
|
changesEnabled,
|
|
568
649
|
filesEnabled,
|
|
569
650
|
);
|
|
@@ -586,7 +667,7 @@ export function useSandboxWorkspaceTabs(
|
|
|
586
667
|
// A warm live read failed but the durable capture is intact: retain an
|
|
587
668
|
// immediate, deterministic review surface instead of hanging unresolved.
|
|
588
669
|
defaultTabRef.current.value = sourceDrivenDefaultTab(
|
|
589
|
-
|
|
670
|
+
captureHasChanges,
|
|
590
671
|
changesEnabled,
|
|
591
672
|
filesEnabled,
|
|
592
673
|
);
|
|
@@ -664,7 +745,7 @@ export function useSandboxWorkspaceTabs(
|
|
|
664
745
|
...(requestedFileRequestId !== null && requestedFileRequestId !== undefined
|
|
665
746
|
? { requestedPathRequestId: requestedFileRequestId }
|
|
666
747
|
: {}),
|
|
667
|
-
requestedPathReady: liveness
|
|
748
|
+
requestedPathReady: sandboxAcceptsLiveIo(liveness),
|
|
668
749
|
}
|
|
669
750
|
: {})}
|
|
670
751
|
// Ordinary capture browsing does not warm a box. The first edit — or an
|
|
@@ -683,9 +764,11 @@ export function useSandboxWorkspaceTabs(
|
|
|
683
764
|
content: (
|
|
684
765
|
<div className="h-full bg-og-bg p-1">
|
|
685
766
|
<SandboxTerminal
|
|
767
|
+
key={sessionId}
|
|
686
768
|
result={terminal}
|
|
687
769
|
terminalCapability={capabilities?.Terminal ?? null}
|
|
688
770
|
onActivate={() => requestWarmIntent("warmTerminal")}
|
|
771
|
+
onReconnectNeeded={caps.renegotiate}
|
|
689
772
|
showHeader
|
|
690
773
|
shell={capabilities?.Terminal.shell ?? undefined}
|
|
691
774
|
liveness={liveness}
|
|
@@ -696,25 +779,65 @@ export function useSandboxWorkspaceTabs(
|
|
|
696
779
|
});
|
|
697
780
|
}
|
|
698
781
|
|
|
699
|
-
//
|
|
700
|
-
|
|
782
|
+
// Browser — independent from sandbox capability negotiation. BrowserSessions
|
|
783
|
+
// are workspace resources and may live in this agent's sandbox, a peer's
|
|
784
|
+
// sandbox, a connected machine, or an external browser placement.
|
|
785
|
+
if (browserEnabled) {
|
|
786
|
+
list.push({
|
|
787
|
+
id: WORKBENCH_TAB_BROWSER,
|
|
788
|
+
label: <WorkbenchTabLabel icon={<Globe2Icon />}>Browser</WorkbenchTabLabel>,
|
|
789
|
+
content: (
|
|
790
|
+
<Suspense fallback={<WorkbenchSurfaceLoading name="Browser" />}>
|
|
791
|
+
<LazyBrowserViewer
|
|
792
|
+
key={sessionId}
|
|
793
|
+
client={client}
|
|
794
|
+
workspaceId={workspaceId}
|
|
795
|
+
sessionId={sessionId}
|
|
796
|
+
// WorkspaceDock mounts a heavy surface only after its first visit,
|
|
797
|
+
// then keeps it mounted behind `hidden` when another tab is
|
|
798
|
+
// selected. Keep that already-visited surface enabled while the
|
|
799
|
+
// dock itself remains open: tearing down its controller + media
|
|
800
|
+
// socket on every Browser↔Computer tab switch turned an ordinary
|
|
801
|
+
// click into a multi-second cold reconnect. This does not eagerly
|
|
802
|
+
// start BrowserViewer—the lazy panel is still unmounted until the
|
|
803
|
+
// user/agent first visits it.
|
|
804
|
+
enabled={workspaceVisible}
|
|
805
|
+
onNotify={onNotify}
|
|
806
|
+
{...(desktopEnabled ? { createLinkedComputer } : {})}
|
|
807
|
+
{...(onOpenComputerSession ? { onOpenComputer: onOpenComputerSession } : {})}
|
|
808
|
+
{...(browserWebSocketFactory ? { webSocketFactory: browserWebSocketFactory } : {})}
|
|
809
|
+
className="h-full"
|
|
810
|
+
/>
|
|
811
|
+
</Suspense>
|
|
812
|
+
),
|
|
813
|
+
});
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
// Computer — stable legacy tab id, new workspace resource body. Like Browser,
|
|
817
|
+
// it is independent from the selected agent's sandbox capability document.
|
|
818
|
+
if (desktopEnabled) {
|
|
701
819
|
list.push({
|
|
702
820
|
id: WORKBENCH_TAB_DESKTOP,
|
|
703
|
-
label: <WorkbenchTabLabel icon={<MonitorIcon />}>
|
|
704
|
-
badge: watchDesktop ? (
|
|
705
|
-
<span className="rounded-og-xs bg-og-status-running/20 px-1 text-og-xs text-og-status-running">
|
|
706
|
-
Live
|
|
707
|
-
</span>
|
|
708
|
-
) : undefined,
|
|
821
|
+
label: <WorkbenchTabLabel icon={<MonitorIcon />}>Computer</WorkbenchTabLabel>,
|
|
709
822
|
content: (
|
|
710
|
-
<
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
823
|
+
<Suspense fallback={<WorkbenchSurfaceLoading name="Computer" />}>
|
|
824
|
+
<LazyComputerViewer
|
|
825
|
+
key={sessionId}
|
|
826
|
+
client={client}
|
|
827
|
+
workspaceId={workspaceId}
|
|
828
|
+
sessionId={sessionId}
|
|
829
|
+
// Same visited-surface lifetime as Browser above. A live noVNC
|
|
830
|
+
// connection survives tab switches and ResizeObserver re-fits its
|
|
831
|
+
// canvas when this hidden panel becomes visible again, making the
|
|
832
|
+
// second and later Computer opens local-machine fast.
|
|
833
|
+
enabled={workspaceVisible}
|
|
834
|
+
onNotify={onNotify}
|
|
835
|
+
requestedComputerSessionId={requestedComputerSessionId}
|
|
836
|
+
requestedComputerRequestId={requestedComputerRequestId}
|
|
837
|
+
{...(computerWebSocketFactory ? { webSocketFactory: computerWebSocketFactory } : {})}
|
|
838
|
+
className="h-full"
|
|
839
|
+
/>
|
|
840
|
+
</Suspense>
|
|
718
841
|
),
|
|
719
842
|
});
|
|
720
843
|
}
|
|
@@ -726,14 +849,24 @@ export function useSandboxWorkspaceTabs(
|
|
|
726
849
|
changesEnabled,
|
|
727
850
|
filesEnabled,
|
|
728
851
|
terminalOn,
|
|
729
|
-
|
|
852
|
+
browserEnabled,
|
|
853
|
+
desktopEnabled,
|
|
730
854
|
captureAvailable,
|
|
731
855
|
dirtyCount,
|
|
732
|
-
watchDesktop,
|
|
733
856
|
warmTerminal,
|
|
734
857
|
requestedFilePath,
|
|
735
858
|
requestedFileRequestId,
|
|
736
859
|
onOpenFile,
|
|
860
|
+
onOpenComputerSession,
|
|
861
|
+
requestedComputerSessionId,
|
|
862
|
+
requestedComputerRequestId,
|
|
863
|
+
onNotify,
|
|
864
|
+
browserWebSocketFactory,
|
|
865
|
+
computerWebSocketFactory,
|
|
866
|
+
workspaceVisible,
|
|
867
|
+
resolvedActiveTab,
|
|
868
|
+
client,
|
|
869
|
+
workspaceId,
|
|
737
870
|
liveness,
|
|
738
871
|
sessionId,
|
|
739
872
|
files,
|
|
@@ -747,13 +880,14 @@ export function useSandboxWorkspaceTabs(
|
|
|
747
880
|
caps.error,
|
|
748
881
|
caps.viewerCapReached,
|
|
749
882
|
requestWarmIntent,
|
|
883
|
+
createLinkedComputer,
|
|
750
884
|
]);
|
|
751
885
|
|
|
752
886
|
return {
|
|
753
887
|
tabs,
|
|
754
888
|
defaultTab,
|
|
755
889
|
machine: {
|
|
756
|
-
enabled:
|
|
890
|
+
enabled: machineSurfaceEnabled,
|
|
757
891
|
chip,
|
|
758
892
|
activeMachine,
|
|
759
893
|
loading: machines.loading,
|
|
@@ -771,7 +905,7 @@ export type SandboxWorkspaceProps = ClientOverride & {
|
|
|
771
905
|
primary: ReactNode;
|
|
772
906
|
/**
|
|
773
907
|
* Built-in surfaces this host wants to expose. Omit for Changes, Files,
|
|
774
|
-
* Terminal, and Desktop. Disabled surfaces remain behaviorally dormant.
|
|
908
|
+
* Terminal, Browser, and Desktop. Disabled surfaces remain behaviorally dormant.
|
|
775
909
|
*/
|
|
776
910
|
surfaces?: readonly SandboxWorkspaceSurface[] | undefined;
|
|
777
911
|
/** Host tabs injected BEFORE the workbench tabs (e.g. a "Run" landing tab). */
|
|
@@ -783,6 +917,10 @@ export type SandboxWorkspaceProps = ClientOverride & {
|
|
|
783
917
|
initialTab?: string | undefined;
|
|
784
918
|
/** Host-routed notifications (no toast dependency in the package). */
|
|
785
919
|
onNotify?: ((notification: WorkspaceNotification) => void) | undefined;
|
|
920
|
+
/** Alternate Browser frame transport for non-browser runtimes and deterministic tests. */
|
|
921
|
+
browserWebSocketFactory?: BrowserFrameWebSocketFactory | undefined;
|
|
922
|
+
/** Alternate Computer frame transport for non-browser runtimes and deterministic tests. */
|
|
923
|
+
computerWebSocketFactory?: ComputerFrameWebSocketFactory | undefined;
|
|
786
924
|
/** Controlled collapsed state for hosts with their own dock toggle. */
|
|
787
925
|
collapsed?: boolean | undefined;
|
|
788
926
|
onCollapsedChange?: ((collapsed: boolean) => void) | undefined;
|
|
@@ -814,6 +952,8 @@ export function SandboxWorkspace(props: SandboxWorkspaceProps): ReactNode {
|
|
|
814
952
|
trailingTabs,
|
|
815
953
|
initialTab,
|
|
816
954
|
onNotify,
|
|
955
|
+
browserWebSocketFactory,
|
|
956
|
+
computerWebSocketFactory,
|
|
817
957
|
collapsed,
|
|
818
958
|
onCollapsedChange,
|
|
819
959
|
showCollapseControl,
|
|
@@ -834,7 +974,13 @@ export function SandboxWorkspace(props: SandboxWorkspaceProps): ReactNode {
|
|
|
834
974
|
path: string;
|
|
835
975
|
requestId: number;
|
|
836
976
|
} | null>(null);
|
|
977
|
+
const [requestedComputer, setRequestedComputer] = useState<{
|
|
978
|
+
sessionId: string;
|
|
979
|
+
computerSessionId: string;
|
|
980
|
+
requestId: number;
|
|
981
|
+
} | null>(null);
|
|
837
982
|
const nextFileRequestId = useRef(0);
|
|
983
|
+
const nextComputerRequestId = useRef(0);
|
|
838
984
|
const selectedTab = storedSelection?.sessionId === sessionId ? storedSelection.tab : null;
|
|
839
985
|
const activeTabHint = selectedTab ?? initialTab ?? leadingTabs?.[0]?.id ?? null;
|
|
840
986
|
const openFile = useCallback(
|
|
@@ -845,6 +991,18 @@ export function SandboxWorkspace(props: SandboxWorkspaceProps): ReactNode {
|
|
|
845
991
|
},
|
|
846
992
|
[sessionId],
|
|
847
993
|
);
|
|
994
|
+
const openComputer = useCallback(
|
|
995
|
+
(computerSessionId: string) => {
|
|
996
|
+
nextComputerRequestId.current += 1;
|
|
997
|
+
setRequestedComputer({
|
|
998
|
+
sessionId,
|
|
999
|
+
computerSessionId,
|
|
1000
|
+
requestId: nextComputerRequestId.current,
|
|
1001
|
+
});
|
|
1002
|
+
setStoredSelection({ sessionId, tab: WORKBENCH_TAB_DESKTOP });
|
|
1003
|
+
},
|
|
1004
|
+
[sessionId],
|
|
1005
|
+
);
|
|
848
1006
|
|
|
849
1007
|
const {
|
|
850
1008
|
tabs: workbenchTabs,
|
|
@@ -858,10 +1016,18 @@ export function SandboxWorkspace(props: SandboxWorkspaceProps): ReactNode {
|
|
|
858
1016
|
...(surfaces ? { surfaces } : {}),
|
|
859
1017
|
...(initialTab ? { initialTab } : {}),
|
|
860
1018
|
activeTab: activeTabHint,
|
|
1019
|
+
workspaceVisible: collapsed !== true,
|
|
861
1020
|
...(onNotify ? { onNotify } : {}),
|
|
1021
|
+
...(browserWebSocketFactory ? { browserWebSocketFactory } : {}),
|
|
1022
|
+
...(computerWebSocketFactory ? { computerWebSocketFactory } : {}),
|
|
862
1023
|
requestedFilePath: requestedFile?.sessionId === sessionId ? requestedFile.path : null,
|
|
863
1024
|
requestedFileRequestId: requestedFile?.sessionId === sessionId ? requestedFile.requestId : null,
|
|
864
1025
|
onOpenFile: openFile,
|
|
1026
|
+
onOpenComputerSession: openComputer,
|
|
1027
|
+
requestedComputerSessionId:
|
|
1028
|
+
requestedComputer?.sessionId === sessionId ? requestedComputer.computerSessionId : null,
|
|
1029
|
+
requestedComputerRequestId:
|
|
1030
|
+
requestedComputer?.sessionId === sessionId ? requestedComputer.requestId : null,
|
|
865
1031
|
});
|
|
866
1032
|
|
|
867
1033
|
// A user's tab click wins forever; before that we follow the source-driven
|
|
@@ -929,13 +1095,13 @@ function MachineStateChip({ chip }: { chip: MachineChip }) {
|
|
|
929
1095
|
<div
|
|
930
1096
|
role="status"
|
|
931
1097
|
aria-label={`Machine: ${chip.label}`}
|
|
932
|
-
className="inline-flex min-h-7 items-center gap-1.5 px-2 py-1 text-og-xs font-medium text-og-fg-muted max-[1023px]:min-h-11 pointer-coarse:min-h-11"
|
|
1098
|
+
className="inline-flex min-h-7 min-w-0 items-center gap-1.5 px-2 py-1 text-og-xs font-medium text-og-fg-muted max-[1023px]:min-h-11 pointer-coarse:min-h-11"
|
|
933
1099
|
>
|
|
934
1100
|
<span
|
|
935
1101
|
className={cn("size-1.5 shrink-0 rounded-full", chipDotClass(chip.state))}
|
|
936
1102
|
aria-hidden
|
|
937
1103
|
/>
|
|
938
|
-
<span className="max-w-[11rem] truncate">{chip.label}</span>
|
|
1104
|
+
<span className="min-w-0 max-w-[11rem] truncate">{chip.label}</span>
|
|
939
1105
|
</div>
|
|
940
1106
|
);
|
|
941
1107
|
}
|