038ed94a6c
When the TUI exits via Ctrl+C, SIGTERM/SIGHUP, or a crash, prompt_toolkit's teardown can be bypassed, leaving DEC 1004 (focus reporting) and 1000/1002/1003 (mouse tracking) enabled. The terminal then emits raw ESC[I/ESC[O focus events and fragmented SGR mouse reports as visible text in whatever runs next in the same tab. _run_cleanup() — the once-only cleanup that runs on every catchable exit path (atexit-registered + called on the normal/EOF/interrupt exit) — now emits _TERMINAL_INPUT_MODE_RESET_SEQ (the same disable sequence the in-session leak recovery already uses) as its FIRST step, so the terminal is usable immediately on Ctrl+C and a later teardown step raising can't skip it. The reset is gated on a new _tui_input_modes_active flag (set right before app.run(), cleared once the modes are disabled) so non-TUI one-shot CLI runs — which share _run_cleanup via atexit — don't emit codes for modes they never enabled. Writes to sys.stdout when it's the terminal, else falls back to /dev/tty. SIGKILL is uncatchable and the kanban worker's os._exit(0) bypasses atexit, but both are non-TTY/non-TUI so there is nothing to reset there. Adds tests/cli/test_tui_terminal_reset_on_exit.py (9): emits on a TTY when the TUI ran, no-ops when the TUI never ran, /dev/tty fallback when stdout is redirected, no-op when neither is available, swallows stdout errors, flag set and cleared, and wired into _run_cleanup as the first step even when a later step raises. Fixes #36823 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>