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()