Skip to content

feat(workspaces): per-workspace AI provider config#181

Merged
luokerenx4 merged 2 commits into
masterfrom
dev
May 13, 2026
Merged

feat(workspaces): per-workspace AI provider config#181
luokerenx4 merged 2 commits into
masterfrom
dev

Conversation

@luokerenx4
Copy link
Copy Markdown
Contributor

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 / codex inside the workspace terminal pick up the same provider as OpenAlice-spawned sessions.

Per-session contributions

2026-05-13 — Workspace-level AI provider

  • CLI-native file layout — claude reads <workspace>/.claude/settings.local.json from cwd; codex reads <workspace>/.codex/config.toml + .codex/env.json via CODEX_HOME (set by the codex adapter). No .openalice/ shadow files — the workspace IS the config.
  • Bootstrap skeleton — both chat and auto-quant templates now drop a .codex/ skeleton (config.toml with the OpenAlice MCP block, auth.json symlinked to ~/.codex/auth.json so codex doesn't crash before user configures). .git/info/exclude pre-emptively ignores secret files (.claude/settings.local.json, .codex/auth.json).
  • composeEnv adapter hook — new optional composeEnv?(ctx) on CliAdapter. Codex uses it for CODEX_HOME (plumbing — where files live, not what to auth as) and to shuttle .codex/env.json values into the spawn env (codex's env_key indirection has no file-based alternative). Claude adapter unchanged — claude reads env.ANTHROPIC_BASE_URL from .claude/settings.local.json natively (verified via claude --debug api against a bogus host).
  • Backend routes — 3 new endpoints under /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 existing PathTraversal guard extended with readWorkspaceFile / writeWorkspaceFile helpers.
  • UI — new WorkspaceAIConfigModal (tabs per agent, profile quick-pick + manual fields + reset). Two entry points share one modal instance (state lifted to WorkspacesContext, modal rendered at provider level so it survives activity switches):
    • button on each workspace row in the sidebar — configure without opening a tab
    • AI Provider button in the WorkspacePage header — configure while inside a workspace
  • Codex MCP simplification — old mcpJsonToCodexFlags translator removed. Workspace's own .codex/config.toml carries the MCP block directly now; codex reads it via CODEX_HOME.

Key commits: 7e56d1d (feature), eff21b7 (sidebar entry point)

Full commit log

eff21b7 feat(workspaces/ui): add ⚙ AI Provider button to sidebar workspace row
7e56d1d feat(workspaces): per-workspace AI provider config via CLI-native files

Migration note

Legacy workspaces created before this change don't have a .codex/ skeleton. The codex adapter defensively skips setting CODEX_HOME when <workspace>/.codex/auth.json is 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 --noEmit clean (root + ui)
  • pnpm test — 88 files, 1687 tests passing
  • vite build clean
  • Manual smoke (curl):
    • Fresh chat workspace: bootstrap creates .codex/{config.toml, auth.json symlink}, .git/info/exclude has both safety lines
    • GET /api/workspaces/agent-profiles returns Claude/MiniMax/DeepSeek
    • PUT .../agent-config/claude writes .claude/settings.local.json with env.ANTHROPIC_BASE_URL + env.ANTHROPIC_API_KEY + model
    • PUT .../agent-config/codex writes .codex/config.toml (preserving MCP block, adding [model_providers.workspace]) + .codex/env.json (containing OPENALICE_WORKSPACE_KEY)
    • GET round-trip parses each correctly
    • PUT empty body resets the file to {} (claude) or back to just MCP + empty env.json (codex)
  • Browser interactive (user-verified): created a workspace, picked Kimi via the sidebar ⚙ button, claude session in the workspace now uses Kimi endpoint without any OpenAlice profile switch

🤖 Generated with Claude Code

Ame and others added 2 commits May 13, 2026 13:07
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>
@luokerenx4 luokerenx4 merged commit 9c8bb10 into master May 13, 2026
2 checks passed
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