import * as aiService from "../services/aiService.js";
import { convertMessagesForAPI } from "../utils/convertMessagesForAPI.js";
import { parseTaskNotificationXml } from "../utils/notificationXml.js";
import { calculateComprehensiveTotalTokens } from "../utils/tokenCalculation.js";
import * as fs from "node:fs/promises";
import { buildSystemPrompt } from "../prompts/index.js";
import { logger } from "../utils/globalLogger.js";
export class AIManager {
    // Service overrides
    constructor(container, options) {
        this.container = container;
        this.isLoading = false;
        this.abortController = null;
        this.toolAbortController = null;
        this.isCompressing = false;
        this.workdir = options.workdir;
        this.systemPrompt = options.systemPrompt;
        this.subagentType = options.subagentType; // Store subagent type
        this.stream = options.stream ?? true; // Default to true if not specified
        this.callbacks = options.callbacks ?? {};
        this.modelOverride = options.modelOverride;
    }
    get toolManager() {
        return this.container.get("ToolManager");
    }
    get messageManager() {
        return this.container.get("MessageManager");
    }
    get memoryService() {
        return this.container.get("MemoryService");
    }
    get taskManager() {
        return this.container.get("TaskManager");
    }
    get backgroundTaskManager() {
        return this.container.get("BackgroundTaskManager");
    }
    get hookManager() {
        return this.container.get("HookManager");
    }
    get reversionManager() {
        return this.container.get("ReversionManager");
    }
    get permissionManager() {
        return this.container.get("PermissionManager");
    }
    get configurationService() {
        return this.container.get("ConfigurationService");
    }
    // Getter methods for accessing dynamic configuration
    getGatewayConfig() {
        return this.configurationService.resolveGatewayConfig();
    }
    getModelConfig() {
        const permissionMode = this.container.has("PermissionMode")
            ? this.container.get("PermissionMode")
            : undefined;
        const parentModelConfig = this.configurationService.resolveModelConfig(undefined, undefined, undefined, permissionMode);
        let modelToUse;
        if (this.modelOverride) {
            if (this.modelOverride === "fastModel") {
                modelToUse = parentModelConfig.fastModel;
            }
            else if (this.modelOverride !== "inherit") {
                modelToUse = this.modelOverride;
            }
        }
        return this.configurationService.resolveModelConfig(modelToUse, undefined, undefined, permissionMode);
    }
    getMaxInputTokens() {
        return this.configurationService.resolveMaxInputTokens();
    }
    getLanguage() {
        return this.configurationService.resolveLanguage();
    }
    getAutoMemoryEnabled() {
        return this.configurationService.resolveAutoMemoryEnabled();
    }
    /**
     * Get filtered tool configuration based on tools list
     */
    getFilteredToolsConfig() {
        // Get available subagents and skills for dynamic prompts
        const availableSubagents = this.subagentManager?.getConfigurations();
        const availableSkills = this.skillManager
            ?.getAvailableSkills()
            .filter((skill) => !skill.disableModelInvocation);
        return this.toolManager.getToolsConfig({
            availableSubagents,
            availableSkills,
            workdir: this.workdir,
            isSubagent: !!this.subagentType,
        });
    }
    setIsLoading(isLoading) {
        this.isLoading = isLoading;
        const options = this.container.get("AgentOptions");
        options?.callbacks?.onLoadingChange?.(isLoading);
    }
    abortAIMessage() {
        // Interrupt AI service
        if (this.abortController) {
            try {
                this.abortController.abort();
            }
            catch (error) {
                logger?.error("Failed to abort AI service:", error);
            }
        }
        // Interrupt tool execution
        if (this.toolAbortController) {
            try {
                this.toolAbortController.abort();
            }
            catch (error) {
                logger?.error("Failed to abort tool execution:", error);
            }
        }
        this.setIsLoading(false);
    }
    // Helper method to generate compactParams
    generateCompactParams(toolName, toolArgs) {
        try {
            const toolPlugin = this.toolManager
                .list()
                .find((plugin) => plugin.name === toolName);
            if (toolPlugin?.formatCompactParams) {
                const context = {
                    workdir: this.workdir,
                    taskManager: this.taskManager,
                };
                return toolPlugin.formatCompactParams(toolArgs, context);
            }
        }
        catch (error) {
            logger?.warn("Failed to generate compactParams", error);
        }
        return "";
    }
    // Private method to handle token statistics and message compression
    async handleTokenUsageAndCompression(usage, abortController, model) {
        if (!usage)
            return;
        // Update token statistics - display comprehensive token usage including cache tokens
        const comprehensiveTotalTokens = calculateComprehensiveTotalTokens(usage);
        this.messageManager.setlatestTotalTokens(comprehensiveTotalTokens);
        // Check if token limit exceeded - use injected configuration
        if (usage.total_tokens +
            (usage.cache_read_input_tokens || 0) +
            (usage.cache_creation_input_tokens || 0) >
            this.getMaxInputTokens()) {
            logger?.debug(`Token usage exceeded ${this.getMaxInputTokens()}, compressing messages...`);
            // Check if messages need compression
            const messagesToCompress = this.messageManager.getMessages();
            // If there are messages to compress, perform compression
            if (messagesToCompress.length > 0) {
                const recentChatMessages = convertMessagesForAPI(messagesToCompress);
                // Save session before compression to preserve original messages
                await this.messageManager.saveSession();
                this.setIsCompressing(true);
                try {
                    const compressionResult = await aiService.compressMessages({
                        gatewayConfig: this.getGatewayConfig(),
                        modelConfig: this.getModelConfig(),
                        messages: recentChatMessages,
                        abortSignal: abortController.signal,
                        model: model,
                    });
                    // Handle usage tracking for compression operations
                    let compressionUsage;
                    if (compressionResult.usage) {
                        compressionUsage = {
                            prompt_tokens: compressionResult.usage.prompt_tokens,
                            completion_tokens: compressionResult.usage.completion_tokens,
                            total_tokens: compressionResult.usage.total_tokens,
                            model: model || this.getModelConfig().model,
                            operation_type: "compress",
                        };
                    }
                    // Execute message reconstruction and sessionId update after compression
                    this.messageManager.compressMessagesAndUpdateSession(compressionResult.content, compressionUsage);
                    // Notify Agent to add to usage tracking
                    if (compressionUsage && this.callbacks?.onUsageAdded) {
                        this.callbacks.onUsageAdded(compressionUsage);
                    }
                    logger?.debug(`Successfully compressed ${messagesToCompress.length} messages and updated session`);
                }
                catch (compressError) {
                    logger?.error("Failed to compress messages:", compressError);
                    this.messageManager.addErrorBlock(`Failed to compress conversation history: ${compressError instanceof Error ? compressError.message : String(compressError)}. You may encounter context limit issues.`);
                }
                finally {
                    this.setIsCompressing(false);
                }
            }
        }
    }
    getIsCompressing() {
        return this.isCompressing;
    }
    setIsCompressing(isCompressing) {
        if (this.isCompressing !== isCompressing) {
            this.isCompressing = isCompressing;
            this.callbacks.onCompressionStateChange?.(isCompressing);
        }
    }
    get subagentManager() {
        return this.container.get("SubagentManager");
    }
    get skillManager() {
        return this.container.get("SkillManager");
    }
    async sendAIMessage(options = {}) {
        const { recursionDepth = 0, model, allowedRules, maxTokens } = options;
        // Only check isLoading for the initial call (recursionDepth === 0)
        if (recursionDepth === 0 && this.isLoading) {
            return;
        }
        // Set loading state early for the initial call, before any async work
        if (recursionDepth === 0) {
            this.setIsLoading(true);
            if (allowedRules && allowedRules.length > 0) {
                this.permissionManager?.addTemporaryRules(allowedRules);
            }
        }
        // Scan for file mentions in the last user message
        if (recursionDepth === 0) {
            const messages = this.messageManager.getMessages();
            const lastMessage = messages[messages.length - 1];
            if (lastMessage && lastMessage.role === "user") {
                for (const block of lastMessage.blocks) {
                    if (block.type === "text") {
                        const content = block.content;
                        const fileMentionRegex = /(?:^|\s)@([\w.\-/]+)/g;
                        let match;
                        while ((match = fileMentionRegex.exec(content)) !== null) {
                            const filePath = match[1];
                            this.messageManager.touchFile(filePath);
                        }
                    }
                }
            }
        }
        // Save session in each recursion to ensure message persistence
        await this.messageManager.saveSession();
        // Only create new AbortControllers for the initial call (recursionDepth === 0)
        // For recursive calls, reuse existing controllers to maintain abort signal
        let abortController;
        let toolAbortController;
        if (recursionDepth === 0) {
            // Create new AbortControllers for initial call
            abortController = new AbortController();
            this.abortController = abortController;
            toolAbortController = new AbortController();
            this.toolAbortController = toolAbortController;
        }
        else {
            // Reuse existing controllers for recursive calls
            abortController = this.abortController;
            toolAbortController = this.toolAbortController;
        }
        // Get recent message history
        const recentMessages = convertMessagesForAPI(this.messageManager.getMessages());
        try {
            // Get combined memory content
            const combinedMemory = await this.messageManager.getCombinedMemory();
            // Track if assistant message has been created
            let assistantMessageCreated = false;
            logger?.debug("modelConfig in sendAIMessage", this.getModelConfig());
            // Get current permission mode and plan file path
            const currentMode = this.permissionManager?.getCurrentEffectiveMode(this.getModelConfig().permissionMode);
            const toolsConfig = this.getFilteredToolsConfig();
            const toolNames = new Set(toolsConfig.map((t) => t.function.name));
            const filteredToolPlugins = this.toolManager
                .getTools()
                .filter((t) => toolNames.has(t.name));
            let planModeOptions;
            if (currentMode === "plan") {
                const planFilePath = this.permissionManager?.getPlanFilePath();
                if (planFilePath) {
                    let planExists = false;
                    try {
                        await fs.access(planFilePath);
                        planExists = true;
                    }
                    catch {
                        planExists = false;
                    }
                    planModeOptions = { planFilePath, planExists };
                }
            }
            let autoMemoryOptions;
            if (this.getAutoMemoryEnabled()) {
                const directory = this.memoryService.getAutoMemoryDirectory(this.workdir);
                const content = await this.memoryService.getAutoMemoryContent(this.workdir);
                autoMemoryOptions = { directory, content };
            }
            // Call AI service with streaming callbacks if enabled
            const callAgentOptions = {
                gatewayConfig: this.getGatewayConfig(),
                modelConfig: this.getModelConfig(),
                messages: recentMessages,
                sessionId: this.messageManager.getSessionId(),
                abortSignal: abortController.signal,
                workdir: this.workdir, // Pass working directory
                tools: toolsConfig, // Pass filtered tool configuration
                model: model, // Use passed model
                systemPrompt: buildSystemPrompt(this.systemPrompt, filteredToolPlugins, {
                    workdir: this.workdir,
                    memory: combinedMemory,
                    language: this.getLanguage(),
                    isSubagent: !!this.subagentType,
                    planMode: planModeOptions,
                    autoMemory: autoMemoryOptions,
                    permissionMode: currentMode,
                }), // Pass custom system prompt
                maxTokens: maxTokens, // Pass max tokens override
            };
            // Add streaming callbacks only if streaming is enabled
            if (this.stream) {
                callAgentOptions.onContentUpdate = (content) => {
                    // Create assistant message on first chunk if not already created
                    if (!assistantMessageCreated) {
                        this.messageManager.addAssistantMessage();
                        assistantMessageCreated = true;
                    }
                    this.messageManager.updateCurrentMessageContent(content);
                };
                callAgentOptions.onToolUpdate = (toolCall) => {
                    // Create assistant message on first tool update if not already created
                    if (!assistantMessageCreated) {
                        this.messageManager.addAssistantMessage();
                        assistantMessageCreated = true;
                    }
                    // Use parametersChunk as compact param for better performance
                    // No need to extract params or generate compact params during streaming
                    // Update tool block with streaming parameters using parametersChunk as compact param
                    this.messageManager.updateToolBlock({
                        id: toolCall.id,
                        name: toolCall.name,
                        parameters: toolCall.parameters,
                        parametersChunk: toolCall.parametersChunk,
                        stage: toolCall.stage || "streaming", // Default to streaming if stage not provided
                    });
                };
                callAgentOptions.onReasoningUpdate = (reasoning) => {
                    // Create assistant message on first reasoning update if not already created
                    if (!assistantMessageCreated) {
                        this.messageManager.addAssistantMessage();
                        assistantMessageCreated = true;
                    }
                    this.messageManager.updateCurrentMessageReasoning(reasoning);
                };
            }
            const result = await aiService.callAgent(callAgentOptions);
            const createdByStreaming = assistantMessageCreated;
            // For non-streaming mode, create assistant message after callAgent returns
            // Also create if streaming mode but no streaming callbacks were called (e.g., when content comes directly in result)
            if (!this.stream ||
                (!assistantMessageCreated &&
                    (result.content || result.tool_calls || result.reasoning_content))) {
                this.messageManager.addAssistantMessage();
                assistantMessageCreated = true;
            }
            // Log finish reason and response headers if available
            if (result.finish_reason) {
                // Log warning headers when finish reason is length
                if (result.finish_reason === "length") {
                    logger?.warn("AI response truncated due to length limit. Response headers:", result.response_headers);
                }
            }
            if (result.additionalFields &&
                Object.keys(result.additionalFields).length > 0) {
                this.messageManager.mergeAssistantAdditionalFields(result.additionalFields);
            }
            // Handle result reasoning content from non-streaming mode
            if (result.reasoning_content && !createdByStreaming) {
                this.messageManager.updateCurrentMessageReasoning(result.reasoning_content);
            }
            // Handle result content from non-streaming mode
            if (result.content && !createdByStreaming) {
                this.messageManager.updateCurrentMessageContent(result.content);
            }
            // Handle usage tracking for agent operations
            let usage;
            if (result.usage) {
                usage = {
                    prompt_tokens: result.usage.prompt_tokens,
                    completion_tokens: result.usage.completion_tokens,
                    total_tokens: result.usage.total_tokens,
                    model: model || this.getModelConfig().model,
                    operation_type: "agent",
                    // Preserve cache fields if present
                    ...(result.usage.cache_read_input_tokens !== undefined && {
                        cache_read_input_tokens: result.usage.cache_read_input_tokens,
                    }),
                    ...(result.usage.cache_creation_input_tokens !== undefined && {
                        cache_creation_input_tokens: result.usage.cache_creation_input_tokens,
                    }),
                    ...(result.usage.cache_creation && {
                        cache_creation: result.usage.cache_creation,
                    }),
                };
            }
            // Set usage on the assistant message if available
            if (usage) {
                const messages = this.messageManager.getMessages();
                const lastMessage = messages[messages.length - 1];
                if (lastMessage && lastMessage.role === "assistant") {
                    lastMessage.usage = usage;
                    this.messageManager.setMessages(messages);
                }
                // Notify Agent to add to usage tracking
                if (this.callbacks?.onUsageAdded) {
                    this.callbacks.onUsageAdded(usage);
                }
            }
            // Collect tool calls for processing
            const toolCalls = [];
            if (result.tool_calls) {
                for (const toolCall of result.tool_calls) {
                    if (toolCall.type === "function") {
                        toolCalls.push(toolCall);
                    }
                }
            }
            if (toolCalls.length > 0) {
                // Execute all tools in parallel using Promise.all
                const toolExecutionPromises = toolCalls.map(async (functionToolCall) => {
                    const toolId = functionToolCall.id || "";
                    // Check if already interrupted, skip tool execution if so
                    if (abortController.signal.aborted ||
                        toolAbortController.signal.aborted) {
                        return;
                    }
                    const toolName = functionToolCall.function?.name || "";
                    // Safely parse tool parameters, handle tools without parameters
                    let toolArgs = {};
                    const argsString = functionToolCall.function?.arguments?.trim();
                    if (!argsString || argsString === "") {
                        // Tool without parameters, use empty object
                        toolArgs = {};
                    }
                    else {
                        try {
                            toolArgs = JSON.parse(argsString);
                        }
                        catch (parseError) {
                            // For non-empty but malformed JSON, still throw exception
                            let errorMessage = `Failed to parse tool arguments`;
                            if (result.finish_reason === "length") {
                                errorMessage +=
                                    " (output truncated, please reduce your output)";
                            }
                            logger?.error(errorMessage, parseError);
                            this.messageManager.updateToolBlock({
                                id: toolId,
                                parameters: argsString,
                                result: errorMessage,
                                success: false,
                                error: errorMessage,
                                stage: "end",
                                name: toolName,
                                compactParams: "",
                            });
                            return;
                        }
                    }
                    const compactParams = this.generateCompactParams(toolName, toolArgs);
                    // Emit start stage for non-streaming tool calls
                    if (!this.stream) {
                        this.messageManager.updateToolBlock({
                            id: toolId,
                            stage: "start",
                            name: toolName,
                            compactParams,
                            parameters: argsString,
                        });
                    }
                    // Emit running stage (tool execution about to start)
                    this.messageManager.updateToolBlock({
                        id: toolId,
                        stage: "running",
                        name: toolName,
                        compactParams,
                        parameters: argsString,
                        parametersChunk: "",
                    });
                    try {
                        // Execute PreToolUse hooks before tool execution
                        const shouldExecuteTool = await this.executePreToolUseHooks(toolName, toolArgs, toolId);
                        // If PreToolUse hooks blocked execution, skip tool execution
                        if (!shouldExecuteTool) {
                            logger?.info(`Tool ${toolName} execution blocked by PreToolUse hooks`);
                            return; // Skip this tool and return from this map function
                        }
                        // Create tool execution context
                        const context = {
                            abortSignal: toolAbortController.signal,
                            backgroundTaskManager: this.backgroundTaskManager,
                            workdir: this.workdir,
                            messageId: this.messageManager.getMessages().slice(-1)[0]?.id,
                            sessionId: this.messageManager.getSessionId(),
                            toolCallId: toolId,
                            taskManager: this.taskManager,
                            onShortResultUpdate: (shortResult) => {
                                this.messageManager.updateToolBlock({
                                    id: toolId,
                                    shortResult,
                                    stage: "running", // Keep it in running stage while updating shortResult
                                });
                            },
                            onResultUpdate: (result) => {
                                this.messageManager.updateToolBlock({
                                    id: toolId,
                                    result,
                                    stage: "running", // Keep it in running stage while updating result
                                });
                            },
                        };
                        // Execute tool
                        const toolResult = await this.toolManager.execute(functionToolCall.function?.name || "", toolArgs, context);
                        // Update message state - tool execution completed
                        this.messageManager.updateToolBlock({
                            id: toolId,
                            parameters: argsString,
                            result: toolResult.content ||
                                (toolResult.error ? `Error: ${toolResult.error}` : ""),
                            success: toolResult.success,
                            error: toolResult.error,
                            stage: "end",
                            name: toolName,
                            compactParams,
                            shortResult: toolResult.shortResult,
                            isManuallyBackgrounded: toolResult.isManuallyBackgrounded,
                            startLineNumber: toolResult.startLineNumber,
                        });
                        // Execute PostToolUse hooks after successful tool completion
                        await this.executePostToolUseHooks(toolId, toolName, toolArgs, toolResult);
                    }
                    catch (toolError) {
                        const errorMessage = toolError instanceof Error
                            ? toolError.message
                            : String(toolError);
                        this.messageManager.updateToolBlock({
                            id: toolId,
                            parameters: JSON.stringify(toolArgs, null, 2),
                            result: `Tool execution failed: ${errorMessage}`,
                            success: false,
                            error: errorMessage,
                            stage: "end",
                            name: toolName,
                            compactParams,
                            isManuallyBackgrounded: false,
                        });
                    }
                });
                // Wait for all tools to complete execution in parallel
                await Promise.all(toolExecutionPromises);
            }
            // Handle token statistics and message compression
            await this.handleTokenUsageAndCompression(result.usage, abortController, model);
            // Finalize text/reasoning blocks for the final response (no tools)
            this.messageManager.finalizeStreamingBlocks();
            // Check if there are tool operations or response was truncated, if so automatically initiate next AI service call
            if (toolCalls.length > 0 || result.finish_reason === "length") {
                // Record committed snapshots to message history
                if (this.reversionManager) {
                    const snapshots = this.reversionManager.getAndClearCommittedSnapshots();
                    if (snapshots.length > 0) {
                        this.messageManager.addFileHistoryBlock(snapshots);
                    }
                }
                // Check interruption status
                const isCurrentlyAborted = abortController.signal.aborted || toolAbortController.signal.aborted;
                // Check if all tools were manually backgrounded
                const lastMessage = this.messageManager.getMessages()[this.messageManager.getMessages().length - 1];
                const toolBlocks = lastMessage?.blocks.filter((block) => block.type === "tool") || [];
                const hasBackgrounded = toolBlocks.length > 0 &&
                    toolBlocks.some((block) => block.isManuallyBackgrounded);
                if (hasBackgrounded) {
                    logger?.info("Some tools were manually backgrounded, stopping recursion.");
                }
                else if (!isCurrentlyAborted) {
                    // If response was truncated, add a hidden continuation message
                    if (result.finish_reason === "length") {
                        this.messageManager.addUserMessage({
                            content: "Output token limit hit. Resume directly — no apology, no recap of what you were doing. Pick up mid-thought if that is where the cut happened. Break remaining work into smaller pieces.",
                            isMeta: true,
                        });
                    }
                    // Duplicate Tool Call Detection
                    if (toolCalls.length > 0) {
                        const messages = this.messageManager.getMessages();
                        // Find the most recent assistant message BEFORE the current one that has tool blocks
                        // The current assistant message is messages[messages.length - 1]
                        let previousAssistantWithTools;
                        for (let i = messages.length - 2; i >= 0; i--) {
                            const msg = messages[i];
                            if (msg.role === "assistant" &&
                                msg.blocks.some((b) => b.type === "tool")) {
                                previousAssistantWithTools = msg;
                                break;
                            }
                        }
                        if (previousAssistantWithTools) {
                            const previousToolBlocks = previousAssistantWithTools.blocks.filter((b) => b.type === "tool");
                            for (const currentToolCall of toolCalls) {
                                const currentName = currentToolCall.function?.name;
                                const currentArgs = currentToolCall.function?.arguments;
                                const isDuplicate = previousToolBlocks.some((prevBlock) => prevBlock.name === currentName &&
                                    prevBlock.parameters === currentArgs);
                                if (isDuplicate && currentName) {
                                    const toolId = currentToolCall.id;
                                    const lastMessage = messages[messages.length - 1];
                                    const toolBlock = lastMessage.blocks.find((b) => b.type === "tool" && b.id === toolId);
                                    if (toolBlock) {
                                        const warning = `\n\nNote: You just called this tool with the same arguments in the previous turn. Please ensure you are not in a loop and consider if you need to change your approach.`;
                                        this.messageManager.updateToolBlock({
                                            id: toolId,
                                            result: (toolBlock.result || "") + warning,
                                            stage: "end",
                                        });
                                    }
                                }
                            }
                        }
                    }
                    // Recursively call AI service, increment recursion depth, and pass same configuration
                    await this.sendAIMessage({
                        recursionDepth: recursionDepth + 1,
                        model,
                        allowedRules,
                        maxTokens,
                    });
                }
            }
        }
        catch (error) {
            this.messageManager.addErrorBlock(error instanceof Error ? error.message : "Unknown error occurred");
        }
        finally {
            // Only execute cleanup and hooks for the initial call
            if (recursionDepth === 0) {
                // Save session in each recursion to ensure message persistence
                await this.messageManager.saveSession();
                // Set loading to false first
                this.setIsLoading(false);
                // Inject pending notifications from background tasks
                const notificationQueue = this.container.has("NotificationQueue")
                    ? this.container.get("NotificationQueue")
                    : undefined;
                if (notificationQueue && notificationQueue.hasPending()) {
                    const notifications = notificationQueue.dequeueAll();
                    for (const notification of notifications) {
                        const block = parseTaskNotificationXml(notification);
                        if (block) {
                            this.messageManager.addNotificationMessage({
                                taskId: block.taskId,
                                taskType: block.taskType,
                                status: block.status,
                                summary: block.summary,
                                outputFile: block.outputFile,
                            });
                        }
                    }
                    // Recursively process the notifications
                    await this.sendAIMessage({
                        recursionDepth: 0,
                        model,
                        allowedRules,
                        maxTokens,
                    });
                }
                else {
                    // Clear temporary rules
                    this.permissionManager?.clearTemporaryRules();
                    // Clear abort controllers
                    this.abortController = null;
                    this.toolAbortController = null;
                    // Execute Stop/SubagentStop hooks only if the operation was not aborted
                    const isCurrentlyAborted = abortController.signal.aborted ||
                        toolAbortController.signal.aborted;
                    if (!isCurrentlyAborted) {
                        // Record committed snapshots to message history for the final turn
                        if (this.reversionManager) {
                            const snapshots = this.reversionManager.getAndClearCommittedSnapshots();
                            if (snapshots.length > 0) {
                                this.messageManager.addFileHistoryBlock(snapshots);
                            }
                        }
                        const shouldContinue = await this.executeStopHooks();
                        // If Stop/SubagentStop hooks indicate we should continue (due to blocking errors),
                        // restart the AI conversation cycle
                        if (shouldContinue) {
                            logger?.info(`${this.subagentType ? "SubagentStop" : "Stop"} hooks indicate issues need fixing, continuing conversation...`);
                            // Restart the conversation to let AI fix the issues
                            // Use recursionDepth = 0 to set loading false again for continuation
                            await this.sendAIMessage({
                                recursionDepth: 0,
                                model,
                                allowedRules,
                                maxTokens,
                            });
                        }
                    }
                }
            }
        }
    }
    /**
     * Execute Stop or SubagentStop hooks when AI response cycle completes
     * Uses "SubagentStop" hook name when triggered by a subagent, otherwise uses "Stop"
     * @returns Promise<boolean> - true if should continue conversation, false if should stop
     */
    async executeStopHooks() {
        if (!this.hookManager)
            return false;
        try {
            // Use "SubagentStop" hook name when triggered by a subagent, otherwise use "Stop"
            const hookName = this.subagentType ? "SubagentStop" : "Stop";
            const context = {
                event: hookName,
                projectDir: this.workdir,
                timestamp: new Date(),
                sessionId: this.messageManager.getSessionId(),
                transcriptPath: this.messageManager.getTranscriptPath(),
                cwd: this.workdir,
                subagentType: this.subagentType, // Include subagent type in hook context
                // Stop hooks don't need toolName, toolInput, toolResponse, or userPrompt
                env: Object.fromEntries(Object.entries(process.env).filter((e) => e[1] !== undefined)), // Include environment variables
            };
            const results = await this.hookManager.executeHooks(hookName, context);
            // Process hook results to handle exit codes and appropriate responses
            let shouldContinue = false;
            if (results.length > 0) {
                const processResult = this.hookManager.processHookResults(hookName, results, this.messageManager);
                // If hook processing indicates we should block (exit code 2), continue conversation
                if (processResult.shouldBlock) {
                    logger?.info(`${hookName} hook blocked stopping with error:`, processResult.errorMessage);
                    shouldContinue = true;
                }
            }
            // Log hook execution results for debugging
            if (results.length > 0) {
                logger?.debug(`Executed ${results.length} ${hookName} hook(s):`, results.map((r) => ({
                    success: r.success,
                    duration: r.duration,
                    exitCode: r.exitCode,
                    timedOut: r.timedOut,
                    stderr: r.stderr,
                })));
            }
            // Trigger auto-memory extraction if enabled and this is the main agent
            if (!this.subagentType) {
                const autoMemoryService = this.container.get("AutoMemoryService");
                if (autoMemoryService) {
                    // Trigger extraction, but don't block the return.
                    // onTurnEnd itself returns quickly after forking.
                    autoMemoryService.onTurnEnd(this.workdir).catch((err) => {
                        logger?.error("Auto-memory extraction trigger failed:", err);
                    });
                }
            }
            return shouldContinue;
        }
        catch (error) {
            // Hook execution errors should not interrupt the main workflow
            logger?.error(`${this.subagentType ? "SubagentStop" : "Stop"} hook execution failed:`, error);
            return false;
        }
    }
    /**
     * Execute PreToolUse hooks before tool execution
     * Returns true if hooks allow tool execution, false if blocked
     */
    async executePreToolUseHooks(toolName, toolInput, toolId) {
        if (!this.hookManager)
            return true;
        try {
            const context = {
                event: "PreToolUse",
                projectDir: this.workdir,
                timestamp: new Date(),
                toolName,
                sessionId: this.messageManager.getSessionId(),
                transcriptPath: this.messageManager.getTranscriptPath(),
                cwd: this.workdir,
                toolInput,
                subagentType: this.subagentType, // Include subagent type in hook context
                env: Object.fromEntries(Object.entries(process.env).filter((e) => e[1] !== undefined)), // Include environment variables
            };
            const results = await this.hookManager.executeHooks("PreToolUse", context);
            // Process hook results to handle exit codes and determine if tool should be blocked
            let shouldContinue = true;
            if (results.length > 0) {
                const processResult = this.hookManager.processHookResults("PreToolUse", results, this.messageManager, toolId, // Pass toolId for proper PreToolUse blocking error handling
                JSON.stringify(toolInput || {}, null, 2));
                shouldContinue = !processResult.shouldBlock;
            }
            // Log hook execution results for debugging
            if (results.length > 0) {
                logger?.debug(`Executed ${results.length} PreToolUse hook(s) for ${toolName}:`, results.map((r) => ({
                    success: r.success,
                    duration: r.duration,
                    exitCode: r.exitCode,
                    timedOut: r.timedOut,
                    stderr: r.stderr,
                })));
            }
            return shouldContinue;
        }
        catch (error) {
            // Hook execution errors should not interrupt the main workflow
            logger?.error("PreToolUse hook execution failed:", error);
            return true; // Allow tool execution on hook errors
        }
    }
    /**
     * Execute PostToolUse hooks after tool completion
     */
    async executePostToolUseHooks(toolId, toolName, toolInput, toolResponse) {
        if (!this.hookManager)
            return;
        try {
            const context = {
                event: "PostToolUse",
                projectDir: this.workdir,
                timestamp: new Date(),
                toolName,
                sessionId: this.messageManager.getSessionId(),
                transcriptPath: this.messageManager.getTranscriptPath(),
                cwd: this.workdir,
                toolInput,
                toolResponse,
                subagentType: this.subagentType, // Include subagent type in hook context
                env: Object.fromEntries(Object.entries(process.env).filter((e) => e[1] !== undefined)), // Include environment variables
            };
            const results = await this.hookManager.executeHooks("PostToolUse", context);
            // Process hook results to handle exit codes and update tool results
            if (results.length > 0) {
                this.hookManager.processHookResults("PostToolUse", results, this.messageManager, toolId);
            }
            // Log hook execution results for debugging
            if (results.length > 0) {
                logger?.debug(`Executed ${results.length} PostToolUse hook(s) for ${toolName}:`, results.map((r) => ({
                    success: r.success,
                    duration: r.duration,
                    exitCode: r.exitCode,
                    timedOut: r.timedOut,
                    stderr: r.stderr,
                })));
            }
        }
        catch (error) {
            // Hook execution errors should not interrupt the main workflow
            logger?.error("PostToolUse hook execution failed:", error);
        }
    }
}
