mirror of
https://github.com/getpaseo/paseo.git
synced 2026-11-08 01:14:52 +00:00
fix(app): preserve pending form edits without repeated updates
Ignore equivalent pending inputs so freshly built fork defaults do not keep the view rendering. Apply fork defaults only to their provider and model, preserving mode and thinking choices made while discovery is pending.
This commit is contained in:
@@ -1371,3 +1371,109 @@ it("resolves modeless providers once their empty catalog is ready", () => {
|
||||
expect(ready.form).toMatchObject({ provider: "pi", model: "" });
|
||||
expect(ready.resolution.status).toBe("completed");
|
||||
});
|
||||
|
||||
it("ignores equivalent pending inputs with freshly built fork defaults", () => {
|
||||
const inputs = {
|
||||
type: "INPUTS_CHANGED" as const,
|
||||
serverId: "host",
|
||||
isVisible: true,
|
||||
isCreateFlow: true,
|
||||
isPreferencesLoading: false,
|
||||
hasSnapshot: true,
|
||||
initialValues: { provider: "codex", model: "gpt-5.3-codex", modeId: "full-access" },
|
||||
preferences: null,
|
||||
allowedProviderMap: codexProviderMap,
|
||||
providerModelsByProvider: makeProviderModelsByProvider([["codex", null]]),
|
||||
};
|
||||
const pending = resolveAgentForm(makeState(), inputs);
|
||||
expect(resolveAgentForm(pending, { ...inputs, initialValues: { ...inputs.initialValues } })).toBe(
|
||||
pending,
|
||||
);
|
||||
});
|
||||
|
||||
it("keeps the new provider's mode and thinking when switching away from pending fork defaults", () => {
|
||||
const claudeModels: AgentModelDefinition[] = [
|
||||
{
|
||||
provider: "claude",
|
||||
id: "sonnet",
|
||||
label: "Sonnet",
|
||||
defaultThinkingOptionId: "low",
|
||||
thinkingOptions: [
|
||||
{ id: "low", label: "Low" },
|
||||
{ id: "high", label: "High" },
|
||||
],
|
||||
},
|
||||
];
|
||||
const claudePrefs = { mode: "bypassPermissions", thinkingByModel: { sonnet: "high" } };
|
||||
const inputs = {
|
||||
type: "INPUTS_CHANGED" as const,
|
||||
serverId: "host",
|
||||
isVisible: true,
|
||||
isCreateFlow: true,
|
||||
isPreferencesLoading: false,
|
||||
hasSnapshot: true,
|
||||
initialValues: {
|
||||
provider: "codex",
|
||||
model: "gpt-5.3-codex",
|
||||
modeId: "full-access",
|
||||
thinkingOptionId: "low",
|
||||
},
|
||||
preferences: { providerPreferences: { claude: claudePrefs } },
|
||||
allowedProviderMap: bothProviderMap,
|
||||
providerModelsByProvider: makeProviderModelsByProvider([
|
||||
["codex", null],
|
||||
["claude", claudeModels],
|
||||
]),
|
||||
};
|
||||
const pending = resolveAgentForm(makeState(), inputs);
|
||||
const switched = resolveAgentForm(pending, {
|
||||
type: "SET_PROVIDER_AND_MODEL_FROM_USER",
|
||||
provider: "claude",
|
||||
modelId: "sonnet",
|
||||
providerDef: TEST_CLAUDE_DEFINITION,
|
||||
providerModels: claudeModels,
|
||||
providerPrefs: claudePrefs,
|
||||
});
|
||||
expect(switched.form).toMatchObject({
|
||||
provider: "claude",
|
||||
model: "sonnet",
|
||||
modeId: "bypassPermissions",
|
||||
thinkingOptionId: "high",
|
||||
});
|
||||
expect(resolveAgentForm(switched, inputs).form).toEqual(switched.form);
|
||||
});
|
||||
|
||||
it("keeps a new model's thinking preference when pending fork defaults finish resolving", () => {
|
||||
const models: AgentModelDefinition[] = [
|
||||
...CODEX_MODELS,
|
||||
{ ...CODEX_MODELS[0], id: "next-model", defaultThinkingOptionId: "low" },
|
||||
];
|
||||
const preferences = {
|
||||
providerPreferences: { codex: { thinkingByModel: { "next-model": "xhigh" } } },
|
||||
};
|
||||
const inputs = {
|
||||
type: "INPUTS_CHANGED" as const,
|
||||
serverId: "host",
|
||||
isVisible: true,
|
||||
isCreateFlow: true,
|
||||
isPreferencesLoading: false,
|
||||
hasSnapshot: true,
|
||||
initialValues: { provider: "codex", model: "gpt-5.3-codex", thinkingOptionId: "low" },
|
||||
preferences,
|
||||
allowedProviderMap: codexProviderMap,
|
||||
providerModelsByProvider: makeProviderModelsByProvider([["codex", null]]),
|
||||
};
|
||||
const pending = resolveAgentForm(makeState(), inputs);
|
||||
const edited = resolveAgentForm(pending, {
|
||||
type: "SET_MODEL_FROM_USER",
|
||||
modelId: "next-model",
|
||||
availableModels: models,
|
||||
providerPrefs: preferences.providerPreferences.codex,
|
||||
});
|
||||
expect(edited.form.thinkingOptionId).toBe("xhigh");
|
||||
const ready = resolveAgentForm(edited, {
|
||||
...inputs,
|
||||
providerModelsByProvider: makeProviderModelsByProvider([["codex", models]]),
|
||||
});
|
||||
expect(ready.form).toEqual(edited.form);
|
||||
});
|
||||
|
||||
@@ -345,8 +345,11 @@ function resolveThinkingOption(input: {
|
||||
} = input;
|
||||
if (!provider) return "";
|
||||
if (userModified) return currentThinkingOptionId;
|
||||
const initialModel = normalizeSelectedModelId(initialValues?.model);
|
||||
const matchesInitialModel =
|
||||
!initialModel || resolveCanonicalModelId(availableModels, initialModel) === modelId;
|
||||
const initialThinkingOptionId =
|
||||
typeof initialValues?.thinkingOptionId === "string"
|
||||
matchesInitialModel && typeof initialValues?.thinkingOptionId === "string"
|
||||
? initialValues.thinkingOptionId.trim()
|
||||
: "";
|
||||
const preferredThinking = resolvePreferredThinkingOptionId({
|
||||
@@ -376,6 +379,10 @@ export function resolveFormState(
|
||||
preferences,
|
||||
});
|
||||
|
||||
const providerInitialValues =
|
||||
initialValues?.provider && initialValues.provider !== result.provider
|
||||
? undefined
|
||||
: initialValues;
|
||||
const providerDef = result.provider ? allowedProviderMap.get(result.provider) : undefined;
|
||||
const providerPrefs = result.provider
|
||||
? preferences?.providerPreferences?.[result.provider]
|
||||
@@ -385,7 +392,7 @@ export function resolveFormState(
|
||||
provider: result.provider,
|
||||
userModified: userModified.modeId,
|
||||
currentModeId: result.modeId,
|
||||
initialValues,
|
||||
initialValues: providerInitialValues,
|
||||
providerDef,
|
||||
providerPrefs,
|
||||
});
|
||||
@@ -394,7 +401,7 @@ export function resolveFormState(
|
||||
provider: result.provider,
|
||||
userModified: userModified.model,
|
||||
currentModel: result.model,
|
||||
initialValues,
|
||||
initialValues: providerInitialValues,
|
||||
providerPrefs,
|
||||
availableModels,
|
||||
});
|
||||
@@ -404,7 +411,7 @@ export function resolveFormState(
|
||||
userModified: userModified.thinkingOptionId,
|
||||
currentThinkingOptionId: result.thinkingOptionId,
|
||||
modelId: result.model,
|
||||
initialValues,
|
||||
initialValues: providerInitialValues,
|
||||
providerPrefs,
|
||||
availableModels,
|
||||
});
|
||||
@@ -535,14 +542,12 @@ function completeResolution(
|
||||
);
|
||||
const waitingForModels =
|
||||
resolved.provider !== null && action.providerModelsByProvider.get(resolved.provider) === null;
|
||||
const nextState = {
|
||||
...state,
|
||||
resolution: waitingForModels
|
||||
? PENDING_AGENT_FORM_RESOLUTION
|
||||
: ({ status: "completed" } as const),
|
||||
};
|
||||
if (!hasFormStateChanged(state.form, resolved)) return nextState;
|
||||
return { ...nextState, form: resolved };
|
||||
const resolution = waitingForModels
|
||||
? PENDING_AGENT_FORM_RESOLUTION
|
||||
: ({ status: "completed" } as const);
|
||||
const form = hasFormStateChanged(state.form, resolved) ? resolved : state.form;
|
||||
if (form === state.form && resolution.status === state.resolution.status) return state;
|
||||
return { ...state, resolution, form };
|
||||
}
|
||||
|
||||
function applyProfile(state: AgentFormReducerState, action: ApplyProfileAction) {
|
||||
|
||||
Reference in New Issue
Block a user