fix: v3.0→HEAD review 修复 — handler 注册 + review verdict + skill 全文注入
基于庞统+司马懿背靠背 review,修复 6 个问题:
P0 致命:
- A1: _legacy_on_complete 补回 review verdict 处理(approved→done,非 approved→@mention assignee)
- A2: 添加 TaskTypeRegistry.register() 启动初始化(注册 Task/Mail/Toolchain handler)
P1 中等:
- B11-1: RoleSkillSection 从索引提示改为全文注入(对齐设计 §2.3 + BootstrapBuilder 行为)
- A8: retry prompt is_mail 硬编码改走 TaskTypeRegistry handler 判断
P2 低:
- _mail_* 4 个方法添加 DEPRECATED 注释
- ticker.py handler check_completion 代码块缩进对齐(28→24 空格)
测试:394 passed, 0 failed
Review reports: docs/design/review-v3-vs-head-{pangtong,simayi}.md
This commit is contained in:
@@ -252,9 +252,47 @@ class Dispatcher:
|
||||
if outcome in ROLLBACK_CURRENT_AGENT_OUTCOMES and _task_db:
|
||||
_dispatcher._rollback_current_agent(
|
||||
_task_db, _task_id, aid)
|
||||
if not _is_review:
|
||||
_dispatcher._task_auto_complete(
|
||||
_task_id, _task_db)
|
||||
if _is_review:
|
||||
if _task_db and outcome in ("completed", "session_revived"):
|
||||
from src.blackboard.blackboard import Blackboard
|
||||
from src.daemon.db import get_connection
|
||||
rconn = get_connection(_task_db)
|
||||
try:
|
||||
review_row = rconn.execute(
|
||||
"SELECT verdict, reviewer, comment FROM reviews "
|
||||
"WHERE task_id=? ORDER BY created_at DESC LIMIT 1",
|
||||
(_task_id,)).fetchone()
|
||||
finally:
|
||||
rconn.close()
|
||||
|
||||
if review_row and review_row["verdict"] == "approved":
|
||||
_dispatcher._mark_task_status(
|
||||
_task_db, _task_id, "done")
|
||||
logger.info(
|
||||
"Legacy %s: review approved, marked done", _task_id)
|
||||
else:
|
||||
verdict_str = review_row["verdict"] if review_row else "未知"
|
||||
tconn = get_connection(_task_db)
|
||||
try:
|
||||
t_row = tconn.execute(
|
||||
"SELECT assignee FROM tasks WHERE id=?",
|
||||
(_task_id,)).fetchone()
|
||||
finally:
|
||||
tconn.close()
|
||||
if t_row and t_row["assignee"]:
|
||||
bb = Blackboard(str(_task_db))
|
||||
bb.add_comment(
|
||||
_task_id, "daemon",
|
||||
f"@{t_row['assignee']} review 未通过 "
|
||||
f"(verdict={verdict_str}): "
|
||||
f"{review_row['comment'] if review_row else ''}",
|
||||
comment_type="review")
|
||||
logger.info(
|
||||
"Legacy %s: review not approved (%s), "
|
||||
"@mentioned assignee",
|
||||
_task_id, verdict_str)
|
||||
else:
|
||||
_dispatcher._task_auto_complete(_task_id, _task_db)
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
"Legacy %s: on_complete error: %s", _task_id, e)
|
||||
@@ -625,6 +663,7 @@ class Dispatcher:
|
||||
|
||||
# ── Mail 信封/载荷分离辅助方法 ──
|
||||
|
||||
# DEPRECATED: Step 5 handler 架构已替代此方法,保留仅供平滑过渡,确认稳定后删除。
|
||||
def _mail_auto_working(self, task_id: str, db_path: Path) -> bool:
|
||||
"""Mail 任务:系统自动标 working(spawn 前)
|
||||
|
||||
@@ -662,6 +701,7 @@ class Dispatcher:
|
||||
logger.error("Mail %s: failed to mark working: %s", task_id, e)
|
||||
return False
|
||||
|
||||
# DEPRECATED: Step 5 handler 架构已替代此方法,保留仅供平滑过渡,确认稳定后删除。
|
||||
def _mail_revert_to_pending(self, task_id: str, db_path: Path) -> None:
|
||||
"""Mail spawn 失败时回退 working → pending,避免永久死锁"""
|
||||
try:
|
||||
@@ -691,6 +731,7 @@ class Dispatcher:
|
||||
task_id,
|
||||
e)
|
||||
|
||||
# DEPRECATED: Step 5 handler 架构已替代此方法,保留仅供平滑过渡,确认稳定后删除。
|
||||
def _mail_auto_complete(self, task_id: str, agent_id: str,
|
||||
db_path: Path, must_haves: str, outcome=None) -> None:
|
||||
"""Mail 任务:on_complete 后自动标 done/failed(含幻觉门控)"""
|
||||
@@ -798,6 +839,7 @@ class Dispatcher:
|
||||
except Exception as e:
|
||||
logger.error("Mail %s: auto-complete error: %s", task_id, e)
|
||||
|
||||
# DEPRECATED: Step 5 handler 架构已替代此方法,保留仅供平滑过渡,确认稳定后删除。
|
||||
def _mail_check_reply(self, original_task_id: str, db_path: Path) -> bool:
|
||||
"""幻觉门控:检查是否有回复邮件(in_reply_to = original_task_id)"""
|
||||
try:
|
||||
|
||||
@@ -1125,9 +1125,10 @@ curl -X POST http://{api_host}:{api_port}/api/projects/{project_id}/tasks/{task_
|
||||
# 构建续杯 message(Mail 用专用模板,Task 用标准模板)
|
||||
task_info = self._get_task_info(db_path, task_id) or {}
|
||||
project_id = task_info.get("project_id", "")
|
||||
is_mail = project_id == "_mail"
|
||||
handler = TaskTypeRegistry.get_by_project(project_id)
|
||||
is_handler = handler is not None
|
||||
|
||||
if is_mail:
|
||||
if is_handler:
|
||||
must_haves = task_info.get("must_haves", "{}")
|
||||
try:
|
||||
meta = json.loads(must_haves) if must_haves else {}
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import os
|
||||
from pathlib import Path
|
||||
from typing import Dict, List, Optional
|
||||
|
||||
@@ -79,7 +80,7 @@ class PriorOutputsSection:
|
||||
|
||||
|
||||
class RoleSkillSection:
|
||||
"""段 3:角色 Skill 索引+引导语(D8 决策:不注全文)。"""
|
||||
"""段 3:角色 Skill 全文注入(对齐设计 §2.3 + BootstrapBuilder 行为)。"""
|
||||
|
||||
name: str = "role_skill"
|
||||
priority: int = 30
|
||||
@@ -91,11 +92,16 @@ class RoleSkillSection:
|
||||
f"你的角色:{context.role}",
|
||||
]
|
||||
if skill_name:
|
||||
lines.append(f"对应 Skill:{skill_name}")
|
||||
lines.append(
|
||||
f"请用 read 工具读取 {SKILL_BASE_PATH}/{skill_name}/SKILL.md "
|
||||
"获取完整操作规范。"
|
||||
)
|
||||
skill_path = os.path.join(SKILL_BASE_PATH, skill_name, "SKILL.md")
|
||||
try:
|
||||
with open(skill_path, encoding="utf-8") as f:
|
||||
skill_content = f.read()
|
||||
if skill_content:
|
||||
lines.append(skill_content)
|
||||
else:
|
||||
lines.append(f"(Skill 文件为空:{skill_name})")
|
||||
except FileNotFoundError:
|
||||
lines.append(f"(Skill 文件不存在:{skill_name})")
|
||||
else:
|
||||
lines.append("无对应 Skill 文件,按通用规范执行。")
|
||||
return "\n".join(lines)
|
||||
|
||||
+15
-15
@@ -1481,21 +1481,21 @@ Parent Task ID: {parent_task.id}
|
||||
# [Step 5] handler 幻觉门控兜底:check_completion 通过 + working → done
|
||||
handler = TaskTypeRegistry.get_by_project(self._current_project_id)
|
||||
if handler and handler.check_completion(task.id, db_path):
|
||||
conn = get_connection(db_path)
|
||||
try:
|
||||
ok = self._transition_status(
|
||||
conn, task.id, "done",
|
||||
agent="daemon",
|
||||
detail={"reason": "mail_auto_done_recheck",
|
||||
"elapsed_minutes": round(elapsed, 1)},
|
||||
)
|
||||
if ok:
|
||||
reclaimed.append(task.id)
|
||||
logger.info("Mail %s: ticker recheck found reply, marked done (%.1fm)",
|
||||
task.id, elapsed)
|
||||
finally:
|
||||
conn.close()
|
||||
continue
|
||||
conn = get_connection(db_path)
|
||||
try:
|
||||
ok = self._transition_status(
|
||||
conn, task.id, "done",
|
||||
agent="daemon",
|
||||
detail={"reason": "mail_auto_done_recheck",
|
||||
"elapsed_minutes": round(elapsed, 1)},
|
||||
)
|
||||
if ok:
|
||||
reclaimed.append(task.id)
|
||||
logger.info("Mail %s: ticker recheck found reply, marked done (%.1fm)",
|
||||
task.id, elapsed)
|
||||
finally:
|
||||
conn.close()
|
||||
continue
|
||||
|
||||
conn = get_connection(db_path)
|
||||
try:
|
||||
|
||||
Reference in New Issue
Block a user