From 01921148893e6156e6c55b72b567ba1c5e4003c0 Mon Sep 17 00:00:00 2001 From: cfdaily Date: Fri, 29 May 2026 13:32:08 +0800 Subject: [PATCH] auto-sync: 2026-05-29 13:32:08 --- src/daemon/ticker.py | 102 +++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 99 insertions(+), 3 deletions(-) diff --git a/src/daemon/ticker.py b/src/daemon/ticker.py index 2777c96..54e16fd 100644 --- a/src/daemon/ticker.py +++ b/src/daemon/ticker.py @@ -514,12 +514,26 @@ Project ID: {project_id} review_prompt: str, project_id: str, new_round: int = 0) -> bool: - """Spawn 庞统进行 review""" + """Spawn 庞统进行 review + + 流程: + 1. spawn 庞统 + 2. 成功后把 parent 设为 reviewing(防重复触发) + 3. 通过 on_complete 回调消费庞统结论 + """ try: agent_id = "pangtong-fujunshi" - # BUG-1 fix: 用传入的 new_round,不依赖 parent_task.round_count(已递增) session_id = f"review-{parent_task.id}-r{new_round}" + # 构造 on_complete 回调:解析庞统结论,更新 parent 状态 + async def _on_review_complete(aid: str, outcome: str): + try: + self._handle_review_conclusion( + parent_task.id, project_id, outcome, new_round) + except Exception: + logger.exception("Review conclusion handler failed for %s", + parent_task.id) + # 用 spawner.spawn_full_agent 直接 spawn result = await self.spawner.spawn_full_agent( agent_id=agent_id, @@ -528,13 +542,95 @@ Project ID: {project_id} task_id=parent_task.id, use_main_session=False, task_db_path=None, + on_complete=_on_review_complete, ) - return result is not None + if result is not None: + # spawn 成功 → parent 进入 reviewing(防下个 tick 重复触发) + self._set_parent_reviewing(parent_task.id, project_id) + return True + return False except Exception as e: logger.exception("Failed to spawn pangtong review for %s", parent_task.id) return False + def _set_parent_reviewing(self, parent_id: str, project_id: str): + """将 parent task 状态设为 reviewing(防重复触发)""" + try: + db_path = self._resolve_db_path(project_id) + conn = get_connection(db_path) + try: + conn.execute("BEGIN IMMEDIATE") + conn.execute( + "UPDATE tasks SET status='reviewing', updated_at=datetime('now') " + "WHERE id=? AND status IN ('done', 'failed')", + (parent_id,)) + conn.commit() + logger.info("Parent %s → reviewing (round review in progress)", + parent_id) + finally: + conn.close() + except Exception: + logger.exception("Failed to set parent %s to reviewing", parent_id) + + def _handle_review_conclusion(self, parent_id: str, project_id: str, + outcome: str, round_num: int): + """解析庞统 review 结论,更新 parent 状态 + + 庞统可能的结论: + - GOAL_ACHIEVED → parent → done(最终完成) + - 创建了新 sub task → parent → working(下一轮自动聚合) + - 其他 → parent → working(保守恢复,让下一轮自然流转) + """ + db_path = self._resolve_db_path(project_id) + conn = get_connection(db_path) + try: + # 解析庞统结论 + is_achieved = False + if outcome and "GOAL_ACHIEVED" in outcome.upper(): + is_achieved = True + + if is_achieved: + # Goal 达成 → parent 最终完成 + conn.execute("BEGIN IMMEDIATE") + conn.execute( + "UPDATE tasks SET status='done', updated_at=datetime('now') " + "WHERE id=? AND status='reviewing'", + (parent_id,)) + conn.commit() + logger.info("Parent %s review conclusion: GOAL_ACHIEVED → done", + parent_id) + else: + # 庞统可能创建了新 sub task 或需要继续 → 恢复 working + conn.execute("BEGIN IMMEDIATE") + conn.execute( + "UPDATE tasks SET status='working', updated_at=datetime('now') " + "WHERE id=? AND status='reviewing'", + (parent_id,)) + conn.commit() + logger.info( + "Parent %s review conclusion: continue → working (round %d)", + parent_id, round_num) + except Exception: + logger.exception("Failed to handle review conclusion for %s", parent_id) + # 安全恢复:reviewing → working + try: + conn.execute("BEGIN IMMEDIATE") + conn.execute( + "UPDATE tasks SET status='working', updated_at=datetime('now') " + "WHERE id=? AND status='reviewing'", + (parent_id,)) + conn.commit() + except Exception: + pass + finally: + conn.close() + + def _resolve_db_path(self, project_id: str) -> Path: + """解析项目 DB 路径""" + from src.utils import get_data_root + return get_data_root() / project_id / "blackboard.db" + # ------------------------------------------------------------------ # @mention 通知处理 (v2.9 #01) # ------------------------------------------------------------------