feat(workspaces): per-workspace AI provider config#181
Merged
Conversation
Each workspace is now a VS-Code-style self-contained unit for AI provider choice. Claude reads `.claude/settings.local.json` from its cwd; codex reads `.codex/config.toml` + `.codex/env.json` via `CODEX_HOME` (set by the codex adapter to the workspace's own .codex/). If a user opens a terminal in the workspace and runs `claude` or `codex` manually — no OpenAlice involvement — they get the same provider as if they spawned via OpenAlice's sidebar. Files in the workspace are the source of truth; the launcher just sets CODEX_HOME and (for codex's env_key indirection) shuttles env.json into the spawn env. Workspace creation does NOT write any provider config — fresh workspaces inherit the user's global CLI defaults (~/.claude/auth, ~/.codex/auth.json via symlink). After creation, the new `[⚙ AI Provider]` button in WorkspacePage opens a modal where the user picks from OpenAlice's existing profiles or fills custom fields; save writes the CLI-native files. Reset deletes them. Backend changes: - bootstrap.sh (chat + auto-quant): create `.codex/` skeleton (config.toml with MCP block, auth.json symlink to ~/.codex/auth.json as graceful fallback), append safety entries to .git/info/exclude. - cli-adapter.ts: optional `composeEnv?(ctx): Record<string, string>` on CliAdapter for per-spawn env contribution. - service.ts: SessionPool factory merges adapter.composeEnv() into spawn env (AFTER buildSpawnEnv so adapter wins on overlap). - codex.ts: composeEnv sets CODEX_HOME (when workspace's .codex/auth.json exists — defensive against legacy workspaces) and exports any vars from .codex/env.json (workspace's API-key store; codex reads via env_key indirection in [model_providers.X]). Deleted the old mcpJsonToCodexFlags translator; MCP wiring lives in the workspace's own .codex/config.toml now. - file-service.ts: added readWorkspaceFile + writeWorkspaceFile helpers using the existing PathTraversal guard; replaces symlink targets with real files on write (for upgrading bootstrap-time auth.json symlink). - routes/workspaces.ts: 3 new routes — GET /agent-profiles (OpenAlice profile list), GET /:id/agent-config (parsed claude + codex configs), PUT /:id/agent-config/:agent (writes the CLI-native files). Frontend: - WorkspaceAIConfigModal (new): tabs for Claude/Codex, profile quick-pick + manual fields + reset. Save writes via PUT. - WorkspacePage: header bar with [⚙ AI Provider] button opening the modal. - api.ts: 3 client methods (listAgentProfiles, getAgentConfig, saveAgentConfig). Verified live: bootstrap creates .codex/ skeleton (auth symlink + MCP block); UI PUT for claude writes settings.local.json with env.ANTHROPIC_BASE_URL + env.ANTHROPIC_API_KEY + model; UI PUT for codex writes config.toml (preserving MCP block) + env.json with the key under OPENALICE_WORKSPACE_KEY (matches env_key in the provider section); GET round-trips correctly; reset path clears files. `.git/info/exclude` catches both .claude/settings.local.json + .codex/auth.json. Migration note: legacy workspaces created before this change don't have a .codex/ skeleton; codex adapter defensively skips setting CODEX_HOME when .codex/auth.json is missing, so those workspaces fall back to the user's global codex setup (also losing workspace-MCP wiring for codex until recreated). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Previously the only entry point to the per-workspace AI-provider modal
was the WorkspacePage header — which requires opening a workspace tab
first. Adds a ⚙ action button next to the existing + / × buttons on
each workspace row in the sidebar so the user can configure provider
without opening anything.
To support multiple entry points opening the same modal instance, lift
modal state into WorkspacesContext:
- `openAgentConfig(wsId)` action on the context
- Provider renders the modal as a sibling to children when state is set
- Modal is mounted at app root via the provider, so it survives
activity switches (closing the Workspaces sidebar doesn't dismiss
an open modal)
Both the new sidebar button and the existing WorkspacePage header
button now call `ctx.openAgentConfig(wsId)`. Removed the local modal
state from WorkspacePage.
Launcher Sidebar.tsx edits are surgical (4 small additions):
- new optional `onConfigureWorkspace?(wsId: string)` prop on
SidebarProps + WorkspaceRowProps
- threaded through the workspace map
- render the ⚙ button conditionally (only when the prop is wired)
between the existing + spawn and × delete buttons
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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.
Summary
Workspaces can now hold their own AI provider settings — each workspace is a VS-Code-style self-contained unit. Open one in MiniMax, another in DeepSeek, another in Kimi, another on Claude Pro/Max OAuth. The CLI tools read their own config from the workspace cwd by convention; OpenAlice's role is the scaffold + visual editor. No spawn-time provider injection — files on disk are the source of truth. Manual
claude/codexinside the workspace terminal pick up the same provider as OpenAlice-spawned sessions.Per-session contributions
2026-05-13 — Workspace-level AI provider
<workspace>/.claude/settings.local.jsonfrom cwd; codex reads<workspace>/.codex/config.toml+.codex/env.jsonviaCODEX_HOME(set by the codex adapter). No.openalice/shadow files — the workspace IS the config..codex/skeleton (config.tomlwith the OpenAlice MCP block,auth.jsonsymlinked to~/.codex/auth.jsonso codex doesn't crash before user configures)..git/info/excludepre-emptively ignores secret files (.claude/settings.local.json,.codex/auth.json).composeEnvadapter hook — new optionalcomposeEnv?(ctx)onCliAdapter. Codex uses it forCODEX_HOME(plumbing — where files live, not what to auth as) and to shuttle.codex/env.jsonvalues into the spawn env (codex'senv_keyindirection has no file-based alternative). Claude adapter unchanged — claude readsenv.ANTHROPIC_BASE_URLfrom.claude/settings.local.jsonnatively (verified viaclaude --debug apiagainst a bogus host)./api/workspaces:agent-profiles(lists OpenAlice's existing AI profiles for the UI's quick-pick),:id/agent-config(read),:id/agent-config/:agent(write). File IO uses the existingPathTraversalguard extended withreadWorkspaceFile/writeWorkspaceFilehelpers.WorkspaceAIConfigModal(tabs per agent, profile quick-pick + manual fields + reset). Two entry points share one modal instance (state lifted toWorkspacesContext, modal rendered at provider level so it survives activity switches):⚙button on each workspace row in the sidebar — configure without opening a tabAI Providerbutton in the WorkspacePage header — configure while inside a workspacemcpJsonToCodexFlagstranslator removed. Workspace's own.codex/config.tomlcarries the MCP block directly now; codex reads it via CODEX_HOME.Key commits:
7e56d1d(feature),eff21b7(sidebar entry point)Full commit log
Migration note
Legacy workspaces created before this change don't have a
.codex/skeleton. The codex adapter defensively skips settingCODEX_HOMEwhen<workspace>/.codex/auth.jsonis missing, so those workspaces silently fall back to the user's global codex setup. They also lose workspace-MCP wiring for codex until recreated. Workspaces created after this commit are fully covered.Test plan
npx tsc --noEmitclean (root + ui)pnpm test— 88 files, 1687 tests passingvite buildclean.codex/{config.toml, auth.json symlink},.git/info/excludehas both safety linesGET /api/workspaces/agent-profilesreturns Claude/MiniMax/DeepSeekPUT .../agent-config/claudewrites.claude/settings.local.jsonwithenv.ANTHROPIC_BASE_URL+env.ANTHROPIC_API_KEY+modelPUT .../agent-config/codexwrites.codex/config.toml(preserving MCP block, adding[model_providers.workspace]) +.codex/env.json(containingOPENALICE_WORKSPACE_KEY){}(claude) or back to just MCP + empty env.json (codex)🤖 Generated with Claude Code