feat(channels): add DingTalk channel via Stream Mode

Implement DingTalk messaging channel using the official Stream Mode
WebSocket protocol with per-message session webhook replies.

- Add DingTalkChannel with send/listen/health_check support
- Add DingTalkConfig (client_id, client_secret, allowed_users)
- Integrate with onboard wizard, integrations registry, and channel
  list/doctor commands
- Include unit tests for user allowlist rules and config serialization
This commit is contained in:
elonf 2026-02-17 00:02:05 +08:00
parent c11c569ddd
commit 9463bf08a4
5 changed files with 449 additions and 5 deletions

View file

@ -1198,6 +1198,7 @@ pub struct ChannelsConfig {
pub email: Option<crate::channels::email_channel::EmailConfig>,
pub irc: Option<IrcConfig>,
pub lark: Option<LarkConfig>,
pub dingtalk: Option<DingTalkConfig>,
}
impl Default for ChannelsConfig {
@ -1214,6 +1215,7 @@ impl Default for ChannelsConfig {
email: None,
irc: None,
lark: None,
dingtalk: None,
}
}
}
@ -1487,6 +1489,18 @@ impl Default for AuditConfig {
}
}
/// DingTalk (钉钉) configuration for Stream Mode messaging
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DingTalkConfig {
/// Client ID (AppKey) from DingTalk developer console
pub client_id: String,
/// Client Secret (AppSecret) from DingTalk developer console
pub client_secret: String,
/// Allowed user IDs (staff IDs). Empty = deny all, "*" = allow all
#[serde(default)]
pub allowed_users: Vec<String>,
}
// ── Config impl ──────────────────────────────────────────────────
impl Default for Config {
@ -1865,6 +1879,7 @@ mod tests {
email: None,
irc: None,
lark: None,
dingtalk: None,
},
memory: MemoryConfig::default(),
tunnel: TunnelConfig::default(),
@ -2127,6 +2142,7 @@ default_temperature = 0.7
email: None,
irc: None,
lark: None,
dingtalk: None,
};
let toml_str = toml::to_string_pretty(&c).unwrap();
let parsed: ChannelsConfig = toml::from_str(&toml_str).unwrap();
@ -2286,6 +2302,7 @@ channel_id = "C123"
email: None,
irc: None,
lark: None,
dingtalk: None,
};
let toml_str = toml::to_string_pretty(&c).unwrap();
let parsed: ChannelsConfig = toml::from_str(&toml_str).unwrap();