//! Non-command tool lifecycle rendering for `ChatWidget`.//!//! This module handles patch, MCP, web search, image, and collaborator tool//! events as transcript cells.use super::*;impl ChatWidget { pub(super) fn on_patch_apply_begin(&mut self, changes: HashMap<PathBuf, FileChange>) { self.record_visible_turn_activity(); self.add_to_history(history_cell::new_patch_event(changes, &self.config.cwd)); } pub(super) fn on_view_image_tool_call(&mut self, path: AbsolutePathBuf) { self.record_visible_turn_activity(); self.flush_answer_stream_with_separator(); self.add_to_history(history_cell::new_view_image_tool_call( path, &self.config.cwd, )); self.request_redraw(); } pub(super) fn on_image_generation_begin(&mut self) { self.record_visible_turn_activity(); self.flush_answer_stream_with_separator(); } pub(super) fn on_image_generation_end( &mut self, call_id: String, status: String, revised_prompt: Option<String>, saved_path: Option<AbsolutePathBuf>, ) { self.flush_answer_stream_with_separator(); self.add_to_history(history_cell::new_image_generation_call( call_id, &status, revised_prompt, saved_path, )); self.request_redraw(); } pub(super) fn on_file_change_completed(&mut self, item: ThreadItem) { let item2 = item.clone(); self.defer_or_handle( |q| q.push_item_completed(item), |s| s.handle_file_change_completed_now(item2), ); } pub(super) fn on_mcp_tool_call_started(&mut self, item: ThreadItem) { let item2 = item.clone(); self.defer_or_handle( |q| q.push_item_started(item), |s| s.handle_mcp_tool_call_started_now(item2), ); } pub(super) fn on_mcp_tool_call_completed(&mut self, item: ThreadItem) { let item2 = item.clone(); self.defer_or_handle( |q| q.push_item_completed(item), |s| s.handle_mcp_tool_call_completed_now(item2), ); } pub(super) fn on_web_search_begin(&mut self, call_id: String) { self.record_visible_turn_activity(); self.flush_answer_stream_with_separator(); self.flush_active_cell(); self.transcript.active_cell = Some(Box::new(history_cell::new_active_web_search_call( call_id, String::new(), self.config.animations, ))); self.bump_active_cell_revision(); self.request_redraw(); } pub(super) fn on_web_search_end( &mut self, call_id: String, query: String, action: codex_app_server_protocol::WebSearchAction, ) { self.flush_answer_stream_with_separator(); let mut handled = false; if let Some(cell) = self .transcript .active_cell .as_mut() .and_then(|cell| cell.as_any_mut().downcast_mut::<WebSearchCell>()) && cell.call_id() == call_id { cell.update(action.clone(), query.clone()); cell.complete(); self.bump_active_cell_revision(); self.flush_active_cell(); handled = true; } if !handled { self.add_to_history(history_cell::new_web_search_call(call_id, query, action)); } self.transcript.had_work_activity = true; } pub(super) fn on_collab_event(&mut self, cell: PlainHistoryCell) { self.flush_answer_stream_with_separator(); self.add_to_history(cell); self.request_redraw(); } pub(super) fn on_collab_agent_tool_call(&mut self, item: ThreadItem) { self.record_visible_turn_activity(); let ThreadItem::CollabAgentToolCall { id, tool, status, .. } = &item else { return; }; if matches!(tool, CollabAgentTool::SpawnAgent) && let Some(spawn_request) = multi_agents::spawn_request_summary(&item) { self.pending_collab_spawn_requests .insert(id.clone(), spawn_request); } let cached_spawn_request = if matches!(tool, CollabAgentTool::SpawnAgent) && !matches!(status, CollabAgentToolCallStatus::InProgress) { self.pending_collab_spawn_requests.remove(id) } else { None }; if let Some(cell) = multi_agents::tool_call_history_cell( &item, cached_spawn_request.as_ref(), |thread_id| self.collab_agent_metadata(thread_id), ) { self.on_collab_event(cell); } } pub(super) fn on_sub_agent_activity(&mut self, item: ThreadItem) { self.record_visible_turn_activity(); if let Some(cell) = multi_agents::sub_agent_activity_history_cell(&item) { self.on_collab_event(cell); } } pub(crate) fn handle_file_change_completed_now(&mut self, item: ThreadItem) { let ThreadItem::FileChange { status, .. } = item else { return; }; // If the patch was successful, just let the "Edited" block stand. // Otherwise, add a failure block. if matches!(status, codex_app_server_protocol::PatchApplyStatus::Failed) { self.add_to_history(history_cell::new_patch_apply_failure(String::new())); } // Mark that actual work was done (patch applied) self.transcript.had_work_activity = true; } pub(crate) fn handle_mcp_tool_call_started_now(&mut self, item: ThreadItem) { self.record_visible_turn_activity(); let ThreadItem::McpToolCall { id, server, tool, arguments, .. } = item else { return; }; self.flush_answer_stream_with_separator(); self.flush_active_cell(); self.transcript.active_cell = Some(Box::new(history_cell::new_active_mcp_tool_call( id, McpInvocation { server, tool, arguments: Some(arguments), }, self.config.animations, ))); self.bump_active_cell_revision(); self.request_redraw(); } pub(crate) fn handle_mcp_tool_call_completed_now(&mut self, item: ThreadItem) { self.flush_answer_stream_with_separator(); let ThreadItem::McpToolCall { id, server, tool, arguments, result, error, duration_ms, .. } = item else { return; }; let invocation = McpInvocation { server, tool, arguments: Some(arguments), }; let duration = Duration::from_millis(duration_ms.unwrap_or_default().max(0) as u64); let result = match (result, error) { (_, Some(error)) => Err(error.message), (Some(result), None) => { let result = *result; Ok(codex_protocol::mcp::CallToolResult { content: result.content, structured_content: result.structured_content, is_error: Some(false), meta: None, }) } (None, None) => Err("MCP tool call completed without a result".to_string()), }; let extra_cell = match self .transcript .active_cell .as_mut() .and_then(|cell| cell.as_any_mut().downcast_mut::<McpToolCallCell>()) { Some(cell) if cell.call_id() == id => cell.complete(duration, result), _ => { self.flush_active_cell(); let mut cell = history_cell::new_active_mcp_tool_call(id, invocation, self.config.animations); let extra_cell = cell.complete(duration, result); self.transcript.active_cell = Some(Box::new(cell)); extra_cell } }; self.flush_active_cell(); if let Some(extra) = extra_cell { self.add_boxed_history(extra); } // Mark that actual work was done (MCP tool call) self.transcript.had_work_activity = true; } pub(crate) fn handle_queued_item_started_now(&mut self, item: ThreadItem) { match item { item @ ThreadItem::CommandExecution { .. } => { self.handle_command_execution_started_now(item); } item @ ThreadItem::McpToolCall { .. } => { self.handle_mcp_tool_call_started_now(item); } _ => {} } } pub(crate) fn handle_queued_item_completed_now(&mut self, item: ThreadItem) { match item { item @ ThreadItem::CommandExecution { .. } => { self.handle_command_execution_completed_now(item); } item @ ThreadItem::FileChange { .. } => self.handle_file_change_completed_now(item), item @ ThreadItem::McpToolCall { .. } => self.handle_mcp_tool_call_completed_now(item), _ => {} } }}