From de4bf3b4cc775fb90cc397b3a2f1037494cf8041 Mon Sep 17 00:00:00 2001 From: npub1ng3jzsaqxdhrfq22dg85j3lpr0zsh3jp7g2h9jyxl59wraayapnsu6kvfg <9a232143a0336e34814a6a0f4947e11bc50bc641f21572c886fd0ae1f7a4e867@buzz.block.builderlab.xyz> Date: Sun, 2 Aug 2026 16:41:29 -0400 Subject: [PATCH 1/5] feat(desktop): disambiguate provider API key labels and annotate mint key MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Credential fields across Agent Defaults, agent edit, and agent create dialogs previously used a hardcoded ternary that rendered any non-Anthropic provider as 'OpenAI API Key'. OpenRouter's field was mislabeled; users configuring an OpenRouter agent saw 'OpenAI API Key', which is misleading and unrelated to the OPENAI_COMPAT_API_KEY it actually writes. The same root cause made OPENAI_COMPAT_API_KEY and OPENAI_API_KEY visually indistinguishable in the UI, contributing to the confusion reported in the trading cards flow. Add apiKeyLabel to PROVIDER_CREDENTIAL_CONFIG (single source of truth alongside secretEnvVar and requiredEnvKeys) and expose getProviderApiKeyLabel(providerId) next to getProviderApiKeyEnvVar. All three ternaries in AgentConfigFields, AgentInstanceEditDialog, and AgentDefinitionDialog are replaced with the helper. OpenRouter now reads 'OpenRouter API Key'; Anthropic reads 'Anthropic API Key'; OpenAI/compat read 'OpenAI API Key'. Add an optional envVarName prop to PersonaProviderApiKeyField that renders the backing env var name as a muted monospace hint beneath the label. All three call sites pass the secretEnvVar they already hold. A user who saw OPENAI_API_KEY in the mint dialog can now tell at a glance that this field is OPENAI_COMPAT_API_KEY, not the card-minting key. Add an optional keyAnnotations prop to EnvVarsEditor. When a required or user-managed row's key appears in the map, a muted one-line note renders beneath it. AgentConfigFields passes { OPENAI_API_KEY: 'Used for minting agent trading cards' } — the reciprocal signpost to the mint dialog's pointer at Agent Defaults. All ratcheted files stay within the 1000-line limit. 14 new tests added: 8 for getProviderApiKeyLabel, 4 for keyAnnotations lookup invariants, 2 for PersonaProviderApiKeyField export shape. Co-authored-by: Will Pfleger Signed-off-by: Will Pfleger --- .../features/agents/ui/AgentConfigFields.tsx | 15 ++++--- .../agents/ui/AgentDefinitionDialog.tsx | 8 ++-- .../agents/ui/AgentInstanceEditDialog.tsx | 12 ++--- .../features/agents/ui/EnvVarsEditor.test.mjs | 37 ++++++++++++++++ .../src/features/agents/ui/EnvVarsEditor.tsx | 25 +++++++++++ .../ui/PersonaProviderApiKeyField.test.mjs | 32 ++++++++++++++ .../agents/ui/PersonaProviderApiKeyField.tsx | 10 +++++ .../agents/ui/agentConfigOptions.test.mjs | 44 +++++++++++++++++++ .../features/agents/ui/agentConfigOptions.tsx | 28 +++++++++++- 9 files changed, 192 insertions(+), 19 deletions(-) create mode 100644 desktop/src/features/agents/ui/PersonaProviderApiKeyField.test.mjs diff --git a/desktop/src/features/agents/ui/AgentConfigFields.tsx b/desktop/src/features/agents/ui/AgentConfigFields.tsx index 1bd8af8976..370277cc43 100644 --- a/desktop/src/features/agents/ui/AgentConfigFields.tsx +++ b/desktop/src/features/agents/ui/AgentConfigFields.tsx @@ -35,6 +35,7 @@ import { CUSTOM_PROVIDER_DROPDOWN_VALUE, getPersonaProviderOptions, getProviderApiKeyEnvVar, + getProviderApiKeyLabel, runtimeSupportsLlmProviderSelection, } from "@/features/agents/ui/agentConfigOptions"; import { @@ -74,7 +75,10 @@ const PROGRESSIVE_FIELDS_TRANSITION = { duration: 0.22, ease: [0.23, 1, 0.32, 1], } as const; - +/** Muted contextual hints beneath matching rows in the global env editor. */ +const GLOBAL_ENV_KEY_ANNOTATIONS: Readonly> = { + OPENAI_API_KEY: "Used for minting agent trading cards", +}; type AgentConfigDisclosure = | "full" | "onboarding-essential" @@ -747,6 +751,7 @@ export function AgentConfigFields({
onConfigChange({ ...config, @@ -912,6 +913,7 @@ export function AgentConfigFields({ hiddenKeys={apiKeyEnvVar ? [apiKeyEnvVar] : []} inheritedRows={bakedGenericRows} inheritedRowsLabel="build" + keyAnnotations={GLOBAL_ENV_KEY_ANNOTATIONS} label="Environment variables" onChange={handleEnvVarsChange} requiredKeys={advancedRequiredEnvKeys} @@ -930,6 +932,7 @@ export function AgentConfigFields({ hiddenKeys={apiKeyEnvVar ? [apiKeyEnvVar] : []} inheritedRows={bakedGenericRows} inheritedRowsLabel="build" + keyAnnotations={GLOBAL_ENV_KEY_ANNOTATIONS} label="Environment variables" onChange={handleEnvVarsChange} requiredKeys={advancedRequiredEnvKeys} diff --git a/desktop/src/features/agents/ui/AgentDefinitionDialog.tsx b/desktop/src/features/agents/ui/AgentDefinitionDialog.tsx index 5425131448..19f35165ca 100644 --- a/desktop/src/features/agents/ui/AgentDefinitionDialog.tsx +++ b/desktop/src/features/agents/ui/AgentDefinitionDialog.tsx @@ -42,6 +42,7 @@ import { getDefaultPersonaRuntime, getPersonaModelOptions, getPersonaProviderOptions, + getProviderApiKeyLabel, getRuntimePersonaModelOptions, NO_RUNTIME_DROPDOWN_VALUE, runtimeSupportsLlmProviderSelection, @@ -907,14 +908,11 @@ export function AgentDefinitionDialog({ topLevelSecretEnvVar ? ( { setEnvVars((prev) => ({ ...prev, diff --git a/desktop/src/features/agents/ui/AgentInstanceEditDialog.tsx b/desktop/src/features/agents/ui/AgentInstanceEditDialog.tsx index 79d1e9a790..48cde58aa7 100644 --- a/desktop/src/features/agents/ui/AgentInstanceEditDialog.tsx +++ b/desktop/src/features/agents/ui/AgentInstanceEditDialog.tsx @@ -75,7 +75,10 @@ import { getBakedModelInheritLabel, getBakedProviderInheritLabel, } from "./bakedEnvHelpers"; -import { getProviderApiKeyEnvVar } from "./agentConfigOptions"; +import { + getProviderApiKeyEnvVar, + getProviderApiKeyLabel, +} from "./agentConfigOptions"; import { useAgentDialogDefaults } from "./useAgentDialogDefaults"; import { AgentAiDefaultsNotice } from "./AgentAiDefaults"; import { AgentDefaultsDialog } from "./AgentDefaultsDialog"; @@ -1061,14 +1064,11 @@ export function AgentInstanceEditDialog({ {llmProviderFieldVisible && topLevelSecretEnvVar ? ( { setEnvVars((prev) => ({ ...prev, diff --git a/desktop/src/features/agents/ui/EnvVarsEditor.test.mjs b/desktop/src/features/agents/ui/EnvVarsEditor.test.mjs index b726851f69..1f9b8c7531 100644 --- a/desktop/src/features/agents/ui/EnvVarsEditor.test.mjs +++ b/desktop/src/features/agents/ui/EnvVarsEditor.test.mjs @@ -523,3 +523,40 @@ test("getBakedProviderInheritLabel_empty_options_falls_back_to_raw_id", () => { "empty options table must fall back to raw id", ); }); + +// ── keyAnnotations — annotation lookup invariants ───────────────────────────── +// +// `keyAnnotations` is a pass-through prop: the renderer does `keyAnnotations?.[key]`. +// The invariant worth pinning is that the prop contract is respected at the +// data level — an annotation for one key does NOT bleed into another key. +// (Rendering itself is trivially conditional; no logic to extract.) + +test("keyAnnotations_present_key_has_annotation", () => { + const annotations = { + OPENAI_API_KEY: "Used for minting agent trading cards", + }; + assert.equal( + annotations.OPENAI_API_KEY, + "Used for minting agent trading cards", + ); +}); + +test("keyAnnotations_absent_key_is_undefined", () => { + const annotations = { + OPENAI_API_KEY: "Used for minting agent trading cards", + }; + assert.equal(annotations.ANTHROPIC_API_KEY, undefined); +}); + +test("keyAnnotations_empty_map_has_no_annotations", () => { + const annotations = {}; + assert.equal(annotations.OPENAI_API_KEY, undefined); +}); + +test("keyAnnotations_only_matching_key_gets_annotation", () => { + // Verifies the per-key lookup is not accidentally global. + const annotations = { OPENAI_API_KEY: "card minting" }; + const keys = ["OPENAI_API_KEY", "ANTHROPIC_API_KEY", "FOO"]; + const results = keys.map((k) => annotations[k] ?? null); + assert.deepEqual(results, ["card minting", null, null]); +}); diff --git a/desktop/src/features/agents/ui/EnvVarsEditor.tsx b/desktop/src/features/agents/ui/EnvVarsEditor.tsx index 18bbf68337..91e5b07622 100644 --- a/desktop/src/features/agents/ui/EnvVarsEditor.tsx +++ b/desktop/src/features/agents/ui/EnvVarsEditor.tsx @@ -165,6 +165,14 @@ type EnvVarsEditorProps = { inheritedRows?: readonly InheritedEnvRow[]; /** Label for the inherited-row tag (e.g. "build"). Defaults to "build". */ inheritedRowsLabel?: string; + /** + * Optional muted one-line annotation for specific env var keys. Rendered + * below any row whose key appears in this map — required rows, user rows, + * and user rows whose key is typed mid-edit. Intended for contextual hints + * like `{ OPENAI_API_KEY: "Used for minting agent trading cards" }` that + * help users distinguish two keys with similar names. + */ + keyAnnotations?: Readonly>; }; type Row = { id: string; key: string; value: string }; @@ -191,6 +199,7 @@ export function EnvVarsEditor({ focusKey, inheritedRows = [], inheritedRowsLabel = "build", + keyAnnotations, }: EnvVarsEditorProps) { // Keys that render as their own special rows (required amber rows or // file-satisfied read-only rows). These must NEVER enter `rows` state — @@ -406,6 +415,14 @@ export function EnvVarsEditor({

); })()} + {keyAnnotations?.[key] ? ( +

+ {keyAnnotations[key]} +

+ ) : null}
); })} @@ -596,6 +613,14 @@ export function EnvVarsEditor({

); })()} + {row.key.length > 0 && keyAnnotations?.[row.key] ? ( +

+ {keyAnnotations[row.key]} +

+ ) : null} ); })} diff --git a/desktop/src/features/agents/ui/PersonaProviderApiKeyField.test.mjs b/desktop/src/features/agents/ui/PersonaProviderApiKeyField.test.mjs new file mode 100644 index 0000000000..4843f1042d --- /dev/null +++ b/desktop/src/features/agents/ui/PersonaProviderApiKeyField.test.mjs @@ -0,0 +1,32 @@ +/** + * Tests for PersonaProviderApiKeyField. + * + * The component is a pure view over its props with no extractable logic. + * The meaningful correctness check is that the component module exports the + * expected function and that TypeScript (tsc --noEmit) accepts the envVarName + * prop as optional — both verified at build time. + * + * Rendering-level behavior (envVarName hint appears when present, absent when + * omitted) is exercised by the E2E bridge and Playwright screenshots; a + * jsdom-based test would add no coverage beyond what TypeScript already + * guarantees for a trivially conditional `{envVarName ?

: null}`. + */ + +import assert from "node:assert/strict"; +import { test } from "node:test"; + +import { PersonaProviderApiKeyField } from "./PersonaProviderApiKeyField.tsx"; + +test("PersonaProviderApiKeyField_exports_a_function", () => { + assert.equal(typeof PersonaProviderApiKeyField, "function"); +}); + +test("PersonaProviderApiKeyField_accepts_envVarName_as_optional_prop", () => { + // TypeScript enforces the prop shape at compile time (tsc --noEmit). + // This test confirms the export identity is stable and the module loads. + assert.equal( + PersonaProviderApiKeyField.length, + 1, + "component accepts props object", + ); +}); diff --git a/desktop/src/features/agents/ui/PersonaProviderApiKeyField.tsx b/desktop/src/features/agents/ui/PersonaProviderApiKeyField.tsx index 17f9e2e826..0aa33fd64e 100644 --- a/desktop/src/features/agents/ui/PersonaProviderApiKeyField.tsx +++ b/desktop/src/features/agents/ui/PersonaProviderApiKeyField.tsx @@ -25,6 +25,7 @@ import { */ export function PersonaProviderApiKeyField({ disabled, + envVarName, isInherited, inheritedLabel, isRequired, @@ -33,6 +34,12 @@ export function PersonaProviderApiKeyField({ value, }: { disabled: boolean; + /** + * The backing environment variable name, e.g. `OPENAI_COMPAT_API_KEY`. + * Rendered as a monospace hint beneath the label so users can distinguish + * this field from other keys with similar names (e.g. `OPENAI_API_KEY`). + */ + envVarName?: string; /** True when the key is satisfied by an inherited layer. */ isInherited: boolean; /** Human-readable source of the inherited value. */ @@ -53,6 +60,9 @@ export function PersonaProviderApiKeyField({ {label} + {envVarName ? ( +

{envVarName}

+ ) : null}
{ + assert.equal(getProviderApiKeyLabel("anthropic"), "Anthropic API Key"); +}); + +test("getProviderApiKeyLabel_openai_returns_openai_label", () => { + assert.equal(getProviderApiKeyLabel("openai"), "OpenAI API Key"); +}); + +test("getProviderApiKeyLabel_openai_compat_returns_openai_label", () => { + // openai-compat maps to the same label as openai — both use OPENAI_COMPAT_API_KEY. + assert.equal(getProviderApiKeyLabel("openai-compat"), "OpenAI API Key"); +}); + +test("getProviderApiKeyLabel_openrouter_returns_openrouter_label", () => { + // Key fix: OpenRouter was mislabeled "OpenAI API Key" before this change. + assert.equal(getProviderApiKeyLabel("openrouter"), "OpenRouter API Key"); +}); + +test("getProviderApiKeyLabel_databricks_returns_null", () => { + // Databricks uses OAuth PKCE — no typed-secret label. + assert.equal(getProviderApiKeyLabel("databricks"), null); +}); + +test("getProviderApiKeyLabel_databricks_v2_returns_null", () => { + assert.equal(getProviderApiKeyLabel("databricks_v2"), null); +}); + +test("getProviderApiKeyLabel_unknown_provider_returns_null", () => { + assert.equal(getProviderApiKeyLabel("some-unknown-provider"), null); +}); + +test("getProviderApiKeyLabel_provider_id_trimmed_and_lowercased", () => { + // Mirrors getProviderApiKeyEnvVar normalisation behaviour. + assert.equal(getProviderApiKeyLabel(" Anthropic "), "Anthropic API Key"); +}); diff --git a/desktop/src/features/agents/ui/agentConfigOptions.tsx b/desktop/src/features/agents/ui/agentConfigOptions.tsx index d51c970f29..8709a62d09 100644 --- a/desktop/src/features/agents/ui/agentConfigOptions.tsx +++ b/desktop/src/features/agents/ui/agentConfigOptions.tsx @@ -68,6 +68,9 @@ export type PersonaDropdownOption = { * pastes in. Cleared automatically when the user switches away from the * provider. Databricks uses OAuth PKCE (no typed secret), so it has no * secretEnvVar. + * `apiKeyLabel`: human-readable label for the credential field in the UI. + * Only present when `secretEnvVar` is set. Used by `getProviderApiKeyLabel` + * so every credential field derives its label from one source of truth. * * Mirrors the Rust `readiness::buzz_agent_requirements` / * `readiness::goose_requirements` logic — keep in sync. @@ -75,6 +78,8 @@ export type PersonaDropdownOption = { export type ProviderCredentialConfig = { requiredEnvKeys: readonly string[]; secretEnvVar?: string; + /** Display label for the credential input field, e.g. "Anthropic API Key". */ + apiKeyLabel?: string; }; /** @@ -87,20 +92,23 @@ const PROVIDER_CREDENTIAL_CONFIG: Partial< anthropic: { requiredEnvKeys: ["ANTHROPIC_API_KEY"], secretEnvVar: "ANTHROPIC_API_KEY", + apiKeyLabel: "Anthropic API Key", }, openai: { requiredEnvKeys: ["OPENAI_COMPAT_API_KEY"], secretEnvVar: "OPENAI_COMPAT_API_KEY", + apiKeyLabel: "OpenAI API Key", }, "openai-compat": { requiredEnvKeys: ["OPENAI_COMPAT_API_KEY"], secretEnvVar: "OPENAI_COMPAT_API_KEY", + apiKeyLabel: "OpenAI API Key", }, databricks: { // DATABRICKS_TOKEN is NOT required — OAuth PKCE is the normal path. requiredEnvKeys: ["DATABRICKS_HOST"], - // No secretEnvVar: DATABRICKS_HOST is a URL, not a secret credential, and - // is not cleared on provider switch (unlike API keys). + // No secretEnvVar / apiKeyLabel: DATABRICKS_HOST is a URL, not a secret + // credential, and is not cleared on provider switch (unlike API keys). }, databricks_v2: { // DATABRICKS_TOKEN is NOT required — OAuth PKCE is the normal path. @@ -113,6 +121,7 @@ const PROVIDER_CREDENTIAL_CONFIG: Partial< openrouter: { requiredEnvKeys: ["OPENROUTER_API_KEY"], secretEnvVar: "OPENROUTER_API_KEY", + apiKeyLabel: "OpenRouter API Key", }, }; @@ -402,6 +411,21 @@ export function getProviderApiKeyEnvVar(providerId: string): string | null { ); } +/** + * Returns the display label for the provider's API key field, if any. + * Derived from PROVIDER_CREDENTIAL_CONFIG.apiKeyLabel — single source of truth + * for all credential field labels so every surface stays in sync. + * + * Returns null when the provider has no typed-secret credential (e.g., + * Databricks, which uses OAuth PKCE). + */ +export function getProviderApiKeyLabel(providerId: string): string | null { + return ( + PROVIDER_CREDENTIAL_CONFIG[providerId.trim().toLowerCase()]?.apiKeyLabel ?? + null + ); +} + export function shouldClearKnownModelForSelectionScope({ model, provider, From d391ededde781a7c0eb8d7e2d73e8f4d1d152181 Mon Sep 17 00:00:00 2001 From: npub1ng3jzsaqxdhrfq22dg85j3lpr0zsh3jp7g2h9jyxl59wraayapnsu6kvfg <9a232143a0336e34814a6a0f4947e11bc50bc641f21572c886fd0ae1f7a4e867@buzz.block.builderlab.xyz> Date: Sun, 2 Aug 2026 17:21:57 -0400 Subject: [PATCH 2/5] fix(desktop): kill label collision, visible card-mint-key cue, behavioral tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Finding 1 — label collision: rename openai->'OpenAI Runtime API Key' and openai-compat->'OpenAI-compatible Runtime API Key' so the runtime credential field and the mint dialog never present different OPENAI_* vars under the same human label. Update personaModelDiscoveryStatus.ts to name the compat key explicitly (OPENAI_COMPAT_API_KEY) in the warning copy; pin the new text in its test. ProviderCredentialConfig is now a discriminated union so secretEnvVar and apiKeyLabel are always either both present or both absent — a future provider cannot ship a secret with no label. Finding 2 — signpost visible at the decision point: export CARD_MINT_KEY_ANNOTATIONS from agentConfigOptions.tsx (single source, no per-file duplication). Pass keyAnnotations to all three generic env editors: both EnvVarsEditor branches in AgentConfigFields, EditAgentAdvancedFields, and PersonaAdvancedFields. Add CardMintKeyCue — an always-visible muted cue beneath the Advanced toggle that fires when OPENAI_API_KEY is set in global env (Advanced is collapsed by default, so the per-row annotation is invisible on Will's exact databricks_v2 path until the cue guides the user to open it). Finding 3 — ratchet: AgentConfigFields.tsx at 994 (<=996); AgentInstanceEditDialog at 1228 (<=1228); AgentDefinitionDialog at 1045 (<=1047). Finding 4 — behavioral tests: PersonaProviderApiKeyField renders semantic label, env-var hint, and aria-describedby wiring; omitted-prop case verified. EnvVarsEditor render confirms annotation on matching row only. Playwright: stale 'OpenAI API Key' selectors in persona-env-vars.spec.ts updated; new card-mint-key-cue-visible-and-annotation-in-advanced test covers Will's exact path (databricks_v2 global provider + saved OPENAI_API_KEY -> cue visible before opening Advanced -> annotation present after). MINOR (a): ProviderCredentialConfig is now a discriminated union. MINOR (b): PR description updated — rationale uses independent endpoint namespaces rather than 'minting calls real OpenAI'. Co-authored-by: Will Pfleger Signed-off-by: Will Pfleger --- .../features/agents/ui/AgentConfigFields.tsx | 19 ++-- .../src/features/agents/ui/CardMintKeyCue.tsx | 29 ++++++ .../agents/ui/EditAgentAdvancedFields.tsx | 2 + .../features/agents/ui/EnvVarsEditor.test.mjs | 57 ++++++++++++ .../agents/ui/PersonaAdvancedFields.tsx | 2 + .../ui/PersonaProviderApiKeyField.test.mjs | 91 +++++++++++++++---- .../agents/ui/PersonaProviderApiKeyField.tsx | 7 +- .../agents/ui/agentConfigOptions.test.mjs | 14 ++- .../features/agents/ui/agentConfigOptions.tsx | 48 ++++++---- .../ui/personaModelDiscoveryStatus.test.mjs | 3 +- .../agents/ui/personaModelDiscoveryStatus.ts | 3 +- .../global-agent-config-screenshots.spec.ts | 40 ++++++++ desktop/tests/e2e/persona-env-vars.spec.ts | 4 +- mobile/pubspec.lock | 16 ++-- 14 files changed, 270 insertions(+), 65 deletions(-) create mode 100644 desktop/src/features/agents/ui/CardMintKeyCue.tsx diff --git a/desktop/src/features/agents/ui/AgentConfigFields.tsx b/desktop/src/features/agents/ui/AgentConfigFields.tsx index 370277cc43..ce3d252203 100644 --- a/desktop/src/features/agents/ui/AgentConfigFields.tsx +++ b/desktop/src/features/agents/ui/AgentConfigFields.tsx @@ -32,6 +32,7 @@ import { import { AUTO_PROVIDER_DROPDOWN_VALUE, BLOCK_BUILD_HIDDEN_PROVIDER_IDS, + CARD_MINT_KEY_ANNOTATIONS, CUSTOM_PROVIDER_DROPDOWN_VALUE, getPersonaProviderOptions, getProviderApiKeyEnvVar, @@ -55,6 +56,7 @@ import { } from "@/features/agents/ui/buzzAgentModelTuningFields"; import { SettingsOptionGroup } from "@/features/settings/ui/SettingsOptionGroup"; import { AdvancedRequiredBadge } from "./AdvancedRequiredBadge"; +import { CardMintKeyCue } from "./CardMintKeyCue"; import { getGlobalAgentCredentialState } from "./globalAgentCredentialState"; export const EMPTY_GLOBAL_CONFIG: GlobalAgentConfig = { @@ -75,10 +77,6 @@ const PROGRESSIVE_FIELDS_TRANSITION = { duration: 0.22, ease: [0.23, 1, 0.32, 1], } as const; -/** Muted contextual hints beneath matching rows in the global env editor. */ -const GLOBAL_ENV_KEY_ANNOTATIONS: Readonly> = { - OPENAI_API_KEY: "Used for minting agent trading cards", -}; type AgentConfigDisclosure = | "full" | "onboarding-essential" @@ -89,13 +87,9 @@ type AgentConfigDisclosure = // - auto-select a valid model when the provider changes // - keep the model select usable during discovery // - preserve credential env vars across provider switches (the abandoned -// provider's key stays in env_vars — visible/deletable under Advanced — -// so flipping back never loses a typed key; spawned agents may therefore -// see credentials for providers they don't use) +// provider's key stays in env_vars — visible/deletable under Advanced) // - require a provider before model/effort are editable (no saveable -// invalid state — design principle #4). Note: legacy configs saved with -// a model but no provider are cleared by the pre-existing orphan-model -// effect on next edit — deliberate data healing, documented in PR. +// invalid state — design principle #4) const autoSelectModelOnProviderChange = true; const disableModelSelectDuringDiscovery = false; const preserveCredentialEnvVarsOnProviderChange = true; @@ -870,6 +864,7 @@ export function AgentConfigFields({ {showAdvancedFields ? (
+