diff --git a/codex-rs/tui/src/bottom_pane/slash_commands.rs b/codex-rs/tui/src/bottom_pane/slash_commands.rs index e907b6c5e554..14684594cac9 100644 --- a/codex-rs/tui/src/bottom_pane/slash_commands.rs +++ b/codex-rs/tui/src/bottom_pane/slash_commands.rs @@ -106,12 +106,16 @@ pub(crate) fn commands_for_input( .collect() } -/// Find a single built-in command by exact name, after applying feature gating. +/// Find a single built-in command by a recognized name or alias, after applying feature gating. /// -/// Side-conversation gating is intentionally enforced by dispatch rather than exact lookup so a +/// Side-conversation gating is intentionally enforced by dispatch rather than command lookup so a /// typed command can produce a side-specific unavailable message while the popup still hides it. pub(crate) fn find_builtin_command(name: &str, flags: BuiltinCommandFlags) -> Option { - let cmd = SlashCommand::from_str(name).ok()?; + let cmd = SlashCommand::from_str(name).ok().or_else(|| { + let repeated_os = name.strip_prefix('g')?.strip_suffix("al")?; + (!repeated_os.is_empty() && repeated_os.bytes().all(|byte| byte == b'o')) + .then_some(SlashCommand::Goal) + })?; builtins_for_input(BuiltinCommandFlags { side_conversation_active: false, ..flags @@ -187,6 +191,14 @@ mod tests { ); } + #[test] + fn goal_command_allows_extra_os_for_dispatch() { + assert_eq!( + find_builtin_command("goooooooooooal", all_enabled_flags()), + Some(SlashCommand::Goal) + ); + } + #[test] fn stop_command_resolves_for_dispatch() { assert_eq!( diff --git a/codex-rs/tui/src/chatwidget/tests/slash_commands.rs b/codex-rs/tui/src/chatwidget/tests/slash_commands.rs index e61ffd48fe75..dd978c10263a 100644 --- a/codex-rs/tui/src/chatwidget/tests/slash_commands.rs +++ b/codex-rs/tui/src/chatwidget/tests/slash_commands.rs @@ -599,12 +599,12 @@ async fn inline_slash_command_is_available_from_local_recall_after_dispatch() { } #[tokio::test] -async fn goal_slash_command_emits_set_goal_event() { +async fn goal_slash_command_with_extra_os_emits_set_goal_event() { let (mut chat, mut rx, mut op_rx) = make_chatwidget_manual(/*model_override*/ None).await; chat.set_feature_enabled(Feature::Goals, /*enabled*/ true); let thread_id = ThreadId::new(); chat.thread_id = Some(thread_id); - let command = "/goal --tokens 98.5K improve benchmark coverage"; + let command = "/goooooooooooal --tokens 98.5K improve benchmark coverage"; submit_composer_text(&mut chat, command);