From f24bd8a2a29942bb54cfcb2932762186059ae774 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Fri, 14 Aug 2026 22:39:54 -0700 Subject: [PATCH] fix(ui): use session-key equivalence for active-row lookups in model/thinking/fast-mode paths (#124020) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Six sites strict-compared row.key === sessionKey while the sibling locked check already used areUiSessionKeysEquivalent (#104045 upgraded only the model path). In alias/transition windows (pre-hello legacy 'main' vs canonical agent:main:main) the optimistic thinking/fast-mode row patch found no row — the click produced no immediate visible change — and the display resolvers missed the active row, falling back to agent defaults. All lookups now go through the equivalence helper the rest of the flow uses. --- ui/src/lib/chat/model-select-state.test.ts | 19 +++++++++++++++++++ ui/src/lib/chat/model-select-state.ts | 9 +++++++-- ui/src/lib/chat/thinking.ts | 8 ++++++-- ui/src/pages/chat/chat-session.ts | 10 +++++++--- 4 files changed, 39 insertions(+), 7 deletions(-) diff --git a/ui/src/lib/chat/model-select-state.test.ts b/ui/src/lib/chat/model-select-state.test.ts index c6f7bcb76436..be5b6d20dbca 100644 --- a/ui/src/lib/chat/model-select-state.test.ts +++ b/ui/src/lib/chat/model-select-state.test.ts @@ -122,6 +122,25 @@ describe("chat-model-select-state", () => { }); }); + it("finds the active row across the legacy main alias window", () => { + // Pre-hello (or legacy-alias) states select "main" while the row list + // already carries the canonical agent:main:main key; a strict compare + // missed the row and the picker fell back to the agent default. + const sessionsResult = createSessionsListResult({ + model: "gpt-5.3-codex", + modelProvider: "openai", + }); + const session = expectDefined(sessionsResult.sessions[0], "alias fixture row"); + sessionsResult.sessions[0] = { ...session, key: "agent:main:main" }; + const value = resolveChatModelOverrideValue( + createChatModelState({ + chatModelCatalog: DEFAULT_CHAT_MODEL_CATALOG, + sessionsResult, + }), + ); + expect(value).toBe("openai/gpt-5.3-codex"); + }); + it("uses the server-qualified value when the active session provider is present", () => { const state = createChatModelState({ chatModelCatalog: createModelCatalog(DEEPSEEK_CHAT_MODEL), diff --git a/ui/src/lib/chat/model-select-state.ts b/ui/src/lib/chat/model-select-state.ts index 8c06cd8bd432..78f3027baf24 100644 --- a/ui/src/lib/chat/model-select-state.ts +++ b/ui/src/lib/chat/model-select-state.ts @@ -6,6 +6,7 @@ import type { SessionsListResult, } from "../../api/types.ts"; import { t } from "../../i18n/index.ts"; +import { areUiSessionKeysEquivalent } from "../sessions/session-key.ts"; import { buildCatalogDisplayLookup, buildChatModelOptionFromLookup, @@ -72,7 +73,9 @@ type ChatFastModeSelectStateInput = { const FAST_MODE_PROVIDER_IDS = new Set(["anthropic", "minimax", "minimax-portal", "openai", "xai"]); function resolveActiveSessionRow(state: ChatModelSelectStateInput) { - return state.sessionsResult?.sessions?.find((row) => row.key === state.sessionKey); + return state.sessionsResult?.sessions?.find((row) => + areUiSessionKeysEquivalent(row.key, state.sessionKey), + ); } export function resolveChatModelOverrideValue(state: ChatModelSelectStateInput): string { @@ -304,7 +307,9 @@ function hasCatalogProviderMetadata(value: string, catalog: ModelCatalogEntry[]) export function resolveChatFastModeSelectState( input: ChatFastModeSelectStateInput, ): ChatFastModeSelectState { - const activeRow = input.sessionsResult?.sessions?.find((row) => row.key === input.sessionKey); + const activeRow = input.sessionsResult?.sessions?.find((row) => + areUiSessionKeysEquivalent(row.key, input.sessionKey), + ); const activeProvider = normalizeChatModelProviderId(activeRow?.modelProvider ?? "") || null; const defaultProvider = normalizeChatModelProviderId(input.sessionsResult?.defaults?.modelProvider ?? "") || null; diff --git a/ui/src/lib/chat/thinking.ts b/ui/src/lib/chat/thinking.ts index 74c04d14ceee..a49082e6e724 100644 --- a/ui/src/lib/chat/thinking.ts +++ b/ui/src/lib/chat/thinking.ts @@ -4,7 +4,6 @@ import { normalizeThinkLevel, resolveThinkingDefaultForModelCore, } from "../../../../src/auto-reply/thinking.shared.js"; -// Control UI module implements thinking behavior. import type { GatewaySessionRow, GatewayThinkingLevelOption, @@ -13,6 +12,8 @@ import type { } from "../../api/types.ts"; import { pushUniqueTrimmedSelectOption } from "../select-options.ts"; import { sessionModelMatchesDefaults } from "../session-model-defaults.ts"; +// Control UI module implements thinking behavior. +import { areUiSessionKeysEquivalent } from "../sessions/session-key.ts"; type ThinkingSessionDefaults = SessionsListResult["defaults"] | undefined; @@ -216,7 +217,10 @@ export function resolveChatThinkingSelectState(params: { sessionsResult: SessionsListResult | null; }): ChatThinkingSelectState { const session = - params.session ?? params.sessionsResult?.sessions?.find((row) => row.key === params.sessionKey); + params.session ?? + params.sessionsResult?.sessions?.find((row) => + areUiSessionKeysEquivalent(row.key, params.sessionKey), + ); const persisted = session?.thinkingLevel; const currentOverride = typeof persisted === "string" && persisted.trim() diff --git a/ui/src/pages/chat/chat-session.ts b/ui/src/pages/chat/chat-session.ts index c672a78afa94..db953ba0fc0a 100644 --- a/ui/src/pages/chat/chat-session.ts +++ b/ui/src/pages/chat/chat-session.ts @@ -331,7 +331,7 @@ function patchSessionRow( host.sessionsResult = { ...current, sessions: current.sessions.map((row) => - row.key === sessionKey ? Object.assign({}, row, patch) : row, + areUiSessionKeysEquivalent(row.key, sessionKey) ? Object.assign({}, row, patch) : row, ), }; } @@ -344,7 +344,9 @@ export function switchChatFastMode( if (!host.client || !host.connected) { return Promise.resolve(false); } - const activeRow = host.sessionsResult?.sessions?.find((row) => row.key === targetSessionKey); + const activeRow = host.sessionsResult?.sessions?.find((row) => + areUiSessionKeysEquivalent(row.key, targetSessionKey), + ); const previousFastMode = activeRow?.fastMode; const previousEffectiveFastMode = activeRow?.effectiveFastMode; const next: FastMode | undefined = @@ -478,7 +480,9 @@ export function switchChatThinkingLevel( if (!host.client || !host.connected) { return Promise.resolve(false); } - const activeRow = host.sessionsResult?.sessions?.find((row) => row.key === targetSessionKey); + const activeRow = host.sessionsResult?.sessions?.find((row) => + areUiSessionKeysEquivalent(row.key, targetSessionKey), + ); const previousThinkingLevel = activeRow?.thinkingLevel; const normalizedNext = (normalizeThinkLevel(nextThinkingLevel) ?? nextThinkingLevel.trim()) || undefined;