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) { diff --git a/packages/opencode/src/agent/agent.ts b/packages/opencode/src/agent/agent.ts index 64875091916..0725933d731 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 624655112bb..1ff303b7662 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") + }, + }) +})