Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions packages/opencode/src/acp/agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
15 changes: 14 additions & 1 deletion packages/opencode/src/agent/agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 } }) {
Expand Down
124 changes: 124 additions & 0 deletions packages/opencode/test/agent/agent.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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")
},
})
})
Loading