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 <martin@zed.dev>
This commit is contained in:
Anant GoelandMartin Ye authored and GitHub committed 2026-06-02 21:06:20 +00:00
1 parent b9dfcae0f9
commit 2465269b05
1 file changed
+104 -34
@@ -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::<Vec<_>>();
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<Self>,
) -> Stateful<Div> {
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,