# ReAct循环
# core/react_loop.py
"""
轻量化ReAct循环实现
支持异步工具调用和上下文管理
"""

import asyncio
import json
import re
import sys
from pathlib import Path
from typing import Dict, List, Any, Optional, Callable, Awaitable
from dataclasses import dataclass
from datetime import datetime

if __package__ in (None, ""):
    sys.path.insert(0, str(Path(__file__).parent.parent.parent))
    from ..utils.agent_logger import get_logger
    from ..utils.tool_registry import get_global_registry
    from ..config import settings
else:
    from ..utils.agent_logger import get_logger
    from ..utils.tool_registry import get_global_registry
    from ..config import settings


@dataclass
class ActionItem:
    """单个动作项"""

    action: str
    action_input: Dict
    observation: Optional[str] = None


@dataclass
class ReActStep:
    """ReAct单步记录"""

    thought: str
    raw_response: str = ""  # 模型完整响应（含 thinking），用于保存会话历史
    actions: Optional[List[ActionItem]] = None
    timestamp: float = 0.0

    def __post_init__(self):
        if self.actions is None:
            self.actions = []


class AsyncReActLoop:
    """异步ReAct循环"""

    def __init__(
        self,
        model_caller: Callable[[List[Dict]], Awaitable[str]],
        tools: Dict[str, Callable],
        context_manager: Any,
        max_iterations: int = 50,
        project_path: Optional[Path] = None,
        todo_manager: Optional[Any] = None,
        context_config: Optional[Any] = None,
    ):
        """
        初始化ReAct循环

        Args:
            model_caller: 异步模型调用函数
            tools: 工具字典
            context_manager: 上下文管理器
            max_iterations: 最大迭代次数
            project_path: 项目路径（用于日志记录）
            todo_manager: 待办管理器
            context_config: 上下文配置
        """
        self.model_caller = model_caller
        self.tools = tools
        self.context_manager = context_manager
        self.max_iterations = max_iterations
        self.project_path = project_path
        self.todo_manager = todo_manager

        self.steps: List[ReActStep] = []
        self.current_context: str = ""
        self.logger = get_logger(project_path) if project_path else None

        # 上下文配置
        self.context_config = context_config

        # Token计数器（用于智能缩减）
        from ..utils.session_manager import _load_tiktoken_encoding

        self.encoding = _load_tiktoken_encoding()

    async def run(
        self,
        initial_prompt: str,
        task_description: str,
        todo_manager: Optional[Any] = None,
        history_messages: Optional[List[Dict]] = None,
    ) -> Dict[str, Any]:
        """
        运行ReAct循环

        Args:
            initial_prompt: 初始提示
            task_description: 任务描述
            todo_manager: to-do-list管理器
            history_messages: 同一会话的历史对话消息（用于多轮任务上下文衔接）

        Returns:
            执行结果
        """
        # 开始日志记录
        if self.logger:
            task_id = await self.logger.start_task(task_description)

        print(f"🚀 开始ReAct循环，最多{self.max_iterations}次迭代")

        # 初始上下文
        self.current_context = await self.context_manager.get_context()

        # 构建初始消息，集成规划提示到系统prompt中
        todo_section = ""
        if todo_manager:
            try:
                todo_summary = await todo_manager.get_todo_summary()
                if "error" not in todo_summary:
                    todo_section = f"""

📋 任务待办清单
简单任务比如仅仅是回答用户，不需要制定和更新待办清单，查看相关文件和分析后快速回答即可。
复杂任务则请你使用待办任务清单:
- 待办清单文件: {todo_summary.get("todo_file", "未知")}
- 总事项: {todo_summary.get("total_todos", 0)} 
- 已完成: {todo_summary.get("completed_todos", 0)}
- 待处理: {todo_summary.get("pending_todos", 0)}
- 完成率: {todo_summary.get("completion_rate", 0):.1f}%

备注：
1. add_todo_item 会返回 todo_id（如 t1、t2），请记住它
2. 标记完成时，优先使用 mark_todo_completed(todo_id="t1")，精确可靠
3. 如果发现需要新的任务步骤，添加新的待办事项
4. 如果任务计划有变，更新现有的待办事项

示例：
add_todo_item(description="实现认证API") → 返回 todo_id: "t1"
mark_todo_completed(todo_id="t1") → 精确标记完成

"""
            except Exception as e:
                print(f"⚠️  获取待办清单摘要失败: {e}")

        system_prompt = f"""{initial_prompt}{todo_section}
        
重要提示 - 思考中的规划：
在每次思考时，请自然地进行规划：
- 如果是复杂任务（涉及应用、系统、项目、架构等），在前几次思考时先分析需求、检查环境、制定计划
- 如果任务中包含"规划"、"分析"、"检查"、"重新"、"计划"、"策略"、"需求"等关键词，在思考中主动进行规划
- 保持思考的自然性，将规划作为思考过程的一部分，而不是独立的任务

"""

        messages = [
            {"role": "system", "content": system_prompt},
        ]

        # ─── 多轮任务上下文衔接 ───────────────────────────────────
        # 同一会话中，第二轮及后续任务需要看到之前的对话历史
        # history_messages 来自 session_manager，包含之前所有轮次的 user/assistant 消息
        # 插入到 system prompt 之后、当前任务之前，让模型了解之前做了什么
        # ⚠️ 不要去掉这段逻辑，否则多轮任务间上下文会脱节
        # ⚠️ token 超限时会由 _compact_context 自动压缩，不需要在这里截断
        if history_messages:
            for msg in history_messages:
                role = msg.get("role", "")
                content = msg.get("content", "")
                if role in ("user", "assistant") and content:
                    messages.append({"role": role, "content": content})
            if len(history_messages) > 0:
                print(f"📜 已加载 {len(history_messages)} 条历史消息到上下文")

        messages.append(
            {
                "role": "user",
                "content": f"任务：{task_description}\n\n当前上下文：\n{self.current_context}\n\n请按照Thought->Action的格式执行（不要输出Observation，系统会自动执行工具并返回结果）",
            },
        )

        start_time = asyncio.get_event_loop().time()

        for iteration in range(self.max_iterations):
            iteration_start = asyncio.get_event_loop().time()
            print(f"\n🔄 迭代 {iteration + 1}/{self.max_iterations}")

            # 调用模型获取思考
            model_start = asyncio.get_event_loop().time()
            response = await self.model_caller(messages)
            model_time = asyncio.get_event_loop().time() - model_start

            # 记录模型调用
            if self.logger:
                await self.logger.log_model_call(
                    messages=messages,
                    response=response,
                    response_time=model_time,
                    model_info={"iteration": iteration + 1},
                )

            # 解析响应（支持多个action）
            thought, actions = self._parse_response(response)

            # 自动更新待办清单（已简化：不再自动记录思考过程）
            # if todo_manager:
            #     await self._update_todo_from_thought(thought, todo_manager)

            # 记录步骤（raw_response 保留完整模型响应，含 thinking，用于会话历史保存）
            step = ReActStep(
                thought=thought, raw_response=response, actions=[], timestamp=asyncio.get_event_loop().time()
            )
            self.steps.append(step)

            # 注意：模型思考内容已在 main_agent.py 中流式打印，此处不再重复显示

            # 检查是否完成（没有action表示任务完成）
            if not actions or await self._is_task_completed(
                thought, actions[0].action if actions else None, task_description
            ):
                print("✅ 任务完成")
                # thought 内容已在流式输出中打印过，不再重复打印
                total_time = asyncio.get_event_loop().time() - start_time
                if self.logger:
                    await self.logger.log_iteration(
                        iteration=iteration + 1,
                        thought=thought,
                        action=None,
                        action_input=None,
                        execution_time=asyncio.get_event_loop().time()
                        - iteration_start,
                    )
                    await self.logger.finish_task(
                        final_status="completed",
                        total_iterations=iteration + 1,
                        total_time=total_time,
                        summary={"final_thought": response},
                    )
                return {
                    "status": "completed",
                    "final_thought": response,
                    "iterations": iteration + 1,
                    "steps": self.steps,
                    "total_time": total_time,
                }

            # 执行所有动作（增强错误处理和重试机制）
            all_observations = []
            all_observations_for_display = []  # 用于显示的简化版本

            for i, action_item in enumerate(actions):
                print(f"🛠️  动作 {i + 1}/{len(actions)}: {action_item.action}")
                action_start = asyncio.get_event_loop().time()

                # 添加重试机制（使用配置的最大重试次数，来自 aacode_config.yaml）
                max_retries = settings.limits.max_retries
                retry_count = 0
                observation = None
                observation_for_display = None

                while retry_count < max_retries:
                    try:
                        # 获取完整的工具执行结果
                        full_result = await self._execute_action_internal(
                            action_item.action, action_item.action_input
                        )

                        # 为Agent保留完整结果
                        observation = full_result

                        # 为用户显示生成简化版本
                        observation_for_display = self._format_observation_for_display(
                            action_item.action, full_result
                        )

                        # 检查是否需要重试（某些错误可以重试）
                        if observation and isinstance(observation, str):
                            if "错误" in observation or "error" in observation.lower():
                                # 检查是否是可重试的错误
                                retryable_errors = [
                                    "timeout",
                                    "connection",
                                    "temporary",
                                    "暂时",
                                ]
                                if any(
                                    err in observation.lower()
                                    for err in retryable_errors
                                ):
                                    retry_count += 1
                                    if retry_count < max_retries:
                                        print(
                                            f"⚠️  动作失败，{retry_count}/{max_retries} 次重试..."
                                        )
                                        await asyncio.sleep(1)  # 等待1秒后重试
                                        continue
                        break  # 成功或不可重试的错误，退出重试循环

                    except asyncio.CancelledError:
                        # 任务被取消，重新抛出以便上层处理
                        raise
                    except Exception as e:
                        retry_count += 1
                        if retry_count < max_retries:
                            print(
                                f"⚠️  动作异常，{retry_count}/{max_retries} 次重试: {str(e)}"
                            )
                            await asyncio.sleep(1)
                        else:
                            observation = f"执行错误（已重试{max_retries}次）: {str(e)}"
                            observation_for_display = observation
                            break

                if observation is None:
                    observation = f"执行失败：未获得结果（已重试{max_retries}次）"
                    observation_for_display = observation

                action_time = asyncio.get_event_loop().time() - action_start

                # 记录观察结果（完整版本）
                action_item.observation = observation
                assert step.actions is not None, "step.actions should not be None"
                step.actions.append(action_item)
                all_observations.append(observation)  # Agent获取完整内容
                all_observations_for_display.append(
                    observation_for_display or observation or ""
                )  # 用户看到简化版本

                # 实时打印 Observation（供客户端显示）
                display_obs = observation_for_display or observation or ""
                if display_obs:
                    # 截断过长的输出，避免刷屏
                    max_display = 3000
                    if len(display_obs) > max_display:
                        display_obs = display_obs[:max_display] + f"\n... (已截断，共{len(observation_for_display or observation)}字符)"
                    print(f"📋 Observation:\n{display_obs}", flush=True)

                # 🔥 新增：从错误中自动更新待办清单
                if todo_manager:
                    await self._update_todo_from_error(observation, todo_manager)

                # 记录工具调用
                if self.logger:
                    await self.logger.log_tool_call(
                        tool_name=action_item.action,
                        tool_input=action_item.action_input or {},
                        result=observation,
                        execution_time=action_time,
                        success=not (
                            observation.startswith("错误")
                            or "error" in observation.lower()
                        ),
                        metadata=(
                            {"retry_count": retry_count} if retry_count > 0 else None
                        ),
                    )

            # 合并所有观察结果（Agent获取完整内容）
            observation = "\n".join(
                [f"动作 {i + 1} 结果: {obs}" for i, obs in enumerate(all_observations)]
            )

            # 合并显示版本（用户看到简化版本）
            observation_for_display = "\n".join(
                [
                    f"动作 {i + 1} 结果: {obs}"
                    for i, obs in enumerate(all_observations_for_display)
                ]
            )

            # 添加到上下文消息（Agent获取完整内容，包括 thinking）
            # ⚠️ response 包含完整的 "💭 思考过程:\n{thinking}\n\nThought: {content}"
            # 不要清理或截断 response，模型需要看到完整的推理过程
            messages.append({"role": "assistant", "content": response})
            messages.append(
                {
                    "role": "user",
                    # ⚠️ 这是 react_loop 迭代驱动消息，不是用户输入
                    # 用 [系统] 前缀明确区分，避免模型误以为是用户在说话
                    "content": f"[系统] 工具执行结果如下，请根据结果继续执行下一步（Thought→Action），如果任务已完成则直接输出最终总结。\n\n观察：{observation}",
                }
            )

            # 上下文一致性检查（在 messages 更新后，确保 assistant token 统计正确）
            await self._validate_context_consistency(
                all_observations, all_observations_for_display, messages
            )

            # 记录迭代（使用完整observation）
            if self.logger:
                await self.logger.log_iteration(
                    iteration=iteration + 1,
                    thought=thought,
                    action=", ".join([a.action for a in actions]) if actions else None,
                    action_input={"multiple_actions": True, "count": len(actions)},
                    observation=observation,  # 日志记录完整内容
                    execution_time=asyncio.get_event_loop().time() - iteration_start,
                )

            # 更新上下文（使用完整observation）
            await self.context_manager.update(observation)
            self.current_context = await self.context_manager.get_compact_context()

            # 智能上下文缩减检查（基于token数）
            current_tokens = self._estimate_tokens(messages)
            trigger_tokens = (
                self.context_config.compact_trigger_tokens
                if self.context_config
                else 8000
            )

            if current_tokens > trigger_tokens:
                print(f"📊 当前token数: {current_tokens}, 触发阈值: {trigger_tokens}")
                await self._compact_context(messages)
                if self.logger:
                    await self.logger.log_context_update(
                        update_type="compact",
                        content=f"在第{iteration + 1}次迭代后执行上下文缩减（token数: {current_tokens}）",
                    )

        total_time = asyncio.get_event_loop().time() - start_time
        if self.logger:
            await self.logger.finish_task(
                final_status="max_iterations_reached",
                total_iterations=self.max_iterations,
                total_time=total_time,
                summary={
                    "last_thought": self.steps[-1].thought if self.steps else None
                },
            )

        print("\n⚠️  达到最大迭代次数，任务可能未完成")
        print(f"💡 提示：你可以继续执行追加任务来完成剩余工作")

        return {
            "status": "max_iterations_reached",
            "iterations": self.max_iterations,
            "steps": self.steps,
            "total_time": total_time,
            "message": "达到最大迭代次数，建议继续会话完成任务",
        }

    async def _is_task_completed(
        self, thought: str, first_action: Optional[str], task_description: str
    ) -> bool:
        """
        使用模型判断任务是否已完成

        Args:
            thought: 当前思考内容
            first_action: 第一个动作（如果有）
            task_description: 原始任务描述

        Returns:
            是否任务已完成
        """
        # 如果没有action，才可能是任务完成
        if not first_action:
            # 检查最近的执行记录中是否有错误
            has_recent_errors = await self._check_recent_errors()
            if has_recent_errors:
                print("⚠️  检测到最近的执行错误，任务未完成，继续解决问题")
                return False

            # 使用模型判断
            try:
                # 获取最近的执行结果上下文
                recent_context = await self._get_recent_execution_context()

                completion_check_prompt = f"""请判断以下任务是否已经完成。

原始任务：{task_description}

当前思考：{thought}

最近执行情况：
{recent_context}

判断标准（严格）：
1. 任务的核心目标是否已经实现（例如：要求写爬虫，是否已经创建并测试了爬虫代码）
2. 是否只是完成了某个子步骤（例如：只是"标记待办事项为完成"不算任务完成）
3. 是否明确表示整个任务已经完成，不需要进一步操作
4. **关键**：如果最近的执行中有错误（ImportError、SyntaxError等），任务未完成
5. **关键**：如果代码未经过实际运行测试验证，任务未完成
6. **关键**：如果只是"写完代码"但没有"运行测试"，任务未完成

请只回答 "YES" 或 "NO"：
- YES: 整个任务已经完成，代码已测试通过，没有错误
- NO: 任务还未完成、有错误需要修复、或只是完成了子步骤

回答："""

                messages = [{"role": "user", "content": completion_check_prompt}]
                response = await self.model_caller(messages)

                # 解析响应
                response_clean = response.strip().upper()
                if "YES" in response_clean[:10]:  # 检查前10个字符
                    return True
                else:
                    return False

            except Exception as e:
                print(f"⚠️  模型判断任务完成状态失败: {e}")
                # 回退到简单判断：如果有错误就不算完成
                if has_recent_errors:
                    return False
                return True

        # 如果有action，检查是否是最终确认动作
        final_actions = ["finalize", "complete_task", "finish"]
        if first_action.lower() in final_actions:
            return True

        return False

    async def _check_recent_errors(self) -> bool:
        """检查最近的步骤中是否有错误"""
        if not self.steps:
            return False

        # 检查最近3步
        recent_steps = self.steps[-3:]
        error_keywords = [
            "error",
            "exception",
            "traceback",
            "failed",
            "failure",
            "错误",
            "异常",
            "失败",
            "importerror",
            "syntaxerror",
            "nameerror",
            "typeerror",
            "valueerror",
            "attributeerror",
        ]

        for step in recent_steps:
            if step.actions:
                for action in step.actions:
                    if action.observation:
                        obs_lower = action.observation.lower()
                        if any(keyword in obs_lower for keyword in error_keywords):
                            return True

        return False

    async def _get_recent_execution_context(self) -> str:
        """获取最近的执行上下文（用于任务完成判断）"""
        if not self.steps:
            return "无执行记录"

        # 获取最近3步的摘要
        recent_steps = self.steps[-3:]
        context_parts = []

        for i, step in enumerate(recent_steps):
            context_parts.append(
                f"\n步骤 {len(self.steps) - len(recent_steps) + i + 1}:"
            )
            context_parts.append(f"  思考: {step.thought[:500]}...")

            if step.actions:
                for action in step.actions:
                    context_parts.append(f"  动作: {action.action}")
                    if action.observation:
                        # 检查是否有错误
                        obs_preview = action.observation[:1000]
                        if any(
                            kw in action.observation.lower()
                            for kw in ["error", "错误", "failed", "失败"]
                        ):
                            context_parts.append(f"  ❌ 结果: {obs_preview}...")
                        else:
                            context_parts.append(f"  ✅ 结果: {obs_preview}...")

        return "\n".join(context_parts)

    def _parse_response(self, response: str) -> tuple:
        """解析模型响应（支持多个action）- 增强健壮性版本"""
        # 阶段1: 尝试解析JSON格式（支持多个action）
        json_patterns = [
            r"```json\s*\n(.*?)\n```",  # 标准json代码块
            r"```JSON\s*\n(.*?)\n```",  # 大写JSON
            r"```\s*\n(\{.*?\})\s*\n```",  # 普通代码块包裹的JSON
            r'(\{[\s\S]*?"thought"[\s\S]*?\})',  # 直接的JSON对象（包含thought字段）
            r"```json\s*\n(.*?)(?:\n```|$)",  # 不严格的结束标记
        ]

        for pattern in json_patterns:
            json_match = re.search(pattern, response, re.DOTALL)
            if json_match:
                json_str = None
                try:
                    json_str = json_match.group(1).strip()
                    # 清理可能的markdown残留
                    json_str = (
                        json_str.replace("```json", "")
                        .replace("```JSON", "")
                        .replace("```", "")
                        .strip()
                    )

                    # 尝试修复常见的JSON格式问题
                    json_str = self._fix_json_format(json_str)

                    data = json.loads(json_str)
                    thought = (
                        data.get("thought")
                        or data.get("thinking")
                        or data.get("reasoning")
                        or ""
                    )

                    # 支持单个action或多个action
                    actions_data = data.get("actions", [])
                    if not actions_data and "action" in data:
                        # 单个action的兼容格式
                        actions_data = [
                            {
                                "action": data.get("action"),
                                "action_input": data.get("action_input", {}),
                            }
                        ]

                    actions = []
                    for action_data in actions_data:
                        if isinstance(action_data, dict) and "action" in action_data:
                            action_name = action_data["action"]
                            action_input = (
                                action_data.get("action_input")
                                or action_data.get("input")
                                or {}
                            )

                            # 验证action_input是字典
                            if not isinstance(action_input, dict):
                                action_input = {"value": action_input}

                            actions.append(
                                ActionItem(
                                    action=action_name, action_input=action_input
                                )
                            )

                    if thought and actions:
                        return thought, actions
                except json.JSONDecodeError as e:
                    # 记录JSON解析错误，但继续尝试其他格式
                    print(f"⚠️  JSON解析失败 (pattern {pattern[:20]}...): {str(e)}")
                    if json_str:
                        print(f"⚠️  尝试的JSON: {json_str[:100]}...")
                    else:
                        print(f"⚠️  尝试的JSON: [无法获取JSON字符串]")
                    continue
                except Exception as e:
                    print(f"⚠️  JSON处理异常: {str(e)}")
                    continue

        # 阶段2: 解析文本格式（支持多个action）
        thought_match = re.search(
            r"Thought[:\s]*(.*?)(?=Action|$)", response, re.DOTALL | re.IGNORECASE
        )
        thought = thought_match.group(1).strip() if thought_match else response[:200]

        actions = []

        # 改进的解析逻辑：精确匹配Action和Action Input对
        lines = response.split("\n")
        i = 0

        while i < len(lines):
            line = lines[i].strip()

            # 匹配Action行（支持编号，但Action名称不能是"Input"）
            action_match = re.match(
                r"Action\s*(\d+)?[:\s]+(?!Input)(.+)", line, re.IGNORECASE
            )

            if action_match:
                action_num = action_match.group(1)  # 可能是None
                action_name = action_match.group(2).strip()

                # 跳过空的action名称或只有冒号
                if (
                    not action_name
                    or action_name == ":"
                    or action_name.lower() == "input"
                ):
                    i += 1
                    continue

                # 清理action名称（移除可能的引号和空格）
                action_name = action_name.strip("`\"' ")

                # 查找对应的Action Input（最多向下查找10行）
                action_input = {}
                found_input = False

                for j in range(i + 1, min(i + 11, len(lines))):
                    input_line = lines[j].strip()

                    # 如果遇到下一个Action，停止查找
                    if re.match(
                        r"Action\s*(\d+)?[:\s]+(?!Input)", input_line, re.IGNORECASE
                    ):
                        break

                    # 匹配Action Input行
                    input_match = re.match(
                        r"Action\s+Input\s*(\d+)?[:\s]+(.+)", input_line, re.IGNORECASE
                    )

                    if input_match:
                        input_num = input_match.group(1)
                        input_text = input_match.group(2).strip()

                        # 检查编号是否匹配（如果都有编号）
                        if action_num and input_num and action_num != input_num:
                            continue

                        # 解析输入
                        if input_text.startswith("{"):
                            try:
                                # 清理可能的markdown残留
                                clean_input = (
                                    input_text.replace("```json", "")
                                    .replace("```", "")
                                    .strip()
                                )
                                # 尝试修复JSON格式
                                clean_input = self._fix_json_format(clean_input)
                                action_input = json.loads(clean_input)
                                found_input = True
                                break
                            except json.JSONDecodeError as e:
                                # JSON解析失败，提供详细错误信息
                                print(f"⚠️  Action Input JSON解析失败: {str(e)} | 原始输入: {input_text[:100]}...")
                                action_input = {
                                    "_error": f"JSON格式错误: {str(e)}",
                                    "_raw": input_text,
                                    "_suggestion": "请检查JSON格式：1) 键名需要双引号 2) 字符串值需要双引号 3) 不要有尾随逗号",
                                }
                                found_input = True
                                break
                        else:
                            # 非JSON格式，尝试智能解析
                            action_input = self._parse_non_json_input(input_text)
                            found_input = True
                            break

                # 添加action（即使没有找到input也添加空字典）
                actions.append(
                    ActionItem(
                        action=action_name,
                        action_input=action_input if found_input else {},
                    )
                )

            i += 1

        # 如果没有解析到thought，使用响应的前200字符
        if not thought:
            thought = response[:500] + ("..." if len(response) > 200 else "")

        return thought, actions

    def _fix_json_format(self, json_str: str) -> str:
        """尝试修复常见的JSON格式问题"""
        # 移除尾随逗号
        json_str = re.sub(r",\s*}", "}", json_str)
        json_str = re.sub(r",\s*]", "]", json_str)

        # 修复单引号为双引号（但要小心字符串内的单引号）
        # 这是一个简化的实现，可能不完美
        # json_str = json_str.replace("'", '"')

        return json_str

    def _parse_non_json_input(self, input_text: str) -> Dict:
        """解析非JSON格式的输入"""
        # 尝试解析key=value格式
        if "=" in input_text:
            result = {}
            pairs = input_text.split(",")
            for pair in pairs:
                if "=" in pair:
                    key, value = pair.split("=", 1)
                    result[key.strip()] = value.strip().strip("\"'")
            if result:
                return result

        # 否则作为单个字符串值
        return {"input": input_text}

    def _format_observation_for_display(self, action: str, result: Any) -> str:
        """
        格式化observation用于显示给用户（简化版本）
        Agent内部仍然获取完整结果
        """
        # 特殊处理：read_file显示可折叠预览
        if action == "read_file" and isinstance(result, dict) and result.get("success"):
            path = result.get("path", "unknown")
            lines = result.get("lines", 0)
            size = result.get("size", 0)
            content = result.get("content", "")

            # 显示前20行作为预览
            preview_lines = content.split("\n")[:20]
            preview = "\n".join(preview_lines)

            if len(content.split("\n")) > 20:
                return f"📄 {path} ({lines}行, {size}字符)\n```\n{preview}\n...\n```\n📋 显示前20行，完整内容已保存（共{len(content.split('\n'))}行）"
            else:
                return f"📄 {path} ({lines}行, {size}字符)\n```\n{preview}\n```"

        # 其他动作返回完整结果（可能被截断）
        result_str = str(result)

        # 判断内容类型
        is_code_content = any(
            indicator in result_str[:200]
            for indicator in [
                "def ",
                "class ",
                "import ",
                "from ",
                "#!/",  # Python
                "function ",
                "const ",
                "let ",
                "var ",  # JavaScript
                "public ",
                "private ",
                "protected ",  # Java/C++
                "<?php",
                "namespace ",  # PHP
            ]
        )

        is_test_output = any(
            indicator in result_str[:500]
            for indicator in [
                "test session starts",
                "PASSED",
                "FAILED",
                "pytest",
                "unittest",
                "Test",
                "Traceback",
                "AssertionError",
                "===",
                "---",
                "collected",
                "passed",
                "failed",
            ]
        )

        # 根据内容类型设置不同的阈值
        if is_test_output:
            truncate_threshold = settings.output.test_output_threshold
            preview_length = settings.output.test_output_preview
        elif is_code_content:
            truncate_threshold = settings.output.code_content_threshold
            preview_length = settings.output.code_content_preview
        else:
            truncate_threshold = settings.output.normal_output_threshold
            preview_length = settings.output.normal_output_preview

        # 如果超过阈值，只显示预览
        if len(result_str) > truncate_threshold:
            preview = result_str[:preview_length]
            return f"{preview}...\n\n（输出过长已截断，共{len(result_str)}字符，Agent已获取完整内容）"

        # 中等长度的输出
        medium_threshold = truncate_threshold // 2
        if len(result_str) > medium_threshold:
            return (
                result_str[:medium_threshold]
                + f"...\n\n（已截断，共{len(result_str)}字符）"
            )

        return result_str

    async def _execute_action_internal(self, action: str, action_input: Dict) -> str:
        """执行动作（内部方法，返回完整结果）"""
        registry = get_global_registry()

        if action not in self.tools:
            # 使用工具注册表提供友好的错误消息
            return registry.format_tool_not_found_error(action)

        try:
            # 验证输入参数
            if action_input is None:
                action_input = {}

            if not isinstance(action_input, dict):
                return f'错误：动作输入必须是字典格式，当前类型：{type(action_input)}\n提示：请使用 {{"key": "value"}} 格式'

            # 检查是否包含JSON解析错误
            if "_error" in action_input:
                error_detail = action_input["_error"]
                raw_input = action_input.get("_raw", "N/A")
                suggestion = action_input.get("_suggestion", "请检查JSON格式")
                return f"❌ 参数解析错误\n\n错误: {error_detail}\n原始输入: {raw_input}\n\n💡 {suggestion}"

            # 使用工具注册表验证参数
            validation_result = registry.validate_call(action, action_input)
            if not validation_result.valid:
                # 返回详细的验证错误消息
                error_msg = f"❌ 参数验证失败\n\n{validation_result.error_message}\n\n"
                # 添加工具文档引用
                doc = registry.get_documentation(action)
                if doc:
                    error_msg += f"📖 工具文档：\n{doc[:500]}..."
                return error_msg

            # 规范化参数（将别名转换为标准名称）
            schema = registry.get_schema(action)
            if schema:
                action_input = schema.normalize_params(action_input)

            # 异步执行工具（增加超时保护）
            try:
                if asyncio.iscoroutinefunction(self.tools[action]):
                    # 为异步工具添加超时保护（默认60秒）
                    result = await asyncio.wait_for(
                        self.tools[action](**action_input), timeout=60.0
                    )
                else:
                    # 同步函数转异步（也添加超时）
                    result = await asyncio.wait_for(
                        asyncio.get_event_loop().run_in_executor(
                            None, lambda: self.tools[action](**action_input)
                        ),
                        timeout=60.0,
                    )
            except asyncio.TimeoutError:
                return f"⏱️ 执行超时\n\n动作 '{action}' 执行超过60秒\n\n💡 提示：任务可能过于复杂，考虑分解为更小的步骤"
            except asyncio.CancelledError:
                # 任务被取消，重新抛出以便上层处理
                raise

            # 处理结果（增强None检查）
            if result is None:
                return "✅ 执行成功（无返回值）"

            # 处理字典结果（增强错误检测）
            if isinstance(result, dict):
                if result.get("error"):  # 只在有实际错误内容时才报错
                    error_msg = result["error"]
                    # 提供更友好的错误提示
                    if "permission" in error_msg.lower() or "权限" in error_msg:
                        return f"🔒 权限错误\n\n{error_msg}\n\n💡 提示：\n- 检查文件/目录权限\n- 可能需要修改权限或使用其他路径\n- 使用 run_shell 执行 chmod 命令修改权限"
                    elif "not found" in error_msg.lower() or "不存在" in error_msg:
                        return f"🔍 未找到错误\n\n{error_msg}\n\n💡 提示：\n- 检查文件/目录是否存在\n- 确认路径是否正确\n- 使用 list_files 查看可用文件"
                    elif "timeout" in error_msg.lower() or "超时" in error_msg:
                        return f"⏱️ 超时错误\n\n{error_msg}\n\n💡 提示：\n- 网络请求或操作超时\n- 可以重试或检查网络连接\n- 考虑增加超时时间"
                    else:
                        return f"❌ 错误：{error_msg}"
                elif "success" in result and not result["success"]:
                    reason = result.get("message") or result.get("reason") or "未知原因"
                    return f"❌ 执行失败：{reason}"

            # 优化：根据内容类型动态调整截断阈值（从配置读取）
            result_str = str(result)

            # 不再在这里做特殊处理，返回完整结果
            # 显示格式化由_format_observation_for_display处理

            # 判断内容类型
            is_code_content = any(
                indicator in result_str[:200]
                for indicator in [
                    "def ",
                    "class ",
                    "import ",
                    "from ",
                    "#!/",  # Python
                    "function ",
                    "const ",
                    "let ",
                    "var ",  # JavaScript
                    "public ",
                    "private ",
                    "protected ",  # Java/C++
                    "<?php",
                    "namespace ",  # PHP
                ]
            )

            is_test_output = any(
                indicator in result_str[:500]
                for indicator in [
                    "test session starts",
                    "PASSED",
                    "FAILED",
                    "pytest",
                    "unittest",
                    "Test",
                    "Traceback",
                    "AssertionError",
                    "===",
                    "---",
                    "collected",
                    "passed",
                    "failed",
                ]
            )

            # 根据内容类型设置不同的阈值（从配置读取）
            if is_test_output:
                truncate_threshold = settings.output.test_output_threshold
                preview_length = settings.output.test_output_preview
            elif is_code_content:
                truncate_threshold = settings.output.code_content_threshold
                preview_length = settings.output.code_content_preview
            else:
                truncate_threshold = settings.output.normal_output_threshold
                preview_length = settings.output.normal_output_preview

            # 如果超过阈值，保存到文件
            if len(result_str) > truncate_threshold:
                try:
                    output_file = await self.context_manager.save_large_output(
                        result_str, f"{action}_output.txt"
                    )
                    # 返回预览内容
                    preview = result_str[:preview_length]
                    # 如果是测试输出且启用了摘要，尝试提取摘要
                    if is_test_output and settings.output.test_summary_enabled:
                        summary_lines = []
                        for line in result_str.split("\n"):
                            if any(
                                keyword in line
                                for keyword in [
                                    "passed",
                                    "failed",
                                    "error",
                                    "PASSED",
                                    "FAILED",
                                    "ERROR",
                                    "===",
                                    "collected",
                                ]
                            ):
                                summary_lines.append(line)
                        if summary_lines:
                            # 使用配置的最大行数
                            max_lines = settings.output.test_summary_max_lines
                            summary = "\n".join(summary_lines[-max_lines:])
                            return f"📄 输出过长（{len(result_str)}字符），已保存到文件：{output_file}\n\n📊 测试摘要：\n{summary}\n\n💡 使用 read_file 工具查看完整内容"

                    return f"📄 输出过长（{len(result_str)}字符），已保存到文件：{output_file}\n\n💡 使用 read_file 工具查看完整内容\n\n前{preview_length}字符预览：\n{preview}..."
                except Exception as e:
                    # 如果保存失败，返回更多内容
                    return f"{result_str[:preview_length]}...\n\n⚠️ （输出过长已截断，保存失败：{str(e)}）"

            # 对于中等长度的输出，返回更多内容
            medium_threshold = truncate_threshold // 2
            if len(result_str) > medium_threshold:
                return (
                    result_str[:medium_threshold]
                    + f"...\n\n（已截断，共{len(result_str)}字符）"
                )

            return result_str

        except TypeError as e:
            # 参数错误（增强提示）
            error_str = str(e)
            error_msg = f"❌ 执行错误：参数不匹配\n\n{error_str}\n\n"

            # 尝试提取缺失或多余的参数信息
            if "missing" in error_str.lower():
                error_msg += "💡 可能原因：缺少必需参数\n"
            elif (
                "unexpected" in error_str.lower()
                or "got an unexpected" in error_str.lower()
            ):
                error_msg += "💡 可能原因：使用了不存在的参数名\n"
            elif "takes" in error_str.lower() and "positional" in error_str.lower():
                error_msg += "💡 可能原因：参数数量不匹配\n"

            error_msg += f"\n📝 你的输入：\n"
            error_msg += f"  动作：{action}\n"
            error_msg += f"  参数：{action_input}\n\n"

            # 添加工具文档（完整版）
            doc = registry.get_documentation(action)
            if doc:
                error_msg += f"📖 正确的工具文档：\n{doc}\n"

            # 添加常见错误提示
            error_msg += "\n🔧 常见解决方法：\n"
            error_msg += "  1. 检查参数名是否拼写正确\n"
            error_msg += "  2. 确认所有必需参数都已提供\n"
            error_msg += "  3. 检查参数类型是否正确（字符串、数字等）\n"
            error_msg += "  4. 参考上面的工具文档和示例\n"

            return error_msg
        except FileNotFoundError as e:
            return f"🔍 执行错误：文件未找到\n\n{str(e)}\n\n💡 提示：\n- 检查文件路径是否正确\n- 确认文件是否存在\n- 使用 list_files 查看可用文件"
        except PermissionError as e:
            return f"🔒 执行错误：权限不足\n\n{str(e)}\n\n💡 提示：\n- 可能需要修改文件权限\n- 使用 run_shell 执行 chmod 命令\n- 或选择有权限的目录"
        except ConnectionError as e:
            return f"🌐 执行错误：网络连接失败\n\n{str(e)}\n\n💡 提示：\n- 检查网络连接\n- 稍后重试\n- 检查防火墙设置"
        except Exception as e:
            # 提供更详细的错误信息（增强诊断）
            import traceback

            error_trace = traceback.format_exc()
            error_type = type(e).__name__
            error_msg = str(e)

            # 特殊错误类型的友好提示
            friendly_tips = {
                "JSONDecodeError": "JSON格式错误，请检查输入的JSON字符串格式",
                "KeyError": "缺少必需的键，请检查输入参数",
                "ValueError": "值错误，请检查输入参数的值是否有效",
                "ImportError": "导入错误，可能缺少依赖包，使用 run_shell 安装",
                "ModuleNotFoundError": "模块未找到，使用 run_shell 安装依赖包",
                "AttributeError": "属性错误，对象可能不存在该属性或方法",
                "IndexError": "索引错误，列表索引超出范围",
                "KeyboardInterrupt": "用户中断操作",
            }

            tip = friendly_tips.get(error_type, "请检查错误信息并修正")

            return f"❌ 执行错误：{error_type}\n\n{error_msg}\n\n💡 提示：{tip}\n\n📋 详细信息：\n{error_trace[:300]}..."

    async def _compact_context(self, messages: List[Dict]):
        """上下文缩减 - 智能版：让模型参与判断重要信息，并缩减文件内容"""
        print("📦 执行智能上下文缩减...")

        # 保存完整历史到文件
        history_file = await self.context_manager.save_history(self.steps)

        # 从配置获取参数
        keep_messages = (
            self.context_config.compact_keep_messages if self.context_config else 20
        )
        keep_rounds = (
            self.context_config.compact_keep_rounds if self.context_config else 8
        )
        summary_steps = (
            self.context_config.compact_summary_steps if self.context_config else 10
        )
        protect_first_rounds = (
            self.context_config.compact_protect_first_rounds
            if self.context_config
            else 3
        )

        # 计算需要缩减的消息范围
        if len(messages) <= keep_messages:
            print("✅ 消息数量未超过阈值，无需缩减")
            return

        # 提取需要总结的中间消息（保留系统提示、前N轮、最近N轮）
        system_messages = messages[:2]  # 系统提示 + 初始任务
        first_rounds_messages = messages[
            2 : 2 + protect_first_rounds * 2
        ]  # 前N轮对话（任务规划）
        recent_messages = messages[-keep_rounds * 2 :]  # 最近N轮对话

        # 中间消息：跳过系统、前N轮、最近N轮
        middle_start = 2 + protect_first_rounds * 2
        middle_end = -keep_rounds * 2
        middle_messages = (
            messages[middle_start:middle_end]
            if len(messages) > middle_start + keep_rounds * 2
            else []
        )

        if not middle_messages:
            print("✅ 没有中间消息需要缩减")
            return

        # 智能缩减文件内容：将大段文件内容保存到.aacode，只保留摘要
        middle_messages = await self._compact_file_contents(middle_messages)

        # 使用模型生成三块智能摘要
        try:
            summaries = await self._generate_three_part_summary(
                middle_messages, self.steps[-summary_steps:]
            )
        except Exception as e:
            print(f"⚠️  智能摘要生成失败，使用简单摘要: {e}")
            summary = await self._generate_summary(self.steps[-summary_steps:])
            summaries = {
                "file_content_summary": "",
                "tool_execution_summary": summary,
                "keep_original_summary": "",
            }

        # 构建缩减后的消息列表
        compacted_messages = system_messages.copy()
        compacted_messages.extend(first_rounds_messages)  # 添加前N轮（任务规划）

        # 插入三块智能摘要
        compact_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
        summary_content = f"""## 🧠 智能历史摘要（AI生成）

**⏰ 压缩时间**: {compact_time}

### 📁 文件内容摘要
{summaries["file_content_summary"] or "无文件读取操作"}

### 🔧 工具执行摘要
{summaries["tool_execution_summary"] or "无工具执行"}

### 💡 重要信息（保留原样）
{summaries["keep_original_summary"] or "无需特别保留的信息"}

**完整历史**: {history_file}

**重要提示**: 
- 上述摘要由AI分析生成，分类保留了关键信息
- 文件内容已归档，可通过归档路径重新读取
- 如需查看完整历史，使用 read_file 工具读取上述文件
- 继续执行当前任务，参考最近的观察结果
- 避免重复已完成的工作"""

        compacted_messages.append({"role": "system", "content": summary_content})

        # 添加最近的消息
        compacted_messages.extend(recent_messages)

        # 计算缩减效果
        old_tokens = self._estimate_tokens(messages)
        new_tokens = self._estimate_tokens(compacted_messages)

        messages.clear()
        messages.extend(compacted_messages)

        print(f"✅ 智能上下文缩减完成：{len(messages)} 条消息 | Token: {old_tokens} → {new_tokens} (减少 {(old_tokens - new_tokens) / old_tokens * 100:.1f}%) | 保护前{protect_first_rounds}轮 | 保留最近{keep_rounds}轮 | 摘要{len(middle_messages)}条")

    async def _compact_file_contents(self, messages: List[Dict]) -> List[Dict]:
        """
        智能缩减内容：将大段内容保存到.aacode，只保留摘要和路径

        处理的内容类型：
        1. read_file 的文件内容
        2. run_shell 的长输出
        3. search_files 的搜索结果
        4. list_files 的文件列表
        5. execute_python 的执行输出

        Args:
            messages: 消息列表

        Returns:
            缩减后的消息列表
        """
        compacted = []

        for msg in messages:
            content = msg.get("content", "")

            # 跳过已经归档的内容（避免重复处理）
            if "[已归档]" in content or "归档路径:" in content:
                compacted.append(msg)
                continue

            # 检查是否包含大段内容（超过500字符）
            if len(content) > 500:
                import re

                new_content = content
                content_modified = False

                # 策略1：匹配代码块（```...```）超过500字符的
                code_blocks = list(
                    re.finditer(r"```[\s\S]{500,}?```", new_content, re.DOTALL)
                )

                for match in code_blocks:
                    content_block = match.group(0)

                    # 判断内容类型
                    content_type = self._detect_content_type(content, content_block)
                    identifier = self._extract_identifier(content, content_type)

                    # 保存到.aacode/context/
                    try:
                        saved_path = await self.context_manager.save_large_output(
                            content_block,
                            f"{content_type}_{identifier}_{asyncio.get_event_loop().time():.0f}.txt",
                        )

                        # 生成智能摘要
                        summary = self._generate_content_summary(
                            content_block, content_type
                        )

                        # 替换为简短引用（包含摘要、路径和哈希）
                        # 从路径中提取哈希值
                        import re

                        hash_match = re.search(r"_([a-f0-9]{8})\.txt$", saved_path)
                        hash_info = f"哈希: {hash_match.group(1)}" if hash_match else ""

                        if content_type == "file_content":
                            replacement = f"""[{self._get_content_type_name(content_type)}已归档]
原文件: {identifier}
归档路径: {saved_path}
大小: {len(content_block)} 字符
{hash_info}
摘要: {summary}
💡 如需查看完整内容，使用 read_file 工具读取归档文件: {saved_path}"""
                        else:
                            replacement = f"""[{self._get_content_type_name(content_type)}已归档]
标识: {identifier}
归档路径: {saved_path}
大小: {len(content_block)} 字符
{hash_info}
摘要: {summary}
💡 如需查看完整内容，使用 read_file 工具读取归档文件: {saved_path}"""

                        new_content = new_content.replace(content_block, replacement)
                        content_modified = True

                    except Exception as e:
                        print(f"⚠️  保存{content_type}内容失败: {e}")

                # 策略2：如果没有代码块，但内容很长（>1500字符），检查是否有大段文本
                if not content_modified and len(content) > 1500:
                    # 检查是否有搜索结果、文件列表等
                    if "匹配" in content or "search" in content.lower():
                        content_type = "search_results"
                        identifier = self._extract_identifier(content, content_type)

                        try:
                            saved_path = await self.context_manager.save_large_output(
                                content,
                                f"{content_type}_{identifier}_{asyncio.get_event_loop().time():.0f}.txt",
                            )

                            summary = self._generate_content_summary(
                                content, content_type
                            )

                            if content_type == "file_content":
                                new_content = f"""[{self._get_content_type_name(content_type)}已归档]
原文件: {identifier}
归档路径: {saved_path}
大小: {len(content)} 字符
摘要: {summary}
💡 如需查看完整内容，使用 read_file 工具读取归档文件，或自己搜索文件"""
                            else:
                                new_content = f"""[{self._get_content_type_name(content_type)}已归档]
标识: {identifier}
归档路径: {saved_path}
大小: {len(content)} 字符
摘要: {summary}
💡 如需查看完整内容，使用 read_file 工具读取归档文件，或自己自主搜索需要的文件"""

                            content_modified = True

                        except Exception as e:
                            print(f"⚠️  保存{content_type}内容失败: {e}")

                # 如果内容被修改，更新消息
                if content_modified:
                    msg = msg.copy()
                    msg["content"] = new_content

            compacted.append(msg)

        return compacted

    def _detect_content_type(self, full_content: str, content_block: str) -> str:
        """检测内容类型"""
        full_lower = full_content.lower()

        # 检查上下文关键词
        if any(
            kw in full_lower
            for kw in ["文件内容", "file content", "读取文件", "read_file"]
        ):
            return "file_content"
        elif any(
            kw in full_lower
            for kw in ["执行命令", "run_shell", "命令输出", "command output", "stdout"]
        ):
            return "shell_output"
        elif any(
            kw in full_lower for kw in ["搜索结果", "search_files", "search results"]
        ):
            return "search_results"
        elif any(kw in full_lower for kw in ["文件列表", "list_files", "file list"]):
            return "file_list"
        elif any(
            kw in full_lower
            for kw in ["执行结果", "execute_python", "代码输出", "code output"]
        ):
            return "code_output"
        else:
            # 默认类型
            return "content"

    def _extract_identifier(self, full_content: str, content_type: str) -> str:
        """提取内容的标识符（文件名、命令等）"""
        import re

        if content_type == "file_content":
            # 策略1: 从观察内容开头提取文件路径（read_file 的标准格式）
            # 格式: "观察：文件内容\n```python\n# path/to/file.py\n..."
            match = re.search(
                r"```[a-z]*\s*\n\s*#\s*([^\n]+\.[\w]+)", full_content, re.IGNORECASE
            )
            if match:
                return match.group(1).strip()

            # 策略2: 查找明确的文件路径标记
            match = re.search(
                r"(?:文件路径|file path|读取文件|read_file)[:\s]+([^\n\s]+\.[\w]+)",
                full_content,
                re.IGNORECASE,
            )
            if match:
                return match.group(1).strip()

            # 策略3: 查找任何看起来像文件路径的内容（包含扩展名）
            match = re.search(
                r"([a-zA-Z0-9_/\-\.]+\.(?:py|js|ts|md|txt|json|yaml|yml|csv|html|css))",
                full_content,
                re.IGNORECASE,
            )
            if match:
                return match.group(1).strip()

            return "unknown_file"

        elif content_type == "shell_output":
            # 提取命令
            match = re.search(
                r"(?:命令|command)[:\s]+([^\n]+)", full_content, re.IGNORECASE
            )
            if match:
                cmd = match.group(1).strip()
                # 只保留命令的前30个字符，清理特殊字符
                cmd = (
                    cmd[:30]
                    .replace(" ", "_")
                    .replace("/", "_")
                    .replace("|", "_")
                    .replace(">", "_")
                )
                return cmd
            return "unknown_cmd"

        elif content_type == "search_results":
            # 提取搜索查询
            match = re.search(
                r"(?:搜索|search|query)[:\s]+([^\n]+)", full_content, re.IGNORECASE
            )
            if match:
                query = (
                    match.group(1)
                    .strip()[:30]
                    .replace(" ", "_")
                    .replace('"', "")
                    .replace("'", "")
                )
                return query
            return "unknown_search"

        elif content_type == "file_list":
            return "file_list"

        elif content_type == "code_output":
            return "code_output"

        else:
            return "content"

    def _generate_content_summary(self, content_block: str, content_type: str) -> str:
        """生成内容的智能摘要"""
        # 提取前200字符作为预览
        preview = content_block[:200].replace("\n", " ").strip()
        if len(content_block) > 200:
            preview += "..."

        # 根据内容类型添加额外信息
        if content_type == "shell_output":
            # 检查是否有错误
            if "error" in content_block.lower() or "failed" in content_block.lower():
                preview = "⚠️ 包含错误信息 | " + preview
            elif (
                "success" in content_block.lower() or "passed" in content_block.lower()
            ):
                preview = "✅ 执行成功 | " + preview

        elif content_type == "search_results":
            # 统计匹配数
            import re

            matches = len(re.findall(r"\n", content_block[:1000]))
            preview = f"约 {matches} 个匹配 | " + preview

        elif content_type == "file_list":
            # 统计文件数
            import re

            files = len(re.findall(r"\n", content_block[:1000]))
            preview = f"约 {files} 个文件 | " + preview

        return preview

    def _get_content_type_name(self, content_type: str) -> str:
        """获取内容类型的友好名称"""
        names = {
            "file_content": "文件内容",
            "shell_output": "命令输出",
            "search_results": "搜索结果",
            "file_list": "文件列表",
            "code_output": "代码输出",
            "content": "内容",
        }
        return names.get(content_type, "内容")

    async def _generate_intelligent_summary(
        self, middle_messages: List[Dict], recent_steps: List[ReActStep]
    ) -> str:
        """使用模型生成智能摘要"""
        # 构建摘要请求
        summary_prompt = f"""请分析以下对话历史和执行步骤，生成一个简洁但全面的摘要。

**摘要要求**：
1. 保留所有关键决策和重要发现
2. 记录已完成的主要任务和子任务
3. 标注遇到的重要错误和解决方案
4. 保留重要的文件路径、配置信息
5. 删除冗余和重复的信息
6. 使用结构化格式（标题、列表）
7. 控制在500-800字符内

**工具内容卸载原则**（重要！）：
对于已经完成其目的的工具输出，应该适当缩减，但必须保留归档路径：

1. **已归档的内容**：
   - 如果看到 "[xxx已归档]" 标记，说明完整内容已保存
   - 摘要中必须保留归档路径，格式：`归档: .aacode/context/xxx.txt`
   - 可以进一步精简摘要，但不能删除归档路径

2. **read_file 的内容**：
   - 如果文件内容已经被使用（如：已经根据内容编写了代码、已经理解了配置）
   - 摘要：保留关键信息 + 归档路径
   - 例：`已读取 config.py，了解数据库配置 | 归档: .aacode/context/file_content_xxx.txt`

3. **run_shell 的输出**：
   - 如果命令输出已经被处理（如：已经根据版本信息安装了包）
   - 摘要：保留结果 + 归档路径
   - 例：`执行 pytest，15 passed | 归档: .aacode/context/shell_output_xxx.txt`

4. **search_files 的结果**：
   - 如果搜索结果已经被使用（如：已经找到目标文件）
   - 摘要：保留关键发现 + 归档路径
   - 例：`搜索到 10 个匹配，定位到 utils/helper.py | 归档: .aacode/context/search_results_xxx.txt`

5. **list_files 的结果**：
   - 如果文件列表已经被使用（如：已经了解项目结构）
   - 摘要：保留结构概览 + 归档路径
   - 例：`项目包含 50 个文件，主要模块 core/, utils/ | 归档: .aacode/context/file_list_xxx.txt`

**关键原则**：
- ✅ 必须保留：归档路径（.aacode/context/xxx.txt）
- ✅ 必须保留：关键决策和发现
- ✅ 可以精简：详细的输出内容
- ❌ 不能删除：归档路径引用
- ❌ 不能删除：未解决的错误信息
   - 摘要：只保留"已读取 xxx 文件，了解了 yyy"，不保留完整内容
   - 例外：如果后续可能还需要参考，保留关键部分

2. **run_shell 的输出**：
   - 如果命令输出已经被处理（如：已经根据版本信息安装了包、已经根据测试结果修复了bug）
   - 摘要：只保留"执行了 xxx 命令，结果是 yyy"，不保留完整输出
   - 例外：如果是错误信息且未解决，保留详细信息

3. **search_files 的结果**：
   - 如果搜索结果已经被使用（如：已经找到了目标文件并进行了修改）
   - 摘要：只保留"搜索到 N 个匹配，定位到 xxx 文件"
   - 例外：如果还需要进一步分析，保留关键匹配

4. **list_files 的结果**：
   - 如果文件列表已经被使用（如：已经了解了项目结构）
   - 摘要：只保留"项目包含 N 个文件，主要有 xxx, yyy"
   - 例外：如果还需要查找特定文件，保留完整列表

5. **execute_python 的输出**：
   - 如果代码执行结果已经被验证（如：测试通过、功能正常）
   - 摘要：只保留"执行了 xxx 代码，测试通过"
   - 例外：如果有错误需要修复，保留错误信息

**判断是否可以卸载的标准**：
- ✅ 可以卸载：工具输出已经达成了其目的（理解、验证、定位等）
- ✅ 可以卸载：后续步骤没有再次引用该内容
- ❌ 不能卸载：内容包含未解决的错误
- ❌ 不能卸载：内容是当前任务的核心参考资料
- ❌ 不能卸载：后续步骤可能需要再次查看

**摘要示例**：

不好的摘要（保留了太多已使用的内容）：
```
步骤1: 读取了 config.py 文件
内容: [完整的500行配置文件内容]
步骤2: 根据配置文件修改了数据库连接
```

好的摘要（适当卸载）：
```
步骤1: 读取 config.py，了解了数据库配置（host=localhost, port=5432）
步骤2: 根据配置修改了数据库连接，测试通过
[config.py 完整内容已归档，如需查看使用 read_file 工具]
```

**对话历史**（{len(middle_messages)}条消息）：
"""

        # 添加中间消息的简要内容
        for i, msg in enumerate(middle_messages[:20]):  # 最多20条
            role = msg.get("role", "unknown")
            content = msg.get("content", "")[:200]  # 每条最多200字符
            summary_prompt += f"\n[{role}] {content}..."

        if len(middle_messages) > 20:
            summary_prompt += f"\n... 还有 {len(middle_messages) - 20} 条消息"

        # 添加执行步骤
        summary_prompt += f"\n\n**执行步骤**（最近{len(recent_steps)}步）：\n"
        for i, step in enumerate(recent_steps):
            summary_prompt += f"\n步骤{i + 1}: {step.thought[:150]}..."
            if step.actions:
                summary_prompt += (
                    f"\n  动作: {', '.join([a.action for a in step.actions])}"
                )

        summary_prompt += (
            "\n\n请生成摘要（500-800字符），注意适当卸载已完成目的的工具内容："
        )

        # 调用模型生成摘要
        try:
            summary_messages = [{"role": "user", "content": summary_prompt}]
            summary_response = await asyncio.wait_for(
                self.model_caller(summary_messages),
                timeout=30.0,  # 30秒超时
            )

            # 清理摘要（移除可能的markdown格式）
            summary = summary_response.strip()
            if summary.startswith("```"):
                summary = "\n".join(summary.split("\n")[1:-1])

            # 限制长度
            if len(summary) > 1000:
                summary = summary[:1000] + "..."

            return summary
        except asyncio.TimeoutError:
            return "摘要生成超时，请查看完整历史"
        except Exception as e:
            return f"摘要生成失败: {str(e)}"

    async def _generate_three_part_summary(
        self, middle_messages: List[Dict], recent_steps: List[ReActStep]
    ) -> Dict[str, str]:
        """使用模型生成三块分类摘要"""
        summary_prompt = f"""请分析以下对话历史，生成三块分类摘要。

**对话历史**（{len(middle_messages)}条消息）：
"""

        # 添加中间消息的简要内容
        for i, msg in enumerate(middle_messages[:30]):  # 最多30条
            role = msg.get("role", "unknown")
            content = msg.get("content", "")[:300]  # 每条最多300字符
            summary_prompt += f"\n[{role}] {content}..."

        if len(middle_messages) > 30:
            summary_prompt += f"\n... 还有 {len(middle_messages) - 30} 条消息"

        summary_prompt += f"""

**要求**：生成三块摘要，返回JSON格式：

{{
  "file_content_summary": "文件内容摘要（200-300字）",
  "tool_execution_summary": "工具执行摘要（200-300字）",
  "keep_original_summary": "需要保留原样的重要信息（100-200字）"
}}

**各块说明**：

1. **file_content_summary**（文件内容摘要）：
   - 总结所有 read_file 操作
   - 格式：`读取了 config.py（数据库配置）、main.py（主程序）| 归档: .aacode/context/xxx.txt`
   - 必须保留归档路径
   - 如果没有文件读取，返回空字符串

2. **tool_execution_summary**（工具执行摘要）：
   - 总结 run_shell、search_files、list_files 等工具执行
   - 格式：`执行了3次测试（2次通过），搜索到10个匹配文件 | 归档: .aacode/context/xxx.txt`
   - 保留关键结果和归档路径
   - 如果没有工具执行，返回空字符串

3. **keep_original_summary**（重要信息保留）：
   - 包含未解决的错误
   - 包含关键决策和架构选择
   - 包含重要技术依赖和技术参数
   - 包含关键工具执行结果
   - 如果没有需要特别保留的，返回空字符串

**关键原则**：
- ✅ 必须保留所有归档路径（.aacode/context/xxx.txt）
- ✅ 已归档内容只保留摘要+路径，不保留详细内容
- ✅ 未解决的错误必须在 keep_original_summary 中
- ❌ 不要重复信息
- ❌ 不要保留已完成任务的详细输出

请返回JSON格式的三块摘要："""

        # 调用模型生成摘要
        try:
            summary_messages = [{"role": "user", "content": summary_prompt}]
            summary_response = await asyncio.wait_for(
                self.model_caller(summary_messages),
                timeout=60.0,  # 60秒超时
            )

            # 解析JSON响应
            summaries = self._extract_json_from_response(summary_response)

            if summaries and isinstance(summaries, dict):
                return {
                    "file_content_summary": summaries.get("file_content_summary", ""),
                    "tool_execution_summary": summaries.get(
                        "tool_execution_summary", ""
                    ),
                    "keep_original_summary": summaries.get("keep_original_summary", ""),
                }
            else:
                # 如果无法解析JSON，返回默认值
                return {
                    "file_content_summary": "",
                    "tool_execution_summary": summary_response[:500],
                    "keep_original_summary": "",
                }
        except asyncio.TimeoutError:
            return {
                "file_content_summary": "",
                "tool_execution_summary": "摘要生成超时",
                "keep_original_summary": "",
            }
        except Exception as e:
            return {
                "file_content_summary": "",
                "tool_execution_summary": f"摘要生成失败: {str(e)}",
                "keep_original_summary": "",
            }

    def _extract_json_from_response(self, response: str) -> Optional[Dict]:
        """从模型响应中提取JSON（复用健壮的解析逻辑）"""
        import json
        import re

        # 使用与 _parse_response 相同的JSON解析模式
        json_patterns = [
            r"```json\s*\n(.*?)\n```",  # 标准json代码块
            r"```JSON\s*\n(.*?)\n```",  # 大写JSON
            r"```\s*\n(\{.*?\})\s*\n```",  # 普通代码块包裹的JSON
            r"(\{[\s\S]*?\})",  # 直接的JSON对象
            r"```json\s*\n(.*?)(?:\n```|$)",  # 不严格的结束标记
        ]

        for pattern in json_patterns:
            json_match = re.search(pattern, response, re.DOTALL)
            if json_match:
                json_str = None
                try:
                    json_str = json_match.group(1).strip()
                    # 清理可能的markdown残留
                    json_str = (
                        json_str.replace("```json", "")
                        .replace("```JSON", "")
                        .replace("```", "")
                        .strip()
                    )

                    # 尝试修复常见的JSON格式问题
                    json_str = self._fix_json_format(json_str)

                    data = json.loads(json_str)
                    return data
                except json.JSONDecodeError:
                    # 记录但继续尝试其他模式
                    continue
                except Exception:
                    continue

        return None

    async def _update_todo_from_thought(self, thought: str, todo_manager) -> None:
        """从思考中自动更新待办清单（已废弃，不再自动记录思考过程）"""
        pass

    async def _update_todo_from_error(self, observation, todo_manager) -> None:
        """从错误观察中自动添加修复任务到待办清单 - 优化版：更精确的错误检测"""
        try:
            if not todo_manager:
                return

            # 1. 处理字典格式的observation
            if isinstance(observation, dict):
                # 特殊处理：run_shell 的返回
                # run_shell 总是返回 success=True（工具执行成功）
                # 需要检查 returncode 来判断命令是否成功
                if "returncode" in observation:
                    returncode = observation.get("returncode", 0)
                    if returncode == 0:
                        # 命令执行成功，不是错误
                        return
                    # returncode != 0，继续检查是否需要添加待办事项
                    # 但不是所有非零退出码都需要添加待办事项
                    # 例如：grep 没找到匹配（退出码1）是正常的
                    stderr = observation.get("stderr", "")
                    if stderr and any(
                        err in stderr.lower()
                        for err in ["error", "exception", "traceback", "failed"]
                    ):
                        # stderr 中有明确的错误信息，才添加待办事项
                        pass
                    else:
                        # 只是非零退出码，但没有明确错误，不添加待办事项
                        return

                # 如果明确标记为成功，直接返回
                if observation.get("success") is True and "error" not in observation:
                    return

                # 如果有 error 字段但 success=True，可能是超时等情况
                if observation.get("success") is True and "error" in observation:
                    # 检查是否是超时
                    if observation.get("timeout"):
                        # 超时可以添加待办事项
                        pass
                    else:
                        # 其他情况，提取 error 字段
                        pass

                # 如果没有error字段且success=True，不当作错误
                if "error" not in observation and observation.get("success") is True:
                    return

                # 提取error字段作为观察内容
                if "error" in observation:
                    observation = str(observation.get("error", ""))
                else:
                    observation = str(observation)

            # 2. 转换为字符串并检查
            obs_str = str(observation)
            obs_lower = obs_str.lower()

            # 3. 先检查成功标识（更严格）
            success_indicators = [
                "'success': true",
                '"success": true',
                "✅",
                "成功",
                "successfully",
                "completed",
                "passed",
            ]

            if any(indicator.lower() in obs_lower for indicator in success_indicators):
                return

            # 4. 排除误报情况
            false_positive_patterns = [
                "no error",
                "error: none",
                "error=false",
                "0 errors",
                "without error",
                "error handling",  # 讨论错误处理的文本
            ]

            if any(pattern in obs_lower for pattern in false_positive_patterns):
                return

            # 5. 检测真正的错误（更精确的标识）
            error_indicators = [
                # Python 异常
                "traceback (most recent call last)",
                "exception:",
                # 明确的错误标记
                "❌",
                "执行失败",
                "execution failed",
                "command failed",
                # 参数错误
                "参数验证失败",
                "parameter validation failed",
                # 权限错误
                "permission denied",
                "权限不足",
                # 工具执行异常
                "工具执行异常",
            ]

            has_error = any(indicator in obs_lower for indicator in error_indicators)

            # 如果没有明确的错误标识，但包含"error"关键词，进一步检查
            if not has_error and "error" in obs_lower:
                # 检查是否是真正的错误消息格式
                import re

                # 匹配 "error: xxx" 或 "错误: xxx" 格式
                if re.search(r"(error|错误)[:\s]+\w+", obs_lower):
                    has_error = True

            if not has_error:
                return

            # 6. 提取更详细的错误信息
            error_type, error_detail = self._extract_error_info(obs_str)

            # 7. 只有在错误类型明确时才添加待办事项
            if error_type != "未知错误":
                fix_task = f"{error_type}: {error_detail}"
                await todo_manager.add_todo_item(
                    item=fix_task, priority="high", category="错误修复"
                )

                # 不再打印"已自动添加待办事项"，因为 add_todo_item 已经打印了
                # 只在日志级别记录
                pass
            else:
                # 未知错误：不自动添加待办事项，避免噪音
                pass

        except Exception as e:
            # 静默失败，避免干扰主流程
            pass

    def _extract_error_info(self, observation: str) -> tuple:
        """提取错误类型和详细信息"""
        import re

        # 1. 尝试匹配 Python 异常
        exception_patterns = [
            (r"(ImportError|ModuleNotFoundError)[:\s]+(.+)", "ImportError"),
            (r"(SyntaxError)[:\s]+(.+)", "SyntaxError"),
            (r"(NameError)[:\s]+(.+)", "NameError"),
            (r"(TypeError)[:\s]+(.+)", "TypeError"),
            (r"(ValueError)[:\s]+(.+)", "ValueError"),
            (r"(AttributeError)[:\s]+(.+)", "AttributeError"),
            (r"(FileNotFoundError)[:\s]+(.+)", "FileNotFoundError"),
            (r"(PermissionError)[:\s]+(.+)", "PermissionError"),
        ]

        for pattern, error_name in exception_patterns:
            match = re.search(pattern, observation, re.IGNORECASE)
            if match:
                error_detail = match.group(2).strip()[:150]
                return error_name, error_detail

        # 2. 尝试匹配命令执行失败（改进：更精确的匹配）
        if "命令执行失败" in observation or "command failed" in observation.lower():
            # 提取退出码
            exitcode_match = re.search(r"退出码[:\s]+(\d+)", observation)
            if exitcode_match:
                exitcode = exitcode_match.group(1)
                # 提取 stderr 或错误消息
                stderr_match = re.search(
                    r"错误输出[:\s]+(.+)", observation, re.IGNORECASE | re.DOTALL
                )
                if stderr_match:
                    stderr_text = stderr_match.group(1).strip()[:150]
                    return "命令执行失败", f"退出码 {exitcode}: {stderr_text}"
                return "命令执行失败", f"退出码 {exitcode}"

            # 没有退出码信息，尝试提取错误输出
            stderr_match = re.search(
                r"错误输出[:\s]+(.+)", observation, re.IGNORECASE | re.DOTALL
            )
            if stderr_match:
                return "命令执行失败", stderr_match.group(1).strip()[:150]

            return "命令执行失败", "命令返回非零退出码"

        # 3. 检查 returncode 或 exit code
        if "returncode" in observation.lower() or "exit code" in observation.lower():
            stderr_match = re.search(
                r"stderr[:\s]+(.+)", observation, re.IGNORECASE | re.DOTALL
            )
            if stderr_match:
                return "命令执行失败", stderr_match.group(1).strip()[:150]
            return "命令执行失败", "命令返回非零退出码"

        # 4. 尝试匹配参数错误
        if (
            "参数验证失败" in observation
            or "parameter validation failed" in observation.lower()
        ):
            # 提取参数名
            param_match = re.search(r"参数[:\s]+(\w+)", observation)
            if param_match:
                return "参数错误", f"参数 {param_match.group(1)} 验证失败"
            return "参数错误", observation[:150]

        # 5. 尝试匹配权限错误
        if "权限" in observation or "permission" in observation.lower():
            return "权限错误", observation[:150]

        # 6. 尝试匹配文件不存在
        if "不存在" in observation or "not found" in observation.lower():
            return "文件不存在", observation[:150]

        # 7. 未知错误
        return "未知错误", observation[:150]

    async def _generate_summary(self, recent_steps: List[ReActStep]) -> str:
        """生成步骤摘要 - 优化：包含更多关键信息"""
        summary_parts = []
        for i, step in enumerate(recent_steps):
            # 保留更多思考内容（从100提高到200字符）
            thought_preview = step.thought[:200] + (
                "..." if len(step.thought) > 200 else ""
            )
            summary_parts.append(f"\n### 步骤 {i + 1}")
            summary_parts.append(f"**思考**: {thought_preview}")

            if step.actions:
                summary_parts.append(
                    f"**动作**: {', '.join([a.action for a in step.actions])}"
                )

                # 添加关键观察结果
                for j, action in enumerate(step.actions):
                    if action.observation:
                        # 保留观察结果的前150字符
                        obs_preview = action.observation[:150] + (
                            "..." if len(action.observation) > 150 else ""
                        )
                        summary_parts.append(f"  - {action.action}: {obs_preview}")

        return "\n".join(summary_parts)

    async def _validate_context_consistency(
        self,
        all_observations: List[str],
        all_observations_for_display: List[str],
        messages: List[Dict],
    ) -> None:
        """
        验证上下文一致性

        Args:
            all_observations: Agent获取的完整观察结果
            all_observations_for_display: 用户看到的简化观察结果
            messages: 当前消息列表
        """
        # 1. 检查观察结果数量一致性
        if len(all_observations) != len(all_observations_for_display):
            print(f"⚠️  上下文一致性警告：观察结果数量不匹配 | Agent观察数: {len(all_observations)}, 用户观察数: {len(all_observations_for_display)}")

        # 2. 检查token使用情况
        current_tokens = self._estimate_tokens(messages)
        if current_tokens > 5000:  # 警告阈值
            # 显示消息分布
            system_tokens = self._estimate_tokens(
                [msg for msg in messages if msg.get("role") == "system"]
            )
            user_tokens = self._estimate_tokens(
                [msg for msg in messages if msg.get("role") == "user"]
            )
            assistant_tokens = self._estimate_tokens(
                [msg for msg in messages if msg.get("role") == "assistant"]
            )
            print(f"📊 上下文大小监控：当前约{current_tokens} tokens | 系统: {system_tokens} | 用户: {user_tokens} | Assistant: {assistant_tokens}")

        # 3. 检查归档路径是否保留（简化版本中）
        for i, obs_display in enumerate(all_observations_for_display):
            if ".aacode/context/" in obs_display:
                # 检查对应的完整观察是否也包含归档路径
                if (
                    i < len(all_observations)
                    and ".aacode/context/" in all_observations[i]
                ):
                    # 提取归档文件名
                    import re

                    archive_match = re.search(
                        r"\.aacode/context/([^\s]+\.txt)", obs_display
                    )
                    if archive_match:
                        archive_file = archive_match.group(1)
                        print(f"✅ 归档路径一致性：动作{i + 1}归档到 {archive_file}")
                else:
                    print(
                        f"⚠️  归档路径不一致：动作{i + 1}的简化版本包含归档路径，但完整版本可能丢失"
                    )

    def _estimate_tokens(self, messages: List[Dict]) -> int:
        """
        估算消息列表的token数

        Args:
            messages: 消息列表

        Returns:
            估算的token数
        """
        if self.encoding:
            # 使用tiktoken精确计算
            total_tokens = 0
            for message in messages:
                content = message.get("content", "")
                try:
                    total_tokens += len(self.encoding.encode(content))
                except:
                    # 回退到简单估算
                    total_tokens += len(content) // 4
            return total_tokens
        else:
            # 简单估算：大约4字符=1token
            total_chars = sum(len(msg.get("content", "")) for msg in messages)
            return total_chars // 4


# 测试
if __name__ == "__main__":
    pass
