Cameron Mcloughlinagent: Re-enable sandbox (#61711)12a19dc3 months agoHistory
12960 lines · 519.6 KB · Rust
RawDownload
use crate::{    DEFAULT_THREAD_TITLE, SelectPermissionGranularity,    agent_configuration::configure_context_server_modal::default_markdown_style,    conversation_view::thread_search_bar::{ThreadSearchBar, ThreadSearchBarEvent},    open_abs_path_at_point,    thread_metadata_store::{ThreadId, ThreadMetadataStore},};use agent_client_protocol::schema::v1 as acp;use std::cell::RefCell; use acp_thread::{    Elicitation, ElicitationEntryId, ElicitationStatus, PlanEntry, SandboxAuthorizationDetails,    SandboxFallbackAuthorizationDetails, SandboxNotAppliedReason, decode_path_escapes,};use agent::{    SandboxStatusKey, SandboxStatusRefresh, SkillLoadingIssue, SkillLoadingIssueKind,    SkillLoadingIssuesUpdated, ThreadSandbox, VerifiedSandboxStatus,};use agent_settings::UserAgentsMd;use agent_skills::MAX_SKILL_DESCRIPTION_LEN;use cloud_api_types::{SubmitAgentThreadFeedbackBody, SubmitAgentThreadFeedbackCommentsBody};use editor::actions::OpenExcerpts;use sandbox::{SandboxFsPolicy, SandboxNetPolicy, SandboxPolicy}; use crate::completion_provider::{AvailableSkill, PromptLocalCommand, pluralize};use crate::message_editor::SharedSessionCapabilities;use crate::ui::{    SandboxGroup, SandboxRow, SandboxSection, SandboxStatusTooltip, TerminalSandboxWarning,    TerminalToolHeader,};use crate::unicode_confusables; use db::kvp::KeyValueStore;use gpui::List;use gpui::Stateful;use gpui::TaskExt;use heapless::Vec as ArrayVec;use language_model::{    FastModeConfirmation, LanguageModel, LanguageModelEffortLevel, LanguageModelId,    LanguageModelProvider, LanguageModelProviderId, LanguageModelRegistry, Speed,};use notifications::status_toast::StatusToast;use settings::{update_settings_file, update_settings_file_with_completion};use ui::{    ButtonLike, CalloutBorderPosition, Checkbox, SpinnerLabel, SpinnerVariant, SplitButton,    SplitButtonStyle, Tab, ToggleState,};use util::markdown::{source_position_from_fragment, split_local_url_fragment};use workspace::{OpenOptions, SERIALIZATION_THROTTLE_TIME}; use super::elicitation::{    ElicitationCard, ElicitationCardHandlers, ElicitationFormState, should_render_elicitation,};use super::*; const DATA_RETENTION_LEARN_MORE_URL: &str = "https://support.claude.com/en/articles/15425996-data-retention-practices-for-mythos-class-models"; #[derive(Default)]struct ThreadFeedbackState {    feedback: Option<ThreadFeedback>,    comments_editor: Option<Entity<Editor>>,} impl ThreadFeedbackState {    pub fn submit(        &mut self,        thread: Entity<AcpThread>,        feedback: ThreadFeedback,        window: &mut Window,        cx: &mut App,    ) {        let Some(telemetry) = thread.read(cx).connection().telemetry() else {            return;        };         let project = thread.read(cx).project().read(cx);        let client = project.client();        let user_store = project.user_store();        let organization = user_store.read(cx).current_organization();         if self.feedback == Some(feedback) {            return;        }         self.feedback = Some(feedback);        match feedback {            ThreadFeedback::Positive => {                self.comments_editor = None;            }            ThreadFeedback::Negative => {                self.comments_editor = Some(Self::build_feedback_comments_editor(window, cx));            }        }        let session_id = thread.read(cx).session_id().clone();        let parent_session_id = thread.read(cx).parent_session_id().cloned();        let agent_telemetry_id = thread.read(cx).connection().telemetry_id();        let task = telemetry.thread_data(&session_id, cx);        let rating = match feedback {            ThreadFeedback::Positive => "positive",            ThreadFeedback::Negative => "negative",        };        cx.background_spawn(async move {            let thread = task.await?;             client                .cloud_client()                .submit_agent_feedback(SubmitAgentThreadFeedbackBody {                    organization_id: organization.map(|organization| organization.id.clone()),                    agent: agent_telemetry_id.to_string(),                    session_id: session_id.to_string(),                    parent_session_id: parent_session_id.map(|id| id.to_string()),                    rating: rating.to_string(),                    thread,                })                .await?;             anyhow::Ok(())        })        .detach_and_log_err(cx);    }     pub fn submit_comments(&mut self, thread: Entity<AcpThread>, cx: &mut App) {        let Some(telemetry) = thread.read(cx).connection().telemetry() else {            return;        };         let Some(comments) = self            .comments_editor            .as_ref()            .map(|editor| editor.read(cx).text(cx))            .filter(|text| !text.trim().is_empty())        else {            return;        };         self.comments_editor.take();         let project = thread.read(cx).project().read(cx);        let client = project.client();        let user_store = project.user_store();        let organization = user_store.read(cx).current_organization();         let session_id = thread.read(cx).session_id().clone();        let agent_telemetry_id = thread.read(cx).connection().telemetry_id();        let task = telemetry.thread_data(&session_id, cx);        cx.background_spawn(async move {            let thread = task.await?;             client                .cloud_client()                .submit_agent_feedback_comments(SubmitAgentThreadFeedbackCommentsBody {                    organization_id: organization.map(|organization| organization.id.clone()),                    agent: agent_telemetry_id.to_string(),                    session_id: session_id.to_string(),                    comments,                    thread,                })                .await?;             anyhow::Ok(())        })        .detach_and_log_err(cx);    }     pub fn clear(&mut self) {        *self = Self::default()    }     pub fn dismiss_comments(&mut self) {        self.comments_editor.take();    }     fn build_feedback_comments_editor(window: &mut Window, cx: &mut App) -> Entity<Editor> {        let buffer = cx.new(|cx| {            let empty_string = String::new();            MultiBuffer::singleton(cx.new(|cx| Buffer::local(empty_string, cx)), cx)        });         let editor = cx.new(|cx| {            let mut editor = Editor::new(                editor::EditorMode::AutoHeight {                    min_lines: 1,                    max_lines: Some(4),                },                buffer,                None,                window,                cx,            );            editor.set_placeholder_text(                "What went wrong? Share your feedback so we can improve.",                window,                cx,            );            editor        });         editor.read(cx).focus_handle(cx).focus(window, cx);        editor    }} struct GeneratingSpinner {    variant: SpinnerVariant,} impl GeneratingSpinner {    fn new(variant: SpinnerVariant) -> Self {        Self { variant }    }} impl Render for GeneratingSpinner {    fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {        SpinnerLabel::with_variant(self.variant).size(LabelSize::Small)    }} #[derive(IntoElement)]struct GeneratingSpinnerElement {    variant: SpinnerVariant,} impl GeneratingSpinnerElement {    fn new(variant: SpinnerVariant) -> Self {        Self { variant }    }} impl RenderOnce for GeneratingSpinnerElement {    fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {        let id = match self.variant {            SpinnerVariant::Dots => "generating-spinner-view",            SpinnerVariant::Sand => "confirmation-spinner-view",            _ => "spinner-view",        };        window.with_id(id, |window| {            window.use_state(cx, |_, _| GeneratingSpinner::new(self.variant))        })    }} pub enum AcpThreadViewEvent {    Interacted,} impl EventEmitter<AcpThreadViewEvent> for ThreadView {} /// `cat -n`-style numbered code block, already stripped of its line-number/// prefixes and ready to render. Line numbers are guaranteed to be contiguous/// starting at `first_number`, so we only store the first number and the line/// count rather than allocating a per-line `Vec`.struct ParsedCatNumberedCode {    code: String,    first_number: u32,    line_count: usize,} fn parse_cat_numbered_markdown_code_block(markdown: &str) -> Option<ParsedCatNumberedCode> {    let (_tag, code) = parse_single_fenced_code_block(markdown)?;    parse_cat_numbered_code(code)} fn parse_single_fenced_code_block(markdown: &str) -> Option<(&str, &str)> {    let first_non_backtick = markdown.find(|character| character != '`')?;    if first_non_backtick < 3 {        return None;    }     let fence = &markdown[..first_non_backtick];    let after_opening_fence = &markdown[first_non_backtick..];    let tag_end = after_opening_fence.find('\n')?;    let tag = &after_opening_fence[..tag_end];    let after_tag = &after_opening_fence[tag_end + 1..];    let closing_fence = format!("\n{fence}\n");    let code = after_tag.strip_suffix(&closing_fence)?;    Some((tag, code))} /// Walks `code` exactly once: for each line it validates and strips the/// `NNN\t` prefix, then pushes the line's content into the accumulating/// code buffer (with `\n` between lines, no trailing newline). Verifies that/// the line numbers form a contiguous, increasing sequence.fn parse_cat_numbered_code(code: &str) -> Option<ParsedCatNumberedCode> {    if code.is_empty() {        return None;    }     let mut output = String::with_capacity(code.len());    let mut first_number = None;    let mut expected_number = None;    let mut line_count: usize = 0;    for raw_line in code.split_inclusive('\n') {        let line = strip_line_ending(raw_line);        let (number, text) = parse_cat_numbered_line(line)?;        if let Some(expected) = expected_number {            if number != expected {                return None;            }        } else {            first_number = Some(number);        }        expected_number = number.checked_add(1);        if line_count > 0 {            output.push('\n');        }        output.push_str(text);        line_count += 1;    }     Some(ParsedCatNumberedCode {        code: output,        first_number: first_number?,        line_count,    })} fn strip_line_ending(line: &str) -> &str {    let without_lf = line.strip_suffix('\n').unwrap_or(line);    without_lf.strip_suffix('\r').unwrap_or(without_lf)} fn parse_cat_numbered_line(line: &str) -> Option<(u32, &str)> {    let (prefix, text) = line.split_once('\t')?;    let number = prefix.trim();    if number.is_empty()        || !prefix            .chars()            .all(|character| character == ' ' || character.is_ascii_digit())    {        return None;    }     Some((number.parse().ok()?, text))} fn render_cat_numbered_code_block(    parsed: ParsedCatNumberedCode,    language: Option<Arc<Language>>,    markdown_style: MarkdownStyle,    copy_button_id: String,    cx: &App,) -> AnyElement {    use std::fmt::Write as _;     let ParsedCatNumberedCode {        code,        first_number,        line_count,    } = parsed;     // Line numbers are contiguous (verified during parsing), so the largest    // line number is `first_number + line_count - 1`. Sizing the gutter to    // that number's digit count means every rendered line contributes exactly    // `gutter_width` bytes to the gutter, plus a newline between adjacent    // lines.    let last_number = first_number        .saturating_add(u32::try_from(line_count.saturating_sub(1)).unwrap_or(u32::MAX));    let gutter_width = last_number.to_string().len().max(1);    let gutter_capacity = line_count * gutter_width + line_count.saturating_sub(1);     let mut gutter = String::with_capacity(gutter_capacity);    for i in 0..line_count {        if i > 0 {            gutter.push('\n');        }        let line_number = first_number.saturating_add(u32::try_from(i).unwrap_or(u32::MAX));        // Writes to a `String` are infallible, so the `Result` can be ignored.        let _ = write!(&mut gutter, "{line_number:>gutter_width$}");    }     let mut code_text_style = markdown_style.base_text_style.clone();    code_text_style.refine(&markdown_style.code_block.text);     let mut gutter_text_style = code_text_style.clone();    gutter_text_style.color = cx.theme().colors().text_muted;     let gutter_len = gutter.len();    let gutter = StyledText::new(gutter).with_runs(vec![gutter_text_style.to_run(gutter_len)]);     // Share `code` between syntax highlighting, the rendered `StyledText`, and    // the copy button via a single `SharedString` (cheap `Arc` clones) instead    // of cloning the underlying `String`.    let code: SharedString = code.into();    let code_runs = highlight_code_runs(&code, language.as_ref(), code_text_style, &markdown_style);    let code_text = StyledText::new(code.clone()).with_runs(code_runs);     let code_block_id = format!("read-file-code-block-{copy_button_id}");    let code_scroll_id = format!("read-file-code-scroll-{copy_button_id}");    let mut container = div()        .id(code_block_id)        .group("read-file-code-block")        .relative()        .w_full()        .whitespace_nowrap();    container.style().refine(&markdown_style.code_block);     // `overflow_x_scroll` only actually scrolls when the container is laid out    // as a flex container: in GPUI the default `Display` is `Block`, and a    // block-level child fills its parent's content width instead of overflowing    // it, so there is nothing for the scroll viewport to scroll. Using `flex()`    // on the scroll wrapper plus `flex_none()` on the inner item lets the inner    // item take its natural width (the unwrapped code), which is what overflows.    // `restrict_scroll_to_axis` then keeps vertical wheel events flowing through    // to the outer thread scroller. This mirrors the standard markdown    // code-block path in `crates/markdown/src/markdown.rs`.    let mut code_scroll = div()        .id(code_scroll_id)        .flex()        .flex_1()        .min_w_0()        .overflow_x_scroll()        .child(div().flex_none().child(code_text));    code_scroll.style().restrict_scroll_to_axis = Some(true);     container        .child(            h_flex()                .items_start()                .min_w_0()                .w_full()                .child(div().flex_none().pr_3().child(gutter))                .child(code_scroll),        )        .child(            h_flex()                .w_4()                .absolute()                .top_0()                .right_0()                .justify_end()                .visible_on_hover("read-file-code-block")                .child(CopyButton::new(copy_button_id, code).tooltip_label("Copy Code")),        )        .into_any_element()} fn highlight_code_runs(    code: &str,    language: Option<&Arc<Language>>,    code_text_style: TextStyle,    markdown_style: &MarkdownStyle,) -> Vec<TextRun> {    if code.is_empty() {        return Vec::new();    }     let Some(language) = language else {        return vec![code_text_style.to_run(code.len())];    };     let mut runs = Vec::new();    let mut offset = 0;    for (range, highlight_id) in language.highlight_text(&Rope::from(code), 0..code.len()) {        if range.start > offset {            runs.push(code_text_style.to_run(range.start - offset));        }         let mut run_style = code_text_style.clone();        if let Some(highlight) = markdown_style.syntax.get(highlight_id).cloned() {            run_style = run_style.highlight(highlight);        }        runs.push(run_style.to_run(range.len()));        offset = range.end;    }     if offset < code.len() {        runs.push(code_text_style.to_run(code.len() - offset));    }     runs} #[cfg(test)]mod numbered_code_block_tests {    use super::*;     #[test]    fn parses_cat_numbered_markdown_code_block() {        let parsed = parse_cat_numbered_markdown_code_block(            "```rs zed/crates/example.rs\n     2\tfn main() {\n     3\t    println!(\"hi\");\n     4\t}\n```\n",        )        .expect("cat-numbered block should parse");         assert_eq!(parsed.line_count, 3);        assert_eq!(parsed.first_number, 2);        assert_eq!(parsed.code, "fn main() {\n    println!(\"hi\");\n}");    }     #[test]    fn parses_cat_numbered_code_with_crlf_line_endings() {        let parsed = parse_cat_numbered_code("     1\tline one\r\n     2\tline two\r\n")            .expect("crlf-terminated cat-numbered code should parse");         assert_eq!(parsed.line_count, 2);        assert_eq!(parsed.first_number, 1);        assert_eq!(parsed.code, "line one\nline two");    }     #[test]

Lines 1–500 of 12960.