fix(desktop): make OpenAI key re-enterable after first save in card mint dialog - #4140
Merged
Conversation
…int dialog Once any key resolved, AgentCardMintDialog hid the key-setup panel forever (needsKey is false) — a write-once shadow field with no update path. Users with an expired key (e.g. HTTP 401) had no way to replace it. Add an editingKey boolean state. When false and needsKey is false the dialog shows the mint form with a new 'Update API key' link button near the cost note. Clicking it sets editingKey = true, which re-enters the existing key panel reusing the same save mutation, testids, and Rust seam (card_mint_save_openai_key). On successful save, editingKey resets to false and the user is back at the mint form. A Cancel button is shown only in update mode (editingKey && !needsKey) so first-time setup is unchanged. Also improve the 401 / 'Incorrect API key' error branch in cardMintStore: instead of surfacing the raw OpenAI wire message, emit actionable copy that names the problem (invalid/expired key) and points at the new affordance. Tests: 3919 passing, tsc --noEmit clean. Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Finding 1 — source-aware key update (Rust):
card_mint_key_status now returns a layer discriminant ("none" | "global" |
"persona" | "agent" | "process") instead of a bool, mirroring the exact
resolution priority of mint_agent_card. The dialog uses the layer to decide
whether to offer a writable input (none/global) or a read-only redirect
(agent/persona/process) pointing the user to the correct settings surface.
The write seam (card_mint_save_openai_key) is only offered when writing global
will actually become the resolved key. New Rust precedence test
key_status_layer_matches_mint_resolution_priority proves the status layer
matches what mint_agent_card would resolve.
Finding 2 — precise 401 matching (cardMintStore.ts):
Replace bare message.includes("401") with startsWith("Card mint failed (HTTP
401 ") to match only the OpenAI-call envelope. Avatar fetch 401 errors now
pass through unchanged. New passthrough test confirms avatar 401 is not
rewritten.
Finding 3 — tri-state key status row (AgentCardMintDialog.tsx):
"Using your saved OpenAI key" now renders only when keyLayer === "global"
(confirmed, writable key). While query is pending or errors, keyLayer is
undefined and the status row is hidden — no false claims about key existence.
Fail-open mint behavior preserved.
Finding 4 — real tests (cardMintKeyUtils.ts + test):
Extract showKeyPanel, isWritableLayer, isReadOnlyLayer, showCancelButton,
showKeyStatusRow, keyPanelTitle into cardMintKeyUtils.ts. The component imports
and uses these functions directly. AgentCardMintDialog.test.mjs now imports the
production module — removing any function will break the tests. Old predicate-
copy approach deleted.
3938 JS tests passing, 16 Rust card tests passing, tsc --noEmit clean.
Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
- showKeyPanel now gates only on none||editingKey; read-only layers (agent/persona/process) show an inline provenance row on the mint form with a Why? affordance that opens the redirect panel with Cancel — Mint card is reachable for every resolved layer - Extract resolve_key_layer() pure helper in card.rs; card_mint_key_status delegates to it instead of duplicating the classification logic; the Rust precedence test now calls the production function directly (no test-local closure), also covering process and blank-value cases - card.rs: 999 lines (under the 1000-line ratchet) - e2eBridge.ts card_mint_key_status stub returns "global" (not boolean true) - Component now imports showCancelButton and keyPanelTitle from cardMintKeyUtils.ts and uses them; new showReadOnlyRow helper added - Tests: 3959 JS passing, 2089 Rust passing, tsc --noEmit clean Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Member
Author
Screenshots — mint dialog statesCaptured at head 1. First-time key setup (no key saved)2. Saved global key — status row + Update affordance2b. Update key panel (after clicking Update)3. Read-only provenance — key from agent settings3b. Read-only Why? panel4. Expired/invalid key — error chip in composer rail |
wpfleger96
added a commit
that referenced
this pull request
Aug 3, 2026
…key (#4406) Two different credentials were presented under the same name throughout the app. The top-level credential field for non-Anthropic providers (OpenAI, OpenAI-compatible, OpenRouter) was labeled "OpenAI API Key" via a hardcoded binary ternary repeated in three dialogs. The card-minting key (`OPENAI_API_KEY`) and the runtime credential (`OPENAI_COMPAT_API_KEY`) have independent endpoint namespaces and consumers (`OPENAI_COMPAT_BASE_URL`/`OPENAI_COMPAT_API_KEY` for runtime, `OPENAI_BASE_URL`/`OPENAI_API_KEY` for minting) and must remain separate — either may require a different credential. This PR makes them impossible to confuse in the UI. ## Changes **Provider-accurate labels from the credential table.** `PROVIDER_CREDENTIAL_CONFIG` entries now carry an `apiKeyLabel` paired with `secretEnvVar` as a discriminated union (both present or neither — a future provider cannot ship a secret field with no label). `getProviderApiKeyLabel(providerId)` is the single source of truth. The three hardcoded ternaries in `AgentConfigFields`, `AgentInstanceEditDialog`, and `AgentDefinitionDialog` are replaced by this helper. Labels: `openai` → "OpenAI Runtime API Key", `openai-compat` → "OpenAI-compatible Runtime API Key", `openrouter` → "OpenRouter API Key" (was incorrectly "OpenAI API Key"), `anthropic` → "Anthropic API Key" (unchanged). **Field names its backing env var.** `PersonaProviderApiKeyField` renders the env var name as a monospace hint beneath the label with `aria-describedby` wiring. All three call sites pass their `secretEnvVar`. A user who sees `OPENAI_API_KEY` in the mint dialog can now confirm at a glance that the credential field shows `OPENAI_COMPAT_API_KEY` — a different key. **Signpost visible at the decision point.** `CARD_MINT_KEY_ANNOTATIONS` is exported from `agentConfigOptions.tsx` (single source) and passed as `keyAnnotations` to all three generic env editors: both `EnvVarsEditor` branches in Agent Defaults, `EditAgentAdvancedFields`, and `PersonaAdvancedFields`. `CardMintKeyCue` — a new small component — renders an always-visible muted cue beneath the Advanced toggle when `OPENAI_API_KEY` is present in global env (Advanced is collapsed by default, so the per-row annotation is invisible until the cue guides the user to open it). **Model discovery error copy.** The `OPENAI_COMPAT_API_KEY required` message now reads "Enter an OpenAI runtime API key (OPENAI_COMPAT_API_KEY) to load OpenAI models." — naming the env var explicitly so it cannot be confused with the mint key. ## Tests - `getProviderApiKeyLabel` helper: pinned correct label per provider including the new distinct labels for `openai` and `openai-compat` - `PersonaProviderApiKeyField` render: semantic label present; env-var hint rendered when `envVarName` provided; `aria-describedby` wired to hint id; hint and describedby absent when prop omitted - `EnvVarsEditor` render: annotation appears exactly once on the matching row; absent for non-matching rows - `personaModelDiscoveryStatus`: pinned new copy naming `OPENAI_COMPAT_API_KEY` explicitly - Playwright: stale `"OpenAI API Key"` selectors 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 opening) ## File sizes (post-format) | File | Lines | |------|-------| | `AgentConfigFields.tsx` | 994 (≤ 996) | | `AgentInstanceEditDialog.tsx` | 1228 (≤ 1228) | | `AgentDefinitionDialog.tsx` | 1045 (≤ 1047) | Related: [#4140](#4140) --------- Signed-off-by: Will Pfleger <pfleger.will@gmail.com> Co-authored-by: npub1ng3jzsaqxdhrfq22dg85j3lpr0zsh3jp7g2h9jyxl59wraayapnsu6kvfg <9a232143a0336e34814a6a0f4947e11bc50bc641f21572c886fd0ae1f7a4e867@buzz.block.builderlab.xyz>
tlongwell-block
pushed a commit
that referenced
this pull request
Aug 3, 2026
The pre-push branch-skew hook correctly refused a tree CI will never test: origin/main advanced over desktop/src/testing/e2eBridge.ts, which this branch also carries via its base. Merging the PR's own base rather than origin/main directly: 4c257c8 already contains current main (5e0efb0) AND Max's resolution of that exact e2eBridge.ts overlap, certified with Desktop Smoke E2E (1) green. Re-resolving main's conflict here would duplicate that work and risk diverging from it. Merge-only, no rebase, no force. merge-tree reported 0 conflicts. Co-authored-by: npub17jjz49l9jjmhhk7cac63j8yt9z555n9cw8vk7v5jz4vzw4ppld5qgj57cc <f4a42a97e594b77bdbd8ee35191c8b28a94a4cb871d96f32921558275421fb68@buzz.block.builderlab.xyz> Signed-off-by: npub17jjz49l9jjmhhk7cac63j8yt9z555n9cw8vk7v5jz4vzw4ppld5qgj57cc <f4a42a97e594b77bdbd8ee35191c8b28a94a4cb871d96f32921558275421fb68@buzz.block.builderlab.xyz> * origin/max/tui-renderer: fix(desktop): disambiguate provider API key labels and annotate mint key (#4406) fix(desktop): make OpenAI key re-enterable after first save in card mint dialog (#4140) fix(config-bridge): add harness-definition env tier and fix equal-value model override (#3580) test(desktop): prove channel repair boundary Polish mobile composer and messaging UI (#3918) ci(linux): enable mesh-llm feature in Linux release and canary builds (#4524) test(desktop): arm validation error before read test(desktop): atomically start channel validation fix(desktop): retain terminal focus on viewport click test(desktop): isolate deferred channel read fix(desktop): stop the terminal fade clobbering the app surface's compositor hint Revert "Merge PR #4523: close channel validation latch race" test(desktop): close channel validation latch race
wpfleger96
pushed a commit
that referenced
this pull request
Aug 3, 2026
…ed-unread * origin/main: (44 commits) chore(release): release Buzz Desktop version 0.5.4 (#4562) test(mobile): assert follow boundary semantics (#4559) docs(release): align desktop handoff instructions (#3988) fix: report agent usage per provider round, not once per turn (#4545) fix(desktop): harden Windows installs against Defender block and orphaned Node (#4382) feat(desktop): improve channel template discovery (#4549) fix(desktop): save key backups to authorized path (#4022) Add channel activity hover menu (#3935) feat(desktop): show saved Run on settings when editing an agent (#4539) fix(desktop): disambiguate provider API key labels and annotate mint key (#4406) fix(desktop): make OpenAI key re-enterable after first save in card mint dialog (#4140) fix(config-bridge): add harness-definition env tier and fix equal-value model override (#3580) Polish mobile composer and messaging UI (#3918) ci(linux): enable mesh-llm feature in Linux release and canary builds (#4524) fix(desktop): stop the create-agent provider config probe from erasing keystrokes (#4411) fix(mobile): recover and pace live subscriptions (#3053) feat(acp): deliver system prompt via _meta.systemPrompt for claude-agent-acp (#4395) fix(security): bump nostr crates for RUSTSEC-2026-0225..0232 + default sprig image to published digest (#4392) fix(desktop): back/forward via keyboard chords, mouse X1/X2 buttons, and swipe gestures (#3778) feat(k8s): Kubernetes backend plugin + desktop deploy path (#4289) ... Signed-off-by: npub1mn7jgtj4w2pd0g0zeuhxsa6jy6p0rewxz4kujt98my82ahfmp72sxjexk7 <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz>
wpfleger96
added a commit
that referenced
this pull request
Aug 3, 2026
Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com> * origin/main: (40 commits) fix(mobile): recover stale relay sessions (#4372) chore(release): release Buzz Desktop version 0.5.4 (#4562) test(mobile): assert follow boundary semantics (#4559) docs(release): align desktop handoff instructions (#3988) fix: report agent usage per provider round, not once per turn (#4545) fix(desktop): harden Windows installs against Defender block and orphaned Node (#4382) feat(desktop): improve channel template discovery (#4549) fix(desktop): save key backups to authorized path (#4022) Add channel activity hover menu (#3935) feat(desktop): show saved Run on settings when editing an agent (#4539) fix(desktop): disambiguate provider API key labels and annotate mint key (#4406) fix(desktop): make OpenAI key re-enterable after first save in card mint dialog (#4140) fix(config-bridge): add harness-definition env tier and fix equal-value model override (#3580) Polish mobile composer and messaging UI (#3918) ci(linux): enable mesh-llm feature in Linux release and canary builds (#4524) fix(desktop): stop the create-agent provider config probe from erasing keystrokes (#4411) fix(mobile): recover and pace live subscriptions (#3053) feat(acp): deliver system prompt via _meta.systemPrompt for claude-agent-acp (#4395) fix(security): bump nostr crates for RUSTSEC-2026-0225..0232 + default sprig image to published digest (#4392) fix(desktop): back/forward via keyboard chords, mouse X1/X2 buttons, and swipe gestures (#3778) ...
wpfleger96
added a commit
that referenced
this pull request
Aug 3, 2026
Syncs Cargo.lock and ci.yml updates from origin/main via the umbrella, which resolves the RUSTSEC-2026-0225..0229 nostr advisory failures in the Security gate. Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com> * commit 'ad9ae1dbb0c2d9b6839c1e18d6fd6a78238b61fb': (40 commits) fix(mobile): recover stale relay sessions (#4372) chore(release): release Buzz Desktop version 0.5.4 (#4562) test(mobile): assert follow boundary semantics (#4559) docs(release): align desktop handoff instructions (#3988) fix: report agent usage per provider round, not once per turn (#4545) fix(desktop): harden Windows installs against Defender block and orphaned Node (#4382) feat(desktop): improve channel template discovery (#4549) fix(desktop): save key backups to authorized path (#4022) Add channel activity hover menu (#3935) feat(desktop): show saved Run on settings when editing an agent (#4539) fix(desktop): disambiguate provider API key labels and annotate mint key (#4406) fix(desktop): make OpenAI key re-enterable after first save in card mint dialog (#4140) fix(config-bridge): add harness-definition env tier and fix equal-value model override (#3580) Polish mobile composer and messaging UI (#3918) ci(linux): enable mesh-llm feature in Linux release and canary builds (#4524) fix(desktop): stop the create-agent provider config probe from erasing keystrokes (#4411) fix(mobile): recover and pace live subscriptions (#3053) feat(acp): deliver system prompt via _meta.systemPrompt for claude-agent-acp (#4395) fix(security): bump nostr crates for RUSTSEC-2026-0225..0232 + default sprig image to published digest (#4392) fix(desktop): back/forward via keyboard chords, mouse X1/X2 buttons, and swipe gestures (#3778) ...
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.






Fixes a write-once dead-end in the card mint dialog where a user with an expired OpenAI key had no way to replace it.
Source-aware key status (Rust + TypeScript).
card_mint_key_statusreturns a layer discriminant ("none" | "global" | "persona" | "agent" | "process") instead of a boolean. A pureresolve_key_layer()helper incard.rsowns the classification logic;card_mint_key_statusdelegates to it, so the production path is under direct test with no duplicate logic.Mint form always reachable. The key panel replaces the mint form only for
none(first-time setup) or when the user explicitly opens the edit panel (editingKey). Keys from agent/persona/process layers show an inline provenance row on the mint form with a "Why?" affordance; clicking it shows the read-only redirect in a panel with a Cancel button that returns to the mint form — never a terminal state.Precise auth-error matching. The 401 handling in
cardMintStore.tsmatchesstartsWith("Card mint failed (HTTP 401 ")plus the specificIncorrect API keytext, so avatar-fetch 401 errors pass through unchanged.Tri-state key status row. "Using your saved OpenAI key · Update" renders only when
keyLayer === "global"(confirmed writable key). Query pending or errored hides the row without asserting key existence.Real tests. Panel visibility derivations live in
cardMintKeyUtils.ts, whichAgentCardMintDialog.tsximports directly. Tests cover all layers including the mint-reachability invariant (Mint reachable for every resolved layer; onlynonegates setup).card.rs— newresolve_key_layer()pure helper;card_mint_key_statusdelegates to it; 999 lines (under the 1000-line ratchet)card/tests.rs— precedence test callsresolve_key_layer()directly (no test-local closure); adds process-layer and blank-value casestauriPersonas.ts—CardMintKeyLayertype; updatedcardMintKeyStatussignaturecardMintKeyUtils.ts—showKeyPanel,showReadOnlyRow,showCancelButton,keyPanelTitle, and helpers; component imports all of themAgentCardMintDialog.tsx— inline provenance rows for all key sources; key panel only for setup/edit; no unused variablescardMintStore.ts— precise 401 prefix matchinge2eBridge.ts—card_mint_key_statusstub returns"global"(not boolean)tsc --noEmitcleanRelated: block/buzz#4406