TUI: replay in-progress MCP calls as started (#23236)

Fixes #22300.

## Summary
MCP tool calls can appear in thread history while still in progress.
During replay, `handle_thread_item` routed every
`ThreadItem::McpToolCall` to the completion handler, so an in-progress
item with no result or error was rendered as `MCP tool call completed
without a result`.

This updates replay handling to mirror command executions: `InProgress`
MCP calls go through `on_mcp_tool_call_started`, while completed and
failed calls continue through the completion path.

## Validation
- `cargo test -p codex-tui
replayed_in_progress_mcp_tool_call_stays_active`
This commit is contained in:
Eric Traut
2026-05-18 11:34:31 -07:00
committed by GitHub
Unverified
parent 53a1f4c29e
commit ae03d073b3
2 changed files with 31 additions and 0 deletions
+4
View File
@@ -133,6 +133,10 @@ impl ChatWidget {
..
} => {}
item @ ThreadItem::FileChange { .. } => self.on_file_change_completed(item),
item @ ThreadItem::McpToolCall {
status: codex_app_server_protocol::McpToolCallStatus::InProgress,
..
} => self.on_mcp_tool_call_started(item),
item @ ThreadItem::McpToolCall { .. } => self.on_mcp_tool_call_completed(item),
ThreadItem::WebSearch { id, query, action } => {
self.on_web_search_begin(id.clone());
@@ -826,6 +826,33 @@ async fn replayed_reasoning_item_shows_raw_reasoning_when_enabled() {
assert!(rendered.contains("Raw reasoning"));
}
#[tokio::test]
async fn replayed_in_progress_mcp_tool_call_stays_active() {
let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(/*model_override*/ None).await;
let _ = drain_insert_history(&mut rx);
chat.replay_thread_item(
AppServerThreadItem::McpToolCall {
id: "mcp-1".to_string(),
server: "copilot-bridge".to_string(),
tool: "copilot".to_string(),
status: codex_app_server_protocol::McpToolCallStatus::InProgress,
arguments: json!({"action": "wait"}),
mcp_app_resource_uri: None,
result: None,
error: None,
duration_ms: None,
},
"turn-1".to_string(),
ReplayKind::ThreadSnapshot,
);
assert!(drain_insert_history(&mut rx).is_empty());
let active = active_blob(&chat);
assert!(active.contains("Calling"));
assert!(!active.contains("MCP tool call completed without a result"));
}
#[tokio::test]
async fn live_reasoning_summary_is_not_rendered_twice_when_item_completes() {
let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(/*model_override*/ None).await;