mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-06-11 08:42:11 +00:00
fix(gateway): stop system tips from auto-uploading local files
This commit is contained in:
parent
b1a25404b6
commit
bdfba45247
3 changed files with 45 additions and 10 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 <focus topic> 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 "<prompt>" 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 <ID> "new title" renames any past session; hermes sessions delete <ID> removes one.',
|
||||
|
|
@ -485,4 +485,3 @@ def get_random_tip(exclude_recent: int = 0) -> str:
|
|||
"""
|
||||
return random.choice(TIPS)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue