From 03d6e109bb0ebd84dace5e7ee289c2017f697f36 Mon Sep 17 00:00:00 2001 From: Cho Young-Hwi Date: Wed, 15 Apr 2026 11:55:26 +0900 Subject: [PATCH 1/2] [#501] Filter system/status messages from bridge forwarding Add _should_forward() filter to Discord bridge that skips: - join/leave messages (online, disconnected, timeout) - system sender messages - Pattern-matched noise (auto-recovered, Resuming agent conversation) - Duplicate consecutive messages within 60s window Bump Telegram bridge pin to 09bb557 which has the same filter. Fixes #501 Co-Authored-By: Claude Opus 4.6 (1M context) --- bridges/discord/discord_bridge.py | 61 +++++++++++++++++++++++++++++-- server/routes.js | 2 +- 2 files changed, 59 insertions(+), 4 deletions(-) diff --git a/bridges/discord/discord_bridge.py b/bridges/discord/discord_bridge.py index 77d1e66..751a0f0 100644 --- a/bridges/discord/discord_bridge.py +++ b/bridges/discord/discord_bridge.py @@ -18,6 +18,7 @@ import json import logging import os +import re import signal import sys import threading @@ -212,6 +213,60 @@ def start_heartbeat(url): return t +# --------------------------------------------------------------------------- +# #501: message filter — suppress AC housekeeping noise from bridge output +# --------------------------------------------------------------------------- + +_NOISE_PATTERNS = [ + re.compile(r"^.+ is online$"), + re.compile(r"disconnected \(timeout\)"), + re.compile(r"^.+ disconnected$"), + re.compile(r"auto-recovered"), + re.compile(r"Resuming agent conversation"), +] + +# Dedup guard: (sender, text) → timestamp of last forward +_last_forwarded: dict[tuple[str, str], float] = {} +_DEDUP_WINDOW = 60 # seconds + + +def _should_forward(msg: dict) -> bool: + """Return True if this AC message should be forwarded to Discord/Telegram.""" + sender = msg.get("sender", "") + text = msg.get("text", "") + msg_type = msg.get("type", "chat") + + # Skip join/leave system messages (online, disconnected, timeout) + if msg_type in ("join", "leave"): + return False + + # Skip system sender entirely + if sender == "system": + return False + + # Pattern-based filter for edge cases + for pat in _NOISE_PATTERNS: + if pat.search(text): + return False + + # Dedup: suppress identical (sender, text) within window + key = (sender, text) + now = time.time() + last = _last_forwarded.get(key) + if last is not None and now - last < _DEDUP_WINDOW: + return False + _last_forwarded[key] = now + + # Prune stale dedup entries periodically + if len(_last_forwarded) > 500: + cutoff = now - _DEDUP_WINDOW + stale = [k for k, t in _last_forwarded.items() if t < cutoff] + for k in stale: + del _last_forwarded[k] + + return True + + # --------------------------------------------------------------------------- # AC → Discord polling # --------------------------------------------------------------------------- @@ -305,12 +360,12 @@ def commit_cursor(): commit_cursor() continue - # Skip system auto-recovery messages - if sender == "system": + if not text: commit_cursor() continue - if not text: + # #501: skip system/status noise and dedup + if not _should_forward(msg): commit_cursor() continue diff --git a/server/routes.js b/server/routes.js index 96ae812..7fe9c3c 100644 --- a/server/routes.js +++ b/server/routes.js @@ -2301,7 +2301,7 @@ router.post("/api/rename", (req, res) => { const BRIDGE_DIR = path.join(CONFIG_DIR, "agentchattr-telegram"); // #444: pin agentchattr-telegram to a known commit (same pattern as // AGENTCHATTR_PIN in bin/quadwork.js for bcurts/agentchattr). -const AGENTCHATTR_TELEGRAM_PIN = "4a6b45f1794c612328b9d5ee6d6fcb3f77015abc"; +const AGENTCHATTR_TELEGRAM_PIN = "09bb557aa4a069687aa6f4d6d08a0df02ae87463"; function telegramPidFile(projectId) { return path.join(CONFIG_DIR, `tg-bridge-${projectId}.pid`); From 5b0a51b172066279da31a05a63a6bab4c59b84fd Mon Sep 17 00:00:00 2001 From: Cho Young-Hwi Date: Wed, 15 Apr 2026 11:57:46 +0900 Subject: [PATCH 2/2] [#501] Fix dedup: record after delivery, not before Split _should_forward() into pure check + _mark_forwarded() called after successful send. Prevents silent message loss on transient delivery failures. Updated Telegram pin to 918687a with the same fix. Co-Authored-By: Claude Opus 4.6 (1M context) --- bridges/discord/discord_bridge.py | 16 +++++++++++----- server/routes.js | 2 +- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/bridges/discord/discord_bridge.py b/bridges/discord/discord_bridge.py index 751a0f0..2f0ea7e 100644 --- a/bridges/discord/discord_bridge.py +++ b/bridges/discord/discord_bridge.py @@ -231,7 +231,7 @@ def start_heartbeat(url): def _should_forward(msg: dict) -> bool: - """Return True if this AC message should be forwarded to Discord/Telegram.""" + """Return True if this AC message should be forwarded. Pure check, no side effects.""" sender = msg.get("sender", "") text = msg.get("text", "") msg_type = msg.get("type", "chat") @@ -255,17 +255,22 @@ def _should_forward(msg: dict) -> bool: last = _last_forwarded.get(key) if last is not None and now - last < _DEDUP_WINDOW: return False - _last_forwarded[key] = now + + return True + + +def _mark_forwarded(msg: dict): + """Record that a message was successfully forwarded. Call after delivery.""" + key = (msg.get("sender", ""), msg.get("text", "")) + _last_forwarded[key] = time.time() # Prune stale dedup entries periodically if len(_last_forwarded) > 500: - cutoff = now - _DEDUP_WINDOW + cutoff = time.time() - _DEDUP_WINDOW stale = [k for k, t in _last_forwarded.items() if t < cutoff] for k in stale: del _last_forwarded[k] - return True - # --------------------------------------------------------------------------- # AC → Discord polling @@ -384,6 +389,7 @@ def commit_cursor(): # Only commit cursor + mark forwarded AFTER # successful Discord delivery. forwarded_ids.add(msg_id) + _mark_forwarded(msg) commit_cursor() # Trim the set if it grows too large if len(forwarded_ids) > MAX_FORWARDED: diff --git a/server/routes.js b/server/routes.js index 7fe9c3c..5a4c03b 100644 --- a/server/routes.js +++ b/server/routes.js @@ -2301,7 +2301,7 @@ router.post("/api/rename", (req, res) => { const BRIDGE_DIR = path.join(CONFIG_DIR, "agentchattr-telegram"); // #444: pin agentchattr-telegram to a known commit (same pattern as // AGENTCHATTR_PIN in bin/quadwork.js for bcurts/agentchattr). -const AGENTCHATTR_TELEGRAM_PIN = "09bb557aa4a069687aa6f4d6d08a0df02ae87463"; +const AGENTCHATTR_TELEGRAM_PIN = "918687a0704a49d5dec9ac4c52e1759bb565eb10"; function telegramPidFile(projectId) { return path.join(CONFIG_DIR, `tg-bridge-${projectId}.pid`);