2026-07-21 16:01:00 +08:00
# Persistent PTY Sessions
2026-08-13 00:36:22 +08:00
English | [中文 ](terminal.zh.md )
2026-07-26 02:33:29 +08:00
2026-08-13 00:36:22 +08:00
Types shared by PTY backends, `ctx.terminals` , and the model-facing consumer. The [persistent PTY Agent Note ](../../.agents/notes/implemented/feature/2026-07-16-persistent-pty-sessions.md ) owns the rationale; this page records the cross-package vocabulary from [`packages/terminal/terminal/src/types.ts` ](../../packages/terminal/terminal/src/types.ts ).
2026-07-21 16:01:00 +08:00
## Identity and readiness
2026-08-13 00:36:22 +08:00
`TerminalSessionId` is a service-minted branded id. Optional names are owner-local display metadata; authorization compares the exact owning `Agent` , not a name or guessed id.
2026-07-21 16:01:00 +08:00
2026-08-13 00:36:22 +08:00
`TerminalWaitReason` says why one send returned. It is independent from `TerminalSessionStatus` : silence or timeout may return while the top-level shell remains alive, while `session_exit` means that shell exited rather than an arbitrary foreground child.
2026-07-21 16:01:00 +08:00
```ts type-equiv
/** Why one interactive send returned control to its caller. */
2026-08-13 00:36:22 +08:00
type TerminalWaitReason = 'stdin_read' | 'inferred_idle' | 'timeout' | 'session_exit'
2026-07-21 16:01:00 +08:00
` ``
` ``ts type-equiv
/** Top-level PTY process status, independent of a send's wait reason. */
2026-08-13 00:36:22 +08:00
type TerminalSessionStatus =
2026-07-21 16:01:00 +08:00
| { kind: 'running' }
| { kind: 'exited'; exitCode: number | null; signal: NodeJS.Signals | null }
` ``
## Backend and live session
2026-08-13 00:36:22 +08:00
A backend owns how one registered type starts and detects readiness. ` TerminalSessionService` publishes the returned session only after setup succeeds, then owns id authorization and cleanup. A backend that cannot clean partial startup resources rejects with ` TerminalBackendCleanupError`, allowing disposal to retain the cleanup failure without replacing the caller's cancellation reason. A backend session owns terminal state and captured-resource quiescence.
2026-07-21 16:01:00 +08:00
` ``ts type-equiv
/** Replaceable provider for one PTY session type. */
2026-08-13 00:36:22 +08:00
interface TerminalBackend {
/** Stable type selected by {@link TerminalSpawnRequest.type}. */
2026-07-21 16:01:00 +08:00
readonly type: string
2026-08-13 00:36:22 +08:00
/** Create an unpublished session or reject after cleaning partial resources; cleanup failure uses {@link TerminalBackendCleanupError}. */
spawn(spec: TerminalBackendSpawnSpec): Promise<TerminalBackendSession>
2026-07-21 16:01:00 +08:00
}
` ``
` ``ts type-equiv
2026-08-13 00:36:22 +08:00
/** Backend-owned live session retained by {@link TerminalSessionService}. */
interface TerminalBackendSession {
2026-07-21 19:29:56 +08:00
/** Initial bounded terminal output returned from ` terminal_open`. */
2026-07-21 16:01:00 +08:00
readonly motd: string
/** Top-level process id when one exists. */
readonly pid?: number
/** Start one exclusive send operation. */
2026-08-13 00:36:22 +08:00
startSend(request: TerminalSendRequest): TerminalSendOperation
2026-07-21 16:01:00 +08:00
/** Read one bounded page from retained scrollback. */
2026-08-13 00:36:22 +08:00
read(request: TerminalReadRequest): TerminalReadResult
2026-07-21 16:01:00 +08:00
/** Signal the verified foreground process group. */
2026-08-13 00:36:22 +08:00
signal(signal: TerminalSignal): Promise<TerminalSignalResult>
2026-07-21 16:01:00 +08:00
/** Observe top-level process status. */
2026-08-13 00:36:22 +08:00
status(): TerminalSessionStatus
2026-07-21 16:01:00 +08:00
/** Idempotently close the captured owned process tree and await quiescence. */
close(reason: string): Promise<void>
}
` ``
## Send and retained output
2026-08-13 00:36:22 +08:00
One live session accepts one active send. Its operation exposes a consuming output cursor for generic background jobs and one terminal result for a foreground caller. ` TerminalReadResult` separately pages the bounded session scrollback.
2026-07-21 16:01:00 +08:00
` ``ts type-equiv
/** Live backend-owned send; exactly one may be active per PTY session. */
2026-08-13 00:36:22 +08:00
interface TerminalSendOperation {
2026-07-21 16:01:00 +08:00
/** Resolves after readiness, timeout, cancellation, or top-level process exit. */
2026-08-13 00:36:22 +08:00
done: Promise<TerminalSendResult>
2026-07-21 16:01:00 +08:00
/** Consume output produced since the prior call. */
2026-08-13 00:36:22 +08:00
readOutput(): TerminalSendRead
2026-07-21 16:01:00 +08:00
/** Request ` SIGINT`; returns false after the operation settled. */
cancel(): boolean
}
` ``
` ``ts type-equiv
/** Settled result for one foreground or background send. */
2026-08-13 00:36:22 +08:00
interface TerminalSendResult {
2026-07-21 16:01:00 +08:00
/** Bounded rendered terminal delta remaining at settlement. */
viewport: string
/** Why the wait returned; this does not imply arbitrary child-process exit. */
2026-08-13 00:36:22 +08:00
waitReason: TerminalWaitReason
2026-07-21 16:01:00 +08:00
/** Top-level session status observed at settlement. */
2026-08-13 00:36:22 +08:00
sessionStatus: TerminalSessionStatus
2026-07-21 16:01:00 +08:00
/** Whether output was dropped from the operation or retained scrollback. */
truncated: boolean
}
` ``
## Ownership and durability
2026-08-13 00:36:22 +08:00
` TerminalSessionService` attaches one awaited cleanup to the exact owner scope, rejects foreign operations, and keeps sessions alive across backend or tool-plugin reload. PTY state and raw bytes remain process-local. Model input and bounded returned output are durable through the existing ` tool/call`, ` tool/result`, and task-result paths rather than duplicate PTY session events.
2026-07-30 21:40:58 +08:00
<!-- BEGIN GENERATED cordis-surface (gen-cordis-catalog.ts) — do not edit between markers -->
<a id="cordis-surface"></a>
2026-07-24 19:54:25 +08:00
## Cordis API
2026-07-30 21:40:58 +08:00
2026-07-24 19:54:25 +08:00
Generated from source by ` scripts/gen-cordis-catalog.ts` (verified fresh by ` pnpm run verify-cordis-catalog` in doc-sync; regenerate with ` pnpm run gen-cordis-catalog`) — this section is byte-identical in both language sides of the page. Signature blocks use a ` ts cordis-catalog` fence and keep the original source JSDoc; dispatch modes are defined in the [primer](../cordis-primer.md#dispatch-modes), and the framework-inherited ` ctx` API lives in [cordis-api/inherited.md](../cordis-api/inherited.md).
2026-07-30 21:40:58 +08:00
2026-08-13 00:36:22 +08:00
<a id="ctxterminals--terminalsessionservice"></a>
2026-07-30 21:40:58 +08:00
2026-08-13 00:36:22 +08:00
### ` ctx.terminals` — ` TerminalSessionService`
2026-07-30 21:40:58 +08:00
In-process registry for replaceable PTY backends and exact-Agent sessions.
` ``ts cordis-catalog
/**
* Register one backend type for this effect scope.
* @param backend - provider with a non-empty unique type.
* @returns disposer that removes exactly this contribution.
*/
2026-08-13 00:36:22 +08:00
registerBackend(backend: TerminalBackend): () => void
2026-07-30 21:40:58 +08:00
/**
* List registered backend types in registration order.
* @returns fresh backend type names.
*/
listBackends(): string[]
/**
* Create and publish one owner-scoped session after backend setup succeeds.
* @param owner - exact registered Agent that owns access and cleanup.
* @param request - backend type plus optional owner-local name and cwd.
* @param signal - cancellation of unpublished setup.
* @returns published identity, metadata, status, and MOTD.
*/
2026-08-13 00:36:22 +08:00
async spawn(owner: Agent, request: TerminalSpawnRequest, signal?: AbortSignal): Promise<TerminalSpawnResult>
2026-07-30 21:40:58 +08:00
/**
* Test whether an exact owner has a published session or unpublished spawn.
* @param owner - exact live owner to inspect.
* @returns true across the entire spawn-to-close interval, with no publication gap.
*/
hasOwnerActivity(owner: Agent): boolean
/**
* Start one exclusive interactive send.
* @param owner - exact session owner.
* @param id - target PTY identity.
* @param request - explicit text, submit behavior, and cancellation.
* @returns live operation handle for foreground await or task registration.
*/
2026-08-13 00:36:22 +08:00
startSend(owner: Agent, id: TerminalSessionId, request: TerminalSendRequest): TerminalSendOperation
2026-07-30 21:40:58 +08:00
/**
* Read one bounded scrollback page from an owned session.
* @param owner - exact session owner.
* @param id - target PTY identity.
* @param request - optional newest-relative offset and line count.
* @returns bounded retained text and pagination metadata.
*/
2026-08-13 00:36:22 +08:00
read(owner: Agent, id: TerminalSessionId, request: TerminalReadRequest = {}): TerminalReadResult
2026-07-30 21:40:58 +08:00
/**
* Deliver an allowed signal through an owned backend session.
* @param owner - exact session owner.
* @param id - target PTY identity.
* @param signal - allowed POSIX signal name.
* @returns delivered foreground process-group identity.
*/
2026-08-13 00:36:22 +08:00
signal(owner: Agent, id: TerminalSessionId, signal: TerminalSignal): Promise<TerminalSignalResult>
2026-07-30 21:40:58 +08:00
/**
* Close one owned session and remove it only after quiescent backend cleanup.
* @param owner - exact session owner.
* @param id - target PTY identity.
* @param reason - diagnostic cleanup reason.
* @returns true for a newly closed session, false when the same close is already in flight.
*/
2026-08-13 00:36:22 +08:00
async kill(owner: Agent, id: TerminalSessionId, reason: string = 'model request'): Promise<boolean>
2026-07-30 21:40:58 +08:00
/**
* List fresh snapshots for exactly one owner.
* @param owner - exact owner whose sessions are visible.
* @returns owner-visible snapshots in publication order.
*/
2026-08-13 00:36:22 +08:00
list(owner: Agent): TerminalSessionSnapshot[]
2026-07-30 21:40:58 +08:00
` ``
Types: [Agent](core.md)
2026-08-13 00:36:22 +08:00
Source: [` packages/terminal/terminal/src/index.ts:105`](../../packages/terminal/terminal/src/index.ts)
2026-07-30 21:40:58 +08:00
<!-- END GENERATED cordis-surface -->