Skip to content

fix(desktop): make OpenAI key re-enterable after first save in card mint dialog - #4140

Merged
wpfleger96 merged 3 commits into
mainfrom
duncan/card-mint-key-update
Aug 3, 2026
Merged

fix(desktop): make OpenAI key re-enterable after first save in card mint dialog#4140
wpfleger96 merged 3 commits into
mainfrom
duncan/card-mint-key-update

Conversation

@wpfleger96

@wpfleger96 wpfleger96 commented Aug 1, 2026

Copy link
Copy Markdown
Member

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_status returns a layer discriminant ("none" | "global" | "persona" | "agent" | "process") instead of a boolean. A pure resolve_key_layer() helper in card.rs owns the classification logic; card_mint_key_status delegates 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.ts matches startsWith("Card mint failed (HTTP 401 ") plus the specific Incorrect API key text, 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, which AgentCardMintDialog.tsx imports directly. Tests cover all layers including the mint-reachability invariant (Mint reachable for every resolved layer; only none gates setup).

  • card.rs — new resolve_key_layer() pure helper; card_mint_key_status delegates to it; 999 lines (under the 1000-line ratchet)
  • card/tests.rs — precedence test calls resolve_key_layer() directly (no test-local closure); adds process-layer and blank-value cases
  • tauriPersonas.tsCardMintKeyLayer type; updated cardMintKeyStatus signature
  • cardMintKeyUtils.tsshowKeyPanel, showReadOnlyRow, showCancelButton, keyPanelTitle, and helpers; component imports all of them
  • AgentCardMintDialog.tsx — inline provenance rows for all key sources; key panel only for setup/edit; no unused variables
  • cardMintStore.ts — precise 401 prefix matching
  • e2eBridge.tscard_mint_key_status stub returns "global" (not boolean)
  • Tests: 3959 JS passing, 2089 Rust passing, tsc --noEmit clean

Related: block/buzz#4406

…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>
@wpfleger96 wpfleger96 self-assigned this Aug 1, 2026
npub1ng3jzsaqxdhrfq22dg85j3lpr0zsh3jp7g2h9jyxl59wraayapnsu6kvfg and others added 2 commits August 1, 2026 13:03
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>
@wpfleger96
wpfleger96 marked this pull request as ready for review August 2, 2026 23:13
@wpfleger96
wpfleger96 requested a review from a team as a code owner August 2, 2026 23:13
@wpfleger96

Copy link
Copy Markdown
Member Author

Screenshots — mint dialog states

Captured at head 7e1659b19 via Playwright scratch spec (uncommitted). All four states introduced by this PR, plus the Update flow and Why? panel.

1. First-time key setup (no key saved)

01-first-time-key-setup


2. Saved global key — status row + Update affordance

02-saved-key-status-row

2b. Update key panel (after clicking Update)

02b-update-key-panel


3. Read-only provenance — key from agent settings

03-read-only-provenance-row

3b. Read-only Why? panel

03b-read-only-why-panel


4. Expired/invalid key — error chip in composer rail

04-expired-key-error-chip

@wpfleger96
wpfleger96 merged commit f810a2f into main Aug 3, 2026
27 checks passed
@wpfleger96
wpfleger96 deleted the duncan/card-mint-key-update branch August 3, 2026 15:12
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)
  ...
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant