feat(cron): add cron.wrap_response config to disable delivery wrapping (#3807)
Adds a config option to suppress the header/footer text that wraps cron job responses when delivered to messaging platforms. Set cron.wrap_response: false in config.yaml for clean output without the 'Cronjob Response: <name>' header and 'The agent cannot see this message' footer. Default is true (preserves current behavior).
This commit is contained in:
@@ -26,6 +26,7 @@ except ImportError:
|
|||||||
msvcrt = None
|
msvcrt = None
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from hermes_constants import get_hermes_home
|
from hermes_constants import get_hermes_home
|
||||||
|
from hermes_cli.config import load_config
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
from hermes_time import now as _hermes_now
|
from hermes_time import now as _hermes_now
|
||||||
@@ -164,18 +165,29 @@ def _deliver_result(job: dict, content: str) -> None:
|
|||||||
logger.warning("Job '%s': platform '%s' not configured/enabled", job["id"], platform_name)
|
logger.warning("Job '%s': platform '%s' not configured/enabled", job["id"], platform_name)
|
||||||
return
|
return
|
||||||
|
|
||||||
# Wrap the content so the user knows this is a cron delivery and that
|
# Optionally wrap the content with a header/footer so the user knows this
|
||||||
# the interactive agent has no visibility into it.
|
# is a cron delivery. Wrapping is on by default; set cron.wrap_response: false
|
||||||
|
# in config.yaml for clean output.
|
||||||
|
wrap_response = True
|
||||||
|
try:
|
||||||
|
user_cfg = load_config()
|
||||||
|
wrap_response = user_cfg.get("cron", {}).get("wrap_response", True)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if wrap_response:
|
||||||
task_name = job.get("name", job["id"])
|
task_name = job.get("name", job["id"])
|
||||||
wrapped = (
|
delivery_content = (
|
||||||
f"Cronjob Response: {task_name}\n"
|
f"Cronjob Response: {task_name}\n"
|
||||||
f"-------------\n\n"
|
f"-------------\n\n"
|
||||||
f"{content}\n\n"
|
f"{content}\n\n"
|
||||||
f"Note: The agent cannot see this message, and therefore cannot respond to it."
|
f"Note: The agent cannot see this message, and therefore cannot respond to it."
|
||||||
)
|
)
|
||||||
|
else:
|
||||||
|
delivery_content = content
|
||||||
|
|
||||||
# Run the async send in a fresh event loop (safe from any thread)
|
# Run the async send in a fresh event loop (safe from any thread)
|
||||||
coro = _send_to_platform(platform, pconfig, chat_id, wrapped, thread_id=thread_id)
|
coro = _send_to_platform(platform, pconfig, chat_id, delivery_content, thread_id=thread_id)
|
||||||
try:
|
try:
|
||||||
result = asyncio.run(coro)
|
result = asyncio.run(coro)
|
||||||
except RuntimeError:
|
except RuntimeError:
|
||||||
@@ -186,7 +198,7 @@ def _deliver_result(job: dict, content: str) -> 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, wrapped, thread_id=thread_id))
|
future = pool.submit(asyncio.run, _send_to_platform(platform, pconfig, chat_id, delivery_content, thread_id=thread_id))
|
||||||
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)
|
||||||
|
|||||||
@@ -429,6 +429,12 @@ DEFAULT_CONFIG = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
"cron": {
|
||||||
|
# Wrap delivered cron responses with a header (task name) and footer
|
||||||
|
# ("The agent cannot see this message"). Set to false for clean output.
|
||||||
|
"wrap_response": True,
|
||||||
|
},
|
||||||
|
|
||||||
# Config schema version - bump this when adding new required fields
|
# Config schema version - bump this when adding new required fields
|
||||||
"_config_version": 10,
|
"_config_version": 10,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -167,6 +167,32 @@ class TestDeliverResultWrapping:
|
|||||||
sent_content = send_mock.call_args.kwargs.get("content") or send_mock.call_args[0][-1]
|
sent_content = send_mock.call_args.kwargs.get("content") or send_mock.call_args[0][-1]
|
||||||
assert "Cronjob Response: abc-123" in sent_content
|
assert "Cronjob Response: abc-123" in sent_content
|
||||||
|
|
||||||
|
def test_delivery_skips_wrapping_when_config_disabled(self):
|
||||||
|
"""When cron.wrap_response is false, deliver raw content without header/footer."""
|
||||||
|
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": "test-job",
|
||||||
|
"name": "daily-report",
|
||||||
|
"deliver": "origin",
|
||||||
|
"origin": {"platform": "telegram", "chat_id": "123"},
|
||||||
|
}
|
||||||
|
_deliver_result(job, "Clean output only.")
|
||||||
|
|
||||||
|
send_mock.assert_called_once()
|
||||||
|
sent_content = send_mock.call_args.kwargs.get("content") or send_mock.call_args[0][-1]
|
||||||
|
assert sent_content == "Clean output only."
|
||||||
|
assert "Cronjob Response" not in sent_content
|
||||||
|
assert "The agent cannot see" not in sent_content
|
||||||
|
|
||||||
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
|
||||||
|
|||||||
Reference in New Issue
Block a user