tui: add feature-gated /plan slash command to switch to Plan mode (#10103)
## Summary Adds a simple `/plan` slash command in the TUI that switches the active collaboration mode to Plan mode. The command is only available when the `collaboration_modes` feature is enabled. ## Changes - Add `plan_mask` helper in `codex-rs/tui/src/collaboration_modes.rs` - Add `SlashCommand::Plan` metadata in `codex-rs/tui/src/slash_command.rs` - Implement and hard-gate `/plan` dispatch in `codex-rs/tui/src/chatwidget.rs` - Hide `/plan` when collaboration modes are disabled in `codex-rs/tui/src/bottom_pane/slash_commands.rs` - Update command popup tests in `codex-rs/tui/src/bottom_pane/command_popup.rs` - Add a focused unit test for `/plan` in `codex-rs/tui/src/chatwidget/tests.rs` ## Behavior notes - `/plan` is now a no-op if `Feature::CollaborationModes` is disabled. - When enabled, `/plan` switches directly to Plan mode without opening the picker. ## Codex author `codex resume 019c05da-d7c3-7322-ae2c-3ca38d0ef702`
This commit is contained in:
parent
81a17bb2c1
commit
11958221a3
6 changed files with 72 additions and 9 deletions
|
|
@ -459,7 +459,7 @@ mod tests {
|
|||
#[test]
|
||||
fn collab_command_hidden_when_collaboration_modes_disabled() {
|
||||
let mut popup = CommandPopup::new(Vec::new(), CommandPopupFlags::default());
|
||||
popup.on_composer_text_change("/coll".to_string());
|
||||
popup.on_composer_text_change("/".to_string());
|
||||
|
||||
let cmds: Vec<&str> = popup
|
||||
.filtered_items()
|
||||
|
|
@ -473,6 +473,10 @@ mod tests {
|
|||
!cmds.contains(&"collab"),
|
||||
"expected '/collab' to be hidden when collaboration modes are disabled, got {cmds:?}"
|
||||
);
|
||||
assert!(
|
||||
!cmds.contains(&"plan"),
|
||||
"expected '/plan' to be hidden when collaboration modes are disabled, got {cmds:?}"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -494,6 +498,25 @@ mod tests {
|
|||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn plan_command_visible_when_collaboration_modes_enabled() {
|
||||
let mut popup = CommandPopup::new(
|
||||
Vec::new(),
|
||||
CommandPopupFlags {
|
||||
collaboration_modes_enabled: true,
|
||||
connectors_enabled: false,
|
||||
personality_command_enabled: true,
|
||||
windows_degraded_sandbox_active: false,
|
||||
},
|
||||
);
|
||||
popup.on_composer_text_change("/plan".to_string());
|
||||
|
||||
match popup.selected_item() {
|
||||
Some(CommandItem::Builtin(cmd)) => assert_eq!(cmd.command(), "plan"),
|
||||
other => panic!("expected plan to be selected for exact match, got {other:?}"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn personality_command_hidden_when_disabled() {
|
||||
let mut popup = CommandPopup::new(
|
||||
|
|
|
|||
|
|
@ -18,7 +18,10 @@ pub(crate) fn builtins_for_input(
|
|||
built_in_slash_commands()
|
||||
.into_iter()
|
||||
.filter(|(_, cmd)| allow_elevate_sandbox || *cmd != SlashCommand::ElevateSandbox)
|
||||
.filter(|(_, cmd)| collaboration_modes_enabled || *cmd != SlashCommand::Collab)
|
||||
.filter(|(_, cmd)| {
|
||||
collaboration_modes_enabled
|
||||
|| !matches!(*cmd, SlashCommand::Collab | SlashCommand::Plan)
|
||||
})
|
||||
.filter(|(_, cmd)| connectors_enabled || *cmd != SlashCommand::Apps)
|
||||
.filter(|(_, cmd)| personality_command_enabled || *cmd != SlashCommand::Personality)
|
||||
.collect()
|
||||
|
|
|
|||
|
|
@ -2668,10 +2668,29 @@ impl ChatWidget {
|
|||
SlashCommand::Personality => {
|
||||
self.open_personality_popup();
|
||||
}
|
||||
SlashCommand::Collab => {
|
||||
if self.collaboration_modes_enabled() {
|
||||
self.open_collaboration_modes_popup();
|
||||
SlashCommand::Plan => {
|
||||
if !self.collaboration_modes_enabled() {
|
||||
self.add_info_message(
|
||||
"Collaboration modes are disabled.".to_string(),
|
||||
Some("Enable collaboration modes to use /plan.".to_string()),
|
||||
);
|
||||
return;
|
||||
}
|
||||
if let Some(mask) = collaboration_modes::plan_mask(self.models_manager.as_ref()) {
|
||||
self.set_collaboration_mask(mask);
|
||||
} else {
|
||||
self.add_info_message("Plan mode unavailable right now.".to_string(), None);
|
||||
}
|
||||
}
|
||||
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::Agent => {
|
||||
self.app_event_tx.send(AppEvent::OpenAgentPicker);
|
||||
|
|
@ -2844,11 +2863,9 @@ impl ChatWidget {
|
|||
|
||||
let trimmed = args.trim();
|
||||
match cmd {
|
||||
SlashCommand::Collab => {
|
||||
SlashCommand::Collab | SlashCommand::Plan => {
|
||||
let _ = trimmed;
|
||||
if self.collaboration_modes_enabled() {
|
||||
self.open_collaboration_modes_popup();
|
||||
}
|
||||
self.dispatch_command(cmd);
|
||||
}
|
||||
SlashCommand::Review if !trimmed.is_empty() => {
|
||||
self.submit_op(Op::Review {
|
||||
|
|
|
|||
|
|
@ -2267,6 +2267,19 @@ async fn collab_slash_command_opens_picker_and_updates_mode() {
|
|||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn plan_slash_command_switches_to_plan_mode() {
|
||||
let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None).await;
|
||||
chat.set_feature_enabled(Feature::CollaborationModes, true);
|
||||
let initial = chat.current_collaboration_mode().clone();
|
||||
|
||||
chat.dispatch_command(SlashCommand::Plan);
|
||||
|
||||
assert!(rx.try_recv().is_err(), "plan should not emit an app event");
|
||||
assert_eq!(chat.active_collaboration_mode_kind(), ModeKind::Plan);
|
||||
assert_eq!(chat.current_collaboration_mode(), &initial);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn collaboration_modes_defaults_to_code_on_startup() {
|
||||
let codex_home = tempdir().expect("tempdir");
|
||||
|
|
|
|||
|
|
@ -59,3 +59,7 @@ pub(crate) fn next_mask(
|
|||
pub(crate) fn code_mask(models_manager: &ModelsManager) -> Option<CollaborationModeMask> {
|
||||
mask_for_kind(models_manager, ModeKind::Code)
|
||||
}
|
||||
|
||||
pub(crate) fn plan_mask(models_manager: &ModelsManager) -> Option<CollaborationModeMask> {
|
||||
mask_for_kind(models_manager, ModeKind::Plan)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ pub enum SlashCommand {
|
|||
Fork,
|
||||
Init,
|
||||
Compact,
|
||||
Plan,
|
||||
Collab,
|
||||
Agent,
|
||||
// Undo,
|
||||
|
|
@ -63,6 +64,7 @@ impl SlashCommand {
|
|||
SlashCommand::Ps => "list background terminals",
|
||||
SlashCommand::Model => "choose what model and reasoning effort to use",
|
||||
SlashCommand::Personality => "choose a communication style for Codex",
|
||||
SlashCommand::Plan => "switch to Plan mode",
|
||||
SlashCommand::Collab => "change collaboration mode (experimental)",
|
||||
SlashCommand::Agent => "switch the active agent thread",
|
||||
SlashCommand::Approvals => "choose what Codex can do without approval",
|
||||
|
|
@ -112,6 +114,7 @@ impl SlashCommand {
|
|||
| SlashCommand::Exit => true,
|
||||
SlashCommand::Rollout => true,
|
||||
SlashCommand::TestApproval => true,
|
||||
SlashCommand::Plan => true,
|
||||
SlashCommand::Collab => true,
|
||||
SlashCommand::Agent => true,
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue