@opengeni/react 0.57.0 → 2.2.0-canary.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 +36 -11
- package/dist/artifacts-spreadsheet.js +1 -1
- package/dist/artifacts.js +1 -1
- package/dist/browser-viewer-GXUICIAG.js +14 -0
- package/dist/{chunk-R3AZOAB5.js → chunk-4MDLEHXL.js} +9 -115
- package/dist/chunk-4MDLEHXL.js.map +1 -0
- package/dist/{chunk-Y7OEDUJ5.js → chunk-5RZN3DD5.js} +1014 -420
- package/dist/chunk-5RZN3DD5.js.map +1 -0
- package/dist/chunk-5YK2ZDIG.js +116 -0
- package/dist/chunk-5YK2ZDIG.js.map +1 -0
- package/dist/chunk-H5YGYNXS.js +329 -0
- package/dist/chunk-H5YGYNXS.js.map +1 -0
- package/dist/{chunk-7WCLAEFV.js → chunk-ICA6R6TW.js} +2 -2
- package/dist/{chunk-7WCLAEFV.js.map → chunk-ICA6R6TW.js.map} +1 -1
- package/dist/{chunk-IGC6A2ZI.js → chunk-JOAVA2TW.js} +589 -217
- package/dist/chunk-JOAVA2TW.js.map +1 -0
- package/dist/{chunk-5UQPNLIO.js → chunk-LTCHJ4MS.js} +9 -9
- package/dist/chunk-LTCHJ4MS.js.map +1 -0
- package/dist/{chunk-C5ZPSSS5.js → chunk-NEXLLTP4.js} +2 -1
- package/dist/{chunk-C5ZPSSS5.js.map → chunk-NEXLLTP4.js.map} +1 -1
- package/dist/chunk-QGBJ6GHG.js +2300 -0
- package/dist/chunk-QGBJ6GHG.js.map +1 -0
- package/dist/{chunk-B6WPZSTT.js → chunk-UOLJWT5Y.js} +78 -16
- package/dist/chunk-UOLJWT5Y.js.map +1 -0
- package/dist/{chunk-MKPIAGVL.js → chunk-W6NFJXSA.js} +8 -19
- package/dist/chunk-W6NFJXSA.js.map +1 -0
- package/dist/{chunk-XBZWOM2K.js → chunk-X4A66EWZ.js} +8 -3
- package/dist/chunk-X4A66EWZ.js.map +1 -0
- package/dist/chunk-X4JAFD5R.js +3907 -0
- package/dist/chunk-X4JAFD5R.js.map +1 -0
- package/dist/{chunk-6KLX2RH5.js → chunk-XSA2QAKP.js} +334 -19
- package/dist/chunk-XSA2QAKP.js.map +1 -0
- package/dist/{chunk-HA46CYQK.js → chunk-XXWH2JLC.js} +792 -206
- package/dist/chunk-XXWH2JLC.js.map +1 -0
- package/dist/client.d.ts +2 -2
- package/dist/components/browser-viewer.d.ts +7 -1
- package/dist/components/computer-viewer.d.ts +4 -1
- package/dist/components/enrollment-consent.d.ts +6 -3
- package/dist/components/file-browser.d.ts +7 -1
- package/dist/components/human-input-form.d.ts +1 -1
- package/dist/components/human-input-surface.d.ts +1 -1
- package/dist/components/machine-card.d.ts +4 -1
- package/dist/components/machines/machine-detail.d.ts +4 -1
- package/dist/components/machines/machine-operation-policy.d.ts +11 -0
- package/dist/components/machines-dashboard.d.ts +3 -1
- package/dist/components/markdown.d.ts +20 -1
- package/dist/components/message-timeline.d.ts +1 -1
- package/dist/components/model-policy-picker.d.ts +2 -0
- package/dist/components/pierre-file.d.ts +1 -1
- package/dist/components/sandbox-files.d.ts +8 -2
- package/dist/components/sandbox-workspace.d.ts +33 -3
- package/dist/components/session-chrome.d.ts +15 -3
- package/dist/components/workspace-dock.d.ts +6 -2
- package/dist/composer.js +5 -5
- package/dist/computer-viewer-ZOP4Q7A7.js +16 -0
- package/dist/computer-viewer-ZOP4Q7A7.js.map +1 -0
- package/dist/hooks/channel-cache.d.ts +23 -0
- package/dist/hooks/shared-machines-list.d.ts +14 -0
- package/dist/hooks/use-attached-browsers.d.ts +2 -1
- package/dist/hooks/use-channels.d.ts +1 -0
- package/dist/hooks/use-composer.d.ts +37 -9
- package/dist/hooks/use-machine-chip.d.ts +3 -3
- package/dist/hooks/use-machines.d.ts +19 -1
- package/dist/hooks/use-sandbox-files.d.ts +1 -0
- package/dist/hooks/use-session-background-commands.d.ts +14 -0
- package/dist/hooks/use-turn-queue.d.ts +12 -0
- package/dist/hooks/use-workspace-sessions.d.ts +1 -0
- package/dist/index.d.ts +7 -5
- package/dist/index.js +1096 -623
- package/dist/index.js.map +1 -1
- package/dist/interaction.js +29 -181
- package/dist/interaction.js.map +1 -1
- package/dist/lib/interaction-errors.d.ts +3 -0
- package/dist/machines.d.ts +4 -2
- package/dist/machines.js +10 -2
- package/dist/model-policy.d.ts +1 -1
- package/dist/model-policy.js +1 -1
- package/dist/platform-activity-row-BJHUHXAT.js +169 -0
- package/dist/platform-activity-row-BJHUHXAT.js.map +1 -0
- package/dist/{queue-surface-implementation-2KVJTYXH.js → queue-surface-implementation-BSKUWHRI.js} +76 -16
- package/dist/queue-surface-implementation-BSKUWHRI.js.map +1 -0
- package/dist/realtime/realtime-control.d.ts +10 -3
- package/dist/realtime.js +138 -29
- package/dist/realtime.js.map +1 -1
- package/dist/session-ui.d.ts +1 -1
- package/dist/session-ui.js +6 -2
- package/dist/session.d.ts +2 -2
- package/dist/session.js +4 -4
- package/dist/timeline/index.d.ts +1 -1
- package/dist/timeline/platform-activity-row.d.ts +15 -0
- package/dist/timeline/screenshot-lightbox.d.ts +1 -1
- package/dist/timeline/shared.d.ts +4 -2
- package/dist/timeline/types.d.ts +48 -5
- package/dist/types/machines.d.ts +2 -2
- package/package.json +3 -3
- package/src/client.ts +12 -1
- package/src/components/artifacts/editable-spreadsheet.tsx +1 -1
- package/src/components/browser-viewer.tsx +361 -58
- package/src/components/chat-composer.tsx +1 -1
- package/src/components/composer.tsx +6 -6
- package/src/components/computer-viewer.tsx +368 -115
- package/src/components/enrollment-consent.tsx +64 -6
- package/src/components/file-browser.tsx +117 -2
- package/src/components/human-input-form.tsx +91 -55
- package/src/components/human-input-surface.tsx +1 -1
- package/src/components/machine-card.tsx +111 -0
- package/src/components/machine-input-display.ts +15 -0
- package/src/components/machines/machine-detail.tsx +47 -0
- package/src/components/machines/machine-operation-policy.tsx +179 -0
- package/src/components/machines-dashboard.tsx +6 -0
- package/src/components/markdown.tsx +174 -20
- package/src/components/message-timeline.tsx +162 -17
- package/src/components/model-policy-picker.tsx +9 -17
- package/src/components/pierre-file.tsx +1 -1
- package/src/components/queue-surface-implementation.tsx +82 -16
- package/src/components/sandbox-files.tsx +135 -19
- package/src/components/sandbox-workspace.tsx +204 -92
- package/src/components/session-chrome.tsx +415 -208
- package/src/components/session-status.tsx +1 -1
- package/src/components/user-message-body.tsx +168 -48
- package/src/components/workspace-dock.tsx +141 -175
- package/src/hooks/channel-cache.ts +104 -0
- package/src/hooks/shared-machines-list.ts +239 -0
- package/src/hooks/use-attached-browsers.ts +5 -1
- package/src/hooks/use-browser-frame-stream.ts +6 -1
- package/src/hooks/use-browser-session.ts +6 -4
- package/src/hooks/use-browser-sessions.ts +7 -1
- package/src/hooks/use-channels.ts +32 -10
- package/src/hooks/use-composer.ts +760 -291
- package/src/hooks/use-computer-frame-stream.ts +14 -9
- package/src/hooks/use-computer-session.ts +16 -14
- package/src/hooks/use-computer-sessions.ts +8 -1
- package/src/hooks/use-last-started-turn-policy.ts +1 -1
- package/src/hooks/use-machine-chip.ts +23 -6
- package/src/hooks/use-machines.ts +104 -6
- package/src/hooks/use-sandbox-files.ts +195 -140
- package/src/hooks/use-sandbox-git.ts +58 -29
- package/src/hooks/use-session-background-commands.ts +56 -0
- package/src/hooks/use-session-capabilities.ts +18 -0
- package/src/hooks/use-turn-queue.ts +111 -12
- package/src/hooks/use-workspace-sessions.ts +5 -1
- package/src/index.ts +26 -4
- package/src/lib/interaction-errors.ts +18 -0
- package/src/machines.ts +9 -1
- package/src/model-policy.ts +10 -2
- package/src/realtime/realtime-control.tsx +200 -37
- package/src/session-context.ts +1 -0
- package/src/session-ui.ts +6 -1
- package/src/session.ts +9 -1
- package/src/timeline/activity-rail.tsx +20 -75
- package/src/timeline/index.ts +4 -0
- package/src/timeline/platform-activity-row.tsx +201 -0
- package/src/timeline/projection.ts +476 -23
- package/src/timeline/screenshot-lightbox.tsx +48 -14
- package/src/timeline/shared.tsx +8 -2
- package/src/timeline/tool-renderers.tsx +104 -6
- package/src/timeline/turn-summary.tsx +6 -1
- package/src/timeline/types.ts +75 -2
- package/src/types/machines.ts +10 -0
- package/styles/compiled.css +115 -104
- package/dist/chunk-5UQPNLIO.js.map +0 -1
- package/dist/chunk-6KLX2RH5.js.map +0 -1
- package/dist/chunk-B6WPZSTT.js.map +0 -1
- package/dist/chunk-HA46CYQK.js.map +0 -1
- package/dist/chunk-IGC6A2ZI.js.map +0 -1
- package/dist/chunk-LLOLUHVG.js +0 -5937
- package/dist/chunk-LLOLUHVG.js.map +0 -1
- package/dist/chunk-MKPIAGVL.js.map +0 -1
- package/dist/chunk-R3AZOAB5.js.map +0 -1
- package/dist/chunk-XBZWOM2K.js.map +0 -1
- package/dist/chunk-Y7OEDUJ5.js.map +0 -1
- package/dist/interactive-workbench-viewers-TOHBXJEE.js +0 -15
- package/dist/queue-surface-implementation-2KVJTYXH.js.map +0 -1
- /package/dist/{interactive-workbench-viewers-TOHBXJEE.js.map → browser-viewer-GXUICIAG.js.map} +0 -0
|
@@ -55,7 +55,7 @@ import {
|
|
|
55
55
|
import { useSandboxTerminal } from "../hooks/use-sandbox-terminal";
|
|
56
56
|
import { useWorkspaceCapture } from "../hooks/use-workspace-capture";
|
|
57
57
|
import { useMachineChip, type MachineChip } from "../hooks/use-machine-chip";
|
|
58
|
-
import { useMachines } from "../hooks/use-machines";
|
|
58
|
+
import { MACHINES_SESSION_POLL_MS, useMachines } from "../hooks/use-machines";
|
|
59
59
|
import type { MachineView } from "../types/machines";
|
|
60
60
|
import { SandboxFiles } from "./sandbox-files";
|
|
61
61
|
import { WorkbenchChanges } from "./workbench-changes";
|
|
@@ -63,13 +63,13 @@ import { SandboxTerminal, type XtermTheme } from "./sandbox-terminal";
|
|
|
63
63
|
import { WorkspaceDock, type WorkspaceDockProps, type WorkspaceTab } from "./workspace-dock";
|
|
64
64
|
|
|
65
65
|
const LazyBrowserViewer = lazy(async () => {
|
|
66
|
-
const
|
|
67
|
-
return { default:
|
|
66
|
+
const { BrowserViewer } = await import("./browser-viewer");
|
|
67
|
+
return { default: BrowserViewer };
|
|
68
68
|
});
|
|
69
69
|
|
|
70
70
|
const LazyComputerViewer = lazy(async () => {
|
|
71
|
-
const
|
|
72
|
-
return { default:
|
|
71
|
+
const { ComputerViewer } = await import("./computer-viewer");
|
|
72
|
+
return { default: ComputerViewer };
|
|
73
73
|
});
|
|
74
74
|
|
|
75
75
|
/** A host-routed notification (replaces the app-only `sonner` toast coupling). */
|
|
@@ -125,18 +125,7 @@ function captureDegradedMessage(reason: string): string {
|
|
|
125
125
|
}
|
|
126
126
|
}
|
|
127
127
|
|
|
128
|
-
function
|
|
129
|
-
return (
|
|
130
|
-
<span className="inline-flex items-center gap-1.5">
|
|
131
|
-
<span className="[&>svg]:size-3.5" aria-hidden>
|
|
132
|
-
{icon}
|
|
133
|
-
</span>
|
|
134
|
-
<span>{children}</span>
|
|
135
|
-
</span>
|
|
136
|
-
);
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
function WorkbenchSurfaceLoading({ name }: { name: "Browser" | "Computer" }) {
|
|
128
|
+
function WorkbenchSurfaceLoading({ name }: { name: "Browser" | "Desktop" }) {
|
|
140
129
|
return (
|
|
141
130
|
<CenteredState
|
|
142
131
|
icon={
|
|
@@ -218,10 +207,51 @@ function sandboxProvisionInFlight(events: SessionEvent[]): boolean {
|
|
|
218
207
|
return inFlight;
|
|
219
208
|
}
|
|
220
209
|
|
|
210
|
+
/**
|
|
211
|
+
* Whether this session currently owns an admitted agent turn. Automatic live
|
|
212
|
+
* Files/Git reads pause across the whole turn: command boundaries are not a safe
|
|
213
|
+
* gap because the next tool can dispatch before a remote provider read releases
|
|
214
|
+
* its sandbox holder. Existing/captured content remains usable, and the terminal
|
|
215
|
+
* turn event resumes one authoritative refresh.
|
|
216
|
+
*/
|
|
217
|
+
function workspaceTurnInFlight(events: SessionEvent[]): boolean {
|
|
218
|
+
const activeTurnIds = new Set<string>();
|
|
219
|
+
let anonymousTurnInFlight = false;
|
|
220
|
+
let sessionOwnsWorkspace = false;
|
|
221
|
+
for (const event of events) {
|
|
222
|
+
if (event.type === "session.status.changed") {
|
|
223
|
+
const status =
|
|
224
|
+
event.payload && typeof event.payload === "object" && !Array.isArray(event.payload)
|
|
225
|
+
? (event.payload as Record<string, unknown>).status
|
|
226
|
+
: null;
|
|
227
|
+
sessionOwnsWorkspace = status === "running" || status === "recovering";
|
|
228
|
+
continue;
|
|
229
|
+
}
|
|
230
|
+
if (event.type === "turn.started") {
|
|
231
|
+
if (event.turnId) activeTurnIds.add(event.turnId);
|
|
232
|
+
else anonymousTurnInFlight = true;
|
|
233
|
+
continue;
|
|
234
|
+
}
|
|
235
|
+
if (
|
|
236
|
+
event.type !== "turn.completed" &&
|
|
237
|
+
event.type !== "turn.failed" &&
|
|
238
|
+
event.type !== "turn.cancelled" &&
|
|
239
|
+
event.type !== "turn.superseded"
|
|
240
|
+
) {
|
|
241
|
+
continue;
|
|
242
|
+
}
|
|
243
|
+
if (event.turnId) activeTurnIds.delete(event.turnId);
|
|
244
|
+
else anonymousTurnInFlight = false;
|
|
245
|
+
}
|
|
246
|
+
// The retained event window preserves the latest status even after a very
|
|
247
|
+
// long turn has evicted its original turn.started event.
|
|
248
|
+
return sessionOwnsWorkspace || anonymousTurnInFlight || activeTurnIds.size > 0;
|
|
249
|
+
}
|
|
250
|
+
|
|
221
251
|
export type WorkspaceMachine = {
|
|
222
252
|
/** Whether at least one built-in workbench surface is enabled. */
|
|
223
253
|
enabled: boolean;
|
|
224
|
-
/** The derived live/waking/offline chip model. */
|
|
254
|
+
/** The derived live/waking/sleeping-or-offline chip model. */
|
|
225
255
|
chip: MachineChip;
|
|
226
256
|
/** The machine these surfaces are bound to (the Modal group box or a
|
|
227
257
|
* self-hosted machine), or null while the fleet is still resolving. */
|
|
@@ -256,14 +286,25 @@ export type UseSandboxWorkspaceTabsOptions = ClientOverride & {
|
|
|
256
286
|
browserWebSocketFactory?: BrowserFrameWebSocketFactory | undefined;
|
|
257
287
|
/** Alternate Computer frame transport for non-browser runtimes and deterministic tests. */
|
|
258
288
|
computerWebSocketFactory?: ComputerFrameWebSocketFactory | undefined;
|
|
289
|
+
/** Host-owned setup page or store listing for attaching an existing Chrome profile. */
|
|
290
|
+
browserExtensionSetupUrl?: string | undefined;
|
|
259
291
|
/** File requested by a Changes guard. The Files surface defers the read until
|
|
260
292
|
* the sandbox is live, then reveals this path. */
|
|
261
293
|
requestedFilePath?: string | null | undefined;
|
|
262
294
|
/** Unique identity for `requestedFilePath`, including repeated requests for the
|
|
263
295
|
* same path. */
|
|
264
296
|
requestedFileRequestId?: string | number | null | undefined;
|
|
297
|
+
/** 1-based line to reveal after `requestedFilePath` opens. */
|
|
298
|
+
requestedFileLine?: number | null | undefined;
|
|
265
299
|
/** Route a guarded diff into the host's Files tab. */
|
|
266
300
|
onOpenFile?: ((path: string) => void) | undefined;
|
|
301
|
+
/** Restore and report navigation inside the built-in workbench surfaces. */
|
|
302
|
+
initialFilePath?: string | null | undefined;
|
|
303
|
+
onFilePathChange?: ((path: string | null) => void) | undefined;
|
|
304
|
+
initialBrowserSessionId?: string | null | undefined;
|
|
305
|
+
onBrowserSessionIdChange?: ((browserSessionId: string | null) => void) | undefined;
|
|
306
|
+
initialComputerSessionId?: string | null | undefined;
|
|
307
|
+
onComputerSessionIdChange?: ((computerSessionId: string | null) => void) | undefined;
|
|
267
308
|
/** Navigate the host dock to one exact linked ComputerSession. */
|
|
268
309
|
onOpenComputerSession?: ((computerSessionId: string) => void) | undefined;
|
|
269
310
|
requestedComputerSessionId?: string | null | undefined;
|
|
@@ -274,7 +315,8 @@ export type UseSandboxWorkspaceTabsResult = {
|
|
|
274
315
|
/** Changes | Files | Terminal | Browser | Desktop (capability-gated where noted). */
|
|
275
316
|
tabs: WorkspaceTab[];
|
|
276
317
|
/** The source-driven default tab: Changes when the first authoritative capture
|
|
277
|
-
*
|
|
318
|
+
* has changes, else Files (a host `initialTab` overrides). Choosing a tab
|
|
319
|
+
* never performs hidden live workspace I/O.
|
|
278
320
|
* null while that source resolves, or permanently when neither Changes nor
|
|
279
321
|
* Files is enabled; the choice latches once. */
|
|
280
322
|
defaultTab: string | null;
|
|
@@ -312,9 +354,17 @@ export function useSandboxWorkspaceTabs(
|
|
|
312
354
|
onNotify,
|
|
313
355
|
browserWebSocketFactory,
|
|
314
356
|
computerWebSocketFactory,
|
|
357
|
+
browserExtensionSetupUrl,
|
|
315
358
|
requestedFilePath,
|
|
316
359
|
requestedFileRequestId,
|
|
360
|
+
requestedFileLine,
|
|
317
361
|
onOpenFile,
|
|
362
|
+
initialFilePath,
|
|
363
|
+
onFilePathChange,
|
|
364
|
+
initialBrowserSessionId,
|
|
365
|
+
onBrowserSessionIdChange,
|
|
366
|
+
initialComputerSessionId,
|
|
367
|
+
onComputerSessionIdChange,
|
|
318
368
|
onOpenComputerSession,
|
|
319
369
|
requestedComputerSessionId,
|
|
320
370
|
requestedComputerRequestId,
|
|
@@ -338,7 +388,7 @@ export function useSandboxWorkspaceTabs(
|
|
|
338
388
|
...(placement ? { placement } : {}),
|
|
339
389
|
});
|
|
340
390
|
if (response.operation.state !== "completed" || response.session.lifecycle !== "active") {
|
|
341
|
-
throw new Error(response.operation.error?.message ?? "The
|
|
391
|
+
throw new Error(response.operation.error?.message ?? "The desktop could not be opened.");
|
|
342
392
|
}
|
|
343
393
|
return {
|
|
344
394
|
id: response.session.id,
|
|
@@ -356,6 +406,7 @@ export function useSandboxWorkspaceTabs(
|
|
|
356
406
|
const resolvedActiveTab = activeTab ?? initialTab;
|
|
357
407
|
const changesActive = resolvedActiveTab === WORKBENCH_TAB_CHANGES;
|
|
358
408
|
const filesActive = resolvedActiveTab === WORKBENCH_TAB_FILES;
|
|
409
|
+
const turnInFlight = workspaceTurnInFlight(events);
|
|
359
410
|
const surfaceIdentity = WORKBENCH_SURFACES.filter((surface) => surfaceSet.has(surface)).join(",");
|
|
360
411
|
|
|
361
412
|
// The two box-warming INTENTS, each off by default and each
|
|
@@ -378,13 +429,16 @@ export function useSandboxWorkspaceTabs(
|
|
|
378
429
|
},
|
|
379
430
|
[sessionId],
|
|
380
431
|
);
|
|
432
|
+
useEffect(() => {
|
|
433
|
+
if (requestedFilePath) requestWarmIntent("warmFiles");
|
|
434
|
+
}, [requestWarmIntent, requestedFilePath, requestedFileRequestId]);
|
|
381
435
|
|
|
382
436
|
// The session's machine fleet + the active-sandbox pointer. Drives the header
|
|
383
|
-
// chip (which machine + its connection state).
|
|
437
|
+
// chip (which machine + its connection state). Shares the session list poll.
|
|
384
438
|
const machines = useMachines({
|
|
385
439
|
workspaceId,
|
|
386
440
|
sessionId,
|
|
387
|
-
pollIntervalMs:
|
|
441
|
+
pollIntervalMs: MACHINES_SESSION_POLL_MS,
|
|
388
442
|
enabled: machineSurfaceEnabled,
|
|
389
443
|
});
|
|
390
444
|
const activeMachine: MachineView | null =
|
|
@@ -509,7 +563,16 @@ export function useSandboxWorkspaceTabs(
|
|
|
509
563
|
// No passive Channel-A reads while cold, even after a conclusive capture
|
|
510
564
|
// miss. A missing capture gets an explicit "Open live workspace" gate.
|
|
511
565
|
enabled: filesEnabled && (captureAvailable || (fileSystemOn && liveWorkspaceExpected)),
|
|
512
|
-
|
|
566
|
+
// A cold sandbox can become warm while the first agent command still owns
|
|
567
|
+
// startup. Do not race that command with an automatic Files read. Keeping
|
|
568
|
+
// this fence at turn scope also avoids unsafe millisecond gaps between
|
|
569
|
+
// sequential tool calls; rendered state and explicit user actions remain.
|
|
570
|
+
active: filesActive && !turnInFlight,
|
|
571
|
+
// A deliberate canonical absolute-path open browses in the selected target's
|
|
572
|
+
// advertised namespace, so the authoritative tree and link share exact paths.
|
|
573
|
+
...(requestedFilePath?.startsWith("/") && capabilities?.FileSystem.root
|
|
574
|
+
? { rootPath: capabilities.FileSystem.root }
|
|
575
|
+
: {}),
|
|
513
576
|
repoPaths,
|
|
514
577
|
liveness: liveIoLiveness,
|
|
515
578
|
capture: captureState.capture,
|
|
@@ -550,10 +613,11 @@ export function useSandboxWorkspaceTabs(
|
|
|
550
613
|
: changesComparison === "branch"
|
|
551
614
|
? workspaceDataEnabled && (captureSupportsBranch || (gitOn && liveWorkspaceExpected))
|
|
552
615
|
: workspaceDataEnabled && gitOn && liveWorkspaceExpected,
|
|
553
|
-
//
|
|
554
|
-
//
|
|
555
|
-
//
|
|
556
|
-
|
|
616
|
+
// Live Git belongs to the visible Changes surface. In particular, do not
|
|
617
|
+
// turn an unresolved/default tab into a hidden provider read: that races the
|
|
618
|
+
// agent's own sandbox commands and used to refresh after every tool output
|
|
619
|
+
// even while Files was visibly selected.
|
|
620
|
+
active: changesActive && !turnInFlight,
|
|
557
621
|
repoPaths,
|
|
558
622
|
liveness: liveIoLiveness,
|
|
559
623
|
comparison: changesComparison,
|
|
@@ -602,14 +666,12 @@ export function useSandboxWorkspaceTabs(
|
|
|
602
666
|
});
|
|
603
667
|
const workspaceWaking = chip.state === "waking";
|
|
604
668
|
|
|
605
|
-
// The pre-paint default
|
|
606
|
-
//
|
|
607
|
-
//
|
|
608
|
-
//
|
|
609
|
-
//
|
|
610
|
-
//
|
|
611
|
-
// the correct live default. The choice latches once so later edits never steal
|
|
612
|
-
// the user's current tab.
|
|
669
|
+
// The pre-paint default comes only from the durable capture. Default selection
|
|
670
|
+
// must never dispatch live provider work: a live Git probe can overlap the
|
|
671
|
+
// agent's first command, and an uncontrolled dock used to keep that probe
|
|
672
|
+
// active forever after it visibly selected Files. A capture with reviewable
|
|
673
|
+
// changes opens Changes; no capture (or an empty one) opens Files. The choice
|
|
674
|
+
// latches once so later edits never steal the user's current tab.
|
|
613
675
|
const defaultIdentity = `${sessionId}\u0000${surfaceIdentity}`;
|
|
614
676
|
const defaultTabRef = useRef<{ identity: string; value: string | null }>({
|
|
615
677
|
identity: defaultIdentity,
|
|
@@ -618,62 +680,25 @@ export function useSandboxWorkspaceTabs(
|
|
|
618
680
|
if (defaultTabRef.current.identity !== defaultIdentity) {
|
|
619
681
|
defaultTabRef.current = { identity: defaultIdentity, value: null };
|
|
620
682
|
}
|
|
621
|
-
const captureIsAuthoritative =
|
|
622
|
-
!liveWorkspaceExpected && (liveness !== undefined || caps.error !== null);
|
|
623
683
|
const captureHasChanges =
|
|
624
684
|
(captureState.fileCount ?? 0) > 0 ||
|
|
625
685
|
(captureState.capture?.repos.some((repo) => (repo.branchDiff?.length ?? 0) > 0) ?? false);
|
|
626
686
|
const captureUnavailable =
|
|
627
687
|
(captureState.fileCount === 0 && !captureHasChanges) || captureState.error !== null;
|
|
628
|
-
const implicitBranchFallbackPending =
|
|
629
|
-
changesComparison === "branch" &&
|
|
630
|
-
git.error !== null &&
|
|
631
|
-
storedChangesComparison?.sessionId !== sessionId;
|
|
632
688
|
if (defaultTabRef.current.value === null) {
|
|
633
689
|
if (initialTab && (!isWorkbenchSurface(initialTab) || surfaceSet.has(initialTab))) {
|
|
634
690
|
defaultTabRef.current.value = initialTab;
|
|
635
|
-
} else if (
|
|
636
|
-
defaultTabRef.current.value = sourceDrivenDefaultTab(
|
|
637
|
-
git.diff.length > 0,
|
|
638
|
-
changesEnabled,
|
|
639
|
-
filesEnabled,
|
|
640
|
-
);
|
|
641
|
-
} else if (
|
|
642
|
-
captureIsAuthoritative &&
|
|
643
|
-
captureState.fileCount !== null &&
|
|
644
|
-
captureState.available
|
|
645
|
-
) {
|
|
691
|
+
} else if (captureState.fileCount !== null && captureState.available) {
|
|
646
692
|
defaultTabRef.current.value = sourceDrivenDefaultTab(
|
|
647
693
|
captureHasChanges,
|
|
648
694
|
changesEnabled,
|
|
649
695
|
filesEnabled,
|
|
650
696
|
);
|
|
651
|
-
} else if (
|
|
652
|
-
captureIsAuthoritative &&
|
|
653
|
-
captureUnavailable &&
|
|
654
|
-
initialWorkspaceTab(events) === WORKBENCH_TAB_CHANGES
|
|
655
|
-
) {
|
|
697
|
+
} else if (captureUnavailable && initialWorkspaceTab(events) === WORKBENCH_TAB_CHANGES) {
|
|
656
698
|
defaultTabRef.current.value = sourceDrivenDefaultTab(true, changesEnabled, filesEnabled);
|
|
657
|
-
} else if (
|
|
658
|
-
//
|
|
659
|
-
//
|
|
660
|
-
defaultTabRef.current.value = sourceDrivenDefaultTab(true, changesEnabled, filesEnabled);
|
|
661
|
-
} else if (captureIsAuthoritative && captureState.fileCount !== null && captureUnavailable) {
|
|
662
|
-
// No durable review surface exists and the machine is resting. Files owns
|
|
663
|
-
// the explicit wake gate; do not issue a live Git probe merely to choose a
|
|
664
|
-
// tab.
|
|
665
|
-
defaultTabRef.current.value = sourceDrivenDefaultTab(false, changesEnabled, filesEnabled);
|
|
666
|
-
} else if (captureState.available && git.error && captureState.fileCount !== null) {
|
|
667
|
-
// A warm live read failed but the durable capture is intact: retain an
|
|
668
|
-
// immediate, deterministic review surface instead of hanging unresolved.
|
|
669
|
-
defaultTabRef.current.value = sourceDrivenDefaultTab(
|
|
670
|
-
captureHasChanges,
|
|
671
|
-
changesEnabled,
|
|
672
|
-
filesEnabled,
|
|
673
|
-
);
|
|
674
|
-
} else if (captureUnavailable && git.error && !implicitBranchFallbackPending) {
|
|
675
|
-
// No capture and live Git failed: Files is the least surprising fallback
|
|
676
|
-
// after the implicit branch-to-working fallback has also had its chance.
|
|
699
|
+
} else if (captureState.fileCount !== null || captureState.error !== null) {
|
|
700
|
+
// No durable review surface exists. Files owns the explicit live-workspace
|
|
701
|
+
// gate; a user can open Changes deliberately if they need a fresh Git read.
|
|
677
702
|
defaultTabRef.current.value = sourceDrivenDefaultTab(false, changesEnabled, filesEnabled);
|
|
678
703
|
}
|
|
679
704
|
}
|
|
@@ -687,7 +712,8 @@ export function useSandboxWorkspaceTabs(
|
|
|
687
712
|
if (changesEnabled)
|
|
688
713
|
list.push({
|
|
689
714
|
id: WORKBENCH_TAB_CHANGES,
|
|
690
|
-
label:
|
|
715
|
+
label: "Changes",
|
|
716
|
+
icon: <GitCompareArrowsIcon />,
|
|
691
717
|
badge: dirtyCount > 0 ? <DirtyBadge count={dirtyCount} /> : undefined,
|
|
692
718
|
content: (
|
|
693
719
|
<ChangesTabBody
|
|
@@ -719,7 +745,8 @@ export function useSandboxWorkspaceTabs(
|
|
|
719
745
|
if (filesEnabled)
|
|
720
746
|
list.push({
|
|
721
747
|
id: WORKBENCH_TAB_FILES,
|
|
722
|
-
label:
|
|
748
|
+
label: "Files",
|
|
749
|
+
icon: <FileCode2Icon />,
|
|
723
750
|
content: (
|
|
724
751
|
<SandboxFiles
|
|
725
752
|
key={sessionId}
|
|
@@ -746,11 +773,16 @@ export function useSandboxWorkspaceTabs(
|
|
|
746
773
|
? { requestedPathRequestId: requestedFileRequestId }
|
|
747
774
|
: {}),
|
|
748
775
|
requestedPathReady: sandboxAcceptsLiveIo(liveness),
|
|
776
|
+
...(requestedFileLine != null && requestedFileLine > 0
|
|
777
|
+
? { requestedLine: requestedFileLine }
|
|
778
|
+
: {}),
|
|
749
779
|
}
|
|
750
780
|
: {})}
|
|
751
781
|
// Ordinary capture browsing does not warm a box. The first edit — or an
|
|
752
782
|
// explicit guarded-file open from Changes — is deliberate live-file intent.
|
|
753
783
|
onEditIntent={() => requestWarmIntent("warmFiles")}
|
|
784
|
+
initialSelectedPath={initialFilePath}
|
|
785
|
+
onSelectedPathChange={onFilePathChange}
|
|
754
786
|
className="h-full"
|
|
755
787
|
/>
|
|
756
788
|
),
|
|
@@ -760,7 +792,8 @@ export function useSandboxWorkspaceTabs(
|
|
|
760
792
|
if (terminalOn) {
|
|
761
793
|
list.push({
|
|
762
794
|
id: WORKBENCH_TAB_TERMINAL,
|
|
763
|
-
label:
|
|
795
|
+
label: "Terminal",
|
|
796
|
+
icon: <SquareTerminalIcon />,
|
|
764
797
|
content: (
|
|
765
798
|
<div className="h-full bg-og-bg p-1">
|
|
766
799
|
<SandboxTerminal
|
|
@@ -785,7 +818,8 @@ export function useSandboxWorkspaceTabs(
|
|
|
785
818
|
if (browserEnabled) {
|
|
786
819
|
list.push({
|
|
787
820
|
id: WORKBENCH_TAB_BROWSER,
|
|
788
|
-
label:
|
|
821
|
+
label: "Browser",
|
|
822
|
+
icon: <Globe2Icon />,
|
|
789
823
|
content: (
|
|
790
824
|
<Suspense fallback={<WorkbenchSurfaceLoading name="Browser" />}>
|
|
791
825
|
<LazyBrowserViewer
|
|
@@ -803,9 +837,12 @@ export function useSandboxWorkspaceTabs(
|
|
|
803
837
|
// user/agent first visits it.
|
|
804
838
|
enabled={workspaceVisible}
|
|
805
839
|
onNotify={onNotify}
|
|
840
|
+
initialBrowserSessionId={initialBrowserSessionId}
|
|
841
|
+
onBrowserSessionIdChange={onBrowserSessionIdChange}
|
|
806
842
|
{...(desktopEnabled ? { createLinkedComputer } : {})}
|
|
807
843
|
{...(onOpenComputerSession ? { onOpenComputer: onOpenComputerSession } : {})}
|
|
808
844
|
{...(browserWebSocketFactory ? { webSocketFactory: browserWebSocketFactory } : {})}
|
|
845
|
+
{...(browserExtensionSetupUrl ? { browserExtensionSetupUrl } : {})}
|
|
809
846
|
className="h-full"
|
|
810
847
|
/>
|
|
811
848
|
</Suspense>
|
|
@@ -818,9 +855,10 @@ export function useSandboxWorkspaceTabs(
|
|
|
818
855
|
if (desktopEnabled) {
|
|
819
856
|
list.push({
|
|
820
857
|
id: WORKBENCH_TAB_DESKTOP,
|
|
821
|
-
label:
|
|
858
|
+
label: "Desktop",
|
|
859
|
+
icon: <MonitorIcon />,
|
|
822
860
|
content: (
|
|
823
|
-
<Suspense fallback={<WorkbenchSurfaceLoading name="
|
|
861
|
+
<Suspense fallback={<WorkbenchSurfaceLoading name="Desktop" />}>
|
|
824
862
|
<LazyComputerViewer
|
|
825
863
|
key={sessionId}
|
|
826
864
|
client={client}
|
|
@@ -832,6 +870,8 @@ export function useSandboxWorkspaceTabs(
|
|
|
832
870
|
// second and later Computer opens local-machine fast.
|
|
833
871
|
enabled={workspaceVisible}
|
|
834
872
|
onNotify={onNotify}
|
|
873
|
+
initialComputerSessionId={initialComputerSessionId}
|
|
874
|
+
onComputerSessionIdChange={onComputerSessionIdChange}
|
|
835
875
|
requestedComputerSessionId={requestedComputerSessionId}
|
|
836
876
|
requestedComputerRequestId={requestedComputerRequestId}
|
|
837
877
|
{...(computerWebSocketFactory ? { webSocketFactory: computerWebSocketFactory } : {})}
|
|
@@ -856,7 +896,14 @@ export function useSandboxWorkspaceTabs(
|
|
|
856
896
|
warmTerminal,
|
|
857
897
|
requestedFilePath,
|
|
858
898
|
requestedFileRequestId,
|
|
899
|
+
requestedFileLine,
|
|
859
900
|
onOpenFile,
|
|
901
|
+
initialFilePath,
|
|
902
|
+
onFilePathChange,
|
|
903
|
+
initialBrowserSessionId,
|
|
904
|
+
onBrowserSessionIdChange,
|
|
905
|
+
initialComputerSessionId,
|
|
906
|
+
onComputerSessionIdChange,
|
|
860
907
|
onOpenComputerSession,
|
|
861
908
|
requestedComputerSessionId,
|
|
862
909
|
requestedComputerRequestId,
|
|
@@ -913,14 +960,35 @@ export type SandboxWorkspaceProps = ClientOverride & {
|
|
|
913
960
|
/** Host tabs injected AFTER the workbench tabs (e.g. a "Debug" tab). */
|
|
914
961
|
trailingTabs?: WorkspaceTab[] | undefined;
|
|
915
962
|
/** Override the pre-paint default tab (e.g. a host landing tab id). When
|
|
916
|
-
* omitted the workbench decides from
|
|
963
|
+
* omitted the workbench decides from the durable workspace capture. */
|
|
917
964
|
initialTab?: string | undefined;
|
|
965
|
+
onActiveTabChange?: ((activeTab: string) => void) | undefined;
|
|
966
|
+
initialFilePath?: string | null | undefined;
|
|
967
|
+
onFilePathChange?: ((path: string | null) => void) | undefined;
|
|
968
|
+
/**
|
|
969
|
+
* Host request to open a workspace file (and optional line) in Files. A new
|
|
970
|
+
* `requestId` re-opens even the same path.
|
|
971
|
+
*/
|
|
972
|
+
openFileRequest?:
|
|
973
|
+
| {
|
|
974
|
+
path: string;
|
|
975
|
+
line?: number | null;
|
|
976
|
+
requestId: number;
|
|
977
|
+
}
|
|
978
|
+
| null
|
|
979
|
+
| undefined;
|
|
980
|
+
initialBrowserSessionId?: string | null | undefined;
|
|
981
|
+
onBrowserSessionIdChange?: ((browserSessionId: string | null) => void) | undefined;
|
|
982
|
+
initialComputerSessionId?: string | null | undefined;
|
|
983
|
+
onComputerSessionIdChange?: ((computerSessionId: string | null) => void) | undefined;
|
|
918
984
|
/** Host-routed notifications (no toast dependency in the package). */
|
|
919
985
|
onNotify?: ((notification: WorkspaceNotification) => void) | undefined;
|
|
920
986
|
/** Alternate Browser frame transport for non-browser runtimes and deterministic tests. */
|
|
921
987
|
browserWebSocketFactory?: BrowserFrameWebSocketFactory | undefined;
|
|
922
988
|
/** Alternate Computer frame transport for non-browser runtimes and deterministic tests. */
|
|
923
989
|
computerWebSocketFactory?: ComputerFrameWebSocketFactory | undefined;
|
|
990
|
+
/** Host-owned setup page or store listing for attaching an existing Chrome profile. */
|
|
991
|
+
browserExtensionSetupUrl?: string | undefined;
|
|
924
992
|
/** Controlled collapsed state for hosts with their own dock toggle. */
|
|
925
993
|
collapsed?: boolean | undefined;
|
|
926
994
|
onCollapsedChange?: ((collapsed: boolean) => void) | undefined;
|
|
@@ -951,9 +1019,18 @@ export function SandboxWorkspace(props: SandboxWorkspaceProps): ReactNode {
|
|
|
951
1019
|
leadingTabs,
|
|
952
1020
|
trailingTabs,
|
|
953
1021
|
initialTab,
|
|
1022
|
+
onActiveTabChange,
|
|
1023
|
+
initialFilePath,
|
|
1024
|
+
onFilePathChange,
|
|
1025
|
+
openFileRequest,
|
|
1026
|
+
initialBrowserSessionId,
|
|
1027
|
+
onBrowserSessionIdChange,
|
|
1028
|
+
initialComputerSessionId,
|
|
1029
|
+
onComputerSessionIdChange,
|
|
954
1030
|
onNotify,
|
|
955
1031
|
browserWebSocketFactory,
|
|
956
1032
|
computerWebSocketFactory,
|
|
1033
|
+
browserExtensionSetupUrl,
|
|
957
1034
|
collapsed,
|
|
958
1035
|
onCollapsedChange,
|
|
959
1036
|
showCollapseControl,
|
|
@@ -972,6 +1049,7 @@ export function SandboxWorkspace(props: SandboxWorkspaceProps): ReactNode {
|
|
|
972
1049
|
const [requestedFile, setRequestedFile] = useState<{
|
|
973
1050
|
sessionId: string;
|
|
974
1051
|
path: string;
|
|
1052
|
+
line: number | null;
|
|
975
1053
|
requestId: number;
|
|
976
1054
|
} | null>(null);
|
|
977
1055
|
const [requestedComputer, setRequestedComputer] = useState<{
|
|
@@ -984,13 +1062,27 @@ export function SandboxWorkspace(props: SandboxWorkspaceProps): ReactNode {
|
|
|
984
1062
|
const selectedTab = storedSelection?.sessionId === sessionId ? storedSelection.tab : null;
|
|
985
1063
|
const activeTabHint = selectedTab ?? initialTab ?? leadingTabs?.[0]?.id ?? null;
|
|
986
1064
|
const openFile = useCallback(
|
|
987
|
-
(path: string) => {
|
|
988
|
-
|
|
989
|
-
|
|
1065
|
+
(path: string, line?: number | null) => {
|
|
1066
|
+
setRequestedFile({
|
|
1067
|
+
sessionId,
|
|
1068
|
+
path,
|
|
1069
|
+
line: line != null && line > 0 ? line : null,
|
|
1070
|
+
requestId: ++nextFileRequestId.current,
|
|
1071
|
+
});
|
|
990
1072
|
setStoredSelection({ sessionId, tab: WORKBENCH_TAB_FILES });
|
|
1073
|
+
onActiveTabChange?.(WORKBENCH_TAB_FILES);
|
|
1074
|
+
onFilePathChange?.(path);
|
|
1075
|
+
onCollapsedChange?.(false);
|
|
991
1076
|
},
|
|
992
|
-
[sessionId],
|
|
1077
|
+
[onActiveTabChange, onCollapsedChange, onFilePathChange, sessionId],
|
|
993
1078
|
);
|
|
1079
|
+
const handledOpenFileRequestId = useRef<number | null>(null);
|
|
1080
|
+
useEffect(() => {
|
|
1081
|
+
if (!openFileRequest) return;
|
|
1082
|
+
if (handledOpenFileRequestId.current === openFileRequest.requestId) return;
|
|
1083
|
+
handledOpenFileRequestId.current = openFileRequest.requestId;
|
|
1084
|
+
openFile(openFileRequest.path, openFileRequest.line);
|
|
1085
|
+
}, [openFile, openFileRequest]);
|
|
994
1086
|
const openComputer = useCallback(
|
|
995
1087
|
(computerSessionId: string) => {
|
|
996
1088
|
nextComputerRequestId.current += 1;
|
|
@@ -1000,8 +1092,10 @@ export function SandboxWorkspace(props: SandboxWorkspaceProps): ReactNode {
|
|
|
1000
1092
|
requestId: nextComputerRequestId.current,
|
|
1001
1093
|
});
|
|
1002
1094
|
setStoredSelection({ sessionId, tab: WORKBENCH_TAB_DESKTOP });
|
|
1095
|
+
onActiveTabChange?.(WORKBENCH_TAB_DESKTOP);
|
|
1096
|
+
onComputerSessionIdChange?.(computerSessionId);
|
|
1003
1097
|
},
|
|
1004
|
-
[sessionId],
|
|
1098
|
+
[onActiveTabChange, onComputerSessionIdChange, sessionId],
|
|
1005
1099
|
);
|
|
1006
1100
|
|
|
1007
1101
|
const {
|
|
@@ -1020,8 +1114,16 @@ export function SandboxWorkspace(props: SandboxWorkspaceProps): ReactNode {
|
|
|
1020
1114
|
...(onNotify ? { onNotify } : {}),
|
|
1021
1115
|
...(browserWebSocketFactory ? { browserWebSocketFactory } : {}),
|
|
1022
1116
|
...(computerWebSocketFactory ? { computerWebSocketFactory } : {}),
|
|
1117
|
+
...(browserExtensionSetupUrl ? { browserExtensionSetupUrl } : {}),
|
|
1118
|
+
initialFilePath,
|
|
1119
|
+
onFilePathChange,
|
|
1120
|
+
initialBrowserSessionId,
|
|
1121
|
+
onBrowserSessionIdChange,
|
|
1122
|
+
initialComputerSessionId,
|
|
1123
|
+
onComputerSessionIdChange,
|
|
1023
1124
|
requestedFilePath: requestedFile?.sessionId === sessionId ? requestedFile.path : null,
|
|
1024
1125
|
requestedFileRequestId: requestedFile?.sessionId === sessionId ? requestedFile.requestId : null,
|
|
1126
|
+
requestedFileLine: requestedFile?.sessionId === sessionId ? requestedFile.line : null,
|
|
1025
1127
|
onOpenFile: openFile,
|
|
1026
1128
|
onOpenComputerSession: openComputer,
|
|
1027
1129
|
requestedComputerSessionId:
|
|
@@ -1040,9 +1142,19 @@ export function SandboxWorkspace(props: SandboxWorkspaceProps): ReactNode {
|
|
|
1040
1142
|
const activeTab =
|
|
1041
1143
|
preferredTab && tabs.some((tab) => tab.id === preferredTab) ? preferredTab : tabs[0]?.id;
|
|
1042
1144
|
const selectTab = useCallback(
|
|
1043
|
-
(tab: string) =>
|
|
1044
|
-
|
|
1145
|
+
(tab: string) => {
|
|
1146
|
+
setStoredSelection({ sessionId, tab });
|
|
1147
|
+
onActiveTabChange?.(tab);
|
|
1148
|
+
},
|
|
1149
|
+
[onActiveTabChange, sessionId],
|
|
1045
1150
|
);
|
|
1151
|
+
useEffect(() => {
|
|
1152
|
+
if (selectedTab !== null || defaultTab === null) return;
|
|
1153
|
+
// Once the source-driven choice resolves, make it the real controlled
|
|
1154
|
+
// selection. Without this handoff the dock could visibly show Files while
|
|
1155
|
+
// the data hooks continued treating the selection as unresolved.
|
|
1156
|
+
setStoredSelection({ sessionId, tab: defaultTab });
|
|
1157
|
+
}, [defaultTab, selectedTab, sessionId]);
|
|
1046
1158
|
|
|
1047
1159
|
return (
|
|
1048
1160
|
<WorkspaceDock
|
|
@@ -1086,7 +1198,7 @@ function chipDotClass(state: MachineChip["state"]): string {
|
|
|
1086
1198
|
}
|
|
1087
1199
|
|
|
1088
1200
|
/**
|
|
1089
|
-
* The dock-header machine status: one quiet, truthful live/waking/
|
|
1201
|
+
* The dock-header machine status: one quiet, truthful live/waking/resting
|
|
1090
1202
|
* indicator. It is intentionally not interactive; detailed machine controls do
|
|
1091
1203
|
* not belong in a transient popover above the workspace tabs.
|
|
1092
1204
|
*/
|