A restored pane came back with the killed program's mouse reporting still on, so every pointer move over it was typed into the new shell's line as an SGR report and the line grew for as long as the pointer stayed there. The chain is short and each link is deliberate. A snapshot is a raw byte stream, not a rendered grid, and `scrollback.rs` says so in its own module doc. `ReplayRing::seeded` puts those bytes into the restored pane's ring verbatim, and the client hands them straight to its parser at `DaemonMsg::Snapshot`. So the dead program's `?1002h` is not a description of a screen, it is an instruction, and it is executed a second time against a shell that never asked for it. There is nothing after it to undo it: `DaemonPane::kill` is a hangup with no grace period, so the program never emitted its own `?1002l`, and the snapshot was photographed before the kill anyway. `restore_preamble` already exists to keep exactly this kind of leftover away from the incoming shell — it resets the alternate screen, the cursor, autowrap and the graphic rendition — and it simply stopped short of the modes that make the terminal talk back. It now also turns off mouse reporting and its encodings, focus reporting, bracketed paste, application cursor keys and the kitty keyboard flags. Unconditionally, which was the choice worth making. The alternative was to fold the snapshot's bytes through a mode tracker and clear only what the fold says is still on, which is what #828 does for the neighbouring re-attach path. That precision is essential there and buys nothing here: there the pane is alive and re-sending a mode it had turned off would be wrong, whereas here the target state is a constant. The incoming shell is brand new, it has not written a byte when the preamble is appended, and it asked for none of these modes, so there is nothing to preserve and no mode whose value has to be discovered. The risk is not symmetric either. Switching off a mode that is already off is a no-op in every emulator, so the blunt version cannot fail; a fold that misparses one sequence leaves the mode on and the bug exactly as it is today, silently. The blunt version also needs nothing from #828, so this can merge in either order. Left alone on purpose: `?1007` alternate scroll, which a default terminal has on, so clearing it would walk away from the default rather than back to it; `?2026` synchronised update, which the client's processor already closes out when a replayed frame ends inside one; and `stale_mode_resets` in the client, whose conditional shape is right for the live pane at a prompt that it serves. Scrolling regions and origin mode are a different failure with no report behind them and are not touched. The test that locked in the short reset list now locks in the long one, and asserts the `?1007` omission so it stays deliberate. A second test runs the real chain — a snapshot ending inside a full-screen program, `ReplayRing::seeded`, the preamble — and asserts that the ring a client would replay has `l` as its last word on every reporting mode. Both fail on the previous preamble. Claude-Session: https://claude.ai/code/session_01UUyWQXzcBAoBzaSX8pc7nU
tty7
A terminal workbench: persistent sessions, remote work, agents.
Pure Rust · GPU rendering on Zed's gpui · VT core from Alacritty
English · 简体中文
Why
A background server owns your shells and panes — not the window. Everything below follows from that.
- Performance — ~2× the throughput of Alacritty, Ghostty, or Kitty (benchmarks)
- Persistent sessions — quit or reboot; your shells and supported agent sessions keep running, no tmux
- Agent-aware — Claude Code, Codex & co.: status, notifications, and git context for every repo at once
- Scriptable by agents — one agent opens a pane for another, hands off a task, waits, and reads the result, with or without the GUI running
- Editor-grade input — suggestions, completion, highlighting, history search, with no plugin to install
- Remote development — files, repos, panes, and git data stay on the remote machine, over a native SSH stack
- Git beside the terminal — source control, diffs, and worktrees without leaving the window
Install
Native builds for macOS, Windows, and Linux on Releases:
| macOS | …-macos-arm64.dmg · …-x86_64.dmg |
drag into Applications |
| Windows | …-setup.exe · portable ….zip |
|
| Linux | …-x86_64.AppImage |
chmod +x and run — X11/Wayland libraries bundled |
What's inside
| Agent-aware | per-pane detection (20 CLIs) · status dot · notifications · branch + diff · tray icon when input is needed · resume after reboot · tab sidebar grouped by repository |
| CLI + Skills | bundled tty7 CLI · agent skill · run streams a command and exits with its code · split · send · wait --until free · capture |
| Editor-grade input | ghost suggestions from history · explained tab completion · syntax highlighting · multi-line editing · click places the caret · ⌃ R fuzzy history |
| Window | tabs & splits · ⌘ P palette · ⌘ F scrollback search · ⌘ J panel with process tree and listening ports · 13 themes, your own YAML, iTerm2 import · IME |
| Shell integration | injected when a pane starts, nothing to install · prompt marks · working directory · exit codes · command-finished notifications · zsh, bash, fish, PowerShell, WSL, remote panes |
| Remote workspaces | remote files, repos, changes, diffs, worktrees, tabs, and panes · reconnect from any client and continue where you left off |
| SSH | native russh stack: profiles with keychain secrets · SFTP panel · port forwarding · jump hosts · one-time, unprivileged tty7-server install |
| Git | panel follows the focused pane · stage, commit, amend, branch, push, stash · side-by-side or unified diffs · commit graph with cherry-pick, revert, and reset · a new worktree opens its own tab |
Supported agents
Detection is free: brand avatar, branch + diff, tab title.
Status takes one click under Settings → Agents to install that agent's hook,
and brings the status dot, notifications, the tray icon, tty7 wait, and resume
after a reboot. Fork needs both — the agent's own fork command, and the hook
that tells tty7 which session to fork.
The full support matrix, all twenty
| Agent | Detected | Status · resume | Fork |
|---|---|---|---|
| Claude Code | ✓ | ✓ | ✓ |
| Codex | ✓ | ✓ | ✓ |
| TraeCode | ✓ | ✓ | ✓ |
| Grok | ✓ | ✓ | ✓ |
| OpenCode | ✓ | ✓ | ✓ |
| Oh My Pi | ✓ | ✓ | ✓ |
| Droid | ✓ | ✓ | ✓ |
| Qwen Code | ✓ | ✓ | ✓ |
| Goose | ✓ | ✓ | ✓ |
| Gemini | ✓ | ✓ | |
| Copilot | ✓ | ✓ | |
| Kimi Code | ✓ | ✓ | |
| Pi | ✓ | ✓ | |
| Aider | ✓ | ||
| Amp | ✓ | ||
| Cursor | ✓ | ||
| Auggie | ✓ | ||
| Hermes | ✓ | ||
| Vibe | ✓ | ||
| Antigravity | ✓ |
None of them are wrapped or proxied — the agent you start is the agent you get,
in a normal PTY, with its own interface. An agent launched through a wrapper
script can be mapped to one by name with agent_commands in config.json.
Documentation
Full documentation lives in docs/ —
keyboard shortcuts ·
config.json ·
CLI reference. The agent-facing CLI interface is also
documented in skills/tty7/SKILL.md.
Install the skill with:
npx skills add l0ng-ai/tty7 # install
npx skills update tty7 # update later
Benchmarks
Same machine, same day, same 155×40 grid — Apple M1 Pro, macOS 26.3.1, five-run averages (2026-07-04):
| tty7 | Alacritty | Ghostty | Kitty | |
|---|---|---|---|---|
Plaintext I/O — 11 MB cat (lower = better) |
95 ms | 239 ms | 179 ms | 185 ms |
| DOOM-fire frame rate (higher = better) | 888 fps | 485 fps | 552 fps | 617 fps |
| Cold-launch memory | 116 MB¹ | 105 MB | 128 MB | 130 MB |
¹ GUI 105 MB + the persistent server 11 MB.
Methodology and one-command reproduction: scripts/bench/.
Built on gpui and alacritty_terminal · Apache-2.0 · Discord · Changelog