Skip to content
Open
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/agent/agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ export const Info = Schema.Struct({
hidden: Schema.optional(Schema.Boolean),
topP: Schema.optional(Schema.Number),
temperature: Schema.optional(Schema.Number),
order: Schema.optional(Schema.Number),
color: Schema.optional(Schema.String),
permission: Permission.Ruleset,
model: Schema.optional(
Expand Down Expand Up @@ -256,6 +257,7 @@ export const layer = Layer.effect(
item.color = value.color ?? item.color
item.hidden = value.hidden ?? item.hidden
item.name = value.name ?? item.name
item.order = value.order ?? item.order
item.steps = value.steps ?? item.steps
item.options = mergeDeep(item.options, value.options ?? {})
item.permission = Permission.merge(item.permission, Permission.fromConfig(value.permission ?? {}))
Expand Down Expand Up @@ -288,6 +290,7 @@ export const layer = Layer.effect(
values(),
sortBy(
[(x) => (cfg.default_agent ? x.name === cfg.default_agent : x.name === "build"), "desc"],
[(x) => x.order ?? Infinity, "asc"],
[(x) => x.name, "asc"],
),
)
Expand Down
5 changes: 5 additions & 0 deletions packages/opencode/src/config/agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ const AgentSchema = Schema.StructWithRest(
disable: Schema.optional(Schema.Boolean),
description: Schema.optional(Schema.String).annotate({ description: "Description of when to use the agent" }),
mode: Schema.optional(Schema.Literals(["subagent", "primary", "all"])),
order: Schema.optional(PositiveInt).annotate({
description:
"Sorting order for agent cycling (Tab). Lower values appear first. Agents without order are sorted alphabetically after ordered agents.",
}),
hidden: Schema.optional(Schema.Boolean).annotate({
description: "Hide this subagent from the @ autocomplete menu (default: false, only applies to mode: subagent)",
}),
Expand All @@ -60,6 +64,7 @@ const KNOWN_KEYS = new Set([
"temperature",
"top_p",
"mode",
"order",
"hidden",
"color",
"steps",
Expand Down
76 changes: 75 additions & 1 deletion packages/opencode/test/agent/agent.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -391,7 +391,7 @@ test("multiple custom agents can be defined", async () => {
})
})

test("Agent.list keeps the default agent first and sorts the rest by name", async () => {
test("Agent.list keeps the default agent first and sorts the rest by name when no order is set", async () => {
await using tmp = await tmpdir({
config: {
default_agent: "plan",
Expand All @@ -417,6 +417,80 @@ test("Agent.list keeps the default agent first and sorts the rest by name", asyn
})
})

test("Agent.list respects order field for sorting", async () => {
await using tmp = await tmpdir({
config: {
default_agent: "build",
agent: {
build: {
description: "Build agent",
mode: "primary",
order: 2,
},
alpha: {
description: "Alpha",
mode: "primary",
order: 1,
},
zebra: {
description: "Zebra",
mode: "primary",
order: 3,
},
},
},
})
await Instance.provide({
directory: tmp.path,
fn: async () => {
const names = (await load(tmp.path, (svc) => svc.list())).map((a) => a.name)
// Default agent ("build") always first regardless of order
expect(names[0]).toBe("build")
// Remaining sorted by order ascending: alpha (1) → zebra (3)
expect(names[1]).toBe("alpha")
expect(names[2]).toBe("zebra")
},
})
})

test("Agent.list sorts agents without order after ordered agents", async () => {
await using tmp = await tmpdir({
config: {
agent: {
zebra: {
description: "Zebra",
mode: "primary",
},
alpha: {
description: "Alpha",
mode: "primary",
order: 1,
},
beta: {
description: "Beta",
mode: "primary",
order: 2,
},
gamma: {
description: "Gamma",
mode: "primary",
},
},
},
})
await Instance.provide({
directory: tmp.path,
fn: async () => {
const names = (await load(tmp.path, (svc) => svc.list())).map((a) => a.name)
// Sorted: by order (alpha:1, beta:2), then alphabetical (gamma, zebra)
expect(names[0]).toBe("alpha")
expect(names[1]).toBe("beta")
expect(names[2]).toBe("gamma")
expect(names[3]).toBe("zebra")
},
})
})

test("Agent.get returns undefined for non-existent agent", async () => {
await using tmp = await tmpdir()
await Instance.provide({
Expand Down
Loading