Compare commits

...

4 Commits

Author SHA1 Message Date
Ahmed Ibrahim
68faa1a5b9 Merge branch 'codex/fix-/e-command-to-exit-instead-of-feedback' of https://github.com/openai/codex into codex/fix-/e-command-to-exit-instead-of-feedback 2025-11-03 15:56:42 -08:00
Ahmed Ibrahim
35ea2ed1c1 enable 2025-11-03 15:46:03 -08:00
Ahmed Ibrahim
b93887684d initial_history 2025-10-30 13:32:09 -07:00
Ahmed Ibrahim
52abd57726 tui: add /exit and /e aliases for /quit; alias-aware resolver and popup filtering 2025-10-30 13:24:59 -07:00
8 changed files with 72 additions and 14 deletions

2
codex-rs/Cargo.lock generated
View File

@@ -992,6 +992,7 @@ dependencies = [
"supports-color",
"tempfile",
"tokio",
"toml",
]
[[package]]
@@ -1459,6 +1460,7 @@ dependencies = [
"lazy_static",
"libc",
"mcp-types",
"once_cell",
"opentelemetry-appender-tracing",
"pathdiff",
"pretty_assertions",

View File

@@ -39,6 +39,7 @@ ctor = { workspace = true }
owo-colors = { workspace = true }
serde_json = { workspace = true }
supports-color = { workspace = true }
toml = { workspace = true }
tokio = { workspace = true, features = [
"io-std",
"macros",

View File

@@ -510,15 +510,21 @@ async fn cli_main(codex_linux_sandbox_exe: Option<PathBuf>) -> anyhow::Result<()
Some(Subcommand::Features(FeaturesCli { sub })) => match sub {
FeaturesSubcommand::List => {
// Respect root-level `-c` overrides plus top-level flags like `--profile`.
let cli_kv_overrides = root_config_overrides
let mut cli_kv_overrides = root_config_overrides
.parse_overrides()
.map_err(anyhow::Error::msg)?;
// Honor `--search` via the new feature toggle.
if interactive.web_search {
cli_kv_overrides.push((
"features.web_search_request".to_string(),
toml::Value::Boolean(true),
));
}
// Thread through relevant top-level flags (at minimum, `--profile`).
// Also honor `--search` since it maps to a feature toggle.
let overrides = ConfigOverrides {
config_profile: interactive.config_profile.clone(),
tools_web_search_request: interactive.web_search.then_some(true),
..Default::default()
};

View File

@@ -71,6 +71,7 @@ strum_macros = { workspace = true }
supports-color = { workspace = true }
tempfile = { workspace = true }
textwrap = { workspace = true }
once_cell = "1"
tree-sitter-highlight = { workspace = true }
tree-sitter-bash = { workspace = true }
tokio = { workspace = true, features = [

View File

@@ -36,7 +36,7 @@ use crate::bottom_pane::prompt_args::prompt_argument_names;
use crate::bottom_pane::prompt_args::prompt_command_with_arg_placeholders;
use crate::bottom_pane::prompt_args::prompt_has_numeric_placeholders;
use crate::slash_command::SlashCommand;
use crate::slash_command::built_in_slash_commands;
use crate::slash_command::resolve_slash_command;
use crate::style::user_message_style;
use codex_protocol::custom_prompts::CustomPrompt;
use codex_protocol::custom_prompts::PROMPTS_CMD_PREFIX;
@@ -932,9 +932,7 @@ impl ChatComposer {
let first_line = self.textarea.text().lines().next().unwrap_or("");
if let Some((name, rest)) = parse_slash_name(first_line)
&& rest.is_empty()
&& let Some((_n, cmd)) = built_in_slash_commands()
.into_iter()
.find(|(n, _)| *n == name)
&& let Some(cmd) = resolve_slash_command(name)
{
self.textarea.set_text("");
return (InputResult::Command(cmd), true);
@@ -1003,9 +1001,7 @@ impl ChatComposer {
if let Some((name, _rest)) = parse_slash_name(&text) {
let treat_as_plain_text = input_starts_with_space || name.contains('/');
if !treat_as_plain_text {
let is_builtin = built_in_slash_commands()
.into_iter()
.any(|(command_name, _)| command_name == name);
let is_builtin = resolve_slash_command(name).is_some();
let prompt_prefix = format!("{PROMPTS_CMD_PREFIX}:");
let is_known_prompt = name
.strip_prefix(&prompt_prefix)

View File

@@ -34,7 +34,12 @@ impl CommandPopup {
pub(crate) fn new(mut prompts: Vec<CustomPrompt>) -> Self {
let builtins = built_in_slash_commands();
// Exclude prompts that collide with builtin command names and sort by name.
let exclude: HashSet<String> = builtins.iter().map(|(n, _)| (*n).to_string()).collect();
let mut exclude: HashSet<String> = builtins.iter().map(|(n, _)| (*n).to_string()).collect();
for (_, cmd) in &builtins {
for alias in cmd.aliases() {
exclude.insert((*alias).to_string());
}
}
prompts.retain(|p| !exclude.contains(&p.name));
prompts.sort_by(|a, b| a.name.cmp(&b.name));
Self {
@@ -46,11 +51,16 @@ impl CommandPopup {
}
pub(crate) fn set_prompts(&mut self, mut prompts: Vec<CustomPrompt>) {
let exclude: HashSet<String> = self
let mut exclude: HashSet<String> = self
.builtins
.iter()
.map(|(n, _)| (*n).to_string())
.collect();
for (_, cmd) in &self.builtins {
for alias in cmd.aliases() {
exclude.insert((*alias).to_string());
}
}
prompts.retain(|p| !exclude.contains(&p.name));
prompts.sort_by(|a, b| a.name.cmp(&b.name));
self.prompts = prompts;
@@ -121,6 +131,18 @@ impl CommandPopup {
for (_, cmd) in self.builtins.iter() {
if let Some((indices, score)) = fuzzy_match(cmd.command(), filter) {
out.push((CommandItem::Builtin(*cmd), Some(indices), score));
continue;
}
let mut best_alias_score: Option<i32> = None;
for alias in cmd.aliases() {
if let Some((_indices, score)) = fuzzy_match(alias, filter)
&& best_alias_score.is_none_or(|best| score < best)
{
best_alias_score = Some(score);
}
}
if let Some(score) = best_alias_score {
out.push((CommandItem::Builtin(*cmd), None, score));
}
}
// Support both search styles:

View File

@@ -94,7 +94,7 @@ use std::io::Write as _;
// (tests access modules directly within the crate)
pub async fn run_main(
cli: Cli,
mut cli: Cli,
codex_linux_sandbox_exe: Option<PathBuf>,
) -> std::io::Result<AppExitInfo> {
let (sandbox_mode, approval_policy) = if cli.full_auto {
@@ -114,6 +114,13 @@ pub async fn run_main(
)
};
// Map the legacy --search flag to the new feature toggle.
if cli.web_search {
cli.config_overrides
.raw_overrides
.push("features.web_search_request=true".to_string());
}
// When using `--oss`, let the bootstrapper pick the model (defaulting to
// gpt-oss:20b) and ensure it is present locally. Also, force the builtin
// `oss` model provider.
@@ -149,7 +156,7 @@ pub async fn run_main(
compact_prompt: None,
include_apply_patch_tool: None,
show_raw_agent_reasoning: cli.oss.then_some(true),
tools_web_search_request: cli.web_search.then_some(true),
tools_web_search_request: None,
experimental_sandbox_command_assessment: None,
additional_writable_roots: additional_dirs,
};

View File

@@ -24,6 +24,7 @@ pub enum SlashCommand {
Status,
Mcp,
Logout,
#[strum(serialize = "exit")]
Quit,
Exit,
Feedback,
@@ -83,6 +84,22 @@ impl SlashCommand {
}
}
/// Additional slash names that map to this command.
pub fn aliases(self) -> &'static [&'static str] {
match self {
SlashCommand::Quit => &["exit", "e"],
_ => &[],
}
}
/// Return true if `name` matches this command's canonical name or an alias.
pub fn matches_name(self, name: &str) -> bool {
if self.command() == name {
return true;
}
self.aliases().contains(&name)
}
fn is_visible(self) -> bool {
match self {
SlashCommand::Rollout | SlashCommand::TestApproval => cfg!(debug_assertions),
@@ -98,3 +115,9 @@ pub fn built_in_slash_commands() -> Vec<(&'static str, SlashCommand)> {
.map(|c| (c.command(), c))
.collect()
}
/// Resolve a slash command name (including aliases) to the corresponding command.
pub fn resolve_slash_command(name: &str) -> Option<SlashCommand> {
use std::str::FromStr;
SlashCommand::from_str(name).ok()
}