Files
tty7/docs/features.md
l0ng-aiandl0ng-ai 9ca3319239 feat(agents): fork an agent session and copy its session id (#241)
* feat(agents): fork an agent session, and copy its session id

A coding-agent conversation is a single thread: to try a risky direction you
either lose the one that got you there, or you don't try it. Every agent tty7
resumes already knows how to branch — `codex fork <id>`, `claude --resume <id>
--fork-session`, `opencode --session <id> --fork`, `grok --resume <id>
--fork-session` — but nothing in tty7 reached them, so the capability was
invisible from the terminal that already knows every pane's session id.

Fork is a per-agent capability beside the existing resume table
(`CLIAgent::fork_command`), not a Codex special case: it is the same `match
self` shape, it reuses the same id validation and the same launch-flag replay,
and four installed agents qualify today. Every command was checked against that
CLI's own `--help`; agents with no fork tty7 could verify return `None` and are
never offered the action, since a guessed flag shape would only ever produce a
usage error in the pane.

Flag replay needed one correctness fix to survive this. A forked pane's own
argv *is* a fork command, so relaunching it would replay the stale subcommand
and id (`codex fork <old>` → an old id as a positional prompt) or double the
modifier (`--fork-session --fork-session`). `codex fork` now sheds its
subcommand exactly as `codex resume` did, and `--fork-session` / `--fork` join
their agents' stale session-targeting lists. That also settles restore: a forked
pane restores through `resume_command`, which now drops the fork flag — a
restart continues the fork rather than branching it again.

Placement follows where the user asked from. A pane-level ask is spatial, so
the pane right-click menu offers Split Right / Left / Down / Up (pane splits
gained a `before` slot for the Left/Up half, which the tree had no way to
express). A tab-level ask isn't, so the tab context menu — inherited verbatim by
the sidebar rows, which is where the request came from — opens the fork in a new
tab with no placement question. The bare action behind the palette, the File
menu and Settings → Keybindings takes the tab-level meaning.

The three ways a fork can't run all surface rather than no-op: no session id
yet (hooks not installed) and a remote pane (the command would shell the *local*
agent) render the row disabled instead of hiding it, so the capability stays
discoverable, and the action paths that have no row to grey out say so in a
notification. Forking mid-turn is allowed but announced — agents fork from the
persisted transcript, so the turn in flight is absent from the copy — and the
parent is untouched either way.

Copy Session ID sits beside Copy Working Directory. Codex has no
copy-or-duplicate subcommand, so "copy the session" is the id: paste it into
`codex resume`, a bug report, or another tool.

Deliberately not built: any reading or writing of an agent's own session files.
tty7's exposure stays the public CLI contract plus the hook payload's session
id, so a change to Codex's rollout format or its version-numbered SQLite index
costs at most a visible shell error. Forked tabs also look exactly like their
parent, by decision — "Rename Tab" is the answer.

Closes #211

* no-mistakes(review): perf(terminal): compute fork menu enablement at menu-open time

* no-mistakes(document): docs: correct fork action surfaces, label, and remote limits

* fix(agents): label forking the same for every agent

The fork row said "Branch Session" on Claude Code and "Fork Session"
everywhere else, on the strength of a source comment claiming "Claude Code
calls it branching". It does not. `claude --help` documents the flag as
`--fork-session`, described as "When resuming, create a new session ID instead
of reusing the original"; the only occurrences of "branch" in its help are an
unrelated git-branch review option. The claim came from otty's own UI wording,
which I mistook for Claude's vocabulary and then wrote into the source as
fact — so the comment goes with the special case rather than being left behind
as a false statement about someone else's tool.

The split was also inconsistent with itself: Grok takes the identical
`--fork-session` flag and was already labelled "Fork Session". Every agent that
has the capability calls it forking — `codex fork`, `--fork-session` on Claude
Code and Grok, `--fork` on OpenCode — so one wording covers all four.

`fork_label` keeps returning `Option<&'static str>`: it is still the UI's single
capability gate (`None` = no verified fork command, no row offered), and
per-agent wording stays expressible should one ever genuinely diverge.

Generated commands are untouched — the existing table test still pins
`claude --resume <id> --fork-session` and the other three verbatim.

Also drops the two doc sentences that promised the per-agent label, and the
stale "Branch Session" mentions left in comments; no occurrence survives
anywhere in the tree.

* no-mistakes(review): fix(agents): fork the pane the tab menu row named

* no-mistakes(document): rewrap fork menu comment after label unification

* fix(agents): repoint Pi's token-gate comment after the rebase

Rebasing #211 onto #240 moved the session-id token gate out of
resume_command and into the shared session_command_flags helper, so
Pi's comment pointing at "the token gate above" no longer names
anything. Comment only; the gate itself is unchanged.

* no-mistakes(document): correct fork placement rationale in menus and changelog

* chore: untrack AGENTS.md per gitignore dev-tool convention

tty7 keeps agent-memory files out of the repo: `/CLAUDE.md` is already
ignored, and on disk it is a symlink to `AGENTS.md`, so tracking the
target defeated the convention. Ignore `/AGENTS.md` alongside it and drop
the tracked copy; the file stays on disk, where the notes belong.

---------

Co-authored-by: l0ng-ai <24760907+l0ng-ai@users.noreply.github.com>
2026-07-29 00:23:49 +08:00

9.7 KiB
Raw Permalink Blame History

Features

English · 简体中文

Input

  • Ghost suggestions — your history completes the whole line as you type; to accept
  • Explained tab completion — every flag and subcommand with its description, for ~100 common commands; when tty7 has nothing to offer the Tab falls through to your shell's own completion, and the whole feature can be turned off (Settings → Terminal → Keyboard, or tab_completion in config.json)
  • Syntax highlighting — as you type, nothing to install
  • Fuzzy history search⌃ R shows what you ran, where, and whether it failed; turn it off (Settings → Terminal → Keyboard, or history_search in config.json) and ⌃ R goes to your shell instead, so an fzf / percol binding keeps working
  • History from day one — your existing shell history works as-is and carries across sessions
  • Line editing — click to place the caret, mouse selection, word motion, undo
  • Multi-line editing — wrapped and multi-line commands edit in place; the grid shifts to keep the caret visible

In the window

  • Tabs & splits — always open in the current directory
  • Repo-grouped sidebar — the left tab sidebar groups rows under a header per git repository, non-repo tabs in a trailing Scratch section; branch switches and in-repo cds never move a row (sidebar_grouping in config.json: repo default, none for a flat list)
  • Command palette ⌘ P · scrollback search ⌘ F
  • ⌘/Ctrl-click links (⌘ on macOS, Ctrl on Windows/Linux) · desktop notifications · copy on select (opt-in, Settings → Terminal → Clipboard)
  • Smart double-click selection — double-click grabs the whole URL, file path, bracket/quote pair, or dictionary-segmented CJK word under the cursor; Shift-click extends a selection (toggle in Settings → Terminal → Mouse; word separators via word_separators in config.json)
  • Nine themes, plus your own — YAML seed themes with solid, gradient, or image backgrounds; iTerm2 .itermcolors import; in-app color editor with a background-image picker
  • Sync with system — Settings → Appearance; pick separate light and dark themes and tty7 follows the OS appearance live (theme_follow_system, theme_preset_light / theme_preset_dark in config.json)
  • Window opacity & blur — Settings → Appearance → Window; applies to every theme, Follow theme returns to the theme's own opacity / blur
  • CJK / IME input

Fonts

  • Hack is bundled — it ships inside the binary, so the default renders identically everywhere without relying on a system install
  • Primary + ordered fallbacksfont_family and font_fallbacks in config.json; optional font_family_bold / font_family_italic for distinct faces, and font_features to pass OpenType features through (contextual ligatures stay off unless you ask for them)
  • Platform-aware defaults — the fallback list names faces the host OS actually ships (PingFang SC / Apple Color Emoji on macOS, Microsoft YaHei / Segoe UI Emoji on Windows, Noto on Linux). Those stock names are appended to a hand-written list too, so a config.json written on another platform still resolves

CJK and the two-column grid

A cell is one advance of the primary face, and a wide (CJK) character is pinned to exactly two of them. A CJK fallback therefore sits flush in its slot only if its ideographs advance twice the primary's Latin advance.

Bundled Hack advances 0.60205em, so a two-column slot is 1.2041em — while every stock CJK face (Microsoft YaHei, PingFang SC, Noto Sans CJK) advances 1.0em. Those glyphs get left-aligned in the slot and the leftover ~0.2em lands as a gap on the right of every character.

Maple Mono NF CN is tried first on every platform for exactly this reason — 0.6em Latin, 1.2em CJK, an exact two-cell fit against Hack. It is referenced by name only, never bundled (~20MB per weight): install it and tty7 picks it up with no config change.

For CJK set tight rather than merely even, change the primary face instead — one that advances 0.5em (Sarasa Mono SC, say) makes two columns exactly 1.0em.

Coding agents

tty7 recognizes third-party coding agents running in a pane (Claude Code, Codex, Gemini CLI, Aider, Amp, OpenCode, and ~10 more) and adds around them — it never wraps or replaces the agent.

  • Brand avatars — the tab chip / sidebar row shows which agent runs where; custom wrappers map in via agent_commands in config.json
  • Status dot — working (blue) / needs your input (amber) / done (green), driven by agent-reported events over an OSC channel; Settings → Agents installs the hooks that feed it (Claude Code, Codex, Copilot CLI, OpenCode, Pi, Grok Build)
  • Notifications — "needs your permission…" the moment an agent blocks on you, and "finished after Ns" per turn, honoring your notification policy
  • Branch at a glance — each sidebar row shows its pane's git branch and working-tree diff (+N M), refreshed on cd and when a command finishes
  • Session resume — panes lost to a reboot re-launch their agent conversation on restore, carrying the original launch flags (claude --dangerously-skip-permissions --resume …) (restore_agent_sessions, on by default)
  • Fork session — branch a live agent conversation into a second, independent one by shelling the agent's own fork command (codex fork <id>, claude --resume <id> --fork-session, also OpenCode and Grok Build); the original is untouched and both continue separately. Right-click a pane to pick a split placement, or right-click the tab / sidebar row to open the fork in a new tab. Needs the agent's hooks installed, since the fork targets the session id they report; a remote pane can't fork, because the command would run against the local agent — and note a fork copies the whole transcript, so repeated forking costs real disk in the agent's own session store
  • Copy Session ID — put the agent's native session id on the clipboard, beside Copy Working Directory, for pasting into codex resume, a bug report, or another tool
  • Context feed — palette commands send the current selection or the repo's git diff to the running agent as a ready-made prompt
  • Tray icon — a system tray / menu bar item that flips to an attention state the moment any agent needs your input; its menu lists every agent pane (brand avatar + status dot, click to reveal), switches the notification policy, and offers Quit and Stop Daemon alongside the plain session-keeping quit (show_tray_icon, on by default)

SSH

A native Rust SSH stack (russh) is the only path — profiles, credentials, and SFTP without shelling out to ssh. There is no system-ssh compat mode.

  • QuickConnect — type user@host[:port] in the palette and connect; IPv6 [::1]:port supported
  • Saved profiles — full connection config with passwords / passphrases in the OS keychain, never on disk
  • ~/.ssh/config aliases — type one to connect (resolved natively — common fields, best-effort — over russh), or import them as profiles in Settings
  • GUI auth — in-pane sheets for password, key passphrase, 2FA, and host-key confirmation (new vs. changed)
  • Built-in SFTP — a slide-in file panel: browse, upload / download, rename / delete / chmod, drag to Finder
  • Port forwarding — Local / Remote / Dynamic, preconfigured or added live, plus ⌘/Ctrl-click localhost:PORT to auto-forward
  • Jump hosts & proxies — multi-hop via profile references or ProxyJump, ProxyCommand, SOCKS5 / HTTP
Entry point Connects via
Saved profiles · QuickConnect · typed user@host[:port] Native russh — SFTP · keychain · GUI auth · L/R/D forwards
~/.ssh/config aliases Resolved natively, then russh (Match/canonicalize/GSSAPI unsupported — no fallback)

Keybindings

Keys are shown in macOS notation — on Windows and Linux, read as Ctrl. The essentials:

⌘ T · ⌘ W · ⌘ ⇧ T new tab · close tab · reopen closed tab
⌘ 1⌘ 9 · ⌃ ⇥ · ⌃ ⇧ ⇥ jump to tab 19 · next tab · previous tab
⌘ D · ⌘ ⇧ D split right · split down
⌘ ] · ⌘ [ next pane · previous pane
⌘ ⌥ ←→↑↓ focus the pane in that direction
⌘ ⏎ · ⌘ ⇧ ⏎ toggle fullscreen · maximize / restore the pane
⌘ K clear the screen and scrollback
⌘ P command palette
⌘ F search the scrollback
⌃ R fuzzy-search shell history
⌘ + · · ⌘ 0 font size up · down · reset

Settings → Keybindings (⌘ ,) lists every shortcut. Click one, press the new keys (Esc cancels, Backspace resets to default), and it takes effect immediately. Pane resize and swap have no default keys — bind them here or run them from the command palette.

tmux preset — remaps pane/tab actions onto a prefix (default ⌃ B): ⌃ B C opens a tab, ⌃ B % splits, ⌃ B then an arrow moves focus. A bare prefix reaches the shell after a brief pause; prefix + an unbound key passes straight through.

Performance notes

  • The PTY is read at device speed and parsed in large batches, off the render path
  • Hot paths are lock-free — a big cat never waits on drawing
  • The daemon buffers up to 16 MiB ahead of the window before backpressure applies