mirror of
https://github.com/openai/codex.git
synced 2026-10-20 17:38:45 +00:00
tui: simplify slash command policy wiring
Centralize slash command metadata, dedupe runtime builtin gating, and make popup-driven queued replay resume explicit. Co-authored-by: Codex <noreply@openai.com>
This commit is contained in:
co-authored by
Codex
parent
5d81980593
commit
9ec1452655
+21
-4
@@ -730,6 +730,7 @@ pub(crate) struct App {
|
||||
primary_thread_id: Option<ThreadId>,
|
||||
primary_session_configured: Option<SessionConfiguredEvent>,
|
||||
pending_primary_events: VecDeque<Event>,
|
||||
resume_queued_inputs_after_app_events: bool,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
@@ -757,6 +758,19 @@ fn normalize_harness_overrides_for_cwd(
|
||||
}
|
||||
|
||||
impl App {
|
||||
fn on_bottom_pane_view_completed(&mut self) {
|
||||
self.resume_queued_inputs_after_app_events = true;
|
||||
}
|
||||
|
||||
fn maybe_resume_queued_inputs_after_app_events(&mut self, app_events_drained: bool) {
|
||||
if !self.resume_queued_inputs_after_app_events || !app_events_drained {
|
||||
return;
|
||||
}
|
||||
|
||||
self.resume_queued_inputs_after_app_events = false;
|
||||
self.chat_widget.maybe_resume_queued_inputs_when_idle();
|
||||
}
|
||||
|
||||
pub fn chatwidget_init_for_forked_or_resumed_thread(
|
||||
&self,
|
||||
tui: &mut tui::Tui,
|
||||
@@ -2293,6 +2307,7 @@ impl App {
|
||||
primary_thread_id: None,
|
||||
primary_session_configured: None,
|
||||
pending_primary_events: VecDeque::new(),
|
||||
resume_queued_inputs_after_app_events: false,
|
||||
};
|
||||
|
||||
// On startup, if Agent mode (workspace-write) or ReadOnly is active, warn about world-writable dirs on Windows.
|
||||
@@ -2425,9 +2440,7 @@ impl App {
|
||||
// or popup flows settle. Only resume once the app-event queue is fully drained so
|
||||
// multi-event commands (for example approvals updates) cannot interleave later
|
||||
// queued input.
|
||||
if app_event_rx.is_empty() {
|
||||
app.chat_widget.maybe_resume_queued_inputs_when_idle();
|
||||
}
|
||||
app.maybe_resume_queued_inputs_after_app_events(app_event_rx.is_empty());
|
||||
match control {
|
||||
AppRunControl::Continue => {}
|
||||
AppRunControl::Exit(reason) => break Ok(reason),
|
||||
@@ -2772,7 +2785,9 @@ impl App {
|
||||
self.chat_widget.handle_serialized_slash_command(draft);
|
||||
self.refresh_status_line();
|
||||
}
|
||||
AppEvent::BottomPaneViewCompleted => {}
|
||||
AppEvent::BottomPaneViewCompleted => {
|
||||
self.on_bottom_pane_view_completed();
|
||||
}
|
||||
AppEvent::UpdateCollaborationMode(mask) => {
|
||||
self.chat_widget.set_collaboration_mask(mask);
|
||||
self.refresh_status_line();
|
||||
@@ -6549,6 +6564,7 @@ guardian_approval = true
|
||||
primary_thread_id: None,
|
||||
primary_session_configured: None,
|
||||
pending_primary_events: VecDeque::new(),
|
||||
resume_queued_inputs_after_app_events: false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6609,6 +6625,7 @@ guardian_approval = true
|
||||
primary_thread_id: None,
|
||||
primary_session_configured: None,
|
||||
pending_primary_events: VecDeque::new(),
|
||||
resume_queued_inputs_after_app_events: false,
|
||||
},
|
||||
rx,
|
||||
op_rx,
|
||||
|
||||
@@ -12,12 +12,6 @@ use crate::render::RectExt;
|
||||
use crate::slash_command::SlashCommand;
|
||||
use codex_protocol::custom_prompts::CustomPrompt;
|
||||
use codex_protocol::custom_prompts::PROMPTS_CMD_PREFIX;
|
||||
use std::collections::HashSet;
|
||||
|
||||
// Hide alias commands in the default popup list so each unique action appears once.
|
||||
// `quit` is an alias of `exit`, so we skip `quit` here.
|
||||
// `approvals` is an alias of `permissions`.
|
||||
const ALIAS_COMMANDS: &[SlashCommand] = &[SlashCommand::Quit, SlashCommand::Approvals];
|
||||
|
||||
/// A selectable item in the popup: either a built-in command or a user prompt.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
@@ -30,6 +24,7 @@ pub(crate) enum CommandItem {
|
||||
pub(crate) struct CommandPopup {
|
||||
command_filter: String,
|
||||
builtins: Vec<SlashCommand>,
|
||||
reserved_builtin_names: std::collections::HashSet<String>,
|
||||
prompts: Vec<CustomPrompt>,
|
||||
state: ScrollState,
|
||||
}
|
||||
@@ -62,25 +57,27 @@ impl From<CommandPopupFlags> for slash_commands::BuiltinCommandFlags {
|
||||
impl CommandPopup {
|
||||
pub(crate) fn new(mut prompts: Vec<CustomPrompt>, flags: CommandPopupFlags) -> Self {
|
||||
// Keep built-in availability in sync with the composer.
|
||||
let builtins: Vec<SlashCommand> = slash_commands::visible_builtins_for_input(flags.into())
|
||||
let builtin_flags = flags.into();
|
||||
let builtins = slash_commands::visible_builtins_for_input(builtin_flags)
|
||||
.into_iter()
|
||||
.filter(|cmd| !cmd.command().starts_with("debug"))
|
||||
.collect();
|
||||
// Exclude prompts that collide with builtin command names and sort by name.
|
||||
let exclude = reserved_builtin_names(&builtins);
|
||||
prompts.retain(|p| !exclude.contains(&p.name));
|
||||
let reserved_builtin_names =
|
||||
slash_commands::reserved_builtin_names_for_input(builtin_flags);
|
||||
prompts.retain(|p| !reserved_builtin_names.contains(&p.name));
|
||||
prompts.sort_by(|a, b| a.name.cmp(&b.name));
|
||||
Self {
|
||||
command_filter: String::new(),
|
||||
builtins,
|
||||
reserved_builtin_names,
|
||||
prompts,
|
||||
state: ScrollState::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn set_prompts(&mut self, mut prompts: Vec<CustomPrompt>) {
|
||||
let exclude = reserved_builtin_names(&self.builtins);
|
||||
prompts.retain(|p| !exclude.contains(&p.name));
|
||||
prompts.retain(|p| !self.reserved_builtin_names.contains(&p.name));
|
||||
prompts.sort_by(|a, b| a.name.cmp(&b.name));
|
||||
self.prompts = prompts;
|
||||
}
|
||||
@@ -138,7 +135,7 @@ impl CommandPopup {
|
||||
if filter.is_empty() {
|
||||
// Built-ins first, in presentation order.
|
||||
for cmd in self.builtins.iter() {
|
||||
if ALIAS_COMMANDS.contains(cmd) {
|
||||
if !cmd.show_in_command_popup() {
|
||||
continue;
|
||||
}
|
||||
out.push((CommandItem::Builtin(*cmd), None));
|
||||
@@ -265,14 +262,6 @@ impl CommandPopup {
|
||||
}
|
||||
}
|
||||
|
||||
fn reserved_builtin_names(builtins: &[SlashCommand]) -> HashSet<String> {
|
||||
builtins
|
||||
.iter()
|
||||
.flat_map(|cmd| std::iter::once(cmd.command()).chain(cmd.command_aliases().iter().copied()))
|
||||
.map(str::to_string)
|
||||
.collect()
|
||||
}
|
||||
|
||||
impl WidgetRef for CommandPopup {
|
||||
fn render_ref(&self, area: Rect, buf: &mut Buffer) {
|
||||
let rows = self.rows_from_matches(self.filtered());
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
//! The same sandbox- and feature-gating rules are used by both the composer
|
||||
//! and the command popup. Centralizing them here keeps those call sites small
|
||||
//! and ensures they stay in sync.
|
||||
use std::str::FromStr;
|
||||
use std::collections::HashSet;
|
||||
|
||||
use codex_utils_fuzzy_match::fuzzy_match;
|
||||
|
||||
@@ -22,20 +22,38 @@ pub(crate) struct BuiltinCommandFlags {
|
||||
pub(crate) allow_elevate_sandbox: bool,
|
||||
}
|
||||
|
||||
fn command_enabled_for_input(cmd: SlashCommand, flags: BuiltinCommandFlags) -> bool {
|
||||
if !flags.allow_elevate_sandbox && cmd == SlashCommand::ElevateSandbox {
|
||||
return false;
|
||||
}
|
||||
if !flags.collaboration_modes_enabled
|
||||
&& matches!(cmd, SlashCommand::Collab | SlashCommand::Plan)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if !flags.connectors_enabled && cmd == SlashCommand::Apps {
|
||||
return false;
|
||||
}
|
||||
if !flags.fast_command_enabled && cmd == SlashCommand::Fast {
|
||||
return false;
|
||||
}
|
||||
if !flags.personality_command_enabled && cmd == SlashCommand::Personality {
|
||||
return false;
|
||||
}
|
||||
if !flags.realtime_conversation_enabled && cmd == SlashCommand::Realtime {
|
||||
return false;
|
||||
}
|
||||
if !flags.audio_device_selection_enabled && cmd == SlashCommand::Settings {
|
||||
return false;
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
/// Return the built-ins that should be visible/usable for the current input.
|
||||
pub(crate) fn builtins_for_input(flags: BuiltinCommandFlags) -> Vec<(&'static str, SlashCommand)> {
|
||||
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.connectors_enabled || *cmd != SlashCommand::Apps)
|
||||
.filter(|(_, cmd)| flags.fast_command_enabled || *cmd != SlashCommand::Fast)
|
||||
.filter(|(_, cmd)| flags.personality_command_enabled || *cmd != SlashCommand::Personality)
|
||||
.filter(|(_, cmd)| flags.realtime_conversation_enabled || *cmd != SlashCommand::Realtime)
|
||||
.filter(|(_, cmd)| flags.audio_device_selection_enabled || *cmd != SlashCommand::Settings)
|
||||
.filter(|(_, cmd)| command_enabled_for_input(*cmd, flags))
|
||||
.collect()
|
||||
}
|
||||
|
||||
@@ -43,26 +61,25 @@ pub(crate) fn builtins_for_input(flags: BuiltinCommandFlags) -> Vec<(&'static st
|
||||
pub(crate) fn visible_builtins_for_input(flags: BuiltinCommandFlags) -> Vec<SlashCommand> {
|
||||
visible_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.connectors_enabled || *cmd != SlashCommand::Apps)
|
||||
.filter(|cmd| flags.fast_command_enabled || *cmd != SlashCommand::Fast)
|
||||
.filter(|cmd| flags.personality_command_enabled || *cmd != SlashCommand::Personality)
|
||||
.filter(|cmd| flags.realtime_conversation_enabled || *cmd != SlashCommand::Realtime)
|
||||
.filter(|cmd| flags.audio_device_selection_enabled || *cmd != SlashCommand::Settings)
|
||||
.filter(|cmd| command_enabled_for_input(*cmd, flags))
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Find a single built-in command by exact name, after applying the gating rules.
|
||||
pub(crate) fn find_builtin_command(name: &str, flags: BuiltinCommandFlags) -> Option<SlashCommand> {
|
||||
let cmd = SlashCommand::from_str(name).ok()?;
|
||||
builtins_for_input(flags)
|
||||
.into_iter()
|
||||
.any(|(_, visible_cmd)| visible_cmd == cmd)
|
||||
.then_some(cmd)
|
||||
.find(|(command_name, _)| *command_name == name)
|
||||
.map(|(_, cmd)| cmd)
|
||||
}
|
||||
|
||||
/// Return every builtin name that should be reserved against custom prompt collisions.
|
||||
pub(crate) fn reserved_builtin_names_for_input(flags: BuiltinCommandFlags) -> HashSet<String> {
|
||||
visible_builtins_for_input(flags)
|
||||
.into_iter()
|
||||
.flat_map(SlashCommand::all_command_names)
|
||||
.map(str::to_string)
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Whether any visible built-in fuzzily matches the provided prefix.
|
||||
|
||||
+344
-200
@@ -66,54 +66,330 @@ pub enum SlashCommand {
|
||||
}
|
||||
|
||||
impl SlashCommand {
|
||||
fn spec(self) -> SlashCommandSpec {
|
||||
match self {
|
||||
SlashCommand::Help => SlashCommandSpec {
|
||||
description: "show slash command help",
|
||||
help_forms: &[""],
|
||||
requires_interaction: false,
|
||||
execution_kind: SlashCommandExecutionKind::Immediate,
|
||||
show_in_command_popup: true,
|
||||
},
|
||||
SlashCommand::Model => SlashCommandSpec {
|
||||
description: "choose what model and reasoning effort to use",
|
||||
help_forms: &[
|
||||
"",
|
||||
"<model> [default|none|minimal|low|medium|high|xhigh] [plan-only|all-modes]",
|
||||
],
|
||||
requires_interaction: true,
|
||||
execution_kind: SlashCommandExecutionKind::ChangesTurnContext,
|
||||
show_in_command_popup: true,
|
||||
},
|
||||
SlashCommand::Fast => SlashCommandSpec {
|
||||
description: "toggle Fast mode to enable fastest inference at 2X plan usage",
|
||||
help_forms: &["", "<on|off|status>"],
|
||||
requires_interaction: false,
|
||||
execution_kind: SlashCommandExecutionKind::ChangesTurnContext,
|
||||
show_in_command_popup: true,
|
||||
},
|
||||
SlashCommand::Approvals => SlashCommandSpec {
|
||||
description: "choose what Codex is allowed to do",
|
||||
help_forms: &[
|
||||
"",
|
||||
"<read-only|auto|full-access> [--smart-approvals] [--confirm-full-access] [--remember-full-access] [--confirm-world-writable] [--remember-world-writable] [--enable-windows-sandbox=elevated|legacy]",
|
||||
],
|
||||
requires_interaction: true,
|
||||
execution_kind: SlashCommandExecutionKind::ChangesTurnContext,
|
||||
show_in_command_popup: false,
|
||||
},
|
||||
SlashCommand::Permissions => SlashCommandSpec {
|
||||
description: "choose what Codex is allowed to do",
|
||||
help_forms: &[
|
||||
"",
|
||||
"<read-only|auto|full-access> [--smart-approvals] [--confirm-full-access] [--remember-full-access] [--confirm-world-writable] [--remember-world-writable] [--enable-windows-sandbox=elevated|legacy]",
|
||||
],
|
||||
requires_interaction: true,
|
||||
execution_kind: SlashCommandExecutionKind::ChangesTurnContext,
|
||||
show_in_command_popup: true,
|
||||
},
|
||||
SlashCommand::ElevateSandbox => SlashCommandSpec {
|
||||
description: "set up elevated agent sandbox",
|
||||
help_forms: &[""],
|
||||
requires_interaction: false,
|
||||
execution_kind: SlashCommandExecutionKind::ChangesTurnContext,
|
||||
show_in_command_popup: true,
|
||||
},
|
||||
SlashCommand::SandboxReadRoot => SlashCommandSpec {
|
||||
description: "let sandbox read a directory: /sandbox-add-read-dir <absolute_path>",
|
||||
help_forms: &["<absolute-directory-path>"],
|
||||
requires_interaction: false,
|
||||
execution_kind: SlashCommandExecutionKind::ChangesTurnContext,
|
||||
show_in_command_popup: true,
|
||||
},
|
||||
SlashCommand::Experimental => SlashCommandSpec {
|
||||
description: "toggle experimental features",
|
||||
help_forms: &["", "<feature-key>=on|off ..."],
|
||||
requires_interaction: true,
|
||||
execution_kind: SlashCommandExecutionKind::ChangesTurnContext,
|
||||
show_in_command_popup: true,
|
||||
},
|
||||
SlashCommand::Skills => SlashCommandSpec {
|
||||
description: "use skills to improve how Codex performs specific tasks",
|
||||
help_forms: &["", "<list|manage>"],
|
||||
requires_interaction: true,
|
||||
execution_kind: SlashCommandExecutionKind::Immediate,
|
||||
show_in_command_popup: true,
|
||||
},
|
||||
SlashCommand::Review => SlashCommandSpec {
|
||||
description: "review my current changes and find issues",
|
||||
help_forms: &[
|
||||
"",
|
||||
"uncommitted",
|
||||
"branch <name>",
|
||||
"commit <sha> [title]",
|
||||
"<instructions>",
|
||||
],
|
||||
requires_interaction: true,
|
||||
execution_kind: SlashCommandExecutionKind::ChangesTurnContext,
|
||||
show_in_command_popup: true,
|
||||
},
|
||||
SlashCommand::Rename => SlashCommandSpec {
|
||||
description: "rename the current thread",
|
||||
help_forms: &["", "<title...>"],
|
||||
requires_interaction: true,
|
||||
execution_kind: SlashCommandExecutionKind::Immediate,
|
||||
show_in_command_popup: true,
|
||||
},
|
||||
SlashCommand::New => SlashCommandSpec {
|
||||
description: "start a new chat during a conversation",
|
||||
help_forms: &[""],
|
||||
requires_interaction: false,
|
||||
execution_kind: SlashCommandExecutionKind::ChangesTurnContext,
|
||||
show_in_command_popup: true,
|
||||
},
|
||||
SlashCommand::Resume => SlashCommandSpec {
|
||||
description: "resume a saved chat",
|
||||
help_forms: &["", "<thread-id>", "<thread-id> --path <rollout-path>"],
|
||||
requires_interaction: true,
|
||||
execution_kind: SlashCommandExecutionKind::ChangesTurnContext,
|
||||
show_in_command_popup: true,
|
||||
},
|
||||
SlashCommand::Fork => SlashCommandSpec {
|
||||
description: "fork the current chat",
|
||||
help_forms: &[""],
|
||||
requires_interaction: false,
|
||||
execution_kind: SlashCommandExecutionKind::ChangesTurnContext,
|
||||
show_in_command_popup: true,
|
||||
},
|
||||
SlashCommand::Init => SlashCommandSpec {
|
||||
description: "create an AGENTS.md file with instructions for Codex",
|
||||
help_forms: &[""],
|
||||
requires_interaction: false,
|
||||
execution_kind: SlashCommandExecutionKind::JustLikeUserMessage,
|
||||
show_in_command_popup: true,
|
||||
},
|
||||
SlashCommand::Compact => SlashCommandSpec {
|
||||
description: "summarize conversation to prevent hitting the context limit",
|
||||
help_forms: &[""],
|
||||
requires_interaction: false,
|
||||
execution_kind: SlashCommandExecutionKind::ChangesTurnContext,
|
||||
show_in_command_popup: true,
|
||||
},
|
||||
SlashCommand::Plan => SlashCommandSpec {
|
||||
description: "switch to Plan mode",
|
||||
help_forms: &["", "<prompt...>"],
|
||||
requires_interaction: false,
|
||||
execution_kind: SlashCommandExecutionKind::JustLikeUserMessage,
|
||||
show_in_command_popup: true,
|
||||
},
|
||||
SlashCommand::Collab => SlashCommandSpec {
|
||||
description: "change collaboration mode (experimental)",
|
||||
help_forms: &["", "<default|plan>"],
|
||||
requires_interaction: true,
|
||||
execution_kind: SlashCommandExecutionKind::Immediate,
|
||||
show_in_command_popup: true,
|
||||
},
|
||||
SlashCommand::Agent => SlashCommandSpec {
|
||||
description: "switch the active agent thread",
|
||||
help_forms: &["", "<thread-id>"],
|
||||
requires_interaction: true,
|
||||
execution_kind: SlashCommandExecutionKind::Immediate,
|
||||
show_in_command_popup: true,
|
||||
},
|
||||
SlashCommand::Diff => SlashCommandSpec {
|
||||
description: "show git diff (including untracked files)",
|
||||
help_forms: &[""],
|
||||
requires_interaction: false,
|
||||
execution_kind: SlashCommandExecutionKind::Immediate,
|
||||
show_in_command_popup: true,
|
||||
},
|
||||
SlashCommand::Copy => SlashCommandSpec {
|
||||
description: "copy the latest Codex output to your clipboard",
|
||||
help_forms: &[""],
|
||||
requires_interaction: false,
|
||||
execution_kind: SlashCommandExecutionKind::Immediate,
|
||||
show_in_command_popup: true,
|
||||
},
|
||||
SlashCommand::Mention => SlashCommandSpec {
|
||||
description: "mention a file",
|
||||
help_forms: &[""],
|
||||
requires_interaction: false,
|
||||
execution_kind: SlashCommandExecutionKind::Immediate,
|
||||
show_in_command_popup: true,
|
||||
},
|
||||
SlashCommand::Status => SlashCommandSpec {
|
||||
description: "show current session configuration and token usage",
|
||||
help_forms: &[""],
|
||||
requires_interaction: false,
|
||||
execution_kind: SlashCommandExecutionKind::Immediate,
|
||||
show_in_command_popup: true,
|
||||
},
|
||||
SlashCommand::DebugConfig => SlashCommandSpec {
|
||||
description: "show config layers and requirement sources for debugging",
|
||||
help_forms: &[""],
|
||||
requires_interaction: false,
|
||||
execution_kind: SlashCommandExecutionKind::Immediate,
|
||||
show_in_command_popup: true,
|
||||
},
|
||||
SlashCommand::Statusline => SlashCommandSpec {
|
||||
description: "configure which items appear in the status line",
|
||||
help_forms: &["", "<item-id>...", "none"],
|
||||
requires_interaction: true,
|
||||
execution_kind: SlashCommandExecutionKind::ChangesTurnContext,
|
||||
show_in_command_popup: true,
|
||||
},
|
||||
SlashCommand::Theme => SlashCommandSpec {
|
||||
description: "choose a syntax highlighting theme",
|
||||
help_forms: &["", "<theme-name>"],
|
||||
requires_interaction: true,
|
||||
execution_kind: SlashCommandExecutionKind::ChangesTurnContext,
|
||||
show_in_command_popup: true,
|
||||
},
|
||||
SlashCommand::Mcp => SlashCommandSpec {
|
||||
description: "list configured MCP tools",
|
||||
help_forms: &[""],
|
||||
requires_interaction: false,
|
||||
execution_kind: SlashCommandExecutionKind::Immediate,
|
||||
show_in_command_popup: true,
|
||||
},
|
||||
SlashCommand::Apps => SlashCommandSpec {
|
||||
description: "manage apps",
|
||||
help_forms: &[""],
|
||||
requires_interaction: false,
|
||||
execution_kind: SlashCommandExecutionKind::Immediate,
|
||||
show_in_command_popup: true,
|
||||
},
|
||||
SlashCommand::Logout => SlashCommandSpec {
|
||||
description: "log out of Codex",
|
||||
help_forms: &[""],
|
||||
requires_interaction: false,
|
||||
execution_kind: SlashCommandExecutionKind::ChangesTurnContext,
|
||||
show_in_command_popup: true,
|
||||
},
|
||||
SlashCommand::Quit => SlashCommandSpec {
|
||||
description: "exit Codex",
|
||||
help_forms: &[""],
|
||||
requires_interaction: false,
|
||||
execution_kind: SlashCommandExecutionKind::Immediate,
|
||||
show_in_command_popup: false,
|
||||
},
|
||||
SlashCommand::Exit => SlashCommandSpec {
|
||||
description: "exit Codex",
|
||||
help_forms: &[""],
|
||||
requires_interaction: false,
|
||||
execution_kind: SlashCommandExecutionKind::Immediate,
|
||||
show_in_command_popup: true,
|
||||
},
|
||||
SlashCommand::Feedback => SlashCommandSpec {
|
||||
description: "send logs to maintainers",
|
||||
help_forms: &["", "<bug|bad-result|good-result|safety-check|other>"],
|
||||
requires_interaction: true,
|
||||
execution_kind: SlashCommandExecutionKind::Immediate,
|
||||
show_in_command_popup: true,
|
||||
},
|
||||
SlashCommand::Rollout => SlashCommandSpec {
|
||||
description: "print the rollout file path",
|
||||
help_forms: &[""],
|
||||
requires_interaction: false,
|
||||
execution_kind: SlashCommandExecutionKind::Immediate,
|
||||
show_in_command_popup: true,
|
||||
},
|
||||
SlashCommand::Ps => SlashCommandSpec {
|
||||
description: "list background terminals",
|
||||
help_forms: &[""],
|
||||
requires_interaction: false,
|
||||
execution_kind: SlashCommandExecutionKind::Immediate,
|
||||
show_in_command_popup: true,
|
||||
},
|
||||
SlashCommand::Stop => SlashCommandSpec {
|
||||
description: "stop all background terminals",
|
||||
help_forms: &[""],
|
||||
requires_interaction: false,
|
||||
execution_kind: SlashCommandExecutionKind::Immediate,
|
||||
show_in_command_popup: true,
|
||||
},
|
||||
SlashCommand::Clear => SlashCommandSpec {
|
||||
description: "clear the terminal and start a new chat",
|
||||
help_forms: &[""],
|
||||
requires_interaction: false,
|
||||
execution_kind: SlashCommandExecutionKind::ChangesTurnContext,
|
||||
show_in_command_popup: true,
|
||||
},
|
||||
SlashCommand::Personality => SlashCommandSpec {
|
||||
description: "choose a communication style for Codex",
|
||||
help_forms: &["", "<none|friendly|pragmatic>"],
|
||||
requires_interaction: true,
|
||||
execution_kind: SlashCommandExecutionKind::ChangesTurnContext,
|
||||
show_in_command_popup: true,
|
||||
},
|
||||
SlashCommand::Realtime => SlashCommandSpec {
|
||||
description: "toggle realtime voice mode (experimental)",
|
||||
help_forms: &[""],
|
||||
requires_interaction: false,
|
||||
execution_kind: SlashCommandExecutionKind::Immediate,
|
||||
show_in_command_popup: true,
|
||||
},
|
||||
SlashCommand::Settings => SlashCommandSpec {
|
||||
description: "configure realtime microphone/speaker",
|
||||
help_forms: &["", "<microphone|speaker> [default|<device-name>]"],
|
||||
requires_interaction: true,
|
||||
execution_kind: SlashCommandExecutionKind::Immediate,
|
||||
show_in_command_popup: true,
|
||||
},
|
||||
SlashCommand::TestApproval => SlashCommandSpec {
|
||||
description: "test approval request",
|
||||
help_forms: &[""],
|
||||
requires_interaction: false,
|
||||
execution_kind: SlashCommandExecutionKind::Immediate,
|
||||
show_in_command_popup: true,
|
||||
},
|
||||
SlashCommand::MultiAgents => SlashCommandSpec {
|
||||
description: "switch the active agent thread",
|
||||
help_forms: &["", "<thread-id>"],
|
||||
requires_interaction: true,
|
||||
execution_kind: SlashCommandExecutionKind::Immediate,
|
||||
show_in_command_popup: true,
|
||||
},
|
||||
SlashCommand::MemoryDrop => SlashCommandSpec {
|
||||
description: "DO NOT USE",
|
||||
help_forms: &[""],
|
||||
requires_interaction: false,
|
||||
execution_kind: SlashCommandExecutionKind::ChangesTurnContext,
|
||||
show_in_command_popup: true,
|
||||
},
|
||||
SlashCommand::MemoryUpdate => SlashCommandSpec {
|
||||
description: "DO NOT USE",
|
||||
help_forms: &[""],
|
||||
requires_interaction: false,
|
||||
execution_kind: SlashCommandExecutionKind::ChangesTurnContext,
|
||||
show_in_command_popup: true,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// User-visible description shown in the popup.
|
||||
pub fn description(self) -> &'static str {
|
||||
match self {
|
||||
SlashCommand::Help => "show slash command help",
|
||||
SlashCommand::Feedback => "send logs to maintainers",
|
||||
SlashCommand::New => "start a new chat during a conversation",
|
||||
SlashCommand::Init => "create an AGENTS.md file with instructions for Codex",
|
||||
SlashCommand::Compact => "summarize conversation to prevent hitting the context limit",
|
||||
SlashCommand::Review => "review my current changes and find issues",
|
||||
SlashCommand::Rename => "rename the current thread",
|
||||
SlashCommand::Resume => "resume a saved chat",
|
||||
SlashCommand::Clear => "clear the terminal and start a new chat",
|
||||
SlashCommand::Fork => "fork the current chat",
|
||||
// SlashCommand::Undo => "ask Codex to undo a turn",
|
||||
SlashCommand::Quit | SlashCommand::Exit => "exit Codex",
|
||||
SlashCommand::Diff => "show git diff (including untracked files)",
|
||||
SlashCommand::Copy => "copy the latest Codex output to your clipboard",
|
||||
SlashCommand::Mention => "mention a file",
|
||||
SlashCommand::Skills => "use skills to improve how Codex performs specific tasks",
|
||||
SlashCommand::Status => "show current session configuration and token usage",
|
||||
SlashCommand::DebugConfig => "show config layers and requirement sources for debugging",
|
||||
SlashCommand::Statusline => "configure which items appear in the status line",
|
||||
SlashCommand::Theme => "choose a syntax highlighting theme",
|
||||
SlashCommand::Ps => "list background terminals",
|
||||
SlashCommand::Stop => "stop all background terminals",
|
||||
SlashCommand::MemoryDrop => "DO NOT USE",
|
||||
SlashCommand::MemoryUpdate => "DO NOT USE",
|
||||
SlashCommand::Model => "choose what model and reasoning effort to use",
|
||||
SlashCommand::Fast => "toggle Fast mode to enable fastest inference at 2X plan usage",
|
||||
SlashCommand::Personality => "choose a communication style for Codex",
|
||||
SlashCommand::Realtime => "toggle realtime voice mode (experimental)",
|
||||
SlashCommand::Settings => "configure realtime microphone/speaker",
|
||||
SlashCommand::Plan => "switch to Plan mode",
|
||||
SlashCommand::Collab => "change collaboration mode (experimental)",
|
||||
SlashCommand::Agent | SlashCommand::MultiAgents => "switch the active agent thread",
|
||||
SlashCommand::Approvals => "choose what Codex is allowed to do",
|
||||
SlashCommand::Permissions => "choose what Codex is allowed to do",
|
||||
SlashCommand::ElevateSandbox => "set up elevated agent sandbox",
|
||||
SlashCommand::SandboxReadRoot => {
|
||||
"let sandbox read a directory: /sandbox-add-read-dir <absolute_path>"
|
||||
}
|
||||
SlashCommand::Experimental => "toggle experimental features",
|
||||
SlashCommand::Mcp => "list configured MCP tools",
|
||||
SlashCommand::Apps => "manage apps",
|
||||
SlashCommand::Logout => "log out of Codex",
|
||||
SlashCommand::Rollout => "print the rollout file path",
|
||||
SlashCommand::TestApproval => "test approval request",
|
||||
}
|
||||
self.spec().description
|
||||
}
|
||||
|
||||
/// Command string without the leading '/'. Provided for compatibility with
|
||||
@@ -162,7 +438,6 @@ impl SlashCommand {
|
||||
| SlashCommand::Feedback
|
||||
| SlashCommand::Rollout
|
||||
| SlashCommand::Ps
|
||||
| SlashCommand::Stop
|
||||
| SlashCommand::Clear
|
||||
| SlashCommand::Personality
|
||||
| SlashCommand::Realtime
|
||||
@@ -170,166 +445,34 @@ impl SlashCommand {
|
||||
| SlashCommand::TestApproval
|
||||
| SlashCommand::MemoryDrop
|
||||
| SlashCommand::MemoryUpdate => &[],
|
||||
SlashCommand::Stop => &["clean"],
|
||||
SlashCommand::MultiAgents => &["multi-agents"],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn all_command_names(self) -> impl Iterator<Item = &'static str> {
|
||||
std::iter::once(self.command()).chain(self.command_aliases().iter().copied())
|
||||
}
|
||||
|
||||
/// Human-facing forms accepted by the TUI.
|
||||
///
|
||||
/// An empty string represents the bare `/command` form.
|
||||
pub fn help_forms(self) -> &'static [&'static str] {
|
||||
match self {
|
||||
SlashCommand::Help => &[""],
|
||||
SlashCommand::Model => &[
|
||||
"",
|
||||
"<model> [default|none|minimal|low|medium|high|xhigh] [plan-only|all-modes]",
|
||||
],
|
||||
SlashCommand::Fast => &["", "<on|off|status>"],
|
||||
SlashCommand::Approvals | SlashCommand::Permissions => &[
|
||||
"",
|
||||
"<read-only|auto|full-access> [--smart-approvals] [--confirm-full-access] [--remember-full-access] [--confirm-world-writable] [--remember-world-writable] [--enable-windows-sandbox=elevated|legacy]",
|
||||
],
|
||||
SlashCommand::ElevateSandbox => &[""],
|
||||
SlashCommand::SandboxReadRoot => &["<absolute-directory-path>"],
|
||||
SlashCommand::Experimental => &["", "<feature-key>=on|off ..."],
|
||||
SlashCommand::Skills => &["", "<list|manage>"],
|
||||
SlashCommand::Review => &[
|
||||
"",
|
||||
"uncommitted",
|
||||
"branch <name>",
|
||||
"commit <sha> [title]",
|
||||
"<instructions>",
|
||||
],
|
||||
SlashCommand::Rename => &["", "<title...>"],
|
||||
SlashCommand::New => &[""],
|
||||
SlashCommand::Resume => &["", "<thread-id>", "<thread-id> --path <rollout-path>"],
|
||||
SlashCommand::Fork => &[""],
|
||||
SlashCommand::Init => &[""],
|
||||
SlashCommand::Compact => &[""],
|
||||
SlashCommand::Plan => &["", "<prompt...>"],
|
||||
SlashCommand::Collab => &["", "<default|plan>"],
|
||||
SlashCommand::Agent | SlashCommand::MultiAgents => &["", "<thread-id>"],
|
||||
SlashCommand::Diff => &[""],
|
||||
SlashCommand::Copy => &[""],
|
||||
SlashCommand::Mention => &[""],
|
||||
SlashCommand::Status => &[""],
|
||||
SlashCommand::DebugConfig => &[""],
|
||||
SlashCommand::Statusline => &["", "<item-id>...", "none"],
|
||||
SlashCommand::Theme => &["", "<theme-name>"],
|
||||
SlashCommand::Mcp => &[""],
|
||||
SlashCommand::Apps => &[""],
|
||||
SlashCommand::Logout => &[""],
|
||||
SlashCommand::Quit | SlashCommand::Exit => &[""],
|
||||
SlashCommand::Feedback => &["", "<bug|bad-result|good-result|safety-check|other>"],
|
||||
SlashCommand::Rollout => &[""],
|
||||
SlashCommand::Ps => &[""],
|
||||
SlashCommand::Stop => &[""],
|
||||
SlashCommand::Clear => &[""],
|
||||
SlashCommand::Personality => &["", "<none|friendly|pragmatic>"],
|
||||
SlashCommand::Realtime => &[""],
|
||||
SlashCommand::Settings => &["", "<microphone|speaker> [default|<device-name>]"],
|
||||
SlashCommand::TestApproval => &[""],
|
||||
SlashCommand::MemoryDrop | SlashCommand::MemoryUpdate => &[""],
|
||||
}
|
||||
self.spec().help_forms
|
||||
}
|
||||
|
||||
/// Whether bare dispatch opens interactive UI that should be resolved before queueing.
|
||||
pub fn requires_interaction(self) -> bool {
|
||||
match self {
|
||||
SlashCommand::Help => false,
|
||||
SlashCommand::Feedback
|
||||
| SlashCommand::Resume
|
||||
| SlashCommand::Review
|
||||
| SlashCommand::Rename
|
||||
| SlashCommand::Model
|
||||
| SlashCommand::Settings
|
||||
| SlashCommand::Personality
|
||||
| SlashCommand::Collab
|
||||
| SlashCommand::Agent
|
||||
| SlashCommand::MultiAgents
|
||||
| SlashCommand::Approvals
|
||||
| SlashCommand::Permissions
|
||||
| SlashCommand::Experimental
|
||||
| SlashCommand::Skills
|
||||
| SlashCommand::Statusline
|
||||
| SlashCommand::Theme => true,
|
||||
SlashCommand::Fast
|
||||
| SlashCommand::ElevateSandbox
|
||||
| SlashCommand::SandboxReadRoot
|
||||
| SlashCommand::New
|
||||
| SlashCommand::Fork
|
||||
| SlashCommand::Init
|
||||
| SlashCommand::Compact
|
||||
| SlashCommand::Plan
|
||||
| SlashCommand::Diff
|
||||
| SlashCommand::Copy
|
||||
| SlashCommand::Mention
|
||||
| SlashCommand::Status
|
||||
| SlashCommand::DebugConfig
|
||||
| SlashCommand::Mcp
|
||||
| SlashCommand::Apps
|
||||
| SlashCommand::Logout
|
||||
| SlashCommand::Quit
|
||||
| SlashCommand::Exit
|
||||
| SlashCommand::Rollout
|
||||
| SlashCommand::Ps
|
||||
| SlashCommand::Stop
|
||||
| SlashCommand::Clear
|
||||
| SlashCommand::Realtime
|
||||
| SlashCommand::TestApproval
|
||||
| SlashCommand::MemoryDrop
|
||||
| SlashCommand::MemoryUpdate => false,
|
||||
}
|
||||
self.spec().requires_interaction
|
||||
}
|
||||
|
||||
/// How this command should behave when dispatched while another turn is running.
|
||||
pub fn execution_kind(self) -> SlashCommandExecutionKind {
|
||||
match self {
|
||||
SlashCommand::Plan | SlashCommand::Init => {
|
||||
SlashCommandExecutionKind::JustLikeUserMessage
|
||||
}
|
||||
SlashCommand::Model
|
||||
| SlashCommand::Fast
|
||||
| SlashCommand::Approvals
|
||||
| SlashCommand::Permissions
|
||||
| SlashCommand::ElevateSandbox
|
||||
| SlashCommand::SandboxReadRoot
|
||||
| SlashCommand::Experimental
|
||||
| SlashCommand::Review
|
||||
| SlashCommand::New
|
||||
| SlashCommand::Resume
|
||||
| SlashCommand::Fork
|
||||
| SlashCommand::Compact
|
||||
| SlashCommand::Clear
|
||||
| SlashCommand::Logout
|
||||
| SlashCommand::Personality
|
||||
| SlashCommand::Statusline
|
||||
| SlashCommand::Theme
|
||||
| SlashCommand::MemoryDrop
|
||||
| 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::Mcp
|
||||
| SlashCommand::Apps
|
||||
| SlashCommand::Quit
|
||||
| SlashCommand::Exit
|
||||
| SlashCommand::Feedback
|
||||
| SlashCommand::Rollout
|
||||
| SlashCommand::Ps
|
||||
| SlashCommand::Stop
|
||||
| SlashCommand::Realtime
|
||||
| SlashCommand::Settings
|
||||
| SlashCommand::TestApproval => SlashCommandExecutionKind::Immediate,
|
||||
}
|
||||
self.spec().execution_kind
|
||||
}
|
||||
|
||||
pub fn show_in_command_popup(self) -> bool {
|
||||
self.spec().show_in_command_popup
|
||||
}
|
||||
|
||||
fn is_visible(self) -> bool {
|
||||
@@ -361,19 +504,20 @@ pub enum SlashCommandExecutionKind {
|
||||
Immediate,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
struct SlashCommandSpec {
|
||||
description: &'static str,
|
||||
help_forms: &'static [&'static str],
|
||||
requires_interaction: bool,
|
||||
execution_kind: SlashCommandExecutionKind,
|
||||
show_in_command_popup: bool,
|
||||
}
|
||||
|
||||
/// 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()
|
||||
.filter(|command| command.is_visible())
|
||||
.flat_map(|command| {
|
||||
std::iter::once((command.command(), command)).chain(
|
||||
command
|
||||
.command_aliases()
|
||||
.iter()
|
||||
.copied()
|
||||
.map(move |alias| (alias, command)),
|
||||
)
|
||||
})
|
||||
.flat_map(|command| command.all_command_names().map(move |name| (name, command)))
|
||||
.collect()
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user