96581dac07
LaunchInput.temperature and LaunchInput.permission were accepted and passed through the council orchestrator but never forwarded to the actual promptAsync API call (SDK doesn't support per-request temperature or permission). Remove the dead fields, the unused AthenaConfig interface, and update tests/docs/schema accordingly.
229 lines
7.3 KiB
TypeScript
229 lines
7.3 KiB
TypeScript
import { describe, expect, test } from "bun:test"
|
|
import { buildCouncilPrompt } from "./council-prompt"
|
|
import { executeCouncil } from "./council-orchestrator"
|
|
import type { CouncilConfig } from "./types"
|
|
|
|
interface MockLaunchInput {
|
|
description: string
|
|
prompt: string
|
|
agent: string
|
|
parentSessionID: string
|
|
parentMessageID: string
|
|
parentAgent?: string
|
|
model?: { providerID: string; modelID: string; variant?: string }
|
|
}
|
|
|
|
function createMockTask(id: string, launch: MockLaunchInput) {
|
|
return {
|
|
id,
|
|
status: "pending" as const,
|
|
parentSessionID: launch.parentSessionID,
|
|
parentMessageID: launch.parentMessageID,
|
|
description: launch.description,
|
|
prompt: launch.prompt,
|
|
agent: launch.agent,
|
|
}
|
|
}
|
|
|
|
describe("executeCouncil", () => {
|
|
//#given a council with 3 members and a question
|
|
//#when executeCouncil is called
|
|
//#then all members are launched with the same prompt and parsed model ids
|
|
test("launches all members with identical prompt and model params", async () => {
|
|
const launches: MockLaunchInput[] = []
|
|
const launcher = {
|
|
launch: async (input: MockLaunchInput) => {
|
|
launches.push(input)
|
|
return createMockTask(`task-${launches.length}`, input)
|
|
},
|
|
}
|
|
|
|
const council: CouncilConfig = {
|
|
members: [
|
|
{ model: "openai/gpt-5.3-codex", name: "openai" },
|
|
{ model: "anthropic/claude-sonnet-4-5", name: "anthropic" },
|
|
{ model: "google/gemini-3-pro", name: "google" },
|
|
],
|
|
}
|
|
|
|
const question = "How can we improve the retry strategy?"
|
|
const result = await executeCouncil({
|
|
question,
|
|
council,
|
|
launcher,
|
|
parentSessionID: "session-1",
|
|
parentMessageID: "message-1",
|
|
parentAgent: "sisyphus",
|
|
})
|
|
|
|
const expectedPrompt = buildCouncilPrompt(question)
|
|
|
|
expect(launches).toHaveLength(3)
|
|
expect(result.launched).toHaveLength(3)
|
|
expect(result.failures).toHaveLength(0)
|
|
expect(result.totalMembers).toBe(3)
|
|
|
|
for (const launch of launches) {
|
|
expect(launch.prompt).toBe(expectedPrompt)
|
|
expect(launch.agent).toBe("athena")
|
|
}
|
|
|
|
expect(launches[0]?.model).toEqual({ providerID: "openai", modelID: "gpt-5.3-codex" })
|
|
expect(launches[1]?.model).toEqual({ providerID: "anthropic", modelID: "claude-sonnet-4-5" })
|
|
expect(launches[2]?.model).toEqual({ providerID: "google", modelID: "gemini-3-pro" })
|
|
})
|
|
|
|
//#given a council with 3 members where 1 launch throws
|
|
//#when executeCouncil is called
|
|
//#then launch failures are captured separately from successful launches
|
|
test("captures launch failures separately from successful launches", async () => {
|
|
const launcher = {
|
|
launch: async (input: MockLaunchInput) => {
|
|
if (input.model?.providerID === "anthropic") {
|
|
throw new Error("Provider unavailable")
|
|
}
|
|
return createMockTask(`task-${input.model?.providerID}`, input)
|
|
},
|
|
}
|
|
|
|
const result = await executeCouncil({
|
|
question: "Find race condition risks",
|
|
council: {
|
|
members: [
|
|
{ model: "openai/gpt-5.3-codex" },
|
|
{ model: "anthropic/claude-sonnet-4-5" },
|
|
{ model: "google/gemini-3-pro" },
|
|
],
|
|
},
|
|
launcher,
|
|
parentSessionID: "session-1",
|
|
parentMessageID: "message-1",
|
|
})
|
|
|
|
expect(result.launched).toHaveLength(2)
|
|
expect(result.failures).toHaveLength(1)
|
|
expect(result.totalMembers).toBe(3)
|
|
expect(result.failures[0]?.member.model).toBe("anthropic/claude-sonnet-4-5")
|
|
expect(result.failures[0]?.error).toContain("Launch failed")
|
|
})
|
|
|
|
//#given a council where all launches throw
|
|
//#when executeCouncil is called
|
|
//#then all members appear as failures with zero launched
|
|
test("returns all failures when every launch throws", async () => {
|
|
const launcher = {
|
|
launch: async () => {
|
|
throw new Error("Model unavailable")
|
|
},
|
|
}
|
|
|
|
const result = await executeCouncil({
|
|
question: "Analyze unknown module",
|
|
council: {
|
|
members: [
|
|
{ model: "openai/gpt-5.3-codex" },
|
|
{ model: "anthropic/claude-sonnet-4-5" },
|
|
],
|
|
},
|
|
launcher,
|
|
parentSessionID: "session-1",
|
|
parentMessageID: "message-1",
|
|
})
|
|
|
|
expect(result.launched).toHaveLength(0)
|
|
expect(result.failures).toHaveLength(2)
|
|
expect(result.totalMembers).toBe(2)
|
|
expect(result.failures.every((f) => f.error.includes("Launch failed"))).toBe(true)
|
|
})
|
|
|
|
//#given a council with one invalid model string
|
|
//#when executeCouncil is called
|
|
//#then invalid member becomes a failure while others still launch
|
|
test("handles invalid model strings without crashing council execution", async () => {
|
|
const launches: MockLaunchInput[] = []
|
|
const launcher = {
|
|
launch: async (input: MockLaunchInput) => {
|
|
launches.push(input)
|
|
return createMockTask(`task-${launches.length}`, input)
|
|
},
|
|
}
|
|
|
|
const result = await executeCouncil({
|
|
question: "Audit dependency graph",
|
|
council: {
|
|
members: [
|
|
{ model: "invalid-model" },
|
|
{ model: "openai/gpt-5.3-codex" },
|
|
],
|
|
},
|
|
launcher,
|
|
parentSessionID: "session-1",
|
|
parentMessageID: "message-1",
|
|
})
|
|
|
|
expect(launches).toHaveLength(1)
|
|
expect(result.launched).toHaveLength(1)
|
|
expect(result.failures).toHaveLength(1)
|
|
expect(result.failures.find((f) => f.member.model === "invalid-model")?.error).toContain("Launch failed")
|
|
})
|
|
|
|
//#given members with per-member variant
|
|
//#when executeCouncil is called
|
|
//#then launch receives variant in model for each corresponding member
|
|
test("passes member variant to launch input model", async () => {
|
|
const launches: MockLaunchInput[] = []
|
|
const launcher = {
|
|
launch: async (input: MockLaunchInput) => {
|
|
launches.push(input)
|
|
return createMockTask(`task-${launches.length}`, input)
|
|
},
|
|
}
|
|
|
|
await executeCouncil({
|
|
question: "Compare architecture options",
|
|
council: {
|
|
members: [
|
|
{ model: "openai/gpt-5.3-codex", variant: "high" },
|
|
{ model: "anthropic/claude-sonnet-4-5" },
|
|
],
|
|
},
|
|
launcher,
|
|
parentSessionID: "session-1",
|
|
parentMessageID: "message-1",
|
|
})
|
|
|
|
expect(launches).toHaveLength(2)
|
|
expect(launches[0]?.model?.variant).toBe("high")
|
|
expect(launches[1]?.model?.variant).toBeUndefined()
|
|
})
|
|
|
|
//#given launched members
|
|
//#when executeCouncil returns
|
|
//#then each launched member has a taskId for background_output retrieval
|
|
test("returns task IDs for background_output retrieval", async () => {
|
|
const launcher = {
|
|
launch: async (input: MockLaunchInput) =>
|
|
createMockTask(`bg_${input.model?.providerID}`, input),
|
|
}
|
|
|
|
const result = await executeCouncil({
|
|
question: "Review error handling",
|
|
council: {
|
|
members: [
|
|
{ model: "openai/gpt-5.3-codex", name: "OpenAI" },
|
|
{ model: "google/gemini-3-pro", name: "Gemini" },
|
|
],
|
|
},
|
|
launcher,
|
|
parentSessionID: "session-1",
|
|
parentMessageID: "message-1",
|
|
})
|
|
|
|
expect(result.launched).toHaveLength(2)
|
|
expect(result.launched[0]?.taskId).toBe("bg_openai")
|
|
expect(result.launched[0]?.member.name).toBe("OpenAI")
|
|
expect(result.launched[1]?.taskId).toBe("bg_google")
|
|
expect(result.launched[1]?.member.name).toBe("Gemini")
|
|
})
|
|
})
|