diff --git a/src/api/toolchain_routes.py b/src/api/toolchain_routes.py index f56586b..7222970 100644 --- a/src/api/toolchain_routes.py +++ b/src/api/toolchain_routes.py @@ -219,7 +219,7 @@ def _toolchain_db_path() -> Path: def _send_toolchain_task( - to_agent: str, + to_agent: str | None, title: str, description: str, event_type: str, @@ -231,7 +231,7 @@ def _send_toolchain_task( """创建 Toolchain Task 并写入 _toolchain DB。 Args: - to_agent: 收件人 Agent ID + to_agent: 收件人 Agent ID,None 表示无指派(待路由/delegate) title: 任务标题 description: 任务描述(模板渲染后的事件信息) event_type: 事件类型(review_result / ci_failure / ...) @@ -243,7 +243,7 @@ def _send_toolchain_task( Returns: 创建的 Task ID """ - if to_agent not in AGENT_IDS: + if to_agent is not None and to_agent not in AGENT_IDS: logger.warning("Unknown agent: %s, skipping toolchain task", to_agent) return "" @@ -1106,6 +1106,44 @@ async def _handle_issues(payload: Dict[str, Any]) -> None: ) elif action == "opened": + # §11b: 无 assignee 的普通 Issue → discussion task + assignees = issue.get("assignees") or [] + single_assignee = issue.get("assignee") + if single_assignee and isinstance(single_assignee, dict) and single_assignee not in assignees: + assignees = list(assignees) + [single_assignee] + + if not assignees and not ("部署失败" in issue_title): + # 无 assignee + 非部署失败 → 检查是否有 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: + issue_body = issue.get("body", "") or "(无描述)" + title_discussion = f"Issue 讨论: {issue_title} ({repo}#{issue_number})" + _send_toolchain_task( + to_agent=None, # 无指派 → router delegate 庞统 + title=title_discussion, + description=f"## Issue 需要讨论\n\n" + f"**{repo}#{issue_number}**: {issue_title}\n\n" + f"{issue_body}", + event_type="issue_discussion", + action_type="issue_discussion", + steps=[], # discussion 不需要结构化步骤 + context_data={ + "issue_number": issue_number, + "repo": repo, + "issue_title": issue_title, + "issue_body": issue_body, + }, + ) + logger.info( + "Issue #%s: no assignee + type label → discussion task", + issue_number) + # discussion task 创建后继续检查 @mention(不 return) + if "部署失败" in issue_title: # 从 Issue body 提取 commit hash(Gitea deploy workflow 格式) sha_match = re.search(r'[0-9a-f]{40}', issue.get("body", "")) diff --git a/src/daemon/toolchain_handler.py b/src/daemon/toolchain_handler.py index 24a0057..075e5fe 100644 --- a/src/daemon/toolchain_handler.py +++ b/src/daemon/toolchain_handler.py @@ -34,6 +34,8 @@ _ACTION_HINTS: Dict[str, str] = { "review_comment": "你收到一个 Review 评论,这是一个需要你查看并响应的事件。", "ci_failure": "你收到一个 CI 失败通知,这是一个需要你修复失败测试的事件。", "issue_assigned": "你收到一个 Issue 指派,这是一个需要你编码实现的事件。", + "issue_closed": "你收到一个 Issue 关闭通知。这是一条纯通知,阅读即可。", + "issue_discussion": "你收到一个需要讨论的 Issue。请阅读 Issue 内容,发起讨论并引导其他 agent 参与。", "deploy_failure": "你收到一个部署失败通知,这是一个需要你排查并修复的事件。", "mention": "你收到一个 @mention 通知,这是一个需要你按指引响应的事件。", "review_merged": "你收到一个 PR 合并通知。这是一条纯通知,阅读即可。", @@ -336,6 +338,11 @@ class ToolchainHandler(BaseTaskHandler): return VerifyResult(True, "issue_closed_passthrough", "issue_closed auto-pass") + # 特殊处理:issue_discussion 始终通过(触发 delegate 庞统 §21 §11b) + if meta.get("action_type") == "issue_discussion": + return VerifyResult(True, "discussion_passthrough", + "issue_discussion auto-pass") + # 1. 优先检查 action_report comment report_row = conn.execute( "SELECT id FROM comments WHERE task_id=? "