auto_execute_tasks.py 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773
  1. #!/usr/bin/env python3
  2. """
  3. 自动任务执行核心调度脚本
  4. 工作流程:
  5. 1. 从 PostgreSQL 数据库 task_list 表中读取 pending 任务
  6. 2. 生成 .cursor/task_execute_instructions.md 执行指令文件
  7. 3. 更新任务状态为 processing,并维护 .cursor/pending_tasks.json
  8. 4. 更新 .cursor/task_trigger.txt 触发器文件
  9. 5. (可选)向 Cursor Chat 发送执行提醒
  10. 6. Cursor 完成任务后,将 pending_tasks.json 中的状态改为 completed
  11. 7. 调度脚本将 completed 状态的任务同步回数据库
  12. 使用方式:
  13. # 执行一次任务检查
  14. python scripts/auto_execute_tasks.py --once
  15. # 循环模式(每 5 分钟检查)
  16. python scripts/auto_execute_tasks.py --interval 300
  17. # 刷新触发器文件(用于现有 processing 任务)
  18. python scripts/auto_execute_tasks.py --refresh-trigger
  19. # 立即发送 Chat 消息触发 Cursor 执行
  20. python scripts/auto_execute_tasks.py --send-chat-now
  21. # 启动 Chat 自动触发循环(每 60 秒检查,有任务时自动发送消息)
  22. python scripts/auto_execute_tasks.py --chat-loop --chat-interval 60
  23. """
  24. from __future__ import annotations
  25. import json
  26. import time
  27. import argparse
  28. import logging
  29. from pathlib import Path
  30. from datetime import datetime
  31. from typing import Any, Dict, List, Optional, Tuple
  32. # ============================================================================
  33. # 日志配置
  34. # ============================================================================
  35. logging.basicConfig(
  36. level=logging.INFO,
  37. format="%(asctime)s - %(levelname)s - %(message)s",
  38. )
  39. logger = logging.getLogger("AutoExecuteTasks")
  40. # ============================================================================
  41. # Windows GUI 自动化依赖(可选)
  42. # ============================================================================
  43. HAS_CURSOR_GUI = False
  44. HAS_PYPERCLIP = False
  45. try:
  46. import win32gui
  47. import win32con
  48. import pyautogui
  49. pyautogui.FAILSAFE = True
  50. pyautogui.PAUSE = 0.5
  51. HAS_CURSOR_GUI = True
  52. try:
  53. import pyperclip
  54. HAS_PYPERCLIP = True
  55. except ImportError:
  56. pass
  57. except ImportError:
  58. logger.info(
  59. "未安装 Windows GUI 自动化依赖(pywin32/pyautogui),"
  60. "将禁用自动 Cursor Chat 功能。"
  61. )
  62. # ============================================================================
  63. # 全局配置
  64. # ============================================================================
  65. WORKSPACE_ROOT = Path(__file__).parent.parent
  66. CURSOR_DIR = WORKSPACE_ROOT / ".cursor"
  67. PENDING_TASKS_FILE = CURSOR_DIR / "pending_tasks.json"
  68. INSTRUCTIONS_FILE = CURSOR_DIR / "task_execute_instructions.md"
  69. TRIGGER_FILE = CURSOR_DIR / "task_trigger.txt"
  70. # 命令行参数控制的全局变量
  71. ENABLE_CHAT: bool = False
  72. CHAT_MESSAGE: str = "请阅读 .cursor/task_execute_instructions.md 并执行任务。"
  73. CHAT_INPUT_POS: Optional[Tuple[int, int]] = None
  74. # ============================================================================
  75. # 数据库操作
  76. # ============================================================================
  77. def get_db_connection():
  78. """获取数据库连接(使用 production 环境配置)"""
  79. try:
  80. import psycopg2
  81. import sys
  82. sys.path.insert(0, str(WORKSPACE_ROOT))
  83. from app.config.config import config
  84. # 强制使用 production 环境的数据库配置
  85. app_config = config['production']
  86. db_uri = app_config.SQLALCHEMY_DATABASE_URI
  87. return psycopg2.connect(db_uri)
  88. except ImportError as e:
  89. logger.error(f"导入依赖失败: {e}")
  90. return None
  91. except Exception as e:
  92. logger.error(f"连接数据库失败: {e}")
  93. return None
  94. def get_pending_tasks() -> List[Dict[str, Any]]:
  95. """从数据库获取所有 pending 任务"""
  96. try:
  97. from psycopg2.extras import RealDictCursor
  98. conn = get_db_connection()
  99. if not conn:
  100. return []
  101. cursor = conn.cursor(cursor_factory=RealDictCursor)
  102. cursor.execute("""
  103. SELECT task_id, task_name, task_description, status,
  104. code_name, code_path, create_time, create_by
  105. FROM task_list
  106. WHERE status = 'pending'
  107. ORDER BY create_time ASC
  108. """)
  109. tasks = cursor.fetchall()
  110. cursor.close()
  111. conn.close()
  112. return [dict(task) for task in tasks]
  113. except Exception as e:
  114. logger.error(f"获取 pending 任务失败: {e}")
  115. return []
  116. def update_task_status(
  117. task_id: int,
  118. status: str,
  119. code_name: Optional[str] = None,
  120. code_path: Optional[str] = None,
  121. ) -> bool:
  122. """更新任务状态"""
  123. try:
  124. conn = get_db_connection()
  125. if not conn:
  126. return False
  127. cursor = conn.cursor()
  128. if code_name and code_path:
  129. cursor.execute("""
  130. UPDATE task_list
  131. SET status = %s, code_name = %s, code_path = %s,
  132. update_time = CURRENT_TIMESTAMP
  133. WHERE task_id = %s
  134. """, (status, code_name, code_path, task_id))
  135. else:
  136. cursor.execute("""
  137. UPDATE task_list
  138. SET status = %s, update_time = CURRENT_TIMESTAMP
  139. WHERE task_id = %s
  140. """, (status, task_id))
  141. conn.commit()
  142. updated = cursor.rowcount > 0
  143. cursor.close()
  144. conn.close()
  145. if updated:
  146. logger.info(f"✅ 任务 {task_id} 状态已更新为: {status}")
  147. return updated
  148. except Exception as e:
  149. logger.error(f"更新任务状态失败: {e}")
  150. return False
  151. # ============================================================================
  152. # 触发器文件管理
  153. # ============================================================================
  154. def update_trigger_file(
  155. task_count: int = 0,
  156. status: str = "有待执行任务",
  157. task_ids: Optional[List[int]] = None,
  158. ) -> None:
  159. """
  160. 更新触发器文件 .cursor/task_trigger.txt
  161. Cursor Rules 会检测此文件的更新时间,如果在 5 分钟内更新,
  162. 则触发自动任务执行。
  163. """
  164. CURSOR_DIR.mkdir(parents=True, exist_ok=True)
  165. gen_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
  166. task_ids_str = ", ".join(str(tid) for tid in (task_ids or []))
  167. content = f"""CURSOR_AUTO_EXECUTE_TASK_TRIGGER
  168. 生成时间: {gen_time}
  169. 状态: {status}
  170. 待处理任务数: {task_count}
  171. 任务ID列表: [{task_ids_str}]
  172. 此文件用于触发Cursor自动执行任务。
  173. 当此文件更新时间在5分钟内时,Cursor应该检查 .cursor/task_execute_instructions.md 并执行其中的任务。
  174. 【重要】Cursor AI 请立即执行以下操作:
  175. 1. 读取 .cursor/task_execute_instructions.md 文件
  176. 2. 按照文件中的任务描述,依次完成每个任务
  177. 3. 完成后更新 .cursor/pending_tasks.json 中对应任务的状态为 completed
  178. """
  179. with TRIGGER_FILE.open("w", encoding="utf-8") as f:
  180. f.write(content)
  181. logger.info(f"✅ 触发器文件已更新: {TRIGGER_FILE}")
  182. def get_processing_task_ids() -> List[int]:
  183. """从 pending_tasks.json 获取所有 processing 状态的任务 ID"""
  184. if not PENDING_TASKS_FILE.exists():
  185. return []
  186. try:
  187. with PENDING_TASKS_FILE.open("r", encoding="utf-8") as f:
  188. tasks = json.load(f)
  189. return [
  190. t.get("task_id") for t in tasks
  191. if t.get("status") == "processing" and t.get("task_id")
  192. ]
  193. except Exception:
  194. return []
  195. # ============================================================================
  196. # 任务文件生成
  197. # ============================================================================
  198. def write_pending_tasks_json(tasks: List[Dict[str, Any]]) -> None:
  199. """将任务列表写入 .cursor/pending_tasks.json"""
  200. CURSOR_DIR.mkdir(parents=True, exist_ok=True)
  201. # 读取现有任务
  202. existing_tasks = []
  203. if PENDING_TASKS_FILE.exists():
  204. try:
  205. with PENDING_TASKS_FILE.open("r", encoding="utf-8") as f:
  206. existing_tasks = json.load(f)
  207. except Exception:
  208. existing_tasks = []
  209. existing_ids = {t["task_id"] for t in existing_tasks if "task_id" in t}
  210. # 添加新任务
  211. for task in tasks:
  212. if task["task_id"] not in existing_ids:
  213. task_info = {
  214. "task_id": task["task_id"],
  215. "task_name": task["task_name"],
  216. "code_path": task.get("code_path", ""),
  217. "code_name": task.get("code_name", ""),
  218. "status": "processing",
  219. "notified_at": datetime.now().isoformat(),
  220. "code_file": task.get("code_file", ""),
  221. }
  222. existing_tasks.append(task_info)
  223. with PENDING_TASKS_FILE.open("w", encoding="utf-8") as f:
  224. json.dump(existing_tasks, f, indent=2, ensure_ascii=False)
  225. logger.info(f"✅ pending_tasks.json 已更新,任务数: {len(existing_tasks)}")
  226. def create_execute_instructions(tasks: List[Dict[str, Any]]) -> None:
  227. """生成任务执行指令文件 .cursor/task_execute_instructions.md"""
  228. CURSOR_DIR.mkdir(parents=True, exist_ok=True)
  229. with INSTRUCTIONS_FILE.open("w", encoding="utf-8") as f:
  230. f.write("# 🤖 Cursor 自动任务执行指令\n\n")
  231. f.write("**⚠️ 重要:请立即执行以下任务!**\n\n")
  232. gen_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
  233. f.write(f"**生成时间**: {gen_time}\n\n")
  234. f.write(f"**待执行任务数量**: {len(tasks)}\n\n")
  235. f.write("## 📋 任务完成后的操作\n\n")
  236. f.write("完成每个任务后,请更新 `.cursor/pending_tasks.json` 中")
  237. f.write("对应任务的 `status` 为 `completed`,\n")
  238. f.write("并填写 `code_name`(代码文件名)和 `code_path`(代码路径)。\n\n")
  239. f.write("调度脚本会自动将完成的任务同步到数据库。\n\n")
  240. f.write("---\n\n")
  241. for idx, task in enumerate(tasks, 1):
  242. task_id = task['task_id']
  243. task_name = task['task_name']
  244. task_desc = task['task_description']
  245. create_time = task.get("create_time", "")
  246. if hasattr(create_time, "strftime"):
  247. create_time = create_time.strftime("%Y-%m-%d %H:%M:%S")
  248. f.write(f"## 🔴 任务 {idx}: {task_name}\n\n")
  249. f.write(f"- **任务ID**: `{task_id}`\n")
  250. f.write(f"- **创建时间**: {create_time}\n")
  251. f.write(f"- **创建者**: {task.get('create_by', 'unknown')}\n\n")
  252. f.write(f"### 📝 任务描述\n\n{task_desc}\n\n")
  253. f.write("---\n\n")
  254. logger.info(f"✅ 执行指令文件已创建: {INSTRUCTIONS_FILE}")
  255. # ============================================================================
  256. # 状态同步
  257. # ============================================================================
  258. def sync_completed_tasks_to_db() -> int:
  259. """将 pending_tasks.json 中 completed 的任务同步到数据库"""
  260. if not PENDING_TASKS_FILE.exists():
  261. return 0
  262. try:
  263. with PENDING_TASKS_FILE.open("r", encoding="utf-8") as f:
  264. tasks = json.load(f)
  265. except Exception as e:
  266. logger.error(f"读取 pending_tasks.json 失败: {e}")
  267. return 0
  268. if not isinstance(tasks, list):
  269. return 0
  270. updated = 0
  271. remaining_tasks = []
  272. for t in tasks:
  273. if t.get("status") == "completed":
  274. task_id = t.get("task_id")
  275. if not task_id:
  276. continue
  277. code_name = t.get("code_name")
  278. code_path = t.get("code_path")
  279. if update_task_status(task_id, "completed", code_name, code_path):
  280. updated += 1
  281. logger.info(f"已同步任务 {task_id} 为 completed")
  282. else:
  283. remaining_tasks.append(t)
  284. else:
  285. remaining_tasks.append(t)
  286. if updated > 0:
  287. with PENDING_TASKS_FILE.open("w", encoding="utf-8") as f:
  288. json.dump(remaining_tasks, f, indent=2, ensure_ascii=False)
  289. logger.info(f"本次共同步 {updated} 个 completed 任务到数据库")
  290. return updated
  291. # ============================================================================
  292. # Cursor Chat 自动化
  293. # ============================================================================
  294. def find_cursor_window() -> Optional[int]:
  295. """查找 Cursor 主窗口句柄"""
  296. if not HAS_CURSOR_GUI:
  297. return None
  298. cursor_windows: List[Dict[str, Any]] = []
  299. def enum_windows_callback(hwnd, _extra):
  300. if win32gui.IsWindowVisible(hwnd):
  301. title = win32gui.GetWindowText(hwnd) or ""
  302. class_name = win32gui.GetClassName(hwnd) or ""
  303. is_cursor = "cursor" in title.lower()
  304. if class_name and "chrome_widgetwin" in class_name.lower():
  305. is_cursor = True
  306. if is_cursor:
  307. left, top, right, bottom = win32gui.GetWindowRect(hwnd)
  308. area = (right - left) * (bottom - top)
  309. cursor_windows.append({"hwnd": hwnd, "area": area})
  310. return True
  311. win32gui.EnumWindows(enum_windows_callback, None)
  312. if not cursor_windows:
  313. logger.warning("未找到 Cursor 窗口")
  314. return None
  315. cursor_windows.sort(key=lambda x: x["area"], reverse=True)
  316. return cursor_windows[0]["hwnd"]
  317. def send_chat_message(
  318. message: str, input_pos: Optional[Tuple[int, int]]
  319. ) -> bool:
  320. """在 Cursor Chat 中发送消息"""
  321. if not HAS_CURSOR_GUI:
  322. logger.warning("当前环境不支持 Cursor GUI 自动化")
  323. return False
  324. hwnd = find_cursor_window()
  325. if not hwnd:
  326. return False
  327. try:
  328. win32gui.ShowWindow(hwnd, win32con.SW_RESTORE)
  329. time.sleep(0.3)
  330. win32gui.SetForegroundWindow(hwnd)
  331. time.sleep(0.5)
  332. except Exception as e:
  333. logger.error(f"激活 Cursor 窗口失败: {e}")
  334. return False
  335. # 点击输入框或使用快捷键
  336. if input_pos:
  337. x, y = input_pos
  338. pyautogui.click(x, y)
  339. time.sleep(0.4)
  340. else:
  341. pyautogui.hotkey("ctrl", "l")
  342. time.sleep(1.0)
  343. pyautogui.hotkey("ctrl", "a")
  344. time.sleep(0.2)
  345. # 输入消息
  346. if HAS_PYPERCLIP:
  347. try:
  348. pyperclip.copy(message)
  349. pyautogui.hotkey("ctrl", "v")
  350. time.sleep(0.5)
  351. except Exception:
  352. pyautogui.write(message, interval=0.03)
  353. else:
  354. pyautogui.write(message, interval=0.03)
  355. time.sleep(0.3)
  356. pyautogui.press("enter")
  357. logger.info("✅ 消息已发送到 Cursor Chat")
  358. return True
  359. def send_chat_for_tasks(force: bool = False) -> bool:
  360. """
  361. 向 Cursor Chat 发送任务提醒
  362. Args:
  363. force: 强制发送,忽略 ENABLE_CHAT 设置
  364. Returns:
  365. 是否成功发送
  366. """
  367. if not force and not ENABLE_CHAT:
  368. return False
  369. if not HAS_CURSOR_GUI:
  370. logger.warning("未安装 GUI 自动化依赖,无法发送 Chat 消息")
  371. return False
  372. processing_ids = get_processing_task_ids()
  373. if not processing_ids:
  374. logger.info("没有 processing 任务,跳过发送")
  375. return False
  376. logger.info(f"📤 发送任务提醒到 Cursor Chat({len(processing_ids)} 个任务)...")
  377. return send_chat_message(CHAT_MESSAGE, CHAT_INPUT_POS)
  378. def chat_trigger_loop(interval: int = 60) -> None:
  379. """
  380. 循环模式:定期检查 processing 任务并发送 Chat 消息
  381. Args:
  382. interval: 检查间隔(秒),默认 60 秒
  383. """
  384. logger.info("=" * 60)
  385. logger.info("🚀 Cursor Chat 自动触发服务已启动")
  386. logger.info(f"⏰ 检查间隔: {interval} 秒")
  387. logger.info(f"📝 发送消息: {CHAT_MESSAGE}")
  388. logger.info("按 Ctrl+C 停止服务")
  389. logger.info("=" * 60)
  390. last_sent_time = 0
  391. min_send_interval = 120 # 最小发送间隔(秒),避免频繁打扰
  392. try:
  393. while True:
  394. try:
  395. # 1. 先同步已完成任务
  396. sync_completed_tasks_to_db()
  397. # 2. 检查是否有 processing 任务
  398. processing_ids = get_processing_task_ids()
  399. if processing_ids:
  400. current_time = time.time()
  401. time_since_last = current_time - last_sent_time
  402. if time_since_last >= min_send_interval:
  403. logger.info(f"📋 发现 {len(processing_ids)} 个待处理任务")
  404. # 更新触发器文件
  405. update_trigger_file(
  406. task_count=len(processing_ids),
  407. status="有待执行任务",
  408. task_ids=processing_ids,
  409. )
  410. # 发送 Chat 消息
  411. if send_chat_for_tasks(force=True):
  412. last_sent_time = current_time
  413. logger.info("✅ 已发送执行提醒")
  414. else:
  415. logger.warning("⚠️ 发送失败,将在下次重试")
  416. else:
  417. remaining = int(min_send_interval - time_since_last)
  418. logger.info(
  419. f"⏳ 距离上次发送不足 {min_send_interval}s,"
  420. f"还需等待 {remaining}s"
  421. )
  422. else:
  423. logger.info("✅ 没有待处理任务")
  424. logger.info(f"⏳ {interval} 秒后再次检查...")
  425. time.sleep(interval)
  426. except KeyboardInterrupt:
  427. raise
  428. except Exception as e:
  429. logger.error(f"❌ 执行出错: {e}")
  430. time.sleep(interval)
  431. except KeyboardInterrupt:
  432. logger.info("\n⛔ 服务已停止")
  433. # ============================================================================
  434. # 主执行流程
  435. # ============================================================================
  436. def auto_execute_tasks_once() -> int:
  437. """执行一次任务检查和处理"""
  438. # 1. 先同步已完成任务到数据库
  439. sync_completed_tasks_to_db()
  440. # 2. 获取 pending 任务
  441. logger.info("🔍 检查 pending 任务...")
  442. tasks = get_pending_tasks()
  443. # 3. 检查是否有现有的 processing 任务
  444. existing_processing_ids = get_processing_task_ids()
  445. if not tasks and not existing_processing_ids:
  446. logger.info("✅ 没有 pending 或 processing 任务")
  447. # 更新触发器文件为"已完成"状态
  448. update_trigger_file(0, "所有任务已完成", [])
  449. return 0
  450. if tasks:
  451. logger.info(f"📋 找到 {len(tasks)} 个 pending 任务")
  452. # 4. 更新任务状态为 processing
  453. for task in tasks:
  454. update_task_status(task["task_id"], "processing")
  455. # 5. 写入 pending_tasks.json
  456. write_pending_tasks_json(tasks)
  457. # 6. 生成执行指令文件
  458. create_execute_instructions(tasks)
  459. # 7. 更新触发器文件(关键:触发 Cursor 自动执行)
  460. all_processing_ids = get_processing_task_ids()
  461. if all_processing_ids:
  462. update_trigger_file(
  463. task_count=len(all_processing_ids),
  464. status="有待执行任务",
  465. task_ids=all_processing_ids,
  466. )
  467. logger.info(f"🔔 已更新触发器,等待 Cursor 执行 {len(all_processing_ids)} 个任务")
  468. return len(tasks)
  469. def auto_execute_tasks_loop(interval: int = 300) -> None:
  470. """循环执行任务检查"""
  471. logger.info("=" * 60)
  472. logger.info("🚀 自动任务执行服务已启动")
  473. logger.info(f"⏰ 检查间隔: {interval} 秒")
  474. logger.info(f"💬 自动 Chat: {'已启用' if ENABLE_CHAT else '未启用'}")
  475. logger.info("按 Ctrl+C 停止服务")
  476. logger.info("=" * 60)
  477. try:
  478. while True:
  479. try:
  480. count = auto_execute_tasks_once()
  481. if count > 0:
  482. send_chat_for_tasks()
  483. logger.info(f"✅ 已处理 {count} 个任务")
  484. logger.info(f"⏳ {interval} 秒后再次检查...")
  485. time.sleep(interval)
  486. except KeyboardInterrupt:
  487. raise
  488. except Exception as e:
  489. logger.error(f"❌ 执行出错: {e}")
  490. time.sleep(interval)
  491. except KeyboardInterrupt:
  492. logger.info("\n⛔ 服务已停止")
  493. def refresh_trigger_only() -> None:
  494. """仅刷新触发器文件(用于现有 processing 任务)"""
  495. processing_ids = get_processing_task_ids()
  496. if not processing_ids:
  497. logger.info("没有 processing 状态的任务")
  498. update_trigger_file(0, "所有任务已完成", [])
  499. return
  500. logger.info(f"📋 发现 {len(processing_ids)} 个 processing 任务")
  501. # 重新生成指令文件(从 pending_tasks.json 读取)
  502. if PENDING_TASKS_FILE.exists():
  503. try:
  504. with PENDING_TASKS_FILE.open("r", encoding="utf-8") as f:
  505. all_tasks = json.load(f)
  506. processing_tasks = [
  507. t for t in all_tasks
  508. if t.get("status") == "processing"
  509. ]
  510. if processing_tasks:
  511. # 重新生成执行指令文件
  512. create_execute_instructions_from_json(processing_tasks)
  513. except Exception as e:
  514. logger.error(f"读取任务文件失败: {e}")
  515. # 更新触发器文件
  516. update_trigger_file(
  517. task_count=len(processing_ids),
  518. status="有待执行任务",
  519. task_ids=processing_ids,
  520. )
  521. logger.info("✅ 触发器已刷新,等待 Cursor 执行任务")
  522. def create_execute_instructions_from_json(tasks: List[Dict[str, Any]]) -> None:
  523. """从 pending_tasks.json 格式的任务列表生成执行指令文件"""
  524. CURSOR_DIR.mkdir(parents=True, exist_ok=True)
  525. with INSTRUCTIONS_FILE.open("w", encoding="utf-8") as f:
  526. f.write("# 🤖 Cursor 自动任务执行指令\n\n")
  527. f.write("**⚠️ 重要:请立即执行以下任务!**\n\n")
  528. gen_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
  529. f.write(f"**生成时间**: {gen_time}\n\n")
  530. f.write(f"**待执行任务数量**: {len(tasks)}\n\n")
  531. f.write("## 📋 任务完成后的操作\n\n")
  532. f.write("完成每个任务后,请更新 `.cursor/pending_tasks.json` 中")
  533. f.write("对应任务的 `status` 为 `completed`,\n")
  534. f.write("并填写 `code_name`(代码文件名)和 `code_path`(代码路径)。\n\n")
  535. f.write("调度脚本会自动将完成的任务同步到数据库。\n\n")
  536. f.write("---\n\n")
  537. for idx, task in enumerate(tasks, 1):
  538. task_id = task.get('task_id', 'N/A')
  539. task_name = task.get('task_name', '未命名任务')
  540. task_desc = task.get('task_description', '无描述')
  541. notified_at = task.get('notified_at', '')
  542. f.write(f"## 🔴 任务 {idx}: {task_name}\n\n")
  543. f.write(f"- **任务ID**: `{task_id}`\n")
  544. f.write(f"- **通知时间**: {notified_at}\n")
  545. f.write(f"- **代码路径**: {task.get('code_path', 'N/A')}\n\n")
  546. f.write(f"### 📝 任务描述\n\n{task_desc}\n\n")
  547. f.write("---\n\n")
  548. logger.info(f"✅ 执行指令文件已更新: {INSTRUCTIONS_FILE}")
  549. def main() -> None:
  550. """主函数"""
  551. parser = argparse.ArgumentParser(
  552. description="自动任务执行调度脚本",
  553. formatter_class=argparse.RawDescriptionHelpFormatter,
  554. )
  555. parser.add_argument(
  556. "--once", action="store_true", help="只执行一次"
  557. )
  558. parser.add_argument(
  559. "--interval", type=int, default=300, help="检查间隔(秒)"
  560. )
  561. parser.add_argument(
  562. "--enable-chat", action="store_true", help="启用自动 Cursor Chat"
  563. )
  564. parser.add_argument(
  565. "--chat-input-pos", type=str, help='Chat 输入框位置 "x,y"'
  566. )
  567. parser.add_argument(
  568. "--chat-message", type=str,
  569. default="请阅读 .cursor/task_execute_instructions.md 并执行任务。",
  570. help="发送到 Chat 的消息"
  571. )
  572. parser.add_argument(
  573. "--refresh-trigger", action="store_true",
  574. help="仅刷新触发器文件(用于现有 processing 任务)"
  575. )
  576. parser.add_argument(
  577. "--chat-loop", action="store_true",
  578. help="启动 Chat 自动触发循环(定期发送执行消息)"
  579. )
  580. parser.add_argument(
  581. "--chat-interval", type=int, default=60,
  582. help="Chat 触发循环的检查间隔(秒),默认 60"
  583. )
  584. parser.add_argument(
  585. "--send-chat-now", action="store_true",
  586. help="立即发送一次 Chat 消息(用于手动触发)"
  587. )
  588. args = parser.parse_args()
  589. global ENABLE_CHAT, CHAT_INPUT_POS, CHAT_MESSAGE
  590. ENABLE_CHAT = bool(args.enable_chat)
  591. CHAT_MESSAGE = args.chat_message
  592. if args.chat_input_pos:
  593. try:
  594. x, y = args.chat_input_pos.split(",")
  595. CHAT_INPUT_POS = (int(x.strip()), int(y.strip()))
  596. except Exception:
  597. pass
  598. # 仅刷新触发器
  599. if args.refresh_trigger:
  600. refresh_trigger_only()
  601. return
  602. # 立即发送一次 Chat 消息
  603. if args.send_chat_now:
  604. if send_chat_for_tasks(force=True):
  605. logger.info("✅ 消息已发送")
  606. else:
  607. logger.error("❌ 发送失败")
  608. return
  609. # Chat 自动触发循环模式
  610. if args.chat_loop:
  611. chat_trigger_loop(interval=args.chat_interval)
  612. return
  613. if args.once:
  614. count = auto_execute_tasks_once()
  615. if count > 0:
  616. send_chat_for_tasks()
  617. logger.info(f"✅ 完成!处理了 {count} 个任务")
  618. else:
  619. auto_execute_tasks_loop(interval=args.interval)
  620. if __name__ == "__main__":
  621. main()