import type { MobileProjectSectionCatalog } from './mobile-project-sections-contract' import type { AgentStatusOrchestrationContext } from './agent-status-types' import type { RemoteServerUpdateSupport } from './remote-server-update' import type { RemoteRuntimeSharedConnectionDiagnostics } from './remote-runtime-shared-control-types' import type { RuntimeHostConnectionState } from './runtime-host-connection-state' import type { RuntimeCapability } from './protocol-version' import type { RuntimeBrowserUnavailableReason, RuntimeDegradation } from './runtime-capability-degradation' import type { TabGroupLayoutNode } from './tab-types' import type { TerminalPaneLayoutNode } from './terminal-tab-types' import type { RuntimeMobileSessionClientTab, RuntimeMobileSessionSnapshotTab, RuntimeMobileSessionTerminalClientTab } from './runtime-mobile-session-tab-contracts' import type { CliStatusCaller } from './orchestration-caller-status' import type { RuntimeHostDescriptor } from './runtime-host-descriptor' import type { TuiAgent } from './tui-agent' export type * from './runtime-mobile-session-tab-contracts' export type RuntimeGraphStatus = 'ready' | 'reloading' | 'unavailable' export type RuntimeDesktopWindowStatus = 'available' | 'openable' | 'initializing' | 'blocked' export const HEADLESS_RUNTIME_WINDOW_ID = 0 export type DeviceScope = 'mobile' | 'runtime' export type RuntimeTerminalDriverState = | { kind: 'idle' } | { kind: 'desktop' } | { kind: 'mobile'; clientId: string } export type RuntimeBrowserDriverState = RuntimeTerminalDriverState export const BROWSER_UNAVAILABLE_ERROR_CODE = 'browser_unavailable' as const // Why: one sentence per cause, each naming the thing the operator can change. The host // renders these so an older client still shows an accurate reason it cannot decode. const BROWSER_UNAVAILABLE_MESSAGES: Record = { unconfigured: 'Browser automation has no backend on this host. Install the Orca desktop app, or set ORCA_BROWSER_EXECUTABLE to a Chromium executable.', driver_missing: 'ORCA_BROWSER_EXECUTABLE is set, but the bundled agent-browser driver is missing or not executable on this host, so Chromium cannot be driven.', executable_not_found: 'ORCA_BROWSER_EXECUTABLE points at a path that does not exist.', executable_not_executable: 'ORCA_BROWSER_EXECUTABLE points at a file that is not executable by this host.', electron_start_failed: 'The installed Electron browser provider failed to start.', chromium_start_failed: 'The Chromium browser provider named by ORCA_BROWSER_EXECUTABLE failed to start.', provider_unhealthy: 'The browser provider started but is no longer answering health checks.', desktop_window_unavailable: 'Browser automation on this host needs a desktop window, and none is available.', unknown: 'Browser automation is unavailable on this host, and the cause could not be determined.' } export function browserUnavailableMessage( reason: RuntimeBrowserUnavailableReason, detail?: string ): string { const base = BROWSER_UNAVAILABLE_MESSAGES[reason] return detail ? `${base} (${detail})` : base } export type RuntimeStatus = { runtimeId: string /** Authenticated requester identity. Missing for in-process callers and older hosts. */ pairedDeviceId?: string rendererGraphEpoch: number graphStatus: RuntimeGraphStatus authoritativeWindowId: number | null desktopWindowStatus?: RuntimeDesktopWindowStatus liveTabCount: number liveLeafCount: number runtimeProtocolVersion?: number minCompatibleRuntimeClientVersion?: number capabilities?: RuntimeCapability[] /** Optional policy for clients that negotiated worktree.create-idempotency.v1. */ worktreeCreateIdempotency?: { dedupeTtlMs: number } /** True only when this Windows host can read process creation times. TEMPORARY: read only by * older clients, which keep re-probing WSL until it is true; remove after their window. */ windowsProcessStartTimeAvailable?: boolean /** * Optional for mixed-version peers. Absence means the host predates structured * degradation reporting, not that the host proved every optional feature available. */ degradations?: RuntimeDegradation[] appVersion?: string remoteUpdateSupport?: RemoteServerUpdateSupport remoteControl?: RemoteRuntimeSharedConnectionDiagnostics | null hostPlatform?: NodeJS.Platform /** Optional display name reported by the answering runtime. */ machineName?: string /** Absent from older hosts and from hosts whose profile could not persist an installation id. */ hostDescriptor?: RuntimeHostDescriptor terminalWindowsShell?: string | null deviceScope?: DeviceScope floatingWorkspaceEnabled?: boolean // COMPAT(runtimeStatusMobileAliases): added 2026-05-15 for older mobile builds. protocolVersion?: number minCompatibleMobileVersion?: number } export type CliRuntimeState = | 'not_running' | 'starting' | 'ready' | 'graph_not_ready' | 'stale_bootstrap' export type CliStatusResult = { target?: { kind: 'local' } | { kind: 'environment'; environment: string } app: { running: boolean pid: number | null desktopWindowStatus?: RuntimeDesktopWindowStatus } runtime: { state: CliRuntimeState reachable: boolean /** Canonical runtime transport verdict, when the caller has runtime evidence. */ connectionState?: RuntimeHostConnectionState runtimeId: string | null appVersion?: string remoteUpdateSupport?: RemoteServerUpdateSupport capabilities?: RuntimeCapability[] degradations?: RuntimeDegradation[] } graph: { state: RuntimeGraphStatus | 'not_running' | 'starting' } /** This process's Orca session ID when it runs as an Orca session; see `CliStatusCaller`. */ caller?: CliStatusCaller } export type RuntimeSyncedTab = { tabId: string worktreeId: string title: string | null activeLeafId: string | null layout: TerminalPaneLayoutNode | null } export type RuntimeSyncedLeaf = { tabId: string worktreeId: string leafId: string paneRuntimeId: number ptyId: string | null paneTitle?: string | null title?: string | null /** True when this leaf is retained by a parked PTY watcher, not mounted in the renderer. */ parked?: boolean } /** Layout-owner builds: a pane the window shows of a workspace main owns. */ export type RuntimeMountedLeaf = { tabId: string leafId: string paneRuntimeId: number /** Retained by a parked PTY watcher, not mounted in the renderer. */ parked?: boolean /** The pane's title as the window resolves it, a reset of a stuck agent title included. */ paneTitle?: string | null } export type RuntimeSyncWindowGraph = { tabs: RuntimeSyncedTab[] leaves: RuntimeSyncedLeaf[] /** Layout-owner builds: what the window shows of main's panes; `tabs` and `leaves` then hold * only workspaces main does not own. Window-to-main only. */ mountedLeaves?: RuntimeMountedLeaf[] mobileSessionTabs?: RuntimeMobileSessionTabsSnapshot[] unchangedMobileSessionWorktrees?: string[] /** Terminal grid that fills each shown tab group, by group id, so main starts a new tab's * terminal at its group's size. Window-to-main only: never copied into session tabs. */ terminalGroupGridSizes?: Record /** The grid a workspace with a single tab group gets, for one the window is not showing. */ singleGroupWorkspaceGridSize?: { cols: number; rows: number } /** Layout-owner builds: the tab group each workspace shows, sent only when it changes. Main merges * each entry and keeps the last; a send without the field changes nothing. null: none shown. */ activeGroupByWorkspace?: Record /** The sidebar's Project grouping inputs for paired phones; sent only when they changed. */ projectSectionCatalog?: MobileProjectSectionCatalog } export type RuntimeRendererSyncWindowGraph = RuntimeSyncWindowGraph & { rendererGeneration: string } export type RuntimeNativeChatLaunchDraftResolution = { tabId: string text: string createdAt: number } export type RuntimeSyncWindowGraphResult = RuntimeStatus & { agentOrchestrationByPaneKey?: Record nativeChatLaunchDraftResolutions?: RuntimeNativeChatLaunchDraftResolution[] mobileSessionResyncWorktrees?: string[] } export type RuntimeMobileSessionTabGroup = { id: string activeTabId: string | null tabOrder: string[] recentTabIds?: string[] } type RuntimeMobileSessionTabMoveBase = { tabId: string targetGroupId: string } export type RuntimeMobileSessionTabMove = | (RuntimeMobileSessionTabMoveBase & { kind: 'reorder'; tabOrder: string[] }) | (RuntimeMobileSessionTabMoveBase & { kind: 'move-to-group'; index?: number }) | (RuntimeMobileSessionTabMoveBase & { kind: 'split' splitDirection: 'left' | 'right' | 'up' | 'down' }) export type RuntimeMobileSessionTabMoveResult = { moved: true } export type RuntimeMobileSessionTabCloseResult = { closed: true refused?: true refusalReason?: | 'missing-intent' | 'stale-publication' | 'stale-terminal' | 'live-host-pty' | 'unknown-liveness' | 'retirement-owner' snapshotRepublished?: true } export type RuntimeSessionTabCloseReason = 'user' | 'pty-exit' | 'cleanup' /** * The publication epoch a runtime answers with for a worktree it has published nothing for yet — * the state every worktree is in for a moment after the host process restarts. * * Bare or with a paired client's navigation suffix, it marks a synthesized placeholder, not a host answer: the * runtime is saying "ask me later", not "those tabs are gone". Clients must not read absence from * such a frame as evidence a tab was closed. */ export const UNPUBLISHED_WORKTREE_PUBLICATION_EPOCH = 'none' /** Suffix a host appends to the epoch when projecting a snapshot for one paired client's navigation. */ export const CLIENT_NAVIGATION_PUBLICATION_EPOCH_SUFFIX = ':client-navigation' export type RuntimeMobileSessionTabsSnapshot = { worktree: string /** Immutable catalog identity used to fence snapshots across path reuse. */ worktreeInstanceId?: string publicationEpoch: string snapshotVersion: number activeGroupId: string | null activeTabId: string | null activeTabType: 'terminal' | 'markdown' | 'file' | 'browser' | 'agent-session' | null tabGroups?: RuntimeMobileSessionTabGroup[] tabGroupLayout?: TabGroupLayoutNode | null retiredTerminalSurfaces?: RuntimeMobileSessionRetiredTerminalSurface[] tabs: RuntimeMobileSessionSnapshotTab[] } export type RuntimeMobileSessionRetiredTerminalSurface = { parentTabId: string leafId: string ptyId: string terminal: string incarnationId?: string } export type RuntimeMobileSessionTabsResult = { worktree: string /** Query-only truth about an executing launch that owns this workspace's first surface. */ initialSurfaceLaunch?: { launchId: string; agent: TuiAgent } publicationEpoch: string snapshotVersion: number navigationIntent?: 'follow' activeGroupId: string | null activeTabId: string | null activeTabType: 'terminal' | 'markdown' | 'file' | 'browser' | 'agent-session' | null tabGroups?: RuntimeMobileSessionTabGroup[] tabGroupLayout?: TabGroupLayoutNode | null retiredTerminalSurfaces?: RuntimeMobileSessionRetiredTerminalSurface[] tabs: RuntimeMobileSessionClientTab[] /** * Set while a freshly started runtime has not yet taken back the client-hosted pages its paired * hosts are still holding. Such a snapshot is authoritative about terminals, which it rehydrated * from disk, but silently empty of browser rows it has simply not heard about yet — so a client * must not read the absence of its own client-hosted rows here as "the host closed them". * * Always bounded: the runtime clears it once a host attaches, and drops it on a deadline so a * host that never returns cannot hold rows open forever. */ clientHostedPagesUnreconciled?: true /** * Set while this runtime has no structured-chat host that can say which chats exist because the * chat journal will not open. The snapshot is still authoritative about everything else, but its * missing `agent-session` rows mean "cannot tell", not "closed". * * Cleared by the first tab restore a host answers. Not bounded by a deadline: the chats are * durable on disk, so holding their tabs strands nothing. */ agentSessionsUnverifiable?: true } export type RuntimeMobileSessionCreateTerminalResult = { tab: RuntimeMobileSessionTerminalClientTab publicationEpoch: string snapshotVersion: number warning?: 'desktop-tab-not-shown' /** The saved folder was gone, so the terminal started at the workspace root. */ startupCwdFallback?: 'workspace-root' } /** Activation's reply: the fallback rides this one answer and is never stored on the snapshot. */ export type RuntimeMobileSessionTabActivationResult = RuntimeMobileSessionTabsResult & Pick export type RuntimeMobileSessionTabsRemovedResult = RuntimeMobileSessionTabsResult & { removed: true activeGroupId: null activeTabId: null activeTabType: null tabs: [] }