898 lines
26 KiB
Markdown
898 lines
26 KiB
Markdown
# Orca Runtime Layer Design
|
|
|
|
## Goal
|
|
|
|
Define the shared runtime/orchestration layer that makes the Orca CLI's live terminal contract implementable.
|
|
|
|
This layer is required because the current codebase splits ownership across:
|
|
|
|
- Electron main process:
|
|
- PTY process lifecycle
|
|
- PTY IDs
|
|
- PTY data and exit events
|
|
- Renderer:
|
|
- tabs
|
|
- split-pane layout
|
|
- active pane
|
|
- terminal titles
|
|
- buffered offscreen writes
|
|
- unread/activity side effects
|
|
- Persistence:
|
|
- repo config
|
|
- worktree metadata
|
|
- saved terminal layout snapshots
|
|
- saved tab state
|
|
|
|
That split is fine for the editor UI, but it is not enough for a CLI that needs:
|
|
|
|
- a stable `runtimeId`
|
|
- live terminal handles
|
|
- safe stale-handle rejection
|
|
- compact live summaries like `worktree ps`
|
|
- terminal reads and writes that do not depend on renderer-local pane IDs
|
|
- a real external transport path from the `orca` CLI into the running app
|
|
|
|
## Problem Statement
|
|
|
|
Today there is no single shared service that can answer:
|
|
|
|
- what live terminal targets currently exist
|
|
- which worktree/tab/leaf each target belongs to
|
|
- which PTY each target is connected to
|
|
- what a safe public handle for that live target should be
|
|
- whether a handle is still valid
|
|
|
|
Relevant current ownership:
|
|
|
|
- PTY ownership: [../src/main/ipc/pty.ts](../src/main/ipc/pty.ts)
|
|
- Renderer tab state: [../src/renderer/src/store/slices/terminals.ts](../src/renderer/src/store/slices/terminals.ts)
|
|
- Pane lifecycle and PTY connection: [../src/renderer/src/components/terminal-pane/use-terminal-pane-lifecycle.ts](../src/renderer/src/components/terminal-pane/use-terminal-pane-lifecycle.ts)
|
|
- Pane -> PTY wiring: [../src/renderer/src/components/terminal-pane/pty-connection.ts](../src/renderer/src/components/terminal-pane/pty-connection.ts)
|
|
- Leaf ID serialization: [../src/renderer/src/components/terminal-pane/layout-serialization.ts](../src/renderer/src/components/terminal-pane/layout-serialization.ts)
|
|
|
|
## Non-Goals
|
|
|
|
This runtime layer does not try to:
|
|
|
|
- replace the renderer store
|
|
- replace the PTY implementation
|
|
- make pane IDs durable across reloads
|
|
- implement every future terminal automation feature in one step
|
|
|
|
The first purpose is to provide a shared control plane for the current app and CLI.
|
|
|
|
## Current Constraints To Preserve
|
|
|
|
The design should stay honest about three existing realities:
|
|
|
|
1. Orca is effectively single-window today.
|
|
The current PTY IPC wiring is attached to one `mainWindow` and forwards PTY data back through that window's `webContents`.
|
|
|
|
2. Leaf-level terminal state is not yet first-class renderer state.
|
|
Today Orca persists tab-level state and layout snapshots, but it does not persist a canonical renderer-side record for each leaf's title, preview, or screen snapshot.
|
|
|
|
3. Hidden terminals already accumulate deferred output in the renderer.
|
|
The runtime layer cannot assume every hidden leaf has a continuously updated visible-screen model without adding new explicit publication behavior.
|
|
|
|
This means the first runtime layer should optimize for correctness in the current single-window app before trying to generalize further.
|
|
|
|
## Core Design Principle
|
|
|
|
The runtime layer should be a main-process service that maintains a live registry built from:
|
|
|
|
- main-process PTY events
|
|
- renderer lifecycle registrations
|
|
- persisted repo/worktree metadata when useful
|
|
|
|
It should be the only place that:
|
|
|
|
- issues live terminal handles
|
|
- validates or rejects handles
|
|
- answers live summary queries
|
|
- exposes terminal read/write operations to the CLI
|
|
|
|
This avoids editor/CLI drift.
|
|
|
|
## Source Of Truth Boundaries
|
|
|
|
The runtime layer must not replace existing durable sources of truth.
|
|
|
|
Durable truth remains:
|
|
|
|
- Git for worktree existence and branch state
|
|
- `Store` persistence for repo config, worktree metadata, and saved session snapshots
|
|
|
|
Live truth becomes:
|
|
|
|
- runtime layer for terminal handles, live leaf/PTy mappings, and live summaries
|
|
|
|
This means:
|
|
|
|
- the runtime layer may cache and index persisted state
|
|
- but it should not become the canonical persistence owner for repo or worktree metadata
|
|
- renderer/UI code should stop inventing separate live-terminal contracts once the runtime layer exists
|
|
|
|
## Why Main Process Ownership
|
|
|
|
The runtime layer should live in the main process, not the renderer.
|
|
|
|
Reasons:
|
|
|
|
- the CLI will need to call into it even when no renderer component currently has focus
|
|
- PTY ownership already lives in the main process
|
|
- handle validation and stale-handle rejection are security and correctness boundaries
|
|
- renderer reloads should not destroy the authoritative registry object itself, even if they invalidate live handles
|
|
|
|
The renderer should publish registrations and updates into the runtime layer, not own the runtime layer.
|
|
|
|
Scope note:
|
|
|
|
- v1 runtime-layer design assumes one active Orca window
|
|
- multi-window support should be treated as a later extension, not an implicit requirement of the first implementation
|
|
|
|
## CLI Transport Boundary
|
|
|
|
The runtime layer also needs a transport boundary for the external CLI.
|
|
|
|
Recommendation:
|
|
|
|
- expose a local-only RPC endpoint from the main process
|
|
- use:
|
|
- Unix domain socket on macOS/Linux
|
|
- named pipe on Windows
|
|
- persist connection metadata in Orca user data:
|
|
- `runtimeId`
|
|
- endpoint path
|
|
- auth token
|
|
- pid
|
|
|
|
Suggested flow:
|
|
|
|
1. Orca main process starts the runtime service.
|
|
2. Orca opens the local RPC endpoint.
|
|
3. Orca writes connection metadata.
|
|
4. CLI reads connection metadata.
|
|
5. CLI connects locally and authenticates.
|
|
6. Runtime service handles CLI requests against the live registry.
|
|
|
|
Security properties:
|
|
|
|
- local machine only
|
|
- random auth token required
|
|
- stale pid/socket detection on startup
|
|
|
|
Why this matters:
|
|
|
|
- Electron renderer IPC is not the CLI transport
|
|
- the main process runtime service is the right authority for requests coming from the external CLI
|
|
|
|
## Runtime Identity
|
|
|
|
The runtime layer must generate a `runtimeId` when Orca launches.
|
|
|
|
Rules:
|
|
|
|
- `runtimeId` is unique per Orca process lifetime
|
|
- any full app restart creates a new `runtimeId`
|
|
- renderer reloads do not necessarily require a new `runtimeId`, but they may invalidate all live handles
|
|
|
|
Recommendation:
|
|
|
|
- keep `runtimeId` stable for the lifetime of the main Electron process
|
|
- separately track a renderer graph epoch that increments only when the renderer graph is explicitly reset or replaced in a way that breaks existing leaf mappings
|
|
|
|
Why:
|
|
|
|
- `runtimeId` is the coarse session identity exposed in CLI responses
|
|
- the renderer graph epoch is the finer invalidation boundary for ephemeral handles
|
|
|
|
CLI-facing simplification:
|
|
|
|
- handles are treated as ephemeral by default
|
|
- if the live graph is rebuilt in a way that invalidates mappings, all prior handles become stale
|
|
|
|
## Public Responsibilities
|
|
|
|
The runtime layer must support:
|
|
|
|
1. `status`
|
|
2. live terminal discovery
|
|
3. canonical selector resolution for CLI-facing repo/worktree lookups
|
|
4. handle issuance
|
|
5. handle validation
|
|
6. handle-based terminal reads
|
|
7. handle-based terminal writes
|
|
8. compact worktree live summaries
|
|
|
|
## Internal Data Model
|
|
|
|
The runtime layer should maintain the following registry objects.
|
|
|
|
### RuntimeState
|
|
|
|
```ts
|
|
type RuntimeState = {
|
|
runtimeId: string
|
|
rendererGraphEpoch: number
|
|
graphStatus: 'ready' | 'reloading' | 'unavailable'
|
|
authoritativeWindowId: number | null
|
|
}
|
|
```
|
|
|
|
### RegisteredTab
|
|
|
|
```ts
|
|
type RegisteredTab = {
|
|
tabId: string
|
|
worktreeId: string
|
|
title: string | null
|
|
activeLeafId: string | null
|
|
layout: TerminalPaneLayoutNode | null
|
|
lastSeenAt: number
|
|
}
|
|
```
|
|
|
|
### RegisteredLeaf
|
|
|
|
```ts
|
|
type RegisteredLeaf = {
|
|
tabId: string
|
|
worktreeId: string
|
|
leafId: string
|
|
paneRuntimeId: number
|
|
ptyId: string | null
|
|
ptyGeneration: number
|
|
lastOutputAt: number | null
|
|
lastExitCode: number | null
|
|
preview: string
|
|
tailBuffer: string[]
|
|
connected: boolean
|
|
writable: boolean
|
|
lastSeenAt: number
|
|
}
|
|
```
|
|
|
|
### TerminalHandleRecord
|
|
|
|
```ts
|
|
type TerminalHandleRecord = {
|
|
handle: string
|
|
runtimeId: string
|
|
rendererGraphEpoch: number
|
|
worktreeId: string
|
|
tabId: string
|
|
leafId: string
|
|
ptyId: string | null
|
|
ptyGeneration: number
|
|
createdAt: number
|
|
}
|
|
```
|
|
|
|
Why these fields matter:
|
|
|
|
- `leafId` gives stable layout identity within the current renderer graph
|
|
- `ptyId` is needed for actual write routing
|
|
- `ptyGeneration` prevents a restarted PTY in the same leaf from inheriting an old handle
|
|
- `tailBuffer` powers `terminal read`
|
|
- `preview` powers cheap discovery and `worktree ps`
|
|
- `writable` prevents CLI writes from racing against renderer-driven close or detach flows
|
|
|
|
## Handle Semantics
|
|
|
|
Handles are synthetic public identifiers issued by the runtime layer.
|
|
|
|
Rules:
|
|
|
|
- handles are opaque
|
|
- handles bind to:
|
|
- `runtimeId`
|
|
- `rendererGraphEpoch`
|
|
- `worktreeId`
|
|
- `tabId`
|
|
- `leafId`
|
|
- current `ptyId`
|
|
- current `ptyGeneration`
|
|
- handles are invalid if:
|
|
- `runtimeId` no longer matches
|
|
- `rendererGraphEpoch` has advanced past the handle's epoch
|
|
- the leaf registration no longer exists
|
|
- the leaf now points at a different `ptyId` or `ptyGeneration`
|
|
- the handle's current target cannot be resolved
|
|
|
|
This is intentionally strict.
|
|
|
|
Why:
|
|
|
|
- the CLI must never silently retarget input to a different live terminal
|
|
- handle invalidation should happen only for real remapping events, not every routine reconciliation pass
|
|
|
|
Stale-handle ergonomics:
|
|
|
|
- stale-handle errors should include the current `runtimeId`
|
|
- if the target leaf still exists but the specific handle is stale, the runtime layer may include a rediscovery hint scoped to that worktree or leaf
|
|
- the runtime layer should not implement a magical handle refresh that silently retargets the caller
|
|
|
|
## Event Sources
|
|
|
|
The runtime layer needs two classes of inputs.
|
|
|
|
### A. Main-process PTY events
|
|
|
|
Current source:
|
|
|
|
- [../src/main/ipc/pty.ts](../src/main/ipc/pty.ts)
|
|
|
|
Add runtime-layer integration points for:
|
|
|
|
- PTY spawned
|
|
- PTY data
|
|
- PTY exit
|
|
- PTY kill
|
|
|
|
What the runtime layer should record:
|
|
|
|
- `ptyId`
|
|
- PTY generation changes for a leaf
|
|
- data arrival timestamps
|
|
- exit code
|
|
- a bounded text tail buffer
|
|
|
|
### B. Renderer graph publication
|
|
|
|
The renderer already knows:
|
|
|
|
- when a tab exists
|
|
- what the saved and current layout is
|
|
- which leaf is active
|
|
- which pane has which current PTY
|
|
- titles derived from OSC updates
|
|
|
|
The runtime layer needs renderer-published graph state like:
|
|
|
|
- which tabs currently exist
|
|
- which leaves currently exist
|
|
- which worktree each tab belongs to
|
|
- which PTY each leaf is currently attached to
|
|
- which leaf is active within each tab
|
|
- what the current layout tree is for each tab
|
|
|
|
These are not current public APIs. They should be introduced as an explicit internal IPC channel.
|
|
|
|
Important source-of-truth rule:
|
|
|
|
- leaf records in the runtime registry are authoritative only when published by the renderer's full-graph sync
|
|
- persisted session state and renderer store state remain advisory inputs for tabs and worktrees, not a substitute for live leaf publication
|
|
|
|
## Suggested Internal IPC Contract
|
|
|
|
These are not CLI commands. They are editor-runtime plumbing.
|
|
|
|
### Renderer -> Main
|
|
|
|
- `runtime:syncWindowGraph`
|
|
|
|
Recommendation:
|
|
|
|
- start with one idempotent full-graph message as the source of truth for renderer-owned tab and leaf structure
|
|
- allow the renderer to resend the full graph whenever tab, layout, active-leaf, or PTY attachment state changes
|
|
- add narrower incremental messages later only if performance proves it necessary
|
|
|
|
Suggested payloads:
|
|
|
|
```ts
|
|
type RuntimeSyncWindowGraph = {
|
|
windowId: number
|
|
tabs: Array<{
|
|
tabId: string
|
|
worktreeId: string
|
|
title: string | null
|
|
activeLeafId: string | null
|
|
layout: TerminalPaneLayoutNode | null
|
|
}>
|
|
leaves: Array<{
|
|
tabId: string
|
|
worktreeId: string
|
|
leafId: string
|
|
paneRuntimeId: number
|
|
ptyId: string | null
|
|
}>
|
|
}
|
|
```
|
|
|
|
Why payloads matter:
|
|
|
|
- this is where ownership boundaries become real
|
|
- if these messages stay vague, implementation will drift back into ad hoc IPC
|
|
- treat full-graph sync as both the normal publication path and the repair path if an earlier renderer event was missed
|
|
|
|
Single-window v1 rule:
|
|
|
|
- Orca should accept exactly one authoritative publishing window in v1
|
|
- if a second window starts publishing, the runtime layer should reject it or mark the graph unavailable until the conflict is resolved
|
|
- `windowId` exists to make that restriction explicit now and extensible later
|
|
|
|
### Main -> Renderer
|
|
|
|
Only if needed for editor features:
|
|
|
|
- `runtime:handleInvalidated`
|
|
- `runtime:statusChanged`
|
|
|
|
The initial version can keep the runtime layer mostly main-owned and query-driven.
|
|
|
|
## How The Renderer Should Integrate
|
|
|
|
The renderer should publish runtime graph snapshots from the same places that already own lifecycle.
|
|
|
|
Recommendation:
|
|
|
|
- start with event-driven full snapshot publication
|
|
- do not add granular register/update/remove messages unless profiling shows the full graph is too expensive
|
|
- build the sync payload in one renderer-side collector/helper, and let lifecycle sites only schedule that helper rather than hand-assembling payload fragments
|
|
|
|
Why:
|
|
|
|
- renderer lifecycle is complex
|
|
- split/close/reload sequences are easy places to lose one incremental event
|
|
- a full snapshot lets the main process repair drift instead of accumulating ghost leaves or stale mappings
|
|
- the initial implementation needs correctness more than minimal event chatter
|
|
- a single collector reduces the risk that `runtime:syncWindowGraph` logic gets duplicated across store and pane lifecycle code
|
|
|
|
### Tab lifecycle
|
|
|
|
Source:
|
|
|
|
- [../src/renderer/src/store/slices/terminals.ts](../src/renderer/src/store/slices/terminals.ts)
|
|
|
|
Integration:
|
|
|
|
- when a tab is created, changed, or closed, republish the full graph
|
|
- when layout snapshot changes, republish the full graph
|
|
|
|
### Leaf/pane lifecycle
|
|
|
|
Source:
|
|
|
|
- [../src/renderer/src/components/terminal-pane/use-terminal-pane-lifecycle.ts](../src/renderer/src/components/terminal-pane/use-terminal-pane-lifecycle.ts)
|
|
- [../src/renderer/src/components/terminal-pane/pty-connection.ts](../src/renderer/src/components/terminal-pane/pty-connection.ts)
|
|
|
|
Integration:
|
|
|
|
- on pane created or closed, republish the full graph
|
|
- on active pane change, republish the full graph
|
|
- on PTY spawn or detach, republish the full graph
|
|
- on PTY respawn for an existing leaf, republish the full graph and let the runtime layer advance `ptyGeneration`
|
|
|
|
### Why the code needs comments
|
|
|
|
When this runtime graph publication is added, it needs comments explaining why Orca duplicates renderer lifecycle into a main-process registry:
|
|
|
|
- the CLI needs a shared live control plane
|
|
- pane IDs are renderer-local and not safe as a public contract
|
|
- handle validation must not depend on renderer-local assumptions
|
|
|
|
Those are design-driven constraints and should be documented in code comments per `AGENTS.md`.
|
|
|
|
## How The Main PTY Layer Should Integrate
|
|
|
|
Current PTY code:
|
|
|
|
- [../src/main/ipc/pty.ts](../src/main/ipc/pty.ts)
|
|
|
|
Required additions:
|
|
|
|
- publish PTY spawn/exit/data events to the runtime service
|
|
- maintain a lightweight PTY registry accessible to the runtime service
|
|
|
|
Suggested PTY event shape:
|
|
|
|
```ts
|
|
type RuntimePtySpawned = {
|
|
ptyId: string
|
|
loadGeneration: number
|
|
}
|
|
|
|
type RuntimePtyData = {
|
|
ptyId: string
|
|
data: string
|
|
at: number
|
|
}
|
|
|
|
type RuntimePtyExit = {
|
|
ptyId: string
|
|
exitCode: number
|
|
at: number
|
|
}
|
|
```
|
|
|
|
The runtime service should not parse terminal DOM state. It should build read models from:
|
|
|
|
- PTY output bytes
|
|
- renderer registrations
|
|
|
|
## Selector Resolution Service
|
|
|
|
The runtime layer should own canonical selector resolution for repo and worktree selectors rather than leaving it to the CLI frontend.
|
|
|
|
Why:
|
|
|
|
- selector semantics are part of the public contract, not presentation glue
|
|
- if the CLI resolves selectors differently from editor-driven integrations, Orca will drift
|
|
|
|
This service should:
|
|
|
|
- accept tagged selectors like `id:`, `path:`, `branch:`, and `issue:`
|
|
- reject ambiguous bare values with structured ambiguity errors
|
|
- return stable repo or worktree identities that downstream runtime operations can use
|
|
|
|
The terminal layer should remain handle-first once discovery is complete, but selector resolution must still be runtime-owned for consistent discovery semantics.
|
|
|
|
## Single-Window Assumption In V1
|
|
|
|
The current app is effectively single-window, and the first runtime layer should embrace that instead of pretending multi-window support already exists.
|
|
|
|
Recommendation:
|
|
|
|
- one runtime service per app process
|
|
- one CLI target runtime per app process
|
|
- v1 should permit only one authoritative publishing window
|
|
- if multiple windows appear later, they may register into the same runtime service only after Orca has an explicit multi-window routing model
|
|
|
|
The runtime layer should not issue window-scoped handles.
|
|
|
|
## Terminal Read Model
|
|
|
|
`terminal show` and `terminal read` need cheap buffers.
|
|
|
|
The runtime layer should maintain:
|
|
|
|
- `preview`
|
|
- `tailBuffer`
|
|
|
|
### Preview
|
|
|
|
Purpose:
|
|
|
|
- cheap discovery
|
|
- worktree summary
|
|
|
|
Strategy:
|
|
|
|
- derived from most recent meaningful lines
|
|
- capped to a few hundred characters
|
|
- should be main-owned once PTY data reaches the runtime service
|
|
|
|
### Tail buffer
|
|
|
|
Purpose:
|
|
|
|
- powers `terminal read`
|
|
|
|
Strategy:
|
|
|
|
- bounded ring buffer by line count and char count
|
|
- updated from PTY output
|
|
|
|
### Visible screen snapshots
|
|
|
|
Visible screen snapshots should be treated as a later enhancement, not a required v1 runtime primitive.
|
|
|
|
Why:
|
|
|
|
- hidden panes currently accumulate deferred output in `pendingWritesRef`, so a renderer-owned "current screen" is not uniformly trustworthy across visible and hidden leaves
|
|
- the CLI needs an honest contract more than a more ambitious but misleading one
|
|
|
|
So:
|
|
|
|
- `terminal show` should rely on runtime-owned metadata plus preview
|
|
- initial `terminal read` should rely on runtime-owned PTY tail data only
|
|
- if Orca later adds explicit visible-screen publication, that can be layered on as an optional richer read mode rather than a v1 requirement
|
|
|
|
## Drift Recovery
|
|
|
|
The runtime layer should assume registrations can drift.
|
|
|
|
Examples:
|
|
|
|
- renderer reload before all leaf removals are delivered
|
|
- pane closes while PTY exit is also firing
|
|
- a restored tab graph replaces leaf IDs
|
|
|
|
Recovery strategy:
|
|
|
|
1. event-driven full graph sync for correctness
|
|
2. explicit epoch bump only when the renderer graph is reset or replaced incompatibly
|
|
3. reject stale handles instead of trying to preserve them through remaps
|
|
|
|
This is another reason handles should be treated as ephemeral by default.
|
|
|
|
## Reload And Unavailable States
|
|
|
|
The runtime layer needs an explicit graph-availability state rather than assuming the renderer graph is always present when PTYs exist.
|
|
|
|
Recommendation:
|
|
|
|
- enter `graphStatus: 'reloading'` when the authoritative renderer is tearing down or the window is reloading
|
|
- enter `graphStatus: 'unavailable'` if no authoritative renderer graph is available
|
|
- return to `graphStatus: 'ready'` only after a fresh successful `runtime:syncWindowGraph`
|
|
|
|
Why:
|
|
|
|
- the current PTY layer can briefly keep PTYs alive while the renderer graph is gone or rebuilding
|
|
- CLI calls should fail closed during that window instead of acting on stale registry state
|
|
|
|
Behavior:
|
|
|
|
- `terminal list`, `terminal show`, `terminal read`, and `terminal send` should reject with a distinct runtime-unavailable error while `graphStatus != 'ready'`
|
|
- `status` should still work and report why the live terminal graph is unavailable
|
|
|
|
## `worktree ps` Summary Model
|
|
|
|
`worktree ps` should be powered by the runtime layer, not persistence alone.
|
|
|
|
For each worktree, it should summarize:
|
|
|
|
- repo
|
|
- branch
|
|
- linked issue
|
|
- unread metadata
|
|
- live terminal count
|
|
- whether any terminal is attached to a live PTY
|
|
- last output time if known
|
|
- recent preview if useful
|
|
|
|
Recommendation:
|
|
|
|
- compute this in the runtime service from:
|
|
- persisted worktree metadata
|
|
- live tab/leaf registrations
|
|
- PTY connectivity
|
|
|
|
Batch-read note:
|
|
|
|
- `worktree ps` is the preferred cheap batched live summary for many worktrees in v1
|
|
- Orca should avoid a second overlapping batch-preview primitive until real usage shows `worktree ps` is insufficient
|
|
|
|
The runtime layer should expose a single summary builder used by both:
|
|
|
|
- CLI `worktree ps`
|
|
- any future editor surfaces that want the same live summary semantics
|
|
|
|
Why:
|
|
|
|
- the CLI needs a cheap orchestration summary across many worktrees
|
|
|
|
## Wait Semantics
|
|
|
|
`terminal wait` needs to be split by what is actually observable.
|
|
|
|
### Safe first support
|
|
|
|
- `exit`
|
|
|
|
This can be grounded in PTY exit events.
|
|
|
|
### Later support requiring instrumentation or heuristics
|
|
|
|
- `output`
|
|
- `idle`
|
|
- `input`
|
|
|
|
Why:
|
|
|
|
- current code does not expose a first-class “waiting for input” state
|
|
- title heuristics exist in the renderer, but they are not sufficient as a strong CLI contract
|
|
|
|
Recommendation:
|
|
|
|
- runtime layer v1 supports only `wait --for exit`
|
|
- later phases may add:
|
|
- output wait from PTY data arrival
|
|
- idle wait from time-based quiescence
|
|
- input wait from agent-specific instrumentation, not generic shell guessing
|
|
|
|
## Failure Modes And Safety Rules
|
|
|
|
### 1. Stale handles
|
|
|
|
Must fail explicitly.
|
|
|
|
Never silently redirect to:
|
|
|
|
- another leaf with the same title
|
|
- the current active leaf
|
|
- another PTY in the same tab
|
|
|
|
This includes PTY restarts inside the same leaf. A restarted process must not inherit an old handle.
|
|
|
|
### 2. Renderer reload
|
|
|
|
The current code already kills prior-generation PTYs on page reload in [../src/main/ipc/pty.ts](../src/main/ipc/pty.ts).
|
|
|
|
The runtime layer should treat renderer reload as a graph invalidation event:
|
|
|
|
- bump `rendererGraphEpoch`
|
|
- invalidate all old handles
|
|
- require fresh discovery
|
|
|
|
During the reload window:
|
|
|
|
- set `graphStatus` to `reloading`
|
|
- reject live terminal operations until a fresh graph sync completes
|
|
|
|
### 3. Missing renderer registrations
|
|
|
|
If the runtime layer has PTYs but no renderer graph for a target:
|
|
|
|
- `status` may report degraded runtime health
|
|
- but terminal discovery and live terminal operations must not surface orphan PTYs as valid targets
|
|
|
|
This should surface as capability truth, not silent omission.
|
|
|
|
### 4. Closing or detached targets
|
|
|
|
If a leaf is present in the graph but is no longer writable:
|
|
|
|
- mark it `writable: false`
|
|
- reject `terminal send`
|
|
- continue to allow metadata reads when useful
|
|
|
|
Why:
|
|
|
|
- current Orca shutdown and PTY replacement flows are partly renderer-driven
|
|
- the CLI should not race writes into a target that Orca is intentionally closing or detaching
|
|
|
|
V1 definition:
|
|
|
|
- `writable` should be computed from facts Orca can actually observe now
|
|
- a target is writable only when:
|
|
- `graphStatus === 'ready'`
|
|
- the leaf exists in the current authoritative graph
|
|
- `ptyId != null`
|
|
- the leaf is still marked `connected`
|
|
- if Orca later adds an explicit renderer-side closing or detaching marker, that can tighten `writable` further
|
|
|
|
## Proposed Implementation Phases
|
|
|
|
### Phase 1: Runtime identity and service skeleton
|
|
|
|
Deliver:
|
|
|
|
- `runtimeId`
|
|
- main-process runtime service object
|
|
- `status` support
|
|
- lifecycle wiring hooks only
|
|
|
|
### Phase 2: Local CLI RPC transport and runtime metadata
|
|
|
|
Deliver:
|
|
|
|
- local socket/pipe listener
|
|
- auth token bootstrap
|
|
- request/response envelope shared by the editor and CLI
|
|
- runtime metadata file in Orca user data
|
|
|
|
Why this comes early:
|
|
|
|
- the CLI contract depends on a real runtime transport boundary
|
|
- it is better to lock the transport and auth model before layering more command handlers on top
|
|
|
|
### Phase 3: Renderer graph sync and PTY event ingestion
|
|
|
|
Deliver:
|
|
|
|
- `runtime:syncWindowGraph`
|
|
- tab/leaf graph registry
|
|
- PTY attach/detach mapping
|
|
- tail buffer updates from PTY events
|
|
- preview generation
|
|
|
|
### Phase 4: Handle issuance and validation
|
|
|
|
Deliver:
|
|
|
|
- handle generation
|
|
- handle lookup
|
|
- stale-handle rejection
|
|
- replacement hints in stale-handle errors when safe
|
|
- `terminal list`
|
|
- `terminal show`
|
|
|
|
### Phase 5: Read and write surface
|
|
|
|
Deliver:
|
|
|
|
- main-owned tail ring buffer
|
|
- `terminal read`
|
|
- `terminal send`
|
|
- `graphStatus`-aware rejection during reload and unavailable windows
|
|
|
|
### Phase 6: Summary service
|
|
|
|
Deliver:
|
|
|
|
- `worktree ps`
|
|
|
|
### Phase 7: Optional richer terminal reads
|
|
|
|
Deliver:
|
|
|
|
- renderer-published visible screen snapshots if Orca proves it needs them
|
|
|
|
### Phase 8: Wait support beyond exit
|
|
|
|
Deliver:
|
|
|
|
- `wait --for exit`
|
|
- explicitly defer the rest until instrumentation exists
|
|
|
|
## Recommended File/Module Shape
|
|
|
|
Main process:
|
|
|
|
- `src/main/runtime/orca-runtime.ts`
|
|
- `src/main/ipc/runtime.ts`
|
|
|
|
Renderer integration:
|
|
|
|
- `src/renderer/src/runtime/sync-runtime-graph.ts`
|
|
- targeted calls from:
|
|
- `terminals.ts`
|
|
- `use-terminal-pane-lifecycle.ts`
|
|
- `pty-connection.ts`
|
|
|
|
Why separate files:
|
|
|
|
- start with one runtime service and one IPC entrypoint so the design stays easy to land
|
|
- split handle, registry, and buffer helpers into separate modules later only if the implementation earns that complexity
|
|
|
|
## Open Questions
|
|
|
|
1. Should `runtimeId` change only on app restart, or also on explicit renderer graph reset?
|
|
|
|
Recommendation:
|
|
|
|
- app restart only
|
|
- use `rendererGraphEpoch` for graph invalidation
|
|
|
|
2. Should handles encode any meaning, or be fully opaque?
|
|
|
|
Recommendation:
|
|
|
|
- fully opaque
|
|
|
|
3. Should visible screen snapshots be pushed continuously or only on demand if Orca adds them later?
|
|
|
|
Recommendation:
|
|
|
|
- defer this until after the tail-buffer-based runtime contract is stable
|
|
- if added later, start with on-demand or throttled publication for visible leaves only
|
|
|
|
4. Should terminal previews come from screen snapshots or tail buffers?
|
|
|
|
Recommendation:
|
|
|
|
- use tail buffer for preview generation
|
|
- reserve visible screen snapshots for an optional richer read mode later
|
|
|
|
5. Should Orca support more than one publishing window in v1?
|
|
|
|
Recommendation:
|
|
|
|
- no
|
|
- keep one authoritative publishing window until PTY routing and renderer graph ownership are explicitly multi-window-safe
|
|
|
|
## Recommendation
|
|
|
|
Build the runtime layer as a main-process orchestration service with:
|
|
|
|
- stable `runtimeId`
|
|
- renderer-published full tab/leaf graph sync
|
|
- PTY-event integration
|
|
- local CLI RPC transport
|
|
- opaque handle issuance
|
|
- strict stale-handle rejection
|
|
- bounded read models for discovery and terminal reads
|
|
|
|
That is the smallest honest architecture that can support the Orca CLI's live terminal contract without drifting from the editor.
|