agent_ui: Display an activity bar for subagents waiting for permission (#52460)
Closes https://github.com/zed-industries/zed/issues/52346 Given the parallel nature of subagents calls, it's possible that there is a subagent way out of view that's waiting for the user to give permissions. Right now, it's kind of hard to know this and you may think something wrong is happening given the thread generation isn't making any progress. This PR adds an "activity bar" to the thread view that displays subagents on a "waiting for confirmation" status. We display the subagent's summary label as well as allow clicking on it to quickly scrolling to that subagent. <img width="500" height="1100" alt="Screenshot 2026-03-25 at 10 09@2x" src="https://github.com/user-attachments/assets/17d2d398-25fc-4660-85c2-bf66ec98c1f1" /> Release Notes: - Agent: Improved the experience of interacting with subagents waiting for confirmation.
This commit is contained in:
1 parent
23fd2490ef
commit
6a2baed5ae
2 files changed
+143
-1
No files matched your search
@@ -237,6 +237,20 @@ impl Conversation {
|
||||
))
|
||||
}
|
||||
|
||||
pub fn subagents_awaiting_permission(&self, cx: &App) -> Vec<(acp::SessionId, usize)> {
|
||||
self.permission_requests
|
||||
.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() {
|
||||
Some((session_id.clone(), tool_call_ids.len()))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn authorize_pending_tool_call(
|
||||
&mut self,
|
||||
session_id: &acp::SessionId,
|
||||
|
||||
@@ -2155,7 +2155,14 @@ impl ThreadView {
|
||||
let plan = thread.plan();
|
||||
let queue_is_empty = !self.has_queued_messages();
|
||||
|
||||
if changed_buffers.is_empty() && plan.is_empty() && queue_is_empty {
|
||||
let subagents_awaiting_permission = self.render_subagents_awaiting_permission(cx);
|
||||
let has_subagents_awaiting = subagents_awaiting_permission.is_some();
|
||||
|
||||
if changed_buffers.is_empty()
|
||||
&& plan.is_empty()
|
||||
&& queue_is_empty
|
||||
&& !has_subagents_awaiting
|
||||
{
|
||||
return None;
|
||||
}
|
||||
|
||||
@@ -2183,6 +2190,14 @@ impl ThreadView {
|
||||
blur_radius: px(2.),
|
||||
spread_radius: px(0.),
|
||||
}])
|
||||
.when_some(subagents_awaiting_permission, |this, element| {
|
||||
this.child(element)
|
||||
})
|
||||
.when(
|
||||
has_subagents_awaiting
|
||||
&& (!plan.is_empty() || !changed_buffers.is_empty() || !queue_is_empty),
|
||||
|this| this.child(Divider::horizontal().color(DividerColor::Border)),
|
||||
)
|
||||
.when(!plan.is_empty(), |this| {
|
||||
this.child(self.render_plan_summary(plan, window, cx))
|
||||
.when(plan_expanded, |parent| {
|
||||
@@ -2442,6 +2457,119 @@ impl ThreadView {
|
||||
)
|
||||
}
|
||||
|
||||
fn render_subagents_awaiting_permission(&self, cx: &Context<Self>) -> Option<AnyElement> {
|
||||
let awaiting = self.conversation.read(cx).subagents_awaiting_permission(cx);
|
||||
|
||||
if awaiting.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let thread = self.thread.read(cx);
|
||||
let entries = thread.entries();
|
||||
let mut subagent_items: Vec<(SharedString, usize)> = Vec::new();
|
||||
|
||||
for (session_id, _) in &awaiting {
|
||||
for (entry_ix, entry) in entries.iter().enumerate() {
|
||||
if let AgentThreadEntry::ToolCall(tool_call) = entry {
|
||||
if let Some(info) = &tool_call.subagent_session_info {
|
||||
if &info.session_id == session_id {
|
||||
let subagent_summary: SharedString = {
|
||||
let summary_text = tool_call.label.read(cx).source().to_string();
|
||||
if !summary_text.is_empty() {
|
||||
summary_text.into()
|
||||
} else {
|
||||
"Subagent".into()
|
||||
}
|
||||
};
|
||||
subagent_items.push((subagent_summary, entry_ix));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if subagent_items.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let item_count = subagent_items.len();
|
||||
|
||||
Some(
|
||||
v_flex()
|
||||
.child(
|
||||
h_flex()
|
||||
.py_1()
|
||||
.px_2()
|
||||
.w_full()
|
||||
.gap_1()
|
||||
.border_b_1()
|
||||
.border_color(cx.theme().colors().border)
|
||||
.child(
|
||||
Label::new("Subagents Awaiting Permission:")
|
||||
.size(LabelSize::Small)
|
||||
.color(Color::Muted),
|
||||
)
|
||||
.child(Label::new(item_count.to_string()).size(LabelSize::Small)),
|
||||
)
|
||||
.child(
|
||||
v_flex().children(subagent_items.into_iter().enumerate().map(
|
||||
|(ix, (label, entry_ix))| {
|
||||
let is_last = ix == item_count - 1;
|
||||
let group = format!("group-{}", entry_ix);
|
||||
|
||||
h_flex()
|
||||
.cursor_pointer()
|
||||
.id(format!("subagent-permission-{}", entry_ix))
|
||||
.group(&group)
|
||||
.p_1()
|
||||
.pl_2()
|
||||
.min_w_0()
|
||||
.w_full()
|
||||
.gap_1()
|
||||
.justify_between()
|
||||
.bg(cx.theme().colors().editor_background)
|
||||
.hover(|s| s.bg(cx.theme().colors().element_hover))
|
||||
.when(!is_last, |this| {
|
||||
this.border_b_1().border_color(cx.theme().colors().border)
|
||||
})
|
||||
.child(
|
||||
h_flex()
|
||||
.gap_1p5()
|
||||
.child(
|
||||
Icon::new(IconName::Circle)
|
||||
.size(IconSize::XSmall)
|
||||
.color(Color::Warning),
|
||||
)
|
||||
.child(
|
||||
Label::new(label)
|
||||
.size(LabelSize::Small)
|
||||
.color(Color::Muted)
|
||||
.truncate(),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
div().visible_on_hover(&group).child(
|
||||
Label::new("Scroll to Subagent")
|
||||
.size(LabelSize::Small)
|
||||
.color(Color::Muted)
|
||||
.truncate(),
|
||||
),
|
||||
)
|
||||
.on_click(cx.listener(move |this, _, _, cx| {
|
||||
this.list_state.scroll_to(ListOffset {
|
||||
item_ix: entry_ix,
|
||||
offset_in_item: px(0.0),
|
||||
});
|
||||
cx.notify();
|
||||
}))
|
||||
},
|
||||
)),
|
||||
)
|
||||
.into_any(),
|
||||
)
|
||||
}
|
||||
|
||||
fn render_message_queue_summary(
|
||||
&self,
|
||||
_window: &mut Window,
|
||||
|
||||
Reference in new issue
Block a user