diff --git a/src/daemon/mail_notify.py b/src/daemon/mail_notify.py index 092fbff..683a0fc 100644 --- a/src/daemon/mail_notify.py +++ b/src/daemon/mail_notify.py @@ -6,7 +6,7 @@ import json import logging from datetime import datetime from pathlib import Path -from typing import Callable, Dict, Optional +from typing import Dict, Optional from src.blackboard.models import Task from src.blackboard.operations import Blackboard @@ -32,7 +32,8 @@ def _fmt_retry_info(reason: str, detail: dict) -> str: "agent_failed", "compact_failed", } if reason in _NO_RETRY_REASONS: - return f"无法重试({_REASON_MAP.get(reason, _REASON_MAP[\"_default\"])[0]})" + reason_human = _REASON_MAP.get(reason, _REASON_MAP.get("_default", ("未知原因", lambda d: "")))[0] + return f"无法重试({reason_human})" count = (detail or {}).get("count", 0) fallback_count = (detail or {}).get("fallback_count", 0) @@ -51,17 +52,26 @@ _REASON_MAP: Dict[str, tuple] = { "max_crash_count": ("连续崩溃达上限", lambda d: f"崩溃 {d.get('count', '?')} 次"), "max_retries": ("续杯耗尽(已自动重试)", lambda d: f"重试 {d.get('count', '?')} 次"), "max_api_retry_count": ("API 连续失败达上限", lambda d: f"API 重试 {d.get('count', '?')} 次"), - "max_monitor_timeouts": ("处理超时达上限", lambda d: f"超时 {d.get('count', '?')} 次,共约 {d.get('elapsed_seconds', 0) // 60} 分钟"), + "max_monitor_timeouts": ( + "处理超时达上限", + lambda d: f"超时 {d.get('count', '?')} 次," + f"共约 {d.get('elapsed_seconds', 0) // 60} 分钟"), "gateway_timeout": ("Agent 执行超时(已续杯重试)", lambda d: ""), "session_stuck": ("会话假死(lock PID 死亡)", lambda d: f"假死 {d.get('stuck_count', '?')} 次"), "revive_failed": ("会话恢复失败", lambda d: f"假死 {d.get('stuck_count', '?')} 次"), "auth_failed": ("Agent 认证失败(配置问题)", lambda d: f"stderr: {_extract_stderr(d)}" if _extract_stderr(d) else ""), - "fallback_exhausted": ("主模型和备用模型均失败", lambda d: f"fallback {d.get('fallback_count', '?')} 次,原因: {d.get('fallback_reason', '未知')}"), + "fallback_exhausted": ( + "主模型和备用模型均失败", + lambda d: f"fallback {d.get('fallback_count', '?')} 次," + f"原因: {d.get('fallback_reason', '未知')}"), "agent_failed": ("收件人主动标记失败", lambda d: ""), "compact_failed": ("上下文压缩失败", lambda d: f"stderr: {_extract_stderr(d)}" if _extract_stderr(d) else ""), "compact_hanging": ("上下文压缩长时间未完成", lambda d: ""), "compact_interrupted": ("上下文压缩被中断(已自动重试)", lambda d: ""), - "gateway_unreachable": ("Gateway 不可达(已自动重试)", lambda d: f"stderr: {_extract_stderr(d)}" if _extract_stderr(d) else ""), + "gateway_unreachable": ( + "Gateway 不可达(已自动重试)", + lambda d: f"stderr: {_extract_stderr(d)}" + if _extract_stderr(d) else ""), "lock_conflict": ("会话锁冲突(已自动重试)", lambda d: ""), "max_retry_count": ("重试耗尽", lambda d: f"重试 {d.get('count', '?')} 次"), "max_lock_retry_count": ("锁冲突重试耗尽", lambda d: f"重试 {d.get('count', '?')} 次"), diff --git a/tests/unit/test_classify_outcome.py b/tests/unit/test_classify_outcome.py index 07f21d8..62a2061 100644 --- a/tests/unit/test_classify_outcome.py +++ b/tests/unit/test_classify_outcome.py @@ -165,14 +165,16 @@ class TestClassifyErrorApi: 1, {"status": "error"}, "rate_limit exceeded", None ) assert result["outcome"] == "api_error" - assert result["should_retry"] is False + assert result["should_retry"] is True + assert result["cooldown_seconds"] == 60 def test_stderr_500(self): result = Spawner._classify_outcome( 1, {"status": "error"}, "HTTP 500 Internal Server Error", None ) assert result["outcome"] == "api_error" - assert result["should_retry"] is False + assert result["should_retry"] is True + assert result["cooldown_seconds"] == 60 class TestClassifyErrorCompact: