diff --git a/gateway/platforms/base.py b/gateway/platforms/base.py index e1b677f12a1..6dbca9c7bc6 100644 --- a/gateway/platforms/base.py +++ b/gateway/platforms/base.py @@ -3736,6 +3736,7 @@ class BasePlatformAdapter(ABC): # Call the handler (this can take a while with tool calls) response = await self._message_handler(event) + is_ephemeral_response = isinstance(response, EphemeralReply) # Slash-command handlers may return an EphemeralReply sentinel to # request that their reply message auto-delete after a TTL (used @@ -3793,12 +3794,16 @@ class BasePlatformAdapter(ABC): if images: logger.info("[%s] extract_images found %d image(s) in response (%d chars)", self.name, len(images), len(response)) - # Auto-detect bare local file paths for native media delivery - # (helps small models that don't use MEDIA: syntax) - local_files, text_content = self.extract_local_files(text_content) - local_files = self.filter_local_delivery_paths(local_files) - if local_files: - logger.info("[%s] extract_local_files found %d file(s) in response", self.name, len(local_files)) + local_files = [] + if not is_ephemeral_response: + # Auto-detect bare local file paths for native media delivery + # (helps small models that don't use MEDIA: syntax). Skip + # system/command notices so config paths stay visible text + # instead of becoming native uploads. + local_files, text_content = self.extract_local_files(text_content) + local_files = self.filter_local_delivery_paths(local_files) + if local_files: + logger.info("[%s] extract_local_files found %d file(s) in response", self.name, len(local_files)) # Auto-TTS: if voice message, generate audio FIRST (before sending text) # Gated via ``_should_auto_tts_for_chat``: fires when the chat has diff --git a/hermes_cli/tips.py b/hermes_cli/tips.py index feebe4310a0..25324a75628 100644 --- a/hermes_cli/tips.py +++ b/hermes_cli/tips.py @@ -215,7 +215,7 @@ TIPS = [ # --- Context & Compression --- "Context auto-compresses when it reaches the threshold — memories are flushed and history summarized.", "The status bar turns yellow, then orange, then red as context fills up.", - "SOUL.md at ~/.hermes/SOUL.md is the agent's primary identity — customize it to shape behavior.", + "SOUL.md is the agent's primary identity file — customize it to shape behavior.", "Hermes loads project context from .hermes.md, AGENTS.md, CLAUDE.md, or .cursorrules (first match).", "Subdirectory AGENTS.md files are discovered progressively as the agent navigates into folders.", "Context files are capped at 20,000 characters with smart head/tail truncation.", @@ -273,7 +273,7 @@ TIPS = [ "Cron scripts live in ~/.hermes/scripts/ and run before the agent — perfect for data collection pipelines.", "prefill_messages_file in config.yaml injects few-shot examples into every API call, never saved to history.", "SOUL.md completely replaces the agent's default identity — rewrite it to make Hermes your own.", - "SOUL.md is auto-seeded with a default personality on first run. Edit ~/.hermes/SOUL.md to customize.", + "SOUL.md is auto-seeded with a default personality on first run. Edit it to customize.", "/compress allocates 60-70% of the summary budget to your topic and aggressively trims the rest.", "On second+ compression, the compressor updates the previous summary instead of starting from scratch.", "Before a gateway session reset, Hermes auto-flushes important facts to memory in the background.", @@ -430,7 +430,7 @@ TIPS = [ 'hermes -z "" is the purest one-shot: final answer on stdout, nothing else — ideal for piping in scripts.', 'hermes chat --pass-session-id injects the session ID into the system prompt so the agent can self-reference it.', 'hermes chat --image path/to/pic.png attaches a local image to a single -q query without a separate upload step.', - 'hermes chat --ignore-user-config skips ~/.hermes/config.yaml — reproducible bug reports and CI runs.', + 'hermes chat --ignore-user-config skips the active user config — reproducible bug reports and CI runs.', "hermes chat --source tool tags programmatic chats so they don't clutter hermes sessions list.", 'hermes dump --show-keys includes redacted API key fingerprints for deeper support debugging.', 'hermes sessions rename "new title" renames any past session; hermes sessions delete removes one.', @@ -485,4 +485,3 @@ def get_random_tip(exclude_recent: int = 0) -> str: """ return random.choice(TIPS) - diff --git a/tests/gateway/test_ephemeral_reply.py b/tests/gateway/test_ephemeral_reply.py index 41565e163b0..61b70748e16 100644 --- a/tests/gateway/test_ephemeral_reply.py +++ b/tests/gateway/test_ephemeral_reply.py @@ -268,6 +268,37 @@ async def test_process_message_unwraps_ephemeral_before_send(): assert ("42", "sent-1") in adapter.deleted +@pytest.mark.asyncio +async def test_process_message_ephemeral_reply_does_not_auto_upload_bare_paths(tmp_path): + """Tips/system notices may mention local paths; they must remain text.""" + adapter = _delete_adapter() + adapter._send_with_retry = AsyncMock( + return_value=SendResult(success=True, message_id="sent-1") + ) + adapter.send_document = AsyncMock( + return_value=SendResult(success=True, message_id="doc-1") + ) + config_path = tmp_path / "config.yaml" + config_path.write_text("model:\n provider: test\n", encoding="utf-8") + reply_text = f"Tip: hermes chat --ignore-user-config skips {config_path}" + + async def _handler(evt): + return EphemeralReply(reply_text, ttl_seconds=0) + + adapter.set_message_handler(_handler) + + event = _make_event(text="/new") + session_key = "agent:main:telegram:private:42" + with patch("gateway.platforms.base.asyncio.sleep", AsyncMock()), patch.object( + adapter, "_keep_typing", new=AsyncMock() + ): + await adapter._process_message_background(event, session_key) + + adapter._send_with_retry.assert_called_once() + assert adapter._send_with_retry.call_args.kwargs["content"] == reply_text + adapter.send_document.assert_not_awaited() + + @pytest.mark.asyncio async def test_process_message_incapable_platform_does_not_schedule_delete(): adapter = _no_delete_adapter()