Allow review while MCPs are loading

This commit is contained in:
Charlie Marsh
2026-06-29 09:39:13 -04:00
parent 45b3fd1788
commit 7807ffc65b
5 changed files with 79 additions and 4 deletions
+6 -3
View File
@@ -1810,9 +1810,6 @@ impl ChatWidget {
{
let op: AppCommand = op.into();
self.prepare_local_op_submission(&op);
if op.is_review() && !self.bottom_pane.is_task_running() {
self.bottom_pane.set_task_running(/*running*/ true);
}
match &self.codex_op_target {
CodexOpTarget::Direct(codex_op_tx) => {
crate::session_log::log_outbound_op(&op);
@@ -1838,6 +1835,12 @@ impl ChatWidget {
}
pub(crate) fn prepare_local_op_submission(&mut self, op: &AppCommand) {
if op.is_review() && !self.turn_lifecycle.agent_turn_running {
self.skip_mcp_startup_for_review();
if !self.bottom_pane.is_task_running() {
self.bottom_pane.set_task_running(/*running*/ true);
}
}
if let AppCommand::Interrupt { behavior } = op
&& self.turn_lifecycle.agent_turn_running
{
@@ -210,6 +210,23 @@ impl ChatWidget {
self.request_redraw();
}
pub(super) fn skip_mcp_startup_for_review(&mut self) {
if self.mcp_startup_status.is_none() {
return;
}
let mcp_startup_owned_status = self.status_header_is_mcp_startup_owned();
self.mcp_startup_status = None;
self.mcp_startup_ignore_updates_until_next_start = true;
self.mcp_startup_allow_terminal_only_next_round = false;
self.mcp_startup_pending_next_round.clear();
self.mcp_startup_pending_next_round_saw_starting = false;
if mcp_startup_owned_status {
self.set_status_header(String::from("Working"));
}
self.request_redraw();
}
pub(crate) fn finish_mcp_startup_after_lag(&mut self) {
if self.mcp_startup_ignore_updates_until_next_start {
if self.mcp_startup_pending_next_round.is_empty() {
@@ -132,6 +132,13 @@ impl ChatWidget {
}
fn slash_command_blocked_by_active_task(&self, cmd: SlashCommand) -> bool {
if cmd == SlashCommand::Review {
let mcp_startup_is_only_task = self.mcp_startup_status.is_some()
&& !self.turn_lifecycle.agent_turn_running
&& !self.input_queue.user_turn_pending_start
&& !self.review.is_review_mode;
return self.bottom_pane.is_task_running() && !mcp_startup_is_only_task;
}
(!cmd.available_during_task() && self.bottom_pane.is_task_running())
|| (cmd == SlashCommand::Resume
&& (self.input_queue.user_turn_pending_start
@@ -69,6 +69,53 @@ async fn mcp_startup_ignores_status_for_other_thread() {
);
}
#[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;
notify_mcp_status(&mut chat, "alpha", McpServerStartupState::Starting);
chat.dispatch_command(SlashCommand::Review);
let popup = render_bottom_popup(&chat, /*width*/ 80);
assert_chatwidget_snapshot!("review_command_during_mcp_startup_opens_popup", popup);
}
#[tokio::test]
async fn review_with_args_during_mcp_startup_skips_local_startup_round() {
let (mut chat, _rx, mut op_rx) = make_chatwidget_manual(/*model_override*/ None).await;
notify_mcp_status(&mut chat, "alpha", McpServerStartupState::Starting);
chat.dispatch_command_with_args(
SlashCommand::Review,
"check regressions".to_string(),
Vec::new(),
);
assert_matches!(
op_rx.try_recv(),
Ok(Op::Review {
target: ReviewTarget::Custom { instructions }
}) if instructions == "check regressions"
);
assert!(chat.mcp_startup_status.is_none());
assert!(chat.bottom_pane.is_task_running());
assert_eq!(chat.status_state.current_status.header, "Working");
}
#[tokio::test]
async fn review_command_remains_blocked_during_agent_turn() {
let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(/*model_override*/ None).await;
handle_turn_started(&mut chat, "turn-1");
chat.dispatch_command(SlashCommand::Review);
let error = drain_insert_history(&mut rx)
.into_iter()
.map(|lines| lines_to_single_string(&lines))
.collect::<String>();
assert!(error.contains("'/review' is disabled while a task is in progress."));
}
#[tokio::test]
async fn mcp_startup_dedupes_same_round_duplicate_failure_warning() {
let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(/*model_override*/ None).await;
+2 -1
View File
@@ -200,7 +200,6 @@ impl SlashCommand {
| SlashCommand::Experimental
| SlashCommand::Memories
| SlashCommand::Import
| SlashCommand::Review
| SlashCommand::Plan
| SlashCommand::Clear
| SlashCommand::Logout
@@ -208,6 +207,7 @@ impl SlashCommand {
| SlashCommand::MemoryUpdate => false,
SlashCommand::Diff
| SlashCommand::Resume
| SlashCommand::Review
| SlashCommand::Model
| SlashCommand::Personality
| SlashCommand::Permissions
@@ -295,6 +295,7 @@ mod tests {
assert!(SlashCommand::Raw.available_in_side_conversation());
assert!(SlashCommand::Raw.supports_inline_args());
assert!(SlashCommand::App.available_during_task());
assert!(SlashCommand::Review.available_during_task());
}
#[test]