diff --git a/crates/acp_thread/src/acp_thread.rs b/crates/acp_thread/src/acp_thread.rs index bffddde099c05438bb81c8bbbe99e3c77a5113e6..58252eaddca553eb1da4c960a829a88afb9eb497 100644 --- a/crates/acp_thread/src/acp_thread.rs +++ b/crates/acp_thread/src/acp_thread.rs @@ -952,6 +952,8 @@ struct RunningTurn { } pub struct AcpThread { + session_id: acp::SessionId, + cwd: Option, parent_session_id: Option, title: SharedString, provisional_title: Option, @@ -963,7 +965,6 @@ pub struct AcpThread { turn_id: u32, running_turn: Option, connection: Rc, - session_id: acp::SessionId, token_usage: Option, prompt_capabilities: acp::PromptCapabilities, _observe_prompt_capabilities: Task>, @@ -1048,87 +1049,6 @@ pub enum TerminalProviderCommand { }, } -impl AcpThread { - pub fn on_terminal_provider_event( - &mut self, - event: TerminalProviderEvent, - cx: &mut Context, - ) { - match event { - TerminalProviderEvent::Created { - terminal_id, - label, - cwd, - output_byte_limit, - terminal, - } => { - let entity = self.register_terminal_created( - terminal_id.clone(), - label, - cwd, - output_byte_limit, - terminal, - cx, - ); - - if let Some(mut chunks) = self.pending_terminal_output.remove(&terminal_id) { - for data in chunks.drain(..) { - entity.update(cx, |term, cx| { - term.inner().update(cx, |inner, cx| { - inner.write_output(&data, cx); - }) - }); - } - } - - if let Some(_status) = self.pending_terminal_exit.remove(&terminal_id) { - entity.update(cx, |_term, cx| { - cx.notify(); - }); - } - - cx.notify(); - } - TerminalProviderEvent::Output { terminal_id, data } => { - if let Some(entity) = self.terminals.get(&terminal_id) { - entity.update(cx, |term, cx| { - term.inner().update(cx, |inner, cx| { - inner.write_output(&data, cx); - }) - }); - } else { - self.pending_terminal_output - .entry(terminal_id) - .or_default() - .push(data); - } - } - TerminalProviderEvent::TitleChanged { terminal_id, title } => { - if let Some(entity) = self.terminals.get(&terminal_id) { - entity.update(cx, |term, cx| { - term.inner().update(cx, |inner, cx| { - inner.breadcrumb_text = title; - cx.emit(::terminal::Event::BreadcrumbsChanged); - }) - }); - } - } - TerminalProviderEvent::Exit { - terminal_id, - status, - } => { - if let Some(entity) = self.terminals.get(&terminal_id) { - entity.update(cx, |_term, cx| { - cx.notify(); - }); - } else { - self.pending_terminal_exit.insert(terminal_id, status); - } - } - } - } -} - #[derive(PartialEq, Eq, Debug)] pub enum ThreadStatus { Idle, @@ -1175,6 +1095,7 @@ impl AcpThread { pub fn new( parent_session_id: Option, title: impl Into, + cwd: Option, connection: Rc, project: Entity, action_log: Entity, @@ -1195,6 +1116,7 @@ impl AcpThread { Self { parent_session_id, + cwd, action_log, shared_buffers: Default::default(), entries: Default::default(), @@ -1268,6 +1190,10 @@ impl AcpThread { &self.session_id } + pub fn cwd(&self) -> Option<&PathBuf> { + self.cwd.as_ref() + } + pub fn status(&self) -> ThreadStatus { if self.running_turn.is_some() { ThreadStatus::Generating @@ -2624,6 +2550,85 @@ impl AcpThread { } } } + + pub fn on_terminal_provider_event( + &mut self, + event: TerminalProviderEvent, + cx: &mut Context, + ) { + match event { + TerminalProviderEvent::Created { + terminal_id, + label, + cwd, + output_byte_limit, + terminal, + } => { + let entity = self.register_terminal_created( + terminal_id.clone(), + label, + cwd, + output_byte_limit, + terminal, + cx, + ); + + if let Some(mut chunks) = self.pending_terminal_output.remove(&terminal_id) { + for data in chunks.drain(..) { + entity.update(cx, |term, cx| { + term.inner().update(cx, |inner, cx| { + inner.write_output(&data, cx); + }) + }); + } + } + + if let Some(_status) = self.pending_terminal_exit.remove(&terminal_id) { + entity.update(cx, |_term, cx| { + cx.notify(); + }); + } + + cx.notify(); + } + TerminalProviderEvent::Output { terminal_id, data } => { + if let Some(entity) = self.terminals.get(&terminal_id) { + entity.update(cx, |term, cx| { + term.inner().update(cx, |inner, cx| { + inner.write_output(&data, cx); + }) + }); + } else { + self.pending_terminal_output + .entry(terminal_id) + .or_default() + .push(data); + } + } + TerminalProviderEvent::TitleChanged { terminal_id, title } => { + if let Some(entity) = self.terminals.get(&terminal_id) { + entity.update(cx, |term, cx| { + term.inner().update(cx, |inner, cx| { + inner.breadcrumb_text = title; + cx.emit(::terminal::Event::BreadcrumbsChanged); + }) + }); + } + } + TerminalProviderEvent::Exit { + terminal_id, + status, + } => { + if let Some(entity) = self.terminals.get(&terminal_id) { + entity.update(cx, |_term, cx| { + cx.notify(); + }); + } else { + self.pending_terminal_exit.insert(terminal_id, status); + } + } + } + } } fn markdown_for_raw_output( @@ -3988,7 +3993,7 @@ mod tests { fn new_session( self: Rc, project: Entity, - _cwd: &Path, + cwd: &Path, cx: &mut App, ) -> Task>> { let session_id = acp::SessionId::new( @@ -4003,6 +4008,7 @@ mod tests { AcpThread::new( None, "Test", + Some(cwd.to_path_buf()), self.clone(), project, action_log, diff --git a/crates/acp_thread/src/connection.rs b/crates/acp_thread/src/connection.rs index 773508f1c898c39d713d5779c82384caf8f190ec..644986bc15eccbe7d2be32ea5ad6e422db930541 100644 --- a/crates/acp_thread/src/connection.rs +++ b/crates/acp_thread/src/connection.rs @@ -45,9 +45,10 @@ pub trait AgentConnection { /// Load an existing session by ID. fn load_session( self: Rc, - _session: AgentSessionInfo, + _session_id: acp::SessionId, _project: Entity, _cwd: &Path, + _title: Option, _cx: &mut App, ) -> Task>> { Task::ready(Err(anyhow::Error::msg("Loading sessions is not supported"))) @@ -71,9 +72,10 @@ pub trait AgentConnection { /// Resume an existing session by ID without replaying previous messages. fn resume_session( self: Rc, - _session: AgentSessionInfo, + _session_id: acp::SessionId, _project: Entity, _cwd: &Path, + _title: Option, _cx: &mut App, ) -> Task>> { Task::ready(Err(anyhow::Error::msg( @@ -619,7 +621,7 @@ mod test_support { fn new_session( self: Rc, project: Entity, - _cwd: &Path, + cwd: &Path, cx: &mut gpui::App, ) -> Task>> { static NEXT_SESSION_ID: AtomicUsize = AtomicUsize::new(0); @@ -630,6 +632,7 @@ mod test_support { AcpThread::new( None, "Test", + Some(cwd.to_path_buf()), self.clone(), project, action_log, diff --git a/crates/agent/src/agent.rs b/crates/agent/src/agent.rs index a93c2d2062b7472f8ed94a6ea0947a685edd204f..d9ad55c7127983516dbb5fe0392ef135186b79f7 100644 --- a/crates/agent/src/agent.rs +++ b/crates/agent/src/agent.rs @@ -361,6 +361,7 @@ impl NativeAgent { let mut acp_thread = acp_thread::AcpThread::new( parent_session_id, title, + None, connection, project.clone(), action_log.clone(), @@ -1277,13 +1278,14 @@ impl acp_thread::AgentConnection for NativeAgentConnection { fn load_session( self: Rc, - session: AgentSessionInfo, + session_id: acp::SessionId, _project: Entity, _cwd: &Path, + _title: Option, cx: &mut App, ) -> Task>> { self.0 - .update(cx, |agent, cx| agent.open_thread(session.session_id, cx)) + .update(cx, |agent, cx| agent.open_thread(session_id, cx)) } fn supports_close_session(&self) -> bool { diff --git a/crates/agent_servers/src/acp.rs b/crates/agent_servers/src/acp.rs index c63e4fab2201671fa6448e9d58f6c925c2c91cd8..ceceb5b8ae02a0674b27e0fa18244a94f2b409de 100644 --- a/crates/agent_servers/src/acp.rs +++ b/crates/agent_servers/src/acp.rs @@ -385,7 +385,7 @@ impl AgentConnection for AcpConnection { cx.spawn(async move |cx| { let response = self.connection - .new_session(acp::NewSessionRequest::new(cwd).mcp_servers(mcp_servers)) + .new_session(acp::NewSessionRequest::new(cwd.clone()).mcp_servers(mcp_servers)) .await .map_err(map_acp_error)?; @@ -560,6 +560,7 @@ impl AgentConnection for AcpConnection { AcpThread::new( None, self.display_name.clone(), + Some(cwd), self.clone(), project, action_log, @@ -598,9 +599,10 @@ impl AgentConnection for AcpConnection { fn load_session( self: Rc, - session: AgentSessionInfo, + session_id: acp::SessionId, project: Entity, cwd: &Path, + title: Option, cx: &mut App, ) -> Task>> { if !self.agent_capabilities.load_session { @@ -612,25 +614,23 @@ impl AgentConnection for AcpConnection { let cwd = cwd.to_path_buf(); let mcp_servers = mcp_servers_for_project(&project, cx); let action_log = cx.new(|_| ActionLog::new(project.clone())); - let title = session - .title - .clone() - .unwrap_or_else(|| self.display_name.clone()); + let title = title.unwrap_or_else(|| self.display_name.clone()); let thread: Entity = cx.new(|cx| { AcpThread::new( None, title, + Some(cwd.clone()), self.clone(), project, action_log, - session.session_id.clone(), + session_id.clone(), watch::Receiver::constant(self.agent_capabilities.prompt_capabilities.clone()), cx, ) }); self.sessions.borrow_mut().insert( - session.session_id.clone(), + session_id.clone(), AcpSession { thread: thread.downgrade(), suppress_abort_err: false, @@ -644,21 +644,20 @@ impl AgentConnection for AcpConnection { let response = match self .connection .load_session( - acp::LoadSessionRequest::new(session.session_id.clone(), cwd) - .mcp_servers(mcp_servers), + acp::LoadSessionRequest::new(session_id.clone(), cwd).mcp_servers(mcp_servers), ) .await { Ok(response) => response, Err(err) => { - self.sessions.borrow_mut().remove(&session.session_id); + self.sessions.borrow_mut().remove(&session_id); return Err(map_acp_error(err)); } }; let (modes, models, config_options) = config_state(response.modes, response.models, response.config_options); - if let Some(session) = self.sessions.borrow_mut().get_mut(&session.session_id) { + if let Some(session) = self.sessions.borrow_mut().get_mut(&session_id) { session.session_modes = modes; session.models = models; session.config_options = config_options.map(ConfigOptions::new); @@ -670,9 +669,10 @@ impl AgentConnection for AcpConnection { fn resume_session( self: Rc, - session: AgentSessionInfo, + session_id: acp::SessionId, project: Entity, cwd: &Path, + title: Option, cx: &mut App, ) -> Task>> { if self @@ -689,25 +689,23 @@ impl AgentConnection for AcpConnection { let cwd = cwd.to_path_buf(); let mcp_servers = mcp_servers_for_project(&project, cx); let action_log = cx.new(|_| ActionLog::new(project.clone())); - let title = session - .title - .clone() - .unwrap_or_else(|| self.display_name.clone()); + let title = title.unwrap_or_else(|| self.display_name.clone()); let thread: Entity = cx.new(|cx| { AcpThread::new( None, title, + Some(cwd.clone()), self.clone(), project, action_log, - session.session_id.clone(), + session_id.clone(), watch::Receiver::constant(self.agent_capabilities.prompt_capabilities.clone()), cx, ) }); self.sessions.borrow_mut().insert( - session.session_id.clone(), + session_id.clone(), AcpSession { thread: thread.downgrade(), suppress_abort_err: false, @@ -721,21 +719,21 @@ impl AgentConnection for AcpConnection { let response = match self .connection .resume_session( - acp::ResumeSessionRequest::new(session.session_id.clone(), cwd) + acp::ResumeSessionRequest::new(session_id.clone(), cwd) .mcp_servers(mcp_servers), ) .await { Ok(response) => response, Err(err) => { - self.sessions.borrow_mut().remove(&session.session_id); + self.sessions.borrow_mut().remove(&session_id); return Err(map_acp_error(err)); } }; let (modes, models, config_options) = config_state(response.modes, response.models, response.config_options); - if let Some(session) = self.sessions.borrow_mut().get_mut(&session.session_id) { + if let Some(session) = self.sessions.borrow_mut().get_mut(&session_id) { session.session_modes = modes; session.models = models; session.config_options = config_options.map(ConfigOptions::new); diff --git a/crates/agent_ui/src/agent_panel.rs b/crates/agent_ui/src/agent_panel.rs index 45c22856c721fdfe165389e1775617afa7ffadb7..b53cb003969f8519f584aef1269554f4277e31f6 100644 --- a/crates/agent_ui/src/agent_panel.rs +++ b/crates/agent_ui/src/agent_panel.rs @@ -9,7 +9,7 @@ use std::{ time::Duration, }; -use acp_thread::{AcpThread, AgentSessionInfo, MentionUri, ThreadStatus}; +use acp_thread::{AcpThread, MentionUri, ThreadStatus}; use agent::{ContextServerRegistry, SharedThread, ThreadStore}; use agent_client_protocol as acp; use agent_servers::AgentServer; @@ -191,7 +191,15 @@ pub fn init(cx: &mut App) { if let Some(panel) = workspace.panel::(cx) { workspace.focus_panel::(window, cx); panel.update(cx, |panel, cx| { - panel.external_thread(action.agent.clone(), None, None, window, cx) + panel.external_thread( + action.agent.clone(), + None, + None, + None, + None, + window, + cx, + ) }); } }) @@ -322,6 +330,8 @@ pub fn init(cx: &mut App) { panel.update(cx, |panel, cx| { panel.external_thread( + None, + None, None, None, Some(AgentInitialContent::ContentBlock { @@ -715,16 +725,9 @@ impl AgentPanel { if let Some(thread_info) = last_active_thread { let agent_type = thread_info.agent_type.clone(); - let session_info = AgentSessionInfo { - session_id: acp::SessionId::new(thread_info.session_id), - cwd: thread_info.cwd, - title: thread_info.title.map(SharedString::from), - updated_at: None, - meta: None, - }; panel.update(cx, |panel, cx| { panel.selected_agent = agent_type; - panel.load_agent_thread(session_info, window, cx); + panel.load_agent_thread(thread_info.session_id.into(), thread_info.cwd, thread_info.title.map(SharedString::from), window, cx); }); } panel @@ -761,7 +764,13 @@ impl AgentPanel { window, |this, _, event, window, cx| match event { ThreadHistoryEvent::Open(thread) => { - this.load_agent_thread(thread.clone(), window, cx); + this.load_agent_thread( + thread.session_id.clone(), + thread.cwd.clone(), + thread.title.clone(), + window, + cx, + ); } }, ) @@ -950,13 +959,17 @@ impl AgentPanel { pub fn open_thread( &mut self, - thread: AgentSessionInfo, + session_id: acp::SessionId, + cwd: Option, + title: Option, window: &mut Window, cx: &mut Context, ) { self.external_thread( Some(crate::ExternalAgent::NativeAgent), - Some(thread), + Some(session_id), + cwd, + title, None, window, cx, @@ -1015,7 +1028,12 @@ impl AgentPanel { self.external_thread( Some(ExternalAgent::NativeAgent), None, - Some(AgentInitialContent::ThreadSummary(thread)), + None, + None, + Some(AgentInitialContent::ThreadSummary { + session_id: thread.session_id, + title: thread.title, + }), window, cx, ); @@ -1067,7 +1085,9 @@ impl AgentPanel { fn external_thread( &mut self, agent_choice: Option, - resume_thread: Option, + resume_session_id: Option, + cwd: Option, + title: Option, initial_content: Option, window: &mut Window, cx: &mut Context, @@ -1129,7 +1149,9 @@ impl AgentPanel { this.update_in(cx, |agent_panel, window, cx| { agent_panel.create_external_thread( server, - resume_thread, + resume_session_id, + cwd, + title, initial_content, workspace, project, @@ -1548,16 +1570,8 @@ impl AgentPanel { }) .await?; - let thread_metadata = acp_thread::AgentSessionInfo { - session_id, - cwd: None, - title: Some(title), - updated_at: Some(chrono::Utc::now()), - meta: None, - }; - this.update_in(cx, |this, window, cx| { - this.open_thread(thread_metadata, window, cx); + this.open_thread(session_id, None, Some(title), window, cx); })?; this.update_in(cx, |_, _window, cx| { @@ -1839,7 +1853,13 @@ impl AgentPanel { let entry = entry.clone(); panel .update(cx, move |this, cx| { - this.load_agent_thread(entry.clone(), window, cx); + this.load_agent_thread( + entry.session_id.clone(), + entry.cwd.clone(), + entry.title.clone(), + window, + cx, + ); }) .ok(); } @@ -1981,6 +2001,8 @@ impl AgentPanel { cx: &mut Context, ) { self.external_thread( + None, + None, None, None, initial_text.map(|text| AgentInitialContent::ContentBlock { @@ -2006,6 +2028,8 @@ impl AgentPanel { Some(crate::ExternalAgent::NativeAgent), None, None, + None, + None, window, cx, ), @@ -2013,6 +2037,8 @@ impl AgentPanel { Some(crate::ExternalAgent::Custom { name }), None, None, + None, + None, window, cx, ), @@ -2021,11 +2047,12 @@ impl AgentPanel { pub fn load_agent_thread( &mut self, - thread: AgentSessionInfo, + session_id: acp::SessionId, + cwd: Option, + title: Option, window: &mut Window, cx: &mut Context, ) { - let session_id = thread.session_id.clone(); if let Some(server_view) = self.background_threads.remove(&session_id) { self.set_active_view(ActiveView::AgentThread { server_view }, true, window, cx); return; @@ -2059,13 +2086,15 @@ impl AgentPanel { let Some(agent) = self.selected_external_agent() else { return; }; - self.external_thread(Some(agent), Some(thread), None, window, cx); + self.external_thread(Some(agent), Some(session_id), cwd, title, None, window, cx); } pub(crate) fn create_external_thread( &mut self, server: Rc, - resume_thread: Option, + resume_session_id: Option, + cwd: Option, + title: Option, initial_content: Option, workspace: WeakEntity, project: Entity, @@ -2087,7 +2116,9 @@ impl AgentPanel { let server_view = cx.new(|cx| { crate::ConnectionView::new( server, - resume_thread, + resume_session_id, + cwd, + title, initial_content, workspace.clone(), project, @@ -2598,7 +2629,15 @@ impl AgentPanel { workspace.focus_panel::(window, cx); if let Some(panel) = workspace.panel::(cx) { panel.update(cx, |panel, cx| { - panel.external_thread(None, None, Some(initial_content), window, cx); + panel.external_thread( + None, + None, + None, + None, + Some(initial_content), + window, + cx, + ); }); } }); @@ -4466,7 +4505,7 @@ impl AgentPanel { }; self.create_external_thread( - server, None, None, workspace, project, ext_agent, window, cx, + server, None, None, None, None, workspace, project, ext_agent, window, cx, ); } @@ -4877,17 +4916,7 @@ mod tests { // Load thread A back via load_agent_thread โ€” should promote from background. panel.update_in(&mut cx, |panel, window, cx| { - panel.load_agent_thread( - AgentSessionInfo { - session_id: session_id_a.clone(), - cwd: None, - title: None, - updated_at: None, - meta: None, - }, - window, - cx, - ); + panel.load_agent_thread(session_id_a.clone(), None, None, window, cx); }); // Thread A should now be the active view, promoted from background. diff --git a/crates/agent_ui/src/agent_ui.rs b/crates/agent_ui/src/agent_ui.rs index caecce3d0282e33daf8164fb17f48bd53be60b9f..eee7a61576e4f4dbdb56c98b497b50cc59c0053d 100644 --- a/crates/agent_ui/src/agent_ui.rs +++ b/crates/agent_ui/src/agent_ui.rs @@ -35,6 +35,7 @@ mod ui; use std::rc::Rc; use std::sync::Arc; +use agent_client_protocol as acp; use agent_settings::{AgentProfileId, AgentSettings}; use assistant_slash_command::SlashCommandRegistry; use client::Client; @@ -241,7 +242,10 @@ pub enum StartThreadIn { /// Content to initialize new external agent with. pub enum AgentInitialContent { - ThreadSummary(acp_thread::AgentSessionInfo), + ThreadSummary { + session_id: acp::SessionId, + title: Option, + }, ContentBlock { blocks: Vec, auto_submit: bool, diff --git a/crates/agent_ui/src/completion_provider.rs b/crates/agent_ui/src/completion_provider.rs index 30778909b2c9a91dab0b20417e973b7e83ea6a17..40ad7bc729269d5dae3364ecf3e0de6e5ee5b0ec 100644 --- a/crates/agent_ui/src/completion_provider.rs +++ b/crates/agent_ui/src/completion_provider.rs @@ -5,7 +5,8 @@ use std::sync::Arc; use std::sync::atomic::AtomicBool; use crate::ThreadHistory; -use acp_thread::{AgentSessionInfo, MentionUri}; +use acp_thread::MentionUri; +use agent_client_protocol as acp; use anyhow::Result; use editor::{ CompletionProvider, Editor, ExcerptId, code_context_menus::COMPLETION_MENU_MAX_WIDTH, @@ -144,8 +145,8 @@ impl PromptContextType { pub(crate) enum Match { File(FileMatch), Symbol(SymbolMatch), - Thread(AgentSessionInfo), - RecentThread(AgentSessionInfo), + Thread(SessionMatch), + RecentThread(SessionMatch), Fetch(SharedString), Rules(RulesContextEntry), Entry(EntryMatch), @@ -165,15 +166,19 @@ impl Match { } } +#[derive(Debug, Clone)] +pub struct SessionMatch { + session_id: acp::SessionId, + title: SharedString, +} + pub struct EntryMatch { mat: Option, entry: PromptContextEntry, } -fn session_title(session: &AgentSessionInfo) -> SharedString { - session - .title - .clone() +fn session_title(title: Option) -> SharedString { + title .filter(|title| !title.is_empty()) .unwrap_or_else(|| SharedString::new_static("New Thread")) } @@ -266,7 +271,8 @@ impl PromptCompletionProvider { } fn completion_for_thread( - thread_entry: AgentSessionInfo, + session_id: acp::SessionId, + title: Option, source_range: Range, recent: bool, source: Arc, @@ -275,9 +281,9 @@ impl PromptCompletionProvider { workspace: Entity, cx: &mut App, ) -> Completion { - let title = session_title(&thread_entry); + let title = session_title(title); let uri = MentionUri::Thread { - id: thread_entry.session_id, + id: session_id, name: title.to_string(), }; @@ -841,7 +847,15 @@ impl PromptCompletionProvider { Some(PromptContextType::Thread) => { if let Some(history) = self.history.upgrade() { - let sessions = history.read(cx).sessions().to_vec(); + let sessions = history + .read(cx) + .sessions() + .iter() + .map(|session| SessionMatch { + session_id: session.session_id.clone(), + title: session_title(session.title.clone()), + }) + .collect::>(); let search_task = filter_sessions_by_query(query, cancellation_flag, sessions, cx); cx.spawn(async move |_cx| { @@ -1018,15 +1032,18 @@ impl PromptCompletionProvider { .read(cx) .sessions() .into_iter() + .map(|session| SessionMatch { + session_id: session.session_id.clone(), + title: session_title(session.title.clone()), + }) .filter(|session| { let uri = MentionUri::Thread { id: session.session_id.clone(), - name: session_title(session).to_string(), + name: session.title.to_string(), }; !mentions.contains(&uri) }) .take(RECENT_COUNT) - .cloned() .map(Match::RecentThread), ); return Task::ready(recent); @@ -1298,7 +1315,8 @@ impl CompletionProvider for PromptCompletio ) } Match::Thread(thread) => Some(Self::completion_for_thread( - thread, + thread.session_id, + Some(thread.title), source_range.clone(), false, source.clone(), @@ -1308,7 +1326,8 @@ impl CompletionProvider for PromptCompletio cx, )), Match::RecentThread(thread) => Some(Self::completion_for_thread( - thread, + thread.session_id, + Some(thread.title), source_range.clone(), true, source.clone(), @@ -1878,9 +1897,9 @@ pub(crate) fn search_symbols( fn filter_sessions_by_query( query: String, cancellation_flag: Arc, - sessions: Vec, + sessions: Vec, cx: &mut App, -) -> Task> { +) -> Task> { if query.is_empty() { return Task::ready(sessions); } @@ -1893,10 +1912,13 @@ fn filter_sessions_by_query( async fn filter_sessions( query: String, cancellation_flag: Arc, - sessions: Vec, + sessions: Vec, executor: BackgroundExecutor, -) -> Vec { - let titles = sessions.iter().map(session_title).collect::>(); +) -> Vec { + let titles = sessions + .iter() + .map(|session| session.title.clone()) + .collect::>(); let candidates = titles .iter() .enumerate() @@ -2338,10 +2360,14 @@ mod tests { #[gpui::test] async fn test_filter_sessions_by_query(cx: &mut TestAppContext) { - let mut alpha = AgentSessionInfo::new("session-alpha"); - alpha.title = Some("Alpha Session".into()); - let mut beta = AgentSessionInfo::new("session-beta"); - beta.title = Some("Beta Session".into()); + let alpha = SessionMatch { + session_id: acp::SessionId::new("session-alpha"), + title: "Alpha Session".into(), + }; + let beta = SessionMatch { + session_id: acp::SessionId::new("session-beta"), + title: "Beta Session".into(), + }; let sessions = vec![alpha.clone(), beta]; diff --git a/crates/agent_ui/src/connection_view.rs b/crates/agent_ui/src/connection_view.rs index 84aa9e9c2b1959ba5c068e3cfa117506ac459ff0..48aa88a95ba3c1fd440c59768f9328719f02aa70 100644 --- a/crates/agent_ui/src/connection_view.rs +++ b/crates/agent_ui/src/connection_view.rs @@ -39,7 +39,7 @@ use prompt_store::{PromptId, PromptStore}; use rope::Point; use settings::{NotifyWhenAgentWaiting, Settings as _, SettingsStore}; use std::cell::RefCell; -use std::path::Path; +use std::path::{Path, PathBuf}; use std::sync::Arc; use std::time::Instant; use std::{collections::BTreeMap, rc::Rc, time::Duration}; @@ -470,7 +470,9 @@ impl ConnectedServerState { impl ConnectionView { pub fn new( agent: Rc, - resume_thread: Option, + resume_session_id: Option, + cwd: Option, + title: Option, initial_content: Option, workspace: WeakEntity, project: Entity, @@ -514,7 +516,9 @@ impl ConnectionView { prompt_store, server_state: Self::initial_state( agent.clone(), - resume_thread, + resume_session_id, + cwd, + title, project, initial_content, window, @@ -540,13 +544,23 @@ impl ConnectionView { } fn reset(&mut self, window: &mut Window, cx: &mut Context) { - let resume_thread_metadata = self + let (resume_session_id, cwd, title) = self .active_thread() - .and_then(|thread| thread.read(cx).resume_thread_metadata.clone()); + .map(|thread_view| { + let thread = thread_view.read(cx).thread.read(cx); + ( + Some(thread.session_id().clone()), + thread.cwd().cloned(), + Some(thread.title()), + ) + }) + .unwrap_or((None, None, None)); let state = Self::initial_state( self.agent.clone(), - resume_thread_metadata, + resume_session_id, + cwd, + title, self.project.clone(), None, window, @@ -570,15 +584,14 @@ impl ConnectionView { fn initial_state( agent: Rc, - resume_thread: Option, + resume_session_id: Option, + cwd: Option, + title: Option, project: Entity, initial_content: Option, window: &mut Window, cx: &mut Context, ) -> ServerState { - let session_id = resume_thread - .as_ref() - .map(|thread| thread.session_id.clone()); if project.read(cx).is_via_collab() && agent.clone().downcast::().is_none() { @@ -586,7 +599,7 @@ impl ConnectionView { error: LoadError::Other( "External agents are not yet supported in shared projects.".into(), ), - session_id, + session_id: resume_session_id.clone(), }; } let mut worktrees = project.read(cx).visible_worktrees(cx).collect::>(); @@ -608,28 +621,22 @@ impl ConnectionView { } }) .collect(); - let session_cwd = resume_thread - .as_ref() - .and_then(|resume| { - resume - .cwd - .as_ref() - .filter(|cwd| { - // Validate with the normalized path (rejects `..` traversals), - // but return the original cwd to preserve its path separators. - // On Windows, `normalize_lexically` rebuilds the path with - // backslashes via `PathBuf::push`, which would corrupt - // forward-slash Linux paths used by WSL agents. - util::paths::normalize_lexically(cwd) - .ok() - .is_some_and(|normalized| { - worktree_roots - .iter() - .any(|root| normalized.starts_with(root.as_ref())) - }) + let session_cwd = cwd + .filter(|cwd| { + // Validate with the normalized path (rejects `..` traversals), + // but return the original cwd to preserve its path separators. + // On Windows, `normalize_lexically` rebuilds the path with + // backslashes via `PathBuf::push`, which would corrupt + // forward-slash Linux paths used by WSL agents. + util::paths::normalize_lexically(cwd) + .ok() + .is_some_and(|normalized| { + worktree_roots + .iter() + .any(|root| normalized.starts_with(root.as_ref())) }) - .map(|path| Arc::from(path.as_path())) }) + .map(|path| path.into()) .or_else(|| worktree_roots.first().cloned()) .unwrap_or_else(|| paths::home_dir().as_path().into()); @@ -643,7 +650,7 @@ impl ConnectionView { ); let connect_task = agent.connect(delegate, cx); - let load_session_id = session_id.clone(); + let load_session_id = resume_session_id.clone(); let load_task = cx.spawn_in(window, async move |this, cx| { let connection = match connect_task.await { Ok(connection) => connection, @@ -666,17 +673,25 @@ impl ConnectionView { telemetry::event!("Agent Thread Started", agent = connection.telemetry_id()); let mut resumed_without_history = false; - let result = if let Some(resume) = resume_thread.clone() { + let result = if let Some(session_id) = load_session_id.clone() { cx.update(|_, cx| { if connection.supports_load_session() { - connection - .clone() - .load_session(resume, project.clone(), &session_cwd, cx) + connection.clone().load_session( + session_id, + project.clone(), + &session_cwd, + title, + cx, + ) } else if connection.supports_resume_session() { resumed_without_history = true; - connection - .clone() - .resume_session(resume, project.clone(), &session_cwd, cx) + connection.clone().resume_session( + session_id, + project.clone(), + &session_cwd, + title, + cx, + ) } else { Task::ready(Err(anyhow!(LoadError::Other( "Loading or resuming sessions is not supported by this agent.".into() @@ -732,7 +747,6 @@ impl ConnectionView { thread, conversation.clone(), resumed_without_history, - resume_thread, initial_content, window, cx, @@ -803,7 +817,7 @@ impl ConnectionView { }); LoadingView { - session_id, + session_id: resume_session_id, title: "Loadingโ€ฆ".into(), _load_task: load_task, _update_title_task: update_title_task, @@ -819,7 +833,6 @@ impl ConnectionView { thread: Entity, conversation: Entity, resumed_without_history: bool, - resume_thread: Option, initial_content: Option, window: &mut Window, cx: &mut Context, @@ -1002,7 +1015,6 @@ impl ConnectionView { prompt_capabilities, available_commands, resumed_without_history, - resume_thread, self.project.downgrade(), self.thread_store.clone(), self.history.clone(), @@ -1680,9 +1692,10 @@ impl ConnectionView { let cwd = root_dir.unwrap_or_else(|| paths::home_dir().as_path().into()); let subagent_thread_task = connected.connection.clone().load_session( - AgentSessionInfo::new(subagent_id.clone()), + subagent_id.clone(), self.project.clone(), &cwd, + None, cx, ); @@ -1704,7 +1717,6 @@ impl ConnectionView { conversation, false, None, - None, window, cx, ); @@ -2606,10 +2618,10 @@ impl ConnectionView { }) } - pub fn delete_history_entry(&mut self, entry: AgentSessionInfo, cx: &mut Context) { - let task = self.history.update(cx, |history, cx| { - history.delete_session(&entry.session_id, cx) - }); + pub fn delete_history_entry(&mut self, session_id: &acp::SessionId, cx: &mut Context) { + let task = self + .history + .update(cx, |history, cx| history.delete_session(&session_id, cx)); task.detach_and_log_err(cx); } } @@ -2856,6 +2868,8 @@ pub(crate) mod tests { Rc::new(StubAgentServer::default_response()), None, None, + None, + None, workspace.downgrade(), project, Some(thread_store), @@ -2939,7 +2953,6 @@ pub(crate) mod tests { async fn test_resume_without_history_adds_notice(cx: &mut TestAppContext) { init_test(cx); - let session = AgentSessionInfo::new(SessionId::new("resume-session")); let fs = FakeFs::new(cx.executor()); let project = Project::test(fs, [], cx).await; let (multi_workspace, cx) = @@ -2953,7 +2966,9 @@ pub(crate) mod tests { cx.new(|cx| { ConnectionView::new( Rc::new(StubAgentServer::new(ResumeOnlyAgentConnection)), - Some(session), + Some(SessionId::new("resume-session")), + None, + None, None, workspace.downgrade(), project, @@ -2997,9 +3012,6 @@ pub(crate) mod tests { let connection = CwdCapturingConnection::new(); let captured_cwd = connection.captured_cwd.clone(); - let mut session = AgentSessionInfo::new(SessionId::new("session-1")); - session.cwd = Some(PathBuf::from("/project/subdir")); - let thread_store = cx.update(|_window, cx| cx.new(|cx| ThreadStore::new(cx))); let history = cx.update(|window, cx| cx.new(|cx| ThreadHistory::new(None, window, cx))); @@ -3007,7 +3019,9 @@ pub(crate) mod tests { cx.new(|cx| { ConnectionView::new( Rc::new(StubAgentServer::new(connection)), - Some(session), + Some(SessionId::new("session-1")), + Some(PathBuf::from("/project/subdir")), + None, None, workspace.downgrade(), project, @@ -3049,9 +3063,6 @@ pub(crate) mod tests { let connection = CwdCapturingConnection::new(); let captured_cwd = connection.captured_cwd.clone(); - let mut session = AgentSessionInfo::new(SessionId::new("session-1")); - session.cwd = Some(PathBuf::from("/some/other/path")); - let thread_store = cx.update(|_window, cx| cx.new(|cx| ThreadStore::new(cx))); let history = cx.update(|window, cx| cx.new(|cx| ThreadHistory::new(None, window, cx))); @@ -3059,7 +3070,9 @@ pub(crate) mod tests { cx.new(|cx| { ConnectionView::new( Rc::new(StubAgentServer::new(connection)), - Some(session), + Some(SessionId::new("session-1")), + Some(PathBuf::from("/some/other/path")), + None, None, workspace.downgrade(), project, @@ -3101,9 +3114,6 @@ pub(crate) mod tests { let connection = CwdCapturingConnection::new(); let captured_cwd = connection.captured_cwd.clone(); - let mut session = AgentSessionInfo::new(SessionId::new("session-1")); - session.cwd = Some(PathBuf::from("/project/../outside")); - let thread_store = cx.update(|_window, cx| cx.new(|cx| ThreadStore::new(cx))); let history = cx.update(|window, cx| cx.new(|cx| ThreadHistory::new(None, window, cx))); @@ -3111,7 +3121,9 @@ pub(crate) mod tests { cx.new(|cx| { ConnectionView::new( Rc::new(StubAgentServer::new(connection)), - Some(session), + Some(SessionId::new("session-1")), + Some(PathBuf::from("/project/../outside")), + None, None, workspace.downgrade(), project, @@ -3424,6 +3436,8 @@ pub(crate) mod tests { Rc::new(agent), None, None, + None, + None, workspace1.downgrade(), project1.clone(), Some(thread_store), @@ -3612,6 +3626,8 @@ pub(crate) mod tests { Rc::new(agent), None, None, + None, + None, workspace.downgrade(), project, Some(thread_store), @@ -3792,6 +3808,7 @@ pub(crate) mod tests { AcpThread::new( None, name, + None, connection, project, action_log, @@ -3894,18 +3911,14 @@ pub(crate) mod tests { fn resume_session( self: Rc, - session: AgentSessionInfo, + session_id: acp::SessionId, project: Entity, _cwd: &Path, + _title: Option, cx: &mut App, ) -> Task>> { - let thread = build_test_thread( - self, - project, - "ResumeOnlyAgentConnection", - session.session_id, - cx, - ); + let thread = + build_test_thread(self, project, "ResumeOnlyAgentConnection", session_id, cx); Task::ready(Ok(thread)) } @@ -3965,7 +3978,7 @@ pub(crate) mod tests { fn new_session( self: Rc, project: Entity, - _cwd: &Path, + cwd: &Path, cx: &mut gpui::App, ) -> Task>> { if !*self.authenticated.lock() { @@ -3980,6 +3993,7 @@ pub(crate) mod tests { AcpThread::new( None, "AuthGatedAgent", + Some(cwd.to_path_buf()), self, project, action_log, @@ -4041,7 +4055,7 @@ pub(crate) mod tests { fn new_session( self: Rc, project: Entity, - _cwd: &Path, + cwd: &Path, cx: &mut gpui::App, ) -> Task>> { Task::ready(Ok(cx.new(|cx| { @@ -4049,6 +4063,7 @@ pub(crate) mod tests { AcpThread::new( None, "SaboteurAgentConnection", + Some(cwd.to_path_buf()), self, project, action_log, @@ -4106,7 +4121,7 @@ pub(crate) mod tests { fn new_session( self: Rc, project: Entity, - _cwd: &Path, + cwd: &Path, cx: &mut gpui::App, ) -> Task>> { Task::ready(Ok(cx.new(|cx| { @@ -4114,6 +4129,7 @@ pub(crate) mod tests { AcpThread::new( None, "RefusalAgentConnection", + Some(cwd.to_path_buf()), self, project, action_log, @@ -4189,6 +4205,7 @@ pub(crate) mod tests { AcpThread::new( None, "CwdCapturingConnection", + Some(cwd.to_path_buf()), self.clone(), project, action_log, @@ -4211,9 +4228,10 @@ pub(crate) mod tests { fn load_session( self: Rc, - session: AgentSessionInfo, + session_id: acp::SessionId, project: Entity, cwd: &Path, + _title: Option, cx: &mut App, ) -> Task>> { *self.captured_cwd.lock() = Some(cwd.to_path_buf()); @@ -4222,10 +4240,11 @@ pub(crate) mod tests { AcpThread::new( None, "CwdCapturingConnection", + Some(cwd.to_path_buf()), self.clone(), project, action_log, - session.session_id, + session_id, watch::Receiver::constant( acp::PromptCapabilities::new() .image(true) @@ -4327,6 +4346,8 @@ pub(crate) mod tests { Rc::new(StubAgentServer::new(connection.as_ref().clone())), None, None, + None, + None, workspace.downgrade(), project.clone(), Some(thread_store.clone()), @@ -6036,6 +6057,7 @@ pub(crate) mod tests { AcpThread::new( parent_session_id, "Test Thread", + None, connection, project, action_log, diff --git a/crates/agent_ui/src/connection_view/thread_view.rs b/crates/agent_ui/src/connection_view/thread_view.rs index 3397c619b7fc6544177ba52e9e71c887c74180cc..1c3b8ba244d895ba3ab2e6473f6cbe33ddae550b 100644 --- a/crates/agent_ui/src/connection_view/thread_view.rs +++ b/crates/agent_ui/src/connection_view/thread_view.rs @@ -247,7 +247,6 @@ pub struct ThreadView { pub is_loading_contents: bool, pub new_server_version_available: Option, pub resumed_without_history: bool, - pub resume_thread_metadata: Option, pub _cancel_task: Option>, _save_task: Option>, _draft_resolve_task: Option>, @@ -307,7 +306,6 @@ impl ThreadView { prompt_capabilities: Rc>, available_commands: Rc>>, resumed_without_history: bool, - resume_thread_metadata: Option, project: WeakEntity, thread_store: Option>, history: Entity, @@ -347,8 +345,8 @@ impl ThreadView { ); if let Some(content) = initial_content { match content { - AgentInitialContent::ThreadSummary(entry) => { - editor.insert_thread_summary(entry, window, cx); + AgentInitialContent::ThreadSummary { session_id, title } => { + editor.insert_thread_summary(session_id, title, window, cx); } AgentInitialContent::ContentBlock { blocks, @@ -439,7 +437,6 @@ impl ThreadView { prompt_capabilities, available_commands, resumed_without_history, - resume_thread_metadata, _subscriptions: subscriptions, permission_dropdown_handle: PopoverMenuHandle::default(), thread_retry_status: None, @@ -1772,18 +1769,7 @@ impl ThreadView { }) .await?; - let thread_metadata = AgentSessionInfo { - session_id, - cwd: None, - title: Some(format!("๐Ÿ”— {}", response.title).into()), - updated_at: Some(chrono::Utc::now()), - meta: None, - }; - - this.update_in(cx, |this, window, cx| { - this.resume_thread_metadata = Some(thread_metadata); - server_view.update(cx, |server_view, cx| server_view.reset(window, cx)); - })?; + server_view.update_in(cx, |server_view, window, cx| server_view.reset(window, cx))?; this.update_in(cx, |this, _window, cx| { if let Some(workspace) = this.workspace.upgrade() { @@ -7906,17 +7892,7 @@ pub(crate) fn open_link( MentionUri::Thread { id, name } => { if let Some(panel) = workspace.panel::(cx) { panel.update(cx, |panel, cx| { - panel.open_thread( - AgentSessionInfo { - session_id: id, - cwd: None, - title: Some(name.into()), - updated_at: None, - meta: None, - }, - window, - cx, - ) + panel.open_thread(id, None, Some(name.into()), window, cx) }); } } diff --git a/crates/agent_ui/src/message_editor.rs b/crates/agent_ui/src/message_editor.rs index 6ce0b7e356dc75f1c3d4db0f318d1978a37d00cc..cee6725cd15c15f4f39ad5e53be5578f5f5cc3d8 100644 --- a/crates/agent_ui/src/message_editor.rs +++ b/crates/agent_ui/src/message_editor.rs @@ -10,7 +10,7 @@ use crate::{ Mention, MentionImage, MentionSet, insert_crease_for_mention, paste_images_as_context, }, }; -use acp_thread::{AgentSessionInfo, MentionUri}; +use acp_thread::MentionUri; use agent::ThreadStore; use agent_client_protocol as acp; use anyhow::{Result, anyhow}; @@ -301,7 +301,8 @@ impl MessageEditor { pub fn insert_thread_summary( &mut self, - thread: AgentSessionInfo, + session_id: acp::SessionId, + title: Option, window: &mut Window, cx: &mut Context, ) { @@ -311,13 +312,11 @@ impl MessageEditor { let Some(workspace) = self.workspace.upgrade() else { return; }; - let thread_title = thread - .title - .clone() + let thread_title = title .filter(|title| !title.is_empty()) .unwrap_or_else(|| SharedString::new_static("New Thread")); let uri = MentionUri::Thread { - id: thread.session_id, + id: session_id, name: thread_title.to_string(), }; let content = format!("{}\n", uri.as_link()); @@ -1571,7 +1570,7 @@ fn find_matching_bracket(text: &str, open: char, close: char) -> Option { mod tests { use std::{cell::RefCell, ops::Range, path::Path, rc::Rc, sync::Arc}; - use acp_thread::{AgentSessionInfo, MentionUri}; + use acp_thread::MentionUri; use agent::{ThreadStore, outline}; use agent_client_protocol as acp; use editor::{ @@ -2811,14 +2810,8 @@ mod tests { let history = cx.update(|window, cx| cx.new(|cx| crate::ThreadHistory::new(None, window, cx))); - // Create a thread metadata to insert as summary - let thread_metadata = AgentSessionInfo { - session_id: acp::SessionId::new("thread-123"), - cwd: None, - title: Some("Previous Conversation".into()), - updated_at: Some(chrono::Utc::now()), - meta: None, - }; + let session_id = acp::SessionId::new("thread-123"); + let title = Some("Previous Conversation".into()); let message_editor = cx.update(|window, cx| { cx.new(|cx| { @@ -2839,17 +2832,17 @@ mod tests { window, cx, ); - editor.insert_thread_summary(thread_metadata.clone(), window, cx); + editor.insert_thread_summary(session_id.clone(), title.clone(), window, cx); editor }) }); // Construct expected values for verification let expected_uri = MentionUri::Thread { - id: thread_metadata.session_id.clone(), - name: thread_metadata.title.as_ref().unwrap().to_string(), + id: session_id.clone(), + name: title.as_ref().unwrap().to_string(), }; - let expected_title = thread_metadata.title.as_ref().unwrap(); + let expected_title = title.as_ref().unwrap(); let expected_link = format!("[@{}]({})", expected_title, expected_uri.to_uri()); message_editor.read_with(cx, |editor, cx| { @@ -2893,14 +2886,6 @@ mod tests { let history = cx.update(|window, cx| cx.new(|cx| crate::ThreadHistory::new(None, window, cx))); - let thread_metadata = AgentSessionInfo { - session_id: acp::SessionId::new("thread-123"), - cwd: None, - title: Some("Previous Conversation".into()), - updated_at: Some(chrono::Utc::now()), - meta: None, - }; - let message_editor = cx.update(|window, cx| { cx.new(|cx| { let mut editor = MessageEditor::new( @@ -2920,7 +2905,12 @@ mod tests { window, cx, ); - editor.insert_thread_summary(thread_metadata, window, cx); + editor.insert_thread_summary( + acp::SessionId::new("thread-123"), + Some("Previous Conversation".into()), + window, + cx, + ); editor }) }); diff --git a/crates/agent_ui/src/thread_history.rs b/crates/agent_ui/src/thread_history.rs index 8f8488cb94f94e036b37ef31c9c588740cd6cf02..6601616e9f2ef447beb448f2753460fa7c380fa6 100644 --- a/crates/agent_ui/src/thread_history.rs +++ b/crates/agent_ui/src/thread_history.rs @@ -948,12 +948,12 @@ impl RenderOnce for HistoryEntryElement { }) .on_click({ let thread_view = self.thread_view.clone(); - let entry = self.entry.clone(); + let session_id = self.entry.session_id.clone(); move |_event, _window, cx| { if let Some(thread_view) = thread_view.upgrade() { thread_view.update(cx, |thread_view, cx| { - thread_view.delete_history_entry(entry.clone(), cx); + thread_view.delete_history_entry(&session_id, cx); }); } } @@ -973,7 +973,13 @@ impl RenderOnce for HistoryEntryElement { { if let Some(panel) = workspace.read(cx).panel::(cx) { panel.update(cx, |panel, cx| { - panel.load_agent_thread(entry.clone(), window, cx); + panel.load_agent_thread( + entry.session_id.clone(), + entry.cwd.clone(), + entry.title.clone(), + window, + cx, + ); }); } } diff --git a/crates/agent_ui/src/ui/mention_crease.rs b/crates/agent_ui/src/ui/mention_crease.rs index 0a61b8e4ef2ec69714f158a72f83cc0528cc8a8f..8b813ef7e40c2afe91b98600b9d1146d4751d48b 100644 --- a/crates/agent_ui/src/ui/mention_crease.rs +++ b/crates/agent_ui/src/ui/mention_crease.rs @@ -269,24 +269,13 @@ fn open_thread( cx: &mut Context, ) { use crate::AgentPanel; - use acp_thread::AgentSessionInfo; let Some(panel) = workspace.panel::(cx) else { return; }; panel.update(cx, |panel, cx| { - panel.load_agent_thread( - AgentSessionInfo { - session_id: id, - cwd: None, - title: Some(name.into()), - updated_at: None, - meta: None, - }, - window, - cx, - ) + panel.load_agent_thread(id, None, Some(name.into()), window, cx) }); } diff --git a/crates/sidebar/src/sidebar.rs b/crates/sidebar/src/sidebar.rs index 7f1512ee549ce7acc7094ee86ccc233443cd6eac..3bb3ea9ea44efe2cf57a4d021b0a1755ac3b3681 100644 --- a/crates/sidebar/src/sidebar.rs +++ b/crates/sidebar/src/sidebar.rs @@ -1013,7 +1013,13 @@ impl Sidebar { if let Some(agent_panel) = workspace.read(cx).panel::(cx) { agent_panel.update(cx, |panel, cx| { - panel.load_agent_thread(session_info, window, cx); + panel.load_agent_thread( + session_info.session_id, + session_info.cwd, + session_info.title, + window, + cx, + ); }); } } diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index 4c188042482443ea0df59096884194f0740fcda1..062d0bd1f956b959f8ff3cabc6d4a44fbd5a3a7a 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -979,21 +979,19 @@ fn handle_open_request(request: OpenRequest, app_state: Arc, cx: &mut }) .await?; - let thread_metadata = acp_thread::AgentSessionInfo { - session_id, - cwd: None, - title: Some(format!("๐Ÿ”— {}", response.title).into()), - updated_at: Some(chrono::Utc::now()), - meta: None, - }; - let sharer_username = response.sharer_username.clone(); multi_workspace.update(cx, |_, window, cx| { workspace.update(cx, |workspace, cx| { if let Some(panel) = workspace.panel::(cx) { panel.update(cx, |panel, cx| { - panel.open_thread(thread_metadata, window, cx); + panel.open_thread( + session_id, + None, + Some(format!("๐Ÿ”— {}", response.title).into()), + window, + cx, + ); }); panel.focus_handle(cx).focus(window, cx); }