Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
edf9957
feat(opencode): Phase 1 — foundation primitives for OpenCode CLI prov…
patricka3125 Apr 21, 2026
facd245
fix(opencode): Phase 1 review items — ANSI fixture, model cleanup, gu…
patricka3125 Apr 21, 2026
0b070b7
feat(install): add opencode_cli provider branch with --auto-approve flag
patricka3125 Apr 21, 2026
8f5be49
fix(install): apply Phase 2 review polish
patricka3125 Apr 21, 2026
1cac5b6
feat(providers): add OpenCodeCliProvider (Phase 3)
patricka3125 Apr 21, 2026
6ec7c5c
docs: add Phase 3 OpenCode provider runtime development walkthrough r…
patricka3125 Apr 21, 2026
f608f5d
fix(opencode): Phase 3 review polish — correct report line count, add…
patricka3125 Apr 21, 2026
d0cee13
feat(opencode): Phase 4 — e2e test, provider docs, README/CHANGELOG
patricka3125 Apr 21, 2026
c812e06
docs: add Phase 4 OpenCode e2e and docs development walkthrough report
patricka3125 Apr 21, 2026
4c30661
fix(opencode): translate CAO mcpServer format to OpenCode's opencode.…
patricka3125 Apr 21, 2026
9f08c9f
docs(opencode): add --yolo DANGEROUS caveat to permission troubleshoo…
patricka3125 Apr 21, 2026
92bfabf
docs: update Phase 4 report with e2e results and Phase 3 regression f…
patricka3125 Apr 21, 2026
3d70846
feat(opencode): native skill discovery via OPENCODE_CONFIG_DIR/skills…
patricka3125 Apr 21, 2026
7c0c224
fix(opencode): handle Nm Ns duration format and extend extraction buffer
patricka3125 Apr 21, 2026
8979d41
refactor(opencode): single capture-pane per get_output call; add wron…
patricka3125 Apr 21, 2026
079f4a9
refactor(opencode): rename on-disk config directory from opencode_cli…
patricka3125 Apr 21, 2026
b5c6078
Merge branch 'main' into feat/opencli-integration
patricka3125 Apr 21, 2026
bd92d87
fix(opencode): fall back to first agent-indented line when user messa…
patricka3125 Apr 21, 2026
89ad483
refactor(terminal_service): guard build_skill_catalog() call and upda…
patricka3125 Apr 21, 2026
b870f4f
docs: update Phase 6 dev report with alt-screen extraction fix and cl…
patricka3125 Apr 21, 2026
e3149a7
docs(opencode): correct extraction_tail_lines docstring + black forma…
patricka3125 Apr 21, 2026
23c56e0
docs(opencode): correct OPENCODE_DISABLE_MOUSE rationale and add UX c…
patricka3125 Apr 21, 2026
1b94c2f
untrack out of scope docs
patricka3125 Apr 21, 2026
38ded3e
fix(opencode): align auto-approve, agent ID, and MCP cleanup semantics
patricka3125 Apr 21, 2026
7358b78
docs(opencode): drop reference to removed design doc from changelog
patricka3125 Apr 22, 2026
b82db76
refactor(opencode): scope extraction_tail_lines to OpenCodeCliProvider
patricka3125 Apr 22, 2026
3f64824
test(opencode): close codecov gaps in cli provider and permission tra…
patricka3125 Apr 22, 2026
bc92a83
Merge branch 'main' into feat/opencli-integration
patricka3125 Apr 22, 2026
37d5510
Merge branch 'main' into feat/opencli-integration
patricka3125 Apr 22, 2026
f7ee124
Merge branch 'main' into feat/opencli-integration
haofeif Apr 22, 2026
675d9f2
Merge branch 'main' into feat/opencli-integration
patricka3125 Apr 22, 2026
0478d08
Merge branch 'main' into feat/opencli-integration
patricka3125 Apr 23, 2026
41b0b40
docs(opencode): mark provider experimental — single-agent flows only
patricka3125 Apr 24, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added

- **OpenCode CLI provider** — Full integration with [OpenCode](https://opencode.ai), a terminal-based AI assistant whose native agent format (Markdown + YAML frontmatter) maps directly onto CAO profiles. Supports `cao install --provider opencode_cli`, all five terminal states (IDLE, PROCESSING, COMPLETED, WAITING_USER_ANSWER, ERROR), permission translation from CAO `allowedTools` to OpenCode `permission:` frontmatter, MCP server wiring via a CAO-owned `opencode.json`, and config isolation from the user's personal OpenCode setup. CAO's on-disk config directory for OpenCode is `~/.aws/opencode/` — users who installed an earlier pre-release build (which used `~/.aws/opencode_cli`) must re-run `cao install --provider opencode_cli` to populate the new location. The old directory can be removed with: `rm -rf ~/.aws/opencode_cli`. Provider docs: [`docs/opencode-cli.md`](docs/opencode-cli.md).
## [2.1.0] - 2026-04-22

### Added
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ Before using CAO, install at least one supported CLI agent tool:
| **Gemini CLI** | [Provider docs](docs/gemini-cli.md) · [Installation](https://github.com/google-gemini/gemini-cli) | Google AI API key |
| **Kimi CLI** | [Provider docs](docs/kimi-cli.md) · [Installation](https://platform.moonshot.cn/docs/kimi-cli) | Moonshot API key |
| **GitHub Copilot CLI** | [Provider docs](docs/copilot-cli.md) · [Installation](https://github.com/features/copilot/cli) | GitHub auth |
| **OpenCode CLI** *(experimental — single-agent only, [#203](https://github.com/awslabs/cli-agent-orchestrator/issues/203))* | [Provider docs](docs/opencode-cli.md) · [Installation](https://opencode.ai) | Per-model API key |
| **Q CLI** | [Installation](https://docs.aws.amazon.com/amazonq/latest/qdeveloper-ug/command-line.html) | AWS credentials |

## Quick Start
Expand Down Expand Up @@ -151,6 +152,7 @@ cao launch --agents code_supervisor --provider codex
cao launch --agents code_supervisor --provider gemini_cli
cao launch --agents code_supervisor --provider kimi_cli
cao launch --agents code_supervisor --provider copilot_cli
cao launch --agents code_supervisor --provider opencode_cli
# Unrestricted access + skip confirmation (DANGEROUS)
cao launch --agents code_supervisor --yolo
```
Expand Down
247 changes: 247 additions & 0 deletions docs/opencode-cli.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,247 @@
# OpenCode CLI Provider

> ⚠️ **Experimental — single-agent flows only.** Multi-agent orchestration (assign / send_message back to a supervisor) is **not yet reliable** on `opencode_cli`: the supervisor's inbox can deadlock with `pending` messages after its turn settles. Single-agent and pure handoff workflows are unaffected. Tracking: [#203](https://github.com/awslabs/cli-agent-orchestrator/issues/203).

## Overview

The OpenCode CLI provider enables CLI Agent Orchestrator (CAO) to work with **OpenCode**, a terminal-based AI assistant with a native agent system. OpenCode uses Markdown files with YAML frontmatter as its agent format — nearly identical to CAO's own profile format — making this integration especially clean.

## Prerequisites

1. **OpenCode binary** — install from [opencode.ai](https://opencode.ai):
```bash
npm install -g opencode-ai
# or
curl -fsSL https://opencode.ai/install | bash
```
2. **Node.js 18+** — required by OpenCode for its plugin system
3. **tmux 3.3+** — required by CAO for terminal management
4. **API credentials** — configure whichever model provider you want OpenCode to use (Anthropic, OpenAI, etc.) per [OpenCode's auth docs](https://opencode.ai/docs/auth)

### First-launch delay

On its **first ever launch** against a fresh CAO config directory (`~/.aws/opencode/`), OpenCode runs `npm install @opencode-ai/plugin` — roughly 57 MB of dependencies that take **5–30 seconds** to install. The TUI will appear blank until the install completes. This is expected; CAO's 120-second initialization timeout covers it automatically.

Subsequent launches complete in ~2 seconds.

## Quick Start

### 1. Install agent profiles

```bash
# Built-in profiles
cao install code_supervisor --provider opencode_cli
cao install developer --provider opencode_cli
cao install reviewer --provider opencode_cli

# Custom or example profiles
cao install examples/assign/data_analyst.md --provider opencode_cli
cao install examples/assign/report_generator.md --provider opencode_cli
```

### 2. Start the CAO server

```bash
uv run cao-server
```

### 3. Launch an agent

```bash
# Standard launch — shows tool summary and asks for confirmation
cao launch --agents developer --provider opencode_cli

# Skip CAO's launch-time confirmation prompt (tool restrictions still enforced)
cao launch --agents developer --provider opencode_cli --auto-approve

# Specify model override
cao launch --agents developer --provider opencode_cli --model anthropic/claude-sonnet-4-6

# Unrestricted (DANGEROUS) — agent can run any command
cao launch --agents developer --provider opencode_cli --yolo
```

Via HTTP API:

```bash
curl -X POST "http://localhost:9889/sessions?provider=opencode_cli&agent_profile=developer"
```

## Config Isolation

CAO runs OpenCode with `OPENCODE_CONFIG_DIR` and `OPENCODE_CONFIG` both pointing at `~/.aws/opencode/`, which is separate from the user's personal OpenCode config at `~/.config/opencode/`. This means:

- CAO-installed agents are visible in OpenCode's agent picker alongside the built-ins
- CAO's MCP wiring (`opencode.json`) never touches the user's personal setup
- Switching between `cao launch` and personal `opencode` usage is safe — they use independent config trees

Storage layout:

```
~/.aws/opencode/
├── opencode.json # MCP servers + per-agent tool gating (written by cao install)
├── package.json # written by opencode on first launch
├── node_modules/ # ~57 MB, written by opencode on first launch
└── agents/
├── code_supervisor.md
├── developer.md
└── ...
```

## Permission and Tool Mapping

OpenCode enforces permissions natively via `permission:` YAML frontmatter in each agent file. CAO translates its `allowedTools` list to an OpenCode `permission:` dict at install time — **no entry in `utils/tool_mapping.py` is needed**.

CAO owns the permission decision, so the translator only ever emits `allow` or `deny`. The `ask` value — OpenCode's native runtime prompt — is intentionally never written, which keeps OpenCode aligned with the other CAO providers (Kiro, Q, Claude Code) where allowed tools are allowed outright.

### Summary

| CAO category | OpenCode tools enabled |
|---|---|
| `execute_bash` | `bash` |
| `fs_read` | `read` |
| `fs_write` | `edit`, `write` |
| `fs_list` | `glob`, `grep` |
| `fs_*` | `read`, `edit`, `write`, `glob`, `grep` |
| `@<mcp-server-name>` | Handled in `opencode.json` (not frontmatter) |

Tools not in any enabled category default to `deny`. The following tools have hardcoded policies regardless of `allowedTools`:

| Tool | Policy | Reason |
|---|---|---|
| `task` | deny | Sub-agents escape CAO's terminal tracking |
| `question` | deny | Blocks unattended flows indefinitely |
| `webfetch`, `websearch`, `codesearch` | deny | Network egress — opt-in only |
| `todowrite`, `skill` | allow | In-memory / additive, no side-effects |

Pass `--yolo` (or set `allowedTools: ["*"]` in the profile) to allow all 13 tools including the above.

### `cao launch --auto-approve`

`--auto-approve` on `cao launch` matches the repo-wide semantics: it skips CAO's launch-time confirmation prompt only. Tool restrictions are still enforced, and this flag does not modify any files in `OPENCODE_CONFIG_DIR`. It has **no** `cao install` counterpart — install-time permissions are driven entirely by the profile's `allowedTools` / `role`.

## Skills

CAO skills (e.g. `cao-supervisor-protocols`, `cao-worker-protocols`) are exposed to OpenCode agents through OpenCode's **native `skill` tool** with progressive loading — they are **not** baked into the agent's system prompt.

At `cao install --provider opencode_cli` time, CAO creates a symlink:

```
~/.aws/opencode/skills → ~/.aws/cli-agent-orchestrator/skills/
```

OpenCode auto-discovers `<OPENCODE_CONFIG_DIR>/skills/` and makes its contents available through the `skill` tool. Metadata (name, description) is listed up front; full skill bodies are loaded on demand. This means:

- Skill additions or removals under `~/.aws/cli-agent-orchestrator/skills/` take effect on the next OpenCode launch with no reinstall required.
- The agent's system prompt stays lean — only `profile.system_prompt`/`profile.prompt` is written to the `.md` body, with no catalog injection.
- CAO's `load_skill` MCP tool remains available as a second path to the same content (cross-provider parity).

## Status Detection

The provider detects terminal state from the tmux capture buffer (ANSI-stripped):

| State | Marker |
|---|---|
| `IDLE` | `ctrl+p commands` footer, no `esc interrupt` |
| `PROCESSING` | `esc interrupt` footer keybind |
| `COMPLETED` | `▣ <agent> · <model> · Ns` completion marker followed by idle footer |
| `WAITING_USER_ANSWER` | `△ Permission required` or `△ Always allow` heading |
| `ERROR` | Fallback — no state marker matched |

## MCP Server Wiring

`cao install --provider opencode_cli` writes MCP server declarations into `~/.aws/opencode/opencode.json`:

- Each `mcpServers` entry from the agent profile is added under the top-level `mcp` key
- The server's tools are default-denied globally (`"<servername>*": false` under `tools`)
- Re-enabled per-agent under `agent.<agent_id>.tools`

The agent ID is the slash-sanitized form of the profile name (`/` → `__`) — the same identifier used for the installed `.md` filename and the runtime `opencode --agent <id>` argument. This keeps the filename, the `--agent` arg, and the `opencode.json` key aligned for any profile name.

Reinstalling an agent whose profile no longer declares `mcpServers` explicitly removes its `agent.<agent_id>` entry from `opencode.json`, so previously-granted MCP tools do not survive as stale grants.

`CAO_TERMINAL_ID` is **not** written to `opencode.json`. OpenCode spawns MCP subprocesses that inherit the tmux window's environment, so the terminal ID propagates naturally — the same mechanism Kiro uses.

## End-to-End Testing

```bash
# Install profiles first
cao install examples/assign/data_analyst.md --provider opencode_cli
cao install examples/assign/report_generator.md --provider opencode_cli
cao install developer --provider opencode_cli

# Start CAO server
uv run cao-server

# Run all OpenCode CLI e2e tests
uv run pytest -m e2e test/e2e/test_assign.py -k opencode -v

# Run a single test
uv run pytest -m e2e test/e2e/test_assign.py::TestOpenCodeCliAssign::test_assign_with_callback -v
```

The `test_assign_with_callback` test validates all four orchestration modes:
- **assign** (non-blocking): supervisor terminal created and stays IDLE
- **send_message** (inbox delivery): worker pushes result to supervisor inbox
- **status transitions**: IDLE → PROCESSING → COMPLETED across concurrent terminals
- **handoff** (blocking): inbox delivery triggers supervisor state transition

## Known Limitations

### Project-local `opencode.json` override

OpenCode's config merge precedence places a project-local `opencode.json` in the current working directory **above** `OPENCODE_CONFIG` (the CAO-managed file). If you `cao launch` in a directory that has its own `opencode.json` with conflicting `agent.<name>.tools` or `tools` entries, CAO's MCP wiring can be silently overridden for that agent.

**Workaround:** remove or rename the project-local `opencode.json` before launching CAO, or move it under `.opencode/` (a subdirectory OpenCode also searches but at a lower priority level).

### Scrolling enters tmux copy mode

When you scroll (mouse wheel or trackpad) inside a CAO-managed OpenCode terminal, tmux enters copy mode instead of scrolling the TUI conversation history. This is intentional.

CAO launches OpenCode with `OPENCODE_DISABLE_MOUSE=1`, which prevents OpenCode from requesting application mouse-reporting mode (`\x1b[?1000h`). Without that request, tmux does not forward scroll events to the OpenCode process — it intercepts them and enters copy mode instead.

The reason for this trade-off: if OpenCode owned scroll events, scrolling the conversation history would move the completion marker (`▣ <agent> · <model> · Ns`) off screen. The footer (`ctrl+p commands`, `esc interrupt`) is pinned to the bottom of the TUI and remains visible regardless of scroll position, so IDLE and PROCESSING detection are unaffected. But COMPLETED detection requires both the completion marker and the idle footer to be present simultaneously in the captured frame — if the marker has scrolled away, CAO never detects COMPLETED even after the agent finishes. Disabling mouse keeps the frame locked to the most recent render.

Press `q` or `Escape` to exit copy mode. If you need to read earlier conversation history, use the `get_output` API endpoint or the `/terminals/<id>/output` endpoint to retrieve the full captured log.

### `opencode.json` concurrent writes

Parallel `cao install --provider opencode_cli` invocations (e.g., from a batch script) can race on the shared `~/.aws/opencode/opencode.json` file. The second writer may clobber the first's agent entry. **Sequential installs are safe.** File locking is deferred to a future release.

## Troubleshooting

### First-launch blank TUI (5–30 seconds)

OpenCode installs `@opencode-ai/plugin` into `~/.aws/opencode/node_modules/` on the first launch. The terminal will appear blank until `npm install` completes. CAO's 120-second initialization timeout covers this automatically.

To pre-populate `node_modules/` before the first CAO launch (optional):
```bash
OPENCODE_CONFIG_DIR=~/.aws/opencode opencode --help
```

### "Unknown provider" error from the server

Ensure the CAO server running on port 9889 is the **dev version**, not the pre-installed binary:
```bash
# Kill any stale installed binary
pkill -f 'cao-server'
# Start the dev server
uv run cao-server
```

### Authentication / model errors

OpenCode itself handles model authentication. Verify your credentials are set for the model provider you want to use. Check `~/.config/opencode/opencode.json` (your personal config) for provider API keys, or set them via environment variables before launching.

### Permission prompt blocking an automated flow

CAO emits only `allow` or `deny` in the permission frontmatter, so `△ Permission required` should not appear for CAO-managed tools. If it does:
1. Verify the profile's `allowedTools` / `role` grants the tool in question and reinstall — CAO translates allowed tools directly to `permission: allow`.
2. If the prompt comes from a tool outside CAO's vocabulary, respond to it manually in the tmux window, or use `--yolo` to disable all restrictions **(DANGEROUS — allows any command including `aws`, `rm`, `curl`)**.

### Status stuck as `PROCESSING`

This can happen if:
- OpenCode launched but the TUI hasn't painted yet (transient — the poller recovers)
- A `node_modules` install is still in progress (wait up to 120s)
- The `opencode` binary isn't on PATH in the tmux window's shell (check `echo $PATH` inside tmux)
47 changes: 47 additions & 0 deletions src/cli_agent_orchestrator/cli/commands/install.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,27 @@
DEFAULT_PROVIDER,
KIRO_AGENTS_DIR,
LOCAL_AGENT_STORE_DIR,
OPENCODE_AGENTS_DIR,
PROVIDERS,
Q_AGENTS_DIR,
SKILLS_DIR,
)
from cli_agent_orchestrator.models.copilot_agent import CopilotAgentConfig
from cli_agent_orchestrator.models.kiro_agent import KiroAgentConfig
from cli_agent_orchestrator.models.opencode_agent import OpenCodeAgentConfig
from cli_agent_orchestrator.models.provider import ProviderType
from cli_agent_orchestrator.models.q_agent import QAgentConfig
from cli_agent_orchestrator.utils.agent_profiles import parse_agent_profile_text
from cli_agent_orchestrator.utils.env import resolve_env_vars, set_env_var
from cli_agent_orchestrator.utils.opencode_config import (
ensure_skills_symlink,
remove_agent_tools,
to_opencode_agent_id,
translate_mcp_server_config,
upsert_agent_tools,
upsert_mcp_server,
)
from cli_agent_orchestrator.utils.opencode_permissions import cao_tools_to_opencode_permission
from cli_agent_orchestrator.utils.skill_injection import compose_agent_prompt


Expand Down Expand Up @@ -250,6 +261,42 @@ def install(agent_source: str, provider: str, env_vars: tuple[str, ...]):
)
agent_file.write_text(frontmatter.dumps(agent_post), encoding="utf-8")

elif provider == ProviderType.OPENCODE_CLI.value:
OPENCODE_AGENTS_DIR.mkdir(parents=True, exist_ok=True)
ensure_skills_symlink()
# Use the raw profile prompt as the body — skills are delivered natively
# via OpenCode's skill tool through the OPENCODE_CONFIG_DIR/skills symlink.
# compose_agent_prompt is NOT called here so the skill catalog stays out
# of the system prompt.
body = profile.system_prompt or profile.prompt or ""
agent_config = OpenCodeAgentConfig(
description=profile.description,
mode="all",
permission=cao_tools_to_opencode_permission(allowed_tools),
)
agent_id = to_opencode_agent_id(profile.name)
agent_file = OPENCODE_AGENTS_DIR / f"{agent_id}.md"
agent_post = frontmatter.Post(
body.rstrip() if body else "",
**agent_config.model_dump(exclude_none=True),
)
agent_file.write_text(frontmatter.dumps(agent_post), encoding="utf-8")

# Upsert MCP server declarations and per-agent tool gating into opencode.json.
# When a profile no longer declares MCP servers, explicitly strip any stale
# agent.<id>.tools entry from a previous install so revoked grants do not
# survive a reinstall.
if profile.mcpServers:
mcp_names = list(profile.mcpServers.keys())
for mcp_name, mcp_cfg in profile.mcpServers.items():
# Translate CAO's mcpServer format to OpenCode's opencode.json format
# before writing (type:stdio+command str+args → type:local+command list).
opencode_mcp_cfg = translate_mcp_server_config(dict(mcp_cfg))
upsert_mcp_server(mcp_name, opencode_mcp_cfg)
upsert_agent_tools(agent_id, mcp_names)
else:
remove_agent_tools(agent_id)

click.echo(f"✓ Agent '{profile.name}' installed successfully")
if env_vars:
click.echo(f"✓ Set {len(env_vars)} env var(s) in {CAO_ENV_FILE}")
Expand Down
1 change: 1 addition & 0 deletions src/cli_agent_orchestrator/cli/commands/launch.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
"gemini_cli",
"kimi_cli",
"kiro_cli",
"opencode_cli",
}


Expand Down
3 changes: 3 additions & 0 deletions src/cli_agent_orchestrator/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,9 @@
Q_AGENTS_DIR = Path.home() / ".aws" / "amazonq" / "cli-agents" # Q CLI agents
KIRO_AGENTS_DIR = Path(os.environ.get("CAO_AGENTS_DIR", str(Path.home() / ".kiro" / "agents")))
COPILOT_AGENTS_DIR = Path.home() / ".copilot" / "agents" # Copilot custom agents
OPENCODE_CONFIG_DIR = Path.home() / ".aws" / "opencode" # OpenCode CAO-managed config root
OPENCODE_AGENTS_DIR = OPENCODE_CONFIG_DIR / "agents" # OpenCode agent .md files
OPENCODE_CONFIG_FILE = OPENCODE_CONFIG_DIR / "opencode.json" # OpenCode MCP + tool gating config

# =============================================================================
# Database Configuration
Expand Down
Loading
Loading