"""Coding Agent — 基于 claude_agent_sdk，深度集成 DockerRunner 和 LocalRunner

接收 Router Agent 的任务书（task_spec），在 Docker 或本地 uv 环境中
编写代码、配置环境、封装接口，完成后注册到 registry 并回报 Router。
"""

from __future__ import annotations

import asyncio
import json
import logging
import uuid
from pathlib import Path
from typing import Any, TYPE_CHECKING

from dotenv import load_dotenv

from claude_agent_sdk import (
    AssistantMessage, UserMessage, SystemMessage, ResultMessage,
    TextBlock, ClaudeAgentOptions, tool,
    create_sdk_mcp_server, ClaudeSDKClient, ThinkingBlock, ToolUseBlock,
)

from tool_agent.config import settings
from tool_agent.models import (
    AgentMessage, MessageType, ToolMeta, ToolStatus, BackendRuntime,
)
from tool_agent.runtime.docker_runner import DockerRunner
from tool_agent.runtime.local_runner import LocalRunner
from tool_agent.registry.registry import ToolRegistry

if TYPE_CHECKING:
    from tool_agent.messaging import MessageBus

load_dotenv(override=True)

logger = logging.getLogger(__name__)

# ---- 全局运行时实例（所有 @tool 函数共享） ----
_docker_runner = DockerRunner(lazy_init=True)
_local_runner = LocalRunner()
_registry = ToolRegistry()


# ===========================================================================
#  Docker 环境工具
# ===========================================================================

@tool(name="create_docker_env", description="""
创建一个隔离的 Docker 开发环境。

Args:
    image: Docker 镜像，如 python:3.12-slim, node:18-slim, ubuntu:22.04
    mem_limit: 内存限制，默认 1g
    nano_cpus: CPU 限制，默认 1000000000 (1 CPU)
    ports: 需要映射的端口列表，如 [8080, 3000]
    volumes: 目录挂载，{宿主机绝对路径: 容器路径}。用于将宿���机 staging 目录挂载到容器，实现文件实时同步。
    use_gpu: 是否启用 GPU，默认 False

Returns:
    container_id, port_mapping 等信息。后续操作需要用 container_id。
""", input_schema={
    "type": "object",
    "properties": {
        "image": {"type": "string", "description": "Docker 镜像名称，如 python:3.12-slim"},
        "mem_limit": {"type": "string", "description": "内存限制，默认 1g"},
        "nano_cpus": {"type": "integer", "description": "CPU 限制，默认 1000000000 (1CPU)"},
        "ports": {"type": "array", "items": {"type": "integer"}, "description": "端口列表，如 [8080]"},
        "volumes": {"type": "object", "description": "目录挂载 {宿主机路径: 容器路径}"},
        "use_gpu": {"type": "boolean", "description": "是否启用 GPU，默认 False"},
    },
    "required": ["image"],
})
async def create_docker_env(args):
    result = _docker_runner.create_container(
        image=args.get("image"),
        mem_limit=args.get("mem_limit", "1g"),
        nano_cpus=args.get("nano_cpus", 1_000_000_000),
        ports=args.get("ports"),
        volumes=args.get("volumes"),
        use_gpu=args.get("use_gpu", False),
        gpu_count=args.get("gpu_count", -1),
    )
    return _text_result(result)


@tool(name="run_in_docker", description="""
在 Docker 容器中执行 Shell 命令。支持前台阻塞执行和后台执行。

Args:
    container_id: 容器 ID，由 create_docker_env 返回
    command: Shell 命令，如 "pip install flask", "python app.py", "git clone ..."
    is_background: 是否后台执行（适合启动长期服务），默认 False
    timeout: 前台命令超时秒数，默认 120

Returns:
    前台: exit_code, stdout, stderr
    后台: status, log_file
""", input_schema={
    "type": "object",
    "properties": {
        "container_id": {"type": "string", "description": "容器 ID"},
        "command": {"type": "string", "description": "Shell 命令"},
        "is_background": {"type": "boolean", "description": "是否后台执行，默认 False"},
        "timeout": {"type": "integer", "description": "超时秒数，默认 120"},
    },
    "required": ["container_id", "command"],
})
async def run_in_docker(args):
    result = _docker_runner.run_command(
        container_id=args["container_id"],
        command=args["command"],
        background=args.get("is_background", False),
        timeout=args.get("timeout", 120),
    )
    return _text_result(result)


@tool(name="rebuild_docker_ports", description="""
重建容器并应用新端口映射，保留容器内所有文件。

使用场景：先 clone 项目，读完 README 后才知道需要暴露哪些端口。
重建后返回新 container_id，旧 ID 失效，后续操作必须使用新 ID。

Args:
    container_id: 当前容器 ID
    ports: 新端口列表，如 [8080, 3306]
""", input_schema={
    "type": "object",
    "properties": {
        "container_id": {"type": "string", "description": "当前容器 ID"},
        "ports": {"type": "array", "items": {"type": "integer"}, "description": "新端口列表"},
    },
    "required": ["container_id", "ports"],
})
async def rebuild_docker_ports(args):
    result = _docker_runner.rebuild_with_ports(
        container_id=args["container_id"],
        ports=args["ports"],
        mem_limit=args.get("mem_limit"),
        nano_cpus=args.get("nano_cpus"),
    )
    return _text_result(result)


@tool(name="destroy_docker_env", description="""
销毁 Docker 容器，释放资源。

Args:
    container_id: 容器 ID
""", input_schema={
    "type": "object",
    "properties": {
        "container_id": {"type": "string", "description": "容器 ID"},
    },
    "required": ["container_id"],
})
async def destroy_docker_env(args):
    result = _docker_runner.destroy_container(args["container_id"])
    return _text_result(result)


# ===========================================================================
#  本地 uv 环境工具
# ===========================================================================

@tool(name="create_uv_project", description="""
在本地创建 uv 项目（隔离虚拟环境）。项目创建在 tools/local/{name} 目录下。

Args:
    name: 项目名称
    python_version: Python 版本，默认 3.12

Returns:
    project_dir 路径
""", input_schema={
    "type": "object",
    "properties": {
        "name": {"type": "string", "description": "项目名称"},
        "python_version": {"type": "string", "description": "Python 版本，默认 3.12"},
    },
    "required": ["name"],
})
async def create_uv_project(args):
    result = _local_runner.create_project(
        name=args["name"],
        python_version=args.get("python_version", "3.12"),
    )
    return _text_result(result)


@tool(name="run_in_uv", description="""
在 uv 项目环境中执行命令（uv run）。支持前台阻塞执行和后台执行。

Args:
    project_dir: 项目目录绝对路径
    command: 要执行的命令，如 "python main.py", "pytest"
    is_background: 是否后台执行（适合启动 HTTP 服务），默认 False
    timeout: 前台命令超时秒数，默认 120

Returns:
    前台: exit_code, stdout, stderr
    后台: pid, log_file
""", input_schema={
    "type": "object",
    "properties": {
        "project_dir": {"type": "string", "description": "项目目录绝对路径"},
        "command": {"type": "string", "description": "要执行的命令"},
        "is_background": {"type": "boolean", "description": "是否后台执行，默认 False"},
        "timeout": {"type": "integer", "description": "超时秒数，默认 120"},
    },
    "required": ["project_dir", "command"],
})
async def run_in_uv(args):
    result = _local_runner.run_command(
        project_dir=Path(args["project_dir"]),
        command=args["command"],
        background=args.get("is_background", False),
        timeout=args.get("timeout", 120),
    )
    
    # 构造给 Agent 的反馈
    feedback = f"Exit Code: {result.get('exit_code')}\n"
    if result.get("exit_code") != 0:
        feedback += f"Stderr: {result.get('stderr')}\n"
        feedback += f"完整日志已写入: {result.get('log_path')}，你可以使用 read_file 查看。"
    else:
        feedback += f"Stdout: {result.get('stdout')}"
        
    return {"content": [{"type": "text", "text": feedback}]}


@tool(name="uv_add_dependency", description="""
为 uv 项目添加依赖（uv add）。

Args:
    project_dir: 项目目录绝对路径
    package: 包名，如 "flask" 或 "flask>=2.0"
    dev: 是否为开发依赖，默认 False
""", input_schema={
    "type": "object",
    "properties": {
        "project_dir": {"type": "string", "description": "项目目录"},
        "package": {"type": "string", "description": "包名"},
        "dev": {"type": "boolean", "description": "是否为开发依赖"},
    },
    "required": ["project_dir", "package"],
})
async def uv_add_dependency(args):
    result = _local_runner.add_dependency(
        project_dir=Path(args["project_dir"]),
        package=args["package"],
        dev=args.get("dev", False),
    )
    return _text_result(result)


# ===========================================================================
#  文件读写工具（宿主机文件操作，配合 volume 挂载使用）
# ===========================================================================

@tool(name="write_file", description="""
将内容写入宿主机文件。用于编写工具代码、API 包装器、配置文件等。
配合 Docker volume 挂载，写入宿主机的文件会实时同步到容器内。

Args:
    path: 文件绝对路径
    content: 文件内容
    mode: 写入模式，"overwrite"(覆盖，默认) 或 "append"(追加)
""", input_schema={
    "type": "object",
    "properties": {
        "path": {"type": "string", "description": "文件绝对路径"},
        "content": {"type": "string", "description": "文件内容"},
        "mode": {"type": "string", "enum": ["overwrite", "append"], "description": "写入模式，默认 overwrite"},
    },
    "required": ["path", "content"],
})
async def write_file(args):
    try:
        p = _validate_path(args["path"])
        if p is None:
            return _text_result({"status": "error", "error": f"Path not allowed: {args['path']}. Must be under data/ or tools/."})
        p.parent.mkdir(parents=True, exist_ok=True)

        mode = args.get("mode", "overwrite")
        if mode == "append":
            with open(p, "a", encoding="utf-8") as f:
                f.write(args["content"])
        else:
            p.write_text(args["content"], encoding="utf-8")

        result = {"status": "success", "path": str(p), "size": len(args["content"]), "mode": mode}
    except Exception as e:
        result = {"status": "error", "error": str(e)}
    return _text_result(result)


@tool(name="read_file", description="""
读取宿主机文件内容。用于阅读 README、配置文件、代码等。
支持分段读取大文件，避免一次性加载过多内容。

Args:
    path: 文件绝对路径
    start_line: 起始行号（从 1 开始），默认 1
    max_lines: 最多读取行数，默认 500（0 表示读取全部）
""", input_schema={
    "type": "object",
    "properties": {
        "path": {"type": "string", "description": "文件绝对路径"},
        "start_line": {"type": "integer", "description": "起始行号，默认 1"},
        "max_lines": {"type": "integer", "description": "最多读取行数，默认 500"},
    },
    "required": ["path"],
})
async def read_file(args):
    try:
        p = _validate_path(args["path"])
        if p is None:
            return _text_result({"status": "error", "error": f"Path not allowed: {args['path']}. Must be under data/ or tools/."})
        if not p.exists():
            result = {"status": "error", "error": f"File not found: {p}"}
        else:
            start_line = args.get("start_line", 1)
            max_lines = args.get("max_lines", 500)

            with open(p, "r", encoding="utf-8", errors="replace") as f:
                lines = f.readlines()

            total_lines = len(lines)
            start_idx = max(0, start_line - 1)

            if max_lines == 0:
                selected_lines = lines[start_idx:]
            else:
                end_idx = start_idx + max_lines
                selected_lines = lines[start_idx:end_idx]

            content = "".join(selected_lines)
            result = {
                "status": "success",
                "path": str(p),
                "content": content,
                "total_lines": total_lines,
                "start_line": start_line,
                "lines_read": len(selected_lines),
            }
    except Exception as e:
        result = {"status": "error", "error": str(e)}
    return _text_result(result)


@tool(name="fetch_url", description="""
获取网页内容。用于读取在线文档、API 文档、GitHub README 等。
自动将 HTML 转换为纯文本，去除标签。

Args:
    url: 网页 URL
    max_length: 最大返回字符数，默认 50000（避免过大响应）
    keep_html: 是否保留 HTML 标签，默认 False
""", input_schema={
    "type": "object",
    "properties": {
        "url": {"type": "string", "description": "网页 URL"},
        "max_length": {"type": "integer", "description": "最大返回字符数，默认 50000"},
        "keep_html": {"type": "boolean", "description": "是否保留 HTML 标签，默认 False"},
    },
    "required": ["url"],
})
async def fetch_url(args):
    try:
        import httpx
        import re
        url = args["url"]
        max_length = args.get("max_length", 50000)
        keep_html = args.get("keep_html", False)

        async with httpx.AsyncClient(timeout=30.0, follow_redirects=True) as client:
            resp = await client.get(url)
            resp.raise_for_status()

            content = resp.text

            # 如果不保留 HTML，转换为纯文本
            if not keep_html and "text/html" in resp.headers.get("content-type", ""):
                # 移除 script 和 style 标签及其内容
                content = re.sub(r'<script[^>]*>.*?</script>', '', content, flags=re.DOTALL | re.IGNORECASE)
                content = re.sub(r'<style[^>]*>.*?</style>', '', content, flags=re.DOTALL | re.IGNORECASE)
                # 移除所有 HTML 标签
                content = re.sub(r'<[^>]+>', '', content)
                # 解码 HTML 实体
                import html
                content = html.unescape(content)
                # 清理多余空白
                content = re.sub(r'\n\s*\n', '\n\n', content)
                content = content.strip()

            content = content[:max_length]
            truncated = len(resp.text) > max_length

            result = {
                "status": "success",
                "url": url,
                "content": content,
                "status_code": resp.status_code,
                "content_length": len(resp.text),
                "truncated": truncated,
            }
    except Exception as e:
        result = {"status": "error", "error": str(e), "url": args["url"]}
    return _text_result(result)


# ===========================================================================
#  工具注册
# ===========================================================================

@tool(name="register_tool", description="""
将部署好的服务注册到工具库，注册后可被外部 Agent 通过 Router 调用。

Args:
    tool_id: 工具唯一标识（小写字母+下划线），如 "flask_demo_api"
    name: 工具显示名称
    description: 工具功能描述
    category: 分类标签，如 "nlp", "cv", "devtools"
    input_schema: JSON Schema 格式的输入参数定义
    output_schema: JSON Schema 格式的输出定义
    stream_support: 是否支持流式返回，默认 False
    runtime_type: "local", "docker" 或 "remote"（后端执行环境）
    container_id: Docker 容器 ID（docker 类型必填）
    host_dir: 宿主机工作目录（代码所在目录，local 类型必填）
    internal_port: 容器/进程内服务端口
    endpoint_path: HTTP API 路径，如 "/api/render"，默认 "/"
    http_method: HTTP 方法，默认 "POST"

Returns:
    注册结果
""", input_schema={
    "type": "object",
    "properties": {
        "tool_id": {"type": "string", "description": "工具 ID（小写字母+下划线）"},
        "name": {"type": "string", "description": "工具显示名称"},
        "description": {"type": "string", "description": "工具功能描述"},
        "category": {"type": "string", "description": "分类标签"},
        "input_schema": {"type": "object", "description": "输入参数 JSON Schema"},
        "output_schema": {"type": "object", "description": "输出 JSON Schema"},
        "stream_support": {"type": "boolean", "description": "是否支持流式"},
        "runtime_type": {"type": "string", "enum": ["local", "docker", "remote"], "description": "后端执行环境"},
        "container_id": {"type": "string", "description": "Docker 容器 ID（docker 类型必填）"},
        "host_dir": {"type": "string", "description": "宿主机工作目录（local 类型必填）"},
        "internal_port": {"type": "integer", "description": "容器/进程内服务端口"},
        "endpoint_path": {"type": "string", "description": "API 路径，默认 /"},
        "http_method": {"type": "string", "description": "HTTP 方法，默认 POST"},
        "tool_slug_ids": {"type": "array", "items": {"type": "string"}, "description": "关联的 KnowHub 工具 tool_slug 列表，如 [\"comfyui\"]，无则留空"},
    },
    "required": ["tool_id", "name", "description", "runtime_type", "internal_port"],
})
async def register_tool_fn(args):
    import re
    from tool_agent.router.status import ToolSource, SourceType, SourceStore

    tool_id = args["tool_id"]
    if not re.match(r'^[a-z][a-z0-9_]*$', tool_id):
        return _text_result({"status": "error", "error": f"Invalid tool_id '{tool_id}'. Must match [a-z][a-z0-9_]*."})

    try:
        runtime_type = args["runtime_type"]

        # 验证必填参数
        if runtime_type == "docker":
            cid = args.get("container_id", "")
            if not cid or not _docker_runner.get_container_info(cid):
                return _text_result({"status": "error", "error": f"Container not found: {cid}"})
        elif runtime_type == "local":
            host_dir_input = args.get("host_dir", "")
            if not host_dir_input:
                return _text_result({"status": "error", "error": "host_dir is required for local runtime"})

            # 转换为相对路径
            host_dir_path = Path(host_dir_input)
            try:
                base_dir = Path(settings.base_dir).resolve()
                host_dir_abs = host_dir_path.resolve()
                host_dir = str(host_dir_abs.relative_to(base_dir))
            except ValueError:
                # 如果不在项目根目录下，保持原样
                host_dir = host_dir_input

        # 1. 注册元数据到 Registry
        backend_runtime = {
            "docker": BackendRuntime.DOCKER,
            "local": BackendRuntime.LOCAL,
        }.get(runtime_type, BackendRuntime.REMOTE)

        tool_meta = ToolMeta(
            tool_id=tool_id,
            name=args["name"],
            description=args["description"],
            category=args.get("category", ""),
            input_schema=args.get("input_schema", {}),
            output_schema=args.get("output_schema", {}),
            stream_support=args.get("stream_support", False),
            status=ToolStatus.ACTIVE,
            backend_runtime=backend_runtime,
            group_ids=args.get("group_ids", []),
            tool_slug_ids=args.get("tool_slug_ids", []),
        )
        _registry.register(tool_meta)

        # 2. 注册来源到 Router（通过 SourceStore）
        source_store = SourceStore()

        if runtime_type == "docker":
            source = ToolSource(
                type=SourceType.DOCKER,
                container_id=args.get("container_id", ""),
                internal_port=args.get("internal_port", 0),
                endpoint_path=args.get("endpoint_path", "/"),
                http_method=args.get("http_method", "POST"),
            )
        else:  # local
            source = ToolSource(
                type=SourceType.LOCAL,
                host_dir=args.get("host_dir", ""),
                internal_port=args.get("internal_port", 0),
                endpoint_path=args.get("endpoint_path", "/"),
                http_method=args.get("http_method", "POST"),
            )

        source_store.add_source(tool_id, source)

        result = {
            "status": "success",
            "tool_id": tool_id,
            "backend_runtime": runtime_type,
            "message": f"Tool '{tool_meta.name}' registered successfully. Metadata saved to registry, source info saved to router.",
        }
    except Exception as e:
        result = {"status": "error", "error": str(e)}
    return _text_result(result)


# ===========================================================================
#  辅助函数
# ===========================================================================

def _text_result(data: dict) -> dict:
    """将 dict 包装为 claude_agent_sdk 的 tool 返回格式"""
    return {"content": [{"type": "text", "text": json.dumps(data, ensure_ascii=False, indent=2)}]}


def _validate_path(path_str: str) -> Path | None:
    """校验路径是否在允许的目录范围内，返回 resolved Path 或 None"""
    p = Path(path_str).resolve()
    allowed_roots = [
        Path(settings.data_dir).resolve(),
        Path(settings.tools_dir).resolve(),
    ]
    if any(p.is_relative_to(root) for root in allowed_roots):
        return p
    return None


# ===========================================================================
#  System Prompt
# ===========================================================================

SYSTEM_PROMPT = """你是 Coding Agent，负责根据任务书（task_spec）编写代码、配置环境、部署服务、注册工具。

## 你拥有的工具

### Docker 环境（适合：重依赖、GPU、非 Python、需要系统隔离的项目）
- create_docker_env: 创建 Docker 容器（支持端口映射、目录挂载）
- run_in_docker: 在容器中执行 Shell 命令（前台/后台）
- rebuild_docker_ports: 重建容器加端口映射（保留文件系统）
- destroy_docker_env: 销毁容器

### 本地 uv 环境（适合：纯 Python 轻量工具）
- create_uv_project: 创建 uv 项目（独立 venv）
- run_in_uv: 在 uv 环境中执行命令
- uv_add_dependency: 添加依赖

### 文件操作（宿主机）
- write_file: 写文件（代码、配置文件）— 配合 Docker volume 挂载实时同步
  - 支持 mode="append" 追加内容到文件末尾
  - **重要**：单次写入内容不要超过 500 行，大文件请分段写入（先 overwrite 写头部，再多次 append 追加）
- read_file: 读文件（README、文档、代码）
  - 支持 start_line 和 max_lines 参数分段读取大文件
- fetch_url: 获取网页内容（自动转纯文本，去除 HTML 标签）
  - 用于读取在线文档、GitHub README、API 文档等

### 注册
- register_tool: 将工具注册到 registry，使其可被外部调用

## 工作流程

### GitHub 项目接入：
1. 根据 task_spec 选择环境类型
   - Python 项目且依赖简单 → uv
   - 需要系统库/端口服务/GPU/非 Python → Docker + volume 挂载
2. 创建环境
3. 对于 Docker：创建宿主机 staging 目录，挂载到容器 /app
4. git clone 项目到环境中
5. read_file 读取 README/文档，理解功能和部署方式
6. 安装依赖
7. 如果项目本身提供 HTTP API → 直接部署启动
8. 如果项目是 CLI/库 → write_file 编写 HTTP API 包装器（FastAPI）
9. 启动服务，测试是否正常（curl 测试）
10. register_tool 注册到工具库
11. 返回结果报告：工具名、功能、访问方式

### 自主编写新工具：
1. 创建环境
2. write_file 编写工具代码
3. write_file 编写 HTTP API 接口
4. 安装依赖并测试
5. register_tool 注册
6. 返回结果报告

## 项目结构规范
工具项目根目录只放核心代码，测试和临时文件放在 tests/ 子目录：
```
{tool_id}/
├── pyproject.toml       # 项目配置
├── main.py              # API 入口（FastAPI + uvicorn）
├── {核心模块}.py         # 核心业务逻辑
└── tests/               # 测试与临时文件
    ├── test_xxx.py      # 测试脚本
    └── output/          # 测试产物（图片、文件等）
```
- 测试脚本必须放在 tests/ 目录下
- 测试中产生的文件（图片、音频、中间结果）保存到 tests/output/
- 运行测试时用 `python tests/test_xxx.py`

## 环境选择原则
- 纯 Python + 依赖少 → uv（零端口，冷启动快）
- 需要端口/服务常驻/GPU/系统依赖 → Docker
- Docker 推荐用 volume 挂载：宿主机写文件 → 容器内直接运行

## 重要规则
- 工具必须提供 HTTP API 才能注册（原生 API 或自己包装）
- 注册时必须提供 input_schema（JSON Schema 格式）
- tool_id 用小写字母和下划线，如 "image_compress_api"
- 必须测试通过后才能注册
- 遇到错误要尝试修复，不要轻易放弃
- 失败时给出清晰的错误原因和已尝试的方案
- 测试阶段只验证核心逻辑（单元测试），不要启动 HTTP 服务器。写好 FastAPI 代码即可，服务启动由 Router 层在调用时负责
- **文件写入规则**：单次 write_file 内容不超过 300 行。大文件必须分段写入：
  1. 第一次用 mode="overwrite" 写入文件头部（import、类定义等）
  2. 后续用 mode="append" 多次追加剩余内容
  3. 每次追加前可用 read_file 确认当前内容
"""


# ===========================================================================
#  CodingAgent 类
# ===========================================================================

ALL_TOOLS = [
    create_docker_env, run_in_docker, rebuild_docker_ports, destroy_docker_env,
    create_uv_project, run_in_uv, uv_add_dependency,
    write_file, read_file, fetch_url,
    register_tool_fn,
]

MCP_SERVER_NAME = "coding_tools"


class CodingAgent:
    """Coding Agent：接收任务书，调用 Claude SDK 自主完成工具编写和部署"""

    def __init__(self, bus: MessageBus | None = None, model: str = "claude-sonnet-4-5") -> None:
        self.bus = bus
        self.model = model

    async def start(self) -> None:
        """作为常驻服务运行，监听 MessageBus 消息"""
        if not self.bus:
            logger.warning("CodingAgent started without MessageBus, standalone mode.")
            return

        logger.info("CodingAgent started, waiting for tasks...")
        while True:
            msg = await self.bus.recv_as_tool()
            await self._handle_message(msg)

    async def _handle_message(self, msg: AgentMessage) -> None:
        if msg.type == MessageType.TOOL_REQUEST:
            task_spec = msg.payload.get("task_spec", "")
            if not task_spec:
                task_spec = msg.payload.get("description", "")
            logger.info(f"Received task: {task_spec[:100]}...")

            # 读取参考文件内容
            ref_contents: dict[str, str] = {}
            for ref_path in msg.payload.get("reference_files", []):
                p = Path(ref_path)
                if not p.is_absolute():
                    p = settings.base_dir / p
                if p.exists():
                    ref_contents[str(ref_path)] = p.read_text(encoding="utf-8")
                else:
                    logger.warning(f"Reference file not found: {p}")

            result = await self.execute(task_spec, ref_contents)
            await self.bus.send_to_router(AgentMessage(
                type=MessageType.TOOL_READY,
                payload={"result": result, **msg.payload},
            ))

        elif msg.type == MessageType.HEALTH_ALERT:
            tool_id = msg.payload.get("tool_id", "")
            error = msg.payload.get("error", "unknown")
            logger.warning(f"Health alert for tool: {tool_id}")
            result = await self.execute(
                f"工具 {tool_id} 出现故障，错误信息：{error}。请诊断并修复。"
            )
            await self.bus.send_to_router(AgentMessage(
                type=MessageType.TOOL_READY,
                payload={"result": result, "tool_id": tool_id},
            ))

    async def execute(self, task: str, reference_files: dict[str, str] | None = None) -> str:
        """执行任务：启动 Claude Agent 会话，自主完成编码部署，返回结果"""
        
        # 1. 动态生成本次任务的唯一标识和 Staging 路径
        task_id = f"task_{uuid.uuid4().hex[:8]}"
        # 建议路径：tools/staging/task_xxxx
        staging_dir = Path(settings.data_dir) / "staging" / task_id
        staging_dir.mkdir(parents=True, exist_ok=True)
        staging_path_abs = str(staging_dir.resolve())

        # 2. 准备工具服务器
        mcp_server = create_sdk_mcp_server(
            name=MCP_SERVER_NAME,
            version="1.0.0",
            tools=ALL_TOOLS,
        )

        # 3. 注入带路径感知的 System Prompt
        new_system_prompt = SYSTEM_PROMPT + f"""

## 调试与自修复指南
1. **当前工作目录**：你的本次任务 Staging 路径为 `{staging_path_abs}`。
2. **日志意识**：当你执行 `run_in_uv` 或 `run_in_docker` 失败（exit_code != 0）时，系统会自动将 stderr 返回给你。
3. **深度诊断**：如果 stderr 信息不足，请主动使用 `read_file` 读取该目录下的 `last_run.log` 文件。
4. **修复闭环**：根据日志中的 Traceback 报错信息，判断是缺少依赖（调用 uv_add_dependency）、代码语法错误（重新 write_file）还是环境配置问题。
5. **重要**：在 Docker 模式下，务必将此路径挂载到容器内部（如 /app）。
"""

        options = ClaudeAgentOptions(
            system_prompt=new_system_prompt,
            mcp_servers={MCP_SERVER_NAME: mcp_server},
            model=self.model,
            setting_sources=["project"],
            allowed_tools=[
                f"mcp__{MCP_SERVER_NAME}__{t.name}"
                for t in ALL_TOOLS
            ]
        )

        result_text = ""

        # 4. 启动客户端交互
        async with ClaudeSDKClient(options=options) as client:
            logger.info(f"[CodingAgent] Starting task: {task[:100]}... in {staging_path_abs}")
            
            # 构造包含路径的任务触发语
            ref_section = ""
            if reference_files:
                parts = ["\n## 参考代码文件\n以下是可供参考的现有代码，请理解其结构和风格后再编写：\n"]
                for path, content in reference_files.items():
                    parts.append(f"### {path}\n```\n{content}\n```\n")
                ref_section = "\n".join(parts)

            context_msg = f"""【任务指令】
工作区绝对路径: {staging_path_abs}
任务描述: {task}
{ref_section}
请开始执行。完成后请给出总结报告。"""
            
            await client.query(context_msg)

            async for message in client.receive_response():
                if isinstance(message, AssistantMessage):
                    for block in message.content:
                        if isinstance(block, TextBlock):
                            logger.info(f"[TEXT] {block.text}")
                            result_text = block.text
                        elif isinstance(block, ThinkingBlock):
                            logger.debug(f"[THINKING] {block.thinking[:200]}...")
                        elif isinstance(block, ToolUseBlock):
                            logger.info(f"[TOOL_USE] {block.name} | {json.dumps(block.input, ensure_ascii=False)[:200]}")

                elif isinstance(message, UserMessage):
                    if message.tool_use_result:
                        # 如果工具返回了 log_path，这里可以在日志中记录一下
                        logger.debug(f"[TOOL_RESULT] {json.dumps(message.tool_use_result, ensure_ascii=False)[:300]}")

                elif isinstance(message, ResultMessage):
                    logger.info(f"[DONE] duration={message.duration_ms}ms | cost=${message.total_cost_usd or 0}")
                    if message.result:
                        result_text = message.result

        # 任务结束，清理后台进程
        await _local_runner.cleanup_background()

        return result_text

# ===========================================================================
#  独立运行入口
# ===========================================================================

async def main():
    """交互式运行 CodingAgent"""
    logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(name)s] %(levelname)s: %(message)s")
    agent = CodingAgent()

    print("=" * 50)
    print("Coding Agent (claude_agent_sdk)")
    print("=" * 50)
    print("输入任务（GitHub 地址 / 工具需求描述）")
    print("-" * 50)

    task = input("请输入任务: ").strip()
    if not task:
        print("错误：输入不能为空")
        return

    print("-" * 50)
    print("正在处理...")
    print("-" * 50)

    result = await agent.execute(task)
    print("=" * 50)
    print("结果：")
    print(result)


if __name__ == "__main__":
    asyncio.run(main())
