"""通过 Cursor SDK 执行用户任务。""" from __future__ import annotations import asyncio import logging import re from typing import Awaitable, Callable import env_config from bridge_manager import warm_cursor_bridge logger = logging.getLogger(__name__) _cursor_lock = asyncio.Lock() WECHAT_SYSTEM_PREFIX = """你是企业微信群里的 Skills 助手,正在回复群成员的消息。 要求: - 用简洁的中文回答(除非用户用其他语言提问) - 使用企业微信支持的 Markdown 子集(加粗、链接、列表;避免复杂表格) - 直接给出结论,不要冗长铺垫 - 若任务涉及 skills.sh,可说明安装命令 `npx skills add owner/repo/skill-name` - **不要**在回复里写 `[图片]` 占位符;企微无法通过 Markdown 显示图片 - 若用户要页面截图,请明确告知其发送:`截图` 或 `preview`(由 bot 自动发图) 用户任务: """ def _cursor_settings() -> dict[str, str | int]: timeout_raw = env_config.env("CURSOR_TIMEOUT", "600") or "600" return { "api_key": env_config.env("CURSOR_API_KEY"), "cwd": env_config.env("CURSOR_CWD", r"d:\LY\test\tech") or r"d:\LY\test\tech", "model": env_config.env("CURSOR_MODEL", "composer-2.5") or "composer-2.5", "timeout": int(timeout_raw), } def strip_mention(text: str) -> str: return re.sub(r"@\S+\s*", "", text).strip() def _build_prompt(task: str) -> str: return WECHAT_SYSTEM_PREFIX + task.strip() def execute_cursor_task_sync(task: str) -> str: from cursor_sdk import Agent, AgentOptions, CursorAgentError, LocalAgentOptions settings = _cursor_settings() api_key = settings["api_key"] if not api_key: raise RuntimeError( "未配置 CURSOR_API_KEY。请在 bot/.env 中设置," "密钥见 https://cursor.com/dashboard/integrations" ) warm_cursor_bridge() cwd = str(settings["cwd"]) prompt = _build_prompt(task) logger.info("Cursor 执行任务 cwd=%s model=%s", cwd, settings["model"]) try: result = Agent.prompt( prompt, AgentOptions( api_key=api_key, model=settings["model"], local=LocalAgentOptions(cwd=cwd), ), ) except CursorAgentError as exc: raise RuntimeError( f"Cursor 启动失败:{exc.message}" + ("(可重试)" if exc.is_retryable else "") ) from exc if result.status == "error": detail = result.result or "运行失败,无详细错误" raise RuntimeError(f"Cursor 执行失败:{detail}") text = (result.result or "").strip() if not text: return "Cursor 已完成任务,但没有返回文本内容。" return text async def run_cursor_task( task: str, on_progress: Callable[[str], Awaitable[None]] | None = None, ) -> str: timeout = int(_cursor_settings()["timeout"]) if on_progress: await on_progress("Cursor 正在执行任务,请稍候…") async with _cursor_lock: try: return await asyncio.wait_for( asyncio.to_thread(execute_cursor_task_sync, task), timeout=timeout, ) except asyncio.TimeoutError as exc: raise RuntimeError(f"Cursor 执行超时(>{timeout}s)") from exc