Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions codex-rs/core/config.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -483,6 +483,14 @@
},
"type": "object"
},
"NotificationMethod": {
"enum": [
"auto",
"osc9",
"bel"
],
"type": "string"
},
"Notifications": {
"anyOf": [
{
Expand Down Expand Up @@ -991,6 +999,15 @@
"default": null,
"description": "Start the TUI in the specified collaboration mode (plan/execute/etc.). Defaults to unset."
},
"notification_method": {
"allOf": [
{
"$ref": "#/definitions/NotificationMethod"
}
],
"default": "auto",
"description": "Notification method to use for unfocused terminal notifications. Defaults to `auto`."
},
"notifications": {
"allOf": [
{
Expand Down
34 changes: 32 additions & 2 deletions codex-rs/core/src/config/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use crate::config::types::McpServerConfig;
use crate::config::types::McpServerDisabledReason;
use crate::config::types::McpServerTransportConfig;
use crate::config::types::Notice;
use crate::config::types::NotificationMethod;
use crate::config::types::Notifications;
use crate::config::types::OtelConfig;
use crate::config::types::OtelConfigToml;
Expand Down Expand Up @@ -192,10 +193,13 @@ pub struct Config {
/// If unset the feature is disabled.
pub notify: Option<Vec<String>>,

/// TUI notifications preference. When set, the TUI will send OSC 9 notifications on approvals
/// and turn completions when not focused.
/// TUI notifications preference. When set, the TUI will send terminal notifications on
/// approvals and turn completions when not focused.
pub tui_notifications: Notifications,

/// Notification method for terminal notifications (osc9 or bel).
pub tui_notification_method: NotificationMethod,

/// Enable ASCII animations and shimmer effects in the TUI.
pub animations: bool,

Expand Down Expand Up @@ -1607,6 +1611,11 @@ impl Config {
.as_ref()
.map(|t| t.notifications.clone())
.unwrap_or_default(),
tui_notification_method: cfg
.tui
.as_ref()
.map(|t| t.notification_method)
.unwrap_or_default(),
animations: cfg.tui.as_ref().map(|t| t.animations).unwrap_or(true),
show_tooltips: cfg.tui.as_ref().map(|t| t.show_tooltips).unwrap_or(true),
experimental_mode: cfg.tui.as_ref().and_then(|t| t.experimental_mode),
Expand Down Expand Up @@ -1764,6 +1773,7 @@ mod tests {
use crate::config::types::FeedbackConfigToml;
use crate::config::types::HistoryPersistence;
use crate::config::types::McpServerTransportConfig;
use crate::config::types::NotificationMethod;
use crate::config::types::Notifications;
use crate::config_loader::RequirementSource;
use crate::features::Feature;
Expand Down Expand Up @@ -1860,6 +1870,7 @@ persistence = "none"
tui,
Tui {
notifications: Notifications::Enabled(true),
notification_method: NotificationMethod::Auto,
animations: true,
show_tooltips: true,
experimental_mode: None,
Expand Down Expand Up @@ -3788,6 +3799,7 @@ model_verbosity = "high"
check_for_update_on_startup: true,
disable_paste_burst: false,
tui_notifications: Default::default(),
tui_notification_method: Default::default(),
animations: true,
show_tooltips: true,
experimental_mode: None,
Expand Down Expand Up @@ -3871,6 +3883,7 @@ model_verbosity = "high"
check_for_update_on_startup: true,
disable_paste_burst: false,
tui_notifications: Default::default(),
tui_notification_method: Default::default(),
animations: true,
show_tooltips: true,
experimental_mode: None,
Expand Down Expand Up @@ -3969,6 +3982,7 @@ model_verbosity = "high"
check_for_update_on_startup: true,
disable_paste_burst: false,
tui_notifications: Default::default(),
tui_notification_method: Default::default(),
animations: true,
show_tooltips: true,
experimental_mode: None,
Expand Down Expand Up @@ -4053,6 +4067,7 @@ model_verbosity = "high"
check_for_update_on_startup: true,
disable_paste_burst: false,
tui_notifications: Default::default(),
tui_notification_method: Default::default(),
animations: true,
show_tooltips: true,
experimental_mode: None,
Expand Down Expand Up @@ -4410,13 +4425,17 @@ mcp_oauth_callback_port = 5678

#[cfg(test)]
mod notifications_tests {
use crate::config::types::NotificationMethod;
use crate::config::types::Notifications;
use assert_matches::assert_matches;
use serde::Deserialize;

#[derive(Deserialize, Debug, PartialEq)]
struct TuiTomlTest {
#[serde(default)]
notifications: Notifications,
#[serde(default)]
notification_method: NotificationMethod,
}

#[derive(Deserialize, Debug, PartialEq)]
Expand Down Expand Up @@ -4447,4 +4466,15 @@ mod notifications_tests {
Notifications::Custom(ref v) if v == &vec!["foo".to_string()]
);
}

#[test]
fn test_tui_notification_method() {
let toml = r#"
[tui]
notification_method = "bel"
"#;
let parsed: RootTomlTest =
toml::from_str(toml).expect("deserialize notification_method=\"bel\"");
assert_eq!(parsed.tui.notification_method, NotificationMethod::Bel);
}
}
24 changes: 24 additions & 0 deletions codex-rs/core/src/config/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -428,6 +428,25 @@ impl Default for Notifications {
}
}

#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, Default)]
#[serde(rename_all = "lowercase")]
pub enum NotificationMethod {
#[default]
Auto,
Osc9,
Bel,
}

impl fmt::Display for NotificationMethod {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
NotificationMethod::Auto => write!(f, "auto"),
NotificationMethod::Osc9 => write!(f, "osc9"),
NotificationMethod::Bel => write!(f, "bel"),
}
}
}

/// Collection of settings that are specific to the TUI.
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default, JsonSchema)]
#[schemars(deny_unknown_fields)]
Expand All @@ -437,6 +456,11 @@ pub struct Tui {
#[serde(default)]
pub notifications: Notifications,

/// Notification method to use for unfocused terminal notifications.
/// Defaults to `auto`.
#[serde(default)]
pub notification_method: NotificationMethod,

/// Enable animations (welcome screen, shimmer effects, spinners).
/// Defaults to `true`.
#[serde(default = "default_true")]
Expand Down
2 changes: 2 additions & 0 deletions codex-rs/tui/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -923,6 +923,7 @@ impl App {
let app_event_tx = AppEventSender::new(app_event_tx);
emit_deprecation_notice(&app_event_tx, ollama_chat_support_notice);
emit_project_config_warnings(&app_event_tx, &config);
tui.set_notification_method(config.tui_notification_method);

let harness_overrides =
normalize_harness_overrides_for_cwd(harness_overrides, &config.cwd)?;
Expand Down Expand Up @@ -1336,6 +1337,7 @@ impl App {
Ok(resumed) => {
self.shutdown_current_thread().await;
self.config = resume_config;
tui.set_notification_method(self.config.tui_notification_method);
self.file_search = FileSearchManager::new(
self.config.cwd.clone(),
self.app_event_tx.clone(),
Expand Down
37 changes: 37 additions & 0 deletions codex-rs/tui/src/notifications/bel.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
use std::fmt;
use std::io;
use std::io::stdout;

use crossterm::Command;
use ratatui::crossterm::execute;

#[derive(Debug, Default)]
pub struct BelBackend;

impl BelBackend {
pub fn notify(&mut self, _message: &str) -> io::Result<()> {
execute!(stdout(), PostNotification)
}
}

/// Command that emits a BEL desktop notification.
#[derive(Debug, Clone)]
pub struct PostNotification;

impl Command for PostNotification {
fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result {
write!(f, "\x07")
}

#[cfg(windows)]
fn execute_winapi(&self) -> io::Result<()> {
Err(std::io::Error::other(
"tried to execute PostNotification using WinAPI; use ANSI instead",
))
}

#[cfg(windows)]
fn is_ansi_code_supported(&self) -> bool {
true
}
}
Loading
Loading