fix: resolve cron auto-delivery target after dotenv reload
Resolve cron auto-delivery targets after reloading .env so bare-platform deliveries pick up home-channel settings before the agent run. Add a regression test for the dotenv-backed home-channel path and clean up scheduler tests that were leaking un-awaited send coroutines.
This commit is contained in:
parent
7b140b31e6
commit
0fd0eb93e8
2 changed files with 66 additions and 11 deletions
|
|
@ -196,7 +196,6 @@ def run_job(job: dict) -> tuple[bool, str, str, Optional[str]]:
|
||||||
job_name = job["name"]
|
job_name = job["name"]
|
||||||
prompt = job["prompt"]
|
prompt = job["prompt"]
|
||||||
origin = _resolve_origin(job)
|
origin = _resolve_origin(job)
|
||||||
delivery_target = _resolve_delivery_target(job)
|
|
||||||
|
|
||||||
logger.info("Running job '%s' (ID: %s)", job_name, job_id)
|
logger.info("Running job '%s' (ID: %s)", job_name, job_id)
|
||||||
logger.info("Prompt: %s", prompt[:100])
|
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"])
|
os.environ["HERMES_SESSION_CHAT_ID"] = str(origin["chat_id"])
|
||||||
if origin.get("chat_name"):
|
if origin.get("chat_name"):
|
||||||
os.environ["HERMES_SESSION_CHAT_NAME"] = origin["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:
|
try:
|
||||||
# Re-read .env and config.yaml fresh every run so provider/key
|
# 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:
|
except UnicodeDecodeError:
|
||||||
load_dotenv(str(_hermes_home / ".env"), override=True, encoding="latin-1")
|
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"
|
model = os.getenv("HERMES_MODEL") or "anthropic/claude-opus-4.6"
|
||||||
|
|
||||||
# Load config.yaml for model, reasoning, prefill, toolsets, provider routing
|
# Load config.yaml for model, reasoning, prefill, toolsets, provider routing
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,8 @@
|
||||||
|
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
from unittest.mock import patch, MagicMock
|
import os
|
||||||
|
from unittest.mock import AsyncMock, patch, MagicMock
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
|
@ -107,7 +108,7 @@ class TestDeliverResultMirrorLogging:
|
||||||
mock_cfg.platforms = {Platform.TELEGRAM: pconfig}
|
mock_cfg.platforms = {Platform.TELEGRAM: pconfig}
|
||||||
|
|
||||||
with patch("gateway.config.load_gateway_config", return_value=mock_cfg), \
|
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")):
|
patch("gateway.mirror.mirror_to_session", side_effect=ConnectionError("network down")):
|
||||||
job = {
|
job = {
|
||||||
"id": "test-job",
|
"id": "test-job",
|
||||||
|
|
@ -140,9 +141,8 @@ class TestDeliverResultMirrorLogging:
|
||||||
}
|
}
|
||||||
|
|
||||||
with patch("gateway.config.load_gateway_config", return_value=mock_cfg), \
|
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("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, \
|
patch("gateway.mirror.mirror_to_session") as mirror_mock:
|
||||||
patch("asyncio.run", side_effect=lambda coro: None):
|
|
||||||
_deliver_result(job, "hello")
|
_deliver_result(job, "hello")
|
||||||
|
|
||||||
send_mock.assert_called_once()
|
send_mock.assert_called_once()
|
||||||
|
|
@ -196,6 +196,60 @@ class TestRunJobSessionPersistence:
|
||||||
assert kwargs["session_id"].startswith("cron_test-job_")
|
assert kwargs["session_id"].startswith("cron_test-job_")
|
||||||
fake_db.close.assert_called_once()
|
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:
|
class TestRunJobConfigLogging:
|
||||||
"""Verify that config.yaml parse failures are logged, not silently swallowed."""
|
"""Verify that config.yaml parse failures are logged, not silently swallowed."""
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue