Files uploaded to ./context/ were going to the workspace context directory
instead of the mission-specific context directory at /root/context/{mission_id}/
- Add mission_id parameter to PathQuery, FinalizeUploadRequest, DownloadUrlRequest
- Update resolve_path_for_workspace to resolve context paths to mission-specific dirs
- Update dashboard uploadFile, uploadFileChunked, finalizeChunkedUpload, downloadFromUrl
- Pass mission.id from control-client.tsx when uploading files
The get_anthropic_api_key_for_claudecode function now checks for OAuth
access tokens in addition to API keys. It also falls back to reading
from ai_providers.json if OpenCode auth.json doesn't have credentials.
This fixes the bug where Claude Code missions failed with "Invalid API
key" even when Anthropic OAuth was properly configured in the dashboard.
When uploading files during a mission, the backend now accepts
workspace_id parameter to correctly resolve relative paths against
the mission's workspace instead of relying on stale runtime state.
- Skip agent validation for Claude Code backend in create_mission
Claude Code has its own built-in agents that don't exist in OpenCode's
agent list, so validation was incorrectly failing.
- Filter model dropdown by selected backend
Claude Code only supports Anthropic models, so the dropdown now only
shows those when Claude Code backend is selected. Also resets model
override when switching to Claude Code if non-Anthropic model was selected.
- Add Claude Code support to run_single_control_turn (control.rs)
Previously only parallel execution via MissionRunner worked.
Now single-mission execution also supports Claude Code backend.
- Read CLI path from backend config file (mission_runner.rs)
The cli_path setting in dashboard was ignored; now it reads from
~/.openagent/data/backend_configs.json before falling back to
env var or default.
- Kill CLI process on mission cancellation (client.rs, mission_runner.rs)
Added ClaudeProcessHandle wrapper with kill() method to properly
terminate the subprocess when a mission is cancelled, preventing
continued API resource consumption.
Replace the separate API key field in Claude Code backend settings with
integration into the AI Providers system:
- Add use_for_backends field to provider data model (stored in opencode.json)
- Add backend selection step in Add Provider modal for Anthropic (OpenCode/Claude Code)
- Add /api/ai/providers/for-backend/:id endpoint to get provider credentials
- Update mission runner to get Anthropic API key from provider system
- Replace API key input with provider status display in Claude Code settings
- Show backend badges on provider cards in AI Providers section
This allows users to authenticate Claude Code using:
- Claude Pro/Max subscription via OAuth
- Anthropic API key through the unified provider system
Simplify the Create New Mission dialog by removing the separate
Backend dropdown and combining it with Agent selection. Agents
are now grouped by backend (OpenCode, Claude Code) using optgroups.
- Implement ClaudeCodeClient with subprocess JSON streaming to Claude CLI
- Implement ClaudeCodeBackend with Backend trait for mission execution
- Update mission runner to support both OpenCode and Claude Code backends
- Add harness tabs to Library Configs page (OpenCode/Claude Code)
- Add CLI path configuration for Claude Code in Settings
- Add comprehensive harness system documentation
- Move scope migration for workspace/desktop MCPs outside the binary
resolution block so it runs even when binaries don't exist locally
- Add automatic duplicate removal on startup (keeps first entry by name)
- This fixes the issue where old configs with scope: Global weren't
being migrated, and duplicate entries weren't being cleaned up
- Add run_migrations() to SQLite store that adds 'backend' column to existing
missions tables (CREATE TABLE IF NOT EXISTS doesn't add columns to existing tables)
- Fix race condition in new-mission-dialog when switching backends: wait for
backendAgents to finish loading before setting default agent, and only use
fallback agents for opencode backend
Replace env-var-only LIBRARY_REMOTE configuration with disk-persisted
settings. The setting can now be edited in the dashboard Settings page,
with LIBRARY_REMOTE env var serving as initial default when no settings
file exists.
Changes:
- Add src/settings.rs for settings storage with JSON persistence
- Add src/api/settings.rs for settings API endpoints
- Update dashboard Settings page with editable library remote field
- Update library-unavailable component to link to Settings page
- Update documentation to recommend Settings page method
The dashboard was storing libraryRepo in localStorage and sending it as
x-openagent-library-remote header, which could override the server's
configured LIBRARY_REMOTE. This caused confusion when client and server
configs diverged (e.g., skills not showing despite being present on server).
Changes:
- Remove libraryRepo from localStorage settings type
- Remove x-openagent-library-remote header from apiFetch
- Add library_remote to HealthResponse type
- Update settings page to show library remote as read-only from server
- Simplify library-unavailable component to show server config instructions
- Fix unrelated TypeScript error (progress: null -> undefined)
The server's LIBRARY_REMOTE env var is now the single source of truth.
Expose the server's configured LIBRARY_REMOTE in the health response.
This allows the dashboard to display the server's library configuration
without relying on client-side localStorage overrides.
When shared_network was set to false but Tailscale wasn't configured,
the container would get no DNS configuration at all. Now we fall back
to binding /etc/resolv.conf to ensure DNS always works.
The MCP process can start before the mission's context file is written,
causing OPEN_AGENT_CONTEXT_ROOT to not be set. This results in uploaded
files being inaccessible inside containers.
Add read_runtime_context() to dynamically read the context file before
each container bash command, ensuring the context folder bind mount is
always configured correctly regardless of startup timing.
Use direct assignment for shared_network to allow resetting it to
None (default behavior). The frontend always sends shared_network
in update requests, so None means "reset to default", not "don't change".
The bind mount was incorrectly using workspace_root/context instead of the
global context root. Mission context files are stored in the global context
root (e.g., /root/context/{mission_id}), so that's what needs to be bind-mounted
into the container for the context symlink to resolve correctly.
- Fix SSE error handler not calling onEvent, leaving UI stuck in updating state
- Fix plugin name prefix matching to avoid corrupting wrong config entries
- Fix EventSource cleanup on component unmount using useRef
Add a new `shared_network` option to workspaces and workspace templates
that controls network configuration for container (nspawn) workspaces:
- When true (default): Share host network, bind-mount /etc/resolv.conf
for DNS resolution
- When false: Use isolated networking (--network-veth) for Tailscale
or other custom network configurations
This fixes DNS resolution issues in container workspaces that don't use
Tailscale by properly sharing the host's DNS configuration.
Changes:
- Add shared_network field to Workspace and WorkspaceTemplate structs
- Update nspawn command building to use shared_network setting
- Add UI toggles in workspace template editor and workspace settings
- Update API types and endpoints
resolve_upload_base was using container-relative paths (e.g., /workspaces/...)
directly on the host filesystem. Added the same container path mapping logic
that resolve_download_path uses to correctly map to host paths.
For chroot workspaces, the context directory bind mount was using the
global OPEN_AGENT_CONTEXT_ROOT env var which is set for the host workspace.
This meant the bind mount was skipped because the path didn't exist.
Now we compute the context root from the workspace_root directly:
workspace_root.join("context"). This ensures each container workspace
gets its own context directory properly bind-mounted at /root/context.
Also creates the context directory if it doesn't exist before the bind mount.
For chroot workspaces, the context symlink was pointing to the host path
(e.g., /root/.openagent/workspaces/xxx/context/mission-id) which doesn't
exist inside the container. Now it points to the container path
(/root/context/mission-id) where the directory is bind-mounted.
Also ensures the mission context directory is created on the host before
the container starts, so the bind mount isn't empty.
When creating a chroot workspace with a template specified,
automatically trigger the build instead of leaving it in
"pending" status. This improves UX by not requiring a
separate POST to /api/workspaces/:id/build.
The build runs asynchronously in the background, same as
the manual build endpoint.
- Add InstalledPluginsSection showing plugins from OpenCode config
- Display installed version vs latest available with update indicators
- Support one-click updates with real-time SSE progress feedback
- Distinguish between "Installed OpenCode Plugins" and "Library Plugins"
- Add API client functions for getInstalledPlugins and updatePlugin
- Add GET /api/system/plugins/installed endpoint to discover plugins from OpenCode config
- Add POST /api/system/plugins/:package/update endpoint with SSE progress streaming
- Query npm registry for latest versions and detect available updates
- Check bun cache for currently installed versions
- Support both scoped (@scope/name) and unscoped package names
- Rename binary from host-mcp to workspace-mcp
- Rename src/bin/host_mcp.rs to src/bin/workspace_mcp.rs
- Update tool prefix from host_* to workspace_*
- Update MCP registration name from "host" to "workspace"
- Add "Builtin" tag to UI for workspace and desktop MCPs
- Update documentation (CLAUDE.md, INSTALL.md, docs-site)
The "workspace MCP" name better reflects that it runs in the
workspace's execution context - inside containers for container
workspaces, on host for host workspaces.
- Add CommandParam struct with name, required, and description fields
- Parse params from command frontmatter (supports simple list and detailed object formats)
- Display params in autocomplete: <required> and [optional]
- Update TypeScript interfaces to include params
EnhancedInput now exposes canSubmit state via onCanSubmitChange callback,
allowing parent components to show proper disabled styling when input is
empty or only has a locked agent without content. This fixes the issue
where buttons remained visually enabled even when clicking would have no
effect.
- Add git reset --hard HEAD before checkout to prevent local changes blocking updates
- Add git clean -fd to remove untracked files that might interfere
- Add 100ms delay before restart to ensure the "restarting" SSE event is flushed
The update system in Settings relies on git to fetch tags and checkout
releases. Updated INSTALL.md to clarify:
- Repository must be git clone'd (not rsync'd) at /opt/open_agent/vaduz-v1
- How to create releases with version tags for update detection
- Dashboard one-click update flow vs manual CLI updates
Open Agent now acts as a pure pass-through frontend to OpenCode.
We no longer impose any timeouts on the SSE event stream.
Changes:
- Remove SSE_INACTIVITY_TIMEOUT (180s) and TOOL_STATUS_CHECK_INTERVAL (30s)
- Remove tool tracking for timeout extension
- Simplify SSE loop to blocking read without timeout
- Document timeout philosophy in module docs and CLAUDE.md
This ensures long-running tools complete naturally and avoids
timeout mismatches between Open Agent and OpenCode. Users can
still abort missions manually via the dashboard.
- Remove disabled attribute from Send/Queue buttons since EnhancedInput
handles submission validation internally (supports lockedAgent badge)
- Fix tool timeout extension by resetting last_activity when continuing
to wait for running tools (prevents immediate timeout on next loop)
- Fix periodic logging to use time-based tracking instead of modulo
check which rarely triggers due to irregular loop timing