mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-04-25 00:51:20 +00:00
fix: extract MEDIA: tags from cron delivery before sending (#5598)
The cron scheduler delivery path passed raw text including MEDIA: tags to _send_to_platform(), so media attachments were delivered as literal text instead of actual files. The send function already supports media_files= but the cron path never used it. Now calls BasePlatformAdapter.extract_media() to split media paths from text before sending, matching the gateway's normal message flow. Salvaged from PR #4877 by robert-hoffmann.
This commit is contained in:
parent
5e88eb2ba0
commit
3d08a2fa1b
2 changed files with 33 additions and 2 deletions
|
|
@ -237,6 +237,10 @@ def _deliver_result(job: dict, content: str, adapters=None, loop=None) -> None:
|
||||||
else:
|
else:
|
||||||
delivery_content = content
|
delivery_content = content
|
||||||
|
|
||||||
|
# Extract MEDIA: tags so attachments are forwarded as files, not raw text
|
||||||
|
from gateway.platforms.base import BasePlatformAdapter
|
||||||
|
media_files, cleaned_delivery_content = BasePlatformAdapter.extract_media(delivery_content)
|
||||||
|
|
||||||
# Prefer the live adapter when the gateway is running — this supports E2EE
|
# Prefer the live adapter when the gateway is running — this supports E2EE
|
||||||
# rooms (e.g. Matrix) where the standalone HTTP path cannot encrypt.
|
# rooms (e.g. Matrix) where the standalone HTTP path cannot encrypt.
|
||||||
runtime_adapter = (adapters or {}).get(platform)
|
runtime_adapter = (adapters or {}).get(platform)
|
||||||
|
|
@ -264,7 +268,7 @@ def _deliver_result(job: dict, content: str, adapters=None, loop=None) -> None:
|
||||||
)
|
)
|
||||||
|
|
||||||
# Standalone path: run the async send in a fresh event loop (safe from any thread)
|
# Standalone path: run the async send in a fresh event loop (safe from any thread)
|
||||||
coro = _send_to_platform(platform, pconfig, chat_id, delivery_content, thread_id=thread_id)
|
coro = _send_to_platform(platform, pconfig, chat_id, cleaned_delivery_content, thread_id=thread_id, media_files=media_files)
|
||||||
try:
|
try:
|
||||||
result = asyncio.run(coro)
|
result = asyncio.run(coro)
|
||||||
except RuntimeError:
|
except RuntimeError:
|
||||||
|
|
@ -275,7 +279,7 @@ def _deliver_result(job: dict, content: str, adapters=None, loop=None) -> None:
|
||||||
coro.close()
|
coro.close()
|
||||||
import concurrent.futures
|
import concurrent.futures
|
||||||
with concurrent.futures.ThreadPoolExecutor(max_workers=1) as pool:
|
with concurrent.futures.ThreadPoolExecutor(max_workers=1) as pool:
|
||||||
future = pool.submit(asyncio.run, _send_to_platform(platform, pconfig, chat_id, delivery_content, thread_id=thread_id))
|
future = pool.submit(asyncio.run, _send_to_platform(platform, pconfig, chat_id, cleaned_delivery_content, thread_id=thread_id, media_files=media_files))
|
||||||
result = future.result(timeout=30)
|
result = future.result(timeout=30)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error("Job '%s': delivery to %s:%s failed: %s", job["id"], platform_name, chat_id, e)
|
logger.error("Job '%s': delivery to %s:%s failed: %s", job["id"], platform_name, chat_id, e)
|
||||||
|
|
|
||||||
|
|
@ -250,6 +250,33 @@ class TestDeliverResultWrapping:
|
||||||
assert "Cronjob Response" not in sent_content
|
assert "Cronjob Response" not in sent_content
|
||||||
assert "The agent cannot see" not in sent_content
|
assert "The agent cannot see" not in sent_content
|
||||||
|
|
||||||
|
def test_delivery_extracts_media_tags_before_send(self):
|
||||||
|
"""Cron delivery should pass MEDIA attachments separately to the send helper."""
|
||||||
|
from gateway.config import Platform
|
||||||
|
|
||||||
|
pconfig = MagicMock()
|
||||||
|
pconfig.enabled = True
|
||||||
|
mock_cfg = MagicMock()
|
||||||
|
mock_cfg.platforms = {Platform.TELEGRAM: pconfig}
|
||||||
|
|
||||||
|
with patch("gateway.config.load_gateway_config", return_value=mock_cfg), \
|
||||||
|
patch("tools.send_message_tool._send_to_platform", new=AsyncMock(return_value={"success": True})) as send_mock, \
|
||||||
|
patch("cron.scheduler.load_config", return_value={"cron": {"wrap_response": False}}):
|
||||||
|
job = {
|
||||||
|
"id": "voice-job",
|
||||||
|
"deliver": "origin",
|
||||||
|
"origin": {"platform": "telegram", "chat_id": "123"},
|
||||||
|
}
|
||||||
|
_deliver_result(job, "Title\nMEDIA:/tmp/test-voice.ogg")
|
||||||
|
|
||||||
|
send_mock.assert_called_once()
|
||||||
|
args, kwargs = send_mock.call_args
|
||||||
|
# Text content should have MEDIA: tag stripped
|
||||||
|
assert "MEDIA:" not in args[3]
|
||||||
|
assert "Title" in args[3]
|
||||||
|
# Media files should be forwarded separately
|
||||||
|
assert kwargs["media_files"] == [("/tmp/test-voice.ogg", False)]
|
||||||
|
|
||||||
def test_no_mirror_to_session_call(self):
|
def test_no_mirror_to_session_call(self):
|
||||||
"""Cron deliveries should NOT mirror into the gateway session."""
|
"""Cron deliveries should NOT mirror into the gateway session."""
|
||||||
from gateway.config import Platform
|
from gateway.config import Platform
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue