Files
orca/src/shared/runtime-session-contracts.ts
T
Jinwoo HongandE2E Test 03e9a03e9e feat(layout-owner): main opens CLI and phone files itself, in the window's live group (dark) (#31123)
Behind ORCA_LAYOUT_OWNER_IS_MAIN, a CLI or phone file open is applied by main as an
openEditorTab layout command, so a host with no window can open files. The window
reports each workspace's active group as an optional view fact on syncWindowGraph,
and shows main's tab once the frame that draws it arrives. Editor language
detection moves to shared so main can name a file's language.

Co-authored-by: E2E Test <e2e@test.local>
2026-10-20 08:48:53 -04:00

316 lines
12 KiB
TypeScript

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<RuntimeBrowserUnavailableReason, string> = {
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
}
export type RuntimeSyncWindowGraph = {
tabs: RuntimeSyncedTab[]
leaves: RuntimeSyncedLeaf[]
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<string, { cols: number; rows: number }>
/** 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<string, string | null>
/** 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<string, AgentStatusOrchestrationContext>
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'
}
export type RuntimeMobileSessionTabsRemovedResult = RuntimeMobileSessionTabsResult & {
removed: true
activeGroupId: null
activeTabId: null
activeTabType: null
tabs: []
}