import { Container } from "./container.js"; import { ForegroundTaskManager } from "../managers/foregroundTaskManager.js"; import { BackgroundTaskManager } from "../managers/backgroundTaskManager.js"; import { MessageQueue } from "../managers/messageQueue.js"; import { TaskManager } from "../services/taskManager.js"; import { MessageManager } from "../managers/messageManager.js"; import { AIManager } from "../managers/aiManager.js"; import { ToolManager } from "../managers/toolManager.js"; import { McpManager } from "../managers/mcpManager.js"; import { LspManager } from "../managers/lspManager.js"; import { PermissionManager } from "../managers/permissionManager.js"; import { PlanManager } from "../managers/planManager.js"; import { HookManager } from "../managers/hookManager.js"; import { SkillManager } from "../managers/skillManager.js"; import { SlashCommandManager } from "../managers/slashCommandManager.js"; import { PluginManager } from "../managers/pluginManager.js"; import { BangManager } from "../managers/bangManager.js"; import { CronManager } from "../managers/cronManager.js"; import { GoalManager } from "../managers/goalManager.js"; import { WorkflowManager } from "../managers/workflowManager.js"; import { MemoryRuleManager } from "../managers/MemoryRuleManager.js"; import { ReversionManager } from "../managers/reversionManager.js"; import { SubagentManager } from "../managers/subagentManager.js"; import { ForkedAgentManager } from "../managers/forkedAgentManager.js"; import { LiveConfigManager } from "../managers/liveConfigManager.js"; import { ConfigurationService } from "../services/configurationService.js"; import { ReversionService } from "../services/reversionService.js"; import { MemoryService } from "../services/memory.js"; import { AutoMemoryService } from "../services/autoMemoryService.js"; import { USER_MEMORY_FILE } from "./constants.js"; import { getGitMainRepoRoot } from "./gitUtils.js"; import type { AgentOptions, McpServerConfig } from "../types/index.js"; import type { PermissionMode, Usage, Task, BackgroundTask, ToolPermissionContext, } from "../types/index.js"; import { logger } from "./globalLogger.js"; import { authService } from "../services/authService.js"; import { remoteSettingsService } from "../services/remoteSettingsService.js"; export interface AgentContainerSetupOptions { options: AgentOptions; workdir: string; configurationService: ConfigurationService; systemPrompt?: string; stream: boolean; // Callbacks to Agent methods onBackgroundTasksChange: (tasks: BackgroundTask[]) => void; onTasksChange: (tasks: Task[]) => void; onPermissionModeChange: (mode: PermissionMode) => void; handlePlanModeTransition: (mode: PermissionMode) => void; setPermissionMode: (mode: PermissionMode) => void; addPermissionRule: (rule: string) => Promise; addUsage: (usage: Usage) => void; } export function setupAgentContainer( setupOptions: AgentContainerSetupOptions, ): Container { const { options, workdir, configurationService, systemPrompt, stream, onBackgroundTasksChange, onTasksChange, onPermissionModeChange, handlePlanModeTransition, setPermissionMode, addPermissionRule, addUsage, } = setupOptions; const callbacks = options.callbacks || {}; const container = new Container(); container.register("AgentOptions", options); container.register("Workdir", workdir); if (options.worktreeName) { container.register("WorktreeName", options.worktreeName); container.register("MainRepoRoot", getGitMainRepoRoot(workdir)); } // Per-agent worktree session state (EnterWorktree/ExitWorktree). Stored in the // container so it is isolated per session, not shared process-wide. container.register("WorktreeSession", null); const messageQueue = new MessageQueue(); container.register("MessageQueue", messageQueue); const foregroundTaskManager = new ForegroundTaskManager(container); container.register("ForegroundTaskManager", foregroundTaskManager); container.register("ConfigurationService", configurationService); const memoryService = new MemoryService(container); container.register("MemoryService", memoryService); const autoMemoryService = new AutoMemoryService(container); container.register("AutoMemoryService", autoMemoryService); const memoryRuleManager = new MemoryRuleManager(container, { workdir }); container.register("MemoryRuleManager", memoryRuleManager); const messageManager = new MessageManager(container, { callbacks: { ...callbacks, onSessionIdChange: (sessionId) => { const taskManager = container.get("TaskManager"); if (taskManager) { taskManager.syncWithSession().catch((error) => { logger.error("Failed to sync task list with session:", error); }); } memoryService.clearCache(); callbacks.onSessionIdChange?.(sessionId); }, }, workdir, }); container.register("MessageManager", messageManager); const resolvedTaskListId = process.env.WAVE_TASK_LIST_ID || messageManager.getRootSessionId(); const taskManager = new TaskManager(container, resolvedTaskListId); container.register("TaskManager", taskManager); taskManager.on("tasksChange", async () => { const tasks = await taskManager.listTasks(); onTasksChange(tasks); }); taskManager.cleanupOldTaskLists(30).catch((error) => { logger.error("Failed to cleanup old task lists:", error); }); const backgroundTaskManager = new BackgroundTaskManager(container, { callbacks: { ...callbacks, onBackgroundTasksChange: (tasks) => { onBackgroundTasksChange(tasks); callbacks.onBackgroundTasksChange?.(tasks); }, }, workdir, }); container.register("BackgroundTaskManager", backgroundTaskManager); if (options.serverUrl) { authService.setServerUrl(options.serverUrl); } const mcpManager = new McpManager(container, { callbacks, mcpServers: options.mcpServers as | Record | undefined, }); container.register("McpManager", mcpManager); // Wire up auth change callback to refresh/clear remote settings authService.onAuthChange(async (event) => { if (event === "login") { await remoteSettingsService.refresh(); } else if (event === "logout") { remoteSettingsService.clear(); } }); const lspManager = options.lspManager || new LspManager(container); container.register("LspManager", lspManager); const permissionManager = new PermissionManager(container, { instanceAllowedRules: options.allowedTools, instanceDeniedRules: options.disallowedTools, }); if (configurationService.resolveAutoMemoryEnabled()) { const autoMemoryDir = memoryService.getAutoMemoryDirectory(workdir); permissionManager.addSystemAdditionalDirectory(autoMemoryDir); permissionManager.addSystemAdditionalDirectory(USER_MEMORY_FILE); } container.register("PermissionManager", permissionManager); permissionManager.setOnConfiguredPermissionModeChange((mode) => { handlePlanModeTransition(mode); onPermissionModeChange(mode); }); const planManager = new PlanManager(container); container.register("PlanManager", planManager); const hookManager = new HookManager(container, workdir); if (options.hooks) { hookManager.loadConfiguration(options.hooks); } container.register("HookManager", hookManager); const skillManager = new SkillManager(container, { workdir, watch: options.watchSkills ?? true, }); container.register("SkillManager", skillManager); const rootSessionId = messageManager.getRootSessionId(); const reversionService = new ReversionService(rootSessionId); container.register("ReversionService", reversionService); reversionService.cleanupOldSessions(30).catch((error) => { logger.error("Failed to cleanup old file history:", error); }); const reversionManager = new ReversionManager(container); container.register("ReversionManager", reversionManager); const canUseToolWithPermissionRequest = options.canUseTool ? async (context: ToolPermissionContext) => { try { const results = await hookManager.executeHooks("PermissionRequest", { event: "PermissionRequest", projectDir: workdir, timestamp: new Date(), sessionId: messageManager.getSessionId(), transcriptPath: messageManager.getTranscriptPath(), cwd: workdir, toolName: context.toolName, toolInput: context.toolInput, planFilePath: permissionManager.getPlanFilePath(), env: Object.fromEntries( Object.entries(process.env).filter((e) => e[1] !== undefined), ) as Record, }); if (results.length > 0) { const processResult = hookManager.processHookResults( "PermissionRequest", results, messageManager, ); if (processResult.shouldBlock) { return { behavior: "deny", message: processResult.errorMessage || "Permission denied by hook execution", }; } } } catch (error) { logger.warn("Failed to execute permission request hooks", { toolName: context.toolName, error: error instanceof Error ? error.message : String(error), }); } const decision = await options.canUseTool!(context); const planFilePath = permissionManager.getPlanFilePath(); if (decision.newPermissionMode) { setPermissionMode(decision.newPermissionMode); } if (decision.newPermissionRule) { await addPermissionRule(decision.newPermissionRule); } if (decision.clearContext) { messageManager.clearMessages(); if (planFilePath) { messageManager.addUserMessage({ content: `Implement the plan at ${planFilePath}`, }); } } return decision; } : undefined; const toolManager = new ToolManager({ container, tools: options.tools, customTools: options.customTools, }); container.register("ToolManager", toolManager); container.register("PermissionMode", options.permissionMode); // Register the full permission mode transition (Agent.setPermissionMode) so // tools like EnterPlanMode can trigger planManager.handlePlanModeTransition // and onPermissionModeChange even in bypass mode (where canUseTool is skipped). container.register("PermissionModeTransition", setPermissionMode); logger.info("Registering CanUseToolCallback", { hasCallback: !!canUseToolWithPermissionRequest, }); container.register("CanUseToolCallback", canUseToolWithPermissionRequest); const liveConfigManager = new LiveConfigManager(container, { workdir, onReload: () => { const models = configurationService.getConfiguredModels(); callbacks.onConfiguredModelsChange?.(models); }, }); container.register("LiveConfigManager", liveConfigManager); // Wire up remote settings hot-update: when polling detects changed settings, // reload configuration so admin changes propagate to the running agent. remoteSettingsService.onSettingsUpdate(async () => { await liveConfigManager.reload(); }); const subagentManager = new SubagentManager(container, { workdir, callbacks: { onSubagentUserMessageAdded: callbacks.onSubagentUserMessageAdded, onSubagentAssistantMessageAdded: callbacks.onSubagentAssistantMessageAdded, onSubagentAssistantContentUpdated: callbacks.onSubagentAssistantContentUpdated, onSubagentAssistantReasoningUpdated: callbacks.onSubagentAssistantReasoningUpdated, onSubagentToolBlockUpdated: callbacks.onSubagentToolBlockUpdated, onSubagentMessagesChange: (subagentId, messages) => { callbacks.onSubagentMessagesChange?.(subagentId, messages); }, onSubagentLatestTotalTokensChange: callbacks.onSubagentLatestTotalTokensChange, }, onUsageAdded: (usage: Usage) => addUsage(usage), stream, }); container.register("SubagentManager", subagentManager); const forkedAgentManager = new ForkedAgentManager(container); container.register("ForkedAgentManager", forkedAgentManager); const aiManager = new AIManager(container, { callbacks: { ...callbacks, onUsageAdded: (usage: Usage) => addUsage(usage), }, workdir, systemPrompt, stream, }); container.register("AIManager", aiManager); const slashCommandManager = new SlashCommandManager(container, { workdir }); container.register("SlashCommandManager", slashCommandManager); slashCommandManager.initialize(); const pluginManager = new PluginManager(container, { workdir }); container.register("PluginManager", pluginManager); const bangManager = new BangManager(container, { workdir }); container.register("BangManager", bangManager); const cronManager = new CronManager(container, messageManager.getSessionId()); container.register("CronManager", cronManager); cronManager.start(); const goalManager = new GoalManager(container); container.register("GoalManager", goalManager); const workflowManager = new WorkflowManager(container); container.register("WorkflowManager", workflowManager); return container; }