From efdcbba053f4971132d944b918d3d81c8b786201 Mon Sep 17 00:00:00 2001 From: Eric Traut Date: Wed, 13 May 2026 15:40:37 -0700 Subject: [PATCH] Remove resurrected `/collab` slash command (#22535) ## Summary `/collab` was intentionally removed in [#12012](https://github.com/openai/codex/pull/12012), but the TUI/app-server migration accidentally brought that slash-command path back. This restores the earlier product decision so the TUI no longer advertises or dispatches `/collab`. This command was redundant because it did the same thing as `/plan` but in a less-intuitive way. ## What Changed - Remove `SlashCommand::Collab` from the TUI slash-command surface. - Delete the picker and app-event plumbing that only existed to service `/collab`. - Remove obsolete TUI test coverage for the deleted picker flow. --- codex-rs/tui/src/app/event_dispatch.rs | 3 - codex-rs/tui/src/app_event.rs | 3 - codex-rs/tui/src/bottom_pane/command_popup.rs | 34 +---------- .../tui/src/bottom_pane/slash_commands.rs | 5 +- codex-rs/tui/src/chatwidget/model_popups.rs | 45 --------------- codex-rs/tui/src/chatwidget/slash_dispatch.rs | 11 ---- .../tui/src/chatwidget/tests/plan_mode.rs | 57 ------------------- codex-rs/tui/src/collaboration_modes.rs | 4 -- codex-rs/tui/src/slash_command.rs | 3 - 9 files changed, 2 insertions(+), 163 deletions(-) diff --git a/codex-rs/tui/src/app/event_dispatch.rs b/codex-rs/tui/src/app/event_dispatch.rs index 16d7daee7..d936f4fe0 100644 --- a/codex-rs/tui/src/app/event_dispatch.rs +++ b/codex-rs/tui/src/app/event_dispatch.rs @@ -756,9 +756,6 @@ impl App { AppEvent::UpdateModel(model) => { self.chat_widget.set_model(&model); } - AppEvent::UpdateCollaborationMode(mask) => { - self.chat_widget.set_collaboration_mask(mask); - } AppEvent::UpdatePersonality(personality) => { self.on_update_personality(personality); } diff --git a/codex-rs/tui/src/app_event.rs b/codex-rs/tui/src/app_event.rs index 530e8876a..ab9055197 100644 --- a/codex-rs/tui/src/app_event.rs +++ b/codex-rs/tui/src/app_event.rs @@ -608,9 +608,6 @@ pub(crate) enum AppEvent { /// Update the current model slug in the running app and widget. UpdateModel(String), - /// Update the active collaboration mask in the running app and widget. - UpdateCollaborationMode(CollaborationModeMask), - /// Update the current personality in the running app and widget. UpdatePersonality(Personality), diff --git a/codex-rs/tui/src/bottom_pane/command_popup.rs b/codex-rs/tui/src/bottom_pane/command_popup.rs index 714fe9dd4..080f320f5 100644 --- a/codex-rs/tui/src/bottom_pane/command_popup.rs +++ b/codex-rs/tui/src/bottom_pane/command_popup.rs @@ -419,7 +419,7 @@ mod tests { } #[test] - fn collab_command_hidden_when_collaboration_modes_disabled() { + fn plan_command_hidden_when_collaboration_modes_disabled() { let mut popup = CommandPopup::new(CommandPopupFlags::default(), Vec::new()); popup.on_composer_text_change("/".to_string()); @@ -431,44 +431,12 @@ mod tests { CommandItem::ServiceTier(command) => command.name, }) .collect(); - assert!( - !cmds.iter().any(|cmd| cmd == "collab"), - "expected '/collab' to be hidden when collaboration modes are disabled, got {cmds:?}" - ); assert!( !cmds.iter().any(|cmd| cmd == "plan"), "expected '/plan' to be hidden when collaboration modes are disabled, got {cmds:?}" ); } - #[test] - fn collab_command_visible_when_collaboration_modes_enabled() { - let mut popup = CommandPopup::new( - CommandPopupFlags { - collaboration_modes_enabled: true, - connectors_enabled: false, - plugins_command_enabled: false, - service_tier_commands_enabled: false, - goal_command_enabled: false, - personality_command_enabled: true, - realtime_conversation_enabled: false, - audio_device_selection_enabled: false, - windows_degraded_sandbox_active: false, - side_conversation_active: false, - }, - Vec::new(), - ); - popup.on_composer_text_change("/collab".to_string()); - - match popup.selected_item() { - Some(CommandItem::Builtin(cmd)) => assert_eq!(cmd.command(), "collab"), - Some(CommandItem::ServiceTier(command)) => { - panic!("expected collab command, got service tier {command:?}") - } - other => panic!("expected collab to be selected for exact match, got {other:?}"), - } - } - #[test] fn plan_command_visible_when_collaboration_modes_enabled() { let mut popup = CommandPopup::new( diff --git a/codex-rs/tui/src/bottom_pane/slash_commands.rs b/codex-rs/tui/src/bottom_pane/slash_commands.rs index 6966b28b8..e907b6c5e 100644 --- a/codex-rs/tui/src/bottom_pane/slash_commands.rs +++ b/codex-rs/tui/src/bottom_pane/slash_commands.rs @@ -72,10 +72,7 @@ pub(crate) fn builtins_for_input(flags: BuiltinCommandFlags) -> Vec<(&'static st built_in_slash_commands() .into_iter() .filter(|(_, cmd)| flags.allow_elevate_sandbox || *cmd != SlashCommand::ElevateSandbox) - .filter(|(_, cmd)| { - flags.collaboration_modes_enabled - || !matches!(*cmd, SlashCommand::Collab | SlashCommand::Plan) - }) + .filter(|(_, cmd)| flags.collaboration_modes_enabled || *cmd != SlashCommand::Plan) .filter(|(_, cmd)| flags.connectors_enabled || *cmd != SlashCommand::Apps) .filter(|(_, cmd)| flags.plugins_command_enabled || *cmd != SlashCommand::Plugins) .filter(|(_, cmd)| flags.goal_command_enabled || *cmd != SlashCommand::Goal) diff --git a/codex-rs/tui/src/chatwidget/model_popups.rs b/codex-rs/tui/src/chatwidget/model_popups.rs index 0ee91c11f..2c0d5aba9 100644 --- a/codex-rs/tui/src/chatwidget/model_popups.rs +++ b/codex-rs/tui/src/chatwidget/model_popups.rs @@ -213,51 +213,6 @@ impl ChatWidget { }); } - pub(crate) fn open_collaboration_modes_popup(&mut self) { - let presets = collaboration_modes::presets_for_tui(self.model_catalog.as_ref()); - if presets.is_empty() { - self.add_info_message( - "No collaboration modes are available right now.".to_string(), - /*hint*/ None, - ); - return; - } - - let current_kind = self - .active_collaboration_mask - .as_ref() - .and_then(|mask| mask.mode) - .or_else(|| { - collaboration_modes::default_mask(self.model_catalog.as_ref()) - .and_then(|mask| mask.mode) - }); - let items: Vec = presets - .into_iter() - .map(|mask| { - let name = mask.name.clone(); - let is_current = current_kind == mask.mode; - let actions: Vec = vec![Box::new(move |tx| { - tx.send(AppEvent::UpdateCollaborationMode(mask.clone())); - })]; - SelectionItem { - name, - is_current, - actions, - dismiss_on_select: true, - ..Default::default() - } - }) - .collect(); - - self.bottom_pane.show_selection_view(SelectionViewParams { - title: Some("Select Collaboration Mode".to_string()), - subtitle: Some("Pick a collaboration preset.".to_string()), - footer_hint: Some(standard_popup_hint_line()), - items, - ..Default::default() - }); - } - fn model_selection_actions( model_for_action: String, effort_for_action: Option, diff --git a/codex-rs/tui/src/chatwidget/slash_dispatch.rs b/codex-rs/tui/src/chatwidget/slash_dispatch.rs index 14a2dffb1..41989c517 100644 --- a/codex-rs/tui/src/chatwidget/slash_dispatch.rs +++ b/codex-rs/tui/src/chatwidget/slash_dispatch.rs @@ -238,16 +238,6 @@ impl ChatWidget { ); } } - SlashCommand::Collab => { - if !self.collaboration_modes_enabled() { - self.add_info_message( - "Collaboration modes are disabled.".to_string(), - Some("Enable collaboration modes to use /collab.".to_string()), - ); - return; - } - self.open_collaboration_modes_popup(); - } SlashCommand::Side => { self.request_empty_side_conversation(); } @@ -965,7 +955,6 @@ impl ChatWidget { | SlashCommand::Personality | SlashCommand::Plan | SlashCommand::Goal - | SlashCommand::Collab | SlashCommand::Side | SlashCommand::Keymap | SlashCommand::Agent diff --git a/codex-rs/tui/src/chatwidget/tests/plan_mode.rs b/codex-rs/tui/src/chatwidget/tests/plan_mode.rs index 9b8540e5a..e9fdb6d98 100644 --- a/codex-rs/tui/src/chatwidget/tests/plan_mode.rs +++ b/codex-rs/tui/src/chatwidget/tests/plan_mode.rs @@ -1367,63 +1367,6 @@ async fn mode_switch_surfaces_reasoning_change_notification_when_model_stays_sam ); } -#[tokio::test] -async fn collab_slash_command_opens_picker_and_updates_mode() { - let (mut chat, mut rx, mut op_rx) = make_chatwidget_manual(/*model_override*/ None).await; - chat.thread_id = Some(ThreadId::new()); - chat.set_feature_enabled(Feature::CollaborationModes, /*enabled*/ true); - - chat.dispatch_command(SlashCommand::Collab); - let popup = render_bottom_popup(&chat, /*width*/ 80); - assert!( - popup.contains("Select Collaboration Mode"), - "expected collaboration picker: {popup}" - ); - - chat.handle_key_event(KeyEvent::from(KeyCode::Enter)); - let selected_mask = match rx.try_recv() { - Ok(AppEvent::UpdateCollaborationMode(mask)) => mask, - other => panic!("expected UpdateCollaborationMode event, got {other:?}"), - }; - chat.set_collaboration_mask(selected_mask); - - chat.bottom_pane - .set_composer_text("hello".to_string(), Vec::new(), Vec::new()); - chat.handle_key_event(KeyEvent::from(KeyCode::Enter)); - match next_submit_op(&mut op_rx) { - Op::UserTurn { - collaboration_mode: - Some(CollaborationMode { - mode: ModeKind::Default, - .. - }), - personality: Some(Personality::Pragmatic), - .. - } => {} - other => { - panic!("expected Op::UserTurn with code collab mode, got {other:?}") - } - } - - chat.bottom_pane - .set_composer_text("follow up".to_string(), Vec::new(), Vec::new()); - chat.handle_key_event(KeyEvent::from(KeyCode::Enter)); - match next_submit_op(&mut op_rx) { - Op::UserTurn { - collaboration_mode: - Some(CollaborationMode { - mode: ModeKind::Default, - .. - }), - personality: Some(Personality::Pragmatic), - .. - } => {} - other => { - panic!("expected Op::UserTurn with code collab mode, got {other:?}") - } - } -} - #[tokio::test] async fn plan_slash_command_switches_to_plan_mode() { let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(/*model_override*/ None).await; diff --git a/codex-rs/tui/src/collaboration_modes.rs b/codex-rs/tui/src/collaboration_modes.rs index e0881bc9c..b81f4bf4d 100644 --- a/codex-rs/tui/src/collaboration_modes.rs +++ b/codex-rs/tui/src/collaboration_modes.rs @@ -11,10 +11,6 @@ fn filtered_presets(_model_catalog: &ModelCatalog) -> Vec .collect() } -pub(crate) fn presets_for_tui(model_catalog: &ModelCatalog) -> Vec { - filtered_presets(model_catalog) -} - pub(crate) fn default_mask(model_catalog: &ModelCatalog) -> Option { let presets = filtered_presets(model_catalog); presets diff --git a/codex-rs/tui/src/slash_command.rs b/codex-rs/tui/src/slash_command.rs index beaaa7f31..970d1cbd5 100644 --- a/codex-rs/tui/src/slash_command.rs +++ b/codex-rs/tui/src/slash_command.rs @@ -36,7 +36,6 @@ pub enum SlashCommand { Compact, Plan, Goal, - Collab, Agent, Side, Copy, @@ -114,7 +113,6 @@ impl SlashCommand { SlashCommand::Settings => "configure realtime microphone/speaker", SlashCommand::Plan => "switch to Plan mode", SlashCommand::Goal => "set or view the goal for a long-running task", - SlashCommand::Collab => "change collaboration mode (experimental)", SlashCommand::Agent | SlashCommand::MultiAgents => "switch the active agent thread", SlashCommand::Side => "start a side conversation in an ephemeral fork", SlashCommand::Permissions => "choose what Codex is allowed to do", @@ -224,7 +222,6 @@ impl SlashCommand { SlashCommand::TestApproval => true, SlashCommand::Realtime => true, SlashCommand::Settings => true, - SlashCommand::Collab => true, SlashCommand::Agent | SlashCommand::MultiAgents => true, SlashCommand::Theme | SlashCommand::Pets => false, }