fix(ui): use session-key equivalence for active-row lookups in model/thinking/fast-mode paths (#124020)

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.
This commit is contained in:
Peter Steinberger
2026-08-14 22:39:54 -07:00
committed by GitHub
parent ab8d7dbb21
commit f24bd8a2a2
4 changed files with 39 additions and 7 deletions
@@ -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),
+7 -2
View File
@@ -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;
+6 -2
View File
@@ -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()
+7 -3
View File
@@ -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;