Skip to content

feat(desktop): per-persona isolated opencode credential store - #2

Merged
lbarasti merged 2 commits into
mainfrom
feat/persona-opencode-credentials
Sep 5, 2026
Merged

lbarasti merged 2 commits into
mainfrom
feat/persona-opencode-credentials

Conversation

@lbarasti

@lbarasti lbarasti commented Sep 5, 2026

Copy link
Copy Markdown
Owner

Summary

Per-persona custom credentials for opencode agents, isolated from the shareable persona definition:

  • Machine-local credential store (managed_agents/credentials.rs): the per-persona opt-in and the secrets live in the desktop-managed store (XDG pair injected at spawn) — never on the persona definition, never in env_vars, never in any persona payload.
  • IPC surface (tauriAgentCredentials.tsget/set/clear_persona_opencode_credential + set_persona_opencode_credentials_enabled): returns provider ids, paths, and the opt-in flag — never keys.
  • Shared isolation gate for spawn and model discovery (apply_spawn_isolation / extend_env_with_isolation): discovery runs against the agent's isolated auth store, so the model list reflects what the agent can authenticate with; opt-in without a provisioned key falls back to the owner's default credentials. Both apply the XDG pair after user env; both keys are now reserved.
  • UI: PersonaOpencodeCredentialsSection renders only via the PersonaOpencodeCredentialsSlot gate (isOpencodeRuntime, never an inline id comparison) and only in edit mode. Dialog submit path refactored over personaSubmitBaseInput/personaNamePoolInput helpers.
  • Changes take effect on the agent's next start — no live-restart path.

Rule 18 in desktop/src/features/agents/AGENTS.md documents the contract.

Testing

  • cargo check / cargo test (managed_agents::credentials tests pass, including persona-id traversal rejection, deterministic dir naming, and opt-in marker round-trip)
  • pnpm --dir desktop typecheck — clean
  • Desktop unit tests for personaDialogState / personaBehaviorDraft / buzzAgentConfig / credentials state — 97 pass
  • Pre-push lanes (file-size, clippy, desktop typecheck + tests, tauri checks) green

🤖 Generated with opencode

Custom credentials isolate a persona's opencode harness auth store in a
machine-local credential store; opt-in and secrets never live on the
shareable persona definition. Spawn and model discovery share one
isolation gate, the XDG roots are reserved keys, and the UI reaches the
store only through the new IPC surface with an edit-mode-only section.

Signed-off-by: Lorenzo Barasti <782103+lbarasti@users.noreply.github.com>
Match the Environment variables editor's visual vocabulary: row shells
(PERSONA_FIELD_SHELL_CLASS, min-h-11 flex rows), font-mono control
inputs, and the shared Button component (ghost icon remove, outline
Add key) instead of hand-rolled buttons and a bespoke grid.

Signed-off-by: Lorenzo Barasti <782103+lbarasti@users.noreply.github.com>
@lbarasti

lbarasti commented Sep 5, 2026

Copy link
Copy Markdown
Owner Author

Review from Fizz (posted here at Lorenzo's request; findings noted for follow-up, not blocking the merge).

Solid overall: the shared spawn/discovery gate, path-sanitized dir names with the hash suffix, 0600 atomic writes, and the path-based cores that make the logic testable are all nice calls. Observations, biggest first:

  1. Err arm breaks the fail-closed principle used elsewhere. The doc says a malformed store counts as provisioned so the agent surfaces opencode's own auth error rather than silently spending on the owner's key — but in apply_spawn_isolation, the Err arm (e.g. I/O failure creating the credential dir) logs to stderr and then spawns without isolation, i.e. on the owner's key while the user believes isolation is active. Same shape in isolation_enabled, which swallows base-dir errors silently. Rare paths, but the stated invariant argues for withholding the spawn — or at least surfacing it in the agent log UI, not just stderr.

  2. XDG reservation is global, injection is opencode-only. XDG_DATA_HOME/XDG_CONFIG_HOME are now in RESERVED_ENV_KEYS, so save-time validation + spawn filtering apply to every harness. Any existing non-opencode agent that legitimately used XDG overrides (custom config volume, etc.) silently loses them. Defensible tradeoff, but worth a changelog line.

  3. Cancel doesn't cancel. The toggle and key writes hit disk immediately, outside the dialog submit path — a user can flip the switch, then hit Cancel, and the opt-in sticks. The copy says "next start" and the design says no live-restart, so this may be deliberate; just noting it is surprising in a dialog.

  4. Minor: credential_state creates the persona dirs on a pure read (get_persona_opencode_credentialscreate_private_dir), while isolation_enabled deliberately avoids creation on the read path. Harmless empty 0700 dirs, just inconsistent intent.

  5. Minor: no lock around auth.json writes (fixed auth.json.tmp name) — concurrent IPC writes from two windows could race. Single-window desktop makes this theoretical.

Only #1 rises to should-fix; #2block#5 parked as agreed.

@lbarasti
lbarasti merged commit 2071551 into main Sep 5, 2026
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