# Edit History — 2026-04-07

## Adapter Architecture

Introduced a pluggable adapter system for agent infrastructure providers. Each adapter bridges our A2A protocol to a different agent runtime:

- `workspace-template/adapters/base.py` — BaseAdapter ABC with setup/create_executor interface
- `workspace-template/adapters/__init__.py` — Auto-discovery registry (scan subdirs for Adapter class)
- `workspace-template/adapters/langgraph/` — Ported from main.py (LangGraph ReAct agent)
- `workspace-template/adapters/claude_code/` — Wraps CLIAgentExecutor
- `workspace-template/adapters/openclaw/` — Real OpenClaw integration: npm install, onboard, gateway start, CLI proxy
- `workspace-template/adapters/deepagents/`, `crewai/`, `autogen/` — Stubs with real dep requirements
- `workspace-template/main.py` refactored: 232-line if/else → 160-line adapter flow
- `workspace-template/entrypoint.sh` — Installs adapter deps (`pip install --user`) at container startup
- `workspace-template/requirements.txt` stripped to bare minimum (A2A SDK + HTTP only)
- `workspace-template/agent.py` — Added Groq provider support

Adding a new agent infra: create `adapters/<name>/` with adapter.py + requirements.txt + `__init__.py` exporting Adapter.

## Docker Volume Isolation

Workspace configs now live entirely in Docker named volumes, not on the host:

- Provisioner creates `ws-{id[:12]}-configs` volume per workspace
- Template files copied via `CopyToContainer` after container start
- `ensureDefaultConfig()` returns `map[string][]byte` (in-memory), no host dirs
- Files API writes via ephemeral Alpine containers when workspace is offline
- Delete cleans up config volume via `RemoveVolume()`
- Zero `ws-*` dirs created on host filesystem

## Auto-Restart on Secret Change

Setting or deleting a workspace secret via `POST/DELETE /workspaces/:id/secrets` now auto-restarts the workspace so the new env var takes effect immediately:

- `SecretsHandler` takes a `restartFunc` callback wired to `WorkspaceHandler.RestartByID`
- `RestartByID` force-stops current container (even if provisioning — last-write-wins), then re-provisions with secrets from DB
- No manual restart needed after setting API keys — frictionless UX
- Brief 10s wait if container is still provisioning to ensure Stop() can find it

## Templates → Framework Presets

Templates are now agent infrastructure presets, not pre-configured personalities:

- Deleted 6 personality templates + 15 org-* templates + 29 ws-* orphan dirs
- 4 framework presets: `claude-code-default`, `langgraph`, `openclaw`, `deepagents`
- All non-Claude templates default to `openai:gpt-4.1-mini`
- Agent roles configured after deployment via Config tab or API
- `scripts/setup-default-org.sh` creates PM + 3 teams via API calls

## Settings + Config Tab Merge

Merged the Settings tab (API keys/secrets) into the Config tab:

- Settings tab removed — `SettingsTab.tsx` deleted, `"settings"` removed from `PanelTab` type
- Secrets UI moved into ConfigTab as a collapsible "Secrets & API Keys" section
- Environment section (required/optional env vars from config.yaml) removed — redundant with secrets
- Description field changed from text input to textarea (multi-line)
- Config tab now uses ⚙ icon, appears between Terminal and Files
- Tab order: Details, Activity, Chat, Terminal, **Config**, Files, Memory, Traces, Events
- Removed "Agent Files" section from DetailsTab (Replace Agent Folder button) — redundant with Files tab upload
- Removed "Agent" section from DetailsTab (model assign/replace/remove) — redundant with Config tab Runtime section
- Removed "Install Skill" section from DetailsTab — redundant with Config tab Skills & Tools section
- Moved "Agent Card" editor from DetailsTab to ConfigTab as collapsible section
- Deleted orphaned `SkillInstaller.tsx` and `SettingsTab.tsx`
- DetailsTab now focused: identity (name/role/tier), status/restart, skills (read-only from card), peers, delete

## Files API → Container File Explorer

Rewrote the Files API and tab to browse the container's actual filesystem via Docker exec, not just host-side config templates:

- `resolveConfigDir()` helper: checks ID-based dir (`ws-{id[:12]}/`) → name-based dir → template match via `config.yaml` name field. Defaults to ID-based for writes.
- `ListFiles`/`ReadFile` exec into running container (`find` + `cat`), falling back to host-side when container is offline
- `?root=` query param: `/configs` (default), `/home`, `/workspace`, `/plugins` — validated against allowlist
- Portable `find`+`stat` script (works on both GNU and BusyBox/Alpine)
- `stdcopy.StdCopy` for Docker stream demux, `io.LimitReader` (5MB) to prevent OOM
- Container lookup matches terminal handler (provisioner name + full ID + DB name)
- Files tab: root selector dropdown, read-only mode for non-`/configs` roots

## Structured Config Tab

Redesigned ConfigTab from raw JSON editor to a form-based config.yaml editor:

- Sections: General, Runtime, Skills/Tools, A2A, Delegation, Sandbox, Environment
- Proper inputs: text fields, dropdowns (tier T1-T3, runtime, sandbox backend), checkboxes (streaming, escalate), tag lists (skills, tools, env vars)
- Raw YAML toggle for power users
- Reads/writes `config.yaml` via Files API (not the DB config endpoint)
- Custom YAML parser handles flat keys, 1-level objects, 2-level nesting (`env.required: [...]`)

## Claude Code OAuth Fix

- `--bare` flag disables OAuth — changed claude-code preset to use `CLAUDE_CODE_OAUTH_TOKEN` env var instead of `apiKeyHelper`
- Removed `--bare` from base_args (keeps `--dangerously-skip-permissions`, `--allowed-tools Bash`)
- Each workspace is a full agentic agent, not just an LLM provider — hooks, CLAUDE.md discovery, etc. are intentionally enabled

## Terminal Fix

- Changed exec shell from `/bin/sh` to `/bin/bash` for tab completion and history
- Removed 30-minute `SetReadDeadline`/`SetWriteDeadline` that killed terminal WebSocket connections — sessions now stay open as long as the connection is alive, ending when user types `exit` or container stops

## Needs Restart Banner

Added `needsRestart` flag to `WorkspaceNodeData` — when config, secrets, or files change, workspace cards and side panel show a "Restart to apply changes" button:

- `SettingsTab`: flag set on secret add AND delete
- `ConfigTab`: flag set on config save
- `FilesTab`: flag set on file save, create, delete, and folder upload
- `WorkspaceNode`: sky-blue restart button on canvas card (hidden while agent has active task)
- `SidePanel`: "Config changed — restart to apply" banner with "Restart Now" button
- Both buttons trigger `POST /workspaces/:id/restart` and clear the flag
- Error toast shown on restart failure (not silently swallowed)

## Code Review Fixes (Round 3)

- Restart buttons show error toast via `showToast("Restart failed", "error")` on failure
- `needsRestart` added to all file mutation paths (create, delete, upload — was only on save)
- `needsRestart` added to secret delete (was only on add)
- Extracted `restartWorkspace(id)` store action — deduplicated restart logic from SidePanel and WorkspaceNode into `canvas/src/store/canvas.ts`; removed unused `api` imports from both components
- Fixed `selectedNodeId!` non-null assertion in SidePanel restart handler — replaced with proper `selectedNodeId &&` guard
- Terminal bash fallback now retries at **attach** level, not create — `ContainerExecCreate` succeeds even if the binary doesn't exist; the error only surfaces at `ContainerExecAttach`. Loop now creates+attaches for each shell candidate (`/bin/bash` → `/bin/sh`)
- Updated terminal comment to reference "the WebSocket→stdin bridge loop below" instead of hardcoded line number
