From 0a47b306d6d238ff13429cae01af73a42b3a9bf9 Mon Sep 17 00:00:00 2001 From: Iuliia Ivashko Date: Fri, 4 Sep 2026 02:16:21 +0300 Subject: [PATCH] fix(ui): stop pinning inherited effort as a session choice on send Sending recorded the effective variant - inherited settings/agent defaults included - as the session's explicit per-session choice, so the picker jumped from Default to the inherited effort right after the send and the session stopped following later default changes. Record only an explicit picker override when the send reflects the live selection; sends carrying a captured configuration for another session keep their captured variant. --- packages/ui/src/sync/session-ui-store.test.js | 95 +++++++++++++++++++ packages/ui/src/sync/session-ui-store.ts | 16 +++- 2 files changed, 110 insertions(+), 1 deletion(-) 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) {