mirror of
https://github.com/stablyai/orca.git
synced 2026-09-30 16:02:56 +00:00
* fix(runtime): recover stale session owners and await retirement * fix(runtime): preserve session hydration and smoke compatibility * test(runtime): cover empty and unindexed session owners * feat(cli): make terminal close the canonical workspace teardown * fix(preload): align ssh termination result type * test(runtime): assert folder hydration owner * fix(runtime): fence legacy terminal stop by worktree host * fix(preload): reconcile ssh result import with main * fix(runtime): keep same-id sibling hosts out of workspace close The stale-owner fallback in the session controller re-routed any worktree whose catalog partition had no tabs to whichever other partition held tabs. Only `runtime:` environment ids rotate across relay restarts; `repoId::path` legitimately repeats across hosts, so an SSH workspace close could retire the local copy's tabs and resume records, or flip owners mid-close and strand the SSH PTY. Restrict the fallback to runtime hosts, and pin the session partition once per workspace close so record clearing targets the partition that owned the tabs. * test(runtime): give the cross-host close fixture a real resume record * fix(preload): take main's ssh-bridge import order so the merge stays duplicate-free
335 lines
9.0 KiB
TypeScript
335 lines
9.0 KiB
TypeScript
import type { AgentSessionPtyWriteRefusal } from './agent-session-pty-write-admission'
|
|
import type {
|
|
AgentProviderSessionMetadata,
|
|
SleepingAgentLaunchConfig
|
|
} from './agent-session-resume'
|
|
import type { StartupCommandDelivery } from './codex-startup-delivery'
|
|
import type { ExecutionHostId } from './execution-host'
|
|
import type { PtyIncarnationId } from './pty-incarnation'
|
|
import type { RuntimeMobileSessionTabsResult } from './runtime-session-contracts'
|
|
import type { TabGroupLayoutNode } from './tab-types'
|
|
import type { TerminalExitCause } from './terminal-exit-cause'
|
|
import type { TerminalPaneLayoutNode } from './terminal-tab-types'
|
|
import type { TuiAgent } from './tui-agent'
|
|
|
|
export type RuntimeTerminalSummary = {
|
|
handle: string
|
|
ptyId: string | null
|
|
incarnationId?: string | null
|
|
orphaned?: boolean
|
|
worktreeId: string
|
|
worktreePath: string
|
|
branch: string
|
|
tabId: string
|
|
leafId: string
|
|
title: string | null
|
|
connected: boolean
|
|
writable: boolean
|
|
lastOutputAt: number | null
|
|
preview: string
|
|
/** Host-resolved agent identity for action consumers; absent when unknown or unsupported. */
|
|
agentIdentity?: TuiAgent
|
|
/** Absent while running or when the host predates the field; never infer a clean finish. */
|
|
exitCause?: TerminalExitCause
|
|
/** Absent when the host predates the field or could not name the execution host. */
|
|
executionHostId?: ExecutionHostId
|
|
}
|
|
|
|
export type RuntimeTerminalVisualTerminalNode = {
|
|
type: 'terminal'
|
|
handle: string
|
|
tabId: string
|
|
leafId: string
|
|
title: string | null
|
|
connected: boolean
|
|
active: boolean
|
|
}
|
|
|
|
export type RuntimeTerminalVisualPaneNode =
|
|
| RuntimeTerminalVisualTerminalNode
|
|
| {
|
|
type: 'pane-split'
|
|
direction: Extract<TerminalPaneLayoutNode, { type: 'split' }>['direction']
|
|
first: RuntimeTerminalVisualPaneNode
|
|
second: RuntimeTerminalVisualPaneNode
|
|
}
|
|
|
|
export type RuntimeTerminalVisualTab = {
|
|
tabId: string
|
|
title: string | null
|
|
activeLeafId: string | null
|
|
panes: RuntimeTerminalVisualPaneNode
|
|
}
|
|
|
|
export type RuntimeTerminalVisualGroupNode = {
|
|
type: 'group'
|
|
groupId: string | null
|
|
activeTabId: string | null
|
|
tabs: RuntimeTerminalVisualTab[]
|
|
}
|
|
|
|
export type RuntimeTerminalVisualLayoutNode =
|
|
| RuntimeTerminalVisualGroupNode
|
|
| {
|
|
type: 'split'
|
|
direction: Extract<TabGroupLayoutNode, { type: 'split' }>['direction']
|
|
first: RuntimeTerminalVisualLayoutNode
|
|
second: RuntimeTerminalVisualLayoutNode
|
|
}
|
|
|
|
export type RuntimeTerminalVisualLayout = {
|
|
worktreeId: string
|
|
worktreePath: string
|
|
root: RuntimeTerminalVisualLayoutNode
|
|
}
|
|
|
|
export type RuntimeTerminalListHostScope = {
|
|
hostIds: ExecutionHostId[]
|
|
omittedHostIds: ExecutionHostId[]
|
|
}
|
|
|
|
export type RuntimeTerminalListResult = {
|
|
terminals: RuntimeTerminalSummary[]
|
|
visualLayouts?: RuntimeTerminalVisualLayout[]
|
|
topologyRevisions?: Record<string, number>
|
|
totalCount: number
|
|
truncated: boolean
|
|
/** Absent from hosts that predate the field; treat that scope as unverifiable. */
|
|
hostScope?: RuntimeTerminalListHostScope
|
|
}
|
|
|
|
export type RuntimeTerminalOrphanAdoptionClaim = {
|
|
terminal: string
|
|
ptyId: string
|
|
incarnationId: PtyIncarnationId
|
|
tabId: string
|
|
leafId: string
|
|
}
|
|
|
|
export type RuntimeTerminalOrphanTopologyTab = {
|
|
tabId: string
|
|
root: TerminalPaneLayoutNode
|
|
activeLeafId: string
|
|
expandedLeafId: string | null
|
|
}
|
|
|
|
export type RuntimeTerminalOrphanTopologyGroup = {
|
|
id: string
|
|
activeTabId: string
|
|
tabOrder: string[]
|
|
recentTabIds?: string[]
|
|
}
|
|
|
|
export type RuntimeTerminalOrphanTopology = {
|
|
tabs: RuntimeTerminalOrphanTopologyTab[]
|
|
groups: RuntimeTerminalOrphanTopologyGroup[]
|
|
groupLayout?: TabGroupLayoutNode
|
|
}
|
|
|
|
export type RuntimeTerminalOrphanAdoptionRequest = {
|
|
worktree: string
|
|
expectedTopologyRevision: number
|
|
claims: RuntimeTerminalOrphanAdoptionClaim[]
|
|
activeTabId?: string
|
|
activeGroupId?: string
|
|
topology?: RuntimeTerminalOrphanTopology
|
|
}
|
|
|
|
export type RuntimeTerminalOrphanAdoptionResult = {
|
|
adopted: boolean
|
|
topologyRevision: number
|
|
snapshot: RuntimeMobileSessionTabsResult
|
|
}
|
|
|
|
export type RuntimeWorktreeTerminalSleepResult = {
|
|
stopped: number
|
|
stoppedPtyIds: string[]
|
|
livePtyIds: string[]
|
|
} & (
|
|
| { postStopVerified: true; postStopFailure?: never; remainingLivePtyIds?: never }
|
|
| {
|
|
postStopVerified: false
|
|
postStopFailure: 'terminal_liveness_unavailable'
|
|
remainingLivePtyIds?: never
|
|
}
|
|
| {
|
|
postStopVerified: false
|
|
postStopFailure: 'terminal_worktree_sleep_still_live'
|
|
remainingLivePtyIds: string[]
|
|
}
|
|
)
|
|
|
|
export type RuntimeWorktreeTerminalCloseResult = {
|
|
closed: number
|
|
stopped: number
|
|
retiredSurfaces: true
|
|
ptyStopVerdict?: 'live' | 'unverifiable'
|
|
ptyStopReason?: string
|
|
}
|
|
|
|
export type RuntimeTerminalInteractiveWaitSource = 'hook' | 'prompt-text' | 'title'
|
|
|
|
export type RuntimeTerminalInteractiveWait = {
|
|
source: RuntimeTerminalInteractiveWaitSource
|
|
reason?: RuntimeTerminalWaitBlockedReason
|
|
since?: number
|
|
}
|
|
|
|
export type RuntimeTerminalShow = RuntimeTerminalSummary & {
|
|
paneRuntimeId: number
|
|
ptyId: string | null
|
|
rendererGraphEpoch: number
|
|
/** Null means evaluated with no wait; absent means not evaluated. */
|
|
agentWait?: RuntimeTerminalInteractiveWait | null
|
|
}
|
|
|
|
export type RuntimeTerminalState = 'running' | 'exited' | 'unknown'
|
|
|
|
export type RuntimeTerminalRead = {
|
|
handle: string
|
|
status: RuntimeTerminalState
|
|
tail: string[]
|
|
truncated: boolean
|
|
limited?: boolean
|
|
oldestCursor?: string
|
|
nextCursor: string | null
|
|
latestCursor?: string
|
|
returnedLineCount?: number
|
|
source?: 'stream' | 'screen' | 'screen-unavailable'
|
|
/** UI-only composer text, excluded from `tail`. */
|
|
draft?: string
|
|
}
|
|
|
|
export type RuntimeTerminalRename = {
|
|
handle: string
|
|
tabId: string
|
|
title: string | null
|
|
}
|
|
|
|
export type RuntimeTerminalSend = {
|
|
handle: string
|
|
accepted: boolean
|
|
bytesWritten: number
|
|
refusedReason?: 'no-agent' | 'permission'
|
|
/**
|
|
* Present only when a durable agent-session lease refused the write. Additive and optional: an
|
|
* old client sees the `accepted: false` it already handles and ignores this field.
|
|
*/
|
|
agentSessionRefusal?: AgentSessionPtyWriteRefusal
|
|
}
|
|
|
|
export type RuntimeTerminalAgentStatusState = 'working' | 'permission' | 'idle' | null
|
|
|
|
export type RuntimeTerminalAgentStatus = {
|
|
handle: string
|
|
isRunningAgent: boolean
|
|
status: RuntimeTerminalAgentStatusState
|
|
}
|
|
|
|
export type RuntimeTerminalPresentation = 'background' | 'focused'
|
|
|
|
type RuntimeTerminalCreateBaseRequestPayload = {
|
|
requestId: string
|
|
worktreeId?: string
|
|
afterTabId?: string
|
|
targetGroupId?: string
|
|
command?: string
|
|
cwd?: string
|
|
env?: Record<string, string>
|
|
envToDelete?: string[]
|
|
launchConfig?: SleepingAgentLaunchConfig
|
|
resumeProviderSession?: AgentProviderSessionMetadata
|
|
launchToken?: string
|
|
launchAgent?: TuiAgent
|
|
viewMode?: 'terminal' | 'chat'
|
|
startupCommandDelivery?: StartupCommandDelivery
|
|
title?: string
|
|
activate?: boolean
|
|
presentation?: RuntimeTerminalPresentation
|
|
surfaceOwner?: false
|
|
}
|
|
|
|
export type RuntimeTerminalCreateRequestPayload =
|
|
| (RuntimeTerminalCreateBaseRequestPayload & { source?: undefined })
|
|
| (RuntimeTerminalCreateBaseRequestPayload & {
|
|
worktreeId: string
|
|
source: 'runtime-session'
|
|
})
|
|
|
|
export type RuntimeTerminalCreate = {
|
|
handle: string
|
|
/** Host-owned PTY incarnation used to fence remote identity observations. */
|
|
incarnationId?: string | null
|
|
tabId?: string
|
|
paneKey?: string | null
|
|
ptyId?: string | null
|
|
worktreeId: string
|
|
title: string | null
|
|
executionHostId?: ExecutionHostId
|
|
hostPlatform?: NodeJS.Platform
|
|
surface?: 'background' | 'visible'
|
|
warning?: string
|
|
agentSessionDisposition?: 'created' | 'adopted'
|
|
isReattach?: true
|
|
/** Spawn process identity for host-internal ownership proof. */
|
|
processId?: number
|
|
}
|
|
|
|
export type RuntimeTerminalSplit = {
|
|
handle: string
|
|
tabId: string
|
|
paneRuntimeId: number
|
|
// Why: paired callers need the host-created leaf identity to focus the exact pane.
|
|
leafId?: string
|
|
}
|
|
|
|
export type RuntimeTerminalResolvePane = {
|
|
handle: string
|
|
/** Host-owned PTY incarnation used to fence remote identity observations. */
|
|
incarnationId?: string | null
|
|
tabId: string
|
|
leafId: string
|
|
ptyId: string | null
|
|
connected?: boolean
|
|
worktreeId?: string
|
|
executionHostId?: ExecutionHostId
|
|
hostPlatform?: NodeJS.Platform
|
|
}
|
|
|
|
export type RuntimeTerminalFocus = {
|
|
handle: string
|
|
tabId: string
|
|
worktreeId: string
|
|
navigated?: boolean
|
|
}
|
|
|
|
export type RuntimeTerminalClose = {
|
|
handle: string
|
|
tabId: string
|
|
closeMode?: 'tab'
|
|
ptyKilled: boolean
|
|
ptyStopVerdict?: 'live' | 'unverifiable'
|
|
ptyStopReason?: string
|
|
}
|
|
|
|
export type RuntimeTerminalWaitCondition = 'exit' | 'tui-idle'
|
|
|
|
export type RuntimeTerminalWaitBlockedReason =
|
|
| 'codex-update-prompt'
|
|
| 'codex-trust-workspace'
|
|
| 'codex-cwd-prompt'
|
|
| 'codex-model-migration-prompt'
|
|
| 'codex-hooks-review-prompt'
|
|
| 'codex-interactive-prompt'
|
|
| 'agent-approval-prompt'
|
|
|
|
export type RuntimeTerminalWait = {
|
|
handle: string
|
|
condition: RuntimeTerminalWaitCondition
|
|
satisfied: boolean
|
|
status: RuntimeTerminalState
|
|
exitCode: number | null
|
|
exitCause?: TerminalExitCause
|
|
blockedReason?: RuntimeTerminalWaitBlockedReason
|
|
}
|