feat(talon): add Telegram channel adapter, CLI wiring, and offset persistence - #4097
Merged
Mason Daugherty (mdrxy) merged 49 commits intoJun 26, 2026
Merged
Conversation
Contributor
There was a problem hiding this comment.
Security Issues
- Missing Authentication / Exposure Bypass
The Telegram adapter marks any private Telegram message asfrom_selfby comparingsender_idtochat_id. In Telegram private chats, those IDs match for every user, so the default self/operator exposure mode can treat arbitrary Telegram users as trusted operators. An attacker who can message the bot could trigger the Talon agent with local host access unless an explicit operator ID or allowlist is configured.
Recommendations
- Do not infer operator trust from
chat.id == from.idfor Telegram private chats. - Require an explicit
DEEPAGENTS_TALON_TELEGRAM_OPERATOR_IDor allowlist before enabling the channel, or persistently bind the first operator only through an explicit local approval/setup flow. - Ensure
ChannelExposurechecks Telegram sender IDs against a trusted configured/captured operator ID, not thefrom_selfmetadata derived here.
Contributor
There was a problem hiding this comment.
Security Issues
- Uncontrolled Resource Consumption (DoS) via unbounded inbound media downloads
Inbound Telegram media are downloaded from the Bot API without any size checks or streaming limits. An attacker who can send messages to the bot (e.g., when exposure mode is set to open, or a malicious/compromised allowed user/channel) can repeatedly send large files to exhaust disk space and memory (the code reads the entire response into memory before writing). The code ignores the file_size field returned by getFile and does not enforce any maximum size during download or impose a streaming cap, creating a clear and realistic DoS vector.
Recommendations
- Before downloading, read and validate result["file_size"] from getFile against a strict cap (e.g., the same MAX_DOCUMENT_BYTES or a lower inbound-specific limit).
- Stream the download in bounded chunks and abort if a cumulative byte cap is exceeded (do not read the entire response into memory).
- Consider limiting total concurrent downloads and rate limiting per chat/user to reduce abuse impact.
added 12 commits
June 20, 2026 12:04
…sistence Implements a pure-Python Telegram Bot API channel adapter for the talon host, following the same patterns as the existing WhatsApp channel. **Telegram channel adapter** (): - with env-var mapping ( prefix) and alias - using — no bridge subprocess needed - Long-polling via with configurable timeout and interval - MarkdownV2 message formatting with automatic plain-text fallback - for images, for other media types - typing indicator - for inbound media downloads - Operator auto-capture from first inbound message - Configurable exposure modes (default, allowlist, open) **Offset persistence** (ticket 23): - in the session directory - Atomic writes via - Loads persisted offset on startup, saves after each polling cycle - Graceful handling of missing/corrupt/invalid offset files **CLI wiring** (): - flag to enable the Telegram channel - Updated factory to accept both and - Both channels can coexist simultaneously **Config** (): - Added to - Added to for alias capture **Tests**: - 36 unit tests covering config, MarkdownV2 escaping, polling, exposure, outbound text/media, edit, typing, inbound media, status, offset persistence - 7 integration tests for factory and simultaneous channel coexistence Closes #21, #22, #23
John Kennedy (jkennedyvz)
force-pushed
the
jk/talon/telegram-channel-adapter
branch
from
June 20, 2026 19:05
63ae07f to
4f3a6e4
Compare
John Kennedy (jkennedyvz)
enabled auto-merge (squash)
June 21, 2026 03:21
Address code review feedback on PR #4097: - Unify / via shared - Remove redundant — callers already validate - Move to and import from both adapters - Centralize open-exposure warning in , eliminating provider-specific wrappers - Convert from stored field to computed returning - Simplify using with a key function - Reuse in instead of reimplementing the size check - Inline trivial property and one-liner - Extract helper in for duplicated truthy-set - Fix tmp file path to use + suffix - Document intentional behavior in docstring - Extract shared helper for identical methods - Consolidate test into shared
added 2 commits
June 21, 2026 20:45
…ss in `send_with_retry`
… size limit - Prefix module-internal symbols with underscore: , , , , , - Remove from - Remove per-type media constants (, in base.py; , , in telegram.py) and — all channels now use a single from config controlled by - Simplify to check only - Update tests to match renamed symbols and use config-based size limit
added 2 commits
June 22, 2026 11:43
send_with_retry only handled SendResult failures, not exceptions. Both built-in adapters can raise from their transport paths (_TelegramError, _WhatsAppBridgeError), so an upload/send failure escaped _send_channel_media and aborted the channel task instead of adding the item to failed and delivering the text fallback. Wrap each send_fn call in _safe_send, which catches exceptions and converts them to failed SendResult objects (retryable=True). This keeps media delivery failures non-fatal at every call site, not just _send_channel_media. Add tests verifying exception-to-SendResult conversion and retry after exception.
Member
|
what's the user story here? |
…annels Remove WhatsApp-specific shared config from .env.example and docker-compose.yml. Both channel configs are now commented-out templates the operator enables via env vars. ASR (voice transcription) defaults to enabled. The Dockerfile COPY path and CMD are updated to drop the talon-whatsapp reference and --whatsapp flag. README is rewritten to cover both channels with separate setup and exposure reference sections.
Co-authored-by: Mason Daugherty <mason@langchain.dev> Signed-off-by: John Kennedy <65985482+jkennedyvz@users.noreply.github.com>
Mason's feedback (PR #4097 discussion_r3455630256): move the ChannelExposureEnv and ChannelExposure Args blocks to attribute-level docstring form so ref-doc parsers (griffe/mkdocstrings) pick them up as field documentation rather than only as constructor params. Also applies the nit on env_prefix (single backticks per project docstring convention).
Use cp -n (no-clobber) so the AGENTS.md template is only seeded on first run. Previously, cp overwrote the agent's accumulated memory with the template on every container start.
added 3 commits
June 23, 2026 22:14
Broaden ASR eligibility so that video files and audio-typed documents sent via Telegram or WhatsApp are transcribed when voice packages are installed and enabled. Previously only `voice` media type triggered transcription. - `_is_voice_message` now matches `media_type == "video"` and checks `media_mime_types` for `audio/*` MIME types - `_document_media_type` classifies documents with audio MIME types or audio file extensions as `voice` instead of `document` - `message_with_media_paths` sets `voice_path` for `video` media so the transcriber can locate the file - ffmpeg's `_convert_to_wav` already extracts audio from video containers, so no change needed there
- Move `_ASR_ELIGIBLE_MEDIA_TYPES` constant from `speech.py` to `base.py`
as `ASR_ELIGIBLE_MEDIA_TYPES` and import it in `speech.py`, eliminating
the duplicated `{"voice", "video"}` set
- Collapse `_document_media_type` from 4 sequential if-blocks to a
2-iteration loop over `(mime_type, guessed)`
- Stop setting `voice_path` for video in `message_with_media_paths` —
video transcription still works via `ASR_ELIGIBLE_MEDIA_TYPES` check in
`_is_voice_message` and `media_path` fallback in `_voice_path`
- Remove the dead MIME-type scan branch from `_is_voice_message` —
`_document_media_type` now classifies audio documents as `"voice"`
upstream, making the scan unreachable in the real channel pipeline
Mason Daugherty (mdrxy)
disabled auto-merge
June 26, 2026 18:44
Mason Daugherty (mdrxy)
approved these changes
Jun 26, 2026
This was referenced Jun 26, 2026
John Kennedy (jkennedyvz)
pushed a commit
that referenced
this pull request
Jun 30, 2026
> [!CAUTION] > Merging this PR will automatically publish to **PyPI** and create a **GitHub release**. For the full release process, see [`.github/RELEASING.md`](https://github.com/langchain-ai/deepagents/blob/main/.github/RELEASING.md). --- _Everything below this line will be the GitHub release body._ --- ## [0.0.2](deepagents-talon==0.0.1...deepagents-talon==0.0.2) (2026-06-30) ### Features * **talon:** `DEEPAGENTS_TALON_RECURSION_LIMIT` env var ([#4354](#4354)) ([82d1eac](82d1eac)) * **talon:** add reaction approval routing ([#4345](#4345)) ([3fe8c0c](3fe8c0c)) * **talon:** add Telegram channel adapter, CLI wiring, and offset persistence ([#4097](#4097)) ([7c87cec](7c87cec)) * **talon:** add tool approval env override ([#4349](#4349)) ([d26481d](d26481d)) * **talon:** audit reaction approval attempts ([#4348](#4348)) ([d7895c4](d7895c4)) * **talon:** ingest Telegram approval reactions ([#4346](#4346)) ([437af0b](437af0b)) ### Bug Fixes * **talon:** default workspace to current directory ([#4099](#4099)) ([5e337ae](5e337ae)) --- _Everything above this line will be the GitHub release body._ --- > [!NOTE] > A **New Contributors** section is appended to the GitHub release notes automatically at publish time (see [Release Pipeline](https://github.com/langchain-ai/deepagents/blob/main/.github/RELEASING.md#release-pipeline), step 2). --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Marcelo5444
pushed a commit
to Marcelo5444/deepagents
that referenced
this pull request
Jul 30, 2026
…sistence (langchain-ai#4097) Implements a Telegram Bot API channel for Talon and updates the shared channel policy so Telegram and WhatsApp use the same exposure rules. --- ## Telegram channel Adds a pure-Python Telegram channel adapter that uses the Bot API directly rather than a local bridge. It supports long polling, persisted update offsets, inbound media downloads, outbound text and media, typing indicators, message edits, and plain-text message delivery. Telegram configuration is wired through `DEEPAGENTS_TALON_TELEGRAM_*`, with `TELEGRAM_BOT_TOKEN` accepted as a convenience alias. The host can run Telegram, WhatsApp, or both channels against the same Talon runtime. ## Shared Channel Exposure Moves provider-specific exposure parsing into the shared channel abstraction via `ChannelExposureEnv` and `channel_exposure_from_env`. Telegram and WhatsApp now share the same handling for `self`, `allowlist`, `open`, mention patterns, open-exposure acknowledgement, and operator identity. `ChannelExposure` now supports multiple operator IDs while preserving the existing single `operator_id` API. The existing `DEEPAGENTS_TALON_TELEGRAM_OPERATOR_ID` and `DEEPAGENTS_TALON_WHATSAPP_OPERATOR_ID` env vars accept comma-separated IDs, so `self` exposure can allow more than one trusted operator without duplicating logic in each provider. ## Media And Runtime Handling Adds shared media helpers for inbound media metadata, outbound media validation, and configured media-size caps. Telegram and WhatsApp both use the common cap handling, while WhatsApp keeps a lower provider-specific cap because its bridge materializes downloads in memory. The host and runtime wiring now preserve channel metadata, support channel-specific approval handling, and allow Telegram channel posts through the same serialized agent execution path as other Talon channels. ## Example App Refreshes the example app from a WhatsApp-only example into a general Talon Docker example. The example now documents running WhatsApp, Telegram, or both; adds placeholder-only env settings for Telegram allowlists and channel toggles; keeps local secrets in an ignored `.env`; and updates the Docker setup so the packaged WhatsApp bridge and Talon runtime work together from the same container. ## Review Notes The exposure changes are intentionally centralized in the shared channel layer. Provider modules should only declare their env var names and provider-specific requirements, such as Telegram requiring an operator ID for `self` exposure. The example app changes are mostly naming and configuration surface updates so it reflects the new multi-channel Talon host rather than only WhatsApp. --------- Signed-off-by: John Kennedy <65985482+jkennedyvz@users.noreply.github.com> Co-authored-by: Deep Agent <agent@deepagents.dev> Co-authored-by: Mason Daugherty <mason@langchain.dev>
Marcelo5444
pushed a commit
to Marcelo5444/deepagents
that referenced
this pull request
Jul 30, 2026
> [!CAUTION] > Merging this PR will automatically publish to **PyPI** and create a **GitHub release**. For the full release process, see [`.github/RELEASING.md`](https://github.com/langchain-ai/deepagents/blob/main/.github/RELEASING.md). --- _Everything below this line will be the GitHub release body._ --- ## [0.0.2](langchain-ai/deepagents@deepagents-talon==0.0.1...deepagents-talon==0.0.2) (2026-06-30) ### Features * **talon:** `DEEPAGENTS_TALON_RECURSION_LIMIT` env var ([langchain-ai#4354](langchain-ai#4354)) ([82d1eac](langchain-ai@82d1eac)) * **talon:** add reaction approval routing ([langchain-ai#4345](langchain-ai#4345)) ([3fe8c0c](langchain-ai@3fe8c0c)) * **talon:** add Telegram channel adapter, CLI wiring, and offset persistence ([langchain-ai#4097](langchain-ai#4097)) ([7c87cec](langchain-ai@7c87cec)) * **talon:** add tool approval env override ([langchain-ai#4349](langchain-ai#4349)) ([d26481d](langchain-ai@d26481d)) * **talon:** audit reaction approval attempts ([langchain-ai#4348](langchain-ai#4348)) ([d7895c4](langchain-ai@d7895c4)) * **talon:** ingest Telegram approval reactions ([langchain-ai#4346](langchain-ai#4346)) ([437af0b](langchain-ai@437af0b)) ### Bug Fixes * **talon:** default workspace to current directory ([langchain-ai#4099](langchain-ai#4099)) ([5e337ae](langchain-ai@5e337ae)) --- _Everything above this line will be the GitHub release body._ --- > [!NOTE] > A **New Contributors** section is appended to the GitHub release notes automatically at publish time (see [Release Pipeline](https://github.com/langchain-ai/deepagents/blob/main/.github/RELEASING.md#release-pipeline), step 2). --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Implements a Telegram Bot API channel for Talon and updates the shared channel policy so Telegram and WhatsApp use the same exposure rules.
Telegram channel
Adds a pure-Python Telegram channel adapter that uses the Bot API directly rather than a local bridge. It supports long polling, persisted update offsets, inbound media downloads, outbound text and media, typing indicators, message edits, and plain-text message delivery.
Telegram configuration is wired through
DEEPAGENTS_TALON_TELEGRAM_*, withTELEGRAM_BOT_TOKENaccepted as a convenience alias. The host can run Telegram, WhatsApp, or both channels against the same Talon runtime.Shared Channel Exposure
Moves provider-specific exposure parsing into the shared channel abstraction via
ChannelExposureEnvandchannel_exposure_from_env. Telegram and WhatsApp now share the same handling forself,allowlist,open, mention patterns, open-exposure acknowledgement, and operator identity.ChannelExposurenow supports multiple operator IDs while preserving the existing singleoperator_idAPI. The existingDEEPAGENTS_TALON_TELEGRAM_OPERATOR_IDandDEEPAGENTS_TALON_WHATSAPP_OPERATOR_IDenv vars accept comma-separated IDs, soselfexposure can allow more than one trusted operator without duplicating logic in each provider.Media And Runtime Handling
Adds shared media helpers for inbound media metadata, outbound media validation, and configured media-size caps. Telegram and WhatsApp both use the common cap handling, while WhatsApp keeps a lower provider-specific cap because its bridge materializes downloads in memory.
The host and runtime wiring now preserve channel metadata, support channel-specific approval handling, and allow Telegram channel posts through the same serialized agent execution path as other Talon channels.
Example App
Refreshes the example app from a WhatsApp-only example into a general Talon Docker example. The example now documents running WhatsApp, Telegram, or both; adds placeholder-only env settings for Telegram allowlists and channel toggles; keeps local secrets in an ignored
.env; and updates the Docker setup so the packaged WhatsApp bridge and Talon runtime work together from the same container.Review Notes
The exposure changes are intentionally centralized in the shared channel layer. Provider modules should only declare their env var names and provider-specific requirements, such as Telegram requiring an operator ID for
selfexposure.The example app changes are mostly naming and configuration surface updates so it reflects the new multi-channel Talon host rather than only WhatsApp.