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.
This commit is contained in:
Eric Traut
2026-05-13 15:40:37 -07:00
committed by GitHub
Unverified
parent e6939e3969
commit efdcbba053
9 changed files with 2 additions and 163 deletions
-3
View File
@@ -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);
}
-3
View File
@@ -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),
+1 -33
View File
@@ -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(
@@ -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)
@@ -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<SelectionItem> = presets
.into_iter()
.map(|mask| {
let name = mask.name.clone();
let is_current = current_kind == mask.mode;
let actions: Vec<SelectionAction> = 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<ReasoningEffortConfig>,
@@ -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
@@ -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;
-4
View File
@@ -11,10 +11,6 @@ fn filtered_presets(_model_catalog: &ModelCatalog) -> Vec<CollaborationModeMask>
.collect()
}
pub(crate) fn presets_for_tui(model_catalog: &ModelCatalog) -> Vec<CollaborationModeMask> {
filtered_presets(model_catalog)
}
pub(crate) fn default_mask(model_catalog: &ModelCatalog) -> Option<CollaborationModeMask> {
let presets = filtered_presets(model_catalog);
presets
-3
View File
@@ -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,
}