diff --git a/codex-rs/tui/src/app.rs b/codex-rs/tui/src/app.rs index 7bd105695..b23b5f1a1 100644 --- a/codex-rs/tui/src/app.rs +++ b/codex-rs/tui/src/app.rs @@ -978,6 +978,27 @@ impl App { self.refresh_status_line(); } + fn should_wait_for_initial_session(session_selection: &SessionSelection) -> bool { + matches!( + session_selection, + SessionSelection::StartFresh | SessionSelection::Exit + ) + } + + fn should_handle_active_thread_events( + waiting_for_initial_session_configured: bool, + has_active_thread_receiver: bool, + ) -> bool { + has_active_thread_receiver && !waiting_for_initial_session_configured + } + + fn should_stop_waiting_for_initial_session( + waiting_for_initial_session_configured: bool, + primary_thread_id: Option, + ) -> bool { + waiting_for_initial_session_configured && primary_thread_id.is_some() + } + #[allow(clippy::too_many_arguments)] pub async fn run( tui: &mut tui::Tui, @@ -1068,6 +1089,8 @@ impl App { let status_line_invalid_items_warned = Arc::new(AtomicBool::new(false)); let enhanced_keys_supported = tui.enhanced_keys_supported(); + let wait_for_initial_session_configured = + Self::should_wait_for_initial_session(&session_selection); let mut chat_widget = match session_selection { SessionSelection::StartFresh | SessionSelection::Exit => { let init = crate::chatwidget::ChatWidgetInit { @@ -1252,6 +1275,7 @@ impl App { let mut thread_created_rx = thread_manager.subscribe_thread_created(); let mut listen_for_threads = true; + let mut waiting_for_initial_session_configured = wait_for_initial_session_configured; let exit_reason = loop { let control = select! { @@ -1264,7 +1288,10 @@ impl App { } else { None } - }, if app.active_thread_rx.is_some() => { + }, if App::should_handle_active_thread_events( + waiting_for_initial_session_configured, + app.active_thread_rx.is_some() + ) => { if let Some(event) = active { app.handle_active_thread_event(tui, event).await?; } else { @@ -1291,6 +1318,12 @@ impl App { AppRunControl::Continue } }; + if App::should_stop_waiting_for_initial_session( + waiting_for_initial_session_configured, + app.primary_thread_id, + ) { + waiting_for_initial_session_configured = false; + } match control { AppRunControl::Continue => {} AppRunControl::Exit(reason) => break reason, @@ -2915,6 +2948,76 @@ mod tests { Ok(()) } + #[test] + fn startup_waiting_gate_is_only_for_fresh_or_exit_session_selection() { + assert_eq!( + App::should_wait_for_initial_session(&SessionSelection::StartFresh), + true + ); + assert_eq!( + App::should_wait_for_initial_session(&SessionSelection::Exit), + true + ); + assert_eq!( + App::should_wait_for_initial_session(&SessionSelection::Resume(PathBuf::from( + "/tmp/restore" + ))), + false + ); + assert_eq!( + App::should_wait_for_initial_session(&SessionSelection::Fork(PathBuf::from( + "/tmp/fork" + ))), + false + ); + } + + #[test] + fn startup_waiting_gate_holds_active_thread_events_until_primary_thread_configured() { + let mut wait_for_initial_session = + App::should_wait_for_initial_session(&SessionSelection::StartFresh); + assert_eq!(wait_for_initial_session, true); + assert_eq!( + App::should_handle_active_thread_events(wait_for_initial_session, true), + false + ); + + assert_eq!( + App::should_stop_waiting_for_initial_session(wait_for_initial_session, None), + false + ); + if App::should_stop_waiting_for_initial_session( + wait_for_initial_session, + Some(ThreadId::new()), + ) { + wait_for_initial_session = false; + } + assert_eq!(wait_for_initial_session, false); + + assert_eq!( + App::should_handle_active_thread_events(wait_for_initial_session, true), + true + ); + } + + #[test] + fn startup_waiting_gate_not_applied_for_resume_or_fork_session_selection() { + let wait_for_resume = App::should_wait_for_initial_session(&SessionSelection::Resume( + PathBuf::from("/tmp/restore"), + )); + assert_eq!( + App::should_handle_active_thread_events(wait_for_resume, true), + true + ); + let wait_for_fork = App::should_wait_for_initial_session(&SessionSelection::Fork( + PathBuf::from("/tmp/fork"), + )); + assert_eq!( + App::should_handle_active_thread_events(wait_for_fork, true), + true + ); + } + #[tokio::test] async fn enqueue_thread_event_does_not_block_when_channel_full() -> Result<()> { let mut app = make_test_app().await;