Skip to content

Commit cd17b4f

Browse files
committed
🤖 refactor: DRY task orchestration + thinking policy
Consolidate shared thinking policy and reduce tool/task scaffolding. Signed-off-by: Thomas Kosiewski <tk@coder.com> --- _Generated with `mux` • Model: gpt-5.2 • Thinking: unknown_ <!-- mux-attribution: model=gpt-5.2 thinking=unknown --> Change-Id: Id77858efe746d9b7551ab266f98886dc7712a5f3
1 parent f89b880 commit cd17b4f

File tree

24 files changed

+604
-981
lines changed

24 files changed

+604
-981
lines changed

‎src/browser/App.tsx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ import {
4040
getModelKey,
4141
} from "@/common/constants/storage";
4242
import { migrateGatewayModel } from "@/browser/hooks/useGatewayModels";
43-
import { enforceThinkingPolicy } from "@/browser/utils/thinking/policy";
43+
import { enforceThinkingPolicy } from "@/common/utils/thinking/policy";
4444
import { getDefaultModel } from "@/browser/hooks/useModelsFromSettings";
4545
import type { BranchListResult } from "@/common/orpc/types";
4646
import { useTelemetry } from "./hooks/useTelemetry";

‎src/browser/components/ChatInput/index.tsx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import { ModelSettings } from "../ModelSettings";
2222
import { useAPI } from "@/browser/contexts/API";
2323
import { useThinkingLevel } from "@/browser/hooks/useThinkingLevel";
2424
import { migrateGatewayModel } from "@/browser/hooks/useGatewayModels";
25-
import { enforceThinkingPolicy } from "@/browser/utils/thinking/policy";
25+
import { enforceThinkingPolicy } from "@/common/utils/thinking/policy";
2626
import { useSendMessageOptions } from "@/browser/hooks/useSendMessageOptions";
2727
import {
2828
getModelKey,

‎src/browser/components/Settings/sections/TasksSection.tsx‎

Lines changed: 74 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
normalizeTaskSettings,
88
type TaskSettings,
99
type SubagentAiDefaults,
10+
type SubagentAiDefaultsEntry,
1011
} from "@/common/types/tasks";
1112
import { BUILT_IN_SUBAGENTS } from "@/common/constants/agents";
1213
import type { ThinkingLevel } from "@/common/types/thinking";
@@ -19,7 +20,33 @@ import {
1920
SelectTrigger,
2021
SelectValue,
2122
} from "@/browser/components/ui/select";
22-
import { enforceThinkingPolicy, getThinkingPolicyForModel } from "@/browser/utils/thinking/policy";
23+
import { enforceThinkingPolicy, getThinkingPolicyForModel } from "@/common/utils/thinking/policy";
24+
25+
const INHERIT = "__inherit__";
26+
const ALL_THINKING_LEVELS = ["off", "low", "medium", "high", "xhigh"] as const;
27+
28+
function updateSubagentDefaultEntry(
29+
previous: SubagentAiDefaults,
30+
agentType: string,
31+
update: (entry: SubagentAiDefaultsEntry) => void
32+
): SubagentAiDefaults {
33+
const next = { ...previous };
34+
const existing = next[agentType] ?? {};
35+
const updated: SubagentAiDefaultsEntry = { ...existing };
36+
update(updated);
37+
38+
if (updated.modelString && updated.thinkingLevel) {
39+
updated.thinkingLevel = enforceThinkingPolicy(updated.modelString, updated.thinkingLevel);
40+
}
41+
42+
if (!updated.modelString && !updated.thinkingLevel) {
43+
delete next[agentType];
44+
} else {
45+
next[agentType] = updated;
46+
}
47+
48+
return next;
49+
}
2350

2451
export function TasksSection() {
2552
const { api } = useAPI();
@@ -30,6 +57,10 @@ export function TasksSection() {
3057
const [saveError, setSaveError] = useState<string | null>(null);
3158
const saveTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
3259
const savingRef = useRef(false);
60+
const pendingSaveRef = useRef<{
61+
taskSettings: TaskSettings;
62+
subagentAiDefaults: SubagentAiDefaults;
63+
} | null>(null);
3364

3465
const { models, hiddenModels } = useModelsFromSettings();
3566

@@ -74,23 +105,36 @@ export function TasksSection() {
74105
if (!api) return;
75106
if (!loaded) return;
76107
if (loadFailed) return;
77-
if (savingRef.current) return;
108+
109+
pendingSaveRef.current = { taskSettings, subagentAiDefaults };
78110

79111
if (saveTimerRef.current) {
80112
clearTimeout(saveTimerRef.current);
81113
saveTimerRef.current = null;
82114
}
83115

84116
saveTimerRef.current = setTimeout(() => {
85-
savingRef.current = true;
86-
void api.config
87-
.saveConfig({ taskSettings, subagentAiDefaults })
88-
.catch((error: unknown) => {
89-
setSaveError(error instanceof Error ? error.message : String(error));
90-
})
91-
.finally(() => {
92-
savingRef.current = false;
93-
});
117+
const flush = () => {
118+
if (savingRef.current) return;
119+
if (!api) return;
120+
121+
const payload = pendingSaveRef.current;
122+
if (!payload) return;
123+
124+
pendingSaveRef.current = null;
125+
savingRef.current = true;
126+
void api.config
127+
.saveConfig(payload)
128+
.catch((error: unknown) => {
129+
setSaveError(error instanceof Error ? error.message : String(error));
130+
})
131+
.finally(() => {
132+
savingRef.current = false;
133+
flush();
134+
});
135+
};
136+
137+
flush();
94138
}, 400);
95139

96140
return () => {
@@ -111,57 +155,29 @@ export function TasksSection() {
111155
setTaskSettings((prev) => normalizeTaskSettings({ ...prev, maxTaskNestingDepth: parsed }));
112156
};
113157

114-
const INHERIT = "__inherit__";
115-
116158
const setSubagentModel = (agentType: string, value: string) => {
117-
setSubagentAiDefaults((prev) => {
118-
const next = { ...prev };
119-
const existing = next[agentType] ?? {};
120-
const updated = { ...existing };
121-
122-
if (value === INHERIT) {
123-
delete updated.modelString;
124-
} else {
125-
updated.modelString = value;
126-
}
127-
128-
if (updated.modelString && updated.thinkingLevel) {
129-
updated.thinkingLevel = enforceThinkingPolicy(updated.modelString, updated.thinkingLevel);
130-
}
131-
132-
if (!updated.modelString && !updated.thinkingLevel) {
133-
delete next[agentType];
134-
} else {
135-
next[agentType] = updated;
136-
}
137-
138-
return next;
139-
});
159+
setSubagentAiDefaults((prev) =>
160+
updateSubagentDefaultEntry(prev, agentType, (updated) => {
161+
if (value === INHERIT) {
162+
delete updated.modelString;
163+
} else {
164+
updated.modelString = value;
165+
}
166+
})
167+
);
140168
};
141169

142170
const setSubagentThinking = (agentType: string, value: string) => {
143-
setSubagentAiDefaults((prev) => {
144-
const next = { ...prev };
145-
const existing = next[agentType] ?? {};
146-
const updated = { ...existing };
147-
148-
if (value === INHERIT) {
149-
delete updated.thinkingLevel;
150-
} else {
151-
const requested = value as ThinkingLevel;
152-
updated.thinkingLevel = updated.modelString
153-
? enforceThinkingPolicy(updated.modelString, requested)
154-
: requested;
155-
}
171+
setSubagentAiDefaults((prev) =>
172+
updateSubagentDefaultEntry(prev, agentType, (updated) => {
173+
if (value === INHERIT) {
174+
delete updated.thinkingLevel;
175+
return;
176+
}
156177

157-
if (!updated.modelString && !updated.thinkingLevel) {
158-
delete next[agentType];
159-
} else {
160-
next[agentType] = updated;
161-
}
162-
163-
return next;
164-
});
178+
updated.thinkingLevel = value as ThinkingLevel;
179+
})
180+
);
165181
};
166182

167183
return (
@@ -224,9 +240,7 @@ export function TasksSection() {
224240
const modelValue = entry?.modelString ?? INHERIT;
225241
const thinkingValue = entry?.thinkingLevel ?? INHERIT;
226242
const allowedThinkingLevels =
227-
modelValue !== INHERIT
228-
? getThinkingPolicyForModel(modelValue)
229-
: (["off", "low", "medium", "high", "xhigh"] as const);
243+
modelValue !== INHERIT ? getThinkingPolicyForModel(modelValue) : ALL_THINKING_LEVELS;
230244

231245
return (
232246
<div

‎src/browser/components/ThinkingSlider.tsx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import type { ThinkingLevel } from "@/common/types/thinking";
33
import { useThinkingLevel } from "@/browser/hooks/useThinkingLevel";
44
import { Tooltip, TooltipTrigger, TooltipContent } from "./ui/tooltip";
55
import { formatKeybind, KEYBINDS } from "@/browser/utils/ui/keybinds";
6-
import { getThinkingPolicyForModel } from "@/browser/utils/thinking/policy";
6+
import { getThinkingPolicyForModel } from "@/common/utils/thinking/policy";
77

88
// Uses CSS variable --color-thinking-mode for theme compatibility
99
// Glow is applied via CSS using color-mix with the theme color

‎src/browser/contexts/ThinkingContext.tsx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import {
1515
} from "@/common/constants/storage";
1616
import { getDefaultModel } from "@/browser/hooks/useModelsFromSettings";
1717
import { migrateGatewayModel } from "@/browser/hooks/useGatewayModels";
18-
import { enforceThinkingPolicy } from "@/browser/utils/thinking/policy";
18+
import { enforceThinkingPolicy } from "@/common/utils/thinking/policy";
1919
import { useAPI } from "@/browser/contexts/API";
2020

2121
interface ThinkingContextType {

‎src/browser/hooks/useAIViewKeybinds.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { matchesKeybind, KEYBINDS, isEditableElement } from "@/browser/utils/ui/
44
import { getModelKey } from "@/common/constants/storage";
55
import { readPersistedState } from "@/browser/hooks/usePersistedState";
66
import type { ThinkingLevel } from "@/common/types/thinking";
7-
import { getThinkingPolicyForModel } from "@/browser/utils/thinking/policy";
7+
import { getThinkingPolicyForModel } from "@/common/utils/thinking/policy";
88
import { getDefaultModel } from "@/browser/hooks/useModelsFromSettings";
99
import type { StreamingMessageAggregator } from "@/browser/utils/messages/StreamingMessageAggregator";
1010
import { isCompactingStream, cancelCompaction } from "@/browser/utils/compaction/handler";

‎src/browser/hooks/useSendMessageOptions.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import type { UIMode } from "@/common/types/mode";
1010
import type { ThinkingLevel } from "@/common/types/thinking";
1111
import type { MuxProviderOptions } from "@/common/types/providerOptions";
1212
import { getSendOptionsFromStorage } from "@/browser/utils/messages/sendOptions";
13-
import { enforceThinkingPolicy } from "@/browser/utils/thinking/policy";
13+
import { enforceThinkingPolicy } from "@/common/utils/thinking/policy";
1414
import { useProviderOptions } from "./useProviderOptions";
1515
import type { GatewayState } from "./useGatewayModels";
1616
import { useExperimentOverrideValue } from "./useExperiments";

‎src/browser/utils/messages/sendOptions.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import { toGatewayModel, migrateGatewayModel } from "@/browser/hooks/useGatewayM
1111
import type { SendMessageOptions } from "@/common/orpc/types";
1212
import type { UIMode } from "@/common/types/mode";
1313
import type { ThinkingLevel } from "@/common/types/thinking";
14-
import { enforceThinkingPolicy } from "@/browser/utils/thinking/policy";
14+
import { enforceThinkingPolicy } from "@/common/utils/thinking/policy";
1515
import type { MuxProviderOptions } from "@/common/types/providerOptions";
1616
import { WORKSPACE_DEFAULTS } from "@/constants/workspaceDefaults";
1717
import { isExperimentEnabled } from "@/browser/hooks/useExperiments";

‎src/common/types/tasks.ts‎

Lines changed: 2 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import assert from "@/common/utils/assert";
2-
import type { ThinkingLevel } from "./thinking";
2+
import { coerceThinkingLevel, type ThinkingLevel } from "./thinking";
33

44
export interface TaskSettings {
55
maxParallelAgentTasks: number;
@@ -40,19 +40,7 @@ export function normalizeSubagentAiDefaults(raw: unknown): SubagentAiDefaults {
4040
? entry.modelString.trim()
4141
: undefined;
4242

43-
const thinkingLevel = (() => {
44-
const value = entry.thinkingLevel;
45-
if (
46-
value === "off" ||
47-
value === "low" ||
48-
value === "medium" ||
49-
value === "high" ||
50-
value === "xhigh"
51-
) {
52-
return value;
53-
}
54-
return undefined;
55-
})();
43+
const thinkingLevel = coerceThinkingLevel(entry.thinkingLevel);
5644

5745
if (!modelString && !thinkingLevel) {
5846
continue;

‎src/common/types/thinking.ts‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,20 @@ export type ThinkingLevel = "off" | "low" | "medium" | "high" | "xhigh";
1313
*/
1414
export type ThinkingLevelOn = Exclude<ThinkingLevel, "off">;
1515

16+
export function isThinkingLevel(value: unknown): value is ThinkingLevel {
17+
return (
18+
value === "off" ||
19+
value === "low" ||
20+
value === "medium" ||
21+
value === "high" ||
22+
value === "xhigh"
23+
);
24+
}
25+
26+
export function coerceThinkingLevel(value: unknown): ThinkingLevel | undefined {
27+
return isThinkingLevel(value) ? value : undefined;
28+
}
29+
1630
/**
1731
* Anthropic thinking token budget mapping
1832
*

0 commit comments

Comments
 (0)