diff --git a/codex-rs/cli/tests/daemon_startup.rs b/codex-rs/cli/tests/daemon_startup.rs index 42e9742a6c..aadb824861 100644 --- a/codex-rs/cli/tests/daemon_startup.rs +++ b/codex-rs/cli/tests/daemon_startup.rs @@ -32,12 +32,6 @@ async fn agents_defers_launch_directory_trust_until_starting_a_task() -> Result< daemon_startup("agents-untrusted").await } -#[tokio::test] -#[cfg(unix)] -async fn agents_checks_trust_when_starting_after_dismissing_overview() -> Result<()> { - daemon_startup("agents-untrusted-new").await -} - #[tokio::test] #[cfg(unix)] async fn discovered_managed_daemon_can_restart_with_auto_start_disabled() -> Result<()> { @@ -202,7 +196,7 @@ async fn daemon_startup(command: &str) -> Result<()> { "" }, serde_json::to_string(&workspace_path)?, - if command.starts_with("agents-untrusted") { + if command == "agents-untrusted" { "untrusted" } else { "trusted" @@ -246,7 +240,6 @@ async fn daemon_startup(command: &str) -> Result<()> { | "agents" | "agents-no-daemon" | "agents-untrusted" - | "agents-untrusted-new" | "discovered" | "bedrock" | "bedrock-running" @@ -316,18 +309,13 @@ async fn daemon_startup(command: &str) -> Result<()> { "Notconnectedtothelocalbackgroundserver" } else if command == "agents" { args.push("agents".into()); - steps.push_back(("Notasksyet", b"\x1b")); + steps.push_back(("Notasksyet", b"n")); steps.push_back(("GPT-5.6-Terra", b"/daemon\r")); steps.push_back(("Restartbackgroundserver", b"\x1b[F\r")); "Restartbackgroundserver?" - } else if command.starts_with("agents-untrusted") { + } else if command == "agents-untrusted" { args.push("agents".into()); - if command == "agents-untrusted-new" { - steps.push_back(("Notasksyet", b"\x1b")); - steps.push_back(("GPT-5.6-Terra", b"/new\r")); - } else { - steps.push_back(("Notasksyet", b"n")); - } + steps.push_back(("Notasksyet", b"n")); "entercontinue·escback" } else if command == "discovered" { args.extend(["--disable".into(), "daemon_auto_start".into()]); diff --git a/codex-rs/cli/tests/snapshots/daemon_startup__agents_overview_before_folder_consent.snap b/codex-rs/cli/tests/snapshots/daemon_startup__agents_overview_before_folder_consent.snap index 5273276df5..82c8730ab2 100644 --- a/codex-rs/cli/tests/snapshots/daemon_startup__agents_overview_before_folder_consent.snap +++ b/codex-rs/cli/tests/snapshots/daemon_startup__agents_overview_before_folder_consent.snap @@ -41,7 +41,7 @@ expression: transition │ │ │ - ? help esc back ↑/↓ move enter open n new + ? help ↑/↓ move enter open n new --- Start a task --- diff --git a/codex-rs/tui/src/app/agent_center/hints.rs b/codex-rs/tui/src/app/agent_center/hints.rs index 95e06f334c..e0d5a51e12 100644 --- a/codex-rs/tui/src/app/agent_center/hints.rs +++ b/codex-rs/tui/src/app/agent_center/hints.rs @@ -146,8 +146,10 @@ impl AgentsOverviewView { } let cancel = if state.help { self.keymap.primary_hint(ListAction::Cancel) - } else { + } else if state.editing_metadata() || self.can_return_to_chat { self.center_list_hint(ListAction::Cancel) + } else { + None }; if let Some(hint) = cancel { hints.push((hint.display_label(), "back".into())); diff --git a/codex-rs/tui/src/app/agent_center_tests.rs b/codex-rs/tui/src/app/agent_center_tests.rs index e3d96442e9..b0a34b1460 100644 --- a/codex-rs/tui/src/app/agent_center_tests.rs +++ b/codex-rs/tui/src/app/agent_center_tests.rs @@ -25,7 +25,7 @@ fn screen(view: &AgentsOverviewView, width: u16, height: u16) -> String { #[tokio::test] async fn live_center_keeps_status_order_across_progress_refreshes() { - let mut app = make_test_app().await; + let mut app = make_test_app_with_chat().await; app.agents_overview.view_state.lock().unwrap().grouping = AgentsOverviewGrouping::Status; let ids = [1, 2, 3, 4, 5].map(ThreadId::from_u128); let mut threads = [ @@ -562,9 +562,18 @@ async fn live_center_rename_retains_target_when_status_leaves_filter() -> Result Ok(()) } +#[tokio::test] +async fn escape_returns_to_a_chat_that_is_still_starting() { + let mut app = make_test_app().await; + app.pending_startup_thread_start = true; + let mut view = app.agents_overview_view(Vec::new(), /*selected_thread_id*/ None); + view.handle_key_event(KeyCode::Esc.into()); + assert!(view.is_complete()); +} + #[tokio::test] async fn live_center_navigation_and_complete_hints() { - let app = make_test_app().await; + let app = make_test_app_with_chat().await; let ready = ThreadId::from_u128(/*value*/ 42); let needs_you = ThreadId::from_u128(/*value*/ 43); let unloaded = ThreadId::from_u128(/*value*/ 44); @@ -714,7 +723,7 @@ async fn live_center_fixed_shortcuts_yield_to_configured_actions() { #[tokio::test] async fn live_center_hints_follow_configured_bindings() { - let mut app = make_test_app().await; + let mut app = make_test_app_with_chat().await; let config: TuiKeymap = toml::from_str( r#" [list] @@ -858,7 +867,7 @@ async fn overview_clears_voice_badge_after_async_close() -> Result<()> { #[tokio::test] async fn live_center_show_more_navigation_search_and_loading() { - let mut app = make_test_app().await; + let mut app = make_test_app_with_chat().await; let (tx, mut rx) = tokio::sync::mpsc::unbounded_channel(); app.app_event_tx = AppEventSender::new(tx); app.agents_overview.view_state.lock().unwrap().has_more = true; diff --git a/codex-rs/tui/src/app/agents_overview.rs b/codex-rs/tui/src/app/agents_overview.rs index f2518c7d3e..6e36418a73 100644 --- a/codex-rs/tui/src/app/agents_overview.rs +++ b/codex-rs/tui/src/app/agents_overview.rs @@ -383,6 +383,8 @@ impl App { .map(|(rank, id)| (id, rank)) .collect() }); + view.can_return_to_chat = + self.pending_startup_thread_start || self.current_displayed_thread_id().is_some(); view.pin_action_pending = self.agents_overview.pending_pin_change.is_some(); view } diff --git a/codex-rs/tui/src/app/agents_overview_tests.rs b/codex-rs/tui/src/app/agents_overview_tests.rs index 3c8d1c3d39..429a9abfbf 100644 --- a/codex-rs/tui/src/app/agents_overview_tests.rs +++ b/codex-rs/tui/src/app/agents_overview_tests.rs @@ -8,10 +8,17 @@ mod command_center; use super::super::agents_overview_view::AgentsOverviewGroup; use super::super::agents_overview_view::AgentsOverviewGrouping; use super::*; +use crate::app::owned_transcript::tests::attach_thread; use crate::app::tests::make_test_app_with_channels; use crate::chatwidget::tests::helpers::normalize_agent_center_snapshot; use codex_config::types::AgentsOverviewGrouping as PersistedAgentsOverviewGrouping; +async fn make_test_app_with_chat() -> Box { + let mut app = make_test_app().await; + attach_thread(&mut app, ThreadId::new()); + app +} + #[tokio::test] async fn overview_worktree_creation_busy_state() { let mut app = make_test_app().await; @@ -787,7 +794,7 @@ async fn check_hidden_thread_does_not_refresh_shared_overview(source: &str) { #[tokio::test] async fn agents_overview_details_show_available_attention_without_expanding_rows() -> Result<()> { - let mut app = make_test_app().await; + let mut app = make_test_app_with_chat().await; let app_server = crate::start_embedded_app_server_for_picker(&app.config).await?; let [root, failed_agent, child, unloaded] = std::array::from_fn(|_| ThreadId::new()); let waiting = ThreadStatus::Active { @@ -911,7 +918,7 @@ fn reasoning_delta(thread_id: ThreadId, item_id: &str, delta: &str) -> ServerNot #[tokio::test] async fn agents_overview_details_render_markdown() { - let mut app = make_test_app().await; + let mut app = make_test_app_with_chat().await; let thread_id = ThreadId::new(); let mut thread = overview_thread( thread_id, @@ -1053,7 +1060,7 @@ fn agents_overview_markdown_preview_preserves_layout_and_bounds() { #[tokio::test] async fn agents_overview_reasoning_uses_existing_events_and_expires_with_attachment() { - let mut app = make_test_app().await; + let mut app = make_test_app_with_chat().await; let thread_id = ThreadId::new(); let mut thread = overview_thread( thread_id, @@ -1153,7 +1160,7 @@ async fn agents_overview_reasoning_uses_existing_events_and_expires_with_attachm #[tokio::test] async fn worktrees_overview_groups_by_project_or_directory() { - let mut app = make_test_app().await; + let mut app = make_test_app_with_chat().await; let root = tempfile::tempdir().unwrap(); let primary = root.path().join("primary"); let linked = root.path().join("linked"); @@ -1304,7 +1311,7 @@ fn overview_directory_group_keys_normalize_aliases() { #[tokio::test] async fn overview_model_grouping_shows_details_and_preserves_selection() { - let mut app = make_test_app().await; + let mut app = make_test_app_with_chat().await; let threads = [ ("Older task", Some("model-a"), 1), ("Other model", Some("model-b"), 2), @@ -1365,6 +1372,7 @@ async fn shared_overview_shows_only_root_sessions() { let first_root = ThreadId::from_string("00000000-0000-0000-0000-000000000101").unwrap(); let unloaded_root = ThreadId::from_string("00000000-0000-0000-0000-000000000102").unwrap(); let [child, second_root, side_thread] = std::array::from_fn(|_| ThreadId::new()); + attach_thread(&mut app, first_root); app.primary_thread_id = Some(first_root); let mut threads = vec![ diff --git a/codex-rs/tui/src/app/agents_overview_usage_tests.rs b/codex-rs/tui/src/app/agents_overview_usage_tests.rs index c03658e79e..5291b8999f 100644 --- a/codex-rs/tui/src/app/agents_overview_usage_tests.rs +++ b/codex-rs/tui/src/app/agents_overview_usage_tests.rs @@ -12,7 +12,7 @@ use pretty_assertions::assert_eq; #[tokio::test] async fn selected_usage_is_cached_and_account_changes_discard_old_results() -> Result<()> { - let mut app = make_test_app().await; + let mut app = make_test_app_with_chat().await; let app_server = crate::start_embedded_app_server_for_picker(&app.config).await?; app.chat_widget.update_account_state( /*status_account_display*/ None, diff --git a/codex-rs/tui/src/app/agents_overview_view.rs b/codex-rs/tui/src/app/agents_overview_view.rs index f28570a2ac..4f562cd194 100644 --- a/codex-rs/tui/src/app/agents_overview_view.rs +++ b/codex-rs/tui/src/app/agents_overview_view.rs @@ -204,6 +204,7 @@ pub(super) struct AgentsOverviewView { directory_group_keys: Vec, pub(super) pinned_thread_ranks: Option>, pub(super) pin_action_pending: bool, + pub(super) can_return_to_chat: bool, selected: usize, state: Arc>, app_event_tx: AppEventSender, @@ -281,6 +282,7 @@ impl AgentsOverviewView { directory_group_keys, pinned_thread_ranks: None, pin_action_pending: false, + can_return_to_chat: false, selected, state, app_event_tx, @@ -796,7 +798,9 @@ impl BottomPaneView for AgentsOverviewView { } ListAction::Accept => self.activate(), ListAction::Cancel => { - if matches!(self.on_ctrl_c(), CancellationEvent::NotHandled) { + if self.can_return_to_chat + && matches!(self.on_ctrl_c(), CancellationEvent::NotHandled) + { self.state().completion = Some(ViewCompletion::Cancelled); } } diff --git a/codex-rs/tui/src/app/session_lifecycle.rs b/codex-rs/tui/src/app/session_lifecycle.rs index 1ab678f622..03159bb407 100644 --- a/codex-rs/tui/src/app/session_lifecycle.rs +++ b/codex-rs/tui/src/app/session_lifecycle.rs @@ -1165,7 +1165,6 @@ impl App { } return; } - // Dismissing the initial agents overview can expose /new before any folder consent. // Resolve trust before loading defaults and applying explicit permission choices. if self.current_displayed_thread_id().is_none() { let mut config = self.chat_widget.config_ref().clone(); diff --git a/codex-rs/tui/src/app/snapshots/codex_tui__app__agents_overview__tests__agents_overview_recent_sessions.snap b/codex-rs/tui/src/app/snapshots/codex_tui__app__agents_overview__tests__agents_overview_recent_sessions.snap index 06fb308d69..ac6328563a 100644 --- a/codex-rs/tui/src/app/snapshots/codex_tui__app__agents_overview__tests__agents_overview_recent_sessions.snap +++ b/codex-rs/tui/src/app/snapshots/codex_tui__app__agents_overview__tests__agents_overview_recent_sessions.snap @@ -1,6 +1,5 @@ --- source: tui/src/app/agents_overview_tests.rs -assertion_line: 633 expression: "age.replace_all(&rendered, \" [age]\")" --- Agent command center Group: Project g @@ -26,4 +25,4 @@ expression: "age.replace_all(&rendered, \" [age]\")" - ? help esc back ↑/↓ move enter open n new + ? help ↑/↓ move enter open n new diff --git a/codex-rs/tui/src/snapshots/codex_tui__app__agents_overview__tests__agents_overview_empty_narrow.snap b/codex-rs/tui/src/snapshots/codex_tui__app__agents_overview__tests__agents_overview_empty_narrow.snap index 54898e76c1..5830783f84 100644 --- a/codex-rs/tui/src/snapshots/codex_tui__app__agents_overview__tests__agents_overview_empty_narrow.snap +++ b/codex-rs/tui/src/snapshots/codex_tui__app__agents_overview__tests__agents_overview_empty_narrow.snap @@ -25,4 +25,4 @@ expression: rendered - ? help esc back ↑/↓ move enter open n n… + ? help ↑/↓ move enter open n new diff --git a/codex-rs/tui/tests/suite/directory_trust.rs b/codex-rs/tui/tests/suite/directory_trust.rs index e23910e015..96b79bb585 100644 --- a/codex-rs/tui/tests/suite/directory_trust.rs +++ b/codex-rs/tui/tests/suite/directory_trust.rs @@ -168,6 +168,7 @@ async fn connected_trust_cancellation_and_acceptance_control_task_creation() -> }; for (expected, input) in [ (prompt, b"\x1b".as_slice()), + ("Launch-folder task", b"\x1b"), ("Launch-folder task", b"\x1b[Bn"), (prompt, b"\x1b"), ("n new", b"n"),