From b70d7c47ddfa903561e3efdbc76ee460e6edc917 Mon Sep 17 00:00:00 2001 From: cfdaily Date: Sun, 7 Jun 2026 01:21:29 +0800 Subject: [PATCH] auto-sync: 2026-06-07 01:21:29 --- docs/design/mail-failure-notification.md | 57 ++++++++++++++++-------- 1 file changed, 38 insertions(+), 19 deletions(-) diff --git a/docs/design/mail-failure-notification.md b/docs/design/mail-failure-notification.md index 7a7adfc..3686818 100644 --- a/docs/design/mail-failure-notification.md +++ b/docs/design/mail-failure-notification.md @@ -1,6 +1,6 @@ # Mail 失败通知设计 -> 版本:v1.1-draft | 日期:2026-06-07 | 作者:庞统 | 状态:待评审 +> 版本:v1.2-draft | 日期:2026-06-07 | 作者:庞统 | 状态:待二次确认 ## 1. 背景 @@ -16,23 +16,36 @@ | `_mark_task("failed")` | spawn 级失败:crashed、auth_failed、crash_limit、timeout 等 | @pangtong(F2) | 发件人(不再 @pangtong) | | `_mail_auto_complete` no_reply_found | Agent 正常退出但没回复 request 类邮件 | 无 | 发件人 | +**所有 Mail failed 都通知发件人**,不管 reason。 + ## 2. 设计方案 ### 2.1 核心机制 Mail 最终标 failed 时,daemon 以 **系统发件人(`system`)** 身份给 from_agent 发一封 inform 邮件,告知原因和处理建议。 -**走标准 `POST /api/mail` 流程**,新增 `system` 为合法发件人。 - ### 2.2 系统发件人 新增 `system` 作为 Mail 的特殊发件人: -- 在 `mail_routes.py` 的 `VALID_AGENTS` 或校验逻辑中,允许 `from="system"` +- **不暴露给外部 API**:`system` 只允许 daemon 内部调用 +- **实现方式**:`mail_notify.py` 直接调用 `send_mail` 的内部函数(`_do_send_mail`),不走 FastAPI HTTP 路由 - `system` 不是真实 Agent,没有 main session,只用于发送通知 - 防递归:通知邮件的 must_haves 中加 `"system_notify": true`,触发前检查该标记 -### 2.3 触发点 +### 2.3 判断 _mail 项目 + +`_mark_task` 签名只有 `(db_path, task_id, status, detail)`,没有 project_id。从 db_path 推断: + +```python +def _is_mail_project(db_path: Path) -> bool: + path_str = str(db_path) + return "/_mail/" in path_str or path_str.endswith("_mail.db") +``` + +覆盖两种 _mail db_path:`data/_mail/blackboard.db`(新)和 `data/_mail.db`(旧)。 + +### 2.4 触发点 **触发点 1**:`_mail_auto_complete` 中 no_reply_found 标 failed 之后(dispatcher.py) @@ -42,10 +55,10 @@ Mail 最终标 failed 时,daemon 以 **系统发件人(`system`)** 身份 **触发点 2**:`_mark_task` 中 status=="failed" 且是 _mail 项目时(spawner.py) - 位置:现有 F2 @pangtong 逻辑处 -- **改动**:当项目是 `_mail` 时,**不 @pangtong**,改为调用 `notify_mail_failed` 通知发件人 -- 当项目不是 `_mail` 时,F2 @pangtong 逻辑不变 +- **改动**:当 `_is_mail_project(db_path)` 时,**不 @pangtong**,改为调用 `notify_mail_failed` +- 当不是 _mail 项目时,F2 @pangtong 逻辑不变 -### 2.4 实现位置 +### 2.5 实现位置 新增 `src/daemon/mail_notify.py`,统一函数 `notify_mail_failed`: @@ -53,14 +66,12 @@ Mail 最终标 failed 时,daemon 以 **系统发件人(`system`)** 身份 def notify_mail_failed(db_path: Path, original_mail_id: str, reason: str, detail: dict = None): """Mail 失败后以 system 身份给发件人发通知邮件 - 走标准 POST /api/mail 流程(from=system)。 + 直接调用 _do_send_mail 内部函数,不走 HTTP。 防递归:检查原邮件 must_haves.system_notify,为 true 则跳过。 """ ``` -内部调用 `send_mail(from="system", to=from_agent, type="inform", ...)`。 - -### 2.5 通知邮件内容 +### 2.6 通知邮件内容 | 字段 | 值 | |------|------| @@ -79,9 +90,11 @@ def notify_mail_failed(db_path: Path, original_mail_id: str, reason: str, detail | `auth_failed` | "邮件「{title}」投递时认证失败" | "需检查 Agent 配置,联系姜维排查" | | `crash_limit` | "邮件「{title}」投递时多次崩溃" | "系统异常,建议稍后重试" | | `task_timeout` | "邮件「{title}」处理超时" | "建议重发或通过其他方式联系" | -| 其他 | "邮件「{title}」投递失败(原因:{reason})" | "建议联系庞统排查" | +| 其他 | "邮件「{title}」投递失败(原因:{reason})" | "建议联系副军师(pangtong-fujunshi)排查" | -### 2.6 防递归 +### 2.7 防递归 + +系统通知邮件本身也可能失败(spawn 崩溃等),必须防止无限递归: ```python # 防递归:系统通知邮件失败不再发通知 @@ -90,7 +103,11 @@ if meta.get("system_notify"): return # 系统邮件失败,不再递归通知 ``` -### 2.7 from_agent 获取 +递归链验证: +1. 原邮件 crash → `_mark_task(failed)` → `_is_mail_project=true` → 不 @pangtong → `notify_mail_failed` +2. 通知邮件(from=system)也 crash → `_mark_task(failed)` → `_is_mail_project=true` → `notify_mail_failed` → 检查 `system_notify=true` → 跳过 ✅ + +### 2.8 from_agent 获取 原 Mail 的发件人存在两个等价位置: - `task.assigned_by` @@ -104,17 +121,17 @@ if meta.get("system_notify"): | 文件 | 内容 | 预估行数 | |------|------|---------| -| `src/daemon/mail_notify.py` | `notify_mail_failed` 函数 | ~60 行 | +| `src/daemon/mail_notify.py` | `notify_mail_failed` + `_is_mail_project` | ~65 行 | ### 3.2 修改文件 | 文件 | 改动 | 预估行数 | |------|------|---------| | `src/daemon/dispatcher.py` | `_mail_auto_complete` 标 failed 后调 notify | ~5 行 | -| `src/daemon/spawner.py` | `_mark_task` failed 分支:`_mail` 项目不 @pangtong,改调 notify | ~10 行 | -| `src/api/mail_routes.py` | 允许 `system` 作为合法发件人(from 校验) | ~3 行 | +| `src/daemon/spawner.py` | `_mark_task` failed 分支:_mail 项目不 @pangtong,改调 notify | ~10 行 | +| `src/api/mail_routes.py` | 拆出 `_do_send_mail` 内部函数供 daemon 调用,valid_agents 校验对内部调用跳过 from 检查 | ~15 行 | -总计约 80 行,4 个文件。 +总计约 95 行,4 个文件。 ### 3.3 不改的 @@ -123,6 +140,7 @@ if meta.get("system_notify"): | F2 @pangtong 对 Task 的逻辑 | Task failed 仍 @pangtong,只对 Mail 不适用 | | no_reply_found 的标 failed 逻辑 | 只在标 failed 之后加通知,不改判定逻辑 | | inform 类型邮件的完成逻辑 | inform 直接标 done,不存在 no_reply_found 场景 | +| 外部 API 的 from 校验 | system 不走 HTTP 路由,外部无法伪造 | ## 4. 验证方式 @@ -133,3 +151,4 @@ if meta.get("system_notify"): | Task failed | Task spawn crash → _mark_task failed | @pangtong(F2 不变) | | 系统通知邮件本身失败 | 通知邮件也 crash | 不再递归(system_notify=true) | | inform 邮件正常完成 | Agent 收到 inform → 直接 done | 不触发通知 | +| inform 邮件 crash | inform 的 spawn 崩溃 | 发件人收到 system 通知(crash 也通知) |