feat(channels): Telegram rich media, polls, interactive commands, and channel_send tool#2356
Merged
Conversation
…nds, and auto-registration Add 7 new ChannelContent variants (DeleteMessage, Audio, Animation, Sticker, MediaGroup, Poll, PollAnswer) with full send/receive support in the Telegram adapter. New API methods: sendAudio, sendAnimation, sendSticker, sendMediaGroup, sendPoll, deleteMessage, setMyCommands, deleteMyCommands, getMyCommands. Inbound parsing now handles audio files (as Audio, not Voice), animations (checked before video to avoid misclassification), stickers, and poll answers. Bot command menu is auto-registered on start() using built-in defaults (BUILTIN_COMMANDS) when no custom commands are configured, and re-registered on /start or first incoming message via the polling loop. Bridge updated with exhaustive match arms in content_to_text and dispatch_message for all new variants, plus 8 new unit tests.
Agents using Messaging or Automation profiles had no way to send images, files, or other media to channel users because channel_send was only available in the Full profile. This made the agent suggest using the API directly instead of its built-in tools.
The channel awareness section in the system prompt told agents about formatting and character limits but never mentioned channel_send. Agents had the tool available but did not know to use it for sending images or files, proposing raw API calls instead. Add a channel_send hint to the Channel section when the tool is granted, including the recipient ID so the agent can use it immediately. Also add a tool_hint entry so channel_send appears with a description in the Your Tools section.
Add send_channel_poll to KernelHandle trait and kernel implementation. Extend channel_send tool with poll_question, poll_options, poll_is_quiz, poll_correct_option, poll_explanation parameters. Telegram adapter stores poll context (question + options) keyed by poll_id and attaches it to inbound PollAnswer messages so agents see readable answers instead of bare numeric indices.
…ivity api_send_media_group and api_send_poll previously logged non-429 errors and returned Ok, making callers believe delivery succeeded. Both now return Err with status and body. channel_send's poll branch now rejects requests that also carry image_url/image_path/file_url/file_path, matching the schema's documented mutual exclusivity.
Intercept /agents in both Command and Text dispatch paths so Telegram bot-command entities reach the interactive handler. Emits an InteractiveMessage with one button per agent, truncating names to fit the 64-byte callback_data limit.
leszek3737
marked this pull request as draft
April 13, 2026 06:33
Replace plain-text /models with a multi-level Telegram inline keyboard: providers list → provider's models (with Back) → confirmation with cleared keyboard. Adds ChannelContent::EditInteractive for in-place editMessageText updates and filters the provider menu to only those with is_available() auth status.
Slicing agent names, provider IDs, and model IDs by byte index risked panics when the cut fell inside a multi-byte UTF-8 character. Extract truncate_utf8 helper in types.rs and use it at every callback_data truncation site (bridge.rs and telegram.rs).
- Add poll_contexts cleanup task (5min interval, 30min TTL) via last_accessed field on PollContext to prevent unbounded growth. - Remove i==0 guard from media group caption handling so each item with a caption uses it instead of silently dropping later ones. - Validate poll options: 2-10 max, and for quiz mode require correct_option_id with bounds checking.
leszek3737
marked this pull request as ready for review
April 13, 2026 07:26
houko
approved these changes
Apr 13, 2026
houko
left a comment
Contributor
There was a problem hiding this comment.
LGTM on the feature shape — rich media dispatch, poll creation, interactive /agents and /models drill-down, and channel_send tool wiring are all well-structured and end-to-end. truncate_utf8 extracted cleanly, PollParams struct avoids clippy too-many-arguments, and kernel.set_agent_model(aid, model, None) correctly defers to the catalog for provider resolution on the model-switch button path.
Merging; will push a follow-up commit to address the two things I'd have blocked on otherwise: (1) poll_options silently filtering out non-string entries, and (2) PollContext.last_accessed never being updated on get, which drops poll question/options metadata from answers arriving >30min after the poll was sent.
houko
added a commit
that referenced
this pull request
Apr 13, 2026
Follow-up to #2356 addressing four issues that would bite in production: 1. channel_send: poll_options silently dropped non-string entries. The previous filter_map(as_str) pattern turned ["a", 42, "c"] into ["a", "c"], slipped past the min-2 check, and sent a poll with the user's third option missing. Extract a parse_poll_options helper that fails fast with a per-index error when any element is not a string, and pin it with eight unit tests covering the type cases, the missing-array case, and the 1/11 bounds. 2. telegram: PollContext.last_accessed was never updated on access. poll_contexts.get() returned an immutable ref, so the background cleanup removed entries 30 minutes after send regardless of whether users were still answering — long-running surveys silently lost their poll_question / poll_options metadata. Switch to get_mut so each answer bumps last_accessed, making the field honest. 3. telegram: poll cleanup task did not respond to shutdown for up to 5 minutes. The loop had a bare interval.tick().await, so a stop() call would block until the next tick. Wrap the tick in a tokio::select! against shutdown.changed() and exit immediately when the signal flips. 4. telegram: api_send_media_group accepted 1 or 11+ items and let the Telegram API reject them with an opaque 400. Validate locally that the slice has 2..=10 items before composing the request.
6 tasks
houko
added a commit
that referenced
this pull request
Apr 13, 2026
…2364) Follow-up to #2356 addressing four issues that would bite in production: 1. channel_send: poll_options silently dropped non-string entries. The previous filter_map(as_str) pattern turned ["a", 42, "c"] into ["a", "c"], slipped past the min-2 check, and sent a poll with the user's third option missing. Extract a parse_poll_options helper that fails fast with a per-index error when any element is not a string, and pin it with eight unit tests covering the type cases, the missing-array case, and the 1/11 bounds. 2. telegram: PollContext.last_accessed was never updated on access. poll_contexts.get() returned an immutable ref, so the background cleanup removed entries 30 minutes after send regardless of whether users were still answering — long-running surveys silently lost their poll_question / poll_options metadata. Switch to get_mut so each answer bumps last_accessed, making the field honest. 3. telegram: poll cleanup task did not respond to shutdown for up to 5 minutes. The loop had a bare interval.tick().await, so a stop() call would block until the next tick. Wrap the tick in a tokio::select! against shutdown.changed() and exit immediately when the signal flips. 4. telegram: api_send_media_group accepted 1 or 11+ items and let the Telegram API reject them with an opaque 400. Validate locally that the slice has 2..=10 items before composing the request.
DaBlitzStein
pushed a commit
to DaBlitzStein/librefang
that referenced
this pull request
Apr 13, 2026
…hardening Brings in: - feat(channels): Telegram rich media, polls, interactive commands, channel_send tool (librefang#2356) - feat(runtime): config-driven session_mode for agent triggers (librefang#2341) - fix(agents): refuse direct DELETE on hand-spawned agents (librefang#2361) - fix(channels): harden poll_options parsing and poll context cleanup (librefang#2364) Conflicts resolved: bridge.rs (new trait methods), prompt_builder.rs (9-param build_channel_section merging bot_username/group_members with granted_tools), tool_runner.rs (cron-tz tests + poll_options tests).
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.
Type
Summary
Extend Telegram adapter with rich media delivery, poll creation, interactive inline-keyboard commands (/agents, /models), and wire channel_send as a built-in tool for Messaging/Automation profiles.
Changes
Telegram adapter
channel_send tool
Hands default model
Types
CI / Docs
Attribution
Testing
cargo clippy --workspace --all-targets -- -D warningspassescargo test --workspacepassesSecurity