Add optional thread_ts field to ChannelMessage and SendMessage for
platform-specific threading (e.g. Slack threads, Discord threads).
- ChannelMessage.thread_ts captures incoming thread context
- SendMessage.thread_ts propagates thread context to replies
- SendMessage::in_thread() builder for fluent API
- Slack: send with thread_ts, capture ts from incoming messages
- All reply paths in runtime preserve thread context via in_thread()
- All other channels initialize thread_ts: None (forward-compatible)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Wire the existing provider-layer streaming infrastructure through the
channel trait and agent loop so Telegram users see tokens arrive
progressively via editMessageText, instead of waiting for the full
response.
Changes:
- Add StreamMode enum (off/partial/block) and draft_update_interval_ms
to TelegramConfig (backward-compatible defaults: off, 1000ms)
- Add supports_draft_updates/send_draft/update_draft/finalize_draft to
Channel trait with no-op defaults (zero impact on existing channels)
- Implement draft methods on TelegramChannel using sendMessage +
editMessageText with rate limiting and Markdown fallback
- Add on_delta mpsc::Sender<String> parameter to run_tool_call_loop
(None preserves existing behavior)
- Wire streaming in process_channel_message: when channel supports
drafts, send initial draft, spawn updater task, finalize on completion
Edge cases handled:
- 4096-char limit: finalize draft and fall back to chunked send
- Broken Markdown: use no parse_mode during streaming, apply on finalize
- Edit failures: fall back to sending complete response as new message
- Rate limiting: configurable draft_update_interval_ms (default 1s)
Refactor the Channel trait to accept a SendMessage struct instead of
separate message and recipient string parameters. This enables passing
additional metadata like email subjects.
Changes:
- Add SendMessage struct with content, recipient, and optional subject
- Update Channel::send() signature to accept &SendMessage
- Update all 12 channel implementations
- Update call sites in channels/mod.rs and gateway/mod.rs
Subject field usage:
- Email: uses subject for email subject line
- DingTalk: uses subject as markdown message title
- All others: ignore subject (no native platform support)
ChannelMessage.sender was used both for display (username) and as the
reply target in Channel::send(). For Telegram, sender is the username
(e.g. "unknown") while send() requires the numeric chat_id, causing
"Bad Request: chat not found" errors.
Add a dedicated reply_to field to ChannelMessage that stores the
channel-specific reply address (Telegram chat_id, Discord channel_id,
Slack channel, etc.). Update all channel implementations and dispatch
code to use reply_to for send/start_typing/stop_typing calls.
This also fixes the same latent bug in Discord and Slack channels where
sender (user ID) was incorrectly passed as the reply target.
Spawns a repeating task that fires the Discord typing endpoint every
8 seconds while the LLM processes a response. Adds start_typing and
stop_typing to the Channel trait with default no-op impls so other
channels can opt in later.