From 4b44047931a8f877d246d916e4cba51e3a496118 Mon Sep 17 00:00:00 2001 From: guunergooner Date: Tue, 7 Apr 2026 15:11:06 +0800 Subject: [PATCH] fix: prevent iTerm2 terminal response sequences from leaking into REPL input (#172) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The earlyInput capture's escape sequence detection was too simplistic — it only checked if the byte after ESC fell in 0x40-0x7E range, treating it as a terminator. This caused DCS sequences (e.g. XTVERSION `\x1bP>|iTerm2 3.6.4\x1b\\`) and CSI parameter sequences (e.g. DA1 `\x1b[?64;...c`) to partially leak into the input buffer as `>|iTerm2 3.6.4?64;1;2;4;6;17;18;21;22c`. Fix by handling each escape sequence type per ECMA-48: - CSI (`ESC [`): skip parameter + intermediate bytes, then final byte - DCS/OSC/SOS/PM (`ESC P/]/X/^`): scan to BEL or ST terminator - Other: keep single-byte skip Closes #171 Co-authored-by: Claude Opus 4.6 --- src/utils/earlyInput.ts | 42 +++++++++++++++++++++++++++++++++-------- 1 file changed, 34 insertions(+), 8 deletions(-) diff --git a/src/utils/earlyInput.ts b/src/utils/earlyInput.ts index 734dad4f4..a5d58db5e 100644 --- a/src/utils/earlyInput.ts +++ b/src/utils/earlyInput.ts @@ -102,17 +102,43 @@ function processChunk(str: string): void { } // Skip escape sequences (arrow keys, function keys, focus events, etc.) - // All escape sequences start with ESC (0x1B) and end with a byte in 0x40-0x7E + // All escape sequences start with ESC (0x1B). if (code === 27) { i++ // Skip the ESC character - // Skip until the terminating byte (@ to ~) or end of string - while ( - i < str.length && - !(str.charCodeAt(i) >= 64 && str.charCodeAt(i) <= 126) - ) { - i++ + if (i >= str.length) continue + + const next = str.charCodeAt(i)! + + // CSI sequences: ESC [ ... + // e.g. \x1b[?64;1;2;4;6;17;18;21;22c (DA1 response) + if (next === 0x5b /* [ */) { + i++ // skip '[' + // Skip parameter bytes (0x30-0x3F) and intermediate bytes (0x20-0x2F) + while (i < str.length && str.charCodeAt(i)! >= 0x20 && str.charCodeAt(i)! <= 0x3f) { + i++ + } + // Skip the final byte (0x40-0x7E) + if (i < str.length && str.charCodeAt(i)! >= 0x40 && str.charCodeAt(i)! <= 0x7e) i++ + continue } - if (i < str.length) i++ // Skip the terminating byte + + // String sequences: DCS (P), OSC (]), SOS (X), PM (^) + // These end with BEL (0x07) or ST (ESC \) + if (next === 0x50 /* P */ || next === 0x5d /* ] */ || next === 0x58 /* X */ || next === 0x5e /* ^ */) { + i++ // skip the introducer + while (i < str.length) { + if (str.charCodeAt(i) === 0x07) { i++; break } // BEL terminates + if (str.charCodeAt(i) === 0x1b && i + 1 < str.length && str.charCodeAt(i + 1)! === 0x5c) { + i += 2; break // ESC \ (ST) terminates + } + i++ + } + continue + } + + // SS2 (N), SS3 (O) — 2-byte sequences, just skip both + // Other simple escape sequences: ESC — just skip the one byte + if (i < str.length) i++ continue }