import type { ComputerAction, ComputerActionReceipt, ComputerClipboard, ComputerFrame, ComputerObservation, ComputerSession, ComputerTarget, InteractionIntervention, InteractionSemanticNode, } from "@opengeni/sdk/interaction"; import { interactionControlFailureFromError } from "@opengeni/sdk/interaction"; import type { DesktopStreamCapability } from "@opengeni/sdk"; import { ChevronDownIcon, CircleAlertIcon, KeyboardIcon, LoaderCircleIcon, LockKeyholeIcon, MonitorIcon, MousePointer2Icon, PanelsTopLeftIcon, PlusIcon, RefreshCwIcon, RotateCcwIcon, } from "lucide-react"; import { type ClipboardEvent, type CompositionEvent, type KeyboardEvent, type MouseEvent, type PointerEvent, type ReactNode, type WheelEvent, useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState, } from "react"; import { type ComputerFrameWebSocketFactory, useComputerFrameStream, } from "../hooks/use-computer-frame-stream"; import { computerActionReceiptError, isStaleComputerActionResult, useComputerSession, } from "../hooks/use-computer-session"; import { useComputerInputPosture } from "../hooks/use-computer-input-posture"; import { useComputerSessions } from "../hooks/use-computer-sessions"; import { useInteractionInterventions } from "../hooks/use-interaction-interventions"; import { cn } from "../lib/cn"; import { copyTextToClipboard } from "../lib/clipboard"; import { isSourcePlacementChangedError } from "../lib/interaction-errors"; import type { EmbeddedComputerInteractionClientOverride } from "../session-context"; import { InteractionInterventionBanner } from "./interaction-intervention-banner"; import { DesktopViewer } from "./desktop-viewer"; import { useViewerMenuDismiss } from "./use-viewer-menu-dismiss"; import { useOpenGeniText, type OpenGeniText } from "../messages/runtime"; export type ComputerViewerNotification = { kind: "error" | "info"; message: string }; export type ComputerViewerProps = EmbeddedComputerInteractionClientOverride & { /** Selected Opengeni agent/session. Peer ComputerSessions remain visible. */ sessionId: string; enabled?: boolean | undefined; className?: string | undefined; onNotify?: ((notification: ComputerViewerNotification) => void) | undefined; /** Tests/demos only. Production uses the browser's native WebSocket. */ webSocketFactory?: ComputerFrameWebSocketFactory | undefined; renderEmpty?: ((create: () => void, creating: boolean) => ReactNode) | undefined; /** Host navigation request for one exact ComputerSession. */ requestedComputerSessionId?: string | null | undefined; requestedComputerRequestId?: string | number | null | undefined; /** Restore and report the Desktop session selected in this task's dock. */ initialComputerSessionId?: string | null | undefined; onComputerSessionIdChange?: ((computerSessionId: string | null) => void) | undefined; }; type ComputerSelection = { sessionId: string; pinned: boolean } | null; type PointerStart = { x: number; y: number; pointerId: number; frame: ComputerFrame }; /** Full ComputerSession surface: workspace discovery, peer switching, app/window/ * screen navigation, semantic controls, exact-frame input, and live media. */ export function ComputerViewer({ sessionId, enabled = true, className, onNotify, webSocketFactory, renderEmpty, requestedComputerSessionId, requestedComputerRequestId, initialComputerSessionId, onComputerSessionIdChange, ...override }: ComputerViewerProps) { const t = useOpenGeniText(); const registry = useComputerSessions({ ...override, sessionId, enabled }); const createSession = registry.create; const refreshRegistry = registry.refresh; const liveRelevant = useMemo( () => registry.relevantSessions.filter((session) => isLiveComputer(session)), [registry.relevantSessions], ); const connectedPlacementLoss = useMemo( () => registry.relevantSessions.find( (session) => session.lifecycle === "lost" && session.failureCode === "source_placement_changed" && session.placement.kind === "connected_machine", ) ?? null, [registry.relevantSessions], ); const recentRelevantFailure = useMemo( () => liveRelevant.length === 0 ? (registry.relevantSessions.find( (session) => ["failed", "lost"].includes(session.lifecycle) && !( session.failureCode === "source_placement_changed" && session.placement.kind === "connected_machine" ), ) ?? null) : null, [liveRelevant.length, registry.relevantSessions], ); const liveSessions = useMemo(() => { const sessions = registry.sessions.filter((session) => isLiveComputer(session)); return recentRelevantFailure ? [...sessions, recentRelevantFailure] : sessions; }, [recentRelevantFailure, registry.sessions]); const relevant = useMemo( () => liveRelevant.length > 0 ? liveRelevant : recentRelevantFailure ? [recentRelevantFailure] : [], [liveRelevant, recentRelevantFailure], ); const [selection, setSelection] = useState(() => initialComputerSessionId ? { sessionId: initialComputerSessionId, pinned: true } : null, ); const selectComputerSession = useCallback( (next: ComputerSelection) => { setSelection(next); onComputerSessionIdChange?.(next?.sessionId ?? null); }, [onComputerSessionIdChange], ); const interventions = useInteractionInterventions({ ...override, enabled, resourceKind: "computer_session", }); const [creating, setCreating] = useState(false); const [createError, setCreateError] = useState(null); const [showControls, setShowControls] = useState(false); const previousSessionIdRef = useRef(sessionId); const handledRequestRef = useRef(null); const seenInterventionIdsRef = useRef(new Set()); const createInFlightRef = useRef(false); const autoCreateSessionRef = useRef(null); const emptyCatalogConfirmationRef = useRef(null); const endedGenerationLossRef = useRef(new Set()); const recreatedPlacementLossRef = useRef(new Set()); const [suppressGenericCreate, setSuppressGenericCreate] = useState(false); const endStaleComputer = registry.end; useEffect(() => { if (!enabled) return; for (const session of registry.sessions) { if (!isAttachedChromeGenerationLoss(session)) continue; setSuppressGenericCreate(true); if (endedGenerationLossRef.current.has(session.id)) continue; endedGenerationLossRef.current.add(session.id); void endStaleComputer(session.id).catch(() => { endedGenerationLossRef.current.delete(session.id); }); } }, [enabled, endStaleComputer, registry.sessions]); const notifyError = useCallback( (cause: unknown, fallback: string) => { if (isStaleComputerActionResult(cause)) return; onNotify?.({ kind: "error", message: cause instanceof Error ? cause.message : fallback, }); }, [onNotify], ); useEffect(() => { if (previousSessionIdRef.current === sessionId) return; previousSessionIdRef.current = sessionId; handledRequestRef.current = null; autoCreateSessionRef.current = null; emptyCatalogConfirmationRef.current = null; recreatedPlacementLossRef.current.clear(); setSuppressGenericCreate(false); setCreateError(null); setSelection( initialComputerSessionId ? { sessionId: initialComputerSessionId, pinned: true } : null, ); }, [initialComputerSessionId, sessionId]); useEffect(() => { if (!enabled || registry.loading) return; const requested = requestedComputerSessionId ? (liveSessions.find((session) => session.id === requestedComputerSessionId) ?? null) : null; const requestKey = requested ? `${requestedComputerRequestId ?? "default"}:${requested.id}` : null; if (requested && requestKey && handledRequestRef.current !== requestKey) { handledRequestRef.current = requestKey; selectComputerSession({ sessionId: requested.id, pinned: true }); return; } const selectedStillLive = liveSessions.some((session) => session.id === selection?.sessionId); // Peer computers remain one click away, but never become the implicit // computer for another agent. Otherwise opening a sandbox's Computer tab // can silently select—and send input to—a connected user's Mac. if (selection?.pinned && selectedStillLive) return; const preferred = relevant[0] ?? null; if (!preferred) { if (selection) selectComputerSession(null); return; } if (!selectedStillLive || !selection?.pinned) { if (selection?.sessionId !== preferred.id || selection.pinned) { selectComputerSession({ sessionId: preferred.id, pinned: false }); } } }, [ liveSessions, enabled, registry.loading, relevant, requestedComputerRequestId, requestedComputerSessionId, selectComputerSession, selection, ]); const selectedRegistrySession = useMemo( () => liveSessions.find((session) => session.id === selection?.sessionId) ?? null, [liveSessions, selection?.sessionId], ); const interventionCounts = useMemo( () => countInterventions(interventions.interventions), [interventions.interventions], ); const selectedInterventions = useMemo( () => interventions.interventions.filter( (intervention) => intervention.resourceId === selection?.sessionId, ), [interventions.interventions, selection?.sessionId], ); const controllerReady = selectedRegistrySession?.lifecycle === "active"; const computer = useComputerSession({ ...override, computerSessionId: selection?.sessionId ?? null, enabled: enabled && selection !== null && controllerReady, }); useEffect(() => { setShowControls(computer.selectedTarget?.kind === "app"); }, [sessionId, selection?.sessionId, computer.selectedTarget?.id, computer.selectedTarget?.kind]); useEffect(() => { if (!isSourcePlacementChangedError(computer.error, "computer_session")) return; void refreshRegistry(); }, [computer.error, refreshRegistry]); const act = computer.act; const actFromFrame = computer.actFromFrame; const refreshComputer = computer.refresh; const frames = useComputerFrameStream({ ...override, computerSessionId: selection?.sessionId ?? null, targetId: computer.selectedTarget?.id ?? null, enabled: enabled && selection !== null && controllerReady && computer.selectedTarget !== null && isComputerFrameTarget(computer.selectedTarget), stream: { format: "jpeg", quality: 78, maxWidth: 1_920, maxHeight: 1_200, everyNthFrame: 1, }, ...(webSocketFactory ? { webSocketFactory } : {}), }); const displayedFrame = frames.frame && frames.frame.computerSessionId === selection?.sessionId && frames.frame.targetId === computer.selectedTarget?.id && frames.frame.targetGeneration === computer.selectedTarget?.targetGeneration ? frames.frame : null; const rfbStream = frames.attachment?.stream.kind === "direct_rfb" ? frames.attachment.stream : null; const appTarget = computer.selectedTarget?.kind === "app"; const appInputPosture = useComputerInputPosture({ ...override, computerSessionId: selection?.sessionId ?? null, controllerGeneration: computer.session?.controller?.controllerGeneration ?? null, enabled: enabled && controllerReady && appTarget, }); const inputPostureRef = useRef({ sessionId: selection?.sessionId ?? null, controllerGeneration: computer.session?.controller?.controllerGeneration ?? null, denied: false, }); const postureSessionId = selection?.sessionId ?? null; const postureController = computer.session?.controller?.controllerGeneration ?? null; if ( inputPostureRef.current.sessionId !== postureSessionId || inputPostureRef.current.controllerGeneration !== postureController ) { inputPostureRef.current = { sessionId: postureSessionId, controllerGeneration: postureController, denied: false, }; } if ( frames.attachment?.computerSessionId === postureSessionId && frames.attachment.controllerGeneration === postureController ) { inputPostureRef.current.denied = frames.attachment.inputAllowed === false; } // Keep a known denial while a fresh attachment is pending or a semantic-only // target has no stream. Only a fresh posture for this controller clears it. const inputDenied = appTarget ? appInputPosture.inputAllowed !== true : inputPostureRef.current.denied; const attachmentInputAllowed = frames.attachment !== null && !inputDenied; const inputPosturePending = appTarget ? appInputPosture.pending : inputDenied && (frames.state === "attaching" || frames.state === "reconnecting"); const machineLocked = selectedRegistrySession?.failureCode === "machine_locked"; const controlUnavailable = computer.controlError !== null; const connectionState = controlUnavailable ? "error" : frames.state; // RFB has one combined input switch; partial native input stays view-only. const rfbInputEnabled = rfbStream?.inputAllowed === true && attachmentInputAllowed && !machineLocked && !controlUnavailable && computer.session?.capabilities?.pointerInput === true && computer.session?.capabilities?.keyboardInput === true; const rfbCapability = useMemo(() => { if (!rfbStream || !frames.attachment) return null; const bounds = computer.selectedTarget?.bounds; return { transport: "vnc-ws", client: "novnc", mode: rfbInputEnabled ? "interactive" : "read-only", url: rfbStream.url, token: null, expiresAt: frames.attachment.expiresAt, resolution: [bounds?.width ?? 1_440, bounds?.height ?? 900], unredacted: true, requiresAcknowledgment: false, acknowledged: true, shared: false, sharedSessionIds: [], reason: null, }; }, [computer.selectedTarget?.bounds, frames.attachment, rfbInputEnabled, rfbStream]); const currentIds = useMemo(() => new Set(relevant.map((session) => session.id)), [relevant]); useEffect(() => { for (const intervention of interventions.interventions) { if (seenInterventionIdsRef.current.has(intervention.id)) continue; seenInterventionIdsRef.current.add(intervention.id); onNotify?.({ kind: "info", message: `${interventionTitle(t, intervention)}: ${intervention.reason}`, }); } }, [interventions.interventions, onNotify, t]); const resolveIntervention = useCallback( (intervention: InteractionIntervention, outcome: "completed" | "dismissed") => { void interventions .resolve(intervention.id, { expectedVersion: intervention.version, outcome, }) .then(() => { onNotify?.({ kind: "info", message: outcome === "completed" ? t("computerViewer.agentNotifiedContinuingWork") : t("computerViewer.requestCancelled"), }); }) .catch((cause) => notifyError(cause, t("computerViewer.couldNotUpdateTheDesktop"))); }, [interventions, notifyError, onNotify, t], ); const createComputer = useCallback(() => { if (createInFlightRef.current) return; createInFlightRef.current = true; setCreateError(null); setCreating(true); void createSession({ sessionId, name: "Desktop" }) .then((response) => { selectComputerSession({ sessionId: response.session.id, pinned: false }); }) .catch((cause) => { const error = cause instanceof Error ? cause : new Error(t("computerViewer.couldNotOpenADesktop")); setCreateError(error); notifyError(error, t("computerViewer.couldNotOpenADesktop")); }) .finally(() => { createInFlightRef.current = false; setCreating(false); }); }, [createSession, notifyError, selectComputerSession, sessionId, t]); useEffect(() => { if ( !enabled || registry.loading || registry.refreshing || liveRelevant.length > 0 || !connectedPlacementLoss || recreatedPlacementLossRef.current.has(connectedPlacementLoss.id) ) { return; } recreatedPlacementLossRef.current.add(connectedPlacementLoss.id); autoCreateSessionRef.current = sessionId; emptyCatalogConfirmationRef.current = null; createComputer(); }, [ connectedPlacementLoss, createComputer, enabled, liveRelevant.length, registry.loading, registry.refreshing, sessionId, ]); useEffect(() => { // Once this task's Desktop has been observed, a later transient empty // registry snapshot must not create a second one. This can happen while the // lazy surface is disabled/re-enabled or while an invalidation refresh // briefly races the association projection. The existing Desktop remains // the task's authority; explicit New desktop is still available. if (relevant.length > 0) { autoCreateSessionRef.current = sessionId; emptyCatalogConfirmationRef.current = null; } }, [relevant.length, sessionId]); useEffect(() => { if ( !enabled || registry.loading || registry.refreshing || registry.error || relevant.length > 0 || suppressGenericCreate || autoCreateSessionRef.current === sessionId ) { return; } // A lazy surface can momentarily publish an empty settled projection while // its initial catalog and a workspace invalidation cross. Confirm the empty // result with one authoritative refresh before creating anything. A real // empty catalog creates on the following render; an existing association // appears and cancels this path. if (emptyCatalogConfirmationRef.current !== sessionId) { emptyCatalogConfirmationRef.current = sessionId; void refreshRegistry(); return; } autoCreateSessionRef.current = sessionId; createComputer(); }, [ createComputer, enabled, refreshRegistry, registry.error, registry.loading, registry.refreshing, relevant.length, sessionId, suppressGenericCreate, ]); const perform = useCallback( async ( action: ComputerAction, frame: ComputerFrame | null, operationId?: string, ): Promise => { if (computer.controlError) throw computer.controlError; if (inputDenied) throw new Error(t("computerViewer.desktopIsViewOnlyRefresh")); let receipt; if (action.type === "pointer") { if (!frame) throw new Error(t("computerViewer.desktopViewIsNotReady")); receipt = await actFromFrame(action, frame, operationId); } else { receipt = await act(action, operationId); } if (!isStaleComputerActionResult(receipt) && receipt.state !== "completed") { throw computerActionReceiptError(t, receipt); } return receipt; }, [act, actFromFrame, computer.controlError, inputDenied, t], ); const reconnect = () => { void refreshComputer(); if (appTarget) appInputPosture.refresh(); else frames.reconnect(); }; const copyFromRfb = useCallback( (event: ClipboardEvent) => { if (!rfbInputEnabled || !rfbStream || computer.session?.capabilities?.clipboard !== true) return; event.preventDefault(); event.stopPropagation(); void (async () => { const receipt = await perform({ type: "clipboard", operation: "copy" }, null); if (isStaleComputerActionResult(receipt)) return; const clipboard = await computer.readClipboard(); if (isStaleComputerActionResult(receipt) || isStaleComputerActionResult(clipboard)) return; if (clipboard.text && !(await copyTextToClipboard(clipboard.text))) { throw new Error(t("computerViewer.desktopTextCouldNotBe")); } })().catch((cause) => notifyError(cause, t("computerViewer.couldNotCopyFromThe"))); }, [computer, notifyError, perform, rfbInputEnabled, rfbStream, t], ); const pasteIntoRfb = useCallback( (text: string): boolean => { if (!rfbInputEnabled || !rfbStream || computer.session?.capabilities?.clipboard !== true) return false; // Keep paste on the canonical ComputerSession action path. RFB // ClientCutText synchronization varies by server and can acknowledge a // local paste without ever updating the remote graphical seat. These two // awaited actions run against the exact selected ComputerSession/target. // Read-after-write is the causal readiness barrier on X11: clipboard // ownership is asynchronous, so a successful write response alone does // not prove that the graphical seat can already serve the selection. void (async () => { const receipt = await perform({ type: "clipboard", operation: "write", text }, null); if (isStaleComputerActionResult(receipt)) return; const clipboard = await computer.readClipboard(); if (isStaleComputerActionResult(receipt) || isStaleComputerActionResult(clipboard)) return; assertExactComputerClipboard(t, clipboard, text); await perform({ type: "clipboard", operation: "paste" }, null); })().catch((cause) => notifyError(cause, t("computerViewer.couldNotPasteIntoThe"))); return true; }, [computer, notifyError, perform, rfbInputEnabled, rfbStream, t], ); const hideGenericCreate = suppressGenericCreate && liveRelevant.length === 0; const generationLossSelected = selectedRegistrySession !== null && isAttachedChromeGenerationLoss(selectedRegistrySession); const generationLossFrames = isAttachedChromeGenerationLossError(frames.error); if (!enabled) return null; if (registry.loading && liveSessions.length === 0) { return ( } text={t("computerViewer.findingWorkspaceDesktops")} {...(className ? { className } : {})} /> ); } if (liveSessions.length === 0) { if (hideGenericCreate) { return (

{t("computerViewer.chromeReconnectedOpenAFresh")}

{t("computerViewer.thisChromeProfileIsLive")}

); } if (renderEmpty) return renderEmpty(createComputer, creating); const error = createError ?? registry.error; return (
{error ? ( ) : ( )}

{error ? t("computerViewer.desktopDidnTOpen") : t("computerViewer.openingDesktop")}

{error?.message ?? t("computerViewer.preparingThisAgentSDesktop")}

{error ? ( ) : null}
); } return (
{ void computer .selectTarget(targetId) .catch((cause) => notifyError(cause, t("computerViewer.couldNotSwitchDesktopViews"))); }} onSelect={(computerSessionId) => selectComputerSession({ sessionId: computerSessionId, pinned: true }) } onFollow={() => { if (relevant[0]) selectComputerSession({ sessionId: relevant[0].id, pinned: false }); }} onCreate={hideGenericCreate ? undefined : createComputer} onRefresh={() => { if (appTarget) { appInputPosture.refresh(); } else if ( (connectionState === "error" || inputDenied) && !generationLossSelected && !generationLossFrames ) { frames.reconnect(); } void Promise.all([refreshRegistry(), refreshComputer()]); }} /> void computer .selectTarget(intervention.targetId) .catch((cause) => notifyError(cause, t("computerViewer.couldNotOpenTheRequested"))) } onResolve={resolveIntervention} /> {!selectedRegistrySession ? ( ) : !controllerReady ? ( ) : ( <>
{rfbStream ? (
{!rfbInputEnabled ? (
{controlUnavailable ? ( <> {t("computerViewer.desktopControlsUnavailable")}{" "} ) : ( t("computerViewer.viewOnlyDirectInputUnavailable") )}
) : null}
) : ( undefined : reconnect } onError={(cause) => notifyError(cause, t("computerViewer.desktopInputFailed"))} /> )} {showControls ? ( void perform(action, null).catch((cause) => notifyError(cause, t("computerViewer.desktopActionFailed")), ) } /> ) : null}
setShowControls((current) => !current)} /> )}
); } function ComputerUnselectedPanel(props: { peerCount: number; creating: boolean; error: Error | null; generationLoss: boolean; onCreate: () => void; }) { const t = useOpenGeniText(); return (
{props.error || props.generationLoss ? ( ) : ( )}

{props.generationLoss ? t("computerViewer.chromeReconnectedOpenAFresh") : props.error ? t("computerViewer.desktopDidnTOpen") : t("computerViewer.openingDesktop")}

{props.generationLoss ? t("computerViewer.thisChromeProfileIsLive") : (props.error?.message ?? (props.peerCount === 1 ? t("computerViewer.preparingThisAgentSDesktop2") : t("computerViewer.preparingThisAgentSDesktop3", { peerCount: props.peerCount })))}

{props.error && !props.generationLoss ? ( ) : null}
); } function ComputerToolbar(props: { sessions: ComputerSession[]; relevantSessionIds: Set; selectedSessionId: string | null; creating: boolean; refreshing: boolean; interventionCounts: Map; targets: ComputerTarget[]; selectedTargetId: string | null; targetsLoading: boolean; onSelectTarget: (id: string) => void; onSelect: (id: string) => void; onFollow: () => void; onCreate?: (() => void) | undefined; onRefresh: () => void; }) { const t = useOpenGeniText(); const detailsRef = useViewerMenuDismiss(); const selected = props.sessions.find((session) => session.id === props.selectedSessionId); const current = props.sessions.filter((session) => props.relevantSessionIds.has(session.id)); const others = props.sessions.filter((session) => !props.relevantSessionIds.has(session.id)); const closeMenu = (restoreFocus: boolean) => { detailsRef.current?.removeAttribute("open"); if (restoreFocus) detailsRef.current?.querySelector("summary")?.focus(); }; const choose = (id: string, restoreFocus: boolean) => { props.onSelect(id); closeMenu(restoreFocus); }; return (
{selected?.name === "Computer" ? t("computerViewer.desktop") : (selected?.name ?? t("computerViewer.desktop"))} {(props.interventionCounts.get(selected?.id ?? "") ?? 0) > 0 ? ( ) : null}
{current.length > 0 ? ( { props.onFollow(); closeMenu(event.detail === 0); }} > {t("computerViewer.followAgent")} ) : null} {props.onCreate ? ( { props.onCreate?.(); closeMenu(event.detail === 0); }} disabled={props.creating} > {t("computerViewer.newDesktop")} ) : null}
{selected ? `${platformLabel(t, selected)} · ${placementLabel(t, selected)}` : ""} {props.onCreate ? ( ) : null}
); } function ComputerSessionGroup(props: { label: string; sessions: ComputerSession[]; selectedId: string | null; interventionCounts: Map; onSelect: (id: string, restoreFocus: boolean) => void; }) { const t = useOpenGeniText(); if (props.sessions.length === 0) return null; return (

{props.label}

{props.sessions.map((session) => { const interventionCount = props.interventionCounts.get(session.id) ?? 0; return ( ); })}
); } function MenuButton(props: { children: ReactNode; onClick: (event: MouseEvent) => void; disabled?: boolean; }) { return ( ); } function ComputerTargetPicker(props: { targets: ComputerTarget[]; selectedTargetId: string | null; loading: boolean; onSelect: (id: string) => void; }) { const t = useOpenGeniText(); const screens = props.targets.filter((target) => target.kind === "screen"); const advancedTargets = props.targets.filter( (target) => target.kind === "app" || (target.kind === "window" && isRenderableComputerView(target)), ); const advancedRef = useViewerMenuDismiss(advancedTargets.length > 0); const selected = props.targets.find((target) => target.id === props.selectedTargetId); const choose = (id: string, restoreFocus = false) => { props.onSelect(id); if (advancedRef.current?.open) { advancedRef.current.removeAttribute("open"); if (restoreFocus) advancedRef.current.querySelector("summary")?.focus(); } }; return ( <> {screens.length > 1 ? ( ) : screens[0] && selected?.kind !== "screen" ? ( ) : null} {advancedTargets.length > 0 ? (
{t("computerViewer.advanced")}

{t("computerViewer.appsAndWindows")}

{advancedTargets.map((target) => ( ))}
) : null} {props.loading ? ( ) : null} ); } function isRenderableComputerView(target: ComputerTarget): boolean { if (target.kind === "screen") return true; if (target.kind !== "window" || !target.bounds) return false; // AX exposes applications and tiny utility/menu windows as valid semantic // targets. Keep those available to tools, but do not present them as visual // desktop tabs when they cannot form a useful live view. return target.bounds.width >= 160 && target.bounds.height >= 90; } function isComputerFrameTarget(target: ComputerTarget): boolean { return target.kind === "window" || target.kind === "screen"; } function ComputerLifecyclePanel(props: { session: ComputerSession; creating: boolean; onRefresh: () => Promise; onRetry: () => void; }) { const t = useOpenGeniText(); const generationLoss = isAttachedChromeGenerationLoss(props.session); const failed = ["failed", "lost", "repair_required"].includes(props.session.lifecycle); const retryCreatesSession = ["failed", "lost"].includes(props.session.lifecycle); return (
{failed ? ( ) : ( )}

{generationLoss ? t("computerViewer.chromeReconnectedOpenAFresh") : failed ? t("computerViewer.desktopNeedsAttention") : lifecycleLabel(t, props.session.lifecycle)}

{generationLoss ? t("computerViewer.thisChromeProfileIsLive") : (computerFailureMessage(t, props.session) ?? t("computerViewer.theDesktopIsBeingPrepared"))}

{failed && !generationLoss ? ( ) : null}
); } type ComputerQueuedAction = { operationId: string; started: Promise; completion: Promise; }; function ComputerViewport(props: { frame: ComputerFrame | null; observation: ComputerObservation | null; target: ComputerTarget | null; machineLocked: boolean; controlUnavailable: boolean; inputAllowed: boolean; connectionState: string; connectionError: Error | null; mutating: boolean; backgroundActions: boolean; backgroundInput: boolean; allowForegroundInputWithinOwnedSeat: boolean; clipboardEnabled: boolean; pointerInput: boolean; pointerClickContinuation: boolean; keyboardInput: boolean; onAction: ( action: ComputerAction, frame: ComputerFrame | null, operationId?: string, ) => Promise; onReadClipboard: () => Promise; onReconnect: () => void; onError: (cause: unknown) => void; }) { const t = useOpenGeniText(); const canvasRef = useRef(null); const inputRef = useRef(null); const composingRef = useRef(false); const pointerStartRef = useRef(null); const clickTimerRef = useRef | null>(null); const lastClickRef = useRef<{ at: number; x: number; y: number; frame: ComputerFrame; clientX: number; clientY: number; canvasRect: Pick; pending: boolean; delivery: ComputerQueuedAction | null; } | null>(null); const wheelRef = useRef<{ x: number; y: number; deltaX: number; deltaY: number; frame: ComputerFrame; timer: ReturnType | null; } | null>(null); const pendingTextRef = useRef<{ text: string; timer: ReturnType; } | null>(null); const actionRef = useRef(props.onAction); const targetRef = useRef(props.target); const readClipboardRef = useRef(props.onReadClipboard); const errorRef = useRef(props.onError); const actionTailRef = useRef>(Promise.resolve()); const actionQueueEpochRef = useRef(0); const queuedTextRef = useRef<{ action: Extract; epoch: number; delivery: ComputerQueuedAction; } | null>(null); const queuedFrameRef = useRef(null); const currentFrameRef = useRef(props.frame); const paintedFrameRef = useRef(null); const [paintedFrame, setPaintedFrame] = useState(null); const decodingFrameRef = useRef(false); const mountedRef = useRef(true); actionRef.current = props.onAction; targetRef.current = props.target; readClipboardRef.current = props.onReadClipboard; errorRef.current = props.onError; const streamFailed = props.connectionState === "error"; const rawInputEnabled = props.target?.kind !== "app" && !streamFailed && !props.controlUnavailable && !props.machineLocked && (props.backgroundInput || // CUA may activate within its allocated Linux desktop. This does not // grant foreground input to an attached physical desktop. props.allowForegroundInputWithinOwnedSeat || !props.backgroundActions || props.target?.kind === "screen" || props.target?.focused === true); const clearBufferedInput = useCallback(() => { if (clickTimerRef.current) clearTimeout(clickTimerRef.current); if (wheelRef.current?.timer) clearTimeout(wheelRef.current.timer); if (pendingTextRef.current?.timer) clearTimeout(pendingTextRef.current.timer); clickTimerRef.current = null; wheelRef.current = null; pendingTextRef.current = null; queuedTextRef.current = null; pointerStartRef.current = null; lastClickRef.current = null; composingRef.current = false; if (inputRef.current) inputRef.current.value = ""; }, []); const pointerInputEnabled = rawInputEnabled && props.pointerInput; const keyboardInputEnabled = rawInputEnabled && props.keyboardInput; useLayoutEffect(() => { const canvas = canvasRef.current; const viewport = canvas?.parentElement; if (!canvas || !viewport) return; const fit = () => fitComputerCanvas(canvas); fit(); if (typeof ResizeObserver === "undefined") { window.addEventListener("resize", fit); return () => window.removeEventListener("resize", fit); } // The canvas is absolute, so fitting it cannot resize the observed dock. const observer = new ResizeObserver(fit); observer.observe(viewport); return () => observer.disconnect(); }, []); const paintQueuedFrames = useCallback(() => { if (decodingFrameRef.current) return; decodingFrameRef.current = true; void (async () => { try { while (mountedRef.current) { const frame = queuedFrameRef.current; queuedFrameRef.current = null; if (!frame) break; let objectUrl: string | null = null; try { const blob = new Blob([frame.data.slice().buffer], { type: frame.mediaType }); if (typeof createImageBitmap === "function") { const bitmap = await createImageBitmap(blob); try { const canvas = canvasRef.current; if ( mountedRef.current && canvas && currentFrameRef.current && sameComputerTarget(frame, currentFrameRef.current) ) { paintCanvas(canvas, bitmap, frame.width, frame.height); paintedFrameRef.current = frame; setPaintedFrame(frame); } } finally { bitmap.close(); } continue; } objectUrl = URL.createObjectURL(blob); const image = await loadImage(objectUrl); const canvas = canvasRef.current; if ( mountedRef.current && canvas && currentFrameRef.current && sameComputerTarget(frame, currentFrameRef.current) ) { paintCanvas(canvas, image, frame.width, frame.height); paintedFrameRef.current = frame; setPaintedFrame(frame); } } catch (cause) { if ( mountedRef.current && currentFrameRef.current && sameComputerTarget(frame, currentFrameRef.current) ) errorRef.current(cause); } finally { if (objectUrl) URL.revokeObjectURL(objectUrl); } } } finally { decodingFrameRef.current = false; if (mountedRef.current && queuedFrameRef.current) paintQueuedFrames(); } })(); }, []); useLayoutEffect(() => { mountedRef.current = true; return () => { mountedRef.current = false; actionQueueEpochRef.current += 1; queuedFrameRef.current = null; clearBufferedInput(); }; }, [clearBufferedInput]); useLayoutEffect(() => { if (currentFrameRef.current && !props.frame) { actionQueueEpochRef.current += 1; clearBufferedInput(); } currentFrameRef.current = props.frame; if (!props.frame) { queuedFrameRef.current = null; paintedFrameRef.current = null; setPaintedFrame(null); return; } queuedFrameRef.current = props.frame; paintQueuedFrames(); }, [clearBufferedInput, paintQueuedFrames, props.frame]); useLayoutEffect(() => { if (!rawInputEnabled || !props.pointerInput || !props.keyboardInput) { actionQueueEpochRef.current += 1; clearBufferedInput(); } }, [clearBufferedInput, props.keyboardInput, props.pointerInput, rawInputEnabled]); const enqueue = useCallback( ( action: ComputerAction, frame: ComputerFrame | null, after?: (isCurrent: () => boolean) => Promise, continuation?: ComputerQueuedAction, ) => { const epoch = actionQueueEpochRef.current; const plainText = action.type === "keyboard" && action.action === "type"; const queuedText = queuedTextRef.current; if ( plainText && !frame && !after && !continuation && queuedText?.epoch === epoch && queuedText.delivery.completion === actionTailRef.current && queuedText.action.value.length + action.value.length <= 1_000_000 ) { // Only combine adjacent text that has not crossed the dispatch boundary. queuedText.action.value += action.value; return queuedText.delivery; } queuedTextRef.current = null; const dispatch = actionRef.current; const admittedTarget = targetRef.current; const isCurrent = () => mountedRef.current && epoch === actionQueueEpochRef.current; const operationId = crypto.randomUUID(); let markStarted!: (value: boolean) => void; const started = new Promise((resolve) => { markStarted = resolve; }); const previous = actionTailRef.current; // Only the real second click may start before its first HTTP receipt. // The controller queue and native causal proof still serialize delivery. const ready = continuation && previous === continuation.completion ? continuation.started : previous.then(() => true); const delivery = ready .then(async (firstStarted) => { if (queuedTextRef.current?.action === action) queuedTextRef.current = null; if (!firstStarted || !isCurrent()) { markStarted(false); return; } const pending = dispatch(action, frame, operationId); markStarted(true); const receipt = await pending; const isCurrentResult = () => isCurrent() && !isStaleComputerActionResult(receipt); if (!isCurrentResult()) return; const observedTarget = receipt.observation?.target; if ( !admittedTarget || receipt.computerSessionId !== admittedTarget.computerSessionId || receipt.controllerGeneration !== admittedTarget.controllerGeneration || receipt.targetId !== admittedTarget.id || (observedTarget && (observedTarget.computerSessionId !== admittedTarget.computerSessionId || observedTarget.controllerGeneration !== admittedTarget.controllerGeneration || observedTarget.id !== admittedTarget.id || observedTarget.targetGeneration !== admittedTarget.targetGeneration)) ) { // Receipt projection can replace the target before React remounts us. actionQueueEpochRef.current += 1; clearBufferedInput(); return; } await after?.(isCurrentResult); }) .catch((cause) => { markStarted(false); if (!isCurrent() || isStaleComputerActionResult(cause)) return; actionQueueEpochRef.current += 1; clearBufferedInput(); errorRef.current(cause); }); const completion = continuation ? Promise.all([previous, delivery]).then(() => undefined) : delivery; actionTailRef.current = completion; const queued = { operationId, started, completion }; if (plainText && !frame && !after && !continuation) { queuedTextRef.current = { action, epoch, delivery: queued }; } return queued; }, [clearBufferedInput], ); const point = useCallback( (frame: ComputerFrame, clientX: number, clientY: number) => computerPoint(canvasRef.current, frame, clientX, clientY), [], ); const flushPendingText = useCallback(() => { const pending = pendingTextRef.current; if (!pending) return; clearTimeout(pending.timer); pendingTextRef.current = null; enqueue({ type: "keyboard", action: "type", value: pending.text }, null); }, [enqueue]); const flushPendingClick = useCallback(() => { const pending = lastClickRef.current; if (!pending) return; if (clickTimerRef.current) clearTimeout(clickTimerRef.current); clickTimerRef.current = null; lastClickRef.current = null; if (!pending.pending) return; enqueue( { type: "pointer", frameId: pending.frame.frameId, action: "click", x: pending.x, y: pending.y, }, pending.frame, ); }, [enqueue]); const flushPendingWheel = useCallback(() => { const batch = wheelRef.current; if (!batch) return; if (batch.timer) clearTimeout(batch.timer); wheelRef.current = null; enqueue( { type: "pointer", frameId: batch.frame.frameId, action: "scroll", x: batch.x, y: batch.y, deltaX: batch.deltaX, deltaY: batch.deltaY, }, batch.frame, ); }, [enqueue]); const pointerDown = (event: PointerEvent) => { const frame = paintedFrameRef.current; if ( !frame || (!props.pointerClickContinuation && props.mutating) || !rawInputEnabled || event.button !== 0 ) return; // Preserve the keyboard sink's focus through the canvas pointer default action. event.preventDefault(); if (keyboardInputEnabled) inputRef.current?.focus({ preventScroll: true }); if (!pointerInputEnabled) return; flushPendingWheel(); flushPendingText(); pointerStartRef.current = { x: event.clientX, y: event.clientY, pointerId: event.pointerId, frame, }; event.currentTarget.setPointerCapture?.(event.pointerId); }; const pointerUp = (event: PointerEvent) => { if (!pointerInputEnabled) return; const start = pointerStartRef.current; pointerStartRef.current = null; if (!start || start.pointerId !== event.pointerId) return; const canvasRect = event.currentTarget.getBoundingClientRect(); const from = point(start.frame, start.x, start.y); const to = point(start.frame, event.clientX, event.clientY); if (!from || !to) return; if (Math.hypot(event.clientX - start.x, event.clientY - start.y) > 5) { flushPendingClick(); enqueue( { type: "pointer", frameId: start.frame.frameId, action: "drag", x: from.x, y: from.y, endX: to.x, endY: to.y, }, start.frame, ); return; } const now = Date.now(); const previous = lastClickRef.current; if ( previous && now - previous.at < 280 && previous.pending === !props.pointerClickContinuation && (props.pointerClickContinuation ? Math.hypot(previous.clientX - event.clientX, previous.clientY - event.clientY) < 6 && previous.canvasRect.left === canvasRect.left && previous.canvasRect.top === canvasRect.top && previous.canvasRect.width === canvasRect.width && previous.canvasRect.height === canvasRect.height && sameComputerTarget(previous.frame, start.frame) && previous.frame.width === start.frame.width && previous.frame.height === start.frame.height : Math.hypot(previous.x - to.x, previous.y - to.y) < 6 && sameFrameFence(previous.frame, start.frame)) ) { if (clickTimerRef.current) clearTimeout(clickTimerRef.current); clickTimerRef.current = null; lastClickRef.current = null; enqueue( { type: "pointer", frameId: start.frame.frameId, action: props.pointerClickContinuation ? "click" : "double_click", ...(props.pointerClickContinuation ? { clickCount: 2 as const } : {}), ...(previous.delivery ? { continuationOfOperationId: previous.delivery.operationId } : {}), x: to.x, y: to.y, }, start.frame, undefined, previous.delivery ?? undefined, ); return; } flushPendingClick(); lastClickRef.current = { at: now, x: to.x, y: to.y, frame: start.frame, clientX: event.clientX, clientY: event.clientY, canvasRect, pending: !props.pointerClickContinuation, delivery: null, }; if (props.pointerClickContinuation) { const delivery = enqueue( { type: "pointer", frameId: start.frame.frameId, action: "click", clickCount: 1, x: to.x, y: to.y, }, start.frame, ); if (lastClickRef.current) lastClickRef.current.delivery = delivery; return; } clickTimerRef.current = setTimeout(() => { clickTimerRef.current = null; lastClickRef.current = null; enqueue( { type: "pointer", frameId: start.frame.frameId, action: "click", x: to.x, y: to.y, }, start.frame, ); }, 280); }; const contextMenu = (event: MouseEvent) => { event.preventDefault(); if (!pointerInputEnabled) return; const frame = paintedFrameRef.current; if (!frame) return; flushPendingWheel(); flushPendingText(); flushPendingClick(); const at = point(frame, event.clientX, event.clientY); if (at) { enqueue( { type: "pointer", frameId: frame.frameId, action: "click", x: at.x, y: at.y, button: "right", }, frame, ); } }; const wheel = (event: WheelEvent) => { if (!pointerInputEnabled) return; const frame = paintedFrameRef.current; if (!frame) return; const at = point(frame, event.clientX, event.clientY); if (!at) return; flushPendingText(); flushPendingClick(); event.preventDefault(); let pending = wheelRef.current; if ( pending && (!sameComputerTarget(pending.frame, frame) || Math.hypot(pending.x - at.x, pending.y - at.y) > 6) ) { flushPendingWheel(); pending = null; } wheelRef.current = { x: at.x, y: at.y, deltaX: (pending?.deltaX ?? 0) + event.deltaX, deltaY: (pending?.deltaY ?? 0) + event.deltaY, frame, timer: pending?.timer ?? setTimeout(flushPendingWheel, 45), }; }; const keyDown = (event: KeyboardEvent) => { if (!keyboardInputEnabled) return; if ( composingRef.current || event.nativeEvent.isComposing || event.nativeEvent.keyCode === 229 ) { return; } flushPendingWheel(); const command = event.metaKey || event.ctrlKey; if ( props.clipboardEnabled && command && !event.altKey && ["c", "v"].includes(event.key.toLowerCase()) ) { flushPendingText(); return; } const key = computerKey(event); if (!key) return; event.preventDefault(); flushPendingText(); flushPendingClick(); enqueue({ type: "keyboard", action: "press", value: key }, null); }; const copy = (event: ClipboardEvent) => { if (!props.clipboardEnabled || !keyboardInputEnabled) return; event.preventDefault(); flushPendingWheel(); flushPendingText(); flushPendingClick(); const readClipboard = readClipboardRef.current; enqueue({ type: "clipboard", operation: "copy" }, null, async (isCurrent) => { const clipboard = await readClipboard(); if (!isCurrent()) return; if (!clipboard.text) return; if (!(await copyTextToClipboard(clipboard.text))) { throw new Error(t("computerViewer.desktopTextCouldNotBe")); } }); }; const paste = (event: ClipboardEvent) => { if (!props.clipboardEnabled || !keyboardInputEnabled) return; event.preventDefault(); flushPendingWheel(); flushPendingText(); flushPendingClick(); const text = event.clipboardData.getData("text/plain"); const readClipboard = readClipboardRef.current; const dispatch = actionRef.current; enqueue({ type: "clipboard", operation: "write", text }, null, async (isCurrent) => { const clipboard = await readClipboard(); if (!isCurrent()) return; assertExactComputerClipboard(t, clipboard, text); await dispatch({ type: "clipboard", operation: "paste" }, null); }); }; const input = (value: string, nativeComposing = false) => { if (composingRef.current || nativeComposing) return; if (!value || !keyboardInputEnabled) return; flushPendingWheel(); flushPendingClick(); const pending = pendingTextRef.current; if (pending) { clearTimeout(pending.timer); pending.text += value; pending.timer = setTimeout(flushPendingText, 16); } else { pendingTextRef.current = { text: value, timer: setTimeout(flushPendingText, 16), }; } if (inputRef.current) inputRef.current.value = ""; }; const compositionEnd = (event: CompositionEvent) => { composingRef.current = false; input(event.currentTarget.value || event.data); }; const showCanvas = props.frame !== null && paintedFrame !== null && sameComputerTarget(props.frame, paintedFrame) && !props.machineLocked && !streamFailed; return (
{ pointerStartRef.current = null; }} onContextMenu={contextMenu} onWheel={wheel} aria-label={t( props.target?.kind === "app" ? "computerViewer.interactiveAppView" : props.target?.kind === "window" ? "computerViewer.interactiveWindowView" : props.target?.kind === "screen" ? "computerViewer.interactiveScreenView" : "computerViewer.interactiveDesktopView", )} />