fix(kimi-code): warn about tmux key configuration (#18)

This commit is contained in:
liruifengv 2026-05-25 16:22:03 +08:00 committed by GitHub
parent 67f8a2d606
commit a964bd2430
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 153 additions and 0 deletions

View file

@ -0,0 +1,5 @@
---
"@moonshot-ai/kimi-code": patch
---
Warn tmux users when extended key settings may prevent modified Enter shortcuts from working.

View file

@ -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<void> {
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<boolean> {
await this.refreshAvailableModels();

View file

@ -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<string | undefined>;
export async function detectTmuxKeyboardWarning(
env: NodeJS.ProcessEnv = process.env,
readTmuxOption: TmuxOptionReader = readTmuxOptionFromProcess,
): Promise<string | undefined> {
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<string | undefined> {
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);
});
});
}

View file

@ -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<string, string | undefined>): 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();
});
});