fix(terminal): keep the byte-recording load for a shell in the alternate screen, report a lines cut only below the ceiling

Review follow-ups for loading a shell from tmux's rendered history.

tmux adds no history while a pane is in the alternate screen, and a
nested tmux client (remote SSH and Docker cases, or tmux run by hand)
holds its pane there, so a full capture of it is one screen. The
display-message query that reads #{history_size} now also reads
#{alternate_on} (PaneCaptureOptions.capturedAlternateOn), and a request
carrying `alt=recording` (only SHELL_LOAD_QUERY sends it) is answered
the old way on such a pane: the visible frame over the tailed byte
recording. Agents, scroll pulls and Load full history are unchanged.
alternate_on cannot tell a nested tmux from vim or less, so a local
shell with one of those open takes the same fallback while it is open.

A full capture without `lines=` was reported as a 'tail' cut whenever
the pane held more than the configured ceiling (possible on tmux before
3.7), offering a Load full history that could never return more. The
cut is now reported only for a client bound below the ceiling.

The response carries `historyLinesCut` when that bound left lines in
tmux, and the banner then says earlier output may be retained instead
of naming the bytes a later tail cut, which undercount what tmux holds.
A bounded grid tile's shell load sends its own `lines=` (via
SHELL_LOAD_BASE_QUERY). CLAUDE.md, docs/architecture-invariants.md and
docs/api-reference.md describe the shell load, the cut report and the
fallback.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
timkjrandClaude Opus 5.5 committed 2026-10-10 23:29:19 -05:00
1 parent b620e42ffa
commit 063e6fc076
15 files changed
+226 -41

No files matched your search

+5 -3
View File
@@ -543,15 +543,17 @@ client that opens many concurrent waits against one session will still hit the c
What a session's terminal shows, for a client to replay: `data.terminalBuffer`,
with `source` (`mux-visible`, `mux-full-history` or `history`), `truncated`,
`truncationReason`, `fullSize`, and `captureCols`/`captureRows` when the pane's
geometry was read. The capture runs synchronous tmux calls on the server; the
`truncationReason`, `fullSize`, `historyLinesCut` (a `lines=` bound left older
lines in tmux, so the remainder is more than `fullSize - retainedBytes`), and
`captureCols`/`captureRows` when the pane's geometry was read. The capture runs synchronous tmux calls on the server; the
`Server-Timing` header reports `capture`, `prepare` and `total`.
| Query | Meaning |
|---|---|
| `full=1` | tmux's scrollback, not only the visible frame (`source: 'mux-full-history'`), ending with a relative cursor move back to the pane's caret. |
| `tail=<bytes>` | Keep the newest `<bytes>` of the result (`truncationReason: 'tail'` when it cut). |
| `lines=<n>` | With `full=1` only: read at most `<n>` lines of tmux history above the visible frame. An integer of at least 1, clamped to the configured history limit; absent or malformed, the whole limit (100,000 lines by default), as before. When the bound left older lines in tmux (its `history_size` is deeper), `truncated` is true with `truncationReason: 'tail'`; a byte cap still reports `'capped'`. Without it a full capture reads all of that history before `tail` cuts it, so a client that keeps a fixed number of lines (the tile grid sends its xterm's scrollback plus its rows) should send it. |
| `lines=<n>` | With `full=1` only: read at most `<n>` lines of tmux history above the visible frame. An integer of at least 1, clamped to the configured history limit; absent or malformed, the whole limit (100,000 lines by default), as before. When a bound below the configured limit left older lines in tmux (its `history_size` is deeper), `truncated` is true with `truncationReason: 'tail'`; a byte cap still reports `'capped'`. Without it a full capture reads all of that history before `tail` cuts it, so a client that keeps a fixed number of lines (the tile grid sends its xterm's scrollback plus its rows) should send it. |
| `alt=recording` | With `full=1` only: if the pane is in the alternate screen (a nested tmux, where tmux keeps no history, or any full-screen app such as vim or less while it is open), answer with the visible frame over the byte recording instead (`source: 'mux-visible'`, `tail` applied as without `full=1`). Shells' automatic loads send it. |
## The `codeman agent` CLI (client over these endpoints)