import type { AgentConfig } from "@opencode-ai/sdk" import type { BuiltinAgentName, AgentOverrides, AgentFactory, AgentPromptMetadata } from "./types" import type { CategoriesConfig, GitMasterConfig } from "../config/schema" import type { LoadedSkill } from "../features/opencode-skill-loader/types" import type { BrowserAutomationProvider } from "../config/schema" import { createSisyphusAgent } from "./sisyphus" import { createOracleAgent, ORACLE_PROMPT_METADATA } from "./oracle" import { createLibrarianAgent, LIBRARIAN_PROMPT_METADATA } from "./librarian" import { createExploreAgent, EXPLORE_PROMPT_METADATA } from "./explore" import { createMultimodalLookerAgent, MULTIMODAL_LOOKER_PROMPT_METADATA } from "./multimodal-looker" import { createMetisAgent, metisPromptMetadata } from "./metis" import { createAtlasAgent, atlasPromptMetadata } from "./atlas" import { createMomusAgent, momusPromptMetadata } from "./momus" import { createHephaestusAgent } from "./hephaestus" import { createAthenaAgent, ATHENA_PROMPT_METADATA } from "./athena/agent" import type { AvailableCategory } from "./dynamic-agent-prompt-builder" import { fetchAvailableModels, readConnectedProvidersCache, readProviderModelsCache, log, } from "../shared" import { CATEGORY_DESCRIPTIONS } from "../tools/delegate-task/constants" import { mergeCategories } from "../shared/merge-categories" import { buildAvailableSkills } from "./builtin-agents/available-skills" import { collectPendingBuiltinAgents } from "./builtin-agents/general-agents" import { maybeCreateSisyphusConfig } from "./builtin-agents/sisyphus-agent" import { maybeCreateHephaestusConfig } from "./builtin-agents/hephaestus-agent" import { maybeCreateAtlasConfig } from "./builtin-agents/atlas-agent" import { buildCustomAgentMetadata, parseRegisteredAgentSummaries } from "./custom-agent-summaries" import { registerCouncilMemberAgents } from "./builtin-agents/council-member-agents" import { appendMissingCouncilPrompt } from "./builtin-agents/athena-council-guard" import type { CouncilConfig } from "../config/schema/athena" type AgentSource = AgentFactory | AgentConfig const agentSources: Partial> = { sisyphus: createSisyphusAgent, hephaestus: createHephaestusAgent, oracle: createOracleAgent, librarian: createLibrarianAgent, explore: createExploreAgent, "multimodal-looker": createMultimodalLookerAgent, metis: createMetisAgent, momus: createMomusAgent, athena: createAthenaAgent, // Note: Atlas is handled specially in createBuiltinAgents() // because it needs OrchestratorContext, not just a model string atlas: createAtlasAgent as AgentFactory, } /** * Metadata for each agent, used to build Sisyphus's dynamic prompt sections * (Delegation Table, Tool Selection, Key Triggers, etc.) */ const agentMetadata: Partial> = { oracle: ORACLE_PROMPT_METADATA, librarian: LIBRARIAN_PROMPT_METADATA, explore: EXPLORE_PROMPT_METADATA, "multimodal-looker": MULTIMODAL_LOOKER_PROMPT_METADATA, metis: metisPromptMetadata, momus: momusPromptMetadata, athena: ATHENA_PROMPT_METADATA, atlas: atlasPromptMetadata, } export async function createBuiltinAgents( disabledAgents: string[] = [], agentOverrides: AgentOverrides = {}, directory?: string, systemDefaultModel?: string, categories?: CategoriesConfig, gitMasterConfig?: GitMasterConfig, discoveredSkills: LoadedSkill[] = [], customAgentSummaries?: unknown, browserProvider?: BrowserAutomationProvider, uiSelectedModel?: string, disabledSkills?: Set, useTaskSystem = false, disableOmoEnv = false, councilConfig?: CouncilConfig ): Promise> { const connectedProviders = readConnectedProvidersCache() const providerModelsConnected = connectedProviders ? (readProviderModelsCache()?.connected ?? []) : [] const mergedConnectedProviders = Array.from( new Set([...(connectedProviders ?? []), ...providerModelsConnected]) ) // IMPORTANT: Do NOT call OpenCode client APIs during plugin initialization. // This function is called from config handler, and calling client API causes deadlock. // See: https://github.com/code-yeongyu/oh-my-opencode/issues/1301 const availableModels = await fetchAvailableModels(undefined, { connectedProviders: mergedConnectedProviders.length > 0 ? mergedConnectedProviders : undefined, }) const isFirstRunNoCache = availableModels.size === 0 && mergedConnectedProviders.length === 0 const result: Record = {} const mergedCategories = mergeCategories(categories) const availableCategories: AvailableCategory[] = Object.entries(mergedCategories).map(([name]) => ({ name, description: categories?.[name]?.description ?? CATEGORY_DESCRIPTIONS[name] ?? "General tasks", })) const availableSkills = buildAvailableSkills(discoveredSkills, browserProvider, disabledSkills) // Collect general agents first (for availableAgents), but don't add to result yet const { pendingAgentConfigs, availableAgents } = collectPendingBuiltinAgents({ agentSources, agentMetadata, disabledAgents, agentOverrides, directory, systemDefaultModel, mergedCategories, gitMasterConfig, browserProvider, uiSelectedModel, availableModels, disabledSkills, disableOmoEnv, }) const registeredAgents = parseRegisteredAgentSummaries(customAgentSummaries) const builtinAgentNames = new Set(Object.keys(agentSources).map((name) => name.toLowerCase())) const disabledAgentNames = new Set(disabledAgents.map((name) => name.toLowerCase())) for (const agent of registeredAgents) { const lowerName = agent.name.toLowerCase() if (builtinAgentNames.has(lowerName)) continue if (disabledAgentNames.has(lowerName)) continue if (availableAgents.some((availableAgent) => availableAgent.name.toLowerCase() === lowerName)) continue availableAgents.push({ name: agent.name, description: agent.description, metadata: buildCustomAgentMetadata(agent.name, agent.description), }) } const sisyphusConfig = maybeCreateSisyphusConfig({ disabledAgents, agentOverrides, uiSelectedModel, availableModels, systemDefaultModel, isFirstRunNoCache, availableAgents, availableSkills, availableCategories, mergedCategories, directory, userCategories: categories, useTaskSystem, disableOmoEnv, }) if (sisyphusConfig) { result["sisyphus"] = sisyphusConfig } const hephaestusConfig = maybeCreateHephaestusConfig({ disabledAgents, agentOverrides, availableModels, systemDefaultModel, isFirstRunNoCache, availableAgents, availableSkills, availableCategories, mergedCategories, directory, useTaskSystem, disableOmoEnv, }) if (hephaestusConfig) { result["hephaestus"] = hephaestusConfig } // Add pending agents after sisyphus and hephaestus to maintain order for (const [name, config] of pendingAgentConfigs) { result[name] = config } const atlasConfig = maybeCreateAtlasConfig({ disabledAgents, agentOverrides, uiSelectedModel, availableModels, systemDefaultModel, availableAgents, availableSkills, mergedCategories, directory, userCategories: categories, }) if (atlasConfig) { result["atlas"] = atlasConfig } if (councilConfig?.members && councilConfig.members.length >= 2 && result["athena"]) { const { agents: councilAgents, registeredKeys, skippedMembers } = registerCouncilMemberAgents(councilConfig) for (const [key, config] of Object.entries(councilAgents)) { result[key] = config } if (registeredKeys.length > 0) { const memberList = registeredKeys.map((key) => `- "${key}"`).join("\n") let councilTaskInstructions = `\n\n## Registered Council Members\n\nUse these as subagent_type in task calls:\n\n${memberList}` if (skippedMembers.length > 0) { const skipDetails = skippedMembers.map((m) => `- **${m.name}**: ${m.reason}`).join("\n") councilTaskInstructions += `\n\n> **Note**: Some configured council members were skipped:\n${skipDetails}` log("[builtin-agents] Some council members were skipped during registration", { skippedMembers }) } result["athena"] = { ...result["athena"], prompt: (result["athena"].prompt ?? "") + councilTaskInstructions, } } else { result["athena"] = appendMissingCouncilPrompt(result["athena"], skippedMembers) } } else if (councilConfig?.members && councilConfig.members.length >= 2 && !result["athena"]) { log("[builtin-agents] Skipping council member registration — Athena is disabled") } else if (result["athena"]) { result["athena"] = appendMissingCouncilPrompt(result["athena"]) } return result }