Preserve Claude model and thinking preferences (#2912)

* fix(agents): preserve Claude thinking selections

Model switches now resolve thinking against the target model's saved preference instead of resetting to the catalog default. Claude owns its configured-model defaults and legacy Fable alias handling, while persisted permission modes remain unchanged.

* chore(claude): tag Fable compatibility shim

* fix(app): resolve persisted model aliases

* fix(models): preserve aliases for older clients

* fix(models): hide compatibility aliases from listings

* fix(settings): hide compatibility model aliases

* test(claude): cover legacy catalog entry

* test(app): expect mock thinking controls in schedules

* fix(app): preserve queued thinking preferences

* fix(app): reconcile schedule provider snapshots

* fix(app): preserve schedule model selections

* fix(claude): preserve exact configured model ids

* fix(app): preserve committed thinking preferences

* fix(app): preserve aliased schedule thinking
This commit is contained in:
Mohamed Boudra
2026-08-06 11:33:18 +02:00
committed by GitHub
parent 73ba37a07a
commit f2054cc701
43 changed files with 1369 additions and 220 deletions
+53 -29
View File
@@ -12,6 +12,8 @@ import {
buildSelectableProviderSelectorProviders,
type ProviderSelectorProvider,
} from "@/provider-selection/provider-selection";
import { filterSelectableModels } from "@/provider-selection/model-catalog";
import { OptimisticFormPreferences } from "@/create-agent-preferences/optimistic-preferences";
import { useProvidersSnapshot } from "./use-providers-snapshot";
import {
useFormPreferences,
@@ -156,7 +158,7 @@ function buildAllProviderModels(
): Map<string, AgentModelDefinition[]> {
const map = new Map<string, AgentModelDefinition[]>();
for (const entry of snapshotEntries ?? []) {
map.set(entry.provider, entry.models ?? []);
map.set(entry.provider, filterSelectableModels(entry.models ?? []) ?? []);
}
return map;
}
@@ -166,7 +168,7 @@ function buildProviderModelsByProvider(
): ProviderModelsByProvider {
const map: ProviderModelsByProvider = new Map();
for (const entry of snapshotEntries ?? []) {
map.set(entry.provider, entry.models ?? null);
map.set(entry.provider, filterSelectableModels(entry.models ?? null));
}
return map;
}
@@ -177,7 +179,7 @@ async function persistProviderPreferences(input: {
availableModels: AgentModelDefinition[] | null;
updatePreferences: (
updates: Partial<FormPreferences> | ((current: FormPreferences) => FormPreferences),
) => Promise<void>;
) => Promise<FormPreferences>;
}): Promise<void> {
const { provider, formState, availableModels, updatePreferences } = input;
const resolvedModel = resolveEffectiveModel(availableModels, formState.model);
@@ -208,6 +210,28 @@ export function useAgentFormState(options: UseAgentFormStateOptions = {}): UseAg
} = options;
const { preferences, isLoading: isPreferencesLoading, updatePreferences } = useFormPreferences();
const preferenceOverlayRef = useRef(new OptimisticFormPreferences(preferences));
useEffect(() => {
preferenceOverlayRef.current.reconcile(preferences);
}, [preferences]);
const updateCurrentPreferences = useCallback(
async (
updates: Partial<FormPreferences> | ((current: FormPreferences) => FormPreferences),
): Promise<FormPreferences> => {
const pendingId = preferenceOverlayRef.current.begin(updates);
try {
const persisted = await updatePreferences(updates);
preferenceOverlayRef.current.commit(pendingId, persisted);
return persisted;
} catch (error) {
preferenceOverlayRef.current.reject(pendingId);
throw error;
}
},
[updatePreferences],
);
const daemons = useHosts();
@@ -294,7 +318,9 @@ export function useAgentFormState(options: UseAgentFormStateOptions = {}): UseAg
: null,
[formState.provider, snapshotEntries],
);
const snapshotSelectedProviderModels = snapshotSelectedEntry?.models ?? null;
const snapshotSelectedProviderModels = filterSelectableModels(
snapshotSelectedEntry?.models ?? null,
);
const selectedProviderIsLoading = snapshotSelectedEntry?.status === "loading";
const snapshotSelectedProviderModes = resolveSelectedProviderModes({
selectedEntry: snapshotSelectedEntry,
@@ -404,7 +430,7 @@ export function useAgentFormState(options: UseAgentFormStateOptions = {}): UseAg
}
const providerModels = allProviderModels.get(provider) ?? null;
const providerDef = selectableProviderDefinitionMap.get(provider);
const providerPrefs = preferences?.providerPreferences?.[provider];
const providerPrefs = preferenceOverlayRef.current.current().providerPreferences?.[provider];
dispatch({
type: "SET_PROVIDER_FROM_USER",
@@ -413,14 +439,9 @@ export function useAgentFormState(options: UseAgentFormStateOptions = {}): UseAg
providerDef,
providerPrefs,
});
void updatePreferences({ provider });
void updateCurrentPreferences({ provider });
},
[
allProviderModels,
preferences?.providerPreferences,
selectableProviderDefinitionMap,
updatePreferences,
],
[allProviderModels, selectableProviderDefinitionMap, updateCurrentPreferences],
);
const setProviderAndModelFromUser = useCallback(
@@ -430,7 +451,7 @@ export function useAgentFormState(options: UseAgentFormStateOptions = {}): UseAg
}
const providerDef = selectableProviderDefinitionMap.get(provider);
const providerModels = allProviderModels.get(provider) ?? null;
const providerPrefs = preferences?.providerPreferences?.[provider];
const providerPrefs = preferenceOverlayRef.current.current().providerPreferences?.[provider];
const normalizedModelId = normalizeSelectedModelId(modelId);
const nextModelId = normalizedModelId || resolveDefaultModelId(providerModels);
@@ -442,7 +463,7 @@ export function useAgentFormState(options: UseAgentFormStateOptions = {}): UseAg
providerModels,
providerPrefs,
});
void updatePreferences((current) =>
void updateCurrentPreferences((current) =>
mergeSelectedComposerPreferences({
preferences: current,
provider,
@@ -452,12 +473,7 @@ export function useAgentFormState(options: UseAgentFormStateOptions = {}): UseAg
}),
);
},
[
allProviderModels,
preferences?.providerPreferences,
selectableProviderDefinitionMap,
updatePreferences,
],
[allProviderModels, selectableProviderDefinitionMap, updateCurrentPreferences],
);
const clearProviderSelectionFromUser = useCallback(() => {
@@ -469,7 +485,7 @@ export function useAgentFormState(options: UseAgentFormStateOptions = {}): UseAg
dispatch({ type: "SET_MODE_FROM_USER", modeId });
const provider = reducerStateRef.current.form.provider;
if (provider) {
void updatePreferences((current) =>
void updateCurrentPreferences((current) =>
mergeSelectedComposerPreferences({
preferences: current,
provider,
@@ -480,17 +496,25 @@ export function useAgentFormState(options: UseAgentFormStateOptions = {}): UseAg
);
}
},
[updatePreferences],
[updateCurrentPreferences],
);
const setModelFromUser = useCallback(
(modelId: string) => {
dispatch({ type: "SET_MODEL_FROM_USER", modelId, availableModels });
const provider = reducerStateRef.current.form.provider;
const providerPrefs = provider
? preferenceOverlayRef.current.current().providerPreferences?.[provider]
: undefined;
dispatch({
type: "SET_MODEL_FROM_USER",
modelId,
availableModels,
providerPrefs,
});
if (provider) {
const normalizedModelId = normalizeSelectedModelId(modelId);
const nextModelId = normalizedModelId || resolveDefaultModelId(availableModels);
void updatePreferences((current) =>
void updateCurrentPreferences((current) =>
mergeSelectedComposerPreferences({
preferences: current,
provider,
@@ -501,7 +525,7 @@ export function useAgentFormState(options: UseAgentFormStateOptions = {}): UseAg
);
}
},
[availableModels, updatePreferences],
[availableModels, updateCurrentPreferences],
);
const setThinkingOptionFromUser = useCallback(
@@ -509,7 +533,7 @@ export function useAgentFormState(options: UseAgentFormStateOptions = {}): UseAg
dispatch({ type: "SET_THINKING_OPTION_FROM_USER", thinkingOptionId });
const { provider, model: modelId } = reducerStateRef.current.form;
if (provider && modelId) {
void updatePreferences((current) =>
void updateCurrentPreferences((current) =>
mergeSelectedComposerPreferences({
preferences: current,
provider,
@@ -522,7 +546,7 @@ export function useAgentFormState(options: UseAgentFormStateOptions = {}): UseAg
);
}
},
[updatePreferences],
[updateCurrentPreferences],
);
const setWorkingDir = useCallback((value: string) => {
@@ -556,9 +580,9 @@ export function useAgentFormState(options: UseAgentFormStateOptions = {}): UseAg
provider: formState.provider,
formState,
availableModels,
updatePreferences,
updatePreferences: updateCurrentPreferences,
});
}, [availableModels, formState, updatePreferences]);
}, [availableModels, formState, updateCurrentPreferences]);
const agentDefinition = formState.provider
? providerDefinitionMap.get(formState.provider)
@@ -29,7 +29,7 @@ async function loadFormPreferences(): Promise<FormPreferences> {
export interface UseFormPreferencesReturn {
preferences: FormPreferences;
isLoading: boolean;
updatePreferences: (updates: FormPreferenceUpdate) => Promise<void>;
updatePreferences: (updates: FormPreferenceUpdate) => Promise<FormPreferences>;
}
export function useFormPreferences(): UseFormPreferencesReturn {
@@ -47,6 +47,7 @@ export function useFormPreferences(): UseFormPreferencesReturn {
async (updates: FormPreferenceUpdate) => {
const next = await createAgentPreferencesService.update(updates);
queryClient.setQueryData<FormPreferences>(FORM_PREFERENCES_QUERY_KEY, next);
return next;
},
[queryClient],
);