diff --git a/codex-rs/tui/src/app/thread_goal_actions.rs b/codex-rs/tui/src/app/thread_goal_actions.rs index 591180175..b1a1bd367 100644 --- a/codex-rs/tui/src/app/thread_goal_actions.rs +++ b/codex-rs/tui/src/app/thread_goal_actions.rs @@ -6,6 +6,7 @@ use crate::bottom_pane::SelectionAction; use crate::bottom_pane::SelectionItem; use crate::bottom_pane::SelectionViewParams; use crate::bottom_pane::popup_consts::standard_popup_hint_line; +use crate::goal_display::GOAL_USAGE; use crate::goal_display::goal_status_label; use crate::goal_display::goal_usage_summary; use codex_app_server_protocol::ThreadGoal; @@ -39,7 +40,7 @@ impl App { let Some(goal) = response.goal else { self.chat_widget.add_info_message( - "Usage: /goal ".to_string(), + GOAL_USAGE.to_string(), Some("No goal is currently set.".to_string()), ); return; @@ -280,7 +281,7 @@ impl App { self.chat_widget .add_error_message("No goal is currently set.".to_string()); self.chat_widget.add_info_message( - "Usage: /goal ".to_string(), + GOAL_USAGE.to_string(), Some("Create a goal before editing it.".to_string()), ); } diff --git a/codex-rs/tui/src/chatwidget/slash_dispatch.rs b/codex-rs/tui/src/chatwidget/slash_dispatch.rs index 08c6db56d..48caf8f54 100644 --- a/codex-rs/tui/src/chatwidget/slash_dispatch.rs +++ b/codex-rs/tui/src/chatwidget/slash_dispatch.rs @@ -13,6 +13,7 @@ use crate::bottom_pane::slash_commands::BuiltinCommandFlags; use crate::bottom_pane::slash_commands::ServiceTierCommand; use crate::bottom_pane::slash_commands::SlashCommandItem; use crate::bottom_pane::slash_commands::find_slash_command; +use crate::goal_display::GOAL_USAGE; #[derive(Clone, Copy, Debug, PartialEq, Eq)] enum SlashCommandDispatchSource { @@ -32,7 +33,6 @@ struct PreparedSlashCommandArgs { const SIDE_STARTING_CONTEXT_LABEL: &str = "Side starting..."; const SIDE_SLASH_COMMAND_UNAVAILABLE_HINT: &str = "Press Ctrl+C to return to the main thread first."; -const GOAL_USAGE: &str = "Usage: /goal "; const GOAL_USAGE_HINT: &str = "Example: /goal improve benchmark coverage"; const RAW_USAGE: &str = "Usage: /raw [on|off]"; diff --git a/codex-rs/tui/src/chatwidget/tests/slash_commands.rs b/codex-rs/tui/src/chatwidget/tests/slash_commands.rs index 099ae1d76..9c2802fb6 100644 --- a/codex-rs/tui/src/chatwidget/tests/slash_commands.rs +++ b/codex-rs/tui/src/chatwidget/tests/slash_commands.rs @@ -784,6 +784,21 @@ async fn goal_control_slash_commands_emit_goal_events() { } } +#[tokio::test] +async fn goal_control_slash_command_without_thread_shows_full_usage() { + let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(/*model_override*/ None).await; + chat.set_feature_enabled(Feature::Goals, /*enabled*/ true); + + submit_composer_text(&mut chat, "/goal pause"); + + let cells = drain_insert_history(&mut rx); + assert_eq!(cells.len(), 1, "expected goal usage message"); + insta::assert_snapshot!( + lines_to_single_string(&cells[0]), + @"• Usage: /goal [|clear|edit|pause|resume] The session must start before you can change a goal." + ); +} + #[tokio::test] async fn goal_edit_slash_command_opens_goal_editor() { for thread_id in [Some(ThreadId::new()), None] { diff --git a/codex-rs/tui/src/goal_display.rs b/codex-rs/tui/src/goal_display.rs index 961b87f16..210bac107 100644 --- a/codex-rs/tui/src/goal_display.rs +++ b/codex-rs/tui/src/goal_display.rs @@ -2,6 +2,8 @@ use crate::status::format_tokens_compact; use codex_app_server_protocol::ThreadGoal; use codex_app_server_protocol::ThreadGoalStatus; +pub(crate) const GOAL_USAGE: &str = "Usage: /goal [|clear|edit|pause|resume]"; + pub(crate) fn format_goal_elapsed_seconds(seconds: i64) -> String { let seconds = seconds.max(0) as u64; if seconds < 60 {