Review fixes:
- Hold back a trailing partial CSI (digit-only intro, ≤7 chars) in
_handleTerminalOutput and prepend it to the next chunk. PTY chunk
boundaries are arbitrary, so '\x1b[?1049h' can arrive as '\x1b[?104' +
'9h' — the per-chunk strip misses it, xterm obeys the reassembled toggle,
and (with the matching ?1049l stripped) stays stuck in the scrollback-less
alt buffer until the next replay. Complete sequences are never held; the
carry resets with the other buffers in _resetBuffers.
- Replay path now also strips mouse-tracking enables (?1000-?1007), matching
the live strip: buffers persisted BEFORE the live strip existed can still
carry them, and a replayed ?1006h re-hijacks the scroll wheel.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Codex's TUI emits alternate-screen toggles (DECSET/DECRST 47/1047/1049),
scrollback-erase (CSI 3 J), and mouse-tracking enables (?1000-1007) during
startup and on every repaint. xterm.js obeys them: it switches to the
scrollback-less alternate buffer, wipes saved lines, and forwards the scroll
wheel to codex — so the user's conversation history both disappears and
becomes unreachable on each tab switch / pane refresh.
Strip these sequences in two places, leaving the visible-viewport erases
(2J / J) intact so codex can still repaint its own rows:
- Session._handleTerminalOutput: filter the live SSE/WS stream and the
persisted terminal buffer at the source, for mode === 'codex'.
- GET /api/sessions/:id/terminal: apply the same strip to the replayed
buffer (ALT_SCREEN_TOGGLE_PATTERN / ERASE_SCROLLBACK_PATTERN) so a
tab-switch replay keeps full scrollback.
Adds test/codex-terminal-output.test.ts covering the strip (alt-screen and
3J removed, 2J/J preserved, Ctrl+L redraws preserved) and confirming codex
output passes through without Ink row-repair mangling.
Co-Authored-By: Saqeb Akhter <saqeb.akhter@gmail.com>