feat: Step 5 引擎接入 + H1-H3/S3 修复 + 审计 D1/D2/D5 修复
引擎接入(dispatcher/spawner/ticker → handler 统一路由): - dispatcher: guardrail/on_checks_passed/on_complete → handler 查询 - spawner: _build_prompt/_build_api_section → handler.build_prompt - ticker: 虚拟项目扫描/assignee/claimed/review/幻觉门控 → handler 判断 Handler 缺陷修复: - H1: _mark_task_status 加 3 次重试(防 DB 锁) - H2: review @mention 加 comment_type='review' - H3: review 非 approved 保持 review 状态(不标 working) - S3: 通知链接改 Gitea(PR/Issue/Commit) 审计修复: - D1: pre_spawn 返回值未检查 → 加 if not 抛 RuntimeError - D2: PromptContext 缺 from_agent/mail_type → 从 must_haves 解析 - D5: _check_reply 查错表 → 恢复查 tasks 表找 in_reply_to 旧方法保留未删(deprecated),确认稳定后再清理。
This commit is contained in:
+36
-25
@@ -215,18 +215,21 @@ class Ticker:
|
||||
logger.exception("Tick %d _general error", tick_num)
|
||||
results["projects"]["_general"] = {"error": str(e)}
|
||||
|
||||
# 虚拟项目 _mail:飞鸽传书
|
||||
mail_db = Path(self.registry.root) / "_mail" / "blackboard.db"
|
||||
if mail_db.exists() and "_mail" not in active_projects:
|
||||
try:
|
||||
pr = await self._tick_project("_mail", {
|
||||
"id": "_mail", "name": "飞鸽传书",
|
||||
"status": "active", "source": "virtual",
|
||||
})
|
||||
results["projects"]["_mail"] = pr
|
||||
except Exception as e:
|
||||
logger.exception("Tick %d _mail error", tick_num)
|
||||
results["projects"]["_mail"] = {"error": str(e)}
|
||||
# 虚拟项目:从注册表自动发现 + _general 硬编码
|
||||
from src.daemon.task_type_registry import TaskTypeRegistry
|
||||
for vp in TaskTypeRegistry.virtual_projects():
|
||||
vp_db = Path(self.registry.root) / vp / "blackboard.db"
|
||||
if vp_db.exists() and vp not in active_projects:
|
||||
try:
|
||||
vp_name = {"_mail": "飞鸽传书", "_toolchain": "工具链事件"}.get(vp, vp)
|
||||
pr = await self._tick_project(vp, {
|
||||
"id": vp, "name": vp_name,
|
||||
"status": "active", "source": "virtual",
|
||||
})
|
||||
results["projects"][vp] = pr
|
||||
except Exception as e:
|
||||
logger.exception("Tick %d %s error", tick_num, vp)
|
||||
results["projects"][vp] = {"error": str(e)}
|
||||
|
||||
logger.debug(
|
||||
"Tick %d complete: %d projects",
|
||||
@@ -948,9 +951,11 @@ Parent Task ID: {parent_task.id}
|
||||
|
||||
now = datetime.utcnow().isoformat()
|
||||
# 重置到 pending 时清空 assignee(避免残留导致重复路由到同一 Agent)
|
||||
# 但 Mail 的 assignee 是收件人,永不清空
|
||||
# handler 虚拟项目(_mail 等)的 assignee 是收件人,永不清空
|
||||
if new_status == "pending":
|
||||
if self._current_project_id == "_mail":
|
||||
from src.daemon.task_type_registry import TaskTypeRegistry
|
||||
handler = TaskTypeRegistry.get_by_project(self._current_project_id)
|
||||
if handler:
|
||||
conn.execute(
|
||||
"UPDATE tasks SET status=?, updated_at=? WHERE id=?",
|
||||
(new_status, now, task_id),
|
||||
@@ -1025,15 +1030,17 @@ Parent Task ID: {parent_task.id}
|
||||
"full", "escalate"):
|
||||
conn = get_connection(db_path)
|
||||
try:
|
||||
# [v2.7.1] Mail 已在 dispatcher 中标 working,跳过 claimed
|
||||
if project_id == "_mail":
|
||||
# [Step 5] handler 项目已在 dispatcher 中标 working,跳过 claimed
|
||||
from src.daemon.task_type_registry import TaskTypeRegistry
|
||||
handler = TaskTypeRegistry.get_by_project(project_id)
|
||||
if handler:
|
||||
conn.execute(
|
||||
"UPDATE tasks SET current_agent=? WHERE id=?",
|
||||
(result["agent_id"], task.id),
|
||||
)
|
||||
conn.commit()
|
||||
dispatched.append(task.id)
|
||||
logger.info("Dispatched %s to %s (session=%s, mail auto-working)",
|
||||
logger.info("Dispatched %s to %s (session=%s, handler auto-working)",
|
||||
task.id, result["agent_id"],
|
||||
result.get("session_id"))
|
||||
else:
|
||||
@@ -1300,8 +1307,10 @@ Parent Task ID: {parent_task.id}
|
||||
async def _dispatch_reviews(self, db_path: Path,
|
||||
project_id: str) -> List[str]:
|
||||
"""扫描 review 状态任务,检查是否有产出,调度审查 Agent"""
|
||||
# mail 任务不走 review 流程,直接跳过
|
||||
if project_id == "_mail":
|
||||
# handler 项目(_mail/_toolchain)不走 review 流程
|
||||
from src.daemon.task_type_registry import TaskTypeRegistry
|
||||
handler = TaskTypeRegistry.get_by_project(project_id)
|
||||
if handler:
|
||||
return []
|
||||
|
||||
queries = Queries(db_path)
|
||||
@@ -1470,10 +1479,10 @@ Parent Task ID: {parent_task.id}
|
||||
|
||||
elapsed = (now - start_time).total_seconds() / 60.0
|
||||
if elapsed > timeout_minutes:
|
||||
# [v2.7.1] Mail 幻觉门控兜底:有回复 + working → done
|
||||
if self._current_project_id == "_mail":
|
||||
has_reply = self._mail_check_reply(task.id, db_path)
|
||||
if has_reply:
|
||||
# [Step 5] handler 幻觉门控兜底:check_completion 通过 + working → done
|
||||
from src.daemon.task_type_registry import TaskTypeRegistry
|
||||
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(
|
||||
@@ -1621,8 +1630,10 @@ Parent Task ID: {parent_task.id}
|
||||
project_dirs[project_id] = self.registry.root / \
|
||||
project_id / "blackboard.db"
|
||||
|
||||
# 虚拟项目
|
||||
for virtual_id in ("_general", "_mail"):
|
||||
# 虚拟项目:_general + 注册表自动发现
|
||||
from src.daemon.task_type_registry import TaskTypeRegistry
|
||||
virtual_ids = ["_general"] + TaskTypeRegistry.virtual_projects()
|
||||
for virtual_id in virtual_ids:
|
||||
virtual_db = Path(self.registry.root) / \
|
||||
virtual_id / "blackboard.db"
|
||||
if virtual_db.exists() and virtual_id not in project_dirs:
|
||||
|
||||
Reference in New Issue
Block a user