@opengeni/react 0.46.1 → 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 +158 -14
- 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-HFO4ERGQ.js → chunk-724BW4TW.js} +586 -35
- 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-U3CSCMWA.js → chunk-FK6VG4LL.js} +9 -13
- package/dist/chunk-FK6VG4LL.js.map +1 -0
- package/dist/{chunk-SRFUT2ZU.js → chunk-FWS3KC3Z.js} +120 -64
- package/dist/chunk-FWS3KC3Z.js.map +1 -0
- 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-LGVMUIRV.js → chunk-JAGDUCZQ.js} +1053 -390
- package/dist/chunk-JAGDUCZQ.js.map +1 -0
- package/dist/{chunk-LWR4MXSS.js → chunk-JR3QFY6B.js} +420 -35
- 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-EB67J347.js → chunk-KZ73RL76.js} +111 -31
- package/dist/chunk-KZ73RL76.js.map +1 -0
- package/dist/chunk-PEI6RANI.js +3234 -0
- package/dist/chunk-PEI6RANI.js.map +1 -0
- package/dist/{chunk-3PB7MIT6.js → chunk-ROIRTM37.js} +220 -72
- package/dist/chunk-ROIRTM37.js.map +1 -0
- package/dist/chunk-VZTQ73XT.js +86 -0
- package/dist/chunk-VZTQ73XT.js.map +1 -0
- package/dist/{chunk-VCBFUSUZ.js → chunk-WSK7G5LE.js} +72 -4
- package/dist/chunk-WSK7G5LE.js.map +1 -0
- package/dist/{chunk-FGZUCXZF.js → chunk-WYA65Y3F.js} +16 -16
- package/dist/chunk-WYA65Y3F.js.map +1 -0
- package/dist/{chunk-6EDV6YFE.js → chunk-X4S56Y2H.js} +147 -35
- package/dist/chunk-X4S56Y2H.js.map +1 -0
- 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 +13 -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 +4 -2
- package/dist/components/composer.d.ts +29 -15
- 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 +5 -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 +5 -5
- package/dist/components/markdown.d.ts +1 -1
- package/dist/components/message-timeline.d.ts +22 -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 +5 -5
- package/dist/components/sandbox-terminal.d.ts +50 -4
- package/dist/components/sandbox-workspace.d.ts +30 -10
- 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 +3 -1
- package/dist/composer.d.ts +11 -11
- package/dist/composer.js +7 -7
- package/dist/hooks/internal.d.ts +7 -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 +3 -3
- 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 +11 -5
- 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 +10 -3
- 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 +25 -4
- package/dist/hooks/use-sandbox-git.d.ts +13 -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-transcription.d.ts +2 -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 +6 -4
- 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 -154
- package/dist/index.js +1718 -908
- 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/composer-responsive-context.d.ts +9 -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/use-portal-token-style.d.ts +14 -3
- 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-J4XJFKRW.js → queue-surface-implementation-2KVJTYXH.js} +117 -64
- 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 +15 -6
- package/dist/realtime.d.ts +4 -4
- package/dist/realtime.js +30 -33
- package/dist/realtime.js.map +1 -1
- package/dist/session-context.d.ts +19 -1
- package/dist/session-ui.d.ts +14 -14
- package/dist/session-ui.js +6 -5
- package/dist/session.d.ts +28 -28
- package/dist/session.js +5 -5
- package/dist/timeline/activity-rail.d.ts +6 -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 +6 -3
- package/dist/timeline/projection.d.ts +3 -3
- package/dist/timeline/registry.d.ts +10 -1
- 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 +42 -2
- 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/dist/voice-recording-store.d.ts +8 -2
- package/package.json +52 -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 +90 -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 +25 -2
- package/src/components/code-editor.tsx +90 -31
- package/src/components/command-palette.tsx +1 -1
- package/src/components/composer-transcription-control.tsx +51 -22
- package/src/components/composer.tsx +49 -23
- package/src/components/computer-viewer.tsx +1139 -0
- package/src/components/copy-button.tsx +1 -1
- package/src/components/desktop-viewer.tsx +103 -75
- package/src/components/file-browser.tsx +39 -17
- package/src/components/generated-video-player.tsx +89 -0
- package/src/components/human-input-form.tsx +2 -2
- package/src/components/interactive-workbench-viewers.ts +2 -0
- package/src/components/machine-card.tsx +1 -1
- package/src/components/machine-input-display.ts +3 -0
- package/src/components/machines/machine-detail.tsx +39 -15
- package/src/components/machines-dashboard.tsx +7 -6
- package/src/components/message-timeline.tsx +327 -37
- package/src/components/model-picker.tsx +2 -2
- package/src/components/model-policy-picker.tsx +16 -15
- package/src/components/queue-surface-implementation.tsx +135 -75
- package/src/components/queue-surface-state.tsx +2 -2
- package/src/components/sandbox-files.tsx +142 -62
- package/src/components/sandbox-terminal.tsx +119 -35
- package/src/components/sandbox-workspace.tsx +394 -228
- package/src/components/session-chrome.tsx +67 -27
- 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/tooltip.tsx +13 -16
- package/src/components/user-message-body.tsx +1 -1
- package/src/components/workbench-changes.tsx +192 -35
- package/src/components/workspace-dock.tsx +13 -5
- package/src/composer.ts +3 -0
- package/src/hooks/internal.ts +132 -38
- 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-codex-accounts.ts +4 -4
- 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-environments.ts +24 -5
- package/src/hooks/use-file-attachments.ts +70 -32
- package/src/hooks/use-goal.ts +17 -5
- package/src/hooks/use-machine-chip.ts +5 -3
- package/src/hooks/use-machines.ts +79 -3
- package/src/hooks/use-packs.ts +51 -0
- package/src/hooks/use-sandbox-files.ts +382 -88
- package/src/hooks/use-sandbox-git.ts +208 -46
- package/src/hooks/use-sandbox-terminal.ts +3 -2
- package/src/hooks/use-session-capabilities.ts +94 -70
- package/src/hooks/use-session-events.ts +18 -4
- package/src/hooks/use-terminal-stream.ts +102 -23
- package/src/hooks/use-transcription.ts +4 -14
- package/src/hooks/use-turn-queue.ts +17 -5
- package/src/hooks/use-video-artifact-playback.ts +28 -0
- package/src/hooks/use-voice-input.ts +163 -19
- package/src/hooks/use-windowed-sections.ts +9 -7
- package/src/hooks/use-workspace-edit.ts +2 -1
- package/src/index.ts +21 -6
- package/src/interaction.ts +71 -0
- package/src/lib/composer-responsive-context.ts +15 -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/lib/use-portal-token-style.ts +60 -15
- package/src/provider.tsx +52 -7
- package/src/realtime/dropdown-menu.tsx +15 -19
- package/src/realtime/realtime-control.tsx +16 -5
- package/src/session-context.ts +90 -0
- package/src/timeline/activity-rail.tsx +31 -6
- package/src/timeline/index.ts +4 -1
- package/src/timeline/parsers.ts +46 -20
- package/src/timeline/projection.ts +233 -13
- package/src/timeline/registry.ts +19 -0
- package/src/timeline/screenshot-lightbox.tsx +30 -9
- package/src/timeline/shared.tsx +59 -11
- package/src/timeline/tool-renderers.tsx +421 -24
- package/src/timeline/turn-summary.tsx +2 -2
- package/src/timeline/types.ts +58 -2
- package/src/voice-recording-store.ts +15 -0
- package/styles/compiled.css +5298 -0
- package/styles/compiled.d.ts +2 -0
- package/styles/effective-tokens.css +27 -0
- package/styles/index.css +18 -0
- package/styles/responsive.css +208 -0
- package/styles/responsive.d.ts +2 -0
- package/styles/tokens.css +50 -17
- package/dist/chunk-3PB7MIT6.js.map +0 -1
- package/dist/chunk-6EDV6YFE.js.map +0 -1
- package/dist/chunk-EB67J347.js.map +0 -1
- package/dist/chunk-EKZH6IDG.js.map +0 -1
- package/dist/chunk-FGZUCXZF.js.map +0 -1
- package/dist/chunk-HFO4ERGQ.js.map +0 -1
- package/dist/chunk-JB5725BI.js +0 -48
- package/dist/chunk-JB5725BI.js.map +0 -1
- package/dist/chunk-LGVMUIRV.js.map +0 -1
- package/dist/chunk-LWR4MXSS.js.map +0 -1
- package/dist/chunk-SRFUT2ZU.js.map +0 -1
- package/dist/chunk-U3CSCMWA.js.map +0 -1
- package/dist/chunk-VCBFUSUZ.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-J4XJFKRW.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,39 +14,63 @@
|
|
|
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
|
-
|
|
17
|
+
import {
|
|
18
|
+
lazy,
|
|
19
|
+
type ReactNode,
|
|
20
|
+
Suspense,
|
|
21
|
+
useCallback,
|
|
22
|
+
useEffect,
|
|
23
|
+
useMemo,
|
|
24
|
+
useRef,
|
|
25
|
+
useState,
|
|
26
|
+
} from "react";
|
|
18
27
|
import type { SessionEvent } from "@opengeni/sdk";
|
|
19
28
|
import {
|
|
20
29
|
CircleCheckIcon,
|
|
21
30
|
CpuIcon,
|
|
22
|
-
|
|
31
|
+
FileCode2Icon,
|
|
32
|
+
GitCompareArrowsIcon,
|
|
33
|
+
Globe2Icon,
|
|
23
34
|
LoaderCircleIcon,
|
|
35
|
+
MonitorIcon,
|
|
24
36
|
RefreshCwIcon,
|
|
37
|
+
SquareTerminalIcon,
|
|
25
38
|
TriangleAlertIcon,
|
|
26
39
|
} from "lucide-react";
|
|
27
40
|
|
|
28
41
|
import { type ClientOverride, useOpenGeni } from "../provider";
|
|
29
42
|
import { cn } from "../lib/cn";
|
|
30
43
|
import { xtermThemeFromTokens } from "../lib/xterm-theme";
|
|
31
|
-
import {
|
|
44
|
+
import { sandboxAcceptsLiveIo } from "../lib/sandbox-liveness";
|
|
45
|
+
import type { BrowserFrameWebSocketFactory } from "../hooks/use-browser-frame-stream";
|
|
46
|
+
import type { ComputerFrameWebSocketFactory } from "../hooks/use-computer-frame-stream";
|
|
32
47
|
import { useSessionCapabilities } from "../hooks/use-session-capabilities";
|
|
33
48
|
import { useSandboxFiles } from "../hooks/use-sandbox-files";
|
|
34
|
-
import {
|
|
49
|
+
import {
|
|
50
|
+
useSandboxGit,
|
|
51
|
+
type SandboxGitComparison,
|
|
52
|
+
type UseSandboxGitResult,
|
|
53
|
+
} from "../hooks/use-sandbox-git";
|
|
35
54
|
import { useSandboxTerminal } from "../hooks/use-sandbox-terminal";
|
|
36
55
|
import { useWorkspaceCapture } from "../hooks/use-workspace-capture";
|
|
37
|
-
import {
|
|
56
|
+
import { useMachineChip, type MachineChip } from "../hooks/use-machine-chip";
|
|
38
57
|
import { useMachines } from "../hooks/use-machines";
|
|
39
58
|
import type { MachineView } from "../types/machines";
|
|
40
|
-
import { connectionStatusForState } from "../types/machines";
|
|
41
|
-
import { ConnectionStatusPill } from "./machine-status-pill";
|
|
42
|
-
import { SharedMachineDisclosure } from "./machine-dock-bar";
|
|
43
59
|
import { SandboxFiles } from "./sandbox-files";
|
|
44
60
|
import { WorkbenchChanges } from "./workbench-changes";
|
|
45
61
|
import { SandboxTerminal, type XtermTheme } from "./sandbox-terminal";
|
|
46
|
-
import { DesktopViewer } from "./desktop-viewer";
|
|
47
62
|
import { WorkspaceDock, type WorkspaceDockProps, type WorkspaceTab } from "./workspace-dock";
|
|
48
63
|
|
|
64
|
+
const LazyBrowserViewer = lazy(async () => {
|
|
65
|
+
const viewers = await import("./interactive-workbench-viewers");
|
|
66
|
+
return { default: viewers.BrowserViewer };
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
const LazyComputerViewer = lazy(async () => {
|
|
70
|
+
const viewers = await import("./interactive-workbench-viewers");
|
|
71
|
+
return { default: viewers.ComputerViewer };
|
|
72
|
+
});
|
|
73
|
+
|
|
49
74
|
/** A host-routed notification (replaces the app-only `sonner` toast coupling). */
|
|
50
75
|
export type WorkspaceNotification = { kind: "error" | "info"; message: string };
|
|
51
76
|
|
|
@@ -53,6 +78,7 @@ export type WorkspaceNotification = { kind: "error" | "info"; message: string };
|
|
|
53
78
|
export const WORKBENCH_TAB_CHANGES = "changes";
|
|
54
79
|
export const WORKBENCH_TAB_FILES = "files";
|
|
55
80
|
export const WORKBENCH_TAB_TERMINAL = "terminal";
|
|
81
|
+
export const WORKBENCH_TAB_BROWSER = "browser";
|
|
56
82
|
export const WORKBENCH_TAB_DESKTOP = "desktop";
|
|
57
83
|
|
|
58
84
|
/**
|
|
@@ -64,6 +90,7 @@ export const WORKBENCH_SURFACES = [
|
|
|
64
90
|
WORKBENCH_TAB_CHANGES,
|
|
65
91
|
WORKBENCH_TAB_FILES,
|
|
66
92
|
WORKBENCH_TAB_TERMINAL,
|
|
93
|
+
WORKBENCH_TAB_BROWSER,
|
|
67
94
|
WORKBENCH_TAB_DESKTOP,
|
|
68
95
|
] as const;
|
|
69
96
|
|
|
@@ -97,6 +124,29 @@ function captureDegradedMessage(reason: string): string {
|
|
|
97
124
|
}
|
|
98
125
|
}
|
|
99
126
|
|
|
127
|
+
function WorkbenchTabLabel({ icon, children }: { icon: ReactNode; children: ReactNode }) {
|
|
128
|
+
return (
|
|
129
|
+
<span className="inline-flex items-center gap-1.5">
|
|
130
|
+
<span className="[&>svg]:size-3.5" aria-hidden>
|
|
131
|
+
{icon}
|
|
132
|
+
</span>
|
|
133
|
+
<span>{children}</span>
|
|
134
|
+
</span>
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function WorkbenchSurfaceLoading({ name }: { name: "Browser" | "Computer" }) {
|
|
139
|
+
return (
|
|
140
|
+
<CenteredState
|
|
141
|
+
icon={
|
|
142
|
+
<LoaderCircleIcon className="size-5 animate-spin motion-reduce:animate-none" aria-hidden />
|
|
143
|
+
}
|
|
144
|
+
>
|
|
145
|
+
<p className="text-og-sm font-medium text-og-fg">Opening {name}</p>
|
|
146
|
+
</CenteredState>
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
|
|
100
150
|
/**
|
|
101
151
|
* Decide a workspace tab from an already-local event log: the newest
|
|
102
152
|
* `workspace.revision.captured` announce carries the change surface stats, so
|
|
@@ -121,7 +171,19 @@ export function initialWorkspaceTab(
|
|
|
121
171
|
bestSeq = event.sequence;
|
|
122
172
|
bestFileCount = typeof stats?.fileCount === "number" ? stats.fileCount : 0;
|
|
123
173
|
}
|
|
124
|
-
|
|
174
|
+
if (bestFileCount > 0) return WORKBENCH_TAB_CHANGES;
|
|
175
|
+
|
|
176
|
+
let latestGitSeq = -1;
|
|
177
|
+
let committedOnly = false;
|
|
178
|
+
for (const event of events ?? []) {
|
|
179
|
+
if (event.type !== "git.changed" || event.sequence > bestSeq) continue;
|
|
180
|
+
if (event.sequence <= latestGitSeq) continue;
|
|
181
|
+
const payload = event.payload as { ahead?: number; reason?: string } | null;
|
|
182
|
+
latestGitSeq = event.sequence;
|
|
183
|
+
committedOnly =
|
|
184
|
+
(typeof payload?.ahead === "number" && payload.ahead > 0) || payload?.reason === "commit";
|
|
185
|
+
}
|
|
186
|
+
return committedOnly ? WORKBENCH_TAB_CHANGES : WORKBENCH_TAB_FILES;
|
|
125
187
|
}
|
|
126
188
|
|
|
127
189
|
/**
|
|
@@ -173,7 +235,7 @@ export type UseSandboxWorkspaceTabsOptions = ClientOverride & {
|
|
|
173
235
|
/** Live event log (usually `useSessionEvents().events`). */
|
|
174
236
|
events: SessionEvent[];
|
|
175
237
|
/**
|
|
176
|
-
* Built-in surfaces this host wants to expose. Omit for all
|
|
238
|
+
* Built-in surfaces this host wants to expose. Omit for all five. A disabled
|
|
177
239
|
* surface is behaviorally dormant: its data hooks, warm intents, stream
|
|
178
240
|
* attachment, and navigation callbacks are not activated.
|
|
179
241
|
*/
|
|
@@ -181,9 +243,15 @@ export type UseSandboxWorkspaceTabsOptions = ClientOverride & {
|
|
|
181
243
|
/** Override the source-driven default tab (e.g. a host landing tab id). When
|
|
182
244
|
* omitted the workbench picks Changes-vs-Files from capture or live Git. */
|
|
183
245
|
initialTab?: string | null | undefined;
|
|
246
|
+
/** Actually selected dock tab. Inactive data surfaces retain cache but issue no live reads. */
|
|
247
|
+
activeTab?: string | null | undefined;
|
|
184
248
|
/** Host-routed notifications (mutation errors, desktop-consent failures). The
|
|
185
249
|
* package never imports a toast library — the host decides how to surface. */
|
|
186
250
|
onNotify?: ((notification: WorkspaceNotification) => void) | undefined;
|
|
251
|
+
/** Alternate frame transport for non-browser runtimes and deterministic tests. */
|
|
252
|
+
browserWebSocketFactory?: BrowserFrameWebSocketFactory | undefined;
|
|
253
|
+
/** Alternate Computer frame transport for non-browser runtimes and deterministic tests. */
|
|
254
|
+
computerWebSocketFactory?: ComputerFrameWebSocketFactory | undefined;
|
|
187
255
|
/** File requested by a Changes guard. The Files surface defers the read until
|
|
188
256
|
* the sandbox is live, then reveals this path. */
|
|
189
257
|
requestedFilePath?: string | null | undefined;
|
|
@@ -192,10 +260,14 @@ export type UseSandboxWorkspaceTabsOptions = ClientOverride & {
|
|
|
192
260
|
requestedFileRequestId?: string | number | null | undefined;
|
|
193
261
|
/** Route a guarded diff into the host's Files tab. */
|
|
194
262
|
onOpenFile?: ((path: string) => void) | undefined;
|
|
263
|
+
/** Navigate the host dock to one exact linked ComputerSession. */
|
|
264
|
+
onOpenComputerSession?: ((computerSessionId: string) => void) | undefined;
|
|
265
|
+
requestedComputerSessionId?: string | null | undefined;
|
|
266
|
+
requestedComputerRequestId?: string | number | null | undefined;
|
|
195
267
|
};
|
|
196
268
|
|
|
197
269
|
export type UseSandboxWorkspaceTabsResult = {
|
|
198
|
-
/** Changes | Files | Terminal | Desktop (capability-gated where noted). */
|
|
270
|
+
/** Changes | Files | Terminal | Browser | Desktop (capability-gated where noted). */
|
|
199
271
|
tabs: WorkspaceTab[];
|
|
200
272
|
/** The source-driven default tab: Changes when the first authoritative capture
|
|
201
273
|
* or live Git result has changes, else Files (a host `initialTab` overrides).
|
|
@@ -208,7 +280,6 @@ export type UseSandboxWorkspaceTabsResult = {
|
|
|
208
280
|
|
|
209
281
|
type SessionWarmIntents = {
|
|
210
282
|
sessionId: string;
|
|
211
|
-
watchDesktop: boolean;
|
|
212
283
|
warmTerminal: boolean;
|
|
213
284
|
warmFiles: boolean;
|
|
214
285
|
};
|
|
@@ -216,7 +287,6 @@ type SessionWarmIntents = {
|
|
|
216
287
|
function emptyWarmIntents(sessionId: string): SessionWarmIntents {
|
|
217
288
|
return {
|
|
218
289
|
sessionId,
|
|
219
|
-
watchDesktop: false,
|
|
220
290
|
warmTerminal: false,
|
|
221
291
|
warmFiles: false,
|
|
222
292
|
};
|
|
@@ -225,36 +295,71 @@ function emptyWarmIntents(sessionId: string): SessionWarmIntents {
|
|
|
225
295
|
/**
|
|
226
296
|
* Build the workbench tabs + the machine-chip model for one session. This is the
|
|
227
297
|
* dock "brain": capability negotiation, capture-backed cold reads, prewarm
|
|
228
|
-
* flags, desktop
|
|
298
|
+
* flags, automatic desktop acknowledgment, and the xterm theme observer — all
|
|
299
|
+
* package-local.
|
|
229
300
|
*/
|
|
230
301
|
export function useSandboxWorkspaceTabs(
|
|
231
302
|
options: UseSandboxWorkspaceTabsOptions,
|
|
232
303
|
): UseSandboxWorkspaceTabsResult {
|
|
233
304
|
const { client, workspaceId } = useOpenGeni(options);
|
|
234
|
-
const {
|
|
235
|
-
|
|
305
|
+
const {
|
|
306
|
+
sessionId,
|
|
307
|
+
events,
|
|
308
|
+
onNotify,
|
|
309
|
+
browserWebSocketFactory,
|
|
310
|
+
computerWebSocketFactory,
|
|
311
|
+
requestedFilePath,
|
|
312
|
+
requestedFileRequestId,
|
|
313
|
+
onOpenFile,
|
|
314
|
+
onOpenComputerSession,
|
|
315
|
+
requestedComputerSessionId,
|
|
316
|
+
requestedComputerRequestId,
|
|
317
|
+
} = options;
|
|
236
318
|
const initialTab = options.initialTab ?? null;
|
|
319
|
+
const activeTab = options.activeTab ?? initialTab;
|
|
237
320
|
const requestedSurfaces = options.surfaces ?? WORKBENCH_SURFACES;
|
|
238
321
|
const surfaceSet = new Set<SandboxWorkspaceSurface>(requestedSurfaces);
|
|
239
322
|
const changesEnabled = surfaceSet.has(WORKBENCH_TAB_CHANGES);
|
|
240
323
|
const filesEnabled = surfaceSet.has(WORKBENCH_TAB_FILES);
|
|
241
324
|
const terminalEnabled = surfaceSet.has(WORKBENCH_TAB_TERMINAL);
|
|
325
|
+
const browserEnabled = surfaceSet.has(WORKBENCH_TAB_BROWSER);
|
|
242
326
|
const desktopEnabled = surfaceSet.has(WORKBENCH_TAB_DESKTOP);
|
|
327
|
+
const createLinkedComputer = useCallback(
|
|
328
|
+
async (name: string) => {
|
|
329
|
+
const response = await client.createComputerSession(workspaceId, {
|
|
330
|
+
operationId: crypto.randomUUID(),
|
|
331
|
+
sessionId,
|
|
332
|
+
name,
|
|
333
|
+
});
|
|
334
|
+
return {
|
|
335
|
+
id: response.session.id,
|
|
336
|
+
placement: response.session.placement,
|
|
337
|
+
};
|
|
338
|
+
},
|
|
339
|
+
[client, sessionId, workspaceId],
|
|
340
|
+
);
|
|
243
341
|
const workspaceDataEnabled = changesEnabled || filesEnabled;
|
|
244
|
-
const
|
|
342
|
+
const machineSurfaceEnabled = workspaceDataEnabled || terminalEnabled || desktopEnabled;
|
|
343
|
+
// Do not speculatively activate both remote workspace surfaces while the dock
|
|
344
|
+
// is still resolving its selected tab. Modal calls cannot be cancelled after
|
|
345
|
+
// dispatch, so that transient state otherwise creates an aborted duplicate
|
|
346
|
+
// batch immediately before the real selected-tab request.
|
|
347
|
+
const resolvedActiveTab = activeTab ?? initialTab;
|
|
348
|
+
const changesActive = resolvedActiveTab === WORKBENCH_TAB_CHANGES;
|
|
349
|
+
const filesActive = resolvedActiveTab === WORKBENCH_TAB_FILES;
|
|
245
350
|
const surfaceIdentity = WORKBENCH_SURFACES.filter((surface) => surfaceSet.has(surface)).join(",");
|
|
246
351
|
|
|
247
|
-
// The
|
|
352
|
+
// The two box-warming INTENTS, each off by default and each
|
|
248
353
|
// flipped true by a genuine user action (never on mount, never on a passive
|
|
249
|
-
// capture glance):
|
|
250
|
-
//
|
|
354
|
+
// capture glance): terminal engagement (`onActivate`) and a deliberate
|
|
355
|
+
// live-file open/edit in Files. Browsing capture-served
|
|
251
356
|
// Changes/Files warms nothing — that is the whole point of Refinement 1.
|
|
252
357
|
const [storedWarmIntents, setStoredWarmIntents] = useState<SessionWarmIntents>(() =>
|
|
253
358
|
emptyWarmIntents(sessionId),
|
|
254
359
|
);
|
|
255
360
|
const warmIntents =
|
|
256
361
|
storedWarmIntents.sessionId === sessionId ? storedWarmIntents : emptyWarmIntents(sessionId);
|
|
257
|
-
const {
|
|
362
|
+
const { warmTerminal, warmFiles } = warmIntents;
|
|
258
363
|
const requestWarmIntent = useCallback(
|
|
259
364
|
(intent: Exclude<keyof SessionWarmIntents, "sessionId">) => {
|
|
260
365
|
setStoredWarmIntents((previous) => {
|
|
@@ -271,7 +376,7 @@ export function useSandboxWorkspaceTabs(
|
|
|
271
376
|
workspaceId,
|
|
272
377
|
sessionId,
|
|
273
378
|
pollIntervalMs: 8000,
|
|
274
|
-
enabled:
|
|
379
|
+
enabled: machineSurfaceEnabled,
|
|
275
380
|
});
|
|
276
381
|
const activeMachine: MachineView | null =
|
|
277
382
|
machines.machines.find((m) => m.sandboxId === machines.activeSandboxId) ??
|
|
@@ -280,8 +385,8 @@ export function useSandboxWorkspaceTabs(
|
|
|
280
385
|
|
|
281
386
|
const caps = useSessionCapabilities(sessionId, {
|
|
282
387
|
events,
|
|
283
|
-
enabled:
|
|
284
|
-
attachDesktop:
|
|
388
|
+
enabled: machineSurfaceEnabled,
|
|
389
|
+
attachDesktop: false,
|
|
285
390
|
attachTerminal: terminalEnabled && warmTerminal,
|
|
286
391
|
// Explicit live-file intent only — NOT "the Files tab is open". A cold edit
|
|
287
392
|
// or guarded-file open wakes the box; a glance at the tree/diff does not.
|
|
@@ -289,6 +394,11 @@ export function useSandboxWorkspaceTabs(
|
|
|
289
394
|
});
|
|
290
395
|
const capabilities = caps.capabilities;
|
|
291
396
|
const liveness = capabilities?.liveness;
|
|
397
|
+
// A missing capability document is not evidence that the sandbox is live.
|
|
398
|
+
// Pass explicit null to live-I/O hooks so fast capture/event hydration cannot
|
|
399
|
+
// exploit their legacy "liveness omitted" compatibility path while negotiation
|
|
400
|
+
// is still pending.
|
|
401
|
+
const liveIoLiveness = liveness ?? null;
|
|
292
402
|
const fileSystemOn = capabilities?.FileSystem.available ?? false;
|
|
293
403
|
// The FS is writable only when it's live AND not read-only. A self-hosted box
|
|
294
404
|
// that's offline (or any read-only advertisement) or a capture-served cold tree
|
|
@@ -300,26 +410,23 @@ export function useSandboxWorkspaceTabs(
|
|
|
300
410
|
// Mutations are live-only. A capture is a review artifact, never a writable
|
|
301
411
|
// surrogate for a sleeping machine; the user must deliberately open the live
|
|
302
412
|
// workspace before create/rename/delete/edit controls appear.
|
|
303
|
-
const filesEditable =
|
|
304
|
-
fileSystemOn && !fsReadOnly && (liveness === "warm" || liveness === "draining");
|
|
413
|
+
const filesEditable = fileSystemOn && !fsReadOnly && sandboxAcceptsLiveIo(liveness);
|
|
305
414
|
const gitOn = capabilities?.Git.available ?? false;
|
|
306
415
|
const terminalOn = terminalEnabled && (capabilities?.Terminal.transport ?? null) !== null;
|
|
307
|
-
//
|
|
308
|
-
//
|
|
309
|
-
//
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
416
|
+
// A descriptor-only pty-ws cell intentionally has no short-lived URL until
|
|
417
|
+
// terminal intent acquires one. Do not mistake that grant boundary for a
|
|
418
|
+
// legacy HTTP PTY and eagerly open a second terminal. The fallback exists only
|
|
419
|
+
// for a genuinely firehose-transport backend with no typed degradation.
|
|
420
|
+
const ptyCapable =
|
|
421
|
+
warmTerminal &&
|
|
422
|
+
(capabilities?.Terminal.ptyCapable ?? false) &&
|
|
423
|
+
capabilities?.Terminal.transport === "sse-events" &&
|
|
424
|
+
capabilities?.Terminal.reason === null;
|
|
313
425
|
const terminal = useSandboxTerminal(sessionId, {
|
|
314
426
|
events: terminalEnabled ? events : [],
|
|
315
427
|
interactive: terminalEnabled && ptyCapable,
|
|
316
428
|
liveness,
|
|
317
429
|
});
|
|
318
|
-
const desktopAdvertised =
|
|
319
|
-
desktopEnabled &&
|
|
320
|
-
((capabilities?.DesktopStream.transport ?? null) !== null ||
|
|
321
|
-
capabilities?.DesktopStream.reason === "lease_cold");
|
|
322
|
-
|
|
323
430
|
// Lazy provisioning (#315) creates the box mid-turn on the first sandbox tool
|
|
324
431
|
// call, emitting sandbox.provision started→completed/failed on the live stream.
|
|
325
432
|
// The on-demand resting hook rests without polling, so when the box warms the
|
|
@@ -346,7 +453,7 @@ export function useSandboxWorkspaceTabs(
|
|
|
346
453
|
enabled: workspaceDataEnabled,
|
|
347
454
|
});
|
|
348
455
|
const captureAvailable = captureState.available;
|
|
349
|
-
const liveWorkspaceExpected = liveness
|
|
456
|
+
const liveWorkspaceExpected = sandboxAcceptsLiveIo(liveness);
|
|
350
457
|
// Capability negotiation and capture resolution are independent requests. A
|
|
351
458
|
// cold capability document can win that race by a few milliseconds; enabling
|
|
352
459
|
// the leaf hooks in that window would make them interpret `capture: null` as a
|
|
@@ -382,7 +489,9 @@ export function useSandboxWorkspaceTabs(
|
|
|
382
489
|
// No passive Channel-A reads while cold, even after a conclusive capture
|
|
383
490
|
// miss. A missing capture gets an explicit "Open live workspace" gate.
|
|
384
491
|
enabled: filesEnabled && (captureAvailable || (fileSystemOn && liveWorkspaceExpected)),
|
|
385
|
-
|
|
492
|
+
active: filesActive,
|
|
493
|
+
repoPaths,
|
|
494
|
+
liveness: liveIoLiveness,
|
|
386
495
|
capture: captureState.capture,
|
|
387
496
|
// A reverted optimistic mutation (e.g. a 409 rename collision) surfaces as a
|
|
388
497
|
// host notification — the tree silently rolls back, the user sees why.
|
|
@@ -392,24 +501,58 @@ export function useSandboxWorkspaceTabs(
|
|
|
392
501
|
message: `Could not ${op}: ${error.message}`,
|
|
393
502
|
}),
|
|
394
503
|
});
|
|
504
|
+
const [storedChangesComparison, setStoredChangesComparison] = useState<{
|
|
505
|
+
sessionId: string;
|
|
506
|
+
value: SandboxGitComparison;
|
|
507
|
+
} | null>(null);
|
|
508
|
+
const captureSupportsBranch =
|
|
509
|
+
captureState.capture !== null &&
|
|
510
|
+
captureState.capture.repos.length > 0 &&
|
|
511
|
+
captureState.capture.repos.every((repo) => repo.branchDiff !== undefined);
|
|
512
|
+
// A sleeping workspace can truthfully serve only the captured working-tree
|
|
513
|
+
// comparison. A live workspace defaults to the branch comparison so committed
|
|
514
|
+
// agent work remains visible. Explicit user selection is scoped per session.
|
|
515
|
+
const changesComparison =
|
|
516
|
+
storedChangesComparison?.sessionId === sessionId
|
|
517
|
+
? storedChangesComparison.value
|
|
518
|
+
: liveWorkspaceExpected || captureSupportsBranch
|
|
519
|
+
? "branch"
|
|
520
|
+
: "working";
|
|
521
|
+
const setChangesComparison = useCallback(
|
|
522
|
+
(value: SandboxGitComparison) => setStoredChangesComparison({ sessionId, value }),
|
|
523
|
+
[sessionId],
|
|
524
|
+
);
|
|
395
525
|
const git = useSandboxGit(sessionId, {
|
|
396
526
|
events,
|
|
397
|
-
enabled:
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
//
|
|
405
|
-
//
|
|
406
|
-
|
|
407
|
-
staged: true,
|
|
527
|
+
enabled:
|
|
528
|
+
changesComparison === "working"
|
|
529
|
+
? workspaceDataEnabled && (captureAvailable || (gitOn && liveWorkspaceExpected))
|
|
530
|
+
: changesComparison === "branch"
|
|
531
|
+
? workspaceDataEnabled && (captureSupportsBranch || (gitOn && liveWorkspaceExpected))
|
|
532
|
+
: workspaceDataEnabled && gitOn && liveWorkspaceExpected,
|
|
533
|
+
// Before the dock chooses its source-driven default, Git is the sole probe
|
|
534
|
+
// that may need to resolve that choice on a live workspace. Files remains
|
|
535
|
+
// dormant, avoiding the old duplicate full-tree read.
|
|
536
|
+
active: changesActive || resolvedActiveTab === null,
|
|
408
537
|
repoPaths,
|
|
409
|
-
liveness,
|
|
410
|
-
|
|
538
|
+
liveness: liveIoLiveness,
|
|
539
|
+
comparison: changesComparison,
|
|
540
|
+
capture: changesComparison === "staged" ? null : captureState.capture,
|
|
411
541
|
});
|
|
412
|
-
|
|
542
|
+
useEffect(() => {
|
|
543
|
+
if (
|
|
544
|
+
changesComparison === "branch" &&
|
|
545
|
+
git.error &&
|
|
546
|
+
storedChangesComparison?.sessionId !== sessionId
|
|
547
|
+
) {
|
|
548
|
+
// `origin/HEAD` is not guaranteed on a newly initialized repository, a
|
|
549
|
+
// connected machine, or a clone whose remote was renamed. The implicit
|
|
550
|
+
// live default must not strand the whole Changes surface when its ordinary
|
|
551
|
+
// working-tree comparison is still valid. Fall back once; an explicit
|
|
552
|
+
// user-selected Branch view remains selected and surfaces its real error.
|
|
553
|
+
setStoredChangesComparison({ sessionId, value: "working" });
|
|
554
|
+
}
|
|
555
|
+
}, [changesComparison, git.error, sessionId, storedChangesComparison?.sessionId]);
|
|
413
556
|
// Token-derived xterm theme; re-derive on a `data-og-theme` flip. Generic — it
|
|
414
557
|
// belongs in the package (an embedder's theme toggle drives it too).
|
|
415
558
|
const [xtermTheme, setXtermTheme] = useState<XtermTheme | undefined>(undefined);
|
|
@@ -425,30 +568,6 @@ export function useSandboxWorkspaceTabs(
|
|
|
425
568
|
return () => observer.disconnect();
|
|
426
569
|
}, [terminalEnabled]);
|
|
427
570
|
|
|
428
|
-
async function acknowledgeAndWatch() {
|
|
429
|
-
try {
|
|
430
|
-
const shared = capabilities?.DesktopStream.shared ?? false;
|
|
431
|
-
await client.acknowledgeStream(workspaceId, sessionId, {
|
|
432
|
-
acknowledgeUnredacted: true,
|
|
433
|
-
acknowledgeShared: shared,
|
|
434
|
-
});
|
|
435
|
-
requestWarmIntent("watchDesktop");
|
|
436
|
-
caps.renegotiate();
|
|
437
|
-
} catch (error) {
|
|
438
|
-
onNotify?.({
|
|
439
|
-
kind: "error",
|
|
440
|
-
message: `Could not start the desktop stream: ${error instanceof Error ? error.message : String(error)}`,
|
|
441
|
-
});
|
|
442
|
-
}
|
|
443
|
-
}
|
|
444
|
-
|
|
445
|
-
// Re-warm WITHOUT re-acknowledging: the consent was already recorded, only the
|
|
446
|
-
// box drained back to cold. Idempotent — the viewer auto-warm de-dupes.
|
|
447
|
-
function rewarmDesktop() {
|
|
448
|
-
if (!watchDesktop) requestWarmIntent("watchDesktop");
|
|
449
|
-
caps.renegotiate();
|
|
450
|
-
}
|
|
451
|
-
|
|
452
571
|
const dirtyCount = git.diff.length;
|
|
453
572
|
|
|
454
573
|
// The one truthful machine indicator, derived from the live capability/liveness
|
|
@@ -458,10 +577,7 @@ export function useSandboxWorkspaceTabs(
|
|
|
458
577
|
capabilitiesState: caps.state,
|
|
459
578
|
activeMachineState: activeMachine?.state ?? null,
|
|
460
579
|
activeIsSelfhosted: activeMachine?.kind === "selfhosted",
|
|
461
|
-
wantsWarm:
|
|
462
|
-
(terminalEnabled && warmTerminal) ||
|
|
463
|
-
(desktopEnabled && watchDesktop) ||
|
|
464
|
-
(workspaceDataEnabled && warmFiles),
|
|
580
|
+
wantsWarm: (terminalEnabled && warmTerminal) || (workspaceDataEnabled && warmFiles),
|
|
465
581
|
capturedAt: captureState.capturedAt,
|
|
466
582
|
});
|
|
467
583
|
const workspaceWaking = chip.state === "waking";
|
|
@@ -484,7 +600,15 @@ export function useSandboxWorkspaceTabs(
|
|
|
484
600
|
}
|
|
485
601
|
const captureIsAuthoritative =
|
|
486
602
|
!liveWorkspaceExpected && (liveness !== undefined || caps.error !== null);
|
|
487
|
-
const
|
|
603
|
+
const captureHasChanges =
|
|
604
|
+
(captureState.fileCount ?? 0) > 0 ||
|
|
605
|
+
(captureState.capture?.repos.some((repo) => (repo.branchDiff?.length ?? 0) > 0) ?? false);
|
|
606
|
+
const captureUnavailable =
|
|
607
|
+
(captureState.fileCount === 0 && !captureHasChanges) || captureState.error !== null;
|
|
608
|
+
const implicitBranchFallbackPending =
|
|
609
|
+
changesComparison === "branch" &&
|
|
610
|
+
git.error !== null &&
|
|
611
|
+
storedChangesComparison?.sessionId !== sessionId;
|
|
488
612
|
if (defaultTabRef.current.value === null) {
|
|
489
613
|
if (initialTab && (!isWorkbenchSurface(initialTab) || surfaceSet.has(initialTab))) {
|
|
490
614
|
defaultTabRef.current.value = initialTab;
|
|
@@ -500,7 +624,7 @@ export function useSandboxWorkspaceTabs(
|
|
|
500
624
|
captureState.available
|
|
501
625
|
) {
|
|
502
626
|
defaultTabRef.current.value = sourceDrivenDefaultTab(
|
|
503
|
-
|
|
627
|
+
captureHasChanges,
|
|
504
628
|
changesEnabled,
|
|
505
629
|
filesEnabled,
|
|
506
630
|
);
|
|
@@ -523,13 +647,13 @@ export function useSandboxWorkspaceTabs(
|
|
|
523
647
|
// A warm live read failed but the durable capture is intact: retain an
|
|
524
648
|
// immediate, deterministic review surface instead of hanging unresolved.
|
|
525
649
|
defaultTabRef.current.value = sourceDrivenDefaultTab(
|
|
526
|
-
|
|
650
|
+
captureHasChanges,
|
|
527
651
|
changesEnabled,
|
|
528
652
|
filesEnabled,
|
|
529
653
|
);
|
|
530
|
-
} else if (captureUnavailable && git.error) {
|
|
654
|
+
} else if (captureUnavailable && git.error && !implicitBranchFallbackPending) {
|
|
531
655
|
// No capture and live Git failed: Files is the least surprising fallback
|
|
532
|
-
//
|
|
656
|
+
// after the implicit branch-to-working fallback has also had its chance.
|
|
533
657
|
defaultTabRef.current.value = sourceDrivenDefaultTab(false, changesEnabled, filesEnabled);
|
|
534
658
|
}
|
|
535
659
|
}
|
|
@@ -543,11 +667,13 @@ export function useSandboxWorkspaceTabs(
|
|
|
543
667
|
if (changesEnabled)
|
|
544
668
|
list.push({
|
|
545
669
|
id: WORKBENCH_TAB_CHANGES,
|
|
546
|
-
label:
|
|
670
|
+
label: <WorkbenchTabLabel icon={<GitCompareArrowsIcon />}>Changes</WorkbenchTabLabel>,
|
|
547
671
|
badge: dirtyCount > 0 ? <DirtyBadge count={dirtyCount} /> : undefined,
|
|
548
672
|
content: (
|
|
549
673
|
<ChangesTabBody
|
|
550
674
|
git={git}
|
|
675
|
+
comparison={changesComparison}
|
|
676
|
+
onComparisonChange={setChangesComparison}
|
|
551
677
|
captureAvailable={captureAvailable}
|
|
552
678
|
captureRevision={captureState.revision}
|
|
553
679
|
capturePending={capturePending}
|
|
@@ -573,13 +699,12 @@ export function useSandboxWorkspaceTabs(
|
|
|
573
699
|
if (filesEnabled)
|
|
574
700
|
list.push({
|
|
575
701
|
id: WORKBENCH_TAB_FILES,
|
|
576
|
-
label:
|
|
702
|
+
label: <WorkbenchTabLabel icon={<FileCode2Icon />}>Files</WorkbenchTabLabel>,
|
|
577
703
|
content: (
|
|
578
704
|
<SandboxFiles
|
|
579
705
|
key={sessionId}
|
|
580
706
|
files={files}
|
|
581
707
|
git={git}
|
|
582
|
-
stagedGit={stagedGit}
|
|
583
708
|
fileSystemAvailable={fileSystemOn || captureAvailable}
|
|
584
709
|
editable={
|
|
585
710
|
filesEditable && files.source === "live" && files.error === null && !files.loading
|
|
@@ -600,7 +725,7 @@ export function useSandboxWorkspaceTabs(
|
|
|
600
725
|
...(requestedFileRequestId !== null && requestedFileRequestId !== undefined
|
|
601
726
|
? { requestedPathRequestId: requestedFileRequestId }
|
|
602
727
|
: {}),
|
|
603
|
-
requestedPathReady: liveness
|
|
728
|
+
requestedPathReady: sandboxAcceptsLiveIo(liveness),
|
|
604
729
|
}
|
|
605
730
|
: {})}
|
|
606
731
|
// Ordinary capture browsing does not warm a box. The first edit — or an
|
|
@@ -615,10 +740,11 @@ export function useSandboxWorkspaceTabs(
|
|
|
615
740
|
if (terminalOn) {
|
|
616
741
|
list.push({
|
|
617
742
|
id: WORKBENCH_TAB_TERMINAL,
|
|
618
|
-
label:
|
|
743
|
+
label: <WorkbenchTabLabel icon={<SquareTerminalIcon />}>Terminal</WorkbenchTabLabel>,
|
|
619
744
|
content: (
|
|
620
745
|
<div className="h-full bg-og-bg p-1">
|
|
621
746
|
<SandboxTerminal
|
|
747
|
+
key={sessionId}
|
|
622
748
|
result={terminal}
|
|
623
749
|
terminalCapability={capabilities?.Terminal ?? null}
|
|
624
750
|
onActivate={() => requestWarmIntent("warmTerminal")}
|
|
@@ -632,25 +758,51 @@ export function useSandboxWorkspaceTabs(
|
|
|
632
758
|
});
|
|
633
759
|
}
|
|
634
760
|
|
|
635
|
-
//
|
|
636
|
-
|
|
761
|
+
// Browser — independent from sandbox capability negotiation. BrowserSessions
|
|
762
|
+
// are workspace resources and may live in this agent's sandbox, a peer's
|
|
763
|
+
// sandbox, a connected machine, or an external browser placement.
|
|
764
|
+
if (browserEnabled) {
|
|
765
|
+
list.push({
|
|
766
|
+
id: WORKBENCH_TAB_BROWSER,
|
|
767
|
+
label: <WorkbenchTabLabel icon={<Globe2Icon />}>Browser</WorkbenchTabLabel>,
|
|
768
|
+
content: (
|
|
769
|
+
<Suspense fallback={<WorkbenchSurfaceLoading name="Browser" />}>
|
|
770
|
+
<LazyBrowserViewer
|
|
771
|
+
key={sessionId}
|
|
772
|
+
client={client}
|
|
773
|
+
workspaceId={workspaceId}
|
|
774
|
+
sessionId={sessionId}
|
|
775
|
+
onNotify={onNotify}
|
|
776
|
+
{...(desktopEnabled ? { createLinkedComputer } : {})}
|
|
777
|
+
{...(onOpenComputerSession ? { onOpenComputer: onOpenComputerSession } : {})}
|
|
778
|
+
{...(browserWebSocketFactory ? { webSocketFactory: browserWebSocketFactory } : {})}
|
|
779
|
+
className="h-full"
|
|
780
|
+
/>
|
|
781
|
+
</Suspense>
|
|
782
|
+
),
|
|
783
|
+
});
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
// Computer — stable legacy tab id, new workspace resource body. Like Browser,
|
|
787
|
+
// it is independent from the selected agent's sandbox capability document.
|
|
788
|
+
if (desktopEnabled) {
|
|
637
789
|
list.push({
|
|
638
790
|
id: WORKBENCH_TAB_DESKTOP,
|
|
639
|
-
label:
|
|
640
|
-
badge: watchDesktop ? (
|
|
641
|
-
<span className="rounded-og-xs bg-og-status-running/20 px-1 text-og-xs text-og-status-running">
|
|
642
|
-
Live
|
|
643
|
-
</span>
|
|
644
|
-
) : undefined,
|
|
791
|
+
label: <WorkbenchTabLabel icon={<MonitorIcon />}>Computer</WorkbenchTabLabel>,
|
|
645
792
|
content: (
|
|
646
|
-
<
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
793
|
+
<Suspense fallback={<WorkbenchSurfaceLoading name="Computer" />}>
|
|
794
|
+
<LazyComputerViewer
|
|
795
|
+
key={sessionId}
|
|
796
|
+
client={client}
|
|
797
|
+
workspaceId={workspaceId}
|
|
798
|
+
sessionId={sessionId}
|
|
799
|
+
onNotify={onNotify}
|
|
800
|
+
requestedComputerSessionId={requestedComputerSessionId}
|
|
801
|
+
requestedComputerRequestId={requestedComputerRequestId}
|
|
802
|
+
{...(computerWebSocketFactory ? { webSocketFactory: computerWebSocketFactory } : {})}
|
|
803
|
+
className="h-full"
|
|
804
|
+
/>
|
|
805
|
+
</Suspense>
|
|
654
806
|
),
|
|
655
807
|
});
|
|
656
808
|
}
|
|
@@ -662,19 +814,27 @@ export function useSandboxWorkspaceTabs(
|
|
|
662
814
|
changesEnabled,
|
|
663
815
|
filesEnabled,
|
|
664
816
|
terminalOn,
|
|
665
|
-
|
|
817
|
+
browserEnabled,
|
|
818
|
+
desktopEnabled,
|
|
666
819
|
captureAvailable,
|
|
667
820
|
dirtyCount,
|
|
668
|
-
watchDesktop,
|
|
669
821
|
warmTerminal,
|
|
670
822
|
requestedFilePath,
|
|
671
823
|
requestedFileRequestId,
|
|
672
824
|
onOpenFile,
|
|
825
|
+
onOpenComputerSession,
|
|
826
|
+
requestedComputerSessionId,
|
|
827
|
+
requestedComputerRequestId,
|
|
828
|
+
onNotify,
|
|
829
|
+
browserWebSocketFactory,
|
|
830
|
+
computerWebSocketFactory,
|
|
831
|
+
client,
|
|
832
|
+
workspaceId,
|
|
673
833
|
liveness,
|
|
674
834
|
sessionId,
|
|
675
835
|
files,
|
|
676
836
|
git,
|
|
677
|
-
|
|
837
|
+
changesComparison,
|
|
678
838
|
terminal,
|
|
679
839
|
xtermTheme,
|
|
680
840
|
capabilities,
|
|
@@ -683,13 +843,14 @@ export function useSandboxWorkspaceTabs(
|
|
|
683
843
|
caps.error,
|
|
684
844
|
caps.viewerCapReached,
|
|
685
845
|
requestWarmIntent,
|
|
846
|
+
createLinkedComputer,
|
|
686
847
|
]);
|
|
687
848
|
|
|
688
849
|
return {
|
|
689
850
|
tabs,
|
|
690
851
|
defaultTab,
|
|
691
852
|
machine: {
|
|
692
|
-
enabled:
|
|
853
|
+
enabled: machineSurfaceEnabled,
|
|
693
854
|
chip,
|
|
694
855
|
activeMachine,
|
|
695
856
|
loading: machines.loading,
|
|
@@ -707,7 +868,7 @@ export type SandboxWorkspaceProps = ClientOverride & {
|
|
|
707
868
|
primary: ReactNode;
|
|
708
869
|
/**
|
|
709
870
|
* Built-in surfaces this host wants to expose. Omit for Changes, Files,
|
|
710
|
-
* Terminal, and Desktop. Disabled surfaces remain behaviorally dormant.
|
|
871
|
+
* Terminal, Browser, and Desktop. Disabled surfaces remain behaviorally dormant.
|
|
711
872
|
*/
|
|
712
873
|
surfaces?: readonly SandboxWorkspaceSurface[] | undefined;
|
|
713
874
|
/** Host tabs injected BEFORE the workbench tabs (e.g. a "Run" landing tab). */
|
|
@@ -719,9 +880,15 @@ export type SandboxWorkspaceProps = ClientOverride & {
|
|
|
719
880
|
initialTab?: string | undefined;
|
|
720
881
|
/** Host-routed notifications (no toast dependency in the package). */
|
|
721
882
|
onNotify?: ((notification: WorkspaceNotification) => void) | undefined;
|
|
883
|
+
/** Alternate Browser frame transport for non-browser runtimes and deterministic tests. */
|
|
884
|
+
browserWebSocketFactory?: BrowserFrameWebSocketFactory | undefined;
|
|
885
|
+
/** Alternate Computer frame transport for non-browser runtimes and deterministic tests. */
|
|
886
|
+
computerWebSocketFactory?: ComputerFrameWebSocketFactory | undefined;
|
|
722
887
|
/** Controlled collapsed state for hosts with their own dock toggle. */
|
|
723
888
|
collapsed?: boolean | undefined;
|
|
724
889
|
onCollapsedChange?: ((collapsed: boolean) => void) | undefined;
|
|
890
|
+
/** Keep the dock's built-in collapse control with controlled collapsed state. */
|
|
891
|
+
showCollapseControl?: boolean | undefined;
|
|
725
892
|
/** Host navigation shown only in the phone workspace overlay header. */
|
|
726
893
|
mobileLeadingControl?: ReactNode | undefined;
|
|
727
894
|
autoSaveId?: string | undefined;
|
|
@@ -735,7 +902,7 @@ export type SandboxWorkspaceProps = ClientOverride & {
|
|
|
735
902
|
* The whole session workspace, ready to mount: `<SandboxWorkspace>` assembles the
|
|
736
903
|
* capability-gated tabs, pins the machine-state chip in the dock header, and
|
|
737
904
|
* renders the resizable `<WorkspaceDock>`. Wrap the tree in `<OpenGeniProvider>`
|
|
738
|
-
* (client + workspaceId) and import `@opengeni/react/
|
|
905
|
+
* (client + workspaceId) and import `@opengeni/react/compiled.css`; that is the
|
|
739
906
|
* entire integration (see `docs/embedding-workbench.md`).
|
|
740
907
|
*/
|
|
741
908
|
export function SandboxWorkspace(props: SandboxWorkspaceProps): ReactNode {
|
|
@@ -748,8 +915,11 @@ export function SandboxWorkspace(props: SandboxWorkspaceProps): ReactNode {
|
|
|
748
915
|
trailingTabs,
|
|
749
916
|
initialTab,
|
|
750
917
|
onNotify,
|
|
918
|
+
browserWebSocketFactory,
|
|
919
|
+
computerWebSocketFactory,
|
|
751
920
|
collapsed,
|
|
752
921
|
onCollapsedChange,
|
|
922
|
+
showCollapseControl,
|
|
753
923
|
mobileLeadingControl,
|
|
754
924
|
autoSaveId,
|
|
755
925
|
defaultSize,
|
|
@@ -767,7 +937,15 @@ export function SandboxWorkspace(props: SandboxWorkspaceProps): ReactNode {
|
|
|
767
937
|
path: string;
|
|
768
938
|
requestId: number;
|
|
769
939
|
} | null>(null);
|
|
940
|
+
const [requestedComputer, setRequestedComputer] = useState<{
|
|
941
|
+
sessionId: string;
|
|
942
|
+
computerSessionId: string;
|
|
943
|
+
requestId: number;
|
|
944
|
+
} | null>(null);
|
|
770
945
|
const nextFileRequestId = useRef(0);
|
|
946
|
+
const nextComputerRequestId = useRef(0);
|
|
947
|
+
const selectedTab = storedSelection?.sessionId === sessionId ? storedSelection.tab : null;
|
|
948
|
+
const activeTabHint = selectedTab ?? initialTab ?? leadingTabs?.[0]?.id ?? null;
|
|
771
949
|
const openFile = useCallback(
|
|
772
950
|
(path: string) => {
|
|
773
951
|
nextFileRequestId.current += 1;
|
|
@@ -776,6 +954,18 @@ export function SandboxWorkspace(props: SandboxWorkspaceProps): ReactNode {
|
|
|
776
954
|
},
|
|
777
955
|
[sessionId],
|
|
778
956
|
);
|
|
957
|
+
const openComputer = useCallback(
|
|
958
|
+
(computerSessionId: string) => {
|
|
959
|
+
nextComputerRequestId.current += 1;
|
|
960
|
+
setRequestedComputer({
|
|
961
|
+
sessionId,
|
|
962
|
+
computerSessionId,
|
|
963
|
+
requestId: nextComputerRequestId.current,
|
|
964
|
+
});
|
|
965
|
+
setStoredSelection({ sessionId, tab: WORKBENCH_TAB_DESKTOP });
|
|
966
|
+
},
|
|
967
|
+
[sessionId],
|
|
968
|
+
);
|
|
779
969
|
|
|
780
970
|
const {
|
|
781
971
|
tabs: workbenchTabs,
|
|
@@ -788,10 +978,18 @@ export function SandboxWorkspace(props: SandboxWorkspaceProps): ReactNode {
|
|
|
788
978
|
events,
|
|
789
979
|
...(surfaces ? { surfaces } : {}),
|
|
790
980
|
...(initialTab ? { initialTab } : {}),
|
|
981
|
+
activeTab: activeTabHint,
|
|
791
982
|
...(onNotify ? { onNotify } : {}),
|
|
983
|
+
...(browserWebSocketFactory ? { browserWebSocketFactory } : {}),
|
|
984
|
+
...(computerWebSocketFactory ? { computerWebSocketFactory } : {}),
|
|
792
985
|
requestedFilePath: requestedFile?.sessionId === sessionId ? requestedFile.path : null,
|
|
793
986
|
requestedFileRequestId: requestedFile?.sessionId === sessionId ? requestedFile.requestId : null,
|
|
794
987
|
onOpenFile: openFile,
|
|
988
|
+
onOpenComputerSession: openComputer,
|
|
989
|
+
requestedComputerSessionId:
|
|
990
|
+
requestedComputer?.sessionId === sessionId ? requestedComputer.computerSessionId : null,
|
|
991
|
+
requestedComputerRequestId:
|
|
992
|
+
requestedComputer?.sessionId === sessionId ? requestedComputer.requestId : null,
|
|
795
993
|
});
|
|
796
994
|
|
|
797
995
|
// A user's tab click wins forever; before that we follow the source-driven
|
|
@@ -800,7 +998,6 @@ export function SandboxWorkspace(props: SandboxWorkspaceProps): ReactNode {
|
|
|
800
998
|
// (Changes) — whose body is a connecting/loading state until the capture lands,
|
|
801
999
|
// so committing the real default at first-resolve produces no CONTENT switch.
|
|
802
1000
|
const tabs: WorkspaceTab[] = [...(leadingTabs ?? []), ...workbenchTabs, ...(trailingTabs ?? [])];
|
|
803
|
-
const selectedTab = storedSelection?.sessionId === sessionId ? storedSelection.tab : null;
|
|
804
1001
|
const preferredTab = selectedTab ?? defaultTab;
|
|
805
1002
|
const activeTab =
|
|
806
1003
|
preferredTab && tabs.some((tab) => tab.id === preferredTab) ? preferredTab : tabs[0]?.id;
|
|
@@ -817,19 +1014,13 @@ export function SandboxWorkspace(props: SandboxWorkspaceProps): ReactNode {
|
|
|
817
1014
|
onActiveTabChange={selectTab}
|
|
818
1015
|
{...(machine.enabled
|
|
819
1016
|
? {
|
|
820
|
-
headerAccessory:
|
|
821
|
-
<MachineStateChip
|
|
822
|
-
chip={machine.chip}
|
|
823
|
-
activeMachine={machine.activeMachine}
|
|
824
|
-
error={machine.error}
|
|
825
|
-
onRetry={() => void machine.refresh()}
|
|
826
|
-
/>
|
|
827
|
-
),
|
|
1017
|
+
headerAccessory: <MachineStateChip chip={machine.chip} />,
|
|
828
1018
|
}
|
|
829
1019
|
: {})}
|
|
830
1020
|
{...(mobileLeadingControl !== undefined ? { mobileLeadingControl } : {})}
|
|
831
1021
|
{...(collapsed !== undefined ? { collapsed } : {})}
|
|
832
1022
|
{...(onCollapsedChange ? { onCollapsedChange } : {})}
|
|
1023
|
+
{...(showCollapseControl !== undefined ? { showCollapseControl } : {})}
|
|
833
1024
|
{...(autoSaveId !== undefined ? { autoSaveId } : {})}
|
|
834
1025
|
{...(defaultSize !== undefined ? { defaultSize } : {})}
|
|
835
1026
|
{...(minSize !== undefined ? { minSize } : {})}
|
|
@@ -850,18 +1041,6 @@ function DirtyBadge({ count }: { count: number }) {
|
|
|
850
1041
|
);
|
|
851
1042
|
}
|
|
852
1043
|
|
|
853
|
-
/** A machine-kind glyph for the chip popover. */
|
|
854
|
-
function MachineKindIcon({
|
|
855
|
-
kind,
|
|
856
|
-
className,
|
|
857
|
-
}: {
|
|
858
|
-
kind: MachineView["kind"] | undefined;
|
|
859
|
-
className?: string;
|
|
860
|
-
}) {
|
|
861
|
-
const Icon = kind === "selfhosted" ? LaptopIcon : CpuIcon;
|
|
862
|
-
return <Icon className={cn("size-3.5 shrink-0 text-og-fg-subtle", className)} aria-hidden />;
|
|
863
|
-
}
|
|
864
|
-
|
|
865
1044
|
function chipDotClass(state: MachineChip["state"]): string {
|
|
866
1045
|
if (state === "live") return "bg-og-status-running";
|
|
867
1046
|
if (state === "waking") return "bg-og-status-idle animate-pulse motion-reduce:animate-none";
|
|
@@ -869,93 +1048,23 @@ function chipDotClass(state: MachineChip["state"]): string {
|
|
|
869
1048
|
}
|
|
870
1049
|
|
|
871
1050
|
/**
|
|
872
|
-
* The dock-header machine
|
|
873
|
-
*
|
|
874
|
-
*
|
|
875
|
-
* fleet failed to resolve (the old per-surface machine bar + Sandbox info tab,
|
|
876
|
-
* folded into one header affordance — recommendation).
|
|
1051
|
+
* The dock-header machine status: one quiet, truthful live/waking/offline
|
|
1052
|
+
* indicator. It is intentionally not interactive; detailed machine controls do
|
|
1053
|
+
* not belong in a transient popover above the workspace tabs.
|
|
877
1054
|
*/
|
|
878
|
-
function MachineStateChip({
|
|
879
|
-
chip,
|
|
880
|
-
activeMachine,
|
|
881
|
-
error,
|
|
882
|
-
onRetry,
|
|
883
|
-
}: {
|
|
884
|
-
chip: MachineChip;
|
|
885
|
-
activeMachine: MachineView | null;
|
|
886
|
-
error: Error | null;
|
|
887
|
-
onRetry: () => void;
|
|
888
|
-
}) {
|
|
889
|
-
const triggerRef = useRef<HTMLButtonElement | null>(null);
|
|
890
|
-
const portalStyle = usePortalTokenStyle(triggerRef);
|
|
1055
|
+
function MachineStateChip({ chip }: { chip: MachineChip }) {
|
|
891
1056
|
return (
|
|
892
|
-
<
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
/>
|
|
904
|
-
<span className="max-w-[11rem] truncate">{chip.label}</span>
|
|
905
|
-
</button>
|
|
906
|
-
</Popover.Trigger>
|
|
907
|
-
<Popover.Portal forceMount>
|
|
908
|
-
<Popover.Content
|
|
909
|
-
forceMount
|
|
910
|
-
data-machine-state-popover
|
|
911
|
-
align="end"
|
|
912
|
-
sideOffset={6}
|
|
913
|
-
collisionPadding={8}
|
|
914
|
-
className="og-root z-50 w-64 max-w-[calc(100vw-1rem)] rounded-og-md border border-og-border bg-og-surface-1 p-3 text-og-sm text-og-fg shadow-lg outline-none"
|
|
915
|
-
style={portalStyle}
|
|
916
|
-
>
|
|
917
|
-
<div className="flex min-w-0 items-center gap-1.5">
|
|
918
|
-
<MachineKindIcon kind={activeMachine?.kind} />
|
|
919
|
-
<span className="min-w-0 truncate font-medium">{activeMachine?.name ?? "Sandbox"}</span>
|
|
920
|
-
</div>
|
|
921
|
-
<div className="mt-2.5 flex items-center justify-between gap-2">
|
|
922
|
-
<span className="text-og-xs text-og-fg-subtle">Connection</span>
|
|
923
|
-
{activeMachine ? (
|
|
924
|
-
<ConnectionStatusPill
|
|
925
|
-
status={connectionStatusForState(activeMachine.state)}
|
|
926
|
-
size="sm"
|
|
927
|
-
/>
|
|
928
|
-
) : (
|
|
929
|
-
<span className="text-og-xs text-og-fg-muted">{chip.label}</span>
|
|
930
|
-
)}
|
|
931
|
-
</div>
|
|
932
|
-
{chip.asOf ? (
|
|
933
|
-
<p className="mt-2.5 text-og-xs leading-4 text-og-fg-subtle">
|
|
934
|
-
Workspace shown as of {formatAsOf(chip.asOf, Date.now())} — the machine is not live.
|
|
935
|
-
</p>
|
|
936
|
-
) : null}
|
|
937
|
-
{activeMachine && activeMachine.sharedSessionCount > 1 ? (
|
|
938
|
-
<div className="mt-2.5">
|
|
939
|
-
<SharedMachineDisclosure
|
|
940
|
-
sharedSessionCount={activeMachine.sharedSessionCount}
|
|
941
|
-
density="full"
|
|
942
|
-
/>
|
|
943
|
-
</div>
|
|
944
|
-
) : null}
|
|
945
|
-
{error ? (
|
|
946
|
-
<div className="mt-2.5 space-y-1.5 border-t border-og-border pt-2.5">
|
|
947
|
-
<p className="text-og-xs leading-4 text-og-danger">
|
|
948
|
-
Couldn't reach the sandbox for this session.
|
|
949
|
-
</p>
|
|
950
|
-
<DockActionButton onClick={onRetry}>
|
|
951
|
-
<RefreshCwIcon className="size-3" />
|
|
952
|
-
Retry
|
|
953
|
-
</DockActionButton>
|
|
954
|
-
</div>
|
|
955
|
-
) : null}
|
|
956
|
-
</Popover.Content>
|
|
957
|
-
</Popover.Portal>
|
|
958
|
-
</Popover.Root>
|
|
1057
|
+
<div
|
|
1058
|
+
role="status"
|
|
1059
|
+
aria-label={`Machine: ${chip.label}`}
|
|
1060
|
+
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"
|
|
1061
|
+
>
|
|
1062
|
+
<span
|
|
1063
|
+
className={cn("size-1.5 shrink-0 rounded-full", chipDotClass(chip.state))}
|
|
1064
|
+
aria-hidden
|
|
1065
|
+
/>
|
|
1066
|
+
<span className="max-w-[11rem] truncate">{chip.label}</span>
|
|
1067
|
+
</div>
|
|
959
1068
|
);
|
|
960
1069
|
}
|
|
961
1070
|
|
|
@@ -965,7 +1074,7 @@ function DockActionButton({ onClick, children }: { onClick: () => void; children
|
|
|
965
1074
|
<button
|
|
966
1075
|
type="button"
|
|
967
1076
|
onClick={onClick}
|
|
968
|
-
className="inline-flex items-center gap-1.5 rounded-og-sm border border-og-border px-2 py-1 text-og-xs font-medium text-og-fg-muted transition-colors hover:border-og-border-strong hover:text-og-fg focus-visible:outline-
|
|
1077
|
+
className="inline-flex items-center gap-1.5 rounded-og-sm border border-og-border px-2 py-1 text-og-xs font-medium text-og-fg-muted transition-colors hover:border-og-border-strong hover:text-og-fg focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-og-accent max-[1023px]:min-h-11 pointer-coarse:min-h-11"
|
|
969
1078
|
>
|
|
970
1079
|
{children}
|
|
971
1080
|
</button>
|
|
@@ -979,7 +1088,49 @@ function DockActionButton({ onClick, children }: { onClick: () => void; children
|
|
|
979
1088
|
* The dock frame is untouched; this is the M5 seam.
|
|
980
1089
|
*/
|
|
981
1090
|
function ChangesTabBody({
|
|
1091
|
+
comparison,
|
|
1092
|
+
onComparisonChange,
|
|
1093
|
+
...props
|
|
1094
|
+
}: {
|
|
1095
|
+
comparison: SandboxGitComparison;
|
|
1096
|
+
onComparisonChange: (comparison: SandboxGitComparison) => void;
|
|
1097
|
+
} & Parameters<typeof ChangesTabContent>[0]) {
|
|
1098
|
+
return (
|
|
1099
|
+
<div className="flex h-full min-h-0 flex-col">
|
|
1100
|
+
<div className="flex shrink-0 items-center gap-1 border-b border-og-border bg-og-surface-1 px-2 py-1.5">
|
|
1101
|
+
{(
|
|
1102
|
+
[
|
|
1103
|
+
["branch", "Branch"],
|
|
1104
|
+
["working", "Uncommitted"],
|
|
1105
|
+
["staged", "Staged"],
|
|
1106
|
+
] as const
|
|
1107
|
+
).map(([value, label]) => (
|
|
1108
|
+
<button
|
|
1109
|
+
key={value}
|
|
1110
|
+
type="button"
|
|
1111
|
+
aria-pressed={comparison === value}
|
|
1112
|
+
onClick={() => onComparisonChange(value)}
|
|
1113
|
+
className={cn(
|
|
1114
|
+
"rounded-og-sm px-2 py-1 text-og-xs font-medium transition-colors focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-og-accent max-[1023px]:min-h-11 pointer-coarse:min-h-11",
|
|
1115
|
+
comparison === value
|
|
1116
|
+
? "bg-og-surface-2 text-og-fg shadow-sm"
|
|
1117
|
+
: "text-og-fg-muted hover:bg-og-surface-2 hover:text-og-fg",
|
|
1118
|
+
)}
|
|
1119
|
+
>
|
|
1120
|
+
{label}
|
|
1121
|
+
</button>
|
|
1122
|
+
))}
|
|
1123
|
+
</div>
|
|
1124
|
+
<div className="min-h-0 flex-1">
|
|
1125
|
+
<ChangesTabContent comparison={comparison} {...props} />
|
|
1126
|
+
</div>
|
|
1127
|
+
</div>
|
|
1128
|
+
);
|
|
1129
|
+
}
|
|
1130
|
+
|
|
1131
|
+
function ChangesTabContent({
|
|
982
1132
|
git,
|
|
1133
|
+
comparison,
|
|
983
1134
|
captureAvailable,
|
|
984
1135
|
captureRevision,
|
|
985
1136
|
capturePending,
|
|
@@ -992,6 +1143,7 @@ function ChangesTabBody({
|
|
|
992
1143
|
onOpenFile,
|
|
993
1144
|
}: {
|
|
994
1145
|
git: UseSandboxGitResult;
|
|
1146
|
+
comparison: SandboxGitComparison;
|
|
995
1147
|
captureAvailable: boolean;
|
|
996
1148
|
captureRevision: number | null;
|
|
997
1149
|
capturePending: boolean;
|
|
@@ -1025,7 +1177,7 @@ function ChangesTabBody({
|
|
|
1025
1177
|
type="button"
|
|
1026
1178
|
onClick={() => void git.refresh()}
|
|
1027
1179
|
disabled={git.loading}
|
|
1028
|
-
className="inline-flex min-h-7 shrink-0 items-center gap-1 rounded-og-sm border border-og-border bg-og-surface-1 px-2 font-medium text-og-fg transition-colors hover:border-og-border-strong focus-visible:outline-
|
|
1180
|
+
className="inline-flex min-h-7 shrink-0 items-center gap-1 rounded-og-sm border border-og-border bg-og-surface-1 px-2 font-medium text-og-fg transition-colors hover:border-og-border-strong focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-og-accent disabled:cursor-not-allowed disabled:opacity-50 pointer-coarse:min-h-11"
|
|
1029
1181
|
>
|
|
1030
1182
|
<RefreshCwIcon
|
|
1031
1183
|
className={cn("size-3", git.loading && "animate-spin motion-reduce:animate-none")}
|
|
@@ -1085,7 +1237,7 @@ function ChangesTabBody({
|
|
|
1085
1237
|
);
|
|
1086
1238
|
}
|
|
1087
1239
|
|
|
1088
|
-
if (!liveWorkspaceExpected && !captureAvailable) {
|
|
1240
|
+
if (!liveWorkspaceExpected && (comparison !== "working" || !captureAvailable)) {
|
|
1089
1241
|
return (
|
|
1090
1242
|
<CenteredState
|
|
1091
1243
|
icon={
|
|
@@ -1105,7 +1257,11 @@ function ChangesTabBody({
|
|
|
1105
1257
|
<p className="text-og-sm leading-5 text-og-fg-subtle">
|
|
1106
1258
|
{workspaceWaking
|
|
1107
1259
|
? "Connecting to the live working tree…"
|
|
1108
|
-
:
|
|
1260
|
+
: comparison === "branch"
|
|
1261
|
+
? "Wake the sandbox to compare this branch with the remote default branch."
|
|
1262
|
+
: comparison === "staged"
|
|
1263
|
+
? "Wake the sandbox to inspect staged changes."
|
|
1264
|
+
: "No captured revision is available yet. Wake the sandbox to inspect uncommitted changes."}
|
|
1109
1265
|
</p>
|
|
1110
1266
|
{!workspaceWaking ? (
|
|
1111
1267
|
<DockActionButton onClick={onWake}>
|
|
@@ -1135,9 +1291,19 @@ function ChangesTabBody({
|
|
|
1135
1291
|
|
|
1136
1292
|
return (
|
|
1137
1293
|
<CenteredState icon={<CircleCheckIcon className="size-5" aria-hidden />} tone="success">
|
|
1138
|
-
<p className="text-og-sm font-medium text-og-fg">
|
|
1294
|
+
<p className="text-og-sm font-medium text-og-fg">
|
|
1295
|
+
{comparison === "branch"
|
|
1296
|
+
? "No branch changes"
|
|
1297
|
+
: comparison === "staged"
|
|
1298
|
+
? "No staged changes"
|
|
1299
|
+
: "No uncommitted changes"}
|
|
1300
|
+
</p>
|
|
1139
1301
|
<p className="text-og-sm leading-5 text-og-fg-subtle">
|
|
1140
|
-
|
|
1302
|
+
{comparison === "branch"
|
|
1303
|
+
? "This branch matches the remote default branch."
|
|
1304
|
+
: comparison === "staged"
|
|
1305
|
+
? "Stage files to review the next commit here."
|
|
1306
|
+
: "Local edits not yet committed will appear here."}
|
|
1141
1307
|
</p>
|
|
1142
1308
|
</CenteredState>
|
|
1143
1309
|
);
|