diff --git a/codex-rs/tui/src/app/session_lifecycle.rs b/codex-rs/tui/src/app/session_lifecycle.rs index 40d7a2384b9b..6f3323f88212 100644 --- a/codex-rs/tui/src/app/session_lifecycle.rs +++ b/codex-rs/tui/src/app/session_lifecycle.rs @@ -26,7 +26,7 @@ impl App { .into_iter() .map(|(thread_id, _)| thread_id) .collect(); - for thread_id in path_backed_thread_ids { + for thread_id in path_backed_thread_ids.iter().copied() { if let Some(channel) = self.thread_event_channels.get(&thread_id) && channel.attachment() == ThreadEventAttachment::Live { @@ -80,7 +80,6 @@ impl App { .add_to_history(super::agent_status_feed::AgentStatusHistoryCell::new( entries, )); - return; } let mut thread_ids = self.agent_navigation.tracked_thread_ids(); @@ -90,7 +89,9 @@ impl App { } } for thread_id in thread_ids { - if self.side_threads.contains_key(&thread_id) { + if path_backed_thread_ids.contains(&thread_id) + || self.side_threads.contains_key(&thread_id) + { continue; } if !self @@ -127,11 +128,19 @@ impl App { } let id = thread_id; let is_primary = self.primary_thread_id == Some(thread_id); - let name = format_agent_picker_item_name( - entry.agent_nickname.as_deref(), - entry.agent_role.as_deref(), - is_primary, - ); + let name = entry + .agent_path + .as_deref() + .map(str::trim) + .filter(|agent_path| !is_primary && !agent_path.is_empty()) + .map(ToOwned::to_owned) + .unwrap_or_else(|| { + format_agent_picker_item_name( + entry.agent_nickname.as_deref(), + entry.agent_role.as_deref(), + is_primary, + ) + }); let uuid = thread_id.to_string(); SelectionItem { name: name.clone(), diff --git a/codex-rs/tui/src/app/tests.rs b/codex-rs/tui/src/app/tests.rs index 6a26b6b537d6..58acb6bfe869 100644 --- a/codex-rs/tui/src/app/tests.rs +++ b/codex-rs/tui/src/app/tests.rs @@ -1544,6 +1544,41 @@ async fn open_agent_picker_clears_running_hint_from_completed_snapshot() -> Resu Ok(()) } +#[tokio::test] +async fn open_agent_picker_selects_path_backed_agent() -> Result<()> { + let (mut app, mut app_event_rx, _op_rx) = Box::pin(make_test_app_with_channels()).await; + let mut app_server = Box::pin(crate::start_embedded_app_server_for_picker( + app.chat_widget.config_ref(), + )) + .await + .expect("embedded app server"); + let thread_id = + ThreadId::from_string("00000000-0000-0000-0000-000000000123").expect("valid thread id"); + app.thread_event_channels + .insert(thread_id, ThreadEventChannel::new(/*capacity*/ 1)); + app.agent_navigation + .record_sub_agent_activity(SubAgentActivityDisplay { + thread_id, + agent_path: "/root/worker".to_string(), + is_running_hint: true, + }); + + Box::pin(app.open_agent_picker(&mut app_server)).await; + + assert_app_snapshot!( + "path_backed_agent_picker", + render_bottom_popup(&app.chat_widget, /*width*/ 80) + ); + while app_event_rx.try_recv().is_ok() {} + app.chat_widget + .handle_key_event(KeyEvent::new(KeyCode::Enter, KeyModifiers::NONE)); + assert_matches!( + app_event_rx.try_recv(), + Ok(AppEvent::SelectAgentThread(selected_thread_id)) if selected_thread_id == thread_id + ); + Ok(()) +} + #[tokio::test] async fn open_agent_picker_refreshes_replay_only_path_backed_liveness() -> Result<()> { let mut app = Box::pin(make_test_app()).await; diff --git a/codex-rs/tui/src/snapshots/codex_tui__app__tests__path_backed_agent_picker.snap b/codex-rs/tui/src/snapshots/codex_tui__app__tests__path_backed_agent_picker.snap new file mode 100644 index 000000000000..c6fd5fdde574 --- /dev/null +++ b/codex-rs/tui/src/snapshots/codex_tui__app__tests__path_backed_agent_picker.snap @@ -0,0 +1,10 @@ +--- +source: tui/src/app/tests.rs +expression: "render_bottom_popup(&app.chat_widget, 80)" +--- + Subagents + Select an agent to watch. ⌥ + ← previous, ⌥ + → next. + +› 1. • /root/worker 00000000-0000-0000-0000-000000000123 + + Press enter to confirm or esc to go back