diff --git a/packages/ui/src/sync/session-ui-store.test.js b/packages/ui/src/sync/session-ui-store.test.js index 8106a68d..9e088bcf 100644 --- a/packages/ui/src/sync/session-ui-store.test.js +++ b/packages/ui/src/sync/session-ui-store.test.js @@ -4,6 +4,7 @@ import { useProjectsStore } from '@/stores/useProjectsStore'; import { useDirectoryStore } from '@/stores/useDirectoryStore'; import { useSessionWorktreeStore } from './session-worktree-store'; import { expandSlashCommandGoalObjective, routeMessage, useSessionUIStore } from './session-ui-store'; +import { useSelectionStore } from './selection-store'; import { setActionRefs, setOptimisticRefs } from './session-actions'; import { useSkillsStore } from '@/stores/useSkillsStore'; import { useCommandsStore } from '@/stores/useCommandsStore'; @@ -352,6 +353,100 @@ describe('sendMessage captured target', () => { }); }); +describe('sendMessage effort recording', () => { + let originalSendMessage; + const calls = []; + + beforeEach(() => { + calls.length = 0; + const childStore = { + getState: () => ({ session: [], message: {}, part: {}, session_status: {} }), + setState: () => {}, + }; + const childStores = { + children: new Map(), + ensureChild: () => childStore, + getChild: () => childStore, + }; + setActionRefs(opencodeClient, childStores, () => '/current/project'); + setOptimisticRefs(() => {}, () => {}); + useConfigStore.setState({ isConnected: true }); + useSessionUIStore.setState({ + currentSessionId: 'session-current', + currentSessionDirectory: '/current/project', + newSessionDraft: { open: false, directoryOverride: null, parentID: null }, + }); + + originalSendMessage = opencodeClient.sendMessage; + opencodeClient.sendMessage = async (params) => { + calls.push(params); + return 'msg'; + }; + }); + + afterEach(() => { + opencodeClient.sendMessage = originalSendMessage; + }); + + const sendWithVariant = (variant, sessionId) => useSessionUIStore.getState().sendMessage( + 'hello', + 'provider-a', + 'model-a', + 'plan', + undefined, + undefined, + undefined, + variant, + 'normal', + { target: { runtimeKey: getRuntimeKey(), sessionId, directory: '/current/project' } }, + ); + + test('does not pin an inherited effort as the session choice', async () => { + useConfigStore.setState({ + currentProviderId: 'provider-a', + currentModelId: 'model-a', + currentAgentName: 'plan', + currentVariant: 'low', + currentVariantSelection: { override: undefined, inherited: 'low' }, + }); + + await sendWithVariant('low', 'session-current'); + + // The send still carries the inherited effort; it is just not recorded + // as this session's explicit choice. + expect(calls[0].variant).toBe('low'); + expect(useSelectionStore.getState().getAgentModelVariantForSession('session-current', 'plan', 'provider-a', 'model-a')).toBe(undefined); + }); + + test('records an explicit effort choice with the send', async () => { + useConfigStore.setState({ + currentProviderId: 'provider-a', + currentModelId: 'model-a', + currentAgentName: 'plan', + currentVariant: 'high', + currentVariantSelection: { override: 'high', inherited: 'low' }, + }); + + await sendWithVariant('high', 'session-current'); + + expect(useSelectionStore.getState().getAgentModelVariantForSession('session-current', 'plan', 'provider-a', 'model-a')).toBe('high'); + }); + + test('keeps the captured variant for a send targeting another session', async () => { + useConfigStore.setState({ + currentProviderId: 'provider-a', + currentModelId: 'model-a', + currentAgentName: 'plan', + currentVariant: 'low', + currentVariantSelection: { override: undefined, inherited: 'low' }, + }); + + await sendWithVariant('low', 'session-other'); + + expect(useSelectionStore.getState().getAgentModelVariantForSession('session-other', 'plan', 'provider-a', 'model-a')).toBe('low'); + }); +}); + describe('slash-command goal objectives', () => { test('expands every $ARGUMENTS reference from the authoritative command template', () => { expect(expandSlashCommandGoalObjective('/issue--to-pr LIN-123 --draft', [{ diff --git a/packages/ui/src/sync/session-ui-store.ts b/packages/ui/src/sync/session-ui-store.ts index 61ecc534..2f31b09a 100644 --- a/packages/ui/src/sync/session-ui-store.ts +++ b/packages/ui/src/sync/session-ui-store.ts @@ -1707,7 +1707,21 @@ export const useSessionUIStore = create()((set, get) => ({ if (targetSessionId && effectiveAgent) { useSelectionStore.getState().saveSessionAgentSelection(targetSessionId, effectiveAgent) useSelectionStore.getState().saveAgentModelForSession(targetSessionId, effectiveAgent, providerID, modelID) - useSelectionStore.getState().saveAgentModelVariantForSession(targetSessionId, effectiveAgent, providerID, modelID, variant) + // `variant` is the effective effort, inherited defaults included. + // Recording it verbatim would pin the inherited default as this + // session's explicit choice, so the picker jumps from "Default" to + // that effort after the send. When the send reflects the live picker + // selection, record only an explicit override; sends carrying a + // captured configuration for another context keep their variant. + const configState = useConfigStore.getState() + const reflectsLiveSelection = targetSessionId === get().currentSessionId + && configState.currentProviderId === providerID + && configState.currentModelId === modelID + && configState.currentAgentName === effectiveAgent + const variantToRecord = reflectsLiveSelection + ? configState.currentVariantSelection.override ?? undefined + : variant + useSelectionStore.getState().saveAgentModelVariantForSession(targetSessionId, effectiveAgent, providerID, modelID, variantToRecord) } if (targetSessionId) {