zeph_config/notifications.rs
1// SPDX-FileCopyrightText: 2026 Andrei G <bug-ops>
2// SPDX-License-Identifier: MIT OR Apache-2.0
3
4//! Configuration for the per-turn completion notification subsystem.
5//!
6//! Notifications are best-effort, fire-and-forget signals sent after each agent turn
7//! completes. Two channels are supported: macOS native banners (via `osascript`)
8//! and an ntfy-compatible JSON webhook POST.
9//!
10//! # Defaults
11//!
12//! All fields default to disabled so existing configs are not affected.
13//!
14//! # Examples
15//!
16//! ```toml
17//! [notifications]
18//! enabled = true
19//! macos_native = true
20//! webhook_url = "https://ntfy.sh"
21//! webhook_topic = "my-topic-here"
22//! title = "Zeph"
23//! min_turn_duration_ms = 3000
24//! only_on_error = false
25//! ```
26
27use serde::{Deserialize, Serialize};
28
29fn default_title() -> String {
30 "Zeph".to_owned()
31}
32
33/// Configuration for the per-turn completion notifier.
34///
35/// Both channels (macOS and webhook) are independently enableable.
36/// At least one channel must be reachable for a notification to fire.
37// Config structs legitimately use multiple boolean flags — each maps to a distinct TOML key.
38#[allow(clippy::struct_excessive_bools)] // config struct — boolean flags are idiomatic for TOML-deserialized configuration
39#[derive(Debug, Clone, Deserialize, Serialize)]
40pub struct NotificationsConfig {
41 /// Master switch. When `false`, no notifications are sent regardless of other fields.
42 #[serde(default)]
43 pub enabled: bool,
44
45 /// Send a macOS Notification Center banner via `osascript`.
46 ///
47 /// Silently no-ops on non-macOS platforms.
48 #[serde(default)]
49 pub macos_native: bool,
50
51 /// URL for the ntfy-compatible webhook endpoint (e.g. `"https://ntfy.sh"`).
52 ///
53 /// Empty string or absent means the webhook channel is disabled.
54 #[serde(default)]
55 pub webhook_url: Option<String>,
56
57 /// ntfy topic. Required when `webhook_url` is set; ignored otherwise.
58 #[serde(default)]
59 pub webhook_topic: Option<String>,
60
61 /// Notification title shown in banners and webhook payloads.
62 #[serde(default = "default_title")]
63 pub title: String,
64
65 /// Minimum successful-turn wall-clock duration in milliseconds before a notification fires.
66 ///
67 /// Set to `0` to always notify. Does NOT apply to error turns — errors always fire
68 /// regardless of duration.
69 #[serde(default)]
70 pub min_turn_duration_ms: u64,
71
72 /// When `true`, only fire on turns that completed with an error.
73 #[serde(default)]
74 pub only_on_error: bool,
75
76 /// Allow non-HTTPS webhook URLs.
77 ///
78 /// When `false` (the default) only `https://` webhook URLs are accepted.
79 /// Set to `true` to allow `http://` URLs for local testing only — never use
80 /// in production as the notification payload is sent in plaintext.
81 #[serde(default)]
82 pub webhook_allow_insecure: bool,
83}
84
85impl Default for NotificationsConfig {
86 fn default() -> Self {
87 Self {
88 enabled: false,
89 macos_native: false,
90 webhook_url: None,
91 webhook_topic: None,
92 title: default_title(),
93 min_turn_duration_ms: 0,
94 only_on_error: false,
95 webhook_allow_insecure: false,
96 }
97 }
98}