diff --git a/codex-rs/tui/src/app.rs b/codex-rs/tui/src/app.rs index 8fc18a1d9b..e6f126c245 100644 --- a/codex-rs/tui/src/app.rs +++ b/codex-rs/tui/src/app.rs @@ -92,6 +92,7 @@ impl App { initial_images: Vec, resume_selection: ResumeSelection, feedback: codex_feedback::CodexFeedback, + initial_events: Vec, ) -> Result { use tokio_stream::StreamExt; let (app_event_tx, mut app_event_rx) = unbounded_channel(); @@ -170,11 +171,17 @@ impl App { pending_update_action: None, }; + for event in initial_events { + app.handle_event(tui, event).await?; + } + + // TODO(jif) clean this #[cfg(not(debug_assertions))] if let Some(latest_version) = upgrade_version { app.handle_event( tui, AppEvent::InsertHistoryCell(Box::new(UpdateAvailableHistoryCell::new( + crate::version::CODEX_CLI_VERSION, latest_version, crate::updates::get_update_action(), ))), diff --git a/codex-rs/tui/src/history_cell.rs b/codex-rs/tui/src/history_cell.rs index 0cbaf12037..29d05c2c5a 100644 --- a/codex-rs/tui/src/history_cell.rs +++ b/codex-rs/tui/src/history_cell.rs @@ -16,7 +16,6 @@ use crate::text_formatting::format_and_truncate_tool_result; use crate::text_formatting::truncate_text; use crate::ui_consts::LIVE_PREFIX_COLS; use crate::updates::UpdateAction; -use crate::version::CODEX_CLI_VERSION; use crate::wrapping::RtOptions; use crate::wrapping::word_wrap_line; use crate::wrapping::word_wrap_lines; @@ -265,15 +264,21 @@ impl HistoryCell for PlainHistoryCell { #[cfg_attr(debug_assertions, allow(dead_code))] #[derive(Debug)] pub(crate) struct UpdateAvailableHistoryCell { + current_version: String, latest_version: String, update_action: Option, } #[cfg_attr(debug_assertions, allow(dead_code))] impl UpdateAvailableHistoryCell { - pub(crate) fn new(latest_version: String, update_action: Option) -> Self { + pub(crate) fn new( + current_version: impl Into, + latest_version: impl Into, + update_action: Option, + ) -> Self { Self { - latest_version, + current_version: current_version.into(), + latest_version: latest_version.into(), update_action, } } @@ -298,7 +303,7 @@ impl HistoryCell for UpdateAvailableHistoryCell { padded_emoji("✨").bold().cyan(), "Update available!".bold().cyan(), " ", - format!("{CODEX_CLI_VERSION} -> {}", self.latest_version).bold(), + format!("{} -> {}", self.current_version, self.latest_version).bold(), ], update_instruction, "", diff --git a/codex-rs/tui/src/lib.rs b/codex-rs/tui/src/lib.rs index 32ca564694..9846479430 100644 --- a/codex-rs/tui/src/lib.rs +++ b/codex-rs/tui/src/lib.rs @@ -8,6 +8,7 @@ use app::App; pub use app::AppExitInfo; use codex_app_server_protocol::AuthMode; use codex_auto_updater::Error as AutoUpdateError; +use codex_auto_updater::UpdateStatus; use codex_core::AuthManager; use codex_core::BUILT_IN_OSS_MODEL_PROVIDER_ID; use codex_core::CodexAuth; @@ -80,6 +81,8 @@ mod wrapping; #[cfg(test)] pub mod test_backend; +use crate::app_event::AppEvent; +use crate::history_cell::UpdateAvailableHistoryCell; use crate::onboarding::TrustDirectorySelection; use crate::onboarding::WSL_INSTRUCTIONS; use crate::onboarding::onboarding_screen::OnboardingScreenArgs; @@ -257,36 +260,30 @@ pub async fn run_main( .try_init(); }; + let mut cached_update_status: Option = None; + match codex_auto_updater::initialize_storage(&config.codex_home) { Ok(_) => { - let t1 = std::time::Instant::now(); match codex_auto_updater::read_cached_status() { - Ok(Some(status)) if status.update_available => { - let t2 = std::time::Instant::now(); - tracing::warn!("Diff: {:?}", t2 - t1); - let current = status.current_version; - let latest = status.latest_version; - tracing::error!( - current_version = current.as_str(), - latest_version = latest.as_str(), - "A newer Codex release is available. Update Codex from {current} to {latest} with `brew upgrade codex`." - ); + Ok(Some(status)) => { + if status.update_available { + cached_update_status = Some(status); + } } - Ok(_) | Err(AutoUpdateError::Unsupported) => {} + Ok(None) | Err(AutoUpdateError::Unsupported) => {} Err(err) => { - tracing::error!(error = ?err, "Failed to read cached Codex update status"); + error!(error = ?err, "Failed to read cached Codex update status"); } } tokio::spawn(async move { if let Err(err) = codex_auto_updater::refresh_status().await { - tracing::error!(error = ?err, "Failed to refresh Codex update status"); + error!(error = ?err, "Failed to refresh Codex update status"); } - error!("Prop done"); }); } Err(err) => { - tracing::error!( + error!( error = ?err, "Failed to initialize internal storage for Codex updates" ); @@ -300,6 +297,7 @@ pub async fn run_main( cli_kv_overrides, active_profile, feedback, + cached_update_status, ) .await .map_err(|err| std::io::Error::other(err.to_string())) @@ -312,9 +310,33 @@ async fn run_ratatui_app( cli_kv_overrides: Vec<(String, toml::Value)>, active_profile: Option, feedback: codex_feedback::CodexFeedback, + cached_update_status: Option, ) -> color_eyre::Result { color_eyre::install()?; + let mut initial_events = Vec::new(); + if let Some(status) = cached_update_status { + if status.update_available { + let update_action = { + #[cfg(not(debug_assertions))] + { + crate::updates::get_update_action() + } + #[cfg(debug_assertions)] + { + None + } + }; + initial_events.push(AppEvent::InsertHistoryCell(Box::new( + UpdateAvailableHistoryCell::new( + status.current_version, + status.latest_version, + update_action, + ), + ))); + } + } + // Forward panic reports through tracing so they appear in the UI status // line, but do not swallow the default/color-eyre panic handler. // Chain to the previous hook so users still get a rich panic report @@ -484,6 +506,7 @@ async fn run_ratatui_app( images, resume_selection, feedback, + initial_events, ) .await;