mirror of
https://github.com/getcompanion-ai/co-mono.git
synced 2026-04-17 05:00:16 +00:00
fix(tui): drain Kitty key release events before exit to prevent SSH leak
Adds Terminal.prepareForExit() to disable Kitty protocol and wait for in-flight release events before fully stopping. This prevents escape sequences from leaking to the parent shell over slow SSH connections. Fixes #1204
This commit is contained in:
parent
c9a20a3aa4
commit
9a4d043b28
5 changed files with 43 additions and 1 deletions
|
|
@ -2,6 +2,10 @@
|
||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Fixed Kitty key release events leaking to parent shell over slow SSH connections ([#1204](https://github.com/badlogic/pi-mono/issues/1204))
|
||||||
|
|
||||||
## [0.51.1] - 2026-02-02
|
## [0.51.1] - 2026-02-02
|
||||||
|
|
||||||
### New Features
|
### New Features
|
||||||
|
|
|
||||||
|
|
@ -2552,6 +2552,10 @@ export class InteractiveMode {
|
||||||
// requestRender() uses process.nextTick(), so we wait one tick
|
// requestRender() uses process.nextTick(), so we wait one tick
|
||||||
await new Promise((resolve) => process.nextTick(resolve));
|
await new Promise((resolve) => process.nextTick(resolve));
|
||||||
|
|
||||||
|
// Drain any in-flight Kitty key release events before stopping.
|
||||||
|
// This prevents escape sequences from leaking to the parent shell over slow SSH.
|
||||||
|
await this.ui.terminal.prepareForExit();
|
||||||
|
|
||||||
this.stop();
|
this.stop();
|
||||||
process.exit(0);
|
process.exit(0);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,14 @@
|
||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Added `Terminal.prepareForExit()` method to drain Kitty key release events before exit
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Fixed Kitty key release events leaking to parent shell over slow SSH connections ([#1204](https://github.com/badlogic/pi-mono/issues/1204))
|
||||||
|
|
||||||
## [0.51.1] - 2026-02-02
|
## [0.51.1] - 2026-02-02
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,14 @@ export interface Terminal {
|
||||||
// Stop the terminal and restore state
|
// Stop the terminal and restore state
|
||||||
stop(): void;
|
stop(): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prepare for process exit by disabling Kitty protocol and draining stdin.
|
||||||
|
* Call this before stop() when exiting to prevent Kitty key release events
|
||||||
|
* from leaking to the parent shell over slow SSH connections.
|
||||||
|
* @param drainMs - How long to drain stdin (default: 50ms)
|
||||||
|
*/
|
||||||
|
prepareForExit(drainMs?: number): Promise<void>;
|
||||||
|
|
||||||
// Write output to terminal
|
// Write output to terminal
|
||||||
write(data: string): void;
|
write(data: string): void;
|
||||||
|
|
||||||
|
|
@ -150,11 +158,25 @@ export class ProcessTerminal implements Terminal {
|
||||||
process.stdout.write("\x1b[?u");
|
process.stdout.write("\x1b[?u");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async prepareForExit(drainMs = 50): Promise<void> {
|
||||||
|
if (!this._kittyProtocolActive) return;
|
||||||
|
|
||||||
|
// Disable Kitty keyboard protocol first
|
||||||
|
process.stdout.write("\x1b[<u");
|
||||||
|
this._kittyProtocolActive = false;
|
||||||
|
setKittyProtocolActive(false);
|
||||||
|
|
||||||
|
// Wait briefly to let any in-flight key release events arrive and be
|
||||||
|
// consumed by our still-active stdin handler. This prevents Kitty
|
||||||
|
// escape sequences from leaking to the parent shell over slow SSH.
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, drainMs));
|
||||||
|
}
|
||||||
|
|
||||||
stop(): void {
|
stop(): void {
|
||||||
// Disable bracketed paste mode
|
// Disable bracketed paste mode
|
||||||
process.stdout.write("\x1b[?2004l");
|
process.stdout.write("\x1b[?2004l");
|
||||||
|
|
||||||
// Disable Kitty keyboard protocol (pop the flags we pushed) - only if we enabled it
|
// Disable Kitty keyboard protocol if not already done by prepareForExit()
|
||||||
if (this._kittyProtocolActive) {
|
if (this._kittyProtocolActive) {
|
||||||
process.stdout.write("\x1b[<u");
|
process.stdout.write("\x1b[<u");
|
||||||
this._kittyProtocolActive = false;
|
this._kittyProtocolActive = false;
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,10 @@ export class VirtualTerminal implements Terminal {
|
||||||
this.xterm.write("\x1b[?2004h");
|
this.xterm.write("\x1b[?2004h");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async prepareForExit(_drainMs?: number): Promise<void> {
|
||||||
|
// No-op for virtual terminal - no Kitty protocol to drain
|
||||||
|
}
|
||||||
|
|
||||||
stop(): void {
|
stop(): void {
|
||||||
// Disable bracketed paste mode
|
// Disable bracketed paste mode
|
||||||
this.xterm.write("\x1b[?2004l");
|
this.xterm.write("\x1b[?2004l");
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue