Add TUI notification condition config (#17175)

Problem: TUI desktop notifications are hard-gated on terminal focus, so
terminal/IDE hosts that want in-focus notifications cannot opt in.

Solution: Add a flat `[tui] notification_condition` setting (`unfocused`
by default, `always` opt-in), carry grouped TUI notification settings
through runtime config, apply method + condition together in the TUI,
and regenerate the config schema.
This commit is contained in:
Eric Traut authored and GitHub committed 2026-04-08 21:50:02 -07:00
1 parent 2f9090be62
commit 6dc5391c7c
8 files changed
+191 -48

No files matched your search

+55 -15
View File
@@ -34,12 +34,14 @@ use codex_config::types::McpServerTransportConfig;
use codex_config::types::MemoriesConfig;
use codex_config::types::MemoriesToml;
use codex_config::types::ModelAvailabilityNuxConfig;
use codex_config::types::NotificationCondition;
use codex_config::types::NotificationMethod;
use codex_config::types::Notifications;
use codex_config::types::SandboxWorkspaceWrite;
use codex_config::types::SkillsConfig;
use codex_config::types::ToolSuggestDiscoverableType;
use codex_config::types::Tui;
use codex_config::types::TuiNotificationSettings;
use codex_features::Feature;
use codex_features::FeaturesToml;
use codex_model_provider_info::LMSTUDIO_OSS_PROVIDER_ID;
@@ -355,8 +357,7 @@ fn config_toml_deserializes_model_availability_nux() {
assert_eq!(
cfg.tui.expect("tui config should deserialize"),
Tui {
notifications: Notifications::default(),
notification_method: NotificationMethod::default(),
notification_settings: TuiNotificationSettings::default(),
animations: true,
show_tooltips: true,
alternate_screen: AltScreenMode::default(),
@@ -1052,8 +1053,7 @@ fn tui_config_missing_notifications_field_defaults_to_enabled() {
assert_eq!(
tui,
Tui {
notifications: Notifications::Enabled(true),
notification_method: NotificationMethod::Auto,
notification_settings: TuiNotificationSettings::default(),
animations: true,
show_tooltips: true,
alternate_screen: AltScreenMode::Auto,
@@ -4583,7 +4583,6 @@ fn test_precedence_fixture_with_o3_profile() -> std::io::Result<()> {
check_for_update_on_startup: true,
disable_paste_burst: false,
tui_notifications: Default::default(),
tui_notification_method: Default::default(),
animations: true,
show_tooltips: true,
model_availability_nux: ModelAvailabilityNuxConfig::default(),
@@ -4730,7 +4729,6 @@ fn test_precedence_fixture_with_gpt3_profile() -> std::io::Result<()> {
check_for_update_on_startup: true,
disable_paste_burst: false,
tui_notifications: Default::default(),
tui_notification_method: Default::default(),
animations: true,
show_tooltips: true,
model_availability_nux: ModelAvailabilityNuxConfig::default(),
@@ -4875,7 +4873,6 @@ fn test_precedence_fixture_with_zdr_profile() -> std::io::Result<()> {
check_for_update_on_startup: true,
disable_paste_burst: false,
tui_notifications: Default::default(),
tui_notification_method: Default::default(),
animations: true,
show_tooltips: true,
model_availability_nux: ModelAvailabilityNuxConfig::default(),
@@ -5006,7 +5003,6 @@ fn test_precedence_fixture_with_gpt5_profile() -> std::io::Result<()> {
check_for_update_on_startup: true,
disable_paste_burst: false,
tui_notifications: Default::default(),
tui_notification_method: Default::default(),
animations: true,
show_tooltips: true,
model_availability_nux: ModelAvailabilityNuxConfig::default(),
@@ -6559,10 +6555,8 @@ speaker = "Desk Speakers"
#[derive(Deserialize, Debug, PartialEq)]
struct TuiTomlTest {
#[serde(default)]
notifications: Notifications,
#[serde(default)]
notification_method: NotificationMethod,
#[serde(default, flatten)]
notifications: TuiNotificationSettings,
}
#[derive(Deserialize, Debug, PartialEq)]
@@ -6577,7 +6571,10 @@ fn test_tui_notifications_true() {
notifications = true
"#;
let parsed: RootTomlTest = toml::from_str(toml).expect("deserialize notifications=true");
assert_matches!(parsed.tui.notifications, Notifications::Enabled(true));
assert_matches!(
parsed.tui.notifications.notifications,
Notifications::Enabled(true)
);
}
#[test]
@@ -6588,7 +6585,7 @@ fn test_tui_notifications_custom_array() {
"#;
let parsed: RootTomlTest = toml::from_str(toml).expect("deserialize notifications=[\"foo\"]");
assert_matches!(
parsed.tui.notifications,
parsed.tui.notifications.notifications,
Notifications::Custom(ref v) if v == &vec!["foo".to_string()]
);
}
@@ -6601,5 +6598,48 @@ fn test_tui_notification_method() {
"#;
let parsed: RootTomlTest =
toml::from_str(toml).expect("deserialize notification_method=\"bel\"");
assert_eq!(parsed.tui.notification_method, NotificationMethod::Bel);
assert_eq!(parsed.tui.notifications.method, NotificationMethod::Bel);
}
#[test]
fn test_tui_notification_condition_defaults_to_unfocused() {
let toml = r#"
[tui]
"#;
let parsed: RootTomlTest =
toml::from_str(toml).expect("deserialize default notification condition");
assert_eq!(
parsed.tui.notifications.condition,
NotificationCondition::Unfocused
);
}
#[test]
fn test_tui_notification_condition_always() {
let toml = r#"
[tui]
notification_condition = "always"
"#;
let parsed: RootTomlTest =
toml::from_str(toml).expect("deserialize notification_condition=\"always\"");
assert_eq!(
parsed.tui.notifications.condition,
NotificationCondition::Always
);
}
#[test]
fn test_tui_notification_condition_rejects_unknown_value() {
let toml = r#"
[tui]
notification_condition = "background"
"#;
let err = toml::from_str::<RootTomlTest>(toml).expect_err("reject unknown condition");
let err = err.to_string();
assert!(
err.contains("unknown variant `background`")
&& err.contains("unfocused")
&& err.contains("always"),
"unexpected error: {err}"
);
}
+4 -14
View File
@@ -38,8 +38,6 @@ use codex_config::types::McpServerTransportConfig;
use codex_config::types::MemoriesConfig;
use codex_config::types::ModelAvailabilityNuxConfig;
use codex_config::types::Notice;
use codex_config::types::NotificationMethod;
use codex_config::types::Notifications;
use codex_config::types::OAuthCredentialsStoreMode;
use codex_config::types::OtelConfig;
use codex_config::types::OtelConfigToml;
@@ -47,6 +45,7 @@ use codex_config::types::OtelExporterKind;
use codex_config::types::ShellEnvironmentPolicy;
use codex_config::types::ToolSuggestConfig;
use codex_config::types::ToolSuggestDiscoverable;
use codex_config::types::TuiNotificationSettings;
use codex_config::types::UriBasedFileOpener;
use codex_config::types::WindowsSandboxModeToml;
use codex_features::Feature;
@@ -301,12 +300,8 @@ pub struct Config {
/// If unset the feature is disabled.
pub notify: Option<Vec<String>>,
/// 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,
/// TUI notification settings, including enabled events, delivery method, and focus condition.
pub tui_notifications: TuiNotificationSettings,
/// Enable ASCII animations and shimmer effects in the TUI.
pub animations: bool,
@@ -2136,12 +2131,7 @@ impl Config {
tui_notifications: cfg
.tui
.as_ref()
.map(|t| t.notifications.clone())
.unwrap_or_default(),
tui_notification_method: cfg
.tui
.as_ref()
.map(|t| t.notification_method)
.map(|t| t.notification_settings.clone())
.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),