From 4d13a6c70a9217a004ec811b4d2a99e82afb8df4 Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Sun, 28 Jun 2026 21:20:32 -0400 Subject: [PATCH] Preserve MCP activity across thread switches --- codex-rs/tui/src/chatwidget/input_restore.rs | 17 +++++++++++++---- .../chatwidget/tests/composer_submission.rs | 3 ++- .../tui/src/chatwidget/tests/mcp_startup.rs | 18 ++++++++++++++++++ .../tui/src/chatwidget/tests/review_mode.rs | 3 ++- codex-rs/tui/src/chatwidget/user_messages.rs | 3 ++- 5 files changed, 37 insertions(+), 7 deletions(-) diff --git a/codex-rs/tui/src/chatwidget/input_restore.rs b/codex-rs/tui/src/chatwidget/input_restore.rs index 8e4c85301c..a096cd061d 100644 --- a/codex-rs/tui/src/chatwidget/input_restore.rs +++ b/codex-rs/tui/src/chatwidget/input_restore.rs @@ -379,13 +379,19 @@ impl ChatWidget { user_turn_pending_start: self.input_queue.user_turn_pending_start, current_collaboration_mode: self.current_collaboration_mode.clone(), active_collaboration_mask: self.active_collaboration_mask.clone(), - task_running: self.bottom_pane.is_task_running(), + foreground_task_running: self.bottom_pane.is_foreground_task_running(), + mcp_startup_running: self.bottom_pane.is_mcp_startup_running(), agent_turn_running: self.turn_lifecycle.agent_turn_running, }) } pub(crate) fn restore_thread_input_state(&mut self, input_state: Option) { - let restored_task_running = input_state.as_ref().is_some_and(|state| state.task_running); + let restored_foreground_task_running = input_state + .as_ref() + .is_some_and(|state| state.foreground_task_running); + let restored_mcp_startup_running = input_state + .as_ref() + .is_some_and(|state| state.mcp_startup_running); if let Some(input_state) = input_state { self.current_collaboration_mode = input_state.current_collaboration_mode; self.active_collaboration_mask = input_state.active_collaboration_mask; @@ -440,10 +446,13 @@ impl ChatWidget { self.turn_lifecycle .restore_running(self.turn_lifecycle.agent_turn_running, Instant::now()); self.update_foreground_task_running_state(); - if restored_task_running && !self.bottom_pane.is_task_running() { + if restored_foreground_task_running && !self.bottom_pane.is_foreground_task_running() { self.bottom_pane.set_task_running(/*running*/ true); - self.refresh_status_surfaces(); } + self.bottom_pane + .set_mcp_startup_running(restored_mcp_startup_running); + self.refresh_plan_mode_nudge(); + self.refresh_status_surfaces(); self.refresh_pending_input_preview(); self.request_redraw(); } diff --git a/codex-rs/tui/src/chatwidget/tests/composer_submission.rs b/codex-rs/tui/src/chatwidget/tests/composer_submission.rs index 238bc3609c..def19c22c1 100644 --- a/codex-rs/tui/src/chatwidget/tests/composer_submission.rs +++ b/codex-rs/tui/src/chatwidget/tests/composer_submission.rs @@ -1063,7 +1063,8 @@ async fn restore_thread_input_state_syncs_sleep_inhibitor_state() { user_turn_pending_start: false, current_collaboration_mode: chat.current_collaboration_mode.clone(), active_collaboration_mask: chat.active_collaboration_mask.clone(), - task_running: true, + foreground_task_running: true, + mcp_startup_running: false, agent_turn_running: true, })); diff --git a/codex-rs/tui/src/chatwidget/tests/mcp_startup.rs b/codex-rs/tui/src/chatwidget/tests/mcp_startup.rs index 7456046656..6c05599ddd 100644 --- a/codex-rs/tui/src/chatwidget/tests/mcp_startup.rs +++ b/codex-rs/tui/src/chatwidget/tests/mcp_startup.rs @@ -107,6 +107,24 @@ async fn mcp_startup_ignores_status_for_other_thread() { ); } +#[tokio::test] +async fn restored_mcp_only_thread_clears_busy_state_when_startup_finishes() { + let (mut chat, _rx, _op_rx) = make_chatwidget_manual(/*model_override*/ None).await; + chat.set_mcp_startup_expected_servers(["alpha".to_string()]); + notify_mcp_status(&mut chat, "alpha", McpServerStartupState::Starting); + let input_state = chat.capture_thread_input_state(); + + chat.restore_thread_input_state(/*input_state*/ None); + chat.restore_thread_input_state(input_state); + + assert!(!chat.bottom_pane.is_foreground_task_running()); + assert!(chat.bottom_pane.is_mcp_startup_running()); + + notify_mcp_status(&mut chat, "alpha", McpServerStartupState::Ready); + + assert!(!chat.bottom_pane.is_task_running()); +} + #[tokio::test] async fn review_command_during_mcp_startup_opens_popup_snapshot() { let (mut chat, _rx, _op_rx) = make_chatwidget_manual(/*model_override*/ None).await; diff --git a/codex-rs/tui/src/chatwidget/tests/review_mode.rs b/codex-rs/tui/src/chatwidget/tests/review_mode.rs index e8fe8ed300..abf566f330 100644 --- a/codex-rs/tui/src/chatwidget/tests/review_mode.rs +++ b/codex-rs/tui/src/chatwidget/tests/review_mode.rs @@ -385,7 +385,8 @@ async fn restore_thread_input_state_restores_pending_steers_without_downgrading_ user_turn_pending_start: false, current_collaboration_mode: chat.current_collaboration_mode.clone(), active_collaboration_mask: chat.active_collaboration_mask.clone(), - task_running: false, + foreground_task_running: false, + mcp_startup_running: false, agent_turn_running: false, })); diff --git a/codex-rs/tui/src/chatwidget/user_messages.rs b/codex-rs/tui/src/chatwidget/user_messages.rs index 6cfb739da5..d234dd2eb1 100644 --- a/codex-rs/tui/src/chatwidget/user_messages.rs +++ b/codex-rs/tui/src/chatwidget/user_messages.rs @@ -131,7 +131,8 @@ pub(crate) struct ThreadInputState { pub(super) user_turn_pending_start: bool, pub(super) current_collaboration_mode: CollaborationMode, pub(super) active_collaboration_mask: Option, - pub(super) task_running: bool, + pub(super) foreground_task_running: bool, + pub(super) mcp_startup_running: bool, pub(super) agent_turn_running: bool, }