zeroclaw/src/config/mod.rs
Nim G 2b8297dc31
feat(channel): add interrupt_on_new_message support for Discord (#3918)
* feat(channel): add /stop command to cancel in-flight tasks

Adds an explicit /stop slash command that allows users on any non-CLI
channel (Matrix, Telegram, Discord, Slack, etc.) to cancel an agent
task that is currently running.

Changes:
- is_stop_command(): new helper that detects /stop (case-insensitive,
  optional @botname suffix), not gated on channel type
- /stop fast path in run_message_dispatch_loop: intercepts /stop before
  semaphore acquisition so the target task is never replaced in the store;
  fires CancellationToken on the running task; sends reply via tokio::spawn
  using the established two-step channel lookup pattern
- register_in_flight separated from interrupt_enabled: all non-CLI tasks
  now enter the in_flight_by_sender store, enabling /stop to reach them;
  auto-cancel-on-new-message remains gated on interrupt_enabled (Telegram/
  Slack only) — this is a deliberate broadening, not a side effect

Deferred to follow-up (feat/matrix-interrupt-on-new-message):
- interrupt_on_new_message config field for Matrix
- thread-aware interruption_scope_key (requires per-channel thread_ts
  semantics analysis; Slack always sets thread_ts, Matrix only for replies)

Supersedes #2855

Tests: 7 new unit tests for is_stop_command; all 4075 tests pass.

* feat(channel): add interrupt_on_new_message support for Discord

---------

Co-authored-by: argenis de la rosa <theonlyhennygod@gmail.com>
2026-03-24 15:30:31 +03:00

105 lines
4.4 KiB
Rust

pub mod schema;
pub mod traits;
pub mod workspace;
#[allow(unused_imports)]
pub use schema::{
apply_runtime_proxy_to_builder, build_runtime_proxy_client,
build_runtime_proxy_client_with_timeouts, runtime_proxy_config, set_runtime_proxy_config,
AgentConfig, AssemblyAiSttConfig, AuditConfig, AutonomyConfig, BackupConfig,
BrowserComputerUseConfig, BrowserConfig, BuiltinHooksConfig, ChannelsConfig,
ClassificationRule, CloudOpsConfig, ComposioConfig, Config, ConversationalAiConfig, CostConfig,
CronConfig, DataRetentionConfig, DeepgramSttConfig, DelegateAgentConfig, DelegateToolConfig,
DiscordConfig, DockerRuntimeConfig, EdgeTtsConfig, ElevenLabsTtsConfig, EmbeddingRouteConfig,
EstopConfig, FeishuConfig, GatewayConfig, GoogleSttConfig, GoogleTtsConfig,
GoogleWorkspaceConfig, HardwareConfig, HardwareTransport, HeartbeatConfig, HooksConfig,
HttpRequestConfig, IMessageConfig, IdentityConfig, ImageProviderDalleConfig,
ImageProviderFluxConfig, ImageProviderImagenConfig, ImageProviderStabilityConfig, JiraConfig,
KnowledgeConfig, LarkConfig, LinkedInConfig, LinkedInContentConfig, LinkedInImageConfig,
MatrixConfig, McpConfig, McpServerConfig, McpTransport, MemoryConfig, Microsoft365Config,
ModelRouteConfig, MultimodalConfig, NextcloudTalkConfig, NodeTransportConfig, NodesConfig,
NotionConfig, ObservabilityConfig, OpenAiSttConfig, OpenAiTtsConfig, OpenVpnTunnelConfig,
OtpConfig, OtpMethod, PeripheralBoardConfig, PeripheralsConfig, PluginsConfig,
ProjectIntelConfig, ProxyConfig, ProxyScope, QdrantConfig, QueryClassificationConfig,
ReliabilityConfig, ResourceLimitsConfig, RuntimeConfig, SandboxBackend, SandboxConfig,
SchedulerConfig, SecretsConfig, SecurityConfig, SecurityOpsConfig, SkillCreationConfig,
SkillsConfig, SkillsPromptInjectionMode, SlackConfig, StorageConfig, StorageProviderConfig,
StorageProviderSection, StreamMode, SwarmConfig, SwarmStrategy, TelegramConfig,
ToolFilterGroup, ToolFilterGroupMode, TranscriptionConfig, TtsConfig, TunnelConfig,
WebFetchConfig, WebSearchConfig, WebhookConfig, WorkspaceConfig,
};
pub fn name_and_presence<T: traits::ChannelConfig>(channel: Option<&T>) -> (&'static str, bool) {
(T::name(), channel.is_some())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn reexported_config_default_is_constructible() {
let config = Config::default();
assert!(config.default_provider.is_some());
assert!(config.default_model.is_some());
assert!(config.default_temperature > 0.0);
}
#[test]
fn reexported_channel_configs_are_constructible() {
let telegram = TelegramConfig {
bot_token: "token".into(),
allowed_users: vec!["alice".into()],
stream_mode: StreamMode::default(),
draft_update_interval_ms: 1000,
interrupt_on_new_message: false,
mention_only: false,
ack_reactions: None,
};
let discord = DiscordConfig {
bot_token: "token".into(),
guild_id: Some("123".into()),
allowed_users: vec![],
listen_to_bots: false,
interrupt_on_new_message: false,
mention_only: false,
};
let lark = LarkConfig {
app_id: "app-id".into(),
app_secret: "app-secret".into(),
encrypt_key: None,
verification_token: None,
allowed_users: vec![],
mention_only: false,
use_feishu: false,
receive_mode: crate::config::schema::LarkReceiveMode::Websocket,
port: None,
};
let feishu = FeishuConfig {
app_id: "app-id".into(),
app_secret: "app-secret".into(),
encrypt_key: None,
verification_token: None,
allowed_users: vec![],
receive_mode: crate::config::schema::LarkReceiveMode::Websocket,
port: None,
};
let nextcloud_talk = NextcloudTalkConfig {
base_url: "https://cloud.example.com".into(),
app_token: "app-token".into(),
webhook_secret: None,
allowed_users: vec!["*".into()],
};
assert_eq!(telegram.allowed_users.len(), 1);
assert_eq!(discord.guild_id.as_deref(), Some("123"));
assert_eq!(lark.app_id, "app-id");
assert_eq!(feishu.app_id, "app-id");
assert_eq!(nextcloud_talk.base_url, "https://cloud.example.com");
}
}