mirror of
https://github.com/openai/codex.git
synced 2026-11-08 20:40:33 +00:00
Keep the agents overview open when there is no chat to return to (#60271)
## What changed Only allow the cancel action to dismiss the agents overview when a chat is displayed or a startup thread is pending. Hide the `esc back` footer hint when there is no chat to return to, while retaining back hints for help and metadata editing. ## Testing Add coverage for returning to a chat that is still starting and for pressing Escape in the overview after cancelling folder trust. Update snapshots and test setup to reflect whether a chat is available. GitOrigin-RevId: af259330eb2a743fde449bbbd47c7f0eb6fa937e
This commit is contained in:
@@ -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()]);
|
||||
|
||||
+1
-1
@@ -41,7 +41,7 @@ expression: transition
|
||||
│
|
||||
│
|
||||
│
|
||||
? help esc back ↑/↓ move enter open n new
|
||||
? help ↑/↓ move enter open n new
|
||||
|
||||
--- Start a task ---
|
||||
|
||||
|
||||
@@ -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()));
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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<App> {
|
||||
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![
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -204,6 +204,7 @@ pub(super) struct AgentsOverviewView {
|
||||
directory_group_keys: Vec<PathBuf>,
|
||||
pub(super) pinned_thread_ranks: Option<HashMap<ThreadId, usize>>,
|
||||
pub(super) pin_action_pending: bool,
|
||||
pub(super) can_return_to_chat: bool,
|
||||
selected: usize,
|
||||
state: Arc<Mutex<AgentsOverviewViewState>>,
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
+1
-2
@@ -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
|
||||
|
||||
+1
-1
@@ -25,4 +25,4 @@ expression: rendered
|
||||
|
||||
|
||||
|
||||
? help esc back ↑/↓ move enter open n n…
|
||||
? help ↑/↓ move enter open n new
|
||||
|
||||
@@ -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"),
|
||||
|
||||
Reference in New Issue
Block a user