Skip to content
12 changes: 12 additions & 0 deletions CHANGELOG-next.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,18 @@

---

## Breaking changes

### zeroclaw-runtime (Beta)

- `IntegrationStatus::ComingSoon` removed. Callers that match on it must drop that arm. Hand-written "planned" entries are gone; if a channel or tool is not in the schema or not a real runtime built-in, it does not appear in the integrations registry.
- `IntegrationCategory` variants `Productivity`, `MusicAudio`, `SmartHome`, `MediaCreative`, `Social` removed. Downstream `match` exhaustiveness will break at compile time. These categories had no live entries (only the now-removed `ComingSoon` placeholders).
- `Google Workspace` recategorised from `Productivity` (removed) to `ToolsAutomation`.
- `IntegrationEntry.status_fn: fn(&Config) -> IntegrationStatus` replaced by `IntegrationEntry.status: IntegrationStatus`. The catalog is now evaluated eagerly inside `all_integrations(&Config)` rather than carrying a per-entry closure.
- `all_integrations()` signature changed from `() -> Vec<IntegrationEntry>` to `(&Config) -> Vec<IntegrationEntry>`. Callers must thread a `Config` reference.

---

## What's New

### Architecture & Workspace
Expand Down
97 changes: 97 additions & 0 deletions crates/zeroclaw-config/src/schema.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2772,6 +2772,12 @@ impl Default for BrowserComputerUseConfig {
#[derive(Debug, Clone, Serialize, Deserialize, Configurable)]
#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
#[prefix = "browser"]
#[integration(
category = "ToolsAutomation",
display_name = "Browser",
description = "Chrome/Chromium control",
status_field = "enabled"
)]
pub struct BrowserConfig {
/// Enable `browser_open` tool (opens URLs in the system browser without scraping)
#[serde(default = "default_true")]
Expand Down Expand Up @@ -3435,6 +3441,12 @@ pub struct GoogleWorkspaceAllowedOperation {
#[derive(Debug, Clone, Serialize, Deserialize, Configurable)]
#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
#[prefix = "google-workspace"]
#[integration(
category = "ToolsAutomation",
display_name = "Google Workspace",
description = "Drive, Gmail, Calendar, Sheets, Docs via gws CLI",
status_field = "enabled"
)]
pub struct GoogleWorkspaceConfig {
/// Enable the `google_workspace` tool. Default: `false`.
#[serde(default)]
Expand Down Expand Up @@ -6375,6 +6387,12 @@ impl Default for HeartbeatConfig {
#[derive(Debug, Clone, Serialize, Deserialize, Configurable)]
#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
#[prefix = "cron"]
#[integration(
category = "ToolsAutomation",
display_name = "Cron",
description = "Scheduled tasks",
status_field = "enabled"
)]
pub struct CronConfig {
/// Enable the cron subsystem. Default: `true`.
#[serde(default = "default_true")]
Expand Down Expand Up @@ -6702,103 +6720,169 @@ pub struct ChannelsConfig {
pub cli: bool,
/// Telegram bot channel configuration.
#[nested]
#[display_name = "Telegram"]
#[description = "Bot API — long-polling"]
pub telegram: Option<TelegramConfig>,
/// Discord bot channel configuration.
#[nested]
#[display_name = "Discord"]
#[description = "Servers, channels & DMs"]
pub discord: Option<DiscordConfig>,
/// Discord history channel — logs ALL messages and forwards @mentions to agent.
#[nested]
#[display_name = "Discord History"]
#[description = "Logs all messages, forwards mentions to the agent"]
pub discord_history: Option<DiscordHistoryConfig>,
/// Slack bot channel configuration.
#[nested]
#[display_name = "Slack"]
#[description = "Workspace apps via Web API"]
pub slack: Option<SlackConfig>,
/// Mattermost bot channel configuration.
#[nested]
#[display_name = "Mattermost"]
#[description = "Self-hosted team chat"]
pub mattermost: Option<MattermostConfig>,
/// Webhook channel configuration.
#[nested]
#[display_name = "Webhooks"]
#[description = "HTTP endpoint for triggers"]
pub webhook: Option<WebhookConfig>,
/// iMessage channel configuration (macOS only).
#[nested]
#[display_name = "iMessage"]
#[description = "macOS AppleScript bridge"]
pub imessage: Option<IMessageConfig>,
/// Matrix channel configuration.
#[nested]
#[display_name = "Matrix"]
#[description = "Matrix protocol (Element)"]
pub matrix: Option<MatrixConfig>,
/// Signal channel configuration.
#[nested]
#[display_name = "Signal"]
#[description = "Privacy-focused via signal-cli"]
pub signal: Option<SignalConfig>,
/// WhatsApp channel configuration (Cloud API or Web mode).
#[nested]
#[display_name = "WhatsApp"]
#[description = "Meta Cloud API or Web mode"]
pub whatsapp: Option<WhatsAppConfig>,
/// Linq Partner API channel configuration.
#[nested]
#[display_name = "Linq"]
#[description = "Linq Partner API for iMessage/RCS/SMS"]
pub linq: Option<LinqConfig>,
/// WATI WhatsApp Business API channel configuration.
#[nested]
#[display_name = "WATI"]
#[description = "WhatsApp Business API gateway"]
pub wati: Option<WatiConfig>,
/// Nextcloud Talk bot channel configuration.
#[nested]
#[display_name = "Nextcloud Talk"]
#[description = "Self-hosted Nextcloud chat"]
pub nextcloud_talk: Option<NextcloudTalkConfig>,
/// Email channel configuration.
#[nested]
#[display_name = "Email"]
#[description = "IMAP / SMTP inbox bridge"]
pub email: Option<crate::scattered_types::EmailConfig>,
/// Gmail Pub/Sub push notification channel configuration.
#[nested]
#[display_name = "Gmail Push"]
#[description = "Pub/Sub push notifications for Gmail"]
pub gmail_push: Option<crate::scattered_types::GmailPushConfig>,
/// IRC channel configuration.
#[nested]
#[display_name = "IRC"]
#[description = "Classic IRC with SASL / NickServ"]
pub irc: Option<IrcConfig>,
/// Lark channel configuration.
#[nested]
#[display_name = "Lark"]
#[description = "ByteDance Lark / Feishu international"]
pub lark: Option<LarkConfig>,
/// LINE Messaging API channel configuration.
#[nested]
#[display_name = "LINE"]
#[description = "LINE Messaging API"]
pub line: Option<LineConfig>,
/// Feishu channel configuration.
#[nested]
#[display_name = "Feishu"]
#[description = "ByteDance Feishu (China)"]
pub feishu: Option<FeishuConfig>,
/// DingTalk channel configuration.
#[nested]
#[display_name = "DingTalk"]
#[description = "DingTalk Stream Mode"]
pub dingtalk: Option<DingTalkConfig>,
/// WeCom (WeChat Enterprise) Bot Webhook channel configuration.
#[nested]
#[display_name = "WeCom"]
#[description = "WeChat Enterprise Bot Webhook"]
pub wecom: Option<WeComConfig>,
/// WeChat personal iLink Bot channel configuration (QR code login).
#[nested]
#[display_name = "WeChat"]
#[description = "WeChat personal iLink Bot (QR login)"]
pub wechat: Option<WeChatConfig>,
/// QQ Official Bot channel configuration.
#[nested]
#[display_name = "QQ Official"]
#[description = "Tencent QQ Bot SDK"]
pub qq: Option<QQConfig>,
/// X/Twitter channel configuration.
#[nested]
#[display_name = "X / Twitter"]
#[description = "X / Twitter API"]
pub twitter: Option<TwitterConfig>,
/// Mochat customer service channel configuration.
#[nested]
#[display_name = "Mochat"]
#[description = "Mochat customer service"]
pub mochat: Option<MochatConfig>,
#[cfg(feature = "channel-nostr")]
#[nested]
#[display_name = "Nostr"]
#[description = "Decentralized DMs (NIP-04)"]
pub nostr: Option<NostrConfig>,
/// ClawdTalk voice channel configuration.
#[nested]
#[display_name = "ClawdTalk"]
#[description = "ClawdTalk voice channel"]
pub clawdtalk: Option<crate::scattered_types::ClawdTalkConfig>,
/// Reddit channel configuration (OAuth2 bot).
#[nested]
#[display_name = "Reddit"]
#[description = "Reddit OAuth2 bot"]
pub reddit: Option<RedditConfig>,
/// Bluesky channel configuration (AT Protocol).
#[nested]
#[display_name = "Bluesky"]
#[description = "Bluesky / AT Protocol"]
pub bluesky: Option<BlueskyConfig>,
/// Voice call channel configuration (Twilio/Telnyx/Plivo).
#[nested]
#[display_name = "Voice Call"]
#[description = "Twilio / Telnyx / Plivo voice calls"]
pub voice_call: Option<crate::scattered_types::VoiceCallConfig>,
/// Voice wake word detection channel configuration.
#[cfg(feature = "voice-wake")]
#[nested]
#[display_name = "Voice Wake"]
#[description = "Local wake-word detection"]
pub voice_wake: Option<VoiceWakeConfig>,
/// Voice duplex configuration (full-duplex voice over WebSocket).
#[nested]
#[display_name = "Voice Duplex"]
#[description = "Full-duplex voice over WebSocket"]
pub voice_duplex: Option<VoiceDuplexConfig>,
/// MQTT channel configuration (SOP listener).
#[nested]
#[display_name = "MQTT"]
#[description = "MQTT SOP listener"]
pub mqtt: Option<MqttConfig>,
/// Base timeout in seconds for processing a single channel message (LLM + tools).
/// Runtime uses this as a per-turn budget that scales with tool-loop depth
Expand Down Expand Up @@ -10017,6 +10101,19 @@ async fn ensure_bootstrap_files(workspace_dir: &Path) -> Result<()> {
}

impl Config {
/// Collect the `IntegrationDescriptor` from every nested config that
/// declares one via `#[integration(...)]`. Adding a new toggleable
/// integration is one struct-level attribute on the new config + one
/// row in this method. The integrations registry consumes the result
/// without per-vendor branches.
pub fn integration_descriptors(&self) -> Vec<crate::config::IntegrationDescriptor> {
vec![
self.browser.integration_descriptor(),
self.cron.integration_descriptor(),
self.google_workspace.integration_descriptor(),
]
}

/// Combine top-level `[cost.prices.<key>]` entries with any per-provider
/// `pricing` entries declared on `[providers.models.<id>]`. Per-provider
/// pricing is keyed as `<provider_id>/<model>` to align with the lookup
Expand Down
39 changes: 39 additions & 0 deletions crates/zeroclaw-config/src/traits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,45 @@ pub struct MapKeySection {
pub description: &'static str,
}

/// One row emitted by the `Configurable` derive's `nested_option_entries()`
/// method — every `#[nested] Option<XConfig>` field on a struct shows up here
/// with its `present` bit and the per-field `#[display_name = "..."]` /
/// `#[description = "..."]` metadata. The integrations registry consumes
/// this verbatim instead of carrying its own per-field hand-list.
#[derive(Debug, Clone, Copy)]
pub struct NestedOptionEntry {
/// snake_case field name on the parent struct (e.g. `"telegram"`,
/// `"voice_duplex"`).
pub field: &'static str,
/// `true` when the parent struct's field is `Some(_)`.
pub present: bool,
/// Display name from `#[display_name = "..."]`; falls back to a
/// title-cased rendering of the snake_case field name when the
/// attribute is absent.
pub display_name: &'static str,
/// One-line summary from `#[description = "..."]`. Empty when the
/// attribute is absent.
pub description: &'static str,
}

/// One row emitted by the `Configurable` derive's `integration_descriptor()`
/// method on structs annotated with `#[integration(...)]`. Used for nested
/// toggleable configs (e.g. `BrowserConfig`, `CronConfig`) where the
/// integration is "active" iff a named bool field on the struct is `true`.
#[derive(Debug, Clone, Copy)]
pub struct IntegrationDescriptor {
pub display_name: &'static str,
pub description: &'static str,
/// Free-form category label (e.g. `"ToolsAutomation"`). The
/// integrations registry maps this string to its own
/// `IntegrationCategory` enum so the schema crate doesn't have to
/// depend on it.
pub category: &'static str,
/// Snapshot of the named status field at the moment this descriptor
/// was built (`status_field = "enabled"` ⇒ `self.enabled`).
pub active: bool,
}

/// The trait for describing a channel
pub trait ChannelConfig {
/// human-readable name
Expand Down
14 changes: 6 additions & 8 deletions crates/zeroclaw-gateway/src/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -574,17 +574,16 @@ pub async fn handle_api_integrations(
}

let config = state.config.lock().clone();
let entries = zeroclaw_runtime::integrations::registry::all_integrations();
let entries = zeroclaw_runtime::integrations::registry::all_integrations(&config);

let integrations: Vec<serde_json::Value> = entries
.iter()
.map(|entry| {
let status = (entry.status_fn)(&config);
serde_json::json!({
"name": entry.name,
"description": entry.description,
"category": entry.category,
"status": status,
"status": entry.status,
})
})
.collect();
Expand All @@ -602,21 +601,20 @@ pub async fn handle_api_integrations_settings(
}

let config = state.config.lock().clone();
let entries = zeroclaw_runtime::integrations::registry::all_integrations();
let entries = zeroclaw_runtime::integrations::registry::all_integrations(&config);

let mut settings = serde_json::Map::new();
for entry in &entries {
let status = (entry.status_fn)(&config);
let enabled = matches!(
status,
entry.status,
zeroclaw_runtime::integrations::IntegrationStatus::Active
);
settings.insert(
entry.name.to_string(),
entry.name.clone(),
serde_json::json!({
"enabled": enabled,
"category": entry.category,
"status": status,
"status": entry.status,
}),
);
}
Expand Down
Loading
Loading