Keep composer mode preferences stable (#1658)
* fix(app): preserve composer mode preferences Treat undefined provider preference updates as no-ops, and keep saved mode selections instead of silently defaulting them when provider metadata is incomplete or does not list the saved mode. * fix(app): document mode preference preservation
This commit is contained in:
@@ -511,6 +511,59 @@ describe("resolveFormState", () => {
|
||||
expect(resolved.thinkingOptionId).toBe("xhigh");
|
||||
});
|
||||
|
||||
it("preserves the saved mode while provider modes are absent from a loading snapshot", () => {
|
||||
const loadingEntries: ProviderSnapshotEntry[] = [
|
||||
{
|
||||
provider: "codex",
|
||||
status: "loading",
|
||||
enabled: true,
|
||||
label: TEST_CODEX_DEFINITION.label,
|
||||
description: TEST_CODEX_DEFINITION.description,
|
||||
defaultModeId: TEST_CODEX_DEFINITION.defaultModeId,
|
||||
},
|
||||
];
|
||||
const providerDefinitions = buildProviderDefinitions(loadingEntries);
|
||||
const resolvableProviderMap = buildProviderDefinitionMapForStatuses({
|
||||
snapshotEntries: loadingEntries,
|
||||
providerDefinitions,
|
||||
statuses: new Set<ProviderSnapshotEntry["status"]>(["ready", "loading"]),
|
||||
});
|
||||
|
||||
const resolved = resolveFormState(
|
||||
undefined,
|
||||
{
|
||||
provider: "codex",
|
||||
providerPreferences: { codex: { mode: "full-access", model: "gpt-5.3-codex" } },
|
||||
},
|
||||
null,
|
||||
INITIAL_USER_MODIFIED,
|
||||
makeState({ provider: "codex", modeId: "full-access", model: "gpt-5.3-codex" }).form,
|
||||
|
||||
resolvableProviderMap,
|
||||
);
|
||||
|
||||
expect(resolved.provider).toBe("codex");
|
||||
expect(resolved.modeId).toBe("full-access");
|
||||
});
|
||||
|
||||
it("preserves a saved mode that is not in the current mode list", () => {
|
||||
const resolved = resolveFormState(
|
||||
undefined,
|
||||
{
|
||||
provider: "codex",
|
||||
providerPreferences: { codex: { mode: "workspace-write", model: "gpt-5.3-codex" } },
|
||||
},
|
||||
CODEX_MODELS,
|
||||
INITIAL_USER_MODIFIED,
|
||||
makeState({ provider: "codex" }).form,
|
||||
|
||||
codexProviderMap,
|
||||
);
|
||||
|
||||
expect(resolved.provider).toBe("codex");
|
||||
expect(resolved.modeId).toBe("workspace-write");
|
||||
});
|
||||
|
||||
it("ignores disabled ready providers when resolving selectable defaults", () => {
|
||||
const entries: ProviderSnapshotEntry[] = [
|
||||
{
|
||||
|
||||
@@ -144,6 +144,24 @@ export function resolveThinkingOptionId(args: {
|
||||
return effectiveModel?.defaultThinkingOptionId ?? thinkingOptions[0]?.id ?? "";
|
||||
}
|
||||
|
||||
const normalizeSelectedModeId = normalizeSelectedModelId;
|
||||
|
||||
function resolvePreferredModeId(input: {
|
||||
initialModeId?: string | null;
|
||||
preferredModeId?: string | null;
|
||||
providerDef: AgentProviderDefinition | undefined;
|
||||
}): string {
|
||||
// Saved modes are user intent. Provider create config validates unknown modes
|
||||
// at submission time, so background form resolution should not erase them.
|
||||
const initialModeId = normalizeSelectedModeId(input.initialModeId);
|
||||
if (initialModeId) return initialModeId;
|
||||
|
||||
const preferredModeId = normalizeSelectedModeId(input.preferredModeId);
|
||||
if (preferredModeId) return preferredModeId;
|
||||
|
||||
return input.providerDef?.defaultModeId ?? input.providerDef?.modes[0]?.id ?? "";
|
||||
}
|
||||
|
||||
export function mergeSelectedComposerPreferences(args: {
|
||||
preferences: FormPreferences;
|
||||
provider: AgentProvider;
|
||||
@@ -259,18 +277,11 @@ function resolveModeId(input: {
|
||||
input;
|
||||
if (userModified) return currentModeId;
|
||||
if (!provider) return "";
|
||||
const validModeIds = providerDef?.modes.map((m) => m.id) ?? [];
|
||||
if (
|
||||
typeof initialValues?.modeId === "string" &&
|
||||
initialValues.modeId.length > 0 &&
|
||||
validModeIds.includes(initialValues.modeId)
|
||||
) {
|
||||
return initialValues.modeId;
|
||||
}
|
||||
if (providerPrefs?.mode && validModeIds.includes(providerPrefs.mode)) {
|
||||
return providerPrefs.mode;
|
||||
}
|
||||
return providerDef?.defaultModeId ?? validModeIds[0] ?? "";
|
||||
return resolvePreferredModeId({
|
||||
initialModeId: initialValues?.modeId,
|
||||
preferredModeId: providerPrefs?.mode,
|
||||
providerDef,
|
||||
});
|
||||
}
|
||||
|
||||
function resolveModelField(input: {
|
||||
@@ -411,11 +422,10 @@ function pickNextModeForProvider(input: {
|
||||
providerPrefs: ProviderPrefs | undefined;
|
||||
}): string {
|
||||
const { providerDef, providerPrefs } = input;
|
||||
const validModeIds = providerDef?.modes.map((m) => m.id) ?? [];
|
||||
if (providerPrefs?.mode && validModeIds.includes(providerPrefs.mode)) {
|
||||
return providerPrefs.mode;
|
||||
}
|
||||
return providerDef?.defaultModeId ?? "";
|
||||
return resolvePreferredModeId({
|
||||
preferredModeId: providerPrefs?.mode,
|
||||
providerDef,
|
||||
});
|
||||
}
|
||||
|
||||
function pickNextModeForProviderAndModel(input: {
|
||||
@@ -425,14 +435,8 @@ function pickNextModeForProviderAndModel(input: {
|
||||
providerDef: AgentProviderDefinition | undefined;
|
||||
providerPrefs: ProviderPrefs | undefined;
|
||||
}): string {
|
||||
const validModeIds = input.providerDef?.modes.map((m) => m.id) ?? [];
|
||||
if (
|
||||
input.currentProvider === input.provider &&
|
||||
input.currentModeId &&
|
||||
validModeIds.includes(input.currentModeId)
|
||||
) {
|
||||
return input.currentModeId;
|
||||
}
|
||||
const currentModeId = normalizeSelectedModeId(input.currentModeId);
|
||||
if (input.currentProvider === input.provider && currentModeId) return currentModeId;
|
||||
return pickNextModeForProvider({
|
||||
providerDef: input.providerDef,
|
||||
providerPrefs: input.providerPrefs,
|
||||
|
||||
Reference in New Issue
Block a user