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:
parent
2eb8f5741a
commit
f6cdba07ec
@ -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:
|
||||
|
||||
@ -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 }
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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", () => {
|
||||
|
||||
@ -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>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user