feat: add OpenCode as a supported agent#36
Conversation
Co-authored-by: Copilot <copilot@github.com>
OpenCode reads MCP entries from {mcp: {<name>: {type, command[],
environment}}}, but the adapter defaulted to McpFormat::McpServers,
so cross-agent MCP deploys silently wrote a Claude-style payload
that OpenCode never loaded — and the disable→enable secret-redaction
path missed OpenCode entries' "environment" block, leaving secrets
unredacted in the SQLite DB.
- Add McpFormat::Opencode variant + deploy_mcp_server_opencode helper
matching opencode.ai/config.json (McpLocalConfig: type/command[]/
environment, additionalProperties: false).
- Centralize the JSON-format top-key dispatch in json_top_key(),
collapsing 3-way inline duplication in remove/restore/read and
forcing explicit handling of every JSON variant via unreachable!.
- Add MCP_ENV_KEYS const so redact_mcp_env and the restore-time
warning handle both "env" and "environment" without threading
McpFormat through every caller.
- Tests: 5 deployer tests covering schema invariants, lifecycle
(remove/restore/read), and a deploy → adapter-read cross-module
roundtrip; 1 redact test for OpenCode's "environment" key. All
356 hk-core tests pass.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ettings discovery OpenCode supports project-level config (opencode.json, AGENTS.md, .opencode/ commands/, .opencode/plugins/) and the `enabled: false` MCP toggle, but the adapter left every project_* method at the trait default and didn't honor the enabled field. HarnessKit's project scanner therefore couldn't see any OpenCode project config, and explicitly-disabled MCP servers showed as active in the UI. - Honor enabled:false in parse_local_mcp_entry, mirroring the type-filter pattern. - Override project_rules_patterns / project_settings_patterns / project_workflow_patterns / project_mcp_config_relpath / project_plugin_dirs with paths verified against opencode.ai/docs (rules, config, commands, plugins, mcp-servers). - Extend global_settings_files: include opencode.jsonc, modes/*.md, and themes/*.json. tools/*.ts is excluded — it's code, like plugins, not settings. - Generalize markdown_files -> files_with_ext(dir, ext) so the new themes scan reuses the helper instead of duplicating the read_dir block. - Reorder project_skill_dirs to cluster with the other project_* methods, matching the trait declaration order used by every other adapter. - Tests: enabled-filter pinned with three states, project paths pinned to literal upstream values, settings test asserts both inclusions and exclusions (tools/lint.ts, *.txt). 13 OpenCode-related tests, 358 total. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The original PR added a multi-check `detect()` (dir exists OR config file exists OR `which opencode`) — more permissive than the other 7 adapters which all use `base_dir().exists()` alone. Drop the extra checks: a `which` hit without a config dir surfaces an agent that has nothing for HarnessKit to manage, which is a UX false positive. Test renamed `detect_accepts_base_dir_or_config_file` → `detect_requires_base_dir`, now covering both the negative (empty home → not detected) and positive (base_dir present → detected) cases. 13 OpenCode tests, 358 total, all green. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- deployer.rs: blank doc-line separators between the bullet list and the trailing paragraph in deploy_mcp_server_opencode's doc comment (clippy doc-list-indent, introduced by b39547b). - manager.rs:457: collapse the nested `if let Some(path)` / `if let Some(manifest) = plugin_toggle_target(path)` pair into a single let-chain (clippy collapsible_if). The nesting was created by 68a185c when the manifest-finding logic was extracted into plugin_toggle_target() — what was an if-then-for-then-if structure became directly nested ifs. Pattern matches existing let-chains in claude.rs. `cargo clippy -p hk-core --all-targets` is now warning-free. 358 tests pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two follow-up bugs discovered during end-to-end testing of PR RealZST#36: 1) MCP enabled state was not surfaced. OpenCode is the only adapter whose schema has a per-entry `enabled: false` flag. Our earlier filter dropped those entries entirely, so an MCP that the user disabled by editing opencode.json directly disappeared from HarnessKit's UI rather than showing as visible-but-disabled (the consistent UX with HK's own toggle-disabled MCPs from any agent). - Add `enabled: bool` to `McpServerEntry`. The 7 other adapters set `true` unconditionally (their formats lack a per-entry disable concept); OpenCode reads the field from JSON, defaulting `true`. - Drop the `return None` filter in `parse_local_mcp_entry` that was hiding `enabled: false` entries. - Scanner uses `server.enabled` instead of hardcoded `true` when building Extension records (both global and project-scope MCP scans). - Test renamed read_mcp_servers_skips_explicitly_disabled → read_mcp_servers_surfaces_disabled_state and inverted to assert entries surface with their correct enabled flag. 2) Project detection didn't recognize OpenCode projects. The marker check (`.claude/`, `.mcp.json`, etc.) was hardcoded in three places — `scanner::discover_projects_recursive`, hk-desktop's `add_project`, and hk-web's `add_project` — and missing `.opencode/`, `opencode.json`, `opencode.jsonc`. - Add `ProjectMarker` enum and `project_markers()` trait method to `AgentAdapter`. Each adapter declares its own markers in its own file — single source of truth, parallel to `project_skill_dirs()` and `project_*_patterns()`. - Add `scanner::is_project_dir(path)` that iterates every adapter's markers; replace the three duplicated inline lists with a single call to this helper. - Add regression test test_every_active_adapter_declares_project_markers so a future adapter that forgets to declare markers fails the build. - Extend test_discover_projects with three OpenCode cases (`.opencode/`, `opencode.json`, `opencode.jsonc`). 359 hk-core tests pass, clippy clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The original PR added the opencode agent to the Rust adapter layer but left two frontend gaps that silently degraded the UX: 1) src/lib/agent-capabilities.ts — no opencode entry. canInstallAtScope() fell through to `?? false`, blocking project-scope skill install in the Marketplace dialog even though the adapter declares project_skill_dirs and project_mcp_config_relpath. Add `opencode: new Set(["skill", "mcp"])` to match the adapter's project-level capabilities (hook unsupported — HookFormat::None). 2) src/index.css — `--agent-opencode` was defined in only 2 of 6 theme variants and the Tailwind `--color-agent-opencode` mapping was missing entirely. Result: the bg-agent-opencode / text-agent-opencode / border-agent-opencode Tailwind classes used by extension-filters.tsx silently rendered no color in every theme. Add the variable to the 4 missing theme blocks (claude light/dark, tiesen light/dark) and the Tailwind mapping in the @theme block. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The original PR's OpencodeMascot scatter animation flew tiles ±20px cardinal / ±16px diagonal, escaping the agent card boundary in the Overview page (visible as dashes outside the card on click). - Reduce scatter amplitude to ~65% (cardinal ±13px, diagonal ±10px) so the explosion stays visually coherent without going as far. Timing, cubic-bezier, rotation, and overshoot are unchanged. - Add `opencode` to the agent-card overflow-hidden list (alongside claude/codex/antigravity) as a safety clip — any residual motion that exceeds the boundary is hidden rather than leaking onto neighbors. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
Thanks for the PR! Pushed a few commits:
A few items I roadmapped for follow-up: npm plugins from Thanks again! Merged. |
👍🏻 Thanks for the thorough review and follow-up fixes. Learned a lot from the changes. |
…drift (#44) The "Install to Agent" mock card on the marketplace preview step was missing OpenCode because PR #36 didn't update an inline hardcoded agent list. Three agent enumerations in this file were duplicated from AGENT_ORDER, so any future agent addition could silently drift again. - Drop the local AGENTS const and inline {id,label} lists; derive all three sites from AGENT_ORDER + agentDisplayName(). - Re-key FLOAT_DELAYS and SCATTER_POSITIONS as Record<(typeof AGENT_ORDER)[number], ...> so adding an agent forces TS to flag missing entries rather than producing undefined at runtime. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Summary
This PR adds OpenCode as a first-class agent in HarnessKit.
What Changed
Scope
This PR intentionally includes:
This PR intentionally does not include:
Screenshots