114 lines
3.9 KiB
Python
114 lines
3.9 KiB
Python
"""Agent behavior policies and rules."""
|
|
|
|
import logging
|
|
from datetime import datetime, timedelta
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class ChatActivityPolicy:
|
|
"""Policy for detecting chat activity and inactivity periods."""
|
|
|
|
def __init__(self, inactivity_threshold_minutes: int = 15):
|
|
"""
|
|
Initialize policy.
|
|
|
|
Args:
|
|
inactivity_threshold_minutes: Minutes of no chat before Hearthkeeper activates
|
|
"""
|
|
self.inactivity_threshold = timedelta(minutes=inactivity_threshold_minutes)
|
|
self.last_message_time: dict[str, datetime] = {}
|
|
|
|
def record_activity(self, session_id: str, occurred_at: datetime | None = None) -> None:
|
|
"""Record that chat activity occurred."""
|
|
self.last_message_time[session_id] = occurred_at or datetime.utcnow()
|
|
|
|
def last_activity_at(self, session_id: str) -> datetime | None:
|
|
"""Get the most recent chat activity time for a session."""
|
|
return self.last_message_time.get(session_id)
|
|
|
|
def clear_activity(self, session_id: str) -> None:
|
|
"""Stop tracking activity for an ended session."""
|
|
self.last_message_time.pop(session_id, None)
|
|
|
|
def minutes_since_activity(self, session_id: str) -> int:
|
|
"""Get minutes since last chat message."""
|
|
if session_id not in self.last_message_time:
|
|
return 0
|
|
elapsed = datetime.utcnow() - self.last_message_time[session_id]
|
|
return int(elapsed.total_seconds() / 60)
|
|
|
|
def should_hearthkeeper_prompt(self, session_id: str) -> bool:
|
|
"""Determine if Hearthkeeper should send a prompt."""
|
|
minutes = self.minutes_since_activity(session_id)
|
|
should = minutes >= self.inactivity_threshold.total_seconds() / 60
|
|
if should:
|
|
logger.info(f"Chat inactive for {minutes} minutes. Hearthkeeper may prompt.")
|
|
return should
|
|
|
|
|
|
class ResponseSuppression:
|
|
"""Policy for when the agent should NOT respond."""
|
|
|
|
# Suppress responses when chat is very active (humans are talking)
|
|
ACTIVE_CHAT_THRESHOLD = 5 # 5+ messages per minute = suppress
|
|
|
|
@staticmethod
|
|
def should_suppress_response(recent_message_count: int, time_window_minutes: int = 1) -> bool:
|
|
"""
|
|
Determine if agent should stay silent due to active chat.
|
|
|
|
Args:
|
|
recent_message_count: Number of messages in the time window
|
|
time_window_minutes: Time window in minutes
|
|
|
|
Returns:
|
|
True if agent should suppress response
|
|
"""
|
|
messages_per_minute = recent_message_count / time_window_minutes
|
|
suppress = messages_per_minute >= ResponseSuppression.ACTIVE_CHAT_THRESHOLD
|
|
|
|
if suppress:
|
|
logger.debug(f"Response suppressed due to active chat ({messages_per_minute:.1f} msg/min)")
|
|
|
|
return suppress
|
|
|
|
|
|
class SuspiciousContentPolicy:
|
|
"""Policy for detecting suspicious content."""
|
|
|
|
# Patterns that raise Warden alerts
|
|
SUSPICIOUS_KEYWORDS = [
|
|
"join our discord",
|
|
"discord.gg",
|
|
"grow your channel",
|
|
"easy money",
|
|
"limited offer",
|
|
]
|
|
|
|
@staticmethod
|
|
def is_suspicious(message: str) -> bool:
|
|
"""
|
|
Check if a message matches suspicious patterns.
|
|
|
|
Args:
|
|
message: Message content
|
|
|
|
Returns:
|
|
True if message is suspicious
|
|
"""
|
|
message_lower = message.lower()
|
|
|
|
for keyword in SuspiciousContentPolicy.SUSPICIOUS_KEYWORDS:
|
|
if keyword in message_lower:
|
|
logger.warning(f"Suspicious content detected: {keyword}")
|
|
return True
|
|
|
|
# Check for multiple links
|
|
link_count = message.count("http") + message.count("www")
|
|
if link_count > 1:
|
|
logger.warning("Suspicious content detected: multiple links")
|
|
return True
|
|
|
|
return False
|