diff --git a/codex-rs/tui/src/notifications/mod.rs b/codex-rs/tui/src/notifications/mod.rs index ff5e3defae..fd5a601754 100644 --- a/codex-rs/tui/src/notifications/mod.rs +++ b/codex-rs/tui/src/notifications/mod.rs @@ -8,6 +8,8 @@ use bel::BelBackend; use codex_config::types::NotificationMethod; use osc9::Osc9Backend; +const WARP_CLI_AGENT_PROTOCOL_VERSION_ENV_VAR: &str = "WARP_CLI_AGENT_PROTOCOL_VERSION"; + #[derive(Debug)] pub enum DesktopNotificationBackend { Osc9(Osc9Backend), @@ -48,10 +50,19 @@ pub fn detect_backend(method: NotificationMethod) -> DesktopNotificationBackend DesktopNotificationBackend::for_method(method) } +pub(crate) fn warp_cli_agent_protocol_detected() -> bool { + env::var_os(WARP_CLI_AGENT_PROTOCOL_VERSION_ENV_VAR).is_some() +} + fn supports_osc9() -> bool { if env::var_os("WT_SESSION").is_some() { return false; } + // Warp declares OSC 9 support via its CLI agent protocol env var. This also + // covers supported Warp sessions across SSH where TERM_PROGRAM is absent. + if warp_cli_agent_protocol_detected() { + return true; + } // Prefer TERM_PROGRAM when present, but keep fallbacks for shells/launchers // that don't set it (e.g., tmux/ssh) to avoid regressing OSC 9 support. if matches!( @@ -73,6 +84,7 @@ fn supports_osc9() -> bool { #[cfg(test)] mod tests { + use super::WARP_CLI_AGENT_PROTOCOL_VERSION_ENV_VAR; use super::detect_backend; use codex_config::types::NotificationMethod; use serial_test::serial; @@ -133,6 +145,7 @@ mod tests { fn auto_prefers_bel_without_hints() { let _term = EnvVarGuard::remove("TERM"); let _term_program = EnvVarGuard::remove("TERM_PROGRAM"); + let _warp_protocol = EnvVarGuard::remove(WARP_CLI_AGENT_PROTOCOL_VERSION_ENV_VAR); let _iterm = EnvVarGuard::remove("ITERM_SESSION_ID"); let _wt = EnvVarGuard::remove("WT_SESSION"); assert!(matches!( @@ -141,11 +154,26 @@ mod tests { )); } + #[test] + #[serial] + fn auto_uses_osc9_for_warp_cli_agent_protocol() { + let _term = EnvVarGuard::remove("TERM"); + let _term_program = EnvVarGuard::remove("TERM_PROGRAM"); + let _warp_protocol = EnvVarGuard::set(WARP_CLI_AGENT_PROTOCOL_VERSION_ENV_VAR, "1"); + let _iterm = EnvVarGuard::remove("ITERM_SESSION_ID"); + let _wt = EnvVarGuard::remove("WT_SESSION"); + assert!(matches!( + detect_backend(NotificationMethod::Auto), + super::DesktopNotificationBackend::Osc9(_) + )); + } + #[test] #[serial] fn auto_uses_osc9_for_iterm() { let _term = EnvVarGuard::remove("TERM"); let _term_program = EnvVarGuard::remove("TERM_PROGRAM"); + let _warp_protocol = EnvVarGuard::remove(WARP_CLI_AGENT_PROTOCOL_VERSION_ENV_VAR); let _iterm = EnvVarGuard::set("ITERM_SESSION_ID", "abc"); let _wt = EnvVarGuard::remove("WT_SESSION"); assert!(matches!( diff --git a/codex-rs/tui/src/tui.rs b/codex-rs/tui/src/tui.rs index fc5d350527..1c917cd180 100644 --- a/codex-rs/tui/src/tui.rs +++ b/codex-rs/tui/src/tui.rs @@ -42,6 +42,7 @@ use crate::custom_terminal; use crate::custom_terminal::Terminal as CustomTerminal; use crate::notifications::DesktopNotificationBackend; use crate::notifications::detect_backend; +use crate::notifications::warp_cli_agent_protocol_detected; use crate::tui::event_stream::EventBroker; use crate::tui::event_stream::TuiEventStream; #[cfg(unix)] @@ -364,7 +365,9 @@ impl Tui { /// Emit a desktop notification now if the terminal is unfocused. /// Returns true if a notification was posted. pub fn notify(&mut self, message: impl AsRef) -> bool { - if self.terminal_focused.load(Ordering::Relaxed) { + // Warp handles notification visibility itself but does not relay focus + // events to the PTY, so Codex should not suppress its notifications here. + if self.terminal_focused.load(Ordering::Relaxed) && !warp_cli_agent_protocol_detected() { return false; }