From d843012de8667dff748d439d2c9a4ca201372afb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Igor=20=C5=A0=C4=87eki=C4=87?= Date: Sat, 12 Sep 2026 03:27:04 +0000 Subject: [PATCH] fix(mobile): restore last picked model and effort on new session Surface: the mobile app (apps/mobile). On the new session page, the last selected model/effort combo is not persisted, so the user is forced to pick for every new session. Persist the last picked model/effort combo and restore it on the new session page, with a reasonable fallback in case the persisted choice is not available. --- .../src/lib/hooks/auto-select-model.test.ts | 32 +++++++++++++++++-- .../mobile/src/lib/hooks/auto-select-model.ts | 22 +++++++------ 2 files changed, 41 insertions(+), 13 deletions(-) diff --git a/apps/mobile/src/lib/hooks/auto-select-model.test.ts b/apps/mobile/src/lib/hooks/auto-select-model.test.ts index 438c6e05e4..58ca386851 100644 --- a/apps/mobile/src/lib/hooks/auto-select-model.test.ts +++ b/apps/mobile/src/lib/hooks/auto-select-model.test.ts @@ -14,6 +14,12 @@ const claude = { variants: ['thinking'], isPreferred: true, }; +const opus = { + id: 'anthropic/opus', + name: 'Opus', + variants: ['low', 'high'], + isPreferred: false, +}; const gpt = { id: 'openai/gpt', name: 'GPT', variants: [], isPreferred: false }; const base: AutoSelectInput = { @@ -60,23 +66,43 @@ describe('pickAutoSelectedModel', () => { ).toEqual({ model: 'kilo-auto/efficient', variant: '' }); }); - it('dev, efficient wins over server lastSelected', () => { + it('dev, persisted server combo wins over the efficient default', () => { expect( pickAutoSelectedModel({ ...base, models: [claude, efficient], lastSelected: { model: 'anthropic/claude', variant: 'thinking' }, }) - ).toEqual({ model: 'kilo-auto/efficient', variant: '' }); + ).toEqual({ model: 'anthropic/claude', variant: 'thinking' }); + }); + + it('dev, server combo restores the selected effort', () => { + expect( + pickAutoSelectedModel({ + ...base, + models: [opus, efficient], + lastSelected: { model: 'anthropic/opus', variant: 'high' }, + }) + ).toEqual({ model: 'anthropic/opus', variant: 'high' }); }); - it('dev, efficient wins over a local persisted preference', () => { + it('dev, local persisted combo wins over the efficient default', () => { expect( pickAutoSelectedModel({ ...base, models: [gpt, efficient], stored: { personal: { model: 'openai/gpt', variant: '' } }, }) + ).toEqual({ model: 'openai/gpt', variant: '' }); + }); + + it('dev, persisted model gone from the catalog → falls back to efficient', () => { + expect( + pickAutoSelectedModel({ + ...base, + models: [claude, efficient], + stored: { personal: { model: 'gone/model', variant: 'high' } }, + }) ).toEqual({ model: 'kilo-auto/efficient', variant: '' }); }); diff --git a/apps/mobile/src/lib/hooks/auto-select-model.ts b/apps/mobile/src/lib/hooks/auto-select-model.ts index 49d183ef59..fa998343fb 100644 --- a/apps/mobile/src/lib/hooks/auto-select-model.ts +++ b/apps/mobile/src/lib/hooks/auto-select-model.ts @@ -24,28 +24,30 @@ export type AutoSelectInput = { }; /** - * Pick the new-session model. In dev builds, `kilo-auto/efficient` wins over every - * override when the catalog holds it. Otherwise the priority is server lastSelected, - * then local persisted preference, then org default, then the first catalog entry. + * Pick the new-session model. A persisted choice is always restored first so + * the user's last picked model/effort combo comes back: the server + * `lastSelected`, then the local persisted preference. When neither is + * available in the catalog, dev builds fall back to `kilo-auto/efficient`, + * then the org default, then the first catalog entry. */ export function pickAutoSelectedModel( input: AutoSelectInput ): { model: string; variant: string } | null { const { models, lastSelected, stored, organizationId, orgDefaultModel, isDev } = input; - const devDefaultMatch = isDev ? models.find(m => m.id === DEV_DEFAULT_MODEL_ID) : undefined; - if (devDefaultMatch) { - return { model: devDefaultMatch.id, variant: pickVariant(devDefaultMatch, undefined) }; - } const serverMatch = lastSelected ? models.find(m => m.id === lastSelected.model) : undefined; - const localEntry = resolveModelForContext(stored, contextKey(organizationId), models); - const orgDefaultMatch = orgDefaultModel ? models.find(m => m.id === orgDefaultModel) : undefined; - const fallback = orgDefaultMatch ?? models[0]; if (serverMatch) { return { model: serverMatch.id, variant: pickVariant(serverMatch, lastSelected?.variant) }; } + const localEntry = resolveModelForContext(stored, contextKey(organizationId), models); if (localEntry) { return localEntry; } + const devDefaultMatch = isDev ? models.find(m => m.id === DEV_DEFAULT_MODEL_ID) : undefined; + if (devDefaultMatch) { + return { model: devDefaultMatch.id, variant: pickVariant(devDefaultMatch, undefined) }; + } + const orgDefaultMatch = orgDefaultModel ? models.find(m => m.id === orgDefaultModel) : undefined; + const fallback = orgDefaultMatch ?? models[0]; if (fallback) { return { model: fallback.id, variant: pickVariant(fallback, undefined) }; }