mirror of
https://github.com/stablyai/orca.git
synced 2026-10-07 16:02:29 +00:00
Reset terminal SGR state around restored output (#14700)
* fix(terminal): reset SGR around restored output * fix(terminal): preserve live replay styling * fix(terminal): ground dead reattach fallback
This commit is contained in:
@@ -7,6 +7,7 @@ import {
|
||||
POST_REPLAY_MODE_RESET,
|
||||
POST_REPLAY_REATTACH_RESET,
|
||||
POST_REPLAY_REATTACH_RESET_KEEP_MOUSE,
|
||||
RESET_GRAPHIC_RENDITION,
|
||||
RESET_MOUSE_REPORTING,
|
||||
buildPostReplayLiveAgentReattachReset,
|
||||
replayPayloadEndsWithCursorHidden
|
||||
@@ -23,14 +24,15 @@ describe('terminal mode reset profiles', () => {
|
||||
|
||||
it('pins the fresh-shell profile', () => {
|
||||
expect(POST_REPLAY_MODE_RESET).toBe(
|
||||
'\x1b[0 q\x1b[<99u\x1b[=0u\x1b[?25h\x1b[?9l\x1b[?1000l\x1b[?1002l\x1b[?1003l\x1b[?1006l\x1b[?1016l\x1b[?1004l\x1b[?2004l'
|
||||
'\x1b[0m\x1b[0 q\x1b[<99u\x1b[=0u\x1b[?25h\x1b[?9l\x1b[?1000l\x1b[?1002l\x1b[?1003l\x1b[?1006l\x1b[?1016l\x1b[?1004l\x1b[?2004l\x1b7'
|
||||
)
|
||||
})
|
||||
|
||||
it('pins the daemon-reattach profile, which keeps bracketed paste', () => {
|
||||
expect(POST_REPLAY_REATTACH_RESET).toBe(
|
||||
'\x1b[0 q\x1b[<99u\x1b[=0u\x1b[?25h\x1b[?9l\x1b[?1000l\x1b[?1002l\x1b[?1003l\x1b[?1006l\x1b[?1016l\x1b[?1004l'
|
||||
'\x1b[0m\x1b[0 q\x1b[<99u\x1b[=0u\x1b[?25h\x1b[?9l\x1b[?1000l\x1b[?1002l\x1b[?1003l\x1b[?1006l\x1b[?1016l\x1b[?1004l\x1b7'
|
||||
)
|
||||
expect(POST_REPLAY_REATTACH_RESET).toContain(RESET_GRAPHIC_RENDITION)
|
||||
expect(POST_REPLAY_REATTACH_RESET).not.toContain('\x1b[?2004l')
|
||||
})
|
||||
|
||||
@@ -46,8 +48,8 @@ describe('terminal mode reset profiles', () => {
|
||||
})
|
||||
|
||||
// Why: #12101 — a cold-restored seed re-arms mouse reporting for a dead TUI.
|
||||
it('disarms mouse reporting on the cold-restore seed', () => {
|
||||
expect(COLD_RESTORE_SEED_MODE_RESET).toBe(RESET_MOUSE_REPORTING)
|
||||
it('clears the pen and disarms mouse reporting on the cold-restore seed', () => {
|
||||
expect(COLD_RESTORE_SEED_MODE_RESET).toBe(`${RESET_GRAPHIC_RENDITION}${RESET_MOUSE_REPORTING}`)
|
||||
})
|
||||
|
||||
// Why: the seed also feeds the daemon emulator and is re-serialized from it, so
|
||||
@@ -73,6 +75,7 @@ describe('terminal mode reset profiles', () => {
|
||||
POST_REPLAY_LIVE_AGENT_SNAPSHOT_RESET,
|
||||
POST_REPLAY_LIVE_SNAPSHOT_RESET
|
||||
]) {
|
||||
expect(profile).not.toContain(RESET_GRAPHIC_RENDITION)
|
||||
expect(profile).not.toContain('\x1b[?1000l')
|
||||
expect(profile).not.toContain('\x1b[?2004l')
|
||||
}
|
||||
|
||||
@@ -6,17 +6,22 @@
|
||||
// Why: SerializeAddon replays mode bits assuming reattach to a live TUI, but Orca restores against a fresh shell with none, so stale bits (e.g. focus reporting rings the bell on click) must be reset.
|
||||
export const RESET_TERMINAL_CURSOR_STYLE = '\x1b[0 q'
|
||||
export const RESET_KITTY_KEYBOARD_PROTOCOL = '\x1b[<99u\x1b[=0u'
|
||||
// Why: abandoned byte-gap replay drains live chunks, so a dropped intensity reset must not style them (STA-4042).
|
||||
export const RESET_GRAPHIC_RENDITION = '\x1b[0m'
|
||||
// Last so a dead process cannot leave stale attributes in the DECSC register.
|
||||
const SAVE_GROUNDED_CURSOR = '\x1b7'
|
||||
// Every mouse mode the daemon can re-arm from a snapshot: protocols 9/1000/1002/1003 + SGR encodings 1006/1016.
|
||||
export const RESET_MOUSE_REPORTING =
|
||||
'\x1b[?9l\x1b[?1000l\x1b[?1002l\x1b[?1003l\x1b[?1006l\x1b[?1016l'
|
||||
|
||||
export const POST_REPLAY_MODE_RESET = `${RESET_TERMINAL_CURSOR_STYLE}${RESET_KITTY_KEYBOARD_PROTOCOL}\x1b[?25h${RESET_MOUSE_REPORTING}\x1b[?1004l\x1b[?2004l`
|
||||
// Why: serialized panes can end with a live pen, but the following shell assumes default attributes.
|
||||
export const POST_REPLAY_MODE_RESET = `${RESET_GRAPHIC_RENDITION}${RESET_TERMINAL_CURSOR_STYLE}${RESET_KITTY_KEYBOARD_PROTOCOL}\x1b[?25h${RESET_MOUSE_REPORTING}\x1b[?1004l\x1b[?2004l${SAVE_GROUNDED_CURSOR}`
|
||||
|
||||
// Why: same-session live replay; keep cursor/focus cleanup but preserve Kitty flags the running TUI relies on.
|
||||
export const POST_REPLAY_LIVE_SNAPSHOT_RESET = `${RESET_TERMINAL_CURSOR_STYLE}\x1b[?25h\x1b[?1004l`
|
||||
|
||||
// Why: daemon reattach hits a live session, so skip the full reset; still clear cursor/focus/mouse/Kitty bits harmful to a plain shell after a bad TUI exit — safe for live TUIs since the post-reattach SIGWINCH repaints the cursor.
|
||||
export const POST_REPLAY_REATTACH_RESET = `${RESET_TERMINAL_CURSOR_STYLE}${RESET_KITTY_KEYBOARD_PROTOCOL}\x1b[?25h${RESET_MOUSE_REPORTING}\x1b[?1004l`
|
||||
// Why: the normal-buffer fallback can follow a dead TUI, so its stale pen and saved pen must not reach the surviving shell.
|
||||
export const POST_REPLAY_REATTACH_RESET = `${RESET_GRAPHIC_RENDITION}${RESET_TERMINAL_CURSOR_STYLE}${RESET_KITTY_KEYBOARD_PROTOCOL}\x1b[?25h${RESET_MOUSE_REPORTING}\x1b[?1004l${SAVE_GROUNDED_CURSOR}`
|
||||
|
||||
// Why: an alt-screen reattach replays the daemon's rehydrateSequences, which re-arm the live TUI's
|
||||
// mouse modes; wiping them one write later hands drags back to xterm's row selection (#8291).
|
||||
@@ -29,16 +34,8 @@ export const POST_REPLAY_LIVE_AGENT_REATTACH_RESET = `${RESET_TERMINAL_CURSOR_ST
|
||||
// Why: a live agent owns cursor/focus here; forcing ?25h/?1004l breaks a parked agent that only arms ?1004h at startup.
|
||||
export const POST_REPLAY_LIVE_AGENT_SNAPSHOT_RESET = RESET_TERMINAL_CURSOR_STYLE
|
||||
|
||||
/** Dead-TUI bytes feed a fresh shell; clear mouse modes here and renderer-owned modes later. */
|
||||
export const COLD_RESTORE_SEED_MODE_RESET = RESET_MOUSE_REPORTING
|
||||
|
||||
// Why separate from every profile above: those clear DEC *mode* bits and none of
|
||||
// them touches SGR. A recovery path that declares bytes unrecoverable has by
|
||||
// definition lost whatever turned the pen on, so the pen must be cleared too —
|
||||
// otherwise a dropped `ESC[22m` leaves bold applied to everything written after
|
||||
// (STA-4042: hidden-delivery gate drops the reset, the abandoned restore then
|
||||
// drains queued foreground chunks under the stale pen).
|
||||
export const RESET_GRAPHIC_RENDITION = '\x1b[0m'
|
||||
/** Dead-TUI bytes feed a fresh shell; clear their pen and mouse modes before re-serialization. */
|
||||
export const COLD_RESTORE_SEED_MODE_RESET = `${RESET_GRAPHIC_RENDITION}${RESET_MOUSE_REPORTING}`
|
||||
|
||||
// CAN, not a bare ESC: xterm dispatches OSC/DCS/APC with
|
||||
// `success = code !== 0x18 && code !== 0x1a`, so ESC grounds the parser but
|
||||
@@ -72,8 +69,6 @@ const REPLAY_BASELINE_BUFFER_RESET = '\x1b[r'
|
||||
// Last, so the saved-cursor register holds grounded state — otherwise a stranded
|
||||
// `ESC 7` is reachable through the live TUI's next `ESC 8`. Only a floor: a
|
||||
// snapshot carrying the model's own DECSC epilogue overwrites it.
|
||||
const SAVE_GROUNDED_CURSOR = '\x1b7'
|
||||
|
||||
/**
|
||||
* Prologue that puts a pane on `targetAlternateScreen` and grounds it for a
|
||||
* serialized snapshot. Shared because the parity/fuzz harnesses replay the same
|
||||
|
||||
Reference in New Issue
Block a user