refactor: split bot.py into runtime/dispatch/worker modules; add retry scheduler, route TTL, ack tracking, follow-up screener, structured event log, and test suite

This commit is contained in:
swaits committed 2026-04-16 13:47:17 -06:00
1 parent 9e6f171c5d
commit 228432f9e3
24 files changed
+3531 -434

No files matched your search

+63
View File
@@ -0,0 +1,63 @@
"""Track recent bot activity per channel, for follow-up detection.
Used by :class:`orac.dispatch.RxRouter` to decide whether a non-triggered
channel message might be a follow-up to Orac (and therefore worth screening
with a cheap classifier) or routine chatter between other participants
(ignore without any API call).
All state is in-memory and thread-safe — state is intentionally NOT persisted
across restarts so a bot restart gives everyone a fresh conversational slate.
"""
from __future__ import annotations
import threading
import time
_lock = threading.Lock()
_last_interaction: dict[str, float] = {}
_last_screener: dict[str, float] = {}
def touch(channel: str) -> None:
"""Mark a channel as having just had Orac activity (trigger reply, screener
hit, or an ADVERT from a peer we were just talking to)."""
now = time.monotonic()
with _lock:
_last_interaction[channel] = now
def was_recent(channel: str, window_s: float) -> bool:
"""True if the bot was last active in *channel* within *window_s* seconds."""
with _lock:
ts = _last_interaction.get(channel)
return ts is not None and (time.monotonic() - ts) < window_s
def last_interaction_age(channel: str) -> float | None:
"""Seconds since last bot activity in *channel*, or None if never seen."""
with _lock:
ts = _last_interaction.get(channel)
return (time.monotonic() - ts) if ts is not None else None
def screener_ok(channel: str, min_interval_s: float) -> bool:
"""Per-channel rate limit for screener API calls.
Returns True if a screener call may fire now, and records the intent. Returns
False if the last screener call for this channel was within ``min_interval_s``.
"""
now = time.monotonic()
with _lock:
last = _last_screener.get(channel)
if last is not None and (now - last) < min_interval_s:
return False
_last_screener[channel] = now
return True
def reset() -> None:
"""Test helper: clear all state."""
with _lock:
_last_interaction.clear()
_last_screener.clear()