项目初始化
This commit is contained in:
106
bot/cursor_runner.py
Normal file
106
bot/cursor_runner.py
Normal file
@@ -0,0 +1,106 @@
|
||||
"""通过 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
|
||||
Reference in New Issue
Block a user