fix(pi-tui): drop capability-probe replies before they reach the editor

This commit is contained in:
liruifengv 2026-06-30 22:36:13 +08:00
parent d7c822f707
commit 8262da57be
2 changed files with 53 additions and 4 deletions

View file

@ -25,7 +25,13 @@ const KITTY_KEYBOARD_PROTOCOL_PUSH = `\x1b[>${DESIRED_KITTY_KEYBOARD_PROTOCOL_FL
export type KeyboardProtocolNegotiationSequence =
| { type: "kitty-flags"; flags: number }
| { type: "device-attributes" };
| { type: "device-attributes" }
// Capability-probe replies (DECRPM private-mode report, OSC 11 background
// color) observed by the startup probe's stdin tap. They are never user
// input and must be consumed at this gate so they cannot leak into the
// editor (plan goal: 探测字节不漏进编辑器).
| { type: "decrpm" }
| { type: "osc11" };
export function parseKeyboardProtocolNegotiationSequence(
sequence: string,
@ -37,11 +43,24 @@ export function parseKeyboardProtocolNegotiationSequence(
if (/^\x1b\[\?[\d;]*c$/.test(sequence)) {
return { type: "device-attributes" };
}
// DECRPM private-mode report: ESC [ ? <mode> ; <status> $ y
if (/^\x1b\[\?\d+;\d+\$y$/.test(sequence)) {
return { type: "decrpm" };
}
// OSC 11 background-color reply: ESC ] 11 ; <payload> (BEL | ST)
if (/^\x1b\]11;.*(?:\x07|\x1b\\)$/.test(sequence)) {
return { type: "osc11" };
}
return undefined;
}
function isKeyboardProtocolNegotiationSequencePrefix(sequence: string): boolean {
return sequence === "\x1b[" || /^\x1b\[\?[\d;]*$/.test(sequence);
// CSI ? ... — kitty flags / DA1 / DECRPM partials. Allows a trailing `$`
// (DECRPM's intermediate byte) so a split `\x1b[?2026;2$` + `y` reassembles.
if (sequence === "\x1b[" || /^\x1b\[\?[\d;]*\$?$/.test(sequence)) return true;
// OSC 11 ... — background-color reply partial (terminated by BEL / ST).
if (/^\x1b\](?:1(?:1(?:;[^\x07]*)?)?)?$/.test(sequence)) return true;
return false;
}
export function isAppleTerminalSession(): boolean {
@ -338,9 +357,16 @@ export class ProcessTerminal implements Terminal {
return true;
}
if (!this._kittyProtocolActive) {
this.enableModifyOtherKeys();
if (negotiationSequence.type === "device-attributes") {
if (!this._kittyProtocolActive) {
this.enableModifyOtherKeys();
}
return true;
}
// DECRPM / OSC 11 capability-probe replies: observed by the probe's
// stdin tap and never legitimate user input. Consume them here so they
// cannot leak into the editor.
return true;
}

View file

@ -172,6 +172,29 @@ describe("ProcessTerminal Kitty keyboard protocol negotiation", () => {
mock.timers.reset();
}
});
it("drops capability-probe replies (DECRPM / OSC 11) before they reach the editor", () => {
const harness = setupNegotiation();
try {
// DECRPM private-mode report (DECRQM ?2026 synchronized output reply).
harness.send("\x1b[?2026;2$y");
assert.equal(harness.getInput(), undefined);
// OSC 11 background-color reply, BEL-terminated.
harness.send("\x1b]11;rgb:1e1e/1e1e/2e2e\x07");
assert.equal(harness.getInput(), undefined);
// OSC 11 background-color reply, ST-terminated.
harness.send("\x1b]11;rgb:1e1e/1e1e/2e2e\x1b\\");
assert.equal(harness.getInput(), undefined);
// A normal keypress must still be delivered.
harness.send("a");
assert.equal(harness.getInput(), "a");
} finally {
harness.cleanup();
}
});
});
describe("ProcessTerminal start", () => {