fix: prevent email loop by detecting bounces and capping conversation length
- email_channel.py: add detect_automated_message() that inspects RFC 3834 Auto-Submitted, mailer-daemon/postmaster sender patterns, X-Loop, multipart/report Content-Type, Precedence, and subject heuristics. fetch_unread_messages() now includes is_automated / automated_reason in every message dict. - main.py: if is_automated is set, call agent.handle_automated_message() instead of process_message() — no reply is ever sent to a bounce source. - agent/core.py: add MAX_USER_MESSAGES = 20 cap; process_message() returns "" without replying once a conversation exceeds the limit and calls notifier.notify_loop_escalation() on first breach. New public method handle_automated_message() records the event and triggers the same one-shot admin alert. - models/conversation.py: add loop_escalated: bool field (persisted) so the admin alert fires at most once per conversation. - notifications/notifier.py: add notify_loop_escalation() which sends a plain-text warning to the admin CC list (Markus Graf / spielgruppen@). https://claude.ai/code/session_01KwvR5hDPjSuJg4kvw5b5e5
This commit is contained in:
@@ -35,6 +35,9 @@ class ConversationState:
|
||||
# Most recent inbound Message-ID — used for reply threading headers only,
|
||||
# NOT for conversation matching (which is always by email address).
|
||||
last_inbound_message_id: str = ""
|
||||
# Loop / automated-sender prevention.
|
||||
# Set to True once the admin has been notified; prevents repeated alerts.
|
||||
loop_escalated: bool = False
|
||||
|
||||
def to_dict(self) -> dict:
|
||||
return {
|
||||
@@ -54,6 +57,7 @@ class ConversationState:
|
||||
"completed": self.completed,
|
||||
"reminder_count": self.reminder_count,
|
||||
"last_inbound_message_id": self.last_inbound_message_id,
|
||||
"loop_escalated": self.loop_escalated,
|
||||
}
|
||||
|
||||
@classmethod
|
||||
@@ -78,4 +82,5 @@ class ConversationState:
|
||||
state.completed = data.get("completed", False)
|
||||
state.reminder_count = data.get("reminder_count", 0)
|
||||
state.last_inbound_message_id = data.get("last_inbound_message_id", "")
|
||||
state.loop_escalated = data.get("loop_escalated", False)
|
||||
return state
|
||||
|
||||
Reference in New Issue
Block a user