sidebar: Surface subagent permission requests (#53428)
Self-Review Checklist: - [x] I've reviewed my own diff for quality, security, and reliability - [x] Unsafe blocks (if any) have justifying comments - [x] The content is consistent with the [UI/UX checklist](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist) - [x] Tests cover the new/changed behavior - [x] Performance impact has been considered and is acceptable Release Notes: - (preview only) sidebar: Fixed issue where tool confirmation indicator would not show up when subagent asks for permissions
This commit is contained in:
1 parent
77cfe1be5d
commit
844dcfda0f
4 files changed
+127
-9
No files matched your search
@@ -1893,6 +1893,14 @@ impl AgentPanel {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn conversation_views(&self) -> Vec<Entity<ConversationView>> {
|
||||
self.active_conversation_view()
|
||||
.into_iter()
|
||||
.cloned()
|
||||
.chain(self.background_threads.values().cloned())
|
||||
.collect()
|
||||
}
|
||||
|
||||
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()
|
||||
|
||||
@@ -354,6 +354,20 @@ impl ConversationView {
|
||||
.pending_tool_call(id, cx)
|
||||
}
|
||||
|
||||
pub fn root_thread_has_pending_tool_call(&self, cx: &App) -> bool {
|
||||
let Some(root_thread) = self.root_thread(cx) else {
|
||||
return false;
|
||||
};
|
||||
let root_id = root_thread.read(cx).id.clone();
|
||||
self.as_connected().is_some_and(|connected| {
|
||||
connected
|
||||
.conversation
|
||||
.read(cx)
|
||||
.pending_tool_call(&root_id, cx)
|
||||
.is_some()
|
||||
})
|
||||
}
|
||||
|
||||
pub fn root_thread(&self, cx: &App) -> Option<Entity<ThreadView>> {
|
||||
match &self.server_state {
|
||||
ServerState::Connected(connected) => {
|
||||
|
||||
@@ -4107,11 +4107,14 @@ fn all_thread_infos_for_workspace(
|
||||
return None.into_iter().flatten();
|
||||
};
|
||||
let agent_panel = agent_panel.read(cx);
|
||||
|
||||
let threads = agent_panel
|
||||
.parent_threads(cx)
|
||||
.conversation_views()
|
||||
.into_iter()
|
||||
.map(|thread_view| {
|
||||
.filter_map(|conversation_view| {
|
||||
let has_pending_tool_call = conversation_view
|
||||
.read(cx)
|
||||
.root_thread_has_pending_tool_call(cx);
|
||||
let thread_view = conversation_view.read(cx).root_thread(cx)?;
|
||||
let thread_view_ref = thread_view.read(cx);
|
||||
let thread = thread_view_ref.thread.read(cx);
|
||||
|
||||
@@ -4125,7 +4128,7 @@ fn all_thread_infos_for_workspace(
|
||||
let session_id = thread.session_id().clone();
|
||||
let is_background = agent_panel.is_background_thread(&session_id);
|
||||
|
||||
let status = if thread.is_waiting_for_confirmation() {
|
||||
let status = if has_pending_tool_call {
|
||||
AgentThreadStatus::WaitingForConfirmation
|
||||
} else if thread.had_error() {
|
||||
AgentThreadStatus::Error
|
||||
@@ -4138,7 +4141,7 @@ fn all_thread_infos_for_workspace(
|
||||
|
||||
let diff_stats = thread.action_log().read(cx).diff_stats(cx);
|
||||
|
||||
ActiveThreadInfo {
|
||||
Some(ActiveThreadInfo {
|
||||
session_id,
|
||||
title,
|
||||
status,
|
||||
@@ -4147,7 +4150,7 @@ fn all_thread_infos_for_workspace(
|
||||
is_background,
|
||||
is_title_generating,
|
||||
diff_stats,
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
Some(threads).into_iter().flatten()
|
||||
@@ -4310,7 +4313,14 @@ fn dump_single_workspace(workspace: &Workspace, output: &mut String, cx: &gpui::
|
||||
let entry_count = thread.entries().len();
|
||||
write!(output, "Active thread: {title} (session: {session_id})").ok();
|
||||
write!(output, " [{status}, {entry_count} entries").ok();
|
||||
if thread.is_waiting_for_confirmation() {
|
||||
if panel
|
||||
.active_conversation_view()
|
||||
.is_some_and(|conversation_view| {
|
||||
conversation_view
|
||||
.read(cx)
|
||||
.root_thread_has_pending_tool_call(cx)
|
||||
})
|
||||
{
|
||||
write!(output, ", awaiting confirmation").ok();
|
||||
}
|
||||
writeln!(output, "]").ok();
|
||||
@@ -4337,7 +4347,10 @@ fn dump_single_workspace(workspace: &Workspace, output: &mut String, cx: &gpui::
|
||||
let entry_count = thread.entries().len();
|
||||
write!(output, " - {title} (session: {session_id})").ok();
|
||||
write!(output, " [{status}, {entry_count} entries").ok();
|
||||
if thread.is_waiting_for_confirmation() {
|
||||
if conversation_view
|
||||
.read(cx)
|
||||
.root_thread_has_pending_tool_call(cx)
|
||||
{
|
||||
write!(output, ", awaiting confirmation").ok();
|
||||
}
|
||||
writeln!(output, "]").ok();
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use super::*;
|
||||
use acp_thread::StubAgentConnection;
|
||||
use acp_thread::{AcpThread, PermissionOptions, StubAgentConnection};
|
||||
use agent::ThreadStore;
|
||||
use agent_ui::{
|
||||
test_support::{active_session_id, open_thread_with_connection, send_message},
|
||||
@@ -189,6 +189,35 @@ fn focus_sidebar(sidebar: &Entity<Sidebar>, cx: &mut gpui::VisualTestContext) {
|
||||
cx.run_until_parked();
|
||||
}
|
||||
|
||||
fn request_test_tool_authorization(
|
||||
thread: &Entity<AcpThread>,
|
||||
tool_call_id: &str,
|
||||
option_id: &str,
|
||||
cx: &mut gpui::VisualTestContext,
|
||||
) {
|
||||
let tool_call_id = acp::ToolCallId::new(tool_call_id);
|
||||
let label = format!("Tool {tool_call_id}");
|
||||
let option_id = acp::PermissionOptionId::new(option_id);
|
||||
let _authorization_task = cx.update(|_, cx| {
|
||||
thread.update(cx, |thread, cx| {
|
||||
thread
|
||||
.request_tool_call_authorization(
|
||||
acp::ToolCall::new(tool_call_id, label)
|
||||
.kind(acp::ToolKind::Edit)
|
||||
.into(),
|
||||
PermissionOptions::Flat(vec![acp::PermissionOption::new(
|
||||
option_id,
|
||||
"Allow",
|
||||
acp::PermissionOptionKind::AllowOnce,
|
||||
)]),
|
||||
cx,
|
||||
)
|
||||
.unwrap()
|
||||
})
|
||||
});
|
||||
cx.run_until_parked();
|
||||
}
|
||||
|
||||
fn format_linked_worktree_chips(worktrees: &[WorktreeInfo]) -> String {
|
||||
let mut seen = Vec::new();
|
||||
let mut chips = Vec::new();
|
||||
@@ -1284,6 +1313,60 @@ async fn test_parallel_threads_shown_with_live_status(cx: &mut TestAppContext) {
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_subagent_permission_request_marks_parent_sidebar_thread_waiting(
|
||||
cx: &mut TestAppContext,
|
||||
) {
|
||||
let project = init_test_project_with_agent_panel("/my-project", cx).await;
|
||||
let (multi_workspace, cx) =
|
||||
cx.add_window_view(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
|
||||
let (sidebar, panel) = setup_sidebar_with_agent_panel(&multi_workspace, cx);
|
||||
|
||||
let connection = StubAgentConnection::new().with_supports_load_session(true);
|
||||
connection.set_next_prompt_updates(vec![acp::SessionUpdate::AgentMessageChunk(
|
||||
acp::ContentChunk::new("Done".into()),
|
||||
)]);
|
||||
open_thread_with_connection(&panel, connection, cx);
|
||||
send_message(&panel, cx);
|
||||
|
||||
let parent_session_id = active_session_id(&panel, cx);
|
||||
save_test_thread_metadata(&parent_session_id, &project, cx).await;
|
||||
|
||||
let subagent_session_id = acp::SessionId::new("subagent-session");
|
||||
cx.update(|_, cx| {
|
||||
let parent_thread = panel.read(cx).active_agent_thread(cx).unwrap();
|
||||
parent_thread.update(cx, |thread: &mut AcpThread, cx| {
|
||||
thread.subagent_spawned(subagent_session_id.clone(), cx);
|
||||
});
|
||||
});
|
||||
cx.run_until_parked();
|
||||
|
||||
let subagent_thread = panel.read_with(cx, |panel, cx| {
|
||||
panel
|
||||
.active_conversation_view()
|
||||
.and_then(|conversation| conversation.read(cx).thread_view(&subagent_session_id))
|
||||
.map(|thread_view| thread_view.read(cx).thread.clone())
|
||||
.expect("Expected subagent thread to be loaded into the conversation")
|
||||
});
|
||||
request_test_tool_authorization(&subagent_thread, "subagent-tool-call", "allow-subagent", cx);
|
||||
|
||||
let parent_status = sidebar.read_with(cx, |sidebar, _cx| {
|
||||
sidebar
|
||||
.contents
|
||||
.entries
|
||||
.iter()
|
||||
.find_map(|entry| match entry {
|
||||
ListEntry::Thread(thread) if thread.metadata.session_id == parent_session_id => {
|
||||
Some(thread.status)
|
||||
}
|
||||
_ => None,
|
||||
})
|
||||
.expect("Expected parent thread entry in sidebar")
|
||||
});
|
||||
|
||||
assert_eq!(parent_status, AgentThreadStatus::WaitingForConfirmation);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_background_thread_completion_triggers_notification(cx: &mut TestAppContext) {
|
||||
let project_a = init_test_project_with_agent_panel("/project-a", cx).await;
|
||||
|
||||
Reference in new issue
Block a user