From 553a9b5f0d0c030199c7930f2023b03a73a34972 Mon Sep 17 00:00:00 2001 From: ashwinhegde19 Date: Fri, 6 Feb 2026 21:04:08 +0530 Subject: [PATCH 1/3] fix: disable mouse tracking on Windows terminal exit Add explicit terminal state reset sequences after renderer cleanup to ensure SGR mouse tracking is properly disabled on exit. On Windows, the terminal may still report mouse events as raw escape sequences if mouse tracking wasn't fully disabled. This fix adds: 1. resetTerminalState() function that writes disable sequences for: - Normal and button-event mouse tracking - Any-event mouse tracking (all movement) - SGR extended mouse mode - RXVT mouse mode - Kitty keyboard protocol - Text attributes 2. Calls resetTerminalState() in three exit paths: - Normal exit via ExitProvider (exit/quit/:q commands) - Error boundary Ctrl+C handler - process.on('exit') safety net for all other exits Fixes #151 --- packages/opencode/src/cli/cmd/tui/app.tsx | 8 +++++++ .../opencode/src/cli/cmd/tui/context/exit.tsx | 4 ++++ .../opencode/src/cli/cmd/tui/util/terminal.ts | 23 +++++++++++++++++++ 3 files changed, 35 insertions(+) diff --git a/packages/opencode/src/cli/cmd/tui/app.tsx b/packages/opencode/src/cli/cmd/tui/app.tsx index 885240f9603..41ed46fe190 100644 --- a/packages/opencode/src/cli/cmd/tui/app.tsx +++ b/packages/opencode/src/cli/cmd/tui/app.tsx @@ -42,6 +42,8 @@ import { writeHeapSnapshot } from "v8" import { PromptRefProvider, usePromptRef } from "./context/prompt" import { registerKiloCommands } from "@/kilocode/kilo-commands" // kilocode_change import { initializeTUIDependencies } from "@kilocode/kilo-gateway/tui" // kilocode_change +// kilocode_change - import resetTerminalState for mouse tracking cleanup +import { resetTerminalState } from "@tui/util/terminal" async function getTerminalBackgroundColor(): Promise<"dark" | "light"> { // can't set raw mode if not a TTY @@ -131,6 +133,10 @@ export function tui(input: { resolve() } + // Safety net: ensure mouse tracking is disabled regardless of exit path + // kilocode_change - register exit handler to reset terminal state + process.on("exit", resetTerminalState) + render( () => { return ( @@ -780,6 +786,8 @@ function ErrorComponent(props: { renderer.setTerminalTitle("") renderer.destroy() win32FlushInputBuffer() + // kilocode_change - reset terminal state to disable mouse tracking on exit + resetTerminalState() await props.onExit() } diff --git a/packages/opencode/src/cli/cmd/tui/context/exit.tsx b/packages/opencode/src/cli/cmd/tui/context/exit.tsx index 3eb2edf7277..f9e708fff2a 100644 --- a/packages/opencode/src/cli/cmd/tui/context/exit.tsx +++ b/packages/opencode/src/cli/cmd/tui/context/exit.tsx @@ -2,6 +2,8 @@ import { useRenderer } from "@opentui/solid" import { createSimpleContext } from "./helper" import { FormatError, FormatUnknownError } from "@/cli/error" import { win32FlushInputBuffer } from "../win32" +// kilocode_change - import resetTerminalState for mouse tracking cleanup +import { resetTerminalState } from "@tui/util/terminal" type Exit = ((reason?: unknown) => Promise) & { message: { set: (value?: string) => () => void @@ -34,6 +36,8 @@ export const { use: useExit, provider: ExitProvider } = createSimpleContext({ renderer.setTerminalTitle("") renderer.destroy() win32FlushInputBuffer() + // kilocode_change - reset terminal state to disable mouse tracking + resetTerminalState() await input.onExit?.() if (reason) { const formatted = FormatError(reason) ?? FormatUnknownError(reason) diff --git a/packages/opencode/src/cli/cmd/tui/util/terminal.ts b/packages/opencode/src/cli/cmd/tui/util/terminal.ts index 2b81068b3f9..abbb16b64c6 100644 --- a/packages/opencode/src/cli/cmd/tui/util/terminal.ts +++ b/packages/opencode/src/cli/cmd/tui/util/terminal.ts @@ -1,5 +1,28 @@ import { RGBA } from "@opentui/core" +/** + * Write escape sequences to disable all mouse tracking modes and reset terminal state. + * This is a safety net to ensure the terminal is clean after exit, even if the renderer's + * cleanup didn't flush properly (e.g. on Windows). + */ +export function resetTerminalState() { + const sequences = [ + "\x1b[?1000l", // disable normal mouse tracking + "\x1b[?1002l", // disable button-event mouse tracking + "\x1b[?1003l", // disable any-event mouse tracking (all movement) + "\x1b[?1006l", // disable SGR extended mouse mode + "\x1b[?1015l", // disable RXVT mouse mode + "\x1b[> /** From c1aca9a0cc9bbd39e0d4db119c0305e2d379b685 Mon Sep 17 00:00:00 2001 From: Alex Alecu Date: Tue, 14 Apr 2026 19:27:13 +0300 Subject: [PATCH 2/3] fix(cli): log error in catch block --- packages/opencode/src/cli/cmd/tui/util/terminal.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/opencode/src/cli/cmd/tui/util/terminal.ts b/packages/opencode/src/cli/cmd/tui/util/terminal.ts index abbb16b64c6..4bb0f86997a 100644 --- a/packages/opencode/src/cli/cmd/tui/util/terminal.ts +++ b/packages/opencode/src/cli/cmd/tui/util/terminal.ts @@ -17,9 +17,9 @@ export function resetTerminalState() { ] try { process.stdout.write(sequences.join("")) - } catch { - // kilocode_change - stdout may already be closed during exit, ignore - // Silent fail is intentional here — process is exiting anyway + } catch (err) { + // kilocode_change - stdout may already be closed during exit + console.error("resetTerminalState failed", err) } } From 316a6473b6762b650c6e1624ffc1cefb930cd3ce Mon Sep 17 00:00:00 2001 From: Alex Alecu Date: Tue, 14 Apr 2026 19:41:50 +0300 Subject: [PATCH 3/3] fix(cli): add kilocode_change markers --- packages/opencode/src/cli/cmd/tui/context/exit.tsx | 6 ++---- packages/opencode/src/cli/cmd/tui/util/terminal.ts | 3 ++- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/packages/opencode/src/cli/cmd/tui/context/exit.tsx b/packages/opencode/src/cli/cmd/tui/context/exit.tsx index 25287781099..4f0c5eae0b2 100644 --- a/packages/opencode/src/cli/cmd/tui/context/exit.tsx +++ b/packages/opencode/src/cli/cmd/tui/context/exit.tsx @@ -2,8 +2,7 @@ import { useRenderer } from "@opentui/solid" import { createSimpleContext } from "./helper" import { FormatError, FormatUnknownError } from "@/cli/error" import { win32FlushInputBuffer } from "../win32" -// kilocode_change - import resetTerminalState for mouse tracking cleanup -import { resetTerminalState } from "@tui/util/terminal" +import { resetTerminalState } from "@tui/util/terminal" // kilocode_change type Exit = ((reason?: unknown) => Promise) & { message: { set: (value?: string) => () => void @@ -40,8 +39,7 @@ export const { use: useExit, provider: ExitProvider } = createSimpleContext({ renderer.setTerminalTitle("") renderer.destroy() win32FlushInputBuffer() - // kilocode_change - reset terminal state to disable mouse tracking - resetTerminalState() + resetTerminalState() // kilocode_change if (reason) { const formatted = FormatError(reason) ?? FormatUnknownError(reason) if (formatted) { diff --git a/packages/opencode/src/cli/cmd/tui/util/terminal.ts b/packages/opencode/src/cli/cmd/tui/util/terminal.ts index 4bb0f86997a..a2e289b3e64 100644 --- a/packages/opencode/src/cli/cmd/tui/util/terminal.ts +++ b/packages/opencode/src/cli/cmd/tui/util/terminal.ts @@ -1,5 +1,6 @@ import { RGBA } from "@opentui/core" +// kilocode_change start /** * Write escape sequences to disable all mouse tracking modes and reset terminal state. * This is a safety net to ensure the terminal is clean after exit, even if the renderer's @@ -18,10 +19,10 @@ export function resetTerminalState() { try { process.stdout.write(sequences.join("")) } catch (err) { - // kilocode_change - stdout may already be closed during exit console.error("resetTerminalState failed", err) } } +// kilocode_change end export namespace Terminal { export type Colors = Awaited>