Skip to content

feat(telegram): inbound reaction routing behind TELEGRAM_INBOUND_REACTIONS env toggle - #13992

Open
giwaov wants to merge 2 commits into
NousResearch:mainfrom
giwaov:fix/13942-telegram-inbound-reactions
Open

feat(telegram): inbound reaction routing behind TELEGRAM_INBOUND_REACTIONS env toggle#13992
giwaov wants to merge 2 commits into
NousResearch:mainfrom
giwaov:fix/13942-telegram-inbound-reactions

Conversation

@giwaov

@giwaov giwaov commented Apr 22, 2026

Copy link
Copy Markdown
Contributor

Summary

Telegram reactions (👍 ✅ 👎 ❌) were silently ignored. This adds opt-in routing of reactions as synthetic messages.

  • Register MessageReactionHandler when TELEGRAM_INBOUND_REACTIONS=1; wrapped in try/except for library compatibility
  • Allowlisted emoji translated to MessageEvent text (👍"👍", etc.)
  • Bounded LRU cache (OrderedDict, max 512 entries, 1 h TTL) correlates bot message IDs to originating chat/thread context
  • Cache is populated after each successful send() call

Test plan

  • With TELEGRAM_INBOUND_REACTIONS=1: react with 👍 → agent receives "👍" message in correct conversation
  • Non-allowlisted emoji → ignored
  • Without env var set → reaction handler not registered, no change in behaviour
  • Cache eviction after 1 h or when 512-entry limit is reached

Fixes #13942

@alt-glitch alt-glitch added type/feature New feature or request P3 Low — cosmetic, nice to have platform/telegram Telegram bot adapter comp/gateway Gateway runner, session dispatch, delivery labels Apr 22, 2026
@RichardAtCT

Copy link
Copy Markdown

Hey @giwaov — thanks for picking up #13942, appreciate it.

A few gaps I noticed that would be worth tightening before this merges:

1. The cache is never populated in practice. _cache_bot_message is only called when metadata contains a _source_event key, but grep -r _source_event against main returns zero hits — nothing in the repo ever sets it. Net effect with the PR as-is: the reaction cache stays empty, _handle_message_reaction always takes the "best-effort minimal source" branch, and the originating session/thread context is lost on every inbound reaction. Verified locally against main.

2. Only 1 of 10 bot-authored send paths is hooked. send() is patched, but send_exec_approval, send_model_picker, send_update_prompt, send_photo, send_video, send_voice, send_document, send_animation, and send_image all bypass the cache even if (1) were fixed. Approval prompts are arguably the highest-value target for this feature and would go uncorrelated.

3. reaction:removed events never fire. The handler only diffs new_reaction, so if a user adds 👍 and then un-taps it, the agent sees nothing. Feishu's inbound-reaction implementation in gateway/platforms/feishu.py emits both reaction:added:EMOJI and reaction:removed:EMOJI — worth mirroring for consistency.

4. Synthetic text format diverges from precedent. Feishu (merged) emits reaction:added:EMOJI; this PR emits [reaction: EMOJI]. Cross-platform agent-side handling is easier if they match.

5. No tests. Test plan is listed but unchecked, and the diff only touches telegram.py. tests/gateway/test_telegram_reactions.py has an established outbound-reaction test pattern that extends cleanly to inbound.


I have an alternative implementation sitting on a fork that covers all of the above — populates the cache from all 10 send paths with kind tags, emits add+remove deltas, matches the Feishu text format, adds a telegram.inbound_reactions YAML bridge alongside the existing telegram.reactions bridge, ships 14 new unit tests (all passing under scripts/run_tests.sh), and updates the Telegram user guide + env-var reference. Clean diff, +547/-1 across 5 files: RichardAtCT#1

Happy to either:

  • (a) Open it as an upstream PR so maintainers can compare side-by-side, or
  • (b) Cherry-pick specific fixes onto your branch as review commits here, whichever you and the maintainers prefer.

Whatever lands the cleanest implementation for the repo works for me — flagging the issues upfront so #13942 actually gets solved rather than half-solved.

giwaov and others added 2 commits April 24, 2026 09:16
Register MessageReactionHandler when TELEGRAM_INBOUND_REACTIONS=1.
Allowlisted emoji (👍 ✅ 👎 ❌) are translated to synthetic MessageEvents
correlated to the originating conversation via a bounded LRU cache that
maps bot message IDs to chat/thread context.

Fixes NousResearch#13942
When TELEGRAM_INBOUND_REACTIONS=true, reactions a user places on
bot-authored messages (👍 ✅ 👎 ❌) are routed through the normal
message pipeline as synthetic "reaction:added:EMOJI" /
"reaction:removed:EMOJI" events — mirroring the Feishu precedent
in gateway/platforms/feishu.py. The bot can observe 👍 on its own
question as a lightweight confirmation signal instead of needing
a follow-up "yes" message.

Implementation:
- Register MessageReactionHandler when the feature is on (PTB's
  polling/webhook already allows Update.ALL_TYPES).
- Cache recent bot-authored outbound messages in a bounded, TTL-pruned
  OrderedDict keyed by (chat_id, message_id) so reactions on third-party
  messages and stale messages are ignored. Send paths are hooked to
  record each outbound with a kind tag (regular / approval / model_picker).
- Drop reactions from the bot's own account to avoid feedback loops
  with lifecycle reactions.
- Only route allowlisted emoji (👍 ✅ 👎 ❌) in v1; custom premium
  emoji and anonymous aggregated counts are ignored.

Config: new TELEGRAM_INBOUND_REACTIONS env var (default false), with
YAML bridge telegram.inbound_reactions → env var alongside the existing
telegram.reactions bridge.

Docs: adds an "Inbound reactions (experimental)" section in the
Telegram user guide and a row in the environment variables reference.

Tests: adds 14 unit tests covering env toggle, cache insert/evict/TTL,
unknown-message drop, added/removed/swap routing, bot-self filter,
unsupported emoji, anonymous admin, custom-emoji skip, and two
config-bridge tests.
@giwaov
giwaov force-pushed the fix/13942-telegram-inbound-reactions branch from b7d3139 to 1cd342e Compare April 24, 2026 08:31
@giwaov

giwaov commented Apr 24, 2026

Copy link
Copy Markdown
Contributor Author

Addressed the review and force-pushed an updated branch on top of current main.

This version now:

  • caches all bot-authored outbound send paths needed for inbound reaction routing
  • routes both reaction:added:EMOJI and reaction:removed:EMOJI
  • drops the dead _source_event dependency
  • matches the Feishu-style synthetic text format
  • adds the inbound-reaction config bridge, docs, and targeted tests

Validation run locally:

  • python -m pytest tests/gateway/test_telegram_reactions.py -q --maxfail=5 -o addopts="-m 'not integration' -n 0 --basetemp=c:/Users/DELL/aibtc-godmode/.pytest_tmp_hermes"

Thanks for the detailed review and for pointing to the more complete approach.

@teknium1 teknium1 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for incorporating the prior review's cache, add/remove-delta, docs, and test coverage. The feature is still needed: current main requests Update.ALL_TYPES at plugins/platforms/telegram/adapter.py:1989-1993, but its registrations at :3173-3190 do not include a reaction handler.

Problems

  • This branch targets the legacy gateway/platforms/telegram.py; current main moved Telegram to plugins/platforms/telegram/adapter.py in 5600105478ffde29d7566b45421b100eaa29c4ef, including platform-specific YAML translation. The implementation and config bridge need a plugin-surface port.
  • gateway/platforms/telegram.py:3261 accepts a cache entry without checking its TTL. Expiry runs only on a later insert (:3220-3226), so an otherwise idle adapter can route a stale reaction.
  • website/docs/user-guide/messaging/telegram.md:565 promises a 👍/✅ acts as yes, while the handler emits reaction:added:<emoji> events (gateway/platforms/telegram.py:3312).

Suggested changes

  • Port the feature to the plugin adapter/config bridge, expire entries at lookup, and add the idle-after-TTL test.
  • Describe the synthetic event behavior without guaranteeing yes-equivalence.

Automated hermes-sweeper review.


# 1. Must be a message we sent
async with self._bot_message_cache_lock:
cached = self._bot_message_cache.get((chat_id, message_id))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This lookup does not enforce the TTL. Entries are pruned only by a later _remember_bot_message() insert, so an idle adapter can route a reaction after the advertised expiry. Validate/delete the timestamp under this lock and add a no-later-send expiry test.


### Inbound reactions (experimental)

Hermes can also observe when *you* react to one of its messages and forward that as a lightweight confirmation signal. Reacting with 👍 or ✅ on a bot question has the same effect as replying "yes". Disabled by default.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The handler emits reaction:added:<emoji> / reaction:removed:<emoji>, not yes; please describe this as a synthetic reaction event rather than guaranteeing equivalent confirmation semantics.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/gateway Gateway runner, session dispatch, delivery P3 Low — cosmetic, nice to have platform/telegram Telegram bot adapter sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:risk-message-delivery Sweeper risk: may drop, duplicate, misroute, or suppress messages sweeper:risk-security-boundary Sweeper risk: may affect sandboxing, auth, credentials, or sensitive data sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state type/feature New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature]: Route Telegram message_reaction events to the agent as observable confirmation signals

4 participants