Skip to content

Terminal Channel

A terminal channel carries the state of a single pseudo-terminal (pty) process — a shell, dev server, build task, or other long-running command. Terminals live independently of any session that may have created them, and may be claimed by clients or by sessions.

For a hands-on walkthrough of terminal flows, see the Terminals guide.

URI

<scheme>:/<id>

The scheme and path of a terminal URI are server-defined. The lightweight terminal catalogue carried on RootState.terminals advertises each live terminal's URI; clients subscribe to a terminal URI to get its full state.

State

Subscribers receive a TerminalState snapshot:

typescript
TerminalState {
  title: string
  cwd?: URI
  cols?: number
  rows?: number
  content: TerminalContentPart[]
  exitCode?: number
  claim: TerminalClaim
  supportsCommandDetection?: boolean
}

content is an ordered array of typed parts. Each part is either unclassified (raw VT output) or a structured command part carrying the command line, accumulated output, exit code, and duration. See the Terminals guide for the part shapes.

A terminal is always owned — the claim field records whether it belongs to a client or a session.

Lifecycle

Creation

createTerminal is a JSON-RPC request that allocates a new terminal with a required initial claim plus optional name, cwd, and dimensions:

jsonc
// Client → Server
{
  "jsonrpc": "2.0",
  "id": 9,
  "method": "createTerminal",
  "params": {
    "channel": "ahp-terminal:/<id>",
    "claim": { "kind": "client", "clientId": "client-abc" },
    "name": "build",
    "cwd": "file:///workspace",
    "cols": 120,
    "rows": 30
  }
}

After creation, the server dispatches root/terminalsChanged so that subscribers of ahp-root:// see the new entry in the terminal catalogue, and the client SHOULD subscribe to the terminal URI to receive the full state.

Disposal

disposeTerminal kills the underlying pty (if running) and removes the terminal. The server dispatches root/terminalsChanged to reflect the new catalogue. There is no "release without disposal" — when a terminal is no longer needed, it is disposed.

Methods and events on this channel

This section lists wire methods that are interpreted in the context of a terminal URI (ahp-terminal:/<id>).

Commands (params.channel = "ahp-terminal:/<id>")

MethodKindPurpose
createTerminalrequestCreate a terminal at the chosen URI with an initial claim.
disposeTerminalrequestDispose this terminal and kill its pty if still running.

Notifications (params.channel = "ahp-terminal:/<id>")

MethodKindMeaning
actionserver → client notificationTerminal action envelope (terminal/* action payloads).
dispatchActionclient → server notificationDispatch terminal client actions (terminal/input, terminal/resized, terminal/claimed, terminal/titleChanged, terminal/cleared).
unsubscribeclient → server notificationStop receiving messages for this terminal channel.

Actions

All terminal-scoped action envelopes carry channel: "<terminal-uri>". Action payloads do NOT carry their own terminal URI.

Data flow

ActionClient-dispatchableReducer effect
terminal/dataNoAppends to the tail content part
terminal/inputYesNo-op (server forwards to the pty)

terminal/data is server-only. terminal/input is a side-effect-only client action — the client dispatches keyboard input and the server forwards it to the pty process. The reducer does not modify state on terminal/input; any resulting output arrives later via terminal/data.

Why two separate actions?

Terminal I/O is intentionally split into terminal/input (client → pty) and terminal/data (pty → client) because standard write-ahead reconciliation is not safe for terminals. A pty is a stateful, mutable process — optimistically applying input or predicting output would produce incorrect state. By keeping input as a side-effect-only action and output as server-authoritative, clients avoid the reconciliation pitfalls that would arise from treating terminal I/O like normal state actions.

Command detection

Shell-integrated terminals can announce command boundaries:

ActionClient-dispatchableReducer effect
terminal/commandDetectionAvailableNoSets supportsCommandDetection: true
terminal/commandExecutedNoAppends a command part, sets supportsCommandDetection: true
terminal/commandFinishedNoMarks the matching command part complete with exit code & duration

The server MUST NOT include shell integration escape sequences in terminal/data — they MUST be stripped before dispatch. Clients MUST check supportsCommandDetection before relying on command boundaries.

Control

ActionClient-dispatchableReducer effect
terminal/resizedYesSets cols, rows
terminal/claimedYesSets claim
terminal/titleChangedYesSets title
terminal/cwdChangedNoSets cwd
terminal/exitedNoSets exitCode
terminal/clearedYesResets content to []

Commands

Catalogue Notifications

The lightweight terminal catalogue on RootState.terminals is kept in sync via the server-side root/terminalsChanged action on the Root Channel. Terminal channels themselves emit no protocol notifications — only action envelopes.

Released under the MIT License.