diff --git a/docs/SMOKE.md b/docs/SMOKE.md index 4035634..5601eb5 100644 --- a/docs/SMOKE.md +++ b/docs/SMOKE.md @@ -106,9 +106,14 @@ each run keeps a copy next to its output as `..mcp-stderr`. output but still deletes every copied agent config. - Tools are auto-approved for the run (Claude `dontAsk` with `mcp__idx__*`, Codex `default_tools_approval_mode="approve"`); Claude and pi - also get no built-in tools, Hermes only the `idx` toolset, and opencode a - permission that denies everything but `idx_*`, so they can only answer - through MCP. + also get no built-in tools, Hermes only the `idx` toolset, and opencode + permissions that deny its shell, file, and web tools, so they can only + answer through MCP. +- opencode 1 (`opencode-ai`) and 2 (`@opencode/cli`) are both supported; the + harness reads `opencode --version` and writes the matching config. Version + 2 runs with `--standalone`, off the shared background service. A wrapper + that pins `XDG_*` paths (such as a Nix `opencode2` wrapper) defeats the + isolated config dirs; put the plain binary first on PATH. - `IDX_AGENT_MODEL_` picks a model (for example `IDX_AGENT_MODEL_CLAUDE=sonnet`). `IDX_AGENT_EXEC_PREFIX` prefixes every agent command, for hosts that inject API keys through a wrapper. @@ -118,4 +123,5 @@ each run keeps a copy next to its output as `..mcp-stderr`. measure pass rates. `--keep` keeps raw agent output and `results.jsonl`. - `nix develop .#agents` adds Claude Code, Codex, and opencode from nixpkgs after your PATH, so installed versions win. pi and Hermes Agent must be - installed on the host. + installed on the host. The nixpkgs opencode (1.2.17) lacks `run --pure`; + use a current opencode 1 or 2. diff --git a/tests/agents/agent-smoke.sh b/tests/agents/agent-smoke.sh index 55472f6..b8f4728 100755 --- a/tests/agents/agent-smoke.sh +++ b/tests/agents/agent-smoke.sh @@ -273,19 +273,39 @@ run_hermes() { } setup_opencode() { + local version mkdir -p "$work/opencode/cwd" - jq -n --argjson s "$server_json" \ - '{"$schema": "https://opencode.ai/config.json", - mcp: {idx: {type: "local", command: ([$s.command] + $s.args), environment: $s.env, enabled: true}}, - permission: {"*": "deny", "idx_*": "allow"}}' \ - > "$work/opencode/cwd/opencode.json" + version="$("${exec_prefix[@]}" opencode --version 2>/dev/null | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -n1)" + if [[ "${version%%.*}" -ge 2 ]]; then + # OpenCode 2 (@opencode/cli). A denied call ends the run, and models + # use Code Mode's `execute` while the server connects, so deny only + # the tools that could route around it. --standalone keeps the run + # off the shared background service. + jq -n --argjson s "$server_json" \ + '{mcp: {servers: {idx: {type: "local", command: ([$s.command] + $s.args), + environment: $s.env, codemode: false}}}, + permissions: ((["read", "edit", "glob", "grep", "shell", "subagent", "skill", + "question", "webfetch", "websearch", "external_directory"] + | map({action: ., resource: "*", effect: "deny"})) + + [{action: "idx_*", resource: "*", effect: "allow"}])}' \ + > "$work/opencode/cwd/opencode.json" + echo --standalone > "$work/opencode/run-flag" + else + jq -n --argjson s "$server_json" \ + '{"$schema": "https://opencode.ai/config.json", + mcp: {idx: {type: "local", command: ([$s.command] + $s.args), environment: $s.env, enabled: true}}, + permission: {"*": "deny", "idx_*": "allow"}}' \ + > "$work/opencode/cwd/opencode.json" + echo --pure > "$work/opencode/run-flag" + fi } run_opencode() { - local prompt="$1" out="$2" model=() home="$work/opencode" + local prompt="$1" out="$2" model=() home="$work/opencode" flag [[ -n "${IDX_AGENT_MODEL_OPENCODE:-}" ]] && model=(-m "$IDX_AGENT_MODEL_OPENCODE") + flag="$(< "$home/run-flag")" (cd "$home/cwd" && XDG_CONFIG_HOME="$home/config" XDG_DATA_HOME="$home/data" \ XDG_CACHE_HOME="$home/cache" XDG_STATE_HOME="$home/state" \ - "${exec_prefix[@]}" opencode run --pure --format json "${model[@]}" "$prompt" < /dev/null > "$out.raw") + "${exec_prefix[@]}" opencode run "$flag" --format json "${model[@]}" "$prompt" < /dev/null > "$out.raw") jq -c 'select(.type=="tool_use") | .part | select(.tool | startswith("idx_")) | select(.state.status=="completed") | {name: (.tool | ltrimstr("idx_")), args: .state.input}' "$out.raw" > "$out.calls" jq -r 'select(.type=="text") | .part.text' "$out.raw" > "$out.answer"