oh-my-opencode/src/agents/athena/council-orchestrator.ts
ismeth f04b73fae3 refactor(athena): remove type assertions and improve agent factories
- Replace 'as AgentConfig' casts with proper typing in agent.ts and council-member-agent.ts
- Extract permission into typed variable following Sisyphus pattern
- Add GPT/non-GPT model branching to council-member-agent
- Use parseModelString for schema validation instead of inline logic
- Add strict() to council and athena config schemas
- Fix athena restriction list (remove redundant athena_council deny)
- Add orchestrator logging for council execution
- Update system prompt to notification-based workflow

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-02-24 22:24:22 +09:00

95 lines
2.8 KiB
TypeScript

import type { LaunchInput, BackgroundTask } from "../../features/background-agent/types"
import { log } from "../../shared/logger"
import { buildCouncilPrompt } from "./council-prompt"
import { parseModelString } from "./model-parser"
import type { CouncilConfig, CouncilLaunchFailure, CouncilLaunchedMember, CouncilLaunchResult, CouncilMemberConfig } from "./types"
export type CouncilLaunchInput = LaunchInput
export interface CouncilLauncher {
launch(input: CouncilLaunchInput): Promise<BackgroundTask>
}
export interface CouncilExecutionInput {
question: string
council: CouncilConfig
launcher: CouncilLauncher
parentSessionID: string
parentMessageID: string
parentAgent?: string
}
/**
* Launches all council members in parallel and returns launch outcomes.
* Does NOT wait for task completion — actual results are collected by the
* agent via background_output calls after this returns.
*/
export async function executeCouncil(input: CouncilExecutionInput): Promise<CouncilLaunchResult> {
const { question, council, launcher, parentSessionID, parentMessageID, parentAgent } = input
log("[athena-council] Executing council", { memberCount: council.members.length })
const prompt = buildCouncilPrompt(question)
const launchResults = await Promise.allSettled(
council.members.map((member) =>
launchMember(member, prompt, launcher, parentSessionID, parentMessageID, parentAgent)
)
)
const launched: CouncilLaunchedMember[] = []
const failures: CouncilLaunchFailure[] = []
launchResults.forEach((result, index) => {
const member = council.members[index]
if (result.status === "fulfilled") {
launched.push({ member, taskId: result.value.id })
return
}
failures.push({
member,
error: `Launch failed: ${result.reason instanceof Error ? result.reason.message : String(result.reason)}`,
})
})
log("[athena-council] Council execution complete", { launched: launched.length, failures: failures.length })
return {
question,
launched,
failures,
totalMembers: council.members.length,
}
}
async function launchMember(
member: CouncilMemberConfig,
prompt: string,
launcher: CouncilLauncher,
parentSessionID: string,
parentMessageID: string,
parentAgent: string | undefined
): Promise<BackgroundTask> {
const parsedModel = parseModelString(member.model)
if (!parsedModel) {
throw new Error(`Invalid model string: "${member.model}"`)
}
const memberName = member.name ?? member.model
return launcher.launch({
description: `Council member: ${memberName}`,
prompt,
agent: "council-member",
parentSessionID,
parentMessageID,
parentAgent,
model: {
providerID: parsedModel.providerID,
modelID: parsedModel.modelID,
...(member.variant ? { variant: member.variant } : {}),
},
})
}