From 45d16dc0d837d269003427918e9a667644b416fc Mon Sep 17 00:00:00 2001 From: wismyzhizi2018 Date: Sat, 27 Jun 2026 17:41:41 +0800 Subject: [PATCH] fix(tui): remove ?1003h mouse tracking to prevent escape sequence leak into input ?1003h (any-event tracking) sends SGR mouse events on every pixel movement. During agent rendering pauses, the ConPTY input buffer overflows, causing raw VT bytes to leak as Key(Char) events into the textarea. The TUI never handles MouseEventKind::Move (hover), so ?1003h is unused. ?1000h (click/release/scroll) + ?1002h (drag) cover all used events. Co-Authored-By: mimo-v2.5-pro --- peri-tui/src/conpty.rs | 11 ++-- ...-mouse-escape-sequence-leaks-into-input.md | 63 +++++++++++++++++++ 2 files changed, 69 insertions(+), 5 deletions(-) create mode 100644 spec/issues/2026-06-27-mouse-escape-sequence-leaks-into-input.md diff --git a/peri-tui/src/conpty.rs b/peri-tui/src/conpty.rs index 5ba98d38..25015439 100644 --- a/peri-tui/src/conpty.rs +++ b/peri-tui/src/conpty.rs @@ -105,8 +105,10 @@ pub const ENABLE_MOUSE_TRACKING_SEQUENCE: &str = concat!( "\x1b[?1000h", // Button-event tracking: drag events. "\x1b[?1002h", - // Any-event tracking: hover/drag parity with crossterm's ANSI path. - "\x1b[?1003h", + // NOTE: ?1003h (any-event tracking / hover) intentionally omitted. + // It causes a mouse-event flood during agent execution pauses, overflowing + // the ConPTY input buffer and leaking raw SGR bytes as Key(Char) events + // into the textarea. ?1002h already covers drag; hover is unused by the TUI. // RXVT coordinate mode. "\x1b[?1015h", // SGR coordinate mode. @@ -116,7 +118,6 @@ pub const ENABLE_MOUSE_TRACKING_SEQUENCE: &str = concat!( pub const DISABLE_MOUSE_TRACKING_SEQUENCE: &str = concat!( "\x1b[?1006l", "\x1b[?1015l", - "\x1b[?1003l", "\x1b[?1002l", "\x1b[?1000l", ); @@ -226,7 +227,7 @@ mod tests { fn enable_sequence_enables_mouse_modes() { assert_eq!( ENABLE_MOUSE_TRACKING_SEQUENCE, - "\x1b[?1000h\x1b[?1002h\x1b[?1003h\x1b[?1015h\x1b[?1006h" + "\x1b[?1000h\x1b[?1002h\x1b[?1015h\x1b[?1006h" ); } @@ -234,7 +235,7 @@ mod tests { fn disable_sequence_reverses_mouse_modes() { assert_eq!( DISABLE_MOUSE_TRACKING_SEQUENCE, - "\x1b[?1006l\x1b[?1015l\x1b[?1003l\x1b[?1002l\x1b[?1000l" + "\x1b[?1006l\x1b[?1015l\x1b[?1002l\x1b[?1000l" ); } diff --git a/spec/issues/2026-06-27-mouse-escape-sequence-leaks-into-input.md b/spec/issues/2026-06-27-mouse-escape-sequence-leaks-into-input.md new file mode 100644 index 00000000..6e164d5d --- /dev/null +++ b/spec/issues/2026-06-27-mouse-escape-sequence-leaks-into-input.md @@ -0,0 +1,63 @@ +# ConPTY 下鼠标转义序列泄漏为输入框乱码 + +## 状态 +- [ ] Open + +## 分类 +- **类型:** Bug +- **严重程度:** P1 — 用户可见功能损坏 +- **模块:** peri-tui / event / conpty + +## 描述 + +TUI 运行一段时间后,输入框出现大量乱码,形如: +``` +66M[<32;27;66M[<32;28;66M[<32;29;66M[<32;30;... +``` + +这些是 SGR 鼠标转义序列的碎片,本应由 ConPTY 翻译为 `MOUSE_EVENT_RECORD`,但因缓冲区溢出导致翻译失败,原始 VT 字节以 `KEY_EVENT_RECORD` 形式进入输入队列,被 crossterm 解析为 `Event::Key(Char(...))` 事件,最终通过 `detect_simulated_paste()` 收集为 paste blob 插入 textarea。 + +## 根因 + +`conpty.rs` 中 `ENABLE_MOUSE_TRACKING_SEQUENCE` 启用了 `\x1b[?1003h`(any-event tracking),导致终端对**每一次鼠标像素移动**都发送 SGR 事件。Agent 执行期间 TUI 忙于渲染(100ms+),事件在 Console 输入缓冲区积压。缓冲区溢出时 ConPTY 无法将 SGR 序列翻译为结构化事件,原始字节泄漏。 + +TUI 代码**从未处理** `MouseEventKind::Move`(hover),`?1003h` 完全多余。`?1000h`(click/release/scroll)+ `?1002h`(drag)已覆盖所有使用场景。 + +## 复现条件(多因素交集,因此偶发) + +1. `?1003h` 启用 → 鼠标移动产生连续事件流 +2. Agent 流式输出期间 TUI 渲染暂停 100ms+ +3. ConPTY 翻译失败(缓冲区接近满时概率性触发) +4. 泄漏字节在 `detect_simulated_paste` 的 1ms+15ms 窗口内连续到达 + +## 修复方案 + +### 主修复:移除 `?1003h` + +从 `ENABLE_MOUSE_TRACKING_SEQUENCE` 和 `DISABLE_MOUSE_TRACKING_SEQUENCE` 中移除 `\x1b[?1003h` / `\x1b[?1003l`。 + +- 事件量从每秒数百(motion)降到个位数(仅 click/drag/scroll) +- 缓冲区溢出概率降至接近零 +- TUI 功能零退化(无 hover 处理代码) + +### 兜底(可选后续):速率过滤 + +在 `next_event()` 中检测 `Key(Char)` 事件速率,超过阈值(如 10ms 内 50+ 个 Char 事件)时批量丢弃,防止其他未知路径的泄漏。 + +## 涉及文件 + +| 文件 | 修改 | +|------|------| +| `peri-tui/src/conpty.rs` | 移除 `?1003h`/`?1003l`,更新注释和测试 | + +## 泄漏字节样本 + +``` +66M[<32;27;66M[<32;28;66M[<32;29;66M[<32;30;66M[<32;31; +66M[<32;32;66M[<32;33;66M[<32;34;66M[<32;35;66M[<32;36; +66M[<32;37;66M[<32;38;66M[<32;40;66M[<32;41;66M[<32;42; +66M[<32;43;66M[<32;44;66M[<32;45;66M[<32;46;66M[<32;47; +66M[<32;48;66M[<32;49;66M[<32;50;66M[<32;51; +``` + +拆解:`<32;` = SGR motion 事件,`66` = 列号(不变),`27→51` = 行号递增(鼠标垂直移动 24 像素),`M` = 终止符。