diff --git a/crates/agent_ui/src/agent_panel.rs b/crates/agent_ui/src/agent_panel.rs index ba5dc488db..ac8b3f190c 100644 --- a/crates/agent_ui/src/agent_panel.rs +++ b/crates/agent_ui/src/agent_panel.rs @@ -679,6 +679,7 @@ fn thread_metadata_to_debug_json( "session_id": metadata.session_id.as_ref().map(|s| s.0.to_string()), "agent_id": metadata.agent_id.0.to_string(), "title": metadata.title.as_ref().map(|t| t.to_string()), + "title_override": metadata.title_override.as_ref().map(|t| t.to_string()), "updated_at": format_timestamp_human(&metadata.updated_at), "created_at": metadata.created_at.as_ref().map(format_timestamp_human), "interacted_at": metadata.interacted_at.as_ref().map(format_timestamp_human), @@ -5295,6 +5296,7 @@ mod tests { session_id: Some(resume_session_id.clone()), agent_id: ProjectAgentId::new("Flaky"), title: Some("Persistent chat".into()), + title_override: None, updated_at: Utc::now(), created_at: Some(Utc::now()), interacted_at: None, diff --git a/crates/agent_ui/src/conversation_view.rs b/crates/agent_ui/src/conversation_view.rs index 3a4cd03042..73b1d36b21 100644 --- a/crates/agent_ui/src/conversation_view.rs +++ b/crates/agent_ui/src/conversation_view.rs @@ -792,7 +792,7 @@ impl ConversationView { .and_then(|id| { let store = ThreadMetadataStore::try_global(cx)?; let entry = store.read(cx).entry_by_session(id)?; - Some((Some(entry.folder_paths().clone()), entry.title.clone())) + Some((Some(entry.folder_paths().clone()), entry.title())) }) .unwrap_or((None, None)); (session_id, work_dirs, title) @@ -1186,6 +1186,7 @@ impl ConversationView { let weak = cx.weak_entity(); cx.new(|cx| { ThreadView::new( + self.thread_id, thread, conversation, weak, @@ -1614,7 +1615,14 @@ impl ConversationView { ); } AcpThreadEvent::TitleUpdated => { - if let Some(title) = thread.read(cx).title() + let override_title = ThreadMetadataStore::try_global(cx).and_then(|store| { + store + .read(cx) + .entry(self.thread_id) + .and_then(|m| m.title_override.clone()) + }); + let title = override_title.or_else(|| thread.read(cx).title()); + if let Some(title) = title && let Some(active_thread) = self.thread_view(&session_id) { let title_editor = active_thread.read(cx).title_editor.clone(); @@ -3589,6 +3597,7 @@ pub(crate) mod tests { session_id: Some(resume_session_id.clone()), agent_id: ProjectAgentId::new("Flaky"), title: Some(stored_title.clone()), + title_override: None, updated_at: Utc::now(), created_at: Some(Utc::now()), interacted_at: None, @@ -7119,24 +7128,6 @@ pub(crate) mod tests { }); } - #[gpui::test] - async fn test_title_editor_is_read_only_when_set_title_unsupported(cx: &mut TestAppContext) { - init_test(cx); - - let (conversation_view, cx) = - setup_conversation_view(StubAgentServer::new(ResumeOnlyAgentConnection), cx).await; - - let active = active_thread(&conversation_view, cx); - let title_editor = cx.read(|cx| active.read(cx).title_editor.clone()); - - title_editor.read_with(cx, |editor, cx| { - assert!( - editor.read_only(cx), - "Title editor should be read-only when the connection does not support set_title" - ); - }); - } - #[gpui::test] async fn test_max_tokens_error_is_rendered(cx: &mut TestAppContext) { init_test(cx); diff --git a/crates/agent_ui/src/conversation_view/thread_view.rs b/crates/agent_ui/src/conversation_view/thread_view.rs index c897139294..81d6fce6cc 100644 --- a/crates/agent_ui/src/conversation_view/thread_view.rs +++ b/crates/agent_ui/src/conversation_view/thread_view.rs @@ -1,6 +1,7 @@ use crate::{ DEFAULT_THREAD_TITLE, SelectPermissionGranularity, agent_configuration::configure_context_server_modal::default_markdown_style, + thread_metadata_store::{ThreadId, ThreadMetadataStore}, }; use agent_client_protocol::schema as acp; use std::cell::RefCell; @@ -264,6 +265,7 @@ impl PermissionSelection { } pub struct ThreadView { + pub(crate) root_thread_id: ThreadId, pub session_id: acp::SessionId, pub parent_session_id: Option, pub thread: Entity, @@ -353,6 +355,7 @@ pub struct TurnFields { impl ThreadView { pub(crate) fn new( + root_thread_id: ThreadId, thread: Entity, conversation: Entity, server_view: WeakEntity, @@ -438,15 +441,17 @@ impl ThreadView { && agent_id.as_ref() == "Codex"; let title_editor = { - let can_edit = thread.update(cx, |thread, cx| thread.can_set_title(cx)); + let metadata = ThreadMetadataStore::try_global(cx) + .and_then(|store| store.read(cx).entry(root_thread_id).cloned()); + let initial_title = if parent_session_id.is_none() { + metadata.as_ref().and_then(|m| m.title()) + } else { + thread.read(cx).title() + } + .unwrap_or_else(|| DEFAULT_THREAD_TITLE.into()); let editor = cx.new(|cx| { let mut editor = Editor::single_line(window, cx); - if let Some(title) = thread.read(cx).title() { - editor.set_text(title, window, cx); - } else { - editor.set_text(DEFAULT_THREAD_TITLE, window, cx); - } - editor.set_read_only(!can_edit); + editor.set_text(initial_title, window, cx); editor }); subscriptions.push(cx.subscribe_in(&editor, window, Self::handle_title_editor_event)); @@ -490,6 +495,7 @@ impl ThreadView { })); let mut this = Self { + root_thread_id, session_id, parent_session_id, focus_handle: cx.focus_handle(), @@ -1647,11 +1653,23 @@ impl ThreadView { } let new_title = title_editor.read(cx).text(cx); + if new_title.is_empty() { + return; + } + let title = SharedString::from(new_title); + if let Some(store) = ThreadMetadataStore::try_global(cx) + && !self.is_subagent() + { + let thread_id = self.root_thread_id; + store.update(cx, |store, cx| { + store.set_title_override(thread_id, title.clone(), cx); + }); + } thread.update(cx, |thread, cx| { - thread - .set_title(new_title.into(), cx) - .detach_and_log_err(cx); - }) + if thread.can_set_title(cx) { + thread.set_title(title, cx).detach_and_log_err(cx); + } + }); } EditorEvent::Blurred => { if title_editor.read(cx).text(cx).is_empty() { diff --git a/crates/agent_ui/src/thread_import.rs b/crates/agent_ui/src/thread_import.rs index 1bee86602e..c2f5216412 100644 --- a/crates/agent_ui/src/thread_import.rs +++ b/crates/agent_ui/src/thread_import.rs @@ -590,6 +590,7 @@ fn collect_importable_threads( session_id: Some(session.session_id), agent_id: agent_id.clone(), title: session.title, + title_override: None, updated_at: session.updated_at.unwrap_or_else(|| Utc::now()), created_at: session.created_at, interacted_at: None, diff --git a/crates/agent_ui/src/thread_metadata_store.rs b/crates/agent_ui/src/thread_metadata_store.rs index 127cd9ee6a..591e65a9d7 100644 --- a/crates/agent_ui/src/thread_metadata_store.rs +++ b/crates/agent_ui/src/thread_metadata_store.rs @@ -135,6 +135,7 @@ fn migrate_thread_metadata(cx: &mut App) -> Task> { } else { Some(entry.title) }, + title_override: None, updated_at: entry.updated_at, created_at: entry.created_at, interacted_at: None, @@ -310,6 +311,10 @@ pub struct ThreadMetadata { pub session_id: Option, pub agent_id: AgentId, pub title: Option, + /// User-supplied title that takes precedence over `title`. Set when the + /// user renames a thread, so that subsequent agent-driven title updates + /// (e.g. from `SessionInfoUpdate`) don't clobber the user's choice. + pub title_override: Option, pub updated_at: DateTime, pub created_at: Option>, /// When a user last interacted to send a message (including queueing). @@ -328,11 +333,14 @@ impl ThreadMetadata { } pub fn display_title(&self) -> SharedString { - self.title - .clone() + self.title() .unwrap_or_else(|| crate::DEFAULT_THREAD_TITLE.into()) } + pub fn title(&self) -> Option { + self.title_override.clone().or_else(|| self.title.clone()) + } + pub fn folder_paths(&self) -> &PathList { self.worktree_paths.folder_path_list() } @@ -422,7 +430,7 @@ impl From<&ThreadMetadata> for acp_thread::AgentSessionInfo { Self { session_id, work_dirs: Some(meta.folder_paths().clone()), - title: meta.title.clone(), + title: meta.title(), updated_at: Some(meta.updated_at), created_at: meta.created_at, meta: None, @@ -674,6 +682,26 @@ impl ThreadMetadataStore { cx.notify(); } + /// Set or clear the user-supplied title for a thread. + pub fn set_title_override( + &mut self, + thread_id: ThreadId, + title_override: SharedString, + cx: &mut Context, + ) { + let Some(existing) = self.entry(thread_id) else { + return; + }; + if existing.title_override.as_ref() == Some(&title_override) { + return; + } + let metadata = ThreadMetadata { + title_override: Some(title_override), + ..existing.clone() + }; + self.save(metadata, cx); + } + fn save_internal(&mut self, metadata: ThreadMetadata) { if let Some(thread) = self.threads.get(&metadata.thread_id) { if thread.folder_paths() != metadata.folder_paths() { @@ -1196,6 +1224,7 @@ impl ThreadMetadataStore { Some(thread_ref.session_id().clone()) }; let title = thread_ref.title(); + let title_override = existing_thread.and_then(|t| t.title_override.clone()); let updated_at = Utc::now(); @@ -1249,6 +1278,7 @@ impl ThreadMetadataStore { session_id, agent_id, title, + title_override, created_at: Some(created_at), interacted_at, updated_at, @@ -1364,6 +1394,9 @@ impl Domain for ThreadMetadataDb { sql!( ALTER TABLE sidebar_threads ADD COLUMN interacted_at TEXT; ), + sql!( + ALTER TABLE sidebar_threads ADD COLUMN title_override TEXT; + ), ]; } @@ -1380,7 +1413,7 @@ impl ThreadMetadataDb { const LIST_QUERY: &str = "SELECT thread_id, session_id, agent_id, title, updated_at, \ created_at, interacted_at, folder_paths, folder_paths_order, archived, main_worktree_paths, \ - main_worktree_paths_order, remote_connection \ + main_worktree_paths_order, remote_connection, title_override \ FROM sidebar_threads \ ORDER BY updated_at DESC"; @@ -1430,12 +1463,13 @@ impl ThreadMetadataDb { .map(serde_json::to_string) .transpose() .context("serialize thread metadata remote connection")?; + let title_override = row.title_override.as_ref().map(|t| t.to_string()); let thread_id = row.thread_id; let archived = row.archived; self.write(move |conn| { - let sql = "INSERT INTO sidebar_threads(thread_id, session_id, agent_id, title, updated_at, created_at, interacted_at, folder_paths, folder_paths_order, archived, main_worktree_paths, main_worktree_paths_order, remote_connection) \ - VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13) \ + let sql = "INSERT INTO sidebar_threads(thread_id, session_id, agent_id, title, updated_at, created_at, interacted_at, folder_paths, folder_paths_order, archived, main_worktree_paths, main_worktree_paths_order, remote_connection, title_override) \ + VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13, ?14) \ ON CONFLICT(thread_id) DO UPDATE SET \ session_id = excluded.session_id, \ agent_id = excluded.agent_id, \ @@ -1448,7 +1482,8 @@ impl ThreadMetadataDb { archived = excluded.archived, \ main_worktree_paths = excluded.main_worktree_paths, \ main_worktree_paths_order = excluded.main_worktree_paths_order, \ - remote_connection = excluded.remote_connection"; + remote_connection = excluded.remote_connection, \ + title_override = excluded.title_override"; let mut stmt = Statement::prepare(conn, sql)?; let mut i = stmt.bind(&thread_id, 1)?; i = stmt.bind(&session_id, i)?; @@ -1462,7 +1497,8 @@ impl ThreadMetadataDb { i = stmt.bind(&archived, i)?; i = stmt.bind(&main_worktree_paths, i)?; i = stmt.bind(&main_worktree_paths_order, i)?; - stmt.bind(&remote_connection, i)?; + i = stmt.bind(&remote_connection, i)?; + stmt.bind(&title_override, i)?; stmt.exec() }) .await @@ -1619,6 +1655,7 @@ impl Column for ThreadMetadata { Column::column(statement, next)?; let (remote_connection_json, next): (Option, i32) = Column::column(statement, next)?; + let (title_override, next): (Option, i32) = Column::column(statement, next)?; let agent_id = agent_id .map(|id| AgentId::new(id)) @@ -1676,6 +1713,9 @@ impl Column for ThreadMetadata { } else { Some(title.into()) }, + title_override: title_override + .filter(|t| !t.is_empty()) + .map(SharedString::from), updated_at, created_at, interacted_at, @@ -1765,6 +1805,7 @@ mod tests { } else { Some(title.to_string().into()) }, + title_override: None, updated_at, created_at: Some(updated_at), interacted_at: None, @@ -1821,6 +1862,83 @@ mod tests { cx.run_until_parked(); } + #[test] + fn test_thread_metadata_title_prefers_override() { + let mut metadata = make_metadata( + "session-1", + "Agent Generated Title", + Utc::now(), + PathList::default(), + ); + metadata.title_override = Some("User Title".into()); + + assert_eq!(metadata.title().as_deref(), Some("User Title")); + assert_eq!(metadata.display_title().as_ref(), "User Title"); + + metadata.title_override = None; + assert_eq!(metadata.title().as_deref(), Some("Agent Generated Title")); + assert_eq!(metadata.display_title().as_ref(), "Agent Generated Title"); + } + + #[gpui::test] + async fn test_database_round_trips_title_override(_cx: &mut TestAppContext) { + let now = Utc::now(); + let mut metadata = make_metadata( + "session-1", + "Agent Generated Title", + now, + PathList::new(&[Path::new("/project-a")]), + ); + metadata.title_override = Some("User Title".into()); + + let thread = std::thread::current(); + let test_name = thread.name().unwrap_or("unknown_test"); + let db_name = format!("THREAD_METADATA_DB_{}", test_name); + let db = ThreadMetadataDb(gpui::block_on(db::open_test_db::( + &db_name, + ))); + + db.save(metadata).await.unwrap(); + + let rows = db.list().unwrap(); + assert_eq!(rows.len(), 1); + assert_eq!(rows[0].title.as_deref(), Some("Agent Generated Title")); + assert_eq!(rows[0].title_override.as_deref(), Some("User Title")); + assert_eq!(rows[0].title().as_deref(), Some("User Title")); + } + + #[gpui::test] + async fn test_store_set_title_override_updates_cached_metadata(cx: &mut TestAppContext) { + init_test(cx); + + let metadata = make_metadata( + "session-1", + "Agent Generated Title", + Utc::now(), + PathList::default(), + ); + let thread_id = metadata.thread_id; + + cx.update(|cx| { + let store = ThreadMetadataStore::global(cx); + store.update(cx, |store, cx| { + store.save(metadata, cx); + store.set_title_override(thread_id, "User Title".into(), cx); + }); + }); + + cx.run_until_parked(); + + cx.update(|cx| { + let store = ThreadMetadataStore::global(cx); + let store = store.read(cx); + let metadata = store.entry(thread_id).expect("metadata should be cached"); + assert_eq!(metadata.title.as_deref(), Some("Agent Generated Title")); + assert_eq!(metadata.title_override.as_deref(), Some("User Title")); + assert_eq!(metadata.display_title().as_ref(), "User Title"); + }); + } + #[gpui::test] async fn test_store_initializes_cache_from_database(cx: &mut TestAppContext) { let first_paths = PathList::new(&[Path::new("/project-a")]); @@ -1947,6 +2065,7 @@ mod tests { session_id: Some(acp::SessionId::new("session-1")), agent_id: agent::ZED_AGENT_ID.clone(), title: Some("First Thread".into()), + title_override: None, updated_at: updated_time, created_at: Some(updated_time), interacted_at: None, @@ -2031,6 +2150,7 @@ mod tests { session_id: Some(acp::SessionId::new("a-session-0")), agent_id: agent::ZED_AGENT_ID.clone(), title: Some("Existing Metadata".into()), + title_override: None, updated_at: now - chrono::Duration::seconds(10), created_at: Some(now - chrono::Duration::seconds(10)), interacted_at: None, @@ -2156,6 +2276,7 @@ mod tests { session_id: Some(acp::SessionId::new("existing-session")), agent_id: agent::ZED_AGENT_ID.clone(), title: Some("Existing Metadata".into()), + title_override: None, updated_at: existing_updated_at, created_at: Some(existing_updated_at), interacted_at: None, @@ -2900,6 +3021,7 @@ mod tests { session_id: Some(acp::SessionId::new("local-linked")), agent_id: agent::ZED_AGENT_ID.clone(), title: Some("Local Linked".into()), + title_override: None, updated_at: now, created_at: Some(now), interacted_at: None, @@ -2913,6 +3035,7 @@ mod tests { session_id: Some(acp::SessionId::new("remote-linked")), agent_id: agent::ZED_AGENT_ID.clone(), title: Some("Remote Linked".into()), + title_override: None, updated_at: now - chrono::Duration::seconds(1), created_at: Some(now - chrono::Duration::seconds(1)), interacted_at: None, diff --git a/crates/sidebar/src/sidebar_tests.rs b/crates/sidebar/src/sidebar_tests.rs index 9b906f0c12..a06af5d464 100644 --- a/crates/sidebar/src/sidebar_tests.rs +++ b/crates/sidebar/src/sidebar_tests.rs @@ -377,6 +377,7 @@ fn save_thread_metadata( session_id: Some(session_id), agent_id: agent::ZED_AGENT_ID.clone(), title, + title_override: None, updated_at, created_at, interacted_at, @@ -412,6 +413,7 @@ fn save_thread_metadata_with_main_paths( session_id: Some(session_id), agent_id: agent::ZED_AGENT_ID.clone(), title: Some(title), + title_override: None, updated_at, created_at: None, interacted_at: None, @@ -891,6 +893,7 @@ async fn test_visible_entries_as_strings(cx: &mut TestAppContext) { agent_id: AgentId::new("zed-agent"), worktree_paths: WorktreePaths::default(), title: Some("Completed thread".into()), + title_override: None, updated_at: Utc::now(), created_at: Some(Utc::now()), interacted_at: None, @@ -917,6 +920,7 @@ async fn test_visible_entries_as_strings(cx: &mut TestAppContext) { agent_id: AgentId::new("zed-agent"), worktree_paths: WorktreePaths::default(), title: Some("Running thread".into()), + title_override: None, updated_at: Utc::now(), created_at: Some(Utc::now()), interacted_at: None, @@ -943,6 +947,7 @@ async fn test_visible_entries_as_strings(cx: &mut TestAppContext) { agent_id: AgentId::new("zed-agent"), worktree_paths: WorktreePaths::default(), title: Some("Error thread".into()), + title_override: None, updated_at: Utc::now(), created_at: Some(Utc::now()), interacted_at: None, @@ -970,6 +975,7 @@ async fn test_visible_entries_as_strings(cx: &mut TestAppContext) { agent_id: AgentId::new("zed-agent"), worktree_paths: WorktreePaths::default(), title: Some("Waiting thread".into()), + title_override: None, updated_at: Utc::now(), created_at: Some(Utc::now()), interacted_at: None, @@ -997,6 +1003,7 @@ async fn test_visible_entries_as_strings(cx: &mut TestAppContext) { agent_id: AgentId::new("zed-agent"), worktree_paths: WorktreePaths::default(), title: Some("Notified thread".into()), + title_override: None, updated_at: Utc::now(), created_at: Some(Utc::now()), interacted_at: None, @@ -4986,6 +4993,7 @@ async fn test_sidebar_keeps_multi_root_thread_with_stale_main_paths(cx: &mut Tes session_id: Some(session_id.clone()), agent_id: agent::ZED_AGENT_ID.clone(), title: Some("Stale Multi-Root Thread".into()), + title_override: None, updated_at: Utc::now(), created_at: None, interacted_at: None, @@ -5066,6 +5074,7 @@ async fn test_activate_archived_thread_with_saved_paths_activates_matching_works session_id: Some(session_id.clone()), agent_id: agent::ZED_AGENT_ID.clone(), title: Some("Archived Thread".into()), + title_override: None, updated_at: Utc::now(), created_at: None, interacted_at: None, @@ -5135,6 +5144,7 @@ async fn test_activate_archived_thread_cwd_fallback_with_matching_workspace( session_id: Some(acp::SessionId::new(Arc::from("unknown-session"))), agent_id: agent::ZED_AGENT_ID.clone(), title: Some("CWD Thread".into()), + title_override: None, updated_at: Utc::now(), created_at: None, interacted_at: None, @@ -5202,6 +5212,7 @@ async fn test_activate_archived_thread_no_paths_no_cwd_uses_active_workspace( session_id: Some(acp::SessionId::new(Arc::from("no-context-session"))), agent_id: agent::ZED_AGENT_ID.clone(), title: Some("Contextless Thread".into()), + title_override: None, updated_at: Utc::now(), created_at: None, interacted_at: None, @@ -5259,6 +5270,7 @@ async fn test_activate_archived_thread_saved_paths_opens_new_workspace(cx: &mut session_id: Some(session_id.clone()), agent_id: agent::ZED_AGENT_ID.clone(), title: Some("New WS Thread".into()), + title_override: None, updated_at: Utc::now(), created_at: None, interacted_at: None, @@ -5315,6 +5327,7 @@ async fn test_activate_archived_thread_reuses_workspace_in_another_window(cx: &m session_id: Some(session_id.clone()), agent_id: agent::ZED_AGENT_ID.clone(), title: Some("Cross Window Thread".into()), + title_override: None, updated_at: Utc::now(), created_at: None, interacted_at: None, @@ -5393,6 +5406,7 @@ async fn test_activate_archived_thread_reuses_workspace_in_another_window_with_t session_id: Some(session_id.clone()), agent_id: agent::ZED_AGENT_ID.clone(), title: Some("Cross Window Thread".into()), + title_override: None, updated_at: Utc::now(), created_at: None, interacted_at: None, @@ -5475,6 +5489,7 @@ async fn test_activate_archived_thread_prefers_current_window_for_matching_paths session_id: Some(session_id.clone()), agent_id: agent::ZED_AGENT_ID.clone(), title: Some("Current Window Thread".into()), + title_override: None, updated_at: Utc::now(), created_at: None, interacted_at: None, @@ -6412,6 +6427,7 @@ async fn test_archive_last_worktree_thread_not_blocked_by_remote_thread_at_same_ session_id: Some(acp::SessionId::new(Arc::from("remote-wt-thread"))), agent_id: agent::ZED_AGENT_ID.clone(), title: Some("Remote Worktree Thread".into()), + title_override: None, updated_at: chrono::TimeZone::with_ymd_and_hms(&Utc, 2024, 1, 1, 0, 0, 0).unwrap(), created_at: None, interacted_at: None, @@ -7226,6 +7242,7 @@ async fn test_unarchive_first_thread_in_group_does_not_create_spurious_draft( session_id: Some(session_id.clone()), agent_id: agent::ZED_AGENT_ID.clone(), title: Some("Unarchived Thread".into()), + title_override: None, updated_at: Utc::now(), created_at: None, interacted_at: None, @@ -7319,6 +7336,7 @@ async fn test_unarchive_into_new_workspace_does_not_create_duplicate_real_thread session_id: Some(session_id.clone()), agent_id: agent::ZED_AGENT_ID.clone(), title: Some("Unarchived Thread".into()), + title_override: None, updated_at: Utc::now(), created_at: None, interacted_at: None, @@ -7545,6 +7563,7 @@ async fn test_unarchive_into_inactive_existing_workspace_does_not_leave_active_d session_id: Some(session_id.clone()), agent_id: agent::ZED_AGENT_ID.clone(), title: Some("Restored In Inactive Workspace".into()), + title_override: None, updated_at: Utc::now(), created_at: None, interacted_at: None, @@ -8395,6 +8414,7 @@ async fn test_unarchive_linked_worktree_thread_into_project_group_shows_only_res session_id: Some(session_id.clone()), agent_id: agent::ZED_AGENT_ID.clone(), title: Some("Unarchived Linked Thread".into()), + title_override: None, updated_at: Utc::now(), created_at: None, interacted_at: None, @@ -8945,6 +8965,7 @@ async fn test_legacy_thread_with_canonical_path_opens_main_repo_workspace(cx: &m session_id: Some(legacy_session.clone()), agent_id: agent::ZED_AGENT_ID.clone(), title: Some("Legacy Main Thread".into()), + title_override: None, updated_at: chrono::TimeZone::with_ymd_and_hms(&Utc, 2024, 1, 1, 0, 0, 0).unwrap(), created_at: None, interacted_at: None, @@ -9934,6 +9955,7 @@ mod property_test { session_id: Some(session_id), agent_id: agent::ZED_AGENT_ID.clone(), title: Some(title), + title_override: None, updated_at, created_at: None, interacted_at: None, @@ -10846,6 +10868,7 @@ async fn test_remote_project_integration_does_not_briefly_render_as_separate_pro session_id: Some(remote_thread_id.clone()), agent_id: agent::ZED_AGENT_ID.clone(), title: Some("Worktree Thread".into()), + title_override: None, updated_at: chrono::TimeZone::with_ymd_and_hms(&Utc, 2024, 1, 1, 0, 0, 1).unwrap(), created_at: None, interacted_at: None, @@ -11567,6 +11590,7 @@ async fn test_remote_archive_thread_with_active_connection( session_id: Some(wt_thread_id.clone()), agent_id: agent::ZED_AGENT_ID.clone(), title: Some("Worktree Thread".into()), + title_override: None, updated_at: chrono::TimeZone::with_ymd_and_hms(&chrono::Utc, 2024, 1, 1, 0, 0, 0) .unwrap(), created_at: None,