fix(athena): resolve 4 compatibility and correctness issues

- Use case-insensitive casing in duplicate name test to verify actual logic
- Align permission type with SDK AgentConfig pattern (as AgentConfig["permission"])
- Move duplicate-name validation from schema to runtime for graceful fallback
- Place skipped members details before 'end your turn' in council guard prompt
This commit is contained in:
ismeth 2026-02-20 20:13:02 +01:00 committed by YeonGyu-Kim
parent 2eb8f5741a
commit f6cdba07ec
5 changed files with 20 additions and 21 deletions

View File

@ -1,6 +1,6 @@
import type { AgentConfig } from "@opencode-ai/sdk"
import type { AgentMode, AgentPromptMetadata } from "../types"
import { createAgentToolRestrictions, type PermissionValue } from "../../shared/permission-compat"
import { createAgentToolRestrictions } from "../../shared/permission-compat"
import { applyModelThinkingConfig } from "./model-thinking-config"
const MODE: AgentMode = "primary"
@ -211,10 +211,10 @@ The switch_agent tool switches the active agent. After you call it, end your res
export function createAthenaAgent(model: string): AgentConfig {
const restrictions = createAgentToolRestrictions(["write", "edit", "call_omo_agent"])
const permission: Record<string, PermissionValue> = {
const permission = {
...restrictions.permission,
question: "allow",
}
} as AgentConfig["permission"]
const base = {
description:

View File

@ -1,6 +1,6 @@
import type { AgentConfig } from "@opencode-ai/sdk"
const MISSING_COUNCIL_PROMPT = `
const MISSING_COUNCIL_PROMPT_HEADER = `
## CRITICAL: No Council Members Configured
@ -32,7 +32,9 @@ You have no council members registered. This means the Athena council config is
}
\`\`\`
Each member requires \`model\` (\`"provider/model-id"\` format) and \`name\` (display name). Minimum 2 members required. Optional fields: \`variant\`, \`temperature\`.
Each member requires \`model\` (\`"provider/model-id"\` format) and \`name\` (display name). Minimum 2 members required. Optional fields: \`variant\`, \`temperature\`.`
const MISSING_COUNCIL_PROMPT_FOOTER = `
---
@ -47,12 +49,14 @@ export function appendMissingCouncilPrompt(
athenaConfig: AgentConfig,
skippedMembers?: Array<{ name: string; reason: string }>,
): AgentConfig {
let prompt = MISSING_COUNCIL_PROMPT
let prompt = MISSING_COUNCIL_PROMPT_HEADER
if (skippedMembers && skippedMembers.length > 0) {
const skipDetails = skippedMembers.map((m) => `- **${m.name}**: ${m.reason}`).join("\n")
prompt += `\n\n### Why Council Failed\n\nThe following members were skipped:\n${skipDetails}`
}
prompt += MISSING_COUNCIL_PROMPT_FOOTER
return { ...athenaConfig, prompt }
}

View File

@ -2,12 +2,12 @@ import { describe, expect, test } from "bun:test"
import { registerCouncilMemberAgents } from "./council-member-agents"
describe("council-member-agents", () => {
test("skips duplicate names and disables council when below minimum", () => {
test("skips case-insensitive duplicate names and disables council when below minimum", () => {
//#given
const config = {
members: [
{ model: "openai/gpt-5.3-codex", name: "GPT" },
{ model: "anthropic/claude-opus-4-6", name: "GPT" },
{ model: "anthropic/claude-opus-4-6", name: "gpt" },
],
}
//#when

View File

@ -330,8 +330,9 @@ describe("CouncilConfigSchema", () => {
expect(result.success).toBe(false)
})
test("rejects council with duplicate member names", () => {
//#given
test("accepts council with duplicate member names for graceful runtime handling", () => {
//#given - duplicate detection is handled at runtime by registerCouncilMemberAgents,
// not at schema level, to allow graceful fallback instead of hard parse failure
const config = {
members: [
{ model: "anthropic/claude-opus-4-6", name: "analyst" },
@ -343,11 +344,11 @@ describe("CouncilConfigSchema", () => {
const result = CouncilConfigSchema.safeParse(config)
//#then
expect(result.success).toBe(false)
expect(result.success).toBe(true)
})
test("rejects council with case-insensitive duplicate names", () => {
//#given
test("accepts council with case-insensitive duplicate names for graceful runtime handling", () => {
//#given - case-insensitive dedup is handled at runtime by registerCouncilMemberAgents
const config = {
members: [
{ model: "anthropic/claude-opus-4-6", name: "Claude" },
@ -359,7 +360,7 @@ describe("CouncilConfigSchema", () => {
const result = CouncilConfigSchema.safeParse(config)
//#then
expect(result.success).toBe(false)
expect(result.success).toBe(true)
})
test("accepts council with unique member names", () => {

View File

@ -20,13 +20,7 @@ export const CouncilMemberSchema = z.object({
}).strict()
export const CouncilConfigSchema = z.object({
members: z.array(CouncilMemberSchema).min(2).refine(
(members) => {
const names = members.map(m => m.name.toLowerCase())
return new Set(names).size === names.length
},
{ message: "Council member names must be unique (case-insensitive)" },
),
members: z.array(CouncilMemberSchema).min(2),
}).strict()
export type CouncilMemberConfig = z.infer<typeof CouncilMemberSchema>