From dec081a3264ba79bfbc054f022fae22ec16ca26f Mon Sep 17 00:00:00 2001 From: Vincent Leraitre <1267662+vnz@users.noreply.github.com> Date: Sun, 15 Mar 2026 12:44:53 +0100 Subject: [PATCH] slack unfurl links * Add unfurl_links config for Slack channel Add unfurl_links: bool (default true) to SlackConfig to control Slack's automatic URL preview expansion. When set to false, links in agent messages are not unfurled, keeping output compact. Applied to SlackAdapter's chat.postMessage payload via unfurl_links and unfurl_media parameters, affecting both real-time and cron delivery paths. Co-Authored-By: Claude Opus 4.6 (1M context) * Rename test per review: clarify it tests explicit true, not default Co-Authored-By: Claude Opus 4.6 (1M context) --------- Co-authored-by: Claude Opus 4.6 (1M context) --- crates/openfang-api/src/channel_bridge.rs | 1 + crates/openfang-channels/src/slack.rs | 33 +++++++++++++++++++++++ crates/openfang-types/src/config.rs | 22 +++++++++++++++ 3 files changed, 56 insertions(+) diff --git a/crates/openfang-api/src/channel_bridge.rs b/crates/openfang-api/src/channel_bridge.rs index 6affca54..a989ec5b 100644 --- a/crates/openfang-api/src/channel_bridge.rs +++ b/crates/openfang-api/src/channel_bridge.rs @@ -1150,6 +1150,7 @@ pub async fn start_channel_bridge_with_config( sl_config.allowed_channels.clone(), sl_config.auto_thread_reply, sl_config.thread_ttl_hours, + sl_config.unfurl_links, )); adapters.push((adapter, sl_config.default_agent.clone())); } diff --git a/crates/openfang-channels/src/slack.rs b/crates/openfang-channels/src/slack.rs index 42beb105..b0915530 100644 --- a/crates/openfang-channels/src/slack.rs +++ b/crates/openfang-channels/src/slack.rs @@ -39,6 +39,8 @@ pub struct SlackAdapter { thread_ttl: Duration, /// Whether auto-thread-reply is enabled. auto_thread_reply: bool, + /// Whether to unfurl (expand previews for) links in posted messages. + unfurl_links: bool, } impl SlackAdapter { @@ -48,6 +50,7 @@ impl SlackAdapter { allowed_channels: Vec, auto_thread_reply: bool, thread_ttl_hours: u64, + unfurl_links: bool, ) -> Self { let (shutdown_tx, shutdown_rx) = watch::channel(false); Self { @@ -61,6 +64,7 @@ impl SlackAdapter { active_threads: Arc::new(DashMap::new()), thread_ttl: Duration::from_secs(thread_ttl_hours * 3600), auto_thread_reply, + unfurl_links, } } @@ -100,6 +104,8 @@ impl SlackAdapter { let mut body = serde_json::json!({ "channel": channel_id, "text": chunk, + "unfurl_links": self.unfurl_links, + "unfurl_media": self.unfurl_links, }); if let Some(ts) = thread_ts { body["thread_ts"] = serde_json::json!(ts); @@ -691,8 +697,35 @@ mod tests { vec!["C123".to_string()], true, 24, + true, ); assert_eq!(adapter.name(), "slack"); assert_eq!(adapter.channel_type(), ChannelType::Slack); } + + #[test] + fn test_slack_adapter_unfurl_links_enabled() { + let adapter = SlackAdapter::new( + "xapp-test".to_string(), + "xoxb-test".to_string(), + vec![], + true, + 24, + true, + ); + assert!(adapter.unfurl_links); + } + + #[test] + fn test_slack_adapter_unfurl_links_disabled() { + let adapter = SlackAdapter::new( + "xapp-test".to_string(), + "xoxb-test".to_string(), + vec![], + true, + 24, + false, + ); + assert!(!adapter.unfurl_links); + } } diff --git a/crates/openfang-types/src/config.rs b/crates/openfang-types/src/config.rs index 421253b9..48472ff2 100644 --- a/crates/openfang-types/src/config.rs +++ b/crates/openfang-types/src/config.rs @@ -1752,6 +1752,9 @@ pub struct SlackConfig { /// Hours to track a thread after last interaction (default: 24). #[serde(default = "default_thread_ttl")] pub thread_ttl_hours: u64, + /// Whether to unfurl (expand previews for) links in messages (default: true). + #[serde(default = "default_true")] + pub unfurl_links: bool, } impl Default for SlackConfig { @@ -1764,6 +1767,7 @@ impl Default for SlackConfig { overrides: ChannelOverrides::default(), auto_thread_reply: true, thread_ttl_hours: 24, + unfurl_links: true, } } } @@ -3872,4 +3876,22 @@ mod tests { "AZURE_OPENAI_KEY" ); } + + #[test] + fn test_slack_config_unfurl_links_defaults_true() { + let config: SlackConfig = toml::from_str("").unwrap(); + assert!(config.unfurl_links); + } + + #[test] + fn test_slack_config_unfurl_links_explicit_false() { + let config: SlackConfig = toml::from_str("unfurl_links = false").unwrap(); + assert!(!config.unfurl_links); + } + + #[test] + fn test_slack_config_unfurl_links_explicit_true() { + let config: SlackConfig = toml::from_str("unfurl_links = true").unwrap(); + assert!(config.unfurl_links); + } }