Skip to content

Commit 67656aa

Browse files
committed
feat(settings): add configurable default effort level
Adds a "Default effort level" setting that lets users choose a fixed reasoning effort (low/medium/high/xhigh/max) or "Last used" for new tasks. Mirrors the existing defaultInitialTaskMode pattern. Changes: - Add DefaultReasoningEffort type and defaultReasoningEffort state to settingsStore (persisted, default "last_used") - Update usePreviewConfig to respect defaultReasoningEffort when selecting the initial effort value for new sessions - Apply the same default in the model-change handler fallback - Add "Default effort level" selector in GeneralSettings > Input - Track setting changes via analytics Closes #1846
1 parent ae3c8b5 commit 67656aa

3 files changed

Lines changed: 161 additions & 95 deletions

File tree

apps/code/src/renderer/features/settings/components/sections/GeneralSettings.tsx

Lines changed: 75 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
import { useAuthStateValue } from "@features/auth/hooks/authQueries";
22
import { SettingRow } from "@features/settings/components/SettingRow";
33
import {
4-
type AutoConvertLongText,
5-
type CompletionSound,
6-
type DefaultInitialTaskMode,
7-
type DiffOpenMode,
4+
type AutoConvertLongText,
5+
type CompletionSound,
6+
type DefaultInitialTaskMode,
7+
type DefaultReasoningEffort,
8+
type DiffOpenMode,
89
type SendMessagesWith,
910
useSettingsStore,
1011
} from "@features/settings/stores/settingsStore";
@@ -75,19 +76,21 @@ export function GeneralSettings() {
7576
dockBounceNotifications,
7677
completionSound,
7778
completionVolume,
78-
autoConvertLongText,
79-
defaultInitialTaskMode,
80-
diffOpenMode,
81-
sendMessagesWith,
82-
hedgehogMode,
83-
setDesktopNotifications,
84-
setDockBadgeNotifications,
85-
setDockBounceNotifications,
86-
setCompletionSound,
87-
setCompletionVolume,
88-
setAutoConvertLongText,
89-
setDefaultInitialTaskMode,
90-
setDiffOpenMode,
79+
autoConvertLongText,
80+
defaultInitialTaskMode,
81+
defaultReasoningEffort,
82+
diffOpenMode,
83+
sendMessagesWith,
84+
hedgehogMode,
85+
setDesktopNotifications,
86+
setDockBadgeNotifications,
87+
setDockBounceNotifications,
88+
setCompletionSound,
89+
setCompletionVolume,
90+
setAutoConvertLongText,
91+
setDefaultInitialTaskMode,
92+
setDefaultReasoningEffort,
93+
setDiffOpenMode,
9194
setSendMessagesWith,
9295
setHedgehogMode,
9396
} = useSettingsStore();
@@ -178,19 +181,31 @@ export function GeneralSettings() {
178181
[diffOpenMode, setDiffOpenMode],
179182
);
180183

181-
const handleDefaultInitialTaskModeChange = useCallback(
182-
(value: DefaultInitialTaskMode) => {
183-
track(ANALYTICS_EVENTS.SETTING_CHANGED, {
184-
setting_name: "default_initial_task_mode",
185-
new_value: value,
186-
old_value: defaultInitialTaskMode,
187-
});
188-
setDefaultInitialTaskMode(value);
189-
},
190-
[defaultInitialTaskMode, setDefaultInitialTaskMode],
191-
);
192-
193-
const handleSendMessagesWithChange = useCallback(
184+
const handleDefaultInitialTaskModeChange = useCallback(
185+
(value: DefaultInitialTaskMode) => {
186+
track(ANALYTICS_EVENTS.SETTING_CHANGED, {
187+
setting_name: "default_initial_task_mode",
188+
new_value: value,
189+
old_value: defaultInitialTaskMode,
190+
});
191+
setDefaultInitialTaskMode(value);
192+
},
193+
[defaultInitialTaskMode, setDefaultInitialTaskMode],
194+
);
195+
196+
const handleDefaultReasoningEffortChange = useCallback(
197+
(value: DefaultReasoningEffort) => {
198+
track(ANALYTICS_EVENTS.SETTING_CHANGED, {
199+
setting_name: "default_reasoning_effort",
200+
new_value: value,
201+
old_value: defaultReasoningEffort,
202+
});
203+
setDefaultReasoningEffort(value);
204+
},
205+
[defaultReasoningEffort, setDefaultReasoningEffort],
206+
);
207+
208+
const handleSendMessagesWithChange = useCallback(
194209
(value: SendMessagesWith) => {
195210
track(ANALYTICS_EVENTS.SETTING_CHANGED, {
196211
setting_name: "send_messages_with",
@@ -381,13 +396,36 @@ export function GeneralSettings() {
381396
<Select.Trigger className="min-w-[100px]" />
382397
<Select.Content>
383398
<Select.Item value="plan">Plan</Select.Item>
384-
<Select.Item value="last_used">Last used</Select.Item>
385-
</Select.Content>
386-
</Select.Root>
387-
</SettingRow>
388-
389-
<SettingRow
390-
label="Send messages with"
399+
<Select.Item value="last_used">Last used</Select.Item>
400+
</Select.Content>
401+
</Select.Root>
402+
</SettingRow>
403+
404+
<SettingRow
405+
label="Default effort level"
406+
description="Choose the default reasoning effort for new tasks, or remember your last-used level"
407+
>
408+
<Select.Root
409+
value={defaultReasoningEffort}
410+
onValueChange={(value) =>
411+
handleDefaultReasoningEffortChange(value as DefaultReasoningEffort)
412+
}
413+
size="1"
414+
>
415+
<Select.Trigger className="min-w-[100px]" />
416+
<Select.Content>
417+
<Select.Item value="last_used">Last used</Select.Item>
418+
<Select.Item value="low">Low</Select.Item>
419+
<Select.Item value="medium">Medium</Select.Item>
420+
<Select.Item value="high">High</Select.Item>
421+
<Select.Item value="xhigh">Extra High</Select.Item>
422+
<Select.Item value="max">Max</Select.Item>
423+
</Select.Content>
424+
</Select.Root>
425+
</SettingRow>
426+
427+
<SettingRow
428+
label="Send messages with"
391429
description="Choose which key combination sends messages. Use Shift+Enter for new lines"
392430
>
393431
<Select.Root

apps/code/src/renderer/features/settings/stores/settingsStore.ts

Lines changed: 24 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ export type CompletionSound =
2424
export type AgentAdapter = "claude" | "codex";
2525
export type AutoConvertLongText = "off" | "1000" | "2500" | "5000" | "10000";
2626
export type DefaultInitialTaskMode = "plan" | "last_used";
27+
export type DefaultReasoningEffort = "low" | "medium" | "high" | "xhigh" | "max" | "last_used";
2728

2829
export interface HintState {
2930
count: number;
@@ -53,9 +54,10 @@ interface SettingsStore {
5354
preventSleepWhileRunning: boolean;
5455
debugLogsCloudRuns: boolean;
5556
customInstructions: string;
56-
defaultInitialTaskMode: DefaultInitialTaskMode;
57-
lastUsedInitialTaskMode: ExecutionMode;
58-
diffOpenMode: DiffOpenMode;
57+
defaultInitialTaskMode: DefaultInitialTaskMode;
58+
lastUsedInitialTaskMode: ExecutionMode;
59+
defaultReasoningEffort: DefaultReasoningEffort;
60+
diffOpenMode: DiffOpenMode;
5961
hedgehogMode: boolean;
6062
mcpAppsDisabledServers: string[];
6163
hints: Record<string, HintState>;
@@ -89,9 +91,10 @@ interface SettingsStore {
8991
setPreventSleepWhileRunning: (enabled: boolean) => void;
9092
setDebugLogsCloudRuns: (enabled: boolean) => void;
9193
setCustomInstructions: (instructions: string) => void;
92-
setDefaultInitialTaskMode: (mode: DefaultInitialTaskMode) => void;
93-
setLastUsedInitialTaskMode: (mode: ExecutionMode) => void;
94-
setDiffOpenMode: (mode: DiffOpenMode) => void;
94+
setDefaultInitialTaskMode: (mode: DefaultInitialTaskMode) => void;
95+
setLastUsedInitialTaskMode: (mode: ExecutionMode) => void;
96+
setDefaultReasoningEffort: (effort: DefaultReasoningEffort) => void;
97+
setDiffOpenMode: (mode: DiffOpenMode) => void;
9598
setHedgehogMode: (enabled: boolean) => void;
9699
setMcpAppsDisabledServers: (servers: string[]) => void;
97100
}
@@ -120,9 +123,10 @@ export const useSettingsStore = create<SettingsStore>()(
120123
preventSleepWhileRunning: false,
121124
debugLogsCloudRuns: false,
122125
customInstructions: "",
123-
defaultInitialTaskMode: "plan",
124-
lastUsedInitialTaskMode: "plan",
125-
diffOpenMode: "auto",
126+
defaultInitialTaskMode: "plan",
127+
lastUsedInitialTaskMode: "plan",
128+
defaultReasoningEffort: "last_used",
129+
diffOpenMode: "auto",
126130
hedgehogMode: false,
127131
mcpAppsDisabledServers: [],
128132
hints: {},
@@ -194,11 +198,13 @@ export const useSettingsStore = create<SettingsStore>()(
194198
setDebugLogsCloudRuns: (enabled) => set({ debugLogsCloudRuns: enabled }),
195199
setCustomInstructions: (instructions) =>
196200
set({ customInstructions: instructions }),
197-
setDefaultInitialTaskMode: (mode) =>
198-
set({ defaultInitialTaskMode: mode }),
199-
setLastUsedInitialTaskMode: (mode) =>
200-
set({ lastUsedInitialTaskMode: mode }),
201-
setDiffOpenMode: (mode) => set({ diffOpenMode: mode }),
201+
setDefaultInitialTaskMode: (mode) =>
202+
set({ defaultInitialTaskMode: mode }),
203+
setLastUsedInitialTaskMode: (mode) =>
204+
set({ lastUsedInitialTaskMode: mode }),
205+
setDefaultReasoningEffort: (effort) =>
206+
set({ defaultReasoningEffort: effort }),
207+
setDiffOpenMode: (mode) => set({ diffOpenMode: mode }),
202208
setHedgehogMode: (enabled) => set({ hedgehogMode: enabled }),
203209
setMcpAppsDisabledServers: (servers) =>
204210
set({ mcpAppsDisabledServers: servers }),
@@ -228,9 +234,10 @@ export const useSettingsStore = create<SettingsStore>()(
228234
preventSleepWhileRunning: state.preventSleepWhileRunning,
229235
debugLogsCloudRuns: state.debugLogsCloudRuns,
230236
customInstructions: state.customInstructions,
231-
defaultInitialTaskMode: state.defaultInitialTaskMode,
232-
lastUsedInitialTaskMode: state.lastUsedInitialTaskMode,
233-
diffOpenMode: state.diffOpenMode,
237+
defaultInitialTaskMode: state.defaultInitialTaskMode,
238+
lastUsedInitialTaskMode: state.lastUsedInitialTaskMode,
239+
defaultReasoningEffort: state.defaultReasoningEffort,
240+
diffOpenMode: state.diffOpenMode,
234241
hedgehogMode: state.hedgehogMode,
235242
hints: state.hints,
236243
mcpAppsDisabledServers: state.mcpAppsDisabledServers,

apps/code/src/renderer/features/task-detail/hooks/usePreviewConfig.ts

Lines changed: 62 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -67,11 +67,12 @@ export function usePreviewConfig(
6767
.then((options) => {
6868
if (abort.signal.aborted) return;
6969

70-
const {
71-
defaultInitialTaskMode,
72-
lastUsedInitialTaskMode,
73-
lastUsedReasoningEffort,
74-
} = useSettingsStore.getState();
70+
const {
71+
defaultInitialTaskMode,
72+
lastUsedInitialTaskMode,
73+
defaultReasoningEffort,
74+
lastUsedReasoningEffort,
75+
} = useSettingsStore.getState();
7576

7677
// Use the mode option's existing currentValue (set by the server
7778
// based on the adapter) when the user hasn't chosen a preference,
@@ -111,27 +112,40 @@ export function usePreviewConfig(
111112
: opt,
112113
);
113114

114-
const withEffort = withMode.map((opt) => {
115-
if (opt.category !== "thought_level" || opt.type !== "select") {
116-
return opt;
117-
}
118-
const validValues = flattenValues(
119-
opt.options as Array<{
120-
value?: string;
121-
options?: Array<{ value: string }>;
122-
}>,
123-
);
124-
if (
125-
lastUsedReasoningEffort &&
126-
validValues.includes(lastUsedReasoningEffort)
127-
) {
128-
return {
129-
...opt,
130-
currentValue: lastUsedReasoningEffort,
131-
} as SessionConfigOption;
132-
}
133-
return opt;
134-
});
115+
const withEffort = withMode.map((opt) => {
116+
if (opt.category !== "thought_level" || opt.type !== "select") {
117+
return opt;
118+
}
119+
const validValues = flattenValues(
120+
opt.options as Array<{
121+
value?: string;
122+
options?: Array<{ value: string }>;
123+
}>,
124+
);
125+
// Mirror the defaultInitialTaskMode pattern: if defaultReasoningEffort
126+
// is "last_used" and we have a valid last-used value, use it;
127+
// otherwise, use the explicit default the user configured.
128+
if (defaultReasoningEffort === "last_used") {
129+
if (
130+
lastUsedReasoningEffort &&
131+
validValues.includes(lastUsedReasoningEffort)
132+
) {
133+
return {
134+
...opt,
135+
currentValue: lastUsedReasoningEffort,
136+
} as SessionConfigOption;
137+
}
138+
return opt;
139+
}
140+
// User chose a fixed default — use it if valid for this model
141+
if (validValues.includes(defaultReasoningEffort)) {
142+
return {
143+
...opt,
144+
currentValue: defaultReasoningEffort,
145+
} as SessionConfigOption;
146+
}
147+
return opt;
148+
});
135149

136150
setConfigOptions(withEffort);
137151
setIsLoading(false);
@@ -168,26 +182,33 @@ export function usePreviewConfig(
168182
? "reasoning_effort"
169183
: "effort";
170184

171-
const { lastUsedReasoningEffort } = useSettingsStore.getState();
172-
const isValidEffort = (effort: unknown): effort is string =>
173-
typeof effort === "string" &&
174-
!!effortOpts?.some((e) => e.value === effort);
175-
if (effortOpts && existingIdx >= 0) {
176-
const currentEffort = updated[existingIdx].currentValue;
177-
const nextEffort = isValidEffort(currentEffort)
178-
? currentEffort
179-
: isValidEffort(lastUsedReasoningEffort)
180-
? lastUsedReasoningEffort
181-
: "high";
185+
const { lastUsedReasoningEffort, defaultReasoningEffort } =
186+
useSettingsStore.getState();
187+
const isValidEffort = (effort: unknown): effort is string =>
188+
typeof effort === "string" &&
189+
!!effortOpts?.some((e) => e.value === effort);
190+
const resolveEffortFallback = (): string => {
191+
if (defaultReasoningEffort !== "last_used") {
192+
return isValidEffort(defaultReasoningEffort)
193+
? defaultReasoningEffort
194+
: "high";
195+
}
196+
return isValidEffort(lastUsedReasoningEffort)
197+
? lastUsedReasoningEffort
198+
: "high";
199+
};
200+
if (effortOpts && existingIdx >= 0) {
201+
const currentEffort = updated[existingIdx].currentValue;
202+
const nextEffort = isValidEffort(currentEffort)
203+
? currentEffort
204+
: resolveEffortFallback();
182205
updated[existingIdx] = {
183206
...updated[existingIdx],
184207
currentValue: nextEffort,
185208
options: effortOpts,
186209
} as SessionConfigOption;
187-
} else if (effortOpts && existingIdx === -1) {
188-
const nextEffort = isValidEffort(lastUsedReasoningEffort)
189-
? lastUsedReasoningEffort
190-
: "high";
210+
} else if (effortOpts && existingIdx === -1) {
211+
const nextEffort = resolveEffortFallback();
191212
updated = [
192213
...updated,
193214
{

0 commit comments

Comments
 (0)