From 77a16bee97c353e073ca83fa7d84dff012631a3e Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Fri, 10 Apr 2026 01:27:23 -0700 Subject: [PATCH] wip --- crates/acp_thread/src/acp_thread.rs | 49 +++++++++--- crates/acp_thread/src/connection.rs | 2 + crates/agent/src/agent.rs | 79 ++++++++++--------- crates/agent/src/db.rs | 4 +- crates/agent/src/tests/mod.rs | 7 +- crates/agent/src/thread.rs | 48 +++++------ crates/agent/src/tools/spawn_agent_tool.rs | 4 +- crates/agent_servers/src/acp.rs | 3 + crates/agent_ui/src/agent_panel.rs | 2 +- crates/agent_ui/src/conversation_view.rs | 6 +- .../src/conversation_view/thread_view.rs | 8 +- crates/agent_ui/src/entry_view_state.rs | 2 +- crates/agent_ui/src/thread_metadata_store.rs | 4 +- .../src/sidebar_tests.proptest-regressions | 10 +++ 14 files changed, 143 insertions(+), 85 deletions(-) create mode 100644 crates/sidebar/src/sidebar_tests.proptest-regressions diff --git a/crates/acp_thread/src/acp_thread.rs b/crates/acp_thread/src/acp_thread.rs index 7fb48c132f971fd3449d116b22bd4437c1ebf611..a049632c793778e67076dc85b02cfbe33912def7 100644 --- a/crates/acp_thread/src/acp_thread.rs +++ b/crates/acp_thread/src/acp_thread.rs @@ -36,6 +36,26 @@ use util::path_list::PathList; use util::{ResultExt, get_default_system_shell_preferring_bash, paths::PathStyle}; use uuid::Uuid; +/// A unique identifier for a conversation thread, allocated by Zed. +/// +/// This is the internal identity type used throughout the application for +/// bookkeeping. It is distinct from `acp::SessionId`, which is allocated +/// by the agent and used for the wire protocol. +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] +pub struct ThreadId(pub Uuid); + +impl ThreadId { + pub fn new() -> Self { + Self(Uuid::new_v4()) + } +} + +impl std::fmt::Display for ThreadId { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.0) + } +} + /// Key used in ACP ToolCall meta to store the tool's programmatic name. /// This is a workaround since ACP's ToolCall doesn't have a dedicated name field. pub const TOOL_NAME_META_KEY: &str = "tool_name"; @@ -1017,9 +1037,10 @@ struct RunningTurn { } pub struct AcpThread { + thread_id: ThreadId, session_id: acp::SessionId, work_dirs: Option, - parent_session_id: Option, + parent_thread_id: Option, title: Option, provisional_title: Option, entries: Vec, @@ -1184,12 +1205,13 @@ impl Error for LoadError {} impl AcpThread { pub fn new( - parent_session_id: Option, + parent_thread_id: Option, title: Option, work_dirs: Option, connection: Rc, project: Entity, action_log: Entity, + thread_id: ThreadId, session_id: acp::SessionId, mut prompt_capabilities_rx: watch::Receiver, cx: &mut Context, @@ -1206,7 +1228,8 @@ impl AcpThread { }); Self { - parent_session_id, + thread_id, + parent_thread_id, work_dirs, action_log, shared_buffers: Default::default(), @@ -1233,8 +1256,12 @@ impl AcpThread { } } - pub fn parent_session_id(&self) -> Option<&acp::SessionId> { - self.parent_session_id.as_ref() + pub fn thread_id(&self) -> ThreadId { + self.thread_id + } + + pub fn parent_thread_id(&self) -> Option { + self.parent_thread_id } pub fn prompt_capabilities(&self) -> acp::PromptCapabilities { @@ -1845,7 +1872,7 @@ impl AcpThread { let agent_telemetry_id = self.connection().telemetry_id(); let session = self.session_id(); - let parent_session_id = self.parent_session_id(); + let parent_thread_id = self.parent_thread_id(); if let ToolCallStatus::Completed | ToolCallStatus::Failed = status { let status = if matches!(status, ToolCallStatus::Completed) { "completed" @@ -1856,7 +1883,7 @@ impl AcpThread { "Agent Tool Call Completed", agent_telemetry_id, session, - parent_session_id, + parent_thread_id, status ); } @@ -1960,7 +1987,7 @@ impl AcpThread { pub fn resolve_locations(&mut self, id: acp::ToolCallId, cx: &mut Context) { let project = self.project.clone(); - let should_update_agent_location = self.parent_session_id.is_none(); + let should_update_agent_location = self.parent_thread_id.is_none(); let Some((_, tool_call)) = self.tool_call_mut(&id) else { return; }; @@ -2236,7 +2263,7 @@ impl AcpThread { .await?; this.update(cx, |this, cx| { - if this.parent_session_id.is_none() { + if this.parent_thread_id.is_none() { this.project .update(cx, |project, cx| project.set_agent_location(None, cx)); } @@ -2538,7 +2565,7 @@ impl AcpThread { let limit = limit.unwrap_or(u32::MAX); let project = self.project.clone(); let action_log = self.action_log.clone(); - let should_update_agent_location = self.parent_session_id.is_none(); + let should_update_agent_location = self.parent_thread_id.is_none(); cx.spawn(async move |this, cx| { let load = project.update(cx, |project, cx| { let path = project @@ -2613,7 +2640,7 @@ impl AcpThread { ) -> Task> { let project = self.project.clone(); let action_log = self.action_log.clone(); - let should_update_agent_location = self.parent_session_id.is_none(); + let should_update_agent_location = self.parent_thread_id.is_none(); cx.spawn(async move |this, cx| { let load = project.update(cx, |project, cx| { let path = project diff --git a/crates/acp_thread/src/connection.rs b/crates/acp_thread/src/connection.rs index 32bb8abde9aa5f67563780a7fe4993028f0df346..a62236bb9fb030e0233c0f50e642fc37e8c7cf0c 100644 --- a/crates/acp_thread/src/connection.rs +++ b/crates/acp_thread/src/connection.rs @@ -734,6 +734,7 @@ mod test_support { cx: &mut gpui::App, ) -> Entity { let action_log = cx.new(|_| ActionLog::new(project.clone())); + let thread_id = ThreadId::new(); let thread = cx.new(|cx| { AcpThread::new( None, @@ -742,6 +743,7 @@ mod test_support { self.clone(), project, action_log, + thread_id, session_id.clone(), watch::Receiver::constant( acp::PromptCapabilities::new() diff --git a/crates/agent/src/agent.rs b/crates/agent/src/agent.rs index fe62d851ef1ba71c787aa6ec516b0b6b67449d67..8214aacf3c95a08357ccd58ac9bf89e5d2d789b0 100644 --- a/crates/agent/src/agent.rs +++ b/crates/agent/src/agent.rs @@ -26,7 +26,7 @@ pub use tools::*; use acp_thread::{ AcpThread, AgentModelSelector, AgentSessionInfo, AgentSessionList, AgentSessionListRequest, - AgentSessionListResponse, TokenUsageRatio, UserMessageId, + AgentSessionListResponse, ThreadId, TokenUsageRatio, UserMessageId, }; use agent_client_protocol as acp; use anyhow::{Context as _, Result, anyhow}; @@ -328,8 +328,9 @@ impl NativeAgent { let connection = Rc::new(NativeAgentConnection(cx.entity())); let thread = thread_handle.read(cx); - let session_id = thread.id().clone(); - let parent_session_id = thread.parent_thread_id(); + let thread_id = thread.id(); + let session_id = thread.session_id().clone(); + let parent_thread_id = thread.parent_thread_id(); let title = thread.title(); let draft_prompt = thread.draft_prompt().map(Vec::from); let scroll_position = thread.ui_scroll_position(); @@ -339,12 +340,13 @@ impl NativeAgent { let prompt_capabilities_rx = thread.prompt_capabilities_rx.clone(); let acp_thread = cx.new(|cx| { let mut acp_thread = acp_thread::AcpThread::new( - parent_session_id, + parent_thread_id, title, None, connection, project.clone(), action_log.clone(), + thread_id, session_id.clone(), prompt_capabilities_rx, cx, @@ -658,7 +660,7 @@ impl NativeAgent { _: &TitleUpdated, cx: &mut Context, ) { - let session_id = thread.read(cx).id(); + let session_id = thread.read(cx).session_id(); let Some(session) = self.sessions.get(session_id) else { return; }; @@ -683,7 +685,7 @@ impl NativeAgent { usage: &TokenUsageUpdated, cx: &mut Context, ) { - let Some(session) = self.sessions.get(thread.read(cx).id()) else { + let Some(session) = self.sessions.get(thread.read(cx).session_id()) else { return; }; session.acp_thread.update(cx, |acp_thread, cx| { @@ -905,6 +907,7 @@ impl NativeAgent { Ok(cx.new(|cx| { let mut thread = Thread::from_db( + ThreadId::new(), id.clone(), db_thread, project_state.project.clone(), @@ -958,16 +961,11 @@ impl NativeAgent { let thread = self.open_thread(id.clone(), project, cx); cx.spawn(async move |this, cx| { let acp_thread = thread.await?; - let result = this - .update(cx, |this, cx| { - this.sessions - .get(&id) - .unwrap() - .thread - .update(cx, |thread, cx| thread.summary(cx)) - })? - .await - .context("Failed to generate summary")?; + let summary_task = this.update(cx, |this, cx| { + let session = this.sessions.get(&id).context("session not found")?; + anyhow::Ok(session.thread.update(cx, |thread, cx| thread.summary(cx))) + })??; + let result = summary_task.await.context("Failed to generate summary")?; drop(acp_thread); Ok(result) }) @@ -978,8 +976,8 @@ impl NativeAgent { return; } - let id = thread.read(cx).id().clone(); - let Some(session) = self.sessions.get_mut(&id) else { + let session_id = thread.read(cx).session_id().clone(); + let Some(session) = self.sessions.get_mut(&session_id) else { return; }; @@ -1010,7 +1008,7 @@ impl NativeAgent { }; let db_thread = db_thread.await; database - .save_thread(id, db_thread, folder_paths) + .save_thread(session_id, db_thread, folder_paths) .await .log_err(); thread_store.update(cx, |store, cx| store.reload(cx)); @@ -1155,7 +1153,7 @@ impl NativeAgentConnection { let Some((thread, acp_thread)) = self.0.update(cx, |agent, _cx| { agent .sessions - .get_mut(&session_id) + .get(&session_id) .map(|s| (s.thread.clone(), s.acp_thread.clone())) }) else { return Task::ready(Err(anyhow!("Session not found"))); @@ -1788,7 +1786,7 @@ impl NativeThreadEnvironment { }; let parent_thread = parent_thread_entity.read(cx); let current_depth = parent_thread.depth(); - let parent_session_id = parent_thread.id().clone(); + let parent_session_id = parent_thread.session_id().clone(); if current_depth >= MAX_SUBAGENT_DEPTH { return Err(anyhow!( @@ -1803,7 +1801,7 @@ impl NativeThreadEnvironment { thread }); - let session_id = subagent_thread.read(cx).id().clone(); + let subagent_thread_id = subagent_thread.read(cx).id(); let acp_thread = self .agent @@ -1821,12 +1819,12 @@ impl NativeThreadEnvironment { telemetry::event!( "Subagent Started", session = parent_thread_entity.read(cx).id().to_string(), - subagent_session = session_id.to_string(), + subagent_session = subagent_thread_id.to_string(), depth, is_resumed = false, ); - self.prompt_subagent(session_id, subagent_thread, acp_thread) + self.prompt_subagent(subagent_thread_id, subagent_thread, acp_thread) } pub(crate) fn resume_subagent_thread( @@ -1834,12 +1832,17 @@ impl NativeThreadEnvironment { session_id: acp::SessionId, cx: &mut App, ) -> Result> { - let (subagent_thread, acp_thread) = self.agent.update(cx, |agent, _cx| { + let (subagent_thread, acp_thread, thread_id) = self.agent.update(cx, |agent, _cx| { let session = agent .sessions .get(&session_id) .ok_or_else(|| anyhow!("No subagent session found with id {session_id}"))?; - anyhow::Ok((session.thread.clone(), session.acp_thread.clone())) + let thread_id = session.thread.read(_cx).id(); + anyhow::Ok(( + session.thread.clone(), + session.acp_thread.clone(), + thread_id, + )) })??; let depth = subagent_thread.read(cx).depth(); @@ -1854,12 +1857,12 @@ impl NativeThreadEnvironment { ); } - self.prompt_subagent(session_id, subagent_thread, acp_thread) + self.prompt_subagent(thread_id, subagent_thread, acp_thread) } fn prompt_subagent( &self, - session_id: acp::SessionId, + thread_id: ThreadId, subagent_thread: Entity, acp_thread: Entity, ) -> Result> { @@ -1867,7 +1870,7 @@ impl NativeThreadEnvironment { anyhow::bail!("Parent thread no longer exists".to_string()); }; Ok(Rc::new(NativeSubagentHandle::new( - session_id, + thread_id, subagent_thread, acp_thread, parent_thread_entity, @@ -1931,7 +1934,7 @@ enum SubagentPromptResult { } pub struct NativeSubagentHandle { - session_id: acp::SessionId, + thread_id: ThreadId, parent_thread: WeakEntity, subagent_thread: Entity, acp_thread: Entity, @@ -1939,13 +1942,13 @@ pub struct NativeSubagentHandle { impl NativeSubagentHandle { fn new( - session_id: acp::SessionId, + thread_id: ThreadId, subagent_thread: Entity, acp_thread: Entity, parent_thread_entity: Entity, ) -> Self { NativeSubagentHandle { - session_id, + thread_id, subagent_thread, parent_thread: parent_thread_entity.downgrade(), acp_thread, @@ -1954,8 +1957,12 @@ impl NativeSubagentHandle { } impl SubagentHandle for NativeSubagentHandle { - fn id(&self) -> acp::SessionId { - self.session_id.clone() + fn id(&self) -> ThreadId { + self.thread_id + } + + fn session_id(&self, cx: &App) -> acp::SessionId { + self.subagent_thread.read(cx).session_id().clone() } fn num_entries(&self, cx: &App) -> usize { @@ -1965,7 +1972,7 @@ impl SubagentHandle for NativeSubagentHandle { fn send(&self, message: String, cx: &AsyncApp) -> Task> { let thread = self.subagent_thread.clone(); let acp_thread = self.acp_thread.clone(); - let subagent_session_id = self.session_id.clone(); + let subagent_thread_id = self.thread_id; let parent_thread = self.parent_thread.clone(); cx.spawn(async move |cx| { @@ -2065,7 +2072,7 @@ impl SubagentHandle for NativeSubagentHandle { parent_thread .update(cx, |parent_thread, cx| { - parent_thread.unregister_running_subagent(&subagent_session_id, cx) + parent_thread.unregister_running_subagent(subagent_thread_id, cx) }) .ok(); diff --git a/crates/agent/src/db.rs b/crates/agent/src/db.rs index bde07a040869bf11a1b95bf433bf6af1e2d0a932..868ac3e4b1a809394109cf07dd1d71eb67139d29 100644 --- a/crates/agent/src/db.rs +++ b/crates/agent/src/db.rs @@ -457,10 +457,10 @@ impl ThreadsDatabase { let title = thread.title.to_string(); let updated_at = thread.updated_at.to_rfc3339(); - let parent_id = thread + let parent_id: Option> = thread .subagent_context .as_ref() - .map(|ctx| ctx.parent_thread_id.0.clone()); + .map(|ctx| Arc::from(ctx.parent_thread_id.0.to_string())); let serialized_folder_paths = folder_paths.serialize(); let (folder_paths_str, folder_paths_order_str): (Option, Option) = if folder_paths.is_empty() { diff --git a/crates/agent/src/tests/mod.rs b/crates/agent/src/tests/mod.rs index ff53136a0ded4bbc283fea30598d8d30e6e29709..de030456de69830714dda4860d55adb1143769a2 100644 --- a/crates/agent/src/tests/mod.rs +++ b/crates/agent/src/tests/mod.rs @@ -157,12 +157,17 @@ impl crate::TerminalHandle for FakeTerminalHandle { } struct FakeSubagentHandle { + thread_id: acp_thread::ThreadId, session_id: acp::SessionId, send_task: Shared>, } impl SubagentHandle for FakeSubagentHandle { - fn id(&self) -> acp::SessionId { + fn id(&self) -> acp_thread::ThreadId { + self.thread_id + } + + fn session_id(&self, _cx: &App) -> acp::SessionId { self.session_id.clone() } diff --git a/crates/agent/src/thread.rs b/crates/agent/src/thread.rs index e3a075ada62b6108c489779d5261c1c89afec8aa..2e7ad69041df134ed842f098acbd26851fab99b9 100644 --- a/crates/agent/src/thread.rs +++ b/crates/agent/src/thread.rs @@ -6,7 +6,7 @@ use crate::{ SystemPromptTemplate, Template, Templates, TerminalTool, ToolPermissionDecision, UpdatePlanTool, WebSearchTool, decide_permission_from_settings, }; -use acp_thread::{MentionUri, UserMessageId}; +use acp_thread::{MentionUri, ThreadId, UserMessageId}; use action_log::ActionLog; use feature_flags::{ FeatureFlagAppExt as _, StreamingEditFileToolFeatureFlag, UpdatePlanToolFeatureFlag, @@ -68,7 +68,7 @@ pub const MAX_SUBAGENT_DEPTH: u8 = 1; #[derive(Clone, Debug, Serialize, Deserialize)] pub struct SubagentContext { /// ID of the parent thread - pub parent_thread_id: acp::SessionId, + pub parent_thread_id: ThreadId, /// Current depth level (0 = root agent, 1 = first-level subagent, etc.) pub depth: u8, @@ -620,8 +620,10 @@ pub trait TerminalHandle { } pub trait SubagentHandle { - /// The session ID of this subagent thread - fn id(&self) -> acp::SessionId; + /// The internal thread ID of this subagent thread, allocated by Zed. + fn id(&self) -> ThreadId; + /// The ACP session ID for this subagent, allocated by the agent. + fn session_id(&self, cx: &App) -> acp::SessionId; /// The current number of entries in the thread. /// Useful for knowing where the next turn will begin fn num_entries(&self, cx: &App) -> usize; @@ -922,7 +924,8 @@ enum CompletionError { } pub struct Thread { - id: acp::SessionId, + id: ThreadId, + session_id: acp::SessionId, prompt_id: PromptId, updated_at: DateTime, title: Option, @@ -996,7 +999,7 @@ impl Thread { cx, ); thread.subagent_context = Some(SubagentContext { - parent_thread_id: parent_thread.read(cx).id().clone(), + parent_thread_id: parent_thread.read(cx).id(), depth: parent_thread.read(cx).depth() + 1, }); thread.inherit_parent_settings(parent_thread, cx); @@ -1048,7 +1051,8 @@ impl Thread { let (prompt_capabilities_tx, prompt_capabilities_rx) = watch::channel(Self::prompt_capabilities(model.as_deref())); Self { - id: acp::SessionId::new(uuid::Uuid::new_v4().to_string()), + id: ThreadId::new(), + session_id: acp::SessionId::new(uuid::Uuid::new_v4().to_string()), prompt_id: PromptId::new(), updated_at: Utc::now(), title: None, @@ -1103,8 +1107,12 @@ impl Thread { self.profile_id = parent.profile_id.clone(); } - pub fn id(&self) -> &acp::SessionId { - &self.id + pub fn id(&self) -> ThreadId { + self.id + } + + pub fn session_id(&self) -> &acp::SessionId { + &self.session_id } /// Returns true if this thread was imported from a shared thread. @@ -1236,7 +1244,8 @@ impl Thread { } pub fn from_db( - id: acp::SessionId, + id: ThreadId, + session_id: acp::SessionId, db_thread: DbThread, project: Entity, project_context: Entity, @@ -1279,6 +1288,7 @@ impl Thread { Self { id, + session_id, prompt_id: PromptId::new(), title: if db_thread.title.is_empty() { None @@ -2908,22 +2918,18 @@ impl Thread { self.running_subagents.push(subagent); } - pub(crate) fn unregister_running_subagent( - &mut self, - subagent_session_id: &acp::SessionId, - cx: &App, - ) { + pub(crate) fn unregister_running_subagent(&mut self, subagent_thread_id: ThreadId, cx: &App) { self.running_subagents.retain(|s| { s.upgrade() - .map_or(false, |s| s.read(cx).id() != subagent_session_id) + .map_or(false, |s| s.read(cx).id() != subagent_thread_id) }); } #[cfg(any(test, feature = "test-support"))] - pub fn running_subagent_ids(&self, cx: &App) -> Vec { + pub fn running_subagent_ids(&self, cx: &App) -> Vec { self.running_subagents .iter() - .filter_map(|s| s.upgrade().map(|s| s.read(cx).id().clone())) + .filter_map(|s| s.upgrade().map(|s| s.read(cx).id())) .collect() } @@ -2931,10 +2937,8 @@ impl Thread { self.subagent_context.is_some() } - pub fn parent_thread_id(&self) -> Option { - self.subagent_context - .as_ref() - .map(|c| c.parent_thread_id.clone()) + pub fn parent_thread_id(&self) -> Option { + self.subagent_context.as_ref().map(|c| c.parent_thread_id) } pub fn depth(&self) -> u8 { diff --git a/crates/agent/src/tools/spawn_agent_tool.rs b/crates/agent/src/tools/spawn_agent_tool.rs index 27afbbdc3ea05ddbfea689d1bb1a18c53b42198b..8b572ba8e34ec75f32379400703644d74061a29e 100644 --- a/crates/agent/src/tools/spawn_agent_tool.rs +++ b/crates/agent/src/tools/spawn_agent_tool.rs @@ -153,12 +153,12 @@ impl AgentTool for SpawnAgentTool { session_info: None, })?; let session_info = SubagentSessionInfo { - session_id: subagent.id(), + session_id: subagent.session_id(cx), message_start_index: subagent.num_entries(cx), message_end_index: None, }; - event_stream.subagent_spawned(subagent.id()); + event_stream.subagent_spawned(subagent.session_id(cx)); event_stream.update_fields_with_meta( acp::ToolCallUpdateFields::new(), Some(acp::Meta::from_iter([( diff --git a/crates/agent_servers/src/acp.rs b/crates/agent_servers/src/acp.rs index dbcaabed1cf1971a6e281d8d31f8dad25dfb7434..37aea702227413082617c5753a8f5a8581cd0628 100644 --- a/crates/agent_servers/src/acp.rs +++ b/crates/agent_servers/src/acp.rs @@ -685,6 +685,7 @@ impl AgentConnection for AcpConnection { self.clone(), project, action_log, + acp_thread::ThreadId::new(), response.session_id.clone(), // ACP doesn't currently support per-session prompt capabilities or changing capabilities dynamically. watch::Receiver::constant(self.agent_capabilities.prompt_capabilities.clone()), @@ -746,6 +747,7 @@ impl AgentConnection for AcpConnection { self.clone(), project, action_log, + acp_thread::ThreadId::new(), session_id.clone(), watch::Receiver::constant(self.agent_capabilities.prompt_capabilities.clone()), cx, @@ -828,6 +830,7 @@ impl AgentConnection for AcpConnection { self.clone(), project, action_log, + acp_thread::ThreadId::new(), session_id.clone(), watch::Receiver::constant(self.agent_capabilities.prompt_capabilities.clone()), cx, diff --git a/crates/agent_ui/src/agent_panel.rs b/crates/agent_ui/src/agent_panel.rs index 0f738ee840d5a50acd04ba977fcdde4c995076fb..fbd261b89ecbe8159b5294e71d5c2e09a6c05a14 100644 --- a/crates/agent_ui/src/agent_panel.rs +++ b/crates/agent_ui/src/agent_panel.rs @@ -3955,7 +3955,7 @@ impl AgentPanel { let thread = active_thread.read(cx); if !thread.is_empty() { - let session_id = thread.id().clone(); + let session_id = thread.session_id().clone(); this.item( ContextMenuEntry::new("New From Summary") .icon(IconName::ThreadFromSummary) diff --git a/crates/agent_ui/src/conversation_view.rs b/crates/agent_ui/src/conversation_view.rs index d38e1344701fc8681b0feaf2fa7843611750532d..b3cf9b4b408eaa9c21c9f3bd215a768cfca1b876 100644 --- a/crates/agent_ui/src/conversation_view.rs +++ b/crates/agent_ui/src/conversation_view.rs @@ -219,7 +219,7 @@ impl Conversation { cx: &'a App, ) -> Option<(acp::SessionId, acp::ToolCallId, &'a PermissionOptions)> { let thread = self.threads.get(session_id)?; - let is_subagent = thread.read(cx).parent_session_id().is_some(); + let is_subagent = thread.read(cx).parent_thread_id().is_some(); let (thread, tool_id) = if is_subagent { let id = self.permission_requests.get(session_id)?.iter().next()?; (thread, id) @@ -246,7 +246,7 @@ impl Conversation { .iter() .filter_map(|(session_id, tool_call_ids)| { let thread = self.threads.get(session_id)?; - if thread.read(cx).parent_session_id().is_some() && !tool_call_ids.is_empty() { + if thread.read(cx).parent_thread_id().is_some() && !tool_call_ids.is_empty() { Some((session_id.clone(), tool_call_ids.len())) } else { None @@ -1250,7 +1250,7 @@ impl ConversationView { cx: &mut Context, ) { let thread_id = thread.read(cx).session_id().clone(); - let is_subagent = thread.read(cx).parent_session_id().is_some(); + let is_subagent = thread.read(cx).parent_thread_id().is_some(); match event { AcpThreadEvent::NewEntry => { let len = thread.read(cx).entries().len(); diff --git a/crates/agent_ui/src/conversation_view/thread_view.rs b/crates/agent_ui/src/conversation_view/thread_view.rs index 412778e054cab1596b2e9555a9cd4a12c3edb6ec..7bdfd42712ded5fa4a49d6f7f0b692df39a73d2b 100644 --- a/crates/agent_ui/src/conversation_view/thread_view.rs +++ b/crates/agent_ui/src/conversation_view/thread_view.rs @@ -56,7 +56,7 @@ impl ThreadFeedbackState { } } let session_id = thread.read(cx).session_id().clone(); - let parent_session_id = thread.read(cx).parent_session_id().cloned(); + let parent_session_id = thread.read(cx).parent_thread_id(); let agent_telemetry_id = thread.read(cx).connection().telemetry_id(); let task = telemetry.thread_data(&session_id, cx); let rating = match feedback { @@ -1050,7 +1050,7 @@ impl ThreadView { cx: &mut Context, ) { let session_id = self.thread.read(cx).session_id().clone(); - let parent_session_id = self.thread.read(cx).parent_session_id().cloned(); + let parent_session_id = self.thread.read(cx).parent_thread_id(); let agent_telemetry_id = self.thread.read(cx).connection().telemetry_id(); let is_first_message = self.thread.read(cx).entries().is_empty(); let thread = self.thread.downgrade(); @@ -1270,7 +1270,7 @@ impl ThreadView { let parent_session_id = self .thread .read(cx) - .parent_session_id() + .parent_thread_id() .map(|id| id.to_string()); telemetry::event!( @@ -2235,7 +2235,7 @@ impl ThreadView { this.child(Divider::horizontal().color(DividerColor::Border)) }) .when( - !changed_buffers.is_empty() && thread.parent_session_id().is_none(), + !changed_buffers.is_empty() && thread.parent_thread_id().is_none(), |this| { this.child(self.render_edits_summary( &changed_buffers, diff --git a/crates/agent_ui/src/entry_view_state.rs b/crates/agent_ui/src/entry_view_state.rs index eeaf8f6935a2294d8d9a1fe71b8d8acd62ee43a2..c45eeac7b3bfe1a162aee2b0e432a469b842dcce 100644 --- a/crates/agent_ui/src/entry_view_state.rs +++ b/crates/agent_ui/src/entry_view_state.rs @@ -73,7 +73,7 @@ impl EntryViewState { match thread_entry { AgentThreadEntry::UserMessage(message) => { let has_id = message.id.is_some(); - let is_subagent = thread.read(cx).parent_session_id().is_some(); + let is_subagent = thread.read(cx).parent_thread_id().is_some(); let chunks = message.chunks.clone(); if let Some(Entry::UserMessage(editor)) = self.entries.get_mut(index) { if !editor.focus_handle(cx).is_focused(window) { diff --git a/crates/agent_ui/src/thread_metadata_store.rs b/crates/agent_ui/src/thread_metadata_store.rs index 4ba68b400a60320e95bfd645ee662f6483dc6cf4..07d0bce883361f2727db694a755ff379738d79c2 100644 --- a/crates/agent_ui/src/thread_metadata_store.rs +++ b/crates/agent_ui/src/thread_metadata_store.rs @@ -879,7 +879,7 @@ impl ThreadMetadataStore { cx.observe_new::(move |thread, _window, cx| { // Don't track subagent threads in the sidebar. - if thread.parent_session_id().is_some() { + if thread.parent_thread_id().is_some() { return; } @@ -965,7 +965,7 @@ impl ThreadMetadataStore { cx: &mut Context, ) { // Don't track subagent threads in the sidebar. - if thread.read(cx).parent_session_id().is_some() { + if thread.read(cx).parent_thread_id().is_some() { return; } diff --git a/crates/sidebar/src/sidebar_tests.proptest-regressions b/crates/sidebar/src/sidebar_tests.proptest-regressions new file mode 100644 index 0000000000000000000000000000000000000000..dd2ed766c10e91cc9968d957b701ac2ee204dad4 --- /dev/null +++ b/crates/sidebar/src/sidebar_tests.proptest-regressions @@ -0,0 +1,10 @@ +# Seeds for failure cases proptest has generated in the past. It is +# automatically read and these particular cases re-run before any +# novel cases are generated. +# +# It is recommended to check this file in to source control so that +# everyone who runs the test benefits from these saved cases. +cc 7ab586bbc9abb1188a649c7c1cacc1a0976a22747ebe6c88111354df7e5b51af # shrinks to TestSidebarInvariantsArgs = TestSidebarInvariantsArgs { __seed: 8174137932693891958, raw_operations: [87] } +cc c26d806fa636aa90647d990385d05d45d071406308696f6664405885b3cf493c # shrinks to TestSidebarInvariantsArgs = TestSidebarInvariantsArgs { __seed: 17908799813400136853, raw_operations: [7] } +cc 32e0926d11da3d33495bf2ace4768e3a2e112d00b90a6289e601a4d94414feec # shrinks to TestSidebarInvariantsArgs = TestSidebarInvariantsArgs { __seed: 15152078651144901147, raw_operations: [36] } +cc bb64a25f4eda0896e496ee89650be38906276aa5695cc3197450da62a2e90196 # shrinks to TestSidebarInvariantsArgs = TestSidebarInvariantsArgs { __seed: 18226949068654728636, raw_operations: [84] }