feat(channels): add threading support to message channels

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>
This commit is contained in:
Daniel Willitzer 2026-02-19 00:42:29 -08:00 committed by Chummy
parent adc998429e
commit 9afe4f28e7
18 changed files with 63 additions and 8 deletions

View file

@ -9,6 +9,9 @@ pub struct ChannelMessage {
pub content: String,
pub channel: String,
pub timestamp: u64,
/// Platform thread identifier (e.g. Slack `ts`, Discord thread ID).
/// When set, replies should be posted as threaded responses.
pub thread_ts: Option<String>,
}
/// Message to send through a channel
@ -17,6 +20,8 @@ pub struct SendMessage {
pub content: String,
pub recipient: String,
pub subject: Option<String>,
/// Platform thread identifier for threaded replies (e.g. Slack `thread_ts`).
pub thread_ts: Option<String>,
}
impl SendMessage {
@ -26,6 +31,7 @@ impl SendMessage {
content: content.into(),
recipient: recipient.into(),
subject: None,
thread_ts: None,
}
}
@ -39,8 +45,15 @@ impl SendMessage {
content: content.into(),
recipient: recipient.into(),
subject: Some(subject.into()),
thread_ts: None,
}
}
/// Set the thread identifier for threaded replies.
pub fn in_thread(mut self, thread_ts: Option<String>) -> Self {
self.thread_ts = thread_ts;
self
}
}
/// Core channel trait — implement for any messaging platform
@ -129,6 +142,7 @@ mod tests {
content: "hello".into(),
channel: "dummy".into(),
timestamp: 123,
thread_ts: None,
})
.await
.map_err(|e| anyhow::anyhow!(e.to_string()))
@ -144,6 +158,7 @@ mod tests {
content: "ping".into(),
channel: "dummy".into(),
timestamp: 999,
thread_ts: None,
};
let cloned = message.clone();