@@ -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;