From c076058bfcbd4f561abc634f3aa109dee598f396 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Catriel=20M=C3=BCller?= Date: Thu, 21 May 2026 18:26:01 -0300 Subject: [PATCH 1/3] fix: fix logo on old windows terminal --- .changeset/bright-windows-logo.md | 5 ++++ packages/opencode/src/kilocode/cli/logo.ts | 14 +++++++--- packages/opencode/test/kilocode/logo.test.ts | 28 +++++++++++++------- 3 files changed, 33 insertions(+), 14 deletions(-) create mode 100644 .changeset/bright-windows-logo.md diff --git a/.changeset/bright-windows-logo.md b/.changeset/bright-windows-logo.md new file mode 100644 index 00000000000..c0ed555e384 --- /dev/null +++ b/.changeset/bright-windows-logo.md @@ -0,0 +1,5 @@ +--- +"@kilocode/cli": patch +--- + +Use the fallback logo in old Windows terminal emulators while keeping the Unicode logo available over SSH. diff --git a/packages/opencode/src/kilocode/cli/logo.ts b/packages/opencode/src/kilocode/cli/logo.ts index ca818c7189d..5226bfeb35b 100644 --- a/packages/opencode/src/kilocode/cli/logo.ts +++ b/packages/opencode/src/kilocode/cli/logo.ts @@ -47,14 +47,20 @@ function flag(value: string | undefined) { if (no.has(key)) return false } +function windows(env: NodeJS.ProcessEnv) { + if (env.WT_SESSION) return true + if (env.TERM_PROGRAM === "vscode") return true + if (env.WEZTERM_PANE) return true + if (env.TERM_PROGRAM === "WezTerm") return true + return false +} + export function supports(env = process.env, platform = process.platform) { const override = flag(env.KILO_UNICODE_LOGO) if (override !== undefined) return override - // Terminals do not expose font glyph coverage over SSH, so prefer the safe logo for remote sessions. if (env.TERM === "dumb") return false - if (env.SSH_TTY) return false - if (env.SSH_CLIENT) return false - if (env.SSH_CONNECTION) return false + // Old Windows Console Host cannot render the sextant glyphs used by the modern logo. + if (platform === "win32") return windows(env) if (env.ConEmuPID) return false if (env.ANSICON) return false return true diff --git a/packages/opencode/test/kilocode/logo.test.ts b/packages/opencode/test/kilocode/logo.test.ts index a936581aa71..91c1a8f067c 100644 --- a/packages/opencode/test/kilocode/logo.test.ts +++ b/packages/opencode/test/kilocode/logo.test.ts @@ -2,15 +2,23 @@ import { describe, expect, test } from "bun:test" import { plain, session, supports, tui } from "../../src/kilocode/cli/logo" describe("kilocode logo", () => { - test("falls back on remote terminals", () => { - expect(supports({ SSH_TTY: "/dev/pts/0" }, "linux")).toBe(false) - expect(supports({ SSH_CLIENT: "127.0.0.1 12345 22" }, "linux")).toBe(false) - expect(supports({ SSH_CONNECTION: "127.0.0.1 12345 127.0.0.1 22" }, "linux")).toBe(false) + test("allows remote terminals", () => { + expect(supports({ SSH_TTY: "/dev/pts/0" }, "linux")).toBe(true) + expect(supports({ SSH_CLIENT: "127.0.0.1 12345 22" }, "linux")).toBe(true) + expect(supports({ SSH_CONNECTION: "127.0.0.1 12345 127.0.0.1 22" }, "linux")).toBe(true) }) - test("allows Windows Terminal locally", () => { - expect(supports({}, "win32")).toBe(true) - expect(supports({ WT_SESSION: "session" }, "linux")).toBe(true) + test("falls back on old Windows terminals", () => { + expect(supports({}, "win32")).toBe(false) + expect(supports({ ANSICON: "1" }, "win32")).toBe(false) + expect(supports({ ConEmuPID: "123" }, "win32")).toBe(false) + }) + + test("allows modern Windows terminals", () => { + expect(supports({ WT_SESSION: "session" }, "win32")).toBe(true) + expect(supports({ TERM_PROGRAM: "vscode" }, "win32")).toBe(true) + expect(supports({ WEZTERM_PANE: "1" }, "win32")).toBe(true) + expect(supports({ TERM_PROGRAM: "WezTerm" }, "win32")).toBe(true) }) test("allows an override", () => { @@ -20,12 +28,12 @@ describe("kilocode logo", () => { test("uses modern and fallback logo variants", () => { expect(tui({ KILO_UNICODE_LOGO: "1" }, "linux").join("\n")).toContain("🬺🬏") - expect(tui({ SSH_TTY: "/dev/pts/0" }, "linux").join("\n")).not.toContain("🬺🬏") - expect(plain({ SSH_TTY: "/dev/pts/0" }, "linux").join("\n")).not.toContain("🬁🬬") + expect(tui({}, "win32").join("\n")).not.toContain("🬺🬏") + expect(plain({}, "win32").join("\n")).not.toContain("🬁🬬") }) test("formats child session exit logo", () => { - const out = session("Title", "ses_test", "", "", { SSH_TTY: "/dev/pts/0" }, "linux") + const out = session("Title", "ses_test", "", "", {}, "win32") expect(out).toContain("Title") expect(out).not.toContain("🬺🬏") }) From 656572c2cfeff16034769381acfb60f9f85091a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Catriel=20M=C3=BCller?= Date: Fri, 22 May 2026 08:18:48 -0300 Subject: [PATCH 2/3] fix: fix kitty proto on old terminals --- .changeset/mingw-terminal-keyboard.md | 5 ++ packages/opencode/src/cli/cmd/tui/app.tsx | 5 +- .../src/kilocode/cli/cmd/tui/util/terminal.ts | 39 ++++++-- .../opencode/test/kilocode/terminal.test.ts | 89 +++++++++++++++++++ .../ui/src/components/provider-icons/types.ts | 30 +++---- 5 files changed, 145 insertions(+), 23 deletions(-) create mode 100644 .changeset/mingw-terminal-keyboard.md create mode 100644 packages/opencode/test/kilocode/terminal.test.ts diff --git a/.changeset/mingw-terminal-keyboard.md b/.changeset/mingw-terminal-keyboard.md new file mode 100644 index 00000000000..d35e517934d --- /dev/null +++ b/.changeset/mingw-terminal-keyboard.md @@ -0,0 +1,5 @@ +--- +"@kilocode/cli": patch +--- + +Avoid leaving advanced keyboard modes enabled after exiting the TUI in mintty and MINGW terminals. diff --git a/packages/opencode/src/cli/cmd/tui/app.tsx b/packages/opencode/src/cli/cmd/tui/app.tsx index 4ec2172f197..a9e04cd57e7 100644 --- a/packages/opencode/src/cli/cmd/tui/app.tsx +++ b/packages/opencode/src/cli/cmd/tui/app.tsx @@ -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, diff --git a/packages/opencode/src/kilocode/cli/cmd/tui/util/terminal.ts b/packages/opencode/src/kilocode/cli/cmd/tui/util/terminal.ts index c7c0e260a8a..9a6c775f923 100644 --- a/packages/opencode/src/kilocode/cli/cmd/tui/util/terminal.ts +++ b/packages/opencode/src/kilocode/cli/cmd/tui/util/terminal.ts @@ -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[", // disable application keypad mode + "\x1b[?66l", // disable numeric keypad application mode + "\x1b[>4;0m", // reset xterm modifyOtherKeys + ...(kitty() ? ["\x1b[ [key, process.env[key]])) as Record + +function env(input: Partial>) { + 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[ { + env({ TERM_PROGRAM: "mintty" }) + + expect(kitty()).toBe(false) + expect(sequences()).not.toContain("\x1b[ { + env({ MSYSTEM: "MINGW64" }) + + expect(kitty()).toBe(false) + expect(sequences()).not.toContain("\x1b[ { + env({ KILO_ENABLE_KITTY_KEYBOARD: "1", MSYSTEM: "MINGW64" }) + + expect(kitty()).toBe(true) + expect(sequences()).toContain("\x1b[ { + env({ KILO_DISABLE_KITTY_KEYBOARD: "1", KILO_ENABLE_KITTY_KEYBOARD: "1" }) + + expect(kitty()).toBe(false) + expect(sequences()).not.toContain("\x1b[ { + 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", + ]), + ) +}) diff --git a/packages/ui/src/components/provider-icons/types.ts b/packages/ui/src/components/provider-icons/types.ts index bb8a45e660d..2f22f2e1bb6 100644 --- a/packages/ui/src/components/provider-icons/types.ts +++ b/packages/ui/src/components/provider-icons/types.ts @@ -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 From 3e8f6d7bc7fcc69877d3ad81c59241bd7d1bb3fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Catriel=20M=C3=BCller?= Date: Fri, 22 May 2026 08:59:50 -0300 Subject: [PATCH 3/3] fix: fix cleanup mouse events --- .changeset/mingw-terminal-keyboard.md | 2 +- .../opencode/src/kilocode/cli/cmd/tui/util/terminal.ts | 9 ++++++++- packages/opencode/test/kilocode/terminal.test.ts | 5 +++++ 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/.changeset/mingw-terminal-keyboard.md b/.changeset/mingw-terminal-keyboard.md index d35e517934d..c6135cc3e10 100644 --- a/.changeset/mingw-terminal-keyboard.md +++ b/.changeset/mingw-terminal-keyboard.md @@ -2,4 +2,4 @@ "@kilocode/cli": patch --- -Avoid leaving advanced keyboard modes enabled after exiting the TUI in mintty and MINGW terminals. +Avoid leaving mouse and advanced keyboard modes enabled after exiting the TUI in mintty and MINGW terminals. diff --git a/packages/opencode/src/kilocode/cli/cmd/tui/util/terminal.ts b/packages/opencode/src/kilocode/cli/cmd/tui/util/terminal.ts index 9a6c775f923..2a5c466e081 100644 --- a/packages/opencode/src/kilocode/cli/cmd/tui/util/terminal.ts +++ b/packages/opencode/src/kilocode/cli/cmd/tui/util/terminal.ts @@ -1,3 +1,5 @@ +import fs from "node:fs" + /** * 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 @@ -23,11 +25,16 @@ export function kitty() { export function sequences() { return [ + "\x1b[?9l", // disable X10 mouse tracking "\x1b[?1000l", // disable normal mouse tracking + "\x1b[?1001l", // disable highlight mouse tracking "\x1b[?1002l", // disable button-event mouse tracking "\x1b[?1003l", // disable any-event mouse tracking (all movement) + "\x1b[?1005l", // disable UTF-8 extended mouse mode "\x1b[?1006l", // disable SGR extended mouse mode + "\x1b[?1007l", // disable alternate scroll mode "\x1b[?1015l", // disable RXVT mouse mode + "\x1b[?1016l", // disable SGR pixel mouse mode "\x1b[?2004l", // disable bracketed paste "\x1b[?1004l", // disable focus tracking "\x1b[?1l", // disable application cursor keys @@ -42,7 +49,7 @@ export function sequences() { export function resetTerminalState() { try { - process.stdout.write(sequences().join("")) + fs.writeSync(process.stdout.fd, sequences().join("")) } catch (err) { console.error("resetTerminalState failed", err) } diff --git a/packages/opencode/test/kilocode/terminal.test.ts b/packages/opencode/test/kilocode/terminal.test.ts index 4a7eb6b976b..50d5d337777 100644 --- a/packages/opencode/test/kilocode/terminal.test.ts +++ b/packages/opencode/test/kilocode/terminal.test.ts @@ -71,11 +71,16 @@ test("resets common terminal input modes", () => { expect(sequences()).toEqual( expect.arrayContaining([ + "\x1b[?9l", "\x1b[?1000l", + "\x1b[?1001l", "\x1b[?1002l", "\x1b[?1003l", + "\x1b[?1005l", "\x1b[?1006l", + "\x1b[?1007l", "\x1b[?1015l", + "\x1b[?1016l", "\x1b[?2004l", "\x1b[?1004l", "\x1b[?1l",