Improve Zed agent thread history to update across all open windows (#47803)

Joseph T. Lyons created

Previously, each Agent panel created its own `ThreadStore` instance, so
thread history updates wouldn't sync between open windows. Users had to
close and reopen windows to see threads created in other windows.

Now `ThreadStore` is initialized once as a global and shared across all
windows. When any window saves a thread, ThreadStore::reload() calls
cx.notify(), which notifies all NativeAgentSessionList observers,
causing all windows' history views to refresh.

Note: This only works for the native Zed agent. It also is only
improving the history view (thread titles)β€”this PR does not sync edits
between multiple windows when the same thread is open in each.

Release Notes:

- Improved Zed agent thread history to update across all open windows

Change summary

crates/agent/src/thread_store.rs       | 15 ++++++++++++++-
crates/agent_ui/src/agent_panel.rs     |  2 +-
crates/agent_ui/src/agent_ui.rs        |  1 +
crates/agent_ui_v2/src/agents_panel.rs |  2 +-
4 files changed, 17 insertions(+), 3 deletions(-)

Detailed changes

crates/agent/src/thread_store.rs πŸ”—

@@ -1,10 +1,14 @@
 use crate::{DbThread, DbThreadMetadata, ThreadsDatabase};
 use agent_client_protocol as acp;
 use anyhow::{Result, anyhow};
-use gpui::{App, Context, Entity, Task, prelude::*};
+use gpui::{App, Context, Entity, Global, Task, prelude::*};
 use project::Project;
 use std::rc::Rc;
 
+struct GlobalThreadStore(Entity<ThreadStore>);
+
+impl Global for GlobalThreadStore {}
+
 // TODO: Remove once ACP thread loading is fully handled elsewhere.
 pub fn load_agent_thread(
     session_id: acp::SessionId,
@@ -37,6 +41,15 @@ pub struct ThreadStore {
 }
 
 impl ThreadStore {
+    pub fn init_global(cx: &mut App) {
+        let thread_store = cx.new(|cx| Self::new(cx));
+        cx.set_global(GlobalThreadStore(thread_store));
+    }
+
+    pub fn global(cx: &App) -> Entity<Self> {
+        cx.global::<GlobalThreadStore>().0.clone()
+    }
+
     pub fn new(cx: &mut Context<Self>) -> Self {
         let this = Self {
             threads: Vec::new(),

crates/agent_ui/src/agent_panel.rs πŸ”—

@@ -507,7 +507,7 @@ impl AgentPanel {
         let context_server_registry =
             cx.new(|cx| ContextServerRegistry::new(project.read(cx).context_server_store(), cx));
 
-        let thread_store = cx.new(|cx| ThreadStore::new(cx));
+        let thread_store = ThreadStore::global(cx);
         let acp_history = cx.new(|cx| AcpThreadHistory::new(None, window, cx));
         let text_thread_history =
             cx.new(|cx| TextThreadHistory::new(text_thread_store.clone(), window, cx));

crates/agent_ui/src/agent_ui.rs πŸ”—

@@ -256,6 +256,7 @@ pub fn init(
     is_eval: bool,
     cx: &mut App,
 ) {
+    agent::ThreadStore::init_global(cx);
     assistant_text_thread::init(client, cx);
     rules_library::init(cx);
     if !is_eval {

crates/agent_ui_v2/src/agents_panel.rs πŸ”—

@@ -124,7 +124,7 @@ impl AgentsPanel {
     ) -> Self {
         let focus_handle = cx.focus_handle();
 
-        let thread_store = cx.new(|cx| ThreadStore::new(cx));
+        let thread_store = ThreadStore::global(cx);
         let history = cx.new(|cx| AcpThreadHistory::new(None, window, cx));
 
         let history_handle = history.clone();