summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorSercan Sagman <[email protected]>2026-01-16 02:09:19 +0300
committerGitHub <[email protected]>2026-01-15 17:09:19 -0600
commit81983d4a2e4506211db8482b8e4fe0384863bf85 (patch)
treed4863e352ef56023bea982064be64f50466c7716
parent7443b9929556d5c86a89df3df95af720bd82cd80 (diff)
downloadopencode-81983d4a2e4506211db8482b8e4fe0384863bf85.tar.gz
opencode-81983d4a2e4506211db8482b8e4fe0384863bf85.zip
fix(agent): default agent selection in acp and headless mode (#8678)
Signed-off-by: assagman <[email protected]>
-rw-r--r--packages/opencode/src/acp/agent.ts3
-rw-r--r--packages/opencode/src/agent/agent.ts15
-rw-r--r--packages/opencode/test/agent/agent.test.ts124
3 files changed, 141 insertions, 1 deletions
diff --git a/packages/opencode/src/acp/agent.ts b/packages/opencode/src/acp/agent.ts
index ebd65bb26..f8792393c 100644
--- a/packages/opencode/src/acp/agent.ts
+++ b/packages/opencode/src/acp/agent.ts
@@ -731,6 +731,9 @@ export namespace ACP {
const defaultAgentName = await AgentModule.defaultAgent()
const currentModeId = availableModes.find((m) => m.name === defaultAgentName)?.id ?? availableModes[0].id
+ // Persist the default mode so prompt() uses it immediately
+ this.sessionManager.setMode(sessionId, currentModeId)
+
const mcpServers: Record<string, Config.Mcp> = {}
for (const server of params.mcpServers) {
if ("type" in server) {
diff --git a/packages/opencode/src/agent/agent.ts b/packages/opencode/src/agent/agent.ts
index 648750919..0725933d7 100644
--- a/packages/opencode/src/agent/agent.ts
+++ b/packages/opencode/src/agent/agent.ts
@@ -255,7 +255,20 @@ export namespace Agent {
}
export async function defaultAgent() {
- return state().then((x) => Object.keys(x)[0])
+ const cfg = await Config.get()
+ const agents = await state()
+
+ if (cfg.default_agent) {
+ const agent = agents[cfg.default_agent]
+ if (!agent) throw new Error(`default agent "${cfg.default_agent}" not found`)
+ if (agent.mode === "subagent") throw new Error(`default agent "${cfg.default_agent}" is a subagent`)
+ if (agent.hidden === true) throw new Error(`default agent "${cfg.default_agent}" is hidden`)
+ return agent.name
+ }
+
+ const primaryVisible = Object.values(agents).find((a) => a.mode !== "subagent" && a.hidden !== true)
+ if (!primaryVisible) throw new Error("no primary visible agent found")
+ return primaryVisible.name
}
export async function generate(input: { description: string; model?: { providerID: string; modelID: string } }) {
diff --git a/packages/opencode/test/agent/agent.test.ts b/packages/opencode/test/agent/agent.test.ts
index 624655112..1ff303b76 100644
--- a/packages/opencode/test/agent/agent.test.ts
+++ b/packages/opencode/test/agent/agent.test.ts
@@ -512,3 +512,127 @@ test("explicit Truncate.DIR deny is respected", async () => {
},
})
})
+
+test("defaultAgent returns build when no default_agent config", async () => {
+ await using tmp = await tmpdir()
+ await Instance.provide({
+ directory: tmp.path,
+ fn: async () => {
+ const agent = await Agent.defaultAgent()
+ expect(agent).toBe("build")
+ },
+ })
+})
+
+test("defaultAgent respects default_agent config set to plan", async () => {
+ await using tmp = await tmpdir({
+ config: {
+ default_agent: "plan",
+ },
+ })
+ await Instance.provide({
+ directory: tmp.path,
+ fn: async () => {
+ const agent = await Agent.defaultAgent()
+ expect(agent).toBe("plan")
+ },
+ })
+})
+
+test("defaultAgent respects default_agent config set to custom agent with mode all", async () => {
+ await using tmp = await tmpdir({
+ config: {
+ default_agent: "my_custom",
+ agent: {
+ my_custom: {
+ description: "My custom agent",
+ },
+ },
+ },
+ })
+ await Instance.provide({
+ directory: tmp.path,
+ fn: async () => {
+ const agent = await Agent.defaultAgent()
+ expect(agent).toBe("my_custom")
+ },
+ })
+})
+
+test("defaultAgent throws when default_agent points to subagent", async () => {
+ await using tmp = await tmpdir({
+ config: {
+ default_agent: "explore",
+ },
+ })
+ await Instance.provide({
+ directory: tmp.path,
+ fn: async () => {
+ await expect(Agent.defaultAgent()).rejects.toThrow('default agent "explore" is a subagent')
+ },
+ })
+})
+
+test("defaultAgent throws when default_agent points to hidden agent", async () => {
+ await using tmp = await tmpdir({
+ config: {
+ default_agent: "compaction",
+ },
+ })
+ await Instance.provide({
+ directory: tmp.path,
+ fn: async () => {
+ await expect(Agent.defaultAgent()).rejects.toThrow('default agent "compaction" is hidden')
+ },
+ })
+})
+
+test("defaultAgent throws when default_agent points to non-existent agent", async () => {
+ await using tmp = await tmpdir({
+ config: {
+ default_agent: "does_not_exist",
+ },
+ })
+ await Instance.provide({
+ directory: tmp.path,
+ fn: async () => {
+ await expect(Agent.defaultAgent()).rejects.toThrow('default agent "does_not_exist" not found')
+ },
+ })
+})
+
+test("defaultAgent returns plan when build is disabled and default_agent not set", async () => {
+ await using tmp = await tmpdir({
+ config: {
+ agent: {
+ build: { disable: true },
+ },
+ },
+ })
+ await Instance.provide({
+ directory: tmp.path,
+ fn: async () => {
+ const agent = await Agent.defaultAgent()
+ // build is disabled, so it should return plan (next primary agent)
+ expect(agent).toBe("plan")
+ },
+ })
+})
+
+test("defaultAgent throws when all primary agents are disabled", async () => {
+ await using tmp = await tmpdir({
+ config: {
+ agent: {
+ build: { disable: true },
+ plan: { disable: true },
+ },
+ },
+ })
+ await Instance.provide({
+ directory: tmp.path,
+ fn: async () => {
+ // build and plan are disabled, no primary-capable agents remain
+ await expect(Agent.defaultAgent()).rejects.toThrow("no primary visible agent found")
+ },
+ })
+})