feat: atomic extract-and-connect + stale PR monitor + response audit #4

Merged
m3taversal merged 70 commits from epimetheus/atomic-connect-and-stale-monitor into main 2026-03-30 11:03:35 +00:00
Showing only changes of commit 251caa3695 - Show all commits

View file

@ -430,18 +430,29 @@ async def handle_message(update: Update, context: ContextTypes.DEFAULT_TYPE):
if len(text) < MIN_MESSAGE_LENGTH:
return
# Conversation window: track history silently, don't auto-respond
# (Ganymede: window = context, not trigger. Reply-to is the only follow-up trigger.)
# Conversation window behavior depends on chat type (Rio: DMs vs groups)
# DMs: auto-respond (always 1-on-1, no false positives)
# Groups: silent context only (reply-to is the only follow-up trigger)
user = msg.from_user
is_dm = msg.chat.type == "private"
if user:
key = (msg.chat_id, user.id)
if key in unanswered_count:
unanswered_count[key] += 1
# Record message in conversation history for context (silent — no response)
history = conversation_history.setdefault(key, [])
history.append({"user": text[:500], "bot": ""})
if len(history) > MAX_HISTORY:
history.pop(0)
if is_dm and unanswered_count[key] < CONVERSATION_WINDOW:
# DM: auto-respond — conversation window fires
logger.info("DM conversation window: @%s msg %d/%d",
user.username or "?", unanswered_count[key], CONVERSATION_WINDOW)
await handle_tagged(update, context)
return
else:
# Group: silent context tracking only
history = conversation_history.setdefault(key, [])
history.append({"user": text[:500], "bot": ""})
if len(history) > MAX_HISTORY:
history.pop(0)
# Expire window after CONVERSATION_WINDOW unanswered messages
if unanswered_count[key] >= CONVERSATION_WINDOW:
del unanswered_count[key]