Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/fix-terminal-title-process-name.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@moonshot-ai/kimi-code": patch
---

Set terminal tab titles without renaming the running process.
2 changes: 0 additions & 2 deletions apps/kimi-code/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@ import { CLI_SHUTDOWN_TIMEOUT_MS, CLI_UI_MODE } from './constant/app';
import { cleanupStaleNativeCacheForCurrent } from './native/native-assets';
import { installNativeModuleHook } from './native/module-hook';
import { runNativeAssetSmokeIfRequested } from './native/smoke';
import { initProcessName } from './utils/process/proctitle';

export async function handleMainCommand(opts: CLIOptions, version: string): Promise<void> {
let validated: ReturnType<typeof validateOptions>;
Expand Down Expand Up @@ -116,7 +115,6 @@ const MIGRATE_CLI_OPTIONS: CLIOptions = {
};

export function main(): void {
initProcessName();
installCrashHandlers();
installNativeModuleHook();
if (runNativeAssetSmokeIfRequested()) return;
Expand Down
2 changes: 1 addition & 1 deletion apps/kimi-code/src/tui/constant/terminal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export const OSC11_RESPONSE_PREFIX_NO_ESC = "]11;rgb:";
// Keep notification/title payloads bounded so terminal tabs and desktop
// notifications stay readable.
export const MAX_TERMINAL_NOTIFICATION_MESSAGE_LENGTH = 240;
export const MAX_PROCESS_TITLE_LENGTH = 32;
export const MAX_TERMINAL_TITLE_LENGTH = 32;

// OSC 11 probing must be short because unsupported terminals do not reply.
export const TERMINAL_THEME_DETECT_TIMEOUT_MS = 250;
Expand Down
4 changes: 2 additions & 2 deletions apps/kimi-code/src/tui/controllers/auth-flow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export interface AuthFlowHost {
appendStartupNotice(extra: string): void;
readonly sessionEventHandler: SessionEventHandler;
fetchSessions(): Promise<void>;
refreshSessionTitle(): void;
updateTerminalTitle(): void;
refreshSkillCommands(session?: SkillListSession): Promise<void>;
}

Expand Down Expand Up @@ -82,7 +82,7 @@ export class AuthFlowController {
await host.syncRuntimeState(session);
host.sessionEventHandler.startSubscription();
void host.fetchSessions();
host.refreshSessionTitle();
host.updateTerminalTitle();
void host.refreshSkillCommands(host.session);
}

Expand Down
5 changes: 3 additions & 2 deletions apps/kimi-code/src/tui/controllers/session-event-handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,6 @@ import {
selectMcpStartupStatusRows,
} from '../utils/mcp-server-status';
import { openUrl } from '#/utils/open-url';
import { setProcessTitle } from '../utils/proctitle';
import { errorReportHintLine } from '../constant/feedback';
import { formatStepDebugTiming } from '#/utils/usage/debug-timing';
import { nextTranscriptId } from '../utils/transcript-id';
Expand Down Expand Up @@ -91,6 +90,7 @@ export interface SessionEventHost {
showStatus(msg: string, color?: string): void;
showNotice(title: string, detail?: string): void;
appendTranscriptEntry(entry: TranscriptEntry): void;
updateTerminalTitle(): void;
sendQueuedMessage(session: Session, item: QueuedMessage): void;
shiftQueuedMessage(): QueuedMessage | undefined;
readonly btwPanelController: BtwPanelController;
Expand Down Expand Up @@ -296,6 +296,7 @@ export class SessionEventHandler {
case 'cron.fired':
case 'error':
case 'warning':
case 'goal.updated':
case 'session.meta.updated':
case 'skill.activated':
case 'subagent.completed':
Expand Down Expand Up @@ -592,7 +593,7 @@ export class SessionEventHandler {
const title = event.title ?? stringValue(event.patch?.['title']);
if (title !== undefined) {
this.host.setAppState({ sessionTitle: title });
setProcessTitle(title, this.host.state.appState.sessionId);
this.host.updateTerminalTitle();
}
}

Expand Down
13 changes: 8 additions & 5 deletions apps/kimi-code/src/tui/kimi-tui.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,9 @@ import {
LLM_NOT_SET_MESSAGE,
MAIN_AGENT_ID,
NO_ACTIVE_SESSION_MESSAGE,
PRODUCT_NAME,
} from './constant/kimi-tui';
import { MAX_TERMINAL_TITLE_LENGTH } from './constant/terminal';
import { combineStartupNotice, isOAuthLoginRequiredError } from './utils/startup';
import { adaptPanelResponse } from './reverse-rpc/approval/adapter';
import { ApprovalController } from './reverse-rpc/approval/controller';
Expand Down Expand Up @@ -117,7 +119,6 @@ import { ImageAttachmentStore, type ImageAttachment } from './utils/image-attach
import { extractMediaAttachments } from './utils/image-placeholder';
import { hasPatchChanges } from './utils/object-patch';
import { openUrl } from '#/utils/open-url';
import { setProcessTitle } from './utils/proctitle';
import { sessionRowsForPicker } from './utils/session-picker-rows';
import { installTerminalFocusTracking } from './utils/terminal-focus';
import { notifyTerminalOnce } from './utils/terminal-notification';
Expand Down Expand Up @@ -462,7 +463,7 @@ export class KimiTUI {
}
void this.fetchSessions();
if (this.session !== undefined) {
this.refreshSessionTitle();
this.updateTerminalTitle();
}
void this.refreshSkillCommands(this.session);
}
Expand Down Expand Up @@ -1090,8 +1091,10 @@ export class KimiTUI {
}
}

refreshSessionTitle(): void {
setProcessTitle(this.state.appState.sessionTitle, this.state.appState.sessionId);
updateTerminalTitle(): void {
const trimmed = this.state.appState.sessionTitle?.trim() ?? '';
const label = trimmed.length > 0 ? trimmed.slice(0, MAX_TERMINAL_TITLE_LENGTH) : PRODUCT_NAME;
this.state.terminal.setTitle(label);
}

resetSessionRuntime(): void {
Expand Down Expand Up @@ -1144,7 +1147,7 @@ export class KimiTUI {
this.resetSessionRuntime();
await this.setSession(session);
await this.syncRuntimeState(session);
this.refreshSessionTitle();
this.updateTerminalTitle();
try {
await this.refreshSkillCommands(this.session);
} catch {
Expand Down
29 changes: 0 additions & 29 deletions apps/kimi-code/src/tui/utils/proctitle.ts

This file was deleted.

31 changes: 0 additions & 31 deletions apps/kimi-code/src/utils/process/proctitle.ts

This file was deleted.

12 changes: 12 additions & 0 deletions apps/kimi-code/test/cli/main.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,18 @@ describe('main entry command handling', () => {
expect(mocks.parse).toHaveBeenCalledWith(process.argv);
});

it('does not rewrite the process title during startup', () => {
const originalTitle = process.title;
try {
process.title = 'kimi-test-runner';
main();

expect(process.title).toBe('kimi-test-runner');
} finally {
process.title = originalTitle;
}
});

it('exits early when update preflight requests process exit', async () => {
const opts = defaultOpts();
mocks.validateOptions.mockReturnValue({ options: opts, uiMode: 'shell' });
Expand Down
29 changes: 28 additions & 1 deletion apps/kimi-code/test/tui/kimi-tui-message-flow.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2809,6 +2809,30 @@ describe('KimiTUI message flow', () => {
expect(write).toHaveBeenCalledWith(deleteAllKittyImages());
});

it('updates terminal title through pi-tui without changing process title', async () => {
const originalTitle = process.title;
const { driver } = await makeDriver(makeSession({ id: 'ses-1' }));
const setTitle = vi.spyOn(driver.state.terminal, 'setTitle').mockImplementation(() => {});

try {
process.title = 'kimi-test-runner';
driver.sessionEventHandler.handleEvent(
{
type: 'session.meta.updated',
sessionId: 'ses-1',
agentId: 'main',
title: 'Implement terminal title',
} as Event,
() => {},
);

expect(setTitle).toHaveBeenCalledWith('Implement terminal title');
expect(process.title).toBe('kimi-test-runner');
} finally {
process.title = originalTitle;
}
});

it('forks the active session and switches to the returned session', async () => {
const originalTitle = process.title;
const source = makeSession({
Expand All @@ -2821,8 +2845,10 @@ describe('KimiTUI message flow', () => {
});
const forkSession = vi.fn(async () => forked);
const { driver, harness } = await makeDriver(source, { forkSession });
const setTitle = vi.spyOn(driver.state.terminal, 'setTitle').mockImplementation(() => {});

try {
process.title = 'kimi-test-runner';
driver.handleUserInput('/fork ignored args');

await vi.waitFor(() => {
Expand All @@ -2832,7 +2858,8 @@ describe('KimiTUI message flow', () => {
});
expect(driver.getCurrentSessionId()).toBe('ses-fork');
});
expect(process.title).toBe('Fork: Source title');
expect(setTitle).toHaveBeenCalledWith('Fork: Source title');
expect(process.title).toBe('kimi-test-runner');
expect(source.close).toHaveBeenCalledOnce();
expect(forked.onEvent).toHaveBeenCalledOnce();
expect(harness.resumeSession).not.toHaveBeenCalled();
Expand Down
Loading