From 2465269b0511383fe2b4ea014fc01e49d1fa5694 Mon Sep 17 00:00:00 2001 From: Anant Goel Date: Tue, 2 Jun 2026 17:06:20 -0400 Subject: [PATCH] agent_ui: Improve sandbox write-path authorization UI (#58358) Updates the sandbox authorization prompt for terminal write-path requests to show a compact, collapsible list of the requested paths. The list now matches the edited-files disclosure style, sorts paths for stable scanning, and uses existing file icons plus muted parent directories so users can quickly see which locations the command wants to write to. This is a UI-only change; sandbox permission behavior remains unchanged. Release Notes: - Improved agent sandbox permission prompts by showing requested write paths in a clearer file list --------- Co-authored-by: Martin Ye --- .../src/conversation_view/thread_view.rs | 138 +++++++++++++----- 1 file changed, 104 insertions(+), 34 deletions(-) diff --git a/crates/agent_ui/src/conversation_view/thread_view.rs b/crates/agent_ui/src/conversation_view/thread_view.rs index 8f14b0571a..e704c864b2 100644 --- a/crates/agent_ui/src/conversation_view/thread_view.rs +++ b/crates/agent_ui/src/conversation_view/thread_view.rs @@ -19,6 +19,7 @@ use crate::message_editor::SharedSessionCapabilities; use db::kvp::KeyValueStore; use gpui::List; +use gpui::Stateful; use gpui::TaskExt; use heapless::Vec as ArrayVec; use language_model::{ @@ -7419,35 +7420,47 @@ impl ThreadView { let is_open = !self .collapsed_sandbox_authorization_details .contains(tool_call_id); - let paths = details - .write_paths - .iter() - .map(|path| path.display().to_string()) - .collect::>(); + let mut paths = details.write_paths.clone(); + paths.sort(); v_flex() - .p_2() - .gap_1() .border_t_1() .border_color(self.tool_card_border_color(cx)) .child( h_flex() .id(("sandbox-authorization-details-header", entry_ix)) - .gap_1() - .pl_0p5() - .rounded_xs() + .p_1() + .justify_between() + .cursor_pointer() .hover(|style| style.bg(cx.theme().colors().element_hover)) + .child( + h_flex() + .gap_1() + .child( + Label::new("Write access") + .size(LabelSize::Small) + .color(Color::Muted), + ) + .child( + Label::new("•") + .size(LabelSize::XSmall) + .color(Color::Disabled), + ) + .child( + Label::new(format!( + "{} {}", + paths.len(), + if paths.len() == 1 { "path" } else { "paths" } + )) + .size(LabelSize::Small) + .color(Color::Muted), + ), + ) .child( Disclosure::new(("sandbox-authorization-details", entry_ix), is_open) .opened_icon(IconName::ChevronUp) .closed_icon(IconName::ChevronDown), ) - .child( - Label::new("Paths") - .size(LabelSize::XSmall) - .color(Color::Muted) - .buffer_font(cx), - ) .on_click(cx.listener({ let tool_call_id = tool_call_id.clone(); move |this, _event, _window, cx| { @@ -7468,30 +7481,87 @@ impl ThreadView { .when(is_open, |this| { this.child( v_flex() - .gap_0p5() - .pl_5() - .children(paths.into_iter().enumerate().map(|(path_ix, path)| { - h_flex() - .gap_1() - .child(Label::new("•").size(LabelSize::Small).color(Color::Muted)) - .child( - div() - .id(format!( - "sandbox-authorization-path-{entry_ix}-{path_ix}" - )) - .w_full() - .max_w_full() - .overflow_x_scroll() - .child( - Label::new(path).buffer_font(cx).size(LabelSize::Small), - ), - ) + .id(("sandbox-authorization-paths-list", entry_ix)) + .max_h_40() + .overflow_y_scroll() + .children(paths.iter().enumerate().map(|(path_ix, path)| { + self.render_sandbox_authorization_path_row( + entry_ix, + path_ix, + path, + path_ix < paths.len() - 1, + cx, + ) })), ) }) .into_any_element() } + fn render_sandbox_authorization_path_row( + &self, + entry_ix: usize, + path_ix: usize, + path: &Path, + show_border: bool, + cx: &Context, + ) -> Stateful
{ + let display_path = path.display().to_string(); + let file_name = path + .file_name() + .map(|name| name.to_string_lossy().into_owned()) + .unwrap_or_else(|| display_path.clone()); + let parent_path = path.parent().and_then(|parent| { + let parent = parent.display().to_string(); + (!parent.is_empty()).then_some(parent) + }); + let path_icon = FileIcons::get_icon(path, cx) + .map(Icon::from_path) + .map(|icon| icon.color(Color::Muted).size(IconSize::Small)) + .unwrap_or_else(|| { + Icon::new(IconName::Folder) + .color(Color::Muted) + .size(IconSize::Small) + }); + + h_flex() + .id(SharedString::from(format!( + "sandbox-authorization-path-{entry_ix}-{path_ix}" + ))) + .min_w_0() + .p_1p5() + .gap_2() + .bg(cx.theme().colors().editor_background) + .when(show_border, |this| { + this.border_b_1().border_color(cx.theme().colors().border) + }) + .child( + h_flex() + .id(SharedString::from(format!( + "sandbox-authorization-path-name-{entry_ix}-{path_ix}" + ))) + .min_w_0() + .gap_0p5() + .child(path_icon) + .child( + Label::new(file_name) + .size(LabelSize::XSmall) + .buffer_font(cx), + ) + .when_some(parent_path, |this, parent_path| { + this.child( + Label::new(format!(" {parent_path}")) + .color(Color::Muted) + .size(LabelSize::XSmall) + .buffer_font(cx), + ) + }) + .tooltip(move |_window, cx| { + Tooltip::with_meta("Requested write path", None, display_path.clone(), cx) + }), + ) + } + fn render_permission_buttons( &self, session_id: acp::SessionId,