From 352385073d21e9bf5270d4847ce7f5adaffccec2 Mon Sep 17 00:00:00 2001 From: liruifengv Date: Mon, 25 May 2026 16:00:17 +0800 Subject: [PATCH] fix(kimi-code): warn about tmux key configuration --- .changeset/warn-tmux-key-config.md | 5 ++ apps/kimi-code/src/tui/kimi-tui.ts | 9 +++ apps/kimi-code/src/tui/utils/tmux-keyboard.ts | 68 ++++++++++++++++++ apps/kimi-code/test/tui/tmux-keyboard.test.ts | 71 +++++++++++++++++++ 4 files changed, 153 insertions(+) create mode 100644 .changeset/warn-tmux-key-config.md create mode 100644 apps/kimi-code/src/tui/utils/tmux-keyboard.ts create mode 100644 apps/kimi-code/test/tui/tmux-keyboard.test.ts diff --git a/.changeset/warn-tmux-key-config.md b/.changeset/warn-tmux-key-config.md new file mode 100644 index 0000000..19c20d9 --- /dev/null +++ b/.changeset/warn-tmux-key-config.md @@ -0,0 +1,5 @@ +--- +"@moonshot-ai/kimi-code": patch +--- + +Warn tmux users when extended key settings may prevent modified Enter shortcuts from working. diff --git a/apps/kimi-code/src/tui/kimi-tui.ts b/apps/kimi-code/src/tui/kimi-tui.ts index ada55d5..28727c0 100644 --- a/apps/kimi-code/src/tui/kimi-tui.ts +++ b/apps/kimi-code/src/tui/kimi-tui.ts @@ -232,6 +232,7 @@ import { installTerminalFocusTracking } from './utils/terminal-focus'; import { notifyTerminalOnce } from './utils/terminal-notification'; import { createTerminalState, type TerminalState } from './utils/terminal-state'; import { installTerminalThemeTracking } from './utils/terminal-theme'; +import { detectTmuxKeyboardWarning } from './utils/tmux-keyboard'; import { nextTranscriptId } from './utils/transcript-id'; export interface KimiTUIStartupInput { @@ -763,6 +764,7 @@ export class KimiTUI { this.showStatus(this.state.startupNotice); this.state.startupNotice = undefined; } + void this.showTmuxKeyboardWarningIfNeeded(); if (this.state.startupState === 'picker') { void this.bootstrapFromPicker(); // resumeSession (fired on picker select) owns post-pick init; nothing @@ -786,6 +788,13 @@ export class KimiTUI { void this.refreshSkillCommands(this.session); } + // Warns tmux users when modified Enter shortcuts are likely to be swallowed. + private async showTmuxKeyboardWarningIfNeeded(): Promise { + const warning = await detectTmuxKeyboardWarning(); + if (warning === undefined || this.aborted) return; + this.showStatus(warning, this.state.theme.colors.warning); + } + // Creates or resumes the startup session and reports whether history should replay. private async init(): Promise { await this.refreshAvailableModels(); diff --git a/apps/kimi-code/src/tui/utils/tmux-keyboard.ts b/apps/kimi-code/src/tui/utils/tmux-keyboard.ts new file mode 100644 index 0000000..88897fa --- /dev/null +++ b/apps/kimi-code/src/tui/utils/tmux-keyboard.ts @@ -0,0 +1,68 @@ +import { spawn } from 'node:child_process'; + +const TMUX_QUERY_TIMEOUT_MS = 2000; + +export const TMUX_EXTENDED_KEYS_OFF_WARNING = + 'tmux extended-keys is off. Modified Enter keys may not work. Add `set -g extended-keys on` to ~/.tmux.conf and restart tmux.'; + +export const TMUX_EXTENDED_KEYS_FORMAT_XTERM_WARNING = + 'tmux extended-keys-format is xterm. Kimi Code works best with csi-u. Add `set -g extended-keys-format csi-u` to ~/.tmux.conf and restart tmux.'; + +export type TmuxOptionReader = (option: string) => Promise; + +export async function detectTmuxKeyboardWarning( + env: NodeJS.ProcessEnv = process.env, + readTmuxOption: TmuxOptionReader = readTmuxOptionFromProcess, +): Promise { + if ((env['TMUX'] ?? '').length === 0) return undefined; + + const [extendedKeys, extendedKeysFormat] = await Promise.all([ + readTmuxOption('extended-keys'), + readTmuxOption('extended-keys-format'), + ]); + + if (extendedKeys === undefined) return undefined; + + if (extendedKeys !== 'on' && extendedKeys !== 'always') { + return TMUX_EXTENDED_KEYS_OFF_WARNING; + } + + if (extendedKeysFormat === 'xterm') { + return TMUX_EXTENDED_KEYS_FORMAT_XTERM_WARNING; + } + + return undefined; +} + +function readTmuxOptionFromProcess(option: string): Promise { + return new Promise((resolve) => { + const proc = spawn('tmux', ['show', '-gv', option], { + stdio: ['ignore', 'pipe', 'ignore'], + }); + let stdout = ''; + let settled = false; + let timer: NodeJS.Timeout; + + const finish = (value: string | undefined) => { + if (settled) return; + settled = true; + clearTimeout(timer); + resolve(value); + }; + + timer = setTimeout(() => { + proc.kill(); + finish(undefined); + }, TMUX_QUERY_TIMEOUT_MS); + + proc.stdout?.on('data', (data: Buffer) => { + stdout += data.toString('utf8'); + }); + proc.on('error', () => { + finish(undefined); + }); + proc.on('close', (code) => { + finish(code === 0 ? stdout.trim() : undefined); + }); + }); +} diff --git a/apps/kimi-code/test/tui/tmux-keyboard.test.ts b/apps/kimi-code/test/tui/tmux-keyboard.test.ts new file mode 100644 index 0000000..efc7315 --- /dev/null +++ b/apps/kimi-code/test/tui/tmux-keyboard.test.ts @@ -0,0 +1,71 @@ +import { describe, expect, it, vi } from 'vitest'; + +import { + detectTmuxKeyboardWarning, + TMUX_EXTENDED_KEYS_FORMAT_XTERM_WARNING, + TMUX_EXTENDED_KEYS_OFF_WARNING, + type TmuxOptionReader, +} from '#/tui/utils/tmux-keyboard'; + +function optionReader(values: Record): TmuxOptionReader { + return vi.fn(async (option: string) => values[option]); +} + +describe('tmux keyboard setup detection', () => { + it('skips checks outside tmux', async () => { + const readOption = optionReader({}); + + await expect(detectTmuxKeyboardWarning({}, readOption)).resolves.toBeUndefined(); + + expect(readOption).not.toHaveBeenCalled(); + }); + + it('does not warn when tmux options cannot be queried', async () => { + const readOption = optionReader({ + 'extended-keys': undefined, + 'extended-keys-format': undefined, + }); + + await expect( + detectTmuxKeyboardWarning({ TMUX: '/tmp/tmux/default,123,0' }, readOption), + ).resolves.toBeUndefined(); + }); + + it('warns when extended-keys is off', async () => { + const readOption = optionReader({ + 'extended-keys': 'off', + 'extended-keys-format': 'csi-u', + }); + + await expect( + detectTmuxKeyboardWarning({ TMUX: '/tmp/tmux/default,123,0' }, readOption), + ).resolves.toBe(TMUX_EXTENDED_KEYS_OFF_WARNING); + }); + + it('warns when extended-keys-format is xterm', async () => { + const readOption = optionReader({ + 'extended-keys': 'on', + 'extended-keys-format': 'xterm', + }); + + await expect( + detectTmuxKeyboardWarning({ TMUX: '/tmp/tmux/default,123,0' }, readOption), + ).resolves.toBe(TMUX_EXTENDED_KEYS_FORMAT_XTERM_WARNING); + }); + + it('accepts on and always with csi-u or absent format', async () => { + await expect( + detectTmuxKeyboardWarning( + { TMUX: '/tmp/tmux/default,123,0' }, + optionReader({ 'extended-keys': 'on', 'extended-keys-format': 'csi-u' }), + ), + ).resolves.toBeUndefined(); + + await expect( + detectTmuxKeyboardWarning( + { TMUX: '/tmp/tmux/default,123,0' }, + optionReader({ 'extended-keys': 'always', 'extended-keys-format': undefined }), + ), + ).resolves.toBeUndefined(); + }); +});