From 027524dac5cbbe036c0989f558104208dbd152f5 Mon Sep 17 00:00:00 2001 From: Charles Cunningham Date: Fri, 13 Mar 2026 12:16:11 -0700 Subject: [PATCH] tui: classify slash commands by execution kind Co-authored-by: Codex --- codex-rs/tui/src/chatwidget.rs | 10 ++-- codex-rs/tui/src/slash_command.rs | 82 +++++++++++++++++++------------ 2 files changed, 58 insertions(+), 34 deletions(-) diff --git a/codex-rs/tui/src/chatwidget.rs b/codex-rs/tui/src/chatwidget.rs index a4b8797d18..ef526b3821 100644 --- a/codex-rs/tui/src/chatwidget.rs +++ b/codex-rs/tui/src/chatwidget.rs @@ -48,6 +48,7 @@ use crate::bottom_pane::StatusLineItem; use crate::bottom_pane::StatusLinePreviewData; use crate::bottom_pane::StatusLineSetupView; use crate::bottom_pane::find_builtin_command; +use crate::slash_command::SlashCommandExecutionKind; use crate::slash_command_invocation::SlashCommandInvocation; use crate::status::RateLimitWindowDisplay; use crate::status::format_directory_display; @@ -4333,7 +4334,7 @@ impl ChatWidget { /// should not open that UI during replay. fn dispatch_command(&mut self, cmd: SlashCommand) -> QueueReplayControl { if self.bottom_pane.is_task_running() - && !cmd.available_during_task() + && !matches!(cmd.execution_kind(), SlashCommandExecutionKind::Immediate) && !cmd.requires_interaction() { self.queue_user_message(SlashCommandInvocation::bare(cmd).into_user_message()); @@ -4714,7 +4715,8 @@ impl ChatWidget { text_elements: Vec, ) { let trimmed = args.trim(); - let should_queue = self.bottom_pane.is_task_running() && !cmd.available_during_task(); + let should_queue = self.bottom_pane.is_task_running() + && !matches!(cmd.execution_kind(), SlashCommandExecutionKind::Immediate); if trimmed.is_empty() { if should_queue && !cmd.requires_interaction() { self.queue_current_inline_bare_slash_command(cmd); @@ -5739,7 +5741,9 @@ impl ChatWidget { self.restore_user_message_to_composer(draft); return; }; - if self.bottom_pane.is_task_running() && !cmd.available_during_task() { + if self.bottom_pane.is_task_running() + && !matches!(cmd.execution_kind(), SlashCommandExecutionKind::Immediate) + { self.queue_user_message(draft); return; } diff --git a/codex-rs/tui/src/slash_command.rs b/codex-rs/tui/src/slash_command.rs index 30ba911770..d2c58ee342 100644 --- a/codex-rs/tui/src/slash_command.rs +++ b/codex-rs/tui/src/slash_command.rs @@ -171,7 +171,7 @@ impl SlashCommand { SlashCommand::Feedback => &["", ""], SlashCommand::Rollout => &[""], SlashCommand::Ps => &[""], - SlashCommand::Clean => &[""], + SlashCommand::Stop => &[""], SlashCommand::Clear => &[""], SlashCommand::Personality => &["", ""], SlashCommand::Realtime => &[""], @@ -221,7 +221,7 @@ impl SlashCommand { | SlashCommand::Exit | SlashCommand::Rollout | SlashCommand::Ps - | SlashCommand::Clean + | SlashCommand::Stop | SlashCommand::Clear | SlashCommand::Realtime | SlashCommand::TestApproval @@ -230,52 +230,53 @@ impl SlashCommand { } } - /// Whether this command can be run while a task is in progress. - pub fn available_during_task(self) -> bool { + /// How this command should behave when dispatched while another turn is running. + pub fn execution_kind(self) -> SlashCommandExecutionKind { match self { - SlashCommand::Help => true, - SlashCommand::New - | SlashCommand::Resume - | SlashCommand::Fork - | SlashCommand::Init - | SlashCommand::Compact - // | SlashCommand::Undo - | SlashCommand::Model + SlashCommand::Plan | SlashCommand::Init => { + SlashCommandExecutionKind::JustLikeUserMessage + } + SlashCommand::Model | SlashCommand::Fast - | SlashCommand::Personality | SlashCommand::Approvals | SlashCommand::Permissions | SlashCommand::ElevateSandbox | SlashCommand::SandboxReadRoot | SlashCommand::Experimental | SlashCommand::Review - | SlashCommand::Plan + | SlashCommand::New + | SlashCommand::Resume + | SlashCommand::Fork + | SlashCommand::Compact | SlashCommand::Clear | SlashCommand::Logout + | SlashCommand::Personality + | SlashCommand::Statusline + | SlashCommand::Theme | SlashCommand::MemoryDrop - | SlashCommand::MemoryUpdate => false, - SlashCommand::Diff - | SlashCommand::Copy - | SlashCommand::Rename - | SlashCommand::Mention + | SlashCommand::MemoryUpdate => SlashCommandExecutionKind::ChangesTurnContext, + SlashCommand::Help | SlashCommand::Skills + | SlashCommand::Rename + | SlashCommand::Collab + | SlashCommand::Agent + | SlashCommand::MultiAgents + | SlashCommand::Diff + | SlashCommand::Copy + | SlashCommand::Mention | SlashCommand::Status | SlashCommand::DebugConfig - | SlashCommand::Ps - | SlashCommand::Stop | SlashCommand::Mcp | SlashCommand::Apps - | SlashCommand::Feedback | SlashCommand::Quit - | SlashCommand::Exit => true, - SlashCommand::Rollout => true, - SlashCommand::TestApproval => true, - SlashCommand::Realtime => true, - SlashCommand::Settings => true, - SlashCommand::Collab => true, - SlashCommand::Agent | SlashCommand::MultiAgents => true, - SlashCommand::Statusline => false, - SlashCommand::Theme => false, + | SlashCommand::Exit + | SlashCommand::Feedback + | SlashCommand::Rollout + | SlashCommand::Ps + | SlashCommand::Stop + | SlashCommand::Realtime + | SlashCommand::Settings + | SlashCommand::TestApproval => SlashCommandExecutionKind::Immediate, } } @@ -289,6 +290,25 @@ impl SlashCommand { } } +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum SlashCommandExecutionKind { + /// Behaves like a normal user message. + /// + /// Enter should submit immediately when idle, and queue while a turn is running. + /// Use this for commands whose effect is "ask the model to do work now". + JustLikeUserMessage, + + /// Does not become a user message, but changes state that affects future turns. + /// + /// While a turn is running, it must queue and apply later in order. + ChangesTurnContext, + + /// Does not submit model work and does not need to wait for the current turn. + /// + /// Run it immediately, even while a turn is in progress. + Immediate, +} + /// Return all built-in commands in a Vec paired with their command string. pub fn built_in_slash_commands() -> Vec<(&'static str, SlashCommand)> { SlashCommand::iter()