From b1e0135ce9695af50a02167255cc1e6409340588 Mon Sep 17 00:00:00 2001 From: liruifengv Date: Mon, 25 May 2026 14:25:39 +0800 Subject: [PATCH 1/3] fix: hide empty current session from sessions list --- apps/kimi-code/src/tui/kimi-tui.ts | 10 +--- .../src/tui/utils/session-picker-rows.ts | 32 ++++++++++ .../tui/utils/session-picker-rows.test.ts | 58 +++++++++++++++++++ 3 files changed, 92 insertions(+), 8 deletions(-) create mode 100644 apps/kimi-code/src/tui/utils/session-picker-rows.ts create mode 100644 apps/kimi-code/test/tui/utils/session-picker-rows.test.ts diff --git a/apps/kimi-code/src/tui/kimi-tui.ts b/apps/kimi-code/src/tui/kimi-tui.ts index 92021068fa..48d44e7336 100644 --- a/apps/kimi-code/src/tui/kimi-tui.ts +++ b/apps/kimi-code/src/tui/kimi-tui.ts @@ -227,6 +227,7 @@ import { } from './utils/mcp-server-status'; 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'; import { createTerminalState, type TerminalState } from './utils/terminal-state'; @@ -1866,14 +1867,7 @@ export class KimiTUI { this.state.loadingSessions = true; try { const sessions = await this.harness.listSessions({ workDir: this.state.appState.workDir }); - this.state.sessions = sessions.map((session) => ({ - id: session.id, - title: session.title ?? null, - last_prompt: session.lastPrompt ?? null, - work_dir: session.workDir, - updated_at: session.updatedAt ?? session.createdAt ?? 0, - metadata: session.metadata, - })); + this.state.sessions = sessionRowsForPicker(sessions, this.state.appState.sessionId); } catch { /* silently ignore */ } finally { diff --git a/apps/kimi-code/src/tui/utils/session-picker-rows.ts b/apps/kimi-code/src/tui/utils/session-picker-rows.ts new file mode 100644 index 0000000000..13a0852d3b --- /dev/null +++ b/apps/kimi-code/src/tui/utils/session-picker-rows.ts @@ -0,0 +1,32 @@ +import type { SessionSummary } from '@moonshot-ai/kimi-code-sdk'; + +import type { SessionRow } from '#/tui/components/dialogs/session-picker'; + +const DEFAULT_SESSION_TITLE = 'New Session'; + +export function sessionRowsForPicker( + sessions: readonly SessionSummary[], + currentSessionId: string, +): SessionRow[] { + return sessions + .filter((session) => !isEmptyCurrentSessionSummary(session, currentSessionId)) + .map((session) => ({ + id: session.id, + title: session.title ?? null, + last_prompt: session.lastPrompt ?? null, + work_dir: session.workDir, + updated_at: session.updatedAt ?? session.createdAt ?? 0, + metadata: session.metadata, + })); +} + +function isEmptyCurrentSessionSummary( + session: Pick, + currentSessionId: string, +): boolean { + if (currentSessionId.length === 0 || session.id !== currentSessionId) return false; + const lastPrompt = session.lastPrompt?.trim(); + if (lastPrompt !== undefined && lastPrompt.length > 0) return false; + const title = session.title?.trim(); + return title === undefined || title.length === 0 || title === DEFAULT_SESSION_TITLE; +} diff --git a/apps/kimi-code/test/tui/utils/session-picker-rows.test.ts b/apps/kimi-code/test/tui/utils/session-picker-rows.test.ts new file mode 100644 index 0000000000..ee4f654d8b --- /dev/null +++ b/apps/kimi-code/test/tui/utils/session-picker-rows.test.ts @@ -0,0 +1,58 @@ +import type { SessionSummary } from '@moonshot-ai/kimi-code-sdk'; +import { describe, expect, it } from 'vitest'; + +import { sessionRowsForPicker } from '#/tui/utils/session-picker-rows'; + +function summary(input: { + readonly id: string; + readonly title?: string; + readonly lastPrompt?: string; +}): SessionSummary { + return { + id: input.id, + title: input.title, + lastPrompt: input.lastPrompt, + workDir: '/tmp/project', + sessionDir: `/tmp/home/sessions/${input.id}`, + createdAt: 1, + updatedAt: 2, + }; +} + +describe('sessionRowsForPicker', () => { + it('omits the empty current session from the picker rows', () => { + const rows = sessionRowsForPicker( + [ + summary({ id: 'ses_current', title: 'New Session' }), + summary({ id: 'ses_previous', title: 'New Session' }), + ], + 'ses_current', + ); + + expect(rows.map((row) => row.id)).toEqual(['ses_previous']); + }); + + it('keeps the current session after prompt metadata exists', () => { + const rows = sessionRowsForPicker( + [ + summary({ + id: 'ses_current', + title: 'Implement feature', + lastPrompt: 'Implement feature', + }), + ], + 'ses_current', + ); + + expect(rows.map((row) => row.id)).toEqual(['ses_current']); + }); + + it('keeps a current session with a non-default title', () => { + const rows = sessionRowsForPicker( + [summary({ id: 'ses_current', title: 'Pinned workspace' })], + 'ses_current', + ); + + expect(rows.map((row) => row.id)).toEqual(['ses_current']); + }); +}); From eed15a4e4d1342d6bf724bde78086f67e1004deb Mon Sep 17 00:00:00 2001 From: liruifengv Date: Mon, 25 May 2026 14:26:53 +0800 Subject: [PATCH 2/3] chore: add changeset for session picker fix --- .changeset/hide-empty-current-session.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/hide-empty-current-session.md diff --git a/.changeset/hide-empty-current-session.md b/.changeset/hide-empty-current-session.md new file mode 100644 index 0000000000..5f490925a4 --- /dev/null +++ b/.changeset/hide-empty-current-session.md @@ -0,0 +1,5 @@ +--- +"@moonshot-ai/kimi-code": patch +--- + +Hide the empty current session from the sessions picker while keeping other empty sessions visible. From 47f97c5383c0e733f55a6cce859f00e2e6c66691 Mon Sep 17 00:00:00 2001 From: liruifengv Date: Mon, 25 May 2026 14:31:58 +0800 Subject: [PATCH 3/3] fix: use TUI content state for session picker filtering --- apps/kimi-code/src/tui/kimi-tui.ts | 6 +++++- .../src/tui/utils/session-picker-rows.ts | 16 ++-------------- .../test/tui/utils/session-picker-rows.test.ts | 16 +++++++++++----- 3 files changed, 18 insertions(+), 20 deletions(-) diff --git a/apps/kimi-code/src/tui/kimi-tui.ts b/apps/kimi-code/src/tui/kimi-tui.ts index 48d44e7336..ada55d5062 100644 --- a/apps/kimi-code/src/tui/kimi-tui.ts +++ b/apps/kimi-code/src/tui/kimi-tui.ts @@ -1867,7 +1867,11 @@ export class KimiTUI { this.state.loadingSessions = true; try { const sessions = await this.harness.listSessions({ workDir: this.state.appState.workDir }); - this.state.sessions = sessionRowsForPicker(sessions, this.state.appState.sessionId); + this.state.sessions = sessionRowsForPicker( + sessions, + this.state.appState.sessionId, + this.hasSessionContent(), + ); } catch { /* silently ignore */ } finally { diff --git a/apps/kimi-code/src/tui/utils/session-picker-rows.ts b/apps/kimi-code/src/tui/utils/session-picker-rows.ts index 13a0852d3b..55e063246a 100644 --- a/apps/kimi-code/src/tui/utils/session-picker-rows.ts +++ b/apps/kimi-code/src/tui/utils/session-picker-rows.ts @@ -2,14 +2,13 @@ import type { SessionSummary } from '@moonshot-ai/kimi-code-sdk'; import type { SessionRow } from '#/tui/components/dialogs/session-picker'; -const DEFAULT_SESSION_TITLE = 'New Session'; - export function sessionRowsForPicker( sessions: readonly SessionSummary[], currentSessionId: string, + currentSessionHasContent: boolean, ): SessionRow[] { return sessions - .filter((session) => !isEmptyCurrentSessionSummary(session, currentSessionId)) + .filter((session) => currentSessionHasContent || session.id !== currentSessionId) .map((session) => ({ id: session.id, title: session.title ?? null, @@ -19,14 +18,3 @@ export function sessionRowsForPicker( metadata: session.metadata, })); } - -function isEmptyCurrentSessionSummary( - session: Pick, - currentSessionId: string, -): boolean { - if (currentSessionId.length === 0 || session.id !== currentSessionId) return false; - const lastPrompt = session.lastPrompt?.trim(); - if (lastPrompt !== undefined && lastPrompt.length > 0) return false; - const title = session.title?.trim(); - return title === undefined || title.length === 0 || title === DEFAULT_SESSION_TITLE; -} diff --git a/apps/kimi-code/test/tui/utils/session-picker-rows.test.ts b/apps/kimi-code/test/tui/utils/session-picker-rows.test.ts index ee4f654d8b..30d56969c2 100644 --- a/apps/kimi-code/test/tui/utils/session-picker-rows.test.ts +++ b/apps/kimi-code/test/tui/utils/session-picker-rows.test.ts @@ -20,19 +20,20 @@ function summary(input: { } describe('sessionRowsForPicker', () => { - it('omits the empty current session from the picker rows', () => { + it('omits the current session when the TUI session has no content', () => { const rows = sessionRowsForPicker( [ summary({ id: 'ses_current', title: 'New Session' }), summary({ id: 'ses_previous', title: 'New Session' }), ], 'ses_current', + false, ); expect(rows.map((row) => row.id)).toEqual(['ses_previous']); }); - it('keeps the current session after prompt metadata exists', () => { + it('keeps the current session when the TUI session has content', () => { const rows = sessionRowsForPicker( [ summary({ @@ -42,17 +43,22 @@ describe('sessionRowsForPicker', () => { }), ], 'ses_current', + true, ); expect(rows.map((row) => row.id)).toEqual(['ses_current']); }); - it('keeps a current session with a non-default title', () => { + it('does not filter empty historical sessions', () => { const rows = sessionRowsForPicker( - [summary({ id: 'ses_current', title: 'Pinned workspace' })], + [ + summary({ id: 'ses_current', title: 'New Session' }), + summary({ id: 'ses_previous_empty', title: 'New Session' }), + ], 'ses_current', + false, ); - expect(rows.map((row) => row.id)).toEqual(['ses_current']); + expect(rows.map((row) => row.id)).toEqual(['ses_previous_empty']); }); });