Hide attached threads from the agents overview (#61218)

## Why

Saved side chats with `attached_to_thread_id` set could appear as root sessions in the agents overview.

## What changed

Exclude attached threads alongside ephemeral threads when building the overview. Reuse the same root eligibility check for history discovery, pinned threads, and refill accounting so attached threads do not count as overview roots.

## Testing

Extend `shared_overview_shows_only_root_sessions` with a saved side chat attached to a root thread, verifying that it stays out of the overview snapshot.

GitOrigin-RevId: 3417d444cfde7fc034c07ec124ba72cbf64b0ef6
This commit is contained in:
Felipe Coury
2026-11-13 00:55:46 +00:00
committed by copyberry
parent 5183e1dcb2
commit 1111483b48
4 changed files with 22 additions and 18 deletions
+4 -6
View File
@@ -15,6 +15,8 @@ mod loading;
#[path = "agents_overview_selection.rs"]
mod selection;
use super::agents_overview_discovery::is_overview_root;
use super::agents_overview_discovery::is_visible_in_agents_overview;
use super::agents_overview_view::AgentsOverviewGroup;
use super::agents_overview_view::AgentsOverviewRow;
use super::agents_overview_view::AgentsOverviewView;
@@ -257,11 +259,7 @@ impl App {
if displayed_refresh
&& !self.agents_overview.threads.contains_key(&thread_id)
&& !self.agents_overview.hidden_threads.contains(&thread_id)
&& thread.parent_thread_id.is_none()
&& !matches!(
thread.source,
SessionSource::SubAgent(SubAgentSource::ThreadSpawn { .. })
)
&& is_overview_root(&thread)
{
self.agents_overview.refill_count =
self.agents_overview.refill_count.saturating_sub(1);
@@ -423,7 +421,7 @@ impl App {
threads.push(target.clone());
}
}
threads.retain(|thread| !thread.ephemeral);
threads.retain(is_visible_in_agents_overview);
for thread in &mut threads {
if thread.parent_thread_id.is_none()
&& let SessionSource::SubAgent(SubAgentSource::ThreadSpawn {
@@ -74,7 +74,7 @@ pub(crate) async fn list_pinned_threads(
Ok(page) => {
let next_cursor = page.next_cursor.clone();
threads.extend(listed_threads(page).filter(|(thread, _)| {
is_overview_thread(thread) && supports_shared_pinning(&thread.source)
is_overview_root(thread) && supports_shared_pinning(&thread.source)
}));
let Some(next_cursor) = next_cursor else {
break;
@@ -93,8 +93,12 @@ pub(crate) async fn list_pinned_threads(
Ok(Some(threads))
}
fn is_overview_thread(thread: &Thread) -> bool {
!thread.ephemeral
pub(super) fn is_visible_in_agents_overview(thread: &Thread) -> bool {
!thread.ephemeral && thread.attached_to_thread_id.is_none()
}
pub(super) fn is_overview_root(thread: &Thread) -> bool {
is_visible_in_agents_overview(thread)
&& thread.parent_thread_id.is_none()
&& !matches!(
thread.source,
@@ -236,7 +240,7 @@ impl SourcePage {
self.exhausted = page.next_cursor.is_none();
self.cursor = page.next_cursor.clone();
self.rows.extend(
listed_threads(page).filter(|(thread, _)| is_overview_thread(thread)),
listed_threads(page).filter(|(thread, _)| is_overview_root(thread)),
);
return true;
}
@@ -1631,6 +1631,14 @@ async fn shared_overview_shows_only_root_sessions() {
);
side.ephemeral = true;
threads.push(side);
let mut attached = overview_thread(
ThreadId::new(),
/*parent_thread_id*/ None,
"Saved side chat",
ThreadStatus::Idle,
);
attached.attached_to_thread_id = Some(first_root.to_string());
threads.push(attached);
let default_model = app
.model_catalog
.models
@@ -5,6 +5,7 @@
use super::App;
use super::agents_overview::AGENTS_OVERVIEW_VIEW_ID;
use super::agents_overview_details::preview_agent_message;
use super::agents_overview_discovery::is_overview_root;
use super::agents_overview_discovery::list_pinned_threads;
use super::app_server_event_targets::ServerNotificationThreadTarget;
use super::app_server_event_targets::server_notification_thread_target;
@@ -15,7 +16,6 @@ use crate::chatwidget::ChatWidget;
use codex_app_server_protocol::ClientRequest;
use codex_app_server_protocol::RequestId;
use codex_app_server_protocol::ServerNotification;
use codex_app_server_protocol::SessionSource;
use codex_app_server_protocol::Thread;
use codex_app_server_protocol::ThreadItemPayload;
use codex_app_server_protocol::ThreadLoadedListParams;
@@ -26,7 +26,6 @@ use codex_app_server_protocol::ThreadStatus;
use codex_app_server_protocol::ThreadTurnsListParams;
use codex_app_server_protocol::ThreadTurnsListResponse;
use codex_protocol::ThreadId;
use codex_protocol::protocol::SubAgentSource;
use futures::StreamExt;
use std::collections::HashMap;
use uuid::Uuid;
@@ -61,12 +60,7 @@ impl App {
self.agents_overview.removed_threads.insert(thread_id);
self.agents_overview.unread.remove(&thread_id);
if let Some(Some(thread)) = self.agents_overview.threads.remove(&thread_id)
&& !thread.ephemeral
&& thread.parent_thread_id.is_none()
&& !matches!(
thread.source,
SessionSource::SubAgent(SubAgentSource::ThreadSpawn { .. })
)
&& is_overview_root(&thread)
&& !self.agents_overview.hidden_threads.contains(&thread_id)
{
self.agents_overview.refill_count += 1;