ctx.terminals
Open and manage terminal tabs through TerminalService. The terminal is a core feature — a built-in DockKind like the editor — so this mirrors ctx.editors: create opens a terminal in a workspace, and closeWorkspace reaps a workspace's terminals. The tab renders from the workspace's terminal records; the PTY session lives on ctx.process.
ctx.terminals: TerminalServiceExample
// open a shell terminal in the active workspace, rooted at a folder
ctx.terminals.create({ cwd: "/path/to/project" });
// open a specific kind in a specific workspace
ctx.terminals.create({ kind: "claude", workspaceId });Methods
On ctx.terminals. Method names link to the full signature.
| Method | What it does |
|---|---|
create(input?) | Open a new terminal in a workspace (defaults to the active one). Returns its record. |
closeWorkspace(id) | Close and kill every terminal in a workspace (e.g. on workspace delete). |
focus(terminalId) | Switch to the workspace containing this terminal and activate its tab in the center dock. No-ops for unknown ids. |
Tab adornments
Leading icons and trailing Phosphor indicators on terminal tabs. Prefer the adorn verbs (setIndicator / bindIndicator / …) — full guide: Tab adornments.
registerTabDecoration remains as a deprecated shim over bindIndicator.
ctx.subscriptions.push(
ctx.terminals.bindActivity({
id: "my-ext.tab",
provide(terminalId) {
if (!isBusy(terminalId)) return null;
return { activity: "working", tooltip: "Working" };
},
}),
);
ctx.terminals.invalidateTabAdornments();| Method | What it does |
|---|---|
setActivity / clearActivity / flashActivity / bindActivity | Host-owned Activity (ADR 0030) |
setIndicator / clearIndicator / flashIndicator / bindIndicator | Trailing static Phosphor indicators |
setIcon / clearIcon / bindIcon | Leading ReactNode icons |
invalidateTabAdornments | Re-query binders |
registerTabDecoration (deprecated) | Shim → bindIndicator |
OSC events
Subscribe to raw OSC (Operating System Command) escape sequences emitted by a terminal's PTY. Unlike the title that appears on the tab, this fires from the raw output stream regardless of whether the terminal's panel is mounted — making it reliable for background workspace monitoring.
Common OSC codes:
| Code | Meaning |
|---|---|
0 | Set window/tab title |
7 | Working directory (file://…) |
9 | iTerm2 notification (attention, progress) |
133 | Shell prompt marker (semantic shell) |
const BRAILLE_START = 0x2800;
const BRAILLE_END = 0x28ff;
const IDLE_CHAR = "\u2733"; // ✳ — Claude Code idle/waiting signal
ctx.subscriptions.push(
ctx.terminals.subscribeOsc(terminalId, ({ code, payload }) => {
if (code !== 0) return;
const first = payload.charCodeAt(0);
if (first >= BRAILLE_START && first <= BRAILLE_END) {
setStatus(terminalId, "busy"); // agent is running
} else if (payload.startsWith(IDLE_CHAR)) {
setStatus(terminalId, "idle"); // agent is waiting for input
}
}),
);| Method | What it does |
|---|---|
subscribeOsc(terminalId, handler) | Subscribe to parsed OSC sequences from a terminal's PTY stream. Returns a Disposable. |
Each event is an OscEvent.
Raw output
Subscribe to the raw PTY output stream of a terminal. The handler receives every chunk of bytes the PTY produces — ANSI escape sequences, OSC sequences, plain text — exactly as they arrive, before any parsing. This fires regardless of whether the terminal's panel is visible, making it suitable for background activity monitoring (for example, confirming an agent is still producing output between OSC signals).
Keep handlers lightweight: they execute synchronously on every PTY chunk, which can be multiple times per second while a program is running.
// Track the last time any output arrived to confirm agent activity.
let lastOutputAt = 0;
ctx.subscriptions.push(
ctx.terminals.subscribeOutput(terminalId, () => {
lastOutputAt = Date.now();
}),
);| Method | What it does |
|---|---|
subscribeOutput(terminalId, handler) | Subscribe to raw PTY output chunks from a terminal. Returns a Disposable. |
Active terminal
Track which terminal tab the user is looking at. "Active" is the center dock's single active panel of the active workspace — null when an editor tab (or nothing) is active, and transiently during workspace switches before the incoming workspace's active tab is published. A terminal that is merely visible in a non-active split does not count.
// clear a "needs attention" marker once the user views the terminal
ctx.subscriptions.push(
ctx.terminals.subscribeActive((terminalId) => {
if (terminalId) attention.delete(terminalId);
}),
);| Method | What it does |
|---|---|
getActive() | The record id of the active center-dock terminal tab, or null. |
subscribeActive(listener) | Subscribe to active-terminal changes (tab activation, group activation, workspace switch). Returns a Disposable. |
Types
Pass TerminalService.
Related: CreateTerminalInput · TerminalRecord · TerminalKind · TerminalTabDecoration · TerminalTabDecorationProvider · OscEvent.
See also
Persistent sessions live on ctx.process. Other State members on ctx.