mirror of
https://github.com/stablyai/orca.git
synced 2026-10-31 00:57:45 +00:00
* fix(terminal): start CLI-created workspace terminals in main so a slow window cannot duplicate them A focused or interactive-agent `orca terminal create` asked the window to make the terminal and reported failure after 10 s, while the window still made it later; a retry then made a second one. Main now starts every terminal that names a workspace and shows it in the window best-effort, as it already did for every other create. * fix(terminal): start a main-created terminal at its worktree's measured pane size An unfocused CLI agent opens as an inactive tab that is not resized until the user opens it, so starting it at 120x40 left its output wrapped to 120 columns. Start it at the size of the pane active in the worktree's active group, where the new tab opens; fall back to 120x40 only when that worktree has no measured pane. * fix(cli): keep sending rendererBacked for hosts before v1.4.211 Hosts without #22384 leave a background-started agent's startup queries unanswered, so a new CLI talking to an old, still-running host must keep routing interactive agents to the window. Newer hosts ignore the flag. * fix(terminal): start host-created terminals at the measured size of their tab group The window publishes, on its graph sync to main only, the grid an unsplit terminal would get in each shown tab group, computed by one pure function that mirrors FitAddon.proposeDimensions and is pinned against it; a ResizeObserver on each group body republishes when that grid changes. Main starts a new terminal at the size of the group it opens in (the named group, else the active one) and falls back to 120x40 only when no window measured it. This replaces the active-pane lookup, which mis-sized split groups and could read a phone's fitted size. * fix(terminal): measure group sizes from an untitled default-font pane and use the live active group A pane with a title or banner pushes its xterm host down, which skewed every group's row count; skip it like a zoomed pane. Size from the window's latest published active group, which is where its reveal places the tab, before the saved session's. * fix(terminal): size a CLI agent in a workspace the window has not shown Co-Authored-By: Claude <noreply@anthropic.com> * fix(terminal): learn the single-group inset only from bodies shown in the content area Co-Authored-By: Claude <noreply@anthropic.com> * chore: rerun PR checks against main with #28297 Co-Authored-By: Claude <noreply@anthropic.com> * chore: rerun PR checks against main with #28301 Co-Authored-By: Claude <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com>
306 lines
11 KiB
TypeScript
306 lines
11 KiB
TypeScript
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'
|
|
|
|
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 }
|
|
}
|
|
|
|
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
|
|
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
|
|
}
|
|
|
|
export type RuntimeMobileSessionTabsRemovedResult = RuntimeMobileSessionTabsResult & {
|
|
removed: true
|
|
activeGroupId: null
|
|
activeTabId: null
|
|
activeTabType: null
|
|
tabs: []
|
|
}
|