diff --git a/cron/scheduler.py b/cron/scheduler.py index 4f85677d8..78c869fc4 100644 --- a/cron/scheduler.py +++ b/cron/scheduler.py @@ -196,7 +196,6 @@ def run_job(job: dict) -> tuple[bool, str, str, Optional[str]]: job_name = job["name"] prompt = job["prompt"] origin = _resolve_origin(job) - delivery_target = _resolve_delivery_target(job) logger.info("Running job '%s' (ID: %s)", job_name, job_id) logger.info("Prompt: %s", prompt[:100]) @@ -207,11 +206,6 @@ def run_job(job: dict) -> tuple[bool, str, str, Optional[str]]: os.environ["HERMES_SESSION_CHAT_ID"] = str(origin["chat_id"]) if origin.get("chat_name"): os.environ["HERMES_SESSION_CHAT_NAME"] = origin["chat_name"] - if delivery_target: - os.environ["HERMES_CRON_AUTO_DELIVER_PLATFORM"] = delivery_target["platform"] - os.environ["HERMES_CRON_AUTO_DELIVER_CHAT_ID"] = str(delivery_target["chat_id"]) - if delivery_target.get("thread_id") is not None: - os.environ["HERMES_CRON_AUTO_DELIVER_THREAD_ID"] = str(delivery_target["thread_id"]) try: # Re-read .env and config.yaml fresh every run so provider/key @@ -222,6 +216,13 @@ def run_job(job: dict) -> tuple[bool, str, str, Optional[str]]: except UnicodeDecodeError: load_dotenv(str(_hermes_home / ".env"), override=True, encoding="latin-1") + delivery_target = _resolve_delivery_target(job) + if delivery_target: + os.environ["HERMES_CRON_AUTO_DELIVER_PLATFORM"] = delivery_target["platform"] + os.environ["HERMES_CRON_AUTO_DELIVER_CHAT_ID"] = str(delivery_target["chat_id"]) + if delivery_target.get("thread_id") is not None: + os.environ["HERMES_CRON_AUTO_DELIVER_THREAD_ID"] = str(delivery_target["thread_id"]) + model = os.getenv("HERMES_MODEL") or "anthropic/claude-opus-4.6" # Load config.yaml for model, reasoning, prefill, toolsets, provider routing diff --git a/tests/cron/test_scheduler.py b/tests/cron/test_scheduler.py index 6af83f1e1..c38dbc440 100644 --- a/tests/cron/test_scheduler.py +++ b/tests/cron/test_scheduler.py @@ -2,7 +2,8 @@ import json import logging -from unittest.mock import patch, MagicMock +import os +from unittest.mock import AsyncMock, patch, MagicMock import pytest @@ -107,7 +108,7 @@ class TestDeliverResultMirrorLogging: mock_cfg.platforms = {Platform.TELEGRAM: pconfig} with patch("gateway.config.load_gateway_config", return_value=mock_cfg), \ - patch("asyncio.run", return_value=None), \ + patch("tools.send_message_tool._send_to_platform", new=AsyncMock(return_value={"success": True})), \ patch("gateway.mirror.mirror_to_session", side_effect=ConnectionError("network down")): job = { "id": "test-job", @@ -140,9 +141,8 @@ class TestDeliverResultMirrorLogging: } with patch("gateway.config.load_gateway_config", return_value=mock_cfg), \ - patch("tools.send_message_tool._send_to_platform", return_value={"success": True}) as send_mock, \ - patch("gateway.mirror.mirror_to_session") as mirror_mock, \ - patch("asyncio.run", side_effect=lambda coro: None): + patch("tools.send_message_tool._send_to_platform", new=AsyncMock(return_value={"success": True})) as send_mock, \ + patch("gateway.mirror.mirror_to_session") as mirror_mock: _deliver_result(job, "hello") send_mock.assert_called_once() @@ -196,6 +196,60 @@ class TestRunJobSessionPersistence: assert kwargs["session_id"].startswith("cron_test-job_") fake_db.close.assert_called_once() + def test_run_job_sets_auto_delivery_env_from_dotenv_home_channel(self, tmp_path, monkeypatch): + job = { + "id": "test-job", + "name": "test", + "prompt": "hello", + "deliver": "telegram", + } + fake_db = MagicMock() + seen = {} + + (tmp_path / ".env").write_text("TELEGRAM_HOME_CHANNEL=-2002\n") + monkeypatch.delenv("TELEGRAM_HOME_CHANNEL", raising=False) + monkeypatch.delenv("HERMES_CRON_AUTO_DELIVER_PLATFORM", raising=False) + monkeypatch.delenv("HERMES_CRON_AUTO_DELIVER_CHAT_ID", raising=False) + monkeypatch.delenv("HERMES_CRON_AUTO_DELIVER_THREAD_ID", raising=False) + + class FakeAgent: + def __init__(self, *args, **kwargs): + pass + + def run_conversation(self, *args, **kwargs): + seen["platform"] = os.getenv("HERMES_CRON_AUTO_DELIVER_PLATFORM") + seen["chat_id"] = os.getenv("HERMES_CRON_AUTO_DELIVER_CHAT_ID") + seen["thread_id"] = os.getenv("HERMES_CRON_AUTO_DELIVER_THREAD_ID") + return {"final_response": "ok"} + + with patch("cron.scheduler._hermes_home", tmp_path), \ + patch("hermes_state.SessionDB", return_value=fake_db), \ + patch( + "hermes_cli.runtime_provider.resolve_runtime_provider", + return_value={ + "api_key": "***", + "base_url": "https://example.invalid/v1", + "provider": "openrouter", + "api_mode": "chat_completions", + }, + ), \ + patch("run_agent.AIAgent", FakeAgent): + success, output, final_response, error = run_job(job) + + assert success is True + assert error is None + assert final_response == "ok" + assert "ok" in output + assert seen == { + "platform": "telegram", + "chat_id": "-2002", + "thread_id": None, + } + assert os.getenv("HERMES_CRON_AUTO_DELIVER_PLATFORM") is None + assert os.getenv("HERMES_CRON_AUTO_DELIVER_CHAT_ID") is None + assert os.getenv("HERMES_CRON_AUTO_DELIVER_THREAD_ID") is None + fake_db.close.assert_called_once() + class TestRunJobConfigLogging: """Verify that config.yaml parse failures are logged, not silently swallowed."""