From 057a4447a3231a1523a238858137cc476f7a6384 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=F0=9D=96=8E=F0=9D=96=9A=F0=9D=96=91=F0=9D=96=8E?= =?UTF-8?q?=F0=9D=96=8E=F0=9D=96=86?= Date: Wed, 26 Aug 2026 20:12:01 +0300 Subject: [PATCH] fix(ui): preserve default in thinking cycle (#3153) --- .../ui/src/components/chat/ModelControls.tsx | 69 ++++++++++++----- .../sections/openchamber/DefaultsSettings.tsx | 5 +- packages/ui/src/hooks/useKeyboardShortcuts.ts | 6 +- .../src/hooks/useMiniChatKeyboardShortcuts.ts | 5 +- packages/ui/src/stores/DOCUMENTATION.md | 7 ++ packages/ui/src/stores/useConfigStore.test.ts | 74 ++++++++++++++++++- packages/ui/src/stores/useConfigStore.ts | 72 ++++++++++++------ .../ui/src/sync/__tests__/issue-2039.test.ts | 34 ++++++++- packages/ui/src/sync/session-ui-store.ts | 7 +- 9 files changed, 225 insertions(+), 54 deletions(-) diff --git a/packages/ui/src/components/chat/ModelControls.tsx b/packages/ui/src/components/chat/ModelControls.tsx index 7a953090..4863a164 100644 --- a/packages/ui/src/components/chat/ModelControls.tsx +++ b/packages/ui/src/components/chat/ModelControls.tsx @@ -324,7 +324,9 @@ export const ModelControls: React.FC = ({ const providers = useConfigStore((state) => state.providers); const currentProviderId = useConfigStore((state) => state.currentProviderId); const currentModelId = useConfigStore((state) => state.currentModelId); - const currentVariant = useConfigStore((state) => state.currentVariant); + const effectiveCurrentVariant = useConfigStore((state) => state.currentVariant); + const currentVariantSelection = useConfigStore((state) => state.currentVariantSelection); + const currentVariant = currentVariantSelection.override ?? undefined; const currentAgentName = useConfigStore((state) => state.currentAgentName); const settingsDefaultVariant = useConfigStore((state) => state.settingsDefaultVariant); const settingsDefaultAgent = useConfigStore((state) => state.settingsDefaultAgent); @@ -332,6 +334,7 @@ export const ModelControls: React.FC = ({ const setSelectedProvider = useConfigStore((state) => state.setSelectedProvider); const setModel = useConfigStore((state) => state.setModel); const setCurrentVariant = useConfigStore((state) => state.setCurrentVariant); + const setCurrentVariantOverride = useConfigStore((state) => state.setCurrentVariantOverride); const getCurrentModelVariants = useConfigStore((state) => state.getCurrentModelVariants); const setAgent = useConfigStore((state) => state.setAgent); const getCurrentProvider = useConfigStore((state) => state.getCurrentProvider); @@ -693,6 +696,30 @@ export const ModelControls: React.FC = ({ return variants ? Object.keys(variants) : []; }, [providers]); + const resolveInheritedVariantForModel = React.useCallback((providerId: string, modelId: string, agentName?: string | null) => { + const variantOptions = getModelVariantOptions(providerId, modelId); + if (variantOptions.length === 0) return undefined; + + let currentInherited: string | undefined; + if (currentProviderId === providerId && currentModelId === modelId) { + currentInherited = currentVariantSelection.inherited + ?? (currentVariantSelection.override === null || currentVariantSelection.override === undefined + ? effectiveCurrentVariant + : undefined); + } + + const effectiveAgentName = agentName ?? uiAgentName ?? currentAgentName; + const agent = effectiveAgentName ? agents.find((candidate) => candidate.name === effectiveAgentName) : undefined; + const agentVariant = ( + agent?.model?.providerID === providerId + && agent.model.modelID === modelId + ) ? agent.variant : undefined; + const candidates = currentSessionId + ? [agentVariant, settingsDefaultVariant, currentInherited] + : [currentInherited, agentVariant, settingsDefaultVariant]; + return candidates.find((candidate) => candidate !== undefined && variantOptions.includes(candidate)); + }, [agents, currentAgentName, currentModelId, currentProviderId, currentSessionId, currentVariantSelection, effectiveCurrentVariant, getModelVariantOptions, settingsDefaultVariant, uiAgentName]); + const resolveModelVariantSelection = React.useCallback((providerId: string, modelId: string) => { const variantOptions = getModelVariantOptions(providerId, modelId); if (variantOptions.length === 0) { @@ -711,10 +738,6 @@ export const ModelControls: React.FC = ({ return currentVariant; } - if (!currentSessionId && settingsDefaultVariant && variantOptions.includes(settingsDefaultVariant)) { - return settingsDefaultVariant; - } - return undefined; }, [ currentAgentName, @@ -724,7 +747,6 @@ export const ModelControls: React.FC = ({ currentVariant, getAgentModelVariantForSession, getModelVariantOptions, - settingsDefaultVariant, uiAgentName, ]); @@ -748,7 +770,10 @@ export const ModelControls: React.FC = ({ } manualVariantSelectionRef.current = true; - setCurrentVariant(variant); + setCurrentVariantOverride( + variant ?? null, + resolveInheritedVariantForModel(providerId, modelId, agentNameOverride), + ); addRecentEffort(providerId, modelId, variant); const effectiveAgentName = agentNameOverride ?? resolveLiveAgentName(); @@ -759,9 +784,11 @@ export const ModelControls: React.FC = ({ addRecentEffort, currentSessionId, getModelVariantOptions, + resolveInheritedVariantForModel, resolveLiveAgentName, saveAgentModelVariantForSession, setCurrentVariant, + setCurrentVariantOverride, ]); const applyModelSelectionWithVariant = React.useCallback((providerId: string, modelId: string, variant: string | undefined, agentNameOverride?: string | null) => { @@ -1121,18 +1148,21 @@ export const ModelControls: React.FC = ({ } if (currentVariant && !availableVariants.includes(currentVariant)) { - setCurrentVariant(undefined); + setCurrentVariantOverride( + null, + resolveInheritedVariantForModel(currentProviderId, currentModelId), + ); return; } // Draft state (no session yet): seed from settings default, but don't override // user selection while drafting. if (!currentSessionId) { - if (!currentVariant && !manualVariantSelectionRef.current) { + if (currentVariantSelection.override === undefined && !manualVariantSelectionRef.current) { const desired = settingsDefaultVariant && availableVariants.includes(settingsDefaultVariant) ? settingsDefaultVariant : undefined; - setCurrentVariant(desired); + setCurrentVariantOverride(desired ?? null, desired); } return; } @@ -1144,13 +1174,14 @@ export const ModelControls: React.FC = ({ currentModelId, ); - const resolvedSaved = savedVariant && availableVariants.includes(savedVariant) - ? savedVariant - : settingsDefaultVariant && availableVariants.includes(settingsDefaultVariant) - ? settingsDefaultVariant - : undefined; - - setCurrentVariant(resolvedSaved); + const inheritedVariant = resolveInheritedVariantForModel(currentProviderId, currentModelId); + if (savedVariant && availableVariants.includes(savedVariant)) { + setCurrentVariantOverride(savedVariant, inheritedVariant); + } else if (currentVariantSelection.override === null) { + setCurrentVariantOverride(null, inheritedVariant); + } else { + setCurrentVariant(inheritedVariant); + } manualVariantSelectionRef.current = false; }, [ availableVariants, @@ -1160,8 +1191,12 @@ export const ModelControls: React.FC = ({ currentProviderId, currentModelId, currentVariant, + currentVariantSelection.override, + effectiveCurrentVariant, getAgentModelVariantForSession, + resolveInheritedVariantForModel, setCurrentVariant, + setCurrentVariantOverride, settingsDefaultVariant, ]); diff --git a/packages/ui/src/components/sections/openchamber/DefaultsSettings.tsx b/packages/ui/src/components/sections/openchamber/DefaultsSettings.tsx index 627964e7..ca4be95c 100644 --- a/packages/ui/src/components/sections/openchamber/DefaultsSettings.tsx +++ b/packages/ui/src/components/sections/openchamber/DefaultsSettings.tsx @@ -42,6 +42,7 @@ export const DefaultsSettings: React.FC = () => { const setModel = useConfigStore((state) => state.setModel); const setAgent = useConfigStore((state) => state.setAgent); const setCurrentVariant = useConfigStore((state) => state.setCurrentVariant); + const setCurrentVariantOverride = useConfigStore((state) => state.setCurrentVariantOverride); const setSettingsDefaultModel = useConfigStore((state) => state.setSettingsDefaultModel); const setSettingsDefaultVariant = useConfigStore((state) => state.setSettingsDefaultVariant); const setSettingsDefaultAgent = useConfigStore((state) => state.setSettingsDefaultAgent); @@ -210,7 +211,7 @@ export const DefaultsSettings: React.FC = () => { setDefaultVariant(newValue); setSettingsDefaultVariant(newValue); if (!chatHasOwnModel) { - setCurrentVariant(newValue); + setCurrentVariantOverride(newValue ?? null, newValue); } try { @@ -219,7 +220,7 @@ export const DefaultsSettings: React.FC = () => { console.warn('Failed to save default variant:', error); } }, - [chatHasOwnModel, setCurrentVariant, setSettingsDefaultVariant] + [chatHasOwnModel, setCurrentVariantOverride, setSettingsDefaultVariant] ); const handleAgentChange = React.useCallback( diff --git a/packages/ui/src/hooks/useKeyboardShortcuts.ts b/packages/ui/src/hooks/useKeyboardShortcuts.ts index 0c99f3b0..61ad272d 100644 --- a/packages/ui/src/hooks/useKeyboardShortcuts.ts +++ b/packages/ui/src/hooks/useKeyboardShortcuts.ts @@ -273,16 +273,16 @@ export const useKeyboardShortcuts = () => { if (state.isSettingsDialogOpen || hasOverlay) return false; const config = useConfigStore.getState(); if (config.getCurrentModelVariants().length === 0) return false; - config.cycleCurrentVariant(); + const nextVariantOverride = config.cycleCurrentVariant(); const sessionId = useSessionUIStore.getState().currentSessionId; - const { currentVariant, currentAgentName, currentProviderId, currentModelId } = useConfigStore.getState(); + const { currentAgentName, currentProviderId, currentModelId } = useConfigStore.getState(); if (sessionId && currentAgentName && currentProviderId && currentModelId) { useSelectionStore.getState().saveAgentModelVariantForSession( sessionId, currentAgentName, currentProviderId, currentModelId, - currentVariant, + nextVariantOverride, ); } }, diff --git a/packages/ui/src/hooks/useMiniChatKeyboardShortcuts.ts b/packages/ui/src/hooks/useMiniChatKeyboardShortcuts.ts index ead55305..aaa31edf 100644 --- a/packages/ui/src/hooks/useMiniChatKeyboardShortcuts.ts +++ b/packages/ui/src/hooks/useMiniChatKeyboardShortcuts.ts @@ -71,10 +71,9 @@ export const useMiniChatKeyboardShortcuts = () => { const configState = useConfigStore.getState(); if (configState.getCurrentModelVariants().length === 0) return false; - configState.cycleCurrentVariant(); + const nextVariantOverride = configState.cycleCurrentVariant(); const sessionId = useSessionUIStore.getState().currentSessionId; const { - currentVariant, currentAgentName, currentProviderId, currentModelId, @@ -85,7 +84,7 @@ export const useMiniChatKeyboardShortcuts = () => { currentAgentName, currentProviderId, currentModelId, - currentVariant, + nextVariantOverride, ); } }, diff --git a/packages/ui/src/stores/DOCUMENTATION.md b/packages/ui/src/stores/DOCUMENTATION.md index 3eaf1ffb..ed68eff9 100644 --- a/packages/ui/src/stores/DOCUMENTATION.md +++ b/packages/ui/src/stores/DOCUMENTATION.md @@ -213,6 +213,13 @@ Each of them therefore keeps two things: - a flat mirror (`agents`, `commands`, `skills`, `mcpServers`, `providers`) that tracks the **active** project only. +Thinking variants keep the effective value in `currentVariant` so existing send +paths capture a stable configuration. The transient `currentVariantSelection` +distinguishes automatic initialization from a picker or shortcut choosing an +explicit override or `Default`; returning to `Default` restores its inherited +effective value. Only explicit overrides are stored in the per-session +selection store. + Every loader and mutation takes an explicit directory; omitting it means the active project, which is what non-Settings callers pass. A load for another directory writes the map and leaves the mirror alone, so browsing another diff --git a/packages/ui/src/stores/useConfigStore.test.ts b/packages/ui/src/stores/useConfigStore.test.ts index b3fb527c..1cd6e4d1 100644 --- a/packages/ui/src/stores/useConfigStore.test.ts +++ b/packages/ui/src/stores/useConfigStore.test.ts @@ -268,6 +268,7 @@ describe('useConfigStore provider persistence', () => { currentProviderId: '', currentModelId: '', currentVariant: undefined, + currentVariantSelection: { override: undefined, inherited: undefined }, selectedProviderId: '', currentAgentName: undefined, agents: [], @@ -525,20 +526,58 @@ describe('useConfigStore provider persistence', () => { expect(state.directoryScoped[DIRECTORY]?.currentVariant).toBe('high'); }); - test('cycleCurrentVariant wraps through every model variant', () => { + test('cycleCurrentVariant reaches Default, low, and medium from inherited high', () => { useConfigStore.setState({ providers: [provider('openai', 'gpt-5.6-sol', { none: {}, low: {}, medium: {}, high: {}, xhigh: {}, max: {} })], currentProviderId: 'openai', currentModelId: 'gpt-5.6-sol', currentVariant: 'high', + currentVariantSelection: { override: undefined, inherited: 'high' }, directoryScoped: {}, }); - const expectedVariants = ['xhigh', 'max', 'none', 'low', 'medium', 'high']; + const expectedVariants = ['xhigh', 'max', undefined, 'none', 'low', 'medium', 'high']; for (const expectedVariant of expectedVariants) { - useConfigStore.getState().cycleCurrentVariant(); - expect(useConfigStore.getState().currentVariant).toBe(expectedVariant); + expect(useConfigStore.getState().cycleCurrentVariant()).toBe(expectedVariant); + expect(useConfigStore.getState().currentVariantSelection.override).toBe(expectedVariant ?? null); } + + useConfigStore.getState().setCurrentVariantOverride('max', 'high'); + expect(useConfigStore.getState().cycleCurrentVariant()).toBe(undefined); + expect(useConfigStore.getState().currentVariant).toBe('high'); + expect(useConfigStore.getState().currentVariantSelection).toEqual({ override: null, inherited: 'high' }); + }); + + test('cycleCurrentVariant toggles a single variant with Default', () => { + useConfigStore.setState({ + providers: [provider('openai', 'single', { high: {} })], + currentProviderId: 'openai', + currentModelId: 'single', + currentVariant: 'high', + currentVariantSelection: { override: null, inherited: 'high' }, + directoryScoped: {}, + }); + + expect(useConfigStore.getState().cycleCurrentVariant()).toBe('high'); + expect(useConfigStore.getState().currentVariantSelection.override).toBe('high'); + expect(useConfigStore.getState().cycleCurrentVariant()).toBe(undefined); + expect(useConfigStore.getState().currentVariantSelection.override).toBeNull(); + expect(useConfigStore.getState().currentVariant).toBe('high'); + }); + + test('an unavailable explicit variant cycles back to Default', () => { + useConfigStore.setState({ + providers: [provider('openai', 'changed', { low: {}, high: {} })], + currentProviderId: 'openai', + currentModelId: 'changed', + currentVariant: 'removed', + currentVariantSelection: { override: 'removed', inherited: 'low' }, + directoryScoped: {}, + }); + + expect(useConfigStore.getState().cycleCurrentVariant()).toBe(undefined); + expect(useConfigStore.getState().currentVariant).toBe('low'); + expect(useConfigStore.getState().currentVariantSelection.override).toBeNull(); }); test('setAgent prefers saved and agent variants before settings default', () => { @@ -716,6 +755,29 @@ describe('useConfigStore provider persistence', () => { expect(state.currentVariant).toBe('high'); }); + test('a fresh session applies the settings thinking level instead of the previous override', () => { + useConfigStore.setState({ + activeDirectoryKey: DIRECTORY, + providers: [provider('openai', 'gpt-5.5', { low: {}, high: {} })], + agents: [testAgent('build')], + currentProviderId: 'openai', + currentModelId: 'gpt-5.5', + currentVariant: 'low', + currentVariantSelection: { override: 'low', inherited: 'high' }, + settingsDefaultModel: 'openai/gpt-5.5', + settingsDefaultVariant: 'high', + selectionSource: 'manual', + directoryScoped: {}, + }); + + useConfigStore.getState().applyDefaultModelAgentSelection(); + + const state = useConfigStore.getState(); + expect(state.currentVariant).toBe('high'); + expect(state.currentVariantSelection).toEqual({ override: 'high', inherited: 'high' }); + expect(state.directoryScoped[DIRECTORY]?.currentVariant).toBe('high'); + }); + test('a thinking level the project model does not offer is ignored', async () => { useConfigStore.setState({ activeDirectoryKey: DIRECTORY, @@ -1052,6 +1114,8 @@ describe('useConfigStore provider persistence', () => { useConfigStore.setState({ activeDirectoryKey: DIRECTORY, selectionSource: 'manual', + currentVariant: 'high', + currentVariantSelection: { override: 'high', inherited: 'medium' }, opencodeDefaultAgent: 'active-default', opencodeDefaultModel: 'active/model', directoryScoped: { @@ -1073,6 +1137,7 @@ describe('useConfigStore provider persistence', () => { agents: [testAgent('other-agent')], currentProviderId: 'other', currentModelId: 'other-model', + currentVariant: 'low', currentAgentName: 'other-agent', selectedProviderId: 'other', agentModelSelections: {}, @@ -1092,6 +1157,7 @@ describe('useConfigStore provider persistence', () => { expect(state.selectionSource).toBe('auto'); expect(state.opencodeDefaultAgent).toBe('other-default'); expect(state.opencodeDefaultModel).toBe('other/model'); + expect(state.currentVariantSelection).toEqual({ override: undefined, inherited: 'low' }); }); test('sync config without defaults clears stored OpenCode defaults without changing manual selection', () => { diff --git a/packages/ui/src/stores/useConfigStore.ts b/packages/ui/src/stores/useConfigStore.ts index c4884748..46ac3379 100644 --- a/packages/ui/src/stores/useConfigStore.ts +++ b/packages/ui/src/stores/useConfigStore.ts @@ -885,6 +885,11 @@ interface DirectoryScopedConfig { selectionSource?: "auto" | "manual"; } +type CurrentVariantSelection = { + override: string | null | undefined; + inherited: string | undefined; +}; + /** * Lift the active directory's cached provider/agent snapshot into the top-level * fields the pickers read (`providers`, `agents`, selections), so a cold start @@ -1006,6 +1011,7 @@ interface ConfigStore { currentProviderId: string; currentModelId: string; currentVariant: string | undefined; + currentVariantSelection: CurrentVariantSelection; currentAgentName: string | undefined; selectedProviderId: string; agentModelSelections: { [agentName: string]: { providerId: string; modelId: string } }; @@ -1098,7 +1104,8 @@ interface ConfigStore { setProvider: (providerId: string) => void; setModel: (modelId: string) => void; setCurrentVariant: (variant: string | undefined) => void; - cycleCurrentVariant: () => void; + setCurrentVariantOverride: (override: string | null | undefined, inherited: string | undefined) => void; + cycleCurrentVariant: () => string | undefined; getCurrentModelVariants: () => string[]; setAgent: (agentName: string | undefined) => void; applyDefaultModelAgentSelection: (options?: { projectDefaultModel?: string; projectDefaultVariant?: string }) => void; @@ -1171,6 +1178,7 @@ export const useConfigStore = create()( currentProviderId: "", currentModelId: "", currentVariant: undefined, + currentVariantSelection: { override: undefined, inherited: undefined }, currentAgentName: undefined, selectedProviderId: "", agentModelSelections: {}, @@ -1437,6 +1445,7 @@ export const useConfigStore = create()( currentProviderId: snapshot.currentProviderId, currentModelId: snapshot.currentModelId, currentVariant: snapshot.currentVariant, + currentVariantSelection: { override: undefined, inherited: snapshot.currentVariant }, currentAgentName: snapshot.currentAgentName, selectedProviderId: snapshot.selectedProviderId, agentModelSelections: snapshot.agentModelSelections, @@ -1453,6 +1462,7 @@ export const useConfigStore = create()( agents: [], currentProviderId: "", currentModelId: "", + currentVariantSelection: { override: undefined, inherited: undefined }, currentAgentName: undefined, selectedProviderId: "", agentModelSelections: {}, @@ -1847,13 +1857,22 @@ export const useConfigStore = create()( }, setCurrentVariant: (variant: string | undefined) => { + get().setCurrentVariantOverride(undefined, variant); + }, + + setCurrentVariantOverride: (override, inherited) => { set((state) => { - if (state.currentVariant === variant) { + const currentVariant = override ?? inherited; + if ( + state.currentVariant === currentVariant + && state.currentVariantSelection.override === override + && state.currentVariantSelection.inherited === inherited + ) { return state; } const directoryKey = state.activeDirectoryKey; - const baseSnapshot: DirectoryScopedConfig = state.directoryScoped[directoryKey] ?? { + const baseSnapshot = state.directoryScoped[directoryKey] ?? { providers: state.providers, agents: state.agents, currentProviderId: state.currentProviderId, @@ -1865,18 +1884,17 @@ export const useConfigStore = create()( defaultProviders: state.defaultProviders, }; - const nextSnapshot: DirectoryScopedConfig = { - ...baseSnapshot, - currentVariant: variant, - selectionSource: "manual", - }; - return { - currentVariant: variant, + currentVariant, + currentVariantSelection: { override, inherited }, selectionSource: "manual", directoryScoped: { ...state.directoryScoped, - [directoryKey]: nextSnapshot, + [directoryKey]: { + ...baseSnapshot, + currentVariant, + selectionSource: "manual", + }, }, }; }); @@ -1894,22 +1912,26 @@ export const useConfigStore = create()( cycleCurrentVariant: () => { const variantKeys = get().getCurrentModelVariants(); if (variantKeys.length === 0) { - return; + return undefined; } - const current = get().currentVariant; - if (!current) { - get().setCurrentVariant(variantKeys[0]); - return; + const state = get(); + const currentOverride = state.currentVariantSelection.override; + const inheritedVariant = state.currentVariantSelection.inherited ?? state.currentVariant; + const currentVariant = currentOverride === undefined + ? state.currentVariant + : currentOverride; + let nextOverride: string | null; + + if (currentVariant === null || currentVariant === undefined) { + nextOverride = variantKeys[0]; + } else { + const index = variantKeys.indexOf(currentVariant); + nextOverride = index >= 0 ? (variantKeys[index + 1] ?? null) : null; } - const index = variantKeys.indexOf(current); - if (index === -1) { - get().setCurrentVariant(variantKeys[0]); - return; - } - - get().setCurrentVariant(variantKeys[(index + 1) % variantKeys.length]); + get().setCurrentVariantOverride(nextOverride, inheritedVariant); + return nextOverride ?? undefined; }, setSelectedProvider: (providerId: string) => { @@ -2659,6 +2681,10 @@ export const useConfigStore = create()( nextState.currentProviderId = resolvedProviderId; nextState.currentModelId = resolvedModelId; nextState.currentVariant = resolvedVariant; + nextState.currentVariantSelection = { + override: resolvedVariant, + inherited: resolvedVariant, + }; } return nextState; diff --git a/packages/ui/src/sync/__tests__/issue-2039.test.ts b/packages/ui/src/sync/__tests__/issue-2039.test.ts index 572d2094..7558bac5 100644 --- a/packages/ui/src/sync/__tests__/issue-2039.test.ts +++ b/packages/ui/src/sync/__tests__/issue-2039.test.ts @@ -4,6 +4,8 @@ import { togglePermissionAutoAccept } from "../../components/chat/permissionAuto const storage = new Map() const createSessionCalls: Array<{ title?: string; directory: string | null; parentID: string | null; metadata?: unknown }> = [] const permissionAutoAcceptCalls: Array<[string, boolean]> = [] +const savedVariantCalls: Array = [] +let configVariantOverride: string | null | undefined // Sync's session→directory index. `createSession` writes it, and directory // resolution reads it as the authoritative source, so the mock has to keep one. const sessionDirectoryRegistry = new Map() @@ -96,6 +98,9 @@ mock.module("@/stores/useConfigStore", () => ({ useConfigStore: { getState: () => ({ currentAgentName: "agent-default", + currentProviderId: "provider", + currentModelId: "model", + currentVariantSelection: { override: configVariantOverride, inherited: "high" }, agents: [], activateDirectory: mock(async () => undefined), applyDefaultModelAgentSelection: mock(() => undefined), @@ -170,7 +175,9 @@ mock.module("../selection-store", () => ({ saveSessionModelSelection: () => undefined, saveSessionAgentSelection: () => undefined, saveAgentModelForSession: () => undefined, - saveAgentModelVariantForSession: () => undefined, + saveAgentModelVariantForSession: (_sessionId: string, _agent: string, _provider: string, _model: string, variant: string | undefined) => { + savedVariantCalls.push(variant) + }, getSessionAgentSelection: () => null, getSessionModelSelection: () => null, getAgentModelForSession: () => null, @@ -348,6 +355,8 @@ describe("issue 2039 draft auto-accept", () => { createSessionCalls.length = 0 sessionDirectoryRegistry.clear() permissionAutoAcceptCalls.length = 0 + savedVariantCalls.length = 0 + configVariantOverride = undefined createdSessionDirectory = undefined useSessionUIStore.setState({ @@ -384,6 +393,29 @@ describe("issue 2039 draft auto-accept", () => { expect(useSessionUIStore.getState().currentSessionId).toBe("ses_issue_2039") }) + test("stores only an explicit draft variant as the session override", async () => { + useSessionUIStore.getState().openNewSessionDraft() + await materializeOpenDraftSession({ + providerID: "provider", + modelID: "model", + agent: "agent-default", + variant: "high", + }) + + expect(savedVariantCalls).toEqual([undefined]) + + configVariantOverride = "high" + useSessionUIStore.getState().openNewSessionDraft() + await materializeOpenDraftSession({ + providerID: "provider", + modelID: "model", + agent: "agent-default", + variant: "high", + }) + + expect(savedVariantCalls).toEqual([undefined, "high"]) + }) + test("does not apply draft auto-accept after the draft is closed", async () => { useSessionUIStore.getState().openNewSessionDraft() useSessionUIStore.getState().setDraftPermissionAutoAcceptEnabled(true) diff --git a/packages/ui/src/sync/session-ui-store.ts b/packages/ui/src/sync/session-ui-store.ts index 769193d3..85aa58be 100644 --- a/packages/ui/src/sync/session-ui-store.ts +++ b/packages/ui/src/sync/session-ui-store.ts @@ -840,13 +840,18 @@ export async function materializeOpenDraftSession(selection: { }) const effectiveDraftAgent = trimmedAgent ?? configState.currentAgentName + const variantOverride = configState.currentProviderId === selection.providerID + && configState.currentModelId === selection.modelID + && configState.currentAgentName === effectiveDraftAgent + ? configState.currentVariantSelection.override ?? undefined + : selection.variant useSelectionStore.getState().saveSessionModelSelection(created.id, selection.providerID, selection.modelID) if (effectiveDraftAgent) { useSelectionStore.getState().saveSessionAgentSelection(created.id, effectiveDraftAgent) useSelectionStore.getState().saveAgentModelForSession(created.id, effectiveDraftAgent, selection.providerID, selection.modelID) - useSelectionStore.getState().saveAgentModelVariantForSession(created.id, effectiveDraftAgent, selection.providerID, selection.modelID, selection.variant) + useSelectionStore.getState().saveAgentModelVariantForSession(created.id, effectiveDraftAgent, selection.providerID, selection.modelID, variantOverride) } store.initializeNewOpenChamberSession(created.id, configState.agents ?? [])