fix(tui): preserve active session after provider logout (#3212)

This commit is contained in:
Haozhe 2026-08-24 21:22:15 +08:00 committed by GitHub
parent d3b27cc778
commit a664226bf2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 91 additions and 29 deletions

View file

@ -0,0 +1,5 @@
---
"@moonshot-ai/kimi-code": patch
---
Preserve the active session and its selected model when logging out of a provider.

View file

@ -241,7 +241,6 @@ export async function handleLogoutCommand(host: SlashCommandHost): Promise<void>
if (target === currentProvider) {
await host.authFlow.refreshConfigAfterLogout();
await host.authFlow.clearActiveSessionAfterLogout();
} else {
const updated = await host.harness.getConfig({ reload: true });
host.setAppState({

View file

@ -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 ?? {},

View file

@ -40,7 +40,6 @@ export interface AuthFlowHost {
resetSessionRuntime(): void;
setSession(session: Session): Promise<void>;
syncRuntimeState(session?: Session): Promise<void>;
closeSession(reason: string): Promise<void>;
appendStartupNotice(extra: string): void;
hydrateLazyConfigDefaults(): Promise<void>;
readonly sessionEventHandler: SessionEventHandler;
@ -134,18 +133,6 @@ export class AuthFlowController {
void host.refreshPluginCommands(host.session);
}
async clearActiveSessionAfterLogout(): Promise<void> {
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<void> {
const { host } = this;
const config = await host.harness.getConfig({ reload: true });
@ -183,9 +170,17 @@ export class AuthFlowController {
async refreshConfigAfterLogout(): Promise<void> {
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,

View file

@ -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 () => {