docs(agent-launch): say when a replayed handle is re-derived, and what a gone pane reads as

The comments claimed a recorded terminal handle is dead after any restart and
that a gone pane's handle resolves to not-found. Neither is what the code does:
the terminal daemon and the SSH relay keep each PTY's handle and a restarted
runtime re-adopts it, so re-deriving it from the pane key changes it only when
that re-adoption does not happen (the PTY's incarnation changed, the runtime had
already issued the pane another handle, or the PTY stored none). A gone pane's
recorded handle reads as an exited terminal. Comments only.
This commit is contained in:
Brennan Benson
2026-10-04 15:55:24 -07:00
parent c83f44dd72
commit 39d15d52c0
3 changed files with 14 additions and 10 deletions
@@ -113,10 +113,12 @@ function readsUnconfirmedLaunchPrompt(
}
/**
* A terminal handle is issued by the process that answered, so a recorded one is dead after a
* restart. The pane key is the durable name: the handle is re-derived from it in this runtime. A
* pane this runtime no longer knows keeps the recorded handle, which then resolves to not-found —
* the truth about a terminal that is gone. The handle stays because shipped clients require one.
* The handle this runtime uses for the recorded pane now. Usually that is the recorded handle: the
* daemon and the SSH relay keep each PTY's handle, and a restarted runtime re-adopts it. It does not
* when the PTY's incarnation changed, when it already issued that pane another handle before reading
* the inventory, or when the PTY stored none; the pane key, the durable name, finds it then. A pane
* this runtime no longer knows keeps the recorded handle, which reads as an exited terminal — the
* truth about one that is gone. The handle stays because shipped clients require one.
*/
function withLiveTerminalHandle(
recorded: AgentLaunchResult,
@@ -4,8 +4,9 @@
* The record is written twice: once when the surface exists and once when the prompt's fate is
* known. A host that dies at any point leaves the replay a truthful answer — the running agent once
* its surface is recorded, an honest "unknown" before — and never a second agent. A "restart" here
* is what a new process sees: the store reopened from disk and a runtime with no in-flight launches,
* which issues its own handles, so a surviving pane comes back under a new one.
* is what a new process sees: the store reopened from disk and a runtime with no in-flight launches.
* Its runtime issues a surviving pane a new handle — the case where the daemon's or relay's handle
* was not re-adopted, the only one in which re-deriving the handle from the pane key changes it.
*/
import { mkdtemp, rm } from 'node:fs/promises'
@@ -74,7 +75,8 @@ const DESKTOP_IPC = {
clientKind: 'runtime' as const,
clientCapabilities: DESKTOP_RENDERER_RUNTIME_CLIENT_CAPABILITIES
}
/** The handle a restarted host issues the pane that outlived the old one. */
/** The handle a restarted host issues a pane that outlived the old one, when it could not
* re-adopt that pane's recorded handle. */
const ADOPTED_HANDLE = 'term_adopted'
let directory: string
@@ -210,7 +212,7 @@ describe('a host restart mid-launch', () => {
await restartHost()
const restarted = restartedHostRuntime()
// The dead host's `term_1` names nothing now; the pane is the durable name.
// The dead host's `term_1` was not re-adopted; the pane is the durable name.
await expect(launch(restarted, PROMPTED_LAUNCH, UPGRADED_PHONE)).resolves.toEqual(
UNCONFIRMED_AGENT
)
@@ -40,7 +40,7 @@ export type AgentLaunchRuntimeStubOptions = {
/** What the runtime reports about an offered prompt's typed line; unset reports nothing. */
lineCarriesPrompt?: boolean
/** Panes this runtime found already running, by the handle it issued them: a restarted host
* adopting a surviving PTY issues a new handle for the same pane. */
* that could not re-adopt a surviving PTY's handle issues a new one for the same pane. */
adoptedPanes?: Record<string, string>
}
@@ -63,7 +63,7 @@ export function setAgentLaunchRecordStore(store: AgentSessionRecordStore | null)
export function runtimeStub(options: AgentLaunchRuntimeStubOptions = {}) {
const worktreeCreateResults = new Map<string, Promise<unknown>>()
// Only the panes this runtime created or adopted: a handle is process-scoped, a pane key is not.
// Only the panes this runtime created or adopted, under the handle it issued them.
const handlesByPaneKey = new Map(Object.entries(options.adoptedPanes ?? {}))
const waitForSetupTerminalCompletion = vi.fn(
async (_handle: string, _signal?: AbortSignal): Promise<{ exitCode: number | null }> => ({