From 28a5370eb7240fe38cfb1b2a005ff940b6a8bc05 Mon Sep 17 00:00:00 2001 From: liruifengv Date: Mon, 25 May 2026 17:27:11 +0800 Subject: [PATCH 1/8] Persist runtime model and default thinking --- .changeset/persist-runtime-model-defaults.md | 6 ++++ packages/agent-core/src/rpc/core-impl.ts | 32 ++++++++++++++++--- .../test/create-session-transport.test.ts | 29 +++++++++++++++++ .../node-sdk/test/session-set-model.test.ts | 6 ++++ .../test/session-set-thinking.test.ts | 32 +++++++++++++++++++ 5 files changed, 101 insertions(+), 4 deletions(-) create mode 100644 .changeset/persist-runtime-model-defaults.md diff --git a/.changeset/persist-runtime-model-defaults.md b/.changeset/persist-runtime-model-defaults.md new file mode 100644 index 0000000000..38c1eb6878 --- /dev/null +++ b/.changeset/persist-runtime-model-defaults.md @@ -0,0 +1,6 @@ +--- +"@moonshot-ai/agent-core": patch +"@moonshot-ai/kimi-code": patch +--- + +Persist runtime model and thinking changes to the default configuration, and honor the configured default thinking state for new sessions. diff --git a/packages/agent-core/src/rpc/core-impl.ts b/packages/agent-core/src/rpc/core-impl.ts index 189ae276da..198ad5fb16 100644 --- a/packages/agent-core/src/rpc/core-impl.ts +++ b/packages/agent-core/src/rpc/core-impl.ts @@ -143,7 +143,9 @@ export class KimiCore implements PromisableMethods { const config = this.reloadProviderManager(); const id = options.id ?? createSessionId(); const modelName = this.providerManager.resolveSelectedModel(options.model); - const thinkingLevel = this.providerManager.resolveThinkingLevel(options.thinking); + const thinkingLevel = this.providerManager.resolveThinkingLevel( + options.thinking ?? defaultThinkingToRuntime(config.defaultThinking), + ); const permissionMode = options.permission ?? config.defaultPermissionMode; const mcpConfig = await resolveSessionMcpConfig({ cwd: workDir, @@ -394,11 +396,16 @@ export class KimiCore implements PromisableMethods { ...payload }: SessionAgentPayload): Promise { this.reloadProviderManager(); - return this.sessionApi(sessionId).setModel(payload); + const result = await this.sessionApi(sessionId).setModel(payload); + await this.persistDefaultModel(result.model); + return result; } - setThinking({ sessionId, ...payload }: SessionAgentPayload) { - return this.sessionApi(sessionId).setThinking(payload); + async setThinking({ sessionId, ...payload }: SessionAgentPayload) { + const api = this.sessionApi(sessionId); + await api.setThinking(payload); + const config = await api.getConfig({ agentId: payload.agentId }); + await this.persistDefaultThinking(config.thinkingLevel !== 'off'); } setPermission({ sessionId, ...payload }: SessionAgentPayload) { @@ -572,6 +579,18 @@ export class KimiCore implements PromisableMethods { return config; } + private async persistDefaultModel(model: string): Promise { + const config = readConfigFile(this.configPath); + if (config.defaultModel === model) return; + await this.setKimiConfig({ defaultModel: model }); + } + + private async persistDefaultThinking(defaultThinking: boolean): Promise { + const config = readConfigFile(this.configPath); + if (config.defaultThinking === defaultThinking) return; + await this.setKimiConfig({ defaultThinking }); + } + private async refreshSessionRuntimeConfig( session: Session, config: KimiConfig, @@ -680,6 +699,11 @@ function requiredWorkDir(operation: string, value: string): string { return normalizeWorkDir(value); } +function defaultThinkingToRuntime(defaultThinking: boolean | undefined): string | undefined { + if (defaultThinking === undefined) return undefined; + return defaultThinking ? 'on' : 'off'; +} + function createSessionId(): string { return `session_${randomUUID()}`; } diff --git a/packages/node-sdk/test/create-session-transport.test.ts b/packages/node-sdk/test/create-session-transport.test.ts index aa60174ea4..421544d64c 100644 --- a/packages/node-sdk/test/create-session-transport.test.ts +++ b/packages/node-sdk/test/create-session-transport.test.ts @@ -460,6 +460,35 @@ effort = "medium" } }); + it('applies configured default thinking to new sessions', async () => { + const homeDir = await makeTempDir(); + const workDir = await makeTempDir(); + await writeFile(join(homeDir, 'config.toml'), 'default_thinking = false\n', 'utf-8'); + const harness = new KimiHarness({ + identity: TEST_IDENTITY, + homeDir, + }); + + try { + const offSession = await harness.createSession({ + id: 'ses_default_thinking_off', + workDir, + }); + + await expect(offSession.getStatus()).resolves.toMatchObject({ thinkingLevel: 'off' }); + + await harness.setConfig({ defaultThinking: true }); + const onSession = await harness.createSession({ + id: 'ses_default_thinking_on', + workDir, + }); + + await expect(onSession.getStatus()).resolves.toMatchObject({ thinkingLevel: 'high' }); + } finally { + await harness.close(); + } + }); + it('applies configured default permission mode to new sessions', async () => { const homeDir = await makeTempDir(); const workDir = await makeTempDir(); diff --git a/packages/node-sdk/test/session-set-model.test.ts b/packages/node-sdk/test/session-set-model.test.ts index b3bcdcf837..f1399c827f 100644 --- a/packages/node-sdk/test/session-set-model.test.ts +++ b/packages/node-sdk/test/session-set-model.test.ts @@ -41,6 +41,9 @@ describe('Session.setModel', () => { await session.setModel('next-model'); await expect(session.getStatus()).resolves.toMatchObject({ model: 'next-model' }); + await expect(harness.getConfig({ reload: true })).resolves.toMatchObject({ + defaultModel: 'next-model', + }); const configEvent = await waitForAgentWireEvent( homeDir, session.id, @@ -98,6 +101,9 @@ describe('Session.setModel', () => { await expect(session.getStatus()).resolves.toMatchObject({ model: 'kimi-code/kimi-for-coding', }); + await expect(harness.getConfig({ reload: true })).resolves.toMatchObject({ + defaultModel: 'kimi-code/kimi-for-coding', + }); const configEvent = await waitForAgentWireEvent( homeDir, session.id, diff --git a/packages/node-sdk/test/session-set-thinking.test.ts b/packages/node-sdk/test/session-set-thinking.test.ts index 70e957a667..4b78351534 100644 --- a/packages/node-sdk/test/session-set-thinking.test.ts +++ b/packages/node-sdk/test/session-set-thinking.test.ts @@ -33,6 +33,38 @@ describe('Session.setThinking', () => { type: 'config.update', thinkingLevel: 'low', }); + await expect(harness.getConfig({ reload: true })).resolves.toMatchObject({ + defaultThinking: true, + }); + } finally { + await harness.close(); + } + }); + + it('persists default thinking off from the effective thinking state', async () => { + const homeDir = await makeTempDir(tempDirs, 'kimi-sdk-thinking-home-'); + const workDir = await makeTempDir(tempDirs, 'kimi-sdk-thinking-work-'); + const harness = new KimiHarness({ homeDir, identity: TEST_IDENTITY }); + + try { + const session = await harness.createSession({ id: 'ses_thinking_off', workDir }); + + await session.setThinking('off'); + + await expect( + waitForAgentWireEvent( + homeDir, + session.id, + 'config.update', + (event) => event['thinkingLevel'] === 'off', + ), + ).resolves.toMatchObject({ + type: 'config.update', + thinkingLevel: 'off', + }); + await expect(harness.getConfig({ reload: true })).resolves.toMatchObject({ + defaultThinking: false, + }); } finally { await harness.close(); } From 5d96b97654bab5696bb75bad55df9503665e50f9 Mon Sep 17 00:00:00 2001 From: liruifengv Date: Mon, 25 May 2026 18:08:35 +0800 Subject: [PATCH 2/8] fix: persist model defaults from TUI --- .changeset/persist-runtime-model-defaults.md | 2 +- apps/kimi-code/src/tui/kimi-tui.ts | 65 +++++++++++++------ .../test/tui/kimi-tui-message-flow.test.ts | 45 +++++++++++++ packages/agent-core/src/rpc/core-impl.ts | 23 +------ .../node-sdk/test/session-set-model.test.ts | 6 -- .../test/session-set-thinking.test.ts | 32 --------- 6 files changed, 93 insertions(+), 80 deletions(-) diff --git a/.changeset/persist-runtime-model-defaults.md b/.changeset/persist-runtime-model-defaults.md index 38c1eb6878..997cbcff6a 100644 --- a/.changeset/persist-runtime-model-defaults.md +++ b/.changeset/persist-runtime-model-defaults.md @@ -3,4 +3,4 @@ "@moonshot-ai/kimi-code": patch --- -Persist runtime model and thinking changes to the default configuration, and honor the configured default thinking state for new sessions. +Persist model selections from the terminal UI to the default configuration, and honor the configured default thinking state for new sessions. diff --git a/apps/kimi-code/src/tui/kimi-tui.ts b/apps/kimi-code/src/tui/kimi-tui.ts index 28727c01d3..91ea2549bc 100644 --- a/apps/kimi-code/src/tui/kimi-tui.ts +++ b/apps/kimi-code/src/tui/kimi-tui.ts @@ -4349,20 +4349,16 @@ export class KimiTUI { return; } - if (alias === this.state.appState.model && thinking === this.state.appState.thinking) { - this.showStatus(`Already using ${alias} with thinking ${thinking ? 'on' : 'off'}.`); - return; - } - const level = thinking ? 'on' : 'off'; const prevModel = this.state.appState.model; const prevThinking = this.state.appState.thinking; + const runtimeChanged = alias !== prevModel || thinking !== prevThinking; + const session = this.session; try { - const session = this.session; - if (session === undefined) { + if (session === undefined && runtimeChanged) { await this.activateModelAfterLogin(alias, thinking); - } else { + } else if (session !== undefined) { if (alias !== prevModel) { await session.setModel(alias); } @@ -4370,23 +4366,50 @@ export class KimiTUI { await session.setThinking(level); } } - this.setAppState({ model: alias, thinking }); - if (session === undefined) { - if (alias !== prevModel) { - this.track('model_switch', { model: alias }); - } - if (thinking !== prevThinking) { - this.track('thinking_toggle', { enabled: thinking }); - } - } - this.showStatus( - `Switched to ${alias} with thinking ${level}.`, - this.state.theme.colors.success, - ); } catch (error) { const msg = formatErrorMessage(error); this.showError(`Failed to switch model: ${msg}`); + return; } + + this.setAppState({ model: alias, thinking }); + if (session === undefined && runtimeChanged) { + if (alias !== prevModel) { + this.track('model_switch', { model: alias }); + } + if (thinking !== prevThinking) { + this.track('thinking_toggle', { enabled: thinking }); + } + } + + let persisted = false; + try { + persisted = await this.persistModelSelection(alias, thinking); + } catch (error) { + const msg = formatErrorMessage(error); + this.showError(`Switched to ${alias}, but failed to save default: ${msg}`); + return; + } + + const status = runtimeChanged + ? `Switched to ${alias} with thinking ${level}.` + : persisted + ? `Saved ${alias} with thinking ${level} as default.` + : `Already using ${alias} with thinking ${level}.`; + this.showStatus(status, this.state.theme.colors.success); + } + + // Persists the selected model and thinking state as the startup defaults. + private async persistModelSelection(alias: string, thinking: boolean): Promise { + const config = await this.harness.getConfig({ reload: true }); + if (config.defaultModel === alias && config.defaultThinking === thinking) { + return false; + } + await this.harness.setConfig({ + defaultModel: alias, + defaultThinking: thinking, + }); + return true; } // Shows the theme selector. diff --git a/apps/kimi-code/test/tui/kimi-tui-message-flow.test.ts b/apps/kimi-code/test/tui/kimi-tui-message-flow.test.ts index 4edeb7780c..e3b683aee5 100644 --- a/apps/kimi-code/test/tui/kimi-tui-message-flow.test.ts +++ b/apps/kimi-code/test/tui/kimi-tui-message-flow.test.ts @@ -117,6 +117,7 @@ function makeHarness(session = makeSession(), overrides: Record k2: { model: 'moonshot-v1', maxContextSize: 100 }, }, })), + setConfig: vi.fn(async () => ({ providers: {} })), createSession: vi.fn(async () => session), resumeSession: vi.fn(async () => session), forkSession: vi.fn(async () => session), @@ -1026,6 +1027,7 @@ describe('KimiTUI message flow', () => { it('applies /model selection with inline thinking state', async () => { const session = makeSession(); + const setConfig = vi.fn(async () => ({ providers: {} })); const { driver } = await makeDriver(session, { getConfig: vi.fn(async () => ({ models: { @@ -1044,7 +1046,10 @@ describe('KimiTUI message flow', () => { capabilities: ['thinking'], }, }, + defaultModel: 'k2', + defaultThinking: false, })), + setConfig, }); driver.handleUserInput('/model turbo'); @@ -1060,11 +1065,51 @@ describe('KimiTUI message flow', () => { await vi.waitFor(() => { expect(session.setModel).toHaveBeenCalledWith('turbo'); expect(session.setThinking).toHaveBeenCalledWith('on'); + expect(setConfig).toHaveBeenCalledWith({ + defaultModel: 'turbo', + defaultThinking: true, + }); }); expect(driver.state.appState.model).toBe('turbo'); expect(driver.state.appState.thinking).toBe(true); }); + it('persists /model selection even when runtime state is unchanged', async () => { + const session = makeSession(); + const setConfig = vi.fn(async () => ({ providers: {} })); + const { driver } = await makeDriver(session, { + getConfig: vi.fn(async () => ({ + models: { + k2: { + provider: 'managed:kimi-code', + model: 'kimi-k2', + maxContextSize: 100, + displayName: 'Kimi K2', + capabilities: ['thinking'], + }, + }, + defaultModel: 'old-default', + defaultThinking: true, + })), + setConfig, + }); + + driver.handleUserInput('/model k2'); + + const picker = driver.state.editorContainer.children[0]; + expect(picker).toBeInstanceOf(ModelSelectorComponent); + (picker as ModelSelectorComponent).handleInput('\r'); + + await vi.waitFor(() => { + expect(setConfig).toHaveBeenCalledWith({ + defaultModel: 'k2', + defaultThinking: false, + }); + }); + expect(session.setModel).not.toHaveBeenCalled(); + expect(session.setThinking).not.toHaveBeenCalled(); + }); + it('deletes Kitty inline images when /new clears the transcript', async () => { setCapabilities({ images: 'kitty', trueColor: true, hyperlinks: true }); const { driver, harness } = await makeDriver(makeSession({ id: 'ses-1' })); diff --git a/packages/agent-core/src/rpc/core-impl.ts b/packages/agent-core/src/rpc/core-impl.ts index 198ad5fb16..0368cdf5b3 100644 --- a/packages/agent-core/src/rpc/core-impl.ts +++ b/packages/agent-core/src/rpc/core-impl.ts @@ -396,16 +396,11 @@ export class KimiCore implements PromisableMethods { ...payload }: SessionAgentPayload): Promise { this.reloadProviderManager(); - const result = await this.sessionApi(sessionId).setModel(payload); - await this.persistDefaultModel(result.model); - return result; + return this.sessionApi(sessionId).setModel(payload); } - async setThinking({ sessionId, ...payload }: SessionAgentPayload) { - const api = this.sessionApi(sessionId); - await api.setThinking(payload); - const config = await api.getConfig({ agentId: payload.agentId }); - await this.persistDefaultThinking(config.thinkingLevel !== 'off'); + setThinking({ sessionId, ...payload }: SessionAgentPayload) { + return this.sessionApi(sessionId).setThinking(payload); } setPermission({ sessionId, ...payload }: SessionAgentPayload) { @@ -579,18 +574,6 @@ export class KimiCore implements PromisableMethods { return config; } - private async persistDefaultModel(model: string): Promise { - const config = readConfigFile(this.configPath); - if (config.defaultModel === model) return; - await this.setKimiConfig({ defaultModel: model }); - } - - private async persistDefaultThinking(defaultThinking: boolean): Promise { - const config = readConfigFile(this.configPath); - if (config.defaultThinking === defaultThinking) return; - await this.setKimiConfig({ defaultThinking }); - } - private async refreshSessionRuntimeConfig( session: Session, config: KimiConfig, diff --git a/packages/node-sdk/test/session-set-model.test.ts b/packages/node-sdk/test/session-set-model.test.ts index f1399c827f..b3bcdcf837 100644 --- a/packages/node-sdk/test/session-set-model.test.ts +++ b/packages/node-sdk/test/session-set-model.test.ts @@ -41,9 +41,6 @@ describe('Session.setModel', () => { await session.setModel('next-model'); await expect(session.getStatus()).resolves.toMatchObject({ model: 'next-model' }); - await expect(harness.getConfig({ reload: true })).resolves.toMatchObject({ - defaultModel: 'next-model', - }); const configEvent = await waitForAgentWireEvent( homeDir, session.id, @@ -101,9 +98,6 @@ describe('Session.setModel', () => { await expect(session.getStatus()).resolves.toMatchObject({ model: 'kimi-code/kimi-for-coding', }); - await expect(harness.getConfig({ reload: true })).resolves.toMatchObject({ - defaultModel: 'kimi-code/kimi-for-coding', - }); const configEvent = await waitForAgentWireEvent( homeDir, session.id, diff --git a/packages/node-sdk/test/session-set-thinking.test.ts b/packages/node-sdk/test/session-set-thinking.test.ts index 4b78351534..70e957a667 100644 --- a/packages/node-sdk/test/session-set-thinking.test.ts +++ b/packages/node-sdk/test/session-set-thinking.test.ts @@ -33,38 +33,6 @@ describe('Session.setThinking', () => { type: 'config.update', thinkingLevel: 'low', }); - await expect(harness.getConfig({ reload: true })).resolves.toMatchObject({ - defaultThinking: true, - }); - } finally { - await harness.close(); - } - }); - - it('persists default thinking off from the effective thinking state', async () => { - const homeDir = await makeTempDir(tempDirs, 'kimi-sdk-thinking-home-'); - const workDir = await makeTempDir(tempDirs, 'kimi-sdk-thinking-work-'); - const harness = new KimiHarness({ homeDir, identity: TEST_IDENTITY }); - - try { - const session = await harness.createSession({ id: 'ses_thinking_off', workDir }); - - await session.setThinking('off'); - - await expect( - waitForAgentWireEvent( - homeDir, - session.id, - 'config.update', - (event) => event['thinkingLevel'] === 'off', - ), - ).resolves.toMatchObject({ - type: 'config.update', - thinkingLevel: 'off', - }); - await expect(harness.getConfig({ reload: true })).resolves.toMatchObject({ - defaultThinking: false, - }); } finally { await harness.close(); } From ee261d9af84609839245ae9ddbc8fae35e308470 Mon Sep 17 00:00:00 2001 From: liruifengv Date: Mon, 25 May 2026 18:21:01 +0800 Subject: [PATCH 3/8] refactor: centralize default thinking resolution --- .../agent-core/src/providers/provider-manager.ts | 10 +++++++++- packages/agent-core/src/rpc/core-impl.ts | 9 +-------- .../test/harness/runtime-provider.test.ts | 14 ++++++++++++-- 3 files changed, 22 insertions(+), 11 deletions(-) diff --git a/packages/agent-core/src/providers/provider-manager.ts b/packages/agent-core/src/providers/provider-manager.ts index 7c2bc14f1a..50f51ea8f2 100644 --- a/packages/agent-core/src/providers/provider-manager.ts +++ b/packages/agent-core/src/providers/provider-manager.ts @@ -113,7 +113,10 @@ export class ProviderManager { } resolveThinkingLevel(requestedThinking?: string): ThinkingEffort { - return resolveThinkingEffort(requestedThinking, this.state.config.thinking); + return resolveThinkingEffort( + requestedThinking ?? defaultThinkingToRuntime(this.state.config.defaultThinking), + this.state.config.thinking, + ); } resolveSelectedModel(requestedModel: string | undefined): string | undefined { @@ -137,3 +140,8 @@ function normalizeString(value: string | undefined): string | undefined { const trimmed = value.trim(); return trimmed.length > 0 ? trimmed : undefined; } + +function defaultThinkingToRuntime(defaultThinking: boolean | undefined): string | undefined { + if (defaultThinking === undefined) return undefined; + return defaultThinking ? 'on' : 'off'; +} diff --git a/packages/agent-core/src/rpc/core-impl.ts b/packages/agent-core/src/rpc/core-impl.ts index 0368cdf5b3..189ae276da 100644 --- a/packages/agent-core/src/rpc/core-impl.ts +++ b/packages/agent-core/src/rpc/core-impl.ts @@ -143,9 +143,7 @@ export class KimiCore implements PromisableMethods { const config = this.reloadProviderManager(); const id = options.id ?? createSessionId(); const modelName = this.providerManager.resolveSelectedModel(options.model); - const thinkingLevel = this.providerManager.resolveThinkingLevel( - options.thinking ?? defaultThinkingToRuntime(config.defaultThinking), - ); + const thinkingLevel = this.providerManager.resolveThinkingLevel(options.thinking); const permissionMode = options.permission ?? config.defaultPermissionMode; const mcpConfig = await resolveSessionMcpConfig({ cwd: workDir, @@ -682,11 +680,6 @@ function requiredWorkDir(operation: string, value: string): string { return normalizeWorkDir(value); } -function defaultThinkingToRuntime(defaultThinking: boolean | undefined): string | undefined { - if (defaultThinking === undefined) return undefined; - return defaultThinking ? 'on' : 'off'; -} - function createSessionId(): string { return `session_${randomUUID()}`; } diff --git a/packages/agent-core/test/harness/runtime-provider.test.ts b/packages/agent-core/test/harness/runtime-provider.test.ts index 31264e8003..4466c9c7db 100644 --- a/packages/agent-core/test/harness/runtime-provider.test.ts +++ b/packages/agent-core/test/harness/runtime-provider.test.ts @@ -585,16 +585,26 @@ describe('ProviderManager thinking level', () => { const manager = new ProviderManager({ config: { providers: {}, + defaultThinking: false, thinking: { effort: 'medium', mode: 'auto' }, }, }); expect(manager.resolveThinkingLevel('on')).toBe('medium'); expect(manager.resolveThinkingLevel('off')).toBe('off'); expect(manager.resolveThinkingLevel('low')).toBe('low'); - expect(manager.resolveThinkingLevel()).toBe('medium'); + expect(manager.resolveThinkingLevel()).toBe('off'); + + const managerOnByDefault = new ProviderManager({ + config: { + providers: {}, + defaultThinking: true, + thinking: { effort: 'medium', mode: 'auto' }, + }, + }); + expect(managerOnByDefault.resolveThinkingLevel()).toBe('medium'); const managerWithoutEffort = new ProviderManager({ - config: { providers: {}, thinking: { mode: 'auto' } }, + config: { providers: {}, defaultThinking: true, thinking: { mode: 'auto' } }, }); expect(managerWithoutEffort.resolveThinkingLevel('on')).toBe('high'); expect(managerWithoutEffort.resolveThinkingLevel()).toBe('high'); From cf98059b8dd6605757762ffb50ea5f3fa294cbf3 Mon Sep 17 00:00:00 2001 From: liruifengv Date: Mon, 25 May 2026 18:29:39 +0800 Subject: [PATCH 4/8] fix: wait for background shutdown notifications --- .changeset/persist-runtime-model-defaults.md | 2 +- packages/agent-core/src/agent/background/index.ts | 15 ++++++++++++++- packages/agent-core/src/session/index.ts | 7 ++++--- 3 files changed, 19 insertions(+), 5 deletions(-) diff --git a/.changeset/persist-runtime-model-defaults.md b/.changeset/persist-runtime-model-defaults.md index 997cbcff6a..b7b6584a74 100644 --- a/.changeset/persist-runtime-model-defaults.md +++ b/.changeset/persist-runtime-model-defaults.md @@ -3,4 +3,4 @@ "@moonshot-ai/kimi-code": patch --- -Persist model selections from the terminal UI to the default configuration, and honor the configured default thinking state for new sessions. +Persist model selections from the terminal UI to the default configuration, honor the configured default thinking state for new sessions, and wait for background task terminal notifications during session shutdown before flushing records. diff --git a/packages/agent-core/src/agent/background/index.ts b/packages/agent-core/src/agent/background/index.ts index 14af9fa52e..b9f0f9e0ac 100644 --- a/packages/agent-core/src/agent/background/index.ts +++ b/packages/agent-core/src/agent/background/index.ts @@ -35,6 +35,7 @@ const NOTIFICATION_TAIL_BYTES = 3_000; export class BackgroundManager extends BackgroundProcessManager { private readonly scheduledNotificationKeys = new Set(); private readonly deliveredNotificationKeys = new Set(); + private readonly terminalNotificationPromises = new Set>(); constructor( public readonly agent: Agent, @@ -85,7 +86,18 @@ export class BackgroundManager extends BackgroundProcessManager { } protected override onLiveTaskTerminal(info: BackgroundTaskInfo): void | Promise { - return this.notifyBackgroundTask(info); + const promise = this.notifyBackgroundTask(info).catch(() => {}); + this.terminalNotificationPromises.add(promise); + void promise.finally(() => { + this.terminalNotificationPromises.delete(promise); + }); + return promise; + } + + async settleTerminalNotifications(): Promise { + while (this.terminalNotificationPromises.size > 0) { + await Promise.all(Array.from(this.terminalNotificationPromises)); + } } private async restoreBackgroundTaskNotifications(): Promise { @@ -183,6 +195,7 @@ export class BackgroundManager extends BackgroundProcessManager { super._reset(); this.scheduledNotificationKeys.clear(); this.deliveredNotificationKeys.clear(); + this.terminalNotificationPromises.clear(); } } diff --git a/packages/agent-core/src/session/index.ts b/packages/agent-core/src/session/index.ts index b4c0adba5d..51184027e4 100644 --- a/packages/agent-core/src/session/index.ts +++ b/packages/agent-core/src/session/index.ts @@ -194,9 +194,10 @@ export class Session { }); if (keepAliveOnExit) return; await Promise.all( - Array.from(this.agents.values(), (agent) => - agent.background.stopAll('Session closed'), - ), + Array.from(this.agents.values(), async (agent) => { + await agent.background.stopAll('Session closed'); + await agent.background.settleTerminalNotifications(); + }), ); } From 77a2701ab9f731e1506e3fb52701f7de05aa556f Mon Sep 17 00:00:00 2001 From: liruifengv Date: Mon, 25 May 2026 18:35:12 +0800 Subject: [PATCH 5/8] Revert "fix: wait for background shutdown notifications" This reverts commit cf98059b8dd6605757762ffb50ea5f3fa294cbf3. --- .changeset/persist-runtime-model-defaults.md | 2 +- packages/agent-core/src/agent/background/index.ts | 15 +-------------- packages/agent-core/src/session/index.ts | 7 +++---- 3 files changed, 5 insertions(+), 19 deletions(-) diff --git a/.changeset/persist-runtime-model-defaults.md b/.changeset/persist-runtime-model-defaults.md index b7b6584a74..997cbcff6a 100644 --- a/.changeset/persist-runtime-model-defaults.md +++ b/.changeset/persist-runtime-model-defaults.md @@ -3,4 +3,4 @@ "@moonshot-ai/kimi-code": patch --- -Persist model selections from the terminal UI to the default configuration, honor the configured default thinking state for new sessions, and wait for background task terminal notifications during session shutdown before flushing records. +Persist model selections from the terminal UI to the default configuration, and honor the configured default thinking state for new sessions. diff --git a/packages/agent-core/src/agent/background/index.ts b/packages/agent-core/src/agent/background/index.ts index b9f0f9e0ac..14af9fa52e 100644 --- a/packages/agent-core/src/agent/background/index.ts +++ b/packages/agent-core/src/agent/background/index.ts @@ -35,7 +35,6 @@ const NOTIFICATION_TAIL_BYTES = 3_000; export class BackgroundManager extends BackgroundProcessManager { private readonly scheduledNotificationKeys = new Set(); private readonly deliveredNotificationKeys = new Set(); - private readonly terminalNotificationPromises = new Set>(); constructor( public readonly agent: Agent, @@ -86,18 +85,7 @@ export class BackgroundManager extends BackgroundProcessManager { } protected override onLiveTaskTerminal(info: BackgroundTaskInfo): void | Promise { - const promise = this.notifyBackgroundTask(info).catch(() => {}); - this.terminalNotificationPromises.add(promise); - void promise.finally(() => { - this.terminalNotificationPromises.delete(promise); - }); - return promise; - } - - async settleTerminalNotifications(): Promise { - while (this.terminalNotificationPromises.size > 0) { - await Promise.all(Array.from(this.terminalNotificationPromises)); - } + return this.notifyBackgroundTask(info); } private async restoreBackgroundTaskNotifications(): Promise { @@ -195,7 +183,6 @@ export class BackgroundManager extends BackgroundProcessManager { super._reset(); this.scheduledNotificationKeys.clear(); this.deliveredNotificationKeys.clear(); - this.terminalNotificationPromises.clear(); } } diff --git a/packages/agent-core/src/session/index.ts b/packages/agent-core/src/session/index.ts index 51184027e4..b4c0adba5d 100644 --- a/packages/agent-core/src/session/index.ts +++ b/packages/agent-core/src/session/index.ts @@ -194,10 +194,9 @@ export class Session { }); if (keepAliveOnExit) return; await Promise.all( - Array.from(this.agents.values(), async (agent) => { - await agent.background.stopAll('Session closed'); - await agent.background.settleTerminalNotifications(); - }), + Array.from(this.agents.values(), (agent) => + agent.background.stopAll('Session closed'), + ), ); } From c4564b6bd7645151fbd2028ae7f11d7e535a7bb0 Mon Sep 17 00:00:00 2001 From: liruifengv Date: Mon, 25 May 2026 18:53:59 +0800 Subject: [PATCH 6/8] test: remove SDK default thinking case --- .../test/create-session-transport.test.ts | 29 ------------------- 1 file changed, 29 deletions(-) diff --git a/packages/node-sdk/test/create-session-transport.test.ts b/packages/node-sdk/test/create-session-transport.test.ts index 421544d64c..aa60174ea4 100644 --- a/packages/node-sdk/test/create-session-transport.test.ts +++ b/packages/node-sdk/test/create-session-transport.test.ts @@ -460,35 +460,6 @@ effort = "medium" } }); - it('applies configured default thinking to new sessions', async () => { - const homeDir = await makeTempDir(); - const workDir = await makeTempDir(); - await writeFile(join(homeDir, 'config.toml'), 'default_thinking = false\n', 'utf-8'); - const harness = new KimiHarness({ - identity: TEST_IDENTITY, - homeDir, - }); - - try { - const offSession = await harness.createSession({ - id: 'ses_default_thinking_off', - workDir, - }); - - await expect(offSession.getStatus()).resolves.toMatchObject({ thinkingLevel: 'off' }); - - await harness.setConfig({ defaultThinking: true }); - const onSession = await harness.createSession({ - id: 'ses_default_thinking_on', - workDir, - }); - - await expect(onSession.getStatus()).resolves.toMatchObject({ thinkingLevel: 'high' }); - } finally { - await harness.close(); - } - }); - it('applies configured default permission mode to new sessions', async () => { const homeDir = await makeTempDir(); const workDir = await makeTempDir(); From 9eda59b64dbf66b7dfbc123fee29bb0aab380ce9 Mon Sep 17 00:00:00 2001 From: liruifengv Date: Mon, 25 May 2026 18:58:35 +0800 Subject: [PATCH 7/8] fix: respect thinking mode off defaults --- packages/agent-core/src/providers/provider-manager.ts | 7 +++---- .../agent-core/test/harness/runtime-provider.test.ts | 9 +++++++++ 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/packages/agent-core/src/providers/provider-manager.ts b/packages/agent-core/src/providers/provider-manager.ts index 50f51ea8f2..17489e6d51 100644 --- a/packages/agent-core/src/providers/provider-manager.ts +++ b/packages/agent-core/src/providers/provider-manager.ts @@ -114,7 +114,7 @@ export class ProviderManager { resolveThinkingLevel(requestedThinking?: string): ThinkingEffort { return resolveThinkingEffort( - requestedThinking ?? defaultThinkingToRuntime(this.state.config.defaultThinking), + requestedThinking ?? defaultThinkingDisableOverride(this.state.config.defaultThinking), this.state.config.thinking, ); } @@ -141,7 +141,6 @@ function normalizeString(value: string | undefined): string | undefined { return trimmed.length > 0 ? trimmed : undefined; } -function defaultThinkingToRuntime(defaultThinking: boolean | undefined): string | undefined { - if (defaultThinking === undefined) return undefined; - return defaultThinking ? 'on' : 'off'; +function defaultThinkingDisableOverride(defaultThinking: boolean | undefined): string | undefined { + return defaultThinking === false ? 'off' : undefined; } diff --git a/packages/agent-core/test/harness/runtime-provider.test.ts b/packages/agent-core/test/harness/runtime-provider.test.ts index 4466c9c7db..a176311b7f 100644 --- a/packages/agent-core/test/harness/runtime-provider.test.ts +++ b/packages/agent-core/test/harness/runtime-provider.test.ts @@ -614,6 +614,15 @@ describe('ProviderManager thinking level', () => { }); expect(managerOffByDefault.resolveThinkingLevel()).toBe('off'); + const managerWithModeOffAndDefaultThinking = new ProviderManager({ + config: { + providers: {}, + defaultThinking: true, + thinking: { effort: 'medium', mode: 'off' }, + }, + }); + expect(managerWithModeOffAndDefaultThinking.resolveThinkingLevel()).toBe('off'); + const managerWithoutThinking = new ProviderManager({ config: { providers: {} } }); expect(managerWithoutThinking.resolveThinkingLevel()).toBe('high'); }); From 34cf62c763ddd6e3fab55a90ab4f8b2e8622ec54 Mon Sep 17 00:00:00 2001 From: liruifengv Date: Mon, 25 May 2026 19:21:52 +0800 Subject: [PATCH 8/8] fix: normalize runtime thinking requests --- packages/agent-core/src/providers/provider-manager.ts | 9 +++++++-- .../agent-core/test/harness/runtime-provider.test.ts | 4 ++++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/packages/agent-core/src/providers/provider-manager.ts b/packages/agent-core/src/providers/provider-manager.ts index 17489e6d51..bebd3f3e7a 100644 --- a/packages/agent-core/src/providers/provider-manager.ts +++ b/packages/agent-core/src/providers/provider-manager.ts @@ -114,7 +114,7 @@ export class ProviderManager { resolveThinkingLevel(requestedThinking?: string): ThinkingEffort { return resolveThinkingEffort( - requestedThinking ?? defaultThinkingDisableOverride(this.state.config.defaultThinking), + resolveRuntimeThinkingRequest(requestedThinking, this.state.config.defaultThinking), this.state.config.thinking, ); } @@ -141,6 +141,11 @@ function normalizeString(value: string | undefined): string | undefined { return trimmed.length > 0 ? trimmed : undefined; } -function defaultThinkingDisableOverride(defaultThinking: boolean | undefined): string | undefined { +function resolveRuntimeThinkingRequest( + requestedThinking: string | undefined, + defaultThinking: boolean | undefined, +): string | undefined { + const normalized = normalizeString(requestedThinking); + if (normalized !== undefined) return normalized; return defaultThinking === false ? 'off' : undefined; } diff --git a/packages/agent-core/test/harness/runtime-provider.test.ts b/packages/agent-core/test/harness/runtime-provider.test.ts index a176311b7f..07367a5c82 100644 --- a/packages/agent-core/test/harness/runtime-provider.test.ts +++ b/packages/agent-core/test/harness/runtime-provider.test.ts @@ -593,6 +593,8 @@ describe('ProviderManager thinking level', () => { expect(manager.resolveThinkingLevel('off')).toBe('off'); expect(manager.resolveThinkingLevel('low')).toBe('low'); expect(manager.resolveThinkingLevel()).toBe('off'); + expect(manager.resolveThinkingLevel('')).toBe('off'); + expect(manager.resolveThinkingLevel(' ')).toBe('off'); const managerOnByDefault = new ProviderManager({ config: { @@ -602,6 +604,7 @@ describe('ProviderManager thinking level', () => { }, }); expect(managerOnByDefault.resolveThinkingLevel()).toBe('medium'); + expect(managerOnByDefault.resolveThinkingLevel(' ')).toBe('medium'); const managerWithoutEffort = new ProviderManager({ config: { providers: {}, defaultThinking: true, thinking: { mode: 'auto' } }, @@ -622,6 +625,7 @@ describe('ProviderManager thinking level', () => { }, }); expect(managerWithModeOffAndDefaultThinking.resolveThinkingLevel()).toBe('off'); + expect(managerWithModeOffAndDefaultThinking.resolveThinkingLevel(' ')).toBe('off'); const managerWithoutThinking = new ProviderManager({ config: { providers: {} } }); expect(managerWithoutThinking.resolveThinkingLevel()).toBe('high');