mirror of
https://github.com/stablyai/orca.git
synced 2026-11-02 10:08:13 +00:00
Main runs one pass at startup and each window load, with or without a window (desktop, orca serve, orcad): every live local terminal no pane names is bound to the pane its record names while main still holds it free, else shown in a new tab bound to it. The window no longer asks main to adopt terminals; it only reads that main holds them. The window's layout.apply loses its adopt field. Main also derives activeWorktreeIdsOnShutdown, remoteSessionIdsByTabId and activeConnectionIdsAtShutdown from its own layout, terminal records and SSH connections, replacing the window's copies in every save and the quit checkpoint. Co-authored-by: E2E Test <e2e@test.local>
214 lines
7.8 KiB
TypeScript
214 lines
7.8 KiB
TypeScript
import type { AgentLaunchRuntime } from '../../shared/agent-launch-runtime'
|
|
import type {
|
|
AutomaticWorkResumeOutcome,
|
|
AutomaticWorkStatus,
|
|
SpawnIntent
|
|
} from '../../shared/host-spawn-admission'
|
|
import type {
|
|
AgentSessionClaimedSpawnResult,
|
|
AgentSessionExecutionClaim,
|
|
AgentSessionSurfaceBinding
|
|
} from '../../shared/agent-session-host-authority'
|
|
import type { AgentProviderSessionMetadata } from '../../shared/agent-session-resume'
|
|
import type { TuiAgent } from '../../shared/tui-agent'
|
|
import type { WorktreeStartupLaunch } from '../../shared/worktree/launch-types'
|
|
import type { PtyIncarnationId } from '../../shared/pty-incarnation'
|
|
import type { PtyBindingSourceExpectation } from '../persistence'
|
|
import type { TerminalPanePlacement } from '../../shared/terminal-pane-placement'
|
|
import type { ExecutionHostId } from '../../shared/execution-host'
|
|
import type {
|
|
PtyProviderBufferSnapshot,
|
|
PtyProcessInfo,
|
|
PtySpawnResult
|
|
} from '../providers/pty-provider-contract'
|
|
import type { PtyProcessInspection } from '../providers/pty-process-inspection'
|
|
import type { WriteSettlement } from '../../shared/pty-write-settlement'
|
|
import type { TerminalInputKind } from '../../shared/terminal-input-kind'
|
|
|
|
export type PtyInventoryRefreshOptions = {
|
|
includeForegroundProcessEvidence?: boolean
|
|
refreshForegroundAgents?: boolean
|
|
}
|
|
|
|
export type RuntimePtyController = {
|
|
claimStablePaneCreate?(args: {
|
|
worktreeId: string
|
|
connectionId: string | null
|
|
tabId: string
|
|
leafId: string
|
|
}): () => void
|
|
adoptStablePane?(opts: {
|
|
cols: number
|
|
rows: number
|
|
cwd?: string
|
|
connectionId: string | null
|
|
worktreeId: string
|
|
preAllocatedHandle: string
|
|
tabId: string
|
|
leafId: string
|
|
}): Promise<{
|
|
result: PtySpawnResult
|
|
owner: {
|
|
handle?: string
|
|
tabId: string
|
|
leafId: string
|
|
ptyId: string
|
|
incarnationId?: string
|
|
}
|
|
materialized?: true
|
|
} | null>
|
|
spawn?(opts: {
|
|
cols: number
|
|
rows: number
|
|
cwd?: string
|
|
command?: string
|
|
launchAgent?: TuiAgent
|
|
commandDelivery?: 'renderer' | 'provider'
|
|
startupCommandDelivery?: WorktreeStartupLaunch['startupCommandDelivery']
|
|
env?: Record<string, string>
|
|
envToDelete?: string[]
|
|
resumeProviderSession?: AgentProviderSessionMetadata
|
|
telemetry?: WorktreeStartupLaunch['telemetry']
|
|
connectionId?: string | null
|
|
worktreeId?: string
|
|
preAllocatedHandle?: string
|
|
tabId?: string
|
|
leafId?: string
|
|
sessionId?: string
|
|
/** Windows shell to spawn AS this PTY, instead of the host default. */
|
|
shellOverride?: string
|
|
/** The agent launch's runtime fact; when present the PTY shell comes only from it. */
|
|
launchRuntime?: AgentLaunchRuntime
|
|
isNewSession?: boolean
|
|
/** No renderer view exists at spawn; main owns delivery and query replies until one mounts. */
|
|
initiallyHidden?: boolean
|
|
persistHostSessionBinding?: boolean
|
|
expectedSourceBinding?: PtyBindingSourceExpectation
|
|
placement?: TerminalPanePlacement
|
|
terminalKittyKeyboardProtocol?: boolean
|
|
terminalColorQueryReplies?: { foreground?: string; background?: string }
|
|
agentSessionEnsure?: {
|
|
claim: AgentSessionExecutionClaim
|
|
surface: AgentSessionSurfaceBinding
|
|
}
|
|
agentSessionCreateOperationId?: string
|
|
spawnIntent?: SpawnIntent
|
|
signal?: AbortSignal
|
|
refuseSleptWorktree?: boolean
|
|
onPtySpawnCommitted?: () => void
|
|
adoptedStablePane?: {
|
|
result: PtySpawnResult
|
|
owner: {
|
|
handle?: string
|
|
tabId: string
|
|
leafId: string
|
|
ptyId: string
|
|
incarnationId?: string
|
|
}
|
|
materialized?: true
|
|
}
|
|
}): Promise<{
|
|
id: string
|
|
pid?: number | null
|
|
incarnationId?: PtyIncarnationId
|
|
wslDistro?: string
|
|
stablePaneOwner?: { handle: string; tabId: string; leafId: string }
|
|
agentSessionEnsure?: AgentSessionClaimedSpawnResult
|
|
}>
|
|
/** The local terminal owner's automatic-restore state; null when it has no fork gate. */
|
|
getAutomaticWorkStatus?(): Promise<AutomaticWorkStatus | null>
|
|
resumeAutomaticWork?(): Promise<AutomaticWorkResumeOutcome>
|
|
write(ptyId: string, data: string, inputKind: TerminalInputKind): boolean
|
|
/** Three-valued settlement; local providers settle synchronously. */
|
|
writeWithSettlement?(
|
|
ptyId: string,
|
|
data: string,
|
|
inputKind: TerminalInputKind
|
|
): WriteSettlement | Promise<WriteSettlement>
|
|
/** Attach-only adoption of a live local daemon session so its output streams
|
|
* to main without a renderer pane; never creates, resizes, or focuses.
|
|
* False on doubt (absent session, SSH-scoped id, non-daemon provider). */
|
|
attach?(ptyId: string): Promise<boolean>
|
|
kill(ptyId: string): boolean
|
|
/** `incarnationId` is the rejected spawn's; a same-id respawn since then is left alone. */
|
|
retireRejectedPty?(ptyId: string, stopConfirmed: boolean, incarnationId: string | undefined): void
|
|
stopAndWait?(
|
|
ptyId: string,
|
|
opts?: { keepHistory?: boolean; deadlineMs?: number }
|
|
): Promise<boolean>
|
|
getCwd?(ptyId: string): Promise<string | null>
|
|
getForegroundProcess(ptyId: string): Promise<string | null>
|
|
inspectProcess?(
|
|
ptyId: string,
|
|
options?: { expectedIncarnationId?: PtyIncarnationId; scanChildProcesses?: boolean }
|
|
): Promise<PtyProcessInspection>
|
|
confirmForegroundProcess?(ptyId: string): Promise<string | null>
|
|
confirmShellForeground?(ptyId: string): Promise<boolean>
|
|
hasChildProcesses?(ptyId: string): Promise<boolean>
|
|
clearBuffer?(ptyId: string): Promise<void>
|
|
resetInputModes?(ptyId: string): Promise<void>
|
|
resize?(ptyId: string, cols: number, rows: number): boolean
|
|
// Why: exact-id mobile polls should not enumerate every local and SSH PTY.
|
|
hasPty?(ptyId: string): boolean | null
|
|
/** Omitting the host lists every registered host. */
|
|
listProcesses?(
|
|
hostId?: ExecutionHostId,
|
|
opts?: { deadlineMs?: number; includeForegroundProcessEvidence?: boolean }
|
|
): Promise<PtyProcessInfo[]>
|
|
listProcessesWithHostScope?(opts?: {
|
|
deadlineMs?: number
|
|
includeForegroundProcessEvidence?: boolean
|
|
}): Promise<{
|
|
processes: PtyProcessInfo[]
|
|
hostIds: ExecutionHostId[]
|
|
}>
|
|
supportsForegroundProcessEvidence?(hostId?: ExecutionHostId): Promise<boolean>
|
|
serializeBuffer?(
|
|
ptyId: string,
|
|
opts?: { scrollbackRows?: number }
|
|
): Promise<{
|
|
data: string
|
|
cols: number
|
|
rows: number
|
|
seq?: number
|
|
lastTitle?: string
|
|
kittyKeyboardFlags?: number
|
|
} | null>
|
|
/** Authoritative provider-owned snapshot for restored PTYs with no mounted renderer. */
|
|
serializeProviderBuffer?(
|
|
ptyId: string,
|
|
opts?: { scrollbackRows?: number }
|
|
): Promise<PtyProviderBufferSnapshot | null>
|
|
// Why: synchronous probe used by maybeHydrateHeadlessFromRenderer to skip
|
|
// hydration when no renderer is authoritative for this PTY. See
|
|
// docs/mobile-prefer-renderer-scrollback.md.
|
|
hasRendererSerializer?(ptyId: string): boolean
|
|
getRendererSerializerGeneration?(ptyId: string): number
|
|
waitForRendererSerializer?(
|
|
ptyId: string,
|
|
afterGeneration: number,
|
|
timeoutMs?: number,
|
|
signal?: AbortSignal
|
|
): Promise<boolean>
|
|
getSize?(ptyId: string): { cols: number; rows: number } | null
|
|
/** False only when the owning provider proved the PTY absent; null = unknown (never a denial). */
|
|
probePtyLiveness?(ptyId: string): Promise<boolean | null>
|
|
}
|
|
|
|
export type PtyControllerTerminalIdentity = Readonly<{
|
|
handle: string
|
|
incarnationId: string
|
|
wslDistro?: string | null
|
|
}>
|
|
|
|
export type PtyControllerInventory = Readonly<{
|
|
livePtyIds: ReadonlySet<string>
|
|
// Why: livePtyIds is worktree-scoped when a target is given; absence proofs
|
|
// must consult the unscoped inventory or a misattributed live PTY reads as dead.
|
|
allLivePtyIds: ReadonlySet<string>
|
|
terminalIdentityByPtyId: ReadonlyMap<string, PtyControllerTerminalIdentity>
|
|
queriedHostIds: ReadonlySet<ExecutionHostId>
|
|
/** Live PTYs the host is killing: still live work, but no longer any pane's to show. */
|
|
exitingPtyIds?: ReadonlySet<string>
|
|
}>
|