mirror of
https://github.com/openai/codex.git
synced 2026-11-03 16:23:51 +00:00
Fix app-server TUI MCP startup warnings regression (#16041)
This addresses #16038 The default `tui_app_server` path stopped surfacing MCP startup failures during cold start, even though the legacy TUI still showed warnings like `MCP startup incomplete (...)`. The app-server bridge emitted per-server startup status notifications, but `tui_app_server` ignored them, so failed MCP handshakes could look like a clean startup. This change teaches `tui_app_server` to consume MCP startup status notifications, preserve the immediate per-server failure warning, and synthesize the same aggregate startup warning the legacy TUI shows once startup settles.
This commit is contained in:
1 parent
7880414a27
commit
54d3ad1ede
4 files changed
+854
-16
No files matched your search
+15
@@ -0,0 +1,15 @@
|
||||
---
|
||||
source: tui/src/chatwidget/tests.rs
|
||||
assertion_line: 11761
|
||||
expression: term.backend().vt100().screen().contents()
|
||||
---
|
||||
|
||||
|
||||
|
||||
⚠ MCP client for `alpha` failed to start: handshake failed
|
||||
⚠ MCP startup incomplete (failed: alpha)
|
||||
|
||||
|
||||
› Ask Codex to do anything
|
||||
|
||||
gpt-5.3-codex default · 100% left · /tmp/project
|
||||
@@ -50,6 +50,8 @@ use codex_app_server_protocol::ItemGuardianApprovalReviewCompletedNotification;
|
||||
use codex_app_server_protocol::ItemGuardianApprovalReviewStartedNotification;
|
||||
use codex_app_server_protocol::ItemStartedNotification;
|
||||
use codex_app_server_protocol::MarketplaceInterface;
|
||||
use codex_app_server_protocol::McpServerStartupState;
|
||||
use codex_app_server_protocol::McpServerStatusUpdatedNotification;
|
||||
use codex_app_server_protocol::PatchApplyStatus as AppServerPatchApplyStatus;
|
||||
use codex_app_server_protocol::PatchChangeKind;
|
||||
use codex_app_server_protocol::PluginAuthPolicy;
|
||||
@@ -2132,6 +2134,11 @@ async fn make_chatwidget_manual(
|
||||
unified_exec_processes: Vec::new(),
|
||||
agent_turn_running: false,
|
||||
mcp_startup_status: None,
|
||||
mcp_startup_expected_servers: None,
|
||||
mcp_startup_ignore_updates_until_next_start: false,
|
||||
mcp_startup_allow_terminal_only_next_round: false,
|
||||
mcp_startup_pending_next_round: HashMap::new(),
|
||||
mcp_startup_pending_next_round_saw_starting: false,
|
||||
connectors_cache: ConnectorsCacheState::default(),
|
||||
connectors_partial_snapshot: None,
|
||||
plugin_install_apps_needing_auth: Vec::new(),
|
||||
@@ -11667,6 +11674,611 @@ async fn mcp_startup_complete_does_not_clear_running_task() {
|
||||
assert!(chat.bottom_pane.status_indicator_visible());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn app_server_mcp_startup_failure_renders_warning_history() {
|
||||
let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(/*model_override*/ None).await;
|
||||
chat.show_welcome_banner = false;
|
||||
chat.set_mcp_startup_expected_servers(["alpha".to_string(), "beta".to_string()]);
|
||||
|
||||
chat.handle_server_notification(
|
||||
ServerNotification::McpServerStatusUpdated(McpServerStatusUpdatedNotification {
|
||||
name: "alpha".to_string(),
|
||||
status: McpServerStartupState::Starting,
|
||||
error: None,
|
||||
}),
|
||||
/*replay_kind*/ None,
|
||||
);
|
||||
|
||||
assert!(drain_insert_history(&mut rx).is_empty());
|
||||
assert!(chat.bottom_pane.is_task_running());
|
||||
|
||||
chat.handle_server_notification(
|
||||
ServerNotification::McpServerStatusUpdated(McpServerStatusUpdatedNotification {
|
||||
name: "alpha".to_string(),
|
||||
status: McpServerStartupState::Failed,
|
||||
error: Some("MCP client for `alpha` failed to start: handshake failed".to_string()),
|
||||
}),
|
||||
/*replay_kind*/ None,
|
||||
);
|
||||
|
||||
let failure_cells = drain_insert_history(&mut rx);
|
||||
let failure_text = failure_cells
|
||||
.iter()
|
||||
.map(|lines| lines_to_single_string(lines))
|
||||
.collect::<String>();
|
||||
assert!(failure_text.contains("MCP client for `alpha` failed to start: handshake failed"));
|
||||
assert!(!failure_text.contains("MCP startup incomplete"));
|
||||
assert!(chat.bottom_pane.is_task_running());
|
||||
|
||||
chat.handle_server_notification(
|
||||
ServerNotification::McpServerStatusUpdated(McpServerStatusUpdatedNotification {
|
||||
name: "beta".to_string(),
|
||||
status: McpServerStartupState::Starting,
|
||||
error: None,
|
||||
}),
|
||||
/*replay_kind*/ None,
|
||||
);
|
||||
|
||||
assert!(drain_insert_history(&mut rx).is_empty());
|
||||
assert!(chat.bottom_pane.is_task_running());
|
||||
|
||||
chat.handle_server_notification(
|
||||
ServerNotification::McpServerStatusUpdated(McpServerStatusUpdatedNotification {
|
||||
name: "beta".to_string(),
|
||||
status: McpServerStartupState::Ready,
|
||||
error: None,
|
||||
}),
|
||||
/*replay_kind*/ None,
|
||||
);
|
||||
|
||||
let summary_cells = drain_insert_history(&mut rx);
|
||||
let summary_text = summary_cells
|
||||
.iter()
|
||||
.map(|lines| lines_to_single_string(lines))
|
||||
.collect::<String>();
|
||||
assert_eq!(summary_text, "⚠ MCP startup incomplete (failed: alpha)\n");
|
||||
assert!(!chat.bottom_pane.is_task_running());
|
||||
|
||||
let width: u16 = 120;
|
||||
let ui_height: u16 = chat.desired_height(width);
|
||||
let vt_height: u16 = 10;
|
||||
let viewport = Rect::new(0, vt_height - ui_height - 1, width, ui_height);
|
||||
|
||||
let backend = VT100Backend::new(width, vt_height);
|
||||
let mut term = crate::custom_terminal::Terminal::with_options(backend).expect("terminal");
|
||||
term.set_viewport_area(viewport);
|
||||
|
||||
for lines in failure_cells.into_iter().chain(summary_cells) {
|
||||
crate::insert_history::insert_history_lines(&mut term, lines)
|
||||
.expect("Failed to insert history lines in test");
|
||||
}
|
||||
|
||||
term.draw(|f| {
|
||||
chat.render(f.area(), f.buffer_mut());
|
||||
})
|
||||
.expect("draw MCP startup warning history");
|
||||
|
||||
assert_snapshot!(
|
||||
"app_server_mcp_startup_failure_renders_warning_history",
|
||||
term.backend().vt100().screen().contents()
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn app_server_mcp_startup_lag_settles_startup_and_ignores_late_updates() {
|
||||
let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(/*model_override*/ None).await;
|
||||
chat.show_welcome_banner = false;
|
||||
chat.set_mcp_startup_expected_servers(["alpha".to_string(), "beta".to_string()]);
|
||||
|
||||
chat.handle_server_notification(
|
||||
ServerNotification::McpServerStatusUpdated(McpServerStatusUpdatedNotification {
|
||||
name: "alpha".to_string(),
|
||||
status: McpServerStartupState::Starting,
|
||||
error: None,
|
||||
}),
|
||||
/*replay_kind*/ None,
|
||||
);
|
||||
chat.handle_server_notification(
|
||||
ServerNotification::McpServerStatusUpdated(McpServerStatusUpdatedNotification {
|
||||
name: "alpha".to_string(),
|
||||
status: McpServerStartupState::Failed,
|
||||
error: Some("MCP client for `alpha` failed to start: handshake failed".to_string()),
|
||||
}),
|
||||
/*replay_kind*/ None,
|
||||
);
|
||||
chat.handle_server_notification(
|
||||
ServerNotification::McpServerStatusUpdated(McpServerStatusUpdatedNotification {
|
||||
name: "beta".to_string(),
|
||||
status: McpServerStartupState::Starting,
|
||||
error: None,
|
||||
}),
|
||||
/*replay_kind*/ None,
|
||||
);
|
||||
|
||||
let _ = drain_insert_history(&mut rx);
|
||||
assert!(chat.bottom_pane.is_task_running());
|
||||
|
||||
chat.finish_mcp_startup_after_lag();
|
||||
|
||||
let summary_text = drain_insert_history(&mut rx)
|
||||
.iter()
|
||||
.map(|lines| lines_to_single_string(lines))
|
||||
.collect::<String>();
|
||||
assert!(summary_text.contains("MCP startup interrupted"));
|
||||
assert!(summary_text.contains("beta"));
|
||||
assert!(summary_text.contains("MCP startup incomplete (failed: alpha)"));
|
||||
assert!(!chat.bottom_pane.is_task_running());
|
||||
|
||||
chat.handle_server_notification(
|
||||
ServerNotification::McpServerStatusUpdated(McpServerStatusUpdatedNotification {
|
||||
name: "beta".to_string(),
|
||||
status: McpServerStartupState::Starting,
|
||||
error: None,
|
||||
}),
|
||||
/*replay_kind*/ None,
|
||||
);
|
||||
|
||||
assert!(drain_insert_history(&mut rx).is_empty());
|
||||
assert!(!chat.bottom_pane.is_task_running());
|
||||
|
||||
chat.handle_server_notification(
|
||||
ServerNotification::McpServerStatusUpdated(McpServerStatusUpdatedNotification {
|
||||
name: "beta".to_string(),
|
||||
status: McpServerStartupState::Ready,
|
||||
error: None,
|
||||
}),
|
||||
/*replay_kind*/ None,
|
||||
);
|
||||
|
||||
assert!(drain_insert_history(&mut rx).is_empty());
|
||||
assert!(!chat.bottom_pane.is_task_running());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn app_server_mcp_startup_after_lag_can_settle_without_starting_updates() {
|
||||
let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(/*model_override*/ None).await;
|
||||
chat.show_welcome_banner = false;
|
||||
chat.set_mcp_startup_expected_servers(["alpha".to_string(), "beta".to_string()]);
|
||||
|
||||
chat.finish_mcp_startup_after_lag();
|
||||
|
||||
chat.handle_server_notification(
|
||||
ServerNotification::McpServerStatusUpdated(McpServerStatusUpdatedNotification {
|
||||
name: "alpha".to_string(),
|
||||
status: McpServerStartupState::Failed,
|
||||
error: Some("MCP client for `alpha` failed to start: handshake failed".to_string()),
|
||||
}),
|
||||
/*replay_kind*/ None,
|
||||
);
|
||||
|
||||
let failure_text = drain_insert_history(&mut rx)
|
||||
.iter()
|
||||
.map(|lines| lines_to_single_string(lines))
|
||||
.collect::<String>();
|
||||
assert!(failure_text.contains("MCP client for `alpha` failed to start: handshake failed"));
|
||||
assert!(chat.bottom_pane.is_task_running());
|
||||
|
||||
chat.handle_server_notification(
|
||||
ServerNotification::McpServerStatusUpdated(McpServerStatusUpdatedNotification {
|
||||
name: "beta".to_string(),
|
||||
status: McpServerStartupState::Ready,
|
||||
error: None,
|
||||
}),
|
||||
/*replay_kind*/ None,
|
||||
);
|
||||
|
||||
let summary_text = drain_insert_history(&mut rx)
|
||||
.iter()
|
||||
.map(|lines| lines_to_single_string(lines))
|
||||
.collect::<String>();
|
||||
assert_eq!(summary_text, "⚠ MCP startup incomplete (failed: alpha)\n");
|
||||
assert!(!chat.bottom_pane.is_task_running());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn app_server_mcp_startup_after_lag_preserves_partial_terminal_only_round() {
|
||||
let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(/*model_override*/ None).await;
|
||||
chat.show_welcome_banner = false;
|
||||
chat.set_mcp_startup_expected_servers(["alpha".to_string(), "beta".to_string()]);
|
||||
|
||||
chat.handle_server_notification(
|
||||
ServerNotification::McpServerStatusUpdated(McpServerStatusUpdatedNotification {
|
||||
name: "alpha".to_string(),
|
||||
status: McpServerStartupState::Starting,
|
||||
error: None,
|
||||
}),
|
||||
/*replay_kind*/ None,
|
||||
);
|
||||
chat.handle_server_notification(
|
||||
ServerNotification::McpServerStatusUpdated(McpServerStatusUpdatedNotification {
|
||||
name: "alpha".to_string(),
|
||||
status: McpServerStartupState::Failed,
|
||||
error: Some("MCP client for `alpha` failed to start: handshake failed".to_string()),
|
||||
}),
|
||||
/*replay_kind*/ None,
|
||||
);
|
||||
chat.handle_server_notification(
|
||||
ServerNotification::McpServerStatusUpdated(McpServerStatusUpdatedNotification {
|
||||
name: "beta".to_string(),
|
||||
status: McpServerStartupState::Starting,
|
||||
error: None,
|
||||
}),
|
||||
/*replay_kind*/ None,
|
||||
);
|
||||
let _ = drain_insert_history(&mut rx);
|
||||
|
||||
chat.finish_mcp_startup_after_lag();
|
||||
let _ = drain_insert_history(&mut rx);
|
||||
assert!(!chat.bottom_pane.is_task_running());
|
||||
|
||||
chat.handle_server_notification(
|
||||
ServerNotification::McpServerStatusUpdated(McpServerStatusUpdatedNotification {
|
||||
name: "alpha".to_string(),
|
||||
status: McpServerStartupState::Failed,
|
||||
error: Some("MCP client for `alpha` failed to start: handshake failed".to_string()),
|
||||
}),
|
||||
/*replay_kind*/ None,
|
||||
);
|
||||
|
||||
assert!(drain_insert_history(&mut rx).is_empty());
|
||||
assert!(!chat.bottom_pane.is_task_running());
|
||||
|
||||
chat.finish_mcp_startup_after_lag();
|
||||
|
||||
chat.handle_server_notification(
|
||||
ServerNotification::McpServerStatusUpdated(McpServerStatusUpdatedNotification {
|
||||
name: "beta".to_string(),
|
||||
status: McpServerStartupState::Ready,
|
||||
error: None,
|
||||
}),
|
||||
/*replay_kind*/ None,
|
||||
);
|
||||
|
||||
let summary_text = drain_insert_history(&mut rx)
|
||||
.iter()
|
||||
.map(|lines| lines_to_single_string(lines))
|
||||
.collect::<String>();
|
||||
assert!(summary_text.contains("MCP client for `alpha` failed to start: handshake failed"));
|
||||
assert!(summary_text.contains("MCP startup incomplete (failed: alpha)"));
|
||||
assert!(!chat.bottom_pane.is_task_running());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn app_server_mcp_startup_next_round_discards_stale_terminal_updates() {
|
||||
let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(/*model_override*/ None).await;
|
||||
chat.show_welcome_banner = false;
|
||||
chat.set_mcp_startup_expected_servers(["alpha".to_string(), "beta".to_string()]);
|
||||
|
||||
chat.handle_server_notification(
|
||||
ServerNotification::McpServerStatusUpdated(McpServerStatusUpdatedNotification {
|
||||
name: "alpha".to_string(),
|
||||
status: McpServerStartupState::Starting,
|
||||
error: None,
|
||||
}),
|
||||
/*replay_kind*/ None,
|
||||
);
|
||||
chat.handle_server_notification(
|
||||
ServerNotification::McpServerStatusUpdated(McpServerStatusUpdatedNotification {
|
||||
name: "alpha".to_string(),
|
||||
status: McpServerStartupState::Failed,
|
||||
error: Some("MCP client for `alpha` failed to start: handshake failed".to_string()),
|
||||
}),
|
||||
/*replay_kind*/ None,
|
||||
);
|
||||
chat.handle_server_notification(
|
||||
ServerNotification::McpServerStatusUpdated(McpServerStatusUpdatedNotification {
|
||||
name: "beta".to_string(),
|
||||
status: McpServerStartupState::Starting,
|
||||
error: None,
|
||||
}),
|
||||
/*replay_kind*/ None,
|
||||
);
|
||||
let _ = drain_insert_history(&mut rx);
|
||||
|
||||
chat.finish_mcp_startup_after_lag();
|
||||
let _ = drain_insert_history(&mut rx);
|
||||
assert!(!chat.bottom_pane.is_task_running());
|
||||
|
||||
chat.handle_server_notification(
|
||||
ServerNotification::McpServerStatusUpdated(McpServerStatusUpdatedNotification {
|
||||
name: "alpha".to_string(),
|
||||
status: McpServerStartupState::Failed,
|
||||
error: Some(
|
||||
"MCP client for `alpha` failed to start: stale handshake failed".to_string(),
|
||||
),
|
||||
}),
|
||||
/*replay_kind*/ None,
|
||||
);
|
||||
assert!(drain_insert_history(&mut rx).is_empty());
|
||||
|
||||
chat.handle_server_notification(
|
||||
ServerNotification::McpServerStatusUpdated(McpServerStatusUpdatedNotification {
|
||||
name: "beta".to_string(),
|
||||
status: McpServerStartupState::Starting,
|
||||
error: None,
|
||||
}),
|
||||
/*replay_kind*/ None,
|
||||
);
|
||||
assert!(drain_insert_history(&mut rx).is_empty());
|
||||
assert!(!chat.bottom_pane.is_task_running());
|
||||
|
||||
chat.handle_server_notification(
|
||||
ServerNotification::McpServerStatusUpdated(McpServerStatusUpdatedNotification {
|
||||
name: "alpha".to_string(),
|
||||
status: McpServerStartupState::Ready,
|
||||
error: None,
|
||||
}),
|
||||
/*replay_kind*/ None,
|
||||
);
|
||||
assert!(drain_insert_history(&mut rx).is_empty());
|
||||
assert!(chat.bottom_pane.is_task_running());
|
||||
|
||||
chat.handle_server_notification(
|
||||
ServerNotification::McpServerStatusUpdated(McpServerStatusUpdatedNotification {
|
||||
name: "beta".to_string(),
|
||||
status: McpServerStartupState::Ready,
|
||||
error: None,
|
||||
}),
|
||||
/*replay_kind*/ None,
|
||||
);
|
||||
|
||||
let summary_text = drain_insert_history(&mut rx)
|
||||
.iter()
|
||||
.map(|lines| lines_to_single_string(lines))
|
||||
.collect::<String>();
|
||||
assert!(summary_text.is_empty());
|
||||
assert!(!chat.bottom_pane.is_task_running());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn app_server_mcp_startup_next_round_keeps_terminal_statuses_after_starting() {
|
||||
let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(/*model_override*/ None).await;
|
||||
chat.show_welcome_banner = false;
|
||||
chat.set_mcp_startup_expected_servers(["alpha".to_string(), "beta".to_string()]);
|
||||
|
||||
chat.finish_mcp_startup_after_lag();
|
||||
|
||||
chat.handle_server_notification(
|
||||
ServerNotification::McpServerStatusUpdated(McpServerStatusUpdatedNotification {
|
||||
name: "alpha".to_string(),
|
||||
status: McpServerStartupState::Starting,
|
||||
error: None,
|
||||
}),
|
||||
/*replay_kind*/ None,
|
||||
);
|
||||
assert!(drain_insert_history(&mut rx).is_empty());
|
||||
|
||||
chat.handle_server_notification(
|
||||
ServerNotification::McpServerStatusUpdated(McpServerStatusUpdatedNotification {
|
||||
name: "alpha".to_string(),
|
||||
status: McpServerStartupState::Failed,
|
||||
error: Some("MCP client for `alpha` failed to start: handshake failed".to_string()),
|
||||
}),
|
||||
/*replay_kind*/ None,
|
||||
);
|
||||
|
||||
let failure_text = drain_insert_history(&mut rx)
|
||||
.iter()
|
||||
.map(|lines| lines_to_single_string(lines))
|
||||
.collect::<String>();
|
||||
assert!(failure_text.contains("MCP client for `alpha` failed to start: handshake failed"));
|
||||
|
||||
chat.handle_server_notification(
|
||||
ServerNotification::McpServerStatusUpdated(McpServerStatusUpdatedNotification {
|
||||
name: "beta".to_string(),
|
||||
status: McpServerStartupState::Starting,
|
||||
error: None,
|
||||
}),
|
||||
/*replay_kind*/ None,
|
||||
);
|
||||
assert!(drain_insert_history(&mut rx).is_empty());
|
||||
assert!(chat.bottom_pane.is_task_running());
|
||||
|
||||
chat.handle_server_notification(
|
||||
ServerNotification::McpServerStatusUpdated(McpServerStatusUpdatedNotification {
|
||||
name: "beta".to_string(),
|
||||
status: McpServerStartupState::Ready,
|
||||
error: None,
|
||||
}),
|
||||
/*replay_kind*/ None,
|
||||
);
|
||||
|
||||
let summary_text = drain_insert_history(&mut rx)
|
||||
.iter()
|
||||
.map(|lines| lines_to_single_string(lines))
|
||||
.collect::<String>();
|
||||
assert_eq!(summary_text, "⚠ MCP startup incomplete (failed: alpha)\n");
|
||||
assert!(!chat.bottom_pane.is_task_running());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn app_server_mcp_startup_next_round_with_empty_expected_servers_reactivates() {
|
||||
let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(/*model_override*/ None).await;
|
||||
chat.show_welcome_banner = false;
|
||||
chat.set_mcp_startup_expected_servers(std::iter::empty::<String>());
|
||||
chat.finish_mcp_startup(Vec::new(), Vec::new());
|
||||
|
||||
chat.handle_server_notification(
|
||||
ServerNotification::McpServerStatusUpdated(McpServerStatusUpdatedNotification {
|
||||
name: "runtime".to_string(),
|
||||
status: McpServerStartupState::Starting,
|
||||
error: None,
|
||||
}),
|
||||
/*replay_kind*/ None,
|
||||
);
|
||||
assert!(drain_insert_history(&mut rx).is_empty());
|
||||
assert!(chat.bottom_pane.is_task_running());
|
||||
|
||||
chat.handle_server_notification(
|
||||
ServerNotification::McpServerStatusUpdated(McpServerStatusUpdatedNotification {
|
||||
name: "runtime".to_string(),
|
||||
status: McpServerStartupState::Failed,
|
||||
error: Some("MCP client for `runtime` failed to start: handshake failed".to_string()),
|
||||
}),
|
||||
/*replay_kind*/ None,
|
||||
);
|
||||
|
||||
let summary_text = drain_insert_history(&mut rx)
|
||||
.iter()
|
||||
.map(|lines| lines_to_single_string(lines))
|
||||
.collect::<String>();
|
||||
assert!(summary_text.contains("MCP client for `runtime` failed to start: handshake failed"));
|
||||
assert!(summary_text.contains("MCP startup incomplete (failed: runtime)"));
|
||||
assert!(!chat.bottom_pane.is_task_running());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn app_server_mcp_startup_after_lag_with_empty_expected_servers_preserves_failures() {
|
||||
let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(/*model_override*/ None).await;
|
||||
chat.show_welcome_banner = false;
|
||||
chat.set_mcp_startup_expected_servers(std::iter::empty::<String>());
|
||||
|
||||
chat.on_mcp_startup_update(McpStartupUpdateEvent {
|
||||
server: "runtime".to_string(),
|
||||
status: McpStartupStatus::Starting,
|
||||
});
|
||||
chat.on_mcp_startup_update(McpStartupUpdateEvent {
|
||||
server: "runtime".to_string(),
|
||||
status: McpStartupStatus::Failed {
|
||||
error: "MCP client for `runtime` failed to start: handshake failed".to_string(),
|
||||
},
|
||||
});
|
||||
|
||||
let warning_text = drain_insert_history(&mut rx)
|
||||
.iter()
|
||||
.map(|lines| lines_to_single_string(lines))
|
||||
.collect::<String>();
|
||||
assert!(warning_text.contains("MCP client for `runtime` failed to start: handshake failed"));
|
||||
assert!(chat.bottom_pane.is_task_running());
|
||||
|
||||
chat.finish_mcp_startup_after_lag();
|
||||
|
||||
let summary_text = drain_insert_history(&mut rx)
|
||||
.iter()
|
||||
.map(|lines| lines_to_single_string(lines))
|
||||
.collect::<String>();
|
||||
assert!(summary_text.contains("MCP startup incomplete (failed: runtime)"));
|
||||
assert!(!chat.bottom_pane.is_task_running());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn app_server_mcp_startup_after_lag_includes_runtime_servers_with_expected_set() {
|
||||
let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(/*model_override*/ None).await;
|
||||
chat.show_welcome_banner = false;
|
||||
chat.set_mcp_startup_expected_servers(["alpha".to_string()]);
|
||||
|
||||
chat.on_mcp_startup_update(McpStartupUpdateEvent {
|
||||
server: "alpha".to_string(),
|
||||
status: McpStartupStatus::Ready,
|
||||
});
|
||||
chat.on_mcp_startup_update(McpStartupUpdateEvent {
|
||||
server: "runtime".to_string(),
|
||||
status: McpStartupStatus::Failed {
|
||||
error: "MCP client for `runtime` failed to start: handshake failed".to_string(),
|
||||
},
|
||||
});
|
||||
|
||||
let warning_text = drain_insert_history(&mut rx)
|
||||
.iter()
|
||||
.map(|lines| lines_to_single_string(lines))
|
||||
.collect::<String>();
|
||||
assert!(warning_text.contains("MCP client for `runtime` failed to start: handshake failed"));
|
||||
assert!(chat.bottom_pane.is_task_running());
|
||||
|
||||
chat.finish_mcp_startup_after_lag();
|
||||
|
||||
let summary_text = drain_insert_history(&mut rx)
|
||||
.iter()
|
||||
.map(|lines| lines_to_single_string(lines))
|
||||
.collect::<String>();
|
||||
assert!(summary_text.contains("MCP startup incomplete (failed: runtime)"));
|
||||
assert!(!chat.bottom_pane.is_task_running());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn app_server_mcp_startup_next_round_after_lag_can_settle_without_starting_updates() {
|
||||
let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(/*model_override*/ None).await;
|
||||
chat.show_welcome_banner = false;
|
||||
chat.set_mcp_startup_expected_servers(["alpha".to_string(), "beta".to_string()]);
|
||||
|
||||
chat.handle_server_notification(
|
||||
ServerNotification::McpServerStatusUpdated(McpServerStatusUpdatedNotification {
|
||||
name: "alpha".to_string(),
|
||||
status: McpServerStartupState::Starting,
|
||||
error: None,
|
||||
}),
|
||||
/*replay_kind*/ None,
|
||||
);
|
||||
chat.handle_server_notification(
|
||||
ServerNotification::McpServerStatusUpdated(McpServerStatusUpdatedNotification {
|
||||
name: "alpha".to_string(),
|
||||
status: McpServerStartupState::Failed,
|
||||
error: Some("MCP client for `alpha` failed to start: handshake failed".to_string()),
|
||||
}),
|
||||
/*replay_kind*/ None,
|
||||
);
|
||||
chat.handle_server_notification(
|
||||
ServerNotification::McpServerStatusUpdated(McpServerStatusUpdatedNotification {
|
||||
name: "beta".to_string(),
|
||||
status: McpServerStartupState::Starting,
|
||||
error: None,
|
||||
}),
|
||||
/*replay_kind*/ None,
|
||||
);
|
||||
let _ = drain_insert_history(&mut rx);
|
||||
|
||||
chat.finish_mcp_startup_after_lag();
|
||||
let _ = drain_insert_history(&mut rx);
|
||||
assert!(!chat.bottom_pane.is_task_running());
|
||||
|
||||
chat.handle_server_notification(
|
||||
ServerNotification::McpServerStatusUpdated(McpServerStatusUpdatedNotification {
|
||||
name: "alpha".to_string(),
|
||||
status: McpServerStartupState::Failed,
|
||||
error: Some(
|
||||
"MCP client for `alpha` failed to start: stale handshake failed".to_string(),
|
||||
),
|
||||
}),
|
||||
/*replay_kind*/ None,
|
||||
);
|
||||
assert!(drain_insert_history(&mut rx).is_empty());
|
||||
|
||||
chat.finish_mcp_startup_after_lag();
|
||||
|
||||
chat.handle_server_notification(
|
||||
ServerNotification::McpServerStatusUpdated(McpServerStatusUpdatedNotification {
|
||||
name: "alpha".to_string(),
|
||||
status: McpServerStartupState::Failed,
|
||||
error: Some("MCP client for `alpha` failed to start: handshake failed".to_string()),
|
||||
}),
|
||||
/*replay_kind*/ None,
|
||||
);
|
||||
|
||||
let failure_text = drain_insert_history(&mut rx)
|
||||
.iter()
|
||||
.map(|lines| lines_to_single_string(lines))
|
||||
.collect::<String>();
|
||||
assert!(failure_text.is_empty());
|
||||
assert!(!chat.bottom_pane.is_task_running());
|
||||
|
||||
chat.handle_server_notification(
|
||||
ServerNotification::McpServerStatusUpdated(McpServerStatusUpdatedNotification {
|
||||
name: "beta".to_string(),
|
||||
status: McpServerStartupState::Ready,
|
||||
error: None,
|
||||
}),
|
||||
/*replay_kind*/ None,
|
||||
);
|
||||
|
||||
let summary_text = drain_insert_history(&mut rx)
|
||||
.iter()
|
||||
.map(|lines| lines_to_single_string(lines))
|
||||
.collect::<String>();
|
||||
assert!(summary_text.contains("MCP client for `alpha` failed to start: handshake failed"));
|
||||
assert!(summary_text.contains("MCP startup incomplete (failed: alpha)"));
|
||||
assert!(!chat.bottom_pane.is_task_running());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn background_event_updates_status_header() {
|
||||
let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(/*model_override*/ None).await;
|
||||
|
||||
Reference in new issue
Block a user