Feature/workspace scoped env - #95
Open
davi0015 wants to merge 8 commits into
Open
Conversation
Per-workspace secret storage for env vars the agent references by name (e.g. $OPENAI_API_KEY) so the literal value never enters model context. Solves the guardrail problem: provider output filters block literal keys, but if the model only writes $VAR_NAME and the shell expands it, there's nothing to block. Variant model: one var name can hold multiple labeled values (prod/staging/ test) with exactly one active. Switching active is a plaintext-metadata flip — zero secret bytes touched. Storage split: - plaintext metadata (names, labels, active id) in IStorageService WORKSPACE - encrypted values blob in ISecretStorageService APPLICATION, one key per workspace (void.envVar.<workspaceHash>). Reads (scrubber on every terminal result) vastly outnumber writes (interactive management), so one decrypt per read beats N decrypts perces designed: terminal injection (_createTerminal merges active vars into IShellLaunchConfig.env), output scrubbing (replace known values with [REDACTED:VAR_NAME] at every result point), LLM advertisement (AVAILABLE_ENV_VARS block in volatile context, names only), management UI (view-title button sibling to the allowlist shield). Soft isolation: terminal still inherits base shell env so nvm/pyenv keep working. strictEnv (hard isolation) is an additive later change on the same storage layer.
Foundation for soft-isolation env var support. No consumers yet. Two-tier storage: - Plaintext metadata (var names, variant labels, active variant id) in IStorageService WORKSPACE scope (key void.workspaceEnvVarsI). Read every turn for LLM advertisement — no decrypt needed. - Encrypted values blob in ISecretStorageService APPLICATION scope, one key per workspace (void.envVar.<workspaceHash>). One decrypt per read covers all variants; reads (scrubber on every terminal result) vastly outnumber writes (interactive management). Variant model: one var name can hold multiple labeled values (prod/staging/ test) with exactly one active. Switching active is a plaintext-metadata flip — zero secret bytes touched. Workspace identity mirrorsspaceIdentity: single-folder → folder URI, saved .code-workspace → configPath, untitled multi-root → first folder URI (so adding/removing folders doesn't break identity). Empty window → no-op. Exports: addVar, addVariant, setActiveVariant, removeVar, removeVariant (metadata + values CRUD), getActiveEnv (for terminal injection), getAllEnvValues (for output scrubbing — all variants, not just active), getActiveVarDescriptors (for LLM advertisement — names + labels only).
TerminalToolService._createTerminal now calls IWorkspaceEnvVarService.getActiveEnv() and merges the result into IShellLaunchConfig.env before creating the terminal. Only terminals created via _createTerminal (hidden temporary ones from run_command, plus persistent ones) get the injected vars. A terminal the user opens manually via the + button doesn't go through this path, so workspace secrets stay scoped to Void's agent terminals. Failure to read (e.g. Keychain locked) logs and skips — terminal creation must not fail because one secret is unreadable. The env is merged on top of any caller-provided config.env so workspace vars take precedence.
Adds _scrubSecrets to TerminalToolService. At every point an output string is produced (runCommand result, readTerminal, readTerminalByName — 4 return points total), each known secret value is replaced with [REDACTED:VAR_NAME]. Uses getAllEnvValues (all variants, active + inactive) so terminals created under a now-inactive variant still get scrubbed — a persistent terminal keeps its injected env even after the user switches the active variant. Replacement uses split/join for literal string matching (no regex escaping). Skips empty values to avoid infinite replacement. Best-effort: if the secret read fails (Keychain locked), returns the text unscrubbed — the provider's input guardrail is the backstop. The commands array in readTerminalByName is NOT scrubbed — it contains the command text the model typed (e.g. eiable references, not secret values.
Instead of scrubbing all stored values (which redacts common config words like 'development' from legitimate output), let the user choose per var whether to scrub. Non-secret config like NODE_ENV=development gets redact=false; secrets get redact=true (the default). Changes: - EnvVarEntry gains redact: boolean - addVar accepts a redact param - New setRedact(name, redact) for toggling in the management UI - getVars() normalizes entries persisted before 'redact' existed to redact=true (safer default) - getAllEnvValues renamed to getScrubableEnvValues, filtered to redact=true vars only — the scrubber only sees values the user marked for scrubbing - TerminalToolService._scrubSecrets updated to call getScrubableEnvValues No length threshold needeuristic.
generateChatVolatileContext now calls IWorkspaceEnvVarService.getActiveVarDescriptors() and appends an <available_env_vars> block to the volatile context. The block lists var names + active variant labels (e.g. 'OPENAI_API_KEY (active: prod)'), never values. Instructs the model to reference them by $VAR_NAME and ask the user to add missing vars rather than searching. The block is volatile (per-turn, not persisted into history), so switching the active variant or adding/removing vars takes effect on the next turn without touching the cacheable system message.
New view-title button (Codicon.key) sibling to the terminal-allowlist shield. Opens a hierarchical QuickPick: Top level: lists all vars as 'VAR_NAME (active: label)' + an 'Add new env var' entry. Add var flow: name (validated against /^[A-Z_][A-Z0-9_]*$/) → variant label → value (password-masked) → redact? (Yes/No pick). Selecting a var drills into variant management: - Switch active variant (pick from variants, (active) marker) - Add variant (label + password-masked value) - Toggle redact (flip between scrub/no-scrub) - Remove var (drops metadata + encrypted values) Values are never displayed — only labels. Value entry uses password: true masking. ignoreFocusLost: true on all prompts so switching focus mid-flow (e.g. to copy a key) doesn't cancel.
Replace the QuickPick env var UI with a proper React editor pane, mirroring the global Settings pane pattern. The key icon opens a full form where all env vars, their values, active state, and redact toggle are visible and editable at once. New files: - workspaceSettingsPane.ts - EditorInput + EditorPane + open action (key icon) - react/src/workspace-settings-tsx/WorkspaceSettings.tsx - React form - react/src/workspace-settings-tsx/index.tsx - mount point The form shows each env var as a card with: - Name (fixed after creation) - List of values (masked by default, eye toggle to reveal, radio to set active, trash to remove) - Redact toggle (switch) - Remove var button Values added inline via Add value button. New vars created via Add env var which opens an inline name+value creation row. Redact defaults to true. Simplified env var model: dropped labels, ids, variants. A var is just a list Index. getValues(name) added for the UI to display masked previews. LLM advertisement simplified to just names. tsup.config.js: added workspace-settings-tsx entry point. void.contribution.ts: register workspaceSettingsPane. services.tsx: expose IWorkspaceEnvVarService to React accessor.
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.
No description provided.