-
Notifications
You must be signed in to change notification settings - Fork 6.7k
Description
Description
The defaultAgent() function does not properly filter agents by their mode and visibility settings. This causes subagents (like explore) and hidden agents (like compaction) to be incorrectly selected as the default agent in ACP and headless (run) modes.
Root causes:
Agent.defaultAgent()returns the first agent fromstate()without checking if it's a subagent or hidden- ACP session init does not persist the resolved default mode, causing
prompt()to use stale/wrong mode
Plugins
N/A
OpenCode version
0.0.0-dev-202601151329
Steps to reproduce
fresh config -> disable build agent -> set a custom default agent -> execute opencode run "echo back" -> it uses plan agent and tries to generate a plan
example jsonc config:
opencode run
current version output:
$ opencode run "echo back"
| Write .opencode/plans/1768489599174-witty-cabin.mdfixed version output:
$ bun run dev run "echo back"
YODA!
echo backopencode acp
// acp-run.ts
import { spawn } from "bun"
const prompt = process.argv[3] || "echo back"
const testVersion = process.argv.find((arg) => arg.startsWith("--test-version="))?.split("=")[1] || "fixed"
console.log(`Using ACP version: ${testVersion}`)
const acpCommand =
testVersion === "current"
? ["opencode", "acp"]
: ["bun", "run", "--cwd", "./packages/opencode/", "--conditions=browser", "src/index.ts", "acp"]
const acp = spawn(acpCommand, {
stdin: "pipe",
stdout: "pipe",
stderr: "ignore",
})
const send = (msg: object) => acp.stdin.write(JSON.stringify(msg) + "\n")
let id = 0
const rpc = (method: string, params: object) => send({ jsonrpc: "2.0", id: ++id, method, params })
// Initialize
rpc("initialize", { protocolVersion: 1, clientCapabilities: {} })
// Create session
rpc("session/new", { cwd: process.cwd(), mcpServers: [] })
// Read responses
const reader = acp.stdout.getReader()
const decoder = new TextDecoder()
let buffer = ""
let sessionId = ""
while (true) {
const { done, value } = await reader.read()
if (done) break
buffer += decoder.decode(value)
const lines = buffer.split("\n")
buffer = lines.pop() || ""
for (const line of lines) {
if (!line) continue
const msg = JSON.parse(line)
// Get session ID from session/new response
if (msg.result?.sessionId) {
sessionId = msg.result.sessionId
rpc("session/prompt", { sessionId, prompt: [{ type: "text", text: prompt }] })
}
// Print agent message chunks
if (msg.params?.update?.sessionUpdate === "agent_message_chunk") {
process.stdout.write(msg.params.update.content.text || "")
}
// Exit on completion
if (msg.result?.stopReason === "end_turn") {
console.log()
process.exit(0)
}
}
}In the project root, checked out to the fix branch in fix(agent): default agent selection in acp and headless modes #8678, execute this script in two different modes:
current opencode version, as-is:
[/path/to/opencode/fix-default-agent-selection]
$ bun acp-run.ts --test-version=current
Using ACP version: current
I'll help you understand the codebase and create a plan. Let me start by exploring the repository structure to understand what we're working with.
I'll help you understand the overall structure of this codebase. Let me start by exploring the repository structure and key files.
Now let me explore the main packages directory to understand the architecture:
...fixed version:
[/path/to/opencode/fix-default-agent-selection]
$ bun acp-run.ts --test-version=fixed
Using ACP version: fixed
YODA! echo back%Screenshot and/or share link
N/A
Operating System
macOS Tahoe 26.3 arm64
Terminal
ghostty 1.2.3
tmux next-3.5
{ "$schema": "https://opencode.ai/config.json", "default_agent": "yoda", "agent": { "build": { "disable": true, }, "yoda": { "prompt": "say YODA! before every response.", }, }, }