fix: fix kitty proto on old terminals

This commit is contained in:
Catriel Müller
2026-05-22 08:18:48 -03:00
parent c076058bfc
commit 656572c2cf
5 changed files with 145 additions and 23 deletions
+5
View File
@@ -0,0 +1,5 @@
---
"@kilocode/cli": patch
---
Avoid leaving advanced keyboard modes enabled after exiting the TUI in mintty and MINGW terminals.
+3 -2
View File
@@ -67,20 +67,21 @@ import { createTuiApi } from "@/cli/cmd/tui/plugin/api"
import { TuiPluginRuntime } from "@/cli/cmd/tui/plugin/runtime"
import type { RouteMap } from "@/cli/cmd/tui/plugin/api"
import { FormatError, FormatUnknownError } from "@/cli/error"
import { resetTerminalState } from "@/kilocode/cli/cmd/tui/util/terminal" // kilocode_change
import { kitty, resetTerminalState } from "@/kilocode/cli/cmd/tui/util/terminal" // kilocode_change
import type { EventSource } from "./context/sdk"
import { DialogVariant } from "./component/dialog-variant"
function rendererConfig(_config: TuiConfig.Info): CliRendererConfig {
const mouseEnabled = !Flag.KILO_DISABLE_MOUSE && (_config.mouse ?? true)
const keyboard = kitty() // kilocode_change
return {
externalOutputMode: "passthrough",
targetFps: 60,
gatherStats: false,
exitOnCtrlC: false,
useKittyKeyboard: {},
...(keyboard ? { useKittyKeyboard: {} } : {}), // kilocode_change
autoFocus: false,
openConsoleOnError: false,
useMouse: mouseEnabled,
@@ -1,21 +1,48 @@
// kilocode_change - new file
/**
* Write escape sequences to disable all mouse tracking modes and reset terminal state.
* Write escape sequences to disable terminal input 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 = [
function truthy(key: string) {
const value = process.env[key]?.toLowerCase()
return value === "true" || value === "1"
}
export function kitty() {
if (truthy("KILO_DISABLE_KITTY_KEYBOARD")) return false
if (truthy("KILO_ENABLE_KITTY_KEYBOARD")) return true
const term = process.env.TERM_PROGRAM?.toLowerCase()
const system = process.env.MSYSTEM?.toLowerCase()
if (term === "mintty") return false
if (system) return false
return true
}
export function sequences() {
return [
"\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[<u", // pop/disable Kitty keyboard protocol
"\x1b[?2004l", // disable bracketed paste
"\x1b[?1004l", // disable focus tracking
"\x1b[?1l", // disable application cursor keys
"\x1b>", // disable application keypad mode
"\x1b[?66l", // disable numeric keypad application mode
"\x1b[>4;0m", // reset xterm modifyOtherKeys
...(kitty() ? ["\x1b[<u"] : []), // pop/disable Kitty keyboard protocol
"\x1b[?25h", // show cursor
"\x1b[0m", // reset text attributes
]
}
export function resetTerminalState() {
try {
process.stdout.write(sequences.join(""))
process.stdout.write(sequences().join(""))
} catch (err) {
console.error("resetTerminalState failed", err)
}
@@ -0,0 +1,89 @@
import { afterEach, expect, test } from "bun:test"
import { kitty, sequences } from "../../src/kilocode/cli/cmd/tui/util/terminal"
const keys = ["TERM_PROGRAM", "MSYSTEM", "KILO_DISABLE_KITTY_KEYBOARD", "KILO_ENABLE_KITTY_KEYBOARD"] as const
type Key = (typeof keys)[number]
const saved = Object.fromEntries(keys.map((key) => [key, process.env[key]])) as Record<Key, string | undefined>
function env(input: Partial<Record<Key, string | undefined>>) {
for (const key of keys) {
const value = input[key]
if (value === undefined) {
delete process.env[key]
continue
}
process.env[key] = value
}
}
function restore() {
for (const key of keys) {
const value = saved[key]
if (value === undefined) {
delete process.env[key]
continue
}
process.env[key] = value
}
}
afterEach(() => {
restore()
})
test("enables Kitty keyboard reset by default", () => {
env({})
expect(kitty()).toBe(true)
expect(sequences()).toContain("\x1b[<u")
})
test("disables Kitty keyboard in mintty", () => {
env({ TERM_PROGRAM: "mintty" })
expect(kitty()).toBe(false)
expect(sequences()).not.toContain("\x1b[<u")
})
test("disables Kitty keyboard in MSYS shells", () => {
env({ MSYSTEM: "MINGW64" })
expect(kitty()).toBe(false)
expect(sequences()).not.toContain("\x1b[<u")
})
test("allows explicitly enabling Kitty keyboard", () => {
env({ KILO_ENABLE_KITTY_KEYBOARD: "1", MSYSTEM: "MINGW64" })
expect(kitty()).toBe(true)
expect(sequences()).toContain("\x1b[<u")
})
test("allows explicitly disabling Kitty keyboard", () => {
env({ KILO_DISABLE_KITTY_KEYBOARD: "1", KILO_ENABLE_KITTY_KEYBOARD: "1" })
expect(kitty()).toBe(false)
expect(sequences()).not.toContain("\x1b[<u")
})
test("resets common terminal input modes", () => {
env({ KILO_DISABLE_KITTY_KEYBOARD: "1" })
expect(sequences()).toEqual(
expect.arrayContaining([
"\x1b[?1000l",
"\x1b[?1002l",
"\x1b[?1003l",
"\x1b[?1006l",
"\x1b[?1015l",
"\x1b[?2004l",
"\x1b[?1004l",
"\x1b[?1l",
"\x1b>",
"\x1b[?66l",
"\x1b[>4;0m",
"\x1b[?25h",
"\x1b[0m",
]),
)
})
@@ -7,12 +7,12 @@ export const iconNames = [
"zai",
"zai-coding-plan",
"xiaomi",
"xiaomi-token-plan-sgp", // kilocode_change
"xiaomi-token-plan-cn", // kilocode_change
"xiaomi-token-plan-ams", // kilocode_change
"xiaomi-token-plan-sgp",
"xiaomi-token-plan-cn",
"xiaomi-token-plan-ams",
"xai",
"wandb",
"wafer.ai", // kilocode_change
"wafer.ai",
"vultr",
"vivgrid",
"vercel",
@@ -20,8 +20,8 @@ export const iconNames = [
"v0",
"upstage",
"togetherai",
"the-grid-ai", // kilocode_change
"tencent-tokenhub", // kilocode_change
"the-grid-ai",
"tencent-tokenhub",
"tencent-coding-plan",
"synthetic",
"submodel",
@@ -32,7 +32,7 @@ export const iconNames = [
"scaleway",
"sap-ai-core",
"requesty",
"regolo-ai", // kilocode_change
"regolo-ai",
"qiniu-ai",
"qihang-ai",
"privatemode-ai",
@@ -48,7 +48,7 @@ export const iconNames = [
"nvidia",
"novita-ai",
"nova",
"neuralwatt", // kilocode_change
"neuralwatt",
"nebius",
"nano-gpt",
"morph",
@@ -56,7 +56,7 @@ export const iconNames = [
"moonshotai-cn",
"modelscope",
"moark",
"mixlayer", // kilocode_change
"mixlayer",
"mistral",
"minimax",
"minimax-coding-plan",
@@ -65,10 +65,10 @@ export const iconNames = [
"meganova",
"lucidquery",
"lmstudio",
"llmgateway", // kilocode_change
"llmgateway",
"llama",
"kuae-cloud-coding-plan",
"kiro", // kilocode_change
"kiro",
"kimi-for-coding",
"kilo",
"jiekou",
@@ -77,7 +77,7 @@ export const iconNames = [
"inception",
"iflowcn",
"huggingface",
"hpc-ai", // kilocode_change
"hpc-ai",
"helicone",
"groq",
"google",
@@ -86,7 +86,7 @@ export const iconNames = [
"gitlab",
"github-models",
"github-copilot",
"frogbot", // kilocode_change
"frogbot",
"friendli",
"firmware",
"fireworks-ai",
@@ -94,7 +94,7 @@ export const iconNames = [
"evroc",
"drun",
"dinference",
"digitalocean", // kilocode_change
"digitalocean",
"deepseek",
"deepinfra",
"cortecs",
@@ -117,7 +117,7 @@ export const iconNames = [
"alibaba-coding-plan-cn",
"alibaba-cn",
"aihubmix",
"abliteration-ai", // kilocode_change
"abliteration-ai",
"abacus",
"302ai",
] as const