From e83e0170625024f39ad97a6b7ad93f3a82fc3f28 Mon Sep 17 00:00:00 2001 From: Chummy Date: Thu, 19 Feb 2026 18:37:08 +0800 Subject: [PATCH] fix(channels): preserve slack thread root ids --- src/channels/cli.rs | 2 ++ src/channels/slack.rs | 40 +++++++++++++++++++++++++++++++++++++++- 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/src/channels/cli.rs b/src/channels/cli.rs index 0adbeb6..11c09eb 100644 --- a/src/channels/cli.rs +++ b/src/channels/cli.rs @@ -75,6 +75,7 @@ mod tests { content: "hello".into(), recipient: "user".into(), subject: None, + thread_ts: None, }) .await; assert!(result.is_ok()); @@ -88,6 +89,7 @@ mod tests { content: String::new(), recipient: String::new(), subject: None, + thread_ts: None, }) .await; assert!(result.is_ok()); diff --git a/src/channels/slack.rs b/src/channels/slack.rs index f03c693..559af15 100644 --- a/src/channels/slack.rs +++ b/src/channels/slack.rs @@ -45,6 +45,15 @@ impl SlackChannel { .and_then(|u| u.as_str()) .map(String::from) } + + /// Resolve the thread identifier for inbound Slack messages. + /// Replies carry `thread_ts` (root thread id); top-level messages only have `ts`. + fn inbound_thread_ts(msg: &serde_json::Value, ts: &str) -> Option { + msg.get("thread_ts") + .and_then(|t| t.as_str()) + .or(if ts.is_empty() { None } else { Some(ts) }) + .map(str::to_string) + } } #[async_trait] @@ -174,7 +183,7 @@ impl Channel for SlackChannel { .duration_since(std::time::UNIX_EPOCH) .unwrap_or_default() .as_secs(), - thread_ts: Some(ts.to_string()), + thread_ts: Self::inbound_thread_ts(msg, ts), }; if tx.send(channel_msg).await.is_err() { @@ -308,4 +317,33 @@ mod tests { assert!(!id.contains('-')); // No UUID dashes assert!(id.starts_with("slack_")); } + + #[test] + fn inbound_thread_ts_prefers_explicit_thread_ts() { + let msg = serde_json::json!({ + "ts": "123.002", + "thread_ts": "123.001" + }); + + let thread_ts = SlackChannel::inbound_thread_ts(&msg, "123.002"); + assert_eq!(thread_ts.as_deref(), Some("123.001")); + } + + #[test] + fn inbound_thread_ts_falls_back_to_ts() { + let msg = serde_json::json!({ + "ts": "123.001" + }); + + let thread_ts = SlackChannel::inbound_thread_ts(&msg, "123.001"); + assert_eq!(thread_ts.as_deref(), Some("123.001")); + } + + #[test] + fn inbound_thread_ts_none_when_ts_missing() { + let msg = serde_json::json!({}); + + let thread_ts = SlackChannel::inbound_thread_ts(&msg, ""); + assert_eq!(thread_ts, None); + } }