Codevisor Docs

Terminal protocol

Implement the bidirectional Codevisor terminal protocol.

Create a terminal with POST /v1/terminals:

{
  "sessionId": "session-id",
  "cwd": "/srv/project",
  "cols": 120,
  "rows": 36,
  "shell": "/bin/zsh",
  "args": ["-l"]
}

shell and args are optional. Set attachOnly: true to attach to an agent-owned background terminal without spawning a shell; creation fails until that terminal has registered. The response contains terminalId, websocketPath, and nextOutputSeq.

Shells the server starts load Ghostty's shell integration (zsh, bash 4 and later, fish, elvish, and nushell), so they set the title to the running command and mark their prompts.

Connect to /v1/terminals/{id}/socket?lastOutputSeq=N. The server replays output after the supplied sequence before sending live frames. The server keeps about 2 MiB of recent output per terminal. When the supplied sequence is older than that, it sends one output frame with reset: true instead: a reconstruction of the terminal's current screen and scrollback, starting with a full reset.

Client frames

Every client frame carries a stable clientId and a monotonic clientSeq.

{ "type": "input", "clientId": "client-1", "clientSeq": 1, "data": "ls\n" }
{ "type": "resize", "clientId": "client-1", "clientSeq": 2, "cols": 120, "rows": 36 }
{ "type": "close", "clientId": "client-1", "clientSeq": 3 }

With protocol 2, three more frames:

{ "type": "focus", "clientId": "client-1", "clientSeq": 4 }
{ "type": "hide", "clientId": "client-1", "clientSeq": 5 }
{ "type": "clear", "clientId": "client-1", "clientSeq": 6 }

When several clients show one terminal, the client being used sets the PTY's size, so programs lay out for the screen someone is using. A client is being used when it types (input) or sends focus: send focus when the terminal is opened, tapped, or clicked into. A terminal's own replies to a program's queries are input but not use: send them with "claim": false, and only from the client whose size the PTY has, so the program gets one answer. Until any client does, the first to show the terminal sets the size. Every client receives { "type": "size", "seq": 0, "cols": 200, "rows": 50 } on attach and whenever the size changes. A client whose own grid is smaller than that should show the PTY's grid, cropped or scaled, rather than reflow it. Send resize whenever the terminal is shown or changes size, and hide when it goes off screen. If the client that set the size hides or disconnects, the most recently active client still showing the terminal takes over.

The server acknowledges each client frame with { "type": "ack", "seq": 0, "clientSeq": 6 }. Keep frames until they are acknowledged and send them again, unchanged, after reconnecting: a connection can drop with frames still in flight, and the clientSeq duplicate check ignores any that did arrive.

clear clears the terminal for every client. When the shell is in the foreground, the server clears the screen and scrollback and sends the shell Ctrl-L so it redraws its prompt. When a program is running, only the scrollback is cleared.

Server frames

Server frames are output, exit, or error objects. Their seq field is the replay cursor. Persist the highest processed sequence and supply it as lastOutputSeq after reconnecting. Treat replayed and reset output as history: it can contain terminal queries that were already answered, so a renderer must not answer them again.

Keep one stable clientId per client attachment and increase clientSeq for every frame. This lets the server reject duplicate input after a client retries or reconnects.

Deleting /v1/terminals/session/{sessionId} closes the live terminal associated with a session so a later create request can start a fresh shell. It returns 200 { "closed": true }; when no live terminal exists, it returns 404 { "closed": false }.

Protocol 2 and flow control

Add protocol=2 to the socket URL to receive output as binary frames. The server first sends a { "type": "ready", "seq": 0, "protocol": 2 } text frame; servers without protocol 2 ignore the parameter and keep sending JSON text. Each binary frame is a kind byte (1 output, 2 reset output), the sequence number as a big-endian 64-bit integer, and the UTF-8 output. Other frames stay JSON text.

After ready, clients can send { "type": "ping", "t": 123, "srtt": 80 }. The server answers { "type": "pong", "seq": 0, "t": 123 }. srtt is the client's smoothed round trip in milliseconds; on slow links the server batches output for a few milliseconds to send fewer, larger frames.

A client that cannot keep up is never allowed to stall the terminal. When its socket falls about 1 MiB behind, the server stops sending it output, and once it drains, catches it up from the last frame it received: with the missed output when that is still retained, otherwise with a reset reconstruction.

Pane titles

When a program sets the terminal title (OSC 0 or 2), the server records it as liveTitle on every workspace pane whose resourceKind is terminal and whose resourceId is the terminal's sessionId. The title arrives in the usual navigation.changed pane updates. It is recorded once it has held for half a second, so animated titles do not flood clients, and is removed when the program clears it or the terminal exits. Show it in place of the pane's title unless the user named the tab.

A single leading activity glyph followed by a space is dropped from the title: a braille spinner frame, or one of ·✢✳✶✻✽◐◓◑◒, so Claude Code's ✳ Claude Code is recorded as Claude Code. What the glyph meant is recorded as terminalActivity:

  • working: the title starts with a braille spinner frame or a half circle (◐◓◑◒), or contains a braille spinner frame as a separate word, as Codex does.
  • idle: the title starts with ✳, Claude Code at its prompt.
  • Absent for any other title, such as a plain shell's.

A spinner animating the title changes neither the title nor the activity, so a working agent is recorded once when it starts and once when it stops. Neither survives a server restart.

Reading the screen

GET /v1/terminals/{id}/screen?format=text returns the terminal's current screen and scrollback as plain text, from the server's own copy of the terminal state: { "format": "text", "screen": "..." }. Use format=vt for escape sequences that reproduce the screen, including colors and the cursor, in a terminal emulator. Unknown terminals return 404.

On this page