Files
orca/src/main/runtime/orca-runtime.ts
T
Jinwoo Hong ed13ca1e6f Layout groundwork (switched off): main starts and restores every pane, and the window only attaches and paints the host's history (#31567)
* feat(terminal): the window's store shows main's pane start status (layout-owner build)

A status-only frame changes one store entry and no pane tree, so the pane's view stays mounted.

* refactor(terminal): keep the frame sync within its line budget

* feat(terminal): every pane main starts shows 'starting' from before it is written (layout-owner build)

* feat(terminal): a pane main is starting waits and attaches; a failed start shows why (layout-owner build)

Fixes owner-visible row 15: a failed agent or setup start no longer opens a plain shell.

* test(terminal): iterate store listeners directly

* fix(terminal): an exit that reports no code keeps its pane, as lost contact (layout-owner build)

* test(terminal): a pane main started attaches to main's terminal at its own grid

* test(terminal): detach census tests assert main's move and frame with the switch on

* fix(terminal): a restored pane paints history only from its terminal's host, never the window's copy (layout-owner build only)

* fix(terminal): main releases a painted cold restore only for the pane's current terminal (layout-owner build only)

* test(terminal): typed ack args in the painted cold-restore test

* test(terminal): exited-pane tests assert main's close with the switch on

These 8 have failed switch-on since #31130 moved the exit close to main.

* feat(layout): main starts the panes a client's startPane or wake names, through its one restored-pane starter (layout-owner build only)

The model's start and stop effects reach the runtime once the command is written, never the
reply. startPane starts a pane whose binding has a start status, and a slept pane from its record.
A wake keeps a pane's resume record until its start binds; a failed start keeps it and shows why.
With the switch on, main owns a running agent's wake even with a window open.

* fix(layout): a window's wake of a pane it resumed itself is record-only, so main does not start it again (layout-owner build only)

* test(layout): a client's startPane starts the pane before any other trigger

* Revert "fix(terminal): a restored pane paints history only from its terminal's host, never the window's copy (layout-owner build only)"

This reverts commit a646d89b26.

* test(terminal): the cold-restore repaint ack names its pane in the layout-owner build

* fix(layout): a wake keeps the pane's resume record in the pane-owned form, so a host crash resumes the agent again (layout-owner build only)

* test(layout): a failing start in the pane-start runner test throws

* feat(terminal): main writes and starts every split of a pane it holds; the window attaches (layout-owner build)

The window's split, a startup split with its command, and a CLI split all go through main's
terminal.split; main's frame mounts the pane, with or without a window intent. Deletes the
window's split wait and withdraw, the split appear intents, and the queued-split skip and cancel.
The e2e helper uses the real split. A startup split leaves focus on its source pane.

* fix(terminal): the attach-only wait leaves sleeping, resuming and held panes to their restore path

* fix(terminal): a split pane attaches to the terminal main bound it to; a taken pane id is refused

Also restores the proof that a split closed while it starts gets one answer and leaves no
pane or start status behind.

* Reapply "fix(terminal): a restored pane paints history only from its terminal's host, never the window's copy (layout-owner build only)"

This reverts commit 420e605ad0.

* fix(layout): a trigger between a restored pane's start and its bind write joins that start, not a second one (layout-owner build only)

* fix(terminal): a window split main cannot start still shows its pane, failed, with why

* fix(terminal): keep the switch import the D1b-1 merge dropped from the layout restore

* feat(layout): main's starter shows a restored pane starting before any await, and a failed start with its reason; startPane starts a pane whose bound terminal ended (layout-owner build only)

* feat(terminal): a window's attach-only view of a pane attaches to its terminal or starts nothing, keeping the binding for main's start (layout-owner build only)

* feat(terminal): a restored pane with no live terminal asks main to start it and attaches once main binds it; the window's wake, hibernated wake, restart-on-show and gone-terminal fallbacks route through main (layout-owner build only)

* fix(terminal): check a taken pane id first, keep the start's own error, clear a gone pane's status

Three test stores gain the pane-start field the window's store always has.

* test(terminal): the admission stub answers whether its pane is still written; a closed pane's failed start clears

* test(terminal): with the switch on, the window's restores, wakes and restart replacements ask main to start the pane and attach to the terminal main binds, starting and resuming nothing themselves

* refactor(terminal): the rebound workspaces' bindings show through one call in the bindings module

* fix(terminal): a held pane connects as today so main's refusal holds it; main's start follows output; a capacity death after main's resume keeps the resume (layout-owner build only)

* fix(layout): main passes its start options only for commands that start panes; a windowed open resumes the agent under main (layout-owner build only)

* refactor(terminal): break the pane start module cycle; typed test fixtures

* test(terminal): a pane main wrote and the user closed mid-start drops its failed status

* fix(terminal): every waiter on main's start runs, a failed or refused start settles and is asked again on the next view, a hidden gone pane waits for its view, a twin main hands back stays on today's path, and lost contact never starts a pane (layout-owner build only)

* test(terminal): iterate the store listeners directly

* test(terminal): a cold-restore paint applies no window resume with the switch on

* fix(terminal): a split main starts is listed to phones and session.tabs without a window's report

* test(terminal): name the main pane start test helper as a fixture

* fix(terminal): a reconnect shows its pane starting and why it failed; a lost owner race shows no failure; a split no one asked to see keeps the phones' tab

* fix(terminal): a kept create another owner won shows no failure; the window attaches to the winner

* fix(terminal): a pane main starts at relaunch keeps its full history and starts before the window's graph lists its tab (layout-owner build only)

* feat(terminal): main replaces the empty shell a hibernated agent left and resumes the agent when a view asks; a start main could not attach shows main's own terminal_not_started (layout-owner build only)

* test(terminal): main seeds its copy of a restored pane even when a window declared one, with the switch on

* fix(terminal): a pane main started hidden paints once when its view attaches (switch-on builds only)

The view's attach snapshot, taken after main unmarks the hidden gate, already holds the bytes main
dropped, so the reveal no longer asks a second restore from main's buffer.

* refactor(terminal): main's pane start reads the pane from its own layout, not the client projection (switch-on builds only)

* fix(terminal): a pane restart stops its terminal before writing the pane unbound, so a stop that fails keeps the pane (switch-on builds only)

* test(terminal): a start main could not bind shows terminal_not_started through the window's wait, from one copy site (switch-on builds only)

* fix(terminal): main's pane start resumes an agent recorded under the pane's number, as the window did; stale switch-on tests show main's start and the known banner loss (switch-on builds only)

* fix(terminal): review round: a visible view alone retires main's hidden-start drops, by episode; main reads a split pane's numbered record by its number; the window keeps a shell main kept (switch-on builds only)

* fix(terminal): main's start of a pane a window view is attaching to leaves it visible, so the view paints once (switch-on builds only)

* fix(terminal): a window view that attaches while main starts its pane takes main's start, with no second provider attach (switch-on builds only)

* fix(terminal): review round: a view takes main's start only when it carries a payload and still owns its terminal; attaching views are counted, keep main's pre-spawn mark off, and a failed attach hides only what it kept visible (switch-on builds only)

* test(terminal): checked test doubles in the view-join and numbered-record tests; reattach options fit the file budget

* test(e2e): with the switch on, the mount-authority spec keeps the window's bindings, which only main's frames write

Claude-Session: W5b

* test(e2e): the mount-authority spec fits its line budget; the layout-owner build check is one helper

Claude-Session: W5b

* test(layout): an unbound pane holding a resume record starts and keeps the record, since main's start resumes from it

Claude-Session: W5b

* fix(terminal): a pane main resumed holds a running agent's record, so it reads awake, not sleeping (switch-on builds only)

Claude-Session: W5b

* fix(terminal): in a split where no pane owns a numbered record, main resumes the tab's one conversation in pane 1 only (switch-on builds only)

Claude-Session: W5b

* fix(terminal): main marks only the record a resume started from live, never a sleep captured after it (switch-on builds only)

Claude-Session: W5b

* test(terminal): with the switch on, a windowless resume leaves the pane's record a running agent's

Claude-Session: W5b

* test(terminal): the resumed-record test double typechecks

Claude-Session: W5b

* refactor(terminal): with the switch off, main's pane start reads no resume record for the live mark

Claude-Session: W5b

* refactor(terminal): the merged files fit their line budget: the folder-fallback notice and a hidden pane's wait for its first view are helpers, and the answering-transport lookup moves to its one test caller

Claude-Session: W5b

* refactor(runtime): the window's layout apply fits the runtime's line budget after the main merge

Claude-Session: W5b
2026-10-21 22:13:57 -04:00

362 lines
17 KiB
TypeScript

import type { RuntimeLinearCommandSurface } from './runtime-linear-command-surface'
import { installRuntimeLinearCommandSurface } from './runtime-linear-command-surface'
import { publishWorkspaceViewChanges } from './runtime-workspace-view-change-events'
import {
OrcaRuntimeWithSidebarTemporal,
sidebarFactsRuntime
} from './sidebar-inputs/orca-runtime-sidebar-temporal'
import { OrcaRuntimeWithAgentTransitions } from './orca-runtime-agent-transitions'
import { ORCA_SIDEBAR_INPUTS_IS_MAIN } from '../../shared/sidebar-inputs-switch'
import { ORCA_LAYOUT_OWNER_IS_MAIN } from '../../shared/layout-owner-switch'
import type { RuntimeCommandSurfaceHost } from './orca-runtime-core'
import type {
AgentLaunchTabPublished,
AgentLaunchTabPublishRequest
} from '../../shared/agent-launch-tab-publication'
import type {
AgentLaunchPaneAddress,
AgentLaunchPaneVerdict
} from '../../shared/agent-launch-pane-verdict'
import { registerWorktreeChangeInvalidator } from '../ipc/worktree-change-invalidators'
import type { AgentSessionRecordStore } from './agent-session-record-store'
import { peekOpenedAgentSessionRecordStore } from './agent-session-record-store-slot'
import { createAgentLaunchRecordWarmupGate } from './agent-launch-record-warmup-gate'
import { WorkspaceLayoutStream, type WorkspaceLayoutReads } from './workspace-layout-stream'
import type { RemoteLayoutSnapshot } from '../../shared/remote-layout-snapshot'
import { registerDetectedWorktreeScanInvalidation } from '../ipc/worktrees/listing/register-detected-worktree-scan-invalidation'
import {
listReferenceWorkspaces,
selectReferenceWorkspace,
listWorkspaceReferences
} from './runtime-reference-catalog'
import { findWorkspaceReferences } from './runtime-reference-find'
import { createReferenceAgentIndex, referenceConnectionHosts } from './runtime-reference-agents'
import type { RuntimeReferenceFindParams } from '../../shared/runtime-reference-contracts'
import { getRepoExecutionHostId } from '../../shared/execution-host'
import type { LayoutApplyReply } from '../../shared/workspace-layout/workspace-layout-command-types'
import { rekeyMovedPaneOwners } from './moved-pane-owner-rekey'
import { applyWindowLayoutCommand, type WindowLayoutCommand } from './runtime-layout-apply'
import type { TerminalCreateOptions } from './runtime-terminal-contracts'
import {
installStructuredAgentAvailability,
type StructuredAgentAvailabilityReader
} from './structured-agent-availability'
import { PairedServerRowsProducer } from './sidebar-inputs/paired-server-rows-producer'
import { RepoIconDataUrls } from './sidebar-inputs/repo-icon-data-urls'
import { WorkspaceRowsProducer } from './sidebar-inputs/workspace-rows-producer'
import { getMainHttpClient } from '../network/http-client'
import { getRuntimeDesktopSurface } from './runtime-desktop-surface'
import { CollapseWriteOrder } from './workspace-view-collapse-writes'
import { SidebarInputsFeed } from './sidebar-inputs/sidebar-inputs-feed'
import { sidebarInputsFeedDomains } from './sidebar-inputs/sidebar-inputs-feed-domains'
import { requireSwitchedOn } from './sidebar-inputs/require-switched-on'
import { HEADLESS_RUNTIME_WINDOW_ID } from '../../shared/runtime-session-contracts'
import { getHostDisplayLabelOverrides } from '../../shared/host-setting-overrides'
// Switch point: a window-fed build keeps no sidebar facts layer.
const OrcaRuntimeSidebarBase: typeof OrcaRuntimeWithAgentTransitions = ORCA_SIDEBAR_INPUTS_IS_MAIN
? OrcaRuntimeWithSidebarTemporal
: OrcaRuntimeWithAgentTransitions
// oxlint-disable-next-line typescript/no-unsafe-declaration-merging -- SAFETY: The existing Linear installer supplies the canonical prototype surface.
class OrcaRuntimeService extends OrcaRuntimeSidebarBase {
/** This runtime as its sidebar facts layer; null unless built with ORCA_SIDEBAR_INPUTS=main. */
get sidebarFacts() {
return sidebarFactsRuntime(this)
}
// Switch point. Idle until relay hosts attach; the fallback refresh runs while a window shows the sidebar or a phone reads the feed.
readonly pairedServerRows = ORCA_SIDEBAR_INPUTS_IS_MAIN
? new PairedServerRowsProducer({
hasDemand: () =>
this.sidebarInputsFeed?.subscribed === true ||
(this.authoritativeWindowId !== null &&
this.authoritativeWindowId !== HEADLESS_RUNTIME_WINDOW_ID &&
this.store?.getUI?.().sidebarOpen !== false),
hostLabelOverrides: () => getHostDisplayLabelOverrides(this.getClientSettings())
})
: null
// Switch point: the phone's icon source.
readonly repoIconDataUrls = ORCA_SIDEBAR_INPUTS_IS_MAIN
? new RepoIconDataUrls({
repos: () => this.store?.getRepos() ?? [],
// Why not the shared desktop client: its net.fetch rejects every manual redirect.
fetch: (url, init) => {
const manualRedirectFetch = getRuntimeDesktopSurface().fetchRepoIcon
return manualRedirectFetch
? manualRedirectFetch(url, init)
: getMainHttpClient().fetch(url, init)
},
encoder: () => getRuntimeDesktopSurface().encodeRepoIconPng
})
: null
// Switch point: the per-writer ordering behind ui.setCollapsed.
readonly collapseWriteOrder = ORCA_SIDEBAR_INPUTS_IS_MAIN ? new CollapseWriteOrder() : null
// Switch point: this host's workspace rows, from the listing worktree.ps reads.
readonly workspaceRows = ORCA_SIDEBAR_INPUTS_IS_MAIN
? new WorkspaceRowsProducer({
store: this.store,
// Its own scans, not the shared listing: metadata joins at read, so no write waits on git.
scan: () => this.scanResolvedWorktreeRepos(),
join: (scans) => this.joinResolvedWorktreeScans(scans),
visible: (snapshot) => this.visibleResolvedWorktreeSnapshot(snapshot),
lifecycle: this.workspaceLifecycle,
onClientEvent: (listener, options) => this.onClientEvent(listener, options)
})
: null
// Switch point: the phone's feed of the producers above; idle until this host's own phone reads.
readonly sidebarInputsFeed = ORCA_SIDEBAR_INPUTS_IS_MAIN
? new SidebarInputsFeed(
() =>
sidebarInputsFeedDomains({
facts: requireSwitchedOn(this.sidebarFacts),
pairedServerRows: requireSwitchedOn(this.pairedServerRows),
workspaceLifecycle: requireSwitchedOn(this.workspaceLifecycle),
workspaceRows: requireSwitchedOn(this.workspaceRows),
repoIcons: requireSwitchedOn(this.repoIconDataUrls),
store: () => this.store,
onClientEvent: (listener, options) => this.onClientEvent(listener, options)
}),
(event) => this.emitClientEvent(event)
)
: null
private async selectReferenceCatalogWorkspace(selector: string, cwd?: string) {
const catalog = listReferenceWorkspaces(
this.requireStore(),
this.resolvedWorktrees.peek()?.worktrees
)
try {
return selectReferenceWorkspace(catalog, selector, cwd)
} catch (error) {
if (!(error instanceof Error) || error.message !== 'selector_not_found') {
throw error
}
// Why: unsaved worktrees and cold-cache branches only resolve after a worktree scan.
const warmed = listReferenceWorkspaces(
this.requireStore(),
await this.listResolvedWorktrees()
)
return selectReferenceWorkspace(warmed, selector, cwd)
}
}
async listWorkspaceReferences(selector: string, cwd?: string) {
return listWorkspaceReferences(await this.selectReferenceCatalogWorkspace(selector, cwd))
}
async findWorkspaceReferences(params: RuntimeReferenceFindParams) {
if (params.worktree && params.repo) {
throw new Error('--repo and --worktree cannot be combined')
}
let workspaces = params.worktree
? [await this.selectReferenceCatalogWorkspace(params.worktree, params.cwd)]
: listReferenceWorkspaces(this.requireStore(), this.resolvedWorktrees.peek()?.worktrees)
if (params.repo) {
const repo = await this.resolveRepoSelector(params.repo)
workspaces = workspaces.filter(
(row) =>
row.kind === 'worktree' &&
row.repoId === repo.id &&
row.hostId === getRepoExecutionHostId(repo)
)
}
return findWorkspaceReferences(params, {
workspaces,
agents: async (workspaceKeys) =>
createReferenceAgentIndex(
this,
this.getOrchestrationDbIfAvailable(),
await this.openAgentSessionRecordStore(),
workspaceKeys,
referenceConnectionHosts(this.listRepos())
)
})
}
constructor(...args: ConstructorParameters<typeof OrcaRuntimeWithAgentTransitions>) {
super(...args)
// Why: the runtime listing re-runs a scan the worktree-change generation overtook and re-lists
// through this runtime's scan cache, so a worktree change must reach both. The desktop IPC
// module registers the generation bump at load; a headless host never loads it.
registerDetectedWorktreeScanInvalidation()
registerWorktreeChangeInvalidator((repoId) => this.invalidateWorktreeCatalog(repoId))
publishWorkspaceViewChanges(this.store, (event) => this.emitClientEvent(event))
}
private structuredAgentAvailability?: ReturnType<typeof installStructuredAgentAvailability>
/** This host's agent health snapshot and every replacement, from first use; returns the release. */
subscribeStructuredAgentAvailability(read: StructuredAgentAvailabilityReader): () => void {
this.structuredAgentAvailability ??= installStructuredAgentAvailability(this.store, this)
return this.structuredAgentAvailability.subscribe(read)
}
/** Whether a window owns the layout and can show a launch's tab ahead of its process. */
canPublishAgentLaunchTab(): boolean {
return Boolean(this.notifier?.publishAgentLaunchTab && this.getAvailableAuthoritativeWindow())
}
/** Shows an agent launch's tab before its process exists, in the window that owns the layout;
* null when no window does, and the launch's tab then appears when it spawns, as before. */
publishAgentLaunchTab(
request: Omit<AgentLaunchTabPublishRequest, 'requestId'>
): Promise<AgentLaunchTabPublished> | null {
if (!this.notifier?.publishAgentLaunchTab || !this.getAvailableAuthoritativeWindow()) {
return null
}
return this.notifier.publishAgentLaunchTab(request)
}
/** Tells the window a launch pane's fate: it keeps a final one on the tab, clears a settled one,
* and takes a withdrawn pane back (the pane alone when the user split the tab). */
reportAgentLaunchPaneVerdict(
pane: AgentLaunchPaneAddress,
verdict: AgentLaunchPaneVerdict
): void {
this.notifier?.agentLaunchPaneVerdict?.({ ...pane, verdict })
}
private readonly agentLaunchRecordWarmup = createAgentLaunchRecordWarmupGate({
isOpen: () => peekOpenedAgentSessionRecordStore() !== null,
open: () => this.openAgentSessionRecordStore()
})
/** Startup is done; the launch record may open once a client that can launch is here too. */
noteAgentLaunchStartupSettled(): void {
this.agentLaunchRecordWarmup.startupSettled()
}
/** A client that can call `agent.launch` connected; its first launch should not open the record. */
noteAgentLaunchClientReady(): void {
this.agentLaunchRecordWarmup.launchClientReady()
}
/** Plans an agent launch a window pane's own spawn asks for, with the planner every host launch
* uses, including its runtime fact. */
async planAgentPaneLaunch(
worktreeId: string,
opts: TerminalCreateOptions
): Promise<TerminalCreateOptions> {
const workspace = await this.resolveTerminalWorkspaceLaunchScope(`id:${worktreeId}`)
return this.resolveAgentTerminalCreateOptions(workspace, opts)
}
/** Whether a running process holds this pane now: such a pane is attached to, never launched into. */
hasLiveTerminalForPaneKey(paneKey: string): boolean {
return this.getPtyRecordForPaneKey(paneKey)?.connected === true
}
private readonly workspaceLayoutStream = new WorkspaceLayoutStream({
store: () => this.store,
homeHostId: (key) => this.tryGetWorkspaceSessionHostIdForWorktree(key),
// An empty title is no title.
liveTitle: (ptyId) => this.getTrackedDisplayTitleForPty(ptyId) || null,
paneStartFact: (paneKey, binding) => this.paneStartStatuses?.get(paneKey, binding) ?? null
})
protected markPaneLiveTitlesDirty(): void {
this.workspaceLayoutStream.markLiveTitlesDirty()
}
/** The read-only layout stream (`layout.subscribe`, not advertised yet). */
subscribeWorkspaceLayouts(
listener: Parameters<WorkspaceLayoutStream['subscribe']>[0]
): ReturnType<WorkspaceLayoutStream['subscribe']> {
return this.workspaceLayoutStream.subscribe(listener)
}
/** Main's one layout projection for readers beside the stream, with or without a window. */
get workspaceLayoutReads(): WorkspaceLayoutReads {
return this.workspaceLayoutStream
}
/** A window's layout command (`layout.apply`), applied to the partitions that hold its tab. */
async applyLayoutCommand(command: WindowLayoutCommand): Promise<LayoutApplyReply> {
// A workspace's first surface comes after the live terminals main is placing.
const placing = this.livePlacementSettledFor(command)
const apply = (): Promise<LayoutApplyReply> =>
applyWindowLayoutCommand(
this.requireStore(),
(worktreeId, tabId) =>
tabId !== null
? this.getWorkspaceSessionHostIdsForTab(worktreeId, tabId)
: [this.tryGetWorkspaceSessionHostIdForWorktree(worktreeId) ?? []].flat(),
command,
(move) => rekeyMovedPaneOwners(this, move),
this.layoutCommandRun(command)
)
// A window's sleep or wake lands after the hook statuses main accepted before it, as in the window.
const reply = await (ORCA_LAYOUT_OWNER_IS_MAIN && ['sleep', 'wake'].includes(command.type)
? this.inResumeRecordOrder(apply)
: (placing?.then(apply) ?? apply()))
// Sent before the reply, so the window can wait for the frame that shows the change.
return { ...reply, frameSeq: this.workspaceLayoutStream.settledSeq() }
}
/** This desktop's display-only copy of each paired server's layout (`layout.getRemoteSnapshots`). */
getRemoteLayoutSnapshots(): Record<string, RemoteLayoutSnapshot> {
return this.store?.getRemoteLayoutSnapshots?.() ?? {}
}
/** The launch record when it is already open, for a reader that must not wait for it. */
openedAgentSessionRecordStore(): AgentSessionRecordStore | null {
return peekOpenedAgentSessionRecordStore()
}
}
// Linear installs these methods on the final runtime prototype below.
// oxlint-disable-next-line typescript/consistent-type-definitions -- SAFETY: This merge models the installed Linear API.
interface OrcaRuntimeService extends RuntimeLinearCommandSurface {}
type OrcaRuntimeServiceExport = RuntimeCommandSurfaceHost<OrcaRuntimeService>
const OrcaRuntimeServiceExport: {
new (...args: ConstructorParameters<typeof OrcaRuntimeService>): OrcaRuntimeServiceExport
readonly prototype: OrcaRuntimeServiceExport
} = OrcaRuntimeService
export { OrcaRuntimeServiceExport as OrcaRuntimeService }
installRuntimeLinearCommandSurface(OrcaRuntimeServiceExport.prototype)
export type { LegacyWorkerTerminalRecoveryResult } from './runtime-legacy-worker-terminal-recovery-types'
export type {
RuntimeAutomationCreateInput,
RuntimeAutomationUpdateInput
} from './runtime-automation-controller'
export type { SubscriptionRegistration } from './runtime-subscription-registry'
export type {
OrchestrationCompatibilityCallerAuthority,
OrchestrationCompatibilityTerminalAuthority,
RuntimeTerminalAgentStatusEvent
} from './runtime-terminal-contracts'
export type { MessageWaitResult } from './runtime-message-waiters'
export type { AccountsSnapshot, CodexRateLimitResetRpcResult } from './runtime-account-controller'
export type {
MobileNotificationDispatchEvent,
MobileNotificationDismissEvent,
MobileNotificationEvent
} from './runtime-mobile-notification-controller'
export type { RuntimeTerminalDataMeta } from './runtime-terminal-stream-consumers'
export type { RemoteFetchResult, RemoteTrackingBase } from './runtime-remote-fetch-controller'
export {
computeTerminalTailWaitState,
tailGainedNewerBlockedReason,
type TerminalTailWaitState
} from './terminal-wait-tail-state'
export { appendNormalizedToTailBuffer } from './terminal-tail-buffer'
export { appendNormalizedToMultilineTailBufferUnwindowed } from './terminal-tail-redraw-buffer'
export { buildPreview } from './terminal-tail-state'
export { buildRestoredTerminalTailSeed } from './terminal-tail-restore-seed'
export { projectTerminalTailLines } from './orca-runtime-terminal-projection'
export { resolveWorktreeScanCacheTtlMs } from './runtime-worktree-scan-cache'
export type {
RuntimeWorktreeLifecycleEvent,
DriverState,
PtyLayoutTarget,
PtyLayoutState,
ApplyLayoutResult,
RuntimeRendererReloadFence
} from './orca-runtime-core'
export {
AUTHORITATIVE_TERMINAL_SNAPSHOT_TIMEOUT_MS,
WORKTREE_SCAN_ADMIN_RECONCILE_INTERVAL_MS,
WORKTREE_SCAN_ADMIN_FINGERPRINT_TIMEOUT_MS
} from './orca-runtime-postlude'