From 6ab7ec0c0d154e8e257f5f84dd717e827ab996c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=A8=8B=E5=BA=8F=E5=91=98=E9=98=BF=E6=B1=9F=28Relakkes?= =?UTF-8?q?=29?= Date: Sun, 4 Oct 2026 13:41:13 +0800 Subject: [PATCH] fix(desktop): default reasoning effort to low for new sessions The global effort default moves from max to low on both the server (/api/effort fallback) and the desktop store. Users who already saved an effort keep it. ChatGPT Official and Grok Official are not in the saved provider list, so their first draft selection carried no effort: the server ran the model default while the selector showed the global value. Resolve the effort against the model catalog for those providers too. --- .../controls/ModelSelector.test.tsx | 1 + desktop/src/pages/EmptySession.test.tsx | 28 +++++++++++++++++-- desktop/src/pages/EmptySession.tsx | 12 +++++++- desktop/src/stores/settingsStore.test.ts | 9 ++++++ desktop/src/stores/settingsStore.ts | 2 +- .../__tests__/e2e/business-flow.test.ts | 4 +-- src/server/__tests__/settings.test.ts | 4 +-- src/server/api/models.ts | 2 +- 8 files changed, 53 insertions(+), 9 deletions(-) diff --git a/desktop/src/components/controls/ModelSelector.test.tsx b/desktop/src/components/controls/ModelSelector.test.tsx index 6dc35d7d..73a16ca0 100644 --- a/desktop/src/components/controls/ModelSelector.test.tsx +++ b/desktop/src/components/controls/ModelSelector.test.tsx @@ -1069,6 +1069,7 @@ describe('ModelSelector', () => { }) useSettingsStore.setState({ locale: 'en', + effortLevel: 'max', availableModels: openAIModels, currentModel: openAIModels[0], activeProviderName: 'ChatGPT Official', diff --git a/desktop/src/pages/EmptySession.test.tsx b/desktop/src/pages/EmptySession.test.tsx index c76c0b07..f4ae7845 100644 --- a/desktop/src/pages/EmptySession.test.tsx +++ b/desktop/src/pages/EmptySession.test.tsx @@ -865,6 +865,30 @@ describe('EmptySession', () => { }) }) + it('carries the global effort into the first draft selection for ChatGPT Official', async () => { + useSettingsStore.setState({ effortLevel: 'low' }) + useProviderStore.setState({ + providers: [], + activeId: 'openai-official', + providerOrder: ['claude-official', 'openai-official', 'grok-official'], + hasLoadedProviders: true, + }) + + render() + + setComposerText('draft question', 14) + fireEvent.click(screen.getByRole('button', { name: /Run/i })) + + await waitFor(() => { + expect(mocks.createSession).toHaveBeenCalledWith({ permissionMode: 'default' }) + }) + + expect(useSessionRuntimeStore.getState().selections['draft-session']).toMatchObject({ + providerId: 'openai-official', + effortLevel: 'low', + }) + }) + it.each([true, false])('materializes raw provider models with 1M=%s before the first draft message', async (enabled) => { useProviderStore.setState({ providers: [{ @@ -902,7 +926,7 @@ describe('EmptySession', () => { expect(useSessionRuntimeStore.getState().selections['draft-session']).toEqual({ providerId: 'provider-minimax', modelId: enabled ? 'MiniMax-M3[1m]' : 'MiniMax-M3', - effortLevel: 'max', + effortLevel: 'low', }) expect(mocks.wsSend.mock.calls.slice(0, 3)).toEqual([ [ @@ -911,7 +935,7 @@ describe('EmptySession', () => { type: 'set_runtime_config', providerId: 'provider-minimax', modelId: enabled ? 'MiniMax-M3[1m]' : 'MiniMax-M3', - effortLevel: 'max', + effortLevel: 'low', }, ], ['draft-session', { type: 'prewarm_session' }], diff --git a/desktop/src/pages/EmptySession.tsx b/desktop/src/pages/EmptySession.tsx index 1e519e43..b0d96062 100644 --- a/desktop/src/pages/EmptySession.tsx +++ b/desktop/src/pages/EmptySession.tsx @@ -34,6 +34,8 @@ import { SlashCommandMenu, } from '../components/chat/SlashCommandMenu' import { useMobileViewport } from '../hooks/useMobileViewport' +import { GROK_OFFICIAL_PROVIDER_ID } from '../constants/grokOfficialProvider' +import { OPENAI_OFFICIAL_PROVIDER_ID } from '../constants/openaiOfficialProvider' import { isDesktopRuntime } from '../lib/desktopRuntime' import { normalizeRuntimeSelection, @@ -400,7 +402,15 @@ export function EmptySession() { activeCustomProvider.apiFormat, getBundledPresetReasoningProviderKind(activeCustomProvider.presetId), ) - : defaultActiveProviderSelection + : defaultActiveProviderSelection && ( + defaultActiveProviderSelection.providerId === OPENAI_OFFICIAL_PROVIDER_ID || + defaultActiveProviderSelection.providerId === GROK_OFFICIAL_PROVIDER_ID + ) + // Built-in providers are not in the saved list. Without an explicit + // effort the server runs the model default while the selector shows + // the global value, so resolve it against the model's catalog here. + ? normalizeRuntimeSelection({ ...defaultActiveProviderSelection, effortLevel }) + : defaultActiveProviderSelection const claudeOAuthRuntimeSelection = !explicitDraftSelection && authStatus.source === 'claude-oauth' && activeProviderId === null && diff --git a/desktop/src/stores/settingsStore.test.ts b/desktop/src/stores/settingsStore.test.ts index 4ae3908a..2a8be34d 100644 --- a/desktop/src/stores/settingsStore.test.ts +++ b/desktop/src/stores/settingsStore.test.ts @@ -160,6 +160,15 @@ describe('settingsStore locale defaults', () => { }) }) +describe('settingsStore effort default', () => { + it('starts at low before the server value loads', async () => { + vi.resetModules() + const { useSettingsStore } = await import('./settingsStore') + + expect(useSettingsStore.getState().effortLevel).toBe('low') + }) +}) + describe('settingsStore UI zoom', () => { beforeEach(() => { vi.resetModules() diff --git a/desktop/src/stores/settingsStore.ts b/desktop/src/stores/settingsStore.ts index afad44cd..34f51ee3 100644 --- a/desktop/src/stores/settingsStore.ts +++ b/desktop/src/stores/settingsStore.ts @@ -204,7 +204,7 @@ let autoQuestionUpdateQueue: Promise = Promise.resolve() export const useSettingsStore = create((set, get) => ({ permissionMode: 'default', currentModel: null, - effortLevel: 'max', + effortLevel: 'low', thinkingEnabled: true, workflowKeywordTriggerEnabled: true, agentTeamsEnabled: true, diff --git a/src/server/__tests__/e2e/business-flow.test.ts b/src/server/__tests__/e2e/business-flow.test.ts index 58645edb..df713c06 100644 --- a/src/server/__tests__/e2e/business-flow.test.ts +++ b/src/server/__tests__/e2e/business-flow.test.ts @@ -457,9 +457,9 @@ describe('Business Flow: Models & Effort', () => { expect(status).toBe(400) }) - it('should default effort to max', async () => { + it('should default effort to low', async () => { const { data } = await api('GET', '/api/effort') - expect(data.level).toBe('max') + expect(data.level).toBe('low') expect(data.available).toEqual(['low', 'medium', 'high', 'xhigh', 'max']) }) diff --git a/src/server/__tests__/settings.test.ts b/src/server/__tests__/settings.test.ts index 518bc526..7bf13d01 100644 --- a/src/server/__tests__/settings.test.ts +++ b/src/server/__tests__/settings.test.ts @@ -1548,7 +1548,7 @@ describe('Models API', () => { expect(res.status).toBe(200) const body = await res.json() - expect(body.level).toBe('max') + expect(body.level).toBe('low') expect(body.available).toEqual(['low', 'medium', 'high', 'xhigh', 'max']) }) @@ -1561,7 +1561,7 @@ describe('Models API', () => { expect(res.status).toBe(200) const body = await res.json() - expect(body.level).toBe('max') + expect(body.level).toBe('low') expect(body.available).toEqual(['low', 'medium', 'high', 'xhigh', 'max']) }) diff --git a/src/server/api/models.ts b/src/server/api/models.ts index b9af8ae5..7787eb4e 100644 --- a/src/server/api/models.ts +++ b/src/server/api/models.ts @@ -119,7 +119,7 @@ const EFFORT_LEVELS = MODEL_REASONING_EFFORTS // CLI's own default runs. Keep this in step with what the `opus` alias resolves to there // (getDefaultOpusModel) so the UI names the model that actually runs. const DEFAULT_MODEL = 'claude-opus-5-5' -const DEFAULT_EFFORT = 'max' +const DEFAULT_EFFORT = 'low' const settingsService = new SettingsService() const providerService = new ProviderService()