Skip to content

feat(telegram): Secretary Mode — business_message support + bot-echo classification + send-as-owner - #60809

Open
bgodlin wants to merge 14 commits into
NousResearch:mainfrom
bgodlin:feat/telegram-secretary-mode
Open

feat(telegram): Secretary Mode — business_message support + bot-echo classification + send-as-owner#60809
bgodlin wants to merge 14 commits into
NousResearch:mainfrom
bgodlin:feat/telegram-secretary-mode

Conversation

@bgodlin

@bgodlin bgodlin commented Jul 8, 2026

Copy link
Copy Markdown

Summary

Adds Telegram Secretary Mode (Chat Automation / Connected Business Bots) support to the gateway and CLI. A connected bot can now receive `business_message` updates sent to a user's personal account and reply on their behalf.

Closes #42400.

What's included

Ingress (gateway adapter):

  • Extract `business_connection_id` from `business_message` updates into `SessionSource`
  • Bypass user-ID auth for business messages (pre-authorized by Chat Automation connection)
  • Handle `BusinessConnection` lifecycle updates (connect/disconnect/settings changes)
  • Classify bot-echo messages (`sender_business_bot` present → skip, `_build_message_event` returns `None`) — fixes Fix Telegram Business manual owner outgoing messages being dropped as bot self-messages #42400 where the gateway re-processed its own relayed sends as customer input

Send path (gateway + CLI):

  • Propagate `business_connection_id` through gateway metadata → adapter `send()` → `bot.send_message`
  • `hermes send --business-connection-id ` CLI flag for standalone Secretary Mode replies without a running gateway

Docs:

  • New `telegram-secretary-mode.md` guide with setup, how-it-works, constraints, and references to the Telegram blog (AI Bot Revolution, May 2026) + API docs
  • Cross-link from the main `telegram.md` page

Tests: 10 tests covering SessionSource field, auth bypass, send-path passthrough, lifecycle handler, and bot-echo classification.

References

Constraints

  • Private chats only (Secretary Mode scope); group automation is separate
  • One bot per account
  • 24h reply window (`can_reply` permission)
  • Owner-outgoing classification (manual owner replies without `sender_bot`) falls through to normal flow — needs the connection owner_id from the lifecycle handler, which arrives on the next `business_connection` update after a gateway restart

@bgodlin
bgodlin force-pushed the feat/telegram-secretary-mode branch from 7fd0411 to c3660f9 Compare July 8, 2026 09:32
@alt-glitch alt-glitch added type/feature New feature or request P3 Low — cosmetic, nice to have comp/gateway Gateway runner, session dispatch, delivery comp/cli CLI entry point, hermes_cli/, setup wizard platform/telegram Telegram bot adapter sweeper:risk-message-delivery Sweeper risk: may drop, duplicate, misroute, or suppress messages labels Jul 8, 2026
@alt-glitch

Copy link
Copy Markdown
Collaborator

This was generated by AI during triage.

Competing Telegram Business / Secretary Mode cluster (all OPEN, overlapping ground, different mechanisms/locations): #46728 (gateway/platforms/telegram.py implementation), #30055 (observe-with-approval draft replies), and issue #42400 (the bot-echo classification this PR addresses). Marking related_to rather than duplicate. A maintainer should pick the canonical implementation.

@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 assembling the ingress, metadata, send, docs, and bot-echo pieces. The feature remains relevant on current main, but the current patch has several blockers.

Problems

  • tools/send_message_tool.py:804 references business_connection_id, but _send_to_platform() has no such argument or local. _handle_send() defines it at line 302 but does not forward it at lines 437-445; standalone Telegram sends will raise NameError.
  • plugins/platforms/telegram/adapter.py:8398-8404 suppresses only sender_business_bot echoes. #42400 requires manual owner outgoing messages to be retained as context but not enqueued; messages with no sender_business_bot still take the normal agent path.
  • gateway/session.py:242-298 does not serialize the new business_connection_id, so restored sources lose send-as-owner routing.
  • Current main's media handler still reads update.message directly at plugins/platforms/telegram/adapter.py:7752; business media needs the effective-message path and coverage.

Suggested changes

  • Thread and test the standalone CLI argument end-to-end.
  • Add explicit owner/bot/customer classification plus source persistence tests.
  • Adapt all inbound handler paths, including media, to current adapter conventions.

Automated hermes-sweeper review.

@@ -800,6 +801,7 @@ async def _send_to_platform(platform, pconfig, chat_id, message, thread_id=None,
thread_id=thread_id,
disable_link_previews=disable_link_previews,
force_document=force_document,
business_connection_id=business_connection_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.

business_connection_id is not in _send_to_platform's signature and is never forwarded from _handle_send, so this Telegram branch raises NameError. Thread the value through both call boundaries and add a standalone send_message regression test.

@teknium1 teknium1 added sweeper:risk-security-boundary Sweeper risk: may affect sandboxing, auth, credentials, or sensitive data sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform labels Jul 10, 2026
@bgodlin
bgodlin force-pushed the feat/telegram-secretary-mode branch from 639a9eb to a0695cb Compare July 18, 2026 17:21
bgodlin added 14 commits July 18, 2026 17:38
Foundation for Secretary Mode support. Carries the connection ID
through the session lifecycle so the send path can reply as the
business owner's personal account.
The adapter now reads business_connection_id from the incoming message
and passes it through build_source to SessionSource. This is the receive
side of Secretary Mode — the ID is available to the session for later
use in the send path.
Business messages carry a server-set business_connection_id that proves
the owner connected the bot via Chat Automation. The external client's
user ID will never be in the allowlist, so without this bypass every
business message is rejected as unauthorized. The ID cannot be spoofed
by the client — it's assigned by Telegram server-side.
When replying to a Secretary Mode message, the send() method now
passes business_connection_id to bot.send_message. This makes the
reply appear as the business owner's personal account rather than
the bot. Only applied when the ID is present in metadata.
_thread_metadata_for_source now includes business_connection_id when
the source carries it. This is the bridge between the receive path
(SessionSource) and the send path (adapter.send), ensuring replies to
Secretary Mode messages are routed as the business owner.
Adds _handle_business_connection handler that tracks active Secretary
Mode connections. Stores {can_reply, user_chat_id} per connection ID
on connect, removes on disconnect. Registered as a TypeHandler in
group -1 so it runs before message handlers.
_build_message_event returns None when sender_business_bot is present,
so the gateway skips its own relayed sends instead of re-processing
them as customer input. Guards added at all 6 call sites.

Customer inbound and manual owner outgoing fall through to the normal
flow — owner-outgoing classification needs the connection owner_id,
which arrives via the BusinessConnection lifecycle handler.

Docs: add references to the Telegram blog (AI Bot Revolution, May 2026)
and the connected-business-bots API docs.
hermes send --business-connection-id <ID> sends as the business owner's
personal account via Telegram's Chat Automation, not as the bot. Threads
through send_message_tool → _send_telegram → Bot API sendMessage.

Enables the standalone CLI to reply in Secretary Mode chats without a
running gateway, complementing the gateway adapter's send path.
Adds a section pointing to the telegram-secretary-mode guide so users
discovering business/Chat Automation features find it from the main doc.
The Secretary Mode patch referenced business_connection_id inside
_send_to_platform without adding it to the signature, and _handle_send
never forwarded the value it extracted — a NameError on every standalone
Telegram send, business or not, and a dead --business-connection-id flag.

- Add business_connection_id as an optional _send_to_platform parameter
  and forward it from _handle_send (cron callers are unaffected).
- Reject the flag for non-Telegram targets instead of silently dropping
  the send-as-owner intent.
- Carry the connection ID on _send_telegram media sends too, so
  attachments are delivered as the owner like the text they accompany.
- Regression tests: plain send NameError, tool-arg threading end to end,
  CLI flag delivery, media kwargs, non-Telegram rejection.
The field was declared but never emitted by to_dict or restored by
from_dict, so any source rehydrated from persistence lost send-as-owner
routing and replies went out as the bot. Round-trip tests live in
tests/gateway/test_telegram_business_message.py.
…sResearch#42400)

Business updates were only guarded against sender_business_bot echoes;
the owner's own manual replies still entered the agent path as prompts,
and business media never arrived at all because _handle_media_message
read update.message (business media comes in as update.business_message).

Intake:
- _classify_business_message: bot_echo / owner_outgoing / customer.
  Owner detection prefers tracked BusinessConnection state and falls
  back to the from_user-vs-chat-peer structural signal, which survives
  gateway restarts (Telegram does not replay connection updates).
- Owner manual replies are appended to the customer chat's session as
  observed context (_observe_business_owner_message) — the agent sees
  the owner already answered — but are never enqueued as prompts.
- Customer slash commands are routed to the agent as plain text: the
  business connection pre-authorizes the conversation, not operator
  authority, so the auth bypass must not reach the command executor.
- _handle_media_message resolves messages via effective_message, so
  business media takes the normal download/caching path; owner media is
  observed with its caption, echoes are dropped.
- _business_connection_id_of accepts only non-empty strings, so
  non-business traffic (and test doubles' auto-attributes) can never be
  misclassified; test_telegram_documents' update mock now mirrors real
  PTB semantics (effective_message == message).

Send:
- _business_kwargs applied to photo/document/video/voice/audio/album/
  animation sends: attachments ride the same business connection as the
  text instead of being delivered as the bot.
- supports_draft_streaming returns False for business sends — the draft
  API has no business_connection_id — so replies take the plain send
  path end to end.
@bgodlin
bgodlin force-pushed the feat/telegram-secretary-mode branch from a0695cb to 7b8b215 Compare July 18, 2026 17:48
@bgodlin

bgodlin commented Jul 19, 2026

Copy link
Copy Markdown
Author

@teknium1 the PR is ready for review again

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

Labels

comp/cli CLI entry point, hermes_cli/, setup wizard 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 type/feature New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Fix Telegram Business manual owner outgoing messages being dropped as bot self-messages

3 participants