tui: classify slash commands by execution kind

Co-authored-by: Codex <noreply@openai.com>
This commit is contained in:
Charles Cunningham
2026-03-13 12:16:11 -07:00
parent 02f6733dea
commit 027524dac5
2 changed files with 58 additions and 34 deletions

View File

@@ -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<TextElement>,
) {
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;
}

View File

@@ -171,7 +171,7 @@ impl SlashCommand {
SlashCommand::Feedback => &["", "<bug|bad-result|good-result|safety-check|other>"],
SlashCommand::Rollout => &[""],
SlashCommand::Ps => &[""],
SlashCommand::Clean => &[""],
SlashCommand::Stop => &[""],
SlashCommand::Clear => &[""],
SlashCommand::Personality => &["", "<none|friendly|pragmatic>"],
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()