mirror of
https://github.com/block/buzz.git
synced 2026-08-18 06:50:31 +02:00
Fix harness default model states in onboarding (#2156)
This commit is contained in:
@@ -297,6 +297,36 @@ export function AgentConfigFields({
|
||||
|
||||
const currentEffortForAutoClear =
|
||||
config.env_vars[BUZZ_AGENT_THINKING_EFFORT] ?? "";
|
||||
|
||||
// When the selected harness changes outside this component (Back → setup
|
||||
// page → choose a different harness → Next), the saved model can belong to
|
||||
// the old harness. In onboarding, heal that stale value as soon as the new
|
||||
// harness catalog proves it is unsupported; otherwise a Codex id like
|
||||
// `gpt-5.5[low]` appears as a Claude Code custom model.
|
||||
React.useEffect(() => {
|
||||
if (!healOnMount) return;
|
||||
const currentModel = (config.model ?? "").trim();
|
||||
if (currentModel.length === 0) return;
|
||||
if (modelDiscoveryLoading || discoveredModelOptions === null) return;
|
||||
if (
|
||||
discoveredModelOptions.some((option) => option.id.trim() === currentModel)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
const nextEnvVars = { ...config.env_vars };
|
||||
delete nextEnvVars[BUZZ_AGENT_THINKING_EFFORT];
|
||||
onCustomModelEditingChange(false);
|
||||
onConfigChange({ ...config, env_vars: nextEnvVars, model: null });
|
||||
}, [
|
||||
config,
|
||||
discoveredModelOptions,
|
||||
modelDiscoveryLoading,
|
||||
onConfigChange,
|
||||
onCustomModelEditingChange,
|
||||
healOnMount,
|
||||
]);
|
||||
|
||||
// Orphan-model clearing follows the mount-time healing policy above: the
|
||||
// backend resolves provider and model independently across layers
|
||||
// (agent → definition → global), so a saved global model WITHOUT a global
|
||||
@@ -452,6 +482,15 @@ export function AgentConfigFields({
|
||||
const { validValues: effortValid, defaultValue: effortDefault } =
|
||||
getProviderEffortConfig(effortProvider, config.model ?? "");
|
||||
const currentEffort = config.env_vars[BUZZ_AGENT_THINKING_EFFORT] ?? "";
|
||||
// Claude Code and Codex both have harness-native effort semantics that are
|
||||
// not represented by Buzz Agent's generic BUZZ_AGENT_THINKING_EFFORT env var:
|
||||
// Claude exposes ACP config option `effort`; Codex currently encodes effort
|
||||
// into model ids (e.g. `gpt-5.5[low]`). Hide the generic control until the
|
||||
// config core can render harness-native options honestly.
|
||||
const effortFieldVisible =
|
||||
showEffortField &&
|
||||
selectedRuntimeId !== "claude" &&
|
||||
selectedRuntimeId !== "codex";
|
||||
|
||||
const fieldClassName = unstyled ? "space-y-4" : "space-y-1.5 p-3";
|
||||
const blockClassName = unstyled ? "" : "p-3";
|
||||
@@ -617,7 +656,7 @@ export function AgentConfigFields({
|
||||
</div>
|
||||
|
||||
{/* Thinking / Effort */}
|
||||
{showEffortField ? (
|
||||
{effortFieldVisible ? (
|
||||
<div className={blockClassName}>
|
||||
<EffortSelectField
|
||||
currentEffort={dependentFieldsDisabled ? "" : currentEffort}
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
import assert from "node:assert/strict";
|
||||
import test from "node:test";
|
||||
|
||||
import { resolveDefaultModelLabel } from "./agentConfigControls.tsx";
|
||||
|
||||
test("uses the harness-discovered default model label for an unset model", () => {
|
||||
assert.equal(
|
||||
resolveDefaultModelLabel({
|
||||
discoveredModelOptions: [
|
||||
{ id: "", label: "Default model (claude-sonnet-5)" },
|
||||
{ id: "claude-opus-4-8", label: "Claude Opus 4.8" },
|
||||
],
|
||||
isSharedCompute: false,
|
||||
}),
|
||||
"Default model (claude-sonnet-5)",
|
||||
);
|
||||
});
|
||||
|
||||
test("falls back to a generic harness default when discovery has no current model", () => {
|
||||
assert.equal(
|
||||
resolveDefaultModelLabel({
|
||||
discoveredModelOptions: [{ id: "", label: "Default model" }],
|
||||
isSharedCompute: false,
|
||||
}),
|
||||
"Default model",
|
||||
);
|
||||
});
|
||||
|
||||
test("an explicit inherited default label wins over harness discovery", () => {
|
||||
assert.equal(
|
||||
resolveDefaultModelLabel({
|
||||
defaultModelLabel: "Default model (team-model)",
|
||||
discoveredModelOptions: [
|
||||
{ id: "", label: "Default model (claude-sonnet-5)" },
|
||||
],
|
||||
isSharedCompute: false,
|
||||
}),
|
||||
"Default model (team-model)",
|
||||
);
|
||||
});
|
||||
@@ -230,6 +230,24 @@ export function RequiredFieldLabel({
|
||||
);
|
||||
}
|
||||
|
||||
export function resolveDefaultModelLabel({
|
||||
defaultModelLabel,
|
||||
discoveredModelOptions,
|
||||
globalModel,
|
||||
isSharedCompute,
|
||||
}: {
|
||||
defaultModelLabel?: string;
|
||||
discoveredModelOptions: readonly PersonaModelOption[] | null;
|
||||
globalModel?: string;
|
||||
isSharedCompute: boolean;
|
||||
}) {
|
||||
return (
|
||||
defaultModelLabel ??
|
||||
discoveredModelOptions?.find((option) => option.id.trim() === "")?.label ??
|
||||
(isSharedCompute ? "Default (auto)" : getDefaultLlmModelLabel(globalModel))
|
||||
);
|
||||
}
|
||||
|
||||
export function AgentModelField({
|
||||
disabled,
|
||||
discoveredModelOptions,
|
||||
@@ -305,16 +323,22 @@ export function AgentModelField({
|
||||
}) {
|
||||
const trimmedModel = model.trim();
|
||||
const isSharedCompute = provider?.trim() === "relay-mesh";
|
||||
const discoveredDefaultOption = discoveredModelOptions?.find(
|
||||
(option) => option.id.trim() === "",
|
||||
);
|
||||
|
||||
// Buzz shared compute always has an automatic routing choice, even when
|
||||
// discovery is empty or returns only explicit live model ids.
|
||||
// Model discovery can report the harness's current/default model while the
|
||||
// persisted value remains empty ("let the harness choose"). Treat that as a
|
||||
// real, displayable option instead of a blank select state. Explicit labels
|
||||
// from a lower-precedence/baked default still win when present.
|
||||
const defaultOption: PersonaModelOption = {
|
||||
id: "",
|
||||
label:
|
||||
defaultModelLabel ??
|
||||
(isSharedCompute
|
||||
? "Default (auto)"
|
||||
: getDefaultLlmModelLabel(globalModel)),
|
||||
label: resolveDefaultModelLabel({
|
||||
defaultModelLabel,
|
||||
discoveredModelOptions,
|
||||
globalModel,
|
||||
isSharedCompute,
|
||||
}),
|
||||
};
|
||||
const discoveredWithoutDefault = (discoveredModelOptions ?? []).filter(
|
||||
(option) => option.id.trim() !== "",
|
||||
@@ -322,7 +346,9 @@ export function AgentModelField({
|
||||
const baseModelOptions = isSharedCompute
|
||||
? [defaultOption, ...discoveredWithoutDefault]
|
||||
: [
|
||||
...(allowDefaultModel ? [defaultOption] : []),
|
||||
...(allowDefaultModel || discoveredDefaultOption
|
||||
? [defaultOption]
|
||||
: []),
|
||||
...discoveredWithoutDefault,
|
||||
];
|
||||
const shouldShowPendingModelOption =
|
||||
@@ -402,6 +428,16 @@ export function AgentModelField({
|
||||
trimmedModel.length > 0
|
||||
? trimmedModel
|
||||
: undefined;
|
||||
// While discovery is in flight with nothing selected, the closed field
|
||||
// reads "Loading models…" instead of a select-prompt — the field isn't
|
||||
// waiting on the user, it's waiting on the harness.
|
||||
const restingPlaceholder =
|
||||
modelDiscoveryLoading &&
|
||||
discoveredModelOptions === null &&
|
||||
trimmedModel.length === 0 &&
|
||||
!isCustomModelEditing
|
||||
? "Loading models..."
|
||||
: placeholder;
|
||||
|
||||
const modelSelect = useCustomSelect ? (
|
||||
<AgentDropdownSelect
|
||||
@@ -411,7 +447,7 @@ export function AgentModelField({
|
||||
id={id}
|
||||
onValueChange={handleModelSelectChange}
|
||||
options={modelOptions}
|
||||
placeholder={placeholder}
|
||||
placeholder={restingPlaceholder}
|
||||
placeholderClassName={placeholderClassName}
|
||||
searchable
|
||||
selectedLabel={stableSelectedModelLabel}
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
import assert from "node:assert/strict";
|
||||
import test from "node:test";
|
||||
|
||||
import { getDiscoveredPersonaModelOptions } from "./usePersonaModelDiscovery.ts";
|
||||
|
||||
function response(overrides = {}) {
|
||||
return {
|
||||
agentName: "mock",
|
||||
agentVersion: "0.0.0",
|
||||
models: [],
|
||||
agentDefaultModel: null,
|
||||
selectedModel: null,
|
||||
supportsSwitching: true,
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
test("merges the harness's own 'default' catalog entry into the canonical default row", () => {
|
||||
const options = getDiscoveredPersonaModelOptions(
|
||||
response({
|
||||
models: [
|
||||
{ id: "default", name: null, description: null },
|
||||
{ id: "claude-opus-4-8", name: null, description: null },
|
||||
{ id: "claude-sonnet-5", name: null, description: null },
|
||||
],
|
||||
}),
|
||||
"",
|
||||
);
|
||||
|
||||
// Exactly one default row (id ""), and no raw "default" entry remains.
|
||||
assert.deepEqual(
|
||||
options.map((option) => option.id),
|
||||
["", "claude-opus-4-8", "claude-sonnet-5"],
|
||||
);
|
||||
assert.equal(options[0].label, "Default model");
|
||||
});
|
||||
|
||||
test("default row shows the harness-reported current model when available", () => {
|
||||
const options = getDiscoveredPersonaModelOptions(
|
||||
response({
|
||||
agentDefaultModel: "gpt-5.5[high]",
|
||||
models: [
|
||||
{ id: "gpt-5.5", name: "GPT-5.5", description: null },
|
||||
{ id: "gpt-5.4", name: "GPT-5.4", description: null },
|
||||
],
|
||||
}),
|
||||
"",
|
||||
);
|
||||
|
||||
assert.equal(options[0].id, "");
|
||||
assert.equal(options[0].label, "Default model (gpt-5.5[high])");
|
||||
assert.deepEqual(
|
||||
options.slice(1).map((option) => option.id),
|
||||
["gpt-5.5", "gpt-5.4"],
|
||||
);
|
||||
});
|
||||
|
||||
test("the 'default' id match is case-insensitive and trimmed", () => {
|
||||
const options = getDiscoveredPersonaModelOptions(
|
||||
response({
|
||||
models: [
|
||||
{ id: " Default ", name: null, description: null },
|
||||
{ id: "claude-sonnet-5", name: null, description: null },
|
||||
],
|
||||
}),
|
||||
"",
|
||||
);
|
||||
|
||||
assert.deepEqual(
|
||||
options.map((option) => option.id),
|
||||
["", "claude-sonnet-5"],
|
||||
);
|
||||
});
|
||||
|
||||
test("explicit-model providers get no default row (no harness default entry)", () => {
|
||||
const options = getDiscoveredPersonaModelOptions(
|
||||
response({
|
||||
models: [
|
||||
{ id: "goose-claude-4-6-sonnet", name: null, description: null },
|
||||
],
|
||||
}),
|
||||
"anthropic",
|
||||
);
|
||||
|
||||
assert.deepEqual(
|
||||
options.map((option) => option.id),
|
||||
["goose-claude-4-6-sonnet"],
|
||||
);
|
||||
});
|
||||
|
||||
test("relay-mesh keeps its automatic routing default row", () => {
|
||||
const options = getDiscoveredPersonaModelOptions(
|
||||
response({
|
||||
models: [{ id: "llama-3", name: "Llama 3", description: null }],
|
||||
}),
|
||||
"relay-mesh",
|
||||
);
|
||||
|
||||
assert.equal(options[0].id, "");
|
||||
assert.equal(options[0].label, "Default (auto)");
|
||||
});
|
||||
|
||||
test("returns null when discovery is unsupported or empty", () => {
|
||||
assert.equal(
|
||||
getDiscoveredPersonaModelOptions(
|
||||
response({ supportsSwitching: false }),
|
||||
"",
|
||||
),
|
||||
null,
|
||||
);
|
||||
assert.equal(getDiscoveredPersonaModelOptions(null, ""), null);
|
||||
});
|
||||
@@ -25,7 +25,18 @@ function stableModelDiscoveryEnvKey(envVars: EnvVarsValue): string {
|
||||
);
|
||||
}
|
||||
|
||||
function getDiscoveredPersonaModelOptions(
|
||||
/**
|
||||
* True when a harness catalog entry is the harness's own "use my default"
|
||||
* row (e.g. Claude Code ships a literal `default` model id). Such entries
|
||||
* mean the same thing as leaving the model unset, so the UI merges them
|
||||
* into the single canonical default row instead of showing two rows for
|
||||
* one idea.
|
||||
*/
|
||||
function isHarnessDefaultModelEntry(model: { id: string }) {
|
||||
return model.id.trim().toLowerCase() === "default";
|
||||
}
|
||||
|
||||
export function getDiscoveredPersonaModelOptions(
|
||||
response: AgentModelsResponse | null,
|
||||
provider: string,
|
||||
): readonly PersonaModelOption[] | null {
|
||||
@@ -33,23 +44,38 @@ function getDiscoveredPersonaModelOptions(
|
||||
return null;
|
||||
}
|
||||
|
||||
const defaultModelOption = providerRequiresExplicitModel(provider)
|
||||
? []
|
||||
: [
|
||||
{
|
||||
id: "",
|
||||
label:
|
||||
provider === "relay-mesh"
|
||||
? "Default (auto)"
|
||||
: response.agentDefaultModel?.trim()
|
||||
? `Default model (${response.agentDefaultModel})`
|
||||
: "Default model",
|
||||
},
|
||||
];
|
||||
// One row per idea: the harness's own default catalog entry (if any) is
|
||||
// absorbed into the canonical default row. Selecting it keeps the stored
|
||||
// model unset — behaviorally identical, and it avoids two saved states
|
||||
// ("default" vs unset) that mean the same thing.
|
||||
const explicitModels = response.models.filter(
|
||||
(model) => !isHarnessDefaultModelEntry(model),
|
||||
);
|
||||
const harnessDefaultEntry = response.models.find(isHarnessDefaultModelEntry);
|
||||
const agentDefaultModel = response.agentDefaultModel?.trim();
|
||||
|
||||
const defaultModelOption =
|
||||
providerRequiresExplicitModel(provider) && harnessDefaultEntry === undefined
|
||||
? []
|
||||
: [
|
||||
{
|
||||
id: "",
|
||||
label:
|
||||
provider === "relay-mesh"
|
||||
? "Default (auto)"
|
||||
: agentDefaultModel
|
||||
? `Default model (${agentDefaultModel})`
|
||||
: "Default model",
|
||||
},
|
||||
];
|
||||
|
||||
if (explicitModels.length === 0 && defaultModelOption.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return [
|
||||
...defaultModelOption,
|
||||
...response.models.map((model) => ({
|
||||
...explicitModels.map((model) => ({
|
||||
id: model.id,
|
||||
label: model.name?.trim() || model.id,
|
||||
})),
|
||||
|
||||
@@ -140,26 +140,45 @@ function AgentDefaultsSection({
|
||||
[selectedRuntimes],
|
||||
);
|
||||
|
||||
function handleHarnessChange(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,
|
||||
};
|
||||
setIsCustomModelEditing(false);
|
||||
setIsCustomProvider(false);
|
||||
setConfig(next);
|
||||
coalescerRef.current?.enqueue(next);
|
||||
}
|
||||
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,
|
||||
};
|
||||
setIsCustomModelEditing(false);
|
||||
setIsCustomProvider(false);
|
||||
setConfig(next);
|
||||
coalescerRef.current?.enqueue(next);
|
||||
},
|
||||
[config],
|
||||
);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (isLoading || !selectedRuntimeId) return;
|
||||
if (config.preferred_runtime === selectedRuntimeId) return;
|
||||
|
||||
// The user can go Back, change which harnesses are selected, then return to
|
||||
// this page without using this page's own harness dropdown. Reconcile that
|
||||
// effective harness change through the same reset path so a Codex model
|
||||
// never survives into Claude Code as a custom model (or vice versa).
|
||||
handleHarnessChange(selectedRuntimeId);
|
||||
}, [
|
||||
config.preferred_runtime,
|
||||
handleHarnessChange,
|
||||
isLoading,
|
||||
selectedRuntimeId,
|
||||
]);
|
||||
|
||||
return (
|
||||
<section className="w-full space-y-4 text-left text-sm">
|
||||
|
||||
@@ -10,6 +10,8 @@ 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 { Button } from "@/shared/ui/button";
|
||||
import { StartupWindowDragRegion } from "@/shared/ui/StartupWindowDragRegion";
|
||||
import { BackupStep } from "./BackupStep";
|
||||
@@ -82,9 +84,29 @@ export function MachineOnboardingFlow({
|
||||
|
||||
const save = runtimeSaveChain.current.then(async () => {
|
||||
const current = await getGlobalAgentConfig();
|
||||
const selectedHarnessChanged =
|
||||
current.preferred_runtime !== preferredRuntimeId;
|
||||
if (!selectedHarnessChanged) {
|
||||
await setGlobalAgentConfig({
|
||||
...current,
|
||||
preferred_runtime: preferredRuntimeId,
|
||||
});
|
||||
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,
|
||||
});
|
||||
});
|
||||
runtimeSaveChain.current = save.then(
|
||||
|
||||
@@ -9775,8 +9775,14 @@ export function maybeInstallE2eTauriMocks() {
|
||||
},
|
||||
];
|
||||
const codexRuntimeModels = [
|
||||
{ id: "codex-mini", name: "Codex mini", description: null },
|
||||
{ id: "codex-pro", name: "Codex pro", description: null },
|
||||
{ id: "gpt-5.5", name: "GPT-5.5", description: null },
|
||||
{ id: "gpt-5.5[low]", name: "GPT-5.5 (low)", description: null },
|
||||
{
|
||||
id: "gpt-5.5[medium]",
|
||||
name: "GPT-5.5 (medium)",
|
||||
description: null,
|
||||
},
|
||||
{ id: "gpt-5.5[high]", name: "GPT-5.5 (high)", description: null },
|
||||
];
|
||||
if (provider === "relay-mesh") {
|
||||
if (!mockMeshState.admitted) {
|
||||
@@ -9808,7 +9814,9 @@ export function maybeInstallE2eTauriMocks() {
|
||||
agentName: "mock-agent",
|
||||
agentVersion: "0.0.0",
|
||||
models,
|
||||
agentDefaultModel: null,
|
||||
agentDefaultModel: agentCommand.includes("codex")
|
||||
? "gpt-5.5[high]"
|
||||
: null,
|
||||
selectedModel: null,
|
||||
supportsSwitching: true,
|
||||
};
|
||||
|
||||
@@ -68,17 +68,24 @@ async function chooseConfigDropdownOption(
|
||||
await page.getByTestId(`${triggerTestId}-option-${value || "empty"}`).click();
|
||||
}
|
||||
|
||||
async function readSavedRuntime(page: Parameters<typeof installMockBridge>[0]) {
|
||||
const savedConfig = await page.evaluate(() =>
|
||||
async function readSavedConfig(page: Parameters<typeof installMockBridge>[0]) {
|
||||
return await page.evaluate(() =>
|
||||
(
|
||||
window as Window & {
|
||||
__BUZZ_E2E_INVOKE_MOCK_COMMAND__?: (
|
||||
command: string,
|
||||
payload: unknown,
|
||||
) => Promise<{ preferred_runtime?: string | null }>;
|
||||
) => Promise<{
|
||||
model?: string | null;
|
||||
preferred_runtime?: string | null;
|
||||
}>;
|
||||
}
|
||||
).__BUZZ_E2E_INVOKE_MOCK_COMMAND__?.("get_global_agent_config", null),
|
||||
);
|
||||
}
|
||||
|
||||
async function readSavedRuntime(page: Parameters<typeof installMockBridge>[0]) {
|
||||
const savedConfig = await readSavedConfig(page);
|
||||
return savedConfig?.preferred_runtime ?? null;
|
||||
}
|
||||
|
||||
@@ -159,6 +166,12 @@ test("authenticated Claude saves the selected runtime and routes to defaults", a
|
||||
await expect(page.getByTestId("global-agent-default-harness")).toHaveText(
|
||||
"Claude",
|
||||
);
|
||||
// Claude Code effort is real, but it is exposed as a Claude ACP-native
|
||||
// config option, not Buzz Agent's generic effort env var. Hide the generic
|
||||
// control until the config core can render that native option.
|
||||
await expect(
|
||||
page.getByTestId("global-agent-thinking-effort-select"),
|
||||
).toHaveCount(0);
|
||||
expect(await readSavedRuntime(page)).toBe("claude");
|
||||
});
|
||||
|
||||
@@ -484,17 +497,66 @@ test("multiple CLI harnesses route to default harness selection", async ({
|
||||
await expect.poll(() => readSavedRuntime(page)).toBe("codex");
|
||||
await expect(page.getByTestId("global-agent-provider")).toHaveCount(0);
|
||||
const modelSelect = page.getByTestId("global-agent-model");
|
||||
const effortSelect = page.getByTestId("global-agent-thinking-effort-select");
|
||||
await expect(modelSelect).toBeVisible();
|
||||
await expect(effortSelect).toBeVisible();
|
||||
await expect(modelSelect).toHaveText("Select a model");
|
||||
// Codex model ids can encode effort (for example, `gpt-5.5[low]`). Until the
|
||||
// config core models Codex-native options directly, don't show Buzz Agent's
|
||||
// generic effort field beside Codex's model catalog.
|
||||
await expect(
|
||||
page.getByTestId("global-agent-thinking-effort-select"),
|
||||
).toHaveCount(0);
|
||||
await expect(modelSelect).toHaveText("Default model (gpt-5.5[high])");
|
||||
await modelSelect.click();
|
||||
await expect(
|
||||
page.getByTestId("global-agent-model-option-codex-mini"),
|
||||
page.getByTestId("global-agent-model-option-gpt-5.5[low]"),
|
||||
).toBeVisible();
|
||||
await page.keyboard.press("Escape");
|
||||
});
|
||||
|
||||
test("changing setup-page harness clears an incompatible saved model", async ({
|
||||
page,
|
||||
}) => {
|
||||
await installMockBridge(
|
||||
page,
|
||||
{
|
||||
acpRuntimesCatalog: [
|
||||
availableRuntime("claude", { status: "logged_in" }),
|
||||
availableRuntime("codex", { status: "logged_in" }),
|
||||
],
|
||||
},
|
||||
{ skipCommunitySeed: true, skipOnboardingSeed: true },
|
||||
);
|
||||
await page.goto("/");
|
||||
await navigateToSetupPage(page);
|
||||
|
||||
await page.getByTestId("onboarding-runtime-codex").click();
|
||||
await page.getByTestId("onboarding-setup-next").click();
|
||||
await expect(page.getByTestId("onboarding-page-config")).toBeVisible();
|
||||
|
||||
await chooseConfigDropdownOption(page, "global-agent-model", "gpt-5.5[low]");
|
||||
await expect(page.getByTestId("global-agent-model")).toHaveText(
|
||||
"gpt-5.5[low]",
|
||||
);
|
||||
|
||||
await page.getByTestId("onboarding-back").click();
|
||||
await expect(page.getByTestId("onboarding-page-2")).toBeVisible();
|
||||
await page.getByTestId("onboarding-runtime-codex").click();
|
||||
await page.getByTestId("onboarding-runtime-claude").click();
|
||||
await page.getByTestId("onboarding-setup-next").click();
|
||||
await expect(page.getByTestId("onboarding-page-config")).toBeVisible();
|
||||
|
||||
await expect(page.getByTestId("global-agent-default-harness")).toHaveText(
|
||||
"Claude",
|
||||
);
|
||||
await expect(page.getByTestId("global-agent-model")).not.toHaveText(
|
||||
"Custom model...",
|
||||
);
|
||||
await expect(page.getByLabel("Custom model ID")).toHaveCount(0);
|
||||
await expect
|
||||
.poll(async () => (await readSavedConfig(page))?.model)
|
||||
.toBeNull();
|
||||
await expect.poll(() => readSavedRuntime(page)).toBe("claude");
|
||||
});
|
||||
|
||||
test("selecting all harnesses routes defaults through Buzz Agent", async ({
|
||||
page,
|
||||
}) => {
|
||||
@@ -557,7 +619,7 @@ test("selecting all harnesses routes defaults through Buzz Agent", async ({
|
||||
await expect(page.getByTestId("global-agent-model")).toBeVisible();
|
||||
await expect(
|
||||
page.getByTestId("global-agent-thinking-effort-select"),
|
||||
).toBeVisible();
|
||||
).toHaveCount(0);
|
||||
|
||||
await chooseConfigDropdownOption(
|
||||
page,
|
||||
|
||||
Reference in New Issue
Block a user