mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-05-20 13:32:30 +00:00
fix(cron): route Telegram cron deliveries to a dedicated topic via TELEGRAM_CRON_THREAD_ID
When Telegram topic mode is enabled, cron messages delivered to the bot's root DM (TELEGRAM_HOME_CHANNEL without a thread id) land in the system lobby — replies there are rebuffed with the lobby reminder and reply_to_message_id is dropped, so users cannot interact with the cron output (#24409). Add an optional TELEGRAM_CRON_THREAD_ID env var that overrides TELEGRAM_HOME_CHANNEL_THREAD_ID for cron deliveries only. Operators can create a "Cron" forum topic in the DM, point this var at its thread id, and replies to cron messages will land in that topic's existing session instead of the lobby. The home-channel thread id (used elsewhere, e.g. restart notifications) is unchanged, and explicit deliver="telegram:chat:thread" targets continue to win over the env var. Per the reporter's clarification on 2026-05-13, option (a) (cron-side route to a dedicated topic + config knob) was chosen. Fixes #24409
This commit is contained in:
@@ -339,6 +339,7 @@ BROWSER_INACTIVITY_TIMEOUT=120
|
||||
# TELEGRAM_ALLOWED_USERS= # Comma-separated user IDs
|
||||
# TELEGRAM_HOME_CHANNEL= # Default chat for cron delivery
|
||||
# TELEGRAM_HOME_CHANNEL_NAME= # Display name for home channel
|
||||
# TELEGRAM_CRON_THREAD_ID= # Forum topic ID for cron deliveries; overrides TELEGRAM_HOME_CHANNEL_THREAD_ID for cron so replies work in topic mode
|
||||
|
||||
# Webhook mode (optional — for cloud deployments like Fly.io/Railway)
|
||||
# Default is long polling. Setting TELEGRAM_WEBHOOK_URL switches to webhook mode.
|
||||
|
||||
+14
-1
@@ -292,10 +292,23 @@ def _get_home_target_chat_id(platform_name: str) -> str:
|
||||
|
||||
|
||||
def _get_home_target_thread_id(platform_name: str) -> Optional[str]:
|
||||
"""Return the optional thread/topic ID for a platform home target."""
|
||||
"""Return the optional thread/topic ID for a platform home target.
|
||||
|
||||
Telegram-only override: ``TELEGRAM_CRON_THREAD_ID`` takes precedence over
|
||||
``TELEGRAM_HOME_CHANNEL_THREAD_ID`` for cron delivery. When topic mode is
|
||||
enabled, deliveries that land in the root DM (thread_id unset) end up in
|
||||
the system-only lobby where the user cannot reply — the gateway returns
|
||||
the lobby reminder and drops ``reply_to_message_id`` (#24409). Pointing
|
||||
cron at a dedicated topic via this env var lets replies work as expected
|
||||
without changing the lobby invariant.
|
||||
"""
|
||||
env_var = _resolve_home_env_var(platform_name)
|
||||
if not env_var:
|
||||
return None
|
||||
if platform_name.lower() == "telegram":
|
||||
cron_thread = os.getenv("TELEGRAM_CRON_THREAD_ID", "").strip()
|
||||
if cron_thread:
|
||||
return cron_thread
|
||||
value = os.getenv(f"{env_var}_THREAD_ID", "").strip()
|
||||
if not value:
|
||||
legacy = _LEGACY_HOME_TARGET_ENV_VARS.get(env_var)
|
||||
|
||||
@@ -243,6 +243,7 @@ _HERMES_BEHAVIORAL_VARS = frozenset({
|
||||
"TELEGRAM_HOME_CHANNEL",
|
||||
"TELEGRAM_HOME_CHANNEL_THREAD_ID",
|
||||
"TELEGRAM_HOME_CHANNEL_NAME",
|
||||
"TELEGRAM_CRON_THREAD_ID",
|
||||
"DISCORD_HOME_CHANNEL",
|
||||
"DISCORD_HOME_CHANNEL_THREAD_ID",
|
||||
"DISCORD_HOME_CHANNEL_NAME",
|
||||
|
||||
@@ -151,6 +151,53 @@ class TestResolveDeliveryTarget:
|
||||
"thread_id": "topic-7",
|
||||
}
|
||||
|
||||
def test_telegram_cron_thread_id_overrides_home_thread_id(self, monkeypatch):
|
||||
"""TELEGRAM_CRON_THREAD_ID wins over TELEGRAM_HOME_CHANNEL_THREAD_ID for cron (#24409)."""
|
||||
monkeypatch.setenv("TELEGRAM_HOME_CHANNEL", "-1001234567890")
|
||||
monkeypatch.setenv("TELEGRAM_HOME_CHANNEL_THREAD_ID", "5")
|
||||
monkeypatch.setenv("TELEGRAM_CRON_THREAD_ID", "42")
|
||||
|
||||
assert _resolve_delivery_target({"deliver": "telegram"}) == {
|
||||
"platform": "telegram",
|
||||
"chat_id": "-1001234567890",
|
||||
"thread_id": "42",
|
||||
}
|
||||
|
||||
def test_telegram_cron_thread_id_sets_thread_when_home_thread_unset(self, monkeypatch):
|
||||
"""TELEGRAM_CRON_THREAD_ID supplies a thread when no home thread is configured."""
|
||||
monkeypatch.setenv("TELEGRAM_HOME_CHANNEL", "-1001234567890")
|
||||
monkeypatch.delenv("TELEGRAM_HOME_CHANNEL_THREAD_ID", raising=False)
|
||||
monkeypatch.setenv("TELEGRAM_CRON_THREAD_ID", "42")
|
||||
|
||||
assert _resolve_delivery_target({"deliver": "telegram"}) == {
|
||||
"platform": "telegram",
|
||||
"chat_id": "-1001234567890",
|
||||
"thread_id": "42",
|
||||
}
|
||||
|
||||
def test_telegram_cron_thread_id_does_not_leak_to_other_platforms(self, monkeypatch):
|
||||
"""TELEGRAM_CRON_THREAD_ID is Telegram-only; other platforms keep their own thread resolution."""
|
||||
monkeypatch.setenv("DISCORD_HOME_CHANNEL", "parent-42")
|
||||
monkeypatch.setenv("DISCORD_HOME_CHANNEL_THREAD_ID", "topic-7")
|
||||
monkeypatch.setenv("TELEGRAM_CRON_THREAD_ID", "42")
|
||||
|
||||
assert _resolve_delivery_target({"deliver": "discord"}) == {
|
||||
"platform": "discord",
|
||||
"chat_id": "parent-42",
|
||||
"thread_id": "topic-7",
|
||||
}
|
||||
|
||||
def test_explicit_telegram_topic_target_overrides_cron_thread_id(self, monkeypatch):
|
||||
"""Explicit ``telegram:chat:thread`` targets bypass TELEGRAM_CRON_THREAD_ID."""
|
||||
monkeypatch.setenv("TELEGRAM_CRON_THREAD_ID", "999")
|
||||
|
||||
job = {"deliver": "telegram:-1003724596514:17"}
|
||||
assert _resolve_delivery_target(job) == {
|
||||
"platform": "telegram",
|
||||
"chat_id": "-1003724596514",
|
||||
"thread_id": "17",
|
||||
}
|
||||
|
||||
def test_explicit_telegram_topic_target_with_thread_id(self):
|
||||
"""deliver: 'telegram:chat_id:thread_id' parses correctly."""
|
||||
job = {
|
||||
|
||||
@@ -248,6 +248,7 @@ For cloud sandbox backends, persistence is filesystem-oriented. `TERMINAL_LIFETI
|
||||
| `TELEGRAM_GROUP_ALLOWED_CHATS` | Comma-separated group/forum chat IDs; any member is authorized |
|
||||
| `TELEGRAM_HOME_CHANNEL` | Default Telegram chat/channel for cron delivery |
|
||||
| `TELEGRAM_HOME_CHANNEL_NAME` | Display name for the Telegram home channel |
|
||||
| `TELEGRAM_CRON_THREAD_ID` | Forum topic ID to receive cron deliveries; overrides `TELEGRAM_HOME_CHANNEL_THREAD_ID` for cron only. Use in topic mode so replies to cron messages open a new session instead of hitting the system lobby (#24409). |
|
||||
| `TELEGRAM_WEBHOOK_URL` | Public HTTPS URL for webhook mode (enables webhook instead of polling) |
|
||||
| `TELEGRAM_WEBHOOK_PORT` | Local listen port for webhook server (default: `8443`) |
|
||||
| `TELEGRAM_WEBHOOK_SECRET` | Secret token Telegram echoes back in each update for verification. **Required whenever `TELEGRAM_WEBHOOK_URL` is set** — the gateway refuses to start without it (GHSA-3vpc-7q5r-276h). Generate with `openssl rand -hex 32`. |
|
||||
|
||||
@@ -258,6 +258,17 @@ Semantics: `all` expands to every platform with a configured home channel. Zero
|
||||
|
||||
`all` composes with explicit targets. `origin,all` delivers to the origin chat *plus* every other connected home channel, de-duplicating by `(platform, chat_id, thread_id)`.
|
||||
|
||||
### Telegram cron topic (`TELEGRAM_CRON_THREAD_ID`)
|
||||
|
||||
When Telegram topic mode is enabled, the root DM is reserved as a system lobby — replies sent there are rebuffed with a lobby reminder and `reply_to_message_id` is dropped, so you cannot reply to a cron message that landed in the main chat.
|
||||
|
||||
Point cron at a dedicated forum topic instead:
|
||||
|
||||
1. In Telegram, open the bot DM and create a topic named e.g. `Cron`. Long-press the topic header → **Copy link**; the trailing integer is the topic's `message_thread_id`.
|
||||
2. Set `TELEGRAM_CRON_THREAD_ID=<that id>` in your `.env`.
|
||||
|
||||
This applies only to cron deliveries. `TELEGRAM_HOME_CHANNEL_THREAD_ID` (used elsewhere, e.g. restart notifications) is unchanged. Explicit `deliver="telegram:chat_id:thread_id"` targets continue to win over the env var. Replies to cron messages now arrive in the existing topic session, so you can act on them directly.
|
||||
|
||||
### Response wrapping
|
||||
|
||||
By default, delivered cron output is wrapped with a header and footer so the recipient knows it came from a scheduled task:
|
||||
|
||||
@@ -256,6 +256,16 @@ TELEGRAM_HOME_CHANNEL_NAME="My Notes"
|
||||
Group chat IDs are negative numbers (e.g., `-1001234567890`). Your personal DM chat ID is the same as your user ID.
|
||||
:::
|
||||
|
||||
### Cron deliveries in topic mode
|
||||
|
||||
If you have topic mode enabled in your bot DM, cron messages delivered to the root chat land in the system-only lobby — replying there opens no session and you see the "main chat is reserved for system commands" notice. Create a dedicated forum topic (e.g. `Cron`) and set:
|
||||
|
||||
```bash
|
||||
TELEGRAM_CRON_THREAD_ID=<topic_thread_id>
|
||||
```
|
||||
|
||||
`TELEGRAM_CRON_THREAD_ID` overrides `TELEGRAM_HOME_CHANNEL_THREAD_ID` for cron deliveries only. Replies in that topic continue the topic's existing session.
|
||||
|
||||
## Voice Messages
|
||||
|
||||
### Incoming Voice (Speech-to-Text)
|
||||
|
||||
Reference in New Issue
Block a user