mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-05-18 04:41:56 +00:00
feat(gateway): add Telegram notification mode to suppress intermediate push notifications
Add a configurable notifications mode for the Telegram platform adapter that controls which messages trigger push notifications. - display.platforms.telegram.notifications: "all" (default) | "important" - HERMES_TELEGRAM_NOTIFICATIONS env var override - In "important" mode, all sends use disable_notification=True except: - Approvals (send_exec_approval) and slash confirmations - Final response messages (metadata["notify"]=True) - Zero overhead in default "all" mode - Zero impact on non-Telegram platforms Closes #22771
This commit is contained in:
parent
ca13993217
commit
236f3b0521
4 changed files with 69 additions and 3 deletions
|
|
@ -2950,6 +2950,18 @@ class BasePlatformAdapter(ABC):
|
||||||
if text_content:
|
if text_content:
|
||||||
logger.info("[%s] Sending response (%d chars) to %s", self.name, len(text_content), event.source.chat_id)
|
logger.info("[%s] Sending response (%d chars) to %s", self.name, len(text_content), event.source.chat_id)
|
||||||
_reply_anchor = _reply_anchor_for_event(event)
|
_reply_anchor = _reply_anchor_for_event(event)
|
||||||
|
# Mark final response messages for notification delivery.
|
||||||
|
# Platform adapters that support per-message notification
|
||||||
|
# control (e.g. Telegram's disable_notification) use this
|
||||||
|
# flag to override silent-mode and ensure the final
|
||||||
|
# response triggers a push notification.
|
||||||
|
# Clone to avoid mutating the metadata shared with the
|
||||||
|
# typing-indicator task (which must remain unmarked).
|
||||||
|
if _thread_metadata is not None:
|
||||||
|
_thread_metadata = dict(_thread_metadata)
|
||||||
|
_thread_metadata["notify"] = True
|
||||||
|
else:
|
||||||
|
_thread_metadata = {"notify": True}
|
||||||
result = await self._send_with_retry(
|
result = await self._send_with_retry(
|
||||||
chat_id=event.source.chat_id,
|
chat_id=event.source.chat_id,
|
||||||
content=text_content,
|
content=text_content,
|
||||||
|
|
|
||||||
|
|
@ -319,6 +319,27 @@ class TelegramAdapter(BasePlatformAdapter):
|
||||||
# Slash-confirm button state: confirm_id → session_key (for /reload-mcp
|
# Slash-confirm button state: confirm_id → session_key (for /reload-mcp
|
||||||
# and any other slash-confirm prompts; see GatewayRunner._request_slash_confirm).
|
# and any other slash-confirm prompts; see GatewayRunner._request_slash_confirm).
|
||||||
self._slash_confirm_state: Dict[str, str] = {}
|
self._slash_confirm_state: Dict[str, str] = {}
|
||||||
|
# Notification mode for message sends.
|
||||||
|
# "all" — every message triggers a push notification (default).
|
||||||
|
# "important" — only final responses, approvals, and slash confirmations
|
||||||
|
# trigger notifications; tool progress, streaming, status
|
||||||
|
# messages are delivered silently via disable_notification.
|
||||||
|
self._notifications_mode: str = "all"
|
||||||
|
|
||||||
|
def _notification_kwargs(
|
||||||
|
self, metadata: Optional[Dict[str, Any]]
|
||||||
|
) -> Dict[str, Any]:
|
||||||
|
"""Return disable_notification kwargs when the adapter is in silent mode.
|
||||||
|
|
||||||
|
In "important" mode, all message sends are silently delivered
|
||||||
|
(disable_notification=True) unless the caller explicitly requests a
|
||||||
|
notification by setting ``metadata["notify"] = True``.
|
||||||
|
"""
|
||||||
|
if getattr(self, "_notifications_mode", "all") != "important":
|
||||||
|
return {}
|
||||||
|
if (metadata or {}).get("notify"):
|
||||||
|
return {}
|
||||||
|
return {"disable_notification": True}
|
||||||
|
|
||||||
def _is_callback_user_authorized(
|
def _is_callback_user_authorized(
|
||||||
self,
|
self,
|
||||||
|
|
@ -1414,6 +1435,7 @@ class TelegramAdapter(BasePlatformAdapter):
|
||||||
reply_to_message_id=reply_to_id,
|
reply_to_message_id=reply_to_id,
|
||||||
**thread_kwargs,
|
**thread_kwargs,
|
||||||
**self._link_preview_kwargs(),
|
**self._link_preview_kwargs(),
|
||||||
|
**self._notification_kwargs(metadata),
|
||||||
)
|
)
|
||||||
except Exception as md_error:
|
except Exception as md_error:
|
||||||
# Markdown parsing failed, try plain text
|
# Markdown parsing failed, try plain text
|
||||||
|
|
@ -1427,6 +1449,7 @@ class TelegramAdapter(BasePlatformAdapter):
|
||||||
reply_to_message_id=reply_to_id,
|
reply_to_message_id=reply_to_id,
|
||||||
**thread_kwargs,
|
**thread_kwargs,
|
||||||
**self._link_preview_kwargs(),
|
**self._link_preview_kwargs(),
|
||||||
|
**self._notification_kwargs(metadata),
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
raise
|
raise
|
||||||
|
|
@ -2374,6 +2397,7 @@ class TelegramAdapter(BasePlatformAdapter):
|
||||||
"caption": caption[:1024] if caption else None,
|
"caption": caption[:1024] if caption else None,
|
||||||
"reply_to_message_id": reply_to_id,
|
"reply_to_message_id": reply_to_id,
|
||||||
**voice_thread_kwargs,
|
**voice_thread_kwargs,
|
||||||
|
**self._notification_kwargs(metadata),
|
||||||
},
|
},
|
||||||
metadata,
|
metadata,
|
||||||
reply_to_id,
|
reply_to_id,
|
||||||
|
|
@ -2398,6 +2422,7 @@ class TelegramAdapter(BasePlatformAdapter):
|
||||||
"caption": caption[:1024] if caption else None,
|
"caption": caption[:1024] if caption else None,
|
||||||
"reply_to_message_id": reply_to_id,
|
"reply_to_message_id": reply_to_id,
|
||||||
**audio_thread_kwargs,
|
**audio_thread_kwargs,
|
||||||
|
**self._notification_kwargs(metadata),
|
||||||
},
|
},
|
||||||
metadata,
|
metadata,
|
||||||
reply_to_id,
|
reply_to_id,
|
||||||
|
|
@ -2534,6 +2559,7 @@ class TelegramAdapter(BasePlatformAdapter):
|
||||||
"media": media,
|
"media": media,
|
||||||
"reply_to_message_id": reply_to_id,
|
"reply_to_message_id": reply_to_id,
|
||||||
**thread_kwargs,
|
**thread_kwargs,
|
||||||
|
**self._notification_kwargs(metadata),
|
||||||
},
|
},
|
||||||
metadata,
|
metadata,
|
||||||
reply_to_id,
|
reply_to_id,
|
||||||
|
|
@ -2591,6 +2617,7 @@ class TelegramAdapter(BasePlatformAdapter):
|
||||||
"caption": caption[:1024] if caption else None,
|
"caption": caption[:1024] if caption else None,
|
||||||
"reply_to_message_id": reply_to_id,
|
"reply_to_message_id": reply_to_id,
|
||||||
**thread_kwargs,
|
**thread_kwargs,
|
||||||
|
**self._notification_kwargs(metadata),
|
||||||
},
|
},
|
||||||
metadata,
|
metadata,
|
||||||
reply_to_id,
|
reply_to_id,
|
||||||
|
|
@ -2686,6 +2713,7 @@ class TelegramAdapter(BasePlatformAdapter):
|
||||||
"caption": caption[:1024] if caption else None,
|
"caption": caption[:1024] if caption else None,
|
||||||
"reply_to_message_id": reply_to_id,
|
"reply_to_message_id": reply_to_id,
|
||||||
**thread_kwargs,
|
**thread_kwargs,
|
||||||
|
**self._notification_kwargs(metadata),
|
||||||
},
|
},
|
||||||
metadata,
|
metadata,
|
||||||
reply_to_id,
|
reply_to_id,
|
||||||
|
|
@ -2731,6 +2759,7 @@ class TelegramAdapter(BasePlatformAdapter):
|
||||||
"caption": caption[:1024] if caption else None,
|
"caption": caption[:1024] if caption else None,
|
||||||
"reply_to_message_id": reply_to_id,
|
"reply_to_message_id": reply_to_id,
|
||||||
**thread_kwargs,
|
**thread_kwargs,
|
||||||
|
**self._notification_kwargs(metadata),
|
||||||
},
|
},
|
||||||
metadata,
|
metadata,
|
||||||
reply_to_id,
|
reply_to_id,
|
||||||
|
|
@ -2781,6 +2810,7 @@ class TelegramAdapter(BasePlatformAdapter):
|
||||||
"caption": caption[:1024] if caption else None,
|
"caption": caption[:1024] if caption else None,
|
||||||
"reply_to_message_id": reply_to_id,
|
"reply_to_message_id": reply_to_id,
|
||||||
**photo_thread_kwargs,
|
**photo_thread_kwargs,
|
||||||
|
**self._notification_kwargs(metadata),
|
||||||
},
|
},
|
||||||
metadata,
|
metadata,
|
||||||
reply_to_id,
|
reply_to_id,
|
||||||
|
|
@ -2816,6 +2846,7 @@ class TelegramAdapter(BasePlatformAdapter):
|
||||||
"caption": caption[:1024] if caption else None,
|
"caption": caption[:1024] if caption else None,
|
||||||
"reply_to_message_id": reply_to_id,
|
"reply_to_message_id": reply_to_id,
|
||||||
**upload_thread_kwargs,
|
**upload_thread_kwargs,
|
||||||
|
**self._notification_kwargs(metadata),
|
||||||
},
|
},
|
||||||
metadata,
|
metadata,
|
||||||
reply_to_id,
|
reply_to_id,
|
||||||
|
|
@ -2861,6 +2892,7 @@ class TelegramAdapter(BasePlatformAdapter):
|
||||||
"caption": caption[:1024] if caption else None,
|
"caption": caption[:1024] if caption else None,
|
||||||
"reply_to_message_id": reply_to_id,
|
"reply_to_message_id": reply_to_id,
|
||||||
**animation_thread_kwargs,
|
**animation_thread_kwargs,
|
||||||
|
**self._notification_kwargs(metadata),
|
||||||
},
|
},
|
||||||
metadata,
|
metadata,
|
||||||
reply_to_id,
|
reply_to_id,
|
||||||
|
|
|
||||||
|
|
@ -4649,7 +4649,29 @@ class GatewayRunner:
|
||||||
if not check_telegram_requirements():
|
if not check_telegram_requirements():
|
||||||
logger.warning("Telegram: python-telegram-bot not installed")
|
logger.warning("Telegram: python-telegram-bot not installed")
|
||||||
return None
|
return None
|
||||||
return TelegramAdapter(config)
|
adapter = TelegramAdapter(config)
|
||||||
|
# Apply Telegram notification mode from config. Controls whether
|
||||||
|
# intermediate messages (tool progress, streaming, status) trigger
|
||||||
|
# push notifications. Supports ENV override for quick testing.
|
||||||
|
_notify_mode = os.getenv("HERMES_TELEGRAM_NOTIFICATIONS", "")
|
||||||
|
if not _notify_mode:
|
||||||
|
try:
|
||||||
|
_gw_cfg = _load_gateway_config()
|
||||||
|
_raw = cfg_get(_gw_cfg, "display", "platforms", "telegram", "notifications")
|
||||||
|
if _raw not in (None, ""):
|
||||||
|
_notify_mode = str(_raw).strip().lower()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
_notify_mode = _notify_mode or "all"
|
||||||
|
if _notify_mode not in ("all", "important"):
|
||||||
|
logger.warning(
|
||||||
|
"Unknown telegram notifications mode '%s', "
|
||||||
|
"defaulting to 'all' (valid: all, important)",
|
||||||
|
_notify_mode,
|
||||||
|
)
|
||||||
|
_notify_mode = "all"
|
||||||
|
adapter._notifications_mode = _notify_mode
|
||||||
|
return adapter
|
||||||
|
|
||||||
elif platform == Platform.DISCORD:
|
elif platform == Platform.DISCORD:
|
||||||
from gateway.platforms.discord import DiscordAdapter, check_discord_requirements
|
from gateway.platforms.discord import DiscordAdapter, check_discord_requirements
|
||||||
|
|
|
||||||
|
|
@ -130,8 +130,8 @@ class TestBasePlatformTopicSessions:
|
||||||
{
|
{
|
||||||
"chat_id": "-1001",
|
"chat_id": "-1001",
|
||||||
"content": "ack",
|
"content": "ack",
|
||||||
"reply_to": "1",
|
"reply_to": None,
|
||||||
"metadata": {"thread_id": "17585"},
|
"metadata": {"thread_id": "17585", "notify": True},
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
assert typing_calls == [
|
assert typing_calls == [
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue