@@ -2,7 +2,7 @@ use std::collections::HashMap;
22
33use {
44 moltis_channels:: {
5- config_view:: ChannelConfigView ,
5+ config_view:: { ChannelConfigView , UntrustedAudience , UntrustedTools } ,
66 gating:: { DmPolicy , GroupPolicy , MentionMode } ,
77 } ,
88 moltis_common:: secret_serde,
@@ -104,6 +104,16 @@ pub struct SlackAccountConfig {
104104 #[ serde( default ) ]
105105 pub channel_allowlist : Vec < String > ,
106106
107+ /// Tool audience ceiling for turns outside an operator direct chat
108+ /// (default: `public`).
109+ #[ serde( default ) ]
110+ pub untrusted_audience : UntrustedAudience ,
111+
112+ /// Name policy for turns outside an operator direct chat
113+ /// (default: `deny_all`).
114+ #[ serde( default ) ]
115+ pub untrusted_tools : UntrustedTools ,
116+
107117 /// Default model for this account.
108118 #[ serde( skip_serializing_if = "Option::is_none" ) ]
109119 pub model : Option < String > ,
@@ -178,6 +188,8 @@ impl std::fmt::Debug for SlackAccountConfig {
178188 . field ( "allowlist" , & self . allowlist )
179189 . field ( "operators" , & self . operators )
180190 . field ( "channel_allowlist" , & self . channel_allowlist )
191+ . field ( "untrusted_audience" , & self . untrusted_audience )
192+ . field ( "untrusted_tools" , & self . untrusted_tools )
181193 . field ( "model" , & self . model )
182194 . field ( "model_provider" , & self . model_provider )
183195 . field ( "agent_id" , & self . agent_id )
@@ -210,6 +222,8 @@ impl Default for SlackAccountConfig {
210222 allowlist : Vec :: new ( ) ,
211223 operators : Vec :: new ( ) ,
212224 channel_allowlist : Vec :: new ( ) ,
225+ untrusted_audience : UntrustedAudience :: default ( ) ,
226+ untrusted_tools : UntrustedTools :: default ( ) ,
213227 model : None ,
214228 model_provider : None ,
215229 agent_id : None ,
@@ -241,6 +255,14 @@ impl ChannelConfigView for SlackAccountConfig {
241255 & self . channel_allowlist
242256 }
243257
258+ fn untrusted_audience ( & self ) -> UntrustedAudience {
259+ self . untrusted_audience
260+ }
261+
262+ fn untrusted_tools ( & self ) -> UntrustedTools {
263+ self . untrusted_tools
264+ }
265+
244266 fn dm_policy ( & self ) -> DmPolicy {
245267 self . dm_policy . clone ( )
246268 }
@@ -292,7 +314,7 @@ pub struct RedactedConfig<'a>(pub &'a SlackAccountConfig);
292314impl Serialize for RedactedConfig < ' _ > {
293315 fn serialize < S : serde:: Serializer > ( & self , serializer : S ) -> Result < S :: Ok , S :: Error > {
294316 let c = self . 0 ;
295- let mut count = 17 ; // always-present fields
317+ let mut count = 20 ; // always-present fields
296318 count += c. signing_secret . is_some ( ) as usize ;
297319 count += !c. reaction_trigger_emojis . is_empty ( ) as usize ;
298320 count += c. model . is_some ( ) as usize ;
@@ -314,6 +336,8 @@ impl Serialize for RedactedConfig<'_> {
314336 s. serialize_field ( "allowlist" , & c. allowlist ) ?;
315337 s. serialize_field ( "operators" , & c. operators ) ?;
316338 s. serialize_field ( "channel_allowlist" , & c. channel_allowlist ) ?;
339+ s. serialize_field ( "untrusted_audience" , & c. untrusted_audience ) ?;
340+ s. serialize_field ( "untrusted_tools" , & c. untrusted_tools ) ?;
317341 if c. model . is_some ( ) {
318342 s. serialize_field ( "model" , & c. model ) ?;
319343 }
@@ -365,6 +389,8 @@ mod tests {
365389 assert ! ( cfg. group_allowlist( ) . is_empty( ) ) ;
366390 assert_eq ! ( cfg. dm_policy( ) , DmPolicy :: Allowlist ) ;
367391 assert_eq ! ( cfg. group_policy( ) , GroupPolicy :: Open ) ;
392+ assert_eq ! ( cfg. untrusted_audience( ) , UntrustedAudience :: Public ) ;
393+ assert_eq ! ( cfg. untrusted_tools( ) , UntrustedTools :: DenyAll ) ;
368394 assert ! ( cfg. model( ) . is_none( ) ) ;
369395 assert ! ( cfg. model_provider( ) . is_none( ) ) ;
370396 }
@@ -479,6 +505,41 @@ mod tests {
479505 assert_eq ! ( redacted[ "ack_reactions" ] , serde_json:: json!( false ) ) ;
480506 }
481507
508+ #[ test]
509+ fn untrusted_tool_ceiling_round_trips_and_redacts ( ) {
510+ let cfg: SlackAccountConfig = serde_json:: from_value ( serde_json:: json!( {
511+ "bot_token" : "xoxb-test" ,
512+ "app_token" : "xapp-test" ,
513+ "untrusted_audience" : "trusted" ,
514+ "untrusted_tools" : "policy" ,
515+ } ) )
516+ . unwrap ( ) ;
517+
518+ assert_eq ! ( cfg. untrusted_audience( ) , UntrustedAudience :: Trusted ) ;
519+ assert_eq ! ( cfg. untrusted_tools( ) , UntrustedTools :: Policy ) ;
520+
521+ let stored = serde_json:: to_value ( & cfg) . unwrap ( ) ;
522+ let round_tripped: SlackAccountConfig = serde_json:: from_value ( stored) . unwrap ( ) ;
523+ assert_eq ! (
524+ round_tripped. untrusted_audience( ) ,
525+ UntrustedAudience :: Trusted
526+ ) ;
527+ assert_eq ! ( round_tripped. untrusted_tools( ) , UntrustedTools :: Policy ) ;
528+
529+ let redacted = serde_json:: to_value ( RedactedConfig ( & round_tripped) ) . unwrap ( ) ;
530+ assert_eq ! ( redacted[ "untrusted_audience" ] , "trusted" ) ;
531+ assert_eq ! ( redacted[ "untrusted_tools" ] , "policy" ) ;
532+ }
533+
534+ #[ test]
535+ fn invalid_untrusted_tool_ceiling_is_rejected ( ) {
536+ let invalid_audience = serde_json:: json!( { "untrusted_audience" : "everyone" } ) ;
537+ assert ! ( serde_json:: from_value:: <SlackAccountConfig >( invalid_audience) . is_err( ) ) ;
538+
539+ let invalid_tools = serde_json:: json!( { "untrusted_tools" : "allow_all" } ) ;
540+ assert ! ( serde_json:: from_value:: <SlackAccountConfig >( invalid_tools) . is_err( ) ) ;
541+ }
542+
482543 #[ test]
483544 fn connection_mode_events_api_round_trip ( ) {
484545 let json = serde_json:: json!( {
0 commit comments