From f30151cc18baffadfc2c9a1739f3ba8ec0d03785 Mon Sep 17 00:00:00 2001 From: Charles Cunningham Date: Fri, 30 Jan 2026 12:18:57 -0800 Subject: [PATCH] Fix backtrack to restore collaboration mode and keep developer instructions in sync --- codex-rs/tui/src/app.rs | 1 + codex-rs/tui/src/app_backtrack.rs | 32 +++++++++++++++++++++++++++++-- codex-rs/tui/src/chatwidget.rs | 14 ++++++++++++++ codex-rs/tui/src/history_cell.rs | 5 +++++ 4 files changed, 50 insertions(+), 2 deletions(-) diff --git a/codex-rs/tui/src/app.rs b/codex-rs/tui/src/app.rs index 48ef0a772d..5116341269 100644 --- a/codex-rs/tui/src/app.rs +++ b/codex-rs/tui/src/app.rs @@ -2911,6 +2911,7 @@ mod tests { message: text.to_string(), text_elements, local_image_paths, + collaboration_mode: None, }) as Arc }; let agent_cell = |text: &str| -> Arc { diff --git a/codex-rs/tui/src/app_backtrack.rs b/codex-rs/tui/src/app_backtrack.rs index 256355dd05..d921df3fee 100644 --- a/codex-rs/tui/src/app_backtrack.rs +++ b/codex-rs/tui/src/app_backtrack.rs @@ -38,6 +38,7 @@ use codex_core::protocol::ErrorEvent; use codex_core::protocol::EventMsg; use codex_core::protocol::Op; use codex_protocol::ThreadId; +use codex_protocol::config_types::CollaborationModeMask; use codex_protocol::user_input::TextElement; use color_eyre::eyre::Result; use crossterm::event::KeyCode; @@ -84,6 +85,8 @@ pub(crate) struct BacktrackSelection { pub(crate) text_elements: Vec, /// Local image paths associated with the selected user message. pub(crate) local_image_paths: Vec, + /// Collaboration mode active when the selected message was sent. + pub(crate) collaboration_mode: Option, } /// An in-flight rollback requested from core. @@ -203,6 +206,10 @@ impl App { return; } + let collaboration_override = selection + .collaboration_mode + .clone() + .and_then(|mask| self.chat_widget.set_collaboration_mask_from_history(mask)); let prefill = selection.prefill.clone(); let text_elements = selection.text_elements.clone(); let local_image_paths = selection.local_image_paths.clone(); @@ -211,6 +218,21 @@ impl App { thread_id: self.chat_widget.thread_id(), }); self.chat_widget.submit_op(Op::ThreadRollback { num_turns }); + if let Some(collaboration_mode) = collaboration_override + && self.chat_widget.thread_id().is_some() + { + self.chat_widget.submit_op(Op::OverrideTurnContext { + cwd: None, + approval_policy: None, + sandbox_policy: None, + windows_sandbox_level: None, + model: None, + effort: None, + summary: None, + collaboration_mode: Some(collaboration_mode), + personality: None, + }); + } if !prefill.is_empty() || !text_elements.is_empty() || !local_image_paths.is_empty() { self.chat_widget .set_composer_text(prefill, text_elements, local_image_paths); @@ -487,7 +509,7 @@ impl App { return None; } - let (prefill, text_elements, local_image_paths) = + let (prefill, text_elements, local_image_paths, collaboration_mode) = nth_user_position(&self.transcript_cells, nth_user_message) .and_then(|idx| self.transcript_cells.get(idx)) .and_then(|cell| cell.as_any().downcast_ref::()) @@ -496,15 +518,17 @@ impl App { cell.message.clone(), cell.text_elements.clone(), cell.local_image_paths.clone(), + cell.collaboration_mode.clone(), ) }) - .unwrap_or_else(|| (String::new(), Vec::new(), Vec::new())); + .unwrap_or_else(|| (String::new(), Vec::new(), Vec::new(), None)); Some(BacktrackSelection { nth_user_message, prefill, text_elements, local_image_paths, + collaboration_mode, }) } @@ -574,6 +598,7 @@ mod tests { message: "first user".to_string(), text_elements: Vec::new(), local_image_paths: Vec::new(), + collaboration_mode: None, }) as Arc, Arc::new(AgentMessageCell::new(vec![Line::from("assistant")], true)) as Arc, @@ -592,6 +617,7 @@ mod tests { message: "first".to_string(), text_elements: Vec::new(), local_image_paths: Vec::new(), + collaboration_mode: None, }) as Arc, Arc::new(AgentMessageCell::new(vec![Line::from("after")], false)) as Arc, @@ -622,6 +648,7 @@ mod tests { message: "first".to_string(), text_elements: Vec::new(), local_image_paths: Vec::new(), + collaboration_mode: None, }) as Arc, Arc::new(AgentMessageCell::new(vec![Line::from("between")], false)) as Arc, @@ -629,6 +656,7 @@ mod tests { message: "second".to_string(), text_elements: Vec::new(), local_image_paths: Vec::new(), + collaboration_mode: None, }) as Arc, Arc::new(AgentMessageCell::new(vec![Line::from("tail")], false)) as Arc, diff --git a/codex-rs/tui/src/chatwidget.rs b/codex-rs/tui/src/chatwidget.rs index 56cebb6abb..3081654f37 100644 --- a/codex-rs/tui/src/chatwidget.rs +++ b/codex-rs/tui/src/chatwidget.rs @@ -3350,6 +3350,7 @@ impl ChatWidget { text, text_elements, local_image_paths, + self.active_collaboration_mask.clone(), )); } @@ -3639,6 +3640,7 @@ impl ChatWidget { event.message, event.text_elements, event.local_images, + None, )); } @@ -5513,6 +5515,18 @@ impl ChatWidget { self.request_redraw(); } + pub(crate) fn set_collaboration_mask_from_history( + &mut self, + mask: CollaborationModeMask, + ) -> Option { + if !self.collaboration_modes_enabled() { + return None; + } + let next_mode = self.current_collaboration_mode.apply_mask(&mask); + self.set_collaboration_mask(mask); + Some(next_mode) + } + fn connectors_enabled(&self) -> bool { self.config.features.enabled(Feature::Apps) } diff --git a/codex-rs/tui/src/history_cell.rs b/codex-rs/tui/src/history_cell.rs index 49ccc481bf..a158e58f26 100644 --- a/codex-rs/tui/src/history_cell.rs +++ b/codex-rs/tui/src/history_cell.rs @@ -47,6 +47,7 @@ use codex_core::protocol::SessionConfiguredEvent; use codex_core::web_search::web_search_detail; use codex_otel::RuntimeMetricsSummary; use codex_protocol::account::PlanType; +use codex_protocol::config_types::CollaborationModeMask; use codex_protocol::mcp::Resource; use codex_protocol::mcp::ResourceTemplate; use codex_protocol::models::WebSearchAction; @@ -168,6 +169,7 @@ pub(crate) struct UserHistoryCell { pub text_elements: Vec, #[allow(dead_code)] pub local_image_paths: Vec, + pub collaboration_mode: Option, } /// Build logical lines for a user message with styled text elements. @@ -1018,11 +1020,13 @@ pub(crate) fn new_user_prompt( message: String, text_elements: Vec, local_image_paths: Vec, + collaboration_mode: Option, ) -> UserHistoryCell { UserHistoryCell { message, text_elements, local_image_paths, + collaboration_mode, } } @@ -3311,6 +3315,7 @@ mod tests { message: msg.to_string(), text_elements: Vec::new(), local_image_paths: Vec::new(), + collaboration_mode: None, }; // Small width to force wrapping more clearly. Effective wrap width is width-2 due to the ▌ prefix and trailing space.