wip

Mikayla Maki created

Change summary

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 
crates/agent_ui/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 
crates/sidebar/src/sidebar_tests.proptest-regressions | 10 +
14 files changed, 143 insertions(+), 85 deletions(-)

Detailed changes

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<PathList>,
-    parent_session_id: Option<acp::SessionId>,
+    parent_thread_id: Option<ThreadId>,
     title: Option<SharedString>,
     provisional_title: Option<SharedString>,
     entries: Vec<AgentThreadEntry>,
@@ -1184,12 +1205,13 @@ impl Error for LoadError {}
 
 impl AcpThread {
     pub fn new(
-        parent_session_id: Option<acp::SessionId>,
+        parent_thread_id: Option<ThreadId>,
         title: Option<SharedString>,
         work_dirs: Option<PathList>,
         connection: Rc<dyn AgentConnection>,
         project: Entity<Project>,
         action_log: Entity<ActionLog>,
+        thread_id: ThreadId,
         session_id: acp::SessionId,
         mut prompt_capabilities_rx: watch::Receiver<acp::PromptCapabilities>,
         cx: &mut Context<Self>,
@@ -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<ThreadId> {
+        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<Self>) {
         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<Result<()>> {
         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

crates/acp_thread/src/connection.rs 🔗

@@ -734,6 +734,7 @@ mod test_support {
             cx: &mut gpui::App,
         ) -> Entity<AcpThread> {
             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()

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<Self>,
     ) {
-        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<Self>,
     ) {
-        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<Rc<dyn SubagentHandle>> {
-        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<Thread>,
         acp_thread: Entity<acp_thread::AcpThread>,
     ) -> Result<Rc<dyn SubagentHandle>> {
@@ -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<Thread>,
     subagent_thread: Entity<Thread>,
     acp_thread: Entity<acp_thread::AcpThread>,
@@ -1939,13 +1942,13 @@ pub struct NativeSubagentHandle {
 
 impl NativeSubagentHandle {
     fn new(
-        session_id: acp::SessionId,
+        thread_id: ThreadId,
         subagent_thread: Entity<Thread>,
         acp_thread: Entity<acp_thread::AcpThread>,
         parent_thread_entity: Entity<Thread>,
     ) -> 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<Result<String>> {
         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();
 

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<Arc<str>> = 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<String>, Option<String>) =
             if folder_paths.is_empty() {

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<Task<String>>,
 }
 
 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()
     }
 

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<Utc>,
     title: Option<SharedString>,
@@ -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>,
         project_context: Entity<ProjectContext>,
@@ -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<acp::SessionId> {
+    pub fn running_subagent_ids(&self, cx: &App) -> Vec<ThreadId> {
         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<acp::SessionId> {
-        self.subagent_context
-            .as_ref()
-            .map(|c| c.parent_thread_id.clone())
+    pub fn parent_thread_id(&self) -> Option<ThreadId> {
+        self.subagent_context.as_ref().map(|c| c.parent_thread_id)
     }
 
     pub fn depth(&self) -> u8 {

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([(

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,

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)

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<Self>,
     ) {
         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();

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<Self>,
     ) {
         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,

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) {

crates/agent_ui/src/thread_metadata_store.rs 🔗

@@ -879,7 +879,7 @@ impl ThreadMetadataStore {
 
         cx.observe_new::<acp_thread::AcpThread>(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<Self>,
     ) {
         // 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;
         }
 

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] }