fix: prevent iTerm2 terminal response sequences from leaking into REPL input (#172)

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 <noreply@anthropic.com>
This commit is contained in:
guunergooner
2026-04-07 15:11:06 +08:00
committed by GitHub
parent 88d4c3ba24
commit 4b44047931

View File

@@ -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 [ ... <final_byte 0x40-0x7E>
// 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 <byte 0x40-0x7E> — just skip the one byte
if (i < str.length) i++
continue
}