Fixes from multi-model council audit (7 members, 19 findings, 9 selected): - Use parseModelString() for cross-provider Anthropic thinking config (#3) - Update stale AGENTS.md athena directory listing (#4) - Replace prompt in appendMissingCouncilPrompt instead of appending (#5) - Extract duplicated session cleanup logic in agent-switch hook (#6) - Surface skipped council members when >=2 valid members exist (#9) - Expand fallback handoff regex with negation guards (#11) - Remove dead council-member agent from agentSources and tests (#12) - Make runtime council member duplicate check case-insensitive (#14) - Fix false-positive schema tests by adding required name field (#18)
86 lines
3.1 KiB
TypeScript
86 lines
3.1 KiB
TypeScript
import type { AgentConfig } from "@opencode-ai/sdk"
|
|
import type { CouncilConfig, CouncilMemberConfig } from "../../config/schema/athena"
|
|
import { createCouncilMemberAgent } from "../athena/council-member-agent"
|
|
import { parseModelString } from "../../tools/delegate-task/model-string-parser"
|
|
import { log } from "../../shared/logger"
|
|
|
|
/** Prefix used for all dynamically-registered council member agent keys. */
|
|
export const COUNCIL_MEMBER_KEY_PREFIX = "Council: "
|
|
|
|
/**
|
|
* Generates a stable agent registration key from a council member's name.
|
|
*/
|
|
function getCouncilMemberAgentKey(member: CouncilMemberConfig): string {
|
|
return `${COUNCIL_MEMBER_KEY_PREFIX}${member.name}`
|
|
}
|
|
|
|
/**
|
|
* Registers council members as individual subagent entries.
|
|
* Each member becomes a separate agent callable via task(subagent_type="Council: <name>").
|
|
* Returns a record of agent keys to configs and the list of registered keys.
|
|
*/
|
|
type SkippedMember = { name: string; reason: string }
|
|
|
|
export function registerCouncilMemberAgents(
|
|
councilConfig: CouncilConfig
|
|
): { agents: Record<string, AgentConfig>; registeredKeys: string[]; skippedMembers: SkippedMember[] } {
|
|
const agents: Record<string, AgentConfig> = {}
|
|
const registeredKeys: string[] = []
|
|
const skippedMembers: SkippedMember[] = []
|
|
const registeredNamesLower = new Set<string>()
|
|
|
|
for (const member of councilConfig.members) {
|
|
const parsed = parseModelString(member.model)
|
|
if (!parsed) {
|
|
skippedMembers.push({
|
|
name: member.name,
|
|
reason: `Invalid model format: '${member.model}' (expected 'provider/model-id')`,
|
|
})
|
|
log("[council-member-agents] Skipping member with invalid model", { model: member.model })
|
|
continue
|
|
}
|
|
|
|
const key = getCouncilMemberAgentKey(member)
|
|
const nameLower = member.name.toLowerCase()
|
|
|
|
if (registeredNamesLower.has(nameLower)) {
|
|
skippedMembers.push({
|
|
name: member.name,
|
|
reason: `Duplicate name: '${member.name}' already registered (case-insensitive match)`,
|
|
})
|
|
log("[council-member-agents] Skipping duplicate council member name", {
|
|
name: member.name,
|
|
model: member.model,
|
|
})
|
|
continue
|
|
}
|
|
|
|
const config = createCouncilMemberAgent(member.model)
|
|
const description = `Council member: ${member.name} (${parsed.providerID}/${parsed.modelID}). Independent read-only code analyst for Athena council. (OhMyOpenCode)`
|
|
|
|
agents[key] = {
|
|
...config,
|
|
description,
|
|
model: member.model,
|
|
...(member.variant ? { variant: member.variant } : {}),
|
|
...(member.temperature !== undefined ? { temperature: member.temperature } : {}),
|
|
}
|
|
|
|
registeredKeys.push(key)
|
|
registeredNamesLower.add(nameLower)
|
|
|
|
log("[council-member-agents] Registered council member agent", {
|
|
key,
|
|
model: member.model,
|
|
variant: member.variant,
|
|
})
|
|
}
|
|
|
|
if (registeredKeys.length < 2) {
|
|
log("[council-member-agents] Fewer than 2 valid council members after model parsing — disabling council mode")
|
|
return { agents: {}, registeredKeys: [], skippedMembers }
|
|
}
|
|
|
|
return { agents, registeredKeys, skippedMembers }
|
|
}
|