From a664226bf2244a232fd778064e2f1edf7691d268 Mon Sep 17 00:00:00 2001 From: Haozhe Date: Mon, 24 Aug 2026 21:22:15 +0800 Subject: [PATCH] fix(tui): preserve active session after provider logout (#3212) --- .changeset/calm-session-logout.md | 5 ++ apps/kimi-code/src/tui/commands/auth.ts | 1 - apps/kimi-code/src/tui/commands/provider.ts | 2 - .../src/tui/controllers/auth-flow.ts | 25 +++--- .../test/tui/kimi-tui-startup.test.ts | 87 ++++++++++++++++--- 5 files changed, 91 insertions(+), 29 deletions(-) create mode 100644 .changeset/calm-session-logout.md diff --git a/.changeset/calm-session-logout.md b/.changeset/calm-session-logout.md new file mode 100644 index 000000000..12f9d5e82 --- /dev/null +++ b/.changeset/calm-session-logout.md @@ -0,0 +1,5 @@ +--- +"@moonshot-ai/kimi-code": patch +--- + +Preserve the active session and its selected model when logging out of a provider. diff --git a/apps/kimi-code/src/tui/commands/auth.ts b/apps/kimi-code/src/tui/commands/auth.ts index 0c573a4ac..197c141d4 100644 --- a/apps/kimi-code/src/tui/commands/auth.ts +++ b/apps/kimi-code/src/tui/commands/auth.ts @@ -241,7 +241,6 @@ export async function handleLogoutCommand(host: SlashCommandHost): Promise if (target === currentProvider) { await host.authFlow.refreshConfigAfterLogout(); - await host.authFlow.clearActiveSessionAfterLogout(); } else { const updated = await host.harness.getConfig({ reload: true }); host.setAppState({ diff --git a/apps/kimi-code/src/tui/commands/provider.ts b/apps/kimi-code/src/tui/commands/provider.ts index 5e0d84b0e..417c21dfc 100644 --- a/apps/kimi-code/src/tui/commands/provider.ts +++ b/apps/kimi-code/src/tui/commands/provider.ts @@ -95,7 +95,6 @@ async function handleProviderDelete(host: SlashCommandHost, providerId: string): // to the marker/default profile, not the logged-out region. refreshKimiRegion(); await host.authFlow.refreshConfigAfterLogout(); - await host.authFlow.clearActiveSessionAfterLogout(); return; } @@ -104,7 +103,6 @@ async function handleProviderDelete(host: SlashCommandHost, providerId: string): const config = await host.harness.removeProvider(providerId); if (activeProvider === providerId) { await host.authFlow.refreshConfigAfterLogout(); - await host.authFlow.clearActiveSessionAfterLogout(); } else { host.setAppState({ availableProviders: config.providers ?? {}, diff --git a/apps/kimi-code/src/tui/controllers/auth-flow.ts b/apps/kimi-code/src/tui/controllers/auth-flow.ts index 67fac913c..0940e445a 100644 --- a/apps/kimi-code/src/tui/controllers/auth-flow.ts +++ b/apps/kimi-code/src/tui/controllers/auth-flow.ts @@ -40,7 +40,6 @@ export interface AuthFlowHost { resetSessionRuntime(): void; setSession(session: Session): Promise; syncRuntimeState(session?: Session): Promise; - closeSession(reason: string): Promise; appendStartupNotice(extra: string): void; hydrateLazyConfigDefaults(): Promise; readonly sessionEventHandler: SessionEventHandler; @@ -134,18 +133,6 @@ export class AuthFlowController { void host.refreshPluginCommands(host.session); } - async clearActiveSessionAfterLogout(): Promise { - await this.host.closeSession('logged out'); - this.host.resetSessionRuntime(); - this.host.setAppState({ - sessionId: '', - model: '', - sessionTitle: null, - }); - await this.host.refreshSkillCommands(); - await this.host.refreshPluginCommands(); - } - async refreshConfigAfterLogin(): Promise { const { host } = this; const config = await host.harness.getConfig({ reload: true }); @@ -183,9 +170,17 @@ export class AuthFlowController { async refreshConfigAfterLogout(): Promise { const config = await this.host.harness.getConfig({ reload: true }); + const availableModels = config.models ?? {}; + const availableProviders = config.providers ?? {}; + + if (this.host.session !== undefined) { + this.host.setAppState({ availableModels, availableProviders }); + return; + } + this.host.setAppState({ - availableModels: config.models ?? {}, - availableProviders: config.providers ?? {}, + availableModels, + availableProviders, model: '', thinkingEffort: 'off', maxContextTokens: 0, diff --git a/apps/kimi-code/test/tui/kimi-tui-startup.test.ts b/apps/kimi-code/test/tui/kimi-tui-startup.test.ts index b2184ee9b..0aea0a76d 100644 --- a/apps/kimi-code/test/tui/kimi-tui-startup.test.ts +++ b/apps/kimi-code/test/tui/kimi-tui-startup.test.ts @@ -1904,21 +1904,33 @@ describe('KimiTUI startup', () => { } }); - it('tracks logout after managed credentials and session state are cleared', async () => { + it('tracks logout while preserving the active session model', async () => { + let loggedOut = false; const session = makeSession(); + const logout = vi.fn(async () => { + loggedOut = true; + }); const harness = makeHarness(session, { - getConfig: vi.fn(async () => ({ - models: { - k2: { provider: 'managed:kimi-code', model: 'moonshot-v1', maxContextSize: 100 }, - }, - providers: { 'managed:kimi-code': { type: 'kimi' } }, - })), + getConfig: vi.fn(async () => + loggedOut + ? { models: {}, providers: {} } + : { + models: { + k2: { + provider: 'managed:kimi-code', + model: 'moonshot-v1', + maxContextSize: 100, + }, + }, + providers: { 'managed:kimi-code': { type: 'kimi' } }, + }, + ), auth: { status: vi.fn(async () => ({ providers: [{ providerName: 'managed:kimi-code', hasToken: true }], })), login: vi.fn(async () => {}), - logout: vi.fn(), + logout, getManagedUsage: vi.fn(), }, }); @@ -1931,13 +1943,66 @@ describe('KimiTUI startup', () => { await handleLogoutCommand(driver as any); expect(harness.auth.logout).toHaveBeenCalledWith('managed:kimi-code'); - expect(session.close).toHaveBeenCalledOnce(); + expect(session.close).not.toHaveBeenCalled(); + expect(driver.state.appState).toMatchObject({ + sessionId: 'ses-1', + model: 'k2', + sessionTitle: 'Session title', + contextTokens: 10, + maxContextTokens: 100, + availableModels: {}, + availableProviders: {}, + }); + expect(harness.track).toHaveBeenCalledWith('logout', { provider: 'managed:kimi-code' }); + }); + + it('clears the config-derived model when logging out without an active session', async () => { + let loggedOut = false; + const logout = vi.fn(async () => { + loggedOut = true; + }); + const harness = makeHarness(makeSession(), { + getConfig: vi.fn(async () => + loggedOut + ? { models: {}, providers: {} } + : { + models: { + k2: { + provider: 'managed:kimi-code', + model: 'moonshot-v1', + maxContextSize: 100, + }, + }, + providers: { 'managed:kimi-code': { type: 'kimi' } }, + defaultModel: 'k2', + }, + ), + auth: { + status: vi.fn(async () => ({ + providers: [{ providerName: 'managed:kimi-code', hasToken: true }], + })), + login: vi.fn(async () => {}), + logout, + getManagedUsage: vi.fn(), + }, + }); + const driver = makeDriver(harness, { ...makeStartupInput(), engineV2: true }); + + await expect(driver.init()).resolves.toBe(false); + expect(driver.state.appState.model).toBe('k2'); + + vi.mocked(promptLogoutProviderSelection).mockResolvedValue('managed:kimi-code'); + await handleLogoutCommand(driver as any); + + expect(harness.createSession).not.toHaveBeenCalled(); expect(driver.state.appState).toMatchObject({ sessionId: '', model: '', - sessionTitle: null, + contextTokens: 0, + maxContextTokens: 0, + availableModels: {}, + availableProviders: {}, }); - expect(harness.track).toHaveBeenCalledWith('logout', { provider: 'managed:kimi-code' }); }); it('keeps the active session when logging out a different provider', async () => {