Skip to content

Commit 6de3d97

Browse files
authored
fix(tui): drain terminal input before exit (#314)
1 parent a10cb94 commit 6de3d97

3 files changed

Lines changed: 29 additions & 1 deletion

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@moonshot-ai/kimi-code": patch
3+
---
4+
5+
Prevent modified keyboard release sequences from appearing after exiting the CLI.

apps/kimi-code/src/tui/kimi-tui.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -558,6 +558,7 @@ export class KimiTUI {
558558
await this.harness.close();
559559
this.sessionEventHandler.stopAllMcpServerStatusSpinners();
560560
this.uninstallRainbowDance();
561+
await this.state.terminal.drainInput();
561562
this.state.ui.stop();
562563
if (this.onExit) {
563564
await this.onExit(exitCode);

apps/kimi-code/test/tui/signal-handlers.test.ts

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
22

3-
import { KimiTUI, type KimiTUIStartupInput } from '#/tui/kimi-tui';
3+
import { KimiTUI, type KimiTUIStartupInput, type TUIState } from '#/tui/kimi-tui';
44

55
interface SignalDriver {
6+
state: TUIState;
67
registerSignalHandlers(): void;
78
unregisterSignalHandlers(): void;
89
emergencyTerminalExit(): never;
@@ -298,6 +299,27 @@ describe('KimiTUI signal handlers', () => {
298299
expect(process.listenerCount('SIGTERM')).toBe(beforeSigterm);
299300
});
300301

302+
it('stop() drains terminal input before stopping the UI and exiting', async () => {
303+
const { driver, tui } = makeDriver();
304+
const events: string[] = [];
305+
const drainInput = vi.spyOn(driver.state.terminal, 'drainInput').mockImplementation(async () => {
306+
events.push('drain');
307+
});
308+
const uiStop = vi.spyOn(driver.state.ui, 'stop').mockImplementation(() => {
309+
events.push('ui.stop');
310+
});
311+
tui.onExit = vi.fn(async () => {
312+
events.push('exit');
313+
});
314+
315+
await tui.stop();
316+
317+
expect(drainInput).toHaveBeenCalledOnce();
318+
expect(uiStop).toHaveBeenCalledOnce();
319+
expect(tui.onExit).toHaveBeenCalledOnce();
320+
expect(events).toEqual(['drain', 'ui.stop', 'exit']);
321+
});
322+
301323
it('start() unregisters signal handlers when initialization throws', async () => {
302324
const { tui } = makeDriver();
303325
// Force the very first awaited call inside start() to reject. We don't

0 commit comments

Comments
 (0)