@@ -246,7 +246,7 @@ pub fn init(cx: &mut App) {
.and_then(|conversation| {
conversation
.read(cx)
- .active_thread()
+ .root_thread_view()
.map(|r| r.read(cx).thread.clone())
});
@@ -797,7 +797,7 @@ impl AgentPanel {
Some(
conversation
.read(cx)
- .root_thread(cx)?
+ .root_thread_view()?
.read(cx)
.thread
.read(cx)
@@ -1287,7 +1287,7 @@ impl AgentPanel {
};
if let Some(draft) = &self.draft_thread {
let agent_matches = *draft.read(cx).agent_key() == desired_agent;
- let has_editor_content = draft.read(cx).active_thread().is_some_and(|tv| {
+ let has_editor_content = draft.read(cx).root_thread_view().is_some_and(|tv| {
!tv.read(cx)
.message_editor
.read(cx)
@@ -1321,7 +1321,7 @@ impl AgentPanel {
conversation_view: &Entity<ConversationView>,
cx: &mut Context<Self>,
) {
- if let Some(acp_thread) = conversation_view.read(cx).root_acp_thread(cx) {
+ if let Some(acp_thread) = conversation_view.read(cx).root_thread(cx) {
self._draft_editor_observation = Some(cx.subscribe(
&acp_thread,
|this, _, e: &AcpThreadEvent, cx| {
@@ -1333,7 +1333,7 @@ impl AgentPanel {
} else {
let cv = conversation_view.clone();
self._draft_editor_observation = Some(cx.observe(&cv, |this, cv, cx| {
- if cv.read(cx).root_acp_thread(cx).is_some() {
+ if cv.read(cx).root_thread(cx).is_some() {
this.observe_draft_editor(&cv, cx);
}
}));
@@ -1421,7 +1421,7 @@ impl AgentPanel {
}
_ => None,
})?;
- let tv = cv.read(cx).active_thread()?;
+ let tv = cv.read(cx).root_thread_view()?;
let text = tv.read(cx).message_editor.read(cx).text(cx);
if text.trim().is_empty() {
None
@@ -1443,7 +1443,7 @@ impl AgentPanel {
_ => None,
});
let Some(cv) = cv else { return };
- let Some(tv) = cv.read(cx).active_thread() else {
+ let Some(tv) = cv.read(cx).root_thread_view() else {
return;
};
let editor = tv.read(cx).message_editor.clone();
@@ -1581,7 +1581,7 @@ impl AgentPanel {
return;
};
- let Some(active_thread) = conversation_view.read(cx).active_thread().cloned() else {
+ let Some(active_thread) = conversation_view.read(cx).root_thread_view() else {
return;
};
@@ -2051,15 +2051,14 @@ impl AgentPanel {
pub fn active_thread_view(&self, cx: &App) -> Option<Entity<ThreadView>> {
let server_view = self.active_conversation_view()?;
- server_view.read(cx).active_thread().cloned()
+ server_view.read(cx).root_thread_view()
}
pub fn active_agent_thread(&self, cx: &App) -> Option<Entity<AcpThread>> {
match &self.base_view {
- BaseView::AgentThread { conversation_view } => conversation_view
- .read(cx)
- .active_thread()
- .map(|r| r.read(cx).thread.clone()),
+ BaseView::AgentThread { conversation_view } => {
+ conversation_view.read(cx).root_thread(cx)
+ }
_ => None,
}
}
@@ -2076,7 +2075,7 @@ impl AgentPanel {
for conversation_view in conversation_views {
if *thread_id == conversation_view.read(cx).thread_id {
- if let Some(thread_view) = conversation_view.read(cx).root_thread_view(cx) {
+ if let Some(thread_view) = conversation_view.read(cx).root_thread_view() {
thread_view.update(cx, |view, cx| view.cancel_generation(cx));
return true;
}
@@ -2091,13 +2090,13 @@ impl AgentPanel {
let mut views = Vec::new();
if let Some(server_view) = self.active_conversation_view() {
- if let Some(thread_view) = server_view.read(cx).root_thread(cx) {
+ if let Some(thread_view) = server_view.read(cx).root_thread_view() {
views.push(thread_view);
}
}
for server_view in self.retained_threads.values() {
- if let Some(thread_view) = server_view.read(cx).root_thread(cx) {
+ if let Some(thread_view) = server_view.read(cx).root_thread_view() {
views.push(thread_view);
}
}
@@ -2164,7 +2163,7 @@ impl AgentPanel {
.retained_threads
.iter()
.filter(|(_id, view)| {
- let Some(thread_view) = view.read(cx).root_thread(cx) else {
+ let Some(thread_view) = view.read(cx).root_thread_view() else {
return true;
};
let thread = thread_view.read(cx).thread.read(cx);
@@ -2391,7 +2390,7 @@ impl AgentPanel {
window: &mut Window,
cx: &mut Context<Self>,
) -> Option<Subscription> {
- server_view.read(cx).active_thread().cloned().map(|tv| {
+ server_view.read(cx).root_thread_view().map(|tv| {
cx.subscribe_in(
&tv,
window,
@@ -3809,12 +3808,12 @@ impl AgentPanel {
VisibleSurface::AgentThread(conversation_view) => {
let server_view_ref = conversation_view.read(cx);
let is_generating_title = server_view_ref.as_native_thread(cx).is_some()
- && server_view_ref.root_thread(cx).map_or(false, |tv| {
+ && server_view_ref.root_thread_view().map_or(false, |tv| {
tv.read(cx).thread.read(cx).has_provisional_title()
});
if let Some(title_editor) = server_view_ref
- .root_thread(cx)
+ .root_thread_view()
.map(|r| r.read(cx).title_editor.clone())
{
if is_generating_title {
@@ -6825,7 +6824,7 @@ mod tests {
// Verify thread A's (background) work_dirs are also updated.
let updated_a_paths = panel.read_with(&cx, |panel, cx| {
let bg_view = panel.retained_threads.get(&thread_id_a).unwrap();
- let root_thread = bg_view.read(cx).root_thread(cx).unwrap();
+ let root_thread = bg_view.read(cx).root_thread_view().unwrap();
root_thread
.read(cx)
.thread
@@ -6845,7 +6844,7 @@ mod tests {
// Verify thread idle C was also updated.
let updated_c_paths = panel.read_with(&cx, |panel, cx| {
let bg_view = panel.retained_threads.get(&thread_id_c).unwrap();
- let root_thread = bg_view.read(cx).root_thread(cx).unwrap();
+ let root_thread = bg_view.read(cx).root_thread_view().unwrap();
root_thread
.read(cx)
.thread
@@ -6899,7 +6898,7 @@ mod tests {
let after_remove_a = panel.read_with(&cx, |panel, cx| {
let bg_view = panel.retained_threads.get(&thread_id_a).unwrap();
- let root_thread = bg_view.read(cx).root_thread(cx).unwrap();
+ let root_thread = bg_view.read(cx).root_thread_view().unwrap();
root_thread
.read(cx)
.thread
@@ -460,13 +460,7 @@ impl ConversationView {
&'a self,
cx: &'a App,
) -> Option<(acp::SessionId, acp::ToolCallId, &'a PermissionOptions)> {
- let session_id = self
- .active_thread()?
- .read(cx)
- .thread
- .read(cx)
- .session_id()
- .clone();
+ let session_id = self.active_thread()?.read(cx).session_id.clone();
self.as_connected()?
.conversation
.read(cx)
@@ -474,7 +468,7 @@ impl ConversationView {
}
pub fn root_thread_has_pending_tool_call(&self, cx: &App) -> bool {
- let Some(root_thread) = self.root_thread(cx) else {
+ let Some(root_thread) = self.root_thread_view() else {
return false;
};
let root_session_id = root_thread.read(cx).thread.read(cx).session_id().clone();
@@ -487,47 +481,18 @@ impl ConversationView {
})
}
- pub fn root_thread(&self, cx: &App) -> Option<Entity<ThreadView>> {
- match &self.server_state {
- ServerState::Connected(connected) => {
- let mut current = connected.active_view()?;
- while let Some(parent_session_id) =
- current.read(cx).thread.read(cx).parent_session_id()
- {
- if let Some(parent) = connected.threads.get(parent_session_id) {
- current = parent;
- } else {
- break;
- }
- }
- Some(current.clone())
- }
- _ => None,
- }
- }
-
- pub(crate) fn root_acp_thread(&self, cx: &App) -> Option<Entity<AcpThread>> {
- let connected = self.as_connected()?;
- let root_session_id = self.root_session_id.as_ref()?;
- connected
- .conversation
- .read(cx)
- .threads
- .get(root_session_id)
- .cloned()
+ pub(crate) fn root_thread(&self, cx: &App) -> Option<Entity<AcpThread>> {
+ self.root_thread_view()
+ .map(|view| view.read(cx).thread.clone())
}
- pub fn root_thread_view(&self, cx: &App) -> Option<Entity<ThreadView>> {
+ pub fn root_thread_view(&self) -> Option<Entity<ThreadView>> {
self.root_session_id
.as_ref()
- .and_then(|sid| self.thread_view(sid, cx))
+ .and_then(|id| self.thread_view(id))
}
- pub fn thread_view(
- &self,
- session_id: &acp::SessionId,
- _cx: &App,
- ) -> Option<Entity<ThreadView>> {
+ pub fn thread_view(&self, session_id: &acp::SessionId) -> Option<Entity<ThreadView>> {
let connected = self.as_connected()?;
connected.threads.get(session_id).cloned()
}
@@ -737,7 +702,7 @@ impl ConversationView {
fn reset(&mut self, window: &mut Window, cx: &mut Context<Self>) {
let (resume_session_id, cwd, title) = self
- .active_thread()
+ .root_thread_view()
.map(|thread_view| {
let tv = thread_view.read(cx);
let thread = tv.thread.read(cx);
@@ -764,7 +729,7 @@ impl ConversationView {
);
self.set_server_state(state, cx);
- if let Some(view) = self.active_thread() {
+ if let Some(view) = self.root_thread_view() {
view.update(cx, |this, cx| {
this.message_editor.update(cx, |editor, cx| {
editor.set_session_capabilities(this.session_capabilities.clone(), cx);
@@ -805,7 +770,7 @@ impl ConversationView {
let connection_entry_subscription =
cx.subscribe(&connection_entry, |this, _entry, event, cx| match event {
AgentConnectionEntryEvent::NewVersionAvailable(version) => {
- if let Some(thread) = this.active_thread() {
+ if let Some(thread) = this.root_thread_view() {
thread.update(cx, |thread, cx| {
thread.new_server_version_available = Some(version.clone());
cx.notify();
@@ -1259,7 +1224,7 @@ impl ConversationView {
}
fn handle_load_error(&mut self, err: LoadError, window: &mut Window, cx: &mut Context<Self>) {
- if let Some(view) = self.active_thread() {
+ if let Some(view) = self.root_thread_view() {
if view
.read(cx)
.message_editor
@@ -1292,7 +1257,7 @@ impl ConversationView {
};
if should_retry {
- if let Some(active) = self.active_thread() {
+ if let Some(active) = self.root_thread_view() {
active.update(cx, |active, cx| {
active.clear_thread_error(cx);
});
@@ -1345,14 +1310,6 @@ impl ConversationView {
matches!(self.server_state, ServerState::Loading { .. })
}
- fn update_turn_tokens(&mut self, cx: &mut Context<Self>) {
- if let Some(active) = self.active_thread() {
- active.update(cx, |active, cx| {
- active.update_turn_tokens(cx);
- });
- }
- }
-
fn send_queued_message_at_index(
&mut self,
index: usize,
@@ -1360,7 +1317,7 @@ impl ConversationView {
window: &mut Window,
cx: &mut Context<Self>,
) {
- if let Some(active) = self.active_thread() {
+ if let Some(active) = self.root_thread_view() {
active.update(cx, |active, cx| {
active.send_queued_message_at_index(index, is_send_now, window, cx);
});
@@ -1375,7 +1332,7 @@ impl ConversationView {
window: &mut Window,
cx: &mut Context<Self>,
) {
- if let Some(active) = self.active_thread() {
+ if let Some(active) = self.root_thread_view() {
active.update(cx, |active, cx| {
active.move_queued_message_to_main_editor(
index,
@@ -1410,7 +1367,7 @@ impl ConversationView {
AcpThreadEvent::NewEntry => {
let len = thread.read(cx).entries().len();
let index = len - 1;
- if let Some(active) = self.thread_view(&session_id, cx) {
+ if let Some(active) = self.thread_view(&session_id) {
let entry_view_state = active.read(cx).entry_view_state.clone();
let list_state = active.read(cx).list_state.clone();
entry_view_state.update(cx, |view_state, cx| {
@@ -1428,7 +1385,7 @@ impl ConversationView {
}
}
AcpThreadEvent::EntryUpdated(index) => {
- if let Some(active) = self.thread_view(&session_id, cx) {
+ if let Some(active) = self.thread_view(&session_id) {
let entry_view_state = active.read(cx).entry_view_state.clone();
let list_state = active.read(cx).list_state.clone();
entry_view_state.update(cx, |view_state, cx| {
@@ -1441,7 +1398,7 @@ impl ConversationView {
}
}
AcpThreadEvent::EntriesRemoved(range) => {
- if let Some(active) = self.thread_view(&session_id, cx) {
+ if let Some(active) = self.thread_view(&session_id) {
let entry_view_state = active.read(cx).entry_view_state.clone();
let list_state = active.read(cx).list_state.clone();
entry_view_state.update(cx, |view_state, _cx| view_state.remove(range.clone()));
@@ -1459,14 +1416,14 @@ impl ConversationView {
}
AcpThreadEvent::ToolAuthorizationReceived(_) => {}
AcpThreadEvent::Retry(retry) => {
- if let Some(active) = self.thread_view(&session_id, cx) {
+ if let Some(active) = self.thread_view(&session_id) {
active.update(cx, |active, _cx| {
active.thread_retry_status = Some(retry.clone());
});
}
}
AcpThreadEvent::Stopped(stop_reason) => {
- if let Some(active) = self.thread_view(&session_id, cx) {
+ if let Some(active) = self.thread_view(&session_id) {
let is_generating =
matches!(thread.read(cx).status(), ThreadStatus::Generating);
active.update(cx, |active, cx| {
@@ -1501,7 +1458,7 @@ impl ConversationView {
cx,
);
- let should_send_queued = if let Some(active) = self.active_thread() {
+ let should_send_queued = if let Some(active) = self.root_thread_view() {
active.update(cx, |active, cx| {
if active.skip_queue_processing_count > 0 {
active.skip_queue_processing_count -= 1;
@@ -1530,7 +1487,7 @@ impl ConversationView {
}
AcpThreadEvent::Refusal => {
let error = ThreadError::Refusal;
- if let Some(active) = self.thread_view(&session_id, cx) {
+ if let Some(active) = self.thread_view(&session_id) {
active.update(cx, |active, cx| {
active.handle_thread_error(error, cx);
active.thread_retry_status.take();
@@ -1544,7 +1501,7 @@ impl ConversationView {
}
}
AcpThreadEvent::Error => {
- if let Some(active) = self.thread_view(&session_id, cx) {
+ if let Some(active) = self.thread_view(&session_id) {
let is_generating =
matches!(thread.read(cx).status(), ThreadStatus::Generating);
active.update(cx, |active, cx| {
@@ -1567,7 +1524,7 @@ impl ConversationView {
}
}
AcpThreadEvent::LoadError(error) => {
- if let Some(view) = self.active_thread() {
+ if let Some(view) = self.root_thread_view() {
if view
.read(cx)
.message_editor
@@ -1586,7 +1543,7 @@ impl ConversationView {
}
AcpThreadEvent::TitleUpdated => {
if let Some(title) = thread.read(cx).title()
- && let Some(active_thread) = self.thread_view(&session_id, cx)
+ && let Some(active_thread) = self.thread_view(&session_id)
{
let title_editor = active_thread.read(cx).title_editor.clone();
title_editor.update(cx, |editor, cx| {
@@ -1598,7 +1555,7 @@ impl ConversationView {
cx.notify();
}
AcpThreadEvent::PromptCapabilitiesUpdated => {
- if let Some(active) = self.thread_view(&session_id, cx) {
+ if let Some(active) = self.thread_view(&session_id) {
active.update(cx, |active, _cx| {
active
.session_capabilities
@@ -1608,11 +1565,14 @@ impl ConversationView {
}
}
AcpThreadEvent::TokenUsageUpdated => {
- self.update_turn_tokens(cx);
- self.emit_token_limit_telemetry_if_needed(thread, cx);
+ if let Some(active) = self.thread_view(&session_id) {
+ active.update(cx, |active, cx| {
+ active.update_turn_tokens(cx);
+ });
+ }
}
AcpThreadEvent::AvailableCommandsUpdated(available_commands) => {
- if let Some(thread_view) = self.thread_view(&session_id, cx) {
+ if let Some(thread_view) = self.thread_view(&session_id) {
let has_commands = !available_commands.is_empty();
let agent_display_name = self
@@ -1729,7 +1689,7 @@ impl ConversationView {
{
pending_auth_method.take();
}
- if let Some(active) = this.active_thread() {
+ if let Some(active) = this.root_thread_view() {
active.update(cx, |active, cx| {
active.handle_thread_error(err, cx);
})
@@ -1777,7 +1737,7 @@ impl ConversationView {
{
pending_auth_method.take();
}
- if let Some(active) = this.active_thread() {
+ if let Some(active) = this.root_thread_view() {
active.update(cx, |active, cx| active.handle_thread_error(err, cx));
}
} else {
@@ -1983,7 +1943,7 @@ impl ConversationView {
}
pub fn has_user_submitted_prompt(&self, cx: &App) -> bool {
- self.active_thread().is_some_and(|active| {
+ self.root_thread_view().is_some_and(|active| {
active
.read(cx)
.thread
@@ -2109,59 +2069,6 @@ impl ConversationView {
.into_any_element()
}
- fn emit_token_limit_telemetry_if_needed(
- &mut self,
- thread: &Entity<AcpThread>,
- cx: &mut Context<Self>,
- ) {
- let Some(active_thread) = self.active_thread() else {
- return;
- };
-
- let (ratio, agent_telemetry_id, session_id) = {
- let thread_data = thread.read(cx);
- let Some(token_usage) = thread_data.token_usage() else {
- return;
- };
- (
- token_usage.ratio(),
- thread_data.connection().telemetry_id(),
- thread_data.session_id().clone(),
- )
- };
-
- let kind = match ratio {
- acp_thread::TokenUsageRatio::Normal => {
- active_thread.update(cx, |active, _cx| {
- active.last_token_limit_telemetry = None;
- });
- return;
- }
- acp_thread::TokenUsageRatio::Warning => "warning",
- acp_thread::TokenUsageRatio::Exceeded => "exceeded",
- };
-
- let should_skip = active_thread
- .read(cx)
- .last_token_limit_telemetry
- .as_ref()
- .is_some_and(|last| *last >= ratio);
- if should_skip {
- return;
- }
-
- active_thread.update(cx, |active, _cx| {
- active.last_token_limit_telemetry = Some(ratio);
- });
-
- telemetry::event!(
- "Agent Token Limit Warning",
- agent = agent_telemetry_id,
- session_id = session_id,
- kind = kind,
- );
- }
-
fn emit_load_error_telemetry(&self, error: &LoadError) {
let error_kind = match error {
LoadError::Unsupported { .. } => "unsupported",
@@ -2268,18 +2175,20 @@ impl ConversationView {
&self,
cx: &App,
) -> Option<Rc<agent::NativeAgentConnection>> {
- let acp_thread = self.active_thread()?.read(cx).thread.read(cx);
- acp_thread.connection().clone().downcast()
+ self.root_thread(cx)?
+ .read(cx)
+ .connection()
+ .clone()
+ .downcast()
}
pub fn as_native_thread(&self, cx: &App) -> Option<Entity<agent::Thread>> {
- let acp_thread = self.active_thread()?.read(cx).thread.read(cx);
self.as_native_connection(cx)?
- .thread(acp_thread.session_id(), cx)
+ .thread(self.root_session_id.as_ref()?, cx)
}
fn queued_messages_len(&self, cx: &App) -> usize {
- self.active_thread()
+ self.root_thread_view()
.map(|thread| thread.read(cx).local_queued_messages.len())
.unwrap_or_default()
}
@@ -2291,7 +2200,7 @@ impl ConversationView {
tracked_buffers: Vec<Entity<Buffer>>,
cx: &mut Context<Self>,
) -> bool {
- match self.active_thread() {
+ match self.root_thread_view() {
Some(thread) => thread.update(cx, |thread, _cx| {
if index < thread.local_queued_messages.len() {
thread.local_queued_messages[index] = QueuedMessage {
@@ -2308,7 +2217,7 @@ impl ConversationView {
}
fn queued_message_contents(&self, cx: &App) -> Vec<Vec<acp::ContentBlock>> {
- match self.active_thread() {
+ match self.root_thread_view() {
None => Vec::new(),
Some(thread) => thread
.read(cx)
@@ -2320,7 +2229,7 @@ impl ConversationView {
}
fn save_queued_message_at_index(&mut self, index: usize, cx: &mut Context<Self>) {
- let editor = match self.active_thread() {
+ let editor = match self.root_thread_view() {
Some(thread) => thread.read(cx).queued_message_editors.get(index).cloned(),
None => None,
};
@@ -2451,7 +2360,7 @@ impl ConversationView {
});
}
- if let Some(active) = self.active_thread() {
+ if let Some(active) = self.root_thread_view() {
active.update(cx, |active, _cx| {
active.last_synced_queue_length = needed_count;
});
@@ -2545,7 +2454,7 @@ impl ConversationView {
return;
}
- let Some(root_thread) = self.root_thread(cx) else {
+ let Some(root_thread) = self.root_thread_view() else {
return;
};
let root_thread = root_thread.read(cx).thread.read(cx);
@@ -2764,7 +2673,7 @@ impl ConversationView {
// For ACP agents, use the agent name (e.g., "Claude Agent", "Gemini CLI")
// This provides better clarity about what refused the request
if self.as_native_connection(cx).is_some() {
- self.active_thread()
+ self.root_thread_view()
.and_then(|active| active.read(cx).model_selector.clone())
.and_then(|selector| selector.read(cx).active_model(cx))
.map(|model| model.name.clone())
@@ -2783,7 +2692,7 @@ impl ConversationView {
pub(crate) fn reauthenticate(&mut self, window: &mut Window, cx: &mut Context<Self>) {
let agent_id = self.agent.agent_id();
- if let Some(active) = self.active_thread() {
+ if let Some(active) = self.root_thread_view() {
active.update(cx, |active, cx| active.clear_thread_error(cx));
}
let this = cx.weak_entity();
@@ -3927,7 +3836,7 @@ pub(crate) mod tests {
let root_session_id = conversation_view
.read_with(cx, |view, cx| {
- view.root_thread(cx)
+ view.root_thread_view()
.map(|thread| thread.read(cx).thread.read(cx).session_id().clone())
})
.expect("Conversation view should have a root thread");