Fix persisted agent defaults display (#2182)

Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Signed-off-by: npub1gjuws2a2dc8z2nszprtg7v6u9q7ffeah3hgl5yx45jwn7y7aqs6s5e9xj6 <44b8e82baa6e0e254e0208d68f335c283c94e7b78dd1fa10d5a49d3f13dd0435@sprout-oss.stage.blox.sqprod.co>
Co-authored-by: Pinky <44b8e82baa6e0e254e0208d68f335c283c94e7b78dd1fa10d5a49d3f13dd0435@sprout-oss.stage.blox.sqprod.co>
This commit is contained in:
Wes
2026-07-20 15:45:34 -07:00
committed by GitHub
co-authored by Pinky
parent 13c11f24ea
commit d538111b51
12 changed files with 253 additions and 70 deletions
@@ -200,8 +200,16 @@ export function AgentConfigFields({
() => getGlobalModelFallback(bakedEnv, effectiveProvider, config.env_vars),
[bakedEnv, config.env_vars, effectiveProvider],
);
const modelField = fieldModel.fields.find(
(field) => field.kind === "model" && field.render === "control",
);
// CLI-login harnesses apply this setting through ACP rather than an env var
// and provide their own default when no model override is persisted.
const modelIsOptional = modelField?.targetApplication.kind === "acpNative";
const modelIsValid =
(config.model?.trim().length ?? 0) > 0 || fallbackModel !== null;
modelIsOptional ||
(config.model?.trim().length ?? 0) > 0 ||
fallbackModel !== null;
React.useEffect(() => {
onValidityChange?.(modelIsValid);
}, [modelIsValid, onValidityChange]);
@@ -653,6 +661,7 @@ export function AgentConfigFields({
isCustomModelEditing={isCustomModelEditing}
isRequired={
showRequiredIndicators &&
!modelIsOptional &&
fallbackModel === null &&
!dependentFieldsDisabled
}
@@ -20,6 +20,13 @@ import type { GlobalAgentConfig } from "@/shared/api/types";
import { getBakedBuildEnv, type BakedEnvEntry } from "@/shared/api/tauri";
import { globalAgentConfigQueryKey } from "@/features/agents/useGlobalAgentConfig";
import { useAcpRuntimesQuery } from "@/features/agents/hooks";
import {
formatRuntimeOptionLabel,
getDefaultPersonaRuntime,
resetConfigForHarnessChange,
sortPersonaRuntimes,
} from "@/features/agents/ui/agentConfigOptions";
import { AgentDropdownSelect } from "@/features/agents/ui/agentConfigControls";
import {
AgentConfigFields,
EMPTY_GLOBAL_CONFIG,
@@ -105,17 +112,36 @@ export function AgentDefaultsEditor({
});
}, []);
// Resolve the buzz-agent runtime catalog entry for model discovery.
const runtimesQuery = useAcpRuntimesQuery();
const buzzAgentRuntime = React.useMemo(
() => (runtimesQuery.data ?? []).find((r) => r.id === "buzz-agent"),
const sortedRuntimes = React.useMemo(
() => sortPersonaRuntimes(runtimesQuery.data ?? []),
[runtimesQuery.data],
);
// A missing/stale preference displays the same effective fallback the backend
// would use; it is persisted only after the user edits and saves this form.
// Keep persona ordering here so this shared editor matches agent dialogs.
const selectedRuntime = React.useMemo(
() =>
sortedRuntimes.find(
(runtime) => runtime.id === config.preferred_runtime,
) ??
getDefaultPersonaRuntime(sortedRuntimes) ??
sortedRuntimes[0],
[config.preferred_runtime, sortedRuntimes],
);
const harnessOptions = React.useMemo(
() =>
sortedRuntimes.map((runtime) => ({
label: formatRuntimeOptionLabel(runtime),
value: runtime.id,
})),
[sortedRuntimes],
);
const configSurfaceLoading = isLoading || runtimesQuery.isLoading;
const configSurfaceError =
loadError ||
runtimesQuery.isError ||
(!configSurfaceLoading && buzzAgentRuntime === undefined);
(!configSurfaceLoading && selectedRuntime === undefined);
function handleConfigChange(next: GlobalAgentConfig) {
configRef.current = next;
@@ -125,6 +151,12 @@ export function AgentDefaultsEditor({
setSaveError(null);
}
function handleHarnessChange(runtimeId: string) {
handleConfigChange(resetConfigForHarnessChange(config, runtimeId));
setIsCustomModelEditing(false);
setIsCustomProvider(false);
}
async function handleSave() {
// Snapshot the config being submitted so we can detect edits that arrive
// during the IPC round-trip and avoid clobbering the user's newer input.
@@ -183,18 +215,36 @@ export function AgentDefaultsEditor({
Couldn't load agent defaults. Restart the app to try again.
</div>
) : (
<AgentConfigFields
bakedEnv={bakedEnv}
selectedRuntime={buzzAgentRuntime}
config={config}
isCustomModelEditing={isCustomModelEditing}
isCustomProvider={isCustomProvider}
onConfigChange={handleConfigChange}
onCustomModelEditingChange={setIsCustomModelEditing}
onIsCustomProviderChange={setIsCustomProvider}
onValidityChange={setConfigIsValid}
useCustomSelect
/>
<>
<div className="space-y-1.5">
<label
className="text-sm font-medium text-foreground"
htmlFor="global-agent-default-harness"
>
Default harness
</label>
<AgentDropdownSelect
id="global-agent-default-harness"
onValueChange={handleHarnessChange}
options={harnessOptions}
placeholder="Select a harness"
testId="global-agent-default-harness"
value={selectedRuntime?.id ?? ""}
/>
</div>
<AgentConfigFields
bakedEnv={bakedEnv}
selectedRuntime={selectedRuntime}
config={config}
isCustomModelEditing={isCustomModelEditing}
isCustomProvider={isCustomProvider}
onConfigChange={handleConfigChange}
onCustomModelEditingChange={setIsCustomModelEditing}
onIsCustomProviderChange={setIsCustomProvider}
onValidityChange={setConfigIsValid}
useCustomSelect
/>
</>
)}
{/* Save bar */}
@@ -5,6 +5,7 @@ import {
getDefaultPersonaRuntime,
getPersonaModelOptions,
getPersonaProviderOptions,
resetConfigForHarnessChange,
runtimeSupportsLlmProviderSelection,
} from "./agentConfigOptions.tsx";
import { formatModelDiscoveryErrorStatus } from "./personaModelDiscoveryStatus.ts";
@@ -145,6 +146,49 @@ test("runtimeSupportsLlmProviderSelection is false for codex and claude", () =>
assert.equal(runtimeSupportsLlmProviderSelection("claude"), false);
});
test("resetConfigForHarnessChange clears harness-specific values", () => {
const config = {
env_vars: { BUZZ_AGENT_THINKING_EFFORT: "high", KEEP_ME: "yes" },
model: "claude-opus",
preferred_runtime: "buzz-agent",
provider: "anthropic",
};
assert.deepEqual(resetConfigForHarnessChange(config, "claude"), {
env_vars: { KEEP_ME: "yes" },
model: null,
preferred_runtime: "claude",
provider: null,
});
});
test("resetConfigForHarnessChange preserves compatible provider selection", () => {
const config = {
env_vars: { KEEP_ME: "yes" },
model: "old-model",
preferred_runtime: "claude",
provider: "anthropic",
};
assert.deepEqual(resetConfigForHarnessChange(config, "goose"), {
env_vars: { KEEP_ME: "yes" },
model: null,
preferred_runtime: "goose",
provider: "anthropic",
});
});
test("resetConfigForHarnessChange does not carry relay mesh to Goose", () => {
const config = {
env_vars: {},
model: "auto",
preferred_runtime: "buzz-agent",
provider: "relay-mesh",
};
assert.equal(resetConfigForHarnessChange(config, "goose").provider, null);
});
// ── getPersonaModelOptions — codex/claude do not use global provider ──────────
//
// The discovery call in AgentDefinitionDialog passes
@@ -1,4 +1,8 @@
import type { AcpRuntimeCatalogEntry } from "@/shared/api/types";
import type {
AcpRuntimeCatalogEntry,
GlobalAgentConfig,
} from "@/shared/api/types";
import { BUZZ_AGENT_THINKING_EFFORT } from "./buzzAgentConfig";
import type { RuntimeFileConfigSubset } from "@/shared/api/tauri";
// Dialogs import getDefaultPersonaRuntime via this re-export; lib code imports
// directly from lib/resolvePersonaRuntime.
@@ -173,6 +177,27 @@ export function runtimeSupportsLlmProviderSelection(runtimeId: string) {
return runtimeId === "buzz-agent" || runtimeId === "goose";
}
/** Clears values whose meaning or support changes with the selected harness. */
export function resetConfigForHarnessChange(
config: GlobalAgentConfig,
runtimeId: string,
): GlobalAgentConfig {
const nextEnvVars = { ...config.env_vars };
delete nextEnvVars[BUZZ_AGENT_THINKING_EFFORT];
return {
...config,
env_vars: nextEnvVars,
model: null,
preferred_runtime: runtimeId || null,
provider:
runtimeSupportsLlmProviderSelection(runtimeId) &&
config.provider !== "relay-mesh"
? config.provider
: null,
};
}
function effectiveModelProviderForOptions(
runtimeId: string,
providerId: string | null | undefined,
@@ -5,8 +5,7 @@ import {
AgentConfigFields,
EMPTY_GLOBAL_CONFIG,
} from "@/features/agents/ui/AgentConfigFields";
import { BUZZ_AGENT_THINKING_EFFORT } from "@/features/agents/ui/buzzAgentConfig";
import { runtimeSupportsLlmProviderSelection } from "@/features/agents/ui/agentConfigOptions";
import { resetConfigForHarnessChange } from "@/features/agents/ui/agentConfigOptions";
import { AgentDropdownSelect } from "@/features/agents/ui/agentConfigControls";
import { createSaveCoalescer } from "./saveCoalescer";
import { getBakedBuildEnv, type BakedEnvEntry } from "@/shared/api/tauri";
@@ -26,6 +25,7 @@ import {
type OnboardingTransitionDirection,
OnboardingSlideTransition,
} from "./OnboardingSlideTransition";
import { ONBOARDING_RUNTIME_ORDER } from "./onboardingRuntimeSelection";
import type { DefaultConfigStepActions } from "./types";
type DefaultConfigStepProps = {
@@ -34,8 +34,6 @@ type DefaultConfigStepProps = {
selectedRuntimeIds: readonly string[];
};
const RUNTIME_ORDER = ["claude", "codex", "goose", "buzz-agent"];
function formatHarnessLabel(runtime: AcpRuntimeCatalogEntry | undefined) {
if (!runtime) return "Select a harness";
return runtime.id === "buzz-agent" ? "Buzz" : runtime.label;
@@ -49,11 +47,11 @@ function sortSelectedRuntimes(
return runtimes
.filter((runtime) => selectedRuntimeIdSet.has(runtime.id))
.sort((left, right) => {
const leftIndex = RUNTIME_ORDER.indexOf(left.id);
const rightIndex = RUNTIME_ORDER.indexOf(right.id);
const leftIndex = ONBOARDING_RUNTIME_ORDER.indexOf(left.id);
const rightIndex = ONBOARDING_RUNTIME_ORDER.indexOf(right.id);
return (
(leftIndex === -1 ? RUNTIME_ORDER.length : leftIndex) -
(rightIndex === -1 ? RUNTIME_ORDER.length : rightIndex)
(leftIndex === -1 ? ONBOARDING_RUNTIME_ORDER.length : leftIndex) -
(rightIndex === -1 ? ONBOARDING_RUNTIME_ORDER.length : rightIndex)
);
});
}
@@ -146,20 +144,7 @@ function AgentDefaultsSection({
const handleHarnessChange = React.useCallback(
(runtimeId: string) => {
const nextEnvVars = { ...config.env_vars };
delete nextEnvVars[BUZZ_AGENT_THINKING_EFFORT];
const nextProvider =
runtimeSupportsLlmProviderSelection(runtimeId) &&
config.provider !== "relay-mesh"
? config.provider
: null;
const next = {
...config,
env_vars: nextEnvVars,
model: null,
preferred_runtime: runtimeId || null,
provider: nextProvider,
};
const next = resetConfigForHarnessChange(config, runtimeId);
setIsCustomModelEditing(false);
setIsCustomProvider(false);
setConfig(next);
@@ -10,8 +10,7 @@ import {
importIdentity,
persistCurrentIdentity,
} from "@/shared/api/tauriIdentity";
import { runtimeSupportsLlmProviderSelection } from "@/features/agents/ui/agentConfigOptions";
import { BUZZ_AGENT_THINKING_EFFORT } from "@/features/agents/ui/buzzAgentConfig";
import { resetConfigForHarnessChange } from "@/features/agents/ui/agentConfigOptions";
import { Button } from "@/shared/ui/button";
import { StartupWindowDragRegion } from "@/shared/ui/StartupWindowDragRegion";
import { BackupStep } from "./BackupStep";
@@ -94,20 +93,9 @@ export function MachineOnboardingFlow({
return;
}
const nextEnvVars = { ...current.env_vars };
delete nextEnvVars[BUZZ_AGENT_THINKING_EFFORT];
await setGlobalAgentConfig({
...current,
env_vars: nextEnvVars,
model: null,
preferred_runtime: preferredRuntimeId,
provider:
preferredRuntimeId &&
runtimeSupportsLlmProviderSelection(preferredRuntimeId) &&
current.provider !== "relay-mesh"
? current.provider
: null,
});
await setGlobalAgentConfig(
resetConfigForHarnessChange(current, preferredRuntimeId ?? ""),
);
});
runtimeSaveChain.current = save.then(
() => undefined,
@@ -101,14 +101,29 @@ test("provider-backed selections drive the default model config step", () => {
getDefaultModelConfigRuntimeId(["claude", "codex", "goose", "buzz-agent"]),
"buzz-agent",
);
});
test("onboarding display order drives the preferred runtime", () => {
assert.equal(
getPreferredRuntimeIdForSelection(["claude", "codex", "goose"]),
"goose",
);
assert.equal(
getPreferredRuntimeIdForSelection(["claude", "codex"]),
getPreferredRuntimeIdForSelection([
"buzz-agent",
"goose",
"codex",
"claude",
]),
"claude",
);
assert.equal(
getPreferredRuntimeIdForSelection(["buzz-agent", "goose", "codex"]),
"codex",
);
assert.equal(
getPreferredRuntimeIdForSelection(["buzz-agent", "goose"]),
"goose",
);
assert.equal(getPreferredRuntimeIdForSelection(["buzz-agent"]), "buzz-agent");
assert.equal(getPreferredRuntimeIdForSelection(["custom"]), "custom");
assert.equal(getPreferredRuntimeIdForSelection([]), null);
});
test("any harness selection drives the defaults step", () => {
@@ -1,11 +1,13 @@
import type { AcpRuntimeCatalogEntry } from "@/shared/api/types";
const KNOWN_ONBOARDING_RUNTIME_IDS = new Set([
"buzz-agent",
export const ONBOARDING_RUNTIME_ORDER = [
"claude",
"codex",
"goose",
]);
"buzz-agent",
];
const KNOWN_ONBOARDING_RUNTIME_IDS = new Set<string>(ONBOARDING_RUNTIME_ORDER);
export function runtimeUsesDefaultModelConfig(runtimeId: string) {
return runtimeId === "buzz-agent" || runtimeId === "goose";
@@ -22,7 +24,14 @@ export function getDefaultModelConfigRuntimeId(runtimeIds: readonly string[]) {
export function getPreferredRuntimeIdForSelection(
runtimeIds: readonly string[],
) {
return getDefaultModelConfigRuntimeId(runtimeIds) ?? runtimeIds[0] ?? null;
const selectedRuntimeIds = new Set(runtimeIds);
return (
ONBOARDING_RUNTIME_ORDER.find((runtimeId) =>
selectedRuntimeIds.has(runtimeId),
) ??
runtimeIds[0] ??
null
);
}
export function runtimeSelectionNeedsDefaultModelConfig(
+2
View File
@@ -6696,6 +6696,8 @@ function withMockRuntimeConfigMetadata(
): RawAcpRuntimeCatalogEntry {
return {
...runtime,
node_required: runtime.node_required ?? false,
auth_status: runtime.auth_status ?? { status: "unknown" },
model_env_var:
"model_env_var" in runtime
? runtime.model_env_var
@@ -198,6 +198,61 @@ test.describe("global agent config screenshots", () => {
});
});
test("settings renders and saves the persisted preferred harness", async ({
page,
}) => {
await installMockBridge(page, {
globalAgentConfig: {
preferred_runtime: "claude",
provider: null,
model: null,
env_vars: {},
},
acpRuntimesCatalog: [...CATALOG_WITH_CLAUDE, CATALOG_WITH_CODEX[1]],
});
await openAiDefaultsSettings(page);
const harness = page.getByTestId("global-agent-default-harness");
await expect(harness).toHaveText("Claude Code");
await expect(page.getByText("Provider", { exact: true })).toHaveCount(0);
await expect(page.locator("#global-agent-model")).toBeVisible();
// Make the form dirty, then return to Claude with no model override. The
// harness-native default keeps the now-actionable Save button enabled.
await harness.press("Enter");
await page.getByTestId("global-agent-default-harness-option-codex").click();
await harness.press("Enter");
await page
.getByTestId("global-agent-default-harness-option-claude")
.click();
await expect(page.getByTestId("global-agent-model")).toHaveText(
/Default model/,
);
await expect(
page.getByRole("button", { name: "Save defaults" }),
).toBeEnabled();
await harness.press("Enter");
await page.getByTestId("global-agent-default-harness-option-codex").click();
const model = page.getByTestId("global-agent-model");
await model.click();
await page.getByTestId("global-agent-model-option-gpt-5.5[high]").click();
await page.getByRole("button", { name: "Save defaults" }).click();
const saved = await page.evaluate(async () =>
(
window as typeof window & {
__BUZZ_E2E_INVOKE_MOCK_COMMAND__?: (
command: string,
payload: unknown,
) => Promise<unknown>;
}
).__BUZZ_E2E_INVOKE_MOCK_COMMAND__?.("get_global_agent_config", null),
);
expect(saved).toMatchObject({ preferred_runtime: "codex" });
});
test("02-create-global-provider-shows-top-level-api-key", async ({
page,
}) => {
@@ -119,7 +119,7 @@ test("requires a runtime selection and routes Buzz Agent to config", async ({
expect(await readSavedRuntime(page)).toBe("buzz-agent");
});
test("rapid harness toggles serialize the persisted preferred runtime", async ({
test("rapid harness toggles serialize the prioritized preferred runtime", async ({
page,
}) => {
await installMockBridge(
@@ -143,7 +143,7 @@ test("rapid harness toggles serialize the persisted preferred runtime", async ({
await page.waitForTimeout(300);
await expect(next).toBeDisabled();
await expect(next).toBeEnabled({ timeout: 700 });
expect(await readSavedRuntime(page)).toBe("buzz-agent");
expect(await readSavedRuntime(page)).toBe("goose");
});
test("authenticated Claude saves the selected runtime and routes to defaults", async ({
@@ -557,7 +557,7 @@ test("changing setup-page harness clears an incompatible saved model", async ({
await expect.poll(() => readSavedRuntime(page)).toBe("claude");
});
test("selecting all harnesses routes defaults through Buzz Agent", async ({
test("selecting all harnesses prioritizes Claude for defaults", async ({
page,
}) => {
await installMockBridge(
@@ -592,10 +592,10 @@ test("selecting all harnesses routes defaults through Buzz Agent", async ({
await expect(page.getByTestId("onboarding-setup-next")).toBeEnabled();
await page.getByTestId("onboarding-setup-next").click();
await expect(page.getByTestId("onboarding-page-config")).toBeVisible();
expect(await readSavedRuntime(page)).toBe("buzz-agent");
expect(await readSavedRuntime(page)).toBe("claude");
const harnessSelect = page.getByTestId("global-agent-default-harness");
await expect(harnessSelect).toHaveText("Buzz");
await expect(harnessSelect).toHaveText("Claude");
await harnessSelect.click();
await expect(
page.getByTestId("global-agent-default-harness-option-claude"),
+1
View File
@@ -369,6 +369,7 @@ type MockBridgeOptions = {
env_vars: Record<string, string>;
provider: string | null;
model: string | null;
preferred_runtime?: string | null;
};
bakedBuildEnv?: Array<{
key: string;