import { createBuiltinAgents } from "../agents"; import { createSisyphusJuniorAgentWithOverrides } from "../agents/sisyphus-junior"; import type { OhMyOpenCodeConfig } from "../config"; import { isTaskSystemEnabled, log, migrateAgentConfig } from "../shared"; import { getAgentConfigKey, getAgentDisplayName, normalizeAgentForPromptKey, } from "../shared/agent-display-names"; import { AGENT_NAME_MAP } from "../shared/migration"; import { setDefaultAgentForSort } from "../shared/agent-sort-shim"; import { registerAgentName } from "../features/claude-code-session-state"; import { discoverConfigSourceSkills, discoverGlobalAgentsSkills, discoverOpencodeGlobalSkills, discoverOpencodeProjectSkills, discoverProjectAgentsSkills, discoverProjectClaudeSkills, discoverUserClaudeSkills, } from "../features/opencode-skill-loader"; import { loadProjectAgents, loadUserAgents, loadOpencodeGlobalAgents, loadOpencodeProjectAgents, loadAgentDefinitions, readOpencodeConfigAgents, } from "../features/claude-code-agent-loader"; import type { PluginComponents } from "./plugin-components-loader"; import { reorderAgentsByPriority } from "./agent-priority-order"; import { remapAgentKeysToDisplayNames } from "./agent-key-remapper"; import { createProtectedAgentNameSet, filterProtectedAgentOverrides, } from "./agent-override-protection"; import { buildPrometheusAgentConfig } from "./prometheus-agent-config-builder"; import { buildPlanDemoteConfig } from "./plan-model-inheritance"; type AgentConfigRecord = Record | undefined> & { build?: Record; plan?: Record; }; function getConfiguredDefaultAgent(config: Record): string | undefined { const defaultAgent = config.default_agent; if (typeof defaultAgent !== "string") return undefined; const trimmedDefaultAgent = defaultAgent.trim(); return trimmedDefaultAgent.length > 0 ? trimmedDefaultAgent : undefined; } export async function applyAgentConfig(params: { config: Record; pluginConfig: OhMyOpenCodeConfig; ctx: { directory: string; client?: any }; pluginComponents: PluginComponents; }): Promise> { const migratedDisabledAgents = (params.pluginConfig.disabled_agents ?? []).map( (agent) => { return AGENT_NAME_MAP[agent.toLowerCase()] ?? AGENT_NAME_MAP[agent] ?? agent; }, ) as typeof params.pluginConfig.disabled_agents; const includeClaudeSkillsForAwareness = params.pluginConfig.claude_code?.skills ?? true; const [ discoveredConfigSourceSkills, discoveredUserSkills, discoveredProjectSkills, discoveredProjectAgentsSkills, discoveredOpencodeGlobalSkills, discoveredOpencodeProjectSkills, discoveredGlobalAgentsSkills, ] = await Promise.all([ discoverConfigSourceSkills({ config: params.pluginConfig.skills, configDir: params.ctx.directory, }), includeClaudeSkillsForAwareness ? discoverUserClaudeSkills() : Promise.resolve([]), includeClaudeSkillsForAwareness ? discoverProjectClaudeSkills(params.ctx.directory) : Promise.resolve([]), includeClaudeSkillsForAwareness ? discoverProjectAgentsSkills(params.ctx.directory) : Promise.resolve([]), discoverOpencodeGlobalSkills(), discoverOpencodeProjectSkills(params.ctx.directory), includeClaudeSkillsForAwareness ? discoverGlobalAgentsSkills() : Promise.resolve([]), ]); const allDiscoveredSkills = [ ...discoveredConfigSourceSkills, ...discoveredOpencodeProjectSkills, ...discoveredProjectSkills, ...discoveredProjectAgentsSkills, ...discoveredOpencodeGlobalSkills, ...discoveredUserSkills, ...discoveredGlobalAgentsSkills, ]; const browserProvider = params.pluginConfig.browser_automation_engine?.provider ?? "playwright"; const currentModel = params.config.model as string | undefined; const disabledSkills = new Set(params.pluginConfig.disabled_skills ?? []); const useTaskSystem = isTaskSystemEnabled(params.pluginConfig); const disableOmoEnv = params.pluginConfig.experimental?.disable_omo_env ?? false; const includeClaudeAgents = params.pluginConfig.claude_code?.agents ?? true; const userAgents = includeClaudeAgents ? loadUserAgents() : {}; const projectAgents = includeClaudeAgents ? loadProjectAgents(params.ctx.directory) : {}; const opencodeGlobalAgents = loadOpencodeGlobalAgents(); const opencodeProjectAgents = loadOpencodeProjectAgents(params.ctx.directory); const rawPluginAgents = params.pluginComponents.agents; const agentDefinitionAgents = params.pluginConfig.agent_definitions ? loadAgentDefinitions(params.pluginConfig.agent_definitions, "definition-file") : {}; const opencodeConfigAgents = readOpencodeConfigAgents(params.ctx.directory); const pluginAgents = Object.fromEntries( Object.entries(rawPluginAgents).map(([key, value]) => { if (!value) return [key, value]; const migrated = migrateAgentConfig(value as Record); if (!migrated.mode) migrated.mode = "subagent"; return [key, migrated]; }), ); const configAgent = params.config.agent as AgentConfigRecord | undefined; const customAgentSummaries = [ ...Object.entries(configAgent ?? {}), ...Object.entries(userAgents), ...Object.entries(projectAgents), ...Object.entries(opencodeGlobalAgents), ...Object.entries(opencodeProjectAgents), ...Object.entries(pluginAgents).filter(([, config]) => config !== undefined), ...Object.entries(agentDefinitionAgents), ...Object.entries(opencodeConfigAgents), ] .filter(([, config]) => config != null) .map(([name, config]) => ({ name, description: typeof (config as Record)?.description === "string" ? ((config as Record).description as string) : "", })); log( "[agent-config-handler] Agent sources loaded", { user: Object.keys(userAgents).length, project: Object.keys(projectAgents).length, opencodeGlobal: Object.keys(opencodeGlobalAgents).length, opencodeProject: Object.keys(opencodeProjectAgents).length, plugin: Object.keys(pluginAgents).length, agentDefinitions: Object.keys(agentDefinitionAgents).length, opencodeConfig: Object.keys(opencodeConfigAgents).length, config: Object.keys(configAgent ?? {}).length, } ); const builtinAgents = await createBuiltinAgents( migratedDisabledAgents, params.pluginConfig.agents, params.ctx.directory, currentModel, params.pluginConfig.categories, params.pluginConfig.git_master, allDiscoveredSkills, customAgentSummaries, browserProvider, currentModel, disabledSkills, useTaskSystem, disableOmoEnv, params.pluginConfig.team_mode?.enabled ?? false, ); const disabledAgentNames = new Set( (migratedDisabledAgents ?? []).map(a => a.toLowerCase()) ); const filterDisabledAgents = (agents: Record) => Object.fromEntries( Object.entries(agents).filter(([name]) => !disabledAgentNames.has(name.toLowerCase())) ); const isSisyphusEnabled = params.pluginConfig.sisyphus_agent?.disabled !== true; const builderEnabled = params.pluginConfig.sisyphus_agent?.default_builder_enabled ?? false; const plannerEnabled = params.pluginConfig.sisyphus_agent?.planner_enabled ?? true; const replacePlan = params.pluginConfig.sisyphus_agent?.replace_plan ?? true; const shouldDemotePlan = plannerEnabled && replacePlan; const configuredDefaultAgent = getConfiguredDefaultAgent(params.config); if (isSisyphusEnabled && builtinAgents.sisyphus) { if (configuredDefaultAgent) { const configKey = getAgentConfigKey(configuredDefaultAgent); const runtimeConfigKey = normalizeAgentForPromptKey(configuredDefaultAgent) ?? configKey; (params.config as { default_agent?: string }).default_agent = getAgentDisplayName(runtimeConfigKey); } else { (params.config as { default_agent?: string }).default_agent = getAgentDisplayName("sisyphus"); } // Assembly order: Sisyphus -> Hephaestus -> Prometheus -> Atlas const agentConfig: Record = { sisyphus: builtinAgents.sisyphus, }; if (builtinAgents.hephaestus) { agentConfig["hephaestus"] = builtinAgents.hephaestus; } if (plannerEnabled) { const prometheusOverride = params.pluginConfig.agents?.["prometheus"] as | (Record & { prompt_append?: string }) | undefined; agentConfig["prometheus"] = await buildPrometheusAgentConfig({ configAgentPlan: configAgent?.plan, pluginPrometheusOverride: prometheusOverride, userCategories: params.pluginConfig.categories, currentModel, disabledTools: params.pluginConfig.disabled_tools, }); } if (builtinAgents.atlas) { agentConfig["atlas"] = builtinAgents.atlas; } agentConfig["sisyphus-junior"] = createSisyphusJuniorAgentWithOverrides( params.pluginConfig.agents?.["sisyphus-junior"], (builtinAgents.atlas as { model?: string } | undefined)?.model, useTaskSystem, ); if (builderEnabled) { const { name: _buildName, ...buildConfigWithoutName } = configAgent?.build ?? {}; const migratedBuildConfig = migrateAgentConfig( buildConfigWithoutName as Record, ); const override = params.pluginConfig.agents?.["OpenCode-Builder"]; const base = { ...migratedBuildConfig, description: `${(configAgent?.build?.description as string) ?? "Build agent"} (OpenCode default)`, }; agentConfig["OpenCode-Builder"] = override ? { ...base, ...override } : base; } const filteredConfigAgents = configAgent ? Object.fromEntries( Object.entries(configAgent) .filter(([key]) => { if (key === "build") return false; if (key === "plan" && shouldDemotePlan) return false; if (key in builtinAgents) return false; return true; }) .map(([key, value]) => { if (!value) return [key, value]; const migrated = migrateAgentConfig(value as Record); if (!migrated.mode) migrated.mode = "subagent"; return [key, migrated]; }), ) : {}; const migratedBuild = configAgent?.build ? migrateAgentConfig(configAgent.build as Record) : {}; const planDemoteConfig = shouldDemotePlan ? buildPlanDemoteConfig( agentConfig["prometheus"] as Record | undefined, params.pluginConfig.agents?.plan as Record | undefined, ) : undefined; const protectedBuiltinAgentNames = createProtectedAgentNameSet([ ...Object.keys(agentConfig), ...Object.keys(builtinAgents), ]); const filteredUserAgents = filterProtectedAgentOverrides( userAgents, protectedBuiltinAgentNames, ); const filteredProjectAgents = filterProtectedAgentOverrides( projectAgents, protectedBuiltinAgentNames, ); const filteredPluginAgents = filterProtectedAgentOverrides( pluginAgents, protectedBuiltinAgentNames, ); const filteredOpencodeGlobalAgents = filterProtectedAgentOverrides( opencodeGlobalAgents, protectedBuiltinAgentNames, ); const filteredOpencodeProjectAgents = filterProtectedAgentOverrides( opencodeProjectAgents, protectedBuiltinAgentNames, ); const filteredAgentDefinitionAgents = filterProtectedAgentOverrides( agentDefinitionAgents, protectedBuiltinAgentNames, ); const filteredOpencodeConfigAgents = filterProtectedAgentOverrides( opencodeConfigAgents, protectedBuiltinAgentNames, ); params.config.agent = { ...agentConfig, ...Object.fromEntries( Object.entries(builtinAgents).filter( ([key]) => key !== "sisyphus" && key !== "hephaestus" && key !== "atlas", ), ), // Precedence: later entries override earlier (project > global > user > plugin) ...filterDisabledAgents(filteredPluginAgents), ...filterDisabledAgents(filteredUserAgents), ...filterDisabledAgents(filteredOpencodeGlobalAgents), ...filterDisabledAgents(filteredProjectAgents), ...filterDisabledAgents(filteredOpencodeProjectAgents), ...filterDisabledAgents(filteredAgentDefinitionAgents), ...filterDisabledAgents(filteredOpencodeConfigAgents), ...filteredConfigAgents, build: { ...migratedBuild, mode: "subagent", hidden: true }, ...(planDemoteConfig ? { plan: planDemoteConfig } : {}), }; } else { const protectedBuiltinAgentNames = createProtectedAgentNameSet( Object.keys(builtinAgents), ); const filteredUserAgents = filterProtectedAgentOverrides( userAgents, protectedBuiltinAgentNames, ); const filteredProjectAgents = filterProtectedAgentOverrides( projectAgents, protectedBuiltinAgentNames, ); const filteredPluginAgents = filterProtectedAgentOverrides( pluginAgents, protectedBuiltinAgentNames, ); const filteredOpencodeGlobalAgents = filterProtectedAgentOverrides( opencodeGlobalAgents, protectedBuiltinAgentNames, ); const filteredOpencodeProjectAgents = filterProtectedAgentOverrides( opencodeProjectAgents, protectedBuiltinAgentNames, ); const filteredAgentDefinitionAgents = filterProtectedAgentOverrides( agentDefinitionAgents, protectedBuiltinAgentNames, ); const filteredOpencodeConfigAgents = filterProtectedAgentOverrides( opencodeConfigAgents, protectedBuiltinAgentNames, ); const defaultedConfigAgents = configAgent ? Object.fromEntries( Object.entries(configAgent).map(([key, value]) => { if (!value) return [key, value]; const migrated = migrateAgentConfig(value as Record); if (!migrated.mode) migrated.mode = "subagent"; return [key, migrated]; }), ) : {}; params.config.agent = { ...builtinAgents, // Precedence: later entries override earlier (project > global > user > plugin) ...filterDisabledAgents(filteredPluginAgents), ...filterDisabledAgents(filteredUserAgents), ...filterDisabledAgents(filteredOpencodeGlobalAgents), ...filterDisabledAgents(filteredProjectAgents), ...filterDisabledAgents(filteredOpencodeProjectAgents), ...filterDisabledAgents(filteredAgentDefinitionAgents), ...filterDisabledAgents(filteredOpencodeConfigAgents), ...defaultedConfigAgents, }; } if (params.config.agent) { params.config.agent = remapAgentKeysToDisplayNames( params.config.agent as Record, ); params.config.agent = reorderAgentsByPriority( params.config.agent as Record, params.pluginConfig.agent_order, ); } const resolvedDefault = (params.config as { default_agent?: string }).default_agent; if (resolvedDefault) { setDefaultAgentForSort(resolvedDefault); } const agentResult = params.config.agent as Record; for (const name of Object.keys(agentResult)) { registerAgentName(name); } log("[config-handler] agents loaded", { agentKeys: Object.keys(agentResult) }); return agentResult; }