8d72a1fa19
引擎接入(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),确认稳定后再清理。
325 lines
12 KiB
Markdown
325 lines
12 KiB
Markdown
# Step 5 引擎接入 — 影响分析与逐点对照
|
||
|
||
## 方法论
|
||
|
||
逐行审查 dispatcher.py / spawner.py / ticker.py 中所有 `is_mail` / `_mail` / `project_id == "_mail"` 分支,
|
||
对照 handler 实现,确认每个特殊处理的去向。
|
||
|
||
---
|
||
|
||
## 一、dispatcher.py(985 行)
|
||
|
||
### 1.1 Guardrail 跳过(L127-129)
|
||
|
||
```python
|
||
is_mail = project_config.get("project_id") == "_mail" if project_config else False
|
||
if self.guardrails and not is_mail:
|
||
violations = self.guardrails.check_task(task)
|
||
```
|
||
|
||
**特殊处理**:Mail 不做 guardrail 检查。
|
||
|
||
**Handler 覆盖**:设计文档 D6 "skip_guardrail 从接口删除,guardrail 自己判断"。Step 5 改为:`if self.guardrails and handler is None`(无 handler 时走 guardrail),或者用 handler.virtual_project 判断。handler 存在时跳过 guardrail。
|
||
|
||
**改动**:`is_mail` → `TaskTypeRegistry.get_by_project(project_id) is not None`
|
||
|
||
---
|
||
|
||
### 1.2 Mail on_checks_passed(L194-213)
|
||
|
||
```python
|
||
on_checks_passed = None
|
||
_mail_marked_working = False
|
||
if is_mail and db_path:
|
||
def _mail_on_checks_passed():
|
||
nonlocal _mail_marked_working
|
||
if not _disp._mail_auto_working(_task_id, _mail_db):
|
||
raise RuntimeError("mail_auto_working_failed")
|
||
_mail_marked_working = True
|
||
on_checks_passed = _mail_on_checks_passed
|
||
```
|
||
|
||
**特殊处理**:Mail spawn 前通过 on_checks_passed 回调标 working,标记成功后才 spawn,spawn 失败回退。
|
||
|
||
**Handler 覆盖**:MailHandler.pre_spawn 调用 `_auto_mark_working`,和 `_mail_auto_working` 逻辑完全一致。
|
||
|
||
**改动**:
|
||
- `on_checks_passed` 改为调用 `handler.pre_spawn(task_id, db_path)`
|
||
- `_mail_marked_working` 标记保留,用于 Exception 回退
|
||
|
||
---
|
||
|
||
### 1.3 Mail on_complete(L224-238)
|
||
|
||
```python
|
||
if is_mail:
|
||
def _mail_on_complete(aid, outcome):
|
||
_dispatcher._mail_auto_complete(_task_id, aid, _mail_db, _must_haves, outcome=outcome)
|
||
on_complete = _mail_on_complete
|
||
```
|
||
|
||
**特殊处理**:Mail on_complete 调用 `_mail_auto_complete`(含 inform/request 分支、幻觉门控、重试 3 次、失败通知)。
|
||
|
||
**Handler 覆盖**:MailHandler 使用基类 post_complete 统一流程(crash→verify→mark→notify)。但现有 `_mail_auto_complete` 有几个细节差异需要注意:
|
||
|
||
| 现有逻辑 | Handler 覆盖 | 差异 |
|
||
|---------|-------------|------|
|
||
| request 无回复 → 重试 3 次标 failed | on_failure 标 failed + notify | ⚠️ 缺少 3 次重试 |
|
||
| inform 只在特定 outcome 标 done | verify 始终返回 True → 基类标 done | ✅ 简化了,合理 |
|
||
| 标 done 重试 3 次 | _mark_task_status 单次 | ⚠️ 缺少重试 |
|
||
| notify_mail_failed | on_failure 中调用 notify_mail_failed | ✅ 一致 |
|
||
|
||
**⚠️ 关键发现**:现有代码标状态时有 **重试 3 次** 机制(防止 DB 锁),handler 的 `_mark_task_status` 只做一次。需要把重试逻辑补到 `_mark_task_status` 或在 handler 层加。
|
||
|
||
**改动**:on_complete 改为调用 `handler.post_complete(task_id, agent_id, outcome, db_path)`
|
||
|
||
---
|
||
|
||
### 1.4 Task on_complete(L241-310)
|
||
|
||
```python
|
||
else:
|
||
def _task_on_complete(aid, outcome):
|
||
# #07.2: crash 回退
|
||
if outcome in ROLLBACK_CURRENT_AGENT_OUTCOMES and _task_db:
|
||
_dispatcher._rollback_current_agent(_task_db, _task_id, aid)
|
||
|
||
if _is_review:
|
||
if outcome in ("completed", "session_revived"):
|
||
# 读 verdict → approved 标 done / 非 approved @mention assignee
|
||
else:
|
||
logger.warning("review agent outcome=%s, NOT marking done", outcome)
|
||
else:
|
||
# executor: 三信号验证 → 标 review
|
||
_dispatcher._task_auto_complete(_task_id, _task_db)
|
||
```
|
||
|
||
**特殊处理清单**:
|
||
|
||
1. **#07.2 crash 回退**:executor 和 review 都回退 current_agent → assignee
|
||
2. **review 分支**:outcome 必须是 "completed" 或 "session_revived" 才走 verdict 读取
|
||
3. **review verdict 读取**:approved → done,非 approved → @mention assignee + 保持 review
|
||
4. **review @mention**:通过 Blackboard.add_comment,comment_type="review"
|
||
5. **executor 分支**:走 _task_auto_complete → 三信号验证 → review
|
||
|
||
**Handler 覆盖**:
|
||
- crash 回退:✅ BaseTaskHandler.post_complete 第一步
|
||
- review verdict:⚠️ **TaskHandler.handle_review_complete 存在但未被 dispatcher 调用**。现有 dispatcher 直接在闭包里做了,不走 handler。
|
||
- @mention:⚠️ handler 用 `conn.execute("INSERT INTO comments")` 直接插入,dispatcher 用 `Blackboard.add_comment`(会做更多处理,如 comment_type="review")
|
||
- executor 三信号:✅ TaskHandler.verify_completion
|
||
|
||
**⚠️ 关键发现**:
|
||
1. dispatcher 的 review @mention 用 `bb.add_comment(..., comment_type="review")`,handler 直接 INSERT 不带 comment_type。需要修复 handler。
|
||
2. dispatcher 对 review outcome 有白名单检查(只处理 "completed"/"session_revived"),handler 的 post_complete 没有 outcome 白名单——crash 已在基类处理,其他 outcome 都会走 verify。
|
||
3. dispatcher review 非 approved 时**保持 review 状态**,handler 的 handle_review_complete 标回 working。这是**行为差异**。
|
||
|
||
**改动**:需要先修复 handler 的 review 分支,再替换 on_complete。
|
||
|
||
---
|
||
|
||
### 1.5 Mail spawn 失败回退(L355-358)
|
||
|
||
```python
|
||
except Exception as e:
|
||
if _mail_marked_working:
|
||
self._mail_revert_to_pending(task.id, db_path)
|
||
```
|
||
|
||
**特殊处理**:spawn 失败(subprocess 启动失败)回退 working → pending。
|
||
|
||
**Handler 覆盖**:❌ handler 没有这个。这是 dispatcher 级别的异常处理,和 handler 无关。但 toolchain 也需要类似逻辑。
|
||
|
||
**改动**:保留在 dispatcher 中,改为 `_mail_marked_working` → `handler_marked_working`。
|
||
|
||
---
|
||
|
||
### 1.6 Legacy dispatch(L584-660)
|
||
|
||
```python
|
||
is_mail_legacy = project_config.get("project_id") == "_mail"
|
||
if is_mail_legacy:
|
||
if not self._mail_auto_working(task.id, db_path_legacy):
|
||
return error
|
||
```
|
||
|
||
**特殊处理**:legacy 路径(router=None 时触发)也有 mail 特殊处理。
|
||
|
||
**Handler 覆盖**:同 1.2/1.3,用 handler 替代。
|
||
|
||
**改动**:同样用 handler.pre_spawn 和 handler.post_complete 替代。
|
||
|
||
---
|
||
|
||
### 1.7 现有 Mail 辅助方法(L658-870)
|
||
|
||
`_mail_auto_working` / `_mail_revert_to_pending` / `_mail_auto_complete` / `_mail_check_reply`
|
||
|
||
**改动**:Step 5 不删这些方法(安全起见保留,标记 deprecated),只改调用方。确认稳定后再删。
|
||
|
||
---
|
||
|
||
## 二、spawner.py(1704 行)
|
||
|
||
### 2.1 _build_prompt 中的 mail 分支(L282-284)
|
||
|
||
```python
|
||
if project_id == "_mail":
|
||
return self._build_mail_prompt(task_id, title, description, must_haves, agent_id)
|
||
```
|
||
|
||
**特殊处理**:Mail 用专用精简模板。
|
||
|
||
**Handler 覆盖**:MailHandler.build_prompt 通过 PromptComposer 拼 3 个 section。
|
||
|
||
**改动**:查注册表 → handler.build_prompt(context)。需要构建 PromptContext 传入。
|
||
|
||
---
|
||
|
||
### 2.2 _build_api_section(L321-325)
|
||
|
||
```python
|
||
success_status = '"done"' if project_id == "_mail" else '"review"'
|
||
```
|
||
|
||
**特殊处理**:Mail 的 success_status 是 done。
|
||
|
||
**Handler 覆盖**:已由 handler 的 PromptSection 处理(TaskApiSection hardcode review,MailApiSection 不含 status 回写指令)。
|
||
|
||
**改动**:如果 handler 存在,跳过 _build_api_section(handler.build_prompt 已包含)。
|
||
|
||
---
|
||
|
||
### 2.3 classify_outcome 中的 handler 调用
|
||
|
||
spawner 在 classify_outcome 后调 on_complete(outcome)。on_complete 是 dispatcher 传入的闭包。
|
||
|
||
**改动**:on_complete 闭包改为调用 handler.post_complete。spawner 本身不直接查注册表。
|
||
|
||
---
|
||
|
||
## 三、ticker.py(1897 行)
|
||
|
||
### 3.1 虚拟项目扫描(L218-229)
|
||
|
||
```python
|
||
mail_db = Path(self.registry.root) / "_mail" / "blackboard.db"
|
||
if mail_db.exists() and "_mail" not in active_projects:
|
||
pr = await self._tick_project("_mail", {...})
|
||
```
|
||
|
||
**特殊处理**:_mail 硬编码扫描。
|
||
|
||
**Handler 覆盖**:TaskTypeRegistry.virtual_projects() 返回 ["_toolchain", "_mail"]。
|
||
|
||
**改动**:循环 `TaskTypeRegistry.virtual_projects()` 替代硬编码。_toolchain 如果也需要 ticker 扫描就自动发现。但需确认 _toolchain 是否需要 ticker——当前 toolchain 任务创建和完成都在 toolchain_routes.py 中处理,可能不需要 ticker 扫描。
|
||
|
||
---
|
||
|
||
### 3.2 _transition_status 中 mail assignee 不清空(L953-960)
|
||
|
||
```python
|
||
if new_status == "pending":
|
||
if self._current_project_id == "_mail":
|
||
# Mail 的 assignee 是收件人,永不清空
|
||
conn.execute("UPDATE tasks SET status=?, updated_at=? WHERE id=?", ...)
|
||
else:
|
||
conn.execute("UPDATE tasks SET status=?, assignee=NULL, ...", ...)
|
||
```
|
||
|
||
**特殊处理**:Mail 重置到 pending 时不清空 assignee(assignee 是收件人)。
|
||
|
||
**Handler 覆盖**:❌ handler 不管 ticker 的状态转换逻辑。这是 ticker 内部逻辑。
|
||
|
||
**改动**:用 `TaskTypeRegistry.get_by_project(project_id)` 判断替代硬编码。
|
||
|
||
---
|
||
|
||
### 3.3 Mail 跳过 claimed 状态(L1029-1043)
|
||
|
||
```python
|
||
if project_id == "_mail":
|
||
conn.execute("UPDATE tasks SET current_agent=? WHERE id=?", ...)
|
||
# 跳过 claimed,直接 working
|
||
```
|
||
|
||
**特殊处理**:Mail 不走 claimed 中间态(已在 dispatcher 中标 working)。
|
||
|
||
**Handler 覆盖**:handler.pre_spawn 的 _auto_mark_working 跳过了 claimed。
|
||
|
||
**改动**:用 handler 判断替代硬编码。
|
||
|
||
---
|
||
|
||
### 3.4 _dispatch_reviews 跳过 mail(L1304)
|
||
|
||
```python
|
||
if project_id == "_mail":
|
||
return []
|
||
```
|
||
|
||
**特殊处理**:Mail 不走 review 流程。
|
||
|
||
**Handler 覆盖**:MailHandler.target_success_status = "done",不走 review。但 ticker 的 _dispatch_reviews 是看项目级。
|
||
|
||
**改动**:用 handler 判断。
|
||
|
||
---
|
||
|
||
### 3.5 Mail 幻觉门控兜底(L1474-1492)
|
||
|
||
```python
|
||
if self._current_project_id == "_mail":
|
||
has_reply = self._mail_check_reply(task.id, db_path)
|
||
if has_reply:
|
||
# working → done
|
||
```
|
||
|
||
**特殊处理**:Ticker 超时检查时,如果 mail 有回复,标 done 而非 failed。
|
||
|
||
**Handler 覆盖**:❌ handler 的 check_completion 只返回 bool,不做状态标记。
|
||
|
||
**改动**:调用 handler.check_completion 替代 _mail_check_reply。状态标记逻辑保留在 ticker 中。
|
||
|
||
---
|
||
|
||
### 3.6 _mail_check_reply(L1555-1575)
|
||
|
||
和 dispatcher 版本一致。
|
||
|
||
**改动**:用 handler.check_completion 替代。
|
||
|
||
---
|
||
|
||
### 3.7 虚拟项目 init + recovery 扫描(L1625-1643)
|
||
|
||
```python
|
||
for virtual_id in ("_general", "_mail"):
|
||
...
|
||
# _mail 项目不清空 assignee
|
||
```
|
||
|
||
**改动**:virtual_projects() + _general 硬编码。
|
||
|
||
---
|
||
|
||
## 四、Handler 缺陷(需在 Step 5 前修复)
|
||
|
||
| # | 缺陷 | 影响 | 修复方案 |
|
||
|---|------|------|---------|
|
||
| H1 | BaseTaskHandler._mark_task_status 无重试 | DB 锁时标状态失败,任务卡住 | 加 3 次重试(和 dispatcher 现有行为一致) |
|
||
| H2 | TaskHandler.handle_review_complete 中 @mention 不带 comment_type="review" | review comment 无类型标记 | INSERT 加 comment_type |
|
||
| H3 | dispatcher review 非 approved 保持 review 状态,handler 标 working | **行为差异** | handler 改为保持 review 状态(和 dispatcher 一致) |
|
||
| H4 | dispatcher review outcome 有白名单("completed"/"session_revived"),handler 无 | crash 之外的异常 outcome 也会走 verify | handler 的 post_complete 已在基类处理 crash,其余 outcome 走 verify 是合理的 |
|
||
|
||
**H3 最关键**——dispatcher review 非 approved 保持 review 状态(等 assignee 自己处理),handler 标 working 会触发 ticker 重新 dispatch executor,这不是预期行为。
|
||
|
||
## 五、改动策略
|
||
|
||
**不删旧代码,只改调用方**:
|
||
1. dispatcher 中 is_mail → handler 判断,on_checks_passed/on_complete → handler.pre_spawn/post_complete
|
||
2. spawner 中 _build_prompt → handler.build_prompt
|
||
3. ticker 中虚拟项目扫描 → registry.virtual_projects(),mail 特殊判断 → handler 判断
|
||
4. 旧方法(_mail_auto_working 等)标记 @deprecated 保留,不删
|
||
|
||
**先修 handler 缺陷(H1-H3),再改引擎**。
|