2026-02-12 16:03:20 +01:00
/// <reference types="bun-types" />
import { describe , expect , test } from "bun:test"
import type { BackgroundManager } from "../../features/background-agent"
2026-02-13 11:19:25 +01:00
import type { BackgroundTask } from "../../features/background-agent/types"
2026-02-12 18:21:33 +01:00
import { createAthenaCouncilTool , filterCouncilMembers } from "./tools"
2026-02-12 16:03:20 +01:00
const mockManager = {
getTask : ( ) = > undefined ,
launch : async ( ) = > {
throw new Error ( "launch should not be called in config validation tests" )
} ,
} as unknown as BackgroundManager
const mockToolContext = {
sessionID : "session-1" ,
messageID : "message-1" ,
agent : "athena" ,
abort : new AbortController ( ) . signal ,
}
2026-02-12 18:21:33 +01:00
const configuredMembers = [
{ name : "Claude" , model : "anthropic/claude-sonnet-4-5" } ,
{ name : "GPT" , model : "openai/gpt-5.3-codex" } ,
{ model : "google/gemini-3-pro" } ,
]
2026-02-18 19:26:20 +01:00
function createRunningTask ( id : string , sessionID = ` ses- ${ id } ` ) : BackgroundTask {
2026-02-13 11:19:25 +01:00
return {
id ,
parentSessionID : "session-1" ,
parentMessageID : "message-1" ,
description : ` Council member task ${ id } ` ,
prompt : "prompt" ,
2026-02-17 14:52:22 +01:00
agent : "council-member" ,
2026-02-18 19:26:20 +01:00
status : "running" ,
sessionID ,
2026-02-13 11:19:25 +01:00
}
}
2026-02-12 18:21:33 +01:00
describe ( "filterCouncilMembers" , ( ) = > {
2026-02-18 20:56:07 +01:00
test ( "returns selection error when selection is undefined" , ( ) = > {
2026-02-18 19:26:20 +01:00
const result = filterCouncilMembers ( configuredMembers , undefined )
2026-02-18 20:56:07 +01:00
expect ( result . members ) . toEqual ( [ ] )
expect ( result . error ) . toBe (
"athena_council runs one member per call. Pass exactly one member in members (single-item array). Available members: Claude, GPT, google/gemini-3-pro."
)
2026-02-12 18:21:33 +01:00
} )
2026-02-18 20:56:07 +01:00
test ( "returns selection error when selection is empty" , ( ) = > {
2026-02-18 19:26:20 +01:00
const result = filterCouncilMembers ( configuredMembers , [ ] )
2026-02-18 20:56:07 +01:00
expect ( result . members ) . toEqual ( [ ] )
expect ( result . error ) . toBe (
"athena_council runs one member per call. Pass exactly one member in members (single-item array). Available members: Claude, GPT, google/gemini-3-pro."
)
2026-02-12 18:21:33 +01:00
} )
test ( "filters members using case-insensitive name and model matching" , ( ) = > {
2026-02-18 19:26:20 +01:00
const result = filterCouncilMembers ( configuredMembers , [ "gpt" , "GOOGLE/GEMINI-3-PRO" ] )
2026-02-12 18:21:33 +01:00
expect ( result . members ) . toEqual ( [ configuredMembers [ 1 ] , configuredMembers [ 2 ] ] )
expect ( result . error ) . toBeUndefined ( )
} )
test ( "returns helpful error when selected members are not configured" , ( ) = > {
2026-02-18 19:26:20 +01:00
const result = filterCouncilMembers ( configuredMembers , [ "mistral" , "xai/grok-3" ] )
2026-02-12 18:21:33 +01:00
expect ( result . members ) . toEqual ( [ ] )
expect ( result . error ) . toBe (
2026-02-18 20:56:07 +01:00
"Unknown council members: mistral, xai/grok-3. Available: Claude (anthropic/claude-sonnet-4-5), GPT (openai/gpt-5.3-codex), google/gemini-3-pro."
2026-02-12 18:21:33 +01:00
)
} )
2026-02-13 16:00:04 +01:00
test ( "deduplicates when same member is selected by both name and model" , ( ) = > {
2026-02-18 19:26:20 +01:00
const result = filterCouncilMembers ( configuredMembers , [ "Claude" , "anthropic/claude-sonnet-4-5" ] )
2026-02-13 16:00:04 +01:00
expect ( result . members ) . toEqual ( [ configuredMembers [ 0 ] ] )
expect ( result . error ) . toBeUndefined ( )
} )
2026-02-12 18:21:33 +01:00
} )
2026-02-12 16:03:20 +01:00
describe ( "createAthenaCouncilTool" , ( ) = > {
test ( "returns error when councilConfig is undefined" , async ( ) = > {
const athenaCouncilTool = createAthenaCouncilTool ( {
backgroundManager : mockManager ,
councilConfig : undefined ,
} )
const result = await athenaCouncilTool . execute ( { question : "How should we proceed?" } , mockToolContext )
2026-02-18 20:56:07 +01:00
expect ( result ) . toBe ( "Athena council is not configured. Add council members to agents.athena.council.members in .opencode/oh-my-opencode.jsonc." )
2026-02-12 16:03:20 +01:00
} )
test ( "returns error when councilConfig has empty members" , async ( ) = > {
const athenaCouncilTool = createAthenaCouncilTool ( {
backgroundManager : mockManager ,
councilConfig : { members : [ ] } ,
} )
const result = await athenaCouncilTool . execute ( { question : "Any concerns?" } , mockToolContext )
2026-02-18 20:56:07 +01:00
expect ( result ) . toBe ( "Athena council is not configured. Add council members to agents.athena.council.members in .opencode/oh-my-opencode.jsonc." )
2026-02-12 16:03:20 +01:00
} )
2026-02-18 19:26:20 +01:00
test ( "returns helpful error when members contains invalid names" , async ( ) = > {
2026-02-12 16:03:20 +01:00
const athenaCouncilTool = createAthenaCouncilTool ( {
backgroundManager : mockManager ,
2026-02-18 19:26:20 +01:00
councilConfig : { members : configuredMembers } ,
2026-02-12 16:03:20 +01:00
} )
2026-02-18 19:26:20 +01:00
const result = await athenaCouncilTool . execute (
{ question : "Who should investigate this?" , members : [ "unknown-model" ] } ,
mockToolContext
)
2026-02-18 20:56:07 +01:00
expect ( result ) . toBe ( "Unknown council members: unknown-model. Available: Claude (anthropic/claude-sonnet-4-5), GPT (openai/gpt-5.3-codex), google/gemini-3-pro." )
2026-02-12 18:21:33 +01:00
} )
2026-02-18 19:26:20 +01:00
test ( "returns selection error when members are omitted" , async ( ) = > {
2026-02-12 18:21:33 +01:00
const athenaCouncilTool = createAthenaCouncilTool ( {
backgroundManager : mockManager ,
councilConfig : { members : configuredMembers } ,
} )
2026-02-18 19:26:20 +01:00
const result = await athenaCouncilTool . execute ( { question : "How should we proceed?" } , mockToolContext )
2026-02-12 18:21:33 +01:00
2026-02-18 19:26:20 +01:00
expect ( result ) . toBe (
"athena_council runs one member per call. Pass exactly one member in members (single-item array). Available members: Claude, GPT, google/gemini-3-pro."
)
2026-02-12 16:03:20 +01:00
} )
2026-02-13 11:19:25 +01:00
2026-02-18 19:26:20 +01:00
test ( "returns selection error when multiple members are provided" , async ( ) = > {
2026-02-13 11:19:25 +01:00
const athenaCouncilTool = createAthenaCouncilTool ( {
2026-02-18 19:26:20 +01:00
backgroundManager : mockManager ,
2026-02-13 11:19:25 +01:00
councilConfig : { members : configuredMembers } ,
} )
2026-02-18 19:26:20 +01:00
const result = await athenaCouncilTool . execute (
{ question : "How should we proceed?" , members : [ "Claude" , "GPT" ] } ,
mockToolContext
)
2026-02-13 11:19:25 +01:00
2026-02-18 19:26:20 +01:00
expect ( result ) . toBe (
"athena_council runs one member per call. Pass exactly one member in members (single-item array). Available members: Claude, GPT, google/gemini-3-pro."
)
2026-02-13 11:19:25 +01:00
} )
2026-02-18 19:26:20 +01:00
test ( "launches selected member and returns background task metadata" , async ( ) = > {
2026-02-13 11:19:25 +01:00
let launchCount = 0
2026-02-17 14:52:22 +01:00
const taskStore = new Map < string , BackgroundTask > ( )
2026-02-13 11:19:25 +01:00
const launchManager = {
launch : async ( ) = > {
launchCount += 1
2026-02-18 19:26:20 +01:00
const task = createRunningTask ( ` bg- ${ launchCount } ` )
2026-02-17 14:52:22 +01:00
taskStore . set ( task . id , task )
return task
2026-02-13 11:19:25 +01:00
} ,
2026-02-17 14:52:22 +01:00
getTask : ( id : string ) = > taskStore . get ( id ) ,
2026-02-13 11:19:25 +01:00
} as unknown as BackgroundManager
2026-02-18 19:26:20 +01:00
2026-02-13 11:19:25 +01:00
const athenaCouncilTool = createAthenaCouncilTool ( {
backgroundManager : launchManager ,
councilConfig : { members : configuredMembers } ,
} )
const result = await athenaCouncilTool . execute (
2026-02-18 19:26:20 +01:00
{ question : "Who should investigate this?" , members : [ "GPT" ] } ,
2026-02-13 11:19:25 +01:00
mockToolContext
)
2026-02-18 19:26:20 +01:00
expect ( launchCount ) . toBe ( 1 )
expect ( result ) . toContain ( "Council member launched in background." )
expect ( result ) . toContain ( "Task ID: bg-1" )
expect ( result ) . toContain ( "Session ID: ses-bg-1" )
expect ( result ) . toContain ( "Member: GPT" )
expect ( result ) . toContain ( "Model: openai/gpt-5.3-codex" )
expect ( result ) . toContain ( "Status: running" )
expect ( result ) . toContain ( "background_output" )
expect ( result ) . toContain ( "task_id=\"bg-1\"" )
expect ( result ) . toContain ( "<task_metadata>" )
expect ( result ) . toContain ( "session_id: ses-bg-1" )
2026-02-13 11:19:25 +01:00
} )
2026-02-18 19:26:20 +01:00
test ( "returns launch failure details when selected member fails" , async ( ) = > {
2026-02-13 11:19:25 +01:00
const launchManager = {
launch : async ( ) = > {
2026-02-18 19:26:20 +01:00
throw new Error ( "provider outage" )
2026-02-13 11:19:25 +01:00
} ,
2026-02-18 19:26:20 +01:00
getTask : ( ) = > undefined ,
2026-02-13 11:19:25 +01:00
} as unknown as BackgroundManager
2026-02-18 19:26:20 +01:00
2026-02-13 11:19:25 +01:00
const athenaCouncilTool = createAthenaCouncilTool ( {
backgroundManager : launchManager ,
councilConfig : { members : configuredMembers } ,
} )
2026-02-18 19:26:20 +01:00
const result = await athenaCouncilTool . execute (
{ question : "Any concerns?" , members : [ "GPT" ] } ,
mockToolContext
)
2026-02-13 11:19:25 +01:00
2026-02-18 19:26:20 +01:00
expect ( result ) . toContain ( "Failed to launch council member." )
2026-02-17 14:52:22 +01:00
expect ( result ) . toContain ( "### Launch Failures" )
expect ( result ) . toContain ( "**GPT**" )
expect ( result ) . toContain ( "provider outage" )
2026-02-13 11:19:25 +01:00
} )
2026-02-12 16:03:20 +01:00
} )