Skip to content
Merged
Show file tree
Hide file tree
Changes from 12 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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ 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. Provider docs: [`docs/opencode-cli.md`](docs/opencode-cli.md). Design doc: [`docs/feat-opencode-provider-design.md`](docs/feat-opencode-provider-design.md).

### Changed

- **Launch prompt clarity + `--auto-approve`** — Redesign the `cao launch` confirmation prompt to show `Role` instead of `Blocked`, clearly distinguish `[Y]` / `[--auto-approve]` / `[--yolo]`, and add `--auto-approve` flag to skip the prompt without removing restrictions (for automated flows, scripts, and agent-to-agent launches)
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** | [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
95 changes: 95 additions & 0 deletions development_reports/phase_3_opencode_provider_runtime.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
# Phase 3 — OpenCode Provider Runtime

## Summary

Implemented the `OpenCodeCliProvider` class, registered it in the provider manager, and added it to the workspace-access guard in the `launch` command. 43 unit tests were written covering all acceptance criteria at 96% line coverage.

---

## Files Created

| File | Purpose |
|------|---------|
| `src/cli_agent_orchestrator/providers/opencode_cli.py` | Full provider implementation (332 lines) |
| `test/providers/test_opencode_cli_unit.py` | 43 unit tests (96% line coverage) |

## Files Modified

| File | Change |
|------|--------|
| `src/cli_agent_orchestrator/providers/manager.py` | Import + `elif` branch for `opencode_cli` |
| `src/cli_agent_orchestrator/cli/commands/launch.py` | Added `"opencode_cli"` to `PROVIDERS_REQUIRING_WORKSPACE_ACCESS` |

---

## Acceptance Criteria Disposition

### AC-1 — `OpenCodeCliProvider` subclasses `BaseProvider` with all required methods
**Satisfied.** The class implements `initialize()`, `get_status()`, `extract_last_message_from_script()`, `get_idle_pattern_for_log()`, `exit_cli()`, and `cleanup()`. The `paste_enter_count` property returns `1`.

### AC-2 — `initialize()` sends the correct inline-env launch command and waits for IDLE/COMPLETED with 120s timeout
**Satisfied.** `_build_launch_command()` emits all seven stability env vars (`OPENCODE_CONFIG`, `OPENCODE_CONFIG_DIR`, `OPENCODE_DISABLE_AUTOUPDATE=1`, `OPENCODE_DISABLE_MOUSE=1`, `OPENCODE_DISABLE_TERMINAL_TITLE=1`, `OPENCODE_CLIENT=cao`, `TERM=xterm-256color`) followed by `opencode [--agent <name>] [--model <name>]` built with `shlex.join`. The `wait_until_status` call uses a 120-second timeout to cover first-run `npm install` cold starts. Verified output:
```
OPENCODE_CONFIG=... OPENCODE_DISABLE_AUTOUPDATE=1 ... opencode --agent developer
OPENCODE_CONFIG=... OPENCODE_DISABLE_AUTOUPDATE=1 ... opencode --agent code-reviewer --model anthropic/claude-sonnet-4-6
```

### AC-3 — `get_status()` correctly classifies all five states
**Satisfied.** Priority-order detection (WAITING_USER_ANSWER → PROCESSING → COMPLETED → IDLE → ERROR) with line-level position guard for stale `esc interrupt` alt-screen remnants.

Key design decisions:
- **Line-level position guard**: During active processing, `esc interrupt` and `ctrl+p commands` appear on the *same* footer line. A stale alt-screen snapshot puts them on separate lines. The guard checks whether any idle-footer or completion-marker line appears *after* the line containing `esc interrupt` — if so, `esc interrupt` is stale.
- **`esc_is_stale` flag**: When the position guard fires, this flag allows the IDLE branch to match even though `esc interrupt` text is still present in the buffer.
- **COMPLETED requires no trailing `▣`**: After completion, the new user input bar shows a partial `▣ Build · Big Pickle` (no duration), which would make the IDLE-post-completion state look like a new incomplete turn. The COMPLETED check confirms no `▣` token follows the last full completion marker.

### AC-4 — `extract_last_message_from_script()` correctly extracts agent text
**Satisfied.** Algorithm:
1. Strip ANSI codes.
2. Find last full `COMPLETION_MARKER_PATTERN` match (requires `·…·…Ns` duration suffix).
3. Search for last `┃ ` *before* the completion marker (unanchored — TUI lines have leading spaces).
4. Extract between `┃ ` end and completion marker start.
5. Skip user-message lines (still using `┃` indent).
6. Strip `Thinking:` preamble lines.
7. Dedent 5-space agent indent.
8. Clean control characters and trailing whitespace.

Raises `ValueError` with descriptive messages for missing completion marker or missing user message.

### AC-5 — Provider registered in `manager.py` and `launch.py`
**Satisfied.** `ProviderManager.create_provider()` routes `"opencode_cli"` to `OpenCodeCliProvider` passing `model=model`. `PROVIDERS_REQUIRING_WORKSPACE_ACCESS` includes `"opencode_cli"`.

### AC-6 — 90%+ test coverage
**Satisfied.** 96% line coverage on `opencode_cli.py` (5 uncovered lines are unreachable exception paths in string cleaning).

---

## Smoke Test

Server-based launch was not used (port 9889 reserved per user instruction). Provider dispatch was verified directly:

```python
from cli_agent_orchestrator.providers.manager import ProviderManager
pm = ProviderManager()
p = pm.create_provider('opencode_cli', 'test-terminal-1', 'cao-session', 'win-1', 'developer')
# → Provider type: OpenCodeCliProvider
# → launch command: OPENCODE_CONFIG=... opencode --agent developer
```

```python
p2 = pm.create_provider('opencode_cli', 't2', 's', 'w', 'code-reviewer', model='anthropic/claude-sonnet-4-6')
# → launch command: ... opencode --agent code-reviewer --model anthropic/claude-sonnet-4-6
```

The `cao install developer --provider opencode_cli` CLI command was also verified in a prior session:
```
✓ Agent 'developer' installed successfully
✓ opencode_cli agent: /home/bajablast69/.aws/opencode_cli/agents/developer.md
```

---

## Design Decisions and Trade-offs

- **`--model` only via launch flag, never frontmatter** (§3.1 exception): OpenCode frontmatter does not support a `model:` key that overrides the active provider; passing it via `--model` at the CLI level is the correct path. The `model` parameter flows from `ProviderManager.create_provider()` → `OpenCodeCliProvider.__init__()` → `_build_launch_command()`.
- **Unanchored `┃ ` search in `extract_last_message_from_script`**: The module-level `USER_MESSAGE_PATTERN = r"^┃\s{2}"` uses `^` anchoring suitable for full-line matching, but TUI output lines have variable leading spaces (e.g. ` ┃ say hello`). The extraction function uses `r"┃\s{2}"` without anchoring to handle this.
- **Completion-marker-first extraction order**: Finding the completion marker first and searching backwards for `┃ ` avoids the bottom input-box trap — the last `┃ ` in a completed screen is the new input-box agent/model header which appears *after* the completion marker. Restricting the `┃ ` search to `clean[:last_completion.start()]` cleanly avoids it.
87 changes: 87 additions & 0 deletions development_reports/phase_4_opencode_e2e_and_docs.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
# Phase 4 — End-to-End Validation and Documentation

## Summary

Added the OpenCode CLI e2e test class (with `require_opencode` fixture), created provider documentation, updated the README provider table, and added a CHANGELOG entry. All unit tests (1434) continue to pass.

---

## Files Created

| File | Purpose |
|------|---------|
| `docs/opencode-cli.md` | Provider documentation (prerequisites, quick start, config isolation, permission/tool mapping, MCP wiring, known limitations, troubleshooting) |
| `development_reports/phase_4_opencode_e2e_and_docs.md` | This report |

## Files Modified

| File | Change |
|------|--------|
| `test/e2e/conftest.py` | Added `require_opencode` fixture (skips if `opencode` not on PATH) |
| `test/e2e/test_assign.py` | Added `TestOpenCodeCliAssign` class with three test methods |
| `README.md` | Added `opencode_cli` row to provider table; added `opencode_cli` to `cao launch` examples |
| `CHANGELOG.md` | Added Unreleased entry announcing OpenCode CLI provider |

---

## Acceptance Criteria Disposition

### AC-1 — E2E test passes: `uv run pytest -m e2e test/e2e/test_assign.py -k opencode`

**Satisfied — all 3 tests passed.**

The dev server was started on port 9888 (`CAO_API_PORT=9888`) leaving the old installed server on port 9889 untouched. Three agent profiles were installed with `--auto-approve` before the run.

```
test/e2e/test_assign.py::TestOpenCodeCliAssign::test_assign_data_analyst PASSED
test/e2e/test_assign.py::TestOpenCodeCliAssign::test_assign_report_generator PASSED
test/e2e/test_assign.py::TestOpenCodeCliAssign::test_assign_with_callback PASSED
```

`test_assign_data_analyst` passed in the first run. `test_assign_report_generator` and `test_assign_with_callback` passed together in 2 minutes 4 seconds (`2 passed in 124.53s`).

**Phase 3 regression discovered and fixed (commit 4c30661):**

The live e2e revealed that `cao install --provider opencode_cli` was writing OpenCode's `opencode.json` with the raw CAO MCP server format (`type: "stdio"`, `command` as string, `args` as separate list) instead of OpenCode's format (`type: "local"`, `command` as a combined list, `enabled: true`). OpenCode rejected the config with: `Configuration is invalid: Invalid input mcp.cao-mcp-server`.

Fix: `translate_mcp_server_config()` added to `utils/opencode_config.py`; the opencode_cli install branch now calls it before `upsert_mcp_server()`. Six unit tests added. The opencode.json was regenerated with the correct format after the fix.

### AC-2 — `docs/opencode-cli.md` exists with required sections

**Satisfied.** Document includes:
- Prerequisites (opencode binary, Node.js, first-launch npm install side effect, 5–30s delay)
- Launch examples (basic, `--auto-approve`, `--model`, `--yolo`, HTTP API)
- Permission/tool mapping reference (summary table + pointer to §9 of design doc)
- Known limitations: §10.3 project-local `opencode.json` override, concurrent-write race
- Troubleshooting: first-launch blank TUI, stale installed server, auth errors, permission prompt, PROCESSING stuck

### AC-3 — `README.md` provider table contains `opencode_cli` row

**Satisfied.** Row added after GitHub Copilot CLI:
```
| **OpenCode CLI** | [Provider docs](docs/opencode-cli.md) · [Installation](https://opencode.ai) | Per-model API key |
```
Also added `cao launch --agents code_supervisor --provider opencode_cli` to the launch examples section.

### AC-4 — `CHANGELOG.md` Unreleased section has the new entry

**Satisfied.** Entry added under `## [Unreleased] → ### Added`, following the style of the Copilot CLI / Gemini CLI entries in `## [2.0.0]`. References both `docs/opencode-cli.md` and `docs/feat-opencode-provider-design.md`.

---

## Test Run Summary

Unit tests (post-regression fix):
```
uv run pytest test/ --ignore=test/e2e -q
1440 passed, 16 skipped, 4 warnings in 44.13s
```

E2E tests (`CAO_API_PORT=9888 uv run pytest -m e2e test/e2e/test_assign.py -k opencode -v`):
```
test_assign_data_analyst PASSED
test_assign_report_generator PASSED (124.53s combined with test below)
test_assign_with_callback PASSED
```

No regressions from Phase 4 changes; one Phase 3 regression found and fixed (see AC-1 above).
Loading