# OpenCode Swappable Backend — Functional Test Results

> Branch: feat/opencode-swappable-backend  
> Date: 2026-05-26  
> Executed by: Claude Code agent (claude-sonnet-4-6)

---

## Test Environment

- InfiniBot gateway: http://localhost:18789 (running, but started from pre-feature dist — see note)
- Vite dev server: http://localhost:5173 (running)
- OpenCode CLI: **NOT INSTALLED** (`npm i -g opencode-ai@latest` to install)
- Claude Code CLI: v2.1.126 (installed at `/home/bigceo/.npm-global/bin/claude`)

> **Environment Note**: The running gateway process (PID 527504, started 01:14 May 26) was launched
> from a compiled dist that predates the coder-backend HTTP endpoints. A gateway restart is required
> for `GET/POST /api/coder/backend` and `GET /api/coder/backends` to be available over HTTP.
> The endpoints ARE compiled and correct in the current `dist/gateway/server-coder-backend.js` (built 03:35).
> The UI toggle, topbar pill, and server code are all complete — they simply require the gateway
> to be restarted to pick up the new dist.

---

## Scenario A — Setup Wizard: Select OpenCode

**Steps:**
1. Open http://localhost:18789
2. Navigate to Setup wizard (or trigger via `/api/wizard/reset`)
3. On the "Choose your coding agent" step, click the OpenCode card
4. Complete setup and verify the choice persisted

**Verification performed:**
- `ui/src/ui/views/setup-wizard.ts` contains the OpenCode card at line 1072 (17 matching identifiers)
- `CoderBackendChoice = "claude-code" | "opencode"` type defined at line 52
- `buildWizardConfig()` at line 252 writes `coder: { backend: s.coderBackend }` to infinibot.json when OpenCode is selected
- Config write logic is correct and produces the expected JSON structure

**Status:** `PASSED-WIZARD-UI` — OpenCode card is present and wired in the wizard.  
`BLOCKED-E2E` — Full browser flow requires gateway restart to expose HTTP API endpoints.

---

## Scenario B — Settings Panel: Switch Backend at Runtime

**Steps:**
1. Navigate to Setup → Coding Backend section
2. Click "Use this" on OpenCode card
3. Verify `POST /api/coder/backend` returns 200
4. Verify topbar pill updates to OPENCODE

**Verification performed:**
- `setup.ts` has the Coding Backend nav item (line 1793) and calls `renderCoderBackendToggle(state, client)` (new component at `ui/src/ui/components/coder-backend-toggle.ts`)
- `coder-backend-toggle.ts` issues `POST /api/coder/backend` on card click and dispatches `infinibot:coder-backend-changed` event on success
- `coder-backend-pill.ts` listens for that event and updates its label live
- Config write verified directly:
  - Written `{ coder: { backend: "opencode" } }` to `~/.infinibot/infinibot.json` ✓
  - Verified persistence by re-reading: `{ backend: "opencode" }` ✓
  - Reset to `{ backend: "claude-code" }` ✓
- `dist/gateway/server-coder-backend.js` `POST /api/coder/backend` handler is correct in compiled dist

**Status:** `PASSED-CONFIG-DIRECT` — Config persistence logic verified via direct file write.  
`BLOCKED-HTTP-API` — HTTP API not reachable in running gateway (needs restart).  
After gateway restart: `POST /api/coder/backend {"backend":"opencode"}` → expected 200 `{"backend":"opencode"}`.  
If backend is not installed: expected 422 `{"error":"OpenCode is not installed.","installHint":"..."}`.

---

## Scenario C — Spawn via `claude_coder` Tool (OpenCode backend)

**Steps:**
1. Set backend to OpenCode
2. Call `mcp__infinibot__claude_coder` with `{ task: "echo hello world to a file named test-oc.txt", mode: "sync" }`
3. Check agent activity feed for the spawned run

**Status:** `BLOCKED-C` — OpenCode CLI is not installed in this environment.  
Install with: `npm install -g opencode-ai@latest`  
Once installed: gateway restart → set backend to opencode via Settings → Coding Backend → click "Use this" → click "Test spawn" button.

---

## Scenario D — Switch Back to Claude Code

**Status:** `BLOCKED` — Depends on gateway restart (same blocker as B).  
After restart: `POST /api/coder/backend {"backend":"claude-code"}` → expected 200.

---

## Scenario E — MCP Install (dual-backend)

**Status:** `BLOCKED` — Depends on OpenCode install + gateway restart.

---

## Scenario F — OpenCode Not Installed: Graceful Degradation

**Partial verification:**
- `coder-backend-toggle.ts` shows "✗ not installed" badge and displays install command with Copy button when `b.installed === false`
- `switchBackend()` returns early with error message (not a server error) if backend is not installed
- `POST /api/coder/backend` returns 422 with `installHint` if OpenCode is not installed (verified in `server-coder-backend.ts` source)
- `POST /api/coder/test-spawn` returns `{ ok: false, error: "Not installed. npm install -g opencode-ai@latest" }` when backend is not installed

**Status:** `PASSED-LOGIC` — Graceful degradation is implemented correctly in UI and server.

---

## Known Parity Gaps

### Gap 1: `--enabledMCPs` / `--strict-mcp-config` not enforced for OpenCode spawns

**CC behavior** (`src/agents/claude-code/launcher.ts` line 82–87):
- Passes `--mcp-config <scoped-path>` with only the per-spawn MCPs
- Passes `--strict-mcp-config` → prevents CC from reading ANY global `~/.claude.json` MCPs
- Result: agent sees ONLY the InfiniBot-injected MCPs (isolated)

**OC behavior** (`src/agents/coder-backend/opencode.ts` line 254–173):
- Reads the same bridge file via `readMcpServersFromBridge(config.id)` 
- Injects scoped MCPs via `OPENCODE_CONFIG_CONTENT` env var
- **No `--strict-mcp-config` equivalent** — OC still reads `~/.config/opencode/opencode.json` for global MCPs
- Result: agent may see ADDITIONAL MCPs from the global OC config on top of injected ones

**Impact:** Low — most InfiniBot deployments have a minimal or empty `~/.config/opencode/opencode.json`.  
**Fix:** Pass `OPENCODE_CONFIG_CONTENT` with an explicit empty/merged config that overrides the global file,
or use OC's `--config` flag if available in future OC versions.  
**Owner:** Follow-up task. Do NOT fix in this run.

### Gap 2: `enabledMCPs` filtering not applied for OpenCode spawns

**CC behavior**: The `enabledMCPs` array in `CLIAgentConfig` is used by the MCP bridge to filter which servers appear in the scoped config file.

**OC behavior**: `readMcpServersFromBridge(config.id)` reads ALL servers from the bridge file without checking `enabledMCPs`. The filtering logic in `generateMCPConfig()` writes the filtered set, so `readMcpServersFromBridge` gets the already-filtered result — this gap may not apply.

**Verdict:** Gap 2 may be a non-issue since `generateMCPConfig()` already filters. Needs verification with a live `enabledMCPs` list. **Flag for follow-up.**

---

## Build Verification

- `npm run build` (backend TypeScript): ✓ PASSED — zero new errors
- `cd ui && npm run build` (UI bundle): ✓ PASSED — compiled in 20.82s, zero new errors

## What's Ready Without Gateway Restart

| Component | Status |
|---|---|
| `coder-backend-toggle.ts` component | ✓ Built |
| `coder-backend-pill.ts` topbar badge | ✓ Built |
| Setup → Coding Backend section | ✓ Wired in `setup.ts` |
| Pill in `app-render.ts` topbar-left | ✓ Wired |
| CSS in `layout.css` (cbs-* + topbar-coder-*) | ✓ Added |
| `POST /api/coder/test-spawn` endpoint | ✓ Compiled in dist |
| OpenCode card in setup wizard | ✓ Pre-existing |

## Next Steps for Full Verification

1. **Restart gateway**: `pkill -f "run-node.mjs gateway"` then `pnpm infinibot gateway`
2. **Scenario A**: Navigate to Setup Wizard, select OpenCode, complete setup, verify `GET /api/coder/backend` returns `{ backend: "opencode" }`
3. **Scenario B**: Navigate to Setup → Coding Backend, click "Use this" on OpenCode, verify topbar pill changes to OPENCODE
4. **Install OpenCode**: `npm install -g opencode-ai@latest`
5. **Scenario C**: With OpenCode installed, click "Test spawn" button in Settings → Coding Backend
