From 7b12bc5f377d9c362700aff8d24d89042a10a295 Mon Sep 17 00:00:00 2001 From: assagman Date: Thu, 15 Jan 2026 16:17:51 +0300 Subject: [PATCH 1/5] fix(agent): filter subagents and hidden agents from defaultAgent selection Signed-off-by: assagman --- packages/opencode/src/agent/agent.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/opencode/src/agent/agent.ts b/packages/opencode/src/agent/agent.ts index 64875091916..f7e940a2874 100644 --- a/packages/opencode/src/agent/agent.ts +++ b/packages/opencode/src/agent/agent.ts @@ -255,7 +255,14 @@ export namespace Agent { } export async function defaultAgent() { - return state().then((x) => Object.keys(x)[0]) + const agents = await list() + const primary = agents.find((a) => a.mode !== "subagent" && a.hidden !== true) + if (primary) return primary.name + + const visible = agents.find((a) => a.hidden !== true) + if (visible) return visible.name + + return "build" } export async function generate(input: { description: string; model?: { providerID: string; modelID: string } }) { From 94ff788d2df9447a40ec01595e05693347cb456a Mon Sep 17 00:00:00 2001 From: assagman Date: Thu, 15 Jan 2026 16:17:55 +0300 Subject: [PATCH 2/5] fix(acp): persist default mode on session init Signed-off-by: assagman --- packages/opencode/src/acp/agent.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/opencode/src/acp/agent.ts b/packages/opencode/src/acp/agent.ts index ebd65bb26da..f8792393c60 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 = {} for (const server of params.mcpServers) { if ("type" in server) { From 9dc3746992378fdbd4ccb7fc04fe5a7591458f6c Mon Sep 17 00:00:00 2001 From: assagman Date: Thu, 15 Jan 2026 16:17:59 +0300 Subject: [PATCH 3/5] test(agent): add coverage for defaultAgent selection logic Signed-off-by: assagman --- packages/opencode/test/agent/agent.test.ts | 115 +++++++++++++++++++++ 1 file changed, 115 insertions(+) diff --git a/packages/opencode/test/agent/agent.test.ts b/packages/opencode/test/agent/agent.test.ts index 624655112bb..133e863253b 100644 --- a/packages/opencode/test/agent/agent.test.ts +++ b/packages/opencode/test/agent/agent.test.ts @@ -512,3 +512,118 @@ test("explicit Truncate.DIR deny is respected", async () => { }, }) }) + +// Tests for Agent.defaultAgent() + +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 falls back when default_agent points to subagent", async () => { + await using tmp = await tmpdir({ + config: { + default_agent: "explore", + }, + }) + await Instance.provide({ + directory: tmp.path, + fn: async () => { + const agent = await Agent.defaultAgent() + // explore is a subagent, so it should fall back to first primary-capable agent + expect(agent).not.toBe("explore") + expect(agent).toBe("build") + }, + }) +}) + +test("defaultAgent falls back 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 () => { + const agent = await Agent.defaultAgent() + // compaction is hidden, so it should fall back to first primary-capable agent + expect(agent).not.toBe("compaction") + expect(agent).toBe("build") + }, + }) +}) + +test("defaultAgent falls back 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 () => { + const agent = await Agent.defaultAgent() + expect(agent).toBe("build") + }, + }) +}) + +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") + }, + }) +}) From d558873ed352846fc3b98ffccb3e657fbc4368bf Mon Sep 17 00:00:00 2001 From: assagman Date: Thu, 15 Jan 2026 20:39:21 +0300 Subject: [PATCH 4/5] core: simplify defaultAgent to single fallback path Previously used 3-step fallback (primary -> visible -> build). Now uses single check: find primary+visible OR hardcoded 'build'. Same behavior, cleaner implementation. Signed-off-by: assagman --- packages/opencode/src/agent/agent.ts | 9 ++------- packages/opencode/test/agent/agent.test.ts | 21 +++++++++++++++++++-- 2 files changed, 21 insertions(+), 9 deletions(-) diff --git a/packages/opencode/src/agent/agent.ts b/packages/opencode/src/agent/agent.ts index f7e940a2874..7ef44710769 100644 --- a/packages/opencode/src/agent/agent.ts +++ b/packages/opencode/src/agent/agent.ts @@ -256,13 +256,8 @@ export namespace Agent { export async function defaultAgent() { const agents = await list() - const primary = agents.find((a) => a.mode !== "subagent" && a.hidden !== true) - if (primary) return primary.name - - const visible = agents.find((a) => a.hidden !== true) - if (visible) return visible.name - - return "build" + const primaryVisible = agents.find((a) => a.mode !== "subagent" && a.hidden !== true) + return primaryVisible?.name || "build" } 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 133e863253b..acb838cd0e1 100644 --- a/packages/opencode/test/agent/agent.test.ts +++ b/packages/opencode/test/agent/agent.test.ts @@ -513,8 +513,6 @@ test("explicit Truncate.DIR deny is respected", async () => { }) }) -// Tests for Agent.defaultAgent() - test("defaultAgent returns build when no default_agent config", async () => { await using tmp = await tmpdir() await Instance.provide({ @@ -627,3 +625,22 @@ test("defaultAgent returns plan when build is disabled and default_agent not set }, }) }) + +test("defaultAgent returns first primary-capable agent when all natives are disabled", async () => { + await using tmp = await tmpdir({ + config: { + agent: { + build: { disable: true }, + plan: { disable: true }, + }, + }, + }) + await Instance.provide({ + directory: tmp.path, + fn: async () => { + const agent = await Agent.defaultAgent() + // build and plan are disabled, so it should return general + expect(agent).toBe("build") + }, + }) +}) From e6a6e6104412b21400760a30e9c12ae5ed12ede1 Mon Sep 17 00:00:00 2001 From: assagman Date: Thu, 15 Jan 2026 21:49:35 +0300 Subject: [PATCH 5/5] core: show clear errors when default_agent config is invalid Users now see specific error messages explaining why their configured default_agent cannot be used (not found, is subagent, or is hidden) instead of silently falling back to a different agent. Signed-off-by: assagman --- packages/opencode/src/agent/agent.ts | 17 +++++++++++--- packages/opencode/test/agent/agent.test.ts | 26 ++++++++-------------- 2 files changed, 23 insertions(+), 20 deletions(-) diff --git a/packages/opencode/src/agent/agent.ts b/packages/opencode/src/agent/agent.ts index 7ef44710769..0725933d731 100644 --- a/packages/opencode/src/agent/agent.ts +++ b/packages/opencode/src/agent/agent.ts @@ -255,9 +255,20 @@ export namespace Agent { } export async function defaultAgent() { - const agents = await list() - const primaryVisible = agents.find((a) => a.mode !== "subagent" && a.hidden !== true) - return primaryVisible?.name || "build" + 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 acb838cd0e1..1ff303b7662 100644 --- a/packages/opencode/test/agent/agent.test.ts +++ b/packages/opencode/test/agent/agent.test.ts @@ -559,7 +559,7 @@ test("defaultAgent respects default_agent config set to custom agent with mode a }) }) -test("defaultAgent falls back when default_agent points to subagent", async () => { +test("defaultAgent throws when default_agent points to subagent", async () => { await using tmp = await tmpdir({ config: { default_agent: "explore", @@ -568,15 +568,12 @@ test("defaultAgent falls back when default_agent points to subagent", async () = await Instance.provide({ directory: tmp.path, fn: async () => { - const agent = await Agent.defaultAgent() - // explore is a subagent, so it should fall back to first primary-capable agent - expect(agent).not.toBe("explore") - expect(agent).toBe("build") + await expect(Agent.defaultAgent()).rejects.toThrow('default agent "explore" is a subagent') }, }) }) -test("defaultAgent falls back when default_agent points to hidden agent", async () => { +test("defaultAgent throws when default_agent points to hidden agent", async () => { await using tmp = await tmpdir({ config: { default_agent: "compaction", @@ -585,15 +582,12 @@ test("defaultAgent falls back when default_agent points to hidden agent", async await Instance.provide({ directory: tmp.path, fn: async () => { - const agent = await Agent.defaultAgent() - // compaction is hidden, so it should fall back to first primary-capable agent - expect(agent).not.toBe("compaction") - expect(agent).toBe("build") + await expect(Agent.defaultAgent()).rejects.toThrow('default agent "compaction" is hidden') }, }) }) -test("defaultAgent falls back when default_agent points to non-existent agent", async () => { +test("defaultAgent throws when default_agent points to non-existent agent", async () => { await using tmp = await tmpdir({ config: { default_agent: "does_not_exist", @@ -602,8 +596,7 @@ test("defaultAgent falls back when default_agent points to non-existent agent", await Instance.provide({ directory: tmp.path, fn: async () => { - const agent = await Agent.defaultAgent() - expect(agent).toBe("build") + await expect(Agent.defaultAgent()).rejects.toThrow('default agent "does_not_exist" not found') }, }) }) @@ -626,7 +619,7 @@ test("defaultAgent returns plan when build is disabled and default_agent not set }) }) -test("defaultAgent returns first primary-capable agent when all natives are disabled", async () => { +test("defaultAgent throws when all primary agents are disabled", async () => { await using tmp = await tmpdir({ config: { agent: { @@ -638,9 +631,8 @@ test("defaultAgent returns first primary-capable agent when all natives are disa await Instance.provide({ directory: tmp.path, fn: async () => { - const agent = await Agent.defaultAgent() - // build and plan are disabled, so it should return general - expect(agent).toBe("build") + // build and plan are disabled, no primary-capable agents remain + await expect(Agent.defaultAgent()).rejects.toThrow("no primary visible agent found") }, }) })