diff --git a/gateway/platforms/base.py b/gateway/platforms/base.py index afb8767124aa..b8c012dc514f 100644 --- a/gateway/platforms/base.py +++ b/gateway/platforms/base.py @@ -1118,6 +1118,17 @@ async def edit_message( """ return SendResult(success=False, error="Not supported") + async def delete_message( + self, + chat_id: str, + message_id: str, + ) -> bool: + """ + Delete a previously sent message. Optional — platforms that don't + support deletion return False. + """ + return False + async def send_typing(self, chat_id: str, metadata=None) -> None: """ Send a typing indicator. diff --git a/gateway/platforms/telegram.py b/gateway/platforms/telegram.py index bec0d690a3be..4f6cde10ecd9 100644 --- a/gateway/platforms/telegram.py +++ b/gateway/platforms/telegram.py @@ -997,7 +997,9 @@ async def send( ] message_ids = [] - thread_id = self._metadata_thread_id(metadata) + thread_id = metadata.get("thread_id") if metadata else None + silent = metadata.get("silent", False) if metadata else False + no_preview = metadata.get("no_preview", False) if metadata else False try: from telegram.error import NetworkError as _NetErr @@ -1030,6 +1032,8 @@ async def send( parse_mode=ParseMode.MARKDOWN_V2, reply_to_message_id=reply_to_id, message_thread_id=effective_thread_id, + disable_notification=silent, + disable_web_page_preview=no_preview, **self._link_preview_kwargs(), ) except Exception as md_error: @@ -1043,6 +1047,8 @@ async def send( parse_mode=None, reply_to_message_id=reply_to_id, message_thread_id=effective_thread_id, + disable_notification=silent, + disable_web_page_preview=no_preview, **self._link_preview_kwargs(), ) else: @@ -1208,6 +1214,24 @@ async def edit_message( ) return SendResult(success=False, error=str(e)) + async def delete_message( + self, + chat_id: str, + message_id: str, + ) -> bool: + """Delete a previously sent Telegram message.""" + if not self._bot: + return False + try: + await self._bot.delete_message( + chat_id=int(chat_id), + message_id=int(message_id), + ) + return True + except Exception as e: + logger.debug("[%s] Failed to delete message %s: %s", self.name, message_id, e) + return False + async def send_update_prompt( self, chat_id: str, prompt: str, default: str = "", session_key: str = "", diff --git a/gateway/run.py b/gateway/run.py index c19303e61b19..3d3ba026bbd0 100644 --- a/gateway/run.py +++ b/gateway/run.py @@ -9226,6 +9226,7 @@ def _run_still_current() -> bool: last_tool = [None] # Mutable container for tracking in closure last_progress_msg = [None] # Track last message for dedup repeat_count = [0] # How many times the same message repeated + progress_msg_id_holder = [None] # Track progress message ID for deletion def progress_callback(event_type: str, tool_name: str = None, preview: str = None, args: dict = None, **kwargs): """Callback invoked by agent on tool lifecycle events.""" @@ -9303,7 +9304,12 @@ def progress_callback(event_type: str, tool_name: str = None, preview: str = Non _progress_thread_id = source.thread_id or event_message_id else: _progress_thread_id = source.thread_id - _progress_metadata = {"thread_id": _progress_thread_id} if _progress_thread_id else None + # Silent mode: tool progress messages are sent without notification + # so the user isn't spammed with sounds/vibrations for every tool call. + # Link preview disabled to keep progress messages compact. + _progress_metadata = {"silent": True, "no_preview": True} + if _progress_thread_id: + _progress_metadata["thread_id"] = _progress_thread_id async def send_progress_messages(): if not progress_queue: @@ -9398,6 +9404,7 @@ async def send_progress_messages(): result = await adapter.send(chat_id=source.chat_id, content=msg, metadata=_progress_metadata) if result.success and result.message_id: progress_msg_id = result.message_id + progress_msg_id_holder[0] = progress_msg_id _last_edit_ts = time.monotonic() @@ -10627,6 +10634,25 @@ async def _notify_long_running(): # Stop progress sender, interrupt monitor, and notification task if progress_task: progress_task.cancel() + + # Auto-delete progress message after agent finishes (if enabled) + _delete_progress = False + try: + _display_cfg = user_config.get("display", {}) + if isinstance(_display_cfg, dict): + _delete_progress = _display_cfg.get("delete_progress_messages", False) + except Exception: + pass + if _delete_progress and progress_msg_id_holder[0]: + try: + adapter = self.adapters.get(source.platform) + if adapter: + await adapter.delete_message( + chat_id=source.chat_id, + message_id=progress_msg_id_holder[0], + ) + except Exception: + pass interrupt_monitor.cancel() _notify_task.cancel() diff --git a/hermes_cli/config.py b/hermes_cli/config.py index a87d1d23c93e..8b58ca1db547 100644 --- a/hermes_cli/config.py +++ b/hermes_cli/config.py @@ -600,6 +600,7 @@ def _ensure_hermes_home_managed(home: Path): "tool_progress_overrides": {}, # DEPRECATED — use display.platforms instead "tool_preview_length": 0, # Max chars for tool call previews (0 = no limit, show full paths/commands) "platforms": {}, # Per-platform display overrides: {"telegram": {"tool_progress": "all"}, "slack": {"tool_progress": "off"}} + "delete_progress_messages": False, # Auto-delete progress messages after final response }, # Web dashboard settings