fix(athena): harden council registration with duplicate detection and count validation
Three registration improvements: gate council member registration on Athena enablement, throw on duplicate council member keys instead of silent overwrite, and disable council mode when valid members drop below 2 after model parsing. Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
parent
7b6d3206ce
commit
c1bf455b63
@ -19,6 +19,7 @@ import {
|
||||
fetchAvailableModels,
|
||||
readConnectedProvidersCache,
|
||||
readProviderModelsCache,
|
||||
log,
|
||||
} from "../shared"
|
||||
import { CATEGORY_DESCRIPTIONS } from "../tools/delegate-task/constants"
|
||||
import { mergeCategories } from "../shared/merge-categories"
|
||||
@ -201,13 +202,13 @@ export async function createBuiltinAgents(
|
||||
result["atlas"] = atlasConfig
|
||||
}
|
||||
|
||||
if (councilConfig && councilConfig.members.length >= 2) {
|
||||
if (councilConfig && councilConfig.members.length >= 2 && result["athena"]) {
|
||||
const { agents: councilAgents, registeredKeys } = registerCouncilMemberAgents(councilConfig)
|
||||
for (const [key, config] of Object.entries(councilAgents)) {
|
||||
result[key] = config
|
||||
}
|
||||
|
||||
if (result["athena"] && registeredKeys.length > 0) {
|
||||
if (registeredKeys.length > 0) {
|
||||
const memberList = registeredKeys.map((key) => `- "${key}"`).join("\n")
|
||||
const councilTaskInstructions = `\n\n## Registered Council Members (use these as subagent_type in task calls)\n\n${memberList}`
|
||||
result["athena"] = {
|
||||
@ -215,6 +216,8 @@ export async function createBuiltinAgents(
|
||||
prompt: (result["athena"].prompt ?? "") + councilTaskInstructions,
|
||||
}
|
||||
}
|
||||
} else if (councilConfig && councilConfig.members.length >= 2 && !result["athena"]) {
|
||||
log("[builtin-agents] Skipping council member registration — Athena is disabled")
|
||||
}
|
||||
|
||||
return result
|
||||
|
||||
63
src/agents/builtin-agents/council-member-agents.test.ts
Normal file
63
src/agents/builtin-agents/council-member-agents.test.ts
Normal file
@ -0,0 +1,63 @@
|
||||
import { describe, expect, test } from "bun:test"
|
||||
import { registerCouncilMemberAgents } from "./council-member-agents"
|
||||
|
||||
describe("council-member-agents", () => {
|
||||
test("throws on duplicate model without name", () => {
|
||||
//#given
|
||||
const config = {
|
||||
members: [
|
||||
{ model: "openai/gpt-5.3-codex" },
|
||||
{ model: "openai/gpt-5.3-codex" },
|
||||
],
|
||||
}
|
||||
//#when + #then
|
||||
expect(() => registerCouncilMemberAgents(config)).toThrow("already registered")
|
||||
})
|
||||
|
||||
test("registers different models without error", () => {
|
||||
//#given
|
||||
const config = {
|
||||
members: [
|
||||
{ model: "openai/gpt-5.3-codex" },
|
||||
{ model: "anthropic/claude-opus-4-6" },
|
||||
],
|
||||
}
|
||||
//#when
|
||||
const result = registerCouncilMemberAgents(config)
|
||||
//#then
|
||||
expect(result.registeredKeys).toHaveLength(2)
|
||||
expect(result.registeredKeys).toContain("Council: GPT 5.3 Codex")
|
||||
expect(result.registeredKeys).toContain("Council: Claude Opus 4.6")
|
||||
})
|
||||
|
||||
test("allows same model with different names", () => {
|
||||
//#given
|
||||
const config = {
|
||||
members: [
|
||||
{ model: "openai/gpt-5.3-codex", name: "GPT Codex" },
|
||||
{ model: "openai/gpt-5.3-codex", name: "Codex GPT" },
|
||||
],
|
||||
}
|
||||
//#when
|
||||
const result = registerCouncilMemberAgents(config)
|
||||
//#then
|
||||
expect(result.registeredKeys).toHaveLength(2)
|
||||
expect(result.agents).toHaveProperty("Council: GPT Codex")
|
||||
expect(result.agents).toHaveProperty("Council: Codex GPT")
|
||||
})
|
||||
|
||||
test("returns empty when valid members below 2", () => {
|
||||
//#given - one valid model, one invalid (no slash separator)
|
||||
const config = {
|
||||
members: [
|
||||
{ model: "openai/gpt-5.3-codex" },
|
||||
{ model: "invalid-no-slash" },
|
||||
],
|
||||
}
|
||||
//#when
|
||||
const result = registerCouncilMemberAgents(config)
|
||||
//#then
|
||||
expect(result.registeredKeys).toHaveLength(0)
|
||||
expect(result.agents).toEqual({})
|
||||
})
|
||||
})
|
||||
@ -71,11 +71,19 @@ export function registerCouncilMemberAgents(
|
||||
const friendlyName = member.name ?? humanizeModelId(member.model)
|
||||
const description = `Council member: ${friendlyName} (${member.model}). Independent read-only code analyst for Athena council. (OhMyOpenCode)`
|
||||
|
||||
if (agents[key]) {
|
||||
const existingModel = agents[key].model ?? "unknown"
|
||||
throw new Error(
|
||||
`Council member key "${key}" is already registered (model: ${existingModel}). Use distinct "name" fields to avoid collisions.`
|
||||
)
|
||||
}
|
||||
|
||||
agents[key] = {
|
||||
...config,
|
||||
description,
|
||||
model: member.model,
|
||||
...(member.variant ? { variant: member.variant } : {}),
|
||||
...(member.temperature !== undefined ? { temperature: member.temperature } : {}),
|
||||
}
|
||||
|
||||
registeredKeys.push(key)
|
||||
@ -87,5 +95,10 @@ export function registerCouncilMemberAgents(
|
||||
})
|
||||
}
|
||||
|
||||
if (registeredKeys.length < 2) {
|
||||
log("[council-member-agents] Fewer than 2 valid council members after model parsing — disabling council mode")
|
||||
return { agents: {}, registeredKeys: [] }
|
||||
}
|
||||
|
||||
return { agents, registeredKeys }
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user