[moz] impl(§22): P1+P2 轻量路径+数据流+Round Review Gitea 适配
§22 全部未完成项一次性补齐:
A. 文档更新:
- §22.3/§22.4 Phase 1 状态更新为 ✅ 已实现
- §22.5 P0/P1/P2 标记全部完成
- §22.6/§22.7 更新实现状态
B2. flow/* label 识别(toolchain_routes.py):
- opened 分支识别 flow/direct → 创建 executor task(跳过 discussion)
- flow/discuss 无 assignee 时走默认 discussion 路径
B3. Discussion prompt 降级机制(spawner.py):
- DISCUSSION_PROMPT_TEMPLATE 加降级引导
B4. task_state 表 + parent_issue 解析(toolchain_routes.py):
- _init_task_state_table: CREATE TABLE IF NOT EXISTS task_state
- _ensure_task_state: 解析 [parent #N] 写入 parent_issue
- _handle_issues assigned 分支调用
B5. _check_round_complete 双源扫描(ticker.py):
- 扫 tasks.parent_task(黑板路径)+ task_state.parent_issue(Gitea 路径)
- 合并两个来源的 parent IDs
This commit is contained in:
@@ -23,7 +23,7 @@ from typing import Any, Dict, List, Optional, Set, Tuple
|
||||
import httpx
|
||||
from fastapi import APIRouter, Header, Request, Response
|
||||
|
||||
from src.blackboard.db import init_db
|
||||
from src.blackboard.db import init_db, get_connection
|
||||
from src.blackboard.models import Task
|
||||
from src.blackboard.operations import Blackboard
|
||||
from src.config.agents import AGENT_IDS
|
||||
@@ -215,9 +215,54 @@ def _toolchain_db_path() -> Path:
|
||||
db = root / TOOLCHAIN_PROJECT_ID / "blackboard.db"
|
||||
db.parent.mkdir(parents=True, exist_ok=True)
|
||||
init_db(db)
|
||||
_init_task_state_table(db)
|
||||
return db
|
||||
|
||||
|
||||
def _init_task_state_table(db: Path) -> None:
|
||||
"""§22.7: 创建 task_state 表(如不存在)"""
|
||||
conn = get_connection(db)
|
||||
try:
|
||||
conn.execute(
|
||||
"CREATE TABLE IF NOT EXISTS task_state ("
|
||||
" issue_number INTEGER PRIMARY KEY,"
|
||||
" repo TEXT,"
|
||||
" parent_issue INTEGER,"
|
||||
" status TEXT DEFAULT 'pending',"
|
||||
" action_type TEXT,"
|
||||
" retry_count INTEGER DEFAULT 0,"
|
||||
" dispatch_count INTEGER DEFAULT 0,"
|
||||
" round_count INTEGER DEFAULT 0,"
|
||||
" created_at TEXT,"
|
||||
" updated_at TEXT"
|
||||
")"
|
||||
)
|
||||
conn.execute(
|
||||
"CREATE INDEX IF NOT EXISTS idx_task_state_parent "
|
||||
"ON task_state(parent_issue)"
|
||||
)
|
||||
conn.commit()
|
||||
except Exception:
|
||||
logger.debug("task_state table init skipped (may already exist)", exc_info=True)
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
|
||||
def _ensure_task_state(db: Path, issue_number: int, repo: str,
|
||||
parent_issue: int) -> None:
|
||||
"""§22.7: 记录 sub Issue 的 parent_issue 映射到 task_state 表"""
|
||||
conn = get_connection(db)
|
||||
try:
|
||||
conn.execute(
|
||||
"INSERT OR IGNORE INTO task_state (issue_number, repo, parent_issue, status, created_at) "
|
||||
"VALUES (?, ?, ?, 'pending', datetime('now'))",
|
||||
(issue_number, repo, parent_issue),
|
||||
)
|
||||
conn.commit()
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
|
||||
def _send_toolchain_task(
|
||||
to_agent: str | None,
|
||||
title: str,
|
||||
@@ -1085,6 +1130,14 @@ async def _handle_issues(payload: Dict[str, Any]) -> None:
|
||||
},
|
||||
)
|
||||
|
||||
# §22.7: 解析 [parent #{N}] 写入 task_state(toolchain DB)
|
||||
parent_match = re.search(r'\[parent\s* #(\d+)\]', issue_title, re.IGNORECASE)
|
||||
if parent_match:
|
||||
parent_issue_num = int(parent_match.group(1))
|
||||
_ensure_task_state(_toolchain_db_path(), issue_number, repo, parent_issue_num)
|
||||
logger.info("Issue #%s: parent_issue=%d recorded in task_state",
|
||||
issue_number, parent_issue_num)
|
||||
|
||||
elif action == "closed":
|
||||
# §21 §11 Issue closed 纯通知(auto-pass)
|
||||
assignee_login = ""
|
||||
@@ -1113,14 +1166,38 @@ async def _handle_issues(payload: Dict[str, Any]) -> None:
|
||||
assignees = list(assignees) + [single_assignee]
|
||||
|
||||
if not assignees and not ("部署失败" in issue_title):
|
||||
# 无 assignee + 非部署失败 → 检查是否有 type/* label
|
||||
# 无 assignee + 非部署失败 → 检查 flow/* 和 type/* label
|
||||
labels_list_opened = [
|
||||
lbl.get("name", "") for lbl in (issue.get("labels") or [])
|
||||
]
|
||||
has_type_label = any(
|
||||
lbl.lower().startswith("type/") for lbl in labels_list_opened
|
||||
)
|
||||
if has_type_label:
|
||||
has_flow_direct = any(
|
||||
lbl.lower() == "flow/direct" for lbl in labels_list_opened
|
||||
)
|
||||
# §22.6: flow/direct → 强制 Direct 路径(跳过 discussion)
|
||||
# 即使无 assignee 也直接创建 executor task(daemon 不自动 assign,
|
||||
# 而是走 delegate 给庞统分配)
|
||||
if has_flow_direct and has_type_label:
|
||||
# flow/direct 无 assignee → 走 delegate 路径(和 discussion 一样不 assign)
|
||||
# 但 action_type 标记为 issue_assigned 让 ticker 走 executor 路径
|
||||
_send_toolchain_task(
|
||||
to_agent=None,
|
||||
title=f"Issue 待分配: {issue_title} ({repo}#{issue_number})",
|
||||
description=f"## Issue (flow/direct)\n\n**{repo}#{issue_number}**: {issue_title}\n\n{issue.get('body', '') or '(无描述)'}",
|
||||
event_type="issue_assigned",
|
||||
action_type="issue_assigned",
|
||||
steps=[],
|
||||
context_data={
|
||||
"issue_number": issue_number,
|
||||
"repo": repo,
|
||||
"issue_title": issue_title,
|
||||
"issue_body": issue.get("body", "") or "",
|
||||
},
|
||||
)
|
||||
logger.info("Issue #%s: flow/direct → executor task (no assignee)", issue_number)
|
||||
elif has_type_label:
|
||||
issue_body = issue.get("body", "") or "(无描述)"
|
||||
title_discussion = f"Issue 讨论: {issue_title} ({repo}#{issue_number})"
|
||||
_send_toolchain_task(
|
||||
|
||||
Reference in New Issue
Block a user