Preserve Claude model and thinking preferences (#2912)

* fix(agents): preserve Claude thinking selections

Model switches now resolve thinking against the target model's saved preference instead of resetting to the catalog default. Claude owns its configured-model defaults and legacy Fable alias handling, while persisted permission modes remain unchanged.

* chore(claude): tag Fable compatibility shim

* fix(app): resolve persisted model aliases

* fix(models): preserve aliases for older clients

* fix(models): hide compatibility aliases from listings

* fix(settings): hide compatibility model aliases

* test(claude): cover legacy catalog entry

* test(app): expect mock thinking controls in schedules

* fix(app): preserve queued thinking preferences

* fix(app): reconcile schedule provider snapshots

* fix(app): preserve schedule model selections

* fix(claude): preserve exact configured model ids

* fix(app): preserve committed thinking preferences

* fix(app): preserve aliased schedule thinking
This commit is contained in:
Mohamed Boudra
2026-08-06 11:33:18 +02:00
committed by GitHub
parent 73ba37a07a
commit f2054cc701
43 changed files with 1369 additions and 220 deletions
@@ -140,7 +140,8 @@ test.describe("Schedules", () => {
await expectSettled(projectTrigger);
await expect(modelTrigger).toContainText("Ten second stream", { timeout: 30_000 });
await expectSettled(modelTrigger);
await expect(thinkingTrigger).toHaveCount(0);
await expect(thinkingTrigger).toContainText("Low");
await expectSettled(thinkingTrigger);
await expect(modeTrigger).toBeVisible({ timeout: 30_000 });
await expectSettled(modeTrigger);
await expect(page.getByTestId("cadence-mode")).toHaveCount(0);
@@ -217,7 +217,8 @@ test.describe("Schedules project target", () => {
const modelTrigger = page.getByTestId("schedule-model-trigger");
await expect(modelTrigger).toContainText("Ten second stream");
await expectSettled(modelTrigger);
await expect(page.getByTestId("schedule-thinking-trigger")).toHaveCount(0);
await expect(page.getByTestId("schedule-thinking-trigger")).toContainText("Low");
await expectSettled(page.getByTestId("schedule-thinking-trigger"));
await expect(page.getByTestId("schedule-mode-trigger")).toBeVisible();
await expectSettled(page.getByTestId("schedule-mode-trigger"));
await expect(page.getByTestId("schedule-isolation-trigger")).toHaveCount(0);
@@ -327,7 +328,8 @@ test.describe("Schedules project target", () => {
await selectModelByLabel(page, "Ten second stream");
await expect(modelTrigger).toContainText("Ten second stream");
await expectSettled(modelTrigger);
await expect(thinkingTrigger).toHaveCount(0);
await expect(thinkingTrigger).toContainText("Low");
await expectSettled(thinkingTrigger);
await expect(modeTrigger).toBeVisible();
await expectSettled(modeTrigger);
@@ -0,0 +1,190 @@
import { expect, test, type Page } from "../support/fixtures";
import { clickNewChat, gotoWorkspace } from "../support/helpers/launcher";
import { seedWorkspace } from "../support/helpers/seed-client";
import {
chooseCommandCenterAgentControl,
submitDraftAgent,
waitForDraftComposer,
} from "../support/helpers/command-center-agent-controls";
import { openCommandCenter } from "../support/helpers/command-center";
import { selectModel } from "../support/helpers/app";
const DISABLE_DEFAULT_SEED_ONCE_KEY = "@paseo:e2e-disable-default-seed-once";
const SEED_NONCE_KEY = "@paseo:e2e-seed-nonce";
async function openNewAgentTab(page: Page): Promise<void> {
await clickNewChat(page);
await waitForDraftComposer(page);
}
async function chooseDraftControl(page: Page, query: string, choice: string): Promise<void> {
await openCommandCenter(page);
await chooseCommandCenterAgentControl({ page, query, choice });
}
async function expectThinkingSelected(page: Page, label: string): Promise<void> {
await expect(
page
.getByRole("button", { name: `Select thinking option (${label})` })
.filter({ visible: true }),
).toBeVisible({ timeout: 30_000 });
}
async function expectModeSelected(page: Page, label: string): Promise<void> {
await expect(
page.getByRole("button", { name: `Select agent mode (${label})` }).filter({ visible: true }),
).toBeVisible({ timeout: 30_000 });
}
async function reloadWithPersistedPreferences(page: Page): Promise<void> {
await page.evaluate(
({ disableKey, nonceKey }) => {
const nonce = localStorage.getItem(nonceKey);
if (!nonce) throw new Error("Expected the Playwright seed nonce before reload");
localStorage.setItem(disableKey, nonce);
},
{ disableKey: DISABLE_DEFAULT_SEED_ONCE_KEY, nonceKey: SEED_NONCE_KEY },
);
await page.reload();
await waitForDraftComposer(page);
}
async function readRememberedThinking(page: Page, modelId: string): Promise<string | null> {
return page.evaluate((selectedModelId) => {
const raw = localStorage.getItem("@paseo:create-agent-preferences");
if (!raw) return null;
const preferences = JSON.parse(raw) as {
providerPreferences?: { mock?: { thinkingByModel?: Record<string, string> } };
};
return preferences.providerPreferences?.mock?.thinkingByModel?.[selectedModelId] ?? null;
}, modelId);
}
async function seedLegacyModelPreference(page: Page): Promise<void> {
await page.evaluate(() => {
const storageKey = "@paseo:create-agent-preferences";
const raw = localStorage.getItem(storageKey);
const preferences = raw ? (JSON.parse(raw) as Record<string, unknown>) : {};
localStorage.setItem(
storageKey,
JSON.stringify({
...preferences,
provider: "mock",
providerPreferences: {
...(preferences.providerPreferences as Record<string, unknown> | undefined),
mock: {
model: "legacy-five-minute-stream",
thinkingByModel: { "legacy-five-minute-stream": "medium" },
},
},
}),
);
});
}
test.describe("Workspace draft thinking preferences", () => {
test.describe.configure({ timeout: 180_000 });
test("restores a model's remembered thinking after another model and a reload", async ({
page,
}) => {
const workspace = await seedWorkspace({ repoPrefix: "draft-thinking-preferences-" });
try {
await gotoWorkspace(page, workspace.workspaceId);
await openNewAgentTab(page);
await chooseDraftControl(
page,
"five minute stream",
"Model › Mock Load Test › Five minute stream",
);
await chooseDraftControl(page, "medium", "Thinking › Medium");
await expectThinkingSelected(page, "Medium");
await chooseDraftControl(
page,
"ten second stream",
"Model › Mock Load Test › Ten second stream",
);
await chooseDraftControl(
page,
"five minute stream",
"Model › Mock Load Test › Five minute stream",
);
await expectThinkingSelected(page, "Medium");
await submitDraftAgent(page, "Remember medium thinking for this model");
await openNewAgentTab(page);
await expectThinkingSelected(page, "Medium");
await chooseDraftControl(
page,
"ten second stream",
"Model › Mock Load Test › Ten second stream",
);
await submitDraftAgent(page, "Hello from the other model");
await openNewAgentTab(page);
await reloadWithPersistedPreferences(page);
await expect.poll(() => readRememberedThinking(page, "five-minute-stream")).toBe("medium");
await chooseDraftControl(
page,
"five minute stream",
"Model › Mock Load Test › Five minute stream",
);
await expectThinkingSelected(page, "Medium");
} finally {
await workspace.cleanup();
}
});
test("restores a provider's permission mode after another provider and a reload", async ({
page,
}) => {
const workspace = await seedWorkspace({ repoPrefix: "draft-mode-preferences-" });
try {
await gotoWorkspace(page, workspace.workspaceId);
await openNewAgentTab(page);
await chooseDraftControl(
page,
"five minute stream",
"Model › Mock Load Test › Five minute stream",
);
await chooseDraftControl(page, "approval test", "Mode › Approval test");
await expectModeSelected(page, "Approval test");
await submitDraftAgent(page, "Remember this permission mode");
await openNewAgentTab(page);
await expectModeSelected(page, "Approval test");
await selectModel(page, "gpt-5.4-mini");
await openNewAgentTab(page);
await reloadWithPersistedPreferences(page);
await selectModel(page, "Five minute stream");
await expectModeSelected(page, "Approval test");
} finally {
await workspace.cleanup();
}
});
test("canonicalizes a retired model preference and restores its thinking option", async ({
page,
}) => {
const workspace = await seedWorkspace({ repoPrefix: "draft-model-alias-preferences-" });
try {
await gotoWorkspace(page, workspace.workspaceId);
await openNewAgentTab(page);
await seedLegacyModelPreference(page);
await reloadWithPersistedPreferences(page);
await expect(
page.getByRole("button", { name: "Select model (Five minute stream)" }),
).toBeVisible({ timeout: 30_000 });
await expectThinkingSelected(page, "Medium");
} finally {
await workspace.cleanup();
}
});
});